From b33189e08ebbb04f955ac4cc632f86b4b5b9face Mon Sep 17 00:00:00 2001 From: Dan Cryer Date: Tue, 8 Oct 2013 07:21:46 +0100 Subject: [PATCH] UI plugins, including quality trend chart, logs and lines of code. Some UI tweaks. --- .gitignore | 1 - PHPCI/Builder.php | 36 +- PHPCI/Controller/BuildController.php | 35 + PHPCI/Model/Base/BuildBase.php | 12 + PHPCI/Model/Base/BuildMetaBase.php | 293 ++++ PHPCI/Model/Base/ProjectBase.php | 78 +- PHPCI/Model/BuildMeta.php | 20 + PHPCI/Plugin/PhpCodeSniffer.php | 16 +- PHPCI/Plugin/PhpCpd.php | 6 +- PHPCI/Plugin/PhpLoc.php | 14 +- PHPCI/Plugin/PhpMessDetector.php | 6 +- PHPCI/Store/Base/BuildMetaStoreBase.php | 86 + PHPCI/Store/BuildMetaStore.php | 20 + PHPCI/Store/BuildStore.php | 48 + PHPCI/View/Build/view.phtml | 71 +- PHPCI/View/Home/index.phtml | 8 +- PHPCI/View/ProjectForm.phtml | 2 + PHPCI/View/UserForm.phtml | 2 + PHPCI/View/layout.phtml | 11 +- composer.lock | 1663 ++++++++++++++++++++ public/assets/css/phpci.css | 45 +- public/assets/js/build-plugins/loc.js | 65 + public/assets/js/build-plugins/log.js | 23 + public/assets/js/build-plugins/time.js | 37 + public/assets/js/build-plugins/warnings.js | 98 ++ public/assets/js/class.js | 64 + public/assets/js/jqueryui.js | 6 + public/assets/js/phpci.js | 223 +-- 28 files changed, 2775 insertions(+), 214 deletions(-) create mode 100644 PHPCI/Model/Base/BuildMetaBase.php create mode 100644 PHPCI/Model/BuildMeta.php create mode 100644 PHPCI/Store/Base/BuildMetaStoreBase.php create mode 100644 PHPCI/Store/BuildMetaStore.php create mode 100644 composer.lock create mode 100644 public/assets/js/build-plugins/loc.js create mode 100644 public/assets/js/build-plugins/log.js create mode 100644 public/assets/js/build-plugins/time.js create mode 100644 public/assets/js/build-plugins/warnings.js create mode 100644 public/assets/js/class.js create mode 100755 public/assets/js/jqueryui.js diff --git a/.gitignore b/.gitignore index d600338f..3904ba6d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,5 @@ .idea vendor/ -composer.lock composer.phar config.php .DS_Store diff --git a/PHPCI/Builder.php b/PHPCI/Builder.php index b8979452..f62fd758 100644 --- a/PHPCI/Builder.php +++ b/PHPCI/Builder.php @@ -73,7 +73,12 @@ class Builder * @var array */ protected $config; - + + /** + * @var string + */ + protected $lastOutput; + /** * An array of key => value pairs that will be used for * interpolation and environment variables @@ -83,6 +88,11 @@ class Builder */ protected $interpolation_vars = array(); + /** + * @var \PHPCI\Store\BuildStore + */ + protected $store; + /** * Set up the builder. * @param \PHPCI\Model\Build @@ -214,17 +224,24 @@ class Builder $this->log('Executing: ' . $command, ' '); - $output = ''; $status = 0; - exec($command, $output, $status); + exec($command, $this->lastOutput, $status); - if (!empty($output) && ($this->verbose || $status != 0)) { - $this->log($output, ' '); + if (!empty($this->lastOutput) && ($this->verbose || $status != 0)) { + $this->log($this->lastOutput, ' '); } return ($status == 0) ? true : false; } + /** + * Returns the output from the last command run. + */ + public function getLastOutput() + { + return implode(PHP_EOL, $this->lastOutput); + } + /** * Add an entry to the build log. * @param string|string[] @@ -433,4 +450,13 @@ class Builder $this->log('Removing build.'); shell_exec(sprintf('rm -Rf "%s"', $this->buildPath)); } + + /** + * Store build meta data + */ + public function storeBuildMeta($key, $value) + { + $value = json_encode($value); + $this->store->setMeta($this->build->getProjectId(), $this->build->getId(), $key, $value); + } } diff --git a/PHPCI/Controller/BuildController.php b/PHPCI/Controller/BuildController.php index b907b9bc..0dc927b7 100644 --- a/PHPCI/Controller/BuildController.php +++ b/PHPCI/Controller/BuildController.php @@ -31,10 +31,28 @@ class BuildController extends \PHPCI\Controller public function view($buildId) { $build = $this->_buildStore->getById($buildId); + $this->view->plugins = $this->getUiPlugins(); $this->view->build = $build; $this->view->data = $this->getBuildData($buildId); } + protected function getUiPlugins() + { + $rtn = array(); + $path = APPLICATION_PATH . 'public/assets/js/build-plugins/'; + $dir = opendir($path); + + while ($item = readdir($dir)) { + if (substr($item, 0, 1) == '.' || substr($item, -3) != '.js') { + continue; + } + + $rtn[] = $item; + } + + return $rtn; + } + /** * AJAX call to get build data: */ @@ -43,6 +61,23 @@ class BuildController extends \PHPCI\Controller die($this->getBuildData($buildId)); } + /** + * AJAX call to get build meta: + */ + public function meta($buildId) + { + $build = $this->_buildStore->getById($buildId); + $key = $this->getParam('key', null); + $numBuilds = $this->getParam('num_builds', 1); + $data = null; + + if ($key && $build) { + $data = $this->_buildStore->getMeta($key, $build->getProjectId(), $buildId, $numBuilds); + } + + die(json_encode($data)); + } + /** * Get build data from database and json encode it: */ diff --git a/PHPCI/Model/Base/BuildBase.php b/PHPCI/Model/Base/BuildBase.php index b681c110..33f3d406 100644 --- a/PHPCI/Model/Base/BuildBase.php +++ b/PHPCI/Model/Base/BuildBase.php @@ -579,4 +579,16 @@ class BuildBase extends Model { return $this->setProjectId($value->getId()); } + + /** + * Get BuildMeta models by BuildId for this Build. + * + * @uses \PHPCI\Store\BuildMetaStore::getByBuildId() + * @uses \PHPCI\Model\BuildMeta + * @return \PHPCI\Model\BuildMeta[] + */ + public function getBuildBuildMetas() + { + return \b8\Store\Factory::getStore('BuildMeta')->getByBuildId($this->getId()); + } } diff --git a/PHPCI/Model/Base/BuildMetaBase.php b/PHPCI/Model/Base/BuildMetaBase.php new file mode 100644 index 00000000..f9aa94e5 --- /dev/null +++ b/PHPCI/Model/Base/BuildMetaBase.php @@ -0,0 +1,293 @@ + null, + 'build_id' => null, + 'key' => null, + 'value' => null, + ); + + /** + * @var array + */ + protected $getters = array( + 'id' => 'getId', + 'build_id' => 'getBuildId', + 'key' => 'getKey', + 'value' => 'getValue', + 'Build' => 'getBuild', + ); + + /** + * @var array + */ + protected $setters = array( + 'id' => 'setId', + 'build_id' => 'setBuildId', + 'key' => 'setKey', + 'value' => 'setValue', + 'Build' => 'setBuild', + ); + + /** + * @var array + */ + public $columns = array( + 'id' => array( + 'type' => 'int', + 'length' => '10', + 'primary_key' => true, + 'auto_increment' => true, + ), + 'build_id' => array( + 'type' => 'int', + 'length' => '11', + ), + 'key' => array( + 'type' => 'varchar', + 'length' => '255', + ), + 'value' => array( + 'type' => 'text', + 'length' => '', + 'nullable' => true, + ), + ); + + /** + * @var array + */ + public $indexes = array( + 'PRIMARY' => array('unique' => true, 'columns' => 'id'), + 'idx_meta_id' => array('unique' => true, 'columns' => 'build_id, key'), + ); + + /** + * @var array + */ + public $foreignKeys = array( + 'fk_meta_build_id' => array( + 'local_col' => 'build_id', + 'update' => 'CASCADE', + 'delete' => 'CASCADE', + 'table' => 'build', + 'col' => 'id' + ), + ); + + + /** + * Get the value of Id / id. + * + * @return int + */ + public function getId() + { + $rtn = $this->data['id']; + + + return $rtn; + } + + /** + * Get the value of BuildId / build_id. + * + * @return int + */ + public function getBuildId() + { + $rtn = $this->data['build_id']; + + + return $rtn; + } + + /** + * Get the value of Key / key. + * + * @return string + */ + public function getKey() + { + $rtn = $this->data['key']; + + + return $rtn; + } + + /** + * Get the value of Value / value. + * + * @return string + */ + public function getValue() + { + $rtn = $this->data['value']; + + + return $rtn; + } + + /** + * Set the value of Id / id. + * + * Must not be null. + * @param $value int + */ + public function setId($value) + { + $this->_validateNotNull('Id', $value); + $this->_validateInt('Id', $value); + if ($this->data['id'] == $value) { + return; + } + + $this->data['id'] = $value; + + $this->_setModified('id'); + } + + /** + * Set the value of BuildId / build_id. + * + * Must not be null. + * @param $value int + */ + public function setBuildId($value) + { + $this->_validateNotNull('BuildId', $value); + $this->_validateInt('BuildId', $value); + if ($this->data['build_id'] == $value) { + return; + } + + $this->data['build_id'] = $value; + + $this->_setModified('build_id'); + } + + /** + * Set the value of Key / key. + * + * Must not be null. + * @param $value string + */ + public function setKey($value) + { + $this->_validateNotNull('Key', $value); + $this->_validateString('Key', $value); + if ($this->data['key'] == $value) { + return; + } + + $this->data['key'] = $value; + + $this->_setModified('key'); + } + + /** + * Set the value of Value / value. + * + * @param $value string + */ + public function setValue($value) + { + + $this->_validateString('Value', $value); + if ($this->data['value'] == $value) { + return; + } + + $this->data['value'] = $value; + + $this->_setModified('value'); + } + + /** + * Get the Build model for this BuildMeta by Id. + * + * @uses \PHPCI\Store\BuildStore::getById() + * @uses \PHPCI\Model\Build + * @return \PHPCI\Model\Build + */ + public function getBuild() + { + $key = $this->getBuildId(); + + if (empty($key)) { + return null; + } + + $cacheKey = 'Cache.Build.' . $key; + $rtn = $this->cache->get($cacheKey, null); + + if (empty($rtn)) { + $rtn = \b8\Store\Factory::getStore('Build')->getById($key); + $this->cache->set($cacheKey, $rtn); + } + + return $rtn; + } + + /** + * Set Build - Accepts an ID, an array representing a Build or a Build model. + * + * @param $value mixed + */ + public function setBuild($value) + { + // Is this an instance of Build? + if ($value instanceof \PHPCI\Model\Build) { + return $this->setBuildObject($value); + } + + // Is this an array representing a Build item? + if (is_array($value) && !empty($value['id'])) { + return $this->setBuildId($value['id']); + } + + // Is this a scalar value representing the ID of this foreign key? + return $this->setBuildId($value); + } + + /** + * Set Build - Accepts a Build model. + * + * @param $value \PHPCI\Model\Build + */ + public function setBuildObject(\PHPCI\Model\Build $value) + { + return $this->setBuildId($value->getId()); + } +} diff --git a/PHPCI/Model/Base/ProjectBase.php b/PHPCI/Model/Base/ProjectBase.php index 4e8b1e38..d00916bb 100644 --- a/PHPCI/Model/Base/ProjectBase.php +++ b/PHPCI/Model/Base/ProjectBase.php @@ -34,11 +34,11 @@ class ProjectBase extends Model protected $data = array( 'id' => null, 'title' => null, - 'access_information' => null, 'reference' => null, 'git_key' => null, 'type' => null, 'token' => null, + 'access_information' => null, ); /** @@ -50,8 +50,8 @@ class ProjectBase extends Model 'reference' => 'getReference', 'git_key' => 'getGitKey', 'type' => 'getType', - 'access_information' => 'getAccessInformation', 'token' => 'getToken', + 'access_information' => 'getAccessInformation', ); /** @@ -63,8 +63,8 @@ class ProjectBase extends Model 'reference' => 'setReference', 'git_key' => 'setGitKey', 'type' => 'setType', - 'access_information' => 'setAccessInformation', 'token' => 'setToken', + 'access_information' => 'setAccessInformation', ); /** @@ -85,10 +85,6 @@ class ProjectBase extends Model 'type' => 'varchar', 'length' => '250', ), - 'access_information' => array( - 'type' => 'varchar', - 'length' => '250', - ), 'git_key' => array( 'type' => 'text', 'length' => '', @@ -102,6 +98,10 @@ class ProjectBase extends Model 'length' => '50', 'nullable' => true, ), + 'access_information' => array( + 'type' => 'varchar', + 'length' => '250', + ), ); /** @@ -157,19 +157,6 @@ class ProjectBase extends Model return $rtn; } - /** - * Get the value of Domain / domain. - * - * @return string - */ - public function getAccessInformation() - { - $rtn = unserialize($this->data['access_information']); - - - return $rtn; - } - /** * Get the value of GitKey / git_key. * @@ -209,6 +196,19 @@ class ProjectBase extends Model return $rtn; } + /** + * Get the value of AccessInformation / access_information. + * + * @return string + */ + public function getAccessInformation() + { + $rtn = $this->data['access_information']; + + + return $rtn; + } + /** * Set the value of Id / id. * @@ -266,25 +266,6 @@ class ProjectBase extends Model $this->_setModified('reference'); } - /** - * Set the value of Domain / domain. - * - * Must not be null. - * @param $value string - */ - public function setAccessInformation($value) - { - $this->_validateNotNull('AccessInformation', $value); - $this->_validateString('AccessInformation', $value); - if ($this->data['access_information'] == $value) { - return; - } - - $this->data['access_information'] = $value; - - $this->_setModified('access_information'); - } - /** * Set the value of GitKey / git_key. * @@ -341,6 +322,25 @@ class ProjectBase extends Model $this->_setModified('token'); } + /** + * Set the value of AccessInformation / access_information. + * + * Must not be null. + * @param $value string + */ + public function setAccessInformation($value) + { + $this->_validateNotNull('AccessInformation', $value); + $this->_validateString('AccessInformation', $value); + if ($this->data['access_information'] == $value) { + return; + } + + $this->data['access_information'] = $value; + + $this->_setModified('access_information'); + } + /** * Get Build models by ProjectId for this Project. * diff --git a/PHPCI/Model/BuildMeta.php b/PHPCI/Model/BuildMeta.php new file mode 100644 index 00000000..afe50fb4 --- /dev/null +++ b/PHPCI/Model/BuildMeta.php @@ -0,0 +1,20 @@ +phpci->executeCommand($cmd, $standard, $suffixes, $ignore, $tab_width, $encoding, $this->phpci->buildPath . $this->path); + $success = $this->phpci->executeCommand($cmd, $standard, $suffixes, $ignore, $tab_width, $encoding, $this->phpci->buildPath . $this->path); + + $output = $this->phpci->getLastOutput(); + + $matches = array(); + if (preg_match_all('/WARNING/', $output, $matches)) { + $this->phpci->storeBuildMeta('phpcs-warnings', count($matches[0])); + } + + $matches = array(); + if (preg_match_all('/ERROR/', $output, $matches)) { + $this->phpci->storeBuildMeta('phpcs-errors', count($matches[0])); + } + + return $success; } } diff --git a/PHPCI/Plugin/PhpCpd.php b/PHPCI/Plugin/PhpCpd.php index ecb54077..5a91dff4 100755 --- a/PHPCI/Plugin/PhpCpd.php +++ b/PHPCI/Plugin/PhpCpd.php @@ -57,6 +57,10 @@ class PhpCpd implements \PHPCI\Plugin $ignore = implode('', $ignore); } - return $this->phpci->executeCommand(PHPCI_BIN_DIR . 'phpcpd %s "%s"', $ignore, $this->phpci->buildPath.$this->path); + $success = $this->phpci->executeCommand(PHPCI_BIN_DIR . 'phpcpd %s "%s"', $ignore, $this->phpci->buildPath.$this->path); + + print $this->phpci->getLastOutput(); + + return $success; } } diff --git a/PHPCI/Plugin/PhpLoc.php b/PHPCI/Plugin/PhpLoc.php index 0ae62200..51ba7108 100644 --- a/PHPCI/Plugin/PhpLoc.php +++ b/PHPCI/Plugin/PhpLoc.php @@ -47,6 +47,18 @@ class PhpLoc implements \PHPCI\Plugin $ignore = implode('', $ignore); } - return $this->phpci->executeCommand(PHPCI_BIN_DIR . 'phploc %s "%s"', $ignore, $this->phpci->buildPath); + $success = $this->phpci->executeCommand(PHPCI_BIN_DIR . 'phploc %s "%s"', $ignore, $this->phpci->buildPath); + $output = $this->phpci->getLastOutput(); + + if (preg_match_all('/\((LOC|CLOC|NCLOC|LLOC)\)\s+([0-9]+)/', $output, $matches)) { + $data = array(); + foreach ($matches[1] as $k => $v) { + $data[$v] = (int)$matches[2][$k]; + } + + $this->phpci->storeBuildMeta('phploc', $data); + } + + return $success; } } \ No newline at end of file diff --git a/PHPCI/Plugin/PhpMessDetector.php b/PHPCI/Plugin/PhpMessDetector.php index 40ca3fcf..c395c3dd 100755 --- a/PHPCI/Plugin/PhpMessDetector.php +++ b/PHPCI/Plugin/PhpMessDetector.php @@ -83,6 +83,10 @@ class PhpMessDetector implements \PHPCI\Plugin } $cmd = PHPCI_BIN_DIR . 'phpmd "%s" text %s %s %s'; - return $this->phpci->executeCommand($cmd, $this->phpci->buildPath . $this->path, implode(',', $this->rules), $ignore, $suffixes); + $success = $this->phpci->executeCommand($cmd, $this->phpci->buildPath . $this->path, implode(',', $this->rules), $ignore, $suffixes); + $errors = count(array_filter(explode(PHP_EOL, $this->phpci->getLastOutput()))); + $this->phpci->storeBuildMeta('phpmd-warnings', $errors); + + return $success; } } diff --git a/PHPCI/Store/Base/BuildMetaStoreBase.php b/PHPCI/Store/Base/BuildMetaStoreBase.php new file mode 100644 index 00000000..b96b3875 --- /dev/null +++ b/PHPCI/Store/Base/BuildMetaStoreBase.php @@ -0,0 +1,86 @@ +getById($value, $useConnection); + } + + + + public function getById($value, $useConnection = 'read') + { + if (is_null($value)) { + throw new \b8\Exception\HttpException('Value passed to ' . __FUNCTION__ . ' cannot be null.'); + } + + $query = 'SELECT * FROM build_meta WHERE id = :id LIMIT 1'; + $stmt = \b8\Database::getConnection($useConnection)->prepare($query); + $stmt->bindValue(':id', $value); + + if ($stmt->execute()) { + if ($data = $stmt->fetch(\PDO::FETCH_ASSOC)) { + return new \PHPCI\Model\BuildMeta($data); + } + } + + return null; + } + + public function getByBuildId($value, $limit = null, $useConnection = 'read') + { + if (is_null($value)) { + throw new \b8\Exception\HttpException('Value passed to ' . __FUNCTION__ . ' cannot be null.'); + } + + $add = ''; + + if ($limit) { + $add .= ' LIMIT ' . $limit; + } + + $query = 'SELECT COUNT(*) AS cnt FROM build_meta WHERE build_id = :build_id' . $add; + $stmt = \b8\Database::getConnection($useConnection)->prepare($query); + $stmt->bindValue(':build_id', $value); + + if ($stmt->execute()) { + $res = $stmt->fetch(\PDO::FETCH_ASSOC); + $count = (int)$res['cnt']; + } else { + $count = 0; + } + + $query = 'SELECT * FROM build_meta WHERE build_id = :build_id' . $add; + $stmt = \b8\Database::getConnection('read')->prepare($query); + $stmt->bindValue(':build_id', $value); + + if ($stmt->execute()) { + $res = $stmt->fetchAll(\PDO::FETCH_ASSOC); + + $map = function ($item) { + return new \PHPCI\Model\BuildMeta($item); + }; + $rtn = array_map($map, $res); + + return array('items' => $rtn, 'count' => $count); + } else { + return array('items' => array(), 'count' => 0); + } + } +} diff --git a/PHPCI/Store/BuildMetaStore.php b/PHPCI/Store/BuildMetaStore.php new file mode 100644 index 00000000..7450ac2a --- /dev/null +++ b/PHPCI/Store/BuildMetaStore.php @@ -0,0 +1,20 @@ + array(), 'count' => 0); } } + + public function getMeta($key, $projectId, $buildId = null, $numResults = 1) + { + $and = $numResults > 1 ? ' AND (`build_id` <= :buildId) ' : ' AND (`build_id` = :buildId) '; + $query = 'SELECT `build_id`, `key`, `value` FROM `build_meta` WHERE `key` = :key AND `project_id` = :projectId ' . $and . ' ORDER BY id DESC LIMIT :numResults'; + + $stmt = \b8\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); + $stmt->bindValue(':numResults', (int)$numResults, \PDO::PARAM_INT); + + if ($stmt->execute()) { + $rtn = $stmt->fetchAll(\PDO::FETCH_ASSOC); + + $rtn = array_reverse($rtn); + $rtn = array_map(function($item) { + $item['value'] = json_decode($item['value'], true); + return $item; + }, $rtn); + + if (!count($rtn)) { + return null; + } else { + return $rtn; + } + + } else { + return null; + } + } + + public function setMeta($projectId, $buildId, $key, $value) + { + $query = 'REPLACE INTO build_meta (project_id, build_id, `key`, `value`) VALUES (:projectId, :buildId, :key, :value)'; + + $stmt = \b8\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); + $stmt->bindValue(':value', $value, \PDO::PARAM_STR); + + if ($stmt->execute()) { + return true; + } else { + return false; + } + } } diff --git a/PHPCI/View/Build/view.phtml b/PHPCI/View/Build/view.phtml index 25ea8809..a9cd569a 100644 --- a/PHPCI/View/Build/view.phtml +++ b/PHPCI/View/Build/view.phtml @@ -16,68 +16,27 @@
  • Delete Build
  • - - - - - - - - - -
    Plugin Status
    +
    - - - - - - - - - - - - - - - - -
    Build CreatedBuild StartedBuild Finished
    - - - - - - - - - - - - -
    Build Log
    -
    +
    + - getStatus() == 0 || $build->getStatus() == 1 || true): ?> - setInterval(function() - { - $.getJSON('build/data/getId(); ?>', updateBuildView); - }, 10000); - +'; +} +?> - $(function() { - updateBuildView(window.initial); - - $('#delete-build').on('click', function (e) { - e.preventDefault(); - confirmDelete("build/delete/getId(); ?>"); - }); - }); + \ No newline at end of file diff --git a/PHPCI/View/Home/index.phtml b/PHPCI/View/Home/index.phtml index 5c645028..a7024590 100644 --- a/PHPCI/View/Home/index.phtml +++ b/PHPCI/View/Home/index.phtml @@ -41,7 +41,8 @@
    -

    Project Overview

    +
    +

    Project Overview

    @@ -57,8 +58,10 @@
    +
    -

    Last 5 Builds

    +
    +

    Last 5 Builds

    @@ -74,6 +77,7 @@
    +
    diff --git a/PHPCI/View/ProjectForm.phtml b/PHPCI/View/ProjectForm.phtml index d59dc8c3..26f4727e 100644 --- a/PHPCI/View/ProjectForm.phtml +++ b/PHPCI/View/ProjectForm.phtml @@ -17,7 +17,9 @@
    +
    +
    diff --git a/PHPCI/View/UserForm.phtml b/PHPCI/View/UserForm.phtml index ea1ae663..40ddcc19 100644 --- a/PHPCI/View/UserForm.phtml +++ b/PHPCI/View/UserForm.phtml @@ -13,6 +13,8 @@
    +
    +
    \ No newline at end of file diff --git a/PHPCI/View/layout.phtml b/PHPCI/View/layout.phtml index a8ed5f79..a6fbfb36 100644 --- a/PHPCI/View/layout.phtml +++ b/PHPCI/View/layout.phtml @@ -9,9 +9,16 @@ + + + - - + + + + + +