Merge branch 'master' into master

This commit is contained in:
icyfire 2016-06-13 17:45:15 +08:00 committed by GitHub
commit 61d09ad485
968 changed files with 167709 additions and 20538 deletions

41
.github/CONTRIBUTING.md vendored Normal file
View file

@ -0,0 +1,41 @@
Contributions to PHPCI are very much encouraged, and we do our best to make it as welcoming and simple as possible.
### Before You Start
Before you start, please make sure that you are aware of, and agree to, the following conditions of contribution:
* By making a contribution to PHPCI, you accept that you are granting copyright ownership for that contribution to Block 8 Limited - the company responsible for PHPCI. In countries where copyright reassignment is not permitted, you grant Block 8 Limited a perpetual, non-exclusive licence to use your contribution in any way and for any purpose.
* By making a contribution to PHPCI, you accept that your code will be released under the open-source [BSD 2-Clause Licence](https://github.com/Block8/PHPCI/blob/master/LICENSE.md).
Block 8 are committed to PHPCI being a truly free and open source project, providing easy to use continuous integration and testing to as many developers as possible. We may, at our sole discretion, provide paid services based upon PHPCI - but PHPCI will always remain free (as in cost, and freedom) and open source.
### The Contribution Process
1. If you are thinking of making a large change or feature addition, [open an issue](/Block8/PHPCI/issues) titled "Intent to implement: <Your Feature>". Describe your idea in detail and discuss it with the community. It might be that someone already has a plan, could help you out, or your idea may simply not be suitable for the project at this time.
2. Fork the PHPCI project on Github
3. Add a feature or fix a bug - We recommend that you do this on a branch within your repository.
4. Create a pull request containing just the one change you want to contribute back to PHPCI. If you have more than one feature or bug fix, please create separate branches within your repository, and then submit a separate pull request for each one. Your pull request should use the template detailed below.
5. We'll then review your pull request and give any necessary feedback, this could be:
* Suggestions to improve your implementation
* Questions
* Issues/bugs related to the change
* Coding standards pointers
6. Once everyone is happy with the submission, we'll merge it back into PHPCI. Your change will then be included in the next project release.
### Not sure what to start with?
We maintain two labels within our issue tracker that may be of interest to new contributors:
* [The "Easy Fix" List](https://github.com/Block8/PHPCI/labels/flag:easy-fix)
* [The "Priority" List](https://github.com/Block8/PHPCI/labels/flag:priority)
### Coding Standards
We require that all contributions meet at least the following guidelines:
* PSR-1 & PSR-2 compliance for all code
* Doc-blocks for all classes and methods
* All files must contain the standard file-level docblock, including the copyright, license and link tags.
All pull requests will be checked against these standards. If you're modifying a file as part of your change which does not comply with the above, please make the necessary changes to bring it into compliance prior to submitting the pull request.
### Other Requirements
When you're adding new features or functionality, or you're updating existing functionality, please ensure that the relevant documentation is also either created or updated on the Wiki.

28
.github/ISSUE_TEMPLATE.md vendored Normal file
View file

@ -0,0 +1,28 @@
Before submitting your issue, please make sure that you've checked all of the checkboxes below.
- [ ] You're running the [latest release](https://github.com/Block8/PHPCI/releases/latest) version of PHPCI.
- [ ] Ensure that you're running at least PHP 5.3.6, you can check this by running `php -v`
- [ ] You've run `composer install --no-dev -o` from the root of your installation.
- [ ] You have set up either the PHPCI [Worker](https://github.com/Block8/PHPCI/wiki/Run-Builds-Using-a-Worker), [Daemon](https://github.com/Block8/PHPCI/wiki/Run-Builds-Using-a-Daemon) or [Cron Job](https://github.com/Block8/PHPCI/wiki/Run-Builds-Using-Cron) to run builds.
To help us better understand your issue, please answer the following.
### Expected behaviour
*Please describe what you're expecting to see happen.*
### Actual behaviour
*Please describe what you're actually seeing happen.*
### Steps to reproduce
*If your issue requires any specific steps to reproduce, please outline them here.*
### Environment info
Operating System:
PHP Version:
MySQL Version:
### Logs or other output that would be helpful
(If logs are large, please upload as attachment).

23
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View file

@ -0,0 +1,23 @@
Contribution Type: bug fix | new plugin | new feature | refactor | cosmetic
Link to Intent to Implement:
Link to Bug:
This pull request affects the following areas:
* [ ] Front-End
* [ ] Builder
* [ ] Build Plugins
**In raising this pull request, I confirm the following (please check boxes):**
- [ ] I have read and understood the [contributing guidelines](/.github/CONTRIBUTING.md)?
- [ ] I have checked that another pull request for this purpose does not exist.
- [ ] I have considered, and confirmed that this submission will be valuable to others.
- [ ] I have created or updated the relevant documentation for this change on the PHPCI Wiki.
- [ ] Do the PHPCI tests pass?
Detailed description of change:

View file

@ -8,11 +8,11 @@ build_settings:
- "PHPCI/Migrations" # Ignore the migrations directory, as both PHPMD and PHPCS can't cope with them. - "PHPCI/Migrations" # Ignore the migrations directory, as both PHPMD and PHPCS can't cope with them.
- "PHPCI/Model/Base" # These files are auto-generated, and sometimes hit PHPMD complexity thresholds. - "PHPCI/Model/Base" # These files are auto-generated, and sometimes hit PHPMD complexity thresholds.
- "PHPCI/Languages" # PHPCS fails on character counts for non-Latin languages - "PHPCI/Languages" # PHPCS fails on character counts for non-Latin languages
- "public/assets" # If there are any PHP files in here, we didn't write them.
setup: setup:
composer: composer:
action: "install" action: "install"
prefer_dist: false
test: test:
php_parallel_lint: php_parallel_lint:
@ -32,7 +32,7 @@ test:
php_docblock_checker: php_docblock_checker:
allowed_warnings: 0 allowed_warnings: 0
failure: broken:
email: email:
committer: true committer: true
cc: ["php-ci@googlegroups.com"] cc: ["php-ci@googlegroups.com"]

View file

@ -135,15 +135,18 @@ class Application extends b8\Application
*/ */
protected function setLayoutVariables(View &$layout) protected function setLayoutVariables(View &$layout)
{ {
/** @var \PHPCI\Store\ProjectStore $projectStore */ $groups = array();
$projectStore = b8\Store\Factory::getStore('Project'); $groupStore = b8\Store\Factory::getStore('ProjectGroup');
$layout->projects = $projectStore->getWhere( $groupList = $groupStore->getWhere(array(), 100, 0, array(), array('title' => 'ASC'));
array('archived' => (int)isset($_GET['archived'])),
50, foreach ($groupList['items'] as $group) {
0, $thisGroup = array('title' => $group->getTitle());
array(), $projects = b8\Store\Factory::getStore('Project')->getByGroupId($group->getId());
array('title' => 'ASC') $thisGroup['projects'] = $projects['items'];
); $groups[] = $thisGroup;
}
$layout->groups = $groups;
} }
/** /**

View file

@ -36,38 +36,44 @@ class BuildFactory
/** /**
* Takes a generic build and returns a type-specific build model. * Takes a generic build and returns a type-specific build model.
* @param Build $base The build from which to get a more specific build type. * @param Build $build The build from which to get a more specific build type.
* @return Build * @return Build
*/ */
public static function getBuild(Build $base) public static function getBuild(Build $build)
{ {
switch($base->getProject()->getType()) $project = $build->getProject();
{
case 'remote': if (!empty($project)) {
$type = 'RemoteGitBuild'; switch ($project->getType()) {
break; case 'remote':
case 'local': $type = 'RemoteGitBuild';
$type = 'LocalBuild'; break;
break; case 'local':
case 'github': $type = 'LocalBuild';
$type = 'GithubBuild'; break;
break; case 'github':
case 'bitbucket': $type = 'GithubBuild';
$type = 'BitbucketBuild'; break;
break; case 'bitbucket':
case 'gitlab': $type = 'BitbucketBuild';
$type = 'GitlabBuild'; break;
break; case 'gitlab':
case 'hg': $type = 'GitlabBuild';
$type = 'MercurialBuild'; break;
break; case 'hg':
case 'svn': $type = 'MercurialBuild';
$type = 'SubversionBuild'; break;
break; case 'svn':
$type = 'SubversionBuild';
break;
default:
return $build;
}
$class = '\\PHPCI\\Model\\Build\\' . $type;
$build = new $class($build->getDataArray());
} }
$type = '\\PHPCI\\Model\\Build\\' . $type; return $build;
return new $type($base->getDataArray());
} }
} }

View file

@ -188,6 +188,14 @@ class Builder implements LoggerAwareInterface
$this->build->sendStatusPostback(); $this->build->sendStatusPostback();
$success = true; $success = true;
$previous_build = $this->build->getProject()->getPreviousBuild($this->build->getBranch());
$previous_state = Build::STATUS_NEW;
if ($previous_build) {
$previous_state = $previous_build->getStatus();
}
try { try {
// Set up the build: // Set up the build:
$this->setupBuild(); $this->setupBuild();
@ -205,19 +213,30 @@ class Builder implements LoggerAwareInterface
$this->build->setStatus(Build::STATUS_FAILED); $this->build->setStatus(Build::STATUS_FAILED);
} }
// Complete stage plugins are always run
$this->pluginExecutor->executePlugins($this->config, 'complete');
if ($success) { if ($success) {
$this->pluginExecutor->executePlugins($this->config, 'success'); $this->pluginExecutor->executePlugins($this->config, 'success');
if ($previous_state == Build::STATUS_FAILED) {
$this->pluginExecutor->executePlugins($this->config, 'fixed');
}
$this->buildLogger->logSuccess(Lang::get('build_success')); $this->buildLogger->logSuccess(Lang::get('build_success'));
} else { } else {
$this->pluginExecutor->executePlugins($this->config, 'failure'); $this->pluginExecutor->executePlugins($this->config, 'failure');
if ($previous_state == Build::STATUS_SUCCESS || $previous_state == Build::STATUS_NEW) {
$this->pluginExecutor->executePlugins($this->config, 'broken');
}
$this->buildLogger->logFailure(Lang::get('build_failed')); $this->buildLogger->logFailure(Lang::get('build_failed'));
} }
} catch (\Exception $ex) { } catch (\Exception $ex) {
$this->build->setStatus(Build::STATUS_FAILED); $this->build->setStatus(Build::STATUS_FAILED);
$this->buildLogger->logFailure(Lang::get('exception') . $ex->getMessage()); $this->buildLogger->logFailure(Lang::get('exception') . $ex->getMessage());
}finally{
// Complete stage plugins are always run
$this->pluginExecutor->executePlugins($this->config, 'complete');
} }
@ -285,8 +304,7 @@ class Builder implements LoggerAwareInterface
*/ */
protected function setupBuild() protected function setupBuild()
{ {
$this->buildPath = $this->build->getBuildPath() . '/'; $this->buildPath = $this->build->getBuildPath();
$this->build->currentBuildPath = $this->buildPath;
$this->interpolator->setupInterpolationVars( $this->interpolator->setupInterpolationVars(
$this->build, $this->build,

View file

@ -16,6 +16,7 @@ use b8\Config;
use b8\Store\Factory; use b8\Store\Factory;
use PHPCI\Helper\Lang; use PHPCI\Helper\Lang;
use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Helper\DialogHelper;
use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
@ -46,6 +47,9 @@ class InstallCommand extends Command
->addOption('admin-pass', null, InputOption::VALUE_OPTIONAL, Lang::get('admin_pass')) ->addOption('admin-pass', null, InputOption::VALUE_OPTIONAL, Lang::get('admin_pass'))
->addOption('admin-mail', null, InputOption::VALUE_OPTIONAL, Lang::get('admin_email')) ->addOption('admin-mail', null, InputOption::VALUE_OPTIONAL, Lang::get('admin_email'))
->addOption('config-path', null, InputOption::VALUE_OPTIONAL, Lang::get('config_path'), $defaultPath) ->addOption('config-path', null, InputOption::VALUE_OPTIONAL, Lang::get('config_path'), $defaultPath)
->addOption('queue-disabled', null, InputOption::VALUE_NONE, 'Don\'t ask for queue details')
->addOption('queue-server', null, InputOption::VALUE_OPTIONAL, 'Beanstalkd queue server hostname')
->addOption('queue-name', null, InputOption::VALUE_OPTIONAL, 'Beanstalkd queue name')
->setDescription(Lang::get('install_phpci')); ->setDescription(Lang::get('install_phpci'));
} }
@ -229,10 +233,37 @@ class InstallCommand extends Command
} }
$phpci['url'] = $url; $phpci['url'] = $url;
$phpci['worker'] = $this->getQueueInformation($input, $output, $dialog);
return $phpci; return $phpci;
} }
/**
* If the user wants to use a queue, get the necessary details.
* @param InputInterface $input
* @param OutputInterface $output
* @param DialogHelper $dialog
* @return array
*/
protected function getQueueInformation(InputInterface $input, OutputInterface $output, DialogHelper $dialog)
{
if ($input->getOption('queue-disabled')) {
return null;
}
$rtn = [];
if (!$rtn['host'] = $input->getOption('queue-server')) {
$rtn['host'] = $dialog->ask($output, 'Enter your beanstalkd hostname [localhost]: ', 'localhost');
}
if (!$rtn['queue'] = $input->getOption('queue-name')) {
$rtn['queue'] = $dialog->ask($output, 'Enter the queue (tube) name to use [phpci]: ', 'phpci');
}
return $rtn;
}
/** /**
* Load configuration for DB form CLI options or ask info to user. * Load configuration for DB form CLI options or ask info to user.
* *

View file

@ -0,0 +1,85 @@
<?php
/**
* PHPCI - Continuous Integration for PHP
*
* @copyright Copyright 2015, Block 8 Limited.
* @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md
* @link https://www.phptesting.org/
*/
namespace PHPCI\Command;
use b8\Config;
use b8\Store\Factory;
use Monolog\Logger;
use PHPCI\BuildFactory;
use PHPCI\Helper\Lang;
use PHPCI\Logging\OutputLogHandler;
use PHPCI\Service\BuildService;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* @author Dan Cryer <dan@block8.co.uk>
* @package PHPCI
* @subpackage Console
*/
class RebuildQueueCommand extends Command
{
/**
* @var OutputInterface
*/
protected $output;
/**
* @var Logger
*/
protected $logger;
/**
* @param \Monolog\Logger $logger
* @param string $name
*/
public function __construct(Logger $logger, $name = null)
{
parent::__construct($name);
$this->logger = $logger;
}
protected function configure()
{
$this
->setName('phpci:rebuild-queue')
->setDescription('Rebuilds the PHPCI worker queue.');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$this->output = $output;
// For verbose mode we want to output all informational and above
// messages to the symphony output interface.
if ($input->hasOption('verbose') && $input->getOption('verbose')) {
$this->logger->pushHandler(
new OutputLogHandler($this->output, Logger::INFO)
);
}
$store = Factory::getStore('Build');
$result = $store->getByStatus(0);
$this->logger->addInfo(Lang::get('found_n_builds', count($result['items'])));
$buildService = new BuildService($store);
while (count($result['items'])) {
$build = array_shift($result['items']);
$build = BuildFactory::getBuild($build);
$this->logger->addInfo('Added build #' . $build->getId() . ' to queue.');
$buildService->addBuildToQueue($build);
}
}
}

View file

@ -0,0 +1,87 @@
<?php
/**
* PHPCI - Continuous Integration for PHP
*
* @copyright Copyright 2015, Block 8 Limited.
* @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md
* @link https://www.phptesting.org/
*/
namespace PHPCI\Command;
use b8\Config;
use Monolog\Logger;
use PHPCI\Logging\OutputLogHandler;
use PHPCI\Worker\BuildWorker;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
/**
* Worker Command - Starts the BuildWorker, which pulls jobs from beanstalkd
* @author Dan Cryer <dan@block8.co.uk>
* @package PHPCI
* @subpackage Console
*/
class WorkerCommand extends Command
{
/**
* @var OutputInterface
*/
protected $output;
/**
* @var Logger
*/
protected $logger;
/**
* @param \Monolog\Logger $logger
* @param string $name
*/
public function __construct(Logger $logger, $name = null)
{
parent::__construct($name);
$this->logger = $logger;
}
protected function configure()
{
$this
->setName('phpci:worker')
->setDescription('Runs the PHPCI build worker.')
->addOption('debug', null, null, 'Run PHPCI in Debug Mode');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$this->output = $output;
// For verbose mode we want to output all informational and above
// messages to the symphony output interface.
if ($input->hasOption('verbose') && $input->getOption('verbose')) {
$this->logger->pushHandler(
new OutputLogHandler($this->output, Logger::INFO)
);
}
// Allow PHPCI to run in "debug mode"
if ($input->hasOption('debug') && $input->getOption('debug')) {
$output->writeln('<comment>Debug mode enabled.</comment>');
define('PHPCI_DEBUG_MODE', true);
}
$config = Config::getInstance()->get('phpci.worker', []);
if (empty($config['host']) || empty($config['queue'])) {
$error = 'The worker is not configured. You must set a host and queue in your config.yml file.';
throw new \Exception($error);
}
$worker = new BuildWorker($config['host'], $config['queue']);
$worker->setLogger($this->logger);
$worker->setMaxJobs(Config::getInstance()->get('phpci.worker.max_jobs', -1));
$worker->startWorker();
}
}

View file

@ -63,24 +63,42 @@ class BuildController extends \PHPCI\Controller
$this->view->plugins = $this->getUiPlugins(); $this->view->plugins = $this->getUiPlugins();
$this->view->build = $build; $this->view->build = $build;
$this->view->data = json_encode($this->getBuildData($build)); $this->view->data = $this->getBuildData($build);
$this->layout->title = Lang::get('build_n', $buildId); $this->layout->title = Lang::get('build_n', $buildId);
$this->layout->subtitle = $build->getProjectTitle(); $this->layout->subtitle = $build->getProjectTitle();
$nav = array( switch ($build->getStatus()) {
'title' => Lang::get('build_n', $buildId), case 0:
'icon' => 'cog', $this->layout->skin = 'blue';
'links' => array( break;
'build/rebuild/' . $build->getId() => Lang::get('rebuild_now'),
),
);
if ($this->currentUserIsAdmin()) { case 1:
$nav['links']['build/delete/' . $build->getId()] = Lang::get('delete_build'); $this->layout->skin = 'yellow';
break;
case 2:
$this->layout->skin = 'green';
break;
case 3:
$this->layout->skin = 'red';
break;
} }
$this->layout->nav = $nav; $rebuild = Lang::get('rebuild_now');
$rebuildLink = PHPCI_URL . 'build/rebuild/' . $build->getId();
$delete = Lang::get('delete_build');
$deleteLink = PHPCI_URL . 'build/delete/' . $build->getId();
$actions = "<a class=\"btn btn-default\" href=\"{$rebuildLink}\">{$rebuild}</a> ";
if ($this->currentUserIsAdmin()) {
$actions .= " <a class=\"btn btn-danger\" href=\"{$deleteLink}\">{$delete}</a>";
}
$this->layout->actions = $actions;
} }
/** /**
@ -144,7 +162,7 @@ class BuildController extends \PHPCI\Controller
/** /**
* Get build data from database and json encode it: * Get build data from database and json encode it:
*/ */
protected function getBuildData($build) protected function getBuildData(Build $build)
{ {
$data = array(); $data = array();
$data['status'] = (int)$build->getStatus(); $data['status'] = (int)$build->getStatus();
@ -152,6 +170,19 @@ class BuildController extends \PHPCI\Controller
$data['created'] = !is_null($build->getCreated()) ? $build->getCreated()->format('Y-m-d H:i:s') : null; $data['created'] = !is_null($build->getCreated()) ? $build->getCreated()->format('Y-m-d H:i:s') : null;
$data['started'] = !is_null($build->getStarted()) ? $build->getStarted()->format('Y-m-d H:i:s') : null; $data['started'] = !is_null($build->getStarted()) ? $build->getStarted()->format('Y-m-d H:i:s') : null;
$data['finished'] = !is_null($build->getFinished()) ? $build->getFinished()->format('Y-m-d H:i:s') : null; $data['finished'] = !is_null($build->getFinished()) ? $build->getFinished()->format('Y-m-d H:i:s') : null;
$data['duration'] = $build->getDuration();
/** @var \PHPCI\Store\BuildErrorStore $errorStore */
$errorStore = b8\Store\Factory::getStore('BuildError');
$errors = $errorStore->getErrorsForBuild($build->getId(), $this->getParam('since', null));
$errorView = new b8\View('Build/errors');
$errorView->build = $build;
$errorView->errors = $errors;
$data['errors'] = $errorStore->getErrorTotalForBuild($build->getId());
$data['error_html'] = $errorView->render();
$data['since'] = (new \DateTime())->format('Y-m-d H:i:s');
return $data; return $data;
} }

View file

@ -104,7 +104,6 @@ class BuildStatusController extends \PHPCI\Controller
} }
} }
} }
} catch (\Exception $e) { } catch (\Exception $e) {
$xml = new \SimpleXMLElement('<projects/>'); $xml = new \SimpleXMLElement('<projects/>');
} }

View file

@ -0,0 +1,120 @@
<?php
/**
* PHPCI - Continuous Integration for PHP
*
* @copyright Copyright 2015, Block 8 Limited.
* @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md
* @link https://www.phptesting.org/
*/
namespace PHPCI\Controller;
use b8;
use b8\Form;
use b8\Store;
use PHPCI\Controller;
use PHPCI\Model\ProjectGroup;
/**
* Project Controller - Allows users to create, edit and view projects.
* @author Dan Cryer <dan@block8.co.uk>
* @package PHPCI
* @subpackage Web
*/
class GroupController extends Controller
{
/**
* @var \PHPCI\Store\ProjectGroupStore
*/
protected $groupStore;
/**
* Set up this controller.
*/
public function init()
{
$this->groupStore = b8\Store\Factory::getStore('ProjectGroup');
}
/**
* List project groups.
*/
public function index()
{
$this->requireAdmin();
$groups = array();
$groupList = $this->groupStore->getWhere(array(), 100, 0, array(), array('title' => 'ASC'));
foreach ($groupList['items'] as $group) {
$thisGroup = array(
'title' => $group->getTitle(),
'id' => $group->getId(),
);
$projects = b8\Store\Factory::getStore('Project')->getByGroupId($group->getId());
$thisGroup['projects'] = $projects['items'];
$groups[] = $thisGroup;
}
$this->view->groups = $groups;
}
/**
* Add or edit a project group.
* @param null $groupId
* @return void|b8\Http\Response\RedirectResponse
*/
public function edit($groupId = null)
{
$this->requireAdmin();
if (!is_null($groupId)) {
$group = $this->groupStore->getById($groupId);
} else {
$group = new ProjectGroup();
}
if ($this->request->getMethod() == 'POST') {
$group->setTitle($this->getParam('title'));
$this->groupStore->save($group);
$response = new b8\Http\Response\RedirectResponse();
$response->setHeader('Location', PHPCI_URL.'group');
return $response;
}
$form = new Form();
$form->setMethod('POST');
$form->setAction(PHPCI_URL . 'group/edit' . (!is_null($groupId) ? '/' . $groupId : ''));
$title = new Form\Element\Text('title');
$title->setContainerClass('form-group');
$title->setClass('form-control');
$title->setLabel('Group Title');
$title->setValue($group->getTitle());
$submit = new Form\Element\Submit();
$submit->setValue('Save Group');
$form->addField($title);
$form->addField($submit);
$this->view->form = $form;
}
/**
* Delete a project group.
* @param $groupId
* @return b8\Http\Response\RedirectResponse
*/
public function delete($groupId)
{
$this->requireAdmin();
$group = $this->groupStore->getById($groupId);
$this->groupStore->delete($group);
$response = new b8\Http\Response\RedirectResponse();
$response->setHeader('Location', PHPCI_URL.'group');
return $response;
}
}

View file

