Merge pull request #14640 from Art4/introduce-eventdispatcher

[EventDispatcher] Proof of concept
This commit is contained in:
Hypolite Petovan 2025-02-05 10:42:10 -05:00 committed by GitHub
commit a378a85c04
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 1229 additions and 73 deletions

View file

@ -71,9 +71,11 @@
"pragmarx/recovery": "^0.2", "pragmarx/recovery": "^0.2",
"psr/clock": "^1.0", "psr/clock": "^1.0",
"psr/container": "^2.0", "psr/container": "^2.0",
"psr/event-dispatcher": "^1.0",
"psr/log": "^1.1", "psr/log": "^1.1",
"seld/cli-prompt": "^1.0", "seld/cli-prompt": "^1.0",
"smarty/smarty": "^4", "smarty/smarty": "^4",
"symfony/event-dispatcher": "^5.4",
"textalk/websocket": "^1.6", "textalk/websocket": "^1.6",
"ua-parser/uap-php": "^3.9", "ua-parser/uap-php": "^3.9",
"xemlock/htmlpurifier-html5": "^0.1.11" "xemlock/htmlpurifier-html5": "^0.1.11"

296
composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "64436f375561718bb857e3e1b0e503c9", "content-hash": "8ee8f9186d271b65b83c2ddbd12c5c03",
"packages": [ "packages": [
{ {
"name": "asika/simple-console", "name": "asika/simple-console",
@ -3234,6 +3234,56 @@
], ],
"time": "2021-11-05T16:47:00+00:00" "time": "2021-11-05T16:47:00+00:00"
}, },
{
"name": "psr/event-dispatcher",
"version": "1.0.0",
"source": {
"type": "git",
"url": "https://github.com/php-fig/event-dispatcher.git",
"reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0",
"reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0",
"shasum": ""
},
"require": {
"php": ">=7.2.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\EventDispatcher\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "http://www.php-fig.org/"
}
],
"description": "Standard interfaces for event handling.",
"keywords": [
"events",
"psr",
"psr-14"
],
"support": {
"issues": "https://github.com/php-fig/event-dispatcher/issues",
"source": "https://github.com/php-fig/event-dispatcher/tree/1.0.0"
},
"time": "2019-01-08T18:20:26+00:00"
},
{ {
"name": "psr/http-client", "name": "psr/http-client",
"version": "1.0.3", "version": "1.0.3",
@ -3709,6 +3759,170 @@
], ],
"time": "2022-01-02T09:53:40+00:00" "time": "2022-01-02T09:53:40+00:00"
}, },
{
"name": "symfony/event-dispatcher",
"version": "v5.4.45",
"source": {
"type": "git",
"url": "https://github.com/symfony/event-dispatcher.git",
"reference": "72982eb416f61003e9bb6e91f8b3213600dcf9e9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/72982eb416f61003e9bb6e91f8b3213600dcf9e9",
"reference": "72982eb416f61003e9bb6e91f8b3213600dcf9e9",
"shasum": ""
},
"require": {
"php": ">=7.2.5",
"symfony/deprecation-contracts": "^2.1|^3",
"symfony/event-dispatcher-contracts": "^2|^3",
"symfony/polyfill-php80": "^1.16"
},
"conflict": {
"symfony/dependency-injection": "<4.4"
},
"provide": {
"psr/event-dispatcher-implementation": "1.0",
"symfony/event-dispatcher-implementation": "2.0"
},
"require-dev": {
"psr/log": "^1|^2|^3",
"symfony/config": "^4.4|^5.0|^6.0",
"symfony/dependency-injection": "^4.4|^5.0|^6.0",
"symfony/error-handler": "^4.4|^5.0|^6.0",
"symfony/expression-language": "^4.4|^5.0|^6.0",
"symfony/http-foundation": "^4.4|^5.0|^6.0",
"symfony/service-contracts": "^1.1|^2|^3",
"symfony/stopwatch": "^4.4|^5.0|^6.0"
},
"suggest": {
"symfony/dependency-injection": "",
"symfony/http-kernel": ""
},
"type": "library",
"autoload": {
"psr-4": {
"Symfony\\Component\\EventDispatcher\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/event-dispatcher/tree/v5.4.45"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2024-09-25T14:11:13+00:00"
},
{
"name": "symfony/event-dispatcher-contracts",
"version": "v2.5.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/event-dispatcher-contracts.git",
"reference": "e0fe3d79b516eb75126ac6fa4cbf19b79b08c99f"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/e0fe3d79b516eb75126ac6fa4cbf19b79b08c99f",
"reference": "e0fe3d79b516eb75126ac6fa4cbf19b79b08c99f",
"shasum": ""
},
"require": {
"php": ">=7.2.5",
"psr/event-dispatcher": "^1"
},
"suggest": {
"symfony/event-dispatcher-implementation": ""
},
"type": "library",
"extra": {
"thanks": {
"url": "https://github.com/symfony/contracts",
"name": "symfony/contracts"
},
"branch-alias": {
"dev-main": "2.5-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Contracts\\EventDispatcher\\": ""
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Generic abstractions related to dispatching event",
"homepage": "https://symfony.com",
"keywords": [
"abstractions",
"contracts",
"decoupling",
"interfaces",
"interoperability",
"standards"
],
"support": {
"source": "https://github.com/symfony/event-dispatcher-contracts/tree/v2.5.4"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2024-09-25T14:11:13+00:00"
},
{ {
"name": "symfony/polyfill-php56", "name": "symfony/polyfill-php56",
"version": "v1.20.0", "version": "v1.20.0",
@ -3774,6 +3988,86 @@
], ],
"time": "2020-10-23T14:02:19+00:00" "time": "2020-10-23T14:02:19+00:00"
}, },
{
"name": "symfony/polyfill-php80",
"version": "v1.31.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php80.git",
"reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/60328e362d4c2c802a54fcbf04f9d3fb892b4cf8",
"reference": "60328e362d4c2c802a54fcbf04f9d3fb892b4cf8",
"shasum": ""
},
"require": {
"php": ">=7.2"
},
"type": "library",
"extra": {
"thanks": {
"url": "https://github.com/symfony/polyfill",
"name": "symfony/polyfill"
}
},
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\Php80\\": ""
},
"classmap": [
"Resources/stubs"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Ion Bazan",
"email": "ion.bazan@gmail.com"
},
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"polyfill",
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-php80/tree/v1.31.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2024-09-09T11:45:10+00:00"
},
{ {
"name": "textalk/websocket", "name": "textalk/websocket",
"version": "1.6.3", "version": "1.6.3",

View file

@ -20,13 +20,10 @@ use Friendica\Content\Nav;
use Friendica\Core\Addon\Capability\ICanLoadAddons; use Friendica\Core\Addon\Capability\ICanLoadAddons;
use Friendica\Core\Config\Factory\Config; use Friendica\Core\Config\Factory\Config;
use Friendica\Core\Container; use Friendica\Core\Container;
use Friendica\Core\Hooks\HookEventBridge;
use Friendica\Core\Logger\LoggerManager; use Friendica\Core\Logger\LoggerManager;
use Friendica\Core\Renderer; use Friendica\Core\Renderer;
use Friendica\Core\Session\Capability\IHandleUserSessions; use Friendica\Core\Session\Capability\IHandleUserSessions;
use Friendica\Database\Definition\DbaDefinition;
use Friendica\Database\Definition\ViewDefinition;
use Friendica\Module\Maintenance;
use Friendica\Security\Authentication;
use Friendica\Core\Config\Capability\IManageConfigValues; use Friendica\Core\Config\Capability\IManageConfigValues;
use Friendica\Core\DiceContainer; use Friendica\Core\DiceContainer;
use Friendica\Core\L10n; use Friendica\Core\L10n;
@ -35,9 +32,15 @@ use Friendica\Core\Logger\Handler\ErrorHandler;
use Friendica\Core\PConfig\Capability\IManagePersonalConfigValues; use Friendica\Core\PConfig\Capability\IManagePersonalConfigValues;
use Friendica\Core\System; use Friendica\Core\System;
use Friendica\Core\Update; use Friendica\Core\Update;
use Friendica\Database\Definition\DbaDefinition;
use Friendica\Database\Definition\ViewDefinition;
use Friendica\Event\ConfigLoadedEvent;
use Friendica\Event\Event;
use Friendica\Module\Maintenance;
use Friendica\Module\Special\HTTPException as ModuleHTTPException; use Friendica\Module\Special\HTTPException as ModuleHTTPException;
use Friendica\Network\HTTPException; use Friendica\Network\HTTPException;
use Friendica\Protocol\ATProtocol\DID; use Friendica\Protocol\ATProtocol\DID;
use Friendica\Security\Authentication;
use Friendica\Security\ExAuth; use Friendica\Security\ExAuth;
use Friendica\Security\OpenWebAuth; use Friendica\Security\OpenWebAuth;
use Friendica\Util\BasePath; use Friendica\Util\BasePath;
@ -45,6 +48,7 @@ use Friendica\Util\DateTimeFormat;
use Friendica\Util\HTTPInputData; use Friendica\Util\HTTPInputData;
use Friendica\Util\HTTPSignature; use Friendica\Util\HTTPSignature;
use Friendica\Util\Profiler; use Friendica\Util\Profiler;
use Psr\EventDispatcher\EventDispatcherInterface;
use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Message\ServerRequestInterface;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
@ -153,6 +157,8 @@ class App
$this->registerErrorHandler(); $this->registerErrorHandler();
$this->registerEventDispatcher();
$this->requestId = $this->container->create(Request::class)->getRequestId(); $this->requestId = $this->container->create(Request::class)->getRequestId();
$this->auth = $this->container->create(Authentication::class); $this->auth = $this->container->create(Authentication::class);
$this->config = $this->container->create(IManageConfigValues::class); $this->config = $this->container->create(IManageConfigValues::class);
@ -172,12 +178,14 @@ class App
$this->mode, $this->mode,
$this->config, $this->config,
$this->profiler, $this->profiler,
$this->container->create(EventDispatcherInterface::class),
$this->appHelper, $this->appHelper,
); );
$this->registerTemplateEngine(); $this->registerTemplateEngine();
$this->runFrontend( $this->runFrontend(
$this->container->create(EventDispatcherInterface::class),
$this->container->create(IManagePersonalConfigValues::class), $this->container->create(IManagePersonalConfigValues::class),
$this->container->create(Page::class), $this->container->create(Page::class),
$this->container->create(Nav::class), $this->container->create(Nav::class),
@ -202,6 +210,8 @@ class App
$this->registerErrorHandler(); $this->registerErrorHandler();
$this->registerEventDispatcher();
$this->load( $this->load(
$serverParams, $serverParams,
$this->container->create(DbaDefinition::class), $this->container->create(DbaDefinition::class),
@ -209,6 +219,7 @@ class App
$this->container->create(Mode::class), $this->container->create(Mode::class),
$this->container->create(IManageConfigValues::class), $this->container->create(IManageConfigValues::class),
$this->container->create(Profiler::class), $this->container->create(Profiler::class),
$this->container->create(EventDispatcherInterface::class),
$this->container->create(AppHelper::class), $this->container->create(AppHelper::class),
); );
@ -230,6 +241,8 @@ class App
$this->registerErrorHandler(); $this->registerErrorHandler();
$this->registerEventDispatcher();
$this->load( $this->load(
$serverParams, $serverParams,
$this->container->create(DbaDefinition::class), $this->container->create(DbaDefinition::class),
@ -237,6 +250,7 @@ class App
$this->container->create(Mode::class), $this->container->create(Mode::class),
$this->container->create(IManageConfigValues::class), $this->container->create(IManageConfigValues::class),
$this->container->create(Profiler::class), $this->container->create(Profiler::class),
$this->container->create(EventDispatcherInterface::class),
$this->container->create(AppHelper::class), $this->container->create(AppHelper::class),
); );
@ -301,6 +315,16 @@ class App
ErrorHandler::register($this->container->create(LoggerInterface::class)); ErrorHandler::register($this->container->create(LoggerInterface::class));
} }
private function registerEventDispatcher(): void
{
/** @var \Friendica\Event\EventDispatcher */
$eventDispatcher = $this->container->create(EventDispatcherInterface::class);
foreach (HookEventBridge::getStaticSubscribedEvents() as $eventName => $methodName) {
$eventDispatcher->addListener($eventName, [HookEventBridge::class, $methodName]);
}
}
private function registerTemplateEngine(): void private function registerTemplateEngine(): void
{ {
Renderer::registerTemplateEngine('Friendica\Render\FriendicaSmartyEngine'); Renderer::registerTemplateEngine('Friendica\Render\FriendicaSmartyEngine');
@ -316,6 +340,7 @@ class App
Mode $mode, Mode $mode,
IManageConfigValues $config, IManageConfigValues $config,
Profiler $profiler, Profiler $profiler,
EventDispatcherInterface $eventDispatcher,
AppHelper $appHelper AppHelper $appHelper
): void { ): void {
if ($config->get('system', 'ini_max_execution_time') !== false) { if ($config->get('system', 'ini_max_execution_time') !== false) {
@ -339,7 +364,8 @@ class App
if ($mode->has(Mode::DBAVAILABLE)) { if ($mode->has(Mode::DBAVAILABLE)) {
Core\Hook::loadHooks(); Core\Hook::loadHooks();
$loader = (new Config())->createConfigFileManager($appHelper->getBasePath(), $serverParams); $loader = (new Config())->createConfigFileManager($appHelper->getBasePath(), $serverParams);
Core\Hook::callAll('load_config', $loader);
$eventDispatcher->dispatch(new ConfigLoadedEvent(ConfigLoadedEvent::CONFIG_LOADED, $loader));
// Hooks are now working, reload the whole definitions with hook enabled // Hooks are now working, reload the whole definitions with hook enabled
$dbaDefinition->load(true); $dbaDefinition->load(true);
@ -385,6 +411,7 @@ class App
* @throws \ImagickException * @throws \ImagickException
*/ */
private function runFrontend( private function runFrontend(
EventDispatcherInterface $eventDispatcher,
IManagePersonalConfigValues $pconfig, IManagePersonalConfigValues $pconfig,
Page $page, Page $page,
Nav $nav, Nav $nav,
@ -424,7 +451,8 @@ class App
$serverVars['REQUEST_METHOD'] === 'GET') { $serverVars['REQUEST_METHOD'] === 'GET') {
System::externalRedirect($this->baseURL . '/' . $this->args->getQueryString()); System::externalRedirect($this->baseURL . '/' . $this->args->getQueryString());
} }
Core\Hook::callAll('init_1');
$eventDispatcher->dispatch(new Event(Event::INIT));
} }
DID::routeRequest($this->args->getCommand(), $serverVars); DID::routeRequest($this->args->getCommand(), $serverVars);

View file

@ -14,7 +14,6 @@ use Friendica\App;
use Friendica\AppHelper; use Friendica\AppHelper;
use Friendica\Content\Nav; use Friendica\Content\Nav;
use Friendica\Core\Config\Capability\IManageConfigValues; use Friendica\Core\Config\Capability\IManageConfigValues;
use Friendica\Core\Hook;
use Friendica\Core\L10n; use Friendica\Core\L10n;
use Friendica\Core\PConfig\Capability\IManagePersonalConfigValues; use Friendica\Core\PConfig\Capability\IManagePersonalConfigValues;
use Friendica\Core\Renderer; use Friendica\Core\Renderer;
@ -22,12 +21,14 @@ use Friendica\Core\Session\Model\UserSession;
use Friendica\Core\System; use Friendica\Core\System;
use Friendica\Core\Theme; use Friendica\Core\Theme;
use Friendica\DI; use Friendica\DI;
use Friendica\Event\HtmlFilterEvent;
use Friendica\Network\HTTPException; use Friendica\Network\HTTPException;
use Friendica\Util\Images; use Friendica\Util\Images;
use Friendica\Util\Network; use Friendica\Util\Network;
use Friendica\Util\Profiler; use Friendica\Util\Profiler;
use Friendica\Util\Strings; use Friendica\Util\Strings;
use GuzzleHttp\Psr7\Utils; use GuzzleHttp\Psr7\Utils;
use Psr\EventDispatcher\EventDispatcherInterface;
use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ResponseInterface;
/** /**
@ -70,6 +71,8 @@ class Page implements ArrayAccess
*/ */
private $basePath; private $basePath;
private EventDispatcherInterface $eventDispatcher;
private $timestamp = 0; private $timestamp = 0;
private $method = ''; private $method = '';
private $module = ''; private $module = '';
@ -78,10 +81,11 @@ class Page implements ArrayAccess
/** /**
* @param string $basepath The Page basepath * @param string $basepath The Page basepath
*/ */
public function __construct(string $basepath) public function __construct(string $basepath, EventDispatcherInterface $eventDispatcher)
{ {
$this->timestamp = microtime(true); $this->timestamp = microtime(true);
$this->basePath = $basepath; $this->basePath = $basepath;
$this->eventDispatcher = $eventDispatcher;
} }
public function setLogging(string $method, string $module, string $command) public function setLogging(string $method, string $module, string $command)
@ -229,7 +233,9 @@ class Page implements ArrayAccess
$touch_icon = 'images/friendica-192.png'; $touch_icon = 'images/friendica-192.png';
} }
Hook::callAll('head', $this->page['htmlhead']); $this->page['htmlhead'] = $this->eventDispatcher->dispatch(
new HtmlFilterEvent(HtmlFilterEvent::HEAD, $this->page['htmlhead'])
)->getHtml();
$tpl = Renderer::getMarkupTemplate('head.tpl'); $tpl = Renderer::getMarkupTemplate('head.tpl');
/* put the head template at the beginning of page['htmlhead'] /* put the head template at the beginning of page['htmlhead']
@ -351,7 +357,9 @@ class Page implements ArrayAccess
]); ]);
} }
Hook::callAll('footer', $this->page['footer']); $this->page['footer'] = $this->eventDispatcher->dispatch(
new HtmlFilterEvent(HtmlFilterEvent::FOOTER, $this->page['footer'])
)->getHtml();
$tpl = Renderer::getMarkupTemplate('footer.tpl'); $tpl = Renderer::getMarkupTemplate('footer.tpl');
$this->page['footer'] = Renderer::replaceMacros($tpl, [ $this->page['footer'] = Renderer::replaceMacros($tpl, [
@ -376,7 +384,9 @@ class Page implements ArrayAccess
{ {
// initialise content region // initialise content region
if ($mode->isNormal()) { if ($mode->isNormal()) {
Hook::callAll('page_content_top', $this->page['content']); $this->page['content'] = $this->eventDispatcher->dispatch(
new HtmlFilterEvent(HtmlFilterEvent::PAGE_CONTENT_TOP, $this->page['content'])
)->getHtml();
} }
$this->page['content'] .= (string)$response->getBody(); $this->page['content'] .= (string)$response->getBody();
@ -474,7 +484,9 @@ class Page implements ArrayAccess
$profiler->set(microtime(true) - $timestamp, 'aftermath'); $profiler->set(microtime(true) - $timestamp, 'aftermath');
if (!$mode->isAjax()) { if (!$mode->isAjax()) {
Hook::callAll('page_end', $this->page['content']); $this->page['content'] = $this->eventDispatcher->dispatch(
new HtmlFilterEvent(HtmlFilterEvent::PAGE_END, $this->page['content'])
)->getHtml();
} }
// Add the navigation (menu) template // Add the navigation (menu) template

View file

@ -7,8 +7,8 @@
namespace Friendica\Content; namespace Friendica\Content;
use Friendica\Core\Hook;
use Friendica\DI; use Friendica\DI;
use Friendica\Event\ArrayFilterEvent;
class Feature class Feature
{ {
@ -41,15 +41,23 @@ class Feature
*/ */
public static function isEnabled(int $uid, $feature): bool public static function isEnabled(int $uid, $feature): bool
{ {
if (!DI::config()->get('feature_lock', $feature, false)) { $config = DI::config();
$enabled = DI::config()->get('feature', $feature) ?? self::getDefault($feature); $pConfig = DI::pConfig();
$enabled = DI::pConfig()->get($uid, 'feature', $feature) ?? $enabled; $eventDispatcher = DI::eventDispatcher();
if (!$config->get('feature_lock', $feature, false)) {
$enabled = $config->get('feature', $feature) ?? self::getDefault($feature);
$enabled = $pConfig->get($uid, 'feature', $feature) ?? $enabled;
} else { } else {
$enabled = true; $enabled = true;
} }
$arr = ['uid' => $uid, 'feature' => $feature, 'enabled' => $enabled]; $arr = ['uid' => $uid, 'feature' => $feature, 'enabled' => $enabled];
Hook::callAll('isEnabled', $arr);
$arr = $eventDispatcher->dispatch(
new ArrayFilterEvent(ArrayFilterEvent::FEATURE_ENABLED, $arr)
)->getArray();
return (bool)$arr['enabled']; return (bool)$arr['enabled'];
} }
@ -86,55 +94,58 @@ class Feature
*/ */
public static function get($filtered = true) public static function get($filtered = true)
{ {
$arr = [ $l10n = DI::l10n();
$config = DI::config();
$eventDispatcher = DI::eventDispatcher();
$arr = [
// General // General
'general' => [ 'general' => [
DI::l10n()->t('General Features'), $l10n->t('General Features'),
//array('expire', DI::l10n()->t('Content Expiration'), DI::l10n()->t('Remove old posts/comments after a period of time')), //array('expire', $l10n->t('Content Expiration'), $l10n->t('Remove old posts/comments after a period of time')),
[self::PHOTO_LOCATION, DI::l10n()->t('Photo Location'), DI::l10n()->t("Photo metadata is normally stripped. This extracts the location \x28if present\x29 prior to stripping metadata and links it to a map."), false, DI::config()->get('feature_lock', self::PHOTO_LOCATION, false)], [self::PHOTO_LOCATION, $l10n->t('Photo Location'), $l10n->t("Photo metadata is normally stripped. This extracts the location \x28if present\x29 prior to stripping metadata and links it to a map."), false, $config->get('feature_lock', self::PHOTO_LOCATION, false)],
[self::COMMUNITY, DI::l10n()->t('Display the community in the navigation'), DI::l10n()->t('If enabled, the community can be accessed via the navigation menu. Independent from this setting, the community timelines can always be accessed via the channels.'), true, DI::config()->get('feature_lock', self::COMMUNITY, false)], [self::COMMUNITY, $l10n->t('Display the community in the navigation'), $l10n->t('If enabled, the community can be accessed via the navigation menu. Independent from this setting, the community timelines can always be accessed via the channels.'), true, $config->get('feature_lock', self::COMMUNITY, false)],
], ],
// Post composition // Post composition
'composition' => [ 'composition' => [
DI::l10n()->t('Post Composition Features'), $l10n->t('Post Composition Features'),
[self::EXPLICIT_MENTIONS, DI::l10n()->t('Explicit Mentions'), DI::l10n()->t('Add explicit mentions to comment box for manual control over who gets mentioned in replies.'), false, DI::config()->get('feature_lock', Feature::EXPLICIT_MENTIONS, false)], [self::EXPLICIT_MENTIONS, $l10n->t('Explicit Mentions'), $l10n->t('Add explicit mentions to comment box for manual control over who gets mentioned in replies.'), false, $config->get('feature_lock', Feature::EXPLICIT_MENTIONS, false)],
[self::ADD_ABSTRACT, DI::l10n()->t('Add an abstract from ActivityPub content warnings'), DI::l10n()->t('Add an abstract when commenting on ActivityPub posts with a content warning. Abstracts are displayed as content warning on systems like Mastodon or Pleroma.'), false, DI::config()->get('feature_lock', self::ADD_ABSTRACT, false)], [self::ADD_ABSTRACT, $l10n->t('Add an abstract from ActivityPub content warnings'), $l10n->t('Add an abstract when commenting on ActivityPub posts with a content warning. Abstracts are displayed as content warning on systems like Mastodon or Pleroma.'), false, $config->get('feature_lock', self::ADD_ABSTRACT, false)],
], ],
// Item tools // Item tools
'tools' => [ 'tools' => [
DI::l10n()->t('Post/Comment Tools'), $l10n->t('Post/Comment Tools'),
[self::CATEGORIES, DI::l10n()->t('Post Categories'), DI::l10n()->t('Add categories to your posts'), false, DI::config()->get('feature_lock', self::CATEGORIES, false)], [self::CATEGORIES, $l10n->t('Post Categories'), $l10n->t('Add categories to your posts'), false, $config->get('feature_lock', self::CATEGORIES, false)],
], ],
// Widget visibility on the network stream // Widget visibility on the network stream
'network' => [ 'network' => [
DI::l10n()->t('Network Widgets'), $l10n->t('Network Widgets'),
[self::CIRCLES, DI::l10n()->t('Circles'), DI::l10n()->t('Display posts that have been created by accounts of the selected circle.'), true, DI::config()->get('feature_lock', self::CIRCLES, false)], [self::CIRCLES, $l10n->t('Circles'), $l10n->t('Display posts that have been created by accounts of the selected circle.'), true, $config->get('feature_lock', self::CIRCLES, false)],
[self::GROUPS, DI::l10n()->t('Groups'), DI::l10n()->t('Display posts that have been distributed by the selected group.'), true, DI::config()->get('feature_lock', self::GROUPS, false)], [self::GROUPS, $l10n->t('Groups'), $l10n->t('Display posts that have been distributed by the selected group.'), true, $config->get('feature_lock', self::GROUPS, false)],
[self::ARCHIVE, DI::l10n()->t('Archives'), DI::l10n()->t('Display an archive where posts can be selected by month and year.'), true, DI::config()->get('feature_lock', self::ARCHIVE, false)], [self::ARCHIVE, $l10n->t('Archives'), $l10n->t('Display an archive where posts can be selected by month and year.'), true, $config->get('feature_lock', self::ARCHIVE, false)],
[self::NETWORKS, DI::l10n()->t('Protocols'), DI::l10n()->t('Display posts with the selected protocols.'), true, DI::config()->get('feature_lock', self::NETWORKS, false)], [self::NETWORKS, $l10n->t('Protocols'), $l10n->t('Display posts with the selected protocols.'), true, $config->get('feature_lock', self::NETWORKS, false)],
[self::ACCOUNTS, DI::l10n()->t('Account Types'), DI::l10n()->t('Display posts done by accounts with the selected account type.'), true, DI::config()->get('feature_lock', self::ACCOUNTS, false)], [self::ACCOUNTS, $l10n->t('Account Types'), $l10n->t('Display posts done by accounts with the selected account type.'), true, $config->get('feature_lock', self::ACCOUNTS, false)],
[self::CHANNELS, DI::l10n()->t('Channels'), DI::l10n()->t('Display posts in the system channels and user defined channels.'), true, DI::config()->get('feature_lock', self::CHANNELS, false)], [self::CHANNELS, $l10n->t('Channels'), $l10n->t('Display posts in the system channels and user defined channels.'), true, $config->get('feature_lock', self::CHANNELS, false)],
[self::SEARCHES, DI::l10n()->t('Saved Searches'), DI::l10n()->t('Display posts that contain subscribed hashtags.'), true, DI::config()->get('feature_lock', self::SEARCHES, false)], [self::SEARCHES, $l10n->t('Saved Searches'), $l10n->t('Display posts that contain subscribed hashtags.'), true, $config->get('feature_lock', self::SEARCHES, false)],
[self::FOLDERS, DI::l10n()->t('Saved Folders'), DI::l10n()->t('Display a list of folders in which posts are stored.'), true, DI::config()->get('feature_lock', self::FOLDERS, false)], [self::FOLDERS, $l10n->t('Saved Folders'), $l10n->t('Display a list of folders in which posts are stored.'), true, $config->get('feature_lock', self::FOLDERS, false)],
[self::NOSHARER, DI::l10n()->t('Own Contacts'), DI::l10n()->t('Include or exclude posts from subscribed accounts. This widget is not visible on all channels.'), true, DI::config()->get('feature_lock', self::NOSHARER, false)], [self::NOSHARER, $l10n->t('Own Contacts'), $l10n->t('Include or exclude posts from subscribed accounts. This widget is not visible on all channels.'), true, $config->get('feature_lock', self::NOSHARER, false)],
[self::TRENDING_TAGS, DI::l10n()->t('Trending Tags'), DI::l10n()->t('Display a list of the most popular tags in recent public posts.'), false, DI::config()->get('feature_lock', self::TRENDING_TAGS, false)], [self::TRENDING_TAGS, $l10n->t('Trending Tags'), $l10n->t('Display a list of the most popular tags in recent public posts.'), false, $config->get('feature_lock', self::TRENDING_TAGS, false)],
], ],
// Advanced Profile Settings // Advanced Profile Settings
'advanced_profile' => [ 'advanced_profile' => [
DI::l10n()->t('Advanced Profile Settings'), $l10n->t('Advanced Profile Settings'),
[self::TAGCLOUD, DI::l10n()->t('Tag Cloud'), DI::l10n()->t('Provide a personal tag cloud on your profile page'), false, DI::config()->get('feature_lock', self::TAGCLOUD, false)], [self::TAGCLOUD, $l10n->t('Tag Cloud'), $l10n->t('Provide a personal tag cloud on your profile page'), false, $config->get('feature_lock', self::TAGCLOUD, false)],
[self::MEMBER_SINCE, DI::l10n()->t('Display Membership Date'), DI::l10n()->t('Display membership date in profile'), false, DI::config()->get('feature_lock', self::MEMBER_SINCE, false)], [self::MEMBER_SINCE, $l10n->t('Display Membership Date'), $l10n->t('Display membership date in profile'), false, $config->get('feature_lock', self::MEMBER_SINCE, false)],
], ],
//Advanced Calendar Settings //Advanced Calendar Settings
'advanced_calendar' => [ 'advanced_calendar' => [
DI::l10n()->t('Advanced Calendar Settings'), $l10n->t('Advanced Calendar Settings'),
[self::PUBLIC_CALENDAR, DI::l10n()->t('Allow anonymous access to your calendar'), DI::l10n()->t('Allows anonymous visitors to consult your calendar and your public events. Contact birthday events are private to you.'), false, DI::config()->get('feature_lock', self::PUBLIC_CALENDAR, false)], [self::PUBLIC_CALENDAR, $l10n->t('Allow anonymous access to your calendar'), $l10n->t('Allows anonymous visitors to consult your calendar and your public events. Contact birthday events are private to you.'), false, $config->get('feature_lock', self::PUBLIC_CALENDAR, false)],
] ]
]; ];
@ -144,7 +155,7 @@ class Feature
foreach ($arr as $k => $x) { foreach ($arr as $k => $x) {
$has_items = false; $has_items = false;
$kquantity = count($arr[$k]); $kquantity = count($arr[$k]);
for ($y = 0; $y < $kquantity; $y ++) { for ($y = 0; $y < $kquantity; $y++) {
if (is_array($arr[$k][$y])) { if (is_array($arr[$k][$y])) {
if ($arr[$k][$y][4] === false) { if ($arr[$k][$y][4] === false) {
$has_items = true; $has_items = true;
@ -159,7 +170,10 @@ class Feature
} }
} }
Hook::callAll('get', $arr); $arr = $eventDispatcher->dispatch(
new ArrayFilterEvent(ArrayFilterEvent::FEATURE_GET, $arr)
)->getArray();
return $arr; return $arr;
} }
} }

View file

@ -10,11 +10,12 @@ namespace Friendica\Content;
use Friendica\App\BaseURL; use Friendica\App\BaseURL;
use Friendica\App\Router; use Friendica\App\Router;
use Friendica\Core\Config\Capability\IManageConfigValues; use Friendica\Core\Config\Capability\IManageConfigValues;
use Friendica\Core\Hook;
use Friendica\Core\L10n; use Friendica\Core\L10n;
use Friendica\Core\Renderer; use Friendica\Core\Renderer;
use Friendica\Core\Session\Capability\IHandleUserSessions; use Friendica\Core\Session\Capability\IHandleUserSessions;
use Friendica\Database\Database; use Friendica\Database\Database;
use Friendica\Event\ArrayFilterEvent;
use Friendica\Event\HtmlFilterEvent;
use Friendica\Model\Contact; use Friendica\Model\Contact;
use Friendica\Model\User; use Friendica\Model\User;
use Friendica\Module\Conversation\Community; use Friendica\Module\Conversation\Community;
@ -22,6 +23,7 @@ use Friendica\Module\Home;
use Friendica\Module\Security\Login; use Friendica\Module\Security\Login;
use Friendica\Network\HTTPException; use Friendica\Network\HTTPException;
use Friendica\Security\OpenWebAuth; use Friendica\Security\OpenWebAuth;
use Psr\EventDispatcher\EventDispatcherInterface;
class Nav class Nav
{ {
@ -63,7 +65,9 @@ class Nav
/** @var Router */ /** @var Router */
private $router; private $router;
public function __construct(BaseURL $baseUrl, L10n $l10n, IHandleUserSessions $session, Database $database, IManageConfigValues $config, Router $router) private EventDispatcherInterface $eventDispatcher;
public function __construct(BaseURL $baseUrl, L10n $l10n, IHandleUserSessions $session, Database $database, IManageConfigValues $config, Router $router, EventDispatcherInterface $eventDispatcher)
{ {
$this->baseUrl = $baseUrl; $this->baseUrl = $baseUrl;
$this->l10n = $l10n; $this->l10n = $l10n;
@ -71,6 +75,7 @@ class Nav
$this->database = $database; $this->database = $database;
$this->config = $config; $this->config = $config;
$this->router = $router; $this->router = $router;
$this->eventDispatcher = $eventDispatcher;
} }
/** /**
@ -114,7 +119,9 @@ class Nav
'$search_hint' => $this->l10n->t('@name, !group, #tags, content') '$search_hint' => $this->l10n->t('@name, !group, #tags, content')
]); ]);
Hook::callAll('page_header', $nav); $nav = $this->eventDispatcher->dispatch(
new HtmlFilterEvent(HtmlFilterEvent::PAGE_HEADER, $nav)
)->getHtml();
return $nav; return $nav;
} }
@ -151,9 +158,11 @@ class Nav
) { ) {
$arr = ['app_menu' => $appMenu]; $arr = ['app_menu' => $appMenu];
Hook::callAll('app_menu', $arr); $arr = $this->eventDispatcher->dispatch(
new ArrayFilterEvent(ArrayFilterEvent::APP_MENU, $arr)
)->getArray();
$appMenu = $arr['app_menu']; $appMenu = $arr['app_menu'] ?? [];
} }
return $appMenu; return $appMenu;
@ -337,7 +346,9 @@ class Nav
'userinfo' => $userinfo, 'userinfo' => $userinfo,
]; ];
Hook::callAll('nav_info', $nav_info); $nav_info = $this->eventDispatcher->dispatch(
new ArrayFilterEvent(ArrayFilterEvent::NAV_INFO, $nav_info)
)->getArray();
return $nav_info; return $nav_info;
} }

View file

@ -0,0 +1,113 @@
<?php
// Copyright (C) 2010-2024, the Friendica project
// SPDX-FileCopyrightText: 2010-2024 the Friendica project
//
// SPDX-License-Identifier: AGPL-3.0-or-later
declare(strict_types=1);
namespace Friendica\Core\Hooks;
use Friendica\Core\Hook;
use Friendica\Event\ArrayFilterEvent;
use Friendica\Event\ConfigLoadedEvent;
use Friendica\Event\Event;
use Friendica\Event\HtmlFilterEvent;
use Friendica\Event\NamedEvent;
/**
* Bridge between the EventDispatcher and the Hook class.
*
* @internal Provides BC
*/
final class HookEventBridge
{
/**
* @internal This allows us to mock the Hook call in tests.
*
* @var \Closure|null
*/
private static $mockedCallHook = null;
/**
* This maps the new event names to the legacy Hook names.
*/
private static array $eventMapper = [
Event::INIT => 'init_1',
ConfigLoadedEvent::CONFIG_LOADED => 'load_config',
ArrayFilterEvent::APP_MENU => 'app_menu',
ArrayFilterEvent::NAV_INFO => 'nav_info',
ArrayFilterEvent::FEATURE_ENABLED => 'isEnabled',
ArrayFilterEvent::FEATURE_GET => 'get',
HtmlFilterEvent::HEAD => 'head',
HtmlFilterEvent::FOOTER => 'footer',
HtmlFilterEvent::PAGE_HEADER => 'page_header',
HtmlFilterEvent::PAGE_CONTENT_TOP => 'page_content_top',
HtmlFilterEvent::PAGE_END => 'page_end',
];
/**
* @return array<string, string>
*/
public static function getStaticSubscribedEvents(): array
{
return [
Event::INIT => 'onNamedEvent',
ConfigLoadedEvent::CONFIG_LOADED => 'onConfigLoadedEvent',
ArrayFilterEvent::APP_MENU => 'onArrayFilterEvent',
ArrayFilterEvent::NAV_INFO => 'onArrayFilterEvent',
ArrayFilterEvent::FEATURE_ENABLED => 'onArrayFilterEvent',
ArrayFilterEvent::FEATURE_GET => 'onArrayFilterEvent',
HtmlFilterEvent::HEAD => 'onHtmlFilterEvent',
HtmlFilterEvent::FOOTER => 'onHtmlFilterEvent',
HtmlFilterEvent::PAGE_HEADER => 'onHtmlFilterEvent',
HtmlFilterEvent::PAGE_CONTENT_TOP => 'onHtmlFilterEvent',
HtmlFilterEvent::PAGE_END => 'onHtmlFilterEvent',
];
}
public static function onNamedEvent(NamedEvent $event): void
{
static::callHook($event->getName(), '');
}
public static function onConfigLoadedEvent(ConfigLoadedEvent $event): void
{
static::callHook($event->getName(), $event->getConfig());
}
public static function onArrayFilterEvent(ArrayFilterEvent $event): void
{
$event->setArray(
static::callHook($event->getName(), $event->getArray())
);
}
public static function onHtmlFilterEvent(HtmlFilterEvent $event): void
{
$event->setHtml(
static::callHook($event->getName(), $event->getHtml())
);
}
/**
* @param string|array|object $data
*
* @return string|array|object
*/
private static function callHook(string $name, $data)
{
// If possible, map the event name to the legacy Hook name
$name = static::$eventMapper[$name] ?? $name;
// Little hack to allow mocking the Hook call in tests.
if (static::$mockedCallHook instanceof \Closure) {
return (static::$mockedCallHook)->__invoke($name, $data);
}
Hook::callAll($name, $data);
return $data;
}
}

View file

@ -789,4 +789,13 @@ abstract class DI
{ {
return self::$dice->create(Content\Post\Repository\PostMedia::class); return self::$dice->create(Content\Post\Repository\PostMedia::class);
} }
/**
* @internal The EventDispatcher should never called outside of the core, like in addons or themes
* @deprecated 2025.02 Use constructor injection instead
*/
public static function eventDispatcher(): \Psr\EventDispatcher\EventDispatcherInterface
{
return self::$dice->create(\Psr\EventDispatcher\EventDispatcherInterface::class);
}
} }

View file

@ -0,0 +1,45 @@
<?php
// Copyright (C) 2010-2024, the Friendica project
// SPDX-FileCopyrightText: 2010-2024 the Friendica project
//
// SPDX-License-Identifier: AGPL-3.0-or-later
declare(strict_types=1);
namespace Friendica\Event;
/**
* Allow Event listener to modify an array.
*
* @internal
*/
final class ArrayFilterEvent extends Event
{
public const APP_MENU = 'friendica.data.app_menu';
public const NAV_INFO = 'friendica.data.nav_info';
public const FEATURE_ENABLED = 'friendica.data.feature_enabled';
public const FEATURE_GET = 'friendica.data.feature_get';
private array $array;
public function __construct(string $name, array $array)
{
parent::__construct($name);
$this->array = $array;
}
public function getArray(): array
{
return $this->array;
}
public function setArray(array $array): void
{
$this->array = $array;
}
}

View file

@ -0,0 +1,36 @@
<?php
// Copyright (C) 2010-2024, the Friendica project
// SPDX-FileCopyrightText: 2010-2024 the Friendica project
//
// SPDX-License-Identifier: AGPL-3.0-or-later
declare(strict_types=1);
namespace Friendica\Event;
use Friendica\Core\Config\Util\ConfigFileManager;
/**
* Notify that the config was loaded
*
* @internal
*/
final class ConfigLoadedEvent extends Event
{
public const CONFIG_LOADED = 'friendica.config_loaded';
private ConfigFileManager $config;
public function __construct(string $name, ConfigFileManager $config)
{
parent::__construct($name);
$this->config = $config;
}
public function getConfig(): ConfigFileManager
{
return $this->config;
}
}

35
src/Event/Event.php Normal file
View file

@ -0,0 +1,35 @@
<?php
// Copyright (C) 2010-2024, the Friendica project
// SPDX-FileCopyrightText: 2010-2024 the Friendica project
//
// SPDX-License-Identifier: AGPL-3.0-or-later
declare(strict_types=1);
namespace Friendica\Event;
/**
* One-way Event to inform listener about something happend.
*
* @internal
*/
class Event implements NamedEvent
{
/**
* Friendica is initialized.
*/
public const INIT = 'friendica.init';
private string $name;
public function __construct(string $name)
{
$this->name = $name;
}
public function getName(): string
{
return $this->name;
}
}

View file

@ -0,0 +1,37 @@
<?php
// Copyright (C) 2010-2024, the Friendica project
// SPDX-FileCopyrightText: 2010-2024 the Friendica project
//
// SPDX-License-Identifier: AGPL-3.0-or-later
declare(strict_types=1);
namespace Friendica\Event;
use Symfony\Component\EventDispatcher\EventDispatcher as SymfonyEventDispatcher;
/**
* Modified Event Dispatcher.
*
* @internal
*/
final class EventDispatcher extends SymfonyEventDispatcher
{
/**
* Add support for named events.
*
* @template T of object
* @param T $event
*
* @return T The passed $event MUST be returned
*/
public function dispatch(object $event, ?string $eventName = null): object
{
if ($eventName === null && $event instanceof NamedEvent) {
$eventName = $event->getName();
}
return parent::dispatch($event, $eventName);
}
}

View file

@ -0,0 +1,47 @@
<?php
// Copyright (C) 2010-2024, the Friendica project
// SPDX-FileCopyrightText: 2010-2024 the Friendica project
//
// SPDX-License-Identifier: AGPL-3.0-or-later
declare(strict_types=1);
namespace Friendica\Event;
/**
* Allow Event listener to modify HTML.
*
* @internal
*/
final class HtmlFilterEvent extends Event
{
public const HEAD = 'friendica.html.head';
public const FOOTER = 'friendica.html.footer';
public const PAGE_HEADER = 'friendica.html.page_header';
public const PAGE_CONTENT_TOP = 'friendica.html.page_content_top';
public const PAGE_END = 'friendica.html.page_end';
private string $html;
public function __construct(string $name, string $html)
{
parent::__construct($name);
$this->html = $html;
}
public function getHtml(): string
{
return $this->html;
}
public function setHtml(string $html): void
{
$this->html = $html;
}
}

20
src/Event/NamedEvent.php Normal file
View file

@ -0,0 +1,20 @@
<?php
// Copyright (C) 2010-2024, the Friendica project
// SPDX-FileCopyrightText: 2010-2024 the Friendica project
//
// SPDX-License-Identifier: AGPL-3.0-or-later
declare(strict_types=1);
namespace Friendica\Event;
/**
* Interface for named events.
*
* @internal
*/
interface NamedEvent
{
public function getName(): string;
}

View file

@ -182,6 +182,9 @@ return (function(string $basepath, array $getVars, array $serverVars, array $coo
['create', [], Dice::CHAIN_CALL], ['create', [], Dice::CHAIN_CALL],
], ],
], ],
\Psr\EventDispatcher\EventDispatcherInterface::class => [
'instanceOf' => \Friendica\Event\EventDispatcher::class,
],
\Friendica\Core\Logger\Capability\IHaveCallIntrospections::class => [ \Friendica\Core\Logger\Capability\IHaveCallIntrospections::class => [
'instanceOf' => \Friendica\Core\Logger\Util\Introspection::class, 'instanceOf' => \Friendica\Core\Logger\Util\Introspection::class,
'constructParams' => [ 'constructParams' => [

View file

@ -0,0 +1,176 @@
<?php
// Copyright (C) 2010-2024, the Friendica project
// SPDX-FileCopyrightText: 2010-2024 the Friendica project
//
// SPDX-License-Identifier: AGPL-3.0-or-later
declare(strict_types=1);
namespace Friendica\Test\Unit\Core\Hooks;
use Friendica\Core\Config\Util\ConfigFileManager;
use Friendica\Core\Hooks\HookEventBridge;
use Friendica\Event\ArrayFilterEvent;
use Friendica\Event\ConfigLoadedEvent;
use Friendica\Event\Event;
use Friendica\Event\HtmlFilterEvent;
use PHPUnit\Framework\TestCase;
class HookEventBridgeTest extends TestCase
{
public function testGetStaticSubscribedEventsReturnsStaticMethods(): void
{
$expected = [
Event::INIT => 'onNamedEvent',
ConfigLoadedEvent::CONFIG_LOADED => 'onConfigLoadedEvent',
ArrayFilterEvent::APP_MENU => 'onArrayFilterEvent',
ArrayFilterEvent::NAV_INFO => 'onArrayFilterEvent',
ArrayFilterEvent::FEATURE_ENABLED => 'onArrayFilterEvent',
ArrayFilterEvent::FEATURE_GET => 'onArrayFilterEvent',
HtmlFilterEvent::HEAD => 'onHtmlFilterEvent',
HtmlFilterEvent::FOOTER => 'onHtmlFilterEvent',
HtmlFilterEvent::PAGE_HEADER => 'onHtmlFilterEvent',
HtmlFilterEvent::PAGE_CONTENT_TOP => 'onHtmlFilterEvent',
HtmlFilterEvent::PAGE_END => 'onHtmlFilterEvent',
];
$this->assertSame(
$expected,
HookEventBridge::getStaticSubscribedEvents()
);
foreach ($expected as $methodName) {
$this->assertTrue(
method_exists(HookEventBridge::class, $methodName),
$methodName . '() is not defined'
);
$this->assertTrue(
(new \ReflectionMethod(HookEventBridge::class, $methodName))->isStatic(),
$methodName . '() is not static'
);
}
}
public static function getNamedEventData(): array
{
return [
['test', 'test'],
[Event::INIT, 'init_1'],
];
}
/**
* @dataProvider getNamedEventData
*/
public function testOnNamedEventCallsHook($name, $expected): void
{
$event = new Event($name);
$reflectionProperty = new \ReflectionProperty(HookEventBridge::class, 'mockedCallHook');
$reflectionProperty->setAccessible(true);
$reflectionProperty->setValue(null, function (string $name, $data) use ($expected) {
$this->assertSame($expected, $name);
$this->assertSame('', $data);
return $data;
});
HookEventBridge::onNamedEvent($event);
}
public static function getConfigLoadedEventData(): array
{
return [
['test', 'test'],
[ConfigLoadedEvent::CONFIG_LOADED, 'load_config'],
];
}
/**
* @dataProvider getConfigLoadedEventData
*/
public function testOnConfigLoadedEventCallsHookWithCorrectValue($name, $expected): void
{
$config = $this->createStub(ConfigFileManager::class);
$event = new ConfigLoadedEvent($name, $config);
$reflectionProperty = new \ReflectionProperty(HookEventBridge::class, 'mockedCallHook');
$reflectionProperty->setAccessible(true);
$reflectionProperty->setValue(null, function (string $name, $data) use ($expected, $config) {
$this->assertSame($expected, $name);
$this->assertSame($config, $data);
return $data;
});
HookEventBridge::onConfigLoadedEvent($event);
}
public static function getArrayFilterEventData(): array
{
return [
['test', 'test'],
[ArrayFilterEvent::APP_MENU, 'app_menu'],
[ArrayFilterEvent::NAV_INFO, 'nav_info'],
[ArrayFilterEvent::FEATURE_ENABLED, 'isEnabled'],
[ArrayFilterEvent::FEATURE_GET, 'get'],
];
}
/**
* @dataProvider getArrayFilterEventData
*/
public function testOnArrayFilterEventCallsHookWithCorrectValue($name, $expected): void
{
$event = new ArrayFilterEvent($name, ['original']);
$reflectionProperty = new \ReflectionProperty(HookEventBridge::class, 'mockedCallHook');
$reflectionProperty->setAccessible(true);
$reflectionProperty->setValue(null, function (string $name, $data) use ($expected) {
$this->assertSame($expected, $name);
$this->assertSame(['original'], $data);
return $data;
});
HookEventBridge::onArrayFilterEvent($event);
}
public static function getHtmlFilterEventData(): array
{
return [
['test', 'test'],
[HtmlFilterEvent::HEAD, 'head'],
[HtmlFilterEvent::FOOTER, 'footer'],
[HtmlFilterEvent::PAGE_HEADER, 'page_header'],
[HtmlFilterEvent::PAGE_CONTENT_TOP, 'page_content_top'],
[HtmlFilterEvent::PAGE_END, 'page_end'],
];
}
/**
* @dataProvider getHtmlFilterEventData
*/
public function testOnHtmlFilterEventCallsHookWithCorrectValue($name, $expected): void
{
$event = new HtmlFilterEvent($name, 'original');
$reflectionProperty = new \ReflectionProperty(HookEventBridge::class, 'mockedCallHook');
$reflectionProperty->setAccessible(true);
$reflectionProperty->setValue(null, function (string $name, $data) use ($expected) {
$this->assertSame($expected, $name);
$this->assertSame('original', $data);
return $data;
});
HookEventBridge::onHtmlFilterEvent($event);
}
}

View file

@ -0,0 +1,66 @@
<?php
// Copyright (C) 2010-2024, the Friendica project
// SPDX-FileCopyrightText: 2010-2024 the Friendica project
//
// SPDX-License-Identifier: AGPL-3.0-or-later
declare(strict_types=1);
namespace Friendica\Test\Unit\Event;
use Friendica\Event\ArrayFilterEvent;
use Friendica\Event\NamedEvent;
use PHPUnit\Framework\TestCase;
class ArrayFilterEventTest extends TestCase
{
public function testImplementationOfInstances(): void
{
$event = new ArrayFilterEvent('test', []);
$this->assertInstanceOf(NamedEvent::class, $event);
}
public static function getPublicConstants(): array
{
return [
[ArrayFilterEvent::APP_MENU, 'friendica.data.app_menu'],
];
}
/**
* @dataProvider getPublicConstants
*/
public function testPublicConstantsAreAvailable($value, $expected): void
{
$this->assertSame($expected, $value);
}
public function testGetNameReturnsName(): void
{
$event = new ArrayFilterEvent('test', []);
$this->assertSame('test', $event->getName());
}
public function testGetArrayReturnsCorrectString(): void
{
$data = ['original'];
$event = new ArrayFilterEvent('test', $data);
$this->assertSame($data, $event->getArray());
}
public function testSetArrayUpdatesHtml(): void
{
$event = new ArrayFilterEvent('test', ['original']);
$expected = ['updated'];
$event->setArray($expected);
$this->assertSame($expected, $event->getArray());
}
}

View file

@ -0,0 +1,56 @@
<?php
// Copyright (C) 2010-2024, the Friendica project
// SPDX-FileCopyrightText: 2010-2024 the Friendica project
//
// SPDX-License-Identifier: AGPL-3.0-or-later
declare(strict_types=1);
namespace Friendica\Test\Unit\Event;
use Friendica\Core\Config\Util\ConfigFileManager;
use Friendica\Event\ConfigLoadedEvent;
use Friendica\Event\NamedEvent;
use PHPUnit\Framework\TestCase;
class ConfigLoadedEventTest extends TestCase
{
public function testImplementationOfInstances(): void
{
$event = new ConfigLoadedEvent('test', $this->createStub(ConfigFileManager::class));
$this->assertInstanceOf(NamedEvent::class, $event);
}
public static function getPublicConstants(): array
{
return [
[ConfigLoadedEvent::CONFIG_LOADED, 'friendica.config_loaded'],
];
}
/**
* @dataProvider getPublicConstants
*/
public function testPublicConstantsAreAvailable($value, $expected): void
{
$this->assertSame($expected, $value);
}
public function testGetNameReturnsName(): void
{
$event = new ConfigLoadedEvent('test', $this->createStub(ConfigFileManager::class));
$this->assertSame('test', $event->getName());
}
public function testGetConfigReturnsCorrectString(): void
{
$config = $this->createStub(ConfigFileManager::class);
$event = new ConfigLoadedEvent('test', $config);
$this->assertSame($config, $event->getConfig());
}
}

View file

@ -0,0 +1,37 @@
<?php
// Copyright (C) 2010-2024, the Friendica project
// SPDX-FileCopyrightText: 2010-2024 the Friendica project
//
// SPDX-License-Identifier: AGPL-3.0-or-later
declare(strict_types=1);
namespace Friendica\Test\Unit\Event;
use Friendica\Event\Event;
use Friendica\Event\EventDispatcher;
use Friendica\Event\NamedEvent;
use PHPUnit\Framework\TestCase;
use Psr\EventDispatcher\EventDispatcherInterface;
class EventDispatcherTest extends TestCase
{
public function testImplementationOfInstances(): void
{
$eventDispatcher = new EventDispatcher();
$this->assertInstanceOf(EventDispatcherInterface::class, $eventDispatcher);
}
public function testDispatchANamedEventUsesNameAsEventName(): void
{
$eventDispatcher = new EventDispatcher();
$eventDispatcher->addListener('test', function (NamedEvent $event) {
$this->assertSame('test', $event->getName());
});
$eventDispatcher->dispatch(new Event('test'));
}
}

View file

@ -0,0 +1,46 @@
<?php
// Copyright (C) 2010-2024, the Friendica project
// SPDX-FileCopyrightText: 2010-2024 the Friendica project
//
// SPDX-License-Identifier: AGPL-3.0-or-later
declare(strict_types=1);
namespace Friendica\Test\Unit\Event;
use Friendica\Event\Event;
use Friendica\Event\NamedEvent;
use PHPUnit\Framework\TestCase;
class EventTest extends TestCase
{
public function testImplementationOfInstances(): void
{
$event = new Event('test');
$this->assertInstanceOf(NamedEvent::class, $event);
}
public static function getPublicConstants(): array
{
return [
[Event::INIT, 'friendica.init'],
];
}
/**
* @dataProvider getPublicConstants
*/
public function testPublicConstantsAreAvailable($value, $expected): void
{
$this->assertSame($expected, $value);
}
public function testGetNameReturnsName(): void
{
$event = new Event('test');
$this->assertSame('test', $event->getName());
}
}

View file

@ -0,0 +1,69 @@
<?php
// Copyright (C) 2010-2024, the Friendica project
// SPDX-FileCopyrightText: 2010-2024 the Friendica project
//
// SPDX-License-Identifier: AGPL-3.0-or-later
declare(strict_types=1);
namespace Friendica\Test\Unit\Event;
use Friendica\Event\HtmlFilterEvent;
use Friendica\Event\NamedEvent;
use PHPUnit\Framework\TestCase;
class HtmlFilterEventTest extends TestCase
{
public function testImplementationOfInstances(): void
{
$event = new HtmlFilterEvent('test', 'original');
$this->assertInstanceOf(NamedEvent::class, $event);
}
public static function getPublicConstants(): array
{
return [
[HtmlFilterEvent::HEAD, 'friendica.html.head'],
[HtmlFilterEvent::FOOTER, 'friendica.html.footer'],
[HtmlFilterEvent::PAGE_CONTENT_TOP, 'friendica.html.page_content_top'],
[HtmlFilterEvent::PAGE_END, 'friendica.html.page_end'],
];
}
/**
* @dataProvider getPublicConstants
*/
public function testPublicConstantsAreAvailable($value, $expected): void
{
$this->assertSame($expected, $value);
}
public function testGetNameReturnsName(): void
{
$event = new HtmlFilterEvent('test', '');
$this->assertSame('test', $event->getName());
}
public function testGetHtmlReturnsCorrectString(): void
{
$data = 'original';
$event = new HtmlFilterEvent('test', $data);
$this->assertSame($data, $event->getHtml());
}
public function testSetHtmlUpdatesHtml(): void
{
$event = new HtmlFilterEvent('test', 'original');
$expected = 'updated';
$event->setHtml($expected);
$this->assertSame($expected, $event->getHtml());
}
}

View file

@ -5,7 +5,7 @@
// //
// SPDX-License-Identifier: AGPL-3.0-or-later // SPDX-License-Identifier: AGPL-3.0-or-later
declare(strict_types = 1); declare(strict_types=1);
namespace Friendica\Test\Unit\Util; namespace Friendica\Test\Unit\Util;

View file

@ -5,7 +5,7 @@
// //
// SPDX-License-Identifier: AGPL-3.0-or-later // SPDX-License-Identifier: AGPL-3.0-or-later
declare(strict_types = 1); declare(strict_types=1);
namespace Friendica\Test\Unit\Util; namespace Friendica\Test\Unit\Util;