<?php //$Id$
//Copyright (c) 2013-2016 Pierre Pronchery <khorben@defora.org>
//This file is part of DeforaOS Web DaPortal
//
//This program is free software: you can redistribute it and/or modify
//it under the terms of the GNU General Public License as published by
//the Free Software Foundation, version 3 of the License.
//
//This program is distributed in the hope that it will be useful,
//but WITHOUT ANY WARRANTY; without even the implied warranty of
//MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//GNU General Public License for more details.
//
//You should have received a copy of the GNU General Public License
//along with this program.  If not, see <http://www.gnu.org/licenses/>.
//FIXME:
//- warn when inserting a page with a title that already exists



//WikiContent
class WikiContent extends Content
{
	//public
	//methods
	//essential
	//WikiContent::WikiContent
	public function __construct(Engine $engine, Module $module,
			$properties = FALSE)
	{
		$this->fields['message'] = 'log message';
		//let wiki pages always be public
		$this->setPublic(TRUE);
		parent::__construct($engine, $module, $properties);
		$this->text_content_by = _('Wiki page by');
		$this->text_more_content = _('More wiki pages...');
		$this->text_submit_content = _('Create a page');
	}


	//accessors
	//WikiContent::canSubmit
	public function canSubmit(Engine $engine, Request $request = NULL,
			&$error = FALSE)
	{
		if(parent::canSubmit($engine, $request, $error) === FALSE)
			return FALSE;
		//verify the title
		$error = _('The title must be set and not empty');
		if(($title = $this->getTitle()) === FALSE
				|| strlen($title) == 0)
			return FALSE;
		$error = _('The title must not contain slash characters');
		if(strpos($title, '/') !== FALSE
				|| strpos($title, '\\') !== FALSE)
			return FALSE;
		return TRUE;
	}


	//WikiContent::canUpdate
	public function canUpdate(Engine $engine, Request $request = NULL,
			&$error = FALSE)
	{
		$credentials = $engine->getCredentials();

		$error = _('You need to be logged in to update wiki pages');
		if($credentials->getUserID() == 0)
			return FALSE;
		//verify the request
		$error = _('The request expired or is invalid');
		if($request !== NULL && $request->isIdempotent())
			return FALSE;
		//verify the title
		$title = ($request !== NULL) ? $request->get('title') : FALSE;
		if($title === FALSE)
			$title = $this->getTitle();
		//it is forbidden to change the title
		$error = _('The title is not allowed to change');
		if($title != $this->getTitle())
			return FALSE;
		$error = _('The title must not contain slash characters');
		if(strpos($title, '/') !== FALSE
				|| strpos($title, '\\') !== FALSE)
			return FALSE;
		return TRUE;
	}


	//WikiContent::getContent
	public function getContent(Engine $engine)
	{
		return $this->getMarkup($engine);
	}


	//WikiContent::setContent
	public function setContent(Engine $engine, $content)
	{
		parent::setContent($engine,
				HTML::filter($engine, $content, array()));
		$this->markup = HTML::filter($engine, $content);
	}


	//useful
	//WikiContent::display
	public function display(Engine $engine, Request $request = NULL)
	{
		$type = ($request !== NULL) ? $request->get('display') : FALSE;
		$types = array('revisions');

		//allow more content types to be explicitly displayed
		if(in_array($type, $types))
			return $this->displayContent($engine, $request);
		return parent::display($engine, $request);
	}


	//WikiContent::displayContent
	public function displayContent(Engine $engine, Request $request)
	{
		$type = $request->get('display');
		$revision = $request->get('revision');
		$diff = $request->get('diff');

		$vbox = new PageElement('vbox');
		if($type === FALSE || $type == 'content')
			$vbox->append('htmlview', array(
					'text' => $this->getMarkup($engine,
						$request)));
		if($this->getID() !== FALSE)
		{
			$stock = $this->getModule()->getName();
			$title = _('Revisions');
			$revisions = $this->_contentRevisions($engine,
					$request);
			if($type === FALSE)
			{
				$expander = $vbox->append('expander',
						array('title' => $title));
				$expander->append($revisions);
			}
			else if($type == 'revisions')
			{
				$container = $vbox->append('title', array(
						'class' => 'revisions',
						'stock' => $stock,
						'text' => $title));
				$vbox->append($revisions);
			}
		}
		return $vbox;
	}

