diff options
Diffstat (limited to 'app/Core')
-rw-r--r-- | app/Core/Base.php | 2 | ||||
-rw-r--r-- | app/Core/ObjectStorage/FileStorage.php | 150 | ||||
-rw-r--r-- | app/Core/ObjectStorage/ObjectStorageException.php | 9 | ||||
-rw-r--r-- | app/Core/ObjectStorage/ObjectStorageInterface.php | 68 | ||||
-rw-r--r-- | app/Core/PluginBase.php | 47 | ||||
-rw-r--r-- | app/Core/PluginLoader.php | 137 | ||||
-rw-r--r-- | app/Core/Router.php | 46 | ||||
-rw-r--r-- | app/Core/Template.php | 52 | ||||
-rw-r--r-- | app/Core/Tool.php | 101 | ||||
-rw-r--r-- | app/Core/Translator.php | 23 |
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(); + } } |