Merge branch 'master' into dc/db-types

This commit is contained in:
Dan Cryer 2015-02-09 11:12:30 +00:00
commit 57a02a82e9
27 changed files with 1040 additions and 451 deletions

View file

@ -22,6 +22,11 @@ use PHPCI\Model\Build;
*/ */
class Application extends b8\Application class Application extends b8\Application
{ {
/**
* @var \PHPCI\Controller
*/
protected $controller;
/** /**
* Initialise PHPCI - Handles session verification, routing, etc. * Initialise PHPCI - Handles session verification, routing, etc.
*/ */
@ -100,7 +105,7 @@ class Application extends b8\Application
$this->response->setContent($view->render()); $this->response->setContent($view->render());
} }
if ($this->response->hasLayout()) { if ($this->response->hasLayout() && $this->controller->layout) {
$this->setLayoutVariables($this->controller->layout); $this->setLayoutVariables($this->controller->layout);
$this->controller->layout->content = $this->response->getContent(); $this->controller->layout->content = $this->response->getContent();

View file

@ -30,6 +30,11 @@ class Controller extends \b8\Controller
*/ */
protected $view; protected $view;
/**
* @var \b8\View
*/
public $layout;
/** /**
* Initialise the controller. * Initialise the controller.
*/ */

View file

@ -13,6 +13,7 @@ use b8\Config;
/** /**
* Languages Helper Class - Handles loading strings files and the strings within them. * Languages Helper Class - Handles loading strings files and the strings within them.
*
* @package PHPCI\Helper * @package PHPCI\Helper
*/ */
class Lang class Lang
@ -23,6 +24,7 @@ class Lang
/** /**
* Get a specific string from the language file. * Get a specific string from the language file.
*
* @param $string * @param $string
* @return mixed|string * @return mixed|string
*/ */
@ -48,6 +50,7 @@ class Lang
/** /**
* Get the currently active language. * Get the currently active language.
*
* @return string|null * @return string|null
*/ */
public static function getLanguage() public static function getLanguage()
@ -57,7 +60,9 @@ class Lang
/** /**
* Try and load a language, and if successful, set it for use throughout the system. * Try and load a language, and if successful, set it for use throughout the system.
*
* @param $language * @param $language
*
* @return bool * @return bool
*/ */
public static function setLanguage($language) public static function setLanguage($language)
@ -73,6 +78,7 @@ class Lang
/** /**
* Return a list of available languages and their names. * Return a list of available languages and their names.
*
* @return array * @return array
*/ */
public static function getLanguageOptions() public static function getLanguageOptions()
@ -90,6 +96,7 @@ class Lang
/** /**
* Get the strings for the currently active language. * Get the strings for the currently active language.
*
* @return string[] * @return string[]
*/ */
public static function getStrings() public static function getStrings()
@ -99,6 +106,7 @@ class Lang
/** /**
* Initialise the Language helper, try load the language file for the user's browser or the configured default. * Initialise the Language helper, try load the language file for the user's browser or the configured default.
*
* @param Config $config * @param Config $config
*/ */
public static function init(Config $config) public static function init(Config $config)
@ -137,6 +145,7 @@ class Lang
/** /**
* Load a specific language file. * Load a specific language file.
*
* @return string[]|null * @return string[]|null
*/ */
protected static function loadLanguage() protected static function loadLanguage()
@ -168,4 +177,24 @@ class Lang
} }
} }
} }
/**
* Create a time tag for localization.
*
* See http://momentjs.com/docs/#/displaying/format/ for a list of supported formats.
*
* @param \DateTime $dateTime The dateTime to represent.
* @param string $format The moment.js format to use.
*
* @return string The formatted tag.
*/
public static function formatDateTime(\DateTime $dateTime, $format = 'lll')
{
return sprintf(
'<time datetime="%s" data-format="%s">%s</time>',
$dateTime->format(\DateTime::ISO8601),
$format,
$dateTime->format(\DateTime::RFC2822)
);
}
} }

View file

