diff --git a/PHPCI/Application.php b/PHPCI/Application.php index 77bd7170..3d889967 100644 --- a/PHPCI/Application.php +++ b/PHPCI/Application.php @@ -10,6 +10,7 @@ namespace PHPCI; use b8; +use b8\Http\Response; use b8\Http\Response\RedirectResponse; use b8\View; @@ -19,52 +20,37 @@ use b8\View; */ class Application extends b8\Application { + public function init() + { + $request =& $this->request; + $route = '/:controller/:action'; + $opts = array('controller' => 'Home', 'action' => 'index'); + + $this->router->clearRoutes(); + $this->router->register($route, $opts, function (&$route, Response &$response) use (&$request) { + $skipValidation = in_array($route['controller'], array('session', 'webhook', 'build-status')); + + if (!$skipValidation && !$this->validateSession()) { + if ($request->isAjax()) { + $response->setResponseCode(401); + $response->setContent(''); + } else { + $response = new RedirectResponse($response); + $response->setHeader('Location', PHPCI_URL.'session/login'); + } + + return false; + } + + return true; + }); + } /** * Handle an incoming web request. */ public function handleRequest() { - try { - $this->initRequest(); - - // Validate the user's session unless it is a login/logout action or a web hook: - $sessionAction = ($this->controllerName == 'Session' && in_array($this->action, array('login', 'logout'))); - $externalAction = in_array($this->controllerName, array('Bitbucket', 'Github', 'Gitlab', 'BuildStatus', 'Git')); - $skipValidation = ($externalAction || $sessionAction); - - if ($skipValidation || $this->validateSession()) { - parent::handleRequest(); - } - } catch (\Exception $ex) { - $content = '

There was a problem with this request

-

Please paste the details below into a - new bug report - so that we can investigate and fix it.

'; - - ob_start(); - var_dump(array( - 'message' => $ex->getMessage(), - 'file' => $ex->getFile(), - 'line' => $ex->getLine(), - 'trace' => $ex->getTraceAsString() - )); - var_dump(array( - 'PATH_INFO' => $_SERVER['PATH_INFO'], - 'REDIRECT_PATH_INFO' => $_SERVER['REDIRECT_PATH_INFO'], - 'REQUEST_URI' => $_SERVER['REQUEST_URI'], - 'PHP_SELF' => $_SERVER['PHP_SELF'], - 'SCRIPT_NAME' => $_SERVER['SCRIPT_NAME'], - 'DOCUMENT_ROOT' => $_SERVER['DOCUMENT_ROOT'], - 'SCRIPT_FILENAME' => $_SERVER['SCRIPT_FILENAME'], - 'SERVER_SOFTWARE' => $_SERVER['SERVER_SOFTWARE'], - )); - $content .= ob_get_contents(); - ob_end_clean(); - - $this->response->setContent($content); - $this->response->disableLayout(); - } - + $this->response = parent::handleRequest(); if (View::exists('layout') && $this->response->hasLayout()) { $view = new View('layout'); @@ -91,14 +77,6 @@ class Application extends b8\Application unset($_SESSION['user_id']); } - if ($this->request->isAjax()) { - $this->response->setResponseCode(401); - $this->response->setContent(''); - } else { - $this->response = new RedirectResponse($this->response); - $this->response->setHeader('Location', PHPCI_URL.'session/login'); - } - return false; } } diff --git a/PHPCI/BuildFactory.php b/PHPCI/BuildFactory.php index 0dd53058..82ebc221 100644 --- a/PHPCI/BuildFactory.php +++ b/PHPCI/BuildFactory.php @@ -9,10 +9,8 @@ namespace PHPCI; +use b8\Store\Factory; use PHPCI\Model\Build; -use PHPCI\Model\Build\LocalBuild; -use PHPCI\Model\Build\GithubBuild; -use PHPCI\Model\Build\BitbucketBuild; /** * PHPCI Build Factory - Takes in a generic "Build" and returns a type-specific build model. @@ -20,9 +18,21 @@ use PHPCI\Model\Build\BitbucketBuild; */ class BuildFactory { + /** + * @param $buildId + * @return Build + */ + public static function getBuildById($buildId) + { + $build = Factory::getStore('Build')->getById($buildId); + + return self::getBuild($build); + } + /** * Takes a generic build and returns a type-specific build model. - * @return \PHPCI\Model\Build\LocalBuild|\PHPCI\Model\Build\GithubBuild|\PHPCI\Model\Build\BitbucketBuild + * @param Build $base The build from which to get a more specific build type. + * @return Build */ public static function getBuild(Build $base) { diff --git a/PHPCI/BuildLogger.php b/PHPCI/BuildLogger.php deleted file mode 100644 index ccb6288a..00000000 --- a/PHPCI/BuildLogger.php +++ /dev/null @@ -1,31 +0,0 @@ - */ -class Builder implements LoggerAwareInterface, BuildLogger +class Builder implements LoggerAwareInterface { /** * @var string @@ -76,12 +78,9 @@ class Builder implements LoggerAwareInterface, BuildLogger protected $lastOutput; /** - * An array of key => value pairs that will be used for - * interpolation and environment variables - * @var array - * @see setInterpolationVars() + * @var BuildInterpolator */ - protected $interpolation_vars = array(); + protected $interpolator; /** * @var \PHPCI\Store\BuildStore @@ -103,24 +102,36 @@ class Builder implements LoggerAwareInterface, BuildLogger */ protected $commandExecutor; + /** + * @var Logging\BuildLogger + */ + protected $buildLogger; + /** * Set up the builder. * @param \PHPCI\Model\Build $build * @param LoggerInterface $logger */ - public function __construct(Build $build, $logger = null) + public function __construct(Build $build, LoggerInterface $logger = null) { - if ($logger) { - $this->setLogger($logger); - } $this->build = $build; - $this->store = Store\Factory::getStore('Build'); + $this->store = Factory::getStore('Build'); + + $this->buildLogger = new BuildLogger($logger, $build); $pluginFactory = $this->buildPluginFactory($build); $pluginFactory->addConfigFromFile(PHPCI_DIR . "/pluginconfig.php"); $this->pluginExecutor = new Plugin\Util\Executor($pluginFactory, $this); - $this->commandExecutor = new CommandExecutor($this, PHPCI_DIR, $this->quiet, $this->verbose); + $this->commandExecutor = new CommandExecutor( + $this->buildLogger, + PHPCI_DIR, + $this->quiet, + $this->verbose + ); + + $this->interpolator = new BuildInterpolator(); + } /** @@ -189,8 +200,7 @@ class Builder implements LoggerAwareInterface, BuildLogger // stages. if ($this->success) { $this->build->setStatus(Build::STATUS_SUCCESS); - } - else { + } else { $this->build->setStatus(Build::STATUS_FAILED); } @@ -199,15 +209,14 @@ class Builder implements LoggerAwareInterface, BuildLogger if ($this->success) { $this->pluginExecutor->executePlugins($this->config, 'success'); - $this->logSuccess('BUILD SUCCESSFUL!'); - } - else { + $this->buildLogger->logSuccess('BUILD SUCCESSFUL!'); + } else { $this->pluginExecutor->executePlugins($this->config, 'failure'); - $this->logFailure("BUILD FAILURE"); + $this->buildLogger->logFailure("BUILD FAILURE"); } // Clean up: - $this->log('Removing build.'); + $this->buildLogger->log('Removing build.'); shell_exec(sprintf('rm -Rf "%s"', $this->buildPath)); // Update the build in the database, ping any external services, etc. @@ -243,107 +252,14 @@ class Builder implements LoggerAwareInterface, BuildLogger } /** - * Add an entry to the build log. - * @param string|string[] $message - * @param string $level - * @param mixed[] $context - */ - public function log($message, $level = LogLevel::INFO, $context = array()) - { - // Skip if no logger has been loaded. - if (!$this->logger) { - return; - } - - if (!is_array($message)) { - $message = array($message); - } - - // The build is added to the context so the logger can use - // details from it if required. - $context['build'] = $this->build; - - foreach ($message as $item) { - $this->logger->log($level, $item, $context); - } - } - - /** - * Add a success-coloured message to the log. - * @param string - */ - public function logSuccess($message) - { - $this->log("\033[0;32m" . $message . "\033[0m"); - } - - /** - * Add a failure-coloured message to the log. - * @param string $message - * @param \Exception $exception The exception that caused the error. - */ - public function logFailure($message, \Exception $exception = null) - { - $context = array(); - - // The psr3 log interface stipulates that exceptions should be passed - // as the exception key in the context array. - if ($exception) { - $context['exception'] = $exception; - } - - $this->log( - "\033[0;31m" . $message . "\033[0m", - LogLevel::ERROR, - $context - ); - } - - /** - * Replace every occurance of the interpolation vars in the given string + * Replace every occurrence of the interpolation vars in the given string * Example: "This is build %PHPCI_BUILD%" => "This is build 182" * @param string $input * @return string */ public function interpolate($input) { - $keys = array_keys($this->interpolation_vars); - $values = array_values($this->interpolation_vars); - return str_replace($keys, $values, $input); - } - - /** - * Sets the variables that will be used for interpolation. This must be run - * from setupBuild() because prior to that, we don't know the buildPath - */ - protected function setInterpolationVars() - { - $this->interpolation_vars = array(); - $this->interpolation_vars['%PHPCI%'] = 1; - $this->interpolation_vars['%COMMIT%'] = $this->build->getCommitId(); - $this->interpolation_vars['%PROJECT%'] = $this->build->getProjectId(); - $this->interpolation_vars['%BUILD%'] = $this->build->getId(); - $this->interpolation_vars['%PROJECT_TITLE%'] = $this->getBuildProjectTitle( - ); - $this->interpolation_vars['%BUILD_PATH%'] = $this->buildPath; - $this->interpolation_vars['%BUILD_URI%'] = PHPCI_URL . "build/view/" . $this->build->getId( - ); - $this->interpolation_vars['%PHPCI_COMMIT%'] = $this->interpolation_vars['%COMMIT%']; - $this->interpolation_vars['%PHPCI_PROJECT%'] = $this->interpolation_vars['%PROJECT%']; - $this->interpolation_vars['%PHPCI_BUILD%'] = $this->interpolation_vars['%BUILD%']; - $this->interpolation_vars['%PHPCI_PROJECT_TITLE%'] = $this->interpolation_vars['%PROJECT_TITLE%']; - $this->interpolation_vars['%PHPCI_BUILD_PATH%'] = $this->interpolation_vars['%BUILD_PATH%']; - $this->interpolation_vars['%PHPCI_BUILD_URI%'] = $this->interpolation_vars['%BUILD_URI%']; - - putenv('PHPCI=1'); - putenv('PHPCI_COMMIT=' . $this->interpolation_vars['%COMMIT%']); - putenv('PHPCI_PROJECT=' . $this->interpolation_vars['%PROJECT%']); - putenv('PHPCI_BUILD=' . $this->interpolation_vars['%BUILD%']); - putenv( - 'PHPCI_PROJECT_TITLE=' . $this->interpolation_vars['%PROJECT_TITLE%'] - ); - putenv('PHPCI_BUILD_PATH=' . $this->interpolation_vars['%BUILD_PATH%']); - putenv('PHPCI_BUILD_URI=' . $this->interpolation_vars['%BUILD_URI%']); + return $this->interpolator->interpolate($input); } /** @@ -351,12 +267,17 @@ class Builder implements LoggerAwareInterface, BuildLogger */ protected function setupBuild() { - $buildId = 'project' . $this->build->getProject()->getId( - ) . '-build' . $this->build->getId(); - $this->ciDir = dirname(__FILE__) . '/../'; + $buildId = 'project' . $this->build->getProject()->getId() + . '-build' . $this->build->getId(); + $this->ciDir = dirname(dirname(__FILE__) . '/../') . '/'; $this->buildPath = $this->ciDir . 'build/' . $buildId . '/'; + $this->build->currentBuildPath = $this->buildPath; - $this->setInterpolationVars(); + $this->interpolator->setupInterpolationVars( + $this->build, + $this->buildPath, + PHPCI_URL + ); // Create a working copy of the project: if (!$this->build->createWorkingCopy($this, $this->buildPath)) { @@ -373,7 +294,7 @@ class Builder implements LoggerAwareInterface, BuildLogger $this->ignore = $this->config['build_settings']['ignore']; } - $this->logSuccess('Working copy created: ' . $this->buildPath); + $this->buildLogger->logSuccess('Working copy created: ' . $this->buildPath); return true; } @@ -385,32 +306,45 @@ class Builder implements LoggerAwareInterface, BuildLogger */ public function setLogger(LoggerInterface $logger) { - $this->logger = $logger; + $this->buildLogger->setLogger($logger); + } + + public function log($message, $level = LogLevel::INFO, $context = array()) + { + $this->buildLogger->log($message, $level, $context); + } + + /** + * Add a success-coloured message to the log. + * @param string + */ + public function logSuccess($message) + { + $this->buildLogger->logSuccess($message); } /** - * returns the logger attached to this builder. - * - * @return LoggerInterface + * Add a failure-coloured message to the log. + * @param string $message + * @param \Exception $exception The exception that caused the error. */ - public function getLogger() + public function logFailure($message, \Exception $exception = null) { - return $this->logger; + $this->buildLogger->logFailure($message, $exception); } - /** * Returns a configured instance of the plugin factory. * * @param Build $build - * @return Factory + * @return PluginFactory */ private function buildPluginFactory(Build $build) { - $pluginFactory = new Factory(); + $pluginFactory = new PluginFactory(); $self = $this; $pluginFactory->registerResource( - function () use($self) { + function () use ($self) { return $self; }, null, @@ -418,7 +352,7 @@ class Builder implements LoggerAwareInterface, BuildLogger ); $pluginFactory->registerResource( - function () use($build) { + function () use ($build) { return $build; }, null, diff --git a/PHPCI/Command/CreateAdminCommand.php b/PHPCI/Command/CreateAdminCommand.php new file mode 100644 index 00000000..647dffca --- /dev/null +++ b/PHPCI/Command/CreateAdminCommand.php @@ -0,0 +1,130 @@ +setName('phpci:create-admin') + ->setDescription('Create an admin user'); + } + + /** + * Creates an admin user in the existing PHPCI database + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + + require(PHPCI_DIR . 'bootstrap.php'); + + // Try to create a user account: + $adminEmail = $this->ask('Admin email address: ', true, FILTER_VALIDATE_EMAIL); + + if (empty($adminEmail)) { + return; + } + + $adminPass = $this->ask('Admin password: '); + $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); + + print 'User account created!' . PHP_EOL; + } catch (\Exception $ex) { + print 'There was a problem creating your account. :(' . PHP_EOL; + print $ex->getMessage(); + print PHP_EOL; + } + } + + protected function ask($question, $emptyOk = false, $validationFilter = null) + { + print $question . ' '; + + $rtn = ''; + $stdin = fopen('php://stdin', 'r'); + $rtn = fgets($stdin); + fclose($stdin); + + $rtn = trim($rtn); + + if (!$emptyOk && empty($rtn)) { + $rtn = $this->ask($question, $emptyOk, $validationFilter); + } elseif ($validationFilter != null && ! empty($rtn)) { + if (! $this -> controlFormat($rtn, $validationFilter, $statusMessage)) { + print $statusMessage; + $rtn = $this->ask($question, $emptyOk, $validationFilter); + } + } + + return $rtn; + } + protected function controlFormat($valueToInspect, $filter, &$statusMessage) + { + $filters = !(is_array($filter))? array($filter) : $filter; + $statusMessage = ''; + $status = true; + $options = array(); + + foreach ($filters as $filter) { + if (! is_int($filter)) { + $regexp = $filter; + $filter = FILTER_VALIDATE_REGEXP; + $options = array( + 'options' => array( + 'regexp' => $regexp, + ) + ); + } + if (! filter_var($valueToInspect, $filter, $options)) { + $status = false; + + switch ($filter) + { + case FILTER_VALIDATE_URL: + $statusMessage = 'Incorrect url format.' . PHP_EOL; + break; + case FILTER_VALIDATE_EMAIL: + $statusMessage = 'Incorrect e-mail format.' . PHP_EOL; + break; + case FILTER_VALIDATE_REGEXP: + $statusMessage = 'Incorrect format.' . PHP_EOL; + break; + } + } + } + + return $status; + } +} diff --git a/PHPCI/Command/DaemoniseCommand.php b/PHPCI/Command/DaemoniseCommand.php index 3c490d75..f8cbc45b 100644 --- a/PHPCI/Command/DaemoniseCommand.php +++ b/PHPCI/Command/DaemoniseCommand.php @@ -10,14 +10,13 @@ namespace PHPCI\Command; +use Monolog\Logger; use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Input\ArgvInput; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; -use b8\Store\Factory; -use PHPCI\Builder; -use PHPCI\BuildFactory; /** * Daemon that loops and call the run-command. @@ -27,6 +26,36 @@ use PHPCI\BuildFactory; */ class DaemoniseCommand extends Command { + /** + * @var Logger + */ + protected $logger; + + /** + * @var OutputInterface + */ + protected $output; + + /** + * @var boolean + */ + protected $run; + + /** + * @var int + */ + protected $sleep; + + /** + * @param \Monolog\Logger $logger + * @param string $name + */ + public function __construct(Logger $logger, $name = null) + { + parent::__construct($name); + $this->logger = $logger; + } + protected function configure() { $this @@ -43,14 +72,19 @@ class DaemoniseCommand extends Command $command = sprintf($cmd, getmypid(), PHPCI_DIR); exec($command); + $this->output = $output; $this->run = true; $this->sleep = 0; - $runner = new RunCommand; + $runner = new RunCommand($this->logger); + + $emptyInput = new ArgvInput(array()); while ($this->run) { + $buildCount = 0; + try { - $buildCount = $runner->execute($input, $output); + $buildCount = $runner->run($emptyInput, $output); } catch (\Exception $e) { var_dump($e); } diff --git a/PHPCI/Command/GenerateCommand.php b/PHPCI/Command/GenerateCommand.php index 3a97f70c..44a499de 100644 --- a/PHPCI/Command/GenerateCommand.php +++ b/PHPCI/Command/GenerateCommand.php @@ -37,7 +37,7 @@ class GenerateCommand extends Command */ protected function execute(InputInterface $input, OutputInterface $output) { - $gen = new CodeGenerator(Database::getConnection(), 'PHPCI', PHPCI_DIR . '/PHPCI/', false); + $gen = new CodeGenerator(Database::getConnection(), ['default' => 'PHPCI'], ['default' => PHPCI_DIR], false); $gen->generateModels(); $gen->generateStores(); } diff --git a/PHPCI/Command/PollCommand.php b/PHPCI/Command/PollCommand.php index 8bbe83cf..503e83a8 100644 --- a/PHPCI/Command/PollCommand.php +++ b/PHPCI/Command/PollCommand.php @@ -80,7 +80,9 @@ class PollCommand extends Command $this->logger->info("Last commit to github for " . $project->getTitle() . " is " . $last_commit); if ($project->getLastCommit() != $last_commit && $last_commit != "") { - $this->logger->info("Last commit is different from database, adding new build for " . $project->getTitle()); + $this->logger->info( + "Last commit is different from database, adding new build for " . $project->getTitle() + ); $build = new Build(); $build->setProjectId($project->getId()); @@ -101,4 +103,3 @@ class PollCommand extends Command $this->logger->addInfo("Finished processing builds"); } } - diff --git a/PHPCI/Command/RunCommand.php b/PHPCI/Command/RunCommand.php index d635b8a2..318f9247 100644 --- a/PHPCI/Command/RunCommand.php +++ b/PHPCI/Command/RunCommand.php @@ -10,9 +10,9 @@ namespace PHPCI\Command; use Monolog\Logger; -use PHPCI\Helper\BuildDBLogHandler; -use PHPCI\Helper\LoggedBuildContextTidier; -use PHPCI\Helper\OutputLogHandler; +use PHPCI\Logging\BuildDBLogHandler; +use PHPCI\Logging\LoggedBuildContextTidier; +use PHPCI\Logging\OutputLogHandler; use Psr\Log\LoggerAwareInterface; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputArgument; @@ -22,6 +22,7 @@ use Symfony\Component\Console\Output\OutputInterface; use b8\Store\Factory; use PHPCI\Builder; use PHPCI\BuildFactory; +use PHPCI\Model\Build; /** * Run console command - Runs any pending builds. @@ -88,17 +89,24 @@ class RunCommand extends Command $build = BuildFactory::getBuild($build); - // Logging relevant to this build should be stored - // against the build itself. - $buildDbLog = new BuildDBLogHandler($build, Logger::INFO); - $this->logger->pushHandler($buildDbLog); + try { + // Logging relevant to this build should be stored + // against the build itself. + $buildDbLog = new BuildDBLogHandler($build, Logger::INFO); + $this->logger->pushHandler($buildDbLog); - $builder = new Builder($build, $this->logger); - $builder->execute(); + $builder = new Builder($build, $this->logger); + $builder->execute(); + + // After execution we no longer want to record the information + // back to this specific build so the handler should be removed. + $this->logger->popHandler($buildDbLog); + } catch (\Exception $ex) { + $build->setStatus(Build::STATUS_FAILED); + $build->setLog($build->getLog() . PHP_EOL . PHP_EOL . $ex->getMessage()); + $store->save($build); + } - // After execution we no longer want to record the information - // back to this specific build so the handler should be removed. - $this->logger->popHandler($buildDbLog); } $this->logger->addInfo("Finished processing builds"); diff --git a/PHPCI/Controller.php b/PHPCI/Controller.php index d95c24e0..442d9248 100644 --- a/PHPCI/Controller.php +++ b/PHPCI/Controller.php @@ -38,7 +38,7 @@ class Controller extends \b8\Controller if (View::exists($this->className)) { $this->controllerView = new View($this->className); } else { - $this->controllerView = new View\UserView('{@content}'); + $this->controllerView = new View\Template('{@content}'); } } diff --git a/PHPCI/Controller/BitbucketController.php b/PHPCI/Controller/BitbucketController.php deleted file mode 100644 index 783421a3..00000000 --- a/PHPCI/Controller/BitbucketController.php +++ /dev/null @@ -1,67 +0,0 @@ - -* @package PHPCI -* @subpackage Web -*/ -class BitbucketController extends \PHPCI\Controller -{ - /** - * @var \PHPCI\Store\BuildStore - */ - protected $buildStore; - - public function init() - { - $this->buildStore = Store\Factory::getStore('Build'); - } - - /** - * Called by Bitbucket POST service. - */ - public function webhook($project) - { - $payload = json_decode($this->getParam('payload'), true); - $branches = array(); - $commits = array(); - - foreach ($payload['commits'] as $commit) { - if (!in_array($commit['branch'], $branches)) { - $branches[] = $commit['branch']; - $commits[$commit['branch']] = $commit['raw_node']; - } - } - - foreach ($branches as $branch) { - try { - - $build = new Build(); - $build->setProjectId($project); - $build->setCommitId($commits[$branch]); - $build->setStatus(Build::STATUS_NEW); - $build->setLog(''); - $build->setCreated(new \DateTime()); - $build->setBranch($branch); - $this->buildStore->save($build); - } catch (\Exception $ex) { - } - } - - die('OK'); - } -} diff --git a/PHPCI/Controller/BuildController.php b/PHPCI/Controller/BuildController.php index c80cb7c8..032ce118 100644 --- a/PHPCI/Controller/BuildController.php +++ b/PHPCI/Controller/BuildController.php @@ -10,6 +10,7 @@ namespace PHPCI\Controller; use b8; +use PHPCI\BuildFactory; use PHPCI\Model\Build; /** @@ -35,7 +36,7 @@ class BuildController extends \PHPCI\Controller */ public function view($buildId) { - $build = $this->buildStore->getById($buildId); + $build = BuildFactory::getBuildById($buildId); $this->view->plugins = $this->getUiPlugins(); $this->view->build = $build; $this->view->data = $this->getBuildData($build); @@ -63,7 +64,7 @@ class BuildController extends \PHPCI\Controller */ public function data($buildId) { - die($this->getBuildData($this->buildStore->getById($buildId))); + die($this->getBuildData(BuildFactory::getBuildById($buildId))); } /** @@ -71,7 +72,7 @@ class BuildController extends \PHPCI\Controller */ public function meta($buildId) { - $build = $this->buildStore->getById($buildId); + $build = BuildFactory::getBuildById($buildId); $key = $this->getParam('key', null); $numBuilds = $this->getParam('num_builds', 1); $data = null; @@ -104,7 +105,7 @@ class BuildController extends \PHPCI\Controller */ public function rebuild($buildId) { - $copy = $this->buildStore->getById($buildId); + $copy = BuildFactory::getBuildById($buildId); $build = new Build(); $build->setProjectId($copy->getProjectId()); @@ -112,6 +113,8 @@ class BuildController extends \PHPCI\Controller $build->setStatus(Build::STATUS_NEW); $build->setBranch($copy->getBranch()); $build->setCreated(new \DateTime()); + $build->setCommitterEmail($copy->getCommitterEmail()); + $build->setCommitMessage($copy->getCommitMessage()); $build = $this->buildStore->save($build); @@ -128,7 +131,7 @@ class BuildController extends \PHPCI\Controller throw new \Exception('You do not have permission to do that.'); } - $build = $this->buildStore->getById($buildId); + $build = BuildFactory::getBuildById($buildId); if (!$build) { $this->response->setResponseCode(404); diff --git a/PHPCI/Controller/GitController.php b/PHPCI/Controller/GitController.php deleted file mode 100644 index b5a6cf87..00000000 --- a/PHPCI/Controller/GitController.php +++ /dev/null @@ -1,69 +0,0 @@ - - */ -class GitController extends \PHPCI\Controller -{ - public function init() - { - $this->_buildStore = Store\Factory::getStore('Build'); - } - - /** - * Called by POSTing to /git/webhook/?branch=&commit= - * - * @param string $project - */ - public function webhook($project) - { - $branch = $this->getParam('branch'); - $commit = $this->getParam('commit'); - - try { - $build = new Build(); - $build->setProjectId($project); - - if ($branch !== null && trim($branch) !== '') { - $build->setBranch($branch); - } else { - $build->setBranch('master'); - } - - if ($commit !== null && trim($commit) !== '') { - $build->setCommitId($commit); - } - - $build->setStatus(Build::STATUS_NEW); - $build->setLog(''); - $build->setCreated(new \DateTime()); - } catch (\Exception $ex) { - header('HTTP/1.1 400 Bad Request'); - header('Ex: ' . $ex->getMessage()); - die('FAIL'); - } - - try { - $this->_buildStore->save($build); - } catch (\Exception $ex) { - header('HTTP/1.1 500 Internal Server Error'); - header('Ex: ' . $ex->getMessage()); - die('FAIL'); - } - - die('OK'); - } -} diff --git a/PHPCI/Controller/GithubController.php b/PHPCI/Controller/GithubController.php deleted file mode 100644 index 4fe99756..00000000 --- a/PHPCI/Controller/GithubController.php +++ /dev/null @@ -1,77 +0,0 @@ - -* @package PHPCI -* @subpackage Web -*/ -class GithubController extends \PHPCI\Controller -{ - /** - * @var \PHPCI\Store\BuildStore - */ - protected $buildStore; - - public function init() - { - $this->buildStore = Store\Factory::getStore('Build'); - } - - /** - * Called by Github Webhooks: - */ - public function webhook($project) - { - $payload = json_decode($this->getParam('payload'), true); - - // Github sends a payload when you close a pull request with a - // non-existant commit. We don't want this. - if ($payload['after'] === '0000000000000000000000000000000000000000') { - die('OK'); - } - - try { - $build = new Build(); - $build->setProjectId($project); - $build->setCommitId($payload['after']); - $build->setStatus(Build::STATUS_NEW); - $build->setLog(''); - $build->setCreated(new \DateTime()); - $build->setBranch(str_replace('refs/heads/', '', $payload['ref'])); - - if (!empty($payload['pusher']['email'])) { - $build->setCommitterEmail($payload['pusher']['email']); - } - - } catch (\Exception $ex) { - header('HTTP/1.1 400 Bad Request'); - header('Ex: ' . $ex->getMessage()); - die('FAIL'); - } - - try { - $build = $this->buildStore->save($build); - $build->sendStatusPostback(); - } catch (\Exception $ex) { - header('HTTP/1.1 500 Internal Server Error'); - header('Ex: ' . $ex->getMessage()); - die('FAIL'); - } - - die('OK'); - } -} diff --git a/PHPCI/Controller/GitlabController.php b/PHPCI/Controller/GitlabController.php deleted file mode 100644 index a466f4dd..00000000 --- a/PHPCI/Controller/GitlabController.php +++ /dev/null @@ -1,66 +0,0 @@ -, Dan Cryer -* @package PHPCI -* @subpackage Web -*/ -class GitlabController extends \PHPCI\Controller -{ - /** - * @var \PHPCI\Store\BuildStore - */ - protected $buildStore; - - public function init() - { - $this->buildStore = Store\Factory::getStore('Build'); - } - - /** - * Called by Gitlab Webhooks: - */ - public function webhook($project) - { - $payload = json_decode(file_get_contents("php://input"), true); - - try { - $build = new Build(); - $build->setProjectId($project); - $build->setCommitId($payload['after']); - $build->setStatus(Build::STATUS_NEW); - $build->setLog(''); - $build->setCreated(new \DateTime()); - $build->setBranch(str_replace('refs/heads/', '', $payload['ref'])); - } catch (\Exception $ex) { - header('HTTP/1.1 400 Bad Request'); - header('Ex: ' . $ex->getMessage()); - die('FAIL'); - } - - try { - $build = $this->buildStore->save($build); - $build->sendStatusPostback(); - } catch (\Exception $ex) { - header('HTTP/1.1 500 Internal Server Error'); - header('Ex: ' . $ex->getMessage()); - die('FAIL'); - } - - die('OK'); - } -} diff --git a/PHPCI/Controller/HomeController.php b/PHPCI/Controller/HomeController.php index 586f46f1..95676efc 100644 --- a/PHPCI/Controller/HomeController.php +++ b/PHPCI/Controller/HomeController.php @@ -10,6 +10,7 @@ namespace PHPCI\Controller; use b8; +use PHPCI\BuildFactory; /** * Home Controller - Displays the PHPCI Dashboard. @@ -73,6 +74,11 @@ class HomeController extends \PHPCI\Controller { $builds = $this->buildStore->getWhere(array(), 5, 0, array(), array('id' => 'DESC')); $view = new b8\View('BuildsTable'); + + foreach ($builds['items'] as &$build) { + $build = BuildFactory::getBuild($build); + } + $view->builds = $builds['items']; return $view->render(); diff --git a/PHPCI/Controller/PluginController.php b/PHPCI/Controller/PluginController.php index 56d01f3e..b7e32ddb 100644 --- a/PHPCI/Controller/PluginController.php +++ b/PHPCI/Controller/PluginController.php @@ -11,6 +11,9 @@ namespace PHPCI\Controller; use b8; use PHPCI\Model\Build; +use PHPCI\Plugin\Util\ComposerPluginInformation; +use PHPCI\Plugin\Util\FilesPluginInformation; +use PHPCI\Plugin\Util\PluginInformationCollection; /** * Plugin Controller - Provides support for installing Composer packages. @@ -59,8 +62,18 @@ class PluginController extends \PHPCI\Controller $this->view->required = $this->required; $json = $this->getComposerJson(); - $this->view->installed = $json['require']; - $this->view->suggested = $json['suggest']; + $this->view->installedPackages = $json['require']; + $this->view->suggestedPackages = $json['suggest']; + + $pluginInfo = new PluginInformationCollection(); + $pluginInfo->add(FilesPluginInformation::newFromDir( + PHPCI_DIR . "PHPCI/Plugin/" + )); + $pluginInfo->add(ComposerPluginInformation::buildFromYaml( + PHPCI_DIR . "vendor/composer/installed.json" + )); + + $this->view->plugins = $pluginInfo->getInstalledPlugins(); return $this->view->render(); } diff --git a/PHPCI/Controller/ProjectController.php b/PHPCI/Controller/ProjectController.php index 7c0b75e7..19fb3149 100644 --- a/PHPCI/Controller/ProjectController.php +++ b/PHPCI/Controller/ProjectController.php @@ -9,6 +9,7 @@ namespace PHPCI\Controller; +use PHPCI\BuildFactory; use PHPCI\Model\Build; use PHPCI\Model\Project; use b8; @@ -113,6 +114,11 @@ class ProjectController extends \PHPCI\Controller $order = array('id' => 'DESC'); $builds = $this->buildStore->getWhere($criteria, 10, $start, array(), $order); $view = new b8\View('BuildsTable'); + + foreach ($builds['items'] as &$build) { + $build = BuildFactory::getBuild($build); + } + $view->builds = $builds['items']; return array($view->render(), $builds['count']); diff --git a/PHPCI/Controller/WebhookController.php b/PHPCI/Controller/WebhookController.php new file mode 100644 index 00000000..1b118440 --- /dev/null +++ b/PHPCI/Controller/WebhookController.php @@ -0,0 +1,206 @@ + + * @author Sami Tikka + * @author Alex Russell + * @package PHPCI + * @subpackage Web + */ +class WebhookController extends \PHPCI\Controller +{ + /** + * @var \PHPCI\Store\BuildStore + */ + protected $buildStore; + + public function init() + { + $this->buildStore = Store\Factory::getStore('Build'); + } + + /** + * Called by Bitbucket POST service. + */ + public function bitbucket($project) + { + $payload = json_decode($this->getParam('payload'), true); + + foreach ($payload['commits'] as $commit) { + try { + $email = $commit['raw_author']; + $email = substr($email, 0, strpos($email, '>')); + $email = substr($email, strpos($email, '<') + 1); + + $build = new Build(); + $build->setProjectId($project); + $build->setCommitId($commit['raw_node']); + $build->setCommitterEmail($email); + $build->setStatus(Build::STATUS_NEW); + $build->setLog(''); + $build->setCreated(new \DateTime()); + $build->setBranch($commit['branch']); + $build->setCommitMessage($commit['message']); + $this->buildStore->save($build); + } catch (\Exception $ex) { + } + } + + die('OK'); + } + + /** + * Called by POSTing to /git/webhook/?branch=&commit= + * + * @param string $project + */ + public function git($project) + { + $branch = $this->getParam('branch'); + $commit = $this->getParam('commit'); + + try { + $build = new Build(); + $build->setProjectId($project); + + if ($branch !== null && trim($branch) !== '') { + $build->setBranch($branch); + } else { + $build->setBranch('master'); + } + + if ($commit !== null && trim($commit) !== '') { + $build->setCommitId($commit); + } + + $build->setStatus(Build::STATUS_NEW); + $build->setLog(''); + $build->setCreated(new \DateTime()); + } catch (\Exception $ex) { + header('HTTP/1.1 400 Bad Request'); + header('Ex: ' . $ex->getMessage()); + die('FAIL'); + } + + try { + $this->_buildStore->save($build); + } catch (\Exception $ex) { + header('HTTP/1.1 500 Internal Server Error'); + header('Ex: ' . $ex->getMessage()); + die('FAIL'); + } + + die('OK'); + } + + /** + * Called by Github Webhooks: + */ + public function github($project) + { + $payload = json_decode($this->getParam('payload'), true); + + // Github sends a payload when you close a pull request with a + // non-existant commit. We don't want this. + if ($payload['after'] === '0000000000000000000000000000000000000000') { + die('OK'); + } + + try { + + if (isset($payload['commits']) && is_array($payload['commits'])) { + // If we have a list of commits, then add them all as builds to be tested: + + foreach ($payload['commits'] as $commit) { + if (!$commit['distinct']) { + continue; + } + + $build = new Build(); + $build->setProjectId($project); + $build->setCommitId($commit['id']); + $build->setStatus(Build::STATUS_NEW); + $build->setLog(''); + $build->setCreated(new \DateTime()); + $build->setBranch(str_replace('refs/heads/', '', $payload['ref'])); + $build->setCommitterEmail($commit['committer']['email']); + $build->setCommitMessage($commit['message']); + $build = $this->buildStore->save($build); + $build->sendStatusPostback(); + } + } elseif (substr($payload['ref'], 0, 10) == 'refs/tags/') { + // If we don't, but we're dealing with a tag, add that instead: + $build = new Build(); + $build->setProjectId($project); + $build->setCommitId($payload['after']); + $build->setStatus(Build::STATUS_NEW); + $build->setLog(''); + $build->setCreated(new \DateTime()); + $build->setBranch(str_replace('refs/tags/', 'Tag: ', $payload['ref'])); + $build->setCommitterEmail($payload['pusher']['email']); + $build->setCommitMessage($payload['head_commit']['message']); + + $build = $this->buildStore->save($build); + $build->sendStatusPostback(); + } + + } catch (\Exception $ex) { + header('HTTP/1.1 500 Internal Server Error'); + header('Ex: ' . $ex->getMessage()); + die('FAIL'); + } + + die('OK'); + } + + /** + * Called by Gitlab Webhooks: + */ + public function gitlab($project) + { + $payloadString = file_get_contents("php://input"); + $payload = json_decode($payloadString, true); + + try { + + if (isset($payload['commits']) && is_array($payload['commits'])) { + // If we have a list of commits, then add them all as builds to be tested: + + foreach ($payload['commits'] as $commit) { + $build = new Build(); + $build->setProjectId($project); + $build->setCommitId($commit['id']); + $build->setStatus(Build::STATUS_NEW); + $build->setLog(''); + $build->setCreated(new \DateTime()); + $build->setBranch(str_replace('refs/heads/', '', $payload['ref'])); + $build->setCommitterEmail($commit['author']['email']); + $build->setCommitMessage($commit['message']); + $build = $this->buildStore->save($build); + $build->sendStatusPostback(); + } + } + + } catch (\Exception $ex) { + header('HTTP/1.1 500 Internal Server Error'); + header('Ex: ' . $ex->getMessage()); + die('FAIL'); + } + + die('OK'); + } +} diff --git a/PHPCI/Helper/BuildInterpolator.php b/PHPCI/Helper/BuildInterpolator.php new file mode 100644 index 00000000..d4609793 --- /dev/null +++ b/PHPCI/Helper/BuildInterpolator.php @@ -0,0 +1,61 @@ + value pairs that will be used for + * interpolation and environment variables + * @var mixed[] + * @see setupInterpolationVars() + */ + protected $interpolation_vars = array(); + + /** + * Sets the variables that will be used for interpolation. + * @param Build $build + * @param string $buildPath + * @param string $phpCiUrl + */ + public function setupInterpolationVars(Build $build, $buildPath, $phpCiUrl) + { + $this->interpolation_vars = array(); + $this->interpolation_vars['%PHPCI%'] = 1; + $this->interpolation_vars['%COMMIT%'] = $build->getCommitId(); + $this->interpolation_vars['%PROJECT%'] = $build->getProjectId(); + $this->interpolation_vars['%BUILD%'] = $build->getId(); + $this->interpolation_vars['%PROJECT_TITLE%'] = $build->getProjectTitle(); + $this->interpolation_vars['%BUILD_PATH%'] = $buildPath; + $this->interpolation_vars['%BUILD_URI%'] = $phpCiUrl . "build/view/" . $build->getId(); + $this->interpolation_vars['%PHPCI_COMMIT%'] = $this->interpolation_vars['%COMMIT%']; + $this->interpolation_vars['%PHPCI_PROJECT%'] = $this->interpolation_vars['%PROJECT%']; + $this->interpolation_vars['%PHPCI_BUILD%'] = $this->interpolation_vars['%BUILD%']; + $this->interpolation_vars['%PHPCI_PROJECT_TITLE%'] = $this->interpolation_vars['%PROJECT_TITLE%']; + $this->interpolation_vars['%PHPCI_BUILD_PATH%'] = $this->interpolation_vars['%BUILD_PATH%']; + $this->interpolation_vars['%PHPCI_BUILD_URI%'] = $this->interpolation_vars['%BUILD_URI%']; + + putenv('PHPCI=1'); + putenv('PHPCI_COMMIT=' . $this->interpolation_vars['%COMMIT%']); + putenv('PHPCI_PROJECT=' . $this->interpolation_vars['%PROJECT%']); + putenv('PHPCI_BUILD=' . $this->interpolation_vars['%BUILD%']); + putenv('PHPCI_PROJECT_TITLE=' . $this->interpolation_vars['%PROJECT_TITLE%']); + putenv('PHPCI_BUILD_PATH=' . $this->interpolation_vars['%BUILD_PATH%']); + putenv('PHPCI_BUILD_URI=' . $this->interpolation_vars['%BUILD_URI%']); + } + + /** + * Replace every occurrence of the interpolation vars in the given string + * Example: "This is build %PHPCI_BUILD%" => "This is build 182" + * @param string $input + * @return string + */ + public function interpolate($input) + { + $keys = array_keys($this->interpolation_vars); + $values = array_values($this->interpolation_vars); + return str_replace($keys, $values, $input); + } +} \ No newline at end of file diff --git a/PHPCI/Helper/CommandExecutor.php b/PHPCI/Helper/CommandExecutor.php index 43813924..a422d970 100644 --- a/PHPCI/Helper/CommandExecutor.php +++ b/PHPCI/Helper/CommandExecutor.php @@ -3,12 +3,13 @@ namespace PHPCI\Helper; -use PHPCI\BuildLogger; +use \PHPCI\Logging\BuildLogger; +use Psr\Log\LogLevel; class CommandExecutor { /** - * @var \PHPCI\BuildLogger + * @var \PHPCI\Logging\BuildLogger */ protected $logger; @@ -75,6 +76,10 @@ class CommandExecutor $status = 0; exec($command, $this->lastOutput, $status); + foreach ($this->lastOutput as &$lastOutput) { + $lastOutput = trim($lastOutput, '"'); + } + if (!empty($this->lastOutput) && ($this->verbose|| $status != 0)) { $this->logger->log($this->lastOutput); } @@ -108,21 +113,26 @@ class CommandExecutor } foreach ($binary as $bin) { + $this->logger->log("Looking for binary: " . $bin, LogLevel::DEBUG); // Check project root directory: if (is_file($this->rootDir . $bin)) { + $this->logger->log("Found in root: " . $bin, LogLevel::DEBUG); return $this->rootDir . $bin; } // Check Composer bin dir: if (is_file($this->rootDir . 'vendor/bin/' . $bin)) { + $this->logger->log("Found in vendor/bin: " . $bin, LogLevel::DEBUG); return $this->rootDir . 'vendor/bin/' . $bin; } - // Use "which" - $which = trim(shell_exec('which ' . $bin)); + // Use "where" for windows and "which" for other OS + $findCmd = (strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN') ? 'which' : 'where'; + $findCmdResult = trim(shell_exec($findCmd . ' ' . $bin)); - if (!empty($which)) { - return $which; + if (!empty($findCmdResult)) { + $this->logger->log("Found in " . $findCmdResult, LogLevel::DEBUG); + return $findCmdResult; } } diff --git a/PHPCI/Helper/BuildDBLogHandler.php b/PHPCI/Logging/BuildDBLogHandler.php similarity index 72% rename from PHPCI/Helper/BuildDBLogHandler.php rename to PHPCI/Logging/BuildDBLogHandler.php index 6ae34379..a877fe50 100644 --- a/PHPCI/Helper/BuildDBLogHandler.php +++ b/PHPCI/Logging/BuildDBLogHandler.php @@ -1,6 +1,6 @@ logValue .= (string)$record['formatted']; + $message = (string)$record['message']; + $message = str_replace($this->build->currentBuildPath, '/', $message); + + $this->logValue .= $message . PHP_EOL; $this->build->setLog($this->logValue); } -} \ No newline at end of file +} diff --git a/PHPCI/Logging/BuildLogger.php b/PHPCI/Logging/BuildLogger.php new file mode 100644 index 00000000..f652ba01 --- /dev/null +++ b/PHPCI/Logging/BuildLogger.php @@ -0,0 +1,96 @@ +logger = $logger; + $this->build = $build; + } + + /** + * Add an entry to the build log. + * @param string|string[] $message + * @param string $level + * @param mixed[] $context + */ + public function log($message, $level = LogLevel::INFO, $context = array()) + { + // Skip if no logger has been loaded. + if (!$this->logger) { + return; + } + + if (!is_array($message)) { + $message = array($message); + } + + // The build is added to the context so the logger can use + // details from it if required. + $context['build'] = $this->build; + + foreach ($message as $item) { + $this->logger->log($level, $item, $context); + } + } + + /** + * Add a success-coloured message to the log. + * @param string + */ + public function logSuccess($message) + { + $this->log("\033[0;32m" . $message . "\033[0m"); + } + + /** + * Add a failure-coloured message to the log. + * @param string $message + * @param \Exception $exception The exception that caused the error. + */ + public function logFailure($message, \Exception $exception = null) + { + $context = array(); + + // The psr3 log interface stipulates that exceptions should be passed + // as the exception key in the context array. + if ($exception) { + $context['exception'] = $exception; + } + + $this->log( + "\033[0;31m" . $message . "\033[0m", + LogLevel::ERROR, + $context + ); + } + + /** + * Sets a logger instance on the object + * + * @param LoggerInterface $logger + * @return null + */ + public function setLogger(LoggerInterface $logger) + { + $this->logger = $logger; + } +} diff --git a/PHPCI/Helper/LoggedBuildContextTidier.php b/PHPCI/Logging/LoggedBuildContextTidier.php similarity index 92% rename from PHPCI/Helper/LoggedBuildContextTidier.php rename to PHPCI/Logging/LoggedBuildContextTidier.php index a67dd88d..2b70f443 100644 --- a/PHPCI/Helper/LoggedBuildContextTidier.php +++ b/PHPCI/Logging/LoggedBuildContextTidier.php @@ -1,12 +1,12 @@ tidyLoggedBuildContext(func_get_arg(0)); } @@ -29,4 +29,4 @@ class LoggedBuildContextTidier } return $logRecord; } -} \ No newline at end of file +} diff --git a/PHPCI/Helper/LoggerConfig.php b/PHPCI/Logging/LoggerConfig.php similarity index 79% rename from PHPCI/Helper/LoggerConfig.php rename to PHPCI/Logging/LoggerConfig.php index de1b2a67..13b1f5e7 100644 --- a/PHPCI/Helper/LoggerConfig.php +++ b/PHPCI/Logging/LoggerConfig.php @@ -1,14 +1,15 @@ config = $configArray; } @@ -46,13 +47,15 @@ class LoggerConfig { * @param $name * @return Logger */ - public function getFor($name) { - $handlers = $this->getHandlers(self::KEY_AlwaysLoaded); + public function getFor($name) + { + $handlers = $this->getHandlers(self::KEY_ALWAYS_LOADED); $handlers = array_merge($handlers, $this->getHandlers($name)); return new Logger($name, $handlers); } - protected function getHandlers($key) { + protected function getHandlers($key) + { $handlers = array(); // They key is expected to either be an array or @@ -60,12 +63,10 @@ class LoggerConfig { if (isset($this->config[$key])) { if (is_callable($this->config[$key])) { $handlers = call_user_func($this->config[$key]); - } - elseif(is_array($this->config[$key])) { + } elseif (is_array($this->config[$key])) { $handlers = $this->config[$key]; } } return $handlers; } - -} \ No newline at end of file +} diff --git a/PHPCI/Helper/OutputLogHandler.php b/PHPCI/Logging/OutputLogHandler.php similarity index 90% rename from PHPCI/Helper/OutputLogHandler.php rename to PHPCI/Logging/OutputLogHandler.php index 993f1c33..d573b03d 100644 --- a/PHPCI/Helper/OutputLogHandler.php +++ b/PHPCI/Logging/OutputLogHandler.php @@ -1,6 +1,6 @@ null, 'plugins' => null, 'committer_email' => null, + 'commit_message' => null, ); /** @@ -62,6 +63,7 @@ class BuildBase extends Model 'finished' => 'getFinished', 'plugins' => 'getPlugins', 'committer_email' => 'getCommitterEmail', + 'commit_message' => 'getCommitMessage', // Foreign key getters: 'Project' => 'getProject', @@ -83,6 +85,7 @@ class BuildBase extends Model 'finished' => 'setFinished', 'plugins' => 'setPlugins', 'committer_email' => 'setCommitterEmail', + 'commit_message' => 'setCommitMessage', // Foreign key setters: 'Project' => 'setProject', @@ -113,6 +116,7 @@ class BuildBase extends Model 'status' => array( 'type' => 'tinyint', 'length' => 4, + 'default' => null, ), 'log' => array( 'type' => 'longtext', @@ -150,6 +154,11 @@ class BuildBase extends Model 'nullable' => true, 'default' => null, ), + 'commit_message' => array( + 'type' => 'text', + 'nullable' => true, + 'default' => null, + ), ); /** @@ -318,6 +327,18 @@ class BuildBase extends Model return $rtn; } + /** + * Get the value of CommitMessage / commit_message. + * + * @return string + */ + public function getCommitMessage() + { + $rtn = $this->data['commit_message']; + + return $rtn; + } + /** * Set the value of Id / id. * @@ -524,6 +545,24 @@ class BuildBase extends Model $this->_setModified('committer_email'); } + /** + * Set the value of CommitMessage / commit_message. + * + * @param $value string + */ + public function setCommitMessage($value) + { + $this->_validateString('CommitMessage', $value); + + if ($this->data['commit_message'] === $value) { + return; + } + + $this->data['commit_message'] = $value; + + $this->_setModified('commit_message'); + } + /** * Get the Project model for this Build by Id. * @@ -543,7 +582,7 @@ class BuildBase extends Model $rtn = $this->cache->get($cacheKey, null); if (empty($rtn)) { - $rtn = Factory::getStore('Project')->getById($key); + $rtn = Factory::getStore('Project', 'PHPCI')->getById($key); $this->cache->set($cacheKey, $rtn); } @@ -590,6 +629,33 @@ class BuildBase extends Model */ public function getBuildBuildMetas() { - return Factory::getStore('BuildMeta')->getByBuildId($this->getId()); + return Factory::getStore('BuildMeta', 'PHPCI')->getByBuildId($this->getId()); } + + + + + public static function getByPrimaryKey($value, $useConnection = 'read') + { + return Factory::getStore('Build', 'PHPCI')->getByPrimaryKey($value, $useConnection); + } + + + public static function getById($value, $useConnection = 'read') + { + return Factory::getStore('Build', 'PHPCI')->getById($value, $useConnection); + } + + public static function getByProjectId($value, $limit = null, $useConnection = 'read') + { + return Factory::getStore('Build', 'PHPCI')->getByProjectId($value, $limit, $useConnection); + } + + public static function getByStatus($value, $limit = null, $useConnection = 'read') + { + return Factory::getStore('Build', 'PHPCI')->getByStatus($value, $limit, $useConnection); + } + + + } diff --git a/PHPCI/Model/Base/BuildMetaBase.php b/PHPCI/Model/Base/BuildMetaBase.php index 9ef0f457..83333102 100644 --- a/PHPCI/Model/Base/BuildMetaBase.php +++ b/PHPCI/Model/Base/BuildMetaBase.php @@ -6,7 +6,7 @@ namespace PHPCI\Model\Base; -use b8\Model; +use PHPCI\Model; use b8\Store\Factory; /** @@ -95,6 +95,7 @@ class BuildMetaBase extends Model 'meta_key' => array( 'type' => 'varchar', 'length' => 255, + 'default' => null, ), 'meta_value' => array( 'type' => 'text', @@ -299,7 +300,7 @@ class BuildMetaBase extends Model $rtn = $this->cache->get($cacheKey, null); if (empty($rtn)) { - $rtn = Factory::getStore('Build')->getById($key); + $rtn = Factory::getStore('Build', 'PHPCI')->getById($key); $this->cache->set($cacheKey, $rtn); } @@ -336,4 +337,26 @@ class BuildMetaBase extends Model { return $this->setBuildId($value->getId()); } + + + + + public static function getByPrimaryKey($value, $useConnection = 'read') + { + return Factory::getStore('BuildMeta', 'PHPCI')->getByPrimaryKey($value, $useConnection); + } + + + public static function getById($value, $useConnection = 'read') + { + return Factory::getStore('BuildMeta', 'PHPCI')->getById($value, $useConnection); + } + + public static function getByBuildId($value, $limit = null, $useConnection = 'read') + { + return Factory::getStore('BuildMeta', 'PHPCI')->getByBuildId($value, $limit, $useConnection); + } + + + } diff --git a/PHPCI/Model/Base/ProjectBase.php b/PHPCI/Model/Base/ProjectBase.php index c16d3ee5..df1c0a34 100644 --- a/PHPCI/Model/Base/ProjectBase.php +++ b/PHPCI/Model/Base/ProjectBase.php @@ -6,7 +6,7 @@ namespace PHPCI\Model\Base; -use b8\Model; +use PHPCI\Model; use b8\Store\Factory; /** @@ -91,10 +91,12 @@ class ProjectBase extends Model 'title' => array( 'type' => 'varchar', 'length' => 250, + 'default' => null, ), 'reference' => array( 'type' => 'varchar', 'length' => 250, + 'default' => null, ), 'git_key' => array( 'type' => 'text', @@ -215,19 +217,11 @@ class ProjectBase extends Model /** * Get the value of AccessInformation / access_information. * - * @param string|null $key Key of desired information - * * @return string */ - public function getAccessInformation($key = null) + public function getAccessInformation() { - if (is_null($key)) { - $rtn = $this->data['access_information']; - } else if (isset($this->data['access_information'][$key])) { - $rtn = $this->data['access_information'][$key]; - } else { - $rtn = null; - } + $rtn = $this->data['access_information']; return $rtn; } @@ -405,6 +399,28 @@ class ProjectBase extends Model */ public function getProjectBuilds() { - return Factory::getStore('Build')->getByProjectId($this->getId()); + return Factory::getStore('Build', 'PHPCI')->getByProjectId($this->getId()); } + + + + + public static function getByPrimaryKey($value, $useConnection = 'read') + { + return Factory::getStore('Project', 'PHPCI')->getByPrimaryKey($value, $useConnection); + } + + + public static function getById($value, $useConnection = 'read') + { + return Factory::getStore('Project', 'PHPCI')->getById($value, $useConnection); + } + + public static function getByTitle($value, $limit = null, $useConnection = 'read') + { + return Factory::getStore('Project', 'PHPCI')->getByTitle($value, $limit, $useConnection); + } + + + } diff --git a/PHPCI/Model/Base/UserBase.php b/PHPCI/Model/Base/UserBase.php index e8035660..f0b0e393 100644 --- a/PHPCI/Model/Base/UserBase.php +++ b/PHPCI/Model/Base/UserBase.php @@ -6,7 +6,7 @@ namespace PHPCI\Model\Base; -use b8\Model; +use PHPCI\Model; use b8\Store\Factory; /** @@ -82,14 +82,17 @@ class UserBase extends Model 'email' => array( 'type' => 'varchar', 'length' => 250, + 'default' => null, ), 'hash' => array( 'type' => 'varchar', 'length' => 250, + 'default' => null, ), 'is_admin' => array( 'type' => 'tinyint', 'length' => 1, + 'default' => null, ), 'name' => array( 'type' => 'varchar', @@ -270,4 +273,26 @@ class UserBase extends Model $this->_setModified('name'); } + + + + + public static function getByPrimaryKey($value, $useConnection = 'read') + { + return Factory::getStore('User', 'PHPCI')->getByPrimaryKey($value, $useConnection); + } + + + public static function getById($value, $useConnection = 'read') + { + return Factory::getStore('User', 'PHPCI')->getById($value, $useConnection); + } + + public static function getByEmail($value, $useConnection = 'read') + { + return Factory::getStore('User', 'PHPCI')->getByEmail($value, $useConnection); + } + + + } diff --git a/PHPCI/Model/Build.php b/PHPCI/Model/Build.php index bc814560..ff0a0b23 100644 --- a/PHPCI/Model/Build.php +++ b/PHPCI/Model/Build.php @@ -26,6 +26,8 @@ class Build extends BuildBase const STATUS_SUCCESS = 2; const STATUS_FAILED = 3; + public $currentBuildPath = null; + /** * Get link to commit from another source (i.e. Github) */ @@ -34,6 +36,15 @@ 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) */ diff --git a/PHPCI/Model/Build/RemoteGitBuild.php b/PHPCI/Model/Build/RemoteGitBuild.php index c4b03d2c..35aa8c90 100644 --- a/PHPCI/Model/Build/RemoteGitBuild.php +++ b/PHPCI/Model/Build/RemoteGitBuild.php @@ -35,8 +35,7 @@ class RemoteGitBuild extends Build public function createWorkingCopy(Builder $builder, $buildPath) { $yamlParser = new YamlParser(); - $success = true; - $key = trim($this->getProject()->getGitKey()); + $key = trim($this->getProject()->getGitKey()); if (!empty($key)) { $success = $this->cloneBySsh($builder, $buildPath); @@ -65,7 +64,9 @@ class RemoteGitBuild extends Build */ protected function cloneByHttp(Builder $builder, $cloneTo) { - return $builder->executeCommand('git clone -b %s %s "%s"', $this->getBranch(), $this->getCloneUrl(), $cloneTo); + $success = $builder->executeCommand('git clone -b %s %s "%s"', $this->getBranch(), $this->getCloneUrl(), $cloneTo); + $builder->executeCommand('cd "%s" && git checkout %s', $cloneTo, $this->getCommitId()); + return $success; } /** @@ -88,7 +89,13 @@ class RemoteGitBuild extends Build // Use the key file to do an SSH clone: $cmd = 'eval `ssh-agent -s` && ssh-add "%s" && git clone -b %s %s "%s" && ssh-agent -k'; $success = $builder->executeCommand($cmd, $keyFile, $this->getBranch(), $this->getCloneUrl(), $cloneTo); - + + $commit = $this->getCommitId(); + + if (!empty($commit) && $commit != 'Manual') { + $builder->executeCommand('cd "%s" && git checkout %s', $cloneTo, $this->getCommitId()); + } + // Remove the key file: unlink($keyFile); diff --git a/PHPCI/Model/Project.php b/PHPCI/Model/Project.php index 066041e4..eceab68b 100644 --- a/PHPCI/Model/Project.php +++ b/PHPCI/Model/Project.php @@ -43,4 +43,19 @@ class Project extends ProjectBase return null; } + + public function getAccessInformation($key = null) + { + $data = unserialize($this->data['access_information']); + + if (is_null($key)) { + $rtn = $data; + } elseif (isset($data[$key])) { + $rtn = $data[$key]; + } else { + $rtn = null; + } + + return $rtn; + } } diff --git a/PHPCI/Plugin/Behat.php b/PHPCI/Plugin/Behat.php index 704d76cf..e4b72155 100644 --- a/PHPCI/Plugin/Behat.php +++ b/PHPCI/Plugin/Behat.php @@ -28,6 +28,12 @@ class Behat implements \PHPCI\Plugin $this->phpci = $phpci; $this->features = ''; + if (isset($options['executable'])) { + $this->executable = $options['executable']; + } else { + $this->executable = $this->phpci->findBinary('atoum'); + } + if (!empty($options['features'])) { $this->features = $options['features']; } @@ -41,7 +47,7 @@ class Behat implements \PHPCI\Plugin $curdir = getcwd(); chdir($this->phpci->buildPath); - $behat = $this->phpci->findBinary('behat'); + $behat = $this->executable; if (!$behat) { $this->phpci->logFailure('Could not find behat.'); diff --git a/PHPCI/Plugin/Codeception.php b/PHPCI/Plugin/Codeception.php index f2e580c7..bd168145 100644 --- a/PHPCI/Plugin/Codeception.php +++ b/PHPCI/Plugin/Codeception.php @@ -69,8 +69,8 @@ class Codeception implements \PHPCI\Plugin return false; } - $cmd = $codecept . ' run -c "%s"'; - $success = $this->phpci->executeCommand($cmd, $this->phpci->buildPath . $configPath); + $cmd = 'cd "%s" && ' . $codecept . ' run -c "%s"'; + $success = $this->phpci->executeCommand($cmd, $this->phpci->buildPath, $this->phpci->buildPath . $configPath); return $success; } diff --git a/PHPCI/Plugin/Composer.php b/PHPCI/Plugin/Composer.php index 1af4abf6..0b89afe0 100644 --- a/PHPCI/Plugin/Composer.php +++ b/PHPCI/Plugin/Composer.php @@ -45,8 +45,11 @@ class Composer implements \PHPCI\Plugin $this->phpci->logFailure('Could not find Composer.'); return false; } - - $cmd = $composerLocation . ' --no-ansi --no-interaction '; + $cmd = ''; + if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') { + $cmd = 'php '; + } + $cmd .= $composerLocation . ' --no-ansi --no-interaction '; $cmd .= ($this->preferDist ? '--prefer-dist' : null) . ' --working-dir="%s" %s'; return $this->phpci->executeCommand($cmd, $this->directory, $this->action); diff --git a/PHPCI/Plugin/Email.php b/PHPCI/Plugin/Email.php index 81671e1c..f6317197 100644 --- a/PHPCI/Plugin/Email.php +++ b/PHPCI/Plugin/Email.php @@ -9,6 +9,7 @@ namespace PHPCI\Plugin; +use b8\View; use PHPCI\Builder; use PHPCI\Model\Build; @@ -40,13 +41,13 @@ class Email implements \PHPCI\Plugin */ protected $fromAddress; - public function __construct(Builder $phpci, - Build $build, - \Swift_Mailer $mailer, - array $options = array() + public function __construct( + Builder $phpci, + Build $build, + \Swift_Mailer $mailer, + array $options = array() - ) - { + ) { $this->phpci = $phpci; $this->build = $build; $this->options = $options; @@ -84,10 +85,16 @@ class Email implements \PHPCI\Plugin sprintf("Log Output:
%s
", $logText) ); } else { + $view = new View('Email/failed'); + $view->build = $this->build; + $view->project = $this->build->getProject(); + + $emailHtml = $view->render(); + $sendFailures = $this->sendSeparateEmails( $addresses, sprintf($subjectTemplate, $projectName, "Failing Build"), - sprintf("Log Output:
%s
", $logText) + $emailHtml ); } @@ -99,18 +106,24 @@ class Email implements \PHPCI\Plugin } /** - * @param array|string $toAddresses Array or single address to send to - * @param string $subject Email subject - * @param string $body Email body + * @param string[]|string $toAddresses Array or single address to send to + * @param string[] $ccList + * @param string $subject Email subject + * @param string $body Email body * @return array Array of failed addresses */ - public function sendEmail($toAddresses, $subject, $body) + public function sendEmail($toAddresses, $ccList, $subject, $body) { $message = \Swift_Message::newInstance($subject) ->setFrom($this->fromAddress) ->setTo($toAddresses) ->setBody($body) ->setContentType("text/html"); + + if (is_array($ccList) && count($ccList)) { + $message->setCc($ccList); + } + $failedAddresses = array(); $this->mailer->send($message, $failedAddresses); @@ -120,8 +133,10 @@ class Email implements \PHPCI\Plugin public function sendSeparateEmails(array $toAddresses, $subject, $body) { $failures = array(); + $ccList = $this->getCcAddresses(); + foreach ($toAddresses as $address) { - $newFailures = $this->sendEmail($address, $subject, $body); + $newFailures = $this->sendEmail($address, $ccList, $subject, $body); foreach ($newFailures as $failure) { $failures[] = $failure; } @@ -150,4 +165,17 @@ class Email implements \PHPCI\Plugin } return $addresses; } -} \ No newline at end of file + + protected function getCcAddresses() + { + $ccAddresses = array(); + + if (isset($this->options['cc'])) { + foreach ($this->options['cc'] as $address) { + $ccAddresses[] = $address; + } + } + + return $ccAddresses; + } +} diff --git a/PHPCI/Plugin/Util/ComposerPluginInformation.php b/PHPCI/Plugin/Util/ComposerPluginInformation.php new file mode 100644 index 00000000..c870514e --- /dev/null +++ b/PHPCI/Plugin/Util/ComposerPluginInformation.php @@ -0,0 +1,145 @@ +composerPackages = $composerPackages; + } + + /** + * Returns an array of objects. Each one represents an available plugin + * and will have the following properties: + * name - The friendly name of the plugin (may be an empty string) + * class - The class of the plugin (will include namespace) + * @return \stdClass[] + */ + public function getInstalledPlugins() + { + $this->loadPluginInfo(); + return $this->pluginInfo; + } + + /** + * Returns an array of all the class names of plugins that have been + * loaded. + * + * @return string[] + */ + public function getPluginClasses() + { + return array_map( + function ($plugin) { + return $plugin->class; + }, + $this->getInstalledPlugins() + ); + } + + protected function loadPluginInfo() + { + if ($this->pluginInfo !== null) { + return; + } + $this->pluginInfo = array(); + foreach ($this->composerPackages as $package) { + $this->addPluginsFromPackage($package); + } + } + + /** + * @param \stdClass $package + */ + protected function addPluginsFromPackage($package) + { + if (isset($package->extra->phpci)) { + $phpciData = $package->extra->phpci; + + if (isset($phpciData->pluginNamespace)) { + $rootNamespace = $phpciData->pluginNamespace; + } else { + $rootNamespace = ""; + } + + if (is_array($phpciData->suppliedPlugins)) { + $this->addPlugins( + $phpciData->suppliedPlugins, + $package->name, + $rootNamespace + ); + } + } + } + + /** + * @param \stdClass[] $plugins + * @param string $sourcePackageName + * @param string $rootNamespace + */ + protected function addPlugins( + array $plugins, + $sourcePackageName, + $rootNamespace = "" + ) { + foreach ($plugins as $plugin) { + if (!isset($plugin->class)) { + continue; + } + $this->addPlugin($plugin, $sourcePackageName, $rootNamespace); + } + } + + /** + * @param \stdClass $plugin + * @param string $sourcePackageName + * @param string $rootNamespace + */ + protected function addPlugin( + $plugin, + $sourcePackageName, + $rootNamespace = "" + ) { + $newPlugin = clone $plugin; + + $newPlugin->class = $rootNamespace . $newPlugin->class; + + if (!isset($newPlugin->name)) { + $newPlugin->name = ""; + } + + $newPlugin->source = $sourcePackageName; + + $this->pluginInfo[] = $newPlugin; + } +} diff --git a/PHPCI/Plugin/Util/Executor.php b/PHPCI/Plugin/Util/Executor.php index 5bec1820..39a5d35e 100644 --- a/PHPCI/Plugin/Util/Executor.php +++ b/PHPCI/Plugin/Util/Executor.php @@ -2,7 +2,7 @@ namespace PHPCI\Plugin\Util; -use PHPCI\BuildLogger; +use \PHPCI\Logging\BuildLogger; class Executor { @@ -17,7 +17,7 @@ class Executor */ protected $pluginFactory; - function __construct(Factory $pluginFactory, BuildLogger $logger) + public function __construct(Factory $pluginFactory, BuildLogger $logger) { $this->pluginFactory = $pluginFactory; $this->logger = $logger; @@ -78,8 +78,7 @@ class Executor $class = str_replace('_', ' ', $plugin); $class = ucwords($class); $class = 'PHPCI\\Plugin\\' . str_replace(' ', '', $class); - } - else { + } else { $class = $plugin; } @@ -105,4 +104,4 @@ class Executor return $rtn; } -} \ No newline at end of file +} diff --git a/PHPCI/Plugin/Util/Factory.php b/PHPCI/Plugin/Util/Factory.php index 8adfd99d..9740516f 100644 --- a/PHPCI/Plugin/Util/Factory.php +++ b/PHPCI/Plugin/Util/Factory.php @@ -36,30 +36,10 @@ class Factory { 'array' ); } + - /** - * Trys to get a function from the file path specified. If the - * file returns a function then $this will be passed to it. - * This enables the config file to call any public methods. - * - * @param $configPath - * @return bool - true if the function exists else false. - */ - public function addConfigFromFile($configPath) + public function getLastOptions() { - // The file is expected to return a function which can - // act on the pluginFactory to register any resources needed. - if (file_exists($configPath)) { - $configFunction = require($configPath); - if (is_callable($configFunction)) { - $configFunction($this); - return true; - } - } - return false; - } - - public function getLastOptions() { return $this->currentPluginOptions; } diff --git a/PHPCI/Plugin/Util/FilesPluginInformation.php b/PHPCI/Plugin/Util/FilesPluginInformation.php new file mode 100644 index 00000000..8366c4cb --- /dev/null +++ b/PHPCI/Plugin/Util/FilesPluginInformation.php @@ -0,0 +1,104 @@ +files = $files; + } + + /** + * Returns an array of objects. Each one represents an available plugin + * and will have the following properties: + * name - The friendly name of the plugin (may be an empty string) + * class - The class of the plugin (will include namespace) + * @return \stdClass[] + */ + public function getInstalledPlugins() + { + if ($this->pluginInfo === null) { + $this->loadPluginInfo(); + } + return $this->pluginInfo; + } + + /** + * Returns an array of all the class names of plugins that have been + * loaded. + * + * @return string[] + */ + public function getPluginClasses() + { + return array_map( + function ($plugin) { + return $plugin->class; + }, + $this->getInstalledPlugins() + ); + } + + protected function loadPluginInfo() + { + $this->pluginInfo = array(); + foreach ($this->files as $fileInfo) { + if ($fileInfo instanceof \SplFileInfo) { + if ($fileInfo->isFile()) { + $this->addPluginFromFile($fileInfo); + } + } + } + } + + protected function addPluginFromFile(\SplFileInfo $fileInfo) + { + $newPlugin = new \stdClass(); + $newPlugin->class = $this->getFullClassFromFile($fileInfo); + $newPlugin->source = "core"; + $parts = explode('\\', $newPlugin->class); + $newPlugin->name = end($parts); + + $this->pluginInfo[] = $newPlugin; + } + + protected function getFullClassFromFile(\SplFileInfo $fileInfo) + { + //TODO: Something less horrible than a regular expression + // on the contents of a file + $contents = file_get_contents($fileInfo->getRealPath()); + + $matches = array(); + preg_match('#class +([A-Za-z]+) +implements#i', $contents, $matches); + $className = $matches[1]; + + $matches = array(); + preg_match('#namespace +([A-Za-z\\\\]+);#i', $contents, $matches); + $namespace = $matches[1]; + + return $namespace . '\\' . $className; + } +} diff --git a/PHPCI/Plugin/Util/InstalledPluginInformation.php b/PHPCI/Plugin/Util/InstalledPluginInformation.php new file mode 100644 index 00000000..133b8de8 --- /dev/null +++ b/PHPCI/Plugin/Util/InstalledPluginInformation.php @@ -0,0 +1,23 @@ +pluginInformations[] = $information; + } + + /** + * Returns an array of objects. Each one represents an available plugin + * and will have the following properties: + * name - The friendly name of the plugin (may be an empty string) + * class - The class of the plugin (will include namespace) + * @return \stdClass[] + */ + public function getInstalledPlugins() + { + $arr = array(); + foreach ($this->pluginInformations as $single) { + $arr = array_merge($arr, $single->getInstalledPlugins()); + } + return $arr; + } + + /** + * Returns an array of all the class names of plugins that have been + * loaded. + * + * @return string[] + */ + public function getPluginClasses() + { + $arr = array(); + foreach ($this->pluginInformations as $single) { + $arr = array_merge($arr, $single->getPluginClasses()); + } + return $arr; + } + +} diff --git a/PHPCI/Store.php b/PHPCI/Store.php new file mode 100644 index 00000000..6806a097 --- /dev/null +++ b/PHPCI/Store.php @@ -0,0 +1,7 @@ +prepare($query); $stmt->bindValue(':id', $value); @@ -58,7 +58,7 @@ class BuildMetaStoreBase extends Store $count = null; - $query = 'SELECT * FROM build_meta WHERE build_id = :build_id' . $add; + $query = 'SELECT * FROM `build_meta` WHERE `build_id` = :build_id' . $add; $stmt = Database::getConnection($useConnection)->prepare($query); $stmt->bindValue(':build_id', $value); diff --git a/PHPCI/Store/Base/BuildStoreBase.php b/PHPCI/Store/Base/BuildStoreBase.php index f0e66085..b67d5f73 100644 --- a/PHPCI/Store/Base/BuildStoreBase.php +++ b/PHPCI/Store/Base/BuildStoreBase.php @@ -8,7 +8,7 @@ namespace PHPCI\Store\Base; use b8\Database; use b8\Exception\HttpException; -use b8\Store; +use PHPCI\Store; use PHPCI\Model\Build; /** @@ -31,7 +31,7 @@ class BuildStoreBase extends Store throw new HttpException('Value passed to ' . __FUNCTION__ . ' cannot be null.'); } - $query = 'SELECT * FROM build WHERE id = :id LIMIT 1'; + $query = 'SELECT * FROM `build` WHERE `id` = :id LIMIT 1'; $stmt = Database::getConnection($useConnection)->prepare($query); $stmt->bindValue(':id', $value); @@ -58,7 +58,7 @@ class BuildStoreBase extends Store $count = null; - $query = 'SELECT * FROM build WHERE project_id = :project_id' . $add; + $query = 'SELECT * FROM `build` WHERE `project_id` = :project_id' . $add; $stmt = Database::getConnection($useConnection)->prepare($query); $stmt->bindValue(':project_id', $value); @@ -90,7 +90,7 @@ class BuildStoreBase extends Store $count = null; - $query = 'SELECT * FROM build WHERE status = :status' . $add; + $query = 'SELECT * FROM `build` WHERE `status` = :status' . $add; $stmt = Database::getConnection($useConnection)->prepare($query); $stmt->bindValue(':status', $value); diff --git a/PHPCI/Store/Base/ProjectStoreBase.php b/PHPCI/Store/Base/ProjectStoreBase.php index 8b78c055..410a305e 100644 --- a/PHPCI/Store/Base/ProjectStoreBase.php +++ b/PHPCI/Store/Base/ProjectStoreBase.php @@ -8,7 +8,7 @@ namespace PHPCI\Store\Base; use b8\Database; use b8\Exception\HttpException; -use b8\Store; +use PHPCI\Store; use PHPCI\Model\Project; /** @@ -31,7 +31,7 @@ class ProjectStoreBase extends Store throw new HttpException('Value passed to ' . __FUNCTION__ . ' cannot be null.'); } - $query = 'SELECT * FROM project WHERE id = :id LIMIT 1'; + $query = 'SELECT * FROM `project` WHERE `id` = :id LIMIT 1'; $stmt = Database::getConnection($useConnection)->prepare($query); $stmt->bindValue(':id', $value); @@ -58,7 +58,7 @@ class ProjectStoreBase extends Store $count = null; - $query = 'SELECT * FROM project WHERE title = :title' . $add; + $query = 'SELECT * FROM `project` WHERE `title` = :title' . $add; $stmt = Database::getConnection($useConnection)->prepare($query); $stmt->bindValue(':title', $value); diff --git a/PHPCI/Store/Base/UserStoreBase.php b/PHPCI/Store/Base/UserStoreBase.php index c4268804..fd903d8e 100644 --- a/PHPCI/Store/Base/UserStoreBase.php +++ b/PHPCI/Store/Base/UserStoreBase.php @@ -8,7 +8,7 @@ namespace PHPCI\Store\Base; use b8\Database; use b8\Exception\HttpException; -use b8\Store; +use PHPCI\Store; use PHPCI\Model\User; /** @@ -31,7 +31,7 @@ class UserStoreBase extends Store throw new HttpException('Value passed to ' . __FUNCTION__ . ' cannot be null.'); } - $query = 'SELECT * FROM user WHERE id = :id LIMIT 1'; + $query = 'SELECT * FROM `user` WHERE `id` = :id LIMIT 1'; $stmt = Database::getConnection($useConnection)->prepare($query); $stmt->bindValue(':id', $value); @@ -50,7 +50,7 @@ class UserStoreBase extends Store throw new HttpException('Value passed to ' . __FUNCTION__ . ' cannot be null.'); } - $query = 'SELECT * FROM user WHERE email = :email LIMIT 1'; + $query = 'SELECT * FROM `user` WHERE `email` = :email LIMIT 1'; $stmt = Database::getConnection($useConnection)->prepare($query); $stmt->bindValue(':email', $value); diff --git a/PHPCI/View/Build/view.phtml b/PHPCI/View/Build/view.phtml index aa18e591..6e181444 100644 --- a/PHPCI/View/Build/view.phtml +++ b/PHPCI/View/Build/view.phtml @@ -1,17 +1,23 @@
-

Build #getId(); ?>

-

Branch: getBranch(); ?> - getCommitId() == 'Manual' ? 'Manual Build' : 'Commit: ' . $build->getCommitId(); ?>

+

getProject()->getTitle(); ?> - Build #getId(); ?>

+ +
+ Branch: getBranch(); ?>
+ Committer: getCommitterEmail(); ?>
+ Commit ID: getCommitId() == 'Manual' ? 'HEAD' : $build->getCommitId(); ?>
+ Commit Message: getCommitMessage(); ?> +
Options