mirror of
https://github.com/FreshRSS/FreshRSS.git
synced 2026-07-16 01:52:53 -04:00
Add read status hook for entries (#8995)
- Add a new `EntriesRead` / `entries_read` hook for extensions. - Trigger the hook when `markRead()` changes entry read/unread state for explicit entry IDs. - Cover both the default DAO path and the SQLite/PGSQL DAO path. - Add Minz hook tests for the new hook signature and argument passing. ## Why Extensions can already react to favorite/bookmark changes through `EntriesFavorite`. This adds the matching read/unread hook requested in #4051 for extensions that need to collect stats or react to manual read state changes. Closes #4051
This commit is contained in:
@@ -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 */
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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:
|
||||
|
||||
28
tests/lib/Minz/HookTypeTest.php
Normal file
28
tests/lib/Minz/HookTypeTest.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class HookTypeTest extends TestCase
|
||||
{
|
||||
public static function testEntriesReadHookUsesPassArgumentsSignature(): void {
|
||||
self::assertSame(Minz_HookSignature::PassArguments, Minz_HookType::EntriesRead->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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user