setEmailTo($user->getEmail(), $user->getName());
+ $email->setSubject('Password reset');
+ $email->setBody($message);
+ $email->send();
+
+ $this->view->emailed = true;
+ }
+
+ return $this->view->render();
+ }
+
+ public function resetPassword($userId, $key)
+ {
+ $user = $this->userStore->getById($userId);
+ $userKey = md5(date('Y-m-d') . $user->getHash());
+
+ if (empty($user) || $key != $userKey) {
+ $this->view->error = 'Invalid password reset request.';
+ return $this->view->render();
+ }
+
+ if ($this->request->getMethod() == 'POST') {
+ $hash = password_hash($this->getParam('password'), PASSWORD_DEFAULT);
+ $user->setHash($hash);
+
+ $_SESSION['user'] = $this->userStore->save($user);
+ $_SESSION['user_id'] = $user->getId();
+
+ header('Location: ' . PHPCI_URL);
+ die;
+ }
+
+ $this->view->id = $userId;
+ $this->view->key = $key;
+
+ return $this->view->render();
+ }
+
+ protected function getLoginRedirect()
+ {
+ $rtn = PHPCI_URL;
+
+ if (!empty($_SESSION['login_redirect'])) {
+ $rtn .= $_SESSION['login_redirect'];
+ $_SESSION['login_redirect'] = null;
+ }
+
+ return $rtn;
+ }
}
diff --git a/PHPCI/Controller/SettingsController.php b/PHPCI/Controller/SettingsController.php
index 2e0fc5ec..ebcab7e8 100644
--- a/PHPCI/Controller/SettingsController.php
+++ b/PHPCI/Controller/SettingsController.php
@@ -2,9 +2,9 @@
/**
* PHPCI - Continuous Integration for PHP
*
- * @copyright Copyright 2013, Block 8 Limited.
+ * @copyright Copyright 2014, Block 8 Limited.
* @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md
- * @link http://www.phptesting.org/
+ * @link https://www.phptesting.org/
*/
namespace PHPCI\Controller;
@@ -48,6 +48,7 @@ class SettingsController extends Controller
$this->view->github = $this->getGithubForm();
$this->view->emailSettings = $this->getEmailForm($emailSettings);
+ $this->view->isWriteable = $this->canWriteConfig();
if (!empty($this->settings['phpci']['github']['token'])) {
$this->view->githubUser = $this->getGithubUser($this->settings['phpci']['github']['token']);
@@ -242,4 +243,9 @@ class SettingsController extends Controller
return $user['body'];
}
+
+ protected function canWriteConfig()
+ {
+ return is_writeable(APPLICATION_PATH . 'PHPCI/config.yml');
+ }
}
diff --git a/PHPCI/Controller/UserController.php b/PHPCI/Controller/UserController.php
index c8fade0c..86fa8662 100644
--- a/PHPCI/Controller/UserController.php
+++ b/PHPCI/Controller/UserController.php
@@ -1,15 +1,17 @@
userStore = b8\Store\Factory::getStore('User');
+ $this->userStore = b8\Store\Factory::getStore('User');
}
/**
@@ -45,18 +47,72 @@ class UserController extends Controller
return $this->view->render();
}
+ public function profile()
+ {
+ $user = $_SESSION['user'];
+ $values = $user->getDataArray();
+
+ if ($this->request->getMethod() == 'POST') {
+ $values = $this->getParams();
+
+ if (!empty($values['password'])) {
+ $values['hash'] = password_hash($values['password'], PASSWORD_DEFAULT);
+ }
+
+ $this->view->updated = true;
+
+ $user->setValues($values);
+ $_SESSION['user'] = $this->userStore->save($user);
+ }
+
+ $form = new Form();
+ $form->setAction(PHPCI_URL.'user/profile');
+ $form->setMethod('POST');
+
+ $name = new Form\Element\Text('name');
+ $name->setClass('form-control');
+ $name->setContainerClass('form-group');
+ $name->setLabel('Name');
+ $name->setRequired(true);
+ $form->addField($name);
+
+ $email = new Form\Element\Email('email');
+ $email->setClass('form-control');
+ $email->setContainerClass('form-group');
+ $email->setLabel('Email Address');
+ $email->setRequired(true);
+ $form->addField($email);
+
+ $password = new Form\Element\Password('password');
+ $password->setClass('form-control');
+ $password->setContainerClass('form-group');
+ $password->setLabel('Password (leave blank if you don\'t want to change it)');
+ $password->setRequired(false);
+ $form->addField($password);
+
+ $submit = new Form\Element\Submit();
+ $submit->setClass('btn btn-success');
+ $submit->setValue('Save »');
+ $form->addField($submit);
+
+ $form->setValues($values);
+
+ $this->view->form = $form;
+
+ return $this->view->render();
+ }
+
/**
* Add a user - handles both form and processing.
*/
public function add()
{
if (!$_SESSION['user']->getIsAdmin()) {
- throw new \Exception('You do not have permission to do that.');
+ throw new ForbiddenException('You do not have permission to do that.');
}
$this->config->set('page_title', 'Add User');
-
$method = $this->request->getMethod();
if ($method == 'POST') {
@@ -77,7 +133,6 @@ class UserController extends Controller
}
$values = $form->getValues();
- $values['is_admin'] = $values['admin'] ? 1 : 0;
$values['hash'] = password_hash($values['password'], PASSWORD_DEFAULT);
$user = new User();
@@ -95,42 +150,40 @@ class UserController extends Controller
public function edit($userId)
{
if (!$_SESSION['user']->getIsAdmin()) {
- throw new \Exception('You do not have permission to do that.');
+ throw new ForbiddenException('You do not have permission to do that.');
}
- $method = $this->request->getMethod();
- $user = $this->userStore->getById($userId);
+ $method = $this->request->getMethod();
+ $user = $this->userStore->getById($userId);
- $this->config->set('page_title', 'Edit: ' . $user->getName());
-
-
- if ($method == 'POST') {
- $values = $this->getParams();
- } else {
- $values = $user->getDataArray();
- $values['admin'] = $values['is_admin'];
+ if (empty($user)) {
+ throw new NotFoundException('User with ID: ' . $userId . ' does not exist.');
}
- $form = $this->userForm($values, 'edit/' . $userId);
+ $values = array_merge($user->getDataArray(), $this->getParams());
+ $form = $this->userForm($values, 'edit/' . $userId);
if ($method != 'POST' || ($method == 'POST' && !$form->validate())) {
- $view = new b8\View('UserForm');
- $view->type = 'edit';
- $view->user = $user;
- $view->form = $form;
+ $view = new b8\View('UserForm');
+ $view->type = 'edit';
+ $view->user = $user;
+ $view->form = $form;
return $view->render();
}
- $values = $form->getValues();
- $values['is_admin'] = $values['admin'] ? 1 : 0;
-
if (!empty($values['password'])) {
$values['hash'] = password_hash($values['password'], PASSWORD_DEFAULT);
}
$user->setValues($values);
- $user = $this->userStore->save($user);
+
+ $isAdmin = $this->getParam('is_admin');
+ if (empty($isAdmin)) {
+ $user->setIsAdmin(0);
+ }
+
+ $this->userStore->save($user);
header('Location: '.PHPCI_URL.'user');
die;
@@ -161,13 +214,20 @@ class UserController extends Controller
$form->addField($field);
$field = new Form\Element\Password('password');
- $field->setRequired(true);
- $field->setLabel('Password' . ($type == 'edit' ? ' (leave blank to keep current password)' : ''));
+
+ if ($type == 'add') {
+ $field->setRequired(true);
+ $field->setLabel('Password');
+ } else {
+ $field->setRequired(false);
+ $field->setLabel('Password (leave blank to keep current password)');
+ }
+
$field->setClass('form-control');
$field->setContainerClass('form-group');
$form->addField($field);
- $field = new Form\Element\Checkbox('admin');
+ $field = new Form\Element\Checkbox('is_admin');
$field->setRequired(false);
$field->setCheckedValue(1);
$field->setLabel('Is this user an administrator?');
@@ -189,10 +249,15 @@ class UserController extends Controller
public function delete($userId)
{
if (!$_SESSION['user']->getIsAdmin()) {
- throw new \Exception('You do not have permission to do that.');
+ throw new ForbiddenException('You do not have permission to do that.');
}
$user = $this->userStore->getById($userId);
+
+ if (empty($user)) {
+ throw new NotFoundException('User with ID: ' . $userId . ' does not exist.');
+ }
+
$this->userStore->delete($user);
header('Location: '.PHPCI_URL.'user');
diff --git a/PHPCI/Controller/WebhookController.php b/PHPCI/Controller/WebhookController.php
index 9a9d143b..e95a5597 100644
--- a/PHPCI/Controller/WebhookController.php
+++ b/PHPCI/Controller/WebhookController.php
@@ -2,15 +2,16 @@
/**
* PHPCI - Continuous Integration for PHP
*
- * @copyright Copyright 2013, Block 8 Limited.
+ * @copyright Copyright 2014, Block 8 Limited.
* @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md
- * @link http://www.phptesting.org/
+ * @link https://www.phptesting.org/
*/
namespace PHPCI\Controller;
use b8;
use b8\Store;
+use PHPCI\BuildFactory;
use PHPCI\Model\Build;
/**
@@ -42,21 +43,16 @@ class WebhookController extends \PHPCI\Controller
foreach ($payload['commits'] as $commit) {
try {
+
$email = $commit['raw_author'];
$email = substr($email, 0, strpos($email, '>'));
$email = substr($email, strpos($email, '<') + 1);
- $build = new Build();
- $build->setProjectId($project);
- $build->setCommitId($commit['raw_node']);
- $build->setCommitterEmail($email);
- $build->setStatus(Build::STATUS_NEW);
- $build->setLog('');
- $build->setCreated(new \DateTime());
- $build->setBranch($commit['branch']);
- $build->setCommitMessage($commit['message']);
- $this->buildStore->save($build);
+ $this->createBuild($project, $commit['raw_node'], $commit['branch'], $email, $commit['message']);
} catch (\Exception $ex) {
+ header('HTTP/1.1 500 Internal Server Error');
+ header('Ex: ' . $ex->getMessage());
+ die('FAIL');
}
}
@@ -74,36 +70,22 @@ class WebhookController extends \PHPCI\Controller
$commit = $this->getParam('commit');
try {
- $build = new Build();
- $build->setProjectId($project);
-
- if ($branch !== null && trim($branch) !== '') {
- $build->setBranch($branch);
- } else {
- $build->setBranch('master');
+ if (empty($branch)) {
+ $branch = 'master';
}
- if ($commit !== null && trim($commit) !== '') {
- $build->setCommitId($commit);
+ if (empty($commit)) {
+ $commit = null;
}
- $build->setStatus(Build::STATUS_NEW);
- $build->setLog('');
- $build->setCreated(new \DateTime());
+ $this->createBuild($project, $commit, $branch, null, null);
+
} catch (\Exception $ex) {
header('HTTP/1.1 400 Bad Request');
header('Ex: ' . $ex->getMessage());
die('FAIL');
}
- try {
- $this->buildStore->save($build); /** bugfix: Errors with PHPCI GitHub hook #296 */
- } catch (\Exception $ex) {
- header('HTTP/1.1 500 Internal Server Error');
- header('Ex: ' . $ex->getMessage());
- die('FAIL');
- }
-
die('OK');
}
@@ -112,11 +94,27 @@ class WebhookController extends \PHPCI\Controller
*/
public function github($project)
{
- $payload = json_decode($this->getParam('payload'), true);
+ $payload = json_decode($this->getParam('payload'), true);
+ // Handle Pull Request web hooks:
+ if (array_key_exists('pull_request', $payload)) {
+ return $this->githubPullRequest($project, $payload);
+ }
+
+ // Handle Push web hooks:
+ if (array_key_exists('commits', $payload)) {
+ return $this->githubCommitRequest($project, $payload);
+ }
+
+ header('HTTP/1.1 200 OK');
+ die('This request type is not supported, this is not an error.');
+ }
+
+ protected function githubCommitRequest($project, array $payload)
+ {
// Github sends a payload when you close a pull request with a
// non-existant commit. We don't want this.
- if ($payload['after'] === '0000000000000000000000000000000000000000') {
+ if (array_key_exists('after', $payload) && $payload['after'] === '0000000000000000000000000000000000000000') {
die('OK');
}
@@ -130,32 +128,16 @@ class WebhookController extends \PHPCI\Controller
continue;
}
- $build = new Build();
- $build->setProjectId($project);
- $build->setCommitId($commit['id']);
- $build->setStatus(Build::STATUS_NEW);
- $build->setLog('');
- $build->setCreated(new \DateTime());
- $build->setBranch(str_replace('refs/heads/', '', $payload['ref']));
- $build->setCommitterEmail($commit['committer']['email']);
- $build->setCommitMessage($commit['message']);
- $build = $this->buildStore->save($build);
- $build->sendStatusPostback();
+ $branch = str_replace('refs/heads/', '', $payload['ref']);
+ $committer = $commit['committer']['email'];
+ $this->createBuild($project, $commit['id'], $branch, $committer, $commit['message']);
}
} elseif (substr($payload['ref'], 0, 10) == 'refs/tags/') {
// If we don't, but we're dealing with a tag, add that instead:
- $build = new Build();
- $build->setProjectId($project);
- $build->setCommitId($payload['after']);
- $build->setStatus(Build::STATUS_NEW);
- $build->setLog('');
- $build->setCreated(new \DateTime());
- $build->setBranch(str_replace('refs/tags/', 'Tag: ', $payload['ref']));
- $build->setCommitterEmail($payload['pusher']['email']);
- $build->setCommitMessage($payload['head_commit']['message']);
-
- $build = $this->buildStore->save($build);
- $build->sendStatusPostback();
+ $branch = str_replace('refs/tags/', 'Tag: ', $payload['ref']);
+ $committer = $payload['pusher']['email'];
+ $message = $payload['head_commit']['message'];
+ $this->createBuild($project, $payload['after'], $branch, $committer, $message);
}
} catch (\Exception $ex) {
@@ -167,6 +149,57 @@ class WebhookController extends \PHPCI\Controller
die('OK');
}
+ protected function githubPullRequest($projectId, array $payload)
+ {
+ // We only want to know about open pull requests:
+ if (!in_array($payload['action'], array('opened', 'synchronize', 'reopened'))) {
+ die('OK');
+ }
+
+ try {
+ $headers = array();
+ $token = \b8\Config::getInstance()->get('phpci.github.token');
+
+ if (!empty($token)) {
+ $headers[] = 'Authorization: token ' . $token;
+ }
+
+ $url = $payload['pull_request']['commits_url'];
+ $http = new \b8\HttpClient();
+ $http->setHeaders($headers);
+ $response = $http->get($url);
+
+ // Check we got a success response:
+ if (!$response['success']) {
+ header('HTTP/1.1 500 Internal Server Error');
+ header('Ex: Could not get commits, failed API request.');
+ die('FAIL');
+ }
+
+ foreach ($response['body'] as $commit) {
+ $branch = str_replace('refs/heads/', '', $payload['pull_request']['base']['ref']);
+ $committer = $commit['commit']['author']['email'];
+ $message = $commit['commit']['message'];
+
+ $extra = array(
+ 'build_type' => 'pull_request',
+ 'pull_request_id' => $payload['pull_request']['id'],
+ 'pull_request_number' => $payload['number'],
+ 'remote_branch' => $payload['pull_request']['head']['ref'],
+ 'remote_url' => $payload['pull_request']['head']['repo']['clone_url'],
+ );
+
+ $this->createBuild($projectId, $commit['sha'], $branch, $committer, $message, $extra);
+ }
+ } catch (\Exception $ex) {
+ header('HTTP/1.1 500 Internal Server Error');
+ header('Ex: ' . $ex->getMessage());
+ die('FAIL');
+ }
+
+ die('OK');
+ }
+
/**
* Called by Gitlab Webhooks:
*/
@@ -181,17 +214,9 @@ class WebhookController extends \PHPCI\Controller
// If we have a list of commits, then add them all as builds to be tested:
foreach ($payload['commits'] as $commit) {
- $build = new Build();
- $build->setProjectId($project);
- $build->setCommitId($commit['id']);
- $build->setStatus(Build::STATUS_NEW);
- $build->setLog('');
- $build->setCreated(new \DateTime());
- $build->setBranch(str_replace('refs/heads/', '', $payload['ref']));
- $build->setCommitterEmail($commit['author']['email']);
- $build->setCommitMessage($commit['message']);
- $build = $this->buildStore->save($build);
- $build->sendStatusPostback();
+ $branch = str_replace('refs/heads/', '', $payload['ref']);
+ $committer = $commit['author']['email'];
+ $this->createBuild($project, $commit['id'], $branch, $committer, $commit['message']);
}
}
@@ -203,4 +228,36 @@ class WebhookController extends \PHPCI\Controller
die('OK');
}
+
+ protected function createBuild($projectId, $commitId, $branch, $committer, $commitMessage, $extra = null)
+ {
+ // Check if a build already exists for this commit ID:
+ $builds = $this->buildStore->getByProjectAndCommit($projectId, $commitId);
+
+ if ($builds['count']) {
+ return true;
+ }
+
+ // If not, create a new build job for it:
+ $build = new Build();
+ $build->setProjectId($projectId);
+ $build->setCommitId($commitId);
+ $build->setStatus(Build::STATUS_NEW);
+ $build->setLog('');
+ $build->setCreated(new \DateTime());
+ $build->setBranch($branch);
+ $build->setCommitterEmail($committer);
+ $build->setCommitMessage($commitMessage);
+
+ if (!is_null($extra)) {
+ $build->setExtra(json_encode($extra));
+ }
+
+ $build = BuildFactory::getBuild($this->buildStore->save($build));
+
+ // Send a status postback if the build type provides one:
+ $build->sendStatusPostback();
+
+ return true;
+ }
}
diff --git a/PHPCI/Helper/BaseCommandExecutor.php b/PHPCI/Helper/BaseCommandExecutor.php
new file mode 100644
index 00000000..9f13dab6
--- /dev/null
+++ b/PHPCI/Helper/BaseCommandExecutor.php
@@ -0,0 +1,108 @@
+logger = $logger;
+ $this->quiet = $quiet;
+ $this->verbose = $verbose;
+
+ $this->lastOutput = array();
+
+ $this->rootDir = $rootDir;
+ }
+
+ /**
+ * Executes shell commands.
+ * @param array $args
+ * @return bool Indicates success
+ */
+ public function executeCommand($args = array())
+ {
+ $this->lastOutput = array();
+
+ $command = call_user_func_array('sprintf', $args);
+
+ if ($this->quiet) {
+ $this->logger->log('Executing: ' . $command);
+ }
+
+ $status = 0;
+ exec($command, $this->lastOutput, $status);
+
+ foreach ($this->lastOutput as &$lastOutput) {
+ $lastOutput = trim($lastOutput, '"');
+ }
+
+ if ($this->logExecOutput && !empty($this->lastOutput) && ($this->verbose|| $status != 0)) {
+ $this->logger->log($this->lastOutput);
+ }
+
+ $rtn = false;
+
+ if ($status == 0) {
+ $rtn = true;
+ }
+
+ return $rtn;
+ }
+
+ /**
+ * Returns the output from the last command run.
+ */
+ public function getLastOutput()
+ {
+ return implode(PHP_EOL, $this->lastOutput);
+ }
+
+ /**
+ * Find a binary required by a plugin.
+ * @param string $binary
+ * @return null|string
+ */
+ abstract public function findBinary($binary);
+}
diff --git a/PHPCI/Helper/Build.php b/PHPCI/Helper/Build.php
index 3aa7403f..7e3d28d3 100644
--- a/PHPCI/Helper/Build.php
+++ b/PHPCI/Helper/Build.php
@@ -2,9 +2,9 @@
/**
* PHPCI - Continuous Integration for PHP
*
- * @copyright Copyright 2013, Block 8 Limited.
+ * @copyright Copyright 2014, Block 8 Limited.
* @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md
- * @link http://www.phptesting.org/
+ * @link https://www.phptesting.org/
*/
namespace PHPCI\Helper;
diff --git a/PHPCI/Helper/BuildInterpolator.php b/PHPCI/Helper/BuildInterpolator.php
index fef51bee..90b78681 100644
--- a/PHPCI/Helper/BuildInterpolator.php
+++ b/PHPCI/Helper/BuildInterpolator.php
@@ -1,4 +1,11 @@
logger = $logger;
- $this->quiet = $quiet;
- $this->verbose = $verbose;
-
- $this->lastOutput = array();
-
- $this->rootDir = $rootDir;
- }
-
/**
* Executes shell commands. Accepts multiple arguments the first
* is the template and everything else is inserted in. c.f. sprintf
* @return bool Indicates success
*/
- public function executeCommand()
- {
- return $this->buildAndExecuteCommand(func_get_args());
- }
-
- /**
- * Executes shell commands.
- * @param array $args
- * @return bool Indicates success
- */
- public function buildAndExecuteCommand($args = array())
- {
- $this->lastOutput = array();
-
- $command = call_user_func_array('sprintf', $args);
-
- if ($this->quiet) {
- $this->logger->log('Executing: ' . $command);
- }
-
- $status = 0;
- exec($command, $this->lastOutput, $status);
-
- foreach ($this->lastOutput as &$lastOutput) {
- $lastOutput = trim($lastOutput, '"');
- }
-
- if ($this->logExecOutput && !empty($this->lastOutput) && ($this->verbose|| $status != 0)) {
- $this->logger->log($this->lastOutput);
- }
-
- $rtn = false;
-
- if ($status == 0) {
- $rtn = true;
- }
-
- return $rtn;
- }
+ public function executeCommand();
/**
* Returns the output from the last command run.
*/
- public function getLastOutput()
- {
- return implode(PHP_EOL, $this->lastOutput);
- }
+ public function getLastOutput();
/**
* Find a binary required by a plugin.
- * @param $binary
+ * @param string $binary
* @return null|string
*/
- public function findBinary($binary)
- {
- if (is_string($binary)) {
- $binary = array($binary);
- }
-
- foreach ($binary as $bin) {
- $this->logger->log("Looking for binary: " . $bin, LogLevel::DEBUG);
- // Check project root directory:
- if (is_file($this->rootDir . $bin)) {
- $this->logger->log("Found in root: " . $bin, LogLevel::DEBUG);
- return $this->rootDir . $bin;
- }
-
- // Check Composer bin dir:
- if (is_file($this->rootDir . 'vendor/bin/' . $bin)) {
- $this->logger->log("Found in vendor/bin: " . $bin, LogLevel::DEBUG);
- return $this->rootDir . 'vendor/bin/' . $bin;
- }
-
- // Use "where" for windows and "which" for other OS
- $findCmd = IS_WIN ? 'where' : 'which';
- $findCmdResult = trim(shell_exec($findCmd . ' ' . $bin));
-
- if (!empty($findCmdResult)) {
- $this->logger->log("Found in " . $findCmdResult, LogLevel::DEBUG);
- return $findCmdResult;
- }
- }
-
- return null;
- }
+ public function findBinary($binary);
}
diff --git a/PHPCI/Helper/Email.php b/PHPCI/Helper/Email.php
new file mode 100644
index 00000000..7c44e0f7
--- /dev/null
+++ b/PHPCI/Helper/Email.php
@@ -0,0 +1,134 @@
+';
+
+ protected $emailTo = array();
+ protected $emailCc = array();
+ protected $subject = 'Email from PHPCI';
+ protected $body = '';
+ protected $isHtml = false;
+ protected $config;
+
+ public function __construct()
+ {
+ $this->config = Config::getInstance();
+ }
+
+ public function setEmailTo($email, $name = null)
+ {
+ $this->emailTo[$email] = $name;
+
+ return $this;
+ }
+
+ public function addCc($email, $name = null)
+ {
+ $this->emailCc[$email] = $name;
+
+ return $this;
+ }
+
+ public function setSubject($subject)
+ {
+ $this->subject = $subject;
+
+ return $this;
+ }
+
+ public function setBody($body)
+ {
+ $this->body = $body;
+
+ return $this;
+ }
+
+ public function setIsHtml($isHtml = false)
+ {
+ $this->isHtml = $isHtml;
+
+ return $this;
+ }
+
+ public function send()
+ {
+ $smtpServer = $this->config->get('phpci.email_settings.smtp_address');
+
+ if (empty($smtpServer)) {
+ return $this->sendViaMail();
+ } else {
+ return $this->sendViaSwiftMailer();
+ }
+ }
+
+ protected function sendViaMail()
+ {
+ $headers = '';
+
+ if ($this->isHtml) {
+ $headers = 'Content-Type: text/html' . PHP_EOL;
+ }
+
+ $headers .= 'From: ' . $this->getFrom() . PHP_EOL;
+
+ $emailTo = array();
+ foreach ($this->emailTo as $email => $name) {
+ $thisTo = $email;
+
+ if (!is_null($name)) {
+ $thisTo = '"' . $name . '" <' . $thisTo . '>';
+ }
+
+ $emailTo[] = $thisTo;
+ }
+
+ $emailTo = implode(', ', $emailTo);
+
+ return mail($emailTo, $this->subject, $this->body, $headers);
+ }
+
+ protected function sendViaSwiftMailer()
+ {
+ $factory = new MailerFactory($this->config->get('phpci'));
+ $mailer = $factory->getSwiftMailerFromConfig();
+
+ $message = \Swift_Message::newInstance($this->subject)
+ ->setFrom($this->getFrom())
+ ->setTo($this->emailTo)
+ ->setBody($this->body);
+
+ if ($this->isHtml) {
+ $message->setContentType('text/html');
+ }
+
+ if (is_array($this->emailCc) && count($this->emailCc)) {
+ $message->setCc($this->emailCc);
+ }
+
+ return $mailer->send($message);
+ }
+
+ protected function getFrom()
+ {
+ $email = $this->config->get('phpci.email_settings.from_address', self::DEFAULT_FROM);
+
+ if (empty($email)) {
+ $email = self::DEFAULT_FROM;
+ }
+
+ return $email;
+ }
+}
diff --git a/PHPCI/Helper/Github.php b/PHPCI/Helper/Github.php
index 6ce2d556..24d419d3 100644
--- a/PHPCI/Helper/Github.php
+++ b/PHPCI/Helper/Github.php
@@ -1,4 +1,11 @@
logger->log("Looking for binary: " . $bin, LogLevel::DEBUG);
+
+ if (is_file($this->rootDir . $bin)) {
+ $this->logger->log("Found in root: " . $bin, LogLevel::DEBUG);
+ $binaryPath = $this->rootDir . $bin;
+ break;
+ }
+
+ if (is_file($this->rootDir . 'vendor/bin/' . $bin)) {
+ $this->logger->log("Found in vendor/bin: " . $bin, LogLevel::DEBUG);
+ $binaryPath = $this->rootDir . 'vendor/bin/' . $bin;
+ break;
+ }
+
+ $findCmdResult = trim(shell_exec('which ' . $bin));
+ if (!empty($findCmdResult)) {
+ $this->logger->log("Found in " . $findCmdResult, LogLevel::DEBUG);
+ $binaryPath = $findCmdResult;
+ break;
+ }
+ }
+ return $binaryPath;
+ }
+}
diff --git a/PHPCI/Helper/User.php b/PHPCI/Helper/User.php
index 64872992..726003fd 100644
--- a/PHPCI/Helper/User.php
+++ b/PHPCI/Helper/User.php
@@ -1,11 +1,11 @@
logger->log("Looking for binary: " . $bin, LogLevel::DEBUG);
+
+ if (is_file($this->rootDir . $bin)) {
+ $this->logger->log("Found in root: " . $bin, LogLevel::DEBUG);
+ $binaryPath = $this->rootDir . $bin;
+ break;
+ }
+
+ if (is_file($this->rootDir . 'vendor/bin/' . $bin)) {
+ $this->logger->log("Found in vendor/bin: " . $bin, LogLevel::DEBUG);
+ $binaryPath = $this->rootDir . 'vendor/bin/' . $bin;
+ break;
+ }
+
+ $findCmdResult = trim(shell_exec('where ' . $bin));
+ if (!empty($findCmdResult)) {
+ $this->logger->log("Found in " . $findCmdResult, LogLevel::DEBUG);
+ $binaryPath = $findCmdResult;
+ break;
+ }
+ }
+ return $binaryPath;
+ }
+}
diff --git a/PHPCI/Logging/BuildDBLogHandler.php b/PHPCI/Logging/BuildDBLogHandler.php
index a877fe50..3c03c501 100644
--- a/PHPCI/Logging/BuildDBLogHandler.php
+++ b/PHPCI/Logging/BuildDBLogHandler.php
@@ -1,8 +1,14 @@
output = $output;
}
-
protected function write(array $record)
{
$this->output->writeln((string)$record['formatted']);
diff --git a/PHPCI/Migrations/20140513143726_initial_migration.php b/PHPCI/Migrations/20140513143726_initial_migration.php
new file mode 100644
index 00000000..8aad60b6
--- /dev/null
+++ b/PHPCI/Migrations/20140513143726_initial_migration.php
@@ -0,0 +1,218 @@
+createBuildTable();
+ $this->createBuildMetaTable();
+ $this->createProjectTable();
+ $this->createUserTable();
+
+ // Set up foreign keys:
+ $build = $this->table('build');
+
+ if (!$build->hasForeignKey('project_id')) {
+ $build->addForeignKey('project_id', 'project', 'id', array('delete'=> 'CASCADE', 'update' => 'CASCADE'));
+ }
+
+ $build->save();
+
+ $buildMeta = $this->table('build_meta');
+
+ if (!$buildMeta->hasForeignKey('build_id')) {
+ $buildMeta->addForeignKey('build_id', 'build', 'id', array('delete'=> 'CASCADE', 'update' => 'CASCADE'));
+ }
+
+ if (!$buildMeta->hasForeignKey('project_id')) {
+ $buildMeta->addForeignKey('project_id', 'project', 'id', array('delete'=> 'CASCADE', 'update' => 'CASCADE'));
+ }
+
+ $buildMeta->save();
+ }
+
+ /**
+ * Migrate Down.
+ */
+ public function down()
+ {
+
+ }
+
+ protected function createBuildTable()
+ {
+ $table = $this->table('build');
+
+ if (!$table->hasColumn('project_id')) {
+ $table->addColumn('project_id', 'integer');
+ }
+
+ if (!$table->hasColumn('commit_id')) {
+ $table->addColumn('commit_id', 'string', array('limit' => 50));
+ }
+
+ if (!$table->hasColumn('status')) {
+ $table->addColumn('status', 'integer', array('limit' => 4));
+ }
+
+ if (!$table->hasColumn('log')) {
+ $table->addColumn('log', 'text');
+ }
+
+ if (!$table->hasColumn('branch')) {
+ $table->addColumn('branch', 'string', array('limit' => 50));
+ }
+
+ if (!$table->hasColumn('created')) {
+ $table->addColumn('created', 'datetime');
+ }
+
+ if (!$table->hasColumn('started')) {
+ $table->addColumn('started', 'datetime');
+ }
+
+ if (!$table->hasColumn('finished')) {
+ $table->addColumn('finished', 'datetime');
+ }
+
+ if (!$table->hasColumn('committer_email')) {
+ $table->addColumn('committer_email', 'string', array('limit' => 250));
+ }
+
+ if (!$table->hasColumn('commit_message')) {
+ $table->addColumn('commit_message', 'text');
+ }
+
+ if (!$table->hasColumn('extra')) {
+ $table->addColumn('extra', 'text');
+ }
+
+ if ($table->hasColumn('plugins')) {
+ $table->removeColumn('plugins');
+ }
+
+ if (!$table->hasIndex(array('project_id'))) {
+ $table->addIndex(array('project_id'));
+ }
+
+ if (!$table->hasIndex(array('status'))) {
+ $table->addIndex(array('status'));
+ }
+
+ $table->save();
+ }
+
+ protected function createBuildMetaTable()
+ {
+ $table = $this->table('build_meta');
+
+ if (!$table->hasColumn('project_id')) {
+ $table->addColumn('project_id', 'integer');
+ }
+
+ if (!$table->hasColumn('build_id')) {
+ $table->addColumn('build_id', 'integer');
+ }
+
+ if (!$table->hasColumn('meta_key')) {
+ $table->addColumn('meta_key', 'string', array('limit' => 250));
+ }
+
+ if (!$table->hasColumn('meta_value')) {
+ $table->addColumn('meta_value', 'text');
+ }
+
+ if (!$table->hasIndex(array('build_id', 'meta_key'))) {
+ $table->addIndex(array('build_id', 'meta_key'));
+ }
+
+ $table->save();
+ }
+
+ protected function createProjectTable()
+ {
+ $table = $this->table('project');
+
+ if (!$table->hasColumn('title')) {
+ $table->addColumn('title', 'string', array('limit' => 250));
+ }
+
+ if (!$table->hasColumn('reference')) {
+ $table->addColumn('reference', 'string', array('limit' => 250));
+ }
+
+ if (!$table->hasColumn('git_key')) {
+ $table->addColumn('git_key', 'text');
+ }
+
+ if (!$table->hasColumn('public_key')) {
+ $table->addColumn('public_key', 'text');
+ }
+
+ if (!$table->hasColumn('type')) {
+ $table->addColumn('type', 'string', array('limit' => 50));
+ }
+
+ if (!$table->hasColumn('access_information')) {
+ $table->addColumn('access_information', 'string', array('limit' => 250));
+ }
+
+ if (!$table->hasColumn('last_commit')) {
+ $table->addColumn('last_commit', 'string', array('limit' => 250));
+ }
+
+ if (!$table->hasColumn('build_config')) {
+ $table->addColumn('build_config', 'text');
+ }
+
+ if (!$table->hasColumn('allow_public_status')) {
+ $table->addColumn('allow_public_status', 'integer');
+ }
+
+ if ($table->hasColumn('token')) {
+ $table->removeColumn('token');
+ }
+
+ if (!$table->hasIndex(array('title'))) {
+ $table->addIndex(array('title'));
+ }
+
+ $table->save();
+ }
+
+ protected function createUserTable()
+ {
+ $table = $this->table('user');
+
+ if (!$table->hasColumn('email')) {
+ $table->addColumn('email', 'string', array('limit' => 250));
+ }
+
+ if (!$table->hasColumn('hash')) {
+ $table->addColumn('hash', 'string', array('limit' => 250));
+ }
+
+ if (!$table->hasColumn('name')) {
+ $table->addColumn('name', 'string', array('limit' => 250));
+ }
+
+ if (!$table->hasColumn('is_admin')) {
+ $table->addColumn('is_admin', 'integer');
+ }
+
+ if (!$table->hasIndex(array('email'))) {
+ $table->addIndex(array('email'));
+ }
+
+ $table->save();
+ }
+}
\ No newline at end of file
diff --git a/PHPCI/Migrations/20140513153133_change_build_keys_migration.php b/PHPCI/Migrations/20140513153133_change_build_keys_migration.php
new file mode 100644
index 00000000..b26a9388
--- /dev/null
+++ b/PHPCI/Migrations/20140513153133_change_build_keys_migration.php
@@ -0,0 +1,26 @@
+table('project');
+ $project->renameColumn('git_key', 'ssh_private_key');
+ $project->renameColumn('public_key', 'ssh_public_key');
+ }
+
+ /**
+ * Migrate Down.
+ */
+ public function down()
+ {
+ $project = $this->table('project');
+ $project->renameColumn('ssh_private_key', 'git_key');
+ $project->renameColumn('ssh_public_key', 'public_key');
+ }
+}
\ No newline at end of file
diff --git a/PHPCI/Model.php b/PHPCI/Model.php
index 7e314693..d1adf9ec 100644
--- a/PHPCI/Model.php
+++ b/PHPCI/Model.php
@@ -1,4 +1,12 @@
null,
'started' => null,
'finished' => null,
- 'plugins' => null,
'committer_email' => null,
'commit_message' => null,
+ 'extra' => null,
);
/**
@@ -61,9 +61,9 @@ class BuildBase extends Model
'created' => 'getCreated',
'started' => 'getStarted',
'finished' => 'getFinished',
- 'plugins' => 'getPlugins',
'committer_email' => 'getCommitterEmail',
'commit_message' => 'getCommitMessage',
+ 'extra' => 'getExtra',
// Foreign key getters:
'Project' => 'getProject',
@@ -83,9 +83,9 @@ class BuildBase extends Model
'created' => 'setCreated',
'started' => 'setStarted',
'finished' => 'setFinished',
- 'plugins' => 'setPlugins',
'committer_email' => 'setCommitterEmail',
'commit_message' => 'setCommitMessage',
+ 'extra' => 'setExtra',
// Foreign key setters:
'Project' => 'setProject',
@@ -143,11 +143,6 @@ class BuildBase extends Model
'nullable' => true,
'default' => null,
),
- 'plugins' => array(
- 'type' => 'text',
- 'nullable' => true,
- 'default' => null,
- ),
'committer_email' => array(
'type' => 'varchar',
'length' => 512,
@@ -159,6 +154,11 @@ class BuildBase extends Model
'nullable' => true,
'default' => null,
),
+ 'extra' => array(
+ 'type' => 'longtext',
+ 'nullable' => true,
+ 'default' => null,
+ ),
);
/**
@@ -303,18 +303,6 @@ class BuildBase extends Model
return $rtn;
}
- /**
- * Get the value of Plugins / plugins.
- *
- * @return string
- */
- public function getPlugins()
- {
- $rtn = $this->data['plugins'];
-
- return $rtn;
- }
-
/**
* Get the value of CommitterEmail / committer_email.
*
@@ -339,6 +327,18 @@ class BuildBase extends Model
return $rtn;
}
+ /**
+ * Get the value of Extra / extra.
+ *
+ * @return string
+ */
+ public function getExtra()
+ {
+ $rtn = $this->data['extra'];
+
+ return $rtn;
+ }
+
/**
* Set the value of Id / id.
*
@@ -509,24 +509,6 @@ class BuildBase extends Model
$this->_setModified('finished');
}
- /**
- * Set the value of Plugins / plugins.
- *
- * @param $value string
- */
- public function setPlugins($value)
- {
- $this->_validateString('Plugins', $value);
-
- if ($this->data['plugins'] === $value) {
- return;
- }
-
- $this->data['plugins'] = $value;
-
- $this->_setModified('plugins');
- }
-
/**
* Set the value of CommitterEmail / committer_email.
*
@@ -563,6 +545,24 @@ class BuildBase extends Model
$this->_setModified('commit_message');
}
+ /**
+ * Set the value of Extra / extra.
+ *
+ * @param $value string
+ */
+ public function setExtra($value)
+ {
+ $this->_validateString('Extra', $value);
+
+ if ($this->data['extra'] === $value) {
+ return;
+ }
+
+ $this->data['extra'] = $value;
+
+ $this->_setModified('extra');
+ }
+
/**
* Get the Project model for this Build by Id.
*
diff --git a/PHPCI/Model/Base/BuildMetaBase.php b/PHPCI/Model/Base/BuildMetaBase.php
index b7f85490..a979e1d9 100644
--- a/PHPCI/Model/Base/BuildMetaBase.php
+++ b/PHPCI/Model/Base/BuildMetaBase.php
@@ -52,6 +52,7 @@ class BuildMetaBase extends Model
'meta_value' => 'getMetaValue',
// Foreign key getters:
+ 'Project' => 'getProject',
'Build' => 'getBuild',
);
@@ -67,6 +68,7 @@ class BuildMetaBase extends Model
'meta_value' => 'setMetaValue',
// Foreign key setters:
+ 'Project' => 'setProject',
'Build' => 'setBuild',
);
@@ -98,7 +100,7 @@ class BuildMetaBase extends Model
'default' => null,
),
'meta_value' => array(
- 'type' => 'text',
+ 'type' => 'longtext',
'nullable' => true,
'default' => null,
),
@@ -110,12 +112,20 @@ class BuildMetaBase extends Model
public $indexes = array(
'PRIMARY' => array('unique' => true, 'columns' => 'id'),
'idx_meta_id' => array('unique' => true, 'columns' => 'build_id, meta_key'),
+ 'project_id' => array('columns' => 'project_id'),
);
/**
* @var array
*/
public $foreignKeys = array(
+ 'build_meta_ibfk_1' => array(
+ 'local_col' => 'project_id',
+ 'update' => 'CASCADE',
+ 'delete' => 'CASCADE',
+ 'table' => 'project',
+ 'col' => 'id'
+ ),
'fk_meta_build_id' => array(
'local_col' => 'build_id',
'update' => 'CASCADE',
@@ -281,6 +291,63 @@ class BuildMetaBase extends Model
$this->_setModified('meta_value');
}
+ /**
+ * Get the Project model for this BuildMeta by Id.
+ *
+ * @uses \PHPCI\Store\ProjectStore::getById()
+ * @uses \PHPCI\Model\Project
+ * @return \PHPCI\Model\Project
+ */
+ public function getProject()
+ {
+ $key = $this->getProjectId();
+
+ if (empty($key)) {
+ return null;
+ }
+
+ $cacheKey = 'Cache.Project.' . $key;
+ $rtn = $this->cache->get($cacheKey, null);
+
+ if (empty($rtn)) {
+ $rtn = Factory::getStore('Project', 'PHPCI')->getById($key);
+ $this->cache->set($cacheKey, $rtn);
+ }
+
+ return $rtn;
+ }
+
+ /**
+ * Set Project - Accepts an ID, an array representing a Project or a Project model.
+ *
+ * @param $value mixed
+ */
+ public function setProject($value)
+ {
+ // Is this an instance of Project?
+ if ($value instanceof \PHPCI\Model\Project) {
+ return $this->setProjectObject($value);
+ }
+
+ // Is this an array representing a Project item?
+ if (is_array($value) && !empty($value['id'])) {
+ return $this->setProjectId($value['id']);
+ }
+
+ // Is this a scalar value representing the ID of this foreign key?
+ return $this->setProjectId($value);
+ }
+
+ /**
+ * Set Project - Accepts a Project model.
+ *
+ * @param $value \PHPCI\Model\Project
+ */
+ public function setProjectObject(\PHPCI\Model\Project $value)
+ {
+ return $this->setProjectId($value->getId());
+ }
+
/**
* Get the Build model for this BuildMeta by Id.
*
diff --git a/PHPCI/Model/Base/ProjectBase.php b/PHPCI/Model/Base/ProjectBase.php
index 9e2f5922..00bee23a 100644
--- a/PHPCI/Model/Base/ProjectBase.php
+++ b/PHPCI/Model/Base/ProjectBase.php
@@ -36,10 +36,9 @@ class ProjectBase extends Model
'id' => null,
'title' => null,
'reference' => null,
- 'git_key' => null,
- 'public_key' => null,
+ 'ssh_private_key' => null,
+ 'ssh_public_key' => null,
'type' => null,
- 'token' => null,
'access_information' => null,
'last_commit' => null,
'build_config' => null,
@@ -54,10 +53,9 @@ class ProjectBase extends Model
'id' => 'getId',
'title' => 'getTitle',
'reference' => 'getReference',
- 'git_key' => 'getGitKey',
- 'public_key' => 'getPublicKey',
+ 'ssh_private_key' => 'getSshPrivateKey',
+ 'ssh_public_key' => 'getSshPublicKey',
'type' => 'getType',
- 'token' => 'getToken',
'access_information' => 'getAccessInformation',
'last_commit' => 'getLastCommit',
'build_config' => 'getBuildConfig',
@@ -74,10 +72,9 @@ class ProjectBase extends Model
'id' => 'setId',
'title' => 'setTitle',
'reference' => 'setReference',
- 'git_key' => 'setGitKey',
- 'public_key' => 'setPublicKey',
+ 'ssh_private_key' => 'setSshPrivateKey',
+ 'ssh_public_key' => 'setSshPublicKey',
'type' => 'setType',
- 'token' => 'setToken',
'access_information' => 'setAccessInformation',
'last_commit' => 'setLastCommit',
'build_config' => 'setBuildConfig',
@@ -107,12 +104,12 @@ class ProjectBase extends Model
'length' => 250,
'default' => null,
),
- 'git_key' => array(
+ 'ssh_private_key' => array(
'type' => 'text',
'nullable' => true,
'default' => null,
),
- 'public_key' => array(
+ 'ssh_public_key' => array(
'type' => 'text',
'nullable' => true,
'default' => null,
@@ -122,12 +119,6 @@ class ProjectBase extends Model
'length' => 50,
'default' => 1,
),
- 'token' => array(
- 'type' => 'varchar',
- 'length' => 50,
- 'nullable' => true,
- 'default' => null,
- ),
'access_information' => array(
'type' => 'varchar',
'length' => 250,
@@ -202,25 +193,25 @@ class ProjectBase extends Model
}
/**
- * Get the value of GitKey / git_key.
+ * Get the value of SshPrivateKey / ssh_private_key.
*
* @return string
*/
- public function getGitKey()
+ public function getSshPrivateKey()
{
- $rtn = $this->data['git_key'];
+ $rtn = $this->data['ssh_private_key'];
return $rtn;
}
/**
- * Get the value of PublicKey / public_key.
+ * Get the value of SshPublicKey / ssh_public_key.
*
* @return string
*/
- public function getPublicKey()
+ public function getSshPublicKey()
{
- $rtn = $this->data['public_key'];
+ $rtn = $this->data['ssh_public_key'];
return $rtn;
}
@@ -237,18 +228,6 @@ class ProjectBase extends Model
return $rtn;
}
- /**
- * Get the value of Token / token.
- *
- * @return string
- */
- public function getToken()
- {
- $rtn = $this->data['token'];
-
- return $rtn;
- }
-
/**
* Get the value of AccessInformation / access_information.
*
@@ -358,39 +337,39 @@ class ProjectBase extends Model
}
/**
- * Set the value of GitKey / git_key.
+ * Set the value of SshPrivateKey / ssh_private_key.
*
* @param $value string
*/
- public function setGitKey($value)
+ public function setSshPrivateKey($value)
{
- $this->_validateString('GitKey', $value);
+ $this->_validateString('SshPrivateKey', $value);
- if ($this->data['git_key'] === $value) {
+ if ($this->data['ssh_private_key'] === $value) {
return;
}
- $this->data['git_key'] = $value;
+ $this->data['ssh_private_key'] = $value;
- $this->_setModified('git_key');
+ $this->_setModified('ssh_private_key');
}
/**
- * Set the value of PublicKey / public_key.
+ * Set the value of SshPublicKey / ssh_public_key.
*
* @param $value string
*/
- public function setPublicKey($value)
+ public function setSshPublicKey($value)
{
- $this->_validateString('PublicKey', $value);
+ $this->_validateString('SshPublicKey', $value);
- if ($this->data['public_key'] === $value) {
+ if ($this->data['ssh_public_key'] === $value) {
return;
}
- $this->data['public_key'] = $value;
+ $this->data['ssh_public_key'] = $value;
- $this->_setModified('public_key');
+ $this->_setModified('ssh_public_key');
}
/**
@@ -413,24 +392,6 @@ class ProjectBase extends Model
$this->_setModified('type');
}
- /**
- * Set the value of Token / token.
- *
- * @param $value string
- */
- public function setToken($value)
- {
- $this->_validateString('Token', $value);
-
- if ($this->data['token'] === $value) {
- return;
- }
-
- $this->data['token'] = $value;
-
- $this->_setModified('token');
- }
-
/**
* Set the value of AccessInformation / access_information.
*
@@ -516,4 +477,16 @@ class ProjectBase extends Model
{
return Factory::getStore('Build', 'PHPCI')->getByProjectId($this->getId());
}
+
+ /**
+ * Get BuildMeta models by ProjectId for this Project.
+ *
+ * @uses \PHPCI\Store\BuildMetaStore::getByProjectId()
+ * @uses \PHPCI\Model\BuildMeta
+ * @return \PHPCI\Model\BuildMeta[]
+ */
+ public function getProjectBuildMetas()
+ {
+ return Factory::getStore('BuildMeta', 'PHPCI')->getByProjectId($this->getId());
+ }
}
diff --git a/PHPCI/Model/Build.php b/PHPCI/Model/Build.php
index e8b8cd5a..4ca3cf9e 100644
--- a/PHPCI/Model/Build.php
+++ b/PHPCI/Model/Build.php
@@ -1,11 +1,11 @@
array(
'ignore' => array(
- 'vendor/',
+ 'vendor',
)
)
);
@@ -164,4 +164,19 @@ class Build extends BuildBase
{
return null;
}
+
+ public function getExtra($key = null)
+ {
+ $data = json_decode($this->data['extra'], true);
+
+ if (is_null($key)) {
+ $rtn = $data;
+ } elseif (isset($data[$key])) {
+ $rtn = $data[$key];
+ } else {
+ $rtn = null;
+ }
+
+ return $rtn;
+ }
}
diff --git a/PHPCI/Model/Build/BitbucketBuild.php b/PHPCI/Model/Build/BitbucketBuild.php
index fb50a710..3af0d2b5 100644
--- a/PHPCI/Model/Build/BitbucketBuild.php
+++ b/PHPCI/Model/Build/BitbucketBuild.php
@@ -1,11 +1,11 @@
getProject()->getGitKey());
+ $key = trim($this->getProject()->getSshPrivateKey());
if (!empty($key)) {
return 'git@bitbucket.org:' . $this->getProject()->getReference() . '.git';
diff --git a/PHPCI/Model/Build/GithubBuild.php b/PHPCI/Model/Build/GithubBuild.php
index f358355a..6d3f217b 100644
--- a/PHPCI/Model/Build/GithubBuild.php
+++ b/PHPCI/Model/Build/GithubBuild.php
@@ -1,14 +1,15 @@
getProject()->getGitKey());
+ $key = trim($this->getProject()->getSshPrivateKey());
if (!empty($key)) {
return 'git@github.com:' . $this->getProject()->getReference() . '.git';
@@ -115,4 +116,29 @@ class GithubBuild extends RemoteGitBuild
return $link;
}
+
+ protected function postCloneSetup(Builder $builder, $cloneTo)
+ {
+ $buildType = $this->getExtra('build_type');
+
+ $success = true;
+
+ try {
+ if (!empty($buildType) && $buildType == 'pull_request') {
+ $remoteUrl = $this->getExtra('remote_url');
+ $remoteBranch = $this->getExtra('remote_branch');
+
+ $cmd = 'cd "%s" && git checkout -b phpci/' . $this->getId() . ' %s && git pull -q --no-edit %s %s';
+ $success = $builder->executeCommand($cmd, $cloneTo, $this->getBranch(), $remoteUrl, $remoteBranch);
+ }
+ } catch (\Exception $ex) {
+ $success = false;
+ }
+
+ if ($success) {
+ $success = parent::postCloneSetup($builder, $cloneTo);
+ }
+
+ return $success;
+ }
}
diff --git a/PHPCI/Model/Build/GitlabBuild.php b/PHPCI/Model/Build/GitlabBuild.php
index 1ce777ae..83053090 100644
--- a/PHPCI/Model/Build/GitlabBuild.php
+++ b/PHPCI/Model/Build/GitlabBuild.php
@@ -1,11 +1,11 @@
getProject()->getGitKey());
+ $key = trim($this->getProject()->getSshPrivateKey());
if (!empty($key)) {
$user = $this->getProject()->getAccessInformation("user");
diff --git a/PHPCI/Model/Build/LocalBuild.php b/PHPCI/Model/Build/LocalBuild.php
index 9f2be613..3fd5e532 100644
--- a/PHPCI/Model/Build/LocalBuild.php
+++ b/PHPCI/Model/Build/LocalBuild.php
@@ -1,11 +1,11 @@
getProject()->getGitKey());
+ $key = trim($this->getProject()->getSshPrivateKey());
if (!empty($key)) {
$success = $this->cloneBySsh($builder, $buildPath);
@@ -65,12 +65,8 @@ class RemoteGitBuild extends Build
$cmd .= ' -b %s %s "%s"';
$success = $builder->executeCommand($cmd, $this->getBranch(), $this->getCloneUrl(), $cloneTo);
- if (!empty($commit) && $commit != 'Manual') {
- $cmd = 'cd "%s" && git checkout %s';
- if (IS_WIN) {
- $cmd = 'cd /d "%s" && git checkout %s';
- }
- $builder->executeCommand($cmd, $cloneTo, $this->getCommitId());
+ if ($success) {
+ $success = $this->postCloneSetup($builder, $cloneTo);
}
return $success;
@@ -104,15 +100,8 @@ class RemoteGitBuild extends Build
$success = $builder->executeCommand($cmd, $this->getBranch(), $this->getCloneUrl(), $cloneTo);
- // Checkout a specific commit if we need to:
- $commit = $this->getCommitId();
-
- if (!empty($commit) && $commit != 'Manual') {
- $cmd = 'cd "%s" && git checkout %s';
- if (IS_WIN) {
- $cmd = 'cd /d "%s" && git checkout %s';
- }
- $builder->executeCommand($cmd, $cloneTo, $this->getCommitId());
+ if ($success) {
+ $success = $this->postCloneSetup($builder, $cloneTo);
}
// Remove the key file and git wrapper:
@@ -122,6 +111,24 @@ class RemoteGitBuild extends Build
return $success;
}
+ protected function postCloneSetup(Builder $builder, $cloneTo)
+ {
+ $success = true;
+ $commit = $this->getCommitId();
+
+ if (!empty($commit) && $commit != 'Manual') {
+ $cmd = 'cd "%s" && git checkout %s';
+
+ if (IS_WIN) {
+ $cmd = 'cd /d "%s" && git checkout %s';
+ }
+
+ $success = $builder->executeCommand($cmd, $cloneTo, $this->getCommitId());
+ }
+
+ return $success;
+ }
+
/**
* Create an SSH key file on disk for this build.
* @param $cloneTo
@@ -133,7 +140,7 @@ class RemoteGitBuild extends Build
$keyFile = $keyPath . '.key';
// Write the contents of this project's git key to the file:
- file_put_contents($keyFile, $this->getProject()->getGitKey());
+ file_put_contents($keyFile, $this->getProject()->getSshPrivateKey());
chmod($keyFile, 0600);
// Return the filename:
diff --git a/PHPCI/Model/BuildMeta.php b/PHPCI/Model/BuildMeta.php
index caa4b2fa..fc2af129 100644
--- a/PHPCI/Model/BuildMeta.php
+++ b/PHPCI/Model/BuildMeta.php
@@ -1,7 +1,10 @@
phpci->executeCommand($cmd, $this->phpci->buildPath . 'composer.phar');
$this->phpci->executeCommand($cmd, $this->phpci->buildPath . 'composer.lock');
diff --git a/PHPCI/Plugin/Codeception.php b/PHPCI/Plugin/Codeception.php
index 5b811896..e29b81a2 100644
--- a/PHPCI/Plugin/Codeception.php
+++ b/PHPCI/Plugin/Codeception.php
@@ -2,9 +2,9 @@
/**
* PHPCI - Continuous Integration for PHP
*
- * @copyright Copyright 2013, Block 8 Limited.
+ * @copyright Copyright 2014, Block 8 Limited.
* @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md
- * @link http://www.phptesting.org/
+ * @link https://www.phptesting.org/
*/
namespace PHPCI\Plugin;
diff --git a/PHPCI/Plugin/Composer.php b/PHPCI/Plugin/Composer.php
index 3a13b672..dbea469c 100644
--- a/PHPCI/Plugin/Composer.php
+++ b/PHPCI/Plugin/Composer.php
@@ -1,11 +1,11 @@
buildPath;
- $this->phpci = $phpci;
+ $path = $phpci->buildPath;
+ $this->phpci = $phpci;
$this->build = $build;
- $this->directory = isset($options['directory']) ? $path . '/' . $options['directory'] : $path;
- $this->action = isset($options['action']) ? $options['action'] : 'install';
- $this->preferDist = isset($options['prefer_dist']) ? $options['prefer_dist'] : true;
+ $this->directory = $path;
+ $this->action = 'install';
+ $this->preferDist = false;
+
+ if (array_key_exists('directory', $options)) {
+ $this->directory = $path . '/' . $options['directory'];
+ }
+
+ if (array_key_exists('action', $options)) {
+ $this->action = $options['action'];
+ }
+
+ if (array_key_exists('prefer_dist', $options)) {
+ $this->preferDist = (bool)$options['prefer_dist'];
+ }
}
/**
@@ -59,12 +71,24 @@ class Composer implements PHPCI\Plugin, PHPCI\ZeroConfigPlugin
$this->phpci->logFailure('Could not find Composer.');
return false;
}
+
$cmd = '';
+
if (IS_WIN) {
$cmd = 'php ';
}
+
$cmd .= $composerLocation . ' --no-ansi --no-interaction ';
- $cmd .= ($this->preferDist ? '--prefer-dist' : null) . ' --working-dir="%s" %s';
+
+ if ($this->preferDist) {
+ $this->phpci->log('Using --prefer-dist flag');
+ $cmd .= '--prefer-dist';
+ } else {
+ $this->phpci->log('Using --prefer-source flag');
+ $cmd .= '--prefer-source';
+ }
+
+ $cmd .= ' --working-dir="%s" %s';
return $this->phpci->executeCommand($cmd, $this->directory, $this->action);
}
diff --git a/PHPCI/Plugin/CopyBuild.php b/PHPCI/Plugin/CopyBuild.php
index 021d5e80..ca7dae76 100644
--- a/PHPCI/Plugin/CopyBuild.php
+++ b/PHPCI/Plugin/CopyBuild.php
@@ -1,11 +1,11 @@
phpci->executeCommand($cmd, $this->directory, $build, $this->directory);
if ($this->ignore) {
foreach ($this->phpci->ignore as $file) {
$cmd = 'rm -Rf "%s/%s"';
+ if (IS_WIN) {
+ $cmd = 'rmdir /S /Q "%s\%s"';
+ }
$this->phpci->executeCommand($cmd, $this->directory, $file);
}
}
diff --git a/PHPCI/Plugin/Email.php b/PHPCI/Plugin/Email.php
index 0b23a1d7..70fca197 100644
--- a/PHPCI/Plugin/Email.php
+++ b/PHPCI/Plugin/Email.php
@@ -1,11 +1,11 @@
build->getBranch();
- return $this->phpci->executeCommand($cmd, $this->directory, $options['branch']);
+ $cmd = 'cd "%s" && git checkout %s && git merge "%s"';
+ $path = $this->phpci->buildPath;
+ return $this->phpci->executeCommand($cmd, $path, $options['branch'], $this->build->getBranch());
}
}
diff --git a/PHPCI/Plugin/Grunt.php b/PHPCI/Plugin/Grunt.php
index 0c845184..106a11b4 100644
--- a/PHPCI/Plugin/Grunt.php
+++ b/PHPCI/Plugin/Grunt.php
@@ -1,11 +1,11 @@
+ * @package PHPCI
+ * @subpackage Plugins
+ */
+class HipchatNotify implements \PHPCI\Plugin
+{
+ private $authToken;
+ private $userAgent;
+ private $cookie;
+
+ public function __construct(Builder $phpci, Build $build, array $options = array())
+ {
+ $this->phpci = $phpci;
+ $this->build = $build;
+
+ $this->userAgent = "PHPCI/1.0 (+http://www.phptesting.org/)";
+ $this->cookie = "phpcicookie";
+
+ if (is_array($options) && isset($options['authToken']) && isset($options['room'])) {
+ $this->authToken = $options['authToken'];
+ $this->room = $options['room'];
+
+ if (isset($options['message'])) {
+ $this->message = $options['message'];
+ } else {
+ $this->message = '%PROJECT_TITLE% built at %BUILD_URI%';
+ }
+ } else {
+ throw new \Exception('Please define room and authToken for hipchat_notify plugin!');
+ }
+
+ }
+
+ public function execute()
+ {
+ $hipChat = new \HipChat\HipChat($this->authToken);
+ $message = $this->phpci->interpolate($this->message);
+
+ if (is_array($this->room)) {
+ foreach ($this->room as $room) {
+ $hipChat->message_room($room, 'PHPCI', $message);
+ }
+ } else {
+ $hipChat->message_room($this->room, 'PHPCI', $message);
+ }
+ }
+}
diff --git a/PHPCI/Plugin/Irc.php b/PHPCI/Plugin/Irc.php
index 0defc037..fd774b8c 100644
--- a/PHPCI/Plugin/Irc.php
+++ b/PHPCI/Plugin/Irc.php
@@ -1,4 +1,11 @@
phpci = $phpci;
@@ -74,19 +91,18 @@ class Mysql implements \PHPCI\Plugin
/**
* Connects to MySQL and runs a specified set of queries.
+ * @return boolean
*/
public function execute()
{
- $success = true;
-
try {
$opts = array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION);
- $this->pdo = new PDO('mysql:host=' . $this->host, $this->user, $this->pass, $opts);
+ $pdo = new PDO('mysql:host=' . $this->host, $this->user, $this->pass, $opts);
foreach ($this->queries as $query) {
if (!is_array($query)) {
// Simple query
- $this->pdo->query($this->phpci->interpolate($query));
+ $pdo->query($this->phpci->interpolate($query));
} elseif (isset($query['import'])) {
// SQL file execution
$this->executeFile($query['import']);
@@ -98,10 +114,14 @@ class Mysql implements \PHPCI\Plugin
$this->phpci->logFailure($ex->getMessage());
return false;
}
-
- return $success;
+ return true;
}
+ /**
+ * @param string $query
+ * @return boolean
+ * @throws \Exception
+ */
protected function executeFile($query)
{
if (!isset($query['file'])) {
diff --git a/PHPCI/Plugin/PackageBuild.php b/PHPCI/Plugin/PackageBuild.php
index 3036d13b..e5dcda41 100644
--- a/PHPCI/Plugin/PackageBuild.php
+++ b/PHPCI/Plugin/PackageBuild.php
@@ -1,11 +1,11 @@
phpci = $phpci;
- $this->build = $build;
+ $this->phpci = $phpci;
+ $this->build = $build;
$this->queries = $options;
$buildSettings = $phpci->getConfig('build_settings');
@@ -47,6 +74,7 @@ class Pgsql implements \PHPCI\Plugin
/**
* Connects to PgSQL and runs a specified set of queries.
+ * @return boolean
*/
public function execute()
{
@@ -61,7 +89,6 @@ class Pgsql implements \PHPCI\Plugin
$this->phpci->logFailure($ex->getMessage());
return false;
}
-
return true;
}
}
diff --git a/PHPCI/Plugin/Phing.php b/PHPCI/Plugin/Phing.php
index 270b3aa9..21f7a972 100644
--- a/PHPCI/Plugin/Phing.php
+++ b/PHPCI/Plugin/Phing.php
@@ -2,9 +2,9 @@
/**
* PHPCI - Continuous Integration for PHP
*
- * @copyright Copyright 2013, Block 8 Limited.
+ * @copyright Copyright 2014, Block 8 Limited.
* @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md
- * @link http://www.phptesting.org/
+ * @link https://www.phptesting.org/
*/
namespace PHPCI\Plugin;
diff --git a/PHPCI/Plugin/PhpCodeSniffer.php b/PHPCI/Plugin/PhpCodeSniffer.php
index 7c7f81e0..3ecf2f48 100755
--- a/PHPCI/Plugin/PhpCodeSniffer.php
+++ b/PHPCI/Plugin/PhpCodeSniffer.php
@@ -1,11 +1,11 @@
suffixes = (array)$options['suffixes'];
}
- if (isset($options['directory'])) {
- $this->directory = $options['directory'];
- }
-
- if (isset($options['standard'])) {
- $this->standard = $options['standard'];
- }
-
if (!empty($options['tab_width'])) {
$this->tab_width = ' --tab-width='.$options['tab_width'];
}
@@ -125,20 +117,15 @@ class PhpCodeSniffer implements PHPCI\Plugin, PHPCI\ZeroConfigPlugin
$this->encoding = ' --encoding=' . $options['encoding'];
}
- if (isset($options['path'])) {
- $this->path = $options['path'];
- }
+ $this->setOptions($options);
+ }
- if (isset($options['ignore'])) {
- $this->ignore = $options['ignore'];
- }
-
- if (isset($options['allowed_warnings'])) {
- $this->allowed_warnings = (int)$options['allowed_warnings'];
- }
-
- if (isset($options['allowed_errors'])) {
- $this->allowed_errors = (int)$options['allowed_errors'];
+ protected function setOptions($options)
+ {
+ foreach (array('directory', 'standard', 'path', 'ignore', 'allowed_warnings', 'allowed_errors') as $key) {
+ if (array_key_exists($key, $options)) {
+ $this->{$key} = $options[$key];
+ }
}
}
diff --git a/PHPCI/Plugin/PhpCpd.php b/PHPCI/Plugin/PhpCpd.php
index 2e0b908e..9325efa6 100644
--- a/PHPCI/Plugin/PhpCpd.php
+++ b/PHPCI/Plugin/PhpCpd.php
@@ -1,11 +1,11 @@
+* @package PHPCI
+* @subpackage Plugins
+*/
+class PhpDocblockChecker implements PHPCI\Plugin, PHPCI\ZeroConfigPlugin
+{
+ /**
+ * @var \PHPCI\Builder
+ */
+ protected $phpci;
+
+ /**
+ * @var \PHPCI\Model\Build
+ */
+ protected $build;
+
+ /**
+ * @var string Based on the assumption the root may not hold the code to be
+ * tested, extends the build path.
+ */
+ protected $path;
+
+ /**
+ * @var array - paths to ignore
+ */
+ protected $ignore;
+
+ protected $skipClasses = false;
+ protected $skipMethods = false;
+
+ public static function canExecute($stage, Builder $builder, Build $build)
+ {
+ if ($stage == 'test') {
+ return true;
+ }
+
+ return false;
+ }
+
+
+ public function __construct(Builder $phpci, Build $build, array $options = array())
+ {
+ $this->phpci = $phpci;
+ $this->build = $build;
+ $this->ignore = $phpci->ignore;
+ $this->path = '';
+ $this->allowed_warnings = 0;
+
+ if (isset($options['zero_config']) && $options['zero_config']) {
+ $this->allowed_warnings = -1;
+ }
+
+ if (array_key_exists('skip_classes', $options)) {
+ $this->skipClasses = true;
+ }
+
+ if (array_key_exists('skip_methods', $options)) {
+ $this->skipMethods = true;
+ }
+
+ if (!empty($options['path'])) {
+ $this->path = $options['path'];
+ }
+
+ if (array_key_exists('allowed_warnings', $options)) {
+ $this->allowed_warnings = (int)$options['allowed_warnings'];
+ }
+ }
+
+ /**
+ * Runs PHP Mess Detector in a specified directory.
+ */
+ public function execute()
+ {
+ // Check that the binary exists:
+ $checker = $this->phpci->findBinary('phpdoccheck');
+
+ if (!$checker) {
+ $this->phpci->logFailure('Could not find phpdoccheck.');
+ return false;
+ }
+
+ // Build ignore string:
+ $ignore = '';
+ if (count($this->ignore)) {
+ $ignore = ' --exclude="' . implode(',', $this->ignore) . '"';
+ }
+
+ // Are we skipping any checks?
+ $add = '';
+ if ($this->skipClasses) {
+ $add .= ' --skip-classes';
+ }
+
+ if ($this->skipMethods) {
+ $add .= ' --skip-methods';
+ }
+
+ // Build command string:
+ $path = $this->phpci->buildPath . $this->path;
+ $cmd = $checker . ' --json --directory="%s"%s%s';
+
+ // Disable exec output logging, as we don't want the XML report in the log:
+ $this->phpci->logExecOutput(false);
+
+ // Run checker:
+ $this->phpci->executeCommand(
+ $cmd,
+ $path,
+ $ignore,
+ $add
+ );
+
+ // Re-enable exec output logging:
+ $this->phpci->logExecOutput(true);
+
+ $output = json_decode($this->phpci->getLastOutput());
+ $errors = count($output);
+ $success = true;
+
+ $this->build->storeMeta('phpdoccheck-warnings', $errors);
+ $this->build->storeMeta('phpdoccheck-data', $output);
+
+ if ($this->allowed_warnings != -1 && $errors > $this->allowed_warnings) {
+ $success = false;
+ }
+
+ return $success;
+ }
+}
diff --git a/PHPCI/Plugin/PhpLoc.php b/PHPCI/Plugin/PhpLoc.php
index 000b82cc..26ec8204 100644
--- a/PHPCI/Plugin/PhpLoc.php
+++ b/PHPCI/Plugin/PhpLoc.php
@@ -2,9 +2,9 @@
/**
* PHPCI - Continuous Integration for PHP
*
- * @copyright Copyright 2013, Block 8 Limited.
+ * @copyright Copyright 2014, Block 8 Limited.
* @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md
- * @link http://www.phptesting.org/
+ * @link https://www.phptesting.org/
*/
namespace PHPCI\Plugin;
@@ -68,7 +68,7 @@ class PhpLoc implements PHPCI\Plugin, PHPCI\ZeroConfigPlugin
return false;
}
- $success = $this->phpci->executeCommand($phploc . ' %s "%s"', $ignore, $this->phpci->buildPath);
+ $success = $this->phpci->executeCommand($phploc . ' %s "%s"', $ignore, $this->directory);
$output = $this->phpci->getLastOutput();
if (preg_match_all('/\((LOC|CLOC|NCLOC|LLOC)\)\s+([0-9]+)/', $output, $matches)) {
diff --git a/PHPCI/Plugin/PhpMessDetector.php b/PHPCI/Plugin/PhpMessDetector.php
index 2be7fed4..deb6eafd 100755
--- a/PHPCI/Plugin/PhpMessDetector.php
+++ b/PHPCI/Plugin/PhpMessDetector.php
@@ -1,11 +1,11 @@
ignore)) {
- $ignore = ' --exclude ' . implode(',', $this->ignore);
- }
-
- $suffixes = '';
- if (count($this->suffixes)) {
- $suffixes = ' --suffixes ' . implode(',', $this->suffixes);
- }
-
- if (!empty($this->rules) && !is_array($this->rules)) {
- $this->phpci->logFailure('The "rules" option must be an array.');
+ if (!$this->tryAndProcessRules()) {
return false;
}
- foreach ($this->rules as &$rule) {
- if (strpos($rule, '/') !== false) {
- $rule = $this->phpci->buildPath . $rule;
- }
- }
+ $phpmdBinaryPath = $this->phpci->findBinary('phpmd');
- $phpmd = $this->phpci->findBinary('phpmd');
-
- if (!$phpmd) {
+ if (!$phpmdBinaryPath) {
$this->phpci->logFailure('Could not find phpmd.');
return false;
}
-
- $path = $this->phpci->buildPath . $this->path;
- if (!empty($this->path) && $this->path{0} == '/') {
- $path = $this->path;
- }
- $cmd = $phpmd . ' "%s" xml %s %s %s';
+ $this->executePhpMd($phpmdBinaryPath);
- // Disable exec output logging, as we don't want the XML report in the log:
- $this->phpci->logExecOutput(false);
-
- // Run PHPMD:
- $this->phpci->executeCommand(
- $cmd,
- $path,
- implode(',', $this->rules),
- $ignore,
- $suffixes
- );
-
- // Re-enable exec output logging:
- $this->phpci->logExecOutput(true);
-
- $success = true;
-
- list($errors, $data) = $this->processReport(trim($this->phpci->getLastOutput()));
- $this->build->storeMeta('phpmd-warnings', $errors);
+ list($errorCount, $data) = $this->processReport(trim($this->phpci->getLastOutput()));
+ $this->build->storeMeta('phpmd-warnings', $errorCount);
$this->build->storeMeta('phpmd-data', $data);
- if ($this->allowed_warnings != -1 && $errors > $this->allowed_warnings) {
- $success = false;
- }
-
- return $success;
+ return $this->wasLastExecSuccessful($errorCount);
}
protected function overrideSetting($options, $key)
@@ -200,4 +157,79 @@ class PhpMessDetector implements PHPCI\Plugin, PHPCI\ZeroConfigPlugin
return array($warnings, $data);
}
+
+ protected function tryAndProcessRules()
+ {
+ if (!empty($this->rules) && !is_array($this->rules)) {
+ $this->phpci->logFailure('The "rules" option must be an array.');
+ return false;
+ }
+
+ foreach ($this->rules as &$rule) {
+ if (strpos($rule, '/') !== false) {
+ $rule = $this->phpci->buildPath . $rule;
+ }
+ }
+
+ return true;
+ }
+
+ protected function executePhpMd($binaryPath)
+ {
+ $cmd = $binaryPath . ' "%s" xml %s %s %s';
+
+ $path = $this->getTargetPath();
+
+ $ignore = '';
+ if (count($this->ignore)) {
+ $ignore = ' --exclude ' . implode(',', $this->ignore);
+ }
+
+ $suffixes = '';
+ if (count($this->suffixes)) {
+ $suffixes = ' --suffixes ' . implode(',', $this->suffixes);
+ }
+
+ // Disable exec output logging, as we don't want the XML report in the log:
+ $this->phpci->logExecOutput(false);
+
+ // Run PHPMD:
+ $this->phpci->executeCommand(
+ $cmd,
+ $path,
+ implode(',', $this->rules),
+ $ignore,
+ $suffixes
+ );
+
+ // Re-enable exec output logging:
+ $this->phpci->logExecOutput(true);
+ }
+
+ protected function getTargetPath()
+ {
+ $path = $this->phpci->buildPath . $this->path;
+ if (!empty($this->path) && $this->path{0} == '/') {
+ $path = $this->path;
+ return $path;
+ }
+ return $path;
+ }
+
+ /**
+ * Returns a boolean indicating if the error count can be considered a success.
+ *
+ * @param int $errorCount
+ * @return bool
+ */
+ protected function wasLastExecSuccessful($errorCount)
+ {
+ $success = true;
+
+ if ($this->allowed_warnings != -1 && $errorCount > $this->allowed_warnings) {
+ $success = false;
+ return $success;
+ }
+ return $success;
+ }
}
diff --git a/PHPCI/Plugin/PhpParallelLint.php b/PHPCI/Plugin/PhpParallelLint.php
index 282098cc..30398995 100644
--- a/PHPCI/Plugin/PhpParallelLint.php
+++ b/PHPCI/Plugin/PhpParallelLint.php
@@ -1,11 +1,11 @@
phpci->executeCommand($phpspec . ' --format=pretty --no-code-generation');
+ $success = $this->phpci->executeCommand($phpspec . ' --format=pretty --no-code-generation run');
chdir($curdir);
diff --git a/PHPCI/Plugin/PhpUnit.php b/PHPCI/Plugin/PhpUnit.php
index 16de7aa0..acefbcc0 100755
--- a/PHPCI/Plugin/PhpUnit.php
+++ b/PHPCI/Plugin/PhpUnit.php
@@ -1,11 +1,11 @@
args = $options['args'];
+ $this->args = $this->phpci->interpolate($options['args']);
}
if (isset($options['path'])) {
@@ -132,9 +132,16 @@ class PhpUnit implements PHPCI\Plugin, PHPCI\ZeroConfigPlugin
$success &= $this->runDir($this->directory);
}
- $output = $this->phpci->getLastOutput();
- $tapParser = new TapParser($output);
- $output = $tapParser->parse();
+ $tapString = $this->phpci->getLastOutput();
+
+ try {
+ $tapParser = new TapParser($tapString);
+ $output = $tapParser->parse();
+ } catch (\Exception $ex) {
+ $this->phpci->logFailure($tapString);
+ throw $ex;
+ }
+
$failures = $tapParser->getTotalFailures();
$this->build->storeMeta('phpunit-errors', $failures);
diff --git a/PHPCI/Plugin/Shell.php b/PHPCI/Plugin/Shell.php
index 73439cad..fceea3e2 100644
--- a/PHPCI/Plugin/Shell.php
+++ b/PHPCI/Plugin/Shell.php
@@ -2,9 +2,9 @@
/**
* PHPCI - Continuous Integration for PHP
*
- * @copyright Copyright 2013, Block 8 Limited.
+ * @copyright Copyright 2014, Block 8 Limited.
* @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md
- * @link http://www.phptesting.org/
+ * @link https://www.phptesting.org/
*/
namespace PHPCI\Plugin;
diff --git a/PHPCI/Plugin/Sqlite.php b/PHPCI/Plugin/Sqlite.php
new file mode 100644
index 00000000..1ffdcc88
--- /dev/null
+++ b/PHPCI/Plugin/Sqlite.php
@@ -0,0 +1,81 @@
+
+* @package PHPCI
+* @subpackage Plugins
+*/
+class Sqlite implements \PHPCI\Plugin
+{
+ /**
+ * @var \PHPCI\Builder
+ */
+ protected $phpci;
+
+ /**
+ * @var \PHPCI\Model\Build
+ */
+ protected $build;
+
+ /**
+ * @var array
+ */
+ protected $queries = array();
+
+ /**
+ * @var string
+ */
+ protected $path;
+
+ /**
+ * @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->queries = $options;
+ $buildSettings = $phpci->getConfig('build_settings');
+
+ if (isset($buildSettings['sqlite'])) {
+ $sql = $buildSettings['sqlite'];
+ $this->path = $sql['path'];
+ }
+ }
+
+ /**
+ * Connects to SQLite and runs a specified set of queries.
+ * @return boolean
+ */
+ public function execute()
+ {
+ try {
+ $opts = array(PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION);
+ $pdo = new PDO('sqlite:' . $this->path, $opts);
+
+ foreach ($this->queries as $query) {
+ $pdo->query($query);
+ }
+ } catch (\Exception $ex) {
+ $this->phpci->logFailure($ex->getMessage());
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/PHPCI/Plugin/Util/TapParser.php b/PHPCI/Plugin/Util/TapParser.php
index 595e83ef..5b099b03 100644
--- a/PHPCI/Plugin/Util/TapParser.php
+++ b/PHPCI/Plugin/Util/TapParser.php
@@ -7,12 +7,12 @@ class TapParser
const TEST_COUNTS_PATTERN = '/([0-9]+)\.\.([0-9]+)/';
const TEST_LINE_PATTERN = '/(ok|not ok)\s+[0-9]+\s+\-\s+([^\n]+)::([^\n]+)/';
const TEST_MESSAGE_PATTERN = '/message\:\s+\'([^\']+)\'/';
+ const TEST_COVERAGE_PATTERN = '/Generating code coverage report/';
/**
* @var string
*/
protected $tapString;
-
protected $failures = 0;
/**
@@ -43,16 +43,23 @@ class TapParser
throw new \Exception('TapParser only supports TAP version 13');
}
+ if (preg_match(self::TEST_COVERAGE_PATTERN, $lines[count($lines) - 1])) {
+ array_pop($lines);
+ if ($lines[count($lines) - 1] == "") {
+ array_pop($lines);
+ }
+ }
+
$matches = array();
$totalTests = 0;
if (preg_match(self::TEST_COUNTS_PATTERN, $lines[0], $matches)) {
array_shift($lines);
- $totalTests = (int)$matches[2];
+ $totalTests = (int) $matches[2];
}
if (preg_match(self::TEST_COUNTS_PATTERN, $lines[count($lines) - 1], $matches)) {
array_pop($lines);
- $totalTests = (int)$matches[2];
+ $totalTests = (int) $matches[2];
}
$rtn = $this->processTestLines($lines);
diff --git a/PHPCI/Plugin/Wipe.php b/PHPCI/Plugin/Wipe.php
index 7a0c2107..e87800b4 100644
--- a/PHPCI/Plugin/Wipe.php
+++ b/PHPCI/Plugin/Wipe.php
@@ -1,11 +1,11 @@
directory)) {
- $cmd = 'rm -rf %s*';
+ $cmd = 'rm -Rf "%s"';
+ if (IS_WIN) {
+ $cmd = 'rmdir /S /Q "%s"';
+ }
$success = $this->phpci->executeCommand($cmd, $this->directory);
}
return $success;
diff --git a/PHPCI/Store.php b/PHPCI/Store.php
index 6806a097..bb8e4a21 100644
--- a/PHPCI/Store.php
+++ b/PHPCI/Store.php
@@ -1,4 +1,12 @@
prepare($query);
+ $stmt->bindValue(':project_id', $value);
+
+ if ($stmt->execute()) {
+ $res = $stmt->fetchAll(\PDO::FETCH_ASSOC);
+
+ $map = function ($item) {
+ return new BuildMeta($item);
+ };
+ $rtn = array_map($map, $res);
+
+ return array('items' => $rtn, 'count' => $count);
+ } else {
+ return array('items' => array(), 'count' => 0);
+ }
+ }
+
public function getByBuildId($value, $limit = null, $useConnection = 'read')
{
if (is_null($value)) {
diff --git a/PHPCI/Store/BuildMetaStore.php b/PHPCI/Store/BuildMetaStore.php
index ad17ef93..9800dcad 100644
--- a/PHPCI/Store/BuildMetaStore.php
+++ b/PHPCI/Store/BuildMetaStore.php
@@ -1,7 +1,10 @@
prepare($query);
+ $stmt = Database::getConnection('read')->prepare($query);
$stmt->bindValue(':pid', $projectId);
if ($stmt->execute()) {
@@ -39,6 +41,28 @@ class BuildStore extends BuildStoreBase
}
}
+ public function getByProjectAndCommit($projectId, $commitId)
+ {
+ $query = 'SELECT * FROM `build` WHERE `project_id` = :project_id AND `commit_id` = :commit_id';
+ $stmt = Database::getConnection('read')->prepare($query);
+ $stmt->bindValue(':project_id', $projectId);
+ $stmt->bindValue(':commit_id', $commitId);
+
+ if ($stmt->execute()) {
+ $res = $stmt->fetchAll(\PDO::FETCH_ASSOC);
+
+ $map = function ($item) {
+ return new Build($item);
+ };
+
+ $rtn = array_map($map, $res);
+
+ return array('items' => $rtn, 'count' => count($rtn));
+ } else {
+ return array('items' => array(), 'count' => 0);
+ }
+ }
+
public function getMeta($key, $projectId, $buildId = null, $numResults = 1)
{
$select = '`build_id`, `meta_key`, `meta_value`';
@@ -46,7 +70,7 @@ class BuildStore extends BuildStoreBase
$where = '`meta_key` = :key AND `project_id` = :projectId ' . $and;
$query = 'SELECT '.$select.' FROM `build_meta` WHERE '.$where.' ORDER BY id DESC LIMIT :numResults';
- $stmt = \b8\Database::getConnection('read')->prepare($query);
+ $stmt = Database::getConnection('read')->prepare($query);
$stmt->bindValue(':key', $key, \PDO::PARAM_STR);
$stmt->bindValue(':projectId', (int)$projectId, \PDO::PARAM_INT);
$stmt->bindValue(':buildId', (int)$buildId, \PDO::PARAM_INT);
@@ -77,7 +101,7 @@ class BuildStore extends BuildStoreBase
$cols = '`project_id`, `build_id`, `meta_key`, `meta_value`';
$query = 'REPLACE INTO build_meta ('.$cols.') VALUES (:projectId, :buildId, :key, :value)';
- $stmt = \b8\Database::getConnection('read')->prepare($query);
+ $stmt = Database::getConnection('read')->prepare($query);
$stmt->bindValue(':key', $key, \PDO::PARAM_STR);
$stmt->bindValue(':projectId', (int)$projectId, \PDO::PARAM_INT);
$stmt->bindValue(':buildId', (int)$buildId, \PDO::PARAM_INT);
diff --git a/PHPCI/Store/ProjectStore.php b/PHPCI/Store/ProjectStore.php
index c844d756..0596d0c9 100644
--- a/PHPCI/Store/ProjectStore.php
+++ b/PHPCI/Store/ProjectStore.php
@@ -1,11 +1,11 @@
getStatus())
getBranch(); ?>
- getPlugins(), true);
-
- if ( !is_array($plugins) ) {
- $plugins = array();
- }
- if ( 0 === count($plugins) ) {
- ?>
- $pluginstatus):
- $subcls = $pluginstatus?'label label-success':'label label-danger';
- ?> Build()->formatPluginName($plugin); ?>
-
+
diff --git a/PHPCI/View/Email/failed.phtml b/PHPCI/View/Email/failed.phtml
index fc199540..4391748f 100644
--- a/PHPCI/View/Email/failed.phtml
+++ b/PHPCI/View/Email/failed.phtml
@@ -1,15 +1,15 @@
- = $project->getTitle(); ?> - Build #= $build->getId(); ?>
+ getTitle(); ?> - Build #getId(); ?>
-
Your commit = $build->getCommitId(); ?> caused a failed build in project = $project->getTitle(); ?> .
+
Your commit getCommitId(); ?> caused a failed build in project getTitle(); ?> .
-
= $build->getCommitMessage(); ?>
+
getCommitMessage(); ?>
-
Please review your commit and the build log .
+
Please review your commit and the build log .
\ No newline at end of file
diff --git a/PHPCI/View/Plugin/index.phtml b/PHPCI/View/Plugin/index.phtml
index 3cfac153..a224c752 100644
--- a/PHPCI/View/Plugin/index.phtml
+++ b/PHPCI/View/Plugin/index.phtml
@@ -1,8 +1,4 @@
Packages and Provided Plugins
-
-
PHPCI cannot automatically install/remove plugins for you, as either the shell_exec()
- function is disabled or PHPCI could not find Composer. PHPCI will update composer.json for you, but you will need to run Composer manually to make the changes.
-
PHPCI cannot update composer.json for you as it is not writable.
@@ -12,12 +8,8 @@
has been removed.
-
-
has been installed.
-
-
-
has been added to composer.json and will be installed next time you run composer update.
+
has been added to composer.json for you and will be installed next time you run composer update.
@@ -34,9 +26,9 @@
- = $plugin->name; ?>
- = $plugin->class; ?>
- = $plugin->source; ?>
+ name; ?>
+ class; ?>
+ source; ?>
diff --git a/PHPCI/View/Session.phtml b/PHPCI/View/Session.phtml
new file mode 100644
index 00000000..ed75d2b4
--- /dev/null
+++ b/PHPCI/View/Session.phtml
@@ -0,0 +1,88 @@
+
+
+
+
Log in to PHPCI
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/PHPCI/View/Session/forgotPassword.phtml b/PHPCI/View/Session/forgotPassword.phtml
new file mode 100644
index 00000000..a9897bbd
--- /dev/null
+++ b/PHPCI/View/Session/forgotPassword.phtml
@@ -0,0 +1,33 @@
+
+
+ We've emailed you a link to reset your password.
+
+
+
+
+
+ Don't worry! Just enter your email address below and we'll email you a link to reset your password.
+
+
+
+
+
+
\ No newline at end of file
diff --git a/PHPCI/View/Session/login.phtml b/PHPCI/View/Session/login.phtml
index 2314a639..bc2f16e1 100644
--- a/PHPCI/View/Session/login.phtml
+++ b/PHPCI/View/Session/login.phtml
@@ -1,91 +1,6 @@
-
-
-
-
Log in to PHPCI
+
+
Incorrect email address or password
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Incorrect email address or password
-
-
-
-
-
-
-
-
-
+
Forgotten your password?
\ No newline at end of file
diff --git a/PHPCI/View/Session/resetPassword.phtml b/PHPCI/View/Session/resetPassword.phtml
new file mode 100644
index 00000000..9544879e
--- /dev/null
+++ b/PHPCI/View/Session/resetPassword.phtml
@@ -0,0 +1,27 @@
+
+
+
+
+ Please enter a new password
+
+
+
+
+
+
+
+
+
+
+
diff --git a/PHPCI/View/Settings/index.phtml b/PHPCI/View/Settings/index.phtml
index 3f0eb5ec..322206c4 100644
--- a/PHPCI/View/Settings/index.phtml
+++ b/PHPCI/View/Settings/index.phtml
@@ -10,6 +10,12 @@
+
+
+ PHPCI cannot write to your config.yml file, settings may not be saved properly until this is rectified.
+
+
+
Your Github account has been linked.
diff --git a/PHPCI/View/User/profile.phtml b/PHPCI/View/User/profile.phtml
new file mode 100644
index 00000000..5e006738
--- /dev/null
+++ b/PHPCI/View/User/profile.phtml
@@ -0,0 +1,15 @@
+
User()->getName(); ?>
+
+
+
Your details have been updated.
+
+
+
+
+
Update your details
+
+
+
+
+
+
diff --git a/PHPCI/View/exception.phtml b/PHPCI/View/exception.phtml
new file mode 100644
index 00000000..1ecbe6a4
--- /dev/null
+++ b/PHPCI/View/exception.phtml
@@ -0,0 +1,9 @@
+
+
+
Sorry, there was a problem
+
+
+
+ getMessage(); ?>
+
+
\ No newline at end of file
diff --git a/PHPCI/View/layout.phtml b/PHPCI/View/layout.phtml
index 58b78cc2..ed3e539e 100644
--- a/PHPCI/View/layout.phtml
+++ b/PHPCI/View/layout.phtml
@@ -37,13 +37,27 @@
- User()->getName()); ?>
- Log out
+
+
+
+
+
+ User()->getName()); ?>
+
+
+
+
User()->getIsAdmin()): ?>
+
Admin
diff --git a/PHPCI/ZeroConfigPlugin.php b/PHPCI/ZeroConfigPlugin.php
index b779cf5e..73f7746c 100644
--- a/PHPCI/ZeroConfigPlugin.php
+++ b/PHPCI/ZeroConfigPlugin.php
@@ -2,9 +2,9 @@
/**
* PHPCI - Continuous Integration for PHP
*
- * @copyright Copyright 2013, Block 8 Limited.
+ * @copyright Copyright 2014, Block 8 Limited.
* @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md
- * @link http://www.phptesting.org/
+ * @link https://www.phptesting.org/
*/
namespace PHPCI;
diff --git a/README.md b/README.md
index 77d9ef0c..eb0db9a9 100644
--- a/README.md
+++ b/README.md
@@ -3,11 +3,9 @@ PHPCI
PHPCI is a free and open source continuous integration tool specifically designed for PHP. We've built it with simplicity in mind, so whilst it doesn't do *everything* Jenkins can do, it is a breeze to set up and use.
-_**Please be aware that PHPCI is a beta-release project, so whilst it is very stable, there may be bugs and/or missing features.**_
-
**Current Build Status**
-[](http://phpci.block8.net/build-status/view/2)
+[](http://phpci.block8.net/build-status/view/2?branch=master)
##What it does:
* Clones your project from Github, Bitbucket or a local path
diff --git a/Tests/PHPCI/Helper/CommandExecutorTest.php b/Tests/PHPCI/Helper/CommandExecutorTest.php
index 4925e639..8ae045dc 100644
--- a/Tests/PHPCI/Helper/CommandExecutorTest.php
+++ b/Tests/PHPCI/Helper/CommandExecutorTest.php
@@ -2,13 +2,13 @@
namespace PHPCI\Plugin\Tests\Helper;
-use PHPCI\Helper\CommandExecutor;
+use PHPCI\Helper\UnixCommandExecutor;
use \Prophecy\PhpUnit\ProphecyTestCase;
class CommandExecutorTest extends ProphecyTestCase
{
/**
- * @var CommandExecutor
+ * @var UnixCommandExecutor
*/
protected $testedExecutor;
@@ -16,33 +16,33 @@ class CommandExecutorTest extends ProphecyTestCase
{
parent::setUp();
$mockBuildLogger = $this->prophesize('PHPCI\Logging\BuildLogger');
- $this->testedExecutor = new CommandExecutor($mockBuildLogger->reveal(), __DIR__ . "/");
+ $this->testedExecutor = new UnixCommandExecutor($mockBuildLogger->reveal(), __DIR__ . "/");
}
public function testGetLastOutput_ReturnsOutputOfCommand()
{
- $this->testedExecutor->buildAndExecuteCommand(array('echo "%s"', 'Hello World'));
+ $this->testedExecutor->executeCommand(array('echo "%s"', 'Hello World'));
$output = $this->testedExecutor->getLastOutput();
$this->assertEquals("Hello World", $output);
}
public function testGetLastOutput_ForgetsPreviousCommandOutput()
{
- $this->testedExecutor->buildAndExecuteCommand(array('echo "%s"', 'Hello World'));
- $this->testedExecutor->buildAndExecuteCommand(array('echo "%s"', 'Hello Tester'));
+ $this->testedExecutor->executeCommand(array('echo "%s"', 'Hello World'));
+ $this->testedExecutor->executeCommand(array('echo "%s"', 'Hello Tester'));
$output = $this->testedExecutor->getLastOutput();
$this->assertEquals("Hello Tester", $output);
}
public function testExecuteCommand_ReturnsTrueForValidCommands()
{
- $returnValue = $this->testedExecutor->buildAndExecuteCommand(array('echo "%s"', 'Hello World'));
+ $returnValue = $this->testedExecutor->executeCommand(array('echo "%s"', 'Hello World'));
$this->assertTrue($returnValue);
}
public function testExecuteCommand_ReturnsFalseForInvalidCommands()
{
- $returnValue = $this->testedExecutor->buildAndExecuteCommand(array('eerfdcvcho "%s" > /dev/null 2>&1', 'Hello World'));
+ $returnValue = $this->testedExecutor->executeCommand(array('eerfdcvcho "%s" > /dev/null 2>&1', 'Hello World'));
$this->assertFalse($returnValue);
}
diff --git a/bootstrap.php b/bootstrap.php
index ee819c49..3b951a43 100755
--- a/bootstrap.php
+++ b/bootstrap.php
@@ -33,10 +33,24 @@ $autoload = function ($class) {
spl_autoload_register($autoload, true, true);
-if (!file_exists(dirname(__FILE__) . '/PHPCI/config.yml') && (!defined('PHPCI_IS_CONSOLE') || !PHPCI_IS_CONSOLE)) {
+// If the PHPCI config file is not where we expect it, try looking in
+// env for an alternative config path.
+$configFile = dirname(__FILE__) . '/PHPCI/config.yml';
+
+if (!file_exists($configFile)) {
+ $configEnv = getenv('phpci_config_file');
+
+ if (!empty($configEnv)) {
+ $configFile = $configEnv;
+ }
+}
+
+// If we don't have a config file at all, fail at this point and tell the user to install:
+if (!file_exists($configFile) && (!defined('PHPCI_IS_CONSOLE') || !PHPCI_IS_CONSOLE)) {
die('PHPCI has not yet been installed - Please use the command ./console phpci:install to install it.');
}
+// If composer has not been run, fail at this point and tell the user to install:
if (!file_exists(dirname(__FILE__) . '/vendor/autoload.php') && defined('PHPCI_IS_CONSOLE') && PHPCI_IS_CONSOLE) {
file_put_contents('php://stderr', 'Please install PHPCI with "composer install" before using console');
exit(1);
@@ -45,8 +59,10 @@ if (!file_exists(dirname(__FILE__) . '/vendor/autoload.php') && defined('PHPCI_I
// Load Composer autoloader:
require_once(dirname(__FILE__) . '/vendor/autoload.php');
-$loggerConfig = LoggerConfig::newFromFile(__DIR__ . "/loggerconfig.php");
-Handler::register($loggerConfig->getFor('_'));
+if (defined('PHPCI_IS_CONSOLE') && PHPCI_IS_CONSOLE) {
+ $loggerConfig = LoggerConfig::newFromFile(__DIR__ . "/loggerconfig.php");
+ Handler::register($loggerConfig->getFor('_'));
+}
// Load configuration if present:
$conf = array();
@@ -54,9 +70,10 @@ $conf['b8']['app']['namespace'] = 'PHPCI';
$conf['b8']['app']['default_controller'] = 'Home';
$conf['b8']['view']['path'] = dirname(__FILE__) . '/PHPCI/View/';
-if (file_exists(dirname(__FILE__) . '/PHPCI/config.yml')) {
- $config = new b8\Config($conf);
- $config->loadYaml(dirname(__FILE__) . '/PHPCI/config.yml');
+$config = new b8\Config($conf);
+
+if (file_exists($configFile)) {
+ $config->loadYaml($configFile);
}
require_once(dirname(__FILE__) . '/vars.php');
diff --git a/composer.json b/composer.json
index a6dec175..3d9a81be 100644
--- a/composer.json
+++ b/composer.json
@@ -23,7 +23,7 @@
},
"require": {
- "php": ">=5.3.3",
+ "php": ">=5.3.8",
"ext-mcrypt": "*",
"ext-pdo": "*",
"ext-pdo_mysql": "*",
@@ -34,7 +34,8 @@
"symfony/console" : "~2.1",
"psr/log": "~1.0",
"monolog/monolog": "~1.6",
- "pimple/pimple": "~1.1"
+ "pimple/pimple": "~1.1",
+ "robmorgan/phinx": "*"
},
"require-dev": {
@@ -43,6 +44,7 @@
},
"suggest": {
+ "block8/php-docblock-checker": "PHP Docblock Checker",
"phpmd/phpmd": "PHP Mess Detector",
"sebastian/phpcpd": "PHP Copy/Paste Detector",
"squizlabs/php_codesniffer": "PHP Code Sniffer",
@@ -51,6 +53,7 @@
"phploc/phploc": "PHP Lines of Code",
"atoum/atoum": "Atoum",
"jakub-onderka/php-parallel-lint": "Parallel Linting Tool",
- "behat/behat": "Behat BDD Testing"
+ "behat/behat": "Behat BDD Testing",
+ "hipchat/hipchat-php": "Hipchat integration"
}
}
\ No newline at end of file
diff --git a/composer.lock b/composer.lock
index 1110c4b5..4a2b4e73 100644
--- a/composer.lock
+++ b/composer.lock
@@ -3,20 +3,20 @@
"This file locks the dependencies of your project to a known state",
"Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file"
],
- "hash": "d6834b50e3ea80f4c6f0a05707d0e5f1",
+ "hash": "4fafee12f4bcc7e7dac5f997b511eabb",
"packages": [
{
"name": "block8/b8framework",
- "version": "1.1.2",
+ "version": "1.1.4",
"source": {
"type": "git",
"url": "https://github.com/Block8/b8framework.git",
- "reference": "63a18f2fdc1dc31b657ea39ef841339d89f24ce8"
+ "reference": "2ae699b8b6a28752e9eebbd43c518fdc80a9d322"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/Block8/b8framework/zipball/63a18f2fdc1dc31b657ea39ef841339d89f24ce8",
- "reference": "63a18f2fdc1dc31b657ea39ef841339d89f24ce8",
+ "url": "https://api.github.com/repos/Block8/b8framework/zipball/2ae699b8b6a28752e9eebbd43c518fdc80a9d322",
+ "reference": "2ae699b8b6a28752e9eebbd43c518fdc80a9d322",
"shasum": ""
},
"require": {
@@ -50,7 +50,7 @@
"mvc",
"php"
],
- "time": "2014-04-01 15:30:13"
+ "time": "2014-05-13 09:41:11"
},
{
"name": "ircmaxell/password-compat",
@@ -246,26 +246,86 @@
"time": "2012-12-21 11:40:51"
},
{
- "name": "swiftmailer/swiftmailer",
- "version": "v5.1.0",
+ "name": "robmorgan/phinx",
+ "version": "v0.3.4",
"source": {
"type": "git",
- "url": "https://github.com/swiftmailer/swiftmailer.git",
- "reference": "748c01c1144713ac0118396935d10b6ceec91b68"
+ "url": "https://github.com/robmorgan/phinx.git",
+ "reference": "5de112662b64d4d6ffa45d4dfc557c2a4c1620cd"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/748c01c1144713ac0118396935d10b6ceec91b68",
- "reference": "748c01c1144713ac0118396935d10b6ceec91b68",
+ "url": "https://api.github.com/repos/robmorgan/phinx/zipball/5de112662b64d4d6ffa45d4dfc557c2a4c1620cd",
+ "reference": "5de112662b64d4d6ffa45d4dfc557c2a4c1620cd",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.2",
+ "symfony/class-loader": "2.*",
+ "symfony/config": "2.*",
+ "symfony/console": "2.*",
+ "symfony/yaml": "2.*"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "3.7.*",
+ "squizlabs/php_codesniffer": "dev-phpcs-fixer"
+ },
+ "bin": [
+ "bin/phinx"
+ ],
+ "type": "library",
+ "autoload": {
+ "psr-0": {
+ "Phinx": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Rob Morgan",
+ "email": "robbym@gmail.com",
+ "homepage": "http://robmorgan.id.au",
+ "role": "Lead Developer"
+ }
+ ],
+ "description": "Phinx makes it ridiculously easy to manage the database migrations for your PHP app.",
+ "homepage": "http://phinx.org",
+ "keywords": [
+ "database",
+ "database migrations",
+ "db",
+ "migrations",
+ "phinx"
+ ],
+ "time": "2014-04-27 15:25:07"
+ },
+ {
+ "name": "swiftmailer/swiftmailer",
+ "version": "v5.2.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/swiftmailer/swiftmailer.git",
+ "reference": "043e336b871f17a117f76ef8e190eddfc04c8d48"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/043e336b871f17a117f76ef8e190eddfc04c8d48",
+ "reference": "043e336b871f17a117f76ef8e190eddfc04c8d48",
"shasum": ""
},
"require": {
"php": ">=5.2.4"
},
+ "require-dev": {
+ "mockery/mockery": "~0.9.1"
+ },
"type": "library",
"extra": {
"branch-alias": {
- "dev-master": "5.1-dev"
+ "dev-master": "5.2-dev"
}
},
"autoload": {
@@ -294,7 +354,109 @@
"mail",
"mailer"
],
- "time": "2014-03-18 09:03:27"
+ "time": "2014-05-08 08:11:19"
+ },
+ {
+ "name": "symfony/class-loader",
+ "version": "v2.4.4",
+ "target-dir": "Symfony/Component/ClassLoader",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/ClassLoader.git",
+ "reference": "5101f3094903a95db3552a834a9c6585afc10d3b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/ClassLoader/zipball/5101f3094903a95db3552a834a9c6585afc10d3b",
+ "reference": "5101f3094903a95db3552a834a9c6585afc10d3b",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "require-dev": {
+ "symfony/finder": "~2.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.4-dev"
+ }
+ },
+ "autoload": {
+ "psr-0": {
+ "Symfony\\Component\\ClassLoader\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com",
+ "homepage": "http://fabien.potencier.org",
+ "role": "Lead Developer"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "http://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony ClassLoader Component",
+ "homepage": "http://symfony.com",
+ "time": "2014-04-16 10:34:31"
+ },
+ {
+ "name": "symfony/config",
+ "version": "v2.4.4",
+ "target-dir": "Symfony/Component/Config",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/Config.git",
+ "reference": "2effc67af6f21a0d267210b72d0b0b691d113528"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/Config/zipball/2effc67af6f21a0d267210b72d0b0b691d113528",
+ "reference": "2effc67af6f21a0d267210b72d0b0b691d113528",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3",
+ "symfony/filesystem": "~2.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.4-dev"
+ }
+ },
+ "autoload": {
+ "psr-0": {
+ "Symfony\\Component\\Config\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com",
+ "homepage": "http://fabien.potencier.org",
+ "role": "Lead Developer"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "http://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony Config Component",
+ "homepage": "http://symfony.com",
+ "time": "2014-04-22 08:11:06"
},
{
"name": "symfony/console",
@@ -351,6 +513,55 @@
"homepage": "http://symfony.com",
"time": "2014-04-27 13:34:57"
},
+ {
+ "name": "symfony/filesystem",
+ "version": "v2.4.4",
+ "target-dir": "Symfony/Component/Filesystem",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/Filesystem.git",
+ "reference": "a3af8294bcce4a7c1b2892363b0c9d8109affad4"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/Filesystem/zipball/a3af8294bcce4a7c1b2892363b0c9d8109affad4",
+ "reference": "a3af8294bcce4a7c1b2892363b0c9d8109affad4",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.4-dev"
+ }
+ },
+ "autoload": {
+ "psr-0": {
+ "Symfony\\Component\\Filesystem\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com",
+ "homepage": "http://fabien.potencier.org",
+ "role": "Lead Developer"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "http://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony Filesystem Component",
+ "homepage": "http://symfony.com",
+ "time": "2014-04-16 10:34:31"
+ },
{
"name": "symfony/yaml",
"version": "v2.4.4",
@@ -882,7 +1093,7 @@
],
"platform": {
- "php": ">=5.3.3",
+ "php": ">=5.3.8",
"ext-mcrypt": "*",
"ext-pdo": "*",
"ext-pdo_mysql": "*"
diff --git a/phinx.php b/phinx.php
new file mode 100644
index 00000000..3c407b1e
--- /dev/null
+++ b/phinx.php
@@ -0,0 +1,29 @@
+get('b8.database.servers.write');
+
+if (!is_array($writeServers)) {
+ $writeServers = array($writeServers);
+}
+
+$conf = array(
+ 'paths' => array(
+ 'migrations' => 'PHPCI/Migrations',
+ ),
+
+ 'environments' => array(
+ 'default_migration_table' => 'migration',
+ 'default_database' => 'phpci',
+ 'phpci' => array(
+ 'adapter' => 'mysql',
+ 'host' => end($writeServers),
+ 'name' => $config->get('b8.database.name'),
+ 'user' => $config->get('b8.database.username'),
+ 'pass' => $config->get('b8.database.password'),
+ ),
+ ),
+);
+
+return $conf;
diff --git a/phpci.yml b/phpci.yml
index 9dda1fa4..4522d0fe 100644
--- a/phpci.yml
+++ b/phpci.yml
@@ -5,18 +5,24 @@ build_settings:
- "Tests"
- "PHPCI/Command" # PHPMD complains about un-used parameters, but they are required.
- "public/install.php" # PHPCS really doesn't like PHP mixed with HTML (and so it shouldn't)
+ - "PHPCI/Migrations" # Ignore the migrations directory, as both PHPMD and PHPCS can't cope with them.
setup:
composer:
action: "install"
+ prefer_dist: false
test:
php_mess_detector:
- allowed_warnings: 10 # Set max warnings at the current level - Disallow any further errors creeping in.
+ allowed_warnings: 0
php_code_sniffer:
standard: "PSR2"
+ allowed_warnings: 0
+ allowed_errors: 0
php_loc:
php_unit:
+ php_docblock_checker:
+ allowed_warnings: -1 # Allow unlimited warnings for now.
failure:
email:
diff --git a/public/assets/css/phpci.css b/public/assets/css/phpci.css
index 7093c2aa..ce98017a 100644
--- a/public/assets/css/phpci.css
+++ b/public/assets/css/phpci.css
@@ -80,4 +80,8 @@ h1 {
position: absolute;
right: 25px;
width: 200px;
+}
+
+.panel-body {
+ overflow: auto;
}
\ No newline at end of file
diff --git a/public/assets/js/build-plugins/loc.js b/public/assets/js/build-plugins/loc.js
index d71a42ad..45eeac91 100644
--- a/public/assets/js/build-plugins/loc.js
+++ b/public/assets/js/build-plugins/loc.js
@@ -4,6 +4,7 @@ var locPlugin = PHPCI.UiPlugin.extend({
title: 'Lines of Code',
lastData: null,
displayOnUpdate: false,
+ rendered: false,
register: function() {
var self = this;
@@ -14,8 +15,7 @@ var locPlugin = PHPCI.UiPlugin.extend({
});
$(window).on('build-updated', function(data) {
- if (data.queryData.status > 1) {
- self.displayOnUpdate = true;
+ if (data.queryData.status > 1 && !self.rendered) {
query();
}
});
@@ -29,10 +29,7 @@ var locPlugin = PHPCI.UiPlugin.extend({
onUpdate: function(e) {
this.lastData = e.queryData;
-
- if (this.displayOnUpdate) {
- this.displayChart();
- }
+ this.displayChart();
},
displayChart: function() {
@@ -42,6 +39,8 @@ var locPlugin = PHPCI.UiPlugin.extend({
return;
}
+ this.rendered = true;
+
$('#phploc-lines').empty().animate({height: '275px'});
var titles = ['Build', 'Lines', 'Comment Lines', 'Non-Comment Lines', 'Logical Lines'];
diff --git a/public/assets/js/build-plugins/phpcs.js b/public/assets/js/build-plugins/phpcs.js
index aaac3ec9..ce302965 100644
--- a/public/assets/js/build-plugins/phpcs.js
+++ b/public/assets/js/build-plugins/phpcs.js
@@ -3,8 +3,8 @@ var phpcsPlugin = PHPCI.UiPlugin.extend({
css: 'col-lg-12 col-md-12 col-sm-12 col-xs-12',
title: 'PHP Code Sniffer',
lastData: null,
- displayOnUpdate: false,
box: true,
+ rendered: false,
register: function() {
var self = this;
@@ -14,16 +14,14 @@ var phpcsPlugin = PHPCI.UiPlugin.extend({
self.onUpdate(data);
});
- $(window).on('build-updated', function(data) {
- if (data.queryData.status > 1) {
- self.displayOnUpdate = true;
+ $(window).on('build-updated', function() {
+ if (!self.rendered) {
query();
}
});
},
render: function() {
-
return $('
' +
'' +
'' +
@@ -35,10 +33,11 @@ var phpcsPlugin = PHPCI.UiPlugin.extend({
},
onUpdate: function(e) {
- if (this.lastData && this.lastData[0]) {
+ if (!e.queryData) {
return;
}
+ this.rendered = true;
this.lastData = e.queryData;
var errors = this.lastData[0].meta_value;
diff --git a/public/assets/js/build-plugins/phpdoccheck.js b/public/assets/js/build-plugins/phpdoccheck.js
new file mode 100644
index 00000000..adfb75ff
--- /dev/null
+++ b/public/assets/js/build-plugins/phpdoccheck.js
@@ -0,0 +1,79 @@
+var phpdoccheckPlugin = PHPCI.UiPlugin.extend({
+ id: 'build-phpdoccheck-warnings',
+ css: 'col-lg-12 col-md-12 col-sm-12 col-xs-12',
+ title: 'PHP Docblock Checker',
+ lastData: null,
+ displayOnUpdate: false,
+ box: true,
+ rendered: false,
+
+ register: function() {
+ var self = this;
+ var query = PHPCI.registerQuery('phpdoccheck-data', -1, {key: 'phpdoccheck-data'})
+
+ $(window).on('phpdoccheck-data', function(data) {
+ self.onUpdate(data);
+ });
+
+ $(window).on('build-updated', function() {
+ if (!self.rendered) {
+ self.displayOnUpdate = true;
+ query();
+ }
+ });
+ },
+
+ render: function() {
+ return $('' +
+ '' +
+ '' +
+ ' Type ' +
+ ' File ' +
+ ' Line ' +
+ ' Class ' +
+ ' Method ' +
+ ' ' +
+ '
');
+ },
+
+ onUpdate: function(e) {
+ if (!e.queryData) {
+ return;
+ }
+
+ this.rendered = true;
+ this.lastData = e.queryData;
+
+ var errors = this.lastData[0].meta_value;
+ var tbody = $('#phpdoccheck-data tbody');
+ tbody.empty();
+
+ for (var i in errors) {
+ var file = errors[i].file;
+
+ if (PHPCI.fileLinkTemplate) {
+ var fileLink = PHPCI.fileLinkTemplate.replace('{FILE}', file);
+ fileLink = fileLink.replace('{LINE}', errors[i].line);
+
+ file = '' + file + ' ';
+ }
+
+ var row = $(' ' +
+ ''+errors[i].type+' ' +
+ ''+file+' ' +
+ ''+errors[i].line+' ' +
+ ''+errors[i].class+' ' +
+ ''+errors[i].method+' ');
+
+ if (errors[i].type == 'method') {
+ row.addClass('danger');
+ } else {
+ row.addClass('warning');
+ }
+
+ tbody.append(row);
+ }
+ }
+});
+
+PHPCI.registerPlugin(new phpdoccheckPlugin());
diff --git a/public/assets/js/build-plugins/phpmd.js b/public/assets/js/build-plugins/phpmd.js
index abf90f4c..c2e6c7e1 100644
--- a/public/assets/js/build-plugins/phpmd.js
+++ b/public/assets/js/build-plugins/phpmd.js
@@ -5,6 +5,7 @@ var phpmdPlugin = PHPCI.UiPlugin.extend({
lastData: null,
displayOnUpdate: false,
box: true,
+ rendered: false,
register: function() {
var self = this;
@@ -14,8 +15,8 @@ var phpmdPlugin = PHPCI.UiPlugin.extend({
self.onUpdate(data);
});
- $(window).on('build-updated', function(data) {
- if (data.queryData.status > 1) {
+ $(window).on('build-updated', function() {
+ if (!self.rendered) {
self.displayOnUpdate = true;
query();
}
@@ -36,10 +37,11 @@ var phpmdPlugin = PHPCI.UiPlugin.extend({
},
onUpdate: function(e) {
- if (this.lastData && this.lastData[0]) {
+ if (!e.queryData) {
return;
}
+ this.rendered = true;
this.lastData = e.queryData;
var errors = this.lastData[0].meta_value;
diff --git a/public/assets/js/build-plugins/phpunit.js b/public/assets/js/build-plugins/phpunit.js
index c09855a9..1afe4d27 100644
--- a/public/assets/js/build-plugins/phpunit.js
+++ b/public/assets/js/build-plugins/phpunit.js
@@ -5,6 +5,7 @@ var phpunitPlugin = PHPCI.UiPlugin.extend({
lastData: null,
displayOnUpdate: false,
box: true,
+ rendered: false,
register: function() {
var self = this;
@@ -14,8 +15,8 @@ var phpunitPlugin = PHPCI.UiPlugin.extend({
self.onUpdate(data);
});
- $(window).on('build-updated', function(data) {
- if (data.queryData.status > 1) {
+ $(window).on('build-updated', function() {
+ if (!self.rendered) {
self.displayOnUpdate = true;
query();
}
@@ -33,10 +34,11 @@ var phpunitPlugin = PHPCI.UiPlugin.extend({
},
onUpdate: function(e) {
- if (this.lastData && this.lastData[0]) {
+ if (!e.queryData) {
return;
}
+ this.rendered = true;
this.lastData = e.queryData;
var tests = this.lastData[0].meta_value;
diff --git a/public/assets/js/build-plugins/warnings.js b/public/assets/js/build-plugins/warnings.js
index cc2db78f..7ae48970 100644
--- a/public/assets/js/build-plugins/warnings.js
+++ b/public/assets/js/build-plugins/warnings.js
@@ -7,10 +7,12 @@ var warningsPlugin = PHPCI.UiPlugin.extend({
'phpcs-warnings': 'PHPCS Warnings',
'phpcs-errors': 'PHPCS Errors',
'phplint-errors': 'PHPLint Errors',
- 'phpunit-errors': 'PHPUnit Errors'
+ 'phpunit-errors': 'PHPUnit Errors',
+ 'phpdoccheck-warnings': 'PHP Docblock Checker Warnings'
},
data: {},
displayOnUpdate: false,
+ rendered: false,
register: function() {
var self = this;
@@ -20,12 +22,12 @@ var warningsPlugin = PHPCI.UiPlugin.extend({
queries.push(PHPCI.registerQuery(key, -1, {num_builds: 10, key: key}));
}
- $(window).on('phpmd-warnings phpcs-warnings phpcs-errors phplint-errors phpunit-errors', function(data) {
+ $(window).on('phpmd-warnings phpcs-warnings phpcs-errors phplint-errors phpunit-errors phpdoccheck-errors', function(data) {
self.onUpdate(data);
});
$(window).on('build-updated', function(data) {
- if (data.queryData.status > 1) {
+ if (!self.rendered && data.queryData.status > 1) {
self.displayOnUpdate = true;
for (var query in queries) {
queries[query]();
@@ -67,6 +69,7 @@ var warningsPlugin = PHPCI.UiPlugin.extend({
displayChart: function() {
var self = this;
+ self.rendered = true;
$('#build-warnings').empty().animate({height: '275px'});
diff --git a/public/assets/js/phpci.js b/public/assets/js/phpci.js
index 05d4c3cb..9d86b849 100644
--- a/public/assets/js/phpci.js
+++ b/public/assets/js/phpci.js
@@ -317,8 +317,19 @@ var PHPCIObject = Class.extend({
updateInterval: null,
init: function(build) {
+ var self = this;
this.buildId = build;
this.registerQuery('build-updated', 10);
+
+ $(window).on('build-updated', function(data) {
+
+ // If the build has finished, stop updating every 10 seconds:
+ if (data.queryData.status > 1) {
+ self.cancelQuery('build-updated');
+ $(window).trigger({type: 'build-complete'});
+ }
+
+ });
},
registerQuery: function(name, seconds, query) {
@@ -337,12 +348,16 @@ var PHPCIObject = Class.extend({
};
if (seconds != -1) {
- setInterval(cb, seconds * 1000);
+ self.queries[name] = setInterval(cb, seconds * 1000);
}
return cb;
},
+ cancelQuery: function (name) {
+ clearInterval(this.queries[name]);
+ },
+
registerPlugin: function(plugin) {
this.plugins[plugin.id] = plugin;
plugin.register();
diff --git a/public/index.php b/public/index.php
index 134e9a7b..94191794 100644
--- a/public/index.php
+++ b/public/index.php
@@ -2,9 +2,9 @@
/**
* PHPCI - Continuous Integration for PHP
*
-* @copyright Copyright 2013, Block 8 Limited.
-* @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md
-* @link http://www.phptesting.org/
+* @copyright Copyright 2014, Block 8 Limited.
+* @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md
+* @link https://www.phptesting.org/
*/
session_start();