Merge remote-tracking branch 'origin/master' into feature/pluginfactoryconfig

Conflicts:
	PHPCI/Builder.php
	PHPCI/Plugin/Util/Factory.php
This commit is contained in:
steve.brazier 2014-03-25 14:13:45 +00:00
commit cc71045e57
79 changed files with 1857 additions and 761 deletions

View file

@ -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 = '<h1>There was a problem with this request</h1>
<p>Please paste the details below into a
<a href="https://github.com/Block8/PHPCI/issues/new">new bug report</a>
so that we can investigate and fix it.</p>';
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;
}
}

View file

@ -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)
{

View file

@ -1,31 +0,0 @@
<?php
namespace PHPCI;
use Psr\Log\LogLevel;
/**
* PHPCI Build Logger
*/
interface 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());
/**
* Add a success-coloured message to the log.
* @param string
*/
public function logSuccess($message);
/**
* 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);
}

View file

@ -9,21 +9,23 @@
namespace PHPCI;
use PHPCI\Helper\BuildInterpolator;
use PHPCI\Helper\CommandExecutor;
use PHPCI\Helper\MailerFactory;
use PHPCI\Logging\BuildLogger;
use PHPCI\Model\Build;
use b8\Store;
use b8\Config;
use PHPCI\Plugin\Util\Factory;
use b8\Store\Factory;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
use PHPCI\Plugin\Util\Factory as PluginFactory;
/**
* PHPCI Build Runner
* @author Dan Cryer <dan@block8.co.uk>
*/
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,

View file

@ -0,0 +1,130 @@
<?php
/**
* PHPCI - Continuous Integration for PHP
*
* @copyright Copyright 2013, Block 8 Limited.
* @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md
* @link http://www.phptesting.org/
*/
namespace PHPCI\Command;
use Symfony\Component\Console\Command\Command;
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;
/**
* Create admin command - creates an admin user
* @author Wogan May (@woganmay)
* @package PHPCI
* @subpackage Console
*/
class CreateAdminCommand extends Command
{
protected function configure()
{
$this
->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;
}
}

View file

@ -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);
}

View file

@ -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();
}

View file

@ -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");
}
}

View file

@ -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");

View file

@ -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}');
}
}

View file

@ -1,67 +0,0 @@
<?php
/**
* PHPCI - Continuous Integration for PHP
*
* @copyright Copyright 2013, Block 8 Limited.
* @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md
* @link http://www.phptesting.org/
*/
namespace PHPCI\Controller;
use b8;
use b8\Store;
use PHPCI\Model\Build;
/**
* BitBucket Controller - Processes webhook pings from BitBucket.
* @author Dan Cryer <dan@block8.co.uk>
* @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');
}
}

View file

@ -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);

View file

@ -1,69 +0,0 @@
<?php
/**
* PHPCI - Continuous Integration for PHP
*
* @copyright Copyright 2013, Block 8 Limited.
* @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md
* @link http://www.phptesting.org/
*/
namespace PHPCI\Controller;
use b8;
use b8\Store;
use PHPCI\Model\Build;
/**
* @author Sami Tikka <stikka@iki.fi>
*/
class GitController extends \PHPCI\Controller
{
public function init()
{
$this->_buildStore = Store\Factory::getStore('Build');
}
/**
* Called by POSTing to /git/webhook/<project_id>?branch=<branch>&commit=<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');
}
}

View file

@ -1,77 +0,0 @@
<?php
/**
* PHPCI - Continuous Integration for PHP
*
* @copyright Copyright 2013, Block 8 Limited.
* @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md
* @link http://www.phptesting.org/
*/
namespace PHPCI\Controller;
use b8;
use b8\Store;
use PHPCI\Model\Build;
/**
* Github Controller - Processes webhook pings from Github.
* @author Dan Cryer <dan@block8.co.uk>
* @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');
}
}

View file

@ -1,66 +0,0 @@
<?php
/**
* PHPCI - Continuous Integration for PHP
*
* @copyright Copyright 2013, Block 8 Limited.
* @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md
* @link http://www.phptesting.org/
*/
namespace PHPCI\Controller;
use b8;
use b8\Store;
use PHPCI\Model\Build;
/**
* Gitlab Controller - Processes webhook pings from Gitlab.
* @author Alex Russell <alex@clevercherry.com>, Dan Cryer <dan@block8.co.uk>
* @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');
}
}

View file

@ -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();

View file

@ -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();
}

View file

@ -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']);

View file

@ -0,0 +1,206 @@
<?php
/**
* PHPCI - Continuous Integration for PHP
*
* @copyright Copyright 2013, Block 8 Limited.
* @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md
* @link http://www.phptesting.org/
*/
namespace PHPCI\Controller;
use b8;
use b8\Store;
use PHPCI\Model\Build;
/**
* Webhook Controller - Processes webhook pings from BitBucket, Github, Gitlab, etc.
* @author Dan Cryer <dan@block8.co.uk>
* @author Sami Tikka <stikka@iki.fi>
* @author Alex Russell <alex@clevercherry.com>
* @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/<project_id>?branch=<branch>&commit=<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');
}
}

View file

@ -0,0 +1,61 @@
<?php
namespace PHPCI\Helper;
use PHPCI\Model\Build;
class BuildInterpolator
{
/**
* An array of key => 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);
}
}

View file

@ -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;
}
}

View file

@ -1,6 +1,6 @@
<?php
namespace PHPCI\Helper;
namespace PHPCI\Logging;
use b8\Store;
@ -16,7 +16,7 @@ class BuildDBLogHandler extends AbstractProcessingHandler
protected $logValue;
function __construct(
public function __construct(
Build $build,
$level = LogLevel::INFO,
$bubble = true
@ -29,7 +29,10 @@ class BuildDBLogHandler extends AbstractProcessingHandler
protected function write(array $record)
{
$this->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);
}
}
}

View file

@ -0,0 +1,96 @@
<?php
namespace PHPCI\Logging;
use PHPCI\Model\Build;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
class BuildLogger implements LoggerAwareInterface
{
/**
* @var LoggerInterface
*/
protected $logger;
/**
* @var Build
*/
protected $build;
public function __construct(LoggerInterface $logger, Build $build)
{
$this->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;
}
}

View file

@ -1,12 +1,12 @@
<?php
namespace PHPCI\Helper;
namespace PHPCI\Logging;
use PHPCI\Model\Build;
class LoggedBuildContextTidier
{
function __invoke()
public function __invoke()
{
return $this->tidyLoggedBuildContext(func_get_arg(0));
}
@ -29,4 +29,4 @@ class LoggedBuildContextTidier
}
return $logRecord;
}
}
}

View file

