<?php //$Id$
//Copyright (c) 2012-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/>.



//ProjectModule
class ProjectModule extends MultiContentModule
{
	//public
	//methods
	//essential
	//ProjectModule::call
	public function call(Engine $engine, Request $request, $internal = 0)
	{
		if($internal)
			return parent::call($engine, $request, $internal);
		switch(($action = $request->getAction()))
		{
			case 'bug_list':
				return $this->callBugList($engine, $request);
			case 'bug_reply':
				return $this->callBugReply($engine, $request);
			case 'browse':
			case 'gallery':
			case 'members':
			case 'homepage':
			case 'timeline':
				return $this->callDisplay($engine, $request);
			case 'bugList':
			case 'bugReply':
			case 'download':
			case 'latest':
				$action = 'call'.$action;
				return $this->$action($engine, $request);
		}
		return parent::call($engine, $request, $internal);
	}


	//static
	//useful
	//ProjectModule::attachSCM
	static public function attachSCM(Engine $engine, $name, &$error = FALSE)
	{
		//XXX use $module->getName()
		$module = 'project';
		$error = _('No SCM configured for this project');

		if(strpos($name, '/') !== FALSE || strlen($name) == 0)
			return FALSE;
		$error = _('Internal server error');
		$filename = "./modules/$module/scm/$name.php";
		$res = include_once($filename);
		if($res === FALSE)
			return FALSE;
		$name = $name.'SCMProject';
		$ret = new $name();
		$engine->log(LOG_DEBUG, 'Attaching '.get_class($ret));
		$ret->attach($engine);
		return $ret;
	}


	//protected
	static protected $content_classes = array('project' => 'ProjectContent',
		'bug' => 'BugProjectContent',
		'bugreply' => 'BugReplyProjectContent',
		'download' => 'DownloadProjectContent',
		'screenshot' => 'ScreenshotProjectContent');
	//properties
	//queries
	//FIXME use daportal_user_enabled and daportal_content_public
	static protected $query_list_admin_bugs = "SELECT
		daportal_content.content_id AS id, bug_id,
		daportal_content.enabled AS enabled,
		daportal_content.public AS public,
		timestamp, name AS module,
		daportal_user.user_id AS user_id, username, title
		FROM daportal_content, daportal_module, daportal_user,
		daportal_bug
		WHERE daportal_content.module_id=daportal_module.module_id
		AND daportal_module.module_id=:module_id
		AND daportal_content.user_id=daportal_user.user_id
		AND daportal_user.enabled='1'
		AND daportal_content.content_id=daportal_bug.content_id";
	static protected $query_list_admin_bugs_count = "SELECT
		COUNT(*) AS count
		FROM daportal_content, daportal_module, daportal_user,
		daportal_bug
		WHERE daportal_content.module_id=daportal_module.module_id
		AND daportal_module.module_id=:module_id
		AND daportal_content.user_id=daportal_user.user_id
		AND daportal_user.enabled='1'
		AND daportal_content.content_id=daportal_bug.content_id";
	//IN:	module_id
	static protected $query_list_bugs = 'SELECT bug.content_id AS id,
		bug.timestamp AS timestamp,
		bug.module_id AS module_id, bug.module AS module,
		bug.user_id AS user_id, bug.username AS username,
		bug.group_id AS group_id, bug.groupname AS groupname,
		bug.title AS title, bug.content AS content,
		bug.enabled AS enabled, bug.public AS public,
		bug_id, type, state, priority, assigned,
		daportal_bug.project_id AS project_id,
		project.title AS project
		FROM daportal_content_public bug, daportal_bug,
		daportal_content_public project, daportal_project
		WHERE bug.content_id=daportal_bug.content_id
		AND project.content_id=daportal_bug.project_id
		AND daportal_project.project_id=project.content_id
		AND bug.module_id=:module_id';
	//IN:	project_id
	//	download_id
	static protected $project_query_project_download_insert = 'INSERT INTO
		daportal_project_download (project_id, download_id)
		VALUES (:project_id, :download_id)';