@ -23,15 +23,20 @@ use PHPCI\Model\Build;
class HomeController extends \PHPCI\Controller class HomeController extends \PHPCI\Controller
{ {
/** /**
* @var \b8\Store\BuildStore * @var \PHPCI\Store\BuildStore
*/ */
protected $buildStore; protected $buildStore;
/** /**
* @var \b8\Store\ProjectStore * @var \PHPCI\Store\ProjectStore
*/ */
protected $projectStore; protected $projectStore;
/**
* @var \PHPCI\Store\ProjectGroupStore
*/
protected $groupStore;
/** /**
* Initialise the controller, set up stores and services. * Initialise the controller, set up stores and services.
*/ */
@ -39,6 +44,7 @@ class HomeController extends \PHPCI\Controller
{ {
$this->buildStore = b8\Store\Factory::getStore('Build'); $this->buildStore = b8\Store\Factory::getStore('Build');
$this->projectStore = b8\Store\Factory::getStore('Project'); $this->projectStore = b8\Store\Factory::getStore('Project');
$this->groupStore = b8\Store\Factory::getStore('ProjectGroup');
} }
/** /**
@ -47,15 +53,6 @@ class HomeController extends \PHPCI\Controller
public function index() public function index()
{ {
$this->layout->title = Lang::get('dashboard'); $this->layout->title = Lang::get('dashboard');
$projects = $this->projectStore->getWhere(
array('archived' => (int)isset($_GET['archived'])),
50,
0,
array(),
array('title' => 'ASC')
);
$builds = $this->buildStore->getLatestBuilds(null, 10); $builds = $this->buildStore->getLatestBuilds(null, 10);
foreach ($builds as &$build) { foreach ($builds as &$build) {
@ -63,8 +60,7 @@ class HomeController extends \PHPCI\Controller
} }
$this->view->builds = $builds; $this->view->builds = $builds;
$this->view->projects = $projects['items']; $this->view->groups = $this->getGroupInfo();
$this->view->summary = $this->getSummaryHtml($projects);
return $this->view->render(); return $this->view->render();
} }
@ -102,7 +98,7 @@ class HomeController extends \PHPCI\Controller
$failures = array(); $failures = array();
$counts = array(); $counts = array();
foreach ($projects['items'] as $project) { foreach ($projects as $project) {
$summaryBuilds[$project->getId()] = $this->buildStore->getLatestBuilds($project->getId()); $summaryBuilds[$project->getId()] = $this->buildStore->getLatestBuilds($project->getId());
$count = $this->buildStore->getWhere( $count = $this->buildStore->getWhere(
@ -122,7 +118,7 @@ class HomeController extends \PHPCI\Controller
} }
$summaryView = new b8\View('SummaryTable'); $summaryView = new b8\View('SummaryTable');
$summaryView->projects = $projects['items']; $summaryView->projects = $projects;
$summaryView->builds = $summaryBuilds; $summaryView->builds = $summaryBuilds;
$summaryView->successful = $successes; $summaryView->successful = $successes;
$summaryView->failed = $failures; $summaryView->failed = $failures;
@ -147,4 +143,24 @@ class HomeController extends \PHPCI\Controller
return $view->render(); return $view->render();
} }
/**
* Get a summary of the project groups we have, and what projects they have in them.
* @return array
*/
protected function getGroupInfo()
{
$rtn = array();
$groups = $this->groupStore->getWhere(array(), 100, 0, array(), array('title' => 'ASC'));
foreach ($groups['items'] as $group) {
$thisGroup = array('title' => $group->getTitle());
$projects = $this->projectStore->getByGroupId($group->getId());
$thisGroup['projects'] = $projects['items'];
$thisGroup['summary'] = $this->getSummaryHtml($thisGroup['projects']);
$rtn[] = $thisGroup;
}
return $rtn;
}
} }

View file

@ -23,24 +23,6 @@ use PHPCI\Plugin\Util\PluginInformationCollection;
*/ */
class PluginController extends \PHPCI\Controller class PluginController extends \PHPCI\Controller
{ {
protected $required = array(
'php',
'ext-pdo',
'ext-pdo_mysql',
'block8/b8framework',
'ircmaxell/password-compat',
'swiftmailer/swiftmailer',
'symfony/yaml',
'symfony/console',
'psr/log',
'monolog/monolog',
'pimple/pimple',
'robmorgan/phinx',
);
protected $canInstall;
protected $composerPath;
/** /**
* List all enabled plugins, installed and recommend packages. * List all enabled plugins, installed and recommend packages.
* @return string * @return string
@ -49,12 +31,8 @@ class PluginController extends \PHPCI\Controller
{ {
$this->requireAdmin(); $this->requireAdmin();
$this->view->canWrite = is_writable(APPLICATION_PATH . 'composer.json');
$this->view->required = $this->required;
$json = $this->getComposerJson(); $json = $this->getComposerJson();
$this->view->installedPackages = $json['require']; $this->view->installedPackages = $json['require'];
$this->view->suggestedPackages = $json['suggest'];
$pluginInfo = new PluginInformationCollection(); $pluginInfo = new PluginInformationCollection();
$pluginInfo->add(FilesPluginInformation::newFromDir( $pluginInfo->add(FilesPluginInformation::newFromDir(
@ -71,49 +49,6 @@ class PluginController extends \PHPCI\Controller
return $this->view->render(); return $this->view->render();
} }
/**
* Remove a given package.
*/
public function remove()
{
$this->requireAdmin();
$package = $this->getParam('package', null);
$json = $this->getComposerJson();
$response = new b8\Http\Response\RedirectResponse();
if (!in_array($package, $this->required)) {
unset($json['require'][$package]);
$this->setComposerJson($json);
$response->setHeader('Location', PHPCI_URL . 'plugin?r=' . $package);
return $response;
}
$response->setHeader('Location', PHPCI_URL);
return $response;
}
/**
* Install a given package.
*/
public function install()
{
$this->requireAdmin();
$package = $this->getParam('package', null);
$version = $this->getParam('version', '*');
$json = $this->getComposerJson();
$json['require'][$package] = $version;
$this->setComposerJson($json);
$response = new b8\Http\Response\RedirectResponse();
$response->setHeader('Location', PHPCI_URL . 'plugin?w=' . $package);
return $response;
}
/** /**
* Get the json-decoded contents of the composer.json file. * Get the json-decoded contents of the composer.json file.
* @return mixed * @return mixed
@ -123,83 +58,4 @@ class PluginController extends \PHPCI\Controller
$json = file_get_contents(APPLICATION_PATH . 'composer.json'); $json = file_get_contents(APPLICATION_PATH . 'composer.json');
return json_decode($json, true); return json_decode($json, true);
} }
/**
* Convert array to json and save composer.json
*
* @param $array
*/
protected function setComposerJson($array)
{
if (defined('JSON_PRETTY_PRINT')) {
$json = json_encode($array, JSON_PRETTY_PRINT);
} else {
$json = json_encode($array);
}
file_put_contents(APPLICATION_PATH . 'composer.json', $json);
}
/**
* Find a system binary.
* @param $binary
* @return null|string
*/
protected function findBinary($binary)
{
if (is_string($binary)) {
$binary = array($binary);
}
foreach ($binary as $bin) {
// Check project root directory:
if (is_file(APPLICATION_PATH . $bin)) {
return APPLICATION_PATH . $bin;
}
// Check Composer bin dir:
if (is_file(APPLICATION_PATH . 'vendor/bin/' . $bin)) {
return APPLICATION_PATH . 'vendor/bin/' . $bin;
}
// Use "which"
$which = trim(shell_exec('which ' . $bin));
if (!empty($which)) {
return $which;
}
}
return null;
}
/**
* Perform a search on packagist.org.
*/
public function packagistSearch()
{
$searchQuery = $this->getParam('q', '');
$http = new \b8\HttpClient();
$http->setHeaders(array('User-Agent: PHPCI/1.0 (+https://www.phptesting.org)'));
$res = $http->get('https://packagist.org/search.json', array('q' => $searchQuery));
$response = new b8\Http\Response\JsonResponse();
$response->setContent($res['body']);
return $response;
}
/**
* Look up available versions of a given package on packagist.org
*/
public function packagistVersions()
{
$name = $this->getParam('p', '');
$http = new \b8\HttpClient();
$http->setHeaders(array('User-Agent: PHPCI/1.0 (+https://www.phptesting.org)'));
$res = $http->get('https://packagist.org/packages/'.$name.'.json');
$response = new b8\Http\Response\JsonResponse();
$response->setContent($res['body']);
return $response;
}
} }

View file

@ -220,6 +220,7 @@ class ProjectController extends PHPCI\Controller
'build_config' => $this->getParam('build_config', null), 'build_config' => $this->getParam('build_config', null),
'allow_public_status' => $this->getParam('allow_public_status', 0), 'allow_public_status' => $this->getParam('allow_public_status', 0),
'branch' => $this->getParam('branch', null), 'branch' => $this->getParam('branch', null),
'group' => $this->getParam('group_id', null),
); );
$project = $this->projectService->createProject($title, $type, $reference, $options); $project = $this->projectService->createProject($title, $type, $reference, $options);
@ -284,6 +285,7 @@ class ProjectController extends PHPCI\Controller
'allow_public_status' => $this->getParam('allow_public_status', 0), 'allow_public_status' => $this->getParam('allow_public_status', 0),
'archived' => $this->getParam('archived', 0), 'archived' => $this->getParam('archived', 0),
'branch' => $this->getParam('branch', null), 'branch' => $this->getParam('branch', null),
'group' => $this->getParam('group_id', null),
); );
$project = $this->projectService->updateProject($project, $title, $type, $reference, $options); $project = $this->projectService->updateProject($project, $title, $type, $reference, $options);
@ -352,6 +354,20 @@ class ProjectController extends PHPCI\Controller
$field->setClass('form-control')->setContainerClass('form-group')->setValue('master'); $field->setClass('form-control')->setContainerClass('form-group')->setValue('master');
$form->addField($field); $form->addField($field);
$field = Form\Element\Select::create('group_id', 'Project Group', true);
$field->setClass('form-control')->setContainerClass('form-group')->setValue(1);
$groups = array();
$groupStore = b8\Store\Factory::getStore('ProjectGroup');
$groupList = $groupStore->getWhere(array(), 100, 0, array(), array('title' => 'ASC'));
foreach ($groupList['items'] as $group) {
$groups[$group->getId()] = $group->getTitle();
}
$field->setOptions($groups);
$form->addField($field);
$field = Form\Element\Checkbox::create('allow_public_status', Lang::get('allow_public_status'), false); $field = Form\Element\Checkbox::create('allow_public_status', Lang::get('allow_public_status'), false);
$field->setContainerClass('form-group'); $field->setContainerClass('form-group');
$field->setCheckedValue(1); $field->setCheckedValue(1);

View file

