]> git.mxchange.org Git - friendica.git/commitdiff
EventDispatcher proof of concept
authorArt4 <art4@wlabs.de>
Fri, 24 Jan 2025 14:36:05 +0000 (14:36 +0000)
committerHypolite Petovan <hypolite@mrpetovan.com>
Tue, 4 Feb 2025 18:22:52 +0000 (13:22 -0500)
17 files changed:
composer.json
composer.lock
src/App.php
src/App/Page.php
src/Event/Event.php [new file with mode: 0644]
src/Event/EventDispatcher.php [new file with mode: 0644]
src/Event/HtmlFilterEvent.php [new file with mode: 0644]
src/Event/NamedEvent.php [new file with mode: 0644]
src/EventSubscriber/HookEventBridge.php [new file with mode: 0644]
src/EventSubscriber/StaticEventSubscriber.php [new file with mode: 0644]
static/dependencies.config.php
tests/Unit/Event/EventDispatcherTest.php [new file with mode: 0644]
tests/Unit/Event/EventTest.php [new file with mode: 0644]
tests/Unit/Event/HtmlFilterEventTest.php [new file with mode: 0644]
tests/Unit/EventSubscriber/HookEventBridgeTest.php [new file with mode: 0644]
tests/Unit/Util/BasePathTest.php
tests/Unit/Util/CryptoTest.php

index 89c8a9f41ded6e4dffde8fcf909c5e7162efc66a..ce332415b6bd46c70734d6188bfdcaf1364a3bc5 100644 (file)
                "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"
index 11cc4076617a3cf3691820b07eca7411f82e33b0..3086ed7be947b9b193a897a257d91bfe6d7bca23 100644 (file)
@@ -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",
             ],
             "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",
             ],
             "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",
             ],
             "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",
index 5623f9a4137387bbe9bb24a7f911f0a9d9b46fa4..ace40117208ad56b169321391379b5cd40b2f6f0 100644 (file)
@@ -23,10 +23,6 @@ use Friendica\Core\Container;
 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 +31,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\Event;
+use Friendica\EventSubscriber\HookEventBridge;
+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 +47,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 +156,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);
@@ -178,6 +183,7 @@ class App
                $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 +208,8 @@ class App
 
                $this->registerErrorHandler();
 
