diff --git a/app/Models/EntryDAO.php b/app/Models/EntryDAO.php index 096d7e6e0..500a6f26c 100644 --- a/app/Models/EntryDAO.php +++ b/app/Models/EntryDAO.php @@ -538,6 +538,9 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { if (($affected > 0) && (!$this->updateCacheUnreads(null, null))) { return false; } + if ($affected > 0) { + Minz_ExtensionManager::callHook(Minz_HookType::EntriesRead, $ids, $is_read); + } return $affected; } else { FreshRSS_UserDAO::touch(); @@ -555,7 +558,11 @@ class FreshRSS_EntryDAO extends Minz_ModelPdo { $stm->bindValue(':id', $ids, PDO::PARAM_STR) && // TODO: Test PDO::PARAM_INT on 32-bit platform $stm->bindValue(':old_is_read', $is_read ? 0 : 1, PDO::PARAM_INT) && $stm->execute()) { - return $stm->rowCount(); + $affected = $stm->rowCount(); + if ($affected > 0) { + Minz_ExtensionManager::callHook(Minz_HookType::EntriesRead, [$ids], $is_read); + } + return $affected; } else { $info = $stm === false ? $this->pdo->errorInfo() : $stm->errorInfo(); /** @var array{0:string,1:int,2:string} $info */ diff --git a/app/Models/EntryDAOSQLite.php b/app/Models/EntryDAOSQLite.php index 54da380d0..35f3611aa 100644 --- a/app/Models/EntryDAOSQLite.php +++ b/app/Models/EntryDAOSQLite.php @@ -164,6 +164,9 @@ class FreshRSS_EntryDAOSQLite extends FreshRSS_EntryDAO { } } $this->pdo->commit(); + if ($affected > 0) { + Minz_ExtensionManager::callHook(Minz_HookType::EntriesRead, [$ids], $is_read); + } return $affected; } } diff --git a/docs/en/developers/03_Backend/05_Extensions.md b/docs/en/developers/03_Backend/05_Extensions.md index bdf2df3e4..54fc1cbfa 100644 --- a/docs/en/developers/03_Backend/05_Extensions.md +++ b/docs/en/developers/03_Backend/05_Extensions.md @@ -181,6 +181,8 @@ Example response for a `query_icon_info` request: * `Minz_HookType::CustomFaviconHash` (`function(FreshRSS_Feed $feed): string | null`): Enables the modification of custom favicon hashes by returning params from the hook function. The hook should check if the `customFaviconExt` attribute of `$feed` is set to the extension's name before returning a custom value. Otherwise, the return value should be null. * `Minz_HookType::EntriesFavorite` (`function(array $ids, bool $is_favorite): void`): will be executed when some entries are marked or unmarked as favorites (starred) +* `Minz_HookType::EntriesRead` (`function(array $ids, bool $is_read): void`): + will be executed when some entries are marked or unmarked as read * `Minz_HookType::EntryAutoRead` (`function(FreshRSS_Entry $entry, string $why): void`): Triggered when an entry is automatically marked as read. The *why* parameter supports the rules {`filter`, `upon_reception`, `same_title_in_feed`, `same_guid_in_category`}. * `Minz_HookType::EntryAutoUnread` (`function(FreshRSS_Entry $entry, string $why): void`): Triggered when an entry is automatically marked as unread. The *why* parameter supports the rules {`updated_article`}. * `Minz_HookType::EntryBeforeDisplay` (`function($entry) -> Entry | null`): will be executed every time an entry is rendered. The entry itself (instance of FreshRSS\_Entry) will be passed as parameter. diff --git a/lib/Minz/HookType.php b/lib/Minz/HookType.php index bdd43035c..043934cb1 100644 --- a/lib/Minz/HookType.php +++ b/lib/Minz/HookType.php @@ -9,6 +9,7 @@ enum Minz_HookType: string { case CustomFaviconBtnUrl = 'custom_favicon_btn_url'; // function(FreshRSS_Feed $feed): string | null case CustomFaviconHash = 'custom_favicon_hash'; // function(FreshRSS_Feed $feed): string | null case EntriesFavorite = 'entries_favorite'; // function(array $ids, bool $is_favorite): void + case EntriesRead = 'entries_read'; // function(array $ids, bool $is_read): void case EntryAutoRead = 'entry_auto_read'; // function(FreshRSS_Entry $entry, string $why): void case EntryAutoUnread = 'entry_auto_unread'; // function(FreshRSS_Entry $entry, string $why): void case EntryBeforeAdd = 'entry_before_add'; // function(FreshRSS_Entry $entry) -> FreshRSS_Entry | null @@ -62,6 +63,7 @@ enum Minz_HookType: string { case self::CustomFaviconBtnUrl: case self::CustomFaviconHash: case self::EntriesFavorite: + case self::EntriesRead: case self::EntryAutoRead: case self::EntryAutoUnread: case self::SimplepieAfterInit: diff --git a/tests/lib/Minz/HookTypeTest.php b/tests/lib/Minz/HookTypeTest.php new file mode 100644 index 000000000..80320fdfd --- /dev/null +++ b/tests/lib/Minz/HookTypeTest.php @@ -0,0 +1,28 @@ +signature()); + } + + public static function testEntriesReadHookPassesIdsAndReadState(): void { + $actual_ids = []; + $actual_is_read = null; + + Minz_ExtensionManager::addHook( + Minz_HookType::EntriesRead, + static function (array $ids, bool $is_read) use (&$actual_ids, &$actual_is_read): void { + $actual_ids = $ids; + $actual_is_read = $is_read; + } + ); + + Minz_ExtensionManager::callHook(Minz_HookType::EntriesRead, ['123', '456'], false); + + self::assertSame(['123', '456'], $actual_ids); + self::assertFalse($actual_is_read); + } +}