/** * 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); } $file = $_FILES['file']; $status_file = $file['error']; if ($status_file !== 0) { Minz_Log::error('File cannot be uploaded. Error code: ' . $status_file); Minz_Request::bad(_t('feedback.import_export.file_cannot_be_uploaded'), array('c' => 'importExport', 'a' => 'index')); } @set_time_limit(300); $type_file = $this->guessFileType($file['name']); $list_files = array('opml' => array(), 'json_starred' => array(), 'json_feed' => array()); // We try to list all files according to their type $list = array(); if ($type_file === 'zip' && extension_loaded('zip')) { $zip = zip_open($file['tmp_name']); if (!is_resource($zip)) { // zip_open cannot open file: something is wrong Minz_Log::error('Zip archive cannot be imported. Error code: ' . $zip); Minz_Request::bad(_t('feedback.import_export.zip_error'), array('c' => 'importExport', 'a' => 'index')); } while (($zipfile = zip_read($zip)) !== false) { if (!is_resource($zipfile)) { // zip_entry() can also return an error code! Minz_Log::error('Zip file cannot be imported. Error code: ' . $zipfile); } else { $type_zipfile = $this->guessFileType(zip_entry_name($zipfile)); if ($type_file !== 'unknown') { $list_files[$type_zipfile][] = zip_entry_read($zipfile, zip_entry_filesize($zipfile)); } } } zip_close($zip); } elseif ($type_file === 'zip') { // Zip extension is not loaded Minz_Request::bad(_t('feedback.import_export.no_zip_extension'), array('c' => 'importExport', 'a' => 'index')); } elseif ($type_file !== 'unknown') { $list_files[$type_file][] = file_get_contents($file['tmp_name']); } // Import file contents. // OPML first(so categories and feeds are imported) // Starred articles then so the "favourite" status is already set // And finally all other files. $error = false; foreach ($list_files['opml'] as $opml_file) { $error = $this->importOpml($opml_file); } foreach ($list_files['json_starred'] as $article_file) { $error = $this->importJson($article_file, true); } foreach ($list_files['json_feed'] as $article_file) { $error = $this->importJson($article_file); } // And finally, we get import status and redirect to the home page Minz_Session::_param('actualize_feeds', true); $content_notif = $error === true ? _t('feedback.import_export.feeds_imported_with_errors') : _t('feedback.import_export.feeds_imported'); Minz_Request::good($content_notif); }
public function deleteCategory($id) { $sql = 'DELETE FROM `' . $this->prefix . 'category` WHERE id=?'; $stm = $this->bd->prepare($sql); $values = array($id); if ($stm && $stm->execute($values)) { return $stm->rowCount(); } else { $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); Minz_Log::error('SQL error deleteCategory: ' . $info[2]); return false; } }
public function updateCachedValues() { //For one single feed, call updateLastUpdate($id) $sql = 'UPDATE `' . $this->prefix . 'feed` ' . 'SET cache_nbEntries=(SELECT COUNT(e1.id) FROM `' . $this->prefix . 'entry` e1 WHERE e1.id_feed=`' . $this->prefix . 'feed`.id),' . 'cache_nbUnreads=(SELECT COUNT(e2.id) FROM `' . $this->prefix . 'entry` e2 WHERE e2.id_feed=`' . $this->prefix . 'feed`.id AND e2.is_read=0)'; $stm = $this->bd->prepare($sql); if ($stm && $stm->execute()) { return $stm->rowCount(); } else { $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); Minz_Log::error('SQL error updateCachedValues: ' . $info[2]); return false; } }
/** * Démarre l'application (lance le dispatcher et renvoie la réponse) */ public function run() { try { $this->dispatcher->run(); } catch (Minz_Exception $e) { try { Minz_Log::error($e->getMessage()); } catch (Minz_PermissionDeniedException $e) { $this->killApp($e->getMessage()); } if ($e instanceof Minz_FileNotExistException || $e instanceof Minz_ControllerNotExistException || $e instanceof Minz_ControllerNotActionControllerException || $e instanceof Minz_ActionException) { Minz_Error::error(404, array('error' => array($e->getMessage())), true); } else { $this->killApp(); } } }
public function deleteUser($username) { $db = FreshRSS_Context::$system_conf->db; require_once APP_PATH . '/SQL/install.sql.' . $db['type'] . '.php'; if ($db['type'] === 'sqlite') { return unlink(join_path(DATA_PATH, 'users', $username, 'db.sqlite')); } else { $userPDO = new Minz_ModelPdo($username); $sql = sprintf(SQL_DROP_TABLES, $db['prefix'] . $username . '_'); $stm = $userPDO->bd->prepare($sql); if ($stm && $stm->execute()) { return true; } else { $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); Minz_Log::error('SQL error : ' . $info[2]); return false; } } }
public function checkAction() { $this->view->change_view('update', 'index'); if (file_exists(UPDATE_FILENAME)) { // There is already an update file to apply: we don't need to check // the webserver! // Or if already check during the last hour, do nothing. Minz_Request::forward(array('c' => 'update'), true); return; } $c = curl_init(FRESHRSS_UPDATE_WEBSITE); curl_setopt($c, CURLOPT_RETURNTRANSFER, true); curl_setopt($c, CURLOPT_SSL_VERIFYPEER, true); curl_setopt($c, CURLOPT_SSL_VERIFYHOST, 2); $result = curl_exec($c); $c_status = curl_getinfo($c, CURLINFO_HTTP_CODE); $c_error = curl_error($c); curl_close($c); if ($c_status !== 200) { Minz_Log::error('Error during update (HTTP code ' . $c_status . '): ' . $c_error); $this->view->message = array('status' => 'bad', 'title' => _t('gen.short.damn'), 'body' => _t('feedback.update.server_not_found', FRESHRSS_UPDATE_WEBSITE)); return; } $res_array = explode("\n", $result, 2); $status = $res_array[0]; if (strpos($status, 'UPDATE') !== 0) { $this->view->message = array('status' => 'bad', 'title' => _t('gen.short.damn'), 'body' => _t('feedback.update.none')); @touch(join_path(DATA_PATH, 'last_update.txt')); return; } $script = $res_array[1]; if (file_put_contents(UPDATE_FILENAME, $script) !== false) { $version = explode(' ', $status, 2); $version = $version[1]; @file_put_contents(join_path(DATA_PATH, 'last_update.txt'), $version); Minz_Request::forward(array('c' => 'update'), true); } else { $this->view->message = array('status' => 'bad', 'title' => _t('gen.short.damn'), 'body' => _t('feedback.update.error', 'Cannot save the update script')); } }
/** * Mark all the articles in a category as read. * There is a fail safe to prevent to mark as read articles that are * loaded during the mark as read action. Then the cache is updated. * * If $idMax equals 0, a deprecated debug message is logged * * @param integer $id category ID * @param integer $idMax fail safe article ID * @return integer affected rows */ public function markReadCat($id, $idMax = 0) { if ($idMax == 0) { $idMax = time() . '000000'; Minz_Log::debug('Calling markReadCat(0) is deprecated!'); } $sql = 'UPDATE `' . $this->prefix . 'entry` ' . 'SET is_read=1 ' . 'WHERE is_read=0 AND id <= ? AND ' . 'id_feed IN (SELECT f.id FROM `' . $this->prefix . 'feed` f WHERE f.category=?)'; $values = array($idMax, $id); $stm = $this->bd->prepare($sql); if (!($stm && $stm->execute($values))) { $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); Minz_Log::error('SQL error markReadCat: ' . $info[2]); return false; } $affected = $stm->rowCount(); if ($affected > 0 && !$this->updateCacheUnreads($id, false)) { return false; } return $affected; }
# # ***** END LICENSE BLOCK ***** require '../../constants.php'; require LIB_PATH . '/lib_rss.php'; //Includes class autoloader if (file_exists(DATA_PATH . '/do-install.txt')) { require APP_PATH . '/install.php'; } else { session_cache_limiter(''); Minz_Session::init('FreshRSS'); Minz_Session::_param('keepAlive', 1); //For Persona if (!file_exists(DATA_PATH . '/no-cache.txt')) { require LIB_PATH . '/http-conditional.php'; $currentUser = Minz_Session::param('currentUser', ''); $dateLastModification = $currentUser === '' ? time() : max(@filemtime(join_path(USERS_PATH, $currentUser, 'log.txt')), @filemtime(join_path(DATA_PATH, 'config.php'))); if (httpConditional($dateLastModification, 0, 0, false, PHP_COMPRESSION, true)) { exit; //No need to send anything } } try { $front_controller = new FreshRSS(); $front_controller->init(); $front_controller->run(); } catch (Exception $e) { echo '### Fatal error! ###<br />', "\n"; Minz_Log::error($e->getMessage()); echo 'See logs files.'; } }
/** * 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' => 'subscription', 'a' => 'index'), true); } $feedDAO = FreshRSS_Factory::createFeedDao(); $this->catDAO = new FreshRSS_CategoryDAO(); $url_redirect = array('c' => 'subscription', 'a' => 'index', 'params' => array()); $limits = FreshRSS_Context::$system_conf->limits; $this->view->feeds = $feedDAO->listFeeds(); if (count($this->view->feeds) >= $limits['max_feeds']) { Minz_Request::bad(_t('feedback.sub.feed.over_max', $limits['max_feeds']), $url_redirect); } if (Minz_Request::isPost()) { @set_time_limit(300); $cat = Minz_Request::param('category'); if ($cat === 'nc') { // 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) { // 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(); } // HTTP information are useful if feed is protected behind a // HTTP authentication $user = trim(Minz_Request::param('http_user', '')); $pass = Minz_Request::param('http_pass', ''); $http_auth = ''; if ($user != '' && $pass != '') { //TODO: Sanitize $http_auth = $user . ':' . $pass; } $transaction_started = false; try { $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('feedback.sub.feed.invalid_url', $url), $url_redirect); } try { $feed->load(true); } catch (FreshRSS_Feed_Exception $e) { // Something went bad (timeout, server not found, etc.) Minz_Log::warning($e->getMessage()); Minz_Request::bad(_t('feedback.sub.feed.internal_problem', _url('index', 'logs')), $url_redirect); } catch (Minz_FileNotExistException $e) { // Cache directory doesn't exist! Minz_Log::error($e->getMessage()); Minz_Request::bad(_t('feedback.sub.feed.internal_problem', _url('index', 'logs')), $url_redirect); } if ($feedDAO->searchByUrl($feed->url())) { Minz_Request::bad(_t('feedback.sub.feed.already_subscribed', $feed->name()), $url_redirect); } $feed->_category($cat); $feed->_httpAuth($http_auth); // Call the extension hook $name = $feed->name(); $feed = Minz_ExtensionManager::callHook('feed_before_insert', $feed); if ($feed === null) { Minz_Request::bad(_t('feedback.sub.feed.not_added', $name), $url_redirect); } $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('feedback.sub.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(); //$feed->pubSubHubbubPrepare(); //TODO: prepare PubSubHubbub already when adding the feed $is_read = FreshRSS_Context::$user_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::$user_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. $feedDAO->beginTransaction(); foreach ($entries as $entry) { // Entries are added without any verification. $entry->_feed($feed->id()); $entry->_id(min(time(), $entry->date(true)) . uSecString()); $entry->_isRead($is_read); $entry = Minz_ExtensionManager::callHook('entry_before_insert', $entry); if ($entry === null) { // An extension has returned a null value, there is nothing to insert. continue; } $values = $entry->toArray(); $entryDAO->addEntry($values); } $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('feedback.sub.feed.added', $feed->name()), $url_redirect); } else { // GET request: we must ask confirmation to user before adding feed. Minz_View::prependTitle(_t('sub.feed.title_add') . ' · '); $this->view->categories = $this->catDAO->listCategories(false); $this->view->feed = new FreshRSS_Feed($url); try { // We try to get more information about the feed. $this->view->feed->load(true); $this->view->load_ok = true; } catch (Exception $e) { $this->view->load_ok = false; } $feed = $feedDAO->searchByUrl($this->view->feed->url()); if ($feed) { // Already subscribe so we redirect to the feed configuration page. $url_redirect['params']['id'] = $feed->id(); Minz_Request::good(_t('feedback.sub.feed.already_subscribed', $feed->name()), $url_redirect); } } }
public function cleanOldEntries($id, $date_min, $keep = 15) { //Remember to call updateLastUpdate($id) or updateCachedValues() just after $sql = 'DELETE FROM ' . $this->prefix . 'entry ' . 'WHERE id_feed=:id_feed AND id<=:id_max ' . 'AND is_favorite=false ' . 'AND lastSeen < (SELECT maxLastSeen FROM (SELECT (MAX(e3.lastSeen)-99) AS maxLastSeen FROM ' . $this->prefix . 'entry e3 WHERE e3.id_feed=:id_feed) recent) ' . 'AND id NOT IN (SELECT id FROM (SELECT e2.id FROM ' . $this->prefix . 'entry e2 WHERE e2.id_feed=:id_feed ORDER BY id DESC LIMIT :keep) keep)'; //Double select: MySQL doesn't support 'LIMIT & IN/ALL/ANY/SOME subquery' $stm = $this->bd->prepare($sql); if ($stm) { $id_max = intval($date_min) . '000000'; $stm->bindParam(':id_feed', $id, PDO::PARAM_INT); $stm->bindParam(':id_max', $id_max, PDO::PARAM_STR); $stm->bindParam(':keep', $keep, PDO::PARAM_INT); } if ($stm && $stm->execute()) { return $stm->rowCount(); } else { $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); Minz_Log::error('SQL error cleanOldEntries: ' . $info[2]); return false; } }
/** * 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); $conf = get_user_configuration($current_user); if (!is_null($conf)) { $login_ok = strcasecmp($email, $conf->mail_login) === 0; } else { $reason = 'Invalid configuration for user ' . '[' . $current_user . ']'; } } } 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('feedback.auth.login.invalid'); } header('Content-Type: application/json; charset=UTF-8'); $this->view->res = $res; } }
public function updateLastSeen($id_feed, $guids) { if (count($guids) < 1) { return 0; } $sql = 'UPDATE `' . $this->prefix . 'entry` SET lastSeen=? WHERE id_feed=? AND guid IN (' . str_repeat('?,', count($guids) - 1) . '?)'; $stm = $this->bd->prepare($sql); $values = array(time(), $id_feed); $values = array_merge($values, $guids); if ($stm && $stm->execute($values)) { return $stm->rowCount(); } else { $info = $stm == null ? array(0 => '', 1 => '', 2 => 'syntax error') : $stm->errorInfo(); if ($this->autoAddColumn($info)) { return $this->updateLastSeen($id_feed, $guids); } Minz_Log::error('SQL error updateLastSeen: ' . $info[0] . ': ' . $info[1] . ' ' . $info[2] . ' while updating feed ' . $id_feed); return false; } }
/** * Mark all the articles in a feed as read. * There is a fail safe to prevent to mark as read articles that are * loaded during the mark as read action. Then the cache is updated. * * If $idMax equals 0, a deprecated debug message is logged * * @param integer $id feed ID * @param integer $idMax fail safe article ID * @return integer affected rows */ public function markReadFeed($id, $idMax = 0) { if ($idMax == 0) { $idMax = time() . '000000'; Minz_Log::debug('Calling markReadFeed(0) is deprecated!'); } $this->bd->beginTransaction(); $sql = 'UPDATE `' . $this->prefix . 'entry` ' . 'SET is_read=1 ' . 'WHERE id_feed=? AND is_read=0 AND id <= ?'; $values = array($id, $idMax); $stm = $this->bd->prepare($sql); if (!($stm && $stm->execute($values))) { $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); Minz_Log::error('SQL error markReadFeed: ' . $info[2]); $this->bd->rollBack(); return false; } $affected = $stm->rowCount(); if ($affected > 0) { $sql = 'UPDATE `' . $this->prefix . 'feed` ' . 'SET cache_nbUnreads=cache_nbUnreads-' . $affected . ' WHERE id=?'; $values = array($id); $stm = $this->bd->prepare($sql); if (!($stm && $stm->execute($values))) { $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); Minz_Log::error('SQL error markReadFeed: ' . $info[2]); $this->bd->rollBack(); return false; } } $this->bd->commit(); return $affected; }