@ -1,14 +1,15 @@
<?php
namespace PHPCI\Helper;
namespace PHPCI\Logging;
use Monolog\Logger;
class LoggerConfig {
class LoggerConfig
{
const KEY_AlwaysLoaded = "_";
const KEY_ALWAYS_LOADED = "_";
private $config;
@ -23,8 +24,7 @@ class LoggerConfig {
{
if (file_exists($filePath)) {
$configArray = require($filePath);
}
else {
} else {
$configArray = array();
}
return new self($configArray);
@ -36,7 +36,8 @@ class LoggerConfig {
* array of LogHandlers.
* @param array $configArray
*/
function __construct(array $configArray = array()) {
public function __construct(array $configArray = array())
{
$this->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;
}
}
}

View file

@ -1,6 +1,6 @@
<?php
namespace PHPCI\Helper;
namespace PHPCI\Logging;
use Monolog\Handler\AbstractProcessingHandler;
use Psr\Log\LogLevel;
@ -14,7 +14,7 @@ class OutputLogHandler extends AbstractProcessingHandler
*/
protected $output;
function __construct(
public function __construct(
OutputInterface $output,
$level = LogLevel::INFO,
$bubble = true
@ -30,4 +30,4 @@ class OutputLogHandler extends AbstractProcessingHandler
}
}
}

7
PHPCI/Model.php Normal file
View file

@ -0,0 +1,7 @@
<?php
namespace PHPCI;
abstract class Model extends \b8\Model
{
}

View file

@ -6,7 +6,7 @@
namespace PHPCI\Model\Base;
use b8\Model;
use PHPCI\Model;
use b8\Store\Factory;
/**
@ -44,6 +44,7 @@ class BuildBase extends Model
'finished' => 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);
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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)
*/

View file

@ -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);

View file

@ -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;
}
}

View file

@ -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.');

View file

@ -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;
}

View file

@ -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);

View file

@ -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: <br><pre>%s</pre>", $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: <br><pre>%s</pre>", $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;
}
}
protected function getCcAddresses()
{
$ccAddresses = array();
if (isset($this->options['cc'])) {
foreach ($this->options['cc'] as $address) {
$ccAddresses[] = $address;
}
}
return $ccAddresses;
}
}

View file

@ -0,0 +1,145 @@
<?php
namespace PHPCI\Plugin\Util;
class ComposerPluginInformation implements InstalledPluginInformation
{
/**
* @var array
*/
protected $composerPackages;
/**
* @var array
*/
protected $pluginInfo = null;
/**
* @param string $filePath The path of installed.json created by composer.
* @return ComposerPluginInformation
*/
public static function buildFromYaml($filePath)
{
if (file_exists($filePath)) {
$installed = json_decode(file_get_contents($filePath));
} else {
$installed = array();
}
return new self($installed);
}
/**
* @param \stdClass[] $composerPackages This should be the contents of the
* installed.json file created by composer
*/
public function __construct(array $composerPackages)
{
$this->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;
}
}

View file

@ -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;
}
}
}

View file

@ -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;
}

View file

@ -0,0 +1,104 @@
<?php
namespace PHPCI\Plugin\Util;
class FilesPluginInformation implements InstalledPluginInformation
{
/**
* A collection of all the file path information for
* the installed plugins.
*
* @var \SplFileInfo[]
*/
protected $files;
/**
* Each item in the array contains the information for
* a single plugin.
*
* @var array
*/
protected $pluginInfo = null;
public static function newFromDir($dirPath)
{
return new self(new \DirectoryIterator($dirPath));
}
public function __construct(\Iterator $files)
{
$this->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;
}
}

View file

@ -0,0 +1,23 @@
<?php
namespace PHPCI\Plugin\Util;
interface InstalledPluginInformation
{
/**
* 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();
/**
* Returns an array of all the class names of plugins that have been
* loaded.
*
* @return string[]
*/
public function getPluginClasses();
}

View file

@ -0,0 +1,48 @@
<?php
namespace PHPCI\Plugin\Util;
class PluginInformationCollection implements InstalledPluginInformation
{
/**
* @var InstalledPluginInformation[]
*/
protected $pluginInformations = array();
public function add(InstalledPluginInformation $information)
{
$this->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;
}
}

7
PHPCI/Store.php Normal file
View file

@ -0,0 +1,7 @@
<?php
namespace PHPCI;
abstract class Store extends \b8\Store
{
}

View file

