diff options
| author | 2014-10-31 16:57:11 +0100 | |
|---|---|---|
| committer | 2014-10-31 16:57:11 +0100 | |
| commit | 54479a5027d83b5dc8deee5e2795c9d89c732ba0 (patch) | |
| tree | 7ae55930f3ab6d5e2a548784e4d7561809f7c68c /app/Controllers | |
| parent | cff8636e770b2072a41928cd918b37654c0dafbb (diff) | |
| parent | 724e13f0a6419b046b33da71e66058e279551edd (diff) | |
Merge branch 'dev' into beta
Conflicts:
CHANGELOG
README.fr.md
README.md
app/Controllers/feedController.php
app/Controllers/indexController.php
app/i18n/en.php
app/i18n/fr.php
app/views/helpers/view/normal_view.phtml
app/views/stats/index.phtml
app/views/stats/repartition.phtml
constants.php
p/scripts/main.js
Diffstat (limited to 'app/Controllers')
| -rw-r--r-- | app/Controllers/authController.php | 349 | ||||
| -rw-r--r-- | app/Controllers/categoryController.php | 192 | ||||
| -rwxr-xr-x | app/Controllers/configureController.php | 287 | ||||
| -rwxr-xr-x | app/Controllers/entryController.php | 242 | ||||
| -rw-r--r-- | app/Controllers/errorController.php | 59 | ||||
| -rwxr-xr-x | app/Controllers/feedController.php | 679 | ||||
| -rw-r--r-- | app/Controllers/importExportController.php | 239 | ||||
| -rwxr-xr-x | app/Controllers/indexController.php | 617 | ||||
| -rwxr-xr-x | app/Controllers/javascriptController.php | 10 | ||||
| -rw-r--r-- | app/Controllers/statsController.php | 31 | ||||
| -rw-r--r-- | app/Controllers/subscriptionController.php | 115 | ||||
| -rw-r--r-- | app/Controllers/updateController.php | 43 | ||||
| -rw-r--r-- | app/Controllers/userController.php (renamed from app/Controllers/usersController.php) | 126 |
13 files changed, 1733 insertions, 1256 deletions
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); } } |
