diff options
130 files changed, 6781 insertions, 5082 deletions
@@ -1,5 +1,28 @@ # Journal des modifications +## 2014-10-31 FreshRSS 0.9.2 (beta) + +* UI + * New subscription page (introduce .box items) + * Change feed category by drag and drop + * New feed aside on the main page + * New configuration / administration organization +* Configuration + * New options in config.php for cache duration, timeout, max inactivity, max number of feeds and categories per user. +* Refactoring + * Refactor authentication system (introduce FreshRSS_Auth model) + * Refactor indexController (introduce FreshRSS_Context model) + * Use ```_t()```, ```_i()```, ```_url()```, ```Minz_Request::good()``` and ```Minz_Request::bad()``` as much as possible + * Refactor javascript_vars.phtml + * Better coding style +* I18n + * Introduce a new system for i18n keys (not finished yet) +* Misc. + * Fix global view (didn't work anymore) + * Add do_post_update for update system + * Introduce ```checkInstallAction``` to test if FreshRSS installation is ok + + ## 2014-10-09 FreshRSS 0.8.1 / 0.9.1 (beta) * UI diff --git a/README.fr.md b/README.fr.md index 050f49ec6..937db486c 100644 --- a/README.fr.md +++ b/README.fr.md @@ -10,8 +10,8 @@ Il permet de gérer plusieurs utilisateurs, et dispose d’un mode de lecture an * Site officiel : http://freshrss.org * Démo : http://demo.freshrss.org/ * Développeur : Marien Fressinaud <dev@marienfressinaud.fr> -* Version actuelle : x.y.z -* Date de publication : YYYY-MM-DD +* Version actuelle : 0.9.2 +* Date de publication : 2014-10-31 * License [GNU AGPL 3](http://www.gnu.org/licenses/agpl-3.0.html)  @@ -10,8 +10,8 @@ It is a multi-user application with an anonymous reading mode. * Official website: http://freshrss.org * Demo: http://demo.freshrss.org/ * Developer: Marien Fressinaud <dev@marienfressinaud.fr> -* Current version: x.y.z -* Publication date: YYYY-MM-DD +* Current version: 0.9.2 +* Publication date: 2014-10-31 * License [GNU AGPL 3](http://www.gnu.org/licenses/agpl-3.0.html)  diff --git a/app/Controllers/authController.php b/app/Controllers/authController.php new file mode 100644 index 000000000..44496cd3e --- /dev/null +++ b/app/Controllers/authController.php @@ -0,0 +1,349 @@ +<?php + +/** + * This controller handles action about authentication. + */ +class FreshRSS_auth_Controller extends Minz_ActionController { + /** + * This action handles authentication management page. + * + * Parameters are: + * - token (default: current token) + * - anon_access (default: false) + * - anon_refresh (default: false) + * - auth_type (default: none) + * - unsafe_autologin (default: false) + * - api_enabled (default: false) + * + * @todo move unsafe_autologin in an extension. + */ + public function indexAction() { + if (!FreshRSS_Auth::hasAccess('admin')) { + Minz_Error::error(403); + } + + Minz_View::prependTitle(_t('gen.title.authentication') . ' · '); + + if (Minz_Request::isPost()) { + $ok = true; + + $current_token = FreshRSS_Context::$conf->token; + $token = Minz_Request::param('token', $current_token); + FreshRSS_Context::$conf->_token($token); + $ok &= FreshRSS_Context::$conf->save(); + + $anon = Minz_Request::param('anon_access', false); + $anon = ((bool)$anon) && ($anon !== 'no'); + $anon_refresh = Minz_Request::param('anon_refresh', false); + $anon_refresh = ((bool)$anon_refresh) && ($anon_refresh !== 'no'); + $auth_type = Minz_Request::param('auth_type', 'none'); + $unsafe_autologin = Minz_Request::param('unsafe_autologin', false); + $api_enabled = Minz_Request::param('api_enabled', false); + if ($anon != Minz_Configuration::allowAnonymous() || + $auth_type != Minz_Configuration::authType() || + $anon_refresh != Minz_Configuration::allowAnonymousRefresh() || + $unsafe_autologin != Minz_Configuration::unsafeAutologinEnabled() || + $api_enabled != Minz_Configuration::apiEnabled()) { + + Minz_Configuration::_authType($auth_type); + Minz_Configuration::_allowAnonymous($anon); + Minz_Configuration::_allowAnonymousRefresh($anon_refresh); + Minz_Configuration::_enableAutologin($unsafe_autologin); + Minz_Configuration::_enableApi($api_enabled); + $ok &= Minz_Configuration::writeFile(); + } + + invalidateHttpCache(); + + if ($ok) { + Minz_Request::good(_t('configuration_updated'), + array('c' => 'auth', 'a' => 'index')); + } else { + Minz_Request::bad(_t('error_occurred'), + array('c' => 'auth', 'a' => 'index')); + } + } + } + + /** + * This action handles the login page. + * + * It forwards to the correct login page (form or Persona) or main page if + * the user is already connected. + */ + public function loginAction() { + if (FreshRSS_Auth::hasAccess()) { + Minz_Request::forward(array('c' => 'index', 'a' => 'index'), true); + } + + $auth_type = Minz_Configuration::authType(); + switch ($auth_type) { + case 'form': + Minz_Request::forward(array('c' => 'auth', 'a' => 'formLogin')); + break; + case 'persona': + Minz_Request::forward(array('c' => 'auth', 'a' => 'personaLogin')); + break; + case 'http_auth': + case 'none': + // It should not happened! + Minz_Error::error(404); + default: + // TODO load plugin instead + Minz_Error::error(404); + } + } + + /** + * This action handles form login page. + * + * If this action is reached through a POST request, username and password + * are compared to login the current user. + * + * Parameters are: + * - nonce (default: false) + * - username (default: '') + * - challenge (default: '') + * - keep_logged_in (default: false) + * + * @todo move unsafe autologin in an extension. + */ + public function formLoginAction() { + invalidateHttpCache(); + + $file_mtime = @filemtime(PUBLIC_PATH . '/scripts/bcrypt.min.js'); + Minz_View::appendScript(Minz_Url::display('/scripts/bcrypt.min.js?' . $file_mtime)); + + if (Minz_Request::isPost()) { + $nonce = Minz_Session::param('nonce'); + $username = Minz_Request::param('username', ''); + $challenge = Minz_Request::param('challenge', ''); + try { + $conf = new FreshRSS_Configuration($username); + } catch(Minz_Exception $e) { + // $username is not a valid user, nor the configuration file! + Minz_Log::warning('Login failure: ' . $e->getMessage()); + Minz_Request::bad(_t('invalid_login'), + array('c' => 'auth', 'a' => 'login')); + } + + $ok = FreshRSS_FormAuth::checkCredentials( + $username, $conf->passwordHash, $nonce, $challenge + ); + if ($ok) { + // Set session parameter to give access to the user. + Minz_Session::_param('currentUser', $username); + Minz_Session::_param('passwordHash', $conf->passwordHash); + FreshRSS_Auth::giveAccess(); + + // Set cookie parameter if nedded. + if (Minz_Request::param('keep_logged_in')) { + FreshRSS_FormAuth::makeCookie($username, $conf->passwordHash); + } else { + FreshRSS_FormAuth::deleteCookie(); + } + + // All is good, go back to the index. + Minz_Request::good(_t('feedback.login.success'), + array('c' => 'index', 'a' => 'index')); + } else { + Minz_Log::warning('Password mismatch for' . + ' user=' . $username . + ', nonce=' . $nonce . + ', c=' . $challenge); + Minz_Request::bad(_t('invalid_login'), + array('c' => 'auth', 'a' => 'login')); + } + } elseif (Minz_Configuration::unsafeAutologinEnabled()) { + $username = Minz_Request::param('u', ''); + $password = Minz_Request::param('p', ''); + Minz_Request::_param('p'); + + if (!$username) { + return; + } + + try { + $conf = new FreshRSS_Configuration($username); + } catch(Minz_Exception $e) { + // $username is not a valid user, nor the configuration file! + Minz_Log::warning('Login failure: ' . $e->getMessage()); + return; + } + + if (!function_exists('password_verify')) { + include_once(LIB_PATH . '/password_compat.php'); + } + + $s = $conf->passwordHash; + $ok = password_verify($password, $s); + unset($password); + if ($ok) { + Minz_Session::_param('currentUser', $username); + Minz_Session::_param('passwordHash', $s); + FreshRSS_Auth::giveAccess(); + + Minz_Request::good(_t('feedback.login.success'), + array('c' => 'index', 'a' => 'index')); + } else { + Minz_Log::warning('Unsafe password mismatch for user ' . $username); + Minz_Request::bad(_t('invalid_login'), + array('c' => 'auth', 'a' => 'login')); + } + } + } + + /** + * This action handles Persona login page. + * + * If this action is reached through a POST request, assertion from Persona + * is verificated and user connected if all is ok. + * + * Parameter is: + * - assertion (default: false) + * + * @todo: Persona system should be moved to a plugin + */ + public function personaLoginAction() { + $this->view->res = false; + + if (Minz_Request::isPost()) { + $this->view->_useLayout(false); + + $assert = Minz_Request::param('assertion'); + $url = 'https://verifier.login.persona.org/verify'; + $params = 'assertion=' . $assert . '&audience=' . + urlencode(Minz_Url::display(null, 'php', true)); + $ch = curl_init(); + $options = array( + CURLOPT_URL => $url, + CURLOPT_RETURNTRANSFER => TRUE, + CURLOPT_POST => 2, + CURLOPT_POSTFIELDS => $params + ); + curl_setopt_array($ch, $options); + $result = curl_exec($ch); + curl_close($ch); + + $res = json_decode($result, true); + + $login_ok = false; + $reason = ''; + if ($res['status'] === 'okay') { + $email = filter_var($res['email'], FILTER_VALIDATE_EMAIL); + if ($email != '') { + $persona_file = DATA_PATH . '/persona/' . $email . '.txt'; + if (($current_user = @file_get_contents($persona_file)) !== false) { + $current_user = trim($current_user); + try { + $conf = new FreshRSS_Configuration($current_user); + $login_ok = strcasecmp($email, $conf->mail_login) === 0; + } catch (Minz_Exception $e) { + //Permission denied or conf file does not exist + $reason = 'Invalid configuration for user ' . + '[' . $current_user . '] ' . $e->getMessage(); + } + } + } else { + $reason = 'Invalid email format [' . $res['email'] . ']'; + } + } else { + $reason = $res['reason']; + } + + if ($login_ok) { + Minz_Session::_param('currentUser', $current_user); + Minz_Session::_param('mail', $email); + FreshRSS_Auth::giveAccess(); + invalidateHttpCache(); + } else { + Minz_Log::error($reason); + + $res = array(); + $res['status'] = 'failure'; + $res['reason'] = _t('invalid_login'); + } + + header('Content-Type: application/json; charset=UTF-8'); + $this->view->res = $res; + } + } + + /** + * This action removes all accesses of the current user. + */ + public function logoutAction() { + invalidateHttpCache(); + FreshRSS_Auth::removeAccess(); + Minz_Request::good(_t('feedback.logout.success'), + array('c' => 'index', 'a' => 'index')); + } + + /** + * This action resets the authentication system. + * + * After reseting, form auth is set by default. + */ + public function resetAction() { + 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', ''); + $challenge = Minz_Request::param('challenge', ''); + + $ok = FreshRSS_FormAuth::checkCredentials( + $username, $conf->passwordHash, $nonce, $challenge + ); + + 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' => 'auth', 'a' => 'reset')); + } + } else { + Minz_Log::warning('Password mismatch for' . + ' user=' . $username . + ', nonce=' . $nonce . + ', c=' . $challenge); + Minz_Request::bad(_t('invalid_login'), + array('c' => 'auth', 'a' => 'reset')); + } + } + } +} diff --git a/app/Controllers/categoryController.php b/app/Controllers/categoryController.php new file mode 100644 index 000000000..50b1d841a --- /dev/null +++ b/app/Controllers/categoryController.php @@ -0,0 +1,192 @@ +<?php + +/** + * Controller to handle actions relative to categories. + * User needs to be connected. + */ +class FreshRSS_category_Controller extends Minz_ActionController { + /** + * 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 (!FreshRSS_Auth::hasAccess()) { + Minz_Error::error(403); + } + + $catDAO = new FreshRSS_CategoryDAO(); + $catDAO->checkDefault(); + } + + /** + * This action creates a new category. + * + * Request parameter is: + * - new-category + */ + public function createAction() { + $catDAO = new FreshRSS_CategoryDAO(); + $url_redirect = array('c' => 'subscription', 'a' => 'index'); + + $limits = Minz_Configuration::limits(); + $this->view->categories = $catDAO->listCategories(false); + + if (count($this->view->categories) >= $limits['max_categories']) { + Minz_Request::bad(_t('sub.categories.over_max', $limits['max_categories']), + $url_redirect); + } + + if (Minz_Request::isPost()) { + invalidateHttpCache(); + + $cat_name = Minz_Request::param('new-category'); + if (!$cat_name) { + Minz_Request::bad(_t('category_no_name'), $url_redirect); + } + + $cat = new FreshRSS_Category($cat_name); + + if ($catDAO->searchByName($cat->name()) != null) { + Minz_Request::bad(_t('category_name_exists'), $url_redirect); + } + + $values = array( + 'id' => $cat->id(), + 'name' => $cat->name(), + ); + + if ($catDAO->addCategory($values)) { + Minz_Request::good(_t('category_created', $cat->name()), $url_redirect); + } else { + Minz_Request::bad(_t('error_occurred'), $url_redirect); + } + } + + Minz_Request::forward($url_redirect, true); + } + + /** + * This action updates the given category. + * + * Request parameters are: + * - id + * - name + */ + public function updateAction() { + $catDAO = new FreshRSS_CategoryDAO(); + $url_redirect = array('c' => 'subscription', 'a' => 'index'); + + if (Minz_Request::isPost()) { + invalidateHttpCache(); + + $id = Minz_Request::param('id'); + $name = Minz_Request::param('name', ''); + if (strlen($name) <= 0) { + Minz_Request::bad(_t('category_no_name'), $url_redirect); + } + + if ($catDAO->searchById($id) == null) { + Minz_Request::bad(_t('category_not_exist'), $url_redirect); + } + + $cat = new FreshRSS_Category($name); + $values = array( + 'name' => $cat->name(), + ); + + if ($catDAO->updateCategory($id, $values)) { + Minz_Request::good(_t('category_updated'), $url_redirect); + } else { + Minz_Request::bad(_t('error_occurred'), $url_redirect); + } + } + + Minz_Request::forward($url_redirect, true); + } + + /** + * This action deletes a category. + * Feeds in the given category are moved in the default category. + * Related user queries are deleted too. + * + * Request parameter is: + * - id (of a category) + */ + public function deleteAction() { + $feedDAO = FreshRSS_Factory::createFeedDao(); + $catDAO = new FreshRSS_CategoryDAO(); + $default_category = $catDAO->getDefault(); + $url_redirect = array('c' => 'subscription', 'a' => 'index'); + + if (Minz_Request::isPost()) { + invalidateHttpCache(); + + $id = Minz_Request::param('id'); + if (!$id) { + Minz_Request::bad(_t('category_no_id'), $url_redirect); + } + + if ($id === $default_category->id()) { + Minz_Request::bad(_t('category_not_delete_default'), $url_redirect); + } + + if ($feedDAO->changeCategory($id, $default_category->id()) === false) { + Minz_Request::bad(_t('error_occurred'), $url_redirect); + } + + if ($catDAO->deleteCategory($id) === false) { + Minz_Request::bad(_t('error_occurred'), $url_redirect); + } + + // Remove related queries. + FreshRSS_Context::$conf->remove_query_by_get('c_' . $id); + FreshRSS_Context::$conf->save(); + + Minz_Request::good(_t('category_deleted'), $url_redirect); + } + + Minz_Request::forward($url_redirect, true); + } + + /** + * This action deletes all the feeds relative to a given category. + * Feed-related queries are deleted. + * + * Request parameter is: + * - id (of a category) + */ + public function emptyAction() { + $feedDAO = FreshRSS_Factory::createFeedDao(); + $url_redirect = array('c' => 'subscription', 'a' => 'index'); + + if (Minz_Request::isPost()) { + invalidateHttpCache(); + + $id = Minz_Request::param('id'); + if (!$id) { + Minz_Request::bad(_t('category_no_id'), $url_redirect); + } + + // List feeds to remove then related user queries. + $feeds = $feedDAO->listByCategory($id); + + if ($feedDAO->deleteFeedByCategory($id)) { + // TODO: Delete old favicons + + // Remove related queries + foreach ($feeds as $feed) { + FreshRSS_Context::$conf->remove_query_by_get('f_' . $feed->id()); + } + FreshRSS_Context::$conf->save(); + + Minz_Request::good(_t('category_emptied'), $url_redirect); + } else { + Minz_Request::bad(_t('error_occurred'), $url_redirect); + } + } + + Minz_Request::forward($url_redirect, true); + } +} diff --git a/app/Controllers/configureController.php b/app/Controllers/configureController.php index 231865bd7..1c8ac9111 100755 --- a/app/Controllers/configureController.php +++ b/app/Controllers/configureController.php @@ -8,175 +8,10 @@ class FreshRSS_configure_Controller extends Minz_ActionController { * 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. - * - * @todo see if the category default configuration is needed here or if - * we can move it to the categorize action */ public function firstAction() { - if (!$this->view->loginOk) { - Minz_Error::error( - 403, - array('error' => array(_t('access_denied'))) - ); - } - - $catDAO = new FreshRSS_CategoryDAO(); - $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(); - $defaultCategory = $catDAO->getDefault(); - $defaultId = $defaultCategory->id(); - - if (Minz_Request::isPost()) { - $cats = Minz_Request::param('categories', array()); - $ids = Minz_Request::param('ids', array()); - $newCat = trim(Minz_Request::param('new_category', '')); - - foreach ($cats as $key => $name) { - if (strlen($name) > 0) { - $cat = new FreshRSS_Category($name); - $values = array( - 'name' => $cat->name(), - ); - $catDAO->updateCategory($ids[$key], $values); - } 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(); - } - } - - if ($newCat != '') { - $cat = new FreshRSS_Category($newCat); - $values = array( - 'id' => $cat->id(), - 'name' => $cat->name(), - ); - - if ($catDAO->searchByName($newCat) == null) { - $catDAO->addCategory($values); - } - } - invalidateHttpCache(); - - 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(_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); - - $feedDAO = FreshRSS_Factory::createFeedDao(); - $this->view->feeds = $feedDAO->listFeeds(); - - $id = Minz_Request::param('id'); - if ($id == false && !empty($this->view->feeds)) { - $id = current($this->view->feeds)->id(); - } - - $this->view->flux = false; - if ($id != false) { - $this->view->flux = $this->view->feeds[$id]; - - if (!$this->view->flux) { - Minz_Error::error( - 404, - array('error' => array(_t('page_not_found'))) - ); - } else { - if (Minz_Request::isPost() && $this->view->flux) { - $user = Minz_Request::param('http_user', ''); - $pass = Minz_Request::param('http_pass', ''); - - $httpAuth = ''; - if ($user != '' || $pass != '') { - $httpAuth = $user . ':' . $pass; - } - - $cat = intval(Minz_Request::param('category', 0)); - - $values = array( - 'name' => Minz_Request::param('name', ''), - 'description' => sanitizeHTML(Minz_Request::param('description', '', true)), - 'website' => Minz_Request::param('website', ''), - 'url' => Minz_Request::param('url', ''), - 'category' => $cat, - 'pathEntries' => Minz_Request::param('path_entries', ''), - 'priority' => intval(Minz_Request::param('priority', 0)), - 'httpAuth' => $httpAuth, - 'keep_history' => intval(Minz_Request::param('keep_history', -2)), - 'ttl' => intval(Minz_Request::param('ttl', -2)), - ); - - if ($feedDAO->updateFeed($id, $values)) { - $this->view->flux->_category($cat); - $this->view->flux->faviconPrepare(); - $notif = array( - 'type' => 'good', - 'content' => _t('feed_updated') - ); - } else { - $notif = array( - 'type' => 'bad', - 'content' => _t('error_occurred_update') - ); - } - invalidateHttpCache(); - - Minz_Session::_param('notification', $notif); - Minz_Request::forward(array('c' => 'configure', 'a' => 'feed', 'params' => array('id' => $id)), true); - } - - Minz_View::prependTitle(_t('rss_feed_management') . ' — ' . $this->view->flux->name() . ' · '); - } - } else { - Minz_View::prependTitle(_t('rss_feed_management') . ' · '); + if (!FreshRSS_Auth::hasAccess()) { + Minz_Error::error(403); } } @@ -206,23 +41,23 @@ class FreshRSS_configure_Controller extends Minz_ActionController { */ public function displayAction() { if (Minz_Request::isPost()) { - $this->view->conf->_language(Minz_Request::param('language', 'en')); - $this->view->conf->_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)); - $this->view->conf->_topline_date(Minz_Request::param('topline_date', false)); - $this->view->conf->_topline_link(Minz_Request::param('topline_link', false)); - $this->view->conf->_bottomline_read(Minz_Request::param('bottomline_read', false)); - $this->view->conf->_bottomline_favorite(Minz_Request::param('bottomline_favorite', false)); - $this->view->conf->_bottomline_sharing(Minz_Request::param('bottomline_sharing', false)); - $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); + FreshRSS_Context::$conf->_language(Minz_Request::param('language', 'en')); + FreshRSS_Context::$conf->_theme(Minz_Request::param('theme', FreshRSS_Themes::$defaultTheme)); + FreshRSS_Context::$conf->_content_width(Minz_Request::param('content_width', 'thin')); + FreshRSS_Context::$conf->_topline_read(Minz_Request::param('topline_read', false)); + FreshRSS_Context::$conf->_topline_favorite(Minz_Request::param('topline_favorite', false)); + FreshRSS_Context::$conf->_topline_date(Minz_Request::param('topline_date', false)); + FreshRSS_Context::$conf->_topline_link(Minz_Request::param('topline_link', false)); + FreshRSS_Context::$conf->_bottomline_read(Minz_Request::param('bottomline_read', false)); + FreshRSS_Context::$conf->_bottomline_favorite(Minz_Request::param('bottomline_favorite', false)); + FreshRSS_Context::$conf->_bottomline_sharing(Minz_Request::param('bottomline_sharing', false)); + FreshRSS_Context::$conf->_bottomline_tags(Minz_Request::param('bottomline_tags', false)); + FreshRSS_Context::$conf->_bottomline_date(Minz_Request::param('bottomline_date', false)); + FreshRSS_Context::$conf->_bottomline_link(Minz_Request::param('bottomline_link', false)); + FreshRSS_Context::$conf->_html5_notif_timeout(Minz_Request::param('html5_notif_timeout', 0)); + FreshRSS_Context::$conf->save(); + + Minz_Session::_param('language', FreshRSS_Context::$conf->language); Minz_Translate::reset(); invalidateHttpCache(); @@ -264,27 +99,27 @@ class FreshRSS_configure_Controller extends Minz_ActionController { */ public function readingAction() { if (Minz_Request::isPost()) { - $this->view->conf->_posts_per_page(Minz_Request::param('posts_per_page', 10)); - $this->view->conf->_view_mode(Minz_Request::param('view_mode', 'normal')); - $this->view->conf->_default_view((int)Minz_Request::param('default_view', FreshRSS_Entry::STATE_ALL)); - $this->view->conf->_auto_load_more(Minz_Request::param('auto_load_more', false)); - $this->view->conf->_display_posts(Minz_Request::param('display_posts', false)); - $this->view->conf->_display_categories(Minz_Request::param('display_categories', false)); - $this->view->conf->_hide_read_feeds(Minz_Request::param('hide_read_feeds', false)); - $this->view->conf->_onread_jump_next(Minz_Request::param('onread_jump_next', false)); - $this->view->conf->_lazyload(Minz_Request::param('lazyload', false)); - $this->view->conf->_sticky_post(Minz_Request::param('sticky_post', false)); - $this->view->conf->_reading_confirm(Minz_Request::param('reading_confirm', false)); - $this->view->conf->_sort_order(Minz_Request::param('sort_order', 'DESC')); - $this->view->conf->_mark_when(array( + FreshRSS_Context::$conf->_posts_per_page(Minz_Request::param('posts_per_page', 10)); + FreshRSS_Context::$conf->_view_mode(Minz_Request::param('view_mode', 'normal')); + FreshRSS_Context::$conf->_default_view(Minz_Request::param('default_view', 'adaptive')); + FreshRSS_Context::$conf->_auto_load_more(Minz_Request::param('auto_load_more', false)); + FreshRSS_Context::$conf->_display_posts(Minz_Request::param('display_posts', false)); + FreshRSS_Context::$conf->_display_categories(Minz_Request::param('display_categories', false)); + FreshRSS_Context::$conf->_hide_read_feeds(Minz_Request::param('hide_read_feeds', false)); + FreshRSS_Context::$conf->_onread_jump_next(Minz_Request::param('onread_jump_next', false)); + FreshRSS_Context::$conf->_lazyload(Minz_Request::param('lazyload', false)); + FreshRSS_Context::$conf->_sticky_post(Minz_Request::param('sticky_post', false)); + FreshRSS_Context::$conf->_reading_confirm(Minz_Request::param('reading_confirm', false)); + FreshRSS_Context::$conf->_sort_order(Minz_Request::param('sort_order', 'DESC')); + FreshRSS_Context::$conf->_mark_when(array( 'article' => Minz_Request::param('mark_open_article', false), 'site' => Minz_Request::param('mark_open_site', false), 'scroll' => Minz_Request::param('mark_scroll', false), 'reception' => Minz_Request::param('mark_upon_reception', false), )); - $this->view->conf->save(); + FreshRSS_Context::$conf->save(); - Minz_Session::_param('language', $this->view->conf->language); + Minz_Session::_param('language', FreshRSS_Context::$conf->language); Minz_Translate::reset(); invalidateHttpCache(); @@ -305,8 +140,8 @@ class FreshRSS_configure_Controller extends Minz_ActionController { public function sharingAction() { if (Minz_Request::isPost()) { $params = Minz_Request::params(); - $this->view->conf->_sharing($params['share']); - $this->view->conf->save(); + FreshRSS_Context::$conf->_sharing($params['share']); + FreshRSS_Context::$conf->save(); invalidateHttpCache(); Minz_Request::good(_t('configuration_updated'), @@ -330,11 +165,11 @@ class FreshRSS_configure_Controller extends Minz_ActionController { */ 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', 'f1', 'f2', 'f3', 'f4', 'f5', 'f6', 'f7', 'f8', 'f9', - 'f10', 'f11', 'f12'); + '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', 'f1', 'f2', 'f3', 'f4', 'f5', 'f6', 'f7', 'f8', 'f9', + 'f10', 'f11', 'f12'); $this->view->list_keys = $list_keys; if (Minz_Request::isPost()) { @@ -347,8 +182,8 @@ class FreshRSS_configure_Controller extends Minz_ActionController { } } - $this->view->conf->_shortcuts($shortcuts_ok); - $this->view->conf->save(); + FreshRSS_Context::$conf->_shortcuts($shortcuts_ok); + FreshRSS_Context::$conf->save(); invalidateHttpCache(); Minz_Request::good(_t('shortcuts_updated'), @@ -359,15 +194,6 @@ class FreshRSS_configure_Controller extends Minz_ActionController { } /** - * This action display the user configuration page - * - * @todo move that action in the user controller - */ - public function usersAction() { - Minz_View::prependTitle(_t('users') . ' · '); - } - - /** * This action handles the archive configuration page. * * It displays the archive configuration page. @@ -384,10 +210,10 @@ class FreshRSS_configure_Controller extends Minz_ActionController { */ public function archivingAction() { if (Minz_Request::isPost()) { - $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(); + FreshRSS_Context::$conf->_old_entries(Minz_Request::param('old_entries', 3)); + FreshRSS_Context::$conf->_keep_history_default(Minz_Request::param('keep_history_default', 0)); + FreshRSS_Context::$conf->_ttl_default(Minz_Request::param('ttl_default', -2)); + FreshRSS_Context::$conf->save(); invalidateHttpCache(); Minz_Request::good(_t('configuration_updated'), @@ -396,11 +222,11 @@ class FreshRSS_configure_Controller extends Minz_ActionController { Minz_View::prependTitle(_t('archiving_configuration') . ' · '); - $entryDAO = FreshRSS_Factory::createEntryDao(); + $entryDAO = FreshRSS_Factory::createEntryDao('freshrss'); $this->view->nb_total = $entryDAO->count(); $this->view->size_user = $entryDAO->size(); - if (Minz_Configuration::isAdmin(Minz_Session::param('currentUser', '_'))) { + if (FreshRSS_Auth::hasAccess('admin')) { $this->view->size_total = $entryDAO->size(true); } } @@ -424,8 +250,8 @@ class FreshRSS_configure_Controller extends Minz_ActionController { $query['name'] = _t('query_number', $key + 1); } } - $this->view->conf->_queries($queries); - $this->view->conf->save(); + FreshRSS_Context::$conf->_queries($queries); + FreshRSS_Context::$conf->save(); Minz_Request::good(_t('configuration_updated'), array('c' => 'configure', 'a' => 'queries')); @@ -433,7 +259,7 @@ class FreshRSS_configure_Controller extends Minz_ActionController { $this->view->query_get = array(); $cat_dao = new FreshRSS_CategoryDAO(); $feed_dao = FreshRSS_Factory::createFeedDao(); - foreach ($this->view->conf->queries as $key => $query) { + foreach (FreshRSS_Context::$conf->queries as $key => $query) { if (!isset($query['get'])) { continue; } @@ -501,7 +327,7 @@ class FreshRSS_configure_Controller extends Minz_ActionController { */ public function addQueryAction() { $whitelist = array('get', 'order', 'name', 'search', 'state'); - $queries = $this->view->conf->queries; + $queries = FreshRSS_Context::$conf->queries; $query = Minz_Request::params(); $query['name'] = _t('query_number', count($queries) + 1); foreach ($query as $key => $value) { @@ -509,12 +335,9 @@ class FreshRSS_configure_Controller extends Minz_ActionController { 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(); + FreshRSS_Context::$conf->_queries($queries); + FreshRSS_Context::$conf->save(); 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 ab66d9198..b4beed619 100755 --- a/app/Controllers/entryController.php +++ b/app/Controllers/entryController.php @@ -1,150 +1,183 @@ <?php +/** + * Controller to handle every entry actions. + */ class FreshRSS_entry_Controller extends Minz_ActionController { - public function firstAction () { - if (!$this->view->loginOk) { - Minz_Error::error ( - 403, - array ('error' => array (Minz_Translate::t ('access_denied'))) - ); + /** + * 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 (!FreshRSS_Auth::hasAccess()) { + Minz_Error::error(403); } - $this->params = array (); - $output = Minz_Request::param('output', ''); - if (($output != '') && ($this->view->conf->view_mode !== $output)) { - $this->params['output'] = $output; - } - - $this->redirect = false; - $ajax = Minz_Request::param ('ajax'); - if ($ajax) { - $this->view->_useLayout (false); - } - } - - public function lastAction () { - $ajax = Minz_Request::param ('ajax'); - if (!$ajax && $this->redirect) { - Minz_Request::forward (array ( - 'c' => 'index', - 'a' => 'index', - 'params' => $this->params - ), true); - } else { - Minz_Request::_param ('ajax'); + // If ajax request, we do not print layout + $this->ajax = Minz_Request::param('ajax'); + if ($this->ajax) { + $this->view->_useLayout(false); + Minz_Request::_param('ajax'); } } - public function readAction () { - $this->redirect = true; - - $id = Minz_Request::param ('id'); - $get = Minz_Request::param ('get'); - $nextGet = Minz_Request::param ('nextGet', $get); - $idMax = Minz_Request::param ('idMax', 0); + /** + * Mark one or several entries as read (or not!). + * + * If request concerns several entries, it MUST be a POST request. + * If request concerns several entries, only mark them as read is available. + * + * Parameters are: + * - id (default: false) + * - get (default: false) /(c_\d+|f_\d+|s|a)/ + * - nextGet (default: $get) + * - idMax (default: 0) + * - is_read (default: true) + * + * @todo nextGet system should not be present here... or should be? + */ + public function readAction() { + $id = Minz_Request::param('id'); + $get = Minz_Request::param('get'); + $next_get = Minz_Request::param('nextGet', $get); + $id_max = Minz_Request::param('idMax', 0); + $params = array(); $entryDAO = FreshRSS_Factory::createEntryDao(); - if ($id == false) { + if ($id === false) { + // id is false? It MUST be a POST request! if (!Minz_Request::isPost()) { return; } if (!$get) { - $entryDAO->markReadEntries ($idMax); + // No get? Mark all entries as read (from $id_max) + $entryDAO->markReadEntries($id_max); } else { - $typeGet = $get[0]; - $get = substr ($get, 2); - switch ($typeGet) { - case 'c': - $entryDAO->markReadCat ($get, $idMax); - break; - case 'f': - $entryDAO->markReadFeed ($get, $idMax); - break; - case 's': - $entryDAO->markReadEntries ($idMax, true); - break; - case 'a': - $entryDAO->markReadEntries ($idMax); - break; + $type_get = $get[0]; + $get = substr($get, 2); + switch($type_get) { + case 'c': + $entryDAO->markReadCat($get, $id_max); + break; + case 'f': + $entryDAO->markReadFeed($get, $id_max); + break; + case 's': + $entryDAO->markReadEntries($id_max, true); + break; + case 'a': + $entryDAO->markReadEntries($id_max); + break; } - if ($nextGet !== 'a') { - $this->params['get'] = $nextGet; + + if ($next_get !== 'a') { + // Redirect to the correct page (category, feed or starred) + // Not "a" because it is the default value if nothing is + // given. + $params['get'] = $next_get; } } - - $notif = array ( - 'type' => 'good', - 'content' => Minz_Translate::t ('feeds_marked_read') - ); - Minz_Session::_param ('notification', $notif); } else { - $is_read = (bool)(Minz_Request::param ('is_read', true)); - $entryDAO->markRead ($id, $is_read); + $is_read = (bool)(Minz_Request::param('is_read', true)); + $entryDAO->markRead($id, $is_read); } - } - public function bookmarkAction () { - $this->redirect = true; + if (!$this->ajax) { + Minz_Request::good(_t('feeds_marked_read'), array( + 'c' => 'index', + 'a' => 'index', + 'params' => $params, + ), true); + } + } - $id = Minz_Request::param ('id'); - if ($id) { + /** + * This action marks an entry as favourite (bookmark) or not. + * + * Parameter is: + * - id (default: false) + * - is_favorite (default: true) + * If id is false, nothing happened. + */ + public function bookmarkAction() { + $id = Minz_Request::param('id'); + $is_favourite = (bool)Minz_Request::param('is_favorite', true); + if ($id !== false) { $entryDAO = FreshRSS_Factory::createEntryDao(); - $entryDAO->markFavorite ($id, (bool)(Minz_Request::param ('is_favorite', true))); + $entryDAO->markFavorite($id, $is_favourite); + } + + if (!$this->ajax) { + Minz_Request::forward(array( + 'c' => 'index', + 'a' => 'index', + ), true); } } + /** + * This action optimizes database to reduce its size. + * + * This action shouldbe reached by a POST request. + * + * @todo move this action in configure controller. + * @todo call this action through web-cron when available + */ public function optimizeAction() { - if (Minz_Request::isPost()) { - @set_time_limit(300); + $url_redirect = array( + 'c' => 'configure', + 'a' => 'archiving', + ); - // La table des entrées a tendance à grossir énormément - // Cette action permet d'optimiser cette table permettant de grapiller un peu de place - // Cette fonctionnalité n'est à appeler qu'occasionnellement - $entryDAO = FreshRSS_Factory::createEntryDao(); - $entryDAO->optimizeTable(); + if (!Minz_Request::isPost()) { + Minz_Request::forward($url_redirect, true); + } - $feedDAO = FreshRSS_Factory::createFeedDao(); - $feedDAO->updateCachedValues(); + @set_time_limit(300); - invalidateHttpCache(); + $entryDAO = FreshRSS_Factory::createEntryDao(); + $entryDAO->optimizeTable(); - $notif = array ( - 'type' => 'good', - 'content' => Minz_Translate::t ('optimization_complete') - ); - Minz_Session::_param ('notification', $notif); - } + $feedDAO = FreshRSS_Factory::createFeedDao(); + $feedDAO->updateCachedValues(); - Minz_Request::forward(array( - 'c' => 'configure', - 'a' => 'archiving' - ), true); + invalidateHttpCache(); + Minz_Request::good(_t('optimization_complete'), $url_redirect); } + /** + * This action purges old entries from feeds. + * + * @todo should be a POST request + * @todo should be in feedController + */ public function purgeAction() { @set_time_limit(300); - $nb_month_old = max($this->view->conf->old_entries, 1); + $nb_month_old = max(FreshRSS_Context::$conf->old_entries, 1); $date_min = time() - (3600 * 24 * 30 * $nb_month_old); $feedDAO = FreshRSS_Factory::createFeedDao(); $feeds = $feedDAO->listFeeds(); - $nbTotal = 0; + $nb_total = 0; invalidateHttpCache(); foreach ($feeds as $feed) { - $feedHistory = $feed->keepHistory(); - if ($feedHistory == -2) { //default - $feedHistory = $this->view->conf->keep_history_default; + $feed_history = $feed->keepHistory(); + if ($feed_history == -2) { + // TODO: -2 must be a constant! + // -2 means we take the default value from configuration + $feed_history = FreshRSS_Context::$conf->keep_history_default; } - if ($feedHistory >= 0) { - $nb = $feedDAO->cleanOldEntries($feed->id(), $date_min, $feedHistory); + + if ($feed_history >= 0) { + $nb = $feedDAO->cleanOldEntries($feed->id(), $date_min, $feed_history); if ($nb > 0) { - $nbTotal += $nb; - Minz_Log::record($nb . ' old entries cleaned in feed [' . $feed->url() . ']', Minz_Log::DEBUG); - //$feedDAO->updateLastUpdate($feed->id()); + $nb_total += $nb; + Minz_Log::debug($nb . ' old entries cleaned in feed [' . $feed->url() . ']'); } } } @@ -152,16 +185,9 @@ class FreshRSS_entry_Controller extends Minz_ActionController { $feedDAO->updateCachedValues(); invalidateHttpCache(); - - $notif = array( - 'type' => 'good', - 'content' => Minz_Translate::t('purge_completed', $nbTotal) - ); - Minz_Session::_param('notification', $notif); - - Minz_Request::forward(array( + Minz_Request::good(_t('purge_completed', $nb_total), array( 'c' => 'configure', 'a' => 'archiving' - ), true); + )); } } diff --git a/app/Controllers/errorController.php b/app/Controllers/errorController.php index 922650b3d..6c080bea8 100644 --- a/app/Controllers/errorController.php +++ b/app/Controllers/errorController.php @@ -1,35 +1,48 @@ <?php +/** + * Controller to handle error page. + */ class FreshRSS_error_Controller extends Minz_ActionController { + /** + * This action is the default one for the controller. + * + * It is called by Minz_Error::error() method. + * + * Parameters are: + * - code (default: 404) + * - logs (default: array()) + */ public function indexAction() { - switch (Minz_Request::param('code')) { - case 403: - $this->view->code = 'Error 403 - Forbidden'; - break; - case 404: - $this->view->code = 'Error 404 - Not found'; - break; - case 500: - $this->view->code = 'Error 500 - Internal Server Error'; - break; - case 503: - $this->view->code = 'Error 503 - Service Unavailable'; - break; - default: - $this->view->code = 'Error 404 - Not found'; + $code_int = Minz_Request::param('code', 404); + switch ($code_int) { + case 403: + $this->view->code = 'Error 403 - Forbidden'; + break; + case 404: + $this->view->code = 'Error 404 - Not found'; + break; + case 500: + $this->view->code = 'Error 500 - Internal Server Error'; + break; + case 503: + $this->view->code = 'Error 503 - Service Unavailable'; + break; + default: + $this->view->code = 'Error 404 - Not found'; } $errors = Minz_Request::param('logs', array()); $this->view->errorMessage = trim(implode($errors)); if ($this->view->errorMessage == '') { - switch(Minz_Request::param('code')) { - case 403: - $this->view->errorMessage = Minz_Translate::t('forbidden_access'); - break; - case 404: - default: - $this->view->errorMessage = Minz_Translate::t('page_not_found'); - break; + switch($code_int) { + case 403: + $this->view->errorMessage = _t('access_denied'); + break; + case 404: + default: + $this->view->errorMessage = _t('page_not_found'); + break; } } diff --git a/app/Controllers/feedController.php b/app/Controllers/feedController.php index f75c969d9..9990a852c 100755 --- a/app/Controllers/feedController.php +++ b/app/Controllers/feedController.php @@ -1,180 +1,199 @@ <?php +/** + * Controller to handle every feed actions. + */ class FreshRSS_feed_Controller extends Minz_ActionController { - public function firstAction () { - if (!$this->view->loginOk) { + /** + * 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 (!FreshRSS_Auth::hasAccess()) { // Token is useful in the case that anonymous refresh is forbidden // and CRON task cannot be used with php command so the user can // set a CRON task to refresh his feeds by using token inside url - $token = $this->view->conf->token; - $token_param = Minz_Request::param ('token', ''); + $token = FreshRSS_Context::$conf->token; + $token_param = Minz_Request::param('token', ''); $token_is_ok = ($token != '' && $token == $token_param); - $action = Minz_Request::actionName (); - if (!(($token_is_ok || Minz_Configuration::allowAnonymousRefresh()) && - $action === 'actualize') - ) { - Minz_Error::error ( - 403, - array ('error' => array (Minz_Translate::t ('access_denied'))) - ); + $action = Minz_Request::actionName(); + if ($action !== 'actualize' || + !(Minz_Configuration::allowAnonymousRefresh() || $token_is_ok)) { + Minz_Error::error(403); } } } - public function addAction () { - $url = Minz_Request::param('url_rss', false); + /** + * This action subscribes to a feed. + * + * It can be reached by both GET and POST requests. + * + * GET request displays a form to add and configure a feed. + * Request parameter is: + * - url_rss (default: false) + * + * POST request adds a feed in database. + * Parameters are: + * - url_rss (default: false) + * - category (default: false) + * - new_category (required if category == 'nc') + * - http_user (default: false) + * - http_pass (default: false) + * It tries to get website information from RSS feed. + * If no category is given, feed is added to the default one. + * + * If url_rss is false, nothing happened. + */ + public function addAction() { + $url = Minz_Request::param('url_rss'); if ($url === false) { + // No url, do nothing Minz_Request::forward(array( - 'c' => 'configure', - 'a' => 'feed' + 'c' => 'subscription', + 'a' => 'index' ), true); } $feedDAO = FreshRSS_Factory::createFeedDao(); - $this->catDAO = new FreshRSS_CategoryDAO (); - $this->catDAO->checkDefault (); + $this->catDAO = new FreshRSS_CategoryDAO(); + $url_redirect = array( + 'c' => 'subscription', + 'a' => 'index', + 'params' => array(), + ); + + $limits = Minz_Configuration::limits(); + $this->view->feeds = $feedDAO->listFeeds(); + if (count($this->view->feeds) >= $limits['max_feeds']) { + Minz_Request::bad(_t('sub.feeds.over_max', $limits['max_feeds']), + $url_redirect); + } if (Minz_Request::isPost()) { @set_time_limit(300); - - $cat = Minz_Request::param ('category', false); + $cat = Minz_Request::param('category'); if ($cat === 'nc') { - $new_cat = Minz_Request::param ('new_category'); + // User want to create a new category, new_category parameter + // must exist + $new_cat = Minz_Request::param('new_category'); if (empty($new_cat['name'])) { $cat = false; } else { $cat = $this->catDAO->addCategory($new_cat); } } + if ($cat === false) { - $def_cat = $this->catDAO->getDefault (); - $cat = $def_cat->id (); + // If category was not given or if creating new category failed, + // get the default category + $this->catDAO->checkDefault(); + $def_cat = $this->catDAO->getDefault(); + $cat = $def_cat->id(); } - $user = Minz_Request::param ('http_user'); - $pass = Minz_Request::param ('http_pass'); - $params = array (); + // HTTP information are useful if feed is protected behind a + // HTTP authentication + $user = Minz_Request::param('http_user'); + $pass = Minz_Request::param('http_pass'); + $http_auth = ''; + if ($user != '' || $pass != '') { + $http_auth = $user . ':' . $pass; + } - $transactionStarted = false; + $transaction_started = false; try { - $feed = new FreshRSS_Feed ($url); - $feed->_category ($cat); - - $httpAuth = ''; - if ($user != '' || $pass != '') { - $httpAuth = $user . ':' . $pass; - } - $feed->_httpAuth ($httpAuth); + $feed = new FreshRSS_Feed($url); + } catch (FreshRSS_BadUrl_Exception $e) { + // Given url was not a valid url! + Minz_Log::warning($e->getMessage()); + Minz_Request::bad(_t('invalid_url', $url), $url_redirect); + } + try { $feed->load(true); - - $values = array ( - 'url' => $feed->url (), - 'category' => $feed->category (), - 'name' => $feed->name (), - 'website' => $feed->website (), - 'description' => $feed->description (), - 'lastUpdate' => time (), - 'httpAuth' => $feed->httpAuth (), - ); - - if ($feedDAO->searchByUrl ($values['url'])) { - // on est déjà abonné à ce flux - $notif = array ( - 'type' => 'bad', - 'content' => Minz_Translate::t ('already_subscribed', $feed->name ()) - ); - Minz_Session::_param ('notification', $notif); - } else { - $id = $feedDAO->addFeed ($values); - if (!$id) { - // problème au niveau de la base de données - $notif = array ( - 'type' => 'bad', - 'content' => Minz_Translate::t ('feed_not_added', $feed->name ()) - ); - Minz_Session::_param ('notification', $notif); - } else { - $feed->_id ($id); - $feed->faviconPrepare(); - - $is_read = $this->view->conf->mark_when['reception'] ? 1 : 0; - - $entryDAO = FreshRSS_Factory::createEntryDao(); - $entries = array_reverse($feed->entries()); //We want chronological order and SimplePie uses reverse order - - // on calcule la date des articles les plus anciens qu'on accepte - $nb_month_old = $this->view->conf->old_entries; - $date_min = time () - (3600 * 24 * 30 * $nb_month_old); - - //MySQL: http://docs.oracle.com/cd/E17952_01/refman-5.5-en/optimizing-innodb-transaction-management.html - //SQLite: http://stackoverflow.com/questions/1711631/how-do-i-improve-the-performance-of-sqlite - $preparedStatement = $entryDAO->addEntryPrepare(); - $transactionStarted = true; - $feedDAO->beginTransaction(); - // on ajoute les articles en masse sans vérification - foreach ($entries as $entry) { - $values = $entry->toArray(); - $values['id_feed'] = $feed->id(); - $values['id'] = min(time(), $entry->date(true)) . uSecString(); - $values['is_read'] = $is_read; - $entryDAO->addEntry($values, $preparedStatement); - } - $feedDAO->updateLastUpdate($feed->id()); - if ($transactionStarted) { - $feedDAO->commit(); - } - $transactionStarted = false; - - // ok, ajout terminé - $notif = array ( - 'type' => 'good', - 'content' => Minz_Translate::t ('feed_added', $feed->name ()) - ); - Minz_Session::_param ('notification', $notif); - - // permet de rediriger vers la page de conf du flux - $params['id'] = $feed->id (); - } - } - } catch (FreshRSS_BadUrl_Exception $e) { - Minz_Log::record ($e->getMessage (), Minz_Log::WARNING); - $notif = array ( - 'type' => 'bad', - 'content' => Minz_Translate::t ('invalid_url', $url) - ); - Minz_Session::_param ('notification', $notif); } catch (FreshRSS_Feed_Exception $e) { - Minz_Log::record ($e->getMessage (), Minz_Log::WARNING); - $notif = array ( - 'type' => 'bad', - 'content' => Minz_Translate::t ('internal_problem_feed', Minz_Url::display(array('a' => 'logs'))) + // Something went bad (timeout, server not found, etc.) + Minz_Log::warning($e->getMessage()); + Minz_Request::bad( + _t('internal_problem_feed', _url('index', 'logs')), + $url_redirect ); - Minz_Session::_param ('notification', $notif); } catch (Minz_FileNotExistException $e) { - // Répertoire de cache n'existe pas - Minz_Log::record ($e->getMessage (), Minz_Log::ERROR); - $notif = array ( - 'type' => 'bad', - 'content' => Minz_Translate::t ('internal_problem_feed', Minz_Url::display(array('a' => 'logs'))) + // Cache directory doesn't exist! + Minz_Log::error($e->getMessage()); + Minz_Request::bad( + _t('internal_problem_feed', _url('index', 'logs')), + $url_redirect ); - Minz_Session::_param ('notification', $notif); } - if ($transactionStarted) { - $feedDAO->rollBack (); + + if ($feedDAO->searchByUrl($feed->url())) { + Minz_Request::bad(_t('already_subscribed', $feed->name()), $url_redirect); } - Minz_Request::forward (array ('c' => 'configure', 'a' => 'feed', 'params' => $params), true); + $feed->_category($cat); + $feed->_httpAuth($http_auth); + + $values = array( + 'url' => $feed->url(), + 'category' => $feed->category(), + 'name' => $feed->name(), + 'website' => $feed->website(), + 'description' => $feed->description(), + 'lastUpdate' => time(), + 'httpAuth' => $feed->httpAuth(), + ); + + $id = $feedDAO->addFeed($values); + if (!$id) { + // There was an error in database... we cannot say what here. + Minz_Request::bad(_t('feed_not_added', $feed->name()), $url_redirect); + } + + // Ok, feed has been added in database. Now we have to refresh entries. + $feed->_id($id); + $feed->faviconPrepare(); + + $is_read = FreshRSS_Context::$conf->mark_when['reception'] ? 1 : 0; + + $entryDAO = FreshRSS_Factory::createEntryDao(); + // We want chronological order and SimplePie uses reverse order. + $entries = array_reverse($feed->entries()); + + // Calculate date of oldest entries we accept in DB. + $nb_month_old = FreshRSS_Context::$conf->old_entries; + $date_min = time() - (3600 * 24 * 30 * $nb_month_old); + + // Use a shared statement and a transaction to improve a LOT the + // performances. + $prepared_statement = $entryDAO->addEntryPrepare(); + $feedDAO->beginTransaction(); + foreach ($entries as $entry) { + // Entries are added without any verification. + $values = $entry->toArray(); + $values['id_feed'] = $feed->id(); + $values['id'] = min(time(), $entry->date(true)) . uSecString(); + $values['is_read'] = $is_read; + $entryDAO->addEntry($values, $prepared_statement); + } + $feedDAO->updateLastUpdate($feed->id()); + $feedDAO->commit(); + + // Entries are in DB, we redirect to feed configuration page. + $url_redirect['params']['id'] = $feed->id(); + Minz_Request::good(_t('feed_added', $feed->name()), $url_redirect); } else { + // GET request: we must ask confirmation to user before adding feed. + Minz_View::prependTitle(_t('add_rss_feed') . ' · '); - // GET request so we must ask confirmation to user - Minz_View::prependTitle(Minz_Translate::t('add_rss_feed') . ' · '); $this->view->categories = $this->catDAO->listCategories(false); $this->view->feed = new FreshRSS_Feed($url); try { - // We try to get some more information about the feed + // We try to get more information about the feed. $this->view->feed->load(true); $this->view->load_ok = true; } catch (Exception $e) { @@ -183,256 +202,282 @@ class FreshRSS_feed_Controller extends Minz_ActionController { $feed = $feedDAO->searchByUrl($this->view->feed->url()); if ($feed) { - // Already subscribe so we redirect to the feed configuration page - $notif = array( - 'type' => 'bad', - 'content' => Minz_Translate::t( - 'already_subscribed', $feed->name() - ) - ); - Minz_Session::_param('notification', $notif); - - Minz_Request::forward(array( - 'c' => 'configure', - 'a' => 'feed', - 'params' => array( - 'id' => $feed->id() - ) - ), true); + // Already subscribe so we redirect to the feed configuration page. + $url_redirect['params']['id'] = $feed->id(); + Minz_Request::good(_t('already_subscribed', $feed->name()), $url_redirect); } } } - public function truncateAction () { - if (Minz_Request::isPost ()) { - $id = Minz_Request::param ('id'); - $feedDAO = FreshRSS_Factory::createFeedDao(); - $n = $feedDAO->truncate($id); - $notif = array( - 'type' => $n === false ? 'bad' : 'good', - 'content' => Minz_Translate::t ('n_entries_deleted', $n) - ); - Minz_Session::_param ('notification', $notif); - invalidateHttpCache(); - Minz_Request::forward (array ('c' => 'configure', 'a' => 'feed', 'params' => array('id' => $id)), true); + /** + * This action remove entries from a given feed. + * + * It should be reached by a POST action. + * + * Parameter is: + * - id (default: false) + */ + public function truncateAction() { + $id = Minz_Request::param('id'); + $url_redirect = array( + 'c' => 'subscription', + 'a' => 'index', + 'params' => array('id' => $id) + ); + + if (!Minz_Request::isPost()) { + Minz_Request::forward($url_redirect, true); + } + + $feedDAO = FreshRSS_Factory::createFeedDao(); + $n = $feedDAO->truncate($id); + + invalidateHttpCache(); + if ($n === false) { + Minz_Request::bad(_t('error_occurred'), $url_redirect); + } else { + Minz_Request::good(_t('n_entries_deleted', $n), $url_redirect); } } - public function actualizeAction () { + /** + * This action actualizes entries from one or several feeds. + * + * Parameters are: + * - id (default: false) + * - force (default: false) + * If id is not specified, all the feeds are actualized. But if force is + * false, process stops at 10 feeds to avoid time execution problem. + */ + public function actualizeAction() { @set_time_limit(300); $feedDAO = FreshRSS_Factory::createFeedDao(); $entryDAO = FreshRSS_Factory::createEntryDao(); Minz_Session::_param('actualize_feeds', false); - $id = Minz_Request::param ('id'); - $force = Minz_Request::param ('force', false); + $id = Minz_Request::param('id'); + $force = Minz_Request::param('force'); - // on créé la liste des flux à mettre à actualiser - // si on veut mettre un flux à jour spécifiquement, on le met - // dans la liste, mais seul (permet d'automatiser le traitement) - $feeds = array (); + // Create a list of feeds to actualize. + // If id is set and valid, corresponding feed is added to the list but + // alone in order to automatize further process. + $feeds = array(); if ($id) { - $feed = $feedDAO->searchById ($id); + $feed = $feedDAO->searchById($id); if ($feed) { - $feeds = array ($feed); + $feeds[] = $feed; } } else { - $feeds = $feedDAO->listFeedsOrderUpdate($this->view->conf->ttl_default); + $feeds = $feedDAO->listFeedsOrderUpdate(FreshRSS_Context::$conf->ttl_default); } - // on calcule la date des articles les plus anciens qu'on accepte - $nb_month_old = max($this->view->conf->old_entries, 1); - $date_min = time () - (3600 * 24 * 30 * $nb_month_old); + // Calculate date of oldest entries we accept in DB. + $nb_month_old = max(FreshRSS_Context::$conf->old_entries, 1); + $date_min = time() - (3600 * 24 * 30 * $nb_month_old); - $i = 0; - $flux_update = 0; - $is_read = $this->view->conf->mark_when['reception'] ? 1 : 0; + $updated_feeds = 0; + $is_read = FreshRSS_Context::$conf->mark_when['reception'] ? 1 : 0; foreach ($feeds as $feed) { if (!$feed->lock()) { - Minz_Log::record('Feed already being actualized: ' . $feed->url(), Minz_Log::NOTICE); + Minz_Log::notice('Feed already being actualized: ' . $feed->url()); continue; } - try { - $url = $feed->url(); - $feedHistory = $feed->keepHistory(); + try { + // Load entries $feed->load(false); - $entries = array_reverse($feed->entries()); //We want chronological order and SimplePie uses reverse order - $hasTransaction = false; + } catch (FreshRSS_Feed_Exception $e) { + Minz_Log::notice($e->getMessage()); + $feedDAO->updateLastUpdate($feed->id(), 1); + $feed->unlock(); + continue; + } - if (count($entries) > 0) { - //For this feed, check last n entry GUIDs already in database - $existingGuids = array_fill_keys ($entryDAO->listLastGuidsByFeed ($feed->id (), count($entries) + 10), 1); - $useDeclaredDate = empty($existingGuids); + $url = $feed->url(); + $feed_history = $feed->keepHistory(); + if ($feed_history == -2) { + // TODO: -2 must be a constant! + // -2 means we take the default value from configuration + $feed_history = FreshRSS_Context::$conf->keep_history_default; + } - if ($feedHistory == -2) { //default - $feedHistory = $this->view->conf->keep_history_default; + // We want chronological order and SimplePie uses reverse order. + $entries = array_reverse($feed->entries()); + if (count($entries) > 0) { + // For this feed, check last n entry GUIDs already in database. + $existing_guids = array_fill_keys($entryDAO->listLastGuidsByFeed( + $feed->id(), count($entries) + 10 + ), 1); + $use_declared_date = empty($existing_guids); + + // Add entries in database if possible. + $prepared_statement = $entryDAO->addEntryPrepare(); + $feedDAO->beginTransaction(); + foreach ($entries as $entry) { + $entry_date = $entry->date(true); + if (isset($existing_guids[$entry->guid()]) || + ($feed_history == 0 && $entry_date < $date_min)) { + // This entry already exists in DB or should not be added + // considering configuration and date. + continue; } - $preparedStatement = $entryDAO->addEntryPrepare(); - $hasTransaction = true; - $feedDAO->beginTransaction(); - - // On ne vérifie pas strictement que l'article n'est pas déjà en BDD - // La BDD refusera l'ajout car (id_feed, guid) doit être unique - foreach ($entries as $entry) { - $eDate = $entry->date(true); - if ((!isset($existingGuids[$entry->guid()])) && - (($feedHistory != 0) || ($eDate >= $date_min))) { - $values = $entry->toArray(); - //Use declared date at first import, otherwise use discovery date - $values['id'] = ($useDeclaredDate || $eDate < $date_min) ? - min(time(), $eDate) . uSecString() : - uTimeString(); - $values['is_read'] = $is_read; - $entryDAO->addEntry($values, $preparedStatement); - } + $id = uTimeString(); + if ($use_declared_date || $entry_date < $date_min) { + // Use declared date at first import. + $id = min(time(), $entry_date) . uSecString(); } - } - if (($feedHistory >= 0) && (rand(0, 30) === 1)) { - if (!$hasTransaction) { - $feedDAO->beginTransaction(); - } - $nb = $feedDAO->cleanOldEntries ($feed->id (), $date_min, max($feedHistory, count($entries) + 10)); - if ($nb > 0) { - Minz_Log::record ($nb . ' old entries cleaned in feed [' . $feed->url() . ']', Minz_Log::DEBUG); - } + $values = $entry->toArray(); + $values['id'] = $id; + $values['is_read'] = $is_read; + $entryDAO->addEntry($values, $prepared_statement); } + } - // on indique que le flux vient d'être mis à jour en BDD - $feedDAO->updateLastUpdate ($feed->id (), 0, $hasTransaction); - if ($hasTransaction) { - $feedDAO->commit(); + if ($feed_history >= 0 && rand(0, 30) === 1) { + // TODO: move this function in web cron when available (see entry::purge) + // Remove old entries once in 30. + if (!$feedDAO->hasTransaction()) { + $feedDAO->beginTransaction(); } - $flux_update++; - if (($feed->url() !== $url)) { //HTTP 301 Moved Permanently - Minz_Log::record('Feed ' . $url . ' moved permanently to ' . $feed->url(), Minz_Log::NOTICE); - $feedDAO->updateFeed($feed->id(), array('url' => $feed->url())); + + $nb = $feedDAO->cleanOldEntries($feed->id(), + $date_min, + max($feed_history, count($entries) + 10)); + if ($nb > 0) { + Minz_Log::debug($nb . ' old entries cleaned in feed [' . + $feed->url() . ']'); } - } catch (FreshRSS_Feed_Exception $e) { - Minz_Log::record ($e->getMessage (), Minz_Log::NOTICE); - $feedDAO->updateLastUpdate ($feed->id (), 1); + } + + $feedDAO->updateLastUpdate($feed->id(), 0, $feedDAO->hasTransaction()); + if ($feedDAO->hasTransaction()) { + $feedDAO->commit(); + } + + if ($feed->url() !== $url) { + // HTTP 301 Moved Permanently + Minz_Log::notice('Feed ' . $url . ' moved permanently to ' . $feed->url()); + $feedDAO->updateFeed($feed->id(), array('url' => $feed->url())); } $feed->faviconPrepare(); $feed->unlock(); + $updated_feeds++; unset($feed); - // On arrête à 10 flux pour ne pas surcharger le serveur - // sauf si le paramètre $force est à vrai - $i++; - if ($i >= 10 && !$force) { + // No more than 10 feeds unless $force is true to avoid overloading + // the server. + if ($updated_feeds >= 10 && !$force) { break; } } - $url = array (); - if ($flux_update === 1) { - // on a mis un seul flux à jour - $feed = reset ($feeds); - $notif = array ( - 'type' => 'good', - 'content' => Minz_Translate::t ('feed_actualized', $feed->name ()) - ); - } elseif ($flux_update > 1) { - // plusieurs flux on été mis à jour - $notif = array ( + if (Minz_Request::param('ajax')) { + // Most of the time, ajax request is for only one feed. But since + // there are several parallel requests, we should return that there + // are several updated feeds. + $notif = array( 'type' => 'good', - 'content' => Minz_Translate::t ('n_feeds_actualized', $flux_update) + 'content' => _t('feeds_actualized') ); + Minz_Session::_param('notification', $notif); + // No layout in ajax request. + $this->view->_useLayout(false); + return; + } + + // Redirect to the main page with correct notification. + if ($updated_feeds === 1) { + $feed = reset($feeds); + Minz_Request::good(_t('feed_actualized', $feed->name()), + array('get' => 'f_' . $feed->id())); + } elseif ($updated_feeds > 1) { + Minz_Request::good(_t('n_feeds_actualized', $updated_feeds), array()); } else { - // aucun flux n'a été mis à jour, oups - $notif = array ( - 'type' => 'good', - 'content' => Minz_Translate::t ('no_feed_to_refresh') - ); + Minz_Request::good(_t('no_feed_to_refresh'), array()); } + } - if ($i === 1) { - // Si on a voulu mettre à jour qu'un flux - // on filtre l'affichage par ce flux - $feed = reset ($feeds); - $url['params'] = array ('get' => 'f_' . $feed->id ()); + /** + * This action changes the category of a feed. + * + * This page must be reached by a POST request. + * + * Parameters are: + * - f_id (default: false) + * - c_id (default: false) + * If c_id is false, default category is used. + * + * @todo should handle order of the feed inside the category. + */ + public function moveAction() { + if (!Minz_Request::isPost()) { + Minz_Request::forward(array('c' => 'subscription'), true); } - if (Minz_Request::param ('ajax', 0) === 0) { - Minz_Session::_param ('notification', $notif); - Minz_Request::forward ($url, true); + $feed_id = Minz_Request::param('f_id'); + $cat_id = Minz_Request::param('c_id'); + + if ($cat_id === false) { + // If category was not given get the default one. + $catDAO = new FreshRSS_CategoryDAO(); + $catDAO->checkDefault(); + $def_cat = $catDAO->getDefault(); + $cat_id = $def_cat->id(); + } + + $feedDAO = FreshRSS_Factory::createFeedDao(); + $values = array('category' => $cat_id); + + $feed = $feedDAO->searchById($feed_id); + if ($feed && ($feed->category() == $cat_id || + $feedDAO->updateFeed($feed_id, $values))) { + // TODO: return something useful } else { - // Une requête Ajax met un seul flux à jour. - // Comme en principe plusieurs requêtes ont lieu, - // on indique que "plusieurs flux ont été mis à jour". - // Cela permet d'avoir une notification plus proche du - // ressenti utilisateur - $notif = array ( - 'type' => 'good', - 'content' => Minz_Translate::t ('feeds_actualized') - ); - Minz_Session::_param ('notification', $notif); - // et on désactive le layout car ne sert à rien - $this->view->_useLayout (false); + Minz_Log::warning('Cannot move feed `' . $feed_id . '` ' . + 'in the category `' . $cat_id . '`'); + Minz_Error::error(404); } } - public function deleteAction () { - if (Minz_Request::isPost ()) { - $type = Minz_Request::param ('type', 'feed'); - $id = Minz_Request::param ('id'); - - $feedDAO = FreshRSS_Factory::createFeedDao(); - if ($type == 'category') { - // List feeds to remove then related user queries. - $feeds = $feedDAO->listByCategory($id); + /** + * This action deletes a feed. + * + * This page must be reached by a POST request. + * If there are related queries, they are deleted too. + * + * Parameters are: + * - id (default: false) + * - r (default: false) + * r permits to redirect to a given page at the end of this action. + * + * @todo handle "r" redirection in Minz_Request::forward()? + */ + public function deleteAction() { + $redirect_url = Minz_Request::param('r', false, true); + if (!$redirect_url) { + $redirect_url = array('c' => 'subscription', 'a' => 'index'); + } - 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(); + if (!Minz_Request::isPost()) { + Minz_Request::forward($redirect_url, true); + } - $notif = array ( - 'type' => 'good', - 'content' => Minz_Translate::t ('category_emptied') - ); - //TODO: Delete old favicons - } else { - $notif = array ( - 'type' => 'bad', - 'content' => Minz_Translate::t ('error_occured') - ); - } - } 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') - ); - //TODO: Delete old favicon - } else { - $notif = array ( - 'type' => 'bad', - 'content' => Minz_Translate::t ('error_occured') - ); - } - } + $id = Minz_Request::param('id'); + $feedDAO = FreshRSS_Factory::createFeedDao(); + if ($feedDAO->deleteFeed($id)) { + // TODO: Delete old favicon - Minz_Session::_param ('notification', $notif); + // Remove related queries + FreshRSS_Context::$conf->remove_query_by_get('f_' . $id); + FreshRSS_Context::$conf->save(); - $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::good(_t('feed_deleted'), $redirect_url); + } else { + Minz_Request::bad(_t('error_occurred'), $redirect_url); } } } diff --git a/app/Controllers/importExportController.php b/app/Controllers/importExportController.php index f329766b8..4e2dbd157 100644 --- a/app/Controllers/importExportController.php +++ b/app/Controllers/importExportController.php @@ -1,12 +1,17 @@ <?php +/** + * Controller to handle every import and export actions. + */ class FreshRSS_importExport_Controller extends Minz_ActionController { + /** + * 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( - 403, - array('error' => array(_t('access_denied'))) - ); + if (!FreshRSS_Auth::hasAccess()) { + Minz_Error::error(403); } require_once(LIB_PATH . '/lib_opml.php'); @@ -16,13 +21,23 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { $this->feedDAO = FreshRSS_Factory::createFeedDao(); } + /** + * This action displays the main page for import / export system. + */ public function indexAction() { - $this->view->categories = $this->catDAO->listCategories(); $this->view->feeds = $this->feedDAO->listFeeds(); - Minz_View::prependTitle(_t('import_export') . ' · '); } + /** + * This action handles import action. + * + * It must be reached by a POST request. + * + * Parameter is: + * - file (default: nothing!) + * Available file types are: zip, json or xml. + */ public function importAction() { if (!Minz_Request::isPost()) { Minz_Request::forward(array('c' => 'importExport', 'a' => 'index'), true); @@ -92,10 +107,10 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { $error = $this->importOpml($opml_file); } foreach ($list_files['json_starred'] as $article_file) { - $error = $this->importArticles($article_file, true); + $error = $this->importJson($article_file, true); } foreach ($list_files['json_feed'] as $article_file) { - $error = $this->importArticles($article_file); + $error = $this->importJson($article_file); } // And finally, we get import status and redirect to the home page @@ -105,11 +120,15 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { Minz_Request::good($content_notif); } + /** + * This method tries to guess the file type based on its name. + * + * Itis a *very* basic guess file type function. Only based on filename. + * That's could be improved but should be enough for what we have to do. + * + * @todo move into lib_rss.php + */ private function guessFileType($filename) { - // 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. - if (substr_compare($filename, '.zip', -4) === 0) { return 'zip'; } elseif (substr_compare($filename, '.opml', -5) === 0 || @@ -125,6 +144,12 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { } } + /** + * This method parses and imports an OPML file. + * + * @param string $opml_file the OPML file content. + * @return boolean true if an error occured, false else. + */ private function importOpml($opml_file) { $opml_array = array(); try { @@ -139,35 +164,78 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { return $this->addOpmlElements($opml_array['body']); } + /** + * This method imports an OPML file based on its body. + * + * @param array $opml_elements an OPML element (body or outline). + * @param string $parent_cat the name of the parent category. + * @return boolean true if an error occured, false else. + */ private function addOpmlElements($opml_elements, $parent_cat = null) { $error = false; + + $nb_feeds = count($this->feedDAO->listFeeds()); + $nb_cats = count($this->catDAO->listCategories(false)); + $limits = Minz_Configuration::limits(); + foreach ($opml_elements as $elt) { - $res = false; + $is_error = false; if (isset($elt['xmlUrl'])) { - $res = $this->addFeedOpml($elt, $parent_cat); + // If xmlUrl exists, it means it is a feed + if ($nb_feeds >= $limits['max_feeds']) { + Minz_Log::warning(_t('sub.feeds.over_max', + $limits['max_feeds'])); + $is_error = true; + continue; + } + + $is_error = $this->addFeedOpml($elt, $parent_cat); + if (!$is_error) { + $nb_feeds += 1; + } } else { - $res = $this->addCategoryOpml($elt, $parent_cat); + // No xmlUrl? It should be a category! + $limit_reached = ($nb_cats >= $limits['max_categories']); + if ($limit_reached) { + Minz_Log::warning(_t('sub.categories.over_max', + $limits['max_categories'])); + } + + $is_error = $this->addCategoryOpml($elt, $parent_cat, $limit_reached); + if (!$is_error) { + $nb_cats += 1; + } } - if (!$error && $res) { + if (!$error && $is_error) { // oops: there is at least one error! - $error = $res; + $error = $is_error; } } return $error; } + /** + * This method imports an OPML feed element. + * + * @param array $feed_elt an OPML element (must be a feed element). + * @param string $parent_cat the name of the parent category. + * @return boolean true if an error occured, false else. + */ private function addFeedOpml($feed_elt, $parent_cat) { + $default_cat = $this->catDAO->getDefault(); if (is_null($parent_cat)) { // This feed has no parent category so we get the default one - $parent_cat = $this->catDAO->getDefault()->name(); + $parent_cat = $default_cat->name(); } $cat = $this->catDAO->searchByName($parent_cat); - - if (!$cat) { - return true; + if (is_null($cat)) { + // If there is not $cat, it means parent category does not exist in + // database. + // If it happens, take the default category. + $cat = $default_cat; } // We get different useful information @@ -203,12 +271,24 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { return $error; } - private function addCategoryOpml($cat_elt, $parent_cat) { + /** + * This method imports an OPML category element. + * + * @param array $cat_elt an OPML element (must be a category element). + * @param string $parent_cat the name of the parent category. + * @param boolean $cat_limit_reached indicates if category limit has been reached. + * if yes, category is not added (but we try for feeds!) + * @return boolean true if an error occured, false else. + */ + private function addCategoryOpml($cat_elt, $parent_cat, $cat_limit_reached) { // Create a new Category object $cat = new FreshRSS_Category(Minz_Helper::htmlspecialchars_utf8($cat_elt['text'])); - $id = $this->catDAO->addCategoryObject($cat); - $error = ($id === false); + $error = true; + if (!$cat_limit_reached) { + $id = $this->catDAO->addCategoryObject($cat); + $error = ($id === false); + } if (isset($cat_elt['@outlines'])) { // Our cat_elt contains more categories or more feeds, so we @@ -223,28 +303,55 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { return $error; } - private function importArticles($article_file, $starred = false) { + /** + * This method import a JSON-based file (Google Reader format). + * + * @param string $article_file the JSON file content. + * @param boolean $starred true if articles from the file must be starred. + * @return boolean true if an error occured, false else. + */ + private function importJson($article_file, $starred = false) { $article_object = json_decode($article_file, true); if (is_null($article_object)) { Minz_Log::warning('Try to import a non-JSON file'); return true; } - $is_read = $this->view->conf->mark_when['reception'] ? 1 : 0; + $is_read = FreshRSS_Context::$conf->mark_when['reception'] ? 1 : 0; - $google_compliant = ( - strpos($article_object['id'], 'com.google') !== false - ); + $google_compliant = strpos($article_object['id'], 'com.google') !== false; $error = false; $article_to_feed = array(); + $nb_feeds = count($this->feedDAO->listFeeds()); + $limits = Minz_Configuration::limits(); + // 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); + $key = $google_compliant ? 'htmlUrl' : 'feedUrl'; + $feed = new FreshRSS_Feed($item['origin'][$key]); + $feed = $this->feedDAO->searchByUrl($feed->url()); + if (is_null($feed)) { - $error = true; - } else { + // Feed does not exist in DB,we should to try to add it. + if ($nb_feeds >= $limits['max_feeds']) { + // Oops, no more place! + Minz_Log::warning(_t('sub.feeds.over_max', $limits['max_feeds'])); + } else { + $feed = $this->addFeedJson($item['origin'], $google_compliant); + } + + if (is_null($feed)) { + // Still null? It means something went wrong. + $error = true; + } else { + // Nice! Increase the counter. + $nb_feeds += 1; + } + } + + if (!is_null($feed)) { $article_to_feed[$item['id']] = $feed->id(); } } @@ -254,6 +361,7 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { $this->entryDAO->beginTransaction(); foreach ($article_object['items'] as $item) { if (!isset($article_to_feed[$item['id']])) { + // Related feed does not exist for this entry, do nothing. continue; } @@ -263,6 +371,7 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { 'summary' : 'content'; $tags = $item['categories']; if ($google_compliant) { + // Remove tags containing "/state/com.google" which are useless. $tags = array_filter($tags, function($var) { return strpos($var, '/state/com.google') === false; }); @@ -288,7 +397,15 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { return $error; } - private function addFeedArticles($origin, $google_compliant) { + /** + * This method import a JSON-based feed (Google Reader format). + * + * @param array $origin represents a feed. + * @param boolean $google_compliant takes care of some specific values if true. + * @return FreshRSS_Feed if feed is in database at the end of the process, + * else null. + */ + private function addFeedJson($origin, $google_compliant) { $default_cat = $this->catDAO->getDefault(); $return = null; @@ -298,14 +415,14 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { $website = $origin['htmlUrl']; try { - // Create a Feed object and add it in DB + // Create a Feed object and add it in database. $feed = new FreshRSS_Feed($url); $feed->_category($default_cat->id()); $feed->_name($name); $feed->_website($website); // addFeedObject checks if feed is already in DB so nothing else to - // check here + // check here. $id = $this->feedDAO->addFeedObject($feed); if ($id !== false) { @@ -319,6 +436,16 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { return $return; } + /** + * This action handles export action. + * + * This action must be reached by a POST request. + * + * Parameters are: + * - export_opml (default: false) + * - export_starred (default: false) + * - export_feeds (default: array()) a list of feed ids + */ public function exportAction() { if (!Minz_Request::isPost()) { Minz_Request::forward(array('c' => 'importExport', 'a' => 'index'), true); @@ -336,7 +463,7 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { } if ($export_starred) { - $export_files['starred.json'] = $this->generateArticles('starred'); + $export_files['starred.json'] = $this->generateEntries('starred'); } foreach ($export_feeds as $feed_id) { @@ -344,9 +471,7 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { if ($feed) { $filename = 'feed_' . $feed->category() . '_' . $feed->id() . '.json'; - $export_files[$filename] = $this->generateArticles( - 'feed', $feed - ); + $export_files[$filename] = $this->generateEntries('feed', $feed); } } @@ -366,10 +491,16 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { $type = $this->guessFileType($filename); $this->exportFile('freshrss_' . $filename, $export_files[$filename], $type); } else { + // Nothing to do... Minz_Request::forward(array('c' => 'importExport', 'a' => 'index'), true); } } + /** + * This method returns the OPML file based on user subscriptions. + * + * @return string the OPML file content. + */ private function generateOpml() { $list = array(); foreach ($this->catDAO->listCategories() as $key => $cat) { @@ -381,7 +512,14 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { return $this->view->helperToString('export/opml'); } - private function generateArticles($type, $feed = NULL) { + /** + * This method returns a JSON file content. + * + * @param string $type must be "starred" or "feed" + * @param FreshRSS_Feed $feed feed of which we want to get entries. + * @return string the JSON file content. + */ + private function generateEntries($type, $feed = NULL) { $this->view->categories = $this->catDAO->listCategories(); if ($type == 'starred') { @@ -389,15 +527,14 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { $this->view->type = 'starred'; $unread_fav = $this->entryDAO->countUnreadReadFavorites(); $this->view->entries = $this->entryDAO->listWhere( - 's', '', FreshRSS_Entry::STATE_ALL, 'ASC', - $unread_fav['all'] + 's', '', FreshRSS_Entry::STATE_ALL, 'ASC', $unread_fav['all'] ); } elseif ($type == 'feed' && !is_null($feed)) { $this->view->list_title = _t('feed_list', $feed->name()); $this->view->type = 'feed/' . $feed->id(); $this->view->entries = $this->entryDAO->listWhere( 'f', $feed->id(), FreshRSS_Entry::STATE_ALL, 'ASC', - $this->view->conf->posts_per_page + FreshRSS_Context::$conf->posts_per_page ); $this->view->feed = $feed; } @@ -405,6 +542,12 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { return $this->view->helperToString('export/articles'); } + /** + * This method zips a list of files and returns it by HTTP. + * + * @param array $files list of files where key is filename and value the content. + * @throws Exception if Zip extension is not loaded. + */ private function exportZip($files) { if (!extension_loaded('zip')) { throw new Exception(); @@ -428,6 +571,14 @@ class FreshRSS_importExport_Controller extends Minz_ActionController { unlink($zip_file); } + /** + * This method returns a single file (OPML or JSON) by HTTP. + * + * @param string $filename + * @param string $content + * @param string $type the file type (opml, json_feed or json_starred). + * If equals to unknown, nothing happens. + */ private function exportFile($filename, $content, $type) { if ($type === 'unknown') { return; diff --git a/app/Controllers/indexController.php b/app/Controllers/indexController.php index d3b299f4e..1cf618f7f 100755 --- a/app/Controllers/indexController.php +++ b/app/Controllers/indexController.php @@ -1,499 +1,234 @@ <?php +/** + * This class handles main actions of FreshRSS. + */ class FreshRSS_index_Controller extends Minz_ActionController { - private $nb_not_read_cat = 0; - - public function indexAction () { - $output = Minz_Request::param ('output'); - $token = $this->view->conf->token; - - // check if user is logged in - if (!$this->view->loginOk && !Minz_Configuration::allowAnonymous()) { - $token_param = Minz_Request::param ('token', ''); - $token_is_ok = ($token != '' && $token === $token_param); - if ($output === 'rss' && !$token_is_ok) { - Minz_Error::error ( - 403, - array ('error' => array (Minz_Translate::t ('access_denied'))) - ); - return; - } elseif ($output !== 'rss') { - // "hard" redirection is not required, just ask dispatcher to - // forward to the login form without 302 redirection - Minz_Request::forward(array('c' => 'index', 'a' => 'formLogin')); - return; - } - } - - $params = Minz_Request::params (); - if (isset ($params['search'])) { - $params['search'] = urlencode ($params['search']); - } - $this->view->url = array ( + /** + * This action only redirect on the default view mode (normal or global) + */ + public function indexAction() { + $prefered_output = FreshRSS_Context::$conf->view_mode; + Minz_Request::forward(array( 'c' => 'index', - 'a' => 'index', - 'params' => $params - ); - - if ($output === 'rss') { - // no layout for RSS output - $this->view->_useLayout (false); - header('Content-Type: application/rss+xml; charset=utf-8'); - } elseif ($output === 'global') { - Minz_View::appendScript (Minz_Url::display ('/scripts/global_view.js?' . @filemtime(PUBLIC_PATH . '/scripts/global_view.js'))); - } - - $catDAO = new FreshRSS_CategoryDAO(); - $entryDAO = FreshRSS_Factory::createEntryDao(); + 'a' => $prefered_output + )); + } - $this->view->cat_aside = $catDAO->listCategories (); - $this->view->nb_favorites = $entryDAO->countUnreadReadFavorites (); - $this->view->nb_not_read = FreshRSS_CategoryDAO::CountUnreads($this->view->cat_aside, 1); - $this->view->currentName = ''; - - $this->view->get_c = ''; - $this->view->get_f = ''; - - $get = Minz_Request::param ('get', 'a'); - $getType = $get[0]; - $getId = substr ($get, 2); - if (!$this->checkAndProcessType ($getType, $getId)) { - Minz_Log::record ('Not found [' . $getType . '][' . $getId . ']', Minz_Log::DEBUG); - Minz_Error::error ( - 404, - array ('error' => array (Minz_Translate::t ('page_not_found'))) - ); + /** + * This action displays the normal view of FreshRSS. + */ + public function normalAction() { + if (!FreshRSS_Auth::hasAccess() && !Minz_Configuration::allowAnonymous()) { + Minz_Request::forward(array('c' => 'auth', 'a' => 'login')); return; } - // mise à jour des titres - $this->view->rss_title = $this->view->currentName . ' | ' . Minz_View::title(); - Minz_View::prependTitle( - ($this->nb_not_read_cat > 0 ? '(' . formatNumber($this->nb_not_read_cat) . ') ' : '') . - $this->view->currentName . - ' · ' - ); - - // On récupère les différents éléments de filtrage - $this->view->state = Minz_Request::param('state', $this->view->conf->default_view); - $state_param = Minz_Request::param ('state', null); - $filter = Minz_Request::param ('search', ''); - $this->view->order = $order = Minz_Request::param ('order', $this->view->conf->sort_order); - $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': - $hasUnread = $this->view->nb_not_read > 0; - break; - case 's': - // This is deprecated. The favorite button does not exist anymore - $hasUnread = $this->view->nb_favorites['unread'] > 0; - break; - case 'c': - $hasUnread = (!isset($this->view->cat_aside[$getId])) || ($this->view->cat_aside[$getId]->nbNotRead() > 0); - break; - case 'f': - $myFeed = FreshRSS_CategoryDAO::findFeed($this->view->cat_aside, $getId); - $hasUnread = ($myFeed === null) || ($myFeed->nbNotRead() > 0); - break; - default: - $hasUnread = true; - break; - } - if (!$hasUnread && ($state_param === null)) { - $this->view->state = FreshRSS_Entry::STATE_ALL; - } + try { + $this->updateContext(); + } catch (FreshRSS_Context_Exception $e) { + Minz_Error::error(404); } - $today = @strtotime('today'); - $this->view->today = $today; - - // on calcule la date des articles les plus anciens qu'on affiche - $nb_month_old = $this->view->conf->old_entries; - $date_min = $today - (3600 * 24 * 30 * $nb_month_old); //Do not use a fast changing value such as time() to allow SQL caching - $keepHistoryDefault = $this->view->conf->keep_history_default; - try { - $entries = $entryDAO->listWhere($getType, $getId, $this->view->state, $order, $nb + 1, $first, $filter, $date_min, true, $keepHistoryDefault); - - // Si on a récupéré aucun article "non lus" - // on essaye de récupérer tous les articles - if ($this->view->state === FreshRSS_Entry::STATE_NOT_READ && empty($entries) && ($state_param === null) && ($filter == '')) { - Minz_Log::record('Conflicting information about nbNotRead!', Minz_Log::DEBUG); - $feedDAO = FreshRSS_Factory::createFeedDao(); - try { - $feedDAO->updateCachedValues(); - } catch (Exception $ex) { - Minz_Log::record('Failed to automatically correct nbNotRead! ' + $ex->getMessage(), Minz_Log::NOTICE); - } - $this->view->state = FreshRSS_Entry::STATE_ALL; - $entries = $entryDAO->listWhere($getType, $getId, $this->view->state, $order, $nb, $first, $filter, $date_min, true, $keepHistoryDefault); + $entries = $this->listEntriesByContext(); + + $nb_entries = count($entries); + if ($nb_entries > FreshRSS_Context::$number) { + // We have more elements for pagination + $last_entry = array_pop($entries); + FreshRSS_Context::$next_id = $last_entry->id(); } - Minz_Request::_param('state', $this->view->state); - if (count($entries) <= $nb) { - $this->view->nextId = ''; - } else { //We have more elements for pagination - $lastEntry = array_pop($entries); - $this->view->nextId = $lastEntry->id(); + $first_entry = $nb_entries > 0 ? $entries[0] : null; + FreshRSS_Context::$id_max = $first_entry === null ? + (time() - 1) . '000000' : + $first_entry->id(); + if (FreshRSS_Context::$order === 'ASC') { + // In this case we do not know but we guess id_max + $id_max = (time() - 1) . '000000'; + if (strcmp($id_max, FreshRSS_Context::$id_max) > 0) { + FreshRSS_Context::$id_max = $id_max; + } } $this->view->entries = $entries; } catch (FreshRSS_EntriesGetter_Exception $e) { - Minz_Log::record ($e->getMessage (), Minz_Log::NOTICE); - Minz_Error::error ( - 404, - array ('error' => array (Minz_Translate::t ('page_not_found'))) - ); + Minz_Log::notice($e->getMessage()); + Minz_Error::error(404); } - } - /* - * Vérifie que la catégorie / flux sélectionné existe - * + Initialise correctement les variables de vue get_c et get_f - * + Met à jour la variable $this->nb_not_read_cat - */ - private function checkAndProcessType ($getType, $getId) { - switch ($getType) { - case 'a': - $this->view->currentName = Minz_Translate::t ('your_rss_feeds'); - $this->nb_not_read_cat = $this->view->nb_not_read; - $this->view->get_c = $getType; - return true; - case 's': - $this->view->currentName = Minz_Translate::t ('your_favorites'); - $this->nb_not_read_cat = $this->view->nb_favorites['unread']; - $this->view->get_c = $getType; - return true; - case 'c': - $cat = isset($this->view->cat_aside[$getId]) ? $this->view->cat_aside[$getId] : null; - if ($cat === null) { - $catDAO = new FreshRSS_CategoryDAO(); - $cat = $catDAO->searchById($getId); - } - if ($cat) { - $this->view->currentName = $cat->name (); - $this->nb_not_read_cat = $cat->nbNotRead (); - $this->view->get_c = $getId; - return true; - } else { - return false; - } - case 'f': - $feed = FreshRSS_CategoryDAO::findFeed($this->view->cat_aside, $getId); - if (empty($feed)) { - $feedDAO = FreshRSS_Factory::createFeedDao(); - $feed = $feedDAO->searchById($getId); - } - if ($feed) { - $this->view->currentName = $feed->name (); - $this->nb_not_read_cat = $feed->nbNotRead (); - $this->view->get_f = $getId; - $this->view->get_c = $feed->category (); - return true; - } else { - return false; - } - default: - return false; + $this->view->categories = FreshRSS_Context::$categories; + + $this->view->rss_title = FreshRSS_Context::$name . ' | ' . Minz_View::title(); + $title = FreshRSS_Context::$name; + if (FreshRSS_Context::$get_unread > 0) { + $title = '(' . FreshRSS_Context::$get_unread . ') ' . $title; } + Minz_View::prependTitle($title . ' · '); } - - public function aboutAction () { - Minz_View::prependTitle (Minz_Translate::t ('about') . ' · '); + + /** + * This action displays the reader view of FreshRSS. + * + * @todo: change this view into specific CSS rules? + */ + public function readerAction() { + $this->normalAction(); } - public function logsAction () { - if (!$this->view->loginOk) { - Minz_Error::error ( - 403, - array ('error' => array (Minz_Translate::t ('access_denied'))) - ); + /** + * This action displays the global view of FreshRSS. + */ + public function globalAction() { + if (!FreshRSS_Auth::hasAccess() && !Minz_Configuration::allowAnonymous()) { + Minz_Request::forward(array('c' => 'auth', 'a' => 'login')); + return; } - Minz_View::prependTitle (Minz_Translate::t ('logs') . ' · '); + Minz_View::appendScript(Minz_Url::display('/scripts/global_view.js?' . @filemtime(PUBLIC_PATH . '/scripts/global_view.js'))); - if (Minz_Request::isPost ()) { - FreshRSS_LogDAO::truncate(); + try { + $this->updateContext(); + } catch (FreshRSS_Context_Exception $e) { + Minz_Error::error(404); } - $logs = FreshRSS_LogDAO::lines(); //TODO: ask only the necessary lines + $this->view->categories = FreshRSS_Context::$categories; - //gestion pagination - $page = Minz_Request::param ('page', 1); - $this->view->logsPaginator = new Minz_Paginator ($logs); - $this->view->logsPaginator->_nbItemsPerPage (50); - $this->view->logsPaginator->_currentPage ($page); + $this->view->rss_title = FreshRSS_Context::$name . ' | ' . Minz_View::title(); + $title = _t('gen.title.global_view'); + if (FreshRSS_Context::$get_unread > 0) { + $title = '(' . FreshRSS_Context::$get_unread . ') ' . $title; + } + Minz_View::prependTitle($title . ' · '); } - public function loginAction () { - $this->view->_useLayout (false); - - $url = 'https://verifier.login.persona.org/verify'; - $assert = Minz_Request::param ('assertion'); - $params = 'assertion=' . $assert . '&audience=' . - urlencode (Minz_Url::display (null, 'php', true)); - $ch = curl_init (); - $options = array ( - CURLOPT_URL => $url, - CURLOPT_RETURNTRANSFER => TRUE, - CURLOPT_POST => 2, - CURLOPT_POSTFIELDS => $params - ); - curl_setopt_array ($ch, $options); - $result = curl_exec ($ch); - curl_close ($ch); - - $res = json_decode ($result, true); - - $loginOk = false; - $reason = ''; - if ($res['status'] === 'okay') { - $email = filter_var($res['email'], FILTER_VALIDATE_EMAIL); - if ($email != '') { - $personaFile = DATA_PATH . '/persona/' . $email . '.txt'; - if (($currentUser = @file_get_contents($personaFile)) !== false) { - $currentUser = trim($currentUser); - if (ctype_alnum($currentUser)) { - try { - $this->conf = new FreshRSS_Configuration($currentUser); - $loginOk = strcasecmp($email, $this->conf->mail_login) === 0; - } catch (Minz_Exception $e) { - $reason = 'Invalid configuration for user [' . $currentUser . ']! ' . $e->getMessage(); //Permission denied or conf file does not exist - } - } else { - $reason = 'Invalid username format [' . $currentUser . ']!'; - } - } - } else { - $reason = 'Invalid email format [' . $res['email'] . ']!'; - } + /** + * This action displays the RSS feed of FreshRSS. + */ + public function rssAction() { + $token = FreshRSS_Context::$conf->token; + $token_param = Minz_Request::param('token', ''); + $token_is_ok = ($token != '' && $token === $token_param); + + // Check if user has access. + if (!FreshRSS_Auth::hasAccess() && + !Minz_Configuration::allowAnonymous() && + !$token_is_ok) { + Minz_Error::error(403); } - if ($loginOk) { - Minz_Session::_param('currentUser', $currentUser); - Minz_Session::_param ('mail', $email); - $this->view->loginOk = true; - invalidateHttpCache(); - } else { - $res = array (); - $res['status'] = 'failure'; - $res['reason'] = $reason == '' ? Minz_Translate::t ('invalid_login') : $reason; - Minz_Log::record ('Persona: ' . $res['reason'], Minz_Log::WARNING); + + try { + $this->updateContext(); + } catch (FreshRSS_Context_Exception $e) { + Minz_Error::error(404); } - header('Content-Type: application/json; charset=UTF-8'); - $this->view->res = json_encode ($res); - } + try { + $this->view->entries = $this->listEntriesByContext(); + } catch (FreshRSS_EntriesGetter_Exception $e) { + Minz_Log::notice($e->getMessage()); + Minz_Error::error(404); + } - public function logoutAction () { + // No layout for RSS output. + $this->view->rss_title = FreshRSS_Context::$name . ' | ' . Minz_View::title(); $this->view->_useLayout(false); - invalidateHttpCache(); - Minz_Session::_param('currentUser'); - Minz_Session::_param('mail'); - Minz_Session::_param('passwordHash'); + header('Content-Type: application/rss+xml; charset=utf-8'); } - private static function makeLongTermCookie($username, $passwordHash) { - do { - $token = sha1(Minz_Configuration::salt() . $username . uniqid(mt_rand(), true)); - $tokenFile = DATA_PATH . '/tokens/' . $token . '.txt'; - } while (file_exists($tokenFile)); - if (@file_put_contents($tokenFile, $username . "\t" . $passwordHash) === false) { - return false; - } - $expire = time() + 2629744; //1 month //TODO: Use a configuration instead - Minz_Session::setLongTermCookie('FreshRSS_login', $token, $expire); - Minz_Session::_param('token', $token); - return $token; - } + /** + * This action updates the Context object by using request parameters. + * + * Parameters are: + * - state (default: conf->default_view) + * - search (default: empty string) + * - order (default: conf->sort_order) + * - nb (default: conf->posts_per_page) + * - next (default: empty string) + */ + private function updateContext() { + // Update number of read / unread variables. + $entryDAO = FreshRSS_Factory::createEntryDao(); + FreshRSS_Context::$total_starred = $entryDAO->countUnreadReadFavorites(); + FreshRSS_Context::$total_unread = FreshRSS_CategoryDAO::CountUnreads( + FreshRSS_Context::$categories, 1 + ); - private static function deleteLongTermCookie() { - Minz_Session::deleteLongTermCookie('FreshRSS_login'); - $token = Minz_Session::param('token', null); - if (ctype_alnum($token)) { - @unlink(DATA_PATH . '/tokens/' . $token . '.txt'); - } - Minz_Session::_param('token'); - if (rand(0, 10) === 1) { - self::purgeTokens(); - } - } + FreshRSS_Context::_get(Minz_Request::param('get', 'a')); - private static function purgeTokens() { - $oldest = time() - 2629744; //1 month //TODO: Use a configuration instead - foreach (new DirectoryIterator(DATA_PATH . '/tokens/') as $fileInfo) { - $extension = pathinfo($fileInfo->getFilename(), PATHINFO_EXTENSION); - if ($extension === 'txt' && $fileInfo->getMTime() < $oldest) { - @unlink($fileInfo->getPathname()); - } + FreshRSS_Context::$state = Minz_Request::param( + 'state', FreshRSS_Context::$conf->default_state + ); + $state_forced_by_user = Minz_Request::param('state', false) !== false; + if (FreshRSS_Context::$conf->default_view === 'adaptive' && + FreshRSS_Context::$get_unread <= 0 && + !FreshRSS_Context::isStateEnabled(FreshRSS_Entry::STATE_READ) && + !$state_forced_by_user) { + FreshRSS_Context::$state |= FreshRSS_Entry::STATE_READ; } + + FreshRSS_Context::$search = Minz_Request::param('search', ''); + FreshRSS_Context::$order = Minz_Request::param( + 'order', FreshRSS_Context::$conf->sort_order + ); + FreshRSS_Context::$number = Minz_Request::param( + 'nb', FreshRSS_Context::$conf->posts_per_page + ); + FreshRSS_Context::$first_id = Minz_Request::param('next', ''); } - public function formLoginAction () { - if ($this->view->loginOk) { - Minz_Request::forward(array('c' => 'index', 'a' => 'index'), true); - } + /** + * This method returns a list of entries based on the Context object. + */ + private function listEntriesByContext() { + $entryDAO = FreshRSS_Factory::createEntryDao(); - if (Minz_Request::isPost()) { - $ok = false; - $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)) { - if (!function_exists('password_verify')) { - include_once(LIB_PATH . '/password_compat.php'); - } - try { - $conf = new FreshRSS_Configuration($username); - $s = $conf->passwordHash; - $ok = password_verify($nonce . $s, $c); - if ($ok) { - Minz_Session::_param('currentUser', $username); - Minz_Session::_param('passwordHash', $s); - if (Minz_Request::param('keep_logged_in', false)) { - self::makeLongTermCookie($username, $s); - } else { - self::deleteLongTermCookie(); - } - } else { - Minz_Log::record('Password mismatch for user ' . $username . ', nonce=' . $nonce . ', c=' . $c, Minz_Log::WARNING); - } - } catch (Minz_Exception $me) { - Minz_Log::record('Login failure: ' . $me->getMessage(), Minz_Log::WARNING); - } - } else { - Minz_Log::record('Invalid credential parameters: user=' . $username . ' challenge=' . $c . ' nonce=' . $nonce, Minz_Log::DEBUG); - } - if (!$ok) { - $notif = array( - 'type' => 'bad', - 'content' => Minz_Translate::t('invalid_login') - ); - Minz_Session::_param('notification', $notif); - } - $this->view->_useLayout(false); - Minz_Request::forward(array('c' => 'index', 'a' => 'index'), true); - } elseif (Minz_Configuration::unsafeAutologinEnabled() && isset($_GET['u']) && isset($_GET['p'])) { - Minz_Session::_param('currentUser'); - Minz_Session::_param('mail'); - Minz_Session::_param('passwordHash'); - $username = ctype_alnum($_GET['u']) ? $_GET['u'] : ''; - $passwordPlain = $_GET['p']; - Minz_Request::_param('p'); //Discard plain-text password ASAP - $_GET['p'] = ''; - if (!function_exists('password_verify')) { - include_once(LIB_PATH . '/password_compat.php'); - } - try { - $conf = new FreshRSS_Configuration($username); - $s = $conf->passwordHash; - $ok = password_verify($passwordPlain, $s); - unset($passwordPlain); - if ($ok) { - Minz_Session::_param('currentUser', $username); - Minz_Session::_param('passwordHash', $s); - } else { - Minz_Log::record('Unsafe password mismatch for user ' . $username, Minz_Log::WARNING); - } - } catch (Minz_Exception $me) { - Minz_Log::record('Unsafe login failure: ' . $me->getMessage(), Minz_Log::WARNING); - } - Minz_Request::forward(array('c' => 'index', 'a' => 'index'), true); - } elseif (!Minz_Configuration::canLogIn()) { - Minz_Error::error ( - 403, - array ('error' => array (Minz_Translate::t ('access_denied'))) - ); + $get = FreshRSS_Context::currentGet(true); + if (count($get) > 1) { + $type = $get[0]; + $id = $get[1]; + } else { + $type = $get; + $id = ''; } - invalidateHttpCache(); - } - public function formLogoutAction () { - $this->view->_useLayout(false); - invalidateHttpCache(); - Minz_Session::_param('currentUser'); - Minz_Session::_param('mail'); - Minz_Session::_param('passwordHash'); - self::deleteLongTermCookie(); - Minz_Request::forward(array('c' => 'index', 'a' => 'index'), true); + return $entryDAO->listWhere( + $type, $id, FreshRSS_Context::$state, FreshRSS_Context::$order, + FreshRSS_Context::$number + 1, FreshRSS_Context::$first_id, + FreshRSS_Context::$search + ); } - 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; - } + /** + * This action displays the about page of FreshRSS. + */ + public function aboutAction() { + Minz_View::prependTitle(_t('about') . ' · '); + } - $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; + /** + * This action displays logs of FreshRSS for the current user. + */ + public function logsAction() { + if (!FreshRSS_Auth::hasAccess()) { + Minz_Error::error(403); } - invalidateHttpCache(); + Minz_View::prependTitle(_t('logs') . ' · '); 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'); - } + FreshRSS_LogDAO::truncate(); + } - $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); + $logs = FreshRSS_LogDAO::lines(); //TODO: ask only the necessary lines - Minz_Request::bad(_t('invalid_login'), - array('c' => 'index', 'a' => 'resetAuth')); - } - } + //gestion pagination + $page = Minz_Request::param('page', 1); + $this->view->logsPaginator = new Minz_Paginator($logs); + $this->view->logsPaginator->_nbItemsPerPage(50); + $this->view->logsPaginator->_currentPage($page); } } diff --git a/app/Controllers/javascriptController.php b/app/Controllers/javascriptController.php index 67148350f..62f413989 100755 --- a/app/Controllers/javascriptController.php +++ b/app/Controllers/javascriptController.php @@ -1,14 +1,14 @@ <?php class FreshRSS_javascript_Controller extends Minz_ActionController { - public function firstAction () { - $this->view->_useLayout (false); + public function firstAction() { + $this->view->_useLayout(false); } - public function actualizeAction () { + public function actualizeAction() { header('Content-Type: text/javascript; charset=UTF-8'); $feedDAO = FreshRSS_Factory::createFeedDao(); - $this->view->feeds = $feedDAO->listFeedsOrderUpdate($this->view->conf->ttl_default); + $this->view->feeds = $feedDAO->listFeedsOrderUpdate(FreshRSS_Context::$conf->ttl_default); } public function nbUnreadsPerFeedAction() { @@ -37,7 +37,7 @@ class FreshRSS_javascript_Controller extends Minz_ActionController { return; //Success } } catch (Minz_Exception $me) { - Minz_Log::record('Nonce failure: ' . $me->getMessage(), Minz_Log::WARNING); + Minz_Log::warning('Nonce failure: ' . $me->getMessage()); } } $this->view->nonce = ''; //Failure diff --git a/app/Controllers/statsController.php b/app/Controllers/statsController.php index 3069be34d..18fbca6df 100644 --- a/app/Controllers/statsController.php +++ b/app/Controllers/statsController.php @@ -6,6 +6,19 @@ class FreshRSS_stats_Controller extends Minz_ActionController { /** + * 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 (!FreshRSS_Auth::hasAccess()) { + Minz_Error::error(403); + } + + Minz_View::prependTitle(_t('stats') . ' · '); + } + + /** * This action handles the statistic main page. * * It displays the statistic main page. @@ -99,7 +112,7 @@ class FreshRSS_stats_Controller extends Minz_ActionController { $categoryDAO = new FreshRSS_CategoryDAO(); $feedDAO = FreshRSS_Factory::createFeedDao(); Minz_View::appendScript(Minz_Url::display('/scripts/flotr2.min.js?' . @filemtime(PUBLIC_PATH . '/scripts/flotr2.min.js'))); - $id = Minz_Request::param ('id', null); + $id = Minz_Request::param('id', null); $this->view->categories = $categoryDAO->listCategories(); $this->view->feed = $feedDAO->searchById($id); $this->view->days = $statsDAO->getDays(); @@ -111,20 +124,4 @@ class FreshRSS_stats_Controller extends Minz_ActionController { $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( - 403, array('error' => array(Minz_Translate::t('access_denied'))) - ); - } - - Minz_View::prependTitle(Minz_Translate::t('stats') . ' · '); - } - } diff --git a/app/Controllers/subscriptionController.php b/app/Controllers/subscriptionController.php new file mode 100644 index 000000000..67b95eba6 --- /dev/null +++ b/app/Controllers/subscriptionController.php @@ -0,0 +1,115 @@ +<?php + +/** + * Controller to handle subscription actions. + */ +class FreshRSS_subscription_Controller extends Minz_ActionController { + /** + * 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 (!FreshRSS_Auth::hasAccess()) { + Minz_Error::error(403); + } + + $catDAO = new FreshRSS_CategoryDAO(); + + $catDAO->checkDefault(); + $this->view->categories = $catDAO->listCategories(false); + $this->view->default_category = $catDAO->getDefault(); + } + + /** + * This action handles the main subscription page + * + * It displays categories and associated feeds. + */ + public function indexAction() { + Minz_View::appendScript(Minz_Url::display('/scripts/category.js?' . + @filemtime(PUBLIC_PATH . '/scripts/category.js'))); + Minz_View::prependTitle(_t('subscription_management') . ' · '); + + $id = Minz_Request::param('id'); + if ($id !== false) { + $feedDAO = FreshRSS_Factory::createFeedDao(); + $this->view->feed = $feedDAO->searchById($id); + } + } + + /** + * 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() { + if (Minz_Request::param('ajax')) { + $this->view->_useLayout(false); + } + + $feedDAO = FreshRSS_Factory::createFeedDao(); + $this->view->feeds = $feedDAO->listFeeds(); + + $id = Minz_Request::param('id'); + if ($id === false || !isset($this->view->feeds[$id])) { + Minz_Error::error(404); + return; + } + + $this->view->feed = $this->view->feeds[$id]; + + Minz_View::prependTitle(_t('rss_feed_management') . ' · ' . $this->view->feed->name() . ' · '); + + if (Minz_Request::isPost()) { + $user = Minz_Request::param('http_user', ''); + $pass = Minz_Request::param('http_pass', ''); + + $httpAuth = ''; + if ($user != '' || $pass != '') { + $httpAuth = $user . ':' . $pass; + } + + $cat = intval(Minz_Request::param('category', 0)); + + $values = array( + 'name' => Minz_Request::param('name', ''), + 'description' => sanitizeHTML(Minz_Request::param('description', '', true)), + 'website' => Minz_Request::param('website', ''), + 'url' => Minz_Request::param('url', ''), + 'category' => $cat, + 'pathEntries' => Minz_Request::param('path_entries', ''), + 'priority' => intval(Minz_Request::param('priority', 0)), + 'httpAuth' => $httpAuth, + 'keep_history' => intval(Minz_Request::param('keep_history', -2)), + 'ttl' => intval(Minz_Request::param('ttl', -2)), + ); + + invalidateHttpCache(); + + if ($feedDAO->updateFeed($id, $values)) { + $this->view->feed->_category($cat); + $this->view->feed->faviconPrepare(); + + Minz_Request::good(_t('feed_updated'), array('c' => 'subscription', 'params' => array('id' => $id))); + } else { + Minz_Request::bad(_t('error_occurred_update'), array('c' => 'subscription')); + } + } + } +} diff --git a/app/Controllers/updateController.php b/app/Controllers/updateController.php index da5bddc65..0896b13ac 100644 --- a/app/Controllers/updateController.php +++ b/app/Controllers/updateController.php @@ -3,16 +3,12 @@ class FreshRSS_update_Controller extends Minz_ActionController { public function firstAction() { $current_user = Minz_Session::param('currentUser', ''); - if (!$this->view->loginOk && Minz_Configuration::isAdmin($current_user)) { - Minz_Error::error( - 403, - array('error' => array(_t('access_denied'))) - ); + if (!FreshRSS_Auth::hasAccess('admin')) { + Minz_Error::error(403); } 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; @@ -24,6 +20,8 @@ class FreshRSS_update_Controller extends Minz_ActionController { } public function indexAction() { + Minz_View::prependTitle(_t('update_system') . ' · '); + if (file_exists(UPDATE_FILENAME) && !is_writable(FRESHRSS_PATH)) { $this->view->message = array( 'status' => 'bad', @@ -108,6 +106,19 @@ class FreshRSS_update_Controller extends Minz_ActionController { require(UPDATE_FILENAME); + if (Minz_Request::param('post_conf', false)) { + $res = do_post_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')); + } + } + if (Minz_Request::isPost()) { save_info_update(); } @@ -116,14 +127,26 @@ class FreshRSS_update_Controller extends Minz_ActionController { $res = apply_update(); if ($res === true) { - @unlink(UPDATE_FILENAME); - @file_put_contents(DATA_PATH . '/last_update.txt', time()); - - Minz_Request::good(_t('update_finished')); + Minz_Request::forward(array( + 'c' => 'update', + 'a' => 'apply', + 'params' => array('post_conf' => true) + ), true); } else { Minz_Request::bad(_t('update_problem', $res), array('c' => 'update', 'a' => 'index')); } } } + + /** + * This action displays information about installation. + */ + public function checkInstallAction() { + Minz_View::prependTitle(_t('gen.title.check_install') . ' · '); + + $this->view->status_php = check_install_php(); + $this->view->status_files = check_install_files(); + $this->view->status_database = check_install_database(); + } } diff --git a/app/Controllers/usersController.php b/app/Controllers/userController.php index a9e6c32bc..5050571a9 100644 --- a/app/Controllers/usersController.php +++ b/app/Controllers/userController.php @@ -1,19 +1,30 @@ <?php -class FreshRSS_users_Controller extends Minz_ActionController { - - const BCRYPT_COST = 9; //Will also have to be computed client side on mobile devices, so do not use a too high cost - +/** + * Controller to handle user actions. + */ +class FreshRSS_user_Controller extends Minz_ActionController { + // Will also have to be computed client side on mobile devices, + // so do not use a too high cost + const BCRYPT_COST = 9; + + /** + * 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( - 403, - array('error' => array(Minz_Translate::t('access_denied'))) - ); + if (!FreshRSS_Auth::hasAccess()) { + Minz_Error::error(403); } } - public function authAction() { + /** + * This action displays the user profile page. + */ + public function profileAction() { + Minz_View::prependTitle(_t('gen.title.user_profile') . ' · '); + if (Minz_Request::isPost()) { $ok = true; @@ -28,9 +39,9 @@ class FreshRSS_users_Controller extends Minz_ActionController { $passwordPlain = ''; $passwordHash = preg_replace('/^\$2[xy]\$/', '\$2a\$', $passwordHash); //Compatibility with bcrypt.js $ok &= ($passwordHash != ''); - $this->view->conf->_passwordHash($passwordHash); + FreshRSS_Context::$conf->_passwordHash($passwordHash); } - Minz_Session::_param('passwordHash', $this->view->conf->passwordHash); + Minz_Session::_param('passwordHash', FreshRSS_Context::$conf->passwordHash); $passwordPlain = Minz_Request::param('apiPasswordPlain', '', true); if ($passwordPlain != '') { @@ -41,16 +52,17 @@ class FreshRSS_users_Controller extends Minz_ActionController { $passwordPlain = ''; $passwordHash = preg_replace('/^\$2[xy]\$/', '\$2a\$', $passwordHash); //Compatibility with bcrypt.js $ok &= ($passwordHash != ''); - $this->view->conf->_apiPasswordHash($passwordHash); + FreshRSS_Context::$conf->_apiPasswordHash($passwordHash); } - if (Minz_Configuration::isAdmin(Minz_Session::param('currentUser', '_'))) { - $this->view->conf->_mail_login(Minz_Request::param('mail_login', '', true)); + // TODO: why do we need of hasAccess here? + if (FreshRSS_Auth::hasAccess('admin')) { + FreshRSS_Context::$conf->_mail_login(Minz_Request::param('mail_login', '', true)); } - $email = $this->view->conf->mail_login; + $email = FreshRSS_Context::$conf->mail_login; Minz_Session::_param('mail', $email); - $ok &= $this->view->conf->save(); + $ok &= FreshRSS_Context::$conf->save(); if ($email != '') { $personaFile = DATA_PATH . '/persona/' . $email . '.txt'; @@ -58,53 +70,47 @@ class FreshRSS_users_Controller extends Minz_ActionController { $ok &= (file_put_contents($personaFile, Minz_Session::param('currentUser', '_')) !== false); } - if (Minz_Configuration::isAdmin(Minz_Session::param('currentUser', '_'))) { - $current_token = $this->view->conf->token; - $token = Minz_Request::param('token', $current_token); - $this->view->conf->_token($token); - $ok &= $this->view->conf->save(); - - $anon = Minz_Request::param('anon_access', false); - $anon = ((bool)$anon) && ($anon !== 'no'); - $anon_refresh = Minz_Request::param('anon_refresh', false); - $anon_refresh = ((bool)$anon_refresh) && ($anon_refresh !== 'no'); - $auth_type = Minz_Request::param('auth_type', 'none'); - $unsafe_autologin = Minz_Request::param('unsafe_autologin', false); - $api_enabled = Minz_Request::param('api_enabled', false); - if ($anon != Minz_Configuration::allowAnonymous() || - $auth_type != Minz_Configuration::authType() || - $anon_refresh != Minz_Configuration::allowAnonymousRefresh() || - $unsafe_autologin != Minz_Configuration::unsafeAutologinEnabled() || - $api_enabled != Minz_Configuration::apiEnabled()) { - - Minz_Configuration::_authType($auth_type); - Minz_Configuration::_allowAnonymous($anon); - Minz_Configuration::_allowAnonymousRefresh($anon_refresh); - Minz_Configuration::_enableAutologin($unsafe_autologin); - Minz_Configuration::_enableApi($api_enabled); - $ok &= Minz_Configuration::writeFile(); - } + if ($ok) { + Minz_Request::good(_t('feedback.user_profile.updated'), + array('c' => 'user', 'a' => 'profile')); + } else { + Minz_Request::bad(_t('error_occurred'), + array('c' => 'user', 'a' => 'profile')); } + } + } - invalidateHttpCache(); + /** + * This action displays the user management page. + */ + public function manageAction() { + if (!FreshRSS_Auth::hasAccess('admin')) { + Minz_Error::error(403); + } - $notif = array( - 'type' => $ok ? 'good' : 'bad', - 'content' => Minz_Translate::t($ok ? 'configuration_updated' : 'error_occurred') - ); - Minz_Session::_param('notification', $notif); + Minz_View::prependTitle(_t('gen.title.user_management') . ' · '); + + // Get the correct current user. + $username = Minz_Request::param('u', Minz_Session::param('currentUser')); + if (!FreshRSS_UserDAO::exist($username)) { + $username = Minz_Session::param('currentUser'); } - Minz_Request::forward(array('c' => 'configure', 'a' => 'users'), true); + $this->view->current_user = $username; + + // Get information about the current user. + $entryDAO = FreshRSS_Factory::createEntryDao($this->view->current_user); + $this->view->nb_articles = $entryDAO->count(); + $this->view->size_user = $entryDAO->size(); } public function createAction() { - if (Minz_Request::isPost() && Minz_Configuration::isAdmin(Minz_Session::param('currentUser', '_'))) { + if (Minz_Request::isPost() && FreshRSS_Auth::hasAccess('admin')) { $db = Minz_Configuration::dataBase(); require_once(APP_PATH . '/SQL/install.sql.' . $db['type'] . '.php'); - $new_user_language = Minz_Request::param('new_user_language', $this->view->conf->language); - if (!in_array($new_user_language, $this->view->conf->availableLanguages())) { - $new_user_language = $this->view->conf->language; + $new_user_language = Minz_Request::param('new_user_language', FreshRSS_Context::$conf->language); + if (!in_array($new_user_language, FreshRSS_Context::$conf->availableLanguages())) { + $new_user_language = FreshRSS_Context::$conf->language; } $new_user_name = Minz_Request::param('new_user_name'); @@ -162,15 +168,16 @@ class FreshRSS_users_Controller extends Minz_ActionController { $notif = array( 'type' => $ok ? 'good' : 'bad', - 'content' => Minz_Translate::t($ok ? 'user_created' : 'error_occurred', $new_user_name) + 'content' => _t($ok ? 'user_created' : 'error_occurred', $new_user_name) ); Minz_Session::_param('notification', $notif); } - Minz_Request::forward(array('c' => 'configure', 'a' => 'users'), true); + + Minz_Request::forward(array('c' => 'user', 'a' => 'manage'), true); } public function deleteAction() { - if (Minz_Request::isPost() && Minz_Configuration::isAdmin(Minz_Session::param('currentUser', '_'))) { + if (Minz_Request::isPost() && FreshRSS_Auth::hasAccess('admin')) { $db = Minz_Configuration::dataBase(); require_once(APP_PATH . '/SQL/install.sql.' . $db['type'] . '.php'); @@ -194,10 +201,11 @@ class FreshRSS_users_Controller extends Minz_ActionController { $notif = array( 'type' => $ok ? 'good' : 'bad', - 'content' => Minz_Translate::t($ok ? 'user_deleted' : 'error_occurred', $username) + 'content' => _t($ok ? 'user_deleted' : 'error_occurred', $username) ); Minz_Session::_param('notification', $notif); } - Minz_Request::forward(array('c' => 'configure', 'a' => 'users'), true); + + Minz_Request::forward(array('c' => 'user', 'a' => 'manage'), true); } } diff --git a/app/Exceptions/BadUrlException.php b/app/Exceptions/BadUrlException.php index 7d1fe110e..59574e1e5 100644 --- a/app/Exceptions/BadUrlException.php +++ b/app/Exceptions/BadUrlException.php @@ -1,6 +1,6 @@ <?php class FreshRSS_BadUrl_Exception extends FreshRSS_Feed_Exception { - public function __construct ($url) { - parent::__construct ('`' . $url . '` is not a valid URL'); + public function __construct($url) { + parent::__construct('`' . $url . '` is not a valid URL'); } } diff --git a/app/Exceptions/ContextException.php b/app/Exceptions/ContextException.php new file mode 100644 index 000000000..357751b7c --- /dev/null +++ b/app/Exceptions/ContextException.php @@ -0,0 +1,10 @@ +<?php + +/** + * An exception raised when a context is invalid + */ +class FreshRSS_Context_Exception extends Exception { + public function __construct($message) { + parent::__construct($message); + } +} diff --git a/app/Exceptions/EntriesGetterException.php b/app/Exceptions/EntriesGetterException.php index eaa330979..5f47c830b 100644 --- a/app/Exceptions/EntriesGetterException.php +++ b/app/Exceptions/EntriesGetterException.php @@ -1,7 +1,7 @@ <?php class FreshRSS_EntriesGetter_Exception extends Exception { - public function __construct ($message) { - parent::__construct ($message); + public function __construct($message) { + parent::__construct($message); } } diff --git a/app/Exceptions/FeedException.php b/app/Exceptions/FeedException.php index 50918ba95..2433b3964 100644 --- a/app/Exceptions/FeedException.php +++ b/app/Exceptions/FeedException.php @@ -1,6 +1,6 @@ <?php class FreshRSS_Feed_Exception extends Exception { - public function __construct ($message) { - parent::__construct ($message); + public function __construct($message) { + parent::__construct($message); } } diff --git a/app/FreshRSS.php b/app/FreshRSS.php index cdf8962cb..6114a5d1a 100644 --- a/app/FreshRSS.php +++ b/app/FreshRSS.php @@ -1,146 +1,42 @@ <?php + class FreshRSS extends Minz_FrontController { public function init() { if (!isset($_SESSION)) { Minz_Session::init('FreshRSS'); } - $loginOk = $this->accessControl(Minz_Session::param('currentUser', '')); - $this->loadParamsView(); + + // Need to be called just after session init because it initializes + // current user. + FreshRSS_Auth::init(); + if (Minz_Request::isPost() && !is_referer_from_same_domain()) { - $loginOk = false; //Basic protection against XSRF attacks + // Basic protection against XSRF attacks + FreshRSS_Auth::removeAccess(); + $http_referer = empty($_SERVER['HTTP_REFERER']) ? '' : $_SERVER['HTTP_REFERER']; Minz_Error::error( 403, - array('error' => array(Minz_Translate::t('access_denied') . ' [HTTP_REFERER=' . - htmlspecialchars(empty($_SERVER['HTTP_REFERER']) ? '' : $_SERVER['HTTP_REFERER']) . ']')) + array('error' => array( + _t('access_denied'), + ' [HTTP_REFERER=' . htmlspecialchars($http_referer) . ']' + )) ); } - Minz_View::_param('loginOk', $loginOk); - $this->loadStylesAndScripts($loginOk); //TODO: Do not load that when not needed, e.g. some Ajax requests - $this->loadNotifications(); - } - private static function getCredentialsFromLongTermCookie() { - $token = Minz_Session::getLongTermCookie('FreshRSS_login'); - if (!ctype_alnum($token)) { - return array(); - } - $tokenFile = DATA_PATH . '/tokens/' . $token . '.txt'; - $mtime = @filemtime($tokenFile); - if ($mtime + 2629744 < time()) { //1 month //TODO: Use a configuration instead - @unlink($tokenFile); - return array(); //Expired or token does not exist - } - $credentials = @file_get_contents($tokenFile); - return $credentials === false ? array() : explode("\t", $credentials, 2); - } + // Load context and configuration. + FreshRSS_Context::init(); - private function accessControl($currentUser) { - if ($currentUser == '') { - switch (Minz_Configuration::authType()) { - case 'form': - $credentials = self::getCredentialsFromLongTermCookie(); - if (isset($credentials[1])) { - $currentUser = trim($credentials[0]); - Minz_Session::_param('passwordHash', trim($credentials[1])); - } - $loginOk = $currentUser != ''; - if (!$loginOk) { - $currentUser = Minz_Configuration::defaultUser(); - Minz_Session::_param('passwordHash'); - } - break; - case 'http_auth': - $currentUser = httpAuthUser(); - $loginOk = $currentUser != ''; - break; - case 'persona': - $loginOk = false; - $email = filter_var(Minz_Session::param('mail'), FILTER_VALIDATE_EMAIL); - if ($email != '') { //TODO: Remove redundancy with indexController - $personaFile = DATA_PATH . '/persona/' . $email . '.txt'; - if (($currentUser = @file_get_contents($personaFile)) !== false) { - $currentUser = trim($currentUser); - $loginOk = true; - } - } - if (!$loginOk) { - $currentUser = Minz_Configuration::defaultUser(); - } - break; - case 'none': - $currentUser = Minz_Configuration::defaultUser(); - $loginOk = true; - break; - default: - $currentUser = Minz_Configuration::defaultUser(); - $loginOk = false; - break; - } - } else { - $loginOk = true; - } - - if (!ctype_alnum($currentUser)) { - Minz_Session::_param('currentUser', ''); - die('Invalid username [' . $currentUser . ']!'); - } - - try { - $this->conf = new FreshRSS_Configuration($currentUser); - Minz_View::_param ('conf', $this->conf); - Minz_Session::_param('currentUser', $currentUser); - } catch (Minz_Exception $me) { - $loginOk = false; - try { - $this->conf = new FreshRSS_Configuration(Minz_Configuration::defaultUser()); - Minz_Session::_param('currentUser', Minz_Configuration::defaultUser()); - Minz_View::_param('conf', $this->conf); - $notif = array( - 'type' => 'bad', - 'content' => 'Invalid configuration for user [' . $currentUser . ']!', - ); - Minz_Session::_param ('notification', $notif); - Minz_Log::record ($notif['content'] . ' ' . $me->getMessage(), Minz_Log::WARNING); - Minz_Session::_param('currentUser', ''); - } catch (Exception $e) { - die($e->getMessage()); - } - } - - if ($loginOk) { - switch (Minz_Configuration::authType()) { - case 'form': - $loginOk = Minz_Session::param('passwordHash') === $this->conf->passwordHash; - break; - case 'http_auth': - $loginOk = strcasecmp($currentUser, httpAuthUser()) === 0; - break; - case 'persona': - $loginOk = strcasecmp(Minz_Session::param('mail'), $this->conf->mail_login) === 0; - break; - case 'none': - $loginOk = true; - break; - default: - $loginOk = false; - break; - } - } - return $loginOk; - } - - private function loadParamsView () { - Minz_Session::_param ('language', $this->conf->language); + // Init i18n. + Minz_Session::_param('language', FreshRSS_Context::$conf->language); Minz_Translate::init(); - $output = Minz_Request::param ('output', ''); - if (($output === '') || ($output !== 'normal' && $output !== 'rss' && $output !== 'reader' && $output !== 'global')) { - $output = $this->conf->view_mode; - Minz_Request::_param ('output', $output); - } + + $this->loadStylesAndScripts(); + $this->loadNotifications(); + $this->loadExtensions(); } - private function loadStylesAndScripts($loginOk) { - $theme = FreshRSS_Themes::load($this->conf->theme); + private function loadStylesAndScripts() { + $theme = FreshRSS_Themes::load(FreshRSS_Context::$conf->theme); if ($theme) { foreach($theme['files'] as $file) { if ($file[0] === '_') { @@ -157,26 +53,44 @@ class FreshRSS extends Minz_FrontController { } } - switch (Minz_Configuration::authType()) { - case 'form': - if (!$loginOk) { - Minz_View::appendScript(Minz_Url::display ('/scripts/bcrypt.min.js?' . @filemtime(PUBLIC_PATH . '/scripts/bcrypt.min.js'))); - } - break; - case 'persona': - Minz_View::appendScript('https://login.persona.org/include.js'); - break; - } Minz_View::appendScript(Minz_Url::display('/scripts/jquery.min.js?' . @filemtime(PUBLIC_PATH . '/scripts/jquery.min.js'))); Minz_View::appendScript(Minz_Url::display('/scripts/shortcut.js?' . @filemtime(PUBLIC_PATH . '/scripts/shortcut.js'))); Minz_View::appendScript(Minz_Url::display('/scripts/main.js?' . @filemtime(PUBLIC_PATH . '/scripts/main.js'))); + + if (Minz_Configuration::authType() === 'persona') { + // TODO move it in a plugin + // Needed for login AND logout with Persona. + Minz_View::appendScript('https://login.persona.org/include.js'); + $file_mtime = @filemtime(PUBLIC_PATH . '/scripts/persona.js'); + Minz_View::appendScript(Minz_Url::display('/scripts/persona.js?' . $file_mtime)); + } } - private function loadNotifications () { - $notif = Minz_Session::param ('notification'); + private function loadNotifications() { + $notif = Minz_Session::param('notification'); if ($notif) { - Minz_View::_param ('notification', $notif); - Minz_Session::_param ('notification'); + Minz_View::_param('notification', $notif); + Minz_Session::_param('notification'); + } + } + + private function loadExtensions() { + $extensionPath = FRESHRSS_PATH . '/extensions/'; + //TODO: Add a preference to load only user-selected extensions + foreach (scandir($extensionPath) as $key => $extension) { + if (ctype_alpha($extension)) { + $mtime = @filemtime($extensionPath . $extension . '/style.css'); + if ($mtime !== false) { + Minz_View::appendStyle(Minz_Url::display('/ext.php?c&e=' . $extension . '&' . $mtime)); + } + $mtime = @filemtime($extensionPath . $extension . '/script.js'); + if ($mtime !== false) { + Minz_View::appendScript(Minz_Url::display('/ext.php?j&e=' . $extension . '&' . $mtime)); + } + if (file_exists($extensionPath . $extension . '/module.php')) { + //TODO: include + } + } } } } diff --git a/app/Models/Auth.php b/app/Models/Auth.php new file mode 100644 index 000000000..2971d65c8 --- /dev/null +++ b/app/Models/Auth.php @@ -0,0 +1,232 @@ +<?php + +/** + * This class handles all authentication process. + */ +class FreshRSS_Auth { + /** + * Determines if user is connected. + */ + private static $login_ok = false; + + /** + * This method initializes authentication system. + */ + public static function init() { + self::$login_ok = Minz_Session::param('loginOk', false); + $current_user = Minz_Session::param('currentUser', ''); + if ($current_user === '') { + $current_user = Minz_Configuration::defaultUser(); + Minz_Session::_param('currentUser', $current_user); + } + + if (self::$login_ok) { + self::giveAccess(); + } elseif (self::accessControl()) { + self::giveAccess(); + FreshRSS_UserDAO::touch($current_user); + } else { + // Be sure all accesses are removed! + self::removeAccess(); + } + } + + /** + * This method checks if user is allowed to connect. + * + * Required session parameters are also set in this method (such as + * currentUser). + * + * @return boolean true if user can be connected, false else. + */ + private static function accessControl() { + switch (Minz_Configuration::authType()) { + case 'form': + $credentials = FreshRSS_FormAuth::getCredentialsFromCookie(); + $current_user = ''; + if (isset($credentials[1])) { + $current_user = trim($credentials[0]); + Minz_Session::_param('currentUser', $current_user); + Minz_Session::_param('passwordHash', trim($credentials[1])); + } + return $current_user != ''; + case 'http_auth': + $current_user = httpAuthUser(); + $login_ok = $current_user != ''; + if ($login_ok) { + Minz_Session::_param('currentUser', $current_user); + } + return $login_ok; + case 'persona': + $email = filter_var(Minz_Session::param('mail'), FILTER_VALIDATE_EMAIL); + $persona_file = DATA_PATH . '/persona/' . $email . '.txt'; + if (($current_user = @file_get_contents($persona_file)) !== false) { + $current_user = trim($current_user); + Minz_Session::_param('currentUser', $current_user); + Minz_Session::_param('mail', $email); + return true; + } + return false; + case 'none': + return true; + default: + // TODO load extension + return false; + } + } + + /** + * Gives access to the current user. + */ + public static function giveAccess() { + $current_user = Minz_Session::param('currentUser'); + try { + $conf = new FreshRSS_Configuration($current_user); + } catch(Minz_Exception $e) { + die($e->getMessage()); + } + + switch (Minz_Configuration::authType()) { + case 'form': + self::$login_ok = Minz_Session::param('passwordHash') === $conf->passwordHash; + break; + case 'http_auth': + self::$login_ok = strcasecmp($current_user, httpAuthUser()) === 0; + break; + case 'persona': + self::$login_ok = strcasecmp(Minz_Session::param('mail'), $conf->mail_login) === 0; + break; + case 'none': + self::$login_ok = true; + break; + default: + // TODO: extensions + self::$login_ok = false; + } + + Minz_Session::_param('loginOk', self::$login_ok); + } + + /** + * Returns if current user has access to the given scope. + * + * @param string $scope general (default) or admin + * @return boolean true if user has corresponding access, false else. + */ + public static function hasAccess($scope = 'general') { + $ok = self::$login_ok; + switch ($scope) { + case 'general': + break; + case 'admin': + $ok &= Minz_Session::param('currentUser') === Minz_Configuration::defaultUser(); + break; + default: + $ok = false; + } + return $ok; + } + + /** + * Removes all accesses for the current user. + */ + public static function removeAccess() { + Minz_Session::_param('loginOk'); + self::$login_ok = false; + Minz_Session::_param('currentUser', Minz_Configuration::defaultUser()); + + switch (Minz_Configuration::authType()) { + case 'form': + Minz_Session::_param('passwordHash'); + FreshRSS_FormAuth::deleteCookie(); + break; + case 'persona': + Minz_Session::_param('mail'); + break; + case 'http_auth': + case 'none': + // Nothing to do... + break; + default: + // TODO: extensions + } + } +} + + +class FreshRSS_FormAuth { + public static function checkCredentials($username, $hash, $nonce, $challenge) { + if (!ctype_alnum($username) || + !ctype_graph($challenge) || + !ctype_alnum($nonce)) { + Minz_Log::debug('Invalid credential parameters:' . + ' user=' . $username . + ' challenge=' . $challenge . + ' nonce=' . $nonce); + return false; + } + + if (!function_exists('password_verify')) { + include_once(LIB_PATH . '/password_compat.php'); + } + + return password_verify($nonce . $hash, $challenge); + } + + public static function getCredentialsFromCookie() { + $token = Minz_Session::getLongTermCookie('FreshRSS_login'); + if (!ctype_alnum($token)) { + return array(); + } + + $token_file = DATA_PATH . '/tokens/' . $token . '.txt'; + $mtime = @filemtime($token_file); + if ($mtime + 2629744 < time()) { + // Token has expired (> 1 month) or does not exist. + // TODO: 1 month -> use a configuration instead + @unlink($token_file); + return array(); + } + + $credentials = @file_get_contents($token_file); + return $credentials === false ? array() : explode("\t", $credentials, 2); + } + + public static function makeCookie($username, $password_hash) { + do { + $token = sha1(Minz_Configuration::salt() . $username . uniqid(mt_rand(), true)); + $token_file = DATA_PATH . '/tokens/' . $token . '.txt'; + } while (file_exists($token_file)); + + if (@file_put_contents($token_file, $username . "\t" . $password_hash) === false) { + return false; + } + + $expire = time() + 2629744; //1 month //TODO: Use a configuration instead + Minz_Session::setLongTermCookie('FreshRSS_login', $token, $expire); + return $token; + } + + public static function deleteCookie() { + $token = Minz_Session::getLongTermCookie('FreshRSS_login'); + Minz_Session::deleteLongTermCookie('FreshRSS_login'); + if (ctype_alnum($token)) { + @unlink(DATA_PATH . '/tokens/' . $token . '.txt'); + } + + if (rand(0, 10) === 1) { + self::purgeTokens(); + } + } + + public static function purgeTokens() { + $oldest = time() - 2629744; // 1 month // TODO: Use a configuration instead + foreach (new DirectoryIterator(DATA_PATH . '/tokens/') as $file_info) { + // $extension = $file_info->getExtension(); doesn't work in PHP < 5.3.7 + $extension = pathinfo($file_info->getFilename(), PATHINFO_EXTENSION); + if ($extension === 'txt' && $file_info->getMTime() < $oldest) { + @unlink($file_info->getPathname()); + } + } + } +} diff --git a/app/Models/Category.php b/app/Models/Category.php index 0a0dbd3ca..37cb44dc3 100644 --- a/app/Models/Category.php +++ b/app/Models/Category.php @@ -7,65 +7,65 @@ class FreshRSS_Category extends Minz_Model { private $nbNotRead = -1; private $feeds = null; - public function __construct ($name = '', $feeds = null) { - $this->_name ($name); - if (isset ($feeds)) { - $this->_feeds ($feeds); + public function __construct($name = '', $feeds = null) { + $this->_name($name); + if (isset($feeds)) { + $this->_feeds($feeds); $this->nbFeed = 0; $this->nbNotRead = 0; foreach ($feeds as $feed) { $this->nbFeed++; - $this->nbNotRead += $feed->nbNotRead (); + $this->nbNotRead += $feed->nbNotRead(); } } } - public function id () { + public function id() { return $this->id; } - public function name () { + public function name() { return $this->name; } - public function nbFeed () { + public function nbFeed() { if ($this->nbFeed < 0) { - $catDAO = new FreshRSS_CategoryDAO (); - $this->nbFeed = $catDAO->countFeed ($this->id ()); + $catDAO = new FreshRSS_CategoryDAO(); + $this->nbFeed = $catDAO->countFeed($this->id()); } return $this->nbFeed; } - public function nbNotRead () { + public function nbNotRead() { if ($this->nbNotRead < 0) { - $catDAO = new FreshRSS_CategoryDAO (); - $this->nbNotRead = $catDAO->countNotRead ($this->id ()); + $catDAO = new FreshRSS_CategoryDAO(); + $this->nbNotRead = $catDAO->countNotRead($this->id()); } return $this->nbNotRead; } - public function feeds () { + public function feeds() { if ($this->feeds === null) { $feedDAO = FreshRSS_Factory::createFeedDao(); - $this->feeds = $feedDAO->listByCategory ($this->id ()); + $this->feeds = $feedDAO->listByCategory($this->id()); $this->nbFeed = 0; $this->nbNotRead = 0; foreach ($this->feeds as $feed) { $this->nbFeed++; - $this->nbNotRead += $feed->nbNotRead (); + $this->nbNotRead += $feed->nbNotRead(); } } return $this->feeds; } - public function _id ($value) { + public function _id($value) { $this->id = $value; } - public function _name ($value) { - $this->name = $value; + public function _name($value) { + $this->name = substr(trim($value), 0, 255); } - public function _feeds ($values) { - if (!is_array ($values)) { - $values = array ($values); + public function _feeds($values) { + if (!is_array($values)) { + $values = array($values); } $this->feeds = $values; diff --git a/app/Models/CategoryDAO.php b/app/Models/CategoryDAO.php index f11f87f47..2e333d2f1 100644 --- a/app/Models/CategoryDAO.php +++ b/app/Models/CategoryDAO.php @@ -1,19 +1,19 @@ <?php class FreshRSS_CategoryDAO extends Minz_ModelPdo { - public function addCategory ($valuesTmp) { - $sql = 'INSERT INTO `' . $this->prefix . 'category` (name) VALUES(?)'; - $stm = $this->bd->prepare ($sql); + public function addCategory($valuesTmp) { + $sql = 'INSERT INTO `' . $this->prefix . 'category`(name) VALUES(?)'; + $stm = $this->bd->prepare($sql); - $values = array ( + $values = array( substr($valuesTmp['name'], 0, 255), ); - if ($stm && $stm->execute ($values)) { + if ($stm && $stm->execute($values)) { return $this->bd->lastInsertId(); } else { $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); - Minz_Log::record('SQL error addCategory: ' . $info[2], Minz_Log::ERROR); + Minz_Log::error('SQL error addCategory: ' . $info[2] ); return false; } } @@ -31,73 +31,73 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo { return $cat->id(); } - public function updateCategory ($id, $valuesTmp) { + public function updateCategory($id, $valuesTmp) { $sql = 'UPDATE `' . $this->prefix . 'category` SET name=? WHERE id=?'; - $stm = $this->bd->prepare ($sql); + $stm = $this->bd->prepare($sql); - $values = array ( + $values = array( $valuesTmp['name'], $id ); - if ($stm && $stm->execute ($values)) { + if ($stm && $stm->execute($values)) { return $stm->rowCount(); } else { $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); - Minz_Log::record('SQL error updateCategory: ' . $info[2], Minz_Log::ERROR); + Minz_Log::error('SQL error updateCategory: ' . $info[2]); return false; } } - public function deleteCategory ($id) { + public function deleteCategory($id) { $sql = 'DELETE FROM `' . $this->prefix . 'category` WHERE id=?'; - $stm = $this->bd->prepare ($sql); + $stm = $this->bd->prepare($sql); - $values = array ($id); + $values = array($id); - if ($stm && $stm->execute ($values)) { + if ($stm && $stm->execute($values)) { return $stm->rowCount(); } else { $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); - Minz_Log::record('SQL error deleteCategory: ' . $info[2], Minz_Log::ERROR); + Minz_Log::error('SQL error deleteCategory: ' . $info[2]); return false; } } - public function searchById ($id) { + public function searchById($id) { $sql = 'SELECT * FROM `' . $this->prefix . 'category` WHERE id=?'; - $stm = $this->bd->prepare ($sql); + $stm = $this->bd->prepare($sql); - $values = array ($id); + $values = array($id); - $stm->execute ($values); - $res = $stm->fetchAll (PDO::FETCH_ASSOC); - $cat = self::daoToCategory ($res); + $stm->execute($values); + $res = $stm->fetchAll(PDO::FETCH_ASSOC); + $cat = self::daoToCategory($res); - if (isset ($cat[0])) { + if (isset($cat[0])) { return $cat[0]; } else { return null; } } - public function searchByName ($name) { + public function searchByName($name) { $sql = 'SELECT * FROM `' . $this->prefix . 'category` WHERE name=?'; - $stm = $this->bd->prepare ($sql); + $stm = $this->bd->prepare($sql); - $values = array ($name); + $values = array($name); - $stm->execute ($values); - $res = $stm->fetchAll (PDO::FETCH_ASSOC); - $cat = self::daoToCategory ($res); + $stm->execute($values); + $res = $stm->fetchAll(PDO::FETCH_ASSOC); + $cat = self::daoToCategory($res); - if (isset ($cat[0])) { + if (isset($cat[0])) { return $cat[0]; } else { return null; } } - public function listCategories ($prePopulateFeeds = true, $details = false) { + public function listCategories($prePopulateFeeds = true, $details = false) { if ($prePopulateFeeds) { $sql = 'SELECT c.id AS c_id, c.name AS c_name, ' . ($details ? 'f.* ' : 'f.id, f.name, f.url, f.website, f.priority, f.error, f.cache_nbEntries, f.cache_nbUnreads ') @@ -105,80 +105,80 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo { . 'LEFT OUTER JOIN `' . $this->prefix . 'feed` f ON f.category=c.id ' . 'GROUP BY f.id ' . 'ORDER BY c.name, f.name'; - $stm = $this->bd->prepare ($sql); - $stm->execute (); - return self::daoToCategoryPrepopulated ($stm->fetchAll (PDO::FETCH_ASSOC)); + $stm = $this->bd->prepare($sql); + $stm->execute(); + return self::daoToCategoryPrepopulated($stm->fetchAll(PDO::FETCH_ASSOC)); } else { $sql = 'SELECT * FROM `' . $this->prefix . 'category` ORDER BY name'; - $stm = $this->bd->prepare ($sql); - $stm->execute (); - return self::daoToCategory ($stm->fetchAll (PDO::FETCH_ASSOC)); + $stm = $this->bd->prepare($sql); + $stm->execute(); + return self::daoToCategory($stm->fetchAll(PDO::FETCH_ASSOC)); } } - public function getDefault () { + public function getDefault() { $sql = 'SELECT * FROM `' . $this->prefix . 'category` WHERE id=1'; - $stm = $this->bd->prepare ($sql); + $stm = $this->bd->prepare($sql); - $stm->execute (); - $res = $stm->fetchAll (PDO::FETCH_ASSOC); - $cat = self::daoToCategory ($res); + $stm->execute(); + $res = $stm->fetchAll(PDO::FETCH_ASSOC); + $cat = self::daoToCategory($res); - if (isset ($cat[0])) { + if (isset($cat[0])) { return $cat[0]; } else { return false; } } - public function checkDefault () { - $def_cat = $this->searchById (1); + public function checkDefault() { + $def_cat = $this->searchById(1); if ($def_cat == null) { - $cat = new FreshRSS_Category (Minz_Translate::t ('default_category')); - $cat->_id (1); + $cat = new FreshRSS_Category(_t('default_category')); + $cat->_id(1); - $values = array ( - 'id' => $cat->id (), - 'name' => $cat->name (), + $values = array( + 'id' => $cat->id(), + 'name' => $cat->name(), ); - $this->addCategory ($values); + $this->addCategory($values); } } - public function count () { + public function count() { $sql = 'SELECT COUNT(*) AS count FROM `' . $this->prefix . 'category`'; - $stm = $this->bd->prepare ($sql); - $stm->execute (); - $res = $stm->fetchAll (PDO::FETCH_ASSOC); + $stm = $this->bd->prepare($sql); + $stm->execute(); + $res = $stm->fetchAll(PDO::FETCH_ASSOC); return $res[0]['count']; } - public function countFeed ($id) { + public function countFeed($id) { $sql = 'SELECT COUNT(*) AS count FROM `' . $this->prefix . 'feed` WHERE category=?'; - $stm = $this->bd->prepare ($sql); - $values = array ($id); - $stm->execute ($values); - $res = $stm->fetchAll (PDO::FETCH_ASSOC); + $stm = $this->bd->prepare($sql); + $values = array($id); + $stm->execute($values); + $res = $stm->fetchAll(PDO::FETCH_ASSOC); return $res[0]['count']; } - public function countNotRead ($id) { + public function countNotRead($id) { $sql = 'SELECT COUNT(*) AS count FROM `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed=f.id WHERE category=? AND e.is_read=0'; - $stm = $this->bd->prepare ($sql); - $values = array ($id); - $stm->execute ($values); - $res = $stm->fetchAll (PDO::FETCH_ASSOC); + $stm = $this->bd->prepare($sql); + $values = array($id); + $stm->execute($values); + $res = $stm->fetchAll(PDO::FETCH_ASSOC); return $res[0]['count']; } public static function findFeed($categories, $feed_id) { foreach ($categories as $category) { - foreach ($category->feeds () as $feed) { - if ($feed->id () === $feed_id) { + foreach ($category->feeds() as $feed) { + if ($feed->id() === $feed_id) { return $feed; } } @@ -189,8 +189,8 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo { public static function CountUnreads($categories, $minPriority = 0) { $n = 0; foreach ($categories as $category) { - foreach ($category->feeds () as $feed) { - if ($feed->priority () >= $minPriority) { + foreach ($category->feeds() as $feed) { + if ($feed->priority() >= $minPriority) { $n += $feed->nbNotRead(); } } @@ -198,11 +198,11 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo { return $n; } - public static function daoToCategoryPrepopulated ($listDAO) { - $list = array (); + public static function daoToCategoryPrepopulated($listDAO) { + $list = array(); - if (!is_array ($listDAO)) { - $listDAO = array ($listDAO); + if (!is_array($listDAO)) { + $listDAO = array($listDAO); } $previousLine = null; @@ -210,11 +210,11 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo { foreach ($listDAO as $line) { if ($previousLine['c_id'] != null && $line['c_id'] !== $previousLine['c_id']) { // End of the current category, we add it to the $list - $cat = new FreshRSS_Category ( + $cat = new FreshRSS_Category( $previousLine['c_name'], - FreshRSS_FeedDAO::daoToFeed ($feedsDao, $previousLine['c_id']) + FreshRSS_FeedDAO::daoToFeed($feedsDao, $previousLine['c_id']) ); - $cat->_id ($previousLine['c_id']); + $cat->_id($previousLine['c_id']); $list[$previousLine['c_id']] = $cat; $feedsDao = array(); //Prepare for next category @@ -226,29 +226,29 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo { // add the last category if ($previousLine != null) { - $cat = new FreshRSS_Category ( + $cat = new FreshRSS_Category( $previousLine['c_name'], - FreshRSS_FeedDAO::daoToFeed ($feedsDao, $previousLine['c_id']) + FreshRSS_FeedDAO::daoToFeed($feedsDao, $previousLine['c_id']) ); - $cat->_id ($previousLine['c_id']); + $cat->_id($previousLine['c_id']); $list[$previousLine['c_id']] = $cat; } return $list; } - public static function daoToCategory ($listDAO) { - $list = array (); + public static function daoToCategory($listDAO) { + $list = array(); - if (!is_array ($listDAO)) { - $listDAO = array ($listDAO); + if (!is_array($listDAO)) { + $listDAO = array($listDAO); } foreach ($listDAO as $key => $dao) { - $cat = new FreshRSS_Category ( + $cat = new FreshRSS_Category( $dao['name'] ); - $cat->_id ($dao['id']); + $cat->_id($dao['id']); $list[$key] = $cat; } diff --git a/app/Models/Configuration.php b/app/Models/Configuration.php index 95f819779..53f136513 100644 --- a/app/Models/Configuration.php +++ b/app/Models/Configuration.php @@ -14,7 +14,8 @@ class FreshRSS_Configuration { 'apiPasswordHash' => '', //CRYPT_BLOWFISH 'posts_per_page' => 20, 'view_mode' => 'normal', - 'default_view' => FreshRSS_Entry::STATE_NOT_READ, + 'default_view' => 'adaptive', + 'default_state' => FreshRSS_Entry::STATE_NOT_READ, 'auto_load_more' => true, 'display_posts' => false, 'display_categories' => false, @@ -47,6 +48,7 @@ class FreshRSS_Configuration { 'focus_search' => 'a', 'user_filter' => 'u', 'help' => 'f1', + 'close_dropdown' => 'escape', ), 'topline_read' => true, 'topline_favorite' => true, @@ -139,44 +141,48 @@ class FreshRSS_Configuration { } $this->data['language'] = $value; } - public function _posts_per_page ($value) { + public function _posts_per_page($value) { $value = intval($value); $this->data['posts_per_page'] = $value > 0 ? $value : 10; } - public function _view_mode ($value) { + public function _view_mode($value) { if ($value === 'global' || $value === 'reader') { $this->data['view_mode'] = $value; } else { $this->data['view_mode'] = 'normal'; } } - public function _default_view ($value) { + public function _default_view($value) { 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: + case 'all': $this->data['default_view'] = $value; + $this->data['default_state'] = (FreshRSS_Entry::STATE_READ + + FreshRSS_Entry::STATE_NOT_READ); break; + case 'adaptive': + case 'unread': default: - $this->data['default_view'] = FreshRSS_Entry::STATE_ALL; - break; + $this->data['default_view'] = $value; + $this->data['default_state'] = FreshRSS_Entry::STATE_NOT_READ; } } - public function _display_posts ($value) { + public function _default_state($value) { + $this->data['default_state'] = (int)$value; + } + + public function _display_posts($value) { $this->data['display_posts'] = ((bool)$value) && $value !== 'no'; } - public function _display_categories ($value) { + public function _display_categories($value) { $this->data['display_categories'] = ((bool)$value) && $value !== 'no'; } public function _hide_read_feeds($value) { $this->data['hide_read_feeds'] = (bool)$value; } - public function _onread_jump_next ($value) { + public function _onread_jump_next($value) { $this->data['onread_jump_next'] = ((bool)$value) && $value !== 'no'; } - public function _lazyload ($value) { + public function _lazyload($value) { $this->data['lazyload'] = ((bool)$value) && $value !== 'no'; } public function _sticky_post($value) { @@ -185,7 +191,7 @@ class FreshRSS_Configuration { public function _reading_confirm($value) { $this->data['reading_confirm'] = ((bool)$value) && $value !== 'no'; } - public function _sort_order ($value) { + public function _sort_order($value) { $this->data['sort_order'] = $value === 'ASC' ? 'ASC' : 'DESC'; } public function _old_entries($value) { @@ -200,20 +206,20 @@ class FreshRSS_Configuration { $value = intval($value); $this->data['ttl_default'] = $value >= -1 ? $value : 3600; } - public function _shortcuts ($values) { + public function _shortcuts($values) { foreach ($values as $key => $value) { if (isset($this->data['shortcuts'][$key])) { $this->data['shortcuts'][$key] = $value; } } } - public function _passwordHash ($value) { + public function _passwordHash($value) { $this->data['passwordHash'] = ctype_graph($value) && (strlen($value) >= 60) ? $value : ''; } - public function _apiPasswordHash ($value) { + public function _apiPasswordHash($value) { $this->data['apiPasswordHash'] = ctype_graph($value) && (strlen($value) >= 60) ? $value : ''; } - public function _mail_login ($value) { + public function _mail_login($value) { $value = filter_var($value, FILTER_VALIDATE_EMAIL); if ($value) { $this->data['mail_login'] = $value; @@ -221,17 +227,17 @@ class FreshRSS_Configuration { $this->data['mail_login'] = ''; } } - public function _anon_access ($value) { + public function _anon_access($value) { $this->data['anon_access'] = ((bool)$value) && $value !== 'no'; } - public function _mark_when ($values) { + public function _mark_when($values) { foreach ($values as $key => $value) { if (isset($this->data['mark_when'][$key])) { $this->data['mark_when'][$key] = ((bool)$value) && $value !== 'no'; } } } - public function _sharing ($values) { + public function _sharing($values) { $this->data['sharing'] = array(); $unique = array(); foreach ($values as $value) { @@ -242,7 +248,7 @@ class FreshRSS_Configuration { // Verify URL and add default value when needed if (isset($value['url'])) { $is_url = ( - filter_var ($value['url'], FILTER_VALIDATE_URL) || + filter_var($value['url'], FILTER_VALIDATE_URL) || (version_compare(PHP_VERSION, '5.3.3', '<') && (strpos($value, '-') > 0) && ($value === filter_var($value, FILTER_SANITIZE_URL))) @@ -266,7 +272,7 @@ class FreshRSS_Configuration { } } } - public function _queries ($values) { + public function _queries($values) { $this->data['queries'] = array(); foreach ($values as $value) { $value = array_filter($value); @@ -291,7 +297,7 @@ class FreshRSS_Configuration { } } - public function _html5_notif_timeout ($value) { + public function _html5_notif_timeout($value) { $value = intval($value); $this->data['html5_notif_timeout'] = $value >= 0 ? $value : 0; } diff --git a/app/Models/Context.php b/app/Models/Context.php new file mode 100644 index 000000000..36c4087eb --- /dev/null +++ b/app/Models/Context.php @@ -0,0 +1,268 @@ +<?php + +/** + * The context object handles the current configuration file and different + * useful functions associated to the current view state. + */ +class FreshRSS_Context { + public static $conf = null; + public static $categories = array(); + + public static $name = ''; + + public static $total_unread = 0; + public static $total_starred = array( + 'all' => 0, + 'read' => 0, + 'unread' => 0, + ); + + public static $get_unread = 0; + public static $current_get = array( + 'all' => false, + 'starred' => false, + 'feed' => false, + 'category' => false, + ); + public static $next_get = 'a'; + + public static $state = 0; + public static $order = 'DESC'; + public static $number = 0; + public static $search = ''; + public static $first_id = ''; + public static $next_id = ''; + public static $id_max = ''; + + /** + * Initialize the context. + * + * Set the correct $conf and $categories variables. + */ + public static function init() { + // Init configuration. + $current_user = Minz_Session::param('currentUser'); + try { + self::$conf = new FreshRSS_Configuration($current_user); + } catch(Minz_Exception $e) { + Minz_Log::error('Cannot load configuration file of user `' . $current_user . '`'); + die($e->getMessage()); + } + + $catDAO = new FreshRSS_CategoryDAO(); + self::$categories = $catDAO->listCategories(); + } + + /** + * Returns if the current state includes $state parameter. + */ + public static function isStateEnabled($state) { + return self::$state & $state; + } + + /** + * Returns the current state with or without $state parameter. + */ + public static function getRevertState($state) { + if (self::$state & $state) { + return self::$state & ~$state; + } else { + return self::$state | $state; + } + } + + /** + * Return the current get as a string or an array. + * + * If $array is true, the first item of the returned value is 'f' or 'c' and + * the second is the id. + */ + public static function currentGet($array = false) { + if (self::$current_get['all']) { + return 'a'; + } elseif (self::$current_get['starred']) { + return 's'; + } elseif (self::$current_get['feed']) { + if ($array) { + return array('f', self::$current_get['feed']); + } else { + return 'f_' . self::$current_get['feed']; + } + } elseif (self::$current_get['category']) { + if ($array) { + return array('c', self::$current_get['category']); + } else { + return 'c_' . self::$current_get['category']; + } + } + } + + /** + * Return true if $get parameter correspond to the $current_get attribute. + */ + public static function isCurrentGet($get) { + $type = $get[0]; + $id = substr($get, 2); + + switch($type) { + case 'a': + return self::$current_get['all']; + case 's': + return self::$current_get['starred']; + case 'f': + return self::$current_get['feed'] == $id; + case 'c': + return self::$current_get['category'] == $id; + default: + return false; + } + } + + /** + * Set the current $get attribute. + * + * Valid $get parameter are: + * - a + * - s + * - f_<feed id> + * - c_<category id> + * + * $name and $get_unread attributes are also updated as $next_get + * Raise an exception if id or $get is invalid. + */ + public static function _get($get) { + $type = $get[0]; + $id = substr($get, 2); + $nb_unread = 0; + + switch($type) { + case 'a': + self::$current_get['all'] = true; + self::$name = _t('your_rss_feeds'); + self::$get_unread = self::$total_unread; + break; + case 's': + self::$current_get['starred'] = true; + self::$name = _t('your_favorites'); + self::$get_unread = self::$total_starred['unread']; + + // Update state if favorite is not yet enabled. + self::$state = self::$state | FreshRSS_Entry::STATE_FAVORITE; + break; + case 'f': + // We try to find the corresponding feed. + $feed = FreshRSS_CategoryDAO::findFeed(self::$categories, $id); + if ($feed === null) { + $feedDAO = FreshRSS_Factory::createFeedDao(); + $feed = $feedDAO->searchById($id); + + if (!$feed) { + throw new FreshRSS_Context_Exception('Invalid feed: ' . $id); + } + } + + self::$current_get['feed'] = $id; + self::$current_get['category'] = $feed->category(); + self::$name = $feed->name(); + self::$get_unread = $feed->nbNotRead(); + break; + case 'c': + // We try to find the corresponding category. + self::$current_get['category'] = $id; + if (!isset(self::$categories[$id])) { + $catDAO = new FreshRSS_CategoryDAO(); + $cat = $catDAO->searchById($id); + + if (!$cat) { + throw new FreshRSS_Context_Exception('Invalid category: ' . $id); + } + } else { + $cat = self::$categories[$id]; + } + + self::$name = $cat->name(); + self::$get_unread = $cat->nbNotRead(); + break; + default: + throw new FreshRSS_Context_Exception('Invalid getter: ' . $get); + } + + self::_nextGet(); + } + + /** + * Set the value of $next_get attribute. + */ + public static function _nextGet() { + $get = self::currentGet(); + // By default, $next_get == $get + self::$next_get = $get; + + if (self::$conf->onread_jump_next && strlen($get) > 2) { + $another_unread_id = ''; + $found_current_get = false; + switch ($get[0]) { + case 'f': + // We search the next feed with at least one unread article in + // same category as the currend feed. + foreach (self::$categories as $cat) { + if ($cat->id() != self::$current_get['category']) { + // We look into the category of the current feed! + continue; + } + + foreach ($cat->feeds() as $feed) { + if ($feed->id() == self::$current_get['feed']) { + // Here is our current feed! Fine, the next one will + // be a potential candidate. + $found_current_get = true; + continue; + } + + if ($feed->nbNotRead() > 0) { + $another_unread_id = $feed->id(); + if ($found_current_get) { + // We have found our current feed and now we + // have an feed with unread articles. Leave the + // loop! + break; + } + } + } + break; + } + + // If no feed have been found, next_get is the current category. + self::$next_get = empty($another_unread_id) ? + 'c_' . self::$current_get['category'] : + 'f_' . $another_unread_id; + break; + case 'c': + // We search the next category with at least one unread article. + foreach (self::$categories as $cat) { + if ($cat->id() == self::$current_get['category']) { + // Here is our current category! Next one could be our + // champion if it has unread articles. + $found_current_get = true; + continue; + } + + if ($cat->nbNotRead() > 0) { + $another_unread_id = $cat->id(); + if ($found_current_get) { + // Unread articles and the current category has + // already been found? Leave the loop! + break; + } + } + } + + // No unread category? The main stream will be our destination! + self::$next_get = empty($another_unread_id) ? + 'a' : + 'c_' . $another_unread_id; + break; + } + } + } +} diff --git a/app/Models/DatabaseDAO.php b/app/Models/DatabaseDAO.php new file mode 100644 index 000000000..0d85718e3 --- /dev/null +++ b/app/Models/DatabaseDAO.php @@ -0,0 +1,83 @@ +<?php + +/** + * This class is used to test database is well-constructed. + */ +class FreshRSS_DatabaseDAO extends Minz_ModelPdo { + public function tablesAreCorrect() { + $sql = 'SHOW TABLES'; + $stm = $this->bd->prepare($sql); + $stm->execute(); + $res = $stm->fetchAll(PDO::FETCH_ASSOC); + + $tables = array( + $this->prefix . 'category' => false, + $this->prefix . 'feed' => false, + $this->prefix . 'entry' => false, + ); + foreach ($res as $value) { + $tables[array_pop($value)] = true; + } + + return count(array_keys($tables, true, true)) == count($tables); + } + + public function getSchema($table) { + $sql = 'DESC ' . $this->prefix . $table; + $stm = $this->bd->prepare($sql); + $stm->execute(); + + return $this->listDaoToSchema($stm->fetchAll(PDO::FETCH_ASSOC)); + } + + public function checkTable($table, $schema) { + $columns = $this->getSchema($table); + + $ok = (count($columns) == count($schema)); + foreach ($columns as $c) { + $ok &= in_array($c['name'], $schema); + } + + return $ok; + } + + public function categoryIsCorrect() { + return $this->checkTable('category', array( + 'id', 'name' + )); + } + + public function feedIsCorrect() { + return $this->checkTable('feed', array( + 'id', 'url', 'category', 'name', 'website', 'description', 'lastUpdate', + 'priority', 'pathEntries', 'httpAuth', 'error', 'keep_history', 'ttl', + 'cache_nbEntries', 'cache_nbUnreads' + )); + } + + public function entryIsCorrect() { + return $this->checkTable('entry', array( + 'id', 'guid', 'title', 'author', 'content_bin', 'link', 'date', 'is_read', + 'is_favorite', 'id_feed', 'tags' + )); + } + + public function daoToSchema($dao) { + return array( + 'name' => $dao['Field'], + 'type' => strtolower($dao['Type']), + 'notnull' => (bool)$dao['Null'], + 'default' => $dao['Default'], + ); + } + + public function listDaoToSchema($listDAO) { + $list = array(); + + foreach ($listDAO as $dao) { + $list[] = $this->daoToSchema($dao); + } + + return $list; + } +} diff --git a/app/Models/DatabaseDAOSQLite.php b/app/Models/DatabaseDAOSQLite.php new file mode 100644 index 000000000..7f53f967d --- /dev/null +++ b/app/Models/DatabaseDAOSQLite.php @@ -0,0 +1,48 @@ +<?php + +/** + * This class is used to test database is well-constructed (SQLite). + */ +class FreshRSS_DatabaseDAOSQLite extends FreshRSS_DatabaseDAO { + public function tablesAreCorrect() { + $sql = 'SELECT name FROM sqlite_master WHERE type="table"'; + $stm = $this->bd->prepare($sql); + $stm->execute(); + $res = $stm->fetchAll(PDO::FETCH_ASSOC); + + $tables = array( + 'category' => false, + 'feed' => false, + 'entry' => false, + ); + foreach ($res as $value) { + $tables[$value['name']] = true; + } + + return count(array_keys($tables, true, true)) == count($tables); + } + + public function getSchema($table) { + $sql = 'PRAGMA table_info(' . $table . ')'; + $stm = $this->bd->prepare($sql); + $stm->execute(); + + return $this->listDaoToSchema($stm->fetchAll(PDO::FETCH_ASSOC)); + } + + public function entryIsCorrect() { + return $this->checkTable('entry', array( + 'id', 'guid', 'title', 'author', 'content', 'link', 'date', 'is_read', + 'is_favorite', 'id_feed', 'tags' + )); + } + + public function daoToSchema($dao) { + return array( + 'name' => $dao['name'], + 'type' => strtolower($dao['type']), + 'notnull' => $dao['notnull'] === '1' ? true : false, + 'default' => $dao['dflt_value'], + ); + } +} diff --git a/app/Models/Entry.php b/app/Models/Entry.php index 9d7dd5dc4..346c98a92 100644 --- a/app/Models/Entry.php +++ b/app/Models/Entry.php @@ -1,12 +1,11 @@ <?php class FreshRSS_Entry extends Minz_Model { - const STATE_ALL = 0; const STATE_READ = 1; const STATE_NOT_READ = 2; + const STATE_ALL = 3; const STATE_FAVORITE = 4; const STATE_NOT_FAVORITE = 8; - const STATE_STRICT = 16; private $id = 0; private $guid; @@ -20,134 +19,134 @@ class FreshRSS_Entry extends Minz_Model { private $feed; private $tags; - public function __construct ($feed = '', $guid = '', $title = '', $author = '', $content = '', - $link = '', $pubdate = 0, $is_read = false, $is_favorite = false, $tags = '') { - $this->_guid ($guid); - $this->_title ($title); - $this->_author ($author); - $this->_content ($content); - $this->_link ($link); - $this->_date ($pubdate); - $this->_isRead ($is_read); - $this->_isFavorite ($is_favorite); - $this->_feed ($feed); - $this->_tags (preg_split('/[\s#]/', $tags)); + public function __construct($feed = '', $guid = '', $title = '', $author = '', $content = '', + $link = '', $pubdate = 0, $is_read = false, $is_favorite = false, $tags = '') { + $this->_guid($guid); + $this->_title($title); + $this->_author($author); + $this->_content($content); + $this->_link($link); + $this->_date($pubdate); + $this->_isRead($is_read); + $this->_isFavorite($is_favorite); + $this->_feed($feed); + $this->_tags(preg_split('/[\s#]/', $tags)); } - public function id () { + public function id() { return $this->id; } - public function guid () { + public function guid() { return $this->guid; } - public function title () { + public function title() { return $this->title; } - public function author () { + public function author() { return $this->author === null ? '' : $this->author; } - public function content () { + public function content() { return $this->content; } - public function link () { + public function link() { return $this->link; } - public function date ($raw = false) { + public function date($raw = false) { if ($raw) { return $this->date; } else { - return timestamptodate ($this->date); + return timestamptodate($this->date); } } - public function dateAdded ($raw = false) { + public function dateAdded($raw = false) { $date = intval(substr($this->id, 0, -6)); if ($raw) { return $date; } else { - return timestamptodate ($date); + return timestamptodate($date); } } - public function isRead () { + public function isRead() { return $this->is_read; } - public function isFavorite () { + public function isFavorite() { return $this->is_favorite; } - public function feed ($object = false) { + public function feed($object = false) { if ($object) { $feedDAO = FreshRSS_Factory::createFeedDao(); - return $feedDAO->searchById ($this->feed); + return $feedDAO->searchById($this->feed); } else { return $this->feed; } } - public function tags ($inString = false) { + public function tags($inString = false) { if ($inString) { - return empty ($this->tags) ? '' : '#' . implode(' #', $this->tags); + return empty($this->tags) ? '' : '#' . implode(' #', $this->tags); } else { return $this->tags; } } - public function _id ($value) { + public function _id($value) { $this->id = $value; } - public function _guid ($value) { + public function _guid($value) { $this->guid = $value; } - public function _title ($value) { + public function _title($value) { $this->title = $value; } - public function _author ($value) { + public function _author($value) { $this->author = $value; } - public function _content ($value) { + public function _content($value) { $this->content = $value; } - public function _link ($value) { + public function _link($value) { $this->link = $value; } - public function _date ($value) { + public function _date($value) { $value = intval($value); $this->date = $value > 1 ? $value : time(); } - public function _isRead ($value) { + public function _isRead($value) { $this->is_read = $value; } - public function _isFavorite ($value) { + public function _isFavorite($value) { $this->is_favorite = $value; } - public function _feed ($value) { + public function _feed($value) { $this->feed = $value; } - public function _tags ($value) { - if (!is_array ($value)) { - $value = array ($value); + public function _tags($value) { + if (!is_array($value)) { + $value = array($value); } foreach ($value as $key => $t) { if (!$t) { - unset ($value[$key]); + unset($value[$key]); } } $this->tags = $value; } - public function isDay ($day, $today) { + public function isDay($day, $today) { $date = $this->dateAdded(true); switch ($day) { - case FreshRSS_Days::TODAY: - $tomorrow = $today + 86400; - return $date >= $today && $date < $tomorrow; - case FreshRSS_Days::YESTERDAY: - $yesterday = $today - 86400; - return $date >= $yesterday && $date < $today; - case FreshRSS_Days::BEFORE_YESTERDAY: - $yesterday = $today - 86400; - return $date < $yesterday; - default: - return false; + case FreshRSS_Days::TODAY: + $tomorrow = $today + 86400; + return $date >= $today && $date < $tomorrow; + case FreshRSS_Days::YESTERDAY: + $yesterday = $today - 86400; + return $date >= $yesterday && $date < $today; + case FreshRSS_Days::BEFORE_YESTERDAY: + $yesterday = $today - 86400; + return $date < $yesterday; + default: + return false; } } @@ -158,7 +157,7 @@ class FreshRSS_Entry extends Minz_Model { $entryDAO = FreshRSS_Factory::createEntryDao(); $entry = $entryDAO->searchByGuid($this->feed, $this->guid); - if($entry) { + if ($entry) { // l'article existe déjà en BDD, en se contente de recharger ce contenu $this->content = $entry->content(); } else { @@ -168,25 +167,25 @@ class FreshRSS_Entry extends Minz_Model { htmlspecialchars_decode($this->link(), ENT_QUOTES), $pathEntries ); } catch (Exception $e) { - // rien à faire, on garde l'ancien contenu (requête a échoué) + // rien à faire, on garde l'ancien contenu(requête a échoué) } } } } - public function toArray () { - return array ( - 'id' => $this->id (), - 'guid' => $this->guid (), - 'title' => $this->title (), - 'author' => $this->author (), - 'content' => $this->content (), - 'link' => $this->link (), - 'date' => $this->date (true), - 'is_read' => $this->isRead (), - 'is_favorite' => $this->isFavorite (), - 'id_feed' => $this->feed (), - 'tags' => $this->tags (true), + public function toArray() { + return array( + 'id' => $this->id(), + 'guid' => $this->guid(), + 'title' => $this->title(), + 'author' => $this->author(), + 'content' => $this->content(), + 'link' => $this->link(), + 'date' => $this->date(true), + 'is_read' => $this->isRead(), + 'is_favorite' => $this->isFavorite(), + 'id_feed' => $this->feed(), + 'tags' => $this->tags(true), ); } } diff --git a/app/Models/EntryDAO.php b/app/Models/EntryDAO.php index c1f87ee34..5d2909c62 100644 --- a/app/Models/EntryDAO.php +++ b/app/Models/EntryDAO.php @@ -40,11 +40,11 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { } else { $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); if ((int)($info[0] / 1000) !== 23) { //Filter out "SQLSTATE Class code 23: Constraint Violation" because of expected duplicate entries - Minz_Log::record('SQL error addEntry: ' . $info[0] . ': ' . $info[1] . ' ' . $info[2] - . ' while adding entry in feed ' . $valuesTmp['id_feed'] . ' with title: ' . $valuesTmp['title'], Minz_Log::ERROR); + Minz_Log::error('SQL error addEntry: ' . $info[0] . ': ' . $info[1] . ' ' . $info[2] + . ' while adding entry in feed ' . $valuesTmp['id_feed'] . ' with title: ' . $valuesTmp['title']); } /*else { - Minz_Log::record ('SQL error ' . $info[0] . ': ' . $info[1] . ' ' . $info[2] - . ' while adding entry in feed ' . $valuesTmp['id_feed'] . ' with title: ' . $valuesTmp['title'], Minz_Log::DEBUG); + Minz_Log::debug('SQL error ' . $info[0] . ': ' . $info[1] . ' ' . $info[2] + . ' while adding entry in feed ' . $valuesTmp['id_feed'] . ' with title: ' . $valuesTmp['title']); }*/ return false; } @@ -94,7 +94,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { return $stm->rowCount(); } else { $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); - Minz_Log::record('SQL error markFavorite: ' . $info[2], Minz_Log::ERROR); + Minz_Log::error('SQL error markFavorite: ' . $info[2]); return false; } } @@ -124,7 +124,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { return true; } else { $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); - Minz_Log::record('SQL error updateCacheUnreads: ' . $info[2], Minz_Log::ERROR); + Minz_Log::error('SQL error updateCacheUnreads: ' . $info[2]); return false; } } @@ -147,7 +147,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { $stm = $this->bd->prepare($sql); if (!($stm && $stm->execute($values))) { $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); - Minz_Log::record('SQL error markRead: ' . $info[2], Minz_Log::ERROR); + Minz_Log::error('SQL error markRead: ' . $info[2]); return false; } $affected = $stm->rowCount(); @@ -166,7 +166,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { return $stm->rowCount(); } else { $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); - Minz_Log::record('SQL error markRead: ' . $info[2], Minz_Log::ERROR); + Minz_Log::error('SQL error markRead: ' . $info[2]); return false; } } @@ -175,7 +175,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { public function markReadEntries($idMax = 0, $onlyFavorites = false, $priorityMin = 0) { if ($idMax == 0) { $idMax = time() . '000000'; - Minz_Log::record('Calling markReadEntries(0) is deprecated!', Minz_Log::DEBUG); + Minz_Log::debug('Calling markReadEntries(0) is deprecated!'); } $sql = 'UPDATE `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed=f.id ' @@ -190,7 +190,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { $stm = $this->bd->prepare($sql); if (!($stm && $stm->execute($values))) { $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); - Minz_Log::record('SQL error markReadEntries: ' . $info[2], Minz_Log::ERROR); + Minz_Log::error('SQL error markReadEntries: ' . $info[2]); return false; } $affected = $stm->rowCount(); @@ -203,7 +203,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { public function markReadCat($id, $idMax = 0) { if ($idMax == 0) { $idMax = time() . '000000'; - Minz_Log::record('Calling markReadCat(0) is deprecated!', Minz_Log::DEBUG); + Minz_Log::debug('Calling markReadCat(0) is deprecated!'); } $sql = 'UPDATE `' . $this->prefix . 'entry` e INNER JOIN `' . $this->prefix . 'feed` f ON e.id_feed=f.id ' @@ -213,7 +213,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { $stm = $this->bd->prepare($sql); if (!($stm && $stm->execute($values))) { $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); - Minz_Log::record('SQL error markReadCat: ' . $info[2], Minz_Log::ERROR); + Minz_Log::error('SQL error markReadCat: ' . $info[2]); return false; } $affected = $stm->rowCount(); @@ -226,7 +226,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { public function markReadFeed($id, $idMax = 0) { if ($idMax == 0) { $idMax = time() . '000000'; - Minz_Log::record('Calling markReadFeed(0) is deprecated!', Minz_Log::DEBUG); + Minz_Log::debug('Calling markReadFeed(0) is deprecated!'); } $this->bd->beginTransaction(); @@ -237,7 +237,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { $stm = $this->bd->prepare($sql); if (!($stm && $stm->execute($values))) { $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); - Minz_Log::record('SQL error markReadFeed: ' . $info[2], Minz_Log::ERROR); + Minz_Log::error('SQL error markReadFeed: ' . $info[2]); $this->bd->rollBack(); return false; } @@ -251,7 +251,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { $stm = $this->bd->prepare($sql); if (!($stm && $stm->execute($values))) { $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); - Minz_Log::record('SQL error markReadFeed: ' . $info[2], Minz_Log::ERROR); + Minz_Log::error('SQL error markReadFeed: ' . $info[2]); $this->bd->rollBack(); return false; } @@ -299,7 +299,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { return 'CONCAT(' . $s1 . ',' . $s2 . ')'; //MySQL } - private function sqlListWhere($type = 'a', $id = '', $state = FreshRSS_Entry::STATE_ALL, $order = 'DESC', $limit = 1, $firstId = '', $filter = '', $date_min = 0, $showOlderUnreadsorFavorites = false, $keepHistoryDefault = 0) { + private function sqlListWhere($type = 'a', $id = '', $state = FreshRSS_Entry::STATE_ALL, $order = 'DESC', $limit = 1, $firstId = '', $filter = '', $date_min = 0) { if (!$state) { $state = FreshRSS_Entry::STATE_ALL; } @@ -307,34 +307,32 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { $joinFeed = false; $values = array(); switch ($type) { - case 'a': - $where .= 'f.priority > 0 '; - $joinFeed = true; - break; - case 's': //Deprecated: use $state instead - $where .= 'e1.is_favorite=1 '; - break; - case 'c': - $where .= 'f.category=? '; - $values[] = intval($id); - $joinFeed = true; - break; - case 'f': - $where .= 'e1.id_feed=? '; - $values[] = intval($id); - break; - case 'A': - $where .= '1 '; - break; - default: - throw new FreshRSS_EntriesGetter_Exception('Bad type in Entry->listByType: [' . $type . ']!'); + case 'a': + $where .= 'f.priority > 0 '; + $joinFeed = true; + break; + case 's': //Deprecated: use $state instead + $where .= 'e1.is_favorite=1 '; + break; + case 'c': + $where .= 'f.category=? '; + $values[] = intval($id); + $joinFeed = true; + break; + case 'f': + $where .= 'e1.id_feed=? '; + $values[] = intval($id); + break; + case 'A': + $where .= '1 '; + break; + default: + throw new FreshRSS_EntriesGetter_Exception('Bad type in Entry->listByType: [' . $type . ']!'); } 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) { @@ -356,23 +354,14 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { default: throw new FreshRSS_EntriesGetter_Exception('Bad order in Entry->listByType: [' . $order . ']!'); } - if ($firstId === '' && parent::$sharedDbType === 'mysql') { - $firstId = $order === 'DESC' ? '9000000000'. '000000' : '0'; //MySQL optimization. Tested on MySQL 5.5 with 150k articles - } + /*if ($firstId === '' && parent::$sharedDbType === 'mysql') { + $firstId = $order === 'DESC' ? '9000000000'. '000000' : '0'; //MySQL optimization. TODO: check if this is needed again, after the filtering for old articles has been removed in 0.9-dev + }*/ if ($firstId !== '') { $where .= 'AND e1.id ' . ($order === 'DESC' ? '<=' : '>=') . $firstId . ' '; } - if (($date_min > 0) && ($type !== 's')) { - $where .= 'AND (e1.id >= ' . $date_min . '000000'; - if ($showOlderUnreadsorFavorites) { //Lax date constraint - $where .= ' OR e1.is_read=0 OR e1.is_favorite=1 OR (f.keep_history <> 0'; - if (intval($keepHistoryDefault) === 0) { - $where .= ' AND f.keep_history <> -2'; //default - } - $where .= ')'; - } - $where .= ') '; - $joinFeed = true; + if ($date_min > 0) { + $where .= 'AND e1.id >= ' . $date_min . '000000 '; } $search = ''; if ($filter !== '') { @@ -434,8 +423,8 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { . ($limit > 0 ? ' LIMIT ' . $limit : '')); //TODO: See http://explainextended.com/2009/10/23/mysql-order-by-limit-performance-late-row-lookups/ } - public function listWhere($type = 'a', $id = '', $state = FreshRSS_Entry::STATE_ALL, $order = 'DESC', $limit = 1, $firstId = '', $filter = '', $date_min = 0, $showOlderUnreadsorFavorites = false, $keepHistoryDefault = 0) { - list($values, $sql) = $this->sqlListWhere($type, $id, $state, $order, $limit, $firstId, $filter, $date_min, $showOlderUnreadsorFavorites, $keepHistoryDefault); + public function listWhere($type = 'a', $id = '', $state = FreshRSS_Entry::STATE_ALL, $order = 'DESC', $limit = 1, $firstId = '', $filter = '', $date_min = 0) { + list($values, $sql) = $this->sqlListWhere($type, $id, $state, $order, $limit, $firstId, $filter, $date_min); $sql = 'SELECT e.id, e.guid, e.title, e.author, ' . ($this->isCompressed() ? 'UNCOMPRESS(content_bin) AS content' : 'content') @@ -452,8 +441,8 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { return self::daoToEntry($stm->fetchAll(PDO::FETCH_ASSOC)); } - public function listIdsWhere($type = 'a', $id = '', $state = FreshRSS_Entry::STATE_ALL, $order = 'DESC', $limit = 1, $firstId = '', $filter = '', $date_min = 0, $showOlderUnreadsorFavorites = false, $keepHistoryDefault = 0) { //For API - list($values, $sql) = $this->sqlListWhere($type, $id, $state, $order, $limit, $firstId, $filter, $date_min, $showOlderUnreadsorFavorites, $keepHistoryDefault); + public function listIdsWhere($type = 'a', $id = '', $state = FreshRSS_Entry::STATE_ALL, $order = 'DESC', $limit = 1, $firstId = '', $filter = '', $date_min = 0) { //For API + list($values, $sql) = $this->sqlListWhere($type, $id, $state, $order, $limit, $firstId, $filter, $date_min); $stm = $this->bd->prepare($sql); $stm->execute($values); diff --git a/app/Models/EntryDAOSQLite.php b/app/Models/EntryDAOSQLite.php index 9dc395c3c..4a3fe24a2 100644 --- a/app/Models/EntryDAOSQLite.php +++ b/app/Models/EntryDAOSQLite.php @@ -26,7 +26,7 @@ class FreshRSS_EntryDAOSQLite extends FreshRSS_EntryDAO { return true; } else { $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); - Minz_Log::record('SQL error updateCacheUnreads: ' . $info[2], Minz_Log::ERROR); + Minz_Log::error('SQL error updateCacheUnreads: ' . $info[2]); return false; } } @@ -47,7 +47,7 @@ class FreshRSS_EntryDAOSQLite extends FreshRSS_EntryDAO { $stm = $this->bd->prepare($sql); if (!($stm && $stm->execute($values))) { $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); - Minz_Log::record('SQL error markRead 1: ' . $info[2], Minz_Log::ERROR); + Minz_Log::error('SQL error markRead 1: ' . $info[2]); $this->bd->rollBack(); return false; } @@ -59,7 +59,7 @@ class FreshRSS_EntryDAOSQLite extends FreshRSS_EntryDAO { $stm = $this->bd->prepare($sql); if (!($stm && $stm->execute($values))) { $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); - Minz_Log::record('SQL error markRead 2: ' . $info[2], Minz_Log::ERROR); + Minz_Log::error('SQL error markRead 2: ' . $info[2]); $this->bd->rollBack(); return false; } @@ -72,7 +72,7 @@ class FreshRSS_EntryDAOSQLite extends FreshRSS_EntryDAO { public function markReadEntries($idMax = 0, $onlyFavorites = false, $priorityMin = 0) { if ($idMax == 0) { $idMax = time() . '000000'; - Minz_Log::record('Calling markReadEntries(0) is deprecated!', Minz_Log::DEBUG); + Minz_Log::debug('Calling markReadEntries(0) is deprecated!'); } $sql = 'UPDATE `' . $this->prefix . 'entry` SET is_read=1 WHERE is_read=0 AND id <= ?'; @@ -85,7 +85,7 @@ class FreshRSS_EntryDAOSQLite extends FreshRSS_EntryDAO { $stm = $this->bd->prepare($sql); if (!($stm && $stm->execute($values))) { $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); - Minz_Log::record('SQL error markReadEntries: ' . $info[2], Minz_Log::ERROR); + Minz_Log::error('SQL error markReadEntries: ' . $info[2]); return false; } $affected = $stm->rowCount(); @@ -98,7 +98,7 @@ class FreshRSS_EntryDAOSQLite extends FreshRSS_EntryDAO { public function markReadCat($id, $idMax = 0) { if ($idMax == 0) { $idMax = time() . '000000'; - Minz_Log::record('Calling markReadCat(0) is deprecated!', Minz_Log::DEBUG); + Minz_Log::debug('Calling markReadCat(0) is deprecated!'); } $sql = 'UPDATE `' . $this->prefix . 'entry` ' @@ -109,7 +109,7 @@ class FreshRSS_EntryDAOSQLite extends FreshRSS_EntryDAO { $stm = $this->bd->prepare($sql); if (!($stm && $stm->execute($values))) { $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); - Minz_Log::record('SQL error markReadCat: ' . $info[2], Minz_Log::ERROR); + Minz_Log::error('SQL error markReadCat: ' . $info[2]); return false; } $affected = $stm->rowCount(); @@ -124,6 +124,6 @@ class FreshRSS_EntryDAOSQLite extends FreshRSS_EntryDAO { } public function size($all = false) { - return @filesize(DATA_PATH . '/' . Minz_Session::param('currentUser', '_') . '.sqlite'); + return @filesize(DATA_PATH . '/' . $this->current_user . '.sqlite'); } } diff --git a/app/Models/Factory.php b/app/Models/Factory.php index 08569b2e2..91cb84998 100644 --- a/app/Models/Factory.php +++ b/app/Models/Factory.php @@ -2,30 +2,39 @@ class FreshRSS_Factory { - public static function createFeedDao() { + public static function createFeedDao($username = null) { $db = Minz_Configuration::dataBase(); if ($db['type'] === 'sqlite') { - return new FreshRSS_FeedDAOSQLite(); + return new FreshRSS_FeedDAOSQLite($username); } else { - return new FreshRSS_FeedDAO(); + return new FreshRSS_FeedDAO($username); } } - public static function createEntryDao() { + public static function createEntryDao($username = null) { $db = Minz_Configuration::dataBase(); if ($db['type'] === 'sqlite') { - return new FreshRSS_EntryDAOSQLite(); + return new FreshRSS_EntryDAOSQLite($username); } else { - return new FreshRSS_EntryDAO(); + return new FreshRSS_EntryDAO($username); } } - public static function createStatsDAO() { + public static function createStatsDAO($username = null) { $db = Minz_Configuration::dataBase(); if ($db['type'] === 'sqlite') { - return new FreshRSS_StatsDAOSQLite(); + return new FreshRSS_StatsDAOSQLite($username); } else { - return new FreshRSS_StatsDAO(); + return new FreshRSS_StatsDAO($username); + } + } + + public static function createDatabaseDAO($username = null) { + $db = Minz_Configuration::dataBase(); + if ($db['type'] === 'sqlite') { + return new FreshRSS_DatabaseDAOSQLite($username); + } else { + return new FreshRSS_DatabaseDAO($username); } } diff --git a/app/Models/Feed.php b/app/Models/Feed.php index 03baf3ad2..bd1babeea 100644 --- a/app/Models/Feed.php +++ b/app/Models/Feed.php @@ -217,7 +217,8 @@ class FreshRSS_Feed extends Minz_Model { $mtime = $feed->init(); if ((!$mtime) || $feed->error()) { - throw new FreshRSS_Feed_Exception($feed->error() . ' [' . $url . ']'); + $errorMessage = $feed->error(); + throw new FreshRSS_Feed_Exception(($errorMessage == '' ? 'Feed error' : $errorMessage) . ' [' . $url . ']'); } if ($loadDetails) { diff --git a/app/Models/FeedDAO.php b/app/Models/FeedDAO.php index b89ae2045..74597c730 100644 --- a/app/Models/FeedDAO.php +++ b/app/Models/FeedDAO.php @@ -19,7 +19,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo { return $this->bd->lastInsertId(); } else { $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); - Minz_Log::record('SQL error addFeed: ' . $info[2], Minz_Log::ERROR); + Minz_Log::error('SQL error addFeed: ' . $info[2]); return false; } } @@ -77,7 +77,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo { return $stm->rowCount(); } else { $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); - Minz_Log::record('SQL error updateFeed: ' . $info[2], Minz_Log::ERROR); + Minz_Log::error('SQL error updateFeed: ' . $info[2]); return false; } } @@ -107,7 +107,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo { return $stm->rowCount(); } else { $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); - Minz_Log::record('SQL error updateLastUpdate: ' . $info[2], Minz_Log::ERROR); + Minz_Log::error('SQL error updateLastUpdate: ' . $info[2]); return false; } } @@ -131,7 +131,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo { return $stm->rowCount(); } else { $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); - Minz_Log::record('SQL error changeCategory: ' . $info[2], Minz_Log::ERROR); + Minz_Log::error('SQL error changeCategory: ' . $info[2]); return false; } } @@ -146,7 +146,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo { return $stm->rowCount(); } else { $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); - Minz_Log::record('SQL error deleteFeed: ' . $info[2], Minz_Log::ERROR); + Minz_Log::error('SQL error deleteFeed: ' . $info[2]); return false; } } @@ -160,7 +160,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo { return $stm->rowCount(); } else { $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); - Minz_Log::record('SQL error deleteFeedByCategory: ' . $info[2], Minz_Log::ERROR); + Minz_Log::error('SQL error deleteFeedByCategory: ' . $info[2]); return false; } } @@ -191,7 +191,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo { $res = $stm->fetchAll(PDO::FETCH_ASSOC); $feed = current(self::daoToFeed($res)); - if (isset($feed)) { + if (isset($feed) && $feed !== false) { return $feed; } else { return null; @@ -289,7 +289,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo { return $stm->rowCount(); } else { $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); - Minz_Log::record('SQL error updateCachedValues: ' . $info[2], Minz_Log::ERROR); + Minz_Log::error('SQL error updateCachedValues: ' . $info[2]); return false; } } @@ -301,7 +301,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo { $this->bd->beginTransaction(); if (!($stm && $stm->execute($values))) { $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); - Minz_Log::record('SQL error truncate: ' . $info[2], Minz_Log::ERROR); + Minz_Log::error('SQL error truncate: ' . $info[2]); $this->bd->rollBack(); return false; } @@ -313,7 +313,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo { $stm = $this->bd->prepare($sql); if (!($stm && $stm->execute($values))) { $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); - Minz_Log::record('SQL error truncate: ' . $info[2], Minz_Log::ERROR); + Minz_Log::error('SQL error truncate: ' . $info[2]); $this->bd->rollBack(); return false; } @@ -338,7 +338,7 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo { return $stm->rowCount(); } else { $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); - Minz_Log::record('SQL error cleanOldEntries: ' . $info[2], Minz_Log::ERROR); + Minz_Log::error('SQL error cleanOldEntries: ' . $info[2]); return false; } } diff --git a/app/Models/FeedDAOSQLite.php b/app/Models/FeedDAOSQLite.php index 0d1872389..7599fda53 100644 --- a/app/Models/FeedDAOSQLite.php +++ b/app/Models/FeedDAOSQLite.php @@ -11,7 +11,7 @@ class FreshRSS_FeedDAOSQLite extends FreshRSS_FeedDAO { return $stm->rowCount(); } else { $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); - Minz_Log::record('SQL error updateCachedValues: ' . $info[2], Minz_Log::ERROR); + Minz_Log::error('SQL error updateCachedValues: ' . $info[2]); return false; } } diff --git a/app/Models/Log.php b/app/Models/Log.php index d2794458b..df2de72ac 100644 --- a/app/Models/Log.php +++ b/app/Models/Log.php @@ -5,22 +5,22 @@ class FreshRSS_Log extends Minz_Model { private $level; private $information; - public function date () { + public function date() { return $this->date; } - public function level () { + public function level() { return $this->level; } - public function info () { + public function info() { return $this->information; } - public function _date ($date) { + public function _date($date) { $this->date = $date; } - public function _level ($level) { + public function _level($level) { $this->level = $level; } - public function _info ($information) { + public function _info($information) { $this->information = $information; } } diff --git a/app/Models/LogDAO.php b/app/Models/LogDAO.php index d1e515200..21593435d 100644 --- a/app/Models/LogDAO.php +++ b/app/Models/LogDAO.php @@ -2,15 +2,15 @@ class FreshRSS_LogDAO { public static function lines() { - $logs = array (); + $logs = array(); $handle = @fopen(LOG_PATH . '/' . Minz_Session::param('currentUser', '_') . '.log', 'r'); if ($handle) { while (($line = fgets($handle)) !== false) { - if (preg_match ('/^\[([^\[]+)\] \[([^\[]+)\] --- (.*)$/', $line, $matches)) { + if (preg_match('/^\[([^\[]+)\] \[([^\[]+)\] --- (.*)$/', $line, $matches)) { $myLog = new FreshRSS_Log (); - $myLog->_date ($matches[1]); - $myLog->_level ($matches[2]); - $myLog->_info ($matches[3]); + $myLog->_date($matches[1]); + $myLog->_level($matches[2]); + $myLog->_info($matches[3]); $logs[] = $myLog; } } diff --git a/app/Models/StatsDAO.php b/app/Models/StatsDAO.php index 08dd4cd5c..283d5dcb1 100644 --- a/app/Models/StatsDAO.php +++ b/app/Models/StatsDAO.php @@ -237,7 +237,7 @@ SQL; $interval_in_days = $period; } - return round($res['count'] / ($interval_in_days / $period), 2); + return $res['count'] / ($interval_in_days / $period); } /** @@ -415,8 +415,8 @@ SQL; * @return string */ private function convertToTranslatedJson($data = array()) { - $translated = array_map(function ($a) { - return Minz_Translate::t($a); + $translated = array_map(function($a) { + return _t($a); }, $data); return json_encode($translated); diff --git a/app/Models/Themes.php b/app/Models/Themes.php index 68fc17a2b..e3b260261 100644 --- a/app/Models/Themes.php +++ b/app/Models/Themes.php @@ -82,6 +82,7 @@ class FreshRSS_Themes extends Minz_Model { 'favorite' => '★', 'help' => 'ⓘ', 'icon' => '⊚', + 'import' => '⤓', 'key' => '⚿', 'link' => '↗', 'login' => '🔒', diff --git a/app/Models/UserDAO.php b/app/Models/UserDAO.php index 9f64fb4a7..60fca71b1 100644 --- a/app/Models/UserDAO.php +++ b/app/Models/UserDAO.php @@ -9,7 +9,7 @@ class FreshRSS_UserDAO extends Minz_ModelPdo { $ok = false; if (defined('SQL_CREATE_TABLES')) { //E.g. MySQL - $sql = sprintf(SQL_CREATE_TABLES, $db['prefix'] . $username . '_', Minz_Translate::t('default_category')); + $sql = sprintf(SQL_CREATE_TABLES, $db['prefix'] . $username . '_', _t('default_category')); $stm = $userPDO->bd->prepare($sql); $ok = $stm && $stm->execute(); } else { //E.g. SQLite @@ -17,7 +17,7 @@ class FreshRSS_UserDAO extends Minz_ModelPdo { if (is_array($SQL_CREATE_TABLES)) { $ok = true; foreach ($SQL_CREATE_TABLES as $instruction) { - $sql = sprintf($instruction, '', Minz_Translate::t('default_category')); + $sql = sprintf($instruction, '', _t('default_category')); $stm = $userPDO->bd->prepare($sql); $ok &= ($stm && $stm->execute()); } @@ -28,7 +28,7 @@ class FreshRSS_UserDAO extends Minz_ModelPdo { return true; } else { $info = empty($stm) ? array(2 => 'syntax error') : $stm->errorInfo(); - Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR); + Minz_Log::error('SQL error : ' . $info[2]); return false; } } @@ -48,9 +48,21 @@ class FreshRSS_UserDAO extends Minz_ModelPdo { return true; } else { $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); - Minz_Log::record ('SQL error : ' . $info[2], Minz_Log::ERROR); + Minz_Log::error('SQL error : ' . $info[2]); return false; } } } + + public static function exist($username) { + return file_exists(DATA_PATH . '/' . $username . '_user.php'); + } + + public static function touch($username) { + return touch(DATA_PATH . '/' . $username . '_user.php'); + } + + public static function mtime($username) { + return @filemtime(DATA_PATH . '/' . $username . '_user.php'); + } } diff --git a/app/SQL/install.sql.mysql.php b/app/SQL/install.sql.mysql.php index 16cb3a3b8..cf0159199 100644 --- a/app/SQL/install.sql.mysql.php +++ b/app/SQL/install.sql.mysql.php @@ -57,5 +57,3 @@ INSERT IGNORE INTO `%1$scategory` (id, name) VALUES(1, "%2$s"); '); define('SQL_DROP_TABLES', 'DROP TABLES %1$sentry, %1$sfeed, %1$scategory'); - -define('SQL_SHOW_TABLES', 'SHOW tables;'); diff --git a/app/SQL/install.sql.sqlite.php b/app/SQL/install.sql.sqlite.php index 7988ada04..30bca2810 100644 --- a/app/SQL/install.sql.sqlite.php +++ b/app/SQL/install.sql.sqlite.php @@ -55,5 +55,3 @@ $SQL_CREATE_TABLES = array( ); define('SQL_DROP_TABLES', 'DROP TABLES %1$sentry, %1$sfeed, %1$scategory'); - -define('SQL_SHOW_TABLES', 'SELECT name FROM sqlite_master WHERE type="table"'); diff --git a/app/actualize_script.php b/app/actualize_script.php index 4c306b8da..6ce4178cd 100755 --- a/app/actualize_script.php +++ b/app/actualize_script.php @@ -7,14 +7,32 @@ ob_implicit_flush(false); ob_start(); echo 'Results: ', "\n"; //Buffered +if (defined('STDOUT')) { + $begin_date = date_create('now'); + fwrite(STDOUT, 'Starting feed actualization at ' . $begin_date->format('c') . "\n"); //Unbuffered +} + Minz_Configuration::init(); $users = listUsers(); shuffle($users); //Process users in random order -array_unshift($users, Minz_Configuration::defaultUser()); //But always start with admin -$users = array_unique($users); + +if (Minz_Configuration::defaultUser() !== ''){ + array_unshift($users, Minz_Configuration::defaultUser()); //But always start with admin + $users = array_unique($users); +} + +$limits = Minz_Configuration::limits(); +$minLastActivity = time() - $limits['max_inactivity']; foreach ($users as $myUser) { + if (($myUser !== Minz_Configuration::defaultUser()) && (FreshRSS_UserDAO::mtime($myUser) < $minLastActivity)) { + syslog(LOG_INFO, 'FreshRSS skip inactive user ' . $myUser); + if (defined('STDOUT')) { + fwrite(STDOUT, 'FreshRSS skip inactive user ' . $myUser . "\n"); //Unbuffered + } + continue; + } syslog(LOG_INFO, 'FreshRSS actualize ' . $myUser); if (defined('STDOUT')) { fwrite(STDOUT, 'Actualize ' . $myUser . "...\n"); //Unbuffered @@ -49,6 +67,10 @@ foreach ($users as $myUser) { syslog(LOG_INFO, 'FreshRSS actualize done.'); if (defined('STDOUT')) { fwrite(STDOUT, 'Done.' . "\n"); + $end_date = date_create('now'); + $duration = date_diff($end_date, $begin_date); + fwrite(STDOUT, 'Ending feed actualization at ' . $end_date->format('c') . "\n"); //Unbuffered + fwrite(STDOUT, 'Feed actualizations took ' . $duration->format('%a day(s), %h hour(s), %i minute(s) and %s seconds') . ' for ' . count($users) . " users\n"); //Unbuffered } echo 'End.', "\n"; ob_end_flush(); diff --git a/app/i18n/en.php b/app/i18n/en.php index 0d3654744..edf7e0921 100644 --- a/app/i18n/en.php +++ b/app/i18n/en.php @@ -1,455 +1,533 @@ <?php - -return array ( - // LAYOUT - 'login' => 'Login', - 'keep_logged_in' => 'Keep me logged in <small>(1 month)</small>', - 'login_with_persona' => 'Login with Persona', - 'login_persona_problem' => 'Connection problem with Persona?', - 'logout' => 'Logout', - 'search' => 'Search words or #tags', - 'search_short' => 'Search', - - 'configuration' => 'Configuration', - 'users' => 'Users', - 'categories' => 'Categories', - 'category' => 'Category', - 'feed' => 'Feed', - 'feeds' => 'Feeds', - 'shortcuts' => 'Shortcuts', - 'queries' => 'User queries', - 'query_search' => 'Search for "%s"', - 'query_order_asc' => 'Display oldest articles first', - 'query_order_desc' => 'Display newest articles first', - 'query_get_category' => 'Display "%s" category', - 'query_get_feed' => 'Display "%s" feed', - 'query_get_all' => 'Display all articles', - 'query_get_favorite' => 'Display favorite articles', - 'query_state_0' => 'Display all articles', - 'query_state_1' => 'Display read articles', - 'query_state_2' => 'Display unread articles', - 'query_state_3' => 'Display all articles', - 'query_state_4' => 'Display favorite articles', - 'query_state_5' => 'Display read favorite articles', - 'query_state_6' => 'Display unread favorite articles', - 'query_state_7' => 'Display favorite articles', - 'query_state_8' => 'Display not favorite articles', - 'query_state_9' => 'Display read not favorite articles', - 'query_state_10' => 'Display unread not favorite articles', - 'query_state_11' => 'Display not favorite articles', - 'query_state_12' => 'Display all articles', - 'query_state_13' => 'Display read articles', - 'query_state_14' => 'Display unread articles', - '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', - 'stats_main' => 'Main statistics', - 'stats_repartition' => 'Articles repartition', - 'stats_entry_per_hour' => 'Per hour', - 'stats_entry_per_day_of_week' => 'Per day of week', - 'stats_entry_per_month' => 'Per month', - 'stats_percent_of_total' => '%% of total', - - 'last_week' => 'Last week', - 'last_month' => 'Last month', - 'last_3_month' => 'Last three months', - 'last_6_month' => 'Last six months', - 'last_year' => 'Last year', - - 'your_rss_feeds' => 'Your RSS feeds', - 'add_rss_feed' => 'Add a RSS feed', - 'no_rss_feed' => 'No RSS feed', - 'import_export' => 'Import / export', - 'bookmark' => 'Subscribe (FreshRSS bookmark)', - - 'subscription_management' => 'Subscriptions management', - 'main_stream' => 'Main stream', - 'all_feeds' => 'All feeds', - 'favorite_feeds' => 'Favourites (%s)', - 'not_read' => '%d unread', - 'not_reads' => '%d unread', - - 'filter' => 'Filter', - 'see_website' => 'See website', - 'administration' => 'Manage', - 'actualize' => 'Actualize', - - 'mark_read' => 'Mark as read', - 'mark_favorite' => 'Mark as favourite', - 'mark_all_read' => 'Mark all as read', - 'mark_feed_read' => 'Mark feed as read', - 'mark_cat_read' => 'Mark category as read', - 'before_one_day' => 'Before one day', - 'before_one_week' => 'Before one week', - 'display' => 'Display', - 'normal_view' => 'Normal view', - 'reader_view' => 'Reading view', - 'global_view' => 'Global view', - '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', - 'older_first' => 'Oldest first', - 'newer_first' => 'Newer first', - - // Pagination - 'first' => 'First', - 'previous' => 'Previous', - 'next' => 'Next', - 'last' => 'Last', - - // CONTROLLERS - 'article_published_on' => 'This article originally appeared on <a href="%s">%s</a>', - 'article_published_on_author' => 'This article originally appeared on <a href="%s">%s</a> by %s', - - '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', - - 'default_category' => 'Uncategorized', - 'categories_updated' => 'Categories have been updated', - 'categories_management' => 'Categories management', - 'feed_updated' => 'Feed has been updated', - 'rss_feed_management' => 'RSS feeds management', - 'configuration_updated' => 'Configuration has been updated', - 'sharing_management' => 'Sharing options management', - 'bad_opml_file' => 'Your OPML file is invalid', - 'shortcuts_updated' => 'Shortcuts have been updated', - 'shortcuts_navigation' => 'Navigation', - 'shortcuts_navigation_help' => 'With the "Shift" modifier, navigation shortcuts apply on feeds.<br/>With the "Alt" modifier, navigation shortcuts apply on categories.', - 'shortcuts_article_action' => 'Article actions', - 'shortcuts_other_action' => 'Other actions', - 'feeds_marked_read' => 'Feeds have been marked as read', - 'updated' => 'Modifications have been updated', - - 'already_subscribed' => 'You have already subscribed to <em>%s</em>', - 'feed_added' => 'RSS feed <em>%s</em> has been added', - 'feed_not_added' => '<em>%s</em> could not be added', - 'internal_problem_feed' => 'The RSS feed could not be added. <a href="%s">Check FressRSS logs</a> for details.', - 'invalid_url' => 'URL <em>%s</em> is invalid', - 'feed_actualized' => '<em>%s</em> has been updated', - 'n_feeds_actualized' => '%d feeds have been updated', - 'feeds_actualized' => 'RSS feeds have been updated', - 'no_feed_actualized' => 'No RSS feed has been updated', - 'n_entries_deleted' => '%d articles have been deleted', - 'feeds_imported_with_errors' => 'Your feeds have been imported but some errors occurred', - 'feeds_imported' => 'Your feeds have been imported and will now be updated', - 'category_emptied' => 'Category has been emptied', - 'feed_deleted' => 'Feed has been deleted', - 'feed_validator' => 'Check the validity of the feed', - - 'optimization_complete' => 'Optimization complete', - - 'your_rss_feeds' => 'Your RSS feeds', - 'your_favorites' => 'Your favourites', - 'public' => 'Public', - 'invalid_login' => 'Login is invalid', - - 'file_is_nok' => 'Check permissions on <em>%s</em> 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 <em>%s</em>.', - 'category_number' => 'Category n°%d', - 'ask_empty' => 'Clear?', - 'number_feeds' => '%d feeds', - 'can_not_be_deleted' => 'Cannot be deleted', - 'add_category' => 'Add a category', - 'new_category' => 'New category', - - 'javascript_for_shortcuts' => 'JavaScript must be enabled in order to use shortcuts', - 'javascript_should_be_activated'=> 'JavaScript must be enabled', - 'shift_for_all_read' => '+ <code>shift</code> to mark all articles as read', - 'see_on_website' => 'See on original website', - 'next_article' => 'Skip to the next article', - 'last_article' => 'Skip to the last article', - 'previous_article' => 'Skip to the previous article', - 'first_article' => 'Skip to the first article', - 'next_page' => 'Skip to the next page', - 'previous_page' => 'Skip to the previous page', - 'collapse_article' => 'Collapse', - '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<br />(OPML, Json or Zip)', - 'file_to_import_no_zip' => 'File to import<br />(OPML or Json)', - 'import' => 'Import', - 'file_cannot_be_uploaded' => 'File cannot be uploaded!', - 'zip_error' => 'An error occured during Zip import.', - 'no_zip_extension' => 'Zip extension is not present on your server.', - 'export' => 'Export', - 'export_opml' => 'Export list of feeds (OPML)', - 'export_starred' => 'Export your favourites', - 'export_no_zip_extension' => 'Zip extension is not present on your server. Please try to export files one by one.', - 'starred_list' => 'List of favourite articles', - 'feed_list' => 'List of %s articles', - 'or' => 'or', - - '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' => '%d articles', - 'by_feed' => 'by feed', - 'by_default' => 'By default', - 'keep_history' => 'Minimum number of articles to keep', - 'ttl' => 'Do not automatically refresh more often than', - 'categorize' => 'Store in a category', - 'truncate' => 'Delete all articles', - 'advanced' => 'Advanced', - 'show_in_all_flux' => 'Show in main stream', - 'yes' => 'Yes', - 'no' => 'No', - 'css_path_on_website' => 'Articles CSS path on original website', - 'retrieve_truncated_feeds' => 'Retrieves truncated RSS feeds (attention, requires more time!)', - 'http_authentication' => 'HTTP Authentication', - 'http_username' => 'HTTP username', - 'http_password' => 'HTTP password', - 'blank_to_disable' => 'Leave blank to disable', - 'share_name' => 'Share name to display', - 'share_url' => 'Share URL to use', - '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.', - - 'current_user' => 'Current user', - 'default_user' => 'Username of the default user <small>(maximum 16 alphanumeric characters)</small>', - 'password_form' => 'Password<br /><small>(for the Web-form login method)</small>', - 'password_api' => 'Password API<br /><small>(e.g., for mobile apps)</small>', - 'persona_connection_email' => 'Login mail address<br /><small>(for <a href="https://persona.org/" rel="external">Mozilla Persona</a>)</small>', - 'allow_anonymous' => 'Allow anonymous reading of the articles of the default user (%s)', - 'allow_anonymous_refresh' => 'Allow anonymous refresh of the articles', - 'unsafe_autologin' => 'Allow unsafe automatic login using the format: ', - 'api_enabled' => 'Allow <abbr>API</abbr> access <small>(required for mobile apps)</small>', - 'auth_token' => 'Authentication token', - 'explain_token' => 'Allows to access RSS output of the default user without authentication.<br /><kbd>%s?output=rss&token=%s</kbd>', - 'login_configuration' => 'Login', - 'is_admin' => 'is administrator', - 'auth_type' => 'Authentication method', - 'auth_none' => 'None (dangerous)', - 'auth_form' => 'Web form (traditional, requires JavaScript)', - 'http_auth' => 'HTTP (for advanced users with HTTPS)', - 'auth_persona' => 'Mozilla Persona (modern, requires JavaScript)', - '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', - 'user_deleted' => 'User %s has been deleted', - - 'language' => 'Language', - 'month' => 'months', - 'archiving_configuration' => 'Archiving', - '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 (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' => 'while scrolling', - 'upon_reception' => 'upon reception of the article', - 'your_shaarli' => 'Your Shaarli', - 'your_wallabag' => 'Your wallabag', - 'your_diaspora_pod' => 'Your Diaspora* pod', - 'sharing' => 'Sharing', - 'share' => 'Share', - 'by_email' => 'By email', - 'optimize_bdd' => 'Optimize database', - 'optimize_todo_sometimes' => 'To do occasionally to reduce the size of the database', - 'theme' => 'Theme', - 'content_width' => 'Content width', - 'width_thin' => 'Thin', - 'width_medium' => 'Medium', - 'width_large' => 'Large', - 'width_no_limit' => 'No limit', - 'more_information' => 'More information', - 'activate_sharing' => 'Activate sharing', - 'shaarli' => 'Shaarli', - 'blogotext' => 'Blogotext', - 'wallabag' => 'wallabag', - 'diaspora' => 'Diaspora*', - 'twitter' => 'Twitter', - 'g+' => 'Google+', - 'facebook' => 'Facebook', - 'email' => 'Email', - 'print' => 'Print', - - 'article' => 'Article', - 'title' => 'Title', - 'author' => 'Author', - 'publication_date' => 'Date of publication', - 'by' => 'by', - - 'load_more' => 'Load more articles', - 'nothing_to_load' => 'There are no more articles', - - 'rss_feeds_of' => 'RSS feed of %s', - - 'refresh' => 'Refresh', - 'no_feed_to_refresh' => 'There is no feed to refresh…', - - 'today' => 'Today', - 'yesterday' => 'Yesterday', - 'before_yesterday' => 'Before yesterday', - 'new_article' => 'There are new available articles, click to refresh the page.', - 'by_author' => 'By <em>%s</em>', - 'related_tags' => 'Related tags', - 'no_feed_to_display' => 'There is no article to show.', - - 'about_freshrss' => 'About FreshRSS', - 'project_website' => 'Project website', - 'lead_developer' => 'Lead developer', - 'website' => 'Website', - 'bugs_reports' => 'Bugs reports', - 'github_or_email' => '<a href="https://github.com/marienfressinaud/FreshRSS/issues">on Github</a> or <a href="mailto:dev@marienfressinaud.fr">by mail</a>', - 'license' => 'License', - 'agpl3' => '<a href="https://www.gnu.org/licenses/agpl-3.0.html">AGPL 3</a>', - 'freshrss_description' => 'FreshRSS is a RSS feeds aggregator to self-host like <a href="http://tontof.net/kriss/feed/">Kriss Feed</a> or <a href="http://projet.idleman.fr/leed/">Leed</a>. It is light and easy to take in hand while being powerful and configurable tool.', - 'credits' => 'Credits', - 'credits_content' => 'Some design elements come from <a href="http://twitter.github.io/bootstrap/">Bootstrap</a> although FreshRSS doesn’t use this framework. <a href="https://git.gnome.org/browse/gnome-icon-theme-symbolic">Icons</a> come from <a href="https://www.gnome.org/">GNOME project</a>. <em>Open Sans</em> font police has been created by <a href="https://www.google.com/webfonts/specimen/Open+Sans">Steve Matteson</a>. Favicons are collected with <a href="https://getfavicon.appspot.com/">getFavicon API</a>. FreshRSS is based on <a href="https://github.com/marienfressinaud/MINZ">Minz</a>, a PHP framework.', - 'version' => 'Version', - - 'logs' => 'Logs', - 'logs_empty' => 'Log file is empty', - 'clear_logs' => 'Clear the logs', - - 'forbidden_access' => 'Access is forbidden!', - '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.', - - // DATE - 'january' => 'January', - 'february' => 'February', - 'march' => 'March', - 'april' => 'April', - 'may' => 'May', - 'june' => 'June', - 'july' => 'July', - 'august' => 'August', - 'september' => 'September', - 'october' => 'October', - 'november' => 'November', - 'december' => 'December', - 'january' => 'Jan', - 'february' => 'Feb', - 'march' => 'Mar', - 'april' => 'Apr', - 'may' => 'May', - 'june' => 'Jun', - 'july' => 'Jul', - 'august' => 'Aug', - 'september' => 'Sep', - 'october' => 'Oct', - 'november' => 'Nov', - 'december' => 'Dec', - 'sun' => 'Sun', - 'mon' => 'Mon', - 'tue' => 'Tue', - 'wed' => 'Wed', - 'thu' => 'Thu', - 'fri' => 'Fri', - 'sat' => 'Sat', - // special format for date() function - 'Jan' => '\J\a\n\u\a\r\y', - 'Feb' => '\F\e\b\r\u\a\r\y', - 'Mar' => '\M\a\r\c\h', - 'Apr' => '\A\p\r\i\l', - 'May' => '\M\a\y', - 'Jun' => '\J\u\n\e', - 'Jul' => '\J\u\l\y', - 'Aug' => '\A\u\g\u\s\t', - 'Sep' => '\S\e\p\t\e\m\b\e\r', - 'Oct' => '\O\c\t\o\b\e\r', - 'Nov' => '\N\o\v\e\m\b\e\r', - 'Dec' => '\D\e\c\e\m\b\e\r', - // format for date() function, %s allows to indicate month in letter - 'format_date' => '%s j\<\s\u\p\>S\<\/\s\u\p\> Y', - 'format_date_hour' => '%s j\<\s\u\p\>S\<\/\s\u\p\> Y \a\t H\:i', - - 'status_favorites' => 'Favourites', - 'status_read' => 'Read', - 'status_unread' => 'Unread', - 'status_total' => 'Total', - - 'stats_entry_repartition' => 'Entries repartition', - 'stats_entry_per_day' => 'Entries per day (last 30 days)', - 'stats_feed_per_category' => 'Feeds per category', - '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.', + return array ( + 'Apr' => '\\A\\p\\r\\i\\l', + 'Aug' => '\\A\\u\\g\\u\\s\\t', + 'Dec' => '\\D\\e\\c\\e\\m\\b\\e\\r', + 'Feb' => '\\F\\e\\b\\r\\u\\a\\r\\y', + 'Jan' => '\\J\\a\\n\\u\\a\\r\\y', + 'Jul' => '\\J\\u\\l\\y', + 'Jun' => '\\J\\u\\n\\e', + 'Mar' => '\\M\\a\\r\\c\\h', + 'May' => '\\M\\a\\y', + 'Nov' => '\\N\\o\\v\\e\\m\\b\\e\\r', + 'Oct' => '\\O\\c\\t\\o\\b\\e\\r', + 'Sep' => '\\S\\e\\p\\t\\e\\m\\b\\e\\r', + 'about' => 'About', + 'about_freshrss' => 'About FreshRSS', + 'access_denied' => 'You don’t have permission to access this page', + 'access_protected_feeds' => 'Connection allows to access HTTP protected RSS feeds', + 'activate_sharing' => 'Activate sharing', + 'actualize' => 'Actualize', + 'add_category' => 'Add a category', + 'add_query' => 'Add a query', + 'add_rss_feed' => 'Add a RSS feed', + 'admin.check_install.cache.nok' => 'Check permissions on <em>./data/cache</em> directory. HTTP server must have rights to write into', + 'admin.check_install.cache.ok' => 'Permissions on cache directory are good.', + 'admin.check_install.categories.nok' => 'Category table is bad configured.', + 'admin.check_install.categories.ok' => 'Category table is ok.', + 'admin.check_install.connection.nok' => 'Connection to the database cannot being established.', + 'admin.check_install.connection.ok' => 'Connection to the database is ok.', + 'admin.check_install.ctype.nok' => 'You lack a required library for character type checking (php-ctype).', + 'admin.check_install.ctype.ok' => 'You have the required library for character type checking (ctype).', + 'admin.check_install.curl.nok' => 'You lack cURL (php5-curl package).', + 'admin.check_install.curl.ok' => 'You have cURL extension.', + 'admin.check_install.data.nok' => 'Check permissions on <em>./data</em> directory. HTTP server must have rights to write into', + 'admin.check_install.data.ok' => 'Permissions on data directory are good.', + 'admin.check_install.database' => 'Database installation', + 'admin.check_install.dom.nok' => 'You lack a required library to browse the DOM (php-xml package).', + 'admin.check_install.dom.ok' => 'You have the required library to browse the DOM.', + 'admin.check_install.entries.nok' => 'Entry table is bad configured.', + 'admin.check_install.entries.ok' => 'Entry table is ok.', + 'admin.check_install.favicons.nok' => 'Check permissions on <em>./data/favicons</em> directory. HTTP server must have rights to write into', + 'admin.check_install.favicons.ok' => 'Permissions on favicons directory are good.', + 'admin.check_install.feeds.nok' => 'Feed table is bad configured.', + 'admin.check_install.feeds.ok' => 'Feed table is ok.', + 'admin.check_install.files' => 'File installation', + 'admin.check_install.json.nok' => 'You lack JSON (php5-json package).', + 'admin.check_install.json.ok' => 'You have JSON extension.', + 'admin.check_install.logs.nok' => 'Check permissions on <em>./data/logs</em> directory. HTTP server must have rights to write into', + 'admin.check_install.logs.ok' => 'Permissions on logs directory are good.', + 'admin.check_install.minz.nok' => 'You lack the Minz framework.', + 'admin.check_install.minz.ok' => 'You have the Minz framework.', + 'admin.check_install.pcre.nok' => 'You lack a required library for regular expressions (php-pcre).', + 'admin.check_install.pcre.ok' => 'You have the required library for regular expressions (PCRE).', + 'admin.check_install.pdo.nok' => 'You lack PDO or one of the supported drivers (pdo_mysql, pdo_sqlite).', + 'admin.check_install.pdo.ok' => 'You have PDO and at least one of the supported drivers (pdo_mysql, pdo_sqlite).', + 'admin.check_install.persona.nok' => 'Check permissions on <em>./data/persona</em> directory. HTTP server must have rights to write into', + 'admin.check_install.persona.ok' => 'Permissions on Mozilla Persona directory are good.', + 'admin.check_install.php' => 'PHP installation', + 'admin.check_install.php.nok' => 'Your PHP version is %s but FreshRSS requires at least version %s.', + 'admin.check_install.php.ok' => 'Your PHP version is %s, which is compatible with FreshRSS.', + 'admin.check_install.tables.nok' => 'There is one or more lacking tables in the database.', + 'admin.check_install.tables.ok' => 'Tables are existing in the database.', + 'admin.check_install.tokens.nok' => 'Check permissions on <em>./data/tokens</em> directory. HTTP server must have rights to write into', + 'admin.check_install.tokens.ok' => 'Permissions on tokens directory are good.', + 'admin.check_install.zip.nok' => 'You lack ZIP extension (php5-zip package).', + 'admin.check_install.zip.ok' => 'You have ZIP extension.', + 'admin.users.articles_and_size' => '%s articles (%s)', + 'administration' => 'Manage', + 'advanced' => 'Advanced', + 'after_onread' => 'After “mark all as read”,', + 'agpl3' => '<a href="https://www.gnu.org/licenses/agpl-3.0.html">AGPL 3</a>', + 'all_feeds' => 'All feeds', + 'allow_anonymous' => 'Allow anonymous reading of the articles of the default user (%s)', + 'allow_anonymous_refresh' => 'Allow anonymous refresh of the articles', + 'already_subscribed' => 'You have already subscribed to <em>%s</em>', + 'api_enabled' => 'Allow <abbr>API</abbr> access <small>(required for mobile apps)</small>', + 'apr' => 'apr', + 'april' => 'Apr', + 'archiving_configuration' => 'Archiving', + 'archiving_configuration_help' => 'More options are available in the individual stream settings', + 'article' => 'Article', + 'article_icons' => 'Article icons', + 'article_open_on_website' => 'when article is opened on its original website', + 'article_published_on' => 'This article originally appeared on <a href="%s">%s</a>', + 'article_published_on_author' => 'This article originally appeared on <a href="%s">%s</a> by %s', + 'article_viewed' => 'when article is viewed', + 'articles' => 'articles', + 'articles_per_page' => 'Number of articles per page', + 'articles_to_display' => 'Articles to display', + 'ask_empty' => 'Clear?', + 'attention' => 'Attention!', + 'aug' => 'aug', + 'august' => 'Aug', + 'auth_form' => 'Web form (traditional, requires JavaScript)', + 'auth_form_not_set' => 'A problem occured during authentication system configuration. Please retry later.', + 'auth_form_set' => 'Form is now your default authentication system.', + 'auth_no_password_set' => 'Administrator password hasn’t been set. This feature isn’t available.', + 'auth_none' => 'None (dangerous)', + 'auth_not_persona' => 'Only Persona system can be reset.', + 'auth_persona' => 'Mozilla Persona (modern, requires JavaScript)', + 'auth_reset' => 'Authentication reset', + 'auth_token' => 'Authentication token', + 'auth_type' => 'Authentication method', + 'auth_will_reset' => 'Authentication system will be reset: a form will be used instead of Persona.', + 'author' => 'Author', + 'auto_load_more' => 'Load next articles at the page bottom', + 'auto_read_when' => 'Mark article as read…', + 'auto_share' => 'Share', + 'auto_share_help' => 'If there is only one sharing mode, it is used. Else modes are accessible by their number.', + 'back_to_rss_feeds' => '← Go back to your RSS feeds', + 'bad_opml_file' => 'Your OPML file is invalid', + 'base_url' => 'Base URL', + 'bdd' => 'Database', + 'bdd_conf_is_ko' => 'Verify your database information.', + 'bdd_conf_is_ok' => 'Database configuration has been saved.', + 'bdd_configuration' => 'Database configuration', + 'bdd_type' => 'Type of database', + 'before_one_day' => 'Before one day', + 'before_one_week' => 'Before one week', + 'before_yesterday' => 'Before yesterday', + 'blank_to_disable' => 'Leave blank to disable', + 'blogotext' => 'Blogotext', + 'bookmark' => 'Subscribe (FreshRSS bookmark)', + 'bottom_line' => 'Bottom line', + 'bugs_reports' => 'Bugs reports', + 'by' => 'by', + 'by_author' => 'By <em>%s</em>', + 'by_default' => 'By default', + 'by_email' => 'By email', + 'by_feed' => 'by feed', + 'cache_is_ok' => 'Permissions on cache directory are good', + 'can_not_be_deleted' => 'Cannot be deleted', + 'cancel' => 'Cancel', + 'categories' => 'Categories', + 'categories_management' => 'Categories management', + 'categories_updated' => 'Categories have been updated', + 'categorize' => 'Store in a category', + 'category' => 'Category', + 'category_created' => 'Category %s has been created.', + 'category_deleted' => 'Category has been deleted.', + 'category_emptied' => 'Category has been emptied', + 'category_empty' => 'Empty category', + 'category_name_exists' => 'Category name already exists.', + 'category_no_id' => 'You must precise the id of the category.', + 'category_no_name' => 'Category name cannot be empty.', + 'category_not_delete_default' => 'You cannot delete the default category!', + 'category_not_exist' => 'The category does not exist!', + 'category_number' => 'Category n°%d', + 'category_updated' => 'Category has been updated.', + 'change_value' => 'You should change this value by any other', + 'checks' => 'Checks', + 'choose_language' => 'Choose a language for FreshRSS', + 'clear_logs' => 'Clear the logs', + 'collapse_article' => 'Collapse', + 'conf.users.articles_and_size' => '%s articles (%s)', + 'configuration' => 'Configuration', + 'configuration_updated' => 'Configuration has been updated', + '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 will lose related favorites and user queries. It cannot be cancelled!', + 'congratulations' => 'Congratulations!', + 'content_width' => 'Content width', + 'create' => 'Create', + 'create_user' => 'Create new user', + 'credits' => 'Credits', + 'credits_content' => 'Some design elements come from <a href="http://twitter.github.io/bootstrap/">Bootstrap</a> although FreshRSS doesn’t use this framework. <a href="https://git.gnome.org/browse/gnome-icon-theme-symbolic">Icons</a> come from <a href="https://www.gnome.org/">GNOME project</a>. <em>Open Sans</em> font police has been created by <a href="https://www.google.com/webfonts/specimen/Open+Sans">Steve Matteson</a>. Favicons are collected with <a href="https://getfavicon.appspot.com/">getFavicon API</a>. FreshRSS is based on <a href="https://github.com/marienfressinaud/MINZ">Minz</a>, a PHP framework.', + 'css_path_on_website' => 'Articles CSS path on original website', + 'ctype_is_nok' => 'You lack a required library for character type checking (php-ctype)', + 'ctype_is_ok' => 'You have the required library for character type checking (ctype)', + 'curl_is_nok' => 'You lack cURL (php5-curl package)', + 'curl_is_ok' => 'You have version %s of cURL', + 'current_user' => 'Current user', + 'damn' => 'Damn!', + 'data_is_ok' => 'Permissions on data directory are good', + 'dec' => 'dec', + 'december' => 'Dec', + 'default_category' => 'Uncategorized', + 'default_user' => 'Username of the default user <small>(maximum 16 alphanumeric characters)</small>', + 'default_view' => 'Default view', + 'delete' => 'Delete', + 'delete_articles_every' => 'Remove articles after', + 'diaspora' => 'Diaspora*', + 'display' => 'Display', + 'display_articles_unfolded' => 'Show articles unfolded by default', + 'display_categories_unfolded' => 'Show categories folded by default', + 'display_configuration' => 'Display', + 'do_not_change_if_doubt' => 'Don’t change if you doubt about it', + 'dom_is_nok' => 'You lack a required library to browse the DOM (php-xml package)', + 'dom_is_ok' => 'You have the required library to browse the DOM', + 'email' => 'Email', + 'error_occurred' => 'An error occurred', + 'error_occurred_update' => 'Nothing was changed', + 'explain_token' => 'Allows to access RSS output of the default user without authentication.<br /><kbd>%s?output=rss&token=%s</kbd>', + 'export' => 'Export', + 'export_no_zip_extension' => 'Zip extension is not present on your server. Please try to export files one by one.', + 'export_opml' => 'Export list of feeds (OPML)', + 'export_starred' => 'Export your favourites', + 'facebook' => 'Facebook', + 'favicons_is_ok' => 'Permissions on favicons directory are good', + 'favorite_feeds' => 'Favourites (%s)', + 'feb' => 'feb', + 'february' => 'Feb', + 'feed' => 'Feed', + 'feed_actualized' => '<em>%s</em> has been updated', + 'feed_added' => 'RSS feed <em>%s</em> has been added', + 'feed_deleted' => 'Feed has been deleted', + 'feed_description' => 'Description', + 'feed_empty' => 'This feed is empty. Please verify that it is still maintained.', + 'feed_in_error' => 'This feed has encountered a problem. Please verify that it is always reachable then actualize it.', + 'feed_list' => 'List of %s articles', + 'feed_not_added' => '<em>%s</em> could not be added', + 'feed_updated' => 'Feed has been updated', + 'feed_url' => 'Feed URL', + 'feed_validator' => 'Check the validity of the feed', + 'feedback.login.error' => 'Login is invalid', + 'feedback.login.success' => 'You are connected', + 'feedback.logout.success' => 'You are disconnected', + 'feedback.user_profile.updated' => 'Your profile has been modified', + 'feeds' => 'Feeds', + 'feeds_actualized' => 'RSS feeds have been updated', + 'feeds_imported' => 'Your feeds have been imported and will now be updated', + 'feeds_imported_with_errors' => 'Your feeds have been imported but some errors occurred', + 'feeds_marked_read' => 'Feeds have been marked as read', + 'feeds_moved_category_deleted' => 'When you delete a category, their feeds are automatically classified under <em>%s</em>.', + 'file_cannot_be_uploaded' => 'File cannot be uploaded!', + 'file_is_nok' => 'Check permissions on <em>%s</em> directory. HTTP server must have rights to write into', + 'file_to_import' => 'File to import<br />(OPML, Json or Zip)', + 'file_to_import_no_zip' => 'File to import<br />(OPML or Json)', + 'filter' => 'Filter', + 'finish_installation' => 'Complete installation', + 'first' => 'First', + 'first_article' => 'Skip to the first article', + 'fix_errors_before' => 'Fix errors before skip to the next step.', + 'focus_search' => 'Access search box', + 'format_date' => '%s j\\<\\s\\u\\p\\>S\\<\\/\\s\\u\\p\\> Y', + 'format_date_hour' => '%s j\\<\\s\\u\\p\\>S\\<\\/\\s\\u\\p\\> Y \\a\\t H\\:i', + 'freshrss' => 'FreshRSS', + 'freshrss_description' => 'FreshRSS is a RSS feeds aggregator to self-host like <a href="http://tontof.net/kriss/feed/">Kriss Feed</a> or <a href="http://projet.idleman.fr/leed/">Leed</a>. It is light and easy to take in hand while being powerful and configurable tool.', + 'freshrss_installation' => 'Installation · FreshRSS', + 'fri' => 'Fri', + 'g+' => 'Google+', + 'gen.menu.admin' => 'Administration', + 'gen.menu.authentication' => 'Authentication', + 'gen.menu.check_install' => 'Installation checking', + 'gen.menu.user_management' => 'Manage users', + 'gen.menu.user_profile' => 'Profile', + 'gen.title.authentication' => 'Authentication', + 'gen.title.check_install' => 'Installation checking', + 'gen.title.global_view' => 'Global view', + 'gen.title.user_management' => 'Manage users', + 'gen.title.user_profile' => 'Profile', + 'general_conf_is_ok' => 'General configuration has been saved.', + 'general_configuration' => 'General configuration', + 'github_or_email' => '<a href="https://github.com/marienfressinaud/FreshRSS/issues">on Github</a> or <a href="mailto:dev@marienfressinaud.fr">by mail</a>', + 'global_view' => 'Global view', + 'help' => 'Display documentation', + 'hide_read_feeds' => 'Hide categories & feeds with no unread article (does not work with “Show all articles” configuration)', + 'host' => 'Host', + 'html5_notif_timeout' => 'HTML5 notification timeout', + 'http_auth' => 'HTTP (for advanced users with HTTPS)', + 'http_authentication' => 'HTTP Authentication', + 'http_password' => 'HTTP password', + 'http_referer_is_nok' => 'Please check that you are not altering your HTTP REFERER.', + 'http_referer_is_ok' => 'Your HTTP REFERER is known and corresponds to your server.', + 'http_username' => 'HTTP username', + 'img_with_lazyload' => 'Use "lazy load" mode to load pictures', + 'import' => 'Import', + 'import_export' => 'Import / export', + 'informations' => 'Information', + 'install_not_deleted' => 'Something went wrong; you must delete the file <em>%s</em> manually.', + 'installation_is_ok' => 'The installation process was successful.<br />The final step will now attempt to delete any file and database backup created during the update process.<br />You may choose to skip this step by deleting <kbd>./data/do-install.txt</kbd> manually.', + 'installation_step' => 'Installation — step %d · FreshRSS', + 'internal_problem_feed' => 'The RSS feed could not be added. <a href="%s">Check FressRSS logs</a> for details.', + 'invalid_login' => 'Login is invalid', + 'invalid_url' => 'URL <em>%s</em> is invalid', + 'is_admin' => 'is administrator', + 'jan' => 'jan', + 'january' => 'Jan', + 'javascript_for_shortcuts' => 'JavaScript must be enabled in order to use shortcuts', + 'javascript_is_better' => 'FreshRSS is more pleasant with JavaScript enabled', + 'javascript_should_be_activated' => 'JavaScript must be enabled', + 'jul' => 'jul', + 'july' => 'Jul', + 'jump_next' => 'jump to next unread sibling (feed or category)', + 'jun' => 'jun', + 'june' => 'Jun', + 'keep_history' => 'Minimum number of articles to keep', + 'keep_logged_in' => 'Keep me logged in <small>(1 month)</small>', + 'language' => 'Language', + 'language_defined' => 'Language has been defined.', + 'last' => 'Last', + 'last_3_month' => 'Last three months', + 'last_6_month' => 'Last six months', + 'last_article' => 'Skip to the last article', + 'last_month' => 'Last month', + 'last_week' => 'Last week', + 'last_year' => 'Last year', + 'lead_developer' => 'Lead developer', + 'license' => 'License', + 'load_more' => 'Load more articles', + 'log_is_ok' => 'Permissions on logs directory are good', + 'login' => 'Login', + 'login_configuration' => 'Login', + 'login_persona_problem' => 'Connection problem with Persona?', + 'login_required' => 'Login required:', + 'login_with_persona' => 'Login with Persona', + 'logout' => 'Logout', + 'logs' => 'Logs', + 'logs_empty' => 'Log file is empty', + 'main_stream' => 'Main stream', + 'mar' => 'mar', + 'march' => 'Mar', + 'mark_all_read' => 'Mark all as read', + 'mark_cat_read' => 'Mark category as read', + 'mark_favorite' => 'Mark as favourite', + 'mark_feed_read' => 'Mark feed as read', + 'mark_read' => 'Mark as read', + 'may' => 'May', + 'minz_is_nok' => 'You lack the Minz framework. You should execute <em>build.sh</em> script or <a href="https://github.com/marienfressinaud/MINZ">download it on Github</a> and install in <em>%s</em> directory the content of its <em>/lib</em> directory.', + 'minz_is_ok' => 'You have the Minz framework', + 'mon' => 'Mon', + 'month' => 'months', + 'more_information' => 'More information', + 'n_entries_deleted' => '%d articles have been deleted', + 'n_feeds_actualized' => '%d feeds have been updated', + 'new_article' => 'There are new available articles, click to refresh the page.', + 'new_category' => 'New category', + 'newer_first' => 'Newer first', + 'next' => 'Next', + 'next_article' => 'Skip to the next article', + 'next_page' => 'Skip to the next page', + 'next_step' => 'Go to the next step', + 'no' => 'No', + 'no_feed_actualized' => 'No RSS feed has been updated', + 'no_feed_to_display' => 'There is no article to show.', + 'no_feed_to_refresh' => 'There is no feed to refresh…', + 'no_query' => 'You haven’t created any user query yet.', + 'no_query_filter' => 'No filter', + 'no_rss_feed' => 'No RSS feed', + 'no_selected_feed' => 'No feed selected.', + 'no_update' => 'No update to apply', + 'no_zip_extension' => 'Zip extension is not present on your server.', + 'normal_view' => 'Normal view', + 'not_read' => '%d unread', + 'not_reads' => '%d unread', + 'not_yet_implemented' => 'Not yet implemented', + 'nothing_to_load' => 'There are no more articles', + 'notif_body_new_articles' => 'There are \\d new articles to read on FreshRSS.', + 'notif_title_new_articles' => 'FreshRSS: new articles!', + 'nov' => 'nov', + 'november' => 'Nov', + 'number_articles' => '%d articles', + 'number_divided_when_reader' => 'Divided by 2 in the reading view.', + 'number_feeds' => '%d feeds', + 'oct' => 'oct', + 'october' => 'Oct', + 'ok' => 'Ok!', + 'older_first' => 'Oldest first', + 'oops' => 'Oops!', + 'optimization_complete' => 'Optimization complete', + 'optimize_bdd' => 'Optimize database', + 'optimize_todo_sometimes' => 'To do occasionally to reduce the size of the database', + 'or' => 'or', + 'page_not_found' => 'You are looking for a page which doesn’t exist', + 'password' => 'Password', + 'password_api' => 'Password API<br /><small>(e.g., for mobile apps)</small>', + 'password_form' => 'Password<br /><small>(for the Web-form login method)</small>', + 'pcre_is_nok' => 'You lack a required library for regular expressions (php-pcre)', + 'pcre_is_ok' => 'You have the required library for regular expressions (PCRE)', + 'pdo_is_nok' => 'You lack PDO or one of the supported drivers (pdo_mysql, pdo_sqlite)', + 'pdo_is_ok' => 'You have PDO and at least one of the supported drivers (pdo_mysql, pdo_sqlite)', + 'persona_connection_email' => 'Login mail address<br /><small>(for <a href="https://persona.org/" rel="external">Mozilla Persona</a>)</small>', + 'persona_is_ok' => 'Permissions on Mozilla Persona directory are good', + 'php_is_nok' => 'Your PHP version is %s but FreshRSS requires at least version %s', + 'php_is_ok' => 'Your PHP version is %s, which is compatible with FreshRSS', + 'prefix' => 'Table prefix', + 'previous' => 'Previous', + 'previous_article' => 'Skip to the previous article', + 'previous_page' => 'Skip to the previous page', + 'print' => 'Print', + 'project_website' => 'Project website', + 'public' => 'Public', + 'publication_date' => 'Date of publication', + 'purge_completed' => 'Purge completed (%d articles deleted)', + 'purge_now' => 'Purge now', + 'queries' => 'User queries', + 'query_created' => 'Query "%s" has been created.', + 'query_deprecated' => 'This query is no longer valid. The referenced category or feed has been deleted.', + 'query_filter' => 'Filter applied:', + 'query_get_all' => 'Display all articles', + 'query_get_category' => 'Display "%s" category', + 'query_get_favorite' => 'Display favorite articles', + 'query_get_feed' => 'Display "%s" feed', + 'query_number' => 'Query n°%d', + 'query_order_asc' => 'Display oldest articles first', + 'query_order_desc' => 'Display newest articles first', + 'query_search' => 'Search for "%s"', + 'query_state_0' => 'Display all articles', + 'query_state_1' => 'Display read articles', + 'query_state_2' => 'Display unread articles', + 'query_state_3' => 'Display all articles', + 'query_state_4' => 'Display favorite articles', + 'query_state_5' => 'Display read favorite articles', + 'query_state_6' => 'Display unread favorite articles', + 'query_state_7' => 'Display favorite articles', + 'query_state_8' => 'Display not favorite articles', + 'query_state_9' => 'Display read not favorite articles', + 'query_state_10' => 'Display unread not favorite articles', + 'query_state_11' => 'Display not favorite articles', + 'query_state_12' => 'Display all articles', + 'query_state_13' => 'Display read articles', + 'query_state_14' => 'Display unread articles', + 'query_state_15' => 'Display all articles', + 'random_string' => 'Random string', + 'reader_view' => 'Reading view', + 'reading_configuration' => 'Reading', + 'reading_confirm' => 'Display a confirmation dialog on “mark all as read” actions', + 'refresh' => 'Refresh', + 'related_tags' => 'Related tags', + 'retrieve_truncated_feeds' => 'Retrieves truncated RSS feeds (attention, requires more time!)', + 'rss_feed_management' => 'RSS feeds management', + 'rss_feeds_of' => 'RSS feed of %s', + 'rss_view' => 'RSS feed', + 'sat' => 'Sat', + 'save' => 'Save', + 'scroll' => 'while scrolling', + 'search' => 'Search words or #tags', + 'search_short' => 'Search', + 'seconds_(0_means_no_timeout)' => 'seconds (0 means no timeout)', + 'see_on_website' => 'See on original website', + 'see_website' => 'See website', + 'sep' => 'sep', + 'september' => 'Sep', + 'shaarli' => 'Shaarli', + 'share' => 'Share', + 'share_name' => 'Share name to display', + 'share_url' => 'Share URL to use', + 'sharing' => 'Sharing', + 'sharing_management' => 'Sharing options management', + 'shift_for_all_read' => '+ <code>shift</code> to mark all articles as read', + 'shortcuts' => 'Shortcuts', + 'shortcuts_article_action' => 'Article actions', + 'shortcuts_navigation' => 'Navigation', + 'shortcuts_navigation_help' => 'With the "Shift" modifier, navigation shortcuts apply on feeds.<br/>With the "Alt" modifier, navigation shortcuts apply on categories.', + 'shortcuts_other_action' => 'Other actions', + 'shortcuts_updated' => 'Shortcuts have been updated', + 'show_adaptive' => 'Adjust showing', + 'show_all_articles' => 'Show all articles', + 'show_favorite' => 'Show only favorites', + 'show_in_all_flux' => 'Show in main stream', + 'show_not_favorite' => 'Show all but favorites', + 'show_not_reads' => 'Show only unread', + 'show_read' => 'Show only read', + 'sort_order' => 'Sort order', + 'starred_list' => 'List of favourite articles', + 'stats' => 'Statistics', + 'stats_entry_count' => 'Entry count', + 'stats_entry_per_category' => 'Entries per category', + 'stats_entry_per_day' => 'Entries per day (last 30 days)', + 'stats_entry_per_day_of_week' => 'Per day of week (average: %.2f messages)', + 'stats_entry_per_hour' => 'Per hour (average: %.2f messages)', + 'stats_entry_per_month' => 'Per month (average: %.2f messages)', + 'stats_entry_repartition' => 'Entries repartition', + 'stats_feed_per_category' => 'Feeds per category', + 'stats_idle' => 'Idle feeds', + 'stats_main' => 'Main statistics', + 'stats_no_idle' => 'There is no idle feed!', + 'stats_percent_of_total' => '%% of total', + 'stats_repartition' => 'Articles repartition', + 'stats_top_feed' => 'Top ten feeds', + 'status_favorites' => 'Favourites', + 'status_read' => 'Read', + 'status_total' => 'Total', + 'status_unread' => 'Unread', + 'steps' => 'Steps', + 'sticky_post' => 'Stick the article to the top when opened', + 'sub.categories.over_max' => 'You have reached your limit of categories (%d)', + 'sub.feeds.over_max' => 'You have reached your limit of feeds (%d)', + 'submit' => 'Submit', + 'subscription_management' => 'Subscriptions management', + 'sun' => 'Sun', + 'theme' => 'Theme', + 'think_to_add' => 'You may add some feeds.', + 'this_is_the_end' => 'This is the end', + 'thu' => 'Thu', + 'title' => 'Title', + 'today' => 'Today', + 'top_line' => 'Top line', + 'truncate' => 'Delete all articles', + 'ttl' => 'Do not automatically refresh more often than', + 'tue' => 'Tue', + 'twitter' => 'Twitter', + 'unsafe_autologin' => 'Allow unsafe automatic login using the format: ', + 'update' => 'Update', + 'update_apply' => 'Apply', + 'update_can_apply' => 'An update is available.', + 'update_check' => 'Check for new updates', + 'update_end' => 'Update process is completed, now you can go to the final step.', + 'update_finished' => 'Update completed!', + 'update_last' => 'Last verification: %s', + 'update_long' => 'This can take a long time, depending on the size of your database. You may have to wait for this page to time out (~5 minutes) and then refresh this page.', + 'update_problem' => 'The update process has encountered an error: %s', + 'update_server_not_found' => 'Update server cannot be found. [%s]', + 'update_start' => 'Start update process', + 'update_system' => 'Update system', + 'updated' => 'Modifications have been updated', + 'upon_reception' => 'upon reception of the article', + 'user_created' => 'User %s has been created', + 'user_deleted' => 'User %s has been deleted', + '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.', + 'username' => 'Username', + 'username_admin' => 'Administrator username', + 'users' => 'Users', + 'users_list' => 'List of users', + 'version' => 'Version', + 'version_update' => 'Update', + 'wallabag' => 'wallabag', + 'website' => 'Website', + 'website_url' => 'Website URL', + 'wed' => 'Wed', + 'width_large' => 'Large', + 'width_medium' => 'Medium', + 'width_no_limit' => 'No limit', + 'width_thin' => 'Thin', + 'yes' => 'Yes', + 'yesterday' => 'Yesterday', + 'your_diaspora_pod' => 'Your Diaspora* pod', + 'your_favorites' => 'Your favourites', + 'your_rss_feeds' => 'Your RSS feeds', + 'your_shaarli' => 'Your Shaarli', + 'your_wallabag' => 'Your wallabag', + 'zip_error' => 'An error occured during Zip import.', ); diff --git a/app/i18n/fr.php b/app/i18n/fr.php index c72fc3e93..3ba8574b2 100644 --- a/app/i18n/fr.php +++ b/app/i18n/fr.php @@ -1,455 +1,533 @@ <?php - -return array ( - // LAYOUT - 'login' => 'Connexion', - 'keep_logged_in' => 'Rester connecté <small>(1 mois)</small>', - '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', - - 'configuration' => 'Configuration', - 'users' => 'Utilisateurs', - 'categories' => 'Catégories', - 'category' => 'Catégorie', - 'feed' => 'Flux', - 'feeds' => 'Flux', - 'shortcuts' => 'Raccourcis', - 'queries' => 'Filtres utilisateurs', - 'query_search' => 'Recherche de "%s"', - 'query_order_asc' => 'Afficher les articles les plus anciens en premier', - 'query_order_desc' => 'Afficher les articles les plus récents en premier', - 'query_get_category' => 'Afficher la catégorie "%s"', - 'query_get_feed' => 'Afficher le flux "%s"', - 'query_get_all' => 'Afficher tous les articles', - 'query_get_favorite' => 'Afficher les articles favoris', - 'query_state_0' => 'Afficher tous les articles', - 'query_state_1' => 'Afficher les articles lus', - 'query_state_2' => 'Afficher les articles non lus', - 'query_state_3' => 'Afficher tous les articles', - 'query_state_4' => 'Afficher les articles favoris', - 'query_state_5' => 'Afficher les articles lus et favoris', - 'query_state_6' => 'Afficher les articles non lus et favoris', - 'query_state_7' => 'Afficher les articles favoris', - 'query_state_8' => 'Afficher les articles non favoris', - 'query_state_9' => 'Afficher les articles lus et non favoris', - 'query_state_10' => 'Afficher les articles non lus et non favoris', - 'query_state_11' => 'Afficher les articles non favoris', - 'query_state_12' => 'Afficher tous les articles', - 'query_state_13' => 'Afficher les articles lus', - 'query_state_14' => 'Afficher les articles non lus', - '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', - 'stats_main' => 'Statistiques principales', - 'stats_repartition' => 'Répartition des articles', - 'stats_entry_per_hour' => 'Par heure', - 'stats_entry_per_day_of_week' => 'Par jour de la semaine', - 'stats_entry_per_month' => 'Par mois', - 'stats_percent_of_total' => '%% du total', - - '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', - 'no_rss_feed' => 'Aucun flux RSS', - 'import_export' => 'Importer / exporter', - 'bookmark' => 'S’abonner (bookmark FreshRSS)', - - 'subscription_management' => 'Gestion des abonnements', - 'main_stream' => 'Flux principal', - 'all_feeds' => 'Tous les flux', - 'favorite_feeds' => 'Favoris (%s)', - 'not_read' => '%d non lu', - 'not_reads' => '%d non lus', - - 'filter' => 'Filtrer', - 'see_website' => 'Voir le site', - 'administration' => 'Gérer', - 'actualize' => 'Actualiser', - - 'mark_read' => 'Marquer comme lu', - 'mark_favorite' => 'Mettre en favori', - 'mark_all_read' => 'Tout marquer comme lu', - 'mark_feed_read' => 'Marquer le flux comme lu', - 'mark_cat_read' => 'Marquer la catégorie comme lue', - 'before_one_day' => 'Antérieurs à 1 jour', - 'before_one_week' => 'Antérieurs à 1 semaine', - 'display' => 'Affichage', - 'normal_view' => 'Vue normale', - 'reader_view' => 'Vue lecture', - 'global_view' => 'Vue globale', - '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', - 'older_first' => 'Plus anciens en premier', - 'newer_first' => 'Plus récents en premier', - - // Pagination - 'first' => 'Début', - 'previous' => 'Précédent', - 'next' => 'Suivant', - 'last' => 'Fin', - - // CONTROLLERS - 'article_published_on' => 'Article publié initialement sur <a href="%s">%s</a>', - 'article_published_on_author' => 'Article publié initialement sur <a href="%s">%s</a> par %s', - - 'access_denied' => 'Vous n’avez pas le droit d’accéder à cette page !', - 'page_not_found' => 'La page que vous cherchez n’existe pas !', - 'error_occurred' => 'Une erreur est survenue !', - 'error_occurred_update' => 'Rien n’a été modifié !', - - 'default_category' => 'Sans catégorie', - 'categories_updated' => 'Les catégories ont été mises à jour.', - 'categories_management' => 'Gestion des catégories', - 'feed_updated' => 'Le flux a été mis à jour.', - 'rss_feed_management' => 'Gestion des flux RSS', - 'configuration_updated' => 'La configuration a été mise à jour.', - 'sharing_management' => 'Gestion des options de partage', - 'bad_opml_file' => 'Votre fichier OPML n’est pas valide.', - 'shortcuts_updated' => 'Les raccourcis ont été mis à jour.', - 'shortcuts_navigation' => 'Navigation', - 'shortcuts_navigation_help' => 'Avec le modificateur "Shift", les raccourcis de navigation s’appliquent aux flux.<br/>Avec le modificateur "Alt", les raccourcis de navigation s’appliquent aux catégories.', - 'shortcuts_article_action' => 'Actions associées à l’article courant', - 'shortcuts_other_action' => 'Autres actions', - 'feeds_marked_read' => 'Les flux ont été marqués comme lus.', - 'updated' => 'Modifications enregistrées.', - - 'already_subscribed' => 'Vous êtes déjà abonné à <em>%s</em>', - 'feed_added' => 'Le flux <em>%s</em> a bien été ajouté.', - 'feed_not_added' => '<em>%s</em> n’a pas pu être ajouté.', - 'internal_problem_feed' => 'Le flux ne peut pas être ajouté. <a href="%s">Consulter les logs de FreshRSS</a> pour plus de détails.', - 'invalid_url' => 'L’url <em>%s</em> est invalide.', - 'feed_actualized' => '<em>%s</em> a été mis à jour.', - 'n_feeds_actualized' => '%d flux ont été mis à jour.', - 'feeds_actualized' => 'Les flux ont été mis à jour.', - 'no_feed_actualized' => 'Aucun flux n’a pu être mis à jour.', - 'n_entries_deleted' => '%d articles ont été supprimés.', - 'feeds_imported_with_errors' => 'Vos flux ont été importés mais des erreurs sont survenues.', - 'feeds_imported' => 'Vos flux ont été importés et vont maintenant être actualisés.', - 'category_emptied' => 'La catégorie a été vidée.', - 'feed_deleted' => 'Le flux a été supprimé.', - 'feed_validator' => 'Vérifier la valididé du flux', - - 'optimization_complete' => 'Optimisation terminée.', - - 'your_rss_feeds' => 'Vos flux RSS', - 'your_favorites' => 'Vos favoris', - 'public' => 'Public', - 'invalid_login' => 'L’identifiant est invalide !', - - 'file_is_nok' => 'Veuillez vérifier les droits sur le répertoire <em>%s</em>. 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 <em>%s</em>.', - 'category_number' => 'Catégorie n°%d', - 'ask_empty' => 'Vider ?', - 'number_feeds' => '%d flux', - 'can_not_be_deleted' => 'Ne peut pas être supprimée.', - 'add_category' => 'Ajouter une catégorie', - 'new_category' => 'Nouvelle catégorie', - - 'javascript_for_shortcuts' => 'Le JavaScript doit être activé pour pouvoir profiter des raccourcis.', - 'javascript_should_be_activated'=> 'Le JavaScript doit être activé.', - 'shift_for_all_read' => '+ <code>shift</code> pour marquer tous les articles comme lus', - 'see_on_website' => 'Voir sur le site d’origine', - 'next_article' => 'Passer à l’article suivant', - 'last_article' => 'Passer au dernier article', - 'previous_article' => 'Passer à l’article précédent', - 'first_article' => 'Passer au premier article', - 'next_page' => 'Passer à la page suivante', - 'previous_page' => 'Passer à la page précédente', - 'collapse_article' => 'Refermer', - '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<br />(OPML, Json ou Zip)', - 'file_to_import_no_zip' => 'Fichier à importer<br />(OPML ou Json)', - 'import' => 'Importer', - 'file_cannot_be_uploaded' => 'Le fichier ne peut pas être téléchargé!', - 'zip_error' => 'Une erreur est survenue durant l’import du fichier Zip.', - 'no_zip_extension' => 'L’extension Zip n’est pas présente sur votre serveur.', - 'export' => 'Exporter', - 'export_opml' => 'Exporter la liste des flux (OPML)', - 'export_starred' => 'Exporter les favoris', - 'export_no_zip_extension' => 'L’extension Zip n’est pas présente sur votre serveur. Veuillez essayer d’exporter les fichiers un par un.', - 'starred_list' => 'Liste des articles favoris', - 'feed_list' => 'Liste des articles de %s', - 'or' => 'ou', - - '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' => '%d articles', - 'by_feed' => 'par flux', - 'by_default' => 'Par défaut', - 'keep_history' => 'Nombre minimum d’articles à conserver', - 'ttl' => 'Ne pas automatiquement rafraîchir plus souvent que', - 'categorize' => 'Ranger dans une catégorie', - 'truncate' => 'Supprimer tous les articles', - 'advanced' => 'Avancé', - 'show_in_all_flux' => 'Afficher dans le flux principal', - 'yes' => 'Oui', - 'no' => 'Non', - 'css_path_on_website' => 'Sélecteur CSS des articles sur le site d’origine', - 'retrieve_truncated_feeds' => 'Permet de récupérer les flux tronqués (attention, demande plus de temps !)', - 'http_authentication' => 'Authentification HTTP', - 'http_username' => 'Identifiant HTTP', - 'http_password' => 'Mot de passe HTTP', - 'blank_to_disable' => 'Laissez vide pour désactiver', - 'share_name' => 'Nom du partage à afficher', - 'share_url' => 'URL du partage à utiliser', - '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.', - - 'current_user' => 'Utilisateur actuel', - 'password_form' => 'Mot de passe<br /><small>(pour connexion par formulaire)</small>', - 'password_api' => 'Mot de passe API<br /><small>(ex. : pour applis mobiles)</small>', - 'default_user' => 'Nom de l’utilisateur par défaut <small>(16 caractères alphanumériques maximum)</small>', - 'persona_connection_email' => 'Adresse courriel de connexion<br /><small>(pour <a href="https://persona.org/" rel="external">Mozilla Persona</a>)</small>', - 'allow_anonymous' => 'Autoriser la lecture anonyme des articles de l’utilisateur par défaut (%s)', - 'allow_anonymous_refresh' => 'Autoriser le rafraîchissement anonyme des flux', - 'unsafe_autologin' => 'Autoriser les connexions automatiques non-sûres au format : ', - 'api_enabled' => 'Autoriser l’accès par <abbr>API</abbr> <small>(nécessaire pour les applis mobiles)</small>', - '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.<br /><kbd>%s?output=rss&token=%s</kbd>', - 'login_configuration' => 'Identification', - 'is_admin' => 'est administrateur', - 'auth_type' => 'Méthode d’authentification', - 'auth_none' => 'Aucune (dangereux)', - 'auth_form' => 'Formulaire (traditionnel, requiert JavaScript)', - 'http_auth' => 'HTTP (pour utilisateurs avancés avec HTTPS)', - 'auth_persona' => 'Mozilla Persona (moderne, requiert JavaScript)', - '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éé.', - 'user_deleted' => 'L’utilisateur %s a été supprimé.', - - 'language' => 'Langue', - 'month' => 'mois', - 'archiving_configuration' => 'Archivage', - 'delete_articles_every' => 'Supprimer les articles après', - 'purge_now' => 'Purger maintenant', - 'purge_completed' => 'Purge effectuée (%d articles supprimés).', - 'archiving_configuration_help' => 'D’autres options sont disponibles dans la configuration individuelle des flux.', - 'reading_configuration' => 'Lecture', - 'display_configuration' => 'Affichage', - 'articles_per_page' => 'Nombre d’articles par page', - '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 (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”', - 'auto_read_when' => 'Marquer un article comme lu…', - 'article_viewed' => 'lorsque l’article est affiché', - 'article_open_on_website' => 'lorsque l’article est ouvert sur le site d’origine', - 'scroll' => 'au défilement de la page', - 'upon_reception' => 'dès la réception du nouvel article', - 'your_shaarli' => 'Votre Shaarli', - 'your_wallabag' => 'Votre wallabag', - 'your_diaspora_pod' => 'Votre pod Diaspora*', - 'sharing' => 'Partage', - 'share' => 'Partager', - 'by_email' => 'Par courriel', - 'optimize_bdd' => 'Optimiser la base de données', - 'optimize_todo_sometimes' => 'À faire de temps en temps pour réduire la taille de la BDD', - 'theme' => 'Thème', - 'content_width' => 'Largeur du contenu', - 'width_thin' => 'Fine', - 'width_medium' => 'Moyenne', - 'width_large' => 'Large', - 'width_no_limit' => 'Pas de limite', - 'more_information' => 'Plus d’informations', - 'activate_sharing' => 'Activer le partage', - 'shaarli' => 'Shaarli', - 'blogotext' => 'Blogotext', - 'wallabag' => 'wallabag', - 'diaspora' => 'Diaspora*', - 'twitter' => 'Twitter', - 'g+' => 'Google+', - 'facebook' => 'Facebook', - 'email' => 'Courriel', - 'print' => 'Imprimer', - - 'article' => 'Article', - 'title' => 'Titre', - 'author' => 'Auteur', - 'publication_date' => 'Date de publication', - 'by' => 'par', - - 'load_more' => 'Charger plus d’articles', - 'nothing_to_load' => 'Fin des articles', - - 'rss_feeds_of' => 'Flux RSS de %s', - - 'refresh' => 'Actualisation', - 'no_feed_to_refresh' => 'Il n’y a aucun flux à actualiser…', - - 'today' => 'Aujourd’hui', - 'yesterday' => 'Hier', - 'before_yesterday' => 'À partir d’avant-hier', - 'new_article' => 'Il y a de nouveaux articles disponibles, cliquez pour rafraîchir la page.', - 'by_author' => 'Par <em>%s</em>', - 'related_tags' => 'Tags associés', - 'no_feed_to_display' => 'Il n’y a aucun article à afficher.', - - 'about_freshrss' => 'À propos de FreshRSS', - 'project_website' => 'Site du projet', - 'lead_developer' => 'Développeur principal', - 'website' => 'Site Internet', - 'bugs_reports' => 'Rapports de bugs', - 'github_or_email' => '<a href="https://github.com/marienfressinaud/FreshRSS/issues">sur Github</a> ou <a href="mailto:dev@marienfressinaud.fr">par courriel</a>', - 'license' => 'Licence', - 'agpl3' => '<a href="https://www.gnu.org/licenses/agpl-3.0.html">AGPL 3</a>', - 'freshrss_description' => 'FreshRSS est un agrégateur de flux RSS à auto-héberger à l’image de <a href="http://tontof.net/kriss/feed/">Kriss Feed</a> ou <a href="http://projet.idleman.fr/leed/">Leed</a>. Il se veut léger et facile à prendre en main tout en étant un outil puissant et paramétrable.', - 'credits' => 'Crédits', - 'credits_content' => 'Des éléments de design sont issus du <a href="http://twitter.github.io/bootstrap/">projet Bootstrap</a> bien que FreshRSS n’utilise pas ce framework. Les <a href="https://git.gnome.org/browse/gnome-icon-theme-symbolic">icônes</a> sont issues du <a href="https://www.gnome.org/">projet GNOME</a>. La police <em>Open Sans</em> utilisée a été créée par <a href="https://www.google.com/webfonts/specimen/Open+Sans">Steve Matteson</a>. Les favicons sont récupérés grâce au site <a href="https://getfavicon.appspot.com/">getFavicon</a>. FreshRSS repose sur <a href="https://github.com/marienfressinaud/MINZ">Minz</a>, un framework PHP.', - 'version' => 'Version', - - 'logs' => 'Logs', - 'logs_empty' => 'Les logs sont vides.', - 'clear_logs' => 'Effacer les logs', - - 'forbidden_access' => 'L’accès vous est interdit !', - '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.', - - // DATE - 'january' => 'janvier', - 'february' => 'février', - 'march' => 'mars', - 'april' => 'avril', - 'may' => 'mai', - 'june' => 'juin', - 'july' => 'juillet', - 'august' => 'août', - 'september' => 'septembre', - 'october' => 'octobre', - 'november' => 'novembre', - 'december' => 'décembre', - 'jan' => 'jan.', - 'feb' => 'fév.', - 'mar' => 'mar.', - 'apr' => 'avr.', - 'may' => 'mai.', - 'jun' => 'juin', - 'jul' => 'jui.', - 'aug' => 'août', - 'sep' => 'sep.', - 'oct' => 'oct.', - 'nov' => 'nov.', - 'dec' => 'déc.', - 'sun' => 'dim.', - 'mon' => 'lun.', - 'tue' => 'mar.', - 'wed' => 'mer.', - 'thu' => 'jeu.', - 'fri' => 'ven.', - 'sat' => 'sam.', - // format spécial pour la fonction date() - 'Jan' => '\j\a\n\v\i\e\r', - 'Feb' => '\f\é\v\r\i\e\r', - 'Mar' => '\m\a\r\s', - 'Apr' => '\a\v\r\i\l', - 'May' => '\m\a\i', - 'Jun' => '\j\u\i\n', - 'Jul' => '\j\u\i\l\l\e\t', - 'Aug' => '\a\o\û\t', - 'Sep' => '\s\e\p\t\e\m\b\r\e', - 'Oct' => '\o\c\t\o\b\r\e', - 'Nov' => '\n\o\v\e\m\b\r\e', - 'Dec' => '\d\é\c\e\m\b\r\e', - // format pour la fonction date(), %s permet d'indiquer le mois en toutes lettres - 'format_date' => 'j %s Y', - 'format_date_hour' => 'j %s Y \à H\:i', - - 'status_favorites' => 'favoris', - 'status_read' => 'lus', - 'status_unread' => 'non lus', - 'status_total' => 'total', - - 'stats_entry_repartition' => 'Répartition des articles', - 'stats_entry_per_day' => 'Nombre d’articles par jour (30 derniers jours)', - 'stats_feed_per_category' => 'Flux par catégorie', - '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.', + return array ( + 'Apr' => '\\a\\v\\r\\i\\l', + 'Aug' => '\\a\\o\\û\\t', + 'Dec' => '\\d\\é\\c\\e\\m\\b\\r\\e', + 'Feb' => '\\f\\é\\v\\r\\i\\e\\r', + 'Jan' => '\\j\\a\\n\\v\\i\\e\\r', + 'Jul' => '\\j\\u\\i\\l\\l\\e\\t', + 'Jun' => '\\j\\u\\i\\n', + 'Mar' => '\\m\\a\\r\\s', + 'May' => '\\m\\a\\i', + 'Nov' => '\\n\\o\\v\\e\\m\\b\\r\\e', + 'Oct' => '\\o\\c\\t\\o\\b\\r\\e', + 'Sep' => '\\s\\e\\p\\t\\e\\m\\b\\r\\e', + 'about' => 'À propos', + 'about_freshrss' => 'À propos de FreshRSS', + 'access_denied' => 'Vous n’avez pas le droit d’accéder à cette page !', + 'access_protected_feeds' => 'La connexion permet d’accéder aux flux protégés par une authentification HTTP.', + 'activate_sharing' => 'Activer le partage', + 'actualize' => 'Actualiser', + 'add_category' => 'Ajouter une catégorie', + 'add_query' => 'Créer un filtre', + 'add_rss_feed' => 'Ajouter un flux RSS', + 'admin.check_install.cache.nok' => 'Veuillez vérifier les droits sur le répertoire <em>./data/cache</em>. Le serveur HTTP doit être capable d’écrire dedans', + 'admin.check_install.cache.ok' => 'Les droits sur le répertoire de cache sont bons.', + 'admin.check_install.categories.nok' => 'La table category est mal configurée.', + 'admin.check_install.categories.ok' => 'La table category est bien configurée.', + 'admin.check_install.connection.nok' => 'La connexion à la base de données est impossible.', + 'admin.check_install.connection.ok' => 'La connexion à la base de données est bonne.', + 'admin.check_install.ctype.nok' => 'Il manque une librairie pour la vérification des types de caractères (php-ctype).', + 'admin.check_install.ctype.ok' => 'Vous disposez du nécessaire pour la vérification des types de caractères (ctype).', + 'admin.check_install.curl.nok' => 'Vous ne disposez pas de cURL (paquet php5-curl).', + 'admin.check_install.curl.ok' => 'Vous disposez de cURL.', + 'admin.check_install.data.nok' => 'Veuillez vérifier les droits sur le répertoire <em>./data</em>. Le serveur HTTP doit être capable d’écrire dedans', + 'admin.check_install.data.ok' => 'Les droits sur le répertoire de data sont bons.', + 'admin.check_install.database' => 'Installation de la base de données', + 'admin.check_install.dom.nok' => 'Il manque une librairie pour parcourir le DOM (paquet php-xml).', + 'admin.check_install.dom.ok' => 'Vous disposez du nécessaire pour parcourir le DOM.', + 'admin.check_install.entries.nok' => 'La table entry est mal configurée.', + 'admin.check_install.entries.ok' => 'La table entry est bien configurée.', + 'admin.check_install.favicons.nok' => 'Veuillez vérifier les droits sur le répertoire <em>./data/favicons</em>. Le serveur HTTP doit être capable d’écrire dedans', + 'admin.check_install.favicons.ok' => 'Les droits sur le répertoire des favicons sont bons.', + 'admin.check_install.feeds.nok' => 'La table feed est mal configurée.', + 'admin.check_install.feeds.ok' => 'La table feed est bien configurée.', + 'admin.check_install.files' => 'Installation des fichiers', + 'admin.check_install.json.nok' => 'Vous ne disposez pas de JSON (paquet php5-json).', + 'admin.check_install.json.ok' => 'Vous disposez de l\'extension JSON.', + 'admin.check_install.logs.nok' => 'Veuillez vérifier les droits sur le répertoire <em>./data/logs</em>. Le serveur HTTP doit être capable d’écrire dedans', + 'admin.check_install.logs.ok' => 'Les droits sur le répertoire des logs sont bons.', + 'admin.check_install.minz.nok' => 'Vous ne disposez pas de la librairie Minz.', + 'admin.check_install.minz.ok' => 'Vous disposez du framework Minz', + 'admin.check_install.pcre.nok' => 'Il manque une librairie pour les expressions régulières (php-pcre).', + 'admin.check_install.pcre.ok' => 'Vous disposez du nécessaire pour les expressions régulières (PCRE).', + 'admin.check_install.pdo.nok' => 'Vous ne disposez pas de PDO ou d’un des drivers supportés (pdo_mysql, pdo_sqlite).', + 'admin.check_install.pdo.ok' => 'Vous disposez de PDO et d’au moins un des drivers supportés (pdo_mysql, pdo_sqlite).', + 'admin.check_install.persona.nok' => 'Veuillez vérifier les droits sur le répertoire <em>./data/persona</em>. Le serveur HTTP doit être capable d’écrire dedans', + 'admin.check_install.persona.ok' => 'Les droits sur le répertoire de Mozilla Persona sont bons.', + 'admin.check_install.php' => 'Installation de PHP', + 'admin.check_install.php.nok' => 'Votre version de PHP est la %s mais FreshRSS requiert au moins la version %s.', + 'admin.check_install.php.ok' => 'Votre version de PHP est la %s, qui est compatible avec FreshRSS.', + 'admin.check_install.tables.nok' => 'Il manque une ou plusieurs tables en base de données.', + 'admin.check_install.tables.ok' => 'Les tables sont bien présentes en base de données.', + 'admin.check_install.tokens.nok' => 'Veuillez vérifier les droits sur le répertoire <em>./data/tokens</em>. Le serveur HTTP doit être capable d’écrire dedans', + 'admin.check_install.tokens.ok' => 'Les droits sur le répertoire des tokens sont bons.', + 'admin.check_install.zip.nok' => 'Vous ne disposez pas de l\'extension ZIP (paquet php5-zip).', + 'admin.check_install.zip.ok' => 'Vous disposez de l\'extension ZIP.', + 'admin.users.articles_and_size' => '%s articles (%s)', + 'administration' => 'Gérer', + 'advanced' => 'Avancé', + 'after_onread' => 'Après “marquer tout comme lu”,', + 'agpl3' => '<a href="https://www.gnu.org/licenses/agpl-3.0.html">AGPL 3</a>', + 'all_feeds' => 'Tous les flux', + 'allow_anonymous' => 'Autoriser la lecture anonyme des articles de l’utilisateur par défaut (%s)', + 'allow_anonymous_refresh' => 'Autoriser le rafraîchissement anonyme des flux', + 'already_subscribed' => 'Vous êtes déjà abonné à <em>%s</em>', + 'api_enabled' => 'Autoriser l’accès par <abbr>API</abbr> <small>(nécessaire pour les applis mobiles)</small>', + 'apr' => 'avr.', + 'april' => 'avril', + 'archiving_configuration' => 'Archivage', + 'archiving_configuration_help' => 'D’autres options sont disponibles dans la configuration individuelle des flux.', + 'article' => 'Article', + 'article_icons' => 'Icônes d’article', + 'article_open_on_website' => 'lorsque l’article est ouvert sur le site d’origine', + 'article_published_on' => 'Article publié initialement sur <a href="%s">%s</a>', + 'article_published_on_author' => 'Article publié initialement sur <a href="%s">%s</a> par %s', + 'article_viewed' => 'lorsque l’article est affiché', + 'articles' => 'articles', + 'articles_per_page' => 'Nombre d’articles par page', + 'articles_to_display' => 'Articles à afficher', + 'ask_empty' => 'Vider ?', + 'attention' => 'Attention !', + 'aug' => 'août', + 'august' => 'août', + 'auth_form' => 'Formulaire (traditionnel, requiert JavaScript)', + 'auth_form_not_set' => 'Un problème est survenu lors de la configuration de votre système d’authentification. Veuillez réessayer plus tard.', + 'auth_form_set' => 'Le formulaire est désormais votre système d’authentification.', + 'auth_no_password_set' => 'Aucun mot de passe administrateur n’a été précisé. Cette fonctionnalité n’est pas disponible.', + 'auth_none' => 'Aucune (dangereux)', + 'auth_not_persona' => 'Seul le système d’authentification Persona peut être réinitialisé.', + 'auth_persona' => 'Mozilla Persona (moderne, requiert JavaScript)', + 'auth_reset' => 'Réinitialisation de l’authentification', + 'auth_token' => 'Jeton d’identification', + 'auth_type' => 'Méthode d’authentification', + 'auth_will_reset' => 'Le système d’authentification va être réinitialisé : un formulaire sera utilisé à la place de Persona.', + 'author' => 'Auteur', + 'auto_load_more' => 'Charger les articles suivants en bas de page', + 'auto_read_when' => 'Marquer un article comme lu…', + '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.', + 'back_to_rss_feeds' => '← Retour à vos flux RSS', + 'bad_opml_file' => 'Votre fichier OPML n’est pas valide.', + 'base_url' => 'Base de l’URL', + 'bdd' => 'Base de données', + 'bdd_conf_is_ko' => 'Vérifiez les informations d’accès à la base de données.', + 'bdd_conf_is_ok' => 'La configuration de la base de données a été enregistrée.', + 'bdd_configuration' => 'Base de données', + 'bdd_type' => 'Type de base de données', + 'before_one_day' => 'Antérieurs à 1 jour', + 'before_one_week' => 'Antérieurs à 1 semaine', + 'before_yesterday' => 'À partir d’avant-hier', + 'blank_to_disable' => 'Laissez vide pour désactiver', + 'blogotext' => 'Blogotext', + 'bookmark' => 'S’abonner (bookmark FreshRSS)', + 'bottom_line' => 'Ligne du bas', + 'bugs_reports' => 'Rapports de bugs', + 'by' => 'par', + 'by_author' => 'Par <em>%s</em>', + 'by_default' => 'Par défaut', + 'by_email' => 'Par courriel', + 'by_feed' => 'par flux', + 'cache_is_ok' => 'Les droits sur le répertoire de cache sont bons', + 'can_not_be_deleted' => 'Ne peut pas être supprimée.', + 'cancel' => 'Annuler', + 'categories' => 'Catégories', + 'categories_management' => 'Gestion des catégories', + 'categories_updated' => 'Les catégories ont été mises à jour.', + 'categorize' => 'Ranger dans une catégorie', + 'category' => 'Catégorie', + 'category_created' => 'La catégorie %s a été créée.', + 'category_deleted' => 'La catégorie a été supprimée.', + 'category_emptied' => 'La catégorie a été vidée.', + 'category_empty' => 'Catégorie vide', + 'category_name_exists' => 'Une catégorie possède déjà ce nom.', + 'category_no_id' => 'Vous devez préciser l’id de la catégorie.', + 'category_no_name' => 'Vous devez préciser un nom pour la catégorie.', + 'category_not_delete_default' => 'Vous ne pouvez pas supprimer la catégorie par défaut !', + 'category_not_exist' => 'Cette catégorie n’existe pas !', + 'category_number' => 'Catégorie n°%d', + 'category_updated' => 'La catégorie a été mise à jour.', + 'change_value' => 'Vous devriez changer cette valeur par n’importe quelle autre', + 'checks' => 'Vérifications', + 'choose_language' => 'Choisissez la langue pour FreshRSS', + 'clear_logs' => 'Effacer les logs', + 'collapse_article' => 'Refermer', + 'conf.users.articles_and_size' => '%s articles (%s)', + 'configuration' => 'Configuration', + 'configuration_updated' => 'La configuration a été mise à jour.', + '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 perdrez les favoris et les filtres associés. Cette action ne peut être annulée !', + 'congratulations' => 'Félicitations !', + 'content_width' => 'Largeur du contenu', + 'create' => 'Créer', + 'create_user' => 'Créer un nouvel utilisateur', + 'credits' => 'Crédits', + 'credits_content' => 'Des éléments de design sont issus du <a href="http://twitter.github.io/bootstrap/">projet Bootstrap</a> bien que FreshRSS n’utilise pas ce framework. Les <a href="https://git.gnome.org/browse/gnome-icon-theme-symbolic">icônes</a> sont issues du <a href="https://www.gnome.org/">projet GNOME</a>. La police <em>Open Sans</em> utilisée a été créée par <a href="https://www.google.com/webfonts/specimen/Open+Sans">Steve Matteson</a>. Les favicons sont récupérés grâce au site <a href="https://getfavicon.appspot.com/">getFavicon</a>. FreshRSS repose sur <a href="https://github.com/marienfressinaud/MINZ">Minz</a>, un framework PHP.', + 'css_path_on_website' => 'Sélecteur CSS des articles sur le site d’origine', + 'ctype_is_nok' => 'Il manque une librairie pour la vérification des types de caractères (php-ctype)', + 'ctype_is_ok' => 'Vous disposez du nécessaire pour la vérification des types de caractères (ctype)', + 'curl_is_nok' => 'Vous ne disposez pas de cURL (paquet php5-curl)', + 'curl_is_ok' => 'Vous disposez de cURL dans sa version %s', + 'current_user' => 'Utilisateur actuel', + 'damn' => 'Arf !', + 'data_is_ok' => 'Les droits sur le répertoire de data sont bons', + 'dec' => 'déc.', + 'december' => 'décembre', + 'default_category' => 'Sans catégorie', + 'default_user' => 'Nom de l’utilisateur par défaut <small>(16 caractères alphanumériques maximum)</small>', + 'default_view' => 'Vue par défaut', + 'delete' => 'Supprimer', + 'delete_articles_every' => 'Supprimer les articles après', + 'diaspora' => 'Diaspora*', + 'display' => 'Affichage', + 'display_articles_unfolded' => 'Afficher les articles dépliés par défaut', + 'display_categories_unfolded' => 'Afficher les catégories pliées par défaut', + 'display_configuration' => 'Affichage', + 'do_not_change_if_doubt' => 'Laissez tel quel dans le doute', + 'dom_is_nok' => 'Il manque une librairie pour parcourir le DOM (paquet php-xml)', + 'dom_is_ok' => 'Vous disposez du nécessaire pour parcourir le DOM', + 'email' => 'Courriel', + 'error_occurred' => 'Une erreur est survenue !', + 'error_occurred_update' => 'Rien n’a été modifié !', + 'explain_token' => 'Permet d’accéder à la sortie RSS de l’utilisateur par défaut sans besoin de s’authentifier.<br /><kbd>%s?output=rss&token=%s</kbd>', + 'export' => 'Exporter', + 'export_no_zip_extension' => 'L’extension Zip n’est pas présente sur votre serveur. Veuillez essayer d’exporter les fichiers un par un.', + 'export_opml' => 'Exporter la liste des flux (OPML)', + 'export_starred' => 'Exporter les favoris', + 'facebook' => 'Facebook', + 'favicons_is_ok' => 'Les droits sur le répertoire des favicons sont bons', + 'favorite_feeds' => 'Favoris (%s)', + 'feb' => 'fév.', + 'february' => 'février', + 'feed' => 'Flux', + 'feed_actualized' => '<em>%s</em> a été mis à jour.', + 'feed_added' => 'Le flux <em>%s</em> a bien été ajouté.', + 'feed_deleted' => 'Le flux a été supprimé.', + 'feed_description' => 'Description', + 'feed_empty' => 'Ce flux est vide. Veuillez vérifier qu’il est toujours maintenu.', + 'feed_in_error' => 'Ce flux a rencontré un problème. Veuillez vérifier qu’il est toujours accessible puis actualisez-le.', + 'feed_list' => 'Liste des articles de %s', + 'feed_not_added' => '<em>%s</em> n’a pas pu être ajouté.', + 'feed_updated' => 'Le flux a été mis à jour.', + 'feed_url' => 'URL du flux', + 'feed_validator' => 'Vérifier la valididé du flux', + 'feedback.login.error' => 'L’identifiant est invalide !', + 'feedback.login.success' => 'Vous êtes désormais connecté', + 'feedback.logout.success' => 'Vous avez été déconnecté', + 'feedback.user_profile.updated' => 'Votre profil a été mis à jour', + 'feeds' => 'Flux', + 'feeds_actualized' => 'Les flux ont été mis à jour.', + 'feeds_imported' => 'Vos flux ont été importés et vont maintenant être actualisés.', + 'feeds_imported_with_errors' => 'Vos flux ont été importés mais des erreurs sont survenues.', + 'feeds_marked_read' => 'Les flux ont été marqués comme lus.', + 'feeds_moved_category_deleted' => 'Lors de la suppression d’une catégorie, ses flux seront automatiquement classés dans <em>%s</em>.', + 'file_cannot_be_uploaded' => 'Le fichier ne peut pas être téléchargé !', + 'file_is_nok' => 'Veuillez vérifier les droits sur le répertoire <em>%s</em>. Le serveur HTTP doit être capable d’écrire dedans', + 'file_to_import' => 'Fichier à importer<br />(OPML, Json ou Zip)', + 'file_to_import_no_zip' => 'Fichier à importer<br />(OPML ou Json)', + 'filter' => 'Filtrer', + 'finish_installation' => 'Terminer l’installation', + 'first' => 'Début', + 'first_article' => 'Passer au premier article', + 'fix_errors_before' => 'Veuillez corriger les erreurs avant de passer à l’étape suivante.', + 'focus_search' => 'Accéder à la recherche', + 'format_date' => 'j %s Y', + 'format_date_hour' => 'j %s Y \\à H\\:i', + 'freshrss' => 'FreshRSS', + 'freshrss_description' => 'FreshRSS est un agrégateur de flux RSS à auto-héberger à l’image de <a href="http://tontof.net/kriss/feed/">Kriss Feed</a> ou <a href="http://projet.idleman.fr/leed/">Leed</a>. Il se veut léger et facile à prendre en main tout en étant un outil puissant et paramétrable.', + 'freshrss_installation' => 'Installation · FreshRSS', + 'fri' => 'ven.', + 'g+' => 'Google+', + 'gen.menu.admin' => 'Administration', + 'gen.menu.authentication' => 'Authentification', + 'gen.menu.check_install' => 'Vérification de l\'installation', + 'gen.menu.user_management' => 'Gestion des utilisateurs', + 'gen.menu.user_profile' => 'Profil', + 'gen.title.authentication' => 'Authentification', + 'gen.title.check_install' => 'Vérification de l\'installation', + 'gen.title.global_view' => 'Vue globale', + 'gen.title.user_management' => 'Gestion des utilisateurs', + 'gen.title.user_profile' => 'Profil', + 'general_conf_is_ok' => 'La configuration générale a été enregistrée.', + 'general_configuration' => 'Configuration générale', + 'github_or_email' => '<a href="https://github.com/marienfressinaud/FreshRSS/issues">sur Github</a> ou <a href="mailto:dev@marienfressinaud.fr">par courriel</a>', + 'global_view' => 'Vue globale', + 'help' => 'Afficher la documentation', + 'hide_read_feeds' => 'Cacher les catégories & flux sans article non-lu (ne fonctionne pas avec la configuration “Afficher tous les articles”)', + 'host' => 'Hôte', + 'html5_notif_timeout' => 'Temps d’affichage de la notification HTML5', + 'http_auth' => 'HTTP (pour utilisateurs avancés avec HTTPS)', + 'http_authentication' => 'Authentification HTTP', + 'http_password' => 'Mot de passe HTTP', + 'http_referer_is_nok' => 'Veuillez vérifier que vous ne modifiez pas votre HTTP REFERER.', + 'http_referer_is_ok' => 'Le HTTP REFERER est connu et semble correspondre à votre serveur.', + 'http_username' => 'Identifiant HTTP', + 'img_with_lazyload' => 'Utiliser le mode “chargement différé” pour les images', + 'import' => 'Importer', + 'import_export' => 'Importer / exporter', + 'informations' => 'Informations', + 'install_not_deleted' => 'Quelque chose s’est mal passé, vous devez supprimer le fichier <em>%s</em> à la main.', + 'installation_is_ok' => 'L’installation s’est bien passée.<br />La dernière étape va maintenant tenter de supprimer les fichiers ainsi que d’éventuelles copies de base de données créés durant le processus de mise à jour.<br />Vous pouvez choisir de sauter cette étape en supprimant <kbd>./data/do-install.txt</kbd> manuellement.', + 'installation_step' => 'Installation — étape %d · FreshRSS', + 'internal_problem_feed' => 'Le flux ne peut pas être ajouté. <a href="%s">Consulter les logs de FreshRSS</a> pour plus de détails.', + 'invalid_login' => 'L’identifiant est invalide !', + 'invalid_url' => 'L’url <em>%s</em> est invalide.', + 'is_admin' => 'est administrateur', + 'jan' => 'jan.', + 'january' => 'janvier', + 'javascript_for_shortcuts' => 'Le JavaScript doit être activé pour pouvoir profiter des raccourcis.', + 'javascript_is_better' => 'FreshRSS est plus agréable à utiliser avec JavaScript activé', + 'javascript_should_be_activated' => 'Le JavaScript doit être activé.', + 'jul' => 'jui.', + 'july' => 'juillet', + 'jump_next' => 'sauter au prochain voisin non lu (flux ou catégorie)', + 'jun' => 'juin', + 'june' => 'juin', + 'keep_history' => 'Nombre minimum d’articles à conserver', + 'keep_logged_in' => 'Rester connecté <small>(1 mois)</small>', + 'language' => 'Langue', + 'language_defined' => 'La langue a bien été définie.', + 'last' => 'Fin', + 'last_3_month' => 'Depuis les trois derniers mois', + 'last_6_month' => 'Depuis les six derniers mois', + 'last_article' => 'Passer au dernier article', + 'last_month' => 'Depuis le mois dernier', + 'last_week' => 'Depuis la semaine dernière', + 'last_year' => 'Depuis l’année dernière', + 'lead_developer' => 'Développeur principal', + 'license' => 'Licence', + 'load_more' => 'Charger plus d’articles', + 'log_is_ok' => 'Les droits sur le répertoire des logs sont bons', + 'login' => 'Connexion', + 'login_configuration' => 'Identification', + 'login_persona_problem' => 'Problème de connexion à Persona ?', + 'login_required' => 'Accès protégé par mot de passe :', + 'login_with_persona' => 'Connexion avec Persona', + 'logout' => 'Déconnexion', + 'logs' => 'Logs', + 'logs_empty' => 'Les logs sont vides.', + 'main_stream' => 'Flux principal', + 'mar' => 'mar.', + 'march' => 'mars', + 'mark_all_read' => 'Tout marquer comme lu', + 'mark_cat_read' => 'Marquer la catégorie comme lue', + 'mark_favorite' => 'Mettre en favori', + 'mark_feed_read' => 'Marquer le flux comme lu', + 'mark_read' => 'Marquer comme lu', + 'may' => 'mai.', + 'minz_is_nok' => 'Vous ne disposez pas de la librairie Minz. Vous devriez exécuter le script <em>build.sh</em> ou bien <a href="https://github.com/marienfressinaud/MINZ">la télécharger sur Github</a> et installer dans le répertoire <em>%s</em> le contenu de son répertoire <em>/lib</em>.', + 'minz_is_ok' => 'Vous disposez du framework Minz', + 'mon' => 'lun.', + 'month' => 'mois', + 'more_information' => 'Plus d’informations', + 'n_entries_deleted' => '%d articles ont été supprimés.', + 'n_feeds_actualized' => '%d flux ont été mis à jour.', + 'new_article' => 'Il y a de nouveaux articles disponibles, cliquez pour rafraîchir la page.', + 'new_category' => 'Nouvelle catégorie', + 'newer_first' => 'Plus récents en premier', + 'next' => 'Suivant', + 'next_article' => 'Passer à l’article suivant', + 'next_page' => 'Passer à la page suivante', + 'next_step' => 'Passer à l’étape suivante', + 'no' => 'Non', + 'no_feed_actualized' => 'Aucun flux n’a pu être mis à jour.', + 'no_feed_to_display' => 'Il n’y a aucun article à afficher.', + 'no_feed_to_refresh' => 'Il n’y a aucun flux à actualiser…', + 'no_query' => 'Vous n’avez pas encore créé de filtre.', + 'no_query_filter' => 'Aucun filtre appliqué', + 'no_rss_feed' => 'Aucun flux RSS', + 'no_selected_feed' => 'Aucun flux sélectionné.', + 'no_update' => 'Aucune mise à jour à appliquer', + 'no_zip_extension' => 'L’extension Zip n’est pas présente sur votre serveur.', + 'normal_view' => 'Vue normale', + 'not_read' => '%d non lu', + 'not_reads' => '%d non lus', + 'not_yet_implemented' => 'Pas encore implémenté', + 'nothing_to_load' => 'Fin des articles', + 'notif_body_new_articles' => 'Il y a \\d nouveaux articles à lire sur FreshRSS.', + 'notif_title_new_articles' => 'FreshRSS : nouveaux articles !', + 'nov' => 'nov.', + 'november' => 'novembre', + 'number_articles' => '%d articles', + 'number_divided_when_reader' => 'Divisé par 2 dans la vue de lecture.', + 'number_feeds' => '%d flux', + 'oct' => 'oct.', + 'october' => 'octobre', + 'ok' => 'Ok !', + 'older_first' => 'Plus anciens en premier', + 'oops' => 'Oups !', + 'optimization_complete' => 'Optimisation terminée.', + 'optimize_bdd' => 'Optimiser la base de données', + 'optimize_todo_sometimes' => 'À faire de temps en temps pour réduire la taille de la BDD', + 'or' => 'ou', + 'page_not_found' => 'La page que vous cherchez n’existe pas !', + 'password' => 'Mot de passe', + 'password_api' => 'Mot de passe API<br /><small>(ex. : pour applis mobiles)</small>', + 'password_form' => 'Mot de passe<br /><small>(pour connexion par formulaire)</small>', + 'pcre_is_nok' => 'Il manque une librairie pour les expressions régulières (php-pcre)', + 'pcre_is_ok' => 'Vous disposez du nécessaire pour les expressions régulières (PCRE)', + 'pdo_is_nok' => 'Vous ne disposez pas de PDO ou d’un des drivers supportés (pdo_mysql, pdo_sqlite)', + 'pdo_is_ok' => 'Vous disposez de PDO et d’au moins un des drivers supportés (pdo_mysql, pdo_sqlite)', + 'persona_connection_email' => 'Adresse courriel de connexion<br /><small>(pour <a href="https://persona.org/" rel="external">Mozilla Persona</a>)</small>', + 'persona_is_ok' => 'Les droits sur le répertoire de Mozilla Persona sont bons', + 'php_is_nok' => 'Votre version de PHP est la %s mais FreshRSS requiert au moins la version %s', + 'php_is_ok' => 'Votre version de PHP est la %s, qui est compatible avec FreshRSS', + 'prefix' => 'Préfixe des tables', + 'previous' => 'Précédent', + 'previous_article' => 'Passer à l’article précédent', + 'previous_page' => 'Passer à la page précédente', + 'print' => 'Imprimer', + 'project_website' => 'Site du projet', + 'public' => 'Public', + 'publication_date' => 'Date de publication', + 'purge_completed' => 'Purge effectuée (%d articles supprimés).', + 'purge_now' => 'Purger maintenant', + 'queries' => 'Filtres utilisateurs', + 'query_created' => 'Le filtre "%s" a bien été créé.', + 'query_deprecated' => 'Ce filtre n’est plus valide. La catégorie ou le flux concerné a été supprimé.', + 'query_filter' => 'Filtres appliqués :', + 'query_get_all' => 'Afficher tous les articles', + 'query_get_category' => 'Afficher la catégorie "%s"', + 'query_get_favorite' => 'Afficher les articles favoris', + 'query_get_feed' => 'Afficher le flux "%s"', + 'query_number' => 'Filtre n°%d', + 'query_order_asc' => 'Afficher les articles les plus anciens en premier', + 'query_order_desc' => 'Afficher les articles les plus récents en premier', + 'query_search' => 'Recherche de "%s"', + 'query_state_0' => 'Afficher tous les articles', + 'query_state_1' => 'Afficher les articles lus', + 'query_state_2' => 'Afficher les articles non lus', + 'query_state_3' => 'Afficher tous les articles', + 'query_state_4' => 'Afficher les articles favoris', + 'query_state_5' => 'Afficher les articles lus et favoris', + 'query_state_6' => 'Afficher les articles non lus et favoris', + 'query_state_7' => 'Afficher les articles favoris', + 'query_state_8' => 'Afficher les articles non favoris', + 'query_state_9' => 'Afficher les articles lus et non favoris', + 'query_state_10' => 'Afficher les articles non lus et non favoris', + 'query_state_11' => 'Afficher les articles non favoris', + 'query_state_12' => 'Afficher tous les articles', + 'query_state_13' => 'Afficher les articles lus', + 'query_state_14' => 'Afficher les articles non lus', + 'query_state_15' => 'Afficher tous les articles', + 'random_string' => 'Chaîne aléatoire', + 'reader_view' => 'Vue lecture', + 'reading_configuration' => 'Lecture', + 'reading_confirm' => 'Afficher une confirmation lors des actions “marquer tout comme lu”', + 'refresh' => 'Actualisation', + 'related_tags' => 'Tags associés', + 'retrieve_truncated_feeds' => 'Permet de récupérer les flux tronqués (attention, demande plus de temps !)', + 'rss_feed_management' => 'Gestion des flux RSS', + 'rss_feeds_of' => 'Flux RSS de %s', + 'rss_view' => 'Flux RSS', + 'sat' => 'sam.', + 'save' => 'Enregistrer', + 'scroll' => 'au défilement de la page', + 'search' => 'Rechercher des mots ou des #tags', + 'search_short' => 'Rechercher', + 'seconds_(0_means_no_timeout)' => 'secondes (0 signifie aucun timeout ) ', + 'see_on_website' => 'Voir sur le site d’origine', + 'see_website' => 'Voir le site', + 'sep' => 'sep.', + 'september' => 'septembre', + 'shaarli' => 'Shaarli', + 'share' => 'Partager', + 'share_name' => 'Nom du partage à afficher', + 'share_url' => 'URL du partage à utiliser', + 'sharing' => 'Partage', + 'sharing_management' => 'Gestion des options de partage', + 'shift_for_all_read' => '+ <code>shift</code> pour marquer tous les articles comme lus', + 'shortcuts' => 'Raccourcis', + 'shortcuts_article_action' => 'Actions associées à l’article courant', + 'shortcuts_navigation' => 'Navigation', + 'shortcuts_navigation_help' => 'Avec le modificateur "Shift", les raccourcis de navigation s’appliquent aux flux.<br/>Avec le modificateur "Alt", les raccourcis de navigation s’appliquent aux catégories.', + 'shortcuts_other_action' => 'Autres actions', + 'shortcuts_updated' => 'Les raccourcis ont été mis à jour.', + 'show_adaptive' => 'Adapter l’affichage', + 'show_all_articles' => 'Afficher tous les articles', + 'show_favorite' => 'Afficher les favoris', + 'show_in_all_flux' => 'Afficher dans le flux principal', + 'show_not_favorite' => 'Afficher tout sauf les favoris', + 'show_not_reads' => 'Afficher les non lus', + 'show_read' => 'Afficher les lus', + 'sort_order' => 'Ordre de tri', + 'starred_list' => 'Liste des articles favoris', + 'stats' => 'Statistiques', + 'stats_entry_count' => 'Nombre d’articles', + 'stats_entry_per_category' => 'Articles par catégorie', + 'stats_entry_per_day' => 'Nombre d’articles par jour (30 derniers jours)', + 'stats_entry_per_day_of_week' => 'Par jour de la semaine (moyenne : %.2f messages)', + 'stats_entry_per_hour' => 'Par heure (moyenne : %.2f messages)', + 'stats_entry_per_month' => 'Par mois (moyenne : %.2f messages)', + 'stats_entry_repartition' => 'Répartition des articles', + 'stats_feed_per_category' => 'Flux par catégorie', + 'stats_idle' => 'Flux inactifs', + 'stats_main' => 'Statistiques principales', + 'stats_no_idle' => 'Il n’y a aucun flux inactif !', + 'stats_percent_of_total' => '%% du total', + 'stats_repartition' => 'Répartition des articles', + 'stats_top_feed' => 'Les dix plus gros flux', + 'status_favorites' => 'favoris', + 'status_read' => 'lus', + 'status_total' => 'total', + 'status_unread' => 'non lus', + 'steps' => 'Étapes', + 'sticky_post' => 'Aligner l’article en haut quand il est ouvert', + 'sub.categories.over_max' => 'Vous avez atteint votre limite de catégories (%d)', + 'sub.feeds.over_max' => 'Vous avez atteint votre limite de flux (%d)', + 'submit' => 'Valider', + 'subscription_management' => 'Gestion des abonnements', + 'sun' => 'dim.', + 'theme' => 'Thème', + 'think_to_add' => 'Vous pouvez ajouter des flux.', + 'this_is_the_end' => 'This is the end', + 'thu' => 'jeu.', + 'title' => 'Titre', + 'today' => 'Aujourd’hui', + 'top_line' => 'Ligne du haut', + 'truncate' => 'Supprimer tous les articles', + 'ttl' => 'Ne pas automatiquement rafraîchir plus souvent que', + 'tue' => 'mar.', + 'twitter' => 'Twitter', + 'unsafe_autologin' => 'Autoriser les connexions automatiques non-sûres au format : ', + 'update' => 'Mise à jour', + 'update_apply' => 'Appliquer la mise à jour', + 'update_can_apply' => 'Une mise à jour est disponible.', + 'update_check' => 'Vérifier les mises à jour', + 'update_end' => 'La mise à jour est terminée, vous pouvez maintenant passer à l’étape finale.', + 'update_finished' => 'La mise à jour est terminée !', + 'update_last' => 'Dernière vérification : %s', + 'update_long' => 'Ce processus peut prendre longtemps, selon la taille de votre base de données. Vous aurez peut-être à attendre que cette page dépasse son temps maximum d’exécution (~5 minutes) puis à la recharger.', + 'update_problem' => 'La mise à jour a rencontré un problème : %s', + 'update_server_not_found' => 'Le serveur de mise à jour n’a pas été trouvé. [%s]', + 'update_start' => 'Lancer la mise à jour', + 'update_system' => 'Système de mise à jour', + 'updated' => 'Modifications enregistrées.', + 'upon_reception' => 'dès la réception du nouvel article', + 'user_created' => 'L’utilisateur %s a été créé.', + 'user_deleted' => 'L’utilisateur %s a été supprimé.', + '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.', + 'username' => 'Nom d’utilisateur', + 'username_admin' => 'Nom d’utilisateur administrateur', + 'users' => 'Utilisateurs', + 'users_list' => 'Liste des utilisateurs', + 'version' => 'Version', + 'version_update' => 'Mise à jour', + 'wallabag' => 'wallabag', + 'website' => 'Site Internet', + 'website_url' => 'URL du site', + 'wed' => 'mer.', + 'width_large' => 'Large', + 'width_medium' => 'Moyenne', + 'width_no_limit' => 'Pas de limite', + 'width_thin' => 'Fine', + 'yes' => 'Oui', + 'yesterday' => 'Hier', + 'your_diaspora_pod' => 'Votre pod Diaspora*', + 'your_favorites' => 'Vos favoris', + 'your_rss_feeds' => 'Vos flux RSS', + 'your_shaarli' => 'Votre Shaarli', + 'your_wallabag' => 'Votre wallabag', + 'zip_error' => 'Une erreur est survenue durant l’import du fichier Zip.', ); diff --git a/app/i18n/install.en.php b/app/i18n/install.en.php deleted file mode 100644 index c422de90f..000000000 --- a/app/i18n/install.en.php +++ /dev/null @@ -1,69 +0,0 @@ -<?php -return array ( - 'freshrss_installation' => 'Installation · FreshRSS', - 'freshrss' => 'FreshRSS', - 'installation_step' => 'Installation — step %d · FreshRSS', - 'steps' => 'Steps', - 'checks' => 'Checks', - 'general_configuration' => 'General configuration', - 'bdd_configuration' => 'Database configuration', - 'bdd_type' => 'Type of database', - 'version_update' => 'Update', - 'this_is_the_end' => 'This is the end', - - 'ok' => 'Ok!', - 'congratulations' => 'Congratulations!', - 'attention' => 'Attention!', - 'damn' => 'Damn!', - 'oops' => 'Oops!', - 'next_step' => 'Go to the next step', - - 'language_defined' => 'Language has been defined.', - 'choose_language' => 'Choose a language for FreshRSS', - - 'javascript_is_better' => 'FreshRSS is more pleasant with JavaScript enabled', - 'php_is_ok' => 'Your PHP version is %s, which is compatible with FreshRSS', - 'php_is_nok' => 'Your PHP version is %s but FreshRSS requires at least version %s', - 'minz_is_ok' => 'You have the Minz framework', - 'minz_is_nok' => 'You lack the Minz framework. You should execute <em>build.sh</em> script or <a href="https://github.com/marienfressinaud/MINZ">download it on Github</a> and install in <em>%s</em> directory the content of its <em>/lib</em> directory.', - 'curl_is_ok' => 'You have version %s of cURL', - 'curl_is_nok' => 'You lack cURL (php5-curl package)', - 'pdo_is_ok' => 'You have PDO and at least one of the supported drivers (pdo_mysql, pdo_sqlite)', - 'pdo_is_nok' => 'You lack PDO or one of the supported drivers (pdo_mysql, pdo_sqlite)', - 'dom_is_ok' => 'You have the required library to browse the DOM', - 'dom_is_nok' => 'You lack a required library to browse the DOM (php-xml package)', - 'pcre_is_ok' => 'You have the required library for regular expressions (PCRE)', - 'pcre_is_nok' => 'You lack a required library for regular expressions (php-pcre)', - 'ctype_is_ok' => 'You have the required library for character type checking (ctype)', - 'ctype_is_nok' => 'You lack a required library for character type checking (php-ctype)', - 'cache_is_ok' => 'Permissions on cache directory are good', - 'log_is_ok' => 'Permissions on logs directory are good', - 'favicons_is_ok' => 'Permissions on favicons directory are good', - '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 <em>%s</em> 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.', - 'random_string' => 'Random string', - 'change_value' => 'You should change this value by any other', - 'base_url' => 'Base URL', - 'do_not_change_if_doubt' => 'Don’t change if you doubt about it', - - 'bdd_conf_is_ok' => 'Database configuration has been saved.', - 'bdd_conf_is_ko' => 'Verify your database information.', - 'host' => 'Host', - 'bdd' => 'Database', - 'prefix' => 'Table prefix', - - 'update_start' => 'Start update process', - 'update_long' => 'This can take a long time, depending on the size of your database. You may have to wait for this page to time out (~5 minutes) and then refresh this page.', - 'update_end' => 'Update process is completed, now you can go to the final step.', - - - 'installation_is_ok' => 'The installation process was successful.<br />The final step will now attempt to delete any file and database backup created during the update process.<br />You may choose to skip this step by deleting <kbd>./data/do-install.txt</kbd> manually.', - 'finish_installation' => 'Complete installation', - 'install_not_deleted' => 'Something went wrong; you must delete the file <em>%s</em> manually.', -); diff --git a/app/i18n/install.fr.php b/app/i18n/install.fr.php deleted file mode 100644 index 785c02459..000000000 --- a/app/i18n/install.fr.php +++ /dev/null @@ -1,68 +0,0 @@ -<?php -return array ( - 'freshrss_installation' => 'Installation · FreshRSS', - 'freshrss' => 'FreshRSS', - 'installation_step' => 'Installation — étape %d · FreshRSS', - 'steps' => 'Étapes', - 'checks' => 'Vérifications', - 'general_configuration' => 'Configuration générale', - 'bdd_configuration' => 'Base de données', - 'bdd_type' => 'Type de base de données', - 'version_update' => 'Mise à jour', - 'this_is_the_end' => 'This is the end', - - 'ok' => 'Ok !', - 'congratulations' => 'Félicitations !', - 'attention' => 'Attention !', - 'damn' => 'Arf !', - 'oops' => 'Oups !', - 'next_step' => 'Passer à l’étape suivante', - - 'language_defined' => 'La langue a bien été définie.', - 'choose_language' => 'Choisissez la langue pour FreshRSS', - - 'javascript_is_better' => 'FreshRSS est plus agréable à utiliser avec JavaScript activé', - 'php_is_ok' => 'Votre version de PHP est la %s, qui est compatible avec FreshRSS', - 'php_is_nok' => 'Votre version de PHP est la %s mais FreshRSS requiert au moins la version %s', - 'minz_is_ok' => 'Vous disposez du framework Minz', - 'minz_is_nok' => 'Vous ne disposez pas de la librairie Minz. Vous devriez exécuter le script <em>build.sh</em> ou bien <a href="https://github.com/marienfressinaud/MINZ">la télécharger sur Github</a> et installer dans le répertoire <em>%s</em> le contenu de son répertoire <em>/lib</em>.', - 'curl_is_ok' => 'Vous disposez de cURL dans sa version %s', - 'curl_is_nok' => 'Vous ne disposez pas de cURL (paquet php5-curl)', - 'pdo_is_ok' => 'Vous disposez de PDO et d’au moins un des drivers supportés (pdo_mysql, pdo_sqlite)', - 'pdo_is_nok' => 'Vous ne disposez pas de PDO ou d’un des drivers supportés (pdo_mysql, pdo_sqlite)', - 'dom_is_ok' => 'Vous disposez du nécessaire pour parcourir le DOM', - 'dom_is_nok' => 'Il manque une librairie pour parcourir le DOM (paquet php-xml)', - 'pcre_is_ok' => 'Vous disposez du nécessaire pour les expressions régulières (PCRE)', - 'pcre_is_nok' => 'Il manque une librairie pour les expressions régulières (php-pcre)', - 'ctype_is_ok' => 'Vous disposez du nécessaire pour la vérification des types de caractères (ctype)', - 'ctype_is_nok' => 'Il manque une librairie pour la vérification des types de caractères (php-ctype)', - 'cache_is_ok' => 'Les droits sur le répertoire de cache sont bons', - 'log_is_ok' => 'Les droits sur le répertoire des logs sont bons', - 'favicons_is_ok' => 'Les droits sur le répertoire des favicons sont bons', - '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 <em>%s</em>. 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.', - 'random_string' => 'Chaîne aléatoire', - 'change_value' => 'Vous devriez changer cette valeur par n’importe quelle autre', - 'base_url' => 'Base de l’URL', - 'do_not_change_if_doubt' => 'Laissez tel quel dans le doute', - - 'bdd_conf_is_ok' => 'La configuration de la base de données a été enregistrée.', - 'bdd_conf_is_ko' => 'Vérifiez les informations d’accès à la base de données.', - 'host' => 'Hôte', - 'bdd' => 'Base de données', - 'prefix' => 'Préfixe des tables', - - 'update_start' => 'Lancer la mise à jour', - 'update_long' => 'Ce processus peut prendre longtemps, selon la taille de votre base de données. Vous aurez peut-être à attendre que cette page dépasse son temps maximum d’exécution (~5 minutes) puis à la recharger.', - 'update_end' => 'La mise à jour est terminée, vous pouvez maintenant passer à l’étape finale.', - - 'installation_is_ok' => 'L’installation s’est bien passée.<br />La dernière étape va maintenant tenter de supprimer les fichiers ainsi que d’éventuelles copies de base de données créés durant le processus de mise à jour.<br />Vous pouvez choisir de sauter cette étape en supprimant <kbd>./data/do-install.txt</kbd> manuellement.', - 'finish_installation' => 'Terminer l’installation', - 'install_not_deleted' => 'Quelque chose s’est mal passé, vous devez supprimer le fichier <em>%s</em> à la main.', -); diff --git a/app/install.php b/app/install.php index 4449cd063..cfdd733ce 100644 --- a/app/install.php +++ b/app/install.php @@ -54,11 +54,6 @@ function initTranslate() { if (file_exists($file)) { $translates = array_merge($translates, include($file)); } - - $file = APP_PATH . '/i18n/install.' . $actual . '.php'; - if (file_exists($file)) { - $translates = array_merge($translates, include($file)); - } } function getBetterLanguage($fallback) { diff --git a/app/layout/aside_configure.phtml b/app/layout/aside_configure.phtml index d5c9bf4c9..53c52d3e3 100644 --- a/app/layout/aside_configure.phtml +++ b/app/layout/aside_configure.phtml @@ -18,15 +18,25 @@ <li class="item<?php echo Minz_Request::actionName() === 'queries' ? ' active' : ''; ?>"> <a href="<?php echo _url('configure', 'queries'); ?>"><?php echo _t('queries'); ?></a> </li> - <li class="separator"></li> - <li class="item<?php echo Minz_Request::actionName() === 'users' ? ' active' : ''; ?>"> - <a href="<?php echo _url('configure', 'users'); ?>"><?php echo _t('users'); ?></a> - </li> - <?php - $current_user = Minz_Session::param('currentUser', ''); - if (Minz_Configuration::isAdmin($current_user)) { - ?> - <li class="item<?php echo Minz_Request::controllerName() === 'update' ? ' active' : ''; ?>"> + <li class="item<?php echo Minz_Request::controllerName() === 'user' && + Minz_Request::actionName() === 'profile'? ' active' : ''; ?>"> + <a href="<?php echo _url('user', 'profile'); ?>"><?php echo _t('gen.menu.user_profile'); ?></a> + </li> + <?php if (FreshRSS_Auth::hasAccess('admin')) { ?> + <li class="nav-header"><?php echo _t('gen.menu.admin'); ?></li> + <li class="item<?php echo Minz_Request::controllerName() === 'user' && + Minz_Request::actionName() === 'manage' ? ' active' : ''; ?>"> + <a href="<?php echo _url('user', 'manage'); ?>"><?php echo _t('gen.menu.user_management'); ?></a> + </li> + <li class="item<?php echo Minz_Request::controllerName() === 'auth' ? ' active' : ''; ?>"> + <a href="<?php echo _url('auth', 'index'); ?>"><?php echo _t('gen.menu.authentication'); ?></a> + </li> + <li class="item<?php echo Minz_Request::controllerName() === 'update' && + Minz_Request::actionName() === 'checkInstall' ? ' active' : ''; ?>"> + <a href="<?php echo _url('update', 'checkInstall'); ?>"><?php echo _t('gen.menu.check_install'); ?></a> + </li> + <li class="item<?php echo Minz_Request::controllerName() === 'update' && + Minz_Request::actionName() === 'index' ? ' active' : ''; ?>"> <a href="<?php echo _url('update', 'index'); ?>"><?php echo _t('update'); ?></a> </li> <?php } ?> diff --git a/app/layout/aside_feed.phtml b/app/layout/aside_feed.phtml index 5ffb1c791..3c23e0178 100644 --- a/app/layout/aside_feed.phtml +++ b/app/layout/aside_feed.phtml @@ -1,75 +1,95 @@ -<ul class="nav nav-list aside aside_feed"> - <li class="nav-header"><?php echo Minz_Translate::t ('your_rss_feeds'); ?></li> +<?php + $class = ''; + if (FreshRSS_Context::$conf->hide_read_feeds && + FreshRSS_Context::isStateEnabled(FreshRSS_Entry::STATE_NOT_READ) && + !FreshRSS_Context::isStateEnabled(FreshRSS_Entry::STATE_READ)) { + $class = ' state_unread'; + } +?> - <li class="nav-form"><form id="add_rss" method="post" action="<?php echo Minz_Url::display (array ('c' => 'feed', 'a' => 'add')); ?>" autocomplete="off"> - <div class="stick"> - <input type="url" name="url_rss" placeholder="<?php echo Minz_Translate::t ('add_rss_feed'); ?>" /> - <div class="dropdown"> - <div id="dropdown-cat" class="dropdown-target"></div> +<div class="aside aside_feed<?php echo $class; ?>" id="aside_feed"> + <a class="toggle_aside" href="#close"><?php echo _i('close'); ?></a> - <a class="dropdown-toggle btn" href="#dropdown-cat"><?php echo FreshRSS_Themes::icon('down'); ?></a> - <ul class="dropdown-menu"> - <li class="dropdown-close"><a href="#close">❌</a></li> - - <li class="dropdown-header"><?php echo Minz_Translate::t ('category'); ?></li> - - <li class="input"> - <select name="category" id="category"> - <?php foreach ($this->categories as $cat) { ?> - <option value="<?php echo $cat->id (); ?>"<?php echo $cat->id () == 1 ? ' selected="selected"' : ''; ?>> - <?php echo $cat->name (); ?> - </option> - <?php } ?> - <option value="nc"><?php echo Minz_Translate::t ('new_category'); ?></option> - </select> - </li> - - <li class="input" style="display:none"> - <input type="text" name="new_category[name]" id="new_category_name" autocomplete="off" placeholder="<?php echo Minz_Translate::t ('new_category'); ?>" /> - </li> + <?php if (FreshRSS_Auth::hasAccess()) { ?> + <div class="stick configure-feeds no-mobile"> + <a class="btn btn-important" href="<?php echo _url('subscription', 'index'); ?>"><?php echo _t('subscription_management'); ?></a> + <a class="btn btn-important" href="<?php echo _url('importExport', 'index'); ?>"><?php echo _i('import'); ?></a> + </div> + <?php } elseif (Minz_Configuration::needsLogin()) { ?> + <a href="<?php echo _url('index', 'about'); ?>"><?php echo _t('about_freshrss'); ?></a> + <?php } ?> - <li class="separator"></li> + <form id="mark-read-aside" method="post" style="display: none"></form> - <li class="dropdown-header"><?php echo Minz_Translate::t ('http_authentication'); ?></li> - <li class="input"> - <input type="text" name="http_user" id="http_user_add" autocomplete="off" placeholder="<?php echo Minz_Translate::t ('username'); ?>" /> - </li> - <li class="input"> - <input type="password" name="http_pass" id="http_pass_add" autocomplete="off" placeholder="<?php echo Minz_Translate::t ('password'); ?>" /> - </li> - </ul> + <ul class="tree"> + <li class="tree-folder category all<?php echo FreshRSS_Context::isCurrentGet('a') ? ' active' : ''; ?>"> + <div class="tree-folder-title"> + <?php echo _i('all'); ?> <a class="title" data-unread="<?php echo format_number(FreshRSS_Context::$total_unread); ?>" href="<?php echo _url('index', 'index'); ?>"><?php echo _t('main_stream'); ?></a> </div> - <button class="btn" type="submit"><?php echo FreshRSS_Themes::icon('add'); ?></button> - </div> - </form></li> - - <li class="item"> - <a onclick="return false;" href="javascript:(function(){var%20url%20=%20location.href;window.open('<?php echo Minz_Url::display(array('c' => 'feed', 'a' => 'add'), 'html', true); ?>&url_rss='+encodeURIComponent(url), '_blank');})();"> - <?php echo Minz_Translate::t('bookmark'); ?> - </a> - </li> + </li> - <li class="item<?php echo Minz_Request::controllerName () == 'importExport' ? ' active' : ''; ?>"> - <a href="<?php echo _url ('importExport', 'index'); ?>"><?php echo Minz_Translate::t ('import_export'); ?></a> - </li> + <li class="tree-folder category favorites<?php echo FreshRSS_Context::isCurrentGet('s') ? ' active' : ''; ?>"> + <div class="tree-folder-title"> + <?php echo _i('bookmark'); ?> <a class="title" data-unread="<?php echo format_number(FreshRSS_Context::$total_starred['unread']); ?>" href="<?php echo _url('index', 'index', 'get', 's'); ?>"><?php echo _t('favorite_feeds', format_number(FreshRSS_Context::$total_starred['all'])); ?></a> + </div> + </li> - <li class="item<?php echo Minz_Request::actionName () == 'categorize' ? ' active' : ''; ?>"> - <a href="<?php echo _url ('configure', 'categorize'); ?>"><?php echo Minz_Translate::t ('categories_management'); ?></a> - </li> + <?php + foreach ($this->categories as $cat) { + $feeds = $cat->feeds(); + if (!empty($feeds)) { + $c_active = FreshRSS_Context::isCurrentGet('c_' . $cat->id()); + $c_show = $c_active && (!FreshRSS_Context::$conf->display_categories || + FreshRSS_Context::$current_get['feed']); + ?> + <li class="tree-folder category<?php echo $c_active ? ' active' : ''; ?>" data-unread="<?php echo $cat->nbNotRead(); ?>"> + <div class="tree-folder-title"> + <a class="dropdown-toggle" href="#"><?php echo _i($c_show ? 'up' : 'down'); ?></a> + <a class="title" data-unread="<?php echo format_number($cat->nbNotRead()); ?>" href="<?php echo _url('index', 'index', 'get', 'c_' . $cat->id()); ?>"><?php echo $cat->name(); ?></a> + </div> - <li class="separator"></li> + <ul class="tree-folder-items<?php echo $c_show ? ' active' : ''; ?>"> + <?php + foreach ($feeds as $feed) { + $f_active = FreshRSS_Context::isCurrentGet('f_' . $feed->id()); + ?> + <li id="f_<?php echo $feed->id(); ?>" class="item feed<?php echo $f_active ? ' active' : ''; ?><?php echo $feed->inError() ? ' error' : ''; ?><?php echo $feed->nbEntries() <= 0 ? ' empty' : ''; ?>" data-unread="<?php echo $feed->nbNotRead(); ?>" data-priority="<?php echo $feed->priority(); ?>"> + <div class="dropdown no-mobile"> + <div class="dropdown-target"></div> + <a class="dropdown-toggle" data-fweb="<?php echo $feed->website(); ?>"><?php echo _i('configure'); ?></a> + <?php /* feed_config_template */ ?> + </div> + <img class="favicon" src="<?php echo $feed->favicon(); ?>" alt="✇" /> <a class="item-title" data-unread="<?php echo format_number($feed->nbNotRead()); ?>" href="<?php echo _url('index', 'index', 'get', 'f_' . $feed->id()); ?>"><?php echo $feed->name(); ?></a> + </li> + <?php } ?> + </ul> + </li> + <?php + } + } + ?> + </ul> +</div> - <?php if (!empty ($this->feeds)) { ?> - <?php foreach ($this->feeds as $feed) { ?> - <?php $nbEntries = $feed->nbEntries (); ?> - <li class="item<?php echo (isset($this->flux) && $this->flux->id () == $feed->id ()) ? ' active' : ''; ?><?php echo $feed->inError () ? ' error' : ''; ?><?php echo $nbEntries == 0 ? ' empty' : ''; ?>"> - <a href="<?php echo _url ('configure', 'feed', 'id', $feed->id ()); ?>"> - <img class="favicon" src="<?php echo $feed->favicon (); ?>" alt="✇" /> - <?php echo $feed->name (); ?> - </a> - </li> - <?php } ?> - <?php } else { ?> - <li class="item disable"><?php echo Minz_Translate::t ('no_rss_feed'); ?></li> - <?php } ?> -</ul> +<script id="feed_config_template" type="text/html"> + <ul class="dropdown-menu"> + <li class="dropdown-close"><a href="#close">❌</a></li> + <li class="item"><a href="<?php echo _url('index', 'index', 'get', 'f_!!!!!!'); ?>"><?php echo _t('filter'); ?></a></li> + <?php if (FreshRSS_Auth::hasAccess()) { ?> + <li class="item"><a href="<?php echo _url('stats', 'repartition', 'id', '!!!!!!'); ?>"><?php echo _t('stats'); ?></a></li> + <?php } ?> + <li class="item"><a target="_blank" href="http://example.net/"><?php echo _t('see_website'); ?></a></li> + <?php if (FreshRSS_Auth::hasAccess()) { ?> + <li class="separator"></li> + <li class="item"><a href="<?php echo _url('subscription', 'index', 'id', '!!!!!!'); ?>"><?php echo _t('administration'); ?></a></li> + <li class="item"><a href="<?php echo _url('feed', 'actualize', 'id', '!!!!!!'); ?>"><?php echo _t('actualize'); ?></a></li> + <li class="item"> + <?php $confirm = FreshRSS_Context::$conf->reading_confirm ? 'confirm' : ''; ?> + <button class="read_all as-link <?php echo $confirm; ?>" + form="mark-read-aside" + formaction="<?php echo _url('entry', 'read', 'get', 'f_!!!!!!'); ?>" + type="submit"><?php echo _t('mark_read'); ?></button> + </li> + <?php } ?> + </ul> +</script> diff --git a/app/layout/aside_flux.phtml b/app/layout/aside_flux.phtml deleted file mode 100644 index aac3c0896..000000000 --- a/app/layout/aside_flux.phtml +++ /dev/null @@ -1,103 +0,0 @@ -<div class="aside aside_flux<?php if ($this->conf->hide_read_feeds && ($this->state & FreshRSS_Entry::STATE_NOT_READ) && !($this->state & FreshRSS_Entry::STATE_READ)) echo ' state_unread'; ?>" id="aside_flux"> - <a class="toggle_aside" href="#close"><?php echo _i('close'); ?></a> - - <ul class="categories"> - <?php if ($this->loginOk) { ?> - <form id="mark-read-aside" method="post" style="display: none"></form> - - <li> - <div class="stick configure-feeds"> - <a class="btn btn-important" href="<?php echo _url('configure', 'feed'); ?>"><?php echo _t('subscription_management'); ?></a> - <a class="btn btn-important" href="<?php echo _url('configure', 'categorize'); ?>" title="<?php echo _t('categories_management'); ?>"><?php echo _i('category-white'); ?></a> - </div> - </li> - <?php } elseif (Minz_Configuration::needsLogin()) { ?> - <li><a href="<?php echo _url('index', 'about'); ?>"><?php echo _t('about_freshrss'); ?></a></li> - <?php } ?> - - <?php - $arUrl = array('c' => 'index', 'a' => 'index', 'params' => array()); - if ($this->conf->view_mode !== Minz_Request::param('output', 'normal')) { - $arUrl['params']['output'] = 'normal'; - } - ?> - <li> - <div class="category all<?php echo $this->get_c == 'a' ? ' active' : ''; ?>"> - <a data-unread="<?php echo formatNumber($this->nb_not_read); ?>" class="btn<?php echo $this->get_c == 'a' ? ' active' : ''; ?>" href="<?php echo Minz_Url::display($arUrl); ?>"> - <?php echo _i('all'); ?> - <?php echo _t('main_stream'); ?> - </a> - </div> - </li> - - <li> - <div class="category favorites<?php echo $this->get_c == 's' ? ' active' : ''; ?>"> - <a data-unread="<?php echo formatNumber($this->nb_favorites['unread']); ?>" class="btn<?php echo $this->get_c == 's' ? ' active' : ''; ?>" href="<?php $arUrl['params']['get'] = 's'; echo Minz_Url::display($arUrl); ?>"> - <?php echo _i('bookmark'); ?> - <?php echo _t('favorite_feeds', formatNumber($this->nb_favorites['all'])); ?> - </a> - </div> - </li> - - <?php - foreach ($this->cat_aside as $cat) { - $feeds = $cat->feeds(); - if (!empty($feeds)) { - $c_active = false; - $c_show = false; - if ($this->get_c == $cat->id()) { - $c_active = true; - if (!$this->conf->display_categories || $this->get_f) { - $c_show = true; - } - } - ?><li data-unread="<?php echo $cat->nbNotRead(); ?>"<?php if ($c_active) echo ' class="active"'; ?>><?php - ?><div class="category stick<?php echo $c_active ? ' active' : ''; ?>"><?php - ?><a data-unread="<?php echo formatNumber($cat->nbNotRead()); ?>" class="btn<?php echo $c_active ? ' active' : ''; ?>" href="<?php $arUrl['params']['get'] = 'c_' . $cat->id(); echo Minz_Url::display($arUrl); ?>"><?php echo $cat->name(); ?></a><?php - ?><a class="btn dropdown-toggle" href="#"><?php echo _i($c_show ? 'up' : 'down'); ?></a><?php - ?></div><?php - ?><ul class="feeds<?php echo $c_show ? ' active' : ''; ?>"><?php - foreach ($feeds as $feed) { - $feed_id = $feed->id(); - $nbEntries = $feed->nbEntries(); - $f_active = ($this->get_f == $feed_id); - ?><li id="f_<?php echo $feed_id; ?>" class="item<?php echo $f_active ? ' active' : ''; ?><?php echo $feed->inError() ? ' error' : ''; ?><?php echo $nbEntries == 0 ? ' empty' : ''; ?>" data-unread="<?php echo $feed->nbNotRead(); ?>"><?php - ?><div class="dropdown"><?php - ?><div class="dropdown-target"></div><?php - ?><a class="dropdown-toggle" data-fweb="<?php echo $feed->website(); ?>"><?php echo _i('configure'); ?></a><?php - /* feed_config_template */ - ?></div><?php - ?> <img class="favicon" src="<?php echo $feed->favicon(); ?>" alt="✇" /> <?php - ?><a class="feed" data-unread="<?php echo formatNumber($feed->nbNotRead()); ?>" data-priority="<?php echo $feed->priority(); ?>" href="<?php $arUrl['params']['get'] = 'f_' . $feed_id; echo Minz_Url::display($arUrl); ?>"><?php echo $feed->name(); ?></a><?php - ?></li><?php - } - ?></ul><?php - ?></li><?php - } - } ?> - </ul> - <span class="aside_flux_ender"><!-- For fixed menu --></span> -</div> - -<script id="feed_config_template" type="text/html"> - <ul class="dropdown-menu"> - <li class="dropdown-close"><a href="#close">❌</a></li> - <li class="item"><a href="<?php echo _url('index', 'index', 'get', 'f_!!!!!!'); ?>"><?php echo _t('filter'); ?></a></li> - <?php if ($this->loginOk) { ?> - <li class="item"><a href="<?php echo _url('stats', 'repartition', 'id', '!!!!!!'); ?>"><?php echo _t('stats'); ?></a></li> - <?php } ?> - <li class="item"><a target="_blank" href="http://example.net/"><?php echo _t('see_website'); ?></a></li> - <?php if ($this->loginOk) { ?> - <li class="separator"></li> - <li class="item"><a href="<?php echo _url('configure', 'feed', 'id', '!!!!!!'); ?>"><?php echo _t('administration'); ?></a></li> - <li class="item"><a href="<?php echo _url('feed', 'actualize', 'id', '!!!!!!'); ?>"><?php echo _t('actualize'); ?></a></li> - <li class="item"> - <?php $confirm = $this->conf->reading_confirm ? 'confirm' : ''; ?> - <button class="read_all as-link <?php echo $confirm; ?>" - form="mark-read-aside" - formaction="<?php echo _url('entry', 'read', 'get', 'f_!!!!!!'); ?>" - type="submit"><?php echo _t('mark_read'); ?></button> - </li> - <?php } ?> - </ul> -</script> diff --git a/app/layout/aside_stats.phtml b/app/layout/aside_stats.phtml index fbfb9d84d..1cd31a99c 100644 --- a/app/layout/aside_stats.phtml +++ b/app/layout/aside_stats.phtml @@ -1,12 +1,12 @@ <ul class="nav nav-list aside"> - <li class="nav-header"><?php echo Minz_Translate::t ('stats'); ?></li> - <li class="item<?php echo Minz_Request::actionName () == 'index' ? ' active' : ''; ?>"> - <a href="<?php echo _url ('stats', 'index'); ?>"><?php echo Minz_Translate::t ('stats_main'); ?></a> + <li class="nav-header"><?php echo _t('stats'); ?></li> + <li class="item<?php echo Minz_Request::actionName() == 'index' ? ' active' : ''; ?>"> + <a href="<?php echo _url('stats', 'index'); ?>"><?php echo _t('stats_main'); ?></a> </li> - <li class="item<?php echo Minz_Request::actionName () == 'idle' ? ' active' : ''; ?>"> - <a href="<?php echo _url ('stats', 'idle'); ?>"><?php echo Minz_Translate::t ('stats_idle'); ?></a> + <li class="item<?php echo Minz_Request::actionName() == 'idle' ? ' active' : ''; ?>"> + <a href="<?php echo _url('stats', 'idle'); ?>"><?php echo _t('stats_idle'); ?></a> </li> - <li class="item<?php echo Minz_Request::actionName () == 'repartition' ? ' active' : ''; ?>"> - <a href="<?php echo _url ('stats', 'repartition'); ?>"><?php echo Minz_Translate::t ('stats_repartition'); ?></a> + <li class="item<?php echo Minz_Request::actionName() == 'repartition' ? ' active' : ''; ?>"> + <a href="<?php echo _url('stats', 'repartition'); ?>"><?php echo _t('stats_repartition'); ?></a> </li> </ul> diff --git a/app/layout/aside_subscription.phtml b/app/layout/aside_subscription.phtml new file mode 100644 index 000000000..cbf5df67d --- /dev/null +++ b/app/layout/aside_subscription.phtml @@ -0,0 +1,17 @@ +<ul class="nav nav-list aside"> + <li class="nav-header"><?php echo _t('subscription_management'); ?></li> + + <li class="item<?php echo Minz_Request::controllerName() == 'subscription' ? ' active' : ''; ?>"> + <a href="<?php echo _url('subscription', 'index'); ?>"><?php echo _t('subscription_management'); ?></a> + </li> + + <li class="item<?php echo Minz_Request::controllerName() == 'importExport' ? ' active' : ''; ?>"> + <a href="<?php echo _url('importExport', 'index'); ?>"><?php echo _t('import_export'); ?></a> + </li> + + <li class="item"> + <a onclick="return false;" href="javascript:(function(){var%20url%20=%20location.href;window.open('<?php echo Minz_Url::display(array('c' => 'feed', 'a' => 'add'), 'html', true); ?>&url_rss='+encodeURIComponent(url), '_blank');})();"> + <?php echo _t('bookmark'); ?> + </a> + </li> +</ul> diff --git a/app/layout/header.phtml b/app/layout/header.phtml index 028e63d8a..c73d9cdbb 100644 --- a/app/layout/header.phtml +++ b/app/layout/header.phtml @@ -1,22 +1,11 @@ <?php if (Minz_Configuration::canLogIn()) { ?><ul class="nav nav-head nav-login"><?php - switch (Minz_Configuration::authType()) { - case 'form': - if ($this->loginOk) { - ?><li class="item"><?php echo _i('logout'); ?> <a class="signout" href="<?php echo _url('index', 'formLogout'); ?>"><?php echo _t('logout'); ?></a></li><?php + if (FreshRSS_Auth::hasAccess()) { + ?><li class="item"><?php echo _i('logout'); ?> <a class="signout" href="<?php echo _url('auth', 'logout'); ?>"><?php echo _t('logout'); ?></a></li><?php } else { - ?><li class="item"><?php echo _i('login'); ?> <a class="signin" href="<?php echo _url('index', 'formLogin'); ?>"><?php echo _t('login'); ?></a></li><?php + ?><li class="item"><?php echo _i('login'); ?> <a class="signin" href="<?php echo _url('auth', 'login'); ?>"><?php echo _t('login'); ?></a></li><?php } - break; - case 'persona': - if ($this->loginOk) { - ?><li class="item"><?php echo _i('logout'); ?> <a class="signout" href="#"><?php echo _t('logout'); ?></a></li><?php - } else { - ?><li class="item"><?php echo _i('login'); ?> <a class="signin" href="#"><?php echo _t('login'); ?></a></li><?php - } - break; - } ?></ul><?php } ?> @@ -32,7 +21,7 @@ if (Minz_Configuration::canLogIn()) { </div> <div class="item search"> - <?php if ($this->loginOk || Minz_Configuration::allowAnonymous()) { ?> + <?php if (FreshRSS_Auth::hasAccess() || Minz_Configuration::allowAnonymous()) { ?> <form action="<?php echo _url('index', 'index'); ?>" method="get"> <div class="stick"> <?php $search = Minz_Request::param('search', ''); ?> @@ -59,7 +48,7 @@ if (Minz_Configuration::canLogIn()) { <?php } ?> </div> - <?php if ($this->loginOk) { ?> + <?php if (FreshRSS_Auth::hasAccess()) { ?> <div class="item configure"> <div class="dropdown"> <div id="dropdown-configure" class="dropdown-target"></div> @@ -73,12 +62,13 @@ if (Minz_Configuration::canLogIn()) { <li class="item"><a href="<?php echo _url('configure', 'sharing'); ?>"><?php echo _t('sharing'); ?></a></li> <li class="item"><a href="<?php echo _url('configure', 'shortcut'); ?>"><?php echo _t('shortcuts'); ?></a></li> <li class="item"><a href="<?php echo _url('configure', 'queries'); ?>"><?php echo _t('queries'); ?></a></li> + <li class="item"><a href="<?php echo _url('user', 'profile'); ?>"><?php echo _t('gen.menu.user_profile'); ?></a></li> + <?php if (FreshRSS_Auth::hasAccess('admin')) { ?> <li class="separator"></li> - <li class="item"><a href="<?php echo _url('configure', 'users'); ?>"><?php echo _t('users'); ?></a></li> - <?php - $current_user = Minz_Session::param('currentUser', ''); - if (Minz_Configuration::isAdmin($current_user)) { - ?> + <li class="dropdown-header"><?php echo _t('gen.menu.admin'); ?></li> + <li class="item"><a href="<?php echo _url('user', 'manage'); ?>"><?php echo _t('gen.menu.user_management'); ?></a></li> + <li class="item"><a href="<?php echo _url('auth', 'index'); ?>"><?php echo _t('gen.menu.authentication'); ?></a></li> + <li class="item"><a href="<?php echo _url('update', 'checkInstall'); ?>"><?php echo _t('gen.menu.check_install'); ?></a></li> <li class="item"><a href="<?php echo _url('update', 'index'); ?>"><?php echo _t('update'); ?></a></li> <?php } ?> <li class="separator"></li> @@ -87,29 +77,15 @@ if (Minz_Configuration::canLogIn()) { <li class="item"><a href="<?php echo _url('index', 'about'); ?>"><?php echo _t('about'); ?></a></li> <?php if (Minz_Configuration::canLogIn()) { - ?><li class="separator"></li><?php - switch (Minz_Configuration::authType()) { - case 'form': - ?><li class="item"><a class="signout" href="<?php echo _url('index', 'formLogout'); ?>"><?php echo _i('logout'), ' ', _t('logout'); ?></a></li><?php - break; - case 'persona': - ?><li class="item"><a class="signout" href="#"><?php echo _i('logout'), ' ', _t('logout'); ?></a></li><?php - break; - } + ?><li class="separator"></li> + <li class="item"><a class="signout" href="<?php echo _url('auth', 'logout'); ?>"><?php echo _i('logout'), ' ', _t('logout'); ?></a></li><?php } ?> </ul> </div> </div> - <?php } elseif (Minz_Configuration::canLogIn()) { - ?><div class="item configure"><?php - switch (Minz_Configuration::authType()) { - case 'form': - echo _i('login'); ?><a class="signin" href="<?php echo _url('index', 'formLogin'); ?>"><?php echo _t('login'); ?></a></li><?php - break; - case 'persona': - echo _i('login'); ?><a class="signin" href="#"><?php echo _t('login'); ?></a></li><?php - break; - } - ?></div><?php - } ?> + <?php } elseif (Minz_Configuration::canLogIn()) { ?> + <div class="item configure"> + <?php echo _i('login'); ?><a class="signin" href="<?php echo _url('auth', 'login'); ?>"><?php echo _t('login'); ?></a> + </div> + <?php } ?> </div> diff --git a/app/layout/layout.phtml b/app/layout/layout.phtml index f95f45b5e..1827d6c26 100644 --- a/app/layout/layout.phtml +++ b/app/layout/layout.phtml @@ -1,5 +1,5 @@ <!DOCTYPE html> -<html lang="<?php echo $this->conf->language; ?>" xml:lang="<?php echo $this->conf->language; ?>"> +<html lang="<?php echo FreshRSS_Context::$conf->language; ?>" xml:lang="<?php echo FreshRSS_Context::$conf->language; ?>"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="initial-scale=1.0" /> @@ -10,21 +10,22 @@ <?php $this->renderHelper('javascript_vars'); ?> //]]></script> <?php - if (!empty($this->nextId)) { - $params = Minz_Request::params(); - $params['next'] = $this->nextId; - $params['ajax'] = 1; + $url_base = Minz_Request::currentRequest(); + if (FreshRSS_Context::$next_id !== '') { + $url_next = $url_base; + $url_next['params']['next'] = FreshRSS_Context::$next_id; + $url_next['params']['ajax'] = 1; ?> - <link id="prefetch" rel="next prefetch" href="<?php echo Minz_Url::display(array('c' => Minz_Request::controllerName(), 'a' => Minz_Request::actionName(), 'params' => $params)); ?>" /> + <link id="prefetch" rel="next prefetch" href="<?php echo Minz_Url::display($url_next); ?>" /> <?php } ?> <link rel="shortcut icon" id="favicon" type="image/x-icon" sizes="16x16 64x64" href="<?php echo Minz_Url::display('/favicon.ico'); ?>" /> <link rel="icon msapplication-TileImage apple-touch-icon" type="image/png" sizes="256x256" href="<?php echo Minz_Url::display('/themes/icons/favicon-256.png'); ?>" /> <?php - if (isset($this->url)) { - $rss_url = $this->url; - $rss_url['params']['output'] = 'rss'; + if (isset($this->rss_title)) { + $url_rss = $url_base; + $url_rss['a'] = 'rss'; ?> - <link rel="alternate" type="application/rss+xml" title="<?php echo $this->rss_title; ?>" href="<?php echo Minz_Url::display($rss_url); ?>" /> + <link rel="alternate" type="application/rss+xml" title="<?php echo $this->rss_title; ?>" href="<?php echo Minz_Url::display($url_rss); ?>" /> <?php } ?> <link rel="prefetch" href="<?php echo FreshRSS_Themes::icon('starred', true); ?>"> <link rel="prefetch" href="<?php echo FreshRSS_Themes::icon('non-starred', true); ?>"> @@ -56,7 +57,7 @@ ?> <div id="notification" class="notification <?php echo $status; ?>"> <span class="msg"><?php echo $msg; ?></span> - <a class="close" href=""><?php echo FreshRSS_Themes::icon('close'); ?></a> + <a class="close" href=""><?php echo _i('close'); ?></a> </div> </body> </html> diff --git a/app/layout/nav_entries.phtml b/app/layout/nav_entries.phtml index 3141e92a0..ca6849193 100644 --- a/app/layout/nav_entries.phtml +++ b/app/layout/nav_entries.phtml @@ -1,5 +1,5 @@ <ul id="nav_entries"> - <li class="item"><a class="previous_entry" href="#"><?php echo FreshRSS_Themes::icon('prev'); ?></a></li> - <li class="item"><a class="up" href="#"><?php echo FreshRSS_Themes::icon('up'); ?></a></li> - <li class="item"><a class="next_entry" href="#"><?php echo FreshRSS_Themes::icon('next'); ?></a></li> + <li class="item"><a class="previous_entry" href="#"><?php echo _i('prev'); ?></a></li> + <li class="item"><a class="up" href="#"><?php echo _i('up'); ?></a></li> + <li class="item"><a class="next_entry" href="#"><?php echo _i('next'); ?></a></li> </ul>
\ No newline at end of file diff --git a/app/layout/nav_menu.phtml b/app/layout/nav_menu.phtml index a9e6614e7..775daf088 100644 --- a/app/layout/nav_menu.phtml +++ b/app/layout/nav_menu.phtml @@ -1,90 +1,31 @@ -<?php - $actual_view = Minz_Request::param('output', 'normal'); -?> +<?php $actual_view = Minz_Request::actionName(); ?> + <div class="nav_menu"> <?php if ($actual_view === 'normal') { ?> - <a class="btn toggle_aside" href="#aside_flux"><?php echo _i('category'); ?></a> + <a class="btn toggle_aside" href="#aside_feed"><?php echo _i('category'); ?></a> <?php } ?> - <?php if ($this->loginOk) { ?> + <?php if (FreshRSS_Auth::hasAccess()) { ?> <div id="nav_menu_actions" class="stick"> <?php - $url_state = $this->url; - - if ($this->state & FreshRSS_Entry::STATE_READ) { - $url_state['params']['state'] = $this->state & ~FreshRSS_Entry::STATE_READ; - $checked = 'true'; - $class = 'active'; - } else { - $url_state['params']['state'] = $this->state | FreshRSS_Entry::STATE_READ; - $checked = 'false'; - $class = ''; - } - ?> - <a id="toggle-read" - class="btn <?php echo $class; ?>" - aria-checked="<?php echo $checked; ?>" - href="<?php echo Minz_Url::display($url_state); ?>" - title="<?php echo _t('show_read'); ?>"> - <?php echo _i('read'); ?> - </a> - - <?php - if ($this->state & FreshRSS_Entry::STATE_NOT_READ) { - $url_state['params']['state'] = $this->state & ~FreshRSS_Entry::STATE_NOT_READ; - $checked = 'true'; - $class = 'active'; - } else { - $url_state['params']['state'] = $this->state | FreshRSS_Entry::STATE_NOT_READ; - $checked = 'false'; - $class = ''; - } - ?> - <a id="toggle-unread" - class="btn <?php echo $class; ?>" - aria-checked="<?php echo $checked; ?>" - href="<?php echo Minz_Url::display($url_state); ?>" - title="<?php echo _t('show_not_reads'); ?>"> - <?php echo _i('unread'); ?> - </a> - - <?php - if ($this->state & FreshRSS_Entry::STATE_FAVORITE || $this->get_c == 's') { - $url_state['params']['state'] = $this->state & ~FreshRSS_Entry::STATE_FAVORITE; - $checked = 'true'; - $class = 'active'; - } else { - $url_state['params']['state'] = $this->state | FreshRSS_Entry::STATE_FAVORITE; - $checked = 'false'; - $class = ''; - } - ?> - <a id="toggle-favorite" - class="btn <?php echo $class; ?>" - aria-checked="<?php echo $checked; ?>" - href="<?php echo Minz_Url::display($url_state); ?>" - title="<?php echo _t('show_favorite'); ?>"> - <?php echo _i('starred'); ?> - </a> - - <?php - if ($this->state & FreshRSS_Entry::STATE_NOT_FAVORITE) { - $url_state['params']['state'] = $this->state & ~FreshRSS_Entry::STATE_NOT_FAVORITE; - $checked = 'true'; - $class = 'active'; - } else { - $url_state['params']['state'] = $this->state | FreshRSS_Entry::STATE_NOT_FAVORITE; - $checked = 'false'; - $class = ''; - } + $states = array( + 'read' => FreshRSS_Entry::STATE_READ, + 'unread' => FreshRSS_Entry::STATE_NOT_READ, + 'starred' => FreshRSS_Entry::STATE_FAVORITE, + 'non-starred' => FreshRSS_Entry::STATE_NOT_FAVORITE, + ); + + foreach ($states as $state_str => $state) { + $state_enabled = FreshRSS_Context::isStateEnabled($state); + $url_state = Minz_Request::currentRequest(); + $url_state['params']['state'] = FreshRSS_Context::getRevertState($state); ?> - <a id="toggle-not-favorite" - class="btn <?php echo $class; ?>" - aria-checked="<?php echo $checked; ?>" - href="<?php echo Minz_Url::display($url_state); ?>" - title="<?php echo _t('show_not_favorite'); ?>"> - <?php echo _i('non-starred'); ?> - </a> + <a id="toggle-<?php echo $state_str; ?>" + class="btn <?php echo $state_enabled ? 'active' : ''; ?>" + aria-checked="<?php echo $state_enabled ? 'true' : 'false'; ?>" + title="<?php echo _t($state_str); ?>" + href="<?php echo Minz_Url::display($url_state); ?>"><?php echo _i($state_str); ?></a> + <?php } ?> <div class="dropdown"> <div id="dropdown-query" class="dropdown-target"></div> @@ -98,18 +39,18 @@ <a class="no-mobile" href="<?php echo _url('configure', 'queries'); ?>"><?php echo _i('configure'); ?></a> </li> - <?php foreach ($this->conf->queries as $query) { ?> + <?php foreach (FreshRSS_Context::$conf->queries as $query) { ?> <li class="item query"> <a href="<?php echo $query['url']; ?>"><?php echo $query['name']; ?></a> </li> <?php } ?> - <?php if (count($this->conf->queries) > 0) { ?> + <?php if (count(FreshRSS_Context::$conf->queries) > 0) { ?> <li class="separator no-mobile"></li> <?php } ?> <?php - $url_query = $this->url; + $url_query = Minz_Request::currentRequest();; $url_query['c'] = 'configure'; $url_query['a'] = 'addQuery'; ?> @@ -117,83 +58,34 @@ </ul> </div> </div> + <?php - $get = false; + $get = FreshRSS_Context::currentGet(); $string_mark = _t('mark_all_read'); - if ($this->get_f) { - $get = 'f_' . $this->get_f; + if ($get[0] == 'f') { $string_mark = _t('mark_feed_read'); - } elseif ($this->get_c && $this->get_c != 'a') { - if ($this->get_c === 's') { - $get = 's'; - } else { - $get = 'c_' . $this->get_c; - } + } elseif ($get[0] == 'c') { $string_mark = _t('mark_cat_read'); } - $nextGet = $get; - if ($this->conf->onread_jump_next && strlen($get) > 2) { - $anotherUnreadId = ''; - $foundCurrent = false; - switch ($get[0]) { - case 'c': - foreach ($this->cat_aside as $cat) { - if ($cat->id() == $this->get_c) { - $foundCurrent = true; - continue; - } - if ($cat->nbNotRead() <= 0) continue; - $anotherUnreadId = $cat->id(); - if ($foundCurrent) break; - } - $nextGet = empty($anotherUnreadId) ? 'a' : 'c_' . $anotherUnreadId; - break; - case 'f': - foreach ($this->cat_aside as $cat) { - if ($cat->id() == $this->get_c) { - foreach ($cat->feeds() as $feed) { - if ($feed->id() == $this->get_f) { - $foundCurrent = true; - continue; - } - if ($feed->nbNotRead() <= 0) continue; - $anotherUnreadId = $feed->id(); - if ($foundCurrent) break; - } - break; - } - } - $nextGet = empty($anotherUnreadId) ? 'c_' . $this->get_c : 'f_' . $anotherUnreadId; - break; - } - } - $p = isset($this->entries[0]) ? $this->entries[0] : null; - $idMax = $p === null ? (time() - 1) . '000000' : $p->id(); - - if ($this->order === 'ASC') { //In this case we do not know but we guess idMax - $idMax2 = (time() - 1) . '000000'; - if (strcmp($idMax2, $idMax) > 0) { - $idMax = $idMax2; - } - } - - $arUrl = array('c' => 'entry', 'a' => 'read', 'params' => array('get' => $get, 'nextGet' => $nextGet, 'idMax' => $idMax)); - $output = Minz_Request::param('output', ''); - if ($output != '' && $this->conf->view_mode !== $output) { - $arUrl['params']['output'] = $output; - } - $markReadUrl = Minz_Url::display($arUrl); - Minz_Session::_param('markReadUrl', $markReadUrl); + $mark_read_url = array( + 'c' => 'entry', + 'a' => 'read', + 'params' => array( + 'get' => $get, + 'nextGet' => FreshRSS_Context::$next_get, + 'idMax' => FreshRSS_Context::$id_max, + ) + ); ?> <form id="mark-read-menu" method="post" style="display: none"></form> <div class="stick" id="nav_menu_read_all"> - <?php $confirm = $this->conf->reading_confirm ? 'confirm' : ''; ?> + <?php $confirm = FreshRSS_Context::$conf->reading_confirm ? 'confirm' : ''; ?> <button class="read_all btn <?php echo $confirm; ?>" form="mark-read-menu" - formaction="<?php echo $markReadUrl; ?>" + formaction="<?php echo Minz_Url::display($mark_read_url); ?>" type="submit"><?php echo _t('mark_read'); ?></button> <div class="dropdown"> @@ -206,15 +98,16 @@ <li class="item"> <button class="as-link <?php echo $confirm; ?>" form="mark-read-menu" - formaction="<?php echo $markReadUrl; ?>" + formaction="<?php echo Minz_Url::display($mark_read_url); ?>" type="submit"><?php echo $string_mark; ?></button> </li> <li class="separator"></li> <?php - $mark_before_today = $arUrl; - $mark_before_today['params']['idMax'] = $this->today . '000000'; - $mark_before_one_week = $arUrl; - $mark_before_one_week['params']['idMax'] = ($this->today - 604800) . '000000'; + $today = @strtotime('today'); + $mark_before_today = $mark_read_url; + $mark_before_today['params']['idMax'] = $today . '000000'; + $mark_before_one_week = $mark_read_url; + $mark_before_one_week['params']['idMax'] = ($today - 604800) . '000000'; ?> <li class="item"> <button class="as-link <?php echo $confirm; ?>" @@ -233,27 +126,27 @@ </div> <?php } ?> - <?php $url_output = $this->url; ?> + <?php $url_output = Minz_Request::currentRequest(); ?> <div class="stick" id="nav_menu_views"> - <?php $url_output['params']['output'] = 'normal'; ?> + <?php $url_output['a'] = 'normal'; ?> <a class="view_normal btn <?php echo $actual_view == 'normal'? 'active' : ''; ?>" title="<?php echo _t('normal_view'); ?>" href="<?php echo Minz_Url::display($url_output); ?>"> <?php echo _i("view-normal"); ?> </a> - <?php $url_output['params']['output'] = 'global'; ?> + <?php $url_output['a'] = 'global'; ?> <a class="view_global btn <?php echo $actual_view == 'global'? 'active' : ''; ?>" title="<?php echo _t('global_view'); ?>" href="<?php echo Minz_Url::display($url_output); ?>"> <?php echo _i("view-global"); ?> </a> - <?php $url_output['params']['output'] = 'reader'; ?> - <a class="view_reader btn <?php echo $actual_view == 'reader'? 'active' : ''; ?>" title="<?php echo _t('reader_view'); ?>" href="<?php echo Minz_Url::display($url_output); ?>"> + <?php $url_output['a'] = 'reader'; ?> + <a class="view_reader btn <?php echo $actual_view == 'reader'? 'active' : ''; ?>" title="<?php echo _t('global_view'); ?>" href="<?php echo Minz_Url::display($url_output); ?>"> <?php echo _i("view-reader"); ?> </a> <?php - $url_output['params']['output'] = 'rss'; - if ($this->conf->token) { - $url_output['params']['token'] = $this->conf->token; + $url_output['a'] = 'rss'; + if (FreshRSS_Context::$conf->token) { + $url_output['params']['token'] = FreshRSS_Context::$conf->token; } ?> <a class="view_rss btn" target="_blank" title="<?php echo _t('rss_view'); ?>" href="<?php echo Minz_Url::display($url_output); ?>"> @@ -284,7 +177,7 @@ </div> <?php - if ($this->order === 'DESC') { + if (FreshRSS_Context::$order === 'DESC') { $order = 'ASC'; $icon = 'up'; $title = 'older_first'; @@ -293,14 +186,14 @@ $icon = 'down'; $title = 'newer_first'; } - $url_order = $this->url; + $url_order = Minz_Request::currentRequest(); $url_order['params']['order'] = $order; ?> <a id="toggle-order" class="btn" href="<?php echo Minz_Url::display($url_order); ?>" title="<?php echo _t($title); ?>"> <?php echo _i($icon); ?> </a> - <?php if ($this->loginOk || Minz_Configuration::allowAnonymousRefresh()) { ?> + <?php if (FreshRSS_Auth::hasAccess() || Minz_Configuration::allowAnonymousRefresh()) { ?> <a id="actualize" class="btn" href="<?php echo _url('feed', 'actualize'); ?>"><?php echo _i('refresh'); ?></a> <?php } ?> </div> diff --git a/app/views/index/formLogin.phtml b/app/views/auth/formLogin.phtml index b05cdced4..0194a11a5 100644 --- a/app/views/index/formLogin.phtml +++ b/app/views/auth/formLogin.phtml @@ -1,9 +1,7 @@ <div class="prompt"> - <h1><?php echo _t('login'); ?></h1><?php + <h1><?php echo _t('login'); ?></h1> - switch (Minz_Configuration::authType()) { - case 'form': - ?><form id="crypto-form" method="post" action="<?php echo _url('index', 'formLogin'); ?>"> + <form id="crypto-form" method="post" action="<?php echo _url('auth', 'login'); ?>"> <div> <label for="username"><?php echo _t('username'); ?></label> <input type="text" id="username" name="username" size="16" required="required" maxlength="16" pattern="[0-9a-zA-Z]{1,16}" autofocus="autofocus" /> @@ -24,23 +22,7 @@ <div> <button id="loginButton" type="submit" class="btn btn-important"><?php echo _t('login'); ?></button> </div> - </form><?php - break; - - case 'persona': - ?><p> - <a class="signin btn btn-important" href="#"> - <?php echo _i('login'); ?> - <?php echo _t('login_with_persona'); ?> - </a><br /><br /> - - <?php echo _i('help'); ?> - <small> - <a href="<?php echo _url('index', 'resetAuth'); ?>"><?php echo _t('login_persona_problem'); ?></a> - </small> - </p><?php - break; - } ?> + </form> <p><a href="<?php echo _url('index', 'about'); ?>"><?php echo _t('about_freshrss'); ?></a></p> </div> diff --git a/app/views/auth/index.phtml b/app/views/auth/index.phtml new file mode 100644 index 000000000..17a81f08b --- /dev/null +++ b/app/views/auth/index.phtml @@ -0,0 +1,84 @@ +<?php $this->partial('aside_configure'); ?> + +<div class="post"> + <a href="<?php echo _url('index', 'index'); ?>"><?php echo _t('back_to_rss_feeds'); ?></a> + + <form method="post" action="<?php echo _url('auth', 'index'); ?>"> + <legend><?php echo _t('auth_type'); ?></legend> + + <div class="form-group"> + <label class="group-name" for="auth_type"><?php echo _t('auth_type'); ?></label> + <div class="group-controls"> + <select id="auth_type" name="auth_type" required="required"> + <?php if (!in_array(Minz_Configuration::authType(), array('form', 'persona', 'http_auth', 'none'))) { ?> + <option selected="selected"></option> + <?php } ?> + <option value="form"<?php echo Minz_Configuration::authType() === 'form' ? ' selected="selected"' : '', cryptAvailable() ? '' : ' disabled="disabled"'; ?>><?php echo _t('auth_form'); ?></option> + <option value="persona"<?php echo Minz_Configuration::authType() === 'persona' ? ' selected="selected"' : '', FreshRSS_Context::$conf->mail_login == '' ? ' disabled="disabled"' : ''; ?>><?php echo _t('auth_persona'); ?></option> + <option value="http_auth"<?php echo Minz_Configuration::authType() === 'http_auth' ? ' selected="selected"' : '', httpAuthUser() == '' ? ' disabled="disabled"' : ''; ?>><?php echo _t('http_auth'); ?> (REMOTE_USER = '<?php echo httpAuthUser(); ?>')</option> + <option value="none"<?php echo Minz_Configuration::authType() === 'none' ? ' selected="selected"' : ''; ?>><?php echo _t('auth_none'); ?></option> + </select> + </div> + </div> + + <div class="form-group"> + <div class="group-controls"> + <label class="checkbox" for="anon_access"> + <input type="checkbox" name="anon_access" id="anon_access" value="1"<?php echo Minz_Configuration::allowAnonymous() ? ' checked="checked"' : '', + Minz_Configuration::canLogIn() ? '' : ' disabled="disabled"'; ?> /> + <?php echo _t('allow_anonymous', Minz_Configuration::defaultUser()); ?> + </label> + </div> + </div> + + <div class="form-group"> + <div class="group-controls"> + <label class="checkbox" for="anon_refresh"> + <input type="checkbox" name="anon_refresh" id="anon_refresh" value="1"<?php echo Minz_Configuration::allowAnonymousRefresh() ? ' checked="checked"' : '', + Minz_Configuration::canLogIn() ? '' : ' disabled="disabled"'; ?> /> + <?php echo _t('allow_anonymous_refresh'); ?> + </label> + </div> + </div> + + <div class="form-group"> + <div class="group-controls"> + <label class="checkbox" for="unsafe_autologin"> + <input type="checkbox" name="unsafe_autologin" id="unsafe_autologin" value="1"<?php echo Minz_Configuration::unsafeAutologinEnabled() ? ' checked="checked"' : '', + Minz_Configuration::canLogIn() ? '' : ' disabled="disabled"'; ?> /> + <?php echo _t('unsafe_autologin'); ?> + <kbd>p/i/?a=formLogin&u=Alice&p=1234</kbd> + </label> + </div> + </div> + + <?php if (Minz_Configuration::canLogIn()) { ?> + <div class="form-group"> + <label class="group-name" for="token"><?php echo _t('auth_token'); ?></label> + <?php $token = FreshRSS_Context::$conf->token; ?> + <div class="group-controls"> + <input type="text" id="token" name="token" value="<?php echo $token; ?>" placeholder="<?php echo _t('blank_to_disable'); ?>"<?php + echo Minz_Configuration::canLogIn() ? '' : ' disabled="disabled"'; ?> /> + <?php echo _i('help'); ?> <?php echo _t('explain_token', Minz_Url::display(null, 'html', true), $token); ?> + </div> + </div> + <?php } ?> + + <div class="form-group"> + <div class="group-controls"> + <label class="checkbox" for="api_enabled"> + <input type="checkbox" name="api_enabled" id="api_enabled" value="1"<?php echo Minz_Configuration::apiEnabled() ? ' checked="checked"' : '', + Minz_Configuration::needsLogin() ? '' : ' disabled="disabled"'; ?> /> + <?php echo _t('api_enabled'); ?> + </label> + </div> + </div> + + <div class="form-group form-actions"> + <div class="group-controls"> + <button type="submit" class="btn btn-important"><?php echo _t('save'); ?></button> + <button type="reset" class="btn"><?php echo _t('cancel'); ?></button> + </div> + </div> + </form> +</div> diff --git a/app/views/auth/logout.phtml b/app/views/auth/logout.phtml new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/app/views/auth/logout.phtml diff --git a/app/views/auth/personaLogin.phtml b/app/views/auth/personaLogin.phtml new file mode 100644 index 000000000..dd3e22b52 --- /dev/null +++ b/app/views/auth/personaLogin.phtml @@ -0,0 +1,24 @@ +<?php if ($this->res === false) { ?> +<div class="prompt"> + <h1><?php echo _t('login'); ?></h1> + + <p> + <a class="signin btn btn-important" href="<?php echo _url('auth', 'login'); ?>"> + <?php echo _i('login'); ?> <?php echo _t('login_with_persona'); ?> + </a> + + <br /><br /> + + <?php echo _i('help'); ?> + <small> + <a href="<?php echo _url('auth', 'reset'); ?>"><?php echo _t('login_persona_problem'); ?></a> + </small> + </p> + + <p><a href="<?php echo _url('index', 'about'); ?>"><?php echo _t('about_freshrss'); ?></a></p> +</div> +<?php +} else { + echo json_encode($this->res); +} +?> diff --git a/app/views/index/resetAuth.phtml b/app/views/auth/reset.phtml index 6d4282c14..e501555c4 100644 --- a/app/views/index/resetAuth.phtml +++ b/app/views/auth/reset.phtml @@ -9,7 +9,7 @@ <?php } ?> <?php if (!$this->no_form) { ?> - <form id="crypto-form" method="post" action="<?php echo _url('index', 'resetAuth'); ?>"> + <form id="crypto-form" method="post" action="<?php echo _url('auth', 'reset'); ?>"> <p class="alert alert-warn"> <span class="alert-head"><?php echo _t('attention'); ?></span><br /> <?php echo _t('auth_will_reset'); ?> diff --git a/app/views/configure/archiving.phtml b/app/views/configure/archiving.phtml index c9cc7fe02..7c2d79343 100644 --- a/app/views/configure/archiving.phtml +++ b/app/views/configure/archiving.phtml @@ -1,31 +1,31 @@ <?php $this->partial('aside_configure'); ?> <div class="post"> - <a href="<?php echo _url('index', 'index'); ?>"><?php echo Minz_Translate::t('back_to_rss_feeds'); ?></a> + <a href="<?php echo _url('index', 'index'); ?>"><?php echo _t('back_to_rss_feeds'); ?></a> <form method="post" action="<?php echo _url('configure', 'archiving'); ?>"> - <legend><?php echo Minz_Translate::t('archiving_configuration'); ?></legend> - <p><?php echo FreshRSS_Themes::icon('help'); ?> <?php echo Minz_Translate::t('archiving_configuration_help'); ?></p> + <legend><?php echo _t('archiving_configuration'); ?></legend> + <p><?php echo _i('help'); ?> <?php echo _t('archiving_configuration_help'); ?></p> <div class="form-group"> - <label class="group-name" for="old_entries"><?php echo Minz_Translate::t('delete_articles_every'); ?></label> + <label class="group-name" for="old_entries"><?php echo _t('delete_articles_every'); ?></label> <div class="group-controls"> - <input type="number" id="old_entries" name="old_entries" min="1" max="1200" value="<?php echo $this->conf->old_entries; ?>" /> <?php echo Minz_Translate::t('month'); ?> - <a class="btn confirm" href="<?php echo _url('entry', 'purge'); ?>"><?php echo Minz_Translate::t('purge_now'); ?></a> + <input type="number" id="old_entries" name="old_entries" min="1" max="1200" value="<?php echo FreshRSS_Context::$conf->old_entries; ?>" /> <?php echo _t('month'); ?> + <a class="btn confirm" href="<?php echo _url('entry', 'purge'); ?>"><?php echo _t('purge_now'); ?></a> </div> </div> <div class="form-group"> - <label class="group-name" for="keep_history_default"><?php echo Minz_Translate::t('keep_history'), ' ', Minz_Translate::t('by_feed'); ?></label> + <label class="group-name" for="keep_history_default"><?php echo _t('keep_history'), ' ', _t('by_feed'); ?></label> <div class="group-controls"> <select class="number" name="keep_history_default" id="keep_history_default" required="required"><?php foreach (array('' => '', 0 => '0', 10 => '10', 50 => '50', 100 => '100', 500 => '500', 1000 => '1 000', 5000 => '5 000', 10000 => '10 000', -1 => '∞') as $v => $t) { - echo '<option value="' . $v . ($this->conf->keep_history_default == $v ? '" selected="selected' : '') . '">' . $t . ' </option>'; + echo '<option value="' . $v . (FreshRSS_Context::$conf->keep_history_default == $v ? '" selected="selected' : '') . '">' . $t . ' </option>'; } - ?></select> (<?php echo Minz_Translate::t('by_default'); ?>) + ?></select> (<?php echo _t('by_default'); ?>) </div> </div> <div class="form-group"> - <label class="group-name" for="ttl_default"><?php echo Minz_Translate::t('ttl'); ?></label> + <label class="group-name" for="ttl_default"><?php echo _t('ttl'); ?></label> <div class="group-controls"> <select class="number" name="ttl_default" id="ttl_default" required="required"><?php $found = false; @@ -34,44 +34,49 @@ 36000 => '10h', 43200 => '12h', 64800 => '18h', 86400 => '1d', 129600 => '1.5d', 172800 => '2d', 259200 => '3d', 345600 => '4d', 432000 => '5d', 518400 => '6d', 604800 => '1wk', -1 => '∞') as $v => $t) { - echo '<option value="' . $v . ($this->conf->ttl_default == $v ? '" selected="selected' : '') . '">' . $t . '</option>'; - if ($this->conf->ttl_default == $v) { + echo '<option value="' . $v . (FreshRSS_Context::$conf->ttl_default == $v ? '" selected="selected' : '') . '">' . $t . '</option>'; + if (FreshRSS_Context::$conf->ttl_default == $v) { $found = true; } } if (!$found) { - echo '<option value="' . intval($this->conf->ttl_default) . '" selected="selected">' . intval($this->conf->ttl_default) . 's</option>'; + echo '<option value="' . intval(FreshRSS_Context::$conf->ttl_default) . '" selected="selected">' . intval(FreshRSS_Context::$conf->ttl_default) . 's</option>'; } - ?></select> (<?php echo Minz_Translate::t('by_default'); ?>) + ?></select> (<?php echo _t('by_default'); ?>) </div> </div> <div class="form-group form-actions"> <div class="group-controls"> - <button type="submit" class="btn btn-important"><?php echo Minz_Translate::t('save'); ?></button> - <button type="reset" class="btn"><?php echo Minz_Translate::t('cancel'); ?></button> + <button type="submit" class="btn btn-important"><?php echo _t('save'); ?></button> + <button type="reset" class="btn"><?php echo _t('cancel'); ?></button> </div> </div> </form> <form method="post" action="<?php echo _url('entry', 'optimize'); ?>"> - <legend><?php echo Minz_Translate::t ('advanced'); ?></legend> + <legend><?php echo _t('advanced'); ?></legend> <div class="form-group"> - <p class="group-name"><?php echo Minz_Translate::t('current_user'); ?></p> + <label class="group-name"><?php echo _t('current_user'); ?></label> <div class="group-controls"> - <p><?php echo formatNumber($this->nb_total), ' ', Minz_Translate::t('articles'), ', ', formatBytes($this->size_user); ?></p> - <input type="hidden" name="optimiseDatabase" value="1" /> - <button type="submit" class="btn btn-important"><?php echo Minz_Translate::t('optimize_bdd'); ?></button> - <?php echo FreshRSS_Themes::icon('help'); ?> <?php echo Minz_Translate::t('optimize_todo_sometimes'); ?> + <?php echo _t('conf.users.articles_and_size', format_number($this->nb_total), format_bytes($this->size_user)); ?> </div> </div> - <?php if (Minz_Configuration::isAdmin(Minz_Session::param('currentUser', '_'))) { ?> + <?php if (FreshRSS_Auth::hasAccess('admin')) { ?> <div class="form-group"> - <p class="group-name"><?php echo Minz_Translate::t('users'); ?></p> + <label class="group-name"><?php echo _t('users'); ?></label> <div class="group-controls"> - <p><?php echo formatBytes($this->size_total); ?></p> + <?php echo format_bytes($this->size_total); ?> + </div> + </div> + + <div class="form-group form-actions"> + <div class="group-controls"> + <input type="hidden" name="optimiseDatabase" value="1" /> + <button type="submit" class="btn btn-important"><?php echo _t('optimize_bdd'); ?></button> + <?php echo _i('help'); ?> <?php echo _t('optimize_todo_sometimes'); ?> </div> </div> <?php } ?> diff --git a/app/views/configure/categorize.phtml b/app/views/configure/categorize.phtml deleted file mode 100644 index 23d1c9fa1..000000000 --- a/app/views/configure/categorize.phtml +++ /dev/null @@ -1,55 +0,0 @@ -<?php $this->partial ('aside_feed'); ?> - -<div class="post"> - <a href="<?php echo _url ('index', 'index'); ?>"><?php echo Minz_Translate::t ('back_to_rss_feeds'); ?></a> - - <form method="post" action="<?php echo _url ('configure', 'categorize'); ?>"> - <legend><?php echo Minz_Translate::t ('categories_management'); ?></legend> - - <p class="alert alert-warn"><?php echo Minz_Translate::t ('feeds_moved_category_deleted', $this->defaultCategory->name ()); ?></p> - - <?php $i = 0; foreach ($this->categories as $cat) { $i++; ?> - <div class="form-group"> - <label class="group-name" for="cat_<?php echo $cat->id (); ?>"> - <?php echo Minz_Translate::t ('category_number', $i); ?> - </label> - <div class="group-controls"> - <div class="stick"> - <input type="text" id="cat_<?php echo $cat->id (); ?>" name="categories[]" value="<?php echo $cat->name (); ?>" /> - - <?php if ($cat->nbFeed () > 0) { ?> - <a class="btn" href="<?php echo _url('index', 'index', 'get', 'c_' . $cat->id ()); ?>"> - <?php echo _i('link'); ?> - </a> - <button formaction="<?php echo _url('feed', 'delete', 'id', $cat->id (), 'type', 'category'); ?>" - class="btn btn-attention confirm" - data-str-confirm="<?php echo _t('confirm_action_feed_cat'); ?>" - type="submit"><?php echo _t('ask_empty'); ?></button> - <?php } ?> - </div> - (<?php echo Minz_Translate::t ('number_feeds', $cat->nbFeed ()); ?>) - - <?php if ($cat->id () === $this->defaultCategory->id ()) { ?> - <?php echo FreshRSS_Themes::icon('help'); ?> <?php echo Minz_Translate::t ('can_not_be_deleted'); ?> - <?php } ?> - - <input type="hidden" name="ids[]" value="<?php echo $cat->id (); ?>" /> - </div> - </div> - <?php } ?> - - <div class="form-group"> - <label class="group-name" for="new_category"><?php echo Minz_Translate::t ('add_category'); ?></label> - <div class="group-controls"> - <input type="text" id="new_category" name="new_category" placeholder="<?php echo Minz_Translate::t ('new_category'); ?>" /> - </div> - </div> - - <div class="form-group form-actions"> - <div class="group-controls"> - <button type="submit" class="btn btn-important"><?php echo Minz_Translate::t ('save'); ?></button> - <button type="reset" class="btn"><?php echo Minz_Translate::t ('cancel'); ?></button> - </div> - </div> - </form> -</div> diff --git a/app/views/configure/display.phtml b/app/views/configure/display.phtml index 8eb3a156b..69205fa93 100644 --- a/app/views/configure/display.phtml +++ b/app/views/configure/display.phtml @@ -1,31 +1,31 @@ -<?php $this->partial ('aside_configure'); ?> +<?php $this->partial('aside_configure'); ?> <div class="post"> - <a href="<?php echo _url ('index', 'index'); ?>"><?php echo Minz_Translate::t ('back_to_rss_feeds'); ?></a> + <a href="<?php echo _url('index', 'index'); ?>"><?php echo _t('back_to_rss_feeds'); ?></a> - <form method="post" action="<?php echo _url ('configure', 'display'); ?>"> - <legend><?php echo Minz_Translate::t ('display_configuration'); ?></legend> + <form method="post" action="<?php echo _url('configure', 'display'); ?>"> + <legend><?php echo _t('display_configuration'); ?></legend> <div class="form-group"> - <label class="group-name" for="language"><?php echo Minz_Translate::t ('language'); ?></label> + <label class="group-name" for="language"><?php echo _t('language'); ?></label> <div class="group-controls"> <select name="language" id="language"> - <?php $languages = $this->conf->availableLanguages (); ?> + <?php $languages = FreshRSS_Context::$conf->availableLanguages(); ?> <?php foreach ($languages as $short => $lib) { ?> - <option value="<?php echo $short; ?>"<?php echo $this->conf->language === $short ? ' selected="selected"' : ''; ?>><?php echo $lib; ?></option> + <option value="<?php echo $short; ?>"<?php echo FreshRSS_Context::$conf->language === $short ? ' selected="selected"' : ''; ?>><?php echo $lib; ?></option> <?php } ?> </select> </div> </div> <div class="form-group"> - <label class="group-name" for="theme"><?php echo Minz_Translate::t ('theme'); ?></label> + <label class="group-name" for="theme"><?php echo _t('theme'); ?></label> <div class="group-controls"> <select name="theme" id="theme" required=""><?php $found = false; foreach ($this->themes as $theme) { - ?><option value="<?php echo $theme['id']; ?>"<?php if ($this->conf->theme === $theme['id']) { echo ' selected="selected"'; $found = true; } ?>><?php - echo $theme['name'] . ' — ' . Minz_Translate::t ('by') . ' ' . $theme['author']; + ?><option value="<?php echo $theme['id']; ?>"<?php if (FreshRSS_Context::$conf->theme === $theme['id']) { echo ' selected="selected"'; $found = true; } ?>><?php + echo $theme['name'] . ' — ' . _t('by') . ' ' . $theme['author']; ?></option><?php } if (!$found) { @@ -35,74 +35,74 @@ </div> </div> - <?php $width = $this->conf->content_width; ?> + <?php $width = FreshRSS_Context::$conf->content_width; ?> <div class="form-group"> - <label class="group-name" for="content_width"><?php echo Minz_Translate::t('content_width'); ?></label> + <label class="group-name" for="content_width"><?php echo _t('content_width'); ?></label> <div class="group-controls"> <select name="content_width" id="content_width" required=""> <option value="thin" <?php echo $width === 'thin'? 'selected="selected"' : ''; ?>> - <?php echo Minz_Translate::t('width_thin'); ?> + <?php echo _t('width_thin'); ?> </option> <option value="medium" <?php echo $width === 'medium'? 'selected="selected"' : ''; ?>> - <?php echo Minz_Translate::t('width_medium'); ?> + <?php echo _t('width_medium'); ?> </option> <option value="large" <?php echo $width === 'large'? 'selected="selected"' : ''; ?>> - <?php echo Minz_Translate::t('width_large'); ?> + <?php echo _t('width_large'); ?> </option> <option value="no_limit" <?php echo $width === 'no_limit'? 'selected="selected"' : ''; ?>> - <?php echo Minz_Translate::t('width_no_limit'); ?> + <?php echo _t('width_no_limit'); ?> </option> </select> </div> </div> <div class="form-group"> - <label class="group-name" for="theme"><?php echo Minz_Translate::t ('article_icons'); ?></label> + <label class="group-name" for="theme"><?php echo _t('article_icons'); ?></label> <table> <thead> <tr> <th> </th> - <th title="<?php echo Minz_Translate::t ('mark_read'); ?>"><?php echo FreshRSS_Themes::icon('read'); ?></th> - <th title="<?php echo Minz_Translate::t ('mark_favorite'); ?>"><?php echo FreshRSS_Themes::icon('bookmark'); ?></th> - <th><?php echo Minz_Translate::t ('sharing'); ?></th> - <th><?php echo Minz_Translate::t ('related_tags'); ?></th> - <th><?php echo Minz_Translate::t ('publication_date'); ?></th> - <th><?php echo FreshRSS_Themes::icon('link'); ?></th> + <th title="<?php echo _t('mark_read'); ?>"><?php echo _i('read'); ?></th> + <th title="<?php echo _t('mark_favorite'); ?>"><?php echo _i('bookmark'); ?></th> + <th><?php echo _t('sharing'); ?></th> + <th><?php echo _t('related_tags'); ?></th> + <th><?php echo _t('publication_date'); ?></th> + <th><?php echo _i('link'); ?></th> </tr> </thead> <tbody> <tr> - <th><?php echo Minz_Translate::t ('top_line'); ?></th> - <td><input type="checkbox" name="topline_read" value="1"<?php echo $this->conf->topline_read ? ' checked="checked"' : ''; ?> /></td> - <td><input type="checkbox" name="topline_favorite" value="1"<?php echo $this->conf->topline_favorite ? ' checked="checked"' : ''; ?> /></td> + <th><?php echo _t('top_line'); ?></th> + <td><input type="checkbox" name="topline_read" value="1"<?php echo FreshRSS_Context::$conf->topline_read ? ' checked="checked"' : ''; ?> /></td> + <td><input type="checkbox" name="topline_favorite" value="1"<?php echo FreshRSS_Context::$conf->topline_favorite ? ' checked="checked"' : ''; ?> /></td> <td><input type="checkbox" disabled="disabled" /></td> <td><input type="checkbox" disabled="disabled" /></td> - <td><input type="checkbox" name="topline_date" value="1"<?php echo $this->conf->topline_date ? ' checked="checked"' : ''; ?> /></td> - <td><input type="checkbox" name="topline_link" value="1"<?php echo $this->conf->topline_link ? ' checked="checked"' : ''; ?> /></td> + <td><input type="checkbox" name="topline_date" value="1"<?php echo FreshRSS_Context::$conf->topline_date ? ' checked="checked"' : ''; ?> /></td> + <td><input type="checkbox" name="topline_link" value="1"<?php echo FreshRSS_Context::$conf->topline_link ? ' checked="checked"' : ''; ?> /></td> </tr><tr> - <th><?php echo Minz_Translate::t ('bottom_line'); ?></th> - <td><input type="checkbox" name="bottomline_read" value="1"<?php echo $this->conf->bottomline_read ? ' checked="checked"' : ''; ?> /></td> - <td><input type="checkbox" name="bottomline_favorite" value="1"<?php echo $this->conf->bottomline_favorite ? ' checked="checked"' : ''; ?> /></td> - <td><input type="checkbox" name="bottomline_sharing" value="1"<?php echo $this->conf->bottomline_sharing ? ' checked="checked"' : ''; ?> /></td> - <td><input type="checkbox" name="bottomline_tags" value="1"<?php echo $this->conf->bottomline_tags ? ' checked="checked"' : ''; ?> /></td> - <td><input type="checkbox" name="bottomline_date" value="1"<?php echo $this->conf->bottomline_date ? ' checked="checked"' : ''; ?> /></td> - <td><input type="checkbox" name="bottomline_link" value="1"<?php echo $this->conf->bottomline_link ? ' checked="checked"' : ''; ?> /></td> + <th><?php echo _t('bottom_line'); ?></th> + <td><input type="checkbox" name="bottomline_read" value="1"<?php echo FreshRSS_Context::$conf->bottomline_read ? ' checked="checked"' : ''; ?> /></td> + <td><input type="checkbox" name="bottomline_favorite" value="1"<?php echo FreshRSS_Context::$conf->bottomline_favorite ? ' checked="checked"' : ''; ?> /></td> + <td><input type="checkbox" name="bottomline_sharing" value="1"<?php echo FreshRSS_Context::$conf->bottomline_sharing ? ' checked="checked"' : ''; ?> /></td> + <td><input type="checkbox" name="bottomline_tags" value="1"<?php echo FreshRSS_Context::$conf->bottomline_tags ? ' checked="checked"' : ''; ?> /></td> + <td><input type="checkbox" name="bottomline_date" value="1"<?php echo FreshRSS_Context::$conf->bottomline_date ? ' checked="checked"' : ''; ?> /></td> + <td><input type="checkbox" name="bottomline_link" value="1"<?php echo FreshRSS_Context::$conf->bottomline_link ? ' checked="checked"' : ''; ?> /></td> </tr> </tbody> </table><br /> </div> <div class="form-group"> - <label class="group-name" for="posts_per_page"><?php echo Minz_Translate::t ('html5_notif_timeout'); ?></label> + <label class="group-name" for="posts_per_page"><?php echo _t('html5_notif_timeout'); ?></label> <div class="group-controls"> - <input type="number" id="html5_notif_timeout" name="html5_notif_timeout" value="<?php echo $this->conf->html5_notif_timeout; ?>" /> <?php echo Minz_Translate::t ('seconds_(0_means_no_timeout)'); ?> + <input type="number" id="html5_notif_timeout" name="html5_notif_timeout" value="<?php echo FreshRSS_Context::$conf->html5_notif_timeout; ?>" /> <?php echo _t('seconds_(0_means_no_timeout)'); ?> </div> </div> <div class="form-group form-actions"> <div class="group-controls"> - <button type="submit" class="btn btn-important"><?php echo Minz_Translate::t ('save'); ?></button> - <button type="reset" class="btn"><?php echo Minz_Translate::t ('cancel'); ?></button> + <button type="submit" class="btn btn-important"><?php echo _t('save'); ?></button> + <button type="reset" class="btn"><?php echo _t('cancel'); ?></button> </div> </div> </form> diff --git a/app/views/configure/feed.phtml b/app/views/configure/feed.phtml deleted file mode 100644 index e96a28739..000000000 --- a/app/views/configure/feed.phtml +++ /dev/null @@ -1,182 +0,0 @@ -<?php $this->partial ('aside_feed'); ?> - -<?php if ($this->flux) { ?> -<div class="post"> - <a href="<?php echo _url ('index', 'index'); ?>"><?php echo Minz_Translate::t ('back_to_rss_feeds'); ?></a> <?php echo Minz_Translate::t ('or'); ?> <a href="<?php echo _url ('index', 'index', 'get', 'f_' . $this->flux->id ()); ?>"><?php echo Minz_Translate::t ('filter'); ?></a> - - <h1><?php echo $this->flux->name (); ?></h1> - <?php echo $this->flux->description (); ?> - - <?php $nbEntries = $this->flux->nbEntries (); ?> - - <?php if ($this->flux->inError ()) { ?> - <p class="alert alert-error"><span class="alert-head"><?php echo Minz_Translate::t ('damn'); ?></span> <?php echo Minz_Translate::t ('feed_in_error'); ?></p> - <?php } elseif ($nbEntries === 0) { ?> - <p class="alert alert-warn"><?php echo Minz_Translate::t ('feed_empty'); ?></p> - <?php } ?> - - <form method="post" action="<?php echo _url ('configure', 'feed', 'id', $this->flux->id ()); ?>" autocomplete="off"> - <legend><?php echo Minz_Translate::t ('informations'); ?></legend> - <div class="form-group"> - <label class="group-name" for="name"><?php echo Minz_Translate::t ('title'); ?></label> - <div class="group-controls"> - <input type="text" name="name" id="name" class="extend" value="<?php echo $this->flux->name () ; ?>" /> - </div> - </div> - <div class="form-group"> - <label class="group-name" for="description"><?php echo Minz_Translate::t ('feed_description'); ?></label> - <div class="group-controls"> - <textarea name="description" id="description"><?php echo htmlspecialchars($this->flux->description(), ENT_NOQUOTES, 'UTF-8'); ?></textarea> - </div> - </div> - <div class="form-group"> - <label class="group-name" for="website"><?php echo Minz_Translate::t ('website_url'); ?></label> - <div class="group-controls"> - <div class="stick"> - <input type="text" name="website" id="website" class="extend" value="<?php echo $this->flux->website (); ?>" /> - <a class="btn" target="_blank" href="<?php echo $this->flux->website (); ?>"><?php echo FreshRSS_Themes::icon('link'); ?></a> - </div> - </div> - </div> - <div class="form-group"> - <label class="group-name" for="url"><?php echo Minz_Translate::t ('feed_url'); ?></label> - <div class="group-controls"> - <div class="stick"> - <input type="text" name="url" id="url" class="extend" value="<?php echo $this->flux->url (); ?>" /> - <a class="btn" target="_blank" href="<?php echo $this->flux->url (); ?>"><?php echo FreshRSS_Themes::icon('link'); ?></a> - </div> - - <a class="btn" target="_blank" href="http://validator.w3.org/feed/check.cgi?url=<?php echo $this->flux->url (); ?>"><?php echo Minz_Translate::t ('feed_validator'); ?></a> - </div> - </div> - <div class="form-group"> - <label class="group-name" for="category"><?php echo Minz_Translate::t ('category'); ?></label> - <div class="group-controls"> - <select name="category" id="category"> - <?php foreach ($this->categories as $cat) { ?> - <option value="<?php echo $cat->id (); ?>"<?php echo $cat->id ()== $this->flux->category () ? ' selected="selected"' : ''; ?>> - <?php echo $cat->name (); ?> - </option> - <?php } ?> - </select> - </div> - </div> - <div class="form-group"> - <label class="group-name" for="priority"><?php echo Minz_Translate::t ('show_in_all_flux'); ?></label> - <div class="group-controls"> - <label class="checkbox" for="priority"> - <input type="checkbox" name="priority" id="priority" value="10"<?php echo $this->flux->priority () > 0 ? ' checked="checked"' : ''; ?> /> - <?php echo Minz_Translate::t ('yes'); ?> - </label> - </div> - </div> - <div class="form-group"> - <div class="group-controls"> - <a href="<?php echo _url('stats', 'repartition', 'id', $this->flux->id()); ?>"> - <?php echo _i('stats'); ?> <?php echo _t('stats'); ?> - </a> - </div> - </div> - <div class="form-group form-actions"> - <div class="group-controls"> - <button class="btn btn-important"><?php echo _t('save'); ?></button> - <button class="btn btn-attention confirm" - data-str-confirm="<?php echo _t('confirm_action_feed_cat'); ?>" - formaction="<?php echo _url('feed', 'delete', 'id', $this->flux->id ()); ?>" - formmethod="post"><?php echo _t('delete'); ?></button> - </div> - </div> - - <legend><?php echo Minz_Translate::t ('archiving_configuration'); ?></legend> - - <div class="form-group"> - <div class="group-controls"> - <div class="stick"> - <input type="text" value="<?php echo _t('number_articles', $nbEntries); ?>" disabled="disabled" /> - <a class="btn" href="<?php echo _url('feed', 'actualize', 'id', $this->flux->id ()); ?>"> - <?php echo _i('refresh'); ?> <?php echo _t('actualize'); ?> - </a> - </div> - </div> - </div> - <div class="form-group"> - <label class="group-name" for="keep_history"><?php echo Minz_Translate::t ('keep_history'); ?></label> - <div class="group-controls"> - <select class="number" name="keep_history" id="keep_history" required="required"><?php - foreach (array('' => '', -2 => Minz_Translate::t('by_default'), 0 => '0', 10 => '10', 50 => '50', 100 => '100', 500 => '500', 1000 => '1 000', 5000 => '5 000', 10000 => '10 000', -1 => '∞') as $v => $t) { - echo '<option value="' . $v . ($this->flux->keepHistory() === $v ? '" selected="selected' : '') . '">' . $t . '</option>'; - } - ?></select> - </div> - </div> - <div class="form-group"> - <label class="group-name" for="ttl"><?php echo Minz_Translate::t('ttl'); ?></label> - <div class="group-controls"> - <select class="number" name="ttl" id="ttl" required="required"><?php - $found = false; - foreach (array(-2 => Minz_Translate::t('by_default'), 900 => '15min', 1200 => '20min', 1500 => '25min', 1800 => '30min', 2700 => '45min', - 3600 => '1h', 5400 => '1.5h', 7200 => '2h', 10800 => '3h', 14400 => '4h', 18800 => '5h', 21600 => '6h', 25200 => '7h', 28800 => '8h', - 36000 => '10h', 43200 => '12h', 64800 => '18h', - 86400 => '1d', 129600 => '1.5d', 172800 => '2d', 259200 => '3d', 345600 => '4d', 432000 => '5d', 518400 => '6d', - 604800 => '1wk', 1209600 => '2wk', 1814400 => '3wk', 2419200 => '4wk', 2629744 => '1mo', -1 => '∞') as $v => $t) { - echo '<option value="' . $v . ($this->flux->ttl() === $v ? '" selected="selected' : '') . '">' . $t . '</option>'; - if ($this->flux->ttl() == $v) { - $found = true; - } - } - if (!$found) { - echo '<option value="' . intval($this->flux->ttl()) . '" selected="selected">' . intval($this->flux->ttl()) . 's</option>'; - } - ?></select> - </div> - </div> - <div class="form-group form-actions"> - <div class="group-controls"> - <button class="btn btn-important"><?php echo Minz_Translate::t ('save'); ?></button> - <button class="btn btn-attention confirm" formmethod="post" formaction="<?php echo Minz_Url::display (array ('c' => 'feed', 'a' => 'truncate', 'params' => array ('id' => $this->flux->id ()))); ?>"><?php echo Minz_Translate::t ('truncate'); ?></button> - </div> - </div> - - <legend><?php echo Minz_Translate::t ('login_configuration'); ?></legend> - <?php $auth = $this->flux->httpAuth (false); ?> - <div class="form-group"> - <label class="group-name" for="http_user"><?php echo Minz_Translate::t ('http_username'); ?></label> - <div class="group-controls"> - <input type="text" name="http_user" id="http_user" class="extend" value="<?php echo $auth['username']; ?>" autocomplete="off" /> - <?php echo FreshRSS_Themes::icon('help'); ?> <?php echo Minz_Translate::t ('access_protected_feeds'); ?> - </div> - - <label class="group-name" for="http_pass"><?php echo Minz_Translate::t ('http_password'); ?></label> - <div class="group-controls"> - <input type="password" name="http_pass" id="http_pass" class="extend" value="<?php echo $auth['password']; ?>" autocomplete="off" /> - </div> - </div> - - <div class="form-group form-actions"> - <div class="group-controls"> - <button type="submit" class="btn btn-important"><?php echo Minz_Translate::t ('save'); ?></button> - <button type="reset" class="btn"><?php echo Minz_Translate::t ('cancel'); ?></button> - </div> - </div> - - <legend><?php echo Minz_Translate::t ('advanced'); ?></legend> - <div class="form-group"> - <label class="group-name" for="path_entries"><?php echo Minz_Translate::t ('css_path_on_website'); ?></label> - <div class="group-controls"> - <input type="text" name="path_entries" id="path_entries" class="extend" value="<?php echo $this->flux->pathEntries (); ?>" placeholder="<?php echo Minz_Translate::t ('blank_to_disable'); ?>" /> - <?php echo FreshRSS_Themes::icon('help'); ?> <?php echo Minz_Translate::t ('retrieve_truncated_feeds'); ?> - </div> - </div> - - <div class="form-group form-actions"> - <div class="group-controls"> - <button type="submit" class="btn btn-important"><?php echo Minz_Translate::t ('save'); ?></button> - <button type="reset" class="btn"><?php echo Minz_Translate::t ('cancel'); ?></button> - </div> - </div> - </form> -</div> - -<?php } else { ?> -<div class="alert alert-warn"><span class="alert-head"><?php echo Minz_Translate::t ('no_selected_feed'); ?></span> <?php echo Minz_Translate::t ('think_to_add'); ?></div> -<?php } ?> diff --git a/app/views/configure/queries.phtml b/app/views/configure/queries.phtml index e778ce078..994dfc11b 100644 --- a/app/views/configure/queries.phtml +++ b/app/views/configure/queries.phtml @@ -6,7 +6,7 @@ <form method="post" action="<?php echo _url('configure', 'queries'); ?>"> <legend><?php echo _t('queries'); ?></legend> - <?php foreach ($this->conf->queries as $key => $query) { ?> + <?php foreach (FreshRSS_Context::$conf->queries as $key => $query) { ?> <div class="form-group" id="query-group-<?php echo $key; ?>"> <label class="group-name" for="queries_<?php echo $key; ?>_name"> <?php echo _t('query_number', $key + 1); ?> @@ -82,7 +82,7 @@ </div> <?php } ?> - <?php if (count($this->conf->queries) > 0) { ?> + <?php if (count(FreshRSS_Context::$conf->queries) > 0) { ?> <div class="form-group form-actions"> <div class="group-controls"> <button type="submit" class="btn btn-important"><?php echo _t('save'); ?></button> diff --git a/app/views/configure/reading.phtml b/app/views/configure/reading.phtml index 8b2da2a28..b8f673466 100644 --- a/app/views/configure/reading.phtml +++ b/app/views/configure/reading.phtml @@ -1,36 +1,36 @@ -<?php $this->partial ('aside_configure'); ?> +<?php $this->partial('aside_configure'); ?> <div class="post"> - <a href="<?php echo _url ('index', 'index'); ?>"><?php echo Minz_Translate::t ('back_to_rss_feeds'); ?></a> + <a href="<?php echo _url('index', 'index'); ?>"><?php echo _t('back_to_rss_feeds'); ?></a> - <form method="post" action="<?php echo _url ('configure', 'reading'); ?>"> - <legend><?php echo Minz_Translate::t ('reading_configuration'); ?></legend> + <form method="post" action="<?php echo _url('configure', 'reading'); ?>"> + <legend><?php echo _t('reading_configuration'); ?></legend> <div class="form-group"> - <label class="group-name" for="posts_per_page"><?php echo Minz_Translate::t ('articles_per_page'); ?></label> + <label class="group-name" for="posts_per_page"><?php echo _t('articles_per_page'); ?></label> <div class="group-controls"> - <input type="number" id="posts_per_page" name="posts_per_page" value="<?php echo $this->conf->posts_per_page; ?>" min="5" max="50" /> + <input type="number" id="posts_per_page" name="posts_per_page" value="<?php echo FreshRSS_Context::$conf->posts_per_page; ?>" min="5" max="50" /> <?php echo _i('help'); ?> <?php echo _t('number_divided_when_reader'); ?> </div> </div> <div class="form-group"> - <label class="group-name" for="sort_order"><?php echo Minz_Translate::t ('sort_order'); ?></label> + <label class="group-name" for="sort_order"><?php echo _t('sort_order'); ?></label> <div class="group-controls"> <select name="sort_order" id="sort_order"> - <option value="DESC"<?php echo $this->conf->sort_order === 'DESC' ? ' selected="selected"' : ''; ?>><?php echo Minz_Translate::t ('newer_first'); ?></option> - <option value="ASC"<?php echo $this->conf->sort_order === 'ASC' ? ' selected="selected"' : ''; ?>><?php echo Minz_Translate::t ('older_first'); ?></option> + <option value="DESC"<?php echo FreshRSS_Context::$conf->sort_order === 'DESC' ? ' selected="selected"' : ''; ?>><?php echo _t('newer_first'); ?></option> + <option value="ASC"<?php echo FreshRSS_Context::$conf->sort_order === 'ASC' ? ' selected="selected"' : ''; ?>><?php echo _t('older_first'); ?></option> </select> </div> </div> <div class="form-group"> - <label class="group-name" for="view_mode"><?php echo Minz_Translate::t ('default_view'); ?></label> + <label class="group-name" for="view_mode"><?php echo _t('default_view'); ?></label> <div class="group-controls"> <select name="view_mode" id="view_mode"> - <option value="normal"<?php echo $this->conf->view_mode === 'normal' ? ' selected="selected"' : ''; ?>><?php echo Minz_Translate::t ('normal_view'); ?></option> - <option value="reader"<?php echo $this->conf->view_mode === 'reader' ? ' selected="selected"' : ''; ?>><?php echo Minz_Translate::t ('reader_view'); ?></option> - <option value="global"<?php echo $this->conf->view_mode === 'global' ? ' selected="selected"' : ''; ?>><?php echo Minz_Translate::t ('global_view'); ?></option> + <option value="normal"<?php echo FreshRSS_Context::$conf->view_mode === 'normal' ? ' selected="selected"' : ''; ?>><?php echo _t('normal_view'); ?></option> + <option value="reader"<?php echo FreshRSS_Context::$conf->view_mode === 'reader' ? ' selected="selected"' : ''; ?>><?php echo _t('reader_view'); ?></option> + <option value="global"<?php echo FreshRSS_Context::$conf->view_mode === 'global' ? ' selected="selected"' : ''; ?>><?php echo _t('global_view'); ?></option> </select> </div> </div> @@ -39,9 +39,9 @@ <label class="group-name" for="view_mode"><?php echo _t('articles_to_display'); ?></label> <div class="group-controls"> <select name="default_view" id="default_view"> - <option value="<?php echo FreshRSS_Entry::STATE_NOT_READ; ?>"<?php echo $this->conf->default_view === FreshRSS_Entry::STATE_NOT_READ ? ' selected="selected"' : ''; ?>><?php echo _t('show_adaptive'); ?></option> - <option value="<?php echo FreshRSS_Entry::STATE_ALL; ?>"<?php echo $this->conf->default_view === FreshRSS_Entry::STATE_ALL ? ' selected="selected"' : ''; ?>><?php echo _t('show_all_articles'); ?></option> - <option value="<?php echo FreshRSS_Entry::STATE_STRICT + FreshRSS_Entry::STATE_NOT_READ; ?>"<?php echo $this->conf->default_view === FreshRSS_Entry::STATE_STRICT + FreshRSS_Entry::STATE_NOT_READ ? ' selected="selected"' : ''; ?>><?php echo _t('show_not_reads'); ?></option> + <option value="adaptive"<?php echo FreshRSS_Context::$conf->default_view === 'adaptive' ? ' selected="selected"' : ''; ?>><?php echo _t('show_adaptive'); ?></option> + <option value="all"<?php echo FreshRSS_Context::$conf->default_view === 'all' ? ' selected="selected"' : ''; ?>><?php echo _t('show_all_articles'); ?></option> + <option value="unread"<?php echo FreshRSS_Context::$conf->default_view === 'unread' ? ' selected="selected"' : ''; ?>><?php echo _t('show_not_reads'); ?></option> </select> </div> </div> @@ -49,8 +49,8 @@ <div class="form-group"> <div class="group-controls"> <label class="checkbox" for="hide_read_feeds"> - <input type="checkbox" name="hide_read_feeds" id="hide_read_feeds" value="1"<?php echo $this->conf->hide_read_feeds ? ' checked="checked"' : ''; ?> /> - <?php echo Minz_Translate::t('hide_read_feeds'); ?> + <input type="checkbox" name="hide_read_feeds" id="hide_read_feeds" value="1"<?php echo FreshRSS_Context::$conf->hide_read_feeds ? ' checked="checked"' : ''; ?> /> + <?php echo _t('hide_read_feeds'); ?> </label> </div> </div> @@ -58,9 +58,9 @@ <div class="form-group"> <div class="group-controls"> <label class="checkbox" for="display_posts"> - <input type="checkbox" name="display_posts" id="display_posts" value="1"<?php echo $this->conf->display_posts ? ' checked="checked"' : ''; ?> /> - <?php echo Minz_Translate::t ('display_articles_unfolded'); ?> - <noscript> — <strong><?php echo Minz_Translate::t ('javascript_should_be_activated'); ?></strong></noscript> + <input type="checkbox" name="display_posts" id="display_posts" value="1"<?php echo FreshRSS_Context::$conf->display_posts ? ' checked="checked"' : ''; ?> /> + <?php echo _t('display_articles_unfolded'); ?> + <noscript> — <strong><?php echo _t('javascript_should_be_activated'); ?></strong></noscript> </label> </div> </div> @@ -68,9 +68,9 @@ <div class="form-group"> <div class="group-controls"> <label class="checkbox" for="display_categories"> - <input type="checkbox" name="display_categories" id="display_categories" value="1"<?php echo $this->conf->display_categories ? ' checked="checked"' : ''; ?> /> - <?php echo Minz_Translate::t ('display_categories_unfolded'); ?> - <noscript> — <strong><?php echo Minz_Translate::t ('javascript_should_be_activated'); ?></strong></noscript> + <input type="checkbox" name="display_categories" id="display_categories" value="1"<?php echo FreshRSS_Context::$conf->display_categories ? ' checked="checked"' : ''; ?> /> + <?php echo _t('display_categories_unfolded'); ?> + <noscript> — <strong><?php echo _t('javascript_should_be_activated'); ?></strong></noscript> </label> </div> </div> @@ -78,9 +78,9 @@ <div class="form-group"> <div class="group-controls"> <label class="checkbox" for="sticky_post"> - <input type="checkbox" name="sticky_post" id="sticky_post" value="1"<?php echo $this->conf->sticky_post ? ' checked="checked"' : ''; ?> /> - <?php echo Minz_Translate::t ('sticky_post'); ?> - <noscript> — <strong><?php echo Minz_Translate::t ('javascript_should_be_activated'); ?></strong></noscript> + <input type="checkbox" name="sticky_post" id="sticky_post" value="1"<?php echo FreshRSS_Context::$conf->sticky_post ? ' checked="checked"' : ''; ?> /> + <?php echo _t('sticky_post'); ?> + <noscript> — <strong><?php echo _t('javascript_should_be_activated'); ?></strong></noscript> </label> </div> </div> @@ -88,9 +88,9 @@ <div class="form-group"> <div class="group-controls"> <label class="checkbox" for="auto_load_more"> - <input type="checkbox" name="auto_load_more" id="auto_load_more" value="1"<?php echo $this->conf->auto_load_more ? ' checked="checked"' : ''; ?> /> - <?php echo Minz_Translate::t ('auto_load_more'); ?> - <noscript> — <strong><?php echo Minz_Translate::t ('javascript_should_be_activated'); ?></strong></noscript> + <input type="checkbox" name="auto_load_more" id="auto_load_more" value="1"<?php echo FreshRSS_Context::$conf->auto_load_more ? ' checked="checked"' : ''; ?> /> + <?php echo _t('auto_load_more'); ?> + <noscript> — <strong><?php echo _t('javascript_should_be_activated'); ?></strong></noscript> </label> </div> </div> @@ -98,9 +98,9 @@ <div class="form-group"> <div class="group-controls"> <label class="checkbox" for="lazyload"> - <input type="checkbox" name="lazyload" id="lazyload" value="1"<?php echo $this->conf->lazyload ? ' checked="checked"' : ''; ?> /> - <?php echo Minz_Translate::t ('img_with_lazyload'); ?> - <noscript> — <strong><?php echo Minz_Translate::t ('javascript_should_be_activated'); ?></strong></noscript> + <input type="checkbox" name="lazyload" id="lazyload" value="1"<?php echo FreshRSS_Context::$conf->lazyload ? ' checked="checked"' : ''; ?> /> + <?php echo _t('img_with_lazyload'); ?> + <noscript> — <strong><?php echo _t('javascript_should_be_activated'); ?></strong></noscript> </label> </div> </div> @@ -108,49 +108,49 @@ <div class="form-group"> <div class="group-controls"> <label class="checkbox" for="reading_confirm"> - <input type="checkbox" name="reading_confirm" id="reading_confirm" value="1"<?php echo $this->conf->reading_confirm ? ' checked="checked"' : ''; ?> /> - <?php echo Minz_Translate::t ('reading_confirm'); ?> - <noscript> — <strong><?php echo Minz_Translate::t ('javascript_should_be_activated'); ?></strong></noscript> + <input type="checkbox" name="reading_confirm" id="reading_confirm" value="1"<?php echo FreshRSS_Context::$conf->reading_confirm ? ' checked="checked"' : ''; ?> /> + <?php echo _t('reading_confirm'); ?> + <noscript> — <strong><?php echo _t('javascript_should_be_activated'); ?></strong></noscript> </label> </div> </div> <div class="form-group"> - <label class="group-name"><?php echo Minz_Translate::t ('auto_read_when'); ?></label> + <label class="group-name"><?php echo _t('auto_read_when'); ?></label> <div class="group-controls"> <label class="checkbox" for="check_open_article"> - <input type="checkbox" name="mark_open_article" id="check_open_article" value="1"<?php echo $this->conf->mark_when['article'] ? ' checked="checked"' : ''; ?> /> - <?php echo Minz_Translate::t('article_viewed'); ?> + <input type="checkbox" name="mark_open_article" id="check_open_article" value="1"<?php echo FreshRSS_Context::$conf->mark_when['article'] ? ' checked="checked"' : ''; ?> /> + <?php echo _t('article_viewed'); ?> </label> <label class="checkbox" for="check_open_site"> - <input type="checkbox" name="mark_open_site" id="check_open_site" value="1"<?php echo $this->conf->mark_when['site'] ? ' checked="checked"' : ''; ?> /> - <?php echo Minz_Translate::t ('article_open_on_website'); ?> + <input type="checkbox" name="mark_open_site" id="check_open_site" value="1"<?php echo FreshRSS_Context::$conf->mark_when['site'] ? ' checked="checked"' : ''; ?> /> + <?php echo _t('article_open_on_website'); ?> </label> <label class="checkbox" for="check_scroll"> - <input type="checkbox" name="mark_scroll" id="check_scroll" value="1"<?php echo $this->conf->mark_when['scroll'] ? ' checked="checked"' : ''; ?> /> - <?php echo Minz_Translate::t ('scroll'); ?> + <input type="checkbox" name="mark_scroll" id="check_scroll" value="1"<?php echo FreshRSS_Context::$conf->mark_when['scroll'] ? ' checked="checked"' : ''; ?> /> + <?php echo _t('scroll'); ?> </label> <label class="checkbox" for="check_reception"> - <input type="checkbox" name="mark_upon_reception" id="check_reception" value="1"<?php echo $this->conf->mark_when['reception'] ? ' checked="checked"' : ''; ?> /> - <?php echo Minz_Translate::t ('upon_reception'); ?> + <input type="checkbox" name="mark_upon_reception" id="check_reception" value="1"<?php echo FreshRSS_Context::$conf->mark_when['reception'] ? ' checked="checked"' : ''; ?> /> + <?php echo _t('upon_reception'); ?> </label> </div> </div> <div class="form-group"> - <label class="group-name"><?php echo Minz_Translate::t ('after_onread'); ?></label> + <label class="group-name"><?php echo _t('after_onread'); ?></label> <div class="group-controls"> <label class="checkbox" for="onread_jump_next"> - <input type="checkbox" name="onread_jump_next" id="onread_jump_next" value="1"<?php echo $this->conf->onread_jump_next ? ' checked="checked"' : ''; ?> /> - <?php echo Minz_Translate::t ('jump_next'); ?> + <input type="checkbox" name="onread_jump_next" id="onread_jump_next" value="1"<?php echo FreshRSS_Context::$conf->onread_jump_next ? ' checked="checked"' : ''; ?> /> + <?php echo _t('jump_next'); ?> </label> </div> </div> <div class="form-group form-actions"> <div class="group-controls"> - <button type="submit" class="btn btn-important"><?php echo Minz_Translate::t ('save'); ?></button> - <button type="reset" class="btn"><?php echo Minz_Translate::t ('cancel'); ?></button> + <button type="submit" class="btn btn-important"><?php echo _t('save'); ?></button> + <button type="reset" class="btn"><?php echo _t('cancel'); ?></button> </div> </div> diff --git a/app/views/configure/sharing.phtml b/app/views/configure/sharing.phtml index 02ce331da..ef5e85a0c 100644 --- a/app/views/configure/sharing.phtml +++ b/app/views/configure/sharing.phtml @@ -1,38 +1,38 @@ -<?php $this->partial ('aside_configure'); ?> +<?php $this->partial('aside_configure'); ?> <div class="post"> - <a href="<?php echo _url ('index', 'index'); ?>"><?php echo Minz_Translate::t ('back_to_rss_feeds'); ?></a> + <a href="<?php echo _url('index', 'index'); ?>"><?php echo _t('back_to_rss_feeds'); ?></a> - <form method="post" action="<?php echo _url ('configure', 'sharing'); ?>" - data-simple='<div class="form-group" id="group-share-##key##"><label class="group-name">##label##</label><div class="group-controls"><a href="#" class="remove btn btn-attention" data-remove="group-share-##key##"><?php echo FreshRSS_Themes::icon('close'); ?></a> + <form method="post" action="<?php echo _url('configure', 'sharing'); ?>" + data-simple='<div class="form-group" id="group-share-##key##"><label class="group-name">##label##</label><div class="group-controls"><a href="#" class="remove btn btn-attention" data-remove="group-share-##key##"><?php echo _i('close'); ?></a> <input type="hidden" id="share_##key##_type" name="share[##key##][type]" value="##type##" /></div></div>' data-advanced='<div class="form-group" id="group-share-##key##"><label class="group-name">##label##</label><div class="group-controls"> <input type="hidden" id="share_##key##_type" name="share[##key##][type]" value="##type##" /> <div class="stick"> - <input type="text" id="share_##key##_name" name="share[##key##][name]" class="extend" value="" placeholder="<?php echo Minz_Translate::t ('share_name'); ?>" size="64" /> - <input type="url" id="share_##key##_url" name="share[##key##][url]" class="extend" value="" placeholder="<?php echo Minz_Translate::t ('share_url'); ?>" size="64" /> - <a href="#" class="remove btn btn-attention" data-remove="group-share-##key##"><?php echo FreshRSS_Themes::icon('close'); ?></a></div> - <a target="_blank" class="btn" title="<?php echo Minz_Translate::t('more_information'); ?>" href="##help##"><?php echo FreshRSS_Themes::icon('help'); ?></a> + <input type="text" id="share_##key##_name" name="share[##key##][name]" class="extend" value="" placeholder="<?php echo _t('share_name'); ?>" size="64" /> + <input type="url" id="share_##key##_url" name="share[##key##][url]" class="extend" value="" placeholder="<?php echo _t('share_url'); ?>" size="64" /> + <a href="#" class="remove btn btn-attention" data-remove="group-share-##key##"><?php echo _i('close'); ?></a></div> + <a target="_blank" class="btn" title="<?php echo _t('more_information'); ?>" href="##help##"><?php echo _i('help'); ?></a> </div></div>'> - <legend><?php echo Minz_Translate::t ('sharing'); ?></legend> - <?php foreach ($this->conf->sharing as $key => $sharing): ?> - <?php $share = $this->conf->shares[$sharing['type']]; ?> + <legend><?php echo _t('sharing'); ?></legend> + <?php foreach (FreshRSS_Context::$conf->sharing as $key => $sharing): ?> + <?php $share = FreshRSS_Context::$conf->shares[$sharing['type']]; ?> <div class="form-group" id="group-share-<?php echo $key; ?>"> <label class="group-name"> - <?php echo Minz_Translate::t ($sharing['type']); ?> + <?php echo _t($sharing['type']); ?> </label> <div class="group-controls"> <input type='hidden' id='share_<?php echo $key;?>_type' name="share[<?php echo $key;?>][type]" value='<?php echo $sharing['type']?>' /> <?php if ($share['form'] === 'advanced') { ?> <div class="stick"> - <input type="text" id="share_<?php echo $key;?>_name" name="share[<?php echo $key;?>][name]" class="extend" value="<?php echo $sharing['name']?>" placeholder="<?php echo Minz_Translate::t ('share_name'); ?>" size="64" /> - <input type="url" id="share_<?php echo $key;?>_url" name="share[<?php echo $key;?>][url]" class="extend" value="<?php echo $sharing['url']?>" placeholder="<?php echo Minz_Translate::t ('share_url'); ?>" size="64" /> - <a href='#' class='remove btn btn-attention' data-remove="group-share-<?php echo $key; ?>"><?php echo FreshRSS_Themes::icon('close'); ?></a> + <input type="text" id="share_<?php echo $key;?>_name" name="share[<?php echo $key;?>][name]" class="extend" value="<?php echo $sharing['name']?>" placeholder="<?php echo _t('share_name'); ?>" size="64" /> + <input type="url" id="share_<?php echo $key;?>_url" name="share[<?php echo $key;?>][url]" class="extend" value="<?php echo $sharing['url']?>" placeholder="<?php echo _t('share_url'); ?>" size="64" /> + <a href='#' class='remove btn btn-attention' data-remove="group-share-<?php echo $key; ?>"><?php echo _i('close'); ?></a> </div> - <a target="_blank" class="btn" title="<?php echo Minz_Translate::t('more_information'); ?>" href="<?php echo $share['help']?>"><?php echo FreshRSS_Themes::icon('help'); ?></a> + <a target="_blank" class="btn" title="<?php echo _t('more_information'); ?>" href="<?php echo $share['help']?>"><?php echo _i('help'); ?></a> <?php } else { ?> - <a href='#' class='remove btn btn-attention' data-remove="group-share-<?php echo $key; ?>"><?php echo FreshRSS_Themes::icon('close'); ?></a> + <a href='#' class='remove btn btn-attention' data-remove="group-share-<?php echo $key; ?>"><?php echo _i('close'); ?></a> <?php } ?> </div> </div> @@ -41,18 +41,18 @@ <div class="form-group"> <div class="group-controls"> <select> - <?php foreach($this->conf->shares as $key => $params):?> - <option value='<?php echo $key?>' data-form='<?php echo $params['form']?>' data-help='<?php if (!empty($params['help'])) {echo $params['help'];}?>'><?php echo Minz_Translate::t($key) ?></option> + <?php foreach(FreshRSS_Context::$conf->shares as $key => $params):?> + <option value='<?php echo $key?>' data-form='<?php echo $params['form']?>' data-help='<?php if (!empty($params['help'])) {echo $params['help'];}?>'><?php echo _t($key) ?></option> <?php endforeach; ?> </select> - <a href='#' class='share add btn'><?php echo FreshRSS_Themes::icon('add'); ?></a> + <a href='#' class='share add btn'><?php echo _i('add'); ?></a> </div> </div> <div class="form-group form-actions"> <div class="group-controls"> - <button type="submit" class="btn btn-important"><?php echo Minz_Translate::t ('save'); ?></button> - <button type="reset" class="btn"><?php echo Minz_Translate::t ('cancel'); ?></button> + <button type="submit" class="btn btn-important"><?php echo _t('save'); ?></button> + <button type="reset" class="btn"><?php echo _t('cancel'); ?></button> </div> </div> </form> diff --git a/app/views/configure/shortcut.phtml b/app/views/configure/shortcut.phtml index a4029b676..66a23877e 100644 --- a/app/views/configure/shortcut.phtml +++ b/app/views/configure/shortcut.phtml @@ -1,7 +1,7 @@ -<?php $this->partial ('aside_configure'); ?> +<?php $this->partial('aside_configure'); ?> <div class="post"> - <a href="<?php echo _url ('index', 'index'); ?>"><?php echo Minz_Translate::t ('back_to_rss_feeds'); ?></a> + <a href="<?php echo _url('index', 'index'); ?>"><?php echo _t('back_to_rss_feeds'); ?></a> <datalist id="keys"> <?php foreach ($this->list_keys as $key) { ?> @@ -9,110 +9,117 @@ <?php } ?> </datalist> - <?php $s = $this->conf->shortcuts; ?> + <?php $s = FreshRSS_Context::$conf->shortcuts; ?> - <form method="post" action="<?php echo _url ('configure', 'shortcut'); ?>"> - <legend><?php echo Minz_Translate::t ('shortcuts'); ?></legend> + <form method="post" action="<?php echo _url('configure', 'shortcut'); ?>"> + <legend><?php echo _t('shortcuts'); ?></legend> - <noscript><p class="alert alert-error"><?php echo Minz_Translate::t ('javascript_for_shortcuts'); ?></p></noscript> + <noscript><p class="alert alert-error"><?php echo _t('javascript_for_shortcuts'); ?></p></noscript> - <legend><?php echo Minz_Translate::t ('shortcuts_navigation'); ?></legend> + <legend><?php echo _t('shortcuts_navigation'); ?></legend> + + <p class="alert alert-warn"><?php echo _t('shortcuts_navigation_help');?></p> <div class="form-group"> - <label class="group-name" for="next_entry"><?php echo Minz_Translate::t ('next_article'); ?></label> + <label class="group-name" for="next_entry"><?php echo _t('next_article'); ?></label> <div class="group-controls"> <input type="text" id="next_entry" name="shortcuts[next_entry]" list="keys" value="<?php echo $s['next_entry']; ?>" /> </div> </div> <div class="form-group"> - <label class="group-name" for="prev_entry"><?php echo Minz_Translate::t ('previous_article'); ?></label> + <label class="group-name" for="prev_entry"><?php echo _t('previous_article'); ?></label> <div class="group-controls"> <input type="text" id="prev_entry" name="shortcuts[prev_entry]" list="keys" value="<?php echo $s['prev_entry']; ?>" /> </div> </div> <div class="form-group"> - <label class="group-name" for="first_entry"><?php echo Minz_Translate::t ('first_article'); ?></label> + <label class="group-name" for="first_entry"><?php echo _t('first_article'); ?></label> <div class="group-controls"> <input type="text" id="first_entry" name="shortcuts[first_entry]" list="keys" value="<?php echo $s['first_entry']; ?>" /> </div> </div> <div class="form-group"> - <label class="group-name" for="last_entry"><?php echo Minz_Translate::t ('last_article'); ?></label> + <label class="group-name" for="last_entry"><?php echo _t('last_article'); ?></label> <div class="group-controls"> <input type="text" id="last_entry" name="shortcuts[last_entry]" list="keys" value="<?php echo $s['last_entry']; ?>" /> </div> </div> - <div><?php echo Minz_Translate::t ('shortcuts_navigation_help');?></div> - - <legend><?php echo Minz_Translate::t ('shortcuts_article_action');?></legend> + <legend><?php echo _t('shortcuts_article_action');?></legend> <div class="form-group"> - <label class="group-name" for="mark_read"><?php echo Minz_Translate::t ('mark_read'); ?></label> + <label class="group-name" for="mark_read"><?php echo _t('mark_read'); ?></label> <div class="group-controls"> <input type="text" id="mark_read" name="shortcuts[mark_read]" list="keys" value="<?php echo $s['mark_read']; ?>" /> - <?php echo Minz_Translate::t ('shift_for_all_read'); ?> + <?php echo _t('shift_for_all_read'); ?> </div> </div> <div class="form-group"> - <label class="group-name" for="mark_favorite"><?php echo Minz_Translate::t ('mark_favorite'); ?></label> + <label class="group-name" for="mark_favorite"><?php echo _t('mark_favorite'); ?></label> <div class="group-controls"> <input type="text" id="mark_favorite" name="shortcuts[mark_favorite]" list="keys" value="<?php echo $s['mark_favorite']; ?>" /> </div> </div> <div class="form-group"> - <label class="group-name" for="go_website"><?php echo Minz_Translate::t ('see_on_website'); ?></label> + <label class="group-name" for="go_website"><?php echo _t('see_on_website'); ?></label> <div class="group-controls"> <input type="text" id="go_website" name="shortcuts[go_website]" list="keys" value="<?php echo $s['go_website']; ?>" /> </div> </div> <div class="form-group"> - <label class="group-name" for="auto_share_shortcut"><?php echo Minz_Translate::t ('auto_share'); ?></label> + <label class="group-name" for="auto_share_shortcut"><?php echo _t('auto_share'); ?></label> <div class="group-controls"> <input type="text" id="auto_share_shortcut" name="shortcuts[auto_share]" list="keys" value="<?php echo $s['auto_share']; ?>" /> - <?php echo Minz_Translate::t ('auto_share_help'); ?> + <?php echo _t('auto_share_help'); ?> </div> </div> <div class="form-group"> - <label class="group-name" for="collapse_entry"><?php echo Minz_Translate::t ('collapse_article'); ?></label> + <label class="group-name" for="collapse_entry"><?php echo _t('collapse_article'); ?></label> <div class="group-controls"> <input type="text" id="collapse_entry" name="shortcuts[collapse_entry]" list="keys" value="<?php echo $s['collapse_entry']; ?>" /> </div> </div> - <legend><?php echo Minz_Translate::t ('shortcuts_other_action');?></legend> + <legend><?php echo _t('shortcuts_other_action');?></legend> <div class="form-group"> - <label class="group-name" for="load_more_shortcut"><?php echo Minz_Translate::t ('load_more'); ?></label> + <label class="group-name" for="load_more_shortcut"><?php echo _t('load_more'); ?></label> <div class="group-controls"> <input type="text" id="load_more_shortcut" name="shortcuts[load_more]" list="keys" value="<?php echo $s['load_more']; ?>" /> </div> </div> <div class="form-group"> - <label class="group-name" for="focus_search_shortcut"><?php echo Minz_Translate::t ('focus_search'); ?></label> + <label class="group-name" for="focus_search_shortcut"><?php echo _t('focus_search'); ?></label> <div class="group-controls"> <input type="text" id="focus_search_shortcut" name="shortcuts[focus_search]" list="keys" value="<?php echo $s['focus_search']; ?>" /> </div> </div> <div class="form-group"> - <label class="group-name" for="user_filter_shortcut"><?php echo Minz_Translate::t ('user_filter'); ?></label> + <label class="group-name" for="user_filter_shortcut"><?php echo _t('user_filter'); ?></label> <div class="group-controls"> <input type="text" id="user_filter_shortcut" name="shortcuts[user_filter]" list="keys" value="<?php echo $s['user_filter']; ?>" /> - <?php echo Minz_Translate::t ('user_filter_help'); ?> + <?php echo _t('user_filter_help'); ?> + </div> + </div> + + <div class="form-group"> + <label class="group-name" for="close_dropdown_shortcut"><?php echo _t('close_dropdown'); ?></label> + <div class="group-controls"> + <input type="text" id="help_shortcut" name="shortcuts[close_dropdown]" list="keys" value="<?php echo $s['close_dropdown']; ?>" /> </div> </div> <div class="form-group"> - <label class="group-name" for="help_shortcut"><?php echo Minz_Translate::t ('help'); ?></label> + <label class="group-name" for="help_shortcut"><?php echo _t('help'); ?></label> <div class="group-controls"> <input type="text" id="help_shortcut" name="shortcuts[help]" list="keys" value="<?php echo $s['help']; ?>" /> </div> @@ -120,8 +127,8 @@ <div class="form-group form-actions"> <div class="group-controls"> - <button type="submit" class="btn btn-important"><?php echo Minz_Translate::t ('save'); ?></button> - <button type="reset" class="btn"><?php echo Minz_Translate::t ('cancel'); ?></button> + <button type="submit" class="btn btn-important"><?php echo _t('save'); ?></button> + <button type="reset" class="btn"><?php echo _t('cancel'); ?></button> </div> </div> </form> diff --git a/app/views/configure/users.phtml b/app/views/configure/users.phtml deleted file mode 100644 index 272896fb2..000000000 --- a/app/views/configure/users.phtml +++ /dev/null @@ -1,211 +0,0 @@ -<?php $this->partial('aside_configure'); ?> - -<div class="post"> - <a href="<?php echo _url('index', 'index'); ?>"><?php echo Minz_Translate::t('back_to_rss_feeds'); ?></a> - - <form method="post" action="<?php echo _url('users', 'auth'); ?>"> - <legend><?php echo Minz_Translate::t('login_configuration'); ?></legend> - - <div class="form-group"> - <label class="group-name" for="current_user"><?php echo Minz_Translate::t('current_user'); ?></label> - <div class="group-controls"> - <input id="current_user" type="text" disabled="disabled" value="<?php echo Minz_Session::param('currentUser', '_'); ?>" /> - <label class="checkbox" for="is_admin"> - <input type="checkbox" id="is_admin" disabled="disabled" <?php echo Minz_Configuration::isAdmin(Minz_Session::param('currentUser', '_')) ? 'checked="checked" ' : ''; ?>/> - <?php echo Minz_Translate::t('is_admin'); ?> - </label> - </div> - </div> - - <div class="form-group"> - <label class="group-name" for="passwordPlain"><?php echo Minz_Translate::t('password_form'); ?></label> - <div class="group-controls"> - <div class="stick"> - <input type="password" id="passwordPlain" name="passwordPlain" autocomplete="off" pattern=".{7,}" <?php echo cryptAvailable() ? '' : 'disabled="disabled" '; ?>/> - <a class="btn toggle-password"><?php echo FreshRSS_Themes::icon('key'); ?></a> - </div> - <noscript><b><?php echo Minz_Translate::t('javascript_should_be_activated'); ?></b></noscript> - </div> - </div> - - <?php if (Minz_Configuration::apiEnabled()) { ?> - <div class="form-group"> - <label class="group-name" for="apiPasswordPlain"><?php echo Minz_Translate::t('password_api'); ?></label> - <div class="group-controls"> - <div class="stick"> - <input type="password" id="apiPasswordPlain" name="apiPasswordPlain" autocomplete="off" pattern=".{7,}" <?php echo cryptAvailable() ? '' : 'disabled="disabled" '; ?>/> - <a class="btn toggle-password"><?php echo FreshRSS_Themes::icon('key'); ?></a> - </div> - </div> - </div> - <?php } ?> - - <div class="form-group"> - <label class="group-name" for="mail_login"><?php echo Minz_Translate::t('persona_connection_email'); ?></label> - <?php $mail = $this->conf->mail_login; ?> - <div class="group-controls"> - <input type="email" id="mail_login" name="mail_login" class="extend" autocomplete="off" value="<?php echo $mail; ?>" <?php echo Minz_Configuration::isAdmin(Minz_Session::param('currentUser', '_')) ? '' : 'disabled="disabled"'; ?> placeholder="alice@example.net" /> - <noscript><b><?php echo Minz_Translate::t('javascript_should_be_activated'); ?></b></noscript> - </div> - </div> - - <div class="form-group form-actions"> - <div class="group-controls"> - <button type="submit" class="btn btn-important"><?php echo Minz_Translate::t('save'); ?></button> - <button type="reset" class="btn"><?php echo Minz_Translate::t('cancel'); ?></button> - </div> - </div> - - <?php if (Minz_Configuration::isAdmin(Minz_Session::param('currentUser', '_'))) { ?> - - <legend><?php echo Minz_Translate::t('auth_type'); ?></legend> - - <div class="form-group"> - <label class="group-name" for="auth_type"><?php echo Minz_Translate::t('auth_type'); ?></label> - <div class="group-controls"> - <select id="auth_type" name="auth_type" required="required"> - <?php if (!in_array(Minz_Configuration::authType(), array('form', 'persona', 'http_auth', 'none'))) { ?> - <option selected="selected"></option> - <?php } ?> - <option value="form"<?php echo Minz_Configuration::authType() === 'form' ? ' selected="selected"' : '', cryptAvailable() ? '' : ' disabled="disabled"'; ?>><?php echo Minz_Translate::t('auth_form'); ?></option> - <option value="persona"<?php echo Minz_Configuration::authType() === 'persona' ? ' selected="selected"' : '', $this->conf->mail_login == '' ? ' disabled="disabled"' : ''; ?>><?php echo Minz_Translate::t('auth_persona'); ?></option> - <option value="http_auth"<?php echo Minz_Configuration::authType() === 'http_auth' ? ' selected="selected"' : '', httpAuthUser() == '' ? ' disabled="disabled"' : ''; ?>><?php echo Minz_Translate::t('http_auth'); ?> (REMOTE_USER = '<?php echo httpAuthUser(); ?>')</option> - <option value="none"<?php echo Minz_Configuration::authType() === 'none' ? ' selected="selected"' : ''; ?>><?php echo Minz_Translate::t('auth_none'); ?></option> - </select> - </div> - </div> - - <div class="form-group"> - <div class="group-controls"> - <label class="checkbox" for="anon_access"> - <input type="checkbox" name="anon_access" id="anon_access" value="1"<?php echo Minz_Configuration::allowAnonymous() ? ' checked="checked"' : '', - Minz_Configuration::canLogIn() ? '' : ' disabled="disabled"'; ?> /> - <?php echo Minz_Translate::t('allow_anonymous', Minz_Configuration::defaultUser()); ?> - </label> - </div> - </div> - - <div class="form-group"> - <div class="group-controls"> - <label class="checkbox" for="anon_refresh"> - <input type="checkbox" name="anon_refresh" id="anon_refresh" value="1"<?php echo Minz_Configuration::allowAnonymousRefresh() ? ' checked="checked"' : '', - Minz_Configuration::canLogIn() ? '' : ' disabled="disabled"'; ?> /> - <?php echo Minz_Translate::t('allow_anonymous_refresh'); ?> - </label> - </div> - </div> - - <div class="form-group"> - <div class="group-controls"> - <label class="checkbox" for="unsafe_autologin"> - <input type="checkbox" name="unsafe_autologin" id="unsafe_autologin" value="1"<?php echo Minz_Configuration::unsafeAutologinEnabled() ? ' checked="checked"' : '', - Minz_Configuration::canLogIn() ? '' : ' disabled="disabled"'; ?> /> - <?php echo Minz_Translate::t('unsafe_autologin'); ?> - <kbd>p/i/?a=formLogin&u=Alice&p=1234</kbd> - </label> - </div> - </div> - - <?php if (Minz_Configuration::canLogIn()) { ?> - <div class="form-group"> - <label class="group-name" for="token"><?php echo Minz_Translate::t('auth_token'); ?></label> - <?php $token = $this->conf->token; ?> - <div class="group-controls"> - <input type="text" id="token" name="token" value="<?php echo $token; ?>" placeholder="<?php echo Minz_Translate::t('blank_to_disable'); ?>"<?php - echo Minz_Configuration::canLogIn() ? '' : ' disabled="disabled"'; ?> /> - <?php echo FreshRSS_Themes::icon('help'); ?> <?php echo Minz_Translate::t('explain_token', Minz_Url::display(null, 'html', true), $token); ?> - </div> - </div> - <?php } ?> - - <div class="form-group"> - <div class="group-controls"> - <label class="checkbox" for="api_enabled"> - <input type="checkbox" name="api_enabled" id="api_enabled" value="1"<?php echo Minz_Configuration::apiEnabled() ? ' checked="checked"' : '', - Minz_Configuration::needsLogin() ? '' : ' disabled="disabled"'; ?> /> - <?php echo Minz_Translate::t('api_enabled'); ?> - </label> - </div> - </div> - - <div class="form-group form-actions"> - <div class="group-controls"> - <button type="submit" class="btn btn-important"><?php echo Minz_Translate::t('save'); ?></button> - <button type="reset" class="btn"><?php echo Minz_Translate::t('cancel'); ?></button> - </div> - </div> - </form> - - <form method="post" action="<?php echo _url('users', 'delete'); ?>"> - <legend><?php echo Minz_Translate::t('users'); ?></legend> - - <div class="form-group"> - <label class="group-name" for="users_list"><?php echo Minz_Translate::t('users_list'); ?></label> - <div class="group-controls"> - <select id="users_list" name="username"><?php - foreach (listUsers() as $user) { - echo '<option>', $user, '</option>'; - } - ?></select> - </div> - </div> - - <div class="form-group form-actions"> - <div class="group-controls"> - <button type="submit" class="btn btn-attention confirm"><?php echo Minz_Translate::t('delete'); ?></button> - </div> - </div> - </form> - - <form method="post" action="<?php echo _url('users', 'create'); ?>"> - <legend><?php echo Minz_Translate::t('create_user'); ?></legend> - - <div class="form-group"> - <label class="group-name" for="new_user_language"><?php echo Minz_Translate::t ('language'); ?></label> - <div class="group-controls"> - <select name="new_user_language" id="new_user_language"> - <?php $languages = $this->conf->availableLanguages (); ?> - <?php foreach ($languages as $short => $lib) { ?> - <option value="<?php echo $short; ?>"<?php echo $this->conf->language === $short ? ' selected="selected"' : ''; ?>><?php echo $lib; ?></option> - <?php } ?> - </select> - </div> - </div> - - <div class="form-group"> - <label class="group-name" for="new_user_name"><?php echo Minz_Translate::t('username'); ?></label> - <div class="group-controls"> - <input id="new_user_name" name="new_user_name" type="text" size="16" required="required" maxlength="16" autocomplete="off" pattern="[0-9a-zA-Z]{1,16}" placeholder="demo" /> - </div> - </div> - - <div class="form-group"> - <label class="group-name" for="new_user_passwordPlain"><?php echo Minz_Translate::t('password_form'); ?></label> - <div class="group-controls"> - <div class="stick"> - <input type="password" id="new_user_passwordPlain" name="new_user_passwordPlain" autocomplete="off" pattern=".{7,}" /> - <a class="btn toggle-password"><?php echo FreshRSS_Themes::icon('key'); ?></a> - </div> - <noscript><b><?php echo Minz_Translate::t('javascript_should_be_activated'); ?></b></noscript> - </div> - </div> - - <div class="form-group"> - <label class="group-name" for="new_user_email"><?php echo Minz_Translate::t('persona_connection_email'); ?></label> - <?php $mail = $this->conf->mail_login; ?> - <div class="group-controls"> - <input type="email" id="new_user_email" name="new_user_email" class="extend" autocomplete="off" placeholder="alice@example.net" /> - </div> - </div> - - <div class="form-group form-actions"> - <div class="group-controls"> - <button type="submit" class="btn btn-important"><?php echo Minz_Translate::t('create'); ?></button> - <button type="reset" class="btn"><?php echo Minz_Translate::t('cancel'); ?></button> - </div> - </div> - - </form> - - <?php } ?> -</div> diff --git a/app/views/entry/bookmark.phtml b/app/views/entry/bookmark.phtml index c1fc32b7f..c346d2c4c 100755 --- a/app/views/entry/bookmark.phtml +++ b/app/views/entry/bookmark.phtml @@ -1,16 +1,16 @@ <?php header('Content-Type: application/json; charset=UTF-8'); -if (Minz_Request::param ('is_favorite', true)) { - Minz_Request::_param ('is_favorite', 0); +if (Minz_Request::param('is_favorite', true)) { + Minz_Request::_param('is_favorite', 0); } else { - Minz_Request::_param ('is_favorite', 1); + Minz_Request::_param('is_favorite', 1); } -$url = Minz_Url::display (array ( - 'c' => Minz_Request::controllerName (), - 'a' => Minz_Request::actionName (), - 'params' => Minz_Request::params (), +$url = Minz_Url::display(array( + 'c' => Minz_Request::controllerName(), + 'a' => Minz_Request::actionName(), + 'params' => Minz_Request::params(), )); -echo json_encode (array ('url' => str_ireplace ('&', '&', $url), 'icon' => FreshRSS_Themes::icon(Minz_Request::param ('is_favorite') ? 'non-starred' : 'starred'))); +echo json_encode(array('url' => str_ireplace('&', '&', $url), 'icon' => _i(Minz_Request::param('is_favorite') ? 'non-starred' : 'starred'))); diff --git a/app/views/entry/read.phtml b/app/views/entry/read.phtml index 9e79d4c07..fabdec9e0 100755 --- a/app/views/entry/read.phtml +++ b/app/views/entry/read.phtml @@ -1,16 +1,16 @@ <?php header('Content-Type: application/json; charset=UTF-8'); -if (Minz_Request::param ('is_read', true)) { - Minz_Request::_param ('is_read', 0); +if (Minz_Request::param('is_read', true)) { + Minz_Request::_param('is_read', 0); } else { - Minz_Request::_param ('is_read', 1); + Minz_Request::_param('is_read', 1); } -$url = Minz_Url::display (array ( - 'c' => Minz_Request::controllerName (), - 'a' => Minz_Request::actionName (), - 'params' => Minz_Request::params (), +$url = Minz_Url::display(array( + 'c' => Minz_Request::controllerName(), + 'a' => Minz_Request::actionName(), + 'params' => Minz_Request::params(), )); -echo json_encode (array ('url' => str_ireplace ('&', '&', $url), 'icon' => FreshRSS_Themes::icon(Minz_Request::param ('is_read') ? 'unread' : 'read'))); +echo json_encode(array('url' => str_ireplace('&', '&', $url), 'icon' => _i(Minz_Request::param('is_read') ? 'unread' : 'read'))); diff --git a/app/views/error/index.phtml b/app/views/error/index.phtml index ef4fbd39d..5e1949800 100644 --- a/app/views/error/index.phtml +++ b/app/views/error/index.phtml @@ -3,7 +3,7 @@ <h1 class="alert-head"><?php echo $this->code; ?></h1> <p> <?php echo $this->errorMessage; ?><br /> - <a href="<?php echo _url('index', 'index'); ?>"><?php echo Minz_Translate::t('back_to_rss_feeds'); ?></a> + <a href="<?php echo _url('index', 'index'); ?>"><?php echo _t('back_to_rss_feeds'); ?></a> </p> </div> </div> diff --git a/app/views/feed/add.phtml b/app/views/feed/add.phtml index 849dacac6..1db053b52 100644 --- a/app/views/feed/add.phtml +++ b/app/views/feed/add.phtml @@ -1,16 +1,16 @@ <?php if ($this->feed) { ?> <div class="post"> - <h1><?php echo Minz_Translate::t ('add_rss_feed'); ?></h1> + <h1><?php echo _t('add_rss_feed'); ?></h1> <?php if (!$this->load_ok) { ?> - <p class="alert alert-error"><span class="alert-head"><?php echo Minz_Translate::t('damn'); ?></span> <?php echo Minz_Translate::t('internal_problem_feed', _url('index', 'logs')); ?></p> + <p class="alert alert-error"><span class="alert-head"><?php echo _t('damn'); ?></span> <?php echo _t('internal_problem_feed', _url('index', 'logs')); ?></p> <?php } ?> <form method="post" action="<?php echo _url('feed', 'add'); ?>" autocomplete="off"> - <legend><?php echo Minz_Translate::t('informations'); ?></legend> + <legend><?php echo _t('informations'); ?></legend> <?php if ($this->load_ok) { ?> <div class="form-group"> - <label class="group-name"><?php echo Minz_Translate::t('title'); ?></label> + <label class="group-name"><?php echo _t('title'); ?></label> <div class="group-controls"> <label><?php echo $this->feed->name() ; ?></label> </div> @@ -18,7 +18,7 @@ <?php $desc = $this->feed->description(); if ($desc != '') { ?> <div class="form-group"> - <label class="group-name"><?php echo Minz_Translate::t('feed_description'); ?></label> + <label class="group-name"><?php echo _t('feed_description'); ?></label> <div class="group-controls"> <label><?php echo htmlspecialchars($desc, ENT_NOQUOTES, 'UTF-8'); ?></label> </div> @@ -26,26 +26,26 @@ <?php } ?> <div class="form-group"> - <label class="group-name"><?php echo Minz_Translate::t('website_url'); ?></label> + <label class="group-name"><?php echo _t('website_url'); ?></label> <div class="group-controls"> <?php echo $this->feed->website(); ?> - <a class="btn" target="_blank" href="<?php echo $this->feed->website(); ?>"><?php echo FreshRSS_Themes::icon('link'); ?></a> + <a class="btn" target="_blank" href="<?php echo $this->feed->website(); ?>"><?php echo _i('link'); ?></a> </div> </div> <?php } ?> <div class="form-group"> - <label class="group-name" for="url"><?php echo Minz_Translate::t('feed_url'); ?></label> + <label class="group-name" for="url"><?php echo _t('feed_url'); ?></label> <div class="group-controls"> <div class="stick"> <input type="text" name="url_rss" id="url" class="extend" value="<?php echo $this->feed->url(); ?>" /> - <a class="btn" target="_blank" href="<?php echo $this->feed->url(); ?>"><?php echo FreshRSS_Themes::icon('link'); ?></a> + <a class="btn" target="_blank" href="<?php echo $this->feed->url(); ?>"><?php echo _i('link'); ?></a> </div> - <a class="btn" target="_blank" href="http://validator.w3.org/feed/check.cgi?url=<?php echo $this->feed->url(); ?>"><?php echo Minz_Translate::t('feed_validator'); ?></a> + <a class="btn" target="_blank" href="http://validator.w3.org/feed/check.cgi?url=<?php echo $this->feed->url(); ?>"><?php echo _t('feed_validator'); ?></a> </div> </div> <div class="form-group"> - <label class="group-name" for="category"><?php echo Minz_Translate::t('category'); ?></label> + <label class="group-name" for="category"><?php echo _t('category'); ?></label> <div class="group-controls"> <select name="category" id="category"> <?php foreach ($this->categories as $cat) { ?> @@ -53,37 +53,37 @@ <?php echo $cat->name(); ?> </option> <?php } ?> - <option value="nc"><?php echo Minz_Translate::t('new_category'); ?></option> + <option value="nc"><?php echo _t('new_category'); ?></option> </select> <span style="display: none;"> - <input type="text" name="new_category[name]" id="new_category_name" autocomplete="off" placeholder="<?php echo Minz_Translate::t('new_category'); ?>" /> + <input type="text" name="new_category[name]" id="new_category_name" autocomplete="off" placeholder="<?php echo _t('new_category'); ?>" /> </span> </div> </div> - <legend><?php echo Minz_Translate::t('http_authentication'); ?></legend> + <legend><?php echo _t('http_authentication'); ?></legend> <?php $auth = $this->feed->httpAuth(false); ?> <div class="form-group"> - <label class="group-name" for="http_user"><?php echo Minz_Translate::t('http_username'); ?></label> + <label class="group-name" for="http_user"><?php echo _t('http_username'); ?></label> <div class="group-controls"> <input type="text" name="http_user" id="http_user" class="extend" value="<?php echo $auth['username']; ?>" autocomplete="off" /> </div> - <label class="group-name" for="http_pass"><?php echo Minz_Translate::t('http_password'); ?></label> + <label class="group-name" for="http_pass"><?php echo _t('http_password'); ?></label> <div class="group-controls"> <input type="password" name="http_pass" id="http_pass" class="extend" value="<?php echo $auth['password']; ?>" autocomplete="off" /> </div> <div class="group-controls"> - <?php echo FreshRSS_Themes::icon('help'); ?> <?php echo Minz_Translate::t('access_protected_feeds'); ?> + <?php echo _i('help'); ?> <?php echo _t('access_protected_feeds'); ?> </div> </div> <div class="form-group form-actions"> <div class="group-controls"> - <button type="submit" class="btn btn-important"><?php echo Minz_Translate::t('save'); ?></button> - <button type="reset" class="btn"><?php echo Minz_Translate::t('cancel'); ?></button> + <button type="submit" class="btn btn-important"><?php echo _t('save'); ?></button> + <button type="reset" class="btn"><?php echo _t('cancel'); ?></button> </div> </div> </form> diff --git a/app/views/feed/move.phtml b/app/views/feed/move.phtml new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/app/views/feed/move.phtml diff --git a/app/views/helpers/feed/update.phtml b/app/views/helpers/feed/update.phtml new file mode 100644 index 000000000..d79ef2666 --- /dev/null +++ b/app/views/helpers/feed/update.phtml @@ -0,0 +1,174 @@ +<div class="post"> + <h1><?php echo $this->feed->name(); ?></h1> + + <div> + <a href="<?php echo _url('index', 'index', 'get', 'f_' . $this->feed->id()); ?>"><?php echo _i('link'); ?> <?php echo _t('filter'); ?></a> + <?php echo _t('or'); ?> + <a href="<?php echo _url('stats', 'repartition', 'id', $this->feed->id()); ?>"><?php echo _i('stats'); ?> <?php echo _t('stats'); ?></a> + </div> + + <p><?php echo $this->feed->description(); ?></p> + + <?php $nbEntries = $this->feed->nbEntries(); ?> + + <?php if ($this->feed->inError()) { ?> + <p class="alert alert-error"><span class="alert-head"><?php echo _t('damn'); ?></span> <?php echo _t('feed_in_error'); ?></p> + <?php } elseif ($nbEntries === 0) { ?> + <p class="alert alert-warn"><?php echo _t('feed_empty'); ?></p> + <?php } ?> + + <form method="post" action="<?php echo _url('subscription', 'feed', 'id', $this->feed->id()); ?>" autocomplete="off"> + <legend><?php echo _t('informations'); ?></legend> + <div class="form-group"> + <label class="group-name" for="name"><?php echo _t('title'); ?></label> + <div class="group-controls"> + <input type="text" name="name" id="name" class="extend" value="<?php echo $this->feed->name() ; ?>" /> + </div> + </div> + <div class="form-group"> + <label class="group-name" for="description"><?php echo _t('feed_description'); ?></label> + <div class="group-controls"> + <textarea name="description" id="description"><?php echo htmlspecialchars($this->feed->description(), ENT_NOQUOTES, 'UTF-8'); ?></textarea> + </div> + </div> + <div class="form-group"> + <label class="group-name" for="website"><?php echo _t('website_url'); ?></label> + <div class="group-controls"> + <div class="stick"> + <input type="text" name="website" id="website" class="extend" value="<?php echo $this->feed->website(); ?>" /> + <a class="btn" target="_blank" href="<?php echo $this->feed->website(); ?>"><?php echo _i('link'); ?></a> + </div> + </div> + </div> + <div class="form-group"> + <label class="group-name" for="url"><?php echo _t('feed_url'); ?></label> + <div class="group-controls"> + <div class="stick"> + <input type="text" name="url" id="url" class="extend" value="<?php echo $this->feed->url(); ?>" /> + <a class="btn" target="_blank" href="<?php echo $this->feed->url(); ?>"><?php echo _i('link'); ?></a> + </div> + + <a class="btn" target="_blank" href="http://validator.w3.org/feed/check.cgi?url=<?php echo $this->feed->url(); ?>"><?php echo _t('feed_validator'); ?></a> + </div> + </div> + <div class="form-group"> + <label class="group-name" for="category"><?php echo _t('category'); ?></label> + <div class="group-controls"> + <select name="category" id="category"> + <?php foreach ($this->categories as $cat) { ?> + <option value="<?php echo $cat->id(); ?>"<?php echo $cat->id()== $this->feed->category() ? ' selected="selected"' : ''; ?>> + <?php echo $cat->name(); ?> + </option> + <?php } ?> + </select> + </div> + </div> + <div class="form-group"> + <label class="group-name" for="priority"><?php echo _t('show_in_all_flux'); ?></label> + <div class="group-controls"> + <label class="checkbox" for="priority"> + <input type="checkbox" name="priority" id="priority" value="10"<?php echo $this->feed->priority() > 0 ? ' checked="checked"' : ''; ?> /> + <?php echo _t('yes'); ?> + </label> + </div> + </div> + + <div class="form-group form-actions"> + <div class="group-controls"> + <button class="btn btn-important"><?php echo _t('save'); ?></button> + <button class="btn btn-attention confirm" + data-str-confirm="<?php echo _t('confirm_action_feed_cat'); ?>" + formaction="<?php echo _url('feed', 'delete', 'id', $this->feed->id()); ?>" + formmethod="post"><?php echo _t('delete'); ?></button> + </div> + </div> + + <legend><?php echo _t('archiving_configuration'); ?></legend> + + <div class="form-group"> + <div class="group-controls"> + <div class="stick"> + <input type="text" value="<?php echo _t('number_articles', $nbEntries); ?>" disabled="disabled" /> + <a class="btn" href="<?php echo _url('feed', 'actualize', 'id', $this->feed->id()); ?>"> + <?php echo _i('refresh'); ?> <?php echo _t('actualize'); ?> + </a> + </div> + </div> + </div> + <div class="form-group"> + <label class="group-name" for="keep_history"><?php echo _t('keep_history'); ?></label> + <div class="group-controls"> + <select class="number" name="keep_history" id="keep_history" required="required"><?php + foreach (array('' => '', -2 => _t('by_default'), 0 => '0', 10 => '10', 50 => '50', 100 => '100', 500 => '500', 1000 => '1 000', 5000 => '5 000', 10000 => '10 000', -1 => '∞') as $v => $t) { + echo '<option value="' . $v . ($this->feed->keepHistory() === $v ? '" selected="selected' : '') . '">' . $t . '</option>'; + } + ?></select> + </div> + </div> + <div class="form-group"> + <label class="group-name" for="ttl"><?php echo _t('ttl'); ?></label> + <div class="group-controls"> + <select class="number" name="ttl" id="ttl" required="required"><?php + $found = false; + foreach (array(-2 => _t('by_default'), 900 => '15min', 1200 => '20min', 1500 => '25min', 1800 => '30min', 2700 => '45min', + 3600 => '1h', 5400 => '1.5h', 7200 => '2h', 10800 => '3h', 14400 => '4h', 18800 => '5h', 21600 => '6h', 25200 => '7h', 28800 => '8h', + 36000 => '10h', 43200 => '12h', 64800 => '18h', + 86400 => '1d', 129600 => '1.5d', 172800 => '2d', 259200 => '3d', 345600 => '4d', 432000 => '5d', 518400 => '6d', + 604800 => '1wk', 1209600 => '2wk', 1814400 => '3wk', 2419200 => '4wk', 2629744 => '1mo', -1 => '∞') as $v => $t) { + echo '<option value="' . $v . ($this->feed->ttl() === $v ? '" selected="selected' : '') . '">' . $t . '</option>'; + if ($this->feed->ttl() == $v) { + $found = true; + } + } + if (!$found) { + echo '<option value="' . intval($this->feed->ttl()) . '" selected="selected">' . intval($this->feed->ttl()) . 's</option>'; + } + ?></select> + </div> + </div> + <div class="form-group form-actions"> + <div class="group-controls"> + <button class="btn btn-important"><?php echo _t('save'); ?></button> + <button class="btn btn-attention confirm" formmethod="post" formaction="<?php echo _url('feed', 'truncate', 'id', $this->feed->id()); ?>"><?php echo _t('truncate'); ?></button> + </div> + </div> + + <legend><?php echo _t('login_configuration'); ?></legend> + <?php $auth = $this->feed->httpAuth(false); ?> + <div class="form-group"> + <label class="group-name" for="http_user"><?php echo _t('http_username'); ?></label> + <div class="group-controls"> + <input type="text" name="http_user" id="http_user" class="extend" value="<?php echo $auth['username']; ?>" autocomplete="off" /> + <?php echo _i('help'); ?> <?php echo _t('access_protected_feeds'); ?> + </div> + + <label class="group-name" for="http_pass"><?php echo _t('http_password'); ?></label> + <div class="group-controls"> + <input type="password" name="http_pass" id="http_pass" class="extend" value="<?php echo $auth['password']; ?>" autocomplete="off" /> + </div> + </div> + + <div class="form-group form-actions"> + <div class="group-controls"> + <button type="submit" class="btn btn-important"><?php echo _t('save'); ?></button> + <button type="reset" class="btn"><?php echo _t('cancel'); ?></button> + </div> + </div> + + <legend><?php echo _t('advanced'); ?></legend> + <div class="form-group"> + <label class="group-name" for="path_entries"><?php echo _t('css_path_on_website'); ?></label> + <div class="group-controls"> + <input type="text" name="path_entries" id="path_entries" class="extend" value="<?php echo $this->feed->pathEntries(); ?>" placeholder="<?php echo _t('blank_to_disable'); ?>" /> + <?php echo _i('help'); ?> <?php echo _t('retrieve_truncated_feeds'); ?> + </div> + </div> + + <div class="form-group form-actions"> + <div class="group-controls"> + <button type="submit" class="btn btn-important"><?php echo _t('save'); ?></button> + <button type="reset" class="btn"><?php echo _t('cancel'); ?></button> + </div> + </div> + </form> +</div> diff --git a/app/views/helpers/javascript_vars.phtml b/app/views/helpers/javascript_vars.phtml index 1139eb446..8e9141d4e 100644 --- a/app/views/helpers/javascript_vars.phtml +++ b/app/views/helpers/javascript_vars.phtml @@ -1,21 +1,39 @@ +"use strict"; <?php -echo '"use strict";', "\n"; +$mark = FreshRSS_Context::$conf->mark_when; +$mail = Minz_Session::param('mail', false); +$auto_actualize = Minz_Session::param('actualize_feeds', false); +$hide_posts = (FreshRSS_Context::$conf->display_posts || + Minz_Request::param('output') === 'reader'); +$s = FreshRSS_Context::$conf->shortcuts; -$mark = $this->conf->mark_when; -echo 'var ', - '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', - ',auto_mark_scroll=', $mark['scroll'] ? 'true' : 'false', - ',auto_load_more=', $this->conf->auto_load_more ? 'true' : 'false', - ',does_lazyload=', $this->conf->lazyload ? 'true' : 'false', - ',sticky_post=', $this->conf->sticky_post ? 'true' : 'false'; +$url_login = Minz_Url::display(array( + 'c' => 'auth', + 'a' => 'login' +), 'php'); +$url_logout = Minz_Url::display(array( + 'c' => 'auth', + 'a' => 'logout' +), 'php'); -$s = $this->conf->shortcuts; -echo ',shortcuts={', +echo 'var context={', + 'hide_posts:', $hide_posts ? 'false' : 'true', ',', + 'display_order:"', Minz_Request::param('order', FreshRSS_Context::$conf->sort_order), '",', + 'auto_mark_article:', $mark['article'] ? 'true' : 'false', ',', + 'auto_mark_site:', $mark['site'] ? 'true' : 'false', ',', + 'auto_mark_scroll:', $mark['scroll'] ? 'true' : 'false', ',', + 'auto_load_more:', FreshRSS_Context::$conf->auto_load_more ? 'true' : 'false', ',', + 'auto_actualize_feeds:', $auto_actualize ? 'true' : 'false', ',', + 'does_lazyload:', FreshRSS_Context::$conf->lazyload ? 'true' : 'false', ',', + 'sticky_post:', FreshRSS_Context::$conf->sticky_post ? 'true' : 'false', ',', + 'html5_notif_timeout:', FreshRSS_Context::$conf->html5_notif_timeout, ',', + 'auth_type:"', Minz_Configuration::authType(), '",', + 'current_user_mail:', $mail ? ('"' . $mail . '"') : 'null', ',', + 'current_view:"', Minz_Request::param('output', 'normal'), '"', +"},\n"; + +echo 'shortcuts={', 'mark_read:"', $s['mark_read'], '",', 'mark_favorite:"', $s['mark_favorite'], '",', 'go_website:"', $s['go_website'], '",', @@ -28,34 +46,24 @@ echo ',shortcuts={', 'auto_share:"', $s['auto_share'], '",', 'focus_search:"', $s['focus_search'], '",', 'user_filter:"', $s['user_filter'], '",', - 'help:"', $s['help'], '"', + 'help:"', $s['help'], '",', + 'close_dropdown:"', $s['close_dropdown'], '"', "},\n"; -if (Minz_Request::param ('output') === 'global') { - echo "iconClose='", FreshRSS_Themes::icon('close'), "',\n"; -} - -$authType = Minz_Configuration::authType(); -if ($authType === 'persona') { - // If user is disconnected, current_user_mail MUST be null - $mail = Minz_Session::param ('mail', false); - if ($mail) { - echo 'current_user_mail="' . $mail . '",'; - } else { - echo 'current_user_mail=null,'; - } -} - -echo 'authType="', $authType, '",', - 'url_freshrss="', _url ('index', 'index'), '",', - 'url_login="', _url ('index', 'login'), '",', - 'url_logout="', _url ('index', 'logout'), '",'; - -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"; +echo 'url={', + 'index:"', _url('index', 'index'), '",', + 'login:"', $url_login, '",', + 'logout:"', $url_logout, '",', + 'help:"', FRESHRSS_WIKI, '"', +"},\n"; +echo 'i18n={', + 'confirmation_default:"', _t('confirm_action'), '",', + 'notif_title_articles:"', _t('notif_title_new_articles'), '",', + 'notif_body_articles:"', _t('notif_body_new_articles'), '",', + 'category_empty:"', _t('category_empty'), '"', +"},\n"; -$autoActualise = Minz_Session::param('actualize_feeds', false); -echo 'auto_actualize_feeds=', $autoActualise ? 'true' : 'false', ";\n"; +echo 'icons={', + 'close:\'', _i('close'), '\'', +"}\n";
\ No newline at end of file diff --git a/app/views/helpers/logs_pagination.phtml b/app/views/helpers/logs_pagination.phtml index e3d14810e..ad46279c7 100755 --- a/app/views/helpers/logs_pagination.phtml +++ b/app/views/helpers/logs_pagination.phtml @@ -1,7 +1,7 @@ <?php - $c = Minz_Request::controllerName (); - $a = Minz_Request::actionName (); - $params = Minz_Request::params (); + $c = Minz_Request::controllerName(); + $a = Minz_Request::actionName(); + $params = Minz_Request::params(); ?> <?php if ($this->nbPage > 1) { ?> @@ -9,14 +9,14 @@ <?php $params[$getteur] = 1; ?> <li class="item pager-first"> <?php if ($this->currentPage > 1) { ?> - <a href="<?php echo Minz_Url::display (array ('c' => $c, 'a' => $a, 'params' => $params)); ?>">« <?php echo Minz_Translate::t('first'); ?></a> + <a href="<?php echo Minz_Url::display(array('c' => $c, 'a' => $a, 'params' => $params)); ?>">« <?php echo _t('first'); ?></a> <?php } ?> </li> <?php $params[$getteur] = $this->currentPage - 1; ?> <li class="item pager-previous"> <?php if ($this->currentPage > 1) { ?> - <a href="<?php echo Minz_Url::display (array ('c' => $c, 'a' => $a, 'params' => $params)); ?>">‹ <?php echo Minz_Translate::t('previous'); ?></a> + <a href="<?php echo Minz_Url::display(array('c' => $c, 'a' => $a, 'params' => $params)); ?>">‹ <?php echo _t('previous'); ?></a> <?php } ?> </li> @@ -24,7 +24,7 @@ <?php if($i > 0 && $i <= $this->nbPage) { ?> <?php if ($i != $this->currentPage) { ?> <?php $params[$getteur] = $i; ?> - <li class="item pager-item"><a href="<?php echo Minz_Url::display (array ('c' => $c, 'a' => $a, 'params' => $params)); ?>"><?php echo $i; ?></a></li> + <li class="item pager-item"><a href="<?php echo Minz_Url::display(array('c' => $c, 'a' => $a, 'params' => $params)); ?>"><?php echo $i; ?></a></li> <?php } else { ?> <li class="item pager-current"><?php echo $i; ?></li> <?php } ?> @@ -34,13 +34,13 @@ <?php $params[$getteur] = $this->currentPage + 1; ?> <li class="item pager-next"> <?php if ($this->currentPage < $this->nbPage) { ?> - <a href="<?php echo Minz_Url::display (array ('c' => $c, 'a' => $a, 'params' => $params)); ?>"><?php echo Minz_Translate::t('next'); ?> ›</a> + <a href="<?php echo Minz_Url::display(array('c' => $c, 'a' => $a, 'params' => $params)); ?>"><?php echo _t('next'); ?> ›</a> <?php } ?> </li> <?php $params[$getteur] = $this->nbPage; ?> <li class="item pager-last"> <?php if ($this->currentPage < $this->nbPage) { ?> - <a href="<?php echo Minz_Url::display (array ('c' => $c, 'a' => $a, 'params' => $params)); ?>"><?php echo Minz_Translate::t('last'); ?> »</a> + <a href="<?php echo Minz_Url::display(array('c' => $c, 'a' => $a, 'params' => $params)); ?>"><?php echo _t('last'); ?> »</a> <?php } ?> </li> </ul> diff --git a/app/views/helpers/pagination.phtml b/app/views/helpers/pagination.phtml index cea338364..3ea6c3582 100755 --- a/app/views/helpers/pagination.phtml +++ b/app/views/helpers/pagination.phtml @@ -1,28 +1,32 @@ <?php - $c = Minz_Request::controllerName(); - $a = Minz_Request::actionName(); - $params = Minz_Request::params(); - $markReadUrl = Minz_Session::param('markReadUrl'); - Minz_Session::_param('markReadUrl', false); + $url_next = Minz_Request::currentRequest(); + $url_next['params']['next'] = FreshRSS_Context::$next_id; + $url_next['params']['ajax'] = 1; + + $url_mark_read = array( + 'c' => 'entry', + 'a' => 'read', + 'params' => array( + 'get' => FreshRSS_Context::currentGet(), + 'nextGet' => FreshRSS_Context::$next_get, + 'idMax' => FreshRSS_Context::$id_max, + ) + ); ?> <form id="mark-read-pagination" method="post" style="display: none"></form> <ul class="pagination"> <li class="item pager-next"> - <?php if (!empty($this->nextId)) { ?> - <?php - $params['next'] = $this->nextId; - $params['ajax'] = 1; - ?> - <a id="load_more" href="<?php echo Minz_Url::display(array('c' => $c, 'a' => $a, 'params' => $params)); ?>"> + <?php if (FreshRSS_Context::$next_id) { ?> + <a id="load_more" href="<?php echo Minz_Url::display($url_next); ?>"> <?php echo _t('load_more'); ?> </a> - <?php } elseif ($markReadUrl) { ?> + <?php } elseif ($url_mark_read) { ?> <button id="bigMarkAsRead" - class="as-link <?php echo $this->conf->reading_confirm ? 'confirm' : ''; ?>" + class="as-link <?php echo FreshRSS_Context::$conf->reading_confirm ? 'confirm' : ''; ?>" form="mark-read-pagination" - formaction="<?php echo $markReadUrl; ?>" + formaction="<?php echo Minz_Url::display($url_mark_read); ?>" type="submit"> <?php echo _t('nothing_to_load'); ?><br /> <span class="bigTick">✓</span><br /> diff --git a/app/views/helpers/view/global_view.phtml b/app/views/helpers/view/global_view.phtml deleted file mode 100644 index 72bcf4c73..000000000 --- a/app/views/helpers/view/global_view.phtml +++ /dev/null @@ -1,53 +0,0 @@ -<?php $this->partial ('nav_menu'); ?> - -<?php if (!empty($this->entries)) { ?> -<div id="stream" class="global categories"> -<?php - $arUrl = array('c' => 'index', 'a' => 'index', 'params' => array()); - if ($this->conf->view_mode !== 'normal') { - $arUrl['params']['output'] = 'normal'; - } - $p = Minz_Request::param('state', ''); - if (($p != '') && ($this->conf->default_view !== $p)) { - $arUrl['params']['state'] = $p; - } - - foreach ($this->cat_aside as $cat) { - $feeds = $cat->feeds (); - if (!empty ($feeds)) { -?> - <div class="box-category"> - <div class="category"> - <a data-unread="<?php echo formatNumber($cat->nbNotRead()); ?>" class="btn" href="<?php $arUrl['params']['get'] = 'c_' . $cat->id (); echo Minz_Url::display($arUrl); ?>"> - <?php echo $cat->name(); ?> - </a> - </div> - <ul class="feeds"> - <?php foreach ($feeds as $feed) { ?> - <?php $not_read = $feed->nbNotRead (); ?> - <li id="f_<?php echo $feed->id (); ?>" class="item<?php echo $feed->inError () ? ' error' : ''; ?><?php echo $feed->nbEntries () == 0 ? ' empty' : ''; ?>"> - <img class="favicon" src="<?php echo $feed->favicon (); ?>" alt="✇" /> - <a class="feed" data-unread="<?php echo formatNumber($feed->nbNotRead()); ?>" data-priority="<?php echo $feed->priority (); ?>" href="<?php $arUrl['params']['get'] = 'f_' . $feed->id(); echo Minz_Url::display($arUrl); ?>"> - <?php echo $feed->name(); ?> - </a> - </li> - <?php } ?> - </ul> - </div> -<?php - } - } -?> -</div> - -<div id="overlay"></div> -<div id="panel"<?php echo $this->conf->display_posts ? '' : ' class="hide_posts"'; ?>> - <a class="close" href="#"><?php echo FreshRSS_Themes::icon('close'); ?></a> -</div> - -<?php } else { ?> -<div id="stream" class="prompt alert alert-warn global"> - <h2><?php echo _t('no_feed_to_display'); ?></h2> - <a href="<?php echo _url('configure', 'feed'); ?>"><?php echo _t('think_to_add'); ?></a><br /><br /> -</div> -<?php } ?> diff --git a/app/views/helpers/view/reader_view.phtml b/app/views/helpers/view/reader_view.phtml deleted file mode 100644 index c80dca519..000000000 --- a/app/views/helpers/view/reader_view.phtml +++ /dev/null @@ -1,44 +0,0 @@ -<?php -$this->partial ('nav_menu'); - -if (!empty($this->entries)) { - $lazyload = $this->conf->lazyload; - $content_width = $this->conf->content_width; -?> - -<div id="stream" class="reader"> - <?php foreach ($this->entries as $item) { ?> - - <div class="flux<?php echo !$item->isRead () ? ' not_read' : ''; ?><?php echo $item->isFavorite () ? ' favorite' : ''; ?>" id="flux_<?php echo $item->id (); ?>"> - <div class="flux_content"> - <div class="content <?php echo $content_width; ?>"> - <?php - $feed = FreshRSS_CategoryDAO::findFeed($this->cat_aside, $item->feed ()); //We most likely already have the feed object in cache - if (empty($feed)) $feed = $item->feed (true); - ?> - <a href="<?php echo $item->link (); ?>"> - <img class="favicon" src="<?php echo $feed->favicon (); ?>" alt="✇" /> <span><?php echo $feed->name(); ?></span> - </a> - <h1 class="title"><?php echo $item->title (); ?></h1> - - <div class="author"><?php - $author = $item->author(); - echo $author != '' ? Minz_Translate::t('by_author', $author) . ' — ' : '', - $item->date(); - ?></div> - - <?php echo $item->content(); ?> - </div> - </div> - </div> - <?php } ?> - - <?php $this->renderHelper('pagination'); ?> -</div> - -<?php } else { ?> -<div id="stream" class="prompt alert alert-warn reader"> - <h2><?php echo _t('no_feed_to_display'); ?></h2> - <a href="<?php echo _url('configure', 'feed'); ?>"><?php echo _t('think_to_add'); ?></a><br /><br /> -</div> -<?php } ?> diff --git a/app/views/importExport/index.phtml b/app/views/importExport/index.phtml index 35371faca..36c0eab4e 100644 --- a/app/views/importExport/index.phtml +++ b/app/views/importExport/index.phtml @@ -1,4 +1,4 @@ -<?php $this->partial('aside_feed'); ?> +<?php $this->partial('aside_subscription'); ?> <div class="post "> <a href="<?php echo _url('index', 'index'); ?>"><?php echo _t('back_to_rss_feeds'); ?></a> diff --git a/app/views/index/about.phtml b/app/views/index/about.phtml index 76ff804d8..407d13ae9 100644 --- a/app/views/index/about.phtml +++ b/app/views/index/about.phtml @@ -1,27 +1,27 @@ <div class="post content"> - <a href="<?php echo _url ('index', 'index'); ?>"><?php echo Minz_Translate::t ('back_to_rss_feeds'); ?></a> + <a href="<?php echo _url('index', 'index'); ?>"><?php echo _t('back_to_rss_feeds'); ?></a> - <h1><?php echo Minz_Translate::t ('about_freshrss'); ?></h1> + <h1><?php echo _t('about_freshrss'); ?></h1> <dl class="infos"> - <dt><?php echo Minz_Translate::t ('project_website'); ?></dt> + <dt><?php echo _t('project_website'); ?></dt> <dd><a href="<?php echo FRESHRSS_WEBSITE; ?>"><?php echo FRESHRSS_WEBSITE; ?></a></dd> - <dt><?php echo Minz_Translate::t ('lead_developer'); ?></dt> - <dd><a href="mailto:contact@marienfressinaud.fr">Marien Fressinaud</a> — <a href="http://marienfressinaud.fr"><?php echo Minz_Translate::t ('website'); ?></a></dd> + <dt><?php echo _t('lead_developer'); ?></dt> + <dd><a href="mailto:contact@marienfressinaud.fr">Marien Fressinaud</a> — <a href="http://marienfressinaud.fr"><?php echo _t('website'); ?></a></dd> - <dt><?php echo Minz_Translate::t ('bugs_reports'); ?></dt> - <dd><?php echo Minz_Translate::t ('github_or_email'); ?></dd> + <dt><?php echo _t('bugs_reports'); ?></dt> + <dd><?php echo _t('github_or_email'); ?></dd> - <dt><?php echo Minz_Translate::t ('license'); ?></dt> - <dd><?php echo Minz_Translate::t ('agpl3'); ?></dd> + <dt><?php echo _t('license'); ?></dt> + <dd><?php echo _t('agpl3'); ?></dd> - <dt><?php echo Minz_Translate::t ('version'); ?></dt> + <dt><?php echo _t('version'); ?></dt> <dd><?php echo FRESHRSS_VERSION; ?></dd> </dl> - <p><?php echo Minz_Translate::t ('freshrss_description'); ?></p> + <p><?php echo _t('freshrss_description'); ?></p> - <h1><?php echo Minz_Translate::t ('credits'); ?></h1> - <p><?php echo Minz_Translate::t ('credits_content'); ?></p> + <h1><?php echo _t('credits'); ?></h1> + <p><?php echo _t('credits_content'); ?></p> </div> diff --git a/app/views/index/global.phtml b/app/views/index/global.phtml new file mode 100644 index 000000000..ae7f5ffbc --- /dev/null +++ b/app/views/index/global.phtml @@ -0,0 +1,54 @@ +<?php + $this->partial('nav_menu'); + + $class = ''; + if (FreshRSS_Context::$conf->hide_read_feeds && + FreshRSS_Context::isStateEnabled(FreshRSS_Entry::STATE_NOT_READ) && + !FreshRSS_Context::isStateEnabled(FreshRSS_Entry::STATE_READ)) { + $class = ' state_unread'; + } +?> + +<div id="stream" class="global<?php echo $class; ?>"> +<?php + $url_base = array( + 'c' => 'index', + 'a' => 'index', + 'params' => Minz_Request::params() + ); + + foreach ($this->categories as $cat) { + $feeds = $cat->feeds(); + $url_base['params']['get'] = 'c_' . $cat->id(); + + if (!empty($feeds)) { +?> + <div class="box category" data-unread="<?php echo $cat->nbNotRead(); ?>"> + <div class="box-title"><a class="title" data-unread="<?php echo format_number($cat->nbNotRead()); ?>" href="<?php echo Minz_Url::display($url_base); ?>"><?php echo $cat->name(); ?></a></div> + + <ul class="box-content"> + <?php + foreach ($feeds as $feed) { + $nb_not_read = $feed->nbNotRead(); + $error = $feed->inError() ? ' error' : ''; + $empty = $feed->nbEntries() === 0 ? ' empty' : ''; + $url_base['params']['get'] = 'f_' . $feed->id(); + ?> + <li id="f_<?php echo $feed->id(); ?>" class="item feed<?php echo $error, $empty; ?>" data-unread="<?php echo $feed->nbNotRead(); ?>" data-priority="<?php echo $feed->priority(); ?>"> + <img class="favicon" src="<?php echo $feed->favicon(); ?>" alt="✇" /> + <a class="item-title" data-unread="<?php echo format_number($feed->nbNotRead()); ?>" href="<?php echo Minz_Url::display($url_base); ?>"><?php echo $feed->name(); ?></a> + </li> + <?php } ?> + </ul> + </div> +<?php + } + } +?> +</div> + +<div id="overlay"> + <a class="close" href="#"><?php echo _i('close'); ?></a> +</div> +<div id="panel"<?php echo FreshRSS_Context::$conf->display_posts ? '' : ' class="hide_posts"'; ?>> +</div> diff --git a/app/views/index/index.phtml b/app/views/index/index.phtml index 1ff36ca8e..8b93461dd 100644 --- a/app/views/index/index.phtml +++ b/app/views/index/index.phtml @@ -1,25 +1,23 @@ <?php -$output = Minz_Request::param ('output', 'normal'); +$output = Minz_Request::param('output', 'normal'); -if ($this->loginOk || Minz_Configuration::allowAnonymous()) { +if (FreshRSS_Auth::hasAccess() || Minz_Configuration::allowAnonymous()) { if ($output === 'normal') { - $this->renderHelper ('view/normal_view'); + $this->renderHelper('view/normal_view'); } elseif ($output === 'reader') { - $this->renderHelper ('view/reader_view'); - } elseif ($output === 'global') { - $this->renderHelper ('view/global_view'); + $this->renderHelper('view/reader_view'); } elseif ($output === 'rss') { - $this->renderHelper ('view/rss_view'); + $this->renderHelper('view/rss_view'); } else { - Minz_Request::_param ('output', 'normal'); + Minz_Request::_param('output', 'normal'); $output = 'normal'; - $this->renderHelper ('view/normal_view'); + $this->renderHelper('view/normal_view'); } } elseif ($output === 'rss') { // token has already been checked in the controller so we can show the view - $this->renderHelper ('view/rss_view'); + $this->renderHelper('view/rss_view'); } else { // Normally, it should not happen, but log it anyway - Minz_Log::record ('Something is wrong in ' . __FILE__ . ' line ' . __LINE__, Minz_Log::ERROR); + Minz_Log::error('Something is wrong in ' . __FILE__ . ' line ' . __LINE__); } diff --git a/app/views/index/login.phtml b/app/views/index/login.phtml deleted file mode 100644 index cc814deff..000000000 --- a/app/views/index/login.phtml +++ /dev/null @@ -1 +0,0 @@ -<?php print_r ($this->res); ?> diff --git a/app/views/index/logout.phtml b/app/views/index/logout.phtml deleted file mode 100644 index a0aba9318..000000000 --- a/app/views/index/logout.phtml +++ /dev/null @@ -1 +0,0 @@ -OK
\ No newline at end of file diff --git a/app/views/index/logs.phtml b/app/views/index/logs.phtml index 1b77b39af..101692daf 100644 --- a/app/views/index/logs.phtml +++ b/app/views/index/logs.phtml @@ -1,25 +1,25 @@ <div class="post content"> - <a href="<?php echo _url ('index', 'index'); ?>"><?php echo Minz_Translate::t ('back_to_rss_feeds'); ?></a> + <a href="<?php echo _url('index', 'index'); ?>"><?php echo _t('back_to_rss_feeds'); ?></a> - <h1><?php echo Minz_Translate::t ('logs'); ?></h1> - <form method="post" action="<?php echo _url ('index', 'logs'); ?>"><p> + <h1><?php echo _t('logs'); ?></h1> + <form method="post" action="<?php echo _url('index', 'logs'); ?>"><p> <input type="hidden" name="clearLogs" /> - <button type="submit" class="btn"><?php echo Minz_Translate::t ('clear_logs'); ?></button> + <button type="submit" class="btn"><?php echo _t('clear_logs'); ?></button> </p></form> - <?php $items = $this->logsPaginator->items (); ?> + <?php $items = $this->logsPaginator->items(); ?> - <?php if (!empty ($items)) { ?> + <?php if (!empty($items)) { ?> <div class="logs"> - <?php $this->logsPaginator->render ('logs_pagination.phtml', 'page'); ?> + <?php $this->logsPaginator->render('logs_pagination.phtml', 'page'); ?> <?php foreach ($items as $log) { ?> - <div class="log <?php echo $log->level (); ?>"><span class="date"><?php echo @date ('Y-m-d H:i:s', @strtotime ($log->date ())); ?></span><?php echo htmlspecialchars ($log->info (), ENT_NOQUOTES, 'UTF-8'); ?></div> + <div class="log <?php echo $log->level(); ?>"><span class="date"><?php echo @date('Y-m-d H:i:s', @strtotime($log->date())); ?></span><?php echo htmlspecialchars($log->info(), ENT_NOQUOTES, 'UTF-8'); ?></div> <?php } ?> - <?php $this->logsPaginator->render ('logs_pagination.phtml','page'); ?> + <?php $this->logsPaginator->render('logs_pagination.phtml','page'); ?> </div> <?php } else { ?> - <p class="alert alert-warn"><?php echo Minz_Translate::t ('logs_empty'); ?></p> + <p class="alert alert-warn"><?php echo _t('logs_empty'); ?></p> <?php } ?> </div> diff --git a/app/views/helpers/view/normal_view.phtml b/app/views/index/normal.phtml index 6d9789f8d..02d621bd0 100644 --- a/app/views/helpers/view/normal_view.phtml +++ b/app/views/index/normal.phtml @@ -1,147 +1,149 @@ <?php -$this->partial ('aside_flux'); -$this->partial ('nav_menu'); +$this->partial('aside_feed'); +$this->partial('nav_menu'); if (!empty($this->entries)) { $display_today = true; $display_yesterday = true; $display_others = true; - if ($this->loginOk) { - $sharing = $this->conf->sharing; + if (FreshRSS_Auth::hasAccess()) { + $sharing = FreshRSS_Context::$conf->sharing; } else { $sharing = array(); } - $hidePosts = !$this->conf->display_posts; - $lazyload = $this->conf->lazyload; - $topline_read = $this->conf->topline_read; - $topline_favorite = $this->conf->topline_favorite; - $topline_date = $this->conf->topline_date; - $topline_link = $this->conf->topline_link; - $bottomline_read = $this->conf->bottomline_read; - $bottomline_favorite = $this->conf->bottomline_favorite; - $bottomline_sharing = $this->conf->bottomline_sharing && (count($sharing)); - $bottomline_tags = $this->conf->bottomline_tags; - $bottomline_date = $this->conf->bottomline_date; - $bottomline_link = $this->conf->bottomline_link; + $hidePosts = !FreshRSS_Context::$conf->display_posts; + $lazyload = FreshRSS_Context::$conf->lazyload; + $topline_read = FreshRSS_Context::$conf->topline_read; + $topline_favorite = FreshRSS_Context::$conf->topline_favorite; + $topline_date = FreshRSS_Context::$conf->topline_date; + $topline_link = FreshRSS_Context::$conf->topline_link; + $bottomline_read = FreshRSS_Context::$conf->bottomline_read; + $bottomline_favorite = FreshRSS_Context::$conf->bottomline_favorite; + $bottomline_sharing = FreshRSS_Context::$conf->bottomline_sharing && (count($sharing)); + $bottomline_tags = FreshRSS_Context::$conf->bottomline_tags; + $bottomline_date = FreshRSS_Context::$conf->bottomline_date; + $bottomline_link = FreshRSS_Context::$conf->bottomline_link; - $content_width = $this->conf->content_width; + $content_width = FreshRSS_Context::$conf->content_width; + + $today = @strtotime('today'); ?> <div id="stream" class="normal<?php echo $hidePosts ? ' hide_posts' : ''; ?>"><?php ?><div id="new-article"> - <a href="<?php echo Minz_Url::display ($this->url); ?>"><?php echo Minz_Translate::t ('new_article'); ?></a> + <a href="<?php echo Minz_Url::display(Minz_Request::currentRequest()); ?>"><?php echo _t('new_article'); ?></a> </div><?php foreach ($this->entries as $item) { - if ($display_today && $item->isDay (FreshRSS_Days::TODAY, $this->today)) { + if ($display_today && $item->isDay(FreshRSS_Days::TODAY, $today)) { ?><div class="day" id="day_today"><?php - echo Minz_Translate::t ('today'); - ?><span class="date"> — <?php echo timestamptodate (time (), false); ?></span><?php - ?><span class="name"><?php echo $this->currentName; ?></span><?php + echo _t('today'); + ?><span class="date"> — <?php echo timestamptodate(time(), false); ?></span><?php + ?><span class="name"><?php echo FreshRSS_Context::$name; ?></span><?php ?></div><?php $display_today = false; } - if ($display_yesterday && $item->isDay (FreshRSS_Days::YESTERDAY, $this->today)) { + if ($display_yesterday && $item->isDay(FreshRSS_Days::YESTERDAY, $today)) { ?><div class="day" id="day_yesterday"><?php - echo Minz_Translate::t ('yesterday'); - ?><span class="date"> — <?php echo timestamptodate (time () - 86400, false); ?></span><?php - ?><span class="name"><?php echo $this->currentName; ?></span><?php + echo _t('yesterday'); + ?><span class="date"> — <?php echo timestamptodate(time() - 86400, false); ?></span><?php + ?><span class="name"><?php echo FreshRSS_Context::$name; ?></span><?php ?></div><?php $display_yesterday = false; } - if ($display_others && $item->isDay (FreshRSS_Days::BEFORE_YESTERDAY, $this->today)) { + if ($display_others && $item->isDay(FreshRSS_Days::BEFORE_YESTERDAY, $today)) { ?><div class="day" id="day_before_yesterday"><?php - echo Minz_Translate::t ('before_yesterday'); - ?><span class="name"><?php echo $this->currentName; ?></span><?php + echo _t('before_yesterday'); + ?><span class="name"><?php echo FreshRSS_Context::$name; ?></span><?php ?></div><?php $display_others = false; } - ?><div class="flux<?php echo !$item->isRead () ? ' not_read' : ''; ?><?php echo $item->isFavorite () ? ' favorite' : ''; ?>" id="flux_<?php echo $item->id (); ?>"> + ?><div class="flux<?php echo !$item->isRead() ? ' not_read' : ''; ?><?php echo $item->isFavorite() ? ' favorite' : ''; ?>" id="flux_<?php echo $item->id(); ?>"> <ul class="horizontal-list flux_header"><?php - if ($this->loginOk) { + if (FreshRSS_Auth::hasAccess()) { if ($topline_read) { ?><li class="item manage"><?php - $arUrl = array('c' => 'entry', 'a' => 'read', 'params' => array('id' => $item->id ())); + $arUrl = array('c' => 'entry', 'a' => 'read', 'params' => array('id' => $item->id())); if ($item->isRead()) { $arUrl['params']['is_read'] = 0; } ?><a class="read" href="<?php echo Minz_Url::display($arUrl); ?>"><?php - echo FreshRSS_Themes::icon($item->isRead () ? 'read' : 'unread'); ?></a><?php + echo _i($item->isRead() ? 'read' : 'unread'); ?></a><?php ?></li><?php } if ($topline_favorite) { ?><li class="item manage"><?php - $arUrl = array('c' => 'entry', 'a' => 'bookmark', 'params' => array('id' => $item->id ())); + $arUrl = array('c' => 'entry', 'a' => 'bookmark', 'params' => array('id' => $item->id())); if ($item->isFavorite()) { $arUrl['params']['is_favorite'] = 0; } ?><a class="bookmark" href="<?php echo Minz_Url::display($arUrl); ?>"><?php - echo FreshRSS_Themes::icon($item->isFavorite () ? 'starred' : 'non-starred'); ?></a><?php + echo _i($item->isFavorite() ? 'starred' : 'non-starred'); ?></a><?php ?></li><?php } } - $feed = FreshRSS_CategoryDAO::findFeed($this->cat_aside, $item->feed ()); //We most likely already have the feed object in cache + $feed = FreshRSS_CategoryDAO::findFeed($this->categories, $item->feed()); //We most likely already have the feed object in cache if ($feed == null) { $feed = $item->feed(true); if ($feed == null) { $feed = FreshRSS_Feed::example(); } } - ?><li class="item website"><a href="<?php echo _url ('index', 'index', 'get', 'f_' . $feed->id ()); ?>"><img class="favicon" src="<?php echo $feed->favicon (); ?>" alt="✇" /> <span><?php echo $feed->name(); ?></span></a></li> - <li class="item title"><a target="_blank" href="<?php echo $item->link (); ?>"><?php echo $item->title (); ?></a></li> - <?php if ($topline_date) { ?><li class="item date"><?php echo $item->date (); ?> </li><?php } ?> - <?php if ($topline_link) { ?><li class="item link"><a target="_blank" href="<?php echo $item->link (); ?>"><?php echo FreshRSS_Themes::icon('link'); ?></a></li><?php } ?> + ?><li class="item website"><a href="<?php echo _url('index', 'index', 'get', 'f_' . $feed->id()); ?>"><img class="favicon" src="<?php echo $feed->favicon(); ?>" alt="✇" /> <span><?php echo $feed->name(); ?></span></a></li> + <li class="item title"><a target="_blank" href="<?php echo $item->link(); ?>"><?php echo $item->title(); ?></a></li> + <?php if ($topline_date) { ?><li class="item date"><?php echo $item->date(); ?> </li><?php } ?> + <?php if ($topline_link) { ?><li class="item link"><a target="_blank" href="<?php echo $item->link(); ?>"><?php echo _i('link'); ?></a></li><?php } ?> </ul> <div class="flux_content"> <div class="content <?php echo $content_width; ?>"> - <h1 class="title"><a target="_blank" href="<?php echo $item->link (); ?>"><?php echo $item->title (); ?></a></h1> + <h1 class="title"><a target="_blank" href="<?php echo $item->link(); ?>"><?php echo $item->title(); ?></a></h1> <?php $author = $item->author(); - echo $author != '' ? '<div class="author">' . Minz_Translate::t('by_author', $author) . '</div>' : '', + echo $author != '' ? '<div class="author">' . _t('by_author', $author) . '</div>' : '', $lazyload && $hidePosts ? lazyimg($item->content()) : $item->content(); ?> </div> <ul class="horizontal-list bottom"><?php - if ($this->loginOk) { + if (FreshRSS_Auth::hasAccess()) { if ($bottomline_read) { ?><li class="item manage"><?php - $arUrl = array('c' => 'entry', 'a' => 'read', 'params' => array('id' => $item->id ())); + $arUrl = array('c' => 'entry', 'a' => 'read', 'params' => array('id' => $item->id())); if ($item->isRead()) { $arUrl['params']['is_read'] = 0; } ?><a class="read" href="<?php echo Minz_Url::display($arUrl); ?>"><?php - echo FreshRSS_Themes::icon($item->isRead () ? 'read' : 'unread'); ?></a><?php + echo _i($item->isRead() ? 'read' : 'unread'); ?></a><?php ?></li><?php } if ($bottomline_favorite) { ?><li class="item manage"><?php - $arUrl = array('c' => 'entry', 'a' => 'bookmark', 'params' => array('id' => $item->id ())); + $arUrl = array('c' => 'entry', 'a' => 'bookmark', 'params' => array('id' => $item->id())); if ($item->isFavorite()) { $arUrl['params']['is_favorite'] = 0; } ?><a class="bookmark" href="<?php echo Minz_Url::display($arUrl); ?>"><?php - echo FreshRSS_Themes::icon($item->isFavorite () ? 'starred' : 'non-starred'); ?></a><?php + echo _i($item->isFavorite() ? 'starred' : 'non-starred'); ?></a><?php ?></li><?php } } ?> <li class="item"><?php if ($bottomline_sharing) { - $link = urlencode ($item->link ()); - $title = urlencode ($item->title () . ' · ' . $feed->name ()); + $link = urlencode($item->link()); + $title = urlencode($item->title() . ' · ' . $feed->name()); ?><div class="dropdown"> - <div id="dropdown-share-<?php echo $item->id ();?>" class="dropdown-target"></div> - <a class="dropdown-toggle" href="#dropdown-share-<?php echo $item->id ();?>"> - <?php echo FreshRSS_Themes::icon('share'); ?> - <?php echo Minz_Translate::t ('share'); ?> + <div id="dropdown-share-<?php echo $item->id();?>" class="dropdown-target"></div> + <a class="dropdown-toggle" href="#dropdown-share-<?php echo $item->id();?>"> + <?php echo _i('share'); ?> + <?php echo _t('share'); ?> </a> <ul class="dropdown-menu"> <li class="dropdown-close"><a href="#close">❌</a></li> <?php foreach ($sharing as $share) :?> <li class="item share"> - <a target="_blank" href="<?php echo FreshRSS_Share::generateUrl($this->conf->shares, $share, $item->link(), $item->title() . ' . ' . $feed->name())?>"> - <?php echo Minz_Translate::t ($share['name']);?> + <a target="_blank" href="<?php echo FreshRSS_Share::generateUrl(FreshRSS_Context::$conf->shares, $share, $item->link(), $item->title() . ' . ' . $feed->name())?>"> + <?php echo _t($share['name']);?> </a> </li> <?php endforeach;?> @@ -168,10 +170,10 @@ if (!empty($this->entries)) { </li><?php } if ($bottomline_date) { - ?><li class="item date"><?php echo $item->date (); ?></li><?php + ?><li class="item date"><?php echo $item->date(); ?></li><?php } if ($bottomline_link) { - ?><li class="item link"><a target="_blank" href="<?php echo $item->link (); ?>"><?php echo FreshRSS_Themes::icon('link'); ?></a></li><?php + ?><li class="item link"><a target="_blank" href="<?php echo $item->link(); ?>"><?php echo _i('link'); ?></a></li><?php } ?> </ul> </div> @@ -181,11 +183,11 @@ if (!empty($this->entries)) { <?php $this->renderHelper('pagination'); ?> </div> -<?php $this->partial ('nav_entries'); ?> +<?php $this->partial('nav_entries'); ?> <?php } else { ?> <div id="stream" class="prompt alert alert-warn normal"> <h2><?php echo _t('no_feed_to_display'); ?></h2> - <a href="<?php echo _url('configure', 'feed'); ?>"><?php echo _t('think_to_add'); ?></a><br /><br /> + <a href="<?php echo _url('subscription', 'index'); ?>"><?php echo _t('think_to_add'); ?></a><br /><br /> </div> <?php } ?> diff --git a/app/views/index/reader.phtml b/app/views/index/reader.phtml new file mode 100644 index 000000000..f07868488 --- /dev/null +++ b/app/views/index/reader.phtml @@ -0,0 +1,44 @@ +<?php +$this->partial('nav_menu'); + +if (!empty($this->entries)) { + $lazyload = FreshRSS_Context::$conf->lazyload; + $content_width = FreshRSS_Context::$conf->content_width; +?> + +<div id="stream" class="reader"> + <?php foreach ($this->entries as $item) { ?> + + <div class="flux<?php echo !$item->isRead() ? ' not_read' : ''; ?><?php echo $item->isFavorite() ? ' favorite' : ''; ?>" id="flux_<?php echo $item->id(); ?>"> + <div class="flux_content"> + <div class="content <?php echo $content_width; ?>"> + <?php + $feed = FreshRSS_CategoryDAO::findFeed($this->categories, $item->feed()); //We most likely already have the feed object in cache + if (empty($feed)) $feed = $item->feed(true); + ?> + <a href="<?php echo $item->link(); ?>"> + <img class="favicon" src="<?php echo $feed->favicon(); ?>" alt="✇" /> <span><?php echo $feed->name(); ?></span> + </a> + <h1 class="title"><?php echo $item->title(); ?></h1> + + <div class="author"><?php + $author = $item->author(); + echo $author != '' ? _t('by_author', $author) . ' — ' : '', + $item->date(); + ?></div> + + <?php echo $item->content(); ?> + </div> + </div> + </div> + <?php } ?> + + <?php $this->renderHelper('pagination'); ?> +</div> + +<?php } else { ?> +<div id="stream" class="prompt alert alert-warn reader"> + <h2><?php echo _t('no_feed_to_display'); ?></h2> + <a href="<?php echo _url('subscription', 'index'); ?>"><?php echo _t('think_to_add'); ?></a><br /><br /> +</div> +<?php } ?> diff --git a/app/views/helpers/view/rss_view.phtml b/app/views/index/rss.phtml index 2c6ca610b..e34b15ab1 100755 --- a/app/views/helpers/view/rss_view.phtml +++ b/app/views/index/rss.phtml @@ -3,25 +3,25 @@ <channel> <title><?php echo $this->rss_title; ?></title> <link><?php echo Minz_Url::display(null, 'html', true); ?></link> - <description><?php echo Minz_Translate::t ('rss_feeds_of', $this->rss_title); ?></description> + <description><?php echo _t('rss_feeds_of', $this->rss_title); ?></description> <pubDate><?php echo date('D, d M Y H:i:s O'); ?></pubDate> <lastBuildDate><?php echo gmdate('D, d M Y H:i:s'); ?> GMT</lastBuildDate> - <atom:link href="<?php echo Minz_Url::display ($this->url, 'html', true); ?>" rel="self" type="application/rss+xml" /> + <atom:link href="<?php echo Minz_Url::display($this->url, 'html', true); ?>" rel="self" type="application/rss+xml" /> <?php foreach ($this->entries as $item) { ?> <item> - <title><?php echo $item->title (); ?></title> - <link><?php echo $item->link (); ?></link> - <?php $author = $item->author (); ?> + <title><?php echo $item->title(); ?></title> + <link><?php echo $item->link(); ?></link> + <?php $author = $item->author(); ?> <?php if ($author != '') { ?> <dc:creator><?php echo $author; ?></dc:creator> <?php } ?> <description><![CDATA[<?php - echo $item->content (); + echo $item->content(); ?>]]></description> - <pubDate><?php echo date('D, d M Y H:i:s O', $item->date (true)); ?></pubDate> - <guid isPermaLink="false"><?php echo $item->id (); ?></guid> + <pubDate><?php echo date('D, d M Y H:i:s O', $item->date(true)); ?></pubDate> + <guid isPermaLink="false"><?php echo $item->id(); ?></guid> </item> <?php } ?> diff --git a/app/views/stats/idle.phtml b/app/views/stats/idle.phtml index 6f3d4a117..75cba1081 100644 --- a/app/views/stats/idle.phtml +++ b/app/views/stats/idle.phtml @@ -25,7 +25,7 @@ <li class="item"> <div class="stick"> <a class="btn" href="<?php echo _url('index', 'index', 'get', 'f_' . $feed['id']); ?>"><?php echo _i('link'); ?> <?php echo _t('filter'); ?></a> - <a class="btn" href="<?php echo _url('configure', 'feed', 'id', $feed['id']); ?>"><?php echo _i('configure'); ?> <?php echo _t('administration'); ?></a> + <a class="btn" href="<?php echo _url('subscription', 'index', 'id', $feed['id']); ?>"><?php echo _i('configure'); ?> <?php echo _t('administration'); ?></a> <button class="btn btn-attention confirm" form="form-delete" formaction="<?php echo _url('feed', 'delete', 'id', $feed['id'], 'r', $current_url); ?>"><?php echo _t('delete'); ?></button> </div> </li> diff --git a/app/views/stats/index.phtml b/app/views/stats/index.phtml index fa57a77c0..1300cb2c7 100644 --- a/app/views/stats/index.phtml +++ b/app/views/stats/index.phtml @@ -1,54 +1,54 @@ <?php $this->partial('aside_stats'); ?> <div class="post"> - <a href="<?php echo _url ('index', 'index'); ?>"><?php echo _t ('back_to_rss_feeds'); ?></a> + <a href="<?php echo _url('index', 'index'); ?>"><?php echo _t('back_to_rss_feeds'); ?></a> - <h1><?php echo _t ('stats_main'); ?></h1> + <h1><?php echo _t('stats_main'); ?></h1> <div class="stat half"> - <h2><?php echo _t ('stats_entry_repartition'); ?></h2> + <h2><?php echo _t('stats_entry_repartition'); ?></h2> <table> <thead> <tr> <th> </th> - <th><?php echo _t ('main_stream'); ?></th> - <th><?php echo _t ('all_feeds'); ?></th> + <th><?php echo _t('main_stream'); ?></th> + <th><?php echo _t('all_feeds'); ?></th> </tr> </thead> <tbody> <tr> - <th><?php echo _t ('status_total'); ?></th> - <td class="numeric"><?php echo formatNumber($this->repartition['main_stream']['total']); ?></td> - <td class="numeric"><?php echo formatNumber($this->repartition['all_feeds']['total']); ?></td> + <th><?php echo _t('status_total'); ?></th> + <td class="numeric"><?php echo format_number($this->repartition['main_stream']['total']); ?></td> + <td class="numeric"><?php echo format_number($this->repartition['all_feeds']['total']); ?></td> </tr> <tr> - <th><?php echo _t ('status_read'); ?></th> - <td class="numeric"><?php echo formatNumber($this->repartition['main_stream']['read']); ?></td> - <td class="numeric"><?php echo formatNumber($this->repartition['all_feeds']['read']); ?></td> + <th><?php echo _t('status_read'); ?></th> + <td class="numeric"><?php echo format_number($this->repartition['main_stream']['read']); ?></td> + <td class="numeric"><?php echo format_number($this->repartition['all_feeds']['read']); ?></td> </tr> <tr> - <th><?php echo _t ('status_unread'); ?></th> - <td class="numeric"><?php echo formatNumber($this->repartition['main_stream']['unread']); ?></td> - <td class="numeric"><?php echo formatNumber($this->repartition['all_feeds']['unread']); ?></td> + <th><?php echo _t('status_unread'); ?></th> + <td class="numeric"><?php echo format_number($this->repartition['main_stream']['unread']); ?></td> + <td class="numeric"><?php echo format_number($this->repartition['all_feeds']['unread']); ?></td> </tr> <tr> - <th><?php echo _t ('status_favorites'); ?></th> - <td class="numeric"><?php echo formatNumber($this->repartition['main_stream']['favorite']); ?></td> - <td class="numeric"><?php echo formatNumber($this->repartition['all_feeds']['favorite']); ?></td> + <th><?php echo _t('status_favorites'); ?></th> + <td class="numeric"><?php echo format_number($this->repartition['main_stream']['favorite']); ?></td> + <td class="numeric"><?php echo format_number($this->repartition['all_feeds']['favorite']); ?></td> </tr> </tbody> </table> </div><!-- --><div class="stat half"> - <h2><?php echo _t ('stats_top_feed'); ?></h2> + <h2><?php echo _t('stats_top_feed'); ?></h2> <table> <thead> <tr> - <th><?php echo _t ('feed'); ?></th> - <th><?php echo _t ('category'); ?></th> - <th><?php echo _t ('stats_entry_count'); ?></th> - <th><?php echo _t ('stats_percent_of_total'); ?></th> + <th><?php echo _t('feed'); ?></th> + <th><?php echo _t('category'); ?></th> + <th><?php echo _t('stats_entry_count'); ?></th> + <th><?php echo _t('stats_percent_of_total'); ?></th> </tr> </thead> <tbody> @@ -56,8 +56,8 @@ <tr> <td><a href="<?php echo _url('stats', 'repartition', 'id', $feed['id']); ?>"><?php echo $feed['name']; ?></a></td> <td><?php echo $feed['category']; ?></td> - <td class="numeric"><?php echo formatNumber($feed['count']); ?></td> - <td class="numeric"><?php echo formatNumber($feed['count'] / $this->repartition['all_feeds']['total'] * 100, 1);?></td> + <td class="numeric"><?php echo format_number($feed['count']); ?></td> + <td class="numeric"><?php echo format_number($feed['count'] / $this->repartition['all_feeds']['total'] * 100, 1);?></td> </tr> <?php endforeach;?> </tbody> @@ -65,18 +65,18 @@ </div> <div class="stat"> - <h2><?php echo _t ('stats_entry_per_day'); ?></h2> + <h2><?php echo _t('stats_entry_per_day'); ?></h2> <div id="statsEntryPerDay" style="height: 300px"></div> </div> <div class="stat half"> - <h2><?php echo _t ('stats_feed_per_category'); ?></h2> + <h2><?php echo _t('stats_feed_per_category'); ?></h2> <div id="statsFeedPerCategory" style="height: 300px"></div> <div id="statsFeedPerCategoryLegend"></div> </div><!-- --><div class="stat half"> - <h2><?php echo _t ('stats_entry_per_category'); ?></h2> + <h2><?php echo _t('stats_entry_per_category'); ?></h2> <div id="statsEntryPerCategory" style="height: 300px"></div> <div id="statsEntryPerCategoryLegend"></div> </div> diff --git a/app/views/stats/repartition.phtml b/app/views/stats/repartition.phtml index 750a3ffdc..32268a546 100644 --- a/app/views/stats/repartition.phtml +++ b/app/views/stats/repartition.phtml @@ -5,7 +5,7 @@ <h1><?php echo _t('stats_repartition'); ?></h1> - <select id="feed_select"> + <select id="feed_select" class="select-change"> <option data-url="<?php echo _url('stats', 'repartition')?>"><?php echo _t('all_feeds')?></option> <?php foreach ($this->categories as $category) { $feeds = $category->feeds(); @@ -24,23 +24,23 @@ </select> <?php if ($this->feed) {?> - <a class="btn" href="<?php echo _url('configure', 'feed', 'id', $this->feed->id()); ?>"> + <a class="btn" href="<?php echo _url('subscription', 'index', 'id', $this->feed->id()); ?>"> <?php echo _i('configure'); ?> <?php echo _t('administration'); ?> </a> <?php }?> <div class="stat"> - <h2><?php echo _t('stats_entry_per_hour'); ?></h2> + <h2><?php echo _t('stats_entry_per_hour', $this->averageHour); ?></h2> <div id="statsEntryPerHour" style="height: 300px"></div> </div> <div class="stat half"> - <h2><?php echo _t('stats_entry_per_day_of_week'); ?></h2> + <h2><?php echo _t('stats_entry_per_day_of_week', $this->averageDayOfWeek); ?></h2> <div id="statsEntryPerDayOfWeek" style="height: 300px"></div> </div><!-- --><div class="stat half"> - <h2><?php echo _t('stats_entry_per_month'); ?></h2> + <h2><?php echo _t('stats_entry_per_month', $this->averageMonth); ?></h2> <div id="statsEntryPerMonth" style="height: 300px"></div> </div> </div> @@ -56,19 +56,10 @@ function initStats() { return; } // Entry per hour - var avg_h = []; - for (var i = -1; i <= 24; i++) { - avg_h.push([i, <?php echo $this->averageHour?>]); - } Flotr.draw(document.getElementById('statsEntryPerHour'), [{ data: <?php echo $this->repartitionHour ?>, bars: {horizontal: false, show: true} - }, { - data: avg_h, - lines: {show: true}, - label: "<?php echo $this->averageHour?>", - yaxis: 2 }], { grid: {verticalLines: false}, @@ -81,23 +72,13 @@ function initStats() { max: 23.9, tickDecimals: 0}, yaxis: {min: 0}, - y2axis: {showLabels: false}, mouse: {relative: true, track: true, trackDecimals: 0, trackFormatter: function(obj) {return numberFormat(obj.y);}} }); // Entry per day of week - var avg_dow = []; - for (var i = -1; i <= 7; i++) { - avg_dow.push([i, <?php echo $this->averageDayOfWeek?>]); - } Flotr.draw(document.getElementById('statsEntryPerDayOfWeek'), [{ data: <?php echo $this->repartitionDayOfWeek ?>, bars: {horizontal: false, show: true} - }, { - data: avg_dow, - lines: {show: true}, - label: "<?php echo $this->averageDayOfWeek?>", - yaxis: 2 }], { grid: {verticalLines: false}, @@ -111,23 +92,13 @@ function initStats() { max: 6.9, tickDecimals: 0}, yaxis: {min: 0}, - y2axis: {showLabels: false}, mouse: {relative: true, track: true, trackDecimals: 0, trackFormatter: function(obj) {return numberFormat(obj.y);}} }); // Entry per month - var avg_m = []; - for (var i = 0; i <= 13; i++) { - avg_m.push([i, <?php echo $this->averageMonth?>]); - } Flotr.draw(document.getElementById('statsEntryPerMonth'), [{ data: <?php echo $this->repartitionMonth ?>, bars: {horizontal: false, show: true} - }, { - data: avg_m, - lines: {show: true}, - label: "<?php echo $this->averageMonth?>", - yaxis: 2 }], { grid: {verticalLines: false}, @@ -141,7 +112,6 @@ function initStats() { max: 12.9, tickDecimals: 0}, yaxis: {min: 0}, - y2axis: {showLabels: false}, mouse: {relative: true, track: true, trackDecimals: 0, trackFormatter: function(obj) {return numberFormat(obj.y);}} }); diff --git a/app/views/subscription/feed.phtml b/app/views/subscription/feed.phtml new file mode 100644 index 000000000..48a401c4a --- /dev/null +++ b/app/views/subscription/feed.phtml @@ -0,0 +1,15 @@ +<?php + +if (!Minz_Request::param('ajax')) { + $this->partial('aside_subscription'); +} + +if ($this->feed) { + $this->renderHelper('feed/update'); +} else { +?> +<div class="alert alert-warn"> + <span class="alert-head"><?php echo _t('no_selected_feed'); ?></span> + <?php echo _t('think_to_add'); ?> +</div> +<?php } ?> diff --git a/app/views/subscription/index.phtml b/app/views/subscription/index.phtml new file mode 100644 index 000000000..2c56f79ed --- /dev/null +++ b/app/views/subscription/index.phtml @@ -0,0 +1,148 @@ +<?php $this->partial('aside_subscription'); ?> + +<div class="post drop-section"> + <a href="<?php echo _url('index', 'index'); ?>"><?php echo _t('back_to_rss_feeds'); ?></a> + + <h2><?php echo _t('subscription_management'); ?></h2> + + <form id="add_rss" method="post" action="<?php echo _url('feed', 'add'); ?>" autocomplete="off"> + <div class="stick"> + <input type="url" name="url_rss" class="extend" placeholder="<?php echo _t('add_rss_feed'); ?>" /> + <div class="dropdown"> + <div id="dropdown-cat" class="dropdown-target"></div> + + <a class="dropdown-toggle btn" href="#dropdown-cat"><?php echo _i('down'); ?></a> + <ul class="dropdown-menu"> + <li class="dropdown-close"><a href="#close">❌</a></li> + + <li class="dropdown-header"><?php echo _t('category'); ?></li> + + <li class="input"> + <select name="category" id="category"> + <?php foreach ($this->categories as $cat) { ?> + <option value="<?php echo $cat->id(); ?>"<?php echo $cat->id() == 1 ? ' selected="selected"' : ''; ?>> + <?php echo $cat->name(); ?> + </option> + <?php } ?> + <option value="nc"><?php echo _t('new_category'); ?></option> + </select> + </li> + + <li class="input" style="display:none"> + <input type="text" name="new_category[name]" id="new_category_name" autocomplete="off" placeholder="<?php echo _t('new_category'); ?>" /> + </li> + + <li class="separator"></li> + + <li class="dropdown-header"><?php echo _t('http_authentication'); ?></li> + <li class="input"> + <input type="text" name="http_user" id="http_user_add" autocomplete="off" placeholder="<?php echo _t('username'); ?>" /> + </li> + <li class="input"> + <input type="password" name="http_pass" id="http_pass_add" autocomplete="off" placeholder="<?php echo _t('password'); ?>" /> + </li> + </ul> + </div> + <button class="btn" type="submit"><?php echo _i('add'); ?></button> + </div> + </form> + + <p class="alert alert-warn"> + <?php echo _t('feeds_moved_category_deleted', $this->default_category->name()); ?> + </p> + + <div class="box"> + <div class="box-title"><label for="new-category"><?php echo _t('add_category'); ?></label></div> + + <ul class="box-content box-content-centered"> + <form action="<?php echo _url('category', 'create'); ?>" method="post"> + <li class="item"><input type="text" id="new-category" name="new-category" placeholder="<?php echo _t('new_category'); ?>" /></li> + <li class="item"><button class="btn btn-important" type="submit"><?php echo _t('submit'); ?></button></li> + </form> + </ul> + </div> + + <form id="controller-category" method="post" style="display: none;"></form> + + <?php + foreach ($this->categories as $cat) { + $feeds = $cat->feeds(); + ?> + <div class="box"> + <div class="box-title"> + <form action="<?php echo _url('category', 'update', 'id', $cat->id()); ?>" method="post"> + <input type="text" name="name" value="<?php echo $cat->name(); ?>" /> + + <div class="dropdown"> + <div id="dropdown-cat-<?php echo $cat->id(); ?>" class="dropdown-target"></div> + + <a class="dropdown-toggle btn" href="#dropdown-cat-<?php echo $cat->id(); ?>"><?php echo _i('down'); ?></a> + <ul class="dropdown-menu"> + <li class="dropdown-close"><a href="#close">❌</a></li> + + <li class="item"><a href="<?php echo _url('index', 'index', 'get', 'c_' . $cat->id()); ?>"><?php echo _t('filter'); ?></a></li> + + <?php + $no_feed = empty($feeds); + $is_default = ($cat->id() === $this->default_category->id()); + + if (!$no_feed || !$is_default) { + ?> + <li class="separator"></li> + <?php } if (!$no_feed) { ?> + <li class="item"> + <button class="as-link confirm" + data-str-confirm="<?php echo _t('confirm_action_feed_cat'); ?>" + type="submit" + form="controller-category" + formaction="<?php echo _url('category', 'empty', 'id', $cat->id()); ?>"> + <?php echo _t('ask_empty'); ?></button> + </li> + <?php } if (!$is_default) { ?> + <li class="item"> + <button class="as-link confirm" + data-str-confirm="<?php echo _t('confirm_action_feed_cat'); ?>" + type="submit" + form="controller-category" + formaction="<?php echo _url('category', 'delete', 'id', $cat->id()); ?>"> + <?php echo _t('delete'); ?></button> + </li> + <?php } ?> + </ul> + </div> + </form> + </div> + + <ul class="box-content" data-cat-id="<?php echo $cat->id(); ?>"> + <?php if (!empty($feeds)) { ?> + <?php + foreach ($feeds as $feed) { + $error = $feed->inError() ? ' error' : ''; + $empty = $feed->nbEntries() == 0 ? ' empty' : ''; + ?> + <li class="item feed<?php echo $error, $empty; ?>" + draggable="true" + data-feed-id="<?php echo $feed->id(); ?>" + dropzone="move"> + <a class="configure open-slider" href="<?php echo _url('subscription', 'feed', 'id', $feed->id()); ?>"><?php echo _i('configure'); ?></a> + <img class="favicon" src="<?php echo $feed->favicon(); ?>" alt="✇" /> <?php echo $feed->name(); ?> + </li> + <?php } + } else { + ?> + <li class="item disabled" dropzone="move"><?php echo _t('category_empty'); ?></li> + <?php } ?> + </ul> + </div> + <?php } ?> +</div> + +<?php $class = isset($this->feed) ? ' class="active"' : ''; ?> +<a href="#" id="close-slider"<?php echo $class; ?>></a> +<div id="slider"<?php echo $class; ?>> +<?php + if (isset($this->feed)) { + $this->renderHelper('feed/update'); + } +?> +</div> diff --git a/app/views/update/checkInstall.phtml b/app/views/update/checkInstall.phtml new file mode 100644 index 000000000..b36551340 --- /dev/null +++ b/app/views/update/checkInstall.phtml @@ -0,0 +1,38 @@ +<?php $this->partial('aside_configure'); ?> + +<div class="post"> + <a href="<?php echo _url('index', 'index'); ?>"><?php echo _t('back_to_rss_feeds'); ?></a> + + <h2><?php echo _t('admin.check_install.php'); ?></h2> + + <?php foreach ($this->status_php as $key => $status) { ?> + <p class="alert <?php echo $status ? 'alert-success' : 'alert-error'; ?>"> + <?php + if ($key === 'php') { + echo _t('admin.check_install.' . $key . '.' . ($status ? 'ok' : 'nok'), PHP_VERSION, '5.2.1'); + } else { + echo _t('admin.check_install.' . $key . '.' . ($status ? 'ok' : 'nok')); + } + ?> + </p> + <?php } ?> + + <h2><?php echo _t('admin.check_install.files'); ?></h2> + + <?php foreach ($this->status_files as $key => $status) { ?> + <p class="alert <?php echo $status ? 'alert-success' : 'alert-error'; ?>"> + <?php echo _t('admin.check_install.' . $key . '.' . ($status ? 'ok' : 'nok')); ?> + </p> + <?php } ?> + + <?php /* + <h2><?php echo _t('admin.check_install.database'); ?></h2> + + <?php foreach ($this->status_database as $key => $status) { ?> + <p class="alert <?php echo $status ? 'alert-success' : 'alert-error'; ?>"> + <?php echo _t('admin.check_install.' . $key . '.' . ($status ? 'ok' : 'nok')); ?> + </p> + <?php } ?> + */ ?> + +</div> diff --git a/app/views/user/manage.phtml b/app/views/user/manage.phtml new file mode 100644 index 000000000..e46e02572 --- /dev/null +++ b/app/views/user/manage.phtml @@ -0,0 +1,79 @@ +<?php $this->partial('aside_configure'); ?> + +<div class="post"> + <a href="<?php echo _url('index', 'index'); ?>"><?php echo _t('back_to_rss_feeds'); ?></a> + + <form method="post" action="<?php echo _url('user', 'create'); ?>"> + <legend><?php echo _t('create_user'); ?></legend> + + <div class="form-group"> + <label class="group-name" for="new_user_language"><?php echo _t('language'); ?></label> + <div class="group-controls"> + <select name="new_user_language" id="new_user_language"> + <?php $languages = FreshRSS_Context::$conf->availableLanguages(); ?> + <?php foreach ($languages as $short => $lib) { ?> + <option value="<?php echo $short; ?>"<?php echo FreshRSS_Context::$conf->language === $short ? ' selected="selected"' : ''; ?>><?php echo $lib; ?></option> + <?php } ?> + </select> + </div> + </div> + + <div class="form-group"> + <label class="group-name" for="new_user_name"><?php echo _t('username'); ?></label> + <div class="group-controls"> + <input id="new_user_name" name="new_user_name" type="text" size="16" required="required" maxlength="16" autocomplete="off" pattern="[0-9a-zA-Z]{1,16}" placeholder="demo" /> + </div> + </div> + + <div class="form-group"> + <label class="group-name" for="new_user_passwordPlain"><?php echo _t('password_form'); ?></label> + <div class="group-controls"> + <div class="stick"> + <input type="password" id="new_user_passwordPlain" name="new_user_passwordPlain" autocomplete="off" pattern=".{7,}" /> + <a class="btn toggle-password"><?php echo _i('key'); ?></a> + </div> + <noscript><b><?php echo _t('javascript_should_be_activated'); ?></b></noscript> + </div> + </div> + + <div class="form-group"> + <label class="group-name" for="new_user_email"><?php echo _t('persona_connection_email'); ?></label> + <?php $mail = FreshRSS_Context::$conf->mail_login; ?> + <div class="group-controls"> + <input type="email" id="new_user_email" name="new_user_email" class="extend" autocomplete="off" placeholder="alice@example.net" /> + </div> + </div> + + <div class="form-group form-actions"> + <div class="group-controls"> + <button type="submit" class="btn btn-important"><?php echo _t('create'); ?></button> + <button type="reset" class="btn"><?php echo _t('cancel'); ?></button> + </div> + </div> + </form> + + <form method="post" action="<?php echo _url('user', 'delete'); ?>"> + <legend><?php echo _t('users'); ?></legend> + + <div class="form-group"> + <label class="group-name" for="user-list"><?php echo _t('users_list'); ?></label> + <div class="group-controls"> + <select id="user-list" class="select-change" name="username"> + <?php foreach (listUsers() as $username) { ?> + <option data-url="<?php echo _url('user', 'manage', 'u', $username); ?>" <?php echo $this->current_user === $username ? 'selected="selected"' : ''; ?> value="<?php echo $username; ?>"><?php echo $username; ?></option> + <?php } ?> + </select> + + <p><?php echo _t('admin.users.articles_and_size', + format_number($this->nb_articles), + format_bytes($this->size_user)); ?></p> + </div> + </div> + + <div class="form-group form-actions"> + <div class="group-controls"> + <button type="submit" class="btn btn-attention confirm"><?php echo _t('delete'); ?></button> + </div> + </div> + </form> +</div> diff --git a/app/views/user/profile.phtml b/app/views/user/profile.phtml new file mode 100644 index 000000000..60257012c --- /dev/null +++ b/app/views/user/profile.phtml @@ -0,0 +1,59 @@ +<?php $this->partial('aside_configure'); ?> + +<div class="post"> + <a href="<?php echo _url('index', 'index'); ?>"><?php echo _t('back_to_rss_feeds'); ?></a> + + <form method="post" action="<?php echo _url('user', 'profile'); ?>"> + <legend><?php echo _t('login_configuration'); ?></legend> + + <div class="form-group"> + <label class="group-name" for="current_user"><?php echo _t('current_user'); ?></label> + <div class="group-controls"> + <input id="current_user" type="text" disabled="disabled" value="<?php echo Minz_Session::param('currentUser', '_'); ?>" /> + <label class="checkbox" for="is_admin"> + <input type="checkbox" id="is_admin" disabled="disabled" <?php echo FreshRSS_Auth::hasAccess('admin') ? 'checked="checked" ' : ''; ?>/> + <?php echo _t('is_admin'); ?> + </label> + </div> + </div> + + <div class="form-group"> + <label class="group-name" for="passwordPlain"><?php echo _t('password_form'); ?></label> + <div class="group-controls"> + <div class="stick"> + <input type="password" id="passwordPlain" name="passwordPlain" autocomplete="off" pattern=".{7,}" <?php echo cryptAvailable() ? '' : 'disabled="disabled" '; ?>/> + <a class="btn toggle-password"><?php echo _i('key'); ?></a> + </div> + <noscript><b><?php echo _t('javascript_should_be_activated'); ?></b></noscript> + </div> + </div> + + <?php if (Minz_Configuration::apiEnabled()) { ?> + <div class="form-group"> + <label class="group-name" for="apiPasswordPlain"><?php echo _t('password_api'); ?></label> + <div class="group-controls"> + <div class="stick"> + <input type="password" id="apiPasswordPlain" name="apiPasswordPlain" autocomplete="off" pattern=".{7,}" <?php echo cryptAvailable() ? '' : 'disabled="disabled" '; ?>/> + <a class="btn toggle-password"><?php echo _i('key'); ?></a> + </div> + </div> + </div> + <?php } ?> + + <div class="form-group"> + <label class="group-name" for="mail_login"><?php echo _t('persona_connection_email'); ?></label> + <?php $mail = FreshRSS_Context::$conf->mail_login; ?> + <div class="group-controls"> + <input type="email" id="mail_login" name="mail_login" class="extend" autocomplete="off" value="<?php echo $mail; ?>" <?php echo FreshRSS_Auth::hasAccess('admin') ? '' : 'disabled="disabled"'; ?> placeholder="alice@example.net" /> + <noscript><b><?php echo _t('javascript_should_be_activated'); ?></b></noscript> + </div> + </div> + + <div class="form-group form-actions"> + <div class="group-controls"> + <button type="submit" class="btn btn-important"><?php echo _t('save'); ?></button> + <button type="reset" class="btn"><?php echo _t('cancel'); ?></button> + </div> + </div> + </form> +</div> diff --git a/constants.php b/constants.php index 464e2da66..39e5f95c1 100644 --- a/constants.php +++ b/constants.php @@ -1,5 +1,5 @@ <?php -define('FRESHRSS_VERSION', '0.8.0'); +define('FRESHRSS_VERSION', '0.9.2'); define('FRESHRSS_WEBSITE', 'http://freshrss.org'); define('FRESHRSS_UPDATE_WEBSITE', 'https://update.freshrss.org?v=' . FRESHRSS_VERSION); define('FRESHRSS_WIKI', 'http://doc.freshrss.org'); diff --git a/extensions/.gitignore b/extensions/.gitignore new file mode 100644 index 000000000..d93e5e396 --- /dev/null +++ b/extensions/.gitignore @@ -0,0 +1 @@ +/[xX] diff --git a/extensions/Read-me.txt b/extensions/Read-me.txt new file mode 100644 index 000000000..e7b66d5bc --- /dev/null +++ b/extensions/Read-me.txt @@ -0,0 +1,15 @@ +== FreshRSS extensions == + +You may place in this directory some custom extensions for FreshRSS. + +The structure must be: + +./FreshRSS/extensions/ + ./NameOfExtensionAlphanumeric/ + ./style.css + ./script.js + ./module.php + +Each file is optional. + +The name of non-official extensions should start by an 'x'. diff --git a/lib/Minz/Configuration.php b/lib/Minz/Configuration.php index 4e9da58b4..6cbc9fc0b 100644 --- a/lib/Minz/Configuration.php +++ b/lib/Minz/Configuration.php @@ -60,6 +60,15 @@ class Minz_Configuration { 'prefix' => '', ); + const MAX_SMALL_INT = 16384; + private static $limits = array( + 'cache_duration' => 800, //SimplePie cache duration in seconds + 'timeout' => 10, //SimplePie timeout in seconds + 'max_inactivity' => PHP_INT_MAX, //Time in seconds after which a user who has not used the account is considered inactive (no auto-refresh of feeds). + 'max_feeds' => Minz_Configuration::MAX_SMALL_INT, + 'max_categories' => Minz_Configuration::MAX_SMALL_INT, + ); + /* * Getteurs */ @@ -97,12 +106,12 @@ class Minz_Configuration { public static function dataBase () { return self::$db; } + public static function limits() { + return self::$limits; + } public static function defaultUser () { return self::$default_user; } - public static function isAdmin($currentUser) { - return $currentUser === self::$default_user; - } public static function allowAnonymous() { return self::$allow_anonymous; } @@ -181,6 +190,7 @@ class Minz_Configuration { 'api_enabled' => self::$api_enabled, 'unsafe_autologin_enabled' => self::$unsafe_autologin_enabled, ), + 'limits' => self::$limits, 'db' => self::$db, ); @rename(DATA_PATH . self::CONF_PATH_NAME, DATA_PATH . self::CONF_PATH_NAME . '.bak.php'); @@ -294,6 +304,40 @@ class Minz_Configuration { ); } + if (isset($ini_array['limits'])) { + $limits = $ini_array['limits']; + if (isset($limits['cache_duration'])) { + $v = intval($limits['cache_duration']); + if ($v > 0) { + self::$limits['cache_duration'] = $v; + } + } + if (isset($limits['timeout'])) { + $v = intval($limits['timeout']); + if ($v > 0) { + self::$limits['timeout'] = $v; + } + } + if (isset($limits['max_inactivity'])) { + $v = intval($limits['max_inactivity']); + if ($v > 0) { + self::$limits['max_inactivity'] = $v; + } + } + if (isset($limits['max_feeds'])) { + $v = intval($limits['max_feeds']); + if ($v > 0 && $v < Minz_Configuration::MAX_SMALL_INT) { + self::$limits['max_feeds'] = $v; + } + } + if (isset($limits['max_categories'])) { + $v = intval($limits['max_categories']); + if ($v > 0 && $v < Minz_Configuration::MAX_SMALL_INT) { + self::$limits['max_categories'] = $v; + } + } + } + // Base de données if (isset ($ini_array['db'])) { $db = $ini_array['db']; diff --git a/lib/Minz/FrontController.php b/lib/Minz/FrontController.php index f13882801..e95c56bf3 100644 --- a/lib/Minz/FrontController.php +++ b/lib/Minz/FrontController.php @@ -46,7 +46,7 @@ class Minz_FrontController { ); Minz_Request::forward ($url); } catch (Minz_Exception $e) { - Minz_Log::record ($e->getMessage (), Minz_Log::ERROR); + Minz_Log::error($e->getMessage()); $this->killApp ($e->getMessage ()); } @@ -85,7 +85,7 @@ class Minz_FrontController { $this->dispatcher->run(); } catch (Minz_Exception $e) { try { - Minz_Log::record ($e->getMessage (), Minz_Log::ERROR); + Minz_Log::error($e->getMessage()); } catch (Minz_PermissionDeniedException $e) { $this->killApp ($e->getMessage ()); } diff --git a/lib/Minz/ModelPdo.php b/lib/Minz/ModelPdo.php index b4bfca746..6198cd85c 100644 --- a/lib/Minz/ModelPdo.php +++ b/lib/Minz/ModelPdo.php @@ -16,6 +16,8 @@ class Minz_ModelPdo { public static $useSharedBd = true; private static $sharedBd = null; private static $sharedPrefix; + private static $has_transaction = false; + private static $sharedCurrentUser; protected static $sharedDbType; /** @@ -23,6 +25,7 @@ class Minz_ModelPdo { */ protected $bd; + protected $current_user; protected $prefix; public function dbType() { @@ -37,6 +40,7 @@ class Minz_ModelPdo { if (self::$useSharedBd && self::$sharedBd != null && $currentUser === null) { $this->bd = self::$sharedBd; $this->prefix = self::$sharedPrefix; + $this->current_user = self::$sharedCurrentUser; return; } @@ -45,6 +49,8 @@ class Minz_ModelPdo { if ($currentUser === null) { $currentUser = Minz_Session::param('currentUser', '_'); } + $this->current_user = $currentUser; + self::$sharedCurrentUser = $currentUser; try { $type = $db['type']; @@ -91,12 +97,18 @@ class Minz_ModelPdo { public function beginTransaction() { $this->bd->beginTransaction(); + self::$has_transaction = true; + } + public function hasTransaction() { + return self::$has_transaction; } public function commit() { $this->bd->commit(); + self::$has_transaction = false; } public function rollBack() { $this->bd->rollBack(); + self::$has_transaction = false; } public static function clean() { diff --git a/lib/Minz/Request.php b/lib/Minz/Request.php index f7a24c026..4b97a3caf 100644 --- a/lib/Minz/Request.php +++ b/lib/Minz/Request.php @@ -45,6 +45,13 @@ class Minz_Request { public static function defaultActionName() { return self::$default_action_name; } + public static function currentRequest() { + return array( + 'c' => self::$controller_name, + 'a' => self::$action_name, + 'params' => self::$params, + ); + } /** * Setteurs diff --git a/lib/Minz/View.php b/lib/Minz/View.php index a0dec1824..b40448491 100644 --- a/lib/Minz/View.php +++ b/lib/Minz/View.php @@ -71,9 +71,7 @@ class Minz_View { */ public function render () { if ((include($this->view_filename)) === false) { - Minz_Log::record ('File not found: `' - . $this->view_filename . '`', - Minz_Log::NOTICE); + Minz_Log::notice('File not found: `' . $this->view_filename . '`'); } } @@ -87,9 +85,7 @@ class Minz_View { . $part . '.phtml'; if ((include($fic_partial)) === false) { - Minz_Log::record ('File not found: `' - . $fic_partial . '`', - Minz_Log::WARNING); + Minz_Log::warning('File not found: `' . $fic_partial . '`'); } } @@ -103,9 +99,7 @@ class Minz_View { . $helper . '.phtml'; if ((include($fic_helper)) === false) {; - Minz_Log::record ('File not found: `' - . $fic_helper . '`', - Minz_Log::WARNING); + Minz_Log::warning('File not found: `' . $fic_helper . '`'); } } diff --git a/lib/SimplePie/SimplePie.php b/lib/SimplePie/SimplePie.php index 06c100f59..84001dd9a 100644 --- a/lib/SimplePie/SimplePie.php +++ b/lib/SimplePie/SimplePie.php @@ -1455,7 +1455,11 @@ class SimplePie { // Load the Cache $this->data = $cache->load(); - if (!empty($this->data)) + if ($cache->mtime() + $this->cache_duration > time()) { //FreshRSS + $this->raw_data = false; + return true; // If the cache is still valid, just return true + } + elseif (!empty($this->data)) { // If the cache is for an outdated build of SimplePie if (!isset($this->data['build']) || $this->data['build'] !== SIMPLEPIE_BUILD) @@ -1487,7 +1491,7 @@ class SimplePie } } // Check if the cache has been updated - elseif ($cache->mtime() + $this->cache_duration < time()) + else //if ($cache->mtime() + $this->cache_duration < time()) //FreshRSS removed { // If we have last-modified and/or etag set //if (isset($this->data['headers']['last-modified']) || isset($this->data['headers']['etag'])) //FreshRSS removed @@ -1516,6 +1520,7 @@ class SimplePie } else { + $cache->touch(); //FreshRSS $this->error = $file->error; //FreshRSS return !empty($this->data); //FreshRSS //unset($file); //FreshRSS removed @@ -1533,17 +1538,18 @@ class SimplePie } } } - // If the cache is still valid, just return true - else - { - $this->raw_data = false; - return true; - } + //// If the cache is still valid, just return true + //else //FreshRSS removed + //{ + // $this->raw_data = false; + // return true; + //} } // If the cache is empty, delete it else { - $cache->unlink(); + //$cache->unlink(); //FreshRSS removed + $cache->touch(); //FreshRSS $this->data = array(); } } diff --git a/lib/SimplePie/SimplePie/Cache/File.php b/lib/SimplePie/SimplePie/Cache/File.php index 3b163545b..cb4b528c4 100644 --- a/lib/SimplePie/SimplePie/Cache/File.php +++ b/lib/SimplePie/SimplePie/Cache/File.php @@ -136,11 +136,11 @@ class SimplePie_Cache_File implements SimplePie_Cache_Base */ public function mtime() { - if (file_exists($this->name)) + //if (file_exists($this->name)) //FreshRSS removed { - return filemtime($this->name); + return @filemtime($this->name); //FreshRSS } - return false; + //return false; //FreshRSS removed } /** @@ -150,11 +150,11 @@ class SimplePie_Cache_File implements SimplePie_Cache_Base */ public function touch() { - if (file_exists($this->name)) + //if (file_exists($this->name)) //FreshRSS removed { - return touch($this->name); + return @touch($this->name); //FreshRSS } - return false; + //return false; //FreshRSS removed } /** diff --git a/lib/SimplePie/SimplePie/Parse/Date.php b/lib/SimplePie/SimplePie/Parse/Date.php index ef800f125..ba7c0703e 100644 --- a/lib/SimplePie/SimplePie/Parse/Date.php +++ b/lib/SimplePie/SimplePie/Parse/Date.php @@ -331,6 +331,7 @@ class SimplePie_Parse_Date 'CCT' => 23400, 'CDT' => -18000, 'CEDT' => 7200, + 'CEST' => 7200, //FreshRSS 'CET' => 3600, 'CGST' => -7200, 'CGT' => -10800, diff --git a/lib/lib_opml.php b/lib/lib_opml.php index 16a9921ea..f320335bb 100644 --- a/lib/lib_opml.php +++ b/lib/lib_opml.php @@ -101,6 +101,7 @@ function libopml_parse_string($xml) { // First, we get all "head" elements. Head is required but its sub-elements // are optional. + // TODO: test head exists! foreach ($opml->head->children() as $key => $value) { if (in_array($key, unserialize(HEAD_ELEMENTS), true)) { $array['head'][$key] = (string)$value; @@ -114,6 +115,7 @@ function libopml_parse_string($xml) { // Then, we get body oulines. Body must contain at least one outline // element. $at_least_one_outline = false; + // TODO: test body exists! foreach ($opml->body->children() as $key => $value) { if ($key === 'outline') { $at_least_one_outline = true; diff --git a/lib/lib_rss.php b/lib/lib_rss.php index 31c9cdbc1..e7ca95aba 100644 --- a/lib/lib_rss.php +++ b/lib/lib_rss.php @@ -56,12 +56,14 @@ function checkUrl($url) { } } -function formatNumber($n, $precision = 0) { - return str_replace(' ', ' ', //Espace insécable //TODO: remplacer par une espace _fine_ insécable - number_format($n, $precision, '.', ' ')); //number_format does not seem to be Unicode-compatible +function format_number($n, $precision = 0) { + // number_format does not seem to be Unicode-compatible + return str_replace(' ', ' ', //Espace fine insécable + number_format($n, $precision, '.', ' ') + ); } -function formatBytes($bytes, $precision = 2, $system = 'IEC') { +function format_bytes($bytes, $precision = 2, $system = 'IEC') { if ($system === 'IEC') { $base = 1024; $units = array('B', 'KiB', 'MiB', 'GiB', 'TiB'); @@ -73,15 +75,15 @@ function formatBytes($bytes, $precision = 2, $system = 'IEC') { $pow = $bytes === 0 ? 0 : floor(log($bytes) / log($base)); $pow = min($pow, count($units) - 1); $bytes /= pow($base, $pow); - return formatNumber($bytes, $precision) . ' ' . $units[$pow]; + return format_number($bytes, $precision) . ' ' . $units[$pow]; } function timestamptodate ($t, $hour = true) { - $month = Minz_Translate::t (date('M', $t)); + $month = _t(date('M', $t)); if ($hour) { - $date = Minz_Translate::t ('format_date_hour', $month); + $date = _t('format_date_hour', $month); } else { - $date = Minz_Translate::t ('format_date', $month); + $date = _t('format_date', $month); } return @date ($date, $t); @@ -106,10 +108,12 @@ function html_only_entity_decode($text) { } function customSimplePie() { + $limits = Minz_Configuration::limits(); $simplePie = new SimplePie(); - $simplePie->set_useragent(Minz_Translate::t('freshrss') . '/' . FRESHRSS_VERSION . ' (' . PHP_OS . '; ' . FRESHRSS_WEBSITE . ') ' . SIMPLEPIE_NAME . '/' . SIMPLEPIE_VERSION); + $simplePie->set_useragent(_t('freshrss') . '/' . FRESHRSS_VERSION . ' (' . PHP_OS . '; ' . FRESHRSS_WEBSITE . ') ' . SIMPLEPIE_NAME . '/' . SIMPLEPIE_VERSION); $simplePie->set_cache_location(CACHE_PATH); - $simplePie->set_cache_duration(800); + $simplePie->set_cache_duration($limits['cache_duration']); + $simplePie->set_timeout($limits['timeout']); $simplePie->strip_htmltags(array( 'base', 'blink', 'body', 'doctype', 'embed', 'font', 'form', 'frame', 'frameset', 'html', @@ -244,3 +248,71 @@ function is_referer_from_same_domain() { } return (isset($host['port']) ? $host['port'] : 0) === (isset($referer['port']) ? $referer['port'] : 0); } + + +/** + * Check PHP and its extensions are well-installed. + * + * @return array of tested values. + */ +function check_install_php() { + $pdo_mysql = extension_loaded('pdo_mysql'); + $pdo_sqlite = extension_loaded('pdo_sqlite'); + return array( + 'php' => version_compare(PHP_VERSION, '5.2.1') >= 0, + 'minz' => file_exists(LIB_PATH . '/Minz'), + 'curl' => extension_loaded('curl'), + 'pdo' => $pdo_mysql || $pdo_sqlite, + 'pcre' => extension_loaded('pcre'), + 'ctype' => extension_loaded('ctype'), + 'dom' => class_exists('DOMDocument'), + 'json' => extension_loaded('json'), + 'zip' => extension_loaded('zip'), + ); +} + + +/** + * Check different data files and directories exist. + * + * @return array of tested values. + */ +function check_install_files() { + return array( + 'data' => DATA_PATH && is_writable(DATA_PATH), + 'cache' => CACHE_PATH && is_writable(CACHE_PATH), + 'logs' => LOG_PATH && is_writable(LOG_PATH), + 'favicons' => is_writable(DATA_PATH . '/favicons'), + 'persona' => is_writable(DATA_PATH . '/persona'), + 'tokens' => is_writable(DATA_PATH . '/tokens'), + ); +} + + +/** + * Check database is well-installed. + * + * @return array of tested values. + */ +function check_install_database() { + $status = array( + 'connection' => true, + 'tables' => false, + 'categories' => false, + 'feeds' => false, + 'entries' => false, + ); + + try { + $dbDAO = FreshRSS_Factory::createDatabaseDAO(); + + $status['tables'] = $dbDAO->tablesAreCorrect(); + $status['categories'] = $dbDAO->categoryIsCorrect(); + $status['feeds'] = $dbDAO->feedIsCorrect(); + $status['entries'] = $dbDAO->entryIsCorrect(); + } catch(Minz_PDOConnectionException $e) { + $status['connection'] = false; + } + + return $status; +} diff --git a/p/api/greader.php b/p/api/greader.php index 5a6fdad7d..1a66c30fb 100644 --- a/p/api/greader.php +++ b/p/api/greader.php @@ -160,7 +160,7 @@ function authorizationToUserConf() { return $conf; } else { logMe('Invalid API authorisation for user ' . $user . ': ' . $headerAuthX[1] . "\n"); - Minz_Log::record('Invalid API authorisation for user ' . $user . ': ' . $headerAuthX[1], Minz_Log::WARNING); + Minz_Log::warning('Invalid API authorisation for user ' . $user . ': ' . $headerAuthX[1]); unauthorized(); } } else { @@ -181,7 +181,7 @@ function clientLogin($email, $pass) { //http://web.archive.org/web/2013060409104 $conf = new FreshRSS_Configuration($email); } catch (Exception $e) { logMe($e->getMessage() . "\n"); - Minz_Log::record('Invalid API user ' . $email, Minz_Log::WARNING); + Minz_Log::warning('Invalid API user ' . $email); unauthorized(); } if ($conf->apiPasswordHash != '' && password_verify($pass, $conf->apiPasswordHash)) { @@ -191,7 +191,7 @@ function clientLogin($email, $pass) { //http://web.archive.org/web/2013060409104 'Auth=', $auth, "\n"; exit(); } else { - Minz_Log::record('Password API mismatch for user ' . $email, Minz_Log::WARNING); + Minz_Log::warning('Password API mismatch for user ' . $email); unauthorized(); } } else { diff --git a/p/ext.php b/p/ext.php new file mode 100644 index 000000000..a1dde2f93 --- /dev/null +++ b/p/ext.php @@ -0,0 +1,38 @@ +<?php +if (!isset($_GET['e'])) { + header('HTTP/1.1 400 Bad Request'); + die(); +} +$extension = substr($_GET['e'], 0, 64); +if (!ctype_alpha($extension)) { + header('HTTP/1.1 400 Bad Request'); + die(); +} + +require('../constants.php'); +$filename = FRESHRSS_PATH . '/extensions/' . $extension . '/'; + +if (isset($_GET['j'])) { + header('Content-Type: application/javascript; charset=UTF-8'); + header('Content-Disposition: inline; filename="script.js"'); + $filename .= 'script.js'; +} elseif (isset($_GET['c'])) { + header('Content-Type: text/css; charset=UTF-8'); + header('Content-Disposition: inline; filename="style.css"'); + $filename .= 'style.css'; +} else { + header('HTTP/1.1 400 Bad Request'); + die(); +} + +$mtime = @filemtime($filename); +if ($mtime == false) { + header('HTTP/1.1 404 Not Found'); + die(); +} + +require(LIB_PATH . '/http-conditional.php'); + +if (!httpConditional($mtime, 604800, 2)) { + readfile($filename); +} diff --git a/p/i/index.php b/p/i/index.php index 7b34eefd1..ec969c159 100755 --- a/p/i/index.php +++ b/p/i/index.php @@ -46,7 +46,7 @@ if (file_exists(DATA_PATH . '/do-install.txt')) { $front_controller->run(); } catch (Exception $e) { echo '### Fatal error! ###<br />', "\n"; - Minz_Log::record($e->getMessage(), Minz_Log::ERROR); + Minz_Log::error($e->getMessage()); echo 'See logs files.'; } } diff --git a/p/scripts/bcrypt.min.js b/p/scripts/bcrypt.min.js index 6892caddc..614f0c259 100644 --- a/p/scripts/bcrypt.min.js +++ b/p/scripts/bcrypt.min.js @@ -1,41 +1,45 @@ -/* +(function(){/* bcrypt.js (c) 2013 Daniel Wirtz <dcode@dcode.io> Released under the Apache License, Version 2.0 see: https://github.com/dcodeIO/bcrypt.js for details */ -function p(n){throw n;}var q=null; -(function(n){function u(c,a,b,f){for(var d,e=c[a],l=c[a+1],e=e^b[0],h=0;14>=h;)d=f[e>>24&255],d+=f[256|e>>16&255],d^=f[512|e>>8&255],d+=f[768|e&255],l^=d^b[++h],d=f[l>>24&255],d+=f[256|l>>16&255],d^=f[512|l>>8&255],d+=f[768|l&255],e^=d^b[++h];c[a]=l^b[17];c[a+1]=e;return c}function s(c,a){var b,f=0;for(b=0;4>b;b++)f=f<<8|c[a]&255,a=(a+1)%c.length;return{key:f,a:a}}function y(c,a,b){for(var f=0,d=[0,0],e=a.length,l=b.length,h=0;h<e;h++){var g=s(c,f),f=g.a;a[h]^=g.key}for(h=0;h<e;h+=2)d=u(d,0,a,b), -a[h]=d[0],a[h+1]=d[1];for(h=0;h<l;h+=2)d=u(d,0,a,b),b[h]=d[0],b[h+1]=d[1]}function C(c,a,b,f){for(var d=0,e=[0,0],l=b.length,h=f.length,g,k=0;k<l;k++)g=s(a,d),d=g.a,b[k]^=g.key;for(k=d=0;k<l;k+=2)g=s(c,d),d=g.a,e[0]^=g.key,g=s(c,d),d=g.a,e[1]^=g.key,e=u(e,0,b,f),b[k]=e[0],b[k+1]=e[1];for(k=0;k<h;k+=2)g=s(c,d),d=g.a,e[0]^=g.key,g=s(c,d),d=g.a,e[1]^=g.key,e=u(e,0,b,f),f[k]=e[0],f[k+1]=e[1]}function z(c){"undefined"!==typeof process&&"function"===typeof process.nextTick?process.nextTick(c):setTimeout(c, -0)}function A(c,a,b,f){function d(){if(k<b)for(var n=new Date;k<b&&!(k+=1,y(c,h,g),y(a,h,g),100<Date.now()-n););else{for(k=0;64>k;k++)for(m=0;m<l>>1;m++)u(e,m<<1,h,g);n=[];for(k=0;k<l;k++)n.push((e[k]>>24&255)>>>0),n.push((e[k]>>16&255)>>>0),n.push((e[k]>>8&255)>>>0),n.push((e[k]&255)>>>0);return f?(f(q,n),q):n}f&&z(d);return q}var e=B.slice(),l=e.length;(4>b||31<b)&&p(Error("Illegal number of rounds: "+b));16!=a.length&&p(Error("Illegal salt length: "+a.length+" != 16"));b=1<<b;var h=D.slice(),g= -E.slice();C(a,c,h,g);var k=0,m;if("undefined"!==typeof f)return d(),q;for(var n;;)if((n=d())!==q)return n}function F(c){for(var a,b,f=[],d=0;d<c.length;d++){a=c.charCodeAt(d);b=[];do b.push(a&255),a>>=8;while(a);f=f.concat(b.reverse())}return f}function w(c,a,b){function f(a){var b=[];b.push("$2");"a"<=d&&b.push(d);b.push("$");10>l&&b.push("0");b.push(l.toString());b.push("$");b.push(v.b(h,h.length));b.push(v.b(a,4*B.length-1));return b.join("")}var d,e;("$"!=a.charAt(0)||"2"!=a.charAt(1))&&p(Error("Invalid salt version: "+ -a.substring(0,2)));"$"==a.charAt(2)?(d=String.fromCharCode(0),e=3):(d=a.charAt(2),("a"!=d||"$"!=a.charAt(3))&&p(Error("Invalid salt revision: "+a.substring(2,4))),e=4);"$"<a.charAt(e+2)&&p(Error("Missing salt rounds"));var l=10*parseInt(a.substring(e,e+1),10)+parseInt(a.substring(e+1,e+2),10);a=a.substring(e+3,e+25);c=F(c+("a"<=d?"\x00":""));var h=[],h=v.c(a,16);if("undefined"==typeof b)return f(A(c,h,l));A(c,h,l,function(a,c){a?b(a,q):b(q,f(c))});return q}function G(){if("undefined"!==typeof module&& -module.exports)return require("crypto").randomBytes(16);var c=new Uint32Array(16);n.crypto&&"function"===typeof n.crypto.getRandomValues?n.crypto.getRandomValues(c):"function"===typeof x?x(c):p(Error("Failed to generate random values: Web Crypto API not available / no polyfill set"));return Array.prototype.slice.call(c)}var t="./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789".split(""),r=[-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, --1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,0,1,54,55,56,57,58,59,60,61,62,63,-1,-1,-1,-1,-1,-1,-1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,-1,-1,-1,-1,-1,-1,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,-1,-1,-1,-1,-1],v={b:function(c,a){var b=0,f=[],d,e;for((0>=a||a>c.length)&&p(Error("Invalid 'len': "+a));b<a;){d=c[b++]&255;f.push(t[d>>2&63]);d=(d&3)<<4;if(b>=a){f.push(t[d&63]);break}e=c[b++]&255;d|=e>>4&15;f.push(t[d&63]);d=(e&15)<< -2;if(b>=a){f.push(t[d&63]);break}e=c[b++]&255;d|=e>>6&3;f.push(t[d&63]);f.push(t[e&63])}return f.join("")},c:function(c,a){var b=0,f=c.length,d=0,e=[],l,h,g;for(0>=a&&p(Error("Illegal 'len': "+a));b<f-1&&d<a;){g=c.charCodeAt(b++);l=g<r.length?r[g]:-1;g=c.charCodeAt(b++);h=g<r.length?r[g]:-1;if(-1==l||-1==h)break;g=l<<2>>>0;g|=(h&48)>>4;e.push(String.fromCharCode(g));if(++d>=a||b>=f)break;g=c.charCodeAt(b++);l=g<r.length?r[g]:-1;if(-1==l)break;g=(h&15)<<4>>>0;g|=(l&60)>>2;e.push(String.fromCharCode(g)); -if(++d>=a||b>=f)break;g=c.charCodeAt(b++);h=g<r.length?r[g]:-1;g=(l&3)<<6>>>0;g|=h;e.push(String.fromCharCode(g));++d}f=[];for(b=0;b<d;b++)f.push(e[b].charCodeAt(0));return f}},m={},D=[608135816,2242054355,320440878,57701188,2752067618,698298832,137296536,3964562569,1160258022,953160567,3193202383,887688300,3232508343,3380367581,1065670069,3041331479,2450970073,2306472731],E=[3509652390,2564797868,805139163,3491422135,3101798381,1780907670,3128725573,4046225305,614570311,3012652279,134345442,2240740374, -1667834072,1901547113,2757295779,4103290238,227898511,1921955416,1904987480,2182433518,2069144605,3260701109,2620446009,720527379,3318853667,677414384,3393288472,3101374703,2390351024,1614419982,1822297739,2954791486,3608508353,3174124327,2024746970,1432378464,3864339955,2857741204,1464375394,1676153920,1439316330,715854006,3033291828,289532110,2706671279,2087905683,3018724369,1668267050,732546397,1947742710,3462151702,2609353502,2950085171,1814351708,2050118529,680887927,999245976,1800124847,3300911131, -1713906067,1641548236,4213287313,1216130144,1575780402,4018429277,3917837745,3693486850,3949271944,596196993,3549867205,258830323,2213823033,772490370,2760122372,1774776394,2652871518,566650946,4142492826,1728879713,2882767088,1783734482,3629395816,2517608232,2874225571,1861159788,326777828,3124490320,2130389656,2716951837,967770486,1724537150,2185432712,2364442137,1164943284,2105845187,998989502,3765401048,2244026483,1075463327,1455516326,1322494562,910128902,469688178,1117454909,936433444,3490320968, -3675253459,1240580251,122909385,2157517691,634681816,4142456567,3825094682,3061402683,2540495037,79693498,3249098678,1084186820,1583128258,426386531,1761308591,1047286709,322548459,995290223,1845252383,2603652396,3431023940,2942221577,3202600964,3727903485,1712269319,422464435,3234572375,1170764815,3523960633,3117677531,1434042557,442511882,3600875718,1076654713,1738483198,4213154764,2393238008,3677496056,1014306527,4251020053,793779912,2902807211,842905082,4246964064,1395751752,1040244610,2656851899, -3396308128,445077038,3742853595,3577915638,679411651,2892444358,2354009459,1767581616,3150600392,3791627101,3102740896,284835224,4246832056,1258075500,768725851,2589189241,3069724005,3532540348,1274779536,3789419226,2764799539,1660621633,3471099624,4011903706,913787905,3497959166,737222580,2514213453,2928710040,3937242737,1804850592,3499020752,2949064160,2386320175,2390070455,2415321851,4061277028,2290661394,2416832540,1336762016,1754252060,3520065937,3014181293,791618072,3188594551,3933548030,2332172193, -3852520463,3043980520,413987798,3465142937,3030929376,4245938359,2093235073,3534596313,375366246,2157278981,2479649556,555357303,3870105701,2008414854,3344188149,4221384143,3956125452,2067696032,3594591187,2921233993,2428461,544322398,577241275,1471733935,610547355,4027169054,1432588573,1507829418,2025931657,3646575487,545086370,48609733,2200306550,1653985193,298326376,1316178497,3007786442,2064951626,458293330,2589141269,3591329599,3164325604,727753846,2179363840,146436021,1461446943,4069977195, -705550613,3059967265,3887724982,4281599278,3313849956,1404054877,2845806497,146425753,1854211946,1266315497,3048417604,3681880366,3289982499,290971E4,1235738493,2632868024,2414719590,3970600049,1771706367,1449415276,3266420449,422970021,1963543593,2690192192,3826793022,1062508698,1531092325,1804592342,2583117782,2714934279,4024971509,1294809318,4028980673,1289560198,2221992742,1669523910,35572830,157838143,1052438473,1016535060,1802137761,1753167236,1386275462,3080475397,2857371447,1040679964,2145300060, -2390574316,1461121720,2956646967,4031777805,4028374788,33600511,2920084762,1018524850,629373528,3691585981,3515945977,2091462646,2486323059,586499841,988145025,935516892,3367335476,2599673255,2839830854,265290510,3972581182,2759138881,3795373465,1005194799,847297441,406762289,1314163512,1332590856,1866599683,4127851711,750260880,613907577,1450815602,3165620655,3734664991,3650291728,3012275730,3704569646,1427272223,778793252,1343938022,2676280711,2052605720,1946737175,3164576444,3914038668,3967478842, -3682934266,1661551462,3294938066,4011595847,840292616,3712170807,616741398,312560963,711312465,1351876610,322626781,1910503582,271666773,2175563734,1594956187,70604529,3617834859,1007753275,1495573769,4069517037,2549218298,2663038764,504708206,2263041392,3941167025,2249088522,1514023603,1998579484,1312622330,694541497,2582060303,2151582166,1382467621,776784248,2618340202,3323268794,2497899128,2784771155,503983604,4076293799,907881277,423175695,432175456,1378068232,4145222326,3954048622,3938656102, -3820766613,2793130115,2977904593,26017576,3274890735,3194772133,1700274565,1756076034,4006520079,3677328699,720338349,1533947780,354530856,688349552,3973924725,1637815568,332179504,3949051286,53804574,2852348879,3044236432,1282449977,3583942155,3416972820,4006381244,1617046695,2628476075,3002303598,1686838959,431878346,2686675385,1700445008,1080580658,1009431731,832498133,3223435511,2605976345,2271191193,2516031870,1648197032,4164389018,2548247927,300782431,375919233,238389289,3353747414,2531188641, -2019080857,1475708069,455242339,2609103871,448939670,3451063019,1395535956,2413381860,1841049896,1491858159,885456874,4264095073,4001119347,1565136089,3898914787,1108368660,540939232,1173283510,2745871338,3681308437,4207628240,3343053890,4016749493,1699691293,1103962373,3625875870,2256883143,3830138730,1031889488,3479347698,1535977030,4236805024,3251091107,2132092099,1774941330,1199868427,1452454533,157007616,2904115357,342012276,595725824,1480756522,206960106,497939518,591360097,863170706,2375253569, -3596610801,1814182875,2094937945,3421402208,1082520231,3463918190,2785509508,435703966,3908032597,1641649973,2842273706,3305899714,1510255612,2148256476,2655287854,3276092548,4258621189,236887753,3681803219,274041037,1734335097,3815195456,3317970021,1899903192,1026095262,4050517792,356393447,2410691914,3873677099,3682840055,3913112168,2491498743,4132185628,2489919796,1091903735,1979897079,3170134830,3567386728,3557303409,857797738,1136121015,1342202287,507115054,2535736646,337727348,3213592640,1301675037, -2528481711,1895095763,1721773893,3216771564,62756741,2142006736,835421444,2531993523,1442658625,3659876326,2882144922,676362277,1392781812,170690266,3921047035,1759253602,3611846912,1745797284,664899054,1329594018,3901205900,3045908486,2062866102,2865634940,3543621612,3464012697,1080764994,553557557,3656615353,3996768171,991055499,499776247,1265440854,648242737,3940784050,980351604,3713745714,1749149687,3396870395,4211799374,3640570775,1161844396,3125318951,1431517754,545492359,4268468663,3499529547, -1437099964,2702547544,3433638243,2581715763,2787789398,1060185593,1593081372,2418618748,4260947970,69676912,2159744348,86519011,2512459080,3838209314,1220612927,3339683548,133810670,1090789135,1078426020,1569222167,845107691,3583754449,4072456591,1091646820,628848692,1613405280,3757631651,526609435,236106946,48312990,2942717905,3402727701,1797494240,859738849,992217954,4005476642,2243076622,3870952857,3732016268,765654824,3490871365,2511836413,1685915746,3888969200,1414112111,2273134842,3281911079, -4080962846,172450625,2569994100,980381355,4109958455,2819808352,2716589560,2568741196,3681446669,3329971472,1835478071,660984891,3704678404,4045999559,3422617507,3040415634,1762651403,1719377915,3470491036,2693910283,3642056355,3138596744,1364962596,2073328063,1983633131,926494387,3423689081,2150032023,4096667949,1749200295,3328846651,309677260,2016342300,1779581495,3079819751,111262694,1274766160,443224088,298511866,1025883608,3806446537,1145181785,168956806,3641502830,3584813610,1689216846,3666258015, -3200248200,1692713982,2646376535,4042768518,1618508792,1610833997,3523052358,4130873264,2001055236,3610705100,2202168115,4028541809,2961195399,1006657119,2006996926,3186142756,1430667929,3210227297,1314452623,4074634658,4101304120,2273951170,1399257539,3367210612,3027628629,1190975929,2062231137,2333990788,2221543033,2438960610,1181637006,548689776,2362791313,3372408396,3104550113,3145860560,296247880,1970579870,3078560182,3769228297,1714227617,3291629107,3898220290,166772364,1251581989,493813264, -448347421,195405023,2709975567,677966185,3703036547,1463355134,2715995803,1338867538,1343315457,2802222074,2684532164,233230375,2599980071,2000651841,3277868038,1638401717,4028070440,3237316320,6314154,819756386,300326615,590932579,1405279636,3267499572,3150704214,2428286686,3959192993,3461946742,1862657033,1266418056,963775037,2089974820,2263052895,1917689273,448879540,3550394620,3981727096,150775221,3627908307,1303187396,508620638,2975983352,2726630617,1817252668,1876281319,1457606340,908771278, -3720792119,3617206836,2455994898,1729034894,1080033504,976866871,3556439503,2881648439,1522871579,1555064734,1336096578,3548522304,2579274686,3574697629,3205460757,3593280638,3338716283,3079412587,564236357,2993598910,1781952180,1464380207,3163844217,3332601554,1699332808,1393555694,1183702653,3581086237,1288719814,691649499,2847557200,2895455976,3193889540,2717570544,1781354906,1676643554,2592534050,3230253752,1126444790,2770207658,2633158820,2210423226,2615765581,2414155088,3127139286,673620729, -2805611233,1269405062,4015350505,3341807571,4149409754,1057255273,2012875353,2162469141,2276492801,2601117357,993977747,3918593370,2654263191,753973209,36408145,2530585658,25011837,3520020182,2088578344,530523599,2918365339,1524020338,1518925132,3760827505,3759777254,1202760957,3985898139,3906192525,674977740,4174734889,2031300136,2019492241,3983892565,4153806404,3822280332,352677332,2297720250,60907813,90501309,3286998549,1016092578,2535922412,2839152426,457141659,509813237,4120667899,652014361, -1966332200,2975202805,55981186,2327461051,676427537,3255491064,2882294119,3433927263,1307055953,942726286,933058658,2468411793,3933900994,4215176142,1361170020,2001714738,2830558078,3274259782,1222529897,1679025792,2729314320,3714953764,1770335741,151462246,3013232138,1682292957,1483529935,471910574,1539241949,458788160,3436315007,1807016891,3718408830,978976581,1043663428,3165965781,1927990952,4200891579,2372276910,3208408903,3533431907,1412390302,2931980059,4132332400,1947078029,3881505623,4168226417, -2941484381,1077988104,1320477388,886195818,18198404,3786409E3,2509781533,112762804,3463356488,1866414978,891333506,18488651,661792760,1628790961,3885187036,3141171499,876946877,2693282273,1372485963,791857591,2686433993,3759982718,3167212022,3472953795,2716379847,445679433,3561995674,3504004811,3574258232,54117162,3331405415,2381918588,3769707343,4154350007,1140177722,4074052095,668550556,3214352940,367459370,261225585,2610173221,4209349473,3468074219,3265815641,314222801,3066103646,3808782860,282218597, -3406013506,3773591054,379116347,1285071038,846784868,2669647154,3771962079,3550491691,2305946142,453669953,1268987020,3317592352,3279303384,3744833421,2610507566,3859509063,266596637,3847019092,517658769,3462560207,3443424879,370717030,4247526661,2224018117,4143653529,4112773975,2788324899,2477274417,1456262402,2901442914,1517677493,1846949527,2295493580,3734397586,2176403920,1280348187,1908823572,3871786941,846861322,1172426758,3287448474,3383383037,1655181056,3139813346,901632758,1897031941,2986607138, -3066810236,3447102507,1393639104,373351379,950779232,625454576,3124240540,4148612726,2007998917,544563296,2244738638,2330496472,2058025392,1291430526,424198748,50039436,29584100,3605783033,2429876329,2791104160,1057563949,3255363231,3075367218,3463963227,1469046755,985887462],B=[1332899944,1700884034,1701343084,1684370003,1668446532,1869963892],x=q;m.setRandomPolyfill=function(c){x=c};m.genSaltSync=function(c){c||(c=10);var a;c=c||10;(4>c||31<c)&&p(Error("Illegal number of rounds: "+c));var b=[]; -b.push("$2a$");10>c&&b.push("0");b.push(c.toString());b.push("$");try{b.push(v.b(G(),16)),a=b.join("")}catch(f){p(f)}return a};m.genSalt=function(c,a,b){"function"==typeof a&&(b=a,a=-1);var f;"function"==typeof c?(b=c,f=10):f=parseInt(c,10);"function"!=typeof b&&p(Error("Illegal or missing 'callback': "+b));z(function(){try{var a=m.genSaltSync(f);b(q,a)}catch(c){b(c,q)}})};m.hashSync=function(c,a){a||(a=10);"number"==typeof a&&(a=m.genSaltSync(a));return w(c,a)};m.hash=function(c,a,b){"function"!= -typeof b&&p(Error("Illegal 'callback': "+b));"number"==typeof a?m.genSalt(a,function(a,d){w(c,d,b)}):w(c,a,b)};m.compareSync=function(c,a){("string"!=typeof c||"string"!=typeof a)&&p(Error("Illegal argument types: "+typeof c+", "+typeof a));60!=a.length&&p(Error("Illegal hash length: "+a.length+" != 60"));for(var b=m.hashSync(c,a.substr(0,a.length-31)),f=b.length==a.length,d=b.length<a.length?b.length:a.length,e=0;e<d;++e)b.length>=e&&(a.length>=e&&b[e]!=a[e])&&(f=!1);return f};m.compare=function(c, -a,b){"function"!=typeof b&&p(Error("Illegal 'callback': "+b));m.hash(c,a.substr(0,29),function(c,d){b(c,a===d)})};m.getRounds=function(c){"string"!=typeof c&&p(Error("Illegal type of 'hash': "+typeof c));return parseInt(c.split("$")[2],10)};m.getSalt=function(c){"string"!=typeof c&&p(Error("Illegal type of 'hash': "+typeof c));60!=c.length&&p(Error("Illegal hash length: "+c.length+" != 60"));return c.substring(0,29)};"undefined"!=typeof module&&module.exports?module.exports=m:"undefined"!=typeof define&& -define.amd?define("bcrypt",function(){return m}):(n.dcodeIO||(n.dcodeIO={}),n.dcodeIO.bcrypt=m)})(this); +function l(t){throw t;}var p=null; +(function(t){function B(c){if("undefined"!==typeof module&&module&&module.exports)try{return require("crypto").randomBytes(c)}catch(a){}try{var b;(t.crypto||t.msCrypto).getRandomValues(b=new Uint32Array(c));return Array.prototype.slice.call(b)}catch(d){}x||l(Error("Neither WebCryptoAPI nor a crypto module is available. Use bcrypt.setRandomFallback to set an alternative"));return x(c)}function F(c){var a=[],b=0;G.f(function(){return b>=c.length?p:c.charCodeAt(b++)},function(b){a.push(b)});return a} +function y(c,a){var b=0,d=[],f,e;for((0>=a||a>c.length)&&l(Error("Illegal len: "+a));b<a;){f=c[b++]&255;d.push(u[f>>2&63]);f=(f&3)<<4;if(b>=a){d.push(u[f&63]);break}e=c[b++]&255;f|=e>>4&15;d.push(u[f&63]);f=(e&15)<<2;if(b>=a){d.push(u[f&63]);break}e=c[b++]&255;f|=e>>6&3;d.push(u[f&63]);d.push(u[e&63])}return d.join("")}function H(c){for(var a=0,b=c.length,d=0,f=[],e,k,h;a<b-1&&16>d;){h=c.charCodeAt(a++);e=h<r.length?r[h]:-1;h=c.charCodeAt(a++);k=h<r.length?r[h]:-1;if(-1==e||-1==k)break;h=e<<2>>>0; +h|=(k&48)>>4;f.push(z(h));if(16<=++d||a>=b)break;h=c.charCodeAt(a++);e=h<r.length?r[h]:-1;if(-1==e)break;h=(k&15)<<4>>>0;h|=(e&60)>>2;f.push(z(h));if(16<=++d||a>=b)break;h=c.charCodeAt(a++);k=h<r.length?r[h]:-1;h=(e&3)<<6>>>0;h|=k;f.push(z(h));++d}c=[];for(a=0;a<d;a++)c.push(f[a].charCodeAt(0));return c}function w(c,a,b,d){for(var f,e=c[a],k=c[a+1],e=e^b[0],h=0;14>=h;)f=d[e>>24&255],f+=d[256|e>>16&255],f^=d[512|e>>8&255],f+=d[768|e&255],k^=f^b[++h],f=d[k>>24&255],f+=d[256|k>>16&255],f^=d[512|k>>8& +255],f+=d[768|k&255],e^=f^b[++h];c[a]=k^b[17];c[a+1]=e;return c}function v(c,a){for(var b=0,d=0;4>b;++b)d=d<<8|c[a]&255,a=(a+1)%c.length;return{key:d,a:a}}function C(c,a,b){for(var d=0,f=[0,0],e=a.length,k=b.length,h,g=0;g<e;g++)h=v(c,d),d=h.a,a[g]^=h.key;for(g=0;g<e;g+=2)f=w(f,0,a,b),a[g]=f[0],a[g+1]=f[1];for(g=0;g<k;g+=2)f=w(f,0,a,b),b[g]=f[0],b[g+1]=f[1]}function I(c,a,b,d){for(var f=0,e=[0,0],k=b.length,h=d.length,g,m=0;m<k;m++)g=v(a,f),f=g.a,b[m]^=g.key;for(m=f=0;m<k;m+=2)g=v(c,f),f=g.a,e[0]^= +g.key,g=v(c,f),f=g.a,e[1]^=g.key,e=w(e,0,b,d),b[m]=e[0],b[m+1]=e[1];for(m=0;m<h;m+=2)g=v(c,f),f=g.a,e[0]^=g.key,g=v(c,f),f=g.a,e[1]^=g.key,e=w(e,0,b,d),d[m]=e[0],d[m+1]=e[1]}function D(c,a,b,d,f){function e(){f&&f(q/b);if(q<b)for(var g=Date.now();q<b&&!(q+=1,C(c,m,n),C(a,m,n),100<Date.now()-g););else{for(q=0;64>q;q++)for(r=0;r<h>>1;r++)w(k,r<<1,m,n);g=[];for(q=0;q<h;q++)g.push((k[q]>>24&255)>>>0),g.push((k[q]>>16&255)>>>0),g.push((k[q]>>8&255)>>>0),g.push((k[q]&255)>>>0);if(d){d(p,g);return}return g}d&& +s(e)}var k=E.slice(),h=k.length,g;if(4>b||31<b){g=Error("Illegal number of rounds (4-31): "+b);if(d){s(d.bind(this,g));return}l(g)}if(16!==a.length){g=Error("Illegal salt length: "+a.length+" != 16");if(d){s(d.bind(this,g));return}l(g)}b=1<<b;var m=J.slice(),n=K.slice(),q=0,r;I(a,c,m,n);if("undefined"!==typeof d)e();else for(;;)if("undefined"!==typeof(g=e()))return g||[]}function A(c,a,b,d){function f(a){var b=[];b.push("$2");"a"<=k&&b.push(k);b.push("$");10>g&&b.push("0");b.push(g.toString());b.push("$"); +b.push(y(m,m.length));b.push(y(a,4*E.length-1));return b.join("")}var e;if("string"!==typeof c||"string"!==typeof a){e=Error("Invalid string / salt: Not a string");if(b){s(b.bind(this,e));return}l(e)}var k,h;if("$"!==a.charAt(0)||"2"!==a.charAt(1)){e=Error("Invalid salt version: "+a.substring(0,2));if(b){s(b.bind(this,e));return}l(e)}if("$"===a.charAt(2))k=String.fromCharCode(0),h=3;else{k=a.charAt(2);if("a"!==k&&"y"!==k||"$"!==a.charAt(3)){e=Error("Invalid salt revision: "+a.substring(2,4));if(b){s(b.bind(this, +e));return}l(e)}h=4}if("$"<a.charAt(h+2)){e=Error("Missing salt rounds");if(b){s(b.bind(this,e));return}l(e)}var g=10*parseInt(a.substring(h,h+1),10)+parseInt(a.substring(h+1,h+2),10);a=a.substring(h+3,h+25);c=F(c+("a"<=k?"\x00":""));var m=H(a);if("undefined"==typeof b)return f(D(c,m,g));D(c,m,g,function(a,d){a?b(a,p):b(p,f(d))},d)}var n={},x=p;try{B(1)}catch(L){}x=p;n.l=function(c){x=c};n.genSaltSync=function(c,a){"undefined"===typeof c?c=10:"number"!==typeof c&&l(Error("Illegal arguments: "+typeof c+ +", "+typeof a));(4>c||31<c)&&l(Error("Illegal number of rounds (4-31): "+c));var b=[];b.push("$2a$");10>c&&b.push("0");b.push(c.toString());b.push("$");b.push(y(B(16),16));return b.join("")};n.genSalt=function(c,a,b){"function"===typeof a&&(b=a,a=void 0);"function"===typeof c&&(b=c,c=10);"function"!==typeof b&&l(Error("Illegal callback: "+typeof b));"number"!==typeof c?s(b.bind(this,Error("Illegal arguments: "+typeof c))):s(function(){try{b(p,n.genSaltSync(c))}catch(a){b(a)}})};n.hashSync=function(c, +a){"undefined"===typeof a&&(a=10);"number"===typeof a&&(a=n.genSaltSync(a));("string"!==typeof c||"string"!==typeof a)&&l(Error("Illegal arguments: "+typeof c+", "+typeof a));return A(c,a)};n.hash=function(c,a,b,d){"function"!==typeof b&&l(Error("Illegal callback: "+typeof b));"string"===typeof c&&"number"===typeof a?n.genSalt(a,function(a,e){A(c,e,b,d)}):"string"===typeof c&&"string"===typeof a?A(c,a,b,d):s(b.bind(this,Error("Illegal arguments: "+typeof c+", "+typeof a)))};n.compareSync=function(c, +a){("string"!==typeof c||"string"!==typeof a)&&l(Error("Illegal arguments: "+typeof c+", "+typeof a));if(60!==a.length)return!1;for(var b=n.hashSync(c,a.substr(0,a.length-31)),d=b.length===a.length,f=b.length<a.length?b.length:a.length,e=0;e<f;++e)b.length>=e&&(a.length>=e&&b[e]!=a[e])&&(d=!1);return d};n.compare=function(c,a,b,d){"function"!==typeof b&&l(Error("Illegal callback: "+typeof b));"string"!==typeof c||"string"!==typeof a?s(b.bind(this,Error("Illegal arguments: "+typeof c+", "+typeof a))): +n.hash(c,a.substr(0,29),function(d,c){b(d,a===c)},d)};n.getRounds=function(c){"string"!==typeof c&&l(Error("Illegal arguments: "+typeof c));return parseInt(c.split("$")[2],10)};n.getSalt=function(c){"string"!==typeof c&&l(Error("Illegal arguments: "+typeof c));60!==c.length&&l(Error("Illegal hash length: "+c.length+" != 60"));return c.substring(0,29)};var s="undefined"!==typeof process&&process&&"function"===typeof process.nextTick?"function"===typeof setImmediate?setImmediate:process.nextTick:setTimeout, +u="./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789".split(""),r=[-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,0,1,54,55,56,57,58,59,60,61,62,63,-1,-1,-1,-1,-1,-1,-1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,-1,-1,-1,-1,-1,-1,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,-1,-1,-1,-1,-1],z=String.fromCharCode,G=function(){var c={h:1114111, +g:function(a,b){var d=p;"number"===typeof a&&(d=a,a=function(){return p});for(;d!==p||(d=a())!==p;)128>d?b(d&127):(2048>d?b(d>>6&31|192):(65536>d?b(d>>12&15|224):(b(d>>18&7|240),b(d>>12&63|128)),b(d>>6&63|128)),b(d&63|128)),d=p},e:function(a,b){function d(a){a=a.slice(0,a.indexOf(p));var b=Error(a.toString());b.name="TruncatedError";b.bytes=a;l(b)}for(var c,e,k,h;(c=a())!==p;)0===(c&128)?b(c):192===(c&224)?((e=a())===p&&d([c,e]),b((c&31)<<6|e&63)):224===(c&240)?(((e=a())===p||(k=a())===p)&&d([c,e, +k]),b((c&15)<<12|(e&63)<<6|k&63)):240===(c&248)?(((e=a())===p||(k=a())===p||(h=a())===p)&&d([c,e,k,h]),b((c&7)<<18|(e&63)<<12|(k&63)<<6|h&63)):l(RangeError("Illegal starting byte: "+c))},b:function(a,b){for(var c,f=p;(c=f!==p?f:a())!==p;)55296<=c&&57343>=c&&(f=a())!==p&&56320<=f&&57343>=f?(b(1024*(c-55296)+f-56320+65536),f=p):b(c);f!==p&&b(f)},d:function(a,b){var c=p;"number"===typeof a&&(c=a,a=function(){return p});for(;c!==p||(c=a())!==p;)65535>=c?b(c):(c-=65536,b((c>>10)+55296),b(c%1024+56320)), +c=p},f:function(a,b){c.b(a,function(a){c.g(a,b)})},k:function(a,b){c.e(a,function(a){c.d(a,b)})},c:function(a){return 128>a?1:2048>a?2:65536>a?3:4},j:function(a){for(var b,d=0;(b=a())!==p;)d+=c.c(b);return d},i:function(a){var b=0,d=0;c.b(a,function(a){++b;d+=c.c(a)});return[b,d]}};return c}();Date.now=Date.now||function(){return+new Date};var J=[608135816,2242054355,320440878,57701188,2752067618,698298832,137296536,3964562569,1160258022,953160567,3193202383,887688300,3232508343,3380367581,1065670069, +3041331479,2450970073,2306472731],K=[3509652390,2564797868,805139163,3491422135,3101798381,1780907670,3128725573,4046225305,614570311,3012652279,134345442,2240740374,1667834072,1901547113,2757295779,4103290238,227898511,1921955416,1904987480,2182433518,2069144605,3260701109,2620446009,720527379,3318853667,677414384,3393288472,3101374703,2390351024,1614419982,1822297739,2954791486,3608508353,3174124327,2024746970,1432378464,3864339955,2857741204,1464375394,1676153920,1439316330,715854006,3033291828, +289532110,2706671279,2087905683,3018724369,1668267050,732546397,1947742710,3462151702,2609353502,2950085171,1814351708,2050118529,680887927,999245976,1800124847,3300911131,1713906067,1641548236,4213287313,1216130144,1575780402,4018429277,3917837745,3693486850,3949271944,596196993,3549867205,258830323,2213823033,772490370,2760122372,1774776394,2652871518,566650946,4142492826,1728879713,2882767088,1783734482,3629395816,2517608232,2874225571,1861159788,326777828,3124490320,2130389656,2716951837,967770486, +1724537150,2185432712,2364442137,1164943284,2105845187,998989502,3765401048,2244026483,1075463327,1455516326,1322494562,910128902,469688178,1117454909,936433444,3490320968,3675253459,1240580251,122909385,2157517691,634681816,4142456567,3825094682,3061402683,2540495037,79693498,3249098678,1084186820,1583128258,426386531,1761308591,1047286709,322548459,995290223,1845252383,2603652396,3431023940,2942221577,3202600964,3727903485,1712269319,422464435,3234572375,1170764815,3523960633,3117677531,1434042557, +442511882,3600875718,1076654713,1738483198,4213154764,2393238008,3677496056,1014306527,4251020053,793779912,2902807211,842905082,4246964064,1395751752,1040244610,2656851899,3396308128,445077038,3742853595,3577915638,679411651,2892444358,2354009459,1767581616,3150600392,3791627101,3102740896,284835224,4246832056,1258075500,768725851,2589189241,3069724005,3532540348,1274779536,3789419226,2764799539,1660621633,3471099624,4011903706,913787905,3497959166,737222580,2514213453,2928710040,3937242737,1804850592, +3499020752,2949064160,2386320175,2390070455,2415321851,4061277028,2290661394,2416832540,1336762016,1754252060,3520065937,3014181293,791618072,3188594551,3933548030,2332172193,3852520463,3043980520,413987798,3465142937,3030929376,4245938359,2093235073,3534596313,375366246,2157278981,2479649556,555357303,3870105701,2008414854,3344188149,4221384143,3956125452,2067696032,3594591187,2921233993,2428461,544322398,577241275,1471733935,610547355,4027169054,1432588573,1507829418,2025931657,3646575487,545086370, +48609733,2200306550,1653985193,298326376,1316178497,3007786442,2064951626,458293330,2589141269,3591329599,3164325604,727753846,2179363840,146436021,1461446943,4069977195,705550613,3059967265,3887724982,4281599278,3313849956,1404054877,2845806497,146425753,1854211946,1266315497,3048417604,3681880366,3289982499,290971E4,1235738493,2632868024,2414719590,3970600049,1771706367,1449415276,3266420449,422970021,1963543593,2690192192,3826793022,1062508698,1531092325,1804592342,2583117782,2714934279,4024971509, +1294809318,4028980673,1289560198,2221992742,1669523910,35572830,157838143,1052438473,1016535060,1802137761,1753167236,1386275462,3080475397,2857371447,1040679964,2145300060,2390574316,1461121720,2956646967,4031777805,4028374788,33600511,2920084762,1018524850,629373528,3691585981,3515945977,2091462646,2486323059,586499841,988145025,935516892,3367335476,2599673255,2839830854,265290510,3972581182,2759138881,3795373465,1005194799,847297441,406762289,1314163512,1332590856,1866599683,4127851711,750260880, +613907577,1450815602,3165620655,3734664991,3650291728,3012275730,3704569646,1427272223,778793252,1343938022,2676280711,2052605720,1946737175,3164576444,3914038668,3967478842,3682934266,1661551462,3294938066,4011595847,840292616,3712170807,616741398,312560963,711312465,1351876610,322626781,1910503582,271666773,2175563734,1594956187,70604529,3617834859,1007753275,1495573769,4069517037,2549218298,2663038764,504708206,2263041392,3941167025,2249088522,1514023603,1998579484,1312622330,694541497,2582060303, +2151582166,1382467621,776784248,2618340202,3323268794,2497899128,2784771155,503983604,4076293799,907881277,423175695,432175456,1378068232,4145222326,3954048622,3938656102,3820766613,2793130115,2977904593,26017576,3274890735,3194772133,1700274565,1756076034,4006520079,3677328699,720338349,1533947780,354530856,688349552,3973924725,1637815568,332179504,3949051286,53804574,2852348879,3044236432,1282449977,3583942155,3416972820,4006381244,1617046695,2628476075,3002303598,1686838959,431878346,2686675385, +1700445008,1080580658,1009431731,832498133,3223435511,2605976345,2271191193,2516031870,1648197032,4164389018,2548247927,300782431,375919233,238389289,3353747414,2531188641,2019080857,1475708069,455242339,2609103871,448939670,3451063019,1395535956,2413381860,1841049896,1491858159,885456874,4264095073,4001119347,1565136089,3898914787,1108368660,540939232,1173283510,2745871338,3681308437,4207628240,3343053890,4016749493,1699691293,1103962373,3625875870,2256883143,3830138730,1031889488,3479347698,1535977030, +4236805024,3251091107,2132092099,1774941330,1199868427,1452454533,157007616,2904115357,342012276,595725824,1480756522,206960106,497939518,591360097,863170706,2375253569,3596610801,1814182875,2094937945,3421402208,1082520231,3463918190,2785509508,435703966,3908032597,1641649973,2842273706,3305899714,1510255612,2148256476,2655287854,3276092548,4258621189,236887753,3681803219,274041037,1734335097,3815195456,3317970021,1899903192,1026095262,4050517792,356393447,2410691914,3873677099,3682840055,3913112168, +2491498743,4132185628,2489919796,1091903735,1979897079,3170134830,3567386728,3557303409,857797738,1136121015,1342202287,507115054,2535736646,337727348,3213592640,1301675037,2528481711,1895095763,1721773893,3216771564,62756741,2142006736,835421444,2531993523,1442658625,3659876326,2882144922,676362277,1392781812,170690266,3921047035,1759253602,3611846912,1745797284,664899054,1329594018,3901205900,3045908486,2062866102,2865634940,3543621612,3464012697,1080764994,553557557,3656615353,3996768171,991055499, +499776247,1265440854,648242737,3940784050,980351604,3713745714,1749149687,3396870395,4211799374,3640570775,1161844396,3125318951,1431517754,545492359,4268468663,3499529547,1437099964,2702547544,3433638243,2581715763,2787789398,1060185593,1593081372,2418618748,4260947970,69676912,2159744348,86519011,2512459080,3838209314,1220612927,3339683548,133810670,1090789135,1078426020,1569222167,845107691,3583754449,4072456591,1091646820,628848692,1613405280,3757631651,526609435,236106946,48312990,2942717905, +3402727701,1797494240,859738849,992217954,4005476642,2243076622,3870952857,3732016268,765654824,3490871365,2511836413,1685915746,3888969200,1414112111,2273134842,3281911079,4080962846,172450625,2569994100,980381355,4109958455,2819808352,2716589560,2568741196,3681446669,3329971472,1835478071,660984891,3704678404,4045999559,3422617507,3040415634,1762651403,1719377915,3470491036,2693910283,3642056355,3138596744,1364962596,2073328063,1983633131,926494387,3423689081,2150032023,4096667949,1749200295,3328846651, +309677260,2016342300,1779581495,3079819751,111262694,1274766160,443224088,298511866,1025883608,3806446537,1145181785,168956806,3641502830,3584813610,1689216846,3666258015,3200248200,1692713982,2646376535,4042768518,1618508792,1610833997,3523052358,4130873264,2001055236,3610705100,2202168115,4028541809,2961195399,1006657119,2006996926,3186142756,1430667929,3210227297,1314452623,4074634658,4101304120,2273951170,1399257539,3367210612,3027628629,1190975929,2062231137,2333990788,2221543033,2438960610, +1181637006,548689776,2362791313,3372408396,3104550113,3145860560,296247880,1970579870,3078560182,3769228297,1714227617,3291629107,3898220290,166772364,1251581989,493813264,448347421,195405023,2709975567,677966185,3703036547,1463355134,2715995803,1338867538,1343315457,2802222074,2684532164,233230375,2599980071,2000651841,3277868038,1638401717,4028070440,3237316320,6314154,819756386,300326615,590932579,1405279636,3267499572,3150704214,2428286686,3959192993,3461946742,1862657033,1266418056,963775037, +2089974820,2263052895,1917689273,448879540,3550394620,3981727096,150775221,3627908307,1303187396,508620638,2975983352,2726630617,1817252668,1876281319,1457606340,908771278,3720792119,3617206836,2455994898,1729034894,1080033504,976866871,3556439503,2881648439,1522871579,1555064734,1336096578,3548522304,2579274686,3574697629,3205460757,3593280638,3338716283,3079412587,564236357,2993598910,1781952180,1464380207,3163844217,3332601554,1699332808,1393555694,1183702653,3581086237,1288719814,691649499,2847557200, +2895455976,3193889540,2717570544,1781354906,1676643554,2592534050,3230253752,1126444790,2770207658,2633158820,2210423226,2615765581,2414155088,3127139286,673620729,2805611233,1269405062,4015350505,3341807571,4149409754,1057255273,2012875353,2162469141,2276492801,2601117357,993977747,3918593370,2654263191,753973209,36408145,2530585658,25011837,3520020182,2088578344,530523599,2918365339,1524020338,1518925132,3760827505,3759777254,1202760957,3985898139,3906192525,674977740,4174734889,2031300136,2019492241, +3983892565,4153806404,3822280332,352677332,2297720250,60907813,90501309,3286998549,1016092578,2535922412,2839152426,457141659,509813237,4120667899,652014361,1966332200,2975202805,55981186,2327461051,676427537,3255491064,2882294119,3433927263,1307055953,942726286,933058658,2468411793,3933900994,4215176142,1361170020,2001714738,2830558078,3274259782,1222529897,1679025792,2729314320,3714953764,1770335741,151462246,3013232138,1682292957,1483529935,471910574,1539241949,458788160,3436315007,1807016891, +3718408830,978976581,1043663428,3165965781,1927990952,4200891579,2372276910,3208408903,3533431907,1412390302,2931980059,4132332400,1947078029,3881505623,4168226417,2941484381,1077988104,1320477388,886195818,18198404,3786409E3,2509781533,112762804,3463356488,1866414978,891333506,18488651,661792760,1628790961,3885187036,3141171499,876946877,2693282273,1372485963,791857591,2686433993,3759982718,3167212022,3472953795,2716379847,445679433,3561995674,3504004811,3574258232,54117162,3331405415,2381918588, +3769707343,4154350007,1140177722,4074052095,668550556,3214352940,367459370,261225585,2610173221,4209349473,3468074219,3265815641,314222801,3066103646,3808782860,282218597,3406013506,3773591054,379116347,1285071038,846784868,2669647154,3771962079,3550491691,2305946142,453669953,1268987020,3317592352,3279303384,3744833421,2610507566,3859509063,266596637,3847019092,517658769,3462560207,3443424879,370717030,4247526661,2224018117,4143653529,4112773975,2788324899,2477274417,1456262402,2901442914,1517677493, +1846949527,2295493580,3734397586,2176403920,1280348187,1908823572,3871786941,846861322,1172426758,3287448474,3383383037,1655181056,3139813346,901632758,1897031941,2986607138,3066810236,3447102507,1393639104,373351379,950779232,625454576,3124240540,4148612726,2007998917,544563296,2244738638,2330496472,2058025392,1291430526,424198748,50039436,29584100,3605783033,2429876329,2791104160,1057563949,3255363231,3075367218,3463963227,1469046755,985887462],E=[1332899944,1700884034,1701343084,1684370003,1668446532, +1869963892];"undefined"!==typeof module&&module.exports?module.exports=n:"undefined"!==typeof define&&define.amd?define(function(){return n}):(t.dcodeIO=t.dcodeIO||{}).bcrypt=n})(this);})(); diff --git a/p/scripts/category.js b/p/scripts/category.js new file mode 100644 index 000000000..c33e68528 --- /dev/null +++ b/p/scripts/category.js @@ -0,0 +1,118 @@ +"use strict"; + +var loading = false, + dnd_successful = false; + +function dragend_process(t) { + t.style.display = 'none'; + + if (loading) { + window.setTimeout(function() { + dragend_process(t); + }, 50); + } + + if (!dnd_successful) { + t.style.display = 'block'; + t.style.opacity = 1.0; + } else { + var parent = $(t.parentNode); + $(t).remove(); + + if (parent.children().length <= 0) { + parent.append('<li class="item disabled" dropzone="move">' + i18n['category_empty'] + '</li>'); + } + } +} + +function init_draggable() { + if (!(window.$ && window.i18n)) { + if (window.console) { + console.log('FreshRSS waiting for JS…'); + } + window.setTimeout(init_draggable, 50); + return; + } + + $.event.props.push('dataTransfer'); + + var draggable = '[draggable="true"]', + dropzone = '[dropzone="move"]'; + + $('.drop-section').on('dragstart', draggable, function(e) { + e.dataTransfer.effectAllowed = 'move'; + e.dataTransfer.setData('text/html', e.target.outerHTML); + e.dataTransfer.setData('text', e.target.getAttribute('data-feed-id')); + e.target.style.opacity = 0.3; + + dnd_successful = false; + }); + $('.drop-section').on('dragend', draggable, function(e) { + dragend_process(e.target); + }); + + $('.drop-section').on('dragenter', dropzone, function(e) { + $(this).addClass('drag-hover'); + + e.preventDefault(); + }); + $('.drop-section').on('dragleave', dropzone, function(e) { + var pos_this = $(this).position(), + scroll_top = $(document).scrollTop(), + top = pos_this.top, + left = pos_this.left, + right = left + $(this).width(), + bottom = top + $(this).height(), + mouse_x = e.originalEvent.screenX, + mouse_y = e.originalEvent.clientY + scroll_top; + + if (left <= mouse_x && mouse_x <= right && + top <= mouse_y && mouse_y <= bottom) { + // HACK because dragleave is triggered when hovering children! + return; + } + $(this).removeClass('drag-hover'); + }); + $('.drop-section').on('dragover', dropzone, function(e) { + e.dataTransfer.dropEffect = "move"; + + e.preventDefault(); + return false; + }); + $('.drop-section').on('drop', dropzone, function(e) { + var feed_id = e.dataTransfer.getData('text'), + cat_id = e.target.parentNode.getAttribute('data-cat-id'); + + loading = true; + + $.ajax({ + type: 'POST', + url: './?c=feed&a=move', + data : { + f_id: feed_id, + c_id: cat_id + } + }).success(function() { + $(e.target).after(e.dataTransfer.getData('text/html')); + if ($(e.target).hasClass('disabled')) { + $(e.target).remove(); + } + dnd_successful = true; + }).complete(function() { + loading = false; + }); + + $(this).removeClass('drag-hover'); + + e.preventDefault(); + }); +} + + +if (document.readyState && document.readyState !== 'loading') { + init_draggable(); +} else if (document.addEventListener) { + document.addEventListener('DOMContentLoaded', function () { + init_draggable(); + }, false); +} diff --git a/p/scripts/global_view.js b/p/scripts/global_view.js index 7105520a6..7d7ba22b5 100644 --- a/p/scripts/global_view.js +++ b/p/scripts/global_view.js @@ -9,7 +9,7 @@ function load_panel(link) { panel_loading = true; $.get(link, function (data) { - $("#panel").append($(".nav_menu, #stream .day, #stream .flux, #stream .pagination", data)); + $("#panel").append($(".nav_menu, #stream .day, #stream .flux, #stream .pagination, #stream.prompt", data)); $("#panel .nav_menu").children().not("#nav_menu_read_all").remove(); @@ -24,12 +24,13 @@ function load_panel(link) { // en en ouvrant une autre ensuite, on se retrouve au même point de scroll $("#panel").scrollTop(0); - $('#panel').on('click', '#nav_menu_read_all > a, #nav_menu_read_all .item > a, #bigMarkAsRead', function () { + $('#panel').on('click', '#nav_menu_read_all button, #bigMarkAsRead', function () { + console.log($(this).attr("formaction")); $.ajax({ - url: $(this).attr("href"), + type: "POST", + url: $(this).attr("formaction"), async: false }); - //$("#panel .close").first().click(); window.location.reload(false); return false; }); @@ -39,9 +40,8 @@ function load_panel(link) { } function init_close_panel() { - $("#panel .close").click(function () { - $("#panel").html('<a class="close" href="#">' + window.iconClose + '</a>'); - init_close_panel(); + $("#overlay .close").click(function () { + $("#panel").html(''); $("#panel").slideToggle(); $("#overlay").fadeOut(); @@ -50,7 +50,8 @@ function init_close_panel() { } function init_global_view() { - $("#stream .box-category a").click(function () { + // TODO: should be based on generic classes. + $(".box a").click(function () { var link = $(this).attr("href"); load_panel(link); diff --git a/p/scripts/main.js b/p/scripts/main.js index 00cd96fbe..dc5428048 100644 --- a/p/scripts/main.js +++ b/p/scripts/main.js @@ -4,14 +4,6 @@ var $stream = null, shares = 0, ajax_loading = false; -function is_normal_mode() { - return $stream.hasClass('normal'); -} - -function is_global_mode() { - return $stream.hasClass('global'); -} - function redirect(url, new_tab) { if (url) { if (new_tab) { @@ -33,7 +25,7 @@ function needsScroll($elem) { } function str2int(str) { - if (str == '' || str === undefined) { + if (!str) { return 0; } return parseInt(str.replace(/\D/g, ''), 10) || 0; @@ -64,31 +56,31 @@ function incLabel(p, inc, spaceAfter) { function incUnreadsFeed(article, feed_id, nb) { //Update unread: feed - var elem = $('#' + feed_id + '>.feed').get(0), + var elem = $('#' + feed_id).get(0), feed_unreads = elem ? str2int(elem.getAttribute('data-unread')) : 0, feed_priority = elem ? str2int(elem.getAttribute('data-priority')) : 0; if (elem) { - elem.setAttribute('data-unread', numberFormat(feed_unreads + nb)); - elem = $(elem).closest('li').get(0); + elem.setAttribute('data-unread', feed_unreads + nb); + elem = $(elem).children('.item-title').get(0); if (elem) { - elem.setAttribute('data-unread', feed_unreads + nb); + elem.setAttribute('data-unread', numberFormat(feed_unreads + nb)); } } //Update unread: category - elem = $('#' + feed_id).parent().prevAll('.category').children(':first').get(0); + elem = $('#' + feed_id).parents('.category').get(0); feed_unreads = elem ? str2int(elem.getAttribute('data-unread')) : 0; if (elem) { - elem.setAttribute('data-unread', numberFormat(feed_unreads + nb)); - elem = $(elem).closest('li').get(0); + elem.setAttribute('data-unread', feed_unreads + nb); + elem = $(elem).find('.title').get(0); if (elem) { - elem.setAttribute('data-unread', feed_unreads + nb); + elem.setAttribute('data-unread', numberFormat(feed_unreads + nb)); } } //Update unread: all if (feed_priority > 0) { - elem = $('#aside_flux .all').children(':first').get(0); + elem = $('#aside_feed .all .title').get(0); if (elem) { feed_unreads = elem ? str2int(elem.getAttribute('data-unread')) : 0; elem.setAttribute('data-unread', numberFormat(feed_unreads + nb)); @@ -97,7 +89,7 @@ function incUnreadsFeed(article, feed_id, nb) { //Update unread: favourites if (article && article.closest('div').hasClass('favorite')) { - elem = $('#aside_flux .favorites').children(':first').get(0); + elem = $('#aside_feed .favorites .title').get(0); if (elem) { feed_unreads = elem ? str2int(elem.getAttribute('data-unread')) : 0; elem.setAttribute('data-unread', numberFormat(feed_unreads + nb)); @@ -105,7 +97,7 @@ function incUnreadsFeed(article, feed_id, nb) { } var isCurrentView = false; - //Update unread: title + // Update unread: title document.title = document.title.replace(/^((?:\([ 0-9]+\) )?)/, function (m, p1) { var $feed = $('#' + feed_id); if (article || ($feed.closest('.active').length > 0 && $feed.siblings('.active').length === 0)) { @@ -202,7 +194,7 @@ function mark_favorite(active) { } $b.find('.icon').replaceWith(data.icon); - var favourites = $('.favorites>a').contents().last().get(0); + var favourites = $('#aside_feed .favorites .title').contents().last().get(0); if (favourites && favourites.textContent) { favourites.textContent = favourites.textContent.replace(/((?: \([ 0-9]+\))?\s*)$/, function (m, p1) { return incLabel(p1, inc, false); @@ -210,7 +202,7 @@ function mark_favorite(active) { } if (active.closest('div').hasClass('not_read')) { - var elem = $('#aside_flux .favorites').children(':first').get(0), + var elem = $('#aside_feed .favorites .title').get(0), feed_unreads = elem ? str2int(elem.getAttribute('data-unread')) : 0; if (elem) { elem.setAttribute('data-unread', numberFormat(feed_unreads + inc)); @@ -226,7 +218,7 @@ function toggleContent(new_active, old_active) { return; } - if (does_lazyload) { + if (context['does_lazyload']) { new_active.find('img[data-original], iframe[data-original]').each(function () { this.setAttribute('src', this.getAttribute('data-original')); this.removeAttribute('data-original'); @@ -245,12 +237,12 @@ function toggleContent(new_active, old_active) { var box_to_move = "html,body", relative_move = false; - if (is_global_mode()) { + if (context['current_view'] == 'global') { box_to_move = "#panel"; relative_move = true; } - if (sticky_post) { + if (context['sticky_post']) { var prev_article = new_active.prevAll('.flux'), new_pos = new_active.position().top, old_scroll = $(box_to_move).scrollTop(); @@ -259,7 +251,7 @@ function toggleContent(new_active, old_active) { new_pos = prev_article.position().top; } - if (hide_posts) { + if (context['hide_posts']) { if (relative_move) { new_pos += old_scroll; } @@ -278,7 +270,7 @@ function toggleContent(new_active, old_active) { } } - if (auto_mark_article && new_active.hasClass('active')) { + if (context['auto_mark_article'] && new_active.hasClass('active')) { mark_read(new_active, true); } } @@ -300,42 +292,42 @@ function next_entry() { } function prev_feed() { - var active_feed = $("#aside_flux .feeds li.active"); + var active_feed = $("#aside_feed .tree-folder-items .item.active"); if (active_feed.length > 0) { - active_feed.prevAll(':visible:first').find('a.feed').each(function(){this.click();}); + active_feed.prevAll(':visible:first').find('a').each(function(){this.click();}); } else { last_feed(); } } function next_feed() { - var active_feed = $("#aside_flux .feeds li.active"); + var active_feed = $("#aside_feed .tree-folder-items .item.active"); if (active_feed.length > 0) { - active_feed.nextAll(':visible:first').find('a.feed').each(function(){this.click();}); + active_feed.nextAll(':visible:first').find('a').each(function(){this.click();}); } else { first_feed(); } } function first_feed() { - var feed = $("#aside_flux .feeds.active li:visible:first"); + var feed = $("#aside_feed .tree-folder-items.active .item:visible:first"); if (feed.length > 0) { feed.find('a')[1].click(); } } function last_feed() { - var feed = $("#aside_flux .feeds.active li:visible:last"); + var feed = $("#aside_feed .tree-folder-items.active .item:visible:last"); if (feed.length > 0) { feed.find('a')[1].click(); } } function prev_category() { - var active_cat = $("#aside_flux .category.stick.active"); + var active_cat = $("#aside_feed .tree-folder.active"); if (active_cat.length > 0) { - var prev_cat = active_cat.parent('li').prevAll(':visible:first').find('.category.stick a.btn'); + var prev_cat = active_cat.prevAll(':visible:first').find('.tree-folder-title .title'); if (prev_cat.length > 0) { prev_cat[0].click(); } @@ -346,10 +338,10 @@ function prev_category() { } function next_category() { - var active_cat = $("#aside_flux .category.stick.active"); + var active_cat = $("#aside_feed .tree-folder.active"); if (active_cat.length > 0) { - var next_cat = active_cat.parent('li').nextAll(':visible:first').find('.category.stick a.btn'); + var next_cat = active_cat.nextAll(':visible:first').find('.tree-folder-title .title'); if (next_cat.length > 0) { next_cat[0].click(); } @@ -360,16 +352,16 @@ function next_category() { } function first_category() { - var cat = $("#aside_flux .category.stick:visible:first"); + var cat = $("#aside_feed .tree-folder:visible:first"); if (cat.length > 0) { - cat.find('a.btn')[0].click(); + cat.find('.tree-folder-title .title')[0].click(); } } function last_category() { - var cat = $("#aside_flux .category.stick:visible:last"); + var cat = $("#aside_feed .tree-folder:visible:last"); if (cat.length > 0) { - cat.find('a.btn')[0].click(); + cat.find('.tree-folder-title .title')[0].click(); } } @@ -378,14 +370,12 @@ function collapse_entry() { var flux_current = $(".flux.current"); flux_current.toggleClass("active"); - if (isCollapsed && auto_mark_article) { + if (isCollapsed && context['auto_mark_article']) { mark_read(flux_current, true); } } function user_filter(key) { - console.log('user filter'); - console.warn(key); var filter = $('#dropdown-query'); var filters = filter.siblings('.dropdown-menu').find('.item.query a'); if (typeof key === "undefined") { @@ -459,12 +449,12 @@ function inMarkViewport(flux, box_to_follow, relative_follow) { function init_posts() { var box_to_follow = $(window), relative_follow = false; - if (is_global_mode()) { + if (context['current_view'] == 'global') { box_to_follow = $("#panel"); relative_follow = true; } - if (auto_mark_scroll) { + if (context['auto_mark_scroll']) { box_to_follow.scroll(function () { $('.not_read:visible').each(function () { if ($(this).children(".flux_content").is(':visible') && inMarkViewport($(this), box_to_follow, relative_follow)) { @@ -474,7 +464,7 @@ function init_posts() { }); } - if (auto_load_more) { + if (context['auto_load_more']) { box_to_follow.scroll(function () { var load_more = $("#load_more"); if (!load_more.is(':visible')) { @@ -494,10 +484,11 @@ function init_posts() { } function init_column_categories() { - if (!is_normal_mode()) { + if (context['current_view'] !== 'normal') { return; } - $('#aside_flux').on('click', '.category>a.dropdown-toggle', function () { + + $('#aside_feed').on('click', '.tree-folder>.tree-folder-title>a.dropdown-toggle', function () { $(this).children().each(function() { if (this.alt === '▽') { this.src = this.src.replace('/icons/down.', '/icons/up.'); @@ -507,12 +498,12 @@ function init_column_categories() { this.alt = '▽'; } }); - $(this).parent().next(".feeds").slideToggle(); + $(this).parent().next(".tree-folder-items").slideToggle(); return false; }); - $('#aside_flux').on('click', '.feeds .dropdown-toggle', function () { + $('#aside_feed').on('click', '.tree-folder-items .item .dropdown-toggle', function () { if ($(this).nextAll('.dropdown-menu').length === 0) { - var feed_id = $(this).closest('li').attr('id').substr(2), + var feed_id = $(this).closest('.item').attr('id').substr(2), feed_web = $(this).data('fweb'), template = $('#feed_config_template').html().replace(/!!!!!!/g, feed_id).replace('http://example.net/', feed_web); $(this).attr('href', '#dropdown-' + feed_id).prev('.dropdown-target').attr('id', 'dropdown-' + feed_id).parent().append(template); @@ -634,7 +625,7 @@ function init_shortcuts() { shortcut.add(shortcuts.go_website, function () { var url_website = $('.flux.current > .flux_header > .title > a').attr("href"); - if (auto_mark_site) { + if (context['auto_mark_site']) { $(".flux.current").each(function () { mark_read($(this), true); }); @@ -658,7 +649,13 @@ function init_shortcuts() { }); shortcut.add(shortcuts.help, function () { - redirect(help_url, true); + redirect(url['help'], true); + }, { + 'disable_in_input': true + }); + + shortcut.add(shortcuts.close_dropdown, function () { + window.location.hash = null; }, { 'disable_in_input': true }); @@ -674,7 +671,7 @@ function init_stream(divStream) { new_active = $(this).parent(); isCollapsed = true; if (e.target.tagName.toUpperCase() === 'A') { //Leave real links alone - if (auto_mark_article) { + if (context['auto_mark_article']) { mark_read(new_active, true); } return true; @@ -720,7 +717,7 @@ function init_stream(divStream) { $(this).attr('target', '_blank'); }); - if (auto_mark_site) { + if (context['auto_mark_site']) { // catch mouseup instead of click so we can have the correct behaviour // with middle button click (scroll button). divStream.on('mouseup', '.flux .link > a', function (e) { @@ -780,7 +777,7 @@ function init_actualize() { return false; }); - if (auto_actualize_feeds) { + if (context['auto_actualize_feeds']) { auto = true; $("#actualize").click(); } @@ -851,9 +848,9 @@ function notifs_html5_show(nb) { return } - var notification = new window.Notification(str_notif_title_articles, { + var notification = new window.Notification(i18n['notif_title_articles'], { icon: "../themes/icons/favicon-256.png", - body: str_notif_body_articles.replace("\d", nb), + body: i18n['notif_body_articles'].replace("\d", nb), tag: "freshRssNewArticles" }); @@ -861,10 +858,10 @@ function notifs_html5_show(nb) { window.location.reload(); } - if (html5_notif_timeout !== 0){ + if (context['html5_notif_timeout'] !== 0){ setTimeout(function() { notification.close(); - }, html5_notif_timeout * 1000); + }, context['html5_notif_timeout'] * 1000); } } @@ -879,12 +876,12 @@ function init_notifs_html5() { function refreshUnreads() { $.getJSON('./?c=javascript&a=nbUnreadsPerFeed').done(function (data) { - var isAll = $('.category.all > .active').length > 0, + var isAll = $('.category.all.active').length > 0, new_articles = false; $.each(data, function(feed_id, nbUnreads) { feed_id = 'f_' + feed_id; - var elem = $('#' + feed_id + '>.feed').get(0), + var elem = $('#' + feed_id).get(0), feed_unreads = elem ? str2int(elem.getAttribute('data-unread')) : 0; if ((incUnreadsFeed(null, feed_id, nbUnreads - feed_unreads) || isAll) && //Update of current view? @@ -894,7 +891,7 @@ function refreshUnreads() { }; }); - var nb_unreads = str2int($('.category.all>a').attr('data-unread')); + var nb_unreads = str2int($('.category.all .title').attr('data-unread')); if (nb_unreads > 0 && new_articles) { faviconNbUnread(nb_unreads); @@ -918,7 +915,7 @@ function load_more_posts() { $.get(url_load_more, function (data) { box_load_more.children('.flux:last').after($('#stream', data).children('.flux, .day')); $('.pagination').replaceWith($('.pagination', data)); - if (display_order === 'ASC') { + if (context['display_order'] === 'ASC') { $('#nav_menu_read_all > .read_all').attr( 'formaction', $('#bigMarkAsRead').attr('formaction') ); @@ -949,7 +946,7 @@ function focus_search() { function init_load_more(box) { box_load_more = box; - if (!does_lazyload) { + if (!context['does_lazyload']) { $('img[postpone], audio[postpone], iframe[postpone], video[postpone]').each(function () { this.removeAttribute('postpone'); }); @@ -1017,7 +1014,7 @@ function init_crypto_form() { try { var strong = window.Uint32Array && window.crypto && (typeof window.crypto.getRandomValues === 'function'), s = dcodeIO.bcrypt.hashSync($('#passwordPlain').val(), data.salt1), - c = dcodeIO.bcrypt.hashSync(data.nonce + s, strong ? 4 : poormanSalt()); + c = dcodeIO.bcrypt.hashSync(data.nonce + s, strong ? dcodeIO.bcrypt.genSaltSync(4) : poormanSalt()); $('#challenge').val(c); if (s == '' || c == '') { openNotification('Crypto error!', 'bad'); @@ -1038,73 +1035,13 @@ function init_crypto_form() { } //</crypto form (Web login)> -//<persona> -function init_persona() { - if (!(navigator.id)) { - if (window.console) { - console.log('FreshRSS waiting for Persona…'); - } - window.setTimeout(init_persona, 100); - return; - } - $('a.signin').click(function() { - navigator.id.request(); - return false; - }); - $('a.signout').click(function() { - navigator.id.logout(); - return false; - }); - - navigator.id.watch({ - loggedInUser: current_user_mail, - - onlogin: function(assertion) { - // A user has logged in! Here you need to: - // 1. Send the assertion to your backend for verification and to create a session. - // 2. Update your UI. - $.ajax ({ - type: 'POST', - url: url_login, - data: {assertion: assertion}, - success: function(res, status, xhr) { - /*if (res.status === 'failure') { - alert (res_obj.reason); - } else*/ if (res.status === 'okay') { - location.href = url_freshrss; - } - }, - error: function(res, status, xhr) { - alert("Login failure: " + res); - } - }); - }, - onlogout: function() { - // A user has logged out! Here you need to: - // Tear down the user's session by redirecting the user or making a call to your backend. - // Also, make sure loggedInUser will get set to null on the next page load. - // (That's a literal JavaScript null. Not false, 0, or undefined. null.) - $.ajax ({ - type: 'POST', - url: url_logout, - success: function(res, status, xhr) { - location.href = url_freshrss; - }, - error: function(res, status, xhr) { - //alert("logout failure" + res); - } - }); - } - }); -} -//</persona> function init_confirm_action() { $('body').on('click', '.confirm', function () { var str_confirmation = $(this).attr('data-str-confirm'); if (!str_confirmation) { - str_confirmation = str_confirmation_default; + str_confirmation = i18n['confirmation_default']; } return confirm(str_confirmation); @@ -1150,7 +1087,7 @@ function init_share_observers() { } function init_stats_observers() { - $('#feed_select').on('change', function(e) { + $('.select-change').on('change', function(e) { redirect($(this).find(':selected').data('url')); }); } @@ -1199,7 +1136,7 @@ function init_password_observers() { function faviconNbUnread(n) { if (typeof n === 'undefined') { - n = str2int($('.category.all>a').attr('data-unread')); + n = str2int($('.category.all .title').attr('data-unread')); } //http://remysharp.com/2010/08/24/dynamic-favicons/ var canvas = document.createElement('canvas'), @@ -1233,8 +1170,44 @@ function faviconNbUnread(n) { } } +function init_slider_observers() { + var slider = $('#slider'), + closer = $('#close-slider'); + if (slider.length < 1) { + return; + } + + $('.post').on('click', '.open-slider', function() { + if (ajax_loading) { + return false; + } + + ajax_loading = true; + var url_slide = $(this).attr('href'); + + $.ajax({ + type: 'GET', + url: url_slide, + data : { ajax: true } + }).done(function (data) { + slider.html(data); + closer.addClass('active'); + slider.addClass('active'); + ajax_loading = false; + }); + + return false; + }); + + closer.on('click', function() { + closer.removeClass('active'); + slider.removeClass('active'); + return false; + }); +} + function init_all() { - if (!(window.$ && window.url_freshrss)) { + if (!(window.$ && window.context)) { if (window.console) { console.log('FreshRSS waiting for JS…'); } @@ -1242,11 +1215,6 @@ function init_all() { return; } init_notifications(); - switch (authType) { - case 'persona': - init_persona(); - break; - } init_confirm_action(); $stream = $('#stream'); if ($stream.length > 0) { @@ -1268,6 +1236,7 @@ function init_all() { init_feed_observers(); init_password_observers(); init_stats_observers(); + init_slider_observers(); } if (window.console) { diff --git a/p/scripts/persona.js b/p/scripts/persona.js new file mode 100644 index 000000000..36aeeaf56 --- /dev/null +++ b/p/scripts/persona.js @@ -0,0 +1,76 @@ +"use strict"; + +function init_persona() { + if (!(navigator.id && window.$)) { + if (window.console) { + console.log('FreshRSS (Persona) waiting for JS…'); + } + window.setTimeout(init_persona, 100); + return; + } + + $('a.signin').click(function() { + navigator.id.request(); + return false; + }); + + $('a.signout').click(function() { + navigator.id.logout(); + return false; + }); + + navigator.id.watch({ + loggedInUser: context['current_user_mail'], + + onlogin: function(assertion) { + // A user has logged in! Here you need to: + // 1. Send the assertion to your backend for verification and to create a session. + // 2. Update your UI. + $.ajax ({ + type: 'POST', + url: url['login'], + data: {assertion: assertion}, + success: function(res, status, xhr) { + if (res.status === 'failure') { + openNotification(res.reason, 'bad'); + } else if (res.status === 'okay') { + location.href = url['index']; + } + }, + error: function(res, status, xhr) { + // alert(res); + } + }); + }, + onlogout: function() { + // A user has logged out! Here you need to: + // Tear down the user's session by redirecting the user or making a call to your backend. + // Also, make sure loggedInUser will get set to null on the next page load. + // (That's a literal JavaScript null. Not false, 0, or undefined. null.) + $.ajax ({ + type: 'POST', + url: url['logout'], + success: function(res, status, xhr) { + location.href = url['index']; + }, + error: function(res, status, xhr) { + // alert(res); + } + }); + } + }); +} + +if (document.readyState && document.readyState !== 'loading') { + if (window.console) { + console.log('FreshRSS (Persona) immediate init…'); + } + init_persona(); +} else if (document.addEventListener) { + document.addEventListener('DOMContentLoaded', function () { + if (window.console) { + console.log('FreshRSS (Persona) waiting for DOMContentLoaded…'); + } + init_persona(); + }, false); +} diff --git a/p/scripts/shortcut.js b/p/scripts/shortcut.js index debaffbaa..e78cf6f5e 100644 --- a/p/scripts/shortcut.js +++ b/p/scripts/shortcut.js @@ -43,7 +43,9 @@ shortcut = { //Find Which key is pressed if (e.keyCode) code = e.keyCode; else if (e.which) code = e.which; - var character = String.fromCharCode(code).toLowerCase(); + if (code == 32 || (code >= 48 && code <= 90) || (code >= 96 && code <= 111) || (code >= 186 && code <= 192) || (code >= 219 && code <= 222)) { //FreshRSS + var character = String.fromCharCode(code).toLowerCase(); + } if(code == 188) character=","; //If the user presses , when the type is onkeydown if(code == 190) character="."; //If the user presses , when the type is onkeydown diff --git a/p/themes/Dark/dark.css b/p/themes/Dark/dark.css index 10f6e655b..03bf3c985 100644 --- a/p/themes/Dark/dark.css +++ b/p/themes/Dark/dark.css @@ -435,6 +435,77 @@ a.btn { font-size: 0; } +/*=== Boxes */ +.box { + border: 1px solid #000; + border-radius: 5px; +} +.box .box-title { + margin: 0; + padding: 5px 10px; + background: #26303F; + border-bottom: 1px solid #000; + border-radius: 5px 5px 0 0; +} +.box .box-content { + max-height: 260px; +} + +.box .box-content .item { + padding: 0 10px; + font-size: 0.9rem; + line-height: 2.5em; +} + +.box .box-content .item .configure { + visibility: hidden; +} +.box .box-content .item:hover .configure { + visibility: visible; +} + +/*=== Tree */ +.tree { + margin: 10px 0; +} +.tree-folder-title { + position: relative; + padding: 0 10px; + line-height: 2.5rem; + font-size: 1rem; + background: #1c1c1c; +} +.tree-folder-title .title { + background: inherit; + color: #888; +} +.tree-folder-title .title:hover { + text-decoration: none; +} +.tree-folder.active .tree-folder-title { + background: #2c2c2c; + font-weight: bold; +} +.tree-folder-items { + border-top: 1px solid #222; + border-bottom: 1px solid #222; + background: #161616; +} +.tree-folder-items > .item { + padding: 0 10px; + line-height: 2.5rem; + font-size: 0.8rem; +} +.tree-folder-items > .item.active { + background: #1c1c1c; +} +.tree-folder-items > .item > a { + text-decoration: none; +} +.tree-folder-items > .item.active > a { + color: #888; +} + /*=== STRUCTURE */ /*===============*/ /*=== Header */ @@ -471,81 +542,59 @@ a.btn { border-right: 1px solid #333; background: #1c1c1c; } -.aside.aside_flux { - padding: 10px 0 50px; - border-right: 1px solid #333; - background: #1c1c1c; -} - -/*=== Aside main page (categories) */ -.categories { +.aside.aside_feed { + padding: 10px 0; text-align: center; } -.category { - width: 235px; - margin: 10px auto; - text-align: left; -} -.category .btn:first-child { - position: relative; - width: 213px; -} -.category.stick .btn:first-child { - width: 176px; +.aside.aside_feed .tree { + position: sticky; + top: 0; + margin: 10px 0 50px; } -.category .btn:first-child:not([data-unread="0"]):after { + +/*=== Aside main page (categories) */ +.aside_feed .tree-folder-title > .title:not([data-unread="0"]):after { position: absolute; - top: 3px; right: 3px; - padding: 1px 5px; - background: #111; - color: #888; - border: 1px solid #000; - border-radius: 5px; + right: 0; + margin: 10px 0; + padding: 0 10px; + font-size: 0.9rem; + line-height: 1.5rem; + background: inherit; + border-left: 1px solid #666; } /*=== Aside main page (feeds) */ -.categories .feeds .item.active { - background: #333; +.feed.item.empty.active { + background: #c95; } -.categories .feeds .item.active .feed { - color: #888; +.feed.item.error.active { + background: #a44; } -.categories .feeds .item.empty .feed { +.feed.item.empty, +.feed.item.empty > a { color: #c95; } -.categories .feeds .item.empty.active { - background: #c95; -} -.categories .feeds .item.error .feed { +.feed.item.error, +.feed.item.error > a { color: #a44; } -.categories .feeds .item.error.active { - background: #a44; +.feed.item.empty.active, +.feed.item.empty.active > a { + color: #111; } -.categories .feeds .item.empty.active .feed, -.categories .feeds .item.error.active .feed { +.feed.item.error.active, +.feed.item.error.active > a { color: #fff; } -.categories .feeds .item .feed { - margin: 0; - width: 165px; - line-height: 3em; - font-size: 0.8em; - text-align: left; - text-decoration: none; -} -.categories .feeds .feed:not([data-unread="0"]) { - font-weight: bold; -} -.categories .feeds .dropdown-menu:after { +.aside_feed .tree-folder-items .dropdown-menu:after { left: 2px; } -.categories .feeds .item .dropdown-target:target ~ .dropdown-toggle > .icon, -.categories .feeds .item:hover .dropdown-toggle > .icon, -.categories .feeds .item.active .dropdown-toggle > .icon { - vertical-align: middle; - background-color: #111; +.aside_feed .tree-folder-items .item .dropdown-target:target ~ .dropdown-toggle > .icon, +.aside_feed .tree-folder-items .item:hover .dropdown-toggle > .icon, +.aside_feed .tree-folder-items .item.active .dropdown-toggle > .icon { border-radius: 3px; + background-color: #111; } /*=== Configuration pages */ @@ -796,46 +845,34 @@ a.btn { /*=== GLOBAL VIEW */ /*================*/ -#stream.global .box-category { - text-align: left; - background: #1a1a1a; - border: 1px solid #000; - border-radius: 5px; +.box.category .box-title .title { + font-weight: normal; + text-decoration: none; text-align: left; + color: #888; } -#stream.global .category { - margin: 0; +.box.category:not([data-unread="0"]) .box-title { + background: #34495E; } -#stream.global .btn { - width: auto; - height: 2em; - margin: 0; - padding: 0 10px; - line-height: 2em; - font-size: 1.2rem; +.box.category:not([data-unread="0"]) .box-title:active { background: #26303F; - border: none; - border-bottom: 1px solid #000; - border-radius: 5px 5px 0 0; } -#stream.global .btn:not([data-unread="0"]) { - font-weight: bold; - background: #34495e; +.box.category:not([data-unread="0"]) .box-title .title { color: #fff; -} -#stream.global .btn:first-child:not([data-unread="0"]):after { - top: 0; right: 5px; font-weight: bold; +} +.box.category .title:not([data-unread="0"]):after { + position: absolute; + top: 5px; right: 10px; border: 0; background: none; - color: #fff; -} -#stream.global .box-category .feeds { - max-height: 250px; + font-weight: bold; + box-shadow: none; + text-shadow: none; } -#stream.global .box-category .feeds .item { +.box.category .item.feed { padding: 2px 10px; - font-size: 0.9rem; + font-size: 0.8rem; } /*=== Panel */ @@ -934,17 +971,20 @@ a.btn { } .aside .toggle_aside, #panel .close { - position: absolute; display: block; - top: 0; right: 0; - width: 30px; - height: 30px; - line-height: 30px; + width: 100%; + height: 50px; + line-height: 50px; text-align: center; background: #111; - border-left: 1px solid #333; border-bottom: 1px solid #333; - border-radius: 0 0 0 5px; + } + + .aside.aside_feed { + padding: 0; + } + .aside.aside_feed .tree { + position: static; } .nav_menu .btn { diff --git a/p/themes/Flat/flat.css b/p/themes/Flat/flat.css index 484cee9f3..313bee0ee 100644 --- a/p/themes/Flat/flat.css +++ b/p/themes/Flat/flat.css @@ -438,6 +438,79 @@ a.btn { background: url("loader.gif") center center no-repeat #34495e; } +/*=== Boxes */ +.box { + border: 1px solid #ddd; + border-radius: 5px; +} +.box .box-title { + margin: 0; + padding: 5px 10px; + background: #ecf0f1; + color: #333; + border-bottom: 1px solid #ddd; + border-radius: 5px 5px 0 0; +} +.box .box-content { + max-height: 260px; +} + +.box .box-content .item { + padding: 0 10px; + font-size: 0.9rem; + line-height: 2.5em; +} + +.box .box-content .item .configure { + visibility: hidden; +} +.box .box-content .item .configure .icon { + vertical-align: middle; + background-color: #95a5a6; + border-radius: 3px; +} +.box .box-content .item:hover .configure { + visibility: visible; +} + +/*=== Tree */ +.tree { + margin: 10px 0; +} +.tree-folder-title { + position: relative; + padding: 0 10px; + background: #34495e; + line-height: 2.5rem; + font-size: 1rem; +} +.tree-folder-title .title { + background: inherit; + color: #fff; +} +.tree-folder-title .title:hover { + text-decoration: none; +} +.tree-folder.active .tree-folder-title { + background: #2980b9; + font-weight: bold; +} +.tree-folder-items { + background: #2c3e50; +} +.tree-folder-items > .item { + padding: 0 10px; + line-height: 2.5rem; + font-size: 0.8rem; +} +.tree-folder-items > .item.active { + background: #2980b9; +} +.tree-folder-items > .item > a { + text-decoration: none; + color: #fff; +} + /*=== STRUCTURE */ /*===============*/ /*=== Header */ @@ -473,76 +546,56 @@ a.btn { .aside { background: #ecf0f1; } -.aside.aside_flux { - padding: 10px 0 50px; - background: #ecf0f1; -} - -/*=== Aside main page (categories) */ -.categories { +.aside.aside_feed { + padding: 10px 0; text-align: center; + background: #34495e; + border-radius: 0 10px 0 0; } -.category { - width: 233px; - margin: 10px auto; - text-align: left; -} -.category .btn:first-child { - position: relative; - width: 212px; -} -.category.stick .btn:first-child { - width: 175px; +.aside.aside_feed .tree { + position: sticky; + top: 0; + margin: 10px 0 50px; } -.category .btn:first-child:not([data-unread="0"]):after { + +/*=== Aside main page (categories) */ +.aside_feed .tree-folder-title > .title:not([data-unread="0"]):after { position: absolute; - top: 5px; right: 5px; - padding: 1px 5px; - background: #3498DB; - color: #fff; - border-radius: 5px; + right: 0; + margin: 10px 0; + padding: 0 10px; + font-size: 0.9rem; + line-height: 1.5rem; + background: inherit; } /*=== Aside main page (feeds) */ -.categories .feeds .item.active { - background: #2980b9; -} -.categories .feeds .item.empty.active { +.feed.item.empty.active { background: #f39c12; } -.categories .feeds .item.error.active { +.feed.item.error.active { background: #bd362f; } -.categories .feeds .item.empty .feed { +.feed.item.empty, +.feed.item.empty > a { color: #e67e22; } -.categories .feeds .item.error .feed { +.feed.item.error, +.feed.item.error > a { color: #bd362f; } -.categories .feeds .item.active .feed, -.categories .feeds .item.empty.active .feed, -.categories .feeds .item.error.active .feed { +.feed.item.empty.active, +.feed.item.error.active, +.feed.item.empty.active > a, +.feed.item.error.active > a { color: #fff; } -.categories .feeds .item .feed { - margin: 0; - width: 165px; - line-height: 3em; - font-size: 0.8em; - text-align: left; - text-decoration: none; -} -.categories .feeds .feed:not([data-unread="0"]) { - font-weight: bold; -} -.categories .feeds .dropdown-menu:after { +.aside_feed .tree-folder-items .dropdown-menu:after { left: 2px; } -.categories .feeds .item .dropdown-target:target ~ .dropdown-toggle > .icon, -.categories .feeds .item:hover .dropdown-toggle > .icon, -.categories .feeds .item.active .dropdown-toggle > .icon { - vertical-align: middle; - background-color: #95a5a6; +.aside_feed .tree-folder-items .item .dropdown-target:target ~ .dropdown-toggle > .icon, +.aside_feed .tree-folder-items .item:hover .dropdown-toggle > .icon, +.aside_feed .tree-folder-items .item.active .dropdown-toggle > .icon { border-radius: 3px; } @@ -617,6 +670,12 @@ a.btn { padding: 5px 0; } +#dropdown-query ~ .dropdown-menu .dropdown-header .icon { + vertical-align: middle; + background-color: #95a5a6; + border-radius: 3px; +} + /*=== Feed articles */ .flux { border-left: 2px solid #ecf0f1; @@ -792,44 +851,33 @@ a.btn { /*=== GLOBAL VIEW */ /*================*/ -#stream.global .box-category { +.box.category .box-title .title { + font-weight: normal; + text-decoration: none; text-align: left; - border: 1px solid #ddd; - border-radius: 5px; } -#stream.global .category { - margin: 0; +.box.category:not([data-unread="0"]) .box-title { + background: #3498db; } -#stream.global .btn { - width: auto; - height: 2em; - margin: 0; - padding: 0 10px; - line-height: 2em; - font-size: 1.2rem; - background: #ecf0f1; - color: #333; - border-bottom: 1px solid #ddd; - border-radius: 5px 5px 0 0; +.box.category:not([data-unread="0"]) .box-title:active { + background: #2980b9; } -#stream.global .btn:not([data-unread="0"]) { +.box.category:not([data-unread="0"]) .box-title .title { font-weight: bold; - background: #3498db; color: #fff; } -#stream.global .btn:first-child:not([data-unread="0"]):after { - top: 0; right: 5px; - font-weight: bold; - background: none; +.box.category .title:not([data-unread="0"]):after { + position: absolute; + top: 5px; right: 10px; border: 0; - color: #fff; -} -#stream.global .box-category .feeds { - max-height: 250px; + background: none; + font-weight: bold; + box-shadow: none; + text-shadow: none; } -#stream.global .box-category .feeds .item { +.box.category .item.feed { padding: 2px 10px; - font-size: 0.9rem; + font-size: 0.8rem; } /*=== DIVERS */ @@ -920,15 +968,19 @@ a.btn { } .aside .toggle_aside, #panel .close { - position: absolute; display: block; - top: 0; right: 0; - width: 32px; - height: 32px; - line-height: 30px; + width: 100%; + height: 50px; + line-height: 50px; text-align: center; - background: #34495e; - border-radius: 0 0 0 5px; + background: #2c3e50; + } + + .aside.aside_feed { + padding: 0; + } + .aside.aside_feed .tree { + position: static; } .nav_menu .btn { diff --git a/p/themes/Origine/origine.css b/p/themes/Origine/origine.css index 08fc08379..afd6ec04f 100644 --- a/p/themes/Origine/origine.css +++ b/p/themes/Origine/origine.css @@ -467,6 +467,82 @@ a.btn { font-size: 0; } +/*=== Boxes */ +.box { + background: #fff; + border-radius: 5px; + box-shadow: 0 0 3px #bbb; +} +.box .box-title { + margin: 0; + padding: 5px 10px; + background: #f6f6f6; + border-bottom: 1px solid #ddd; + border-radius: 5px 5px 0 0; +} +.box .box-content { + min-height: 2.5em; + max-height: 260px; +} + +.box .box-content .item { + padding: 0 10px; + font-size: 0.9rem; + line-height: 2.5em; +} + +.box .box-content .item .configure { + visibility: hidden; +} +.box .box-content .item:hover .configure { + visibility: visible; +} + +/*=== Tree */ +.tree { + margin: 10px 0; +} +.tree-folder-title { + position: relative; + padding: 0 10px; + background: #fff; + line-height: 2.5rem; + font-size: 1rem; +} +.tree-folder-title .title { + background: inherit; + color: #444; +} +.tree-folder-title .title:hover { + text-decoration: none; +} +.tree-folder.active .tree-folder-title { + background: #f0f0f0; + font-weight: bold; +} +.tree-folder.active .tree-folder-title .title { + color: #0062BE; +} +.tree-folder-items { + border-top: 1px solid #ccc; + border-bottom: 1px solid #ccc; + background: #f6f6f6; +} +.tree-folder-items > .item { + padding: 0 10px; + line-height: 2.5rem; + font-size: 0.8rem; +} +.tree-folder-items > .item.active { + background: #0062be; +} +.tree-folder-items > .item > a { + text-decoration: none; +} +.tree-folder-items > .item.active > a { + color: #fff; +} + /*=== STRUCTURE */ /*===============*/ /*=== Header */ @@ -505,79 +581,57 @@ a.btn { border-right: 1px solid #aaa; background: #fff; } -.aside.aside_flux { - padding: 10px 0 50px; -} - -/*=== Aside main page (categories) */ -.categories { +.aside.aside_feed { + padding: 10px 0; text-align: center; + background: #fff; } -.category { - width: 235px; - margin: 10px auto; - text-align: left; -} -.category .btn:first-child { - position: relative; - width: 213px; -} -.category.stick .btn:first-child { - width: 176px; +.aside.aside_feed .tree { + position: sticky; + top: 0; + margin: 10px 0 50px; } -.category .btn:first-child:not([data-unread="0"]):after { + +/*=== Aside main page (categories) */ +.aside_feed .category .title:not([data-unread="0"]):after { position: absolute; - top: 3px; right: 3px; - padding: 1px 5px; - background: #ccc; - color: #fff; - border: 1px solid #bbb; - border-radius: 5px; - box-shadow: 1px 3px 3px #aaa inset; - text-shadow: 0 0 1px #aaa; + right: 0; + margin: 10px 0; + padding: 0 10px; + font-size: 0.9rem; + line-height: 1.5rem; + background: inherit; + border-left: 1px solid #aaa; } /*=== Aside main page (feeds) */ -.categories .feeds .item.active { - background: #0062BE; -} -.categories .feeds .item.empty.active { +.feed.item.empty.active { background: #e67e22; } -.categories .feeds .item.error.active { - background: #BD362F; +.feed.item.error.active { + background: #bd362f; } -.categories .feeds .item.empty .feed { +.feed.item.empty, +.feed.item.empty > a { color: #e67e22; } -.categories .feeds .item.error .feed { - color: #BD362F; +.feed.item.error, +.feed.item.error > a { + color: #bd362f; } -.categories .feeds .item.active .feed, -.categories .feeds .item.empty.active .feed, -.categories .feeds .item.error.active .feed { +.feed.item.empty.active, +.feed.item.error.active, +.feed.item.empty.active > a, +.feed.item.error.active > a { color: #fff; } -.categories .feeds .item .feed { - margin: 0; - width: 165px; - line-height: 3em; - font-size: 0.8em; - text-align: left; - text-decoration: none; -} -.categories .feeds .feed:not([data-unread="0"]) { - font-weight: bold; -} -.categories .feeds .dropdown-menu:after { +.aside_feed .tree-folder-items .dropdown-menu:after { left: 2px; } -.categories .feeds .item .dropdown-target:target ~ .dropdown-toggle > .icon, -.categories .feeds .item:hover .dropdown-toggle > .icon, -.categories .feeds .item.active .dropdown-toggle > .icon { +.aside_feed .tree-folder-items .item .dropdown-target:target ~ .dropdown-toggle > .icon, +.aside_feed .tree-folder-items .item.active .dropdown-toggle > .icon { background-color: #fff; border-radius: 3px; - vertical-align: middle; } /*=== Configuration pages */ @@ -842,35 +896,24 @@ a.btn { /*=== GLOBAL VIEW */ /*================*/ -#stream.global .box-category { - background: #fff; - border-radius: 5px; +.box.category .box-title .title { + font-weight: normal; + text-decoration: none; text-align: left; - box-shadow: 0 0 3px #bbb; } -#stream.global .category { - margin: 0; +.box.category:not([data-unread="0"]) .box-title { + background: #0084CC; } -#stream.global .btn { - width: auto; - height: 2em; - margin: 0; - padding: 0 10px; - background: #f6f6f6; - border: none; - border-bottom: 1px solid #ddd; - border-radius: 5px 5px 0 0; - line-height: 2em; - font-size: 1.2rem; +.box.category:not([data-unread="0"]) .box-title:active { + background: #3498db; } -#stream.global .btn:not([data-unread="0"]) { - background: #0084CC; +.box.category:not([data-unread="0"]) .box-title .title { color: #fff; font-weight: bold; - text-shadow: none; } -#stream.global .btn:first-child:not([data-unread="0"]):after { - top: 0; right: 5px; +.box.category .title:not([data-unread="0"]):after { + position: absolute; + top: 5px; right: 10px; border: 0; background: none; color: #fff; @@ -878,12 +921,9 @@ a.btn { box-shadow: none; text-shadow: none; } -#stream.global .box-category .feeds { - max-height: 250px; -} -#stream.global .box-category .feeds .item { +.box.category .item.feed { padding: 2px 10px; - font-size: 0.9rem; + font-size: 0.8rem; } /*=== DIVERS */ @@ -977,17 +1017,20 @@ a.btn { } .aside .toggle_aside, #panel .close { - position: absolute; display: block; - top: 0; right: 0; - width: 30px; - height: 30px; - line-height: 30px; + width: 100%; + height: 50px; + line-height: 50px; text-align: center; background: #f6f6f6; - border-left: 1px solid #ddd; border-bottom: 1px solid #ddd; - border-radius: 0 0 0 5px; + } + + .aside.aside_feed { + padding: 0; + } + .aside.aside_feed .tree { + position: static; } .nav_menu .btn { diff --git a/p/themes/Pafat/pafat.css b/p/themes/Pafat/pafat.css index a35ac861d..8b1e7866c 100644 --- a/p/themes/Pafat/pafat.css +++ b/p/themes/Pafat/pafat.css @@ -192,48 +192,12 @@ a.btn { text-decoration: none; } - -.category.stick .btn { - background:#5bc0de; - color : #FFF; - border-color :#5bc0de; -} - -.category.stick .btn:first-child:hover, .category.stick .btn:last-child:hover, .category.stick .btn.active:first-child, .category.stick.active .btn:last-child { - background:#39b3d7; - border-color : #39b3d7; -} - - .btn.active, .btn:active, .dropdown-target:target ~ .btn.dropdown-toggle { background: #eee; } -.category.all > .btn { - background: #428bca; - color : #FFF; - border-color : #428bca; -} - -.category.all > .btn:hover { - background: #3276b1; - border-color : #3276b1; -} - -.category.favorites > .btn { - background:#f0ad4e; - border-color: #f0ad4e; - color : #fff; -} - -.category.favorites > .btn:hover { - background: #ed9c28; - border-color : #ed9c28; - color : white; -} - .btn-important { background: #5cb85c; color: #fff; @@ -491,6 +455,80 @@ a.btn { font-size: 0; } +/*=== Boxes */ +.box { + border: 1px solid #aaa; + border-radius: 5px; +} +.box .box-title { + margin: 0; + padding: 5px 10px; + background: #f6f6f6; + border-bottom: 1px solid #aaa; + border-radius: 5px 5px 0 0; +} +.box .box-content { + max-height: 260px; +} + +.box .box-content .item { + padding: 0 10px; + font-size: 0.9rem; + line-height: 2.5em; +} + +.box .box-content .item .configure { + visibility: hidden; +} +.box .box-content .item:hover .configure { + visibility: visible; +} + +/*=== Tree */ +.tree { + margin: 10px 0; +} +.tree-folder-title { + position: relative; + margin: 5px; + padding: 0 10px; + line-height: 2rem; + font-size: 0.9rem; + background: #5bc0de; + color: #fff; + border-top: 1px solid transparent; + border-bottom: 1px solid transparent; + border-radius: 5px; +} +.tree-folder-title .title { + background: inherit; + color: #fff; +} +.tree-folder-title .title:hover { + text-decoration: none; +} +.tree-folder.active .tree-folder-title { + background: #39b3d7; + font-weight: bold; + font-size: 1rem; + border-top: 1px solid #666; + border-bottom: 1px solid #666; +} +.tree-folder-items > .item { + padding: 0 10px; + line-height: 2.5rem; + font-size: 0.8rem; +} +.tree-folder-items > .item.active { + background: #5cb85c; +} +.tree-folder-items > .item > a { + text-decoration: none; +} +.tree-folder-items > .item.active > a { + color: #fff; +} + /*=== STRUCTURE */ /*===============*/ /*=== Header */ @@ -543,75 +581,68 @@ a.btn { border-right: 1px solid #aaa; background: #fff; } -.aside.aside_flux { - padding: 10px 0 50px; +.aside.aside_feed { + padding: 10px 0; + text-align: center; +} +.aside.aside_feed .tree { + position: sticky; + top: 5px; + margin: 10px 0 50px; } /*=== Aside main page (categories) */ -.categories { - text-align: center; +.aside_feed .tree-folder-title > .title:not([data-unread="0"]):after { + position: absolute; + top: 0.25rem; right: 3px; + padding: 0px 5px; + border: 1px solid #fff; + border-radius: 3px; + font-size: 0.8rem; + line-height: 1.5rem; } -.category { - width: 215px; - margin: 10px auto; - text-align: left; +.aside_feed .tree-folder.all .tree-folder-title { + background: #428bca; } -.category .btn:first-child { - position: relative; - width: 203px; +.aside_feed .tree-folder.all.active .tree-folder-title { + background: #3276b1; } -.category.stick .btn:first-child { - width: 176px; +.aside_feed .tree-folder.favorites .tree-folder-title { + background: #f0ad4e; } -.category .btn:first-child:not([data-unread="0"]):after { - position: absolute; - top: 2px; right: 3px; - padding: 0px 3px; - border: 1px solid ; - border-radius: 3px; - font-size:10pt; - line-height : 20px; +.aside_feed .tree-folder.favorites.active .tree-folder-title { + background: #ed9c28; } /*=== Aside main page (feeds) */ -.categories .feeds .item.active { - background: #5cb85c; +.feed.item.empty.active { + background: #e67e22; } -.categories .feeds .item.active .feed { - color: #fff; +.feed.item.error.active { + background: #bd362f; } -.categories .feeds .item.empty .feed { +.feed.item.empty, +.feed.item.empty > a { color: #e67e22; } -.categories .feeds .item.empty.active { - background: #e67e22; +.feed.item.error, +.feed.item.error > a { + color: #bd362f; } -.categories .feeds .item.empty.active .feed { +.feed.item.empty.active, +.feed.item.error.active, +.feed.item.empty.active > a, +.feed.item.error.active > a { color: #fff; } -.categories .feeds .item.error .feed { - color: #BD362F; -} -.categories .feeds .item .feed { - margin: 0; - width: 165px; - line-height: 3em; - font-size: 0.8em; - text-align: left; - text-decoration: none; -} -.categories .feeds .feed:not([data-unread="0"]) { - font-weight: bold; -} -.categories .feeds .dropdown-menu:after { +.aside_feed .tree-folder-items .dropdown-menu:after { left: 2px; } -.categories .feeds .item .dropdown-target:target ~ .dropdown-toggle > .icon, -.categories .feeds .item:hover .dropdown-toggle > .icon, -.categories .feeds .item.active .dropdown-toggle > .icon { - background-color: #fff; +.aside_feed .tree-folder-items .item .dropdown-target:target ~ .dropdown-toggle > .icon, +.aside_feed .tree-folder-items .item:hover .dropdown-toggle > .icon, +.aside_feed .tree-folder-items .item.active .dropdown-toggle > .icon { border-radius: 3px; - vertical-align: middle; + background-color: #fff; } /*=== Configuration pages */ @@ -877,63 +908,32 @@ a.btn { /*=== GLOBAL VIEW */ /*================*/ -#stream.global .box-category { - background: #fff; - border:none; +.box.category .box-title .title { + font-weight: normal; + text-decoration: none; text-align: left; } - -#stream.global .category { - margin: 0; -} - -#stream.global .category:first-child { - margin: 0; -} - - -#stream.global .btn { - width: auto; - height: 2em; - margin: 0; - padding: 0 10px; - background: #f6f6f6; - border-bottom: 1px solid #aaa; - border-radius: 5px 5px 0 0; - line-height: 2em; - font-size: 1.2rem; +.box.category:not([data-unread="0"]) .box-title { + background: #5BC0DE; } - -#stream.global .btn:not([data-unread="0"]) { - background: #5bc0de; - border-color : #5bc0de; - color: #fff; +.box.category:not([data-unread="0"]) .box-title .title { font-weight: bold; - text-shadow: none; - + color: #fff; } - - -#stream.global .btn:first-child:not([data-unread="0"]):after { - top: 0; right: 5px; +.box.category .title:not([data-unread="0"]):after { + position: absolute; + top: 5px; right: 10px; border: 0; background: none; - color: #fff; font-weight: bold; box-shadow: none; text-shadow: none; + font-size: 0.8rem; + line-height: 1.6rem; } - -#stream.global .box-category .feeds { - max-height: 250px; - width: 302px; - border : solid #aaa 1px; - border-top : none; -} - -#stream.global .box-category .feeds .item { +.box.category .item.feed { padding: 2px 10px; - font-size: 0.9rem; + font-size: 0.8rem; } /*=== DIVERS */ @@ -1027,17 +1027,20 @@ a.btn { } .aside .toggle_aside, #panel .close { - position: absolute; display: block; - top: 0; right: 0; - width: 30px; - height: 30px; - line-height: 30px; + width: 100%; + height: 40px; + line-height: 40px; text-align: center; background: #f6f6f6; - border-left: 1px solid #ddd; border-bottom: 1px solid #ddd; - border-radius: 0 0 0 5px; + } + + .aside.aside_feed { + padding: 0; + } + .aside.aside_feed .tree { + position: static; } .nav_menu .btn { diff --git a/p/themes/Screwdriver/screwdriver.css b/p/themes/Screwdriver/screwdriver.css index 37fa18e10..b6c2e670e 100644 --- a/p/themes/Screwdriver/screwdriver.css +++ b/p/themes/Screwdriver/screwdriver.css @@ -497,6 +497,88 @@ a.btn { font-size: 0; } +/*=== Boxes */ +.box { + background: #EDE7DE; + border-radius: 4px 4px 0 0; +} +.box .box-title { + margin: 0; + padding: 5px 10px; + background: linear-gradient(0deg, #EDE7DE 0%, #fff 100%) #171717; + background: -webkit-linear-gradient(bottom, #EDE7DE 0%, #fff 100%); + box-shadow: 0px -1px #fff inset,0 -2px #ccc inset; + color: #888; + text-shadow: 0 1px #ccc; + border-radius: 4px 4px 0 0; + font-size: 1.1rem; + font-weight: normal; +} +.box .box-content { + max-height: 260px; +} + +.box .box-content .item { + padding: 0 10px; + font-size: 0.9rem; + line-height: 2.5em; +} + +.box .box-content .item .configure { + visibility: hidden; +} +.box .box-content .item:hover .configure { + visibility: visible; +} + +/*=== Tree */ +.tree { + margin: 10px 0; +} +.tree-folder-title { + position: relative; + padding: 0 10px; + line-height: 2.5rem; + font-size: 0.9rem; +} +.tree-folder-title .title { + background: inherit; + color: #fff; +} +.tree-folder-title .title:hover { + text-decoration: none; +} +.tree-folder.active .tree-folder-title { + background: linear-gradient(180deg, #222 0%, #171717 100%) #171717; + background: -webkit-linear-gradient(top, #222 0%, #171717 100%); + box-shadow: 0px 1px #171717, 0px 1px rgba(255, 255, 255, 0.08) inset; + text-shadow: 0 0 2px rgba(255,255,255,0.28); + color: #fff; +} +.tree-folder-items { + background: #171717; + padding: 8px 0; + box-shadow: 0 4px 4px #171717 inset, 0 1px rgba(255,255,255,0.08),0 -1px rgba(255,255,255,0.08); +} +.tree-folder-items > .item { + padding: 0 10px; + line-height: 2.5rem; + font-size: 0.8rem; +} +.tree-folder-items > .item.active { + background: linear-gradient(180deg, #222 0%, #171717 100%) #171717; + background: -webkit-linear-gradient(top, #222 0%, #171717 100%); + border-radius: 4px; + margin: 0px 8px; + box-shadow: 0px 1px #171717, 0px 1px rgba(255, 255, 255, 0.08) inset, 0 2px 2px #111; +} +.tree-folder-items > .item > a { + text-decoration: none; + color: #fff; +} +.tree-folder-items > .item.active > a { +} + /*=== STRUCTURE */ /*===============*/ /*=== Header */ @@ -535,7 +617,7 @@ a.btn { /*=== Body */ #global { background:#EDE7DE; - height: calc(100% - 85px); + height: calc(100% - 60px); } .aside { border-radius: 0px 12px 0px 0px; @@ -544,105 +626,44 @@ a.btn { background: #222; width: 235px; } -.aside.aside_flux { - padding: 10px 0 50px; - background: #222; -} - -/*=== Aside main page (categories) */ -.categories { +.aside.aside_feed { + padding: 10px 0; text-align: center; } -.categories .btn-important { - border: none; -} -.category { - width: 235px; - margin: 10px auto 0; - text-align: left; -} -#aside_flux ul.feeds{ - box-shadow: 0 4px 4px #171717 inset, 0 1px rgba(255,255,255,0.08),0 -1px rgba(255,255,255,0.08); -} -ul.feeds{ - background:#171717; - padding:8px 0; - box-shadow: 0 4px 4px #EDE7DE inset; -} -ul.feeds.active{ - box-shadow: 0 0 0 #171717 inset, 0 -2px 2px #111 inset,0 1px rgba(255,255,255,0.08),0 -1px rgba(255,255,255,0); -} -.category.stick.active{ - background: linear-gradient(180deg, #222 0%, #171717 100%) #171717; - background: -webkit-linear-gradient(top, #222 0%, #171717 100%); - box-shadow: 0px 1px #171717, 0px 1px rgba(255, 255, 255, 0.08) inset; -} -.category .btn { - color: #fff; - border: none; - background: transparent; -} -.category .btn:first-child { - position: relative; - width: 213px; - background: transparent; -} -.category.stick .btn:first-child { - width: 176px; +.aside.aside_feed .tree { + position: sticky; + top: 0; + margin: 10px 0 50px; } -.category .btn:first-child:not([data-unread="0"]):after { + +/*=== Aside main page (categories) */ +.aside_feed .tree-folder-title > .title:not([data-unread="0"]):after { position: absolute; - top: 3px; right: 3px; + right: 3px; padding: 1px 5px; - background: transparent; color: #fff; text-shadow: 0 1px rgba(255,255,255,0.08); } +.aside_feed .btn-important { + border: none; +} /*=== Aside main page (feeds) */ -.categories .feeds .item.active { - background: linear-gradient(180deg, #222 0%, #171717 100%) #171717; - background: -webkit-linear-gradient(top, #222 0%, #171717 100%); - border-radius: 4px; - margin: 0px 8px; - box-shadow: 0px 1px #171717, 0px 1px rgba(255, 255, 255, 0.08) inset, 0 2px 2px #111; -} -.categories .feeds .item.active .feed { - color: #fff; -} -.categories .feeds .item.empty .feed { +.feed.item.empty, +.feed.item.empty > a { color: #e67e22; } -.categories .feeds .item.empty.active { - background: #e67e22; -} -.categories .feeds .item.empty.active .feed { - color: #fff; -} -.categories .feeds .item.error .feed { +.feed.item.error, +.feed.item.error > a { color: #BD362F; } -.categories .feeds .item .feed { - margin: 0; - width: 165px; - line-height: 3em; - font-size: 0.8em; - text-align: left; - text-decoration: none; - color:#ccc; -} -.categories .feeds .feed:not([data-unread="0"]) { - font-weight: bold; -} -.categories .feeds .dropdown-menu:after { +.aside_feed .tree-folder-items .dropdown-menu:after { left: 2px; } -.categories .feeds .item .dropdown-target:target ~ .dropdown-toggle > .icon, -.categories .feeds .item:hover .dropdown-toggle > .icon, -.categories .feeds .item.active .dropdown-toggle > .icon { - background-color: transparent; +.aside_feed .tree-folder-items .item .dropdown-target:target ~ .dropdown-toggle > .icon, +.aside_feed .tree-folder-items .item:hover .dropdown-toggle > .icon, +.aside_feed .tree-folder-items .item.active .dropdown-toggle > .icon { border-radius: 3px; - vertical-align: middle; } /*=== Configuration pages */ @@ -934,63 +955,50 @@ opacity: 1; /*=== GLOBAL VIEW */ /*================*/ -#stream.global{ - background:#222; +#stream.global { + background: #222; padding: 24px 0; box-shadow: 0 1px #fff, 0 -2px 2px #171717 inset, 0 2px 2px #171717 inset; } -#stream.global .box-category { - background: #fff; - border-radius: 4px 4px 0 0; - text-align: left; - box-shadow: 0 0 4px #171717; - overflow:hidden; -} -#stream.global .category { - margin: 0; -} -#stream.global .btn { - width: auto; - height: 2em; - margin: 0; - padding: 0 10px; + +.box.category .box-title { background: linear-gradient(0deg, #EDE7DE 0%, #fff 100%) #171717; background: -webkit-linear-gradient(bottom, #EDE7DE 0%, #fff 100%); - border: none; box-shadow: 0px -1px #fff inset,0 -2px #ccc inset; border-radius: none; line-height: 2em; font-size: 1.2rem; - color:#888; text-shadow:0 1px #ccc; } -#stream.global .btn:not([data-unread="0"]) { +.box.category .box-title .title { + font-weight: normal; + text-decoration: none; + text-align: left; + color: #888; +} +.box.category:not([data-unread="0"]) .box-title { +} +.box.category:not([data-unread="0"]) .box-title:active { +} +.box.category:not([data-unread="0"]) .box-title .title { color: #222; font-weight: bold; } -#stream.global .btn:first-child:not([data-unread="0"]):after { - top: 0; - right: 5px; +.box.category .title:not([data-unread="0"]):after { + position: absolute; + top: 5px; right: 10px; border: 0; background: none; - color: #222; font-weight: bold; - box-shadow: none; - text-shadow: none; } -#stream.global .box-category .feeds { - max-height: 250px; - color:#222; - background:#EDE7DE; -} -#stream.global .box-category .feeds .item { +.box.category .item.feed { padding: 2px 10px; - font-size: 0.9rem; - overflow:hidden; + font-size: 0.8rem; } -#stream.global .box-category .feed { - color:#222; +.box.category .item.feed:not(.empty):not(.error) .item-title { + color: #222; } + /*=== PANEL */ /*===========*/ #panel { @@ -1101,12 +1109,10 @@ opacity: 1; } .aside .toggle_aside, #panel .close { - position: absolute; display: block; - top: 0; right: 0; - width: 30px; - height: 30px; - line-height: 30px; + width: 100%; + height: 40px; + line-height: 40px; text-align: center; background: #171717; box-shadow: 0 1px rgba(255,255,255,0.08); @@ -1117,6 +1123,13 @@ opacity: 1; margin: 20px 0 0; } + .aside.aside_feed { + padding: 0; + } + .aside.aside_feed .tree { + position: static; + } + .nav_menu .btn { margin: 5px 10px; } diff --git a/p/themes/base-theme/base.css b/p/themes/base-theme/base.css index ccfab10df..ac30dbfea 100644 --- a/p/themes/base-theme/base.css +++ b/p/themes/base-theme/base.css @@ -329,6 +329,66 @@ a.btn { font-size: 0; } +/*=== Boxes */ +.box { +} +.box .box-title { + margin: 0; + padding: 5px 10px; +} +.box .box-content { + max-height: 260px; +} + +.box .box-content .item { + padding: 0 10px; + font-size: 0.9rem; + line-height: 2.5em; +} + +.box .box-content .item .configure { + visibility: hidden; +} +.box .box-content .item:hover .configure { + visibility: visible; +} + +/*=== Tree */ +.tree { + margin: 10px 0; +} +.tree-folder-title { + position: relative; + padding: 0 10px; + line-height: 2.5rem; + font-size: 1rem; +} +.tree-folder-title .title { + background: inherit; +} +.tree-folder-title .title:hover { + text-decoration: none; +} +.tree-folder.active .tree-folder-title { + font-weight: bold; +} +.tree-folder.active .tree-folder-title .title { +} +.tree-folder-items { +} +.tree-folder-items > .item { + padding: 0 10px; + line-height: 2.5rem; + font-size: 0.8rem; +} +.tree-folder-items > .item.active { +} +.tree-folder-items > .item > a { + text-decoration: none; +} +.tree-folder-items > .item.active > a { +} + /*=== STRUCTURE */ /*===============*/ /*=== Header */ @@ -362,65 +422,49 @@ a.btn { } .aside { } -.aside.aside_flux { - padding: 10px 0 50px; -} - -/*=== Aside main page (categories) */ -.categories { +.aside.aside_feed { + padding: 10px 0; text-align: center; } -.category { - width: 235px; - margin: 10px auto; - text-align: left; +.aside.aside_feed .tree { + position: sticky; + top: 0; + margin: 10px 0 50px; } -.category .btn:first-child { - position: relative; - width: 213px; -} -.category.stick .btn:first-child { - width: 176px; -} -.category .btn:first-child:not([data-unread="0"]):after { + +/*=== Aside main page (categories) */ +.aside_feed .tree-folder-title > .title:not([data-unread="0"]):after { position: absolute; - top: 3px; right: 3px; - padding: 1px 5px; + right: 0; + margin: 10px 0; + padding: 0 10px; + font-size: 0.9rem; + line-height: 1.5rem; } /*=== Aside main page (feeds) */ -.categories .feeds .item.active { -} -.categories .feeds .item.empty.active { +.feed.item.empty.active { } -.categories .feeds .item.error.active { +.feed.item.error.active { } -.categories .feeds .item.empty .feed { +.feed.item.empty, +.feed.item.empty > a { } -.categories .feeds .item.error .feed { -} -.categories .feeds .item.active .feed, -.categories .feeds .item.empty.active .feed, -.categories .feeds .item.error.active .feed { -} -.categories .feeds .item .feed { - margin: 0; - width: 165px; - line-height: 3em; - font-size: 0.8em; - text-align: left; - text-decoration: none; +.feed.item.error, +.feed.item.error > a { } -.categories .feeds .feed:not([data-unread="0"]) { - font-weight: bold; +.feed.item.empty.active, +.feed.item.error.active, +.feed.item.empty.active > a, +.feed.item.error.active > a { } -.categories .feeds .dropdown-menu:after { +.aside_feed .tree-folder-items .dropdown-menu:after { left: 2px; } -.categories .feeds .item .dropdown-target:target ~ .dropdown-toggle > .icon, -.categories .feeds .item:hover .dropdown-toggle > .icon, -.categories .feeds .item.active .dropdown-toggle > .icon { - vertical-align: middle; +.aside_feed .tree-folder-items .item .dropdown-target:target ~ .dropdown-toggle > .icon, +.aside_feed .tree-folder-items .item:hover .dropdown-toggle > .icon, +.aside_feed .tree-folder-items .item.active .dropdown-toggle > .icon { + border-radius: 3px; } /*=== Configuration pages */ @@ -621,33 +665,30 @@ a.btn { /*=== GLOBAL VIEW */ /*================*/ -#stream.global .box-category { +.box.category .box-title .title { + font-weight: normal; + text-decoration: none; text-align: left; } -#stream.global .category { - margin: 0; +.box.category:not([data-unread="0"]) .box-title { } -#stream.global .btn { - width: auto; - height: 2em; - margin: 0; - padding: 0 10px; - line-height: 2em; - font-size: 1.2rem; +.box.category:not([data-unread="0"]) .box-title:active { } -#stream.global .btn:not([data-unread="0"]) { +.box.category:not([data-unread="0"]) .box-title .title { font-weight: bold; } -#stream.global .btn:first-child:not([data-unread="0"]):after { - top: 0; right: 5px; +.box.category .title:not([data-unread="0"]):after { + position: absolute; + top: 5px; right: 10px; + border: 0; + background: none; font-weight: bold; + box-shadow: none; + text-shadow: none; } -#stream.global .box-category .feeds { - max-height: 250px; -} -#stream.global .box-category .feeds .item { +.box.category .item.feed { padding: 2px 10px; - font-size: 0.9rem; + font-size: 0.8rem; } /*=== DIVERS */ @@ -726,15 +767,20 @@ a.btn { } .aside .toggle_aside, #panel .close { - position: absolute; display: block; - top: 0; right: 0; - width: 30px; - height: 30px; - line-height: 30px; + width: 100%; + height: 50px; + line-height: 50px; text-align: center; } + .aside.aside_feed { + padding: 0; + } + .aside.aside_feed .tree { + position: static; + } + .nav_menu .btn { margin: 5px 10px; } diff --git a/p/themes/base-theme/template.css b/p/themes/base-theme/template.css index dc011503d..5ba621415 100644 --- a/p/themes/base-theme/template.css +++ b/p/themes/base-theme/template.css @@ -73,7 +73,9 @@ label { input { width: 180px; } -textarea { +textarea, +input[type="file"], +input.extend:focus { width: 300px; } input, select, textarea { @@ -85,9 +87,6 @@ input[type="checkbox"] { width: 15px !important; min-height: 15px !important; } -input.extend:focus { - width: 300px; -} button.as-link, button.as-link:hover, button.as-link:active { @@ -180,6 +179,7 @@ a.btn { .dropdown { position: relative; display: inline-block; + vertical-align: middle; } .dropdown-target { display: none; @@ -280,6 +280,91 @@ a.btn { width: 100px; } +/*=== Boxes */ +.box { + display: inline-block; + width: 20rem; + max-width: 95%; + margin: 20px 10px; + border: 1px solid #ccc; + vertical-align: top; +} +.box .box-title { + position: relative; + font-size: 1.2rem; + font-weight: bold; + text-align: center; +} +.box .box-title a { + display: block; +} +.box .box-title form { + margin: 0; +} +.box .box-content { + display: block; + overflow: auto; +} +.box .box-content .item { + display: block; +} +.box .box-content .item.disabled { + text-align: center; + font-style: italic; +} + +.box .box-content-centered { + padding: 30px 5px; + text-align: center; +} +.box .box-content-centered .btn { + margin: 20px 0 0; +} + +/*=== Draggable */ +.drag-hover { + margin: 0 0 5px; + border-bottom: 2px solid #ccc; +} +[draggable=true] { + cursor: grab; +} + +/*=== Tree */ +.tree { + margin: 0; + padding: 0; + list-style: none; + text-align: left; +} +.tree-folder-items { + padding: 0; + list-style: none; +} +.tree-folder-title { + display: block; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} +.tree-folder-title .title { + display: inline-block; + width: 100%; + vertical-align: middle; +} +.tree-folder-items > .item { + display: block; + white-space: nowrap; +} +.tree-folder-items > .item > a { + display: inline-block; + vertical-align: middle; + width: calc(100% - 32px); + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; +} + /*=== STRUCTURE */ /*===============*/ /*=== Header */ @@ -318,60 +403,37 @@ a.btn { .aside { display: table-cell; height: 100%; - width: 250px; + width: 300px; vertical-align: top; } -.aside.aside_flux { - background: #fff; -} -/*=== Aside main page (categories) */ -.categories { - list-style: none; - margin: 0; -} -.state_unread li:not(.active)[data-unread="0"] { - display: none; -} -.category { - display: block; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; -} -.category .btn:not([data-unread="0"]):after { - content: attr(data-unread); +/*=== Aside main page */ +.aside_feed .category .title { + width: calc(100% - 35px); } -/*=== Aside main page (feeds) */ -.categories .feeds { - width: 100%; - list-style: none; +.aside_feed .tree-folder-title .icon { + padding: 5px; } -.categories .feeds:not(.active) { - display: none; +.aside_feed .tree-folder-items .item.feed { + padding: 0px 15px; } -.categories .feeds .feed { - display: inline-block; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - vertical-align: middle; +.aside_feed .tree-folder-items:not(.active) { + display: none; } -.categories .feeds .feed:not([data-unread="0"]):before { - content: "(" attr(data-unread) ") "; +.aside_feed .tree-folder-items .dropdown { + vertical-align: top; } -.categories .feeds .dropdown-menu { +.aside_feed .tree-folder-items .dropdown-menu { left: 0; } -.categories .feeds .item .dropdown-toggle > .icon { +.aside_feed .tree-folder-items .item .dropdown-toggle > .icon { visibility: hidden; cursor: pointer; - vertical-align: top; } -.categories .feeds .item .dropdown-target:target ~ .dropdown-toggle > .icon, -.categories .feeds .item:hover .dropdown-toggle > .icon, -.categories .feeds .item.active .dropdown-toggle > .icon { +.aside_feed .tree-folder-items .item .dropdown-target:target ~ .dropdown-toggle > .icon, +.aside_feed .tree-folder-items .item:hover .dropdown-toggle > .icon, +.aside_feed .tree-folder-items .item.active .dropdown-toggle > .icon { visibility: visible; } @@ -425,6 +487,7 @@ a.btn { .flux .item.date { width: 145px; text-align: right; + overflow: hidden; } .flux .item > a { display: block; @@ -512,7 +575,7 @@ br + br + br { position: fixed; bottom: 0; left: 0; display: table; - width: 250px; + width: 300px; background: #fff; table-layout: fixed; } @@ -558,28 +621,12 @@ br + br + br { /*=== GLOBAL VIEW */ /*================*/ -/*=== Category boxes */ -#stream.global .box-category { - display: inline-block; - width: 19em; - max-width: 95%; - margin: 20px 10px; - border: 1px solid #ccc; - vertical-align: top; -} -#stream.global .category { - width: 100%; -} -#stream.global .btn { - display: block; -} -#stream.global .box-category .feeds { - display: block; - overflow: auto; +#stream.global { + text-align: center; } -#stream.global .box-category .feed { - width: 19em; - max-width: 90%; + +#stream.global .box { + text-align: left; } /*=== Panel */ @@ -598,51 +645,84 @@ br + br + br { overflow: auto; background: #fff; } -#panel .close { +#overlay .close { position: fixed; top: 0; bottom: 0; left: 0; right: 0; display: block; } -#panel .close img { +#overlay .close img { display: none; } +/*=== Slider */ +#slider { + position: fixed; + top: 0; bottom: 0; + left: 100%; right: 0; + overflow: auto; + background: #fff; + border-left: 1px solid #aaa; + transition: left 200ms linear; + -moz-transition: left 200ms linear; + -webkit-transition: left 200ms linear; + -o-transition: left 200ms linear; + -ms-transition: left 200ms linear; +} +#slider.active { + left: 40%; +} +#close-slider { + position: fixed; + top: 0; bottom: 0; + left: 100%; right: 0; + cursor: pointer; +} +#close-slider.active { + left: 0; +} + /*=== DIVERS */ /*===========*/ +.category .title:not([data-unread="0"]):after { + content: attr(data-unread); +} +.feed .item-title:not([data-unread="0"]):before { + content: "(" attr(data-unread) ") "; +} +.feed .item-title:not([data-unread="0"]) { + font-weight: bold; +} + +.state_unread .category:not(.active)[data-unread="0"], +.state_unread .feed:not(.active)[data-unread="0"] { + display: none; +} + .nav-login, .nav_menu .search, +.aside .toggle_aside, .nav_menu .toggle_aside { display: none; } -.aside .toggle_aside { - position: absolute; - right: 0; - display: none; - width: 30px; - height: 30px; - line-height: 30px; - text-align: center; -} /*=== MOBILE */ /*===========*/ @media(max-width: 840px) { .header, .aside .btn-important, - .aside .feeds .dropdown, .flux_header .item.website span, .item.date, .day .date, .dropdown-menu > .no-mobile, .no-mobile { display: none; } + .aside .toggle_aside, .nav-login { display: block; } .nav_menu .toggle_aside, - .aside .toggle_aside, .nav_menu .search, #panel .close img { display: inline-block; @@ -660,9 +740,6 @@ br + br + br { width: 90%; overflow: auto; } - .aside .categories { - margin: 10px 0 75px; - } .flux_header .item.website { width: 40px; @@ -684,12 +761,8 @@ br + br + br { width: 100%; } - #stream.global .box-category { - margin: 10px 0; - } - #panel { - top: 0; bottom: 0; + top: 25px; bottom: 30px; left: 0; right: 0; } #panel .close { diff --git a/p/themes/icons/import.svg b/p/themes/icons/import.svg new file mode 100644 index 000000000..18a54ab59 --- /dev/null +++ b/p/themes/icons/import.svg @@ -0,0 +1 @@ +<svg xmlns:svg="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg" version="1.1" height="16" width="16"><g transform="translate(-80,-648)"/><g transform="translate(-80,-648)"/><g transform="translate(-80,-648)"/><g transform="translate(-80,-648)"><path d="m84.406 657a0.5 0.5 0 0 0-0.312 0.219l-1 1.5a0.5 0.5 0 1 0 0.813 0.563l1-1.5A0.5 0.5 0 0 0 84.406 657zm7 0a0.5 0.5 0 0 0-0.312 0.781l1 1.5a0.5 0.5 0 1 0 0.813-0.562l-1-1.5A0.5 0.5 0 0 0 91.406 657z" style="-inkscape-font-specification:Sans;baseline-shift:baseline;block-progression:tb;direction:ltr;fill:#ffffff;font-family:Sans;font-size:medium;letter-spacing:normal;line-height:normal;text-align:start;text-anchor:start;text-decoration:none;text-indent:0;text-transform:none;word-spacing:normal;writing-mode:lr-tb"/><g transform="translate(-80,110)"><path d="m167 539 0 5.563-1.281-1.281C165.531 543.093 165.265 543 165 543l-1 0 0 1c0 0.265 0.093 0.531 0.281 0.719l3 3 0.281 0.281 0.875 0 0.281-0.281 3-3C171.907 544.531 172 544.265 172 544l0-1-1 0c-0.265 0-0.531 0.093-0.719 0.281L169 544.563 169 539z" style="-inkscape-font-specification:Bitstream Vera Sans;block-progression:tb;direction:ltr;fill:#ffffff;font-family:Bitstream Vera Sans;font-size:medium;letter-spacing:normal;line-height:normal;text-align:start;text-anchor:start;text-decoration:none;text-indent:0;text-transform:none;word-spacing:normal;writing-mode:lr-tb"/><path d="m163 549 0 4 10 0 0-4zm3.344 1.438c0.021-0.001 0.042-0.001 0.063 0 0.291-0.056 0.599 0.204 0.594 0.5l0 0.063 2 0 0-0.062c-0.004-0.264 0.236-0.507 0.5-0.507 0.264 0 0.504 0.243 0.5 0.507L170 551c0 0.545-0.455 1-1 1l-2 0c-0.545 0-1-0.455-1-1l0-0.062c-0.011-0.217 0.137-0.432 0.344-0.5z" fill="#ffffff"/></g></g><g transform="translate(-80,-648)"/><g transform="translate(-80,-648)"/><g transform="translate(-80,-648)"/></svg> |