@ -8,7 +8,7 @@ namespace PHPCI\Store\Base;
use b8\Database;
use b8\Exception\HttpException;
use b8\Store;
use PHPCI\Store;
use PHPCI\Model\BuildMeta;
/**
@ -31,7 +31,7 @@ class BuildMetaStoreBase extends Store
throw new HttpException('Value passed to ' . __FUNCTION__ . ' cannot be null.');
}
$query = 'SELECT * FROM build_meta WHERE id = :id LIMIT 1';
$query = 'SELECT * FROM `build_meta` WHERE `id` = :id LIMIT 1';
$stmt = Database::getConnection($useConnection)->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);

View file

@ -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);

View file

@ -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);

View file

@ -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);

View file

@ -1,17 +1,23 @@
<div id="title">
<h1 style="display: inline-block">Build #<?php print $build->getId(); ?></h1>
<h3 style="margin-left: 40px; display: inline-block">Branch: <?php print $build->getBranch(); ?> - <?php print $build->getCommitId() == 'Manual' ? 'Manual Build' : 'Commit: ' . $build->getCommitId(); ?></h3>
<h1 style="display: inline-block"><?php print $build->getProject()->getTitle(); ?> - Build #<?php print $build->getId(); ?></h1>
<div id="build-info">
<strong>Branch: </strong> <?php print $build->getBranch(); ?><br>
<strong>Committer: </strong> <?php print $build->getCommitterEmail(); ?><br>
<strong>Commit ID: </strong> <?php print $build->getCommitId() == 'Manual' ? 'HEAD' : $build->getCommitId(); ?><br>
<strong>Commit Message: </strong> <?php print $build->getCommitMessage(); ?>
</div>
</div>
<div class="row">
<div class="col-lg-3">
<ul class="nav nav-pills nav-stacked">
<li><a href="<?= PHPCI_URL ?>"><i class="icon-home"></i> Dashboard</a></li>
<li><a href="<?= PHPCI_URL ?>project/view/<?php print $build->getProject()->getId(); ?>"><i class="icon-folder-open"></i> <?php print htmlspecialchars($build->getProject()->getTitle()); ?></a></li>
<li><a href="<?php echo PHPCI_URL ?>"><i class="icon-home"></i> Dashboard</a></li>
<li><a href="<?php echo PHPCI_URL ?>project/view/<?php print $build->getProject()->getId(); ?>"><i class="icon-folder-open"></i> <?php print htmlspecialchars($build->getProject()->getTitle()); ?></a></li>
</ul>
<h5>Options</h5>
<ul class="nav nav-pills nav-stacked">
<li><a href="<?= PHPCI_URL ?>build/rebuild/<?php print $build->getId(); ?>"><i class="icon-cog"></i> Rebuild</a></li>
<li><a href="<?php echo PHPCI_URL ?>build/rebuild/<?php print $build->getId(); ?>"><i class="icon-cog"></i> Rebuild</a></li>
<?php if($this->User()->getIsAdmin()): ?>
<li><a href="#" id="delete-build"><i class="icon-trash"></i> Delete Build</a></li>
<?php endif; ?>
@ -39,4 +45,4 @@ foreach ($plugins as $plugin) {
$(document).ready(function() {
PHPCI.renderPlugins();
});
</script>
</script>

View file

@ -37,8 +37,8 @@ switch($build->getStatus())
}
?>
<tr class="<?php print $cls; ?>">
<td><a href="<?= PHPCI_URL ?>build/view/<?php print $build->getId(); ?>">#<?php print str_pad($build->getId(), 6, '0', STR_PAD_LEFT); ?></a></td>
<td><a href="<?= PHPCI_URL ?>project/view/<?php print $build->getProjectId(); ?>">
<td><a href="<?php echo PHPCI_URL ?>build/view/<?php print $build->getId(); ?>">#<?php print str_pad($build->getId(), 6, '0', STR_PAD_LEFT); ?></a></td>
<td><a href="<?php echo PHPCI_URL ?>project/view/<?php print $build->getProjectId(); ?>">
<?php
if (is_object($build->getProject())) {
print htmlspecialchars($build->getProject()->getTitle());
@ -56,24 +56,24 @@ switch($build->getStatus())
$plugins = array();
}
if ( 0 === count($plugins) ) {
?> <span class='label label-<?= $subcls ?>'><?= $status ?></span> <?php
?> <span class='label label-<?php echo $subcls ?>'><?php echo $status ?></span> <?php
}
?>
<?php
foreach($plugins as $plugin => $pluginstatus):
$subcls = $pluginstatus?'label label-success':'label label-danger';
?> <span class='<?= $subcls ?>'><?php print $this->Build()->formatPluginName($plugin); ?></span> <?php endforeach; ?>
?> <span class='<?php echo $subcls ?>'><?php print $this->Build()->formatPluginName($plugin); ?></span> <?php endforeach; ?>
<br style='clear:both;' />
</td>
<td>
<div class="btn-group">
<a class="btn btn-default btn-small" href="<?= PHPCI_URL ?>build/view/<?php print $build->getId(); ?>">View</a>
<a class="btn btn-default btn-small" href="<?php echo PHPCI_URL ?>build/view/<?php print $build->getId(); ?>">View</a>
<?php if($this->User()->getIsAdmin()): ?>
<button class="btn btn-default btn-small dropdown-toggle" data-toggle="dropdown">
<span class="caret"></span>
</button>
<ul class="dropdown-menu">
<li><a href="javascript:confirmDelete('<?= PHPCI_URL ?>build/delete/<?php print $build->getId(); ?>', 'Build').onClose = function(){refreshBuildsTable();};">Delete Build</a></li>
<li><a href="<?php echo PHPCI_URL ?>build/delete/<?php print $build->getId(); ?>" class="phpci-app-delete-build">Delete Build</a></li>
</ul>
<?php endif; ?>
</div>

View file

@ -0,0 +1,15 @@
<div style="background: #900; padding: 25px;">
<div style="background: #fff; padding: 15px; border-radius: 5px">
<div style="font-family: arial, verdana, sans-serif; font-size: 25px; margin-bottom: 15px">
<?= $project->getTitle(); ?> - Build #<?= $build->getId(); ?>
</div>
<div style="font-family: arial, verdana, sans-serif; font-size: 15px">
<p>Your commit <strong><?= $build->getCommitId(); ?></strong> caused a failed build in project <strong><?= $project->getTitle(); ?></strong>.</p>
<p style="margin: 10px; background: #fafafa"><?= $build->getCommitMessage(); ?></p>
<p>Please review <a href="<?= $build->getCommitLink(); ?>">your commit</a> and the <a href="<?= PHPCI_URL . 'build/view/' . $build->getId(); ?>">build log</a>.</p>
</div>
</div>
</div>

View file

@ -4,14 +4,14 @@
<div class="row">
<div class="col-lg-3">
<ul class="nav nav-pills nav-stacked">
<li class="active"><a href="<?= PHPCI_URL ?>"><i class="icon-home"></i> Dashboard</a></li>
<li class="active"><a href="<?php echo PHPCI_URL ?>"><i class="icon-home"></i> Dashboard</a></li>
</ul>
<?php if (count($projects)): ?>
<h5>Projects</h5>
<ul class="nav nav-pills nav-stacked">
<?php foreach($projects as $project): ?>
<li><a href="<?= PHPCI_URL ?>project/view/<?php print $project->getId(); ?>"><?php print htmlspecialchars($project->getTitle()); ?></a></li>
<li><a href="<?php echo PHPCI_URL ?>project/view/<?php print $project->getId(); ?>"><?php print htmlspecialchars($project->getTitle()); ?></a></li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
@ -60,8 +60,10 @@
<script>
refreshBuildsTable = function()
{
$('#latest-builds').load('<?= PHPCI_URL ?>home/latest');
$('#latest-builds').load('<?php echo PHPCI_URL ?>home/latest', function () {
$('#latest-builds').trigger('latest-builds:reload');
});
};
setInterval(refreshBuildsTable, 10000);
</script>
</script>

View file

@ -1,4 +1,4 @@
<h1 id="title">Plugins</h1>
<h1 id="title">Packages and Provided Plugins</h1>
<?php if (!$canInstall): ?>
<p class="alert alert-danger">PHPCI cannot automatically install/remove plugins for you, as either the <strong>shell_exec()</strong>
function is disabled or PHPCI could not find Composer. PHPCI will update composer.json for you, but you will need to run Composer manually to make the changes.</p>
@ -9,19 +9,42 @@
<?php endif; ?>
<?php if (isset($_GET['r'])): ?>
<p class="alert alert-success"><strong><?= $_GET['r']; ?></strong> has been removed.</p>
<p class="alert alert-success"><strong><?php echo $_GET['r']; ?></strong> has been removed.</p>
<?php endif; ?>
<?php if (isset($_GET['i'])): ?>
<p class="alert alert-success"><strong><?= $_GET['i']; ?></strong> has been installed.</p>
<p class="alert alert-success"><strong><?php echo $_GET['i']; ?></strong> has been installed.</p>
<?php endif; ?>
<?php if (isset($_GET['w'])): ?>
<p class="alert alert-success"><strong><?= $_GET['w']; ?></strong> has been added to composer.json and will be installed next time you run composer update.</p>
<p class="alert alert-success"><strong><?php echo $_GET['w']; ?></strong> has been added to composer.json and will be installed next time you run composer update.</p>
<?php endif; ?>
<div class="box">
<h3 class="title">Installed Plugins</h3>
<h3 class="title">Available Plugins</h3>
<table class="table-striped table-bordered table">
<thead>
<tr>
<th>Name</th>
<th>Class</th>
<th>Provided by Package</th>
</tr>
</thead>
<tbody>
<?php foreach ($plugins as $plugin): ?>
<tr>
<td><?= $plugin->name; ?></td>
<td><?= $plugin->class; ?></td>
<td><?= $plugin->source; ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<div class="box">
<h3 class="title">Installed Packages</h3>
<table class="table-striped table-bordered table">
<thead>
@ -32,13 +55,13 @@
</tr>
</thead>
<tbody>
<?php foreach ($installed as $package => $version): ?>
<?php foreach ($installedPackages as $package => $version): ?>
<tr>
<td><?= $package; ?></td>
<td><?= $version; ?></td>
<td><?php echo $package; ?></td>
<td><?php echo $version; ?></td>
<td>
<?php if (!in_array($package, $required) && $canWrite): ?>
<a class="btn btn-danger btn-small" href="<?= PHPCI_URL ?>plugin/remove?package=<?= $package; ?>">Remove &raquo;</a>
<a class="btn btn-danger btn-small" href="<?php echo PHPCI_URL ?>plugin/remove?package=<?php echo $package; ?>">Remove &raquo;</a>
<?php endif; ?>
</td>
</tr>
@ -48,7 +71,7 @@
</div>
<div class="box">
<h3 class="title">Suggested Plugins</h3>
<h3 class="title">Suggested Packages</h3>
<table class="table-striped table-bordered table">
<thead>
@ -59,14 +82,14 @@
</tr>
</thead>
<tbody>
<?php foreach ($suggested as $package => $version): ?>
<?php if (in_array($package, array_keys($installed))) { continue; } ?>
<?php foreach ($suggestedPackages as $package => $version): ?>
<?php if (in_array($package, array_keys($installedPackages))) { continue; } ?>
<tr>
<td><?= $package; ?></td>
<td><?= $version; ?></td>
<td><?php echo $package; ?></td>
<td><?php echo $version; ?></td>
<td>
<?php if ($canWrite): ?>
<button data-name="<?= $package; ?>" class="install-package btn btn-success btn-small">Install &raquo;</button>
<button data-name="<?php echo $package; ?>" class="install-package btn btn-success btn-small">Install &raquo;</button>
<?php endif; ?>
</td>
</tr>
@ -76,7 +99,7 @@
</div>
<div class="box">
<h3 class="title">Search Packagist for More Plugins</h3>
<h3 class="title">Search Packagist for More Packages</h3>
<div class="input-group">
<input id="search-query" type="text" class="form-control">

View file

@ -5,15 +5,15 @@
<div class="row">
<div class="col-lg-3">
<ul class="nav nav-pills nav-stacked">
<li><a href="<?= PHPCI_URL ?>"><i class="icon-home"></i> Dashboard</a></li>
<li><a href="<?= PHPCI_URL ?>project/view/<?php print $project->getId(); ?>"><i class="icon-folder-open"></i> <?php print htmlspecialchars($project->getTitle()); ?></a></li>
<li><a href="<?php echo PHPCI_URL ?>"><i class="icon-home"></i> Dashboard</a></li>
<li><a href="<?php echo PHPCI_URL ?>project/view/<?php print $project->getId(); ?>"><i class="icon-folder-open"></i> <?php print htmlspecialchars($project->getTitle()); ?></a></li>
</ul>
<h5>Options</h5>
<ul class="nav nav-pills nav-stacked">
<li><a href="<?= PHPCI_URL ?>project/build/<?php print $project->getId(); ?>"><i class="icon-cog"></i> Build Now</a></li>
<li><a href="<?php echo PHPCI_URL ?>project/build/<?php print $project->getId(); ?>"><i class="icon-cog"></i> Build Now</a></li>
<?php if($this->User()->getIsAdmin()): ?>
<li><a href="<?= PHPCI_URL ?>project/edit/<?php print $project->getId(); ?>"><i class="icon-edit"></i> Edit Project</a></li>
<li><a href="<?php echo PHPCI_URL ?>project/edit/<?php print $project->getId(); ?>"><i class="icon-edit"></i> Edit Project</a></li>
<li><a href="#" id="delete-project"><i class="icon-trash"></i> Delete Project</a></li>
<?php endif; ?>
</ul>
@ -27,17 +27,17 @@
switch($project->getType())
{
case 'github':
$url = (empty($_SERVER['HTTPS']) ? 'http' : 'https') . '://' . $_SERVER['HTTP_HOST'] . '/github/webhook/' . $project->getId();
$url = PHPCI_URL . 'webhook/github/' . $project->getId();
print ' as a "WebHook URL" in the <a href="https://github.com/' . $project->getReference() . '/settings/hooks">Service Hooks</a> section of your Github repository.<br><br><strong style="word-wrap: break-word;">' . $url . '</strong>';
break;
case 'gitlab':
$url = (empty($_SERVER['HTTPS']) ? 'http' : 'https') . '://' . $_SERVER['HTTP_HOST'] . '/gitlab/webhook/' . $project->getId();
$url = PHPCI_URL. 'webhook/gitlab/' . $project->getId();
print ' as a "WebHook URL" in the Web Hooks section of your Gitlab repository.<br><br><strong style="word-wrap: break-word;">' . $url . '</strong>';
break;
case 'bitbucket':
$url = (empty($_SERVER['HTTPS']) ? 'http' : 'https') . '://' . $_SERVER['HTTP_HOST'] . '/bitbucket/webhook/' . $project->getId();
$url = PHPCI_URL . 'webhook/bitbucket/' . $project->getId();
print ' as a "POST" service in the <a href="https://bitbucket.org/' . $project->getReference() . '/admin/services">Services</a> section of your Bitbucket repository.<br><br><strong style="word-wrap: break-word;">' . $url . '</strong>';
break;
}
@ -86,14 +86,14 @@
<script>
setInterval(function()
{
$('#latest-builds').load('<?= PHPCI_URL ?>project/builds/<?php print $project->getId(); ?>');
$('#latest-builds').load('<?php echo PHPCI_URL ?>project/builds/<?php print $project->getId(); ?>');
}, 10000);
$(function() {
$('#delete-project').on('click', function (e) {
e.preventDefault();
confirmDelete(
"<?= PHPCI_URL ?>project/delete/<?php print $project->getId(); ?>", "Project"
"<?php echo PHPCI_URL ?>project/delete/<?php print $project->getId(); ?>", "Project"
).onCloseConfirmed = function () {window.location = '/'};
});
})

View file

@ -39,4 +39,4 @@ if(!empty($token)) {
?>
$(document).ready(setupProjectForm);
</script>
</script>

View file

@ -5,9 +5,9 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" type="text/css" href="<?= PHPCI_URL ?>assets/css/bootstrap.min.css">
<link rel="stylesheet" type="text/css" href="<?php echo PHPCI_URL ?>assets/css/bootstrap.min.css">
<script src="//code.jquery.com/jquery-1.8.1.min.js"></script>
<script src="<?= PHPCI_URL ?>assets/js/bootstrap.min.js"></script>
<script src="<?php echo PHPCI_URL ?>assets/js/bootstrap.min.js"></script>
<style type="text/css">
@ -88,4 +88,4 @@
</div>
</div>
</body>
</html>
</html>

View file

@ -34,7 +34,7 @@
?>
<?php if (!empty($id) && empty($settings['phpci']['github']['token'])): ?>
<p class="alert alert-warning clearfix">
Before you can start using Github, you need to <a href="<?= $githubUri; ?>">sign in</a> and grant PHPCI access to your account.
Before you can start using Github, you need to <a href="<?php echo $githubUri; ?>">sign in</a> and grant PHPCI access to your account.
</p>
<?php endif; ?>
@ -42,7 +42,7 @@
<p class="alert alert-success">
PHPCI is successfully linked to Github account
<strong>
<a href="<?= $githubUser['html_url']; ?>"><?= $githubUser['name']; ?></a>
<a href="<?php echo $githubUser['html_url']; ?>"><?php echo $githubUser['name']; ?></a>
</strong>
</p>
<?php endif; ?>

View file

@ -78,11 +78,11 @@ foreach($projects as $project):
?>
<tr class="<?php print $cls; ?>">
<td>
<span class='label label-<?= $subcls ?>'>
<?= $health ?>
<span class='label label-<?php echo $subcls ?>'>
<?php echo $health ?>
</span>
</td>
<td><a href='<?= PHPCI_URL ?>project/view/<?= $project->getId() ?>'><?= htmlspecialchars($project->getTitle()) ?></a></td>
<td><a href='<?php echo PHPCI_URL ?>project/view/<?php echo $project->getId() ?>'><?php echo htmlspecialchars($project->getTitle()) ?></a></td>
<td><?php print is_null($success) ? 'Never' : $success; ?></td>
<td><?php print is_null($failure) ? 'Never' : $failure; ?></td>
<td>
@ -92,6 +92,6 @@ foreach($projects as $project):
}
?>
</td>
<td><a class="btn btn-default btn-small" href='<?= PHPCI_URL ?>project/build/<?= $project->getId(); ?>'>build now &raquo;</a></td>
<td><a class="btn btn-default btn-small" href='<?php echo PHPCI_URL ?>project/build/<?php echo $project->getId(); ?>'>build now &raquo;</a></td>
</tr>
<?php endforeach; ?>

View file

@ -5,10 +5,10 @@
<div class="row">
<div class="col-lg-3">
<ul class="nav nav-pills nav-stacked">
<li><a href="<?= PHPCI_URL ?>"><i class="icon-home"></i> Dashboard</a></li>
<li class="active"><a href="<?= PHPCI_URL ?>user"><i class="icon-user"></i> Users</a></li>
<li><a href="<?php echo PHPCI_URL ?>"><i class="icon-home"></i> Dashboard</a></li>
<li class="active"><a href="<?php echo PHPCI_URL ?>user"><i class="icon-user"></i> Users</a></li>
<?php if($this->User()->getIsAdmin()): ?>
<li><a href="<?= PHPCI_URL ?>user/add"><i class="icon-plus-sign"></i> Add User</a></li>
<li><a href="<?php echo PHPCI_URL ?>user/add"><i class="icon-plus-sign"></i> Add User</a></li>
<?php endif; ?>
</ul>
</div>
@ -39,18 +39,18 @@
}
?>
<tr class="<?php print $cls; ?>">
<td><a href="<?= PHPCI_URL ?>user/edit/<?php print $user->getId(); ?>"><?php print $user->getEmail(); ?></a></td>
<td><a href="<?php echo PHPCI_URL ?>user/edit/<?php print $user->getId(); ?>"><?php print $user->getEmail(); ?></a></td>
<td><?php print htmlspecialchars($user->getName()); ?></td>
<td><?php print $status; ?></td>
<td>
<?php if($this->User()->getIsAdmin()): ?>
<div class="btn-group">
<a class="btn btn-default btn-small" href="<?= PHPCI_URL ?>user/edit/<?php print $user->getId(); ?>">Edit</a>
<a class="btn btn-default btn-small" href="<?php echo PHPCI_URL ?>user/edit/<?php print $user->getId(); ?>">Edit</a>
<button class="btn btn-default btn-small dropdown-toggle" data-toggle="dropdown">
<span class="caret"></span>
</button>
<ul class="dropdown-menu">
<li><a href="javascript:confirmDelete('<?= PHPCI_URL ?>user/delete/<?php print $user->getId(); ?>', 'User', true);">Delete User</a></li>
<li><a href="<?php echo PHPCI_URL ?>user/delete/<?php print $user->getId(); ?>" class="phpci-app-delete-user">Delete User</a></li>
</ul>
</div>
<?php endif; ?>

View file

@ -17,4 +17,4 @@
<?php print $form; ?>
</div>
</div>
</div>
</div>

View file

@ -7,21 +7,22 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href='//fonts.googleapis.com/css?family=Roboto:300,500&subset=latin,latin-ext' rel='stylesheet' type='text/css'>
<link rel="stylesheet" type="text/css" href="<?= PHPCI_URL ?>assets/css/bootstrap.min.css">
<link rel="stylesheet" type="text/css" href="<?= PHPCI_URL ?>assets/css/phpci.css">
<link rel="stylesheet" type="text/css" href="<?php echo PHPCI_URL ?>assets/css/bootstrap.min.css">
<link rel="stylesheet" type="text/css" href="<?php echo PHPCI_URL ?>assets/css/phpci.css">
<link rel="shortcut icon" type="image/x-icon" href="<?= PHPCI_URL ?>favicon.ico">
<link rel="shortcut icon" type="image/png" href="<?= PHPCI_URL ?>assets/img/favicon.png">
<link rel="shortcut icon" type="image/x-icon" href="<?php echo PHPCI_URL ?>favicon.ico">
<link rel="shortcut icon" type="image/png" href="<?php echo PHPCI_URL ?>assets/img/favicon.png">
<script>window.PHPCI_URL = <?php print json_encode(PHPCI_URL) ?></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script type="text/javascript" src="https://www.google.com/jsapi"></script>
<script src="<?= PHPCI_URL ?>assets/js/bootstrap.min.js"></script>
<script src="<?= PHPCI_URL ?>assets/js/jqueryui.js"></script>
<script src="<?= PHPCI_URL ?>assets/js/class.js"></script>
<script src="<?= PHPCI_URL ?>assets/js/phpci.js"></script>
<script src="<?php echo PHPCI_URL ?>assets/js/bootstrap.min.js"></script>
<script src="<?php echo PHPCI_URL ?>assets/js/jqueryui.js"></script>
<script src="<?php echo PHPCI_URL ?>assets/js/class.js"></script>
<script src="<?php echo PHPCI_URL ?>assets/js/phpci.js"></script>
<script src="<?php echo PHPCI_URL ?>assets/js/init.js"></script>
</head>
<body>
<div class="navbar navbar-fixed-top">
@ -32,7 +33,7 @@
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="<?= PHPCI_URL ?>"><img src="<?= PHPCI_URL ?>/assets/img/logo.png"></a>
<a class="navbar-brand" href="<?php echo PHPCI_URL ?>"><img src="<?php echo PHPCI_URL ?>/assets/img/logo.png"></a>
<div class="nav-collapse collapse navbar-responsive-collapse">
<ul class="nav navbar-nav pull-right">
@ -41,15 +42,15 @@
<?php if ($this->User()->getIsAdmin()): ?>
<li>
<div class="btn-group">
<a class="btn btn-success navbar-btn" href="<?= PHPCI_URL ?>project/add">Add Project</a>
<a class="btn btn-success navbar-btn" href="<?php echo PHPCI_URL ?>project/add">Add Project</a>
<button type="button" class="btn navbar-btn btn-default dropdown-toggle" data-toggle="dropdown">
Admin <span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu">
<li><a href="/settings">Settings</a></li>
<li><a href="/plugin">Manage Plugins</a></li>
<li><a href="/user">Manage Users</a></li>
<li><a href="<?php echo PHPCI_URL ?>settings">Settings</a></li>
<li><a href="<?php echo PHPCI_URL ?>plugin">Manage Plugins</a></li>
<li><a href="<?php echo PHPCI_URL ?>user">Manage Users</a></li>
</ul>
</div>
</li>

View file

@ -0,0 +1,49 @@
<?php
namespace PHPCI\Plugin\Tests\Helper;
use PHPCI\Helper\BuildInterpolator;
use Prophecy\PhpUnit\ProphecyTestCase;
class BuildInterpolatorTest extends ProphecyTestCase
{
/**
* @var BuildInterpolator
*/
protected $testedInterpolator;
protected function setUp()
{
parent::setup();
$this->testedInterpolator = new BuildInterpolator();
}
public function testInterpolate_LeavesStringsUnchangedByDefault()
{
$string = "Hello World";
$expectedOutput = "Hello World";
$actualOutput = $this->testedInterpolator->interpolate($string);
$this->assertEquals($expectedOutput, $actualOutput);
}
public function testInterpolate_LeavesStringsUnchangedWhenBuildIsSet()
{
$build = $this->prophesize('PHPCI\\Model\\Build')->reveal();
$string = "Hello World";
$expectedOutput = "Hello World";
$this->testedInterpolator->setupInterpolationVars(
$build,
"/buildpath/",
"phpci.com"
);
$actualOutput = $this->testedInterpolator->interpolate($string);
$this->assertEquals($expectedOutput, $actualOutput);
}
}

