From 27764b36353b3066a9e92da2a96ac17b546295be Mon Sep 17 00:00:00 2001 From: Alexis Degrugillier Date: Sat, 22 Feb 2014 16:53:51 -0500 Subject: Improve sharing Previously, the share page can handle only a limited number of shares and only one of each type. Now the configuration has been change to be more flexible and allows an unlimited number of shares. The share description is located in an array and the share configuration is stored along with the user configuration. Note: I tried to include the specific javascript code in a separate file but I did not succeded to import it. --- app/Controllers/configureController.php | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) (limited to 'app/Controllers') diff --git a/app/Controllers/configureController.php b/app/Controllers/configureController.php index ad8bc546a..104fa8900 100755 --- a/app/Controllers/configureController.php +++ b/app/Controllers/configureController.php @@ -192,16 +192,8 @@ class FreshRSS_configure_Controller extends Minz_ActionController { public function sharingAction () { if (Minz_Request::isPost ()) { - $this->view->conf->_sharing (array( - 'shaarli' => Minz_Request::param ('shaarli', false), - 'wallabag' => Minz_Request::param ('wallabag', false), - 'diaspora' => Minz_Request::param ('diaspora', false), - 'twitter' => Minz_Request::param ('twitter', false), - 'g+' => Minz_Request::param ('g+', false), - 'facebook' => Minz_Request::param ('facebook', false), - 'email' => Minz_Request::param ('email', false), - 'print' => Minz_Request::param ('print', false), - )); + $params = Minz_Request::params(); + $this->view->conf->_sharing ($params['share']); $this->view->conf->save(); invalidateHttpCache(); -- cgit v1.2.3 From 1e105a64ab12b37f55b605a89e869f7bbb520e95 Mon Sep 17 00:00:00 2001 From: Alexis Degrugillier Date: Mon, 24 Feb 2014 19:51:57 -0500 Subject: Add a configuration option to get sticky articles Now the user can choose if an opened article will be repositioned to the top or not See #338 --- app/Controllers/configureController.php | 1 + app/Models/Configuration.php | 4 ++++ app/i18n/en.php | 1 + app/i18n/fr.php | 1 + app/views/configure/display.phtml | 10 ++++++++++ app/views/helpers/javascript_vars.phtml | 3 ++- p/scripts/main.js | 34 +++++++++++++++++---------------- 7 files changed, 37 insertions(+), 17 deletions(-) (limited to 'app/Controllers') diff --git a/app/Controllers/configureController.php b/app/Controllers/configureController.php index 104fa8900..f831f25d0 100755 --- a/app/Controllers/configureController.php +++ b/app/Controllers/configureController.php @@ -148,6 +148,7 @@ class FreshRSS_configure_Controller extends Minz_ActionController { $this->view->conf->_display_posts(Minz_Request::param('display_posts', false)); $this->view->conf->_onread_jump_next(Minz_Request::param('onread_jump_next', false)); $this->view->conf->_lazyload (Minz_Request::param('lazyload', false)); + $this->view->conf->_sticky_post (Minz_Request::param('sticky_post', false)); $this->view->conf->_sort_order(Minz_Request::param('sort_order', 'DESC')); $this->view->conf->_mark_when (array( 'article' => Minz_Request::param('mark_open_article', false), diff --git a/app/Models/Configuration.php b/app/Models/Configuration.php index bacb79510..48efe3bf6 100644 --- a/app/Models/Configuration.php +++ b/app/Models/Configuration.php @@ -17,6 +17,7 @@ class FreshRSS_Configuration { 'display_posts' => false, 'onread_jump_next' => true, 'lazyload' => true, + 'sticky_post' => true, 'sort_order' => 'DESC', 'anon_access' => false, 'mark_when' => array( @@ -140,6 +141,9 @@ class FreshRSS_Configuration { public function _lazyload ($value) { $this->data['lazyload'] = ((bool)$value) && $value !== 'no'; } + public function _sticky_post($value) { + $this->data['sticky_post'] = ((bool)$value) && $value !== 'no'; + } public function _sort_order ($value) { $this->data['sort_order'] = $value === 'ASC' ? 'ASC' : 'DESC'; } diff --git a/app/i18n/en.php b/app/i18n/en.php index 69c2fd310..e67447520 100644 --- a/app/i18n/en.php +++ b/app/i18n/en.php @@ -215,6 +215,7 @@ return array ( 'top_line' => 'Top line', 'bottom_line' => 'Bottom line', 'img_with_lazyload' => 'Use "lazy load" mode to load pictures', + 'sticky_post' => 'Stick the article to the top when opened', 'auto_read_when' => 'Mark article as read…', 'article_selected' => 'when article is selected', 'article_open_on_website' => 'when article is opened on its original website', diff --git a/app/i18n/fr.php b/app/i18n/fr.php index 452c560a7..2bd4fabab 100644 --- a/app/i18n/fr.php +++ b/app/i18n/fr.php @@ -215,6 +215,7 @@ return array ( 'top_line' => 'Ligne du haut', 'bottom_line' => 'Ligne du bas', 'img_with_lazyload' => 'Utiliser le mode “chargement différé” pour les images', + 'sticky_post' => 'Aligner l’article en haut quand il est ouvert', 'auto_read_when' => 'Marquer un article comme lu…', 'article_selected' => 'lorsque l’article est sélectionné', 'article_open_on_website' => 'lorsque l’article est ouvert sur le site d’origine', diff --git a/app/views/configure/display.phtml b/app/views/configure/display.phtml index 9104e4ef1..846db4b4c 100644 --- a/app/views/configure/display.phtml +++ b/app/views/configure/display.phtml @@ -110,6 +110,16 @@ +
+
+ +
+
+
diff --git a/app/views/helpers/javascript_vars.phtml b/app/views/helpers/javascript_vars.phtml index afe0ab258..2ce11d7d7 100644 --- a/app/views/helpers/javascript_vars.phtml +++ b/app/views/helpers/javascript_vars.phtml @@ -11,7 +11,8 @@ echo 'var ', ',auto_mark_scroll=', $mark['scroll'] ? 'true' : 'false', ',auto_load_more=', $this->conf->auto_load_more ? 'true' : 'false', ',full_lazyload=', $this->conf->lazyload && ($this->conf->display_posts || Minz_Request::param('output') === 'reader') ? 'true' : 'false', - ',does_lazyload=', $this->conf->lazyload ? 'true' : 'false'; + ',does_lazyload=', $this->conf->lazyload ? 'true' : 'false', + ',sticky_post=', $this->conf->sticky_post ? 'true' : 'false'; $s = $this->conf->shortcuts; echo ',shortcuts={', diff --git a/p/scripts/main.js b/p/scripts/main.js index ec27bd50c..6cc992379 100644 --- a/p/scripts/main.js +++ b/p/scripts/main.js @@ -238,26 +238,28 @@ function toggleContent(new_active, old_active) { var new_pos = new_active.position().top, old_scroll = $(box_to_move).scrollTop(); - if (hide_posts) { + if (sticky_post) { + if (hide_posts) { - new_pos = new_active.position().top; - old_scroll = $(box_to_move).scrollTop(); + new_pos = new_active.position().top; + old_scroll = $(box_to_move).scrollTop(); - if (relative_move) { - new_pos += old_scroll; - } + if (relative_move) { + new_pos += old_scroll; + } - if (old_active[0] !== new_active[0]) { - new_active.children(".flux_content").first().each(function () { - $(box_to_move).scrollTop(new_pos).scrollTop(); - }); - } - } else { - if (relative_move) { - new_pos += old_scroll; - } + if (old_active[0] !== new_active[0]) { + new_active.children(".flux_content").first().each(function () { + $(box_to_move).scrollTop(new_pos).scrollTop(); + }); + } + } else { + if (relative_move) { + new_pos += old_scroll; + } - $(box_to_move).scrollTop(new_pos).scrollTop(); + $(box_to_move).scrollTop(new_pos).scrollTop(); + } } if (auto_mark_article) { -- cgit v1.2.3 From f44683b5671b323ba96f0c4cd47ba9458e934679 Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Fri, 28 Feb 2014 20:22:50 +0100 Subject: API streamContents for categories and feeds https://github.com/marienfressinaud/FreshRSS/issues/13 --- app/Controllers/configureController.php | 2 +- app/Models/CategoryDAO.php | 6 +++--- app/Models/EntryDAO.php | 4 ++-- app/Models/FeedDAO.php | 6 +++--- lib/lib_opml.php | 2 +- p/api/greader.php | 22 ++++++++++++++++------ 6 files changed, 26 insertions(+), 16 deletions(-) (limited to 'app/Controllers') diff --git a/app/Controllers/configureController.php b/app/Controllers/configureController.php index f831f25d0..41a7920f0 100755 --- a/app/Controllers/configureController.php +++ b/app/Controllers/configureController.php @@ -44,7 +44,7 @@ class FreshRSS_configure_Controller extends Minz_ActionController { 'name' => $cat->name (), ); - if ($catDAO->searchByName ($newCat) == false) { + if ($catDAO->searchByName ($newCat) == null) { $catDAO->addCategory ($values); } } diff --git a/app/Models/CategoryDAO.php b/app/Models/CategoryDAO.php index 5355228a5..f3c02e3e4 100644 --- a/app/Models/CategoryDAO.php +++ b/app/Models/CategoryDAO.php @@ -64,7 +64,7 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo { if (isset ($cat[0])) { return $cat[0]; } else { - return false; + return null; } } public function searchByName ($name) { @@ -80,7 +80,7 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo { if (isset ($cat[0])) { return $cat[0]; } else { - return false; + return null; } } @@ -120,7 +120,7 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo { public function checkDefault () { $def_cat = $this->searchById (1); - if ($def_cat === false) { + if ($def_cat == null) { $cat = new FreshRSS_Category (Minz_Translate::t ('default_category')); $cat->_id (1); diff --git a/app/Models/EntryDAO.php b/app/Models/EntryDAO.php index b25ae444b..3fc7a05ef 100644 --- a/app/Models/EntryDAO.php +++ b/app/Models/EntryDAO.php @@ -307,7 +307,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { $stm->execute ($values); $res = $stm->fetchAll (PDO::FETCH_ASSOC); $entries = self::daoToEntry ($res); - return isset ($entries[0]) ? $entries[0] : false; + return isset ($entries[0]) ? $entries[0] : null; } public function searchById ($id) { @@ -320,7 +320,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { $stm->execute ($values); $res = $stm->fetchAll (PDO::FETCH_ASSOC); $entries = self::daoToEntry ($res); - return isset ($entries[0]) ? $entries[0] : false; + return isset ($entries[0]) ? $entries[0] : null; } public function listWhere($type = 'a', $id = '', $state = 'all', $order = 'DESC', $limit = 1, $firstId = '', $filter = '', $date_min = 0, $keepHistoryDefault = 0) { diff --git a/app/Models/FeedDAO.php b/app/Models/FeedDAO.php index 79d8cff90..fb4a847a0 100644 --- a/app/Models/FeedDAO.php +++ b/app/Models/FeedDAO.php @@ -170,7 +170,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo { if (isset ($feed[$id])) { return $feed[$id]; } else { - return false; + return null; } } public function searchByUrl ($url) { @@ -186,7 +186,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo { if (isset ($feed)) { return $feed; } else { - return false; + return null; } } @@ -198,7 +198,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo { return self::daoToFeed ($stm->fetchAll (PDO::FETCH_ASSOC)); } - public function listCategoryNames() { + public function arrayCategoryNames() { $sql = 'SELECT f.id, c.name as c_name FROM `' . $this->prefix . 'feed` f ' . 'INNER JOIN `' . $this->prefix . 'category` c ON c.id = f.category'; $stm = $this->bd->prepare ($sql); diff --git a/lib/lib_opml.php b/lib/lib_opml.php index 9feb12ae0..05e54d85e 100644 --- a/lib/lib_opml.php +++ b/lib/lib_opml.php @@ -58,7 +58,7 @@ function opml_import ($xml) { $title = htmlspecialchars($title, ENT_COMPAT, 'UTF-8'); $catDAO = new FreshRSS_CategoryDAO (); $cat = $catDAO->searchByName ($title); - if ($cat === false) { + if ($cat == null) { $cat = new FreshRSS_Category ($title); $values = array ( 'name' => $cat->name () diff --git a/p/api/greader.php b/p/api/greader.php index 291bcdf1f..e99e1c0c8 100644 --- a/p/api/greader.php +++ b/p/api/greader.php @@ -266,7 +266,7 @@ function streamContents($path, $include_target, $start_time, $count, $order, $ex header('Content-Type: application/json; charset=UTF-8'); $feedDAO = new FreshRSS_FeedDAO(); - $feedCategoryNames = $feedDAO->listCategoryNames(); + $arrayFeedCategoryNames = $feedDAO->arrayCategoryNames(); switch ($path) { case 'reading-list': @@ -275,8 +275,14 @@ function streamContents($path, $include_target, $start_time, $count, $order, $ex case 'starred': $type = 's'; break; + case 'feed': + $type = 'f'; + break; case 'label': $type = 'c'; + $categoryDAO = new FreshRSS_CategoryDAO(); + $cat = $categoryDAO->searchByName($include_target); + $include_target = $cat == null ? -1 : $cat->id(); break; default: $type = 'A'; @@ -298,7 +304,7 @@ function streamContents($path, $include_target, $start_time, $count, $order, $ex $items = array(); foreach ($entries as $entry) { $f_id = $entry->feed(); - $c_name = isset($feedCategoryNames[$f_id]) ? $feedCategoryNames[$f_id] : '_'; + $c_name = isset($arrayFeedCategoryNames[$f_id]) ? $arrayFeedCategoryNames[$f_id] : '_'; $item = array( 'id' => /*'tag:google.com,2005:reader/item/' .*/ dec2hex($entry->id()), //64-bit hexa http://code.google.com/p/google-reader-api/wiki/ItemId 'crawlTimeMsec' => substr($entry->id(), 0, -3), @@ -352,9 +358,11 @@ function streamContentsItemsIds($streamId, $start_time, $count, $order, $exclude $type = 'f'; $id = basename($streamId); } elseif (strpos($streamId, 'user/-/label/') === 0) { - $type = 'C'; + $type = 'c'; $c_name = basename($streamId); - notImplemented(); //TODO + $categoryDAO = new FreshRSS_CategoryDAO(); + $cat = $categoryDAO->searchByName($c_name); + $id = $cat == null ? -1 : $cat->id(); } switch ($exclude_target) { @@ -441,7 +449,7 @@ function markAllAsRead($streamId, $olderThanId) { logMe('----------------------------------------------------------------'."\n"); logMe(print_r($debugInfo, true)); -$pathInfo = empty($_SERVER['PATH_INFO']) ? '/Error' : $_SERVER['PATH_INFO']; +$pathInfo = empty($_SERVER['PATH_INFO']) ? '/Error' : urldecode($_SERVER['PATH_INFO']); $pathInfos = explode('/', $pathInfo); logMe('pathInfos => ' . print_r($pathInfos, true)); @@ -471,7 +479,9 @@ if ($user != null) { Minz_Session::_param('currentUser', $user); -if (count($pathInfos)<3) badRequest(); +if (count($pathInfos) < 3) { + badRequest(); +} elseif ($pathInfos[1] === 'accounts') { if (($pathInfos[2] === 'ClientLogin') && isset($_REQUEST['Email']) && isset($_REQUEST['Passwd'])) clientLogin($_REQUEST['Email'], $_REQUEST['Passwd']); -- cgit v1.2.3 From 29b3bbfe284a6e56413a2e89b740ffc4172c6847 Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Sat, 1 Mar 2014 14:45:58 +0100 Subject: API: Real password system https://github.com/marienfressinaud/FreshRSS/issues/13 Expiring token not implemented yet --- app/Controllers/usersController.php | 12 +++++ app/Models/Configuration.php | 4 ++ app/i18n/en.php | 1 + app/i18n/fr.php | 1 + app/views/configure/users.phtml | 12 ++++- p/api/greader.php | 89 ++++++++++++++++++++++--------------- 6 files changed, 82 insertions(+), 37 deletions(-) (limited to 'app/Controllers') diff --git a/app/Controllers/usersController.php b/app/Controllers/usersController.php index bb4f34c5e..b03989cd7 100644 --- a/app/Controllers/usersController.php +++ b/app/Controllers/usersController.php @@ -32,6 +32,18 @@ class FreshRSS_users_Controller extends Minz_ActionController { } Minz_Session::_param('passwordHash', $this->view->conf->passwordHash); + $passwordPlain = Minz_Request::param('apiPasswordPlain', false); + if ($passwordPlain != '') { + if (!function_exists('password_hash')) { + include_once(LIB_PATH . '/password_compat.php'); + } + $passwordHash = password_hash($passwordPlain, PASSWORD_BCRYPT, array('cost' => self::BCRYPT_COST)); + $passwordPlain = ''; + $passwordHash = preg_replace('/^\$2[xy]\$/', '\$2a\$', $passwordHash); //Compatibility with bcrypt.js + $ok &= ($passwordHash != ''); + $this->view->conf->_apiPasswordHash($passwordHash); + } + if (Minz_Configuration::isAdmin(Minz_Session::param('currentUser', '_'))) { $this->view->conf->_mail_login(Minz_Request::param('mail_login', false)); } diff --git a/app/Models/Configuration.php b/app/Models/Configuration.php index 48efe3bf6..827a1d166 100644 --- a/app/Models/Configuration.php +++ b/app/Models/Configuration.php @@ -10,6 +10,7 @@ class FreshRSS_Configuration { 'mail_login' => '', 'token' => '', 'passwordHash' => '', //CRYPT_BLOWFISH + 'apiPasswordHash' => '', //CRYPT_BLOWFISH 'posts_per_page' => 20, 'view_mode' => 'normal', 'default_view' => 'not_read', @@ -165,6 +166,9 @@ class FreshRSS_Configuration { public function _passwordHash ($value) { $this->data['passwordHash'] = ctype_graph($value) && (strlen($value) >= 60) ? $value : ''; } + public function _apiPasswordHash ($value) { + $this->data['apiPasswordHash'] = ctype_graph($value) && (strlen($value) >= 60) ? $value : ''; + } public function _mail_login ($value) { $value = filter_var($value, FILTER_VALIDATE_EMAIL); if ($value) { diff --git a/app/i18n/en.php b/app/i18n/en.php index e67447520..d504ffc11 100644 --- a/app/i18n/en.php +++ b/app/i18n/en.php @@ -176,6 +176,7 @@ return array ( 'current_user' => 'Current user', 'default_user' => 'Username of the default user (maximum 16 alphanumeric characters)', 'password_form' => 'Password
(for the Web-form login method)', + 'password_api' => 'Password API
(e.g., for mobile apps)', 'persona_connection_email' => 'Login mail address
(for Mozilla Persona)', 'allow_anonymous' => 'Allow anonymous reading of the articles of the default user (%s)', 'allow_anonymous_refresh' => 'Allow anonymous refresh of the articles', diff --git a/app/i18n/fr.php b/app/i18n/fr.php index 2bd4fabab..c5581a78b 100644 --- a/app/i18n/fr.php +++ b/app/i18n/fr.php @@ -175,6 +175,7 @@ return array ( 'current_user' => 'Utilisateur actuel', 'password_form' => 'Mot de passe
(pour connexion par formulaire)', + 'password_api' => 'Mot de passe API
(ex. : pour applis mobiles)', 'default_user' => 'Nom de l’utilisateur par défaut (16 caractères alphanumériques maximum)', 'persona_connection_email' => 'Adresse courriel de connexion
(pour Mozilla Persona)', 'allow_anonymous' => 'Autoriser la lecture anonyme des articles de l’utilisateur par défaut (%s)', diff --git a/app/views/configure/users.phtml b/app/views/configure/users.phtml index 0677db881..f5c7dff17 100644 --- a/app/views/configure/users.phtml +++ b/app/views/configure/users.phtml @@ -20,7 +20,15 @@
- + /> + +
+
+ +
+ +
+ />
@@ -85,7 +93,7 @@ conf->token; ?>
- />
diff --git a/p/api/greader.php b/p/api/greader.php index e99e1c0c8..035a031dd 100644 --- a/p/api/greader.php +++ b/p/api/greader.php @@ -20,9 +20,6 @@ Server-side API compatible with Google Reader API layer 2 * https://github.com/theoldreader/api */ -define('TEMP_PASSWORD', 'temp123'); //Change to another ASCII password -define('TEMP_AUTH', 'XtofqkkOkCULRLH8'); //Change to another random ASCII auth - require('../../constants.php'); require(LIB_PATH . '/lib_rss.php'); //Includes class autoloader @@ -119,14 +116,28 @@ function checkCompatibility() { exit(); } -function authorizationToUser() { +function authorizationToUserConf() { $headerAuth = headerVariable('Authorization', 'GoogleLogin_auth'); //Input is 'GoogleLogin auth', but PHP replaces spaces by '_' http://php.net/language.variables.external if ($headerAuth != '') { $headerAuthX = explode('/', $headerAuth, 2); - if ((count($headerAuthX) === 2) && ($headerAuthX[1] === TEMP_AUTH)) { + if (count($headerAuthX) === 2) { $user = $headerAuthX[0]; if (ctype_alnum($user)) { - return $user; + try { + $conf = new FreshRSS_Configuration($user); + } catch (Exception $e) { + logMe($e->getMessage() . "\n"); + unauthorized(); + } + if ($headerAuthX[1] === sha1(Minz_Configuration::salt() . $conf->user . $conf->apiPasswordHash)) { + return $conf; + } else { + logMe('Invalid API authorisation for user ' . $user . ': ' . $headerAuthX[1] . "\n"); + Minz_Log::record('Invalid API authorisation for user ' . $user . ': ' . $headerAuthX[1], Minz_Log::WARNING); + unauthorized(); + } + } else { + badRequest(); } } } @@ -135,28 +146,45 @@ function authorizationToUser() { function clientLogin($email, $pass) { //http://web.archive.org/web/20130604091042/http://undoc.in/clientLogin.html logMe('clientLogin(' . $email . ")\n"); - if ($pass !== TEMP_PASSWORD) { - unauthorized(); + if (ctype_alnum($email)) { + if (!function_exists('password_verify')) { + include_once(LIB_PATH . '/password_compat.php'); + } + try { + $conf = new FreshRSS_Configuration($email); + } catch (Exception $e) { + logMe($e->getMessage() . "\n"); + Minz_Log::record('Invalid API user ' . $email, Minz_Log::WARNING); + unauthorized(); + } + if ($conf->apiPasswordHash != '' && password_verify($pass, $conf->apiPasswordHash)) { + header('Content-Type: text/plain; charset=UTF-8'); + $auth = $email . '/' . sha1(Minz_Configuration::salt() . $conf->user . $conf->apiPasswordHash); + echo 'SID=', $auth, "\n", + 'Auth=', $auth, "\n"; + exit(); + } else { + Minz_Log::record('Password API mismatch for user ' . $email, Minz_Log::WARNING); + unauthorized(); + } + } else { + badRequest(); } - header('Content-Type: text/plain; charset=UTF-8'); - $auth = $email . '/' . TEMP_AUTH; - echo 'SID=', $auth, "\n", - 'Auth=', $auth, "\n"; - exit(); + die(); } -function token($user) { +function token($conf) { //http://blog.martindoms.com/2009/08/15/using-the-google-reader-api-part-1/ https://github.com/ericmann/gReader-Library/blob/master/greader.class.php - logMe('token('. $user . ")\n"); - $token = 'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ01234'; //Must have 57 characters... + logMe('token('. $conf->user . ")\n"); //TODO: Implement real token that expires + $token = str_pad(sha1(Minz_Configuration::salt() . $conf->user . $conf->apiPasswordHash), 57, 'Z'); //Must have 57 characters echo $token, "\n"; exit(); } -function checkToken($user, $token) { +function checkToken($conf, $token) { //http://code.google.com/p/google-reader-api/wiki/ActionToken logMe('checkToken(' . $token . ")\n"); - if ($token === 'ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ01234') { + if ($token === str_pad(sha1(Minz_Configuration::salt() . $conf->user . $conf->apiPasswordHash), 57, 'Z')) { return true; } unauthorized(); @@ -462,32 +490,23 @@ if (!Minz_Configuration::apiEnabled()) { Minz_Session::init('FreshRSS'); -$user = authorizationToUser(); -$conf = null; +$conf = authorizationToUserConf(); +$user = $conf == null ? '' : $conf->user; logMe('User => ' . $user . "\n"); -if ($user != null) { - try { - $conf = new FreshRSS_Configuration($user); - } catch (Exception $e) { - logMe($e->getMessage()); - $user = null; - badRequest(); - } -} - Minz_Session::_param('currentUser', $user); if (count($pathInfos) < 3) { badRequest(); } elseif ($pathInfos[1] === 'accounts') { - if (($pathInfos[2] === 'ClientLogin') && isset($_REQUEST['Email']) && isset($_REQUEST['Passwd'])) + if (($pathInfos[2] === 'ClientLogin') && isset($_REQUEST['Email']) && isset($_REQUEST['Passwd'])) { clientLogin($_REQUEST['Email'], $_REQUEST['Passwd']); + } } elseif ($pathInfos[1] === 'reader' && $pathInfos[2] === 'api' && isset($pathInfos[3]) && $pathInfos[3] === '0' && isset($pathInfos[4])) { - if ($user == null) { + if ($user == '') { unauthorized(); } $timestamp = isset($_GET['ck']) ? intval($_GET['ck']) : 0; //ck=[unix timestamp] : Use the current Unix time here, helps Google with caching. @@ -543,7 +562,7 @@ elseif ($pathInfos[1] === 'reader' && $pathInfos[2] === 'api' && isset($pathInfo break; case 'edit-tag': //http://blog.martindoms.com/2010/01/20/using-the-google-reader-api-part-3/ $token = isset($_POST['T']) ? trim($_POST['T']) : ''; - checkToken($user, $token); + checkToken($conf, $token); $a = isset($_POST['a']) ? $_POST['a'] : ''; //Add: user/-/state/com.google/read user/-/state/com.google/starred $r = isset($_POST['r']) ? $_POST['r'] : ''; //Remove: user/-/state/com.google/read user/-/state/com.google/starred $e_ids = multiplePosts('i'); //item IDs @@ -551,7 +570,7 @@ elseif ($pathInfos[1] === 'reader' && $pathInfos[2] === 'api' && isset($pathInfo break; case 'mark-all-as-read': $token = isset($_POST['T']) ? trim($_POST['T']) : ''; - checkToken($user, $token); + checkToken($conf, $token); $streamId = $_POST['s']; //StreamId $ts = isset($_POST['ts']) ? $_POST['ts'] : '0'; //Older than timestamp in nanoseconds if (!ctype_digit($ts)) { @@ -560,7 +579,7 @@ elseif ($pathInfos[1] === 'reader' && $pathInfos[2] === 'api' && isset($pathInfo markAllAsRead($streamId, $ts); break; case 'token': - Token($user); + Token($conf); break; } } elseif ($pathInfos[1] === 'check' && $pathInfos[2] === 'compatibility') { -- cgit v1.2.3 From 71f7ce1be5833b54b0f4e1f37e6557425c364725 Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Sat, 1 Mar 2014 15:47:15 +0100 Subject: API: SQL optimisation https://github.com/marienfressinaud/FreshRSS/issues/13 --- app/Controllers/entryController.php | 4 +++- app/Models/EntryDAO.php | 43 ++++++++++++++++++++++++++++--------- app/Models/FeedDAO.php | 6 +++--- p/api/greader.php | 11 +++------- 4 files changed, 42 insertions(+), 22 deletions(-) (limited to 'app/Controllers') diff --git a/app/Controllers/entryController.php b/app/Controllers/entryController.php index 1756c91e5..ca7122a7c 100755 --- a/app/Controllers/entryController.php +++ b/app/Controllers/entryController.php @@ -137,11 +137,13 @@ class FreshRSS_entry_Controller extends Minz_ActionController { if ($nb > 0) { $nbTotal += $nb; Minz_Log::record($nb . ' old entries cleaned in feed [' . $feed->url() . ']', Minz_Log::DEBUG); - $feedDAO->updateLastUpdate($feed->id()); + //$feedDAO->updateLastUpdate($feed->id()); } } } + $feedDAO->updateCachedValues(); + invalidateHttpCache(); $notif = array( diff --git a/app/Models/EntryDAO.php b/app/Models/EntryDAO.php index 3fc7a05ef..e90b9a7fe 100644 --- a/app/Models/EntryDAO.php +++ b/app/Models/EntryDAO.php @@ -35,11 +35,15 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { } } - public function markFavorite ($id, $is_favorite = true) { + public function markFavorite($ids, $is_favorite = true) { + if (!is_array($ids)) { + $ids = array($ids); + } $sql = 'UPDATE `' . $this->prefix . 'entry` e ' . 'SET e.is_favorite = ? ' - . 'WHERE e.id=?'; - $values = array ($is_favorite ? 1 : 0, $id); + . 'WHERE e.id IN (' . str_repeat('?,', count($ids) - 1). '?)'; + $values = array ($is_favorite ? 1 : 0); + $values = array_merge($values, $ids); $stm = $this->bd->prepare ($sql); if ($stm && $stm->execute ($values)) { return $stm->rowCount(); @@ -49,6 +53,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { return false; } } + public function markRead ($id, $is_read = true) { $sql = 'UPDATE `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id ' . 'SET e.is_read = ?,' @@ -64,6 +69,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { return false; } } + public function markReadEntries ($idMax = 0, $onlyFavorites = false, $priorityMin = 0) { if ($idMax == 0) { $sql = 'UPDATE `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed = f.id ' @@ -323,7 +329,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { return isset ($entries[0]) ? $entries[0] : null; } - public function listWhere($type = 'a', $id = '', $state = 'all', $order = 'DESC', $limit = 1, $firstId = '', $filter = '', $date_min = 0, $keepHistoryDefault = 0) { + private function sqlListWhere($type = 'a', $id = '', $state = 'all', $order = 'DESC', $limit = 1, $firstId = '', $filter = '', $date_min = 0, $keepHistoryDefault = 0) { $where = ''; $joinFeed = false; $values = array(); @@ -432,14 +438,22 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { } } + return array($values, + 'SELECT e1.id FROM `' . $this->prefix . 'entry` e1 ' + . ($joinFeed ? 'INNER JOIN `' . $this->prefix . 'feed` f ON e1.id_feed = f.id ' : '') + . 'WHERE ' . $where + . $search + . 'ORDER BY e1.id ' . $order + . ($limit > 0 ? ' LIMIT ' . $limit : '')); //TODO: See http://explainextended.com/2009/10/23/mysql-order-by-limit-performance-late-row-lookups/ + } + + public function listWhere($type = 'a', $id = '', $state = 'all', $order = 'DESC', $limit = 1, $firstId = '', $filter = '', $date_min = 0, $keepHistoryDefault = 0) { + list($values, $sql) = $this->sqlListWhere($type, $id, $state, $order, $limit, $firstId, $filter, $date_min, $keepHistoryDefault); + $sql = 'SELECT e.id, e.guid, e.title, e.author, UNCOMPRESS(e.content_bin) AS content, e.link, e.date, e.is_read, e.is_favorite, e.id_feed, e.tags ' . 'FROM `' . $this->prefix . 'entry` e ' - . 'INNER JOIN (SELECT e1.id FROM `' . $this->prefix . 'entry` e1 ' - . ($joinFeed ? 'INNER JOIN `' . $this->prefix . 'feed` f ON e1.id_feed = f.id ' : '') - . 'WHERE ' . $where - . $search - . 'ORDER BY e1.id ' . $order - . ($limit > 0 ? ' LIMIT ' . $limit : '') //TODO: See http://explainextended.com/2009/10/23/mysql-order-by-limit-performance-late-row-lookups/ + . 'INNER JOIN (' + . $sql . ') e2 ON e2.id = e.id ' . 'ORDER BY e.id ' . $order; @@ -449,6 +463,15 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { return self::daoToEntry ($stm->fetchAll (PDO::FETCH_ASSOC)); } + public function listIdsWhere($type = 'a', $id = '', $state = 'all', $order = 'DESC', $limit = 1, $firstId = '', $filter = '', $date_min = 0, $keepHistoryDefault = 0) { + list($values, $sql) = $this->sqlListWhere($type, $id, $state, $order, $limit, $firstId, $filter, $date_min, $keepHistoryDefault); + + $stm = $this->bd->prepare($sql); + $stm->execute($values); + + return $stm->fetchAll(PDO::FETCH_COLUMN, 0); + } + public function listLastGuidsByFeed($id, $n) { $sql = 'SELECT guid FROM `' . $this->prefix . 'entry` WHERE id_feed=? ORDER BY id DESC LIMIT ' . intval($n); $stm = $this->bd->prepare ($sql); diff --git a/app/Models/FeedDAO.php b/app/Models/FeedDAO.php index fb4a847a0..f9e1ad9e8 100644 --- a/app/Models/FeedDAO.php +++ b/app/Models/FeedDAO.php @@ -242,6 +242,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo { return $res[0]['count']; } + public function countNotRead ($id) { $sql = 'SELECT COUNT(*) AS count FROM `' . $this->prefix . 'entry` WHERE id_feed=? AND is_read=0'; $stm = $this->bd->prepare ($sql); @@ -251,6 +252,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo { return $res[0]['count']; } + public function updateCachedValues () { //For one single feed, call updateLastUpdate($id) $sql = 'UPDATE `' . $this->prefix . 'feed` f ' . 'INNER JOIN (' @@ -263,9 +265,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo { . 'SET f.cache_nbEntries=x.nbEntries, f.cache_nbUnreads=x.nbUnreads'; $stm = $this->bd->prepare ($sql); - $values = array ($feed_id); - - if ($stm && $stm->execute ($values)) { + if ($stm && $stm->execute()) { return $stm->rowCount(); } else { $info = $stm->errorInfo(); diff --git a/p/api/greader.php b/p/api/greader.php index 035a031dd..d1846fdaf 100644 --- a/p/api/greader.php +++ b/p/api/greader.php @@ -403,17 +403,12 @@ function streamContentsItemsIds($streamId, $start_time, $count, $order, $exclude } $entryDAO = new FreshRSS_EntryDAO(); - $entries = $entryDAO->listWhere($type, $id, $state, $order === 'o' ? 'ASC' : 'DESC', $count, '', '', $start_time); + $ids = $entryDAO->listIdsWhere($type, $id, $state, $order === 'o' ? 'ASC' : 'DESC', $count, '', '', $start_time); $itemRefs = array(); - foreach ($entries as $entry) { - $f_id = $entry->feed(); + foreach ($ids as $id) { $itemRefs[] = array( - 'id' => $entry->id(), //64-bit decimal - //'timestampUsec' => $entry->dateAdded(true), - /*'directStreamIds' => array( - 'feed/' . $entry->feed() - ),*/ + 'id' => $id, //64-bit decimal ); } -- cgit v1.2.3 From 736ee492a0fe723b0d6ce9ea59fb3f3eef7ecf23 Mon Sep 17 00:00:00 2001 From: Alexis Degrugillier Date: Sat, 1 Mar 2014 14:30:55 -0500 Subject: Show only unread when selecting unread Before, when there was no unread articles, all articles where displayed. This behaviour was not the one intended. Now, when there is no unread articles, a message is displayed to alert that there is no article. See #428 Correction after comment --- app/Controllers/indexController.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'app/Controllers') diff --git a/app/Controllers/indexController.php b/app/Controllers/indexController.php index 38f4c0e7c..c760e0f01 100755 --- a/app/Controllers/indexController.php +++ b/app/Controllers/indexController.php @@ -84,6 +84,7 @@ class FreshRSS_index_Controller extends Minz_ActionController { // On récupère les différents éléments de filtrage $this->view->state = $state = Minz_Request::param ('state', $this->view->conf->default_view); + $state_param = Minz_Request::param ('state', null); $filter = Minz_Request::param ('search', ''); if (!empty($filter)) { $state = 'all'; //Search always in read and unread articles @@ -111,7 +112,7 @@ class FreshRSS_index_Controller extends Minz_ActionController { $hasUnread = true; break; } - if (!$hasUnread) { + if (!$hasUnread && is_null($state_param)) { $this->view->state = $state = 'all'; } } @@ -129,7 +130,7 @@ class FreshRSS_index_Controller extends Minz_ActionController { // Si on a récupéré aucun article "non lus" // on essaye de récupérer tous les articles - if ($state === 'not_read' && empty($entries)) { + if ($state === 'not_read' && empty($entries) && is_null($state_param)) { Minz_Log::record ('Conflicting information about nbNotRead!', Minz_Log::DEBUG); $this->view->state = 'all'; $entries = $entryDAO->listWhere($getType, $getId, 'all', $order, $nb, $first, $filter, $date_min, $keepHistoryDefault); -- cgit v1.2.3 From a8d06d12705a6467f2d11a8a6afc51ae2ea7088a Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Sat, 1 Mar 2014 20:56:27 +0100 Subject: Micro-optimisation is_null https://github.com/marienfressinaud/FreshRSS/pull/434 https://github.com/marienfressinaud/FreshRSS/issues/428 --- app/Controllers/indexController.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'app/Controllers') diff --git a/app/Controllers/indexController.php b/app/Controllers/indexController.php index c760e0f01..c83c5b630 100755 --- a/app/Controllers/indexController.php +++ b/app/Controllers/indexController.php @@ -112,7 +112,7 @@ class FreshRSS_index_Controller extends Minz_ActionController { $hasUnread = true; break; } - if (!$hasUnread && is_null($state_param)) { + if (!$hasUnread && ($state_param === null)) { $this->view->state = $state = 'all'; } } @@ -130,7 +130,7 @@ class FreshRSS_index_Controller extends Minz_ActionController { // Si on a récupéré aucun article "non lus" // on essaye de récupérer tous les articles - if ($state === 'not_read' && empty($entries) && is_null($state_param)) { + if ($state === 'not_read' && empty($entries) && ($state_param === null)) { Minz_Log::record ('Conflicting information about nbNotRead!', Minz_Log::DEBUG); $this->view->state = 'all'; $entries = $entryDAO->listWhere($getType, $getId, 'all', $order, $nb, $first, $filter, $date_min, $keepHistoryDefault); -- cgit v1.2.3 From b07f9157b18bffdb28833ed1363284571be3644e Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Sun, 2 Mar 2014 13:35:09 +0100 Subject: New unsafe autologin mode https://github.com/marienfressinaud/FreshRSS/issues/440 --- app/Controllers/indexController.php | 26 ++++++++++++++++++++++++++ lib/Minz/Configuration.php | 14 ++++++++++++++ p/i/install.php | 3 +++ 3 files changed, 43 insertions(+) (limited to 'app/Controllers') diff --git a/app/Controllers/indexController.php b/app/Controllers/indexController.php index c83c5b630..0905e591a 100755 --- a/app/Controllers/indexController.php +++ b/app/Controllers/indexController.php @@ -352,6 +352,32 @@ class FreshRSS_index_Controller extends Minz_ActionController { } $this->view->_useLayout(false); Minz_Request::forward(array('c' => 'index', 'a' => 'index'), true); + } elseif (Minz_Configuration::unsafeAutologinEnabled() && isset($_GET['u']) && isset($_GET['p'])) { + Minz_Session::_param('currentUser'); + Minz_Session::_param('mail'); + Minz_Session::_param('passwordHash'); + $username = ctype_alnum($_GET['u']) ? $_GET['u'] : ''; + $passwordPlain = $_GET['p']; + Minz_Request::_param('p'); //Discard plain-text password ASAP + $_GET['p'] = ''; + if (!function_exists('password_verify')) { + include_once(LIB_PATH . '/password_compat.php'); + } + try { + $conf = new FreshRSS_Configuration($username); + $s = $conf->passwordHash; + $ok = password_verify($passwordPlain, $s); + unset($passwordPlain); + if ($ok) { + Minz_Session::_param('currentUser', $username); + Minz_Session::_param('passwordHash', $s); + } else { + Minz_Log::record('Unsafe password mismatch for user ' . $username, Minz_Log::WARNING); + } + } catch (Minz_Exception $me) { + Minz_Log::record('Unsafe login failure: ' . $me->getMessage(), Minz_Log::WARNING); + } + Minz_Request::forward(array('c' => 'index', 'a' => 'index'), true); } elseif (!Minz_Configuration::canLogIn()) { Minz_Error::error ( 403, diff --git a/lib/Minz/Configuration.php b/lib/Minz/Configuration.php index ff71d747c..324aae881 100644 --- a/lib/Minz/Configuration.php +++ b/lib/Minz/Configuration.php @@ -55,6 +55,7 @@ class Minz_Configuration { private static $allow_anonymous_refresh = false; private static $auth_type = 'none'; private static $api_enabled = false; + private static $unsafe_autologin_enabled = false; private static $db = array ( 'type' => 'mysql', @@ -135,6 +136,9 @@ class Minz_Configuration { public static function apiEnabled() { return self::$api_enabled; } + public static function unsafeAutologinEnabled() { + return self::$unsafe_autologin_enabled; + } public static function _allowAnonymous($allow = false) { self::$allow_anonymous = ((bool)$allow) && self::canLogIn(); @@ -158,6 +162,9 @@ class Minz_Configuration { public static function _enableApi($value = false) { self::$api_enabled = (bool)$value; } + public static function _enableAutologin($value = false) { + self::$unsafe_autologin_enabled = (bool)$value; + } /** * Initialise les variables de configuration @@ -188,6 +195,7 @@ class Minz_Configuration { 'allow_anonymous_refresh' => self::$allow_anonymous_refresh, 'auth_type' => self::$auth_type, 'api_enabled' => self::$api_enabled, + 'unsafe_autologin_enabled' => self::$unsafe_autologin_enabled, ), 'db' => self::$db, ); @@ -310,6 +318,12 @@ class Minz_Configuration { ($general['api_enabled'] !== 'no') ); } + if (isset ($general['unsafe_autologin_enabled'])) { + self::$unsafe_autologin_enabled = ( + ((bool)($general['unsafe_autologin_enabled'])) && + ($general['unsafe_autologin_enabled'] !== 'no') + ); + } // Base de données if (isset ($ini_array['db'])) { diff --git a/p/i/install.php b/p/i/install.php index 720813323..a7563d5ee 100644 --- a/p/i/install.php +++ b/p/i/install.php @@ -235,6 +235,9 @@ function saveStep3 () { 'default_user' => $_SESSION['default_user'], 'auth_type' => $_SESSION['auth_type'], 'allow_anonymous' => isset($_SESSION['allow_anonymous']) ? $_SESSION['allow_anonymous'] : false, + 'allow_anonymous_refresh' => false, + 'unsafe_autologin_enabled' => false, + 'api_enabled' => false, ), 'db' => array( 'type' => $_SESSION['bd_type'], -- cgit v1.2.3 From fc6769c1b10314b50be4a3d970c5c4917be6305c Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Mon, 3 Mar 2014 21:25:14 +0100 Subject: API: Add continuation mode https://github.com/marienfressinaud/FreshRSS/issues/443 Needed for e.g. EasyRSS --- app/Controllers/indexController.php | 2 +- app/Models/EntryDAO.php | 22 +++++++++++++--------- p/api/greader.php | 30 ++++++++++++++++++++++-------- 3 files changed, 36 insertions(+), 18 deletions(-) (limited to 'app/Controllers') diff --git a/app/Controllers/indexController.php b/app/Controllers/indexController.php index 0905e591a..2f263dff0 100755 --- a/app/Controllers/indexController.php +++ b/app/Controllers/indexController.php @@ -126,7 +126,7 @@ class FreshRSS_index_Controller extends Minz_ActionController { $keepHistoryDefault = $this->view->conf->keep_history_default; try { - $entries = $entryDAO->listWhere($getType, $getId, $state, $order, $nb + 1, $first, $filter, $date_min, $keepHistoryDefault); + $entries = $entryDAO->listWhere($getType, $getId, $state, $order, $nb + 1, $first, $filter, $date_min, true, $keepHistoryDefault); // Si on a récupéré aucun article "non lus" // on essaye de récupérer tous les articles diff --git a/app/Models/EntryDAO.php b/app/Models/EntryDAO.php index cc52ea120..e4cf128ea 100644 --- a/app/Models/EntryDAO.php +++ b/app/Models/EntryDAO.php @@ -376,7 +376,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { return isset ($entries[0]) ? $entries[0] : null; } - private function sqlListWhere($type = 'a', $id = '', $state = 'all', $order = 'DESC', $limit = 1, $firstId = '', $filter = '', $date_min = 0, $keepHistoryDefault = 0) { + private function sqlListWhere($type = 'a', $id = '', $state = 'all', $order = 'DESC', $limit = 1, $firstId = '', $filter = '', $date_min = 0, $showOlderUnreadsorFavorites = false, $keepHistoryDefault = 0) { $where = ''; $joinFeed = false; $values = array(); @@ -429,11 +429,15 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { $where .= 'AND e1.id ' . ($order === 'DESC' ? '<=' : '>=') . $firstId . ' '; } if (($date_min > 0) && ($type !== 's')) { - $where .= 'AND (e1.id >= ' . $date_min . '000000 OR e1.is_read = 0 OR e1.is_favorite = 1 OR (f.keep_history <> 0'; - if (intval($keepHistoryDefault) === 0) { - $where .= ' AND f.keep_history <> -2'; //default + $where .= 'AND (e1.id >= ' . $date_min . '000000'; + if ($showOlderUnreadsorFavorites) { //Lax date constraint + $where .= ' OR e1.is_read = 0 OR e1.is_favorite = 1 OR (f.keep_history <> 0'; + if (intval($keepHistoryDefault) === 0) { + $where .= ' AND f.keep_history <> -2'; //default + } + $where .= ')'; } - $where .= ')) '; + $where .= ') '; $joinFeed = true; } $search = ''; @@ -494,8 +498,8 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { . ($limit > 0 ? ' LIMIT ' . $limit : '')); //TODO: See http://explainextended.com/2009/10/23/mysql-order-by-limit-performance-late-row-lookups/ } - public function listWhere($type = 'a', $id = '', $state = 'all', $order = 'DESC', $limit = 1, $firstId = '', $filter = '', $date_min = 0, $keepHistoryDefault = 0) { - list($values, $sql) = $this->sqlListWhere($type, $id, $state, $order, $limit, $firstId, $filter, $date_min, $keepHistoryDefault); + public function listWhere($type = 'a', $id = '', $state = 'all', $order = 'DESC', $limit = 1, $firstId = '', $filter = '', $date_min = 0, $showOlderUnreadsorFavorites = false, $keepHistoryDefault = 0) { + list($values, $sql) = $this->sqlListWhere($type, $id, $state, $order, $limit, $firstId, $filter, $date_min, $showOlderUnreadsorFavorites, $keepHistoryDefault); $sql = 'SELECT e.id, e.guid, e.title, e.author, UNCOMPRESS(e.content_bin) AS content, e.link, e.date, e.is_read, e.is_favorite, e.id_feed, e.tags ' . 'FROM `' . $this->prefix . 'entry` e ' @@ -510,8 +514,8 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { return self::daoToEntry ($stm->fetchAll (PDO::FETCH_ASSOC)); } - public function listIdsWhere($type = 'a', $id = '', $state = 'all', $order = 'DESC', $limit = 1, $firstId = '', $filter = '', $date_min = 0, $keepHistoryDefault = 0) { //For API - list($values, $sql) = $this->sqlListWhere($type, $id, $state, $order, $limit, $firstId, $filter, $date_min, $keepHistoryDefault); + public function listIdsWhere($type = 'a', $id = '', $state = 'all', $order = 'DESC', $limit = 1, $firstId = '', $filter = '', $date_min = 0, $showOlderUnreadsorFavorites = false, $keepHistoryDefault = 0) { //For API + list($values, $sql) = $this->sqlListWhere($type, $id, $state, $order, $limit, $firstId, $filter, $date_min, $showOlderUnreadsorFavorites, $keepHistoryDefault); $stm = $this->bd->prepare($sql); $stm->execute($values); diff --git a/p/api/greader.php b/p/api/greader.php index f26217279..b1ed34d27 100644 --- a/p/api/greader.php +++ b/p/api/greader.php @@ -288,7 +288,7 @@ function unreadCount() { exit(); } -function streamContents($path, $include_target, $start_time, $count, $order, $exclude_target) +function streamContents($path, $include_target, $start_time, $count, $order, $exclude_target, $continuation) {//http://code.google.com/p/pyrfeed/wiki/GoogleReaderAPI http://blog.martindoms.com/2009/10/16/using-the-google-reader-api-part-2/#feed logMe('streamContents(' . $include_target . ")\n"); header('Content-Type: application/json; charset=UTF-8'); @@ -326,8 +326,12 @@ function streamContents($path, $include_target, $start_time, $count, $order, $ex break; } + if (!empty($continuation)) { + $count++; //Shift by one element + } + $entryDAO = new FreshRSS_EntryDAO(); - $entries = $entryDAO->listWhere($type, $include_target, $state, $order === 'o' ? 'ASC' : 'DESC', $count, '', '', $start_time); + $entries = $entryDAO->listWhere($type, $include_target, $state, $order === 'o' ? 'ASC' : 'DESC', $count, $continuation, '', $start_time); $items = array(); foreach ($entries as $entry) { @@ -371,11 +375,20 @@ function streamContents($path, $include_target, $start_time, $count, $order, $ex $items[] = $item; } - echo json_encode(array( + if (!empty($continuation)) { + array_shift($items); //Discard first element that was already sent in the previous response + } + + $response = array( 'id' => 'user/-/state/com.google/reading-list', 'updated' => time(), 'items' => $items, - )), "\n"; + ); + if ((count($entries) >= $count) && (!empty($entry))) { + $response['continuation'] = $entry->id(); + } + + echo json_encode($response), "\n"; exit(); } @@ -520,27 +533,28 @@ elseif ($pathInfos[1] === 'reader' && $pathInfos[2] === 'api' && isset($pathInfo $count = isset($_GET['n']) ? intval($_GET['n']) : 20; //n=[integer] : The maximum number of results to return. $order = isset($_GET['r']) ? $_GET['r'] : 'd'; //r=[d|n|o] : Sort order of item results. d or n gives items in descending date order, o in ascending order. $start_time = isset($_GET['ot']) ? intval($_GET['ot']) : 0; //ot=[unix timestamp] : The time from which you want to retrieve items. Only items that have been crawled by Google Reader after this time will be returned. + $continuation = isset($_GET['c']) ? $_GET['c'] : ''; //Continuation token. If a StreamContents response does not represent all items in a timestamp range, it will have a continuation attribute. The same request can be re-issued with the value of that attribute put in this parameter to get more items if (isset($pathInfos[5]) && $pathInfos[5] === 'contents' && isset($pathInfos[6])) { if (isset($pathInfos[7])) { if ($pathInfos[6] === 'feed') { $include_target = $pathInfos[7]; - StreamContents($pathInfos[6], $include_target, $start_time, $count, $order, $exclude_target); + StreamContents($pathInfos[6], $include_target, $start_time, $count, $order, $exclude_target, $continuation); } elseif ($pathInfos[6] === 'user' && isset($pathInfos[8]) && isset($pathInfos[9])) { if ($pathInfos[8] === 'state') { if ($pathInfos[9] === 'com.google' && isset($pathInfos[10])) { if ($pathInfos[10] === 'reading-list' || $pathInfos[10] === 'starred') { $include_target = ''; - streamContents($pathInfos[10], $include_target, $start_time, $count, $order, $exclude_target); + streamContents($pathInfos[10], $include_target, $start_time, $count, $order, $exclude_target, $continuation); } } } elseif ($pathInfos[8] === 'label') { $include_target = $pathInfos[9]; - streamContents($pathInfos[8], $include_target, $start_time, $count, $order, $exclude_target); + streamContents($pathInfos[8], $include_target, $start_time, $count, $order, $exclude_target, $continuation); } } } else { //EasyRSS $include_target = ''; - streamContents('reading-list', $include_target, $start_time, $count, $order, $exclude_target); + streamContents('reading-list', $include_target, $start_time, $count, $order, $exclude_target, $continuation); } } elseif ($pathInfos[5] === 'items') { if ($pathInfos[6] === 'ids' && isset($_GET['s'])) { -- cgit v1.2.3 From ff8fd9565413d8b690e0027bd6a091d8e74165b1 Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Mon, 3 Mar 2014 21:27:45 +0100 Subject: API: Bug previous commit https://github.com/marienfressinaud/FreshRSS/issues/443 Bug in https://github.com/marienfressinaud/FreshRSS/commit/fc6769c1b10314b50be4a3d970c5c4917be6305c --- app/Controllers/indexController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app/Controllers') diff --git a/app/Controllers/indexController.php b/app/Controllers/indexController.php index 2f263dff0..73f454715 100755 --- a/app/Controllers/indexController.php +++ b/app/Controllers/indexController.php @@ -133,7 +133,7 @@ class FreshRSS_index_Controller extends Minz_ActionController { if ($state === 'not_read' && empty($entries) && ($state_param === null)) { Minz_Log::record ('Conflicting information about nbNotRead!', Minz_Log::DEBUG); $this->view->state = 'all'; - $entries = $entryDAO->listWhere($getType, $getId, 'all', $order, $nb, $first, $filter, $date_min, $keepHistoryDefault); + $entries = $entryDAO->listWhere($getType, $getId, 'all', $order, $nb, $first, $filter, $date_min, true, $keepHistoryDefault); } if (count($entries) <= $nb) { -- cgit v1.2.3 From 670ee57b6288549ac611d53d86d8f5ffee641b0e Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Sat, 8 Mar 2014 20:16:10 +0100 Subject: Interface to enable/disable API and unsafe automatic login https://github.com/marienfressinaud/FreshRSS/issues/13 https://github.com/marienfressinaud/FreshRSS/issues/440 --- app/Controllers/usersController.php | 8 +++++++- app/Models/Feed.php | 11 ++++++----- app/i18n/en.php | 2 ++ app/i18n/fr.php | 2 ++ app/views/configure/users.phtml | 21 +++++++++++++++++++++ 5 files changed, 38 insertions(+), 6 deletions(-) (limited to 'app/Controllers') diff --git a/app/Controllers/usersController.php b/app/Controllers/usersController.php index b03989cd7..fa967cedc 100644 --- a/app/Controllers/usersController.php +++ b/app/Controllers/usersController.php @@ -69,13 +69,19 @@ class FreshRSS_users_Controller extends Minz_ActionController { $anon_refresh = Minz_Request::param('anon_refresh', false); $anon_refresh = ((bool)$anon_refresh) && ($anon_refresh !== 'no'); $auth_type = Minz_Request::param('auth_type', 'none'); + $unsafe_autologin = Minz_Request::param('unsafe_autologin', false); + $api_enabled = Minz_Request::param('api_enabled', false); if ($anon != Minz_Configuration::allowAnonymous() || $auth_type != Minz_Configuration::authType() || - $anon_refresh != Minz_Configuration::allowAnonymousRefresh()) { + $anon_refresh != Minz_Configuration::allowAnonymousRefresh() || + $unsafe_autologin != Minz_Configuration::unsafeAutologinEnabled() || + $api_enabled != Minz_Configuration::apiEnabled()) { Minz_Configuration::_authType($auth_type); Minz_Configuration::_allowAnonymous($anon); Minz_Configuration::_allowAnonymousRefresh($anon_refresh); + Minz_Configuration::_enableAutologin($unsafe_autologin); + Minz_Configuration::_enableApi($api_enabled); $ok &= Minz_Configuration::writeFile(); } } diff --git a/app/Models/Feed.php b/app/Models/Feed.php index 73f9c32fb..bce3bd24f 100644 --- a/app/Models/Feed.php +++ b/app/Models/Feed.php @@ -254,11 +254,12 @@ class FreshRSS_Feed extends Minz_Model { $elinks = array(); foreach ($item->get_enclosures() as $enclosure) { $elink = $enclosure->get_link(); - if (array_key_exists($elink, $elinks)) continue; - $elinks[$elink] = '1'; - $mime = strtolower($enclosure->get_type()); - if (strpos($mime, 'image/') === 0) { - $content .= '
'; + if (empty($elinks[$elink])) { + $elinks[$elink] = '1'; + $mime = strtolower($enclosure->get_type()); + if (strpos($mime, 'image/') === 0) { + $content .= '
'; + } } } diff --git a/app/i18n/en.php b/app/i18n/en.php index d504ffc11..69247165f 100644 --- a/app/i18n/en.php +++ b/app/i18n/en.php @@ -180,6 +180,8 @@ return array ( 'persona_connection_email' => 'Login mail address
(for Mozilla Persona)', 'allow_anonymous' => 'Allow anonymous reading of the articles of the default user (%s)', 'allow_anonymous_refresh' => 'Allow anonymous refresh of the articles', + 'unsafe_autologin' => 'Allow unsafe automatic login using the format: ', + 'api_enabled' => 'Allow API access (required for mobile apps)', 'auth_token' => 'Authentication token', 'explain_token' => 'Allows to access RSS output of the default user without authentication.
%s?output=rss&token=%s', 'login_configuration' => 'Login', diff --git a/app/i18n/fr.php b/app/i18n/fr.php index c5581a78b..e364ed130 100644 --- a/app/i18n/fr.php +++ b/app/i18n/fr.php @@ -180,6 +180,8 @@ return array ( 'persona_connection_email' => 'Adresse courriel de connexion
(pour Mozilla Persona)', 'allow_anonymous' => 'Autoriser la lecture anonyme des articles de l’utilisateur par défaut (%s)', 'allow_anonymous_refresh' => 'Autoriser le rafraîchissement anonyme des flux', + 'unsafe_autologin' => 'Autoriser les connexion automatiques non-sûres au format : ', + 'api_enabled' => 'Autoriser l’accès par API (nécessaire pour les applis mobiles)', 'auth_token' => 'Jeton d’identification', 'explain_token' => 'Permet d’accéder à la sortie RSS de l’utilisateur par défaut sans besoin de s’authentifier.
%s?output=rss&token=%s', 'login_configuration' => 'Identification', diff --git a/app/views/configure/users.phtml b/app/views/configure/users.phtml index 3f3f19418..1a02b0e90 100644 --- a/app/views/configure/users.phtml +++ b/app/views/configure/users.phtml @@ -90,6 +90,17 @@
+
+
+ +
+
+
@@ -102,6 +113,16 @@
+
+
+ +
+
+
-- cgit v1.2.3 From 3083af6288f23ba6986232798fde67b91517f287 Mon Sep 17 00:00:00 2001 From: Alexis Degrugillier Date: Sun, 9 Mar 2014 21:45:25 -0400 Subject: Enhance feed adding popup I added a new option in the category select. It allows the user to add dynamically a new category and add the feed to the newly created category. See #356 --- app/Controllers/feedController.php | 8 ++++++++ app/layout/aside_feed.phtml | 5 +++++ p/scripts/main.js | 13 +++++++++++++ 3 files changed, 26 insertions(+) (limited to 'app/Controllers') diff --git a/app/Controllers/feedController.php b/app/Controllers/feedController.php index c718fcd5c..9996725e4 100755 --- a/app/Controllers/feedController.php +++ b/app/Controllers/feedController.php @@ -30,6 +30,14 @@ class FreshRSS_feed_Controller extends Minz_ActionController { $url = Minz_Request::param ('url_rss'); $cat = Minz_Request::param ('category', false); + if ($cat === 'nc') { + $new_cat = Minz_Request::param ('new_category'); + if (empty($new_cat['name'])) { + $cat = false; + } else { + $cat = $this->catDAO->addCategory($new_cat); + } + } if ($cat === false) { $def_cat = $this->catDAO->getDefault (); $cat = $def_cat->id (); diff --git a/app/layout/aside_feed.phtml b/app/layout/aside_feed.phtml index e324b15bd..6f3cdafb2 100644 --- a/app/layout/aside_feed.phtml +++ b/app/layout/aside_feed.phtml @@ -20,9 +20,14 @@ name (); ?> + + +
  • diff --git a/p/scripts/main.js b/p/scripts/main.js index b69ac0a2a..3a4d4e4fb 100644 --- a/p/scripts/main.js +++ b/p/scripts/main.js @@ -986,6 +986,18 @@ function init_share_observers() { }); }; +function init_feed_observers() { + $('select[id="category"]').on('change', function(){ + var detail = $(this).parent('li').next('li'); + if ($(this).val() === 'nc') { + detail.show(); + detail.find('input').focus(); + } else { + detail.hide(); + } + }); +}; + function init_all() { if (!(window.$ && window.url_freshrss && ((!full_lazyload) || $.fn.lazyload))) { if (window.console) { @@ -1017,6 +1029,7 @@ function init_all() { window.setInterval(refreshUnreads, 120000); } else { init_share_observers(); + init_feed_observers(); } if (window.console) { -- cgit v1.2.3 From 8a8fe93d34cc0886502f02732228051f77a0f1ff Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Mon, 10 Mar 2014 17:34:29 +0100 Subject: SQL: Update cached values after an optimization https://github.com/marienfressinaud/FreshRSS/issues/452 --- app/Controllers/entryController.php | 3 +++ 1 file changed, 3 insertions(+) (limited to 'app/Controllers') diff --git a/app/Controllers/entryController.php b/app/Controllers/entryController.php index ca7122a7c..bbcb990f5 100755 --- a/app/Controllers/entryController.php +++ b/app/Controllers/entryController.php @@ -100,6 +100,9 @@ class FreshRSS_entry_Controller extends Minz_ActionController { $entryDAO = new FreshRSS_EntryDAO(); $entryDAO->optimizeTable(); + $feedDAO = new FreshRSS_FeedDAO(); + $feedDAO->updateCachedValues(); + invalidateHttpCache(); $notif = array ( -- cgit v1.2.3 From 608b3a8656d986ff177e97e968256bcbf6785c13 Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Mon, 10 Mar 2014 20:16:41 +0100 Subject: Link to logs in case of error when adding new feed https://github.com/marienfressinaud/FreshRSS/issues/453 --- app/Controllers/feedController.php | 4 ++-- app/i18n/en.php | 2 +- app/i18n/fr.php | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) (limited to 'app/Controllers') diff --git a/app/Controllers/feedController.php b/app/Controllers/feedController.php index 9996725e4..16516ad39 100755 --- a/app/Controllers/feedController.php +++ b/app/Controllers/feedController.php @@ -136,7 +136,7 @@ class FreshRSS_feed_Controller extends Minz_ActionController { Minz_Log::record ($e->getMessage (), Minz_Log::WARNING); $notif = array ( 'type' => 'bad', - 'content' => Minz_Translate::t ('internal_problem_feed') + 'content' => Minz_Translate::t ('internal_problem_feed', Minz_Url::display(array('a' => 'logs'))) ); Minz_Session::_param ('notification', $notif); } catch (Minz_FileNotExistException $e) { @@ -144,7 +144,7 @@ class FreshRSS_feed_Controller extends Minz_ActionController { Minz_Log::record ($e->getMessage (), Minz_Log::ERROR); $notif = array ( 'type' => 'bad', - 'content' => Minz_Translate::t ('internal_problem_feed') + 'content' => Minz_Translate::t ('internal_problem_feed', Minz_Url::display(array('a' => 'logs'))) ); Minz_Session::_param ('notification', $notif); } diff --git a/app/i18n/en.php b/app/i18n/en.php index 69247165f..7f95500ad 100644 --- a/app/i18n/en.php +++ b/app/i18n/en.php @@ -89,7 +89,7 @@ return array ( 'already_subscribed' => 'You have already subscribed to %s', 'feed_added' => 'RSS feed %s has been added', 'feed_not_added' => '%s could not be added', - 'internal_problem_feed' => 'The RSS feed could not be added. Check FressRSS logs for details.', + 'internal_problem_feed' => 'The RSS feed could not be added. Check FressRSS logs for details.', 'invalid_url' => 'URL %s is invalid', 'feed_actualized' => '%s has been updated', 'n_feeds_actualized' => '%d feeds have been updated', diff --git a/app/i18n/fr.php b/app/i18n/fr.php index e364ed130..91daa4f51 100644 --- a/app/i18n/fr.php +++ b/app/i18n/fr.php @@ -89,7 +89,7 @@ return array ( 'already_subscribed' => 'Vous êtes déjà abonné à %s', 'feed_added' => 'Le flux %s a bien été ajouté', 'feed_not_added' => '%s n’a pas pu être ajouté', - 'internal_problem_feed' => 'Le flux n’a pas pu être ajouté. Consulter les logs de FreshRSS pour plus de détails.', + 'internal_problem_feed' => 'Le flux n’a pas pu être ajouté. Consulter les logs de FreshRSS pour plus de détails.', 'invalid_url' => 'L’url %s est invalide', 'feed_actualized' => '%s a été mis à jour', 'n_feeds_actualized' => '%d flux ont été mis à jour', -- cgit v1.2.3 From f31747ee7bdaebf02e7e58d51c27102e0c7c9ef1 Mon Sep 17 00:00:00 2001 From: Alexis Degrugillier Date: Fri, 14 Mar 2014 06:15:52 -0400 Subject: Refresh page after getting new articles. Change the URL to the refresh link. Before it was redirecting to the home page. Now it is redirecting to the same page, which make more sense. See #457 --- app/Controllers/indexController.php | 4 +--- app/layout/nav_menu.phtml | 20 +++++--------------- app/views/helpers/view/normal_view.phtml | 2 +- 3 files changed, 7 insertions(+), 19 deletions(-) (limited to 'app/Controllers') diff --git a/app/Controllers/indexController.php b/app/Controllers/indexController.php index 73f454715..9da1e5022 100755 --- a/app/Controllers/indexController.php +++ b/app/Controllers/indexController.php @@ -25,16 +25,14 @@ class FreshRSS_index_Controller extends Minz_ActionController { } } - // construction of RSS url of this feed $params = Minz_Request::params (); - $params['output'] = 'rss'; if (isset ($params['search'])) { $params['search'] = urlencode ($params['search']); } if (!Minz_Configuration::allowAnonymous()) { $params['token'] = $token; } - $this->view->rss_url = array ( + $this->view->url = array ( 'c' => 'index', 'a' => 'index', 'params' => $params diff --git a/app/layout/nav_menu.phtml b/app/layout/nav_menu.phtml index 097809e08..ffdc95b24 100644 --- a/app/layout/nav_menu.phtml +++ b/app/layout/nav_menu.phtml @@ -99,17 +99,6 @@
    - 'index', - 'a' => 'index', - 'params' => $params - ); - ?> diff --git a/lib/Minz/View.php b/lib/Minz/View.php index 00d9a1a6d..f034ab10b 100644 --- a/lib/Minz/View.php +++ b/lib/Minz/View.php @@ -108,7 +108,7 @@ class Minz_View { */ public function helperToString($helper) { ob_start(); - renderHelper($helper); + $this->renderHelper($helper); return ob_get_clean(); } -- cgit v1.2.3 From 05cc97a8f43d779fbea52f5ef47fe6a71c3ab692 Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Fri, 28 Mar 2014 18:44:52 +0100 Subject: Export form is not displayed if there is nothing to export --- app/Controllers/importExportController.php | 1 + app/views/importExport/index.phtml | 2 ++ 2 files changed, 3 insertions(+) (limited to 'app/Controllers') diff --git a/app/Controllers/importExportController.php b/app/Controllers/importExportController.php index 040cf3f7b..51f0708fd 100644 --- a/app/Controllers/importExportController.php +++ b/app/Controllers/importExportController.php @@ -131,6 +131,7 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { ); $this->view->feed = $feed; } + return $this->view->helperToString('export/articles'); } } diff --git a/app/views/importExport/index.phtml b/app/views/importExport/index.phtml index b82e0d26b..386c88648 100644 --- a/app/views/importExport/index.phtml +++ b/app/views/importExport/index.phtml @@ -19,6 +19,7 @@
    + feeds) > 0) { ?>
    @@ -47,4 +48,5 @@
    + -- cgit v1.2.3 From d464833922e9cc00948698aed0a2af1f0b55e101 Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Fri, 28 Mar 2014 22:13:42 +0100 Subject: Improve import / export functionnality It is not finished yet and does not even work at all!! - ZIP archive can be uploaded - Entries are imported from starred.json and feed*.json but not added in DB for the moment. - Fix export (add author, id -> guid, content -> content.content and add alternate) See https://github.com/marienfressinaud/FreshRSS/issues/163 --- app/Controllers/feedController.php | 2 + app/Controllers/importExportController.php | 260 ++++++++++++++++++++++++----- app/views/helpers/export/articles.phtml | 11 +- 3 files changed, 228 insertions(+), 45 deletions(-) (limited to 'app/Controllers') diff --git a/app/Controllers/feedController.php b/app/Controllers/feedController.php index 16516ad39..43435d99d 100755 --- a/app/Controllers/feedController.php +++ b/app/Controllers/feedController.php @@ -328,6 +328,8 @@ class FreshRSS_feed_Controller extends Minz_ActionController { } public function massiveImportAction () { + # TODO: this function has moved to importExportController.php + # I keep it for the moment but should be deleted in a near future. @set_time_limit(300); $this->catDAO = new FreshRSS_CategoryDAO (); diff --git a/app/Controllers/importExportController.php b/app/Controllers/importExportController.php index 51f0708fd..6194ce214 100644 --- a/app/Controllers/importExportController.php +++ b/app/Controllers/importExportController.php @@ -3,21 +3,22 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { public function firstAction() { if (!$this->view->loginOk) { - Minz_Error::error ( + Minz_Error::error( 403, - array ('error' => array (Minz_Translate::t ('access_denied'))) + array('error' => array(Minz_Translate::t('access_denied'))) ); } require_once(LIB_PATH . '/lib_opml.php'); + + $this->catDAO = new FreshRSS_CategoryDAO(); + $this->entryDAO = new FreshRSS_EntryDAO(); + $this->feedDAO = new FreshRSS_FeedDAO(); } public function indexAction() { - $catDAO = new FreshRSS_CategoryDAO(); - $this->view->categories = $catDAO->listCategories(); - - $feedDAO = new FreshRSS_FeedDAO(); - $this->view->feeds = $feedDAO->listFeeds(); + $this->view->categories = $this->catDAO->listCategories(); + $this->view->feeds = $this->feedDAO->listFeeds(); // au niveau de la vue, permet de ne pas voir un flux sélectionné dans la liste $this->view->flux = false; @@ -27,38 +28,218 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { public function importAction() { if (Minz_Request::isPost() && $_FILES['file']['error'] == 0) { - invalidateHttpCache(); - // on parse le fichier OPML pour récupérer les catégories et les flux associés + @set_time_limit(300); + + $file = $_FILES['file']; + $type_file = $this->guess_file_type($file['name']); + + $list_files = array( + 'opml' => array(), + 'json_starred' => array(), + 'json_feed' => array() + ); + + // We try to list all files according to their type + // A zip file is first opened and then its files are listed + $list = array(); + if ($type_file === 'zip') { + $zip = zip_open($file['tmp_name']); + + while (($zipfile = zip_read($zip)) !== false) { + $type_zipfile = $this->guess_file_type(zip_entry_name($zipfile)); + + if ($type_file !== 'unknown') { + $list_files[$type_zipfile][] = zip_entry_read( + $zipfile, + zip_entry_filesize($zipfile) + ); + } + } + + zip_close($zip); + } elseif ($type_file !== 'unknown') { + $list_files[$type_file][] = file_get_contents($file['tmp_name']); + } + + // Import different files. + // OPML first(so categories and feeds are imported) + // Starred articles then so the "favourite" status is already set + // And finally all other files. + $error = false; + foreach ($list_files['opml'] as $opml_file) { + $error = $this->import_opml($opml_file); + } + foreach ($list_files['json_starred'] as $article_file) { + $error = $this->import_articles($article_file, true); + } + foreach ($list_files['json_feed'] as $article_file) { + $error = $this->import_articles($article_file); + } + + // And finally, we get import status and redirect to the home page + $notif = null; + if ($error === true) { + $notif = array( + 'type' => 'good', + 'content' => Minz_Translate::t('feeds_imported_with_errors') + ); + } else { + $notif = array( + 'type' => 'good', + 'content' => Minz_Translate::t('feeds_imported') + ); + } + + Minz_Session::_param('notification', $notif); + Minz_Session::_param('actualize_feeds', true); + + Minz_Request::forward(array( + 'c' => 'index', + 'a' => 'index' + ), true); + } + + // What are you doing? you have to call this controller + // with a POST request! + Minz_Request::forward(array( + 'c' => 'importExport', + 'a' => 'index' + )); + } + + private function guess_file_type($filename) { + // A *very* basic guess file type function. Only based on filename + // That's could be improved but should be enough, at least for a first + // implementation. + // TODO: improve this function? + + if (substr_compare($filename, '.zip', -4) === 0) { + return 'zip'; + } elseif (substr_compare($filename, '.opml', -5) === 0) { + return 'opml'; + } elseif (strcmp($filename, 'starred.json') === 0) { + return 'json_starred'; + } elseif (substr_compare($filename, '.json', -5) === 0 && + strpos($filename, 'feed_') === 0) { + return 'json_feed'; + } else { + return 'unknown'; + } + } + + private function import_opml($opml_file) { + $categories = array(); + $feeds = array(); + try { + list($categories, $feeds) = opml_import($opml_file); + } catch (FreshRSS_Opml_Exception $e) { + Minz_Log::warning($e->getMessage()); + return true; + } + + $this->catDAO->checkDefault(); + + // on ajoute les catégories en masse dans une fonction à part + $this->addCategories($categories); + + // on calcule la date des articles les plus anciens qu'on accepte + $nb_month_old = $this->view->conf->old_entries; + $date_min = time() -(3600 * 24 * 30 * $nb_month_old); + + // la variable $error permet de savoir si une erreur est survenue + // Le but est de ne pas arrêter l'import même en cas d'erreur + // L'utilisateur sera mis au courant s'il y a eu des erreurs, mais + // ne connaîtra pas les détails. Ceux-ci seront toutefois logguées + $error = false; + foreach ($feeds as $feed) { try { - list ($categories, $feeds) = opml_import ( - file_get_contents ($_FILES['file']['tmp_name']) + $values = array( + 'id' => $feed->id(), + 'url' => $feed->url(), + 'category' => $feed->category(), + 'name' => $feed->name(), + 'website' => $feed->website(), + 'description' => $feed->description(), + 'lastUpdate' => 0, + 'httpAuth' => $feed->httpAuth() ); - // On redirige vers le controller feed qui va se charger d'insérer les flux en BDD - // les flux sont mis au préalable dans des variables de Request - Minz_Request::_param ('categories', $categories); - Minz_Request::_param ('feeds', $feeds); - Minz_Request::forward (array ('c' => 'feed', 'a' => 'massiveImport')); - } catch (FreshRSS_Opml_Exception $e) { - Minz_Log::record ($e->getMessage (), Minz_Log::WARNING); - - $notif = array ( - 'type' => 'bad', - 'content' => Minz_Translate::t ('bad_opml_file') + // ajout du flux que s'il n'est pas déjà en BDD + if (!$this->feedDAO->searchByUrl($values['url'])) { + $id = $this->feedDAO->addFeed($values); + if ($id) { + $feed->_id($id); + $feed->faviconPrepare(); + } else { + $error = true; + } + } + } catch (FreshRSS_Feed_Exception $e) { + $error = true; + Minz_Log::record($e->getMessage(), Minz_Log::WARNING); + } + } + + return $error; + } + + private function addCategories($categories) { + foreach ($categories as $cat) { + if (!$this->catDAO->searchByName($cat->name())) { + $values = array( + 'id' => $cat->id(), + 'name' => $cat->name(), ); - Minz_Session::_param ('notification', $notif); + $this->catDAO->addCategory($values); + } + } + } + + private function import_articles($article_file, $starred = false) { + $article_object = json_decode($article_file, true); + if (is_null($article_object)) { + Minz_Log::warning('Try to import a non-JSON file'); + return true; + } + + $google_compliant = (strpos($article_object['id'], 'com.google') !== false); + + foreach ($article_object['items'] as $item) { + $key = $google_compliant ? 'htmlUrl' : 'feedUrl'; + $feed = $this->feedDAO->searchByUrl($item['origin'][$key]); + if (is_null($feed)) { + $feed = new FreshRSS_Feed($item['origin'][$key]); + $feed->_name ($item['origin']['title']); + $feed->_website ($item['origin']['htmlUrl']); + + $error = $this->addFeed($feed); // TODO - Minz_Request::forward (array ( - 'c' => 'configure', - 'a' => 'importExport' - ), true); + if ($error) { + continue; + } } + + $author = isset($item['author']) ? $item['author'] : ''; + $key_content = $google_compliant && !isset($item['content']) ? 'summary' : 'content'; + $tags = $item['categories']; + if ($google_compliant) { + $tags = array_filter($tags, function($var) { + return strpos($var, '/state/com.google') === false; + }); + } + $entry = new FreshRSS_Entry( + $feed->id(), $item['id'], $item['title'], $author, + $item[$key_content]['content'], $item['alternate'][0]['href'], + $item['published'], false, $starred, $tags + ); + + Minz_Log::debug(print_r($entry, true)); // TODO } } public function exportAction() { if (Minz_Request::isPost()) { - $this->view->_useLayout (false); + $this->view->_useLayout(false); $export_opml = Minz_Request::param('export_opml', false); $export_starred = Minz_Request::param('export_starred', false); @@ -76,9 +257,8 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { if ($export_starred) { $zip->addFromString('starred.json', $this->generate_articles('starred')); } - $feedDAO = new FreshRSS_FeedDAO (); foreach ($export_feeds as $feed_id) { - $feed = $feedDAO->searchById($feed_id); + $feed = $this->feedDAO->searchById($feed_id); $zip->addFromString( 'feed_' . $feed->category() . '_' . $feed->id() . '.json', $this->generate_articles('feed', $feed) @@ -96,13 +276,10 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { } private function generate_opml() { - $feedDAO = new FreshRSS_FeedDAO (); - $catDAO = new FreshRSS_CategoryDAO (); - - $list = array (); - foreach ($catDAO->listCategories () as $key => $cat) { - $list[$key]['name'] = $cat->name (); - $list[$key]['feeds'] = $feedDAO->listByCategory ($cat->id ()); + $list = array(); + foreach ($this->catDAO->listCategories() as $key => $cat) { + $list[$key]['name'] = $cat->name(); + $list[$key]['feeds'] = $this->feedDAO->listByCategory($cat->id()); } $this->view->categories = $list; @@ -110,22 +287,19 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { } private function generate_articles($type, $feed = NULL) { - $entryDAO = new FreshRSS_EntryDAO(); - - $catDAO = new FreshRSS_CategoryDAO(); - $this->view->categories = $catDAO->listCategories(); + $this->view->categories = $this->catDAO->listCategories(); if ($type == 'starred') { $this->view->list_title = Minz_Translate::t("starred_list"); $this->view->type = 'starred'; - $this->view->entries = $entryDAO->listWhere( + $this->view->entries = $this->entryDAO->listWhere( 's', '', 'all', 'ASC', $entryDAO->countUnreadReadFavorites()['all'] ); } elseif ($type == 'feed' && !is_null($feed)) { $this->view->list_title = Minz_Translate::t("feed_list", $feed->name()); $this->view->type = 'feed/' . $feed->id(); - $this->view->entries = $entryDAO->listWhere( + $this->view->entries = $this->entryDAO->listWhere( 'f', $feed->id(), 'all', 'ASC', $this->view->conf->posts_per_page ); diff --git a/app/views/helpers/export/articles.phtml b/app/views/helpers/export/articles.phtml index 71ac22f44..ffdca1daa 100644 --- a/app/views/helpers/export/articles.phtml +++ b/app/views/helpers/export/articles.phtml @@ -16,12 +16,19 @@ } $articles['items'][] = array( - 'id' => $entry->id(), + 'id' => $entry->guid(), 'categories' => array_values($entry->tags()), 'title' => $entry->title(), + 'author' => $entry->author(), 'published' => $entry->date(true), 'updated' => $entry->date(true), - 'content' => $entry->content(), + 'alternate' => array(array( + 'href' => $entry->link(), + 'type' => 'text/html' + )), + 'content' => array( + 'content' => $entry->content() + ), 'origin' => array( 'streamId' => $feed->id(), 'title' => $feed->name(), -- cgit v1.2.3 From 7676a197a4767f735dabae6ad9cf40ef65e91aa7 Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Sat, 29 Mar 2014 19:10:18 +0100 Subject: Fix typo + improve detection OPML file --- app/Controllers/importExportController.php | 7 ++++--- app/views/importExport/index.phtml | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) (limited to 'app/Controllers') diff --git a/app/Controllers/importExportController.php b/app/Controllers/importExportController.php index 6194ce214..cbadeb6ca 100644 --- a/app/Controllers/importExportController.php +++ b/app/Controllers/importExportController.php @@ -115,7 +115,8 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { if (substr_compare($filename, '.zip', -4) === 0) { return 'zip'; - } elseif (substr_compare($filename, '.opml', -5) === 0) { + } elseif (substr_compare($filename, '.opml', -5) === 0 || + substr_compare($filename, '.xml', -4) === 0) { return 'opml'; } elseif (strcmp($filename, 'starred.json') === 0) { return 'json_starred'; @@ -144,7 +145,7 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { // on calcule la date des articles les plus anciens qu'on accepte $nb_month_old = $this->view->conf->old_entries; - $date_min = time() -(3600 * 24 * 30 * $nb_month_old); + $date_min = time() - (3600 * 24 * 30 * $nb_month_old); // la variable $error permet de savoir si une erreur est survenue // Le but est de ne pas arrêter l'import même en cas d'erreur @@ -294,7 +295,7 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { $this->view->type = 'starred'; $this->view->entries = $this->entryDAO->listWhere( 's', '', 'all', 'ASC', - $entryDAO->countUnreadReadFavorites()['all'] + $this->entryDAO->countUnreadReadFavorites()['all'] ); } elseif ($type == 'feed' && !is_null($feed)) { $this->view->list_title = Minz_Translate::t("feed_list", $feed->name()); diff --git a/app/views/importExport/index.phtml b/app/views/importExport/index.phtml index 386c88648..309058959 100644 --- a/app/views/importExport/index.phtml +++ b/app/views/importExport/index.phtml @@ -34,7 +34,7 @@ - feeds as $feed) { ?> -- cgit v1.2.3 From 9ea3819402746d8425d4a608f2d5f3c0f5bc29fb Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Sat, 29 Mar 2014 20:18:57 +0100 Subject: Better OPML import / export - use a new OPML library (https://github.com/marienfressinaud/lib_opml) - import has been completely rewritten (far better!) - introduce addFeedObject and addCategoryObject (in DAO for the moment). Permit to add easily feeds and categories (check if they already exist in DB) - introduce html_chars_utf8 (wrap htmlspecialchars for UTF-8) --- app/Controllers/importExportController.php | 124 ++++++++----- app/Exceptions/OpmlException.php | 6 - app/Models/CategoryDAO.php | 12 ++ app/Models/FeedDAO.php | 29 +++ app/views/helpers/export/opml.phtml | 43 +++-- lib/lib_opml.php | 277 ++++++++++++++++++++--------- lib/lib_rss.php | 4 + 7 files changed, 345 insertions(+), 150 deletions(-) delete mode 100644 app/Exceptions/OpmlException.php (limited to 'app/Controllers') diff --git a/app/Controllers/importExportController.php b/app/Controllers/importExportController.php index cbadeb6ca..b6b4d0fed 100644 --- a/app/Controllers/importExportController.php +++ b/app/Controllers/importExportController.php @@ -129,71 +129,101 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { } private function import_opml($opml_file) { - $categories = array(); - $feeds = array(); + $opml_array = array(); try { - list($categories, $feeds) = opml_import($opml_file); - } catch (FreshRSS_Opml_Exception $e) { + $opml_array = libopml_parse_string($opml_file); + } catch (LibOPML_Exception $e) { Minz_Log::warning($e->getMessage()); return true; } $this->catDAO->checkDefault(); - // on ajoute les catégories en masse dans une fonction à part - $this->addCategories($categories); - - // on calcule la date des articles les plus anciens qu'on accepte - $nb_month_old = $this->view->conf->old_entries; - $date_min = time() - (3600 * 24 * 30 * $nb_month_old); + return $this->addOpmlElements($opml_array['body']); + } - // la variable $error permet de savoir si une erreur est survenue - // Le but est de ne pas arrêter l'import même en cas d'erreur - // L'utilisateur sera mis au courant s'il y a eu des erreurs, mais - // ne connaîtra pas les détails. Ceux-ci seront toutefois logguées + private function addOpmlElements($opml_elements, $parent_cat = null) { $error = false; - foreach ($feeds as $feed) { - try { - $values = array( - 'id' => $feed->id(), - 'url' => $feed->url(), - 'category' => $feed->category(), - 'name' => $feed->name(), - 'website' => $feed->website(), - 'description' => $feed->description(), - 'lastUpdate' => 0, - 'httpAuth' => $feed->httpAuth() - ); + foreach ($opml_elements as $elt) { + $res = false; + if (isset($elt['xmlUrl'])) { + $res = $this->addFeedOpml($elt, $parent_cat); + } else { + $res = $this->addCategoryOpml($elt, $parent_cat); + } - // ajout du flux que s'il n'est pas déjà en BDD - if (!$this->feedDAO->searchByUrl($values['url'])) { - $id = $this->feedDAO->addFeed($values); - if ($id) { - $feed->_id($id); - $feed->faviconPrepare(); - } else { - $error = true; - } - } - } catch (FreshRSS_Feed_Exception $e) { - $error = true; - Minz_Log::record($e->getMessage(), Minz_Log::WARNING); + if (!$error && $res) { + // oops: there is at least one error! + $error = $res; } } return $error; } - private function addCategories($categories) { - foreach ($categories as $cat) { - if (!$this->catDAO->searchByName($cat->name())) { - $values = array( - 'id' => $cat->id(), - 'name' => $cat->name(), - ); - $this->catDAO->addCategory($values); + private function addFeedOpml($feed_elt, $parent_cat) { + if (is_null($parent_cat)) { + // This feed has no parent category so we get the default one + $parent_cat = $catDAO->getDefault()->name(); + } + + $cat = $this->catDAO->searchByName($parent_cat); + + if (!$cat) { + return true; + } + + // We get different useful information + $url = html_chars_utf8($feed_elt['xmlUrl']); + $name = html_chars_utf8($feed_elt['text']); + $website = ''; + if (isset($feed_elt['htmlUrl'])) { + $website = html_chars_utf8($feed_elt['htmlUrl']); + } + $description = ''; + if (isset($feed_elt['description'])) { + $description = html_chars_utf8($feed_elt['description']); + } + + $error = false; + try { + // Create a Feed object and add it in DB + $feed = new FreshRSS_Feed($url); + $feed->_category($cat->id()); + $feed->_name($name); + $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); + } catch (FreshRSS_Feed_Exception $e) { + Minz_Log::record($e->getMessage(), Minz_Log::WARNING); + $error = true; + } + + return $error; + } + + private function addCategoryOpml($cat_elt, $parent_cat) { + // Create a new Category object + $cat = new FreshRSS_Category(html_chars_utf8($cat_elt['text'])); + + $id = $this->catDAO->addCategoryObject($cat); + $error = ($id === false); + + if (isset($cat_elt['@outlines'])) { + // Our cat_elt contains more categories or more feeds, so we + // add them recursively. + // Note: FreshRSS does not support yet category arborescence + $res = $this->addOpmlElements($cat_elt['@outlines'], $cat->name()); + if (!$error && $res) { + $error = true; } } + + return $error; } private function import_articles($article_file, $starred = false) { diff --git a/app/Exceptions/OpmlException.php b/app/Exceptions/OpmlException.php deleted file mode 100644 index e0ea3e493..000000000 --- a/app/Exceptions/OpmlException.php +++ /dev/null @@ -1,6 +0,0 @@ -searchByName($category->name())) { + // Category does not exist yet in DB so we add it before continue + $values = array( + 'name' => $category->name(), + ); + return $this->addCategory($values); + } + + return false; + } + public function updateCategory ($id, $valuesTmp) { $sql = 'UPDATE `' . $this->prefix . 'category` SET name=? WHERE id=?'; $stm = $this->bd->prepare ($sql); diff --git a/app/Models/FeedDAO.php b/app/Models/FeedDAO.php index ca25c3aeb..eac21df7e 100644 --- a/app/Models/FeedDAO.php +++ b/app/Models/FeedDAO.php @@ -24,6 +24,35 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo { } } + public function addFeedObject($feed) { + // TODO: not sure if we should write this method in DAO since DAO + // should not be aware about feed class + + // Add feed only if we don't find it in DB + if (!$this->searchByUrl($feed->url())) { + $values = array( + 'id' => $feed->id(), + 'url' => $feed->url(), + 'category' => $feed->category(), + 'name' => $feed->name(), + 'website' => $feed->website(), + 'description' => $feed->description(), + 'lastUpdate' => 0, + 'httpAuth' => $feed->httpAuth() + ); + + $id = $this->addFeed($values); + if ($id) { + $feed->_id($id); + $feed->faviconPrepare(); + } + + return $id; + } + + return false; + } + public function updateFeed ($id, $valuesTmp) { $set = ''; foreach ($valuesTmp as $key => $v) { diff --git a/app/views/helpers/export/opml.phtml b/app/views/helpers/export/opml.phtml index 2e66e5054..adbac904d 100644 --- a/app/views/helpers/export/opml.phtml +++ b/app/views/helpers/export/opml.phtml @@ -1,15 +1,30 @@ '; -?> - - - - <?php echo Minz_Configuration::title (); ?> OPML Feed - - - -categories); ?> - - + +$opml_array = array( + 'head' => array( + 'title' => Minz_Configuration::title(), + 'dateCreated' => date('D, d M Y H:i:s') + ), + 'body' => array() +); + +foreach ($this->categories as $key => $cat) { + $opml_array['body'][$key] = array( + 'text' => $cat['name'], + '@outlines' => array() + ); + + foreach ($cat['feeds'] as $feed) { + $opml_array['body'][$key]['@outlines'][] = array( + 'text' => $feed->name(), + 'type' => 'rss', + 'xmlUrl' => $feed->url(), + 'htmlUrl' => $feed->website(), + 'description' => htmlspecialchars( + $feed->description(), ENT_COMPAT, 'UTF-8' + ) + ); + } +} + +echo libopml_render($opml_array); diff --git a/lib/lib_opml.php b/lib/lib_opml.php index 05e54d85e..16a9921ea 100644 --- a/lib/lib_opml.php +++ b/lib/lib_opml.php @@ -1,23 +1,86 @@ ' . "\n"; - - foreach ($cat['feeds'] as $feed) { - $txt .= "\t" . '' . "\n"; +/* * + * lib_opml is a free library to manage OPML format in PHP. + * It takes in consideration only version 2.0 (http://dev.opml.org/spec2.html). + * Basically it means "text" attribute for outline elements is required. + * + * lib_opml requires SimpleXML (http://php.net/manual/en/book.simplexml.php) + * + * Usages: + * > include('lib_opml.php'); + * > $filename = 'my_opml_file.xml'; + * > $opml_array = libopml_parse_file($filename); + * > print_r($opml_array); + * + * > $opml_string = [...]; + * > $opml_array = libopml_parse_string($opml_string); + * > print_r($opml_array); + * + * > $opml_array = [...]; + * > $opml_string = libopml_render($opml_array); + * > $opml_object = libopml_render($opml_array, true); + * > echo $opml_string; + * > print_r($opml_object); + * + * If parsing fails for any reason (e.g. not an XML string, does not match with + * the specifications), a LibOPML_Exception is raised. + * + * Author: Marien Fressinaud + * Url: https://github.com/marienfressinaud/lib_opml + * Version: 0.1 + * Date: 2014-03-29 + * License: public domain + * + * */ + +class LibOPML_Exception extends Exception {} + + +// These elements are optional +define('HEAD_ELEMENTS', serialize(array( + 'title', 'dateCreated', 'dateModified', 'ownerName', 'ownerEmail', + 'ownerId', 'docs', 'expansionState', 'vertScrollState', 'windowTop', + 'windowLeft', 'windowBottom', 'windowRight' +))); + + +function libopml_parse_outline($outline_xml) { + $outline = array(); + + // An outline may contain any kind of attributes but "text" attribute is + // required ! + $text_is_present = false; + foreach ($outline_xml->attributes() as $key => $value) { + $outline[$key] = (string)$value; + + if ($key === 'text') { + $text_is_present = true; } + } - $txt .= '' . "\n"; + if (!$text_is_present) { + throw new LibOPML_Exception( + 'Outline does not contain any text attribute' + ); } - return $txt; + foreach ($outline_xml->children() as $key => $value) { + // An outline may contain any number of outline children + if ($key === 'outline') { + $outline['@outlines'][] = libopml_parse_outline($value); + } else { + throw new LibOPML_Exception( + 'Body can contain only outline elements' + ); + } + } + + return $outline; } -function opml_import ($xml) { - $xml = html_only_entity_decode($xml); //!\ Assume UTF-8 +function libopml_parse_string($xml) { $dom = new DOMDocument(); $dom->recover = true; $dom->strictErrorChecking = false; @@ -27,94 +90,142 @@ function opml_import ($xml) { $opml = simplexml_import_dom($dom); if (!$opml) { - throw new FreshRSS_Opml_Exception (); + throw new LibOPML_Exception(); } - $catDAO = new FreshRSS_CategoryDAO(); - $catDAO->checkDefault(); - $defCat = $catDAO->getDefault(); + $array = array( + 'version' => (string)$opml['version'], + 'head' => array(), + 'body' => array() + ); + + // First, we get all "head" elements. Head is required but its sub-elements + // are optional. + foreach ($opml->head->children() as $key => $value) { + if (in_array($key, unserialize(HEAD_ELEMENTS), true)) { + $array['head'][$key] = (string)$value; + } else { + throw new LibOPML_Exception( + $key . 'is not part of OPML format' + ); + } + } - $categories = array (); - $feeds = array (); + // Then, we get body oulines. Body must contain at least one outline + // element. + $at_least_one_outline = false; + foreach ($opml->body->children() as $key => $value) { + if ($key === 'outline') { + $at_least_one_outline = true; + $array['body'][] = libopml_parse_outline($value); + } else { + throw new LibOPML_Exception( + 'Body can contain only outline elements' + ); + } + } + + if (!$at_least_one_outline) { + throw new LibOPML_Exception( + 'Body must contain at least one outline element' + ); + } - foreach ($opml->body->outline as $outline) { - if (!isset ($outline['xmlUrl'])) { - // Catégorie - $title = ''; + return $array; +} - if (isset ($outline['text'])) { - $title = (string) $outline['text']; - } elseif (isset ($outline['title'])) { - $title = (string) $outline['title']; - } - if ($title) { - // Permet d'éviter les soucis au niveau des id : - // ceux-ci sont générés en fonction de la date, - // un flux pourrait être dans une catégorie X avec l'id Y - // alors qu'il existe déjà la catégorie X mais avec l'id Z - // Y ne sera pas ajouté et le flux non plus vu que l'id - // de sa catégorie n'exisera pas - $title = htmlspecialchars($title, ENT_COMPAT, 'UTF-8'); - $catDAO = new FreshRSS_CategoryDAO (); - $cat = $catDAO->searchByName ($title); - if ($cat == null) { - $cat = new FreshRSS_Category ($title); - $values = array ( - 'name' => $cat->name () - ); - $cat->_id ($catDAO->addCategory ($values)); - } - - $feeds = array_merge ($feeds, getFeedsOutline ($outline, $cat->id ())); +function libopml_parse_file($filename) { + $file_content = file_get_contents($filename); + + if ($file_content === false) { + throw new LibOPML_Exception( + $filename . ' cannot be found' + ); + } + + return libopml_parse_string($file_content); +} + + +function libopml_render_outline($parent_elt, $outline) { + // Outline MUST be an array! + if (!is_array($outline)) { + throw new LibOPML_Exception( + 'Outline element must be defined as array' + ); + } + + $outline_elt = $parent_elt->addChild('outline'); + $text_is_present = false; + foreach ($outline as $key => $value) { + // Only outlines can be an array and so we consider children are also + // outline elements. + if ($key === '@outlines' && is_array($value)) { + foreach ($value as $outline_child) { + libopml_render_outline($outline_elt, $outline_child); } + } elseif (is_array($value)) { + throw new LibOPML_Exception( + 'Type of outline elements cannot be array: ' . $key + ); } else { - // Flux rss sans catégorie, on récupère l'ajoute dans la catégorie par défaut - $feeds[] = getFeed ($outline, $defCat->id()); + // Detect text attribute is present, that's good :) + if ($key === 'text') { + $text_is_present = true; + } + + $outline_elt->addAttribute($key, $value); } } - return array ($categories, $feeds); + if (!$text_is_present) { + throw new LibOPML_Exception( + 'You must define at least a text element for all outlines' + ); + } } -/** - * import all feeds of a given outline tag - */ -function getFeedsOutline ($outline, $cat_id) { - $feeds = array (); - foreach ($outline->children () as $child) { - if (isset ($child['xmlUrl'])) { - $feeds[] = getFeed ($child, $cat_id); - } else { - $feeds = array_merge( - $feeds, - getFeedsOutline ($child, $cat_id) - ); +function libopml_render($array, $as_xml_object = false) { + $opml = new SimpleXMLElement(''); + + // Create head element. $array['head'] is optional but head element will + // exist in the final XML object. + $head = $opml->addChild('head'); + if (isset($array['head'])) { + foreach ($array['head'] as $key => $value) { + if (in_array($key, unserialize(HEAD_ELEMENTS), true)) { + $head->addChild($key, $value); + } } } - return $feeds; -} + // Check body is set and contains at least one element + if (!isset($array['body'])) { + throw new LibOPML_Exception( + '$array must contain a body element' + ); + } + if (count($array['body']) <= 0) { + throw new LibOPML_Exception( + 'Body element must contain at least one element (array)' + ); + } -function getFeed ($outline, $cat_id) { - $url = (string) $outline['xmlUrl']; - $url = htmlspecialchars($url, ENT_COMPAT, 'UTF-8'); - $title = ''; - if (isset ($outline['text'])) { - $title = (string) $outline['text']; - } elseif (isset ($outline['title'])) { - $title = (string) $outline['title']; - } - $title = htmlspecialchars($title, ENT_COMPAT, 'UTF-8'); - $feed = new FreshRSS_Feed ($url); - $feed->_category ($cat_id); - $feed->_name ($title); - if (isset($outline['htmlUrl'])) { - $feed->_website(htmlspecialchars((string)$outline['htmlUrl'], ENT_COMPAT, 'UTF-8')); - } - if (isset($outline['description'])) { - $feed->_description(sanitizeHTML((string)$outline['description'])); - } - return $feed; + // Create outline elements + $body = $opml->addChild('body'); + foreach ($array['body'] as $outline) { + libopml_render_outline($body, $outline); + } + + // And return the final result + if ($as_xml_object) { + return $opml; + } else { + $dom = dom_import_simplexml($opml)->ownerDocument; + $dom->formatOutput = true; + $dom->encoding = 'UTF-8'; + return $dom->saveXML(); + } } diff --git a/lib/lib_rss.php b/lib/lib_rss.php index 2077fe63f..0f8161129 100644 --- a/lib/lib_rss.php +++ b/lib/lib_rss.php @@ -244,3 +244,7 @@ function cryptAvailable() { } return false; } + +function html_chars_utf8($str) { + return htmlspecialchars($str, ENT_COMPAT, 'UTF-8'); +} -- cgit v1.2.3 From 779afe9c4ee00f8099a423e1fceee40197a90cfa Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Sun, 30 Mar 2014 14:28:13 +0200 Subject: Import of articles is implemented! - Remove massiveImportAction and addCategories from FeedController - Fix typo for some methods (camelCase) - addCategoryObject and addFeedObject return id if corresponding object already exists in DB - introduce addEntryObject. Return -1 if Entry already exist (in order to keep quite good performances) - Complete importArticles method Need some more tests + better performance --- app/Controllers/feedController.php | 88 ---------------------------- app/Controllers/importExportController.php | 92 +++++++++++++++++++++--------- app/Models/CategoryDAO.php | 5 +- app/Models/EntryDAO.php | 30 ++++++++++ app/Models/FeedDAO.php | 5 +- 5 files changed, 100 insertions(+), 120 deletions(-) (limited to 'app/Controllers') diff --git a/app/Controllers/feedController.php b/app/Controllers/feedController.php index 43435d99d..95c9c1e9c 100755 --- a/app/Controllers/feedController.php +++ b/app/Controllers/feedController.php @@ -327,82 +327,6 @@ class FreshRSS_feed_Controller extends Minz_ActionController { } } - public function massiveImportAction () { - # TODO: this function has moved to importExportController.php - # I keep it for the moment but should be deleted in a near future. - @set_time_limit(300); - - $this->catDAO = new FreshRSS_CategoryDAO (); - $this->catDAO->checkDefault (); - - $entryDAO = new FreshRSS_EntryDAO (); - $feedDAO = new FreshRSS_FeedDAO (); - - $categories = Minz_Request::param ('categories', array (), true); - $feeds = Minz_Request::param ('feeds', array (), true); - - // on ajoute les catégories en masse dans une fonction à part - $this->addCategories ($categories); - - // on calcule la date des articles les plus anciens qu'on accepte - $nb_month_old = $this->view->conf->old_entries; - $date_min = time () - (3600 * 24 * 30 * $nb_month_old); - - // la variable $error permet de savoir si une erreur est survenue - // Le but est de ne pas arrêter l'import même en cas d'erreur - // L'utilisateur sera mis au courant s'il y a eu des erreurs, mais - // ne connaîtra pas les détails. Ceux-ci seront toutefois logguées - $error = false; - $i = 0; - foreach ($feeds as $feed) { - try { - $values = array ( - 'id' => $feed->id (), - 'url' => $feed->url (), - 'category' => $feed->category (), - 'name' => $feed->name (), - 'website' => $feed->website (), - 'description' => $feed->description (), - 'lastUpdate' => 0, - 'httpAuth' => $feed->httpAuth () - ); - - // ajout du flux que s'il n'est pas déjà en BDD - if (!$feedDAO->searchByUrl ($values['url'])) { - $id = $feedDAO->addFeed ($values); - if ($id) { - $feed->_id ($id); - $feed->faviconPrepare(); - } else { - $error = true; - } - } - } catch (FreshRSS_Feed_Exception $e) { - $error = true; - Minz_Log::record ($e->getMessage (), Minz_Log::WARNING); - } - } - - if ($error) { - $res = Minz_Translate::t ('feeds_imported_with_errors'); - } else { - $res = Minz_Translate::t ('feeds_imported'); - } - - $notif = array ( - 'type' => 'good', - 'content' => $res - ); - Minz_Session::_param ('notification', $notif); - Minz_Session::_param ('actualize_feeds', true); - - // et on redirige vers la page d'accueil - Minz_Request::forward (array ( - 'c' => 'index', - 'a' => 'index' - ), true); - } - public function deleteAction () { if (Minz_Request::isPost ()) { $type = Minz_Request::param ('type', 'feed'); @@ -446,16 +370,4 @@ class FreshRSS_feed_Controller extends Minz_ActionController { } } } - - private function addCategories ($categories) { - foreach ($categories as $cat) { - if (!$this->catDAO->searchByName ($cat->name ())) { - $values = array ( - 'id' => $cat->id (), - 'name' => $cat->name (), - ); - $catDAO->addCategory ($values); - } - } - } } diff --git a/app/Controllers/importExportController.php b/app/Controllers/importExportController.php index b6b4d0fed..3b6bf2c5c 100644 --- a/app/Controllers/importExportController.php +++ b/app/Controllers/importExportController.php @@ -31,7 +31,7 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { @set_time_limit(300); $file = $_FILES['file']; - $type_file = $this->guess_file_type($file['name']); + $type_file = $this->guessFileType($file['name']); $list_files = array( 'opml' => array(), @@ -46,7 +46,7 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { $zip = zip_open($file['tmp_name']); while (($zipfile = zip_read($zip)) !== false) { - $type_zipfile = $this->guess_file_type(zip_entry_name($zipfile)); + $type_zipfile = $this->guessFileType(zip_entry_name($zipfile)); if ($type_file !== 'unknown') { $list_files[$type_zipfile][] = zip_entry_read( @@ -67,13 +67,13 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { // And finally all other files. $error = false; foreach ($list_files['opml'] as $opml_file) { - $error = $this->import_opml($opml_file); + $error = $this->importOpml($opml_file); } foreach ($list_files['json_starred'] as $article_file) { - $error = $this->import_articles($article_file, true); + $error = $this->importArticles($article_file, true); } foreach ($list_files['json_feed'] as $article_file) { - $error = $this->import_articles($article_file); + $error = $this->importArticles($article_file); } // And finally, we get import status and redirect to the home page @@ -107,7 +107,7 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { )); } - private function guess_file_type($filename) { + private function guessFileType($filename) { // A *very* basic guess file type function. Only based on filename // That's could be improved but should be enough, at least for a first // implementation. @@ -128,7 +128,7 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { } } - private function import_opml($opml_file) { + private function importOpml($opml_file) { $opml_array = array(); try { $opml_array = libopml_parse_string($opml_file); @@ -164,7 +164,7 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { private function addFeedOpml($feed_elt, $parent_cat) { if (is_null($parent_cat)) { // This feed has no parent category so we get the default one - $parent_cat = $catDAO->getDefault()->name(); + $parent_cat = $this->catDAO->getDefault()->name(); } $cat = $this->catDAO->searchByName($parent_cat); @@ -199,7 +199,7 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { $id = $this->feedDAO->addFeedObject($feed); $error = ($id === false); } catch (FreshRSS_Feed_Exception $e) { - Minz_Log::record($e->getMessage(), Minz_Log::WARNING); + Minz_Log::warning($e->getMessage()); $error = true; } @@ -226,28 +226,23 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { return $error; } - private function import_articles($article_file, $starred = false) { + private function importArticles($article_file, $starred = false) { $article_object = json_decode($article_file, true); if (is_null($article_object)) { Minz_Log::warning('Try to import a non-JSON file'); return true; } + $is_read = $this->view->conf->mark_when['reception'] ? 1 : 0; + $google_compliant = (strpos($article_object['id'], 'com.google') !== false); + $error = false; foreach ($article_object['items'] as $item) { - $key = $google_compliant ? 'htmlUrl' : 'feedUrl'; - $feed = $this->feedDAO->searchByUrl($item['origin'][$key]); + $feed = $this->addFeedArticles($item['origin'], $google_compliant); if (is_null($feed)) { - $feed = new FreshRSS_Feed($item['origin'][$key]); - $feed->_name ($item['origin']['title']); - $feed->_website ($item['origin']['htmlUrl']); - - $error = $this->addFeed($feed); // TODO - - if ($error) { - continue; - } + $error = true; + continue; } $author = isset($item['author']) ? $item['author'] : ''; @@ -258,14 +253,55 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { return strpos($var, '/state/com.google') === false; }); } + $entry = new FreshRSS_Entry( $feed->id(), $item['id'], $item['title'], $author, $item[$key_content]['content'], $item['alternate'][0]['href'], - $item['published'], false, $starred, $tags + $item['published'], $is_read, $starred ); + $entry->_tags($tags); - Minz_Log::debug(print_r($entry, true)); // TODO + $id = $this->entryDAO->addEntryObject( + $entry, $this->view->conf, $feed->keepHistory() + ); + + if (!$error && ($id === false)) { + $error = true; + } } + + return $error; + } + + private function addFeedArticles($origin, $google_compliant) { + $default_cat = $this->catDAO->getDefault(); + + $return = null; + $key = $google_compliant ? 'htmlUrl' : 'feedUrl'; + $url = $origin[$key]; + $name = $origin['title']; + $website = $origin['htmlUrl']; + $error = false; + try { + // Create a Feed object and add it in DB + $feed = new FreshRSS_Feed($url); + $feed->_category($default_cat->id()); + $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); + + if ($id !== false) { + $feed->_id($id); + $return = $feed; + } + } catch (FreshRSS_Feed_Exception $e) { + Minz_Log::warning($e->getMessage()); + } + + return $return; } public function exportAction() { @@ -283,16 +319,16 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { // Stuff with content if ($export_opml) { - $zip->addFromString('feeds.opml', $this->generate_opml()); + $zip->addFromString('feeds.opml', $this->generateOpml()); } if ($export_starred) { - $zip->addFromString('starred.json', $this->generate_articles('starred')); + $zip->addFromString('starred.json', $this->generateArticles('starred')); } foreach ($export_feeds as $feed_id) { $feed = $this->feedDAO->searchById($feed_id); $zip->addFromString( 'feed_' . $feed->category() . '_' . $feed->id() . '.json', - $this->generate_articles('feed', $feed) + $this->generateArticles('feed', $feed) ); } @@ -306,7 +342,7 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { } } - private function generate_opml() { + private function generateOpml() { $list = array(); foreach ($this->catDAO->listCategories() as $key => $cat) { $list[$key]['name'] = $cat->name(); @@ -317,7 +353,7 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { return $this->view->helperToString('export/opml'); } - private function generate_articles($type, $feed = NULL) { + private function generateArticles($type, $feed = NULL) { $this->view->categories = $this->catDAO->listCategories(); if ($type == 'starred') { diff --git a/app/Models/CategoryDAO.php b/app/Models/CategoryDAO.php index 8be732b98..6a9b839b9 100644 --- a/app/Models/CategoryDAO.php +++ b/app/Models/CategoryDAO.php @@ -19,7 +19,8 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo { } public function addCategoryObject($category) { - if (!$this->searchByName($category->name())) { + $cat = $this->searchByName($category->name()); + if (!$cat) { // Category does not exist yet in DB so we add it before continue $values = array( 'name' => $category->name(), @@ -27,7 +28,7 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo { return $this->addCategory($values); } - return false; + return $cat->id(); } public function updateCategory ($id, $valuesTmp) { diff --git a/app/Models/EntryDAO.php b/app/Models/EntryDAO.php index e4cf128ea..6d00967fc 100644 --- a/app/Models/EntryDAO.php +++ b/app/Models/EntryDAO.php @@ -35,6 +35,36 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { } } + public function addEntryObject($entry, $conf, $feedHistory) { + $existingGuids = array_fill_keys( + $this->listLastGuidsByFeed($entry->feed(), 20), 1 + ); + + $nb_month_old = max($conf->old_entries, 1); + $date_min = time() - (3600 * 24 * 30 * $nb_month_old); + + $eDate = $entry->date(true); + + if ($feedHistory == -2) { + $feedHistory = $conf->keep_history_default; + } + + if (!isset($existingGuids[$entry->guid()]) && + ($feedHistory != 0 || $eDate >= $date_min)) { + $values = $entry->toArray(); + + $useDeclaredDate = empty($existingGuids); + $values['id'] = ($useDeclaredDate || $eDate < $date_min) ? + min(time(), $eDate) . uSecString() : + uTimeString(); + + return $this->addEntry($values); + } + + // We don't return Entry object to avoid a research in DB + return -1; + } + public function markFavorite($ids, $is_favorite = true) { if (!is_array($ids)) { $ids = array($ids); diff --git a/app/Models/FeedDAO.php b/app/Models/FeedDAO.php index eac21df7e..b65ff4af0 100644 --- a/app/Models/FeedDAO.php +++ b/app/Models/FeedDAO.php @@ -29,7 +29,8 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo { // should not be aware about feed class // Add feed only if we don't find it in DB - if (!$this->searchByUrl($feed->url())) { + $feed_search = $this->searchByUrl($feed->url()); + if (!$feed_search) { $values = array( 'id' => $feed->id(), 'url' => $feed->url(), @@ -50,7 +51,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo { return $id; } - return false; + return $feed_search->id(); } public function updateFeed ($id, $valuesTmp) { -- cgit v1.2.3 From 34b17b748efb996f0202815dcf095a45d28e38da Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Sun, 30 Mar 2014 14:55:29 +0200 Subject: Fix coding style --- app/Controllers/importExportController.php | 49 ++++++++++++++++++------------ app/layout/aside_feed.phtml | 2 +- 2 files changed, 31 insertions(+), 20 deletions(-) (limited to 'app/Controllers') diff --git a/app/Controllers/importExportController.php b/app/Controllers/importExportController.php index 3b6bf2c5c..02d62c890 100644 --- a/app/Controllers/importExportController.php +++ b/app/Controllers/importExportController.php @@ -20,9 +20,6 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { $this->view->categories = $this->catDAO->listCategories(); $this->view->feeds = $this->feedDAO->listFeeds(); - // au niveau de la vue, permet de ne pas voir un flux sélectionné dans la liste - $this->view->flux = false; - Minz_View::prependTitle(Minz_Translate::t('import_export') . ' · '); } @@ -46,7 +43,9 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { $zip = zip_open($file['tmp_name']); while (($zipfile = zip_read($zip)) !== false) { - $type_zipfile = $this->guessFileType(zip_entry_name($zipfile)); + $type_zipfile = $this->guessFileType( + zip_entry_name($zipfile) + ); if ($type_file !== 'unknown') { $list_files[$type_zipfile][] = zip_entry_read( @@ -58,7 +57,9 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { zip_close($zip); } elseif ($type_file !== 'unknown') { - $list_files[$type_file][] = file_get_contents($file['tmp_name']); + $list_files[$type_file][] = file_get_contents( + $file['tmp_name'] + ); } // Import different files. @@ -79,18 +80,19 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { // And finally, we get import status and redirect to the home page $notif = null; if ($error === true) { - $notif = array( - 'type' => 'good', - 'content' => Minz_Translate::t('feeds_imported_with_errors') + $content_notif = Minz_Translate::t( + 'feeds_imported_with_errors' ); } else { - $notif = array( - 'type' => 'good', - 'content' => Minz_Translate::t('feeds_imported') + $content_notif = Minz_Translate::t( + 'feeds_imported' ); } - Minz_Session::_param('notification', $notif); + Minz_Session::_param('notification', array( + 'type' => 'good', + 'content' => $content_notif + )); Minz_Session::_param('actualize_feeds', true); Minz_Request::forward(array( @@ -235,7 +237,9 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { $is_read = $this->view->conf->mark_when['reception'] ? 1 : 0; - $google_compliant = (strpos($article_object['id'], 'com.google') !== false); + $google_compliant = ( + strpos($article_object['id'], 'com.google') !== false + ); $error = false; foreach ($article_object['items'] as $item) { @@ -246,7 +250,8 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { } $author = isset($item['author']) ? $item['author'] : ''; - $key_content = $google_compliant && !isset($item['content']) ? 'summary' : 'content'; + $key_content = ($google_compliant && !isset($item['content'])) ? + 'summary' : 'content'; $tags = $item['categories']; if ($google_compliant) { $tags = array_filter($tags, function($var) { @@ -312,17 +317,21 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { $export_starred = Minz_Request::param('export_starred', false); $export_feeds = Minz_Request::param('export_feeds', false); - // code from https://stackoverflow.com/questions/1061710/php-zip-files-on-the-fly + // From https://stackoverflow.com/questions/1061710/php-zip-files-on-the-fly $file = tempnam('tmp', 'zip'); $zip = new ZipArchive(); $zip->open($file, ZipArchive::OVERWRITE); // Stuff with content if ($export_opml) { - $zip->addFromString('feeds.opml', $this->generateOpml()); + $zip->addFromString( + 'feeds.opml', $this->generateOpml() + ); } if ($export_starred) { - $zip->addFromString('starred.json', $this->generateArticles('starred')); + $zip->addFromString( + 'starred.json', $this->generateArticles('starred') + ); } foreach ($export_feeds as $feed_id) { $feed = $this->feedDAO->searchById($feed_id); @@ -357,14 +366,16 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { $this->view->categories = $this->catDAO->listCategories(); if ($type == 'starred') { - $this->view->list_title = Minz_Translate::t("starred_list"); + $this->view->list_title = Minz_Translate::t('starred_list'); $this->view->type = 'starred'; $this->view->entries = $this->entryDAO->listWhere( 's', '', 'all', 'ASC', $this->entryDAO->countUnreadReadFavorites()['all'] ); } elseif ($type == 'feed' && !is_null($feed)) { - $this->view->list_title = Minz_Translate::t("feed_list", $feed->name()); + $this->view->list_title = Minz_Translate::t( + 'feed_list', $feed->name() + ); $this->view->type = 'feed/' . $feed->id(); $this->view->entries = $this->entryDAO->listWhere( 'f', $feed->id(), 'all', 'ASC', diff --git a/app/layout/aside_feed.phtml b/app/layout/aside_feed.phtml index ae16efd96..1fad60af5 100644 --- a/app/layout/aside_feed.phtml +++ b/app/layout/aside_feed.phtml @@ -56,7 +56,7 @@ feeds)) { ?> feeds as $feed) { ?> nbEntries (); ?> -
  • +
  • ✇ name (); ?> -- cgit v1.2.3 From 19517baf13dba7ebd7d41dbbacceaea3ed75af8e Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Sun, 30 Mar 2014 17:49:39 +0200 Subject: Add a bookmark to easily subscribe to websites - FeedController->addAction (GET) shows a confirmation page - If already subscribe, redirect to Configure->feedAction - Add a bookmark in aside_feed See #425 #426 and #464 --- app/Controllers/feedController.php | 53 +++++++++++++++++++--- app/i18n/en.php | 1 + app/i18n/fr.php | 3 +- app/layout/aside_feed.phtml | 6 +++ app/views/feed/add.phtml | 91 ++++++++++++++++++++++++++++++++++++++ p/scripts/main.js | 2 +- 6 files changed, 148 insertions(+), 8 deletions(-) create mode 100644 app/views/feed/add.phtml (limited to 'app/Controllers') diff --git a/app/Controllers/feedController.php b/app/Controllers/feedController.php index 16516ad39..503295587 100755 --- a/app/Controllers/feedController.php +++ b/app/Controllers/feedController.php @@ -22,13 +22,23 @@ class FreshRSS_feed_Controller extends Minz_ActionController { } public function addAction () { - @set_time_limit(300); + $url = Minz_Request::param('url_rss', false); + + if ($url === false) { + Minz_Request::forward(array( + 'c' => 'configure', + 'a' => 'feed' + ), true); + } + + $feedDAO = new FreshRSS_FeedDAO (); + $this->catDAO = new FreshRSS_CategoryDAO (); + $this->catDAO->checkDefault (); + + if (Minz_Request::isPost()) { + @set_time_limit(300); - if (Minz_Request::isPost ()) { - $this->catDAO = new FreshRSS_CategoryDAO (); - $this->catDAO->checkDefault (); - $url = Minz_Request::param ('url_rss'); $cat = Minz_Request::param ('category', false); if ($cat === 'nc') { $new_cat = Minz_Request::param ('new_category'); @@ -60,7 +70,6 @@ class FreshRSS_feed_Controller extends Minz_ActionController { $feed->load(true); - $feedDAO = new FreshRSS_FeedDAO (); $values = array ( 'url' => $feed->url (), 'category' => $feed->category (), @@ -154,6 +163,38 @@ class FreshRSS_feed_Controller extends Minz_ActionController { Minz_Request::forward (array ('c' => 'configure', 'a' => 'feed', 'params' => $params), true); } + + // GET request so we must ask confirmation to user + Minz_View::prependTitle(Minz_Translate::t('add_rss_feed') . ' · '); + $this->view->categories = $this->catDAO->listCategories(); + $this->view->feed = new FreshRSS_Feed($url); + try { + // We try to get some more information about the feed + $this->view->feed->load(true); + $this->view->load_ok = true; + } catch (Exception $e) { + $this->view->load_ok = false; + } + + $feed = $feedDAO->searchByUrl($this->view->feed->url()); + if ($feed) { + // Already subscribe so we redirect to the feed configuration page + $notif = array( + 'type' => 'bad', + 'content' => Minz_Translate::t( + 'already_subscribed', $feed->name() + ) + ); + Minz_Session::_param('notification', $notif); + + Minz_Request::forward(array( + 'c' => 'configure', + 'a' => 'feed', + 'params' => array( + 'id' => $feed->id() + ) + ), true); + } } public function truncateAction () { diff --git a/app/i18n/en.php b/app/i18n/en.php index e9bed39a7..3d516bbb4 100644 --- a/app/i18n/en.php +++ b/app/i18n/en.php @@ -22,6 +22,7 @@ return array ( 'add_rss_feed' => 'Add a RSS feed', 'no_rss_feed' => 'No RSS feed', 'import_export' => 'Import / export', + 'bookmark' => 'Subscribe (FreshRSS bookmark)', 'subscription_management' => 'Subscriptions management', 'main_stream' => 'Main stream', diff --git a/app/i18n/fr.php b/app/i18n/fr.php index 24f3741c8..9d745fc9a 100644 --- a/app/i18n/fr.php +++ b/app/i18n/fr.php @@ -22,6 +22,7 @@ return array ( 'add_rss_feed' => 'Ajouter un flux RSS', 'no_rss_feed' => 'Aucun flux RSS', 'import_export' => 'Importer / exporter', + 'bookmark' => 'S’abonner (bookmark FreshRSS)', 'subscription_management' => 'Gestion des abonnements', 'main_stream' => 'Flux principal', @@ -89,7 +90,7 @@ return array ( 'already_subscribed' => 'Vous êtes déjà abonné à %s', 'feed_added' => 'Le flux %s a bien été ajouté', 'feed_not_added' => '%s n’a pas pu être ajouté', - 'internal_problem_feed' => 'Le flux n’a pas pu être ajouté. Consulter les logs de FreshRSS pour plus de détails.', + 'internal_problem_feed' => 'Le flux ne peux pas être ajouté. Consulter les logs de FreshRSS pour plus de détails.', 'invalid_url' => 'L’url %s est invalide', 'feed_actualized' => '%s a été mis à jour', 'n_feeds_actualized' => '%d flux ont été mis à jour', diff --git a/app/layout/aside_feed.phtml b/app/layout/aside_feed.phtml index 899cafe02..7a5b2ce5e 100644 --- a/app/layout/aside_feed.phtml +++ b/app/layout/aside_feed.phtml @@ -43,6 +43,12 @@
  • +
  • + + + +
  • +
  • diff --git a/app/views/feed/add.phtml b/app/views/feed/add.phtml new file mode 100644 index 000000000..56ac5f8dd --- /dev/null +++ b/app/views/feed/add.phtml @@ -0,0 +1,91 @@ +feed) { ?> +
    +

    + + load_ok) { ?> +

    + + +
    + + load_ok) { ?> +
    + +
    + +
    +
    + + feed->description(); if ($desc != '') { ?> +
    + +
    + +
    +
    + + +
    + +
    + +
    +
    + + +
    + +
    + + +   +
    +
    +
    + +
    + + + + + +
    +
    + + + feed->httpAuth(false); ?> +
    + +
    + +
    + + +
    + +
    + +
    + +
    +
    + +
    +
    + + +
    +
    +
    +
    + diff --git a/p/scripts/main.js b/p/scripts/main.js index a03be85eb..818d459b3 100644 --- a/p/scripts/main.js +++ b/p/scripts/main.js @@ -988,7 +988,7 @@ function init_share_observers() { function init_feed_observers() { $('select[id="category"]').on('change', function() { - var detail = $(this).parent('li').next('li'); + var detail = $('#new_category_name').parent(); if ($(this).val() === 'nc') { detail.show(); detail.find('input').focus(); -- cgit v1.2.3 From 8ef32a10c6fd8e4ace44a5e797f6176f80b9560e Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Sun, 30 Mar 2014 20:47:00 +0200 Subject: Improve compatibility PHP 5.3 --- app/Controllers/importExportController.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'app/Controllers') diff --git a/app/Controllers/importExportController.php b/app/Controllers/importExportController.php index 02d62c890..a9b103a34 100644 --- a/app/Controllers/importExportController.php +++ b/app/Controllers/importExportController.php @@ -368,9 +368,10 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { if ($type == 'starred') { $this->view->list_title = Minz_Translate::t('starred_list'); $this->view->type = 'starred'; + $unread_fav = $this->entryDAO->countUnreadReadFavorites(); $this->view->entries = $this->entryDAO->listWhere( 's', '', 'all', 'ASC', - $this->entryDAO->countUnreadReadFavorites()['all'] + $unread_fav['all'] ); } elseif ($type == 'feed' && !is_null($feed)) { $this->view->list_title = Minz_Translate::t( -- cgit v1.2.3 From b55571de8f19fab15202826950184cfcb90d364c Mon Sep 17 00:00:00 2001 From: Alexis Degrugillier Date: Tue, 8 Apr 2014 23:26:41 -0400 Subject: Split reading configuration page I splitted the reading configuration page in 2 pages. The display configuration page to prepare for #265 and the reading configuration page. It is just a test, feel free to discard that. I also change some wording to be consistent through out the application. --- app/Controllers/configureController.php | 50 +++++++++---- app/i18n/en.php | 4 +- app/i18n/fr.php | 4 +- app/layout/aside_configure.phtml | 5 +- app/layout/header.phtml | 3 +- app/views/configure/display.phtml | 128 +------------------------------- app/views/configure/reading.phtml | 125 +++++++++++++++++++++++++++++++ app/views/configure/shortcut.phtml | 2 +- 8 files changed, 173 insertions(+), 148 deletions(-) create mode 100644 app/views/configure/reading.phtml (limited to 'app/Controllers') diff --git a/app/Controllers/configureController.php b/app/Controllers/configureController.php index b4de19e11..df5212041 100755 --- a/app/Controllers/configureController.php +++ b/app/Controllers/configureController.php @@ -140,21 +140,6 @@ class FreshRSS_configure_Controller extends Minz_ActionController { public function displayAction () { if (Minz_Request::isPost()) { $this->view->conf->_language(Minz_Request::param('language', 'en')); - $this->view->conf->_posts_per_page(Minz_Request::param('posts_per_page', 10)); - $this->view->conf->_view_mode(Minz_Request::param('view_mode', 'normal')); - $this->view->conf->_default_view (Minz_Request::param('default_view', 'a')); - $this->view->conf->_auto_load_more(Minz_Request::param('auto_load_more', false)); - $this->view->conf->_display_posts(Minz_Request::param('display_posts', false)); - $this->view->conf->_onread_jump_next(Minz_Request::param('onread_jump_next', false)); - $this->view->conf->_lazyload (Minz_Request::param('lazyload', false)); - $this->view->conf->_sticky_post (Minz_Request::param('sticky_post', false)); - $this->view->conf->_sort_order(Minz_Request::param('sort_order', 'DESC')); - $this->view->conf->_mark_when (array( - 'article' => Minz_Request::param('mark_open_article', false), - 'site' => Minz_Request::param('mark_open_site', false), - 'scroll' => Minz_Request::param('mark_scroll', false), - 'reception' => Minz_Request::param('mark_upon_reception', false), - )); $themeId = Minz_Request::param('theme', ''); if ($themeId == '') { $themeId = FreshRSS_Themes::defaultTheme; @@ -187,6 +172,41 @@ class FreshRSS_configure_Controller extends Minz_ActionController { $this->view->themes = FreshRSS_Themes::get(); + Minz_View::prependTitle (Minz_Translate::t ('display_configuration') . ' · '); + } + + public function readingAction () { + if (Minz_Request::isPost()) { + $this->view->conf->_posts_per_page(Minz_Request::param('posts_per_page', 10)); + $this->view->conf->_view_mode(Minz_Request::param('view_mode', 'normal')); + $this->view->conf->_default_view (Minz_Request::param('default_view', 'a')); + $this->view->conf->_auto_load_more(Minz_Request::param('auto_load_more', false)); + $this->view->conf->_display_posts(Minz_Request::param('display_posts', false)); + $this->view->conf->_onread_jump_next(Minz_Request::param('onread_jump_next', false)); + $this->view->conf->_lazyload (Minz_Request::param('lazyload', false)); + $this->view->conf->_sticky_post (Minz_Request::param('sticky_post', false)); + $this->view->conf->_sort_order(Minz_Request::param('sort_order', 'DESC')); + $this->view->conf->_mark_when (array( + 'article' => Minz_Request::param('mark_open_article', false), + 'site' => Minz_Request::param('mark_open_site', false), + 'scroll' => Minz_Request::param('mark_scroll', false), + 'reception' => Minz_Request::param('mark_upon_reception', false), + )); + $this->view->conf->save(); + + Minz_Session::_param ('language', $this->view->conf->language); + Minz_Translate::reset (); + invalidateHttpCache(); + + $notif = array ( + 'type' => 'good', + 'content' => Minz_Translate::t ('configuration_updated') + ); + Minz_Session::_param ('notification', $notif); + + Minz_Request::forward (array ('c' => 'configure', 'a' => 'reading'), true); + } + Minz_View::prependTitle (Minz_Translate::t ('reading_configuration') . ' · '); } diff --git a/app/i18n/en.php b/app/i18n/en.php index 91bb9adce..d6096bbe8 100644 --- a/app/i18n/en.php +++ b/app/i18n/en.php @@ -79,7 +79,6 @@ return array ( 'sharing_management' => 'Sharing options management', 'bad_opml_file' => 'Your OPML file is invalid', 'shortcuts_updated' => 'Shortcuts have been updated', - 'shortcuts_management' => 'Shortcuts management', 'shortcuts_navigation' => 'Navigation', 'shortcuts_navigation_help' => 'With the "Shift" modifier, navigation shortcuts apply on feeds.
    With the "Alt" modifier, navigation shortcuts apply on categories.', 'shortcuts_article_action' => 'Article actions', @@ -212,6 +211,7 @@ return array ( 'purge_completed' => 'Purge completed (%d articles deleted)', 'archiving_configuration_help' => 'More options are available in the individual stream settings', 'reading_configuration' => 'Reading', + 'display_configuration' => 'Display', 'articles_per_page' => 'Number of articles per page', 'default_view' => 'Default view', 'sort_order' => 'Sort order', @@ -219,7 +219,7 @@ return array ( 'display_articles_unfolded' => 'Show articles unfolded by default', 'after_onread' => 'After “mark all as read”,', 'jump_next' => 'jump to next unread sibling (feed or category)', - 'reading_icons' => 'Reading icons', + 'article_icons' => 'Article icons', 'top_line' => 'Top line', 'bottom_line' => 'Bottom line', 'img_with_lazyload' => 'Use "lazy load" mode to load pictures', diff --git a/app/i18n/fr.php b/app/i18n/fr.php index 0897db93d..d89cbb098 100644 --- a/app/i18n/fr.php +++ b/app/i18n/fr.php @@ -79,7 +79,6 @@ return array ( 'sharing_management' => 'Gestion des options de partage', 'bad_opml_file' => 'Votre fichier OPML n’est pas valide', 'shortcuts_updated' => 'Les raccourcis ont été mis à jour', - 'shortcuts_management' => 'Gestion des raccourcis', 'shortcuts_navigation' => 'Navigation', 'shortcuts_navigation_help' => 'Avec le modificateur "Shift", les raccourcis de navigation s’appliquent aux flux.
    Avec le modificateur "Alt", les raccourcis de navigation s’appliquent aux catégories.', 'shortcuts_article_action' => 'Actions associées à l’article courant', @@ -212,6 +211,7 @@ return array ( 'purge_completed' => 'Purge effectuée (%d articles supprimés)', 'archiving_configuration_help' => 'D’autres options sont disponibles dans la configuration individuelle des flux', 'reading_configuration' => 'Lecture', + 'display_configuration' => 'Affichage', 'articles_per_page' => 'Nombre d’articles par page', 'default_view' => 'Vue par défaut', 'sort_order' => 'Ordre de tri', @@ -219,7 +219,7 @@ return array ( 'display_articles_unfolded' => 'Afficher les articles dépliés par défaut', 'after_onread' => 'Après “marquer tout comme lu”,', 'jump_next' => 'sauter au prochain voisin non lu (flux ou catégorie)', - 'reading_icons' => 'Icônes de lecture', + 'article_icons' => 'Icônes d’article', 'top_line' => 'Ligne du haut', 'bottom_line' => 'Ligne du bas', 'img_with_lazyload' => 'Utiliser le mode “chargement différé” pour les images', diff --git a/app/layout/aside_configure.phtml b/app/layout/aside_configure.phtml index 27f11ab6d..43adeb3c6 100644 --- a/app/layout/aside_configure.phtml +++ b/app/layout/aside_configure.phtml @@ -1,7 +1,10 @@ diff --git a/app/views/stats/main.phtml b/app/views/stats/main.phtml deleted file mode 100644 index fe372e221..000000000 --- a/app/views/stats/main.phtml +++ /dev/null @@ -1,127 +0,0 @@ -partial('aside_stats'); ?> - -
    - - -

    - -
    -

    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
     
    repartition['main_stream']['total']); ?>repartition['all_feeds']['total']); ?>
    repartition['main_stream']['read']); ?>repartition['all_feeds']['read']); ?>
    repartition['main_stream']['unread']); ?>repartition['all_feeds']['unread']); ?>
    repartition['main_stream']['favorite']); ?>repartition['all_feeds']['favorite']); ?>
    -
    - -
    -

    -
    -
    - -
    -

    -
    -
    -
    - -
    -

    -
    -
    -
    - -
    -

    - - - - - - - - - - topFeed as $feed): ?> - - - - - - - -
    -
    -
    - - diff --git a/app/views/stats/repartition.phtml b/app/views/stats/repartition.phtml new file mode 100644 index 000000000..4455abe2a --- /dev/null +++ b/app/views/stats/repartition.phtml @@ -0,0 +1,100 @@ +partial('aside_stats'); ?> + +
    + + + feed) {?> +

    + + + feed->name(); ?> + +

    + +

    + + +
    +

    +
    +
    + +
    +

    +
    +
    + +
    +

    +
    +
    +
    + + \ No newline at end of file -- cgit v1.2.3 From ad9fbf3887b79d69dcb53080e3f7c367b5bc17e1 Mon Sep 17 00:00:00 2001 From: plopoyop Date: Sat, 26 Jul 2014 13:29:55 +0200 Subject: Correct bug in add/remove users --- app/Controllers/usersController.php | 4 ++-- app/Models/UserDAO.php | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) (limited to 'app/Controllers') diff --git a/app/Controllers/usersController.php b/app/Controllers/usersController.php index 35fa3675f..a9e6c32bc 100644 --- a/app/Controllers/usersController.php +++ b/app/Controllers/usersController.php @@ -100,7 +100,7 @@ class FreshRSS_users_Controller extends Minz_ActionController { public function createAction() { if (Minz_Request::isPost() && Minz_Configuration::isAdmin(Minz_Session::param('currentUser', '_'))) { $db = Minz_Configuration::dataBase(); - require_once(APP_PATH . '/SQL/sql.' . $db['type'] . '.php'); + require_once(APP_PATH . '/SQL/install.sql.' . $db['type'] . '.php'); $new_user_language = Minz_Request::param('new_user_language', $this->view->conf->language); if (!in_array($new_user_language, $this->view->conf->availableLanguages())) { @@ -172,7 +172,7 @@ class FreshRSS_users_Controller extends Minz_ActionController { public function deleteAction() { if (Minz_Request::isPost() && Minz_Configuration::isAdmin(Minz_Session::param('currentUser', '_'))) { $db = Minz_Configuration::dataBase(); - require_once(APP_PATH . '/SQL/sql.' . $db['type'] . '.php'); + require_once(APP_PATH . '/SQL/install.sql.' . $db['type'] . '.php'); $username = Minz_Request::param('username'); $ok = ctype_alnum($username); diff --git a/app/Models/UserDAO.php b/app/Models/UserDAO.php index dcf847a62..1763fac67 100644 --- a/app/Models/UserDAO.php +++ b/app/Models/UserDAO.php @@ -3,11 +3,11 @@ class FreshRSS_UserDAO extends Minz_ModelPdo { public function createUser($username) { $db = Minz_Configuration::dataBase(); - require_once(APP_PATH . '/SQL/sql.' . $db['type'] . '.php'); + require_once(APP_PATH . '/SQL/install.sql.' . $db['type'] . '.php'); if (defined('SQL_CREATE_TABLES')) { $sql = sprintf(SQL_CREATE_TABLES, $db['prefix'] . $username . '_', Minz_Translate::t('default_category')); - $stm = $c->prepare($sql); + $stm = $this->bd->prepare($sql); $ok = $stm && $stm->execute(); } else { global $SQL_CREATE_TABLES; @@ -32,7 +32,7 @@ class FreshRSS_UserDAO extends Minz_ModelPdo { public function deleteUser($username) { $db = Minz_Configuration::dataBase(); - require_once(APP_PATH . '/SQL/sql.' . $db['type'] . '.php'); + require_once(APP_PATH . '/SQL/install.sql.' . $db['type'] . '.php'); $sql = sprintf(SQL_DROP_TABLES, $db['prefix'] . $username . '_'); $stm = $this->bd->prepare($sql); -- cgit v1.2.3 From 35be1769de28df3fff1a26e40d1d6b1e587a2847 Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Fri, 1 Aug 2014 20:20:25 +0200 Subject: Basic protection against XSRF using Referer https://github.com/marienfressinaud/FreshRSS/issues/554 Also edited the error controler to use the log message passed in Minz_Error::error(). --- app/Controllers/errorController.php | 52 +++++++++++++++++++++++-------------- app/FreshRSS.php | 11 +++++++- app/views/error/index.phtml | 13 ++-------- lib/Minz/Translate.php | 2 +- 4 files changed, 45 insertions(+), 33 deletions(-) (limited to 'app/Controllers') diff --git a/app/Controllers/errorController.php b/app/Controllers/errorController.php index dc9a2ee25..922650b3d 100644 --- a/app/Controllers/errorController.php +++ b/app/Controllers/errorController.php @@ -1,26 +1,38 @@ view->code = 'Error 403 - Forbidden'; - break; - case 404: - $this->view->code = 'Error 404 - Not found'; - break; - case 500: - $this->view->code = 'Error 500 - Internal Server Error'; - break; - case 503: - $this->view->code = 'Error 503 - Service Unavailable'; - break; - default: - $this->view->code = 'Error 404 - Not found'; + public function indexAction() { + switch (Minz_Request::param('code')) { + case 403: + $this->view->code = 'Error 403 - Forbidden'; + break; + case 404: + $this->view->code = 'Error 404 - Not found'; + break; + case 500: + $this->view->code = 'Error 500 - Internal Server Error'; + break; + case 503: + $this->view->code = 'Error 503 - Service Unavailable'; + break; + default: + $this->view->code = 'Error 404 - Not found'; } - - $this->view->logs = Minz_Request::param ('logs'); - - Minz_View::prependTitle ($this->view->code . ' · '); + + $errors = Minz_Request::param('logs', array()); + $this->view->errorMessage = trim(implode($errors)); + if ($this->view->errorMessage == '') { + switch(Minz_Request::param('code')) { + case 403: + $this->view->errorMessage = Minz_Translate::t('forbidden_access'); + break; + case 404: + default: + $this->view->errorMessage = Minz_Translate::t('page_not_found'); + break; + } + } + + Minz_View::prependTitle($this->view->code . ' · '); } } diff --git a/app/FreshRSS.php b/app/FreshRSS.php index 84cf3429b..cd6048f75 100644 --- a/app/FreshRSS.php +++ b/app/FreshRSS.php @@ -6,6 +6,16 @@ class FreshRSS extends Minz_FrontController { } $loginOk = $this->accessControl(Minz_Session::param('currentUser', '')); $this->loadParamsView(); + if (Minz_Request::isPost() && !empty($_SERVER['HTTP_REFERER']) && + Minz_Request::getDomainName() !== parse_url($_SERVER['HTTP_REFERER'], PHP_URL_HOST)) { + $loginOk = false; //Basic protection against XSRF attacks + Minz_Error::error( + 403, + array('error' => array(Minz_Translate::t('access_denied') . ' [HTTP_REFERER=' . + htmlspecialchars(empty($_SERVER['HTTP_REFERER']) ? '' : $_SERVER['HTTP_REFERER']) . ']')) + ); + } + Minz_View::_param('loginOk', $loginOk); $this->loadStylesAndScripts($loginOk); //TODO: Do not load that when not needed, e.g. some Ajax requests $this->loadNotifications(); } @@ -95,7 +105,6 @@ class FreshRSS extends Minz_FrontController { break; } } - Minz_View::_param ('loginOk', $loginOk); return $loginOk; } diff --git a/app/views/error/index.phtml b/app/views/error/index.phtml index 6a09c3aa2..ef4fbd39d 100644 --- a/app/views/error/index.phtml +++ b/app/views/error/index.phtml @@ -1,18 +1,9 @@

    code; ?>

    -

    -
    - + errorMessage; ?>
    +

    diff --git a/lib/Minz/Translate.php b/lib/Minz/Translate.php index df48350e9..8c2f90041 100644 --- a/lib/Minz/Translate.php +++ b/lib/Minz/Translate.php @@ -75,5 +75,5 @@ function _t($key) { unset($args[0]); array_unshift($args, $key); - return call_user_func_array("Minz_Translate::t", $args); + return call_user_func_array('Minz_Translate::t', $args); } -- cgit v1.2.3 From 84826491a3fac3bde21c07ad69b114e5b56ed297 Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Fri, 8 Aug 2014 23:53:16 +0200 Subject: Improve export function If there is only one file to export, we don't need of a .zip archive. So it is exported as a simple file (.json or .opml) See https://github.com/marienfressinaud/FreshRSS/issues/494 --- app/Controllers/importExportController.php | 74 ++++++++++++++++++++---------- app/views/importExport/export.phtml | 0 2 files changed, 51 insertions(+), 23 deletions(-) create mode 100644 app/views/importExport/export.phtml (limited to 'app/Controllers') diff --git a/app/Controllers/importExportController.php b/app/Controllers/importExportController.php index ba172cc6d..67c6eb867 100644 --- a/app/Controllers/importExportController.php +++ b/app/Controllers/importExportController.php @@ -316,39 +316,42 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { $export_opml = Minz_Request::param('export_opml', false); $export_starred = Minz_Request::param('export_starred', false); - $export_feeds = Minz_Request::param('export_feeds', false); + $export_feeds = Minz_Request::param('export_feeds', array ()); - // From https://stackoverflow.com/questions/1061710/php-zip-files-on-the-fly - $file = tempnam('tmp', 'zip'); - $zip = new ZipArchive(); - $zip->open($file, ZipArchive::OVERWRITE); - - // Stuff with content + $export_files = array (); if ($export_opml) { - $zip->addFromString( - 'feeds.opml', $this->generateOpml() - ); + $export_files['feeds.opml'] = $this->generateOpml(); } + if ($export_starred) { - $zip->addFromString( - 'starred.json', $this->generateArticles('starred') - ); + $export_files['starred.json'] = $this->generateArticles('starred'); } + foreach ($export_feeds as $feed_id) { $feed = $this->feedDAO->searchById($feed_id); - $zip->addFromString( - 'feed_' . $feed->category() . '_' . $feed->id() . '.json', - $this->generateArticles('feed', $feed) + $filename = 'feed_' . $feed->category() . '_' + . $feed->id() . '.json'; + $export_files[$filename] = $this->generateArticles( + 'feed', $feed ); } - // Close and send to user - $zip->close(); - header('Content-Type: application/zip'); - header('Content-Length: ' . filesize($file)); - header('Content-Disposition: attachment; filename="freshrss_export.zip"'); - readfile($file); - unlink($file); + $nb_files = count($export_files); + if ($nb_files > 1) { + // If there are more than 1 file to export, we need an .zip + $this->exportZip($export_files); + } elseif ($nb_files === 1) { + // Only one file? Guess its type and export it. + $filename = key($export_files); + $type = null; + if (substr_compare($filename, '.opml', -5) === 0) { + $type = "text/xml"; + } elseif (substr_compare($filename, '.json', -5) === 0) { + $type = "text/json"; + } + + $this->exportFile($filename, $export_files[$filename], $type); + } } } @@ -388,4 +391,29 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { return $this->view->helperToString('export/articles'); } + + private function exportZip($files) { + // From https://stackoverflow.com/questions/1061710/php-zip-files-on-the-fly + $zip_file = tempnam('tmp', 'zip'); + $zip = new ZipArchive(); + $zip->open($zip_file, ZipArchive::OVERWRITE); + + foreach ($files as $filename => $content) { + $zip->addFromString($filename, $content); + } + + // Close and send to user + $zip->close(); + header('Content-Type: application/zip'); + header('Content-Length: ' . filesize($zip_file)); + header('Content-Disposition: attachment; filename="freshrss_export.zip"'); + readfile($zip_file); + unlink($zip_file); + } + + private function exportFile($filename, $content, $type) { + header('Content-Type: ' . $type . '; charset=utf-8'); + header('Content-disposition: attachment; filename=' . $filename); + print($content); + } } diff --git a/app/views/importExport/export.phtml b/app/views/importExport/export.phtml new file mode 100644 index 000000000..e69de29bb -- cgit v1.2.3 From fda8eba4d147a7624f64c03001df1d317804c0d4 Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Sat, 9 Aug 2014 00:12:37 +0200 Subject: Add a test to check presence of Zip archive. A notification is shown if we cannot use ZipArchive. See https://github.com/marienfressinaud/FreshRSS/issues/494 --- app/Controllers/importExportController.php | 22 +++++++++++++++++++++- app/i18n/en.php | 1 + app/i18n/fr.php | 1 + 3 files changed, 23 insertions(+), 1 deletion(-) (limited to 'app/Controllers') diff --git a/app/Controllers/importExportController.php b/app/Controllers/importExportController.php index 67c6eb867..ba4ca4eb0 100644 --- a/app/Controllers/importExportController.php +++ b/app/Controllers/importExportController.php @@ -339,7 +339,17 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { $nb_files = count($export_files); if ($nb_files > 1) { // If there are more than 1 file to export, we need an .zip - $this->exportZip($export_files); + try { + $this->exportZip($export_files); + } catch (Exception $e) { + # Oops, there is no Zip extension! + $notif = array( + 'type' => 'bad', + 'content' => _t('export_no_zip_extension') + ); + Minz_Session::_param('notification', $notif); + Minz_Request::forward(array('c' => 'importExport'), true); + } } elseif ($nb_files === 1) { // Only one file? Guess its type and export it. $filename = key($export_files); @@ -351,6 +361,8 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { } $this->exportFile($filename, $export_files[$filename], $type); + } else { + Minz_Request::forward(array('c' => 'importExport'), true); } } } @@ -393,6 +405,10 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { } private function exportZip($files) { + if (!extension_loaded('zip')) { + throw new Exception(); + } + // From https://stackoverflow.com/questions/1061710/php-zip-files-on-the-fly $zip_file = tempnam('tmp', 'zip'); $zip = new ZipArchive(); @@ -412,6 +428,10 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { } private function exportFile($filename, $content, $type) { + if (is_null($type)) { + return; + } + header('Content-Type: ' . $type . '; charset=utf-8'); header('Content-disposition: attachment; filename=' . $filename); print($content); diff --git a/app/i18n/en.php b/app/i18n/en.php index 9e5bfb223..532327e4a 100644 --- a/app/i18n/en.php +++ b/app/i18n/en.php @@ -185,6 +185,7 @@ return array ( 'export' => 'Export', 'export_opml' => 'Export list of feeds (OPML)', 'export_starred' => 'Export your favourites', + 'export_no_zip_extension' => 'Zip extension is not present on your server. Please try to export files one by one.', 'starred_list' => 'List of favourite articles', 'feed_list' => 'List of %s articles', 'or' => 'or', diff --git a/app/i18n/fr.php b/app/i18n/fr.php index 94d2e5f06..1f2844d47 100644 --- a/app/i18n/fr.php +++ b/app/i18n/fr.php @@ -185,6 +185,7 @@ return array ( 'export' => 'Exporter', 'export_opml' => 'Exporter la liste des flux (OPML)', 'export_starred' => 'Exporter les favoris', + 'export_no_zip_extension' => 'L’extension Zip n’est pas présente sur votre serveur. Veuillez essayer d’exporter les fichiers un par un.', 'starred_list' => 'Liste des articles favoris', 'feed_list' => 'Liste des articles de %s', 'or' => 'ou', -- cgit v1.2.3 From d007b22beb701b78968db420c3be6336ee98a350 Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Sat, 9 Aug 2014 00:23:22 +0200 Subject: Change view import/export if no zip extension Show a select with only one choice is there is no zip extension on the server. Fix typo. See https://github.com/marienfressinaud/FreshRSS/issues/494 --- app/Controllers/importExportController.php | 12 +++++++----- app/views/importExport/index.phtml | 27 +++++++++++++++++---------- 2 files changed, 24 insertions(+), 15 deletions(-) (limited to 'app/Controllers') diff --git a/app/Controllers/importExportController.php b/app/Controllers/importExportController.php index ba4ca4eb0..2b3353d93 100644 --- a/app/Controllers/importExportController.php +++ b/app/Controllers/importExportController.php @@ -329,11 +329,13 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { foreach ($export_feeds as $feed_id) { $feed = $this->feedDAO->searchById($feed_id); - $filename = 'feed_' . $feed->category() . '_' - . $feed->id() . '.json'; - $export_files[$filename] = $this->generateArticles( - 'feed', $feed - ); + if ($feed) { + $filename = 'feed_' . $feed->category() . '_' + . $feed->id() . '.json'; + $export_files[$filename] = $this->generateArticles( + 'feed', $feed + ); + } } $nb_files = count($export_files); diff --git a/app/views/importExport/index.phtml b/app/views/importExport/index.phtml index 309058959..d7df1619d 100644 --- a/app/views/importExport/index.phtml +++ b/app/views/importExport/index.phtml @@ -1,12 +1,12 @@ -partial ('aside_feed'); ?> +partial('aside_feed'); ?>
    - + - +
    - +
    @@ -14,27 +14,34 @@
    - +
    feeds) > 0) { ?>
    - +
    - > + '; ?> feeds as $feed) { ?> @@ -44,7 +51,7 @@
    - +
    -- cgit v1.2.3 From 2b25aa8f68dccf7001b965fc6c08c3a794ec6b04 Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Sat, 9 Aug 2014 18:46:22 +0200 Subject: Option to hide (or not) feeds/categories with no unread article https://github.com/marienfressinaud/FreshRSS/issues/430 https://github.com/marienfressinaud/FreshRSS/issues/575 --- app/Controllers/configureController.php | 1 + app/Models/Configuration.php | 4 ++++ app/i18n/en.php | 1 + app/i18n/fr.php | 1 + app/layout/aside_flux.phtml | 2 +- app/views/configure/reading.phtml | 9 +++++++++ 6 files changed, 17 insertions(+), 1 deletion(-) (limited to 'app/Controllers') diff --git a/app/Controllers/configureController.php b/app/Controllers/configureController.php index 79f40b30b..0bf58880f 100755 --- a/app/Controllers/configureController.php +++ b/app/Controllers/configureController.php @@ -184,6 +184,7 @@ class FreshRSS_configure_Controller extends Minz_ActionController { $this->view->conf->_default_view((int)Minz_Request::param('default_view', FreshRSS_Entry::STATE_ALL)); $this->view->conf->_auto_load_more(Minz_Request::param('auto_load_more', false)); $this->view->conf->_display_posts(Minz_Request::param('display_posts', false)); + $this->view->conf->_hide_read_feeds(Minz_Request::param('hide_read_feeds', false)); $this->view->conf->_onread_jump_next(Minz_Request::param('onread_jump_next', false)); $this->view->conf->_lazyload(Minz_Request::param('lazyload', false)); $this->view->conf->_sticky_post(Minz_Request::param('sticky_post', false)); diff --git a/app/Models/Configuration.php b/app/Models/Configuration.php index 7596c54cd..2f47312c0 100644 --- a/app/Models/Configuration.php +++ b/app/Models/Configuration.php @@ -17,6 +17,7 @@ class FreshRSS_Configuration { 'default_view' => FreshRSS_Entry::STATE_NOT_READ, 'auto_load_more' => true, 'display_posts' => false, + 'hide_read_feeds' => true, 'onread_jump_next' => true, 'lazyload' => true, 'sticky_post' => true, @@ -141,6 +142,9 @@ class FreshRSS_Configuration { public function _display_posts ($value) { $this->data['display_posts'] = ((bool)$value) && $value !== 'no'; } + public function _hide_read_feeds($value) { + $this->data['hide_read_feeds'] = (bool)$value; + } public function _onread_jump_next ($value) { $this->data['onread_jump_next'] = ((bool)$value) && $value !== 'no'; } diff --git a/app/i18n/en.php b/app/i18n/en.php index 532327e4a..d80299b10 100644 --- a/app/i18n/en.php +++ b/app/i18n/en.php @@ -262,6 +262,7 @@ return array ( 'sort_order' => 'Sort order', 'auto_load_more' => 'Load next articles at the page bottom', 'display_articles_unfolded' => 'Show articles unfolded by default', + 'hide_read_feeds' => 'Hide categories & feeds with no unread article (only in “unread articles” display mode)', 'after_onread' => 'After “mark all as read”,', 'jump_next' => 'jump to next unread sibling (feed or category)', 'article_icons' => 'Article icons', diff --git a/app/i18n/fr.php b/app/i18n/fr.php index 0705d80b2..4be028ac3 100644 --- a/app/i18n/fr.php +++ b/app/i18n/fr.php @@ -262,6 +262,7 @@ return array ( 'sort_order' => 'Ordre de tri', 'auto_load_more' => 'Charger les articles suivants en bas de page', 'display_articles_unfolded' => 'Afficher les articles dépliés par défaut', + 'hide_read_feeds' => 'Cacher les catégories & flux sans article non-lu (uniquement en affichage “articles non lus”)', 'after_onread' => 'Après “marquer tout comme lu”,', 'jump_next' => 'sauter au prochain voisin non lu (flux ou catégorie)', 'article_icons' => 'Icônes d’article', diff --git a/app/layout/aside_flux.phtml b/app/layout/aside_flux.phtml index cc04e757c..aee8f8754 100644 --- a/app/layout/aside_flux.phtml +++ b/app/layout/aside_flux.phtml @@ -1,4 +1,4 @@ -
    +
      diff --git a/app/views/configure/reading.phtml b/app/views/configure/reading.phtml index d56726730..c0525f1ed 100644 --- a/app/views/configure/reading.phtml +++ b/app/views/configure/reading.phtml @@ -62,6 +62,15 @@
    +
    +
    + +
    +
    +
    -- cgit v1.2.3 From 9a5d6245fbeb413766362fd6b2c4f5f5b6a22a22 Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Sun, 10 Aug 2014 10:55:51 +0200 Subject: Improve update API Update script must implement 4 functions: - apply_update() to perform the update (most important). Return true if all is ok, else false. - need_info_update() returns true if we need more info for update, else false. If this function always returns false, you don't need to implement following functions (but it's better to not forget) - ask_info_update() should be a HTML form to ask infos. Method must be post and action must point to _url('update', 'apply') (or leave it blank) - save_info_update() is called for POST requests (to save form from ask_info_update()) --- app/Controllers/updateController.php | 37 +++++++++++++++++++++--------------- app/views/update/apply.phtml | 9 +++++++++ app/views/update/index.phtml | 2 ++ 3 files changed, 33 insertions(+), 15 deletions(-) create mode 100644 app/views/update/apply.phtml (limited to 'app/Controllers') diff --git a/app/Controllers/updateController.php b/app/Controllers/updateController.php index a94af4417..1095f9da7 100644 --- a/app/Controllers/updateController.php +++ b/app/Controllers/updateController.php @@ -84,26 +84,33 @@ class FreshRSS_update_Controller extends Minz_ActionController { public function applyAction() { require(UPDATE_FILENAME); - $res = apply_update(); - if ($res === true) { - @unlink(UPDATE_FILENAME); + if (Minz_Request::isPost()) { + save_info_update(); + } - // TODO: record last update + if (!need_info_update()) { + $res = apply_update(); - Minz_Session::_param('notification', array( - 'type' => 'good', - 'content' => Minz_Translate::t('update_finished') - )); + if ($res === true) { + @unlink(UPDATE_FILENAME); - Minz_Request::forward(array(), true); - } else { - Minz_Session::_param('notification', array( - 'type' => 'bad', - 'content' => Minz_Translate::t('update_problem', $res) - )); + // TODO: record last update + + Minz_Session::_param('notification', array( + 'type' => 'good', + 'content' => Minz_Translate::t('update_finished') + )); - Minz_Request::forward(array('c' => 'update'), true); + Minz_Request::forward(array(), true); + } else { + Minz_Session::_param('notification', array( + 'type' => 'bad', + 'content' => Minz_Translate::t('update_problem', $res) + )); + + Minz_Request::forward(array('c' => 'update'), true); + } } } } \ No newline at end of file diff --git a/app/views/update/apply.phtml b/app/views/update/apply.phtml new file mode 100644 index 000000000..d7ea466c5 --- /dev/null +++ b/app/views/update/apply.phtml @@ -0,0 +1,9 @@ +partial('aside_configure'); ?> + +
    + + +

    + + +
    \ No newline at end of file diff --git a/app/views/update/index.phtml b/app/views/update/index.phtml index 8f6ee6269..1824c02b8 100644 --- a/app/views/update/index.phtml +++ b/app/views/update/index.phtml @@ -1,6 +1,8 @@ partial('aside_configure'); ?>
    + +

    message)) { ?> -- cgit v1.2.3 From 3ca8c7ec4c55b4fa751fbcdc8e28f28351c4a967 Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Sun, 10 Aug 2014 11:52:18 +0200 Subject: Litlle improvements (update system) - Check UPDATE_FILENAME exists before applying update - Add empty line at the end of files --- app/Controllers/updateController.php | 7 ++++++- app/views/update/apply.phtml | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) (limited to 'app/Controllers') diff --git a/app/Controllers/updateController.php b/app/Controllers/updateController.php index 1095f9da7..fa62f4a70 100644 --- a/app/Controllers/updateController.php +++ b/app/Controllers/updateController.php @@ -79,10 +79,15 @@ class FreshRSS_update_Controller extends Minz_ActionController { 'body' => _t('update_server_not_found', FRESHRSS_UPDATE_WEBSITE) ); } + curl_close($c); } public function applyAction() { + if (!file_exists(UPDATE_FILENAME)) { + Minz_Request::forward(array('c' => 'update'), true); + } + require(UPDATE_FILENAME); if (Minz_Request::isPost()) { @@ -113,4 +118,4 @@ class FreshRSS_update_Controller extends Minz_ActionController { } } } -} \ No newline at end of file +} diff --git a/app/views/update/apply.phtml b/app/views/update/apply.phtml index d7ea466c5..30566c7ab 100644 --- a/app/views/update/apply.phtml +++ b/app/views/update/apply.phtml @@ -6,4 +6,4 @@

    -
    \ No newline at end of file +
    -- cgit v1.2.3 From eceb7756cfcf117c2a18984291181a84697ed3cd Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Sun, 10 Aug 2014 20:29:43 +0200 Subject: Add possibility to keep logged in with form Add an option to keep logged in. Change lifetime of session cookie to 1 year. See https://github.com/marienfressinaud/FreshRSS/issues/465 --- app/Controllers/indexController.php | 9 +++++++++ app/i18n/en.php | 1 + app/i18n/fr.php | 1 + app/views/index/formLogin.phtml | 23 +++++++++++++++-------- lib/Minz/Session.php | 35 ++++++++++++++++++++++++++++++----- 5 files changed, 56 insertions(+), 13 deletions(-) (limited to 'app/Controllers') diff --git a/app/Controllers/indexController.php b/app/Controllers/indexController.php index 3119073b8..18b99d0df 100755 --- a/app/Controllers/indexController.php +++ b/app/Controllers/indexController.php @@ -298,6 +298,7 @@ class FreshRSS_index_Controller extends Minz_ActionController { public function formLoginAction () { if (Minz_Request::isPost()) { $ok = false; + $keep_logged_in = Minz_Request::param('keep_logged_in', false); $nonce = Minz_Session::param('nonce'); $username = Minz_Request::param('username', ''); $c = Minz_Request::param('challenge', ''); @@ -312,6 +313,11 @@ class FreshRSS_index_Controller extends Minz_ActionController { if ($ok) { Minz_Session::_param('currentUser', $username); Minz_Session::_param('passwordHash', $s); + if ($keep_logged_in) { + // New cookie with a lifetime of 1 year! + Minz_Session::keepCookie(31536000); + Minz_Session::regenerateID(); + } } else { Minz_Log::record('Password mismatch for user ' . $username . ', nonce=' . $nonce . ', c=' . $c, Minz_Log::WARNING); } @@ -371,6 +377,9 @@ class FreshRSS_index_Controller extends Minz_ActionController { Minz_Session::_param('currentUser'); Minz_Session::_param('mail'); Minz_Session::_param('passwordHash'); + Minz_Session::keepCookie(0); + Minz_Session::regenerateID(); + Minz_Request::forward(array('c' => 'index', 'a' => 'index'), true); } } diff --git a/app/i18n/en.php b/app/i18n/en.php index d80299b10..3c55f62a2 100644 --- a/app/i18n/en.php +++ b/app/i18n/en.php @@ -3,6 +3,7 @@ return array ( // LAYOUT 'login' => 'Login', + 'keep_logged_in' => 'Keep me logged in', 'login_with_persona' => 'Login with Persona', 'logout' => 'Logout', 'search' => 'Search words or #tags', diff --git a/app/i18n/fr.php b/app/i18n/fr.php index 4be028ac3..63d779471 100644 --- a/app/i18n/fr.php +++ b/app/i18n/fr.php @@ -3,6 +3,7 @@ return array ( // LAYOUT 'login' => 'Connexion', + 'session_active' => 'Rester connecté', 'login_with_persona' => 'Connexion avec Persona', 'logout' => 'Déconnexion', 'search' => 'Rechercher des mots ou des #tags', diff --git a/app/views/index/formLogin.phtml b/app/views/index/formLogin.phtml index cc925ea59..f01a950b6 100644 --- a/app/views/index/formLogin.phtml +++ b/app/views/index/formLogin.phtml @@ -1,32 +1,39 @@
    -

    - +
    - +
    - +
    - + +
    +
    +
    +

    - - + +

    -

    +

    diff --git a/lib/Minz/Session.php b/lib/Minz/Session.php index ddabc4658..c859be2ed 100644 --- a/lib/Minz/Session.php +++ b/lib/Minz/Session.php @@ -15,13 +15,15 @@ class Minz_Session { * Le nom de session est utilisé comme nom pour les cookies et les URLs (i.e. PHPSESSID). * Il ne doit contenir que des caractères alphanumériques ; il doit être court et descriptif */ - public static function init ($name) { + public static function init($name) { + $cookie = session_get_cookie_params(); + self::keepCookie($cookie['lifetime']); + // démarre la session - session_name ($name); - session_set_cookie_params (0, dirname(empty($_SERVER['REQUEST_URI']) ? '/' : dirname($_SERVER['REQUEST_URI'])), null, false, true); - session_start (); + session_name($name); + session_start(); - if (isset ($_SESSION)) { + if (isset($_SESSION)) { self::$session = $_SESSION; } } @@ -68,4 +70,27 @@ class Minz_Session { Minz_Translate::reset (); } } + + + /** + * Spécifie la durée de vie des cookies + * @param $l la durée de vie + */ + public static function keepCookie($l) { + $cookie_dir = dirname( + empty($_SERVER['SCRIPT_NAME']) ? '' : $_SERVER['SCRIPT_NAME'] + ) . '/'; + session_set_cookie_params($l, $cookie_dir, $_SERVER['HTTP_HOST'], + false, true); + } + + + /** + * Régénère un id de session. + * Utile pour appeler session_set_cookie_params après session_start() + */ + public static function regenerateID() { + session_regenerate_id(true); + } + } -- cgit v1.2.3 From 93a77f84d45ee4eccda7c837dc08f17360f3de0f Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Sun, 10 Aug 2014 21:08:10 +0200 Subject: Change cookie lifetime to 1 month. See https://github.com/marienfressinaud/FreshRSS/issues/465 --- app/Controllers/indexController.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'app/Controllers') diff --git a/app/Controllers/indexController.php b/app/Controllers/indexController.php index 18b99d0df..b907c8eed 100755 --- a/app/Controllers/indexController.php +++ b/app/Controllers/indexController.php @@ -314,8 +314,8 @@ class FreshRSS_index_Controller extends Minz_ActionController { Minz_Session::_param('currentUser', $username); Minz_Session::_param('passwordHash', $s); if ($keep_logged_in) { - // New cookie with a lifetime of 1 year! - Minz_Session::keepCookie(31536000); + // New cookie with a lifetime of 1 month. + Minz_Session::keepCookie(2592000); Minz_Session::regenerateID(); } } else { -- cgit v1.2.3 From 94570aaf5a23dfc02bf1120d168ec30c2ab3f044 Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Mon, 11 Aug 2014 19:02:27 +0200 Subject: Improve system import/export Miss checking presence of zip extension during import See https://github.com/marienfressinaud/FreshRSS/issues/494 --- app/Controllers/importExportController.php | 10 +++++++++- app/i18n/en.php | 2 ++ app/i18n/fr.php | 2 ++ app/views/importExport/index.phtml | 4 +++- 4 files changed, 16 insertions(+), 2 deletions(-) (limited to 'app/Controllers') diff --git a/app/Controllers/importExportController.php b/app/Controllers/importExportController.php index 2b3353d93..dd6c23322 100644 --- a/app/Controllers/importExportController.php +++ b/app/Controllers/importExportController.php @@ -39,7 +39,7 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { // We try to list all files according to their type // A zip file is first opened and then its files are listed $list = array(); - if ($type_file === 'zip') { + if ($type_file === 'zip' && extension_loaded('zip')) { $zip = zip_open($file['tmp_name']); while (($zipfile = zip_read($zip)) !== false) { @@ -56,6 +56,14 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { } zip_close($zip); + } elseif ($type_file === 'zip') { + // Zip extension is not loaded + Minz_Session::_param('notification', array( + 'type' => 'bad', + 'content' => _t('no_zip_extension') + )); + + Minz_Request::forward(array('c' => 'importExport'), true); } elseif ($type_file !== 'unknown') { $list_files[$type_file][] = file_get_contents( $file['tmp_name'] diff --git a/app/i18n/en.php b/app/i18n/en.php index 748d9a81b..0c87f52be 100644 --- a/app/i18n/en.php +++ b/app/i18n/en.php @@ -182,7 +182,9 @@ return array ( 'focus_search' => 'Access search box', 'file_to_import' => 'File to import
    (OPML, Json or Zip)', + 'file_to_import_no_zip' => 'File to import
    (OPML or Json)', 'import' => 'Import', + 'no_zip_extension' => 'Zip extension is not present on your server.', 'export' => 'Export', 'export_opml' => 'Export list of feeds (OPML)', 'export_starred' => 'Export your favourites', diff --git a/app/i18n/fr.php b/app/i18n/fr.php index ba8c8686a..57ddebc20 100644 --- a/app/i18n/fr.php +++ b/app/i18n/fr.php @@ -182,7 +182,9 @@ return array ( 'focus_search' => 'Accéder à la recherche', 'file_to_import' => 'Fichier à importer
    (OPML, Json ou Zip)', + 'file_to_import_no_zip' => 'Fichier à importer
    (OPML ou Json)', 'import' => 'Importer', + 'no_zip_extension' => 'L’extension Zip n’est pas présente sur votre serveur.', 'export' => 'Exporter', 'export_opml' => 'Exporter la liste des flux (OPML)', 'export_starred' => 'Exporter les favoris', diff --git a/app/views/importExport/index.phtml b/app/views/importExport/index.phtml index e1458e916..35371faca 100644 --- a/app/views/importExport/index.phtml +++ b/app/views/importExport/index.phtml @@ -6,7 +6,9 @@
    - +
    -- cgit v1.2.3 From 8ffd59f34ac458827f2a0217e4630caf69705853 Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Mon, 11 Aug 2014 19:18:12 +0200 Subject: Improve import system Catch errors of zip_open and log it. A notification is shown to indicate something went wrong. See https://github.com/marienfressinaud/FreshRSS/issues/494 --- app/Controllers/importExportController.php | 14 ++++++++++++++ app/i18n/en.php | 1 + app/i18n/fr.php | 1 + 3 files changed, 16 insertions(+) (limited to 'app/Controllers') diff --git a/app/Controllers/importExportController.php b/app/Controllers/importExportController.php index dd6c23322..15871ed80 100644 --- a/app/Controllers/importExportController.php +++ b/app/Controllers/importExportController.php @@ -42,6 +42,20 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { if ($type_file === 'zip' && extension_loaded('zip')) { $zip = zip_open($file['tmp_name']); + if (!is_resource($zip)) { + Minz_Log::error( + 'Zip file cannot be imported. Error code: ' . $zip + ); + + // zip_open cannot open file: something is wrong + Minz_Session::_param('notification', array( + 'type' => 'bad', + 'content' => _t('zip_error') + )); + + Minz_Request::forward(array('c' => 'importExport'), true); + } + while (($zipfile = zip_read($zip)) !== false) { $type_zipfile = $this->guessFileType( zip_entry_name($zipfile) diff --git a/app/i18n/en.php b/app/i18n/en.php index 0c87f52be..416ca851f 100644 --- a/app/i18n/en.php +++ b/app/i18n/en.php @@ -184,6 +184,7 @@ return array ( 'file_to_import' => 'File to import
    (OPML, Json or Zip)', 'file_to_import_no_zip' => 'File to import
    (OPML or Json)', 'import' => 'Import', + 'zip_error' => 'An error occured during Zip import.', 'no_zip_extension' => 'Zip extension is not present on your server.', 'export' => 'Export', 'export_opml' => 'Export list of feeds (OPML)', diff --git a/app/i18n/fr.php b/app/i18n/fr.php index 57ddebc20..d68006a87 100644 --- a/app/i18n/fr.php +++ b/app/i18n/fr.php @@ -184,6 +184,7 @@ return array ( 'file_to_import' => 'Fichier à importer
    (OPML, Json ou Zip)', 'file_to_import_no_zip' => 'Fichier à importer
    (OPML ou Json)', 'import' => 'Importer', + 'zip_error' => 'Une erreur est survenue durant l’import du fichier Zip.', 'no_zip_extension' => 'L’extension Zip n’est pas présente sur votre serveur.', 'export' => 'Exporter', 'export_opml' => 'Exporter la liste des flux (OPML)', -- cgit v1.2.3 From 909d8747ba09f9c9a6ac895f1f4f0763bdb27a55 Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Tue, 12 Aug 2014 20:15:46 +0200 Subject: Update system now uses HTTPS connection - Add some curl checks - Refactor code --- app/Controllers/updateController.php | 60 +++++++++++++++++++----------------- constants.php | 2 +- 2 files changed, 33 insertions(+), 29 deletions(-) (limited to 'app/Controllers') diff --git a/app/Controllers/updateController.php b/app/Controllers/updateController.php index fa62f4a70..857d975b2 100644 --- a/app/Controllers/updateController.php +++ b/app/Controllers/updateController.php @@ -44,43 +44,47 @@ class FreshRSS_update_Controller extends Minz_ActionController { $c = curl_init(FRESHRSS_UPDATE_WEBSITE); curl_setopt($c, CURLOPT_RETURNTRANSFER, true); + curl_setopt($c, CURLOPT_SSL_VERIFYPEER, true); + curl_setopt($c, CURLOPT_SSL_VERIFYHOST, 2); $result = curl_exec($c); + $c_status = curl_getinfo($c, CURLINFO_HTTP_CODE); + curl_close($c); - if (curl_getinfo($c, CURLINFO_HTTP_CODE) == 200) { - $res_array = explode("\n", $result, 2); - $status = $res_array[0]; - - if (strpos($status, 'UPDATE') === 0) { - $script = $res_array[1]; - if (file_put_contents(UPDATE_FILENAME, $script) !== false) { - $this->view->message = array( - 'status' => 'good', - 'title' => _t('ok'), - 'body' => _t('update_can_apply', _url('update', 'apply')) - ); - } else { - $this->view->message = array( - 'status' => 'bad', - 'title' => _t('damn'), - 'body' => _t('update_problem', 'Cannot save the update script') - ); - } - } else { - $this->view->message = array( - 'status' => 'bad', - 'title' => _t('damn'), - 'body' => _t('no_update') - ); - } - } else { + if ($c_status !== 200) { $this->view->message = array( 'status' => 'bad', 'title' => _t('damn'), 'body' => _t('update_server_not_found', FRESHRSS_UPDATE_WEBSITE) ); + return; } - curl_close($c); + $res_array = explode("\n", $result, 2); + $status = $res_array[0]; + if (strpos($status, 'UPDATE') !== 0) { + $this->view->message = array( + 'status' => 'bad', + 'title' => _t('damn'), + 'body' => _t('no_update') + ); + + return; + } + + $script = $res_array[1]; + if (file_put_contents(UPDATE_FILENAME, $script) !== false) { + $this->view->message = array( + 'status' => 'good', + 'title' => _t('ok'), + 'body' => _t('update_can_apply', _url('update', 'apply')) + ); + } else { + $this->view->message = array( + 'status' => 'bad', + 'title' => _t('damn'), + 'body' => _t('update_problem', 'Cannot save the update script') + ); + } } public function applyAction() { diff --git a/constants.php b/constants.php index a968b82f4..ba9c508dc 100644 --- a/constants.php +++ b/constants.php @@ -1,7 +1,7 @@ Date: Tue, 12 Aug 2014 20:59:27 +0200 Subject: New wrappers Minz_Request::good() and bad() 1. Set a notification message in session variable 2. Redirect to a specific url First use in importExportController.php See https://github.com/marienfressinaud/FreshRSS/conversations/576 --- app/Controllers/importExportController.php | 57 ++++++------------------------ lib/Minz/Request.php | 25 +++++++++++++ 2 files changed, 35 insertions(+), 47 deletions(-) (limited to 'app/Controllers') diff --git a/app/Controllers/importExportController.php b/app/Controllers/importExportController.php index 15871ed80..92b39b575 100644 --- a/app/Controllers/importExportController.php +++ b/app/Controllers/importExportController.php @@ -43,17 +43,9 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { $zip = zip_open($file['tmp_name']); if (!is_resource($zip)) { - Minz_Log::error( - 'Zip file cannot be imported. Error code: ' . $zip - ); - // zip_open cannot open file: something is wrong - Minz_Session::_param('notification', array( - 'type' => 'bad', - 'content' => _t('zip_error') - )); - - Minz_Request::forward(array('c' => 'importExport'), true); + Minz_Log::error('Zip file cannot be imported. Error code: ' . $zip); + Minz_Request::bad(_t('zip_error'), array('c' => 'importExport')); } while (($zipfile = zip_read($zip)) !== false) { @@ -72,12 +64,7 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { zip_close($zip); } elseif ($type_file === 'zip') { // Zip extension is not loaded - Minz_Session::_param('notification', array( - 'type' => 'bad', - 'content' => _t('no_zip_extension') - )); - - Minz_Request::forward(array('c' => 'importExport'), true); + Minz_Request::bad(_t('no_zip_extension'), array('c' => 'importExport')); } elseif ($type_file !== 'unknown') { $list_files[$type_file][] = file_get_contents( $file['tmp_name'] @@ -100,35 +87,16 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { } // And finally, we get import status and redirect to the home page - $notif = null; - if ($error === true) { - $content_notif = Minz_Translate::t( - 'feeds_imported_with_errors' - ); - } else { - $content_notif = Minz_Translate::t( - 'feeds_imported' - ); - } - - Minz_Session::_param('notification', array( - 'type' => 'good', - 'content' => $content_notif - )); Minz_Session::_param('actualize_feeds', true); - Minz_Request::forward(array( - 'c' => 'index', - 'a' => 'index' - ), true); + $content_notif = $error === true ? _t('feeds_imported_with_errors') : + _t('feeds_imported'); + Minz_Request::good($content_notif); } // What are you doing? you have to call this controller // with a POST request! - Minz_Request::forward(array( - 'c' => 'importExport', - 'a' => 'index' - )); + Minz_Request::forward(array('c' => 'importExport')); } private function guessFileType($filename) { @@ -362,17 +330,12 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { $nb_files = count($export_files); if ($nb_files > 1) { - // If there are more than 1 file to export, we need an .zip + // If there are more than 1 file to export, we need a zip archive. try { $this->exportZip($export_files); } catch (Exception $e) { # Oops, there is no Zip extension! - $notif = array( - 'type' => 'bad', - 'content' => _t('export_no_zip_extension') - ); - Minz_Session::_param('notification', $notif); - Minz_Request::forward(array('c' => 'importExport'), true); + Minz_Request::bad(_t('export_no_zip_extension'), array('c' => 'importExport')); } } elseif ($nb_files === 1) { // Only one file? Guess its type and export it. @@ -386,7 +349,7 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { $this->exportFile($filename, $export_files[$filename], $type); } else { - Minz_Request::forward(array('c' => 'importExport'), true); + Minz_Request::forward(array('c' => 'importExport')); } } } diff --git a/lib/Minz/Request.php b/lib/Minz/Request.php index 755784522..2f745a04c 100644 --- a/lib/Minz/Request.php +++ b/lib/Minz/Request.php @@ -146,6 +146,31 @@ class Minz_Request { } } + + /** + * Wrappers good notifications + redirection + * @param $msg notification content + * @param $url url array to where we should be forwarded + */ + public static function good($msg, $url = array()) { + Minz_Session::_param('notification', array( + 'type' => 'good', + 'content' => $msg + )); + + Minz_Request::forward($url, true); + } + + public static function bad($msg, $url = array()) { + Minz_Session::_param('notification', array( + 'type' => 'bad', + 'content' => $msg + )); + + Minz_Request::forward($url, true); + } + + /** * Permet de récupérer une variable de type $_GET * @param $param nom de la variable -- cgit v1.2.3 From 7900c5e550acafaf0b877635840a8a270eb06078 Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Tue, 12 Aug 2014 21:56:34 +0200 Subject: Move htmlspecialchars_utf8 from Request to Helper And remove html_chars_utf8 to use htmlspecialchars_utf8 instead in importExportController --- app/Controllers/importExportController.php | 10 +++++----- lib/Minz/Helper.php | 11 +++++++++++ lib/Minz/Request.php | 8 +------- lib/lib_rss.php | 4 ---- 4 files changed, 17 insertions(+), 16 deletions(-) (limited to 'app/Controllers') diff --git a/app/Controllers/importExportController.php b/app/Controllers/importExportController.php index 92b39b575..a8e2c2bc2 100644 --- a/app/Controllers/importExportController.php +++ b/app/Controllers/importExportController.php @@ -166,15 +166,15 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { } // We get different useful information - $url = html_chars_utf8($feed_elt['xmlUrl']); - $name = html_chars_utf8($feed_elt['text']); + $url = Minz_Helper::htmlspecialchars_utf8($feed_elt['xmlUrl']); + $name = Minz_Helper::htmlspecialchars_utf8($feed_elt['text']); $website = ''; if (isset($feed_elt['htmlUrl'])) { - $website = html_chars_utf8($feed_elt['htmlUrl']); + $website = Minz_Helper::htmlspecialchars_utf8($feed_elt['htmlUrl']); } $description = ''; if (isset($feed_elt['description'])) { - $description = html_chars_utf8($feed_elt['description']); + $description = Minz_Helper::htmlspecialchars_utf8($feed_elt['description']); } $error = false; @@ -200,7 +200,7 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { private function addCategoryOpml($cat_elt, $parent_cat) { // Create a new Category object - $cat = new FreshRSS_Category(html_chars_utf8($cat_elt['text'])); + $cat = new FreshRSS_Category(Minz_Helper::htmlspecialchars_utf8($cat_elt['text'])); $id = $this->catDAO->addCategoryObject($cat); $error = ($id === false); diff --git a/lib/Minz/Helper.php b/lib/Minz/Helper.php index b058211d3..13bfdd93e 100644 --- a/lib/Minz/Helper.php +++ b/lib/Minz/Helper.php @@ -19,4 +19,15 @@ class Minz_Helper { return stripslashes($var); } } + + /** + * Wrapper for htmlspecialchars. + * Force UTf-8 value and can be used on array too. + */ + public static function htmlspecialchars_utf8($p) { + if (is_array($p)) { + return array_map('self::htmlspecialchars_utf8', $p); + } + return htmlspecialchars($p, ENT_COMPAT, 'UTF-8'); + } } diff --git a/lib/Minz/Request.php b/lib/Minz/Request.php index f3ecaf55c..52f53012f 100644 --- a/lib/Minz/Request.php +++ b/lib/Minz/Request.php @@ -27,19 +27,13 @@ class Minz_Request { public static function params() { return self::$params; } - static function htmlspecialchars_utf8($p) { - if (is_array($p)) { - return array_map('self::htmlspecialchars_utf8', $p); - } - return htmlspecialchars($p, ENT_COMPAT, 'UTF-8'); - } public static function param($key, $default = false, $specialchars = false) { if (isset(self::$params[$key])) { $p = self::$params[$key]; if (is_object($p) || $specialchars) { return $p; } else { - return self::htmlspecialchars_utf8($p); + return Minz_Helper::htmlspecialchars_utf8($p); } } else { return $default; diff --git a/lib/lib_rss.php b/lib/lib_rss.php index 86c0a4ae4..823f53716 100644 --- a/lib/lib_rss.php +++ b/lib/lib_rss.php @@ -230,7 +230,3 @@ function cryptAvailable() { } return false; } - -function html_chars_utf8($str) { - return htmlspecialchars($str, ENT_COMPAT, 'UTF-8'); -} -- cgit v1.2.3 From 775ff40780935471dcd74b0d81c04b80e3e4603c Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Tue, 12 Aug 2014 22:55:43 +0200 Subject: Improve import system Catch more errors Code refactoring --- app/Controllers/importExportController.php | 125 ++++++++++++++++------------- app/i18n/en.php | 1 + app/i18n/fr.php | 1 + 3 files changed, 70 insertions(+), 57 deletions(-) (limited to 'app/Controllers') diff --git a/app/Controllers/importExportController.php b/app/Controllers/importExportController.php index a8e2c2bc2..c7f47fc13 100644 --- a/app/Controllers/importExportController.php +++ b/app/Controllers/importExportController.php @@ -24,35 +24,49 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { } public function importAction() { - if (Minz_Request::isPost() && $_FILES['file']['error'] == 0) { - @set_time_limit(300); + if (!Minz_Request::isPost()) { + // What are you doing? you have to call this controller + // with a POST request! + Minz_Request::forward(array('c' => 'importExport', 'a' => 'index'), true); + } - $file = $_FILES['file']; - $type_file = $this->guessFileType($file['name']); + $file = $_FILES['file']; + $status_file = $file['error']; - $list_files = array( - 'opml' => array(), - 'json_starred' => array(), - 'json_feed' => array() - ); + if ($status_file !== 0) { + Minz_Log::error('File cannot be imported. Error code: ' . $status_file); + Minz_Request::bad(_t('file_cannot_be_imported'), + array('c' => 'importExport', 'a' => 'index')); + } - // We try to list all files according to their type - // A zip file is first opened and then its files are listed - $list = array(); - if ($type_file === 'zip' && extension_loaded('zip')) { - $zip = zip_open($file['tmp_name']); + @set_time_limit(300); - if (!is_resource($zip)) { - // zip_open cannot open file: something is wrong - Minz_Log::error('Zip file cannot be imported. Error code: ' . $zip); - Minz_Request::bad(_t('zip_error'), array('c' => 'importExport')); - } + $type_file = $this->guessFileType($file['name']); - while (($zipfile = zip_read($zip)) !== false) { - $type_zipfile = $this->guessFileType( - zip_entry_name($zipfile) - ); + $list_files = array( + 'opml' => array(), + 'json_starred' => array(), + 'json_feed' => array() + ); + // We try to list all files according to their type + // A zip file is first opened and then its files are listed + $list = array(); + if ($type_file === 'zip' && extension_loaded('zip')) { + $zip = zip_open($file['tmp_name']); + + if (!is_resource($zip)) { + // zip_open cannot open file: something is wrong + Minz_Log::error('Zip archive cannot be imported. Error code: ' . $zip); + Minz_Request::bad(_t('zip_error'), array('c' => 'importExport')); + } + + while (($zipfile = zip_read($zip)) !== false) { + if (!is_resource($zipfile)) { + // zip_entry() can also return an error code! + Minz_Log::error('Zip file cannot be imported. Error code: ' . $zipfile); + } else { + $type_zipfile = $this->guessFileType(zip_entry_name($zipfile)); if ($type_file !== 'unknown') { $list_files[$type_zipfile][] = zip_entry_read( $zipfile, @@ -60,43 +74,39 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { ); } } - - zip_close($zip); - } elseif ($type_file === 'zip') { - // Zip extension is not loaded - Minz_Request::bad(_t('no_zip_extension'), array('c' => 'importExport')); - } elseif ($type_file !== 'unknown') { - $list_files[$type_file][] = file_get_contents( - $file['tmp_name'] - ); } - // Import different files. - // OPML first(so categories and feeds are imported) - // Starred articles then so the "favourite" status is already set - // And finally all other files. - $error = false; - foreach ($list_files['opml'] as $opml_file) { - $error = $this->importOpml($opml_file); - } - foreach ($list_files['json_starred'] as $article_file) { - $error = $this->importArticles($article_file, true); - } - foreach ($list_files['json_feed'] as $article_file) { - $error = $this->importArticles($article_file); - } - - // And finally, we get import status and redirect to the home page - Minz_Session::_param('actualize_feeds', true); + zip_close($zip); + } elseif ($type_file === 'zip') { + // Zip extension is not loaded + Minz_Request::bad(_t('no_zip_extension'), array('c' => 'importExport')); + } elseif ($type_file !== 'unknown') { + $list_files[$type_file][] = file_get_contents( + $file['tmp_name'] + ); + } - $content_notif = $error === true ? _t('feeds_imported_with_errors') : - _t('feeds_imported'); - Minz_Request::good($content_notif); + // Import different files. + // OPML first(so categories and feeds are imported) + // Starred articles then so the "favourite" status is already set + // And finally all other files. + $error = false; + foreach ($list_files['opml'] as $opml_file) { + $error = $this->importOpml($opml_file); + } + foreach ($list_files['json_starred'] as $article_file) { + $error = $this->importArticles($article_file, true); } + foreach ($list_files['json_feed'] as $article_file) { + $error = $this->importArticles($article_file); + } + + // And finally, we get import status and redirect to the home page + Minz_Session::_param('actualize_feeds', true); - // What are you doing? you have to call this controller - // with a POST request! - Minz_Request::forward(array('c' => 'importExport')); + $content_notif = $error === true ? _t('feeds_imported_with_errors') : + _t('feeds_imported'); + Minz_Request::good($content_notif); } private function guessFileType($filename) { @@ -335,7 +345,8 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { $this->exportZip($export_files); } catch (Exception $e) { # Oops, there is no Zip extension! - Minz_Request::bad(_t('export_no_zip_extension'), array('c' => 'importExport')); + Minz_Request::bad(_t('export_no_zip_extension'), + array('c' => 'importExport', 'a' => 'index')); } } elseif ($nb_files === 1) { // Only one file? Guess its type and export it. @@ -349,7 +360,7 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { $this->exportFile($filename, $export_files[$filename], $type); } else { - Minz_Request::forward(array('c' => 'importExport')); + Minz_Request::forward(array('c' => 'importExport', 'a' => 'index'), true); } } } diff --git a/app/i18n/en.php b/app/i18n/en.php index 416ca851f..f4a15e747 100644 --- a/app/i18n/en.php +++ b/app/i18n/en.php @@ -184,6 +184,7 @@ return array ( 'file_to_import' => 'File to import
    (OPML, Json or Zip)', 'file_to_import_no_zip' => 'File to import
    (OPML or Json)', 'import' => 'Import', + 'file_cannot_be_uploaded' => 'File cannot be uploaded!', 'zip_error' => 'An error occured during Zip import.', 'no_zip_extension' => 'Zip extension is not present on your server.', 'export' => 'Export', diff --git a/app/i18n/fr.php b/app/i18n/fr.php index d68006a87..4675e17ee 100644 --- a/app/i18n/fr.php +++ b/app/i18n/fr.php @@ -184,6 +184,7 @@ return array ( 'file_to_import' => 'Fichier à importer
    (OPML, Json ou Zip)', 'file_to_import_no_zip' => 'Fichier à importer
    (OPML ou Json)', 'import' => 'Importer', + 'file_cannot_be_uploaded' => 'Le fichier ne peut pas être téléchargé!', 'zip_error' => 'Une erreur est survenue durant l’import du fichier Zip.', 'no_zip_extension' => 'L’extension Zip n’est pas présente sur votre serveur.', 'export' => 'Exporter', -- cgit v1.2.3 From ee1b8f6f72e8c2cbd3e0ad7b4322a4bb6863c028 Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Wed, 13 Aug 2014 00:09:48 +0200 Subject: Long term cookie to keep session open Token system https://github.com/marienfressinaud/FreshRSS/issues/465 --- app/Controllers/indexController.php | 41 +++++++++++++++++++++++++++++-------- app/FreshRSS.php | 28 ++++++++++++++++++++++--- data/tokens/.gitignore | 1 + data/tokens/index.html | 13 ++++++++++++ lib/Minz/Session.php | 19 ++++++++++++----- 5 files changed, 86 insertions(+), 16 deletions(-) create mode 100644 data/tokens/.gitignore create mode 100644 data/tokens/index.html (limited to 'app/Controllers') diff --git a/app/Controllers/indexController.php b/app/Controllers/indexController.php index b907c8eed..dd5b91e47 100755 --- a/app/Controllers/indexController.php +++ b/app/Controllers/indexController.php @@ -295,10 +295,39 @@ class FreshRSS_index_Controller extends Minz_ActionController { Minz_Session::_param('passwordHash'); } + private static function makeLongTermCookie($username, $passwordHash) { + do { + $token = sha1(Minz_Configuration::salt() . $username . uniqid(mt_rand(), true)); + $tokenFile = DATA_PATH . '/tokens/' . $token . '.txt'; + } while (file_exists($tokenFile)); + if (@file_put_contents($tokenFile, $username . "\t" . $passwordHash) === false) { + return false; + } + $expire = time() + 2629744; //1 month //TODO: Use a configuration instead + Minz_Session::setLongTermCookie('FreshRSS_login', $token, $expire); + Minz_Session::_param('token', $token); + return $token; + } + + private static function deleteLongTermCookie() { + Minz_Session::deleteLongTermCookie('FreshRSS_login'); + $token = Minz_Session::param('token', null); + if (ctype_alnum($token)) { + @unlink(DATA_PATH . '/tokens/' . $token . '.txt'); + } + Minz_Session::_param('token'); + if (rand(0, 10) === 1) { + self::purgeTokens(); + } + } + + private static function purgeTokens() { + //TODO: Delete old token files + } + public function formLoginAction () { if (Minz_Request::isPost()) { $ok = false; - $keep_logged_in = Minz_Request::param('keep_logged_in', false); $nonce = Minz_Session::param('nonce'); $username = Minz_Request::param('username', ''); $c = Minz_Request::param('challenge', ''); @@ -313,10 +342,8 @@ class FreshRSS_index_Controller extends Minz_ActionController { if ($ok) { Minz_Session::_param('currentUser', $username); Minz_Session::_param('passwordHash', $s); - if ($keep_logged_in) { - // New cookie with a lifetime of 1 month. - Minz_Session::keepCookie(2592000); - Minz_Session::regenerateID(); + if (Minz_Request::param('keep_logged_in', false)) { + self::makeLongTermCookie($username, $s); } } else { Minz_Log::record('Password mismatch for user ' . $username . ', nonce=' . $nonce . ', c=' . $c, Minz_Log::WARNING); @@ -377,9 +404,7 @@ class FreshRSS_index_Controller extends Minz_ActionController { Minz_Session::_param('currentUser'); Minz_Session::_param('mail'); Minz_Session::_param('passwordHash'); - Minz_Session::keepCookie(0); - Minz_Session::regenerateID(); - + self::deleteLongTermCookie(); Minz_Request::forward(array('c' => 'index', 'a' => 'index'), true); } } diff --git a/app/FreshRSS.php b/app/FreshRSS.php index 7c333b090..30f711e20 100644 --- a/app/FreshRSS.php +++ b/app/FreshRSS.php @@ -20,13 +20,35 @@ class FreshRSS extends Minz_FrontController { $this->loadNotifications(); } + private static function getCredentialsFromLongTermCookie() { + $token = Minz_Session::getLongTermCookie('FreshRSS_login'); + if (!ctype_alnum($token)) { + return array(); + } + $tokenFile = DATA_PATH . '/tokens/' . $token . '.txt'; + $mtime = @filemtime($tokenFile); + if ($mtime + 2629744 < time()) { //1 month //TODO: Use a configuration instead + @unlink($tokenFile); + return array(); //Expired or token does not exist + } + $credentials = @file_get_contents($tokenFile); + return $credentials === false ? array() : explode("\t", $credentials, 2); + } + private function accessControl($currentUser) { if ($currentUser == '') { switch (Minz_Configuration::authType()) { case 'form': - $currentUser = Minz_Configuration::defaultUser(); - Minz_Session::_param('passwordHash'); - $loginOk = false; + $credentials = self::getCredentialsFromLongTermCookie(); + if (isset($credentials[1])) { + $currentUser = trim($credentials[0]); + Minz_Session::_param('passwordHash', trim($credentials[1])); + } + $loginOk = $currentUser != ''; + if (!$loginOk) { + $currentUser = Minz_Configuration::defaultUser(); + Minz_Session::_param('passwordHash'); + } break; case 'http_auth': $currentUser = httpAuthUser(); diff --git a/data/tokens/.gitignore b/data/tokens/.gitignore new file mode 100644 index 000000000..2211df63d --- /dev/null +++ b/data/tokens/.gitignore @@ -0,0 +1 @@ +*.txt diff --git a/data/tokens/index.html b/data/tokens/index.html new file mode 100644 index 000000000..85faaa37e --- /dev/null +++ b/data/tokens/index.html @@ -0,0 +1,13 @@ + + + + + +Redirection + + + + +

    Redirection

    + + diff --git a/lib/Minz/Session.php b/lib/Minz/Session.php index 906acc03c..af4de75bb 100644 --- a/lib/Minz/Session.php +++ b/lib/Minz/Session.php @@ -65,11 +65,8 @@ class Minz_Session { * @param $l la durée de vie */ public static function keepCookie($l) { - $cookie_dir = dirname( - empty($_SERVER['REQUEST_URI']) ? '' : $_SERVER['REQUEST_URI'] - ) . '/'; - session_set_cookie_params($l, $cookie_dir, $_SERVER['HTTP_HOST'], - false, true); + $cookie_dir = empty($_SERVER['REQUEST_URI']) ? '' : $_SERVER['REQUEST_URI']; + session_set_cookie_params($l, $cookie_dir, '', false, true); } @@ -81,4 +78,16 @@ class Minz_Session { session_regenerate_id(true); } + public static function deleteLongTermCookie($name) { + setcookie($name, '', 1, '', '', false, true); + } + + public static function setLongTermCookie($name, $value, $expire) { + setcookie($name, $value, $expire, '', '', false, true); + } + + public static function getLongTermCookie($name) { + return isset($_COOKIE[$name]) ? $_COOKIE[$name] : null; + } + } -- cgit v1.2.3 From 359ccc8e4c20a60506c680e5054dbe8416fef4a9 Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Wed, 13 Aug 2014 00:25:52 +0200 Subject: Long term cookie minor change https://github.com/marienfressinaud/FreshRSS/issues/465 --- app/Controllers/indexController.php | 2 ++ 1 file changed, 2 insertions(+) (limited to 'app/Controllers') diff --git a/app/Controllers/indexController.php b/app/Controllers/indexController.php index dd5b91e47..834db496c 100755 --- a/app/Controllers/indexController.php +++ b/app/Controllers/indexController.php @@ -344,6 +344,8 @@ class FreshRSS_index_Controller extends Minz_ActionController { Minz_Session::_param('passwordHash', $s); if (Minz_Request::param('keep_logged_in', false)) { self::makeLongTermCookie($username, $s); + } else { + self::deleteLongTermCookie(); } } else { Minz_Log::record('Password mismatch for user ' . $username . ', nonce=' . $nonce . ', c=' . $c, Minz_Log::WARNING); -- cgit v1.2.3 From 5c2f3349fa4341533aad6294c896b32c4befd58f Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Wed, 13 Aug 2014 20:09:23 +0200 Subject: Long term cookie: purge old tokens https://github.com/marienfressinaud/FreshRSS/issues/465 --- app/Controllers/indexController.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'app/Controllers') diff --git a/app/Controllers/indexController.php b/app/Controllers/indexController.php index 834db496c..9202f2b85 100755 --- a/app/Controllers/indexController.php +++ b/app/Controllers/indexController.php @@ -322,7 +322,12 @@ class FreshRSS_index_Controller extends Minz_ActionController { } private static function purgeTokens() { - //TODO: Delete old token files + $oldest = time() - 2629744; //1 month //TODO: Use a configuration instead + foreach (new DirectoryIterator(DATA_PATH . '/tokens/') as $fileInfo) { + if ($fileInfo->getExtension() === 'txt' && $fileInfo->getMTime() < $oldest) { + @unlink($fileInfo->getPathname()); + } + } } public function formLoginAction () { -- cgit v1.2.3 From 0f1133ddfe8e3967470ea50f235e12ad04dc71a7 Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Sun, 17 Aug 2014 21:03:01 +0200 Subject: Fix some forward() actions See https://github.com/marienfressinaud/FreshRSS/issues/494 --- app/Controllers/importExportController.php | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) (limited to 'app/Controllers') diff --git a/app/Controllers/importExportController.php b/app/Controllers/importExportController.php index c7f47fc13..2f5fcc137 100644 --- a/app/Controllers/importExportController.php +++ b/app/Controllers/importExportController.php @@ -34,8 +34,8 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { $status_file = $file['error']; if ($status_file !== 0) { - Minz_Log::error('File cannot be imported. Error code: ' . $status_file); - Minz_Request::bad(_t('file_cannot_be_imported'), + Minz_Log::error('File cannot be uploaded. Error code: ' . $status_file); + Minz_Request::bad(_t('file_cannot_be_uploaded'), array('c' => 'importExport', 'a' => 'index')); } @@ -50,7 +50,6 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { ); // We try to list all files according to their type - // A zip file is first opened and then its files are listed $list = array(); if ($type_file === 'zip' && extension_loaded('zip')) { $zip = zip_open($file['tmp_name']); @@ -58,7 +57,8 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { if (!is_resource($zip)) { // zip_open cannot open file: something is wrong Minz_Log::error('Zip archive cannot be imported. Error code: ' . $zip); - Minz_Request::bad(_t('zip_error'), array('c' => 'importExport')); + Minz_Request::bad(_t('zip_error'), + array('c' => 'importExport', 'a' => 'index')); } while (($zipfile = zip_read($zip)) !== false) { @@ -79,14 +79,13 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { zip_close($zip); } elseif ($type_file === 'zip') { // Zip extension is not loaded - Minz_Request::bad(_t('no_zip_extension'), array('c' => 'importExport')); + Minz_Request::bad(_t('no_zip_extension'), + array('c' => 'importExport', 'a' => 'index')); } elseif ($type_file !== 'unknown') { - $list_files[$type_file][] = file_get_contents( - $file['tmp_name'] - ); + $list_files[$type_file][] = file_get_contents($file['tmp_name']); } - // Import different files. + // Import file contents. // OPML first(so categories and feeds are imported) // Starred articles then so the "favourite" status is already set // And finally all other files. @@ -103,7 +102,6 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { // And finally, we get import status and redirect to the home page Minz_Session::_param('actualize_feeds', true); - $content_notif = $error === true ? _t('feeds_imported_with_errors') : _t('feeds_imported'); Minz_Request::good($content_notif); -- cgit v1.2.3 From e3bb80de17c79cf32a2e3a606f216aebf48f92e5 Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Sun, 17 Aug 2014 22:14:13 +0200 Subject: Refactor import / export source code See https://github.com/marienfressinaud/FreshRSS/issues/494 --- app/Controllers/importExportController.php | 107 ++++++++++++++--------------- 1 file changed, 53 insertions(+), 54 deletions(-) (limited to 'app/Controllers') diff --git a/app/Controllers/importExportController.php b/app/Controllers/importExportController.php index 2f5fcc137..2197c3af5 100644 --- a/app/Controllers/importExportController.php +++ b/app/Controllers/importExportController.php @@ -5,7 +5,7 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { if (!$this->view->loginOk) { Minz_Error::error( 403, - array('error' => array(Minz_Translate::t('access_denied'))) + array('error' => array(_t('access_denied'))) ); } @@ -20,13 +20,11 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { $this->view->categories = $this->catDAO->listCategories(); $this->view->feeds = $this->feedDAO->listFeeds(); - Minz_View::prependTitle(Minz_Translate::t('import_export') . ' · '); + Minz_View::prependTitle(_t('import_export') . ' · '); } public function importAction() { if (!Minz_Request::isPost()) { - // What are you doing? you have to call this controller - // with a POST request! Minz_Request::forward(array('c' => 'importExport', 'a' => 'index'), true); } @@ -309,57 +307,53 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { } public function exportAction() { - if (Minz_Request::isPost()) { - $this->view->_useLayout(false); + if (!Minz_Request::isPost()) { + Minz_Request::forward(array('c' => 'importExport', 'a' => 'index'), true); + } - $export_opml = Minz_Request::param('export_opml', false); - $export_starred = Minz_Request::param('export_starred', false); - $export_feeds = Minz_Request::param('export_feeds', array ()); + $this->view->_useLayout(false); - $export_files = array (); - if ($export_opml) { - $export_files['feeds.opml'] = $this->generateOpml(); - } + $export_opml = Minz_Request::param('export_opml', false); + $export_starred = Minz_Request::param('export_starred', false); + $export_feeds = Minz_Request::param('export_feeds', array()); - if ($export_starred) { - $export_files['starred.json'] = $this->generateArticles('starred'); - } + $export_files = array(); + if ($export_opml) { + $export_files['feeds.opml'] = $this->generateOpml(); + } - foreach ($export_feeds as $feed_id) { - $feed = $this->feedDAO->searchById($feed_id); - if ($feed) { - $filename = 'feed_' . $feed->category() . '_' - . $feed->id() . '.json'; - $export_files[$filename] = $this->generateArticles( - 'feed', $feed - ); - } - } + if ($export_starred) { + $export_files['starred.json'] = $this->generateArticles('starred'); + } - $nb_files = count($export_files); - if ($nb_files > 1) { - // If there are more than 1 file to export, we need a zip archive. - try { - $this->exportZip($export_files); - } catch (Exception $e) { - # Oops, there is no Zip extension! - Minz_Request::bad(_t('export_no_zip_extension'), - array('c' => 'importExport', 'a' => 'index')); - } - } elseif ($nb_files === 1) { - // Only one file? Guess its type and export it. - $filename = key($export_files); - $type = null; - if (substr_compare($filename, '.opml', -5) === 0) { - $type = "text/xml"; - } elseif (substr_compare($filename, '.json', -5) === 0) { - $type = "text/json"; - } + foreach ($export_feeds as $feed_id) { + $feed = $this->feedDAO->searchById($feed_id); + if ($feed) { + $filename = 'feed_' . $feed->category() . '_' + . $feed->id() . '.json'; + $export_files[$filename] = $this->generateArticles( + 'feed', $feed + ); + } + } - $this->exportFile($filename, $export_files[$filename], $type); - } else { - Minz_Request::forward(array('c' => 'importExport', 'a' => 'index'), true); + $nb_files = count($export_files); + if ($nb_files > 1) { + // If there are more than 1 file to export, we need a zip archive. + try { + $this->exportZip($export_files); + } catch (Exception $e) { + # Oops, there is no Zip extension! + Minz_Request::bad(_t('export_no_zip_extension'), + array('c' => 'importExport', 'a' => 'index')); } + } elseif ($nb_files === 1) { + // Only one file? Guess its type and export it. + $filename = key($export_files); + $type = $this->guessFileType($filename); + $this->exportFile('freshrss_' . $filename, $export_files[$filename], $type); + } else { + Minz_Request::forward(array('c' => 'importExport', 'a' => 'index'), true); } } @@ -378,7 +372,7 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { $this->view->categories = $this->catDAO->listCategories(); if ($type == 'starred') { - $this->view->list_title = Minz_Translate::t('starred_list'); + $this->view->list_title = _t('starred_list'); $this->view->type = 'starred'; $unread_fav = $this->entryDAO->countUnreadReadFavorites(); $this->view->entries = $this->entryDAO->listWhere( @@ -386,9 +380,7 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { $unread_fav['all'] ); } elseif ($type == 'feed' && !is_null($feed)) { - $this->view->list_title = Minz_Translate::t( - 'feed_list', $feed->name() - ); + $this->view->list_title = _t('feed_list', $feed->name()); $this->view->type = 'feed/' . $feed->id(); $this->view->entries = $this->entryDAO->listWhere( 'f', $feed->id(), FreshRSS_Entry::STATE_ALL, 'ASC', @@ -424,11 +416,18 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { } private function exportFile($filename, $content, $type) { - if (is_null($type)) { + if ($type === 'unknown') { return; } - header('Content-Type: ' . $type . '; charset=utf-8'); + $content_type = ''; + if ($type === 'opml') { + $content_type = "text/opml"; + } elseif ($type === 'json_feed' || $type === 'json_starred') { + $content_type = "text/json"; + } + + header('Content-Type: ' . $content_type . '; charset=utf-8'); header('Content-disposition: attachment; filename=' . $filename); print($content); } -- cgit v1.2.3 From 393fce3e8aaef1b00ab34bf35b7e8b329a5e3dc5 Mon Sep 17 00:00:00 2001 From: Alexis Degrugillier Date: Tue, 19 Aug 2014 08:55:44 -0400 Subject: Add a feed selector in repartition statistics. Before we could choose the feed in the statistics only from the feed options in the main view. Now with the new drop-down list, it is possible to choose it from the statistics page. The rendering needs to be polished to be nicer. --- app/Controllers/statsController.php | 2 ++ app/views/stats/repartition.phtml | 39 +++++++++++++++++++++++++------------ p/scripts/main.js | 7 +++++++ 3 files changed, 36 insertions(+), 12 deletions(-) (limited to 'app/Controllers') diff --git a/app/Controllers/statsController.php b/app/Controllers/statsController.php index 06a20c2a6..934b076a5 100644 --- a/app/Controllers/statsController.php +++ b/app/Controllers/statsController.php @@ -58,9 +58,11 @@ class FreshRSS_stats_Controller extends Minz_ActionController { public function repartitionAction() { $statsDAO = FreshRSS_Factory::createStatsDAO(); + $categoryDAO = new FreshRSS_CategoryDAO(); $feedDAO = FreshRSS_Factory::createFeedDao(); Minz_View::appendScript(Minz_Url::display('/scripts/flotr2.min.js?' . @filemtime(PUBLIC_PATH . '/scripts/flotr2.min.js'))); $id = Minz_Request::param ('id', null); + $this->view->categories = $categoryDAO->listCategories (); $this->view->feed = $feedDAO->searchById($id); $this->view->days = $statsDAO->getDays(); $this->view->months = $statsDAO->getMonths(); diff --git a/app/views/stats/repartition.phtml b/app/views/stats/repartition.phtml index 09892d3c5..3dc319731 100644 --- a/app/views/stats/repartition.phtml +++ b/app/views/stats/repartition.phtml @@ -2,23 +2,38 @@
    - + +

    + + + feed) {?> -

    - - - feed->name(); ?> - -

    - -

    + + + - +

    - +

    @@ -93,7 +108,7 @@ function initStats() { yaxis: {min: 0}, mouse: {relative: true, track: true, trackDecimals: 0, trackFormatter: function(obj) {return numberFormat(obj.y);}} }); - + } initStats(); diff --git a/p/scripts/main.js b/p/scripts/main.js index ae7b69364..4802e0941 100644 --- a/p/scripts/main.js +++ b/p/scripts/main.js @@ -1063,6 +1063,12 @@ function init_share_observers() { }); } +function init_stats_observers() { + $('#feed_select').on('change', function(e) { + redirect($(this).find(':selected').data('url')); + }); +} + function init_remove_observers() { $('.post').on('click', 'a.remove', function(e) { var remove_what = $(this).attr('data-remove'); @@ -1177,6 +1183,7 @@ function init_all() { init_remove_observers(); init_feed_observers(); init_password_observers(); + init_stats_observers(); } if (window.console) { -- cgit v1.2.3 From ea99ac1259083ff0a9eb6131d777454b54045626 Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Tue, 19 Aug 2014 21:55:49 +0200 Subject: Syntax 581 #581 --- app/Controllers/statsController.php | 2 +- app/views/stats/repartition.phtml | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) (limited to 'app/Controllers') diff --git a/app/Controllers/statsController.php b/app/Controllers/statsController.php index 934b076a5..98f46f0d2 100644 --- a/app/Controllers/statsController.php +++ b/app/Controllers/statsController.php @@ -62,7 +62,7 @@ class FreshRSS_stats_Controller extends Minz_ActionController { $feedDAO = FreshRSS_Factory::createFeedDao(); Minz_View::appendScript(Minz_Url::display('/scripts/flotr2.min.js?' . @filemtime(PUBLIC_PATH . '/scripts/flotr2.min.js'))); $id = Minz_Request::param ('id', null); - $this->view->categories = $categoryDAO->listCategories (); + $this->view->categories = $categoryDAO->listCategories(); $this->view->feed = $feedDAO->searchById($id); $this->view->days = $statsDAO->getDays(); $this->view->months = $statsDAO->getMonths(); diff --git a/app/views/stats/repartition.phtml b/app/views/stats/repartition.phtml index 3dc319731..1f920a7ae 100644 --- a/app/views/stats/repartition.phtml +++ b/app/views/stats/repartition.phtml @@ -6,16 +6,16 @@

    conf->display_categories ? ' checked="checked"' : ''; ?> /> + + + +
    +
    +
    -- cgit v1.2.3 From 3b8c381689334a15e7c034425f8615860dc3fa13 Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Wed, 17 Sep 2014 13:37:39 +0200 Subject: No cache for update system See https://github.com/marienfressinaud/FreshRSS/issues/616 --- app/Controllers/updateController.php | 2 ++ 1 file changed, 2 insertions(+) (limited to 'app/Controllers') diff --git a/app/Controllers/updateController.php b/app/Controllers/updateController.php index 78d636163..da5bddc65 100644 --- a/app/Controllers/updateController.php +++ b/app/Controllers/updateController.php @@ -10,6 +10,8 @@ class FreshRSS_update_Controller extends Minz_ActionController { ); } + invalidateHttpCache(); + Minz_View::prependTitle(_t('update_system') . ' · '); $this->view->update_to_apply = false; $this->view->last_update_time = 'unknown'; -- cgit v1.2.3 From 4211539c24e36531f2c5440ad454c4e208131047 Mon Sep 17 00:00:00 2001 From: plopoyop Date: Thu, 18 Sep 2014 10:46:34 +0200 Subject: Add timeout option for HTML5 notification --- app/Controllers/configureController.php | 1 + app/Models/Configuration.php | 7 +++++++ app/i18n/de.php | 2 ++ app/i18n/en.php | 2 ++ app/i18n/fr.php | 2 ++ app/views/configure/display.phtml | 7 +++++++ app/views/helpers/javascript_vars.phtml | 2 ++ p/scripts/main.js | 6 ++++++ 8 files changed, 29 insertions(+) (limited to 'app/Controllers') diff --git a/app/Controllers/configureController.php b/app/Controllers/configureController.php index bb96bfae3..b1cd45014 100755 --- a/app/Controllers/configureController.php +++ b/app/Controllers/configureController.php @@ -157,6 +157,7 @@ class FreshRSS_configure_Controller extends Minz_ActionController { $this->view->conf->_bottomline_tags(Minz_Request::param('bottomline_tags', false)); $this->view->conf->_bottomline_date(Minz_Request::param('bottomline_date', false)); $this->view->conf->_bottomline_link(Minz_Request::param('bottomline_link', false)); + $this->view->conf->_html5_notif_timeout(Minz_Request::param('html5_notif_timeout', 0)); $this->view->conf->save(); Minz_Session::_param('language', $this->view->conf->language); diff --git a/app/Models/Configuration.php b/app/Models/Configuration.php index e4408df73..830d39f0d 100644 --- a/app/Models/Configuration.php +++ b/app/Models/Configuration.php @@ -60,6 +60,7 @@ class FreshRSS_Configuration { 'bottomline_link' => true, 'sharing' => array(), 'queries' => array(), + 'html5_notif_timeout' => 0, ); private $available_languages = array( @@ -269,6 +270,12 @@ class FreshRSS_Configuration { $this->data['content_width'] = 'thin'; } } + + public function _html5_notif_timeout ($value) { + $value = intval($value); + $this->data['html5_notif_timeout'] = $value >= 0 ? $value : 0; + } + public function _token($value) { $this->data['token'] = $value; } diff --git a/app/i18n/de.php b/app/i18n/de.php index 4301a8b6d..3f7d8ca27 100644 --- a/app/i18n/de.php +++ b/app/i18n/de.php @@ -212,6 +212,8 @@ return array ( 'reading_icons' => 'Lese Symbol', 'top_line' => 'Kopfzeile', 'bottom_line' => 'Fusszeile', + 'html5_notif_timeout' => 'HTML5 notification timeout', + 'seconds_(0_means_no_timeout)' => 'seconds (0 means no timeout)', 'img_with_lazyload' => 'Verwende die "träge laden" Methode zum laden von Bildern', 'auto_read_when' => 'Artikel als gelesen markieren…', 'article_selected' => 'wenn der Artikel ausgewählt ist', diff --git a/app/i18n/en.php b/app/i18n/en.php index 8f39115ad..1e842a1e1 100644 --- a/app/i18n/en.php +++ b/app/i18n/en.php @@ -280,6 +280,8 @@ return array ( 'article_icons' => 'Article icons', 'top_line' => 'Top line', 'bottom_line' => 'Bottom line', + 'html5_notif_timeout' => 'HTML5 notification timeout', + 'seconds_(0_means_no_timeout)' => 'seconds (0 means no timeout)', 'img_with_lazyload' => 'Use "lazy load" mode to load pictures', 'sticky_post' => 'Stick the article to the top when opened', 'reading_confirm' => 'Display a confirmation dialog on “mark all as read” actions', diff --git a/app/i18n/fr.php b/app/i18n/fr.php index 48b4c1732..bbfa71f66 100644 --- a/app/i18n/fr.php +++ b/app/i18n/fr.php @@ -280,6 +280,8 @@ return array ( 'article_icons' => 'Icônes d’article', 'top_line' => 'Ligne du haut', 'bottom_line' => 'Ligne du bas', + 'html5_notif_timeout' => 'Temps d\'affichage de la notification HTML5', + 'seconds_(0_means_no_timeout)' => 'secondes (0 signifie aucun timeout ) ', 'img_with_lazyload' => 'Utiliser le mode “chargement différé” pour les images', 'sticky_post' => 'Aligner l’article en haut quand il est ouvert', 'reading_confirm' => 'Afficher une confirmation lors des actions “marquer tout comme lu”', diff --git a/app/views/configure/display.phtml b/app/views/configure/display.phtml index 955fc6747..8eb3a156b 100644 --- a/app/views/configure/display.phtml +++ b/app/views/configure/display.phtml @@ -91,6 +91,13 @@
    + +
    + +
    + +
    +
    diff --git a/app/views/helpers/javascript_vars.phtml b/app/views/helpers/javascript_vars.phtml index 2144f1576..bf0ffdb76 100644 --- a/app/views/helpers/javascript_vars.phtml +++ b/app/views/helpers/javascript_vars.phtml @@ -54,6 +54,8 @@ echo 'authType="', $authType, '",', echo 'str_confirmation="', Minz_Translate::t('confirm_action'), '"', ",\n"; echo 'str_notif_title_articles="', Minz_Translate::t('notif_title_new_articles'), '"', ",\n"; echo 'str_notif_body_articles="', Minz_Translate::t('notif_body_new_articles'), '"', ",\n"; +echo 'html5_notif_timeout=', $this->conf->html5_notif_timeout,",\n"; + $autoActualise = Minz_Session::param('actualize_feeds', false); echo 'auto_actualize_feeds=', $autoActualise ? 'true' : 'false', ";\n"; diff --git a/p/scripts/main.js b/p/scripts/main.js index fd49d62ba..c568bac35 100644 --- a/p/scripts/main.js +++ b/p/scripts/main.js @@ -866,6 +866,12 @@ function notifs_html5_show(nb) { notification.onclick = function() { window.location.reload(); } + + if (html5_notif_timeout !== 0){ + setTimeout(function() { + notification.close(); + }, html5_notif_timeout * 1000); + } } function init_notifs_html5() { -- cgit v1.2.3 From 23609ad858552c54fc3fe8d9f8e2f7d966fd28a1 Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Thu, 18 Sep 2014 14:18:00 +0200 Subject: Add page for reset auth type [NOT WORKING] See https://github.com/marienfressinaud/FreshRSS/issues/521 --- app/Controllers/indexController.php | 77 +++++++++++++++++++++++++++++++++++++ app/views/index/resetAuth.phtml | 33 ++++++++++++++++ 2 files changed, 110 insertions(+) create mode 100644 app/views/index/resetAuth.phtml (limited to 'app/Controllers') diff --git a/app/Controllers/indexController.php b/app/Controllers/indexController.php index 1d74a570b..fccf16ecf 100755 --- a/app/Controllers/indexController.php +++ b/app/Controllers/indexController.php @@ -420,4 +420,81 @@ class FreshRSS_index_Controller extends Minz_ActionController { self::deleteLongTermCookie(); Minz_Request::forward(array('c' => 'index', 'a' => 'index'), true); } + + public function resetAuthAction() { + Minz_View::prependTitle(_t('reset_auth') . ' · '); + + $this->view->no_form = false; + // Enable changement of auth only if Persona! + if (Minz_Configuration::authType() != 'persona') { + $this->view->message = array( + 'status' => 'bad', + 'title' => _t('damn'), + 'body' => _t('auth_not_persona') + ); + $this->view->no_form = true; + return; + } + + $conf = new FreshRSS_Configuration(Minz_Configuration::defaultUser()); + // Admin user must have set its master password. + if (!$conf->passwordHash) { + $this->view->message = array( + 'status' => 'bad', + 'title' => _t('damn'), + 'body' => _t('auth_no_password_set') + ); + $this->view->no_form = true; + return; + } + + if (Minz_Request::isPost()) { + $nonce = Minz_Session::param('nonce'); + $username = Minz_Request::param('username', ''); + $c = Minz_Request::param('challenge', ''); + if (!(ctype_alnum($username) && ctype_graph($c) && ctype_alnum($nonce))) { + Minz_Log::debug('Invalid credential parameters:' . + ' user=' . $username . + ' challenge=' . $c . + ' nonce=' . $nonce); + Minz_Session::_param('notification', array( + 'type' => 'bad', + 'content' => Minz_Translate::t('invalid_login') + )); + return; + } + + if (!function_exists('password_verify')) { + include_once(LIB_PATH . '/password_compat.php'); + } + + try { + $s = $conf->passwordHash; + $ok = password_verify($nonce . $s, $c); + if (!$ok) { + Minz_Log::debug('Password mismatch for user ' . $username . + ', nonce=' . $nonce . ', c=' . $c); + Minz_Session::_param('notification', array( + 'type' => 'bad', + 'content' => Minz_Translate::t('invalid_login') + )); + return; + } + + Minz_Configuration::_authType('form'); + $ok = Minz_Configuration::writeFile(); + + if ($ok) { + Minz_Request::good(_t('auth_form_set')); + } else { + Minz_Session::_param('notification', array( + 'type' => 'bad', + 'content' => _t('auth_form_not_set') + )); + } + } catch (Minz_Exception $e) { + Minz_Log::warning('Login failure: ' . $e->getMessage()); + } + } + } } diff --git a/app/views/index/resetAuth.phtml b/app/views/index/resetAuth.phtml new file mode 100644 index 000000000..7f3b54bdb --- /dev/null +++ b/app/views/index/resetAuth.phtml @@ -0,0 +1,33 @@ +
    +

    + + message)) { ?> +

    + message['title']; ?> + message['body']; ?> +

    + + + no_form) { ?> + +

    + + +

    + +
    + + +
    +
    + + +
    + +
    +
    + +
    + + +
    -- cgit v1.2.3 From f727a1383639d5bdc762f73dfe93b9a5d577cb41 Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Thu, 18 Sep 2014 14:50:54 +0200 Subject: Improve reset auth system - Add bcrypt.js in resetAuthAction() - Rename init_loginForm() in init_crypto_form() - Load init_crypto_form() everytime (if no #crypto-form, do nothing) --- app/Controllers/indexController.php | 3 +++ app/views/index/formLogin.phtml | 2 +- app/views/index/resetAuth.phtml | 2 +- p/scripts/main.js | 27 +++++++++++++++------------ 4 files changed, 20 insertions(+), 14 deletions(-) (limited to 'app/Controllers') diff --git a/app/Controllers/indexController.php b/app/Controllers/indexController.php index fccf16ecf..86863cc84 100755 --- a/app/Controllers/indexController.php +++ b/app/Controllers/indexController.php @@ -423,6 +423,9 @@ class FreshRSS_index_Controller extends Minz_ActionController { public function resetAuthAction() { Minz_View::prependTitle(_t('reset_auth') . ' · '); + Minz_View::appendScript(Minz_Url::display( + '/scripts/bcrypt.min.js?' . @filemtime(PUBLIC_PATH . '/scripts/bcrypt.min.js') + )); $this->view->no_form = false; // Enable changement of auth only if Persona! diff --git a/app/views/index/formLogin.phtml b/app/views/index/formLogin.phtml index b79c1b614..34f10de4a 100644 --- a/app/views/index/formLogin.phtml +++ b/app/views/index/formLogin.phtml @@ -3,7 +3,7 @@ switch (Minz_Configuration::authType()) { case 'form': - ?>
    + ?>
    diff --git a/app/views/index/resetAuth.phtml b/app/views/index/resetAuth.phtml index 7f3b54bdb..78cc527b3 100644 --- a/app/views/index/resetAuth.phtml +++ b/app/views/index/resetAuth.phtml @@ -9,7 +9,7 @@ no_form) { ?> - +

    diff --git a/p/scripts/main.js b/p/scripts/main.js index fd49d62ba..04151c30d 100644 --- a/p/scripts/main.js +++ b/p/scripts/main.js @@ -977,7 +977,7 @@ function init_load_more(box) { } // -// +// function poormanSalt() { //If crypto.getRandomValues is not available var text = '$2a$04$', base = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ.0123456789/abcdefghijklmnopqrstuvwxyz'; @@ -987,20 +987,24 @@ function poormanSalt() { //If crypto.getRandomValues is not available return text; } -function init_loginForm() { - var $loginForm = $('#loginForm'); - if ($loginForm.length === 0) { +function init_crypto_form() { + var $crypto_form = $('#crypto-form'); + if ($crypto_form.length === 0) { return; } + if (!(window.dcodeIO)) { if (window.console) { console.log('FreshRSS waiting for bcrypt.js…'); } - window.setTimeout(init_loginForm, 100); + window.setTimeout(init_crypto_form, 100); return; } - $loginForm.on('submit', function() { - $('#loginButton').attr('disabled', ''); + + $crypto_form.on('submit', function() { + var $submit_button = $(this).find('button[type="submit"]'); + $submit_button.attr('disabled', ''); + var success = false; $.ajax({ url: './?c=javascript&a=nonce&user=' + $('#username').val(), @@ -1027,11 +1031,12 @@ function init_loginForm() { }).fail(function() { alert('Communication error!'); }); - $('#loginButton').removeAttr('disabled'); + + $submit_button.removeAttr('disabled'); return success; }); } -// +// // function init_persona() { @@ -1233,14 +1238,12 @@ function init_all() { } init_notifications(); switch (authType) { - case 'form': - init_loginForm(); - break; case 'persona': init_persona(); break; } init_confirm_action(); + init_crypto_form(); $stream = $('#stream'); if ($stream.length > 0) { init_actualize(); -- cgit v1.2.3 From d2799d168e0d885cb6de24cf012e2a909215fcd8 Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Thu, 18 Sep 2014 15:09:47 +0200 Subject: Improve resetAuth redirections See https://github.com/marienfressinaud/FreshRSS/issues/521 --- app/Controllers/indexController.php | 39 ++++++++++++++----------------------- 1 file changed, 15 insertions(+), 24 deletions(-) (limited to 'app/Controllers') diff --git a/app/Controllers/indexController.php b/app/Controllers/indexController.php index 86863cc84..26e2618f0 100755 --- a/app/Controllers/indexController.php +++ b/app/Controllers/indexController.php @@ -451,6 +451,8 @@ class FreshRSS_index_Controller extends Minz_ActionController { return; } + invalidateHttpCache(); + if (Minz_Request::isPost()) { $nonce = Minz_Session::param('nonce'); $username = Minz_Request::param('username', ''); @@ -460,43 +462,32 @@ class FreshRSS_index_Controller extends Minz_ActionController { ' user=' . $username . ' challenge=' . $c . ' nonce=' . $nonce); - Minz_Session::_param('notification', array( - 'type' => 'bad', - 'content' => Minz_Translate::t('invalid_login') - )); - return; + Minz_Request::bad(_t('invalid_login'), + array('c' => 'index', 'a' => 'resetAuth')); } if (!function_exists('password_verify')) { include_once(LIB_PATH . '/password_compat.php'); } - try { - $s = $conf->passwordHash; - $ok = password_verify($nonce . $s, $c); - if (!$ok) { - Minz_Log::debug('Password mismatch for user ' . $username . - ', nonce=' . $nonce . ', c=' . $c); - Minz_Session::_param('notification', array( - 'type' => 'bad', - 'content' => Minz_Translate::t('invalid_login') - )); - return; - } - + $s = $conf->passwordHash; + $ok = password_verify($nonce . $s, $c); + if ($ok) { Minz_Configuration::_authType('form'); $ok = Minz_Configuration::writeFile(); if ($ok) { Minz_Request::good(_t('auth_form_set')); } else { - Minz_Session::_param('notification', array( - 'type' => 'bad', - 'content' => _t('auth_form_not_set') - )); + Minz_Request::bad(_t('auth_form_not_set'), + array('c' => 'index', 'a' => 'resetAuth')); } - } catch (Minz_Exception $e) { - Minz_Log::warning('Login failure: ' . $e->getMessage()); + } else { + Minz_Log::debug('Password mismatch for user ' . $username . + ', nonce=' . $nonce . ', c=' . $c); + + Minz_Request::bad(_t('invalid_login'), + array('c' => 'index', 'a' => 'resetAuth')); } } } -- cgit v1.2.3 From 94915f1c0a477c240e6072b6ccc2f71152181510 Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Thu, 18 Sep 2014 15:32:59 +0200 Subject: Reset auth system: i18n and style See https://github.com/marienfressinaud/FreshRSS/issues/521 --- app/Controllers/indexController.php | 2 +- app/i18n/en.php | 10 ++++++++++ app/i18n/fr.php | 10 ++++++++++ app/views/index/resetAuth.phtml | 12 ++++++------ 4 files changed, 27 insertions(+), 7 deletions(-) (limited to 'app/Controllers') diff --git a/app/Controllers/indexController.php b/app/Controllers/indexController.php index 26e2618f0..b69c09127 100755 --- a/app/Controllers/indexController.php +++ b/app/Controllers/indexController.php @@ -422,7 +422,7 @@ class FreshRSS_index_Controller extends Minz_ActionController { } public function resetAuthAction() { - Minz_View::prependTitle(_t('reset_auth') . ' · '); + Minz_View::prependTitle(_t('auth_reset') . ' · '); Minz_View::appendScript(Minz_Url::display( '/scripts/bcrypt.min.js?' . @filemtime(PUBLIC_PATH . '/scripts/bcrypt.min.js') )); diff --git a/app/i18n/en.php b/app/i18n/en.php index b14b36b32..11edc603f 100644 --- a/app/i18n/en.php +++ b/app/i18n/en.php @@ -159,6 +159,7 @@ return array ( 'save' => 'Save', 'delete' => 'Delete', 'cancel' => 'Cancel', + 'submit' => 'Submit', 'back_to_rss_feeds' => '← Go back to your RSS feeds', 'feeds_moved_category_deleted' => 'When you delete a category, their feeds are automatically classified under %s.', @@ -204,6 +205,7 @@ return array ( 'informations' => 'Information', 'damn' => 'Damn!', 'ok' => 'Ok!', + 'attention' => 'Be careful!', 'feed_in_error' => 'This feed has encountered a problem. Please verify that it is always reachable then actualize it.', 'feed_empty' => 'This feed is empty. Please verify that it is still maintained.', 'feed_description' => 'Description', @@ -255,6 +257,7 @@ return array ( 'users_list' => 'List of users', 'create_user' => 'Create new user', 'username' => 'Username', + 'username_admin' => 'Administrator username', 'password' => 'Password', 'create' => 'Create', 'user_created' => 'User %s has been created', @@ -434,4 +437,11 @@ return array ( 'no_update' => 'No update to apply', 'update_problem' => 'The update process has encountered an error: %s', 'update_finished' => 'Update completed!', + + 'auth_reset' => 'Authentication reset', + 'auth_will_reset' => 'Authentication system will be reseted: form will be used instead of Persona.', + 'auth_not_persona' => 'Only Persona system can be reseted.', + 'auth_no_password_set' => 'Administrator password hasn’t been set. This feature isn’t available.', + 'auth_form_set' => 'Form is now your default authentication system.', + 'auth_form_not_set' => 'A problem occured during authentication system configuration. Please retry later.', ); diff --git a/app/i18n/fr.php b/app/i18n/fr.php index 3dc429bbb..279cb89fa 100644 --- a/app/i18n/fr.php +++ b/app/i18n/fr.php @@ -159,6 +159,7 @@ return array ( 'save' => 'Enregistrer', 'delete' => 'Supprimer', 'cancel' => 'Annuler', + 'submit' => 'Valider', 'back_to_rss_feeds' => '← Retour à vos flux RSS', 'feeds_moved_category_deleted' => 'Lors de la suppression d’une catégorie, ses flux seront automatiquement classés dans %s.', @@ -204,6 +205,7 @@ return array ( 'informations' => 'Informations', 'damn' => 'Arf !', 'ok' => 'Ok !', + 'attention' => 'Attention !', 'feed_in_error' => 'Ce flux a rencontré un problème. Veuillez vérifier qu’il est toujours accessible puis actualisez-le.', 'feed_empty' => 'Ce flux est vide. Veuillez vérifier qu’il est toujours maintenu.', 'feed_description' => 'Description', @@ -255,6 +257,7 @@ return array ( 'users_list' => 'Liste des utilisateurs', 'create_user' => 'Créer un nouvel utilisateur', 'username' => 'Nom d’utilisateur', + 'username_admin' => 'Nom d’utilisateur administrateur', 'password' => 'Mot de passe', 'create' => 'Créer', 'user_created' => 'L’utilisateur %s a été créé.', @@ -434,4 +437,11 @@ return array ( 'no_update' => 'Aucune mise à jour à appliquer', 'update_problem' => 'La mise à jour a rencontré un problème : %s', 'update_finished' => 'La mise à jour est terminée !', + + 'auth_reset' => 'Reset de l’authentification', + 'auth_will_reset' => 'Le système d’authentification va être remis à zéro : le formulaire sera utilisé à la place de Persona.', + 'auth_not_persona' => 'Seul le système d’authentification Persona peut être remis à zéro.', + 'auth_no_password_set' => 'Aucun mot de passe administrateur n’a été précisé. Cette fonctionnalité n’est pas disponible.', + 'auth_form_set' => 'Le formulaire est désormais votre système d’authentification.', + 'auth_form_not_set' => 'Un problème est survenu lors de la configuration de votre système d’authentification. Veuillez réessayer plus tard.', ); diff --git a/app/views/index/resetAuth.phtml b/app/views/index/resetAuth.phtml index 78cc527b3..6d4282c14 100644 --- a/app/views/index/resetAuth.phtml +++ b/app/views/index/resetAuth.phtml @@ -1,9 +1,9 @@ -

    -

    +
    +

    message)) { ?>

    - message['title']; ?> + message['title']; ?>
    message['body']; ?>

    @@ -11,12 +11,12 @@ no_form) { ?>

    - +

    - +
    @@ -26,7 +26,7 @@
    - +
    -- cgit v1.2.3 From ba378791c2f36251a22e8a98351ab9b23a640c17 Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Fri, 19 Sep 2014 13:52:50 +0200 Subject: Fix blank page for invalid user queries Add a "deprecated" information See https://github.com/marienfressinaud/FreshRSS/issues/625 --- app/Controllers/configureController.php | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) (limited to 'app/Controllers') diff --git a/app/Controllers/configureController.php b/app/Controllers/configureController.php index b1cd45014..f72683e34 100755 --- a/app/Controllers/configureController.php +++ b/app/Controllers/configureController.php @@ -328,6 +328,8 @@ class FreshRSS_configure_Controller extends Minz_ActionController { Minz_Request::forward(array('c' => 'configure', 'a' => 'queries'), true); } else { $this->view->query_get = array(); + $cat_dao = new FreshRSS_CategoryDAO(); + $feed_dao = FreshRSS_Factory::createFeedDao(); foreach ($this->view->conf->queries as $key => $query) { if (!isset($query['get'])) { continue; @@ -335,31 +337,49 @@ class FreshRSS_configure_Controller extends Minz_ActionController { switch ($query['get'][0]) { case 'c': - $dao = new FreshRSS_CategoryDAO(); - $category = $dao->searchById(substr($query['get'], 2)); + $category = $cat_dao->searchById(substr($query['get'], 2)); + + $deprecated = true; + $cat_name = ''; + if ($category) { + $cat_name = $category->name(); + $deprecated = false; + } + $this->view->query_get[$key] = array( 'type' => 'category', - 'name' => $category->name(), + 'name' => $cat_name, + 'deprecated' => $deprecated, ); break; case 'f': - $dao = FreshRSS_Factory::createFeedDao(); - $feed = $dao->searchById(substr($query['get'], 2)); + $feed = $feed_dao->searchById(substr($query['get'], 2)); + + $deprecated = true; + $feed_name = ''; + if ($feed) { + $feed_name = $feed->name(); + $deprecated = false; + } + $this->view->query_get[$key] = array( 'type' => 'feed', - 'name' => $feed->name(), + 'name' => $feed_name, + 'deprecated' => $deprecated, ); break; case 's': $this->view->query_get[$key] = array( 'type' => 'favorite', 'name' => 'favorite', + 'deprecated' => false, ); break; case 'a': $this->view->query_get[$key] = array( 'type' => 'all', 'name' => 'all', + 'deprecated' => false, ); break; } -- cgit v1.2.3 From ae4ecd8e099b23d38e761dec6de68da801816734 Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Fri, 19 Sep 2014 15:44:33 +0200 Subject: Coding style (user queries) --- app/Controllers/configureController.php | 17 ++++++----------- app/i18n/en.php | 1 + app/i18n/fr.php | 1 + app/layout/nav_menu.phtml | 5 ++++- 4 files changed, 12 insertions(+), 12 deletions(-) (limited to 'app/Controllers') diff --git a/app/Controllers/configureController.php b/app/Controllers/configureController.php index f72683e34..91b74aa29 100755 --- a/app/Controllers/configureController.php +++ b/app/Controllers/configureController.php @@ -313,19 +313,14 @@ class FreshRSS_configure_Controller extends Minz_ActionController { foreach ($queries as $key => $query) { if (!$query['name']) { - $query['name'] = Minz_Translate::t('query_number', $key + 1); + $query['name'] = _t('query_number', $key + 1); } } $this->view->conf->_queries($queries); $this->view->conf->save(); - $notif = array( - 'type' => 'good', - 'content' => Minz_Translate::t('configuration_updated') - ); - Minz_Session::_param('notification', $notif); - - Minz_Request::forward(array('c' => 'configure', 'a' => 'queries'), true); + Minz_Request::good(_t('configuration_updated'), + array('c' => 'configure', 'a' => 'queries')); } else { $this->view->query_get = array(); $cat_dao = new FreshRSS_CategoryDAO(); @@ -392,14 +387,14 @@ class FreshRSS_configure_Controller extends Minz_ActionController { public function addQueryAction() { $queries = $this->view->conf->queries; $query = Minz_Request::params(); - $query['name'] = Minz_Translate::t('query_number', count($queries) + 1); + $query['name'] = _t('query_number', count($queries) + 1); unset($query['output']); unset($query['token']); $queries[] = $query; $this->view->conf->_queries($queries); $this->view->conf->save(); - // Minz_Request::forward(array('params' => $query), true); - Minz_Request::forward(array('c' => 'configure', 'a' => 'queries'), true); + Minz_Request::good(_t('query_created', $query['name']), + array('c' => 'configure', 'a' => 'queries')); } } diff --git a/app/i18n/en.php b/app/i18n/en.php index e2bd21623..e84787508 100644 --- a/app/i18n/en.php +++ b/app/i18n/en.php @@ -43,6 +43,7 @@ return array ( 'query_state_15' => 'Display all articles', 'query_number' => 'Query n°%d', 'add_query' => 'Add a query', + 'query_created' => 'Query "%s" has been created.', 'no_query' => 'You haven’t created any user query yet.', 'query_filter' => 'Filter applied:', 'no_query_filter' => 'No filter', diff --git a/app/i18n/fr.php b/app/i18n/fr.php index e06bba487..fab5c3470 100644 --- a/app/i18n/fr.php +++ b/app/i18n/fr.php @@ -43,6 +43,7 @@ return array ( 'query_state_15' => 'Afficher tous les articles', 'query_number' => 'Filtre n°%d', 'add_query' => 'Créer un filtre', + 'query_created' => 'Le filtre "%s" a bien été créé.', 'no_query' => 'Vous n’avez pas encore créé de filtre.', 'query_filter' => 'Filtres appliqués :', 'no_query_filter' => 'Aucun filtre appliqué', diff --git a/app/layout/nav_menu.phtml b/app/layout/nav_menu.phtml index 2a09b0c12..03f496d1f 100644 --- a/app/layout/nav_menu.phtml +++ b/app/layout/nav_menu.phtml @@ -93,7 +93,10 @@
    -- cgit v1.2.3 From d284958d52f633738e8cc736f7f4a50e0c984ecf Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Fri, 19 Sep 2014 19:44:40 +0200 Subject: Improve redirection when deleting an idle feed --- app/Controllers/feedController.php | 9 ++++++--- app/views/stats/idle.phtml | 6 +++++- lib/Minz/Request.php | 5 +++++ 3 files changed, 16 insertions(+), 4 deletions(-) (limited to 'app/Controllers') diff --git a/app/Controllers/feedController.php b/app/Controllers/feedController.php index 3326b2059..65d4b3a37 100755 --- a/app/Controllers/feedController.php +++ b/app/Controllers/feedController.php @@ -412,10 +412,13 @@ class FreshRSS_feed_Controller extends Minz_ActionController { Minz_Session::_param ('notification', $notif); - if ($type == 'category') { - Minz_Request::forward (array ('c' => 'configure', 'a' => 'categorize'), true); + $redirect_url = Minz_Request::param('r', false, true); + if ($redirect_url) { + Minz_Request::forward($redirect_url); + } elseif ($type == 'category') { + Minz_Request::forward(array ('c' => 'configure', 'a' => 'categorize'), true); } else { - Minz_Request::forward (array ('c' => 'configure', 'a' => 'feed'), true); + Minz_Request::forward(array ('c' => 'configure', 'a' => 'feed'), true); } } } diff --git a/app/views/stats/idle.phtml b/app/views/stats/idle.phtml index 3ce8d3d3e..6f3d4a117 100644 --- a/app/views/stats/idle.phtml +++ b/app/views/stats/idle.phtml @@ -6,6 +6,10 @@

    'stats', 'a' => 'idle'), + 'php', true + )); $nothing = true; foreach ($this->idleFeeds as $period => $feeds) { if (!empty($feeds)) { @@ -22,7 +26,7 @@
    - +
  • diff --git a/lib/Minz/Request.php b/lib/Minz/Request.php index 52f53012f..f7a24c026 100644 --- a/lib/Minz/Request.php +++ b/lib/Minz/Request.php @@ -124,6 +124,11 @@ class Minz_Request { * > sinon, le dispatcher recharge en interne */ public static function forward($url = array(), $redirect = false) { + if (!is_array($url)) { + header('Location: ' . $url); + exit(); + } + $url = Minz_Url::checkUrl($url); if ($redirect) { -- cgit v1.2.3 From a14d325432b1d6515beaaccef73fa01f3ab3a85c Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Fri, 19 Sep 2014 20:30:54 +0200 Subject: Auto-redirect from formLogin if already logged in --- app/Controllers/indexController.php | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'app/Controllers') diff --git a/app/Controllers/indexController.php b/app/Controllers/indexController.php index bc03f1916..e8e26b142 100755 --- a/app/Controllers/indexController.php +++ b/app/Controllers/indexController.php @@ -337,6 +337,10 @@ class FreshRSS_index_Controller extends Minz_ActionController { } public function formLoginAction () { + if ($this->view->loginOk) { + Minz_Request::forward(array('c' => 'index', 'a' => 'index'), true); + } + if (Minz_Request::isPost()) { $ok = false; $nonce = Minz_Session::param('nonce'); -- cgit v1.2.3 From ea7f21ba7f4049b51c2978b7ebaad00e17fa5af5 Mon Sep 17 00:00:00 2001 From: Alexis Degrugillier Date: Sun, 21 Sep 2014 11:21:51 -0400 Subject: Add some comments It is a work in progress. I only did that for a single class so you can tell me if this is a good idea, if the content is missing something. Any comments are welcome. --- app/Controllers/configureController.php | 172 +++++++++++++++++++++++++++++++- 1 file changed, 170 insertions(+), 2 deletions(-) (limited to 'app/Controllers') diff --git a/app/Controllers/configureController.php b/app/Controllers/configureController.php index 91b74aa29..8fae29ea2 100755 --- a/app/Controllers/configureController.php +++ b/app/Controllers/configureController.php @@ -1,6 +1,17 @@ view->loginOk) { Minz_Error::error( @@ -13,6 +24,17 @@ class FreshRSS_configure_Controller extends Minz_ActionController { $catDAO->checkDefault(); } + /** + * This action handles the category configuration page + * + * It displays the category configuration page. + * If this action is reached through a POST request, it loops through + * every category to check for modification then add a new category if + * needed then sends a notification to the user. + * If a category name is emptied, the category is deleted and all + * related feeds are moved to the default category. + * If a category name is changed, it is updated. + */ public function categorizeAction() { $feedDAO = FreshRSS_Factory::createFeedDao(); $catDAO = new FreshRSS_CategoryDAO(); @@ -66,6 +88,28 @@ class FreshRSS_configure_Controller extends Minz_ActionController { Minz_View::prependTitle(Minz_Translate::t('categories_management') . ' · '); } + /** + * This action handles the feed configuration page. + * + * It displays the feed configuration page. + * If this action is reached through a POST request, it stores all new + * configuraiton values then sends a notification to the user. + * + * The options available on the page are: + * - name + * - description + * - website URL + * - feed URL + * - category id (default: default category id) + * - CSS path to article on website + * - display in main stream (default: 0) + * - HTTP authentication + * - number of article to retain (default: -2) + * - refresh frequency (default: -2) + * Default values are empty strings unless specified. + * + * @todo change the notification code + */ public function feedAction() { $catDAO = new FreshRSS_CategoryDAO(); $this->view->categories = $catDAO->listCategories(false); @@ -138,6 +182,33 @@ class FreshRSS_configure_Controller extends Minz_ActionController { } } + /** + * This action handles the display configuration page. + * + * It displays the display configuration page. + * If this action is reached through a POST request, it stores all new + * configuration values then sends a notification to the user. + * + * The options available on the page are: + * - language (default: en) + * - theme (default: Origin) + * - content width (default: thin) + * - display of read action in header + * - display of favorite action in header + * - display of date in header + * - display of open action in header + * - display of read action in footer + * - display of favorite action in footer + * - display of sharing action in footer + * - display of tags in footer + * - display of date in footer + * - display of open action in footer + * - html5 notification timeout (default: 0) + * Default values are false unless specified. + * + * @todo refactor to theme section to use the same syntax everywhere + * @todo change the notification code + */ public function displayAction() { if (Minz_Request::isPost()) { $this->view->conf->_language(Minz_Request::param('language', 'en')); @@ -178,6 +249,35 @@ class FreshRSS_configure_Controller extends Minz_ActionController { Minz_View::prependTitle(Minz_Translate::t('display_configuration') . ' · '); } + /** + * This action handles the reading configuration page. + * + * It displays the reading configuration page. + * If this action is reached through a POST request, it stores all new + * configuration values then sends a notification to the user. + * + * The options available on the page are: + * - number of posts per page (default: 10) + * - view mode (default: normal) + * - default article view (default: all) + * - load automatically articles + * - display expanded articles + * - display expanded categories + * - hide categories and feeds without unread articles + * - jump on next category or feed when marked as read + * - image lazy loading + * - stick open articles to the top + * - display a confirmation when reading all articles + * - article order (default: DESC) + * - mark articles as read when: + * - displayed + * - opened on site + * - scrolled + * - received + * Default values are false unless specified. + * + * @todo change the notification code + */ public function readingAction() { if (Minz_Request::isPost()) { $this->view->conf->_posts_per_page(Minz_Request::param('posts_per_page', 10)); @@ -216,6 +316,15 @@ class FreshRSS_configure_Controller extends Minz_ActionController { Minz_View::prependTitle(Minz_Translate::t('reading_configuration') . ' · '); } + /** + * This action handles the sharing configuration page. + * + * It displays the sharing configuration page. + * If this action is reached through a POST request, it stores all + * configuration values then sends a notification to the user. + * + * @todo change the notification code + */ public function sharingAction() { if (Minz_Request::isPost()) { $params = Minz_Request::params(); @@ -235,6 +344,22 @@ class FreshRSS_configure_Controller extends Minz_ActionController { Minz_View::prependTitle(Minz_Translate::t('sharing') . ' · '); } + /** + * This action handles the shortcut configuration page. + * + * It displays the shortcut configuration page. + * If this action is reached through a POST request, it stores all new + * configuration values then sends a notification to the user. + * + * The authorized values for shortcuts are letters (a to z), numbers (0 + * to 9), function keys (f1 to f12), backspace, delete, down, end, enter, + * escape, home, insert, left, page down, page up, return, right, space, + * tab and up. + * + * @todo remove numbers from the list of authorized shortcuts since they + * are used to access shortcuts and user queries + * @todo change the notification code + */ public function shortcutAction() { $list_keys = array('a', 'b', 'backspace', 'c', 'd', 'delete', 'down', 'e', 'end', 'enter', 'escape', 'f', 'g', 'h', 'home', 'i', 'insert', 'j', 'k', 'l', 'left', @@ -271,10 +396,31 @@ class FreshRSS_configure_Controller extends Minz_ActionController { Minz_View::prependTitle(Minz_Translate::t('shortcuts') . ' · '); } + /** + * This action display the user configuration page + * + * @todo move that action in the user controller + */ public function usersAction() { Minz_View::prependTitle(Minz_Translate::t('users') . ' · '); } + /** + * This action handles the archive configuration page. + * + * It displays the archive configuration page. + * If this action is reached through a POST request, it stores all new + * configuration values then sends a notification to the user. + * + * The options available on that page are: + * - duration to retain old article (default: 3) + * - number of article to retain per feed (default: 0) + * - refresh frequency (default: -2) + * + * @todo explain why the default value is -2 but this value does not + * exist in the drop-down list + * @todo change the notification code + */ public function archivingAction() { if (Minz_Request::isPost()) { $old = Minz_Request::param('old_entries', 3); @@ -306,7 +452,17 @@ class FreshRSS_configure_Controller extends Minz_ActionController { $this->view->size_total = $entryDAO->size(true); } } - + + /** + * This action handles the user queries configuration page. + * + * If this action is reached through a POST request, it stores all new + * configuration values then sends a notification to the user then + * redirect to the same page. + * If this action is not reached through a POST request, it displays the + * configuration page and verifies that every user query is runable by + * checking if categories and feeds are still in use. + */ public function queriesAction() { if (Minz_Request::isPost()) { $queries = Minz_Request::param('queries', array()); @@ -383,7 +539,19 @@ class FreshRSS_configure_Controller extends Minz_ActionController { Minz_View::prependTitle(Minz_Translate::t('queries') . ' · '); } - + + /** + * This action handles the creation of a user query. + * + * It gets the GET parameters and stores them in the configuration query + * storage. Before it is saved, the unwanted parameters are unset to keep + * lean data. + * + * @todo change the way of keeping lean data to have a more defensive + * code. At the moment, the code accepts any parameters and discard those + * on the black list. I think it is safer if we maintain a whitelist + * instead. + */ public function addQueryAction() { $queries = $this->view->conf->queries; $query = Minz_Request::params(); -- cgit v1.2.3 From cf01508fa58b5bdd85606fb766a79643a04ca6b7 Mon Sep 17 00:00:00 2001 From: Alexis Degrugillier Date: Sun, 21 Sep 2014 11:25:53 -0400 Subject: Fix typo --- app/Controllers/configureController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app/Controllers') diff --git a/app/Controllers/configureController.php b/app/Controllers/configureController.php index 8fae29ea2..7340ca17a 100755 --- a/app/Controllers/configureController.php +++ b/app/Controllers/configureController.php @@ -206,7 +206,7 @@ class FreshRSS_configure_Controller extends Minz_ActionController { * - html5 notification timeout (default: 0) * Default values are false unless specified. * - * @todo refactor to theme section to use the same syntax everywhere + * @todo refactor theme section to use the same syntax everywhere * @todo change the notification code */ public function displayAction() { -- cgit v1.2.3 From bad165b0c424c9c650e22fec11ceecf1748d98e7 Mon Sep 17 00:00:00 2001 From: Alexis Degrugillier Date: Sun, 21 Sep 2014 11:28:46 -0400 Subject: Add a todo --- app/Controllers/configureController.php | 2 ++ 1 file changed, 2 insertions(+) (limited to 'app/Controllers') diff --git a/app/Controllers/configureController.php b/app/Controllers/configureController.php index 7340ca17a..99c72d869 100755 --- a/app/Controllers/configureController.php +++ b/app/Controllers/configureController.php @@ -420,6 +420,8 @@ class FreshRSS_configure_Controller extends Minz_ActionController { * @todo explain why the default value is -2 but this value does not * exist in the drop-down list * @todo change the notification code + * @todo refactor configuration setting syntax to be consistent with the + * other methods */ public function archivingAction() { if (Minz_Request::isPost()) { -- cgit v1.2.3 From 0420a989391c9af30ee6d85638c81bfb4f605132 Mon Sep 17 00:00:00 2001 From: Alexis Degrugillier Date: Sun, 21 Sep 2014 11:51:53 -0400 Subject: Add comments on the stat controller --- app/Controllers/statsController.php | 42 +++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) (limited to 'app/Controllers') diff --git a/app/Controllers/statsController.php b/app/Controllers/statsController.php index 000b41dd2..64f66f2fa 100644 --- a/app/Controllers/statsController.php +++ b/app/Controllers/statsController.php @@ -1,7 +1,21 @@ view->topFeed = $statsDAO->calculateTopFeed(); } + /** + * This action handles the idle feed statistic page. + * + * It displays the list of idle feed for different period. The supported + * periods are: + * - last year + * - last 6 months + * - last 3 months + * - last month + * - last week + */ public function idleAction() { $statsDAO = FreshRSS_Factory::createStatsDAO(); $feeds = $statsDAO->calculateFeedLastDate(); @@ -56,6 +81,18 @@ class FreshRSS_stats_Controller extends Minz_ActionController { $this->view->idleFeeds = $idleFeeds; } + /** + * This action handles the article repartition statistic page. + * + * It displays the number of article and the average of article for the + * following periods: + * - hour of the day + * - day of the week + * - month + * + * @todo verify that the metrics used here make some sense. Especially + * for the average. + */ public function repartitionAction() { $statsDAO = FreshRSS_Factory::createStatsDAO(); $categoryDAO = new FreshRSS_CategoryDAO(); @@ -74,6 +111,11 @@ class FreshRSS_stats_Controller extends Minz_ActionController { $this->view->averageMonth = $statsDAO->calculateEntryAveragePerFeedPerMonth($id); } + /** + * This action is called before every other action in that class. It is + * the common boiler plate for every action. It is triggered by the + * underlying framework. + */ public function firstAction() { if (!$this->view->loginOk) { Minz_Error::error( -- cgit v1.2.3 From 5889ef35c8ecbbae948420af9f2b05e7010593ae Mon Sep 17 00:00:00 2001 From: Alexis Degrugillier Date: Sun, 21 Sep 2014 12:01:36 -0400 Subject: Change todo align --- app/Controllers/configureController.php | 14 +++++++------- app/Controllers/statsController.php | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) (limited to 'app/Controllers') diff --git a/app/Controllers/configureController.php b/app/Controllers/configureController.php index 99c72d869..7b8dd672f 100755 --- a/app/Controllers/configureController.php +++ b/app/Controllers/configureController.php @@ -10,7 +10,7 @@ class FreshRSS_configure_Controller extends Minz_ActionController { * underlying framework. * * @todo see if the category default configuration is needed here or if - * we can move it to the categorize action + * we can move it to the categorize action */ public function firstAction() { if (!$this->view->loginOk) { @@ -357,7 +357,7 @@ class FreshRSS_configure_Controller extends Minz_ActionController { * tab and up. * * @todo remove numbers from the list of authorized shortcuts since they - * are used to access shortcuts and user queries + * are used to access shortcuts and user queries * @todo change the notification code */ public function shortcutAction() { @@ -418,10 +418,10 @@ class FreshRSS_configure_Controller extends Minz_ActionController { * - refresh frequency (default: -2) * * @todo explain why the default value is -2 but this value does not - * exist in the drop-down list + * exist in the drop-down list * @todo change the notification code * @todo refactor configuration setting syntax to be consistent with the - * other methods + * other methods */ public function archivingAction() { if (Minz_Request::isPost()) { @@ -550,9 +550,9 @@ class FreshRSS_configure_Controller extends Minz_ActionController { * lean data. * * @todo change the way of keeping lean data to have a more defensive - * code. At the moment, the code accepts any parameters and discard those - * on the black list. I think it is safer if we maintain a whitelist - * instead. + * code. At the moment, the code accepts any parameters and discard + * those on the black list. I think it is safer if we maintain a + * whitelist instead. */ public function addQueryAction() { $queries = $this->view->conf->queries; diff --git a/app/Controllers/statsController.php b/app/Controllers/statsController.php index 64f66f2fa..256543f37 100644 --- a/app/Controllers/statsController.php +++ b/app/Controllers/statsController.php @@ -91,7 +91,7 @@ class FreshRSS_stats_Controller extends Minz_ActionController { * - month * * @todo verify that the metrics used here make some sense. Especially - * for the average. + * for the average. */ public function repartitionAction() { $statsDAO = FreshRSS_Factory::createStatsDAO(); -- cgit v1.2.3 From f87dfbc528f4fa451a2a213fdbd3fcaf27308de8 Mon Sep 17 00:00:00 2001 From: Alexis Degrugillier Date: Sun, 21 Sep 2014 18:09:10 -0400 Subject: Change notification code Before, it was using the old way to display notifications to the user. Now, it uses the encapsulated method to do that. --- app/Controllers/configureController.php | 63 +++++++-------------------------- 1 file changed, 12 insertions(+), 51 deletions(-) (limited to 'app/Controllers') diff --git a/app/Controllers/configureController.php b/app/Controllers/configureController.php index 7b8dd672f..1e341b412 100755 --- a/app/Controllers/configureController.php +++ b/app/Controllers/configureController.php @@ -72,13 +72,8 @@ class FreshRSS_configure_Controller extends Minz_ActionController { } invalidateHttpCache(); - $notif = array( - 'type' => 'good', - 'content' => Minz_Translate::t('categories_updated') - ); - Minz_Session::_param('notification', $notif); - - Minz_Request::forward(array('c' => 'configure', 'a' => 'categorize'), true); + Minz_Request::good(_t('categories_updated'), + array('c' => 'configure', 'a' => 'categorize')); } $this->view->categories = $catDAO->listCategories(false); @@ -107,8 +102,6 @@ class FreshRSS_configure_Controller extends Minz_ActionController { * - number of article to retain (default: -2) * - refresh frequency (default: -2) * Default values are empty strings unless specified. - * - * @todo change the notification code */ public function feedAction() { $catDAO = new FreshRSS_CategoryDAO(); @@ -207,7 +200,6 @@ class FreshRSS_configure_Controller extends Minz_ActionController { * Default values are false unless specified. * * @todo refactor theme section to use the same syntax everywhere - * @todo change the notification code */ public function displayAction() { if (Minz_Request::isPost()) { @@ -235,13 +227,8 @@ class FreshRSS_configure_Controller extends Minz_ActionController { Minz_Translate::reset(); invalidateHttpCache(); - $notif = array( - 'type' => 'good', - 'content' => Minz_Translate::t('configuration_updated') - ); - Minz_Session::_param('notification', $notif); - - Minz_Request::forward(array('c' => 'configure', 'a' => 'display'), true); + Minz_Request::good(_t('configuration_updated'), + array('c' => 'configure', 'a' => 'display')); } $this->view->themes = FreshRSS_Themes::get(); @@ -275,8 +262,6 @@ class FreshRSS_configure_Controller extends Minz_ActionController { * - scrolled * - received * Default values are false unless specified. - * - * @todo change the notification code */ public function readingAction() { if (Minz_Request::isPost()) { @@ -304,13 +289,8 @@ class FreshRSS_configure_Controller extends Minz_ActionController { Minz_Translate::reset(); invalidateHttpCache(); - $notif = array( - 'type' => 'good', - 'content' => Minz_Translate::t('configuration_updated') - ); - Minz_Session::_param('notification', $notif); - - Minz_Request::forward(array('c' => 'configure', 'a' => 'reading'), true); + Minz_Request::good(_t('configuration_updated'), + array('c' => 'configure', 'a' => 'reading')); } Minz_View::prependTitle(Minz_Translate::t('reading_configuration') . ' · '); @@ -322,8 +302,6 @@ class FreshRSS_configure_Controller extends Minz_ActionController { * It displays the sharing configuration page. * If this action is reached through a POST request, it stores all * configuration values then sends a notification to the user. - * - * @todo change the notification code */ public function sharingAction() { if (Minz_Request::isPost()) { @@ -332,13 +310,8 @@ class FreshRSS_configure_Controller extends Minz_ActionController { $this->view->conf->save(); invalidateHttpCache(); - $notif = array( - 'type' => 'good', - 'content' => Minz_Translate::t('configuration_updated') - ); - Minz_Session::_param('notification', $notif); - - Minz_Request::forward(array('c' => 'configure', 'a' => 'sharing'), true); + Minz_Request::good(_t('configuration_updated'), + array('c' => 'configure', 'a' => 'sharing')); } Minz_View::prependTitle(Minz_Translate::t('sharing') . ' · '); @@ -358,7 +331,6 @@ class FreshRSS_configure_Controller extends Minz_ActionController { * * @todo remove numbers from the list of authorized shortcuts since they * are used to access shortcuts and user queries - * @todo change the notification code */ public function shortcutAction() { $list_keys = array('a', 'b', 'backspace', 'c', 'd', 'delete', 'down', 'e', 'end', 'enter', @@ -384,13 +356,8 @@ class FreshRSS_configure_Controller extends Minz_ActionController { $this->view->conf->save(); invalidateHttpCache(); - $notif = array( - 'type' => 'good', - 'content' => Minz_Translate::t('shortcuts_updated') - ); - Minz_Session::_param('notification', $notif); - - Minz_Request::forward(array('c' => 'configure', 'a' => 'shortcut'), true); + Minz_Request::good(_t('shortcuts_updated'), + array('c' => 'configure', 'a' => 'shortcut')); } Minz_View::prependTitle(Minz_Translate::t('shortcuts') . ' · '); @@ -419,7 +386,6 @@ class FreshRSS_configure_Controller extends Minz_ActionController { * * @todo explain why the default value is -2 but this value does not * exist in the drop-down list - * @todo change the notification code * @todo refactor configuration setting syntax to be consistent with the * other methods */ @@ -435,13 +401,8 @@ class FreshRSS_configure_Controller extends Minz_ActionController { $this->view->conf->save(); invalidateHttpCache(); - $notif = array( - 'type' => 'good', - 'content' => Minz_Translate::t('configuration_updated') - ); - Minz_Session::_param('notification', $notif); - - Minz_Request::forward(array('c' => 'configure', 'a' => 'archiving'), true); + Minz_Request::good(_t('configuration_updated'), + array('c' => 'configure', 'a' => 'archiving')); } Minz_View::prependTitle(Minz_Translate::t('archiving_configuration') . ' · '); -- cgit v1.2.3 From 3753ea8fd673feb16a2bfb323fea2b5ff964d77e Mon Sep 17 00:00:00 2001 From: Alexis Degrugillier Date: Sun, 21 Sep 2014 18:23:25 -0400 Subject: Change parameter settings to be consistent through out the file --- app/Controllers/configureController.php | 20 ++++---------------- 1 file changed, 4 insertions(+), 16 deletions(-) (limited to 'app/Controllers') diff --git a/app/Controllers/configureController.php b/app/Controllers/configureController.php index 1e341b412..0e4c0be52 100755 --- a/app/Controllers/configureController.php +++ b/app/Controllers/configureController.php @@ -198,17 +198,11 @@ class FreshRSS_configure_Controller extends Minz_ActionController { * - display of open action in footer * - html5 notification timeout (default: 0) * Default values are false unless specified. - * - * @todo refactor theme section to use the same syntax everywhere */ public function displayAction() { if (Minz_Request::isPost()) { $this->view->conf->_language(Minz_Request::param('language', 'en')); - $themeId = Minz_Request::param('theme', ''); - if ($themeId == '') { - $themeId = FreshRSS_Themes::defaultTheme; - } - $this->view->conf->_theme($themeId); + $this->view->conf->_theme(Minz_Request::param('theme', FreshRSS_Themes::$defaultTheme)); $this->view->conf->_content_width(Minz_Request::param('content_width', 'thin')); $this->view->conf->_topline_read(Minz_Request::param('topline_read', false)); $this->view->conf->_topline_favorite(Minz_Request::param('topline_favorite', false)); @@ -386,18 +380,12 @@ class FreshRSS_configure_Controller extends Minz_ActionController { * * @todo explain why the default value is -2 but this value does not * exist in the drop-down list - * @todo refactor configuration setting syntax to be consistent with the - * other methods */ public function archivingAction() { if (Minz_Request::isPost()) { - $old = Minz_Request::param('old_entries', 3); - $keepHistoryDefault = Minz_Request::param('keep_history_default', 0); - $ttlDefault = Minz_Request::param('ttl_default', -2); - - $this->view->conf->_old_entries($old); - $this->view->conf->_keep_history_default($keepHistoryDefault); - $this->view->conf->_ttl_default($ttlDefault); + $this->view->conf->_old_entries(Minz_Request::param('old_entries', 3)); + $this->view->conf->_keep_history_default(Minz_Request::param('keep_history_default', 0)); + $this->view->conf->_ttl_default(Minz_Request::param('ttl_default', -2)); $this->view->conf->save(); invalidateHttpCache(); -- cgit v1.2.3 From 04403c5dfae2db051416ccf0b41559d02bb0d287 Mon Sep 17 00:00:00 2001 From: Alexis Degrugillier Date: Sun, 21 Sep 2014 18:26:27 -0400 Subject: Remove number values for shortcuts as they are used in the default configuration --- app/Controllers/configureController.php | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) (limited to 'app/Controllers') diff --git a/app/Controllers/configureController.php b/app/Controllers/configureController.php index 0e4c0be52..a44ef3104 100755 --- a/app/Controllers/configureController.php +++ b/app/Controllers/configureController.php @@ -322,17 +322,13 @@ class FreshRSS_configure_Controller extends Minz_ActionController { * to 9), function keys (f1 to f12), backspace, delete, down, end, enter, * escape, home, insert, left, page down, page up, return, right, space, * tab and up. - * - * @todo remove numbers from the list of authorized shortcuts since they - * are used to access shortcuts and user queries */ public function shortcutAction() { $list_keys = array('a', 'b', 'backspace', 'c', 'd', 'delete', 'down', 'e', 'end', 'enter', 'escape', 'f', 'g', 'h', 'home', 'i', 'insert', 'j', 'k', 'l', 'left', 'm', 'n', 'o', 'p', 'page_down', 'page_up', 'q', 'r', 'return', 'right', 's', 'space', 't', 'tab', 'u', 'up', 'v', 'w', 'x', 'y', - 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', - '9', 'f1', 'f2', 'f3', 'f4', 'f5', 'f6', 'f7', 'f8', 'f9', + 'z', 'f1', 'f2', 'f3', 'f4', 'f5', 'f6', 'f7', 'f8', 'f9', 'f10', 'f11', 'f12'); $this->view->list_keys = $list_keys; -- cgit v1.2.3 From 23e4577e02944567b8ac15581d9c0a0561d82046 Mon Sep 17 00:00:00 2001 From: Alexis Degrugillier Date: Sun, 21 Sep 2014 18:40:08 -0400 Subject: Change param filter for user queries Before, the filter was based on a blacklist so the user could add something and have unwanted behavior. Now, the filter is based on a whilelist so the user can use only predetermined parameters. --- app/Controllers/configureController.php | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) (limited to 'app/Controllers') diff --git a/app/Controllers/configureController.php b/app/Controllers/configureController.php index a44ef3104..2bd13997f 100755 --- a/app/Controllers/configureController.php +++ b/app/Controllers/configureController.php @@ -493,18 +493,17 @@ class FreshRSS_configure_Controller extends Minz_ActionController { * It gets the GET parameters and stores them in the configuration query * storage. Before it is saved, the unwanted parameters are unset to keep * lean data. - * - * @todo change the way of keeping lean data to have a more defensive - * code. At the moment, the code accepts any parameters and discard - * those on the black list. I think it is safer if we maintain a - * whitelist instead. */ public function addQueryAction() { + $whitelist = array('get', 'order', 'name', 'search', 'state'); $queries = $this->view->conf->queries; $query = Minz_Request::params(); $query['name'] = _t('query_number', count($queries) + 1); - unset($query['output']); - unset($query['token']); + foreach ($query as $key => $value) { + if (!in_array($key, $whitelist)) { + unset($query[$key]); + } + } $queries[] = $query; $this->view->conf->_queries($queries); $this->view->conf->save(); -- cgit v1.2.3 From c791d84ded1eeb1b32fe81918ac1bcfa520c6bda Mon Sep 17 00:00:00 2001 From: Alexis Degrugillier Date: Sun, 21 Sep 2014 18:49:34 -0400 Subject: Fix user queries Before, when adding a user query where the state contains the strict filter, the label applied to the query was wrong. Now, the strict filter is always removed so the label applied is correct. --- app/Controllers/configureController.php | 3 +++ 1 file changed, 3 insertions(+) (limited to 'app/Controllers') diff --git a/app/Controllers/configureController.php b/app/Controllers/configureController.php index 2bd13997f..426cad34d 100755 --- a/app/Controllers/configureController.php +++ b/app/Controllers/configureController.php @@ -504,6 +504,9 @@ class FreshRSS_configure_Controller extends Minz_ActionController { unset($query[$key]); } } + if ($query['state'] & FreshRSS_Entry::STATE_STRICT) { + $query['state'] -= FreshRSS_Entry::STATE_STRICT; + } $queries[] = $query; $this->view->conf->_queries($queries); $this->view->conf->save(); -- cgit v1.2.3 From 0ea3a5a3943b30eb6a5d8763b698a939fe1a98ae Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Wed, 24 Sep 2014 11:49:32 +0200 Subject: Coding style configureController.php --- app/Controllers/configureController.php | 40 ++++++++++++++++----------------- 1 file changed, 20 insertions(+), 20 deletions(-) (limited to 'app/Controllers') diff --git a/app/Controllers/configureController.php b/app/Controllers/configureController.php index 426cad34d..4422f33af 100755 --- a/app/Controllers/configureController.php +++ b/app/Controllers/configureController.php @@ -16,7 +16,7 @@ class FreshRSS_configure_Controller extends Minz_ActionController { if (!$this->view->loginOk) { Minz_Error::error( 403, - array('error' => array(Minz_Translate::t('access_denied'))) + array('error' => array(_t('access_denied'))) ); } @@ -73,14 +73,14 @@ class FreshRSS_configure_Controller extends Minz_ActionController { invalidateHttpCache(); Minz_Request::good(_t('categories_updated'), - array('c' => 'configure', 'a' => 'categorize')); + array('c' => 'configure', 'a' => 'categorize')); } $this->view->categories = $catDAO->listCategories(false); $this->view->defaultCategory = $catDAO->getDefault(); $this->view->feeds = $feedDAO->listFeeds(); - Minz_View::prependTitle(Minz_Translate::t('categories_management') . ' · '); + Minz_View::prependTitle(_t('categories_management') . ' · '); } /** @@ -122,7 +122,7 @@ class FreshRSS_configure_Controller extends Minz_ActionController { if (!$this->view->flux) { Minz_Error::error( 404, - array('error' => array(Minz_Translate::t('page_not_found'))) + array('error' => array(_t('page_not_found'))) ); } else { if (Minz_Request::isPost() && $this->view->flux) { @@ -154,12 +154,12 @@ class FreshRSS_configure_Controller extends Minz_ActionController { $this->view->flux->faviconPrepare(); $notif = array( 'type' => 'good', - 'content' => Minz_Translate::t('feed_updated') + 'content' => _t('feed_updated') ); } else { $notif = array( 'type' => 'bad', - 'content' => Minz_Translate::t('error_occurred_update') + 'content' => _t('error_occurred_update') ); } invalidateHttpCache(); @@ -168,10 +168,10 @@ class FreshRSS_configure_Controller extends Minz_ActionController { Minz_Request::forward(array('c' => 'configure', 'a' => 'feed', 'params' => array('id' => $id)), true); } - Minz_View::prependTitle(Minz_Translate::t('rss_feed_management') . ' — ' . $this->view->flux->name() . ' · '); + Minz_View::prependTitle(_t('rss_feed_management') . ' — ' . $this->view->flux->name() . ' · '); } } else { - Minz_View::prependTitle(Minz_Translate::t('rss_feed_management') . ' · '); + Minz_View::prependTitle(_t('rss_feed_management') . ' · '); } } @@ -222,12 +222,12 @@ class FreshRSS_configure_Controller extends Minz_ActionController { invalidateHttpCache(); Minz_Request::good(_t('configuration_updated'), - array('c' => 'configure', 'a' => 'display')); + array('c' => 'configure', 'a' => 'display')); } $this->view->themes = FreshRSS_Themes::get(); - Minz_View::prependTitle(Minz_Translate::t('display_configuration') . ' · '); + Minz_View::prependTitle(_t('display_configuration') . ' · '); } /** @@ -284,10 +284,10 @@ class FreshRSS_configure_Controller extends Minz_ActionController { invalidateHttpCache(); Minz_Request::good(_t('configuration_updated'), - array('c' => 'configure', 'a' => 'reading')); + array('c' => 'configure', 'a' => 'reading')); } - Minz_View::prependTitle(Minz_Translate::t('reading_configuration') . ' · '); + Minz_View::prependTitle(_t('reading_configuration') . ' · '); } /** @@ -305,10 +305,10 @@ class FreshRSS_configure_Controller extends Minz_ActionController { invalidateHttpCache(); Minz_Request::good(_t('configuration_updated'), - array('c' => 'configure', 'a' => 'sharing')); + array('c' => 'configure', 'a' => 'sharing')); } - Minz_View::prependTitle(Minz_Translate::t('sharing') . ' · '); + Minz_View::prependTitle(_t('sharing') . ' · '); } /** @@ -347,10 +347,10 @@ class FreshRSS_configure_Controller extends Minz_ActionController { invalidateHttpCache(); Minz_Request::good(_t('shortcuts_updated'), - array('c' => 'configure', 'a' => 'shortcut')); + array('c' => 'configure', 'a' => 'shortcut')); } - Minz_View::prependTitle(Minz_Translate::t('shortcuts') . ' · '); + Minz_View::prependTitle(_t('shortcuts') . ' · '); } /** @@ -359,7 +359,7 @@ class FreshRSS_configure_Controller extends Minz_ActionController { * @todo move that action in the user controller */ public function usersAction() { - Minz_View::prependTitle(Minz_Translate::t('users') . ' · '); + Minz_View::prependTitle(_t('users') . ' · '); } /** @@ -386,10 +386,10 @@ class FreshRSS_configure_Controller extends Minz_ActionController { invalidateHttpCache(); Minz_Request::good(_t('configuration_updated'), - array('c' => 'configure', 'a' => 'archiving')); + array('c' => 'configure', 'a' => 'archiving')); } - Minz_View::prependTitle(Minz_Translate::t('archiving_configuration') . ' · '); + Minz_View::prependTitle(_t('archiving_configuration') . ' · '); $entryDAO = FreshRSS_Factory::createEntryDao(); $this->view->nb_total = $entryDAO->count(); @@ -484,7 +484,7 @@ class FreshRSS_configure_Controller extends Minz_ActionController { } } - Minz_View::prependTitle(Minz_Translate::t('queries') . ' · '); + Minz_View::prependTitle(_t('queries') . ' · '); } /** -- cgit v1.2.3 From a80e12cbf7a38d5a745b8ce980e9c662f9c64dfb Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Wed, 24 Sep 2014 12:32:49 +0200 Subject: Remove queries at the same time as categories See https://github.com/marienfressinaud/FreshRSS/issues/625 --- app/Controllers/configureController.php | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) (limited to 'app/Controllers') diff --git a/app/Controllers/configureController.php b/app/Controllers/configureController.php index 4422f33af..d2ca0777f 100755 --- a/app/Controllers/configureController.php +++ b/app/Controllers/configureController.php @@ -32,7 +32,8 @@ class FreshRSS_configure_Controller extends Minz_ActionController { * every category to check for modification then add a new category if * needed then sends a notification to the user. * If a category name is emptied, the category is deleted and all - * related feeds are moved to the default category. + * related feeds are moved to the default category. Related user queries + * are deleted too. * If a category name is changed, it is updated. */ public function categorizeAction() { @@ -56,6 +57,18 @@ class FreshRSS_configure_Controller extends Minz_ActionController { } elseif ($ids[$key] != $defaultId) { $feedDAO->changeCategory($ids[$key], $defaultId); $catDAO->deleteCategory($ids[$key]); + + // Remove related queries. + $final_queries = array(); + $id_cat_in_query = 'c_' . $ids[$key]; + foreach ($this->view->conf->queries as $key => $query) { + if (empty($query['get']) || + $query['get'] !== $id_cat_in_query) { + $final_queries[$key] = $query; + } + } + $this->view->conf->_queries($final_queries); + $this->view->conf->save(); } } -- cgit v1.2.3 From 666162ae56c2df8ae6a7ed6602b69f6caad10c7c Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Wed, 24 Sep 2014 12:34:44 +0200 Subject: Fix a bug when $query['state'] doesn't exist Add a test in addQueryAction(). --- app/Controllers/configureController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'app/Controllers') diff --git a/app/Controllers/configureController.php b/app/Controllers/configureController.php index d2ca0777f..e6e9172e6 100755 --- a/app/Controllers/configureController.php +++ b/app/Controllers/configureController.php @@ -517,7 +517,7 @@ class FreshRSS_configure_Controller extends Minz_ActionController { unset($query[$key]); } } - if ($query['state'] & FreshRSS_Entry::STATE_STRICT) { + if (!empty($query['state']) && $query['state'] & FreshRSS_Entry::STATE_STRICT) { $query['state'] -= FreshRSS_Entry::STATE_STRICT; } $queries[] = $query; -- cgit v1.2.3 From 5a1baff9be7cf9fe3f59fe2a7dc34fbadacc1a99 Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Wed, 24 Sep 2014 13:28:09 +0200 Subject: Refactor removing query by get param See https://github.com/marienfressinaud/FreshRSS/issues/625 --- app/Controllers/configureController.php | 10 +--------- app/Models/Configuration.php | 10 ++++++++++ 2 files changed, 11 insertions(+), 9 deletions(-) (limited to 'app/Controllers') diff --git a/app/Controllers/configureController.php b/app/Controllers/configureController.php index e6e9172e6..231865bd7 100755 --- a/app/Controllers/configureController.php +++ b/app/Controllers/configureController.php @@ -59,15 +59,7 @@ class FreshRSS_configure_Controller extends Minz_ActionController { $catDAO->deleteCategory($ids[$key]); // Remove related queries. - $final_queries = array(); - $id_cat_in_query = 'c_' . $ids[$key]; - foreach ($this->view->conf->queries as $key => $query) { - if (empty($query['get']) || - $query['get'] !== $id_cat_in_query) { - $final_queries[$key] = $query; - } - } - $this->view->conf->_queries($final_queries); + $this->view->conf->remove_query_by_get('c_' . $ids[$key]); $this->view->conf->save(); } } diff --git a/app/Models/Configuration.php b/app/Models/Configuration.php index ea1556cbb..91d2ab846 100644 --- a/app/Models/Configuration.php +++ b/app/Models/Configuration.php @@ -124,6 +124,16 @@ class FreshRSS_Configuration { return $this->available_languages; } + public function remove_query_by_get($get) { + $final_queries = array(); + foreach ($this->queries as $key => $query) { + if (empty($query['get']) || $query['get'] !== $get) { + $final_queries[$key] = $query; + } + } + $this->_queries($final_queries); + } + public function _language($value) { if (!isset($this->available_languages[$value])) { $value = 'en'; -- cgit v1.2.3 From 17f686095ddebe1a7c94fe665eddfdfef9696015 Mon Sep 17 00:00:00 2001 From: Marien Fressinaud Date: Wed, 24 Sep 2014 13:38:07 +0200 Subject: Remove queries at the same time as feeds See https://github.com/marienfressinaud/FreshRSS/issues/625 --- app/Controllers/feedController.php | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'app/Controllers') diff --git a/app/Controllers/feedController.php b/app/Controllers/feedController.php index 65d4b3a37..c7cc25fbb 100755 --- a/app/Controllers/feedController.php +++ b/app/Controllers/feedController.php @@ -383,7 +383,16 @@ class FreshRSS_feed_Controller extends Minz_ActionController { $feedDAO = FreshRSS_Factory::createFeedDao(); if ($type == 'category') { + // List feeds to remove then related user queries. + $feeds = $feedDAO->listByCategory($id); + if ($feedDAO->deleteFeedByCategory ($id)) { + // Remove related queries + foreach ($feeds as $feed) { + $this->view->conf->remove_query_by_get('f_' . $feed->id()); + } + $this->view->conf->save(); + $notif = array ( 'type' => 'good', 'content' => Minz_Translate::t ('category_emptied') @@ -397,6 +406,10 @@ class FreshRSS_feed_Controller extends Minz_ActionController { } } else { if ($feedDAO->deleteFeed ($id)) { + // Remove related queries + $this->view->conf->remove_query_by_get('f_' . $id); + $this->view->conf->save(); + $notif = array ( 'type' => 'good', 'content' => Minz_Translate::t ('feed_deleted') -- cgit v1.2.3