From aa2868eb17e776371b342184b4f552a40518138a Mon Sep 17 00:00:00 2001 From: Patrick Georgi Date: Fri, 19 Aug 2011 22:01:55 +0200 Subject: [PATCH 01/69] Add basic framework for web based repository access /p/$project/source/repo/ is assigned to a method that takes care of providing repository access. For now, this results in an exception on all SCMs. --- src/IDF/Scm.php | 5 +++++ src/IDF/Views/Source.php | 6 ++++++ src/IDF/conf/urls.php | 5 +++++ 3 files changed, 16 insertions(+) diff --git a/src/IDF/Scm.php b/src/IDF/Scm.php index 5ebb8d9..350b073 100644 --- a/src/IDF/Scm.php +++ b/src/IDF/Scm.php @@ -497,5 +497,10 @@ class IDF_Scm { return 0; } + + public function repository($request, $match) + { + throw new Exception('This repository does not support web based repository access'); + } } diff --git a/src/IDF/Views/Source.php b/src/IDF/Views/Source.php index 1496e54..1a556a0 100644 --- a/src/IDF/Views/Source.php +++ b/src/IDF/Views/Source.php @@ -132,6 +132,12 @@ class IDF_Views_Source $request); } + public function repository($request, $match) + { + $scm = IDF_Scm::get($request->project); + return $scm->repository($request, $match); + } + public $treeBase_precond = array('IDF_Precondition::accessSource', 'IDF_Views_Source_Precondition::scmAvailable', 'IDF_Views_Source_Precondition::revisionValid'); diff --git a/src/IDF/conf/urls.php b/src/IDF/conf/urls.php index 03779c0..d79c998 100644 --- a/src/IDF/conf/urls.php +++ b/src/IDF/conf/urls.php @@ -245,6 +245,11 @@ $ctl[] = array('regex' => '#^/p/([\-\w]+)/source/changesrev/$#', 'model' => 'IDF_Views_Source_Svn', 'method' => 'changelogRev'); +$ctl[] = array('regex' => '#^/p/([\-\w]+)/source/repo/(.*)$#', + 'base' => $base, + 'model' => 'IDF_Views_Source', + 'method' => 'repository'); + // ---------- WIKI ----------------------------------------- $ctl[] = array('regex' => '#^/p/([\-\w]+)/doc/$#', From 34c9d04a35f710dc9512d4f4a960ac2487429891 Mon Sep 17 00:00:00 2001 From: Patrick Georgi Date: Sat, 20 Aug 2011 11:39:41 +0200 Subject: [PATCH 02/69] Provide http access to git repositories /p/$project/source/repo for git repos now exposes both "dumb" and "smart" http protocol access. --- src/IDF/Scm/Git.php | 78 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/src/IDF/Scm/Git.php b/src/IDF/Scm/Git.php index 53bb417..e0dadbe 100644 --- a/src/IDF/Scm/Git.php +++ b/src/IDF/Scm/Git.php @@ -924,4 +924,82 @@ class IDF_Scm_Git extends IDF_Scm } return false; } + + public function repository($request, $match) + { + // authenticate: authenticate connection through "extra" password + if (isset($_SERVER['HTTP_AUTHORIZATION']) && $_SERVER['HTTP_AUTHORIZATION'] != '') + list($_SERVER['PHP_AUTH_USER'], $_SERVER['PHP_AUTH_PW']) = explode(':' , base64_decode(substr($_SERVER['HTTP_AUTHORIZATION'], 6))); + + if (isset($_SERVER['PHP_AUTH_USER'])) { + $sql = new Pluf_SQL('login=%s', array($_SERVER['PHP_AUTH_USER'])); + $users = Pluf::factory('Pluf_User')->getList(array('filter'=>$sql->gen())); + if ((count($users) == 1) && ($users[0]->active)) { + $user = $users[0]; + $realkey = substr(sha1($user->password.Pluf::f('secret_key')), 0, 8); + if ($_SERVER['PHP_AUTH_PW'] == $realkey) { + $request->user = $user; + } + } + } + + if (IDF_Precondition::accessSource($request) !== true) { + $response = new Pluf_HTTP_Response(""); + $response->status_code = 401; + $response->headers['WWW-Authenticate']='Basic realm="git for '.$this->project.'"'; + return $response; + } + + $path = $match[2]; + + // update files before delivering them + if (($path == 'objects/info/pack') || ($path == 'info/refs')) { + $cmd = sprintf(Pluf::f('idf_exec_cmd_prefix', ''). + 'GIT_DIR=%s '.Pluf::f('git_path', 'git').' update-server-info -f', + escapeshellarg($this->repo)); + self::shell_exec('IDF_Scm_Git::repository', $cmd); + } + + // smart HTTP discovery + if (($path == 'info/refs') && + (array_key_exists('service', $request->GET))){ + $service = $request->GET["service"]; + switch ($service) { + case 'git-upload-pack': + case 'git-receive-pack': + $content = sprintf('%04x',strlen($service)+15). + '# service='.$service."\n0000"; + $content .= self::shell_exec('IDF_Scm_Git::repository', + $service.' --stateless-rpc --advertise-refs '. + $this->repo); + $response = new Pluf_HTTP_Response($content, + 'application/x-'.$service.'-advertisement'); + return $response; + default: + throw new Exception('unknown service: '.$service); + } + } + + switch($path) { + // smart HTTP RPC + case 'git-upload-pack': + case 'git-receive-pack': + $response = new Pluf_HTTP_Response_CommandPassThru($path. + ' --stateless-rpc '.$this->repo, + 'application/x-'.$path.'-result'); + $response->addStdin('php://input'); + return $response; + + // regular file + default: + // make sure we're inside the repo hierarchy (ie. no break-out) + if (is_file($this->repo.'/'.$path) && + strpos(realpath($this->repo.'/'.$path), $this->repo.'/') == 0) { + return new Pluf_HTTP_Response_File($this->repo.'/'.$path, + 'application/octet-stream'); + } else { + return new Pluf_HTTP_Response_NotFound($request); + } + } + } } From 4ae0019e0f35281c3032d7c2cef3b403e6669edb Mon Sep 17 00:00:00 2001 From: Thomas Keller Date: Sat, 24 Sep 2011 00:40:33 +0200 Subject: [PATCH 03/69] Add a sponsored-by flag in NEWS.mdtext. --- NEWS.mdtext | 3 +++ 1 file changed, 3 insertions(+) diff --git a/NEWS.mdtext b/NEWS.mdtext index 882a660..3078b76 100644 --- a/NEWS.mdtext +++ b/NEWS.mdtext @@ -1,5 +1,8 @@ # InDefero 1.2 - xxx xxx xx xx:xx 2011 UTC +The development of this version of Indefero has partially been sponsored by the friendly folks +from Scilab ! + ATTENTION: You need Pluf [324ae60b](http://projects.ceondo.com/p/pluf/source/commit/324ae60b) or newer to properly run this version of Indefero! From 7f610fd2f3f1d33b32e26b0ea1b9250032186fda Mon Sep 17 00:00:00 2001 From: Thomas Keller Date: Sun, 25 Sep 2011 01:27:36 +0200 Subject: [PATCH 04/69] Add an option to configure an external URL per project, e.g. to allow the linking of the home page of the project. This feature was kindly sponsored by Scilab. --- logo/external_link.svg | 284 ++++++++++++++++++ src/IDF/Form/Admin/ProjectCreate.php | 14 +- src/IDF/Form/Admin/ProjectUpdate.php | 24 +- src/IDF/Form/ProjectConf.php | 44 ++- src/IDF/Project.php | 20 +- src/IDF/Views.php | 42 +-- src/IDF/Views/Project.php | 2 +- src/IDF/templates/idf/admin/summary.html | 6 + src/IDF/templates/idf/base-full.html | 2 +- src/IDF/templates/idf/base.html | 2 +- .../templates/idf/gadmin/projects/create.html | 6 + .../templates/idf/gadmin/projects/update.html | 6 + src/IDF/templates/idf/index.html | 4 + www/media/idf/css/style.css | 6 + www/media/idf/img/external_link.png | Bin 0 -> 509 bytes 15 files changed, 427 insertions(+), 35 deletions(-) create mode 100644 logo/external_link.svg create mode 100644 www/media/idf/img/external_link.png diff --git a/logo/external_link.svg b/logo/external_link.svg new file mode 100644 index 0000000..e448789 --- /dev/null +++ b/logo/external_link.svg @@ -0,0 +1,284 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + diff --git a/src/IDF/Form/Admin/ProjectCreate.php b/src/IDF/Form/Admin/ProjectCreate.php index c39a028..12d27cd 100644 --- a/src/IDF/Form/Admin/ProjectCreate.php +++ b/src/IDF/Form/Admin/ProjectCreate.php @@ -72,6 +72,13 @@ class IDF_Form_Admin_ProjectCreate extends Pluf_Form 'widget_attrs' => array('size' => '35'), )); + $this->fields['external_project_url'] = new Pluf_Form_Field_Varchar( + array('required' => false, + 'label' => __('External URL'), + 'widget_attrs' => array('size' => '35'), + 'initial' => '', + )); + $this->fields['scm'] = new Pluf_Form_Field_Varchar( array('required' => true, 'label' => __('Repository type'), @@ -235,6 +242,11 @@ class IDF_Form_Admin_ProjectCreate extends Pluf_Form return $shortname; } + public function clean_external_project_url() + { + return IDF_Form_ProjectConf::checkWebURL($this->cleaned_data['external_project_url']); + } + public function clean() { if ($this->cleaned_data['scm'] != 'svn') { @@ -298,7 +310,7 @@ class IDF_Form_Admin_ProjectCreate extends Pluf_Form $conf = new IDF_Conf(); $conf->setProject($project); $keys = array('scm', 'svn_remote_url', 'svn_username', - 'svn_password', 'mtn_master_branch'); + 'svn_password', 'mtn_master_branch', 'external_project_url'); foreach ($keys as $key) { $this->cleaned_data[$key] = (!empty($this->cleaned_data[$key])) ? $this->cleaned_data[$key] : ''; diff --git a/src/IDF/Form/Admin/ProjectUpdate.php b/src/IDF/Form/Admin/ProjectUpdate.php index e600e08..c7cdc07 100644 --- a/src/IDF/Form/Admin/ProjectUpdate.php +++ b/src/IDF/Form/Admin/ProjectUpdate.php @@ -53,6 +53,13 @@ class IDF_Form_Admin_ProjectUpdate extends Pluf_Form 'widget_attrs' => array('size' => '35'), )); + $this->fields['external_project_url'] = new Pluf_Form_Field_Varchar( + array('required' => false, + 'label' => __('External URL'), + 'widget_attrs' => array('size' => '35'), + 'initial' => $conf->getVal('external_project_url'), + )); + if ($this->project->getConf()->getVal('scm') == 'mtn') { $this->fields['mtn_master_branch'] = new Pluf_Form_Field_Varchar( array('required' => false, @@ -115,6 +122,11 @@ class IDF_Form_Admin_ProjectUpdate extends Pluf_Form return IDF_Form_MembersConf::checkBadLogins($this->cleaned_data['members']); } + public function clean_external_project_url() + { + return IDF_Form_ProjectConf::checkWebURL($this->cleaned_data['external_project_url']); + } + public function save($commit=true) { if (!$this->isValid()) { @@ -127,10 +139,16 @@ class IDF_Form_Admin_ProjectUpdate extends Pluf_Form $this->project->shortdesc = $this->cleaned_data['shortdesc']; $this->project->update(); - $keys = array('mtn_master_branch'); + $conf = $this->project->getConf(); + $keys = array('mtn_master_branch', 'external_project_url'); foreach ($keys as $key) { - if (!empty($this->cleaned_data[$key])) { - $this->project->getConf()->setVal($key, $this->cleaned_data[$key]); + if (array_key_exists($key, $this->cleaned_data)) { + if (!empty($this->cleaned_data[$key])) { + $conf->setVal($key, $this->cleaned_data[$key]); + } + else { + $conf->delVal($key); + } } } } diff --git a/src/IDF/Form/ProjectConf.php b/src/IDF/Form/ProjectConf.php index 482320e..74257c2 100644 --- a/src/IDF/Form/ProjectConf.php +++ b/src/IDF/Form/ProjectConf.php @@ -32,6 +32,7 @@ class IDF_Form_ProjectConf extends Pluf_Form public function initFields($extra=array()) { $this->project = $extra['project']; + $conf = $this->project->getConf(); // Basic part $this->fields['name'] = new Pluf_Form_Field_Varchar(array('required' => true, @@ -51,6 +52,11 @@ class IDF_Form_ProjectConf extends Pluf_Form ), 'widget' => 'Pluf_Form_Widget_TextareaInput', )); + $this->fields['external_project_url'] = new Pluf_Form_Field_Varchar(array('required' => false, + 'label' => __('External URL'), + 'widget_attrs' => array('size' => '68'), + 'initial' => $conf->getVal('external_project_url'), + )); // Logo part $upload_path = Pluf::f('upload_path', false); @@ -118,20 +124,48 @@ class IDF_Form_ProjectConf extends Pluf_Form return $this->cleaned_data['logo']; } + public function clean_external_project_url() + { + return self::checkWebURL($this->cleaned_data['external_project_url']); + } + + public static function checkWebURL($url) + { + $url = trim($url); + if (empty($url)) { + return ''; + } + + $parsed = parse_url($url); + if ($parsed === false || !array_key_exists('scheme', $parsed) || + ($parsed['scheme'] != 'http' && $parsed['scheme'] != 'https')) { + throw new Pluf_Form_Invalid(__('The entered URL is invalid. Only http and https URLs are allowed.')); + } + + return $url; + } + public function save($commit=true) { - $conf = $this->project->getConf(); - // Basic part $this->project->name = $this->cleaned_data['name']; $this->project->shortdesc = $this->cleaned_data['shortdesc']; $this->project->description = $this->cleaned_data['description']; $this->project->update(); - // Logo part - if ($this->cleaned_data['logo'] !== "") { - $conf->setVal('logo', $this->cleaned_data['logo']); + $conf = $this->project->getConf(); + $keys = array('logo', 'external_project_url'); + foreach ($keys as $key) { + if (array_key_exists($key, $this->cleaned_data)) { + if (!empty($this->cleaned_data[$key])) { + $conf->setVal($key, $this->cleaned_data[$key]); + } + else { + $conf->delVal($key); + } + } } + if ($this->cleaned_data['logo_remove'] === true) { @unlink(Pluf::f('upload_path') . '/' . $this->project->shortname . $conf->getVal('logo')); $conf->delVal('logo'); diff --git a/src/IDF/Project.php b/src/IDF/Project.php index 66a070e..598f4a3 100644 --- a/src/IDF/Project.php +++ b/src/IDF/Project.php @@ -132,7 +132,7 @@ class IDF_Project extends Pluf_Model } return $projects[0]; } - + /** * Returns the number of open/closed issues. * @@ -167,7 +167,7 @@ GROUP BY uid"; $key = ($v['id'] === '-1') ? null : $v['id']; $ownerStatistics[$key] = (int)$v['nb']; } - + arsort($ownerStatistics); return $ownerStatistics; @@ -552,6 +552,22 @@ GROUP BY uid"; return $this->_pconf; } + /** + * Magic overload that falls back to the values of the internal configuration + * if no getter / caller matched + * + * @param string $key + */ + public function __get($key) + { + try { + return parent::__get($key); + } + catch (Exception $e) { + return $this->getConf()->getVal($key); + } + } + /** * Get simple statistics about the project. * diff --git a/src/IDF/Views.php b/src/IDF/Views.php index beca428..70bbdc3 100644 --- a/src/IDF/Views.php +++ b/src/IDF/Views.php @@ -40,10 +40,10 @@ class IDF_Views public function index($request, $match, $api=false) { $projects = self::getProjects($request->user); - $stats = self::getProjectsStatistics ($projects); - + $stats = self::getProjectsStatistics($projects); + if ($api == true) return $projects; - return Pluf_Shortcuts_RenderToResponse('idf/index.html', + return Pluf_Shortcuts_RenderToResponse('idf/index.html', array('page_title' => __('Projects'), 'projects' => $projects, 'stats' => new Pluf_Template_ContextVars($stats)), @@ -55,7 +55,7 @@ class IDF_Views */ public function login($request, $match) { - if (isset($request->POST['action']) + if (isset($request->POST['action']) and $request->POST['action'] == 'new-user') { $login = (isset($request->POST['login'])) ? $request->POST['login'] : ''; $url = Pluf_HTTP_URL_urlForView('IDF_Views::register', array(), @@ -91,7 +91,7 @@ class IDF_Views $params = array('request'=>$request); if ($request->method == 'POST') { $form = new IDF_Form_Register(array_merge( - (array)$request->POST, + (array)$request->POST, (array)$request->FILES ), $params); if ($form->isValid()) { @@ -108,7 +108,7 @@ class IDF_Views $context = new Pluf_Template_Context(array()); $tmpl = new Pluf_Template('idf/terms.html'); $terms = Pluf_Template::markSafe($tmpl->render($context)); - return Pluf_Shortcuts_RenderToResponse('idf/register/index.html', + return Pluf_Shortcuts_RenderToResponse('idf/register/index.html', array('page_title' => $title, 'form' => $form, 'terms' => $terms), @@ -133,7 +133,7 @@ class IDF_Views } else { $form = new IDF_Form_RegisterInputKey(); } - return Pluf_Shortcuts_RenderToResponse('idf/register/inputkey.html', + return Pluf_Shortcuts_RenderToResponse('idf/register/inputkey.html', array('page_title' => $title, 'form' => $form), $request); @@ -168,7 +168,7 @@ class IDF_Views $request->session->clear(); $request->session->setData('login_time', gmdate('Y-m-d H:i:s')); $user->last_login = gmdate('Y-m-d H:i:s'); - $user->update(); + $user->update(); $request->user->setMessage(__('Welcome! You can now participate in the life of your project of choice.')); $url = Pluf_HTTP_URL_urlForView('IDF_Views::index'); return new Pluf_HTTP_Response_Redirect($url); @@ -176,7 +176,7 @@ class IDF_Views } else { $form = new IDF_Form_RegisterConfirmation(null, $extra); } - return Pluf_Shortcuts_RenderToResponse('idf/register/confirmation.html', + return Pluf_Shortcuts_RenderToResponse('idf/register/confirmation.html', array('page_title' => $title, 'new_user' => $user, 'form' => $form), @@ -213,7 +213,7 @@ class IDF_Views /** * If the key is valid, provide a nice form to reset the password - * and automatically login the user. + * and automatically login the user. * * This is also firing the password change event for the plugins. */ @@ -238,7 +238,7 @@ class IDF_Views $request->session->clear(); $request->session->setData('login_time', gmdate('Y-m-d H:i:s')); $user->last_login = gmdate('Y-m-d H:i:s'); - $user->update(); + $user->update(); $request->user->setMessage(__('Welcome back! Next time, you can use your broswer options to remember the password.')); $url = Pluf_HTTP_URL_urlForView('IDF_Views::index'); return new Pluf_HTTP_Response_Redirect($url); @@ -246,12 +246,12 @@ class IDF_Views } else { $form = new IDF_Form_PasswordReset(null, $extra); } - return Pluf_Shortcuts_RenderToResponse('idf/user/passrecovery.html', + return Pluf_Shortcuts_RenderToResponse('idf/user/passrecovery.html', array('page_title' => $title, 'new_user' => $user, 'form' => $form), $request); - + } /** @@ -270,7 +270,7 @@ class IDF_Views } else { $form = new IDF_Form_PasswordInputKey(); } - return Pluf_Shortcuts_RenderToResponse('idf/user/passrecovery-inputkey.html', + return Pluf_Shortcuts_RenderToResponse('idf/user/passrecovery-inputkey.html', array('page_title' => $title, 'form' => $form), $request); @@ -283,7 +283,7 @@ class IDF_Views { $title = __('Here to Help You!'); $projects = self::getProjects($request->user); - return Pluf_Shortcuts_RenderToResponse('idf/faq.html', + return Pluf_Shortcuts_RenderToResponse('idf/faq.html', array( 'page_title' => $title, 'projects' => $projects, @@ -299,7 +299,7 @@ class IDF_Views { $title = __('InDefero API (Application Programming Interface)'); $projects = self::getProjects($request->user); - return Pluf_Shortcuts_RenderToResponse('idf/faq-api.html', + return Pluf_Shortcuts_RenderToResponse('idf/faq-api.html', array( 'page_title' => $title, 'projects' => $projects, @@ -335,7 +335,7 @@ class IDF_Views ); $sql = new Pluf_SQL("model_class='IDF_Project' AND owner_class='Pluf_User' AND owner_id=%s AND negative=".$false, $user->id); $rows = Pluf::factory('Pluf_RowPermission')->getList(array('filter' => $sql->gen())); - + $sql = sprintf('%s=%s', $db->qn('private'), $false); if ($rows->count() > 0) { $ids = array(); @@ -347,7 +347,7 @@ class IDF_Views return Pluf::factory('IDF_Project')->getList(array('filter' => $sql, 'order' => 'name ASC')); } - + /** * Returns statistics on a list of projects. * @@ -362,7 +362,7 @@ class IDF_Views 'issues' => 0, 'docpages' => 0, 'commits' => 0); - + // Count for each projects foreach ($projects as $p) { $pstats = $p->getStats (); @@ -372,7 +372,7 @@ class IDF_Views $forgestats['docpages'] += $pstats['docpages']; $forgestats['commits'] += $pstats['commits']; } - + // Count projects $forgestats['projects'] = count($projects); @@ -380,7 +380,7 @@ class IDF_Views $sql = new Pluf_SQL('first_name != %s', array('---')); $forgestats['members'] = Pluf::factory('Pluf_User') ->getCount(array('filter' => $sql->gen())); - + return $forgestats; } } diff --git a/src/IDF/Views/Project.php b/src/IDF/Views/Project.php index 11c08f4..70b2efa 100644 --- a/src/IDF/Views/Project.php +++ b/src/IDF/Views/Project.php @@ -305,7 +305,7 @@ class IDF_Views_Project return new Pluf_HTTP_Response_Redirect($url); } } else { - $form = new IDF_Form_ProjectConf($prj->getData(), $extra); + $form = new IDF_Form_ProjectConf(null, $extra); } $logo = $prj->getConf()->getVal('logo'); diff --git a/src/IDF/templates/idf/admin/summary.html b/src/IDF/templates/idf/admin/summary.html index 5a44503..c75391d 100644 --- a/src/IDF/templates/idf/admin/summary.html +++ b/src/IDF/templates/idf/admin/summary.html @@ -25,6 +25,12 @@ +{$form.f.external_project_url.labelTag}: +{if $form.f.external_project_url.errors}{$form.f.external_project_url.fieldErrors}{/if} +{$form.f.external_project_url|unsafe} + + + {$form.f.description.labelTag}: {if $form.f.description.errors}{$form.f.description.fieldErrors}{/if} {$form.f.description|unsafe} diff --git a/src/IDF/templates/idf/base-full.html b/src/IDF/templates/idf/base-full.html index 3f1c447..ff0d1e4 100644 --- a/src/IDF/templates/idf/base-full.html +++ b/src/IDF/templates/idf/base-full.html @@ -37,7 +37,7 @@
- {if $project}

{$project}{if $project.private}{trans 'Private project'}{/if}{$p}

{/if} + {if $project}

{$project}{if $project.private}{trans 'Private project'}{/if}{$p}{assign $url = $project.external_project_url}{if $url != ''} {/if}

{/if} {include 'idf/main-menu.html'}