فهرست منبع

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
Alexis Degrugillier 6 ماه پیش
والد
کامیت
72884813e1

+ 9 - 9
app/Controllers/feedController.php

@@ -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();

+ 4 - 4
app/Controllers/importExportController.php

@@ -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.

+ 1 - 1
app/Controllers/javascriptController.php

@@ -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);

+ 1 - 1
app/Controllers/updateController.php

@@ -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);

+ 1 - 1
app/FreshRSS.php

@@ -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 {

+ 2 - 2
app/Models/Entry.php

@@ -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);

+ 1 - 1
app/Models/EntryDAO.php

@@ -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();

+ 3 - 3
app/Models/Feed.php

@@ -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) {

+ 1 - 1
app/Models/FilterActionsTrait.php

@@ -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':

+ 1 - 1
app/Models/ViewMode.php

@@ -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) {

+ 1 - 1
app/Services/ImportService.php

@@ -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) {

+ 1 - 1
app/actualize_script.php

@@ -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;
 	});

+ 2 - 2
app/layout/aside_configure.phtml

@@ -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 } ?>

+ 3 - 3
app/layout/header.phtml

@@ -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>

+ 2 - 2
app/layout/nav_menu.phtml

@@ -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 ?>

+ 1 - 1
app/views/auth/formLogin.phtml

@@ -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">

+ 1 - 1
app/views/auth/register.phtml

@@ -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

+ 1 - 1
app/views/helpers/export/articles.phtml

@@ -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;

+ 1 - 1
app/views/helpers/feed/update.phtml

@@ -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">

+ 1 - 1
app/views/helpers/javascript_vars.phtml

@@ -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(),

+ 1 - 1
app/views/index/normal.phtml

@@ -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;
 		}

+ 1 - 1
app/views/index/reader.phtml

@@ -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;
 		}

+ 1 - 1
app/views/index/rss.phtml

@@ -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;
 		}

+ 2 - 2
cli/actualize-user.php

@@ -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();

+ 3 - 3
docs/en/developers/03_Backend/05_Extensions.md

@@ -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 doesn’t advertise it in the header can still be automatically supported.

+ 2 - 2
docs/fr/developers/03_Backend/05_Extensions.md

@@ -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 {

+ 67 - 134
lib/Minz/ExtensionManager.php

@@ -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;
 		}
 	}
 
+	/**
+	 * @param string|Minz_HookType $hook the hook or its name
+	 * @return Minz_HookType|null
+	 */
+	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 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 to call.
-	 * @param mixed ...$args additional parameters (for signature, please see self::$hook_list).
+	 * @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 $hook_name, ...$args) {
-		if (!isset(self::$hook_list[$hook_name])) {
+	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;

+ 9 - 0
lib/Minz/HookSignature.php

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

+ 68 - 0
lib/Minz/HookType.php

@@ -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!');
+		}
+	}
+}

+ 1 - 1
p/api/fever.php

@@ -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;
 			}

+ 1 - 1
p/api/greader.php

@@ -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;
 			}

+ 1 - 1
p/api/misc.php

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