diff --git a/PHPCI/Command/CreateAdminCommand.php b/PHPCI/Command/CreateAdminCommand.php index 30d800b1..fc40c307 100644 --- a/PHPCI/Command/CreateAdminCommand.php +++ b/PHPCI/Command/CreateAdminCommand.php @@ -9,6 +9,7 @@ namespace PHPCI\Command; +use PHPCI\Service\UserService; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; @@ -37,7 +38,9 @@ class CreateAdminCommand extends Command */ protected function execute(InputInterface $input, OutputInterface $output) { - + $userStore = Factory::getStore('User'); + $userService = new UserService($userStore); + require(PHPCI_DIR . 'bootstrap.php'); // Try to create a user account: @@ -51,15 +54,7 @@ class CreateAdminCommand extends Command $adminName = $this->ask('Admin name: '); try { - $user = new \PHPCI\Model\User(); - $user->setEmail($adminEmail); - $user->setName($adminName); - $user->setIsAdmin(1); - $user->setHash(password_hash($adminPass, PASSWORD_DEFAULT)); - - $store = \b8\Store\Factory::getStore('User'); - $store->save($user); - + $userService->createUser($adminName, $adminEmail, $adminPass, 1); print 'User account created!' . PHP_EOL; } catch (\Exception $ex) { print 'There was a problem creating your account. :(' . PHP_EOL; diff --git a/PHPCI/Command/InstallCommand.php b/PHPCI/Command/InstallCommand.php index 43495dc0..e4af4722 100644 --- a/PHPCI/Command/InstallCommand.php +++ b/PHPCI/Command/InstallCommand.php @@ -21,7 +21,7 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Helper\DialogHelper; -use PHPCI\Model\User; +use PHPCI\Service\UserService; /** @@ -236,16 +236,11 @@ class InstallCommand extends Command $adminName = $dialog->ask($output, 'Enter your name: '); try { - $user = new User(); - $user->setEmail($adminEmail); - $user->setName($adminName); - $user->setIsAdmin(1); - $user->setHash(password_hash($adminPass, PASSWORD_DEFAULT)); - $this->reloadConfig(); - $store = Factory::getStore('User'); - $store->save($user); + $userStore = Factory::getStore('User'); + $userService = new UserService($userStore); + $userService->createUser($adminName, $adminEmail, $adminPass, 1); $output->writeln('User account created!'); } catch (\Exception $ex) { diff --git a/PHPCI/Controller/BuildController.php b/PHPCI/Controller/BuildController.php index 145236d5..fb9957cb 100644 --- a/PHPCI/Controller/BuildController.php +++ b/PHPCI/Controller/BuildController.php @@ -13,6 +13,7 @@ use b8; use b8\Exception\HttpException\NotFoundException; use PHPCI\BuildFactory; use PHPCI\Model\Build; +use PHPCI\Service\BuildService; /** * Build Controller - Allows users to run and view builds. @@ -26,10 +27,16 @@ class BuildController extends \PHPCI\Controller * @var \PHPCI\Store\BuildStore */ protected $buildStore; + + /** + * @var \PHPCI\Service\BuildService + */ + protected $buildService; public function init() { - $this->buildStore = b8\Store\Factory::getStore('Build'); + $this->buildStore = b8\Store\Factory::getStore('Build'); + $this->buildService = new BuildService($this->buildStore); } /** @@ -123,17 +130,7 @@ class BuildController extends \PHPCI\Controller throw new NotFoundException('Build with ID: ' . $buildId . ' does not exist.'); } - $build = new Build(); - $build->setProjectId($copy->getProjectId()); - $build->setCommitId($copy->getCommitId()); - $build->setStatus(Build::STATUS_NEW); - $build->setBranch($copy->getBranch()); - $build->setCreated(new \DateTime()); - $build->setCommitterEmail($copy->getCommitterEmail()); - $build->setCommitMessage($copy->getCommitMessage()); - $build->setExtra(json_encode($copy->getExtra())); - - $build = $this->buildStore->save($build); + $build = $this->buildService->createDuplicateBuild($copy); header('Location: '.PHPCI_URL.'build/view/' . $build->getId()); exit; @@ -154,7 +151,7 @@ class BuildController extends \PHPCI\Controller throw new NotFoundException('Build with ID: ' . $buildId . ' does not exist.'); } - $this->buildStore->delete($build); + $this->buildService->deleteBuild($build); header('Location: '.PHPCI_URL.'project/view/' . $build->getProjectId()); exit; diff --git a/PHPCI/Controller/ProjectController.php b/PHPCI/Controller/ProjectController.php index 38986c59..56dc9ed0 100644 --- a/PHPCI/Controller/ProjectController.php +++ b/PHPCI/Controller/ProjectController.php @@ -20,6 +20,8 @@ use PHPCI\Helper\Github; use PHPCI\Helper\SshKey; use PHPCI\Model\Build; use PHPCI\Model\Project; +use PHPCI\Service\BuildService; +use PHPCI\Service\ProjectService; /** * Project Controller - Allows users to create, edit and view projects. @@ -29,20 +31,32 @@ use PHPCI\Model\Project; */ class ProjectController extends \PHPCI\Controller { + /** + * @var \PHPCI\Store\ProjectStore + */ + protected $projectStore; + + /** + * @var \PHPCI\Service\ProjectService + */ + protected $projectService; + /** * @var \PHPCI\Store\BuildStore */ protected $buildStore; /** - * @var \PHPCI\Store\ProjectStore + * @var \PHPCI\Service\BuildService */ - protected $projectStore; + protected $buildService; public function init() { $this->buildStore = Store\Factory::getStore('Build'); $this->projectStore = Store\Factory::getStore('Project'); + $this->projectService = new ProjectService($this->projectStore); + $this->buildService = new BuildService($this->buildStore); } /** @@ -88,15 +102,7 @@ class ProjectController extends \PHPCI\Controller throw new NotFoundException('Project with id: ' . $projectId . ' not found'); } - $build = new Build(); - $build->setProjectId($projectId); - $build->setCommitId('Manual'); - $build->setStatus(Build::STATUS_NEW); - $build->setBranch($project->getBranch()); - $build->setCreated(new \DateTime()); - $build->setCommitterEmail($_SESSION['user']->getEmail()); - - $build = $this->buildStore->save($build); + $build = $this->buildService->createBuild($project, null, null, $_SESSION['user']->getEmail()); header('Location: '.PHPCI_URL.'build/view/' . $build->getId()); exit; @@ -112,7 +118,7 @@ class ProjectController extends \PHPCI\Controller } $project = $this->projectStore->getById($projectId); - $this->projectStore->delete($project); + $this->projectService->deleteProject($project); header('Location: '.PHPCI_URL); exit; @@ -179,35 +185,24 @@ class ProjectController extends \PHPCI\Controller return $view->render(); } else { - return $this->addProject($form); + $title = $this->getParam('title', 'New Project'); + $reference = $this->getParam('reference', null); + $type = $this->getParam('type', null); + + $options = array( + 'ssh_private_key' => $this->getParam('key', null), + 'ssh_public_key' => $this->getParam('pubkey', null), + 'build_config' => $this->getParam('build_config', null), + 'allow_public_status' => $this->getParam('allow_public_status', 0), + 'branch' => $this->getParam('branch', null), + ); + + $project = $this->projectService->createProject($title, $type, $reference, $options); + header('Location: '.PHPCI_URL.'project/view/' . $project->getId()); + die; } } - protected function addProject(Form $form) - { - $values = $form->getValues(); - - $matches = array(); - if ($values['type'] == "gitlab" && preg_match('`^(.*)@(.*):(.*)/(.*)\.git`', $values['reference'], $matches)) { - $info = array(); - $info['user'] = $matches[1]; - $info['domain'] = $matches[2]; - $values['access_information'] = serialize($info); - $values['reference'] = $matches[3]."/".$matches[4]; - } - - $values['ssh_private_key'] = $values['key']; - $values['ssh_public_key'] = $values['pubkey']; - - $project = new Project(); - $project->setValues($values); - - $project = $this->projectStore->save($project); - - header('Location: '.PHPCI_URL.'project/view/' . $project->getId()); - die; - } - /** * Edit a project. Handles both the form and processing. */ @@ -252,21 +247,19 @@ class ProjectController extends \PHPCI\Controller return $view->render(); } - $values = $form->getValues(); - $values['ssh_private_key'] = $values['key']; - $values['ssh_public_key'] = $values['pubkey']; + $title = $this->getParam('title', 'New Project'); + $reference = $this->getParam('reference', null); + $type = $this->getParam('type', null); - if ($values['type'] == "gitlab") { - preg_match('`^(.*)@(.*):(.*)/(.*)\.git`', $values['reference'], $matches); - $info = array(); - $info["user"] = $matches[1]; - $info["domain"] = $matches[2]; - $values['access_information'] = serialize($info); - $values['reference'] = $matches[3] . "/" . $matches[4]; - } + $options = array( + 'ssh_private_key' => $this->getParam('key', null), + 'ssh_public_key' => $this->getParam('pubkey', null), + 'build_config' => $this->getParam('build_config', null), + 'allow_public_status' => $this->getParam('allow_public_status', 0), + 'branch' => $this->getParam('branch', null), + ); - $project->setValues($values); - $project = $this->projectStore->save($project); + $project = $this->projectService->updateProject($project, $title, $type, $reference, $options); header('Location: '.PHPCI_URL.'project/view/' . $project->getId()); die; @@ -337,7 +330,7 @@ class ProjectController extends \PHPCI\Controller $field = Form\Element\Checkbox::create('allow_public_status', $label, false); $field->setContainerClass('form-group'); $field->setCheckedValue(1); - $field->setValue(1); + $field->setValue(0); $form->addField($field); $field = new Form\Element\Submit(); diff --git a/PHPCI/Controller/SettingsController.php b/PHPCI/Controller/SettingsController.php index 51edf749..34eac9be 100644 --- a/PHPCI/Controller/SettingsController.php +++ b/PHPCI/Controller/SettingsController.php @@ -81,6 +81,8 @@ class SettingsController extends Controller public function email() { $this->settings['phpci']['email_settings'] = $this->getParams(); + $this->settings['phpci']['email_settings']['smtp_encryption'] = $this->getParam('smtp_encryption', 0); + $error = $this->storeSettings(); if ($error) { diff --git a/PHPCI/Controller/UserController.php b/PHPCI/Controller/UserController.php index 86fa8662..2a675e9d 100644 --- a/PHPCI/Controller/UserController.php +++ b/PHPCI/Controller/UserController.php @@ -15,6 +15,7 @@ use b8\Exception\HttpException\NotFoundException; use b8\Form; use PHPCI\Controller; use PHPCI\Model\User; +use PHPCI\Service\UserService; /** * User Controller - Allows an administrator to view, add, edit and delete users. @@ -29,9 +30,15 @@ class UserController extends Controller */ protected $userStore; + /** + * @var \PHPCI\Service\UserService + */ + protected $userService; + public function init() { $this->userStore = b8\Store\Factory::getStore('User'); + $this->userService = new UserService($this->userStore); } /** @@ -53,16 +60,11 @@ class UserController extends Controller $values = $user->getDataArray(); if ($this->request->getMethod() == 'POST') { - $values = $this->getParams(); + $name = $this->getParam('name', null); + $email = $this->getParam('email', null); + $password = $this->getParam('password', null); - if (!empty($values['password'])) { - $values['hash'] = password_hash($values['password'], PASSWORD_DEFAULT); - } - - $this->view->updated = true; - - $user->setValues($values); - $_SESSION['user'] = $this->userStore->save($user); + $_SESSION['user'] = $this->userService->updateUser($name, $email, $password); } $form = new Form(); @@ -132,13 +134,13 @@ class UserController extends Controller return $view->render(); } - $values = $form->getValues(); - $values['hash'] = password_hash($values['password'], PASSWORD_DEFAULT); - $user = new User(); - $user->setValues($values); + $name = $this->getParam('name', null); + $email = $this->getParam('email', null); + $password = $this->getParam('password', null); + $isAdmin = (int)$this->getParam('is_admin', 0); - $user = $this->userStore->save($user); + $this->userService->createUser($name, $email, $password, $isAdmin); header('Location: '.PHPCI_URL.'user'); die; @@ -172,18 +174,12 @@ class UserController extends Controller return $view->render(); } - if (!empty($values['password'])) { - $values['hash'] = password_hash($values['password'], PASSWORD_DEFAULT); - } + $name = $this->getParam('name', null); + $email = $this->getParam('email', null); + $password = $this->getParam('password', null); + $isAdmin = (int)$this->getParam('is_admin', 0); - $user->setValues($values); - - $isAdmin = $this->getParam('is_admin'); - if (empty($isAdmin)) { - $user->setIsAdmin(0); - } - - $this->userStore->save($user); + $this->userService->updateUser($user, $name, $email, $password, $isAdmin); header('Location: '.PHPCI_URL.'user'); die; @@ -258,7 +254,7 @@ class UserController extends Controller throw new NotFoundException('User with ID: ' . $userId . ' does not exist.'); } - $this->userStore->delete($user); + $this->userService->deleteUser($user); header('Location: '.PHPCI_URL.'user'); die; diff --git a/PHPCI/Controller/WebhookController.php b/PHPCI/Controller/WebhookController.php index e95a5597..4d4501ed 100644 --- a/PHPCI/Controller/WebhookController.php +++ b/PHPCI/Controller/WebhookController.php @@ -13,6 +13,7 @@ use b8; use b8\Store; use PHPCI\BuildFactory; use PHPCI\Model\Build; +use PHPCI\Service\BuildService; /** * Webhook Controller - Processes webhook pings from BitBucket, Github, Gitlab, etc. @@ -29,9 +30,21 @@ class WebhookController extends \PHPCI\Controller */ protected $buildStore; + /** + * @var \PHPCI\Store\ProjectStore + */ + protected $projectStore; + + /** + * @var \PHPCI\Service\BuildService + */ + protected $buildService; + public function init() { $this->buildStore = Store\Factory::getStore('Build'); + $this->projectStore = Store\Factory::getStore('Project'); + $this->buildService = new BuildService($this->buildStore); } /** @@ -238,22 +251,15 @@ class WebhookController extends \PHPCI\Controller return true; } - // If not, create a new build job for it: - $build = new Build(); - $build->setProjectId($projectId); - $build->setCommitId($commitId); - $build->setStatus(Build::STATUS_NEW); - $build->setLog(''); - $build->setCreated(new \DateTime()); - $build->setBranch($branch); - $build->setCommitterEmail($committer); - $build->setCommitMessage($commitMessage); + $project = $this->projectStore->getById($projectId); - if (!is_null($extra)) { - $build->setExtra(json_encode($extra)); + if (empty($project)) { + throw new \Exception('Project does not exist:' . $projectId); } - $build = BuildFactory::getBuild($this->buildStore->save($build)); + // If not, create a new build job for it: + $build = $this->buildService->createBuild($project, $commitId, $branch, $committer, $commitMessage, $extra); + $build = BuildFactory::getBuild($build); // Send a status postback if the build type provides one: $build->sendStatusPostback(); diff --git a/PHPCI/Helper/BaseCommandExecutor.php b/PHPCI/Helper/BaseCommandExecutor.php index 554d9083..b1cdbc26 100644 --- a/PHPCI/Helper/BaseCommandExecutor.php +++ b/PHPCI/Helper/BaseCommandExecutor.php @@ -30,6 +30,7 @@ abstract class BaseCommandExecutor implements CommandExecutor protected $verbose; protected $lastOutput; + protected $lastError; public $logExecOutput = true; @@ -78,16 +79,40 @@ abstract class BaseCommandExecutor implements CommandExecutor } $status = 0; - exec($command, $this->lastOutput, $status); + $descriptorSpec = array( + 0 => array("pipe", "r"), // stdin + 1 => array("pipe", "w"), // stdout + 2 => array("pipe", "w"), // stderr + ); - foreach ($this->lastOutput as &$lastOutput) { - $lastOutput = trim($lastOutput, '"'); + $pipes = array(); + + $process = proc_open($command, $descriptorSpec, $pipes, dirname($this->buildPath), null); + + if (is_resource($process)) { + fclose($pipes[0]); + + $this->lastOutput = stream_get_contents($pipes[1]); + $this->lastError = stream_get_contents($pipes[2]); + + fclose($pipes[1]); + fclose($pipes[2]); + + $status = proc_close($process); } - if ($this->logExecOutput && !empty($this->lastOutput) && ($this->verbose|| $status != 0)) { + $this->lastOutput = array_filter(explode(PHP_EOL, $this->lastOutput)); + + $shouldOutput = ($this->logExecOutput && ($this->verbose || $status != 0)); + + if ($shouldOutput && !empty($this->lastOutput)) { $this->logger->log($this->lastOutput); } + if (!empty($this->lastError)) { + $this->logger->log("\033[0;31m" . $this->lastError . "\033[0m", LogLevel::ERROR); + } + $rtn = false; if ($status == 0) { @@ -105,6 +130,14 @@ abstract class BaseCommandExecutor implements CommandExecutor return implode(PHP_EOL, $this->lastOutput); } + /** + * Returns the stderr output from the last command run. + */ + public function getLastError() + { + return $this->lastError; + } + /** * Find a binary required by a plugin. * @param string $binary @@ -170,8 +203,11 @@ abstract class BaseCommandExecutor implements CommandExecutor $composer = $path.'/composer.json'; if (is_file($composer)) { $json = json_decode(file_get_contents($composer)); + if (isset($json->config->{"bin-dir"})) { return $path.'/'.$json->config->{"bin-dir"}; + } elseif (is_dir($path . '/vendor/bin')) { + return $path . '/vendor/bin'; } } } diff --git a/PHPCI/Helper/MailerFactory.php b/PHPCI/Helper/MailerFactory.php index 81d37831..b0ec4860 100644 --- a/PHPCI/Helper/MailerFactory.php +++ b/PHPCI/Helper/MailerFactory.php @@ -40,7 +40,7 @@ class MailerFactory return \Swift_Mailer::newInstance($transport); } - protected function getMailConfig($configName) + public function getMailConfig($configName) { if (isset($this->emailConfig[$configName]) && $this->emailConfig[$configName] != "") { return $this->emailConfig[$configName]; diff --git a/PHPCI/Helper/SshKey.php b/PHPCI/Helper/SshKey.php index 087b0ed3..f36940ca 100644 --- a/PHPCI/Helper/SshKey.php +++ b/PHPCI/Helper/SshKey.php @@ -29,7 +29,7 @@ class SshKey mkdir($tempPath); } - $return = array(); + $return = array('private_key' => '', 'public_key' => ''); if ($this->canGenerateKeys()) { shell_exec('ssh-keygen -q -t rsa -b 2048 -f '.$keyFile.' -N "" -C "deploy@phpci"'); @@ -37,15 +37,13 @@ class SshKey $pub = file_get_contents($keyFile . '.pub'); $prv = file_get_contents($keyFile); - if (empty($pub)) { - $pub = ''; + if (!empty($pub)) { + $return['public_key'] = $pub; } - if (empty($prv)) { - $prv = ''; + if (!empty($prv)) { + $return['private_key'] = $prv; } - - $return = array('private_key' => $prv, 'public_key' => $pub); } return $return; diff --git a/PHPCI/Helper/User.php b/PHPCI/Helper/User.php index 726003fd..065deacf 100644 --- a/PHPCI/Helper/User.php +++ b/PHPCI/Helper/User.php @@ -20,6 +20,11 @@ class User public function __call($method, $params = array()) { $user = $_SESSION['user']; + + if (!is_object($user)) { + return null; + } + return call_user_func_array(array($user, $method), $params); } } diff --git a/PHPCI/Logging/BuildDBLogHandler.php b/PHPCI/Logging/BuildDBLogHandler.php index 3c03c501..782471db 100644 --- a/PHPCI/Logging/BuildDBLogHandler.php +++ b/PHPCI/Logging/BuildDBLogHandler.php @@ -9,7 +9,7 @@ namespace PHPCI\Logging; -use b8\Store; +use b8\Store\Factory; use Monolog\Handler\AbstractProcessingHandler; use PHPCI\Model\Build; @@ -40,5 +40,7 @@ class BuildDBLogHandler extends AbstractProcessingHandler $this->logValue .= $message . PHP_EOL; $this->build->setLog($this->logValue); + + Factory::getStore('Build')->save($this->build); } } diff --git a/PHPCI/Migrations/20140730143702_fix_database_columns.php b/PHPCI/Migrations/20140730143702_fix_database_columns.php new file mode 100644 index 00000000..6594dd02 --- /dev/null +++ b/PHPCI/Migrations/20140730143702_fix_database_columns.php @@ -0,0 +1,49 @@ +table('build'); + $build->changeColumn('project_id', 'integer', array('null' => false)); + $build->changeColumn('commit_id', 'string', array('limit' => 50, 'null' => false)); + $build->changeColumn('status', 'integer', array('null' => false)); + $build->changeColumn('log', 'text', array('null' => true, 'default' => '')); + $build->changeColumn('branch', 'string', array('limit' => 50, 'null' => false, 'default' => 'master')); + $build->changeColumn('created', 'datetime', array('null' => true)); + $build->changeColumn('started', 'datetime', array('null' => true)); + $build->changeColumn('finished', 'datetime', array('null' => true)); + $build->changeColumn('committer_email', 'string', array('limit' => 512, 'null' => true)); + $build->changeColumn('commit_message', 'text', array('null' => true)); + $build->changeColumn('extra', 'text', array('null' => true)); + + $buildMeta = $this->table('build_meta'); + $buildMeta->changeColumn('project_id', 'integer', array('null' => false)); + $buildMeta->changeColumn('build_id', 'integer', array('null' => false)); + $buildMeta->changeColumn('meta_key', 'string', array('limit' => 250, 'null' => false)); + $buildMeta->changeColumn('meta_value', 'text', array('null' => false)); + + $project = $this->table('project'); + $project->changeColumn('title', 'string', array('limit' => 250, 'null' => false)); + $project->changeColumn('reference', 'string', array('limit' => 250, 'null' => false)); + $project->changeColumn('branch', 'string', array('limit' => 50, 'null' => false, 'default' => 'master')); + $project->changeColumn('ssh_private_key', 'text', array('null' => true, 'default' => null)); + $project->changeColumn('ssh_public_key', 'text', array('null' => true, 'default' => null)); + $project->changeColumn('type', 'string', array('limit' => 50, 'null' => false)); + $project->changeColumn('access_information', 'string', array('limit' => 250, 'null' => true, 'default' => null)); + $project->changeColumn('last_commit', 'string', array('limit' => 250, 'null' => true, 'default' => null)); + $project->changeColumn('ssh_public_key', 'text', array('null' => true, 'default' => null)); + $project->changeColumn('allow_public_status', 'integer', array('null' => false, 'default' => 0)); + + $user = $this->table('user'); + $user->changeColumn('email', 'string', array('limit' => 250, 'null' => false)); + $user->changeColumn('hash', 'string', array('limit' => 250, 'null' => false)); + $user->changeColumn('is_admin', 'integer', array('null' => false, 'default' => 0)); + $user->changeColumn('name', 'string', array('limit' => 250, 'null' => false)); + } +} diff --git a/PHPCI/Model/Base/ProjectBase.php b/PHPCI/Model/Base/ProjectBase.php index b56279b5..5e1f4f37 100644 --- a/PHPCI/Model/Base/ProjectBase.php +++ b/PHPCI/Model/Base/ProjectBase.php @@ -36,6 +36,7 @@ class ProjectBase extends Model 'id' => null, 'title' => null, 'reference' => null, + 'branch' => null, 'ssh_private_key' => null, 'ssh_public_key' => null, 'type' => null, @@ -200,17 +201,15 @@ class ProjectBase extends Model } /** - * Get the value of Branch / branch. - * - * @return string - */ + * Get the value of Branch / branch. + * + * @return string + */ public function getBranch() { - if (empty($this->data['branch'])) { - return $this->getType() === 'hg' ? 'default' : 'master'; - } else { - return $this->data['branch']; - } + $rtn = $this->data['branch']; + + return $rtn; } /** @@ -365,6 +364,7 @@ class ProjectBase extends Model */ public function setBranch($value) { + $this->_validateNotNull('Branch', $value); $this->_validateString('Branch', $value); if ($this->data['branch'] === $value) { diff --git a/PHPCI/Model/Build.php b/PHPCI/Model/Build.php index 4ca3cf9e..0286b3b8 100644 --- a/PHPCI/Model/Build.php +++ b/PHPCI/Model/Build.php @@ -38,15 +38,6 @@ class Build extends BuildBase return '#'; } - /** - * @return string - */ - public function getProjectTitle() - { - $project = $this->getProject(); - return $project ? $project->getTitle() : ""; - } - /** * Get link to branch from another source (i.e. Github) */ @@ -55,6 +46,11 @@ class Build extends BuildBase return '#'; } + public function getFileLinkTemplate() + { + return null; + } + /** * Send status updates to any relevant third parties (i.e. Github) */ @@ -63,6 +59,15 @@ class Build extends BuildBase return; } + /** + * @return string + */ + public function getProjectTitle() + { + $project = $this->getProject(); + return $project ? $project->getTitle() : ""; + } + /** * Store build metadata */ @@ -160,11 +165,6 @@ class Build extends BuildBase return $config; } - public function getFileLinkTemplate() - { - return null; - } - public function getExtra($key = null) { $data = json_decode($this->data['extra'], true); diff --git a/PHPCI/Model/Build/GithubBuild.php b/PHPCI/Model/Build/GithubBuild.php index 6d3f217b..e0ff0fc0 100644 --- a/PHPCI/Model/Build/GithubBuild.php +++ b/PHPCI/Model/Build/GithubBuild.php @@ -109,8 +109,19 @@ class GithubBuild extends RemoteGitBuild public function getFileLinkTemplate() { - $link = 'https://github.com/' . $this->getProject()->getReference() . '/'; - $link .= 'blob/' . $this->getBranch() . '/'; + $reference = $this->getProject()->getReference(); + $branch = $this->getBranch(); + + if ($this->getExtra('build_type') == 'pull_request') { + $matches = array(); + preg_match('/\/([a-zA-Z0-9_\-]+\/[a-zA-Z0-9_\-]+)/', $this->getExtra('remote_url'), $matches); + + $reference = $matches[1]; + $branch = $this->getExtra('remote_branch'); + } + + $link = 'https://github.com/' . $reference . '/'; + $link .= 'blob/' . $branch . '/'; $link .= '{FILE}'; $link .= '#L{LINE}'; diff --git a/PHPCI/Model/Build/GitlabBuild.php b/PHPCI/Model/Build/GitlabBuild.php index 83053090..086bc046 100644 --- a/PHPCI/Model/Build/GitlabBuild.php +++ b/PHPCI/Model/Build/GitlabBuild.php @@ -61,7 +61,17 @@ class GitlabBuild extends RemoteGitBuild if (!empty($key)) { $user = $this->getProject()->getAccessInformation("user"); $domain = $this->getProject()->getAccessInformation("domain"); - return $user . '@' . $domain . ':' . $this->getProject()->getReference() . '.git'; + $port = $this->getProject()->getAccessInformation('port'); + + $url = $user . '@' . $domain . ':'; + + if (!empty($port)) { + $url .= $port . '/'; + } + + $url .= $this->getProject()->getReference() . '.git'; + + return $url; } } } diff --git a/PHPCI/Model/Project.php b/PHPCI/Model/Project.php index 55df01c5..702d9c89 100644 --- a/PHPCI/Model/Project.php +++ b/PHPCI/Model/Project.php @@ -44,9 +44,25 @@ class Project extends ProjectBase return null; } + public function setAccessInformation($value) + { + if (is_array($value)) { + $value = json_encode($value); + } + + parent::setAccessInformation($value); + } + public function getAccessInformation($key = null) { - $data = unserialize($this->data['access_information']); + $info = $this->data['access_information']; + + // Handle old-format (serialized) access information first: + if (!empty($info) && substr($info, 0, 1) != '{') { + $data = unserialize($info); + } else { + $data = json_decode($info, true); + } if (is_null($key)) { $rtn = $data; @@ -58,4 +74,18 @@ class Project extends ProjectBase return $rtn; } + + /** + * Get the value of Branch / branch. + * + * @return string + */ + public function getBranch() + { + if (empty($this->data['branch'])) { + return $this->getType() === 'hg' ? 'default' : 'master'; + } else { + return $this->data['branch']; + } + } } diff --git a/PHPCI/Plugin/PhpLoc.php b/PHPCI/Plugin/PhpLoc.php index 26ec8204..6e402cc7 100644 --- a/PHPCI/Plugin/PhpLoc.php +++ b/PHPCI/Plugin/PhpLoc.php @@ -43,7 +43,11 @@ class PhpLoc implements PHPCI\Plugin, PHPCI\ZeroConfigPlugin { $this->phpci = $phpci; $this->build = $build; - $this->directory = isset($options['directory']) ? $options['directory'] : $phpci->buildPath; + $this->directory = $phpci->buildPath; + + if (isset($options['directory'])) { + $this->directory .= $options['directory']; + } } /** diff --git a/PHPCI/Service/BuildService.php b/PHPCI/Service/BuildService.php new file mode 100644 index 00000000..300df728 --- /dev/null +++ b/PHPCI/Service/BuildService.php @@ -0,0 +1,111 @@ +buildStore = $buildStore; + } + + /** + * @param Project $project + * @param string|null $commitId + * @param string|null $branch + * @param string|null $committerEmail + * @param string|null $commitMessage + * @param string|null $extra + * @return \PHPCI\Model\Build + */ + public function createBuild( + Project $project, + $commitId = null, + $branch = null, + $committerEmail = null, + $commitMessage = null, + $extra = null + ) { + $build = new Build(); + $build->setCreated(new \DateTime()); + $build->setProject($project); + $build->setStatus(0); + + if (!is_null($commitId)) { + $build->setCommitId($commitId); + } else { + $build->setCommitId('Manual'); + } + + if (!is_null($branch)) { + $build->setBranch($branch); + } else { + $build->setBranch($project->getBranch()); + } + + if (!is_null($committerEmail)) { + $build->setCommitterEmail($committerEmail); + } + + if (!is_null($commitMessage)) { + $build->setCommitMessage($commitMessage); + } + + if (!is_null($extra)) { + $build->setExtra(json_encode($extra)); + } + + return $this->buildStore->save($build); + } + + /** + * @param Build $copyFrom + * @return \PHPCI\Model\Build + */ + public function createDuplicateBuild(Build $copyFrom) + { + $data = $copyFrom->getDataArray(); + + // Clean up unwanted properties from the original build: + unset($data['id']); + unset($data['status']); + unset($data['log']); + unset($data['started']); + unset($data['finished']); + + $build = new Build(); + $build->setValues($data); + $build->setCreated(new \DateTime()); + + return $this->buildStore->save($build); + } + + /** + * Delete a given build. + * @param Build $build + * @return bool + */ + public function deleteBuild(Build $build) + { + return $this->buildStore->delete($build); + } +} diff --git a/PHPCI/Service/ProjectService.php b/PHPCI/Service/ProjectService.php new file mode 100644 index 00000000..1bc1b248 --- /dev/null +++ b/PHPCI/Service/ProjectService.php @@ -0,0 +1,124 @@ +projectStore = $projectStore; + } + + /** + * Create a new project model and use the project store to save it. + * @param string $title + * @param string $type + * @param string $reference + * @param array $options + * @return \PHPCI\Model\Project + */ + public function createProject($title, $type, $reference, $options = array()) + { + // Create base project and use updateProject() to set its properties: + $project = new Project(); + return $this->updateProject($project, $title, $type, $reference, $options); + } + + /** + * Update the properties of a given project. + * @param Project $project + * @param string $title + * @param string $type + * @param string $reference + * @param array $options + * @return \PHPCI\Model\Project + */ + public function updateProject(Project $project, $title, $type, $reference, $options = array()) + { + // Set basic properties: + $project->setTitle($title); + $project->setType($type); + $project->setReference($reference); + $project->setAllowPublicStatus(0); + + // Handle extra project options: + if (array_key_exists('ssh_private_key', $options)) { + $project->setSshPrivateKey($options['ssh_private_key']); + } + + if (array_key_exists('ssh_public_key', $options)) { + $project->setSshPublicKey($options['ssh_public_key']); + } + + if (array_key_exists('build_config', $options)) { + $project->setBuildConfig($options['build_config']); + } + + if (array_key_exists('allow_public_status', $options)) { + $project->setAllowPublicStatus((int)$options['allow_public_status']); + } + + if (array_key_exists('branch', $options)) { + $project->setBranch($options['branch']); + } + + // Allow certain project types to set access information: + $this->processAccessInformation($project); + + // Save and return the project: + return $this->projectStore->save($project); + } + + /** + * Delete a given project. + * @param Project $project + * @return bool + */ + public function deleteProject(Project $project) + { + return $this->projectStore->delete($project); + } + + /** + * In circumstances where it is necessary, populate access information based on other project properties. + * @see ProjectService::createProject() + * @param Project $project + */ + protected function processAccessInformation(Project &$project) + { + $matches = array(); + $reference = $project->getReference(); + + if ($project->getType() == 'gitlab') { + $info = array(); + + if (preg_match('`^(.*)@(.*):([0-9]+)?/?(.*)/(.*)\.git`', $reference, $matches)) { + $info['user'] = $matches[1]; + $info['domain'] = $matches[2]; + $info['port'] = $matches[3]; + + $project->setReference($matches[4] . '/' . $matches[5]); + } + + $project->setAccessInformation($info); + } + } +} diff --git a/PHPCI/Service/UserService.php b/PHPCI/Service/UserService.php new file mode 100644 index 00000000..8f20044f --- /dev/null +++ b/PHPCI/Service/UserService.php @@ -0,0 +1,61 @@ +store = $store; + } + + public function createUser($name, $emailAddress, $password, $isAdmin = false) + { + $user = new User(); + $user->setName($name); + $user->setEmail($emailAddress); + $user->setHash(password_hash($password, PASSWORD_DEFAULT)); + $user->setIsAdmin(($isAdmin ? 1 : 0)); + + return $this->store->save($user); + } + + public function updateUser(User $user, $name, $emailAddress, $password = null, $isAdmin = null) + { + $user->setName($name); + $user->setEmail($emailAddress); + + if (!empty($password)) { + $user->setHash(password_hash($password, PASSWORD_DEFAULT)); + } + + if (!is_null($isAdmin)) { + $user->setIsAdmin(($isAdmin ? 1 : 0)); + } + + return $this->store->save($user); + } + + public function deleteUser(User $user) + { + return $this->store->delete($user); + } +} diff --git a/PHPCI/View/BuildStatus/view.phtml b/PHPCI/View/BuildStatus/view.phtml index 2b81cf45..e8536bf6 100644 --- a/PHPCI/View/BuildStatus/view.phtml +++ b/PHPCI/View/BuildStatus/view.phtml @@ -165,21 +165,7 @@ getBranch(); ?> - getPlugins(), true); - - if ( !is_array($plugins) ) { - $plugins = array(); - } - if ( 0 === count($plugins) ) { - ?> - $pluginstatus): - $subcls = $pluginstatus?'label label-success':'label label-danger'; - ?> Build()->formatPluginName($plugin); ?> -
+ diff --git a/PHPCI/View/Home/index.phtml b/PHPCI/View/Home/index.phtml index a103b413..bcac0775 100644 --- a/PHPCI/View/Home/index.phtml +++ b/PHPCI/View/Home/index.phtml @@ -66,12 +66,26 @@