@ -49,7 +49,7 @@ class SessionController extends \PHPCI\Controller
} else { } else {
unset($_SESSION['login_token']); unset($_SESSION['login_token']);
$user = $this->userStore->getByLoginOrEmail($this->getParam('email')); $user = $this->userStore->getByEmail($this->getParam('email'));
if ($user && password_verify($this->getParam('password', ''), $user->getHash())) { if ($user && password_verify($this->getParam('password', ''), $user->getHash())) {
session_regenerate_id(true); session_regenerate_id(true);
@ -68,7 +68,7 @@ class SessionController extends \PHPCI\Controller
$form->setAction(PHPCI_URL.'session/login'); $form->setAction(PHPCI_URL.'session/login');
$email = new b8\Form\Element\Email('email'); $email = new b8\Form\Element\Email('email');
$email->setLabel(Lang::get('login')); $email->setLabel(Lang::get('email_address'));
$email->setRequired(true); $email->setRequired(true);
$email->setContainerClass('form-group'); $email->setContainerClass('form-group');
$email->setClass('form-control'); $email->setClass('form-control');

View file

@ -444,7 +444,7 @@ class SettingsController extends Controller
$field->setClass('form-control'); $field->setClass('form-control');
$field->setContainerClass('form-group'); $field->setContainerClass('form-group');
$field->setOptions(Lang::getLanguageOptions()); $field->setOptions(Lang::getLanguageOptions());
$field->setValue('en'); $field->setValue(Lang::getLanguage());
$form->addField($field); $form->addField($field);

View file

@ -27,6 +27,8 @@ use PHPCI\Store\ProjectStore;
* @author Guillaume Perréal <adirelle@gmail.com> * @author Guillaume Perréal <adirelle@gmail.com>
* @package PHPCI * @package PHPCI
* @subpackage Web * @subpackage Web
*
* @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
*/ */
class WebhookController extends \b8\Controller class WebhookController extends \b8\Controller
{ {
@ -80,11 +82,64 @@ class WebhookController extends \b8\Controller
} }
/** /**
* Called by Bitbucket POST service. * Called by Bitbucket.
*/ */
public function bitbucket($projectId) public function bitbucket($projectId)
{ {
$project = $this->fetchProject($projectId, 'bitbucket'); $project = $this->fetchProject($projectId, 'bitbucket');
// Support both old services and new webhooks
if ($payload = $this->getParam('payload')) {
return $this->bitbucketService(json_decode($payload, true), $project);
}
$payload = json_decode(file_get_contents("php://input"), true);
if (empty($payload['push']['changes'])) {
// Invalid event from bitbucket
return [
'status' => 'failed',
'commits' => []
];
}
return $this->bitbucketWebhook($payload, $project);
}
/**
* Bitbucket webhooks.
*/
protected function bitbucketWebhook($payload, $project)
{
$results = array();
$status = 'failed';
foreach ($payload['push']['changes'] as $commit) {
try {
$email = $commit['new']['target']['author']['raw'];
$email = substr($email, 0, strpos($email, '>'));
$email = substr($email, strpos($email, '<') + 1);
$results[$commit['new']['target']['hash']] = $this->createBuild(
$project,
$commit['new']['target']['hash'],
$commit['new']['name'],
$email,
$commit['new']['target']['message']
);
$status = 'ok';
} catch (Exception $ex) {
$results[$commit['new']['target']['hash']] = array('status' => 'failed', 'error' => $ex->getMessage());
}
}
return array('status' => $status, 'commits' => $results);
}
/**
* Bitbucket POST service.
*/
protected function bitbucketService($payload, $project)
{
$payload = json_decode($this->getParam('payload'), true); $payload = json_decode($this->getParam('payload'), true);
$results = array(); $results = array();
@ -170,7 +225,7 @@ class WebhookController extends \b8\Controller
protected function githubCommitRequest(Project $project, array $payload) protected function githubCommitRequest(Project $project, array $payload)
{ {
// Github sends a payload when you close a pull request with a // Github sends a payload when you close a pull request with a
// non-existant commit. We don't want this. // non-existent commit. We don't want this.
if (array_key_exists('after', $payload) && $payload['after'] === '0000000000000000000000000000000000000000') { if (array_key_exists('after', $payload) && $payload['after'] === '0000000000000000000000000000000000000000') {
return array('status' => 'ignored'); return array('status' => 'ignored');
} }
@ -364,10 +419,6 @@ class WebhookController extends \b8\Controller
// If not, create a new build job for it: // If not, create a new build job for it:
$build = $this->buildService->createBuild($project, $commitId, $branch, $committer, $commitMessage, $extra); $build = $this->buildService->createBuild($project, $commitId, $branch, $committer, $commitMessage, $extra);
$build = BuildFactory::getBuild($build);
// Send a status postback if the build type provides one:
$build->sendStatusPostback();
return array('status' => 'ok', 'buildID' => $build->getID()); return array('status' => 'ok', 'buildID' => $build->getID());
} }

View file

@ -21,7 +21,7 @@ final class AnsiConverter
static private $converter = null; static private $converter = null;
/** /**
* Initialize the singletion. * Initialize the singleton.
* *
* @return AnsiToHtmlConverter * @return AnsiToHtmlConverter
*/ */
@ -35,7 +35,7 @@ final class AnsiConverter
} }
/** /**
* Convert a text containing ANSI colr sequences into HTML code. * Convert a text containing ANSI color sequences into HTML code.
* *
* @param string $text The text to convert * @param string $text The text to convert
* *
@ -47,7 +47,7 @@ final class AnsiConverter
} }
/** /**
* Do not instanciate this class. * Do not instantiate this class.
*/ */
private function __construct() private function __construct()
{ {

View file

@ -76,6 +76,7 @@ abstract class BaseCommandExecutor implements CommandExecutor
$this->lastOutput = array(); $this->lastOutput = array();
$command = call_user_func_array('sprintf', $args); $command = call_user_func_array('sprintf', $args);
$this->logger->logDebug($command);
if ($this->quiet) { if ($this->quiet) {
$this->logger->log('Executing: ' . $command); $this->logger->log('Executing: ' . $command);
@ -89,7 +90,6 @@ abstract class BaseCommandExecutor implements CommandExecutor
); );
$pipes = array(); $pipes = array();
$process = proc_open($command, $descriptorSpec, $pipes, $this->buildPath, null); $process = proc_open($command, $descriptorSpec, $pipes, $this->buildPath, null);
if (is_resource($process)) { if (is_resource($process)) {

View file

@ -38,6 +38,7 @@ class BuildInterpolator
$this->interpolation_vars['%COMMIT%'] = $build->getCommitId(); $this->interpolation_vars['%COMMIT%'] = $build->getCommitId();
$this->interpolation_vars['%SHORT_COMMIT%'] = substr($build->getCommitId(), 0, 7); $this->interpolation_vars['%SHORT_COMMIT%'] = substr($build->getCommitId(), 0, 7);
$this->interpolation_vars['%COMMIT_EMAIL%'] = $build->getCommitterEmail(); $this->interpolation_vars['%COMMIT_EMAIL%'] = $build->getCommitterEmail();
$this->interpolation_vars['%COMMIT_MESSAGE%'] = $build->getCommitMessage();
$this->interpolation_vars['%COMMIT_URI%'] = $build->getCommitLink(); $this->interpolation_vars['%COMMIT_URI%'] = $build->getCommitLink();
$this->interpolation_vars['%BRANCH%'] = $build->getBranch(); $this->interpolation_vars['%BRANCH%'] = $build->getBranch();
$this->interpolation_vars['%BRANCH_URI%'] = $build->getBranchLink(); $this->interpolation_vars['%BRANCH_URI%'] = $build->getBranchLink();
@ -49,6 +50,7 @@ class BuildInterpolator
$this->interpolation_vars['%BUILD_URI%'] = $phpCiUrl . "build/view/" . $build->getId(); $this->interpolation_vars['%BUILD_URI%'] = $phpCiUrl . "build/view/" . $build->getId();
$this->interpolation_vars['%PHPCI_COMMIT%'] = $this->interpolation_vars['%COMMIT%']; $this->interpolation_vars['%PHPCI_COMMIT%'] = $this->interpolation_vars['%COMMIT%'];
$this->interpolation_vars['%PHPCI_SHORT_COMMIT%'] = $this->interpolation_vars['%SHORT_COMMIT%']; $this->interpolation_vars['%PHPCI_SHORT_COMMIT%'] = $this->interpolation_vars['%SHORT_COMMIT%'];
$this->interpolation_vars['%PHPCI_COMMIT_MESSAGE%'] = $this->interpolation_vars['%COMMIT_MESSAGE%'];
$this->interpolation_vars['%PHPCI_COMMIT_EMAIL%'] = $this->interpolation_vars['%COMMIT_EMAIL%']; $this->interpolation_vars['%PHPCI_COMMIT_EMAIL%'] = $this->interpolation_vars['%COMMIT_EMAIL%'];
$this->interpolation_vars['%PHPCI_COMMIT_URI%'] = $this->interpolation_vars['%COMMIT_URI%']; $this->interpolation_vars['%PHPCI_COMMIT_URI%'] = $this->interpolation_vars['%COMMIT_URI%'];
$this->interpolation_vars['%PHPCI_PROJECT%'] = $this->interpolation_vars['%PROJECT%']; $this->interpolation_vars['%PHPCI_PROJECT%'] = $this->interpolation_vars['%PROJECT%'];
@ -61,6 +63,7 @@ class BuildInterpolator
putenv('PHPCI=1'); putenv('PHPCI=1');
putenv('PHPCI_COMMIT=' . $this->interpolation_vars['%COMMIT%']); putenv('PHPCI_COMMIT=' . $this->interpolation_vars['%COMMIT%']);
putenv('PHPCI_SHORT_COMMIT=' . $this->interpolation_vars['%SHORT_COMMIT%']); putenv('PHPCI_SHORT_COMMIT=' . $this->interpolation_vars['%SHORT_COMMIT%']);
putenv('PHPCI_COMMIT_MESSAGE=' . $this->interpolation_vars['%COMMIT_MESSAGE%']);
putenv('PHPCI_COMMIT_EMAIL=' . $this->interpolation_vars['%COMMIT_EMAIL%']); putenv('PHPCI_COMMIT_EMAIL=' . $this->interpolation_vars['%COMMIT_EMAIL%']);
putenv('PHPCI_COMMIT_URI=' . $this->interpolation_vars['%COMMIT_URI%']); putenv('PHPCI_COMMIT_URI=' . $this->interpolation_vars['%COMMIT_URI%']);
putenv('PHPCI_PROJECT=' . $this->interpolation_vars['%PROJECT%']); putenv('PHPCI_PROJECT=' . $this->interpolation_vars['%PROJECT%']);

View file

@ -12,7 +12,7 @@ namespace PHPCI\Helper;
use b8\Config; use b8\Config;
/** /**
* Login Is Disabled Helper - Checks if login is disalbed in the view * Login Is Disabled Helper - Checks if login is disabled in the view
* @author Stephen Ball <phpci@stephen.rebelinblue.com> * @author Stephen Ball <phpci@stephen.rebelinblue.com>
* @package PHPCI * @package PHPCI
* @subpackage Web * @subpackage Web

View file

@ -71,7 +71,7 @@ class MailerFactory
} else { } else {
// Check defaults // Check defaults
switch($configName) { switch ($configName) {
case 'smtp_address': case 'smtp_address':
return "localhost"; return "localhost";
case 'default_mailto_address': case 'default_mailto_address':

View file

@ -114,6 +114,7 @@ i din foretrukne hosting-platform.',
'default_branch' => 'Default branch navn', 'default_branch' => 'Default branch navn',
'allow_public_status' => 'Tillad offentlig status-side og -billede for dette projekt?', 'allow_public_status' => 'Tillad offentlig status-side og -billede for dette projekt?',
'archived' => 'Archived', 'archived' => 'Archived',
'archived_menu' => 'Archived',
'save_project' => 'Gem Projekt', 'save_project' => 'Gem Projekt',
'error_mercurial' => 'Mercurial repository-URL skal starte med http:// eller https://', 'error_mercurial' => 'Mercurial repository-URL skal starte med http:// eller https://',
@ -203,7 +204,7 @@ Services</a> sektionen under dit Bitbucket-repository.',
'build_finished' => 'Build Afsluttet', 'build_finished' => 'Build Afsluttet',
'test_message' => 'Message', 'test_message' => 'Message',
'test_no_message' => 'No message', 'test_no_message' => 'No message',
'test_success' => 'Succesfull: %d', 'test_success' => 'Successful: %d',
'test_fail' => 'Failures: %d', 'test_fail' => 'Failures: %d',
'test_skipped' => 'Skipped: %d', 'test_skipped' => 'Skipped: %d',
'test_error' => 'Errors: %d', 'test_error' => 'Errors: %d',

View file

@ -39,7 +39,7 @@ PHPCI',
'reset_email_title' => 'PHPCI Passwort zurücksetzen für %s', 'reset_email_title' => 'PHPCI Passwort zurücksetzen für %s',
'reset_invalid' => 'Fehlerhafte Anfrage für das Zurücksetzen eines Passwortes', 'reset_invalid' => 'Fehlerhafte Anfrage für das Zurücksetzen eines Passwortes',
'email_address' => 'Emailadresse', 'email_address' => 'Emailadresse',
'login' => 'Login / Email Address', 'login' => 'Login / Emailadresse',
'password' => 'Passwort', 'password' => 'Passwort',
'log_in' => 'Einloggen', 'log_in' => 'Einloggen',
@ -115,7 +115,8 @@ generiert. Um es zu verwenden, fügen Sie einfach den folgenden Public Key im Ab
(falls Sie Ihrem Projektrepository kein phpci.yml hinzufügen können)', (falls Sie Ihrem Projektrepository kein phpci.yml hinzufügen können)',
'default_branch' => 'Name des Standardbranches', 'default_branch' => 'Name des Standardbranches',
'allow_public_status' => 'Öffentliche Statusseite und -bild für dieses Projekt einschalten?', 'allow_public_status' => 'Öffentliche Statusseite und -bild für dieses Projekt einschalten?',
'archived' => 'Archived', 'archived' => 'Archiviert',
'archived_menu' => 'Archiviert',
'save_project' => 'Projekt speichern', 'save_project' => 'Projekt speichern',
'error_mercurial' => 'Mercurial Repository-URL muss mit http://, oder https:// beginnen', 'error_mercurial' => 'Mercurial Repository-URL muss mit http://, oder https:// beginnen',
@ -129,7 +130,7 @@ generiert. Um es zu verwenden, fügen Sie einfach den folgenden Public Key im Ab
'all_branches' => 'Alle Branches', 'all_branches' => 'Alle Branches',
'builds' => 'Builds', 'builds' => 'Builds',
'id' => 'ID', 'id' => 'ID',
'date' => 'Date', 'date' => 'Datum',
'project' => 'Projekt', 'project' => 'Projekt',
'commit' => 'Commit', 'commit' => 'Commit',
'branch' => 'Branch', 'branch' => 'Branch',
@ -150,6 +151,9 @@ generiert. Um es zu verwenden, fügen Sie einfach den folgenden Public Key im Ab
'webhooks_help_bitbucket' => 'Um für dieses Projekt automatisch einen Build zu starten, wenn neue Commits gepushed werden, fügen Sie die untenstehende URL als "POST" Service in der <a href="https://bitbucket.org/%s/admin/services">Services</a>-Sektion Ihres Bitbucket Repositories hinzu.', 'webhooks_help_bitbucket' => 'Um für dieses Projekt automatisch einen Build zu starten, wenn neue Commits gepushed werden, fügen Sie die untenstehende URL als "POST" Service in der <a href="https://bitbucket.org/%s/admin/services">Services</a>-Sektion Ihres Bitbucket Repositories hinzu.',
// View Build // View Build
'errors' => 'Fehler',
'information' => 'Information',
'build_x_not_found' => 'Build mit ID %d existiert nicht.', 'build_x_not_found' => 'Build mit ID %d existiert nicht.',
'build_n' => 'Build %d', 'build_n' => 'Build %d',
'rebuild_now' => 'Build neu starten', 'rebuild_now' => 'Build neu starten',
@ -208,14 +212,14 @@ generiert. Um es zu verwenden, fügen Sie einfach den folgenden Public Key im Ab
'build_created' => 'Build erstellt', 'build_created' => 'Build erstellt',
'build_started' => 'Build gestartet', 'build_started' => 'Build gestartet',
'build_finished' => 'Build abgeschlossen', 'build_finished' => 'Build abgeschlossen',
'test_message' => 'Message', 'test_message' => 'Nachricht',
'test_no_message' => 'No message', 'test_no_message' => 'Keine Nachricht',
'test_success' => 'Succesfull: %d', 'test_success' => 'Erfolgreich: %d',
'test_fail' => 'Failures: %d', 'test_fail' => 'Fehlschläge: %d',
'test_skipped' => 'Skipped: %d', 'test_skipped' => 'Übersprungen: %d',
'test_error' => 'Errors: %d', 'test_error' => 'Fehler: %d',
'test_todo' => 'Todos: %d', 'test_todo' => 'Todos: %d',
'test_total' => '%d test(s)', 'test_total' => '%d Test(s)',
// Users // Users
'name' => 'Name', 'name' => 'Name',
@ -291,6 +295,19 @@ generiert. Um es zu verwenden, fügen Sie einfach den folgenden Public Key im Ab
'search_packagist_for_more' => 'Packagist nach mehr Packages durchsuchen', 'search_packagist_for_more' => 'Packagist nach mehr Packages durchsuchen',
'search' => 'Suchen &raquo;', 'search' => 'Suchen &raquo;',
// Summary plugin
'build-summary' => 'Zusammenfassung',
'stage' => 'Abschnitt',
'duration' => 'Dauer',
'plugin' => 'Plugin',
'stage_setup' => 'Vorbereitung',
'stage_test' => 'Test',
'stage_complete' => 'Vollständig',
'stage_success' => 'Erfolg',
'stage_failure' => 'Fehlschlag',
'stage_broken' => 'Defekt',
'stage_fixed' => 'Behoben',
// Installer // Installer
'installation_url' => 'PHPCI Installations-URL', 'installation_url' => 'PHPCI Installations-URL',
'db_host' => 'Datenbankserver', 'db_host' => 'Datenbankserver',
@ -399,5 +416,18 @@ generiert. Um es zu verwenden, fügen Sie einfach den folgenden Public Key im Ab
'build_file_missing' => 'Angegebene Builddatei existiert nicht.', 'build_file_missing' => 'Angegebene Builddatei existiert nicht.',
'property_file_missing' => 'Angegebene Eigenschaftsdatei existiert nicht.', 'property_file_missing' => 'Angegebene Eigenschaftsdatei existiert nicht.',
'could_not_process_report' => 'Konnte den von diesem Tool erstellten Bericht nicht verarbeiten.', 'could_not_process_report' => 'Konnte den von diesem Tool erstellten Bericht nicht verarbeiten.',
'shell_not_enabled' => 'Das Shell-Plugin ist nicht aktiviert. Bitte aktivieren Sie es via config.yml.' 'shell_not_enabled' => 'Das Shell-Plugin ist nicht aktiviert. Bitte aktivieren Sie es via config.yml.',
// Error Levels:
'critical' => 'Kritisch',
'high' => 'Hoch',
'normal' => 'Normal',
'low' => 'Niedrig',
// Plugins that generate errors:
'php_mess_detector' => 'PHP Mess Detector',
'php_code_sniffer' => 'PHP Code Sniffer',
'php_unit' => 'PHP Unit',
'php_cpd' => 'PHP Copy/Paste Detector',
'php_docblock_checker' => 'PHP Docblock Checker',
); );

View file

@ -115,6 +115,7 @@ PHPCI',
'default_branch' => 'Προκαθορισμένο όνομα διακλάδωσης', 'default_branch' => 'Προκαθορισμένο όνομα διακλάδωσης',
'allow_public_status' => 'Ενεργοποίηση της σελίδας δημόσιας κατάστασης και την εικόνα για το έργο αυτό;', 'allow_public_status' => 'Ενεργοποίηση της σελίδας δημόσιας κατάστασης και την εικόνα για το έργο αυτό;',
'archived' => 'Archived', 'archived' => 'Archived',
'archived_menu' => 'Archived',
'save_project' => 'Αποθήκευση έργου', 'save_project' => 'Αποθήκευση έργου',
'error_mercurial' => 'Ο σύνδεσμος URL του ευμετάβλητου αποθετηρίου πρέπει να ξεκινάει με http:// ή https://', 'error_mercurial' => 'Ο σύνδεσμος URL του ευμετάβλητου αποθετηρίου πρέπει να ξεκινάει με http:// ή https://',
@ -204,7 +205,7 @@ Services</a> του Bitbucket αποθετηρίου σας.',
'build_finished' => 'Η κατασκευή ολοκληρώθηκε', 'build_finished' => 'Η κατασκευή ολοκληρώθηκε',
'test_message' => 'Message', 'test_message' => 'Message',
'test_no_message' => 'No message', 'test_no_message' => 'No message',
'test_success' => 'Succesfull: %d', 'test_success' => 'Successful: %d',
'test_fail' => 'Failures: %d', 'test_fail' => 'Failures: %d',
'test_skipped' => 'Skipped: %d', 'test_skipped' => 'Skipped: %d',
'test_error' => 'Errors: %d', 'test_error' => 'Errors: %d',

View file

@ -116,6 +116,7 @@ PHPCI',
'default_branch' => 'Default branch name', 'default_branch' => 'Default branch name',
'allow_public_status' => 'Enable public status page and image for this project?', 'allow_public_status' => 'Enable public status page and image for this project?',
'archived' => 'Archived', 'archived' => 'Archived',
'archived_menu' => 'Archived',
'save_project' => 'Save Project', 'save_project' => 'Save Project',
'error_mercurial' => 'Mercurial repository URL must be start with http:// or https://', 'error_mercurial' => 'Mercurial repository URL must be start with http:// or https://',
@ -153,6 +154,9 @@ PHPCI',
Services</a> section of your Bitbucket repository.', Services</a> section of your Bitbucket repository.',
// View Build // View Build
'errors' => 'Errors',
'information' => 'Information',
'build_x_not_found' => 'Build with ID %d does not exist.', 'build_x_not_found' => 'Build with ID %d does not exist.',
'build_n' => 'Build %d', 'build_n' => 'Build %d',
'rebuild_now' => 'Rebuild Now', 'rebuild_now' => 'Rebuild Now',
@ -213,7 +217,7 @@ PHPCI',
'build_finished' => 'Build Finished', 'build_finished' => 'Build Finished',
'test_message' => 'Message', 'test_message' => 'Message',
'test_no_message' => 'No message', 'test_no_message' => 'No message',
'test_success' => 'Succesfull: %d', 'test_success' => 'Successful: %d',
'test_fail' => 'Failures: %d', 'test_fail' => 'Failures: %d',
'test_skipped' => 'Skipped: %d', 'test_skipped' => 'Skipped: %d',
'test_error' => 'Errors: %d', 'test_error' => 'Errors: %d',
@ -305,6 +309,8 @@ PHPCI',
'stage_complete' => 'Complete', 'stage_complete' => 'Complete',
'stage_success' => 'Success', 'stage_success' => 'Success',
'stage_failure' => 'Failure', 'stage_failure' => 'Failure',
'stage_broken' => 'Broken',
'stage_fixed' => 'Fixed',
// Installer // Installer
'installation_url' => 'PHPCI Installation URL', 'installation_url' => 'PHPCI Installation URL',
@ -414,5 +420,22 @@ PHPCI',
'build_file_missing' => 'Specified build file does not exist.', 'build_file_missing' => 'Specified build file does not exist.',
'property_file_missing' => 'Specified property file does not exist.', 'property_file_missing' => 'Specified property file does not exist.',
'could_not_process_report' => 'Could not process the report generated by this tool.', 'could_not_process_report' => 'Could not process the report generated by this tool.',
'shell_not_enabled' => 'The shell plugin is not enabled. Please enable it via config.yml.' 'shell_not_enabled' => 'The shell plugin is not enabled. Please enable it via config.yml.',
// Error Levels:
'critical' => 'Critical',
'high' => 'High',
'normal' => 'Normal',
'low' => 'Low',
// Plugins that generate errors:
'php_mess_detector' => 'PHP Mess Detector',
'php_code_sniffer' => 'PHP Code Sniffer',
'php_unit' => 'PHP Unit',
'php_cpd' => 'PHP Copy/Paste Detector',
'php_docblock_checker' => 'PHP Docblock Checker',
'behat' => 'Behat',
'technical_debt' => 'Technical Debt',
); );

View file

@ -115,6 +115,7 @@ PHPCI',
'default_branch' => 'Nombre de la rama por defecto', 'default_branch' => 'Nombre de la rama por defecto',
'allow_public_status' => '¿Activar página pública con el estado del proyecto?', 'allow_public_status' => '¿Activar página pública con el estado del proyecto?',
'archived' => 'Archivado', 'archived' => 'Archivado',
'archived_menu' => 'Archivado',
'save_project' => 'Guardar Proyecto', 'save_project' => 'Guardar Proyecto',
'error_mercurial' => 'La URL del repositorio de Mercurial debe comenzar con http:// or https://', 'error_mercurial' => 'La URL del repositorio de Mercurial debe comenzar con http:// or https://',

View file

@ -115,6 +115,7 @@ PHPCI',
'default_branch' => 'Nom de la branche par défaut', 'default_branch' => 'Nom de la branche par défaut',
'allow_public_status' => 'Activer la page de statut publique et l\'image pour ce projet&nbsp;?', 'allow_public_status' => 'Activer la page de statut publique et l\'image pour ce projet&nbsp;?',
'archived' => 'Archived', 'archived' => 'Archived',
'archived_menu' => 'Archived',
'save_project' => 'Enregistrer le projet', 'save_project' => 'Enregistrer le projet',
'error_mercurial' => 'Les URLs de dépôt Mercurial doivent commencer par http:// ou https://', 'error_mercurial' => 'Les URLs de dépôt Mercurial doivent commencer par http:// ou https://',

View file

@ -113,8 +113,9 @@ PHPCI',
(se non puoi aggiungere il file phpci.yml nel repository di questo progetto)', (se non puoi aggiungere il file phpci.yml nel repository di questo progetto)',
'default_branch' => 'Nome del branch di default', 'default_branch' => 'Nome del branch di default',
'allow_public_status' => 'Vuoi rendere pubblica la pagina dello stato e l\'immagine per questo progetto?', 'allow_public_status' => 'Vuoi rendere pubblica la pagina dello stato e l\'immagine per questo progetto?',
'save_project' => 'Salva il Progetto',
'archived' => 'Archived', 'archived' => 'Archived',
'archived_menu' => 'Archived',
'save_project' => 'Salva il Progetto',
'error_mercurial' => 'L\'URL del repository Mercurial URL deve iniziare con http:// o https://', 'error_mercurial' => 'L\'URL del repository Mercurial URL deve iniziare con http:// o https://',
'error_remote' => 'L\'URL del repository deve iniziare con git://, http:// o https://', 'error_remote' => 'L\'URL del repository deve iniziare con git://, http:// o https://',
@ -206,7 +207,7 @@ PHPCI',
'build_finished' => 'Build Terminata', 'build_finished' => 'Build Terminata',
'test_message' => 'Message', 'test_message' => 'Message',
'test_no_message' => 'No message', 'test_no_message' => 'No message',
'test_success' => 'Succesfull: %d', 'test_success' => 'Successful: %d',
'test_fail' => 'Failures: %d', 'test_fail' => 'Failures: %d',
'test_skipped' => 'Skipped: %d', 'test_skipped' => 'Skipped: %d',
'test_error' => 'Errors: %d', 'test_error' => 'Errors: %d',

View file

@ -115,6 +115,7 @@ van je gekozen source code hosting platform',
'default_branch' => 'Standaard branch naam', 'default_branch' => 'Standaard branch naam',
'allow_public_status' => 'Publieke statuspagina en afbeelding beschikbaar maken voor dit project?', 'allow_public_status' => 'Publieke statuspagina en afbeelding beschikbaar maken voor dit project?',
'archived' => 'Archived', 'archived' => 'Archived',
'archived_menu' => 'Archived',
'save_project' => 'Project opslaan', 'save_project' => 'Project opslaan',
'error_mercurial' => 'Mercurial repository URL dient te starten met http:// of https://', 'error_mercurial' => 'Mercurial repository URL dient te starten met http:// of https://',
@ -204,7 +205,7 @@ Services</a> sectie van je Bitbucket repository toegevoegd worden.',
'build_finished' => 'Build beëindigd', 'build_finished' => 'Build beëindigd',
'test_message' => 'Message', 'test_message' => 'Message',
'test_no_message' => 'No message', 'test_no_message' => 'No message',
'test_success' => 'Succesfull: %d', 'test_success' => 'Successful: %d',
'test_fail' => 'Failures: %d', 'test_fail' => 'Failures: %d',
'test_skipped' => 'Skipped: %d', 'test_skipped' => 'Skipped: %d',
'test_error' => 'Errors: %d', 'test_error' => 'Errors: %d',

View file

@ -116,6 +116,7 @@ od wybranego kodu źródłowego platformy hostingowej.',
'default_branch' => 'Domyślna nazwa gałęzi', 'default_branch' => 'Domyślna nazwa gałęzi',
'allow_public_status' => 'Włączyć status publiczny dla tego projektu?', 'allow_public_status' => 'Włączyć status publiczny dla tego projektu?',
'archived' => 'W archiwum', 'archived' => 'W archiwum',
'archived_menu' => 'W archiwum',
'save_project' => 'Zachowaj Projekt', 'save_project' => 'Zachowaj Projekt',
'error_mercurial' => 'URL repozytorium Mercurialnego powinno zaczynać się od http:// and https://', 'error_mercurial' => 'URL repozytorium Mercurialnego powinno zaczynać się od http:// and https://',
@ -129,7 +130,7 @@ od wybranego kodu źródłowego platformy hostingowej.',
'all_branches' => 'Wszystkie Gałęzie', 'all_branches' => 'Wszystkie Gałęzie',
'builds' => 'Budowania', 'builds' => 'Budowania',
'id' => 'ID', 'id' => 'ID',
'date' => 'Date', 'date' => 'Data',
'project' => 'Projekt', 'project' => 'Projekt',
'commit' => 'Commit', 'commit' => 'Commit',
'branch' => 'Gałąź', 'branch' => 'Gałąź',
@ -205,14 +206,14 @@ Services</a> repozytoria Bitbucket.',
'build_created' => 'Budowanie Stworzone', 'build_created' => 'Budowanie Stworzone',
'build_started' => 'Budowanie Rozpoczęte', 'build_started' => 'Budowanie Rozpoczęte',
'build_finished' => 'Budowanie Zakończone', 'build_finished' => 'Budowanie Zakończone',
'test_message' => 'Message', 'test_message' => 'Wiadomość',
'test_no_message' => 'No message', 'test_no_message' => 'Brak wiadomości',
'test_success' => 'Succesfull: %d', 'test_success' => 'Powodzenie: %d',
'test_fail' => 'Failures: %d', 'test_fail' => 'Niepowodzenia: %d',
'test_skipped' => 'Skipped: %d', 'test_skipped' => 'Pominęte: %d',
'test_error' => 'Errors: %d', 'test_error' => 'Błędy: %d',
'test_todo' => 'Todos: %d', 'test_todo' => 'Do zrobienia: %d',
'test_total' => '%d test(s)', 'test_total' => '%d test(ów)',
// Users // Users
'name' => 'Nazwa', 'name' => 'Nazwa',
@ -343,10 +344,10 @@ Przejrzyj powyższą listę błędów przed kontynuowaniem.',
'incorrect_format' => 'Niepoprawny format', 'incorrect_format' => 'Niepoprawny format',
// Create Build Command // Create Build Command
'create_build_project' => 'Create a build for a project', 'create_build_project' => 'Utwórz budowanie dla projektu',
'project_id_argument' => 'A project ID', 'project_id_argument' => 'ID projektu',
'commit_id_option' => 'Commit ID to build', 'commit_id_option' => 'ID Commita do budowania',
'branch_name_option' => 'Branch to build', 'branch_name_option' => 'Gałąź do budowania',
// Run Command // Run Command
'run_all_pending' => 'Uruchom wszystkie oczekujące budowy w PHPCI', 'run_all_pending' => 'Uruchom wszystkie oczekujące budowy w PHPCI',

View file

@ -42,7 +42,6 @@ PHPCI',
'password' => 'Пароль', 'password' => 'Пароль',
'log_in' => 'Войти', 'log_in' => 'Войти',
// Top Nav // Top Nav
'toggle_navigation' => 'Скрыть/показать панель навигации', 'toggle_navigation' => 'Скрыть/показать панель навигации',
'n_builds_pending' => '%d сборок ожидает', 'n_builds_pending' => '%d сборок ожидает',
@ -108,12 +107,13 @@ PHPCI',
'repo_name' => 'Репозиторий / Внешний URL / Локальный путь', 'repo_name' => 'Репозиторий / Внешний URL / Локальный путь',
'project_title' => 'Название проекта', 'project_title' => 'Название проекта',
'project_private_key' => 'Приватный ключ для доступа к репозиторию 'project_private_key' => 'Приватный ключ для доступа к репозиторию
(оставьте поле пустым для локального использования и/или анонимного доступа)', (оставьте поле пустым для локального использования и/или анонимного доступа)',
'build_config' => 'Конфигурация сборки проекта для PHPCI 'build_config' => 'Конфигурация сборки проекта для PHPCI
(если вы не добавили файл phpci.yml в репозиторий вашего проекта)', (если вы не добавили файл phpci.yml в репозиторий вашего проекта)',
'default_branch' => 'Ветка по умолчанию', 'default_branch' => 'Ветка по умолчанию',
'allow_public_status' => 'Разрешить публичный статус и изображение (статуса) для проекта', 'allow_public_status' => 'Разрешить публичный статус и изображение (статуса) для проекта',
'archived' => 'Запакован', 'archived' => 'Архивный',
'archived_menu' => 'Архив',
'save_project' => 'Сохранить проект', 'save_project' => 'Сохранить проект',
'error_mercurial' => 'URL репозитория Mercurial должен начинаться с http:// или https://', 'error_mercurial' => 'URL репозитория Mercurial должен начинаться с http:// или https://',
@ -139,13 +139,13 @@ PHPCI',
'webhooks' => 'Webhooks', 'webhooks' => 'Webhooks',
'webhooks_help_github' => 'Чтобы Автоматически собирать этот проект при публикации новых коммитов, добавьте URL ниже в качестве нового хука в разделе настроек <a href="https://github.com/%s/settings/hooks">Webhooks 'webhooks_help_github' => 'Чтобы Автоматически собирать этот проект при публикации новых коммитов, добавьте URL ниже в качестве нового хука в разделе настроек <a href="https://github.com/%s/settings/hooks">Webhooks
and Services</a> вашего GitHub репозитория.', and Services</a> вашего GitHub репозитория.',
'webhooks_help_gitlab' => 'Чтобы Автоматически собирать этот проект при публикации новых коммитов, добавьте URL ниже в качестве "WebHook URL" 'webhooks_help_gitlab' => 'Чтобы Автоматически собирать этот проект при публикации новых коммитов, добавьте URL ниже в качестве "WebHook URL"
в разделе "Web Hooks" вашего GitLab репозитория.', в разделе "Web Hooks" вашего GitLab репозитория.',
'webhooks_help_bitbucket' => 'Чтобы Автоматически собирать этот проект при публикации новых коммитов, добавьте URL ниже как "POST" сервис в разделе <a href="https://bitbucket.org/%s/admin/services"> 'webhooks_help_bitbucket' => 'Чтобы Автоматически собирать этот проект при публикации новых коммитов, добавьте URL ниже как "POST" сервис в разделе <a href="https://bitbucket.org/%s/admin/services">
Services</a> вашего Bitbucket репозитория.', Services</a> вашего Bitbucket репозитория.',
// View Build // View Build
'build_x_not_found' => 'Сборки с ID %d не существует.', 'build_x_not_found' => 'Сборки с ID %d не существует.',
@ -185,6 +185,11 @@ PHPCI',
'technical_debt' => 'Технические долги', 'technical_debt' => 'Технические долги',
'behat' => 'Behat', 'behat' => 'Behat',
'codeception_feature' => 'Свойство',
'codeception_suite' => 'Набор',
'codeception_time' => 'Время',
'codeception_synopsis' => 'Тестов выполнено: <strong>%1$d</strong> (за <strong>%2$f</strong> сек.). Провалов: <strong>%3$d</strong>.',
'file' => 'Файл', 'file' => 'Файл',
'line' => 'Строка', 'line' => 'Строка',
'class' => 'Класс', 'class' => 'Класс',
@ -202,12 +207,12 @@ PHPCI',
'build_finished' => 'Сборка окончена', 'build_finished' => 'Сборка окончена',
'test_message' => 'Message', 'test_message' => 'Message',
'test_no_message' => 'No message', 'test_no_message' => 'No message',
'test_success' => 'Succesfull: %d', 'test_success' => 'Успешно: %d',
'test_fail' => 'Failures: %d', 'test_fail' => 'Провалено: %d',
'test_skipped' => 'Skipped: %d', 'test_skipped' => 'Пропущено: %d',
'test_error' => 'Errors: %d', 'test_error' => 'Ошибок: %d',
'test_todo' => 'Todos: %d', 'test_todo' => 'Todo: %d',
'test_total' => '%d test(s)', 'test_total' => 'Тестов: %d',
// Users // Users
'name' => 'Имя', 'name' => 'Имя',
@ -282,6 +287,19 @@ PHPCI',
'search_packagist_for_more' => 'Искать на Packagist', 'search_packagist_for_more' => 'Искать на Packagist',
'search' => 'Искать &raquo;', 'search' => 'Искать &raquo;',
// Summary plugin
'build-summary' => 'Сводка',
'stage' => 'Этап',
'duration' => 'Продолжительность',
'plugin' => 'Плагин',
'stage_setup' => 'Установка',
'stage_test' => 'тестирование',
'stage_complete' => 'Завершение',
'stage_success' => 'Успешное завершение',
'stage_failure' => 'Провал',
'stage_broken' => 'Поломка',
'stage_fixed' => 'Исправление',
// Installer // Installer
'installation_url' => 'URL-адрес PHPCI для установки', 'installation_url' => 'URL-адрес PHPCI для установки',
'db_host' => 'Хост базы данных', 'db_host' => 'Хост базы данных',

View file

@ -113,7 +113,8 @@ PHPCI',
(якщо ви не додали файл phpci.yml до репозиторію вашого проекту)', (якщо ви не додали файл phpci.yml до репозиторію вашого проекту)',
'default_branch' => 'Назва гілки за замовчуванням', 'default_branch' => 'Назва гілки за замовчуванням',
'allow_public_status' => 'Увімкнути публічну сторінку статусу та зображення для цього проекта?', 'allow_public_status' => 'Увімкнути публічну сторінку статусу та зображення для цього проекта?',
'archived' => 'Archived', 'archived' => 'Архівний',
'archived_menu' => 'Архів',
'save_project' => 'Зберегти проект', 'save_project' => 'Зберегти проект',
'error_mercurial' => 'URL репозиторію Mercurial повинен починатись із http:// або https://', 'error_mercurial' => 'URL репозиторію Mercurial повинен починатись із http:// або https://',
@ -204,7 +205,7 @@ PHPCI',
'build_finished' => 'Збірка завершена', 'build_finished' => 'Збірка завершена',
'test_message' => 'Message', 'test_message' => 'Message',
'test_no_message' => 'No message', 'test_no_message' => 'No message',
'test_success' => 'Succesfull: %d', 'test_success' => 'Successful: %d',
'test_fail' => 'Failures: %d', 'test_fail' => 'Failures: %d',
'test_skipped' => 'Skipped: %d', 'test_skipped' => 'Skipped: %d',
'test_error' => 'Errors: %d', 'test_error' => 'Errors: %d',

View file

@ -67,7 +67,7 @@ class BuildLogger implements LoggerAwareInterface
} }
} }
/** /**
* Add a success-coloured message to the log. * Add a success-coloured message to the log.
* @param string * @param string
*/ */
@ -98,6 +98,17 @@ class BuildLogger implements LoggerAwareInterface
); );
} }
/**
* Add a debug message to the log.
* @param string
*/
public function logDebug($message)
{
if (defined('PHPCI_DEBUG_MODE') && PHPCI_DEBUG_MODE) {
$this->log("\033[0;33m" . $message . "\033[0m");
}
}
/** /**
* Sets a logger instance on the object * Sets a logger instance on the object
* *

View file

@ -20,7 +20,7 @@ class FixDatabaseColumns extends AbstractMigration
$build->changeColumn('project_id', 'integer', array('null' => false)); $build->changeColumn('project_id', 'integer', array('null' => false));
$build->changeColumn('commit_id', 'string', array('limit' => 50, 'null' => false)); $build->changeColumn('commit_id', 'string', array('limit' => 50, 'null' => false));
$build->changeColumn('status', 'integer', array('null' => false)); $build->changeColumn('status', 'integer', array('null' => false));
$build->changeColumn('log', 'text', array('null' => true, 'default' => '')); $build->changeColumn('log', 'text', array('null' => true));
$build->changeColumn('branch', 'string', array('limit' => 50, 'null' => false, 'default' => 'master')); $build->changeColumn('branch', 'string', array('limit' => 50, 'null' => false, 'default' => 'master'));
$build->changeColumn('created', 'datetime', array('null' => true)); $build->changeColumn('created', 'datetime', array('null' => true));
$build->changeColumn('started', 'datetime', array('null' => true)); $build->changeColumn('started', 'datetime', array('null' => true));

View file

@ -14,7 +14,6 @@ class FixColumnTypes extends AbstractMigration
$build = $this->table('build'); $build = $this->table('build');
$build->changeColumn('log', 'text', array( $build->changeColumn('log', 'text', array(
'null' => true, 'null' => true,
'default' => '',
'limit' => MysqlAdapter::TEXT_MEDIUM, 'limit' => MysqlAdapter::TEXT_MEDIUM,
)); ));

View file

@ -0,0 +1,29 @@
<?php
use Phinx\Migration\AbstractMigration;
class AddProjectGroups extends AbstractMigration
{
public function change()
{
$table = $this->table('project_group');
$table->addColumn('title', 'string', array('limit' => 100, 'null' => false));
$table->save();
$group = new \PHPCI\Model\ProjectGroup();
$group->setTitle('Projects');
/** @var \PHPCI\Model\ProjectGroup $group */
$group = \b8\Store\Factory::getStore('ProjectGroup')->save($group);
$table = $this->table('project');
$table->addColumn('group_id', 'integer', array(
'signed' => true,
'null' => false,
'default' => $group->getId(),
));
$table->addForeignKey('group_id', 'project_group', 'id', array('delete'=> 'RESTRICT', 'update' => 'CASCADE'));
$table->save();
}
}

View file

@ -0,0 +1,40 @@
<?php
use Phinx\Migration\AbstractMigration;
class RemoveUniqueNameIndex extends AbstractMigration
{
/**
* Change Method.
*
* Write your reversible migrations using this method.
*
* More information on writing migrations is available here:
* http://docs.phinx.org/en/latest/migrations.html#the-abstractmigration-class
*
* The following commands can be used in this method and Phinx will
* automatically reverse them when rolling back:
*
* createTable
* renameTable
* addColumn
* renameColumn
* addIndex
* addForeignKey
*
* Remember to call "create()" or "update()" and NOT "save()" when working
* with the Table class.
*/
public function change()
{
$user = $this->table('user');
if ($user->hasIndex('name', array('unique' => true))) {
$user->removeIndex('name', array('unique' => true));
$user->save();
}
$user->addIndex('name', array('unique' => false));
$user->save();
}
}

View file

@ -0,0 +1,24 @@
<?php
use Phinx\Migration\AbstractMigration;
use Phinx\Db\Adapter\MysqlAdapter;
class ErrorsTable extends AbstractMigration
{
public function change()
{
$table = $this->table('build_error');
$table->addColumn('build_id', 'integer', array('signed' => true));
$table->addColumn('plugin', 'string', array('limit' => 100));
$table->addColumn('file', 'string', array('limit' => 250, 'null' => true));
$table->addColumn('line_start', 'integer', array('signed' => false, 'null' => true));
$table->addColumn('line_end', 'integer', array('signed' => false, 'null' => true));
$table->addColumn('severity', 'integer', array('signed' => false, 'limit' => MysqlAdapter::INT_TINY));
$table->addColumn('message', 'string', array('limit' => 250));
$table->addColumn('created_date', 'datetime');
$table->addIndex(array('build_id', 'created_date'), array('unique' => false));
$table->addForeignKey('build_id', 'build', 'id', array('delete'=> 'CASCADE', 'update' => 'CASCADE'));
$table->save();
}
}

View file

@ -0,0 +1,183 @@
<?php
use Phinx\Migration\AbstractMigration;
use PHPCI\Model\BuildMeta;
use PHPCI\Model\BuildError;
class ConvertErrors extends AbstractMigration
{
/**
* @var \PHPCI\Store\BuildMetaStore
*/
protected $metaStore;
/**
* @var \PHPCI\Store\BuildErrorStore
*/
protected $errorStore;
public function change()
{
$count = 100;
$this->metaStore = \b8\Store\Factory::getStore('BuildMeta');
$this->errorStore = \b8\Store\Factory::getStore('BuildError');
while ($count == 100) {
$data = $this->metaStore->getErrorsForUpgrade(100);
$count = count($data);
/** @var \PHPCI\Model\BuildMeta $meta */
foreach ($data as $meta) {
try {
switch ($meta->getMetaKey()) {
case 'phpmd-data':
$this->processPhpMdMeta($meta);
break;
case 'phpcs-data':
$this->processPhpCsMeta($meta);
break;
case 'phpdoccheck-data':
$this->processPhpDocCheckMeta($meta);
break;
case 'phpcpd-data':
$this->processPhpCpdMeta($meta);
break;
case 'technicaldebt-data':
$this->processTechnicalDebtMeta($meta);
break;
}
} catch (\Exception $ex) {}
$this->metaStore->delete($meta);
}
}
}
protected function processPhpMdMeta(BuildMeta $meta)
{
$data = json_decode($meta->getMetaValue(), true);
if (is_array($data) && count($data)) {
foreach ($data as $error) {
$buildError = new BuildError();
$buildError->setBuildId($meta->getBuildId());
$buildError->setPlugin('php_mess_detector');
$buildError->setCreatedDate(new \DateTime());
$buildError->setFile($error['file']);
$buildError->setLineStart($error['line_start']);
$buildError->setLineEnd($error['line_end']);
$buildError->setSeverity(BuildError::SEVERITY_HIGH);
$buildError->setMessage($error['message']);
$this->errorStore->save($buildError);
}
}
}
protected function processPhpCsMeta(BuildMeta $meta)
{
$data = json_decode($meta->getMetaValue(), true);
if (is_array($data) && count($data)) {
foreach ($data as $error) {
$buildError = new BuildError();
$buildError->setBuildId($meta->getBuildId());
$buildError->setPlugin('php_code_sniffer');
$buildError->setCreatedDate(new \DateTime());
$buildError->setFile($error['file']);
$buildError->setLineStart($error['line']);
$buildError->setLineEnd($error['line']);
$buildError->setMessage($error['message']);
switch ($error['type']) {
case 'ERROR':
$buildError->setSeverity(BuildError::SEVERITY_HIGH);
break;
case 'WARNING':
$buildError->setSeverity(BuildError::SEVERITY_LOW);
break;
}
$this->errorStore->save($buildError);
}
}
}
protected function processPhpDocCheckMeta(BuildMeta $meta)
{
$data = json_decode($meta->getMetaValue(), true);
if (is_array($data) && count($data)) {
foreach ($data as $error) {
$buildError = new BuildError();
$buildError->setBuildId($meta->getBuildId());
$buildError->setPlugin('php_docblock_checker');
$buildError->setCreatedDate(new \DateTime());
$buildError->setFile($error['file']);
$buildError->setLineStart($error['line']);
$buildError->setLineEnd($error['line']);
switch ($error['type']) {
case 'method':
$buildError->setMessage($error['class'] . '::' . $error['method'] . ' is missing a docblock.');
$buildError->setSeverity(BuildError::SEVERITY_NORMAL);
break;
case 'class':
$buildError->setMessage('Class ' . $error['class'] . ' is missing a docblock.');
$buildError->setSeverity(BuildError::SEVERITY_LOW);
break;
}
$this->errorStore->save($buildError);
}
}
}
protected function processPhpCpdMeta(BuildMeta $meta)
{
$data = json_decode($meta->getMetaValue(), true);
if (is_array($data) && count($data)) {
foreach ($data as $error) {
$buildError = new BuildError();
$buildError->setBuildId($meta->getBuildId());
$buildError->setPlugin('php_cpd');
$buildError->setCreatedDate(new \DateTime());
$buildError->setFile($error['file']);
$buildError->setLineStart($error['line_start']);
$buildError->setLineEnd($error['line_end']);
$buildError->setSeverity(BuildError::SEVERITY_NORMAL);
$buildError->setMessage('Copy and paste detected.');
$this->errorStore->save($buildError);
}
}
}
protected function processTechnicalDebtMeta(BuildMeta $meta)
{
$data = json_decode($meta->getMetaValue(), true);
if (is_array($data) && count($data)) {
foreach ($data as $error) {
$buildError = new BuildError();
$buildError->setBuildId($meta->getBuildId());
$buildError->setPlugin('technical_debt');
$buildError->setCreatedDate(new \DateTime());
$buildError->setFile($error['file']);
$buildError->setLineStart($error['line']);
$buildError->setSeverity(BuildError::SEVERITY_NORMAL);
$buildError->setMessage($error['message']);
$this->errorStore->save($buildError);
}
}
}
}

View file

@ -118,7 +118,7 @@ class BuildBase extends Model
'default' => null, 'default' => null,
), ),
'log' => array( 'log' => array(
'type' => 'text', 'type' => 'mediumtext',
'nullable' => true, 'nullable' => true,
'default' => null, 'default' => null,
), ),
@ -621,6 +621,18 @@ class BuildBase extends Model
return $this->setProjectId($value->getId()); return $this->setProjectId($value->getId());
} }
/**
* Get BuildError models by BuildId for this Build.
*
* @uses \PHPCI\Store\BuildErrorStore::getByBuildId()
* @uses \PHPCI\Model\BuildError
* @return \PHPCI\Model\BuildError[]
*/
public function getBuildBuildErrors()
{
return Factory::getStore('BuildError', 'PHPCI')->getByBuildId($this->getId());
}
/** /**
* Get BuildMeta models by BuildId for this Build. * Get BuildMeta models by BuildId for this Build.
* *

View file

@ -0,0 +1,503 @@
<?php
/**
* BuildError base model for table: build_error
*/
namespace PHPCI\Model\Base;
use PHPCI\Model;
use b8\Store\Factory;
/**
* BuildError Base Model
*/
class BuildErrorBase extends Model
{
/**
* @var array
*/
public static $sleepable = array();
/**
* @var string
*/
protected $tableName = 'build_error';
/**
* @var string
*/
protected $modelName = 'BuildError';
/**
* @var array
*/
protected $data = array(
'id' => null,
'build_id' => null,
'plugin' => null,
'file' => null,
'line_start' => null,
'line_end' => null,
'severity' => null,
'message' => null,
'created_date' => null,
);
/**
* @var array
*/
protected $getters = array(
// Direct property getters:
'id' => 'getId',
'build_id' => 'getBuildId',
'plugin' => 'getPlugin',
'file' => 'getFile',
'line_start' => 'getLineStart',
'line_end' => 'getLineEnd',
'severity' => 'getSeverity',
'message' => 'getMessage',
'created_date' => 'getCreatedDate',
// Foreign key getters:
'Build' => 'getBuild',
);
/**
* @var array
*/
protected $setters = array(
// Direct property setters:
'id' => 'setId',
'build_id' => 'setBuildId',
'plugin' => 'setPlugin',
'file' => 'setFile',
'line_start' => 'setLineStart',
'line_end' => 'setLineEnd',
'severity' => 'setSeverity',
'message' => 'setMessage',
'created_date' => 'setCreatedDate',
// Foreign key setters:
'Build' => 'setBuild',
);
/**
* @var array
*/
public $columns = array(
'id' => array(
'type' => 'int',
'length' => 11,
'primary_key' => true,
'auto_increment' => true,
'default' => null,
),
'build_id' => array(
'type' => 'int',
'length' => 11,
'default' => null,
),
'plugin' => array(
'type' => 'varchar',
'length' => 100,
'default' => null,
),
'file' => array(
'type' => 'varchar',
'length' => 250,
'nullable' => true,
'default' => null,
),
'line_start' => array(
'type' => 'int',
'length' => 11,
'nullable' => true,
'default' => null,
),
'line_end' => array(
'type' => 'int',
'length' => 11,
'nullable' => true,
'default' => null,
),
'severity' => array(
'type' => 'tinyint',
'length' => 3,
'default' => null,
),
'message' => array(
'type' => 'varchar',
'length' => 250,
'default' => null,
),
'created_date' => array(
'type' => 'datetime',
'default' => null,
),
);
/**
* @var array
*/
public $indexes = array(
'PRIMARY' => array('unique' => true, 'columns' => 'id'),
'build_id' => array('columns' => 'build_id, created_date'),
);
/**
* @var array
*/
public $foreignKeys = array(
'build_error_ibfk_1' => array(
'local_col' => 'build_id',
'update' => 'CASCADE',
'delete' => 'CASCADE',
'table' => 'build',
'col' => 'id'
),
);
/**
* Get the value of Id / id.
*
* @return int
*/
public function getId()
{
$rtn = $this->data['id'];
return $rtn;
}
/**
* Get the value of BuildId / build_id.
*
* @return int
*/
public function getBuildId()
{
$rtn = $this->data['build_id'];
return $rtn;
}
/**
* Get the value of Plugin / plugin.
*
* @return string
*/
public function getPlugin()
{
$rtn = $this->data['plugin'];
return $rtn;
}
/**
* Get the value of File / file.
*
* @return string
*/
public function getFile()
{
$rtn = $this->data['file'];
return $rtn;
}
/**
* Get the value of LineStart / line_start.
*
* @return int
*/
public function getLineStart()
{
$rtn = $this->data['line_start'];
return $rtn;
}
/**
* Get the value of LineEnd / line_end.
*
* @return int
*/
public function getLineEnd()
{
$rtn = $this->data['line_end'];
return $rtn;
}
/**
* Get the value of Severity / severity.
*
* @return int
*/
public function getSeverity()
{
$rtn = $this->data['severity'];
return $rtn;
}
/**
* Get the value of Message / message.
*
* @return string
*/
public function getMessage()
{
$rtn = $this->data['message'];
return $rtn;
}
/**
* Get the value of CreatedDate / created_date.
*
* @return \DateTime
*/
public function getCreatedDate()
{
$rtn = $this->data['created_date'];
if (!empty($rtn)) {
$rtn = new \DateTime($rtn);
}
return $rtn;
}
/**
* Set the value of Id / id.
*
* Must not be null.
* @param $value int
*/
public function setId($value)
{
$this->_validateNotNull('Id', $value);
$this->_validateInt('Id', $value);
if ($this->data['id'] === $value) {
return;
}
$this->data['id'] = $value;
$this->_setModified('id');
}
/**
* Set the value of BuildId / build_id.
*
* Must not be null.
* @param $value int
*/
public function setBuildId($value)
{
$this->_validateNotNull('BuildId', $value);
$this->_validateInt('BuildId', $value);
if ($this->data['build_id'] === $value) {
return;
}
$this->data['build_id'] = $value;
$this->_setModified('build_id');
}
/**
* Set the value of Plugin / plugin.
*
* Must not be null.
* @param $value string
*/
public function setPlugin($value)
{
$this->_validateNotNull('Plugin', $value);
$this->_validateString('Plugin', $value);
if ($this->data['plugin'] === $value) {
return;
}
$this->data['plugin'] = $value;
$this->_setModified('plugin');
}
/**
* Set the value of File / file.
*
* @param $value string
*/
public function setFile($value)
{
$this->_validateString('File', $value);
if ($this->data['file'] === $value) {
return;
}
$this->data['file'] = $value;
$this->_setModified('file');
}
/**
* Set the value of LineStart / line_start.
*
* @param $value int
*/
public function setLineStart($value)
{
$this->_validateInt('LineStart', $value);
if ($this->data['line_start'] === $value) {
return;
}
$this->data['line_start'] = $value;
$this->_setModified('line_start');
}
/**
* Set the value of LineEnd / line_end.
*
* @param $value int
*/
public function setLineEnd($value)
{
$this->_validateInt('LineEnd', $value);
if ($this->data['line_end'] === $value) {
return;
}
$this->data['line_end'] = $value;
$this->_setModified('line_end');
}
/**
* Set the value of Severity / severity.
*
* Must not be null.
* @param $value int
*/
public function setSeverity($value)
{
$this->_validateNotNull('Severity', $value);
$this->_validateInt('Severity', $value);
if ($this->data['severity'] === $value) {
return;
}
$this->data['severity'] = $value;
$this->_setModified('severity');
}
/**
* Set the value of Message / message.
*
* Must not be null.
* @param $value string
*/
public function setMessage($value)
{
$this->_validateNotNull('Message', $value);
$this->_validateString('Message', $value);
if ($this->data['message'] === $value) {
return;
}
$this->data['message'] = $value;
$this->_setModified('message');
}
/**
* Set the value of CreatedDate / created_date.
*
* Must not be null.
* @param $value \DateTime
*/
public function setCreatedDate($value)
{
$this->_validateNotNull('CreatedDate', $value);
$this->_validateDate('CreatedDate', $value);
if ($this->data['created_date'] === $value) {
return;
}
$this->data['created_date'] = $value;
$this->_setModified('created_date');
}
/**
* Get the Build model for this BuildError by Id.
*
* @uses \PHPCI\Store\BuildStore::getById()
* @uses \PHPCI\Model\Build
* @return \PHPCI\Model\Build
*/
public function getBuild()
{
$key = $this->getBuildId();
if (empty($key)) {
return null;
}
$cacheKey = 'Cache.Build.' . $key;
$rtn = $this->cache->get($cacheKey, null);
if (empty($rtn)) {
$rtn = Factory::getStore('Build', 'PHPCI')->getById($key);
$this->cache->set($cacheKey, $rtn);
}
return $rtn;
}
/**
* Set Build - Accepts an ID, an array representing a Build or a Build model.
*
* @param $value mixed
*/
public function setBuild($value)
{
// Is this an instance of Build?
if ($value instanceof \PHPCI\Model\Build) {
return $this->setBuildObject($value);
}
// Is this an array representing a Build item?
if (is_array($value) && !empty($value['id'])) {
return $this->setBuildId($value['id']);
}
// Is this a scalar value representing the ID of this foreign key?
return $this->setBuildId($value);
}
/**
* Set Build - Accepts a Build model.
*
* @param $value \PHPCI\Model\Build
*/
public function setBuildObject(\PHPCI\Model\Build $value)
{
return $this->setBuildId($value->getId());
}
}

