Refactored structure
This commit is contained in:
parent
963225382c
commit
e5164ae1dd
329 changed files with 277 additions and 457 deletions
145
src/B8Framework/Application.php
Executable file
145
src/B8Framework/Application.php
Executable file
|
|
@ -0,0 +1,145 @@
|
|||
<?php
|
||||
|
||||
namespace b8;
|
||||
|
||||
use b8\Config;
|
||||
use b8\Exception\HttpException\NotFoundException;
|
||||
use b8\Http;
|
||||
use b8\View;
|
||||
use b8\Controller;
|
||||
use b8\Http\Response;
|
||||
use b8\Http\Request;
|
||||
|
||||
class Application
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $route;
|
||||
|
||||
/**
|
||||
* @var Controller
|
||||
*/
|
||||
protected $controller;
|
||||
|
||||
/**
|
||||
* @var Request
|
||||
*/
|
||||
protected $request;
|
||||
|
||||
/**
|
||||
* @var Response
|
||||
*/
|
||||
protected $response;
|
||||
|
||||
/**
|
||||
* @var Config
|
||||
*/
|
||||
protected $config;
|
||||
|
||||
public function __construct(Config $config, Http\Request $request = null)
|
||||
{
|
||||
$this->config = $config;
|
||||
$this->response = new Http\Response();
|
||||
|
||||
if (!is_null($request)) {
|
||||
$this->request = $request;
|
||||
} else {
|
||||
$this->request = new Http\Request();
|
||||
}
|
||||
|
||||
$this->router = new Http\Router($this, $this->request, $this->config);
|
||||
|
||||
if (method_exists($this, 'init')) {
|
||||
$this->init();
|
||||
}
|
||||
}
|
||||
|
||||
public function handleRequest()
|
||||
{
|
||||
$this->route = $this->router->dispatch();
|
||||
|
||||
if (!empty($this->route['callback'])) {
|
||||
$callback = $this->route['callback'];
|
||||
|
||||
if (!$callback($this->route, $this->response)) {
|
||||
return $this->response;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$this->controllerExists($this->route)) {
|
||||
throw new NotFoundException('Controller ' . $this->toPhpName($this->route['controller']) . ' does not exist!');
|
||||
}
|
||||
|
||||
$action = lcfirst($this->toPhpName($this->route['action']));
|
||||
if (!$this->getController()->hasAction($action)) {
|
||||
throw new NotFoundException('Controller ' . $this->toPhpName($this->route['controller']) . ' does not have action ' . $action . '!');
|
||||
}
|
||||
|
||||
return $this->getController()->handleAction($action, $this->route['args']);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Controller
|
||||
*/
|
||||
public function getController()
|
||||
{
|
||||
if (empty($this->controller)) {
|
||||
$controllerClass = $this->getControllerClass($this->route);
|
||||
$this->controller = $this->loadController($controllerClass);
|
||||
}
|
||||
return $this->controller;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $class
|
||||
*
|
||||
* @return Controller
|
||||
*/
|
||||
protected function loadController($class)
|
||||
{
|
||||
$controller = new $class($this->config, $this->request, $this->response);
|
||||
$controller->init();
|
||||
return $controller;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $route
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function controllerExists($route)
|
||||
{
|
||||
return class_exists($this->getControllerClass($route));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $route
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getControllerClass($route)
|
||||
{
|
||||
$namespace = $this->toPhpName($route['namespace']);
|
||||
$controller = $this->toPhpName($route['controller']);
|
||||
return $this->config->get('b8.app.namespace') . '\\' . $namespace . '\\' . $controller . 'Controller';
|
||||
}
|
||||
|
||||
public function isValidRoute($route)
|
||||
{
|
||||
if ($this->controllerExists($route)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function toPhpName($string)
|
||||
{
|
||||
$string = str_replace('-', ' ', $string);
|
||||
$string = ucwords($string);
|
||||
$string = str_replace(' ', '', $string);
|
||||
|
||||
return $string;
|
||||
}
|
||||
}
|
||||
29
src/B8Framework/Cache.php
Executable file
29
src/B8Framework/Cache.php
Executable file
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
namespace b8;
|
||||
|
||||
/**
|
||||
* @package b8
|
||||
* @subpackage Cache
|
||||
*/
|
||||
|
||||
class Cache
|
||||
{
|
||||
const TYPE_APC = 'ApcCache';
|
||||
const TYPE_REQUEST = 'RequestCache';
|
||||
|
||||
protected static $instance = array();
|
||||
|
||||
/**
|
||||
* Get a cache object of a specified type.
|
||||
*/
|
||||
public static function getCache($type = self::TYPE_REQUEST)
|
||||
{
|
||||
if (!isset(self::$instance[$type])) {
|
||||
$class = '\\b8\\Cache\\' . $type;
|
||||
self::$instance[$type] = new $class();
|
||||
}
|
||||
|
||||
return self::$instance[$type];
|
||||
}
|
||||
}
|
||||
149
src/B8Framework/Cache/ApcCache.php
Executable file
149
src/B8Framework/Cache/ApcCache.php
Executable file
|
|
@ -0,0 +1,149 @@
|
|||
<?php
|
||||
|
||||
namespace b8\Cache;
|
||||
|
||||
use b8\Type;
|
||||
|
||||
class ApcCache implements Type\Cache
|
||||
{
|
||||
/**
|
||||
* Check if caching is enabled.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function isEnabled()
|
||||
{
|
||||
$rtn = false;
|
||||
$apcCli = ini_get('apc.enable_cli');
|
||||
|
||||
if (function_exists('apc_fetch') && (php_sapi_name() != 'cli' || in_array($apcCli, array('1', 1, true, 'On')))) {
|
||||
$rtn = true;
|
||||
}
|
||||
|
||||
return $rtn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get item from the cache:
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $default
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function get($key, $default = null)
|
||||
{
|
||||
if (!$this->isEnabled()) {
|
||||
return $default;
|
||||
}
|
||||
|
||||
$success = false;
|
||||
$rtn = apc_fetch($key, $success);
|
||||
if (!$success) {
|
||||
$rtn = $default;
|
||||
}
|
||||
|
||||
return $rtn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an item to the cache:
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
* @param integer $ttl
|
||||
*
|
||||
* @return array|bool
|
||||
*/
|
||||
public function set($key, $value = null, $ttl = 0)
|
||||
{
|
||||
if (!$this->isEnabled()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return apc_store($key, $value, $ttl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an item from the cache:
|
||||
*
|
||||
* @param string $key
|
||||
*
|
||||
* @return bool|string[]
|
||||
*/
|
||||
public function delete($key)
|
||||
{
|
||||
if (!$this->isEnabled()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return apc_delete($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an item is in the cache:
|
||||
*
|
||||
* @param string $key
|
||||
*
|
||||
* @return bool|string[]
|
||||
*/
|
||||
public function contains($key)
|
||||
{
|
||||
if (!$this->isEnabled()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return apc_exists($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Short-hand syntax for get()
|
||||
*
|
||||
* @see Config::get()
|
||||
*
|
||||
* @param string $key
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function __get($key)
|
||||
{
|
||||
return $this->get($key, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Short-hand syntax for set()
|
||||
*
|
||||
* @see Config::set()
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return array|bool
|
||||
*/
|
||||
public function __set($key, $value = null)
|
||||
{
|
||||
return $this->set($key, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is set
|
||||
*
|
||||
* @param string $key
|
||||
*
|
||||
* @return bool|string[]
|
||||
*/
|
||||
public function __isset($key)
|
||||
{
|
||||
return $this->contains($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unset
|
||||
*
|
||||
* @param string $key
|
||||
*/
|
||||
public function __unset($key)
|
||||
{
|
||||
$this->delete($key);
|
||||
}
|
||||
}
|
||||
90
src/B8Framework/Cache/RequestCache.php
Executable file
90
src/B8Framework/Cache/RequestCache.php
Executable file
|
|
@ -0,0 +1,90 @@
|
|||
<?php
|
||||
|
||||
namespace b8\Cache;
|
||||
|
||||
use b8\Type;
|
||||
|
||||
class RequestCache implements Type\Cache
|
||||
{
|
||||
protected $data = array();
|
||||
|
||||
/**
|
||||
* Check if caching is enabled.
|
||||
*/
|
||||
public function isEnabled()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get item from the cache:
|
||||
*/
|
||||
public function get($key, $default = null)
|
||||
{
|
||||
return $this->contains($key) ? $this->data[$key] : $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an item to the cache:
|
||||
*/
|
||||
public function set($key, $value = null, $ttl = 0)
|
||||
{
|
||||
$this->data[$key] = $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an item from the cache:
|
||||
*/
|
||||
public function delete($key)
|
||||
{
|
||||
if ($this->contains($key)) {
|
||||
unset($this->data[$key]);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an item is in the cache:
|
||||
*/
|
||||
public function contains($key)
|
||||
{
|
||||
return array_key_exists($key, $this->data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Short-hand syntax for get()
|
||||
* @see Config::get()
|
||||
*/
|
||||
public function __get($key)
|
||||
{
|
||||
return $this->get($key, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Short-hand syntax for set()
|
||||
* @see Config::set()
|
||||
*/
|
||||
public function __set($key, $value = null)
|
||||
{
|
||||
return $this->set($key, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is set
|
||||
*/
|
||||
public function __isset($key)
|
||||
{
|
||||
return $this->contains($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unset
|
||||
*/
|
||||
public function __unset($key)
|
||||
{
|
||||
$this->delete($key);
|
||||
}
|
||||
}
|
||||
176
src/B8Framework/Config.php
Executable file
176
src/B8Framework/Config.php
Executable file
|
|
@ -0,0 +1,176 @@
|
|||
<?php
|
||||
|
||||
namespace b8;
|
||||
|
||||
use Symfony\Component\Yaml\Parser as YamlParser;
|
||||
|
||||
if (!defined('B8_PATH')) {
|
||||
define('B8_PATH', dirname(__FILE__) . '/');
|
||||
}
|
||||
|
||||
class Config
|
||||
{
|
||||
protected static $instance;
|
||||
|
||||
public static function getInstance()
|
||||
{
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $config = array();
|
||||
|
||||
public function __construct($settings = null)
|
||||
{
|
||||
self::$instance = $this;
|
||||
|
||||
if (empty($settings)) {
|
||||
return;
|
||||
} elseif (is_array($settings)) {
|
||||
// Array of setting data.
|
||||
$this->setArray($settings);
|
||||
} elseif (is_string($settings) && file_exists($settings)) {
|
||||
$this->loadYaml($settings);
|
||||
}
|
||||
}
|
||||
|
||||
public function loadYaml($yamlFile)
|
||||
{
|
||||
// Path to a YAML file.
|
||||
$parser = new YamlParser();
|
||||
$yaml = file_get_contents($yamlFile);
|
||||
$config = (array)$parser->parse($yaml);
|
||||
|
||||
if (empty($config)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->setArray($config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a configuration value by key, returning a default value if not set.
|
||||
* @param $key string
|
||||
* @param $default mixed
|
||||
* @return mixed
|
||||
*/
|
||||
public function get($key, $default = null)
|
||||
{
|
||||
$keyParts = explode('.', $key);
|
||||
$selected = $this->config;
|
||||
|
||||
$i = -1;
|
||||
$last_part = count($keyParts) - 1;
|
||||
while ($part = array_shift($keyParts)) {
|
||||
$i++;
|
||||
|
||||
if (!array_key_exists($part, $selected)) {
|
||||
return $default;
|
||||
}
|
||||
|
||||
if ($i === $last_part) {
|
||||
return $selected[$part];
|
||||
} else {
|
||||
$selected = $selected[$part];
|
||||
}
|
||||
}
|
||||
|
||||
return $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a value by key.
|
||||
* @param $key string
|
||||
* @param $value mixed
|
||||
*/
|
||||
public function set($key, $value = null)
|
||||
{
|
||||
$this->config[$key] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set an array of values.
|
||||
*/
|
||||
public function setArray($array)
|
||||
{
|
||||
self::deepMerge($this->config, $array);
|
||||
}
|
||||
|
||||
/**
|
||||
* Short-hand syntax for get()
|
||||
* @see Config::get()
|
||||
*/
|
||||
public function __get($key)
|
||||
{
|
||||
return $this->get($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Short-hand syntax for set()
|
||||
* @see Config::set()
|
||||
*/
|
||||
public function __set($key, $value = null)
|
||||
{
|
||||
return $this->set($key, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Is set
|
||||
*/
|
||||
public function __isset($key)
|
||||
{
|
||||
return isset($this->config[$key]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Unset
|
||||
*/
|
||||
public function __unset($key)
|
||||
{
|
||||
unset($this->config[$key]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deeply merge the $target array onto the $source array.
|
||||
* The $source array will be modified!
|
||||
* @param array $source
|
||||
* @param array $target
|
||||
*/
|
||||
public static function deepMerge(&$source, $target)
|
||||
{
|
||||
if (count($source) === 0) {
|
||||
$source = $target;
|
||||
return;
|
||||
}
|
||||
foreach ($target as $target_key => $target_value) {
|
||||
if (isset($source[$target_key])) {
|
||||
if (!is_array($source[$target_key]) && !is_array($target_value)) {
|
||||
// Neither value is an array, overwrite
|
||||
$source[$target_key] = $target_value;
|
||||
|
||||
} elseif (is_array($source[$target_key]) && is_array($target_value)) {
|
||||
// Both are arrays, deep merge them
|
||||
self::deepMerge($source[$target_key], $target_value);
|
||||
|
||||
} elseif (is_array($source[$target_key])) {
|
||||
// Source is the array, push target value
|
||||
$source[$target_key][] = $target_value;
|
||||
} else {
|
||||
// Target is the array, push source value and copy back
|
||||
$target_value[] = $source[$target_key];
|
||||
$source[$target_key] = $target_value;
|
||||
}
|
||||
} else {
|
||||
// No merge required, just set the value
|
||||
$source[$target_key] = $target_value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getArray()
|
||||
{
|
||||
return $this->config;
|
||||
}
|
||||
}
|
||||
116
src/B8Framework/Controller.php
Executable file
116
src/B8Framework/Controller.php
Executable file
|
|
@ -0,0 +1,116 @@
|
|||
<?php
|
||||
|
||||
namespace b8;
|
||||
|
||||
use b8\Config;
|
||||
use b8\Http\Request;
|
||||
use b8\Http\Response;
|
||||
use b8\View;
|
||||
|
||||
/**
|
||||
* b8 Abstract Controller class
|
||||
* @package b8
|
||||
*/
|
||||
abstract class Controller
|
||||
{
|
||||
/**
|
||||
* @var Request
|
||||
*/
|
||||
protected $request;
|
||||
|
||||
/**
|
||||
* @var Response
|
||||
*/
|
||||
protected $response;
|
||||
|
||||
/**
|
||||
* @var Config
|
||||
*/
|
||||
protected $config;
|
||||
|
||||
/**
|
||||
* @var View
|
||||
*/
|
||||
protected $controllerView;
|
||||
|
||||
/**
|
||||
* @var View
|
||||
*/
|
||||
protected $view;
|
||||
|
||||
public function __construct(Config $config, Request $request, Response $response)
|
||||
{
|
||||
$this->config = $config;
|
||||
$this->request = $request;
|
||||
$this->response = $response;
|
||||
}
|
||||
|
||||
public function hasAction($name)
|
||||
{
|
||||
if (method_exists($this, $name)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (method_exists($this, '__call')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles an action on this controller and returns a Response object.
|
||||
* @return Response
|
||||
*/
|
||||
public function handleAction($action, $actionParams)
|
||||
{
|
||||
return call_user_func_array(array($this, $action), $actionParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialise the controller.
|
||||
*/
|
||||
abstract public function init();
|
||||
|
||||
/**
|
||||
* Get a hash of incoming request parameters ($_GET, $_POST)
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getParams()
|
||||
{
|
||||
return $this->request->getParams();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a specific incoming request parameter.
|
||||
*
|
||||
* @param $key
|
||||
* @param mixed $default Default return value (if key does not exist)
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function getParam($key, $default = null)
|
||||
{
|
||||
return $this->request->getParam($key, $default);
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the value of an incoming request parameter.
|
||||
* @param $key
|
||||
* @param $value
|
||||
*/
|
||||
public function setParam($key, $value)
|
||||
{
|
||||
return $this->request->setParam($key, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an incoming request parameter.
|
||||
* @param $key
|
||||
*/
|
||||
public function unsetParam($key)
|
||||
{
|
||||
return $this->request->unsetParam($key);
|
||||
}
|
||||
}
|
||||
246
src/B8Framework/Controller/RestController.php
Executable file
246
src/B8Framework/Controller/RestController.php
Executable file
|
|
@ -0,0 +1,246 @@
|
|||
<?php
|
||||
|
||||
namespace b8\Controller;
|
||||
use b8\Controller,
|
||||
b8\Type\RestUser,
|
||||
b8\Store\Factory,
|
||||
b8\Exception\HttpException;
|
||||
|
||||
class RestController extends Controller
|
||||
{
|
||||
const SEARCHTYPE_AND = 'AND';
|
||||
const SEARCHTYPE_OR = 'OR';
|
||||
|
||||
public $requiresAuthentication = true;
|
||||
public $updateLastAction = true;
|
||||
|
||||
/**
|
||||
* @var \b8\Type\RestUser
|
||||
*/
|
||||
protected $activeUser;
|
||||
protected $where = array();
|
||||
protected $limit = null;
|
||||
protected $offset = null;
|
||||
protected $joins = array();
|
||||
protected $arrayDepth = 2;
|
||||
protected $params = null;
|
||||
protected $order = array();
|
||||
protected $group = null;
|
||||
protected $manualJoins = array();
|
||||
protected $manualWheres = array();
|
||||
protected $searchType = self::SEARCHTYPE_AND;
|
||||
protected $_resourceName = null;
|
||||
protected $_modelName = null;
|
||||
protected $_tableName = null;
|
||||
protected $_modelClass = null;
|
||||
|
||||
public function init()
|
||||
{
|
||||
}
|
||||
|
||||
protected function setControllerView()
|
||||
{
|
||||
}
|
||||
|
||||
protected function setView()
|
||||
{
|
||||
}
|
||||
|
||||
public function handleAction($action, $actionParams)
|
||||
{
|
||||
$response = call_user_func_array(array($this, $action), $actionParams);
|
||||
$this->response->setContent($response);
|
||||
|
||||
return $this->response;
|
||||
}
|
||||
|
||||
public function setActiveUser(RestUser $user)
|
||||
{
|
||||
$this->activeUser = $user;
|
||||
}
|
||||
|
||||
public function getActiveUser()
|
||||
{
|
||||
return $this->activeUser;
|
||||
}
|
||||
|
||||
public function index()
|
||||
{
|
||||
if(!$this->activeUser->checkPermission('canRead', $this->_resourceName))
|
||||
{
|
||||
throw new HttpException\ForbiddenException('You do not have permission do this.');
|
||||
}
|
||||
|
||||
$this->where = $this->_parseWhere();
|
||||
$this->limit = is_null($this->limit) ? $this->getParam('limit', 25) : $this->limit;
|
||||
$this->offset = is_null($this->offset) ? $this->getParam('offset', 0) : $this->offset;
|
||||
$this->order = is_null($this->order) || !count($this->order) ? $this->getParam('order', array()) : $this->order;
|
||||
$this->group = is_null($this->group) || !count($this->group) ? $this->getParam('group', null) : $this->group;
|
||||
$this->searchType = $this->getParam('searchType', self::SEARCHTYPE_AND);
|
||||
|
||||
$store = Factory::getStore($this->_modelName);
|
||||
$data = $store->getWhere($this->where, $this->limit, $this->offset, $this->joins, $this->order, $this->manualJoins, $this->group, $this->manualWheres, $this->searchType);
|
||||
|
||||
$rtn = array(
|
||||
'debug' => array(
|
||||
'where' => $this->where,
|
||||
'searchType' => $this->searchType,
|
||||
),
|
||||
'limit' => $this->limit,
|
||||
'offset' => $this->offset,
|
||||
'total' => $data['count'],
|
||||
'items' => array()
|
||||
);
|
||||
|
||||
foreach($data['items'] as $item)
|
||||
{
|
||||
$rtn['items'][] = $item->toArray($this->arrayDepth);
|
||||
}
|
||||
|
||||
return $rtn;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
protected function _parseWhere()
|
||||
{
|
||||
$clauses = array(
|
||||
'fuzzy' => 'like',
|
||||
'gt' => '>',
|
||||
'gte' => '>=',
|
||||
'lt' => '<',
|
||||
'lte' => '<=',
|
||||
'neq' => '!=',
|
||||
'between' => 'between'
|
||||
);
|
||||
|
||||
$where = $this->getParam('where', array());
|
||||
$where = array_merge($where, $this->where);
|
||||
|
||||
if(count($where))
|
||||
{
|
||||
foreach($where as &$value)
|
||||
{
|
||||
if(!is_array($value) || !isset($value['operator']))
|
||||
{
|
||||
if(is_array($value) && count($value) == 1)
|
||||
{
|
||||
$value = array_shift($value);
|
||||
}
|
||||
|
||||
$value = array(
|
||||
'operator' => '=',
|
||||
'value' => $value
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
foreach($clauses as $clause => $operator)
|
||||
{
|
||||
$fields = $this->getParam($clause, array());
|
||||
|
||||
if(count($clause))
|
||||
{
|
||||
if(!is_array($fields))
|
||||
{
|
||||
$fields = array($fields);
|
||||
}
|
||||
foreach($fields as $field)
|
||||
{
|
||||
if(isset($where[$field]))
|
||||
{
|
||||
$where[$field]['operator'] = $operator;
|
||||
if($operator == 'like')
|
||||
{
|
||||
$where[$field]['value'] = str_replace(' ', '%', $where[$field]['value']);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $where;
|
||||
}
|
||||
|
||||
public function get($key)
|
||||
{
|
||||
if(!$this->activeUser->checkPermission('canRead', $this->_resourceName))
|
||||
{
|
||||
throw new HttpException\ForbiddenException('You do not have permission do this.');
|
||||
}
|
||||
|
||||
$rtn = Factory::getStore($this->_modelName)->getByPrimaryKey($key);
|
||||
|
||||
if(is_object($rtn) && method_exists($rtn, 'toArray'))
|
||||
{
|
||||
$rtn = $rtn->toArray($this->arrayDepth);
|
||||
}
|
||||
|
||||
return array(strtolower($this->_modelName) => $rtn);
|
||||
}
|
||||
|
||||
public function put($key)
|
||||
{
|
||||
if(!$this->activeUser->checkPermission('canEdit', $this->_resourceName))
|
||||
{
|
||||
throw new HttpException\ForbiddenException('You do not have permission do this.');
|
||||
}
|
||||
|
||||
$store = Factory::getStore($this->_modelName);
|
||||
|
||||
if($obj = $store->getByPrimaryKey($key))
|
||||
{
|
||||
$obj->setValues($this->getParams());
|
||||
$rtn = $store->save($obj);
|
||||
|
||||
return array(strtolower($this->_modelName) => $rtn->toArray($this->arrayDepth));
|
||||
}
|
||||
else
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public function post()
|
||||
{
|
||||
if(!$this->activeUser->checkPermission('canCreate', $this->_resourceName))
|
||||
{
|
||||
throw new HttpException\ForbiddenException('You do not have permission do this.');
|
||||
}
|
||||
|
||||
$store = Factory::getStore($this->_modelName);
|
||||
|
||||
$modelClass = $this->_modelClass;
|
||||
$obj = new $modelClass();
|
||||
$obj->setValues($this->getParams());
|
||||
$rtn = $store->save($obj);
|
||||
|
||||
return array(strtolower($this->_modelName) => $rtn->toArray($this->arrayDepth));
|
||||
}
|
||||
|
||||
public function delete($key)
|
||||
{
|
||||
if(!$this->activeUser->checkPermission('canDelete', $this->_resourceName))
|
||||
{
|
||||
throw new HttpException\ForbiddenException('You do not have permission do this.');
|
||||
}
|
||||
|
||||
$store = Factory::getStore($this->_modelName);
|
||||
|
||||
try
|
||||
{
|
||||
if($obj = $store->getByPrimaryKey($key))
|
||||
{
|
||||
$store->delete($obj);
|
||||
return array('deleted' => true);
|
||||
}
|
||||
}
|
||||
catch(\Exception $ex)
|
||||
{
|
||||
}
|
||||
|
||||
return array('deleted' => false);
|
||||
}
|
||||
}
|
||||
151
src/B8Framework/Database.php
Executable file
151
src/B8Framework/Database.php
Executable file
|
|
@ -0,0 +1,151 @@
|
|||
<?php
|
||||
|
||||
namespace b8;
|
||||
|
||||
class Database extends \PDO
|
||||
{
|
||||
protected static $initialised = false;
|
||||
protected static $servers = array('read' => array(), 'write' => array());
|
||||
protected static $connections = array('read' => null, 'write' => null);
|
||||
protected static $details = array();
|
||||
protected static $lastUsed = array('read' => null, 'write' => null);
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
public static function setReadServers($read)
|
||||
{
|
||||
$config = Config::getInstance();
|
||||
|
||||
$settings = $config->get('b8.database', array());
|
||||
$settings['servers']['read'] = $read;
|
||||
$config->set('b8.database', $settings);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
public static function setWriteServers($write)
|
||||
{
|
||||
$config = Config::getInstance();
|
||||
|
||||
$settings = $config->get('b8.database', array());
|
||||
$settings['servers']['write'] = $write;
|
||||
$config->set('b8.database', $settings);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
public static function setDetails($database, $username, $password)
|
||||
{
|
||||
$config = Config::getInstance();
|
||||
$settings = $config->get('b8.database', array());
|
||||
$settings['name'] = $database;
|
||||
$settings['username'] = $username;
|
||||
$settings['password'] = $password;
|
||||
$config->set('b8.database', $settings);
|
||||
}
|
||||
|
||||
protected static function init()
|
||||
{
|
||||
$config = Config::getInstance();
|
||||
$settings = $config->get('b8.database', array());
|
||||
self::$servers['read'] = $settings['servers']['read'];
|
||||
self::$servers['write'] = $settings['servers']['write'];
|
||||
self::$details['db'] = $settings['name'];
|
||||
self::$details['user'] = $settings['username'];
|
||||
self::$details['pass'] = $settings['password'];
|
||||
self::$initialised = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $type
|
||||
*
|
||||
* @return \b8\Database
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function getConnection($type = 'read')
|
||||
{
|
||||
if (!self::$initialised) {
|
||||
self::init();
|
||||
}
|
||||
|
||||
// If the connection hasn't been used for 5 minutes, force a reconnection:
|
||||
if (!is_null(self::$lastUsed[$type]) && (time() - self::$lastUsed[$type]) > 300) {
|
||||
self::$connections[$type] = null;
|
||||
}
|
||||
|
||||
if(is_null(self::$connections[$type])) {
|
||||
if (is_array(self::$servers[$type])) {
|
||||
// Shuffle, so we pick a random server:
|
||||
$servers = self::$servers[$type];
|
||||
shuffle($servers);
|
||||
} else {
|
||||
// Only one server was specified
|
||||
$servers = array(self::$servers[$type]);
|
||||
}
|
||||
|
||||
$connection = null;
|
||||
|
||||
// Loop until we get a working connection:
|
||||
while(count($servers))
|
||||
{
|
||||
// Pull the next server:
|
||||
$server = array_shift($servers);
|
||||
|
||||
if (stristr($server, ':')) {
|
||||
list($host, $port) = explode(':', $server);
|
||||
$server = $host . ';port=' . $port;
|
||||
}
|
||||
|
||||
// Try to connect:
|
||||
try
|
||||
{
|
||||
$connection = new self('mysql:host=' . $server . ';dbname=' . self::$details['db'],
|
||||
self::$details['user'],
|
||||
self::$details['pass'],
|
||||
array(
|
||||
\PDO::ATTR_PERSISTENT => false,
|
||||
\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
|
||||
\PDO::ATTR_TIMEOUT => 2,
|
||||
\PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\'',
|
||||
));
|
||||
}
|
||||
catch(\PDOException $ex)
|
||||
{
|
||||
$connection = false;
|
||||
}
|
||||
|
||||
// Opened a connection? Break the loop:
|
||||
if($connection)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// No connection? Oh dear.
|
||||
if(!$connection && $type == 'read')
|
||||
{
|
||||
throw new \Exception('Could not connect to any ' . $type . ' servers.');
|
||||
}
|
||||
|
||||
self::$connections[$type] = $connection;
|
||||
}
|
||||
|
||||
self::$lastUsed[$type] = time();
|
||||
return self::$connections[$type];
|
||||
}
|
||||
|
||||
public function getDetails()
|
||||
{
|
||||
return self::$details;
|
||||
}
|
||||
|
||||
public static function reset()
|
||||
{
|
||||
self::$connections = array('read' => null, 'write' => null);
|
||||
self::$lastUsed = array('read' => null, 'write' => null);
|
||||
self::$initialised = false;
|
||||
}
|
||||
}
|
||||
153
src/B8Framework/Database/CodeGenerator.php
Executable file
153
src/B8Framework/Database/CodeGenerator.php
Executable file
|
|
@ -0,0 +1,153 @@
|
|||
<?php
|
||||
|
||||
namespace b8\Database;
|
||||
use b8\Database,
|
||||
b8\Database\Map,
|
||||
b8\View\Template;
|
||||
|
||||
class CodeGenerator
|
||||
{
|
||||
protected $_db = null;
|
||||
protected $_map = null;
|
||||
protected $_tables = null;
|
||||
protected $_ns = null;
|
||||
protected $_path = null;
|
||||
|
||||
/**
|
||||
* @param Database $db
|
||||
* @param array $namespaces
|
||||
* @param string $path
|
||||
* @param bool $includeCountQueries
|
||||
*/
|
||||
public function __construct(Database $db, array $namespaces, $path, $includeCountQueries = true)
|
||||
{
|
||||
$this->_db = $db;
|
||||
$this->_ns = $namespaces;
|
||||
$this->_path = $path;
|
||||
$this->_map = new Map($this->_db);
|
||||
$this->_tables = $this->_map->generate();
|
||||
$this->_counts = $includeCountQueries;
|
||||
}
|
||||
|
||||
protected function getNamespace($modelName)
|
||||
{
|
||||
return array_key_exists($modelName, $this->_ns) ? $this->_ns[$modelName] : $this->_ns['default'];
|
||||
}
|
||||
|
||||
public function getPath($namespace)
|
||||
{
|
||||
return array_key_exists($namespace, $this->_path) ? $this->_path[$namespace] : $this->_path['default'];
|
||||
}
|
||||
|
||||
public function generateModels()
|
||||
{
|
||||
print PHP_EOL . 'GENERATING MODELS' . PHP_EOL . PHP_EOL;
|
||||
|
||||
foreach($this->_tables as $tableName => $table)
|
||||
{
|
||||
$namespace = $this->getNamespace($table['php_name']);
|
||||
$modelPath = $this->getPath($namespace) . str_replace('\\', '/', $namespace) . '/Model/';
|
||||
$basePath = $modelPath . 'Base/';
|
||||
$modelFile = $modelPath . $table['php_name'] . '.php';
|
||||
$baseFile = $basePath . $table['php_name'] . 'Base.php';
|
||||
|
||||
if (!is_dir($basePath)) {
|
||||
@mkdir($basePath, 0777, true);
|
||||
}
|
||||
|
||||
$model = $this->_processTemplate($tableName, $table, 'ModelTemplate');
|
||||
$base = $this->_processTemplate($tableName, $table, 'BaseModelTemplate');
|
||||
|
||||
print '-- ' . $table['php_name'] . PHP_EOL;
|
||||
|
||||
if(!is_file($modelFile))
|
||||
{
|
||||
print '-- -- Writing new Model' . PHP_EOL;
|
||||
file_put_contents($modelFile, $model);
|
||||
}
|
||||
|
||||
print '-- -- Writing base Model' . PHP_EOL;
|
||||
file_put_contents($baseFile, $base);
|
||||
}
|
||||
}
|
||||
|
||||
public function generateStores()
|
||||
{
|
||||
print PHP_EOL . 'GENERATING STORES' . PHP_EOL . PHP_EOL;
|
||||
|
||||
foreach($this->_tables as $tableName => $table)
|
||||
{
|
||||
$namespace = $this->getNamespace($table['php_name']);
|
||||
$storePath = $this->getPath($namespace) . str_replace('\\', '/', $namespace) . '/Store/';
|
||||
$basePath = $storePath . 'Base/';
|
||||
$storeFile = $storePath . $table['php_name'] . 'Store.php';
|
||||
$baseFile = $basePath . $table['php_name'] . 'StoreBase.php';
|
||||
|
||||
if (!is_dir($basePath)) {
|
||||
@mkdir($basePath, 0777, true);
|
||||
}
|
||||
|
||||
$model = $this->_processTemplate($tableName, $table, 'StoreTemplate');
|
||||
$base = $this->_processTemplate($tableName, $table, 'BaseStoreTemplate');
|
||||
|
||||
print '-- ' . $table['php_name'] . PHP_EOL;
|
||||
|
||||
if(!is_file($storeFile))
|
||||
{
|
||||
print '-- -- Writing new Store' . PHP_EOL;
|
||||
file_put_contents($storeFile, $model);
|
||||
}
|
||||
|
||||
print '-- -- Writing base Store' . PHP_EOL;
|
||||
file_put_contents($baseFile, $base);
|
||||
}
|
||||
}
|
||||
|
||||
public function generateControllers()
|
||||
{
|
||||
print PHP_EOL . 'GENERATING CONTROLLERS' . PHP_EOL . PHP_EOL;
|
||||
|
||||
@mkdir($this->_path . 'Controller/Base/', 0777, true);
|
||||
|
||||
foreach($this->_tables as $tableName => $table)
|
||||
{
|
||||
$namespace = $this->getNamespace($table['php_name']);
|
||||
$controllerPath = $this->getPath($namespace) . str_replace('\\', '/', $namespace) . '/Controller/';
|
||||
$basePath = $controllerPath . 'Base/';
|
||||
$controllerFile = $controllerPath . $table['php_name'] . 'Controller.php';
|
||||
$baseFile = $basePath . $table['php_name'] . 'ControllerBase.php';
|
||||
|
||||
if (!is_dir($basePath)) {
|
||||
@mkdir($basePath, 0777, true);
|
||||
}
|
||||
|
||||
$model = $this->_processTemplate($tableName, $table, 'ControllerTemplate');
|
||||
$base = $this->_processTemplate($tableName, $table, 'BaseControllerTemplate');
|
||||
|
||||
print '-- ' . $table['php_name'] . PHP_EOL;
|
||||
|
||||
if(!is_file($controllerFile))
|
||||
{
|
||||
print '-- -- Writing new Controller' . PHP_EOL;
|
||||
file_put_contents($controllerFile, $model);
|
||||
}
|
||||
|
||||
print '-- -- Writing base Controller' . PHP_EOL;
|
||||
file_put_contents($baseFile, $base);
|
||||
}
|
||||
}
|
||||
|
||||
protected function _processTemplate($tableName, $table, $template)
|
||||
{
|
||||
$tpl = Template::createFromFile($template, B8_PATH . 'Database/CodeGenerator/');
|
||||
$tpl->appNamespace = $this->getNamespace($table['php_name']);
|
||||
$tpl->name = $tableName;
|
||||
$tpl->table = $table;
|
||||
$tpl->counts = $this->_counts;
|
||||
$tpl->addFunction('get_namespace', function($args, $view) {
|
||||
return $this->getNamespace($view->getVariable($args['model']));
|
||||
});
|
||||
|
||||
return $tpl->render();
|
||||
}
|
||||
}
|
||||
22
src/B8Framework/Database/CodeGenerator/BaseControllerTemplate.html
Executable file
22
src/B8Framework/Database/CodeGenerator/BaseControllerTemplate.html
Executable file
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* {@table.php_name} base controller for table: {@name}
|
||||
*/
|
||||
|
||||
namespace {@appNamespace}\Controller\Base;
|
||||
|
||||
use b8\Controller\RestController;
|
||||
|
||||
/**
|
||||
* {@table.php_name} base Controller
|
||||
* @see {@appNamespace}\Controller\{@table.php_name}
|
||||
* @uses {@appNamespace}\Store\{@table.php_name}Store
|
||||
* @uses {@appNamespace}\Model\{@table.php_name}
|
||||
*/
|
||||
class {@table.php_name}ControllerBase extends RestController
|
||||
{
|
||||
protected $_modelName = '{@table.php_name}';
|
||||
protected $_resourceName = '{@table.php_name.toLowerCase}s';
|
||||
protected $_modelClass = '\{@appNamespace}\Model\{@table.php_name}';
|
||||
}
|
||||
268
src/B8Framework/Database/CodeGenerator/BaseModelTemplate.html
Executable file
268
src/B8Framework/Database/CodeGenerator/BaseModelTemplate.html
Executable file
|
|
@ -0,0 +1,268 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* {@table.php_name} base model for table: {@name}
|
||||
*/
|
||||
|
||||
namespace {@appNamespace}\Model\Base;
|
||||
|
||||
use {@appNamespace}\Model;
|
||||
use b8\Store\Factory;
|
||||
|
||||
/**
|
||||
* {@table.php_name} Base Model
|
||||
*/
|
||||
class {@table.php_name}Base extends Model
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public static $sleepable = array();
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $tableName = '{@name}';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $modelName = '{@table.php_name}';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $data = array(
|
||||
{loop table.columns}
|
||||
'{@item.name}' => null,
|
||||
{/loop}
|
||||
);
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $getters = array(
|
||||
// Direct property getters:
|
||||
{loop table.columns}
|
||||
'{@item.name}' => 'get{@item.php_name}',
|
||||
{/loop}
|
||||
|
||||
// Foreign key getters:
|
||||
{loop table.relationships.toOne}
|
||||
'{@item.php_name}' => 'get{@item.php_name}',
|
||||
{/loop}
|
||||
);
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $setters = array(
|
||||
// Direct property setters:
|
||||
{loop table.columns}
|
||||
'{@item.name}' => 'set{@item.php_name}',
|
||||
{/loop}
|
||||
|
||||
// Foreign key setters:
|
||||
{loop table.relationships.toOne}
|
||||
'{@item.php_name}' => 'set{@item.php_name}',
|
||||
{/loop}
|
||||
);
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public $columns = array(
|
||||
{loop table.columns}
|
||||
'{@item.name}' => array(
|
||||
'type' => '{@item.type}',
|
||||
{if item.length}
|
||||
'length' => {@item.length},
|
||||
{/if}
|
||||
{if item.null}
|
||||
'nullable' => true,
|
||||
{/if}
|
||||
{if item.is_primary_key}
|
||||
'primary_key' => true,
|
||||
{/if}
|
||||
{if item.auto}
|
||||
'auto_increment' => true,
|
||||
{/if}
|
||||
{if item.default_is_null}
|
||||
'default' => null,
|
||||
{/if}
|
||||
{ifnot item.default_is_null}
|
||||
{if item.default}
|
||||
'default' => {if item.default.isNumeric}{@item.default}{/if}{ifnot item.default.isNumeric}'{@item.default}'{/ifnot},
|
||||
{/if}
|
||||
{/ifnot}
|
||||
),
|
||||
{/loop}
|
||||
);
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public $indexes = array(
|
||||
{loop table.indexes}
|
||||
'{@item.name}' => array({if item.unique}'unique' => true, {/if}'columns' => '{@item.columns}'),
|
||||
{/loop}
|
||||
);
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public $foreignKeys = array(
|
||||
{loop table.relationships.toOne}
|
||||
'{@item.fk_name}' => array(
|
||||
'local_col' => '{@item.from_col}',
|
||||
'update' => '{@item.fk_update}',
|
||||
'delete' => '{@item.fk_delete}',
|
||||
'table' => '{@item.table}',
|
||||
'col' => '{@item.col}'
|
||||
),
|
||||
{/loop}
|
||||
);
|
||||
|
||||
{loop table.columns}
|
||||
/**
|
||||
* Get the value of {@item.php_name} / {@item.name}.
|
||||
*
|
||||
{if item.validate_int}
|
||||
* @return int
|
||||
{/if}{if item.validate_string}
|
||||
* @return string
|
||||
{/if}{if item.validate_float}
|
||||
* @return float
|
||||
{/if}{if item.validate_date}
|
||||
* @return \DateTime
|
||||
{/if}
|
||||
*/
|
||||
public function get{@item.php_name}()
|
||||
{
|
||||
$rtn = $this->data['{@item.name}'];
|
||||
|
||||
{if item.validate_date}
|
||||
if (!empty($rtn)) {
|
||||
$rtn = new \DateTime($rtn);
|
||||
}
|
||||
|
||||
{/if}
|
||||
return $rtn;
|
||||
}
|
||||
|
||||
{/loop}
|
||||
{loop table.columns}
|
||||
/**
|
||||
* Set the value of {@item.php_name} / {@item.name}.
|
||||
*
|
||||
{if item.validate_null}
|
||||
* Must not be null.
|
||||
{/if}{if item.validate_int}
|
||||
* @param $value int
|
||||
{/if}{if item.validate_string}
|
||||
* @param $value string
|
||||
{/if}{if item.validate_float}
|
||||
* @param $value float
|
||||
{/if}{if item.validate_date}
|
||||
* @param $value \DateTime
|
||||
{/if}
|
||||
*/
|
||||
public function set{@item.php_name}($value)
|
||||
{
|
||||
{if item.validate_null}
|
||||
$this->_validateNotNull('{@item.php_name}', $value);
|
||||
{/if}
|
||||
{if item.validate_int}
|
||||
$this->_validateInt('{@item.php_name}', $value);
|
||||
{/if}
|
||||
{if item.validate_string}
|
||||
$this->_validateString('{@item.php_name}', $value);
|
||||
{/if}
|
||||
{if item.validate_float}
|
||||
$this->_validateFloat('{@item.php_name}', $value);
|
||||
{/if}
|
||||
{if item.validate_date}
|
||||
$this->_validateDate('{@item.php_name}', $value);
|
||||
{/if}
|
||||
|
||||
if ($this->data['{@item.name}'] === $value) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->data['{@item.name}'] = $value;
|
||||
|
||||
$this->_setModified('{@item.name}');
|
||||
}
|
||||
|
||||
{/loop}{loop table.relationships.toOne}
|
||||
/**
|
||||
* Get the {@item.table_php_name} model for this {@parent.table.php_name} by {@item.col_php}.
|
||||
*
|
||||
* @uses \{@parent.appNamespace}\Store\{@item.table_php_name}Store::getBy{@item.col_php}()
|
||||
* @uses \{@parent.appNamespace}\Model\{@item.table_php_name}
|
||||
* @return \{@parent.appNamespace}\Model\{@item.table_php_name}
|
||||
*/
|
||||
public function get{@item.php_name}()
|
||||
{
|
||||
$key = $this->get{@item.from_col_php}();
|
||||
|
||||
if (empty($key)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$cacheKey = 'Cache.{@item.table_php_name}.' . $key;
|
||||
$rtn = $this->cache->get($cacheKey, null);
|
||||
|
||||
if (empty($rtn)) {
|
||||
$rtn = Factory::getStore('{@item.table_php_name}', '{get_namespace model: item.table_php_name}')->getBy{@item.col_php}($key);
|
||||
$this->cache->set($cacheKey, $rtn);
|
||||
}
|
||||
|
||||
return $rtn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set {@item.php_name} - Accepts an ID, an array representing a {@item.table_php_name} or a {@item.table_php_name} model.
|
||||
*
|
||||
* @param $value mixed
|
||||
*/
|
||||
public function set{@item.php_name}($value)
|
||||
{
|
||||
// Is this an instance of {@item.table_php_name}?
|
||||
if ($value instanceof \{@parent.appNamespace}\Model\{@item.table_php_name}) {
|
||||
return $this->set{@item.php_name}Object($value);
|
||||
}
|
||||
|
||||
// Is this an array representing a {@item.table_php_name} item?
|
||||
if (is_array($value) && !empty($value['{@item.col}'])) {
|
||||
return $this->set{@item.from_col_php}($value['{@item.col}']);
|
||||
}
|
||||
|
||||
// Is this a scalar value representing the ID of this foreign key?
|
||||
return $this->set{@item.from_col_php}($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set {@item.php_name} - Accepts a {@item.table_php_name} model.
|
||||
*
|
||||
* @param $value \{@parent.appNamespace}\Model\{@item.table_php_name}
|
||||
*/
|
||||
public function set{@item.php_name}Object(\{@parent.appNamespace}\Model\{@item.table_php_name} $value)
|
||||
{
|
||||
return $this->set{@item.from_col_php}($value->get{@item.col_php}());
|
||||
}
|
||||
|
||||
{/loop}{loop table.relationships.toMany}
|
||||
/**
|
||||
* Get {@item.table_php} models by {@item.from_col_php} for this {@parent.table.php_name}.
|
||||
*
|
||||
* @uses \{@parent.appNamespace}\Store\{@item.table_php}Store::getBy{@item.from_col_php}()
|
||||
* @uses \{@parent.appNamespace}\Model\{@item.table_php}
|
||||
* @return \{@parent.appNamespace}\Model\{@item.table_php}[]
|
||||
*/
|
||||
public function get{@item.php_name}()
|
||||
{
|
||||
return Factory::getStore('{@item.table_php}', '{get_namespace model: item.table_php_name}')->getBy{@item.from_col_php}($this->get{@item.col_php}());
|
||||
}
|
||||
|
||||
{/loop}}
|
||||
101
src/B8Framework/Database/CodeGenerator/BaseStoreTemplate.html
Executable file
101
src/B8Framework/Database/CodeGenerator/BaseStoreTemplate.html
Executable file
|
|
@ -0,0 +1,101 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* {@table.php_name} base store for table: {@name}
|
||||
*/
|
||||
|
||||
namespace {@appNamespace}\Store\Base;
|
||||
|
||||
use b8\Database;
|
||||
use b8\Exception\HttpException;
|
||||
use {@appNamespace}\Store;
|
||||
use {@appNamespace}\Model\{@table.php_name};
|
||||
|
||||
/**
|
||||
* {@table.php_name} Base Store
|
||||
*/
|
||||
class {@table.php_name}StoreBase extends Store
|
||||
{
|
||||
protected $tableName = '{@name}';
|
||||
protected $modelName = '\{@appNamespace}\Model\{@table.php_name}';
|
||||
{if table.primary_key}
|
||||
protected $primaryKey = '{@table.primary_key.column}';
|
||||
|
||||
public function getByPrimaryKey($value, $useConnection = 'read')
|
||||
{
|
||||
return $this->getBy{@table.primary_key.php_name}($value, $useConnection);
|
||||
}
|
||||
{/if}
|
||||
{ifnot table.primary_key}
|
||||
|
||||
public function getByPrimaryKey($value, $useConnection = 'read')
|
||||
{
|
||||
throw new \Exception('getByPrimaryKey is not implemented for this store, as the table has no primary key.');
|
||||
}
|
||||
{/ifnot}
|
||||
{loop table.columns}
|
||||
{if item.unique_indexed}
|
||||
|
||||
public function getBy{@item.php_name}($value, $useConnection = 'read')
|
||||
{
|
||||
if (is_null($value)) {
|
||||
throw new HttpException('Value passed to ' . __FUNCTION__ . ' cannot be null.');
|
||||
}
|
||||
|
||||
$query = 'SELECT * FROM `{@parent.name}` WHERE `{@item.name}` = :{@item.name} LIMIT 1';
|
||||
$stmt = Database::getConnection($useConnection)->prepare($query);
|
||||
$stmt->bindValue(':{@item.name}', $value);
|
||||
|
||||
if ($stmt->execute()) {
|
||||
if ($data = $stmt->fetch(\PDO::FETCH_ASSOC)) {
|
||||
return new {@parent.table.php_name}($data);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
{/if}
|
||||
{if item.many_indexed}
|
||||
|
||||
public function getBy{@item.php_name}($value, $limit = 1000, $useConnection = 'read')
|
||||
{
|
||||
if (is_null($value)) {
|
||||
throw new HttpException('Value passed to ' . __FUNCTION__ . ' cannot be null.');
|
||||
}
|
||||
|
||||
{if counts}
|
||||
$query = 'SELECT COUNT(*) AS cnt FROM `{@parent.name}` WHERE `{@item.name}` = :{@item.name}';
|
||||
$stmt = Database::getConnection($useConnection)->prepare($query);
|
||||
$stmt->bindValue(':{@item.name}', $value);
|
||||
|
||||
if ($stmt->execute()) {
|
||||
$res = $stmt->fetch(\PDO::FETCH_ASSOC);
|
||||
$count = (int)$res['cnt'];
|
||||
} else {
|
||||
$count = 0;
|
||||
}
|
||||
{/if}
|
||||
|
||||
$query = 'SELECT * FROM `{@parent.name}` WHERE `{@item.name}` = :{@item.name} LIMIT :limit';
|
||||
$stmt = Database::getConnection($useConnection)->prepare($query);
|
||||
$stmt->bindValue(':{@item.name}', $value);
|
||||
$stmt->bindValue(':limit', (int)$limit, \PDO::PARAM_INT);
|
||||
|
||||
if ($stmt->execute()) {
|
||||
$res = $stmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||
|
||||
$map = function ($item) {
|
||||
return new {@parent.table.php_name}($item);
|
||||
};
|
||||
$rtn = array_map($map, $res);
|
||||
|
||||
{ifnot counts}$count = count($rtn);{/ifnot}
|
||||
|
||||
return array('items' => $rtn, 'count' => $count);
|
||||
} else {
|
||||
return array('items' => array(), 'count' => 0);
|
||||
}
|
||||
}
|
||||
{/if}
|
||||
{/loop}
|
||||
}
|
||||
20
src/B8Framework/Database/CodeGenerator/ControllerTemplate.html
Executable file
20
src/B8Framework/Database/CodeGenerator/ControllerTemplate.html
Executable file
|
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* {@table.php_name} controller for table: {@name}
|
||||
*/
|
||||
|
||||
namespace {@appNamespace}\Controller;
|
||||
|
||||
use {@appNamespace}\Controller\Base\{@table.php_name}ControllerBase;
|
||||
|
||||
/**
|
||||
* {@table.php_name} Controller
|
||||
* @uses {@appNamespace}\Controller\Base\{@table.php_name}Base
|
||||
* @uses {@appNamespace}\Store\{@table.php_name}Store
|
||||
* @uses {@appNamespace}\Model\{@table.php_name}
|
||||
*/
|
||||
class {@table.php_name}Controller extends {@table.php_name}ControllerBase
|
||||
{
|
||||
// This class has been left blank so that you can modify it - changes in this file will not be overwritten.
|
||||
}
|
||||
18
src/B8Framework/Database/CodeGenerator/ModelTemplate.html
Executable file
18
src/B8Framework/Database/CodeGenerator/ModelTemplate.html
Executable file
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* {@table.php_name} model for table: {@name}
|
||||
*/
|
||||
|
||||
namespace {@appNamespace}\Model;
|
||||
|
||||
use {@appNamespace}\Model\Base\{@table.php_name}Base;
|
||||
|
||||
/**
|
||||
* {@table.php_name} Model
|
||||
* @uses {@appNamespace}\Model\Base\{@table.php_name}Base
|
||||
*/
|
||||
class {@table.php_name} extends {@table.php_name}Base
|
||||
{
|
||||
// This class has been left blank so that you can modify it - changes in this file will not be overwritten.
|
||||
}
|
||||
18
src/B8Framework/Database/CodeGenerator/StoreTemplate.html
Executable file
18
src/B8Framework/Database/CodeGenerator/StoreTemplate.html
Executable file
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* {@table.php_name} store for table: {@name}
|
||||
*/
|
||||
|
||||
namespace {@appNamespace}\Store;
|
||||
|
||||
use {@appNamespace}\Store\Base\{@table.php_name}StoreBase;
|
||||
|
||||
/**
|
||||
* {@table.php_name} Store
|
||||
* @uses {@appNamespace}\Store\Base\{@table.php_name}StoreBase
|
||||
*/
|
||||
class {@table.php_name}Store extends {@table.php_name}StoreBase
|
||||
{
|
||||
// This class has been left blank so that you can modify it - changes in this file will not be overwritten.
|
||||
}
|
||||
428
src/B8Framework/Database/Generator.php
Executable file
428
src/B8Framework/Database/Generator.php
Executable file
|
|
@ -0,0 +1,428 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Database generator updates a database to match a set of Models.
|
||||
*/
|
||||
|
||||
namespace b8\Database;
|
||||
use b8\Database;
|
||||
|
||||
class Generator
|
||||
{
|
||||
protected $_db = null;
|
||||
protected $_map = null;
|
||||
protected $_tables = null;
|
||||
protected $_ns = null;
|
||||
protected $_path = null;
|
||||
|
||||
public function __construct(Database $db, $namespace, $path)
|
||||
{
|
||||
$this->_db = $db;
|
||||
$this->_ns = $namespace;
|
||||
$this->_path = $path;
|
||||
$this->_map = new Map($this->_db);
|
||||
$this->_tables = $this->_map->generate();
|
||||
}
|
||||
|
||||
public function generate()
|
||||
{
|
||||
error_reporting(E_ERROR & E_WARNING);
|
||||
$di = new \DirectoryIterator($this->_path);
|
||||
|
||||
$this->_todo = array(
|
||||
'drop_fk' => array(),
|
||||
'drop_index'=> array(),
|
||||
'create' => array(),
|
||||
'alter' => array(),
|
||||
'add_index' => array(),
|
||||
'add_fk' => array(),
|
||||
);
|
||||
|
||||
foreach($di as $file)
|
||||
{
|
||||
if($file->isDot())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
$fileName = explode('.', $file->getBasename());
|
||||
if ($fileName[count($fileName)-1] != 'php')
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$modelName = '\\' . $this->_ns . '\\Model\\Base\\' . str_replace('.php', '', $file->getFilename());
|
||||
|
||||
require_once($this->_path . $file->getFilename());
|
||||
$model = new $modelName();
|
||||
$columns = $model->columns;
|
||||
$indexes = $model->indexes;
|
||||
$foreignKeys = $model->foreignKeys;
|
||||
$tableName = $model->getTableName();
|
||||
|
||||
if(!array_key_exists($tableName, $this->_tables))
|
||||
{
|
||||
$this->_createTable($tableName, $columns, $indexes, $foreignKeys);
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
$table = $this->_tables[$tableName];
|
||||
$this->_updateColumns($tableName, $table, $columns);
|
||||
$this->_updateRelationships($tableName, $table, $foreignKeys);
|
||||
$this->_updateIndexes($tableName, $table, $indexes);
|
||||
}
|
||||
}
|
||||
|
||||
print 'DROP FK: ' . count($this->_todo['drop_fk']) . PHP_EOL;
|
||||
print 'DROP INDEX: ' . count($this->_todo['drop_index']) . PHP_EOL;
|
||||
print 'CREATE TABLE: ' . count($this->_todo['create']) . PHP_EOL;
|
||||
print 'ALTER TABLE: ' . count($this->_todo['alter']) . PHP_EOL;
|
||||
print 'ADD INDEX: ' . count($this->_todo['add_index']) . PHP_EOL;
|
||||
print 'ADD FK: ' . count($this->_todo['add_fk']) . PHP_EOL;
|
||||
|
||||
|
||||
$order = array_keys($this->_todo);
|
||||
|
||||
while($group = array_shift($order))
|
||||
{
|
||||
if(!isset($this->_todo[$group]) || !is_array($this->_todo[$group]) || !count($this->_todo[$group]))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach($this->_todo[$group] as $query)
|
||||
{
|
||||
try
|
||||
{
|
||||
//print $query . PHP_EOL;
|
||||
$this->_db->query($query);
|
||||
}
|
||||
catch(\Exception $ex)
|
||||
{
|
||||
print 'FAILED TO EXECUTE: ' . $query . PHP_EOL;
|
||||
print $ex->getMessage().PHP_EOL.PHP_EOL;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function _createTable($tbl, $cols, $idxs, $fks)
|
||||
{
|
||||
$defs = array();
|
||||
$pks = array();
|
||||
foreach($cols as $colName => $def)
|
||||
{
|
||||
$add = '`' . $colName . '` ' . $def['type'];
|
||||
|
||||
switch($def['type'])
|
||||
{
|
||||
case 'text':
|
||||
case 'longtext':
|
||||
case 'mediumtext':
|
||||
case 'date':
|
||||
case 'datetime':
|
||||
case 'float':
|
||||
$add .= '';
|
||||
break;
|
||||
|
||||
default:
|
||||
$add .= !empty($def['length']) ? '(' . $def['length'] . ')' : '';
|
||||
break;
|
||||
}
|
||||
|
||||
if(empty($def['nullable']) || !$def['nullable'])
|
||||
{
|
||||
$add .= ' NOT NULL ';
|
||||
}
|
||||
|
||||
if(!empty($def['default']))
|
||||
{
|
||||
$add .= ' DEFAULT ' . (is_numeric($def['default']) ? $def['default'] : '\'' . $def['default'] . '\'');
|
||||
}
|
||||
|
||||
if(!empty($def['auto_increment']) && $def['auto_increment'])
|
||||
{
|
||||
$add .= ' AUTO_INCREMENT ';
|
||||
}
|
||||
|
||||
if(!empty($def['primary_key']) && $def['primary_key'])
|
||||
{
|
||||
$pks[] = '`' . $colName . '`';
|
||||
}
|
||||
|
||||
$defs[] = $add;
|
||||
}
|
||||
|
||||
if(count($pks))
|
||||
{
|
||||
$defs[] = 'PRIMARY KEY (' . implode(', ', $pks) . ')';
|
||||
}
|
||||
|
||||
$stmt = 'CREATE TABLE `' . $tbl . '` (' . PHP_EOL;
|
||||
$stmt .= implode(", \n", $defs);
|
||||
|
||||
$stmt .= PHP_EOL . ') ENGINE=InnoDB DEFAULT CHARSET=utf8';
|
||||
$stmt .= PHP_EOL;
|
||||
|
||||
$this->_todo['create'][] = $stmt;
|
||||
|
||||
foreach($idxs as $name => $idx)
|
||||
{
|
||||
$this->_addIndex($tbl, $name, $idx);
|
||||
}
|
||||
|
||||
foreach($fks as $name => $fk)
|
||||
{
|
||||
$this->_addFk($tbl, $name, $fk);
|
||||
}
|
||||
}
|
||||
|
||||
protected function _updateColumns($tableName, $table, $columns)
|
||||
{
|
||||
$currentColumns = $table['columns'];
|
||||
|
||||
while($column = array_shift($currentColumns))
|
||||
{
|
||||
if(!array_key_exists($column['name'], $columns))
|
||||
{
|
||||
$this->_todo['alter'][$tableName.'.'.$column['name']] = 'ALTER TABLE `' . $tableName . '` DROP COLUMN `' . $column['name'] . '`';
|
||||
}
|
||||
else
|
||||
{
|
||||
$model = $columns[$column['name']];
|
||||
|
||||
$model['nullable'] = !isset($model['nullable']) ? false : $model['nullable'];
|
||||
$model['default'] = !isset($model['default']) ? false : $model['default'];
|
||||
$model['auto_increment'] = !isset($model['auto_increment']) ? false : $model['auto_increment'];
|
||||
$model['primary_key'] = !isset($model['primary_key']) ? false : $model['primary_key'];
|
||||
$column['is_primary_key'] = !isset($column['is_primary_key']) ? false : $column['is_primary_key'];
|
||||
|
||||
if( $column['type'] != $model['type'] ||
|
||||
($column['length'] != $model['length'] && !in_array($model['type'], array('text', 'longtext', 'mediumtext', 'date', 'datetime', 'float'))) ||
|
||||
$column['null'] != $model['nullable'] ||
|
||||
$column['default'] != $model['default'] ||
|
||||
$column['auto'] != $model['auto_increment'])
|
||||
{
|
||||
$this->_updateColumn($tableName, $column['name'], $column['name'], $model);
|
||||
}
|
||||
}
|
||||
|
||||
unset($columns[$column['name']]);
|
||||
}
|
||||
|
||||
if(count($columns))
|
||||
{
|
||||
foreach($columns as $name => $model)
|
||||
{
|
||||
// Check if we're renaming a column:
|
||||
if(isset($model['rename']))
|
||||
{
|
||||
unset($this->_todo['alter'][$tableName.'.'.$model['rename']]);
|
||||
$this->_updateColumn($tableName, $model['rename'], $name, $model);
|
||||
continue;
|
||||
}
|
||||
|
||||
// New column
|
||||
$add = '`' . $name . '` ' . $model['type'];;
|
||||
switch($model['type'])
|
||||
{
|
||||
case 'text':
|
||||
case 'longtext':
|
||||
case 'mediumtext':
|
||||
case 'date':
|
||||
case 'datetime':
|
||||
case 'float':
|
||||
$add .= '';
|
||||
break;
|
||||
|
||||
default:
|
||||
$add .= !empty($model['length']) ? '(' . $model['length'] . ')' : '';
|
||||
break;
|
||||
}
|
||||
|
||||
if(empty($model['nullable']) || !$model['nullable'])
|
||||
{
|
||||
$add .= ' NOT NULL ';
|
||||
}
|
||||
|
||||
if(!empty($model['default']))
|
||||
{
|
||||
$add .= ' DEFAULT ' . (is_numeric($model['default']) ? $model['default'] : '\'' . $model['default'] . '\'');
|
||||
}
|
||||
|
||||
if(!empty($model['auto_increment']) && $model['auto_increment'])
|
||||
{
|
||||
$add .= ' AUTO_INCREMENT ';
|
||||
}
|
||||
|
||||
if(!empty($model['primary_key']) && $model['primary_key'] && !isset($table['indexes']['PRIMARY']))
|
||||
{
|
||||
$add .= ' PRIMARY KEY ';
|
||||
}
|
||||
|
||||
$this->_todo['alter'][] = 'ALTER TABLE `' . $tableName . '` ADD COLUMN ' . $add;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function _updateColumn($tableName, $prevName, $newName, $model)
|
||||
{
|
||||
$add = '`' . $newName . '` ' . $model['type'];;
|
||||
switch($model['type'])
|
||||
{
|
||||
case 'text':
|
||||
case 'longtext':
|
||||
case 'mediumtext':
|
||||
case 'date':
|
||||
case 'datetime':
|
||||
case 'float':
|
||||
$add .= '';
|
||||
break;
|
||||
|
||||
default:
|
||||
$add .= !empty($model['length']) ? '(' . $model['length'] . ')' : '';
|
||||
break;
|
||||
}
|
||||
|
||||
if(empty($model['nullable']) || !$model['nullable'])
|
||||
{
|
||||
$add .= ' NOT NULL ';
|
||||
}
|
||||
|
||||
if(!empty($model['default']))
|
||||
{
|
||||
$add .= ' DEFAULT ' . (is_numeric($model['default']) ? $model['default'] : '\'' . $model['default'] . '\'');
|
||||
}
|
||||
|
||||
if(!empty($model['auto_increment']) && $model['auto_increment'])
|
||||
{
|
||||
$add .= ' AUTO_INCREMENT ';
|
||||
}
|
||||
|
||||
$this->_todo['alter'][] = 'ALTER TABLE `' . $tableName . '` CHANGE COLUMN `' . $prevName . '` ' . $add;
|
||||
}
|
||||
|
||||
protected function _updateRelationships($tableName, $table, $foreignKeys)
|
||||
{
|
||||
$current = $table['relationships']['toOne'];
|
||||
|
||||
while($foreignKey = array_shift($current))
|
||||
{
|
||||
if(!array_key_exists($foreignKey['fk_name'], $foreignKeys))
|
||||
{
|
||||
$this->_dropFk($tableName, $foreignKey['fk_name']);
|
||||
}
|
||||
elseif( $foreignKey['from_col'] != $foreignKeys[$foreignKey['fk_name']]['local_col'] ||
|
||||
$foreignKey['table'] != $foreignKeys[$foreignKey['fk_name']]['table'] ||
|
||||
$foreignKey['col'] != $foreignKeys[$foreignKey['fk_name']]['col'] ||
|
||||
$foreignKey['fk_update'] != $foreignKeys[$foreignKey['fk_name']]['update'] ||
|
||||
$foreignKey['fk_delete'] != $foreignKeys[$foreignKey['fk_name']]['delete'])
|
||||
{
|
||||
$this->_alterFk($tableName, $foreignKey['fk_name'], $foreignKeys[$foreignKey['fk_name']]);
|
||||
}
|
||||
|
||||
unset($foreignKeys[$foreignKey['fk_name']]);
|
||||
}
|
||||
|
||||
if(count($foreignKeys))
|
||||
{
|
||||
foreach($foreignKeys as $name => $foreignKey)
|
||||
{
|
||||
// New column
|
||||
$this->_addFk($tableName, $name, $foreignKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function _updateIndexes($tableName, $table, $indexes)
|
||||
{
|
||||
$current = $table['indexes'];
|
||||
|
||||
while($index = array_shift($current))
|
||||
{
|
||||
if(!array_key_exists($index['name'], $indexes))
|
||||
{
|
||||
$this->_dropIndex($tableName, $index['name']);
|
||||
}
|
||||
elseif( $index['unique'] != $indexes[$index['name']]['unique'] ||
|
||||
$index['columns'] != $indexes[$index['name']]['columns'])
|
||||
{
|
||||
$this->_alterIndex($tableName, $index['name'], $index);
|
||||
}
|
||||
|
||||
unset($indexes[$index['name']]);
|
||||
}
|
||||
|
||||
if(count($indexes))
|
||||
{
|
||||
foreach($indexes as $name => $index)
|
||||
{
|
||||
if($name == 'PRIMARY')
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// New index
|
||||
$this->_addIndex($tableName, $name, $index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function _addIndex($table, $name, $idx, $stage = 'add_index')
|
||||
{
|
||||
if($name == 'PRIMARY')
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$q = 'CREATE ' . (isset($idx['unique']) && $idx['unique'] ? 'UNIQUE' : '') . ' INDEX `' . $name . '` ON `' . $table . '` (' . $idx['columns'] . ')';
|
||||
|
||||
$this->_todo[$stage][] = $q;
|
||||
}
|
||||
|
||||
protected function _alterIndex($table, $name, $idx, $stage = 'index')
|
||||
{
|
||||
$this->_dropIndex($table, $name, $stage);
|
||||
$this->_addIndex($table, $name, $idx, $stage);
|
||||
}
|
||||
|
||||
protected function _dropIndex($table, $idxName, $stage = 'drop_index')
|
||||
{
|
||||
if($idxName == 'PRIMARY')
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
$q = 'DROP INDEX `' . $idxName . '` ON `' . $table . '`';
|
||||
$this->_todo[$stage][] = $q;
|
||||
}
|
||||
|
||||
protected function _addFk($table, $name, $fk)
|
||||
{
|
||||
$q = 'ALTER TABLE `' . $table . '` ADD CONSTRAINT `' . $name . '` FOREIGN KEY (`' . $fk['local_col'] . '`) REFERENCES `'.$fk['table'].'` (`'.$fk['col'].'`)';
|
||||
|
||||
if(!empty($fk['delete']))
|
||||
{
|
||||
$q .= ' ON DELETE ' . $fk['delete'] . ' ';
|
||||
}
|
||||
|
||||
if(!empty($fk['update']))
|
||||
{
|
||||
$q .= ' ON UPDATE ' . $fk['update'] . ' ';
|
||||
}
|
||||
|
||||
$this->_todo['add_fk'][] = $q;
|
||||
}
|
||||
|
||||
protected function _alterFk($table, $name, $fk)
|
||||
{
|
||||
$this->_dropFk($table, $name);
|
||||
$this->_addFk($table, $name, $fk);
|
||||
}
|
||||
|
||||
protected function _dropFk($table, $name)
|
||||
{
|
||||
$q = 'ALTER TABLE `'.$table.'` DROP FOREIGN KEY `' . $name . '`';
|
||||
$this->_todo['drop_fk'][] = $q;
|
||||
}
|
||||
}
|
||||
258
src/B8Framework/Database/Map.php
Executable file
258
src/B8Framework/Database/Map.php
Executable file
|
|
@ -0,0 +1,258 @@
|
|||
<?php
|
||||
|
||||
namespace b8\Database;
|
||||
use b8\Database;
|
||||
|
||||
class Map
|
||||
{
|
||||
protected $_db = null;
|
||||
protected $_tables = array();
|
||||
|
||||
public function __construct(Database $db)
|
||||
{
|
||||
$this->_db = $db;
|
||||
}
|
||||
|
||||
public function generate()
|
||||
{
|
||||
$tables = $this->_getTables();
|
||||
|
||||
|
||||
foreach($tables as $table)
|
||||
{
|
||||
$this->_tables[$table] = array();
|
||||
$this->_tables[$table]['php_name'] = $this->_generatePhpName($table);
|
||||
}
|
||||
|
||||
$this->_getRelationships();
|
||||
$this->_getColumns();
|
||||
$this->_getIndexes();
|
||||
|
||||
return $this->_tables;
|
||||
}
|
||||
|
||||
protected function _getTables()
|
||||
{
|
||||
$details = $this->_db->getDetails();
|
||||
|
||||
$rtn = array();
|
||||
|
||||
foreach($this->_db->query('SHOW TABLES')->fetchAll(\PDO::FETCH_ASSOC) as $tbl)
|
||||
{
|
||||
$rtn[] = $tbl['Tables_in_' . $details['db']];
|
||||
}
|
||||
|
||||
return $rtn;
|
||||
}
|
||||
|
||||
protected function _getRelationships()
|
||||
{
|
||||
foreach($this->_tables as $table => $t)
|
||||
{
|
||||
$res = $this->_db->query('SHOW CREATE TABLE `'.$table.'`')->fetchAll(\PDO::FETCH_ASSOC);
|
||||
|
||||
foreach($res as $r)
|
||||
{
|
||||
$str = $r['Create Table'];
|
||||
|
||||
$matches = array();
|
||||
if(preg_match_all('/CONSTRAINT\s+\`([a-zA-Z0-9_]+)\`\s+FOREIGN\s+KEY\s+\(\`([a-zA-Z0-9_]+)\`\)\s+REFERENCES\s+\`([a-zA-Z0-9_]+)\`\s+\(\`([a-zA-Z0-9_]+)\`\)(\s+ON (DELETE|UPDATE) (SET NULL|NO ACTION|CASCADE|RESTRICT))?(\s+ON (DELETE|UPDATE) (SET NULL|NO ACTION|CASCADE|RESTRICT))?/', $str, $matches))
|
||||
{
|
||||
for($i = 0; $i < count($matches[0]); $i++)
|
||||
{
|
||||
$fromTable = $table;
|
||||
$fromCol = $matches[2][$i];
|
||||
$toTable = $matches[3][$i];
|
||||
$toCol = $matches[4][$i];
|
||||
$fkName = $matches[1][$i];
|
||||
$fk = array();
|
||||
|
||||
if(isset($matches[6][$i]))
|
||||
{
|
||||
$fk[$matches[6][$i]] = $matches[7][$i];
|
||||
}
|
||||
|
||||
if(isset($matches[9][$i]))
|
||||
{
|
||||
$fk[$matches[9][$i]] = $matches[10][$i];
|
||||
}
|
||||
|
||||
$fk['UPDATE'] = empty($fk['UPDATE']) ? '' : $fk['UPDATE'];
|
||||
$fk['DELETE'] = empty($fk['DELETE']) ? '' : $fk['DELETE'];
|
||||
|
||||
if(isset($this->_tables[$fromTable]) && isset($this->_tables[$toTable]))
|
||||
{
|
||||
$phpName = $this->_generateFkName($fromCol, $this->_tables[$fromTable]['php_name']);
|
||||
|
||||
$this->_tables[$fromTable]['relationships']['toOne'][$fromCol] = array('fk_name' => $fkName, 'fk_delete' => $fk['DELETE'], 'fk_update' => $fk['UPDATE'], 'table_php_name' => $this->_tables[$toTable]['php_name'], 'from_col_php' => $this->_generatePhpName($fromCol), 'from_col' => $fromCol, 'php_name' => $phpName, 'table' => $toTable, 'col' => $toCol, 'col_php' => $this->_generatePhpName($toCol));
|
||||
|
||||
$phpName = $this->_generateFkName($fromCol, $this->_tables[$fromTable]['php_name']) . $this->_tables[$fromTable]['php_name'].'s';
|
||||
$this->_tables[$toTable]['relationships']['toMany'][] = array('from_col_php' => $this->_generatePhpName($fromCol), 'php_name' => $phpName, 'thisCol' => $toCol, 'table' => $fromTable, 'table_php' => $this->_generatePhpName($fromTable), 'fromCol' => $fromCol, 'col_php' => $this->_generatePhpName($toCol));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function _getColumns()
|
||||
{
|
||||
foreach($this->_tables as $key => &$val)
|
||||
{
|
||||
$cols = array();
|
||||
foreach($this->_db->query('DESCRIBE `' . $key . '`')->fetchAll(\PDO::FETCH_ASSOC) as $column)
|
||||
{
|
||||
$col = $this->_processColumn(array(), $column, $val);
|
||||
$cols[$col['name']] = $col;
|
||||
}
|
||||
|
||||
$val['columns'] = $cols;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected function _getIndexes()
|
||||
{
|
||||
foreach($this->_tables as $key => &$val)
|
||||
{
|
||||
$indexes = array();
|
||||
|
||||
foreach($this->_db->query('SHOW INDEXES FROM `' . $key . '`')->fetchAll(\PDO::FETCH_ASSOC) as $idx)
|
||||
{
|
||||
if(!isset($indexes[$idx['Key_name']]))
|
||||
{
|
||||
$indexes[$idx['Key_name']] = array();
|
||||
$indexes[$idx['Key_name']]['name'] = $idx['Key_name'];
|
||||
$indexes[$idx['Key_name']]['unique'] = ($idx['Non_unique'] == '0') ? true : false;
|
||||
$indexes[$idx['Key_name']]['columns'] = array();
|
||||
}
|
||||
|
||||
$indexes[$idx['Key_name']]['columns'][$idx['Seq_in_index']] = $idx['Column_name'];
|
||||
}
|
||||
|
||||
$indexes = array_map(function($idx)
|
||||
{
|
||||
ksort($idx['columns']);
|
||||
$idx['columns'] = implode(', ', $idx['columns']);
|
||||
|
||||
return $idx;
|
||||
}, $indexes);
|
||||
|
||||
$val['indexes'] = $indexes;
|
||||
}
|
||||
}
|
||||
|
||||
protected function _processColumn($col, $column, &$table)
|
||||
{
|
||||
$col['name'] = $column['Field'];
|
||||
$col['php_name']= $this->_generatePhpName($col['name']);
|
||||
$matches = array();
|
||||
|
||||
preg_match('/^([a-zA-Z]+)(\()?([0-9\,]+)?(\))?/', $column['Type'], $matches);
|
||||
|
||||
$col['type'] = strtolower($matches[1]);
|
||||
|
||||
if(isset($matches[3]))
|
||||
{
|
||||
$col['length'] = $matches[3];
|
||||
}
|
||||
|
||||
$col['null'] = strtolower($column['Null']) == 'yes' ? true : false;
|
||||
$col['auto'] = strtolower($column['Extra']) == 'auto_increment' ? true : false;
|
||||
|
||||
if ($column['Default'] == 'NULL' || is_null($column['Default'])) {
|
||||
$col['default_is_null'] = true;
|
||||
} else {
|
||||
$col['default_is_null'] = false;
|
||||
$col['default'] = $column['Default'];
|
||||
}
|
||||
|
||||
if(!empty($column['Key']))
|
||||
{
|
||||
if($column['Key'] == 'PRI')
|
||||
{
|
||||
$col['is_primary_key'] = true;
|
||||
$table['primary_key'] = array('column' => $col['name'], 'php_name' => $col['php_name']);
|
||||
}
|
||||
|
||||
if($column['Key'] == 'PRI' || $column['Key'] == 'UNI')
|
||||
{
|
||||
$col['unique_indexed'] = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
$col['many_indexed'] = true;
|
||||
}
|
||||
}
|
||||
|
||||
$col['validate']= array();
|
||||
|
||||
if(!$col['null'])
|
||||
{
|
||||
$col['validate_null'] = true;
|
||||
}
|
||||
|
||||
switch($col['type'])
|
||||
{
|
||||
case 'tinyint':
|
||||
case 'smallint':
|
||||
case 'int':
|
||||
case 'mediumint':
|
||||
case 'bigint':
|
||||
$col['php_type'] = 'int';
|
||||
$col['to_php'] = '_sqlToInt';
|
||||
$col['validate_int']= true;
|
||||
break;
|
||||
|
||||
case 'float':
|
||||
case 'decimal':
|
||||
$col['php_type'] = 'float';
|
||||
$col['to_php'] = '_sqlToFloat';
|
||||
$col['validate_float'] = true;
|
||||
break;
|
||||
|
||||
case 'datetime':
|
||||
case 'date':
|
||||
$col['php_type'] = 'DateTime';
|
||||
$col['to_php'] = '_sqlToDateTime';
|
||||
$col['to_sql'] = '_dateTimeToSql';
|
||||
$col['validate_date'] = true;
|
||||
break;
|
||||
|
||||
case 'varchar':
|
||||
case 'text':
|
||||
default:
|
||||
$col['php_type'] = 'string';
|
||||
$col['validate_string'] = true;
|
||||
break;
|
||||
}
|
||||
|
||||
return $col;
|
||||
}
|
||||
|
||||
protected function _generatePhpName($sqlName)
|
||||
{
|
||||
$rtn = $sqlName;
|
||||
$rtn = str_replace('_', ' ', $rtn);
|
||||
$rtn = ucwords($rtn);
|
||||
$rtn = str_replace(' ', '', $rtn);
|
||||
|
||||
return $rtn;
|
||||
}
|
||||
|
||||
protected function _generateFkName($sqlName, $tablePhpName)
|
||||
{
|
||||
$fkMethod = substr($sqlName, 0, strripos($sqlName, '_'));
|
||||
|
||||
if(empty($fkMethod))
|
||||
{
|
||||
$fkMethod = (substr(strtolower($sqlName), -2) == 'id') ? substr($sqlName, 0, -2) : $tablePhpName;
|
||||
}
|
||||
|
||||
$fkMethod = str_replace('_', ' ', $fkMethod);
|
||||
$fkMethod = ucwords($fkMethod);
|
||||
$fkMethod = str_replace(' ', '', $fkMethod);
|
||||
|
||||
return $fkMethod;
|
||||
}
|
||||
}
|
||||
24
src/B8Framework/Exception/HttpException.php
Executable file
24
src/B8Framework/Exception/HttpException.php
Executable file
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
namespace b8\Exception;
|
||||
|
||||
class HttpException extends \Exception
|
||||
{
|
||||
protected $errorCode = 500;
|
||||
protected $statusMessage = 'Internal Server Error';
|
||||
|
||||
public function getErrorCode()
|
||||
{
|
||||
return $this->errorCode;
|
||||
}
|
||||
|
||||
public function getStatusMessage()
|
||||
{
|
||||
return $this->statusMessage;
|
||||
}
|
||||
|
||||
public function getHttpHeader()
|
||||
{
|
||||
return 'HTTP/1.1 ' . $this->errorCode . ' ' . $this->statusMessage;
|
||||
}
|
||||
}
|
||||
10
src/B8Framework/Exception/HttpException/BadRequestException.php
Executable file
10
src/B8Framework/Exception/HttpException/BadRequestException.php
Executable file
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace b8\Exception\HttpException;
|
||||
use b8\Exception\HttpException;
|
||||
|
||||
class BadRequestException extends HttpException
|
||||
{
|
||||
protected $errorCode = 400;
|
||||
protected $statusMessage = 'Bad Request';
|
||||
}
|
||||
10
src/B8Framework/Exception/HttpException/ForbiddenException.php
Executable file
10
src/B8Framework/Exception/HttpException/ForbiddenException.php
Executable file
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace b8\Exception\HttpException;
|
||||
use b8\Exception\HttpException;
|
||||
|
||||
class ForbiddenException extends HttpException
|
||||
{
|
||||
protected $errorCode = 403;
|
||||
protected $statusMessage = 'Forbidden';
|
||||
}
|
||||
10
src/B8Framework/Exception/HttpException/NotAuthorizedException.php
Executable file
10
src/B8Framework/Exception/HttpException/NotAuthorizedException.php
Executable file
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace b8\Exception\HttpException;
|
||||
use b8\Exception\HttpException;
|
||||
|
||||
class NotAuthorizedException extends HttpException
|
||||
{
|
||||
protected $errorCode = 401;
|
||||
protected $statusMessage = 'Not Authorized';
|
||||
}
|
||||
10
src/B8Framework/Exception/HttpException/NotFoundException.php
Executable file
10
src/B8Framework/Exception/HttpException/NotFoundException.php
Executable file
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace b8\Exception\HttpException;
|
||||
use b8\Exception\HttpException;
|
||||
|
||||
class NotFoundException extends HttpException
|
||||
{
|
||||
protected $errorCode = 404;
|
||||
protected $statusMessage = 'Not Found';
|
||||
}
|
||||
8
src/B8Framework/Exception/HttpException/ServerErrorException.php
Executable file
8
src/B8Framework/Exception/HttpException/ServerErrorException.php
Executable file
|
|
@ -0,0 +1,8 @@
|
|||
<?php
|
||||
|
||||
namespace b8\Exception\HttpException;
|
||||
use b8\Exception\HttpException;
|
||||
|
||||
class ServerErrorException extends HttpException
|
||||
{
|
||||
}
|
||||
10
src/B8Framework/Exception/HttpException/ValidationException.php
Executable file
10
src/B8Framework/Exception/HttpException/ValidationException.php
Executable file
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
|
||||
namespace b8\Exception\HttpException;
|
||||
use b8\Exception\HttpException;
|
||||
|
||||
class ValidationException extends HttpException
|
||||
{
|
||||
protected $errorCode = 400;
|
||||
protected $statusMessage = 'Bad Request';
|
||||
}
|
||||
44
src/B8Framework/Form.php
Executable file
44
src/B8Framework/Form.php
Executable file
|
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
namespace b8;
|
||||
use b8\Form\FieldSet,
|
||||
b8\View;
|
||||
|
||||
class Form extends FieldSet
|
||||
{
|
||||
protected $_action = '';
|
||||
protected $_method = 'POST';
|
||||
|
||||
public function getAction()
|
||||
{
|
||||
return $this->_action;
|
||||
}
|
||||
|
||||
public function setAction($action)
|
||||
{
|
||||
$this->_action = $action;
|
||||
}
|
||||
|
||||
public function getMethod()
|
||||
{
|
||||
return $this->_method;
|
||||
}
|
||||
|
||||
public function setMethod($method)
|
||||
{
|
||||
$this->_method = $method;
|
||||
}
|
||||
|
||||
protected function _onPreRender(View &$view)
|
||||
{
|
||||
$view->action = $this->getAction();
|
||||
$view->method = $this->getMethod();
|
||||
|
||||
parent::_onPreRender($view);
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
return $this->render();
|
||||
}
|
||||
}
|
||||
7
src/B8Framework/Form/ControlGroup.php
Executable file
7
src/B8Framework/Form/ControlGroup.php
Executable file
|
|
@ -0,0 +1,7 @@
|
|||
<?php
|
||||
|
||||
namespace b8\Form;
|
||||
|
||||
class ControlGroup extends FieldSet
|
||||
{
|
||||
}
|
||||
118
src/B8Framework/Form/Element.php
Executable file
118
src/B8Framework/Form/Element.php
Executable file
|
|
@ -0,0 +1,118 @@
|
|||
<?php
|
||||
|
||||
namespace b8\Form;
|
||||
|
||||
use b8\View;
|
||||
use b8\Config;
|
||||
|
||||
abstract class Element
|
||||
{
|
||||
protected $_name;
|
||||
protected $_id;
|
||||
protected $_label;
|
||||
protected $_css;
|
||||
protected $_ccss;
|
||||
protected $_parent;
|
||||
|
||||
public function __construct($name = null)
|
||||
{
|
||||
if(!is_null($name))
|
||||
{
|
||||
$this->setName($name);
|
||||
}
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
return $this->_name;
|
||||
}
|
||||
|
||||
public function setName($name)
|
||||
{
|
||||
$this->_name = strtolower(preg_replace('/([^a-zA-Z0-9_\-])/', '', $name));
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getId()
|
||||
{
|
||||
return !$this->_id ? 'element-'.$this->_name : $this->_id;
|
||||
}
|
||||
|
||||
public function setId($id)
|
||||
{
|
||||
$this->_id = $id;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getLabel()
|
||||
{
|
||||
return $this->_label;
|
||||
}
|
||||
|
||||
public function setLabel($label)
|
||||
{
|
||||
$this->_label = $label;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getClass()
|
||||
{
|
||||
return $this->_css;
|
||||
}
|
||||
|
||||
public function setClass($class)
|
||||
{
|
||||
$this->_css = $class;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getContainerClass()
|
||||
{
|
||||
return $this->_ccss;
|
||||
}
|
||||
|
||||
public function setContainerClass($class)
|
||||
{
|
||||
$this->_ccss = $class;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setParent(Element $parent)
|
||||
{
|
||||
$this->_parent = $parent;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function render($viewFile = null)
|
||||
{
|
||||
$viewPath = Config::getInstance()->get('b8.view.path');
|
||||
|
||||
if(is_null($viewFile))
|
||||
{
|
||||
$class = explode('\\', get_called_class());
|
||||
$viewFile = end($class);
|
||||
}
|
||||
|
||||
if(file_exists($viewPath . 'Form/' . $viewFile . '.phtml'))
|
||||
{
|
||||
$view = new View('Form/' . $viewFile);
|
||||
}
|
||||
else
|
||||
{
|
||||
$view = new View($viewFile, B8_PATH . 'Form/View/');
|
||||
}
|
||||
|
||||
$view->name = $this->getName();
|
||||
$view->id = $this->getId();
|
||||
$view->label = $this->getLabel();
|
||||
$view->css = $this->getClass();
|
||||
$view->ccss = $this->getContainerClass();
|
||||
$view->parent = $this->_parent;
|
||||
|
||||
$this->_onPreRender($view);
|
||||
|
||||
return $view->render();
|
||||
}
|
||||
|
||||
abstract protected function _onPreRender(View &$view);
|
||||
}
|
||||
19
src/B8Framework/Form/Element/Button.php
Executable file
19
src/B8Framework/Form/Element/Button.php
Executable file
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace b8\Form\Element;
|
||||
use b8\Form\Input,
|
||||
b8\View;
|
||||
|
||||
class Button extends Input
|
||||
{
|
||||
public function validate()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function _onPreRender(View &$view)
|
||||
{
|
||||
parent::_onPreRender($view);
|
||||
$view->type = 'button';
|
||||
}
|
||||
}
|
||||
48
src/B8Framework/Form/Element/Checkbox.php
Executable file
48
src/B8Framework/Form/Element/Checkbox.php
Executable file
|
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
|
||||
namespace b8\Form\Element;
|
||||
use b8\View,
|
||||
b8\Form\Input;
|
||||
|
||||
class Checkbox extends Input
|
||||
{
|
||||
protected $_checked;
|
||||
protected $_checkedValue;
|
||||
|
||||
public function getCheckedValue()
|
||||
{
|
||||
return $this->_checkedValue;
|
||||
}
|
||||
|
||||
public function setCheckedValue($value)
|
||||
{
|
||||
$this->_checkedValue = $value;
|
||||
}
|
||||
|
||||
public function setValue($value)
|
||||
{
|
||||
if(is_bool($value) && $value == true)
|
||||
{
|
||||
$this->_value = $this->getCheckedValue();
|
||||
$this->_checked = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if($value == $this->getCheckedValue())
|
||||
{
|
||||
$this->_value = $this->getCheckedValue();
|
||||
$this->_checked = true;
|
||||
return;
|
||||
}
|
||||
|
||||
$this->_value = $value;
|
||||
$this->_checked = false;
|
||||
}
|
||||
|
||||
public function _onPreRender(View &$view)
|
||||
{
|
||||
parent::_onPreRender($view);
|
||||
$view->checkedValue = $this->getCheckedValue();
|
||||
$view->checked = $this->_checked;
|
||||
}
|
||||
}
|
||||
8
src/B8Framework/Form/Element/CheckboxGroup.php
Executable file
8
src/B8Framework/Form/Element/CheckboxGroup.php
Executable file
|
|
@ -0,0 +1,8 @@
|
|||
<?php
|
||||
|
||||
namespace b8\Form\Element;
|
||||
use b8\Form\FieldSet;
|
||||
|
||||
class CheckboxGroup extends FieldSet
|
||||
{
|
||||
}
|
||||
28
src/B8Framework/Form/Element/Csrf.php
Executable file
28
src/B8Framework/Form/Element/Csrf.php
Executable file
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
namespace b8\Form\Element;
|
||||
use b8\Form\Element\Hidden,
|
||||
b8\View;
|
||||
|
||||
class Csrf extends Hidden
|
||||
{
|
||||
protected $_rows = 4;
|
||||
|
||||
public function validate()
|
||||
{
|
||||
if($this->_value != $_COOKIE[$this->getName()])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function _onPreRender(View &$view)
|
||||
{
|
||||
parent::_onPreRender($view);
|
||||
$csrf = md5(microtime(true));
|
||||
$view->csrf = $csrf;
|
||||
setcookie($this->getName(), $csrf);
|
||||
}
|
||||
}
|
||||
18
src/B8Framework/Form/Element/Email.php
Executable file
18
src/B8Framework/Form/Element/Email.php
Executable file
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
namespace b8\Form\Element;
|
||||
use b8\View;
|
||||
|
||||
class Email extends Text
|
||||
{
|
||||
public function render($viewFile = null)
|
||||
{
|
||||
return parent::render(($viewFile ? $viewFile : 'Text'));
|
||||
}
|
||||
|
||||
protected function _onPreRender(View &$view)
|
||||
{
|
||||
parent::_onPreRender($view);
|
||||
$view->type = 'email';
|
||||
}
|
||||
}
|
||||
9
src/B8Framework/Form/Element/Hidden.php
Executable file
9
src/B8Framework/Form/Element/Hidden.php
Executable file
|
|
@ -0,0 +1,9 @@
|
|||
<?php
|
||||
|
||||
namespace b8\Form\Element;
|
||||
use b8\Form\Input,
|
||||
b8\View;
|
||||
|
||||
class Hidden extends Input
|
||||
{
|
||||
}
|
||||
19
src/B8Framework/Form/Element/Password.php
Executable file
19
src/B8Framework/Form/Element/Password.php
Executable file
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace b8\Form\Element;
|
||||
use b8\Form\Element\Text,
|
||||
b8\View;
|
||||
|
||||
class Password extends Text
|
||||
{
|
||||
public function render($viewFile = null)
|
||||
{
|
||||
return parent::render(($viewFile ? $viewFile : 'Text'));
|
||||
}
|
||||
|
||||
protected function _onPreRender(View &$view)
|
||||
{
|
||||
parent::_onPreRender($view);
|
||||
$view->type = 'password';
|
||||
}
|
||||
}
|
||||
8
src/B8Framework/Form/Element/Radio.php
Executable file
8
src/B8Framework/Form/Element/Radio.php
Executable file
|
|
@ -0,0 +1,8 @@
|
|||
<?php
|
||||
|
||||
namespace b8\Form\Element;
|
||||
use b8\Form\Element\Select;
|
||||
|
||||
class Radio extends Select
|
||||
{
|
||||
}
|
||||
21
src/B8Framework/Form/Element/Select.php
Executable file
21
src/B8Framework/Form/Element/Select.php
Executable file
|
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
namespace b8\Form\Element;
|
||||
use b8\View,
|
||||
b8\Form\Input;
|
||||
|
||||
class Select extends Input
|
||||
{
|
||||
protected $_options = array();
|
||||
|
||||
public function setOptions(array $options)
|
||||
{
|
||||
$this->_options = $options;
|
||||
}
|
||||
|
||||
protected function _onPreRender(View &$view)
|
||||
{
|
||||
parent::_onPreRender($view);
|
||||
$view->options = $this->_options;
|
||||
}
|
||||
}
|
||||
21
src/B8Framework/Form/Element/Submit.php
Executable file
21
src/B8Framework/Form/Element/Submit.php
Executable file
|
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
namespace b8\Form\Element;
|
||||
use b8\Form\Element\Button,
|
||||
b8\View;
|
||||
|
||||
class Submit extends Button
|
||||
{
|
||||
protected $_value = 'Submit';
|
||||
|
||||
public function render($viewFile = null)
|
||||
{
|
||||
return parent::render(($viewFile ? $viewFile : 'Button'));
|
||||
}
|
||||
|
||||
protected function _onPreRender(View &$view)
|
||||
{
|
||||
parent::_onPreRender($view);
|
||||
$view->type = 'submit';
|
||||
}
|
||||
}
|
||||
14
src/B8Framework/Form/Element/Text.php
Executable file
14
src/B8Framework/Form/Element/Text.php
Executable file
|
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
|
||||
namespace b8\Form\Element;
|
||||
use b8\Form\Input,
|
||||
b8\View;
|
||||
|
||||
class Text extends Input
|
||||
{
|
||||
protected function _onPreRender(View &$view)
|
||||
{
|
||||
parent::_onPreRender($view);
|
||||
$view->type = 'text';
|
||||
}
|
||||
}
|
||||
26
src/B8Framework/Form/Element/TextArea.php
Executable file
26
src/B8Framework/Form/Element/TextArea.php
Executable file
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
namespace b8\Form\Element;
|
||||
use b8\Form\Element\Text,
|
||||
b8\View;
|
||||
|
||||
class TextArea extends Text
|
||||
{
|
||||
protected $_rows = 4;
|
||||
|
||||
public function getRows()
|
||||
{
|
||||
return $this->_rows;
|
||||
}
|
||||
|
||||
public function setRows($rows)
|
||||
{
|
||||
$this->_rows = $rows;
|
||||
}
|
||||
|
||||
protected function _onPreRender(View &$view)
|
||||
{
|
||||
parent::_onPreRender($view);
|
||||
$view->rows = $this->getRows();
|
||||
}
|
||||
}
|
||||
18
src/B8Framework/Form/Element/Url.php
Executable file
18
src/B8Framework/Form/Element/Url.php
Executable file
|
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
namespace b8\Form\Element;
|
||||
use b8\View;
|
||||
|
||||
class Url extends Text
|
||||
{
|
||||
public function render($viewFile = null)
|
||||
{
|
||||
return parent::render(($viewFile ? $viewFile : 'Text'));
|
||||
}
|
||||
|
||||
protected function _onPreRender(View &$view)
|
||||
{
|
||||
parent::_onPreRender($view);
|
||||
$view->type = 'url';
|
||||
}
|
||||
}
|
||||
114
src/B8Framework/Form/FieldSet.php
Executable file
114
src/B8Framework/Form/FieldSet.php
Executable file
|
|
@ -0,0 +1,114 @@
|
|||
<?php
|
||||
|
||||
namespace b8\Form;
|
||||
use b8\Form\Element,
|
||||
b8\Form\Input,
|
||||
b8\View;
|
||||
|
||||
class FieldSet extends Element
|
||||
{
|
||||
protected $_children = array();
|
||||
|
||||
public function getValues()
|
||||
{
|
||||
$rtn = array();
|
||||
|
||||
foreach($this->_children as $field)
|
||||
{
|
||||
if($field instanceof FieldSet)
|
||||
{
|
||||
$fieldName = $field->getName();
|
||||
|
||||
if(empty($fieldName))
|
||||
{
|
||||
$rtn = array_merge($rtn, $field->getValues());
|
||||
}
|
||||
else
|
||||
{
|
||||
$rtn[$fieldName] = $field->getValues();
|
||||
}
|
||||
}
|
||||
elseif($field instanceof Input)
|
||||
{
|
||||
if($field->getName())
|
||||
{
|
||||
$rtn[$field->getName()] = $field->getValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $rtn;
|
||||
}
|
||||
|
||||
public function setValues(array $values)
|
||||
{
|
||||
foreach($this->_children as $field)
|
||||
{
|
||||
if($field instanceof FieldSet)
|
||||
{
|
||||
$fieldName = $field->getName();
|
||||
|
||||
if(empty($fieldName) || !isset($values[$fieldName]))
|
||||
{
|
||||
$field->setValues($values);
|
||||
}
|
||||
else
|
||||
{
|
||||
$field->setValues($values[$fieldName]);
|
||||
}
|
||||
}
|
||||
elseif($field instanceof Input)
|
||||
{
|
||||
$fieldName = $field->getName();
|
||||
|
||||
if(isset($values[$fieldName]))
|
||||
{
|
||||
$field->setValue($values[$fieldName]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function addField(Element $field)
|
||||
{
|
||||
$this->_children[$field->getName()] = $field;
|
||||
$field->setParent($this);
|
||||
}
|
||||
|
||||
public function validate()
|
||||
{
|
||||
$rtn = true;
|
||||
|
||||
foreach($this->_children as $child)
|
||||
{
|
||||
if(!$child->validate())
|
||||
{
|
||||
$rtn = false;
|
||||
}
|
||||
}
|
||||
|
||||
return $rtn;
|
||||
}
|
||||
|
||||
protected function _onPreRender(View &$view)
|
||||
{
|
||||
$rendered = array();
|
||||
|
||||
foreach($this->_children as $child)
|
||||
{
|
||||
$rendered[] = $child->render();
|
||||
}
|
||||
|
||||
$view->children = $rendered;
|
||||
}
|
||||
|
||||
public function getChildren()
|
||||
{
|
||||
return $this->_children;
|
||||
}
|
||||
|
||||
public function getChild($fieldName)
|
||||
{
|
||||
return $this->_children[$fieldName];
|
||||
}
|
||||
}
|
||||
124
src/B8Framework/Form/Input.php
Executable file
124
src/B8Framework/Form/Input.php
Executable file
|
|
@ -0,0 +1,124 @@
|
|||
<?php
|
||||
|
||||
namespace b8\Form;
|
||||
use b8\Form\Element,
|
||||
b8\View;
|
||||
|
||||
class Input extends Element
|
||||
{
|
||||
protected $_required = false;
|
||||
protected $_pattern;
|
||||
protected $_validator;
|
||||
protected $_value;
|
||||
protected $_error;
|
||||
protected $_customError = false;
|
||||
|
||||
public static function create($name, $label, $required = false)
|
||||
{
|
||||
$el = new static();
|
||||
$el->setName($name);
|
||||
$el->setLabel($label);
|
||||
$el->setRequired($required);
|
||||
|
||||
return $el;
|
||||
}
|
||||
|
||||
public function getValue()
|
||||
{
|
||||
return $this->_value;
|
||||
}
|
||||
|
||||
public function setValue($value)
|
||||
{
|
||||
$this->_value = $value;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getRequired()
|
||||
{
|
||||
return $this->_required;
|
||||
}
|
||||
|
||||
public function setRequired($required)
|
||||
{
|
||||
$this->_required = (bool)$required;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getValidator()
|
||||
{
|
||||
return $this->_validator;
|
||||
}
|
||||
|
||||
public function setValidator($validator)
|
||||
{
|
||||
if(is_callable($validator) || $validator instanceof \Closure)
|
||||
{
|
||||
$this->_validator = $validator;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getPattern()
|
||||
{
|
||||
return $this->_pattern;
|
||||
}
|
||||
|
||||
public function setPattern($pattern)
|
||||
{
|
||||
$this->_pattern = $pattern;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function validate()
|
||||
{
|
||||
if($this->getRequired() && empty($this->_value))
|
||||
{
|
||||
$this->_error = $this->getLabel() . ' is required.';
|
||||
return false;
|
||||
}
|
||||
|
||||
if($this->getPattern() && !preg_match('/'.$this->getPattern().'/', $this->_value))
|
||||
{
|
||||
$this->_error = 'Invalid value entered.';
|
||||
return false;
|
||||
}
|
||||
|
||||
$validator = $this->getValidator();
|
||||
|
||||
if(is_callable($validator))
|
||||
{
|
||||
try
|
||||
{
|
||||
call_user_func_array($validator, array($this->_value));
|
||||
}
|
||||
catch(\Exception $ex)
|
||||
{
|
||||
$this->_error = $ex->getMessage();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->_customError) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function setError($message)
|
||||
{
|
||||
$this->_customError = true;
|
||||
$this->_error = $message;
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function _onPreRender(View &$view)
|
||||
{
|
||||
$view->value = $this->getValue();
|
||||
$view->error = $this->_error;
|
||||
$view->pattern = $this->_pattern;
|
||||
$view->required = $this->_required;
|
||||
}
|
||||
}
|
||||
1
src/B8Framework/Form/View/Button.phtml
Executable file
1
src/B8Framework/Form/View/Button.phtml
Executable file
|
|
@ -0,0 +1 @@
|
|||
<input class="btn <?php print $css; ?>" type="<?php print $type; ?>" value="<?php print $value; ?>">
|
||||
17
src/B8Framework/Form/View/Checkbox.phtml
Executable file
17
src/B8Framework/Form/View/Checkbox.phtml
Executable file
|
|
@ -0,0 +1,17 @@
|
|||
<?php if(!($parent instanceof \b8\Form\Element\CheckboxGroup)): ?>
|
||||
<div class="control-group <?php print $ccss ?> <?php print (isset($error) ? 'error' : ''); ?>">
|
||||
<div class="controls">
|
||||
<div class="checkbox">
|
||||
<?php endif; ?>
|
||||
<label class="checkbox <?php print $css; ?>" for="<?php print $id ?>">
|
||||
<input type="checkbox" id="<?php print $id; ?>" name="<?php print $name; ?>" value="<?php print $checkedValue; ?>" <?php print ($checked ? 'checked' : ''); ?> <?php print $required ? 'required' : '' ?>>
|
||||
<?php print $label; ?>
|
||||
</label>
|
||||
<?php if(isset($error)): ?>
|
||||
<span class="help-block"><?php print $error; ?></span>
|
||||
<?php endif; ?>
|
||||
<?php if(!($parent instanceof \b8\Form\Element\CheckboxGroup)): ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
11
src/B8Framework/Form/View/CheckboxGroup.phtml
Executable file
11
src/B8Framework/Form/View/CheckboxGroup.phtml
Executable file
|
|
@ -0,0 +1,11 @@
|
|||
<div class="control-group <?php print $css; ?>">
|
||||
<?php if($label): ?>
|
||||
<label class="control-label"><?php print $label; ?></label>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="controls">
|
||||
<?php foreach($children as $field): ?>
|
||||
<?php print $field; ?>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
6
src/B8Framework/Form/View/ControlGroup.phtml
Executable file
6
src/B8Framework/Form/View/ControlGroup.phtml
Executable file
|
|
@ -0,0 +1,6 @@
|
|||
<div class="control-group <?php print $css; ?>">
|
||||
|
||||
<?php foreach($children as $field): ?>
|
||||
<?php print $field; ?>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
1
src/B8Framework/Form/View/Csrf.phtml
Executable file
1
src/B8Framework/Form/View/Csrf.phtml
Executable file
|
|
@ -0,0 +1 @@
|
|||
<input type="hidden" id="<?php print $id; ?>" name="<?php print $name; ?>" value="<?php print $csrf; ?>">
|
||||
9
src/B8Framework/Form/View/FieldSet.phtml
Executable file
9
src/B8Framework/Form/View/FieldSet.phtml
Executable file
|
|
@ -0,0 +1,9 @@
|
|||
<fieldset class="row <?php print $css; ?>">
|
||||
<?php if($label): ?>
|
||||
<legend><?php print $label; ?></legend>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php foreach($children as $field): ?>
|
||||
<?php print $field; ?>
|
||||
<?php endforeach; ?>
|
||||
</fieldset>
|
||||
6
src/B8Framework/Form/View/Form.phtml
Executable file
6
src/B8Framework/Form/View/Form.phtml
Executable file
|
|
@ -0,0 +1,6 @@
|
|||
<form id="<?php print $id; ?>" class="<?php print $css; ?>" action="<?php print $action; ?>" method="<?php print $method; ?>">
|
||||
<?php foreach($children as $field): ?>
|
||||
<?php print $field; ?>
|
||||
<?php endforeach; ?>
|
||||
|
||||
</form>
|
||||
1
src/B8Framework/Form/View/Hidden.phtml
Executable file
1
src/B8Framework/Form/View/Hidden.phtml
Executable file
|
|
@ -0,0 +1 @@
|
|||
<input type="hidden" id="<?php print $id; ?>" name="<?php print $name; ?>" value="<?php print $value; ?>">
|
||||
18
src/B8Framework/Form/View/Radio.phtml
Executable file
18
src/B8Framework/Form/View/Radio.phtml
Executable file
|
|
@ -0,0 +1,18 @@
|
|||
<div id="<?php print $id; ?>" class="control-group <?php print $ccss; ?>">
|
||||
<?php if($label): ?>
|
||||
<label class="control-label"><?php print $label; ?></label>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="controls">
|
||||
<?php foreach($options as $val => $lbl): ?>
|
||||
<label class="radio" for="radio-<?php print $id; ?>-<?php print $val; ?>">
|
||||
<input type="radio" id="radio-<?php print $id; ?>-<?php print $val; ?>" class="<?php print $css; ?>" name="<?php print $name; ?>" value="<?php print $val; ?>" <?php print ($value == $val) ? ' checked="checked"' : ''; ?> <?php print $required ? 'required' : '' ?>>
|
||||
<?php print $lbl; ?>
|
||||
</label>
|
||||
<?php endforeach; ?>
|
||||
|
||||
<?php if(isset($error)): ?>
|
||||
<span class="help-block"><?php print $error; ?></span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
17
src/B8Framework/Form/View/Select.phtml
Executable file
17
src/B8Framework/Form/View/Select.phtml
Executable file
|
|
@ -0,0 +1,17 @@
|
|||
<div class="control-group <?php print $ccss; ?>">
|
||||
<?php if($label): ?>
|
||||
<label class="control-label" for="<?php print $id ?>"><?php print $label; ?></label>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="controls">
|
||||
<select id="<?php print $id; ?>" class="<?php print $css; ?>" name="<?php print $name; ?>">
|
||||
<?php foreach($options as $val => $lbl): ?>
|
||||
<option value="<?php print $val; ?>" <?php print ($value == $val) ? ' selected="selected"' : ''; ?>><?php print $lbl; ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
|
||||
<?php if(isset($error)): ?>
|
||||
<span class="help-block"><?php print $error; ?></span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
13
src/B8Framework/Form/View/Text.phtml
Executable file
13
src/B8Framework/Form/View/Text.phtml
Executable file
|
|
@ -0,0 +1,13 @@
|
|||
<div class="control-group <?php print $ccss; ?> <?php print (isset($error) ? 'error' : ''); ?>">
|
||||
<?php if($label): ?>
|
||||
<label class="control-label" for="<?php print $id ?>"><?php print $label; ?></label>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="controls">
|
||||
<input id="<?php print $id; ?>" type="<?php print $type; ?>" class="<?php print $css; ?>" name="<?php print $name; ?>" <?php print isset($value) ? ' value="' . $value . '"' : '' ?> <?php print isset($pattern) ? ' pattern="' . $pattern . '"' : '' ?> <?php print $required ? ' required' : '' ?>>
|
||||
|
||||
<?php if(isset($error)): ?>
|
||||
<span class="help-block"><?php print $error; ?></span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
13
src/B8Framework/Form/View/TextArea.phtml
Executable file
13
src/B8Framework/Form/View/TextArea.phtml
Executable file
|
|
@ -0,0 +1,13 @@
|
|||
<div class="control-group <?php print $ccss; ?> <?php print (isset($error) ? 'error' : ''); ?>">
|
||||
<?php if($label): ?>
|
||||
<label class="control-label" for="<?php print $id ?>"><?php print $label; ?></label>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="controls">
|
||||
<textarea rows="<?php print $rows; ?>" id="<?php print $id; ?>" class="<?php print $css; ?>" name="<?php print $name; ?>" <?php print $required ? ' required' : '' ?>><?php print isset($value) ? $value : '' ?></textarea>
|
||||
|
||||
<?php if(isset($error)): ?>
|
||||
<span class="help-block"><?php print $error; ?></span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
149
src/B8Framework/Http/Request.php
Executable file
149
src/B8Framework/Http/Request.php
Executable file
|
|
@ -0,0 +1,149 @@
|
|||
<?php
|
||||
|
||||
namespace b8\Http;
|
||||
|
||||
class Request
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $params = array();
|
||||
|
||||
/**
|
||||
* Request data.
|
||||
*/
|
||||
protected $data = array();
|
||||
|
||||
/**
|
||||
* Set up the request.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->parseInput();
|
||||
|
||||
$this->data['path'] = $this->getRequestPath();
|
||||
$this->data['parts'] = array_values(array_filter(explode('/', $this->data['path'])));
|
||||
}
|
||||
|
||||
protected function getRequestPath()
|
||||
{
|
||||
$path = '';
|
||||
|
||||
// Start out with the REQUEST_URI:
|
||||
if (!empty($_SERVER['REQUEST_URI'])) {
|
||||
$path = $_SERVER['REQUEST_URI'];
|
||||
}
|
||||
|
||||
if ($_SERVER['SCRIPT_NAME'] != $_SERVER['REQUEST_URI']) {
|
||||
$scriptPath = str_replace('/index.php', '', $_SERVER['SCRIPT_NAME']);
|
||||
$path = str_replace($scriptPath, '', $path);
|
||||
}
|
||||
|
||||
// Remove index.php from the URL if it is present:
|
||||
$path = str_replace(array('/index.php', 'index.php'), '', $path);
|
||||
|
||||
// Also cut out the query string:
|
||||
$path = explode('?', $path);
|
||||
$path = array_shift($path);
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse incoming variables, incl. $_GET, $_POST and also reads php://input for PUT/DELETE.
|
||||
*/
|
||||
protected function parseInput()
|
||||
{
|
||||
$params = $_REQUEST;
|
||||
|
||||
if(!isset($_SERVER['REQUEST_METHOD']) || in_array($_SERVER['REQUEST_METHOD'], array('PUT', 'DELETE')))
|
||||
{
|
||||
$vars = file_get_contents('php://input');
|
||||
|
||||
if(!is_string($vars) || strlen(trim($vars)) === 0)
|
||||
{
|
||||
$vars = '';
|
||||
}
|
||||
|
||||
$inputData = array();
|
||||
parse_str($vars, $inputData);
|
||||
|
||||
$params = array_merge($params, $inputData);
|
||||
}
|
||||
|
||||
$this->setParams($params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all request parameters.
|
||||
* @return array
|
||||
*/
|
||||
public function getParams()
|
||||
{
|
||||
return $this->params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a specific request parameter, or a default value if not set.
|
||||
*/
|
||||
public function getParam($key, $default = null)
|
||||
{
|
||||
if (isset($this->params[$key])) {
|
||||
return $this->params[$key];
|
||||
} else {
|
||||
return $default;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set or override a request parameter.
|
||||
*/
|
||||
public function setParam($key, $value = null)
|
||||
{
|
||||
$this->params[$key] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set an array of request parameters.
|
||||
*/
|
||||
public function setParams(array $params)
|
||||
{
|
||||
$this->params = array_merge($this->params, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Un-set a specific parameter.
|
||||
*/
|
||||
public function unsetParam($key)
|
||||
{
|
||||
unset($this->params[$key]);
|
||||
}
|
||||
|
||||
public function getMethod()
|
||||
{
|
||||
return strtoupper($_SERVER['REQUEST_METHOD']);
|
||||
}
|
||||
|
||||
public function getPath()
|
||||
{
|
||||
return $this->data['path'];
|
||||
}
|
||||
|
||||
public function getPathParts()
|
||||
{
|
||||
return $this->data['parts'];
|
||||
}
|
||||
|
||||
public function isAjax()
|
||||
{
|
||||
if (!isset($_SERVER['HTTP_X_REQUESTED_WITH'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) == 'xmlhttprequest') {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
132
src/B8Framework/Http/Response.php
Executable file
132
src/B8Framework/Http/Response.php
Executable file
|
|
@ -0,0 +1,132 @@
|
|||
<?php
|
||||
|
||||
namespace b8\Http;
|
||||
|
||||
class Response
|
||||
{
|
||||
protected $data = array();
|
||||
|
||||
public function __construct(Response $createFrom = null)
|
||||
{
|
||||
if (!is_null($createFrom)) {
|
||||
$this->data = $createFrom->getData();
|
||||
}
|
||||
}
|
||||
|
||||
public function hasLayout()
|
||||
{
|
||||
return !isset($this->data['layout']) ? true : $this->data['layout'];
|
||||
}
|
||||
|
||||
public function disableLayout()
|
||||
{
|
||||
$this->data['layout'] = false;
|
||||
}
|
||||
|
||||
public function enableLayout()
|
||||
{
|
||||
$this->data['layout'] = true;
|
||||
}
|
||||
|
||||
public function getData()
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
public function setResponseCode($code)
|
||||
{
|
||||
$this->data['code'] = (int)$code;
|
||||
}
|
||||
|
||||
public function setHeader($key, $val)
|
||||
{
|
||||
$this->data['headers'][$key] = $val;
|
||||
}
|
||||
|
||||
public function clearHeaders()
|
||||
{
|
||||
$this->data['headers'] = array();
|
||||
}
|
||||
|
||||
public function setContent($content)
|
||||
{
|
||||
$this->data['body'] = $content;
|
||||
}
|
||||
|
||||
public function getContent()
|
||||
{
|
||||
return $this->data['body'];
|
||||
}
|
||||
|
||||
public function flush()
|
||||
{
|
||||
$this->sendResponseCode();
|
||||
|
||||
if (isset($this->data['headers'])) {
|
||||
foreach ($this->data['headers'] as $header => $val) {
|
||||
header($header . ': ' . $val, true);
|
||||
}
|
||||
}
|
||||
|
||||
return $this->flushBody();
|
||||
}
|
||||
|
||||
protected function sendResponseCode()
|
||||
{
|
||||
if (!isset($this->data['code'])) {
|
||||
$this->data['code'] = 200;
|
||||
}
|
||||
|
||||
switch ($this->data['code'])
|
||||
{
|
||||
// 300 class
|
||||
case 301:
|
||||
$text = 'Moved Permanently';
|
||||
break;
|
||||
case 302:
|
||||
$text = 'Moved Temporarily';
|
||||
break;
|
||||
|
||||
// 400 class errors
|
||||
case 400:
|
||||
$text = 'Bad Request';
|
||||
break;
|
||||
case 401:
|
||||
$text = 'Not Authorized';
|
||||
break;
|
||||
case 403:
|
||||
$text = 'Forbidden';
|
||||
break;
|
||||
case 404:
|
||||
$text = 'Not Found';
|
||||
break;
|
||||
|
||||
// 500 class errors
|
||||
case 500:
|
||||
$text = 'Internal Server Error';
|
||||
break;
|
||||
|
||||
// OK
|
||||
case 200:
|
||||
default:
|
||||
$text = 'OK';
|
||||
break;
|
||||
}
|
||||
|
||||
header('HTTP/1.1 ' . $this->data['code'] . ' ' . $text, true, $this->data['code']);
|
||||
}
|
||||
|
||||
protected function flushBody()
|
||||
{
|
||||
if (isset($this->data['body'])) {
|
||||
return $this->data['body'];
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
public function __toString()
|
||||
{
|
||||
return $this->flush();
|
||||
}
|
||||
}
|
||||
30
src/B8Framework/Http/Response/JsonResponse.php
Executable file
30
src/B8Framework/Http/Response/JsonResponse.php
Executable file
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
namespace b8\Http\Response;
|
||||
|
||||
use b8\Http\Response;
|
||||
|
||||
class JsonResponse extends Response
|
||||
{
|
||||
public function __construct(Response $createFrom = null)
|
||||
{
|
||||
parent::__construct($createFrom);
|
||||
|
||||
$this->setContent(array());
|
||||
$this->setHeader('Content-Type', 'application/json');
|
||||
}
|
||||
|
||||
public function hasLayout()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function flushBody()
|
||||
{
|
||||
if (isset($this->data['body'])) {
|
||||
return json_encode($this->data['body']);
|
||||
}
|
||||
|
||||
return json_encode(null);
|
||||
}
|
||||
}
|
||||
27
src/B8Framework/Http/Response/RedirectResponse.php
Executable file
27
src/B8Framework/Http/Response/RedirectResponse.php
Executable file
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
namespace b8\Http\Response;
|
||||
|
||||
use b8\Http\Response;
|
||||
|
||||
class RedirectResponse extends Response
|
||||
{
|
||||
public function __construct(Response $createFrom = null)
|
||||
{
|
||||
parent::__construct($createFrom);
|
||||
|
||||
$this->setContent(null);
|
||||
$this->setResponseCode(302);
|
||||
}
|
||||
|
||||
public function hasLayout()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function flush()
|
||||
{
|
||||
parent::flush();
|
||||
die;
|
||||
}
|
||||
}
|
||||
123
src/B8Framework/Http/Router.php
Executable file
123
src/B8Framework/Http/Router.php
Executable file
|
|
@ -0,0 +1,123 @@
|
|||
<?php
|
||||
|
||||
namespace b8\Http;
|
||||
|
||||
use b8\Application;
|
||||
use b8\Config;
|
||||
use b8\Http\Request;
|
||||
|
||||
class Router
|
||||
{
|
||||
/**
|
||||
* @var \b8\Http\Request;
|
||||
*/
|
||||
protected $request;
|
||||
|
||||
/**
|
||||
* @var \b8\Http\Config;
|
||||
*/
|
||||
protected $config;
|
||||
|
||||
/**
|
||||
* @var \b8\Application
|
||||
*/
|
||||
protected $application;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $routes = array(array('route' => '/:controller/:action', 'callback' => null, 'defaults' => array()));
|
||||
|
||||
public function __construct(Application $application, Request $request, Config $config)
|
||||
{
|
||||
$this->application = $application;
|
||||
$this->request = $request;
|
||||
$this->config = $config;
|
||||
}
|
||||
|
||||
public function clearRoutes()
|
||||
{
|
||||
$this->routes = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $route Route definition
|
||||
* @param array $options
|
||||
* @param callable $callback
|
||||
* @throws \InvalidArgumentException
|
||||
*/
|
||||
public function register($route, $options = array(), $callback = null)
|
||||
{
|
||||
if (!is_callable($callback)) {
|
||||
throw new \InvalidArgumentException('$callback must be callable.');
|
||||
}
|
||||
|
||||
array_unshift($this->routes, array('route' => $route, 'callback' => $callback, 'defaults' => $options));
|
||||
}
|
||||
|
||||
public function dispatch()
|
||||
{
|
||||
foreach ($this->routes as $route) {
|
||||
$pathParts = $this->request->getPathParts();
|
||||
|
||||
//-------
|
||||
// Set up default values for everything:
|
||||
//-------
|
||||
$thisNamespace = 'Controller';
|
||||
$thisController = null;
|
||||
$thisAction = null;
|
||||
|
||||
if (array_key_exists('namespace', $route['defaults'])) {
|
||||
$thisNamespace = $route['defaults']['namespace'];
|
||||
}
|
||||
|
||||
if (array_key_exists('controller', $route['defaults'])) {
|
||||
$thisController = $route['defaults']['controller'];
|
||||
}
|
||||
|
||||
if (array_key_exists('action', $route['defaults'])) {
|
||||
$thisAction = $route['defaults']['action'];
|
||||
}
|
||||
|
||||
$routeParts = array_filter(explode('/', $route['route']));
|
||||
$routeMatches = true;
|
||||
|
||||
while (count($routeParts)) {
|
||||
$routePart = array_shift($routeParts);
|
||||
$pathPart = array_shift($pathParts);
|
||||
|
||||
switch ($routePart) {
|
||||
case ':namespace':
|
||||
$thisNamespace = !is_null($pathPart) ? $pathPart : $thisNamespace;
|
||||
break;
|
||||
case ':controller':
|
||||
$thisController = !is_null($pathPart) ? $pathPart : $thisController;
|
||||
break;
|
||||
case ':action':
|
||||
$thisAction = !is_null($pathPart) ? $pathPart : $thisAction;
|
||||
break;
|
||||
default:
|
||||
if ($routePart != $pathPart) {
|
||||
$routeMatches = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$routeMatches || !count($pathParts)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$thisArgs = $pathParts;
|
||||
|
||||
if ($routeMatches) {
|
||||
$route = array('namespace' => $thisNamespace, 'controller' => $thisController, 'action' => $thisAction, 'args' => $thisArgs, 'callback' => $route['callback']);
|
||||
|
||||
if ($this->application->isValidRoute($route)) {
|
||||
return $route;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
188
src/B8Framework/HttpClient.php
Executable file
188
src/B8Framework/HttpClient.php
Executable file
|
|
@ -0,0 +1,188 @@
|
|||
<?php
|
||||
|
||||
namespace b8;
|
||||
|
||||
class HttpClient
|
||||
{
|
||||
protected $_base = '';
|
||||
protected $_params = array();
|
||||
protected $_headers = array();
|
||||
|
||||
public function __construct($base = null)
|
||||
{
|
||||
$settings = Config::getInstance()->get('b8.http.client', array('base_url' => '', 'params' => array()));
|
||||
$this->_base = $settings['base_url'];
|
||||
$this->_params = isset($settings['params']) && is_array($settings['params']) ? $settings['params'] : array();
|
||||
$this->_headers = array('Content-Type: application/x-www-form-urlencoded');
|
||||
|
||||
if(!is_null($base))
|
||||
{
|
||||
$this->_base = $base;
|
||||
}
|
||||
}
|
||||
|
||||
public function setHeaders(array $headers)
|
||||
{
|
||||
$this->_headers = $headers;
|
||||
}
|
||||
|
||||
public function request($method, $uri, $params = array())
|
||||
{
|
||||
// Clean incoming:
|
||||
$method = strtoupper($method);
|
||||
$getParams = $this->_params;
|
||||
|
||||
if($method == 'GET' || $method == 'DELETE')
|
||||
{
|
||||
$getParams = array_merge($getParams, $params);
|
||||
}
|
||||
else
|
||||
{
|
||||
$bodyParams = is_array($params) ? http_build_query($params) : $params;
|
||||
}
|
||||
|
||||
$getParams = http_build_query($getParams);
|
||||
|
||||
if(substr($uri, 0, 1) != '/' && !empty($this->_base))
|
||||
{
|
||||
$uri = '/' . $uri;
|
||||
}
|
||||
|
||||
// Build HTTP context array:
|
||||
$context = array();
|
||||
$context['http']['user_agent'] = 'b8/1.0';
|
||||
$context['http']['timeout'] = 30;
|
||||
$context['http']['method'] = $method;
|
||||
$context['http']['ignore_errors'] = true;
|
||||
$context['http']['header'] = implode(PHP_EOL, $this->_headers);
|
||||
|
||||
if(in_array($method, array('PUT', 'POST')))
|
||||
{
|
||||
$context['http']['content'] = $bodyParams;
|
||||
}
|
||||
|
||||
$uri .= '?' . $getParams;
|
||||
|
||||
$context = stream_context_create($context);
|
||||
$result = file_get_contents($this->_base . $uri, false, $context);
|
||||
|
||||
$res = array();
|
||||
$res['headers'] = $http_response_header;
|
||||
$res['code'] = (int)preg_replace('/HTTP\/1\.[0-1] ([0-9]+)/', '$1', $res['headers'][0]);
|
||||
$res['success'] = false;
|
||||
$res['body'] = $this->_decodeResponse($result);
|
||||
|
||||
if($res['code'] >= 200 && $res['code'] < 300)
|
||||
{
|
||||
$res['success'] = true;
|
||||
}
|
||||
|
||||
// Handle JSON responses:
|
||||
foreach($res['headers'] as $header)
|
||||
{
|
||||
if(stripos($header, 'Content-Type') !== false || stripos($header, 'b8-Type') !== false)
|
||||
{
|
||||
if(stripos($header, 'application/json') !== false)
|
||||
{
|
||||
$res['text_body'] = $res['body'];
|
||||
$res['body'] = json_decode($res['body'], true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
public function get($uri, $params = array())
|
||||
{
|
||||
return $this->request('GET', $uri, $params);
|
||||
}
|
||||
|
||||
public function put($uri, $params = array())
|
||||
{
|
||||
return $this->request('PUT', $uri, $params);
|
||||
}
|
||||
|
||||
public function post($uri, $params = array())
|
||||
{
|
||||
return $this->request('POST', $uri, $params);
|
||||
}
|
||||
|
||||
public function delete($uri, $params = array())
|
||||
{
|
||||
return $this->request('DELETE', $uri, $params);
|
||||
}
|
||||
|
||||
protected function _decodeResponse($originalResponse)
|
||||
{
|
||||
$response = $originalResponse;
|
||||
$body = '';
|
||||
|
||||
do
|
||||
{
|
||||
$line = $this->_readChunk($response);
|
||||
|
||||
if($line == PHP_EOL)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
$length = hexdec(trim($line));
|
||||
|
||||
if(!is_int($length) || empty($response) || $line === false || $length < 1)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
$data = $this->_readChunk($response, $length);
|
||||
|
||||
// remove the amount received from the total length on the next loop
|
||||
// it'll attempt to read that much less data
|
||||
$length -= strlen($data);
|
||||
|
||||
// store in string for later use
|
||||
$body .= $data;
|
||||
|
||||
// zero or less or end of connection break
|
||||
if($length <= 0 || empty($response))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
while(true);
|
||||
}
|
||||
while(true);
|
||||
|
||||
if(empty($body))
|
||||
{
|
||||
$body = $originalResponse;
|
||||
}
|
||||
|
||||
return $body;
|
||||
}
|
||||
|
||||
function _readChunk(&$string, $len = 4096)
|
||||
{
|
||||
$rtn = '';
|
||||
for($i = 0; $i <= $len; $i++)
|
||||
{
|
||||
if(empty($string))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
$char = $string[0];
|
||||
$string = substr($string, 1);
|
||||
$rtn .= $char;
|
||||
|
||||
if($char == PHP_EOL)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $rtn;
|
||||
}
|
||||
}
|
||||
149
src/B8Framework/Image.php
Executable file
149
src/B8Framework/Image.php
Executable file
|
|
@ -0,0 +1,149 @@
|
|||
<?php
|
||||
|
||||
namespace b8;
|
||||
|
||||
class Image
|
||||
{
|
||||
public static $cachePath = '/tmp/';
|
||||
public static $sourcePath = './';
|
||||
|
||||
|
||||
/**
|
||||
* @var \Imagick
|
||||
*/
|
||||
protected $source;
|
||||
|
||||
public function __construct($imagePath)
|
||||
{
|
||||
$this->setSource(new \Imagick(self::$sourcePath . $imagePath));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Imagick
|
||||
*/
|
||||
public function getSource()
|
||||
{
|
||||
return $this->source;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Imagick $image
|
||||
*/
|
||||
public function setSource(\Imagick $image)
|
||||
{
|
||||
$this->source = $image;
|
||||
}
|
||||
|
||||
public function render($media, $width, $height, $format = 'jpeg')
|
||||
{
|
||||
$cachePath = self::$cachePath . $media['fileId'] . '.' . $width . 'x' . $height . '.' . $format;
|
||||
|
||||
if(file_exists($cachePath) && 0)
|
||||
{
|
||||
$output = file_get_contents($cachePath);
|
||||
}
|
||||
else
|
||||
{
|
||||
$output = $this->doRender($media, $width, $height, $format);
|
||||
file_put_contents($cachePath, $output);
|
||||
}
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
public function doRender($media, $width, $height, $format = 'jpeg')
|
||||
{
|
||||
$focal = !empty($media['focal_point']) ? $media['focal_point'] : array(0, 0);
|
||||
$focalX = (int)$focal[0];
|
||||
$focalY = (int)$focal[1];
|
||||
|
||||
$width = (int)$width;
|
||||
$height = (int)$height;
|
||||
|
||||
$source = $this->getSource();
|
||||
$sourceWidth = $source->getImageWidth();
|
||||
$sourceHeight = $source->getImageHeight();
|
||||
$sourceRatio = $sourceWidth / $sourceHeight;
|
||||
$targetRatio = $height != 'auto' ? $width / $height : $sourceRatio;
|
||||
|
||||
$quads = $this->_getQuadrants($sourceWidth, $sourceHeight);
|
||||
|
||||
foreach($quads as $name => $l)
|
||||
{
|
||||
if($focalX >= $l[0] && $focalX <= $l[1] && $focalY >= $l[2] && $focalY <= $l[3])
|
||||
{
|
||||
$useQuad = $name;
|
||||
}
|
||||
}
|
||||
|
||||
if($sourceRatio <= $targetRatio)
|
||||
{
|
||||
$scale = $sourceWidth / $width;
|
||||
}
|
||||
else
|
||||
{
|
||||
$scale = $sourceHeight / $height;
|
||||
}
|
||||
|
||||
$resizeWidth = (int)($sourceWidth / $scale);
|
||||
$resizeHeight = (int)($sourceHeight / $scale);
|
||||
|
||||
if($height == 'auto')
|
||||
{
|
||||
$height = $resizeHeight;
|
||||
}
|
||||
|
||||
$source->scaleImage($resizeWidth, $resizeHeight);
|
||||
|
||||
switch($useQuad)
|
||||
{
|
||||
case 'top_left':
|
||||
$cropX = 0;
|
||||
$cropY = 0;
|
||||
break;
|
||||
|
||||
case 'top_right':
|
||||
$cropX = ($resizeWidth - $width);
|
||||
$cropY = 0;
|
||||
break;
|
||||
|
||||
case 'middle_left':
|
||||
$cropX = 0;
|
||||
$cropY = ($resizeHeight - $height) / 2;
|
||||
break;
|
||||
|
||||
case 'middle-right':
|
||||
$cropX = ($resizeWidth - $width);
|
||||
$cropY = ($resizeHeight - $height) / 2;
|
||||
break;
|
||||
|
||||
case 'bottom_left':
|
||||
$cropX = 0;
|
||||
$cropY = ($resizeHeight - $height);
|
||||
break;
|
||||
|
||||
case 'bottom_right':
|
||||
$cropX = ($resizeWidth - $width);
|
||||
$cropY = ($resizeHeight - $height);
|
||||
break;
|
||||
}
|
||||
|
||||
$source->cropImage($width, $height, $cropX, $cropY);
|
||||
$source->setImageFormat($format);
|
||||
|
||||
return $source;
|
||||
}
|
||||
|
||||
protected function _getQuadrants($x, $y)
|
||||
{
|
||||
$rtn = array();
|
||||
$rtn['top_left'] = array(0, $x / 2, 0, $y / 3);
|
||||
$rtn['top_right'] = array(($x / 2) + 1, $x, 0, $y / 3);
|
||||
$rtn['middle_left'] = array(0, $y / 2, ($y / 3)+1, (($y / 3) * 2));
|
||||
$rtn['middle_right'] = array(($x / 2) + 1, $x, ($y / 3)+1, (($y / 3) * 2));
|
||||
$rtn['bottom_left'] = array(0, $y / 2, (($y / 3) * 2)+1, $y);
|
||||
$rtn['bottom_right'] = array(($x / 2) + 1, $x, (($y / 3) * 2)+1, $y);
|
||||
|
||||
return $rtn;
|
||||
}
|
||||
}
|
||||
224
src/B8Framework/Model.php
Executable file
224
src/B8Framework/Model.php
Executable file
|
|
@ -0,0 +1,224 @@
|
|||
<?php
|
||||
|
||||
namespace b8;
|
||||
|
||||
use b8\Exception\HttpException;
|
||||
use b8\Cache;
|
||||
|
||||
class Model
|
||||
{
|
||||
public static $sleepable = array();
|
||||
protected $getters = array();
|
||||
protected $setters = array();
|
||||
protected $data = array();
|
||||
protected $modified = array();
|
||||
protected $tableName;
|
||||
protected $cache;
|
||||
|
||||
public function __construct($initialData = array())
|
||||
{
|
||||
if(is_array($initialData))
|
||||
{
|
||||
$this->data = array_merge($this->data, $initialData);
|
||||
}
|
||||
|
||||
$this->cache = Cache::getCache(Cache::TYPE_REQUEST);
|
||||
}
|
||||
|
||||
public function getTableName()
|
||||
{
|
||||
return $this->tableName;
|
||||
}
|
||||
|
||||
public function toArray($depth = 2, $currentDepth = 0)
|
||||
{
|
||||
if(isset(static::$sleepable) && is_array(static::$sleepable) && count(static::$sleepable))
|
||||
{
|
||||
$sleepable = static::$sleepable;
|
||||
}
|
||||
else
|
||||
{
|
||||
$sleepable = array_keys($this->getters);
|
||||
}
|
||||
|
||||
$rtn = array();
|
||||
foreach($sleepable as $property)
|
||||
{
|
||||
$rtn[$property] = $this->_propertyToArray($property, $currentDepth, $depth);
|
||||
}
|
||||
|
||||
return $rtn;
|
||||
}
|
||||
|
||||
protected function _propertyToArray($property, $currentDepth, $depth)
|
||||
{
|
||||
$rtn = null;
|
||||
|
||||
if(array_key_exists($property, $this->getters))
|
||||
{
|
||||
$method = $this->getters[$property];
|
||||
$rtn = $this->{$method}();
|
||||
|
||||
if(is_object($rtn) || is_array($rtn))
|
||||
{
|
||||
$rtn = ($depth > $currentDepth) ? $this->_valueToArray($rtn, $currentDepth, $depth) : null;
|
||||
}
|
||||
}
|
||||
|
||||
return $rtn;
|
||||
}
|
||||
|
||||
protected function _valueToArray($value, $currentDepth, $depth)
|
||||
{
|
||||
$rtn = null;
|
||||
if(!is_null($value))
|
||||
{
|
||||
if(is_object($value) && method_exists($value, 'toArray'))
|
||||
{
|
||||
$rtn = $value->toArray($depth, $currentDepth + 1);
|
||||
}
|
||||
elseif(is_array($value))
|
||||
{
|
||||
$childArray = array();
|
||||
|
||||
foreach($value as $k => $v)
|
||||
{
|
||||
$childArray[$k] = $this->_valueToArray($v, $currentDepth + 1, $depth);
|
||||
}
|
||||
|
||||
$rtn = $childArray;
|
||||
}
|
||||
else
|
||||
{
|
||||
$rtn = (is_string($value) && !mb_check_encoding($value, 'UTF-8')) ? mb_convert_encoding($value, 'UTF-8') : $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $rtn;
|
||||
}
|
||||
|
||||
public function getDataArray()
|
||||
{
|
||||
return $this->data;
|
||||
}
|
||||
|
||||
public function getModified()
|
||||
{
|
||||
return $this->modified;
|
||||
}
|
||||
|
||||
public function setValues(array $values)
|
||||
{
|
||||
foreach($values as $key => $value)
|
||||
{
|
||||
if(isset($this->setters[$key]))
|
||||
{
|
||||
$func = $this->setters[$key];
|
||||
|
||||
if($value === 'null')
|
||||
{
|
||||
$value = null;
|
||||
}
|
||||
elseif($value === 'true')
|
||||
{
|
||||
$value = true;
|
||||
}
|
||||
elseif($value === 'false')
|
||||
{
|
||||
$value = false;
|
||||
}
|
||||
|
||||
$this->{$func}($value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected function _setModified($column)
|
||||
{
|
||||
$this->modified[$column] = $column;
|
||||
}
|
||||
|
||||
//----------------
|
||||
// Validation
|
||||
//----------------
|
||||
protected function _validateString($name, $value)
|
||||
{
|
||||
if(!is_string($value) && !is_null($value))
|
||||
{
|
||||
throw new HttpException\ValidationException($name . ' must be a string.');
|
||||
}
|
||||
}
|
||||
|
||||
protected function _validateInt($name, &$value)
|
||||
{
|
||||
if(is_bool($value))
|
||||
{
|
||||
$value = $value ? 1 : 0;
|
||||
}
|
||||
|
||||
if(!is_numeric($value) && !is_null($value))
|
||||
{
|
||||
throw new HttpException\ValidationException($name . ' must be an integer.');
|
||||
}
|
||||
|
||||
if(!is_int($value) && !is_null($value))
|
||||
{
|
||||
$value = (int)$value;
|
||||
}
|
||||
}
|
||||
|
||||
protected function _validateFloat($name, &$value)
|
||||
{
|
||||
if(!is_numeric($value) && !is_null($value))
|
||||
{
|
||||
throw new HttpException\ValidationException($name . ' must be a float.');
|
||||
}
|
||||
|
||||
if(!is_float($value) && !is_null($value))
|
||||
{
|
||||
$value = (float)$value;
|
||||
}
|
||||
}
|
||||
|
||||
protected function _validateDate($name, &$value)
|
||||
{
|
||||
if(is_string($value))
|
||||
{
|
||||
$value = empty($value) ? null : new \DateTime($value);
|
||||
}
|
||||
|
||||
if((!is_object($value) || !($value instanceof \DateTime)) && !is_null($value))
|
||||
{
|
||||
throw new HttpException\ValidationException($name . ' must be a date object.');
|
||||
}
|
||||
|
||||
|
||||
$value = empty($value) ? null : $value->format('Y-m-d H:i:s');
|
||||
}
|
||||
|
||||
protected function _validateNotNull($name, &$value)
|
||||
{
|
||||
if(is_null($value))
|
||||
{
|
||||
throw new HttpException\ValidationException($name . ' must not be null.');
|
||||
}
|
||||
}
|
||||
|
||||
public function __get($key)
|
||||
{
|
||||
if (array_key_exists($key, $this->getters)) {
|
||||
$getter = $this->getters[$key];
|
||||
return $this->{$getter}();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function __set($key, $value)
|
||||
{
|
||||
if (array_key_exists($key, $this->setters)) {
|
||||
$setter = $this->setters[$key];
|
||||
return $this->{$setter}($value);
|
||||
}
|
||||
}
|
||||
}
|
||||
379
src/B8Framework/Store.php
Executable file
379
src/B8Framework/Store.php
Executable file
|
|
@ -0,0 +1,379 @@
|
|||
<?php
|
||||
|
||||
namespace b8;
|
||||
use b8\Exception\HttpException;
|
||||
use b8\Database,
|
||||
b8\Model;
|
||||
|
||||
abstract class Store
|
||||
{
|
||||
protected $modelName = null;
|
||||
protected $tableName = null;
|
||||
protected $primaryKey = null;
|
||||
|
||||
/**
|
||||
* @return \b8\Model
|
||||
*/
|
||||
abstract public function getByPrimaryKey($key, $useConnection = 'read');
|
||||
|
||||
public function getWhere($where = array(), $limit = 25, $offset = 0, $joins = array(), $order = array(), $manualJoins = array(), $group = null, $manualWheres = array(), $whereType = 'AND')
|
||||
{
|
||||
$query = 'SELECT ' . $this->tableName . '.* FROM ' . $this->tableName;
|
||||
$countQuery = 'SELECT COUNT(*) AS cnt FROM ' . $this->tableName;
|
||||
|
||||
$wheres = array();
|
||||
$params = array();
|
||||
foreach($where as $key => $value)
|
||||
{
|
||||
$key = $this->fieldCheck($key);
|
||||
|
||||
if(!is_array($value))
|
||||
{
|
||||
$params[] = $value;
|
||||
$wheres[] = $key . ' = ?';
|
||||
}
|
||||
else
|
||||
{
|
||||
if(isset($value['operator']))
|
||||
{
|
||||
if(is_array($value['value']))
|
||||
{
|
||||
if($value['operator'] == 'between')
|
||||
{
|
||||
$params[] = $value['value'][0];
|
||||
$params[] = $value['value'][1];
|
||||
$wheres[] = $key . ' BETWEEN ? AND ?';
|
||||
}
|
||||
elseif($value['operator'] == 'IN')
|
||||
{
|
||||
$in = array();
|
||||
|
||||
foreach($value['value'] as $item)
|
||||
{
|
||||
$params[] = $item;
|
||||
$in[] = '?';
|
||||
}
|
||||
|
||||
$wheres[] = $key . ' IN (' . implode(', ', $in) . ') ';
|
||||
}
|
||||
else
|
||||
{
|
||||
$ors = array();
|
||||
foreach($value['value'] as $item)
|
||||
{
|
||||
if($item == 'null')
|
||||
{
|
||||
switch($value['operator'])
|
||||
{
|
||||
case '!=':
|
||||
$ors[] = $key . ' IS NOT NULL';
|
||||
break;
|
||||
|
||||
case '==':
|
||||
default:
|
||||
$ors[] = $key . ' IS NULL';
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$params[] = $item;
|
||||
$ors[] = $this->fieldCheck($key) . ' ' . $value['operator'] . ' ?';
|
||||
}
|
||||
}
|
||||
$wheres[] = '(' . implode(' OR ', $ors) . ')';
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if($value['operator'] == 'like')
|
||||
{
|
||||
$params[] = '%' . $value['value'] . '%';
|
||||
$wheres[] = $key . ' ' . $value['operator'] . ' ?';
|
||||
}
|
||||
else
|
||||
{
|
||||
if($value['value'] === 'null')
|
||||
{
|
||||
switch($value['operator'])
|
||||
{
|
||||
case '!=':
|
||||
$wheres[] = $key . ' IS NOT NULL';
|
||||
break;
|
||||
|
||||
case '==':
|
||||
default:
|
||||
$wheres[] = $key . ' IS NULL';
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$params[] = $value['value'];
|
||||
$wheres[] = $key . ' ' . $value['operator'] . ' ?';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$wheres[] = $key . ' IN (' . implode(', ', array_map(array(Database::getConnection('read'), 'quote'), $value)) . ')';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(count($joins))
|
||||
{
|
||||
foreach($joins as $table => $join)
|
||||
{
|
||||
$query .= ' LEFT JOIN ' . $table . ' ' . $join['alias'] . ' ON ' . $join['on'] . ' ';
|
||||
$countQuery .= ' LEFT JOIN ' . $table . ' ' . $join['alias'] . ' ON ' . $join['on'] . ' ';
|
||||
}
|
||||
}
|
||||
|
||||
if(count($manualJoins))
|
||||
{
|
||||
foreach($manualJoins as $join)
|
||||
{
|
||||
$query .= ' ' . $join . ' ';
|
||||
$countQuery .= ' ' . $join . ' ';
|
||||
}
|
||||
}
|
||||
|
||||
$hasWhere = false;
|
||||
if(count($wheres))
|
||||
{
|
||||
$hasWhere = true;
|
||||
$query .= ' WHERE (' . implode(' ' . $whereType . ' ', $wheres) . ')';
|
||||
$countQuery .= ' WHERE (' . implode(' ' . $whereType . ' ', $wheres) . ')';
|
||||
}
|
||||
|
||||
if(count($manualWheres))
|
||||
{
|
||||
foreach($manualWheres as $where)
|
||||
{
|
||||
if(!$hasWhere)
|
||||
{
|
||||
$hasWhere = true;
|
||||
$query .= ' WHERE ';
|
||||
$countQuery .= ' WHERE ';
|
||||
}
|
||||
else
|
||||
{
|
||||
$query .= ' ' . $where['type'] . ' ';
|
||||
$countQuery .= ' ' . $where['type'] . ' ';
|
||||
}
|
||||
|
||||
$query .= ' ' . $where['query'];
|
||||
$countQuery .= ' ' . $where['query'];
|
||||
|
||||
if(isset($where['params']))
|
||||
{
|
||||
foreach($where['params'] as $param)
|
||||
{
|
||||
$params[] = $param;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!is_null($group))
|
||||
{
|
||||
$query .= ' GROUP BY ' . $group . ' ';
|
||||
}
|
||||
|
||||
if(count($order))
|
||||
{
|
||||
$orders = array();
|
||||
if(is_string($order) && $order == 'rand')
|
||||
{
|
||||
$query .= ' ORDER BY RAND() ';
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach($order as $key => $value)
|
||||
{
|
||||
$orders[] = $this->fieldCheck($key) . ' ' . $value;
|
||||
}
|
||||
|
||||
$query .= ' ORDER BY ' . implode(', ', $orders);
|
||||
}
|
||||
}
|
||||
|
||||
if($limit)
|
||||
{
|
||||
$query .= ' LIMIT ' . $limit;
|
||||
}
|
||||
|
||||
if($offset)
|
||||
{
|
||||
$query .= ' OFFSET ' . $offset;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$stmt = Database::getConnection('read')->prepare($countQuery);
|
||||
$stmt->execute($params);
|
||||
$res = $stmt->fetch(\PDO::FETCH_ASSOC);
|
||||
$count = (int)$res['cnt'];
|
||||
}
|
||||
catch(\PDOException $ex)
|
||||
{
|
||||
$count = 0;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
$stmt = Database::getConnection('read')->prepare($query);
|
||||
$stmt->execute($params);
|
||||
$res = $stmt->fetchAll(\PDO::FETCH_ASSOC);
|
||||
$rtn = array();
|
||||
|
||||
foreach($res as $data)
|
||||
{
|
||||
$rtn[] = new $this->modelName($data);
|
||||
}
|
||||
|
||||
return array('items' => $rtn, 'count' => $count);
|
||||
}
|
||||
catch(\PDOException $ex)
|
||||
{
|
||||
throw $ex;
|
||||
}
|
||||
}
|
||||
|
||||
public function save(Model $obj, $saveAllColumns = false)
|
||||
{
|
||||
if(!isset($this->primaryKey))
|
||||
{
|
||||
throw new HttpException\BadRequestException('Save not implemented for this store.');
|
||||
}
|
||||
|
||||
if(!($obj instanceof $this->modelName))
|
||||
{
|
||||
throw new HttpException\BadRequestException(get_class($obj) . ' is an invalid model type for this store.');
|
||||
}
|
||||
|
||||
$data = $obj->getDataArray();
|
||||
|
||||
if(isset($data[$this->primaryKey]))
|
||||
{
|
||||
$rtn = $this->saveByUpdate($obj, $saveAllColumns);
|
||||
}
|
||||
else
|
||||
{
|
||||
$rtn = $this->saveByInsert($obj, $saveAllColumns);
|
||||
}
|
||||
|
||||
return $rtn;
|
||||
}
|
||||
|
||||
public function saveByUpdate(Model $obj, $saveAllColumns = false)
|
||||
{
|
||||
$rtn = null;
|
||||
$data = $obj->getDataArray();
|
||||
$modified = ($saveAllColumns) ? array_keys($data) : $obj->getModified();
|
||||
|
||||
$updates = array();
|
||||
$update_params = array();
|
||||
foreach($modified as $key)
|
||||
{
|
||||
$updates[] = $key . ' = :' . $key;
|
||||
$update_params[] = array($key, $data[$key]);
|
||||
}
|
||||
|
||||
if(count($updates))
|
||||
{
|
||||
$qs = 'UPDATE ' . $this->tableName . '
|
||||
SET ' . implode(', ', $updates) . '
|
||||
WHERE ' . $this->primaryKey . ' = :primaryKey';
|
||||
$q = Database::getConnection('write')->prepare($qs);
|
||||
|
||||
foreach($update_params as $update_param)
|
||||
{
|
||||
$q->bindValue(':' . $update_param[0], $update_param[1]);
|
||||
}
|
||||
|
||||
$q->bindValue(':primaryKey', $data[$this->primaryKey]);
|
||||
$q->execute();
|
||||
|
||||
$rtn = $this->getByPrimaryKey($data[$this->primaryKey], 'write');
|
||||
}
|
||||
else
|
||||
{
|
||||
$rtn = $obj;
|
||||
}
|
||||
|
||||
return $rtn;
|
||||
}
|
||||
|
||||
public function saveByInsert(Model $obj, $saveAllColumns = false)
|
||||
{
|
||||
$rtn = null;
|
||||
$data = $obj->getDataArray();
|
||||
$modified = ($saveAllColumns) ? array_keys($data) : $obj->getModified();
|
||||
|
||||
$cols = array();
|
||||
$values = array();
|
||||
$qParams = array();
|
||||
foreach($modified as $key)
|
||||
{
|
||||
$cols[] = $key;
|
||||
$values[] = ':' . $key;
|
||||
$qParams[':' . $key] = $data[$key];
|
||||
}
|
||||
|
||||
if(count($cols))
|
||||
{
|
||||
$qs = 'INSERT INTO ' . $this->tableName . ' (' . implode(', ', $cols) . ') VALUES (' . implode(', ', $values) . ')';
|
||||
$q = Database::getConnection('write')->prepare($qs);
|
||||
|
||||
if($q->execute($qParams))
|
||||
{
|
||||
$id = !empty($data[$this->primaryKey]) ? $data[$this->primaryKey] : Database::getConnection('write')->lastInsertId();
|
||||
$rtn = $this->getByPrimaryKey($id, 'write');
|
||||
}
|
||||
}
|
||||
|
||||
return $rtn;
|
||||
}
|
||||
|
||||
public function delete(Model $obj)
|
||||
{
|
||||
if(!isset($this->primaryKey))
|
||||
{
|
||||
throw new HttpException\BadRequestException('Delete not implemented for this store.');
|
||||
}
|
||||
|
||||
if(!($obj instanceof $this->modelName))
|
||||
{
|
||||
throw new HttpException\BadRequestException(get_class($obj) . ' is an invalid model type for this store.');
|
||||
}
|
||||
|
||||
$data = $obj->getDataArray();
|
||||
|
||||
$q = Database::getConnection('write')->prepare('DELETE FROM ' . $this->tableName . ' WHERE ' . $this->primaryKey . ' = :primaryKey');
|
||||
$q->bindValue(':primaryKey', $data[$this->primaryKey]);
|
||||
$q->execute();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
protected function fieldCheck($field)
|
||||
{
|
||||
if(empty($field))
|
||||
{
|
||||
throw new HttpException('You cannot have an empty field name.');
|
||||
}
|
||||
|
||||
if(strpos($field, '.') === false)
|
||||
{
|
||||
return $this->tableName . '.' . $field;
|
||||
}
|
||||
|
||||
return $field;
|
||||
}
|
||||
}
|
||||
65
src/B8Framework/Store/Factory.php
Executable file
65
src/B8Framework/Store/Factory.php
Executable file
|
|
@ -0,0 +1,65 @@
|
|||
<?php
|
||||
|
||||
namespace b8\Store;
|
||||
use b8\Config;
|
||||
|
||||
class Factory
|
||||
{
|
||||
/**
|
||||
* @var \b8\Store\Factory
|
||||
*/
|
||||
protected static $instance;
|
||||
|
||||
/**
|
||||
* A collection of the stores currently loaded by the factory.
|
||||
* @var \b8\Store[]
|
||||
*/
|
||||
protected $loadedStores = array();
|
||||
|
||||
/**
|
||||
* @return Factory
|
||||
*/
|
||||
public static function getInstance()
|
||||
{
|
||||
if(!isset(self::$instance))
|
||||
{
|
||||
self::$instance = new self();
|
||||
}
|
||||
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $storeName string Store name (should match a model name).
|
||||
*
|
||||
* @return \b8\Store
|
||||
*/
|
||||
public static function getStore($storeName, $namespace = null)
|
||||
{
|
||||
$factory = self::getInstance();
|
||||
return $factory->loadStore($storeName, $namespace);
|
||||
}
|
||||
|
||||
protected function __construct()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $store
|
||||
*
|
||||
* @return \b8\Store;
|
||||
*/
|
||||
public function loadStore($store, $namespace = null)
|
||||
{
|
||||
if(!isset($this->loadedStores[$store]))
|
||||
{
|
||||
$namespace = is_null($namespace) ? Config::getInstance()->get('b8.app.namespace') : $namespace;
|
||||
$class = $namespace . '\\Store\\' . $store . 'Store';
|
||||
$obj = new $class();
|
||||
|
||||
$this->loadedStores[$store] = $obj;
|
||||
}
|
||||
|
||||
return $this->loadedStores[$store];
|
||||
}
|
||||
}
|
||||
17
src/B8Framework/Type/Cache.php
Executable file
17
src/B8Framework/Type/Cache.php
Executable file
|
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
namespace b8\Type;
|
||||
|
||||
interface Cache
|
||||
{
|
||||
public function get($key, $default = null);
|
||||
public function set($key, $value = null, $ttl = 0);
|
||||
public function delete($key);
|
||||
public function contains($key);
|
||||
public function isEnabled();
|
||||
|
||||
public function __get($key);
|
||||
public function __set($key, $value = null);
|
||||
public function __unset($key);
|
||||
public function __isset($key);
|
||||
}
|
||||
8
src/B8Framework/Type/RestUser.php
Executable file
8
src/B8Framework/Type/RestUser.php
Executable file
|
|
@ -0,0 +1,8 @@
|
|||
<?php
|
||||
|
||||
namespace b8\Type;
|
||||
|
||||
interface RestUser
|
||||
{
|
||||
public function checkPermission($permission, $resource);
|
||||
}
|
||||
86
src/B8Framework/View.php
Executable file
86
src/B8Framework/View.php
Executable file
|
|
@ -0,0 +1,86 @@
|
|||
<?php
|
||||
|
||||
namespace b8;
|
||||
use b8\Exception\HttpException;
|
||||
|
||||
class View
|
||||
{
|
||||
protected $_vars = array();
|
||||
protected static $_helpers = array();
|
||||
protected static $extension = 'phtml';
|
||||
|
||||
public function __construct($file, $path = null)
|
||||
{
|
||||
if (!self::exists($file, $path)) {
|
||||
throw new \Exception('View file does not exist: ' . $file);
|
||||
}
|
||||
|
||||
$this->viewFile = self::getViewFile($file, $path);
|
||||
}
|
||||
|
||||
protected static function getViewFile($file, $path = null)
|
||||
{
|
||||
$viewPath = is_null($path) ? Config::getInstance()->get('b8.view.path') : $path;
|
||||
$fullPath = $viewPath . $file . '.' . static::$extension;
|
||||
|
||||
return $fullPath;
|
||||
}
|
||||
|
||||
public static function exists($file, $path = null)
|
||||
{
|
||||
if (!file_exists(self::getViewFile($file, $path))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function __isset($var)
|
||||
{
|
||||
return isset($this->_vars[$var]);
|
||||
}
|
||||
|
||||
public function __get($var)
|
||||
{
|
||||
return $this->_vars[$var];
|
||||
}
|
||||
|
||||
public function __set($var, $val)
|
||||
{
|
||||
$this->_vars[$var] = $val;
|
||||
}
|
||||
|
||||
public function __call($method, $params = array())
|
||||
{
|
||||
if(!isset(self::$_helpers[$method]))
|
||||
{
|
||||
$class = '\\' . Config::getInstance()->get('b8.app.namespace') . '\\Helper\\' . $method;
|
||||
|
||||
if(!class_exists($class))
|
||||
{
|
||||
$class = '\\b8\\View\\Helper\\' . $method;
|
||||
}
|
||||
|
||||
if(!class_exists($class))
|
||||
{
|
||||
throw new HttpException('Helper class does not exist: ' . $class);
|
||||
}
|
||||
|
||||
self::$_helpers[$method] = new $class();
|
||||
}
|
||||
|
||||
return self::$_helpers[$method];
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
extract($this->_vars);
|
||||
|
||||
ob_start();
|
||||
require($this->viewFile);
|
||||
$html = ob_get_contents();
|
||||
ob_end_clean();
|
||||
|
||||
return $html;
|
||||
}
|
||||
}
|
||||
11
src/B8Framework/View/Helper/Format.php
Executable file
11
src/B8Framework/View/Helper/Format.php
Executable file
|
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
|
||||
namespace b8\View\Helper;
|
||||
|
||||
class Format
|
||||
{
|
||||
public function Currency($number, $symbol = true)
|
||||
{
|
||||
return ($symbol ? '£' : '') . number_format($number, 2, '.', ',');
|
||||
}
|
||||
}
|
||||
544
src/B8Framework/View/Template.php
Executable file
544
src/B8Framework/View/Template.php
Executable file
|
|
@ -0,0 +1,544 @@
|
|||
<?php
|
||||
|
||||
namespace b8\View;
|
||||
use b8\View;
|
||||
|
||||
class Template extends View
|
||||
{
|
||||
public static $templateFunctions = array();
|
||||
protected static $extension = 'html';
|
||||
|
||||
public function __construct($viewCode)
|
||||
{
|
||||
$this->viewCode = $viewCode;
|
||||
|
||||
if (!count(self::$templateFunctions)) {
|
||||
self::$templateFunctions = array('include' => array($this, 'includeTemplate'), 'call' => array($this, 'callHelperFunction'));
|
||||
}
|
||||
}
|
||||
|
||||
public static function createFromFile($file, $path = null)
|
||||
{
|
||||
if (!static::exists($file, $path)) {
|
||||
throw new \Exception('View file does not exist: ' . $file);
|
||||
}
|
||||
|
||||
$viewFile = static::getViewFile($file, $path);
|
||||
return new static(file_get_contents($viewFile));
|
||||
}
|
||||
|
||||
public static function createFromString($string)
|
||||
{
|
||||
return new static($string);
|
||||
}
|
||||
|
||||
public function addFunction($name, $handler)
|
||||
{
|
||||
self::$templateFunctions[$name] = $handler;
|
||||
}
|
||||
|
||||
public function removeFunction($name)
|
||||
{
|
||||
unset(self::$templateFunctions[$name]);
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return $this->parse($this->viewCode);
|
||||
}
|
||||
|
||||
protected function parse($string)
|
||||
{
|
||||
$lastCond = null;
|
||||
$keywords = array('ifnot', 'if', 'else', 'for', 'loop', '@', '/ifnot', '/if', '/for', '/loop');
|
||||
|
||||
foreach (self::$templateFunctions as $function => $handler) {
|
||||
$keywords[] = $function;
|
||||
}
|
||||
|
||||
$stack = array('children' => array(array('type' => 'string', 'body' => '')));
|
||||
$stack['children'][0]['parent'] =& $stack;
|
||||
$current =& $stack['children'][0];
|
||||
|
||||
while (!empty($string)) {
|
||||
$current['body'] .= $this->readUntil('{', $string);
|
||||
|
||||
if (!empty($string)) {
|
||||
$gotKeyword = false;
|
||||
|
||||
foreach($keywords as $keyword) {
|
||||
$kwLen = strlen($keyword) + 1;
|
||||
|
||||
if (substr($string, 0, $kwLen) == '{' . $keyword) {
|
||||
$gotKeyword = true;
|
||||
$item = array('type' => $keyword, 'cond' => '', 'children' => '');
|
||||
$string = substr($string, $kwLen);
|
||||
|
||||
$cond = trim($this->readUntil('}', $string));
|
||||
$item['cond'] = $cond;
|
||||
$lastCond = $cond;
|
||||
$string = substr($string, 1);
|
||||
|
||||
if (array_key_exists($keyword, self::$templateFunctions)) {
|
||||
$item['function_name'] = $keyword;
|
||||
$item['type'] = 'function';
|
||||
}
|
||||
|
||||
$str = array('type' => 'string', 'body' => '');
|
||||
$parent =& $current['parent'];
|
||||
|
||||
if (substr($current['body'], (0 - strlen(PHP_EOL))) === PHP_EOL) {
|
||||
$current['body'] = substr($current['body'], 0, strlen($current['body']) - strlen(PHP_EOL));
|
||||
}
|
||||
|
||||
$item['parent'] =& $parent;
|
||||
|
||||
$parent['children'][] = $item;
|
||||
|
||||
if ($keyword == '@' || $item['type'] == 'function') {
|
||||
// If we're processing a variable, add a string to the parent and move up to that as current.
|
||||
$parent['children'][] = $str;
|
||||
$current =& $parent['children'][count($parent['children']) - 1];
|
||||
$current['parent'] =& $parent;
|
||||
} elseif (substr($keyword, 0, 1) == '/') {
|
||||
// If we're processing the end of a block (if/loop), add a string to the parent's parent and move up to that.
|
||||
$parent =& $parent['parent'];
|
||||
$parent['children'][] = $str;
|
||||
$current =& $parent['children'][count($parent['children']) - 1];
|
||||
$current['parent'] =& $parent;
|
||||
} else {
|
||||
$parent['children'][count($parent['children']) - 1]['children'][] = $str;
|
||||
$current =& $parent['children'][count($parent['children']) - 1]['children'][0];
|
||||
$current['parent'] =& $parent['children'][count($parent['children']) - 1];
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$gotKeyword) {
|
||||
$current['body'] .= substr($string, 0, 1);
|
||||
$string = substr($string, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->processStack($stack);
|
||||
}
|
||||
|
||||
protected function processStack($stack)
|
||||
{
|
||||
$res = '';
|
||||
|
||||
while (count($stack['children'])) {
|
||||
$current = array_shift($stack['children']);
|
||||
|
||||
switch ($current['type']) {
|
||||
case 'string':
|
||||
$res .= $current['body'];
|
||||
break;
|
||||
|
||||
case '@':
|
||||
$res .= $this->doParseVar($current['cond']);
|
||||
break;
|
||||
|
||||
case 'if':
|
||||
$res .= $this->doParseIf($current['cond'], $current);
|
||||
break;
|
||||
|
||||
case 'ifnot':
|
||||
$res .= $this->doParseIfNot($current['cond'], $current);
|
||||
break;
|
||||
|
||||
case 'loop':
|
||||
$res .= $this->doParseLoop($current['cond'], $current);
|
||||
break;
|
||||
|
||||
case 'for':
|
||||
$res .= $this->doParseFor($current['cond'], $current);
|
||||
break;
|
||||
|
||||
case 'function':
|
||||
$res .= $this->doParseFunction($current);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
protected function readUntil($until, &$string)
|
||||
{
|
||||
$read = '';
|
||||
|
||||
while (!empty($string)) {
|
||||
$char = substr($string, 0, 1);
|
||||
|
||||
if ($char == $until) {
|
||||
break;
|
||||
}
|
||||
|
||||
$read .= $char;
|
||||
$string = substr($string, 1);
|
||||
}
|
||||
|
||||
return $read;
|
||||
}
|
||||
|
||||
protected function doParseVar($var)
|
||||
{
|
||||
if($var == 'year')
|
||||
{
|
||||
return date('Y');
|
||||
}
|
||||
|
||||
$val = $this->processVariableName($var);
|
||||
return $val;
|
||||
}
|
||||
|
||||
protected function doParseIf($condition, $stack)
|
||||
{
|
||||
if ($this->ifConditionIsTrue($condition)) {
|
||||
return $this->processStack($stack);
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
protected function doParseIfNot($condition, $stack)
|
||||
{
|
||||
if (!$this->ifConditionIsTrue($condition)) {
|
||||
return $this->processStack($stack);
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
protected function ifConditionIsTrue($condition)
|
||||
{
|
||||
$matches = array();
|
||||
|
||||
if (preg_match('/([a-zA-Z0-9_\-\(\):\s.\"]+)\s+?([\!\=\<\>]+)?\s+?([a-zA-Z0-9\(\)_\-:\s.\"]+)?/', $condition, $matches)) {
|
||||
$left = is_numeric($matches[1]) ? intval($matches[1]) : $this->processVariableName($matches[1]);
|
||||
$right = is_numeric($matches[3]) ? intval($matches[3]) : $this->processVariableName($matches[3]);
|
||||
$operator = $matches[2];
|
||||
|
||||
switch ($operator) {
|
||||
case '==':
|
||||
case '=':
|
||||
return ($left == $right);
|
||||
|
||||
case '!=':
|
||||
return ($left != $right);
|
||||
|
||||
case '>=':
|
||||
return ($left >= $right);
|
||||
|
||||
case '<=':
|
||||
return ($left <= $right);
|
||||
|
||||
case '>':
|
||||
return ($left > $right);
|
||||
|
||||
case '<':
|
||||
return ($left < $right);
|
||||
}
|
||||
} elseif (preg_match('/([a-zA-Z0-9_\-\(\):\s.]+)/', $condition, $matches)) {
|
||||
return $this->processVariableName($condition) ? true : false;
|
||||
}
|
||||
}
|
||||
|
||||
protected function doParseLoop($var, $stack)
|
||||
{
|
||||
$working = $this->processVariableName($var);
|
||||
|
||||
if(is_null($working))
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
if(!is_array($working))
|
||||
{
|
||||
$working = array($working);
|
||||
}
|
||||
|
||||
$rtn = '';
|
||||
foreach ($working as $key => $val) {
|
||||
// Make sure we support nesting loops:
|
||||
$keyWas = isset($this->key) ? $this->key : null;
|
||||
$valWas = isset($this->value) ? $this->value : null;
|
||||
$itemWas = isset($this->item) ? $this->item : null;
|
||||
|
||||
// Set up the necessary variables within the stack:
|
||||
$this->parent = $this;
|
||||
$this->item = $val;
|
||||
$this->key = $key;
|
||||
$this->value = $val;
|
||||
$rtn .= $this->processStack($stack);
|
||||
|
||||
// Restore state for any parent nested loops:
|
||||
$this->item = $itemWas;
|
||||
$this->key = $keyWas;
|
||||
$this->value = $valWas;
|
||||
}
|
||||
|
||||
return $rtn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes loops in templates, of the following styles:
|
||||
*
|
||||
* <code>
|
||||
* {for myarray.items}
|
||||
* {@item.title}
|
||||
* {/for}
|
||||
* </code>
|
||||
*
|
||||
* Or:
|
||||
*
|
||||
* <code>
|
||||
* {for 0:pages.count; i++}
|
||||
* <a href="/item/{@i}">{@i}</a>
|
||||
* {/for}
|
||||
* </code>
|
||||
*
|
||||
* @param $cond string The condition string for the loop, to be parsed (e.g. "myarray.items" or "0:pages.count; i++")
|
||||
* @param $stack string The child stack for this loop, to be processed for each item.
|
||||
* @return string
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function doParseFor($cond, $stack)
|
||||
{
|
||||
// If this is a simple foreach loop, jump over to parse loop:
|
||||
if (strpos($cond, ';') === false) {
|
||||
return $this->doParseLoop($cond, $stack);
|
||||
}
|
||||
|
||||
// Otherwise, process as a for loop:
|
||||
$parts = explode(';', $cond);
|
||||
$range = explode(':', trim($parts[0]));
|
||||
|
||||
// Process range:
|
||||
$rangeLeft = $this->getForRangePart($range[0]);
|
||||
$rangeRight = $this->getForRangePart($range[1]);
|
||||
|
||||
// Process variable & incrementor / decrementor:
|
||||
$parts[1] = trim($parts[1]);
|
||||
|
||||
$matches = array();
|
||||
if (preg_match('/([a-zA-Z0-9_]+)(\+\+|\-\-)/', $parts[1], $matches)) {
|
||||
$varName = $matches[1];
|
||||
$direction = $matches[2] == '++' ? 'increment' : 'decrement';
|
||||
} else {
|
||||
throw new \Exception('Syntax error in for loop: ' . $cond);
|
||||
}
|
||||
|
||||
$rtn = '';
|
||||
|
||||
if ($direction == 'increment') {
|
||||
for ($i = $rangeLeft; $i < $rangeRight; $i++) {
|
||||
$this->parent = $this;
|
||||
$this->{$varName} = $i;
|
||||
$rtn .= $this->processStack($stack);
|
||||
}
|
||||
} else {
|
||||
for ($i = $rangeLeft; $i > $rangeRight; $i--) {
|
||||
$this->parent = $this;
|
||||
$this->{$varName} = $i;
|
||||
$rtn .= $this->processStack($stack);
|
||||
}
|
||||
}
|
||||
|
||||
return $rtn;
|
||||
}
|
||||
|
||||
protected function getForRangePart($part)
|
||||
{
|
||||
if (is_numeric($part)) {
|
||||
return intval($part);
|
||||
}
|
||||
|
||||
$varPart = $this->processVariableName($part);
|
||||
|
||||
if (is_numeric($varPart)) {
|
||||
return intval($varPart);
|
||||
}
|
||||
|
||||
throw new \Exception('Invalid range in for loop: ' . $part);
|
||||
}
|
||||
|
||||
public function processVariableName($varName)
|
||||
{
|
||||
// Case one - Test for function calls:
|
||||
if (substr($varName, 0, 1) == '(' && substr($varName, -1) == ')') {
|
||||
|
||||
$functionCall = substr($varName, 1, -1);
|
||||
$parts = explode(' ', $functionCall, 2);
|
||||
$functionName = $parts[0];
|
||||
$arguments = isset($parts[1]) ? $parts[1] : null;
|
||||
|
||||
return $this->executeTemplateFunction($functionName, $arguments);
|
||||
}
|
||||
|
||||
// Case two - Test if it is just a string:
|
||||
if (substr($varName, 0, 1) == '"' && substr($varName, -1) == '"') {
|
||||
return substr($varName, 1, -1);
|
||||
}
|
||||
|
||||
// Case three - Test if it is just a number:
|
||||
if (is_numeric($varName)) {
|
||||
return $varName;
|
||||
}
|
||||
|
||||
// Case four - Test for helper calls:
|
||||
if (strpos($varName, ':') !== false) {
|
||||
list($helper, $property) = explode(':', $varName);
|
||||
|
||||
$helper = $this->{$helper}();
|
||||
|
||||
if (property_exists($helper, $property) || method_exists($helper, '__get')) {
|
||||
return $helper->{$property};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
// Case five - Process as a variable:
|
||||
$varPart = explode('.', $varName);
|
||||
$thisPart = array_shift($varPart);
|
||||
|
||||
|
||||
if(!array_key_exists($thisPart, $this->_vars))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
$working = $this->{$thisPart};
|
||||
|
||||
while(count($varPart))
|
||||
{
|
||||
$thisPart = array_shift($varPart);
|
||||
|
||||
if(is_object($working)) {
|
||||
// Check if we're working with an actual property:
|
||||
if (property_exists($working, $thisPart)) {
|
||||
$working = $working->{$thisPart};
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if the object has a magic __get method:
|
||||
if (method_exists($working, '__get')) {
|
||||
$working = $working->{$thisPart};
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if(is_array($working) && array_key_exists($thisPart, $working))
|
||||
{
|
||||
$working = $working[$thisPart];
|
||||
continue;
|
||||
}
|
||||
|
||||
if($thisPart == 'toLowerCase')
|
||||
{
|
||||
$working = strtolower($working);
|
||||
continue;
|
||||
}
|
||||
|
||||
if($thisPart == 'toUpperCase')
|
||||
{
|
||||
$working = strtoupper($working);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($thisPart == 'isNumeric')
|
||||
{
|
||||
return is_numeric($working);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return $working;
|
||||
}
|
||||
|
||||
protected function doParseFunction($stack)
|
||||
{
|
||||
return $this->executeTemplateFunction($stack['function_name'], $stack['cond']);
|
||||
}
|
||||
|
||||
protected function executeTemplateFunction($function, $args)
|
||||
{
|
||||
if (array_key_exists($function, self::$templateFunctions)) {
|
||||
$handler = self::$templateFunctions[$function];
|
||||
$args = $this->processFunctionArguments($args);
|
||||
return $handler($args, $this);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function processFunctionArguments($args)
|
||||
{
|
||||
$rtn = array();
|
||||
|
||||
$args = explode(';', $args);
|
||||
|
||||
foreach ($args as $arg) {
|
||||
$arg = explode(':', $arg);
|
||||
|
||||
if (count($arg) == 2) {
|
||||
|
||||
$key = trim($arg[0]);
|
||||
$val = trim($arg[1]);
|
||||
|
||||
if (strpos($val, ',') !== false) {
|
||||
$val = explode(',', $val);
|
||||
}
|
||||
|
||||
$rtn[$key] = $val;
|
||||
}
|
||||
}
|
||||
|
||||
return $rtn;
|
||||
}
|
||||
|
||||
public function getVariable($variable)
|
||||
{
|
||||
return $this->processVariableName($variable);
|
||||
}
|
||||
|
||||
protected function includeTemplate($args, $view)
|
||||
{
|
||||
$template = static::createFromFile($view->getVariable($args['template']));
|
||||
|
||||
if (isset($args['variables'])) {
|
||||
if (!is_array($args['variables'])) {
|
||||
$args['variables'] = array($args['variables']);
|
||||
}
|
||||
|
||||
foreach ($args['variables'] as $variable) {
|
||||
|
||||
$variable = explode('=>', $variable);
|
||||
$variable = array_map('trim', $variable);
|
||||
|
||||
if (count($variable) == 1) {
|
||||
$template->{$variable[0]} = $view->getVariable($variable[0]);
|
||||
} else {
|
||||
$template->{$variable[1]} = $view->getVariable($variable[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $template->render();
|
||||
}
|
||||
|
||||
protected function callHelperFunction($args)
|
||||
{
|
||||
$helper = $args['helper'];
|
||||
$function = $args['method'];
|
||||
|
||||
return $this->{$helper}()->{$function}();
|
||||
}
|
||||
}
|
||||
14
src/B8Framework/View/UserView.php
Executable file
14
src/B8Framework/View/UserView.php
Executable file
|
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
|
||||
namespace b8\View;
|
||||
|
||||
use b8\View\Template;
|
||||
|
||||
class UserView extends Template
|
||||
{
|
||||
public function __construct($string)
|
||||
{
|
||||
trigger_error('Use of UserView is now deprecated. Please use Template instead.', E_USER_NOTICE);
|
||||
parent::__construct($string);
|
||||
}
|
||||
}
|
||||
174
src/PHPCI/Application.php
Normal file
174
src/PHPCI/Application.php
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
<?php
|
||||
/**
|
||||
* PHPCI - Continuous Integration for PHP
|
||||
*
|
||||
* @copyright Copyright 2014, Block 8 Limited.
|
||||
* @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md
|
||||
* @link https://www.phptesting.org/
|
||||
*/
|
||||
|
||||
namespace PHPCI;
|
||||
|
||||
use b8;
|
||||
use b8\Exception\HttpException;
|
||||
use b8\Http\Response;
|
||||
use b8\Http\Response\RedirectResponse;
|
||||
use b8\View;
|
||||
|
||||
/**
|
||||
* PHPCI Front Controller
|
||||
* @author Dan Cryer <dan@block8.co.uk>
|
||||
*/
|
||||
class Application extends b8\Application
|
||||
{
|
||||
/**
|
||||
* @var \PHPCI\Controller
|
||||
*/
|
||||
protected $controller;
|
||||
|
||||
/**
|
||||
* Initialise PHPCI - Handles session verification, routing, etc.
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
$request =& $this->request;
|
||||
$route = '/:controller/:action';
|
||||
$opts = array('controller' => 'Home', 'action' => 'index');
|
||||
|
||||
// Inlined as a closure to fix "using $this when not in object context" on 5.3
|
||||
$validateSession = function () {
|
||||
if (!empty($_SESSION['phpci_user_id'])) {
|
||||
$user = b8\Store\Factory::getStore('User')->getByPrimaryKey($_SESSION['phpci_user_id']);
|
||||
|
||||
if ($user) {
|
||||
$_SESSION['phpci_user'] = $user;
|
||||
return true;
|
||||
}
|
||||
|
||||
unset($_SESSION['phpci_user_id']);
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
$skipAuth = array($this, 'shouldSkipAuth');
|
||||
|
||||
// Handler for the route we're about to register, checks for a valid session where necessary:
|
||||
$routeHandler = function (&$route, Response &$response) use (&$request, $validateSession, $skipAuth) {
|
||||
$skipValidation = in_array($route['controller'], array('session', 'webhook', 'build-status'));
|
||||
|
||||
if (!$skipValidation && !$validateSession() && (!is_callable($skipAuth) || !$skipAuth())) {
|
||||
if ($request->isAjax()) {
|
||||
$response->setResponseCode(401);
|
||||
$response->setContent('');
|
||||
} else {
|
||||
$_SESSION['phpci_login_redirect'] = substr($request->getPath(), 1);
|
||||
$response = new RedirectResponse($response);
|
||||
$response->setHeader('Location', PHPCI_URL.'session/login');
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
$this->router->clearRoutes();
|
||||
$this->router->register($route, $opts, $routeHandler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle an incoming web request.
|
||||
*
|
||||
* @return b8\b8\Http\Response|Response
|
||||
*/
|
||||
public function handleRequest()
|
||||
{
|
||||
try {
|
||||
$this->response = parent::handleRequest();
|
||||
} catch (HttpException $ex) {
|
||||
$this->config->set('page_title', 'Error');
|
||||
|
||||
$view = new View('exception');
|
||||
$view->exception = $ex;
|
||||
|
||||
$this->response->setResponseCode($ex->getErrorCode());
|
||||
$this->response->setContent($view->render());
|
||||
} catch (\Exception $ex) {
|
||||
$this->config->set('page_title', 'Error');
|
||||
|
||||
$view = new View('exception');
|
||||
$view->exception = $ex;
|
||||
|
||||
$this->response->setResponseCode(500);
|
||||
$this->response->setContent($view->render());
|
||||
}
|
||||
|
||||
if ($this->response->hasLayout() && $this->controller->layout) {
|
||||
$this->setLayoutVariables($this->controller->layout);
|
||||
|
||||
$this->controller->layout->content = $this->response->getContent();
|
||||
$this->response->setContent($this->controller->layout->render());
|
||||
}
|
||||
|
||||
return $this->response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a particular controller, and injects our layout view into it.
|
||||
* @param $class
|
||||
* @return mixed
|
||||
*/
|
||||
protected function loadController($class)
|
||||
{
|
||||
$controller = parent::loadController($class);
|
||||
$controller->layout = new View('layout');
|
||||
$controller->layout->title = 'PHPCI';
|
||||
$controller->layout->breadcrumb = array();
|
||||
|
||||
return $controller;
|
||||
}
|
||||
|
||||
/**
|
||||
* Injects variables into the layout before rendering it.
|
||||
* @param View $layout
|
||||
*/
|
||||
protected function setLayoutVariables(View &$layout)
|
||||
{
|
||||
$groups = array();
|
||||
$groupStore = b8\Store\Factory::getStore('ProjectGroup');
|
||||
$groupList = $groupStore->getWhere(array(), 100, 0, array(), array('title' => 'ASC'));
|
||||
|
||||
foreach ($groupList['items'] as $group) {
|
||||
$thisGroup = array('title' => $group->getTitle());
|
||||
$projects = b8\Store\Factory::getStore('Project')->getByGroupId($group->getId());
|
||||
$thisGroup['projects'] = $projects['items'];
|
||||
$groups[] = $thisGroup;
|
||||
}
|
||||
|
||||
$layout->groups = $groups;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether we should skip auth (because it is disabled)
|
||||
* @return bool
|
||||
*/
|
||||
protected function shouldSkipAuth()
|
||||
{
|
||||
$config = b8\Config::getInstance();
|
||||
$state = (bool)$config->get('phpci.authentication_settings.state', false);
|
||||
$userId = $config->get('phpci.authentication_settings.user_id', 0);
|
||||
|
||||
if (false !== $state && 0 != (int)$userId) {
|
||||
$user = b8\Store\Factory::getStore('User')
|
||||
->getByPrimaryKey($userId);
|
||||
|
||||
if ($user) {
|
||||
$_SESSION['phpci_user'] = $user;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
79
src/PHPCI/BuildFactory.php
Normal file
79
src/PHPCI/BuildFactory.php
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
<?php
|
||||
/**
|
||||
* PHPCI - Continuous Integration for PHP
|
||||
*
|
||||
* @copyright Copyright 2014, Block 8 Limited.
|
||||
* @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md
|
||||
* @link https://www.phptesting.org/
|
||||
*/
|
||||
|
||||
namespace PHPCI;
|
||||
|
||||
use b8\Store\Factory;
|
||||
use PHPCI\Model\Build;
|
||||
|
||||
/**
|
||||
* PHPCI Build Factory - Takes in a generic "Build" and returns a type-specific build model.
|
||||
* @author Dan Cryer <dan@block8.co.uk>
|
||||
*/
|
||||
class BuildFactory
|
||||
{
|
||||
/**
|
||||
* @param $buildId
|
||||
* @return Build
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function getBuildById($buildId)
|
||||
{
|
||||
$build = Factory::getStore('Build')->getById($buildId);
|
||||
|
||||
if (empty($build)) {
|
||||
throw new \Exception('Build ID ' . $buildId . ' does not exist.');
|
||||
}
|
||||
|
||||
return self::getBuild($build);
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes a generic build and returns a type-specific build model.
|
||||
* @param Build $build The build from which to get a more specific build type.
|
||||
* @return Build
|
||||
*/
|
||||
public static function getBuild(Build $build)
|
||||
{
|
||||
$project = $build->getProject();
|
||||
|
||||
if (!empty($project)) {
|
||||
switch ($project->getType()) {
|
||||
case 'remote':
|
||||
$type = 'RemoteGitBuild';
|
||||
break;
|
||||
case 'local':
|
||||
$type = 'LocalBuild';
|
||||
break;
|
||||
case 'github':
|
||||
$type = 'GithubBuild';
|
||||
break;
|
||||
case 'bitbucket':
|
||||
$type = 'BitbucketBuild';
|
||||
break;
|
||||
case 'gitlab':
|
||||
$type = 'GitlabBuild';
|
||||
break;
|
||||
case 'hg':
|
||||
$type = 'MercurialBuild';
|
||||
break;
|
||||
case 'svn':
|
||||
$type = 'SubversionBuild';
|
||||
break;
|
||||
default:
|
||||
return $build;
|
||||
}
|
||||
|
||||
$class = '\\PHPCI\\Model\\Build\\' . $type;
|
||||
$build = new $class($build->getDataArray());
|
||||
}
|
||||
|
||||
return $build;
|
||||
}
|
||||
}
|
||||
423
src/PHPCI/Builder.php
Normal file
423
src/PHPCI/Builder.php
Normal file
|
|
@ -0,0 +1,423 @@
|
|||
<?php
|
||||
/**
|
||||
* PHPCI - Continuous Integration for PHP
|
||||
*
|
||||
* @copyright Copyright 2014, Block 8 Limited.
|
||||
* @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md
|
||||
* @link https://www.phptesting.org/
|
||||
*/
|
||||
|
||||
namespace PHPCI;
|
||||
|
||||
use PHPCI\Helper\BuildInterpolator;
|
||||
use PHPCI\Helper\Lang;
|
||||
use PHPCI\Helper\MailerFactory;
|
||||
use PHPCI\Logging\BuildLogger;
|
||||
use PHPCI\Model\Build;
|
||||
use b8\Config;
|
||||
use b8\Store\Factory;
|
||||
use Psr\Log\LoggerAwareInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Psr\Log\LogLevel;
|
||||
use PHPCI\Plugin\Util\Factory as PluginFactory;
|
||||
|
||||
/**
|
||||
* PHPCI Build Runner
|
||||
* @author Dan Cryer <dan@block8.co.uk>
|
||||
*/
|
||||
class Builder implements LoggerAwareInterface
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $buildPath;
|
||||
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
public $ignore = array();
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $directory;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $verbose = true;
|
||||
|
||||
/**
|
||||
* @var \PHPCI\Model\Build
|
||||
*/
|
||||
protected $build;
|
||||
|
||||
/**
|
||||
* @var LoggerInterface
|
||||
*/
|
||||
protected $logger;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $config;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $lastOutput;
|
||||
|
||||
/**
|
||||
* @var BuildInterpolator
|
||||
*/
|
||||
protected $interpolator;
|
||||
|
||||
/**
|
||||
* @var \PHPCI\Store\BuildStore
|
||||
*/
|
||||
protected $store;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
public $quiet = false;
|
||||
|
||||
/**
|
||||
* @var \PHPCI\Plugin\Util\Executor
|
||||
*/
|
||||
protected $pluginExecutor;
|
||||
|
||||
/**
|
||||
* @var Helper\CommandExecutor
|
||||
*/
|
||||
protected $commandExecutor;
|
||||
|
||||
/**
|
||||
* @var Logging\BuildLogger
|
||||
*/
|
||||
protected $buildLogger;
|
||||
|
||||
/**
|
||||
* Set up the builder.
|
||||
* @param \PHPCI\Model\Build $build
|
||||
* @param LoggerInterface $logger
|
||||
*/
|
||||
public function __construct(Build $build, LoggerInterface $logger = null)
|
||||
{
|
||||
$this->build = $build;
|
||||
$this->store = Factory::getStore('Build');
|
||||
|
||||
$this->buildLogger = new BuildLogger($logger, $build);
|
||||
|
||||
$pluginFactory = $this->buildPluginFactory($build);
|
||||
$pluginFactory->addConfigFromFile(PHPCI_DIR . "/pluginconfig.php");
|
||||
$this->pluginExecutor = new Plugin\Util\Executor($pluginFactory, $this->buildLogger);
|
||||
|
||||
$executorClass = 'PHPCI\Helper\UnixCommandExecutor';
|
||||
if (IS_WIN) {
|
||||
$executorClass = 'PHPCI\Helper\WindowsCommandExecutor';
|
||||
}
|
||||
|
||||
$this->commandExecutor = new $executorClass(
|
||||
$this->buildLogger,
|
||||
PHPCI_DIR,
|
||||
$this->quiet,
|
||||
$this->verbose
|
||||
);
|
||||
|
||||
$this->interpolator = new BuildInterpolator();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the config array, as read from phpci.yml
|
||||
* @param array|null $config
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function setConfigArray($config)
|
||||
{
|
||||
if (is_null($config) || !is_array($config)) {
|
||||
throw new \Exception(Lang::get('missing_phpci_yml'));
|
||||
}
|
||||
|
||||
$this->config = $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Access a variable from the phpci.yml file.
|
||||
* @param string
|
||||
* @return mixed
|
||||
*/
|
||||
public function getConfig($key)
|
||||
{
|
||||
$rtn = null;
|
||||
|
||||
if (isset($this->config[$key])) {
|
||||
$rtn = $this->config[$key];
|
||||
}
|
||||
|
||||
return $rtn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Access a variable from the config.yml
|
||||
* @param $key
|
||||
* @return mixed
|
||||
*/
|
||||
public function getSystemConfig($key)
|
||||
{
|
||||
return Config::getInstance()->get($key);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string The title of the project being built.
|
||||
*/
|
||||
public function getBuildProjectTitle()
|
||||
{
|
||||
return $this->build->getProject()->getTitle();
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the active build.
|
||||
*/
|
||||
public function execute()
|
||||
{
|
||||
// Update the build in the database, ping any external services.
|
||||
$this->build->setStatus(Build::STATUS_RUNNING);
|
||||
$this->build->setStarted(new \DateTime());
|
||||
$this->store->save($this->build);
|
||||
$this->build->sendStatusPostback();
|
||||
$success = true;
|
||||
|
||||
$previous_build = $this->build->getProject()->getPreviousBuild($this->build->getBranch());
|
||||
|
||||
$previous_state = Build::STATUS_NEW;
|
||||
|
||||
if ($previous_build) {
|
||||
$previous_state = $previous_build->getStatus();
|
||||
}
|
||||
|
||||
try {
|
||||
// Set up the build:
|
||||
$this->setupBuild();
|
||||
|
||||
// Run the core plugin stages:
|
||||
foreach (array('setup', 'test') as $stage) {
|
||||
$success &= $this->pluginExecutor->executePlugins($this->config, $stage);
|
||||
}
|
||||
|
||||
// Set the status so this can be used by complete, success and failure
|
||||
// stages.
|
||||
if ($success) {
|
||||
$this->build->setStatus(Build::STATUS_SUCCESS);
|
||||
} else {
|
||||
$this->build->setStatus(Build::STATUS_FAILED);
|
||||
}
|
||||
|
||||
|
||||
if ($success) {
|
||||
$this->pluginExecutor->executePlugins($this->config, 'success');
|
||||
|
||||
if ($previous_state == Build::STATUS_FAILED) {
|
||||
$this->pluginExecutor->executePlugins($this->config, 'fixed');
|
||||
}
|
||||
|
||||
$this->buildLogger->logSuccess(Lang::get('build_success'));
|
||||
} else {
|
||||
$this->pluginExecutor->executePlugins($this->config, 'failure');
|
||||
|
||||
if ($previous_state == Build::STATUS_SUCCESS || $previous_state == Build::STATUS_NEW) {
|
||||
$this->pluginExecutor->executePlugins($this->config, 'broken');
|
||||
}
|
||||
|
||||
$this->buildLogger->logFailure(Lang::get('build_failed'));
|
||||
}
|
||||
} catch (\Exception $ex) {
|
||||
$this->build->setStatus(Build::STATUS_FAILED);
|
||||
$this->buildLogger->logFailure(Lang::get('exception') . $ex->getMessage());
|
||||
}finally{
|
||||
// Complete stage plugins are always run
|
||||
$this->pluginExecutor->executePlugins($this->config, 'complete');
|
||||
}
|
||||
|
||||
|
||||
// Update the build in the database, ping any external services, etc.
|
||||
$this->build->sendStatusPostback();
|
||||
$this->build->setFinished(new \DateTime());
|
||||
|
||||
// Clean up:
|
||||
$this->buildLogger->log(Lang::get('removing_build'));
|
||||
$this->build->removeBuildDirectory();
|
||||
|
||||
$this->store->save($this->build);
|
||||
}
|
||||
|
||||
/**
|
||||
* Used by this class, and plugins, to execute shell commands.
|
||||
*/
|
||||
public function executeCommand()
|
||||
{
|
||||
return $this->commandExecutor->executeCommand(func_get_args());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the output from the last command run.
|
||||
*/
|
||||
public function getLastOutput()
|
||||
{
|
||||
return $this->commandExecutor->getLastOutput();
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify whether exec output should be logged.
|
||||
* @param bool $enableLog
|
||||
*/
|
||||
public function logExecOutput($enableLog = true)
|
||||
{
|
||||
$this->commandExecutor->logExecOutput = $enableLog;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a binary required by a plugin.
|
||||
* @param string $binary
|
||||
* @param bool $quiet
|
||||
*
|
||||
* @return null|string
|
||||
*/
|
||||
public function findBinary($binary, $quiet = false)
|
||||
{
|
||||
return $this->commandExecutor->findBinary($binary, $quiet = false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace every occurrence of the interpolation vars in the given string
|
||||
* Example: "This is build %PHPCI_BUILD%" => "This is build 182"
|
||||
* @param string $input
|
||||
* @return string
|
||||
*/
|
||||
public function interpolate($input)
|
||||
{
|
||||
return $this->interpolator->interpolate($input);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up a working copy of the project for building.
|
||||
*/
|
||||
protected function setupBuild()
|
||||
{
|
||||
$this->buildPath = $this->build->getBuildPath();
|
||||
|
||||
$this->interpolator->setupInterpolationVars(
|
||||
$this->build,
|
||||
$this->buildPath,
|
||||
PHPCI_URL
|
||||
);
|
||||
|
||||
$this->commandExecutor->setBuildPath($this->buildPath);
|
||||
|
||||
// Create a working copy of the project:
|
||||
if (!$this->build->createWorkingCopy($this, $this->buildPath)) {
|
||||
throw new \Exception(Lang::get('could_not_create_working'));
|
||||
}
|
||||
|
||||
// Does the project's phpci.yml request verbose mode?
|
||||
if (!isset($this->config['build_settings']['verbose']) || !$this->config['build_settings']['verbose']) {
|
||||
$this->verbose = false;
|
||||
}
|
||||
|
||||
// Does the project have any paths it wants plugins to ignore?
|
||||
if (isset($this->config['build_settings']['ignore'])) {
|
||||
$this->ignore = $this->config['build_settings']['ignore'];
|
||||
}
|
||||
|
||||
$this->buildLogger->logSuccess(Lang::get('working_copy_created', $this->buildPath));
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a logger instance on the object
|
||||
*
|
||||
* @param LoggerInterface $logger
|
||||
* @return null
|
||||
*/
|
||||
public function setLogger(LoggerInterface $logger)
|
||||
{
|
||||
$this->buildLogger->setLogger($logger);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write to the build log.
|
||||
* @param $message
|
||||
* @param string $level
|
||||
* @param array $context
|
||||
*/
|
||||
public function log($message, $level = LogLevel::INFO, $context = array())
|
||||
{
|
||||
$this->buildLogger->log($message, $level, $context);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a success-coloured message to the log.
|
||||
* @param string
|
||||
*/
|
||||
public function logSuccess($message)
|
||||
{
|
||||
$this->buildLogger->logSuccess($message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a failure-coloured message to the log.
|
||||
* @param string $message
|
||||
* @param \Exception $exception The exception that caused the error.
|
||||
*/
|
||||
public function logFailure($message, \Exception $exception = null)
|
||||
{
|
||||
$this->buildLogger->logFailure($message, $exception);
|
||||
}
|
||||
/**
|
||||
* Returns a configured instance of the plugin factory.
|
||||
*
|
||||
* @param Build $build
|
||||
* @return PluginFactory
|
||||
*/
|
||||
private function buildPluginFactory(Build $build)
|
||||
{
|
||||
$pluginFactory = new PluginFactory();
|
||||
|
||||
$self = $this;
|
||||
$pluginFactory->registerResource(
|
||||
function () use ($self) {
|
||||
return $self;
|
||||
},
|
||||
null,
|
||||
'PHPCI\Builder'
|
||||
);
|
||||
|
||||
$pluginFactory->registerResource(
|
||||
function () use ($build) {
|
||||
return $build;
|
||||
},
|
||||
null,
|
||||
'PHPCI\Model\Build'
|
||||
);
|
||||
|
||||
$logger = $this->logger;
|
||||
$pluginFactory->registerResource(
|
||||
function () use ($logger) {
|
||||
return $logger;
|
||||
},
|
||||
null,
|
||||
'Psr\Log\LoggerInterface'
|
||||
);
|
||||
|
||||
$pluginFactory->registerResource(
|
||||
function () use ($self) {
|
||||
$factory = new MailerFactory($self->getSystemConfig('phpci'));
|
||||
return $factory->getSwiftMailerFromConfig();
|
||||
},
|
||||
null,
|
||||
'Swift_Mailer'
|
||||
);
|
||||
|
||||
return $pluginFactory;
|
||||
}
|
||||
}
|
||||
82
src/PHPCI/Command/CreateAdminCommand.php
Normal file
82
src/PHPCI/Command/CreateAdminCommand.php
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
<?php
|
||||
/**
|
||||
* PHPCI - Continuous Integration for PHP
|
||||
*
|
||||
* @copyright Copyright 2014, Block 8 Limited.
|
||||
* @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md
|
||||
* @link https://www.phptesting.org/
|
||||
*/
|
||||
|
||||
namespace PHPCI\Command;
|
||||
|
||||
use PHPCI\Service\UserService;
|
||||
use PHPCI\Helper\Lang;
|
||||
use PHPCI\Store\UserStore;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* Create admin command - creates an admin user
|
||||
* @author Wogan May (@woganmay)
|
||||
* @package PHPCI
|
||||
* @subpackage Console
|
||||
*/
|
||||
class CreateAdminCommand extends Command
|
||||
{
|
||||
/**
|
||||
* @var UserStore
|
||||
*/
|
||||
protected $userStore;
|
||||
|
||||
/**
|
||||
* @param UserStore $userStore
|
||||
*/
|
||||
public function __construct(UserStore $userStore)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->userStore = $userStore;
|
||||
}
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('phpci:create-admin')
|
||||
->setDescription(Lang::get('create_admin_user'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an admin user in the existing PHPCI database
|
||||
*
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$userService = new UserService($this->userStore);
|
||||
|
||||
/** @var $dialog \Symfony\Component\Console\Helper\DialogHelper */
|
||||
$dialog = $this->getHelperSet()->get('dialog');
|
||||
|
||||
// Function to validate mail address.
|
||||
$mailValidator = function ($answer) {
|
||||
if (!filter_var($answer, FILTER_VALIDATE_EMAIL)) {
|
||||
throw new \InvalidArgumentException(Lang::get('must_be_valid_email'));
|
||||
}
|
||||
|
||||
return $answer;
|
||||
};
|
||||
|
||||
$adminEmail = $dialog->askAndValidate($output, Lang::get('enter_email'), $mailValidator, false);
|
||||
$adminName = $dialog->ask($output, Lang::get('enter_name'));
|
||||
$adminPass = $dialog->askHiddenResponse($output, Lang::get('enter_password'));
|
||||
|
||||
try {
|
||||
$userService->createUser($adminName, $adminEmail, $adminPass, true);
|
||||
$output->writeln(Lang::get('user_created'));
|
||||
} catch (\Exception $e) {
|
||||
$output->writeln(sprintf('<error>%s</error>', Lang::get('failed_to_create')));
|
||||
$output->writeln(sprintf('<error>%s</error>', $e->getMessage()));
|
||||
}
|
||||
}
|
||||
}
|
||||
85
src/PHPCI/Command/CreateBuildCommand.php
Normal file
85
src/PHPCI/Command/CreateBuildCommand.php
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
<?php
|
||||
/**
|
||||
* PHPCI - Continuous Integration for PHP
|
||||
*
|
||||
* @copyright Copyright 2014, Block 8 Limited.
|
||||
* @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md
|
||||
* @link https://www.phptesting.org/
|
||||
*/
|
||||
|
||||
namespace PHPCI\Command;
|
||||
|
||||
use PHPCI\Helper\Lang;
|
||||
use PHPCI\Service\BuildService;
|
||||
use PHPCI\Store\ProjectStore;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* Create build command - creates a build for a project
|
||||
* @author Jérémy DECOOL (@jdecool)
|
||||
* @package PHPCI
|
||||
* @subpackage Console
|
||||
*/
|
||||
class CreateBuildCommand extends Command
|
||||
{
|
||||
/**
|
||||
* @var ProjectStore
|
||||
*/
|
||||
protected $projectStore;
|
||||
|
||||
/**
|
||||
* @var BuildService
|
||||
*/
|
||||
protected $buildService;
|
||||
|
||||
/**
|
||||
* @param ProjectStore $projectStore
|
||||
*/
|
||||
public function __construct(ProjectStore $projectStore, BuildService $buildService)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->projectStore = $projectStore;
|
||||
$this->buildService = $buildService;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('phpci:create-build')
|
||||
->setDescription(Lang::get('create_build_project'))
|
||||
->addArgument('projectId', InputArgument::REQUIRED, Lang::get('project_id_argument'))
|
||||
->addOption('commit', null, InputOption::VALUE_OPTIONAL, Lang::get('commit_id_option'))
|
||||
->addOption('branch', null, InputOption::VALUE_OPTIONAL, Lang::get('branch_name_option'));
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$projectId = $input->getArgument('projectId');
|
||||
$commitId = $input->getOption('commit');
|
||||
$branch = $input->getOption('branch');
|
||||
|
||||
$project = $this->projectStore->getById($projectId);
|
||||
if (empty($project)) {
|
||||
throw new \InvalidArgumentException('Project does not exist: ' . $projectId);
|
||||
}
|
||||
|
||||
try {
|
||||
$this->buildService->createBuild($project, $commitId, $branch);
|
||||
$output->writeln(Lang::get('build_created'));
|
||||
} catch (\Exception $e) {
|
||||
$output->writeln(sprintf('<error>%s</error>', Lang::get('failed')));
|
||||
$output->writeln(sprintf('<error>%s</error>', $e->getMessage()));
|
||||
}
|
||||
}
|
||||
}
|
||||
202
src/PHPCI/Command/DaemonCommand.php
Normal file
202
src/PHPCI/Command/DaemonCommand.php
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* PHPCI - Continuous Integration for PHP
|
||||
*
|
||||
* @copyright Copyright 2014, Block 8 Limited.
|
||||
* @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md
|
||||
* @link https://www.phptesting.org/
|
||||
*/
|
||||
|
||||
namespace PHPCI\Command;
|
||||
|
||||
use Monolog\Logger;
|
||||
use PHPCI\ProcessControl\Factory;
|
||||
use PHPCI\ProcessControl\ProcessControlInterface;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* Daemon that loops and call the run-command.
|
||||
* @author Gabriel Baker <gabriel.baker@autonomicpilot.co.uk>
|
||||
* @package PHPCI
|
||||
* @subpackage Console
|
||||
*/
|
||||
class DaemonCommand extends Command
|
||||
{
|
||||
|
||||
/**
|
||||
* @var Logger
|
||||
*/
|
||||
protected $logger;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $pidFilePath;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $logFilePath;
|
||||
|
||||
/**
|
||||
* @var ProcessControlInterface
|
||||
*/
|
||||
protected $processControl;
|
||||
|
||||
public function __construct(Logger $logger, ProcessControlInterface $processControl = null, $name = null)
|
||||
{
|
||||
parent::__construct($name);
|
||||
$this->logger = $logger;
|
||||
$this->processControl = $processControl ?: Factory::getInstance();
|
||||
}
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('phpci:daemon')
|
||||
->setDescription('Initiates the daemon to run commands.')
|
||||
->addArgument(
|
||||
'state', InputArgument::REQUIRED, 'start|stop|status'
|
||||
)
|
||||
->addOption(
|
||||
'pid-file', 'p', InputOption::VALUE_REQUIRED,
|
||||
'Path of the PID file',
|
||||
implode(DIRECTORY_SEPARATOR,
|
||||
array(PHPCI_DIR, 'daemon', 'daemon.pid'))
|
||||
)
|
||||
->addOption(
|
||||
'log-file', 'l', InputOption::VALUE_REQUIRED,
|
||||
'Path of the log file',
|
||||
implode(DIRECTORY_SEPARATOR,
|
||||
array(PHPCI_DIR, 'daemon', 'daemon.log'))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loops through running.
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$this->pidFilePath = $input->getOption('pid-file');
|
||||
$this->logFilePath = $input->getOption('log-file');
|
||||
|
||||
$state = $input->getArgument('state');
|
||||
|
||||
switch ($state) {
|
||||
case 'start':
|
||||
$this->startDaemon();
|
||||
break;
|
||||
case 'stop':
|
||||
$this->stopDaemon();
|
||||
break;
|
||||
case 'status':
|
||||
$this->statusDaemon($output);
|
||||
break;
|
||||
default:
|
||||
$this->output->writeln("<error>Not a valid choice, please use start, stop or status</error>");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected function startDaemon()
|
||||
{
|
||||
$pid = $this->getRunningPid();
|
||||
if ($pid) {
|
||||
$this->logger->notice("Daemon already started", array('pid' => $pid));
|
||||
return "alreadystarted";
|
||||
}
|
||||
|
||||
$this->logger->info("Trying to start the daemon");
|
||||
|
||||
$cmd = "nohup %s/daemonise phpci:daemonise > %s 2>&1 &";
|
||||
$command = sprintf($cmd, PHPCI_DIR, $this->logFilePath);
|
||||
$output = $exitCode = null;
|
||||
exec($command, $output, $exitCode);
|
||||
|
||||
if ($exitCode !== 0) {
|
||||
$this->logger->error(sprintf("daemonise exited with status %d", $exitCode));
|
||||
return "notstarted";
|
||||
}
|
||||
|
||||
for ($i = 0; !($pid = $this->getRunningPid()) && $i < 5; $i++) {
|
||||
sleep(1);
|
||||
}
|
||||
|
||||
if (!$pid) {
|
||||
$this->logger->error("Could not start the daemon");
|
||||
return "notstarted";
|
||||
}
|
||||
|
||||
$this->logger->notice("Daemon started", array('pid' => $pid));
|
||||
return "started";
|
||||
}
|
||||
|
||||
protected function stopDaemon()
|
||||
{
|
||||
$pid = $this->getRunningPid();
|
||||
if (!$pid) {
|
||||
$this->logger->notice("Cannot stop the daemon as it is not started");
|
||||
return "notstarted";
|
||||
}
|
||||
|
||||
$this->logger->info("Trying to terminate the daemon", array('pid' => $pid));
|
||||
$this->processControl->kill($pid);
|
||||
|
||||
for ($i = 0; ($pid = $this->getRunningPid()) && $i < 5; $i++) {
|
||||
sleep(1);
|
||||
}
|
||||
|
||||
if ($pid) {
|
||||
$this->logger->warning("The daemon is resiting, trying to kill it", array('pid' => $pid));
|
||||
$this->processControl->kill($pid, true);
|
||||
|
||||
for ($i = 0; ($pid = $this->getRunningPid()) && $i < 5; $i++) {
|
||||
sleep(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$pid) {
|
||||
$this->logger->notice("Daemon stopped");
|
||||
return "stopped";
|
||||
}
|
||||
|
||||
$this->logger->error("Could not stop the daemon");
|
||||
}
|
||||
|
||||
protected function statusDaemon(OutputInterface $output)
|
||||
{
|
||||
$pid = $this->getRunningPid();
|
||||
if ($pid) {
|
||||
$output->writeln(sprintf('The daemon is running, PID: %d', $pid));
|
||||
return "running";
|
||||
}
|
||||
|
||||
$output->writeln('The daemon is not running');
|
||||
return "notrunning";
|
||||
}
|
||||
|
||||
/** Check if there is a running daemon
|
||||
*
|
||||
* @return int|null
|
||||
*/
|
||||
protected function getRunningPid()
|
||||
{
|
||||
if (!file_exists($this->pidFilePath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$pid = intval(trim(file_get_contents($this->pidFilePath)));
|
||||
|
||||
if($this->processControl->isRunning($pid, true)) {
|
||||
return $pid;
|
||||
}
|
||||
|
||||
// Not found, remove the stale PID file
|
||||
unlink($this->pidFilePath);
|
||||
}
|
||||
}
|
||||
110
src/PHPCI/Command/DaemoniseCommand.php
Normal file
110
src/PHPCI/Command/DaemoniseCommand.php
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
<?php
|
||||
/**
|
||||
* PHPCI - Continuous Integration for PHP
|
||||
*
|
||||
* @copyright Copyright 2014, Block 8 Limited.
|
||||
* @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md
|
||||
* @link https://www.phptesting.org/
|
||||
*/
|
||||
|
||||
namespace PHPCI\Command;
|
||||
|
||||
use Monolog\Logger;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\ArgvInput;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* Daemon that loops and call the run-command.
|
||||
* @author Gabriel Baker <gabriel.baker@autonomicpilot.co.uk>
|
||||
* @package PHPCI
|
||||
* @subpackage Console
|
||||
*/
|
||||
class DaemoniseCommand extends Command
|
||||
{
|
||||
/**
|
||||
* @var Logger
|
||||
*/
|
||||
protected $logger;
|
||||
|
||||
/**
|
||||
* @var OutputInterface
|
||||
*/
|
||||
protected $output;
|
||||
|
||||
/**
|
||||
* @var boolean
|
||||
*/
|
||||
protected $run;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $sleep;
|
||||
|
||||
/**
|
||||
* @param \Monolog\Logger $logger
|
||||
* @param string $name
|
||||
*/
|
||||
public function __construct(Logger $logger, $name = null)
|
||||
{
|
||||
parent::__construct($name);
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('phpci:daemonise')
|
||||
->setDescription('Starts the daemon to run commands.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Loops through running.
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$cmd = "echo %s > '%s/daemon/daemon.pid'";
|
||||
$command = sprintf($cmd, getmypid(), PHPCI_DIR);
|
||||
exec($command);
|
||||
|
||||
$this->output = $output;
|
||||
$this->run = true;
|
||||
$this->sleep = 0;
|
||||
$runner = new RunCommand($this->logger);
|
||||
$runner->setMaxBuilds(1);
|
||||
$runner->setDaemon(true);
|
||||
|
||||
$emptyInput = new ArgvInput(array());
|
||||
|
||||
while ($this->run) {
|
||||
|
||||
$buildCount = 0;
|
||||
|
||||
try {
|
||||
$buildCount = $runner->run($emptyInput, $output);
|
||||
} catch (\Exception $e) {
|
||||
$output->writeln('<error>Exception: ' . $e->getMessage() . '</error>');
|
||||
$output->writeln('<error>Line: ' . $e->getLine() . ' - File: ' . $e->getFile() . '</error>');
|
||||
}
|
||||
|
||||
if (0 == $buildCount && $this->sleep < 15) {
|
||||
$this->sleep++;
|
||||
} elseif (1 < $this->sleep) {
|
||||
$this->sleep--;
|
||||
}
|
||||
echo '.'.(0 === $buildCount?'':'build');
|
||||
sleep($this->sleep);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when log entries are made in Builder / the plugins.
|
||||
* @see \PHPCI\Builder::log()
|
||||
*/
|
||||
public function logCallback($log)
|
||||
{
|
||||
$this->output->writeln($log);
|
||||
}
|
||||
}
|
||||
48
src/PHPCI/Command/GenerateCommand.php
Normal file
48
src/PHPCI/Command/GenerateCommand.php
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
/**
|
||||
* PHPCI - Continuous Integration for PHP
|
||||
*
|
||||
* @copyright Copyright 2014, Block 8 Limited.
|
||||
* @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md
|
||||
* @link https://www.phptesting.org/
|
||||
*/
|
||||
|
||||
namespace PHPCI\Command;
|
||||
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use b8\Database;
|
||||
use b8\Database\CodeGenerator;
|
||||
|
||||
/**
|
||||
* Generate console command - Reads the database and generates models and stores.
|
||||
* @author Dan Cryer <dan@block8.co.uk>
|
||||
* @package PHPCI
|
||||
* @subpackage Console
|
||||
*/
|
||||
class GenerateCommand extends Command
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('phpci:generate')
|
||||
->setDescription('Generate models and stores from the database.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates Model and Store classes by reading database meta data.
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$gen = new CodeGenerator(
|
||||
Database::getConnection(),
|
||||
array('default' => 'PHPCI'),
|
||||
array('default' => PHPCI_DIR),
|
||||
false
|
||||
);
|
||||
|
||||
$gen->generateModels();
|
||||
$gen->generateStores();
|
||||
}
|
||||
}
|
||||
422
src/PHPCI/Command/InstallCommand.php
Normal file
422
src/PHPCI/Command/InstallCommand.php
Normal file
|
|
@ -0,0 +1,422 @@
|
|||
<?php
|
||||
/**
|
||||
* PHPCI - Continuous Integration for PHP
|
||||
*
|
||||
* @copyright Copyright 2014, Block 8 Limited.
|
||||
* @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md
|
||||
* @link https://www.phptesting.org/
|
||||
*/
|
||||
|
||||
namespace PHPCI\Command;
|
||||
|
||||
use Exception;
|
||||
use PDO;
|
||||
|
||||
use b8\Config;
|
||||
use b8\Store\Factory;
|
||||
use PHPCI\Helper\Lang;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Helper\DialogHelper;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use PHPCI\Service\UserService;
|
||||
use Symfony\Component\Console\Question\ConfirmationQuestion;
|
||||
|
||||
/**
|
||||
* Install console command - Installs PHPCI.
|
||||
* @author Dan Cryer <dan@block8.co.uk>
|
||||
* @package PHPCI
|
||||
* @subpackage Console
|
||||
*/
|
||||
class InstallCommand extends Command
|
||||
{
|
||||
protected $configFilePath;
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$defaultPath = PHPCI_DIR . 'PHPCI/config.yml';
|
||||
|
||||
$this
|
||||
->setName('phpci:install')
|
||||
->addOption('url', null, InputOption::VALUE_OPTIONAL, Lang::get('installation_url'))
|
||||
->addOption('db-host', null, InputOption::VALUE_OPTIONAL, Lang::get('db_host'))
|
||||
->addOption('db-name', null, InputOption::VALUE_OPTIONAL, Lang::get('db_name'))
|
||||
->addOption('db-user', null, InputOption::VALUE_OPTIONAL, Lang::get('db_user'))
|
||||
->addOption('db-pass', null, InputOption::VALUE_OPTIONAL, Lang::get('db_pass'))
|
||||
->addOption('admin-name', null, InputOption::VALUE_OPTIONAL, Lang::get('admin_name'))
|
||||
->addOption('admin-pass', null, InputOption::VALUE_OPTIONAL, Lang::get('admin_pass'))
|
||||
->addOption('admin-mail', null, InputOption::VALUE_OPTIONAL, Lang::get('admin_email'))
|
||||
->addOption('config-path', null, InputOption::VALUE_OPTIONAL, Lang::get('config_path'), $defaultPath)
|
||||
->addOption('queue-disabled', null, InputOption::VALUE_NONE, 'Don\'t ask for queue details')
|
||||
->addOption('queue-server', null, InputOption::VALUE_OPTIONAL, 'Beanstalkd queue server hostname')
|
||||
->addOption('queue-name', null, InputOption::VALUE_OPTIONAL, 'Beanstalkd queue name')
|
||||
->setDescription(Lang::get('install_phpci'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs PHPCI - Can be run more than once as long as you ^C instead of entering an email address.
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$this->configFilePath = $input->getOption('config-path');
|
||||
|
||||
if (!$this->verifyNotInstalled($output)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$output->writeln('');
|
||||
$output->writeln('<info>******************</info>');
|
||||
$output->writeln('<info> '.Lang::get('welcome_to_phpci').'</info>');
|
||||
$output->writeln('<info>******************</info>');
|
||||
$output->writeln('');
|
||||
|
||||
$this->checkRequirements($output);
|
||||
|
||||
$output->writeln(Lang::get('please_answer'));
|
||||
$output->writeln('-------------------------------------');
|
||||
$output->writeln('');
|
||||
|
||||
// ----
|
||||
// Get MySQL connection information and verify that it works:
|
||||
// ----
|
||||
$connectionVerified = false;
|
||||
|
||||
while (!$connectionVerified) {
|
||||
$db = $this->getDatabaseInformation($input, $output);
|
||||
|
||||
$connectionVerified = $this->verifyDatabaseDetails($db, $output);
|
||||
}
|
||||
|
||||
$output->writeln('');
|
||||
|
||||
$conf = array();
|
||||
$conf['b8']['database'] = $db;
|
||||
|
||||
// ----
|
||||
// Get basic installation details (URL, etc)
|
||||
// ----
|
||||
$conf['phpci'] = $this->getPhpciConfigInformation($input, $output);
|
||||
|
||||
$this->writeConfigFile($conf);
|
||||
$this->setupDatabase($output);
|
||||
$admin = $this->getAdminInformation($input, $output);
|
||||
$this->createAdminUser($admin, $output);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check PHP version, required modules and for disabled functions.
|
||||
*
|
||||
* @param OutputInterface $output
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function checkRequirements(OutputInterface $output)
|
||||
{
|
||||
$output->write('Checking requirements...');
|
||||
$errors = false;
|
||||
|
||||
// Check PHP version:
|
||||
if (!(version_compare(PHP_VERSION, '5.3.8') >= 0)) {
|
||||
$output->writeln('');
|
||||
$output->writeln('<error>'.Lang::get('phpci_php_req').'</error>');
|
||||
$errors = true;
|
||||
}
|
||||
|
||||
// Check required extensions are present:
|
||||
$requiredExtensions = array('PDO', 'pdo_mysql');
|
||||
|
||||
foreach ($requiredExtensions as $extension) {
|
||||
if (!extension_loaded($extension)) {
|
||||
$output->writeln('');
|
||||
$output->writeln('<error>'.Lang::get('extension_required', $extension).'</error>');
|
||||
$errors = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Check required functions are callable:
|
||||
$requiredFunctions = array('exec', 'shell_exec');
|
||||
|
||||
foreach ($requiredFunctions as $function) {
|
||||
if (!function_exists($function)) {
|
||||
$output->writeln('');
|
||||
$output->writeln('<error>'.Lang::get('function_required', $function).'</error>');
|
||||
$errors = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('password_hash')) {
|
||||
$output->writeln('');
|
||||
$output->writeln('<error>'.Lang::get('function_required', $function).'</error>');
|
||||
$errors = true;
|
||||
}
|
||||
|
||||
if ($errors) {
|
||||
throw new Exception(Lang::get('requirements_not_met'));
|
||||
}
|
||||
|
||||
$output->writeln(' <info>'.Lang::get('ok').'</info>');
|
||||
$output->writeln('');
|
||||
}
|
||||
|
||||
/**
|
||||
* Load information for admin user form CLI options or ask info to user.
|
||||
*
|
||||
* @param InputInterface $input
|
||||
* @param OutputInterface $output
|
||||
* @return array
|
||||
*/
|
||||
protected function getAdminInformation(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$admin = array();
|
||||
|
||||
/**
|
||||
* @var \Symfony\Component\Console\Helper\DialogHelper
|
||||
*/
|
||||
$dialog = $this->getHelperSet()->get('dialog');
|
||||
|
||||
// Function to validate mail address.
|
||||
$mailValidator = function ($answer) {
|
||||
if (!filter_var($answer, FILTER_VALIDATE_EMAIL)) {
|
||||
throw new \InvalidArgumentException(Lang::get('must_be_valid_email'));
|
||||
}
|
||||
|
||||
return $answer;
|
||||
};
|
||||
|
||||
if ($adminEmail = $input->getOption('admin-mail')) {
|
||||
$adminEmail = $mailValidator($adminEmail);
|
||||
} else {
|
||||
$adminEmail = $dialog->askAndValidate($output, Lang::get('enter_email'), $mailValidator, false);
|
||||
}
|
||||
if (!$adminName = $input->getOption('admin-name')) {
|
||||
$adminName = $dialog->ask($output, Lang::get('enter_name'));
|
||||
}
|
||||
if (!$adminPass = $input->getOption('admin-pass')) {
|
||||
$adminPass = $dialog->askHiddenResponse($output, Lang::get('enter_password'));
|
||||
}
|
||||
|
||||
$admin['mail'] = $adminEmail;
|
||||
$admin['name'] = $adminName;
|
||||
$admin['pass'] = $adminPass;
|
||||
|
||||
return $admin;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load configuration for PHPCI form CLI options or ask info to user.
|
||||
*
|
||||
* @param InputInterface $input
|
||||
* @param OutputInterface $output
|
||||
* @return array
|
||||
*/
|
||||
protected function getPhpciConfigInformation(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$phpci = array();
|
||||
|
||||
/**
|
||||
* @var \Symfony\Component\Console\Helper\DialogHelper
|
||||
*/
|
||||
$dialog = $this->getHelperSet()->get('dialog');
|
||||
|
||||
// FUnction do validate URL.
|
||||
$urlValidator = function ($answer) {
|
||||
if (!filter_var($answer, FILTER_VALIDATE_URL)) {
|
||||
throw new Exception(Lang::get('must_be_valid_url'));
|
||||
}
|
||||
|
||||
return rtrim($answer, '/');
|
||||
};
|
||||
|
||||
if ($url = $input->getOption('url')) {
|
||||
$url = $urlValidator($url);
|
||||
} else {
|
||||
$url = $dialog->askAndValidate($output, Lang::get('enter_phpci_url'), $urlValidator, false);
|
||||
}
|
||||
|
||||
$phpci['url'] = $url;
|
||||
$phpci['worker'] = $this->getQueueInformation($input, $output, $dialog);
|
||||
|
||||
return $phpci;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the user wants to use a queue, get the necessary details.
|
||||
* @param InputInterface $input
|
||||
* @param OutputInterface $output
|
||||
* @param DialogHelper $dialog
|
||||
* @return array
|
||||
*/
|
||||
protected function getQueueInformation(InputInterface $input, OutputInterface $output, DialogHelper $dialog)
|
||||
{
|
||||
if ($input->getOption('queue-disabled')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$rtn = [];
|
||||
|
||||
$helper = $this->getHelper('question');
|
||||
$question = new ConfirmationQuestion('Use beanstalkd to manage build queue? ', true);
|
||||
|
||||
if (!$helper->ask($input, $output, $question)) {
|
||||
$output->writeln('<error>Skipping beanstalkd configuration.</error>');
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!$rtn['host'] = $input->getOption('queue-server')) {
|
||||
$rtn['host'] = $dialog->ask($output, 'Enter your beanstalkd hostname [localhost]: ', 'localhost');
|
||||
}
|
||||
|
||||
if (!$rtn['queue'] = $input->getOption('queue-name')) {
|
||||
$rtn['queue'] = $dialog->ask($output, 'Enter the queue (tube) name to use [phpci]: ', 'phpci');
|
||||
}
|
||||
|
||||
return $rtn;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load configuration for DB form CLI options or ask info to user.
|
||||
*
|
||||
* @param InputInterface $input
|
||||
* @param OutputInterface $output
|
||||
* @return array
|
||||
*/
|
||||
protected function getDatabaseInformation(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$db = array();
|
||||
|
||||
/**
|
||||
* @var \Symfony\Component\Console\Helper\DialogHelper
|
||||
*/
|
||||
$dialog = $this->getHelperSet()->get('dialog');
|
||||
|
||||
if (!$dbHost = $input->getOption('db-host')) {
|
||||
$dbHost = $dialog->ask($output, Lang::get('enter_db_host'), 'localhost');
|
||||
}
|
||||
|
||||
if (!$dbName = $input->getOption('db-name')) {
|
||||
$dbName = $dialog->ask($output, Lang::get('enter_db_name'), 'phpci');
|
||||
}
|
||||
|
||||
if (!$dbUser = $input->getOption('db-user')) {
|
||||
$dbUser = $dialog->ask($output, Lang::get('enter_db_user'), 'phpci');
|
||||
}
|
||||
|
||||
if (!$dbPass = $input->getOption('db-pass')) {
|
||||
$dbPass = $dialog->askHiddenResponse($output, Lang::get('enter_db_pass'));
|
||||
}
|
||||
|
||||
$db['servers']['read'] = $dbHost;
|
||||
$db['servers']['write'] = $dbHost;
|
||||
$db['name'] = $dbName;
|
||||
$db['username'] = $dbUser;
|
||||
$db['password'] = $dbPass;
|
||||
|
||||
return $db;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try and connect to MySQL using the details provided.
|
||||
* @param array $db
|
||||
* @param OutputInterface $output
|
||||
* @return bool
|
||||
*/
|
||||
protected function verifyDatabaseDetails(array $db, OutputInterface $output)
|
||||
{
|
||||
try {
|
||||
$pdo = new PDO(
|
||||
'mysql:host='.$db['servers']['write'].';dbname='.$db['name'],
|
||||
$db['username'],
|
||||
$db['password'],
|
||||
array(
|
||||
\PDO::ATTR_PERSISTENT => false,
|
||||
\PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
|
||||
\PDO::ATTR_TIMEOUT => 2,
|
||||
\PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\'',
|
||||
)
|
||||
);
|
||||
|
||||
unset($pdo);
|
||||
|
||||
return true;
|
||||
|
||||
} catch (Exception $ex) {
|
||||
$output->writeln('<error>'.Lang::get('could_not_connect').'</error>');
|
||||
$output->writeln('<error>' . $ex->getMessage() . '</error>');
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the PHPCI config.yml file.
|
||||
* @param array $config
|
||||
*/
|
||||
protected function writeConfigFile(array $config)
|
||||
{
|
||||
$dumper = new \Symfony\Component\Yaml\Dumper();
|
||||
$yaml = $dumper->dump($config, 4);
|
||||
|
||||
file_put_contents($this->configFilePath, $yaml);
|
||||
}
|
||||
|
||||
protected function setupDatabase(OutputInterface $output)
|
||||
{
|
||||
$output->write(Lang::get('setting_up_db'));
|
||||
|
||||
$phinxBinary = escapeshellarg(PHPCI_DIR . 'vendor/bin/phinx');
|
||||
$phinxScript = escapeshellarg(PHPCI_DIR . 'phinx.php');
|
||||
shell_exec($phinxBinary . ' migrate -c ' . $phinxScript);
|
||||
|
||||
$output->writeln('<info>'.Lang::get('ok').'</info>');
|
||||
}
|
||||
|
||||
/**
|
||||
* Create admin user using information loaded before.
|
||||
*
|
||||
* @param array $admin
|
||||
* @param OutputInterface $output
|
||||
*/
|
||||
protected function createAdminUser($admin, $output)
|
||||
{
|
||||
try {
|
||||
$this->reloadConfig();
|
||||
|
||||
$userStore = Factory::getStore('User');
|
||||
$userService = new UserService($userStore);
|
||||
$userService->createUser($admin['name'], $admin['mail'], $admin['pass'], 1);
|
||||
|
||||
$output->writeln('<info>'.Lang::get('user_created').'</info>');
|
||||
} catch (\Exception $ex) {
|
||||
$output->writeln('<error>'.Lang::get('failed_to_create').'</error>');
|
||||
$output->writeln('<error>' . $ex->getMessage() . '</error>');
|
||||
}
|
||||
}
|
||||
|
||||
protected function reloadConfig()
|
||||
{
|
||||
$config = Config::getInstance();
|
||||
|
||||
if (file_exists($this->configFilePath)) {
|
||||
$config->loadYaml($this->configFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param OutputInterface $output
|
||||
* @return bool
|
||||
*/
|
||||
protected function verifyNotInstalled(OutputInterface $output)
|
||||
{
|
||||
if (file_exists($this->configFilePath)) {
|
||||
$content = file_get_contents($this->configFilePath);
|
||||
|
||||
if (!empty($content)) {
|
||||
$output->writeln('<error>'.Lang::get('config_exists').'</error>');
|
||||
$output->writeln('<error>'.Lang::get('update_instead').'</error>');
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
105
src/PHPCI/Command/PollCommand.php
Normal file
105
src/PHPCI/Command/PollCommand.php
Normal file
|
|
@ -0,0 +1,105 @@
|
|||
<?php
|
||||
/**
|
||||
* PHPCI - Continuous Integration for PHP
|
||||
*
|
||||
* @copyright Copyright 2014, Block 8 Limited.
|
||||
* @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md
|
||||
* @link https://www.phptesting.org/
|
||||
*/
|
||||
|
||||
namespace PHPCI\Command;
|
||||
|
||||
use b8\Store\Factory;
|
||||
use b8\HttpClient;
|
||||
use Monolog\Logger;
|
||||
use PHPCI\Helper\Lang;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Yaml\Parser;
|
||||
use PHPCI\Model\Build;
|
||||
|
||||
/**
|
||||
* Run console command - Poll github for latest commit id
|
||||
* @author Jimmy Cleuren <jimmy.cleuren@gmail.com>
|
||||
* @package PHPCI
|
||||
* @subpackage Console
|
||||
*/
|
||||
class PollCommand extends Command
|
||||
{
|
||||
/**
|
||||
* @var \Monolog\Logger
|
||||
*/
|
||||
protected $logger;
|
||||
|
||||
public function __construct(Logger $logger, $name = null)
|
||||
{
|
||||
parent::__construct($name);
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('phpci:poll-github')
|
||||
->setDescription(Lang::get('poll_github'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Pulls all pending builds from the database and runs them.
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$parser = new Parser();
|
||||
$yaml = file_get_contents(APPLICATION_PATH . 'PHPCI/config.yml');
|
||||
$this->settings = $parser->parse($yaml);
|
||||
|
||||
$token = $this->settings['phpci']['github']['token'];
|
||||
|
||||
if (!$token) {
|
||||
$this->logger->error(Lang::get('no_token'));
|
||||
return;
|
||||
}
|
||||
|
||||
$buildStore = Factory::getStore('Build');
|
||||
|
||||
$this->logger->addInfo(Lang::get('finding_projects'));
|
||||
$projectStore = Factory::getStore('Project');
|
||||
$result = $projectStore->getWhere();
|
||||
$this->logger->addInfo(Lang::get('found_n_projects', count($result['items'])));
|
||||
|
||||
foreach ($result['items'] as $project) {
|
||||
$http = new HttpClient('https://api.github.com');
|
||||
$commits = $http->get('/repos/' . $project->getReference() . '/commits', array('access_token' => $token));
|
||||
|
||||
$last_commit = $commits['body'][0]['sha'];
|
||||
$last_committer = $commits['body'][0]['commit']['committer']['email'];
|
||||
$message = $commits['body'][0]['commit']['message'];
|
||||
|
||||
$this->logger->info(Lang::get('last_commit_is', $project->getTitle(), $last_commit));
|
||||
|
||||
if ($project->getLastCommit() != $last_commit && $last_commit != "") {
|
||||
$this->logger->info(
|
||||
Lang::get('adding_new_build')
|
||||
);
|
||||
|
||||
$build = new Build();
|
||||
$build->setProjectId($project->getId());
|
||||
$build->setCommitId($last_commit);
|
||||
$build->setStatus(Build::STATUS_NEW);
|
||||
$build->setBranch($project->getBranch());
|
||||
$build->setCreated(new \DateTime());
|
||||
$build->setCommitMessage($message);
|
||||
if (!empty($last_committer)) {
|
||||
$build->setCommitterEmail($last_committer);
|
||||
}
|
||||
$buildStore->save($build);
|
||||
|
||||
$project->setLastCommit($last_commit);
|
||||
$projectStore->save($project);
|
||||
}
|
||||
}
|
||||
|
||||
$this->logger->addInfo(Lang::get('finished_processing_builds'));
|
||||
}
|
||||
}
|
||||
93
src/PHPCI/Command/RebuildCommand.php
Normal file
93
src/PHPCI/Command/RebuildCommand.php
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
<?php
|
||||
/**
|
||||
* PHPCI - Continuous Integration for PHP
|
||||
*
|
||||
* @copyright Copyright 2014, Block 8 Limited.
|
||||
* @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md
|
||||
* @link https://www.phptesting.org/
|
||||
*/
|
||||
|
||||
namespace PHPCI\Command;
|
||||
|
||||
use b8\Store\Factory;
|
||||
use Monolog\Logger;
|
||||
use PHPCI\Service\BuildService;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\ArgvInput;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* Re-runs the last run build.
|
||||
* @author Dan Cryer <dan@block8.co.uk>
|
||||
* @package PHPCI
|
||||
* @subpackage Console
|
||||
*/
|
||||
class RebuildCommand extends Command
|
||||
{
|
||||
/**
|
||||
* @var Logger
|
||||
*/
|
||||
protected $logger;
|
||||
|
||||
/**
|
||||
* @var OutputInterface
|
||||
*/
|
||||
protected $output;
|
||||
|
||||
/**
|
||||
* @var boolean
|
||||
*/
|
||||
protected $run;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $sleep;
|
||||
|
||||
/**
|
||||
* @param \Monolog\Logger $logger
|
||||
* @param string $name
|
||||
*/
|
||||
public function __construct(Logger $logger, $name = null)
|
||||
{
|
||||
parent::__construct($name);
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('phpci:rebuild')
|
||||
->setDescription('Re-runs the last run build.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Loops through running.
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$runner = new RunCommand($this->logger);
|
||||
$runner->setMaxBuilds(1);
|
||||
$runner->setDaemon(false);
|
||||
|
||||
/** @var \PHPCI\Store\BuildStore $store */
|
||||
$store = Factory::getStore('Build');
|
||||
$service = new BuildService($store);
|
||||
|
||||
$builds = $store->getLatestBuilds(null, 1);
|
||||
$lastBuild = array_shift($builds);
|
||||
$service->createDuplicateBuild($lastBuild);
|
||||
|
||||
$runner->run(new ArgvInput(array()), $output);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when log entries are made in Builder / the plugins.
|
||||
* @see \PHPCI\Builder::log()
|
||||
*/
|
||||
public function logCallback($log)
|
||||
{
|
||||
$this->output->writeln($log);
|
||||
}
|
||||
}
|
||||
85
src/PHPCI/Command/RebuildQueueCommand.php
Normal file
85
src/PHPCI/Command/RebuildQueueCommand.php
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
<?php
|
||||
/**
|
||||
* PHPCI - Continuous Integration for PHP
|
||||
*
|
||||
* @copyright Copyright 2015, Block 8 Limited.
|
||||
* @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md
|
||||
* @link https://www.phptesting.org/
|
||||
*/
|
||||
|
||||
namespace PHPCI\Command;
|
||||
|
||||
use b8\Config;
|
||||
use b8\Store\Factory;
|
||||
use Monolog\Logger;
|
||||
use PHPCI\BuildFactory;
|
||||
use PHPCI\Helper\Lang;
|
||||
use PHPCI\Logging\OutputLogHandler;
|
||||
use PHPCI\Service\BuildService;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
|
||||
/**
|
||||
* @author Dan Cryer <dan@block8.co.uk>
|
||||
* @package PHPCI
|
||||
* @subpackage Console
|
||||
*/
|
||||
class RebuildQueueCommand extends Command
|
||||
{
|
||||
/**
|
||||
* @var OutputInterface
|
||||
*/
|
||||
protected $output;
|
||||
|
||||
/**
|
||||
* @var Logger
|
||||
*/
|
||||
protected $logger;
|
||||
|
||||
/**
|
||||
* @param \Monolog\Logger $logger
|
||||
* @param string $name
|
||||
*/
|
||||
public function __construct(Logger $logger, $name = null)
|
||||
{
|
||||
parent::__construct($name);
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('phpci:rebuild-queue')
|
||||
->setDescription('Rebuilds the PHPCI worker queue.');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$this->output = $output;
|
||||
|
||||
// For verbose mode we want to output all informational and above
|
||||
// messages to the symphony output interface.
|
||||
if ($input->hasOption('verbose') && $input->getOption('verbose')) {
|
||||
$this->logger->pushHandler(
|
||||
new OutputLogHandler($this->output, Logger::INFO)
|
||||
);
|
||||
}
|
||||
|
||||
$store = Factory::getStore('Build');
|
||||
$result = $store->getByStatus(0);
|
||||
|
||||
$this->logger->addInfo(Lang::get('found_n_builds', count($result['items'])));
|
||||
|
||||
$buildService = new BuildService($store);
|
||||
|
||||
while (count($result['items'])) {
|
||||
$build = array_shift($result['items']);
|
||||
$build = BuildFactory::getBuild($build);
|
||||
|
||||
$this->logger->addInfo('Added build #' . $build->getId() . ' to queue.');
|
||||
$buildService->addBuildToQueue($build);
|
||||
}
|
||||
}
|
||||
}
|
||||
178
src/PHPCI/Command/RunCommand.php
Normal file
178
src/PHPCI/Command/RunCommand.php
Normal file
|
|
@ -0,0 +1,178 @@
|
|||
<?php
|
||||
/**
|
||||
* PHPCI - Continuous Integration for PHP
|
||||
*
|
||||
* @copyright Copyright 2014, Block 8 Limited.
|
||||
* @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md
|
||||
* @link https://www.phptesting.org/
|
||||
*/
|
||||
|
||||
namespace PHPCI\Command;
|
||||
|
||||
use b8\Config;
|
||||
use Monolog\Logger;
|
||||
use PHPCI\Helper\Lang;
|
||||
use PHPCI\Logging\BuildDBLogHandler;
|
||||
use PHPCI\Logging\LoggedBuildContextTidier;
|
||||
use PHPCI\Logging\OutputLogHandler;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use b8\Store\Factory;
|
||||
use PHPCI\Builder;
|
||||
use PHPCI\BuildFactory;
|
||||
use PHPCI\Model\Build;
|
||||
|
||||
/**
|
||||
* Run console command - Runs any pending builds.
|
||||
* @author Dan Cryer <dan@block8.co.uk>
|
||||
* @package PHPCI
|
||||
* @subpackage Console
|
||||
*/
|
||||
class RunCommand extends Command
|
||||
{
|
||||
/**
|
||||
* @var OutputInterface
|
||||
*/
|
||||
protected $output;
|
||||
|
||||
/**
|
||||
* @var Logger
|
||||
*/
|
||||
protected $logger;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $maxBuilds = 100;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $isFromDaemon = false;
|
||||
|
||||
/**
|
||||
* @param \Monolog\Logger $logger
|
||||
* @param string $name
|
||||
*/
|
||||
public function __construct(Logger $logger, $name = null)
|
||||
{
|
||||
parent::__construct($name);
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('phpci:run-builds')
|
||||
->setDescription(Lang::get('run_all_pending'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Pulls all pending builds from the database and runs them.
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$this->output = $output;
|
||||
|
||||
// For verbose mode we want to output all informational and above
|
||||
// messages to the symphony output interface.
|
||||
if ($input->hasOption('verbose') && $input->getOption('verbose')) {
|
||||
$this->logger->pushHandler(
|
||||
new OutputLogHandler($this->output, Logger::INFO)
|
||||
);
|
||||
}
|
||||
|
||||
$running = $this->validateRunningBuilds();
|
||||
|
||||
$this->logger->pushProcessor(new LoggedBuildContextTidier());
|
||||
$this->logger->addInfo(Lang::get('finding_builds'));
|
||||
$store = Factory::getStore('Build');
|
||||
$result = $store->getByStatus(0, $this->maxBuilds);
|
||||
$this->logger->addInfo(Lang::get('found_n_builds', count($result['items'])));
|
||||
|
||||
$builds = 0;
|
||||
|
||||
while (count($result['items'])) {
|
||||
$build = array_shift($result['items']);
|
||||
$build = BuildFactory::getBuild($build);
|
||||
|
||||
// Skip build (for now) if there's already a build running in that project:
|
||||
if (!$this->isFromDaemon && in_array($build->getProjectId(), $running)) {
|
||||
$this->logger->addInfo(Lang::get('skipping_build', $build->getId()));
|
||||
$result['items'][] = $build;
|
||||
|
||||
// Re-run build validator:
|
||||
$running = $this->validateRunningBuilds();
|
||||
continue;
|
||||
}
|
||||
|
||||
$builds++;
|
||||
|
||||
try {
|
||||
// Logging relevant to this build should be stored
|
||||
// against the build itself.
|
||||
$buildDbLog = new BuildDBLogHandler($build, Logger::INFO);
|
||||
$this->logger->pushHandler($buildDbLog);
|
||||
|
||||
$builder = new Builder($build, $this->logger);
|
||||
$builder->execute();
|
||||
|
||||
// After execution we no longer want to record the information
|
||||
// back to this specific build so the handler should be removed.
|
||||
$this->logger->popHandler($buildDbLog);
|
||||
} catch (\Exception $ex) {
|
||||
$build->setStatus(Build::STATUS_FAILED);
|
||||
$build->setFinished(new \DateTime());
|
||||
$build->setLog($build->getLog() . PHP_EOL . PHP_EOL . $ex->getMessage());
|
||||
$store->save($build);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$this->logger->addInfo(Lang::get('finished_processing_builds'));
|
||||
|
||||
return $builds;
|
||||
}
|
||||
|
||||
public function setMaxBuilds($numBuilds)
|
||||
{
|
||||
$this->maxBuilds = (int)$numBuilds;
|
||||
}
|
||||
|
||||
public function setDaemon($fromDaemon)
|
||||
{
|
||||
$this->isFromDaemon = (bool)$fromDaemon;
|
||||
}
|
||||
|
||||
protected function validateRunningBuilds()
|
||||
{
|
||||
/** @var \PHPCI\Store\BuildStore $store */
|
||||
$store = Factory::getStore('Build');
|
||||
$running = $store->getByStatus(1);
|
||||
$rtn = array();
|
||||
|
||||
$timeout = Config::getInstance()->get('phpci.build.failed_after', 1800);
|
||||
|
||||
foreach ($running['items'] as $build) {
|
||||
/** @var \PHPCI\Model\Build $build */
|
||||
$build = BuildFactory::getBuild($build);
|
||||
|
||||
$now = time();
|
||||
$start = $build->getStarted()->getTimestamp();
|
||||
|
||||
if (($now - $start) > $timeout) {
|
||||
$this->logger->addInfo(Lang::get('marked_as_failed', $build->getId()));
|
||||
$build->setStatus(Build::STATUS_FAILED);
|
||||
$build->setFinished(new \DateTime());
|
||||
$store->save($build);
|
||||
$build->removeBuildDirectory();
|
||||
continue;
|
||||
}
|
||||
|
||||
$rtn[$build->getProjectId()] = true;
|
||||
}
|
||||
|
||||
return $rtn;
|
||||
}
|
||||
}
|
||||
68
src/PHPCI/Command/UpdateCommand.php
Normal file
68
src/PHPCI/Command/UpdateCommand.php
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
<?php
|
||||
/**
|
||||
* PHPCI - Continuous Integration for PHP
|
||||
*
|
||||
* @copyright Copyright 2014, Block 8 Limited.
|
||||
* @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md
|
||||
* @link https://www.phptesting.org/
|
||||
*/
|
||||
|
||||
namespace PHPCI\Command;
|
||||
|
||||
use b8\Config;
|
||||
use Monolog\Logger;
|
||||
use PHPCI\Helper\Lang;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
/**
|
||||
* Generate console command - Reads the database and generates models and stores.
|
||||
* @author Dan Cryer <dan@block8.co.uk>
|
||||
* @package PHPCI
|
||||
* @subpackage Console
|
||||
*/
|
||||
class UpdateCommand extends Command
|
||||
{
|
||||
/**
|
||||
* @var \Monolog\Logger
|
||||
*/
|
||||
protected $logger;
|
||||
|
||||
public function __construct(Logger $logger, $name = null)
|
||||
{
|
||||
parent::__construct($name);
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('phpci:update')
|
||||
->setDescription(Lang::get('update_phpci'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates Model and Store classes by reading database meta data.
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
if (!$this->verifyInstalled($output)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$output->write(Lang::get('updating_phpci'));
|
||||
|
||||
shell_exec(PHPCI_DIR . 'vendor/bin/phinx migrate -c "' . PHPCI_DIR . 'phinx.php"');
|
||||
|
||||
$output->writeln('<info>'.Lang::get('ok').'</info>');
|
||||
}
|
||||
|
||||
protected function verifyInstalled(OutputInterface $output)
|
||||
{
|
||||
$config = Config::getInstance();
|
||||
$phpciUrl = $config->get('phpci.url');
|
||||
|
||||
return !empty($phpciUrl);
|
||||
}
|
||||
}
|
||||
87
src/PHPCI/Command/WorkerCommand.php
Normal file
87
src/PHPCI/Command/WorkerCommand.php
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
<?php
|
||||
/**
|
||||
* PHPCI - Continuous Integration for PHP
|
||||
*
|
||||
* @copyright Copyright 2015, Block 8 Limited.
|
||||
* @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md
|
||||
* @link https://www.phptesting.org/
|
||||
*/
|
||||
|
||||
namespace PHPCI\Command;
|
||||
|
||||
use b8\Config;
|
||||
use Monolog\Logger;
|
||||
use PHPCI\Logging\OutputLogHandler;
|
||||
use PHPCI\Worker\BuildWorker;
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
|
||||
/**
|
||||
* Worker Command - Starts the BuildWorker, which pulls jobs from beanstalkd
|
||||
* @author Dan Cryer <dan@block8.co.uk>
|
||||
* @package PHPCI
|
||||
* @subpackage Console
|
||||
*/
|
||||
class WorkerCommand extends Command
|
||||
{
|
||||
/**
|
||||
* @var OutputInterface
|
||||
*/
|
||||
protected $output;
|
||||
|
||||
/**
|
||||
* @var Logger
|
||||
*/
|
||||
protected $logger;
|
||||
|
||||
/**
|
||||
* @param \Monolog\Logger $logger
|
||||
* @param string $name
|
||||
*/
|
||||
public function __construct(Logger $logger, $name = null)
|
||||
{
|
||||
parent::__construct($name);
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('phpci:worker')
|
||||
->setDescription('Runs the PHPCI build worker.')
|
||||
->addOption('debug', null, null, 'Run PHPCI in Debug Mode');
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$this->output = $output;
|
||||
|
||||
// For verbose mode we want to output all informational and above
|
||||
// messages to the symphony output interface.
|
||||
if ($input->hasOption('verbose') && $input->getOption('verbose')) {
|
||||
$this->logger->pushHandler(
|
||||
new OutputLogHandler($this->output, Logger::INFO)
|
||||
);
|
||||
}
|
||||
|
||||
// Allow PHPCI to run in "debug mode"
|
||||
if ($input->hasOption('debug') && $input->getOption('debug')) {
|
||||
$output->writeln('<comment>Debug mode enabled.</comment>');
|
||||
define('PHPCI_DEBUG_MODE', true);
|
||||
}
|
||||
|
||||
$config = Config::getInstance()->get('phpci.worker', []);
|
||||
|
||||
if (empty($config['host']) || empty($config['queue'])) {
|
||||
$error = 'The worker is not configured. You must set a host and queue in your config.yml file.';
|
||||
throw new \Exception($error);
|
||||
}
|
||||
|
||||
$worker = new BuildWorker($config['host'], $config['queue']);
|
||||
$worker->setLogger($this->logger);
|
||||
$worker->setMaxJobs(Config::getInstance()->get('phpci.worker.max_jobs', -1));
|
||||
$worker->startWorker();
|
||||
}
|
||||
}
|
||||
129
src/PHPCI/Controller.php
Normal file
129
src/PHPCI/Controller.php
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
<?php
|
||||
/**
|
||||
* PHPCI - Continuous Integration for PHP
|
||||
*
|
||||
* @copyright Copyright 2014, Block 8 Limited.
|
||||
* @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md
|
||||
* @link https://www.phptesting.org/
|
||||
*/
|
||||
|
||||
namespace PHPCI;
|
||||
|
||||
use b8\Config;
|
||||
use b8\Exception\HttpException\ForbiddenException;
|
||||
use b8\Http\Request;
|
||||
use b8\Http\Response;
|
||||
use b8\View;
|
||||
|
||||
/**
|
||||
* PHPCI Base Controller
|
||||
* @package PHPCI
|
||||
*/
|
||||
class Controller extends \b8\Controller
|
||||
{
|
||||
/**
|
||||
* @var \b8\View
|
||||
*/
|
||||
protected $controllerView;
|
||||
|
||||
/**
|
||||
* @var \b8\View
|
||||
*/
|
||||
protected $view;
|
||||
|
||||
/**
|
||||
* @var \b8\View
|
||||
*/
|
||||
public $layout;
|
||||
|
||||
/**
|
||||
* Initialise the controller.
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
// Extended by actual controllers.
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Config $config
|
||||
* @param Request $request
|
||||
* @param Response $response
|
||||
*/
|
||||
public function __construct(Config $config, Request $request, Response $response)
|
||||
{
|
||||
parent::__construct($config, $request, $response);
|
||||
|
||||
$class = explode('\\', get_class($this));
|
||||
$this->className = substr(array_pop($class), 0, -10);
|
||||
$this->setControllerView();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the view that this controller should use.
|
||||
*/
|
||||
protected function setControllerView()
|
||||
{
|
||||
if (View::exists($this->className)) {
|
||||
$this->controllerView = new View($this->className);
|
||||
} else {
|
||||
$this->controllerView = new View\Template('{@content}');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the view that this controller action should use.
|
||||
* @param $action
|
||||
*/
|
||||
protected function setView($action)
|
||||
{
|
||||
if (View::exists($this->className . '/' . $action)) {
|
||||
$this->view = new View($this->className . '/' . $action);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the incoming request.
|
||||
* @param $action
|
||||
* @param $actionParams
|
||||
* @return \b8\b8\Http\Response|Response
|
||||
*/
|
||||
public function handleAction($action, $actionParams)
|
||||
{
|
||||
$this->setView($action);
|
||||
$response = parent::handleAction($action, $actionParams);
|
||||
|
||||
if ($response instanceof Response) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
if (is_string($response)) {
|
||||
$this->controllerView->content = $response;
|
||||
} elseif (isset($this->view)) {
|
||||
$this->controllerView->content = $this->view->render();
|
||||
}
|
||||
|
||||
$this->response->setContent($this->controllerView->render());
|
||||
|
||||
return $this->response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Require that the currently logged in user is an administrator.
|
||||
* @throws ForbiddenException
|
||||
*/
|
||||
protected function requireAdmin()
|
||||
{
|
||||
if (!$this->currentUserIsAdmin()) {
|
||||
throw new ForbiddenException('You do not have permission to do that.');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the currently logged in user is an administrator.
|
||||
* @return bool
|
||||
*/
|
||||
protected function currentUserIsAdmin()
|
||||
{
|
||||
return $_SESSION['phpci_user']->getIsAdmin();
|
||||
}
|
||||
}
|
||||
279
src/PHPCI/Controller/BuildController.php
Normal file
279
src/PHPCI/Controller/BuildController.php
Normal file
|
|
@ -0,0 +1,279 @@
|
|||
<?php
|
||||
/**
|
||||
* PHPCI - Continuous Integration for PHP
|
||||
*
|
||||
* @copyright Copyright 2014, Block 8 Limited.
|
||||
* @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md
|
||||
* @link https://www.phptesting.org/
|
||||
*/
|
||||
|
||||
namespace PHPCI\Controller;
|
||||
|
||||
use b8;
|
||||
use b8\Exception\HttpException\NotFoundException;
|
||||
use b8\Http\Response\JsonResponse;
|
||||
use PHPCI\BuildFactory;
|
||||
use PHPCI\Helper\AnsiConverter;
|
||||
use PHPCI\Helper\Lang;
|
||||
use PHPCI\Model\Build;
|
||||
use PHPCI\Model\Project;
|
||||
use PHPCI\Service\BuildService;
|
||||
|
||||
/**
|
||||
* Build Controller - Allows users to run and view builds.
|
||||
* @author Dan Cryer <dan@block8.co.uk>
|
||||
* @package PHPCI
|
||||
* @subpackage Web
|
||||
*/
|
||||
class BuildController extends \PHPCI\Controller
|
||||
{
|
||||
/**
|
||||
* @var \PHPCI\Store\BuildStore
|
||||
*/
|
||||
protected $buildStore;
|
||||
|
||||
/**
|
||||
* @var \PHPCI\Service\BuildService
|
||||
*/
|
||||
protected $buildService;
|
||||
|
||||
/**
|
||||
* Initialise the controller, set up stores and services.
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
$this->buildStore = b8\Store\Factory::getStore('Build');
|
||||
$this->buildService = new BuildService($this->buildStore);
|
||||
}
|
||||
|
||||
/**
|
||||
* View a specific build.
|
||||
*/
|
||||
public function view($buildId)
|
||||
{
|
||||
try {
|
||||
$build = BuildFactory::getBuildById($buildId);
|
||||
} catch (\Exception $ex) {
|
||||
$build = null;
|
||||
}
|
||||
|
||||
if (empty($build)) {
|
||||
throw new NotFoundException(Lang::get('build_x_not_found', $buildId));
|
||||
}
|
||||
|
||||
$this->view->plugins = $this->getUiPlugins();
|
||||
$this->view->build = $build;
|
||||
$this->view->data = $this->getBuildData($build);
|
||||
|
||||
$this->layout->title = Lang::get('build_n', $buildId);
|
||||
$this->layout->subtitle = $build->getProjectTitle();
|
||||
|
||||
switch ($build->getStatus()) {
|
||||
case 0:
|
||||
$this->layout->skin = 'blue';
|
||||
break;
|
||||
|
||||
case 1:
|
||||
$this->layout->skin = 'yellow';
|
||||
break;
|
||||
|
||||
case 2:
|
||||
$this->layout->skin = 'green';
|
||||
break;
|
||||
|
||||
case 3:
|
||||
$this->layout->skin = 'red';
|
||||
break;
|
||||
}
|
||||
|
||||
$rebuild = Lang::get('rebuild_now');
|
||||
$rebuildLink = PHPCI_URL . 'build/rebuild/' . $build->getId();
|
||||
|
||||
$delete = Lang::get('delete_build');
|
||||
$deleteLink = PHPCI_URL . 'build/delete/' . $build->getId();
|
||||
|
||||
$actions = "<a class=\"btn btn-default\" href=\"{$rebuildLink}\">{$rebuild}</a> ";
|
||||
|
||||
if ($this->currentUserIsAdmin()) {
|
||||
$actions .= " <a class=\"btn btn-danger\" href=\"{$deleteLink}\">{$delete}</a>";
|
||||
}
|
||||
|
||||
$this->layout->actions = $actions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of the JS plugins to include.
|
||||
* @return array
|
||||
*/
|
||||
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:
|
||||
*/
|
||||
public function data($buildId)
|
||||
{
|
||||
$response = new JsonResponse();
|
||||
$build = BuildFactory::getBuildById($buildId);
|
||||
|
||||
if (!$build) {
|
||||
$response->setResponseCode(404);
|
||||
$response->setContent(array());
|
||||
return $response;
|
||||
}
|
||||
|
||||
$response->setContent($this->getBuildData($build));
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX call to get build meta:
|
||||
*/
|
||||
public function meta($buildId)
|
||||
{
|
||||
$build = BuildFactory::getBuildById($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, $build->getBranch(), $numBuilds);
|
||||
}
|
||||
|
||||
$response = new JsonResponse();
|
||||
$response->setContent($data);
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get build data from database and json encode it:
|
||||
*/
|
||||
protected function getBuildData(Build $build)
|
||||
{
|
||||
$data = array();
|
||||
$data['status'] = (int)$build->getStatus();
|
||||
$data['log'] = $this->cleanLog($build->getLog());
|
||||
$data['created'] = !is_null($build->getCreated()) ? $build->getCreated()->format('Y-m-d H:i:s') : null;
|
||||
$data['started'] = !is_null($build->getStarted()) ? $build->getStarted()->format('Y-m-d H:i:s') : null;
|
||||
$data['finished'] = !is_null($build->getFinished()) ? $build->getFinished()->format('Y-m-d H:i:s') : null;
|
||||
$data['duration'] = $build->getDuration();
|
||||
|
||||
/** @var \PHPCI\Store\BuildErrorStore $errorStore */
|
||||
$errorStore = b8\Store\Factory::getStore('BuildError');
|
||||
$errors = $errorStore->getErrorsForBuild($build->getId(), $this->getParam('since', null));
|
||||
|
||||
$errorView = new b8\View('Build/errors');
|
||||
$errorView->build = $build;
|
||||
$errorView->errors = $errors;
|
||||
|
||||
$data['errors'] = $errorStore->getErrorTotalForBuild($build->getId());
|
||||
$data['error_html'] = $errorView->render();
|
||||
$data['since'] = (new \DateTime())->format('Y-m-d H:i:s');
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a build using an existing build as a template:
|
||||
*/
|
||||
public function rebuild($buildId)
|
||||
{
|
||||
$copy = BuildFactory::getBuildById($buildId);
|
||||
|
||||
if (empty($copy)) {
|
||||
throw new NotFoundException(Lang::get('build_x_not_found', $buildId));
|
||||
}
|
||||
|
||||
$build = $this->buildService->createDuplicateBuild($copy);
|
||||
|
||||
if ($this->buildService->queueError) {
|
||||
$_SESSION['global_error'] = Lang::get('add_to_queue_failed');
|
||||
}
|
||||
|
||||
$response = new b8\Http\Response\RedirectResponse();
|
||||
$response->setHeader('Location', PHPCI_URL.'build/view/' . $build->getId());
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a build.
|
||||
*/
|
||||
public function delete($buildId)
|
||||
{
|
||||
$this->requireAdmin();
|
||||
|
||||
$build = BuildFactory::getBuildById($buildId);
|
||||
|
||||
if (empty($build)) {
|
||||
throw new NotFoundException(Lang::get('build_x_not_found', $buildId));
|
||||
}
|
||||
|
||||
$this->buildService->deleteBuild($build);
|
||||
|
||||
$response = new b8\Http\Response\RedirectResponse();
|
||||
$response->setHeader('Location', PHPCI_URL.'project/view/' . $build->getProjectId());
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse log for unix colours and replace with HTML.
|
||||
*/
|
||||
protected function cleanLog($log)
|
||||
{
|
||||
return AnsiConverter::convert($log);
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows the UI to poll for the latest running and pending builds.
|
||||
*/
|
||||
public function latest()
|
||||
{
|
||||
$rtn = array(
|
||||
'pending' => $this->formatBuilds($this->buildStore->getByStatus(Build::STATUS_NEW)),
|
||||
'running' => $this->formatBuilds($this->buildStore->getByStatus(Build::STATUS_RUNNING)),
|
||||
);
|
||||
|
||||
$response = new JsonResponse();
|
||||
$response->setContent($rtn);
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a list of builds into rows suitable for the dropdowns in the PHPCI header bar.
|
||||
* @param $builds
|
||||
* @return array
|
||||
*/
|
||||
protected function formatBuilds($builds)
|
||||
{
|
||||
Project::$sleepable = array('id', 'title', 'reference', 'type');
|
||||
|
||||
$rtn = array('count' => $builds['count'], 'items' => array());
|
||||
|
||||
foreach ($builds['items'] as $build) {
|
||||
$item = $build->toArray(1);
|
||||
|
||||
$header = new b8\View('Build/header-row');
|
||||
$header->build = $build;
|
||||
|
||||
$item['header_row'] = $header->render();
|
||||
$rtn['items'][$item['id']] = $item;
|
||||
}
|
||||
|
||||
ksort($rtn['items']);
|
||||
return $rtn;
|
||||
}
|
||||
}
|
||||
204
src/PHPCI/Controller/BuildStatusController.php
Normal file
204
src/PHPCI/Controller/BuildStatusController.php
Normal file
|
|
@ -0,0 +1,204 @@
|
|||
<?php
|
||||
/**
|
||||
* PHPCI - Continuous Integration for PHP
|
||||
*
|
||||
* @copyright Copyright 2014, Block 8 Limited.
|
||||
* @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md
|
||||
* @link https://www.phptesting.org/
|
||||
*/
|
||||
|
||||
namespace PHPCI\Controller;
|
||||
|
||||
use b8;
|
||||
use b8\Exception\HttpException\NotFoundException;
|
||||
use b8\Store;
|
||||
use PHPCI\BuildFactory;
|
||||
use PHPCI\Model\Project;
|
||||
use PHPCI\Model\Build;
|
||||
use PHPCI\Service\BuildStatusService;
|
||||
|
||||
/**
|
||||
* Build Status Controller - Allows external access to build status information / images.
|
||||
* @author Dan Cryer <dan@block8.co.uk>
|
||||
* @package PHPCI
|
||||
* @subpackage Web
|
||||
*/
|
||||
class BuildStatusController extends \PHPCI\Controller
|
||||
{
|
||||
/* @var \PHPCI\Store\ProjectStore */
|
||||
protected $projectStore;
|
||||
/* @var \PHPCI\Store\BuildStore */
|
||||
protected $buildStore;
|
||||
|
||||
/**
|
||||
* Initialise the controller, set up stores and services.
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
$this->response->disableLayout();
|
||||
$this->buildStore = Store\Factory::getStore('Build');
|
||||
$this->projectStore = Store\Factory::getStore('Project');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns status of the last build
|
||||
* @param $projectId
|
||||
* @return string
|
||||
*/
|
||||
protected function getStatus($projectId)
|
||||
{
|
||||
$branch = $this->getParam('branch', 'master');
|
||||
try {
|
||||
$project = $this->projectStore->getById($projectId);
|
||||
$status = 'passing';
|
||||
|
||||
if (!$project->getAllowPublicStatus()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isset($project) && $project instanceof Project) {
|
||||
$build = $project->getLatestBuild($branch, array(2,3));
|
||||
|
||||
if (isset($build) && $build instanceof Build && $build->getStatus() != 2) {
|
||||
$status = 'failed';
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$status = 'error';
|
||||
}
|
||||
|
||||
return $status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays projects information in ccmenu format
|
||||
*
|
||||
* @param $projectId
|
||||
* @return bool
|
||||
* @throws \Exception
|
||||
* @throws b8\Exception\HttpException
|
||||
*/
|
||||
public function ccxml($projectId)
|
||||
{
|
||||
/* @var Project $project */
|
||||
$project = $this->projectStore->getById($projectId);
|
||||
$xml = new \SimpleXMLElement('<Projects/>');
|
||||
|
||||
if (!$project instanceof Project || !$project->getAllowPublicStatus()) {
|
||||
return $this->renderXml($xml);
|
||||
}
|
||||
|
||||
try {
|
||||
$branchList = $this->buildStore->getBuildBranches($projectId);
|
||||
|
||||
if (!$branchList) {
|
||||
$branchList = array($project->getBranch());
|
||||
}
|
||||
|
||||
foreach ($branchList as $branch) {
|
||||
$buildStatusService = new BuildStatusService($branch, $project, $project->getLatestBuild($branch));
|
||||
if ($attributes = $buildStatusService->toArray()) {
|
||||
$projectXml = $xml->addChild('Project');
|
||||
foreach ($attributes as $attributeKey => $attributeValue) {
|
||||
$projectXml->addAttribute($attributeKey, $attributeValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$xml = new \SimpleXMLElement('<projects/>');
|
||||
}
|
||||
|
||||
return $this->renderXml($xml);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \SimpleXMLElement $xml
|
||||
* @return bool
|
||||
*/
|
||||
protected function renderXml(\SimpleXMLElement $xml = null)
|
||||
{
|
||||
$this->response->setHeader('Content-Type', 'text/xml');
|
||||
$this->response->setContent($xml->asXML());
|
||||
$this->response->flush();
|
||||
echo $xml->asXML();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the appropriate build status image in SVG format for a given project.
|
||||
*/
|
||||
public function image($projectId)
|
||||
{
|
||||
$style = $this->getParam('style', 'plastic');
|
||||
$label = $this->getParam('label', 'build');
|
||||
|
||||
$status = $this->getStatus($projectId);
|
||||
|
||||
if (is_null($status)) {
|
||||
$response = new b8\Http\Response\RedirectResponse();
|
||||
$response->setHeader('Location', '/');
|
||||
return $response;
|
||||
}
|
||||
|
||||
$color = ($status == 'passing') ? 'green' : 'red';
|
||||
$image = file_get_contents(sprintf(
|
||||
'http://img.shields.io/badge/%s-%s-%s.svg?style=%s',
|
||||
$label,
|
||||
$status,
|
||||
$color,
|
||||
$style
|
||||
));
|
||||
|
||||
$this->response->disableLayout();
|
||||
$this->response->setHeader('Content-Type', 'image/svg+xml');
|
||||
$this->response->setContent($image);
|
||||
return $this->response;
|
||||
}
|
||||
|
||||
/**
|
||||
* View the public status page of a given project, if enabled.
|
||||
* @param $projectId
|
||||
* @return string
|
||||
* @throws \b8\Exception\HttpException\NotFoundException
|
||||
*/
|
||||
public function view($projectId)
|
||||
{
|
||||
$project = $this->projectStore->getById($projectId);
|
||||
|
||||
if (empty($project)) {
|
||||
throw new NotFoundException('Project with id: ' . $projectId . ' not found');
|
||||
}
|
||||
|
||||
if (!$project->getAllowPublicStatus()) {
|
||||
throw new NotFoundException('Project with id: ' . $projectId . ' not found');
|
||||
}
|
||||
|
||||
$builds = $this->getLatestBuilds($projectId);
|
||||
|
||||
if (count($builds)) {
|
||||
$this->view->latest = $builds[0];
|
||||
}
|
||||
|
||||
$this->view->builds = $builds;
|
||||
$this->view->project = $project;
|
||||
|
||||
return $this->view->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render latest builds for project as HTML table.
|
||||
*/
|
||||
protected function getLatestBuilds($projectId)
|
||||
{
|
||||
$criteria = array('project_id' => $projectId);
|
||||
$order = array('id' => 'DESC');
|
||||
$builds = $this->buildStore->getWhere($criteria, 10, 0, array(), $order);
|
||||
|
||||
foreach ($builds['items'] as &$build) {
|
||||
$build = BuildFactory::getBuild($build);
|
||||
}
|
||||
|
||||
return $builds['items'];
|
||||
}
|
||||
}
|
||||
121
src/PHPCI/Controller/GroupController.php
Normal file
121
src/PHPCI/Controller/GroupController.php
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
<?php
|
||||
/**
|
||||
* PHPCI - Continuous Integration for PHP
|
||||
*
|
||||
* @copyright Copyright 2015, Block 8 Limited.
|
||||
* @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md
|
||||
* @link https://www.phptesting.org/
|
||||
*/
|
||||
|
||||
namespace PHPCI\Controller;
|
||||
|
||||
use b8;
|
||||
use b8\Form;
|
||||
use b8\Store;
|
||||
use PHPCI\Controller;
|
||||
use PHPCI\Model\ProjectGroup;
|
||||
use PHPCI\Helper\Lang;
|
||||
|
||||
/**
|
||||
* Project Controller - Allows users to create, edit and view projects.
|
||||
* @author Dan Cryer <dan@block8.co.uk>
|
||||
* @package PHPCI
|
||||
* @subpackage Web
|
||||
*/
|
||||
class GroupController extends Controller
|
||||
{
|
||||
/**
|
||||
* @var \PHPCI\Store\ProjectGroupStore
|
||||
*/
|
||||
protected $groupStore;
|
||||
|
||||
/**
|
||||
* Set up this controller.
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
$this->groupStore = b8\Store\Factory::getStore('ProjectGroup');
|
||||
}
|
||||
|
||||
/**
|
||||
* List project groups.
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$this->requireAdmin();
|
||||
|
||||
$groups = array();
|
||||
$groupList = $this->groupStore->getWhere(array(), 100, 0, array(), array('title' => 'ASC'));
|
||||
|
||||
foreach ($groupList['items'] as $group) {
|
||||
$thisGroup = array(
|
||||
'title' => $group->getTitle(),
|
||||
'id' => $group->getId(),
|
||||
);
|
||||
$projects = b8\Store\Factory::getStore('Project')->getByGroupId($group->getId());
|
||||
$thisGroup['projects'] = $projects['items'];
|
||||
$groups[] = $thisGroup;
|
||||
}
|
||||
|
||||
$this->view->groups = $groups;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add or edit a project group.
|
||||
* @param null $groupId
|
||||
* @return void|b8\Http\Response\RedirectResponse
|
||||
*/
|
||||
public function edit($groupId = null)
|
||||
{
|
||||
$this->requireAdmin();
|
||||
|
||||
if (!is_null($groupId)) {
|
||||
$group = $this->groupStore->getById($groupId);
|
||||
} else {
|
||||
$group = new ProjectGroup();
|
||||
}
|
||||
|
||||
if ($this->request->getMethod() == 'POST') {
|
||||
$group->setTitle($this->getParam('title'));
|
||||
$this->groupStore->save($group);
|
||||
|
||||
$response = new b8\Http\Response\RedirectResponse();
|
||||
$response->setHeader('Location', PHPCI_URL.'group');
|
||||
return $response;
|
||||
}
|
||||
|
||||
$form = new Form();
|
||||
$form->setMethod('POST');
|
||||
$form->setAction(PHPCI_URL . 'group/edit' . (!is_null($groupId) ? '/' . $groupId : ''));
|
||||
|
||||
$title = new Form\Element\Text('title');
|
||||
$title->setContainerClass('form-group');
|
||||
$title->setClass('form-control');
|
||||
$title->setLabel(Lang::get('group_title'));
|
||||
$title->setValue($group->getTitle());
|
||||
|
||||
$submit = new Form\Element\Submit();
|
||||
$submit->setValue(Lang::get('group_save'));
|
||||
|
||||
$form->addField($title);
|
||||
$form->addField($submit);
|
||||
|
||||
$this->view->form = $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a project group.
|
||||
* @param $groupId
|
||||
* @return b8\Http\Response\RedirectResponse
|
||||
*/
|
||||
public function delete($groupId)
|
||||
{
|
||||
$this->requireAdmin();
|
||||
$group = $this->groupStore->getById($groupId);
|
||||
|
||||
$this->groupStore->delete($group);
|
||||
$response = new b8\Http\Response\RedirectResponse();
|
||||
$response->setHeader('Location', PHPCI_URL.'group');
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
166
src/PHPCI/Controller/HomeController.php
Normal file
166
src/PHPCI/Controller/HomeController.php
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
<?php
|
||||
/**
|
||||
* PHPCI - Continuous Integration for PHP
|
||||
*
|
||||
* @copyright Copyright 2014, Block 8 Limited.
|
||||
* @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md
|
||||
* @link https://www.phptesting.org/
|
||||
*/
|
||||
|
||||
namespace PHPCI\Controller;
|
||||
|
||||
use b8;
|
||||
use PHPCI\BuildFactory;
|
||||
use PHPCI\Helper\Lang;
|
||||
use PHPCI\Model\Build;
|
||||
|
||||
/**
|
||||
* Home Controller - Displays the PHPCI Dashboard.
|
||||
* @author Dan Cryer <dan@block8.co.uk>
|
||||
* @package PHPCI
|
||||
* @subpackage Web
|
||||
*/
|
||||
class HomeController extends \PHPCI\Controller
|
||||
{
|
||||
/**
|
||||
* @var \PHPCI\Store\BuildStore
|
||||
*/
|
||||
protected $buildStore;
|
||||
|
||||
/**
|
||||
* @var \PHPCI\Store\ProjectStore
|
||||
*/
|
||||
protected $projectStore;
|
||||
|
||||
/**
|
||||
* @var \PHPCI\Store\ProjectGroupStore
|
||||
*/
|
||||
protected $groupStore;
|
||||
|
||||
/**
|
||||
* Initialise the controller, set up stores and services.
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
$this->buildStore = b8\Store\Factory::getStore('Build');
|
||||
$this->projectStore = b8\Store\Factory::getStore('Project');
|
||||
$this->groupStore = b8\Store\Factory::getStore('ProjectGroup');
|
||||
}
|
||||
|
||||
/**
|
||||
* Display PHPCI dashboard:
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$this->layout->title = Lang::get('dashboard');
|
||||
$builds = $this->buildStore->getLatestBuilds(null, 10);
|
||||
|
||||
foreach ($builds as &$build) {
|
||||
$build = BuildFactory::getBuild($build);
|
||||
}
|
||||
|
||||
$this->view->builds = $builds;
|
||||
$this->view->groups = $this->getGroupInfo();
|
||||
|
||||
return $this->view->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX get latest builds table (HTML)
|
||||
*/
|
||||
public function latest()
|
||||
{
|
||||
$this->response->disableLayout();
|
||||
$this->response->setContent($this->getLatestBuildsHtml());
|
||||
return $this->response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ajax request for the project overview section of the dashboard.
|
||||
*/
|
||||
public function summary()
|
||||
{
|
||||
$this->response->disableLayout();
|
||||
$projects = $this->projectStore->getWhere(array(), 50, 0, array(), array('title' => 'ASC'));
|
||||
$this->response->setContent($this->getSummaryHtml($projects));
|
||||
return $this->response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the HTML for the project overview section of the dashboard.
|
||||
* @param $projects
|
||||
* @return string
|
||||
*/
|
||||
protected function getSummaryHtml($projects)
|
||||
{
|
||||
$summaryBuilds = array();
|
||||
$successes = array();
|
||||
$failures = array();
|
||||
$counts = array();
|
||||
|
||||
foreach ($projects as $project) {
|
||||
$summaryBuilds[$project->getId()] = $this->buildStore->getLatestBuilds($project->getId());
|
||||
|
||||
$count = $this->buildStore->getWhere(
|
||||
array('project_id' => $project->getId()),
|
||||
1,
|
||||
0,
|
||||
array(),
|
||||
array('id' => 'DESC')
|
||||
);
|
||||
$counts[$project->getId()] = $count['count'];
|
||||
|
||||
$success = $this->buildStore->getLastBuildByStatus($project->getId(), Build::STATUS_SUCCESS);
|
||||
$failure = $this->buildStore->getLastBuildByStatus($project->getId(), Build::STATUS_FAILED);
|
||||
|
||||
$successes[$project->getId()] = $success;
|
||||
$failures[$project->getId()] = $failure;
|
||||
}
|
||||
|
||||
$summaryView = new b8\View('SummaryTable');
|
||||
$summaryView->projects = $projects;
|
||||
$summaryView->builds = $summaryBuilds;
|
||||
$summaryView->successful = $successes;
|
||||
$summaryView->failed = $failures;
|
||||
$summaryView->counts = $counts;
|
||||
|
||||
return $summaryView->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get latest builds and render as a table.
|
||||
*/
|
||||
protected function getLatestBuildsHtml()
|
||||
{
|
||||
$builds = $this->buildStore->getWhere(array(), 5, 0, array(), array('id' => 'DESC'));
|
||||
$view = new b8\View('BuildsTable');
|
||||
|
||||
foreach ($builds['items'] as &$build) {
|
||||
$build = BuildFactory::getBuild($build);
|
||||
}
|
||||
|
||||
$view->builds = $builds['items'];
|
||||
|
||||
return $view->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a summary of the project groups we have, and what projects they have in them.
|
||||
* @return array
|
||||
*/
|
||||
protected function getGroupInfo()
|
||||
{
|
||||
$rtn = array();
|
||||
$groups = $this->groupStore->getWhere(array(), 100, 0, array(), array('title' => 'ASC'));
|
||||
|
||||
foreach ($groups['items'] as $group) {
|
||||
$thisGroup = array('title' => $group->getTitle());
|
||||
$projects = $this->projectStore->getByGroupId($group->getId());
|
||||
$thisGroup['projects'] = $projects['items'];
|
||||
$thisGroup['summary'] = $this->getSummaryHtml($thisGroup['projects']);
|
||||
$rtn[] = $thisGroup;
|
||||
}
|
||||
|
||||
return $rtn;
|
||||
}
|
||||
}
|
||||
61
src/PHPCI/Controller/PluginController.php
Normal file
61
src/PHPCI/Controller/PluginController.php
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
<?php
|
||||
/**
|
||||
* PHPCI - Continuous Integration for PHP
|
||||
*
|
||||
* @copyright Copyright 2014, Block 8 Limited.
|
||||
* @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md
|
||||
* @link https://www.phptesting.org/
|
||||
*/
|
||||
|
||||
namespace PHPCI\Controller;
|
||||
|
||||
use b8;
|
||||
use PHPCI\Helper\Lang;
|
||||
use PHPCI\Plugin\Util\ComposerPluginInformation;
|
||||
use PHPCI\Plugin\Util\FilesPluginInformation;
|
||||
use PHPCI\Plugin\Util\PluginInformationCollection;
|
||||
|
||||
/**
|
||||
* Plugin Controller - Provides support for installing Composer packages.
|
||||
* @author Dan Cryer <dan@block8.co.uk>
|
||||
* @package PHPCI
|
||||
* @subpackage Web
|
||||
*/
|
||||
class PluginController extends \PHPCI\Controller
|
||||
{
|
||||
/**
|
||||
* List all enabled plugins, installed and recommend packages.
|
||||
* @return string
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$this->requireAdmin();
|
||||
|
||||
$json = $this->getComposerJson();
|
||||
$this->view->installedPackages = $json['require'];
|
||||
|
||||
$pluginInfo = new PluginInformationCollection();
|
||||
$pluginInfo->add(FilesPluginInformation::newFromDir(
|
||||
PHPCI_DIR . "PHPCI/Plugin/"
|
||||
));
|
||||
$pluginInfo->add(ComposerPluginInformation::buildFromYaml(
|
||||
PHPCI_DIR . "vendor/composer/installed.json"
|
||||
));
|
||||
|
||||
$this->view->plugins = $pluginInfo->getInstalledPlugins();
|
||||
|
||||
$this->layout->title = Lang::get('plugins');
|
||||
|
||||
return $this->view->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the json-decoded contents of the composer.json file.
|
||||
* @return mixed
|
||||
*/
|
||||
protected function getComposerJson()
|
||||
{
|
||||
$json = file_get_contents(APPLICATION_PATH . 'composer.json');
|
||||
return json_decode($json, true);
|
||||
}
|
||||
}
|
||||
451
src/PHPCI/Controller/ProjectController.php
Normal file
451
src/PHPCI/Controller/ProjectController.php
Normal file
|
|
@ -0,0 +1,451 @@
|
|||
<?php
|
||||
/**
|
||||
* PHPCI - Continuous Integration for PHP
|
||||
*
|
||||
* @copyright Copyright 2014, Block 8 Limited.
|
||||
* @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md
|
||||
* @link https://www.phptesting.org/
|
||||
*/
|
||||
|
||||
namespace PHPCI\Controller;
|
||||
|
||||
use b8;
|
||||
use b8\Form;
|
||||
use b8\Exception\HttpException\NotFoundException;
|
||||
use b8\Store;
|
||||
use PHPCI;
|
||||
use PHPCI\BuildFactory;
|
||||
use PHPCI\Helper\Github;
|
||||
use PHPCI\Helper\Lang;
|
||||
use PHPCI\Helper\SshKey;
|
||||
use PHPCI\Service\BuildService;
|
||||
use PHPCI\Service\ProjectService;
|
||||
|
||||
/**
|
||||
* Project Controller - Allows users to create, edit and view projects.
|
||||
* @author Dan Cryer <dan@block8.co.uk>
|
||||
* @package PHPCI
|
||||
* @subpackage Web
|
||||
*/
|
||||
class ProjectController extends PHPCI\Controller
|
||||
{
|
||||
/**
|
||||
* @var \PHPCI\Store\ProjectStore
|
||||
*/
|
||||
protected $projectStore;
|
||||
|
||||
/**
|
||||
* @var \PHPCI\Service\ProjectService
|
||||
*/
|
||||
protected $projectService;
|
||||
|
||||
/**
|
||||
* @var \PHPCI\Store\BuildStore
|
||||
*/
|
||||
protected $buildStore;
|
||||
|
||||
/**
|
||||
* @var \PHPCI\Service\BuildService
|
||||
*/
|
||||
protected $buildService;
|
||||
|
||||
/**
|
||||
* Initialise the controller, set up stores and services.
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
$this->buildStore = Store\Factory::getStore('Build');
|
||||
$this->projectStore = Store\Factory::getStore('Project');
|
||||
$this->projectService = new ProjectService($this->projectStore);
|
||||
$this->buildService = new BuildService($this->buildStore);
|
||||
}
|
||||
|
||||
/**
|
||||
* View a specific project.
|
||||
*/
|
||||
public function view($projectId)
|
||||
{
|
||||
$branch = $this->getParam('branch', '');
|
||||
$project = $this->projectStore->getById($projectId);
|
||||
|
||||
if (empty($project)) {
|
||||
throw new NotFoundException(Lang::get('project_x_not_found', $projectId));
|
||||
}
|
||||
|
||||
$per_page = 10;
|
||||
$page = $this->getParam('p', 1);
|
||||
$builds = $this->getLatestBuildsHtml($projectId, urldecode($branch), (($page - 1) * $per_page));
|
||||
$pages = $builds[1] == 0 ? 1 : ceil($builds[1] / $per_page);
|
||||
|
||||
if ($page > $pages) {
|
||||
$response = new b8\Http\Response\RedirectResponse();
|
||||
$response->setHeader('Location', PHPCI_URL.'project/view/'.$projectId);
|
||||
return $response;
|
||||
}
|
||||
|
||||
$this->view->builds = $builds[0];
|
||||
$this->view->total = $builds[1];
|
||||
$this->view->project = $project;
|
||||
$this->view->branch = urldecode($branch);
|
||||
$this->view->branches = $this->projectStore->getKnownBranches($projectId);
|
||||
$this->view->page = $page;
|
||||
$this->view->pages = $pages;
|
||||
|
||||
$this->layout->title = $project->getTitle();
|
||||
$this->layout->subtitle = $this->view->branch;
|
||||
|
||||
return $this->view->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new pending build for a project.
|
||||
*/
|
||||
public function build($projectId, $branch = '')
|
||||
{
|
||||
/* @var \PHPCI\Model\Project $project */
|
||||
$project = $this->projectStore->getById($projectId);
|
||||
|
||||
if (empty($branch)) {
|
||||
$branch = $project->getBranch();
|
||||
}
|
||||
|
||||
if (empty($project)) {
|
||||
throw new NotFoundException(Lang::get('project_x_not_found', $projectId));
|
||||
}
|
||||
|
||||
$email = $_SESSION['phpci_user']->getEmail();
|
||||
$build = $this->buildService->createBuild($project, null, urldecode($branch), $email);
|
||||
|
||||
if ($this->buildService->queueError) {
|
||||
$_SESSION['global_error'] = Lang::get('add_to_queue_failed');
|
||||
}
|
||||
|
||||
$response = new b8\Http\Response\RedirectResponse();
|
||||
$response->setHeader('Location', PHPCI_URL.'build/view/' . $build->getId());
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a project.
|
||||
*/
|
||||
public function delete($projectId)
|
||||
{
|
||||
$this->requireAdmin();
|
||||
|
||||
$project = $this->projectStore->getById($projectId);
|
||||
$this->projectService->deleteProject($project);
|
||||
|
||||
$response = new b8\Http\Response\RedirectResponse();
|
||||
$response->setHeader('Location', PHPCI_URL);
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX get latest builds.
|
||||
*/
|
||||
public function builds($projectId)
|
||||
{
|
||||
$branch = $this->getParam('branch', '');
|
||||
$builds = $this->getLatestBuildsHtml($projectId, urldecode($branch));
|
||||
|
||||
$this->response->disableLayout();
|
||||
$this->response->setContent($builds[0]);
|
||||
return $this->response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render latest builds for project as HTML table.
|
||||
*
|
||||
* @param $projectId
|
||||
* @param string $branch A urldecoded branch name.
|
||||
* @param int $start
|
||||
* @return array
|
||||
*/
|
||||
protected function getLatestBuildsHtml($projectId, $branch = '', $start = 0)
|
||||
{
|
||||
$criteria = array('project_id' => $projectId);
|
||||
if (!empty($branch)) {
|
||||
$criteria['branch'] = $branch;
|
||||
}
|
||||
|
||||
$order = array('id' => 'DESC');
|
||||
$builds = $this->buildStore->getWhere($criteria, 10, $start, array(), $order);
|
||||
$view = new b8\View('BuildsTable');
|
||||
|
||||
foreach ($builds['items'] as &$build) {
|
||||
$build = BuildFactory::getBuild($build);
|
||||
}
|
||||
|
||||
$view->builds = $builds['items'];
|
||||
|
||||
return array($view->render(), $builds['count']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new project. Handles both the form, and processing.
|
||||
*/
|
||||
public function add()
|
||||
{
|
||||
$this->layout->title = Lang::get('add_project');
|
||||
$this->requireAdmin();
|
||||
|
||||
$method = $this->request->getMethod();
|
||||
|
||||
$pub = null;
|
||||
$values = $this->getParams();
|
||||
|
||||
if ($method != 'POST') {
|
||||
$sshKey = new SshKey();
|
||||
$key = $sshKey->generate();
|
||||
|
||||
$values['key'] = $key['private_key'];
|
||||
$values['pubkey'] = $key['public_key'];
|
||||
$pub = $key['public_key'];
|
||||
}
|
||||
|
||||
$form = $this->projectForm($values);
|
||||
|
||||
if ($method != 'POST' || ($method == 'POST' && !$form->validate())) {
|
||||
$view = new b8\View('ProjectForm');
|
||||
$view->type = 'add';
|
||||
$view->project = null;
|
||||
$view->form = $form;
|
||||
$view->key = $pub;
|
||||
|
||||
return $view->render();
|
||||
} else {
|
||||
$title = $this->getParam('title', 'New Project');
|
||||
$reference = $this->getParam('reference', null);
|
||||
$type = $this->getParam('type', null);
|
||||
|
||||
$options = array(
|
||||
'ssh_private_key' => $this->getParam('key', null),
|
||||
'ssh_public_key' => $this->getParam('pubkey', null),
|
||||
'build_config' => $this->getParam('build_config', null),
|
||||
'allow_public_status' => $this->getParam('allow_public_status', 0),
|
||||
'branch' => $this->getParam('branch', null),
|
||||
'group' => $this->getParam('group_id', null),
|
||||
);
|
||||
|
||||
$project = $this->projectService->createProject($title, $type, $reference, $options);
|
||||
|
||||
$response = new b8\Http\Response\RedirectResponse();
|
||||
$response->setHeader('Location', PHPCI_URL.'project/view/' . $project->getId());
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit a project. Handles both the form and processing.
|
||||
*/
|
||||
public function edit($projectId)
|
||||
{
|
||||
$this->requireAdmin();
|
||||
|
||||
$method = $this->request->getMethod();
|
||||
$project = $this->projectStore->getById($projectId);
|
||||
|
||||
if (empty($project)) {
|
||||
throw new NotFoundException(Lang::get('project_x_not_found', $projectId));
|
||||
}
|
||||
|
||||
$this->layout->title = $project->getTitle();
|
||||
$this->layout->subtitle = Lang::get('edit_project');
|
||||
|
||||
$values = $project->getDataArray();
|
||||
$values['key'] = $values['ssh_private_key'];
|
||||
$values['pubkey'] = $values['ssh_public_key'];
|
||||
|
||||
if ($values['type'] == "gitlab") {
|
||||
$accessInfo = $project->getAccessInformation();
|
||||
$reference = $accessInfo["user"].'@'.$accessInfo["domain"].':' . $project->getReference().".git";
|
||||
$values['reference'] = $reference;
|
||||
}
|
||||
|
||||
if ($method == 'POST') {
|
||||
$values = $this->getParams();
|
||||
}
|
||||
|
||||
$form = $this->projectForm($values, 'edit/' . $projectId);
|
||||
|
||||
if ($method != 'POST' || ($method == 'POST' && !$form->validate())) {
|
||||
$view = new b8\View('ProjectForm');
|
||||
$view->type = 'edit';
|
||||
$view->project = $project;
|
||||
$view->form = $form;
|
||||
$view->key = $values['pubkey'];
|
||||
|
||||
return $view->render();
|
||||
}
|
||||
|
||||
$title = $this->getParam('title', Lang::get('new_project'));
|
||||
$reference = $this->getParam('reference', null);
|
||||
$type = $this->getParam('type', null);
|
||||
|
||||
$options = array(
|
||||
'ssh_private_key' => $this->getParam('key', null),
|
||||
'ssh_public_key' => $this->getParam('pubkey', null),
|
||||
'build_config' => $this->getParam('build_config', null),
|
||||
'allow_public_status' => $this->getParam('allow_public_status', 0),
|
||||
'archived' => $this->getParam('archived', 0),
|
||||
'branch' => $this->getParam('branch', null),
|
||||
'group' => $this->getParam('group_id', null),
|
||||
);
|
||||
|
||||
$project = $this->projectService->updateProject($project, $title, $type, $reference, $options);
|
||||
|
||||
$response = new b8\Http\Response\RedirectResponse();
|
||||
$response->setHeader('Location', PHPCI_URL.'project/view/' . $project->getId());
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create add / edit project form.
|
||||
*/
|
||||
protected function projectForm($values, $type = 'add')
|
||||
{
|
||||
$form = new Form();
|
||||
$form->setMethod('POST');
|
||||
$form->setAction(PHPCI_URL.'project/' . $type);
|
||||
$form->addField(new Form\Element\Csrf('csrf'));
|
||||
$form->addField(new Form\Element\Hidden('pubkey'));
|
||||
|
||||
$options = array(
|
||||
'choose' => Lang::get('select_repository_type'),
|
||||
'github' => Lang::get('github'),
|
||||
'bitbucket' => Lang::get('bitbucket'),
|
||||
'gitlab' => Lang::get('gitlab'),
|
||||
'remote' => Lang::get('remote'),
|
||||
'local' => Lang::get('local'),
|
||||
'hg' => Lang::get('hg'),
|
||||
'svn' => Lang::get('svn'),
|
||||
);
|
||||
|
||||
$field = Form\Element\Select::create('type', Lang::get('where_hosted'), true);
|
||||
$field->setPattern('^(github|bitbucket|gitlab|remote|local|hg|svn)');
|
||||
$field->setOptions($options);
|
||||
$field->setClass('form-control')->setContainerClass('form-group');
|
||||
$form->addField($field);
|
||||
|
||||
$container = new Form\ControlGroup('github-container');
|
||||
$container->setClass('github-container');
|
||||
|
||||
$field = Form\Element\Select::create('github', Lang::get('choose_github'), false);
|
||||
$field->setClass('form-control')->setContainerClass('form-group');
|
||||
$container->addField($field);
|
||||
$form->addField($container);
|
||||
|
||||
$field = Form\Element\Text::create('reference', Lang::get('repo_name'), true);
|
||||
$field->setValidator($this->getReferenceValidator($values));
|
||||
$field->setClass('form-control')->setContainerClass('form-group');
|
||||
$form->addField($field);
|
||||
|
||||
$field = Form\Element\Text::create('title', Lang::get('project_title'), true);
|
||||
$field->setClass('form-control')->setContainerClass('form-group');
|
||||
$form->addField($field);
|
||||
|
||||
$field = Form\Element\TextArea::create('key', Lang::get('project_private_key'), false);
|
||||
$field->setClass('form-control')->setContainerClass('form-group');
|
||||
$field->setRows(6);
|
||||
$form->addField($field);
|
||||
|
||||
$field = Form\Element\TextArea::create('build_config', Lang::get('build_config'), false);
|
||||
$field->setClass('form-control')->setContainerClass('form-group');
|
||||
$field->setRows(6);
|
||||
$form->addField($field);
|
||||
|
||||
$field = Form\Element\Text::create('branch', Lang::get('default_branch'), true);
|
||||
$field->setClass('form-control')->setContainerClass('form-group')->setValue('master');
|
||||
$form->addField($field);
|
||||
|
||||
$field = Form\Element\Select::create('group_id', 'Project Group', true);
|
||||
$field->setClass('form-control')->setContainerClass('form-group')->setValue(1);
|
||||
|
||||
$groups = array();
|
||||
$groupStore = b8\Store\Factory::getStore('ProjectGroup');
|
||||
$groupList = $groupStore->getWhere(array(), 100, 0, array(), array('title' => 'ASC'));
|
||||
|
||||
foreach ($groupList['items'] as $group) {
|
||||
$groups[$group->getId()] = $group->getTitle();
|
||||
}
|
||||
|
||||
$field->setOptions($groups);
|
||||
$form->addField($field);
|
||||
|
||||
$field = Form\Element\Checkbox::create('allow_public_status', Lang::get('allow_public_status'), false);
|
||||
$field->setContainerClass('form-group');
|
||||
$field->setCheckedValue(1);
|
||||
$field->setValue(0);
|
||||
$form->addField($field);
|
||||
|
||||
$field = Form\Element\Checkbox::create('archived', Lang::get('archived'), false);
|
||||
$field->setContainerClass('form-group');
|
||||
$field->setCheckedValue(1);
|
||||
$field->setValue(0);
|
||||
$form->addField($field);
|
||||
|
||||
$field = new Form\Element\Submit();
|
||||
$field->setValue(Lang::get('save_project'));
|
||||
$field->setContainerClass('form-group');
|
||||
$field->setClass('btn-success');
|
||||
$form->addField($field);
|
||||
|
||||
$form->setValues($values);
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an array of repositories from Github's API.
|
||||
*/
|
||||
protected function githubRepositories()
|
||||
{
|
||||
$github = new Github();
|
||||
|
||||
$response = new b8\Http\Response\JsonResponse();
|
||||
$response->setContent($github->getRepositories());
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the validator to use to check project references.
|
||||
* @param $values
|
||||
* @return callable
|
||||
*/
|
||||
protected function getReferenceValidator($values)
|
||||
{
|
||||
return function ($val) use ($values) {
|
||||
$type = $values['type'];
|
||||
|
||||
$validators = array(
|
||||
'hg' => array(
|
||||
'regex' => '/^(https?):\/\//',
|
||||
'message' => Lang::get('error_mercurial')
|
||||
),
|
||||
'remote' => array(
|
||||
'regex' => '/^(git|https?):\/\//',
|
||||
'message' => Lang::get('error_remote')
|
||||
),
|
||||
'gitlab' => array(
|
||||
'regex' => '`^(.*)@(.*):(.*)/(.*)\.git`',
|
||||
'message' => Lang::get('error_gitlab')
|
||||
),
|
||||
'github' => array(
|
||||
'regex' => '/^[a-zA-Z0-9_\-]+\/[a-zA-Z0-9_\-\.]+$/',
|
||||
'message' => Lang::get('error_github')
|
||||
),
|
||||
'bitbucket' => array(
|
||||
'regex' => '/^[a-zA-Z0-9_\-]+\/[a-zA-Z0-9_\-\.]+$/',
|
||||
'message' => Lang::get('error_bitbucket')
|
||||
),
|
||||
);
|
||||
|
||||
if (in_array($type, $validators) && !preg_match($validators[$type]['regex'], $val)) {
|
||||
throw new \Exception($validators[$type]['message']);
|
||||
} elseif ($type == 'local' && !is_dir($val)) {
|
||||
throw new \Exception(Lang::get('error_path'));
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
}
|
||||
}
|
||||
213
src/PHPCI/Controller/SessionController.php
Normal file
213
src/PHPCI/Controller/SessionController.php
Normal file
|
|
@ -0,0 +1,213 @@
|
|||
<?php
|
||||
/**
|
||||
* PHPCI - Continuous Integration for PHP
|
||||
*
|
||||
* @copyright Copyright 2014, Block 8 Limited.
|
||||
* @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md
|
||||
* @link https://www.phptesting.org/
|
||||
*/
|
||||
|
||||
namespace PHPCI\Controller;
|
||||
|
||||
use b8;
|
||||
use PHPCI\Helper\Email;
|
||||
use PHPCI\Helper\Lang;
|
||||
|
||||
/**
|
||||
* Session Controller - Handles user login / logout.
|
||||
* @author Dan Cryer <dan@block8.co.uk>
|
||||
* @package PHPCI
|
||||
* @subpackage Web
|
||||
*/
|
||||
class SessionController extends \PHPCI\Controller
|
||||
{
|
||||
/**
|
||||
* @var \PHPCI\Store\UserStore
|
||||
*/
|
||||
protected $userStore;
|
||||
|
||||
/**
|
||||
* Initialise the controller, set up stores and services.
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
$this->response->disableLayout();
|
||||
$this->userStore = b8\Store\Factory::getStore('User');
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles user login (form and processing)
|
||||
*/
|
||||
public function login()
|
||||
{
|
||||
$isLoginFailure = false;
|
||||
|
||||
if ($this->request->getMethod() == 'POST') {
|
||||
$token = $this->getParam('token');
|
||||
if (!isset($token, $_SESSION['login_token']) || $token !== $_SESSION['login_token']) {
|
||||
$isLoginFailure = true;
|
||||
} else {
|
||||
unset($_SESSION['login_token']);
|
||||
|
||||
$user = $this->userStore->getByEmail($this->getParam('email'));
|
||||
|
||||
if ($user && password_verify($this->getParam('password', ''), $user->getHash())) {
|
||||
session_regenerate_id(true);
|
||||
$_SESSION['phpci_user_id'] = $user->getId();
|
||||
$response = new b8\Http\Response\RedirectResponse();
|
||||
$response->setHeader('Location', $this->getLoginRedirect());
|
||||
return $response;
|
||||
} else {
|
||||
$isLoginFailure = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$form = new b8\Form();
|
||||
$form->setMethod('POST');
|
||||
$form->setAction(PHPCI_URL.'session/login');
|
||||
|
||||
$email = new b8\Form\Element\Email('email');
|
||||
$email->setLabel(Lang::get('email_address'));
|
||||
$email->setRequired(true);
|
||||
$email->setContainerClass('form-group');
|
||||
$email->setClass('form-control');
|
||||
$form->addField($email);
|
||||
|
||||
$pwd = new b8\Form\Element\Password('password');
|
||||
$pwd->setLabel(Lang::get('password'));
|
||||
$pwd->setRequired(true);
|
||||
$pwd->setContainerClass('form-group');
|
||||
$pwd->setClass('form-control');
|
||||
$form->addField($pwd);
|
||||
|
||||
$pwd = new b8\Form\Element\Submit();
|
||||
$pwd->setValue(Lang::get('log_in'));
|
||||
$pwd->setClass('btn-success');
|
||||
$form->addField($pwd);
|
||||
|
||||
$tokenValue = $this->generateToken();
|
||||
$_SESSION['login_token'] = $tokenValue;
|
||||
$token = new b8\Form\Element\Hidden('token');
|
||||
$token->setValue($tokenValue);
|
||||
$form->addField($token);
|
||||
|
||||
$this->view->form = $form->render();
|
||||
$this->view->failed = $isLoginFailure;
|
||||
|
||||
return $this->view->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles user logout.
|
||||
*/
|
||||
public function logout()
|
||||
{
|
||||
unset($_SESSION['phpci_user']);
|
||||
unset($_SESSION['phpci_user_id']);
|
||||
|
||||
session_destroy();
|
||||
|
||||
$response = new b8\Http\Response\RedirectResponse();
|
||||
$response->setHeader('Location', PHPCI_URL);
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows the user to request a password reset email.
|
||||
* @return string
|
||||
*/
|
||||
public function forgotPassword()
|
||||
{
|
||||
if ($this->request->getMethod() == 'POST') {
|
||||
$email = $this->getParam('email', null);
|
||||
$user = $this->userStore->getByEmail($email);
|
||||
|
||||
if (empty($user)) {
|
||||
$this->view->error = Lang::get('reset_no_user_exists');
|
||||
return $this->view->render();
|
||||
}
|
||||
|
||||
$key = md5(date('Y-m-d') . $user->getHash());
|
||||
$url = PHPCI_URL;
|
||||
|
||||
$message = Lang::get('reset_email_body', $user->getName(), $url, $user->getId(), $key);
|
||||
|
||||
$email = new Email();
|
||||
$email->setEmailTo($user->getEmail(), $user->getName());
|
||||
$email->setSubject(Lang::get('reset_email_title', $user->getName()));
|
||||
$email->setBody($message);
|
||||
$email->send();
|
||||
|
||||
$this->view->emailed = true;
|
||||
}
|
||||
|
||||
return $this->view->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows the user to change their password after a password reset email.
|
||||
* @param $userId
|
||||
* @param $key
|
||||
* @return string
|
||||
*/
|
||||
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 = Lang::get('reset_invalid');
|
||||
return $this->view->render();
|
||||
}
|
||||
|
||||
if ($this->request->getMethod() == 'POST') {
|
||||
$hash = password_hash($this->getParam('password'), PASSWORD_DEFAULT);
|
||||
$user->setHash($hash);
|
||||
|
||||
$_SESSION['phpci_user'] = $this->userStore->save($user);
|
||||
$_SESSION['phpci_user_id'] = $user->getId();
|
||||
|
||||
$response = new b8\Http\Response\RedirectResponse();
|
||||
$response->setHeader('Location', PHPCI_URL);
|
||||
return $response;
|
||||
}
|
||||
|
||||
$this->view->id = $userId;
|
||||
$this->view->key = $key;
|
||||
|
||||
return $this->view->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the URL the user was trying to go to prior to being asked to log in.
|
||||
* @return string
|
||||
*/
|
||||
protected function getLoginRedirect()
|
||||
{
|
||||
$rtn = PHPCI_URL;
|
||||
|
||||
if (!empty($_SESSION['phpci_login_redirect'])) {
|
||||
$rtn .= $_SESSION['phpci_login_redirect'];
|
||||
$_SESSION['phpci_login_redirect'] = null;
|
||||
}
|
||||
|
||||
return $rtn;
|
||||
}
|
||||
|
||||
/** Generate a random token.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function generateToken()
|
||||
{
|
||||
if (function_exists('openssl_random_pseudo_bytes')) {
|
||||
return bin2hex(openssl_random_pseudo_bytes(16));
|
||||
}
|
||||
|
||||
return sprintf("%04x", mt_rand(0, 0xFFFF))
|
||||
. sprintf("%04x", mt_rand(0, 0xFFFF))
|
||||
. sprintf("%04x", mt_rand(0, 0xFFFF))
|
||||
. sprintf("%04x", mt_rand(0, 0xFFFF));
|
||||
}
|
||||
}
|
||||
496
src/PHPCI/Controller/SettingsController.php
Normal file
496
src/PHPCI/Controller/SettingsController.php
Normal file
|
|
@ -0,0 +1,496 @@
|
|||
<?php
|
||||
/**
|
||||
* PHPCI - Continuous Integration for PHP
|
||||
*
|
||||
* @copyright Copyright 2014, Block 8 Limited.
|
||||
* @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md
|
||||
* @link https://www.phptesting.org/
|
||||
*/
|
||||
|
||||
namespace PHPCI\Controller;
|
||||
|
||||
use b8;
|
||||
use b8\Form;
|
||||
use b8\HttpClient;
|
||||
use PHPCI\Controller;
|
||||
use PHPCI\Helper\Lang;
|
||||
use Symfony\Component\Yaml\Dumper;
|
||||
use Symfony\Component\Yaml\Parser;
|
||||
|
||||
/**
|
||||
* Settings Controller
|
||||
*
|
||||
* @author Dan Cryer <dan@block8.co.uk>
|
||||
* @package PHPCI
|
||||
* @subpackage Web
|
||||
*/
|
||||
class SettingsController extends Controller
|
||||
{
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $settings;
|
||||
|
||||
/**
|
||||
* Initialise the controller, set up stores and services.
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
parent::init();
|
||||
|
||||
$parser = new Parser();
|
||||
$yaml = file_get_contents(PHPCI_CONFIG_FILE);
|
||||
$this->settings = $parser->parse($yaml);
|
||||
}
|
||||
|
||||
/**
|
||||
* Display settings forms.
|
||||
* @return string
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$this->requireAdmin();
|
||||
|
||||
$this->layout->title = Lang::get('settings');
|
||||
|
||||
$this->view->settings = $this->settings;
|
||||
|
||||
$basicSettings = array();
|
||||
if (isset($this->settings['phpci']['basic'])) {
|
||||
$basicSettings = $this->settings['phpci']['basic'];
|
||||
}
|
||||
|
||||
$buildSettings = array();
|
||||
if (isset($this->settings['phpci']['build'])) {
|
||||
$buildSettings = $this->settings['phpci']['build'];
|
||||
}
|
||||
|
||||
$emailSettings = array();
|
||||
if (isset($this->settings['phpci']['email_settings'])) {
|
||||
$emailSettings = $this->settings['phpci']['email_settings'];
|
||||
}
|
||||
|
||||
$authSettings = array();
|
||||
if (isset($this->settings['phpci']['authentication_settings'])) {
|
||||
$authSettings = $this->settings['phpci']['authentication_settings'];
|
||||
}
|
||||
|
||||
$this->view->configFile = PHPCI_CONFIG_FILE;
|
||||
$this->view->basicSettings = $this->getBasicForm($basicSettings);
|
||||
$this->view->buildSettings = $this->getBuildForm($buildSettings);
|
||||
$this->view->github = $this->getGithubForm();
|
||||
$this->view->emailSettings = $this->getEmailForm($emailSettings);
|
||||
$this->view->authenticationSettings = $this->getAuthenticationForm($authSettings);
|
||||
$this->view->isWriteable = $this->canWriteConfig();
|
||||
|
||||
if (!empty($this->settings['phpci']['github']['token'])) {
|
||||
$this->view->githubUser = $this->getGithubUser($this->settings['phpci']['github']['token']);
|
||||
}
|
||||
|
||||
return $this->view->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Save Github settings.
|
||||
*/
|
||||
public function github()
|
||||
{
|
||||
$this->requireAdmin();
|
||||
|
||||
$this->settings['phpci']['github']['id'] = $this->getParam('githubid', '');
|
||||
$this->settings['phpci']['github']['secret'] = $this->getParam('githubsecret', '');
|
||||
$error = $this->storeSettings();
|
||||
|
||||
$response = new b8\Http\Response\RedirectResponse();
|
||||
|
||||
if ($error) {
|
||||
$response->setHeader('Location', PHPCI_URL . 'settings?saved=2');
|
||||
} else {
|
||||
$response->setHeader('Location', PHPCI_URL . 'settings?saved=1');
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save email settings.
|
||||
*/
|
||||
public function email()
|
||||
{
|
||||
$this->requireAdmin();
|
||||
|
||||
$this->settings['phpci']['email_settings'] = $this->getParams();
|
||||
$this->settings['phpci']['email_settings']['smtp_encryption'] = $this->getParam('smtp_encryption', 0);
|
||||
|
||||
$error = $this->storeSettings();
|
||||
|
||||
$response = new b8\Http\Response\RedirectResponse();
|
||||
|
||||
if ($error) {
|
||||
$response->setHeader('Location', PHPCI_URL . 'settings?saved=2');
|
||||
} else {
|
||||
$response->setHeader('Location', PHPCI_URL . 'settings?saved=1');
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save build settings.
|
||||
*/
|
||||
public function build()
|
||||
{
|
||||
$this->requireAdmin();
|
||||
|
||||
$this->settings['phpci']['build'] = $this->getParams();
|
||||
|
||||
$error = $this->storeSettings();
|
||||
|
||||
$response = new b8\Http\Response\RedirectResponse();
|
||||
|
||||
if ($error) {
|
||||
$response->setHeader('Location', PHPCI_URL . 'settings?saved=2');
|
||||
} else {
|
||||
$response->setHeader('Location', PHPCI_URL . 'settings?saved=1');
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save basic settings.
|
||||
*/
|
||||
public function basic()
|
||||
{
|
||||
$this->requireAdmin();
|
||||
|
||||
$this->settings['phpci']['basic'] = $this->getParams();
|
||||
$error = $this->storeSettings();
|
||||
|
||||
$response = new b8\Http\Response\RedirectResponse();
|
||||
|
||||
if ($error) {
|
||||
$response->setHeader('Location', PHPCI_URL . 'settings?saved=2');
|
||||
} else {
|
||||
$response->setHeader('Location', PHPCI_URL . 'settings?saved=1');
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle authentication settings
|
||||
*/
|
||||
public function authentication()
|
||||
{
|
||||
$this->requireAdmin();
|
||||
|
||||
$this->settings['phpci']['authentication_settings']['state'] = $this->getParam('disable_authentication', 0);
|
||||
$this->settings['phpci']['authentication_settings']['user_id'] = $_SESSION['phpci_user_id'];
|
||||
|
||||
$error = $this->storeSettings();
|
||||
|
||||
$response = new b8\Http\Response\RedirectResponse();
|
||||
|
||||
if ($error) {
|
||||
$response->setHeader('Location', PHPCI_URL . 'settings?saved=2');
|
||||
} else {
|
||||
$response->setHeader('Location', PHPCI_URL . 'settings?saved=1');
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Github redirects users back to this URL when t
|
||||
*/
|
||||
public function githubCallback()
|
||||
{
|
||||
$code = $this->getParam('code', null);
|
||||
$github = $this->settings['phpci']['github'];
|
||||
|
||||
if (!is_null($code)) {
|
||||
$http = new HttpClient();
|
||||
$url = 'https://github.com/login/oauth/access_token';
|
||||
$params = array('client_id' => $github['id'], 'client_secret' => $github['secret'], 'code' => $code);
|
||||
$resp = $http->post($url, $params);
|
||||
|
||||
if ($resp['success']) {
|
||||
parse_str($resp['body'], $resp);
|
||||
|
||||
$this->settings['phpci']['github']['token'] = $resp['access_token'];
|
||||
$this->storeSettings();
|
||||
|
||||
$response = new b8\Http\Response\RedirectResponse();
|
||||
$response->setHeader('Location', PHPCI_URL . 'settings?linked=1');
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
|
||||
$response = new b8\Http\Response\RedirectResponse();
|
||||
$response->setHeader('Location', PHPCI_URL . 'settings?linked=2');
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert config to yaml and store to file.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
protected function storeSettings()
|
||||
{
|
||||
$dumper = new Dumper();
|
||||
$yaml = $dumper->dump($this->settings, 4);
|
||||
file_put_contents(PHPCI_CONFIG_FILE, $yaml);
|
||||
|
||||
if (error_get_last()) {
|
||||
$error_get_last = error_get_last();
|
||||
return $error_get_last['message'];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Github settings form.
|
||||
* @return Form
|
||||
*/
|
||||
protected function getGithubForm()
|
||||
{
|
||||
$form = new Form();
|
||||
$form->setMethod('POST');
|
||||
$form->setAction(PHPCI_URL . 'settings/github');
|
||||
$form->addField(new Form\Element\Csrf('csrf'));
|
||||
|
||||
$field = new Form\Element\Text('githubid');
|
||||
$field->setRequired(true);
|
||||
$field->setPattern('[a-zA-Z0-9]+');
|
||||
$field->setLabel(Lang::get('application_id'));
|
||||
$field->setClass('form-control');
|
||||
$field->setContainerClass('form-group');
|
||||
$form->addField($field);
|
||||
|
||||
if (isset($this->settings['phpci']['github']['id'])) {
|
||||
$field->setValue($this->settings['phpci']['github']['id']);
|
||||
}
|
||||
|
||||
$field = new Form\Element\Text('githubsecret');
|
||||
$field->setRequired(true);
|
||||
$field->setPattern('[a-zA-Z0-9]+');
|
||||
$field->setLabel(Lang::get('application_secret'));
|
||||
$field->setClass('form-control');
|
||||
$field->setContainerClass('form-group');
|
||||
$form->addField($field);
|
||||
|
||||
if (isset($this->settings['phpci']['github']['secret'])) {
|
||||
$field->setValue($this->settings['phpci']['github']['secret']);
|
||||
}
|
||||
|
||||
$field = new Form\Element\Submit();
|
||||
$field->setValue(Lang::get('save'));
|
||||
$field->setClass('btn btn-success pull-right');
|
||||
$form->addField($field);
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the email settings form.
|
||||
* @param array $values
|
||||
* @return Form
|
||||
*/
|
||||
protected function getEmailForm($values = array())
|
||||
{
|
||||
$form = new Form();
|
||||
$form->setMethod('POST');
|
||||
$form->setAction(PHPCI_URL . 'settings/email');
|
||||
$form->addField(new Form\Element\Csrf('csrf'));
|
||||
|
||||
$field = new Form\Element\Text('smtp_address');
|
||||
$field->setRequired(false);
|
||||
$field->setLabel(Lang::get('smtp_server'));
|
||||
$field->setClass('form-control');
|
||||
$field->setContainerClass('form-group');
|
||||
$field->setValue('localhost');
|
||||
$form->addField($field);
|
||||
|
||||
$field = new Form\Element\Text('smtp_port');
|
||||
$field->setRequired(false);
|
||||
$field->setPattern('[0-9]+');
|
||||
$field->setLabel(Lang::get('smtp_port'));
|
||||
$field->setClass('form-control');
|
||||
$field->setContainerClass('form-group');
|
||||
$field->setValue(25);
|
||||
$form->addField($field);
|
||||
|
||||
$field = new Form\Element\Text('smtp_username');
|
||||
$field->setRequired(false);
|
||||
$field->setLabel(Lang::get('smtp_username'));
|
||||
$field->setClass('form-control');
|
||||
$field->setContainerClass('form-group');
|
||||
$form->addField($field);
|
||||
|
||||
$field = new Form\Element\Password('smtp_password');
|
||||
$field->setRequired(false);
|
||||
$field->setLabel(Lang::get('smtp_password'));
|
||||
$field->setClass('form-control');
|
||||
$field->setContainerClass('form-group');
|
||||
$form->addField($field);
|
||||
|
||||
$field = new Form\Element\Email('from_address');
|
||||
$field->setRequired(false);
|
||||
$field->setLabel(Lang::get('from_email_address'));
|
||||
$field->setClass('form-control');
|
||||
$field->setContainerClass('form-group');
|
||||
$form->addField($field);
|
||||
|
||||
$field = new Form\Element\Email('default_mailto_address');
|
||||
$field->setRequired(false);
|
||||
$field->setLabel(Lang::get('default_notification_address'));
|
||||
$field->setClass('form-control');
|
||||
$field->setContainerClass('form-group');
|
||||
$form->addField($field);
|
||||
|
||||
$field = new Form\Element\Select('smtp_encryption');
|
||||
$field->setOptions(array('' => Lang::get('none'), 'tls' => Lang::get('tls'), 'ssl' => Lang::get('ssl')));
|
||||
$field->setRequired(false);
|
||||
$field->setLabel(Lang::get('use_smtp_encryption'));
|
||||
$field->setContainerClass('form-group');
|
||||
$field->setValue(1);
|
||||
$form->addField($field);
|
||||
|
||||
$field = new Form\Element\Submit();
|
||||
$field->setValue(Lang::get('save'));
|
||||
$field->setClass('btn btn-success pull-right');
|
||||
$form->addField($field);
|
||||
|
||||
$form->setValues($values);
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Call Github API for our Github user object.
|
||||
* @param $token
|
||||
* @return mixed
|
||||
*/
|
||||
protected function getGithubUser($token)
|
||||
{
|
||||
$http = new HttpClient('https://api.github.com');
|
||||
$user = $http->get('/user', array('access_token' => $token));
|
||||
|
||||
return $user['body'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if we can write the PHPCI config file.
|
||||
* @return bool
|
||||
*/
|
||||
protected function canWriteConfig()
|
||||
{
|
||||
return is_writeable(PHPCI_CONFIG_FILE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Build settings form.
|
||||
* @param array $values
|
||||
* @return Form
|
||||
*/
|
||||
protected function getBuildForm($values = array())
|
||||
{
|
||||
$form = new Form();
|
||||
$form->setMethod('POST');
|
||||
$form->setAction(PHPCI_URL . 'settings/build');
|
||||
|
||||
$field = new Form\Element\Select('failed_after');
|
||||
$field->setRequired(false);
|
||||
$field->setLabel(Lang::get('failed_after'));
|
||||
$field->setClass('form-control');
|
||||
$field->setContainerClass('form-group');
|
||||
$field->setOptions(array(
|
||||
300 => Lang::get('5_mins'),
|
||||
900 => Lang::get('15_mins'),
|
||||
1800 => Lang::get('30_mins'),
|
||||
3600 => Lang::get('1_hour'),
|
||||
10800 => Lang::get('3_hours'),
|
||||
));
|
||||
$field->setValue(1800);
|
||||
$form->addField($field);
|
||||
|
||||
|
||||
$field = new Form\Element\Submit();
|
||||
$field->setValue(Lang::get('save'));
|
||||
$field->setClass('btn btn-success pull-right');
|
||||
$form->addField($field);
|
||||
|
||||
$form->setValues($values);
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Basic settings form.
|
||||
* @param array $values
|
||||
* @return Form
|
||||
*/
|
||||
protected function getBasicForm($values = array())
|
||||
{
|
||||
$form = new Form();
|
||||
$form->setMethod('POST');
|
||||
$form->setAction(PHPCI_URL . 'settings/basic');
|
||||
|
||||
$field = new Form\Element\Select('language');
|
||||
$field->setRequired(true);
|
||||
$field->setLabel(Lang::get('language'));
|
||||
$field->setClass('form-control');
|
||||
$field->setContainerClass('form-group');
|
||||
$field->setOptions(Lang::getLanguageOptions());
|
||||
$field->setValue(Lang::getLanguage());
|
||||
$form->addField($field);
|
||||
|
||||
|
||||
$field = new Form\Element\Submit();
|
||||
$field->setValue(Lang::get('save'));
|
||||
$field->setClass('btn btn-success pull-right');
|
||||
$form->addField($field);
|
||||
|
||||
$form->setValues($values);
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Form for disabling user authentication while using a default user
|
||||
*
|
||||
* @param array $values
|
||||
* @return Form
|
||||
*/
|
||||
protected function getAuthenticationForm($values = array())
|
||||
{
|
||||
$form = new Form();
|
||||
$form->setMethod('POST');
|
||||
$form->setAction(PHPCI_URL . 'settings/authentication');
|
||||
$form->addField(new Form\Element\Csrf('csrf'));
|
||||
|
||||
$field = new Form\Element\Checkbox('disable_authentication');
|
||||
$field->setCheckedValue(1);
|
||||
$field->setRequired(false);
|
||||
$field->setLabel('Disable Authentication?');
|
||||
$field->setContainerClass('form-group');
|
||||
$field->setValue(0);
|
||||
|
||||
if (isset($values['state'])) {
|
||||
$field->setValue((int)$values['state']);
|
||||
}
|
||||
|
||||
$form->addField($field);
|
||||
|
||||
$field = new Form\Element\Submit();
|
||||
$field->setValue('Save »');
|
||||
$field->setClass('btn btn-success pull-right');
|
||||
$form->addField($field);
|
||||
|
||||
$form->setValues($values);
|
||||
|
||||
return $form;
|
||||
}
|
||||
}
|
||||
296
src/PHPCI/Controller/UserController.php
Normal file
296
src/PHPCI/Controller/UserController.php
Normal file
|
|
@ -0,0 +1,296 @@
|
|||
<?php
|
||||
/**
|
||||
* PHPCI - Continuous Integration for PHP
|
||||
*
|
||||
* @copyright Copyright 2014, Block 8 Limited.
|
||||
* @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md
|
||||
* @link https://www.phptesting.org/
|
||||
*/
|
||||
|
||||
namespace PHPCI\Controller;
|
||||
|
||||
use b8;
|
||||
use b8\Exception\HttpException\NotFoundException;
|
||||
use b8\Form;
|
||||
use PHPCI\Controller;
|
||||
use PHPCI\Helper\Lang;
|
||||
use PHPCI\Service\UserService;
|
||||
|
||||
/**
|
||||
* User Controller - Allows an administrator to view, add, edit and delete users.
|
||||
* @author Dan Cryer <dan@block8.co.uk>
|
||||
* @package PHPCI
|
||||
* @subpackage Web
|
||||
*/
|
||||
class UserController extends Controller
|
||||
{
|
||||
/**
|
||||
* @var \PHPCI\Store\UserStore
|
||||
*/
|
||||
protected $userStore;
|
||||
|
||||
/**
|
||||
* @var \PHPCI\Service\UserService
|
||||
*/
|
||||
protected $userService;
|
||||
|
||||
/**
|
||||
* Initialise the controller, set up stores and services.
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
$this->userStore = b8\Store\Factory::getStore('User');
|
||||
$this->userService = new UserService($this->userStore);
|
||||
}
|
||||
|
||||
/**
|
||||
* View user list.
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$users = $this->userStore->getWhere(array(), 1000, 0, array(), array('email' => 'ASC'));
|
||||
$this->view->users = $users;
|
||||
|
||||
$this->layout->title = Lang::get('manage_users');
|
||||
|
||||
return $this->view->render();
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows the user to edit their profile.
|
||||
* @return string
|
||||
*/
|
||||
public function profile()
|
||||
{
|
||||
$user = $_SESSION['phpci_user'];
|
||||
|
||||
if ($this->request->getMethod() == 'POST') {
|
||||
$name = $this->getParam('name', null);
|
||||
$email = $this->getParam('email', null);
|
||||
$password = $this->getParam('password', null);
|
||||
|
||||
$currentLang = Lang::getLanguage();
|
||||
$chosenLang = $this->getParam('language', $currentLang);
|
||||
|
||||
if ($chosenLang !== $currentLang) {
|
||||
setcookie('phpcilang', $chosenLang, time() + (10 * 365 * 24 * 60 * 60), '/');
|
||||
Lang::setLanguage($chosenLang);
|
||||
}
|
||||
|
||||
$_SESSION['phpci_user'] = $this->userService->updateUser($user, $name, $email, $password);
|
||||
$user = $_SESSION['phpci_user'];
|
||||
|
||||
$this->view->updated = 1;
|
||||
}
|
||||
|
||||
$this->layout->title = $user->getName();
|
||||
$this->layout->subtitle = Lang::get('edit_profile');
|
||||
|
||||
$values = $user->getDataArray();
|
||||
|
||||
if (array_key_exists('phpcilang', $_COOKIE)) {
|
||||
$values['language'] = $_COOKIE['phpcilang'];
|
||||
}
|
||||
|
||||
$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(Lang::get('name'));
|
||||
$name->setRequired(true);
|
||||
$form->addField($name);
|
||||
|
||||
$email = new Form\Element\Email('email');
|
||||
$email->setClass('form-control');
|
||||
$email->setContainerClass('form-group');
|
||||
$email->setLabel(Lang::get('email_address'));
|
||||
$email->setRequired(true);
|
||||
$form->addField($email);
|
||||
|
||||
$password = new Form\Element\Password('password');
|
||||
$password->setClass('form-control');
|
||||
$password->setContainerClass('form-group');
|
||||
$password->setLabel(Lang::get('password_change'));
|
||||
$password->setRequired(false);
|
||||
$form->addField($password);
|
||||
|
||||
$lang = new Form\Element\Select('language');
|
||||
$lang->setClass('form-control');
|
||||
$lang->setContainerClass('form-group');
|
||||
$lang->setLabel(Lang::get('language'));
|
||||
$lang->setRequired(true);
|
||||
$lang->setOptions(Lang::getLanguageOptions());
|
||||
$lang->setValue(Lang::getLanguage());
|
||||
$form->addField($lang);
|
||||
|
||||
$submit = new Form\Element\Submit();
|
||||
$submit->setClass('btn btn-success');
|
||||
$submit->setValue(Lang::get('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()
|
||||
{
|
||||
$this->requireAdmin();
|
||||
|
||||
$this->layout->title = Lang::get('add_user');
|
||||
|
||||
$method = $this->request->getMethod();
|
||||
|
||||
if ($method == 'POST') {
|
||||
$values = $this->getParams();
|
||||
} else {
|
||||
$values = array();
|
||||
}
|
||||
|
||||
$form = $this->userForm($values);
|
||||
|
||||
if ($method != 'POST' || ($method == 'POST' && !$form->validate())) {
|
||||
$view = new b8\View('UserForm');
|
||||
$view->type = 'add';
|
||||
$view->user = null;
|
||||
$view->form = $form;
|
||||
|
||||
return $view->render();
|
||||
}
|
||||
|
||||
|
||||
$name = $this->getParam('name', null);
|
||||
$email = $this->getParam('email', null);
|
||||
$password = $this->getParam('password', null);
|
||||
$isAdmin = (int)$this->getParam('is_admin', 0);
|
||||
|
||||
$this->userService->createUser($name, $email, $password, $isAdmin);
|
||||
|
||||
$response = new b8\Http\Response\RedirectResponse();
|
||||
$response->setHeader('Location', PHPCI_URL . 'user');
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Edit a user - handles both form and processing.
|
||||
*/
|
||||
public function edit($userId)
|
||||
{
|
||||
$this->requireAdmin();
|
||||
|
||||
$method = $this->request->getMethod();
|
||||
$user = $this->userStore->getById($userId);
|
||||
|
||||
if (empty($user)) {
|
||||
throw new NotFoundException(Lang::get('user_n_not_found', $userId));
|
||||
}
|
||||
|
||||
$this->layout->title = $user->getName();
|
||||
$this->layout->subtitle = Lang::get('edit_user');
|
||||
|
||||
$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;
|
||||
|
||||
return $view->render();
|
||||
}
|
||||
|
||||
$name = $this->getParam('name', null);
|
||||
$email = $this->getParam('email', null);
|
||||
$password = $this->getParam('password', null);
|
||||
$isAdmin = (int)$this->getParam('is_admin', 0);
|
||||
|
||||
$this->userService->updateUser($user, $name, $email, $password, $isAdmin);
|
||||
|
||||
$response = new b8\Http\Response\RedirectResponse();
|
||||
$response->setHeader('Location', PHPCI_URL . 'user');
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create user add / edit form.
|
||||
*/
|
||||
protected function userForm($values, $type = 'add')
|
||||
{
|
||||
$form = new Form();
|
||||
$form->setMethod('POST');
|
||||
$form->setAction(PHPCI_URL.'user/' . $type);
|
||||
$form->addField(new Form\Element\Csrf('csrf'));
|
||||
|
||||
$field = new Form\Element\Email('email');
|
||||
$field->setRequired(true);
|
||||
$field->setLabel(Lang::get('email_address'));
|
||||
$field->setClass('form-control');
|
||||
$field->setContainerClass('form-group');
|
||||
$form->addField($field);
|
||||
|
||||
$field = new Form\Element\Text('name');
|
||||
$field->setRequired(true);
|
||||
$field->setLabel(Lang::get('name'));
|
||||
$field->setClass('form-control');
|
||||
$field->setContainerClass('form-group');
|
||||
$form->addField($field);
|
||||
|
||||
$field = new Form\Element\Password('password');
|
||||
|
||||
if ($type == 'add') {
|
||||
$field->setRequired(true);
|
||||
$field->setLabel(Lang::get('password'));
|
||||
} else {
|
||||
$field->setRequired(false);
|
||||
$field->setLabel(Lang::get('password_change'));
|
||||
}
|
||||
|
||||
$field->setClass('form-control');
|
||||
$field->setContainerClass('form-group');
|
||||
$form->addField($field);
|
||||
|
||||
$field = new Form\Element\Checkbox('is_admin');
|
||||
$field->setRequired(false);
|
||||
$field->setCheckedValue(1);
|
||||
$field->setLabel(Lang::get('is_user_admin'));
|
||||
$field->setContainerClass('form-group');
|
||||
$form->addField($field);
|
||||
|
||||
$field = new Form\Element\Submit();
|
||||
$field->setValue(Lang::get('save_user'));
|
||||
$field->setClass('btn-success');
|
||||
$form->addField($field);
|
||||
|
||||
$form->setValues($values);
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a user.
|
||||
*/
|
||||
public function delete($userId)
|
||||
{
|
||||
$this->requireAdmin();
|
||||
|
||||
$user = $this->userStore->getById($userId);
|
||||
|
||||
if (empty($user)) {
|
||||
throw new NotFoundException(Lang::get('user_n_not_found', $userId));
|
||||
}
|
||||
|
||||
$this->userService->deleteUser($user);
|
||||
|
||||
$response = new b8\Http\Response\RedirectResponse();
|
||||
$response->setHeader('Location', PHPCI_URL . 'user');
|
||||
return $response;
|
||||
}
|
||||
}
|
||||
453
src/PHPCI/Controller/WebhookController.php
Normal file
453
src/PHPCI/Controller/WebhookController.php
Normal file
|
|
@ -0,0 +1,453 @@
|
|||
<?php
|
||||
/**
|
||||
* PHPCI - Continuous Integration for PHP
|
||||
*
|
||||
* @copyright Copyright 2014-2015, Block 8 Limited.
|
||||
* @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md
|
||||
* @link https://www.phptesting.org/
|
||||
*/
|
||||
|
||||
namespace PHPCI\Controller;
|
||||
|
||||
use b8;
|
||||
use b8\Store;
|
||||
use Exception;
|
||||
use PHPCI\BuildFactory;
|
||||
use PHPCI\Model\Project;
|
||||
use PHPCI\Service\BuildService;
|
||||
use PHPCI\Store\BuildStore;
|
||||
use PHPCI\Store\ProjectStore;
|
||||
|
||||
/**
|
||||
* Webhook Controller - Processes webhook pings from BitBucket, Github, Gitlab, etc.
|
||||
*
|
||||
* @author Dan Cryer <dan@block8.co.uk>
|
||||
* @author Sami Tikka <stikka@iki.fi>
|
||||
* @author Alex Russell <alex@clevercherry.com>
|
||||
* @author Guillaume Perréal <adirelle@gmail.com>
|
||||
* @package PHPCI
|
||||
* @subpackage Web
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
|
||||
*/
|
||||
class WebhookController extends \b8\Controller
|
||||
{
|
||||
/**
|
||||
* @var BuildStore
|
||||
*/
|
||||
protected $buildStore;
|
||||
|
||||
/**
|
||||
* @var ProjectStore
|
||||
*/
|
||||
protected $projectStore;
|
||||
|
||||
/**
|
||||
* @var BuildService
|
||||
*/
|
||||
protected $buildService;
|
||||
|
||||
/**
|
||||
* Initialise the controller, set up stores and services.
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
$this->buildStore = Store\Factory::getStore('Build');
|
||||
$this->projectStore = Store\Factory::getStore('Project');
|
||||
$this->buildService = new BuildService($this->buildStore);
|
||||
}
|
||||
|
||||
/** Handle the action, Ensuring to return a JsonResponse.
|
||||
*
|
||||
* @param string $action
|
||||
* @param mixed $actionParams
|
||||
*
|
||||
* @return \b8\Http\Response
|
||||
*/
|
||||
public function handleAction($action, $actionParams)
|
||||
{
|
||||
$response = new b8\Http\Response\JsonResponse();
|
||||
try {
|
||||
$data = parent::handleAction($action, $actionParams);
|
||||
if (isset($data['responseCode'])) {
|
||||
$response->setResponseCode($data['responseCode']);
|
||||
unset($data['responseCode']);
|
||||
}
|
||||
$response->setContent($data);
|
||||
} catch (Exception $ex) {
|
||||
$response->setResponseCode(500);
|
||||
$response->setContent(array('status' => 'failed', 'error' => $ex->getMessage()));
|
||||
}
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by Bitbucket.
|
||||
*/
|
||||
public function bitbucket($projectId)
|
||||
{
|
||||
$project = $this->fetchProject($projectId, 'bitbucket');
|
||||
|
||||
// Support both old services and new webhooks
|
||||
if ($payload = $this->getParam('payload')) {
|
||||
return $this->bitbucketService(json_decode($payload, true), $project);
|
||||
}
|
||||
|
||||
$payload = json_decode(file_get_contents("php://input"), true);
|
||||
|
||||
if (empty($payload['push']['changes'])) {
|
||||
// Invalid event from bitbucket
|
||||
return [
|
||||
'status' => 'failed',
|
||||
'commits' => []
|
||||
];
|
||||
}
|
||||
|
||||
return $this->bitbucketWebhook($payload, $project);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bitbucket webhooks.
|
||||
*/
|
||||
protected function bitbucketWebhook($payload, $project)
|
||||
{
|
||||
$results = array();
|
||||
$status = 'failed';
|
||||
foreach ($payload['push']['changes'] as $commit) {
|
||||
try {
|
||||
$email = $commit['new']['target']['author']['raw'];
|
||||
$email = substr($email, 0, strpos($email, '>'));
|
||||
$email = substr($email, strpos($email, '<') + 1);
|
||||
|
||||
$results[$commit['new']['target']['hash']] = $this->createBuild(
|
||||
$project,
|
||||
$commit['new']['target']['hash'],
|
||||
$commit['new']['name'],
|
||||
$email,
|
||||
$commit['new']['target']['message']
|
||||
);
|
||||
$status = 'ok';
|
||||
} catch (Exception $ex) {
|
||||
$results[$commit['new']['target']['hash']] = array('status' => 'failed', 'error' => $ex->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
return array('status' => $status, 'commits' => $results);
|
||||
}
|
||||
|
||||
/**
|
||||
* Bitbucket POST service.
|
||||
*/
|
||||
protected function bitbucketService($payload, $project)
|
||||
{
|
||||
$payload = json_decode($this->getParam('payload'), true);
|
||||
|
||||
$results = array();
|
||||
$status = 'failed';
|
||||
foreach ($payload['commits'] as $commit) {
|
||||
try {
|
||||
$email = $commit['raw_author'];
|
||||
$email = substr($email, 0, strpos($email, '>'));
|
||||
$email = substr($email, strpos($email, '<') + 1);
|
||||
|
||||
$results[$commit['raw_node']] = $this->createBuild(
|
||||
$project,
|
||||
$commit['raw_node'],
|
||||
$commit['branch'],
|
||||
$email,
|
||||
$commit['message']
|
||||
);
|
||||
$status = 'ok';
|
||||
} catch (Exception $ex) {
|
||||
$results[$commit['raw_node']] = array('status' => 'failed', 'error' => $ex->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
return array('status' => $status, 'commits' => $results);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by POSTing to /webhook/git/<project_id>?branch=<branch>&commit=<commit>
|
||||
*
|
||||
* @param string $projectId
|
||||
*/
|
||||
public function git($projectId)
|
||||
{
|
||||
$project = $this->fetchProject($projectId, array('local', 'remote'));
|
||||
$branch = $this->getParam('branch', $project->getBranch());
|
||||
$commit = $this->getParam('commit');
|
||||
$commitMessage = $this->getParam('message');
|
||||
$committer = $this->getParam('committer');
|
||||
|
||||
return $this->createBuild($project, $commit, $branch, $committer, $commitMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by Github Webhooks:
|
||||
*/
|
||||
public function github($projectId)
|
||||
{
|
||||
$project = $this->fetchProject($projectId, 'github');
|
||||
|
||||
switch ($_SERVER['CONTENT_TYPE']) {
|
||||
case 'application/json':
|
||||
$payload = json_decode(file_get_contents('php://input'), true);
|
||||
break;
|
||||
case 'application/x-www-form-urlencoded':
|
||||
$payload = json_decode($this->getParam('payload'), true);
|
||||
break;
|
||||
default:
|
||||
return array('status' => 'failed', 'error' => 'Content type not supported.', 'responseCode' => 401);
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
return array('status' => 'ignored', 'message' => 'Unusable payload.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the payload when Github sends a commit webhook.
|
||||
*
|
||||
* @param Project $project
|
||||
* @param array $payload
|
||||
* @param b8\Http\Response\JsonResponse $response
|
||||
*
|
||||
* @return b8\Http\Response\JsonResponse
|
||||
*/
|
||||
protected function githubCommitRequest(Project $project, array $payload)
|
||||
{
|
||||
// Github sends a payload when you close a pull request with a
|
||||
// non-existent commit. We don't want this.
|
||||
if (array_key_exists('after', $payload) && $payload['after'] === '0000000000000000000000000000000000000000') {
|
||||
return array('status' => 'ignored');
|
||||
}
|
||||
|
||||
if (isset($payload['commits']) && is_array($payload['commits'])) {
|
||||
// If we have a list of commits, then add them all as builds to be tested:
|
||||
|
||||
$results = array();
|
||||
$status = 'failed';
|
||||
foreach ($payload['commits'] as $commit) {
|
||||
if (!$commit['distinct']) {
|
||||
$results[$commit['id']] = array('status' => 'ignored');
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
$branch = str_replace('refs/heads/', '', $payload['ref']);
|
||||
$committer = $commit['committer']['email'];
|
||||
$results[$commit['id']] = $this->createBuild(
|
||||
$project,
|
||||
$commit['id'],
|
||||
$branch,
|
||||
$committer,
|
||||
$commit['message']
|
||||
);
|
||||
$status = 'ok';
|
||||
} catch (Exception $ex) {
|
||||
$results[$commit['id']] = array('status' => 'failed', 'error' => $ex->getMessage());
|
||||
}
|
||||
}
|
||||
return array('status' => $status, 'commits' => $results);
|
||||
}
|
||||
|
||||
if (substr($payload['ref'], 0, 10) == 'refs/tags/') {
|
||||
// If we don't, but we're dealing with a tag, add that instead:
|
||||
$branch = str_replace('refs/tags/', 'Tag: ', $payload['ref']);
|
||||
$committer = $payload['pusher']['email'];
|
||||
$message = $payload['head_commit']['message'];
|
||||
return $this->createBuild($project, $payload['after'], $branch, $committer, $message);
|
||||
}
|
||||
|
||||
return array('status' => 'ignored', 'message' => 'Unusable payload.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle the payload when Github sends a Pull Request webhook.
|
||||
*
|
||||
* @param Project $project
|
||||
* @param array $payload
|
||||
*/
|
||||
protected function githubPullRequest(Project $project, array $payload)
|
||||
{
|
||||
// We only want to know about open pull requests:
|
||||
if (!in_array($payload['action'], array('opened', 'synchronize', 'reopened'))) {
|
||||
return array('status' => 'ok');
|
||||
}
|
||||
|
||||
$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']) {
|
||||
throw new Exception('Could not get commits, failed API request.');
|
||||
}
|
||||
|
||||
$results = array();
|
||||
$status = 'failed';
|
||||
foreach ($response['body'] as $commit) {
|
||||
// Skip all but the current HEAD commit ID:
|
||||
$id = $commit['sha'];
|
||||
if ($id != $payload['pull_request']['head']['sha']) {
|
||||
$results[$id] = array('status' => 'ignored', 'message' => 'not branch head');
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
$branch = str_replace('refs/heads/', '', $payload['pull_request']['base']['ref']);
|
||||
$committer = $commit['commit']['author']['email'];
|
||||
$message = $commit['commit']['message'];
|
||||
|
||||
$remoteUrlKey = $payload['pull_request']['head']['repo']['private'] ? 'ssh_url' : 'clone_url';
|
||||
|
||||
$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'][$remoteUrlKey],
|
||||
);
|
||||
|
||||
$results[$id] = $this->createBuild($project, $id, $branch, $committer, $message, $extra);
|
||||
$status = 'ok';
|
||||
} catch (Exception $ex) {
|
||||
$results[$id] = array('status' => 'failed', 'error' => $ex->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
return array('status' => $status, 'commits' => $results);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by Gitlab Webhooks:
|
||||
*/
|
||||
public function gitlab($projectId)
|
||||
{
|
||||
$project = $this->fetchProject($projectId, 'gitlab');
|
||||
|
||||
$payloadString = file_get_contents("php://input");
|
||||
$payload = json_decode($payloadString, true);
|
||||
|
||||
// build on merge request events
|
||||
if (isset($payload['object_kind']) && $payload['object_kind'] == 'merge_request') {
|
||||
$attributes = $payload['object_attributes'];
|
||||
if ($attributes['state'] == 'opened' || $attributes['state'] == 'reopened') {
|
||||
$branch = $attributes['source_branch'];
|
||||
$commit = $attributes['last_commit'];
|
||||
$committer = $commit['author']['email'];
|
||||
|
||||
return $this->createBuild($project, $commit['id'], $branch, $committer, $commit['message']);
|
||||
}
|
||||
}
|
||||
|
||||
// build on push events
|
||||
if (isset($payload['commits']) && is_array($payload['commits'])) {
|
||||
// If we have a list of commits, then add them all as builds to be tested:
|
||||
|
||||
$results = array();
|
||||
$status = 'failed';
|
||||
foreach ($payload['commits'] as $commit) {
|
||||
try {
|
||||
$branch = str_replace('refs/heads/', '', $payload['ref']);
|
||||
$committer = $commit['author']['email'];
|
||||
$results[$commit['id']] = $this->createBuild(
|
||||
$project,
|
||||
$commit['id'],
|
||||
$branch,
|
||||
$committer,
|
||||
$commit['message']
|
||||
);
|
||||
$status = 'ok';
|
||||
} catch (Exception $ex) {
|
||||
$results[$commit['id']] = array('status' => 'failed', 'error' => $ex->getMessage());
|
||||
}
|
||||
}
|
||||
return array('status' => $status, 'commits' => $results);
|
||||
}
|
||||
|
||||
return array('status' => 'ignored', 'message' => 'Unusable payload.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper for creating a new build.
|
||||
*
|
||||
* @param Project $project
|
||||
* @param string $commitId
|
||||
* @param string $branch
|
||||
* @param string $committer
|
||||
* @param string $commitMessage
|
||||
* @param array $extra
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function createBuild(
|
||||
Project $project,
|
||||
$commitId,
|
||||
$branch,
|
||||
$committer,
|
||||
$commitMessage,
|
||||
array $extra = null
|
||||
) {
|
||||
// Check if a build already exists for this commit ID:
|
||||
$builds = $this->buildStore->getByProjectAndCommit($project->getId(), $commitId);
|
||||
|
||||
if ($builds['count']) {
|
||||
return array(
|
||||
'status' => 'ignored',
|
||||
'message' => sprintf('Duplicate of build #%d', $builds['items'][0]->getId())
|
||||
);
|
||||
}
|
||||
|
||||
// If not, create a new build job for it:
|
||||
$build = $this->buildService->createBuild($project, $commitId, $branch, $committer, $commitMessage, $extra);
|
||||
|
||||
return array('status' => 'ok', 'buildID' => $build->getID());
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch a project and check its type.
|
||||
*
|
||||
* @param int $projectId
|
||||
* @param array|string $expectedType
|
||||
*
|
||||
* @return Project
|
||||
*
|
||||
* @throws Exception If the project does not exist or is not of the expected type.
|
||||
*/
|
||||
protected function fetchProject($projectId, $expectedType)
|
||||
{
|
||||
$project = $this->projectStore->getById($projectId);
|
||||
|
||||
if (empty($projectId)) {
|
||||
throw new Exception('Project does not exist: ' . $projectId);
|
||||
}
|
||||
|
||||
if (is_array($expectedType)
|
||||
? !in_array($project->getType(), $expectedType)
|
||||
: $project->getType() !== $expectedType
|
||||
) {
|
||||
throw new Exception('Wrong project type: ' . $project->getType());
|
||||
}
|
||||
|
||||
return $project;
|
||||
}
|
||||
}
|
||||
68
src/PHPCI/ErrorHandler.php
Normal file
68
src/PHPCI/ErrorHandler.php
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
<?php
|
||||
/**
|
||||
* PHPCI - Continuous Integration for PHP
|
||||
*
|
||||
* @copyright Copyright 2014, Block 8 Limited.
|
||||
* @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md
|
||||
* @link https://www.phptesting.org/
|
||||
*/
|
||||
|
||||
namespace PHPCI;
|
||||
|
||||
/**
|
||||
* Error Handler
|
||||
*
|
||||
* @package PHPCI\Logging
|
||||
*/
|
||||
class ErrorHandler
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $levels = array(
|
||||
E_WARNING => 'Warning',
|
||||
E_NOTICE => 'Notice',
|
||||
E_USER_ERROR => 'User Error',
|
||||
E_USER_WARNING => 'User Warning',
|
||||
E_USER_NOTICE => 'User Notice',
|
||||
E_STRICT => 'Runtime Notice',
|
||||
E_RECOVERABLE_ERROR => 'Catchable Fatal Error',
|
||||
E_DEPRECATED => 'Deprecated',
|
||||
E_USER_DEPRECATED => 'User Deprecated',
|
||||
);
|
||||
|
||||
/**
|
||||
* Registers an instance of the error handler to throw ErrorException.
|
||||
*/
|
||||
public static function register()
|
||||
{
|
||||
$handler = new static();
|
||||
set_error_handler(array($handler, 'handleError'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param integer $level
|
||||
* @param string $message
|
||||
* @param string $file
|
||||
* @param integer $line
|
||||
*
|
||||
* @throws \ErrorException
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function handleError($level, $message, $file, $line)
|
||||
{
|
||||
if (error_reporting() & $level === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
$exceptionLevel = isset($this->levels[$level]) ? $this->levels[$level] : $level;
|
||||
throw new \ErrorException(
|
||||
sprintf('%s: %s in %s line %d', $exceptionLevel, $message, $file, $line),
|
||||
0,
|
||||
$level,
|
||||
$file,
|
||||
$line
|
||||
);
|
||||
}
|
||||
}
|
||||
55
src/PHPCI/Helper/AnsiConverter.php
Normal file
55
src/PHPCI/Helper/AnsiConverter.php
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
/**
|
||||
* PHPCI - Continuous Integration for PHP
|
||||
*
|
||||
* @copyright Copyright 2014, Block 8 Limited.
|
||||
* @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md
|
||||
* @link https://www.phptesting.org/
|
||||
*/
|
||||
|
||||
namespace PHPCI\Helper;
|
||||
|
||||
use SensioLabs\AnsiConverter\AnsiToHtmlConverter;
|
||||
|
||||
/**
|
||||
* Converts ANSI output to HTML.
|
||||
*
|
||||
* @package PHPCI\Helper
|
||||
*/
|
||||
final class AnsiConverter
|
||||
{
|
||||
static private $converter = null;
|
||||
|
||||
/**
|
||||
* Initialize the singleton.
|
||||
*
|
||||
* @return AnsiToHtmlConverter
|
||||
*/
|
||||
private static function getInstance()
|
||||
{
|
||||
if (self::$converter === null) {
|
||||
self::$converter = new AnsiToHtmlConverter(null, false);
|
||||
}
|
||||
|
||||
return self::$converter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a text containing ANSI color sequences into HTML code.
|
||||
*
|
||||
* @param string $text The text to convert
|
||||
*
|
||||
* @return string The HTML code.
|
||||
*/
|
||||
public static function convert($text)
|
||||
{
|
||||
return self::getInstance()->convert($text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Do not instantiate this class.
|
||||
*/
|
||||
private function __construct()
|
||||
{
|
||||
}
|
||||
}
|
||||
227
src/PHPCI/Helper/BaseCommandExecutor.php
Normal file
227
src/PHPCI/Helper/BaseCommandExecutor.php
Normal file
|
|
@ -0,0 +1,227 @@
|
|||
<?php
|
||||
/**
|
||||
* PHPCI - Continuous Integration for PHP
|
||||
*
|
||||
* @copyright Copyright 2014, Block 8 Limited.
|
||||
* @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md
|
||||
* @link https://www.phptesting.org/
|
||||
*/
|
||||
|
||||
namespace PHPCI\Helper;
|
||||
|
||||
use Exception;
|
||||
use PHPCI\Logging\BuildLogger;
|
||||
use Psr\Log\LogLevel;
|
||||
|
||||
/**
|
||||
* Handles running system commands with variables.
|
||||
* @package PHPCI\Helper
|
||||
*/
|
||||
abstract class BaseCommandExecutor implements CommandExecutor
|
||||
{
|
||||
/**
|
||||
* @var BuildLogger
|
||||
*/
|
||||
protected $logger;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $quiet;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected $verbose;
|
||||
|
||||
protected $lastOutput;
|
||||
protected $lastError;
|
||||
|
||||
public $logExecOutput = true;
|
||||
|
||||
/**
|
||||
* The path which findBinary will look in.
|
||||
* @var string
|
||||
*/
|
||||
protected $rootDir;
|
||||
|
||||
/**
|
||||
* Current build path
|
||||
* @var string
|
||||
*/
|
||||
protected $buildPath;
|
||||
|
||||
/**
|
||||
* @param BuildLogger $logger
|
||||
* @param string $rootDir
|
||||
* @param bool $quiet
|
||||
* @param bool $verbose
|
||||
*/
|
||||
public function __construct(BuildLogger $logger, $rootDir, &$quiet = false, &$verbose = false)
|
||||
{
|
||||
$this->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);
|
||||
$this->logger->logDebug($command);
|
||||
|
||||
if ($this->quiet) {
|
||||
$this->logger->log('Executing: ' . $command);
|
||||
}
|
||||
|
||||
$status = 0;
|
||||
$descriptorSpec = array(
|
||||
0 => array("pipe", "r"), // stdin
|
||||
1 => array("pipe", "w"), // stdout
|
||||
2 => array("pipe", "w"), // stderr
|
||||
);
|
||||
|
||||
$pipes = array();
|
||||
$process = proc_open($command, $descriptorSpec, $pipes, $this->buildPath, null);
|
||||
|
||||
if (is_resource($process)) {
|
||||
fclose($pipes[0]);
|
||||
|
||||
$this->lastOutput = stream_get_contents($pipes[1]);
|
||||
$this->lastError = stream_get_contents($pipes[2]);
|
||||
|
||||
fclose($pipes[1]);
|
||||
fclose($pipes[2]);
|
||||
|
||||
$status = proc_close($process);
|
||||
}
|
||||
|
||||
$this->lastOutput = array_filter(explode(PHP_EOL, $this->lastOutput));
|
||||
|
||||
$shouldOutput = ($this->logExecOutput && ($this->verbose || $status != 0));
|
||||
|
||||
if ($shouldOutput && !empty($this->lastOutput)) {
|
||||
$this->logger->log($this->lastOutput);
|
||||
}
|
||||
|
||||
if (!empty($this->lastError)) {
|
||||
$this->logger->log("\033[0;31m" . $this->lastError . "\033[0m", LogLevel::ERROR);
|
||||
}
|
||||
|
||||
$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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the stderr output from the last command run.
|
||||
*/
|
||||
public function getLastError()
|
||||
{
|
||||
return $this->lastError;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a binary required by a plugin.
|
||||
* @param string $binary
|
||||
* @param bool $quiet
|
||||
* @return null|string
|
||||
*/
|
||||
public function findBinary($binary, $quiet = false)
|
||||
{
|
||||
$composerBin = $this->getComposerBinDir(realpath($this->buildPath));
|
||||
|
||||
if (is_string($binary)) {
|
||||
$binary = array($binary);
|
||||
}
|
||||
|
||||
foreach ($binary as $bin) {
|
||||
$this->logger->log(Lang::get('looking_for_binary', $bin), LogLevel::DEBUG);
|
||||
|
||||
if (is_dir($composerBin) && is_file($composerBin.'/'.$bin)) {
|
||||
$this->logger->log(Lang::get('found_in_path', $composerBin, $bin), LogLevel::DEBUG);
|
||||
return $composerBin . '/' . $bin;
|
||||
}
|
||||
|
||||
if (is_file($this->rootDir . $bin)) {
|
||||
$this->logger->log(Lang::get('found_in_path', 'root', $bin), LogLevel::DEBUG);
|
||||
return $this->rootDir . $bin;
|
||||
}
|
||||
|
||||
if (is_file($this->rootDir . 'vendor/bin/' . $bin)) {
|
||||
$this->logger->log(Lang::get('found_in_path', 'vendor/bin', $bin), LogLevel::DEBUG);
|
||||
return $this->rootDir . 'vendor/bin/' . $bin;
|
||||
}
|
||||
|
||||
$findCmdResult = $this->findGlobalBinary($bin);
|
||||
if (is_file($findCmdResult)) {
|
||||
$this->logger->log(Lang::get('found_in_path', '', $bin), LogLevel::DEBUG);
|
||||
return $findCmdResult;
|
||||
}
|
||||
}
|
||||
|
||||
if ($quiet) {
|
||||
return;
|
||||
}
|
||||
throw new Exception(Lang::get('could_not_find', implode('/', $binary)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a binary which is installed globally on the system
|
||||
* @param string $binary
|
||||
* @return null|string
|
||||
*/
|
||||
abstract protected function findGlobalBinary($binary);
|
||||
|
||||
/**
|
||||
* Try to load the composer.json file in the building project
|
||||
* If the bin-dir is configured, return the full path to it
|
||||
* @param string $path Current build path
|
||||
* @return string|null
|
||||
*/
|
||||
public function getComposerBinDir($path)
|
||||
{
|
||||
if (is_dir($path)) {
|
||||
$composer = $path.'/composer.json';
|
||||
if (is_file($composer)) {
|
||||
$json = json_decode(file_get_contents($composer));
|
||||
|
||||
if (isset($json->config->{"bin-dir"})) {
|
||||
return $path.'/'.$json->config->{"bin-dir"};
|
||||
} elseif (is_dir($path . '/vendor/bin')) {
|
||||
return $path . '/vendor/bin';
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the buildPath property.
|
||||
* @param string $path
|
||||
*/
|
||||
public function setBuildPath($path)
|
||||
{
|
||||
$this->buildPath = $path;
|
||||
}
|
||||
}
|
||||
29
src/PHPCI/Helper/Build.php
Normal file
29
src/PHPCI/Helper/Build.php
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
/**
|
||||
* PHPCI - Continuous Integration for PHP
|
||||
*
|
||||
* @copyright Copyright 2014, Block 8 Limited.
|
||||
* @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md
|
||||
* @link https://www.phptesting.org/
|
||||
*/
|
||||
|
||||
namespace PHPCI\Helper;
|
||||
|
||||
/**
|
||||
* User Helper - Provides access to logged in user information in views.
|
||||
* @author Dan Cryer <dan@block8.co.uk>
|
||||
* @package PHPCI
|
||||
* @subpackage Web
|
||||
*/
|
||||
class Build
|
||||
{
|
||||
/**
|
||||
* Returns a more human-friendly version of a plugin name.
|
||||
* @param $name
|
||||
* @return mixed
|
||||
*/
|
||||
public function formatPluginName($name)
|
||||
{
|
||||
return str_replace('Php', 'PHP', ucwords(str_replace('_', ' ', $name)));
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue