From d165ed1fb6096d5bd0558d3ed637e53bc728baa7 Mon Sep 17 00:00:00 2001 From: Alexandre Alapetite Date: Sun, 13 Aug 2023 15:11:22 +0200 Subject: Fix hash of articles with loadCompleteContent (#5576) * Fix hash of articles with loadCompleteContent The detection of modified articles was wrong for feeds using loadCompleteContent. Indeed, the hash is supposed to computed on the content provided by the server of the RSS feed, excluding further modifications. Furthermore, read hash from database instead of recomputing it all the time. Slightly related to https://github.com/FreshRSS/FreshRSS/pull/5574 * Explicit SQL alias * PHPDocs --- app/Models/Entry.php | 17 +++++++++++++---- app/Models/EntryDAO.php | 18 +++++++++++------- 2 files changed, 24 insertions(+), 11 deletions(-) (limited to 'app/Models') diff --git a/app/Models/Entry.php b/app/Models/Entry.php index 77f39f256..7da27e409 100644 --- a/app/Models/Entry.php +++ b/app/Models/Entry.php @@ -61,7 +61,7 @@ class FreshRSS_Entry extends Minz_Model { } /** @param array{'id'?:string,'id_feed'?:int,'guid'?:string,'title'?:string,'author'?:string,'content'?:string,'link'?:string,'date'?:int|string,'lastSeen'?:int, - * 'is_read'?:bool|int,'is_favorite'?:bool|int,'tags'?:string|array,'attributes'?:string,'thumbnail'?:string,'timestamp'?:string} $dao */ + * 'hash'?:string,'is_read'?:bool|int,'is_favorite'?:bool|int,'tags'?:string|array,'attributes'?:string,'thumbnail'?:string,'timestamp'?:string} $dao */ public static function fromArray(array $dao): FreshRSS_Entry { if (empty($dao['content'])) { $dao['content'] = ''; @@ -101,6 +101,9 @@ class FreshRSS_Entry extends Minz_Model { if (!empty($dao['attributes'])) { $entry->_attributes('', $dao['attributes']); } + if (!empty($dao['hash'])) { + $entry->_hash($dao['hash']); + } return $entry; } @@ -147,6 +150,13 @@ class FreshRSS_Entry extends Minz_Model { ($mime == '' && $length == 0 && preg_match('/[.](avif|gif|jpe?g|png|svg|webp)$/i', $elink)); } + /** + * Provides the original content without additional content potentially added by loadCompleteContent(). + */ + public function originalContent(): string { + return preg_replace('#.*#s', '', $this->content); + } + /** * @param bool $withEnclosures Set to true to include the enclosures in the returned HTML, false otherwise. * @param bool $allowDuplicateEnclosures Set to false to remove obvious enclosure duplicates (based on simple string comparison), true otherwise. @@ -412,7 +422,7 @@ HTML; public function hash(): string { if ($this->hash == '') { //Do not include $this->date because it may be automatically generated when lacking - $this->hash = md5($this->link . $this->title . $this->authors(true) . $this->content . $this->tags(true)); + $this->hash = md5($this->link . $this->title . $this->authors(true) . $this->originalContent() . $this->tags(true)); } return $this->hash; } @@ -473,7 +483,6 @@ HTML; } /** @param int|string $value */ public function _date($value): void { - $this->hash = ''; $value = intval($value); $this->date = $value > 1 ? $value : time(); } @@ -770,7 +779,7 @@ HTML; ); if ('' !== $fullContent) { $fullContent = "{$fullContent}"; - $originalContent = preg_replace('#.*#s', '', $this->content()); + $originalContent = $this->originalContent(); switch ($feed->attributes('content_action')) { case 'prepend': $this->content = $fullContent . $originalContent; diff --git a/app/Models/EntryDAO.php b/app/Models/EntryDAO.php index e8a531ec0..8306320a0 100644 --- a/app/Models/EntryDAO.php +++ b/app/Models/EntryDAO.php @@ -729,8 +729,9 @@ SQL; public function searchByGuid(int $id_feed, string $guid): ?FreshRSS_Entry { $content = static::isCompressed() ? 'UNCOMPRESS(content_bin) AS content' : 'content'; + $hash = static::sqlHexEncode('hash'); $sql = <<fetchAssoc($sql, [':id_feed' => $id_feed, ':guid' => $guid]); @@ -741,8 +742,9 @@ SQL; public function searchById(string $id): ?FreshRSS_Entry { $content = static::isCompressed() ? 'UNCOMPRESS(content_bin) AS content' : 'content'; + $hash = static::sqlHexEncode('hash'); $sql = <<fetchAssoc($sql, [':id' => $id]); @@ -1136,9 +1138,10 @@ SQL; if ($order !== 'DESC' && $order !== 'ASC') { $order = 'DESC'; } - $content = static::isCompressed() ? 'UNCOMPRESS(content_bin) AS content' : 'content'; + $content = static::isCompressed() ? 'UNCOMPRESS(e0.content_bin) AS content' : 'e0.content'; + $hash = static::sqlHexEncode('e0.hash'); $sql = <<fetch(PDO::FETCH_ASSOC)) { /** @var array{'id':string,'id_feed':int,'guid':string,'title':string,'author':string,'content':string,'link':string,'date':int, - * 'is_read':int,'is_favorite':int,'tags':string,'attributes'?:string} $row */ + * 'hash':string,'is_read':int,'is_favorite':int,'tags':string,'attributes'?:string} $row */ yield FreshRSS_Entry::fromArray($row); } } @@ -1198,9 +1201,10 @@ SQL; $order = 'DESC'; } $content = static::isCompressed() ? 'UNCOMPRESS(content_bin) AS content' : 'content'; + $hash = static::sqlHexEncode('hash'); $repeats = str_repeat('?,', count($ids) - 1) . '?'; $sql = <<fetch(PDO::FETCH_ASSOC)) { /** @var array{'id':string,'id_feed':int,'guid':string,'title':string,'author':string,'content':string,'link':string,'date':int, - * 'is_read':int,'is_favorite':int,'tags':string,'attributes'?:string} $row */ + * 'hash':string,'is_read':int,'is_favorite':int,'tags':string,'attributes'?:string} $row */ yield FreshRSS_Entry::fromArray($row); } } -- cgit v1.2.3