View file

@ -15,7 +15,7 @@ class CommandExecutorTest extends ProphecyTestCase
protected function setUp()
{
parent::setUp();
$mockBuildLogger = $this->prophesize('\PHPCI\BuildLogger');
$mockBuildLogger = $this->prophesize('PHPCI\Logging\BuildLogger');
$this->testedExecutor = new CommandExecutor($mockBuildLogger->reveal(), __DIR__ . "/");
}

View file

@ -0,0 +1,107 @@
<?php
namespace PHPCI\Plugin\Tests\Helper;
use PHPCI\Logging\BuildLogger;
use Prophecy\Argument;
use Prophecy\PhpUnit\ProphecyTestCase;
use Psr\Log\LogLevel;
class BuildLoggerTest extends ProphecyTestCase
{
/**
* @var BuildLogger
*/
protected $testedBuildLogger;
protected $mockLogger;
protected $mockBuild;
protected function setUp()
{
parent::setUp();
$this->mockLogger = $this->prophesize('\Psr\Log\LoggerInterface');
$this->mockBuild = $this->prophesize('\PHPCI\Model\Build');
$this->testedBuildLogger = new BuildLogger(
$this->mockLogger->reveal(),
$this->mockBuild->reveal()
);
}
public function testLog_CallsWrappedLogger()
{
$level = LogLevel::NOTICE;
$message = "Testing";
$contextIn = array();
$this->mockLogger->log($level, $message, Argument::type('array'))
->shouldBeCalledTimes(1);
$this->testedBuildLogger->log($message, $level, $contextIn);
}
public function testLog_CallsWrappedLoggerForEachMessage()
{
$level = LogLevel::NOTICE;
$message = array("One", "Two", "Three");
$contextIn = array();
$this->mockLogger->log($level, "One", Argument::type('array'))
->shouldBeCalledTimes(1);
$this->mockLogger->log($level, "Two", Argument::type('array'))
->shouldBeCalledTimes(1);
$this->mockLogger->log($level, "Three", Argument::type('array'))
->shouldBeCalledTimes(1);
$this->testedBuildLogger->log($message, $level, $contextIn);
}
public function testLog_AddsBuildToContext()
{
$level = LogLevel::NOTICE;
$message = "Testing";
$contextIn = array();
$expectedContext = array(
'build' => $this->mockBuild->reveal()
);
$this->mockLogger->log($level, $message, $expectedContext)
->shouldBeCalledTimes(1);
$this->testedBuildLogger->log($message, $level, $contextIn);
}
public function testLogFailure_LogsAsErrorLevel()
{
$message = "Testing";
$expectedLevel = LogLevel::ERROR;
$this->mockLogger->log($expectedLevel,
Argument::type('string'),
Argument::type('array'))
->shouldBeCalledTimes(1);
$this->testedBuildLogger->logFailure($message);
}
public function testLogFailure_AddsExceptionContext()
{
$message = "Testing";
$exception = new \Exception("Expected Exception");
$this->mockLogger->log(Argument::type('string'),
Argument::type('string'),
Argument::withEntry('exception', $exception))
->shouldBeCalledTimes(1);
$this->testedBuildLogger->logFailure($message, $exception);
}
}

