First draft for plugins system
This commit is contained in:
31
app/Core/PluginBase.php
Normal file
31
app/Core/PluginBase.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
namespace Core;
|
||||
|
||||
/**
|
||||
* Plugin Base class
|
||||
*
|
||||
* @package core
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
abstract class PluginBase extends Base
|
||||
{
|
||||
/**
|
||||
* Method called for each request
|
||||
*
|
||||
* @abstract
|
||||
* @access public
|
||||
*/
|
||||
abstract public function initialize();
|
||||
|
||||
/**
|
||||
* Returns all classes that needs to be stored in the DI container
|
||||
*
|
||||
* @access public
|
||||
* @return array
|
||||
*/
|
||||
public function getClasses()
|
||||
{
|
||||
return array();
|
||||
}
|
||||
}
|
||||
144
app/Core/PluginLoader.php
Normal file
144
app/Core/PluginLoader.php
Normal file
@@ -0,0 +1,144 @@
|
||||
<?php
|
||||
|
||||
namespace Core;
|
||||
|
||||
use DirectoryIterator;
|
||||
use PDOException;
|
||||
|
||||
/**
|
||||
* Plugin Loader
|
||||
*
|
||||
* @package core
|
||||
* @author Frederic Guillot
|
||||
*/
|
||||
class PluginLoader extends Base
|
||||
{
|
||||
/**
|
||||
* Schema version table for plugins
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const TABLE_SCHEMA = 'plugin_schema_versions';
|
||||
|
||||
/**
|
||||
* Plugin folder
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const PATH = __DIR__.'/../../plugins';
|
||||
|
||||
/**
|
||||
* Scan plugin folder and load plugins
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function scan()
|
||||
{
|
||||
if (file_exists(self::PATH)) {
|
||||
$dir = new DirectoryIterator(self::PATH);
|
||||
|
||||
foreach ($dir as $fileinfo) {
|
||||
if (! $fileinfo->isDot() && $fileinfo->isDir()) {
|
||||
$plugin = $fileinfo->getFilename();
|
||||
$this->loadSchema($plugin);
|
||||
$this->load($plugin);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load plugin
|
||||
*
|
||||
* @access public
|
||||
*/
|
||||
public function load($plugin)
|
||||
{
|
||||
$class = '\Plugin\\'.$plugin.'\\Plugin';
|
||||
$instance = new $class($this->container);
|
||||
|
||||
Tool::buildDic($this->container, $instance->getClasses());
|
||||
|
||||
$instance->initialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load plugin schema
|
||||
*
|
||||
* @access public
|
||||
* @param string $plugin
|
||||
*/
|
||||
public function loadSchema($plugin)
|
||||
{
|
||||
$filename = __DIR__.'/../../plugins/'.$plugin.'/Schema/'.ucfirst(DB_DRIVER).'.php';
|
||||
|
||||
if (file_exists($filename)) {
|
||||
require($filename);
|
||||
$this->migrateSchema($plugin);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute plugin schema migrations
|
||||
*
|
||||
* @access public
|
||||
* @param string $plugin
|
||||
*/
|
||||
public function migrateSchema($plugin)
|
||||
{
|
||||
$last_version = constant('\Plugin\\'.$plugin.'\Schema\VERSION');
|
||||
$current_version = $this->getSchemaVersion($plugin);
|
||||
|
||||
try {
|
||||
|
||||
$this->db->startTransaction();
|
||||
$this->db->getDriver()->disableForeignKeys();
|
||||
|
||||
for ($i = $current_version + 1; $i <= $last_version; $i++) {
|
||||
$function_name = '\Plugin\\'.$plugin.'\Schema\version_'.$i;
|
||||
|
||||
if (function_exists($function_name)) {
|
||||
call_user_func($function_name, $this->db->getConnection());
|
||||
}
|
||||
}
|
||||
|
||||
$this->db->getDriver()->enableForeignKeys();
|
||||
$this->db->closeTransaction();
|
||||
$this->setSchemaVersion($plugin, $i - 1);
|
||||
}
|
||||
catch (PDOException $e) {
|
||||
$this->db->cancelTransaction();
|
||||
$this->db->getDriver()->enableForeignKeys();
|
||||
die('Unable to migrate schema for the plugin: '.$plugin.' => '.$e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current plugin schema version
|
||||
*
|
||||
* @access public
|
||||
* @param string $plugin
|
||||
* @return integer
|
||||
*/
|
||||
public function getSchemaVersion($plugin)
|
||||
{
|
||||
return (int) $this->db->table(self::TABLE_SCHEMA)->eq('plugin', strtolower($plugin))->findOneColumn('version');
|
||||
}
|
||||
|
||||
/**
|
||||
* Save last plugin schema version
|
||||
*
|
||||
* @access public
|
||||
* @param string $plugin
|
||||
* @param integer $version
|
||||
* @return boolean
|
||||
*/
|
||||
public function setSchemaVersion($plugin, $version)
|
||||
{
|
||||
$dictionary = array(
|
||||
strtolower($plugin) => $version
|
||||
);
|
||||
|
||||
return $this->db->getDriver()->upsert(self::TABLE_SCHEMA, 'plugin', 'version', $dictionary);
|
||||
}
|
||||
}
|
||||
@@ -213,49 +213,17 @@ class Router extends Base
|
||||
if (! empty($_GET['controller']) && ! empty($_GET['action'])) {
|
||||
$controller = $this->sanitize($_GET['controller'], 'app');
|
||||
$action = $this->sanitize($_GET['action'], 'index');
|
||||
$plugin = ! empty($_GET['plugin']) ? $this->sanitize($_GET['plugin'], '') : '';
|
||||
}
|
||||
else {
|
||||
list($controller, $action) = $this->findRoute($this->getPath($uri, $query_string));
|
||||
list($controller, $action) = $this->findRoute($this->getPath($uri, $query_string)); // TODO: add plugin for routes
|
||||
$plugin = '';
|
||||
}
|
||||
|
||||
return $this->load(
|
||||
__DIR__.'/../Controller/'.ucfirst($controller).'.php',
|
||||
$controller,
|
||||
'\Controller\\'.ucfirst($controller),
|
||||
$action
|
||||
);
|
||||
}
|
||||
$class = empty($plugin) ? '\Controller\\'.ucfirst($controller) : '\Plugin\\'.ucfirst($plugin).'\Controller\\'.ucfirst($controller);
|
||||
|
||||
/**
|
||||
* Load a controller and execute the action
|
||||
*
|
||||
* @access private
|
||||
* @param string $filename
|
||||
* @param string $controller
|
||||
* @param string $class
|
||||
* @param string $method
|
||||
* @return bool
|
||||
*/
|
||||
private function load($filename, $controller, $class, $method)
|
||||
{
|
||||
if (file_exists($filename)) {
|
||||
|
||||
require $filename;
|
||||
|
||||
if (! method_exists($class, $method)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->action = $method;
|
||||
$this->controller = $controller;
|
||||
|
||||
$instance = new $class($this->container);
|
||||
$instance->beforeAction($controller, $method);
|
||||
$instance->$method();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
$instance = new $class($this->container);
|
||||
$instance->beforeAction($controller, $action);
|
||||
$instance->$action();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,11 +13,12 @@ use LogicException;
|
||||
class Template extends Helper
|
||||
{
|
||||
/**
|
||||
* Template path
|
||||
* List of template overrides
|
||||
*
|
||||
* @var string
|
||||
* @access private
|
||||
* @var array
|
||||
*/
|
||||
const PATH = 'app/Template/';
|
||||
private $overrides = array();
|
||||
|
||||
/**
|
||||
* Render a template
|
||||
@@ -33,16 +34,10 @@ class Template extends Helper
|
||||
*/
|
||||
public function render($__template_name, array $__template_args = array())
|
||||
{
|
||||
$__template_file = self::PATH.$__template_name.'.php';
|
||||
|
||||
if (! file_exists($__template_file)) {
|
||||
throw new LogicException('Unable to load the template: "'.$__template_name.'"');
|
||||
}
|
||||
|
||||
extract($__template_args);
|
||||
|
||||
ob_start();
|
||||
include $__template_file;
|
||||
include $this->getTemplateFile($__template_name);
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
@@ -62,4 +57,41 @@ class Template extends Helper
|
||||
$template_args + array('content_for_layout' => $this->render($template_name, $template_args))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Define a new template override
|
||||
*
|
||||
* @access public
|
||||
* @param string $original_template
|
||||
* @param string $new_template
|
||||
*/
|
||||
public function setTemplateOverride($original_template, $new_template)
|
||||
{
|
||||
$this->overrides[$original_template] = $new_template;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find template filename
|
||||
*
|
||||
* Core template name: 'task/show'
|
||||
* Plugin template name: 'myplugin:task/show'
|
||||
*
|
||||
* @access public
|
||||
* @param string $template_name
|
||||
* @return string
|
||||
*/
|
||||
public function getTemplateFile($template_name)
|
||||
{
|
||||
$template_name = isset($this->overrides[$template_name]) ? $this->overrides[$template_name] : $template_name;
|
||||
|
||||
if (strpos($template_name, ':') !== false) {
|
||||
list($plugin, $template) = explode(':', $template_name);
|
||||
$path = __DIR__.'/../../plugins/'.ucfirst($plugin).'/Template/'.$template.'.php';
|
||||
}
|
||||
else {
|
||||
$path = __DIR__.'/../Template/'.$template_name.'.php';
|
||||
}
|
||||
|
||||
return $path;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
namespace Core;
|
||||
|
||||
use Pimple\Container;
|
||||
|
||||
/**
|
||||
* Tool class
|
||||
*
|
||||
@@ -23,7 +25,6 @@ class Tool
|
||||
$fp = fopen($filename, 'w');
|
||||
|
||||
if (is_resource($fp)) {
|
||||
|
||||
foreach ($rows as $fields) {
|
||||
fputcsv($fp, $fields);
|
||||
}
|
||||
@@ -51,4 +52,24 @@ class Tool
|
||||
|
||||
return $identifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build dependency injection container from an array
|
||||
*
|
||||
* @static
|
||||
* @access public
|
||||
* @param Container $container
|
||||
* @param array $namespaces
|
||||
*/
|
||||
public static function buildDIC(Container $container, array $namespaces)
|
||||
{
|
||||
foreach ($namespaces as $namespace => $classes) {
|
||||
foreach ($classes as $name) {
|
||||
$class = '\\'.$namespace.'\\'.$name;
|
||||
$container[lcfirst($name)] = function ($c) use ($class) {
|
||||
return new $class($c);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ class Translator
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const PATH = 'app/Locale/';
|
||||
const PATH = 'app/Locale';
|
||||
|
||||
/**
|
||||
* Locale
|
||||
@@ -196,18 +196,27 @@ class Translator
|
||||
* @static
|
||||
* @access public
|
||||
* @param string $language Locale code: fr_FR
|
||||
* @param string $path Locale folder
|
||||
*/
|
||||
public static function load($language)
|
||||
public static function load($language, $path = self::PATH)
|
||||
{
|
||||
setlocale(LC_TIME, $language.'.UTF-8', $language);
|
||||
|
||||
$filename = self::PATH.$language.DIRECTORY_SEPARATOR.'translations.php';
|
||||
$filename = $path.DIRECTORY_SEPARATOR.$language.DIRECTORY_SEPARATOR.'translations.php';
|
||||
|
||||
if (file_exists($filename)) {
|
||||
self::$locales = require $filename;
|
||||
}
|
||||
else {
|
||||
self::$locales = array();
|
||||
self::$locales = array_merge(self::$locales, require($filename));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear locales stored in memory
|
||||
*
|
||||
* @static
|
||||
* @access public
|
||||
*/
|
||||
public static function unload()
|
||||
{
|
||||
self::$locales = array();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user