diff options
| author | 2025-06-03 00:16:17 +0200 | |
|---|---|---|
| committer | 2025-06-03 00:16:17 +0200 | |
| commit | cc35094bb261cb3185def89d745317fa756560ee (patch) | |
| tree | 0d94bbd1dfe1013101dd96b8dbfa975d275ddf7b | |
| parent | 430d4e898e60ec1892d7191cf6e039aaf693822f (diff) | |
Add API endpoint for extensions (#7576)
* Add API endpoint for extensions
Useful for https://github.com/FreshRSS/FreshRSS/issues/7572
* Support PATH_INFO
Now also support being invoked like `/api/misc.php/Extension%20Name/`
* More documentation
| -rw-r--r-- | docs/en/admins/10_ServerConfig.md | 2 | ||||
| -rw-r--r-- | docs/en/developers/03_Backend/05_Extensions.md | 2 | ||||
| -rw-r--r-- | docs/fr/developers/03_Backend/05_Extensions.md | 2 | ||||
| -rw-r--r-- | docs/fr/users/01_Installation.md | 2 | ||||
| -rw-r--r-- | lib/Minz/ExtensionManager.php | 18 | ||||
| -rw-r--r-- | p/api/index.php | 14 | ||||
| -rw-r--r-- | p/api/misc.php | 68 |
7 files changed, 100 insertions, 8 deletions
diff --git a/docs/en/admins/10_ServerConfig.md b/docs/en/admins/10_ServerConfig.md index b7f57e61a..07ea147b6 100644 --- a/docs/en/admins/10_ServerConfig.md +++ b/docs/en/admins/10_ServerConfig.md @@ -98,7 +98,7 @@ server { fastcgi_pass unix:/var/run/php/php8.1-fpm.sock; fastcgi_split_path_info ^(.+\.php)(/.*)$; # By default, the variable PATH_INFO is not set under PHP-FPM - # But FreshRSS API greader.php need it. If you have a “Bad Request” error, double check this var! + # But FreshRSS APIs greader.php and misc.php need it. If you have a “Bad Request” error, double check this var! # NOTE: the separate $path_info variable is required. For more details, see: # https://trac.nginx.org/nginx/ticket/321 set $path_info $fastcgi_path_info; diff --git a/docs/en/developers/03_Backend/05_Extensions.md b/docs/en/developers/03_Backend/05_Extensions.md index aeb693cba..db9b03d60 100644 --- a/docs/en/developers/03_Backend/05_Extensions.md +++ b/docs/en/developers/03_Backend/05_Extensions.md @@ -164,6 +164,8 @@ final class HelloWorldExtension extends Minz_Extension The following events are available: +* `api_misc` (`function(): void`): to allow extensions to have own API endpoint + on `/api/misc.php/Extension%20Name/` or `/api/misc.php?ext=Extension%20Name`. * `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. * `entry_auto_read` (`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`}. * `entry_auto_unread` (`function(FreshRSS_Entry $entry, string $why): void`): Triggered when an entry is automatically marked as unread. The *why* parameter supports the rules {`updated_article`}. diff --git a/docs/fr/developers/03_Backend/05_Extensions.md b/docs/fr/developers/03_Backend/05_Extensions.md index 728d5c843..6996cee58 100644 --- a/docs/fr/developers/03_Backend/05_Extensions.md +++ b/docs/fr/developers/03_Backend/05_Extensions.md @@ -218,6 +218,8 @@ final class HelloWorldExtension extends Minz_Extension The following events are available: +* `api_misc` (`function(): void`) : permet aux extensions d’avoir leur propre point d’accès API + sur `/api/misc.php/Nom%20Extension/` ou `/api/misc.php?ext=Nom%20Extension`. * `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 diff --git a/docs/fr/users/01_Installation.md b/docs/fr/users/01_Installation.md index 85b75fea5..8236776fd 100644 --- a/docs/fr/users/01_Installation.md +++ b/docs/fr/users/01_Installation.md @@ -118,7 +118,7 @@ server { fastcgi_pass unix:/var/run/php/php8.1-fpm.sock; fastcgi_split_path_info ^(.+\.php)(/.*)$; # Par défaut la variable PATH_INFO n’est pas définie sous PHP-FPM - # or l’API FreshRSS greader.php en a besoin. Si vous avez un “Bad Request”, vérifiez bien cette dernière ! + # mais les APIs FreshRSS greader.php et misc.php en ont besoin. Si vous avez un “Bad Request”, vérifiez bien cette dernière ! # REMARQUE : l’utilisation de la variable $path_info est requis. Pour plus de détails, voir : # https://trac.nginx.org/nginx/ticket/321 set $path_info $fastcgi_path_info; diff --git a/lib/Minz/ExtensionManager.php b/lib/Minz/ExtensionManager.php index fea1f5879..b1d499392 100644 --- a/lib/Minz/ExtensionManager.php +++ b/lib/Minz/ExtensionManager.php @@ -22,6 +22,10 @@ final class Minz_ExtensionManager { * @var array<string,array{'list':array<callable>,'signature':'NoneToNone'|'NoneToString'|'OneToOne'|'PassArguments'}> */ private static array $hook_list = [ + 'api_misc' => [ // function(): void + 'list' => [], + 'signature' => 'NoneToNone', + ], 'check_url_before_add' => [ // function($url) -> Url | null 'list' => [], 'signature' => 'OneToOne', @@ -429,4 +433,18 @@ final class Minz_ExtensionManager { call_user_func($function); } } + + /** + * 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. + */ + public static function callHookUnique(string $hook_name): bool { + foreach (self::$hook_list[$hook_name]['list'] ?? [] as $function) { + call_user_func($function); + return true; + } + return false; + } } diff --git a/p/api/index.php b/p/api/index.php index 66c9e465c..fd9828080 100644 --- a/p/api/index.php +++ b/p/api/index.php @@ -28,9 +28,7 @@ echo json_encode([ <h2>Google Reader compatible API</h2> <dl> <dt>Your API address:</dt> -<dd><?php -echo Minz_Url::display('/api/greader.php', 'html', true); -?></dd> +<dd><?= Minz_Url::display('/api/greader.php', 'html', true) ?></dd> <dt>Google Reader API configuration test:</dt> <dd id="greaderOutput">?</dd> </dl> @@ -38,12 +36,16 @@ echo Minz_Url::display('/api/greader.php', 'html', true); <h2>Fever compatible API</h2> <dl> <dt>Your API address:</dt> -<dd><?php -echo Minz_Url::display('/api/fever.php', 'html', true); -?></dd> +<dd><?= Minz_Url::display('/api/fever.php', 'html', true) ?></dd> <dt>Fever API configuration test:</dt> <dd id="feverOutput">?</dd> </dl> +<h2>API for extensions</h2> +<dl> +<dt>Your API address:</dt> +<dd><?= Minz_Url::display('/api/misc.php/Extension%20name/', 'html', true) ?></dd> +</dl> + </body> </html> diff --git a/p/api/misc.php b/p/api/misc.php new file mode 100644 index 000000000..7724caa7a --- /dev/null +++ b/p/api/misc.php @@ -0,0 +1,68 @@ +<?php +declare(strict_types=1); +/** + * API entry point for FreshRSS extensions on + * `/api/misc.php/Extension%20name/` or `/api/misc.php?ext=Extension%20name` + */ + +require(__DIR__ . '/../../constants.php'); +require(LIB_PATH . '/lib_rss.php'); //Includes class autoloader + +function badRequest(): never { + header('HTTP/1.1 400 Bad Request'); + header('Content-Type: text/plain; charset=UTF-8'); + die('Bad Request!'); +} + +function serviceUnavailable(): never { + header('HTTP/1.1 503 Service Unavailable'); + header('Content-Type: text/plain; charset=UTF-8'); + die('Service Unavailable!'); +} + +$extensionName = is_string($_GET['ext'] ?? null) ? $_GET['ext'] : ''; + +if ($extensionName === '') { + $pathInfo = ''; + if (empty($_SERVER['PATH_INFO']) || !is_string($_SERVER['PATH_INFO'] ?? null)) { + if (!empty($_SERVER['ORIG_PATH_INFO']) && is_string($_SERVER['ORIG_PATH_INFO'])) { + // Compatibility https://php.net/reserved.variables.server + $pathInfo = $_SERVER['ORIG_PATH_INFO']; + } + } else { + $pathInfo = $_SERVER['PATH_INFO']; + } + $pathInfo = rawurldecode($pathInfo); + $pathInfo = preg_replace('%^(/api)?(/misc\.php)?%', '', $pathInfo); //Discard common errors + if ($pathInfo !== '' && is_string($pathInfo)) { + $pathInfos = explode('/', $pathInfo, limit: 3); + if (count($pathInfos) > 1) { + $extensionName = $pathInfos[1]; + } + } +} + +if ($extensionName === '') { + badRequest(); +} + +Minz_Session::init('FreshRSS', volatile: true); + +FreshRSS_Context::initSystem(); +if ( + !FreshRSS_Context::hasSystemConf() || + !FreshRSS_Context::systemConf()->api_enabled || + empty(FreshRSS_Context::systemConf()->extensions_enabled[$extensionName]) +) { + serviceUnavailable(); +} + +// Only enable the extension that is being called +FreshRSS_Context::systemConf()->extensions_enabled = [$extensionName => true]; +Minz_ExtensionManager::init(); + +Minz_Translate::init(); + +if (!Minz_ExtensionManager::callHookUnique('api_misc')) { + serviceUnavailable(); +} |