View file

@ -99,7 +99,7 @@ class BuildMetaBase extends Model
'default' => null, 'default' => null,
), ),
'meta_value' => array( 'meta_value' => array(
'type' => 'text', 'type' => 'mediumtext',
'default' => null, 'default' => null,
), ),
); );

View file

@ -45,6 +45,7 @@ class ProjectBase extends Model
'ssh_public_key' => null, 'ssh_public_key' => null,
'allow_public_status' => null, 'allow_public_status' => null,
'archived' => null, 'archived' => null,
'group_id' => null,
); );
/** /**
@ -64,8 +65,10 @@ class ProjectBase extends Model
'ssh_public_key' => 'getSshPublicKey', 'ssh_public_key' => 'getSshPublicKey',
'allow_public_status' => 'getAllowPublicStatus', 'allow_public_status' => 'getAllowPublicStatus',
'archived' => 'getArchived', 'archived' => 'getArchived',
'group_id' => 'getGroupId',
// Foreign key getters: // Foreign key getters:
'Group' => 'getGroup',
); );
/** /**
@ -85,8 +88,10 @@ class ProjectBase extends Model
'ssh_public_key' => 'setSshPublicKey', 'ssh_public_key' => 'setSshPublicKey',
'allow_public_status' => 'setAllowPublicStatus', 'allow_public_status' => 'setAllowPublicStatus',
'archived' => 'setArchived', 'archived' => 'setArchived',
'group_id' => 'setGroupId',
// Foreign key setters: // Foreign key setters:
'Group' => 'setGroup',
); );
/** /**
@ -153,10 +158,14 @@ class ProjectBase extends Model
), ),
'archived' => array( 'archived' => array(
'type' => 'tinyint', 'type' => 'tinyint',
'length' => 4, 'length' => 1,
'nullable' => true,
'default' => null, 'default' => null,
), ),
'group_id' => array(
'type' => 'int',
'length' => 11,
'default' => 1,
),
); );
/** /**
@ -165,12 +174,20 @@ class ProjectBase extends Model
public $indexes = array( public $indexes = array(
'PRIMARY' => array('unique' => true, 'columns' => 'id'), 'PRIMARY' => array('unique' => true, 'columns' => 'id'),
'idx_project_title' => array('columns' => 'title'), 'idx_project_title' => array('columns' => 'title'),
'group_id' => array('columns' => 'group_id'),
); );
/** /**
* @var array * @var array
*/ */
public $foreignKeys = array( public $foreignKeys = array(
'project_ibfk_1' => array(
'local_col' => 'group_id',
'update' => 'CASCADE',
'delete' => '',
'table' => 'project_group',
'col' => 'id'
),
); );
/** /**
@ -317,6 +334,18 @@ class ProjectBase extends Model
return $rtn; return $rtn;
} }
/**
* Get the value of GroupId / group_id.
*
* @return int
*/
public function getGroupId()
{
$rtn = $this->data['group_id'];
return $rtn;
}
/** /**
* Set the value of Id / id. * Set the value of Id / id.
* *
@ -530,10 +559,12 @@ class ProjectBase extends Model
/** /**
* Set the value of Archived / archived. * Set the value of Archived / archived.
* *
* Must not be null.
* @param $value int * @param $value int
*/ */
public function setArchived($value) public function setArchived($value)
{ {
$this->_validateNotNull('Archived', $value);
$this->_validateInt('Archived', $value); $this->_validateInt('Archived', $value);
if ($this->data['archived'] === $value) { if ($this->data['archived'] === $value) {
@ -545,6 +576,83 @@ class ProjectBase extends Model
$this->_setModified('archived'); $this->_setModified('archived');
} }
/**
* Set the value of GroupId / group_id.
*
* Must not be null.
* @param $value int
*/
public function setGroupId($value)
{
$this->_validateNotNull('GroupId', $value);
$this->_validateInt('GroupId', $value);
if ($this->data['group_id'] === $value) {
return;
}
$this->data['group_id'] = $value;
$this->_setModified('group_id');
}
/**
* Get the ProjectGroup model for this Project by Id.
*
* @uses \PHPCI\Store\ProjectGroupStore::getById()
* @uses \PHPCI\Model\ProjectGroup
* @return \PHPCI\Model\ProjectGroup
*/
public function getGroup()
{
$key = $this->getGroupId();
if (empty($key)) {
return null;
}
$cacheKey = 'Cache.ProjectGroup.' . $key;
$rtn = $this->cache->get($cacheKey, null);
if (empty($rtn)) {
$rtn = Factory::getStore('ProjectGroup', 'PHPCI')->getById($key);
$this->cache->set($cacheKey, $rtn);
}
return $rtn;
}
/**
* Set Group - Accepts an ID, an array representing a ProjectGroup or a ProjectGroup model.
*
* @param $value mixed
*/
public function setGroup($value)
{
// Is this an instance of ProjectGroup?
if ($value instanceof \PHPCI\Model\ProjectGroup) {
return $this->setGroupObject($value);
}
// Is this an array representing a ProjectGroup item?
if (is_array($value) && !empty($value['id'])) {
return $this->setGroupId($value['id']);
}
// Is this a scalar value representing the ID of this foreign key?
return $this->setGroupId($value);
}
/**
* Set Group - Accepts a ProjectGroup model.
*
* @param $value \PHPCI\Model\ProjectGroup
*/
public function setGroupObject(\PHPCI\Model\ProjectGroup $value)
{
return $this->setGroupId($value->getId());
}
/** /**
* Get Build models by ProjectId for this Project. * Get Build models by ProjectId for this Project.
* *

View file

@ -0,0 +1,168 @@
<?php
/**
* ProjectGroup base model for table: project_group
*/
namespace PHPCI\Model\Base;
use PHPCI\Model;
use b8\Store\Factory;
/**
* ProjectGroup Base Model
*/
class ProjectGroupBase extends Model
{
/**
* @var array
*/
public static $sleepable = array();
/**
* @var string
*/
protected $tableName = 'project_group';
/**
* @var string
*/
protected $modelName = 'ProjectGroup';
/**
* @var array
*/
protected $data = array(
'id' => null,
'title' => null,
);
/**
* @var array
*/
protected $getters = array(
// Direct property getters:
'id' => 'getId',
'title' => 'getTitle',
// Foreign key getters:
);
/**
* @var array
*/
protected $setters = array(
// Direct property setters:
'id' => 'setId',
'title' => 'setTitle',
// Foreign key setters:
);
/**
* @var array
*/
public $columns = array(
'id' => array(
'type' => 'int',
'length' => 11,
'primary_key' => true,
'auto_increment' => true,
'default' => null,
),
'title' => array(
'type' => 'varchar',
'length' => 100,
'default' => null,
),
);
/**
* @var array
*/
public $indexes = array(
'PRIMARY' => array('unique' => true, 'columns' => 'id'),
);
/**
* @var array
*/
public $foreignKeys = array(
);
/**
* Get the value of Id / id.
*
* @return int
*/
public function getId()
{
$rtn = $this->data['id'];
return $rtn;
}
/**
* Get the value of Title / title.
*
* @return string
*/
public function getTitle()
{
$rtn = $this->data['title'];
return $rtn;
}
/**
* Set the value of Id / id.
*
* Must not be null.
* @param $value int
*/
public function setId($value)
{
$this->_validateNotNull('Id', $value);
$this->_validateInt('Id', $value);
if ($this->data['id'] === $value) {
return;
}
$this->data['id'] = $value;
$this->_setModified('id');
}
/**
* Set the value of Title / title.
*
* Must not be null.
* @param $value string
*/
public function setTitle($value)
{
$this->_validateNotNull('Title', $value);
$this->_validateString('Title', $value);
if ($this->data['title'] === $value) {
return;
}
$this->data['title'] = $value;
$this->_setModified('title');
}
/**
* Get Project models by GroupId for this ProjectGroup.
*
* @uses \PHPCI\Store\ProjectStore::getByGroupId()
* @uses \PHPCI\Model\Project
* @return \PHPCI\Model\Project[]
*/
public function getGroupProjects()
{
return Factory::getStore('Project', 'PHPCI')->getByGroupId($this->getId());
}
}

View file

@ -106,6 +106,8 @@ class UserBase extends Model
public $indexes = array( public $indexes = array(
'PRIMARY' => array('unique' => true, 'columns' => 'id'), 'PRIMARY' => array('unique' => true, 'columns' => 'id'),
'idx_email' => array('unique' => true, 'columns' => 'email'), 'idx_email' => array('unique' => true, 'columns' => 'email'),
'email' => array('unique' => true, 'columns' => 'email'),
'name' => array('columns' => 'name'),
); );
/** /**

View file

@ -28,7 +28,7 @@ class Build extends BuildBase
const STATUS_SUCCESS = 2; const STATUS_SUCCESS = 2;
const STATUS_FAILED = 3; const STATUS_FAILED = 3;
public $currentBuildPath = null; public $currentBuildPath;
/** /**
* Get link to commit from another source (i.e. Github) * Get link to commit from another source (i.e. Github)
@ -99,16 +99,21 @@ class Build extends BuildBase
{ {
$build_config = null; $build_config = null;
// Try phpci.yml first:
if (is_file($buildPath . '/phpci.yml')) {
$build_config = file_get_contents($buildPath . '/phpci.yml');
}
// Try getting the project build config from the database: // Try getting the project build config from the database:
if (empty($build_config)) { if (empty($build_config)) {
$build_config = $this->getProject()->getBuildConfig(); $build_config = $this->getProject()->getBuildConfig();
} }
// Try .phpci.yml
if (is_file($buildPath . '/.phpci.yml')) {
$build_config = file_get_contents($buildPath . '/.phpci.yml');
}
// Try phpci.yml first:
if (empty($build_config) && is_file($buildPath . '/phpci.yml')) {
$build_config = file_get_contents($buildPath . '/phpci.yml');
}
// Fall back to zero config plugins: // Fall back to zero config plugins:
if (empty($build_config)) { if (empty($build_config)) {
$build_config = $this->getZeroConfigPlugins($builder); $build_config = $this->getZeroConfigPlugins($builder);
@ -208,14 +213,36 @@ class Build extends BuildBase
/** /**
* Allows specific build types (e.g. Github) to report violations back to their respective services. * Allows specific build types (e.g. Github) to report violations back to their respective services.
* @param Builder $builder * @param Builder $builder
* @param $file * @param $plugin
* @param $line
* @param $message * @param $message
* @return mixed * @param int $severity
* @param null $file
* @param null $lineStart
* @param null $lineEnd
* @return BuildError
*/ */
public function reportError(Builder $builder, $file, $line, $message) public function reportError(
{ Builder $builder,
return array($builder, $file, $line, $message); $plugin,
$message,
$severity = BuildError::SEVERITY_NORMAL,
$file = null,
$lineStart = null,
$lineEnd = null
) {
unset($builder);
$error = new BuildError();
$error->setBuild($this);
$error->setCreatedDate(new \DateTime());
$error->setPlugin($plugin);
$error->setMessage($message);
$error->setSeverity($severity);
$error->setFile($file);
$error->setLineStart($lineStart);
$error->setLineEnd($lineEnd);
return Factory::getStore('BuildError')->save($error);
} }
/** /**
@ -228,7 +255,13 @@ class Build extends BuildBase
if (!$this->getId()) { if (!$this->getId()) {
return null; return null;
} }
return PHPCI_BUILD_ROOT_DIR . $this->getId();
if (empty($this->currentBuildPath)) {
$buildDirectory = $this->getId() . '_' . substr(md5(microtime(true)), 0, 5);
$this->currentBuildPath = PHPCI_BUILD_ROOT_DIR . $buildDirectory . DIRECTORY_SEPARATOR;
}
return $this->currentBuildPath;
} }
/** /**
@ -244,4 +277,25 @@ class Build extends BuildBase
exec(sprintf(IS_WIN ? 'rmdir /S /Q "%s"' : 'rm -Rf "%s"', $buildPath)); exec(sprintf(IS_WIN ? 'rmdir /S /Q "%s"' : 'rm -Rf "%s"', $buildPath));
} }
/**
* Get the number of seconds a build has been running for.
* @return int
*/
public function getDuration()
{
$start = $this->getStarted();
if (empty($start)) {
return 0;
}
$end = $this->getFinished();
if (empty($end)) {
$end = new \DateTime();
}
return $end->getTimestamp() - $start->getTimestamp();
}
} }