@ -63,7 +63,7 @@ class SshKey
*/ */
public function canGenerateKeys() public function canGenerateKeys()
{ {
$keygen = @shell_exec('ssh-keygen -h'); $keygen = @shell_exec('ssh-keygen --help');
$canGenerateKeys = !empty($keygen); $canGenerateKeys = !empty($keygen);
return $canGenerateKeys; return $canGenerateKeys;

View file

@ -14,21 +14,21 @@ $strings = array(
// Log in: // Log in:
'log_in_to_phpci' => 'Connectez-vous à PHPCI', 'log_in_to_phpci' => 'Connectez-vous à PHPCI',
'login_error' => 'Adresse email ou mot de passe invalide', 'login_error' => 'Adresse email ou mot de passe invalide',
'forgotten_password_link' => 'Mot de passe oublié ?', 'forgotten_password_link' => 'Mot de passe oublié&nbsp;?',
'reset_emailed' => 'Nous vous avons envoyé un email avec un lien pour réinitialiser votre mot de passe.', 'reset_emailed' => 'Nous vous avons envoyé un email avec un lien pour réinitialiser votre mot de passe.',
'reset_header' => '<strong>Pas d\'inquiétude</strong><br>Entrez simplement votre adresse email ci-dessous 'reset_header' => '<strong>Pas d\'inquiétude</strong><br>Entrez simplement votre adresse email ci-dessous
et nous vous enverrons un message avec un lien pour réinitialiser votre mot de passe.', et nous vous enverrons un message avec un lien pour réinitialiser votre mot de passe.',
'reset_email_address' => 'Entrez votre adresse email:', 'reset_email_address' => 'Entrez votre adresse email:',
'reset_send_email' => 'Envoyer le mail', 'reset_send_email' => 'Envoyer le mail',
'reset_enter_password' => 'Veuillez entrer un nouveau mot de passe', 'reset_enter_password' => 'Veuillez entrer un nouveau mot de passe',
'reset_new_password' => 'Nouveau mot de passe :', 'reset_new_password' => 'Nouveau mot de passe&nbsp;:',
'reset_change_password' => 'Modifier le mot de passe', 'reset_change_password' => 'Modifier le mot de passe',
'reset_no_user_exists' => 'Il n\'existe aucun utilisateur avec cette adresse email, merci de réessayer.', 'reset_no_user_exists' => 'Il n\'existe aucun utilisateur avec cette adresse email, merci de réessayer.',
'reset_email_body' => 'Bonjour %s, 'reset_email_body' => 'Bonjour %s,
Vous avez reçu cet email parce qu\'une demande de réinitialisation de mot de passe a été faite pour votre compte PHPCI. Vous avez reçu cet email parce qu\'une demande de réinitialisation de mot de passe a été faite pour votre compte PHPCI.
Si c\'est bien vous, merci de cliquer sur le lien suivant pour réinitialiser votre mot de passe : %ssession/reset-password/%d/%s Si c\'est bien vous, merci de cliquer sur le lien suivant pour réinitialiser votre mot de passe&nbsp;: %ssession/reset-password/%d/%s
Sinon, merci d\'ignorer ce message. Sinon, merci d\'ignorer ce message.
@ -49,9 +49,9 @@ PHPCI',
'n_builds_running' => '%d builds en cours d\'exécution', 'n_builds_running' => '%d builds en cours d\'exécution',
'edit_profile' => 'Éditer le profil', 'edit_profile' => 'Éditer le profil',
'sign_out' => 'Déconnexion', 'sign_out' => 'Déconnexion',
'branch_x' => 'Branche : %s', 'branch_x' => 'Branche&nbsp;: %s',
'created_x' => 'Créé à : %s', 'created_x' => 'Créé à&nbsp;: %s',
'started_x' => 'Démarré à : %s', 'started_x' => 'Démarré à&nbsp;: %s',
// Sidebar // Sidebar
'hello_name' => 'Salut %s', 'hello_name' => 'Salut %s',
@ -67,19 +67,19 @@ PHPCI',
'delete_project' => 'Supprimer le projet', 'delete_project' => 'Supprimer le projet',
// Project Summary: // Project Summary:
'no_builds_yet' => 'Aucun build pour le moment!', 'no_builds_yet' => 'Aucun build pour le moment&nbsp;!',
'x_of_x_failed' => '%d parmis les derniers %d builds ont échoué.', 'x_of_x_failed' => '%d des %d derniers builds ont échoué.',
'x_of_x_failed_short' => '%d / %d ont échoué.', 'x_of_x_failed_short' => '%d échecs / %d.',
'last_successful_build' => ' Le dernier build qui a réussi est %s.', 'last_successful_build' => ' Le dernier build réussi date du %s.',
'never_built_successfully' => ' Aucun build n\'a été exécuté avec succès sur ce projet.', 'never_built_successfully' => ' Aucun build de ce projet n\'a réussi.',
'all_builds_passed' => 'Les derniers %d builds ont réussis.', 'all_builds_passed' => 'Les %d derniers builds ont réussi.',
'all_builds_passed_short' => '%d / %d ont réussis.', 'all_builds_passed_short' => '%d réussites / %d.',
'last_failed_build' => ' Le dernier build en échec est %s.', 'last_failed_build' => ' Le dernier build en échec date du %s.',
'never_failed_build' => ' Ce projet n\'a jamais eu un build en échec.', 'never_failed_build' => ' Aucun build de ce projet n\'a échoué.',
'view_project' => 'Voir le projet', 'view_project' => 'Voir le projet',
// Timeline: // Timeline:
'latest_builds' => 'Les derniers Builds', 'latest_builds' => 'Derniers builds',
'pending' => 'En attente', 'pending' => 'En attente',
'running' => 'En cours', 'running' => 'En cours',
'success' => 'Terminé', 'success' => 'Terminé',
@ -102,8 +102,8 @@ PHPCI',
'local' => 'Chemin local', 'local' => 'Chemin local',
'hg' => 'Mercurial', 'hg' => 'Mercurial',
'where_hosted' => 'Où est hébergé votre projet ?', 'where_hosted' => 'Où est hébergé votre projet&nbsp;?',
'choose_github' => 'Choisissez un dépôt GitHub :', 'choose_github' => 'Choisissez un dépôt GitHub&nbsp;:',
'repo_name' => 'Nom du dépôt / URL (distance) ou chemin (local)', 'repo_name' => 'Nom du dépôt / URL (distance) ou chemin (local)',
'project_title' => 'Titre du projet', 'project_title' => 'Titre du projet',
@ -112,7 +112,7 @@ PHPCI',
'build_config' => 'Configuration PHPCI spécifique pour ce projet 'build_config' => 'Configuration PHPCI spécifique pour ce projet
(si vous ne pouvez pas ajouter de fichier phpci.yml à la racine du dépôt)', (si vous ne pouvez pas ajouter de fichier phpci.yml à la racine du dépôt)',
'default_branch' => 'Nom de la branche par défaut', 'default_branch' => 'Nom de la branche par défaut',
'allow_public_status' => 'Activer la page de statut publique et l\'image pour ce projet ?', 'allow_public_status' => 'Activer la page de statut publique et l\'image pour ce projet&nbsp;?',
'save_project' => 'Enregistrer le projet', 'save_project' => 'Enregistrer le projet',
'error_mercurial' => 'Les URLs de dépôt Mercurial doivent commencer par http:// ou https://', 'error_mercurial' => 'Les URLs de dépôt Mercurial doivent commencer par http:// ou https://',
@ -155,7 +155,7 @@ PHPCI',
'committed_by_x' => 'Committé par %s', 'committed_by_x' => 'Committé par %s',
'commit_id_x' => 'Commit: %s', 'commit_id_x' => 'Commit&nbsp;: %s',
'chart_display' => 'Ce graphique s\'affichera une fois que le build sera terminé.', 'chart_display' => 'Ce graphique s\'affichera une fois que le build sera terminé.',
@ -207,7 +207,7 @@ PHPCI',
'update_your_details' => 'Mettre à jour vos préférences', 'update_your_details' => 'Mettre à jour vos préférences',
'your_details_updated' => 'Vos préférences ont été bien mises à jour.', 'your_details_updated' => 'Vos préférences ont été bien mises à jour.',
'add_user' => 'Ajouter un utilisateur', 'add_user' => 'Ajouter un utilisateur',
'is_admin' => 'Est-il administrateur ?', 'is_admin' => 'Est-il administrateur&nbsp;?',
'yes' => 'Oui', 'yes' => 'Oui',
'no' => 'Non', 'no' => 'Non',
'edit' => 'Éditer', 'edit' => 'Éditer',
@ -287,17 +287,17 @@ PHPCI',
'config_path' => 'Chemin vers le fichier de configuration', 'config_path' => 'Chemin vers le fichier de configuration',
'install_phpci' => 'Installer PHPCI', 'install_phpci' => 'Installer PHPCI',
'welcome_to_phpci' => 'Bienvenue sur PHPCI', 'welcome_to_phpci' => 'Bienvenue sur PHPCI',
'please_answer' => 'Merci de répondre aux questions suivantes:', 'please_answer' => 'Merci de répondre aux questions suivantes :',
'phpci_php_req' => 'PHPCI requiert au moins PHP 5.3.8 pour fonctionner.', 'phpci_php_req' => 'PHPCI requiert au moins PHP 5.3.8 pour fonctionner.',
'extension_required' => 'Extensions requises: %s', 'extension_required' => 'Extensions requises : %s',
'function_required' => 'PHPCI doit être capable d\'appeler la fonction %s(). Est-ce qu\'elle est désactivée dans votre php.ini?', 'function_required' => 'PHPCI doit être capable d\'appeler la fonction %s(). Est-ce qu\'elle est désactivée dans votre php.ini?',
'requirements_not_met' => 'PHPCI ne peut pas être installé parce que toutes les conditions requises ne sont pas respectées. 'requirements_not_met' => 'PHPCI ne peut pas être installé parce que toutes les conditions requises ne sont pas respectées.
Merci de corriger les erreurs ci-dessus avant de continuer.', Merci de corriger les erreurs ci-dessus avant de continuer.',
'must_be_valid_email' => 'Doit être une adresse email valide.', 'must_be_valid_email' => 'Doit être une adresse email valide.',
'must_be_valid_url' => 'Doit être une URL valide.', 'must_be_valid_url' => 'Doit être une URL valide.',
'enter_name' => 'Nom de l\'admin:', 'enter_name' => 'Nom de l\'admin :',
'enter_email' => 'Email de l\'admin:', 'enter_email' => 'Email de l\'admin :',
'enter_password' => 'Mot de passe de l\'admin:', 'enter_password' => 'Mot de passe de l\'admin :',
'enter_phpci_url' => 'Votre URL vers PHPCI (par exemple "http://phpci.local"): ', 'enter_phpci_url' => 'Votre URL vers PHPCI (par exemple "http://phpci.local"): ',
'enter_db_host' => 'Merci d\'entrer le nom d\'hôte MySQL [localhost]: ', 'enter_db_host' => 'Merci d\'entrer le nom d\'hôte MySQL [localhost]: ',
@ -306,7 +306,7 @@ PHPCI',
'enter_db_pass' => 'Merci d\'entrer le mot de passe MySQL: ', 'enter_db_pass' => 'Merci d\'entrer le mot de passe MySQL: ',
'could_not_connect' => 'PHPCI ne peut pas se connecter à MySQL à partir des informations fournies. Veuillez réessayer..', 'could_not_connect' => 'PHPCI ne peut pas se connecter à MySQL à partir des informations fournies. Veuillez réessayer..',
'setting_up_db' => 'Paramétrage de la base de données... ', 'setting_up_db' => 'Paramétrage de la base de données... ',
'user_created' => 'Le compte utilisateur a été créé!', 'user_created' => 'Le compte utilisateur a été créé !',
'failed_to_create' => 'PHPCI n\'a pas réussi à créer votre compte admin.', 'failed_to_create' => 'PHPCI n\'a pas réussi à créer votre compte admin.',
'config_exists' => 'Le fichier de configuration PHPCI existe et n\'est pas vide.', 'config_exists' => 'Le fichier de configuration PHPCI existe et n\'est pas vide.',
'update_instead' => 'Si vous essayez de mettre à jour PHPCI, merci d\'utiliser la commande phpci:update.', 'update_instead' => 'Si vous essayez de mettre à jour PHPCI, merci d\'utiliser la commande phpci:update.',
@ -365,7 +365,7 @@ PHPCI',
'n_emails_sent' => '%d emails envoyés.', 'n_emails_sent' => '%d emails envoyés.',
'n_emails_failed' => '%d emails dont l\'envoi a échoué.', 'n_emails_failed' => '%d emails dont l\'envoi a échoué.',
'unable_to_set_env' => 'Impossible d\'initialiser la variable d\'environnement', 'unable_to_set_env' => 'Impossible d\'initialiser la variable d\'environnement',
'tag_created' => 'Tag créé par PHPCI: %s', 'tag_created' => 'Tag créé par PHPCI : %s',
'x_built_at_x' => '%PROJECT_TITLE% construit à %BUILD_URI%', 'x_built_at_x' => '%PROJECT_TITLE% construit à %BUILD_URI%',
'hipchat_settings' => 'Merci de définir une "room" et un "authToken" pour le plugin hipchat_notify', 'hipchat_settings' => 'Merci de définir une "room" et un "authToken" pour le plugin hipchat_notify',
'irc_settings' => 'Vous devez configurer un serveur, une "room" et un "nick".', 'irc_settings' => 'Vous devez configurer un serveur, une "room" et un "nick".',

View file

@ -84,7 +84,7 @@ PHPCI',
'success' => 'Успешно', 'success' => 'Успешно',
'successful' => 'Успешна', 'successful' => 'Успешна',
'failed' => 'Провалена', 'failed' => 'Провалена',
'manual_build' => 'Ручной сборки', 'manual_build' => 'Запущена вручную',
// Add/Edit Project: // Add/Edit Project:
'new_project' => 'Новый проект', 'new_project' => 'Новый проект',

View file

@ -194,6 +194,10 @@ class Build extends BuildBase
return $rtn; return $rtn;
} }
/**
* Returns the commit message for this build.
* @return string
*/
public function getCommitMessage() public function getCommitMessage()
{ {
$rtn = htmlspecialchars($this->data['commit_message']); $rtn = htmlspecialchars($this->data['commit_message']);

View file

@ -182,7 +182,7 @@ class Email implements \PHPCI\Plugin
$addresses[] = $this->options['default_mailto_address']; $addresses[] = $this->options['default_mailto_address'];
return $addresses; return $addresses;
} }
return $addresses; return array_unique($addresses);
} }
/** /**

View file

@ -83,7 +83,7 @@ class Pgsql implements \PHPCI\Plugin
$pdo = new PDO('pgsql:host=' . $this->host, $this->user, $this->pass, $opts); $pdo = new PDO('pgsql:host=' . $this->host, $this->user, $this->pass, $opts);
foreach ($this->queries as $query) { foreach ($this->queries as $query) {
$pdo->query($query); $pdo->query($this->phpci->interpolate($query));
} }
} catch (\Exception $ex) { } catch (\Exception $ex) {
$this->phpci->logFailure($ex->getMessage()); $this->phpci->logFailure($ex->getMessage());

View file

@ -70,7 +70,7 @@ class Sqlite implements \PHPCI\Plugin
$pdo = new PDO('sqlite:' . $this->path, $opts); $pdo = new PDO('sqlite:' . $this->path, $opts);
foreach ($this->queries as $query) { foreach ($this->queries as $query) {
$pdo->query($query); $pdo->query($this->phpci->interpolate($query));
} }
} catch (\Exception $ex) { } catch (\Exception $ex) {
$this->phpci->logFailure($ex->getMessage()); $this->phpci->logFailure($ex->getMessage());

View file

@ -100,6 +100,7 @@ class BuildService
$build = new Build(); $build = new Build();
$build->setValues($data); $build->setValues($data);
$build->setCreated(new \DateTime()); $build->setCreated(new \DateTime());
$build->setStatus(0);
return $this->buildStore->save($build); return $this->buildStore->save($build);
} }

View file

@ -48,7 +48,7 @@
?> ?>
<li class="time-label"> <li class="time-label">
<span class="bg-gray"> <span class="bg-gray">
<?php print $last->format('M j Y'); ?> <?php print Lang::formatDateTime($last, 'll'); ?>
</span> </span>
</li> </li>
<?php endif; ?> <?php endif; ?>
@ -58,7 +58,7 @@
<li> <li>
<i class="fa fa-<?php print $build->getProject()->getIcon(); ?> bg-<?php print $color; ?>"></i> <i class="fa fa-<?php print $build->getProject()->getIcon(); ?> bg-<?php print $color; ?>"></i>
<div class="timeline-item"> <div class="timeline-item">
<span class="time"><i class="fa fa-clock-o"></i> <?php print $updated->format('H:i'); ?></span> <span class="time"><i class="fa fa-clock-o"></i> <?php print Lang::formatDateTime($updated, 'LT'); ?></span>
<h3 class="timeline-header"> <h3 class="timeline-header">
<a href="<?php print PHPCI_URL; ?>project/view/<?php print $build->getProjectId(); ?>"> <a href="<?php print PHPCI_URL; ?>project/view/<?php print $build->getProjectId(); ?>">
<?php print $build->getProject()->getTitle(); ?> <?php print $build->getProject()->getTitle(); ?>

View file

@ -27,12 +27,12 @@ foreach($projects as $project):
case 2: case 2:
$successes++; $successes++;
$statuses[] = 'ok'; $statuses[] = 'ok';
$success = is_null($success) && !is_null($build->getFinished()) ? $build->getFinished()->format('M j Y g:ia') : $success; $success = is_null($success) && !is_null($build->getFinished()) ? Lang::formatDateTime($build->getFinished()) : $success;
break; break;
case 3: case 3:
$failures++; $failures++;
$statuses[] = 'failed'; $statuses[] = 'failed';
$failure = is_null($failure) && !is_null($build->getFinished()) ? $build->getFinished()->format('M j Y g:ia') : $failure; $failure = is_null($failure) && !is_null($build->getFinished()) ? Lang::formatDateTime($build->getFinished()) : $failure;
break; break;
} }
} }
@ -59,7 +59,7 @@ foreach($projects as $project):
$message = Lang::get('x_of_x_failed', $failures, $buildCount); $message = Lang::get('x_of_x_failed', $failures, $buildCount);
if (!is_null($lastSuccess) && !is_null($lastSuccess->getFinished())) { if (!is_null($lastSuccess) && !is_null($lastSuccess->getFinished())) {
$message .= Lang::get('last_successful_build', $lastSuccess->getFinished()->format('M j Y')); $message .= Lang::get('last_successful_build', Lang::formatDateTime($lastSuccess->getFinished()));
} else { } else {
$message .= Lang::get('never_built_successfully'); $message .= Lang::get('never_built_successfully');
} }
@ -68,7 +68,7 @@ foreach($projects as $project):
$shortMessage = Lang::get('all_builds_passed_short', $buildCount, $buildCount); $shortMessage = Lang::get('all_builds_passed_short', $buildCount, $buildCount);
if (!is_null($lastFailure) && !is_null($lastFailure->getFinished())) { if (!is_null($lastFailure) && !is_null($lastFailure->getFinished())) {
$message .= Lang::get('last_failed_build', $lastFailure->getFinished()->format('M j Y')); $message .= Lang::get('last_failed_build', Lang::formatDateTime($lastFailure->getFinished()));
} else { } else {
$message .= Lang::get('never_failed_build'); $message .= Lang::get('never_failed_build');
} }

View file

@ -16,6 +16,7 @@
<!-- Theme style --> <!-- Theme style -->
<link href="<?php print PHPCI_URL; ?>assets/css/AdminLTE.css" rel="stylesheet" type="text/css" /> <link href="<?php print PHPCI_URL; ?>assets/css/AdminLTE.css" rel="stylesheet" type="text/css" />
<link href="<?php print PHPCI_URL; ?>assets/css/AdminLTE-custom.css" rel="stylesheet" type="text/css" />
<script> <script>
var PHPCI_URL = '<?php print PHPCI_URL; ?>'; var PHPCI_URL = '<?php print PHPCI_URL; ?>';
@ -41,53 +42,7 @@
<![endif]--> <![endif]-->
<style> <style>
.skin-blue .logo, .skin-blue .logo:hover {
background-image: url('/assets/img/logo-large.png');
background-repeat: no-repeat;
background-size: 40%;
background-position: 65px;
text-indent: -5000px;
}
.build-info-panel {
}
.build-info-panel .box-header h1.box-title {
border: 0;
font-size: 1.5em;
font-weight: bold;
margin-left: 110px;
}
.build-info-panel h1.box-title span {
font-weight: normal;
}
.build-info-panel img {
border: 2px solid #fff;
border-radius: 50%;
margin-top: -40px;
}
.build-info-panel #build-info {
margin-left: 110px;
min-height: 50px;
}
.build-info-panel .commit-message {
margin-bottom: 20px;
}
.small-box h3 a, .small-box h4 a {
color: #fff;
}
.pagination>li>span {
font-weight: bold;
background: #337ab7;
color: #fff;
}
</style> </style>
</head> </head>
@ -320,6 +275,7 @@
<script src="<?php print PHPCI_URL; ?>assets/js/plugins/daterangepicker/daterangepicker.js" type="text/javascript"></script> <script src="<?php print PHPCI_URL; ?>assets/js/plugins/daterangepicker/daterangepicker.js" type="text/javascript"></script>
<script src="<?php print PHPCI_URL; ?>assets/js/plugins/datepicker/bootstrap-datepicker.js" type="text/javascript"></script> <script src="<?php print PHPCI_URL; ?>assets/js/plugins/datepicker/bootstrap-datepicker.js" type="text/javascript"></script>
<script src="<?php print PHPCI_URL; ?>assets/js/plugins/datepicker/locales/bootstrap-datepicker.<?php print Lang::getLanguage(); ?>.js" type="text/javascript"></script>
<script src="<?php print PHPCI_URL; ?>assets/js/AdminLTE/app.js" type="text/javascript"></script> <script src="<?php print PHPCI_URL; ?>assets/js/AdminLTE/app.js" type="text/javascript"></script>
</body> </body>

View file

@ -0,0 +1,28 @@
<?php
namespace PHPCI\Tests\Helper;
use DateTime;
use PHPCI\Helper\Lang;
use Prophecy\PhpUnit\ProphecyTestCase;
class LangTest extends ProphecyTestCase
{
public function testLang_UsePassedParameters()
{
$dateTime = $this->prophesize('DateTime');
$dateTime->format(DateTime::ISO8601)->willReturn("ISODATE");
$dateTime->format(DateTime::RFC2822)->willReturn("RFCDATE");
$this->assertEquals('<time datetime="ISODATE" data-format="FORMAT">RFCDATE</time>', Lang::formatDateTime($dateTime->reveal(), 'FORMAT'));
}
public function testLang_UseDefaultFormat()
{
$dateTime = $this->prophesize('DateTime');
$dateTime->format(DateTime::ISO8601)->willReturn("ISODATE");
$dateTime->format(DateTime::RFC2822)->willReturn("RFCDATE");
$this->assertEquals('<time datetime="ISODATE" data-format="lll">RFCDATE</time>', Lang::formatDateTime($dateTime->reveal()));
}
}

View file

@ -57,6 +57,10 @@ class EmailTest extends \PHPUnit_Framework_TestCase
->method('getStatus') ->method('getStatus')
->will($this->returnValue(\PHPCI\Model\Build::STATUS_SUCCESS)); ->will($this->returnValue(\PHPCI\Model\Build::STATUS_SUCCESS));
$this->mockBuild->expects($this->any())
->method('getCommitterEmail')
->will($this->returnValue("committer@test.com"));
$this->mockCiBuilder = $this->getMock( $this->mockCiBuilder = $this->getMock(
'\PHPCI\Builder', '\PHPCI\Builder',
array( array(
@ -151,8 +155,53 @@ class EmailTest extends \PHPUnit_Framework_TestCase
); );
$this->assertEquals($expectedReturn, $returnValue); $this->assertEquals($expectedReturn, $returnValue);
}
/**
* @covers PHPUnit::execute
*/
public function testExecute_UniqueRecipientsFromWithCommitter()
{
$this->loadEmailPluginWithOptions(
array(
'addresses' => array('test-receiver@example.com', 'test-receiver2@example.com')
)
);
$actualMails = [];
$this->catchMailPassedToSend($actualMails);
$returnValue = $this->testedEmailPlugin->execute();
$this->assertTrue($returnValue);
$this->assertCount(2, $actualMails);
$actualTos = array(key($actualMails[0]->getTo()), key($actualMails[1]->getTo()));
$this->assertContains('test-receiver@example.com', $actualTos);
$this->assertContains('test-receiver2@example.com', $actualTos);
}
/**
* @covers PHPUnit::execute
*/
public function testExecute_UniqueRecipientsWithCommiter()
{
$this->loadEmailPluginWithOptions(
array(
'commiter' => true,
'addresses' => array('test-receiver@example.com', 'committer@test.com')
)
);
$actualMails = [];
$this->catchMailPassedToSend($actualMails);
$returnValue = $this->testedEmailPlugin->execute();
$this->assertTrue($returnValue);
$actualTos = array(key($actualMails[0]->getTo()), key($actualMails[1]->getTo()));
$this->assertContains('test-receiver@example.com', $actualTos);
$this->assertContains('committer@test.com', $actualTos);
} }
/** /**
@ -215,12 +264,16 @@ class EmailTest extends \PHPUnit_Framework_TestCase
*/ */
protected function catchMailPassedToSend(&$actualMail) protected function catchMailPassedToSend(&$actualMail)
{ {
$this->mockMailer->expects($this->once()) $this->mockMailer->expects(is_array($actualMail) ? $this->atLeast(1) : $this->once())
->method('send') ->method('send')
->will( ->will(
$this->returnCallback( $this->returnCallback(
function ($passedMail) use (&$actualMail) { function ($passedMail) use (&$actualMail) {
$actualMail = $passedMail; if(is_array($actualMail)) {
$actualMail[] = $passedMail;
} else {
$actualMail = $passedMail;
}
return array(); return array();
} }
) )

View file

@ -56,7 +56,7 @@ class BuildServiceTest extends \PHPUnit_Framework_TestCase
$this->assertNull($returnValue->getStarted()); $this->assertNull($returnValue->getStarted());
$this->assertNull($returnValue->getFinished()); $this->assertNull($returnValue->getFinished());
$this->assertNull($returnValue->getLog()); $this->assertNull($returnValue->getLog());
$this->assertNull($returnValue->getCommitMessage()); $this->assertEmpty($returnValue->getCommitMessage());
$this->assertNull($returnValue->getCommitterEmail()); $this->assertNull($returnValue->getCommitterEmail());
$this->assertNull($returnValue->getExtra()); $this->assertNull($returnValue->getExtra());
$this->assertEquals('master', $returnValue->getBranch()); $this->assertEquals('master', $returnValue->getBranch());

View file

@ -0,0 +1,53 @@
.skin-blue .logo, .skin-blue .logo:hover {
background-image: url('/assets/img/logo-large.png');
background-repeat: no-repeat;
background-size: 40%;
background-position: 65px;
text-indent: -5000px;
}
.build-info-panel {
}
.build-info-panel .box-header h1.box-title {
border: 0;
font-size: 1.5em;
font-weight: bold;
margin-left: 110px;
}
.build-info-panel h1.box-title span {
font-weight: normal;
}
.build-info-panel img {
border: 2px solid #fff;
border-radius: 50%;
margin-top: -40px;
}
.build-info-panel #build-info {
margin-left: 110px;
min-height: 50px;
}
.build-info-panel .commit-message {
margin-bottom: 20px;
}
.small-box h3 a, .small-box h4 a {
color: #fff;
}
.pagination>li>span {
font-weight: bold;
background: #337ab7;
color: #fff;
}
#plugins table td {
max-width: 300px;
overflow: hidden;
text-overflow: ellipsis;
}

View file

@ -18,11 +18,16 @@
margin: 4px; margin: 4px;
} }
.daterangepicker.opensright .ranges, .daterangepicker.opensright .calendar { .daterangepicker.opensright .ranges, .daterangepicker.opensright .calendar,
.daterangepicker.openscenter .ranges, .daterangepicker.openscenter .calendar {
float: right; float: right;
margin: 4px; margin: 4px;
} }
.daterangepicker.single .ranges, .daterangepicker.single .calendar {
float: none;
}
.daterangepicker .ranges { .daterangepicker .ranges {
width: 160px; width: 160px;
text-align: left; text-align: left;
@ -41,6 +46,14 @@
max-width: 270px; max-width: 270px;
} }
.daterangepicker.show-calendar .calendar {
display: block;
}
.daterangepicker .calendar.single .calendar-date {
border: none;
}
.daterangepicker .calendar th, .daterangepicker .calendar td { .daterangepicker .calendar th, .daterangepicker .calendar td {
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
white-space: nowrap; white-space: nowrap;
@ -48,7 +61,8 @@
min-width: 32px; min-width: 32px;
} }
.daterangepicker .ranges label { .daterangepicker .daterangepicker_start_input label,
.daterangepicker .daterangepicker_end_input label {
color: #333; color: #333;
display: block; display: block;
font-size: 11px; font-size: 11px;
@ -66,7 +80,6 @@
} }
.daterangepicker .ranges .input-mini { .daterangepicker .ranges .input-mini {
background-color: #eee;
border: 1px solid #ccc; border: 1px solid #ccc;
border-radius: 4px; border-radius: 4px;
color: #555; color: #555;
@ -153,6 +166,37 @@
content: ''; content: '';
} }
.daterangepicker.openscenter:before {
position: absolute;
top: -7px;
left: 0;
right: 0;
width: 0;
margin-left: auto;
margin-right: auto;
display: inline-block;
border-right: 7px solid transparent;
border-bottom: 7px solid #ccc;
border-left: 7px solid transparent;
border-bottom-color: rgba(0, 0, 0, 0.2);
content: '';
}
.daterangepicker.openscenter:after {
position: absolute;
top: -6px;
left: 0;
right: 0;
width: 0;
margin-left: auto;
margin-right: auto;
display: inline-block;
border-right: 6px solid transparent;
border-bottom: 6px solid #fff;
border-left: 6px solid transparent;
content: '';
}
.daterangepicker.opensright:before { .daterangepicker.opensright:before {
position: absolute; position: absolute;
top: -7px; top: -7px;
@ -196,7 +240,7 @@
color: #999; color: #999;
} }
.daterangepicker td.disabled { .daterangepicker td.disabled, .daterangepicker option.disabled {
color: #999; color: #999;
} }
@ -211,6 +255,24 @@
border-radius: 0; border-radius: 0;
} }
.daterangepicker td.start-date {
-webkit-border-radius: 4px 0 0 4px;
-moz-border-radius: 4px 0 0 4px;
border-radius: 4px 0 0 4px;
}
.daterangepicker td.end-date {
-webkit-border-radius: 0 4px 4px 0;
-moz-border-radius: 0 4px 4px 0;
border-radius: 0 4px 4px 0;
}
.daterangepicker td.start-date.end-date {
-webkit-border-radius: 4px;
-moz-border-radius: 4px;
border-radius: 4px;
}
.daterangepicker td.active, .daterangepicker td.active:hover { .daterangepicker td.active, .daterangepicker td.active:hover {
background-color: #357ebd; background-color: #357ebd;
border-color: #3071a9; border-color: #3071a9;
@ -239,7 +301,20 @@
width: 40%; width: 40%;
} }
.daterangepicker select.hourselect, .daterangepicker select.minuteselect, .daterangepicker select.ampmselect { .daterangepicker select.hourselect, .daterangepicker select.minuteselect, .daterangepicker select.secondselect, .daterangepicker select.ampmselect {
width: 50px; width: 50px;
margin-bottom: 0; margin-bottom: 0;
} }
.daterangepicker_start_input {
float: left;
}
.daterangepicker_end_input {
float: left;
padding-left: 11px
}
.daterangepicker th.month {
width: auto;
}