	protected function _contentRevisions(Engine $engine, Request $request)
	{
		$module = $this->getModule()->getName();
		$title = $this->getTitle();
		$error = _('Could not list revisions');

		if(($root = static::getRoot($module)) === FALSE
				|| strpos($title, '/') !== FALSE)
			return new PageElement('dialog', array(
				'type' => 'error', 'text' => $error));
		//obtain the revision list
		$cmd = 'rlog';
		$cmd .= ' '.escapeshellarg($root.'/'.$title);
		exec($cmd, $rcs, $res);
		if($res != 0)
			return new PageElement('dialog', array(
				'type' => 'error', 'text' => $error));
		for($i = 0, $cnt = count($rcs); $i < $cnt;)
			if($rcs[$i++] == '----------------------------')
				break;
		$columns = array('title' => _('Name'), 'date' => _('Date'),
				'username' => _('Author'),
				'message' => _('Message'));
		$view = new PageElement('treeview', array(
			'class' => 'revisions', 'columns' => $columns));
		$lsp = '======================================================';
		$ssp = '----------------------------';
		for(; $i < $cnt - 2; $i += 3)
		{
			$row = $view->append('row');
			//name
			$revision = substr($rcs[$i], 9);
			$r = $this->getRequest(FALSE, array(
					'revision' => $revision));
			$name = new PageElement('label');
			$name->append('link', array('request' => $r,
					'text' => $revision));
			if($i < $cnt - 5)
			{
				//FIXME revision1 may be wrong
				$revision1 = substr($rcs[$i + 4], 9);
				$r = $this->getRequest(FALSE, array(
						'diff' => '',
						'r1' => $revision1,
						'r2' => $revision));
				$name->append('label', array('text' => ' ('));
				$name->append('link', array('request' => $r,
						'text' => 'diff'));
				$name->append('label', array('text' => ')'));
			}
			$row->set('title', $name);
			//date
			$date = substr($rcs[$i + 1], 6, 19);
			$row->set('date', $date);
			//username
			$username = substr($rcs[$i + 1], 36);
			$username = substr($username, 0, strspn($username,
					'abcdefghijklmnopqrstuvwxyz'
					.'ABCDEFGHIJKLMNOPQRSTUV'
					.'WXYZ0123456789'));
			if(($user = User::lookup($engine, $username)) !== FALSE)
			{
				$r = new Request('user', FALSE,
					$user->getUserID(),
					$user->getUsername());
				$username = new PageElement('link', array(
						'request' => $r,
						'stock' => 'user',
						'text' => $username));
			}
			$row->set('username', $username);
			//message
			$message = $rcs[$i + 2];
			if($message == $ssp || strncmp($message, $lsp,
					strlen($lsp)) == 0)
				$message = '';
			else
			{
				$apnd = '';
				for($i++; $i < $cnt && $rcs[$i + 2] != $ssp
					&& strncmp($rcs[$i + 2], $lsp,
						strlen($lsp)) != 0; $i++)
						$apnd = '...';
				$message .= $apnd;
			}
			$row->set('message', $message);
		}
		return $view;
	}


	//WikiContent::form
	public function form(Engine $engine, Request $request)
	{
		return parent::form($engine, $request);
	}

	public function _formSubmit(Engine $engine, Request $request)
	{
		$vbox = new PageElement('vbox');
		$vbox->append('entry', array('name' => 'title',
				'text' => _('Title: '),
				'value' => $request->get('title')));
		$vbox->append('htmledit', array('name' => 'content',
				'value' => $request->get('content')));
		$vbox->append('entry', array('text' => _('Log message: '),
				'name' => 'message',
				'value' => $request->get('message')));
		return $vbox;
	}

	public function _formUpdate(Engine $engine, Request $request)
	{
		$vbox = new PageElement('vbox');

		if(($value = $request->get('content')) === FALSE)
			$value = $this->getMarkup($engine);
		$vbox->append('htmledit', array('name' => 'content',
				'value' => $value));
		$value = $request->get('message');
		$vbox->append('entry', array('text' => _('Log message: '),
				'name' => 'message',
				'value' => $value));
		return $vbox;
	}


	//WikiContent::previewContent
	public function previewContent(Engine $engine, Request $request = NULL)
	{
		//XXX use the cache from the database instead
		$content = HTML::filter($engine, $this->getContent($engine),
				array());
		$length = $this->preview_length;
		$text = ($length <= 0 || strlen($content) < $length)
			? $content : substr($content, 0, $length).'...';

		return new PageElement('label', array('text' => $text));
	}


	//WikiContent::save
	public function save(Engine $engine, Request $request = NULL,
			&$error = FALSE)
	{
		return parent::save($engine, $request, $error);
	}

	protected function _saveInsert(Engine $engine, Request $request = NULL,
			&$error)
	{
		$module = $this->getModule()->getName();
		$cred = $engine->getCredentials();
		$username = $cred->getUsername();
		$content = $request->get('content');

		$error = _('Could not find the wiki repository');
		if(($root = static::getRoot($module)) === FALSE)
			return FALSE;
		//XXX check first if this title already exists for this module
		$file = $root.'/'.$this->getTitle();
		$error = _('A wiki page already exists with this name');
		if(file_exists($file.',v'))
			return FALSE;
		//translate the content
		//XXX remains even in case of failure
		$this->setContent($engine, $request->get('content'));
		//insert the content
		if(parent::_saveInsert($engine, $request, $error) === FALSE)
			return FALSE;
		$error = _('Could not create the wiki page');
		if(($fp = fopen($file, 'x')) === FALSE)
			return FALSE;
		$message = $request->get('message');
		$emessage = ($message !== FALSE && strlen($message) > 0)
			? ' -m'.escapeshellarg($message) : '';
		$eusername = escapeshellarg($username);
		$efile = escapeshellarg($file);
		$cmd = 'ci -q '.$emessage.' -w'.$eusername.' '.$efile;
		$res = -1;
		if(fwrite($fp, $this->markup) !== FALSE)
		{
			$error = _('Could not write the wiki page');
			if(fclose($fp) !== FALSE)
				exec($cmd, $rcs, $res);
		}
		else
			fclose($fp);
		if(file_exists($file))
			unlink($file);
		if(($ret = ($res == 0) ? TRUE : FALSE) === FALSE)
			if(file_exists($file.',v'))
				unlink($file.',v');
		return $ret;
	}