+               $this->registerEventDispatcher();
+
                $this->load(
                        $serverParams,
                        $this->container->create(DbaDefinition::class),
@@ -230,6 +238,8 @@ class App
 
                $this->registerErrorHandler();
 
+               $this->registerEventDispatcher();
+
                $this->load(
                        $serverParams,
                        $this->container->create(DbaDefinition::class),
@@ -301,6 +311,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');
@@ -385,6 +405,7 @@ class App
         * @throws \ImagickException
         */
        private function runFrontend(
+               EventDispatcherInterface $eventDispatcher,
                IManagePersonalConfigValues $pconfig,
                Page $page,
                Nav $nav,
@@ -424,7 +445,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);
index ca831731846e2dbdcfa23abb9678aca38d0791d6..b5a00859f1cd1c3f8087ead95af362e97881f07e 100644 (file)
@@ -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,10 @@ 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 +358,10 @@ 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 +386,10 @@ 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 +487,10 @@ 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/Event/Event.php b/src/Event/Event.php
new file mode 100644 (file)
index 0000000..248b310
--- /dev/null
@@ -0,0 +1,33 @@
+<?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.
+ */
+final 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;
+       }
+}
diff --git a/src/Event/EventDispatcher.php b/src/Event/EventDispatcher.php
new file mode 100644 (file)
index 0000000..6175996
--- /dev/null
@@ -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;
+
+use Symfony\Component\EventDispatcher\EventDispatcher as SymfonyEventDispatcher;
+
+/**
+ * Modified Event Dispatcher.
+ */
+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);
+       }
+}
diff --git a/src/Event/HtmlFilterEvent.php b/src/Event/HtmlFilterEvent.php
new file mode 100644 (file)
index 0000000..d8b7b74
--- /dev/null
@@ -0,0 +1,49 @@
+<?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.
+ */
+final class HtmlFilterEvent implements NamedEvent
+{
+       public const HEAD = 'friendica.html.head';
+
+       public const FOOTER = 'friendica.html.footer';
+
+       public const PAGE_CONTENT_TOP = 'friendica.html.page_content_top';
+
+       public const PAGE_END = 'friendica.html.page_end';
+
+       private string $name;
+
+       private string $html;
+
+       public function __construct(string $name, string $html)
+       {
+               $this->name = $name;
+               $this->html = $html;
+       }
+
+       public function getName(): string
+       {
+               return $this->name;
+       }
+
+       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 (file)
index 0000000..4246496
--- /dev/null
@@ -0,0 +1,18 @@
+<?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.
+ */
+interface NamedEvent
+{
+       public function getName(): string;
+}
diff --git a/src/EventSubscriber/HookEventBridge.php b/src/EventSubscriber/HookEventBridge.php
new file mode 100644 (file)
index 0000000..b1408ca
--- /dev/null
@@ -0,0 +1,90 @@
+<?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\EventSubscriber;
+
+use Friendica\Core\Hook;
+use Friendica\Event\Event;
+use Friendica\Event\HtmlFilterEvent;
+use Friendica\Event\NamedEvent;
+
+/**
+ * Bridge between the EventDispatcher and the Hook class.
+ */
+final class HookEventBridge implements StaticEventSubscriber
+{
+       /**
+        * 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',
+               HtmlFilterEvent::HEAD             => 'head',
+               HtmlFilterEvent::FOOTER           => 'footer',
+               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',
+                       HtmlFilterEvent::HEAD             => 'onHtmlFilterEvent',
+                       HtmlFilterEvent::FOOTER           => 'onHtmlFilterEvent',
+                       HtmlFilterEvent::PAGE_CONTENT_TOP => 'onHtmlFilterEvent',
+                       HtmlFilterEvent::PAGE_END         => 'onHtmlFilterEvent',
+               ];
+       }
+
+       public static function onNamedEvent(NamedEvent $event): void
+       {
+               $name = $event->getName();
+
+               $name = static::$eventMapper[$name] ?? $name;
+
+               static::callHook($name, '');
+       }
+
+       public static function onHtmlFilterEvent(HtmlFilterEvent $event): void
+       {
+               $name = $event->getName();
+
+               $name = static::$eventMapper[$name] ?? $name;
+
+               $event->setHtml(
+                       static::callHook($name, $event->getHtml())
+               );
+       }
+
+       /**
+        * @param string|array $data
+        *
+        * @return string|array
+        */
+       private static function callHook(string $name, $data)
+       {
+               // 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/EventSubscriber/StaticEventSubscriber.php b/src/EventSubscriber/StaticEventSubscriber.php
new file mode 100644 (file)
index 0000000..16b60fb
--- /dev/null
@@ -0,0 +1,32 @@
+<?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\EventSubscriber;
+
+/**
+ * Define events that should be reacted to.
+ */
+interface StaticEventSubscriber
+{
+       /**
+        * Return an array of events to subscribe to.
+        * The key must the event class name.
+        * The value must the method of the implementing class to call.
+        * The method will be called statically with the event class as first parameter.
+        *
+        * Example:
+        *
+        * ```php
+        * return [Event::class => 'onEvent'];
+        * ```
+        *
+        * @return array<class-string, string>
+        */
+       public static function getStaticSubscribedEvents(): array;
+}
index f1c2f4e52e3d2648bda150a4a5b5bd9f0f576db1..5ffaf134e448845ca966a3699400930e123deee8 100644 (file)
@@ -182,6 +182,9 @@ return (function(string $basepath, array $getVars, array $serverVars, array $coo
                                ['create', [], Dice::CHAIN_CALL],
                        ],
                ],
+               \Psr\EventDispatcher\EventDispatcherInterface::class => [
+                       'instanceOf' => \Friendica\Event\EventDispatcher::class,
+               ],
                \Friendica\Core\Logger\Capability\IHaveCallIntrospections::class => [
                        'instanceOf' => \Friendica\Core\Logger\Util\Introspection::class,
                        'constructParams' => [
diff --git a/tests/Unit/Event/EventDispatcherTest.php b/tests/Unit/Event/EventDispatcherTest.php
new file mode 100644 (file)
index 0000000..2cc1527
--- /dev/null
@@ -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'));
+       }
+}
diff --git a/tests/Unit/Event/EventTest.php b/tests/Unit/Event/EventTest.php
new file mode 100644 (file)
index 0000000..8d7f882
--- /dev/null
@@ -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());
+       }
+}
diff --git a/tests/Unit/Event/HtmlFilterEventTest.php b/tests/Unit/Event/HtmlFilterEventTest.php
new file mode 100644 (file)
index 0000000..ae1d27a
--- /dev/null
@@ -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());
+       }
+}
diff --git a/tests/Unit/EventSubscriber/HookEventBridgeTest.php b/tests/Unit/EventSubscriber/HookEventBridgeTest.php
new file mode 100644 (file)
index 0000000..a9cc237
--- /dev/null
@@ -0,0 +1,114 @@
+<?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\EventSubscriber;
+
+use Friendica\Event\Event;
+use Friendica\Event\HtmlFilterEvent;
+use Friendica\EventSubscriber\HookEventBridge;
+use Friendica\EventSubscriber\StaticEventSubscriber;
+use PHPUnit\Framework\TestCase;
+
+class HookEventBridgeTest extends TestCase
+{
+       public function testCorrectImplementation(): void
+       {
+               $this->assertTrue(
+                       is_subclass_of(HookEventBridge::class, StaticEventSubscriber::class, true),
+                       HookEventBridge::class . ' does not implement ' . StaticEventSubscriber::class
+               );
+       }
+
+       public function testGetStaticSubscribedEventsReturnsStaticMethods(): void
+       {
+               $expected = [
+                       Event::INIT                       => 'onNamedEvent',
+                       HtmlFilterEvent::HEAD             => 'onHtmlFilterEvent',
+                       HtmlFilterEvent::FOOTER           => '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 getHtmlFilterEventData(): array
+       {
+               return [
+                       ['test', 'test'],
+                       [HtmlFilterEvent::HEAD, 'head'],
+                       [HtmlFilterEvent::FOOTER, 'footer'],
+                       [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);
+       }
+}
index 26c495b7fe83efa8d45b464e74b129be1ff9972c..dbdf9bd2619fc6442bf64788cb60362510d6b495 100644 (file)
@@ -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,
                        ]
                ];
index 9dbffb29b409904c05fc27bcabd51596facc4c88..41fb1e28265e0253033f9369b7e54dcd499e5775 100644 (file)
@@ -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;