2013-05-10 13:28:43 +02:00
|
|
|
<?php
|
2013-05-16 03:16:56 +02:00
|
|
|
/**
|
|
|
|
* PHPCI - Continuous Integration for PHP
|
|
|
|
*
|
|
|
|
* @copyright Copyright 2013, Block 8 Limited.
|
|
|
|
* @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md
|
|
|
|
* @link http://www.phptesting.org/
|
|
|
|
*/
|
2013-05-10 13:28:43 +02:00
|
|
|
|
|
|
|
namespace PHPCI\Controller;
|
2013-05-16 16:25:39 +02:00
|
|
|
|
|
|
|
use PHPCI\Model\Build;
|
|
|
|
use PHPCI\Model\Project;
|
2013-05-22 17:36:55 +02:00
|
|
|
use b8;
|
|
|
|
use b8\Controller;
|
|
|
|
use b8\Store;
|
2013-05-16 16:25:39 +02:00
|
|
|
use b8\Form;
|
|
|
|
use b8\Registry;
|
2013-05-10 13:28:43 +02:00
|
|
|
|
2013-05-16 03:30:48 +02:00
|
|
|
/**
|
|
|
|
* Project Controller - Allows users to create, edit and view projects.
|
|
|
|
* @author Dan Cryer <dan@block8.co.uk>
|
|
|
|
* @package PHPCI
|
|
|
|
* @subpackage Web
|
|
|
|
*/
|
2013-05-22 17:36:55 +02:00
|
|
|
class ProjectController extends \PHPCI\Controller
|
2013-05-10 13:28:43 +02:00
|
|
|
{
|
2013-05-16 16:25:39 +02:00
|
|
|
public function init()
|
|
|
|
{
|
2013-05-22 17:36:55 +02:00
|
|
|
$this->_buildStore = Store\Factory::getStore('Build');
|
|
|
|
$this->_projectStore = Store\Factory::getStore('Project');
|
2013-05-16 16:25:39 +02:00
|
|
|
}
|
|
|
|
|
2013-05-16 18:17:29 +02:00
|
|
|
/**
|
|
|
|
* View a specific project.
|
|
|
|
*/
|
2013-05-16 16:25:39 +02:00
|
|
|
public function view($projectId)
|
|
|
|
{
|
|
|
|
$project = $this->_projectStore->getById($projectId);
|
|
|
|
$page = $this->getParam('p', 1);
|
|
|
|
$builds = $this->getLatestBuildsHtml($projectId, (($page - 1) * 10));
|
|
|
|
|
2013-05-22 17:36:55 +02:00
|
|
|
$this->view->builds = $builds[0];
|
|
|
|
$this->view->total = $builds[1];
|
|
|
|
$this->view->project = $project;
|
|
|
|
$this->view->page = $page;
|
2013-05-16 16:25:39 +02:00
|
|
|
|
2013-05-22 17:36:55 +02:00
|
|
|
return $this->view->render();
|
2013-05-16 16:25:39 +02:00
|
|
|
}
|
|
|
|
|
2013-05-16 18:17:29 +02:00
|
|
|
/**
|
|
|
|
* Create a new pending build for a project.
|
|
|
|
*/
|
2013-05-16 16:25:39 +02:00
|
|
|
public function build($projectId)
|
|
|
|
{
|
|
|
|
$build = new Build();
|
|
|
|
$build->setProjectId($projectId);
|
|
|
|
$build->setCommitId('Manual');
|
|
|
|
$build->setStatus(0);
|
|
|
|
$build->setBranch('master');
|
|
|
|
$build->setCreated(new \DateTime());
|
|
|
|
|
|
|
|
$build = $this->_buildStore->save($build);
|
|
|
|
|
2013-05-17 18:34:31 +02:00
|
|
|
header('Location: '.PHPCI_URL.'build/view/' . $build->getId());
|
2013-07-15 22:28:22 +02:00
|
|
|
exit;
|
2013-05-16 16:25:39 +02:00
|
|
|
}
|
|
|
|
|
2013-05-16 18:17:29 +02:00
|
|
|
/**
|
|
|
|
* Delete a project.
|
|
|
|
*/
|
2013-05-16 17:46:30 +02:00
|
|
|
public function delete($projectId)
|
2013-05-16 16:25:39 +02:00
|
|
|
{
|
2013-05-22 17:36:55 +02:00
|
|
|
if (!$_SESSION['user']->getIsAdmin()) {
|
2013-05-16 16:25:39 +02:00
|
|
|
throw new \Exception('You do not have permission to do that.');
|
|
|
|
}
|
|
|
|
|
2013-05-16 17:46:30 +02:00
|
|
|
$project = $this->_projectStore->getById($projectId);
|
2013-05-16 16:25:39 +02:00
|
|
|
$this->_projectStore->delete($project);
|
|
|
|
|
2013-05-17 18:34:31 +02:00
|
|
|
header('Location: '.PHPCI_URL);
|
2013-07-25 23:48:08 +02:00
|
|
|
exit;
|
2013-05-16 16:25:39 +02:00
|
|
|
}
|
|
|
|
|
2013-05-16 18:17:29 +02:00
|
|
|
/**
|
|
|
|
* AJAX get latest builds.
|
|
|
|
*/
|
2013-05-16 16:25:39 +02:00
|
|
|
public function builds($projectId)
|
|
|
|
{
|
|
|
|
$builds = $this->getLatestBuildsHtml($projectId);
|
|
|
|
die($builds[0]);
|
|
|
|
}
|
|
|
|
|
2013-05-16 18:17:29 +02:00
|
|
|
/**
|
|
|
|
* Render latest builds for project as HTML table.
|
|
|
|
*/
|
2013-05-16 16:25:39 +02:00
|
|
|
protected function getLatestBuildsHtml($projectId, $start = 0)
|
|
|
|
{
|
2013-05-16 17:03:34 +02:00
|
|
|
$criteria = array('project_id' => $projectId);
|
|
|
|
$order = array('id' => 'DESC');
|
|
|
|
$builds = $this->_buildStore->getWhere($criteria, 10, $start, array(), $order);
|
2013-05-16 16:25:39 +02:00
|
|
|
$view = new b8\View('BuildsTable');
|
|
|
|
$view->builds = $builds['items'];
|
|
|
|
|
|
|
|
return array($view->render(), $builds['count']);
|
|
|
|
}
|
|
|
|
|
2013-05-16 18:17:29 +02:00
|
|
|
/**
|
|
|
|
* Add a new project. Handles both the form, and processing.
|
|
|
|
*/
|
2013-05-16 16:25:39 +02:00
|
|
|
public function add()
|
|
|
|
{
|
2013-05-22 17:36:55 +02:00
|
|
|
if (!$_SESSION['user']->getIsAdmin()) {
|
2013-05-16 16:25:39 +02:00
|
|
|
throw new \Exception('You do not have permission to do that.');
|
|
|
|
}
|
|
|
|
|
2013-05-22 17:36:55 +02:00
|
|
|
$method = $this->request->getMethod();
|
2013-05-17 22:39:32 +02:00
|
|
|
$this->handleGithubResponse();
|
2013-05-16 16:25:39 +02:00
|
|
|
|
|
|
|
if ($method == 'POST') {
|
|
|
|
$values = $this->getParams();
|
|
|
|
$pub = null;
|
|
|
|
} else {
|
|
|
|
$tempPath = sys_get_temp_dir() . '/';
|
|
|
|
|
|
|
|
// FastCGI fix for Windows machines, where temp path is not available to
|
|
|
|
// PHP, and defaults to the unwritable system directory. If the temp
|
|
|
|
// path is pointing to the system directory, shift to the 'TEMP'
|
|
|
|
// sub-folder, which should also exist, but actually be writable.
|
|
|
|
if ($tempPath == getenv("SystemRoot") . '/') {
|
|
|
|
$tempPath = getenv("SystemRoot") . '/TEMP/';
|
|
|
|
}
|
|
|
|
|
2013-05-16 17:46:30 +02:00
|
|
|
$keyFile = $tempPath . md5(microtime(true));
|
2013-05-16 16:25:39 +02:00
|
|
|
|
|
|
|
if (!is_dir($tempPath)) {
|
|
|
|
mkdir($tempPath);
|
|
|
|
}
|
|
|
|
|
2013-05-16 17:46:30 +02:00
|
|
|
shell_exec('ssh-keygen -q -t rsa -b 2048 -f '.$keyFile.' -N "" -C "deploy@phpci"');
|
2013-05-16 16:25:39 +02:00
|
|
|
|
2013-05-16 17:46:30 +02:00
|
|
|
$pub = file_get_contents($keyFile . '.pub');
|
|
|
|
$prv = file_get_contents($keyFile);
|
2013-05-16 16:25:39 +02:00
|
|
|
|
|
|
|
$values = array('key' => $prv, 'pubkey' => $pub, 'token' => $_SESSION['github_token']);
|
|
|
|
}
|
|
|
|
|
|
|
|
$form = $this->projectForm($values);
|
|
|
|
|
|
|
|
if ($method != 'POST' || ($method == 'POST' && !$form->validate())) {
|
|
|
|
$view = new b8\View('ProjectForm');
|
|
|
|
$view->type = 'add';
|
|
|
|
$view->project = null;
|
|
|
|
$view->form = $form;
|
|
|
|
$view->key = $pub;
|
2013-05-17 00:06:01 +02:00
|
|
|
$view->token = $_SESSION['github_token'];
|
2013-05-16 16:25:39 +02:00
|
|
|
|
|
|
|
return $view->render();
|
|
|
|
}
|
|
|
|
|
|
|
|
$values = $form->getValues();
|
|
|
|
$values['git_key'] = $values['key'];
|
|
|
|
|
|
|
|
$project = new Project();
|
|
|
|
$project->setValues($values);
|
|
|
|
|
|
|
|
$project = $this->_projectStore->save($project);
|
|
|
|
|
2013-05-17 18:34:31 +02:00
|
|
|
header('Location: '.PHPCI_URL.'project/view/' . $project->getId());
|
2013-05-16 16:25:39 +02:00
|
|
|
die;
|
|
|
|
}
|
|
|
|
|
2013-05-16 18:17:29 +02:00
|
|
|
/**
|
|
|
|
* Handles log in with Github
|
|
|
|
*/
|
2013-05-16 17:46:30 +02:00
|
|
|
protected function handleGithubResponse()
|
|
|
|
{
|
2013-05-17 17:24:17 +02:00
|
|
|
$github = \b8\Registry::getInstance()->get('github_app');
|
2013-05-16 17:46:30 +02:00
|
|
|
$code = $this->getParam('code', null);
|
|
|
|
|
|
|
|
if (!is_null($code)) {
|
|
|
|
$http = new \b8\HttpClient();
|
|
|
|
$url = 'https://github.com/login/oauth/access_token';
|
|
|
|
$params = array('client_id' => $github['id'], 'client_secret' => $github['secret'], 'code' => $code);
|
|
|
|
$resp = $http->post($url, $params);
|
|
|
|
|
|
|
|
if ($resp['success']) {
|
|
|
|
parse_str($resp['body'], $resp);
|
|
|
|
$_SESSION['github_token'] = $resp['access_token'];
|
2013-05-17 18:34:31 +02:00
|
|
|
header('Location: '.PHPCI_URL.'project/add');
|
2013-05-16 17:46:30 +02:00
|
|
|
die;
|
|
|
|
}
|
|
|
|
}
|
2013-05-17 22:42:29 +02:00
|
|
|
|
|
|
|
if (!isset($_SESSION['github_token'])) {
|
|
|
|
$_SESSION['github_token'] = null;
|
|
|
|
}
|
2013-05-16 17:46:30 +02:00
|
|
|
}
|
|
|
|
|
2013-05-16 18:17:29 +02:00
|
|
|
/**
|
|
|
|
* Edit a project. Handles both the form and processing.
|
|
|
|
*/
|
2013-05-16 17:46:30 +02:00
|
|
|
public function edit($projectId)
|
2013-05-16 16:25:39 +02:00
|
|
|
{
|
2013-05-22 17:36:55 +02:00
|
|
|
if (!$_SESSION['user']->getIsAdmin()) {
|
2013-05-16 16:25:39 +02:00
|
|
|
throw new \Exception('You do not have permission to do that.');
|
|
|
|
}
|
|
|
|
|
2013-05-22 17:36:55 +02:00
|
|
|
$method = $this->request->getMethod();
|
2013-05-16 17:46:30 +02:00
|
|
|
$project = $this->_projectStore->getById($projectId);
|
2013-05-16 16:25:39 +02:00
|
|
|
|
|
|
|
if ($method == 'POST') {
|
|
|
|
$values = $this->getParams();
|
|
|
|
} else {
|
|
|
|
$values = $project->getDataArray();
|
|
|
|
$values['key'] = $values['git_key'];
|
|
|
|
}
|
|
|
|
|
2013-05-16 17:46:30 +02:00
|
|
|
$form = $this->projectForm($values, 'edit/' . $projectId);
|
2013-05-16 16:25:39 +02:00
|
|
|
|
|
|
|
if ($method != 'POST' || ($method == 'POST' && !$form->validate())) {
|
|
|
|
$view = new b8\View('ProjectForm');
|
|
|
|
$view->type = 'edit';
|
|
|
|
$view->project = $project;
|
|
|
|
$view->form = $form;
|
|
|
|
$view->key = null;
|
|
|
|
|
|
|
|
return $view->render();
|
|
|
|
}
|
|
|
|
|
|
|
|
$values = $form->getValues();
|
|
|
|
$values['git_key'] = $values['key'];
|
|
|
|
|
|
|
|
$project->setValues($values);
|
|
|
|
$project = $this->_projectStore->save($project);
|
|
|
|
|
2013-05-17 18:34:31 +02:00
|
|
|
header('Location: '.PHPCI_URL.'project/view/' . $project->getId());
|
2013-05-16 16:25:39 +02:00
|
|
|
die;
|
|
|
|
}
|
|
|
|
|
2013-05-16 18:17:29 +02:00
|
|
|
/**
|
|
|
|
* Create add / edit project form.
|
|
|
|
*/
|
2013-05-16 16:25:39 +02:00
|
|
|
protected function projectForm($values, $type = 'add')
|
|
|
|
{
|
|
|
|
$form = new Form();
|
|
|
|
$form->setMethod('POST');
|
2013-05-17 18:34:31 +02:00
|
|
|
$form->setAction(PHPCI_URL.'project/' . $type);
|
2013-05-16 16:25:39 +02:00
|
|
|
$form->addField(new Form\Element\Csrf('csrf'));
|
|
|
|
$form->addField(new Form\Element\Hidden('token'));
|
|
|
|
$form->addField(new Form\Element\Hidden('pubkey'));
|
|
|
|
|
|
|
|
$options = array(
|
|
|
|
'choose' => 'Select repository type...',
|
|
|
|
'github' => 'Github',
|
|
|
|
'bitbucket' => 'Bitbucket',
|
2013-06-19 17:47:25 +02:00
|
|
|
'remote' => 'Remote URL',
|
2013-05-16 16:25:39 +02:00
|
|
|
'local' => 'Local Path'
|
|
|
|
);
|
|
|
|
|
|
|
|
$field = new Form\Element\Select('type');
|
|
|
|
$field->setRequired(true);
|
2013-06-19 17:47:25 +02:00
|
|
|
$field->setPattern('^(github|bitbucket|remote|local)');
|
2013-05-16 16:25:39 +02:00
|
|
|
$field->setOptions($options);
|
|
|
|
$field->setLabel('Where is your project hosted?');
|
|
|
|
$field->setClass('span4');
|
|
|
|
$form->addField($field);
|
|
|
|
|
|
|
|
if (isset($_SESSION['github_token'])) {
|
|
|
|
$field = new Form\Element\Select('github');
|
|
|
|
$field->setLabel('Choose a Github repository:');
|
|
|
|
$field->setClass('span4');
|
|
|
|
$field->setOptions($this->getGithubRepositories());
|
|
|
|
$form->addField($field);
|
|
|
|
}
|
|
|
|
|
|
|
|
$referenceValidator = function ($val) use ($values) {
|
|
|
|
$type = $values['type'];
|
|
|
|
|
|
|
|
switch($type) {
|
2013-06-19 17:47:25 +02:00
|
|
|
case 'remote':
|
|
|
|
if (!preg_match('/^(git|https?):\/\//', $val)) {
|
|
|
|
throw new \Exception('Repository URL must be start with git://, http:// or https://.');
|
|
|
|
}
|
|
|
|
break;
|
2013-05-16 16:25:39 +02:00
|
|
|
case 'local':
|
|
|
|
if (!is_dir($val)) {
|
|
|
|
throw new \Exception('The path you specified does not exist.');
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case 'github':
|
|
|
|
case 'bitbucket':
|
2013-06-19 11:46:12 +02:00
|
|
|
if (!preg_match('/^[a-zA-Z0-9_\-]+\/[a-zA-Z0-9_\-\.]+$/', $val)) {
|
2013-05-16 16:25:39 +02:00
|
|
|
throw new \Exception('Repository name must be in the format "owner/repo".');
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
};
|
|
|
|
|
|
|
|
$field = new Form\Element\Text('reference');
|
|
|
|
$field->setRequired(true);
|
|
|
|
$field->setValidator($referenceValidator);
|
|
|
|
$field->setLabel('Repository Name / URL (Remote) or Path (Local)');
|
|
|
|
$field->setClass('span4');
|
|
|
|
$form->addField($field);
|
|
|
|
|
|
|
|
$field = new Form\Element\Text('title');
|
|
|
|
$field->setRequired(true);
|
|
|
|
$field->setLabel('Project Title');
|
|
|
|
$field->setClass('span4');
|
|
|
|
$form->addField($field);
|
|
|
|
|
|
|
|
$field = new Form\Element\TextArea('key');
|
|
|
|
$field->setRequired(false);
|
|
|
|
$field->setLabel('Private key to use to access repository (leave blank for local and/or anonymous remotes)');
|
|
|
|
$field->setClass('span7');
|
|
|
|
$field->setRows(6);
|
|
|
|
$form->addField($field);
|
|
|
|
|
|
|
|
$field = new Form\Element\Submit();
|
|
|
|
$field->setValue('Save Project');
|
|
|
|
$field->setClass('btn-success');
|
|
|
|
$form->addField($field);
|
|
|
|
|
|
|
|
$form->setValues($values);
|
|
|
|
return $form;
|
|
|
|
}
|
|
|
|
|
2013-05-16 18:17:29 +02:00
|
|
|
/**
|
|
|
|
* Get an array of repositories from Github's API.
|
|
|
|
*/
|
2013-05-16 16:25:39 +02:00
|
|
|
protected function getGithubRepositories()
|
|
|
|
{
|
|
|
|
$http = new \b8\HttpClient();
|
|
|
|
$url = 'https://api.github.com/user/repos';
|
|
|
|
$res = $http->get($url, array('type' => 'all', 'access_token' => $_SESSION['github_token']));
|
|
|
|
|
|
|
|
$rtn = array();
|
|
|
|
$rtn['choose'] = 'Select a repository...';
|
|
|
|
if ($res['success']) {
|
|
|
|
foreach ($res['body'] as $repo) {
|
|
|
|
$rtn[$repo['full_name']] = $repo['full_name'];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $rtn;
|
|
|
|
}
|
|
|
|
}
|