diff --git a/CHANGELOG b/CHANGELOG index df43fe1d1..d7de86d9b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,28 @@ # Journal des modifications -## 2014-08-xx FreshRSS 0.7.4 +## 2014-09-xx FreshRSS 0.8.0 + +* UI + * New interface for statistics + * Fix filter buttons + * Number of articles divided by 2 in reading view + * Redesign of bigMarkAsRead +* Features + * New automatic update system + * New reset auth system +* Security + * "Mark as read" requires POST requests for several articles + * Test HTTP REFERER in install.php +* Configuration + * New "Show all articles" / "Show only unread" / "Adjust viewing" option + * New notification timeout option +* Misc. + * Improve coding style + comments + * Fix SQLite bug "ON DELETE CASCADE" + * Improve performance when importing articles + + +## 2014-08-24 FreshRSS 0.7.4 * UI * Hide categories/feeds with unread articles when showing only unread articles diff --git a/app/Controllers/configureController.php b/app/Controllers/configureController.php index bb96bfae3..231865bd7 100755 --- a/app/Controllers/configureController.php +++ b/app/Controllers/configureController.php @@ -1,11 +1,22 @@ view->loginOk) { Minz_Error::error( 403, - array('error' => array(Minz_Translate::t('access_denied'))) + array('error' => array(_t('access_denied'))) ); } @@ -13,6 +24,18 @@ 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. Related user queries + * are deleted too. + * If a category name is changed, it is updated. + */ public function categorizeAction() { $feedDAO = FreshRSS_Factory::createFeedDao(); $catDAO = new FreshRSS_CategoryDAO(); @@ -34,6 +57,10 @@ class FreshRSS_configure_Controller extends Minz_ActionController { } elseif ($ids[$key] != $defaultId) { $feedDAO->changeCategory($ids[$key], $defaultId); $catDAO->deleteCategory($ids[$key]); + + // Remove related queries. + $this->view->conf->remove_query_by_get('c_' . $ids[$key]); + $this->view->conf->save(); } } @@ -50,22 +77,37 @@ 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); $this->view->defaultCategory = $catDAO->getDefault(); $this->view->feeds = $feedDAO->listFeeds(); - Minz_View::prependTitle(Minz_Translate::t('categories_management') . ' · '); + Minz_View::prependTitle(_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. + */ public function feedAction() { $catDAO = new FreshRSS_CategoryDAO(); $this->view->categories = $catDAO->listCategories(false); @@ -85,7 +127,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) { @@ -117,12 +159,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(); @@ -131,21 +173,41 @@ 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') . ' · '); } } + /** + * 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. + */ 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)); @@ -157,26 +219,49 @@ 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); 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(); - Minz_View::prependTitle(Minz_Translate::t('display_configuration') . ' · '); + Minz_View::prependTitle(_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. + */ public function readingAction() { if (Minz_Request::isPost()) { $this->view->conf->_posts_per_page(Minz_Request::param('posts_per_page', 10)); @@ -203,18 +288,20 @@ 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') . ' · '); + Minz_View::prependTitle(_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. + */ public function sharingAction() { if (Minz_Request::isPost()) { $params = Minz_Request::params(); @@ -222,25 +309,31 @@ 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') . ' · '); + Minz_View::prependTitle(_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. + */ 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; @@ -258,44 +351,50 @@ 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') . ' · '); + Minz_View::prependTitle(_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') . ' · '); + Minz_View::prependTitle(_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 + */ 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(); - $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') . ' · '); + Minz_View::prependTitle(_t('archiving_configuration') . ' · '); $entryDAO = FreshRSS_Factory::createEntryDao(); $this->view->nb_total = $entryDAO->count(); @@ -305,28 +404,35 @@ 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()); 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(); + $feed_dao = FreshRSS_Factory::createFeedDao(); foreach ($this->view->conf->queries as $key => $query) { if (!isset($query['get'])) { continue; @@ -334,51 +440,83 @@ 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; } } } - Minz_View::prependTitle(Minz_Translate::t('queries') . ' · '); + Minz_View::prependTitle(_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. + */ public function addQueryAction() { + $whitelist = array('get', 'order', 'name', 'search', 'state'); $queries = $this->view->conf->queries; $query = Minz_Request::params(); - $query['name'] = Minz_Translate::t('query_number', count($queries) + 1); - unset($query['output']); - unset($query['token']); + $query['name'] = _t('query_number', count($queries) + 1); + foreach ($query as $key => $value) { + if (!in_array($key, $whitelist)) { + unset($query[$key]); + } + } + if (!empty($query['state']) && $query['state'] & FreshRSS_Entry::STATE_STRICT) { + $query['state'] -= FreshRSS_Entry::STATE_STRICT; + } $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/Controllers/entryController.php b/app/Controllers/entryController.php index ac43587ea..ab66d9198 100755 --- a/app/Controllers/entryController.php +++ b/app/Controllers/entryController.php @@ -45,6 +45,10 @@ class FreshRSS_entry_Controller extends Minz_ActionController { $entryDAO = FreshRSS_Factory::createEntryDao(); if ($id == false) { + if (!Minz_Request::isPost()) { + return; + } + if (!$get) { $entryDAO->markReadEntries ($idMax); } else { diff --git a/app/Controllers/feedController.php b/app/Controllers/feedController.php index 3326b2059..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') @@ -412,10 +425,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/Controllers/importExportController.php b/app/Controllers/importExportController.php index 5adf3878a..f329766b8 100644 --- a/app/Controllers/importExportController.php +++ b/app/Controllers/importExportController.php @@ -109,7 +109,6 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { // 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'; @@ -119,8 +118,7 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { } elseif (substr_compare($filename, '.json', -5) === 0 && strpos($filename, 'starred') !== false) { return 'json_starred'; - } elseif (substr_compare($filename, '.json', -5) === 0 && - strpos($filename, 'feed_') === 0) { + } elseif (substr_compare($filename, '.json', -5) === 0) { return 'json_feed'; } else { return 'unknown'; @@ -239,13 +237,27 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { ); $error = false; + $article_to_feed = array(); + + // First, we check feeds of articles are in DB (and add them if needed). foreach ($article_object['items'] as $item) { $feed = $this->addFeedArticles($item['origin'], $google_compliant); if (is_null($feed)) { $error = true; + } else { + $article_to_feed[$item['id']] = $feed->id(); + } + } + + // Then, articles are imported. + $prepared_statement = $this->entryDAO->addEntryPrepare(); + $this->entryDAO->beginTransaction(); + foreach ($article_object['items'] as $item) { + if (!isset($article_to_feed[$item['id']])) { continue; } + $feed_id = $article_to_feed[$item['id']]; $author = isset($item['author']) ? $item['author'] : ''; $key_content = ($google_compliant && !isset($item['content'])) ? 'summary' : 'content'; @@ -257,21 +269,21 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { } $entry = new FreshRSS_Entry( - $feed->id(), $item['id'], $item['title'], $author, + $feed_id, $item['id'], $item['title'], $author, $item[$key_content]['content'], $item['alternate'][0]['href'], $item['published'], $is_read, $starred ); + $entry->_id(min(time(), $entry->date(true)) . uSecString()); $entry->_tags($tags); - //FIME: Use entryDAO->addEntryPrepare(). Do not call entryDAO->listLastGuidsByFeed() for each entry. Consider using a transaction. - $id = $this->entryDAO->addEntryObject( - $entry, $this->view->conf, $feed->keepHistory() - ); + $values = $entry->toArray(); + $id = $this->entryDAO->addEntry($values, $prepared_statement); if (!$error && ($id === false)) { $error = true; } } + $this->entryDAO->commit(); return $error; } diff --git a/app/Controllers/indexController.php b/app/Controllers/indexController.php index b0b051119..e8e26b142 100755 --- a/app/Controllers/indexController.php +++ b/app/Controllers/indexController.php @@ -83,6 +83,11 @@ class FreshRSS_index_Controller extends Minz_ActionController { $nb = Minz_Request::param ('nb', $this->view->conf->posts_per_page); $first = Minz_Request::param ('next', ''); + $ajax_request = Minz_Request::param('ajax', false); + if ($output === 'reader') { + $nb = max(1, round($nb / 2)); + } + if ($this->view->state === FreshRSS_Entry::STATE_NOT_READ) { //Any unread article in this category at all? switch ($getType) { case 'a': @@ -332,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'); @@ -415,4 +424,75 @@ 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('auth_reset') . ' · '); + 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! + 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; + } + + invalidateHttpCache(); + + 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_Request::bad(_t('invalid_login'), + array('c' => 'index', 'a' => 'resetAuth')); + } + + if (!function_exists('password_verify')) { + include_once(LIB_PATH . '/password_compat.php'); + } + + $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_Request::bad(_t('auth_form_not_set'), + array('c' => 'index', 'a' => 'resetAuth')); + } + } else { + Minz_Log::debug('Password mismatch for user ' . $username . + ', nonce=' . $nonce . ', c=' . $c); + + Minz_Request::bad(_t('invalid_login'), + array('c' => 'index', 'a' => 'resetAuth')); + } + } + } } diff --git a/app/Controllers/statsController.php b/app/Controllers/statsController.php index 98f46f0d2..256543f37 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(); @@ -67,10 +104,18 @@ class FreshRSS_stats_Controller extends Minz_ActionController { $this->view->days = $statsDAO->getDays(); $this->view->months = $statsDAO->getMonths(); $this->view->repartitionHour = $statsDAO->calculateEntryRepartitionPerFeedPerHour($id); + $this->view->averageHour = $statsDAO->calculateEntryAveragePerFeedPerHour($id); $this->view->repartitionDayOfWeek = $statsDAO->calculateEntryRepartitionPerFeedPerDayOfWeek($id); + $this->view->averageDayOfWeek = $statsDAO->calculateEntryAveragePerFeedPerDayOfWeek($id); $this->view->repartitionMonth = $statsDAO->calculateEntryRepartitionPerFeedPerMonth($id); + $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( diff --git a/app/Controllers/updateController.php b/app/Controllers/updateController.php new file mode 100644 index 000000000..da5bddc65 --- /dev/null +++ b/app/Controllers/updateController.php @@ -0,0 +1,129 @@ +view->loginOk && Minz_Configuration::isAdmin($current_user)) { + Minz_Error::error( + 403, + array('error' => array(_t('access_denied'))) + ); + } + + invalidateHttpCache(); + + Minz_View::prependTitle(_t('update_system') . ' · '); + $this->view->update_to_apply = false; + $this->view->last_update_time = 'unknown'; + $this->view->check_last_hour = false; + $timestamp = (int)@file_get_contents(DATA_PATH . '/last_update.txt'); + if (is_numeric($timestamp) && $timestamp > 0) { + $this->view->last_update_time = timestamptodate($timestamp); + $this->view->check_last_hour = (time() - 3600) <= $timestamp; + } + } + + public function indexAction() { + if (file_exists(UPDATE_FILENAME) && !is_writable(FRESHRSS_PATH)) { + $this->view->message = array( + 'status' => 'bad', + 'title' => _t('damn'), + 'body' => _t('file_is_nok', FRESHRSS_PATH) + ); + } elseif (file_exists(UPDATE_FILENAME)) { + // There is an update file to apply! + $this->view->update_to_apply = true; + $this->view->message = array( + 'status' => 'good', + 'title' => _t('ok'), + 'body' => _t('update_can_apply') + ); + } + } + + public function checkAction() { + $this->view->change_view('update', 'index'); + + if (file_exists(UPDATE_FILENAME) || $this->view->check_last_hour) { + // There is already an update file to apply: we don't need to check + // the webserver! + // Or if already check during the last hour, do nothing. + Minz_Request::forward(array('c' => 'update')); + + return; + } + + $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); + $c_error = curl_error($c); + curl_close($c); + + if ($c_status !== 200) { + Minz_Log::error( + 'Error during update (HTTP code ' . $c_status . '): ' . $c_error + ); + + $this->view->message = array( + 'status' => 'bad', + 'title' => _t('damn'), + 'body' => _t('update_server_not_found', FRESHRSS_UPDATE_WEBSITE) + ); + return; + } + + $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') + ); + + @file_put_contents(DATA_PATH . '/last_update.txt', time()); + + return; + } + + $script = $res_array[1]; + if (file_put_contents(UPDATE_FILENAME, $script) !== false) { + Minz_Request::forward(array('c' => 'update')); + } else { + $this->view->message = array( + 'status' => 'bad', + 'title' => _t('damn'), + 'body' => _t('update_problem', 'Cannot save the update script') + ); + } + } + + public function applyAction() { + if (!file_exists(UPDATE_FILENAME) || !is_writable(FRESHRSS_PATH)) { + Minz_Request::forward(array('c' => 'update'), true); + } + + require(UPDATE_FILENAME); + + if (Minz_Request::isPost()) { + save_info_update(); + } + + if (!need_info_update()) { + $res = apply_update(); + + if ($res === true) { + @unlink(UPDATE_FILENAME); + @file_put_contents(DATA_PATH . '/last_update.txt', time()); + + Minz_Request::good(_t('update_finished')); + } else { + Minz_Request::bad(_t('update_problem', $res), + array('c' => 'update', 'a' => 'index')); + } + } + } +} diff --git a/app/FreshRSS.php b/app/FreshRSS.php index 30f711e20..cdf8962cb 100644 --- a/app/FreshRSS.php +++ b/app/FreshRSS.php @@ -6,8 +6,7 @@ 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))) { + if (Minz_Request::isPost() && !is_referer_from_same_domain()) { $loginOk = false; //Basic protection against XSRF attacks Minz_Error::error( 403, @@ -140,11 +139,21 @@ class FreshRSS extends Minz_FrontController { } } - private function loadStylesAndScripts ($loginOk) { + private function loadStylesAndScripts($loginOk) { $theme = FreshRSS_Themes::load($this->conf->theme); if ($theme) { foreach($theme['files'] as $file) { - Minz_View::appendStyle (Minz_Url::display ('/themes/' . $theme['id'] . '/' . $file . '?' . @filemtime(PUBLIC_PATH . '/themes/' . $theme['id'] . '/' . $file))); + if ($file[0] === '_') { + $theme_id = 'base-theme'; + $filename = substr($file, 1); + } else { + $theme_id = $theme['id']; + $filename = $file; + } + $filetime = @filemtime(PUBLIC_PATH . '/themes/' . $theme_id . '/' . $filename); + Minz_View::appendStyle(Minz_Url::display( + '/themes/' . $theme_id . '/' . $filename . '?' . $filetime + )); } } diff --git a/app/Models/Configuration.php b/app/Models/Configuration.php index 4c804a9fb..95f819779 100644 --- a/app/Models/Configuration.php +++ b/app/Models/Configuration.php @@ -45,6 +45,8 @@ class FreshRSS_Configuration { 'load_more' => 'm', 'auto_share' => 's', 'focus_search' => 'a', + 'user_filter' => 'u', + 'help' => 'f1', ), 'topline_read' => true, 'topline_favorite' => true, @@ -58,6 +60,7 @@ class FreshRSS_Configuration { 'bottomline_link' => true, 'sharing' => array(), 'queries' => array(), + 'html5_notif_timeout' => 0, ); private $available_languages = array( @@ -120,6 +123,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'; @@ -138,7 +151,18 @@ class FreshRSS_Configuration { } } public function _default_view ($value) { - $this->data['default_view'] = $value === FreshRSS_Entry::STATE_ALL ? FreshRSS_Entry::STATE_ALL : FreshRSS_Entry::STATE_NOT_READ; + switch ($value) { + case FreshRSS_Entry::STATE_ALL: + // left blank on purpose + case FreshRSS_Entry::STATE_NOT_READ: + // left blank on purpose + case FreshRSS_Entry::STATE_STRICT + FreshRSS_Entry::STATE_NOT_READ: + $this->data['default_view'] = $value; + break; + default: + $this->data['default_view'] = FreshRSS_Entry::STATE_ALL; + break; + } } public function _display_posts ($value) { $this->data['display_posts'] = ((bool)$value) && $value !== 'no'; @@ -209,6 +233,7 @@ class FreshRSS_Configuration { } public function _sharing ($values) { $this->data['sharing'] = array(); + $unique = array(); foreach ($values as $value) { if (!is_array($value)) { continue; @@ -234,7 +259,11 @@ class FreshRSS_Configuration { $value['name'] = $value['type']; } - $this->data['sharing'][] = $value; + $json_value = json_encode($value); + if (!in_array($json_value, $unique)) { + $unique[] = $json_value; + $this->data['sharing'][] = $value; + } } } public function _queries ($values) { @@ -261,6 +290,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/Models/Entry.php b/app/Models/Entry.php index 0bf1f2616..9d7dd5dc4 100644 --- a/app/Models/Entry.php +++ b/app/Models/Entry.php @@ -6,6 +6,7 @@ class FreshRSS_Entry extends Minz_Model { const STATE_NOT_READ = 2; const STATE_FAVORITE = 4; const STATE_NOT_FAVORITE = 8; + const STATE_STRICT = 16; private $id = 0; private $guid; diff --git a/app/Models/EntryDAO.php b/app/Models/EntryDAO.php index 75a8aeba4..c1f87ee34 100644 --- a/app/Models/EntryDAO.php +++ b/app/Models/EntryDAO.php @@ -333,6 +333,8 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { if ($state & FreshRSS_Entry::STATE_NOT_READ) { if (!($state & FreshRSS_Entry::STATE_READ)) { $where .= 'AND e1.is_read=0 '; + } elseif ($state & FreshRSS_Entry::STATE_STRICT) { + $where .= 'AND e1.is_read=0 '; } } elseif ($state & FreshRSS_Entry::STATE_READ) { diff --git a/app/Models/FeedDAO.php b/app/Models/FeedDAO.php index 756b1f008..b89ae2045 100644 --- a/app/Models/FeedDAO.php +++ b/app/Models/FeedDAO.php @@ -331,7 +331,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo { $id_max = intval($date_min) . '000000'; $stm->bindParam(':id_feed', $id, PDO::PARAM_INT); - $stm->bindParam(':id_max', $id_max, PDO::PARAM_INT); + $stm->bindParam(':id_max', $id_max, PDO::PARAM_STR); $stm->bindParam(':keep', $keep, PDO::PARAM_INT); if ($stm && $stm->execute()) { diff --git a/app/Models/StatsDAO.php b/app/Models/StatsDAO.php index 89be76a26..40505ab3e 100644 --- a/app/Models/StatsDAO.php +++ b/app/Models/StatsDAO.php @@ -151,6 +151,74 @@ SQL; return $this->convertToSerie($repartition); } + /** + * Calculates the average number of article per hour per feed + * + * @param integer $feed id + * @return integer + */ + public function calculateEntryAveragePerFeedPerHour($feed = null) { + return $this->calculateEntryAveragePerFeedPerPeriod(1/24, $feed); + } + + /** + * Calculates the average number of article per day of week per feed + * + * @param integer $feed id + * @return integer + */ + public function calculateEntryAveragePerFeedPerDayOfWeek($feed = null) { + return $this->calculateEntryAveragePerFeedPerPeriod(7, $feed); + } + + /** + * Calculates the average number of article per month per feed + * + * @param integer $feed id + * @return integer + */ + public function calculateEntryAveragePerFeedPerMonth($feed = null) { + return $this->calculateEntryAveragePerFeedPerPeriod(30, $feed); + } + + /** + * Calculates the average number of article per feed + * + * @param float $period number used to divide the number of day in the period + * @param integer $feed id + * @return integer + */ + protected function calculateEntryAveragePerFeedPerPeriod($period, $feed = null) { + if ($feed) { + $restrict = "WHERE e.id_feed = {$feed}"; + } else { + $restrict = ''; + } + $sql = <<prefix}entry AS e +{$restrict} +SQL; + $stm = $this->bd->prepare($sql); + $stm->execute(); + $res = $stm->fetch(PDO::FETCH_NAMED); + $date_min = new \DateTime(); + $date_min->setTimestamp($res['date_min']); + $date_max = new \DateTime(); + $date_max->setTimestamp($res['date_max']); + $interval = $date_max->diff($date_min, true); + $interval_in_days = $interval->format('%a'); + if ($interval_in_days <= 0) { + // Surely only one article. + // We will return count / (period/period) == count. + $interval_in_days = $period; + } + + return round($res['count'] / ($interval_in_days / $period), 2); + } + /** * Initialize an array for statistics depending on a range * @@ -247,6 +315,7 @@ SQL; SELECT MAX(f.id) as id , MAX(f.name) AS name , MAX(date) AS last_date +, COUNT(*) AS nb_articles FROM {$this->prefix}feed AS f, {$this->prefix}entry AS e WHERE f.id = e.id_feed diff --git a/app/Models/StatsDAOSQLite.php b/app/Models/StatsDAOSQLite.php index 6cb54ddf6..3b1256de1 100644 --- a/app/Models/StatsDAOSQLite.php +++ b/app/Models/StatsDAOSQLite.php @@ -53,6 +53,7 @@ SQL; $stm->execute(); $res = $stm->fetchAll(PDO::FETCH_NAMED); + $repartition = array(); foreach ($res as $value) { $repartition[(int) $value['period']] = (int) $value['count']; } diff --git a/app/Models/Themes.php b/app/Models/Themes.php index 538eb6554..68fc17a2b 100644 --- a/app/Models/Themes.php +++ b/app/Models/Themes.php @@ -96,6 +96,7 @@ class FreshRSS_Themes extends Minz_Model { 'search' => '🔍', 'share' => '♺', 'starred' => '★', + 'stats' => '%', 'tag' => '⚐', 'up' => '△', 'view-normal' => '☰', diff --git a/app/i18n/en.php b/app/i18n/en.php index be0cdc642..beba02c4d 100644 --- a/app/i18n/en.php +++ b/app/i18n/en.php @@ -5,6 +5,7 @@ return array ( 'login' => 'Login', 'keep_logged_in' => 'Keep me logged in (1 month)', 'login_with_persona' => 'Login with Persona', + 'login_persona_problem' => 'Connection problem with Persona?', 'logout' => 'Logout', 'search' => 'Search words or #tags', 'search_short' => 'Search', @@ -42,9 +43,11 @@ 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', + 'query_deprecated' => 'This query is no longer valid. The referenced category or feed has been deleted.', 'about' => 'About', 'stats' => 'Statistics', 'stats_idle' => 'Idle feeds', @@ -92,6 +95,7 @@ return array ( 'rss_view' => 'RSS feed', 'show_all_articles' => 'Show all articles', 'show_not_reads' => 'Show only unread', + 'show_adaptive' => 'Adjust showing', 'show_read' => 'Show only read', 'show_favorite' => 'Show only favorites', 'show_not_favorite' => 'Show all but favorites', @@ -111,7 +115,7 @@ return array ( 'access_denied' => 'You don’t have permission to access this page', 'page_not_found' => 'You are looking for a page which doesn’t exist', 'error_occurred' => 'An error occurred', - 'error_occurred_update' => 'Nothing was changed', + 'error_occurred_update' => 'Nothing was changed', 'default_category' => 'Uncategorized', 'categories_updated' => 'Categories have been updated', @@ -152,10 +156,13 @@ return array ( 'public' => 'Public', 'invalid_login' => 'Login is invalid', + 'file_is_nok' => 'Check permissions on %s directory. HTTP server must have rights to write into.', + // VIEWS '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.', @@ -180,6 +187,9 @@ return array ( 'auto_share' => 'Share', 'auto_share_help' => 'If there is only one sharing mode, it is used. Else modes are accessible by their number.', 'focus_search' => 'Access search box', + 'user_filter' => 'Access user filters', + 'user_filter_help' => 'If there is only one user filter, it is used. Else filters are accessible by their number.', + 'help' => 'Display documentation', 'file_to_import' => 'File to import
(OPML, Json or Zip)', 'file_to_import_no_zip' => 'File to import
(OPML or Json)', @@ -197,13 +207,15 @@ 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', 'website_url' => 'Website URL', 'feed_url' => 'Feed URL', 'articles' => 'articles', - 'number_articles' => 'Number of articles', + 'number_articles' => '%d articles', 'by_feed' => 'by feed', 'by_default' => 'By default', 'keep_history' => 'Minimum number of articles to keep', @@ -225,7 +237,7 @@ return array ( 'not_yet_implemented' => 'Not yet implemented', 'access_protected_feeds' => 'Connection allows to access HTTP protected RSS feeds', 'no_selected_feed' => 'No feed selected.', - 'think_to_add' => 'You may add some feeds.', + 'think_to_add' => 'You may add some feeds.', 'current_user' => 'Current user', 'default_user' => 'Username of the default user (maximum 16 alphanumeric characters)', @@ -237,7 +249,7 @@ return array ( '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', + 'explain_token' => 'Allows to access RSS output of the default user without authentication.
%s?output=rss&token=%s', 'login_configuration' => 'Login', 'is_admin' => 'is administrator', 'auth_type' => 'Authentication method', @@ -248,6 +260,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', @@ -256,31 +269,35 @@ return array ( 'language' => 'Language', 'month' => 'months', 'archiving_configuration' => 'Archiving', - 'delete_articles_every' => 'Remove articles after', + 'delete_articles_every' => 'Remove articles after', 'purge_now' => 'Purge now', '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', + 'number_divided_when_reader' => 'Divided by 2 in the reading view.', 'default_view' => 'Default view', + 'articles_to_display' => 'Articles to display', 'sort_order' => 'Sort order', 'auto_load_more' => 'Load next articles at the page bottom', 'display_articles_unfolded' => 'Show articles unfolded by default', 'display_categories_unfolded' => 'Show categories folded by default', - 'hide_read_feeds' => 'Hide categories & feeds with no unread article (only in “unread articles” display mode)', + 'hide_read_feeds' => 'Hide categories & feeds with no unread article (does not work with “Show all articles” configuration)', 'after_onread' => 'After “mark all as read”,', 'jump_next' => 'jump to next unread sibling (feed or category)', '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', 'auto_read_when' => 'Mark article as read…', 'article_viewed' => 'when article is viewed', 'article_open_on_website' => 'when article is opened on its original website', - 'scroll' => 'during page scrolls', + 'scroll' => 'while scrolling', 'upon_reception' => 'upon reception of the article', 'your_shaarli' => 'Your Shaarli', 'your_wallabag' => 'Your wallabag', @@ -340,7 +357,7 @@ return array ( 'agpl3' => 'AGPL 3', 'freshrss_description' => 'FreshRSS is a RSS feeds aggregator to self-host like Kriss Feed or Leed. It is light and easy to take in hand while being powerful and configurable tool.', 'credits' => 'Credits', - 'credits_content' => 'Some design elements come from Bootstrap although FreshRSS doesn’t use this framework. Icons come from GNOME project. Open Sans font police used has been created by Steve Matteson. Favicons are collected with getFavicon API. FreshRSS is based on Minz, a PHP framework.', + 'credits_content' => 'Some design elements come from Bootstrap although FreshRSS doesn’t use this framework. Icons come from GNOME project. Open Sans font police has been created by Steve Matteson. Favicons are collected with getFavicon API. FreshRSS is based on Minz, a PHP framework.', 'version' => 'Version', 'logs' => 'Logs', @@ -351,6 +368,7 @@ return array ( 'login_required' => 'Login required:', 'confirm_action' => 'Are you sure you want to perform this action? It cannot be cancelled!', + 'confirm_action_feed_cat' => 'Are you sure you want to perform this action? You may lost related favorites and user queries. It cannot be cancelled!', 'notif_title_new_articles' => 'FreshRSS: new articles!', 'notif_body_new_articles' => 'There are \d new articles to read on FreshRSS.', @@ -414,4 +432,23 @@ return array ( 'stats_entry_per_category' => 'Entries per category', 'stats_top_feed' => 'Top ten feeds', 'stats_entry_count' => 'Entry count', + 'stats_no_idle' => 'There is no idle feed!', + + 'update' => 'Update', + 'update_system' => 'Update system', + 'update_check' => 'Check for new updates', + 'update_last' => 'Last verification: %s', + 'update_can_apply' => 'An update is available.', + 'update_apply' => 'Apply', + 'update_server_not_found' => 'Update server cannot be found. [%s]', + '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 reset: a form will be used instead of Persona.', + 'auth_not_persona' => 'Only Persona system can be reset.', + '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 08f12234e..b0fbf15ae 100644 --- a/app/i18n/fr.php +++ b/app/i18n/fr.php @@ -5,6 +5,7 @@ return array ( 'login' => 'Connexion', 'keep_logged_in' => 'Rester connecté (1 mois)', 'login_with_persona' => 'Connexion avec Persona', + 'login_persona_problem' => 'Problème de connexion à Persona ?', 'logout' => 'Déconnexion', 'search' => 'Rechercher des mots ou des #tags', 'search_short' => 'Rechercher', @@ -42,9 +43,11 @@ 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é', + 'query_deprecated' => 'Ce filtre n’est plus valide. La catégorie ou le flux concerné a été supprimé.', 'about' => 'À propos', 'stats' => 'Statistiques', 'stats_idle' => 'Flux inactifs', @@ -54,11 +57,11 @@ return array ( 'stats_entry_per_day_of_week' => 'Par jour de la semaine', 'stats_entry_per_month' => 'Par mois', - 'last_week' => 'La dernière semaine', - 'last_month' => 'Le dernier mois', - 'last_3_month' => 'Les derniers trois mois', - 'last_6_month' => 'Les derniers six mois', - 'last_year' => 'La dernière année', + 'last_week' => 'Depuis la semaine dernière', + 'last_month' => 'Depuis le mois dernier', + 'last_3_month' => 'Depuis les trois derniers mois', + 'last_6_month' => 'Depuis les six derniers mois', + 'last_year' => 'Depuis l’année dernière', 'your_rss_feeds' => 'Vos flux RSS', 'add_rss_feed' => 'Ajouter un flux RSS', @@ -92,6 +95,7 @@ return array ( 'rss_view' => 'Flux RSS', 'show_all_articles' => 'Afficher tous les articles', 'show_not_reads' => 'Afficher les non lus', + 'show_adaptive' => 'Adapter l’affichage', 'show_read' => 'Afficher les lus', 'show_favorite' => 'Afficher les favoris', 'show_not_favorite' => 'Afficher tout sauf les favoris', @@ -152,10 +156,13 @@ return array ( 'public' => 'Public', 'invalid_login' => 'L’identifiant est invalide !', + 'file_is_nok' => 'Veuillez vérifier les droits sur le répertoire %s. Le serveur HTTP doit être capable d’écrire dedans.', + // VIEWS '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.', @@ -180,6 +187,9 @@ return array ( 'auto_share' => 'Partager', 'auto_share_help' => 'S’il n’y a qu’un mode de partage, celui ci est utilisé automatiquement. Sinon ils sont accessibles par leur numéro.', 'focus_search' => 'Accéder à la recherche', + 'user_filter' => 'Accéder aux filtres utilisateur', + 'user_filter_help' => 'S’il n’y a qu’un filtre utilisateur, celui ci est utilisé automatiquement. Sinon ils sont accessibles par leur numéro.', + 'help' => 'Afficher la documentation', 'file_to_import' => 'Fichier à importer
(OPML, Json ou Zip)', 'file_to_import_no_zip' => 'Fichier à importer
(OPML ou Json)', @@ -197,13 +207,15 @@ 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', 'website_url' => 'URL du site', 'feed_url' => 'URL du flux', 'articles' => 'articles', - 'number_articles' => 'Nombre d’articles', + 'number_articles' => '%d articles', 'by_feed' => 'par flux', 'by_default' => 'Par défaut', 'keep_history' => 'Nombre minimum d’articles à conserver', @@ -225,7 +237,7 @@ return array ( 'not_yet_implemented' => 'Pas encore implémenté', 'access_protected_feeds' => 'La connexion permet d’accéder aux flux protégés par une authentification HTTP.', 'no_selected_feed' => 'Aucun flux sélectionné.', - 'think_to_add' => 'Vous pouvez ajouter des flux.', + 'think_to_add' => 'Vous pouvez ajouter des flux.', 'current_user' => 'Utilisateur actuel', 'password_form' => 'Mot de passe
(pour connexion par formulaire)', @@ -237,7 +249,7 @@ return array ( 'unsafe_autologin' => 'Autoriser les connexions 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', + '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', 'is_admin' => 'est administrateur', 'auth_type' => 'Méthode d’authentification', @@ -248,6 +260,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éé.', @@ -263,17 +276,21 @@ return array ( 'reading_configuration' => 'Lecture', 'display_configuration' => 'Affichage', 'articles_per_page' => 'Nombre d’articles par page', + 'number_divided_when_reader' => 'Divisé par 2 dans la vue de lecture.', 'default_view' => 'Vue par défaut', + 'articles_to_display' => 'Articles à afficher', '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', 'display_categories_unfolded' => 'Afficher les catégories pliées par défaut', - 'hide_read_feeds' => 'Cacher les catégories & flux sans article non-lu (uniquement en affichage “articles non lus”)', + 'hide_read_feeds' => 'Cacher les catégories & flux sans article non-lu (ne fonctionne pas avec la configuration “Afficher tous les articles”)', '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', '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”', @@ -351,6 +368,7 @@ return array ( 'login_required' => 'Accès protégé par mot de passe :', 'confirm_action' => 'Êtes-vous sûr(e) de vouloir continuer ? Cette action ne peut être annulée !', + 'confirm_action_feed_cat' => 'Êtes-vous sûr(e) de vouloir continuer ? Vous pourriez perdre les favoris et les filtres associés. Cette action ne peut être annulée !', 'notif_title_new_articles' => 'FreshRSS : nouveaux articles !', 'notif_body_new_articles' => 'Il y a \d nouveaux articles à lire sur FreshRSS.', @@ -414,4 +432,23 @@ return array ( 'stats_entry_per_category' => 'Articles par catégorie', 'stats_top_feed' => 'Les dix plus gros flux', 'stats_entry_count' => 'Nombre d’articles', + 'stats_no_idle' => 'Il n’y a aucun flux inactif !', + + 'update' => 'Mise à jour', + 'update_system' => 'Système de mise à jour', + 'update_check' => 'Vérifier les mises à jour', + 'update_last' => 'Dernière vérification : %s', + 'update_can_apply' => 'Une mise à jour est disponible.', + 'update_apply' => 'Appliquer la mise à jour', + 'update_server_not_found' => 'Le serveur de mise à jour n’a pas été trouvé. [%s]', + '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' => 'Réinitialisation de l’authentification', + 'auth_will_reset' => 'Le système d’authentification va être réinitialisé : un formulaire sera utilisé à la place de Persona.', + 'auth_not_persona' => 'Seul le système d’authentification Persona peut être réinitialisé.', + '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/i18n/install.en.php b/app/i18n/install.en.php index 50208fcef..c422de90f 100644 --- a/app/i18n/install.en.php +++ b/app/i18n/install.en.php @@ -42,6 +42,8 @@ return array ( 'data_is_ok' => 'Permissions on data directory are good', 'persona_is_ok' => 'Permissions on Mozilla Persona directory are good', 'file_is_nok' => 'Check permissions on %s directory. HTTP server must have rights to write into', + 'http_referer_is_ok' => 'Your HTTP REFERER is known and corresponds to your server.', + 'http_referer_is_nok' => 'Please check that you are not altering your HTTP REFERER.', 'fix_errors_before' => 'Fix errors before skip to the next step.', 'general_conf_is_ok' => 'General configuration has been saved.', diff --git a/app/i18n/install.fr.php b/app/i18n/install.fr.php index 9c039f904..785c02459 100644 --- a/app/i18n/install.fr.php +++ b/app/i18n/install.fr.php @@ -42,6 +42,8 @@ return array ( 'data_is_ok' => 'Les droits sur le répertoire de data sont bons', 'persona_is_ok' => 'Les droits sur le répertoire de Mozilla Persona sont bons', 'file_is_nok' => 'Veuillez vérifier les droits sur le répertoire %s. Le serveur HTTP doit être capable d’écrire dedans', + 'http_referer_is_ok' => 'Le HTTP REFERER est connu et semble correspondre à votre serveur.', + 'http_referer_is_nok' => 'Veuillez vérifier que vous ne modifiez pas votre HTTP REFERER.', 'fix_errors_before' => 'Veuillez corriger les erreurs avant de passer à l’étape suivante.', 'general_conf_is_ok' => 'La configuration générale a été enregistrée.', diff --git a/app/install.php b/app/install.php index eaa1100c1..4449cd063 100644 --- a/app/install.php +++ b/app/install.php @@ -9,10 +9,10 @@ session_name('FreshRSS'); session_set_cookie_params(0, dirname(empty($_SERVER['REQUEST_URI']) ? '/' : dirname($_SERVER['REQUEST_URI'])), null, false, true); session_start(); -if (isset ($_GET['step'])) { - define ('STEP', (int)$_GET['step']); +if (isset($_GET['step'])) { + define('STEP',(int)$_GET['step']); } else { - define ('STEP', 1); + define('STEP', 0); } define('SQL_CREATE_DB', 'CREATE DATABASE IF NOT EXISTS %1$s DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci;'); @@ -23,88 +23,28 @@ if (STEP === 3 && isset($_POST['type'])) { if (isset($_SESSION['bd_type'])) { switch ($_SESSION['bd_type']) { - case 'mysql': - include(APP_PATH . '/SQL/install.sql.mysql.php'); - break; - case 'sqlite': - include(APP_PATH . '/SQL/install.sql.sqlite.php'); - break; + case 'mysql': + include(APP_PATH . '/SQL/install.sql.mysql.php'); + break; + case 'sqlite': + include(APP_PATH . '/SQL/install.sql.sqlite.php'); + break; } } -// -define('SQL_BACKUP006', 'RENAME TABLE `%1$scategory` TO `%1$scategory006`, `%1$sfeed` TO `%1$sfeed006`, `%1$sentry` TO `%1$sentry006`;'); - -define('SQL_SHOW_COLUMNS_UPDATEv006', 'SHOW columns FROM `%1$sentry006` LIKE "id2";'); - -define('SQL_UPDATEv006', ' -ALTER TABLE `%1$scategory006` ADD id2 SMALLINT; - -SET @i = 0; -UPDATE `%1$scategory006` SET id2=(@i:=@i+1) ORDER BY id; - -ALTER TABLE `%1$sfeed006` ADD id2 SMALLINT, ADD category2 SMALLINT; - -SET @i = 0; -UPDATE `%1$sfeed006` SET id2=(@i:=@i+1) ORDER BY name; - -UPDATE `%1$sfeed006` f -INNER JOIN `%1$scategory006` c ON f.category = c.id -SET f.category2 = c.id2; - -INSERT IGNORE INTO `%2$scategory` (name) -SELECT name -FROM `%1$scategory006` -ORDER BY id2; - -INSERT IGNORE INTO `%2$sfeed` (url, category, name, website, description, priority, pathEntries, httpAuth, keep_history) -SELECT url, category2, name, website, description, priority, pathEntries, httpAuth, IF(keep_history = 1, -1, -2) -FROM `%1$sfeed006` -ORDER BY id2; - -ALTER TABLE `%1$sentry006` ADD id2 bigint; - -UPDATE `%1$sentry006` SET id2 = ((date * 1000000) + (rand() * 100000000)); - -INSERT IGNORE INTO `%2$sentry` (id, guid, title, author, link, date, is_read, is_favorite, id_feed, tags) -SELECT e0.id2, e0.guid, e0.title, e0.author, e0.link, e0.date, e0.is_read, e0.is_favorite, f0.id2, e0.tags -FROM `%1$sentry006` e0 -INNER JOIN `%1$sfeed006` f0 ON e0.id_feed = f0.id; -'); - -define('SQL_CONVERT_SELECTv006', ' -SELECT e0.id2, e0.content -FROM `%1$sentry006` e0 -INNER JOIN `%2$sentry` e1 ON e0.id2 = e1.id -WHERE e1.content_bin IS NULL'); - -define('SQL_CONVERT_UPDATEv006', 'UPDATE `%1$sentry` SET ' - . (isset($_SESSION['bd_type']) && $_SESSION['bd_type'] === 'mysql' ? 'content_bin=COMPRESS(?)' : 'content=?') - . ' WHERE id=?;'); - -define('SQL_DROP_BACKUPv006', 'DROP TABLE IF EXISTS `%1$sentry006`, `%1$sfeed006`, `%1$scategory006`;'); - -define('SQL_UPDATE_CACHED_VALUES', ' -UPDATE `%1$sfeed` f -INNER JOIN ( - SELECT e.id_feed, - COUNT(CASE WHEN e.is_read = 0 THEN 1 END) AS nbUnreads, - COUNT(e.id) AS nbEntries - FROM `%1$sentry` e - GROUP BY e.id_feed -) x ON x.id_feed=f.id -SET f.cache_nbEntries=x.nbEntries, f.cache_nbUnreads=x.nbUnreads -'); - -define('SQL_UPDATE_HISTORYv007b', 'UPDATE `%1$sfeed` SET keep_history = CASE WHEN keep_history = 0 THEN -2 WHEN keep_history = 1 THEN -1 ELSE keep_history END;'); +function param($key, $default = false) { + if (isset($_POST[$key])) { + return $_POST[$key]; + } else { + return $default; + } +} -define('SQL_GET_FEEDS', 'SELECT id, url, website FROM `%1$sfeed`;'); -// // gestion internationalisation -$translates = array (); +$translates = array(); $actual = 'en'; -function initTranslate () { +function initTranslate() { global $translates; global $actual; @@ -121,83 +61,95 @@ function initTranslate () { } } -function getBetterLanguage ($fallback) { - $available = availableLanguages (); +function getBetterLanguage($fallback) { + $available = availableLanguages(); $accept = $_SERVER['HTTP_ACCEPT_LANGUAGE']; - $language = strtolower (substr ($accept, 0, 2)); + $language = strtolower(substr($accept, 0, 2)); - if (isset ($available[$language])) { + if (isset($available[$language])) { return $language; } else { return $fallback; } } -function availableLanguages () { - return array ( + +function availableLanguages() { + return array( 'en' => 'English', 'fr' => 'Français' ); } -function _t ($key) { + +function _t($key) { global $translates; $translate = $key; - if (isset ($translates[$key])) { + if (isset($translates[$key])) { $translate = $translates[$key]; } - $args = func_get_args (); + $args = func_get_args(); unset($args[0]); - return vsprintf ($translate, $args); + return vsprintf($translate, $args); } + /*** SAUVEGARDES ***/ -function saveLanguage () { - if (!empty ($_POST)) { - if (!isset ($_POST['language'])) { +function saveLanguage() { + if (!empty($_POST)) { + if (!isset($_POST['language'])) { return false; } $_SESSION['language'] = $_POST['language']; - header ('Location: index.php?step=1'); + header('Location: index.php?step=1'); } } -function saveStep2 () { - if (!empty ($_POST)) { - if (empty ($_POST['title']) || - empty ($_POST['old_entries']) || - empty ($_POST['auth_type']) || - empty ($_POST['default_user'])) { - return false; - } - $_SESSION['salt'] = sha1(uniqid(mt_rand(), true).implode('', stat(__FILE__))); - $_SESSION['title'] = substr(trim($_POST['title']), 0, 25); - $_SESSION['old_entries'] = $_POST['old_entries']; - if ((!ctype_digit($_SESSION['old_entries'])) || ($_SESSION['old_entries'] < 1)) { - $_SESSION['old_entries'] = 3; - } - $_SESSION['mail_login'] = filter_var($_POST['mail_login'], FILTER_VALIDATE_EMAIL); - $_SESSION['default_user'] = substr(preg_replace('/[^a-zA-Z0-9]/', '', $_POST['default_user']), 0, 16); - $_SESSION['auth_type'] = $_POST['auth_type']; - if (!empty($_POST['passwordPlain'])) { +function saveStep2() { + if (!empty($_POST)) { + $_SESSION['title'] = substr(trim(param('title', _t('freshrss'))), 0, 25); + $_SESSION['old_entries'] = param('old_entries', 3); + $_SESSION['auth_type'] = param('auth_type', 'form'); + $_SESSION['default_user'] = substr(preg_replace('/[^a-zA-Z0-9]/', '', param('default_user', '')), 0, 16); + $_SESSION['mail_login'] = filter_var(param('mail_login', ''), FILTER_VALIDATE_EMAIL); + + $password_plain = param('passwordPlain', false); + if ($password_plain !== false) { if (!function_exists('password_hash')) { include_once(LIB_PATH . '/password_compat.php'); } - $passwordHash = password_hash($_POST['passwordPlain'], PASSWORD_BCRYPT, array('cost' => BCRYPT_COST)); + $passwordHash = password_hash($password_plain, PASSWORD_BCRYPT, array('cost' => BCRYPT_COST)); $passwordHash = preg_replace('/^\$2[xy]\$/', '\$2a\$', $passwordHash); //Compatibility with bcrypt.js $_SESSION['passwordHash'] = $passwordHash; } + if (empty($_SESSION['title']) || + empty($_SESSION['old_entries']) || + empty($_SESSION['auth_type']) || + empty($_SESSION['default_user'])) { + return false; + } + + if (($_SESSION['auth_type'] === 'form' && empty($_SESSION['passwordHash'])) || + ($_SESSION['auth_type'] === 'persona' && empty($_SESSION['mail_login']))) { + return false; + } + + $_SESSION['salt'] = sha1(uniqid(mt_rand(), true).implode('', stat(__FILE__))); + if ((!ctype_digit($_SESSION['old_entries'])) ||($_SESSION['old_entries'] < 1)) { + $_SESSION['old_entries'] = 3; + } + $token = ''; if ($_SESSION['mail_login']) { $token = sha1($_SESSION['salt'] . $_SESSION['mail_login']); } - $config_array = array ( + $config_array = array( 'language' => $_SESSION['language'], - 'theme' => $_SESSION['theme'], + 'theme' => 'Origine', 'old_entries' => $_SESSION['old_entries'], 'mail_login' => $_SESSION['mail_login'], 'passwordHash' => $_SESSION['passwordHash'], @@ -214,12 +166,12 @@ function saveStep2 () { file_put_contents($personaFile, $_SESSION['default_user']); } - header ('Location: index.php?step=3'); + header('Location: index.php?step=3'); } } -function saveStep3 () { - if (!empty ($_POST)) { +function saveStep3() { + if (!empty($_POST)) { if ($_SESSION['bd_type'] === 'sqlite') { $_SESSION['bd_base'] = $_SESSION['default_user']; $_SESSION['bd_host'] = ''; @@ -228,10 +180,10 @@ function saveStep3 () { $_SESSION['bd_prefix'] = ''; $_SESSION['bd_prefix_user'] = ''; //No prefix for SQLite } else { - if (empty ($_POST['type']) || - empty ($_POST['host']) || - empty ($_POST['user']) || - empty ($_POST['base'])) { + if (empty($_POST['type']) || + empty($_POST['host']) || + empty($_POST['user']) || + empty($_POST['base'])) { $_SESSION['bd_error'] = 'Missing parameters!'; } $_SESSION['bd_base'] = substr($_POST['base'], 0, 64); @@ -239,7 +191,7 @@ function saveStep3 () { $_SESSION['bd_user'] = $_POST['user']; $_SESSION['bd_password'] = $_POST['pass']; $_SESSION['bd_prefix'] = substr($_POST['prefix'], 0, 16); - $_SESSION['bd_prefix_user'] = $_SESSION['bd_prefix'] . (empty($_SESSION['default_user']) ? '' : ($_SESSION['default_user'] . '_')); + $_SESSION['bd_prefix_user'] = $_SESSION['bd_prefix'] .(empty($_SESSION['default_user']) ? '' :($_SESSION['default_user'] . '_')); } $ini_array = array( @@ -268,15 +220,11 @@ function saveStep3 () { @unlink(DATA_PATH . '/config.php'); //To avoid access-rights problems file_put_contents(DATA_PATH . '/config.php', " 'SET NAMES utf8', - ); - break; - case 'sqlite': - return false; //No update for SQLite needed so far - default: - return false; - } - - $c = new PDO($str, $_SESSION['bd_user'], $_SESSION['bd_password'], $driver_options); - - $stm = $c->prepare(SQL_SHOW_TABLES); - $stm->execute(); - $res = $stm->fetchAll(PDO::FETCH_COLUMN, 0); - if (!in_array($_SESSION['bd_prefix'] . 'entry006', $res)) { - return false; - } - - $sql = sprintf(SQL_SHOW_COLUMNS_UPDATEv006, $_SESSION['bd_prefix']); - $stm = $c->prepare($sql); - $stm->execute(); - $res = $stm->fetchAll(PDO::FETCH_COLUMN, 0); - if (!in_array('id2', $res)) { - if (!$perform) { - return true; - } - $sql = sprintf(SQL_UPDATEv006, $_SESSION['bd_prefix'], $_SESSION['bd_prefix_user']); - $stm = $c->prepare($sql, array(PDO::ATTR_EMULATE_PREPARES => true)); - $stm->execute(); - } - - $sql = sprintf(SQL_CONVERT_SELECTv006, $_SESSION['bd_prefix'], $_SESSION['bd_prefix_user']); - if (!$perform) { - $sql .= ' LIMIT 1'; - } - $stm = $c->prepare($sql); - $stm->execute(); - if (!$perform) { - $res = $stm->fetchAll(PDO::FETCH_COLUMN, 0); - return count($res) > 0; - } else { - @set_time_limit(300); - } - - $c2 = new PDO($str, $_SESSION['bd_user'], $_SESSION['bd_password'], $driver_options); - $sql = sprintf(SQL_CONVERT_UPDATEv006, $_SESSION['bd_prefix_user']); - $stm2 = $c2->prepare($sql); - while ($row = $stm->fetch(PDO::FETCH_ASSOC)) { - $id = $row['id2']; - $content = unserialize(gzinflate(base64_decode($row['content']))); - $stm2->execute(array($content, $id)); - } - - return true; - } catch (PDOException $e) { - return false; - } - return false; -} - function newPdo() { switch ($_SESSION['bd_type']) { - case 'mysql': - $str = 'mysql:host=' . $_SESSION['bd_host'] . ';dbname=' . $_SESSION['bd_base']; - $driver_options = array( - PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8', - ); - break; - case 'sqlite': - $str = 'sqlite:' . DATA_PATH . '/' . $_SESSION['default_user'] . '.sqlite'; - $driver_options = array( - PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, - ); - break; - default: - return false; + case 'mysql': + $str = 'mysql:host=' . $_SESSION['bd_host'] . ';dbname=' . $_SESSION['bd_base']; + $driver_options = array( + PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8', + ); + break; + case 'sqlite': + $str = 'sqlite:' . DATA_PATH . '/' . $_SESSION['default_user'] . '.sqlite'; + $driver_options = array( + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + ); + break; + default: + return false; } return new PDO($str, $_SESSION['bd_user'], $_SESSION['bd_password'], $driver_options); } -function postUpdate() { - $c = newPdo(); - - if ($_SESSION['bd_type'] !== 'sqlite') { //No update for SQLite needed yet - $sql = sprintf(SQL_UPDATE_HISTORYv007b, $_SESSION['bd_prefix_user']); - $stm = $c->prepare($sql); - $stm->execute(); +function deleteInstall() { + $res = unlink(DATA_PATH . '/do-install.txt'); - $sql = sprintf(SQL_UPDATE_CACHED_VALUES, $_SESSION['bd_prefix_user']); - $stm = $c->prepare($sql); - $stm->execute(); - } - - // - $sql = sprintf(SQL_GET_FEEDS, $_SESSION['bd_prefix_user']); - $stm = $c->prepare($sql); - $stm->execute(); - $res = $stm->fetchAll(PDO::FETCH_ASSOC); - foreach ($res as $feed) { - if (empty($feed['url'])) { - continue; - } - $hash = hash('crc32b', $_SESSION['salt'] . $feed['url']); - @file_put_contents(DATA_PATH . '/favicons/' . $hash . '.txt', - empty($feed['website']) ? $feed['url'] : $feed['website']); - } - // -} - -function deleteInstall () { - $res = unlink (DATA_PATH . '/do-install.txt'); - if ($res) { - header ('Location: index.php'); - } - - $needs = array('bd_type', 'bd_host', 'bd_base', 'bd_user', 'bd_password', 'bd_prefix'); - foreach ($needs as $need) { - if (!isset($_SESSION[$need])) { - return false; - } - } - - try { - $c = newPdo(); - $sql = sprintf(SQL_DROP_BACKUPv006, $_SESSION['bd_prefix']); - $stm = $c->prepare($sql); - $stm->execute(); - - return true; - } catch (PDOException $e) { + if (!$res) { return false; } - return false; -} -function moveOldFiles() { - $mvs = array( - '/app/configuration/application.ini' => '/data/application.ini', //v0.6 - '/public/data/Configuration.array.php' => '/data/Configuration.array.php', //v0.6 - ); - $ok = true; - foreach ($mvs as $fFrom => $fTo) { - if (file_exists(FRESHRSS_PATH . $fFrom)) { - if (copy(FRESHRSS_PATH . $fFrom, FRESHRSS_PATH . $fTo)) { - @unlink(FRESHRSS_PATH . $fFrom); - } else { - $ok = false; - } - } - } - return $ok; + header('Location: index.php'); } -function delTree($dir) { //http://php.net/rmdir#110489 - if (!is_dir($dir)) { - return true; - } - $files = array_diff(scandir($dir), array('.', '..')); - foreach ($files as $file) { - $f = $dir . '/' . $file; - if (is_dir($f)) { - @chmod($f, 0777); - delTree($f); - } - else unlink($f); - } - return rmdir($dir); -} /*** VÉRIFICATIONS ***/ -function checkStep () { - $s0 = checkStep0 (); - $s1 = checkStep1 (); - $s2 = checkStep2 (); - $s3 = checkStep3 (); +function checkStep() { + $s0 = checkStep0(); + $s1 = checkStep1(); + $s2 = checkStep2(); + $s3 = checkStep3(); if (STEP > 0 && $s0['all'] != 'ok') { - header ('Location: index.php?step=0'); + header('Location: index.php?step=0'); } elseif (STEP > 1 && $s1['all'] != 'ok') { - header ('Location: index.php?step=1'); + header('Location: index.php?step=1'); } elseif (STEP > 2 && $s2['all'] != 'ok') { - header ('Location: index.php?step=2'); + header('Location: index.php?step=2'); } elseif (STEP > 3 && $s3['all'] != 'ok') { - header ('Location: index.php?step=3'); + header('Location: index.php?step=3'); } $_SESSION['actualize_feeds'] = true; } -function checkStep0 () { - moveOldFiles(); - - if (file_exists(DATA_PATH . '/config.php')) { - $ini_array = include(DATA_PATH . '/config.php'); - } elseif (file_exists(DATA_PATH . '/application.ini')) { //v0.6 - $ini_array = parse_ini_file(DATA_PATH . '/application.ini', true); - $ini_array['general']['title'] = empty($ini_array['general']['title']) ? '' : stripslashes($ini_array['general']['title']); - } else { - $ini_array = null; - } - if ($ini_array) { - $ini_general = isset($ini_array['general']) ? $ini_array['general'] : null; - if ($ini_general) { - $keys = array('environment', 'salt', 'title', 'default_user', 'allow_anonymous', 'allow_anonymous_refresh', 'auth_type', 'api_enabled', 'unsafe_autologin_enabled'); - foreach ($keys as $key) { - if ((empty($_SESSION[$key])) && isset($ini_general[$key])) { - $_SESSION[$key] = $ini_general[$key]; - } - } - } - $ini_db = isset($ini_array['db']) ? $ini_array['db'] : null; - if ($ini_db) { - $keys = array('type', 'host', 'user', 'password', 'base', 'prefix'); - foreach ($keys as $key) { - if ((!isset($_SESSION['bd_' . $key])) && isset($ini_db[$key])) { - $_SESSION['bd_' . $key] = $ini_db[$key]; - } - } - } - } +function checkStep0() { + $languages = availableLanguages(); + $language = isset($_SESSION['language']) && + isset($languages[$_SESSION['language']]); - if (isset($_SESSION['default_user']) && file_exists(DATA_PATH . '/' . $_SESSION['default_user'] . '_user.php')) { - $userConfig = include(DATA_PATH . '/' . $_SESSION['default_user'] . '_user.php'); - } elseif (file_exists(DATA_PATH . '/Configuration.array.php')) { - $userConfig = include(DATA_PATH . '/Configuration.array.php'); //v0.6 - if (empty($_SESSION['auth_type'])) { - $_SESSION['auth_type'] = empty($userConfig['mail_login']) ? 'none' : 'persona'; - } - if (!isset($_SESSION['allow_anonymous'])) { - $_SESSION['allow_anonymous'] = empty($userConfig['anon_access']) ? false : ($userConfig['anon_access'] === 'yes'); - } - } else { - $userConfig = array(); - } - if (empty($_SESSION['auth_type'])) { //v0.7b - $_SESSION['auth_type'] = ''; - } - - $keys = array('language', 'theme', 'old_entries', 'mail_login', 'passwordHash'); - foreach ($keys as $key) { - if ((!isset($_SESSION[$key])) && isset($userConfig[$key])) { - $_SESSION[$key] = $userConfig[$key]; - } - } - - $languages = availableLanguages (); - $language = isset ($_SESSION['language']) && - isset ($languages[$_SESSION['language']]); - - if (empty($_SESSION['passwordHash'])) { //v0.7b - $_SESSION['passwordHash'] = ''; - } - if (empty($_SESSION['theme'])) { - $_SESSION['theme'] = 'Origine'; - } else { - switch (strtolower($_SESSION['theme'])) { - case 'default': //v0.7b - $_SESSION['theme'] = 'Origine'; - break; - case 'flat-design': //v0.7b - $_SESSION['theme'] = 'Flat'; - break; - case 'default_dark': //v0.7b - $_SESSION['theme'] = 'Dark'; - break; - } - } - - return array ( + return array( 'language' => $language ? 'ok' : 'ko', 'all' => $language ? 'ok' : 'ko' ); } -function checkStep1 () { - $php = version_compare (PHP_VERSION, '5.2.1') >= 0; - $minz = file_exists (LIB_PATH . '/Minz'); - $curl = extension_loaded ('curl'); - $pdo_mysql = extension_loaded ('pdo_mysql'); - $pdo_sqlite = extension_loaded ('pdo_sqlite'); +function checkStep1() { + $php = version_compare(PHP_VERSION, '5.2.1') >= 0; + $minz = file_exists(LIB_PATH . '/Minz'); + $curl = extension_loaded('curl'); + $pdo_mysql = extension_loaded('pdo_mysql'); + $pdo_sqlite = extension_loaded('pdo_sqlite'); $pdo = $pdo_mysql || $pdo_sqlite; - $pcre = extension_loaded ('pcre'); - $ctype = extension_loaded ('ctype'); + $pcre = extension_loaded('pcre'); + $ctype = extension_loaded('ctype'); $dom = class_exists('DOMDocument'); - $data = DATA_PATH && is_writable (DATA_PATH); - $cache = CACHE_PATH && is_writable (CACHE_PATH); - $log = LOG_PATH && is_writable (LOG_PATH); - $favicons = is_writable (DATA_PATH . '/favicons'); - $persona = is_writable (DATA_PATH . '/persona'); - - return array ( + $data = DATA_PATH && is_writable(DATA_PATH); + $cache = CACHE_PATH && is_writable(CACHE_PATH); + $log = LOG_PATH && is_writable(LOG_PATH); + $favicons = is_writable(DATA_PATH . '/favicons'); + $persona = is_writable(DATA_PATH . '/persona'); + $http_referer = is_referer_from_same_domain(); + + return array( 'php' => $php ? 'ok' : 'ko', 'minz' => $minz ? 'ok' : 'ko', 'curl' => $curl ? 'ok' : 'ko', @@ -601,44 +324,57 @@ function checkStep1 () { 'log' => $log ? 'ok' : 'ko', 'favicons' => $favicons ? 'ok' : 'ko', 'persona' => $persona ? 'ok' : 'ko', - 'all' => $php && $minz && $curl && $pdo && $pcre && $ctype && $dom && $data && $cache && $log && $favicons && $persona ? 'ok' : 'ko' + 'http_referer' => $http_referer ? 'ok' : 'ko', + 'all' => $php && $minz && $curl && $pdo && $pcre && $ctype && $dom && + $data && $cache && $log && $favicons && $persona && $http_referer ? + 'ok' : 'ko' ); } -function checkStep2 () { - $conf = !empty($_SESSION['salt']) && - !empty($_SESSION['title']) && +function checkStep2() { + $conf = !empty($_SESSION['title']) && !empty($_SESSION['old_entries']) && isset($_SESSION['mail_login']) && !empty($_SESSION['default_user']); + + $form = ( + isset($_SESSION['auth_type']) && + ($_SESSION['auth_type'] != 'form' || !empty($_SESSION['passwordHash'])) + ); + + $persona = ( + isset($_SESSION['auth_type']) && + ($_SESSION['auth_type'] != 'persona' || !empty($_SESSION['mail_login'])) + ); + $defaultUser = empty($_POST['default_user']) ? null : $_POST['default_user']; if ($defaultUser === null) { $defaultUser = empty($_SESSION['default_user']) ? '' : $_SESSION['default_user']; } $data = is_writable(DATA_PATH . '/' . $defaultUser . '_user.php'); - if ($data) { - @unlink(DATA_PATH . '/Configuration.array.php'); //v0.6 - } - return array ( + return array( 'conf' => $conf ? 'ok' : 'ko', + 'form' => $form ? 'ok' : 'ko', + 'persona' => $persona ? 'ok' : 'ko', 'data' => $data ? 'ok' : 'ko', - 'all' => $conf && $data ? 'ok' : 'ko' + 'all' => $conf && $form && $persona && $data ? 'ok' : 'ko' ); } -function checkStep3 () { + +function checkStep3() { $conf = is_writable(DATA_PATH . '/config.php'); - $bd = isset ($_SESSION['bd_type']) && - isset ($_SESSION['bd_host']) && - isset ($_SESSION['bd_user']) && - isset ($_SESSION['bd_password']) && - isset ($_SESSION['bd_base']) && - isset ($_SESSION['bd_prefix']) && - isset ($_SESSION['bd_error']); + $bd = isset($_SESSION['bd_type']) && + isset($_SESSION['bd_host']) && + isset($_SESSION['bd_user']) && + isset($_SESSION['bd_password']) && + isset($_SESSION['bd_base']) && + isset($_SESSION['bd_prefix']) && + isset($_SESSION['bd_error']); $conn = empty($_SESSION['bd_error']); - return array ( + return array( 'bd' => $bd ? 'ok' : 'ko', 'conn' => $conn ? 'ok' : 'ko', 'conf' => $conf ? 'ok' : 'ko', @@ -646,51 +382,41 @@ function checkStep3 () { ); } -function checkBD () { +function checkBD() { $ok = false; try { $str = ''; $driver_options = null; switch ($_SESSION['bd_type']) { - case 'mysql': - $driver_options = array( - PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8' - ); - - try { // on ouvre une connexion juste pour créer la base si elle n'existe pas - $str = 'mysql:host=' . $_SESSION['bd_host'] . ';'; - $c = new PDO ($str, $_SESSION['bd_user'], $_SESSION['bd_password'], $driver_options); - $sql = sprintf (SQL_CREATE_DB, $_SESSION['bd_base']); - $res = $c->query ($sql); - } catch (PDOException $e) { - } - - // on écrase la précédente connexion en sélectionnant la nouvelle BDD - $str = 'mysql:host=' . $_SESSION['bd_host'] . ';dbname=' . $_SESSION['bd_base']; - break; - case 'sqlite': - $str = 'sqlite:' . DATA_PATH . '/' . $_SESSION['default_user'] . '.sqlite'; - $driver_options = array( - PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, - ); - break; - default: - return false; - } - - $c = new PDO ($str, $_SESSION['bd_user'], $_SESSION['bd_password'], $driver_options); + case 'mysql': + $driver_options = array( + PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8' + ); - if ($_SESSION['bd_type'] !== 'sqlite') { //No SQL backup for SQLite - $stm = $c->prepare(SQL_SHOW_TABLES); - $stm->execute(); - $res = $stm->fetchAll(PDO::FETCH_COLUMN, 0); - if (in_array($_SESSION['bd_prefix'] . 'entry', $res) && !in_array($_SESSION['bd_prefix'] . 'entry006', $res)) { - $sql = sprintf(SQL_BACKUP006, $_SESSION['bd_prefix']); //v0.6 - $res = $c->query($sql); //Backup tables + try { // on ouvre une connexion juste pour créer la base si elle n'existe pas + $str = 'mysql:host=' . $_SESSION['bd_host'] . ';'; + $c = new PDO($str, $_SESSION['bd_user'], $_SESSION['bd_password'], $driver_options); + $sql = sprintf(SQL_CREATE_DB, $_SESSION['bd_base']); + $res = $c->query($sql); + } catch (PDOException $e) { } + + // on écrase la précédente connexion en sélectionnant la nouvelle BDD + $str = 'mysql:host=' . $_SESSION['bd_host'] . ';dbname=' . $_SESSION['bd_base']; + break; + case 'sqlite': + $str = 'sqlite:' . DATA_PATH . '/' . $_SESSION['default_user'] . '.sqlite'; + $driver_options = array( + PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, + ); + break; + default: + return false; } + $c = new PDO($str, $_SESSION['bd_user'], $_SESSION['bd_password'], $driver_options); + if (defined('SQL_CREATE_TABLES')) { $sql = sprintf(SQL_CREATE_TABLES, $_SESSION['bd_prefix_user'], _t('default_category')); $stm = $c->prepare($sql); @@ -719,20 +445,20 @@ function checkBD () { } /*** AFFICHAGE ***/ -function printStep0 () { +function printStep0() { global $actual; ?> - -

+ +

- +
- +
+
- +
- +
- +
- +
- + + + + +
@@ -887,25 +624,73 @@ function printStep2 () {
- +
+ /> + +
- +
- - + /> +
+ +
- - + + - +
@@ -913,29 +698,29 @@ function printStep2 () { - -

+ +

-

+

- +
- +
+
- +
- +
- +
- +
- +
- +
- +
- +
@@ -988,10 +773,10 @@ function printStep3 () {
- - + + - +
@@ -999,74 +784,40 @@ function printStep3 () { - - - - -

- -
-
- - -
-
- - -

- -
-
- -
-
- -
- -

- +

+ -

+

@@ -1075,28 +826,27 @@ case 6: - <?php echo _t ('freshrss_installation'); ?> - + <?php echo _t('freshrss_installation'); ?> +
-

-

+

+

@@ -1104,25 +854,22 @@ case 6: switch (STEP) { case 0: default: - printStep0 (); + printStep0(); break; case 1: - printStep1 (); + printStep1(); break; case 2: - printStep2 (); + printStep2(); break; case 3: - printStep3 (); + printStep3(); break; case 4: - printStep4 (); + printStep4(); break; case 5: - printStep5 (); - break; - case 6: - printStep6 (); + printStep5(); break; } ?> diff --git a/app/layout/aside_configure.phtml b/app/layout/aside_configure.phtml index e66f2f64c..d5c9bf4c9 100644 --- a/app/layout/aside_configure.phtml +++ b/app/layout/aside_configure.phtml @@ -1,25 +1,33 @@ diff --git a/app/layout/aside_flux.phtml b/app/layout/aside_flux.phtml index 432d6fdb7..aac3c0896 100644 --- a/app/layout/aside_flux.phtml +++ b/app/layout/aside_flux.phtml @@ -1,16 +1,18 @@
- +
    loginOk) { ?> + +
  • -
    - - +
    + +
  • -
  • +
  • @@ -31,44 +33,42 @@
  • cat_aside as $cat) { - $feeds = $cat->feeds (); - if (!empty ($feeds)) { + $feeds = $cat->feeds(); + if (!empty($feeds)) { $c_active = false; - if ($this->conf->display_categories) { - if ($this->get_c == $cat->id () && $this->get_f) { - $c_active = true; - } - } else { - if ($this->get_c == $cat->id ()) { - $c_active = true; + $c_show = false; + if ($this->get_c == $cat->id()) { + $c_active = true; + if (!$this->conf->display_categories || $this->get_f) { + $c_show = true; } } ?>
  • >
        id (); - $nbEntries = $feed->nbEntries (); + $feed_id = $feed->id(); + $nbEntries = $feed->nbEntries(); $f_active = ($this->get_f == $feed_id); - ?>
      • ✇ name(); ?> ✇ name(); ?>
      diff --git a/app/layout/header.phtml b/app/layout/header.phtml index 2e42bfdea..028e63d8a 100644 --- a/app/layout/header.phtml +++ b/app/layout/header.phtml @@ -75,6 +75,12 @@ if (Minz_Configuration::canLogIn()) {
    • + +
    • +
    • diff --git a/app/layout/layout.phtml b/app/layout/layout.phtml index 96a88d245..f95f45b5e 100644 --- a/app/layout/layout.phtml +++ b/app/layout/layout.phtml @@ -13,6 +13,7 @@ if (!empty($this->nextId)) { $params = Minz_Request::params(); $params['next'] = $this->nextId; + $params['ajax'] = 1; ?> diff --git a/app/layout/nav_menu.phtml b/app/layout/nav_menu.phtml index 25833c16d..a9e6614e7 100644 --- a/app/layout/nav_menu.phtml +++ b/app/layout/nav_menu.phtml @@ -3,11 +3,11 @@ ?> + +
      + +
      + +
      +
      diff --git a/app/views/configure/feed.phtml b/app/views/configure/feed.phtml index a8dd9a8cb..e96a28739 100644 --- a/app/views/configure/feed.phtml +++ b/app/views/configure/feed.phtml @@ -70,27 +70,33 @@
      +
      +
      + + + +
      +
      - - + +
      -
      - - - -
      -
      -
      - -
      - +
      + + + + +
      diff --git a/app/views/configure/queries.phtml b/app/views/configure/queries.phtml index 2895f584c..e778ce078 100644 --- a/app/views/configure/queries.phtml +++ b/app/views/configure/queries.phtml @@ -42,30 +42,37 @@ + (isset($query['get']) ? 1 : 0); // If the only filter is "all" articles, we consider there is no filter $exist = ($exist === 1 && isset($query['get']) && $query['get'] === 'a') ? 0 : $exist; + + $deprecated = (isset($this->query_get[$key]) && + $this->query_get[$key]['deprecated']); ?>
      + +
      +
      +
        - +
      • - +
      • - +
      • - +
      • query_get[$key]['type'], $this->query_get[$key]['name']); ?>
      diff --git a/app/views/configure/reading.phtml b/app/views/configure/reading.phtml index 5a26501a4..8b2da2a28 100644 --- a/app/views/configure/reading.phtml +++ b/app/views/configure/reading.phtml @@ -10,6 +10,7 @@
      +
      @@ -31,14 +32,17 @@ - - +
      +
+ +
+ +
+
diff --git a/app/views/configure/shortcut.phtml b/app/views/configure/shortcut.phtml index bfb13f003..a4029b676 100644 --- a/app/views/configure/shortcut.phtml +++ b/app/views/configure/shortcut.phtml @@ -103,6 +103,21 @@
+
+ +
+ + +
+
+ +
+ +
+ +
+
+
diff --git a/app/views/helpers/javascript_vars.phtml b/app/views/helpers/javascript_vars.phtml index 7144c519a..1139eb446 100644 --- a/app/views/helpers/javascript_vars.phtml +++ b/app/views/helpers/javascript_vars.phtml @@ -4,7 +4,8 @@ echo '"use strict";', "\n"; $mark = $this->conf->mark_when; echo 'var ', - 'hide_posts=', ($this->conf->display_posts || Minz_Request::param('output') === 'reader') ? 'false' : 'true', + 'help_url="', FRESHRSS_WIKI, '"', + ',hide_posts=', ($this->conf->display_posts || Minz_Request::param('output') === 'reader') ? 'false' : 'true', ',display_order="', Minz_Request::param('order', $this->conf->sort_order), '"', ',auto_mark_article=', $mark['article'] ? 'true' : 'false', ',auto_mark_site=', $mark['site'] ? 'true' : 'false', @@ -25,7 +26,9 @@ echo ',shortcuts={', 'collapse_entry:"', $s['collapse_entry'], '",', 'load_more:"', $s['load_more'], '",', 'auto_share:"', $s['auto_share'], '",', - 'focus_search:"', $s['focus_search'], '"', + 'focus_search:"', $s['focus_search'], '",', + 'user_filter:"', $s['user_filter'], '",', + 'help:"', $s['help'], '"', "},\n"; if (Minz_Request::param ('output') === 'global') { @@ -48,9 +51,11 @@ echo 'authType="', $authType, '",', 'url_login="', _url ('index', 'login'), '",', 'url_logout="', _url ('index', 'logout'), '",'; -echo 'str_confirmation="', Minz_Translate::t('confirm_action'), '"', ",\n"; +echo 'str_confirmation_default="', 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/app/views/helpers/pagination.phtml b/app/views/helpers/pagination.phtml index f237e1f3e..cea338364 100755 --- a/app/views/helpers/pagination.phtml +++ b/app/views/helpers/pagination.phtml @@ -1,26 +1,37 @@ + + diff --git a/app/views/helpers/view/global_view.phtml b/app/views/helpers/view/global_view.phtml index db937eeae..72bcf4c73 100644 --- a/app/views/helpers/view/global_view.phtml +++ b/app/views/helpers/view/global_view.phtml @@ -1,5 +1,6 @@ partial ('nav_menu'); ?> +entries)) { ?>
'index', 'a' => 'index', 'params' => array()); @@ -43,3 +44,10 @@
conf->display_posts ? '' : ' class="hide_posts"'; ?>>
+ + +
+

+

+
+ diff --git a/app/views/helpers/view/normal_view.phtml b/app/views/helpers/view/normal_view.phtml index 87bf2e22a..1dbf14f4c 100644 --- a/app/views/helpers/view/normal_view.phtml +++ b/app/views/helpers/view/normal_view.phtml @@ -183,8 +183,8 @@ if (!empty($this->entries)) { partial ('nav_entries'); ?> -
- - +
+

+

diff --git a/app/views/helpers/view/reader_view.phtml b/app/views/helpers/view/reader_view.phtml index 665f72849..c80dca519 100644 --- a/app/views/helpers/view/reader_view.phtml +++ b/app/views/helpers/view/reader_view.phtml @@ -37,8 +37,8 @@ if (!empty($this->entries)) {
-
- - +
+

+

diff --git a/app/views/index/formLogin.phtml b/app/views/index/formLogin.phtml index b79c1b614..b05cdced4 100644 --- a/app/views/index/formLogin.phtml +++ b/app/views/index/formLogin.phtml @@ -3,7 +3,7 @@ switch (Minz_Configuration::authType()) { case 'form': - ?>
+ ?>
@@ -29,8 +29,15 @@ case 'persona': ?>

- - +

+ + + + +

diff --git a/app/views/index/resetAuth.phtml b/app/views/index/resetAuth.phtml new file mode 100644 index 000000000..6d4282c14 --- /dev/null +++ b/app/views/index/resetAuth.phtml @@ -0,0 +1,33 @@ +
+

+ + message)) { ?> +

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

+ + + no_form) { ?> + +

+
+ +

+ +
+ + +
+
+ + +
+ +
+
+ +
+ + +
diff --git a/app/views/stats/idle.phtml b/app/views/stats/idle.phtml index 2ba5237f7..6f3d4a117 100644 --- a/app/views/stats/idle.phtml +++ b/app/views/stats/idle.phtml @@ -1,25 +1,48 @@ partial('aside_stats'); ?> -
+

'stats', 'a' => 'idle'), + 'php', true + )); + $nothing = true; foreach ($this->idleFeeds as $period => $feeds) { if (!empty($feeds)) { + $nothing = false; ?>

-
    - -
  • - + + + +
      +
    • +
      + + + +
      +
    • +
    • + () +
    +
+

+ +

+
diff --git a/app/views/stats/index.phtml b/app/views/stats/index.phtml index a48181fe4..412e77e16 100644 --- a/app/views/stats/index.phtml +++ b/app/views/stats/index.phtml @@ -1,11 +1,11 @@ partial('aside_stats'); ?> -
+
- +

-
+

@@ -38,26 +38,9 @@
-
- -
-

-
-
- -
-

-
-
-
- -
-

-
-
-
- -
+

@@ -70,7 +53,7 @@ topFeed as $feed): ?> - + @@ -78,6 +61,23 @@
+ +
+

+
+
+ +
+

+
+
+
+

+
+
+