	//methods
	//essential
	//ProjectModule::ProjectModule
	protected function __construct($id, $name, $title = FALSE)
	{
		$title = ($title === FALSE) ? _('Projects') : $title;
		$this->content_list_count = 20;
		parent::__construct($id, $name, $title);
	}


	//accessors
	//ProjectModule::canDownload
	protected function canDownload(Engine $engine, Request $request = NULL,
			Content $content = NULL)
	{
		if($content === NULL)
			$content = new ProjectContent($engine, $this);
		return $content->canDownload($engine, $request);
	}


	//ProjectModule::canUpload
	protected function canUpload(Engine $engine, Request $request = NULL,
			Content $content = NULL)
	{
		if($content === NULL)
			$content = new ProjectContent($engine, $this);
		return $content->canUpload($engine, $request);
	}


	//ProjectModule::getBugByID
	protected function getBugByID(Engine $engine, $id)
	{
		return BugProjectContent::loadFromBugID($engine, $this, $id);
	}


	//ProjectModule::getFilter
	protected function getFilter(Engine $engine, Request $request)
	{
		$r = new Request($this->name, 'bug_list');
		$form = new PageElement('form', array('request' => $r,
				'idempotent' => TRUE));
		$hbox = $form->append('hbox');
		$vbox1 = $hbox->append('vbox');
		$vbox2 = $hbox->append('vbox');
		//FIXME fetch the project name in additional cases
		$vbox1->append('entry', array('name' => 'project',
			'value' => $request->get('project'),
			'text' => _('Project: ')));
		$vbox2->append('entry', array('name' => 'username',
			'value' => $request->get('username'),
			'text' => _('Submitted by: ')));
		//FIXME implement the rest
		$bbox = $vbox2->append('hbox');
		$bbox->append('button', array('stock' => 'reset',
				'type' => 'reset',
				'text' => _('Reset')));
		$bbox->append('button', array('stock' => 'submit',
				'type' => 'submit',
				'text' => _('Filter')));
		return $form;
	}


	//ProjectModule::getMembers
	protected function getMembers(Engine $engine, ProjectContent $project)
	{
		return $project->getMembers($engine);
	}


	//ProjectModule::_getProjectByName
	protected function _getProjectByName(Engine $engine, $name)
	{
		return ProjectContent::loadFromName($engine, $this, $name);
	}


	//ProjectModule::isManager
	protected function isManager(Engine $engine, ProjectContent $project)
	{
		$cred = $engine->getCredentials();

		if($cred->isAdmin()
				|| $project->getUserID() == $cred->getUserID())
			return TRUE;
		return FALSE;
	}


	//ProjectModule::isMember
	protected function isMember(Engine $engine, ProjectContent $project)
	{
		$cred = $engine->getCredentials();

		if(($members = $project->getMembers($engine)) === FALSE)
			return FALSE;
		$uid = $cred->getUserID();
		if($project->getUserID() == $uid)
			return TRUE;
		foreach($members as $m)
			if($m->getUserID() == $uid)
				return TRUE;
		return FALSE;
	}


	//ProjectModule::setContext
	protected function setContext(Engine $engine = NULL,
			Request $request = NULL, Content $content = NULL)
	{
		parent::setContext($engine, $request, $content);
		switch(static::$content_class)
		{
			case 'BugProjectContent':
				$this->text_content_admin
					= _('Bugs administration');
				$this->text_content_list_title
					= _('Bug reports');
				$this->text_content_list_title_by
					= _('Bugs reported by');
				$this->text_content_list_title_by_group
					= _('Bugs reported by group');
				$this->text_content_submit_content
					= _('Report bug');
				break;
			case 'BugReplyProjectContent':
				$this->text_content_admin
					= _('Bug replies administration');
				$this->text_content_list_title
					= _('Bug replies');
				$this->text_content_list_title_by
					= _('Bug replies by');
				$this->text_content_list_title_by_group
					= _('Bug replies by group');
				$this->text_content_submit_content
					= _('Reply to a bug');
				break;
			case 'DownloadProjectContent':
				$this->text_content_admin
					= _('Project downloads administration');
				$this->text_content_list_title
					= _('Project download list');
				$this->text_content_list_title_by
					= _('Project downloads from');
				$this->text_content_list_title_by_group
					= _('Project downloads from group');
				$this->text_content_submit_content
					= _('New download');
				break;
			case 'ScreenshotProjectContent':
				$this->text_content_admin
					= _('Project screenshots administration');
				$this->text_content_list_title
					= _('Project screenshot list');
				$this->text_content_list_title_by
					= _('Project screenshots from');
				$this->text_content_list_title_by_group
					= _('Project screenshots from group');
				$this->text_content_submit_content
					= _('New screenshot');
				break;
			default:
			case 'ProjectContent':
				$this->text_content_admin
					= _('Projects administration');
				$this->text_content_list_title
					= _('Project list');
				$this->text_content_list_title_by
					= _('Projects from');
				$this->text_content_list_title_by_group
					= _('Projects from group');
				$this->text_content_submit_content
					= _('New project');
				break;
		}
	}