View file

@ -23,14 +23,14 @@ var phpcpdPlugin = ActiveBuild.UiPlugin.extend({
render: function() { render: function() {
return $('<table class="table" id="phpcpd-data">' + return $('<div class="table-responsive"><table class="table" id="phpcpd-data">' +
'<thead>' + '<thead>' +
'<tr>' + '<tr>' +
' <th>'+Lang.get('file')+'</th>' + ' <th>'+Lang.get('file')+'</th>' +
' <th>'+Lang.get('start')+'</th>' + ' <th>'+Lang.get('start')+'</th>' +
' <th>'+Lang.get('end')+'</th>' + ' <th>'+Lang.get('end')+'</th>' +
'</tr>' + '</tr>' +
'</thead><tbody></tbody></table>'); '</thead><tbody></tbody></table></div>');
}, },

View file

@ -22,14 +22,14 @@ var phpcsPlugin = ActiveBuild.UiPlugin.extend({
}, },
render: function() { render: function() {
return $('<table class="table" id="phpcs-data">' + return $('<div class="table-responsive"><table class="table" id="phpcs-data">' +
'<thead>' + '<thead>' +
'<tr>' + '<tr>' +
' <th>'+Lang.get('file')+'</th>' + ' <th>'+Lang.get('file')+'</th>' +
' <th>'+Lang.get('line')+'</th>' + ' <th>'+Lang.get('line')+'</th>' +
' <th>'+Lang.get('message')+'</th>' + ' <th>'+Lang.get('message')+'</th>' +
'</tr>' + '</tr>' +
'</thead><tbody></tbody></table>'); '</thead><tbody></tbody></table></div>');
}, },
onUpdate: function(e) { onUpdate: function(e) {

View file

@ -24,7 +24,7 @@ var phpdoccheckPlugin = ActiveBuild.UiPlugin.extend({
}, },
render: function() { render: function() {
return $('<table class="table" id="phpdoccheck-data">' + return $('<div class="table-responsive"><table class="table" id="phpdoccheck-data">' +
'<thead>' + '<thead>' +
'<tr>' + '<tr>' +
' <th>'+Lang.get('file')+'</th>' + ' <th>'+Lang.get('file')+'</th>' +
@ -32,7 +32,7 @@ var phpdoccheckPlugin = ActiveBuild.UiPlugin.extend({
' <th>'+Lang.get('class')+'</th>' + ' <th>'+Lang.get('class')+'</th>' +
' <th>'+Lang.get('method')+'</th>' + ' <th>'+Lang.get('method')+'</th>' +
'</tr>' + '</tr>' +
'</thead><tbody></tbody></table>'); '</thead><tbody></tbody></table></div>');
}, },
onUpdate: function(e) { onUpdate: function(e) {

View file

@ -25,7 +25,7 @@ var phpmdPlugin = ActiveBuild.UiPlugin.extend({
render: function() { render: function() {
return $('<table class="table" id="phpmd-data">' + return $('<div class="table-responsive"><table class="table" id="phpmd-data">' +
'<thead>' + '<thead>' +
'<tr>' + '<tr>' +
' <th>'+Lang.get('file')+'</th>' + ' <th>'+Lang.get('file')+'</th>' +
@ -33,7 +33,7 @@ var phpmdPlugin = ActiveBuild.UiPlugin.extend({
' <th>'+Lang.get('end')+'</th>' + ' <th>'+Lang.get('end')+'</th>' +
' <th>'+Lang.get('message')+'</th>' + ' <th>'+Lang.get('message')+'</th>' +
'</tr>' + '</tr>' +
'</thead><tbody></tbody></table>'); '</thead><tbody></tbody></table></div>');
}, },
onUpdate: function(e) { onUpdate: function(e) {

View file

@ -22,14 +22,14 @@ var phptalPlugin = ActiveBuild.UiPlugin.extend({
}, },
render: function() { render: function() {
return $('<table class="table" id="phptal-data">' + return $('<div class="table-responsive"><table class="table" id="phptal-data">' +
'<thead>' + '<thead>' +
'<tr>' + '<tr>' +
' <th>File</th>' + ' <th>File</th>' +
' <th>Line</th>' + ' <th>Line</th>' +
' <th>Message</th>' + ' <th>Message</th>' +
'</tr>' + '</tr>' +
'</thead><tbody></tbody></table>'); '</thead><tbody></tbody></table></div>');
}, },
onUpdate: function(e) { onUpdate: function(e) {

View file

@ -25,12 +25,12 @@ var phpunitPlugin = ActiveBuild.UiPlugin.extend({
render: function() { render: function() {
return $('<table class="table" id="phpunit-data">' + return $('<div class="table-responsive"><table class="table" id="phpunit-data">' +
'<thead>' + '<thead>' +
'<tr>' + '<tr>' +
' <th>'+Lang.get('test')+'</th>' + ' <th>'+Lang.get('test')+'</th>' +
'</tr>' + '</tr>' +
'</thead><tbody></tbody></table>'); '</thead><tbody></tbody></table></div>');
}, },
onUpdate: function(e) { onUpdate: function(e) {

View file

@ -3,7 +3,16 @@ var PHPCI = {
intervals: {}, intervals: {},
init: function () { init: function () {
// Setup the date locale
moment.locale(PHPCI_LANGUAGE);
$(document).ready(function () { $(document).ready(function () {
// Format datetimes
$('time[datetime]').each(function() {
var $this = $(this);
$this.text(moment(this.dateTime).format($this.data('format') || 'lll'));
});
// Update latest builds every 5 seconds: // Update latest builds every 5 seconds:
PHPCI.getBuilds(); PHPCI.getBuilds();
PHPCI.intervals.getBuilds = setInterval(PHPCI.getBuilds, 5000); PHPCI.intervals.getBuilds = setInterval(PHPCI.getBuilds, 5000);

File diff suppressed because one or more lines are too long