	protected function _saveUpdate(Engine $engine, Request $request = NULL,
			&$error)
	{
		$cred = $engine->getCredentials();
		$username = $cred->getUsername();
		$module = $this->getModule()->getName();

		$error = _('Could not find the wiki repository');
		if(($root = static::getRoot($module)) === FALSE)
			return FALSE;
		$file = $root.'/'.$this->getTitle();
		$error = _('No wiki page was found with this name');
		if(realpath($file.',v') === FALSE)
			return FALSE;
		//translate the content
		//XXX remains even in case of failure
		$this->setContent($engine, $request->get('content'));
		//update the content
		if(parent::_saveUpdate($engine, NULL, $error) === FALSE)
			return FALSE;
		$error = _('Could not update the wiki page');
		if(($fp = fopen($file, 'x')) === FALSE)
			return FALSE;
		$message = $request->get('message');
		$emessage = ($message !== FALSE && strlen($message) > 0)
			? ' -m'.escapeshellarg($message) : '';
		$eusername = escapeshellarg($username);
		$efile = escapeshellarg($file);
		$cmd = 'rcs -q -l '.$efile;
		exec($cmd, $rcs, $res);
		$cmd = 'ci -q '.$emessage.' -w'.$eusername.' '.$efile;
		$res = -1;
		if(fwrite($fp, $this->markup) !== FALSE)
		{
			$error = _('Could not write the wiki page');
			if(fclose($fp) !== FALSE)
				exec($cmd, $rcs, $res);
		}
		else
			fclose($fp);
		if(file_exists($file))
			unlink($file);
		return ($res == 0) ? TRUE : FALSE;
	}


	//static
	//methods
	//WikiContent::getRoot
	static public function getRoot($name = FALSE)
	{
		global $config;

		if($name === FALSE)
			$name = 'wiki';
		return $config->get('module::'.$name, 'root');
	}


	//protected
	//properties
	static protected $class = 'WikiContent';


	//methods
	//accessors
	//WikiContent::getMarkup
	protected function getMarkup(Engine $engine, Request $request = NULL)
	{
		$revision = ($request !== NULL) ? $request->get('revision')
			: FALSE;
		$module = $this->getModule()->getName();
		$title = $this->getTitle();

		if($revision !== FALSE)
			return $this->getMarkupRevision($engine, $revision);
		if($request !== NULL && $request->get('diff') !== FALSE)
		{
			$r1 = $request->get('r1');
			$r2 = $request->get('r2');
			if($r1 !== FALSE && $r2 !== FALSE)
				return $this->getMarkupDiff($engine, $r1, $r2);
		}
		if($this->markup !== FALSE)
			return $this->markup;
		if(($rcs = $this->getMarkupRevision($engine)) === FALSE)
			return FALSE;
		$this->setContent($engine, $rcs);
		return $rcs;
	}


	//WikiContent::getMarkupDiff
	protected function getMarkupDiff(Engine $engine, $r1, $r2)
	{
		$module = $this->getModule()->getName();
		$title = $this->getTitle();

		if($r1 === FALSE || $r2 === FALSE)
			return FALSE;
		if(($root = static::getRoot($module)) === FALSE
				|| strpos($title, '/') !== FALSE)
			return FALSE;
		$cmd = 'rcsdiff -q -r'.escapeshellarg($r1)
			.' -r'.escapeshellarg($r2);
		$cmd .= ' -u '.escapeshellarg($root.'/'.$title);
		exec($cmd, $rcs, $res);
		if($res != 0 && $res != 1)
			return FALSE;
		$rcs = implode("\n", $rcs);
		//XXX improve the output format
		return '<pre>'.htmlspecialchars($rcs).'</pre>';
	}


	//WikiContent::getMarkupRevision
	protected function getMarkupRevision(Engine $engine, $revision = FALSE)
	{
		$module = $this->getModule()->getName();
		$title = $this->getTitle();

		if(($root = static::getRoot($module)) === FALSE
				|| strpos($title, '/') !== FALSE)
			return FALSE;
		$cmd = 'co -p -q';
		if($revision !== FALSE)
			$cmd .= ' -r'.escapeshellarg($revision);
		$cmd .= ' '.escapeshellarg($root.'/'.$title);
		exec($cmd, $rcs, $res);
		if($res != 0)
			return FALSE;
		return implode("\n", $rcs);
	}


	//private
	//properties
	private $markup = FALSE;
}

?>
