Add hook enums (#8036)

- add an enum to handle hook types (enum are available since PHP 8.1)
- change hook calls from string value to enum value
This commit is contained in:
Alexis Degrugillier
2025-09-30 16:59:41 -04:00
committed by GitHub
parent bf6e634e04
commit 72884813e1
32 changed files with 200 additions and 190 deletions

View File

@@ -55,7 +55,7 @@ class FreshRSS_feed_Controller extends FreshRSS_ActionController {
$url = trim($url);
/** @var string|null $urlHooked */
$urlHooked = Minz_ExtensionManager::callHook('check_url_before_add', $url);
$urlHooked = Minz_ExtensionManager::callHook(Minz_HookType::CheckUrlBeforeAdd, $url);
if ($urlHooked === null) {
throw new FreshRSS_FeedNotAdded_Exception($url);
}
@@ -106,7 +106,7 @@ class FreshRSS_feed_Controller extends FreshRSS_ActionController {
}
/** @var FreshRSS_Feed|null $feed */
$feed = Minz_ExtensionManager::callHook('feed_before_insert', $feed);
$feed = Minz_ExtensionManager::callHook(Minz_HookType::FeedBeforeInsert, $feed);
if ($feed === null) {
throw new FreshRSS_FeedNotAdded_Exception($url);
}
@@ -465,7 +465,7 @@ class FreshRSS_feed_Controller extends FreshRSS_ActionController {
$categoriesEntriesTitle = [];
foreach ($feeds as $feed) {
$feed = Minz_ExtensionManager::callHook('feed_before_actualize', $feed);
$feed = Minz_ExtensionManager::callHook(Minz_HookType::FeedBeforeActualize, $feed);
if (!($feed instanceof FreshRSS_Feed)) {
continue;
}
@@ -616,10 +616,10 @@ class FreshRSS_feed_Controller extends FreshRSS_ActionController {
$entry->_isFavorite(null); // Do not change favourite state
$entry->_isRead($mark_updated_article_unread ? false : null); //Change is_read according to policy.
if ($mark_updated_article_unread) {
Minz_ExtensionManager::callHook('entry_auto_unread', $entry, 'updated_article');
Minz_ExtensionManager::callHook(Minz_HookType::EntryAutoUnread, $entry, 'updated_article');
}
$entry = Minz_ExtensionManager::callHook('entry_before_insert', $entry);
$entry = Minz_ExtensionManager::callHook(Minz_HookType::EntryBeforeInsert, $entry);
if (!($entry instanceof FreshRSS_Entry)) {
// An extension has returned a null value, there is nothing to insert.
continue;
@@ -642,7 +642,7 @@ class FreshRSS_feed_Controller extends FreshRSS_ActionController {
// If the entry has changed, there is a good chance for the full content to have changed as well.
$entry->loadCompleteContent(true);
$entry = Minz_ExtensionManager::callHook('entry_before_update', $entry);
$entry = Minz_ExtensionManager::callHook(Minz_HookType::EntryBeforeUpdate, $entry);
if (!($entry instanceof FreshRSS_Entry)) {
// An extension has returned a null value, there is nothing to insert.
continue;
@@ -655,7 +655,7 @@ class FreshRSS_feed_Controller extends FreshRSS_ActionController {
$id = uTimeString();
$entry->_id($id);
$entry = Minz_ExtensionManager::callHook('entry_before_insert', $entry);
$entry = Minz_ExtensionManager::callHook(Minz_HookType::EntryBeforeInsert, $entry);
if (!($entry instanceof FreshRSS_Entry)) {
// An extension has returned a null value, there is nothing to insert.
continue;
@@ -681,7 +681,7 @@ class FreshRSS_feed_Controller extends FreshRSS_ActionController {
$feed->pubSubHubbubError(true);
}
$entry = Minz_ExtensionManager::callHook('entry_before_add', $entry);
$entry = Minz_ExtensionManager::callHook(Minz_HookType::EntryBeforeAdd, $entry);
if (!($entry instanceof FreshRSS_Entry)) {
// An extension has returned a null value, there is nothing to insert.
continue;
@@ -911,7 +911,7 @@ class FreshRSS_feed_Controller extends FreshRSS_ActionController {
// Case of a batch refresh (e.g. cron)
$databaseDAO = FreshRSS_Factory::createDatabaseDAO();
$databaseDAO->minorDbMaintenance();
Minz_ExtensionManager::callHookVoid('freshrss_user_maintenance');
Minz_ExtensionManager::callHookVoid(Minz_HookType::FreshrssUserMaintenance);
FreshRSS_feed_Controller::commitNewEntries();
$feedDAO = FreshRSS_Factory::createFeedDao();

View File

@@ -478,14 +478,14 @@ class FreshRSS_importExport_Controller extends FreshRSS_ActionController {
}
$newGuids[$entry->guid()] = true;
$entry = Minz_ExtensionManager::callHook('entry_before_insert', $entry);
$entry = Minz_ExtensionManager::callHook(Minz_HookType::EntryBeforeInsert, $entry);
if (!($entry instanceof FreshRSS_Entry)) {
// An extension has returned a null value, there is nothing to insert.
continue;
}
if (isset($existingHashForGuids['f_' . $feed_id][$entry->guid()])) {
$entry = Minz_ExtensionManager::callHook('entry_before_update', $entry);
$entry = Minz_ExtensionManager::callHook(Minz_HookType::EntryBeforeUpdate, $entry);
if (!($entry instanceof FreshRSS_Entry)) {
// An extension has returned a null value, there is nothing to insert.
continue;
@@ -495,7 +495,7 @@ class FreshRSS_importExport_Controller extends FreshRSS_ActionController {
} else {
$entry->_lastSeen(time());
$entry = Minz_ExtensionManager::callHook('entry_before_add', $entry);
$entry = Minz_ExtensionManager::callHook(Minz_HookType::EntryBeforeAdd, $entry);
if (!($entry instanceof FreshRSS_Entry)) {
// An extension has returned a null value, there is nothing to insert.
continue;
@@ -581,7 +581,7 @@ class FreshRSS_importExport_Controller extends FreshRSS_ActionController {
}
// Call the extension hook
$feed = Minz_ExtensionManager::callHook('feed_before_insert', $feed);
$feed = Minz_ExtensionManager::callHook(Minz_HookType::FeedBeforeInsert, $feed);
if ($feed instanceof FreshRSS_Feed) {
// addFeedObject checks if feed is already in DB so nothing else to
// check here.

View File

@@ -32,7 +32,7 @@ class FreshRSS_javascript_Controller extends FreshRSS_ActionController {
$databaseDAO = FreshRSS_Factory::createDatabaseDAO();
$databaseDAO->minorDbMaintenance();
Minz_ExtensionManager::callHookVoid('freshrss_user_maintenance');
Minz_ExtensionManager::callHookVoid(Minz_HookType::FreshrssUserMaintenance);
$catDAO = FreshRSS_Factory::createCategoryDao();
$this->view->categories = $catDAO->listCategoriesOrderUpdate(FreshRSS_Context::userConf()->dynamic_opml_ttl_default);

View File

@@ -283,7 +283,7 @@ class FreshRSS_update_Controller extends FreshRSS_ActionController {
$res = do_post_update();
}
Minz_ExtensionManager::callHookVoid('post_update');
Minz_ExtensionManager::callHookVoid(Minz_HookType::PostUpdate);
if ($res === true) {
@unlink(UPDATE_FILENAME);

View File

@@ -66,7 +66,7 @@ class FreshRSS extends Minz_FrontController {
self::checkEmailValidated();
}
Minz_ExtensionManager::callHookVoid('freshrss_init');
Minz_ExtensionManager::callHookVoid(Minz_HookType::FreshrssInit);
}
private static function initAuth(): void {

View File

@@ -818,12 +818,12 @@ HTML;
if (!$this->isRead()) {
if ($feed->attributeBoolean('read_upon_reception') ?? FreshRSS_Context::userConf()->mark_when['reception']) {
$this->_isRead(true);
Minz_ExtensionManager::callHook('entry_auto_read', $this, 'upon_reception');
Minz_ExtensionManager::callHook(Minz_HookType::EntryAutoRead, $this, 'upon_reception');
}
if (!empty($titlesAsRead[$this->title()])) {
Minz_Log::debug('Mark title as read: ' . $this->title());
$this->_isRead(true);
Minz_ExtensionManager::callHook('entry_auto_read', $this, 'same_title_in_feed');
Minz_ExtensionManager::callHook(Minz_HookType::EntryAutoRead, $this, 'same_title_in_feed');
}
}
FreshRSS_Context::userConf()->applyFilterActions($this);

View File

@@ -387,7 +387,7 @@ SQL;
$values = array_merge($values, $ids);
$stm = $this->pdo->prepare($sql);
if ($stm !== false && $stm->execute($values)) {
Minz_ExtensionManager::callHook('entries_favorite', $ids, $is_favorite);
Minz_ExtensionManager::callHook(Minz_HookType::EntriesFavorite, $ids, $is_favorite);
return $stm->rowCount();
} else {
$info = $stm === false ? $this->pdo->errorInfo() : $stm->errorInfo();

View File

@@ -255,7 +255,7 @@ class FreshRSS_Feed extends Minz_Model {
$params = '';
if ($this->customFavicon()) {
$current = $this->id . Minz_User::name();
$hookParams = Minz_ExtensionManager::callHook('custom_favicon_hash', $this);
$hookParams = Minz_ExtensionManager::callHook(Minz_HookType::CustomFaviconHash, $this);
$params = $hookParams !== null ? $hookParams : $current;
} else {
$params = $this->website(fallback: true) . $this->proxyParam();
@@ -579,9 +579,9 @@ class FreshRSS_Feed extends Minz_Model {
// Do not use `$simplePie->enable_cache(false);` as it would prevent caching in multiuser context
$this->clearCache();
}
Minz_ExtensionManager::callHook('simplepie_before_init', $simplePie, $this);
Minz_ExtensionManager::callHook(Minz_HookType::SimplepieBeforeInit, $simplePie, $this);
$simplePieResult = $simplePie->init();
Minz_ExtensionManager::callHook('simplepie_after_init', $simplePie, $this, $simplePieResult);
Minz_ExtensionManager::callHook(Minz_HookType::SimplepieAfterInit, $simplePie, $this, $simplePieResult);
if ($simplePieResult === false || $simplePie->get_hash() === '' || !empty($simplePie->error())) {
if ($simplePie->status_code() === 429) {

View File

@@ -132,7 +132,7 @@ trait FreshRSS_FilterActionsTrait {
case 'read':
if (!$entry->isRead()) {
$entry->_isRead(true);
Minz_ExtensionManager::callHook('entry_auto_read', $entry, 'filter');
Minz_ExtensionManager::callHook(Minz_HookType::EntryAutoRead, $entry, 'filter');
}
break;
case 'star':

View File

@@ -51,7 +51,7 @@ final class FreshRSS_ViewMode {
$modes = self::getDefaultModes();
// Allow extensions to add their own view modes
$extensionModes = Minz_ExtensionManager::callHook('view_modes', []);
$extensionModes = Minz_ExtensionManager::callHook(Minz_HookType::ViewModes, []);
if (is_array($extensionModes)) {
foreach ($extensionModes as $mode) {
if ($mode instanceof FreshRSS_ViewMode) {

View File

@@ -311,7 +311,7 @@ class FreshRSS_Import_Service {
// Call the extension hook
/** @var FreshRSS_Feed|null */
$feed = Minz_ExtensionManager::callHook('feed_before_insert', $feed);
$feed = Minz_ExtensionManager::callHook(Minz_HookType::FeedBeforeInsert, $feed);
if ($dry_run) {
if ($feed !== null) {

View File

@@ -96,7 +96,7 @@ foreach ($users as $user) {
// NB: Extensions and hooks are reinitialised there
$app->init();
Minz_ExtensionManager::addHook('feed_before_actualize', static function (FreshRSS_Feed $feed) use ($mutexFile) {
Minz_ExtensionManager::addHook(Minz_HookType::FeedBeforeActualize, static function (FreshRSS_Feed $feed) use ($mutexFile) {
touch($mutexFile);
return $feed;
});

View File

@@ -60,7 +60,7 @@
<a href="<?= _url('index', 'logs') ?>"><?= _t('gen.menu.logs') ?></a>
</li>
<?php } ?>
<?= Minz_ExtensionManager::callHookString('menu_configuration_entry') ?>
<?= Minz_ExtensionManager::callHookString(Minz_HookType::MenuConfigurationEntry) ?>
</ul>
</li>
@@ -88,7 +88,7 @@
<li class="item<?= Minz_Request::actionName() === 'logs' ? ' active' : '' ?>">
<a href="<?= _url('index', 'logs') ?>"><?= _t('gen.menu.logs') ?></a>
</li>
<?= Minz_ExtensionManager::callHookString('menu_admin_entry') ?>
<?= Minz_ExtensionManager::callHookString(Minz_HookType::MenuAdminEntry) ?>
</ul>
</li>
<?php } ?>

View File

@@ -87,7 +87,7 @@
<li class="item"><a href="<?= _url('configure', 'queries') ?>"><?= _t('gen.menu.queries') ?></a></li>
<li class="item"><a href="<?= _url('extension', 'index') ?>"><?= _t('gen.menu.extensions') ?></a></li>
<li class="item"><a href="<?= _url('configure', 'privacy') ?>"><?= _t('gen.menu.privacy') ?></a></li>
<?= Minz_ExtensionManager::callHookString('menu_configuration_entry') ?>
<?= Minz_ExtensionManager::callHookString(Minz_HookType::MenuConfigurationEntry) ?>
</ul>
</li>
<?php if (FreshRSS_Auth::hasAccess('admin')) { ?>
@@ -103,7 +103,7 @@
<?php if (!FreshRSS_Context::systemConf()->disable_update) { ?>
<li class="item"><a href="<?= _url('update', 'index') ?>"><?= _t('gen.menu.update') ?></a></li>
<?php } ?>
<?= Minz_ExtensionManager::callHookString('menu_admin_entry') ?>
<?= Minz_ExtensionManager::callHookString(Minz_HookType::MenuAdminEntry) ?>
</ul>
</li>
<?php } ?>
@@ -117,7 +117,7 @@
<a href="<?= _url('index', 'tos') ?>"><?= _t('index.tos.title')?></a>
</li>
<?php } ?>
<?= Minz_ExtensionManager::callHookString('menu_other_entry') ?>
<?= Minz_ExtensionManager::callHookString(Minz_HookType::MenuOtherEntry) ?>
</ul>
</li>
</ul>

View File

@@ -190,7 +190,7 @@
<div id="nav_menu_views" class="group">
<?php
$readingModes = FreshRSS_ReadingMode::getReadingModes();
$readingModes = Minz_ExtensionManager::callHook('nav_reading_modes', $readingModes);
$readingModes = Minz_ExtensionManager::callHook(Minz_HookType::NavReadingModes, $readingModes);
if (!is_iterable($readingModes)) {
$readingModes = FreshRSS_ReadingMode::getReadingModes();
}
@@ -207,7 +207,7 @@
?>
</div>
<?php $nav_menu_hooks = Minz_ExtensionManager::callHookString('nav_menu'); ?>
<?php $nav_menu_hooks = Minz_ExtensionManager::callHookString(Minz_HookType::NavMenu); ?>
<?php if ($nav_menu_hooks != '') { ?>
<div id="nav_menu_hooks" class="group">
<?= $nav_menu_hooks ?>

View File

@@ -38,7 +38,7 @@
</label>
</div>
<?= Minz_ExtensionManager::callHookString('before_login_btn') ?>
<?= Minz_ExtensionManager::callHookString(Minz_HookType::BeforeLoginBtn) ?>
<div class="form-group form-group-actions">
<button id="loginButton" type="submit" class="btn btn-important" disabled="disabled">

View File

@@ -66,7 +66,7 @@
</div>
<?php } ?>
<?= Minz_ExtensionManager::callHookString('before_login_btn') ?>
<?= Minz_ExtensionManager::callHookString(Minz_HookType::BeforeLoginBtn) ?>
<div class="form-group form-group-actions">
<?php

View File

@@ -22,7 +22,7 @@ if (empty($this->entryIdsTagNames)) {
foreach ($this->entries as $entry) {
if (!$this->internal_rendering) {
/** @var FreshRSS_Entry|null $entry */
$entry = Minz_ExtensionManager::callHook('entry_before_display', $entry);
$entry = Minz_ExtensionManager::callHook(Minz_HookType::EntryBeforeDisplay, $entry);
}
if ($entry === null) {
continue;

View File

@@ -50,7 +50,7 @@
$originalIconUrl = $this->feed->favicon();
$this->feed->_attribute('customFavicon', $currentIconUrl !== $originalIconUrl);
$this->feed->resetFaviconHash();
$ext_url = Minz_ExtensionManager::callHook('custom_favicon_btn_url', $this->feed);
$ext_url = Minz_ExtensionManager::callHook(Minz_HookType::CustomFaviconBtnUrl, $this->feed);
?>
<div class="group-name"><?= _t('sub.feed.icon') ?></div>
<div class="group-controls">

View File

@@ -3,7 +3,7 @@ declare(strict_types=1);
/** @var FreshRSS_View $this */
$mark = FreshRSS_Context::userConf()->mark_when;
$s = FreshRSS_Context::userConf()->shortcuts;
$extData = Minz_ExtensionManager::callHook('js_vars', []);
$extData = Minz_ExtensionManager::callHook(Minz_HookType::JsVars, []);
echo json_encode([
'context' => [
'anonymous' => !FreshRSS_Auth::hasAccess(),

View File

@@ -47,7 +47,7 @@ $today = @strtotime('today');
$nbEntries = 0;
foreach ($this->entries as $item):
/** @var FreshRSS_Entry|null $item */
$item = Minz_ExtensionManager::callHook('entry_before_display', $item);
$item = Minz_ExtensionManager::callHook(Minz_HookType::EntryBeforeDisplay, $item);
if ($item === null) {
continue;
}

View File

@@ -20,7 +20,7 @@ $useKeepUnreadImportant = !FreshRSS_Context::isImportant() && !FreshRSS_Context:
$nbEntries = 0;
foreach ($this->entries as $entry):
/** @var FreshRSS_Entry|null $entry */
$entry = Minz_ExtensionManager::callHook('entry_before_display', $entry);
$entry = Minz_ExtensionManager::callHook(Minz_HookType::EntryBeforeDisplay, $entry);
if ($entry === null) {
continue;
}

View File

@@ -23,7 +23,7 @@
foreach ($this->entries as $item) {
if (!$this->internal_rendering) {
/** @var FreshRSS_Entry|null $item */
$item = Minz_ExtensionManager::callHook('entry_before_display', $item);
$item = Minz_ExtensionManager::callHook(Minz_HookType::EntryBeforeDisplay, $item);
if ($item === null) {
continue;
}

View File

@@ -20,13 +20,13 @@ if (!empty($cliOptions->errors)) {
$username = cliInitUser($cliOptions->user);
Minz_ExtensionManager::callHookVoid('freshrss_user_maintenance');
Minz_ExtensionManager::callHookVoid(Minz_HookType::FreshrssUserMaintenance);
fwrite(STDERR, 'FreshRSS actualizing user “' . $username . "”…\n");
$databaseDAO = FreshRSS_Factory::createDatabaseDAO();
$databaseDAO->minorDbMaintenance();
Minz_ExtensionManager::callHookVoid('freshrss_user_maintenance');
Minz_ExtensionManager::callHookVoid(Minz_HookType::FreshrssUserMaintenance);
FreshRSS_feed_Controller::commitNewEntries();
$feedDAO = FreshRSS_Factory::createFeedDao();

View File

@@ -143,8 +143,8 @@ final class HelloWorldExtension extends Minz_Extension
public function init(): void {
parent::init();
$this->registerHook('entry_before_display', [$this, 'renderEntry']);
$this->registerHook('check_url_before_add', [self::class, 'checkUrl']);
$this->registerHook(Minz_HookType::EntryBeforeDisplay, [$this, 'renderEntry']);
$this->registerHook(Minz_HookType::CheckUrlBeforeAdd, [self::class, 'checkUrl']);
}
public function renderEntry(FreshRSS_Entry $entry): FreshRSS_Entry {
@@ -164,7 +164,7 @@ final class HelloWorldExtension extends Minz_Extension
The following events are available:
* `api_misc` (`function(): void`): to allow extensions to have own API endpoint
* `api_misc` (`function(): void`): to allow extensions to have their own API endpoint
on `/api/misc.php/Extension%20Name/` or `/api/misc.php?ext=Extension%20Name`.
* `before_login_btn` (`function(): string`): Allows to insert HTML before the login button. Applies to the create button on the register page as well. Example use case is inserting a captcha widget.
* `check_url_before_add` (`function($url) -> Url | null`): will be executed every time a URL is added. The URL itself will be passed as parameter. This way a website known to have feeds which doesnt advertise it in the header can still be automatically supported.

View File

@@ -197,8 +197,8 @@ final class HelloWorldExtension extends Minz_Extension
public function init(): void {
parent::init();
$this->registerHook('entry_before_display', [$this, 'renderEntry']);
$this->registerHook('check_url_before_add', [self::class, 'checkUrl']);
$this->registerHook(Minz_HookType::EntryBeforeDisplay, [$this, 'renderEntry']);
$this->registerHook(Minz_HookType::CheckUrlBeforeAdd, [self::class, 'checkUrl']);
}
public function renderEntry(FreshRSS_Entry $entry): FreshRSS_Entry {

View File

@@ -19,114 +19,9 @@ final class Minz_ExtensionManager {
/**
* List of available hooks. Please keep this list sorted.
* @var array<string,array{'list':array<callable>,'signature':'NoneToNone'|'NoneToString'|'OneToOne'|'PassArguments'}>
* @var array<value-of<Minz_HookType>,array{'list':list<callable>,'signature':Minz_HookSignature}>
*/
private static array $hook_list = [
'api_misc' => [ // function(): void
'list' => [],
'signature' => 'NoneToNone',
],
'before_login_btn' => [ // function(): string
'list' => [],
'signature' => 'NoneToString',
],
'check_url_before_add' => [ // function($url) -> Url | null
'list' => [],
'signature' => 'OneToOne',
],
'custom_favicon_btn_url' => [ // function(FreshRSS_Feed $feed): string | null
'list' => [],
'signature' => 'PassArguments',
],
'custom_favicon_hash' => [ // function(FreshRSS_Feed $feed): string | null
'list' => [],
'signature' => 'PassArguments',
],
'entries_favorite' => [ // function(array $ids, bool $is_favorite): void
'list' => [],
'signature' => 'PassArguments',
],
'entry_auto_read' => [ // function(FreshRSS_Entry $entry, string $why): void
'list' => [],
'signature' => 'PassArguments',
],
'entry_auto_unread' => [ // function(FreshRSS_Entry $entry, string $why): void
'list' => [],
'signature' => 'PassArguments',
],
'entry_before_display' => [ // function($entry) -> Entry | null
'list' => [],
'signature' => 'OneToOne',
],
'entry_before_insert' => [ // function($entry) -> Entry | null
'list' => [],
'signature' => 'OneToOne',
],
'entry_before_add' => [ // function($entry) -> Entry | null
'list' => [],
'signature' => 'OneToOne',
],
'entry_before_update' => [ // function($entry) -> Entry | null
'list' => [],
'signature' => 'OneToOne',
],
'feed_before_actualize' => [ // function($feed) -> Feed | null
'list' => [],
'signature' => 'OneToOne',
],
'feed_before_insert' => [ // function($feed) -> Feed | null
'list' => [],
'signature' => 'OneToOne',
],
'freshrss_init' => [ // function() -> none
'list' => [],
'signature' => 'NoneToNone',
],
'freshrss_user_maintenance' => [ // function() -> none
'list' => [],
'signature' => 'NoneToNone',
],
'js_vars' => [ // function($vars = array) -> array | null
'list' => [],
'signature' => 'OneToOne',
],
'menu_admin_entry' => [ // function() -> string
'list' => [],
'signature' => 'NoneToString',
],
'menu_configuration_entry' => [ // function() -> string
'list' => [],
'signature' => 'NoneToString',
],
'menu_other_entry' => [ // function() -> string
'list' => [],
'signature' => 'NoneToString',
],
'nav_menu' => [ // function() -> string
'list' => [],
'signature' => 'NoneToString',
],
'nav_reading_modes' => [ // function($readingModes = array) -> array | null
'list' => [],
'signature' => 'OneToOne',
],
'post_update' => [ // function(none) -> none
'list' => [],
'signature' => 'NoneToNone',
],
'simplepie_after_init' => [ // function(\SimplePie\SimplePie $simplePie, FreshRSS_Feed $feed, bool $result): void
'list' => [],
'signature' => 'PassArguments',
],
'simplepie_before_init' => [ // function(\SimplePie\SimplePie $simplePie, FreshRSS_Feed $feed): void
'list' => [],
'signature' => 'PassArguments',
],
'view_modes' => [ // function($viewModes = array) -> array | null
'list' => [],
'signature' => 'OneToOne',
],
];
private static array $hook_list = [];
/** Remove extensions and hooks from a previous initialisation */
private static function reset(): void {
@@ -134,10 +29,12 @@ final class Minz_ExtensionManager {
self::$ext_list = [];
self::$ext_list_enabled = [];
self::$ext_auto_enabled = [];
foreach (self::$hook_list as $hook_type => $hook_data) {
$hadAny |= !empty($hook_data['list']);
$hook_data['list'] = [];
self::$hook_list[$hook_type] = $hook_data;
foreach (Minz_HookType::cases() as $hook_type) {
$hadAny |= !empty(self::$hook_list[$hook_type->value]['list']);
self::$hook_list[$hook_type->value] = [
'list' => [],
'signature' => $hook_type->signature(),
];
}
if ($hadAny) {
gc_collect_cycles();
@@ -357,46 +254,62 @@ final class Minz_ExtensionManager {
/**
* Add a hook function to a given hook.
*
* The hook name must be a valid one. For the valid list, see self::$hook_list
* array keys.
* The hook name must be a valid one. For the valid list, see Minz_HookType enum.
*
* @param string $hook_name the hook name (must exist).
* @param string|Minz_HookType $hook the hook name (must exist).
* @param callable $hook_function the function name to call (must be callable).
*/
public static function addHook(string $hook_name, $hook_function): void {
if (isset(self::$hook_list[$hook_name]) && is_callable($hook_function)) {
public static function addHook(string|Minz_HookType $hook, $hook_function): void {
if (null === $hook = self::extractHook($hook)) {
return;
}
$hook_name = $hook->value;
if (is_callable($hook_function)) {
self::$hook_list[$hook_name]['list'][] = $hook_function;
}
}
/**
* Call functions related to a given hook.
*
* The hook name must be a valid one. For the valid list, see self::$hook_list
* array keys.
*
* @param string $hook_name the hook to call.
* @param mixed ...$args additional parameters (for signature, please see self::$hook_list).
* @return mixed|void|null final result of the called hook.
* @param string|Minz_HookType $hook the hook or its name
* @return Minz_HookType|null
*/
public static function callHook(string $hook_name, ...$args) {
if (!isset(self::$hook_list[$hook_name])) {
return;
private static function extractHook(string|Minz_HookType $hook) {
if ($hook instanceof Minz_HookType) {
return $hook;
}
return Minz_HookType::tryFrom($hook);
}
/**
* Call functions related to a given hook.
*
* The hook name must be a valid one. For the valid list, see Minz_HookType enum.
*
* @param string|Minz_HookType $hook the hook to call.
* @param mixed ...$args additional parameters (for signature, please see Minz_HookType enum).
* @return mixed|void|null final result of the called hook.
*/
public static function callHook(string|Minz_HookType $hook, ...$args) {
if (null === $hook = self::extractHook($hook)) {
return;
}
$hook_name = $hook->value;
$signature = self::$hook_list[$hook_name]['signature'];
if ($signature === 'OneToOne') {
if ($signature === Minz_HookSignature::OneToOne) {
return self::callOneToOne($hook_name, $args[0] ?? null);
} elseif ($signature === 'PassArguments') {
} elseif ($signature === Minz_HookSignature::PassArguments) {
foreach (self::$hook_list[$hook_name]['list'] as $function) {
$result = call_user_func($function, ...$args);
if ($result !== null) {
return $result;
}
}
} elseif ($signature === 'NoneToString') {
} elseif ($signature === Minz_HookSignature::NoneToString) {
return self::callHookString($hook_name);
} elseif ($signature === 'NoneToNone') {
} elseif ($signature === Minz_HookSignature::NoneToNone) {
self::callHookVoid($hook_name);
}
return;
@@ -411,12 +324,17 @@ final class Minz_ExtensionManager {
*
* If a hook return a null value, the method is stopped and return null.
*
* @param string $hook_name is the hook to call.
* @param string|Minz_HookType $hook is the hook to call.
* @param mixed $arg is the argument to pass to the first extension hook.
* @return mixed|null final chained result of the hooks. If nothing is changed,
* the initial argument is returned.
*/
private static function callOneToOne(string $hook_name, mixed $arg): mixed {
private static function callOneToOne(string|Minz_HookType $hook, mixed $arg): mixed {
if (null === $hook = self::extractHook($hook)) {
return $arg;
}
$hook_name = $hook->value;
$result = $arg;
foreach (self::$hook_list[$hook_name]['list'] as $function) {
$result = call_user_func($function, $arg);
@@ -436,10 +354,15 @@ final class Minz_ExtensionManager {
* The result is concatenated between each hook and the final string is
* returned.
*
* @param string $hook_name is the hook to call.
* @param string|Minz_HookType $hook is the hook to call.
* @return string concatenated result of the call to all the hooks.
*/
public static function callHookString(string $hook_name): string {
public static function callHookString(string|Minz_HookType $hook): string {
if (null === $hook = self::extractHook($hook)) {
return '';
}
$hook_name = $hook->value;
$result = '';
foreach (self::$hook_list[$hook_name]['list'] ?? [] as $function) {
$return = call_user_func($function);
@@ -456,9 +379,14 @@ final class Minz_ExtensionManager {
* This case is simpler than callOneToOne because hooks are called one by
* one, without any consideration of argument nor result.
*
* @param string $hook_name is the hook to call.
* @param string|Minz_HookType $hook is the hook to call.
*/
public static function callHookVoid(string $hook_name): void {
public static function callHookVoid(string|Minz_HookType $hook): void {
if (null === $hook = self::extractHook($hook)) {
return;
}
$hook_name = $hook->value;
foreach (self::$hook_list[$hook_name]['list'] ?? [] as $function) {
call_user_func($function);
}
@@ -468,9 +396,14 @@ final class Minz_ExtensionManager {
* Call a hook which takes no argument and returns nothing.
* Same as callHookVoid but only calls the first extension.
*
* @param string $hook_name is the hook to call.
* @param string|Minz_HookType $hook is the hook to call.
*/
public static function callHookUnique(string $hook_name): bool {
public static function callHookUnique(string|Minz_HookType $hook): bool {
if (null === $hook = self::extractHook($hook)) {
throw new \RuntimeException("The “{$hook}” does not exist!");
}
$hook_name = $hook->value;
foreach (self::$hook_list[$hook_name]['list'] ?? [] as $function) {
call_user_func($function);
return true;

View File

@@ -0,0 +1,9 @@
<?php
declare(strict_types=1);
enum Minz_HookSignature {
case NoneToNone;
case NoneToString;
case OneToOne;
case PassArguments;
}

68
lib/Minz/HookType.php Normal file
View File

@@ -0,0 +1,68 @@
<?php
declare(strict_types=1);
enum Minz_HookType: string {
case ApiMisc = 'api_misc'; // function(): void
case BeforeLoginBtn = 'before_login_btn'; // function(): string
case CheckUrlBeforeAdd = 'check_url_before_add'; // function(string $url) -> string | null
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 EntryAutoRead = 'entry_auto_read'; // function(FreshRSS_Entry $entry, string $why): void
case EntryAutoUnread = 'entry_auto_unread'; // function(FreshRSS_Entry $entry, string $why): void
case EntryBeforeDisplay = 'entry_before_display'; // function(FreshRSS_Entry $entry) -> FreshRSS_Entry | null
case EntryBeforeInsert = 'entry_before_insert'; // function(FreshRSS_Entry $entry) -> FreshRSS_Entry | null
case EntryBeforeAdd = 'entry_before_add'; // function(FreshRSS_Entry $entry) -> FreshRSS_Entry | null
case EntryBeforeUpdate = 'entry_before_update'; // function(FreshRSS_Entry $entry) -> FreshRSS_Entry | null
case FeedBeforeActualize = 'feed_before_actualize'; // function(FreshRSS_Feed $feed) -> FreshRSS_Feed | null
case FeedBeforeInsert = 'feed_before_insert'; // function(FreshRSS_Feed $feed) -> FreshRSS_Feed | null
case FreshrssInit = 'freshrss_init'; // function() -> none
case FreshrssUserMaintenance = 'freshrss_user_maintenance'; // function() -> none
case JsVars = 'js_vars'; // function($vars = array) -> array | null
case MenuAdminEntry = 'menu_admin_entry'; // function() -> string
case MenuConfigurationEntry = 'menu_configuration_entry'; // function() -> string
case MenuOtherEntry = 'menu_other_entry'; // function() -> string
case NavMenu = 'nav_menu'; // function() -> string
case NavReadingModes = 'nav_reading_modes'; // function($readingModes = array) -> array | null
case PostUpdate = 'post_update'; // function(none) -> none
case SimplepieAfterInit = 'simplepie_after_init'; // function(\SimplePie\SimplePie $simplePie, FreshRSS_Feed $feed, bool $result): void
case SimplepieBeforeInit = 'simplepie_before_init'; // function(\SimplePie\SimplePie $simplePie, FreshRSS_Feed $feed): void
case ViewModes = 'view_modes'; // function($viewModes = array) -> array | null
public function signature(): Minz_HookSignature {
switch ($this) {
case Minz_HookType::ApiMisc:
case Minz_HookType::FreshrssInit:
case Minz_HookType::FreshrssUserMaintenance:
case Minz_HookType::PostUpdate:
return Minz_HookSignature::NoneToNone;
case Minz_HookType::BeforeLoginBtn:
case Minz_HookType::MenuAdminEntry:
case Minz_HookType::MenuConfigurationEntry:
case Minz_HookType::MenuOtherEntry:
case Minz_HookType::NavMenu:
return Minz_HookSignature::NoneToString;
case Minz_HookType::CheckUrlBeforeAdd:
case Minz_HookType::EntryBeforeDisplay:
case Minz_HookType::EntryBeforeInsert:
case Minz_HookType::EntryBeforeAdd:
case Minz_HookType::EntryBeforeUpdate:
case Minz_HookType::FeedBeforeActualize:
case Minz_HookType::FeedBeforeInsert:
case Minz_HookType::JsVars:
case Minz_HookType::NavReadingModes:
case Minz_HookType::ViewModes:
return Minz_HookSignature::OneToOne;
case Minz_HookType::CustomFaviconBtnUrl:
case Minz_HookType::CustomFaviconHash:
case Minz_HookType::EntriesFavorite:
case Minz_HookType::EntryAutoRead:
case Minz_HookType::EntryAutoUnread:
case Minz_HookType::SimplepieAfterInit:
case Minz_HookType::SimplepieBeforeInit:
return Minz_HookSignature::PassArguments;
default:
throw new \RuntimeException('The hook is not configured!');
}
}
}

View File

@@ -519,7 +519,7 @@ final class FeverAPI
foreach ($entries as $item) {
/** @var FreshRSS_Entry|null $entry */
$entry = Minz_ExtensionManager::callHook('entry_before_display', $item);
$entry = Minz_ExtensionManager::callHook(Minz_HookType::EntryBeforeDisplay, $item);
if ($entry === null) {
continue;
}

View File

@@ -570,7 +570,7 @@ final class GReaderAPI {
$items = [];
foreach ($entries as $item) {
/** @var FreshRSS_Entry|null $entry */
$entry = Minz_ExtensionManager::callHook('entry_before_display', $item);
$entry = Minz_ExtensionManager::callHook(Minz_HookType::EntryBeforeDisplay, $item);
if ($entry === null) {
continue;
}

View File

@@ -63,6 +63,6 @@ Minz_ExtensionManager::init();
Minz_Translate::init();
if (!Minz_ExtensionManager::callHookUnique('api_misc')) {
if (!Minz_ExtensionManager::callHookUnique(Minz_HookType::ApiMisc)) {
serviceUnavailable();
}