summaryrefslogtreecommitdiff
path: root/app/Core
diff options
context:
space:
mode:
authorFrederic Guillot <fred@kanboard.net>2015-09-13 14:07:56 -0400
committerFrederic Guillot <fred@kanboard.net>2015-09-13 14:07:56 -0400
commita6a00a00400f164c4b18094999a5ed72366dd519 (patch)
tree7d2bfc3fe4a36649f9092463228f4553979aef94 /app/Core
parentc405f99fc8b7420b3e69c633b3259756a1ceb2f2 (diff)
First draft for plugins system
Diffstat (limited to 'app/Core')
-rw-r--r--app/Core/PluginBase.php31
-rw-r--r--app/Core/PluginLoader.php144
-rw-r--r--app/Core/Router.php46
-rw-r--r--app/Core/Template.php52
-rw-r--r--app/Core/Tool.php23
-rw-r--r--app/Core/Translator.php23
6 files changed, 262 insertions, 57 deletions
diff --git a/app/Core/PluginBase.php b/app/Core/PluginBase.php
new file mode 100644
index 00000000..9c3d6e32
--- /dev/null
+++ b/app/Core/PluginBase.php
@@ -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();
+ }
+}
diff --git a/app/Core/PluginLoader.php b/app/Core/PluginLoader.php
new file mode 100644
index 00000000..6030ded4
--- /dev/null
+++ b/app/Core/PluginLoader.php
@@ -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);
+ }
+}
diff --git a/app/Core/Router.php b/app/Core/Router.php
index 6e7576d6..36bbfd55 100644
--- a/app/Core/Router.php
+++ b/app/Core/Router.php
@@ -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
- );
- }
-
- /**
- * 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;
- }
+ $class = empty($plugin) ? '\Controller\\'.ucfirst($controller) : '\Plugin\\'.ucfirst($plugin).'\Controller\\'.ucfirst($controller);
- return false;
+ $instance = new $class($this->container);
+ $instance->beforeAction($controller, $action);
+ $instance->$action();
}
}
diff --git a/app/Core/Template.php b/app/Core/Template.php
index ba869ee6..b75f7da1 100644
--- a/app/Core/Template.php
+++ b/app/Core/Template.php
@@ -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;
+ }
}
diff --git a/app/Core/Tool.php b/app/Core/Tool.php
index 84e42ba8..7939a80e 100644
--- a/app/Core/Tool.php
+++ b/app/Core/Tool.php
@@ -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);
+ };
+ }
+ }
+ }
}
diff --git a/app/Core/Translator.php b/app/Core/Translator.php
index e3d19692..e9aa1f3f 100644
--- a/app/Core/Translator.php
+++ b/app/Core/Translator.php
@@ -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();
+ }
}