diff options
| author | 2022-06-02 08:41:08 +0200 | |
|---|---|---|
| committer | 2022-06-02 08:41:08 +0200 | |
| commit | f85c510ed49be031145f6b35e815ce890cd4f9aa (patch) | |
| tree | c7ac947ba5ddacf85dc5d97330f38f7d91b0964c /app/Models/EntryDAO.php | |
| parent | f988b996ab69104bc45b222fa88d34b5c78f98b3 (diff) | |
New search engine (#4378)
* New possibility to invoke user queries from a search expression
From the search field: `S:"My query"`.
Can be combined with other filters such as `S:"My query" date:P3d` as long as the user queries do not contain `OR`.
A use-case is to have an RSS filter with a stable address or an external API call with the ability to update the user query.
* Draft of parenthesis logic
* More draft
* Working parenthesis (a OR b) (c OR d)
* Working (A) OR (B)
* Support nested parentheses + unit tests + documentation
* search:MySearch and S:3
Diffstat (limited to 'app/Models/EntryDAO.php')
| -rw-r--r-- | app/Models/EntryDAO.php | 510 |
1 files changed, 267 insertions, 243 deletions
diff --git a/app/Models/EntryDAO.php b/app/Models/EntryDAO.php index 8f248e20f..426c294c4 100644 --- a/app/Models/EntryDAO.php +++ b/app/Models/EntryDAO.php @@ -2,23 +2,27 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo implements FreshRSS_Searchable { - public function isCompressed(): bool { + public static function isCompressed(): bool { return true; } - public function hasNativeHex(): bool { + public static function hasNativeHex(): bool { return true; } - public function sqlHexDecode(string $x): string { + protected static function sqlConcat($s1, $s2) { + return 'CONCAT(' . $s1 . ',' . $s2 . ')'; //MySQL + } + + public static function sqlHexDecode(string $x): string { return 'unhex(' . $x . ')'; } - public function sqlHexEncode(string $x): string { + public static function sqlHexEncode(string $x): string { return 'hex(' . $x . ')'; } - public function sqlIgnoreConflict(string $sql): string { + public static function sqlIgnoreConflict(string $sql): string { return str_replace('INSERT INTO ', 'INSERT IGNORE INTO ', $sql); } @@ -90,14 +94,14 @@ SQL; public function addEntry(array $valuesTmp, bool $useTmpTable = true) { if ($this->addEntryPrepared == null) { - $sql = $this->sqlIgnoreConflict( + $sql = static::sqlIgnoreConflict( 'INSERT INTO `_' . ($useTmpTable ? 'entrytmp' : 'entry') . '` (id, guid, title, author, ' - . ($this->isCompressed() ? 'content_bin' : 'content') + . (static::isCompressed() ? 'content_bin' : 'content') . ', link, date, `lastSeen`, hash, is_read, is_favorite, id_feed, tags) ' . 'VALUES(:id, :guid, :title, :author, ' - . ($this->isCompressed() ? 'COMPRESS(:content)' : ':content') + . (static::isCompressed() ? 'COMPRESS(:content)' : ':content') . ', :link, :date, :last_seen, ' - . $this->sqlHexDecode(':hash') + . static::sqlHexDecode(':hash') . ', :is_read, :is_favorite, :id_feed, :tags)'); $this->addEntryPrepared = $this->pdo->prepare($sql); } @@ -132,7 +136,7 @@ SQL; $valuesTmp['tags'] = safe_utf8($valuesTmp['tags']); $this->addEntryPrepared->bindParam(':tags', $valuesTmp['tags']); - if ($this->hasNativeHex()) { + if (static::hasNativeHex()) { $this->addEntryPrepared->bindParam(':hash', $valuesTmp['hash']); } else { $valuesTmp['hashBin'] = hex2bin($valuesTmp['hash']); @@ -189,9 +193,9 @@ SQL; if ($this->updateEntryPrepared === null) { $sql = 'UPDATE `_entry` ' . 'SET title=:title, author=:author, ' - . ($this->isCompressed() ? 'content_bin=COMPRESS(:content)' : 'content=:content') + . (static::isCompressed() ? 'content_bin=COMPRESS(:content)' : 'content=:content') . ', link=:link, date=:date, `lastSeen`=:last_seen' - . ', hash=' . $this->sqlHexDecode(':hash') + . ', hash=' . static::sqlHexDecode(':hash') . ', is_read=COALESCE(:is_read, is_read)' . ', tags=:tags ' . 'WHERE id_feed=:id_feed AND guid=:guid'; @@ -226,7 +230,7 @@ SQL; $valuesTmp['tags'] = safe_utf8($valuesTmp['tags']); $this->updateEntryPrepared->bindParam(':tags', $valuesTmp['tags']); - if ($this->hasNativeHex()) { + if (static::hasNativeHex()) { $this->updateEntryPrepared->bindParam(':hash', $valuesTmp['hash']); } else { $valuesTmp['hashBin'] = hex2bin($valuesTmp['hash']); @@ -649,8 +653,8 @@ SQL; public function selectAll() { $sql = 'SELECT id, guid, title, author, ' - . ($this->isCompressed() ? 'UNCOMPRESS(content_bin) AS content' : 'content') - . ', link, date, `lastSeen`, ' . $this->sqlHexEncode('hash') . ' AS hash, is_read, is_favorite, id_feed, tags ' + . (static::isCompressed() ? 'UNCOMPRESS(content_bin) AS content' : 'content') + . ', link, date, `lastSeen`, ' . static::sqlHexEncode('hash') . ' AS hash, is_read, is_favorite, id_feed, tags ' . 'FROM `_entry`'; $stm = $this->pdo->query($sql); while ($row = $stm->fetch(PDO::FETCH_ASSOC)) { @@ -662,7 +666,7 @@ SQL; 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') + . (static::isCompressed() ? 'UNCOMPRESS(content_bin) AS content' : 'content') . ', link, date, is_read, is_favorite, id_feed, tags ' . 'FROM `_entry` WHERE id_feed=:id_feed AND guid=:guid'; $stm = $this->pdo->prepare($sql); @@ -676,7 +680,7 @@ SQL; /** @return FreshRSS_Entry|null */ public function searchById($id) { $sql = 'SELECT id, guid, title, author, ' - . ($this->isCompressed() ? 'UNCOMPRESS(content_bin) AS content' : 'content') + . (static::isCompressed() ? 'UNCOMPRESS(content_bin) AS content' : 'content') . ', link, date, is_read, is_favorite, id_feed, tags ' . 'FROM `_entry` WHERE id=:id'; $stm = $this->pdo->prepare($sql); @@ -696,281 +700,301 @@ SQL; return isset($res[0]) ? $res[0] : null; } - protected function sqlConcat($s1, $s2) { - return 'CONCAT(' . $s1 . ',' . $s2 . ')'; //MySQL - } + /** @param FreshRSS_BooleanSearch $filters */ + public static function sqlBooleanSearch(string $alias, $filters, int $level = 0) { + $search = ''; + $values = []; - /** - * @param FreshRSS_BooleanSearch|null $filters - */ - protected function sqlListEntriesWhere(string $alias = '', $filters = null, int $state = FreshRSS_Entry::STATE_ALL, - string $order = 'DESC', string $firstId = '', int $date_min = 0) { - $search = ' '; - $values = array(); - if ($state & FreshRSS_Entry::STATE_NOT_READ) { - if (!($state & FreshRSS_Entry::STATE_READ)) { - $search .= 'AND ' . $alias . 'is_read=0 '; + $isOpen = false; + foreach ($filters->searches() as $filter) { + if ($filter == null) { + continue; } - } elseif ($state & FreshRSS_Entry::STATE_READ) { - $search .= 'AND ' . $alias . 'is_read=1 '; - } - if ($state & FreshRSS_Entry::STATE_FAVORITE) { - if (!($state & FreshRSS_Entry::STATE_NOT_FAVORITE)) { - $search .= 'AND ' . $alias . 'is_favorite=1 '; - } - } elseif ($state & FreshRSS_Entry::STATE_NOT_FAVORITE) { - $search .= 'AND ' . $alias . 'is_favorite=0 '; - } - - switch ($order) { - case 'DESC': - case 'ASC': - break; - default: - throw new FreshRSS_EntriesGetter_Exception('Bad order in Entry->listByType: [' . $order . ']!'); - } - if ($firstId !== '') { - $search .= 'AND ' . $alias . 'id ' . ($order === 'DESC' ? '<=' : '>=') . ' ? '; - $values[] = $firstId; - } - if ($date_min > 0) { - $search .= 'AND ' . $alias . 'id >= ? '; - $values[] = $date_min . '000000'; - } - if ($filters && count($filters->searches()) > 0) { - $isOpen = false; - foreach ($filters->searches() as $filter) { - if ($filter == null) { - continue; + if ($filter instanceof FreshRSS_BooleanSearch) { + // BooleanSearches are combined by AND (default) or OR (special case) operator and are recursive + list($filterValues, $filterSearch) = self::sqlBooleanSearch($alias, $filter, $level + 1); + $filterSearch = trim($filterSearch); + + if ($filterSearch !== '') { + if ($search !== '') { + $search .= $filter->operator(); + } + $search .= ' (' . $filterSearch . ') '; + $values = array_merge($values, $filterValues); } - $sub_search = ''; - - if ($filter->getEntryIds()) { - foreach ($filter->getEntryIds() as $entry_ids) { - $sub_search .= 'AND ' . $alias . 'id IN ('; - foreach ($entry_ids as $entry_id) { - $sub_search .= '?,'; - $values[] = $entry_id; - } - $sub_search = rtrim($sub_search, ','); - $sub_search .= ') '; + continue; + } + // Searches are combined by OR and are not recursive + $sub_search = ''; + if ($filter->getEntryIds()) { + foreach ($filter->getEntryIds() as $entry_ids) { + $sub_search .= 'AND ' . $alias . 'id IN ('; + foreach ($entry_ids as $entry_id) { + $sub_search .= '?,'; + $values[] = $entry_id; } + $sub_search = rtrim($sub_search, ','); + $sub_search .= ') '; } - if ($filter->getNotEntryIds()) { - foreach ($filter->getNotEntryIds() as $entry_ids) { - $sub_search .= 'AND ' . $alias . 'id NOT IN ('; - foreach ($entry_ids as $entry_id) { - $sub_search .= '?,'; - $values[] = $entry_id; - } - $sub_search = rtrim($sub_search, ','); - $sub_search .= ') '; + } + if ($filter->getNotEntryIds()) { + foreach ($filter->getNotEntryIds() as $entry_ids) { + $sub_search .= 'AND ' . $alias . 'id NOT IN ('; + foreach ($entry_ids as $entry_id) { + $sub_search .= '?,'; + $values[] = $entry_id; } + $sub_search = rtrim($sub_search, ','); + $sub_search .= ') '; } + } - if ($filter->getMinDate()) { - $sub_search .= 'AND ' . $alias . 'id >= ? '; - $values[] = "{$filter->getMinDate()}000000"; - } - if ($filter->getMaxDate()) { - $sub_search .= 'AND ' . $alias . 'id <= ? '; - $values[] = "{$filter->getMaxDate()}000000"; - } - if ($filter->getMinPubdate()) { - $sub_search .= 'AND ' . $alias . 'date >= ? '; - $values[] = $filter->getMinPubdate(); - } - if ($filter->getMaxPubdate()) { - $sub_search .= 'AND ' . $alias . 'date <= ? '; - $values[] = $filter->getMaxPubdate(); - } + if ($filter->getMinDate()) { + $sub_search .= 'AND ' . $alias . 'id >= ? '; + $values[] = "{$filter->getMinDate()}000000"; + } + if ($filter->getMaxDate()) { + $sub_search .= 'AND ' . $alias . 'id <= ? '; + $values[] = "{$filter->getMaxDate()}000000"; + } + if ($filter->getMinPubdate()) { + $sub_search .= 'AND ' . $alias . 'date >= ? '; + $values[] = $filter->getMinPubdate(); + } + if ($filter->getMaxPubdate()) { + $sub_search .= 'AND ' . $alias . 'date <= ? '; + $values[] = $filter->getMaxPubdate(); + } - //Negation of date intervals must be combined by OR - if ($filter->getNotMinDate() || $filter->getNotMaxDate()) { - $sub_search .= 'AND ('; - if ($filter->getNotMinDate()) { - $sub_search .= $alias . 'id < ?'; - $values[] = "{$filter->getNotMinDate()}000000"; - if ($filter->getNotMaxDate()) { - $sub_search .= ' OR '; - } - } + //Negation of date intervals must be combined by OR + if ($filter->getNotMinDate() || $filter->getNotMaxDate()) { + $sub_search .= 'AND ('; + if ($filter->getNotMinDate()) { + $sub_search .= $alias . 'id < ?'; + $values[] = "{$filter->getNotMinDate()}000000"; if ($filter->getNotMaxDate()) { - $sub_search .= $alias . 'id > ?'; - $values[] = "{$filter->getNotMaxDate()}000000"; + $sub_search .= ' OR '; } - $sub_search .= ') '; } - if ($filter->getNotMinPubdate() || $filter->getNotMaxPubdate()) { - $sub_search .= 'AND ('; - if ($filter->getNotMinPubdate()) { - $sub_search .= $alias . 'date < ?'; - $values[] = $filter->getNotMinPubdate(); - if ($filter->getNotMaxPubdate()) { - $sub_search .= ' OR '; - } - } - if ($filter->getNotMaxPubdate()) { - $sub_search .= $alias . 'date > ?'; - $values[] = $filter->getNotMaxPubdate(); - } - $sub_search .= ') '; + if ($filter->getNotMaxDate()) { + $sub_search .= $alias . 'id > ?'; + $values[] = "{$filter->getNotMaxDate()}000000"; } - - if ($filter->getFeedIds()) { - foreach ($filter->getFeedIds() as $feed_ids) { - $sub_search .= 'AND ' . $alias . 'id_feed IN ('; - foreach ($feed_ids as $feed_id) { - $sub_search .= '?,'; - $values[] = $feed_id; - } - $sub_search = rtrim($sub_search, ','); - $sub_search .= ') '; + $sub_search .= ') '; + } + if ($filter->getNotMinPubdate() || $filter->getNotMaxPubdate()) { + $sub_search .= 'AND ('; + if ($filter->getNotMinPubdate()) { + $sub_search .= $alias . 'date < ?'; + $values[] = $filter->getNotMinPubdate(); + if ($filter->getNotMaxPubdate()) { + $sub_search .= ' OR '; } } - if ($filter->getNotFeedIds()) { - foreach ($filter->getNotFeedIds() as $feed_ids) { - $sub_search .= 'AND ' . $alias . 'id_feed NOT IN ('; - foreach ($feed_ids as $feed_id) { - $sub_search .= '?,'; - $values[] = $feed_id; - } - $sub_search = rtrim($sub_search, ','); - $sub_search .= ') '; - } + if ($filter->getNotMaxPubdate()) { + $sub_search .= $alias . 'date > ?'; + $values[] = $filter->getNotMaxPubdate(); } + $sub_search .= ') '; + } - if ($filter->getLabelIds()) { - foreach ($filter->getLabelIds() as $label_ids) { - if ($label_ids === '*') { - $sub_search .= 'AND EXISTS (SELECT et.id_tag FROM `_entrytag` et WHERE et.id_entry = ' . $alias . 'id) '; - } else { - $sub_search .= 'AND ' . $alias . 'id IN (SELECT et.id_entry FROM `_entrytag` et WHERE et.id_tag IN ('; - foreach ($label_ids as $label_id) { - $sub_search .= '?,'; - $values[] = $label_id; - } - $sub_search = rtrim($sub_search, ','); - $sub_search .= ')) '; - } + if ($filter->getFeedIds()) { + foreach ($filter->getFeedIds() as $feed_ids) { + $sub_search .= 'AND ' . $alias . 'id_feed IN ('; + foreach ($feed_ids as $feed_id) { + $sub_search .= '?,'; + $values[] = $feed_id; } + $sub_search = rtrim($sub_search, ','); + $sub_search .= ') '; } - if ($filter->getNotLabelIds()) { - foreach ($filter->getNotLabelIds() as $label_ids) { - if ($label_ids === '*') { - $sub_search .= 'AND NOT EXISTS (SELECT et.id_tag FROM `_entrytag` et WHERE et.id_entry = ' . $alias . 'id) '; - } else { - $sub_search .= 'AND ' . $alias . 'id NOT IN (SELECT et.id_entry FROM `_entrytag` et WHERE et.id_tag IN ('; - foreach ($label_ids as $label_id) { - $sub_search .= '?,'; - $values[] = $label_id; - } - $sub_search = rtrim($sub_search, ','); - $sub_search .= ')) '; - } + } + if ($filter->getNotFeedIds()) { + foreach ($filter->getNotFeedIds() as $feed_ids) { + $sub_search .= 'AND ' . $alias . 'id_feed NOT IN ('; + foreach ($feed_ids as $feed_id) { + $sub_search .= '?,'; + $values[] = $feed_id; } + $sub_search = rtrim($sub_search, ','); + $sub_search .= ') '; } + } - if ($filter->getLabelNames()) { - foreach ($filter->getLabelNames() as $label_names) { - $sub_search .= 'AND ' . $alias . 'id IN (SELECT et.id_entry FROM `_entrytag` et, `_tag` t WHERE et.id_tag = t.id AND t.name IN ('; - foreach ($label_names as $label_name) { + if ($filter->getLabelIds()) { + foreach ($filter->getLabelIds() as $label_ids) { + if ($label_ids === '*') { + $sub_search .= 'AND EXISTS (SELECT et.id_tag FROM `_entrytag` et WHERE et.id_entry = ' . $alias . 'id) '; + } else { + $sub_search .= 'AND ' . $alias . 'id IN (SELECT et.id_entry FROM `_entrytag` et WHERE et.id_tag IN ('; + foreach ($label_ids as $label_id) { $sub_search .= '?,'; - $values[] = $label_name; + $values[] = $label_id; } $sub_search = rtrim($sub_search, ','); $sub_search .= ')) '; } } - if ($filter->getNotLabelNames()) { - foreach ($filter->getNotLabelNames() as $label_names) { - $sub_search .= 'AND ' . $alias . 'id NOT IN (SELECT et.id_entry FROM `_entrytag` et, `_tag` t WHERE et.id_tag = t.id AND t.name IN ('; - foreach ($label_names as $label_name) { + } + if ($filter->getNotLabelIds()) { + foreach ($filter->getNotLabelIds() as $label_ids) { + if ($label_ids === '*') { + $sub_search .= 'AND NOT EXISTS (SELECT et.id_tag FROM `_entrytag` et WHERE et.id_entry = ' . $alias . 'id) '; + } else { + $sub_search .= 'AND ' . $alias . 'id NOT IN (SELECT et.id_entry FROM `_entrytag` et WHERE et.id_tag IN ('; + foreach ($label_ids as $label_id) { $sub_search .= '?,'; - $values[] = $label_name; + $values[] = $label_id; } $sub_search = rtrim($sub_search, ','); $sub_search .= ')) '; } } + } - if ($filter->getAuthor()) { - foreach ($filter->getAuthor() as $author) { - $sub_search .= 'AND ' . $alias . 'author LIKE ? '; - $values[] = "%{$author}%"; + if ($filter->getLabelNames()) { + foreach ($filter->getLabelNames() as $label_names) { + $sub_search .= 'AND ' . $alias . 'id IN (SELECT et.id_entry FROM `_entrytag` et, `_tag` t WHERE et.id_tag = t.id AND t.name IN ('; + foreach ($label_names as $label_name) { + $sub_search .= '?,'; + $values[] = $label_name; } + $sub_search = rtrim($sub_search, ','); + $sub_search .= ')) '; } - if ($filter->getIntitle()) { - foreach ($filter->getIntitle() as $title) { - $sub_search .= 'AND ' . $alias . 'title LIKE ? '; - $values[] = "%{$title}%"; + } + if ($filter->getNotLabelNames()) { + foreach ($filter->getNotLabelNames() as $label_names) { + $sub_search .= 'AND ' . $alias . 'id NOT IN (SELECT et.id_entry FROM `_entrytag` et, `_tag` t WHERE et.id_tag = t.id AND t.name IN ('; + foreach ($label_names as $label_name) { + $sub_search .= '?,'; + $values[] = $label_name; } + $sub_search = rtrim($sub_search, ','); + $sub_search .= ')) '; } - if ($filter->getTags()) { - foreach ($filter->getTags() as $tag) { - $sub_search .= 'AND ' . $alias . 'tags LIKE ? '; - $values[] = "%{$tag}%"; - } + } + + if ($filter->getAuthor()) { + foreach ($filter->getAuthor() as $author) { + $sub_search .= 'AND ' . $alias . 'author LIKE ? '; + $values[] = "%{$author}%"; } - if ($filter->getInurl()) { - foreach ($filter->getInurl() as $url) { - $sub_search .= 'AND ' . $this->sqlConcat($alias . 'link', $alias . 'guid') . ' LIKE ? '; - $values[] = "%{$url}%"; - } + } + if ($filter->getIntitle()) { + foreach ($filter->getIntitle() as $title) { + $sub_search .= 'AND ' . $alias . 'title LIKE ? '; + $values[] = "%{$title}%"; } + } + if ($filter->getTags()) { + foreach ($filter->getTags() as $tag) { + $sub_search .= 'AND ' . $alias . 'tags LIKE ? '; + $values[] = "%{$tag}%"; + } + } + if ($filter->getInurl()) { + foreach ($filter->getInurl() as $url) { + $sub_search .= 'AND ' . static::sqlConcat($alias . 'link', $alias . 'guid') . ' LIKE ? '; + $values[] = "%{$url}%"; + } + } - if ($filter->getNotAuthor()) { - foreach ($filter->getNotAuthor() as $author) { - $sub_search .= 'AND (NOT ' . $alias . 'author LIKE ?) '; - $values[] = "%{$author}%"; - } + if ($filter->getNotAuthor()) { + foreach ($filter->getNotAuthor() as $author) { + $sub_search .= 'AND (NOT ' . $alias . 'author LIKE ?) '; + $values[] = "%{$author}%"; } - if ($filter->getNotIntitle()) { - foreach ($filter->getNotIntitle() as $title) { - $sub_search .= 'AND (NOT ' . $alias . 'title LIKE ?) '; - $values[] = "%{$title}%"; - } + } + if ($filter->getNotIntitle()) { + foreach ($filter->getNotIntitle() as $title) { + $sub_search .= 'AND (NOT ' . $alias . 'title LIKE ?) '; + $values[] = "%{$title}%"; } - if ($filter->getNotTags()) { - foreach ($filter->getNotTags() as $tag) { - $sub_search .= 'AND (NOT ' . $alias . 'tags LIKE ?) '; - $values[] = "%{$tag}%"; - } + } + if ($filter->getNotTags()) { + foreach ($filter->getNotTags() as $tag) { + $sub_search .= 'AND (NOT ' . $alias . 'tags LIKE ?) '; + $values[] = "%{$tag}%"; } - if ($filter->getNotInurl()) { - foreach ($filter->getNotInurl() as $url) { - $sub_search .= 'AND (NOT ' . $this->sqlConcat($alias . 'link', $alias . 'guid') . ' LIKE ?) '; - $values[] = "%{$url}%"; - } + } + if ($filter->getNotInurl()) { + foreach ($filter->getNotInurl() as $url) { + $sub_search .= 'AND (NOT ' . static::sqlConcat($alias . 'link', $alias . 'guid') . ' LIKE ?) '; + $values[] = "%{$url}%"; } + } - if ($filter->getSearch()) { - foreach ($filter->getSearch() as $search_value) { - $sub_search .= 'AND ' . $this->sqlConcat($alias . 'title', - $this->isCompressed() ? 'UNCOMPRESS(' . $alias . 'content_bin)' : '' . $alias . 'content') . ' LIKE ? '; - $values[] = "%{$search_value}%"; - } + if ($filter->getSearch()) { + foreach ($filter->getSearch() as $search_value) { + $sub_search .= 'AND ' . static::sqlConcat($alias . 'title', + static::isCompressed() ? 'UNCOMPRESS(' . $alias . 'content_bin)' : '' . $alias . 'content') . ' LIKE ? '; + $values[] = "%{$search_value}%"; } - if ($filter->getNotSearch()) { - foreach ($filter->getNotSearch() as $search_value) { - $sub_search .= 'AND (NOT ' . $this->sqlConcat($alias . 'title', - $this->isCompressed() ? 'UNCOMPRESS(' . $alias . 'content_bin)' : '' . $alias . 'content') . ' LIKE ?) '; - $values[] = "%{$search_value}%"; - } + } + if ($filter->getNotSearch()) { + foreach ($filter->getNotSearch() as $search_value) { + $sub_search .= 'AND (NOT ' . static::sqlConcat($alias . 'title', + static::isCompressed() ? 'UNCOMPRESS(' . $alias . 'content_bin)' : '' . $alias . 'content') . ' LIKE ?) '; + $values[] = "%{$search_value}%"; } + } - if ($sub_search != '') { - if ($isOpen) { - $search .= 'OR '; - } else { - $search .= 'AND ('; - $isOpen = true; - } - $search .= '(' . substr($sub_search, 4) . ') '; + if ($sub_search != '') { + if ($isOpen) { + $search .= ' OR '; + } else { + $isOpen = true; } + // Remove superfluous leading 'AND ' + $search .= '(' . substr($sub_search, 4) . ')'; } - if ($isOpen) { - $search .= ') '; + } + + return [ $values, $search ]; + } + + /** @param FreshRSS_BooleanSearch|null $filters */ + protected function sqlListEntriesWhere(string $alias = '', $filters = null, int $state = FreshRSS_Entry::STATE_ALL, + string $order = 'DESC', string $firstId = '', int $date_min = 0) { + $search = ' '; + $values = array(); + if ($state & FreshRSS_Entry::STATE_NOT_READ) { + if (!($state & FreshRSS_Entry::STATE_READ)) { + $search .= 'AND ' . $alias . 'is_read=0 '; + } + } elseif ($state & FreshRSS_Entry::STATE_READ) { + $search .= 'AND ' . $alias . 'is_read=1 '; + } + if ($state & FreshRSS_Entry::STATE_FAVORITE) { + if (!($state & FreshRSS_Entry::STATE_NOT_FAVORITE)) { + $search .= 'AND ' . $alias . 'is_favorite=1 '; + } + } elseif ($state & FreshRSS_Entry::STATE_NOT_FAVORITE) { + $search .= 'AND ' . $alias . 'is_favorite=0 '; + } + + switch ($order) { + case 'DESC': + case 'ASC': + break; + default: + throw new FreshRSS_EntriesGetter_Exception('Bad order in Entry->listByType: [' . $order . ']!'); + } + if ($firstId !== '') { + $search .= 'AND ' . $alias . 'id ' . ($order === 'DESC' ? '<=' : '>=') . ' ? '; + $values[] = $firstId; + } + if ($date_min > 0) { + $search .= 'AND ' . $alias . 'id >= ? '; + $values[] = $date_min . '000000'; + } + if ($filters && count($filters->searches()) > 0) { + list($filterValues, $filterSearch) = self::sqlBooleanSearch($alias, $filters); + $filterSearch = trim($filterSearch); + if ($filterSearch !== '') { + $search .= 'AND (' . $filterSearch . ') '; + $values = array_merge($values, $filterValues); } } return array($values, $search); @@ -1040,7 +1064,7 @@ SQL; list($values, $sql) = $this->sqlListWhere($type, $id, $state, $order, $limit, $firstId, $filters, $date_min); $sql = 'SELECT e0.id, e0.guid, e0.title, e0.author, ' - . ($this->isCompressed() ? 'UNCOMPRESS(content_bin) AS content' : 'content') + . (static::isCompressed() ? 'UNCOMPRESS(content_bin) AS content' : 'content') . ', e0.link, e0.date, e0.is_read, e0.is_favorite, e0.id_feed, e0.tags ' . 'FROM `_entry` e0 ' . 'INNER JOIN (' @@ -1085,7 +1109,7 @@ SQL; } $sql = 'SELECT id, guid, title, author, ' - . ($this->isCompressed() ? 'UNCOMPRESS(content_bin) AS content' : 'content') + . (static::isCompressed() ? 'UNCOMPRESS(content_bin) AS content' : 'content') . ', link, date, is_read, is_favorite, id_feed, tags ' . 'FROM `_entry` ' . 'WHERE id IN (' . str_repeat('?,', count($ids) - 1). '?) ' @@ -1124,7 +1148,7 @@ SQL; return $result; } $guids = array_unique($guids); - $sql = 'SELECT guid, ' . $this->sqlHexEncode('hash') . + $sql = 'SELECT guid, ' . static::sqlHexEncode('hash') . ' AS hex_hash FROM `_entry` WHERE id_feed=? AND guid IN (' . str_repeat('?,', count($guids) - 1). '?)'; $stm = $this->pdo->prepare($sql); $values = array($id_feed); |