View file

@ -45,39 +45,52 @@ class GithubBuild extends RemoteGitBuild
{ {
$token = \b8\Config::getInstance()->get('phpci.github.token'); $token = \b8\Config::getInstance()->get('phpci.github.token');
if (empty($token)) { if (empty($token) || empty($this->data['id'])) {
return; return;
} }
$project = $this->getProject(); $project = $this->getProject();
if (empty($project)) {
return;
}
$url = 'https://api.github.com/repos/'.$project->getReference().'/statuses/'.$this->getCommitId(); $url = 'https://api.github.com/repos/'.$project->getReference().'/statuses/'.$this->getCommitId();
$http = new \b8\HttpClient(); $http = new \b8\HttpClient();
switch($this->getStatus()) switch ($this->getStatus()) {
{
case 0: case 0:
case 1: case 1:
$status = 'pending'; $status = 'pending';
$description = 'PHPCI build running.';
break; break;
case 2: case 2:
$status = 'success'; $status = 'success';
$description = 'PHPCI build passed.';
break; break;
case 3: case 3:
$status = 'failure'; $status = 'failure';
$description = 'PHPCI build failed.';
break; break;
default: default:
$status = 'error'; $status = 'error';
$description = 'PHPCI build failed to complete.';
break; break;
} }
$phpciUrl = \b8\Config::getInstance()->get('phpci.url'); $phpciUrl = \b8\Config::getInstance()->get('phpci.url');
$params = array( 'state' => $status,
'target_url' => $phpciUrl . '/build/view/' . $this->getId()); $params = array(
'state' => $status,
'target_url' => $phpciUrl . '/build/view/' . $this->getId(),
'description' => $description,
'context' => 'PHPCI',
);
$headers = array( $headers = array(
'Authorization: token ' . $token, 'Authorization: token ' . $token,
'Content-Type: application/x-www-form-urlencoded' 'Content-Type: application/x-www-form-urlencoded'
); );
$http->setHeaders($headers); $http->setHeaders($headers);
$http->request('POST', $url, json_encode($params)); $http->request('POST', $url, json_encode($params));
@ -105,10 +118,14 @@ class GithubBuild extends RemoteGitBuild
{ {
$rtn = parent::getCommitMessage($this->data['commit_message']); $rtn = parent::getCommitMessage($this->data['commit_message']);
$reference = $this->getProject()->getReference(); $project = $this->getProject();
$commitLink = '<a target="_blank" href="https://github.com/' . $reference . '/issues/$1">#$1</a>';
$rtn = preg_replace('/\#([0-9]+)/', $commitLink, $rtn); if (!is_null($project)) {
$rtn = preg_replace('/\@([a-zA-Z0-9_]+)/', '<a target="_blank" href="https://github.com/$1">@$1</a>', $rtn); $reference = $project->getReference();
$commitLink = '<a target="_blank" href="https://github.com/' . $reference . '/issues/$1">#$1</a>';
$rtn = preg_replace('/\#([0-9]+)/', $commitLink, $rtn);
$rtn = preg_replace('/\@([a-zA-Z0-9_]+)/', '<a target="_blank" href="https://github.com/$1">@$1</a>', $rtn);
}
return $rtn; return $rtn;
} }
@ -134,7 +151,7 @@ class GithubBuild extends RemoteGitBuild
$link = 'https://github.com/' . $reference . '/'; $link = 'https://github.com/' . $reference . '/';
$link .= 'blob/' . $branch . '/'; $link .= 'blob/' . $branch . '/';
$link .= '{FILE}'; $link .= '{FILE}';
$link .= '#L{LINE}'; $link .= '#L{LINE}-L{LINE_END}';
return $link; return $link;
} }
@ -173,9 +190,16 @@ class GithubBuild extends RemoteGitBuild
/** /**
* @inheritDoc * @inheritDoc
*/ */
public function reportError(Builder $builder, $file, $line, $message) public function reportError(
{ Builder $builder,
$diffLineNumber = $this->getDiffLineNumber($builder, $file, $line); $plugin,
$message,
$severity = BuildError::SEVERITY_NORMAL,
$file = null,
$lineStart = null,
$lineEnd = null
) {
$diffLineNumber = $this->getDiffLineNumber($builder, $file, $lineStart);
if (!is_null($diffLineNumber)) { if (!is_null($diffLineNumber)) {
$helper = new Github(); $helper = new Github();
@ -190,6 +214,8 @@ class GithubBuild extends RemoteGitBuild
$helper->createCommitComment($repo, $commit, $file, $diffLineNumber, $message); $helper->createCommitComment($repo, $commit, $file, $diffLineNumber, $message);
} }
} }
return parent::reportError($builder, $plugin, $message, $severity, $file, $lineStart, $lineEnd);
} }
/** /**
@ -201,6 +227,8 @@ class GithubBuild extends RemoteGitBuild
*/ */
protected function getDiffLineNumber(Builder $builder, $file, $line) protected function getDiffLineNumber(Builder $builder, $file, $line)
{ {
$line = (integer)$line;
$builder->logExecOutput(false); $builder->logExecOutput(false);
$prNumber = $this->getExtra('pull_request_number'); $prNumber = $this->getExtra('pull_request_number');
@ -221,6 +249,6 @@ class GithubBuild extends RemoteGitBuild
$helper = new Diff(); $helper = new Diff();
$lines = $helper->getLinePositions($diff); $lines = $helper->getLinePositions($diff);
return $lines[$line]; return isset($lines[$line]) ? $lines[$line] : null;
} }
} }

View file

@ -0,0 +1,63 @@
<?php
/**
* BuildError model for table: build_error
*/
namespace PHPCI\Model;
use PHPCI\Model\Base\BuildErrorBase;
/**
* BuildError Model
* @uses PHPCI\Model\Base\BuildErrorBase
*/
class BuildError extends BuildErrorBase
{
const SEVERITY_CRITICAL = 0;
const SEVERITY_HIGH = 1;
const SEVERITY_NORMAL = 2;
const SEVERITY_LOW = 3;
/**
* Get the language string key for this error's severity level.
* @return string
*/
public function getSeverityString()
{
switch ($this->getSeverity()) {
case self::SEVERITY_CRITICAL:
return 'critical';
case self::SEVERITY_HIGH:
return 'high';
case self::SEVERITY_NORMAL:
return 'normal';
case self::SEVERITY_LOW:
return 'low';
}
}
/**
* Get the class to apply to HTML elements representing this error.
* @return string
*/
public function getSeverityClass()
{
switch ($this->getSeverity()) {
case self::SEVERITY_CRITICAL:
return 'danger';
case self::SEVERITY_HIGH:
return 'warning';
case self::SEVERITY_NORMAL:
return 'info';
case self::SEVERITY_LOW:
return 'default';
}
}
}

View file

@ -50,6 +50,29 @@ class Project extends ProjectBase
return null; return null;
} }
/**
* Return the previous build from a specific branch, for this project.
* @param string $branch
* @return mixed|null
*/
public function getPreviousBuild($branch = 'master')
{
$criteria = array('branch' => $branch, 'project_id' => $this->getId());
$order = array('id' => 'DESC');
$builds = Store\Factory::getStore('Build')->getWhere($criteria, 1, 1, array(), $order);
if (is_array($builds['items']) && count($builds['items'])) {
$previous = array_shift($builds['items']);
if (isset($previous) && $previous instanceof Build) {
return $previous;
}
}
return null;
}
/** /**
* Store this project's access_information data * Store this project's access_information data
* @param string|array $value * @param string|array $value

View file

@ -0,0 +1,18 @@
<?php
/**
* ProjectGroup model for table: project_group
*/
namespace PHPCI\Model;
use PHPCI\Model\Base\ProjectGroupBase;
/**
* ProjectGroup Model
* @uses PHPCI\Model\Base\ProjectGroupBase
*/
class ProjectGroup extends ProjectGroupBase
{
// This class has been left blank so that you can modify it - changes in this file will not be overwritten.
}

View file

