From 86f69ca396572ca4d7668a33e84cb4f3b523fc4e Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Thu, 4 Dec 2014 19:33:29 +0100 Subject: First draft for the new extension feature - Only system extensions can be loaded for the moment by adding them in the config.php file. - Remove previous system (it will be added properly in the new system in the next step). --- lib/Minz/ExtensionManager.php | 150 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 lib/Minz/ExtensionManager.php (limited to 'lib/Minz/ExtensionManager.php') diff --git a/lib/Minz/ExtensionManager.php b/lib/Minz/ExtensionManager.php new file mode 100644 index 000000000..ae648fe0d --- /dev/null +++ b/lib/Minz/ExtensionManager.php @@ -0,0 +1,150 @@ +Extension where + * must match with the entry point in metadata.json. This class must + * inherit from Minz_Extension class. + */ + public static function init() { + $list_potential_extensions = array_values(array_diff( + scandir(EXTENSIONS_PATH), + array('..', '.') + )); + + self::$ext_auto_enabled = Minz_Configuration::extensionsEnabled(); + + foreach ($list_potential_extensions as $ext_dir) { + $ext_pathname = EXTENSIONS_PATH . '/' . $ext_dir; + $metadata_filename = $ext_pathname . '/' . self::$ext_metaname; + + // Try to load metadata file. + if (!file_exists($metadata_filename)) { + // No metadata file? Invalid! + continue; + } + $meta_raw_content = file_get_contents($metadata_filename); + $meta_json = json_decode($meta_raw_content, true); + if (!$meta_json || !self::is_valid_metadata($meta_json)) { + // metadata.json is not a json file? Invalid! + // or metadata.json is invalid (no required information), invalid! + Minz_Log::warning('`' . $metadata_filename . '` is not a valid metadata file'); + continue; + } + + $meta_json['path'] = $ext_pathname; + + // Try to load extension itself + $extension = self::load($meta_json); + if (!is_null($extension)) { + self::register($extension); + } + } + } + + /** + * Indicates if the given parameter is a valid metadata array. + * + * Required fields are: + * - `name`: the name of the extension + * - `entry_point`: a class name to load the extension source code + * If the extension class name is `TestExtension`, entry point will be `Test`. + * `entry_point` must be composed of alphanumeric characters. + * + * @param $meta is an array of values. + * @return true if the array is valid, false else. + */ + public static function is_valid_metadata($meta) { + return !(empty($meta['name']) || + empty($meta['entrypoint']) || + !ctype_alnum($meta['entrypoint'])); + } + + /** + * Load the extension source code based on info metadata. + * + * @param $info an array containing information about extension. + * @return an extension inheriting from Minz_Extension. + */ + public static function load($info) { + $entry_point_filename = $info['path'] . '/' . self::$ext_entry_point; + $ext_class_name = $info['entrypoint'] . 'Extension'; + + include($entry_point_filename); + + // Test if the given extension class exists. + if (!class_exists($ext_class_name)) { + Minz_Log::warning('`' . $ext_class_name . + '` cannot be found in `' . $entry_point_filename . '`'); + return null; + } + + // Try to load the class. + $extension = null; + try { + $extension = new $ext_class_name($info); + } catch (Minz_ExtensionException $e) { + // We cannot load the extension? Invalid! + Minz_Log::warning('In `' . $metadata_filename . '`: ' . $e->getMessage()); + return null; + } + + // Test if class is correct. + if (!($extension instanceof Minz_Extension)) { + Minz_Log::warning('`' . $ext_class_name . + '` is not an instance of `Minz_Extension`'); + return null; + } + + return $extension; + } + + /** + * Add the extension to the list of the known extensions ($ext_list). + * + * If the extension is present in $ext_auto_enabled and if its type is "system", + * it will be enabled in the same time. + * + * @param $ext a valid extension. + */ + public static function register($ext) { + $name = $ext->getName(); + self::$ext_list[$name] = $ext; + + if ($ext->getType() === 'system' && + in_array($name, self::$ext_auto_enabled)) { + self::enable($ext->getName()); + } + } + + /** + * Enable an extension so it will be called when necessary. + * + * The extension init() method will be called. + * + * @param $ext_name is the name of a valid extension present in $ext_list. + */ + public static function enable($ext_name) { + if (isset(self::$ext_list[$ext_name])) { + $ext = self::$ext_list[$ext_name]; + self::$ext_list_enabled[$ext_name] = $ext; + $ext->init(); + } + } +} -- cgit v1.2.3 From 1086ba4a2bbe43a0101105624f831516b59ba9e9 Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Thu, 4 Dec 2014 19:47:43 +0100 Subject: Enable extensions for users --- app/FreshRSS.php | 6 +++++- app/Models/Configuration.php | 8 ++++++++ lib/Minz/ExtensionManager.php | 11 +++++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) (limited to 'lib/Minz/ExtensionManager.php') diff --git a/app/FreshRSS.php b/app/FreshRSS.php index 2db811a71..166ee1709 100644 --- a/app/FreshRSS.php +++ b/app/FreshRSS.php @@ -6,7 +6,7 @@ class FreshRSS extends Minz_FrontController { Minz_Session::init('FreshRSS'); } - // Load list of extensions and initialize the "system" ones. + // Load list of extensions and enable the "system" ones. Minz_ExtensionManager::init(); // Need to be called just after session init because it initializes @@ -29,6 +29,10 @@ class FreshRSS extends Minz_FrontController { // Load context and configuration. FreshRSS_Context::init(); + // Enable extensions for the current user. + $ext_list = FreshRSS_Context::$conf->extensions_enabled; + Minz_ExtensionManager::enable_by_list($ext_list); + // Init i18n. Minz_Session::_param('language', FreshRSS_Context::$conf->language); Minz_Translate::init(); diff --git a/app/Models/Configuration.php b/app/Models/Configuration.php index 8668470b0..13ce43990 100644 --- a/app/Models/Configuration.php +++ b/app/Models/Configuration.php @@ -64,6 +64,7 @@ class FreshRSS_Configuration { 'sharing' => array(), 'queries' => array(), 'html5_notif_timeout' => 0, + 'extensions_enabled' => array(), ); private $available_languages = array( @@ -342,4 +343,11 @@ class FreshRSS_Configuration { public function _bottomline_link($value) { $this->data['bottomline_link'] = ((bool)$value) && $value !== 'no'; } + + public function _extensions_enabled($value) { + if (!is_array($value)) { + $value = array($value); + } + $this->data['extensions_enabled'] = $value; + } } diff --git a/lib/Minz/ExtensionManager.php b/lib/Minz/ExtensionManager.php index ae648fe0d..789557b9e 100644 --- a/lib/Minz/ExtensionManager.php +++ b/lib/Minz/ExtensionManager.php @@ -147,4 +147,15 @@ class Minz_ExtensionManager { $ext->init(); } } + + /** + * Enable a list of extensions. + * + * @param $ext_list the names of extensions we want to load. + */ + public static function enable_by_list($ext_list) { + foreach ($ext_list as $ext_name) { + self::enable($ext_name); + } + } } -- cgit v1.2.3 From 9fc60317eecba785b66011f04b9a5150296f2df6 Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Fri, 5 Dec 2014 14:17:02 +0100 Subject: First draft for listing and manipulate extensions See https://github.com/FreshRSS/FreshRSS/issues/252 --- app/Controllers/extensionController.php | 43 +++++++++++++++++++++++++++++++++ app/layout/aside_configure.phtml | 4 +++ app/layout/header.phtml | 1 + app/views/extension/index.phtml | 35 +++++++++++++++++++++++++++ lib/Minz/Extension.php | 20 +++++++++++++++ lib/Minz/ExtensionManager.php | 17 +++++++++++++ 6 files changed, 120 insertions(+) create mode 100644 app/Controllers/extensionController.php create mode 100644 app/views/extension/index.phtml (limited to 'lib/Minz/ExtensionManager.php') diff --git a/app/Controllers/extensionController.php b/app/Controllers/extensionController.php new file mode 100644 index 000000000..504be56d3 --- /dev/null +++ b/app/Controllers/extensionController.php @@ -0,0 +1,43 @@ +view->extension_list = Minz_ExtensionManager::list_extensions(); + } + + public function configureAction() { + if (Minz_Request::param('ajax')) { + $this->view->_useLayout(false); + } + } + + public function enableAction() { + + } + + public function disableAction() { + + } + + public function removeAction() { + + } +} diff --git a/app/layout/aside_configure.phtml b/app/layout/aside_configure.phtml index 53c52d3e3..f7f3617c4 100644 --- a/app/layout/aside_configure.phtml +++ b/app/layout/aside_configure.phtml @@ -22,6 +22,10 @@ Minz_Request::actionName() === 'profile'? ' active' : ''; ?>"> +
  • + +
  • +
  • diff --git a/app/views/extension/index.phtml b/app/views/extension/index.phtml new file mode 100644 index 000000000..c6b7c84a1 --- /dev/null +++ b/app/views/extension/index.phtml @@ -0,0 +1,35 @@ +partial('aside_configure'); ?> + +
    + + +

    + + extension_list)) { ?> + + extension_list as $ext) { ?> +
      +
    • + getName()); ?> +
      + + is_enabled()) { ?> + + + + + + + +
      +
    • +
    • getName(); ?>
    • +
    + + +

    + +
    + + +
    diff --git a/lib/Minz/Extension.php b/lib/Minz/Extension.php index ecf510ea2..a1fdd659b 100644 --- a/lib/Minz/Extension.php +++ b/lib/Minz/Extension.php @@ -17,6 +17,8 @@ class Minz_Extension { 'user', ); + private $is_enabled; + /** * The constructor to assign specific information to the extension. * @@ -41,6 +43,8 @@ class Minz_Extension { $this->description = isset($meta_info['description']) ? $meta_info['description'] : ''; $this->version = isset($meta_info['version']) ? $meta_info['version'] : '0.1'; $this->setType(isset($meta_info['type']) ? $meta_info['type'] : 'user'); + + $this->is_enabled = false; } /** @@ -66,6 +70,22 @@ class Minz_Extension { */ public function init() {} + /** + * Set the current extension to enable. + */ + public function enable() { + $this->is_enabled = true; + } + + /** + * Return if the extension is currently enabled. + * + * @return true if extension is enabled, false else. + */ + public function is_enabled() { + return $this->is_enabled; + } + /** * Getters and setters. */ diff --git a/lib/Minz/ExtensionManager.php b/lib/Minz/ExtensionManager.php index 789557b9e..6c32ccf19 100644 --- a/lib/Minz/ExtensionManager.php +++ b/lib/Minz/ExtensionManager.php @@ -144,6 +144,7 @@ class Minz_ExtensionManager { if (isset(self::$ext_list[$ext_name])) { $ext = self::$ext_list[$ext_name]; self::$ext_list_enabled[$ext_name] = $ext; + $ext->enable(); $ext->init(); } } @@ -158,4 +159,20 @@ class Minz_ExtensionManager { self::enable($ext_name); } } + + + + /** + * Returns a list of extensions. + * + * @param $only_enabled if true returns only the enabled extensions (false by default). + * @return an array of extensions. + */ + public static function list_extensions($only_enabled = false) { + if ($only_enabled) { + return self::$ext_list_enabled; + } else { + return self::$ext_list; + } + } } -- cgit v1.2.3 From 2e4682ebd451f8dd291e11141553add9164cbbef Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Sat, 6 Dec 2014 16:17:11 +0100 Subject: Add enable / disable extension features See https://github.com/FreshRSS/FreshRSS/issues/252 --- app/Controllers/extensionController.php | 72 ++++++++++++++++++++++++++++++++- app/Models/Configuration.php | 12 ++++++ lib/Minz/ExtensionManager.php | 18 +++++++-- 3 files changed, 97 insertions(+), 5 deletions(-) (limited to 'lib/Minz/ExtensionManager.php') diff --git a/app/Controllers/extensionController.php b/app/Controllers/extensionController.php index 504be56d3..415f489a6 100644 --- a/app/Controllers/extensionController.php +++ b/app/Controllers/extensionController.php @@ -29,12 +29,80 @@ class FreshRSS_extension_Controller extends Minz_ActionController { } } + /** + * This action enables a disabled extension for the current user. + * + * System extensions can only be enabled by an administrator. + * + * Parameter is: + * - e: the extension name (urlencoded). + */ public function enableAction() { - + $url_redirect = array('c' => 'extension', 'a' => 'index'); + + if (Minz_Request::isPost()) { + $ext_name = urldecode(Minz_Request::param('e')); + $ext = Minz_ExtensionManager::find_extension($ext_name); + + if (is_null($ext)) { + Minz_Request::bad('feedback.extension.not_found', $url_redirect); + } + + if ($ext->is_enabled()) { + Minz_Request::bad('feedback.extension.already_enabled', $url_redirect); + } + + if ($ext->getType() === 'system' && !FreshRSS_Auth::hasAccess('admin')) { + Minz_Request::bad('feedback.extension.no_access', $url_redirect); + } + + $ext->install(); + + FreshRSS_Context::$conf->addExtension($ext_name); + FreshRSS_Context::$conf->save(); + + Minz_Request::good('feedback.extension.enabled', $url_redirect); + } + + Minz_Request::forward($url_redirect, true); } + /** + * This action disables an enabled extension for the current user. + * + * System extensions can only be disabled by an administrator. + * + * Parameter is: + * - e: the extension name (urlencoded). + */ public function disableAction() { - + $url_redirect = array('c' => 'extension', 'a' => 'index'); + + if (Minz_Request::isPost()) { + $ext_name = urldecode(Minz_Request::param('e')); + $ext = Minz_ExtensionManager::find_extension($ext_name); + + if (is_null($ext)) { + Minz_Request::bad('feedback.extension.not_found', $url_redirect); + } + + if (!$ext->is_enabled()) { + Minz_Request::bad('feedback.extension.not_enabled', $url_redirect); + } + + if ($ext->getType() === 'system' && !FreshRSS_Auth::hasAccess('admin')) { + Minz_Request::bad('feedback.extension.no_access', $url_redirect); + } + + $ext->uninstall(); + + FreshRSS_Context::$conf->removeExtension($ext_name); + FreshRSS_Context::$conf->save(); + + Minz_Request::good('feedback.extension.disabled', $url_redirect); + } + + Minz_Request::forward($url_redirect, true); } public function removeAction() { diff --git a/app/Models/Configuration.php b/app/Models/Configuration.php index 13ce43990..83a00d4bb 100644 --- a/app/Models/Configuration.php +++ b/app/Models/Configuration.php @@ -350,4 +350,16 @@ class FreshRSS_Configuration { } $this->data['extensions_enabled'] = $value; } + public function removeExtension($ext_name) { + $this->data['extensions_enabled'] = array_diff( + $this->data['extensions_enabled'], + array($ext_name) + ); + } + public function addExtension($ext_name) { + $found = array_search($ext_name, $this->data['extensions_enabled']) !== false; + if (!$found) { + $this->data['extensions_enabled'][] = $ext_name; + } + } } diff --git a/lib/Minz/ExtensionManager.php b/lib/Minz/ExtensionManager.php index 6c32ccf19..46e421bac 100644 --- a/lib/Minz/ExtensionManager.php +++ b/lib/Minz/ExtensionManager.php @@ -160,10 +160,8 @@ class Minz_ExtensionManager { } } - - /** - * Returns a list of extensions. + * Return a list of extensions. * * @param $only_enabled if true returns only the enabled extensions (false by default). * @return an array of extensions. @@ -175,4 +173,18 @@ class Minz_ExtensionManager { return self::$ext_list; } } + + /** + * Return an extension by its name. + * + * @param $ext_name the name of the extension. + * @return the corresponding extension or null if it doesn't exist. + */ + public static function find_extension($ext_name) { + if (!isset(self::$ext_list[$ext_name])) { + return null; + } + + return self::$ext_list[$ext_name]; + } } -- cgit v1.2.3 From 08546af75ff9a25eac3409649ea4660fe070720c Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Sat, 6 Dec 2014 18:48:00 +0100 Subject: Add a first draft for hooks - New Extension->registerHook($hook_name, $hook_function) method to register a new hook - Only one hook works for the moment: entry_before_insert - ExtensionManager::callHook will need to evolve based on future hooks See https://github.com/FreshRSS/FreshRSS/issues/252 --- app/Controllers/feedController.php | 11 ++++++-- lib/Minz/Extension.php | 10 +++++++ lib/Minz/ExtensionManager.php | 55 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 74 insertions(+), 2 deletions(-) (limited to 'lib/Minz/ExtensionManager.php') diff --git a/app/Controllers/feedController.php b/app/Controllers/feedController.php index 9990a852c..dce79c57a 100755 --- a/app/Controllers/feedController.php +++ b/app/Controllers/feedController.php @@ -329,9 +329,16 @@ class FreshRSS_feed_Controller extends Minz_ActionController { $id = min(time(), $entry_date) . uSecString(); } + $entry->_id($id); + $entry->_isRead($is_read); + + $entry = Minz_ExtensionManager::callHook('entry_before_insert', $entry); + if (is_null($entry)) { + // An extension has returned a null value, there is nothing to insert. + continue; + } + $values = $entry->toArray(); - $values['id'] = $id; - $values['is_read'] = $is_read; $entryDAO->addEntry($values, $prepared_statement); } } diff --git a/lib/Minz/Extension.php b/lib/Minz/Extension.php index 490a5c5cb..c93ba2520 100644 --- a/lib/Minz/Extension.php +++ b/lib/Minz/Extension.php @@ -153,4 +153,14 @@ class Minz_Extension { public function registerViews() { Minz_View::addBasePathname($this->path); } + + /** + * Register a new hook. + * + * @param $hook_name the hook name (must exist). + * @param $hook_function the function name to call (must be callable). + */ + public function registerHook($hook_name, $hook_function) { + Minz_ExtensionManager::addHook($hook_name, $hook_function, $this); + } } diff --git a/lib/Minz/ExtensionManager.php b/lib/Minz/ExtensionManager.php index 46e421bac..7557e178f 100644 --- a/lib/Minz/ExtensionManager.php +++ b/lib/Minz/ExtensionManager.php @@ -2,6 +2,8 @@ /** * An extension manager to load extensions present in EXTENSIONS_PATH. + * + * @todo see coding style for methods!! */ class Minz_ExtensionManager { private static $ext_metaname = 'metadata.json'; @@ -11,6 +13,11 @@ class Minz_ExtensionManager { private static $ext_auto_enabled = array(); + private static $hook_list = array( + 'entry_before_insert' => array(), // function($entry) + ); + private static $ext_to_hooks = array(); + /** * Initialize the extension manager by loading extensions in EXTENSIONS_PATH. * @@ -131,6 +138,8 @@ class Minz_ExtensionManager { in_array($name, self::$ext_auto_enabled)) { self::enable($ext->getName()); } + + self::$ext_to_hooks[$name] = array(); } /** @@ -187,4 +196,50 @@ class Minz_ExtensionManager { return self::$ext_list[$ext_name]; } + + /** + * Add a hook function to a given hook. + * + * The hook name must be a valid one. For the valid list, see self::$hook_list + * array keys. + * + * @param $hook_name the hook name (must exist). + * @param $hook_function the function name to call (must be callable). + * @param $ext the extension which register the hook. + */ + public static function addHook($hook_name, $hook_function, $ext) { + if (isset(self::$hook_list[$hook_name]) && is_callable($hook_function)) { + self::$hook_list[$hook_name][] = $hook_function; + self::$ext_to_hooks[$ext->getName()][] = $hook_name; + } + } + + /** + * Call functions related to a given hook. + * + * The hook name must be a valid one. For the valid list, see self::$hook_list + * array keys. + * + * @param $hook_name the hook to call. + * @param additionnal parameters (for signature, please see self::$hook_list comments) + * @todo hook functions will have different signatures. So the $res = func($args); + * $args = $res; will not work for all of them in the future. We must + * find a better way to call hooks. + */ + public static function callHook($hook_name) { + $args = func_get_args(); + unset($args[0]); + + $result = $args; + foreach (self::$hook_list[$hook_name] as $function) { + $result = call_user_func_array($function, $args); + + if (is_null($result)) { + break; + } + + $args = $result; + } + return $result; + } } -- cgit v1.2.3 From 5932c3427b060d4f0aeab92d7ed17c8e8d4fd1d7 Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Sun, 7 Dec 2014 14:31:50 +0100 Subject: Add entry_before_display hook See https://github.com/FreshRSS/FreshRSS/issues/252 --- app/views/index/normal.phtml | 5 +++++ app/views/index/reader.phtml | 11 +++++++---- lib/Minz/ExtensionManager.php | 6 ++++-- 3 files changed, 16 insertions(+), 6 deletions(-) (limited to 'lib/Minz/ExtensionManager.php') diff --git a/app/views/index/normal.phtml b/app/views/index/normal.phtml index 02d621bd0..9cbd367d0 100644 --- a/app/views/index/normal.phtml +++ b/app/views/index/normal.phtml @@ -35,6 +35,11 @@ if (!empty($this->entries)) { entries as $item) { + $item = Minz_ExtensionManager::callHook('entry_before_display', $item); + if (is_null($item)) { + continue; + } + if ($display_today && $item->isDay(FreshRSS_Days::TODAY, $today)) { ?>
    entries)) { $content_width = FreshRSS_Context::$conf->content_width; ?> -
    - entries as $item) { ?> - -
    +
    entries as $item) { + $item = Minz_ExtensionManager::callHook('entry_before_display', $item); + if (is_null($item)) { + continue; + } + ?>
    array(), // function($entry) + 'entry_before_display' => array(), // function($entry) -> Entry | null + 'entry_before_insert' => array(), // function($entry) -> Entry | null ); private static $ext_to_hooks = array(); @@ -230,7 +232,7 @@ class Minz_ExtensionManager { $args = func_get_args(); unset($args[0]); - $result = $args; + $result = $args[1]; foreach (self::$hook_list[$hook_name] as $function) { $result = call_user_func_array($function, $args); -- cgit v1.2.3 From 188b517daa174ce494f31dec02ae2cff122488ff Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Mon, 8 Dec 2014 13:05:56 +0100 Subject: Add a feed_before_insert hook See https://github.com/FreshRSS/FreshRSS/issues/252 --- app/Controllers/feedController.php | 6 ++++++ app/Controllers/importExportController.php | 30 ++++++++++++++++++++---------- lib/Minz/ExtensionManager.php | 1 + 3 files changed, 27 insertions(+), 10 deletions(-) (limited to 'lib/Minz/ExtensionManager.php') diff --git a/app/Controllers/feedController.php b/app/Controllers/feedController.php index 0a7edbee3..7dda3840e 100755 --- a/app/Controllers/feedController.php +++ b/app/Controllers/feedController.php @@ -138,6 +138,12 @@ class FreshRSS_feed_Controller extends Minz_ActionController { $feed->_category($cat); $feed->_httpAuth($http_auth); + // Call the extension hook + $feed = Minz_ExtensionManager::callHook('feed_before_insert', $feed); + if (is_null($feed)) { + Minz_Request::bad(_t('feed_not_added', $feed->name()), $url_redirect); + } + $values = array( 'url' => $feed->url(), 'category' => $feed->category(), diff --git a/app/Controllers/importExportController.php b/app/Controllers/importExportController.php index c67b30431..52df5bf8b 100644 --- a/app/Controllers/importExportController.php +++ b/app/Controllers/importExportController.php @@ -259,10 +259,16 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { $feed->_website($website); $feed->_description($description); - // addFeedObject checks if feed is already in DB so nothing else to - // check here - $id = $this->feedDAO->addFeedObject($feed); - $error = ($id === false); + // Call the extension hook + $feed = Minz_ExtensionManager::callHook('feed_before_insert', $feed); + if (!is_null($feed)) { + // addFeedObject checks if feed is already in DB so nothing else to + // check here + $id = $this->feedDAO->addFeedObject($feed); + $error = ($id === false); + } else { + $error = true; + } } catch (FreshRSS_Feed_Exception $e) { Minz_Log::warning($e->getMessage()); $error = true; @@ -427,13 +433,17 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { $feed->_name($name); $feed->_website($website); - // addFeedObject checks if feed is already in DB so nothing else to - // check here. - $id = $this->feedDAO->addFeedObject($feed); + // Call the extension hook + $feed = Minz_ExtensionManager::callHook('feed_before_insert', $feed); + if (!is_null($feed)) { + // addFeedObject checks if feed is already in DB so nothing else to + // check here. + $id = $this->feedDAO->addFeedObject($feed); - if ($id !== false) { - $feed->_id($id); - $return = $feed; + if ($id !== false) { + $feed->_id($id); + $return = $feed; + } } } catch (FreshRSS_Feed_Exception $e) { Minz_Log::warning($e->getMessage()); diff --git a/lib/Minz/ExtensionManager.php b/lib/Minz/ExtensionManager.php index 5491c7cf6..9e6a3155a 100644 --- a/lib/Minz/ExtensionManager.php +++ b/lib/Minz/ExtensionManager.php @@ -17,6 +17,7 @@ class Minz_ExtensionManager { private static $hook_list = array( 'entry_before_display' => array(), // function($entry) -> Entry | null 'entry_before_insert' => array(), // function($entry) -> Entry | null + 'feed_before_insert' => array(), // function($feed) -> Feed | null ); private static $ext_to_hooks = array(); -- cgit v1.2.3 From ade1524d43150a45f633e6efac3d7fdea3e1d802 Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Wed, 14 Jan 2015 21:59:53 +0100 Subject: Fix valid extension entry points. Add possibility to use underscores (_) in entrypoints. --- lib/Minz/ExtensionManager.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'lib/Minz/ExtensionManager.php') diff --git a/lib/Minz/ExtensionManager.php b/lib/Minz/ExtensionManager.php index 5880e80f6..3df72abb7 100644 --- a/lib/Minz/ExtensionManager.php +++ b/lib/Minz/ExtensionManager.php @@ -82,9 +82,10 @@ class Minz_ExtensionManager { * @return true if the array is valid, false else. */ public static function is_valid_metadata($meta) { + $valid_chars = array('_'); return !(empty($meta['name']) || empty($meta['entrypoint']) || - !ctype_alnum($meta['entrypoint'])); + !ctype_alnum(str_replace($valid_chars, '', $meta['entrypoint']))); } /** -- cgit v1.2.3 From 789d9fc6c8647fa100d80e3ef52727c813f35f16 Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Wed, 14 Jan 2015 23:40:18 +0100 Subject: Fix coding style in extension manager Yep, same as 8968288... --- app/Controllers/extensionController.php | 10 +++++----- app/FreshRSS.php | 2 +- lib/Minz/ExtensionManager.php | 10 +++++----- 3 files changed, 11 insertions(+), 11 deletions(-) (limited to 'lib/Minz/ExtensionManager.php') diff --git a/app/Controllers/extensionController.php b/app/Controllers/extensionController.php index ec5eecadb..b6d2d3fe4 100644 --- a/app/Controllers/extensionController.php +++ b/app/Controllers/extensionController.php @@ -25,7 +25,7 @@ class FreshRSS_extension_Controller extends Minz_ActionController { 'user' => array(), ); - $extensions = Minz_ExtensionManager::list_extensions(); + $extensions = Minz_ExtensionManager::listExtensions(); foreach ($extensions as $ext) { $this->view->extension_list[$ext->getType()][] = $ext; } @@ -50,7 +50,7 @@ class FreshRSS_extension_Controller extends Minz_ActionController { } $ext_name = urldecode(Minz_Request::param('e')); - $ext = Minz_ExtensionManager::find_extension($ext_name); + $ext = Minz_ExtensionManager::findExtension($ext_name); if (is_null($ext)) { Minz_Error::error(404); @@ -77,7 +77,7 @@ class FreshRSS_extension_Controller extends Minz_ActionController { if (Minz_Request::isPost()) { $ext_name = urldecode(Minz_Request::param('e')); - $ext = Minz_ExtensionManager::find_extension($ext_name); + $ext = Minz_ExtensionManager::findExtension($ext_name); if (is_null($ext)) { Minz_Request::bad(_t('feedback.extensions.not_found', $ext_name), @@ -133,7 +133,7 @@ class FreshRSS_extension_Controller extends Minz_ActionController { if (Minz_Request::isPost()) { $ext_name = urldecode(Minz_Request::param('e')); - $ext = Minz_ExtensionManager::find_extension($ext_name); + $ext = Minz_ExtensionManager::findExtension($ext_name); if (is_null($ext)) { Minz_Request::bad(_t('feedback.extensions.not_found', $ext_name), @@ -193,7 +193,7 @@ class FreshRSS_extension_Controller extends Minz_ActionController { if (Minz_Request::isPost()) { $ext_name = urldecode(Minz_Request::param('e')); - $ext = Minz_ExtensionManager::find_extension($ext_name); + $ext = Minz_ExtensionManager::findExtension($ext_name); if (is_null($ext)) { Minz_Request::bad(_t('feedback.extensions.not_found', $ext_name), diff --git a/app/FreshRSS.php b/app/FreshRSS.php index 785b10299..021687999 100644 --- a/app/FreshRSS.php +++ b/app/FreshRSS.php @@ -53,7 +53,7 @@ class FreshRSS extends Minz_FrontController { // Enable extensions for the current (logged) user. if (FreshRSS_Auth::hasAccess()) { $ext_list = FreshRSS_Context::$user_conf->extensions_enabled; - Minz_ExtensionManager::enable_by_list($ext_list); + Minz_ExtensionManager::enableByList($ext_list); } } diff --git a/lib/Minz/ExtensionManager.php b/lib/Minz/ExtensionManager.php index 3df72abb7..8369e242a 100644 --- a/lib/Minz/ExtensionManager.php +++ b/lib/Minz/ExtensionManager.php @@ -52,7 +52,7 @@ class Minz_ExtensionManager { } $meta_raw_content = file_get_contents($metadata_filename); $meta_json = json_decode($meta_raw_content, true); - if (!$meta_json || !self::is_valid_metadata($meta_json)) { + if (!$meta_json || !self::isValidMetadata($meta_json)) { // metadata.json is not a json file? Invalid! // or metadata.json is invalid (no required information), invalid! Minz_Log::warning('`' . $metadata_filename . '` is not a valid metadata file'); @@ -81,7 +81,7 @@ class Minz_ExtensionManager { * @param $meta is an array of values. * @return true if the array is valid, false else. */ - public static function is_valid_metadata($meta) { + public static function isValidMetadata($meta) { $valid_chars = array('_'); return !(empty($meta['name']) || empty($meta['entrypoint']) || @@ -168,7 +168,7 @@ class Minz_ExtensionManager { * * @param $ext_list the names of extensions we want to load. */ - public static function enable_by_list($ext_list) { + public static function enableByList($ext_list) { foreach ($ext_list as $ext_name) { self::enable($ext_name); } @@ -180,7 +180,7 @@ class Minz_ExtensionManager { * @param $only_enabled if true returns only the enabled extensions (false by default). * @return an array of extensions. */ - public static function list_extensions($only_enabled = false) { + public static function listExtensions($only_enabled = false) { if ($only_enabled) { return self::$ext_list_enabled; } else { @@ -194,7 +194,7 @@ class Minz_ExtensionManager { * @param $ext_name the name of the extension. * @return the corresponding extension or null if it doesn't exist. */ - public static function find_extension($ext_name) { + public static function findExtension($ext_name) { if (!isset(self::$ext_list[$ext_name])) { return null; } -- cgit v1.2.3 From 13cf8b5f9f22a50bba5d1223174407abb1c1d94c Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Thu, 15 Jan 2015 12:02:42 +0100 Subject: Improve hook calls and add post_update hook - To the hook is associated a method signature (OneToOne or NoneToNone for now) so it is easier to call hooks correctly - post_update hook is called during the post update moment. --- app/Controllers/updateController.php | 2 + lib/Minz/ExtensionManager.php | 74 +++++++++++++++++++++++++++++------- 2 files changed, 63 insertions(+), 13 deletions(-) (limited to 'lib/Minz/ExtensionManager.php') diff --git a/app/Controllers/updateController.php b/app/Controllers/updateController.php index 1ea5816da..61b62773b 100644 --- a/app/Controllers/updateController.php +++ b/app/Controllers/updateController.php @@ -112,6 +112,8 @@ class FreshRSS_update_Controller extends Minz_ActionController { if (Minz_Request::param('post_conf', false)) { $res = do_post_update(); + Minz_ExtensionManager::callHook('post_update'); + if ($res === true) { @unlink(UPDATE_FILENAME); @file_put_contents(join_path(DATA_PATH, 'last_update.txt'), ''); diff --git a/lib/Minz/ExtensionManager.php b/lib/Minz/ExtensionManager.php index 8369e242a..7edc7afaa 100644 --- a/lib/Minz/ExtensionManager.php +++ b/lib/Minz/ExtensionManager.php @@ -15,9 +15,22 @@ class Minz_ExtensionManager { // List of available hooks. Please keep this list sorted. private static $hook_list = array( - 'entry_before_display' => array(), // function($entry) -> Entry | null - 'entry_before_insert' => array(), // function($entry) -> Entry | null - 'feed_before_insert' => array(), // function($feed) -> Feed | null + 'entry_before_display' => array( // function($entry) -> Entry | null + 'list' => array(), + 'signature' => 'OneToOne', + ), + 'entry_before_insert' => array( // function($entry) -> Entry | null + 'list' => array(), + 'signature' => 'OneToOne', + ), + 'feed_before_insert' => array( // function($feed) -> Feed | null + 'list' => array(), + 'signature' => 'OneToOne', + ), + 'post_update' => array( // function(none) -> none + 'list' => array(), + 'signature' => 'NoneToNone', + ), ); private static $ext_to_hooks = array(); @@ -214,7 +227,7 @@ class Minz_ExtensionManager { */ public static function addHook($hook_name, $hook_function, $ext) { if (isset(self::$hook_list[$hook_name]) && is_callable($hook_function)) { - self::$hook_list[$hook_name][] = $hook_function; + self::$hook_list[$hook_name]['list'][] = $hook_function; self::$ext_to_hooks[$ext->getName()][] = $hook_name; } } @@ -226,25 +239,60 @@ class Minz_ExtensionManager { * array keys. * * @param $hook_name the hook to call. - * @param additionnal parameters (for signature, please see self::$hook_list comments) - * @todo hook functions will have different signatures. So the $res = func($args); - * $args = $res; will not work for all of them in the future. We must - * find a better way to call hooks. + * @param additionnal parameters (for signature, please see self::$hook_list). + * @return the final result of the called hook. */ public static function callHook($hook_name) { + if (!isset(self::$hook_list[$hook_name])) { + return; + } + + $signature = self::$hook_list[$hook_name]['signature']; + $signature = 'self::call' . $signature; $args = func_get_args(); - unset($args[0]); - $result = $args[1]; - foreach (self::$hook_list[$hook_name] as $function) { - $result = call_user_func_array($function, $args); + return call_user_func_array($signature, $args); + } + + /** + * Call a hook which takes one argument and return a result. + * + * The result is chained between the extension, for instance, first extension + * hook will receive the initial argument and return a result which will be + * passed as an argument to the next extension hook and so on. + * + * If a hook return a null value, the method is stopped and return null. + * + * @param $hook_name is the hook to call. + * @param $arg is the argument to pass to the first extension hook. + * @return the final chained result of the hooks. If nothing is changed, + * the initial argument is returned. + */ + private static function callOneToOne($hook_name, $arg) { + $result = $arg; + foreach (self::$hook_list[$hook_name]['list'] as $function) { + $result = call_user_func($function, $arg); if (is_null($result)) { break; } - $args = $result; + $arg = $result; } return $result; } + + /** + * Call a hook which takes no argument and returns nothing. + * + * This case is simpler than callOneToOne because hooks are called one by + * one, without any consideration of argument nor result. + * + * @param $hook_name is the hook to call. + */ + private static function callNoneToNone($hook_name) { + foreach (self::$hook_list[$hook_name]['list'] as $function) { + call_user_func($function); + } + } } -- cgit v1.2.3 From d30b3becfa50b96982a3b4880c07cc2b770d7eed Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Mon, 19 Jan 2015 13:54:57 +0100 Subject: Addressed warnings when reading from new files There were warnings when reading extensions (trying to use e.g. README and .gitignore as directories), and when reading update file. https://github.com/FreshRSS/FreshRSS/issues/733 --- app/Controllers/updateController.php | 5 ++++- lib/Minz/ExtensionManager.php | 3 +++ 2 files changed, 7 insertions(+), 1 deletion(-) (limited to 'lib/Minz/ExtensionManager.php') diff --git a/app/Controllers/updateController.php b/app/Controllers/updateController.php index 61b62773b..4797a3486 100644 --- a/app/Controllers/updateController.php +++ b/app/Controllers/updateController.php @@ -28,7 +28,10 @@ class FreshRSS_update_Controller extends Minz_ActionController { ); } elseif (file_exists(UPDATE_FILENAME)) { // There is an update file to apply! - $version = file_get_contents(join_path(DATA_PATH, 'last_update.txt')); + $version = @file_get_contents(join_path(DATA_PATH, 'last_update.txt')); + if (empty($version)) { + $version = 'unknown'; + } $this->view->update_to_apply = true; $this->view->message = array( 'status' => 'good', diff --git a/lib/Minz/ExtensionManager.php b/lib/Minz/ExtensionManager.php index 7edc7afaa..f00453f6c 100644 --- a/lib/Minz/ExtensionManager.php +++ b/lib/Minz/ExtensionManager.php @@ -56,6 +56,9 @@ class Minz_ExtensionManager { foreach ($list_potential_extensions as $ext_dir) { $ext_pathname = EXTENSIONS_PATH . '/' . $ext_dir; + if (!is_dir($ext_pathname)) { + continue; + } $metadata_filename = $ext_pathname . '/' . self::$ext_metaname; // Try to load metadata file. -- cgit v1.2.3 From a47ce4f6af9e55267c4ce50af1bcc052229535ad Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Thu, 29 Jan 2015 10:17:29 +0100 Subject: Fix including extensions for actualize_script Extensions could be included multiple times. It resulted in an error. --- lib/Minz/ExtensionManager.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'lib/Minz/ExtensionManager.php') diff --git a/lib/Minz/ExtensionManager.php b/lib/Minz/ExtensionManager.php index f00453f6c..c5c68a8d4 100644 --- a/lib/Minz/ExtensionManager.php +++ b/lib/Minz/ExtensionManager.php @@ -114,7 +114,7 @@ class Minz_ExtensionManager { $entry_point_filename = $info['path'] . '/' . self::$ext_entry_point; $ext_class_name = $info['entrypoint'] . 'Extension'; - include($entry_point_filename); + include_once($entry_point_filename); // Test if the given extension class exists. if (!class_exists($ext_class_name)) { -- cgit v1.2.3