	//calls
	//ProjectModule::callAdmin
	protected function callAdmin(Engine $engine, Request $request)
	{
		switch($request->get('type'))
		{
			case 'bug':
				return $this->_adminBugs($engine, $request);
			case 'project':
			default:
				return parent::callAdmin($engine, $request);
		}
	}

	private function _adminBugs(Engine $engine, Request $request)
	{
		//FIXME also set the columns
		$this->text_content_admin = _('Bugs administration');
		static::$query_list_admin = static::$query_list_admin_bugs;
		static::$query_list_admin_count
			= static::$query_list_admin_bugs_count;
		return parent::callAdmin($engine, $request);
	}


	//ProjectModule::callBugList
	protected function callBugList(Engine $engine, Request $request)
	{
		$db = $engine->getDatabase();
		$title = _('Bug reports');
		$error = FALSE;
		//FIXME use BugProjectContent::listAll() instead
		$query = static::$query_list_bugs;
		$project = FALSE;

		//XXX unlike ProjectModule::list() here getID() is the project
		//determine the current project
		if(($id = $request->getID()) !== FALSE
				&& ($project = $this->getContent($engine, $id,
					$request->getTitle(), $request))
					=== FALSE)
			$error = _('Unknown project');
		else if(($name = $request->get('project')) !== FALSE
				&& strlen($name) > 0)
		{
			if(($project = $this->_getProjectByName($engine,
					$name)) !== FALSE)
				$id = $project->getID();
			else
				$error = _('Unknown project');
		}
		$args = array('module_id' => $this->id);
		if($project !== FALSE)
		{
			$title = _('Bug reports for ').$project->getTitle();
			$query .= ' AND daportal_bug.project_id=:project_id';
			$args['project_id'] = $id;
		}
		$filter = $this->getFilter($engine, $request);
		//filter by user_id
		if(($uid = $request->get('user_id')) !== FALSE)
		{
			$title .= _(' by ').$uid; //XXX
			$query .= ' AND bug.user_id=:user_id';
			$args['user_id'] = $uid;
		}
		//sorting out
		switch(($order = $request->get('sort')))
		{
			case 'date':	$order = 'bug.timestamp DESC';	break;
			case 'title':	$order = 'bug.title ASC';	break;
			default:	$order = 'bug_id DESC';		break;
		}
		$query .= ' ORDER BY '.$order;
		//obtain the corresponding bug reports
		if($error !== FALSE)
			$res = FALSE;
		else if(($res = $db->query($engine, $query, $args)) === FALSE)
		{
			$res = FALSE;
			$error = _('Unable to list bugs');
		}
		else
			$res = new ContentResult($engine, $this,
				'BugProjectContent', $res);
		//build the page
		$page = new Page(array('title' => $title));
		$page->append('title', array('stock' => $this->name,
				'text' => $title));
		if($project !== FALSE)
			$page->append($project->displayToolbar($engine,
					$request));
		if($error !== FALSE)
			$page->append('dialog', array('type' => 'error',
					'text' => $error));
		if($filter !== FALSE)
			$page->append($filter);
		$view = $page->append('treeview');
		$view->set('columns', BugProjectContent::getColumns());
		if($res === FALSE)
			return new PageResponse($page);
		foreach($res as $r)
			$view->append($r->displayRow($engine, $request));
		return new PageResponse($page);
	}