@ -12,6 +12,7 @@ namespace PHPCI\Plugin;
use PHPCI\Builder; use PHPCI\Builder;
use PHPCI\Helper\Lang; use PHPCI\Helper\Lang;
use PHPCI\Model\Build; use PHPCI\Model\Build;
use PHPCI\Model\BuildError;
/** /**
* Behat BDD Plugin * Behat BDD Plugin
@ -119,7 +120,14 @@ class Behat implements \PHPCI\Plugin
'line' => $lineParts[1] 'line' => $lineParts[1]
); );
$this->build->reportError($this->phpci, $lineParts[0], $lineParts[1], 'Behat scenario failed.'); $this->build->reportError(
$this->phpci,
'behat',
'Behat scenario failed.',
BuildError::SEVERITY_HIGH,
$lineParts[0],
$lineParts[1]
);
} }
} }

View file

@ -38,19 +38,19 @@ class Campfire implements \PHPCI\Plugin
*/ */
public function __construct(Builder $phpci, Build $build, array $options = array()) public function __construct(Builder $phpci, Build $build, array $options = array())
{ {
$this->phpci = $phpci; $this->phpci = $phpci;
$this->build = $build; $this->build = $build;
$this->message = $options['message'];
$this->message = $options['message'];
$this->userAgent = "PHPCI/1.0 (+http://www.phptesting.org/)"; $this->userAgent = "PHPCI/1.0 (+http://www.phptesting.org/)";
$this->cookie = "phpcicookie"; $this->cookie = "phpcicookie";
$buildSettings = $phpci->getConfig('build_settings'); $buildSettings = $phpci->getConfig('build_settings');
if (isset($buildSettings['campfire'])) { if (isset($buildSettings['campfire'])) {
$campfire = $buildSettings['campfire']; $campfire = $buildSettings['campfire'];
$this->url = $campfire['url']; $this->url = $campfire['url'];
$this->authToken = $campfire['authToken']; $this->authToken = $campfire['authToken'];
$this->roomId = $campfire['roomId']; $this->roomId = $campfire['roomId'];
} else { } else {
throw new \Exception(Lang::get('no_campfire_settings')); throw new \Exception(Lang::get('no_campfire_settings'));
} }
@ -63,7 +63,7 @@ class Campfire implements \PHPCI\Plugin
*/ */
public function execute() public function execute()
{ {
$url = PHPCI_URL."build/view/".$this->build->getId(); $url = PHPCI_URL . "build/view/" . $this->build->getId();
$message = str_replace("%buildurl%", $url, $this->message); $message = str_replace("%buildurl%", $url, $this->message);
$this->joinRoom($this->roomId); $this->joinRoom($this->roomId);
$status = $this->speak($message, $this->roomId); $status = $this->speak($message, $this->roomId);
@ -101,6 +101,7 @@ class Campfire implements \PHPCI\Plugin
public function speak($message, $roomId, $isPaste = false) public function speak($message, $roomId, $isPaste = false)
{ {
$page = '/room/'.$roomId.'/speak.json'; $page = '/room/'.$roomId.'/speak.json';
if ($isPaste) { if ($isPaste) {
$type = 'PasteMessage'; $type = 'PasteMessage';
} else { } else {
@ -143,10 +144,12 @@ class Campfire implements \PHPCI\Plugin
// We tend to get one space with an otherwise blank response // We tend to get one space with an otherwise blank response
$output = trim($output); $output = trim($output);
if (strlen($output)) { if (strlen($output)) {
/* Responses are JSON. Decode it to a data structure */ /* Responses are JSON. Decode it to a data structure */
return json_decode($output); return json_decode($output);
} }
// Simple 200 OK response (such as for joining a room) // Simple 200 OK response (such as for joining a room)
return true; return true;
} }

View file

@ -39,9 +39,9 @@ class CleanBuild implements \PHPCI\Plugin
*/ */
public function __construct(Builder $phpci, Build $build, array $options = array()) public function __construct(Builder $phpci, Build $build, array $options = array())
{ {
$this->phpci = $phpci; $this->phpci = $phpci;
$this->build = $build; $this->build = $build;
$this->remove = isset($options['remove']) && is_array($options['remove']) ? $options['remove'] : array(); $this->remove = isset($options['remove']) && is_array($options['remove']) ? $options['remove'] : array();
} }
/** /**

View file

@ -83,7 +83,7 @@ class Codeception implements \PHPCI\Plugin, \PHPCI\ZeroConfigPlugin
{ {
$this->phpci = $phpci; $this->phpci = $phpci;
$this->build = $build; $this->build = $build;
$this->path = 'tests/_output/'; $this->path = 'tests' . DIRECTORY_SEPARATOR . '_output' . DIRECTORY_SEPARATOR;
if (empty($options['config'])) { if (empty($options['config'])) {
$this->ymlConfigFile = self::findConfigFile($this->phpci->buildPath); $this->ymlConfigFile = self::findConfigFile($this->phpci->buildPath);
@ -99,7 +99,7 @@ class Codeception implements \PHPCI\Plugin, \PHPCI\ZeroConfigPlugin
} }
/** /**
* Runs Codeception tests, optionally using specified config file(s). * Runs Codeception tests
*/ */
public function execute() public function execute()
{ {
@ -130,6 +130,7 @@ class Codeception implements \PHPCI\Plugin, \PHPCI\ZeroConfigPlugin
} }
$cmd = 'cd "%s" && ' . $codecept . ' run -c "%s" --xml ' . $this->args; $cmd = 'cd "%s" && ' . $codecept . ' run -c "%s" --xml ' . $this->args;
if (IS_WIN) { if (IS_WIN) {
$cmd = 'cd /d "%s" && ' . $codecept . ' run -c "%s" --xml ' . $this->args; $cmd = 'cd /d "%s" && ' . $codecept . ' run -c "%s" --xml ' . $this->args;
} }
@ -137,27 +138,24 @@ class Codeception implements \PHPCI\Plugin, \PHPCI\ZeroConfigPlugin
$configPath = $this->phpci->buildPath . $configPath; $configPath = $this->phpci->buildPath . $configPath;
$success = $this->phpci->executeCommand($cmd, $this->phpci->buildPath, $configPath); $success = $this->phpci->executeCommand($cmd, $this->phpci->buildPath, $configPath);
$this->phpci->log(
'Codeception XML path: '. $this->phpci->buildPath . $this->path . 'report.xml',
Loglevel::DEBUG
);
$this->phpci->log( $xml = file_get_contents($this->phpci->buildPath . $this->path . 'report.xml', false);
'Codeception XML path: '. $this->phpci->buildPath . $this->path . 'report.xml',
Loglevel::DEBUG
);
$xml = file_get_contents($this->phpci->buildPath . $this->path . 'report.xml', false);
$parser = new Parser($this->phpci, $xml); $parser = new Parser($this->phpci, $xml);
$output = $parser->parse(); $output = $parser->parse();
$meta = array( $meta = array(
'tests' => $parser->getTotalTests(), 'tests' => $parser->getTotalTests(),
'timetaken' => $parser->getTotalTimeTaken(), 'timetaken' => $parser->getTotalTimeTaken(),
'failures' => $parser->getTotalFailures() 'failures' => $parser->getTotalFailures()
); );
$this->build->storeMeta('codeception-meta', $meta); $this->build->storeMeta('codeception-meta', $meta);
$this->build->storeMeta('codeception-data', $output); $this->build->storeMeta('codeception-data', $output);
$this->build->storeMeta('codeception-errors', $parser->getTotalFailures()); $this->build->storeMeta('codeception-errors', $parser->getTotalFailures());
$this->phpci->logExecOutput(true); $this->phpci->logExecOutput(true);
return $success; return $success;

View file

@ -38,7 +38,7 @@ class Composer implements PHPCI\Plugin, PHPCI\ZeroConfigPlugin
*/ */
public static function canExecute($stage, Builder $builder, Build $build) public static function canExecute($stage, Builder $builder, Build $build)
{ {
$path = $builder->buildPath . '/composer.json'; $path = $builder->buildPath . DIRECTORY_SEPARATOR . 'composer.json';
if (file_exists($path) && $stage == 'setup') { if (file_exists($path) && $stage == 'setup') {
return true; return true;
@ -55,16 +55,17 @@ class Composer implements PHPCI\Plugin, PHPCI\ZeroConfigPlugin
*/ */
public function __construct(Builder $phpci, Build $build, array $options = array()) public function __construct(Builder $phpci, Build $build, array $options = array())
{ {
$path = $phpci->buildPath; $path = $phpci->buildPath;
$this->phpci = $phpci; $this->phpci = $phpci;
$this->build = $build; $this->build = $build;
$this->directory = $path; $this->directory = $path;
$this->action = 'install'; $this->action = 'install';
$this->preferDist = false; $this->preferDist = false;
$this->nodev = false; $this->preferSource = false;
$this->nodev = false;
if (array_key_exists('directory', $options)) { if (array_key_exists('directory', $options)) {
$this->directory = $path . '/' . $options['directory']; $this->directory = $path . DIRECTORY_SEPARATOR . $options['directory'];
} }
if (array_key_exists('action', $options)) { if (array_key_exists('action', $options)) {
@ -75,6 +76,11 @@ class Composer implements PHPCI\Plugin, PHPCI\ZeroConfigPlugin
$this->preferDist = (bool)$options['prefer_dist']; $this->preferDist = (bool)$options['prefer_dist'];
} }
if (array_key_exists('prefer_source', $options)) {
$this->preferDist = false;
$this->preferSource = (bool)$options['prefer_source'];
}
if (array_key_exists('no_dev', $options)) { if (array_key_exists('no_dev', $options)) {
$this->nodev = (bool)$options['no_dev']; $this->nodev = (bool)$options['no_dev'];
} }
@ -97,10 +103,12 @@ class Composer implements PHPCI\Plugin, PHPCI\ZeroConfigPlugin
if ($this->preferDist) { if ($this->preferDist) {
$this->phpci->log('Using --prefer-dist flag'); $this->phpci->log('Using --prefer-dist flag');
$cmd .= '--prefer-dist'; $cmd .= ' --prefer-dist';
} else { }
if ($this->preferSource) {
$this->phpci->log('Using --prefer-source flag'); $this->phpci->log('Using --prefer-source flag');
$cmd .= '--prefer-source'; $cmd .= ' --prefer-source';
} }
if ($this->nodev) { if ($this->nodev) {

View file

@ -35,12 +35,12 @@ class CopyBuild implements \PHPCI\Plugin
*/ */
public function __construct(Builder $phpci, Build $build, array $options = array()) public function __construct(Builder $phpci, Build $build, array $options = array())
{ {
$path = $phpci->buildPath; $path = $phpci->buildPath;
$this->phpci = $phpci; $this->phpci = $phpci;
$this->build = $build; $this->build = $build;
$this->directory = isset($options['directory']) ? $options['directory'] : $path; $this->directory = isset($options['directory']) ? $options['directory'] : $path;
$this->wipe = isset($options['wipe']) ? (bool)$options['wipe'] : false; $this->wipe = isset($options['wipe']) ? (bool)$options['wipe'] : false;
$this->ignore = isset($options['respect_ignore']) ? (bool)$options['respect_ignore'] : false; $this->ignore = isset($options['respect_ignore']) ? (bool)$options['respect_ignore'] : false;
} }
/** /**

73
PHPCI/Plugin/Deployer.php Normal file
View file

@ -0,0 +1,73 @@
<?php
/**
* PHPCI - Continuous Integration for PHP
*
* @copyright Copyright 2015, Block 8 Limited.
* @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md
* @link https://www.phptesting.org/
*/
namespace PHPCI\Plugin;
use b8\HttpClient;
use PHPCI\Builder;
use PHPCI\Model\Build;
/**
* Integrates PHPCI with Deployer: https://github.com/rebelinblue/deployer
* @author Dan Cryer <dan@block8.co.uk>
* @package PHPCI
* @subpackage Plugins
*/
class Deployer implements \PHPCI\Plugin
{
protected $webhookUrl;
protected $reason;
protected $updateOnly;
/**
* Set up the plugin, configure options, etc.
* @param Builder $phpci
* @param Build $build
* @param array $options
*/
public function __construct(Builder $phpci, Build $build, array $options = array())
{
$this->phpci = $phpci;
$this->build = $build;
$this->reason = 'PHPCI Build #%BUILD% - %COMMIT_MESSAGE%';
if (isset($options['webhook_url'])) {
$this->webhookUrl = $options['webhook_url'];
}
if (isset($options['reason'])) {
$this->reason = $options['reason'];
}
$this->updateOnly = isset($options['update_only']) ? (bool) $options['update_only'] : true;
}
/**
* Copies files from the root of the build directory into the target folder
*/
public function execute()
{
if (empty($this->webhookUrl)) {
$this->phpci->logFailure('You must specify a webhook URL.');
return false;
}
$http = new HttpClient();
$response = $http->post($this->webhookUrl, array(
'reason' => $this->phpci->interpolate($this->reason),
'source' => 'PHPCI',
'url' => $this->phpci->interpolate('%BUILD_URI%'),
'branch' => $this->phpci->interpolate('%BRANCH%'),
'update_only' => $this->updateOnly
));
return $response['success'];
}
}

View file

@ -52,9 +52,9 @@ class Email implements \PHPCI\Plugin
Build $build, Build $build,
array $options = array() array $options = array()
) { ) {
$this->phpci = $phpci; $this->phpci = $phpci;
$this->build = $build; $this->build = $build;
$this->options = $options; $this->options = $options;
} }
/** /**
@ -134,7 +134,7 @@ class Email implements \PHPCI\Plugin
* Send an email to a list of specified subjects. * Send an email to a list of specified subjects.
* *
* @param array $toAddresses * @param array $toAddresses
* List of destinatary of message. * List of destination addresses for message.
* @param string $subject * @param string $subject
* Mail subject * Mail subject
* @param string $body * @param string $body

View file

@ -52,7 +52,7 @@ class Grunt implements \PHPCI\Plugin
// Handle options: // Handle options:
if (isset($options['directory'])) { if (isset($options['directory'])) {
$this->directory = $path . '/' . $options['directory']; $this->directory = $path . DIRECTORY_SEPARATOR . $options['directory'];
} }
if (isset($options['task'])) { if (isset($options['task'])) {

View file

@ -52,7 +52,7 @@ class Gulp implements \PHPCI\Plugin
// Handle options: // Handle options:
if (isset($options['directory'])) { if (isset($options['directory'])) {
$this->directory = $path . '/' . $options['directory']; $this->directory = $path . DIRECTORY_SEPARATOR . $options['directory'];
} }
if (isset($options['task'])) { if (isset($options['task'])) {

View file

@ -41,9 +41,9 @@ class Lint implements PHPCI\Plugin
*/ */
public function __construct(Builder $phpci, Build $build, array $options = array()) public function __construct(Builder $phpci, Build $build, array $options = array())
{ {
$this->phpci = $phpci; $this->phpci = $phpci;
$this->build = $build; $this->build = $build;
$this->directories = array(''); $this->directories = array('');
$this->ignore = $phpci->ignore; $this->ignore = $phpci->ignore;
if (!empty($options['directory'])) { if (!empty($options['directory'])) {
@ -93,7 +93,7 @@ class Lint implements PHPCI\Plugin
if ($item->isFile() && $item->getExtension() == 'php' && !$this->lintFile($php, $itemPath)) { if ($item->isFile() && $item->getExtension() == 'php' && !$this->lintFile($php, $itemPath)) {
$success = false; $success = false;
} elseif ($item->isDir() && $this->recursive && !$this->lintDirectory($php, $itemPath . '/')) { } elseif ($item->isDir() && $this->recursive && !$this->lintDirectory($php, $itemPath . DIRECTORY_SEPARATOR)) {
$success = false; $success = false;
} }

View file

@ -166,10 +166,11 @@ class Mysql implements \PHPCI\Plugin
$args = array( $args = array(
':import_file' => escapeshellarg($import_file), ':import_file' => escapeshellarg($import_file),
':decomp_cmd' => $decomp_cmd, ':decomp_cmd' => $decomp_cmd,
':host' => escapeshellarg($this->host),
':user' => escapeshellarg($this->user), ':user' => escapeshellarg($this->user),
':pass' => escapeshellarg($this->pass), ':pass' => escapeshellarg($this->pass),
':database' => ($database === null)? '': escapeshellarg($database), ':database' => ($database === null)? '': escapeshellarg($database),
); );
return strtr('cat :import_file :decomp_cmd | mysql -u:user -p:pass :database', $args); return strtr('cat :import_file :decomp_cmd | mysql -h:host -u:user -p:pass :database', $args);
} }
} }

View file

@ -33,12 +33,12 @@ class PackageBuild implements \PHPCI\Plugin
*/ */
public function __construct(Builder $phpci, Build $build, array $options = array()) public function __construct(Builder $phpci, Build $build, array $options = array())
{ {
$path = $phpci->buildPath; $path = $phpci->buildPath;
$this->build = $build; $this->build = $build;
$this->phpci = $phpci; $this->phpci = $phpci;
$this->directory = isset($options['directory']) ? $options['directory'] : $path; $this->directory = isset($options['directory']) ? $options['directory'] : $path;
$this->filename = isset($options['filename']) ? $options['filename'] : 'build'; $this->filename = isset($options['filename']) ? $options['filename'] : 'build';
$this->format = isset($options['format']) ? $options['format'] : 'zip'; $this->format = isset($options['format']) ? $options['format'] : 'zip';
} }
/** /**
@ -46,7 +46,7 @@ class PackageBuild implements \PHPCI\Plugin
*/ */
public function execute() public function execute()
{ {
$path = $this->phpci->buildPath; $path = $this->phpci->buildPath;
$build = $this->build; $build = $this->build;
if ($this->directory == $path) { if ($this->directory == $path) {
@ -69,8 +69,7 @@ class PackageBuild implements \PHPCI\Plugin
} }
foreach ($this->format as $format) { foreach ($this->format as $format) {
switch($format) switch ($format) {
{
case 'tar': case 'tar':
$cmd = 'tar cfz "%s/%s.tar.gz" ./*'; $cmd = 'tar cfz "%s/%s.tar.gz" ./*';
break; break;

View file

@ -73,8 +73,11 @@ class Pdepend implements \PHPCI\Plugin
*/ */
public function execute() public function execute()
{ {
if (!file_exists($this->location)) {
mkdir($this->location);
}
if (!is_writable($this->location)) { if (!is_writable($this->location)) {
throw new \Exception(sprintf('The location %s is not writable.', $this->location)); throw new \Exception(sprintf('The location %s is not writable or does not exist.', $this->location));
} }
$pdepend = $this->phpci->findBinary('pdepend'); $pdepend = $this->phpci->findBinary('pdepend');

View file

@ -73,9 +73,9 @@ class Pgsql implements \PHPCI\Plugin
} }
/** /**
* Connects to PgSQL and runs a specified set of queries. * Connects to PgSQL and runs a specified set of queries.
* @return boolean * @return boolean
*/ */
public function execute() public function execute()
{ {
try { try {

View file

@ -213,7 +213,7 @@ class Phar implements \PHPCI\Plugin
$content = ''; $content = '';
$filename = $this->getStub(); $filename = $this->getStub();
if ($filename) { if ($filename) {
$content = file_get_contents($this->getPHPCI()->buildPath . '/' . $this->getStub()); $content = file_get_contents($this->getPHPCI()->buildPath . DIRECTORY_SEPARATOR . $this->getStub());
} }
return $content; return $content;
} }
@ -227,7 +227,8 @@ class Phar implements \PHPCI\Plugin
$success = false; $success = false;
try { try {
$phar = new PHPPhar($this->getDirectory() . '/' . $this->getFilename(), 0, $this->getFilename()); $file = $this->getDirectory() . DIRECTORY_SEPARATOR . $this->getFilename();
$phar = new PHPPhar($file, 0, $this->getFilename());
$phar->buildFromDirectory($this->getPHPCI()->buildPath, $this->getRegExp()); $phar->buildFromDirectory($this->getPHPCI()->buildPath, $this->getRegExp());
$stub = $this->getStubContent(); $stub = $this->getStubContent();
@ -236,7 +237,6 @@ class Phar implements \PHPCI\Plugin
} }
$success = true; $success = true;
} catch (Exception $e) { } catch (Exception $e) {
$this->getPHPCI()->log(Lang::get('phar_internal_error')); $this->getPHPCI()->log(Lang::get('phar_internal_error'));
$this->getPHPCI()->log($e->getMessage()); $this->getPHPCI()->log($e->getMessage());

View file

@ -47,7 +47,7 @@ class Phing implements \PHPCI\Plugin
* Set working directory * Set working directory
*/ */
if (isset($options['directory'])) { if (isset($options['directory'])) {
$directory = $phpci->buildPath . '/' . $options['directory']; $directory = $phpci->buildPath . DIRECTORY_SEPARATOR . $options['directory'];
} else { } else {
$directory = $phpci->buildPath; $directory = $phpci->buildPath;
} }
@ -255,7 +255,7 @@ class Phing implements \PHPCI\Plugin
*/ */
public function setPropertyFile($propertyFile) public function setPropertyFile($propertyFile)
{ {
if (!file_exists($this->getDirectory() . '/' . $propertyFile)) { if (!file_exists($this->getDirectory() . DIRECTORY_SEPARATOR . $propertyFile)) {
throw new \Exception(Lang::get('property_file_missing')); throw new \Exception(Lang::get('property_file_missing'));
} }

View file

@ -12,6 +12,7 @@ namespace PHPCI\Plugin;
use PHPCI; use PHPCI;
use PHPCI\Builder; use PHPCI\Builder;
use PHPCI\Model\Build; use PHPCI\Model\Build;
use PHPCI\Model\BuildError;
/** /**
* PHP Code Sniffer Plugin - Allows PHP Code Sniffer testing. * PHP Code Sniffer Plugin - Allows PHP Code Sniffer testing.
@ -163,14 +164,13 @@ class PhpCodeSniffer implements PHPCI\Plugin, PHPCI\ZeroConfigPlugin
); );
$output = $this->phpci->getLastOutput(); $output = $this->phpci->getLastOutput();
list($errors, $warnings, $data) = $this->processReport($output); list($errors, $warnings) = $this->processReport($output);
$this->phpci->logExecOutput(true); $this->phpci->logExecOutput(true);
$success = true; $success = true;
$this->build->storeMeta('phpcs-warnings', $warnings); $this->build->storeMeta('phpcs-warnings', $warnings);
$this->build->storeMeta('phpcs-errors', $errors); $this->build->storeMeta('phpcs-errors', $errors);
$this->build->storeMeta('phpcs-data', $data);
if ($this->allowed_warnings != -1 && $warnings > $this->allowed_warnings) { if ($this->allowed_warnings != -1 && $warnings > $this->allowed_warnings) {
$success = false; $success = false;
@ -226,23 +226,21 @@ class PhpCodeSniffer implements PHPCI\Plugin, PHPCI\ZeroConfigPlugin
$errors = $data['totals']['errors']; $errors = $data['totals']['errors'];
$warnings = $data['totals']['warnings']; $warnings = $data['totals']['warnings'];
$rtn = array();
foreach ($data['files'] as $fileName => $file) { foreach ($data['files'] as $fileName => $file) {
$fileName = str_replace($this->phpci->buildPath, '', $fileName); $fileName = str_replace($this->phpci->buildPath, '', $fileName);
foreach ($file['messages'] as $message) { foreach ($file['messages'] as $message) {
$this->build->reportError($this->phpci, $fileName, $message['line'], 'PHPCS: ' . $message['message']); $this->build->reportError(
$this->phpci,
$rtn[] = array( 'php_code_sniffer',
'file' => $fileName, 'PHPCS: ' . $message['message'],
'line' => $message['line'], $message['type'] == 'ERROR' ? BuildError::SEVERITY_HIGH : BuildError::SEVERITY_LOW,
'type' => $message['type'], $fileName,
'message' => $message['message'], $message['line']
); );
} }
} }
return array($errors, $warnings, $rtn); return array($errors, $warnings);
} }
} }

View file

@ -12,6 +12,7 @@ namespace PHPCI\Plugin;
use PHPCI\Builder; use PHPCI\Builder;
use PHPCI\Helper\Lang; use PHPCI\Helper\Lang;
use PHPCI\Model\Build; use PHPCI\Model\Build;
use PHPCI\Model\BuildError;
/** /**
* PHP Copy / Paste Detector - Allows PHP Copy / Paste Detector testing. * PHP Copy / Paste Detector - Allows PHP Copy / Paste Detector testing.
@ -28,7 +29,7 @@ class PhpCpd implements \PHPCI\Plugin
/** /**
* @var string, based on the assumption the root may not hold the code to be * @var string, based on the assumption the root may not hold the code to be
* tested, exteds the base path * tested, extends the base path
*/ */
protected $path; protected $path;
@ -74,9 +75,9 @@ class PhpCpd implements \PHPCI\Plugin
if (count($this->ignore)) { if (count($this->ignore)) {
$map = function ($item) { $map = function ($item) {
// remove the trailing slash // remove the trailing slash
$item = (substr($item, -1) == '/' ? substr($item, 0, -1) : $item); $item = rtrim($item, DIRECTORY_SEPARATOR);
if (is_file($this->path . '/' . $item)) { if (is_file(rtrim($this->path, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $item)) {
return ' --names-exclude ' . $item; return ' --names-exclude ' . $item;
} else { } else {
return ' --exclude ' . $item; return ' --exclude ' . $item;
@ -97,9 +98,8 @@ class PhpCpd implements \PHPCI\Plugin
print $this->phpci->getLastOutput(); print $this->phpci->getLastOutput();
list($errorCount, $data) = $this->processReport(file_get_contents($tmpfilename)); $errorCount = $this->processReport(file_get_contents($tmpfilename));
$this->build->storeMeta('phpcpd-warnings', $errorCount); $this->build->storeMeta('phpcpd-warnings', $errorCount);
$this->build->storeMeta('phpcpd-data', $data);
unlink($tmpfilename); unlink($tmpfilename);
@ -122,20 +122,11 @@ class PhpCpd implements \PHPCI\Plugin
} }
$warnings = 0; $warnings = 0;
$data = array();
foreach ($xml->duplication as $duplication) { foreach ($xml->duplication as $duplication) {
foreach ($duplication->file as $file) { foreach ($duplication->file as $file) {
$fileName = (string)$file['path']; $fileName = (string)$file['path'];
$fileName = str_replace($this->phpci->buildPath, '', $fileName); $fileName = str_replace($this->phpci->buildPath, '', $fileName);
$data[] = array(
'file' => $fileName,
'line_start' => (int) $file['line'],
'line_end' => (int) $file['line'] + (int) $duplication['lines'],
'code' => (string) $duplication->codefragment
);
$message = <<<CPD $message = <<<CPD
Copy and paste detected: Copy and paste detected:
@ -144,13 +135,20 @@ Copy and paste detected:
``` ```
CPD; CPD;
$this->build->reportError($this->phpci, $fileName, $file['line'], $message); $this->build->reportError(
$this->phpci,
'php_cpd',
$message,
BuildError::SEVERITY_NORMAL,
$fileName,
$file['line'],
(int) $file['line'] + (int) $duplication['lines']
);
} }
$warnings++; $warnings++;
} }
return array($warnings, $data); return $warnings;
} }
} }

View file

@ -14,7 +14,7 @@ use PHPCI\Helper\Lang;
use PHPCI\Model\Build; use PHPCI\Model\Build;
/** /**
* PHP CS Fixer - Works with the PHP CS Fixer for testing coding standards. * PHP CS Fixer - Works with the PHP Coding Standards Fixer for testing coding standards.
* @author Gabriel Baker <gabriel@autonomicpilot.co.uk> * @author Gabriel Baker <gabriel@autonomicpilot.co.uk>
* @package PHPCI * @package PHPCI
* @subpackage Plugins * @subpackage Plugins
@ -32,10 +32,10 @@ class PhpCsFixer implements \PHPCI\Plugin
protected $build; protected $build;
protected $workingDir = ''; protected $workingDir = '';
protected $level = ' --level=all'; protected $level = ' --level=psr2';
protected $verbose = ''; protected $verbose = '';
protected $diff = ''; protected $diff = '';
protected $levels = array('psr0', 'psr1', 'psr2', 'all'); protected $levels = array('psr0', 'psr1', 'psr2', 'symfony');
/** /**
* Standard Constructor * Standard Constructor

View file

@ -143,7 +143,6 @@ class PhpDocblockChecker implements PHPCI\Plugin, PHPCI\ZeroConfigPlugin
$success = true; $success = true;
$this->build->storeMeta('phpdoccheck-warnings', $errors); $this->build->storeMeta('phpdoccheck-warnings', $errors);
$this->build->storeMeta('phpdoccheck-data', $output);
$this->reportErrors($output); $this->reportErrors($output);
if ($this->allowed_warnings != -1 && $errors > $this->allowed_warnings) { if ($this->allowed_warnings != -1 && $errors > $this->allowed_warnings) {
@ -160,13 +159,22 @@ class PhpDocblockChecker implements PHPCI\Plugin, PHPCI\ZeroConfigPlugin
protected function reportErrors($output) protected function reportErrors($output)
{ {
foreach ($output as $error) { foreach ($output as $error) {
$message = 'Class ' . $error['class'] . ' does not have a Docblock comment.'; $message = 'Class ' . $error['class'] . ' is missing a docblock.';
$severity = PHPCI\Model\BuildError::SEVERITY_LOW;
if ($error['type'] == 'method') { if ($error['type'] == 'method') {
$message = 'Method ' . $error['class'] . '::' . $error['method'] . ' does not have a Docblock comment.'; $message = $error['class'] . '::' . $error['method'] . ' is missing a docblock.';
$severity = PHPCI\Model\BuildError::SEVERITY_NORMAL;
} }
$this->build->reportError($this->phpci, $error['file'], $error['line'], $message); $this->build->reportError(
$this->phpci,
'php_docblock_checker',
$message,
$severity,
$error['file'],
$error['line']
);
} }
} }
} }

View file

@ -69,19 +69,20 @@ class PhpLoc implements PHPCI\Plugin, PHPCI\ZeroConfigPlugin
public function execute() public function execute()
{ {
$ignore = ''; $ignore = '';
if (count($this->phpci->ignore)) {
$map = function ($item) {
return ' --exclude ' . (substr($item, -1) == '/' ? substr($item, 0, -1) : $item);
};
$ignore = array_map($map, $this->phpci->ignore);
if (count($this->phpci->ignore)) {
$map = function ($item) {
return ' --exclude ' . rtrim($item, DIRECTORY_SEPARATOR);
};
$ignore = array_map($map, $this->phpci->ignore);
$ignore = implode('', $ignore); $ignore = implode('', $ignore);
} }
$phploc = $this->phpci->findBinary('phploc'); $phploc = $this->phpci->findBinary('phploc');
$success = $this->phpci->executeCommand($phploc . ' %s "%s"', $ignore, $this->directory); $success = $this->phpci->executeCommand($phploc . ' %s "%s"', $ignore, $this->directory);
$output = $this->phpci->getLastOutput(); $output = $this->phpci->getLastOutput();
if (preg_match_all('/\((LOC|CLOC|NCLOC|LLOC)\)\s+([0-9]+)/', $output, $matches)) { if (preg_match_all('/\((LOC|CLOC|NCLOC|LLOC)\)\s+([0-9]+)/', $output, $matches)) {
$data = array(); $data = array();

View file

@ -38,7 +38,7 @@ class PhpMessDetector implements PHPCI\Plugin, PHPCI\ZeroConfigPlugin
/** /**
* @var string, based on the assumption the root may not hold the code to be * @var string, based on the assumption the root may not hold the code to be
* tested, exteds the base path only if the provided path is relative. Absolute * tested, extends the base path only if the provided path is relative. Absolute
* paths are used verbatim * paths are used verbatim
*/ */
protected $path; protected $path;
@ -50,7 +50,7 @@ class PhpMessDetector implements PHPCI\Plugin, PHPCI\ZeroConfigPlugin
/** /**
* Array of PHPMD rules. Can be one of the builtins (codesize, unusedcode, naming, design, controversial) * Array of PHPMD rules. Can be one of the builtins (codesize, unusedcode, naming, design, controversial)
* or a filenname (detected by checking for a / in it), either absolute or relative to the project root. * or a filename (detected by checking for a / in it), either absolute or relative to the project root.
* @var array * @var array
*/ */
protected $rules; protected $rules;
@ -123,9 +123,8 @@ class PhpMessDetector implements PHPCI\Plugin, PHPCI\ZeroConfigPlugin
$this->executePhpMd($phpmdBinaryPath); $this->executePhpMd($phpmdBinaryPath);
list($errorCount, $data) = $this->processReport(trim($this->phpci->getLastOutput())); $errorCount = $this->processReport(trim($this->phpci->getLastOutput()));
$this->build->storeMeta('phpmd-warnings', $errorCount); $this->build->storeMeta('phpmd-warnings', $errorCount);
$this->build->storeMeta('phpmd-data', $data);
return $this->wasLastExecSuccessful($errorCount); return $this->wasLastExecSuccessful($errorCount);
} }
@ -158,7 +157,6 @@ class PhpMessDetector implements PHPCI\Plugin, PHPCI\ZeroConfigPlugin
} }
$warnings = 0; $warnings = 0;
$data = array();
foreach ($xml->file as $file) { foreach ($xml->file as $file) {
$fileName = (string)$file['name']; $fileName = (string)$file['name'];
@ -166,22 +164,20 @@ class PhpMessDetector implements PHPCI\Plugin, PHPCI\ZeroConfigPlugin
foreach ($file->violation as $violation) { foreach ($file->violation as $violation) {
$warnings++; $warnings++;
$warning = array(
'file' => $fileName,
'line_start' => (int)$violation['beginline'],
'line_end' => (int)$violation['endline'],
'rule' => (string)$violation['rule'],
'ruleset' => (string)$violation['ruleset'],
'priority' => (int)$violation['priority'],
'message' => (string)$violation,
);
$this->build->reportError($this->phpci, $fileName, (int)$violation['beginline'], (string)$violation); $this->build->reportError(
$data[] = $warning; $this->phpci,
'php_mess_detector',
(string)$violation,
PHPCI\Model\BuildError::SEVERITY_HIGH,
$fileName,
(int)$violation['beginline'],
(int)$violation['endline']
);
} }
} }
return array($warnings, $data); return $warnings;
} }
/** /**

View file

@ -55,10 +55,10 @@ class PhpParallelLint implements \PHPCI\Plugin
*/ */
public function __construct(Builder $phpci, Build $build, array $options = array()) public function __construct(Builder $phpci, Build $build, array $options = array())
{ {
$this->phpci = $phpci; $this->phpci = $phpci;
$this->build = $build; $this->build = $build;
$this->directory = $phpci->buildPath; $this->directory = $phpci->buildPath;
$this->ignore = $this->phpci->ignore; $this->ignore = $this->phpci->ignore;
if (isset($options['directory'])) { if (isset($options['directory'])) {
$this->directory = $phpci->buildPath.$options['directory']; $this->directory = $phpci->buildPath.$options['directory'];

View file

@ -155,7 +155,7 @@ class PhpTalLint implements PHPCI\Plugin
if (!$this->lintFile($itemPath)) { if (!$this->lintFile($itemPath)) {
$success = false; $success = false;
} }
} elseif ($item->isDir() && $this->recursive && !$this->lintDirectory($itemPath . '/')) { } elseif ($item->isDir() && $this->recursive && !$this->lintDirectory($itemPath . DIRECTORY_SEPARATOR)) {
$success = false; $success = false;
} }
@ -202,7 +202,9 @@ class PhpTalLint implements PHPCI\Plugin
list($suffixes, $tales) = $this->getFlags(); list($suffixes, $tales) = $this->getFlags();
$lint = dirname(__FILE__) . '/../../vendor/phptal/phptal/tools/phptal_lint.php'; $lint = dirname(__FILE__) . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR;
$lint .= 'vendor' . DIRECTORY_SEPARATOR . 'phptal' . DIRECTORY_SEPARATOR . 'phptal' . DIRECTORY_SEPARATOR;
$lint .= 'tools' . DIRECTORY_SEPARATOR . 'phptal_lint.php';
$cmd = '/usr/bin/env php ' . $lint . ' %s %s "%s"'; $cmd = '/usr/bin/env php ' . $lint . ' %s %s "%s"';
$this->phpci->executeCommand($cmd, $suffixes, $tales, $this->phpci->buildPath . $path); $this->phpci->executeCommand($cmd, $suffixes, $tales, $this->phpci->buildPath . $path);

View file

@ -76,8 +76,8 @@ class PhpUnit implements PHPCI\Plugin, PHPCI\ZeroConfigPlugin
return 'phpunit.xml'; return 'phpunit.xml';
} }
if (file_exists($buildPath . 'tests/phpunit.xml')) { if (file_exists($buildPath . 'tests' . DIRECTORY_SEPARATOR . 'phpunit.xml')) {
return 'tests/phpunit.xml'; return 'tests' . DIRECTORY_SEPARATOR . 'phpunit.xml';
} }
if (file_exists($buildPath . 'phpunit.xml.dist')) { if (file_exists($buildPath . 'phpunit.xml.dist')) {
@ -85,7 +85,7 @@ class PhpUnit implements PHPCI\Plugin, PHPCI\ZeroConfigPlugin
} }
if (file_exists($buildPath . 'tests/phpunit.xml.dist')) { if (file_exists($buildPath . 'tests/phpunit.xml.dist')) {
return 'tests/phpunit.xml.dist'; return 'tests' . DIRECTORY_SEPARATOR . 'phpunit.xml.dist';
} }
return null; return null;
@ -133,7 +133,7 @@ class PhpUnit implements PHPCI\Plugin, PHPCI\ZeroConfigPlugin
} }
if (isset($options['coverage'])) { if (isset($options['coverage'])) {
$this->coverage = " --coverage-html {$options['coverage']} "; $this->coverage = ' --coverage-html ' . $this->phpci->interpolate($options['coverage']) . ' ';
} }
} }
@ -194,7 +194,7 @@ class PhpUnit implements PHPCI\Plugin, PHPCI\ZeroConfigPlugin
} else { } else {
if ($this->runFrom) { if ($this->runFrom) {
$curdir = getcwd(); $curdir = getcwd();
chdir($this->phpci->buildPath.'/'.$this->runFrom); chdir($this->phpci->buildPath . DIRECTORY_SEPARATOR . $this->runFrom);
} }
$phpunit = $this->phpci->findBinary('phpunit'); $phpunit = $this->phpci->findBinary('phpunit');

View file

@ -24,6 +24,7 @@ class SlackNotify implements \PHPCI\Plugin
private $username; private $username;
private $message; private $message;
private $icon; private $icon;
private $show_status;
/** /**
* Set up the plugin, configure options, etc. * Set up the plugin, configure options, etc.
@ -60,6 +61,12 @@ class SlackNotify implements \PHPCI\Plugin
$this->username = 'PHPCI'; $this->username = 'PHPCI';
} }
if (isset($options['show_status'])) {
$this->show_status = (bool) $options['show_status'];
} else {
$this->show_status = true;
}
if (isset($options['icon'])) { if (isset($options['icon'])) {
$this->icon = $options['icon']; $this->icon = $options['icon'];
} }
@ -74,31 +81,7 @@ class SlackNotify implements \PHPCI\Plugin
*/ */
public function execute() public function execute()
{ {
$message = $this->phpci->interpolate($this->message); $body = $this->phpci->interpolate($this->message);
$successfulBuild = $this->build->isSuccessful();
if ($successfulBuild) {
$status = 'Success';
$color = 'good';
} else {
$status = 'Failed';
$color = 'danger';
}
// Build up the attachment data
$attachment = new \Maknz\Slack\Attachment(array(
'fallback' => $message,
'pretext' => $message,
'color' => $color,
'fields' => array(
new \Maknz\Slack\AttachmentField(array(
'title' => 'Status',
'value' => $status,
'short' => false
))
)
));
$client = new \Maknz\Slack\Client($this->webHook); $client = new \Maknz\Slack\Client($this->webHook);
@ -116,12 +99,39 @@ class SlackNotify implements \PHPCI\Plugin
$message->setIcon($this->icon); $message->setIcon($this->icon);
} }
$message->attach($attachment); // Include an attachment which shows the status and hide the message
if ($this->show_status) {
$successfulBuild = $this->build->isSuccessful();
$success = true; if ($successfulBuild) {
$status = 'Success';
$color = 'good';
} else {
$status = 'Failed';
$color = 'danger';
}
$message->send(''); // Build up the attachment data
$attachment = new \Maknz\Slack\Attachment(array(
'fallback' => $body,
'pretext' => $body,
'color' => $color,
'fields' => array(
new \Maknz\Slack\AttachmentField(array(
'title' => 'Status',
'value' => $status,
'short' => false
))
)
));
return $success; $message->attach($attachment);
$body = '';
}
$message->send($body);
return true;
} }
} }

View file

@ -124,12 +124,11 @@ class TechnicalDebt implements PHPCI\Plugin, PHPCI\ZeroConfigPlugin
$success = true; $success = true;
$this->phpci->logExecOutput(false); $this->phpci->logExecOutput(false);
list($errorCount, $data) = $this->getErrorList(); $errorCount = $this->getErrorList();
$this->phpci->log("Found $errorCount instances of " . implode(', ', $this->searches)); $this->phpci->log("Found $errorCount instances of " . implode(', ', $this->searches));
$this->build->storeMeta('technical_debt-warnings', $errorCount); $this->build->storeMeta('technical_debt-warnings', $errorCount);
$this->build->storeMeta('technical_debt-data', $data);
if ($this->allowed_errors != -1 && $errorCount > $this->allowed_errors) { if ($this->allowed_errors != -1 && $errorCount > $this->allowed_errors) {
$success = false; $success = false;
@ -151,6 +150,7 @@ class TechnicalDebt implements PHPCI\Plugin, PHPCI\ZeroConfigPlugin
$ignores = $this->ignore; $ignores = $this->ignore;
$ignores[] = 'phpci.yml'; $ignores[] = 'phpci.yml';
$ignores[] = '.phpci.yml';
foreach ($iterator as $file) { foreach ($iterator as $file) {
$filePath = $file->getRealPath(); $filePath = $file->getRealPath();
@ -163,7 +163,7 @@ class TechnicalDebt implements PHPCI\Plugin, PHPCI\ZeroConfigPlugin
} }
// Ignore hidden files, else .git, .sass_cache, etc. all get looped over // Ignore hidden files, else .git, .sass_cache, etc. all get looped over
if (stripos($filePath, '/.') !== false) { if (stripos($filePath, DIRECTORY_SEPARATOR . '.') !== false) {
$skipFile = true; $skipFile = true;
} }
@ -174,7 +174,6 @@ class TechnicalDebt implements PHPCI\Plugin, PHPCI\ZeroConfigPlugin
$files = array_filter(array_unique($files)); $files = array_filter(array_unique($files));
$errorCount = 0; $errorCount = 0;
$data = array();
foreach ($files as $file) { foreach ($files as $file) {
foreach ($this->searches as $search) { foreach ($this->searches as $search) {
@ -188,21 +187,21 @@ class TechnicalDebt implements PHPCI\Plugin, PHPCI\ZeroConfigPlugin
$content = trim($allLines[$lineNumber - 1]); $content = trim($allLines[$lineNumber - 1]);
$errorCount++; $errorCount++;
$this->phpci->log("Found $search on line $lineNumber of $file:\n$content");
$fileName = str_replace($this->directory, '', $file); $fileName = str_replace($this->directory, '', $file);
$data[] = array(
'file' => $fileName, $this->build->reportError(
'line' => $lineNumber, $this->phpci,
'message' => $content 'technical_debt',
$content,
PHPCI\Model\BuildError::SEVERITY_LOW,
$fileName,
$lineNumber
); );
$this->build->reportError($this->phpci, $fileName, $lineNumber, $content);
} }
} }
} }
return array( $errorCount, $data ); return $errorCount;
} }
} }

View file

@ -50,12 +50,89 @@ class Executor
public function executePlugins(&$config, $stage) public function executePlugins(&$config, $stage)
{ {
$success = true; $success = true;
// Ignore any stages for which we don't have plugins set: $pluginsToExecute = array();
if (!array_key_exists($stage, $config) || !is_array($config[$stage])) {
return $success; // If we have global plugins to execute for this stage, add them to the list to be executed:
if (array_key_exists($stage, $config) && is_array($config[$stage])) {
$pluginsToExecute[] = $config[$stage];
} }
foreach ($config[$stage] as $plugin => $options) { $pluginsToExecute = $this->getBranchSpecificPlugins($config, $stage, $pluginsToExecute);
foreach ($pluginsToExecute as $pluginSet) {
if (!$this->doExecutePlugins($pluginSet, $stage)) {
$success = false;
}
}
return $success;
}
/**
* Check the config for any plugins specific to the branch we're currently building.
* @param $config
* @param $stage
* @param $pluginsToExecute
* @return array
*/
protected function getBranchSpecificPlugins(&$config, $stage, $pluginsToExecute)
{
/** @var \PHPCI\Model\Build $build */
$build = $this->pluginFactory->getResourceFor('PHPCI\Model\Build');
$branch = $build->getBranch();
// If we don't have any branch-specific plugins:
if (!isset($config['branch-' . $branch][$stage]) || !is_array($config['branch-' . $branch][$stage])) {
return $pluginsToExecute;
}
// If we have branch-specific plugins to execute, add them to the list to be executed:
$branchConfig = $config['branch-' . $branch];
$plugins = $branchConfig[$stage];
$runOption = 'after';
if (!empty($branchConfig['run-option'])) {
$runOption = $branchConfig['run-option'];
}
switch ($runOption) {
// Replace standard plugin set for this stage with just the branch-specific ones:
case 'replace':
$pluginsToExecute = array();
$pluginsToExecute[] = $plugins;
break;
// Run branch-specific plugins before standard plugins:
case 'before':
array_unshift($pluginsToExecute, $plugins);
break;
// Run branch-specific plugins after standard plugins:
case 'after':
array_push($pluginsToExecute, $plugins);
break;
default:
array_push($pluginsToExecute, $plugins);
break;
}
return $pluginsToExecute;
}
/**
* Execute the list of plugins found for a given testing stage.
* @param $plugins
* @param $stage
* @return bool
* @throws \Exception
*/
protected function doExecutePlugins(&$plugins, $stage)
{
$success = true;
foreach ($plugins as $plugin => $options) {
$this->logger->log(Lang::get('running_plugin', $plugin)); $this->logger->log(Lang::get('running_plugin', $plugin));
$this->setPluginStatus($stage, $plugin, Build::STATUS_RUNNING); $this->setPluginStatus($stage, $plugin, Build::STATUS_RUNNING);

View file

@ -8,8 +8,8 @@ namespace PHPCI\Plugin\Util;
*/ */
class Factory class Factory
{ {
const TYPE_ARRAY = "array"; const TYPE_ARRAY = "array";
const TYPE_CALLABLE = "callable"; const TYPE_CALLABLE = "callable";
const INTERFACE_PHPCI_PLUGIN = '\PHPCI\Plugin'; const INTERFACE_PHPCI_PLUGIN = '\PHPCI\Plugin';
private $currentPluginOptions; private $currentPluginOptions;

View file

@ -56,6 +56,7 @@ class FilesPluginInformation implements InstalledPluginInformation
if ($this->pluginInfo === null) { if ($this->pluginInfo === null) {
$this->loadPluginInfo(); $this->loadPluginInfo();
} }
return $this->pluginInfo; return $this->pluginInfo;
} }
@ -83,7 +84,7 @@ class FilesPluginInformation implements InstalledPluginInformation
$this->pluginInfo = array(); $this->pluginInfo = array();
foreach ($this->files as $fileInfo) { foreach ($this->files as $fileInfo) {
if ($fileInfo instanceof \SplFileInfo) { if ($fileInfo instanceof \SplFileInfo) {
if ($fileInfo->isFile() && $fileInfo->getExtension()=='php') { if ($fileInfo->isFile() && $fileInfo->getExtension() == 'php') {
$this->addPluginFromFile($fileInfo); $this->addPluginFromFile($fileInfo);
} }
} }
@ -99,11 +100,11 @@ class FilesPluginInformation implements InstalledPluginInformation
$class = $this->getFullClassFromFile($fileInfo); $class = $this->getFullClassFromFile($fileInfo);
if (!is_null($class)) { if (!is_null($class)) {
$newPlugin = new \stdClass(); $newPlugin = new \stdClass();
$newPlugin->class = $class; $newPlugin->class = $class;
$newPlugin->source = "core"; $newPlugin->source = "core";
$parts = explode('\\', $newPlugin->class); $parts = explode('\\', $newPlugin->class);
$newPlugin->name = end($parts); $newPlugin->name = end($parts);
$this->pluginInfo[] = $newPlugin; $this->pluginInfo[] = $newPlugin;
} }

View file

@ -32,9 +32,11 @@ class PluginInformationCollection implements InstalledPluginInformation
public function getInstalledPlugins() public function getInstalledPlugins()
{ {
$arr = array(); $arr = array();
foreach ($this->pluginInformations as $single) { foreach ($this->pluginInformations as $single) {
$arr = array_merge($arr, $single->getInstalledPlugins()); $arr = array_merge($arr, $single->getInstalledPlugins());
} }
return $arr; return $arr;
} }
@ -47,9 +49,11 @@ class PluginInformationCollection implements InstalledPluginInformation
public function getPluginClasses() public function getPluginClasses()
{ {
$arr = array(); $arr = array();
foreach ($this->pluginInformations as $single) { foreach ($this->pluginInformations as $single) {
$arr = array_merge($arr, $single->getPluginClasses()); $arr = array_merge($arr, $single->getPluginClasses());
} }
return $arr; return $arr;
} }
} }

View file

@ -13,9 +13,10 @@ use Symfony\Component\Yaml\Yaml;
class TapParser class TapParser
{ {
const TEST_COUNTS_PATTERN = '/^\d+\.\.(\d+)/'; const TEST_COUNTS_PATTERN = '/^\d+\.\.(\d+)/';
const TEST_LINE_PATTERN = '/^(ok|not ok)(?:\s+\d+)?(?:\s+\-)?\s*(.*?)(?:\s*#\s*(skip|todo)\s*(.*))?\s*$/i'; const TEST_LINE_PATTERN = '/^(ok|not ok)(?:\s+\d+)?(?:\s+\-)?\s*(.*?)(?:\s*#\s*(skip|todo)\s*(.*))?\s*$/i';
const TEST_YAML_START = '/^(\s*)---/'; const TEST_YAML_START = '/^(\s*)---/';
const TEST_DIAGNOSTIC = '/^#/'; const TEST_DIAGNOSTIC = '/^#/';
const TEST_COVERAGE = '/^Generating/';
/** /**
* @var string * @var string
@ -73,7 +74,7 @@ class TapParser
$line = $this->nextLine(); $line = $this->nextLine();
if ($line === $header) { if ($line === $header) {
throw new Exception("Duplicated TAP log, please check the configration."); throw new Exception("Duplicated TAP log, please check the configuration.");
} }
while ($line !== false && ($this->testCount === false || count($this->results) < $this->testCount)) { while ($line !== false && ($this->testCount === false || count($this->results) < $this->testCount)) {
@ -81,7 +82,7 @@ class TapParser
$line = $this->nextLine(); $line = $this->nextLine();
} }
if (count($this->results) !== $this->testCount) { if (false !== $this->testCount && count($this->results) !== $this->testCount) {
throw new Exception(Lang::get('tap_error')); throw new Exception(Lang::get('tap_error'));
} }
@ -96,7 +97,7 @@ class TapParser
*/ */
protected function findTapLog() protected function findTapLog()
{ {
// Look for the beggning of the TAP output // Look for the beginning of the TAP output
do { do {
$header = $this->nextLine(); $header = $this->nextLine();
} while ($header !== false && substr($header, 0, 12) !== 'TAP version '); } while ($header !== false && substr($header, 0, 12) !== 'TAP version ');
@ -123,19 +124,14 @@ class TapParser
return false; return false;
} }
/** Parse a single line. /**
*
* @param string $line * @param string $line
*
* @return boolean
*/ */
protected function parseLine($line) protected function testLine($line)
{ {
if (preg_match(self::TEST_COUNTS_PATTERN, $line, $matches)) { if (preg_match(self::TEST_LINE_PATTERN, $line, $matches)) {
$this->testCount = intval($matches[1]);
} elseif (preg_match(self::TEST_DIAGNOSTIC, $line)) {
return;
} elseif (preg_match(self::TEST_LINE_PATTERN, $line, $matches)) {
$this->results[] = $this->processTestLine( $this->results[] = $this->processTestLine(
$matches[1], $matches[1],
isset($matches[2]) ? $matches[2] : '', isset($matches[2]) ? $matches[2] : '',
@ -143,18 +139,61 @@ class TapParser
isset($matches[4]) ? $matches[4] : null isset($matches[4]) ? $matches[4] : null
); );
} elseif (preg_match(self::TEST_YAML_START, $line, $matches)) { return true;
}
return false;
}
/**
* @param string $line
*
* @return boolean
*/
protected function yamlLine($line)
{
if (preg_match(self::TEST_YAML_START, $line, $matches)) {
$diagnostic = $this->processYamlBlock($matches[1]); $diagnostic = $this->processYamlBlock($matches[1]);
$test = array_pop($this->results); $test = array_pop($this->results);
if (isset($test['message'], $diagnostic['message'])) { if (isset($test['message'], $diagnostic['message'])) {
$test['message'] .= PHP_EOL . $diagnostic['message']; $test['message'] .= PHP_EOL . $diagnostic['message'];
unset($diagnostic['message']); unset($diagnostic['message']);
} }
$this->results[] = array_replace($test, $diagnostic); $this->results[] = array_replace($test, $diagnostic);
} else { return true;
throw new Exception(sprintf('Incorrect TAP data, line %d: %s', $this->lineNumber, $line));
} }
return false;
}
/** Parse a single line.
*
* @param string $line
*
* @throws Exception
*/
protected function parseLine($line)
{
if (preg_match(self::TEST_DIAGNOSTIC, $line) || preg_match(self::TEST_COVERAGE, $line) || !$line) {
return;
}
if (preg_match(self::TEST_COUNTS_PATTERN, $line, $matches)) {
$this->testCount = intval($matches[1]);
return;
}
if ($this->testLine($line)) {
return;
}
if ($this->yamlLine($line)) {
return;
}
throw new Exception(sprintf('Incorrect TAP data, line %d: %s', $this->lineNumber, $line));
} }
/** /**
@ -196,18 +235,20 @@ class TapParser
*/ */
protected function processYamlBlock($indent) protected function processYamlBlock($indent)
{ {
$startLine = $this->lineNumber+1; $startLine = $this->lineNumber + 1;
$endLine = $indent.'...'; $endLine = $indent . '...';
$yamlLines = array(); $yamlLines = array();
do { do {
$line = $this->nextLine(); $line = $this->nextLine();
if ($line === false) { if ($line === false) {
throw new Exception(Lang::get('tap_error_endless_yaml', $startLine)); throw new Exception(Lang::get('tap_error_endless_yaml', $startLine));
} elseif ($line === $endLine) { } elseif ($line === $endLine) {
break; break;
} }
$yamlLines[] = substr($line, strlen($indent));
$yamlLines[] = substr($line, strlen($indent));
} while (true); } while (true);
return Yaml::parse(join("\n", $yamlLines)); return Yaml::parse(join("\n", $yamlLines));

View file

@ -20,6 +20,7 @@ class Codeception implements ParserInterface
protected $totalTests; protected $totalTests;
protected $totalTimeTaken; protected $totalTimeTaken;
protected $totalFailures; protected $totalFailures;
protected $totalErrors;
/** /**
* @param Builder $phpci * @param Builder $phpci
@ -29,7 +30,6 @@ class Codeception implements ParserInterface
{ {
$this->phpci = $phpci; $this->phpci = $phpci;
$this->resultsXml = $resultsXml; $this->resultsXml = $resultsXml;
$this->totalTests = 0; $this->totalTests = 0;
} }
@ -47,6 +47,7 @@ class Codeception implements ParserInterface
$this->totalTests += (int) $testsuite['tests']; $this->totalTests += (int) $testsuite['tests'];
$this->totalTimeTaken += (float) $testsuite['time']; $this->totalTimeTaken += (float) $testsuite['time'];
$this->totalFailures += (int) $testsuite['failures']; $this->totalFailures += (int) $testsuite['failures'];
$this->totalErrors += (int) $testsuite['errors'];
foreach ($testsuite->testcase as $testcase) { foreach ($testsuite->testcase as $testcase) {
$testresult = array( $testresult = array(
@ -67,9 +68,9 @@ class Codeception implements ParserInterface
$testresult['feature'] = sprintf('%s::%s', $testresult['class'], $testresult['name']); $testresult['feature'] = sprintf('%s::%s', $testresult['class'], $testresult['name']);
} }
if (isset($testcase->failure)) { if (isset($testcase->failure) || isset($testcase->error)) {
$testresult['pass'] = false; $testresult['pass'] = false;
$testresult['message'] = (string) $testcase->failure; $testresult['message'] = (string)$testcase->failure . (string)$testcase->error;
} else { } else {
$testresult['pass'] = true; $testresult['pass'] = true;
} }
@ -108,6 +109,6 @@ class Codeception implements ParserInterface
*/ */
public function getTotalFailures() public function getTotalFailures()
{ {
return $this->totalFailures; return $this->totalFailures + $this->totalErrors;
} }
} }

View file

@ -43,7 +43,7 @@ class Wipe implements \PHPCI\Plugin
$path = $phpci->buildPath; $path = $phpci->buildPath;
$this->phpci = $phpci; $this->phpci = $phpci;
$this->build = $build; $this->build = $build;
$this->directory = isset($options['directory']) ? $options['directory'] : $path; $this->directory = isset($options['directory']) ? $this->phpci->interpolate($options['directory']) : $path;
} }
/** /**

View file

@ -132,8 +132,9 @@ class XMPP implements \PHPCI\Plugin
*/ */
public function findConfigFile() public function findConfigFile()
{ {
if (file_exists($this->phpci->buildPath . '/.sendxmpprc')) { if (file_exists($this->phpci->buildPath . DIRECTORY_SEPARATOR . '.sendxmpprc')) {
if (md5(file_get_contents($this->phpci->buildPath . '/.sendxmpprc')) !== md5($this->getConfigFormat())) { if (md5(file_get_contents($this->phpci->buildPath . DIRECTORY_SEPARATOR . '.sendxmpprc'))
!== md5($this->getConfigFormat())) {
return null; return null;
} }
@ -160,7 +161,7 @@ class XMPP implements \PHPCI\Plugin
/* /*
* Try to build conf file * Try to build conf file
*/ */
$config_file = $this->phpci->buildPath . '/.sendxmpprc'; $config_file = $this->phpci->buildPath . DIRECTORY_SEPARATOR . '.sendxmpprc';
if (is_null($this->findConfigFile())) { if (is_null($this->findConfigFile())) {
file_put_contents($config_file, $this->getConfigFormat()); file_put_contents($config_file, $this->getConfigFormat());
chmod($config_file, 0600); chmod($config_file, 0600);
@ -174,7 +175,7 @@ class XMPP implements \PHPCI\Plugin
$tls = ' -t'; $tls = ' -t';
} }
$message_file = $this->phpci->buildPath . '/' . uniqid('xmppmessage'); $message_file = $this->phpci->buildPath . DIRECTORY_SEPARATOR . uniqid('xmppmessage');
if ($this->buildMessage($message_file) === false) { if ($this->buildMessage($message_file) === false) {
return false; return false;
} }

View file

@ -47,7 +47,7 @@ class Factory
*/ */
public static function createProcessControl() public static function createProcessControl()
{ {
switch(true) { switch (true) {
case PosixProcessControl::isAvailable(): case PosixProcessControl::isAvailable():
return new PosixProcessControl(); return new PosixProcessControl();

View file

@ -31,7 +31,7 @@ class PosixProcessControl implements ProcessControlInterface
* Sends a TERMINATE or KILL signal to the process using posix_kill. * Sends a TERMINATE or KILL signal to the process using posix_kill.
* *
* @param int $pid * @param int $pid
* @param bool $forcefully Whetehr to send TERMINATE (false) or KILL (true). * @param bool $forcefully Whether to send TERMINATE (false) or KILL (true).
*/ */
public function kill($pid, $forcefully = false) public function kill($pid, $forcefully = false)
{ {

View file

@ -9,6 +9,10 @@
namespace PHPCI\Service; namespace PHPCI\Service;
use b8\Config;
use Pheanstalk\Pheanstalk;
use Pheanstalk\PheanstalkInterface;
use PHPCI\BuildFactory;
use PHPCI\Helper\Lang; use PHPCI\Helper\Lang;
use PHPCI\Model\Build; use PHPCI\Model\Build;
use PHPCI\Model\Project; use PHPCI\Model\Project;
@ -81,7 +85,17 @@ class BuildService
$build->setExtra(json_encode($extra)); $build->setExtra(json_encode($extra));
} }
return $this->buildStore->save($build); $build = $this->buildStore->save($build);
$buildId = $build->getId();
if (!empty($buildId)) {
$build = BuildFactory::getBuild($build);
$build->sendStatusPostback();
$this->addBuildToQueue($build);
}
return $build;
} }
/** /**
@ -104,7 +118,17 @@ class BuildService
$build->setCreated(new \DateTime()); $build->setCreated(new \DateTime());
$build->setStatus(0); $build->setStatus(0);
return $this->buildStore->save($build); $build = $this->buildStore->save($build);
$buildId = $build->getId();
if (!empty($buildId)) {
$build = BuildFactory::getBuild($build);
$build->sendStatusPostback();
$this->addBuildToQueue($build);
}
return $build;
} }
/** /**
@ -117,4 +141,41 @@ class BuildService
$build->removeBuildDirectory(); $build->removeBuildDirectory();
return $this->buildStore->delete($build); return $this->buildStore->delete($build);
} }
/**
* Takes a build and puts it into the queue to be run (if using a queue)
* @param Build $build
*/
public function addBuildToQueue(Build $build)
{
$buildId = $build->getId();
if (empty($buildId)) {
return;
}
$config = Config::getInstance();
$settings = $config->get('phpci.worker', []);
if (!empty($settings['host']) && !empty($settings['queue'])) {
$jobData = array(
'type' => 'phpci.build',
'build_id' => $build->getId(),
);
if ($config->get('using_custom_file')) {
$jobData['config'] = $config->getArray();
}
$pheanstalk = new Pheanstalk($settings['host']);
$pheanstalk->useTube($settings['queue']);
$pheanstalk->put(
json_encode($jobData),
PheanstalkInterface::DEFAULT_PRIORITY,
PheanstalkInterface::DEFAULT_DELAY,
$config->get('phpci.worker.job_timeout', 600)
);
}
}
} }

View file

@ -89,6 +89,10 @@ class ProjectService
$project->setBranch($options['branch']); $project->setBranch($options['branch']);
} }
if (array_key_exists('group', $options)) {
$project->setGroup($options['group']);
}
// Allow certain project types to set access information: // Allow certain project types to set access information:
$this->processAccessInformation($project); $this->processAccessInformation($project);

View file

@ -0,0 +1,85 @@
<?php
/**
* BuildError base store for table: build_error
*/
namespace PHPCI\Store\Base;
use b8\Database;
use b8\Exception\HttpException;
use PHPCI\Store;
use PHPCI\Model\BuildError;
/**
* BuildError Base Store
*/
class BuildErrorStoreBase extends Store
{
protected $tableName = 'build_error';
protected $modelName = '\PHPCI\Model\BuildError';
protected $primaryKey = 'id';
/**
* Get a BuildError by primary key (Id)
*/
public function getByPrimaryKey($value, $useConnection = 'read')
{
return $this->getById($value, $useConnection);
}
/**
* Get a single BuildError by Id.
* @return null|BuildError
*/
public function getById($value, $useConnection = 'read')
{
if (is_null($value)) {
throw new HttpException('Value passed to ' . __FUNCTION__ . ' cannot be null.');
}
$query = 'SELECT * FROM `build_error` WHERE `id` = :id LIMIT 1';
$stmt = Database::getConnection($useConnection)->prepare($query);
$stmt->bindValue(':id', $value);
if ($stmt->execute()) {
if ($data = $stmt->fetch(\PDO::FETCH_ASSOC)) {
return new BuildError($data);
}
}
return null;
}
/**
* Get multiple BuildError by BuildId.
* @return array
*/
public function getByBuildId($value, $limit = 1000, $useConnection = 'read')
{
if (is_null($value)) {
throw new HttpException('Value passed to ' . __FUNCTION__ . ' cannot be null.');
}
$query = 'SELECT * FROM `build_error` WHERE `build_id` = :build_id LIMIT :limit';
$stmt = Database::getConnection($useConnection)->prepare($query);
$stmt->bindValue(':build_id', $value);
$stmt->bindValue(':limit', (int)$limit, \PDO::PARAM_INT);
if ($stmt->execute()) {
$res = $stmt->fetchAll(\PDO::FETCH_ASSOC);
$map = function ($item) {
return new BuildError($item);
};
$rtn = array_map($map, $res);
$count = count($rtn);
return array('items' => $rtn, 'count' => $count);
} else {
return array('items' => array(), 'count' => 0);
}
}
}

View file

@ -21,10 +21,7 @@ class BuildMetaStoreBase extends Store
protected $primaryKey = 'id'; protected $primaryKey = 'id';
/** /**
* Returns a BuildMeta model by primary key. * Get a BuildMeta by primary key (Id)
* @param mixed $value
* @param string $useConnection
* @return \@appNamespace\Model\BuildMeta|null
*/ */
public function getByPrimaryKey($value, $useConnection = 'read') public function getByPrimaryKey($value, $useConnection = 'read')
{ {
@ -32,11 +29,8 @@ class BuildMetaStoreBase extends Store
} }
/** /**
* Returns a BuildMeta model by Id. * Get a single BuildMeta by Id.
* @param mixed $value * @return null|BuildMeta
* @param string $useConnection
* @throws HttpException
* @return \@appNamespace\Model\BuildMeta|null
*/ */
public function getById($value, $useConnection = 'read') public function getById($value, $useConnection = 'read')
{ {
@ -58,11 +52,7 @@ class BuildMetaStoreBase extends Store
} }
/** /**
* Returns an array of BuildMeta models by ProjectId. * Get multiple BuildMeta by ProjectId.
* @param mixed $value
* @param int $limit
* @param string $useConnection
* @throws HttpException
* @return array * @return array
*/ */
public function getByProjectId($value, $limit = 1000, $useConnection = 'read') public function getByProjectId($value, $limit = 1000, $useConnection = 'read')
@ -94,11 +84,7 @@ class BuildMetaStoreBase extends Store
} }
/** /**
* Returns an array of BuildMeta models by BuildId. * Get multiple BuildMeta by BuildId.
* @param mixed $value
* @param int $limit
* @param string $useConnection
* @throws HttpException
* @return array * @return array
*/ */
public function getByBuildId($value, $limit = 1000, $useConnection = 'read') public function getByBuildId($value, $limit = 1000, $useConnection = 'read')

View file

@ -21,10 +21,7 @@ class BuildStoreBase extends Store
protected $primaryKey = 'id'; protected $primaryKey = 'id';
/** /**
* Returns a Build model by primary key. * Get a Build by primary key (Id)
* @param mixed $value
* @param string $useConnection
* @return \@appNamespace\Model\Build|null
*/ */
public function getByPrimaryKey($value, $useConnection = 'read') public function getByPrimaryKey($value, $useConnection = 'read')
{ {
@ -32,11 +29,8 @@ class BuildStoreBase extends Store
} }
/** /**
* Returns a Build model by Id. * Get a single Build by Id.
* @param mixed $value * @return null|Build
* @param string $useConnection
* @throws HttpException
* @return \@appNamespace\Model\Build|null
*/ */
public function getById($value, $useConnection = 'read') public function getById($value, $useConnection = 'read')
{ {
@ -58,11 +52,7 @@ class BuildStoreBase extends Store
} }
/** /**
* Returns an array of Build models by ProjectId. * Get multiple Build by ProjectId.
* @param mixed $value
* @param int $limit
* @param string $useConnection
* @throws HttpException
* @return array * @return array
*/ */
public function getByProjectId($value, $limit = 1000, $useConnection = 'read') public function getByProjectId($value, $limit = 1000, $useConnection = 'read')
@ -94,11 +84,7 @@ class BuildStoreBase extends Store
} }
/** /**
* Returns an array of Build models by Status. * Get multiple Build by Status.
* @param mixed $value
* @param int $limit
* @param string $useConnection
* @throws HttpException
* @return array * @return array
*/ */
public function getByStatus($value, $limit = 1000, $useConnection = 'read') public function getByStatus($value, $limit = 1000, $useConnection = 'read')

View file

@ -0,0 +1,53 @@
<?php
/**
* ProjectGroup base store for table: project_group
*/
namespace PHPCI\Store\Base;
use b8\Database;
use b8\Exception\HttpException;
use PHPCI\Store;
use PHPCI\Model\ProjectGroup;
/**
* ProjectGroup Base Store
*/
class ProjectGroupStoreBase extends Store
{
protected $tableName = 'project_group';
protected $modelName = '\PHPCI\Model\ProjectGroup';
protected $primaryKey = 'id';
/**
* Get a ProjectGroup by primary key (Id)
*/
public function getByPrimaryKey($value, $useConnection = 'read')
{
return $this->getById($value, $useConnection);
}
/**
* Get a single ProjectGroup by Id.
* @return null|ProjectGroup
*/
public function getById($value, $useConnection = 'read')
{
if (is_null($value)) {
throw new HttpException('Value passed to ' . __FUNCTION__ . ' cannot be null.');
}
$query = 'SELECT * FROM `project_group` WHERE `id` = :id LIMIT 1';
$stmt = Database::getConnection($useConnection)->prepare($query);
$stmt->bindValue(':id', $value);
if ($stmt->execute()) {
if ($data = $stmt->fetch(\PDO::FETCH_ASSOC)) {
return new ProjectGroup($data);
}
}
return null;
}
}

View file

@ -21,10 +21,7 @@ class ProjectStoreBase extends Store
protected $primaryKey = 'id'; protected $primaryKey = 'id';
/** /**
* Returns a Project model by primary key. * Get a Project by primary key (Id)
* @param mixed $value
* @param string $useConnection
* @return \@appNamespace\Model\Project|null
*/ */
public function getByPrimaryKey($value, $useConnection = 'read') public function getByPrimaryKey($value, $useConnection = 'read')
{ {
@ -32,11 +29,8 @@ class ProjectStoreBase extends Store
} }
/** /**
* Returns a Project model by Id. * Get a single Project by Id.
* @param mixed $value * @return null|Project
* @param string $useConnection
* @throws HttpException
* @return \@appNamespace\Model\Project|null
*/ */
public function getById($value, $useConnection = 'read') public function getById($value, $useConnection = 'read')
{ {
@ -58,11 +52,7 @@ class ProjectStoreBase extends Store
} }
/** /**
* Returns an array of Project models by Title. * Get multiple Project by Title.
* @param mixed $value
* @param int $limit
* @param string $useConnection
* @throws HttpException
* @return array * @return array
*/ */
public function getByTitle($value, $limit = 1000, $useConnection = 'read') public function getByTitle($value, $limit = 1000, $useConnection = 'read')
@ -92,4 +82,36 @@ class ProjectStoreBase extends Store
return array('items' => array(), 'count' => 0); return array('items' => array(), 'count' => 0);
} }
} }
/**
* Get multiple Project by GroupId.
* @return array
*/
public function getByGroupId($value, $limit = 1000, $useConnection = 'read')
{
if (is_null($value)) {
throw new HttpException('Value passed to ' . __FUNCTION__ . ' cannot be null.');
}
$query = 'SELECT * FROM `project` WHERE `group_id` = :group_id LIMIT :limit';
$stmt = Database::getConnection($useConnection)->prepare($query);
$stmt->bindValue(':group_id', $value);
$stmt->bindValue(':limit', (int)$limit, \PDO::PARAM_INT);
if ($stmt->execute()) {
$res = $stmt->fetchAll(\PDO::FETCH_ASSOC);
$map = function ($item) {
return new Project($item);
};
$rtn = array_map($map, $res);
$count = count($rtn);
return array('items' => $rtn, 'count' => $count);
} else {
return array('items' => array(), 'count' => 0);
}
}
} }

View file

@ -21,10 +21,7 @@ class UserStoreBase extends Store
protected $primaryKey = 'id'; protected $primaryKey = 'id';
/** /**
* Returns a User model by primary key. * Get a User by primary key (Id)
* @param mixed $value
* @param string $useConnection
* @return \@appNamespace\Model\User|null
*/ */
public function getByPrimaryKey($value, $useConnection = 'read') public function getByPrimaryKey($value, $useConnection = 'read')
{ {
@ -32,11 +29,8 @@ class UserStoreBase extends Store
} }
/** /**
* Returns a User model by Id. * Get a single User by Id.
* @param mixed $value * @return null|User
* @param string $useConnection
* @throws HttpException
* @return \@appNamespace\Model\User|null
*/ */
public function getById($value, $useConnection = 'read') public function getById($value, $useConnection = 'read')
{ {
@ -58,11 +52,8 @@ class UserStoreBase extends Store
} }
/** /**
* Returns a User model by Email. * Get a single User by Email.
* @param string $value * @return null|User
* @param string $useConnection
* @throws HttpException
* @return \@appNamespace\Model\User|null
*/ */
public function getByEmail($value, $useConnection = 'read') public function getByEmail($value, $useConnection = 'read')
{ {
@ -84,28 +75,34 @@ class UserStoreBase extends Store
} }
/** /**
* Returns a User model by Email. * Get multiple User by Name.
* @param string $value * @return array
* @param string $useConnection
* @throws HttpException
* @return \@appNamespace\Model\User|null
*/ */
public function getByLoginOrEmail($value, $useConnection = 'read') public function getByName($value, $limit = 1000, $useConnection = 'read')
{ {
if (is_null($value)) { if (is_null($value)) {
throw new HttpException('Value passed to ' . __FUNCTION__ . ' cannot be null.'); throw new HttpException('Value passed to ' . __FUNCTION__ . ' cannot be null.');
} }
$query = 'SELECT * FROM `user` WHERE `name` = :value OR `email` = :value LIMIT 1';
$query = 'SELECT * FROM `user` WHERE `name` = :name LIMIT :limit';
$stmt = Database::getConnection($useConnection)->prepare($query); $stmt = Database::getConnection($useConnection)->prepare($query);
$stmt->bindValue(':value', $value); $stmt->bindValue(':name', $value);
$stmt->bindValue(':limit', (int)$limit, \PDO::PARAM_INT);
if ($stmt->execute()) { if ($stmt->execute()) {
if ($data = $stmt->fetch(\PDO::FETCH_ASSOC)) { $res = $stmt->fetchAll(\PDO::FETCH_ASSOC);
return new User($data);
}
}
return null; $map = function ($item) {
return new User($item);
};
$rtn = array_map($map, $res);
$count = count($rtn);
return array('items' => $rtn, 'count' => $count);
} else {
return array('items' => array(), 'count' => 0);
}
} }
} }

