diff options
Diffstat (limited to 'app/Models')
| -rw-r--r-- | app/Models/Category.php | 7 | ||||
| -rw-r--r-- | app/Models/CategoryDAO.php | 4 | ||||
| -rw-r--r-- | app/Models/ConfigurationSetter.php | 31 | ||||
| -rw-r--r-- | app/Models/Context.php | 16 | ||||
| -rw-r--r-- | app/Models/Entry.php | 20 | ||||
| -rw-r--r-- | app/Models/EntryDAO.php | 314 | ||||
| -rw-r--r-- | app/Models/EntryDAOSQLite.php | 15 | ||||
| -rw-r--r-- | app/Models/Feed.php | 165 | ||||
| -rw-r--r-- | app/Models/FeedDAO.php | 21 | ||||
| -rw-r--r-- | app/Models/LogDAO.php | 5 | ||||
| -rw-r--r-- | app/Models/Search.php | 229 | ||||
| -rw-r--r-- | app/Models/Searchable.php | 6 | ||||
| -rw-r--r-- | app/Models/Share.php | 2 | ||||
| -rw-r--r-- | app/Models/UserQuery.php | 226 |
14 files changed, 929 insertions, 132 deletions
diff --git a/app/Models/Category.php b/app/Models/Category.php index 37cb44dc3..9a44a2d09 100644 --- a/app/Models/Category.php +++ b/app/Models/Category.php @@ -6,6 +6,7 @@ class FreshRSS_Category extends Minz_Model { private $nbFeed = -1; private $nbNotRead = -1; private $feeds = null; + private $hasFeedsWithError = false; public function __construct($name = '', $feeds = null) { $this->_name($name); @@ -16,6 +17,7 @@ class FreshRSS_Category extends Minz_Model { foreach ($feeds as $feed) { $this->nbFeed++; $this->nbNotRead += $feed->nbNotRead(); + $this->hasFeedsWithError |= $feed->inError(); } } } @@ -51,12 +53,17 @@ class FreshRSS_Category extends Minz_Model { foreach ($this->feeds as $feed) { $this->nbFeed++; $this->nbNotRead += $feed->nbNotRead(); + $this->hasFeedsWithError |= $feed->inError(); } } return $this->feeds; } + public function hasFeedsWithError() { + return $this->hasFeedsWithError; + } + public function _id($value) { $this->id = $value; } diff --git a/app/Models/CategoryDAO.php b/app/Models/CategoryDAO.php index 27a558522..b5abac519 100644 --- a/app/Models/CategoryDAO.php +++ b/app/Models/CategoryDAO.php @@ -1,6 +1,6 @@ <?php -class FreshRSS_CategoryDAO extends Minz_ModelPdo { +class FreshRSS_CategoryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { public function addCategory($valuesTmp) { $sql = 'INSERT INTO `' . $this->prefix . 'category`(name) VALUES(?)'; $stm = $this->bd->prepare($sql); @@ -13,7 +13,7 @@ class FreshRSS_CategoryDAO extends Minz_ModelPdo { return $this->bd->lastInsertId(); } else { $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); - Minz_Log::error('SQL error addCategory: ' . $info[2] ); + Minz_Log::error('SQL error addCategory: ' . $info[2]); return false; } } diff --git a/app/Models/ConfigurationSetter.php b/app/Models/ConfigurationSetter.php index eeb1f2f4c..250c14c39 100644 --- a/app/Models/ConfigurationSetter.php +++ b/app/Models/ConfigurationSetter.php @@ -117,12 +117,11 @@ class FreshRSS_ConfigurationSetter { private function _queries(&$data, $values) { $data['queries'] = array(); foreach ($values as $value) { - $value = array_filter($value); - $params = $value; - unset($params['name']); - unset($params['url']); - $value['url'] = Minz_Url::display(array('params' => $params)); - $data['queries'][] = $value; + if ($value instanceof FreshRSS_UserQuery) { + $data['queries'][] = $value->toArray(); + } elseif (is_array($value)) { + $data['queries'][] = $value; + } } } @@ -192,6 +191,10 @@ class FreshRSS_ConfigurationSetter { $data['auto_remove_article'] = $this->handleBool($value); } + private function _mark_updated_article_unread(&$data, $value) { + $data['mark_updated_article_unread'] = $this->handleBool($value); + } + private function _display_categories(&$data, $value) { $data['display_categories'] = $this->handleBool($value); } @@ -351,6 +354,9 @@ class FreshRSS_ConfigurationSetter { 'min' => 0, 'max' => $max_small_int, ), + 'max_registrations' => array( + 'min' => 0, + ), ); foreach ($values as $key => $value) { @@ -358,10 +364,11 @@ class FreshRSS_ConfigurationSetter { continue; } + $value = intval($value); $limits = $limits_keys[$key]; if ( - (!isset($limits['min']) || $value > $limits['min']) && - (!isset($limits['max']) || $value < $limits['max']) + (!isset($limits['min']) || $value >= $limits['min']) && + (!isset($limits['max']) || $value <= $limits['max']) ) { $data['limits'][$key] = $value; } @@ -371,4 +378,12 @@ class FreshRSS_ConfigurationSetter { private function _unsafe_autologin_enabled(&$data, $value) { $data['unsafe_autologin_enabled'] = $this->handleBool($value); } + + private function _auto_update_url(&$data, $value) { + if (!$value) { + return; + } + + $data['auto_update_url'] = $value; + } } diff --git a/app/Models/Context.php b/app/Models/Context.php index 1c770c756..2a58bd4ba 100644 --- a/app/Models/Context.php +++ b/app/Models/Context.php @@ -10,6 +10,7 @@ class FreshRSS_Context { public static $categories = array(); public static $name = ''; + public static $description = ''; public static $total_unread = 0; public static $total_starred = array( @@ -30,7 +31,7 @@ class FreshRSS_Context { public static $state = 0; public static $order = 'DESC'; public static $number = 0; - public static $search = ''; + public static $search; public static $first_id = ''; public static $next_id = ''; public static $id_max = ''; @@ -94,6 +95,13 @@ class FreshRSS_Context { } /** + * Return true if the current request targets a feed (and not a category or all articles), false otherwise. + */ + public static function isFeed() { + return self::$current_get['feed'] != false; + } + + /** * Return true if $get parameter correspond to the $current_get attribute. */ public static function isCurrentGet($get) { @@ -146,8 +154,8 @@ class FreshRSS_Context { self::$state = self::$state | FreshRSS_Entry::STATE_FAVORITE; break; case 'f': - // We try to find the corresponding feed. - $feed = FreshRSS_CategoryDAO::findFeed(self::$categories, $id); + // We try to find the corresponding feed. When allowing robots, always retrieve the full feed including description + $feed = FreshRSS_Context::$system_conf->allow_robots ? null : FreshRSS_CategoryDAO::findFeed(self::$categories, $id); if ($feed === null) { $feedDAO = FreshRSS_Factory::createFeedDao(); $feed = $feedDAO->searchById($id); @@ -160,6 +168,7 @@ class FreshRSS_Context { self::$current_get['feed'] = $id; self::$current_get['category'] = $feed->category(); self::$name = $feed->name(); + self::$description = $feed->description(); self::$get_unread = $feed->nbNotRead(); break; case 'c': @@ -301,4 +310,5 @@ class FreshRSS_Context { } return false; } + } diff --git a/app/Models/Entry.php b/app/Models/Entry.php index 346c98a92..a562a963a 100644 --- a/app/Models/Entry.php +++ b/app/Models/Entry.php @@ -14,7 +14,8 @@ class FreshRSS_Entry extends Minz_Model { private $content; private $link; private $date; - private $is_read; + private $hash = null; + private $is_read; //Nullable boolean private $is_favorite; private $feed; private $tags; @@ -88,6 +89,14 @@ class FreshRSS_Entry extends Minz_Model { } } + public function hash() { + if ($this->hash === null) { + //Do not include $this->date because it may be automatically generated when lacking + $this->hash = md5($this->link . $this->title . $this->author . $this->content . $this->tags(true)); + } + return $this->hash; + } + public function _id($value) { $this->id = $value; } @@ -95,23 +104,28 @@ class FreshRSS_Entry extends Minz_Model { $this->guid = $value; } public function _title($value) { + $this->hash = null; $this->title = $value; } public function _author($value) { + $this->hash = null; $this->author = $value; } public function _content($value) { + $this->hash = null; $this->content = $value; } public function _link($value) { + $this->hash = null; $this->link = $value; } public function _date($value) { + $this->hash = null; $value = intval($value); $this->date = $value > 1 ? $value : time(); } public function _isRead($value) { - $this->is_read = $value; + $this->is_read = $value === null ? null : (bool)$value; } public function _isFavorite($value) { $this->is_favorite = $value; @@ -120,6 +134,7 @@ class FreshRSS_Entry extends Minz_Model { $this->feed = $value; } public function _tags($value) { + $this->hash = null; if (!is_array($value)) { $value = array($value); } @@ -182,6 +197,7 @@ class FreshRSS_Entry extends Minz_Model { 'content' => $this->content(), 'link' => $this->link(), 'date' => $this->date(true), + 'hash' => $this->hash(), 'is_read' => $this->isRead(), 'is_favorite' => $this->isFavorite(), 'id_feed' => $this->feed(), diff --git a/app/Models/EntryDAO.php b/app/Models/EntryDAO.php index 61beeea13..f74055835 100644 --- a/app/Models/EntryDAO.php +++ b/app/Models/EntryDAO.php @@ -1,25 +1,78 @@ <?php -class FreshRSS_EntryDAO extends Minz_ModelPdo { +class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { public function isCompressed() { return parent::$sharedDbType !== 'sqlite'; } - public function addEntryPrepare() { - $sql = 'INSERT INTO `' . $this->prefix . 'entry`(id, guid, title, author, ' - . ($this->isCompressed() ? 'content_bin' : 'content') - . ', link, date, is_read, is_favorite, id_feed, tags) ' - . 'VALUES(?, ?, ?, ?, ' - . ($this->isCompressed() ? 'COMPRESS(?)' : '?') - . ', ?, ?, ?, ?, ?, ?)'; - return $this->bd->prepare($sql); + public function hasNativeHex() { + return parent::$sharedDbType !== 'sqlite'; + } + + protected function addColumn($name) { + Minz_Log::debug('FreshRSS_EntryDAO::autoAddColumn: ' . $name); + $hasTransaction = false; + try { + $stm = null; + if ($name === 'lastSeen') { //v1.1.1 + if (!$this->bd->inTransaction()) { + $this->bd->beginTransaction(); + $hasTransaction = true; + } + $stm = $this->bd->prepare('ALTER TABLE `' . $this->prefix . 'entry` ADD COLUMN lastSeen INT(11) DEFAULT 0'); + if ($stm && $stm->execute()) { + $stm = $this->bd->prepare('CREATE INDEX entry_lastSeen_index ON `' . $this->prefix . 'entry`(`lastSeen`);'); //"IF NOT EXISTS" does not exist in MySQL 5.7 + if ($stm && $stm->execute()) { + if ($hasTransaction) { + $this->bd->commit(); + } + return true; + } + } + if ($hasTransaction) { + $this->bd->rollBack(); + } + } elseif ($name === 'hash') { //v1.1.1 + $stm = $this->bd->prepare('ALTER TABLE `' . $this->prefix . 'entry` ADD COLUMN hash BINARY(16)'); + return $stm && $stm->execute(); + } + } catch (Exception $e) { + Minz_Log::debug('FreshRSS_EntryDAO::autoAddColumn error: ' . $e->getMessage()); + if ($hasTransaction) { + $this->bd->rollBack(); + } + } + return false; + } + + protected function autoAddColumn($errorInfo) { + if (isset($errorInfo[0])) { + if ($errorInfo[0] == '42S22') { //ER_BAD_FIELD_ERROR + foreach (array('lastSeen', 'hash') as $column) { + if (stripos($errorInfo[2], $column) !== false) { + return $this->addColumn($column); + } + } + } + } + return false; } - public function addEntry($valuesTmp, $preparedStatement = null) { - $stm = $preparedStatement === null ? - FreshRSS_EntryDAO::addEntryPrepare() : - $preparedStatement; + private $addEntryPrepared = null; + + public function addEntry($valuesTmp) { + if ($this->addEntryPrepared === null) { + $sql = 'INSERT INTO `' . $this->prefix . 'entry` (id, guid, title, author, ' + . ($this->isCompressed() ? 'content_bin' : 'content') + . ', link, date, lastSeen, hash, is_read, is_favorite, id_feed, tags) ' + . 'VALUES(?, ?, ?, ?, ' + . ($this->isCompressed() ? 'COMPRESS(?)' : '?') + . ', ?, ?, ?, ' + . ($this->hasNativeHex() ? 'X?' : '?') + . ', ?, ?, ?, ?)'; + $this->addEntryPrepared = $this->bd->prepare($sql); + } $values = array( $valuesTmp['id'], @@ -29,55 +82,76 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { $valuesTmp['content'], substr($valuesTmp['link'], 0, 1023), $valuesTmp['date'], + time(), + $this->hasNativeHex() ? $valuesTmp['hash'] : pack('H*', $valuesTmp['hash']), // X'09AF' hexadecimal literals do not work with SQLite/PDO //hex2bin() is PHP5.4+ $valuesTmp['is_read'] ? 1 : 0, $valuesTmp['is_favorite'] ? 1 : 0, $valuesTmp['id_feed'], substr($valuesTmp['tags'], 0, 1023), ); - if ($stm && $stm->execute($values)) { + if ($this->addEntryPrepared && $this->addEntryPrepared->execute($values)) { return $this->bd->lastInsertId(); } else { - $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); - if ((int)($info[0] / 1000) !== 23) { //Filter out "SQLSTATE Class code 23: Constraint Violation" because of expected duplicate entries + $info = $this->addEntryPrepared == null ? array(0 => '', 1 => '', 2 => 'syntax error') : $this->addEntryPrepared->errorInfo(); + if ($this->autoAddColumn($info)) { + return $this->addEntry($valuesTmp); + } elseif ((int)($info[0] / 1000) !== 23) { //Filter out "SQLSTATE Class code 23: Constraint Violation" because of expected duplicate entries Minz_Log::error('SQL error addEntry: ' . $info[0] . ': ' . $info[1] . ' ' . $info[2] - . ' while adding entry in feed ' . $valuesTmp['id_feed'] . ' with title: ' . $valuesTmp['title']); - } /*else { - Minz_Log::debug('SQL error ' . $info[0] . ': ' . $info[1] . ' ' . $info[2] - . ' while adding entry in feed ' . $valuesTmp['id_feed'] . ' with title: ' . $valuesTmp['title']); - }*/ + . ' while adding entry in feed ' . $valuesTmp['id_feed'] . ' with title: ' . $valuesTmp['title']); + } return false; } } - public function addEntryObject($entry, $conf, $feedHistory) { - $existingGuids = array_fill_keys( - $this->listLastGuidsByFeed($entry->feed(), 20), 1 - ); - - $nb_month_old = max($conf->old_entries, 1); - $date_min = time() - (3600 * 24 * 30 * $nb_month_old); - - $eDate = $entry->date(true); + private $updateEntryPrepared = null; - if ($feedHistory == -2) { - $feedHistory = $conf->keep_history_default; + public function updateEntry($valuesTmp) { + if (!isset($valuesTmp['is_read'])) { + $valuesTmp['is_read'] = null; } - if (!isset($existingGuids[$entry->guid()]) && - ($feedHistory != 0 || $eDate >= $date_min || $entry->isFavorite())) { - $values = $entry->toArray(); - - $useDeclaredDate = empty($existingGuids); - $values['id'] = ($useDeclaredDate || $eDate < $date_min) ? - min(time(), $eDate) . uSecString() : - uTimeString(); + if ($this->updateEntryPrepared === null) { + $sql = 'UPDATE `' . $this->prefix . 'entry` ' + . 'SET title=?, author=?, ' + . ($this->isCompressed() ? 'content_bin=COMPRESS(?)' : 'content=?') + . ', link=?, date=?, lastSeen=?, hash=' + . ($this->hasNativeHex() ? 'X?' : '?') + . ', ' . ($valuesTmp['is_read'] === null ? '' : 'is_read=?, ') + . 'tags=? ' + . 'WHERE id_feed=? AND guid=?'; + $this->updateEntryPrepared = $this->bd->prepare($sql); + } - return $this->addEntry($values); + $values = array( + substr($valuesTmp['title'], 0, 255), + substr($valuesTmp['author'], 0, 255), + $valuesTmp['content'], + substr($valuesTmp['link'], 0, 1023), + $valuesTmp['date'], + time(), + $this->hasNativeHex() ? $valuesTmp['hash'] : pack('H*', $valuesTmp['hash']), + ); + if ($valuesTmp['is_read'] !== null) { + $values[] = $valuesTmp['is_read'] ? 1 : 0; } + $values = array_merge($values, array( + substr($valuesTmp['tags'], 0, 1023), + $valuesTmp['id_feed'], + substr($valuesTmp['guid'], 0, 760), + )); - // We don't return Entry object to avoid a research in DB - return -1; + if ($this->updateEntryPrepared && $this->updateEntryPrepared->execute($values)) { + return $this->bd->lastInsertId(); + } else { + $info = $this->updateEntryPrepared == null ? array(0 => '', 1 => '', 2 => 'syntax error') : $this->updateEntryPrepared->errorInfo(); + if ($this->autoAddColumn($info)) { + return $this->updateEntry($valuesTmp); + } + Minz_Log::error('SQL error updateEntry: ' . $info[0] . ': ' . $info[1] . ' ' . $info[2] + . ' while updating entry with GUID ' . $valuesTmp['guid'] . ' in feed ' . $valuesTmp['id_feed']); + return false; + } } /** @@ -94,6 +168,9 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { if (!is_array($ids)) { $ids = array($ids); } + if (count($ids) < 1) { + return 0; + } $sql = 'UPDATE `' . $this->prefix . 'entry` ' . 'SET is_favorite=? ' . 'WHERE id IN (' . str_repeat('?,', count($ids) - 1). '?)'; @@ -296,11 +373,11 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { * * If $idMax equals 0, a deprecated debug message is logged * - * @param integer $id feed ID + * @param integer $id_feed feed ID * @param integer $idMax fail safe article ID * @return integer affected rows */ - public function markReadFeed($id, $idMax = 0) { + public function markReadFeed($id_feed, $idMax = 0) { if ($idMax == 0) { $idMax = time() . '000000'; Minz_Log::debug('Calling markReadFeed(0) is deprecated!'); @@ -310,7 +387,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { $sql = 'UPDATE `' . $this->prefix . 'entry` ' . 'SET is_read=1 ' . 'WHERE id_feed=? AND is_read=0 AND id <= ?'; - $values = array($id, $idMax); + $values = array($id_feed, $idMax); $stm = $this->bd->prepare($sql); if (!($stm && $stm->execute($values))) { $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); @@ -324,7 +401,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { $sql = 'UPDATE `' . $this->prefix . 'feed` ' . 'SET cache_nbUnreads=cache_nbUnreads-' . $affected . ' WHERE id=?'; - $values = array($id); + $values = array($id_feed); $stm = $this->bd->prepare($sql); if (!($stm && $stm->execute($values))) { $info = $stm == null ? array(2 => 'syntax error') : $stm->errorInfo(); @@ -338,7 +415,7 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { return $affected; } - public function searchByGuid($feed_id, $id) { + public function searchByGuid($id_feed, $guid) { // un guid est unique pour un flux donné $sql = 'SELECT id, guid, title, author, ' . ($this->isCompressed() ? 'UNCOMPRESS(content_bin) AS content' : 'content') @@ -347,8 +424,8 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { $stm = $this->bd->prepare($sql); $values = array( - $feed_id, - $id + $id_feed, + $guid, ); $stm->execute($values); @@ -441,56 +518,50 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { $where .= 'AND e1.id >= ' . $date_min . '000000 '; } $search = ''; - if ($filter !== '') { - require_once(LIB_PATH . '/lib_date.php'); - $filter = trim($filter); - $filter = addcslashes($filter, '\\%_'); - $terms = array_unique(explode(' ', $filter)); - //sort($terms); //Put #tags first //TODO: Put the cheapest filters first - foreach ($terms as $word) { - $word = trim($word); - if (stripos($word, 'intitle:') === 0) { - $word = substr($word, strlen('intitle:')); - $search .= 'AND e1.title LIKE ? '; - $values[] = '%' . $word .'%'; - } elseif (stripos($word, 'inurl:') === 0) { - $word = substr($word, strlen('inurl:')); - $search .= 'AND CONCAT(e1.link, e1.guid) LIKE ? '; - $values[] = '%' . $word .'%'; - } elseif (stripos($word, 'author:') === 0) { - $word = substr($word, strlen('author:')); - $search .= 'AND e1.author LIKE ? '; - $values[] = '%' . $word .'%'; - } elseif (stripos($word, 'date:') === 0) { - $word = substr($word, strlen('date:')); - list($minDate, $maxDate) = parseDateInterval($word); - if ($minDate) { - $search .= 'AND e1.id >= ' . $minDate . '000000 '; - } - if ($maxDate) { - $search .= 'AND e1.id <= ' . $maxDate . '000000 '; - } - } elseif (stripos($word, 'pubdate:') === 0) { - $word = substr($word, strlen('pubdate:')); - list($minDate, $maxDate) = parseDateInterval($word); - if ($minDate) { - $search .= 'AND e1.date >= ' . $minDate . ' '; - } - if ($maxDate) { - $search .= 'AND e1.date <= ' . $maxDate . ' '; - } - } else { - if ($word[0] === '#' && isset($word[1])) { - $search .= 'AND e1.tags LIKE ? '; - $values[] = '%' . $word .'%'; - } else { - $search .= 'AND ' . $this->sqlconcat('e1.title', $this->isCompressed() ? 'UNCOMPRESS(content_bin)' : 'content') . ' LIKE ? '; - $values[] = '%' . $word .'%'; - } + if ($filter) { + if ($filter->getIntitle()) { + $search .= 'AND e1.title LIKE ? '; + $values[] = "%{$filter->getIntitle()}%"; + } + if ($filter->getInurl()) { + $search .= 'AND CONCAT(e1.link, e1.guid) LIKE ? '; + $values[] = "%{$filter->getInurl()}%"; + } + if ($filter->getAuthor()) { + $search .= 'AND e1.author LIKE ? '; + $values[] = "%{$filter->getAuthor()}%"; + } + if ($filter->getMinDate()) { + $search .= 'AND e1.id >= ? '; + $values[] = "{$filter->getMinDate()}000000"; + } + if ($filter->getMaxDate()) { + $search .= 'AND e1.id <= ? '; + $values[] = "{$filter->getMaxDate()}000000"; + } + if ($filter->getMinPubdate()) { + $search .= 'AND e1.date >= ? '; + $values[] = $filter->getMinPubdate(); + } + if ($filter->getMaxPubdate()) { + $search .= 'AND e1.date <= ? '; + $values[] = $filter->getMaxPubdate(); + } + if ($filter->getTags()) { + $tags = $filter->getTags(); + foreach ($tags as $tag) { + $search .= 'AND e1.tags LIKE ? '; + $values[] = "%{$tag}%"; + } + } + if ($filter->getSearch()) { + $search_values = $filter->getSearch(); + foreach ($search_values as $search_value) { + $search .= 'AND ' . $this->sqlconcat('e1.title', $this->isCompressed() ? 'UNCOMPRESS(content_bin)' : 'content') . ' LIKE ? '; + $values[] = "%{$search_value}%"; } } } - return array($values, 'SELECT e1.id FROM `' . $this->prefix . 'entry` e1 ' . ($joinFeed ? 'INNER JOIN `' . $this->prefix . 'feed` f ON e1.id_feed=f.id ' : '') @@ -527,12 +598,51 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { return $stm->fetchAll(PDO::FETCH_COLUMN, 0); } - public function listLastGuidsByFeed($id, $n) { - $sql = 'SELECT guid FROM `' . $this->prefix . 'entry` WHERE id_feed=? ORDER BY id DESC LIMIT ' . intval($n); + public function listHashForFeedGuids($id_feed, $guids) { + if (count($guids) < 1) { + return array(); + } + $sql = 'SELECT guid, hex(hash) AS hexHash FROM `' . $this->prefix . 'entry` WHERE id_feed=? AND guid IN (' . str_repeat('?,', count($guids) - 1). '?)'; $stm = $this->bd->prepare($sql); - $values = array($id); - $stm->execute($values); - return $stm->fetchAll(PDO::FETCH_COLUMN, 0); + $values = array($id_feed); + $values = array_merge($values, $guids); + if ($stm && $stm->execute($values)) { + $result = array(); + $rows = $stm->fetchAll(PDO::FETCH_ASSOC); + foreach ($rows as $row) { + $result[$row['guid']] = $row['hexHash']; + } + return $result; + } else { + $info = $stm == null ? array(0 => '', 1 => '', 2 => 'syntax error') : $stm->errorInfo(); + if ($this->autoAddColumn($info)) { + return $this->listHashForFeedGuids($id_feed, $guids); + } + Minz_Log::error('SQL error listHashForFeedGuids: ' . $info[0] . ': ' . $info[1] . ' ' . $info[2] + . ' while querying feed ' . $id_feed); + return false; + } + } + + 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; + } } public function countUnreadRead() { diff --git a/app/Models/EntryDAOSQLite.php b/app/Models/EntryDAOSQLite.php index ffe0f037c..ff049d813 100644 --- a/app/Models/EntryDAOSQLite.php +++ b/app/Models/EntryDAOSQLite.php @@ -2,6 +2,21 @@ class FreshRSS_EntryDAOSQLite extends FreshRSS_EntryDAO { + protected function autoAddColumn($errorInfo) { + if (empty($errorInfo[0]) || $errorInfo[0] == '42S22') { //ER_BAD_FIELD_ERROR + if ($tableInfo = $this->bd->query("SELECT sql FROM sqlite_master where name='entry'")) { + $showCreate = $tableInfo->fetchColumn(); + Minz_Log::debug('FreshRSS_EntryDAOSQLite::autoAddColumn: ' . $showCreate); + foreach (array('lastSeen', 'hash') as $column) { + if (stripos($showCreate, $column) === false) { + return $this->addColumn($column); + } + } + } + } + return false; + } + protected function sqlConcat($s1, $s2) { return $s1 . '||' . $s2; } diff --git a/app/Models/Feed.php b/app/Models/Feed.php index 5ce03be5d..23491ee8d 100644 --- a/app/Models/Feed.php +++ b/app/Models/Feed.php @@ -19,6 +19,8 @@ class FreshRSS_Feed extends Minz_Model { private $ttl = -2; private $hash = null; private $lockPath = ''; + private $hubUrl = ''; + private $selfUrl = ''; public function __construct($url, $validate=true) { if ($validate) { @@ -49,6 +51,12 @@ class FreshRSS_Feed extends Minz_Model { public function url() { return $this->url; } + public function selfUrl() { + return $this->selfUrl; + } + public function hubUrl() { + return $this->hubUrl; + } public function category() { return $this->category; } @@ -96,6 +104,16 @@ class FreshRSS_Feed extends Minz_Model { public function ttl() { return $this->ttl; } + // public function ttlExpire() { + // $ttl = $this->ttl; + // if ($ttl == -2) { //Default + // $ttl = FreshRSS_Context::$user_conf->ttl_default; + // } + // if ($ttl == -1) { //Never + // $ttl = 64000000; //~2 years. Good enough for PubSubHubbub logic + // } + // return $this->lastUpdate + $ttl; + // } public function nbEntries() { if ($this->nbEntries < 0) { $feedDAO = FreshRSS_Factory::createFeedDao(); @@ -226,6 +244,11 @@ class FreshRSS_Feed extends Minz_Model { throw new FreshRSS_Feed_Exception(($errorMessage == '' ? 'Feed error' : $errorMessage) . ' [' . $url . ']'); } + $links = $feed->get_links('self'); + $this->selfUrl = isset($links[0]) ? $links[0] : null; + $links = $feed->get_links('hub'); + $this->hubUrl = isset($links[0]) ? $links[0] : null; + if ($loadDetails) { // si on a utilisé l'auto-discover, notre url va avoir changé $subscribe_url = $feed->subscribe_url(false); @@ -240,16 +263,16 @@ class FreshRSS_Feed extends Minz_Model { $subscribe_url = $feed->subscribe_url(true); } - $clean_url = url_remove_credentials($subscribe_url); + $clean_url = SimplePie_Misc::url_remove_credentials($subscribe_url); if ($subscribe_url !== null && $subscribe_url !== $url) { $this->_url($clean_url); } - if (($mtime === true) ||($mtime > $this->lastUpdate)) { - Minz_Log::notice('FreshRSS no cache ' . $mtime . ' > ' . $this->lastUpdate . ' for ' . $clean_url); + if (($mtime === true) || ($mtime > $this->lastUpdate)) { + //Minz_Log::debug('FreshRSS no cache ' . $mtime . ' > ' . $this->lastUpdate . ' for ' . $clean_url); $this->loadEntries($feed); // et on charge les articles du flux } else { - Minz_Log::notice('FreshRSS use cache for ' . $clean_url); + //Minz_Log::debug('FreshRSS use cache for ' . $clean_url); $this->entries = array(); } @@ -259,7 +282,7 @@ class FreshRSS_Feed extends Minz_Model { } } - private function loadEntries($feed) { + public function loadEntries($feed) { $entries = array(); foreach ($feed->get_items() as $item) { @@ -333,4 +356,136 @@ class FreshRSS_Feed extends Minz_Model { function unlock() { @unlink($this->lockPath); } + + //<PubSubHubbub> + + function pubSubHubbubEnabled() { + $url = $this->selfUrl ? $this->selfUrl : $this->url; + $hubFilename = PSHB_PATH . '/feeds/' . base64url_encode($url) . '/!hub.json'; + if ($hubFile = @file_get_contents($hubFilename)) { + $hubJson = json_decode($hubFile, true); + if ($hubJson && empty($hubJson['error']) && + (empty($hubJson['lease_end']) || $hubJson['lease_end'] > time())) { + return true; + } + } + return false; + } + + function pubSubHubbubError($error = true) { + $url = $this->selfUrl ? $this->selfUrl : $this->url; + $hubFilename = PSHB_PATH . '/feeds/' . base64url_encode($url) . '/!hub.json'; + $hubFile = @file_get_contents($hubFilename); + $hubJson = $hubFile ? json_decode($hubFile, true) : array(); + if (!isset($hubJson['error']) || $hubJson['error'] !== (bool)$error) { + $hubJson['error'] = (bool)$error; + file_put_contents($hubFilename, json_encode($hubJson)); + file_put_contents(USERS_PATH . '/_/log_pshb.txt', date('c') . "\t" + . 'Set error to ' . ($error ? 1 : 0) . ' for ' . $url . "\n", FILE_APPEND); + } + return false; + } + + function pubSubHubbubPrepare() { + $key = ''; + if (FreshRSS_Context::$system_conf->base_url && $this->hubUrl && $this->selfUrl && @is_dir(PSHB_PATH)) { + $path = PSHB_PATH . '/feeds/' . base64url_encode($this->selfUrl); + $hubFilename = $path . '/!hub.json'; + if ($hubFile = @file_get_contents($hubFilename)) { + $hubJson = json_decode($hubFile, true); + if (!$hubJson || empty($hubJson['key']) || !ctype_xdigit($hubJson['key'])) { + $text = 'Invalid JSON for PubSubHubbub: ' . $this->url; + Minz_Log::warning($text); + file_put_contents(USERS_PATH . '/_/log_pshb.txt', date('c') . "\t" . $text . "\n", FILE_APPEND); + return false; + } + if ((!empty($hubJson['lease_end'])) && ($hubJson['lease_end'] < (time() + (3600 * 23)))) { //TODO: Make a better policy + $text = 'PubSubHubbub lease ends at ' + . date('c', empty($hubJson['lease_end']) ? time() : $hubJson['lease_end']) + . ' and needs renewal: ' . $this->url; + Minz_Log::warning($text); + file_put_contents(USERS_PATH . '/_/log_pshb.txt', date('c') . "\t" . $text . "\n", FILE_APPEND); + $key = $hubJson['key']; //To renew our lease + } elseif (((!empty($hubJson['error'])) || empty($hubJson['lease_end'])) && + (empty($hubJson['lease_start']) || $hubJson['lease_start'] < time() - (3600 * 23))) { //Do not renew too often + $key = $hubJson['key']; //To renew our lease + } + } else { + @mkdir($path, 0777, true); + $key = sha1($path . FreshRSS_Context::$system_conf->salt . uniqid(mt_rand(), true)); + $hubJson = array( + 'hub' => $this->hubUrl, + 'key' => $key, + ); + file_put_contents($hubFilename, json_encode($hubJson)); + @mkdir(PSHB_PATH . '/keys/'); + file_put_contents(PSHB_PATH . '/keys/' . $key . '.txt', base64url_encode($this->selfUrl)); + $text = 'PubSubHubbub prepared for ' . $this->url; + Minz_Log::debug($text); + file_put_contents(USERS_PATH . '/_/log_pshb.txt', date('c') . "\t" . $text . "\n", FILE_APPEND); + } + $currentUser = Minz_Session::param('currentUser'); + if (ctype_alnum($currentUser) && !file_exists($path . '/' . $currentUser . '.txt')) { + touch($path . '/' . $currentUser . '.txt'); + } + } + return $key; + } + + //Parameter true to subscribe, false to unsubscribe. + function pubSubHubbubSubscribe($state) { + if (FreshRSS_Context::$system_conf->base_url && $this->hubUrl && $this->selfUrl) { + $hubFilename = PSHB_PATH . '/feeds/' . base64url_encode($this->selfUrl) . '/!hub.json'; + $hubFile = @file_get_contents($hubFilename); + if ($hubFile === false) { + Minz_Log::warning('JSON not found for PubSubHubbub: ' . $this->url); + return false; + } + $hubJson = json_decode($hubFile, true); + if (!$hubJson || empty($hubJson['key']) || !ctype_xdigit($hubJson['key'])) { + Minz_Log::warning('Invalid JSON for PubSubHubbub: ' . $this->url); + return false; + } + $callbackUrl = checkUrl(FreshRSS_Context::$system_conf->base_url . 'api/pshb.php?k=' . $hubJson['key']); + if ($callbackUrl == '') { + Minz_Log::warning('Invalid callback for PubSubHubbub: ' . $this->url); + return false; + } + $ch = curl_init(); + curl_setopt_array($ch, array( + CURLOPT_URL => $this->hubUrl, + CURLOPT_FOLLOWLOCATION => true, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_USERAGENT => _t('gen.freshrss') . '/' . FRESHRSS_VERSION . ' (' . PHP_OS . '; ' . FRESHRSS_WEBSITE . ')', + CURLOPT_POSTFIELDS => 'hub.verify=sync' + . '&hub.mode=' . ($state ? 'subscribe' : 'unsubscribe') + . '&hub.topic=' . urlencode($this->selfUrl) + . '&hub.callback=' . urlencode($callbackUrl) + ) + ); + $response = curl_exec($ch); + $info = curl_getinfo($ch); + + file_put_contents(USERS_PATH . '/_/log_pshb.txt', date('c') . "\t" . + 'PubSubHubbub ' . ($state ? 'subscribe' : 'unsubscribe') . ' to ' . $this->selfUrl . + ' with callback ' . $callbackUrl . ': ' . $info['http_code'] . ' ' . $response . "\n", FILE_APPEND); + + if (!$state) { //unsubscribe + $hubJson['lease_end'] = time() - 60; + file_put_contents($hubFilename, json_encode($hubJson)); + } + + if (substr($info['http_code'], 0, 1) == '2') { + return true; + } else { + $hubJson['lease_start'] = time(); //Prevent trying again too soon + $hubJson['error'] = true; + file_put_contents($hubFilename, json_encode($hubJson)); + return false; + } + } + return false; + } + + //</PubSubHubbub> } diff --git a/app/Models/FeedDAO.php b/app/Models/FeedDAO.php index 74597c730..475d39286 100644 --- a/app/Models/FeedDAO.php +++ b/app/Models/FeedDAO.php @@ -1,6 +1,6 @@ <?php -class FreshRSS_FeedDAO extends Minz_ModelPdo { +class FreshRSS_FeedDAO extends Minz_ModelPdo implements FreshRSS_Searchable { public function addFeed($valuesTmp) { $sql = 'INSERT INTO `' . $this->prefix . 'feed` (url, category, name, website, description, lastUpdate, priority, httpAuth, error, keep_history, ttl) VALUES(?, ?, ?, ?, ?, ?, 10, ?, 0, -2, -2)'; $stm = $this->bd->prepare($sql); @@ -322,17 +322,20 @@ class FreshRSS_FeedDAO extends Minz_ModelPdo { return $affected; } - public function cleanOldEntries($id, $date_min, $keep = 15) { //Remember to call updateLastUpdate($id) just after + 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=0 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 yet support 'LIMIT & IN/ALL/ANY/SOME subquery' + . 'WHERE id_feed=:id_feed AND id<=:id_max ' + . 'AND is_favorite=0 ' //Do not remove favourites + . 'AND lastSeen < (SELECT maxLastSeen FROM (SELECT (MAX(e3.lastSeen)-99) AS maxLastSeen FROM `' . $this->prefix . 'entry` e3 WHERE e3.id_feed=:id_feed) recent) ' //Do not remove the most newly seen articles, plus a few seconds of tolerance + . '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); - $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) { + $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(); diff --git a/app/Models/LogDAO.php b/app/Models/LogDAO.php index 4c56e3150..ab258cd58 100644 --- a/app/Models/LogDAO.php +++ b/app/Models/LogDAO.php @@ -21,5 +21,10 @@ class FreshRSS_LogDAO { public static function truncate() { file_put_contents(join_path(DATA_PATH, 'users', Minz_Session::param('currentUser', '_'), 'log.txt'), ''); + if (FreshRSS_Auth::hasAccess('admin')) { + file_put_contents(join_path(DATA_PATH, 'users', '_', 'log.txt'), ''); + file_put_contents(join_path(DATA_PATH, 'users', '_', 'log_api.txt'), ''); + file_put_contents(join_path(DATA_PATH, 'users', '_', 'log_pshb.txt'), ''); + } } } diff --git a/app/Models/Search.php b/app/Models/Search.php new file mode 100644 index 000000000..575a9a2cb --- /dev/null +++ b/app/Models/Search.php @@ -0,0 +1,229 @@ +<?php + +require_once(LIB_PATH . '/lib_date.php'); + +/** + * Contains a search from the search form. + * + * It allows to extract meaningful bits of the search and store them in a + * convenient object + */ +class FreshRSS_Search { + + // This contains the user input string + private $raw_input = ''; + // The following properties are extracted from the raw input + private $intitle; + private $min_date; + private $max_date; + private $min_pubdate; + private $max_pubdate; + private $inurl; + private $author; + private $tags; + private $search; + + public function __construct($input) { + if (strcmp($input, '') == 0) { + return; + } + $this->raw_input = $input; + $input = $this->parseIntitleSearch($input); + $input = $this->parseAuthorSearch($input); + $input = $this->parseInurlSearch($input); + $input = $this->parsePubdateSearch($input); + $input = $this->parseDateSearch($input); + $input = $this->parseTagsSeach($input); + $this->parseSearch($input); + } + + public function __toString() { + return $this->getRawInput(); + } + + public function getRawInput() { + return $this->raw_input; + } + + public function getIntitle() { + return $this->intitle; + } + + public function getMinDate() { + return $this->min_date; + } + + public function getMaxDate() { + return $this->max_date; + } + + public function getMinPubdate() { + return $this->min_pubdate; + } + + public function getMaxPubdate() { + return $this->max_pubdate; + } + + public function getInurl() { + return $this->inurl; + } + + public function getAuthor() { + return $this->author; + } + + public function getTags() { + return $this->tags; + } + + public function getSearch() { + return $this->search; + } + + /** + * Parse the search string to find intitle keyword and the search related + * to it. + * The search is the first word following the keyword. + * + * @param string $input + * @return string + */ + private function parseIntitleSearch($input) { + if (preg_match('/intitle:(?P<delim>[\'"])(?P<search>.*)(?P=delim)/U', $input, $matches)) { + $this->intitle = $matches['search']; + return str_replace($matches[0], '', $input); + } + if (preg_match('/intitle:(?P<search>\w*)/', $input, $matches)) { + $this->intitle = $matches['search']; + return str_replace($matches[0], '', $input); + } + return $input; + } + + /** + * Parse the search string to find author keyword and the search related + * to it. + * The search is the first word following the keyword except when using + * a delimiter. Supported delimiters are single quote (') and double + * quotes ("). + * + * @param string $input + * @return string + */ + private function parseAuthorSearch($input) { + if (preg_match('/author:(?P<delim>[\'"])(?P<search>.*)(?P=delim)/U', $input, $matches)) { + $this->author = $matches['search']; + return str_replace($matches[0], '', $input); + } + if (preg_match('/author:(?P<search>\w*)/', $input, $matches)) { + $this->author = $matches['search']; + return str_replace($matches[0], '', $input); + } + return $input; + } + + /** + * Parse the search string to find inurl keyword and the search related + * to it. + * The search is the first word following the keyword except. + * + * @param string $input + * @return string + */ + private function parseInurlSearch($input) { + if (preg_match('/inurl:(?P<search>[^\s]*)/', $input, $matches)) { + $this->inurl = $matches['search']; + return str_replace($matches[0], '', $input); + } + return $input; + } + + /** + * Parse the search string to find date keyword and the search related + * to it. + * The search is the first word following the keyword. + * + * @param string $input + * @return string + */ + private function parseDateSearch($input) { + if (preg_match('/date:(?P<search>[^\s]*)/', $input, $matches)) { + list($this->min_date, $this->max_date) = parseDateInterval($matches['search']); + return str_replace($matches[0], '', $input); + } + return $input; + } + + /** + * Parse the search string to find pubdate keyword and the search related + * to it. + * The search is the first word following the keyword. + * + * @param string $input + * @return string + */ + private function parsePubdateSearch($input) { + if (preg_match('/pubdate:(?P<search>[^\s]*)/', $input, $matches)) { + list($this->min_pubdate, $this->max_pubdate) = parseDateInterval($matches['search']); + return str_replace($matches[0], '', $input); + } + return $input; + } + + /** + * Parse the search string to find tags keyword (# followed by a word) + * and the search related to it. + * The search is the first word following the #. + * + * @param string $input + * @return string + */ + private function parseTagsSeach($input) { + if (preg_match_all('/#(?P<search>[^\s]+)/', $input, $matches)) { + $this->tags = $matches['search']; + return str_replace($matches[0], '', $input); + } + return $input; + } + + /** + * Parse the search string to find search values. + * Every word is a distinct search value, except when using a delimiter. + * Supported delimiters are single quote (') and double quotes ("). + * + * @param string $input + * @return string + */ + private function parseSearch($input) { + $input = $this->cleanSearch($input); + if (strcmp($input, '') == 0) { + return; + } + if (preg_match_all('/(?P<delim>[\'"])(?P<search>.*)(?P=delim)/U', $input, $matches)) { + $this->search = $matches['search']; + $input = str_replace($matches[0], '', $input); + } + $input = $this->cleanSearch($input); + if (strcmp($input, '') == 0) { + return; + } + if (is_array($this->search)) { + $this->search = array_merge($this->search, explode(' ', $input)); + } else { + $this->search = explode(' ', $input); + } + } + + /** + * Remove all unnecessary spaces in the search + * + * @param string $input + * @return string + */ + private function cleanSearch($input) { + $input = preg_replace('/\s+/', ' ', $input); + return trim($input); + } + +} diff --git a/app/Models/Searchable.php b/app/Models/Searchable.php new file mode 100644 index 000000000..d5bcea49d --- /dev/null +++ b/app/Models/Searchable.php @@ -0,0 +1,6 @@ +<?php + +interface FreshRSS_Searchable { + + public function searchById($id); +} diff --git a/app/Models/Share.php b/app/Models/Share.php index db6feda19..2a05f2ee9 100644 --- a/app/Models/Share.php +++ b/app/Models/Share.php @@ -152,7 +152,7 @@ class FreshRSS_Share { * Return the current name of the share option. */ public function name($real = false) { - if ($real || is_null($this->custom_name)) { + if ($real || is_null($this->custom_name) || empty($this->custom_name)) { return $this->name; } else { return $this->custom_name; diff --git a/app/Models/UserQuery.php b/app/Models/UserQuery.php new file mode 100644 index 000000000..52747f538 --- /dev/null +++ b/app/Models/UserQuery.php @@ -0,0 +1,226 @@ +<?php + +/** + * Contains the description of a user query + * + * It allows to extract the meaningful bits of the query to be manipulated in an + * easy way. + */ +class FreshRSS_UserQuery { + + private $deprecated = false; + private $get; + private $get_name; + private $get_type; + private $name; + private $order; + private $search; + private $state; + private $url; + private $feed_dao; + private $category_dao; + + /** + * @param array $query + * @param FreshRSS_Searchable $feed_dao + * @param FreshRSS_Searchable $category_dao + */ + public function __construct($query, FreshRSS_Searchable $feed_dao = null, FreshRSS_Searchable $category_dao = null) { + $this->category_dao = $category_dao; + $this->feed_dao = $feed_dao; + if (isset($query['get'])) { + $this->parseGet($query['get']); + } + if (isset($query['name'])) { + $this->name = $query['name']; + } + if (isset($query['order'])) { + $this->order = $query['order']; + } + if (!isset($query['search'])) { + $query['search'] = ''; + } + // linked to deeply with the search object, need to use dependency injection + $this->search = new FreshRSS_Search($query['search']); + if (isset($query['state'])) { + $this->state = $query['state']; + } + if (isset($query['url'])) { + $this->url = $query['url']; + } + } + + /** + * Convert the current object to an array. + * + * @return array + */ + public function toArray() { + return array_filter(array( + 'get' => $this->get, + 'name' => $this->name, + 'order' => $this->order, + 'search' => $this->search->__toString(), + 'state' => $this->state, + 'url' => $this->url, + )); + } + + /** + * Parse the get parameter in the query string to extract its name and + * type + * + * @param string $get + */ + private function parseGet($get) { + $this->get = $get; + if (preg_match('/(?P<type>[acfs])(_(?P<id>\d+))?/', $get, $matches)) { + switch ($matches['type']) { + case 'a': + $this->parseAll(); + break; + case 'c': + $this->parseCategory($matches['id']); + break; + case 'f': + $this->parseFeed($matches['id']); + break; + case 's': + $this->parseFavorite(); + break; + } + } + } + + /** + * Parse the query string when it is an "all" query + */ + private function parseAll() { + $this->get_name = 'all'; + $this->get_type = 'all'; + } + + /** + * Parse the query string when it is a "category" query + * + * @param integer $id + * @throws FreshRSS_DAO_Exception + */ + private function parseCategory($id) { + if (is_null($this->category_dao)) { + throw new FreshRSS_DAO_Exception('Category DAO is not loaded in UserQuery'); + } + $category = $this->category_dao->searchById($id); + if ($category) { + $this->get_name = $category->name(); + } else { + $this->deprecated = true; + } + $this->get_type = 'category'; + } + + /** + * Parse the query string when it is a "feed" query + * + * @param integer $id + * @throws FreshRSS_DAO_Exception + */ + private function parseFeed($id) { + if (is_null($this->feed_dao)) { + throw new FreshRSS_DAO_Exception('Feed DAO is not loaded in UserQuery'); + } + $feed = $this->feed_dao->searchById($id); + if ($feed) { + $this->get_name = $feed->name(); + } else { + $this->deprecated = true; + } + $this->get_type = 'feed'; + } + + /** + * Parse the query string when it is a "favorite" query + */ + private function parseFavorite() { + $this->get_name = 'favorite'; + $this->get_type = 'favorite'; + } + + /** + * Check if the current user query is deprecated. + * It is deprecated if the category or the feed used in the query are + * not existing. + * + * @return boolean + */ + public function isDeprecated() { + return $this->deprecated; + } + + /** + * Check if the user query has parameters. + * If the type is 'all', it is considered equal to no parameters + * + * @return boolean + */ + public function hasParameters() { + if ($this->get_type === 'all') { + return false; + } + if ($this->hasSearch()) { + return true; + } + if ($this->state) { + return true; + } + if ($this->order) { + return true; + } + if ($this->get) { + return true; + } + return false; + } + + /** + * Check if there is a search in the search object + * + * @return boolean + */ + public function hasSearch() { + return $this->search->getRawInput() != ""; + } + + public function getGet() { + return $this->get; + } + + public function getGetName() { + return $this->get_name; + } + + public function getGetType() { + return $this->get_type; + } + + public function getName() { + return $this->name; + } + + public function getOrder() { + return $this->order; + } + + public function getSearch() { + return $this->search; + } + + public function getState() { + return $this->state; + } + + public function getUrl() { + return $this->url; + } + +} |
