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). --- app/FreshRSS.php | 42 +++++------ constants.php | 3 +- extensions/README.md | 15 ++++ extensions/Read-me.txt | 15 ---- lib/Minz/Configuration.php | 10 +++ lib/Minz/Extension.php | 96 +++++++++++++++++++++++++ lib/Minz/ExtensionException.php | 15 ++++ lib/Minz/ExtensionManager.php | 150 ++++++++++++++++++++++++++++++++++++++++ 8 files changed, 310 insertions(+), 36 deletions(-) create mode 100644 extensions/README.md delete mode 100644 extensions/Read-me.txt create mode 100644 lib/Minz/Extension.php create mode 100644 lib/Minz/ExtensionException.php create mode 100644 lib/Minz/ExtensionManager.php diff --git a/app/FreshRSS.php b/app/FreshRSS.php index 6114a5d1a..2db811a71 100644 --- a/app/FreshRSS.php +++ b/app/FreshRSS.php @@ -6,6 +6,9 @@ class FreshRSS extends Minz_FrontController { Minz_Session::init('FreshRSS'); } + // Load list of extensions and initialize the "system" ones. + Minz_ExtensionManager::init(); + // Need to be called just after session init because it initializes // current user. FreshRSS_Auth::init(); @@ -32,7 +35,6 @@ class FreshRSS extends Minz_FrontController { $this->loadStylesAndScripts(); $this->loadNotifications(); - $this->loadExtensions(); } private function loadStylesAndScripts() { @@ -74,23 +76,23 @@ class FreshRSS extends Minz_FrontController { } } - private function loadExtensions() { - $extensionPath = FRESHRSS_PATH . '/extensions/'; - //TODO: Add a preference to load only user-selected extensions - foreach (scandir($extensionPath) as $key => $extension) { - if (ctype_alpha($extension)) { - $mtime = @filemtime($extensionPath . $extension . '/style.css'); - if ($mtime !== false) { - Minz_View::appendStyle(Minz_Url::display('/ext.php?c&e=' . $extension . '&' . $mtime)); - } - $mtime = @filemtime($extensionPath . $extension . '/script.js'); - if ($mtime !== false) { - Minz_View::appendScript(Minz_Url::display('/ext.php?j&e=' . $extension . '&' . $mtime)); - } - if (file_exists($extensionPath . $extension . '/module.php')) { - //TODO: include - } - } - } - } + // private function loadExtensions() { + // $extensionPath = FRESHRSS_PATH . '/extensions/'; + // //TODO: Add a preference to load only user-selected extensions + // foreach (scandir($extensionPath) as $key => $extension) { + // if (ctype_alpha($extension)) { + // $mtime = @filemtime($extensionPath . $extension . '/style.css'); + // if ($mtime !== false) { + // Minz_View::appendStyle(Minz_Url::display('/ext.php?c&e=' . $extension . '&' . $mtime)); + // } + // $mtime = @filemtime($extensionPath . $extension . '/script.js'); + // if ($mtime !== false) { + // Minz_View::appendScript(Minz_Url::display('/ext.php?j&e=' . $extension . '&' . $mtime)); + // } + // if (file_exists($extensionPath . $extension . '/module.php')) { + // //TODO: include + // } + // } + // } + // } } diff --git a/constants.php b/constants.php index f66a012b0..999e703ba 100644 --- a/constants.php +++ b/constants.php @@ -20,6 +20,7 @@ define('FRESHRSS_PATH', dirname(__FILE__)); define('CACHE_PATH', DATA_PATH . '/cache'); define('LIB_PATH', FRESHRSS_PATH . '/lib'); - define('APP_PATH', FRESHRSS_PATH . '/app'); + define('APP_PATH', FRESHRSS_PATH . '/app'); + define('EXTENSIONS_PATH', FRESHRSS_PATH . '/extensions'); define('TMP_PATH', sys_get_temp_dir()); diff --git a/extensions/README.md b/extensions/README.md new file mode 100644 index 000000000..e7b66d5bc --- /dev/null +++ b/extensions/README.md @@ -0,0 +1,15 @@ +== FreshRSS extensions == + +You may place in this directory some custom extensions for FreshRSS. + +The structure must be: + +./FreshRSS/extensions/ + ./NameOfExtensionAlphanumeric/ + ./style.css + ./script.js + ./module.php + +Each file is optional. + +The name of non-official extensions should start by an 'x'. diff --git a/extensions/Read-me.txt b/extensions/Read-me.txt deleted file mode 100644 index e7b66d5bc..000000000 --- a/extensions/Read-me.txt +++ /dev/null @@ -1,15 +0,0 @@ -== FreshRSS extensions == - -You may place in this directory some custom extensions for FreshRSS. - -The structure must be: - -./FreshRSS/extensions/ - ./NameOfExtensionAlphanumeric/ - ./style.css - ./script.js - ./module.php - -Each file is optional. - -The name of non-official extensions should start by an 'x'. diff --git a/lib/Minz/Configuration.php b/lib/Minz/Configuration.php index 6cbc9fc0b..4d3ab0964 100644 --- a/lib/Minz/Configuration.php +++ b/lib/Minz/Configuration.php @@ -69,6 +69,8 @@ class Minz_Configuration { 'max_categories' => Minz_Configuration::MAX_SMALL_INT, ); + private static $extensions_enabled = array(); + /* * Getteurs */ @@ -133,6 +135,9 @@ class Minz_Configuration { public static function unsafeAutologinEnabled() { return self::$unsafe_autologin_enabled; } + public static function extensionsEnabled() { + return self::$extensions_enabled; + } public static function _allowAnonymous($allow = false) { self::$allow_anonymous = ((bool)$allow) && self::canLogIn(); @@ -338,6 +343,11 @@ class Minz_Configuration { } } + // Extensions + if (isset($ini_array['extensions']) && is_array($ini_array['extensions'])) { + self::$extensions_enabled = $ini_array['extensions']; + } + // Base de données if (isset ($ini_array['db'])) { $db = $ini_array['db']; diff --git a/lib/Minz/Extension.php b/lib/Minz/Extension.php new file mode 100644 index 000000000..f442344a3 --- /dev/null +++ b/lib/Minz/Extension.php @@ -0,0 +1,96 @@ +name = $meta_info['name']; + $this->entrypoint = $meta_info['entrypoint']; + $this->path = $meta_info['path']; + $this->author = isset($meta_info['author']) ? $meta_info['author'] : ''; + $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'); + } + + /** + * Used when installing an extension (e.g. update the database scheme). + * + * It must be redefined by child classes. + */ + public function install() {} + + /** + * Used when uninstalling an extension (e.g. revert the database scheme to + * cancel changes from install). + * + * It must be redefined by child classes. + */ + public function uninstall() {} + + /** + * Call at the initialization of the extension (i.e. when the extension is + * enabled by the extension manager). + * + * It must be redefined by child classes. + */ + public function init() {} + + /** + * Getters and setters. + */ + public function getName() { + return $this->name; + } + public function getEntrypoint() { + return $this->entrypoint; + } + public function getAuthor() { + return $this->author; + } + public function getDescription() { + return $this->description; + } + public function getVersion() { + return $this->version; + } + public function getType() { + return $this->type; + } + private function setType($type) { + if (!in_array($type, self::$authorized_types)) { + throw new Minz_ExtensionException('invalid `type` info', $this->name); + } + $this->type = $type; + } +} diff --git a/lib/Minz/ExtensionException.php b/lib/Minz/ExtensionException.php new file mode 100644 index 000000000..647f1a9b9 --- /dev/null +++ b/lib/Minz/ExtensionException.php @@ -0,0 +1,15 @@ +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(-) 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 0316badf649ef285f068847ef094ace80dd51290 Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Thu, 4 Dec 2014 19:52:07 +0100 Subject: Update gitignore for extensions --- extensions/.gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/.gitignore b/extensions/.gitignore index d93e5e396..cd5592022 100644 --- a/extensions/.gitignore +++ b/extensions/.gitignore @@ -1 +1 @@ -/[xX] +[xX]* -- cgit v1.2.3 From f9b037742a0aeb49cab86782d1a59913c2de47bf Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Thu, 4 Dec 2014 20:41:01 +0100 Subject: Update ext.php to serve any file from extensions Add an extension->getFileUrl() method to facilitate url generation --- lib/Minz/Extension.php | 23 +++++++++++++++++++++++ p/ext.php | 37 +++++++++++++++++++------------------ 2 files changed, 42 insertions(+), 18 deletions(-) diff --git a/lib/Minz/Extension.php b/lib/Minz/Extension.php index f442344a3..72a375a6d 100644 --- a/lib/Minz/Extension.php +++ b/lib/Minz/Extension.php @@ -75,6 +75,9 @@ class Minz_Extension { public function getEntrypoint() { return $this->entrypoint; } + public function getPath() { + return $this->path; + } public function getAuthor() { return $this->author; } @@ -93,4 +96,24 @@ class Minz_Extension { } $this->type = $type; } + + /** + * Return the url for a given file. + * + * @param $filename name of the file to serve. + * @param $type the type (js or css) of the file to serve. + * @return the url corresponding to the file. + */ + public function getFileUrl($filename, $type) { + $dir = end(explode('/', $this->path)); + $file_name_url = urlencode($dir . '/' . $filename); + + $absolute_path = $this->path . '/' . $filename; + $mtime = @filemtime($absolute_path); + + $url = '/ext.php?f=' . $file_name_url . + '&t=' . $type . + '&' . $mtime; + return Minz_Url::display($url); + } } diff --git a/p/ext.php b/p/ext.php index a1dde2f93..39c224a84 100644 --- a/p/ext.php +++ b/p/ext.php @@ -1,32 +1,33 @@ Date: Thu, 4 Dec 2014 20:43:05 +0100 Subject: Remove old code for extensions --- app/FreshRSS.php | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/app/FreshRSS.php b/app/FreshRSS.php index 166ee1709..dc7d0b375 100644 --- a/app/FreshRSS.php +++ b/app/FreshRSS.php @@ -79,24 +79,4 @@ class FreshRSS extends Minz_FrontController { Minz_Session::_param('notification'); } } - - // private function loadExtensions() { - // $extensionPath = FRESHRSS_PATH . '/extensions/'; - // //TODO: Add a preference to load only user-selected extensions - // foreach (scandir($extensionPath) as $key => $extension) { - // if (ctype_alpha($extension)) { - // $mtime = @filemtime($extensionPath . $extension . '/style.css'); - // if ($mtime !== false) { - // Minz_View::appendStyle(Minz_Url::display('/ext.php?c&e=' . $extension . '&' . $mtime)); - // } - // $mtime = @filemtime($extensionPath . $extension . '/script.js'); - // if ($mtime !== false) { - // Minz_View::appendScript(Minz_Url::display('/ext.php?j&e=' . $extension . '&' . $mtime)); - // } - // if (file_exists($extensionPath . $extension . '/module.php')) { - // //TODO: include - // } - // } - // } - // } } -- cgit v1.2.3 From a2da70fd119cc43438f8dd88de54a7d19fafbe1a Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Fri, 5 Dec 2014 10:51:34 +0100 Subject: Fix security hole from ext.php script. Now, ext.php can only serve file under a EXTENSIONS_PATH/ext_dir/static/ directory. A 400 Bad Request error will be returned for other files. See https://github.com/FreshRSS/FreshRSS/issues/252 And https://github.com/FreshRSS/FreshRSS/commit/f9b037742a0aeb49cab86782d1a59913c2de47b --- lib/Minz/Extension.php | 4 ++-- p/ext.php | 34 +++++++++++++++++++++++++++++++++- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/lib/Minz/Extension.php b/lib/Minz/Extension.php index 72a375a6d..ecf510ea2 100644 --- a/lib/Minz/Extension.php +++ b/lib/Minz/Extension.php @@ -106,9 +106,9 @@ class Minz_Extension { */ public function getFileUrl($filename, $type) { $dir = end(explode('/', $this->path)); - $file_name_url = urlencode($dir . '/' . $filename); + $file_name_url = urlencode($dir . '/static/' . $filename); - $absolute_path = $this->path . '/' . $filename; + $absolute_path = $this->path . '/static/' . $filename; $mtime = @filemtime($absolute_path); $url = '/ext.php?f=' . $file_name_url . diff --git a/p/ext.php b/p/ext.php index 39c224a84..5c9f9125f 100644 --- a/p/ext.php +++ b/p/ext.php @@ -7,10 +7,42 @@ if (!isset($_GET['f']) || require('../constants.php'); +/** + * Check if a file can be served by ext.php. A valid file is under a + * EXTENSIONS_PATH/extension_name/static/ directory. + * + * You should sanitize path by using the realpath() function. + * + * @param $path the path to the file we want to serve. + * @return true if it can be served, false else. + * + */ +function is_valid_path($path) { + // It must be under the extension path. + $in_ext_path = (substr($path, 0, strlen(EXTENSIONS_PATH)) === EXTENSIONS_PATH); + if (!$in_ext_path) { + return false; + } + + // File to serve must be under a `ext_dir/static/` directory. + $path_relative_to_ext = substr($path, strlen(EXTENSIONS_PATH) + 1); + $path_splitted = explode('/', $path_relative_to_ext); + if (count($path_splitted) < 3 || $path_splitted[1] !== 'static') { + return false; + } + + return true; +} + $file_name = urldecode($_GET['f']); $file_type = $_GET['t']; -$absolute_filename = EXTENSIONS_PATH . '/' . $file_name; +$absolute_filename = realpath(EXTENSIONS_PATH . '/' . $file_name); + +if (!is_valid_path($absolute_filename)) { + header('HTTP/1.1 400 Bad Request'); + die(); +} switch ($file_type) { case 'css': -- 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 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 f8aa66152fcab24ae7cd9663dab0c0c96a45ca24 Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Fri, 5 Dec 2014 14:48:09 +0100 Subject: Give possibility to register a new Controller. - Add a Extension->registerController(name) method - Controllers must be written in extension_dir/controllers/nameController.php - Controllers must be named as FreshExtension_name_Controller - Controllers must extend Minz_ActionController See https://github.com/FreshRSS/FreshRSS/issues/252 --- lib/Minz/Dispatcher.php | 47 ++++++++++++++++++++++++++++++++++++++++++++--- lib/Minz/Extension.php | 13 +++++++++++++ 2 files changed, 57 insertions(+), 3 deletions(-) diff --git a/lib/Minz/Dispatcher.php b/lib/Minz/Dispatcher.php index f62a92911..66789a3d3 100644 --- a/lib/Minz/Dispatcher.php +++ b/lib/Minz/Dispatcher.php @@ -15,6 +15,7 @@ class Minz_Dispatcher { /* singleton */ private static $instance = null; private static $needsReset; + private static $registrations = array(); private $controller; @@ -38,7 +39,7 @@ class Minz_Dispatcher { self::$needsReset = false; try { - $this->createController ('FreshRSS_' . Minz_Request::controllerName () . '_Controller'); + $this->createController (Minz_Request::controllerName ()); $this->controller->init (); $this->controller->firstAction (); if (!self::$needsReset) { @@ -73,8 +74,11 @@ class Minz_Dispatcher { * > pas une instance de ActionController */ private function createController ($controller_name) { - $filename = APP_PATH . self::CONTROLLERS_PATH_NAME . '/' - . $controller_name . '.php'; + if (self::isRegistered($controller_name)) { + $controller_name = self::loadController($controller_name); + } else { + $controller_name = 'FreshRSS_' . $controller_name . '_Controller'; + } if (!class_exists ($controller_name)) { throw new Minz_ControllerNotExistException ( @@ -114,4 +118,41 @@ class Minz_Dispatcher { $action_name )); } + + /** + * Register a controller file. + * + * @param $base_name the base name of the controller (i.e. ./?c=) + * @param $controller_name the name of the controller (e.g. HelloWorldController). + * @param $filename the file which contains the controller. + */ + public static function registerController($base_name, $controller_name, $filename) { + if (file_exists($filename)) { + self::$registrations[$base_name] = array( + $controller_name, + $filename, + ); + } + } + + /** + * Return if a controller is registered. + * + * @param $base_name the base name of the controller. + * @return true if the controller has been registered, false else. + */ + public static function isRegistered($base_name) { + return isset(self::$registrations[$base_name]); + } + + /** + * Load a controller file (include) and return its name. + * + * @param $base_name the base name of the controller. + */ + private static function loadController($base_name) { + list($controller_name, $filename) = self::$registrations[$base_name]; + include($filename); + return $controller_name; + } } diff --git a/lib/Minz/Extension.php b/lib/Minz/Extension.php index a1fdd659b..ad3465640 100644 --- a/lib/Minz/Extension.php +++ b/lib/Minz/Extension.php @@ -136,4 +136,17 @@ class Minz_Extension { '&' . $mtime; return Minz_Url::display($url); } + + /** + * Register a controller in the Dispatcher. + * + * @param @base_name the base name of the controller. Final name will be: + * FreshExtension__Controller. + */ + public function registerController($base_name) { + $controller_name = 'FreshExtension_' . $base_name . '_Controller'; + $filename = $this->path . '/controllers/' . $base_name . 'Controller.php'; + + Minz_Dispatcher::registerController($base_name, $controller_name, $filename); + } } -- cgit v1.2.3 From c6a682deb94111c1e14cf10e565da3f4214f02dc Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Fri, 5 Dec 2014 15:27:56 +0100 Subject: Extensions can define new views - View base pathname is set to the extension directory - An extension can now override an existing controller / view See https://github.com/FreshRSS/FreshRSS/issues/252 --- lib/Minz/Dispatcher.php | 40 +++++++++++++++++++++++----------------- lib/Minz/Extension.php | 5 +---- lib/Minz/View.php | 10 +++++++--- 3 files changed, 31 insertions(+), 24 deletions(-) diff --git a/lib/Minz/Dispatcher.php b/lib/Minz/Dispatcher.php index 66789a3d3..edd59c7cc 100644 --- a/lib/Minz/Dispatcher.php +++ b/lib/Minz/Dispatcher.php @@ -68,16 +68,17 @@ class Minz_Dispatcher { /** * Instancie le Controller - * @param $controller_name le nom du controller à instancier + * @param $base_name le nom du controller à instancier * @exception ControllerNotExistException le controller n'existe pas * @exception ControllerNotActionControllerException controller n'est * > pas une instance de ActionController */ - private function createController ($controller_name) { - if (self::isRegistered($controller_name)) { - $controller_name = self::loadController($controller_name); + private function createController ($base_name) { + if (self::isRegistered($base_name)) { + self::loadController($base_name); + $controller_name = 'FreshExtension_' . $base_name . '_Controller'; } else { - $controller_name = 'FreshRSS_' . $controller_name . '_Controller'; + $controller_name = 'FreshRSS_' . $base_name . '_Controller'; } if (!class_exists ($controller_name)) { @@ -94,6 +95,10 @@ class Minz_Dispatcher { Minz_Exception::ERROR ); } + + if (self::isRegistered($base_name)) { + $this->setViewPath($this->controller, $base_name); + } } /** @@ -123,15 +128,11 @@ class Minz_Dispatcher { * Register a controller file. * * @param $base_name the base name of the controller (i.e. ./?c=) - * @param $controller_name the name of the controller (e.g. HelloWorldController). - * @param $filename the file which contains the controller. + * @param $base_path the base path where we should look into to find info. */ - public static function registerController($base_name, $controller_name, $filename) { - if (file_exists($filename)) { - self::$registrations[$base_name] = array( - $controller_name, - $filename, - ); + public static function registerController($base_name, $base_path) { + if (!self::isRegistered($base_name)) { + self::$registrations[$base_name] = $base_path; } } @@ -146,13 +147,18 @@ class Minz_Dispatcher { } /** - * Load a controller file (include) and return its name. + * Load a controller file (include). * * @param $base_name the base name of the controller. */ private static function loadController($base_name) { - list($controller_name, $filename) = self::$registrations[$base_name]; - include($filename); - return $controller_name; + $base_path = self::$registrations[$base_name]; + $controller_filename = $base_path . '/controllers/' . $base_name . 'Controller.php'; + include($controller_filename); + } + + private static function setViewPath($controller, $base_name) { + $base_path = self::$registrations[$base_name]; + $controller->view()->setBasePathname($base_path); } } diff --git a/lib/Minz/Extension.php b/lib/Minz/Extension.php index ad3465640..5a61ba2e0 100644 --- a/lib/Minz/Extension.php +++ b/lib/Minz/Extension.php @@ -144,9 +144,6 @@ class Minz_Extension { * FreshExtension__Controller. */ public function registerController($base_name) { - $controller_name = 'FreshExtension_' . $base_name . '_Controller'; - $filename = $this->path . '/controllers/' . $base_name . 'Controller.php'; - - Minz_Dispatcher::registerController($base_name, $controller_name, $filename); + Minz_Dispatcher::registerController($base_name, $this->path); } } diff --git a/lib/Minz/View.php b/lib/Minz/View.php index b40448491..bdfbbe63c 100644 --- a/lib/Minz/View.php +++ b/lib/Minz/View.php @@ -12,6 +12,7 @@ class Minz_View { const LAYOUT_PATH_NAME = '/layout'; const LAYOUT_FILENAME = '/layout.phtml'; + private $base_pathname = APP_PATH; private $view_filename = ''; private $use_layout = null; @@ -35,12 +36,15 @@ class Minz_View { * Change le fichier de vue en fonction d'un controller / action */ public function change_view($controller_name, $action_name) { - $this->view_filename = APP_PATH - . self::VIEWS_PATH_NAME . '/' + $this->view_filename = self::VIEWS_PATH_NAME . '/' . $controller_name . '/' . $action_name . '.phtml'; } + public function setBasePathname($base_pathname) { + $this->base_pathname = $base_pathname; + } + /** * Construit la vue */ @@ -70,7 +74,7 @@ class Minz_View { * Affiche la Vue en elle-même */ public function render () { - if ((include($this->view_filename)) === false) { + if ((include($this->base_pathname . $this->view_filename)) === false) { Minz_Log::notice('File not found: `' . $this->view_filename . '`'); } } -- cgit v1.2.3 From a08c382e0651f22a7db06feba225f3d49289763d Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Sat, 6 Dec 2014 15:20:20 +0100 Subject: Separate views registration from controllers one. - Add an Extension->registerViews() method. - Views are first searched in extension paths, then in APP_PATH. - It gives a way to override easily existing controllers / views. - Change include into an include_once in Dispatcher for new controllers. See https://github.com/FreshRSS/FreshRSS/issues/252 --- lib/Minz/Dispatcher.php | 6 +----- lib/Minz/Extension.php | 7 +++++++ lib/Minz/View.php | 28 ++++++++++++++++++++++++---- 3 files changed, 32 insertions(+), 9 deletions(-) diff --git a/lib/Minz/Dispatcher.php b/lib/Minz/Dispatcher.php index edd59c7cc..125ce5757 100644 --- a/lib/Minz/Dispatcher.php +++ b/lib/Minz/Dispatcher.php @@ -95,10 +95,6 @@ class Minz_Dispatcher { Minz_Exception::ERROR ); } - - if (self::isRegistered($base_name)) { - $this->setViewPath($this->controller, $base_name); - } } /** @@ -154,7 +150,7 @@ class Minz_Dispatcher { private static function loadController($base_name) { $base_path = self::$registrations[$base_name]; $controller_filename = $base_path . '/controllers/' . $base_name . 'Controller.php'; - include($controller_filename); + include_once $controller_filename; } private static function setViewPath($controller, $base_name) { diff --git a/lib/Minz/Extension.php b/lib/Minz/Extension.php index 5a61ba2e0..490a5c5cb 100644 --- a/lib/Minz/Extension.php +++ b/lib/Minz/Extension.php @@ -146,4 +146,11 @@ class Minz_Extension { public function registerController($base_name) { Minz_Dispatcher::registerController($base_name, $this->path); } + + /** + * Register the views in order to be accessible by the application. + */ + public function registerViews() { + Minz_View::addBasePathname($this->path); + } } diff --git a/lib/Minz/View.php b/lib/Minz/View.php index bdfbbe63c..1bc2e862d 100644 --- a/lib/Minz/View.php +++ b/lib/Minz/View.php @@ -12,10 +12,10 @@ class Minz_View { const LAYOUT_PATH_NAME = '/layout'; const LAYOUT_FILENAME = '/layout.phtml'; - private $base_pathname = APP_PATH; private $view_filename = ''; private $use_layout = null; + private static $base_pathnames = array(APP_PATH); private static $title = ''; private static $styles = array (); private static $scripts = array (); @@ -41,8 +41,15 @@ class Minz_View { . $action_name . '.phtml'; } - public function setBasePathname($base_pathname) { - $this->base_pathname = $base_pathname; + /** + * Add a base pathname to search views. + * + * New pathnames will be added at the beginning of the list. + * + * @param $base_pathname the new base pathname. + */ + public static function addBasePathname($base_pathname) { + array_unshift(self::$base_pathnames, $base_pathname); } /** @@ -74,7 +81,20 @@ class Minz_View { * Affiche la Vue en elle-même */ public function render () { - if ((include($this->base_pathname . $this->view_filename)) === false) { + $view_found = false; + + // We search the view in the list of base pathnames. Only the first view + // found is considered. + foreach (self::$base_pathnames as $base) { + $filename = $base . $this->view_filename; + if (file_exists($filename)) { + include $filename; + $view_found = true; + break; + } + } + + if (!$view_found) { Minz_Log::notice('File not found: `' . $this->view_filename . '`'); } } -- 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(-) 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 4c888590e6f0fd89fc1dccebb5e815883eeaa54c Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Sat, 6 Dec 2014 16:39:10 +0100 Subject: Improve system/user types for extensions - system extensions can only be managed by an administrator - system extensions are loaded for all users (even if not logged) - user extensions are loaded for logged users only - system extensions loading is saved in global config.php file See https://github.com/FreshRSS/FreshRSS/issues/252 --- app/Controllers/extensionController.php | 42 ++++++++++++++++++++++----------- app/FreshRSS.php | 8 ++++--- app/views/extension/index.phtml | 4 ++++ lib/Minz/Configuration.php | 19 +++++++++++++-- 4 files changed, 54 insertions(+), 19 deletions(-) diff --git a/app/Controllers/extensionController.php b/app/Controllers/extensionController.php index 415f489a6..e348d9f31 100644 --- a/app/Controllers/extensionController.php +++ b/app/Controllers/extensionController.php @@ -52,16 +52,23 @@ class FreshRSS_extension_Controller extends Minz_ActionController { 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); - } + if ($ext->getType() === 'system' && FreshRSS_Auth::hasAccess('admin')) { + $ext->install(); + + Minz_Configuration::addExtension($ext_name); + Minz_Configuration::writeFile(); - $ext->install(); + Minz_Request::good('feedback.extension.enabled', $url_redirect); + } elseif ($ext->getType() === 'user') { + $ext->install(); - FreshRSS_Context::$conf->addExtension($ext_name); - FreshRSS_Context::$conf->save(); + FreshRSS_Context::$conf->addExtension($ext_name); + FreshRSS_Context::$conf->save(); - Minz_Request::good('feedback.extension.enabled', $url_redirect); + Minz_Request::good('feedback.extension.enabled', $url_redirect); + } else { + Minz_Request::bad('feedback.extension.no_access', $url_redirect); + } } Minz_Request::forward($url_redirect, true); @@ -90,16 +97,23 @@ class FreshRSS_extension_Controller extends Minz_ActionController { 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); - } + if ($ext->getType() === 'system' && FreshRSS_Auth::hasAccess('admin')) { + $ext->uninstall(); + + Minz_Configuration::removeExtension($ext_name); + Minz_Configuration::writeFile(); - $ext->uninstall(); + Minz_Request::good('feedback.extension.disabled', $url_redirect); + } elseif ($ext->getType() === 'user') { + $ext->uninstall(); - FreshRSS_Context::$conf->removeExtension($ext_name); - FreshRSS_Context::$conf->save(); + FreshRSS_Context::$conf->removeExtension($ext_name); + FreshRSS_Context::$conf->save(); - Minz_Request::good('feedback.extension.disabled', $url_redirect); + Minz_Request::good('feedback.extension.disabled', $url_redirect); + } else { + Minz_Request::bad('feedback.extension.no_access', $url_redirect); + } } Minz_Request::forward($url_redirect, true); diff --git a/app/FreshRSS.php b/app/FreshRSS.php index dc7d0b375..b91dfcc46 100644 --- a/app/FreshRSS.php +++ b/app/FreshRSS.php @@ -29,9 +29,11 @@ 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); + // Enable extensions for the current (logged) user. + if (FreshRSS_Auth::hasAccess()) { + $ext_list = FreshRSS_Context::$conf->extensions_enabled; + Minz_ExtensionManager::enable_by_list($ext_list); + } // Init i18n. Minz_Session::_param('language', FreshRSS_Context::$conf->language); diff --git a/app/views/extension/index.phtml b/app/views/extension/index.phtml index c6b7c84a1..0be03d7b5 100644 --- a/app/views/extension/index.phtml +++ b/app/views/extension/index.phtml @@ -10,6 +10,7 @@ extension_list as $ext) { ?>
    • + getType() === 'user' || FreshRSS_Auth::hasAccess('admin')) { ?> getName()); ?>
      @@ -22,6 +23,9 @@
      + + +
    • getName(); ?>
    diff --git a/lib/Minz/Configuration.php b/lib/Minz/Configuration.php index 4d3ab0964..4a3221ef5 100644 --- a/lib/Minz/Configuration.php +++ b/lib/Minz/Configuration.php @@ -165,6 +165,19 @@ class Minz_Configuration { self::$unsafe_autologin_enabled = (bool)$value; } + public function removeExtension($ext_name) { + self::$extensions_enabled = array_diff( + self::$extensions_enabled, + array($ext_name) + ); + } + public function addExtension($ext_name) { + $found = array_search($ext_name, self::$extensions_enabled) !== false; + if (!$found) { + self::$extensions_enabled[] = $ext_name; + } + } + /** * Initialise les variables de configuration * @exception Minz_FileNotExistException si le CONF_PATH_NAME n'existe pas @@ -197,6 +210,7 @@ class Minz_Configuration { ), 'limits' => self::$limits, 'db' => self::$db, + 'extensions_enabled' => self::$extensions_enabled, ); @rename(DATA_PATH . self::CONF_PATH_NAME, DATA_PATH . self::CONF_PATH_NAME . '.bak.php'); $result = file_put_contents(DATA_PATH . self::CONF_PATH_NAME, " Date: Sat, 6 Dec 2014 16:48:13 +0100 Subject: Fix typo (extensions) - change feedback.extension into feedback.extensions - disable button is pushed by default See https://github.com/FreshRSS/FreshRSS/issues/252 --- app/Controllers/extensionController.php | 20 ++++++++++---------- app/views/extension/index.phtml | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/app/Controllers/extensionController.php b/app/Controllers/extensionController.php index e348d9f31..543398b05 100644 --- a/app/Controllers/extensionController.php +++ b/app/Controllers/extensionController.php @@ -45,11 +45,11 @@ class FreshRSS_extension_Controller extends Minz_ActionController { $ext = Minz_ExtensionManager::find_extension($ext_name); if (is_null($ext)) { - Minz_Request::bad('feedback.extension.not_found', $url_redirect); + Minz_Request::bad('feedback.extensions.not_found', $url_redirect); } if ($ext->is_enabled()) { - Minz_Request::bad('feedback.extension.already_enabled', $url_redirect); + Minz_Request::bad('feedback.extensions.already_enabled', $url_redirect); } if ($ext->getType() === 'system' && FreshRSS_Auth::hasAccess('admin')) { @@ -58,16 +58,16 @@ class FreshRSS_extension_Controller extends Minz_ActionController { Minz_Configuration::addExtension($ext_name); Minz_Configuration::writeFile(); - Minz_Request::good('feedback.extension.enabled', $url_redirect); + Minz_Request::good('feedback.extensions.enabled', $url_redirect); } elseif ($ext->getType() === 'user') { $ext->install(); FreshRSS_Context::$conf->addExtension($ext_name); FreshRSS_Context::$conf->save(); - Minz_Request::good('feedback.extension.enabled', $url_redirect); + Minz_Request::good('feedback.extensions.enabled', $url_redirect); } else { - Minz_Request::bad('feedback.extension.no_access', $url_redirect); + Minz_Request::bad('feedback.extensions.no_access', $url_redirect); } } @@ -90,11 +90,11 @@ class FreshRSS_extension_Controller extends Minz_ActionController { $ext = Minz_ExtensionManager::find_extension($ext_name); if (is_null($ext)) { - Minz_Request::bad('feedback.extension.not_found', $url_redirect); + Minz_Request::bad('feedback.extensions.not_found', $url_redirect); } if (!$ext->is_enabled()) { - Minz_Request::bad('feedback.extension.not_enabled', $url_redirect); + Minz_Request::bad('feedback.extensions.not_enabled', $url_redirect); } if ($ext->getType() === 'system' && FreshRSS_Auth::hasAccess('admin')) { @@ -103,16 +103,16 @@ class FreshRSS_extension_Controller extends Minz_ActionController { Minz_Configuration::removeExtension($ext_name); Minz_Configuration::writeFile(); - Minz_Request::good('feedback.extension.disabled', $url_redirect); + Minz_Request::good('feedback.extensions.disabled', $url_redirect); } elseif ($ext->getType() === 'user') { $ext->uninstall(); FreshRSS_Context::$conf->removeExtension($ext_name); FreshRSS_Context::$conf->save(); - Minz_Request::good('feedback.extension.disabled', $url_redirect); + Minz_Request::good('feedback.extensions.disabled', $url_redirect); } else { - Minz_Request::bad('feedback.extension.no_access', $url_redirect); + Minz_Request::bad('feedback.extensions.no_access', $url_redirect); } } diff --git a/app/views/extension/index.phtml b/app/views/extension/index.phtml index 0be03d7b5..142ee4bc2 100644 --- a/app/views/extension/index.phtml +++ b/app/views/extension/index.phtml @@ -15,7 +15,7 @@
    is_enabled()) { ?> - + -- cgit v1.2.3 From 2da7c05fa6768b95a5cd0bd1c8f9934bbff05a03 Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Sat, 6 Dec 2014 17:15:20 +0100 Subject: Update i18n (extensions) See https://github.com/FreshRSS/FreshRSS/issues/252 --- app/Controllers/extensionController.php | 30 ++++++++++++++++++++---------- app/i18n/en/admin.php | 5 +++++ app/i18n/en/feedback.php | 8 ++++++++ app/i18n/en/gen.php | 7 +++++++ app/i18n/fr/admin.php | 5 +++++ app/i18n/fr/feedback.php | 8 ++++++++ app/i18n/fr/gen.php | 7 +++++++ app/views/extension/index.phtml | 8 ++++---- 8 files changed, 64 insertions(+), 14 deletions(-) diff --git a/app/Controllers/extensionController.php b/app/Controllers/extensionController.php index 543398b05..35b001094 100644 --- a/app/Controllers/extensionController.php +++ b/app/Controllers/extensionController.php @@ -45,11 +45,13 @@ class FreshRSS_extension_Controller extends Minz_ActionController { $ext = Minz_ExtensionManager::find_extension($ext_name); if (is_null($ext)) { - Minz_Request::bad('feedback.extensions.not_found', $url_redirect); + Minz_Request::bad(_t('feedback.extensions.not_found', $ext_name), + $url_redirect); } if ($ext->is_enabled()) { - Minz_Request::bad('feedback.extensions.already_enabled', $url_redirect); + Minz_Request::bad(_t('feedback.extensions.already_enabled', $ext_name), + $url_redirect); } if ($ext->getType() === 'system' && FreshRSS_Auth::hasAccess('admin')) { @@ -58,16 +60,19 @@ class FreshRSS_extension_Controller extends Minz_ActionController { Minz_Configuration::addExtension($ext_name); Minz_Configuration::writeFile(); - Minz_Request::good('feedback.extensions.enabled', $url_redirect); + Minz_Request::good(_t('feedback.extensions.enabled', $ext_name), + $url_redirect); } elseif ($ext->getType() === 'user') { $ext->install(); FreshRSS_Context::$conf->addExtension($ext_name); FreshRSS_Context::$conf->save(); - Minz_Request::good('feedback.extensions.enabled', $url_redirect); + Minz_Request::good(_t('feedback.extensions.enabled', $ext_name), + $url_redirect); } else { - Minz_Request::bad('feedback.extensions.no_access', $url_redirect); + Minz_Request::bad(_t('feedback.extensions.no_access', $ext_name), + $url_redirect); } } @@ -90,11 +95,13 @@ class FreshRSS_extension_Controller extends Minz_ActionController { $ext = Minz_ExtensionManager::find_extension($ext_name); if (is_null($ext)) { - Minz_Request::bad('feedback.extensions.not_found', $url_redirect); + Minz_Request::bad(_t('feedback.extensions.not_found', $ext_name), + $url_redirect); } if (!$ext->is_enabled()) { - Minz_Request::bad('feedback.extensions.not_enabled', $url_redirect); + Minz_Request::bad(_t('feedback.extensions.not_enabled', $ext_name), + $url_redirect); } if ($ext->getType() === 'system' && FreshRSS_Auth::hasAccess('admin')) { @@ -103,16 +110,19 @@ class FreshRSS_extension_Controller extends Minz_ActionController { Minz_Configuration::removeExtension($ext_name); Minz_Configuration::writeFile(); - Minz_Request::good('feedback.extensions.disabled', $url_redirect); + Minz_Request::good(_t('feedback.extensions.disabled', $ext_name), + $url_redirect); } elseif ($ext->getType() === 'user') { $ext->uninstall(); FreshRSS_Context::$conf->removeExtension($ext_name); FreshRSS_Context::$conf->save(); - Minz_Request::good('feedback.extensions.disabled', $url_redirect); + Minz_Request::good(_t('feedback.extensions.disabled', $ext_name), + $url_redirect); } else { - Minz_Request::bad('feedback.extensions.no_access', $url_redirect); + Minz_Request::bad(_t('feedback.extensions.no_access', $ext_name), + $url_redirect); } } diff --git a/app/i18n/en/admin.php b/app/i18n/en/admin.php index 74f01ae06..d73775d96 100644 --- a/app/i18n/en/admin.php +++ b/app/i18n/en/admin.php @@ -86,6 +86,11 @@ return array( 'ok' => 'You have ZIP extension.', ), ), + 'extensions' => array( + 'empty_list' => 'There is no installed extension', + 'system' => 'System extension (you have no rights on it)', + 'title' => 'Extensions', + ), 'users' => array( 'articles_and_size' => '%s articles (%s)', ), diff --git a/app/i18n/en/feedback.php b/app/i18n/en/feedback.php index b3866f1dc..df1dc5725 100644 --- a/app/i18n/en/feedback.php +++ b/app/i18n/en/feedback.php @@ -1,6 +1,14 @@ array( + 'already_enabled' => '%s is already enabled', + 'disabled' => '%s is now disabled', + 'enabled' => '%s is now enabled', + 'no_access' => 'You have no access on %s', + 'not_enabled' => '%s is not enabled yet', + 'not_found' => '%s does not exist', + ), 'login' => array( 'error' => 'Login is invalid', 'success' => 'You are connected', diff --git a/app/i18n/en/gen.php b/app/i18n/en/gen.php index 9e06357bc..ba5f0c86d 100644 --- a/app/i18n/en/gen.php +++ b/app/i18n/en/gen.php @@ -190,10 +190,17 @@ return array( 'freshrss_installation' => 'Installation · FreshRSS', 'fri' => 'Fri', 'g+' => 'Google+', + 'actions' => array( + 'disable' => 'Disable', + 'enable' => 'Enable', + 'manage' => 'Manage', + 'remove' => 'Remove', + ), 'menu' => array( 'admin' => 'Administration', 'authentication' => 'Authentication', 'check_install' => 'Installation checking', + 'extensions' => 'Extensions', 'user_management' => 'Manage users', 'user_profile' => 'Profile', ), diff --git a/app/i18n/fr/admin.php b/app/i18n/fr/admin.php index ad1fae6c0..e46a5a7b0 100644 --- a/app/i18n/fr/admin.php +++ b/app/i18n/fr/admin.php @@ -86,6 +86,11 @@ return array( 'ok' => 'Vous disposez de l\'extension ZIP.', ), ), + 'extensions' => array( + 'empty_list' => 'Il n’y a aucune extension installée.', + 'system' => 'Extension système (vous n’avez aucun droit dessus)', + 'title' => 'Extensions', + ), 'users' => array( 'articles_and_size' => '%s articles (%s)', ), diff --git a/app/i18n/fr/feedback.php b/app/i18n/fr/feedback.php index f4bb7cccf..7d3ab9db7 100644 --- a/app/i18n/fr/feedback.php +++ b/app/i18n/fr/feedback.php @@ -1,6 +1,14 @@ array( + 'already_enabled' => '%s est déjà activée', + 'disabled' => '%s est désormais désactivée', + 'enabled' => '%s est désormais activée', + 'no_access' => 'Vous n’avez aucun accès sur %s', + 'not_enabled' => '%s n’est pas encore activée', + 'not_found' => '%s n’existe pas', + ), 'login' => array( 'error' => 'L’identifiant est invalide !', 'success' => 'Vous êtes désormais connecté', diff --git a/app/i18n/fr/gen.php b/app/i18n/fr/gen.php index 0b2395bd0..44d8fb837 100644 --- a/app/i18n/fr/gen.php +++ b/app/i18n/fr/gen.php @@ -191,10 +191,17 @@ return array( 'freshrss_installation' => 'Installation · FreshRSS', 'fri' => 'ven.', 'g+' => 'Google+', + 'actions' => array( + 'disable' => 'Désactiver', + 'enable' => 'Activer', + 'manage' => 'Gérer', + 'remove' => 'Supprimer', + ), 'menu' => array( 'admin' => 'Administration', 'authentication' => 'Authentification', 'check_install' => 'Vérification de l’installation', + 'extensions' => 'Extensions', 'user_management' => 'Gestion des utilisateurs', 'user_profile' => 'Profil', ), diff --git a/app/views/extension/index.phtml b/app/views/extension/index.phtml index 142ee4bc2..d34a84452 100644 --- a/app/views/extension/index.phtml +++ b/app/views/extension/index.phtml @@ -13,14 +13,14 @@ getType() === 'user' || FreshRSS_Auth::hasAccess('admin')) { ?> getName()); ?>
    - + is_enabled()) { ?> - + - + - +
    -- cgit v1.2.3 From bc81979a6b25554c4832d5ccb41b427023096463 Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Sat, 6 Dec 2014 17:25:01 +0100 Subject: Add default behaviour for configure / remove ext See https://github.com/FreshRSS/FreshRSS/issues/252 --- app/Controllers/extensionController.php | 7 ++++++- app/views/extension/configure.phtml | 12 ++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 app/views/extension/configure.phtml diff --git a/app/Controllers/extensionController.php b/app/Controllers/extensionController.php index 35b001094..a1e39af37 100644 --- a/app/Controllers/extensionController.php +++ b/app/Controllers/extensionController.php @@ -130,6 +130,11 @@ class FreshRSS_extension_Controller extends Minz_ActionController { } public function removeAction() { - + if (!FreshRSS_Auth::hasAccess('admin')) { + Minz_Error::error(403); + } + + $url_redirect = array('c' => 'extension', 'a' => 'index'); + Minz_Request::bad('not implemented yet!', $url_redirect); } } diff --git a/app/views/extension/configure.phtml b/app/views/extension/configure.phtml new file mode 100644 index 000000000..a79e9baac --- /dev/null +++ b/app/views/extension/configure.phtml @@ -0,0 +1,12 @@ +partial('aside_configure'); +} + +?> + +
    +

    Extension name

    + Not implemented yet! +
    \ No newline at end of file -- 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(-) 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(-) 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 7ef4d6c033d6d12a644b6cf39940591901fdcb3b Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Sun, 7 Dec 2014 14:39:02 +0100 Subject: Fix entry_before_insert hook The hook must be called also in: - feedController->addAction() - importExportController->importJson() See https://github.com/FreshRSS/FreshRSS/issues/252 --- app/Controllers/feedController.php | 13 ++++++++++--- app/Controllers/importExportController.php | 6 ++++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/app/Controllers/feedController.php b/app/Controllers/feedController.php index dce79c57a..0a7edbee3 100755 --- a/app/Controllers/feedController.php +++ b/app/Controllers/feedController.php @@ -174,10 +174,17 @@ class FreshRSS_feed_Controller extends Minz_ActionController { $feedDAO->beginTransaction(); foreach ($entries as $entry) { // Entries are added without any verification. + $entry->_feed($feed->id()); + $entry->_id(min(time(), $entry->date(true)) . uSecString()); + $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_feed'] = $feed->id(); - $values['id'] = min(time(), $entry->date(true)) . uSecString(); - $values['is_read'] = $is_read; $entryDAO->addEntry($values, $prepared_statement); } $feedDAO->updateLastUpdate($feed->id()); diff --git a/app/Controllers/importExportController.php b/app/Controllers/importExportController.php index 4e2dbd157..c67b30431 100644 --- a/app/Controllers/importExportController.php +++ b/app/Controllers/importExportController.php @@ -385,6 +385,12 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { $entry->_id(min(time(), $entry->date(true)) . uSecString()); $entry->_tags($tags); + $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(); $id = $this->entryDAO->addEntry($values, $prepared_statement); -- cgit v1.2.3 From ea849d7c68cc3de33825c1daafd06b9f8bbf747c Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Sun, 7 Dec 2014 15:03:42 +0100 Subject: Prepare better organization of view files for exts View files must be well-splitted to simplify work for extensions. See https://github.com/FreshRSS/FreshRSS/issues/252 --- app/views/helpers/index/normal/entry_bottom.phtml | 84 ++++++++++++ app/views/helpers/index/normal/entry_header.phtml | 40 ++++++ app/views/index/index.phtml | 23 ---- app/views/index/normal.phtml | 157 +++------------------- 4 files changed, 144 insertions(+), 160 deletions(-) create mode 100644 app/views/helpers/index/normal/entry_bottom.phtml create mode 100644 app/views/helpers/index/normal/entry_header.phtml diff --git a/app/views/helpers/index/normal/entry_bottom.phtml b/app/views/helpers/index/normal/entry_bottom.phtml new file mode 100644 index 000000000..704644f99 --- /dev/null +++ b/app/views/helpers/index/normal/entry_bottom.phtml @@ -0,0 +1,84 @@ +sharing; + } + + $bottomline_read = FreshRSS_Context::$conf->bottomline_read; + $bottomline_favorite = FreshRSS_Context::$conf->bottomline_favorite; + $bottomline_sharing = FreshRSS_Context::$conf->bottomline_sharing && (count($sharing) > 0); + $bottomline_tags = FreshRSS_Context::$conf->bottomline_tags; + $bottomline_date = FreshRSS_Context::$conf->bottomline_date; + $bottomline_link = FreshRSS_Context::$conf->bottomline_link; +?>
    • 'entry', 'a' => 'read', 'params' => array('id' => $this->entry->id())); + if ($this->entry->isRead()) { + $arUrl['params']['is_read'] = 0; + } + ?>entry->isRead() ? 'read' : 'unread'); ?>
    • 'entry', 'a' => 'bookmark', 'params' => array('id' => $this->entry->id())); + if ($this->entry->isFavorite()) { + $arUrl['params']['is_favorite'] = 0; + } + ?>entry->isFavorite() ? 'starred' : 'non-starred'); ?>
    • +
    • entry->link()); + $title = urlencode($this->entry->title() . ' · ' . $feed->name()); + ?> + +
    • entry->tags() : null; + if (!empty($tags)) { + ?>
    • + +
    • entry->date(); ?>
    • +
    diff --git a/app/views/helpers/index/normal/entry_header.phtml b/app/views/helpers/index/normal/entry_header.phtml new file mode 100644 index 000000000..b751b884f --- /dev/null +++ b/app/views/helpers/index/normal/entry_header.phtml @@ -0,0 +1,40 @@ +topline_read; + $topline_favorite = FreshRSS_Context::$conf->topline_favorite; + $topline_date = FreshRSS_Context::$conf->topline_date; + $topline_link = FreshRSS_Context::$conf->topline_link; +?>
    • 'entry', 'a' => 'read', 'params' => array('id' => $this->entry->id())); + if ($this->entry->isRead()) { + $arUrl['params']['is_read'] = 0; + } + ?>entry->isRead() ? 'read' : 'unread'); ?>
    • 'entry', 'a' => 'bookmark', 'params' => array('id' => $this->entry->id())); + if ($this->entry->isFavorite()) { + $arUrl['params']['is_favorite'] = 0; + } + ?>entry->isFavorite() ? 'starred' : 'non-starred'); ?>
    • categories, $this->entry->feed()); //We most likely already have the feed object in cache + if ($feed == null) { + $feed = $this->entry->feed(true); + if ($feed == null) { + $feed = FreshRSS_Feed::example(); + } + } + ?>
    • ✇ name(); ?>
    • +
    • entry->title(); ?>
    • +
    • entry->date(); ?> 
    • + +
    diff --git a/app/views/index/index.phtml b/app/views/index/index.phtml index 8b93461dd..e69de29bb 100644 --- a/app/views/index/index.phtml +++ b/app/views/index/index.phtml @@ -1,23 +0,0 @@ -renderHelper('view/normal_view'); - } elseif ($output === 'reader') { - $this->renderHelper('view/reader_view'); - } elseif ($output === 'rss') { - $this->renderHelper('view/rss_view'); - } else { - Minz_Request::_param('output', 'normal'); - $output = 'normal'; - $this->renderHelper('view/normal_view'); - } -} elseif ($output === 'rss') { - // token has already been checked in the controller so we can show the view - $this->renderHelper('view/rss_view'); -} else { - // Normally, it should not happen, but log it anyway - Minz_Log::error('Something is wrong in ' . __FILE__ . ' line ' . __LINE__); -} diff --git a/app/views/index/normal.phtml b/app/views/index/normal.phtml index 9cbd367d0..fd2289365 100644 --- a/app/views/index/normal.phtml +++ b/app/views/index/normal.phtml @@ -7,23 +7,8 @@ if (!empty($this->entries)) { $display_today = true; $display_yesterday = true; $display_others = true; - if (FreshRSS_Auth::hasAccess()) { - $sharing = FreshRSS_Context::$conf->sharing; - } else { - $sharing = array(); - } $hidePosts = !FreshRSS_Context::$conf->display_posts; $lazyload = FreshRSS_Context::$conf->lazyload; - $topline_read = FreshRSS_Context::$conf->topline_read; - $topline_favorite = FreshRSS_Context::$conf->topline_favorite; - $topline_date = FreshRSS_Context::$conf->topline_date; - $topline_link = FreshRSS_Context::$conf->topline_link; - $bottomline_read = FreshRSS_Context::$conf->bottomline_read; - $bottomline_favorite = FreshRSS_Context::$conf->bottomline_favorite; - $bottomline_sharing = FreshRSS_Context::$conf->bottomline_sharing && (count($sharing)); - $bottomline_tags = FreshRSS_Context::$conf->bottomline_tags; - $bottomline_date = FreshRSS_Context::$conf->bottomline_date; - $bottomline_link = FreshRSS_Context::$conf->bottomline_link; $content_width = FreshRSS_Context::$conf->content_width; @@ -35,12 +20,12 @@ if (!empty($this->entries)) {
    entries as $item) { - $item = Minz_ExtensionManager::callHook('entry_before_display', $item); - if (is_null($item)) { + $this->entry = Minz_ExtensionManager::callHook('entry_before_display', $item); + if (is_null($this->entry)) { continue; } - if ($display_today && $item->isDay(FreshRSS_Days::TODAY, $today)) { + if ($display_today && $this->entry->isDay(FreshRSS_Days::TODAY, $today)) { ?>
    entries)) { ?>
    isDay(FreshRSS_Days::YESTERDAY, $today)) { + if ($display_yesterday && $this->entry->isDay(FreshRSS_Days::YESTERDAY, $today)) { ?>
    entries)) { ?>
    isDay(FreshRSS_Days::BEFORE_YESTERDAY, $today)) { + if ($display_others && $this->entry->isDay(FreshRSS_Days::BEFORE_YESTERDAY, $today)) { ?>
    -
    • 'entry', 'a' => 'read', 'params' => array('id' => $item->id())); - if ($item->isRead()) { - $arUrl['params']['is_read'] = 0; - } - ?>isRead() ? 'read' : 'unread'); ?>
    • 'entry', 'a' => 'bookmark', 'params' => array('id' => $item->id())); - if ($item->isFavorite()) { - $arUrl['params']['is_favorite'] = 0; - } - ?>isFavorite() ? 'starred' : 'non-starred'); ?>
    • categories, $item->feed()); //We most likely already have the feed object in cache - if ($feed == null) { - $feed = $item->feed(true); - if ($feed == null) { - $feed = FreshRSS_Feed::example(); - } - } - ?>
    • ✇ name(); ?>
    • -
    • title(); ?>
    • -
    • date(); ?> 
    • - -
    + ?>
    renderHelper('index/normal/entry_header'); -
    + ?>
    -

    title(); ?>

    +

    entry->title(); ?>

    author(); + $author = $this->entry->author(); echo $author != '' ? '
    ' . _t('by_author', $author) . '
    ' : '', - $lazyload && $hidePosts ? lazyimg($item->content()) : $item->content(); + $lazyload && $hidePosts ? lazyimg($this->entry->content()) : $this->entry->content(); ?> -
    -
    • 'entry', 'a' => 'read', 'params' => array('id' => $item->id())); - if ($item->isRead()) { - $arUrl['params']['is_read'] = 0; - } - ?>isRead() ? 'read' : 'unread'); ?>
    • 'entry', 'a' => 'bookmark', 'params' => array('id' => $item->id())); - if ($item->isFavorite()) { - $arUrl['params']['is_favorite'] = 0; - } - ?>isFavorite() ? 'starred' : 'non-starred'); ?>
    • -
    • link()); - $title = urlencode($item->title() . ' · ' . $feed->name()); - ?> -
    • - - - -
    -
    - - tags() : null; - if (!empty($tags)) { - ?>
  • - -
  • date(); ?>
  • - -
    -
    - + $this->renderHelper('index/normal/entry_bottom'); - renderHelper('pagination'); ?> -
    + ?>
    +
    renderHelper('pagination'); +?>
    partial('nav_entries'); ?> -- cgit v1.2.3 From 198b154064a12cfbeefd494afaa69378e77ffce9 Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Sun, 7 Dec 2014 15:30:24 +0100 Subject: Fix View files inclusion. Now, each part of the view (layout, partials, helpers, views) is included based on the $base_pathnames attribute. Extensions can now override all of these files. See https://github.com/FreshRSS/FreshRSS/issues/252 --- lib/Minz/View.php | 62 ++++++++++++++++++++++++++----------------------------- 1 file changed, 29 insertions(+), 33 deletions(-) diff --git a/lib/Minz/View.php b/lib/Minz/View.php index 1bc2e862d..44034ae9a 100644 --- a/lib/Minz/View.php +++ b/lib/Minz/View.php @@ -13,7 +13,7 @@ class Minz_View { const LAYOUT_FILENAME = '/layout.phtml'; private $view_filename = ''; - private $use_layout = null; + private $use_layout = true; private static $base_pathnames = array(APP_PATH); private static $title = ''; @@ -56,9 +56,6 @@ class Minz_View { * Construit la vue */ public function build () { - if ($this->use_layout === null) { //TODO: avoid file_exists and require views to be explicit - $this->use_layout = file_exists (APP_PATH . self::LAYOUT_PATH_NAME . self::LAYOUT_FILENAME); - } if ($this->use_layout) { $this->buildLayout (); } else { @@ -66,35 +63,40 @@ class Minz_View { } } + /** + * Include a view file. + * + * The file is searched inside list of $base_pathnames. + * + * @param $filename the name of the file to include. + * @return true if the file has been included, false else. + */ + private function includeFile($filename) { + // We search the filename in the list of base pathnames. Only the first view + // found is considered. + foreach (self::$base_pathnames as $base) { + $absolute_filename = $base . $filename; + if (file_exists($absolute_filename)) { + include $absolute_filename; + return true; + } + } + + return false; + } + /** * Construit le layout */ public function buildLayout () { - include ( - APP_PATH - . self::LAYOUT_PATH_NAME - . self::LAYOUT_FILENAME - ); + $this->includeFile(self::LAYOUT_PATH_NAME . self::LAYOUT_FILENAME); } /** * Affiche la Vue en elle-même */ public function render () { - $view_found = false; - - // We search the view in the list of base pathnames. Only the first view - // found is considered. - foreach (self::$base_pathnames as $base) { - $filename = $base . $this->view_filename; - if (file_exists($filename)) { - include $filename; - $view_found = true; - break; - } - } - - if (!$view_found) { + if (!$this->includeFile($this->view_filename)) { Minz_Log::notice('File not found: `' . $this->view_filename . '`'); } } @@ -104,11 +106,8 @@ class Minz_View { * @param $part l'élément partial à ajouter */ public function partial ($part) { - $fic_partial = APP_PATH - . self::LAYOUT_PATH_NAME . '/' - . $part . '.phtml'; - - if ((include($fic_partial)) === false) { + $fic_partial = self::LAYOUT_PATH_NAME . '/' . $part . '.phtml'; + if (!$this->includeFile($fic_partial)) { Minz_Log::warning('File not found: `' . $fic_partial . '`'); } } @@ -118,11 +117,8 @@ class Minz_View { * @param $helper l'élément à afficher */ public function renderHelper ($helper) { - $fic_helper = APP_PATH - . '/views/helpers/' - . $helper . '.phtml'; - - if ((include($fic_helper)) === false) {; + $fic_helper = '/views/helpers/' . $helper . '.phtml'; + if (!$this->includeFile($fic_helper)) { Minz_Log::warning('File not found: `' . $fic_helper . '`'); } } -- cgit v1.2.3 From 2232d1e02a4bc9dbaa99cdbd22efad116ec01403 Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Sun, 7 Dec 2014 15:45:34 +0100 Subject: Load user extensions after all the global inits See https://github.com/FreshRSS/FreshRSS/issues/252 --- app/FreshRSS.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/FreshRSS.php b/app/FreshRSS.php index b91dfcc46..88fe60850 100644 --- a/app/FreshRSS.php +++ b/app/FreshRSS.php @@ -29,18 +29,18 @@ class FreshRSS extends Minz_FrontController { // Load context and configuration. FreshRSS_Context::init(); - // Enable extensions for the current (logged) user. - if (FreshRSS_Auth::hasAccess()) { - $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(); $this->loadStylesAndScripts(); $this->loadNotifications(); + + // Enable extensions for the current (logged) user. + if (FreshRSS_Auth::hasAccess()) { + $ext_list = FreshRSS_Context::$conf->extensions_enabled; + Minz_ExtensionManager::enable_by_list($ext_list); + } } private function loadStylesAndScripts() { -- cgit v1.2.3 From c6dfec3ad351ee3b828c6a2c0a273bad5d9ac0df Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Mon, 8 Dec 2014 12:01:47 +0100 Subject: Add behaviour to configure action (extensions) - Put extension configure view in dir_ext/configure.phtml - Handle POST action in Extension->handleConfigureAction() method See https://github.com/FreshRSS/FreshRSS/issues/252 --- app/Controllers/extensionController.php | 16 ++++++++++++++++ app/views/extension/configure.phtml | 17 ++++++++++++++--- lib/Minz/Extension.php | 21 +++++++++++++++++++++ 3 files changed, 51 insertions(+), 3 deletions(-) diff --git a/app/Controllers/extensionController.php b/app/Controllers/extensionController.php index a1e39af37..73b8070cb 100644 --- a/app/Controllers/extensionController.php +++ b/app/Controllers/extensionController.php @@ -27,6 +27,22 @@ class FreshRSS_extension_Controller extends Minz_ActionController { if (Minz_Request::param('ajax')) { $this->view->_useLayout(false); } + + $ext_name = urldecode(Minz_Request::param('e')); + $ext = Minz_ExtensionManager::find_extension($ext_name); + + if (is_null($ext)) { + Minz_Error::error(404); + } + if ($ext->getType() === 'system' && !FreshRSS_Auth::hasAccess('admin')) { + Minz_Error::error(403); + } + + $this->view->extension = $ext; + + if (Minz_Request::isPost()) { + $this->view->extension->handleConfigureAction(); + } } /** diff --git a/app/views/extension/configure.phtml b/app/views/extension/configure.phtml index a79e9baac..295080d5e 100644 --- a/app/views/extension/configure.phtml +++ b/app/views/extension/configure.phtml @@ -7,6 +7,17 @@ if (!Minz_Request::param('ajax')) { ?>
    -

    Extension name

    - Not implemented yet! -
    \ No newline at end of file +

    extension->getName(); ?> (extension->getVersion(); ?>) — extension->getType(); ?>

    + +

    extension->getDescription(); ?> — extension->getAuthor(); ?>

    + +

    + extension->getConfigureView(); + if ($configure_view !== false) { + echo $configure_view; + } else { + ?> +

    + +
    diff --git a/lib/Minz/Extension.php b/lib/Minz/Extension.php index c93ba2520..1d706ed80 100644 --- a/lib/Minz/Extension.php +++ b/lib/Minz/Extension.php @@ -86,6 +86,27 @@ class Minz_Extension { return $this->is_enabled; } + /** + * Return the content of the configure view for the current extension. + * + * @return the html content from ext_dir/configure.phtml, false if it does + * not exist. + */ + public function getConfigureView() { + $filename = $this->path . '/configure.phtml'; + if (!file_exists($filename)) { + return false; + } + return @file_get_contents($filename); + } + + /** + * Handle the configure POST action. + * + * It must be redefined by child classes. + */ + public function handleConfigureAction() {} + /** * Getters and setters. */ -- 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(-) 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 28c77f22900a10ac43c6d61b2b2d1a146feac42a Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Mon, 8 Dec 2014 13:12:12 +0100 Subject: Fix a bug with feed_before_insert hook $feed->name() was called on a null value. See https://github.com/FreshRSS/FreshRSS/issues/252 --- app/Controllers/feedController.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/Controllers/feedController.php b/app/Controllers/feedController.php index 7dda3840e..379b8d8da 100755 --- a/app/Controllers/feedController.php +++ b/app/Controllers/feedController.php @@ -139,9 +139,10 @@ class FreshRSS_feed_Controller extends Minz_ActionController { $feed->_httpAuth($http_auth); // Call the extension hook + $name = $feed->name(); $feed = Minz_ExtensionManager::callHook('feed_before_insert', $feed); if (is_null($feed)) { - Minz_Request::bad(_t('feed_not_added', $feed->name()), $url_redirect); + Minz_Request::bad(_t('feed_not_added', $name), $url_redirect); } $values = array( -- cgit v1.2.3 From 0543f96a97fa860fdec38d61b117e6b5addf94b6 Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Mon, 8 Dec 2014 13:22:11 +0100 Subject: Update comments for ExtensionController See https://github.com/FreshRSS/FreshRSS/issues/252 --- app/Controllers/extensionController.php | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/app/Controllers/extensionController.php b/app/Controllers/extensionController.php index 73b8070cb..cd56de9eb 100644 --- a/app/Controllers/extensionController.php +++ b/app/Controllers/extensionController.php @@ -23,6 +23,16 @@ class FreshRSS_extension_Controller extends Minz_ActionController { $this->view->extension_list = Minz_ExtensionManager::list_extensions(); } + /** + * This action handles configuration of a given extension. + * + * Only administrator can configure a system extension. + * + * Parameters are: + * - e: the extension name (urlencoded) + * - additional parameters which should be handle by the extension + * handleConfigureAction() method (POST request). + */ public function configureAction() { if (Minz_Request::param('ajax')) { $this->view->_useLayout(false); @@ -49,6 +59,7 @@ 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. + * This action must be reached by a POST request. * * Parameter is: * - e: the extension name (urlencoded). @@ -99,6 +110,7 @@ class FreshRSS_extension_Controller extends Minz_ActionController { * This action disables an enabled extension for the current user. * * System extensions can only be disabled by an administrator. + * This action must be reached by a POST request. * * Parameter is: * - e: the extension name (urlencoded). @@ -145,6 +157,15 @@ class FreshRSS_extension_Controller extends Minz_ActionController { Minz_Request::forward($url_redirect, true); } + /** + * This action handles deletion of an extension. + * + * Only administrator can remove an extension. + * This action must be reached by a POST request. + * + * Parameter is: + * -e: extension name (urlencoded) + */ public function removeAction() { if (!FreshRSS_Auth::hasAccess('admin')) { Minz_Error::error(403); -- cgit v1.2.3 From 76358846abe2eba95668d66d3847cbdfe3f8bcdc Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Mon, 8 Dec 2014 13:36:08 +0100 Subject: Implement extension deletion See https://github.com/FreshRSS/FreshRSS/issues/252 --- app/Controllers/extensionController.php | 22 +++++++++++++++++++++- lib/lib_rss.php | 26 ++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/app/Controllers/extensionController.php b/app/Controllers/extensionController.php index cd56de9eb..adb3e1864 100644 --- a/app/Controllers/extensionController.php +++ b/app/Controllers/extensionController.php @@ -172,6 +172,26 @@ class FreshRSS_extension_Controller extends Minz_ActionController { } $url_redirect = array('c' => 'extension', 'a' => 'index'); - Minz_Request::bad('not implemented yet!', $url_redirect); + + 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(_t('feedback.extensions.not_found', $ext_name), + $url_redirect); + } + + $res = recursive_unlink($ext->getPath()); + if ($res) { + Minz_Request::good(_t('feedback.extensions.removed', $ext_name), + $url_redirect); + } else { + Minz_Request::bad(_t('feedback.extensions.cannot_delete', $ext_name), + $url_redirect); + } + } + + Minz_Request::forward($url_redirect, true); } } diff --git a/lib/lib_rss.php b/lib/lib_rss.php index 8170c7fd9..e466bcb15 100644 --- a/lib/lib_rss.php +++ b/lib/lib_rss.php @@ -319,3 +319,29 @@ function check_install_database() { return $status; } + +/** + * Remove a directory recursively. + * + * From http://php.net/rmdir#110489 + * + * @param $dir the directory to remove + */ +function recursive_unlink($dir) { + if (!is_dir($dir)) { + return true; + } + + $files = array_diff(scandir($dir), array('.', '..')); + foreach ($files as $filename) { + $filename = $dir . '/' . $filename; + if (is_dir($filename)) { + @chmod($filename, 0777); + recursive_unlink($filename); + } else { + unlink($filename); + } + } + + return rmdir($dir); +} -- cgit v1.2.3 From 67aa7e76c186e43a5e0290ed2c512791c7a3005c Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Sat, 13 Dec 2014 10:01:31 +0100 Subject: Fix strings and url on auth configuration page --- app/i18n/en/admin.php | 2 +- app/i18n/fr/admin.php | 2 +- app/views/auth/index.phtml | 5 +++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/app/i18n/en/admin.php b/app/i18n/en/admin.php index 26af23225..083cabfed 100644 --- a/app/i18n/en/admin.php +++ b/app/i18n/en/admin.php @@ -12,7 +12,7 @@ return array( 'title' => 'Authentication', 'title_reset' => 'Authentication reset', 'token' => 'Authentication token', - 'token_help' => 'Allows to access RSS output of the default user without authentication.
    %s?output=rss&token=%s', + 'token_help' => 'Allows to access RSS output of the default user without authentication:', 'type' => 'Authentication method', 'unsafe_autologin' => 'Allow unsafe automatic login using the format: ', ), diff --git a/app/i18n/fr/admin.php b/app/i18n/fr/admin.php index a0d173b0c..1d327a33c 100644 --- a/app/i18n/fr/admin.php +++ b/app/i18n/fr/admin.php @@ -12,7 +12,7 @@ return array( 'title' => 'Authentification', 'title_reset' => 'Réinitialisation de l’authentification', 'token' => 'Jeton d’identification', - 'token_help' => 'Permet d’accéder à la sortie RSS de l’utilisateur par défaut sans besoin de s’authentifier.
    %s?output=rss&token=%s', + 'token_help' => 'Permet d’accéder à la sortie RSS de l’utilisateur par défaut sans besoin de s’authentifier :', 'type' => 'Méthode d’authentification', 'unsafe_autologin' => 'Autoriser les connexions automatiques non-sûres au format : ', ), diff --git a/app/views/auth/index.phtml b/app/views/auth/index.phtml index 420937042..ee1e2d8b9 100644 --- a/app/views/auth/index.phtml +++ b/app/views/auth/index.phtml @@ -47,7 +47,7 @@ /> - p/i/?a=formLogin&u=Alice&p=1234 + 'auth', 'a' => 'login', 'params' => array('u' => 'alice', 'p' => '1234')), 'html', true); ?>
    @@ -59,7 +59,8 @@
    /> - + + array('output' => 'rss', 'token' => $token)), 'html', true); ?>
    -- cgit v1.2.3 From 7f4ca35fc331ca4ce5e097d574e99417da0ef73e Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Wed, 17 Dec 2014 17:21:41 +0100 Subject: Fix i18n strings for sharing Fix https://github.com/FreshRSS/FreshRSS/issues/728 --- app/Controllers/entryController.php | 2 -- app/Controllers/importExportController.php | 2 -- app/views/index/normal.phtml | 8 ++++++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/Controllers/entryController.php b/app/Controllers/entryController.php index c894ae9aa..aae08c413 100755 --- a/app/Controllers/entryController.php +++ b/app/Controllers/entryController.php @@ -34,8 +34,6 @@ class FreshRSS_entry_Controller extends Minz_ActionController { * - nextGet (default: $get) * - idMax (default: 0) * - is_read (default: true) - * - * @todo nextGet system should not be present here... or should be? */ public function readAction() { $id = Minz_Request::param('id'); diff --git a/app/Controllers/importExportController.php b/app/Controllers/importExportController.php index f29051f34..6eefa0f6f 100644 --- a/app/Controllers/importExportController.php +++ b/app/Controllers/importExportController.php @@ -125,8 +125,6 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { * * Itis a *very* basic guess file type function. Only based on filename. * That's could be improved but should be enough for what we have to do. - * - * @todo move into lib_rss.php */ private function guessFileType($filename) { if (substr_compare($filename, '.zip', -4) === 0) { diff --git a/app/views/index/normal.phtml b/app/views/index/normal.phtml index 3a27a702b..66111397c 100644 --- a/app/views/index/normal.phtml +++ b/app/views/index/normal.phtml @@ -140,10 +140,14 @@ if (!empty($this->entries)) {