View file

@ -0,0 +1,80 @@
<?php
/**
* BuildError store for table: build_error
*/
namespace PHPCI\Store;
use b8\Database;
use PHPCI\Model\BuildError;
use PHPCI\Store\Base\BuildErrorStoreBase;
/**
* BuildError Store
* @uses PHPCI\Store\Base\BuildErrorStoreBase
*/
class BuildErrorStore extends BuildErrorStoreBase
{
/**
* Get a list of errors for a given build, since a given time.
* @param $buildId
* @param string $since date string
* @return array
*/
public function getErrorsForBuild($buildId, $since = null)
{
$query = 'SELECT * FROM build_error
WHERE build_id = :build';
if (!is_null($since)) {
$query .= ' AND created_date > :since';
}
$query .= ' LIMIT 15000';
$stmt = Database::getConnection('read')->prepare($query);
$stmt->bindValue(':build', $buildId, \PDO::PARAM_INT);
if (!is_null($since)) {
$stmt->bindValue(':since', $since);
}
if ($stmt->execute()) {
$res = $stmt->fetchAll(\PDO::FETCH_ASSOC);
$map = function ($item) {
return new BuildError($item);
};
$rtn = array_map($map, $res);
return $rtn;
} else {
return array();
}
}
/**
* Gets the total number of errors for a given build.
* @param $buildId
* @param string $since date string
* @return array
*/
public function getErrorTotalForBuild($buildId)
{
$query = 'SELECT COUNT(*) AS total FROM build_error
WHERE build_id = :build';
$stmt = Database::getConnection('read')->prepare($query);
$stmt->bindValue(':build', $buildId, \PDO::PARAM_INT);
if ($stmt->execute()) {
$res = $stmt->fetch(\PDO::FETCH_ASSOC);
return $res['total'];
} else {
return array();
}
}
}

Some files were not shown because too many files have changed in this diff Show more