DaPortal

<?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/>.
require_once('./modules/project/scm.php');
//CVSSCMProject
class CVSSCMProject extends SCMProject
{
//public
//CVSSCMProject::attach
public function attach(Engine $engine)
{
global $config;
$this->cvsroot = $config->get('module::project',
'scm::backend::cvs::cvsroot'); //XXX
$this->repository = $config->get('module::project',
'scm::backend::cvs::repository'); //XXX
parent::attach($engine);
}
//actions
//CVSSCMProject::browse
public function browse(ProjectContent $project, Request $request)
{
$cvsroot = $project->get('cvsroot');
$error = _('No CVS repository defined');
if($this->cvsroot === FALSE || strlen($cvsroot) == 0)
return new PageElement('dialog', array(
'type' => 'error', 'text' => $error));
$vbox = new PageElement('vbox');
//browse
$path = $this->cvsroot.'/'.$cvsroot;
if(($file = $request->get('file')) !== FALSE)
$file = $this->helperSanitizePath($file);
else
$file = '';
$error = _('No such file or directory');
if(($st = @lstat($path.'/'.$file)) === FALSE)
return new PageElement('dialog', array(
'type' => 'error', 'text' => $error));
if(($st['mode'] & static::$S_IFDIR) === static::$S_IFDIR)
return $this->_browseDir($request, $vbox, $path, $file);
if(($revision = $request->get('revision')) !== FALSE)
return $this->_browseFileRevision($request, $vbox,
$path, $file, $revision);
return $this->_browseFile($request, $vbox, $path, $file);
}
private function _browseDir($request, $vbox, $path, $file)
{
$error = _('Could not open directory');
$vbox->append('title', array('text' => _('Browse source')));
if(($dir = opendir($path.'/'.$file)) === FALSE)
return new PageElement('dialog', array(
'type' => 'error', 'text' => $error));
//view
$columns = array('icon' => '', 'title' => _('Filename'),
'date' => _('Date'),
'revision' => _('Revision'),
'username' => _('Author'),
'content' => _('Description'));
$view = $vbox->append('treeview', array('columns' => $columns));
$folders = array();
$files = array();
while(($de = readdir($dir)) !== FALSE)
{
if($de == '.' || $de == '..')
continue;
if(($st = lstat($path.'/'.$file.'/'.$de)) === FALSE)
continue;
if(($st['mode'] & static::$S_IFDIR) == static::$S_IFDIR)
$folders[$de] = $st;
else if(substr($de, -2) != ',v')
continue;
else
$files[$de] = $st;
}
ksort($folders);
ksort($files);
if($file != '' && ($dirname = dirname($file)) !== FALSE
&& ($st = lstat($path.'/'.$dirname)) !== FALSE
&& $st['mode'] & static::$S_IFDIR
== static::$S_IFDIR)
{
if($dirname == '.' || $dirname == '/')
$dirname = FALSE;
$row = $view->append('row');
$icon = new PageElement('image', array(
'stock' => 'updir', 'size' => 16));
$row->set('icon', $icon);
//title
$r = new Request($request->getModule(),
$request->getAction(),
$request->getID(), $request->getTitle(),
array('file' => $dirname));
$link = new PageElement('link', array('request' => $r,
'text' => _('Parent directory')));
$row->set('title', $link);
//date
$date = Common::getDateTime($st['mtime']);
$row->set('date', $date);
}
foreach($folders as $de => $st)
{
$row = $view->append('row');
$icon = Mime::getIconByType($this->engine,
'inode/directory', 16);
$icon = new PageElement('image', array(
'source' => $icon));
$row->set('icon', $icon);
//title
$f = ltrim($file.'/'.$de, '/');
$r = new Request($request->getModule(),
$request->getAction(),
$request->getID(), $request->getTitle(),
array('file' => $f));
$link = new PageElement('link', array('request' => $r,
'text' => $de));
$row->set('title', $link);
//date
$date = Common::getDateTime($st['mtime']);
$row->set('date', $date);
}
foreach($files as $de => $st)
{
$row = $view->append('row');
$icon = Mime::getIcon($this->engine, $de, 16);
$icon = new PageElement('image', array(
'source' => $icon));
$row->set('icon', $icon);
//title
$f = ltrim($file.'/'.$de, '/');
$r = new Request($request->getModule(),
$request->getAction(),
$request->getID(), $request->getTitle(),
array('file' => $f));
$link = new PageElement('link', array('request' => $r,
'text' => substr($de, 0, -2)));
$row->set('title', $link);
//date
$date = Common::getDateTime($st['mtime']);
$row->set('date', $date);
//obtain the revisions
$cmd = 'rlog '.escapeshellarg($path.'/'.$file.'/'.$de);
unset($rcs);
exec($cmd, $rcs, $res);
if(($cnt = count($rcs)) == 0)
continue;
for($revs = 0; $revs < $cnt; $revs++)
if($rcs[$revs]
== '----------------------------')
break;
//revision
$revision = substr($rcs[$revs + 1], 9);
$r = new Request($request->getModule(),
$request->getAction(), $request->getID(),
$request->getTitle(), array(
'file' => $file.'/'.$de,
'revision' => $revision));
$link = new PageElement('link', array('request' => $r,
'text' => $revision));
$row->set('revision', $link);
//author
//XXX code duplication
$username = substr($rcs[$revs + 2], 36);
$username = substr($username, 0, strspn($username,
'0123456789abcdefghijklmnopqrstuvwxyz'
.'ABCDEFGHIJKLMNOPQRSTUVWXYZ'));
if(($user = User::lookup($this->engine, $username))
!== FALSE)
{
$r = new Request('project', 'list',
$user->getUserID(), $username);
$username = new PageElement('link', array(
'request' => $r,
'stock' => 'user',
'text' => $username));
}
$row->set('username', $username);
//message
//FIXME implement
}
closedir($dir);
return $vbox;
}
private function _browseFile(Request $request, PageElement $vbox, $path,
$file)
{
$error = _('Could not list revisions');
//obtain the revisions
$cmd = 'rlog '.escapeshellarg($path.'/'.$file);
exec($cmd, $rcs, $res);
if($res != 0 || count($rcs) == 0)
return new PageElement('dialog', array(
'type' => 'error', 'text' => $error));
//view
$vbox->append('title', array('text' => _('Revisions')));
$columns = array('title' => _('Revision'), 'date' => _('Date'),
'username' => _('Author'),
'message' => _('Message'));
$view = $vbox->append('treeview', array('columns' => $columns,
'alternate' => TRUE));
for($i = 0, $cnt = count($rcs); $i < $cnt;)
if($rcs[$i++] == '----------------------------')
break;
for(; $i < $cnt - 2; $i += 3)
{
$row = $view->append('row');
$revision = substr($rcs[$i], 9);
$r = new Request('project', 'browse', $request->getID(),
$request->getTitle(), array('file' => $file,
'revision' => $revision));
$link = new PageElement('link', array('request' => $r,
'text' => $revision));
$row->set('title', $link);
$row->set('date', substr($rcs[$i + 1], 6, 19));
//username
$username = substr($rcs[$i + 1], 36);
$username = substr($username, 0, strspn($username,
'abcdefghijklmnopqrstuvwxyz'
.'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
.'0123456789'));
if(($user = User::lookup($this->engine, $username))
!== FALSE)
{
$r = new Request('project', 'list',
$user->getUserID(), $username);
$username = new PageElement('link', array(
'request' => $r,
'stock' => 'user',
'text' => $username));
}
$row->set('username', $username);
for(; strncmp($rcs[$i + 2], 'branches: ', 10) == 0;
$i++);
//message
$dashes = '----------------------------';
$longdashes =
'=============================================================================';
$message = $rcs[$i + 2];
if($message == $dashes || $message == $longdashes)
$message = '';
else
{
$msg = '';
for($i++; $i < $cnt && $rcs[$i + 2] != $dashes
&& $rcs[$i + 2] != $longdashes; $i++)
$msg = '...';
$message .= $msg;
}
$row->set('message', $message);
}
return $vbox;
}
private function _browseFileRevision(Request $request,
PageElement $vbox, $path, $file, $revision)
{
$error = 'Internal server error';
$cmd = 'co -p -q -r'.escapeshellarg($revision)
.' '.escapeshellarg($path.'/'.$file);
if(($fp = popen($cmd, 'r')) === FALSE)
return new PageElement('dialog', array(
'type' => 'error', 'text' => $error));
$filename = basename($file);
$type = Mime::getType($this->engine, $filename);
if($request->get('download') !== FALSE)
{
$ret = new PipeResponse($fp);
$ret->setFilename($filename);
$ret->setType($type);
return $ret;
}
$label = $vbox->append('label');
//link back
$r = new Request('project', 'browse', $request->getID(),
$request->getTitle(), array('file' => $file));
$label->append('link', array('request' => $r, 'stock' => 'back',
'text' => 'Back to the revision list'));
//link to the download
$label->append('label', array('text' => ' '));
$rdownload = new Request('project', 'browse', $request->getID(),
$request->getTitle(), array('file' => $file,
'revision' => $revision, 'download' => 1));
$label->append('link', array('request' => $rdownload,
'stock' => 'download',
'text' => 'Download file'));
//link to this page
$label->append('label', array('text' => ' '));
$r = new Request('project', 'browse', $request->getID(),
$request->getTitle(), array('file' => $file,
'revision' => $revision));
$label->append('link', array('request' => $r,
'stock' => 'link',
'text' => 'Permalink'));
if(strncmp($type, 'image/', 6) == 0)
//attempt to render the image inline
$vbox->append('image', array('request' => $rdownload,
'text' => $filename));
else
//render as text
while(($line = fgets($fp)) !== FALSE)
{
$line = rtrim($line, "\r\n");
$vbox->append('label', array(
'class' => 'preformatted',
'text' => $line));
}
pclose($fp);
return $vbox;
}
//CVSSCMProject::download
public function download(ProjectContent $project, Request $request)
{
$title = _('Repository');
$repository = 'pserver:'.$this->repository;
$cvsroot = $project->get('cvsroot');
//repository
if($this->repository === FALSE || strlen($cvsroot) == 0)
return FALSE;
$vbox = new PageElement('vbox');
$vbox->append('title', array('text' => $title));
$vbox->append('label', array('text' => _('The source code can be obtained as follows: ')));
$text = '$ cvs -d:'.$repository.' co '.$cvsroot;
$vbox->append('label', array('text' => $text,
'class' => 'preformatted'));
return $vbox;
}
//CVSSCMProject::timeline
public function timeline(ProjectContent $project, Request $request)
{
$cvsroot = $project->get('cvsroot');
$error = _('No CVS repository defined');
//check the cvsroot
$len = strlen($cvsroot);
if($this->cvsroot === FALSE || $len == 0)
return new PageElement('dialog', array(
'type' => 'error', 'text' => $error));
//history
$error = _('Could not open the project history');
$filename = $this->cvsroot.'/CVSROOT/history';
if(($fp = fopen($filename, 'r')) === FALSE)
return new PageElement('dialog', array(
'type' => 'error', 'text' => $error));
//view
$columns = array('icon' => '', 'title' => _('Filename'),
'date' => _('Date'), 'action' => _('Action'),
'revision' => _('Revision'),
'username' => _('Author'));
$vbox = new PageElement('vbox');
$vbox->append('title', array('text' => _('Timeline')));
$view = $vbox->append('treeview', array(
'columns' => $columns));
//rows
while(($line = fgets($fp)) !== FALSE)
{
$fields = explode('|', $line);
if(strlen($fields[4]) == 0)
continue;
if(strncmp($fields[3], $cvsroot, $len) != 0)
continue;
$event = FALSE;
switch($fields[0][0])
{
case 'A':
$event = 'Add';
$icon = 'add';
break;
case 'F':
$event = 'Release';
$icon = FALSE;
break;
case 'M':
$event = 'Modify';
$icon = 'edit';
break;
case 'R':
$event = 'Remove';
$icon = 'remove';
break;
}
if($event === FALSE)
continue;
$row = $view->prepend('row');
//icon
$icon = new PageElement('image', array(
'stock' => $icon, 'size' => 16));
$row->set('icon', $icon);
//title
$title = substr($fields[3], $len ? $len + 1 : $len).'/'
.$fields[5];
$title = ltrim($title, '/');
$title = rtrim($title, "\n");
$r = new Request($request->getModule(), 'browse',
$request->getID(), $request->getTitle(),
array('file' => $title.',v'));
$link = new PageElement('link', array('request' => $r,
'text' => $title));
$row->set('title', $link);
//date
$date = substr($fields[0], 1, 9);
$date = base_convert($date, 16, 10);
$date = Common::getDateTime($date);
$row->set('date', $date);
$row->set('action', $event);
//revision
$revision = $fields[4];
$r = new Request($request->getModule(), 'browse',
$request->getID(), $request->getTitle(), array(
'file' => $title.',v',
'revision' => $revision));
$link = new PageElement('link', array('request' => $r,
'text' => $revision));
$row->set('revision', $link);
//username
$username = $fields[1];
if(($user = User::lookup($this->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);
}
//cleanup
fclose($fp);
return $vbox;
}
//protected
//methods
//helpers
//CVSSCMProject::helperSanitizePath
protected function helperSanitizePath($path)
{
$path = '/'.trim($path, '/').'/';
$path = str_replace('/./', '/', $path);
//FIXME really implement '..'
if(strpos($path, '/../') !== FALSE)
return '';
return trim($path, '/');
}
//private
//properties
static private $S_IFDIR = 040000;
private $cvsroot = FALSE;
private $repository = FALSE;
}
?>