	//ProjectModule::callBugReply
	protected function callBugReply(Engine $engine, Request $request)
	{
		$cred = $engine->getCredentials();
		$user = new User($engine, $cred->getUserID());

		if(($bug = BugProjectContent::load($engine, $this,
				$request->getID(), $request->getTitle()))
				=== FALSE)
			return $this->callDefault($engine, new Request());
		$project = $this->getContent($engine, $bug->get('project_id'));
		$title = sprintf(_('Reply to #%u/%s: %s'), $bug->get('bug_id'),
				$project->getTitle(), $bug->getTitle());
		$page = new Page(array('title' => $title));
		//title
		$page->append('title', array('stock' => $this->name,
				'text' => $title));
		//FIXME process the request
		//bug
		$vbox = $page->append('vbox'); //XXX for the title level
		$vbox->append($bug->display($engine, $request));
		//preview
		if($request->get('preview') !== FALSE)
		{
			$title = $request->get('title');
			$content = $request->get('content');
			$reply = array('title' => _('Preview: ').$title,
					'user_id' => $user->getUserID(),
					'username' => $user->getUsername(),
					'content' => $content);
			$reply = new BugReplyProjectContent($engine, $this,
					$reply);
			$vbox->append($reply->display($engine, $request));
		}
		//form
		$form = $this->formBugReply($engine, $request, $bug, $project);
		$vbox->append($form);
		return new PageResponse($page);
	}


	//ProjectModule::callDefault
	protected function callDefault(Engine $engine, Request $request)
	{
		$title = _('Projects');
		$latest = $this->canDownload($engine, $request)
			? array('project', 'download', 'bug', 'screenshot')
			: array('project', 'bug');

		if($request !== NULL && $request->getID() !== FALSE)
			return $this->callDisplay($engine, $request);
		$page = new Page(array('title' => $title));
		$page->append('title', array('stock' => $this->getName(),
			'text' => $title));
		$hbox = $page->append('hbox');
		foreach($latest as $l)
		{
			$request = $this->getRequest('latest', array(
				'type' => $l));
			$response = $this->call($engine, $request);
			if($response instanceof PageResponse)
				$hbox->append($response->getContent());
		}
		return new PageResponse($page);
	}


	//ProjectModule::callDownload
	protected function callDownload(Engine $engine, Request $request)
	{
		if($request->getID() !== FALSE)
			return $this->callDisplay($engine, $request);
		$request = $this->getRequest('latest', array(
				'type' => 'download'));
		return $this->callLatest($engine, $request);
	}


	//ProjectModule::callLatest
	protected function callLatest(Engine $engine, Request $request)
	{
		$type = $request->get('type');

		switch($type)
		{
			case 'bug':
				return $this->_latestBugReports($engine,
						$request);
			case 'download':
				return $this->_latestDownloads($engine,
						$request);
			case 'screenshot':
				return $this->_latestScreenshots($engine,
						$request);
			case 'project':
			default:
				return $this->_latestProjects($engine,
						$request);
		}
	}

	private function _latestBugReports(Engine $engine, Request $request)
	{
		$title = _('Latest bug reports');

		//list the latest bug reports
		$page = new Page($title);
		$page->append('title', array('stock' => $this->getName(),
				'text' => $title));
		$vbox = $page->append('vbox');
		if(($bugs = BugProjectContent::listAll($engine, $this,
				'timestamp', $this->content_headline_count))
				=== FALSE)
		{
			$error = _('Could not list bug reports');
			return new ErrorResponse($error);
		}
		$columns = BugProjectContent::getColumns();
		$view = $vbox->append('treeview', array('columns' => $columns));
		foreach($bugs as $b)
			$view->append($b->displayRow($engine, $request));
		$vbox->append('link', array('stock' => 'more',
			'text' => _('More bug reports...'),
			'request' => $this->getRequest('list', array(
				'type' => 'bug'))));
		return new PageResponse($page);
	}

