summaryrefslogtreecommitdiff
path: root/app/Core
diff options
context:
space:
mode:
Diffstat (limited to 'app/Core')
-rw-r--r--app/Core/Base.php2
-rw-r--r--app/Core/ObjectStorage/FileStorage.php150
-rw-r--r--app/Core/ObjectStorage/ObjectStorageException.php9
-rw-r--r--app/Core/ObjectStorage/ObjectStorageInterface.php68
-rw-r--r--app/Core/PluginBase.php47
-rw-r--r--app/Core/PluginLoader.php137
-rw-r--r--app/Core/Router.php46
-rw-r--r--app/Core/Template.php52
-rw-r--r--app/Core/Tool.php101
-rw-r--r--app/Core/Translator.php23
10 files changed, 576 insertions, 59 deletions
diff --git a/app/Core/Base.php b/app/Core/Base.php
index 3db0cf74..5ed8f40a 100644
--- a/app/Core/Base.php
+++ b/app/Core/Base.php
@@ -35,7 +35,6 @@ use Pimple\Container;
* @property \Model\Action $action
* @property \Model\Authentication $authentication
* @property \Model\Board $board
- * @property \Model\Budget $budget
* @property \Model\Category $category
* @property \Model\Color $color
* @property \Model\Comment $comment
@@ -43,7 +42,6 @@ use Pimple\Container;
* @property \Model\Currency $currency
* @property \Model\DateParser $dateParser
* @property \Model\File $file
- * @property \Model\HourlyRate $hourlyRate
* @property \Model\LastLogin $lastLogin
* @property \Model\Link $link
* @property \Model\Notification $notification
diff --git a/app/Core/ObjectStorage/FileStorage.php b/app/Core/ObjectStorage/FileStorage.php
new file mode 100644
index 00000000..66c62334
--- /dev/null
+++ b/app/Core/ObjectStorage/FileStorage.php
@@ -0,0 +1,150 @@
+<?php
+
+namespace Core\ObjectStorage;
+
+/**
+ * Local File Storage
+ *
+ * @package ObjectStorage
+ * @author Frederic Guillot
+ */
+class FileStorage implements ObjectStorageInterface
+{
+ /**
+ * Base path
+ *
+ * @access private
+ * @var string
+ */
+ private $path = '';
+
+ /**
+ * Constructor
+ *
+ * @access public
+ * @param string $path
+ */
+ public function __construct($path)
+ {
+ $this->path = $path;
+ }
+
+ /**
+ * Fetch object contents
+ *
+ * @access public
+ * @param string $key
+ * @return string
+ */
+ public function get($key)
+ {
+ $filename = $this->path.DIRECTORY_SEPARATOR.$key;
+
+ if (! file_exists($filename)) {
+ throw new ObjectStorageException('File not found: '.$filename);
+ }
+
+ return file_get_contents($filename);
+ }
+
+ /**
+ * Save object
+ *
+ * @access public
+ * @param string $key
+ * @param string $blob
+ * @return string
+ */
+ public function put($key, &$blob)
+ {
+ $this->createFolder($key);
+
+ if (file_put_contents($this->path.DIRECTORY_SEPARATOR.$key, $blob) === false) {
+ throw new ObjectStorageException('Unable to write the file: '.$this->path.DIRECTORY_SEPARATOR.$key);
+ }
+ }
+
+ /**
+ * Output directly object content
+ *
+ * @access public
+ * @param string $key
+ */
+ public function passthru($key)
+ {
+ $filename = $this->path.DIRECTORY_SEPARATOR.$key;
+
+ if (! file_exists($filename)) {
+ throw new ObjectStorageException('File not found: '.$filename);
+ }
+
+ return readfile($filename);
+ }
+
+ /**
+ * Move local file to object storage
+ *
+ * @access public
+ * @param string $filename
+ * @param string $key
+ * @return boolean
+ */
+ public function moveFile($src_filename, $key)
+ {
+ $this->createFolder($key);
+ $dst_filename = $this->path.DIRECTORY_SEPARATOR.$key;
+
+ if (! rename($src_filename, $dst_filename)) {
+ throw new ObjectStorageException('Unable to move the file: '.$src_filename.' to '.$dst_filename);
+ }
+
+ return true;
+ }
+
+ /**
+ * Move uploaded file to object storage
+ *
+ * @access public
+ * @param string $filename
+ * @param string $key
+ * @return boolean
+ */
+ public function moveUploadedFile($filename, $key)
+ {
+ $this->createFolder($key);
+ return move_uploaded_file($filename, $this->path.DIRECTORY_SEPARATOR.$key);
+ }
+
+ /**
+ * Remove object
+ *
+ * @access public
+ * @param string $key
+ * @return boolean
+ */
+ public function remove($key)
+ {
+ $filename = $this->path.DIRECTORY_SEPARATOR.$key;
+
+ if (file_exists($filename)) {
+ return unlink($filename);
+ }
+
+ return false;
+ }
+
+ /**
+ * Create object folder
+ *
+ * @access private
+ * @param string $key
+ */
+ private function createFolder($key)
+ {
+ $folder = $this->path.DIRECTORY_SEPARATOR.dirname($key);
+
+ if (! is_dir($folder) && ! mkdir($folder, 0755, true)) {
+ throw new ObjectStorageException('Unable to create folder: '.$folder);
+ }
+ }
+}
diff --git a/app/Core/ObjectStorage/ObjectStorageException.php b/app/Core/ObjectStorage/ObjectStorageException.php
new file mode 100644
index 00000000..e89aeb25
--- /dev/null
+++ b/app/Core/ObjectStorage/ObjectStorageException.php
@@ -0,0 +1,9 @@
+<?php
+
+namespace Core\ObjectStorage;
+
+use Exception;
+
+class ObjectStorageException extends Exception
+{
+}
diff --git a/app/Core/ObjectStorage/ObjectStorageInterface.php b/app/Core/ObjectStorage/ObjectStorageInterface.php
new file mode 100644
index 00000000..5440cf2b
--- /dev/null
+++ b/app/Core/ObjectStorage/ObjectStorageInterface.php
@@ -0,0 +1,68 @@
+<?php
+
+namespace Core\ObjectStorage;
+
+/**
+ * Object Storage Interface
+ *
+ * @package ObjectStorage
+ * @author Frederic Guillot
+ */
+interface ObjectStorageInterface
+{
+ /**
+ * Fetch object contents
+ *
+ * @access public
+ * @param string $key
+ * @return string
+ */
+ public function get($key);
+
+ /**
+ * Save object
+ *
+ * @access public
+ * @param string $key
+ * @param string $blob
+ * @return string
+ */
+ public function put($key, &$blob);
+
+ /**
+ * Output directly object content
+ *
+ * @access public
+ * @param string $key
+ */
+ public function passthru($key);
+
+ /**
+ * Move local file to object storage
+ *
+ * @access public
+ * @param string $filename
+ * @param string $key
+ * @return boolean
+ */
+ public function moveFile($filename, $key);
+
+ /**
+ * Move uploaded file to object storage
+ *
+ * @access public
+ * @param string $filename
+ * @param string $key
+ * @return boolean
+ */
+ public function moveUploadedFile($filename, $key);
+
+ /**
+ * Remove object
+ *
+ * @access public
+ * @param string $key
+ * @return boolean
+ */
+ public function remove($key);
+}
diff --git a/app/Core/PluginBase.php b/app/Core/PluginBase.php
new file mode 100644
index 00000000..457afa03
--- /dev/null
+++ b/app/Core/PluginBase.php
@@ -0,0 +1,47 @@
+<?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();
+ }
+
+ /**
+ * Listen on internal events
+ *
+ * @access public
+ * @param string $event
+ * @param callable $callback
+ */
+ public function on($event, $callback)
+ {
+ $container = $this->container;
+
+ $this->container['dispatcher']->addListener($event, function() use ($container, $callback) {
+ call_user_func($callback, $container);
+ });
+ }
+}
diff --git a/app/Core/PluginLoader.php b/app/Core/PluginLoader.php
new file mode 100644
index 00000000..c7c254f7
--- /dev/null
+++ b/app/Core/PluginLoader.php
@@ -0,0 +1,137 @@
+<?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';
+
+ /**
+ * Scan plugin folder and load plugins
+ *
+ * @access public
+ */
+ public function scan()
+ {
+ if (file_exists(__DIR__.'/../../plugins')) {
+ $dir = new DirectoryIterator(__DIR__.'/../../plugins');
+
+ 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..887c8fb3 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,102 @@ 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);
+ };
+ }
+ }
+ }
+
+ /**
+ * Generate a jpeg thumbnail from an image
+ *
+ * @static
+ * @access public
+ * @param string $src_file Source file image
+ * @param string $dst_file Destination file image
+ * @param integer $resize_width Desired image width
+ * @param integer $resize_height Desired image height
+ */
+ public static function generateThumbnail($src_file, $dst_file, $resize_width = 250, $resize_height = 100)
+ {
+ $metadata = getimagesize($src_file);
+ $src_width = $metadata[0];
+ $src_height = $metadata[1];
+ $dst_y = 0;
+ $dst_x = 0;
+
+ if (empty($metadata['mime'])) {
+ return;
+ }
+
+ if ($resize_width == 0 && $resize_height == 0) {
+ $resize_width = 100;
+ $resize_height = 100;
+ }
+
+ if ($resize_width > 0 && $resize_height == 0) {
+ $dst_width = $resize_width;
+ $dst_height = floor($src_height * ($resize_width / $src_width));
+ $dst_image = imagecreatetruecolor($dst_width, $dst_height);
+ }
+ elseif ($resize_width == 0 && $resize_height > 0) {
+ $dst_width = floor($src_width * ($resize_height / $src_height));
+ $dst_height = $resize_height;
+ $dst_image = imagecreatetruecolor($dst_width, $dst_height);
+ }
+ else {
+
+ $src_ratio = $src_width / $src_height;
+ $resize_ratio = $resize_width / $resize_height;
+
+ if ($src_ratio <= $resize_ratio) {
+ $dst_width = $resize_width;
+ $dst_height = floor($src_height * ($resize_width / $src_width));
+
+ $dst_y = ($dst_height - $resize_height) / 2 * (-1);
+ }
+ else {
+ $dst_width = floor($src_width * ($resize_height / $src_height));
+ $dst_height = $resize_height;
+
+ $dst_x = ($dst_width - $resize_width) / 2 * (-1);
+ }
+
+ $dst_image = imagecreatetruecolor($resize_width, $resize_height);
+ }
+
+ switch ($metadata['mime']) {
+ case 'image/jpeg':
+ case 'image/jpg':
+ $src_image = imagecreatefromjpeg($src_file);
+ break;
+ case 'image/png':
+ $src_image = imagecreatefrompng($src_file);
+ break;
+ case 'image/gif':
+ $src_image = imagecreatefromgif($src_file);
+ break;
+ default:
+ return;
+ }
+
+ imagecopyresampled($dst_image, $src_image, $dst_x, $dst_y, 0, 0, $dst_width, $dst_height, $src_width, $src_height);
+ imagejpeg($dst_image, $dst_file);
+ imagedestroy($dst_image);
+ }
}
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();
+ }
}