diff --git a/composer.json b/composer.json index 89c8a9f41d..ce332415b6 100644 --- a/composer.json +++ b/composer.json @@ -71,9 +71,11 @@ "pragmarx/recovery": "^0.2", "psr/clock": "^1.0", "psr/container": "^2.0", + "psr/event-dispatcher": "^1.0", "psr/log": "^1.1", "seld/cli-prompt": "^1.0", "smarty/smarty": "^4", + "symfony/event-dispatcher": "^5.4", "textalk/websocket": "^1.6", "ua-parser/uap-php": "^3.9", "xemlock/htmlpurifier-html5": "^0.1.11" diff --git a/composer.lock b/composer.lock index 11cc407661..3086ed7be9 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "64436f375561718bb857e3e1b0e503c9", + "content-hash": "8ee8f9186d271b65b83c2ddbd12c5c03", "packages": [ { "name": "asika/simple-console", @@ -3234,6 +3234,56 @@ ], "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", "version": "1.0.3", @@ -3709,6 +3759,170 @@ ], "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", "version": "v1.20.0", @@ -3774,6 +3988,86 @@ ], "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", "version": "1.6.3", diff --git a/src/App.php b/src/App.php index 5623f9a413..7f8e03b5e8 100644 --- a/src/App.php +++ b/src/App.php @@ -20,13 +20,10 @@ use Friendica\Content\Nav; use Friendica\Core\Addon\Capability\ICanLoadAddons; use Friendica\Core\Config\Factory\Config; use Friendica\Core\Container; +use Friendica\Core\Hooks\HookEventBridge; use Friendica\Core\Logger\LoggerManager; use Friendica\Core\Renderer; 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\DiceContainer; use Friendica\Core\L10n; @@ -35,9 +32,15 @@ use Friendica\Core\Logger\Handler\ErrorHandler; use Friendica\Core\PConfig\Capability\IManagePersonalConfigValues; use Friendica\Core\System; 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\Network\HTTPException; use Friendica\Protocol\ATProtocol\DID; +use Friendica\Security\Authentication; use Friendica\Security\ExAuth; use Friendica\Security\OpenWebAuth; use Friendica\Util\BasePath; @@ -45,6 +48,7 @@ use Friendica\Util\DateTimeFormat; use Friendica\Util\HTTPInputData; use Friendica\Util\HTTPSignature; use Friendica\Util\Profiler; +use Psr\EventDispatcher\EventDispatcherInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Log\LoggerInterface; @@ -153,6 +157,8 @@ class App $this->registerErrorHandler(); + $this->registerEventDispatcher(); + $this->requestId = $this->container->create(Request::class)->getRequestId(); $this->auth = $this->container->create(Authentication::class); $this->config = $this->container->create(IManageConfigValues::class); @@ -172,12 +178,14 @@ class App $this->mode, $this->config, $this->profiler, + $this->container->create(EventDispatcherInterface::class), $this->appHelper, ); $this->registerTemplateEngine(); $this->runFrontend( + $this->container->create(EventDispatcherInterface::class), $this->container->create(IManagePersonalConfigValues::class), $this->container->create(Page::class), $this->container->create(Nav::class), @@ -202,6 +210,8 @@ class App $this->registerErrorHandler(); + $this->registerEventDispatcher(); + $this->load( $serverParams, $this->container->create(DbaDefinition::class), @@ -209,6 +219,7 @@ class App $this->container->create(Mode::class), $this->container->create(IManageConfigValues::class), $this->container->create(Profiler::class), + $this->container->create(EventDispatcherInterface::class), $this->container->create(AppHelper::class), ); @@ -230,6 +241,8 @@ class App $this->registerErrorHandler(); + $this->registerEventDispatcher(); + $this->load( $serverParams, $this->container->create(DbaDefinition::class), @@ -237,6 +250,7 @@ class App $this->container->create(Mode::class), $this->container->create(IManageConfigValues::class), $this->container->create(Profiler::class), + $this->container->create(EventDispatcherInterface::class), $this->container->create(AppHelper::class), ); @@ -301,6 +315,16 @@ class App 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 { Renderer::registerTemplateEngine('Friendica\Render\FriendicaSmartyEngine'); @@ -316,6 +340,7 @@ class App Mode $mode, IManageConfigValues $config, Profiler $profiler, + EventDispatcherInterface $eventDispatcher, AppHelper $appHelper ): void { if ($config->get('system', 'ini_max_execution_time') !== false) { @@ -339,7 +364,8 @@ class App if ($mode->has(Mode::DBAVAILABLE)) { Core\Hook::loadHooks(); $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 $dbaDefinition->load(true); @@ -385,6 +411,7 @@ class App * @throws \ImagickException */ private function runFrontend( + EventDispatcherInterface $eventDispatcher, IManagePersonalConfigValues $pconfig, Page $page, Nav $nav, @@ -424,7 +451,8 @@ class App $serverVars['REQUEST_METHOD'] === 'GET') { System::externalRedirect($this->baseURL . '/' . $this->args->getQueryString()); } - Core\Hook::callAll('init_1'); + + $eventDispatcher->dispatch(new Event(Event::INIT)); } DID::routeRequest($this->args->getCommand(), $serverVars); diff --git a/src/App/Page.php b/src/App/Page.php index ca83173184..6937a69240 100644 --- a/src/App/Page.php +++ b/src/App/Page.php @@ -14,7 +14,6 @@ use Friendica\App; use Friendica\AppHelper; use Friendica\Content\Nav; use Friendica\Core\Config\Capability\IManageConfigValues; -use Friendica\Core\Hook; use Friendica\Core\L10n; use Friendica\Core\PConfig\Capability\IManagePersonalConfigValues; use Friendica\Core\Renderer; @@ -22,12 +21,14 @@ use Friendica\Core\Session\Model\UserSession; use Friendica\Core\System; use Friendica\Core\Theme; use Friendica\DI; +use Friendica\Event\HtmlFilterEvent; use Friendica\Network\HTTPException; use Friendica\Util\Images; use Friendica\Util\Network; use Friendica\Util\Profiler; use Friendica\Util\Strings; use GuzzleHttp\Psr7\Utils; +use Psr\EventDispatcher\EventDispatcherInterface; use Psr\Http\Message\ResponseInterface; /** @@ -70,6 +71,8 @@ class Page implements ArrayAccess */ private $basePath; + private EventDispatcherInterface $eventDispatcher; + private $timestamp = 0; private $method = ''; private $module = ''; @@ -78,10 +81,11 @@ class Page implements ArrayAccess /** * @param string $basepath The Page basepath */ - public function __construct(string $basepath) + public function __construct(string $basepath, EventDispatcherInterface $eventDispatcher) { - $this->timestamp = microtime(true); - $this->basePath = $basepath; + $this->timestamp = microtime(true); + $this->basePath = $basepath; + $this->eventDispatcher = $eventDispatcher; } public function setLogging(string $method, string $module, string $command) @@ -229,7 +233,9 @@ class Page implements ArrayAccess $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'); /* 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'); $this->page['footer'] = Renderer::replaceMacros($tpl, [ @@ -376,7 +384,9 @@ class Page implements ArrayAccess { // initialise content region 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(); @@ -474,7 +484,9 @@ class Page implements ArrayAccess $profiler->set(microtime(true) - $timestamp, 'aftermath'); 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 diff --git a/src/Content/Feature.php b/src/Content/Feature.php index 8b5e56eba4..641340133c 100644 --- a/src/Content/Feature.php +++ b/src/Content/Feature.php @@ -7,8 +7,8 @@ namespace Friendica\Content; -use Friendica\Core\Hook; use Friendica\DI; +use Friendica\Event\ArrayFilterEvent; class Feature { @@ -41,15 +41,23 @@ class Feature */ public static function isEnabled(int $uid, $feature): bool { - if (!DI::config()->get('feature_lock', $feature, false)) { - $enabled = DI::config()->get('feature', $feature) ?? self::getDefault($feature); - $enabled = DI::pConfig()->get($uid, 'feature', $feature) ?? $enabled; + $config = DI::config(); + $pConfig = DI::pConfig(); + $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 { $enabled = true; } $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']; } @@ -86,55 +94,58 @@ class Feature */ public static function get($filtered = true) { - $arr = [ + $l10n = DI::l10n(); + $config = DI::config(); + $eventDispatcher = DI::eventDispatcher(); + $arr = [ // General 'general' => [ - DI::l10n()->t('General Features'), - //array('expire', DI::l10n()->t('Content Expiration'), DI::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::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)], + $l10n->t('General Features'), + //array('expire', $l10n->t('Content Expiration'), $l10n->t('Remove old posts/comments after a period of time')), + [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, $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 'composition' => [ - DI::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::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)], + $l10n->t('Post Composition Features'), + [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, $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 'tools' => [ - DI::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)], + $l10n->t('Post/Comment Tools'), + [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 'network' => [ - DI::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::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::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::NETWORKS, DI::l10n()->t('Protocols'), DI::l10n()->t('Display posts with the selected protocols.'), true, DI::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::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::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::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::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::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)], + $l10n->t('Network Widgets'), + [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, $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, $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, $l10n->t('Protocols'), $l10n->t('Display posts with the selected protocols.'), true, $config->get('feature_lock', self::NETWORKS, 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, $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, $l10n->t('Saved Searches'), $l10n->t('Display posts that contain subscribed hashtags.'), true, $config->get('feature_lock', self::SEARCHES, 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, $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, $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' => [ - DI::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::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)], + $l10n->t('Advanced Profile Settings'), + [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, $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' => [ - DI::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)], + $l10n->t('Advanced Calendar Settings'), + [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) { $has_items = false; $kquantity = count($arr[$k]); - for ($y = 0; $y < $kquantity; $y ++) { + for ($y = 0; $y < $kquantity; $y++) { if (is_array($arr[$k][$y])) { if ($arr[$k][$y][4] === false) { $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; } } diff --git a/src/Content/Nav.php b/src/Content/Nav.php index fdfcd4bde6..695d32ffdd 100644 --- a/src/Content/Nav.php +++ b/src/Content/Nav.php @@ -10,11 +10,12 @@ namespace Friendica\Content; use Friendica\App\BaseURL; use Friendica\App\Router; use Friendica\Core\Config\Capability\IManageConfigValues; -use Friendica\Core\Hook; use Friendica\Core\L10n; use Friendica\Core\Renderer; use Friendica\Core\Session\Capability\IHandleUserSessions; use Friendica\Database\Database; +use Friendica\Event\ArrayFilterEvent; +use Friendica\Event\HtmlFilterEvent; use Friendica\Model\Contact; use Friendica\Model\User; use Friendica\Module\Conversation\Community; @@ -22,6 +23,7 @@ use Friendica\Module\Home; use Friendica\Module\Security\Login; use Friendica\Network\HTTPException; use Friendica\Security\OpenWebAuth; +use Psr\EventDispatcher\EventDispatcherInterface; class Nav { @@ -63,14 +65,17 @@ class Nav /** @var 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->l10n = $l10n; - $this->session = $session; - $this->database = $database; - $this->config = $config; - $this->router = $router; + $this->baseUrl = $baseUrl; + $this->l10n = $l10n; + $this->session = $session; + $this->database = $database; + $this->config = $config; + $this->router = $router; + $this->eventDispatcher = $eventDispatcher; } /** @@ -114,7 +119,9 @@ class Nav '$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; } @@ -151,9 +158,11 @@ class Nav ) { $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; @@ -337,7 +346,9 @@ class Nav 'userinfo' => $userinfo, ]; - Hook::callAll('nav_info', $nav_info); + $nav_info = $this->eventDispatcher->dispatch( + new ArrayFilterEvent(ArrayFilterEvent::NAV_INFO, $nav_info) + )->getArray(); return $nav_info; } diff --git a/src/Core/Hooks/HookEventBridge.php b/src/Core/Hooks/HookEventBridge.php new file mode 100644 index 0000000000..2dad2d2b23 --- /dev/null +++ b/src/Core/Hooks/HookEventBridge.php @@ -0,0 +1,113 @@ + '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 + */ + 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; + } +} diff --git a/src/DI.php b/src/DI.php index 94bb4c308a..43a60dae0b 100644 --- a/src/DI.php +++ b/src/DI.php @@ -789,4 +789,13 @@ abstract class DI { 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); + } } diff --git a/src/Event/ArrayFilterEvent.php b/src/Event/ArrayFilterEvent.php new file mode 100644 index 0000000000..839aa40b75 --- /dev/null +++ b/src/Event/ArrayFilterEvent.php @@ -0,0 +1,45 @@ +array = $array; + } + + public function getArray(): array + { + return $this->array; + } + + public function setArray(array $array): void + { + $this->array = $array; + } +} diff --git a/src/Event/ConfigLoadedEvent.php b/src/Event/ConfigLoadedEvent.php new file mode 100644 index 0000000000..67f1407389 --- /dev/null +++ b/src/Event/ConfigLoadedEvent.php @@ -0,0 +1,36 @@ +config = $config; + } + + public function getConfig(): ConfigFileManager + { + return $this->config; + } +} diff --git a/src/Event/Event.php b/src/Event/Event.php new file mode 100644 index 0000000000..90defc3204 --- /dev/null +++ b/src/Event/Event.php @@ -0,0 +1,35 @@ +name = $name; + } + + public function getName(): string + { + return $this->name; + } +} diff --git a/src/Event/EventDispatcher.php b/src/Event/EventDispatcher.php new file mode 100644 index 0000000000..da92a9b76c --- /dev/null +++ b/src/Event/EventDispatcher.php @@ -0,0 +1,37 @@ +getName(); + } + + return parent::dispatch($event, $eventName); + } +} diff --git a/src/Event/HtmlFilterEvent.php b/src/Event/HtmlFilterEvent.php new file mode 100644 index 0000000000..7d1bbb2b55 --- /dev/null +++ b/src/Event/HtmlFilterEvent.php @@ -0,0 +1,47 @@ +html = $html; + } + + public function getHtml(): string + { + return $this->html; + } + + public function setHtml(string $html): void + { + $this->html = $html; + } +} diff --git a/src/Event/NamedEvent.php b/src/Event/NamedEvent.php new file mode 100644 index 0000000000..3f94959cf6 --- /dev/null +++ b/src/Event/NamedEvent.php @@ -0,0 +1,20 @@ + [ + 'instanceOf' => \Friendica\Event\EventDispatcher::class, + ], \Friendica\Core\Logger\Capability\IHaveCallIntrospections::class => [ 'instanceOf' => \Friendica\Core\Logger\Util\Introspection::class, 'constructParams' => [ diff --git a/tests/Unit/Core/Hooks/HookEventBridgeTest.php b/tests/Unit/Core/Hooks/HookEventBridgeTest.php new file mode 100644 index 0000000000..671eb23e94 --- /dev/null +++ b/tests/Unit/Core/Hooks/HookEventBridgeTest.php @@ -0,0 +1,176 @@ + '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); + } +} diff --git a/tests/Unit/Event/ArrayFilterEventTest.php b/tests/Unit/Event/ArrayFilterEventTest.php new file mode 100644 index 0000000000..c709e0ff0a --- /dev/null +++ b/tests/Unit/Event/ArrayFilterEventTest.php @@ -0,0 +1,66 @@ +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()); + } +} diff --git a/tests/Unit/Event/ConfigLoadedEventTest.php b/tests/Unit/Event/ConfigLoadedEventTest.php new file mode 100644 index 0000000000..473b75ac89 --- /dev/null +++ b/tests/Unit/Event/ConfigLoadedEventTest.php @@ -0,0 +1,56 @@ +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()); + } +} diff --git a/tests/Unit/Event/EventDispatcherTest.php b/tests/Unit/Event/EventDispatcherTest.php new file mode 100644 index 0000000000..2cc1527164 --- /dev/null +++ b/tests/Unit/Event/EventDispatcherTest.php @@ -0,0 +1,37 @@ +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')); + } +} diff --git a/tests/Unit/Event/EventTest.php b/tests/Unit/Event/EventTest.php new file mode 100644 index 0000000000..8d7f882451 --- /dev/null +++ b/tests/Unit/Event/EventTest.php @@ -0,0 +1,46 @@ +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()); + } +} diff --git a/tests/Unit/Event/HtmlFilterEventTest.php b/tests/Unit/Event/HtmlFilterEventTest.php new file mode 100644 index 0000000000..ae1d27a5ad --- /dev/null +++ b/tests/Unit/Event/HtmlFilterEventTest.php @@ -0,0 +1,69 @@ +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()); + } +} diff --git a/tests/Unit/Util/BasePathTest.php b/tests/Unit/Util/BasePathTest.php index 26c495b7fe..dbdf9bd261 100644 --- a/tests/Unit/Util/BasePathTest.php +++ b/tests/Unit/Util/BasePathTest.php @@ -5,7 +5,7 @@ // // SPDX-License-Identifier: AGPL-3.0-or-later -declare(strict_types = 1); +declare(strict_types=1); namespace Friendica\Test\Unit\Util; @@ -16,48 +16,48 @@ class BasePathTest extends TestCase { public static function getDataPaths(): array { - $basePath = dirname(__DIR__, 3); + $basePath = dirname(__DIR__, 3); $configPath = $basePath . DIRECTORY_SEPARATOR . 'config'; return [ 'fullPath' => [ - 'server' => [], - 'baseDir' => $configPath, + 'server' => [], + 'baseDir' => $configPath, 'expected' => $configPath, ], 'relative' => [ - 'server' => [], - 'baseDir' => 'config', + 'server' => [], + 'baseDir' => 'config', 'expected' => $configPath, ], 'document_root' => [ 'server' => [ 'DOCUMENT_ROOT' => $configPath, ], - 'baseDir' => '/noooop', + 'baseDir' => '/noooop', 'expected' => $configPath, ], 'pwd' => [ 'server' => [ 'PWD' => $configPath, ], - 'baseDir' => '/noooop', + 'baseDir' => '/noooop', 'expected' => $configPath, ], 'no_overwrite' => [ 'server' => [ 'DOCUMENT_ROOT' => $basePath, - 'PWD' => $basePath, + 'PWD' => $basePath, ], - 'baseDir' => 'config', + 'baseDir' => 'config', 'expected' => $configPath, ], 'no_overwrite_if_invalid' => [ 'server' => [ 'DOCUMENT_ROOT' => '/nopopop', - 'PWD' => $configPath, + 'PWD' => $configPath, ], - 'baseDir' => '/noatgawe22fafa', + 'baseDir' => '/noatgawe22fafa', 'expected' => $configPath, ] ]; diff --git a/tests/Unit/Util/CryptoTest.php b/tests/Unit/Util/CryptoTest.php index 9dbffb29b4..41fb1e2826 100644 --- a/tests/Unit/Util/CryptoTest.php +++ b/tests/Unit/Util/CryptoTest.php @@ -5,7 +5,7 @@ // // SPDX-License-Identifier: AGPL-3.0-or-later -declare(strict_types = 1); +declare(strict_types=1); namespace Friendica\Test\Unit\Util;