	private function _latestProjects(Engine $engine, Request $request)
	{
		$title = _('Latest projects');

		//list the latest projects
		$page = new Page($title);
		$page->append('title', array('stock' => $this->getName(),
				'text' => $title));
		$vbox = $page->append('vbox');
		if(($projects = ProjectContent::listAll($engine, $this,
				'timestamp', $this->content_headline_count))
				=== FALSE)
		{
			$error = _('Could not list projects');
			return new ErrorResponse($error);
		}
		$columns = ProjectContent::getColumns();
		$view = $vbox->append('treeview', array('columns' => $columns));
		foreach($projects as $p)
			$view->append($p->displayRow($engine, $request));
		$vbox->append('link', array('stock' => 'more',
			'text' => _('More projects...'),
			'request' => $this->getRequest('list', array(
				'type' => 'project'))));
		return new PageResponse($page);
	}

	private function _latestDownloads(Engine $engine, Request $request)
	{
		$title = _('Latest downloads');

		//list the latest downloads
		$page = new Page($title);
		$page->append('title', array('stock' => 'download',
				'text' => $title));
		$vbox = $page->append('vbox');
		if(($downloads = DownloadProjectContent::listAll($engine, $this,
				'timestamp', $this->content_headline_count))
				=== FALSE)
		{
			$error = _('Could not list downloads');
			return new ErrorResponse($error);
		}
		$columns = DownloadProjectContent::getColumns();
		$view = $vbox->append('treeview', array('columns' => $columns));
		foreach($downloads as $d)
			$view->append($d->displayRow($engine, $request));
		$vbox->append('link', array('stock' => 'more',
			'text' => _('More downloads...'),
			'request' => $this->getRequest('list', array(
				'type' => 'download'))));
		return new PageResponse($page);
	}

	private function _latestScreenshots(Engine $engine, Request $request)
	{
		$title = _('Latest screenshots');

		//list the latest screenshots
		$page = new Page($title);
		$page->append('title', array('stock' => 'gallery',
				'text' => $title));
		$vbox = $page->append('vbox');
		if(($screenshots = ScreenshotProjectContent::listAll($engine,
				$this, 'timestamp', $this->content_headline_count))
				=== FALSE)
		{
			$error = _('Could not list screenshots');
			return new ErrorResponse($error);
		}
		$columns = ScreenshotProjectContent::getColumns();
		$view = $vbox->append('treeview', array(
			'view' => 'thumbnails'));
		foreach($screenshots as $s)
			$view->append($s->displayRow($engine, $request));
		$vbox->append('link', array('stock' => 'more',
			'text' => _('More screenshots...'),
			'request' => $this->getRequest('list', array(
				'type' => 'screenshot'))));
		return new PageResponse($page);
	}


	//ProjectModule::callSubmitRelease
	protected function callSubmitRelease(Engine $engine, Request $request)
	{
		$project = $this->getContent($engine, $request->getID(),
				$request->getTitle(), $request);

		$error = _('Invalid project');
		if($project === FALSE)
			return new ErrorResponse($error,
				Response::$CODE_ENOENT);
		$error = _('Permission denied');
		if(!$this->canUpload($engine, $request, $project))
			return new ErrorResponse($error, Response::$CODE_EPERM);
		$title = _('New download for project ').$project->getTitle();
		$page = new Page(array('title' => $title));
		$page->append('title', array('stock' => $this->name,
				'text' => $title));
		//process the request
		if(($error = $this->_submitProcessRelease($engine, $request,
				$project, $content)) === FALSE)
			return $this->_submitSuccessRelease($engine, $request,
					$page, $content);
		else if(is_string($error))
			$page->append('dialog', array('type' => 'error',
					'text' => $error));
		//form
		$form = $this->formSubmitRelease($engine, $request, $project);
		$page->append($form);
		return new PageResponse($page);
	}