View file

@ -1,8 +1,10 @@
<?php
use \PHPCI\Helper\LoggerConfig;
namespace PHPCI\Plugin\Tests\Helper;
class LoggerConfigTest extends PHPUnit_Framework_TestCase
use \PHPCI\Logging\LoggerConfig;
class LoggerConfigTest extends \PHPUnit_Framework_TestCase
{
public function testGetFor_ReturnsPSRLogger()
{
@ -20,9 +22,9 @@ class LoggerConfigTest extends PHPUnit_Framework_TestCase
public function testGetFor_AttachesAlwaysPresentHandlers()
{
$expectedHandler = new Monolog\Handler\NullHandler();
$expectedHandler = new \Monolog\Handler\NullHandler();
$config = new LoggerConfig(array(
LoggerConfig::KEY_AlwaysLoaded => function() use ($expectedHandler) {
LoggerConfig::KEY_ALWAYS_LOADED => function() use ($expectedHandler) {
return array($expectedHandler);
}
));
@ -36,7 +38,7 @@ class LoggerConfigTest extends PHPUnit_Framework_TestCase
public function testGetFor_AttachesSpecificHandlers()
{
$expectedHandler = new Monolog\Handler\NullHandler();
$expectedHandler = new \Monolog\Handler\NullHandler();
$config = new LoggerConfig(array(
"Specific" => function() use ($expectedHandler) {
return array($expectedHandler);
@ -52,8 +54,8 @@ class LoggerConfigTest extends PHPUnit_Framework_TestCase
public function testGetFor_IgnoresAlternativeHandlers()
{
$expectedHandler = new Monolog\Handler\NullHandler();
$alternativeHandler = new Monolog\Handler\NullHandler();
$expectedHandler = new \Monolog\Handler\NullHandler();
$alternativeHandler = new \Monolog\Handler\NullHandler();
$config = new LoggerConfig(array(
"Specific" => function() use ($expectedHandler) {

View file

@ -162,7 +162,7 @@ class EmailTest extends \PHPUnit_Framework_TestCase
{
$this->mockMailer->expects($this->once())
->method('send');
$this->testedEmailPlugin->sendEmail("test@email.com", "hello", "body");
$this->testedEmailPlugin->sendEmail("test@email.com", array(), "hello", "body");
}
/**
@ -177,7 +177,7 @@ class EmailTest extends \PHPUnit_Framework_TestCase
$this->mockMailer->expects($this->once())
->method('send')
->with($this->isInstanceOf('\Swift_Message'), $this->anything());
$this->testedEmailPlugin->sendEmail($toAddress, $subject, $body);
$this->testedEmailPlugin->sendEmail($toAddress, array(), $subject, $body);
}
/**
@ -197,7 +197,7 @@ class EmailTest extends \PHPUnit_Framework_TestCase
$actualMail = null;
$this->catchMailPassedToSend($actualMail);
$this->testedEmailPlugin->sendEmail($toAddress, $subject, $body);
$this->testedEmailPlugin->sendEmail($toAddress, array(), $subject, $body);
$this->assertSystemMail(
$toAddress,

View file

@ -0,0 +1,51 @@
<?php
namespace PHPCI\Plugin\Tests\Util;
use PHPCI\Plugin\Util\ComposerPluginInformation;
class ComposerPluginInformationTest extends \PHPUnit_Framework_TestCase
{
/**
* @var ComposerPluginInformation
*/
protected $testedInformation;
protected function setUpFromFile($file)
{
$this->testedInformation = ComposerPluginInformation::buildFromYaml($file);
}
protected function phpciSetup()
{
$this->setUpFromFile(
__DIR__ . "/../../../../vendor/composer/installed.json"
);
}
public function testBuildFromYaml_ReturnsInstance()
{
$this->phpciSetup();
$this->assertInstanceOf(
'\PHPCI\Plugin\Util\ComposerPluginInformation',
$this->testedInformation
);
}
public function testGetInstalledPlugins_ReturnsStdClassArray()
{
$this->phpciSetup();
$plugins = $this->testedInformation->getInstalledPlugins();
$this->assertInternalType("array", $plugins);
$this->assertContainsOnly("stdClass", $plugins);
}
public function testGetPluginClasses_ReturnsStringArray()
{
$this->phpciSetup();
$classes = $this->testedInformation->getPluginClasses();
$this->assertInternalType("array", $classes);
$this->assertContainsOnly("string", $classes);
}
}

View file

@ -22,7 +22,7 @@ class ExecutorTest extends ProphecyTestCase
protected function setUp()
{
parent::setUp();
$this->mockBuildLogger = $this->prophesize('\PHPCI\BuildLogger');
$this->mockBuildLogger = $this->prophesize('\PHPCI\Logging\BuildLogger');
$this->mockFactory = $this->prophesize('\PHPCI\Plugin\Util\Factory');
$this->testedExecutor = new Executor($this->mockFactory->reveal(), $this->mockBuildLogger->reveal());
}

View file

@ -0,0 +1,26 @@
<?php
namespace PHPCI\Plugin\Tests\Util;
use PHPCI\Plugin\Util\FilesPluginInformation;
class FilesPluginInformationTest extends \PHPUnit_Framework_TestCase
{
public function testGetInstalledPlugins_returnsObjectes()
{
$pluginDirPath = realpath(__DIR__ . "/../../../../PHPCI/Plugin/");
$test = FilesPluginInformation::newFromDir($pluginDirPath);
$pluginInfos = $test->getInstalledPlugins();
$this->assertContainsOnlyInstancesOf('stdClass', $pluginInfos);
}
public function testGetPluginClasses_returnsStrings()
{
$pluginDirPath = realpath(__DIR__ . "/../../../../PHPCI/Plugin/");
$test = FilesPluginInformation::newFromDir($pluginDirPath);
$pluginInfos = $test->getPluginClasses();
$this->assertContainsOnly('string', $pluginInfos);
}
}

44
Tests/bootstrap.php Normal file
View file

@ -0,0 +1,44 @@
<?php
/**
* PHPCI - Continuous Integration for PHP
*
* @copyright Copyright 2013, Block 8 Limited.
* @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md
* @link http://www.phptesting.org/
*/
// Let PHP take a guess as to the default timezone, if the user hasn't set one:
date_default_timezone_set(@date_default_timezone_get());
// Set up a basic autoloader for PHPCI:
$autoload = function ($class) {
$file = str_replace(array('\\', '_'), '/', $class);
$file .= '.php';
if (substr($file, 0, 1) == '/') {
$file = substr($file, 1);
}
if (is_file(dirname(__DIR__) . '/' . $file)) {
include(dirname(__DIR__) . '/' . $file);
return;
}
};
spl_autoload_register($autoload, true, true);
// Load Composer autoloader:
require_once(dirname(__DIR__) . '/vendor/autoload.php');
// Load configuration if present:
$conf = array();
$conf['b8']['app']['namespace'] = 'PHPCI';
$conf['b8']['app']['default_controller'] = 'Home';
$conf['b8']['view']['path'] = dirname(__DIR__) . '/PHPCI/View/';
if (file_exists(dirname(__DIR__) . '/PHPCI/config.yml')) {
$config = new b8\Config($conf);
$config->loadYaml(dirname(__DIR__) . '/PHPCI/config.yml');
}
require_once(dirname(__DIR__) . '/vars.php');

View file

@ -34,11 +34,11 @@
},
"require-dev": {
"phpunit/phpunit": "3.7.*",
"phpspec/prophecy-phpunit": "1.*"
},
"suggest": {
"phpunit/phpunit": "PHP unit testing framework",
"phpmd/phpmd": "PHP Mess Detector",
"sebastian/phpcpd": "PHP Copy/Paste Detector",
"squizlabs/php_codesniffer": "PHP Code Sniffer",

64
composer.lock generated
View file

@ -7,16 +7,16 @@
"packages": [
{
"name": "block8/b8framework",
"version": "1.0.1",
"version": "1.1.0",
"source": {
"type": "git",
"url": "https://github.com/Block8/b8framework.git",
"reference": "0497ae34ba7ef828db23b35a1f75d4debfa7eed5"
"reference": "f643e0d3497599016cb62611ceb9288710423121"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Block8/b8framework/zipball/0497ae34ba7ef828db23b35a1f75d4debfa7eed5",
"reference": "0497ae34ba7ef828db23b35a1f75d4debfa7eed5",
"url": "https://api.github.com/repos/Block8/b8framework/zipball/f643e0d3497599016cb62611ceb9288710423121",
"reference": "f643e0d3497599016cb62611ceb9288710423121",
"shasum": ""
},
"require": {
@ -50,7 +50,7 @@
"mvc",
"php"
],
"time": "2013-10-09 14:06:12"
"time": "2014-02-24 11:25:23"
},
{
"name": "ircmaxell/password-compat",
@ -155,16 +155,16 @@
},
{
"name": "pimple/pimple",
"version": "v1.1.0",
"version": "v1.1.1",
"source": {
"type": "git",
"url": "https://github.com/fabpot/Pimple.git",
"reference": "471c7d7c52ad6594e17b8ec33efdd1be592b5d83"
"reference": "2019c145fe393923f3441b23f29bbdfaa5c58c4d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/fabpot/Pimple/zipball/471c7d7c52ad6594e17b8ec33efdd1be592b5d83",
"reference": "471c7d7c52ad6594e17b8ec33efdd1be592b5d83",
"url": "https://api.github.com/repos/fabpot/Pimple/zipball/2019c145fe393923f3441b23f29bbdfaa5c58c4d",
"reference": "2019c145fe393923f3441b23f29bbdfaa5c58c4d",
"shasum": ""
},
"require": {
@ -188,7 +188,9 @@
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
"email": "fabien@symfony.com",
"homepage": "http://fabien.potencier.org",
"role": "Lead Developer"
}
],
"description": "Pimple is a simple Dependency Injection Container for PHP 5.3",
@ -197,7 +199,7 @@
"container",
"dependency injection"
],
"time": "2013-09-19 04:53:08"
"time": "2013-11-22 08:30:29"
},
{
"name": "psr/log",
@ -288,17 +290,17 @@
},
{
"name": "symfony/console",
"version": "v2.4.0",
"version": "v2.4.2",
"target-dir": "Symfony/Component/Console",
"source": {
"type": "git",
"url": "https://github.com/symfony/Console.git",
"reference": "3c1496ae96d24ccc6c340fcc25f71d7a1ab4c12c"
"reference": "940f217cbc3c8a33e5403e7c595495c4884400fe"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/Console/zipball/3c1496ae96d24ccc6c340fcc25f71d7a1ab4c12c",
"reference": "3c1496ae96d24ccc6c340fcc25f71d7a1ab4c12c",
"url": "https://api.github.com/repos/symfony/Console/zipball/940f217cbc3c8a33e5403e7c595495c4884400fe",
"reference": "940f217cbc3c8a33e5403e7c595495c4884400fe",
"shasum": ""
},
"require": {
@ -328,7 +330,9 @@
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
"email": "fabien@symfony.com",
"homepage": "http://fabien.potencier.org",
"role": "Lead Developer"
},
{
"name": "Symfony Community",
@ -337,21 +341,21 @@
],
"description": "Symfony Console Component",
"homepage": "http://symfony.com",
"time": "2013-11-27 09:10:40"
"time": "2014-02-11 13:52:09"
},
{
"name": "symfony/yaml",
"version": "v2.4.0",
"version": "v2.4.2",
"target-dir": "Symfony/Component/Yaml",
"source": {
"type": "git",
"url": "https://github.com/symfony/Yaml.git",
"reference": "1ae235a1b9d3ad3d9f3860ff20acc072df95b7f5"
"reference": "bb6ddaf8956139d1b8c360b4b713ed0138e876b3"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/Yaml/zipball/1ae235a1b9d3ad3d9f3860ff20acc072df95b7f5",
"reference": "1ae235a1b9d3ad3d9f3860ff20acc072df95b7f5",
"url": "https://api.github.com/repos/symfony/Yaml/zipball/bb6ddaf8956139d1b8c360b4b713ed0138e876b3",
"reference": "bb6ddaf8956139d1b8c360b4b713ed0138e876b3",
"shasum": ""
},
"require": {
@ -375,7 +379,9 @@
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
"email": "fabien@symfony.com",
"homepage": "http://fabien.potencier.org",
"role": "Lead Developer"
},
{
"name": "Symfony Community",
@ -384,22 +390,22 @@
],
"description": "Symfony Yaml Component",
"homepage": "http://symfony.com",
"time": "2013-11-26 16:40:27"
"time": "2014-01-07 13:28:54"
}
],
"packages-dev": [
{
"name": "phpspec/prophecy",
"version": "v1.0.4",
"version": "1.1.2",
"source": {
"type": "git",
"url": "https://github.com/phpspec/prophecy.git",
"reference": "79d9c8bd94801bffbf9b56964f6438762da6d8cd"
"reference": "976a65af02a2a0e17ce6c949f7b43437205628bb"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpspec/prophecy/zipball/79d9c8bd94801bffbf9b56964f6438762da6d8cd",
"reference": "79d9c8bd94801bffbf9b56964f6438762da6d8cd",
"url": "https://api.github.com/repos/phpspec/prophecy/zipball/976a65af02a2a0e17ce6c949f7b43437205628bb",
"reference": "976a65af02a2a0e17ce6c949f7b43437205628bb",
"shasum": ""
},
"require-dev": {
@ -408,7 +414,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
"dev-master": "1.1.x-dev"
}
},
"autoload": {
@ -441,7 +447,7 @@
"spy",
"stub"
],
"time": "2013-08-10 11:11:45"
"time": "2014-01-24 11:03:43"
},
{
"name": "phpspec/prophecy-phpunit",

4
console Normal file → Executable file
View file

@ -18,9 +18,10 @@ use PHPCI\Command\UpdateCommand;
use PHPCI\Command\InstallCommand;
use PHPCI\Command\DaemonCommand;
use PHPCI\Command\PollCommand;
use PHPCI\Command\CreateAdminCommand;
use Symfony\Component\Console\Application;
$loggerConfig = \PHPCI\Helper\LoggerConfig::newFromFile(__DIR__ . "/loggerconfig.php");
$loggerConfig = \PHPCI\Logging\LoggerConfig::newFromFile(__DIR__ . "/loggerconfig.php");
$application = new Application();
@ -30,5 +31,6 @@ $application->add(new UpdateCommand($loggerConfig->getFor('UpdateCommand')));
$application->add(new GenerateCommand);
$application->add(new DaemonCommand($loggerConfig->getFor('DaemonCommand')));
$application->add(new PollCommand($loggerConfig->getFor('PollCommand')));
$application->add(new CreateAdminCommand);
$application->run();

View file

@ -15,6 +15,8 @@ require('bootstrap.php');
use PHPCI\Command\DaemoniseCommand;
use Symfony\Component\Console\Application;
$loggerConfig = \PHPCI\Logging\LoggerConfig::newFromFile(__DIR__ . "/loggerconfig.php");
$application = new Application();
$application->add(new DaemoniseCommand);
$application->add(new DaemoniseCommand($loggerConfig->getFor('DaemoniseCommand')));
$application->run();

View file

@ -5,11 +5,6 @@ build_settings:
- "Tests"
- "PHPCI/Command" # PHPMD complains about un-used parameters, but they are required.
- "public/install.php" # PHPCS really doesn't like PHP mixed with HTML (and so it shouldn't)
irc:
server: "irc.freenode.net"
port: 6667
room: "#phpci"
nick: "phpcidev"
test:
php_mess_detector:
@ -17,10 +12,7 @@ test:
standard: "PSR2"
php_loc:
success:
irc:
message: "Build Success. %PROJECT_TITLE% - %COMMIT% - %BUILD_URI%"
failure:
irc:
message: "Build Failed. %PROJECT_TITLE% - %COMMIT% - %BUILD_URI%"
email:
committer: true
cc: ["php-ci@googlegroups.com"]

24
phpunit.xml Normal file
View file

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit backupGlobals="false"
backupStaticAttributes="false"
convertErrorsToExceptions="true"
convertNoticesToExceptions="true"
convertWarningsToExceptions="true"
processIsolation="false"
stopOnFailure="false"
syntaxCheck="false"
bootstrap="./Tests/bootstrap.php"
>
<testsuites>
<testsuite name="PHPCI Helper Test Suite">
<directory suffix="Test.php">./Tests/PHPCI/Helper</directory>
</testsuite>
<testsuite name="PHPCI Logging Test Suite">
<directory suffix="Test.php">./Tests/PHPCI/Logging</directory>
</testsuite>
<testsuite name="PHPCI Plugin Test Suite">
<directory suffix="Test.php">./Tests/PHPCI/Plugin</directory>
</testsuite>
</testsuites>
</phpunit>

36
public/assets/js/init.js Normal file
View file

@ -0,0 +1,36 @@
/**
* @file init.js
* Initialization of frontend of the application goes here
*
* @author Pavel Pavlov <Pavel.Pavlov@alera.ru>
* @date 12/31/13
* @time 3:44 AM
* @license LICENSE.md
*
* @package PHPCI
*/
$(function () {
$('#latest-builds').on('latest-builds:reload', bindAppDeleteEvents);
$('#latest-builds').trigger('latest-builds:reload');
});
function bindAppDeleteEvents () {
$('.phpci-app-delete-build').on('click', function (e) {
e.preventDefault();
confirmDelete(e.target.href, 'Build').onClose = function () {
refreshBuildsTable();
};
return false;
});
$('.phpci-app-delete-user').on('click', function (e) {
e.preventDefault();
confirmDelete(e.target.href, 'User', true);
return false;
});
}

View file

@ -81,6 +81,11 @@ if ($installOK && strtoupper($_SERVER['REQUEST_METHOD']) == 'POST') {
*/
require_once(PHPCI_DIR . 'vendor/autoload.php');
/**
* Temporary save phpci URL for redirect after install ($config is replaced in bootstrap.php)
*/
$phpciUrl = $config['phpci']['url'];
/**
* Write config file:
*/
@ -101,8 +106,8 @@ if ($installOK && strtoupper($_SERVER['REQUEST_METHOD']) == 'POST') {
* Create database:
*/
$dbhost = $config['b8']['database']['servers']['write'][0];
$dbname = $config['b8']['database']['name'];
$dbuser = $config['b8']['database']['username'];
$dbname = $config['b8']['database']['name'] ?: 'phpci';
$dbuser = $config['b8']['database']['username'] ?: 'phpci';
$dbpass = $config['b8']['database']['password'];
$pdo = new PDO('mysql:host=' . $dbhost, $dbuser, $dbpass);
@ -139,7 +144,7 @@ if ($installOK && strtoupper($_SERVER['REQUEST_METHOD']) == 'POST') {
$store->save($user);
}
$formAction = '/session/login';
$formAction = rtrim( $phpciUrl, '/' ) . '/session/login';
}
}