	protected function _submitProcessRelease(Engine $engine,
			Request $request, $project, &$content)
	{
		$db = $engine->getDatabase();
		$query = static::$project_query_project_download_insert;

		//verify the request
		if($request->isIdempotent())
			return TRUE;
		//FIXME obtain the download path
		//XXX this assumes the file was just being uploaded
		$r = new Request('download', 'submit', FALSE, FALSE,
			array('submit' => 'submit'));
		$r->setIdempotent(FALSE);
		if($engine->process($r, TRUE) === FALSE)
			return _('Internal server error');
		//XXX ugly (and race condition)
		//XXX using download_id to workaround a bug in getLastID()
		if(($did = $db->getLastID($engine, 'daportal_download',
				'download_id')) === FALSE)
			return _('Internal server error');
		$q = 'SELECT content_id AS id FROM daportal_download'
			.' WHERE download_id=:download_id';
		$args = array('download_id' => $did);
		if(($res = $db->query($engine, $q, $args)) === FALSE
				|| count($res) != 1)
			return _('Internal server error');
		$res = $res->current();
		$did = $res['id'];
		$args = array('project_id' => $project['id'],
			'download_id' => $did);
		if($db->query($engine, $query, $args) === FALSE)
			return _('Internal server error');
		$content = Content::load($engine, $this, $project['id'],
				$project['title']);
		return FALSE;
	}

	protected function _submitSuccessRelease(Engine $engine,
			Request $request, $page, Content $content)
	{
		$r = new Request($this->name, 'download', $content->getID(),
			$content->getTitle());
		return $this->helperRedirect($engine, $r, $page,
				$this->text_content_submit_progress); //XXX
	}


	//forms
	//ProjectModule::formBugReply
	protected function formBugReply(Engine $engine, Request $request,
			BugProjectContent $bug, $project)
	{
		$r = new Request($this->name, 'bugReply', $request->getID(),
			$request->getTitle());
		$form = new PageElement('form', array('request' => $r));
		$vbox = $form->append('vbox');
		$title = $request->get('title');
		$vbox->append('entry', array('text' => _('Title: '),
				'name' => 'title', 'value' => $title));
		$vbox->append('textview', array('text' => _('Content: '),
				'name' => 'content',
				'value' => $request->get('content')));
		//FIXME really implement
		$r = $bug->getRequest();
		$box = $vbox->append('buttonbox');
		$box->append('button', array('request' => $r,
				'stock' => 'cancel',
				'target' => '_cancel', 'text' => _('Cancel')));
		$box->append('button', array('type' => 'submit',
				'stock' => 'preview', 'name' => 'action',
				'value' => 'preview', 'text' => _('Preview')));
		//FIXME add missing buttons
		return $form;
	}


	//ProjectModule::formSubmitRelease
	protected function formSubmitRelease(Engine $engine, Request $request,
			ProjectContent $project)
	{
		$r = new Request($this->name, 'submit', $project->getID(),
			$project->getTitle(), array('type' => 'download'));
		$form = new PageElement('form', array('request' => $r));
		$form->append('filechooser', array('text' => _('File: '),
				'name' => 'files[]'));
		$value = $request->get('directory');
		$form->append('entry', array('text' => _('Directory: '),
				'name' => 'directory', 'value' => $value));
		$r = new Request($this->name, 'download', $project->getID(),
			$project->getTitle());
		$form->append('button', array('stock' => 'cancel',
				'request' => $r,
				'target' => '_cancel', 'text' => _('Cancel')));
		$form->append('button', array('type' => 'submit',
				'text' => _('Submit'),
				'name' => 'submit', 'value' => 'submit'));
		return $form;
	}


	//helpers
	//ProjectModule::helperListButtons
	protected function helperListButtons(Engine $engine, PageElement $page,
			Request $request = NULL)
	{
	}


	//ProjectModule::helperListView
	protected function helperListView(Engine $engine,
			Request $request = NULL)
	{
		$view = parent::helperListView($engine, $request);
		if($request->get('type') == 'screenshot')
		{
			$view->set('columns', FALSE);
			$view->set('view', 'thumbnails');
		}
		return $view;
	}
}

?>
