diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml index 9c8e20ee5f..bcca97c908 100644 --- a/.github/workflows/code-quality.yml +++ b/.github/workflows/code-quality.yml @@ -76,6 +76,38 @@ jobs: - name: Run PHPStan run: composer run phpstan + phpstan-addons: + name: PHPStan in addons (PHP ${{ matrix.php }}) + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + operating-system: ['ubuntu-latest'] + php: ['8.4'] + + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 2 + + - name: Setup PHP with composer and extensions + uses: shivammathur/setup-php@v2 #https://github.com/shivammathur/setup-php + with: + php-version: ${{ matrix.php }} + coverage: xdebug + tools: none + + - name: Clone addon repository + run: git clone -b develop --single-branch https://git.friendi.ca/friendica/friendica-addons.git addon + + - name: Install Composer dependencies + uses: "ramsey/composer-install@v2" + + - name: Run PHPStan in addons + run: composer run phpstan-addons + phpmd: name: PHPMD (PHP ${{ matrix.php }}) runs-on: ubuntu-latest diff --git a/.phpstan-addons.neon b/.phpstan-addons.neon new file mode 100644 index 0000000000..c4864d468d --- /dev/null +++ b/.phpstan-addons.neon @@ -0,0 +1,52 @@ +# SPDX-FileCopyrightText: 2010 - 2024 the Friendica project +# +# SPDX-License-Identifier: CC0-1.0 + +parameters: + level: 3 + + paths: + - addon/ + + excludePaths: + analyse: + - addon/*/lang/* + - addon/*/vendor/* + - addon/convert/UnitConvertor.php + - addon/pumpio/oauth/* + + scanDirectories: + - mod + - src + - static + - vendor + - view + + dynamicConstantNames: + - DB_UPDATE_VERSION + + ignoreErrors: + + - + # Ignore missing SMTP class in PHPMailer 5.2.21 + # see https://github.com/PHPMailer/PHPMailer/blob/v5.2.21/class.smtp.php + message: '(^.+ an unknown class SMTP\.$)' + path: addon/mailstream/phpmailer + + - + # Ignore missing SMTP class in PHPMailer 5.2.21 + # see https://github.com/PHPMailer/PHPMailer/blob/v5.2.21/class.smtp.php + message: '(^Property .+ has unknown class SMTP as its type\.$)' + path: addon/mailstream/phpmailer + + - + # Ignore missing SMTP class in PHPMailer 5.2.21 + # see https://github.com/PHPMailer/PHPMailer/blob/v5.2.21/class.smtp.php + message: '(^Method .+ has invalid return type SMTP\.$)' + path: addon/mailstream/phpmailer + + - + # Ignore missing SMTP class in PHPMailer 5.2.21 + # see https://github.com/PHPMailer/PHPMailer/blob/v5.2.21/class.smtp.php + message: '(^Instantiated class SMTP not found\.$)' + path: addon/mailstream/phpmailer diff --git a/.phpstan.neon b/.phpstan.neon index 8dfd10deae..fb731728b8 100644 --- a/.phpstan.neon +++ b/.phpstan.neon @@ -3,10 +3,9 @@ # SPDX-License-Identifier: CC0-1.0 parameters: - level: 2 + level: 3 paths: - - addon/ - bin/auth_ejabberd.php - bin/console.php - bin/daemon.php @@ -15,13 +14,6 @@ parameters: - index.php - src/ - excludePaths: - analyse: - - addon/*/lang/* - - addon/*/vendor/* - - addon/convert/UnitConvertor.php - - addon/pumpio/oauth/* - scanDirectories: - mod - static @@ -46,27 +38,3 @@ parameters: # Ignore missing IMAP\Connection class in PHP <= 8.0 message: '(^Parameter .+ has invalid type IMAP\\Connection\.$)' path: src - - - - # Ignore missing SMTP class in PHPMailer 5.2.21 - # see https://github.com/PHPMailer/PHPMailer/blob/v5.2.21/class.smtp.php - message: '(^.+ an unknown class SMTP\.$)' - path: addon/mailstream/phpmailer - - - - # Ignore missing SMTP class in PHPMailer 5.2.21 - # see https://github.com/PHPMailer/PHPMailer/blob/v5.2.21/class.smtp.php - message: '(^Property .+ has unknown class SMTP as its type\.$)' - path: addon/mailstream/phpmailer - - - - # Ignore missing SMTP class in PHPMailer 5.2.21 - # see https://github.com/PHPMailer/PHPMailer/blob/v5.2.21/class.smtp.php - message: '(^Method .+ has invalid return type SMTP\.$)' - path: addon/mailstream/phpmailer - - - - # Ignore missing SMTP class in PHPMailer 5.2.21 - # see https://github.com/PHPMailer/PHPMailer/blob/v5.2.21/class.smtp.php - message: '(^Instantiated class SMTP not found\.$)' - path: addon/mailstream/phpmailer diff --git a/.woodpecker/.code_standards_check.yml b/.woodpecker/.code_standards_check.yml index 1217ea3f0b..d147e8fc69 100644 --- a/.woodpecker/.code_standards_check.yml +++ b/.woodpecker/.code_standards_check.yml @@ -43,14 +43,10 @@ steps: - apt-get update -q - DEBIAN_FRONTEND=noninteractive apt-get install -q -y git - if [ ! -z "$${CI_COMMIT_PULL_REQUEST}" ]; then - git fetch --no-tags origin ${CI_COMMIT_TARGET_BRANCH}; - CHANGED_FILES="$(git diff --name-only --diff-filter=ACMRTUXB $(git merge-base FETCH_HEAD origin/${CI_COMMIT_TARGET_BRANCH})..${CI_COMMIT_SHA})"; + git fetch --no-tags --unshallow origin ${CI_COMMIT_TARGET_BRANCH}:refs/remotes/origin/${CI_COMMIT_TARGET_BRANCH}; + CHANGED_FILES="$(git diff --name-only --diff-filter=ACMRTUXB $(git merge-base ${CI_COMMIT_SHA} origin/${CI_COMMIT_TARGET_BRANCH})..${CI_COMMIT_SHA})"; else CHANGED_FILES="$(git diff --name-only --diff-filter=ACMRTUXB ${CI_COMMIT_SHA})"; fi - - if ! echo "$${CHANGED_FILES}" | grep -qE "^(\\.php-cs-fixer(\\.dist)?\\.php|composer\\.lock)$"; then - EXTRA_ARGS=$(printf -- '--path-mode=intersection\n--\n%s' "$${CHANGED_FILES}"); - else - EXTRA_ARGS=''; - fi + - EXTRA_ARGS="--path-mode=intersection -- $${CHANGED_FILES}"; - ./bin/dev/php-cs-fixer/vendor/bin/php-cs-fixer check --config=.php-cs-fixer.dist.php -v --diff --using-cache=no $${EXTRA_ARGS} diff --git a/.woodpecker/.phpunit.yml b/.woodpecker/.phpunit.yml index c1a81a2fd1..997d50a52e 100644 --- a/.woodpecker/.phpunit.yml +++ b/.woodpecker/.phpunit.yml @@ -78,9 +78,16 @@ steps: image: friendicaci/php${PHP_MAJOR_VERSION}:php${PHP_VERSION} when: matrix: - PHP_MAJOR_VERSION: 8.2 + PHP_MAJOR_VERSION: 8.3 commands: - bin/composer.phar run phpstan; + phpstan-addons: + image: friendicaci/php${PHP_MAJOR_VERSION}:php${PHP_VERSION} + when: + matrix: + PHP_MAJOR_VERSION: 8.3 + commands: + - bin/composer.phar run phpstan-addons; test: image: friendicaci/php${PHP_MAJOR_VERSION}:php${PHP_VERSION} environment: diff --git a/composer.json b/composer.json index a2c9eea3c9..d86bfac2b2 100644 --- a/composer.json +++ b/composer.json @@ -36,7 +36,7 @@ "friendica/json-ld": "^1.0", "geekwright/po": "^2.0", "guzzlehttp/guzzle": "^7", - "guzzlehttp/oauth-subscriber": "^0.6", + "guzzlehttp/oauth-subscriber": "^0.8", "kornrunner/blurhash": "^1.2", "league/html-to-markdown": "^4.8", "level-2/dice": "^4", @@ -153,6 +153,7 @@ "dms/phpunit-arraysubset-asserts": "^0.3.1", "mikey179/vfsstream": "^1.6", "mockery/mockery": "^1.3", + "php-mock/php-mock-mockery": "^1.5", "php-mock/php-mock-phpunit": "^2.10", "phpmd/phpmd": "^2.15", "phpstan/phpstan": "^2.0", @@ -163,6 +164,7 @@ "test:unit": "phpunit -c tests/phpunit.xml --testsuite unit", "phpmd": "phpmd src/ text .phpmd-ruleset.xml --color --cache", "phpstan": "phpstan analyze --memory-limit 1024M --configuration .phpstan.neon", + "phpstan-addons": "phpstan analyze --memory-limit 1024M --configuration .phpstan-addons.neon", "lint": "find . -name \\*.php -not -path './vendor/*' -not -path './view/asset/*' -print0 | xargs -0 -n1 php -l", "docker:translate": "docker run --rm -v $PWD:/data -w /data friendicaci/transifex bin/run_xgettext.sh", "lang:recreate": "bin/run_xgettext.sh", diff --git a/composer.lock b/composer.lock index e12cc6533c..bca6007c01 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": "b77bf714197f04022a5feb001bf07852", + "content-hash": "8aa73e21ed198d8013c09de14e8230cd", "packages": [ { "name": "asika/simple-console", @@ -893,22 +893,22 @@ }, { "name": "guzzlehttp/guzzle", - "version": "7.8.1", + "version": "7.9.3", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "41042bc7ab002487b876a0683fc8dce04ddce104" + "reference": "7b2f29fe81dc4da0ca0ea7d42107a0845946ea77" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/41042bc7ab002487b876a0683fc8dce04ddce104", - "reference": "41042bc7ab002487b876a0683fc8dce04ddce104", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/7b2f29fe81dc4da0ca0ea7d42107a0845946ea77", + "reference": "7b2f29fe81dc4da0ca0ea7d42107a0845946ea77", "shasum": "" }, "require": { "ext-json": "*", - "guzzlehttp/promises": "^1.5.3 || ^2.0.1", - "guzzlehttp/psr7": "^1.9.1 || ^2.5.1", + "guzzlehttp/promises": "^1.5.3 || ^2.0.3", + "guzzlehttp/psr7": "^2.7.0", "php": "^7.2.5 || ^8.0", "psr/http-client": "^1.0", "symfony/deprecation-contracts": "^2.2 || ^3.0" @@ -919,9 +919,9 @@ "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", "ext-curl": "*", - "php-http/client-integration-tests": "dev-master#2c025848417c1135031fdf9c728ee53d0a7ceaee as 3.0.999", + "guzzle/client-integration-tests": "3.0.2", "php-http/message-factory": "^1.1", - "phpunit/phpunit": "^8.5.36 || ^9.6.15", + "phpunit/phpunit": "^8.5.39 || ^9.6.20", "psr/log": "^1.1 || ^2.0 || ^3.0" }, "suggest": { @@ -997,6 +997,10 @@ "rest", "web service" ], + "support": { + "issues": "https://github.com/guzzle/guzzle/issues", + "source": "https://github.com/guzzle/guzzle/tree/7.9.3" + }, "funding": [ { "url": "https://github.com/GrahamCampbell", @@ -1011,37 +1015,39 @@ "type": "tidelift" } ], - "time": "2023-12-03T20:35:24+00:00" + "time": "2025-03-27T13:37:11+00:00" }, { "name": "guzzlehttp/oauth-subscriber", - "version": "0.6.0", + "version": "0.8.1", "source": { "type": "git", "url": "https://github.com/guzzle/oauth-subscriber.git", - "reference": "8d6cab29f8397e5712d00a383eeead36108a3c1f" + "reference": "92b619b03bd21396e51c62e6bce83467d2ce8f53" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/oauth-subscriber/zipball/8d6cab29f8397e5712d00a383eeead36108a3c1f", - "reference": "8d6cab29f8397e5712d00a383eeead36108a3c1f", + "url": "https://api.github.com/repos/guzzle/oauth-subscriber/zipball/92b619b03bd21396e51c62e6bce83467d2ce8f53", + "reference": "92b619b03bd21396e51c62e6bce83467d2ce8f53", "shasum": "" }, "require": { - "guzzlehttp/guzzle": "^6.5|^7.2", - "guzzlehttp/psr7": "^1.7|^2.0", - "php": ">=5.5.0" + "guzzlehttp/guzzle": "^7.9", + "guzzlehttp/psr7": "^2.7", + "php": "^7.2.5 || ^8.0" }, "require-dev": { - "phpunit/phpunit": "~4.0|^9.3.3" + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.36 || ^9.6.15" }, "suggest": { "ext-openssl": "Required to sign using RSA-SHA1" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "0.6-dev" + "bamarni-bin": { + "bin-links": true, + "forward-command": false } }, "autoload": { @@ -1054,32 +1060,64 @@ "MIT" ], "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, { "name": "Michael Dowling", "email": "mtdowling@gmail.com", "homepage": "https://github.com/mtdowling" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" } ], "description": "Guzzle OAuth 1.0 subscriber", - "homepage": "http://guzzlephp.org/", "keywords": [ "Guzzle", "oauth" ], - "time": "2021-07-13T12:01:32+00:00" + "support": { + "issues": "https://github.com/guzzle/oauth-subscriber/issues", + "source": "https://github.com/guzzle/oauth-subscriber/tree/0.8.1" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/oauth-subscriber", + "type": "tidelift" + } + ], + "time": "2025-01-06T19:15:59+00:00" }, { "name": "guzzlehttp/promises", - "version": "2.0.2", + "version": "2.2.0", "source": { "type": "git", "url": "https://github.com/guzzle/promises.git", - "reference": "bbff78d96034045e58e13dedd6ad91b5d1253223" + "reference": "7c69f28996b0a6920945dd20b3857e499d9ca96c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/bbff78d96034045e58e13dedd6ad91b5d1253223", - "reference": "bbff78d96034045e58e13dedd6ad91b5d1253223", + "url": "https://api.github.com/repos/guzzle/promises/zipball/7c69f28996b0a6920945dd20b3857e499d9ca96c", + "reference": "7c69f28996b0a6920945dd20b3857e499d9ca96c", "shasum": "" }, "require": { @@ -1087,7 +1125,7 @@ }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", - "phpunit/phpunit": "^8.5.36 || ^9.6.15" + "phpunit/phpunit": "^8.5.39 || ^9.6.20" }, "type": "library", "extra": { @@ -1131,6 +1169,10 @@ "keywords": [ "promise" ], + "support": { + "issues": "https://github.com/guzzle/promises/issues", + "source": "https://github.com/guzzle/promises/tree/2.2.0" + }, "funding": [ { "url": "https://github.com/GrahamCampbell", @@ -1145,20 +1187,20 @@ "type": "tidelift" } ], - "time": "2023-12-03T20:19:20+00:00" + "time": "2025-03-27T13:27:01+00:00" }, { "name": "guzzlehttp/psr7", - "version": "2.6.2", + "version": "2.7.1", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "45b30f99ac27b5ca93cb4831afe16285f57b8221" + "reference": "c2270caaabe631b3b44c85f99e5a04bbb8060d16" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/45b30f99ac27b5ca93cb4831afe16285f57b8221", - "reference": "45b30f99ac27b5ca93cb4831afe16285f57b8221", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/c2270caaabe631b3b44c85f99e5a04bbb8060d16", + "reference": "c2270caaabe631b3b44c85f99e5a04bbb8060d16", "shasum": "" }, "require": { @@ -1173,8 +1215,8 @@ }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.2", - "http-interop/http-factory-tests": "^0.9", - "phpunit/phpunit": "^8.5.36 || ^9.6.15" + "http-interop/http-factory-tests": "0.9.0", + "phpunit/phpunit": "^8.5.39 || ^9.6.20" }, "suggest": { "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" @@ -1243,6 +1285,10 @@ "uri", "url" ], + "support": { + "issues": "https://github.com/guzzle/psr7/issues", + "source": "https://github.com/guzzle/psr7/tree/2.7.1" + }, "funding": [ { "url": "https://github.com/GrahamCampbell", @@ -1257,7 +1303,7 @@ "type": "tidelift" } ], - "time": "2023-12-03T20:05:35+00:00" + "time": "2025-03-27T12:30:47+00:00" }, { "name": "kornrunner/blurhash", @@ -3333,24 +3379,27 @@ "psr", "psr-18" ], + "support": { + "source": "https://github.com/php-fig/http-client" + }, "time": "2023-09-23T14:17:50+00:00" }, { "name": "psr/http-factory", - "version": "1.0.2", + "version": "1.1.0", "source": { "type": "git", "url": "https://github.com/php-fig/http-factory.git", - "reference": "e616d01114759c4c489f93b099585439f795fe35" + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-factory/zipball/e616d01114759c4c489f93b099585439f795fe35", - "reference": "e616d01114759c4c489f93b099585439f795fe35", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/2b4765fddfe3b508ac62f829e852b1501d3f6e8a", + "reference": "2b4765fddfe3b508ac62f829e852b1501d3f6e8a", "shasum": "" }, "require": { - "php": ">=7.0.0", + "php": ">=7.1", "psr/http-message": "^1.0 || ^2.0" }, "type": "library", @@ -3374,7 +3423,7 @@ "homepage": "https://www.php-fig.org/" } ], - "description": "Common interfaces for PSR-7 HTTP message factories", + "description": "PSR-17: Common interfaces for PSR-7 HTTP message factories", "keywords": [ "factory", "http", @@ -3385,7 +3434,10 @@ "request", "response" ], - "time": "2023-04-10T20:10:41+00:00" + "support": { + "source": "https://github.com/php-fig/http-factory" + }, + "time": "2024-04-15T12:06:14+00:00" }, { "name": "psr/http-message", @@ -3435,6 +3487,9 @@ "request", "response" ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/1.1" + }, "time": "2023-04-04T09:50:52+00:00" }, { @@ -3525,6 +3580,10 @@ } ], "description": "A polyfill for getallheaders.", + "support": { + "issues": "https://github.com/ralouphie/getallheaders/issues", + "source": "https://github.com/ralouphie/getallheaders/tree/develop" + }, "time": "2019-03-08T08:55:37+00:00" }, { @@ -3580,16 +3639,16 @@ }, { "name": "smarty/smarty", - "version": "v4.5.1", + "version": "v4.5.3", "source": { "type": "git", "url": "https://github.com/smarty-php/smarty.git", - "reference": "42b869e3a098b1c8ee07922ccded0e5a5dceadcd" + "reference": "9fc96a13dbaf546c3d7bcf95466726578cd4e0fa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/smarty-php/smarty/zipball/42b869e3a098b1c8ee07922ccded0e5a5dceadcd", - "reference": "42b869e3a098b1c8ee07922ccded0e5a5dceadcd", + "url": "https://api.github.com/repos/smarty-php/smarty/zipball/9fc96a13dbaf546c3d7bcf95466726578cd4e0fa", + "reference": "9fc96a13dbaf546c3d7bcf95466726578cd4e0fa", "shasum": "" }, "require": { @@ -3637,7 +3696,12 @@ "keywords": [ "templating" ], - "time": "2024-03-18T14:19:07+00:00" + "support": { + "forum": "https://github.com/smarty-php/smarty/discussions", + "issues": "https://github.com/smarty-php/smarty/issues", + "source": "https://github.com/smarty-php/smarty/tree/v4.5.3" + }, + "time": "2024-05-28T21:46:01+00:00" }, { "name": "spomky-labs/base64url", @@ -5441,6 +5505,71 @@ ], "time": "2024-02-10T21:37:25+00:00" }, + { + "name": "php-mock/php-mock-mockery", + "version": "1.5.0", + "source": { + "type": "git", + "url": "https://github.com/php-mock/php-mock-mockery.git", + "reference": "291994acdc26daf1e3c659cfbe58b01eeb180b7f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-mock/php-mock-mockery/zipball/291994acdc26daf1e3c659cfbe58b01eeb180b7f", + "reference": "291994acdc26daf1e3c659cfbe58b01eeb180b7f", + "shasum": "" + }, + "require": { + "mockery/mockery": "^1", + "php": ">=5.6", + "php-mock/php-mock-integration": "^2.2.1 || ^3.0" + }, + "require-dev": { + "phpunit/phpunit": "^4|^5|^8" + }, + "type": "library", + "autoload": { + "psr-4": { + "phpmock\\mockery\\": "classes/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "WTFPL" + ], + "authors": [ + { + "name": "Markus Malkusch", + "email": "markus@malkusch.de", + "homepage": "http://markus.malkusch.de", + "role": "Developer" + } + ], + "description": "Mock built-in PHP functions (e.g. time()) with Mockery. This package relies on PHP's namespace fallback policy. No further extension is needed.", + "homepage": "https://github.com/php-mock/php-mock-mockery", + "keywords": [ + "BDD", + "TDD", + "function", + "mock", + "mockery", + "stub", + "test", + "test double", + "testing" + ], + "support": { + "issues": "https://github.com/php-mock/php-mock-mockery/issues", + "source": "https://github.com/php-mock/php-mock-mockery/tree/1.5.0" + }, + "funding": [ + { + "url": "https://github.com/michalbundyra", + "type": "github" + } + ], + "time": "2025-03-08T19:46:20+00:00" + }, { "name": "php-mock/php-mock-phpunit", "version": "2.10.0", @@ -7569,9 +7698,9 @@ "ext-simplexml": "*", "ext-xml": "*" }, - "platform-dev": {}, + "platform-dev": [], "platform-overrides": { "php": "7.4" }, - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.3.0" } diff --git a/doc/Config.md b/doc/Config.md index 3e0459f9c8..afd4e65277 100644 --- a/doc/Config.md +++ b/doc/Config.md @@ -43,7 +43,7 @@ Some examples of common known configuration files: Addons can define their own default configuration values in `addon/[addon]/config/[addon].config.php` which is loaded when the addon is activated. If needed, an alternative `config` path can be used by using the `FRIENDICA_CONFIG_DIR` environment variable (full path required!). -This is useful in case of hardening the system by separating configuration from program binaries. +This is useful in case of hardening the system by separating configuration from program binaries. ### Static Configuration location @@ -160,16 +160,6 @@ $a->config['register_policy'] = REGISTER_CLOSED;
-$a->path = "value";
-
-
-'system' => [
-	'urlpath' => 'value',
-],
-
- - -
 $default_timezone = "value";
 
@@ -313,7 +303,7 @@ Enabling the admin panel for an account, and thus making the account holder admi
     'config' => [
         'admin_email' => 'someone@example.com',
     ]
-    
+
 
 Where you have to match the email address used for the account with the one you enter to the `config/local.config.php` file.
 If more than one account should be able to access the admin panel, separate the email addresses with a comma.
diff --git a/doc/Install.md b/doc/Install.md
index f613b2ffc3..3c162d519b 100644
--- a/doc/Install.md
+++ b/doc/Install.md
@@ -218,7 +218,6 @@ All options will be saved in the `config/local.config.php` and are overruling th
 -	`-U|--dbuser ` The username of the mysql/mariadb database login (env `MYSQL_USER` or `MYSQL_USERNAME`)
 -	`-P|--dbpass ` The password of the mysql/mariadb database login (env `MYSQL_PASSWORD`)
 -	`-d|--dbdata ` The name of the mysql/mariadb database (env `MYSQL_DATABASE`)
--	`-u|--urlpath ` The URL path of Friendica - f.e. '/friendica' (env `FRIENDICA_URL_PATH`)
 -	`-b|--phppath ` The path of the PHP binary (env `FRIENDICA_PHP_PATH`)
 -	`-A|--admin ` The admin email address of Friendica (env `FRIENDICA_ADMIN_MAIL`)
 -	`-T|--tz ` The timezone of Friendica (env `FRIENDICA_TZ`)
diff --git a/doc/Settings.md b/doc/Settings.md
index 00c8be2c2e..7ca0e22286 100644
--- a/doc/Settings.md
+++ b/doc/Settings.md
@@ -419,7 +419,7 @@ We strongly discourage you from doing so, as this will break federation to other
 Say you have a subdirectory for tests and put Friendica into a further subdirectory, the config would be:
 
 	'system' => [
-		'urlpath' => 'tests/friendica',
+		'url' => 'https://example.com/tests/friendica',
 	],
 
 ## Other exceptions
diff --git a/doc/de/Settings.md b/doc/de/Settings.md
index 34b349e885..cee965ccaf 100644
--- a/doc/de/Settings.md
+++ b/doc/de/Settings.md
@@ -410,7 +410,7 @@ Wir raten allerdings dringen davon ab, da es die Interoperabilität mit anderen
 Mal angenommen, du hast ein Unterverzeichnis tests und willst Friendica in ein weiteres Unterverzeichnis installieren, dann lautet die Konfiguration hierfĂĽr:
 
 	'system' => [
-		'urlpath' => 'tests/friendica',
+		'url' => 'https://example.com/tests/friendica',
 	],
 
 ## Weitere Ausnahmen
diff --git a/doc/stats.md b/doc/stats.md
new file mode 100644
index 0000000000..1b6a2dfd2a
--- /dev/null
+++ b/doc/stats.md
@@ -0,0 +1,35 @@
+Monitoring
+===========
+
+* [Home](help)
+
+## Endpoints
+
+Currently, there are two endpoints for statistics available
+
+-	`/stats` Returns some basic statistics of the current node
+-	`/stats/caching` Returns statistics of cache or lock instances, which are used for the currend node
+
+### `/stats`
+
+The statistics contain data about the worker performance, the last cron call, number of reports, inbound and outbound packets, posts and comments.
+
+### `/stats/caching`
+
+The statistics contain data about the opcache, the used caching (like memory usage, hits/misses, entries, ...) and the used lock (including the cache data)
+
+## Configuration
+
+Please define 'stats_key' in your local.config.php in the 'system' section to be able to access the statistics page at /stats?key=your-defined-stats_key
+
+## 3rd Party monitoring tools
+
+### Zabbix
+
+To monitor the health status of your Friendica installation, you can use for example a tool like Zabbix.
+
+### Prometheus
+
+To use [prometheus](https://prometheus.io) for gathering metrics, use the [Friendica exporter](https://git.friendi.ca/friendica/friendica-exporter).
+
+You can find the installation instructions here: https://git.friendi.ca/friendica/friendica-exporter#installation
diff --git a/doc/tools.md b/doc/tools.md
index 2a273e3650..fac1f4b392 100644
--- a/doc/tools.md
+++ b/doc/tools.md
@@ -78,15 +78,3 @@ The following will compress */var/log/friendica* (assuming this is the location
 		daily
 		rotate 2
 	}
-
-### Zabbix
-
-To monitor the health status of your Friendica installation, you can use for example a tool like Zabbix. Please define 'stats_key' in your local.config.php in the 'system' section to be able to access the statistics page at /stats?key=your-defined-stats_key
-
-The statistics contain data about the worker performance, the last cron call, number of reports, inbound and outbound packets, posts and comments.
-
-### Prometheus
-
-To use [prometheus](https://prometheus.io) for gathering metrics, use the [Friendica exporter](https://git.friendi.ca/friendica/friendica-exporter).
-
-You can find the installation instructions here: https://git.friendi.ca/friendica/friendica-exporter#installation
diff --git a/mod/item.php b/mod/item.php
index 46f2a219e3..e416126a4a 100644
--- a/mod/item.php
+++ b/mod/item.php
@@ -46,7 +46,7 @@ function item_post()
 	$eventDispatcher = DI::eventDispatcher();
 
 	$_REQUEST = $eventDispatcher->dispatch(
-		new ArrayFilterEvent(ArrayFilterEvent::POST_LOCAL_START, $_REQUEST)
+		new ArrayFilterEvent(ArrayFilterEvent::INSERT_POST_LOCAL_START, $_REQUEST)
 	)->getArray();
 
 	$return_path = $_REQUEST['return'] ?? '';
@@ -281,10 +281,16 @@ function item_process(array $post, array $request, bool $preview, string $return
 
 	$eventDispatcher = DI::eventDispatcher();
 
-	$post = $eventDispatcher->dispatch(
-		new ArrayFilterEvent(ArrayFilterEvent::POST_LOCAL, $post)
+	$hook_data = [
+		'item' => $post,
+	];
+
+	$hook_data = $eventDispatcher->dispatch(
+		new ArrayFilterEvent(ArrayFilterEvent::INSERT_POST_LOCAL, $hook_data)
 	)->getArray();
 
+	$post = $hook_data['item'] ?? $post;
+
 	unset($post['edit']);
 	unset($post['self']);
 	unset($post['api_source']);
diff --git a/mod/photos.php b/mod/photos.php
index 82b5e43960..57076fc933 100644
--- a/mod/photos.php
+++ b/mod/photos.php
@@ -1022,7 +1022,7 @@ function photos_content()
 				}
 			}
 			$tags = ['title' => DI::l10n()->t('Tags: '), 'tags' => $tag_arr];
-			if ($cmd === 'edit') {
+			if ($cmd === 'edit' && !empty($tag_arr)) {
 				$tags['removeanyurl'] = 'post/' . $link_item['id'] . '/tag/remove?return=' . urlencode(DI::args()->getCommand());
 				$tags['removetitle']  = DI::l10n()->t('[Select tags to remove]');
 			}
diff --git a/src/App/BaseURL.php b/src/App/BaseURL.php
index 83416866f0..7aaafdc8bf 100644
--- a/src/App/BaseURL.php
+++ b/src/App/BaseURL.php
@@ -17,8 +17,7 @@ use Psr\Http\Message\UriInterface;
 use Psr\Log\LoggerInterface;
 
 /**
- * A class which checks and contains the basic
- * environment for the BaseURL (url, urlpath, ssl_policy, hostname, scheme)
+ * A class which checks and contains the basic environment for the BaseURL (url)
  */
 class BaseURL extends Uri implements UriInterface
 {
@@ -43,8 +42,7 @@ class BaseURL extends Uri implements UriInterface
 		/* Relative script path to the web server root
 		 * Not all of those $_SERVER properties can be present, so we do by inverse priority order
 		 */
-		$relativeScriptPath =
-			($server['REDIRECT_URL'] ?? '') ?:
+		$relativeScriptPath = ($server['REDIRECT_URL'] ?? '') ?:
 				($server['REDIRECT_URI'] ?? '') ?:
 					($server['REDIRECT_SCRIPT_URL'] ?? '') ?:
 						($server['SCRIPT_URL'] ?? '') ?:
diff --git a/src/App/Page.php b/src/App/Page.php
index a9fc9674a0..623f464086 100644
--- a/src/App/Page.php
+++ b/src/App/Page.php
@@ -198,7 +198,7 @@ class Page implements ArrayAccess
 	) {
 		// Default title: current module called
 		if (empty($this->page['title']) && $args->getModuleName()) {
-			$this->page['title'] = ucfirst($args->getModuleName());
+			$this->page['title'] = $l10n->t(ucfirst($args->getModuleName()));
 		}
 
 		// Prepend the sitename to the page title
diff --git a/src/BaseCollection.php b/src/BaseCollection.php
index b30dbfc6b0..d7d707960a 100644
--- a/src/BaseCollection.php
+++ b/src/BaseCollection.php
@@ -24,7 +24,7 @@ class BaseCollection extends \ArrayIterator
 	 * @param BaseEntity[] $entities
 	 * @param int|null     $totalCount
 	 */
-	public function __construct(array $entities = [], int $totalCount = null)
+	public function __construct(array $entities = [], ?int $totalCount = null)
 	{
 		parent::__construct($entities);
 
@@ -102,7 +102,7 @@ class BaseCollection extends \ArrayIterator
 	 * @return BaseCollection
 	 * @see array_filter()
 	 */
-	public function filter(callable $callback = null, int $flag = 0): BaseCollection
+	public function filter(?callable $callback = null, int $flag = 0): BaseCollection
 	{
 		$class = get_class($this);
 
@@ -111,8 +111,6 @@ class BaseCollection extends \ArrayIterator
 
 	/**
 	 * Reverse the orders of the elements in the collection
-	 *
-	 * @return $this
 	 */
 	public function reverse(): BaseCollection
 	{
@@ -125,7 +123,6 @@ class BaseCollection extends \ArrayIterator
 	 * Split the collection in smaller collections no bigger than the provided length
 	 *
 	 * @param int $length
-	 * @return static[]
 	 */
 	public function chunk(int $length): array
 	{
@@ -133,11 +130,14 @@ class BaseCollection extends \ArrayIterator
 			throw new \RangeException('BaseCollection->chunk(): Size parameter expected to be greater than 0');
 		}
 
-		return array_map(function ($array) {
-			$class = get_class($this);
+		return array_map(
+			function ($array) {
+				$class = get_class($this);
 
-			return new $class($array);
-		}, array_chunk($this->getArrayCopy(), $length));
+				return new $class($array);
+			},
+			array_chunk($this->getArrayCopy(), $length)
+		);
 	}
 
 
diff --git a/src/BaseRepository.php b/src/BaseRepository.php
index 068a9f4b63..bf495a6a5d 100644
--- a/src/BaseRepository.php
+++ b/src/BaseRepository.php
@@ -130,17 +130,33 @@ abstract class BaseRepository
 	}
 
 	/**
-	 * @param array $condition
-	 * @param array $params
-	 * @return BaseEntity
+	 * Selects the fields of the first row as array
+	 *
+	 * @throws NotFoundException
+	 *
+	 * @return array The resulted fields as array
+	 */
+	final protected function _selectFirstRowAsArray(array $condition, array $params = []): array
+	{
+		$fields = $this->db->selectFirst(static::$table_name, [], $condition, $params);
+
+		if (!$this->db->isResult($fields)) {
+			throw new NotFoundException();
+		}
+
+		return $fields;
+	}
+
+	/**
+	 * @deprecated 2025.05 Use `\Friendica\BaseRepository::_selectFirstRowAsArray()` instead
+	 *
 	 * @throws NotFoundException
 	 */
 	protected function _selectOne(array $condition, array $params = []): BaseEntity
 	{
-		$fields = $this->db->selectFirst(static::$table_name, [], $condition, $params);
-		if (!$this->db->isResult($fields)) {
-			throw new NotFoundException();
-		}
+		@trigger_error('`' . __METHOD__ . '()` is deprecated since 2025.05 and will be removed after 5 months, use `\Friendica\BaseRepository::_selectFirstRowAsArray()` instead.', E_USER_DEPRECATED);
+
+		$fields = $this->_selectFirstRowAsArray( $condition, $params);
 
 		return $this->factory->createFromTableRow($fields);
 	}
diff --git a/src/Console/Contact.php b/src/Console/Contact.php
index 8173054826..d54979c11b 100644
--- a/src/Console/Contact.php
+++ b/src/Console/Contact.php
@@ -56,7 +56,7 @@ HELP;
 		return $help;
 	}
 
-	public function __construct(Mode $appMode, array $argv = null)
+	public function __construct(Mode $appMode, ?array $argv = null)
 	{
 		parent::__construct($argv);
 
@@ -84,13 +84,13 @@ HELP;
 
 		switch ($command) {
 			case 'add':
-				return $this->addContact();
+				return ($this->addContact()) ? 0 : 1;
 			case 'remove':
-				return $this->removeContact();
+				return ($this->removeContact()) ? 0 : 1;
 			case 'search':
-				return $this->searchContact();
+				return ($this->searchContact()) ? 0 : 1;
 			case 'terminate':
-				return $this->terminateContact();
+				return ($this->terminateContact()) ? 0 : 1;
 			default:
 				throw new \Asika\SimpleConsole\CommandArgsException('Wrong command.');
 		}
@@ -206,7 +206,7 @@ HELP;
 	/**
 	 * Marks a contact for removal
 	 */
-	private function removeContact()
+	private function removeContact(): bool
 	{
 		$cid = $this->getArgument(1);
 		if (empty($cid)) {
@@ -218,6 +218,8 @@ HELP;
 		}
 
 		ContactModel::remove($cid);
+
+		return true;
 	}
 
 	/**
diff --git a/src/Console/PoToPhp.php b/src/Console/PoToPhp.php
index ec5c050e70..480da23c46 100644
--- a/src/Console/PoToPhp.php
+++ b/src/Console/PoToPhp.php
@@ -134,7 +134,7 @@ HELP;
 		);
 
 		$fnname = 'string_plural_select_' . $lang;
-		$out = 'if(! function_exists("' . $fnname . '")) {' . "\n";
+		$out    = 'if(! function_exists("' . $fnname . '")) {' . "\n";
 		$out .= 'function ' . $fnname . '($n){' . "\n";
 		$out .= '	$n = intval($n);' . "\n";
 		$out .= '	' . $return . "\n";
@@ -175,11 +175,11 @@ HELP;
 	 * @param string $string
 	 * @param array|string $node
 	 */
-	private static function parse(string $string, &$node = [])
+	private static function parse(string $string, &$node)
 	{
 		// Removes extra outward parentheses
 		if (strpos($string, '(') === 0 && strrpos($string, ')') === strlen($string) - 1) {
-			$string = substr($string, 1, -1);
+			$string = (string) substr($string, 1, -1);
 		}
 
 		$q = strpos($string, '?');
@@ -192,13 +192,13 @@ HELP;
 
 		if ($q === false || $s < $q) {
 			list($then, $else) = explode(':', $string, 2);
-			$node['then'] = $then;
-			$parsedElse = [];
+			$node['then']      = $then;
+			$parsedElse        = [];
 			self::parse($else, $parsedElse);
 			$node['else'] = $parsedElse;
 		} else {
 			list($if, $thenelse) = explode('?', $string, 2);
-			$node['if'] = $if;
+			$node['if']          = $if;
 			self::parse($thenelse, $node);
 		}
 	}
@@ -214,7 +214,7 @@ HELP;
 	private static function render($tree): string
 	{
 		if (is_array($tree)) {
-			$if = trim($tree['if']);
+			$if   = trim($tree['if']);
 			$then = trim($tree['then']);
 			$else = self::render($tree['else']);
 
diff --git a/src/Console/User.php b/src/Console/User.php
index 89c217694d..5a8f62cc8c 100644
--- a/src/Console/User.php
+++ b/src/Console/User.php
@@ -106,21 +106,21 @@ HELP;
 			case 'password':
 				return $this->password();
 			case 'add':
-				return $this->addUser();
+				return ($this->addUser()) ? 0 : 1;
 			case 'allow':
-				return $this->pendingUser(true);
+				return ($this->pendingUser(true)) ? 0 : 1;
 			case 'deny':
-				return $this->pendingUser(false);
+				return ($this->pendingUser(false)) ? 0 : 1;
 			case 'block':
-				return $this->blockUser(true);
+				return ($this->blockUser(true)) ? 0 : 1;
 			case 'unblock':
-				return $this->blockUser(false);
+				return ($this->blockUser(false)) ? 0 : 1;
 			case 'delete':
-				return $this->deleteUser();
+				return ($this->deleteUser()) ? 0 : 1;
 			case 'list':
-				return $this->listUser();
+				return ($this->listUser()) ? 0 : 1;
 			case 'search':
-				return $this->searchUser();
+				return ($this->searchUser()) ? 0 : 1;
 			case 'config':
 				return ($this->configUser()) ? 0 : 1;
 			default:
@@ -178,7 +178,7 @@ HELP;
 	 *
 	 * @throws \Exception
 	 */
-	private function password()
+	private function password(): int
 	{
 		$user = $this->getUserByNick(1);
 
@@ -212,7 +212,7 @@ HELP;
 	 * @throws \Friendica\Network\HTTPException\InternalServerErrorException
 	 * @throws \ImagickException
 	 */
-	private function addUser()
+	private function addUser(): bool
 	{
 		$name   = $this->getArgument(1);
 		$nick   = $this->getArgument(2);
diff --git a/src/Contact/FriendSuggest/Repository/FriendSuggest.php b/src/Contact/FriendSuggest/Repository/FriendSuggest.php
index 1b22c7b797..833421c7f6 100644
--- a/src/Contact/FriendSuggest/Repository/FriendSuggest.php
+++ b/src/Contact/FriendSuggest/Repository/FriendSuggest.php
@@ -8,11 +8,11 @@
 namespace Friendica\Contact\FriendSuggest\Repository;
 
 use Friendica\BaseRepository;
-use Friendica\Contact\FriendSuggest\Collection;
+use Friendica\Contact\FriendSuggest\Collection\FriendSuggests as FriendSuggestsCollection;
 use Friendica\Contact\FriendSuggest\Entity\FriendSuggest as FriendSuggestEntity;
 use Friendica\Contact\FriendSuggest\Exception\FriendSuggestNotFoundException;
 use Friendica\Contact\FriendSuggest\Exception\FriendSuggestPersistenceException;
-use Friendica\Contact\FriendSuggest\Factory;
+use Friendica\Contact\FriendSuggest\Factory\FriendSuggest as FriendSuggestFactory;
 use Friendica\Database\Database;
 use Friendica\Network\HTTPException\NotFoundException;
 use Friendica\Util\DateTimeFormat;
@@ -20,12 +20,12 @@ use Psr\Log\LoggerInterface;
 
 class FriendSuggest extends BaseRepository
 {
-	/** @var Factory\FriendSuggest */
+	/** @var FriendSuggestFactory */
 	protected $factory;
 
 	protected static $table_name = 'fsuggest';
 
-	public function __construct(Database $database, LoggerInterface $logger, Factory\FriendSuggest $factory)
+	public function __construct(Database $database, LoggerInterface $logger, FriendSuggestFactory $factory)
 	{
 		parent::__construct($database, $logger, $factory);
 	}
@@ -49,20 +49,17 @@ class FriendSuggest extends BaseRepository
 	 */
 	private function selectOne(array $condition, array $params = []): FriendSuggestEntity
 	{
-		return parent::_selectOne($condition, $params);
+		$fields = $this->_selectFirstRowAsArray($condition, $params);
+
+		return $this->factory->createFromTableRow($fields);
 	}
 
 	/**
-	 * @param array $condition
-	 * @param array $params
-	 *
-	 * @return Collection\FriendSuggests
-	 *
 	 * @throws \Exception
 	 */
-	private function select(array $condition, array $params = []): Collection\FriendSuggests
+	private function select(array $condition, array $params = []): FriendSuggestsCollection
 	{
-		return new Collection\FriendSuggests(parent::_select($condition, $params)->getArrayCopy());
+		return new FriendSuggestsCollection(parent::_select($condition, $params)->getArrayCopy());
 	}
 
 	/**
@@ -78,13 +75,9 @@ class FriendSuggest extends BaseRepository
 	}
 
 	/**
-	 * @param int $cid
-	 *
-	 * @return Collection\FriendSuggests
-	 *
 	 * @throws FriendSuggestPersistenceException In case the underlying storage cannot select the suggestion
 	 */
-	public function selectForContact(int $cid): Collection\FriendSuggests
+	public function selectForContact(int $cid): FriendSuggestsCollection
 	{
 		try {
 			return $this->select(['cid' => $cid]);
@@ -114,13 +107,9 @@ class FriendSuggest extends BaseRepository
 	}
 
 	/**
-	 * @param Collection\FriendSuggests $fsuggests
-	 *
-	 * @return bool
-	 *
 	 * @throws FriendSuggestNotFoundException in case the underlying storage cannot delete the suggestion
 	 */
-	public function delete(Collection\FriendSuggests $fsuggests): bool
+	public function delete(FriendSuggestsCollection $fsuggests): bool
 	{
 		try {
 			$ids = $fsuggests->column('id');
diff --git a/src/Contact/Introduction/Repository/Introduction.php b/src/Contact/Introduction/Repository/Introduction.php
index 562b3c472f..a624017a2a 100644
--- a/src/Contact/Introduction/Repository/Introduction.php
+++ b/src/Contact/Introduction/Repository/Introduction.php
@@ -10,9 +10,9 @@ namespace Friendica\Contact\Introduction\Repository;
 use Friendica\BaseRepository;
 use Friendica\Contact\Introduction\Exception\IntroductionNotFoundException;
 use Friendica\Contact\Introduction\Exception\IntroductionPersistenceException;
-use Friendica\Contact\Introduction\Collection;
-use Friendica\Contact\Introduction\Entity;
-use Friendica\Contact\Introduction\Factory;
+use Friendica\Contact\Introduction\Collection\Introductions as IntroductionsCollection;
+use Friendica\Contact\Introduction\Entity\Introduction as IntroductionEntity;
+use Friendica\Contact\Introduction\Factory\Introduction as IntroductionFactory;
 use Friendica\Database\Database;
 use Friendica\Network\HTTPException\NotFoundException;
 use Friendica\Util\DateTimeFormat;
@@ -20,37 +20,30 @@ use Psr\Log\LoggerInterface;
 
 class Introduction extends BaseRepository
 {
-	/** @var Factory\Introduction */
+	/** @var IntroductionFactory */
 	protected $factory;
 
 	protected static $table_name = 'intro';
 
-	public function __construct(Database $database, LoggerInterface $logger, Factory\Introduction $factory)
+	public function __construct(Database $database, LoggerInterface $logger, IntroductionFactory $factory)
 	{
 		parent::__construct($database, $logger, $factory);
 	}
 
 	/**
-	 * @param array $condition
-	 * @param array $params
-	 *
-	 * @return Entity\Introduction
-	 *
 	 * @throws NotFoundException the underlying exception if there's no Introduction with the given conditions
 	 */
-	private function selectOne(array $condition, array $params = []): Entity\Introduction
+	private function selectOne(array $condition, array $params = []): IntroductionEntity
 	{
-		return parent::_selectOne($condition, $params);
+		$fields = $this->_selectFirstRowAsArray( $condition, $params);
+
+		return $this->factory->createFromTableRow($fields);
 	}
 
 	/**
 	 * Converts a given Introduction into a DB compatible row array
-	 *
-	 * @param Entity\Introduction $introduction
-	 *
-	 * @return array
 	 */
-	protected function convertToTableRow(Entity\Introduction $introduction): array
+	protected function convertToTableRow(IntroductionEntity $introduction): array
 	{
 		return [
 			'uid'         => $introduction->uid,
@@ -65,14 +58,9 @@ class Introduction extends BaseRepository
 	}
 
 	/**
-	 * @param int $id
-	 * @param int $uid
-	 *
-	 * @return Entity\Introduction
-	 *
 	 * @throws IntroductionNotFoundException in case there is no Introduction with this id
 	 */
-	public function selectOneById(int $id, int $uid): Entity\Introduction
+	public function selectOneById(int $id, int $uid): IntroductionEntity
 	{
 		try {
 			return $this->selectOne(['id' => $id, 'uid' => $uid]);
@@ -88,33 +76,30 @@ class Introduction extends BaseRepository
 	 * @param int|null $min_id
 	 * @param int|null $max_id
 	 * @param int      $limit
-	 *
-	 * @return Collection\Introductions
 	 */
-	public function selectForUser(int $uid, int $min_id = null, int $max_id = null, int $limit = self::LIMIT): Collection\Introductions
+	public function selectForUser(int $uid, ?int $min_id = null, ?int $max_id = null, int $limit = self::LIMIT): IntroductionsCollection
 	{
 		try {
 			$BaseCollection = parent::_selectByBoundaries(
 				['`uid` = ? AND NOT `ignore`',$uid],
 				['order' => ['id' => 'DESC']],
-				$min_id, $max_id, $limit);
+				$min_id,
+				$max_id,
+				$limit
+			);
 		} catch (\Exception $e) {
 			throw new IntroductionPersistenceException(sprintf('Cannot select Introductions for used %d', $uid), $e);
 		}
 
-		return new Collection\Introductions($BaseCollection->getArrayCopy(), $BaseCollection->getTotalCount());
+		return new IntroductionsCollection($BaseCollection->getArrayCopy(), $BaseCollection->getTotalCount());
 	}
 
 	/**
 	 * Selects the introduction for a given contact
 	 *
-	 * @param int $cid
-	 *
-	 * @return Entity\Introduction
-	 *
 	 * @throws IntroductionNotFoundException in case there is not Introduction for this contact
 	 */
-	public function selectForContact(int $cid): Entity\Introduction
+	public function selectForContact(int $cid): IntroductionEntity
 	{
 		try {
 			return $this->selectOne(['contact-id' => $cid]);
@@ -150,13 +135,9 @@ class Introduction extends BaseRepository
 	}
 
 	/**
-	 * @param Entity\Introduction $introduction
-	 *
-	 * @return bool
-	 *
 	 * @throws IntroductionPersistenceException in case the underlying storage cannot delete the Introduction
 	 */
-	public function delete(Entity\Introduction $introduction): bool
+	public function delete(IntroductionEntity $introduction): bool
 	{
 		if (!$introduction->id) {
 			return false;
@@ -170,13 +151,9 @@ class Introduction extends BaseRepository
 	}
 
 	/**
-	 * @param Entity\Introduction $introduction
-	 *
-	 * @return Entity\Introduction
-	 *
 	 * @throws IntroductionPersistenceException In case the underlying storage cannot save the Introduction
 	 */
-	public function save(Entity\Introduction $introduction): Entity\Introduction
+	public function save(IntroductionEntity $introduction): IntroductionEntity
 	{
 		try {
 			$fields = $this->convertToTableRow($introduction);
diff --git a/src/Contact/LocalRelationship/Repository/LocalRelationship.php b/src/Contact/LocalRelationship/Repository/LocalRelationship.php
index 2b9abc3dfc..d1a87d5569 100644
--- a/src/Contact/LocalRelationship/Repository/LocalRelationship.php
+++ b/src/Contact/LocalRelationship/Repository/LocalRelationship.php
@@ -7,59 +7,54 @@
 
 namespace Friendica\Contact\LocalRelationship\Repository;
 
-use Friendica\Contact\LocalRelationship\Entity;
-use Friendica\Contact\LocalRelationship\Exception;
-use Friendica\Contact\LocalRelationship\Factory;
+use Exception;
+use Friendica\BaseRepository;
+use Friendica\Contact\LocalRelationship\Entity\LocalRelationship as LocalRelationshipEntity;
+use Friendica\Contact\LocalRelationship\Exception\LocalRelationshipPersistenceException;
+use Friendica\Contact\LocalRelationship\Factory\LocalRelationship as LocalRelationshipFactory;
 use Friendica\Database\Database;
-use Friendica\Network\HTTPException;
+use Friendica\Network\HTTPException\NotFoundException;
 use Psr\Log\LoggerInterface;
 
-class LocalRelationship extends \Friendica\BaseRepository
+class LocalRelationship extends BaseRepository
 {
 	protected static $table_name = 'user-contact';
 
-	/** @var Factory\LocalRelationship */
+	/** @var LocalRelationshipFactory */
 	protected $factory;
 
-	public function __construct(Database $database, LoggerInterface $logger, Factory\LocalRelationship $factory)
+	public function __construct(Database $database, LoggerInterface $logger, LocalRelationshipFactory $factory)
 	{
 		parent::__construct($database, $logger, $factory);
 	}
 
 	/**
-	 * @param int $uid
-	 * @param int $cid
-	 * @return Entity\LocalRelationship
-	 * @throws HTTPException\NotFoundException
+	 * @throws NotFoundException
 	 */
-	public function selectForUserContact(int $uid, int $cid): Entity\LocalRelationship
+	public function selectForUserContact(int $uid, int $cid): LocalRelationshipEntity
 	{
-		return $this->_selectOne(['uid' => $uid, 'cid' => $cid]);
+		$fields = $this->_selectFirstRowAsArray(['uid' => $uid, 'cid' => $cid]);
+
+		return $this->factory->createFromTableRow($fields);
 	}
 
 	/**
 	 * Returns the existing local relationship between a user and a public contact or a default
 	 * relationship if it doesn't.
 	 *
-	 * @param int $uid
-	 * @param int $cid
-	 * @return Entity\LocalRelationship
-	 * @throws HTTPException\NotFoundException
+	 * @throws NotFoundException
 	 */
-	public function getForUserContact(int $uid, int $cid): Entity\LocalRelationship
+	public function getForUserContact(int $uid, int $cid): LocalRelationshipEntity
 	{
 		try {
 			return $this->selectForUserContact($uid, $cid);
-		} catch (HTTPException\NotFoundException $e) {
+		} catch (NotFoundException $e) {
 			return $this->factory->createFromTableRow(['uid' => $uid, 'cid' => $cid]);
 		}
 	}
 
 	/**
-	 * @param int $uid
-	 * @param int $cid
-	 * @return bool
-	 * @throws \Exception
+	 * @throws Exception
 	 */
 	public function existsForUserContact(int $uid, int $cid): bool
 	{
@@ -68,12 +63,8 @@ class LocalRelationship extends \Friendica\BaseRepository
 
 	/**
 	 * Converts a given local relationship into a DB compatible row array
-	 *
-	 * @param Entity\LocalRelationship $localRelationship
-	 *
-	 * @return array
 	 */
-	protected function convertToTableRow(Entity\LocalRelationship $localRelationship): array
+	protected function convertToTableRow(LocalRelationshipEntity $localRelationship): array
 	{
 		return [
 			'uid'                       => $localRelationship->userId,
@@ -97,13 +88,9 @@ class LocalRelationship extends \Friendica\BaseRepository
 	}
 
 	/**
-	 * @param Entity\LocalRelationship $localRelationship
-	 *
-	 * @return Entity\LocalRelationship
-	 *
-	 * @throws Exception\LocalRelationshipPersistenceException In case the underlying storage cannot save the LocalRelationship
+	 * @throws LocalRelationshipPersistenceException In case the underlying storage cannot save the LocalRelationship
 	 */
-	public function save(Entity\LocalRelationship $localRelationship): Entity\LocalRelationship
+	public function save(LocalRelationshipEntity $localRelationship): LocalRelationshipEntity
 	{
 		try {
 			$fields = $this->convertToTableRow($localRelationship);
@@ -111,8 +98,8 @@ class LocalRelationship extends \Friendica\BaseRepository
 			$this->db->insert(self::$table_name, $fields, Database::INSERT_UPDATE);
 
 			return $localRelationship;
-		} catch (\Exception $exception) {
-			throw new Exception\LocalRelationshipPersistenceException(sprintf('Cannot insert/update the local relationship %d for user %d', $localRelationship->contactId, $localRelationship->userId), $exception);
+		} catch (Exception $exception) {
+			throw new LocalRelationshipPersistenceException(sprintf('Cannot insert/update the local relationship %d for user %d', $localRelationship->contactId, $localRelationship->userId), $exception);
 		}
 	}
 }
diff --git a/src/Content/Conversation.php b/src/Content/Conversation.php
index 722aaf27b5..b9815525af 100644
--- a/src/Content/Conversation.php
+++ b/src/Content/Conversation.php
@@ -265,7 +265,10 @@ class Conversation
 					$phrase = $this->l10n->tt(' likes this', ' like this', $total, $spanatts);
 					break;
 				case 'dislike':
-					$phrase = $this->l10n->tt(' doesn\'t like this', ' don\'t like this', $total, $spanatts);
+					$dislike_translation_plural = ' don\'t like this';
+					// @deprecated 2025.04 this translation is scheduled for removal as a new translation has been added without the typo
+					$dislike_translation_plural = ' don\'t like this';
+					$phrase                     = $this->l10n->tt(' doesn\'t like this', $dislike_translation_plural, $total, $spanatts);
 					break;
 				case 'attendyes':
 					$phrase = $this->l10n->tt(' attends', ' attend', $total, $spanatts);
diff --git a/src/Content/Conversation/Repository/UserDefinedChannel.php b/src/Content/Conversation/Repository/UserDefinedChannel.php
index 229ef8e40c..b256e34e56 100644
--- a/src/Content/Conversation/Repository/UserDefinedChannel.php
+++ b/src/Content/Conversation/Repository/UserDefinedChannel.php
@@ -7,10 +7,10 @@
 
 namespace Friendica\Content\Conversation\Repository;
 
-use Friendica\BaseCollection;
+use Friendica\BaseRepository;
 use Friendica\Content\Conversation\Collection\UserDefinedChannels;
-use Friendica\Content\Conversation\Entity;
-use Friendica\Content\Conversation\Factory;
+use Friendica\Content\Conversation\Entity\UserDefinedChannel as UserDefinedChannelEntity;
+use Friendica\Content\Conversation\Factory\UserDefinedChannel as UserDefinedChannelFactory;
 use Friendica\Core\Config\Capability\IManageConfigValues;
 use Friendica\Database\Database;
 use Friendica\Database\DBA;
@@ -21,13 +21,16 @@ use Friendica\Model\User;
 use Friendica\Util\DateTimeFormat;
 use Psr\Log\LoggerInterface;
 
-class UserDefinedChannel extends \Friendica\BaseRepository
+class UserDefinedChannel extends BaseRepository
 {
 	protected static $table_name = 'channel';
 
+	/** @var UserDefinedChannelFactory */
+	protected $factory;
+
 	private IManageConfigValues $config;
 
-	public function __construct(Database $database, LoggerInterface $logger, Factory\UserDefinedChannel $factory, IManageConfigValues $config)
+	public function __construct(Database $database, LoggerInterface $logger, UserDefinedChannelFactory $factory, IManageConfigValues $config)
 	{
 		parent::__construct($database, $logger, $factory);
 
@@ -40,7 +43,7 @@ class UserDefinedChannel extends \Friendica\BaseRepository
 	 * @return UserDefinedChannels
 	 * @throws \Exception
 	 */
-	protected function _select(array $condition, array $params = []): BaseCollection
+	protected function _select(array $condition, array $params = []): UserDefinedChannels
 	{
 		$rows = $this->db->selectToArray(static::$table_name, [], $condition, $params);
 
@@ -62,12 +65,13 @@ class UserDefinedChannel extends \Friendica\BaseRepository
 	 *
 	 * @param int $id  The id of the user defined channel
 	 * @param int $uid The user that this channel belongs to. (Not part of the primary key)
-	 * @return Entity\UserDefinedChannel
 	 * @throws \Friendica\Network\HTTPException\NotFoundException
 	 */
-	public function selectById(int $id, int $uid): Entity\UserDefinedChannel
+	public function selectById(int $id, int $uid): UserDefinedChannelEntity
 	{
-		return $this->_selectOne(['id' => $id, 'uid' => $uid]);
+		$fields = $this->_selectFirstRowAsArray(['id' => $id, 'uid' => $uid]);
+
+		return $this->factory->createFromTableRow($fields);
 	}
 
 	/**
@@ -106,7 +110,7 @@ class UserDefinedChannel extends \Friendica\BaseRepository
 		return $this->_select(['uid' => $uid]);
 	}
 
-	public function save(Entity\UserDefinedChannel $Channel): Entity\UserDefinedChannel
+	public function save(UserDefinedChannelEntity $Channel): UserDefinedChannelEntity
 	{
 		$fields = [
 			'label'            => $Channel->label,
@@ -165,14 +169,14 @@ class UserDefinedChannel extends \Friendica\BaseRepository
 		$uids = array_column($users, 'uid');
 
 		$usercondition = ['uid' => $uids];
-		$condition = DBA::mergeConditions($usercondition, ["`languages` != ? AND `include-tags` = ? AND `full-text-search` = ? AND `circle` = ?", '', '', '', 0]);
+		$condition     = DBA::mergeConditions($usercondition, ["`languages` != ? AND `include-tags` = ? AND `full-text-search` = ? AND `circle` = ?", '', '', '', 0]);
 		foreach ($this->select($condition) as $channel) {
 			if (!empty($channel->languages) && in_array($language, $channel->languages)) {
 				return true;
 			}
 		}
 
-		$search = '';
+		$search    = '';
 		$condition = DBA::mergeConditions($usercondition, ["`full-text-search` != ? AND `circle` = ? AND `valid`", '', 0]);
 		foreach ($this->select($condition) as $channel) {
 			$search .= '(' . $channel->fullTextSearch . ') ';
@@ -197,7 +201,7 @@ class UserDefinedChannel extends \Friendica\BaseRepository
 	{
 		$condition = $this->getUserCondition();
 		$condition = DBA::mergeConditions($condition, ["`account-type` IN (?, ?) AND `uid` != ?", User::ACCOUNT_TYPE_RELAY, User::ACCOUNT_TYPE_COMMUNITY, 0]);
-		$users = $this->db->selectToArray('user', ['uid'], $condition);
+		$users     = $this->db->selectToArray('user', ['uid'], $condition);
 		if (empty($users)) {
 			return [];
 		}
@@ -210,7 +214,7 @@ class UserDefinedChannel extends \Friendica\BaseRepository
 		$disposableFullTextSearch = new DisposableFullTextSearch($this->db, $searchtext);
 
 		$filteredChannels = $this->select(['uid' => array_column($users, 'uid'), 'publish' => true, 'valid' => true])->filter(
-			function (Entity\UserDefinedChannel $channel) use ($owner_id, $reshare_id, $language, $tags, $media_type, $disposableFullTextSearch, $searchtext) {
+			function (UserDefinedChannelEntity $channel) use ($owner_id, $reshare_id, $language, $tags, $media_type, $disposableFullTextSearch, $searchtext) {
 				static $uids = [];
 
 				// Filter out channels from already picked users
diff --git a/src/Content/Item.php b/src/Content/Item.php
index 9c9cd10bed..901a2eac3d 100644
--- a/src/Content/Item.php
+++ b/src/Content/Item.php
@@ -1011,10 +1011,16 @@ class Item
 			Tag::createImplicitMentions($post['uri-id'], $post['thr-parent-id']);
 		}
 
-		$post = $this->eventDispatcher->dispatch(
-			new ArrayFilterEvent(ArrayFilterEvent::POST_LOCAL_END, $post)
+		$hook_data = [
+			'item' => $post,
+		];
+
+		$hook_data = $this->eventDispatcher->dispatch(
+			new ArrayFilterEvent(ArrayFilterEvent::INSERT_POST_LOCAL_END, $hook_data)
 		)->getArray();
 
+		$post = $hook_data['item'] ?? $post;
+
 		$author = DBA::selectFirst('contact', ['thumb'], ['uid' => $post['uid'], 'self' => true]);
 
 		foreach ($recipients as $recipient) {
diff --git a/src/Content/PageInfo.php b/src/Content/PageInfo.php
index 4d2a1180d3..a53c36b68c 100644
--- a/src/Content/PageInfo.php
+++ b/src/Content/PageInfo.php
@@ -92,6 +92,7 @@ class PageInfo
 	{
 		$eventDispatcher = DI::eventDispatcher();
 
+		/** @var array */
 		$data = $eventDispatcher->dispatch(
 			new ArrayFilterEvent(ArrayFilterEvent::PAGE_INFO, $data),
 		)->getArray();
diff --git a/src/Content/Post/Entity/PostMedia.php b/src/Content/Post/Entity/PostMedia.php
index 4b4268b750..b06e258710 100644
--- a/src/Content/Post/Entity/PostMedia.php
+++ b/src/Content/Post/Entity/PostMedia.php
@@ -13,7 +13,6 @@ use Friendica\Util\Images;
 use Friendica\Util\Proxy;
 use Psr\Http\Message\UriInterface;
 
-
 /**
  * @property-read int $id
  * @property-read int $uriId
@@ -122,8 +121,7 @@ class PostMedia extends BaseEntity
 		?UriInterface $publisherImage = null,
 		?string $blurhash = null,
 		int $id = null
-	)
-	{
+	) {
 		$this->uriId          = $uriId;
 		$this->url            = $url;
 		$this->type           = $type;
@@ -210,7 +208,7 @@ class PostMedia extends BaseEntity
 	 *
 	 * @param \GuzzleHttp\Psr7\Uri $preview
 	 * @param string               $size
-	 * @return $this
+	 * @return self
 	 */
 	public function withPreview(\GuzzleHttp\Psr7\Uri $preview, string $size = ''): self
 	{
@@ -224,8 +222,8 @@ class PostMedia extends BaseEntity
 
 		if ($newWidth && $newHeight && $size) {
 			$dimensionts = Images::getScalingDimensions($newWidth, $newHeight, Proxy::getPixelsFromSize($size));
-			$newWidth = $dimensionts['width'];
-			$newHeight = $dimensionts['height'];
+			$newWidth    = $dimensionts['width'];
+			$newHeight   = $dimensionts['height'];
 		}
 
 		return new self(
diff --git a/src/Content/Post/Factory/PostMedia.php b/src/Content/Post/Factory/PostMedia.php
index 0dbfeb4896..1ebedda72e 100644
--- a/src/Content/Post/Factory/PostMedia.php
+++ b/src/Content/Post/Factory/PostMedia.php
@@ -9,8 +9,9 @@ namespace Friendica\Content\Post\Factory;
 
 use Friendica\BaseFactory;
 use Friendica\Capabilities\ICanCreateFromTableRow;
-use Friendica\Content\Post\Entity;
-use Friendica\Network;
+use Friendica\Content\Post\Entity\PostMedia as PostMediaEntity;
+use Friendica\Network\Entity\MimeType as MimeTypeEntity;
+use Friendica\Network\Factory\MimeType as MimeTypeFactory;
 use Friendica\Util\Network as UtilNetwork;
 use GuzzleHttp\Psr7\Uri;
 use Psr\Log\LoggerInterface;
@@ -18,10 +19,10 @@ use stdClass;
 
 class PostMedia extends BaseFactory implements ICanCreateFromTableRow
 {
-	/** @var Network\Factory\MimeType */
+	/** @var MimeTypeFactory */
 	private $mimeTypeFactory;
 
-	public function __construct(Network\Factory\MimeType $mimeTypeFactory, LoggerInterface $logger)
+	public function __construct(MimeTypeFactory $mimeTypeFactory, LoggerInterface $logger)
 	{
 		parent::__construct($logger);
 
@@ -31,9 +32,9 @@ class PostMedia extends BaseFactory implements ICanCreateFromTableRow
 	/**
 	 * @inheritDoc
 	 */
-	public function createFromTableRow(array $row)
+	public function createFromTableRow(array $row): PostMediaEntity
 	{
-		return new Entity\PostMedia(
+		return new PostMediaEntity(
 			$row['uri-id'],
 			UtilNetwork::createUriFromString($row['url']),
 			$row['type'],
@@ -58,13 +59,13 @@ class PostMedia extends BaseFactory implements ICanCreateFromTableRow
 		);
 	}
 
-	public function createFromBlueskyImageEmbed(int $uriId, stdClass $image): Entity\PostMedia
+	public function createFromBlueskyImageEmbed(int $uriId, stdClass $image): PostMediaEntity
 	{
-		return new Entity\PostMedia(
+		return new PostMediaEntity(
 			$uriId,
 			new Uri($image->fullsize),
-			Entity\PostMedia::TYPE_IMAGE,
-			new Network\Entity\MimeType('unkn', 'unkn'),
+			PostMediaEntity::TYPE_IMAGE,
+			new MimeTypeEntity('unkn', 'unkn'),
 			null,
 			null,
 			null,
@@ -77,13 +78,13 @@ class PostMedia extends BaseFactory implements ICanCreateFromTableRow
 	}
 
 
-	public function createFromBlueskyExternalEmbed(int $uriId, stdClass $external): Entity\PostMedia
+	public function createFromBlueskyExternalEmbed(int $uriId, stdClass $external): PostMediaEntity
 	{
-		return new Entity\PostMedia(
+		return new PostMediaEntity(
 			$uriId,
 			new Uri($external->uri),
-			Entity\PostMedia::TYPE_HTML,
-			new Network\Entity\MimeType('text', 'html'),
+			PostMediaEntity::TYPE_HTML,
+			new MimeTypeEntity('text', 'html'),
 			null,
 			null,
 			null,
diff --git a/src/Content/Post/Repository/PostMedia.php b/src/Content/Post/Repository/PostMedia.php
index 778591c1ab..d28d15bca7 100644
--- a/src/Content/Post/Repository/PostMedia.php
+++ b/src/Content/Post/Repository/PostMedia.php
@@ -7,11 +7,10 @@
 
 namespace Friendica\Content\Post\Repository;
 
-use Friendica\BaseCollection;
 use Friendica\BaseRepository;
-use Friendica\Content\Post\Collection;
-use Friendica\Content\Post\Entity;
-use Friendica\Content\Post\Factory;
+use Friendica\Content\Post\Collection\PostMedias as PostMediasCollection;
+use Friendica\Content\Post\Entity\PostMedia as PostMediaEntity;
+use Friendica\Content\Post\Factory\PostMedia as PostMediaFactory;
 use Friendica\Database\Database;
 use Friendica\Model\Post;
 use Friendica\Util\Strings;
@@ -21,16 +20,19 @@ class PostMedia extends BaseRepository
 {
 	protected static $table_name = 'post-media';
 
-	public function __construct(Database $database, LoggerInterface $logger, Factory\PostMedia $factory)
+	/** @var PostMediaFactory */
+	protected $factory;
+
+	public function __construct(Database $database, LoggerInterface $logger, PostMediaFactory $factory)
 	{
 		parent::__construct($database, $logger, $factory);
 	}
 
-	protected function _select(array $condition, array $params = []): BaseCollection
+	protected function _select(array $condition, array $params = []): PostMediasCollection
 	{
 		$rows = $this->db->selectToArray(static::$table_name, [], $condition, $params);
 
-		$Entities = new Collection\PostMedias();
+		$Entities = new PostMediasCollection();
 		foreach ($rows as $fields) {
 			try {
 				$Entities[] = $this->factory->createFromTableRow($fields);
@@ -42,17 +44,19 @@ class PostMedia extends BaseRepository
 		return $Entities;
 	}
 
-	public function selectOneById(int $postMediaId): Entity\PostMedia
+	public function selectOneById(int $postMediaId): PostMediaEntity
 	{
-		return $this->_selectOne(['id' => $postMediaId]);
+		$fields = $this->_selectFirstRowAsArray(['id' => $postMediaId]);
+
+		return $this->factory->createFromTableRow($fields);
 	}
 
-	public function selectByUriId(int $uriId): Collection\PostMedias
+	public function selectByUriId(int $uriId): PostMediasCollection
 	{
 		return $this->_select(["`uri-id` = ? AND `type` != ?", $uriId, Post\Media::UNKNOWN]);
 	}
 
-	public function save(Entity\PostMedia $PostMedia): Entity\PostMedia
+	public function save(PostMediaEntity $PostMedia): PostMediaEntity
 	{
 		$fields = [
 			'uri-id'          => $PostMedia->uriId,
@@ -97,14 +101,14 @@ class PostMedia extends BaseRepository
 	 * @param int    $uri_id URI id
 	 * @param array  $links list of links that shouldn't be added
 	 * @param bool   $has_media
-	 * @return Collection\PostMedias[] Three collections in "visual", "link" and "additional" keys
+	 * @return PostMediasCollection[] Three collections in "visual", "link" and "additional" keys
 	 */
 	public function splitAttachments(int $uri_id, array $links = [], bool $has_media = true): array
 	{
 		$attachments = [
-			'visual'     => new Collection\PostMedias(),
-			'link'       => new Collection\PostMedias(),
-			'additional' => new Collection\PostMedias(),
+			'visual'     => new PostMediasCollection(),
+			'link'       => new PostMediasCollection(),
+			'additional' => new PostMediasCollection(),
 		];
 
 		if (!$has_media) {
@@ -137,7 +141,7 @@ class PostMedia extends BaseRepository
 
 			// Currently these two types are ignored here.
 			// Posts are added differently and contacts are not displayed as attachments.
-			if (in_array($PostMedia->type, [Entity\PostMedia::TYPE_ACCOUNT, Entity\PostMedia::TYPE_ACTIVITY])) {
+			if (in_array($PostMedia->type, [PostMediaEntity::TYPE_ACCOUNT, PostMediaEntity::TYPE_ACTIVITY])) {
 				continue;
 			}
 
@@ -148,22 +152,22 @@ class PostMedia extends BaseRepository
 			//$PostMedia->filetype = $filetype;
 			//$PostMedia->subtype = $subtype;
 
-			if ($PostMedia->type == Entity\PostMedia::TYPE_HTML || ($PostMedia->mimetype->type == 'text' && $PostMedia->mimetype->subtype == 'html')) {
+			if ($PostMedia->type == PostMediaEntity::TYPE_HTML || ($PostMedia->mimetype->type == 'text' && $PostMedia->mimetype->subtype == 'html')) {
 				$attachments['link'][] = $PostMedia;
 				continue;
 			}
 
 			if (
-				in_array($PostMedia->type, [Entity\PostMedia::TYPE_AUDIO, Entity\PostMedia::TYPE_IMAGE, Entity\PostMedia::TYPE_HLS]) ||
+				in_array($PostMedia->type, [PostMediaEntity::TYPE_AUDIO, PostMediaEntity::TYPE_IMAGE, PostMediaEntity::TYPE_HLS]) ||
 				in_array($PostMedia->mimetype->type, ['audio', 'image'])
 			) {
 				$attachments['visual'][] = $PostMedia;
-			} elseif (($PostMedia->type == Entity\PostMedia::TYPE_VIDEO) || ($PostMedia->mimetype->type == 'video')) {
+			} elseif (($PostMedia->type == PostMediaEntity::TYPE_VIDEO) || ($PostMedia->mimetype->type == 'video')) {
 				if (!empty($PostMedia->height)) {
 					// Peertube videos are delivered in many different resolutions. We pick a moderate one.
 					// Since only Peertube provides a "height" parameter, this wouldn't be executed
 					// when someone for example on Mastodon was sharing multiple videos in a single post.
-					$heights[$PostMedia->height] = (string)$PostMedia->url;
+					$heights[$PostMedia->height]     = (string)$PostMedia->url;
 					$video[(string) $PostMedia->url] = $PostMedia;
 				} else {
 					$attachments['visual'][] = $PostMedia;
diff --git a/src/Content/Text/BBCode.php b/src/Content/Text/BBCode.php
index d111437dff..9c999e789a 100644
--- a/src/Content/Text/BBCode.php
+++ b/src/Content/Text/BBCode.php
@@ -913,13 +913,18 @@ class BBCode
 			default:
 				$text = ($is_quote_share ? "\n" : '');
 
-				$contact = Contact::getByURL($attributes['profile'], false, ['network']);
+				$contact = Contact::getByURL($attributes['profile'], false, ['network', 'url', 'alias']);
 				$network = $contact['network'] ?? Protocol::PHANTOM;
+				if (!empty($contact)) {
+					$profile = Contact::getProfileLink($contact);
+				} else {
+					$profile = $attributes['profile'];
+				}
 
 				$gsid = ContactSelector::getServerIdForProfile($attributes['profile']);
 				$tpl  = Renderer::getMarkupTemplate('shared_content.tpl');
 				$text .= self::SHARED_ANCHOR . Renderer::replaceMacros($tpl, [
-					'$profile'      => $attributes['profile'],
+					'$profile'      => $profile,
 					'$avatar'       => $attributes['avatar'],
 					'$author'       => $attributes['author'],
 					'$link'         => $attributes['link'],
@@ -1205,13 +1210,13 @@ class BBCode
 	 */
 	private static function normalizeVideoLinks(string $text): string
 	{
-		$text = preg_replace("/\[youtube\]https?:\/\/www.youtube.com\/watch\?v\=(.*?)\[\/youtube\]/ism", '[youtube]$1[/youtube]', $text);
-		$text = preg_replace("/\[youtube\]https?:\/\/www.youtube.com\/embed\/(.*?)\[\/youtube\]/ism", '[youtube]$1[/youtube]', $text);
-		$text = preg_replace("/\[youtube\]https?:\/\/www.youtube.com\/shorts\/(.*?)\[\/youtube\]/ism", '[youtube]$1[/youtube]', $text);
-		$text = preg_replace("/\[youtube\]https?:\/\/youtu.be\/(.*?)\[\/youtube\]/ism", '[youtube]$1[/youtube]', $text);
+		$text = preg_replace("/\[youtube\]https?:\/\/(www\.)?youtube\.com\/watch\?v\=(.*?)\[\/youtube\]/ism", '[youtube]$2[/youtube]', $text);
+		$text = preg_replace("/\[youtube\]https?:\/\/(www\.)?youtube\.com\/embed\/(.*?)\[\/youtube\]/ism", '[youtube]$2[/youtube]', $text);
+		$text = preg_replace("/\[youtube\]https?:\/\/(www\.)?youtube\.com\/shorts\/(.*?)\[\/youtube\]/ism", '[youtube]$2[/youtube]', $text);
+		$text = preg_replace("/\[youtube\]https?:\/\/youtu\.be\/(.*?)\[\/youtube\]/ism", '[youtube]$1[/youtube]', $text);
 
-		$text = preg_replace("/\[vimeo\]https?:\/\/player.vimeo.com\/video\/([0-9]+)(.*?)\[\/vimeo\]/ism", '[vimeo]$1[/vimeo]', $text);
-		$text = preg_replace("/\[vimeo\]https?:\/\/vimeo.com\/([0-9]+)(.*?)\[\/vimeo\]/ism", '[vimeo]$1[/vimeo]', $text);
+		$text = preg_replace("/\[vimeo\]https?:\/\/player\.vimeo\.com\/video\/([0-9]+)(.*?)\[\/vimeo\]/ism", '[vimeo]$1[/vimeo]', $text);
+		$text = preg_replace("/\[vimeo\]https?:\/\/vimeo\.com\/([0-9]+)(.*?)\[\/vimeo\]/ism", '[vimeo]$1[/vimeo]', $text);
 
 		return $text;
 	}
@@ -1984,12 +1989,25 @@ class BBCode
 				'',
 				$text
 			);
-		} elseif (in_array($simple_html, [self::INTERNAL, self::EXTERNAL, self::TWITTER_API])) {
+		} elseif (in_array($simple_html, [self::EXTERNAL, self::TWITTER_API])) {
 			$text = preg_replace(
 				"/([@!])\[url\=(.*?)\](.*?)\[\/url\]/ism",
 				'$1$3',
 				$text
 			);
+		} elseif ($simple_html == self::INTERNAL) {
+			if (preg_match_all("/([@!])\[url\=(.*?)\](.*?)\[\/url\]/ism", $text, $matches, PREG_SET_ORDER)) {
+				foreach ($matches as $match) {
+					$contact = Contact::getByURL($match[2], false, ['network', 'url', 'alias']);
+					if (!empty($contact)) {
+						$url = Contact::getProfileLink($contact);
+					} else {
+						$url = $match[2];
+					}
+					$text = str_replace($match[0], '' . $match[1] . '' . $match[3] . '', $text);
+				}
+
+			}
 		} elseif ($simple_html == self::MASTODON_API) {
 			$text = preg_replace(
 				"/([@!])\[url\=(.*?)\](.*?)\[\/url\]/ism",
diff --git a/src/Content/Text/Markdown.php b/src/Content/Text/Markdown.php
index 8aa9453041..17911eae39 100644
--- a/src/Content/Text/Markdown.php
+++ b/src/Content/Text/Markdown.php
@@ -121,11 +121,13 @@ class Markdown
 		$s = str_replace('♲', html_entity_decode('♲', ENT_QUOTES, 'UTF-8'), $s);
 
 		//$s = preg_replace("/([^\]\=]|^)(https?\:\/\/)(vimeo|youtu|www\.youtube|soundcloud)([a-zA-Z0-9\:\/\-\?\&\;\.\=\_\~\#\%\$\!\+\,]+)/ism", '$1[url=$2$3$4]$2$3$4[/url]',$s);
-		$s = BBCode::pregReplaceInTag('/\[url\=?(.*?)\]https?:\/\/www.youtube.com\/watch\?v\=(.*?)\[\/url\]/ism', '[youtube]$2[/youtube]', 'url', $s);
-		$s = BBCode::pregReplaceInTag('/\[url\=https?:\/\/www.youtube.com\/watch\?v\=(.*?)\].*?\[\/url\]/ism', '[youtube]$1[/youtube]', 'url', $s);
-		$s = BBCode::pregReplaceInTag('/\[url\=https?:\/\/www.youtube.com\/shorts\/(.*?)\].*?\[\/url\]/ism', '[youtube]$1[/youtube]', 'url', $s);
-		$s = BBCode::pregReplaceInTag('/\[url\=?(.*?)\]https?:\/\/vimeo.com\/([0-9]+)(.*?)\[\/url\]/ism', '[vimeo]$2[/vimeo]', 'url', $s);
-		$s = BBCode::pregReplaceInTag('/\[url\=https?:\/\/vimeo.com\/([0-9]+)\](.*?)\[\/url\]/ism', '[vimeo]$1[/vimeo]', 'url', $s);
+		$s = BBCode::pregReplaceInTag('/\[url\=?(.*?)\]https?:\/\/www\.youtube\.com\/watch\?v\=(.*?)\[\/url\]/ism', '[youtube]$2[/youtube]', 'url', $s);
+		$s = BBCode::pregReplaceInTag('/\[url\=https?:\/\/(www\.)?youtube\.com\/watch\?v\=(.*?)\].*?\[\/url\]/ism', '[youtube]$2[/youtube]', 'url', $s);
+		$s = BBCode::pregReplaceInTag('/\[url\=https?:\/\/(www\.)?youtube\.com\/embed\/(.*?)\].*?\[\/url\]/ism', '[youtube]$2[/youtube]', 'url', $s);
+		$s = BBCode::pregReplaceInTag('/\[url\=https?:\/\/(www\.)?youtube\.com\/shorts\/(.*?)\].*?\[\/url\]/ism', '[youtube]$2[/youtube]', 'url', $s);
+		$s = BBCode::pregReplaceInTag('/\[url\=?(.*?)\]https?:\/\/vimeo\.com\/([0-9]+)(.*?)\[\/url\]/ism', '[vimeo]$2[/vimeo]', 'url', $s);
+		$s = BBCode::pregReplaceInTag('/\[url\=?(.*?)\]https?:\/\/player\.vimeo\.com\/video\/([0-9]+)(.*?)\[\/url\]/ism', '[vimeo]$2[/vimeo]', 'url', $s);
+		$s = BBCode::pregReplaceInTag('/\[url\=https?:\/\/vimeo\.com\/([0-9]+)\](.*?)\[\/url\]/ism', '[vimeo]$1[/vimeo]', 'url', $s);
 
 		// remove duplicate adjacent code tags
 		$s = preg_replace('/(\[code\])+(.*?)(\[\/code\])+/ism', '[code]$2[/code]', $s);
diff --git a/src/Content/Widget/TrendingTags.php b/src/Content/Widget/TrendingTags.php
index a0cb25c66f..a7590eb20b 100644
--- a/src/Content/Widget/TrendingTags.php
+++ b/src/Content/Widget/TrendingTags.php
@@ -35,10 +35,12 @@ class TrendingTags
 		}
 
 		$tpl = Renderer::getMarkupTemplate('widget/trending_tags.tpl');
-		$o = Renderer::replaceMacros($tpl, [
-			'$title' => DI::l10n()->tt('Trending Tags (last %d hour)', 'Trending Tags (last %d hours)', $period),
-			'$more'  => DI::l10n()->t('More Trending Tags'),
-			'$tags'  => $tags,
+		$o   = Renderer::replaceMacros($tpl, [
+			'$title'    => DI::l10n()->tt('Trending Tags (last %d hour)', 'Trending Tags (last %d hours)', $period),
+			'$more'     => DI::l10n()->t('More Trending Tags'),
+			'$showmore' => DI::l10n()->t('Show More'),
+			'$showless' => DI::l10n()->t('Show Less'),
+			'$tags'     => $tags,
 		]);
 
 		return $o;
diff --git a/src/Core/Addon.php b/src/Core/Addon.php
index 39a1737946..f798021252 100644
--- a/src/Core/Addon.php
+++ b/src/Core/Addon.php
@@ -8,7 +8,6 @@
 namespace Friendica\Core;
 
 use Friendica\DI;
-use Friendica\Model\Contact;
 use Friendica\Util\Strings;
 
 /**
@@ -267,12 +266,6 @@ class Addon
 					if ($type == "author" || $type == "maintainer") {
 						$r = preg_match("|([^<]+)<([^>]+)>|", $v, $m);
 						if ($r) {
-							if (!empty($m[2]) && empty(parse_url($m[2], PHP_URL_SCHEME))) {
-								$contact = Contact::getByURL($m[2], false);
-								if (!empty($contact['url'])) {
-									$m[2] = $contact['url'];
-								}
-							}
 							$info[$type][] = ['name' => $m[1], 'link' => $m[2]];
 						} else {
 							$info[$type][] = ['name' => $v];
diff --git a/src/Core/Cache/Capability/ICanCacheInMemory.php b/src/Core/Cache/Capability/ICanCacheInMemory.php
index 82492d368a..550273f5f0 100644
--- a/src/Core/Cache/Capability/ICanCacheInMemory.php
+++ b/src/Core/Cache/Capability/ICanCacheInMemory.php
@@ -53,4 +53,11 @@ interface ICanCacheInMemory extends ICanCache
 	 * @throws CachePersistenceException In case the underlying cache driver has errors during persistence
 	 */
 	public function compareDelete(string $key, $value): bool;
+
+	/**
+	 * Returns some basic statistics of the used Cache instance
+	 *
+	 * @return array Returns an associative array of statistics
+	 */
+	public function getStats(): array;
 }
diff --git a/src/Core/Cache/Type/APCuCache.php b/src/Core/Cache/Type/APCuCache.php
index f1dde2462c..b269b5aa9d 100644
--- a/src/Core/Cache/Type/APCuCache.php
+++ b/src/Core/Cache/Type/APCuCache.php
@@ -16,10 +16,9 @@ use Friendica\Core\Cache\Exception\InvalidCacheDriverException;
  */
 class APCuCache extends AbstractCache implements ICanCacheInMemory
 {
-	const NAME = 'apcu';
-
 	use CompareSetTrait;
 	use CompareDeleteTrait;
+	const NAME = 'apcu';
 
 	/**
 	 * @throws InvalidCacheDriverException
@@ -147,4 +146,19 @@ class APCuCache extends AbstractCache implements ICanCacheInMemory
 
 		return true;
 	}
+
+	/** {@inheritDoc} */
+	public function getStats(): array
+	{
+		$apcu = apcu_cache_info();
+		$sma  = apcu_sma_info();
+
+		return [
+			'entries'     => $apcu['num_entries'] ?? null,
+			'used_memory' => $apcu['mem_size']    ?? null,
+			'hits'        => $apcu['num_hits']    ?? null,
+			'misses'      => $apcu['num_misses']  ?? null,
+			'avail_mem'   => $sma['avail_mem']    ?? null,
+		];
+	}
 }
diff --git a/src/Core/Cache/Type/ArrayCache.php b/src/Core/Cache/Type/ArrayCache.php
index 7fd44deb0a..148210b4e8 100644
--- a/src/Core/Cache/Type/ArrayCache.php
+++ b/src/Core/Cache/Type/ArrayCache.php
@@ -15,9 +15,8 @@ use Friendica\Core\Cache\Enum;
  */
 class ArrayCache extends AbstractCache implements ICanCacheInMemory
 {
-	const NAME = 'array';
-
 	use CompareDeleteTrait;
+	const NAME = 'array';
 
 	/** @var array Array with the cached data */
 	protected $cachedData = [];
@@ -96,4 +95,10 @@ class ArrayCache extends AbstractCache implements ICanCacheInMemory
 			return false;
 		}
 	}
+
+	/** {@inheritDoc} */
+	public function getStats(): array
+	{
+		return [];
+	}
 }
diff --git a/src/Core/Cache/Type/MemcacheCache.php b/src/Core/Cache/Type/MemcacheCache.php
index 14bd5e310b..b3a6588841 100644
--- a/src/Core/Cache/Type/MemcacheCache.php
+++ b/src/Core/Cache/Type/MemcacheCache.php
@@ -19,11 +19,10 @@ use Memcache;
  */
 class MemcacheCache extends AbstractCache implements ICanCacheInMemory
 {
-	const NAME = 'memcache';
-
 	use CompareSetTrait;
 	use CompareDeleteTrait;
 	use MemcacheCommandTrait;
+	const NAME = 'memcache';
 
 	/**
 	 * @var Memcache
@@ -156,4 +155,21 @@ class MemcacheCache extends AbstractCache implements ICanCacheInMemory
 		$cacheKey = $this->getCacheKey($key);
 		return $this->memcache->add($cacheKey, serialize($value), MEMCACHE_COMPRESSED, $ttl);
 	}
+
+	/** {@inheritDoc} */
+	public function getStats(): array
+	{
+		$stats = $this->memcache->getStats();
+
+		return [
+			'version'           => $stats['version']          ?? null,
+			'entries'           => $stats['curr_items']       ?? null,
+			'used_memory'       => $stats['bytes']            ?? null,
+			'uptime'            => $stats['uptime']           ?? null,
+			'connected_clients' => $stats['curr_connections'] ?? null,
+			'hits'              => $stats['get_hits']         ?? null,
+			'misses'            => $stats['get_misses']       ?? null,
+			'evictions'         => $stats['evictions']        ?? null,
+		];
+	}
 }
diff --git a/src/Core/Cache/Type/MemcachedCache.php b/src/Core/Cache/Type/MemcachedCache.php
index 2e970e6078..03ad7d8322 100644
--- a/src/Core/Cache/Type/MemcachedCache.php
+++ b/src/Core/Cache/Type/MemcachedCache.php
@@ -20,11 +20,10 @@ use Psr\Log\LoggerInterface;
  */
 class MemcachedCache extends AbstractCache implements ICanCacheInMemory
 {
-	const NAME = 'memcached';
-
 	use CompareSetTrait;
 	use CompareDeleteTrait;
 	use MemcacheCommandTrait;
+	const NAME = 'memcached';
 
 	/**
 	 * @var \Memcached
@@ -172,4 +171,27 @@ class MemcachedCache extends AbstractCache implements ICanCacheInMemory
 		$cacheKey = $this->getCacheKey($key);
 		return $this->memcached->add($cacheKey, $value, $ttl);
 	}
+
+	/** {@inheritDoc} */
+	public function getStats(): array
+	{
+		$stats = $this->memcached->getStats();
+
+		// get statistics of the first instance
+		foreach ($stats as $value) {
+			$stats = $value;
+			break;
+		}
+
+		return [
+			'version'           => $stats['version']          ?? null,
+			'entries'           => $stats['curr_items']       ?? null,
+			'used_memory'       => $stats['bytes']            ?? null,
+			'uptime'            => $stats['uptime']           ?? null,
+			'connected_clients' => $stats['curr_connections'] ?? null,
+			'hits'              => $stats['get_hits']         ?? null,
+			'misses'            => $stats['get_misses']       ?? null,
+			'evictions'         => $stats['evictions']        ?? null,
+		];
+	}
 }
diff --git a/src/Core/Cache/Type/ProfilerCacheDecorator.php b/src/Core/Cache/Type/ProfilerCacheDecorator.php
index 8071b79c5e..113aa76688 100644
--- a/src/Core/Cache/Type/ProfilerCacheDecorator.php
+++ b/src/Core/Cache/Type/ProfilerCacheDecorator.php
@@ -166,4 +166,14 @@ class ProfilerCacheDecorator implements ICanCache, ICanCacheInMemory
 	{
 		return $this->cache->getName() . ' (with profiler)';
 	}
+
+	/** {@inheritDoc} */
+	public function getStats(): array
+	{
+		if ($this->cache instanceof ICanCacheInMemory) {
+			return $this->cache->getStats();
+		} else {
+			return [];
+		}
+	}
 }
diff --git a/src/Core/Cache/Type/RedisCache.php b/src/Core/Cache/Type/RedisCache.php
index cf78d362bb..fc04a5433a 100644
--- a/src/Core/Cache/Type/RedisCache.php
+++ b/src/Core/Cache/Type/RedisCache.php
@@ -207,4 +207,21 @@ class RedisCache extends AbstractCache implements ICanCacheInMemory
 		$this->redis->unwatch();
 		return false;
 	}
+
+	/** {@inheritDoc} */
+	public function getStats(): array
+	{
+		$info = $this->redis->info();
+
+		return [
+			'version'           => $info['redis_version']     ?? null,
+			'entries'           => $this->redis->dbSize()     ?? null,
+			'used_memory'       => $info['used_memory']       ?? null,
+			'connected_clients' => $info['connected_clients'] ?? null,
+			'uptime'            => $info['uptime_in_seconds'] ?? null,
+			'hits'              => $info['keyspace_hits']     ?? null,
+			'misses'            => $info['keyspace_misses']   ?? null,
+			'evictions'         => $info['evicted_keys']      ?? null,
+		];
+	}
 }
diff --git a/src/Core/Config/Util/ConfigFileManager.php b/src/Core/Config/Util/ConfigFileManager.php
index d76e3e53cd..96c416e4de 100644
--- a/src/Core/Config/Util/ConfigFileManager.php
+++ b/src/Core/Config/Util/ConfigFileManager.php
@@ -257,6 +257,7 @@ class ConfigFileManager
 
 		// map the legacy configuration structure to the current structure
 		foreach ($htConfigCategories as $htConfigCategory) {
+			/** @phpstan-ignore-next-line $a->config could be modified after `include $fullName` */
 			if (is_array($a->config[$htConfigCategory])) {
 				$keys = array_keys($a->config[$htConfigCategory]);
 
diff --git a/src/Core/Hook.php b/src/Core/Hook.php
index 44dfad70bc..f2ebf1642f 100644
--- a/src/Core/Hook.php
+++ b/src/Core/Hook.php
@@ -40,7 +40,7 @@ class Hook
 	public static function loadHooks()
 	{
 		self::$hooks = [];
-		$stmt = DBA::select('hook', ['hook', 'file', 'function'], [], ['order' => ['priority' => 'desc', 'file']]);
+		$stmt        = DBA::select('hook', ['hook', 'file', 'function'], [], ['order' => ['priority' => 'desc', 'file']]);
 
 		while ($hook = DBA::fetch($stmt)) {
 			self::add($hook['hook'], $hook['file'], $hook['function']);
@@ -171,8 +171,8 @@ class Hook
 	 * Use this function when you want to be able to allow a hook to manipulate
 	 * the provided data.
 	 *
-	 * @param string        $name of the hook to call
-	 * @param string|array &$data to transmit to the callback handler
+	 * @param string                $name of the hook to call
+	 * @param int|string|array|null $data to transmit to the callback handler
 	 * @return void
 	 * @throws \Friendica\Network\HTTPException\InternalServerErrorException
 	 */
diff --git a/src/Core/Hooks/HookEventBridge.php b/src/Core/Hooks/HookEventBridge.php
index 2f1e51a297..06f918ea6d 100644
--- a/src/Core/Hooks/HookEventBridge.php
+++ b/src/Core/Hooks/HookEventBridge.php
@@ -43,15 +43,45 @@ final class HookEventBridge
 		ArrayFilterEvent::NAV_INFO                        => 'nav_info',
 		ArrayFilterEvent::FEATURE_ENABLED                 => 'isEnabled',
 		ArrayFilterEvent::FEATURE_GET                     => 'get',
-		ArrayFilterEvent::POST_LOCAL_START                => 'post_local_start',
-		ArrayFilterEvent::POST_LOCAL                      => 'post_local',
-		ArrayFilterEvent::POST_LOCAL_END                  => 'post_local_end',
+		ArrayFilterEvent::PERMISSION_TOOLTIP_CONTENT      => 'lockview_content',
+		ArrayFilterEvent::INSERT_POST_LOCAL_START         => 'post_local_start',
+		ArrayFilterEvent::INSERT_POST_LOCAL               => 'post_local',
+		ArrayFilterEvent::INSERT_POST_LOCAL_END           => 'post_local_end',
+		ArrayFilterEvent::INSERT_POST_REMOTE              => 'post_remote',
+		ArrayFilterEvent::INSERT_POST_REMOTE_END          => 'post_remote_end',
+		ArrayFilterEvent::PREPARE_POST_START              => 'prepare_body_init',
+		ArrayFilterEvent::PREPARE_POST_FILTER_CONTENT     => 'prepare_body_content_filter',
+		ArrayFilterEvent::PREPARE_POST                    => 'prepare_body',
+		ArrayFilterEvent::PREPARE_POST_END                => 'prepare_body_final',
 		ArrayFilterEvent::PHOTO_UPLOAD_FORM               => 'photo_upload_form',
+		ArrayFilterEvent::PHOTO_UPLOAD_START              => 'photo_post_init',
+		ArrayFilterEvent::PHOTO_UPLOAD                    => 'photo_post_file',
+		ArrayFilterEvent::PHOTO_UPLOAD_END                => 'photo_post_end',
 		ArrayFilterEvent::NETWORK_TO_NAME                 => 'network_to_name',
+		ArrayFilterEvent::NETWORK_CONTENT_START           => 'network_content_init',
+		ArrayFilterEvent::NETWORK_CONTENT_TABS            => 'network_tabs',
+		ArrayFilterEvent::PARSE_LINK                      => 'parse_link',
 		ArrayFilterEvent::CONVERSATION_START              => 'conversation_start',
+		ArrayFilterEvent::FETCH_ITEM_BY_LINK              => 'item_by_link',
+		ArrayFilterEvent::ITEM_TAGGED                     => 'tagged',
 		ArrayFilterEvent::DISPLAY_ITEM                    => 'display_item',
+		ArrayFilterEvent::CACHE_ITEM                      => 'put_item_in_cache',
+		ArrayFilterEvent::CHECK_ITEM_NOTIFICATION         => 'check_item_notification',
+		ArrayFilterEvent::ENOTIFY                         => 'enotify',
+		ArrayFilterEvent::ENOTIFY_STORE                   => 'enotify_store',
+		ArrayFilterEvent::ENOTIFY_MAIL                    => 'enotify_mail',
+		ArrayFilterEvent::DETECT_LANGUAGES                => 'detect_languages',
 		ArrayFilterEvent::RENDER_LOCATION                 => 'render_location',
 		ArrayFilterEvent::ITEM_PHOTO_MENU                 => 'item_photo_menu',
+		ArrayFilterEvent::DIRECTORY_ITEM                  => 'directory_item',
+		ArrayFilterEvent::CONTACT_PHOTO_MENU              => 'contact_photo_menu',
+		ArrayFilterEvent::PROFILE_SIDEBAR_ENTRY           => 'profile_sidebar_enter',
+		ArrayFilterEvent::PROFILE_SIDEBAR                 => 'profile_sidebar',
+		ArrayFilterEvent::PROFILE_TABS                    => 'profile_tabs',
+		ArrayFilterEvent::PROFILE_SETTINGS_FORM           => 'profile_edit',
+		ArrayFilterEvent::PROFILE_SETTINGS_POST           => 'profile_post',
+		ArrayFilterEvent::MODERATION_USERS_TABS           => 'moderation_users_tabs',
+		ArrayFilterEvent::ACL_LOOKUP_END                  => 'acl_lookup_end',
 		ArrayFilterEvent::OEMBED_FETCH_END                => 'oembed_fetch_url',
 		ArrayFilterEvent::PAGE_INFO                       => 'page_info_data',
 		ArrayFilterEvent::SMILEY_LIST                     => 'smilie',
@@ -62,11 +92,34 @@ final class HookEventBridge
 		ArrayFilterEvent::PROTOCOL_SUPPORTS_FOLLOW        => 'support_follow',
 		ArrayFilterEvent::PROTOCOL_SUPPORTS_REVOKE_FOLLOW => 'support_revoke_follow',
 		ArrayFilterEvent::PROTOCOL_SUPPORTS_PROBE         => 'support_probe',
+		ArrayFilterEvent::FOLLOW_CONTACT                  => 'follow',
+		ArrayFilterEvent::UNFOLLOW_CONTACT                => 'unfollow',
+		ArrayFilterEvent::REVOKE_FOLLOW_CONTACT           => 'revoke_follow',
+		ArrayFilterEvent::BLOCK_CONTACT                   => 'block',
+		ArrayFilterEvent::UNBLOCK_CONTACT                 => 'unblock',
+		ArrayFilterEvent::EDIT_CONTACT_FORM               => 'contact_edit',
+		ArrayFilterEvent::EDIT_CONTACT_POST               => 'contact_edit_post',
+		ArrayFilterEvent::AVATAR_LOOKUP                   => 'avatar_lookup',
+		ArrayFilterEvent::ACCOUNT_AUTHENTICATE            => 'authenticate',
+		ArrayFilterEvent::ACCOUNT_REGISTER_FORM           => 'register_form',
+		ArrayFilterEvent::ACCOUNT_REGISTER_POST           => 'register_post',
+		ArrayFilterEvent::ACCOUNT_REGISTER                => 'register_account',
+		ArrayFilterEvent::ACCOUNT_REMOVE                  => 'remove_user',
+		ArrayFilterEvent::EVENT_CREATED                   => 'event_created',
+		ArrayFilterEvent::EVENT_UPDATED                   => 'event_updated',
+		ArrayFilterEvent::ADD_WORKER_TASK                 => 'proc_run',
+		ArrayFilterEvent::STORAGE_CONFIG                  => 'storage_config',
+		ArrayFilterEvent::STORAGE_INSTANCE                => 'storage_instance',
+		ArrayFilterEvent::DB_STRUCTURE_DEFINITION         => 'dbstructure_definition',
+		ArrayFilterEvent::DB_VIEW_DEFINITION              => 'dbview_definition',
 		HtmlFilterEvent::HEAD                             => 'head',
 		HtmlFilterEvent::FOOTER                           => 'footer',
 		HtmlFilterEvent::PAGE_HEADER                      => 'page_header',
 		HtmlFilterEvent::PAGE_CONTENT_TOP                 => 'page_content_top',
 		HtmlFilterEvent::PAGE_END                         => 'page_end',
+		HtmlFilterEvent::MOD_HOME_CONTENT                 => 'home_content',
+		HtmlFilterEvent::MOD_ABOUT_CONTENT                => 'about_hook',
+		HtmlFilterEvent::MOD_PROFILE_CONTENT              => 'profile_advanced',
 		HtmlFilterEvent::JOT_TOOL                         => 'jot_tool',
 		HtmlFilterEvent::CONTACT_BLOCK_END                => 'contact_block_end',
 	];
@@ -85,15 +138,45 @@ final class HookEventBridge
 			ArrayFilterEvent::NAV_INFO                        => 'onArrayFilterEvent',
 			ArrayFilterEvent::FEATURE_ENABLED                 => 'onArrayFilterEvent',
 			ArrayFilterEvent::FEATURE_GET                     => 'onArrayFilterEvent',
-			ArrayFilterEvent::POST_LOCAL_START                => 'onArrayFilterEvent',
-			ArrayFilterEvent::POST_LOCAL                      => 'onArrayFilterEvent',
-			ArrayFilterEvent::POST_LOCAL_END                  => 'onArrayFilterEvent',
+			ArrayFilterEvent::PERMISSION_TOOLTIP_CONTENT      => 'onPermissionTooltipContentEvent',
+			ArrayFilterEvent::INSERT_POST_LOCAL_START         => 'onArrayFilterEvent',
+			ArrayFilterEvent::INSERT_POST_LOCAL               => 'onInsertPostLocalEvent',
+			ArrayFilterEvent::INSERT_POST_LOCAL_END           => 'onInsertPostLocalEndEvent',
+			ArrayFilterEvent::INSERT_POST_REMOTE              => 'onArrayFilterEvent',
+			ArrayFilterEvent::INSERT_POST_REMOTE_END          => 'onArrayFilterEvent',
+			ArrayFilterEvent::PREPARE_POST_START              => 'onPreparePostStartEvent',
+			ArrayFilterEvent::PREPARE_POST_FILTER_CONTENT     => 'onArrayFilterEvent',
+			ArrayFilterEvent::PREPARE_POST                    => 'onArrayFilterEvent',
+			ArrayFilterEvent::PREPARE_POST_END                => 'onArrayFilterEvent',
 			ArrayFilterEvent::PHOTO_UPLOAD_FORM               => 'onArrayFilterEvent',
+			ArrayFilterEvent::PHOTO_UPLOAD_START              => 'onPhotoUploadStartEvent',
+			ArrayFilterEvent::PHOTO_UPLOAD                    => 'onArrayFilterEvent',
+			ArrayFilterEvent::PHOTO_UPLOAD_END                => 'onPhotoUploadEndEvent',
 			ArrayFilterEvent::NETWORK_TO_NAME                 => 'onArrayFilterEvent',
+			ArrayFilterEvent::NETWORK_CONTENT_START           => 'onArrayFilterEvent',
+			ArrayFilterEvent::NETWORK_CONTENT_TABS            => 'onArrayFilterEvent',
+			ArrayFilterEvent::PARSE_LINK                      => 'onArrayFilterEvent',
 			ArrayFilterEvent::CONVERSATION_START              => 'onArrayFilterEvent',
+			ArrayFilterEvent::FETCH_ITEM_BY_LINK              => 'onArrayFilterEvent',
+			ArrayFilterEvent::ITEM_TAGGED                     => 'onArrayFilterEvent',
 			ArrayFilterEvent::DISPLAY_ITEM                    => 'onArrayFilterEvent',
+			ArrayFilterEvent::CACHE_ITEM                      => 'onArrayFilterEvent',
+			ArrayFilterEvent::CHECK_ITEM_NOTIFICATION         => 'onArrayFilterEvent',
+			ArrayFilterEvent::ENOTIFY                         => 'onArrayFilterEvent',
+			ArrayFilterEvent::ENOTIFY_STORE                   => 'onArrayFilterEvent',
+			ArrayFilterEvent::ENOTIFY_MAIL                    => 'onArrayFilterEvent',
+			ArrayFilterEvent::DETECT_LANGUAGES                => 'onArrayFilterEvent',
 			ArrayFilterEvent::RENDER_LOCATION                 => 'onArrayFilterEvent',
 			ArrayFilterEvent::ITEM_PHOTO_MENU                 => 'onArrayFilterEvent',
+			ArrayFilterEvent::DIRECTORY_ITEM                  => 'onArrayFilterEvent',
+			ArrayFilterEvent::CONTACT_PHOTO_MENU              => 'onArrayFilterEvent',
+			ArrayFilterEvent::PROFILE_SIDEBAR_ENTRY           => 'onProfileSidebarEntryEvent',
+			ArrayFilterEvent::PROFILE_SIDEBAR                 => 'onArrayFilterEvent',
+			ArrayFilterEvent::PROFILE_TABS                    => 'onArrayFilterEvent',
+			ArrayFilterEvent::PROFILE_SETTINGS_FORM           => 'onArrayFilterEvent',
+			ArrayFilterEvent::PROFILE_SETTINGS_POST           => 'onArrayFilterEvent',
+			ArrayFilterEvent::MODERATION_USERS_TABS           => 'onArrayFilterEvent',
+			ArrayFilterEvent::ACL_LOOKUP_END                  => 'onArrayFilterEvent',
 			ArrayFilterEvent::OEMBED_FETCH_END                => 'onOembedFetchEndEvent',
 			ArrayFilterEvent::PAGE_INFO                       => 'onArrayFilterEvent',
 			ArrayFilterEvent::SMILEY_LIST                     => 'onArrayFilterEvent',
@@ -104,11 +187,34 @@ final class HookEventBridge
 			ArrayFilterEvent::PROTOCOL_SUPPORTS_FOLLOW        => 'onArrayFilterEvent',
 			ArrayFilterEvent::PROTOCOL_SUPPORTS_REVOKE_FOLLOW => 'onArrayFilterEvent',
 			ArrayFilterEvent::PROTOCOL_SUPPORTS_PROBE         => 'onArrayFilterEvent',
+			ArrayFilterEvent::FOLLOW_CONTACT                  => 'onArrayFilterEvent',
+			ArrayFilterEvent::UNFOLLOW_CONTACT                => 'onArrayFilterEvent',
+			ArrayFilterEvent::REVOKE_FOLLOW_CONTACT           => 'onArrayFilterEvent',
+			ArrayFilterEvent::BLOCK_CONTACT                   => 'onArrayFilterEvent',
+			ArrayFilterEvent::UNBLOCK_CONTACT                 => 'onArrayFilterEvent',
+			ArrayFilterEvent::EDIT_CONTACT_FORM               => 'onArrayFilterEvent',
+			ArrayFilterEvent::EDIT_CONTACT_POST               => 'onArrayFilterEvent',
+			ArrayFilterEvent::AVATAR_LOOKUP                   => 'onArrayFilterEvent',
+			ArrayFilterEvent::ACCOUNT_AUTHENTICATE            => 'onArrayFilterEvent',
+			ArrayFilterEvent::ACCOUNT_REGISTER_FORM           => 'onArrayFilterEvent',
+			ArrayFilterEvent::ACCOUNT_REGISTER_POST           => 'onArrayFilterEvent',
+			ArrayFilterEvent::ACCOUNT_REGISTER                => 'onAccountRegisterEvent',
+			ArrayFilterEvent::ACCOUNT_REMOVE                  => 'onAccountRemoveEvent',
+			ArrayFilterEvent::EVENT_CREATED                   => 'onEventCreatedEvent',
+			ArrayFilterEvent::EVENT_UPDATED                   => 'onEventUpdatedEvent',
+			ArrayFilterEvent::ADD_WORKER_TASK                 => 'onArrayFilterEvent',
+			ArrayFilterEvent::STORAGE_CONFIG                  => 'onArrayFilterEvent',
+			ArrayFilterEvent::STORAGE_INSTANCE                => 'onArrayFilterEvent',
+			ArrayFilterEvent::DB_STRUCTURE_DEFINITION         => 'onArrayFilterEvent',
+			ArrayFilterEvent::DB_VIEW_DEFINITION              => 'onArrayFilterEvent',
 			HtmlFilterEvent::HEAD                             => 'onHtmlFilterEvent',
 			HtmlFilterEvent::FOOTER                           => 'onHtmlFilterEvent',
 			HtmlFilterEvent::PAGE_HEADER                      => 'onHtmlFilterEvent',
 			HtmlFilterEvent::PAGE_CONTENT_TOP                 => 'onHtmlFilterEvent',
 			HtmlFilterEvent::PAGE_END                         => 'onHtmlFilterEvent',
+			HtmlFilterEvent::MOD_HOME_CONTENT                 => 'onHtmlFilterEvent',
+			HtmlFilterEvent::MOD_ABOUT_CONTENT                => 'onHtmlFilterEvent',
+			HtmlFilterEvent::MOD_PROFILE_CONTENT              => 'onHtmlFilterEvent',
 			HtmlFilterEvent::JOT_TOOL                         => 'onHtmlFilterEvent',
 			HtmlFilterEvent::CONTACT_BLOCK_END                => 'onHtmlFilterEvent',
 		];
@@ -131,6 +237,103 @@ final class HookEventBridge
 		);
 	}
 
+	/**
+	 * Map the PERMISSION_TOOLTIP_CONTENT event to `lockview_content` hook
+	 */
+	public static function onPermissionTooltipContentEvent(ArrayFilterEvent $event): void
+	{
+		$data = $event->getArray();
+
+		$model = (array) $data['model'] ?? [];
+
+		$data['model'] = static::callHook($event->getName(), $model);
+
+		$event->setArray($data);
+	}
+
+	/**
+	 * Map the INSERT_POST_LOCAL event to `post_local` hook
+	 */
+	public static function onInsertPostLocalEvent(ArrayFilterEvent $event): void
+	{
+		$data = $event->getArray();
+
+		$item = (array) $data['item'] ?? [];
+
+		$data['item'] = static::callHook($event->getName(), $item);
+
+		$event->setArray($data);
+	}
+
+	/**
+	 * Map the INSERT_POST_LOCAL_END event to `post_local_end` hook
+	 */
+	public static function onInsertPostLocalEndEvent(ArrayFilterEvent $event): void
+	{
+		$data = $event->getArray();
+
+		$item = (array) $data['item'] ?? [];
+
+		$data['item'] = static::callHook($event->getName(), $item);
+
+		$event->setArray($data);
+	}
+
+	/**
+	 * Map the PREPARE_POST_START event to `prepare_body_init` hook
+	 */
+	public static function onPreparePostStartEvent(ArrayFilterEvent $event): void
+	{
+		$data = $event->getArray();
+
+		$item = (array) $data['item'] ?? [];
+
+		$data['item'] = static::callHook($event->getName(), $item);
+
+		$event->setArray($data);
+	}
+
+	/**
+	 * Map the PHOTO_UPLOAD_START event to `photo_post_init` hook
+	 */
+	public static function onPhotoUploadStartEvent(ArrayFilterEvent $event): void
+	{
+		$data = $event->getArray();
+
+		$request = (array) $data['request'] ?? [];
+
+		$data['request'] = static::callHook($event->getName(), $request);
+
+		$event->setArray($data);
+	}
+
+	/**
+	 * Map the PHOTO_UPLOAD_END event to `photo_post_end` hook
+	 */
+	public static function onPhotoUploadEndEvent(ArrayFilterEvent $event): void
+	{
+		$data = $event->getArray();
+
+		$id = (int) $data['id'] ?? 0;
+
+		// one-way-event: we don't care about the returned value
+		static::callHook($event->getName(), $id);
+	}
+
+	/**
+	 * Map the PROFILE_SIDEBAR_ENTRY event to `profile_sidebar_enter` hook
+	 */
+	public static function onProfileSidebarEntryEvent(ArrayFilterEvent $event): void
+	{
+		$data = $event->getArray();
+
+		$profile = (array) $data['profile'] ?? [];
+
+		$data['profile'] = static::callHook($event->getName(), $profile);
+
+		$event->setArray($data);
+	}
+
 	/**
 	 * Map the OEMBED_FETCH_END event to `oembed_fetch_url` hook
 	 */
@@ -187,6 +390,60 @@ final class HookEventBridge
 		$event->setArray($data);
 	}
 
+	/**
+	 * Map the ACCOUNT_REGISTER event to `register_account` hook
+	 */
+	public static function onAccountRegisterEvent(ArrayFilterEvent $event): void
+	{
+		$data = $event->getArray();
+
+		$uid = (int) $data['uid'] ?? 0;
+
+		$data['uid'] = static::callHook($event->getName(), $uid);
+
+		$event->setArray($data);
+	}
+
+	/**
+	 * Map the ACCOUNT_REMOVE event to `remove_account` hook
+	 */
+	public static function onAccountRemoveEvent(ArrayFilterEvent $event): void
+	{
+		$data = $event->getArray();
+
+		$user = (array) $data['user'] ?? [];
+
+		$data['user'] = static::callHook($event->getName(), $user);
+
+		$event->setArray($data);
+	}
+
+	/**
+	 * Map the EVENT_CREATED event to `event_created` hook
+	 */
+	public static function onEventCreatedEvent(ArrayFilterEvent $event): void
+	{
+		$data = $event->getArray();
+
+		$id = (int) $data['event']['id'] ?? 0;
+
+		// one-way-event: we don't care about the returned value
+		static::callHook($event->getName(), $id);
+	}
+
+	/**
+	 * Map the EVENT_UPDATED event to `event_updated` hook
+	 */
+	public static function onEventUpdatedEvent(ArrayFilterEvent $event): void
+	{
+		$data = $event->getArray();
+
+		$id = (int) $data['event']['id'] ?? 0;
+
+		// one-way-event: we don't care about the returned value
+		static::callHook($event->getName(), $id);
+	}
+
 	public static function onArrayFilterEvent(ArrayFilterEvent $event): void
 	{
 		$event->setArray(
@@ -202,9 +459,9 @@ final class HookEventBridge
 	}
 
 	/**
-	 * @param string|array|object $data
+	 * @param int|string|array|object $data
 	 *
-	 * @return string|array|object
+	 * @return int|string|array|object
 	 */
 	private static function callHook(string $name, $data)
 	{
diff --git a/src/Core/Lock/Type/CacheLock.php b/src/Core/Lock/Type/CacheLock.php
index c3794d06a7..c7fa75e021 100644
--- a/src/Core/Lock/Type/CacheLock.php
+++ b/src/Core/Lock/Type/CacheLock.php
@@ -7,7 +7,6 @@
 
 namespace Friendica\Core\Lock\Type;
 
-use Friendica\Core\Cache\Capability\ICanCache;
 use Friendica\Core\Cache\Capability\ICanCacheInMemory;
 use Friendica\Core\Cache\Enum\Duration;
 use Friendica\Core\Cache\Exception\CachePersistenceException;
@@ -156,6 +155,16 @@ class CacheLock extends AbstractLock
 		return $success;
 	}
 
+	/**
+	 * Returns stats about the cache provider
+	 *
+	 * @return array
+	 */
+	public function getCacheStats(): array
+	{
+		return $this->cache->getStats();
+	}
+
 	/**
 	 * @param string $key The original key
 	 *
diff --git a/src/Core/Logger/Factory/AbstractLoggerTypeFactory.php b/src/Core/Logger/Factory/AbstractLoggerTypeFactory.php
index 08a9559279..98c05e187c 100644
--- a/src/Core/Logger/Factory/AbstractLoggerTypeFactory.php
+++ b/src/Core/Logger/Factory/AbstractLoggerTypeFactory.php
@@ -12,6 +12,8 @@ use Psr\Log\LogLevel;
 
 /**
  * Abstract class for creating logger types, which includes common necessary logic/content
+ *
+ * @deprecated 2025.02 Implement `\Friendica\Core\Logger\Factory\LoggerFactory` instead
  */
 abstract class AbstractLoggerTypeFactory
 {
@@ -25,6 +27,8 @@ abstract class AbstractLoggerTypeFactory
 	 */
 	public function __construct(IHaveCallIntrospections $introspection, string $channel)
 	{
+		@trigger_error('Class `' . __CLASS__ . '` is deprecated since 2025.02 and will be removed after 5 months, implement `\Friendica\Core\Logger\Factory\LoggerFactory` instead.', E_USER_DEPRECATED);
+
 		$this->channel       = $channel;
 		$this->introspection = $introspection;
 	}
@@ -44,21 +48,21 @@ abstract class AbstractLoggerTypeFactory
 			// legacy WARNING
 			case "0":
 				return LogLevel::ERROR;
-			// legacy INFO
+				// legacy INFO
 			case "1":
 				return LogLevel::WARNING;
-			// legacy TRACE
+				// legacy TRACE
 			case "2":
 				return LogLevel::NOTICE;
-			// legacy DEBUG
+				// legacy DEBUG
 			case "3":
 				return LogLevel::INFO;
-			// legacy DATA
+				// legacy DATA
 			case "4":
-			// legacy ALL
+				// legacy ALL
 			case "5":
 				return LogLevel::DEBUG;
-			// default if nothing set
+				// default if nothing set
 			default:
 				return $level;
 		}
diff --git a/src/Core/Logger/Factory/DelegatingLoggerFactory.php b/src/Core/Logger/Factory/DelegatingLoggerFactory.php
new file mode 100644
index 0000000000..f0001132dd
--- /dev/null
+++ b/src/Core/Logger/Factory/DelegatingLoggerFactory.php
@@ -0,0 +1,73 @@
+ */
+	private array $factories = [];
+
+	public function __construct(IManageConfigValues $config)
+	{
+		$this->config = $config;
+	}
+
+	public function registerFactory(string $name, LoggerFactory $factory): void
+	{
+		$this->factories[$name] = $factory;
+	}
+
+	/**
+	 * Creates and returns a PSR-3 Logger instance.
+	 *
+	 * Calling this method multiple times with the same parameters SHOULD return the same object.
+	 *
+	 * @param \Psr\Log\LogLevel::* $logLevel The log level
+	 * @param \Friendica\Core\Logger\Capability\LogChannel::* $logChannel The log channel
+	 */
+	public function createLogger(string $logLevel, string $logChannel): LoggerInterface
+	{
+		$factoryName = $this->config->get('system', 'logger_config') ?? '';
+
+		/**
+		 * @deprecated 2025.02 The value `monolog` for `system.logger_config` inside the `config/local.config.php` file is deprecated, please use `stream` or `syslog` instead.
+		 */
+		if ($factoryName === 'monolog') {
+			@trigger_error('The config `system.logger_config` with value `monolog` is deprecated since 2025.02 and will stop working in 5 months, please change the value to `stream` or `syslog` in the `config/local.config.php` file.', \E_USER_DEPRECATED);
+
+			$factoryName = 'stream';
+		}
+
+		if (!array_key_exists($factoryName, $this->factories)) {
+			return new NullLogger();
+		}
+
+		$factory = $this->factories[$factoryName];
+
+		try {
+			$logger = $factory->createLogger($logLevel, $logChannel);
+		} catch (\Throwable $th) {
+			return new NullLogger();
+		}
+
+		return $logger;
+	}
+}
diff --git a/src/Core/Logger/Factory/LegacyLoggerFactory.php b/src/Core/Logger/Factory/LegacyLoggerFactory.php
deleted file mode 100644
index 2c7b6c0237..0000000000
--- a/src/Core/Logger/Factory/LegacyLoggerFactory.php
+++ /dev/null
@@ -1,61 +0,0 @@
-instanceCreator = $instanceCreator;
-		$this->config          = $config;
-		$this->profiler        = $profiler;
-	}
-
-	/**
-	 * Creates and returns a PSR-3 Logger instance.
-	 *
-	 * Calling this method multiple times with the same parameters SHOULD return the same object.
-	 *
-	 * @param \Psr\Log\LogLevel::* $logLevel The log level
-	 * @param \Friendica\Core\Logger\Capability\LogChannel::* $logChannel The log channel
-	 */
-	public function createLogger(string $logLevel, string $logChannel): LoggerInterface
-	{
-		$factory = new Logger($logChannel);
-
-		return $factory->create($this->instanceCreator, $this->config, $this->profiler);
-	}
-}
diff --git a/src/Core/Logger/Factory/Logger.php b/src/Core/Logger/Factory/Logger.php
index fbee580544..78451e713d 100644
--- a/src/Core/Logger/Factory/Logger.php
+++ b/src/Core/Logger/Factory/Logger.php
@@ -18,6 +18,8 @@ use Throwable;
 
 /**
  * The logger factory for the core logging instances
+ *
+ * @deprecated 2025.02 Implement `\Friendica\Core\Logger\Factory\LoggerFactory` instead
  */
 class Logger
 {
@@ -26,6 +28,8 @@ class Logger
 
 	public function __construct(string $channel = LogChannel::DEFAULT)
 	{
+		@trigger_error('Class `' . __CLASS__ . '` is deprecated since 2025.02 and will be removed after 5 months, implement `\Friendica\Core\Logger\Factory\LoggerFactory` instead.', E_USER_DEPRECATED);
+
 		$this->channel = $channel;
 	}
 
diff --git a/src/Core/Logger/Factory/StreamLogger.php b/src/Core/Logger/Factory/StreamLogger.php
index e14fe8258f..b2c6de7f3e 100644
--- a/src/Core/Logger/Factory/StreamLogger.php
+++ b/src/Core/Logger/Factory/StreamLogger.php
@@ -20,6 +20,8 @@ use Psr\Log\NullLogger;
 /**
  * The logger factory for the StreamLogger instance
  *
+ * @deprecated 2025.02 Implement `\Friendica\Core\Logger\Factory\LoggerFactory` instead
+ * @see StreamLoggerFactory
  * @see StreamLoggerClass
  */
 class StreamLogger extends AbstractLoggerTypeFactory
@@ -38,6 +40,8 @@ class StreamLogger extends AbstractLoggerTypeFactory
 	 */
 	public function create(IManageConfigValues $config, string $logfile = null, string $channel = null): LoggerInterface
 	{
+		@trigger_error('Class `' . __CLASS__ . '` is deprecated since 2025.02 and will be removed after 5 months, implement `\Friendica\Core\Logger\Factory\LoggerFactory` instead.', E_USER_DEPRECATED);
+
 		$fileSystem = new FileSystem();
 
 		$logfile = $logfile ?? $config->get('system', 'logfile');
diff --git a/src/Core/Logger/Factory/StreamLoggerFactory.php b/src/Core/Logger/Factory/StreamLoggerFactory.php
new file mode 100644
index 0000000000..8119c40430
--- /dev/null
+++ b/src/Core/Logger/Factory/StreamLoggerFactory.php
@@ -0,0 +1,76 @@
+config        = $config;
+		$this->introspection = $introspection;
+		$this->fileSystem    = $fileSystem;
+	}
+
+	/**
+	 * Creates and returns a PSR-3 Logger instance.
+	 *
+	 * Calling this method multiple times with the same parameters SHOULD return the same object.
+	 *
+	 * @param \Psr\Log\LogLevel::* $logLevel The log level
+	 * @param \Friendica\Core\Logger\Capability\LogChannel::* $logChannel The log channel
+	 *
+	 * @throws LoggerArgumentException
+	 * @throws LogLevelException
+	 */
+	public function createLogger(string $logLevel, string $logChannel): LoggerInterface
+	{
+		$logfile = $this->config->get('system', 'logfile');
+
+		if (!file_exists($logfile) || !is_writable($logfile)) {
+			throw new LoggerArgumentException(sprintf('"%s" is not a valid logfile.', $logfile));
+		}
+
+		if (! array_key_exists($logLevel, StreamLogger::levelToInt)) {
+			throw new LogLevelException(sprintf('The log level "%s" is not supported by "%s".', $logLevel, StreamLogger::class));
+		}
+
+		return new StreamLogger(
+			$logChannel,
+			$this->introspection,
+			$this->fileSystem->createStream($logfile),
+			StreamLogger::levelToInt[$logLevel],
+			getmypid()
+		);
+	}
+}
diff --git a/src/Core/Logger/Factory/SyslogLogger.php b/src/Core/Logger/Factory/SyslogLogger.php
index e9b59f1186..d9f98f05fd 100644
--- a/src/Core/Logger/Factory/SyslogLogger.php
+++ b/src/Core/Logger/Factory/SyslogLogger.php
@@ -16,6 +16,8 @@ use Psr\Log\LoggerInterface;
 /**
  * The logger factory for the SyslogLogger instance
  *
+ * @deprecated 2025.02 Implement `\Friendica\Core\Logger\Factory\LoggerFactory` instead
+ * @see SyslogLoggerFactory
  * @see SyslogLoggerClass
  */
 class SyslogLogger extends AbstractLoggerTypeFactory
@@ -31,6 +33,8 @@ class SyslogLogger extends AbstractLoggerTypeFactory
 	 */
 	public function create(IManageConfigValues $config): LoggerInterface
 	{
+		@trigger_error('Class `' . __CLASS__ . '` is deprecated since 2025.02 and will be removed after 5 months, implement `\Friendica\Core\Logger\Factory\LoggerFactory` instead.', E_USER_DEPRECATED);
+
 		$logOpts     = $config->get('system', 'syslog_flags')    ?? SyslogLoggerClass::DEFAULT_FLAGS;
 		$logFacility = $config->get('system', 'syslog_facility') ?? SyslogLoggerClass::DEFAULT_FACILITY;
 		$loglevel    = SyslogLogger::mapLegacyConfigDebugLevel($config->get('system', 'loglevel'));
diff --git a/src/Core/Logger/Factory/SyslogLoggerFactory.php b/src/Core/Logger/Factory/SyslogLoggerFactory.php
new file mode 100644
index 0000000000..e71eb219f9
--- /dev/null
+++ b/src/Core/Logger/Factory/SyslogLoggerFactory.php
@@ -0,0 +1,66 @@
+config        = $config;
+		$this->introspection = $introspection;
+	}
+
+	/**
+	 * Creates and returns a PSR-3 Logger instance.
+	 *
+	 * Calling this method multiple times with the same parameters SHOULD return the same object.
+	 *
+	 * @param \Psr\Log\LogLevel::* $logLevel The log level
+	 * @param \Friendica\Core\Logger\Capability\LogChannel::* $logChannel The log channel
+	 *
+	 * @throws LogLevelException
+	 */
+	public function createLogger(string $logLevel, string $logChannel): LoggerInterface
+	{
+		$logOpts     = (int) $this->config->get('system', 'syslog_flags')    ?? SyslogLogger::DEFAULT_FLAGS;
+		$logFacility = (int) $this->config->get('system', 'syslog_facility') ?? SyslogLogger::DEFAULT_FACILITY;
+
+		if (!array_key_exists($logLevel, SyslogLogger::logLevels)) {
+			throw new LogLevelException(sprintf('The log level "%s" is not supported by "%s".', $logLevel, SyslogLogger::class));
+		}
+
+		return new SyslogLogger(
+			$logChannel,
+			$this->introspection,
+			SyslogLogger::logLevels[$logLevel],
+			$logOpts,
+			$logFacility
+		);
+	}
+}
diff --git a/src/Core/Logger/Type/StreamLogger.php b/src/Core/Logger/Type/StreamLogger.php
index 81af7e4474..03dfe69b6d 100644
--- a/src/Core/Logger/Type/StreamLogger.php
+++ b/src/Core/Logger/Type/StreamLogger.php
@@ -22,21 +22,19 @@ class StreamLogger extends AbstractLogger
 
 	/**
 	 * The minimum loglevel at which this logger will be triggered
-	 * @var string
 	 */
-	private $logLevel;
+	private int $logLevel;
 
 	/**
 	 * The stream, where the current logger is writing into
-	 * @var resource
+	 * @var resource|null
 	 */
 	private $stream;
 
 	/**
 	 * The current process ID
-	 * @var int
 	 */
-	private $pid;
+	private int $pid;
 
 	/**
 	 * Translates LogLevel log levels to integer values
diff --git a/src/Core/Logger/Type/SyslogLogger.php b/src/Core/Logger/Type/SyslogLogger.php
index 8f24af053c..8b5f34634f 100644
--- a/src/Core/Logger/Type/SyslogLogger.php
+++ b/src/Core/Logger/Type/SyslogLogger.php
@@ -29,7 +29,7 @@ class SyslogLogger extends AbstractLogger
 
 	/**
 	 * Translates LogLevel log levels to syslog log priorities.
-	 * @var array
+	 * @var array
 	 */
 	public const logLevels = [
 		LogLevel::DEBUG     => LOG_DEBUG,
@@ -60,39 +60,33 @@ class SyslogLogger extends AbstractLogger
 	/**
 	 * Indicates what logging options will be used when generating a log message
 	 * @see http://php.net/manual/en/function.openlog.php#refsect1-function.openlog-parameters
-	 *
-	 * @var int
 	 */
-	private $logOpts;
+	private int $logOpts;
 
 	/**
 	 * Used to specify what type of program is logging the message
 	 * @see http://php.net/manual/en/function.openlog.php#refsect1-function.openlog-parameters
-	 *
-	 * @var int
 	 */
-	private $logFacility;
+	private int $logFacility;
 
 	/**
 	 * The minimum loglevel at which this logger will be triggered
-	 * @var int
 	 */
-	private $logLevel;
+	private int $logLevel;
 
 	/**
 	 * A error message of the current operation
-	 * @var string
 	 */
-	private $errorMessage;
+	private string $errorMessage;
 
 	/**
 	 * {@inheritdoc}
 	 *
-	 * @param string $logLevel    The minimum loglevel at which this logger will be triggered
-	 * @param string $logOptions
-	 * @param string $logFacility
+	 * @param int $logLevel    The minimum loglevel at which this logger will be triggered
+	 * @param int $logOptions
+	 * @param int $logFacility
 	 */
-	public function __construct(string $channel, IHaveCallIntrospections $introspection, string $logLevel, string $logOptions, string $logFacility)
+	public function __construct(string $channel, IHaveCallIntrospections $introspection, int $logLevel, int $logOptions, int $logFacility)
 	{
 		parent::__construct($channel, $introspection);
 
@@ -166,7 +160,7 @@ class SyslogLogger extends AbstractLogger
 		restore_error_handler();
 
 		if (!$opened) {
-			throw new LoggerException(sprintf('Can\'t open syslog for ident "%s" and facility "%s": ' . $this->errorMessage, $this->channel, $this->logFacility));
+			throw new LoggerException(sprintf('Can\'t open syslog for ident "%s" and facility "%s": ' . $this->errorMessage, $this->channel, (string) $this->logFacility));
 		}
 
 		$this->syslogWrapper($priority, $message);
@@ -215,7 +209,7 @@ class SyslogLogger extends AbstractLogger
 		restore_error_handler();
 
 		if (!$written) {
-			throw new LoggerException(sprintf('Can\'t write into syslog for ident "%s" and facility "%s": ' . $this->errorMessage, $this->channel, $this->logFacility));
+			throw new LoggerException(sprintf('Can\'t write into syslog for ident "%s" and facility "%s": ' . $this->errorMessage, $this->channel, (string) $this->logFacility));
 		}
 	}
 }
diff --git a/src/Core/Logger/Util/FileSystem.php b/src/Core/Logger/Util/FileSystem.php
index 2d05faf50d..97162dacb8 100644
--- a/src/Core/Logger/Util/FileSystem.php
+++ b/src/Core/Logger/Util/FileSystem.php
@@ -12,7 +12,7 @@ use Friendica\Core\Logger\Exception\LoggerUnusableException;
 /**
  * Util class for filesystem manipulation for Logger classes
  */
-class FileSystem
+class FileSystem implements FileSystemUtil
 {
 	/**
 	 * @var string a error message
@@ -31,7 +31,7 @@ class FileSystem
 	public function createDir(string $file): string
 	{
 		$dirname = null;
-		$pos = strpos($file, '://');
+		$pos     = strpos($file, '://');
 
 		if (!$pos) {
 			$dirname = realpath(dirname($file));
diff --git a/src/Core/Logger/Util/FileSystemUtil.php b/src/Core/Logger/Util/FileSystemUtil.php
new file mode 100644
index 0000000000..1c1bbc2726
--- /dev/null
+++ b/src/Core/Logger/Util/FileSystemUtil.php
@@ -0,0 +1,40 @@
+db->isConnected() & !$this->mode->isInstall();
+		return $this->db->isConnected() && !$this->mode->isInstall();
 	}
 
 	/**
diff --git a/src/Core/Protocol.php b/src/Core/Protocol.php
index 1e5b7e7f21..ef57c22f18 100644
--- a/src/Core/Protocol.php
+++ b/src/Core/Protocol.php
@@ -183,7 +183,12 @@ class Protocol
 			'uid'     => $owner['uid'],
 			'result'  => null,
 		];
-		Hook::callAll('unfollow', $hook_data);
+
+		$eventDispatcher = DI::eventDispatcher();
+
+		$hook_data = $eventDispatcher->dispatch(
+			new ArrayFilterEvent(ArrayFilterEvent::UNFOLLOW_CONTACT, $hook_data),
+		)->getArray();
 
 		return $hook_data['result'];
 	}
@@ -218,7 +223,12 @@ class Protocol
 			'uid'     => $owner['uid'],
 			'result'  => null,
 		];
-		Hook::callAll('revoke_follow', $hook_data);
+
+		$eventDispatcher = DI::eventDispatcher();
+
+		$hook_data = $eventDispatcher->dispatch(
+			new ArrayFilterEvent(ArrayFilterEvent::REVOKE_FOLLOW_CONTACT, $hook_data),
+		)->getArray();
 
 		return $hook_data['result'];
 	}
@@ -256,7 +266,12 @@ class Protocol
 			'uid'     => $uid,
 			'result'  => null,
 		];
-		Hook::callAll('block', $hook_data);
+
+		$eventDispatcher = DI::eventDispatcher();
+
+		$hook_data = $eventDispatcher->dispatch(
+			new ArrayFilterEvent(ArrayFilterEvent::BLOCK_CONTACT, $hook_data),
+		)->getArray();
 
 		return $hook_data['result'];
 	}
@@ -295,7 +310,12 @@ class Protocol
 			'uid'     => $uid,
 			'result'  => null,
 		];
-		Hook::callAll('unblock', $hook_data);
+
+		$eventDispatcher = DI::eventDispatcher();
+
+		$hook_data = $eventDispatcher->dispatch(
+			new ArrayFilterEvent(ArrayFilterEvent::UNBLOCK_CONTACT, $hook_data),
+		)->getArray();
 
 		return $hook_data['result'];
 	}
diff --git a/src/Core/Session/Model/UserSession.php b/src/Core/Session/Model/UserSession.php
index cc3db7f806..70370371ff 100644
--- a/src/Core/Session/Model/UserSession.php
+++ b/src/Core/Session/Model/UserSession.php
@@ -101,7 +101,7 @@ class UserSession implements IHandleUserSessions
 	public function getUserIDForVisitorContactID(int $cid): int
 	{
 		if (empty($this->session->get('remote'))) {
-			return false;
+			return 0;
 		}
 
 		return array_search($cid, $this->session->get('remote'));
@@ -142,7 +142,7 @@ class UserSession implements IHandleUserSessions
 	{
 		return !$this->session->get('authenticated', false);
 	}
-	
+
 	/** {@inheritDoc} */
 	public function setVisitorsContacts(string $my_url)
 	{
diff --git a/src/Core/Storage/Repository/StorageManager.php b/src/Core/Storage/Repository/StorageManager.php
index bbd9e13f14..36efa3a1bf 100644
--- a/src/Core/Storage/Repository/StorageManager.php
+++ b/src/Core/Storage/Repository/StorageManager.php
@@ -20,7 +20,9 @@ use Friendica\Core\Storage\Capability\ICanWriteToStorage;
 use Friendica\Database\Database;
 use Friendica\Core\Storage\Type;
 use Friendica\DI;
+use Friendica\Event\ArrayFilterEvent;
 use Friendica\Network\HTTPException\InternalServerErrorException;
+use Psr\EventDispatcher\EventDispatcherInterface;
 use Psr\Log\LoggerInterface;
 
 /**
@@ -55,6 +57,7 @@ class StorageManager
 	private $config;
 	/** @var LoggerInterface */
 	private $logger;
+	private EventDispatcherInterface $eventDispatcher;
 	/** @var L10n */
 	private $l10n;
 
@@ -71,13 +74,14 @@ class StorageManager
 	 * @throws InvalidClassStorageException in case the active backend class is invalid
 	 * @throws StorageException in case of unexpected errors during the active backend class loading
 	 */
-	public function __construct(Database $dba, IManageConfigValues $config, LoggerInterface $logger, L10n $l10n, bool $includeAddon = true)
+	public function __construct(Database $dba, IManageConfigValues $config, LoggerInterface $logger, EventDispatcherInterface $eventDispatcher, L10n $l10n, bool $includeAddon = true)
 	{
-		$this->dba           = $dba;
-		$this->config        = $config;
-		$this->logger        = $logger;
-		$this->l10n          = $l10n;
-		$this->validBackends = $config->get('storage', 'backends', self::DEFAULT_BACKENDS);
+		$this->dba             = $dba;
+		$this->config          = $config;
+		$this->logger          = $logger;
+		$this->eventDispatcher = $eventDispatcher;
+		$this->l10n            = $l10n;
+		$this->validBackends   = $config->get('storage', 'backends', self::DEFAULT_BACKENDS);
 
 		$currentName = $this->config->get('storage', 'name');
 
@@ -146,8 +150,12 @@ class StorageManager
 					'name'           => $name,
 					'storage_config' => null,
 				];
+
 				try {
-					Hook::callAll('storage_config', $data);
+					$data = $this->eventDispatcher->dispatch(
+						new ArrayFilterEvent(ArrayFilterEvent::STORAGE_CONFIG, $data),
+					)->getArray();
+
 					if (!($data['storage_config'] ?? null) instanceof ICanConfigureStorage) {
 						throw new InvalidClassStorageException(sprintf('Configuration for backend %s was not found', $name));
 					}
@@ -201,8 +209,12 @@ class StorageManager
 						'name'    => $name,
 						'storage' => null,
 					];
+
 					try {
-						Hook::callAll('storage_instance', $data);
+						$data = $this->eventDispatcher->dispatch(
+							new ArrayFilterEvent(ArrayFilterEvent::STORAGE_INSTANCE, $data),
+						)->getArray();
+
 						if (!($data['storage'] ?? null) instanceof ICanReadFromStorage) {
 							throw new InvalidClassStorageException(sprintf('Backend %s was not found', $name));
 						}
diff --git a/src/Core/Storage/Type/Database.php b/src/Core/Storage/Type/Database.php
index 39b84528e5..6ed8527533 100644
--- a/src/Core/Storage/Type/Database.php
+++ b/src/Core/Storage/Type/Database.php
@@ -80,7 +80,7 @@ class Database implements ICanWriteToStorage
 				throw new StorageException(sprintf('Database storage failed to update %s', $reference), 500, new Exception($this->dba->errorMessage(), $this->dba->errorNo()));
 			}
 
-			return $this->dba->lastInsertId();
+			return (string) $this->dba->lastInsertId();
 		}
 	}
 
diff --git a/src/Core/System.php b/src/Core/System.php
index df494bec5d..d7367e871c 100644
--- a/src/Core/System.php
+++ b/src/Core/System.php
@@ -452,19 +452,17 @@ class System
 
 	/**
 	 * Returns the current Load of the System
-	 *
-	 * @return integer
 	 */
-	public static function currentLoad()
+	public static function currentLoad(): float
 	{
 		if (!function_exists('sys_getloadavg')) {
-			return false;
+			return (float) 0;
 		}
 
 		$load_arr = sys_getloadavg();
 
 		if (!is_array($load_arr)) {
-			return false;
+			return (float) 0;
 		}
 
 		return round(max($load_arr[0], $load_arr[1]), 2);
diff --git a/src/Core/Update.php b/src/Core/Update.php
index 7aeefc3793..dc68f280cc 100644
--- a/src/Core/Update.php
+++ b/src/Core/Update.php
@@ -217,7 +217,7 @@ class Update
 										->set('system', 'maintenance', false)
 										->delete('system', 'maintenance_reason')
 										->commit();
-							return $r;
+							return 'Pre update failed';
 						} else {
 							DI::logger()->notice('Pre update executed.', ['version' => $version]);
 						}
@@ -262,7 +262,7 @@ class Update
 										->set('system', 'maintenance', false)
 										->delete('system', 'maintenance_reason')
 										->commit();
-							return $r;
+							return 'Post update failed';
 						} else {
 							DI::config()->set('system', 'build', $version);
 							DI::logger()->notice('Post update executed.', ['version' => $version]);
diff --git a/src/Core/Worker.php b/src/Core/Worker.php
index 995239cde9..fd33278502 100644
--- a/src/Core/Worker.php
+++ b/src/Core/Worker.php
@@ -13,6 +13,7 @@ use Friendica\Core\Logger\Type\WorkerLogger;
 use Friendica\Core\Worker\Entity\Process;
 use Friendica\Database\DBA;
 use Friendica\DI;
+use Friendica\Event\ArrayFilterEvent;
 use Friendica\Util\DateTimeFormat;
 
 /**
@@ -271,12 +272,13 @@ class Worker
 	 *
 	 * @param integer $priority The priority that should be checked
 	 *
-	 * @return integer Is there a process running with that priority?
+	 * @return bool Is there a process running with that priority?
 	 * @throws \Exception
 	 */
-	private static function processWithPriorityActive(int $priority): int
+	private static function processWithPriorityActive(int $priority): bool
 	{
 		$condition = ["`priority` <= ? AND `pid` != 0 AND NOT `done`", $priority];
+
 		return DBA::exists('workerqueue', $condition);
 	}
 
@@ -955,7 +957,7 @@ class Worker
 	/**
 	 * Returns the priority of the next workerqueue job
 	 *
-	 * @return string|bool priority or FALSE on failure
+	 * @return int|false priority or FALSE on failure
 	 * @throws \Exception
 	 */
 	private static function nextPriority()
@@ -1237,10 +1239,6 @@ class Worker
 	 * @return int '0' if worker queue entry already existed or there had been an error, otherwise the ID of the worker task
 	 * @throws \Friendica\Network\HTTPException\InternalServerErrorException
 	 * @note $cmd and string args are surrounded with ''
-	 *
-	 * @hooks 'proc_run'
-	 *    array $arr
-	 *
 	 */
 	public static function add(...$args)
 	{
@@ -1250,7 +1248,12 @@ class Worker
 
 		$arr = ['args' => $args, 'run_cmd' => true];
 
-		Hook::callAll('proc_run', $arr);
+		$eventDispatcher = DI::eventDispatcher();
+
+		$arr = $eventDispatcher->dispatch(
+			new ArrayFilterEvent(ArrayFilterEvent::ADD_WORKER_TASK, $arr),
+		)->getArray();
+
 		if (!$arr['run_cmd'] || !count($args)) {
 			return 1;
 		}
diff --git a/src/Core/Worker/Repository/Process.php b/src/Core/Worker/Repository/Process.php
index 146854234e..ece8dc9db2 100644
--- a/src/Core/Worker/Repository/Process.php
+++ b/src/Core/Worker/Repository/Process.php
@@ -8,11 +8,11 @@
 namespace Friendica\Core\Worker\Repository;
 
 use Friendica\BaseRepository;
+use Friendica\Core\Worker\Entity\Process as ProcessEntity;
 use Friendica\Core\Worker\Exception\ProcessPersistenceException;
+use Friendica\Core\Worker\Factory\Process as ProcessFactory;
 use Friendica\Database\Database;
 use Friendica\Util\DateTimeFormat;
-use Friendica\Core\Worker\Factory;
-use Friendica\Core\Worker\Entity;
 use Psr\Log\LoggerInterface;
 
 /**
@@ -24,13 +24,13 @@ class Process extends BaseRepository
 
 	protected static $table_name = 'process';
 
-	/** @var Factory\Process */
+	/** @var ProcessFactory */
 	protected $factory;
 
 	/** @var string */
 	private $currentHost;
 
-	public function __construct(Database $database, LoggerInterface $logger, Factory\Process $factory, array $server)
+	public function __construct(Database $database, LoggerInterface $logger, ProcessFactory $factory, array $server)
 	{
 		parent::__construct($database, $logger, $factory);
 
@@ -39,13 +39,8 @@ class Process extends BaseRepository
 
 	/**
 	 * Starts and Returns the process for a given PID at the current host
-	 *
-	 * @param int    $pid
-	 * @param string $command
-	 *
-	 * @return Entity\Process
 	 */
-	public function create(int $pid, string $command): Entity\Process
+	public function create(int $pid, string $command): ProcessEntity
 	{
 		// Cleanup inactive process
 		$this->deleteInactive();
@@ -64,7 +59,9 @@ class Process extends BaseRepository
 				}
 			}
 
-			$result = $this->_selectOne(['pid' => $pid, 'hostname' => $this->currentHost]);
+			$fields = $this->_selectFirstRowAsArray(['pid' => $pid, 'hostname' => $this->currentHost]);
+
+			$result = $this->factory->createFromTableRow($fields);
 
 			$this->db->commit();
 
@@ -74,7 +71,7 @@ class Process extends BaseRepository
 		}
 	}
 
-	public function delete(Entity\Process $process)
+	public function delete(ProcessEntity $process)
 	{
 		try {
 			if (!$this->db->delete(static::$table_name, [
diff --git a/src/Database/Database.php b/src/Database/Database.php
index 6ee4394901..88aa636fd3 100644
--- a/src/Database/Database.php
+++ b/src/Database/Database.php
@@ -60,17 +60,17 @@ class Database
 	protected $syslock = null;
 
 	protected $server_info = '';
-	/** @var PDO|mysqli */
+	/** @var PDO|mysqli|null */
 	protected $connection;
-	protected $driver = '';
+	protected $driver               = '';
 	protected $pdo_emulate_prepares = false;
-	private $error = '';
-	private $errorno = 0;
-	private $affected_rows = 0;
-	protected $in_transaction = false;
-	protected $in_retrial = false;
-	protected $testmode = false;
-	private $relation = [];
+	private $error                  = '';
+	private $errorno                = 0;
+	private $affected_rows          = 0;
+	protected $in_transaction       = false;
+	protected $in_retrial           = false;
+	protected $testmode             = false;
+	private $relation               = [];
 	/** @var DbaDefinition */
 	protected $dbaDefinition;
 	/** @var ViewDefinition */
@@ -86,7 +86,7 @@ class Database
 		$this->viewDefinition = $viewDefinition;
 
 		// Use dummy values - necessary for the first factory call of the logger itself
-		$this->logger = new NullLogger();
+		$this->logger   = new NullLogger();
 		$this->profiler = new Profiler($config);
 
 		$this->connect();
@@ -253,7 +253,7 @@ class Database
 	/**
 	 * Return the database object.
 	 *
-	 * @return PDO|mysqli
+	 * @return PDO|mysqli|null
 	 */
 	public function getConnection()
 	{
@@ -474,7 +474,7 @@ class Database
 	 *
 	 * @param string $sql SQL statement
 	 *
-	 * @return bool|object statement object or result object
+	 * @return bool|mysqli_result|mysqli_stmt|object|PDOStatement statement object or result object
 	 * @throws \Exception
 	 */
 	public function p(string $sql)
@@ -630,7 +630,7 @@ class Database
 					} elseif (is_string($args[$param])) {
 						$param_types .= 's';
 					} elseif (is_object($args[$param]) && method_exists($args[$param], '__toString')) {
-						$param_types  .= 's';
+						$param_types .= 's';
 						$args[$param] = (string)$args[$param];
 					} else {
 						$param_types .= 'b';
@@ -677,9 +677,9 @@ class Database
 			}
 
 			$this->logger->error('DB Error', [
-				'code'      => $errorno,
-				'error'     => $error,
-				'params'    => $this->replaceParameters($sql, $args),
+				'code'   => $errorno,
+				'error'  => $error,
+				'params' => $this->replaceParameters($sql, $args),
 			]);
 
 			// On a lost connection we try to reconnect - but only once.
@@ -784,9 +784,9 @@ class Database
 			}
 
 			$this->logger->error('DB Error', [
-				'code'      => $errorno,
-				'error'     => $error,
-				'params'    => $this->replaceParameters($sql, $params),
+				'code'   => $errorno,
+				'error'  => $error,
+				'params' => $this->replaceParameters($sql, $params),
 			]);
 
 			// On a lost connection we simply quit.
@@ -1335,7 +1335,7 @@ class Database
 			return true;
 		}
 
-		$fields = $this->castFields($table, $fields);
+		$fields        = $this->castFields($table, $fields);
 		$direct_fields = [];
 
 		foreach ($fields as $key => $value) {
diff --git a/src/Database/Definition/DbaDefinition.php b/src/Database/Definition/DbaDefinition.php
index 378ba86867..c5d77d3a46 100644
--- a/src/Database/Definition/DbaDefinition.php
+++ b/src/Database/Definition/DbaDefinition.php
@@ -8,9 +8,8 @@
 namespace Friendica\Database\Definition;
 
 use Exception;
-use Friendica\Core\Config\Capability\IManageConfigValues;
-use Friendica\Core\Hook;
 use Friendica\DI;
+use Friendica\Event\ArrayFilterEvent;
 
 /**
  * Stores the whole database definition
@@ -109,12 +108,16 @@ class DbaDefinition
 	{
 		$definition = require $this->configFile;
 
-		if (!$definition) {
+		if (!is_array($definition)) {
 			throw new Exception('Corrupted database structure config file static/dbstructure.config.php');
 		}
 
 		if ($withAddonStructure) {
-			Hook::callAll('dbstructure_definition', $definition);
+			$eventDispatcher = DI::eventDispatcher();
+
+			$definition = $eventDispatcher->dispatch(
+				new ArrayFilterEvent(ArrayFilterEvent::DB_STRUCTURE_DEFINITION, $definition),
+			)->getArray();
 		}
 
 		$this->definition = $definition;
diff --git a/src/Database/Definition/ViewDefinition.php b/src/Database/Definition/ViewDefinition.php
index b64e0d0ffc..3c6cb2f43a 100644
--- a/src/Database/Definition/ViewDefinition.php
+++ b/src/Database/Definition/ViewDefinition.php
@@ -8,7 +8,8 @@
 namespace Friendica\Database\Definition;
 
 use Exception;
-use Friendica\Core\Hook;
+use Friendica\DI;
+use Friendica\Event\ArrayFilterEvent;
 
 /**
  * Stores the whole View definitions
@@ -62,12 +63,16 @@ class ViewDefinition
 	{
 		$definition = require $this->configFile;
 
-		if (!$definition) {
+		if (!is_array($definition)) {
 			throw new Exception('Corrupted database structure config file static/dbstructure.config.php');
 		}
 
 		if ($withAddonStructure) {
-			Hook::callAll('dbview_definition', $definition);
+			$eventDispatcher = DI::eventDispatcher();
+
+			$definition = $eventDispatcher->dispatch(
+				new ArrayFilterEvent(ArrayFilterEvent::DB_VIEW_DEFINITION, $definition),
+			)->getArray();
 		}
 
 		$this->definition = $definition;
diff --git a/src/Event/ArrayFilterEvent.php b/src/Event/ArrayFilterEvent.php
index d21c6f8d78..864f269cdb 100644
--- a/src/Event/ArrayFilterEvent.php
+++ b/src/Event/ArrayFilterEvent.php
@@ -24,24 +24,96 @@ final class ArrayFilterEvent extends Event
 
 	public const FEATURE_GET = 'friendica.data.feature_get';
 
-	public const POST_LOCAL_START = 'friendica.data.post_local_start';
+	public const PERMISSION_TOOLTIP_CONTENT = 'friendica.data.permission_tooltip_content';
 
-	public const POST_LOCAL = 'friendica.data.post_local';
+	public const INSERT_POST_LOCAL_START = 'friendica.data.insert_post_local_start';
 
-	public const POST_LOCAL_END = 'friendica.data.post_local_end';
+	public const INSERT_POST_LOCAL = 'friendica.data.insert_post_local';
+
+	public const INSERT_POST_LOCAL_END = 'friendica.data.insert_post_local_end';
+
+	public const INSERT_POST_REMOTE = 'friendica.data.insert_post_remote';
+
+	public const INSERT_POST_REMOTE_END = 'friendica.data.insert_post_remote_end';
+
+	/**
+	 * item array before any work
+	 */
+	public const PREPARE_POST_START = 'friendica.data.prepare_post_start';
+
+	/**
+	 * before first bbcode to html
+	 */
+	public const PREPARE_POST_FILTER_CONTENT = 'friendica.data.prepare_post_filter_content';
+
+	/**
+	 * after first bbcode to html
+	 */
+	public const PREPARE_POST = 'friendica.data.prepare_post';
+
+	/**
+	 * after attach icons and blockquote special case handling (spoiler, author)
+	 */
+	public const PREPARE_POST_END = 'friendica.data.prepare_post_end';
 
 	public const PHOTO_UPLOAD_FORM = 'friendica.data.photo_upload_form';
 
+	public const PHOTO_UPLOAD_START = 'friendica.data.photo_upload_start';
+
+	public const PHOTO_UPLOAD = 'friendica.data.photo_upload';
+
+	public const PHOTO_UPLOAD_END = 'friendica.data.photo_upload_end';
+
 	public const NETWORK_TO_NAME = 'friendica.data.network_to_name';
 
+	public const NETWORK_CONTENT_START = 'friendica.data.network_content_start';
+
+	public const NETWORK_CONTENT_TABS = 'friendica.data.network_content_tabs';
+
+	public const PARSE_LINK = 'friendica.data.parse_link';
+
 	public const CONVERSATION_START = 'friendica.data.conversation_start';
 
+	public const FETCH_ITEM_BY_LINK = 'friendica.data.fetch_item_by_link';
+
+	public const ITEM_TAGGED = 'friendica.data.item_tagged';
+
 	public const DISPLAY_ITEM = 'friendica.data.display_item';
 
+	public const CACHE_ITEM = 'friendica.data.cache_item';
+
+	public const CHECK_ITEM_NOTIFICATION = 'friendica.data.check_item_notification';
+
+	public const ENOTIFY = 'friendica.data.enotify';
+
+	public const ENOTIFY_STORE = 'friendica.data.enotify_store';
+
+	public const ENOTIFY_MAIL = 'friendica.data.enotify_mail';
+
+	public const DETECT_LANGUAGES = 'friendica.data.detect_languages';
+
 	public const RENDER_LOCATION = 'friendica.data.render_location';
 
 	public const ITEM_PHOTO_MENU = 'friendica.data.item_photo_menu';
 
+	public const DIRECTORY_ITEM = 'friendica.data.directory_item';
+
+	public const CONTACT_PHOTO_MENU = 'friendica.data.contact_photo_menu';
+
+	public const PROFILE_SIDEBAR_ENTRY = 'friendica.data.profile_sidebar_entry';
+
+	public const PROFILE_SIDEBAR = 'friendica.data.profile_sidebar';
+
+	public const PROFILE_TABS = 'friendica.data.profile_tabs';
+
+	public const PROFILE_SETTINGS_FORM = 'friendica.data.profile_settings_form';
+
+	public const PROFILE_SETTINGS_POST = 'friendica.data.profile_settings_post';
+
+	public const MODERATION_USERS_TABS = 'friendica.data.moderation_users_tabs';
+
+	public const ACL_LOOKUP_END = 'friendica.data.acl_lookup_end';
+
 	public const OEMBED_FETCH_END = 'friendica.data.oembed_fetch_end';
 
 	public const PAGE_INFO = 'friendica.data.page_info';
@@ -62,6 +134,46 @@ final class ArrayFilterEvent extends Event
 
 	public const PROTOCOL_SUPPORTS_PROBE = 'friendica.data.protocol_supports_probe';
 
+	public const FOLLOW_CONTACT = 'friendica.data.follow_contact';
+
+	public const UNFOLLOW_CONTACT = 'friendica.data.unfollow_contact';
+
+	public const REVOKE_FOLLOW_CONTACT = 'friendica.data.revoke_follow_contact';
+
+	public const BLOCK_CONTACT = 'friendica.data.block_contact';
+
+	public const UNBLOCK_CONTACT = 'friendica.data.unblock_contact';
+
+	public const EDIT_CONTACT_FORM = 'friendica.data.edit_contact_form';
+
+	public const EDIT_CONTACT_POST = 'friendica.data.edit_contact_post';
+
+	public const AVATAR_LOOKUP = 'friendica.data.avatar_lookup';
+
+	public const ACCOUNT_AUTHENTICATE = 'friendica.data.account_authenticate';
+
+	public const ACCOUNT_REGISTER_FORM = 'friendica.data.account_register_form';
+
+	public const ACCOUNT_REGISTER_POST = 'friendica.data.account_register_post';
+
+	public const ACCOUNT_REGISTER = 'friendica.data.account_register';
+
+	public const ACCOUNT_REMOVE = 'friendica.data.account_remove';
+
+	public const EVENT_CREATED = 'friendica.data.event_created';
+
+	public const EVENT_UPDATED = 'friendica.data.event_updated';
+
+	public const ADD_WORKER_TASK = 'friendica.data.add_worker_task';
+
+	public const STORAGE_CONFIG = 'friendica.data.storage_config';
+
+	public const STORAGE_INSTANCE = 'friendica.data.storage_instance';
+
+	public const DB_STRUCTURE_DEFINITION = 'friendica.data.db_structure_definition';
+
+	public const DB_VIEW_DEFINITION = 'friendica.data.db_view_definition';
+
 	private array $array;
 
 	public function __construct(string $name, array $array)
diff --git a/src/Event/HtmlFilterEvent.php b/src/Event/HtmlFilterEvent.php
index cb8a3992df..1275e0791d 100644
--- a/src/Event/HtmlFilterEvent.php
+++ b/src/Event/HtmlFilterEvent.php
@@ -26,6 +26,12 @@ final class HtmlFilterEvent extends Event
 
 	public const PAGE_END = 'friendica.html.page_end';
 
+	public const MOD_HOME_CONTENT = 'friendica.html.mod_home_content';
+
+	public const MOD_ABOUT_CONTENT = 'friendica.html.mod_about_content';
+
+	public const MOD_PROFILE_CONTENT = 'friendica.html.mod_profile_content';
+
 	public const JOT_TOOL = 'friendica.html.jot_tool';
 
 	public const CONTACT_BLOCK_END = 'friendica.html.contact_block_end';
diff --git a/src/Factory/Api/Friendica/Photo.php b/src/Factory/Api/Friendica/Photo.php
index 82c80ac7f3..051ae66a5d 100644
--- a/src/Factory/Api/Friendica/Photo.php
+++ b/src/Factory/Api/Friendica/Photo.php
@@ -41,7 +41,6 @@ class Photo extends BaseFactory
 	 * @param int    $scale
 	 * @param int    $uid
 	 * @param string $type
-	 * @return Array
 	 */
 	public function createFromId(string $photo_id, int $scale = null, int $uid, string $type = 'json', bool $with_posts = true): array
 	{
@@ -66,7 +65,7 @@ class Photo extends BaseFactory
 		$data['id']       = $data['resource-id'];
 
 		if (is_int($scale)) {
-			$data['data'] = base64_encode(ModelPhoto::getImageDataForPhoto($data));
+			$data['data'] = base64_encode(ModelPhoto::getImageDataForPhoto($data) ?? '');
 		}
 
 		if ($type == 'xml') {
diff --git a/src/Factory/Api/Mastodon/Attachment.php b/src/Factory/Api/Mastodon/Attachment.php
index ae0ebf1826..65eeec22eb 100644
--- a/src/Factory/Api/Mastodon/Attachment.php
+++ b/src/Factory/Api/Mastodon/Attachment.php
@@ -11,7 +11,7 @@ use Friendica\App\BaseURL;
 use Friendica\BaseFactory;
 use Friendica\Model\Attach;
 use Friendica\Model\Photo;
-use Friendica\Network\HTTPException;
+use Friendica\Network\HTTPException\InternalServerErrorException;
 use Friendica\Model\Post;
 use Friendica\Util\Images;
 use Friendica\Util\Proxy;
@@ -32,7 +32,7 @@ class Attachment extends BaseFactory
 	/**
 	 * @param int $uriId Uri-ID of the attachments
 	 * @return array
-	 * @throws HTTPException\InternalServerErrorException
+	 * @throws InternalServerErrorException
 	 */
 	public function createFromUriId(int $uriId): array
 	{
@@ -47,23 +47,25 @@ class Attachment extends BaseFactory
 	/**
 	 * @param int $id id of the media
 	 * @return \Friendica\Object\Api\Mastodon\Attachment
-	 * @throws HTTPException\InternalServerErrorException
+	 * @throws InternalServerErrorException
 	 */
 	public function createFromId(int $id): \Friendica\Object\Api\Mastodon\Attachment
 	{
 		$attachment = Post\Media::getById($id);
+
 		if (empty($attachment)) {
-			return [];
+			throw new InternalServerErrorException();
 		}
+
 		return $this->createFromMediaArray($attachment);
 	}
 
 	/**
 	 * @param array $attachment
 	 * @return \Friendica\Object\Api\Mastodon\Attachment
-	 * @throws HTTPException\InternalServerErrorException
+	 * @throws InternalServerErrorException
 	 */
-	private function createFromMediaArray(array $attachment):  \Friendica\Object\Api\Mastodon\Attachment
+	private function createFromMediaArray(array $attachment): \Friendica\Object\Api\Mastodon\Attachment
 	{
 		$filetype = !empty($attachment['mimetype']) ? strtolower(substr($attachment['mimetype'], 0, strpos($attachment['mimetype'], '/'))) : '';
 
@@ -100,7 +102,7 @@ class Attachment extends BaseFactory
 	 * @param int $id id of the photo
 	 *
 	 * @return array
-	 * @throws HTTPException\InternalServerErrorException
+	 * @throws InternalServerErrorException
 	 */
 	public function createFromPhoto(int $id): array
 	{
@@ -136,7 +138,7 @@ class Attachment extends BaseFactory
 	 * @param int $id id of the attachment
 	 *
 	 * @return array
-	 * @throws HTTPException\InternalServerErrorException
+	 * @throws InternalServerErrorException
 	 */
 	public function createFromAttach(int $id): array
 	{
diff --git a/src/Factory/Api/Twitter/Status.php b/src/Factory/Api/Twitter/Status.php
index b31a89fc44..7d235cc870 100644
--- a/src/Factory/Api/Twitter/Status.php
+++ b/src/Factory/Api/Twitter/Status.php
@@ -39,7 +39,7 @@ class Status extends BaseFactory
 	private $mention;
 	/** @var Activities entity */
 	private $activities;
-	/** @var Activities entity */
+	/** @var Attachment entity */
 	private $attachment;
 	/** @var ContentItem */
 	private $contentItem;
@@ -111,7 +111,7 @@ class Status extends BaseFactory
 	 */
 	private function createFromArray(array $item, int $uid, bool $include_entities): \Friendica\Object\Api\Twitter\Status
 	{
-		$item = Post\Media::addHTMLAttachmentToItem($item);
+		$item   = Post\Media::addHTMLAttachmentToItem($item);
 		$author = $this->twitterUser->createFromContactId($item['author-id'], $uid, true);
 
 		if (!empty($item['causer-id']) && ($item['post-reason'] == Item::PR_ANNOUNCEMENT)) {
@@ -165,7 +165,7 @@ class Status extends BaseFactory
 			$urls     = $this->url->createFromUriId($item['uri-id']);
 			$mentions = $this->mention->createFromUriId($item['uri-id']);
 		} else {
-			$attachments = $this->attachment->createFromUriId($item['uri-id'], $text);
+			$attachments = $this->attachment->createFromUriId($item['uri-id']);
 		}
 
 		$friendica_activities = $this->activities->createFromUriId($item['uri-id'], $uid);
@@ -180,7 +180,7 @@ class Status extends BaseFactory
 				$urls     = array_merge($urls, $this->url->createFromUriId($shared_uri_id));
 				$mentions = array_merge($mentions, $this->mention->createFromUriId($shared_uri_id));
 			} else {
-				$attachments = array_merge($attachments, $this->attachment->createFromUriId($shared_uri_id, $text));
+				$attachments = array_merge($attachments, $this->attachment->createFromUriId($shared_uri_id));
 			}
 		}
 
@@ -203,6 +203,6 @@ class Status extends BaseFactory
 			$entities = [];
 		}
 
-		return new \Friendica\Object\Api\Twitter\Status($text, $statusnetHtml, $friendicaHtml, $item, $author, $owner, $retweeted, $quoted, $geo, $friendica_activities, $entities, $attachments,  $friendica_comments, $liked);
+		return new \Friendica\Object\Api\Twitter\Status($text, $statusnetHtml, $friendicaHtml, $item, $author, $owner, $retweeted, $quoted, $geo, $friendica_activities, $entities, $attachments, $friendica_comments, $liked);
 	}
 }
diff --git a/src/Federation/Repository/GServer.php b/src/Federation/Repository/GServer.php
index 5a12f3339b..d299210800 100644
--- a/src/Federation/Repository/GServer.php
+++ b/src/Federation/Repository/GServer.php
@@ -8,26 +8,37 @@
 namespace Friendica\Federation\Repository;
 
 use Friendica\Database\Database;
-use Friendica\Federation\Factory;
-use Friendica\Federation\Entity;
-use Psr\Log\LoggerInterface;
+use Friendica\Federation\Entity\GServer as GServerEntity;
+use Friendica\Federation\Factory\GServer as GServerFactory;
+use Friendica\Network\HTTPException\NotFoundException;
 
-class GServer extends \Friendica\BaseRepository
+final class GServer
 {
-	protected static $table_name = 'gserver';
+	private string $table_name = 'gserver';
 
-	public function __construct(Database $database, LoggerInterface $logger, Factory\GServer $factory)
+	private Database $db;
+
+	private GServerFactory $factory;
+
+	public function __construct(Database $database, GServerFactory $factory)
 	{
-		parent::__construct($database, $logger, $factory);
+		$this->db      = $database;
+		$this->factory = $factory;
 	}
 
 	/**
 	 * @param int $gsid
-	 * @return Entity\GServer
+	 *
 	 * @throws \Friendica\Network\HTTPException\NotFoundException
 	 */
-	public function selectOneById(int $gsid): Entity\GServer
+	public function selectOneById(int $gsid): GServerEntity
 	{
-		return $this->_selectOne(['id' => $gsid]);
+		$fields = $this->db->selectFirst($this->table_name, [], ['id' => $gsid], []);
+
+		if (!$this->db->isResult($fields)) {
+			throw new NotFoundException();
+		}
+
+		return $this->factory->createFromTableRow($fields);
 	}
 }
diff --git a/src/Model/Contact.php b/src/Model/Contact.php
index cced0ed626..4684317e76 100644
--- a/src/Model/Contact.php
+++ b/src/Model/Contact.php
@@ -14,7 +14,6 @@ use Friendica\Contact\Introduction\Exception\IntroductionNotFoundException;
 use Friendica\Content\Conversation as ConversationContent;
 use Friendica\Content\Pager;
 use Friendica\Content\Text\HTML;
-use Friendica\Core\Hook;
 use Friendica\Core\Protocol;
 use Friendica\Core\Renderer;
 use Friendica\Core\System;
@@ -22,6 +21,7 @@ use Friendica\Core\Worker;
 use Friendica\Database\Database;
 use Friendica\Database\DBA;
 use Friendica\DI;
+use Friendica\Event\ArrayFilterEvent;
 use Friendica\Network\HTTPClient\Client\HttpClientAccept;
 use Friendica\Network\HTTPClient\Client\HttpClientOptions;
 use Friendica\Network\HTTPException\NotFoundException;
@@ -1311,9 +1311,17 @@ class Contact
 			}
 		}
 
-		$args = ['contact' => $contact, 'menu' => &$menu];
+		$args = ['contact' => $contact, 'menu' => $menu];
 
-		Hook::callAll('contact_photo_menu', $args);
+		$eventDispatcher = DI::eventDispatcher();
+
+		$args = $eventDispatcher->dispatch(
+			new ArrayFilterEvent(ArrayFilterEvent::CONTACT_PHOTO_MENU, $args),
+		)->getArray();
+
+		if (is_array($args['menu'])) {
+			$menu = $args['menu'];
+		}
 
 		$menucondensed = [];
 
@@ -2196,7 +2204,11 @@ class Contact
 		$avatar['url']     = '';
 		$avatar['success'] = false;
 
-		Hook::callAll('avatar_lookup', $avatar);
+		$eventDispatcher = DI::eventDispatcher();
+
+		$avatar = $eventDispatcher->dispatch(
+			new ArrayFilterEvent(ArrayFilterEvent::AVATAR_LOOKUP, $avatar),
+		)->getArray();
 
 		if ($avatar['success'] && !empty($avatar['url'])) {
 			return $avatar['url'];
@@ -3144,7 +3156,11 @@ class Contact
 
 		$arr = ['url' => $url, 'uid' => $uid, 'contact' => []];
 
-		Hook::callAll('follow', $arr);
+		$eventDispatcher = DI::eventDispatcher();
+
+		$arr = $eventDispatcher->dispatch(
+			new ArrayFilterEvent(ArrayFilterEvent::FOLLOW_CONTACT, $arr),
+		)->getArray();
 
 		if (empty($arr)) {
 			$result['message'] = DI::l10n()->t('The contact could not be added. Please check the relevant network credentials in your Settings -> Social Networks page.');
diff --git a/src/Model/Contact/User.php b/src/Model/Contact/User.php
index 17663e233b..aa85013811 100644
--- a/src/Model/Contact/User.php
+++ b/src/Model/Contact/User.php
@@ -30,9 +30,8 @@ class User
 	 * Insert a user-contact for a given contact array
 	 *
 	 * @param array $contact
-	 * @return void
 	 */
-	public static function insertForContactArray(array $contact)
+	public static function insertForContactArray(array $contact): bool
 	{
 		if (empty($contact['uid'])) {
 			// We don't create entries for the public user - by now
@@ -339,7 +338,7 @@ class User
 	{
 		$pcid = Contact::getPublicContactId($cid, $uid);
 		if (!$pcid) {
-			return false;
+			return self::FREQUENCY_DEFAULT;
 		}
 
 		$public_contact = DBA::selectFirst('user-contact', ['channel-frequency'], ['cid' => $pcid, 'uid' => $uid]);
diff --git a/src/Model/Event.php b/src/Model/Event.php
index 6cb73f4c37..11fe8a9b4b 100644
--- a/src/Model/Event.php
+++ b/src/Model/Event.php
@@ -9,11 +9,11 @@ namespace Friendica\Model;
 
 use Friendica\Content\Feature;
 use Friendica\Content\Text\BBCode;
-use Friendica\Core\Hook;
 use Friendica\Core\Renderer;
 use Friendica\Core\System;
 use Friendica\Database\DBA;
 use Friendica\DI;
+use Friendica\Event\ArrayFilterEvent;
 use Friendica\Network\HTTPException\InternalServerErrorException;
 use Friendica\Network\HTTPException\NotFoundException;
 use Friendica\Network\HTTPException\UnauthorizedException;
@@ -255,6 +255,7 @@ class Event
 			'finish'    => DateTimeFormat::utc(($arr['finish'] ?? '') ?: DBA::NULL_DATETIME),
 		];
 
+		$eventDispatcher = DI::eventDispatcher();
 
 		if ($event['finish'] < DBA::NULL_DATETIME) {
 			$event['finish'] = DBA::NULL_DATETIME;
@@ -295,17 +296,21 @@ class Event
 				Item::update($fields, ['id' => $item['id']]);
 			}
 
-			Hook::callAll('event_updated', $event['id']);
+			$eventDispatcher->dispatch(
+				new ArrayFilterEvent(ArrayFilterEvent::EVENT_UPDATED, ['event' => $event]),
+			);
 		} else {
 			// New event. Store it.
 			DBA::insert('event', $event);
 
 			$event['id'] = DBA::lastInsertId();
 
-			Hook::callAll("event_created", $event['id']);
+			$eventDispatcher->dispatch(
+				new ArrayFilterEvent(ArrayFilterEvent::EVENT_CREATED, ['event' => $event]),
+			);
 		}
 
-		return $event['id'];
+		return (int) $event['id'];
 	}
 
 	public static function getItemArrayForId(int $event_id, array $item = []): array
@@ -397,7 +402,7 @@ class Event
 	{
 		// First day of the week (0 = Sunday).
 		$firstDay    = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'calendar', 'first_day_of_week') ?? 0;
-		$defaultView = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'calendar', 'defaultView')       ?? 'month';
+		$defaultView = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'calendar', 'default_view')      ?? 'month';
 
 		return [
 			'firstDay'    => $firstDay,
diff --git a/src/Model/GServer.php b/src/Model/GServer.php
index 7029adbb53..8ead599408 100644
--- a/src/Model/GServer.php
+++ b/src/Model/GServer.php
@@ -2200,8 +2200,6 @@ class GServer
 	 * Converts input value to a boolean value
 	 *
 	 * @param string|integer $val
-	 *
-	 * @return boolean
 	 */
 	private static function toBoolean($val): bool
 	{
@@ -2211,7 +2209,7 @@ class GServer
 			return false;
 		}
 
-		return $val;
+		return (bool) $val;
 	}
 
 	/**
diff --git a/src/Model/Item.php b/src/Model/Item.php
index 247bd06435..cac836a7db 100644
--- a/src/Model/Item.php
+++ b/src/Model/Item.php
@@ -14,7 +14,6 @@ use Friendica\Content\Post\Collection\PostMedias;
 use Friendica\Content\Post\Entity\PostMedia;
 use Friendica\Content\Text\BBCode;
 use Friendica\Content\Text\HTML;
-use Friendica\Core\Hook;
 use Friendica\Core\L10n;
 use Friendica\Core\Protocol;
 use Friendica\Core\Renderer;
@@ -672,7 +671,7 @@ class Item
 	/**
 	 * Inserts item record
 	 *
-	 * @param array $item Item array to be inserted
+	 * @param array $item Item array to be inserted
 	 * @param int   $notify Notification (type?)
 	 * @param bool  $post_local (???)
 	 * @return int Zero means error, otherwise primary key (id) is being returned
@@ -695,6 +694,7 @@ class Item
 
 		// If it is a posting where users should get notifications, then define it as wall posting
 		if ($notify) {
+			/** @var array */
 			$item = $itemHelper->prepareOriginPost($item);
 
 			if (is_int($notify) && in_array($notify, Worker::PRIORITIES)) {
@@ -708,6 +708,7 @@ class Item
 			$item['network'] = trim(($item['network'] ?? '') ?: Protocol::PHANTOM);
 		}
 
+		/** @var array */
 		$item = $itemHelper->prepareItemData($item, (bool) $notify);
 
 		// Store conversation data
@@ -749,6 +750,7 @@ class Item
 			}
 		}
 
+		/** @var array */
 		$item = $itemHelper->validateItemData($item);
 
 		// Ensure that there is an avatar cache
@@ -846,16 +848,26 @@ class Item
 				$dummy_session = false;
 			}
 
-			$item = $eventDispatcher->dispatch(
-				new ArrayFilterEvent(ArrayFilterEvent::POST_LOCAL, $item)
+			$hook_data = [
+				'item' => $item,
+			];
+
+			$hook_data = $eventDispatcher->dispatch(
+				new ArrayFilterEvent(ArrayFilterEvent::INSERT_POST_LOCAL, $hook_data)
 			)->getArray();
 
+			/** @var array */
+			$item = $hook_data['item'] ?? $item;
+
 			if ($dummy_session) {
 				unset($_SESSION['authenticated']);
 				unset($_SESSION['uid']);
 			}
 		} elseif (!$notify) {
-			Hook::callAll('post_remote', $item);
+			/** @var array */
+			$item = $eventDispatcher->dispatch(
+				new ArrayFilterEvent(ArrayFilterEvent::INSERT_POST_REMOTE, $item)
+			)->getArray();
 		}
 
 		if (!empty($item['cancel'])) {
@@ -1112,7 +1124,11 @@ class Item
 				DI::contentItem()->copyPermissions($posted_item['thr-parent-id'], $posted_item['uri-id'], $posted_item['parent-uri-id']);
 			}
 		} else {
-			Hook::callAll('post_remote_end', $posted_item);
+			$eventDispatcher = DI::eventDispatcher();
+
+			$posted_item = $eventDispatcher->dispatch(
+				new ArrayFilterEvent(ArrayFilterEvent::INSERT_POST_REMOTE_END, $posted_item)
+			)->getArray();
 		}
 
 		if ($posted_item['gravity'] === self::GRAVITY_PARENT) {
@@ -1897,18 +1913,23 @@ class Item
 
 		$result = [];
 
+		$eventDispatcher = DI::eventDispatcher();
+
 		foreach (self::splitByBlocks($searchtext) as $block) {
 			$languages = $ld->detect($block)->close() ?: [];
 
-			$data = [
+			$hook_data = [
 				'text'      => $block,
 				'detected'  => $languages,
 				'uri-id'    => $uri_id,
 				'author-id' => $author_id,
 			];
-			Hook::callAll('detect_languages', $data);
 
-			foreach ($data['detected'] as $language => $quality) {
+			$hook_data = $eventDispatcher->dispatch(
+				new ArrayFilterEvent(ArrayFilterEvent::DETECT_LANGUAGES, $hook_data),
+			)->getArray();
+
+			foreach ($hook_data['detected'] as $language => $quality) {
 				$result[$language] = max($result[$language] ?? 0, $quality * (strlen($block) / strlen($searchtext)));
 			}
 		}
@@ -2274,9 +2295,16 @@ class Item
 				return true;
 			}
 
-			$arr = ['item' => $item, 'user' => $owner];
+			$eventDispatcher = DI::eventDispatcher();
 
-			Hook::callAll('tagged', $arr);
+			$arr = [
+				'item' => $item,
+				'user' => $owner,
+			];
+
+			$eventDispatcher->dispatch(
+				new ArrayFilterEvent(ArrayFilterEvent::ITEM_TAGGED, $arr),
+			);
 		} else {
 			if (Tag::isMentioned($item['parent-uri-id'], $owner['url'])) {
 				DI::logger()->info('Mention found in parent tag.', ['uri' => $item['uri'], 'uid' => $uid, 'id' => $item_id, 'uri-id' => $item['uri-id'], 'guid' => $item['guid']]);
@@ -3020,8 +3048,18 @@ class Item
 			$item['rendered-html'] = BBCode::convertForUriId($item['uri-id'], $item['body']);
 			$item['rendered-hash'] = hash('md5', BBCode::VERSION . '::' . $body);
 
-			$hook_data = ['item' => $item, 'rendered-html' => $item['rendered-html'], 'rendered-hash' => $item['rendered-hash']];
-			Hook::callAll('put_item_in_cache', $hook_data);
+			$hook_data = [
+				'rendered-html' => $item['rendered-html'],
+				'rendered-hash' => $item['rendered-hash'],
+				'item'          => $item,
+			];
+
+			$eventDispatcher = DI::eventDispatcher();
+
+			$hook_data = $eventDispatcher->dispatch(
+				new ArrayFilterEvent(ArrayFilterEvent::CACHE_ITEM, $hook_data),
+			)->getArray();
+
 			$item['rendered-html'] = $hook_data['rendered-html'];
 			$item['rendered-hash'] = $hook_data['rendered-hash'];
 			unset($hook_data);
@@ -3052,16 +3090,22 @@ class Item
 	 * @return string item body html
 	 * @throws \Friendica\Network\HTTPException\InternalServerErrorException
 	 * @throws \ImagickException
-	 * @hook  prepare_body_init item array before any work
-	 * @hook  prepare_body_content_filter ('item'=>item array, 'filter_reasons'=>string array) before first bbcode to html
-	 * @hook  prepare_body ('item'=>item array, 'html'=>body string, 'is_preview'=>boolean, 'filter_reasons'=>string array) after first bbcode to html
-	 * @hook  prepare_body_final ('item'=>item array, 'html'=>body string) after attach icons and blockquote special case handling (spoiler, author)
 	 */
 	public static function prepareBody(array &$item, bool $attach = false, bool $is_preview = false, bool $only_cache = false): string
 	{
-		$appHelper = DI::appHelper();
-		$uid       = DI::userSession()->getLocalUserId();
-		Hook::callAll('prepare_body_init', $item);
+		$appHelper       = DI::appHelper();
+		$uid             = DI::userSession()->getLocalUserId();
+		$eventDispatcher = DI::eventDispatcher();
+
+		$hook_data = [
+			'item' => $item,
+		];
+
+		$hook_data = $eventDispatcher->dispatch(
+			new ArrayFilterEvent(ArrayFilterEvent::PREPARE_POST_START, $hook_data),
+		)->getArray();
+
+		$item = $hook_data['item'] ?? $item;
 
 		// In order to provide theme developers more possibilities, event items
 		// are treated differently.
@@ -3180,7 +3224,11 @@ class Item
 				'item'           => $item,
 				'filter_reasons' => $filter_reasons
 			];
-			Hook::callAll('prepare_body_content_filter', $hook_data);
+
+			$hook_data = $eventDispatcher->dispatch(
+				new ArrayFilterEvent(ArrayFilterEvent::PREPARE_POST_FILTER_CONTENT, $hook_data),
+			)->getArray();
+
 			$filter_reasons = $hook_data['filter_reasons'];
 			unset($hook_data);
 		}
@@ -3199,7 +3247,11 @@ class Item
 			'preview'        => $is_preview,
 			'filter_reasons' => $filter_reasons
 		];
-		Hook::callAll('prepare_body', $hook_data);
+
+		$hook_data = $eventDispatcher->dispatch(
+			new ArrayFilterEvent(ArrayFilterEvent::PREPARE_POST, $hook_data),
+		)->getArray();
+
 		$s = $hook_data['html'];
 
 		unset($hook_data);
@@ -3251,9 +3303,16 @@ class Item
 
 		$s = HTML::applyContentFilter($s, $filter_reasons);
 
-		$hook_data = ['item' => $item, 'html' => $s];
-		Hook::callAll('prepare_body_final', $hook_data);
-		return $hook_data['html'];
+		$hook_data = [
+			'item' => $item,
+			'html' => $s,
+		];
+
+		$hook_data = $eventDispatcher->dispatch(
+			new ArrayFilterEvent(ArrayFilterEvent::PREPARE_POST_END, $hook_data),
+		)->getArray();
+
+		return (string) $hook_data['html'] ?? $s;
 	}
 
 	/**
@@ -3271,7 +3330,7 @@ class Item
 		}
 
 		$dom = new \DOMDocument();
-		if (!@$dom->loadHTML($html)) {
+		if (empty($html) || !@$dom->loadHTML($html)) {
 			return $html;
 		}
 
@@ -3902,17 +3961,21 @@ class Item
 			return 0;
 		}
 
-		$hookData = [
+		$eventDispatcher = DI::eventDispatcher();
+
+		$hook_data = [
 			'uri'     => $uri,
 			'uid'     => $uid,
 			'item_id' => null,
 		];
 
-		Hook::callAll('item_by_link', $hookData);
+		$hook_data = $eventDispatcher->dispatch(
+			new ArrayFilterEvent(ArrayFilterEvent::FETCH_ITEM_BY_LINK, $hook_data)
+		)->getArray();
 
-		if (isset($hookData['item_id'])) {
-			DI::logger()->info('Hook link fetched', ['uid' => $uid, 'uri' => $uri, 'id' => $hookData['item_id']]);
-			return is_numeric($hookData['item_id']) ? $hookData['item_id'] : 0;
+		if (isset($hook_data['item_id'])) {
+			DI::logger()->info('Hook link fetched', ['uid' => $uid, 'uri' => $uri, 'id' => $hook_data['item_id']]);
+			return is_numeric($hook_data['item_id']) ? $hook_data['item_id'] : 0;
 		}
 
 		if (!$mimetype) {
diff --git a/src/Model/Log/ParsedLogIterator.php b/src/Model/Log/ParsedLogIterator.php
index 44c98462c1..58c8bddb77 100644
--- a/src/Model/Log/ParsedLogIterator.php
+++ b/src/Model/Log/ParsedLogIterator.php
@@ -21,7 +21,7 @@ class ParsedLogIterator implements \Iterator
 	/** @var ReversedFileReader */
 	private $reader;
 
-	/** @var ParsedLogLine current iterator value*/
+	/** @var ParsedLogLine|null current iterator value*/
 	private $value = null;
 
 	/** @var int max number of lines to read */
diff --git a/src/Model/Nodeinfo.php b/src/Model/Nodeinfo.php
index c4ee580e02..0d3a88ad8a 100644
--- a/src/Model/Nodeinfo.php
+++ b/src/Model/Nodeinfo.php
@@ -7,11 +7,9 @@
 
 namespace Friendica\Model;
 
-use Friendica\Core\Addon;
 use Friendica\Core\Config\Capability\IManageConfigValues;
 use Friendica\Database\DBA;
 use Friendica\DI;
-use Friendica\Model\Item;
 use stdClass;
 
 /**
@@ -26,13 +24,14 @@ class Nodeinfo
 	 */
 	public static function update()
 	{
-		$config = DI::config();
-		$logger = DI::logger();
+		$config      = DI::config();
+		$logger      = DI::logger();
+		$addonHelper = DI::addonHelper();
 
 		// If the addon 'statistics_json' is enabled then disable it and activate nodeinfo.
-		if (Addon::isEnabled('statistics_json')) {
+		if ($addonHelper->isAddonEnabled('statistics_json')) {
 			$config->set('system', 'nodeinfo', true);
-			Addon::uninstall('statistics_json');
+			$addonHelper->uninstallAddon('statistics_json');
 		}
 
 		if (empty($config->get('system', 'nodeinfo'))) {
@@ -50,12 +49,12 @@ class Nodeinfo
 
 		$logger->info('user statistics - done', $userStats);
 
-		$posts = DBA::count('post-thread', ["`uri-id` IN (SELECT `uri-id` FROM `post-user` WHERE NOT `deleted` AND `origin`)"]);
+		$posts    = DBA::count('post-thread', ["`uri-id` IN (SELECT `uri-id` FROM `post-user` WHERE NOT `deleted` AND `origin`)"]);
 		$comments = DBA::count('post', ["NOT `deleted` AND `gravity` = ? AND `uri-id` IN (SELECT `uri-id` FROM `post-user` WHERE `origin`)", Item::GRAVITY_COMMENT]);
 		DI::keyValue()->set('nodeinfo_local_posts', $posts);
 		DI::keyValue()->set('nodeinfo_local_comments', $comments);
 
-		$posts = DBA::count('post', ['deleted' => false, 'gravity' => Item::GRAVITY_COMMENT]);
+		$posts    = DBA::count('post', ['deleted' => false, 'gravity' => Item::GRAVITY_COMMENT]);
 		$comments = DBA::count('post', ['deleted' => false, 'gravity' => Item::GRAVITY_COMMENT]);
 		DI::keyValue()->set('nodeinfo_total_posts', $posts);
 		DI::keyValue()->set('nodeinfo_total_comments', $comments);
@@ -66,21 +65,21 @@ class Nodeinfo
 	/**
 	 * Return the supported services
 	 *
-	 * @return Object with supported services
+	 * @return stdClass with supported services
 	 */
-	public static function getUsage(bool $version2 = false)
+	public static function getUsage(bool $version2 = false): stdClass
 	{
 		$config = DI::config();
 
-		$usage = new stdClass();
-		$usage->users = new \stdClass;
+		$usage        = new stdClass();
+		$usage->users = new stdClass();
 
 		if (!empty($config->get('system', 'nodeinfo'))) {
-			$usage->users->total = intval(DI::keyValue()->get('nodeinfo_total_users'));
+			$usage->users->total          = intval(DI::keyValue()->get('nodeinfo_total_users'));
 			$usage->users->activeHalfyear = intval(DI::keyValue()->get('nodeinfo_active_users_halfyear'));
-			$usage->users->activeMonth = intval(DI::keyValue()->get('nodeinfo_active_users_monthly'));
-			$usage->localPosts = intval(DI::keyValue()->get('nodeinfo_local_posts'));
-			$usage->localComments = intval(DI::keyValue()->get('nodeinfo_local_comments'));
+			$usage->users->activeMonth    = intval(DI::keyValue()->get('nodeinfo_active_users_monthly'));
+			$usage->localPosts            = intval(DI::keyValue()->get('nodeinfo_local_posts'));
+			$usage->localComments         = intval(DI::keyValue()->get('nodeinfo_local_comments'));
 
 			if ($version2) {
 				$usage->users->activeWeek = intval(DI::keyValue()->get('nodeinfo_active_users_weekly'));
@@ -97,45 +96,47 @@ class Nodeinfo
 	 */
 	public static function getServices(): array
 	{
+		$addonHelper = DI::addonHelper();
+
 		$services = [
 			'inbound'  => [],
 			'outbound' => [],
 		];
 
-		if (Addon::isEnabled('bluesky')) {
-			$services['inbound'][] = 'bluesky';
+		if ($addonHelper->isAddonEnabled('bluesky')) {
+			$services['inbound'][]  = 'bluesky';
 			$services['outbound'][] = 'bluesky';
 		}
-		if (Addon::isEnabled('dwpost')) {
+		if ($addonHelper->isAddonEnabled('dwpost')) {
 			$services['outbound'][] = 'dreamwidth';
 		}
-		if (Addon::isEnabled('statusnet')) {
-			$services['inbound'][] = 'gnusocial';
+		if ($addonHelper->isAddonEnabled('statusnet')) {
+			$services['inbound'][]  = 'gnusocial';
 			$services['outbound'][] = 'gnusocial';
 		}
-		if (Addon::isEnabled('ijpost')) {
+		if ($addonHelper->isAddonEnabled('ijpost')) {
 			$services['outbound'][] = 'insanejournal';
 		}
-		if (Addon::isEnabled('libertree')) {
+		if ($addonHelper->isAddonEnabled('libertree')) {
 			$services['outbound'][] = 'libertree';
 		}
-		if (Addon::isEnabled('ljpost')) {
+		if ($addonHelper->isAddonEnabled('ljpost')) {
 			$services['outbound'][] = 'livejournal';
 		}
-		if (Addon::isEnabled('pumpio')) {
-			$services['inbound'][] = 'pumpio';
+		if ($addonHelper->isAddonEnabled('pumpio')) {
+			$services['inbound'][]  = 'pumpio';
 			$services['outbound'][] = 'pumpio';
 		}
 
 		$services['outbound'][] = 'smtp';
 
-		if (Addon::isEnabled('tumblr')) {
+		if ($addonHelper->isAddonEnabled('tumblr')) {
 			$services['outbound'][] = 'tumblr';
 		}
-		if (Addon::isEnabled('twitter')) {
+		if ($addonHelper->isAddonEnabled('twitter')) {
 			$services['outbound'][] = 'twitter';
 		}
-		if (Addon::isEnabled('wppost')) {
+		if ($addonHelper->isAddonEnabled('wppost')) {
 			$services['outbound'][] = 'wordpress';
 		}
 
diff --git a/src/Model/Photo.php b/src/Model/Photo.php
index c275753960..74edaf360d 100644
--- a/src/Model/Photo.php
+++ b/src/Model/Photo.php
@@ -244,7 +244,7 @@ class Photo
 	 *
 	 * @param array $photo Photo data. Needs at least 'id', 'type', 'backend-class', 'backend-ref'
 	 *
-	 * @return \Friendica\Object\Image|null Image object or null on error
+	 * @return string|null Image data as string or null on error
 	 */
 	public static function getImageDataForPhoto(array $photo)
 	{
@@ -254,7 +254,7 @@ class Photo
 
 		try {
 			$backendClass = DI::storageManager()->getByName($photo['backend-class'] ?? '');
-			/// @todo refactoring this returning, because the storage returns a "string" which is casted in different ways - a check "instanceof Image" will fail!
+
 			return $backendClass->get($photo['backend-ref'] ?? '');
 		} catch (InvalidClassStorageException $storageException) {
 			try {
@@ -834,10 +834,9 @@ class Photo
 	 * - Sharing a post with a group will create a photo that only the group can see.
 	 * - Sharing a photo again that been shared non public before doesn't alter the permissions.
 	 *
-	 * @return string
 	 * @throws \Exception
 	 */
-	public static function setPermissionFromBody($body, $uid, $original_contact_id, $str_contact_allow, $str_circle_allow, $str_contact_deny, $str_circle_deny)
+	public static function setPermissionFromBody($body, $uid, $original_contact_id, $str_contact_allow, $str_circle_allow, $str_contact_deny, $str_circle_deny): bool
 	{
 		// Simplify image codes
 		$img_body = preg_replace("/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/ism", '[img]$3[/img]', $body);
diff --git a/src/Model/Post/Delayed.php b/src/Model/Post/Delayed.php
index e4862c27c2..b0c5d09dcd 100644
--- a/src/Model/Post/Delayed.php
+++ b/src/Model/Post/Delayed.php
@@ -170,9 +170,8 @@ class Delayed
 	 * @param array  $attachments
 	 * @param int    $preparation_mode
 	 * @param string $uri
-	 * @return bool
 	 */
-	public static function publish(array $item, int $notify = 0, array $taglist = [], array $attachments = [], int $preparation_mode = self::PREPARED, string $uri = '')
+	public static function publish(array $item, int $notify = 0, array $taglist = [], array $attachments = [], int $preparation_mode = self::PREPARED, string $uri = ''): int
 	{
 		if (!empty($attachments)) {
 			$item['attachments'] = $attachments;
diff --git a/src/Model/Post/UserNotification.php b/src/Model/Post/UserNotification.php
index 95c578f486..d212b07b2f 100644
--- a/src/Model/Post/UserNotification.php
+++ b/src/Model/Post/UserNotification.php
@@ -9,10 +9,10 @@ namespace Friendica\Model\Post;
 
 use BadMethodCallException;
 use Exception;
-use Friendica\Core\Hook;
 use Friendica\Database\Database;
 use Friendica\Database\DBA;
 use Friendica\DI;
+use Friendica\Event\ArrayFilterEvent;
 use Friendica\Model\Contact;
 use Friendica\Model\Item;
 use Friendica\Model\Post;
@@ -396,7 +396,12 @@ class UserNotification
 		$profiles = [$owner['nurl']];
 
 		$notification_data = ['uid' => $uid, 'profiles' => []];
-		Hook::callAll('check_item_notification', $notification_data);
+
+		$eventDispatcher = DI::eventDispatcher();
+
+		$notification_data = $eventDispatcher->dispatch(
+			new ArrayFilterEvent(ArrayFilterEvent::CHECK_ITEM_NOTIFICATION, $notification_data),
+		)->getArray();
 
 		// Normalize the connector profiles
 		foreach ($notification_data['profiles'] as $profile) {
diff --git a/src/Model/Profile.php b/src/Model/Profile.php
index 8217b2a96a..731a66cb2b 100644
--- a/src/Model/Profile.php
+++ b/src/Model/Profile.php
@@ -12,13 +12,13 @@ use Friendica\AppHelper;
 use Friendica\Content\Text\BBCode;
 use Friendica\Content\Widget\ContactBlock;
 use Friendica\Core\Cache\Enum\Duration;
-use Friendica\Core\Hook;
 use Friendica\Core\Protocol;
 use Friendica\Core\Renderer;
 use Friendica\Core\Search;
 use Friendica\Core\Worker;
 use Friendica\Database\DBA;
 use Friendica\DI;
+use Friendica\Event\ArrayFilterEvent;
 use Friendica\Network\HTTPException;
 use Friendica\Protocol\Activity;
 use Friendica\Protocol\Diaspora;
@@ -258,11 +258,6 @@ class Profile
 	 * @throws \Friendica\Network\HTTPException\InternalServerErrorException
 	 * @throws \ImagickException
 	 * @note  Returns empty string if passed $profile is wrong type or not populated
-	 *
-	 * @hooks 'profile_sidebar_enter'
-	 *      array $profile - profile data
-	 * @hooks 'profile_sidebar'
-	 *      array $arr
 	 */
 	public static function getVCardHtml(array $profile, bool $block, bool $show_contacts): string
 	{
@@ -282,7 +277,17 @@ class Profile
 
 		$profile['network_link'] = '';
 
-		Hook::callAll('profile_sidebar_enter', $profile);
+		$eventDispatcher = DI::eventDispatcher();
+
+		$hook_data = [
+			'profile' => $profile,
+		];
+
+		$hook_data = $eventDispatcher->dispatch(
+			new ArrayFilterEvent(ArrayFilterEvent::PROFILE_SIDEBAR_ENTRY, $hook_data),
+		)->getArray();
+
+		$profile = $hook_data['profile'] ?? $profile;
 
 		$profile_url = $profile['url'];
 
@@ -473,9 +478,17 @@ class Profile
 			'$network_url'         => $network_url,
 		]);
 
-		$arr = ['profile' => &$profile, 'entry' => &$o];
+		$hook_data = [
+			'profile' => &$profile,
+			'entry'   => &$o,
+		];
 
-		Hook::callAll('profile_sidebar', $arr);
+		$hook_data = $eventDispatcher->dispatch(
+			new ArrayFilterEvent(ArrayFilterEvent::PROFILE_SIDEBAR, $hook_data),
+		)->getArray();
+
+		$profile = $hook_data['profile'] ?? $profile;
+		$o       = $hook_data['entry']   ?? $o;
 
 		return $o;
 	}
diff --git a/src/Model/User.php b/src/Model/User.php
index 927a1a82bd..5195fb77a3 100644
--- a/src/Model/User.php
+++ b/src/Model/User.php
@@ -13,7 +13,6 @@ use ErrorException;
 use Exception;
 use Friendica\App;
 use Friendica\Content\Pager;
-use Friendica\Core\Hook;
 use Friendica\Core\L10n;
 use Friendica\Core\Protocol;
 use Friendica\Core\Search;
@@ -21,6 +20,7 @@ use Friendica\Core\System;
 use Friendica\Core\Worker;
 use Friendica\Database\DBA;
 use Friendica\DI;
+use Friendica\Event\ArrayFilterEvent;
 use Friendica\Module;
 use Friendica\Network\HTTPClient\Client\HttpClientAccept;
 use Friendica\Network\HTTPClient\Client\HttpClientOptions;
@@ -758,12 +758,16 @@ class User
 			'user_record'   => null
 		];
 
-		/*
+		$eventDispatcher = DI::eventDispatcher();
+
+		/**
 		 * An addon indicates successful login by setting 'authenticated' to non-zero value and returning a user record
 		 * Addons should never set 'authenticated' except to indicate success - as hooks may be chained
 		 * and later addons should not interfere with an earlier one that succeeded.
 		 */
-		Hook::callAll('authenticate', $addon_auth);
+		$addon_auth = $eventDispatcher->dispatch(
+			new ArrayFilterEvent(ArrayFilterEvent::ACCOUNT_AUTHENTICATE, $addon_auth),
+		)->getArray();
 
 		if ($addon_auth['authenticated'] && $addon_auth['user_record']) {
 			return $addon_auth['user_record']['uid'];
@@ -1460,11 +1464,20 @@ class User
 			Contact::updateSelfFromUserID($uid, true);
 		}
 
-		Hook::callAll('register_account', $uid);
+		$eventDispatcher = DI::eventDispatcher();
+
+		$hook_data = [
+			'uid' => $uid,
+		];
+
+		$eventDispatcher->dispatch(
+			new ArrayFilterEvent(ArrayFilterEvent::ACCOUNT_REGISTER, $hook_data),
+		);
 
 		self::setRegisterMethodByUserCount();
 
 		$return['user'] = $user;
+
 		return $return;
 	}
 
@@ -1787,7 +1800,17 @@ class User
 			throw new \RuntimeException(DI::l10n()->t("User with delegates can't be removed, please remove delegate users first"));
 		}
 
-		Hook::callAll('remove_user', $user);
+		$eventDispatcher = DI::eventDispatcher();
+
+		$hook_data = [
+			'user' => $user,
+		];
+
+		$hook_data = $eventDispatcher->dispatch(
+			new ArrayFilterEvent(ArrayFilterEvent::ACCOUNT_REMOVE, $hook_data),
+		)->getArray();
+
+		$user = $hook_data['user'] ?? $user;
 
 		// save username (actually the nickname as it is guaranteed
 		// unique), so it cannot be re-registered in the future.
diff --git a/src/Moderation/Repository/Report.php b/src/Moderation/Repository/Report.php
index 69ca81c167..9777e6b9cf 100644
--- a/src/Moderation/Repository/Report.php
+++ b/src/Moderation/Repository/Report.php
@@ -7,12 +7,14 @@
 
 namespace Friendica\Moderation\Repository;
 
-use Friendica\BaseEntity;
 use Friendica\Database\Database;
-use Friendica\DI;
 use Friendica\Model\Post;
-use Friendica\Moderation\Factory;
-use Friendica\Moderation\Collection;
+use Friendica\Moderation\Collection\Report\Posts as PostsCollection;
+use Friendica\Moderation\Collection\Report\Rules as RulesCollection;
+use Friendica\Moderation\Entity\Report as ReportEntity;
+use Friendica\Moderation\Factory\Report as ReportFactory;
+use Friendica\Moderation\Factory\Report\Post as PostFactory;
+use Friendica\Moderation\Factory\Report\Rule as RuleFactory;
 use Friendica\Network\HTTPException\NotFoundException;
 use Friendica\Util\DateTimeFormat;
 use Psr\Log\LoggerInterface;
@@ -21,14 +23,14 @@ final class Report extends \Friendica\BaseRepository
 {
 	protected static $table_name = 'report';
 
-	/** @var Factory\Report */
+	/** @var ReportFactory */
 	protected $factory;
-	/** @var Factory\Report\Post */
+	/** @var PostFactory */
 	protected $postFactory;
-	/** @var Factory\Report\Rule */
+	/** @var RuleFactory */
 	protected $ruleFactory;
 
-	public function __construct(Database $database, LoggerInterface $logger, Factory\Report $factory, Factory\Report\Post $postFactory, Factory\Report\Rule $ruleFactory)
+	public function __construct(Database $database, LoggerInterface $logger, ReportFactory $factory, PostFactory $postFactory, RuleFactory $ruleFactory)
 	{
 		parent::__construct($database, $logger, $factory);
 
@@ -37,12 +39,12 @@ final class Report extends \Friendica\BaseRepository
 		$this->ruleFactory = $ruleFactory;
 	}
 
-	public function selectOneById(int $lastInsertId): \Friendica\Moderation\Entity\Report
+	public function selectOneById(int $lastInsertId): ReportEntity
 	{
 		return $this->_selectOne(['id' => $lastInsertId]);
 	}
 
-	public function save(\Friendica\Moderation\Entity\Report $Report): \Friendica\Moderation\Entity\Report
+	public function save(ReportEntity $Report): ReportEntity
 	{
 		$fields = [
 			'reporter-id'     => $Report->reporterCid,
@@ -73,7 +75,7 @@ final class Report extends \Friendica\BaseRepository
 				if (Post::exists(['uri-id' => $post->uriId])) {
 					$this->db->insert('report-post', ['rid' => $newReportId, 'uri-id' => $post->uriId, 'status' => $post->status]);
 				} else {
-					DI::logger()->notice('Post does not exist', ['uri-id' => $post->uriId, 'report' => $Report]);
+					$this->logger->notice('Post does not exist', ['uri-id' => $post->uriId, 'report' => $Report]);
 				}
 			}
 
@@ -87,15 +89,18 @@ final class Report extends \Friendica\BaseRepository
 		return $Report;
 	}
 
-	protected function _selectOne(array $condition, array $params = []): BaseEntity
+	/**
+	 * @throws NotFoundException
+	 */
+	protected function _selectOne(array $condition, array $params = []): ReportEntity
 	{
 		$fields = $this->db->selectFirst(self::$table_name, [], $condition, $params);
 		if (!$this->db->isResult($fields)) {
 			throw new NotFoundException();
 		}
 
-		$reportPosts = new Collection\Report\Posts(array_map([$this->postFactory, 'createFromTableRow'], $this->db->selectToArray('report-post', ['uri-id', 'status'], ['rid' => $condition['id'] ?? 0])));
-		$reportRules = new Collection\Report\Rules(array_map([$this->ruleFactory, 'createFromTableRow'], $this->db->selectToArray('report-rule', ['line-id', 'text'], ['rid' => $condition['id'] ?? 0])));
+		$reportPosts = new PostsCollection(array_map([$this->postFactory, 'createFromTableRow'], $this->db->selectToArray('report-post', ['uri-id', 'status'], ['rid' => $condition['id'] ?? 0])));
+		$reportRules = new RulesCollection(array_map([$this->ruleFactory, 'createFromTableRow'], $this->db->selectToArray('report-rule', ['line-id', 'text'], ['rid' => $condition['id'] ?? 0])));
 
 		return $this->factory->createFromTableRow($fields, $reportPosts, $reportRules);
 	}
diff --git a/src/Module/Admin/Addons/Details.php b/src/Module/Admin/Addons/Details.php
index a102f4d269..671d23c199 100644
--- a/src/Module/Admin/Addons/Details.php
+++ b/src/Module/Admin/Addons/Details.php
@@ -10,6 +10,7 @@ namespace Friendica\Module\Admin\Addons;
 use Friendica\Content\Text\Markdown;
 use Friendica\Core\Renderer;
 use Friendica\DI;
+use Friendica\Model\Contact;
 use Friendica\Module\BaseAdmin;
 use Friendica\Util\Strings;
 
@@ -92,6 +93,35 @@ class Details extends BaseAdmin
 
 		$addonInfo = $addonHelper->getAddonInfo($addon);
 
+		$addonAuthors = [];
+
+		foreach ($addonInfo->getAuthors() as $addonAuthor) {
+			$addonAuthor['link'] = 'foo@bar.com';
+			if (array_key_exists('link', $addonAuthor) && empty(parse_url($addonAuthor['link'], PHP_URL_SCHEME))) {
+				$contact = Contact::getByURL($addonAuthor['link'], false);
+
+				if (!empty($contact['url'])) {
+					$addonAuthor['link'] = $contact['url'];
+				}
+			}
+
+			$addonAuthors[] = $addonAuthor;
+		}
+
+		$addonMaintainers = [];
+
+		foreach ($addonInfo->getMaintainers() as $addonMaintainer) {
+			if (array_key_exists('link', $addonMaintainer) && empty(parse_url($addonMaintainer['link'], PHP_URL_SCHEME))) {
+				$contact = Contact::getByURL($addonMaintainer['link'], false);
+
+				if (!empty($contact['url'])) {
+					$addonMaintainer['link'] = $contact['url'];
+				}
+			}
+
+			$addonMaintainers[] = $addonMaintainer;
+		}
+
 		$t = Renderer::getMarkupTemplate('admin/addons/details.tpl');
 
 		return Renderer::replaceMacros($t, [
@@ -107,8 +137,8 @@ class Details extends BaseAdmin
 				'name'        => $addonInfo->getName(),
 				'version'     => $addonInfo->getVersion(),
 				'description' => $addonInfo->getDescription(),
-				'author'      => $addonInfo->getAuthors(),
-				'maintainer'  => $addonInfo->getMaintainers(),
+				'author'      => $addonAuthors,
+				'maintainer'  => $addonMaintainers,
 			],
 			'$str_author'     => DI::l10n()->t('Author: '),
 			'$str_maintainer' => DI::l10n()->t('Maintainer: '),
diff --git a/src/Module/Admin/Site.php b/src/Module/Admin/Site.php
index 72ea6fa5a4..584237db91 100644
--- a/src/Module/Admin/Site.php
+++ b/src/Module/Admin/Site.php
@@ -41,13 +41,13 @@ class Site extends BaseAdmin
 			return;
 		}
 
-		$sitename         = (!empty($_POST['sitename'])         ? trim($_POST['sitename'])      : '');
+		$sitename         = (!empty($_POST['sitename'])         ? strip_tags(trim($_POST['sitename'])) : '');
 		$sender_email     = (!empty($_POST['sender_email'])     ? trim($_POST['sender_email'])  : '');
 		$banner           = (!empty($_POST['banner'])           ? trim($_POST['banner'])                             : false);
 		$email_banner     = (!empty($_POST['email_banner'])     ? trim($_POST['email_banner'])                       : false);
 		$shortcut_icon    = (!empty($_POST['shortcut_icon'])    ? trim($_POST['shortcut_icon']) : '');
 		$touch_icon       = (!empty($_POST['touch_icon'])       ? trim($_POST['touch_icon'])    : '');
-		$additional_info  = (!empty($_POST['additional_info'])  ? trim($_POST['additional_info'])                    : '');
+		$additional_info  = (!empty($_POST['additional_info'])  ? strip_tags(trim($_POST['additional_info']))        : '');
 		$language         = (!empty($_POST['language'])         ? trim($_POST['language'])      : '');
 		$theme            = (!empty($_POST['theme'])            ? trim($_POST['theme'])         : '');
 		$theme_mobile     = (!empty($_POST['theme_mobile'])     ? trim($_POST['theme_mobile'])  : '');
@@ -57,7 +57,7 @@ class Site extends BaseAdmin
 		$jpegimagequality = (!empty($_POST['jpegimagequality']) ? intval(trim($_POST['jpegimagequality']))           : 100);
 
 		$register_policy      = (!empty($_POST['register_policy'])         ? intval(trim($_POST['register_policy']))             : 0);
-		$max_registered_users = (!empty($_POST['max_registered_users'])     ? intval(trim($_POST['max_registered_users']))         : 0);
+		$max_registered_users = (!empty($_POST['max_registered_users'])    ? intval(trim($_POST['max_registered_users']))         : 0);
 		$daily_registrations  = (!empty($_POST['max_daily_registrations']) ? intval(trim($_POST['max_daily_registrations']))     : 0);
 		$abandon_days         = (!empty($_POST['abandon_days'])            ? intval(trim($_POST['abandon_days']))                : 0);
 
diff --git a/src/Module/Api/Mastodon/Lists/Accounts.php b/src/Module/Api/Mastodon/Lists/Accounts.php
index 796ced67b8..65a76c78ea 100644
--- a/src/Module/Api/Mastodon/Lists/Accounts.php
+++ b/src/Module/Api/Mastodon/Lists/Accounts.php
@@ -31,7 +31,7 @@ class Accounts extends BaseApi
 			$this->logAndJsonError(422, $this->errorFactory->UnprocessableEntity());
 		}
 
-		return Circle::removeMembers($this->parameters['id'], $request['account_ids']);
+		Circle::removeMembers($this->parameters['id'], $request['account_ids']);
 	}
 
 	protected function post(array $request = [])
diff --git a/src/Module/Api/Mastodon/Media.php b/src/Module/Api/Mastodon/Media.php
index 5dbcfc3687..31ab33c527 100644
--- a/src/Module/Api/Mastodon/Media.php
+++ b/src/Module/Api/Mastodon/Media.php
@@ -13,6 +13,7 @@ use Friendica\Model\Contact;
 use Friendica\Model\Photo;
 use Friendica\Model\Post;
 use Friendica\Module\BaseApi;
+use Friendica\Network\HTTPException\InternalServerErrorException;
 use Friendica\Util\Strings;
 
 /**
@@ -43,12 +44,13 @@ class Media extends BaseApi
 
 		if (in_array($type, [Post\Media::IMAGE, Post\Media::UNKNOWN, Post\Media::APPLICATION])) {
 			$media = Photo::upload($uid, $request['file'], '', null, null, '', '', $request['description']);
-			if (!empty($media)) {
-				$this->logger->info('Uploaded photo', ['media' => $media]);
-				$this->jsonExit(DI::mstdnAttachment()->createFromPhoto($media['id']));
-			} elseif ($type == Post\Media::IMAGE) {
-				$this->jsonExit(DI::mstdnAttachment()->createFromPhoto($media['id']));
+
+			if (empty($media)) {
+				$this->logAndJsonError(422, $this->errorFactory->UnprocessableEntity('Error while uploading media.'));
 			}
+
+			$this->logger->info('Uploaded photo', ['media' => $media]);
+			$this->jsonExit(DI::mstdnAttachment()->createFromPhoto($media['id']));
 		}
 
 		$tempFileName = $request['file']['tmp_name'];
@@ -97,14 +99,24 @@ class Media extends BaseApi
 		$photo = Photo::selectFirst(['resource-id'], ['id' => $this->parameters['id'], 'uid' => $uid]);
 		if (empty($photo['resource-id'])) {
 			$media = Post\Media::getById($this->parameters['id']);
+
 			if (empty($media['uri-id'])) {
 				$this->logAndJsonError(404, $this->errorFactory->RecordNotFound());
 			}
+
 			if (!Post::exists(['uri-id' => $media['uri-id'], 'uid' => $uid, 'origin' => true])) {
 				$this->logAndJsonError(404, $this->errorFactory->RecordNotFound());
 			}
+
 			Post\Media::updateById(['description' => $request['description']], $this->parameters['id']);
-			$this->jsonExit(DI::mstdnAttachment()->createFromId($this->parameters['id']));
+
+			try {
+				$attachment = DI::mstdnAttachment()->createFromId($this->parameters['id'] . '1');
+			} catch (InternalServerErrorException $th) {
+				$this->logAndJsonError(404, $this->errorFactory->RecordNotFound());
+			}
+
+			$this->jsonExit($attachment);
 		}
 
 		Photo::update(['desc' => $request['description']], ['resource-id' => $photo['resource-id']]);
diff --git a/src/Module/Api/Mastodon/Search.php b/src/Module/Api/Mastodon/Search.php
index 668bb4d8d0..f46ea1e981 100644
--- a/src/Module/Api/Mastodon/Search.php
+++ b/src/Module/Api/Mastodon/Search.php
@@ -219,9 +219,10 @@ class Search extends BaseApi
 
 		$condition = ["`id` IN (SELECT `tid` FROM `post-tag` WHERE `type` = ?) AND `name` LIKE ?", Tag::HASHTAG, $q . '%'];
 
-		$tags = DBA::select('tag', ['name'], $condition, $params);
+		$tags = DBA::selectToArray('tag', ['name'], $condition, $params);
 
 		$hashtags = [];
+
 		foreach ($tags as $tag) {
 			if ($version == 1) {
 				$hashtags[] = $tag['name'];
diff --git a/src/Module/Api/Twitter/Lists/Ownership.php b/src/Module/Api/Twitter/Lists/Ownership.php
index bdf34ed280..bea9367a6d 100644
--- a/src/Module/Api/Twitter/Lists/Ownership.php
+++ b/src/Module/Api/Twitter/Lists/Ownership.php
@@ -44,10 +44,11 @@ class Ownership extends BaseApi
 		$this->checkAllowedScope(BaseApi::SCOPE_READ);
 		$uid = BaseApi::getCurrentUserID();
 
-		$circles = $this->dba->select('group', [], ['deleted' => false, 'uid' => $uid, 'cid' => null]);
+		$circles = $this->dba->selectToArray('group', [], ['deleted' => false, 'uid' => $uid, 'cid' => null]);
 
 		// loop through all circles
 		$lists = [];
+
 		foreach ($circles as $circle) {
 			$lists[] = $this->friendicaCircle->createFromId($circle['id']);
 		}
diff --git a/src/Module/BaseProfile.php b/src/Module/BaseProfile.php
index 4a73408bc9..ce5d276861 100644
--- a/src/Module/BaseProfile.php
+++ b/src/Module/BaseProfile.php
@@ -9,9 +9,9 @@ namespace Friendica\Module;
 
 use Friendica\BaseModule;
 use Friendica\Content\Feature;
-use Friendica\Core\Hook;
 use Friendica\Core\Renderer;
 use Friendica\DI;
+use Friendica\Event\ArrayFilterEvent;
 use Friendica\Model\User;
 
 class BaseProfile extends BaseModule
@@ -128,12 +128,16 @@ class BaseProfile extends BaseModule
 			];
 		}
 
-		$arr = ['is_owner' => $is_owner, 'nickname' => $nickname, 'tab' => $current, 'tabs' => $tabs];
+		$hook_data = ['is_owner' => $is_owner, 'nickname' => $nickname, 'tab' => $current, 'tabs' => $tabs];
 
-		Hook::callAll('profile_tabs', $arr);
+		$eventDispatcher = DI::eventDispatcher();
+
+		$hook_data = $eventDispatcher->dispatch(
+			new ArrayFilterEvent(ArrayFilterEvent::PROFILE_TABS, $hook_data),
+		)->getArray();
 
 		$tpl = Renderer::getMarkupTemplate('common_tabs.tpl');
 
-		return Renderer::replaceMacros($tpl, ['$tabs' => $arr['tabs'], '$more' => DI::l10n()->t('More')]);
+		return Renderer::replaceMacros($tpl, ['$tabs' => $hook_data['tabs'], '$more' => DI::l10n()->t('More')]);
 	}
 }
diff --git a/src/Module/Contact/Profile.php b/src/Module/Contact/Profile.php
index a176a20684..dd7288e58c 100644
--- a/src/Module/Contact/Profile.php
+++ b/src/Module/Contact/Profile.php
@@ -7,31 +7,37 @@
 
 namespace Friendica\Module\Contact;
 
-use Friendica\App;
+use Friendica\App\Arguments;
+use Friendica\App\BaseURL;
+use Friendica\App\Page;
 use Friendica\BaseModule;
-use Friendica\Contact\LocalRelationship;
 use Friendica\Contact\LocalRelationship\Entity\LocalRelationship as LocalRelationshipEntity;
+use Friendica\Contact\LocalRelationship\Repository\LocalRelationship as LocalRelationshipRepository;
 use Friendica\Content\ContactSelector;
 use Friendica\Content\Nav;
 use Friendica\Content\Text\BBCode;
 use Friendica\Content\Widget;
 use Friendica\Core\Config\Capability\IManageConfigValues;
-use Friendica\Core\Hook;
 use Friendica\Core\L10n;
 use Friendica\Core\Protocol;
 use Friendica\Core\Renderer;
 use Friendica\Core\Session\Capability\IHandleUserSessions;
 use Friendica\Database\Database;
 use Friendica\Database\DBA;
+use Friendica\Event\ArrayFilterEvent;
 use Friendica\Model\Circle;
-use Friendica\Model\Contact;
-use Friendica\Module;
+use Friendica\Model\Contact as ContactModel;
+use Friendica\Model\Contact\User as UserContact;
+use Friendica\Module\Contact as ContactModule;
 use Friendica\Module\Response;
+use Friendica\Module\Security\Login;
 use Friendica\Navigation\SystemMessages;
-use Friendica\Network\HTTPException;
-use Friendica\User\Settings;
+use Friendica\Network\HTTPException\InternalServerErrorException;
+use Friendica\Network\HTTPException\NotFoundException;
+use Friendica\User\Settings\Repository\UserGServer as UserGServerRepository;
 use Friendica\Util\DateTimeFormat;
 use Friendica\Util\Profiler;
+use Psr\EventDispatcher\EventDispatcherInterface;
 use Psr\Log\LoggerInterface;
 
 /**
@@ -39,9 +45,9 @@ use Psr\Log\LoggerInterface;
  */
 class Profile extends BaseModule
 {
-	/** @var LocalRelationship\Repository\LocalRelationship */
+	/** @var LocalRelationshipRepository */
 	private $localRelationship;
-	/** @var App\Page */
+	/** @var Page */
 	private $page;
 	/** @var IManageConfigValues */
 	private $config;
@@ -51,11 +57,28 @@ class Profile extends BaseModule
 	private $systemMessages;
 	/** @var Database */
 	private $db;
-	/** @var Settings\Repository\UserGServer */
+	/** @var UserGServerRepository */
 	private $userGServer;
+	private EventDispatcherInterface $eventDispatcher;
 
-	public function __construct(Settings\Repository\UserGServer $userGServer, Database $db, SystemMessages $systemMessages, IHandleUserSessions $session, L10n $l10n, LocalRelationship\Repository\LocalRelationship $localRelationship, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, App\Page $page, IManageConfigValues $config, array $server, array $parameters = [])
-	{
+	public function __construct(
+		UserGServerRepository $userGServer,
+		EventDispatcherInterface $eventDispatcher,
+		Database $db,
+		SystemMessages $systemMessages,
+		IHandleUserSessions $session,
+		L10n $l10n,
+		LocalRelationshipRepository $localRelationship,
+		BaseURL $baseUrl,
+		Arguments $args,
+		LoggerInterface $logger,
+		Profiler $profiler,
+		Response $response,
+		Page $page,
+		IManageConfigValues $config,
+		array $server,
+		array $parameters = []
+	) {
 		parent::__construct($l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters);
 
 		$this->localRelationship = $localRelationship;
@@ -65,6 +88,7 @@ class Profile extends BaseModule
 		$this->systemMessages    = $systemMessages;
 		$this->db                = $db;
 		$this->userGServer       = $userGServer;
+		$this->eventDispatcher   = $eventDispatcher;
 	}
 
 	protected function post(array $request = [])
@@ -77,12 +101,14 @@ class Profile extends BaseModule
 
 		// Backward compatibility: The update still needs a user-specific contact ID
 		// Change to user-contact table check by version 2022.03
-		$ucid = Contact::getUserContactId($contact_id, $this->session->getLocalUserId());
+		$ucid = ContactModel::getUserContactId($contact_id, $this->session->getLocalUserId());
 		if (!$ucid || !$this->db->exists('contact', ['id' => $ucid, 'deleted' => false])) {
 			return;
 		}
 
-		Hook::callAll('contact_edit_post', $request);
+		$request = $this->eventDispatcher->dispatch(
+			new ArrayFilterEvent(ArrayFilterEvent::EDIT_CONTACT_POST, $request),
+		)->getArray();
 
 		$fields = [];
 
@@ -120,14 +146,14 @@ class Profile extends BaseModule
 		}
 
 		if (isset($request['channel_frequency'])) {
-			Contact\User::setChannelFrequency($ucid, $this->session->getLocalUserId(), $request['channel_frequency']);
+			UserContact::setChannelFrequency($ucid, $this->session->getLocalUserId(), $request['channel_frequency']);
 		}
 
 		if (isset($request['channel_only'])) {
-			Contact\User::setChannelOnly($ucid, $this->session->getLocalUserId(), $request['channel_only']);
+			UserContact::setChannelOnly($ucid, $this->session->getLocalUserId(), $request['channel_only']);
 		}
 
-		if (!Contact::update($fields, ['id' => $ucid, 'uid' => $this->session->getLocalUserId()])) {
+		if (!ContactModel::update($fields, ['id' => $ucid, 'uid' => $this->session->getLocalUserId()])) {
 			$this->systemMessages->addNotice($this->t('Failed to update contact record.'));
 		}
 		$this->baseUrl->redirect('contact/' . $contact_id);
@@ -136,43 +162,43 @@ class Profile extends BaseModule
 	protected function content(array $request = []): string
 	{
 		if (!$this->session->getLocalUserId()) {
-			return Module\Security\Login::form($_SERVER['REQUEST_URI']);
+			return Login::form($_SERVER['REQUEST_URI']);
 		}
 
 		// Backward compatibility: Ensure to use the public contact when the user contact is provided
 		// Remove by version 2022.03
-		$data = Contact::getPublicAndUserContactID(intval($this->parameters['id']), $this->session->getLocalUserId());
+		$data = ContactModel::getPublicAndUserContactID(intval($this->parameters['id']), $this->session->getLocalUserId());
 		if (empty($data)) {
-			throw new HTTPException\NotFoundException($this->t('Contact not found.'));
+			throw new NotFoundException($this->t('Contact not found.'));
 		}
 
-		$contact = Contact::getById($data['public']);
+		$contact = ContactModel::getById($data['public']);
 		if (!$this->db->isResult($contact)) {
-			throw new HTTPException\NotFoundException($this->t('Contact not found.'));
+			throw new NotFoundException($this->t('Contact not found.'));
 		}
 
 		// Fetch the protocol from the user's contact.
 		if ($data['user']) {
-			$usercontact = Contact::getById($data['user'], ['network', 'protocol']);
+			$usercontact = ContactModel::getById($data['user'], ['network', 'protocol']);
 			if ($this->db->isResult($usercontact)) {
 				$contact['network']  = $usercontact['network'];
 				$contact['protocol'] = $usercontact['protocol'];
 			}
 		}
 
-		if (empty($contact['network']) && Contact::isLocal($contact['url']) ) {
+		if (empty($contact['network']) && ContactModel::isLocal($contact['url']) ) {
 			$contact['network']  = Protocol::DFRN;
 			$contact['protocol'] = Protocol::ACTIVITYPUB;
 		}
 
 		// Don't display contacts that are about to be deleted
 		if ($contact['deleted'] || $contact['network'] == Protocol::PHANTOM) {
-			throw new HTTPException\NotFoundException($this->t('Contact not found.'));
+			throw new NotFoundException($this->t('Contact not found.'));
 		}
 
 		$localRelationship = $this->localRelationship->getForUserContact($this->session->getLocalUserId(), $contact['id']);
 
-		if ($localRelationship->rel === Contact::SELF) {
+		if ($localRelationship->rel === ContactModel::SELF) {
 			$this->baseUrl->redirect('profile/' . $contact['nick'] . '/profile');
 		}
 
@@ -180,8 +206,8 @@ class Profile extends BaseModule
 			self::checkFormSecurityTokenRedirectOnError('contact/' . $contact['id'], 'contact_action', 't');
 
 			$cmd = $this->parameters['action'];
-			if ($cmd === 'update' && $localRelationship->rel !== Contact::NOTHING) {
-				Module\Contact::updateContactFromPoll($contact['id']);
+			if ($cmd === 'update' && $localRelationship->rel !== ContactModel::NOTHING) {
+				ContactModule::updateContactFromPoll($contact['id']);
 			}
 
 			if ($cmd === 'updateprofile') {
@@ -191,12 +217,12 @@ class Profile extends BaseModule
 			if ($cmd === 'block') {
 				if ($localRelationship->blocked) {
 					// @TODO Backward compatibility, replace with $localRelationship->unblock()
-					Contact\User::setBlocked($contact['id'], $this->session->getLocalUserId(), false);
+					UserContact::setBlocked($contact['id'], $this->session->getLocalUserId(), false);
 
 					$message = $this->t('Contact has been unblocked');
 				} else {
 					// @TODO Backward compatibility, replace with $localRelationship->block()
-					Contact\User::setBlocked($contact['id'], $this->session->getLocalUserId(), true);
+					UserContact::setBlocked($contact['id'], $this->session->getLocalUserId(), true);
 					$message = $this->t('Contact has been blocked');
 				}
 
@@ -207,12 +233,12 @@ class Profile extends BaseModule
 			if ($cmd === 'ignore') {
 				if ($localRelationship->ignored) {
 					// @TODO Backward compatibility, replace with $localRelationship->unblock()
-					Contact\User::setIgnored($contact['id'], $this->session->getLocalUserId(), false);
+					UserContact::setIgnored($contact['id'], $this->session->getLocalUserId(), false);
 
 					$message = $this->t('Contact has been unignored');
 				} else {
 					// @TODO Backward compatibility, replace with $localRelationship->block()
-					Contact\User::setIgnored($contact['id'], $this->session->getLocalUserId(), true);
+					UserContact::setIgnored($contact['id'], $this->session->getLocalUserId(), true);
 					$message = $this->t('Contact has been ignored');
 				}
 
@@ -223,12 +249,12 @@ class Profile extends BaseModule
 			if ($cmd === 'collapse') {
 				if ($localRelationship->collapsed) {
 					// @TODO Backward compatibility, replace with $localRelationship->unblock()
-					Contact\User::setCollapsed($contact['id'], $this->session->getLocalUserId(), false);
+					UserContact::setCollapsed($contact['id'], $this->session->getLocalUserId(), false);
 
 					$message = $this->t('Contact has been uncollapsed');
 				} else {
 					// @TODO Backward compatibility, replace with $localRelationship->block()
-					Contact\User::setCollapsed($contact['id'], $this->session->getLocalUserId(), true);
+					UserContact::setCollapsed($contact['id'], $this->session->getLocalUserId(), true);
 					$message = $this->t('Contact has been collapsed');
 				}
 
@@ -239,10 +265,10 @@ class Profile extends BaseModule
 			$this->baseUrl->redirect('contact/' . $contact['id']);
 		}
 
-		$vcard_widget  = Widget\VCard::getHTML($contact);
+		$vcard_widget   = Widget\VCard::getHTML($contact);
 		$circles_widget = '';
 
-		if (!in_array($localRelationship->rel, [Contact::NOTHING, Contact::SELF])) {
+		if (!in_array($localRelationship->rel, [ContactModel::NOTHING, ContactModel::SELF])) {
 			$circles_widget = Circle::sidebarWidget('contact', 'circle', 'full', 'everyone', $data['user']);
 		}
 
@@ -257,9 +283,15 @@ class Profile extends BaseModule
 		]);
 
 		switch ($localRelationship->rel) {
-			case Contact::FRIEND:   $relation_text = $this->t('You are mutual friends with %s', $contact['name']); break;
-			case Contact::FOLLOWER:	$relation_text = $this->t('You are sharing with %s', $contact['name']); break;
-			case Contact::SHARING:  $relation_text = $this->t('%s is sharing with you', $contact['name']); break;
+			case ContactModel::FRIEND:
+				$relation_text = $this->t('You are mutual friends with %s', $contact['name']);
+				break;
+			case ContactModel::FOLLOWER:
+				$relation_text = $this->t('You are sharing with %s', $contact['name']);
+				break;
+			case ContactModel::SHARING:
+				$relation_text = $this->t('%s is sharing with you', $contact['name']);
+				break;
 			default:
 				$relation_text = '';
 		}
@@ -268,7 +300,7 @@ class Profile extends BaseModule
 			$relation_text = '';
 		}
 
-		$url = Contact::magicLinkByContact($contact);
+		$url = ContactModel::magicLinkByContact($contact);
 		if (strpos($url, 'contact/redir/') === 0) {
 			$sparkle = ' class="sparkle" ';
 		} else {
@@ -282,8 +314,7 @@ class Profile extends BaseModule
 			$this->logger->notice('Empty gsid for contact', ['contact' => $contact]);
 		}
 
-		$serverIgnored =
-			$contact['gsid'] &&
+		$serverIgnored = $contact['gsid'] &&
 			$this->userGServer->isIgnoredByUser($this->session->getLocalUserId(), $contact['gsid']) ?
 				$this->t('This contact is on a server you ignored.')
 				: '';
@@ -300,7 +331,7 @@ class Profile extends BaseModule
 		$nettype = $this->t('Network type: %s', ContactSelector::networkToName($contact['network'], $contact['protocol'], $contact['gsid']));
 
 		// tabs
-		$tab_str = Module\Contact::getTabsHTML($contact, Module\Contact::TAB_PROFILE);
+		$tab_str = ContactModule::getTabsHTML($contact, ContactModule::TAB_PROFILE);
 
 		$lost_contact = (($contact['archive'] && $contact['term-date'] > DBA::NULL_DATETIME && $contact['term-date'] < DateTimeFormat::utcNow()) ? $this->t('Communications lost with this contact!') : '');
 
@@ -312,10 +343,10 @@ class Profile extends BaseModule
 				$localRelationship->fetchFurtherInformation,
 				$this->t('Fetch information like preview pictures, title and teaser from the feed item. You can activate this if the feed doesn\'t contain much text. Keywords are taken from the meta header in the feed item and are posted as hash tags.'),
 				[
-					LocalRelationship\Entity\LocalRelationship::FFI_NONE        => $this->t('Disabled'),
-					LocalRelationship\Entity\LocalRelationship::FFI_INFORMATION => $this->t('Fetch information'),
-					LocalRelationship\Entity\LocalRelationship::FFI_KEYWORD     => $this->t('Fetch keywords'),
-					LocalRelationship\Entity\LocalRelationship::FFI_BOTH        => $this->t('Fetch information and keywords')
+					LocalRelationshipEntity::FFI_NONE        => $this->t('Disabled'),
+					LocalRelationshipEntity::FFI_INFORMATION => $this->t('Fetch information'),
+					LocalRelationshipEntity::FFI_KEYWORD     => $this->t('Fetch keywords'),
+					LocalRelationshipEntity::FFI_BOTH        => $this->t('Fetch information and keywords')
 				]
 			];
 		}
@@ -346,8 +377,8 @@ class Profile extends BaseModule
 			];
 		}
 
-		$channel_frequency = Contact\User::getChannelFrequency($contact['id'], $this->session->getLocalUserId());
-		$channel_only      = Contact\User::getChannelOnly($contact['id'], $this->session->getLocalUserId());
+		$channel_frequency = UserContact::getChannelFrequency($contact['id'], $this->session->getLocalUserId());
+		$channel_only      = UserContact::getChannelOnly($contact['id'], $this->session->getLocalUserId());
 
 		$poll_interval = null;
 		if ((($contact['network'] == Protocol::FEED) && !$this->config->get('system', 'adjust_poll_frequency')) || ($contact['network'] == Protocol::MAIL)) {
@@ -356,12 +387,12 @@ class Profile extends BaseModule
 
 		$contact_actions = $this->getContactActions($contact, $localRelationship);
 
-		if (Contact\User::isIsBlocked($contact['id'], $this->session->getLocalUserId())) {
+		if (UserContact::isIsBlocked($contact['id'], $this->session->getLocalUserId())) {
 			$relation_text = $this->t('%s has blocked you', $contact['name'] ?: $contact['nick']);
 			unset($contact_actions['follow']);
 		}
 
-		if ($localRelationship->rel !== Contact::NOTHING) {
+		if ($localRelationship->rel !== ContactModel::NOTHING) {
 			$lbl_info1              = $this->t('Contact Information / Notes');
 			$contact_settings_label = $this->t('Contact Settings');
 		} else {
@@ -407,13 +438,13 @@ class Profile extends BaseModule
 			'$notify_new_posts'          => ['notify_new_posts', $this->t('Notification for new posts'), ($localRelationship->notifyNewPosts), $this->t('Send a notification of every new post of this contact')],
 			'$fetch_further_information' => $fetch_further_information,
 			'$ffi_keyword_denylist'      => ['ffi_keyword_denylist', $this->t('Keyword Deny List'), $localRelationship->ffiKeywordDenylist, $this->t('Comma separated list of keywords that should not be converted to hashtags, when "Fetch information and keywords" is selected')],
-			'$photo'                     => Contact::getPhoto($contact),
+			'$photo'                     => ContactModel::getPhoto($contact),
 			'$name'                      => $contact['name'],
 			'$sparkle'                   => $sparkle,
 			'$url'                       => $url,
 			'$profileurllabel'           => $this->t('Profile URL'),
 			'$profileurl'                => $contact['url'],
-			'$account_type'              => Contact::getAccountType($contact['contact-type']),
+			'$account_type'              => ContactModel::getAccountType($contact['contact-type']),
 			'$location'                  => BBCode::convertForUriId($contact['uri-id'] ?? 0, $contact['location']),
 			'$location_label'            => $this->t('Location:'),
 			'$xmpp'                      => BBCode::convertForUriId($contact['uri-id'] ?? 0, $contact['xmpp']),
@@ -440,18 +471,23 @@ class Profile extends BaseModule
 			'$channel_settings_label' => $this->t('Channel Settings'),
 			'$frequency_label'        => $this->t('Frequency of this contact in relevant channels'),
 			'$frequency_description'  => $this->t("Depending on the type of the channel not all posts from this contact are displayed. By default, posts need to have a minimum amount of interactions (comments, likes) to show in your channels. On the other hand there can be contacts who flood the channel, so you might want to see only some of their posts. Or you don't want to see their content at all, but you don't want to block or hide the contact completely."),
-			'$frequency_default'      => ['channel_frequency', $this->t('Default frequency'), Contact\User::FREQUENCY_DEFAULT, $this->t('Posts by this contact are displayed in the "for you" channel if you interact often with this contact or if a post reached some level of interaction.'), $channel_frequency == Contact\User::FREQUENCY_DEFAULT],
-			'$frequency_always'       => ['channel_frequency', $this->t('Display all posts of this contact'), Contact\User::FREQUENCY_ALWAYS, $this->t('All posts from this contact will appear on the "for you" channel'), $channel_frequency == Contact\User::FREQUENCY_ALWAYS],
-			'$frequency_reduced'      => ['channel_frequency', $this->t('Display only few posts'), Contact\User::FREQUENCY_REDUCED, $this->t('When a contact creates a lot of posts in a short period, this setting reduces the number of displayed posts in every channel.'), $channel_frequency == Contact\User::FREQUENCY_REDUCED],
-			'$frequency_never'        => ['channel_frequency', $this->t('Never display posts'), Contact\User::FREQUENCY_NEVER, $this->t('Posts from this contact will never be displayed in any channel'), $channel_frequency == Contact\User::FREQUENCY_NEVER],
+			'$frequency_default'      => ['channel_frequency', $this->t('Default frequency'), UserContact::FREQUENCY_DEFAULT, $this->t('Posts by this contact are displayed in the "for you" channel if you interact often with this contact or if a post reached some level of interaction.'), $channel_frequency == UserContact::FREQUENCY_DEFAULT],
+			'$frequency_always'       => ['channel_frequency', $this->t('Display all posts of this contact'), UserContact::FREQUENCY_ALWAYS, $this->t('All posts from this contact will appear on the "for you" channel'), $channel_frequency == UserContact::FREQUENCY_ALWAYS],
+			'$frequency_reduced'      => ['channel_frequency', $this->t('Display only few posts'), UserContact::FREQUENCY_REDUCED, $this->t('When a contact creates a lot of posts in a short period, this setting reduces the number of displayed posts in every channel.'), $channel_frequency == UserContact::FREQUENCY_REDUCED],
+			'$frequency_never'        => ['channel_frequency', $this->t('Never display posts'), UserContact::FREQUENCY_NEVER, $this->t('Posts from this contact will never be displayed in any channel'), $channel_frequency == UserContact::FREQUENCY_NEVER],
 			'$channel_only'           => ['channel_only', $this->t('Channel Only'), $channel_only, $this->t('If enabled, posts from this contact will only appear in channels and network streams in circles, but not in the general network stream.')],
 		]);
 
-		$arr = ['contact' => $contact, 'output' => $o];
+		$hook_data = [
+			'contact' => $contact,
+			'output'  => $o,
+		];
 
-		Hook::callAll('contact_edit', $arr);
+		$hook_data = $this->eventDispatcher->dispatch(
+			new ArrayFilterEvent(ArrayFilterEvent::EDIT_CONTACT_FORM, $hook_data),
+		)->getArray();
 
-		return $arr['output'];
+		return $hook_data['output'] ?? $o;
 	}
 
 	/**
@@ -460,18 +496,18 @@ class Profile extends BaseModule
 	 * This includes actions like e.g. 'block', 'hide', 'delete' and others
 	 *
 	 * @param array                    $contact           Public contact row
-	 * @param LocalRelationship\Entity\LocalRelationship $localRelationship
+	 *
 	 * @return array with contact related actions
-	 * @throws HTTPException\InternalServerErrorException
+	 * @throws InternalServerErrorException
 	 */
-	private function getContactActions(array $contact, LocalRelationship\Entity\LocalRelationship $localRelationship): array
+	private function getContactActions(array $contact, LocalRelationshipEntity $localRelationship): array
 	{
 		$poll_enabled    = in_array($contact['network'], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::FEED, Protocol::MAIL]);
 		$contact_actions = [];
 
 		$formSecurityToken = self::getFormSecurityToken('contact_action');
 
-		if ($localRelationship->rel & Contact::SHARING) {
+		if ($localRelationship->rel & ContactModel::SHARING) {
 			$contact_actions['unfollow'] = [
 				'label' => $this->t('Unfollow'),
 				'url'   => 'contact/unfollow?url=' . urlencode($contact['url']) . '&auto=1',
@@ -544,7 +580,7 @@ class Profile extends BaseModule
 			'id'    => 'toggle-collapse',
 		];
 
-		if (Protocol::supportsRevokeFollow($contact['network']) && in_array($localRelationship->rel, [Contact::FOLLOWER, Contact::FRIEND])) {
+		if (Protocol::supportsRevokeFollow($contact['network']) && in_array($localRelationship->rel, [ContactModel::FOLLOWER, ContactModel::FRIEND])) {
 			$contact_actions['revoke_follow'] = [
 				'label' => $this->t('Revoke Follow'),
 				'url'   => 'contact/' . $contact['id'] . '/revoke',
@@ -562,7 +598,7 @@ class Profile extends BaseModule
 	 *
 	 * @param int $contact_id Id of the contact with uid != 0
 	 * @return void
-	 * @throws HTTPException\InternalServerErrorException
+	 * @throws InternalServerErrorException
 	 * @throws \ImagickException
 	 */
 	private function updateContactFromProbe(int $contact_id)
@@ -572,6 +608,6 @@ class Profile extends BaseModule
 		}
 
 		// Update the entry in the contact table
-		Contact::updateFromProbe($contact_id);
+		ContactModel::updateFromProbe($contact_id);
 	}
 }
diff --git a/src/Module/Conversation/Network.php b/src/Module/Conversation/Network.php
index 86c2fcf736..ce4b8d9dd3 100644
--- a/src/Module/Conversation/Network.php
+++ b/src/Module/Conversation/Network.php
@@ -31,7 +31,6 @@ use Friendica\Content\Widget\TrendingTags;
 use Friendica\Core\ACL;
 use Friendica\Core\Cache\Capability\ICanCache;
 use Friendica\Core\Config\Capability\IManageConfigValues;
-use Friendica\Core\Hook;
 use Friendica\Core\L10n;
 use Friendica\Core\PConfig\Capability\IManagePersonalConfigValues;
 use Friendica\Core\Renderer;
@@ -39,6 +38,7 @@ use Friendica\Core\Session\Capability\IHandleUserSessions;
 use Friendica\Core\Worker;
 use Friendica\Database\DBA;
 use Friendica\Database\Database;
+use Friendica\Event\ArrayFilterEvent;
 use Friendica\Model\Contact;
 use Friendica\Model\Circle;
 use Friendica\Model\Post;
@@ -49,6 +49,7 @@ use Friendica\Network\HTTPException;
 use Friendica\Navigation\SystemMessages;
 use Friendica\Util\DateTimeFormat;
 use Friendica\Util\Profiler;
+use Psr\EventDispatcher\EventDispatcherInterface;
 use Psr\Log\LoggerInterface;
 
 class Network extends Timeline
@@ -59,9 +60,9 @@ class Network extends Timeline
 	protected $dateFrom;
 	/** @var string */
 	protected $dateTo;
-	/** @var int */
+	/** @var bool */
 	protected $star;
-	/** @var int */
+	/** @var bool */
 	protected $mention;
 
 	/** @var AppHelper */
@@ -90,12 +91,55 @@ class Network extends Timeline
 	protected $community;
 	/** @var NetworkFactory */
 	protected $networkFactory;
+	private EventDispatcherInterface $eventDispatcher;
 
-	public function __construct(UserDefinedChannelFactory $userDefinedChannel, NetworkFactory $network, CommunityFactory $community, ChannelFactory $channelFactory, UserDefinedChannel $channel, AppHelper $appHelper, TimelineFactory $timeline, SystemMessages $systemMessages, Mode $mode, Conversation $conversation, Page $page, IHandleUserSessions $session, Database $database, IManagePersonalConfigValues $pConfig, IManageConfigValues $config, ICanCache $cache, L10n $l10n, BaseURL $baseUrl, Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, array $server, array $parameters = [])
-	{
-		parent::__construct($channel, $mode, $session, $database, $pConfig, $config, $cache, $l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters);
+	public function __construct(
+		UserDefinedChannelFactory $userDefinedChannel,
+		NetworkFactory $network,
+		CommunityFactory $community,
+		ChannelFactory $channelFactory,
+		UserDefinedChannel $channel,
+		AppHelper $appHelper,
+		EventDispatcherInterface $eventDispatcher,
+		TimelineFactory $timeline,
+		SystemMessages $systemMessages,
+		Mode $mode,
+		Conversation $conversation,
+		Page $page,
+		IHandleUserSessions $session,
+		Database $database,
+		IManagePersonalConfigValues $pConfig,
+		IManageConfigValues $config,
+		ICanCache $cache,
+		L10n $l10n,
+		BaseURL $baseUrl,
+		Arguments $args,
+		LoggerInterface $logger,
+		Profiler $profiler,
+		Response $response,
+		array $server,
+		array $parameters = []
+	) {
+		parent::__construct(
+			$channel,
+			$mode,
+			$session,
+			$database,
+			$pConfig,
+			$config,
+			$cache,
+			$l10n,
+			$baseUrl,
+			$args,
+			$logger,
+			$profiler,
+			$response,
+			$server,
+			$parameters,
+		);
 
 		$this->appHelper          = $appHelper;
+		$this->eventDispatcher    = $eventDispatcher;
 		$this->timeline           = $timeline;
 		$this->systemMessages     = $systemMessages;
 		$this->conversation       = $conversation;
@@ -116,8 +160,13 @@ class Network extends Timeline
 
 		$module = 'network';
 
-		$arr = ['query' => $this->args->getQueryString()];
-		Hook::callAll('network_content_init', $arr);
+		$hook_data = [
+			'query' => $this->args->getQueryString(),
+		];
+
+		$this->eventDispatcher->dispatch(
+			new ArrayFilterEvent(ArrayFilterEvent::NETWORK_CONTENT_START, $hook_data)
+		);
 
 		$o = '';
 
@@ -275,19 +324,24 @@ class Network extends Timeline
 			$tabs = array_merge($tabs, $this->getTabArray($this->community->getTimelines(true), 'network', 'channel'));
 		}
 
-		$arr = ['tabs' => $tabs];
-		Hook::callAll('network_tabs', $arr);
+		$hook_data = [
+			'tabs' => $tabs,
+		];
+
+		$hook_data = $this->eventDispatcher->dispatch(
+			new ArrayFilterEvent(ArrayFilterEvent::NETWORK_CONTENT_TABS, $hook_data)
+		)->getArray();
 
 		if (!empty($network_timelines)) {
 			$tabs = [];
 
-			foreach ($arr['tabs'] as $tab) {
+			foreach ($hook_data['tabs'] as $tab) {
 				if (in_array($tab['code'], $network_timelines)) {
 					$tabs[] = $tab;
 				}
 			}
 		} else {
-			$tabs = $arr['tabs'];
+			$tabs = $hook_data['tabs'];
 		}
 
 		$tpl = Renderer::getMarkupTemplate('common_tabs.tpl');
diff --git a/src/Module/Directory.php b/src/Module/Directory.php
index ad95b49524..786c92dcd0 100644
--- a/src/Module/Directory.php
+++ b/src/Module/Directory.php
@@ -11,10 +11,10 @@ use Friendica\BaseModule;
 use Friendica\Content\Nav;
 use Friendica\Content\Pager;
 use Friendica\Content\Widget;
-use Friendica\Core\Hook;
 use Friendica\Core\Renderer;
 use Friendica\Core\Search;
 use Friendica\DI;
+use Friendica\Event\ArrayFilterEvent;
 use Friendica\Model;
 use Friendica\Model\Profile;
 use Friendica\Network\HTTPException;
@@ -39,7 +39,7 @@ class Directory extends BaseModule
 			DI::page()['aside'] .= Widget::follow();
 		}
 
-		$output = '';
+		$output  = '';
 		$entries = [];
 
 		Nav::setSelected('directory');
@@ -47,7 +47,7 @@ class Directory extends BaseModule
 		$search = trim(rawurldecode($_REQUEST['search'] ?? ''));
 
 		$gDirPath = '';
-		$dirURL = Search::getGlobalDirectory();
+		$dirURL   = Search::getGlobalDirectory();
 		if (strlen($dirURL)) {
 			$gDirPath = OpenWebAuth::getZrlUrl($dirURL, true);
 		}
@@ -161,13 +161,22 @@ class Directory extends BaseModule
 
 		];
 
-		$hook = ['contact' => $contact, 'entry' => $entry];
+		$eventDispatcher = DI::eventDispatcher();
 
-		Hook::callAll('directory_item', $hook);
+		$hook_data = [
+			'contact' => $contact,
+			'entry'   => $entry,
+		];
+
+		$hook_data = $eventDispatcher->dispatch(
+			new ArrayFilterEvent(ArrayFilterEvent::DIRECTORY_ITEM, $hook_data),
+		)->getArray();
+
+		$entry = $hook_data['entry'] ?? $entry;
 
 		unset($profile);
 		unset($location);
 
-		return $hook['entry'];
+		return $entry;
 	}
 }
diff --git a/src/Module/Filer/RemoveTag.php b/src/Module/Filer/RemoveTag.php
index 330f6953a6..eb20efe0bb 100644
--- a/src/Module/Filer/RemoveTag.php
+++ b/src/Module/Filer/RemoveTag.php
@@ -11,7 +11,6 @@ use Friendica\App;
 use Friendica\BaseModule;
 use Friendica\Core\L10n;
 use Friendica\Core\Session\Capability\IHandleUserSessions;
-use Friendica\Core\System;
 use Friendica\Database\DBA;
 use Friendica\Model\Post;
 use Friendica\Module\Response;
@@ -59,20 +58,20 @@ class RemoveTag extends BaseModule
 	}
 
 	/**
-	 * @param array       $request The $_REQUEST array
-	 * @param string|null $type    Output parameter with the computed type
-	 * @param string|null $term    Output parameter with the computed term
+	 * @param array           $request The $_REQUEST array
+	 * @param string|int|null $type    Output parameter with the computed type
+	 * @param string|null     $term    Output parameter with the computed term
 	 *
 	 * @return int The relevant HTTP code
 	 *
 	 * @throws \Exception
 	 */
-	private function removeTag(array $request, string &$type = null, string &$term = null): int
+	private function removeTag(array $request, &$type = null, string &$term = null): int
 	{
 		$item_id = $this->parameters['id'] ?? 0;
 
 		$term = trim($request['term'] ?? '');
-		$cat = trim($request['cat'] ?? '');
+		$cat  = trim($request['cat'] ?? '');
 
 		if (!empty($cat)) {
 			$type = Post\Category::CATEGORY;
diff --git a/src/Module/Friendica.php b/src/Module/Friendica.php
index 1a741b890c..64064423d7 100644
--- a/src/Module/Friendica.php
+++ b/src/Module/Friendica.php
@@ -13,16 +13,17 @@ use Friendica\App\BaseURL;
 use Friendica\BaseModule;
 use Friendica\Core\Addon\AddonHelper;
 use Friendica\Core\Config\Capability\IManageConfigValues;
-use Friendica\Core\Hook;
 use Friendica\Core\KeyValueStorage\Capability\IManageKeyValuePairs;
 use Friendica\Core\L10n;
 use Friendica\Core\Renderer;
 use Friendica\Core\Session\Capability\IHandleUserSessions;
 use Friendica\Database\PostUpdate;
+use Friendica\Event\HtmlFilterEvent;
 use Friendica\Model\User;
 use Friendica\Network\HTTPException;
 use Friendica\Protocol\ActivityPub;
 use Friendica\Util\Profiler;
+use Psr\EventDispatcher\EventDispatcherInterface;
 use Psr\Log\LoggerInterface;
 
 /**
@@ -32,6 +33,7 @@ use Psr\Log\LoggerInterface;
 class Friendica extends BaseModule
 {
 	private AddonHelper $addonHelper;
+	private EventDispatcherInterface $eventDispatcher;
 	/** @var IManageConfigValues */
 	private $config;
 	/** @var IManageKeyValuePairs */
@@ -39,14 +41,28 @@ class Friendica extends BaseModule
 	/** @var IHandleUserSessions */
 	private $session;
 
-	public function __construct(AddonHelper $addonHelper, IHandleUserSessions $session, IManageKeyValuePairs $keyValue, IManageConfigValues $config, L10n $l10n, BaseURL $baseUrl, Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, array $server, array $parameters = [])
-	{
+	public function __construct(
+		AddonHelper $addonHelper,
+		EventDispatcherInterface $eventDispatcher,
+		IHandleUserSessions $session,
+		IManageKeyValuePairs $keyValue,
+		IManageConfigValues $config,
+		L10n $l10n,
+		BaseURL $baseUrl,
+		Arguments $args,
+		LoggerInterface $logger,
+		Profiler $profiler,
+		Response $response,
+		array $server,
+		array $parameters = []
+	) {
 		parent::__construct($l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters);
 
-		$this->config      = $config;
-		$this->keyValue    = $keyValue;
-		$this->session     = $session;
-		$this->addonHelper = $addonHelper;
+		$this->config          = $config;
+		$this->keyValue        = $keyValue;
+		$this->session         = $session;
+		$this->eventDispatcher = $eventDispatcher;
+		$this->addonHelper     = $addonHelper;
 	}
 
 	protected function content(array $request = []): string
@@ -99,7 +115,9 @@ class Friendica extends BaseModule
 
 		$hooked = '';
 
-		Hook::callAll('about_hook', $hooked);
+		$hooked = $this->eventDispatcher->dispatch(
+			new HtmlFilterEvent(HtmlFilterEvent::MOD_ABOUT_CONTENT, $hooked),
+		)->getHtml();
 
 		$tpl = Renderer::getMarkupTemplate('friendica.tpl');
 
diff --git a/src/Module/Home.php b/src/Module/Home.php
index 7e7d59d760..9a6a4a4730 100644
--- a/src/Module/Home.php
+++ b/src/Module/Home.php
@@ -8,10 +8,10 @@
 namespace Friendica\Module;
 
 use Friendica\BaseModule;
-use Friendica\Core\Hook;
 use Friendica\Core\Renderer;
 use Friendica\DI;
 use Friendica\Event\Event;
+use Friendica\Event\HtmlFilterEvent;
 use Friendica\Model\User;
 use Friendica\Module\Security\Login;
 use Friendica\Protocol\ActivityPub;
@@ -66,7 +66,10 @@ class Home extends BaseModule
 		$login = Login::form(DI::args()->getQueryString(), Register::getPolicy() !== Register::CLOSED);
 
 		$content = '';
-		Hook::callAll('home_content', $content);
+
+		$content = $eventDispatcher->dispatch(
+			new HtmlFilterEvent(HtmlFilterEvent::MOD_HOME_CONTENT, $content),
+		)->getHtml();
 
 		$tpl = Renderer::getMarkupTemplate('home.tpl');
 		return Renderer::replaceMacros($tpl, [
diff --git a/src/Module/Moderation/BaseUsers.php b/src/Module/Moderation/BaseUsers.php
index fcf4b4aa16..8a12d2b0a1 100644
--- a/src/Module/Moderation/BaseUsers.php
+++ b/src/Module/Moderation/BaseUsers.php
@@ -11,11 +11,11 @@ use Friendica\App\Arguments;
 use Friendica\App\BaseURL;
 use Friendica\App\Page;
 use Friendica\AppHelper;
-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\Model\Register;
 use Friendica\Model\User;
 use Friendica\Module\BaseModeration;
@@ -24,6 +24,7 @@ use Friendica\Navigation\SystemMessages;
 use Friendica\Network\HTTPException\ServiceUnavailableException;
 use Friendica\Util\Profiler;
 use Friendica\Util\Temporal;
+use Psr\EventDispatcher\EventDispatcherInterface;
 use Psr\Log\LoggerInterface;
 
 abstract class BaseUsers extends BaseModeration
@@ -31,11 +32,28 @@ abstract class BaseUsers extends BaseModeration
 	/** @var Database */
 	protected $database;
 
-	public function __construct(Database $database, Page $page, AppHelper $appHelper, SystemMessages $systemMessages, IHandleUserSessions $session, L10n $l10n, BaseURL $baseUrl, Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, array $server, array $parameters = [])
-	{
+	private EventDispatcherInterface $eventDispatcher;
+
+	public function __construct(
+		Database $database,
+		EventDispatcherInterface $eventDispatcher,
+		Page $page,
+		AppHelper $appHelper,
+		SystemMessages $systemMessages,
+		IHandleUserSessions $session,
+		L10n $l10n,
+		BaseURL $baseUrl,
+		Arguments $args,
+		LoggerInterface $logger,
+		Profiler $profiler,
+		Response $response,
+		array $server,
+		array $parameters = []
+	) {
 		parent::__construct($page, $appHelper, $systemMessages, $session, $l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters);
 
-		$this->database = $database;
+		$this->database        = $database;
+		$this->eventDispatcher = $eventDispatcher;
 	}
 
 	/**
@@ -95,11 +113,21 @@ abstract class BaseUsers extends BaseModeration
 				'accesskey' => 'd',
 			],
 		];
-		$tabs_arr = ['tabs' => $tabs, 'selectedTab' => $selectedTab];
-		Hook::callAll('moderation_users_tabs', $tabs_arr);
+
+		$hook_data = [
+			'tabs'        => $tabs,
+			'selectedTab' => $selectedTab,
+		];
+
+		$hook_data = $this->eventDispatcher->dispatch(
+			new ArrayFilterEvent(ArrayFilterEvent::MODERATION_USERS_TABS, $hook_data),
+		)->getArray();
+
+		$tabs = $hook_data['tabs'] ?? $tabs;
 
 		$tpl = Renderer::getMarkupTemplate('common_tabs.tpl');
-		return Renderer::replaceMacros($tpl, ['$tabs' => $tabs_arr['tabs'], '$more' => $this->t('More')]);
+
+		return Renderer::replaceMacros($tpl, ['$tabs' => $tabs, '$more' => $this->t('More')]);
 	}
 
 	protected function setupUserCallback(): \Closure
diff --git a/src/Module/Moderation/Report/Create.php b/src/Module/Moderation/Report/Create.php
index 9761686ff0..660d5ed9b3 100644
--- a/src/Module/Moderation/Report/Create.php
+++ b/src/Module/Moderation/Report/Create.php
@@ -26,7 +26,6 @@ use Friendica\Moderation\Entity\Report;
 use Friendica\Module\Response;
 use Friendica\Navigation\SystemMessages;
 use Friendica\Network\HTTPException\ForbiddenException;
-use Friendica\Util\Network;
 use Friendica\Util\Profiler;
 use Psr\Log\LoggerInterface;
 
@@ -221,11 +220,19 @@ class Create extends BaseModule
 			}
 
 			if (DI::mode()->isMobile()) {
-				$itemsPerPage = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'system', 'itemspage_mobile_network',
-					DI::config()->get('system', 'itemspage_network_mobile'));
+				$itemsPerPage = DI::pConfig()->get(
+					DI::userSession()->getLocalUserId(),
+					'system',
+					'itemspage_mobile_network',
+					DI::config()->get('system', 'itemspage_network_mobile')
+				);
 			} else {
-				$itemsPerPage = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'system', 'itemspage_network',
-					DI::config()->get('system', 'itemspage_network'));
+				$itemsPerPage = DI::pConfig()->get(
+					DI::userSession()->getLocalUserId(),
+					'system',
+					'itemspage_network',
+					DI::config()->get('system', 'itemspage_network')
+				);
 			}
 
 			$pager = new Pager(DI::l10n(), DI::args()->getQueryString(), $itemsPerPage);
@@ -260,6 +267,11 @@ class Create extends BaseModule
 		$contact = Contact::getById($request['cid'], ['url']);
 
 		$tpl = Renderer::getMarkupTemplate('moderation/report/create/summary.tpl');
+
+		$forward_translation = $this->t('Would you like to forward this report to the remote server?');
+		// @deprecated 2025.04 this translation is scheduled for removal as a new translation has been added without the typo
+		$forward_translation = $this->t('Would you ike to forward this report to the remote server?');
+
 		return Renderer::replaceMacros($tpl, [
 			'$l10n' => [
 				'title'                => $this->t('Create Moderation Report'),
@@ -280,7 +292,7 @@ class Create extends BaseModule
 			'$block'    => ['contact_action', $this->t('Block contact'), self::CONTACT_ACTION_BLOCK, $this->t("Their posts won't appear in your Network page anymore, but their replies can appear in forum threads, with their content collapsed by default. They cannot follow you but still can have access to your public posts by other means.")],
 
 			'$display_forward' => !$this->baseUrl->isLocalUrl($contact['url']),
-			'$forward'         => ['report_forward', $this->t('Forward report'), self::CONTACT_ACTION_BLOCK, $this->t('Would you ike to forward this report to the remote server?')],
+			'$forward'         => ['report_forward', $this->t('Forward report'), self::CONTACT_ACTION_BLOCK, $forward_translation],
 
 			'$summary' => $this->getAside($request),
 		]);
@@ -294,12 +306,18 @@ class Create extends BaseModule
 		}
 
 		switch ($request['category'] ?? 0) {
-			case Report::CATEGORY_SPAM:      $category = $this->t('Spam'); break;
-			case Report::CATEGORY_ILLEGAL:   $category = $this->t('Illegal Content'); break;
-			case Report::CATEGORY_SAFETY:    $category = $this->t('Community Safety'); break;
-			case Report::CATEGORY_UNWANTED:  $category = $this->t('Unwanted Content/Behavior'); break;
-			case Report::CATEGORY_VIOLATION: $category = $this->t('Rules Violation'); break;
-			case Report::CATEGORY_OTHER:     $category = $this->t('Other'); break;
+			case Report::CATEGORY_SPAM:      $category = $this->t('Spam');
+				break;
+			case Report::CATEGORY_ILLEGAL:   $category = $this->t('Illegal Content');
+				break;
+			case Report::CATEGORY_SAFETY:    $category = $this->t('Community Safety');
+				break;
+			case Report::CATEGORY_UNWANTED:  $category = $this->t('Unwanted Content/Behavior');
+				break;
+			case Report::CATEGORY_VIOLATION: $category = $this->t('Rules Violation');
+				break;
+			case Report::CATEGORY_OTHER:     $category = $this->t('Other');
+				break;
 
 			default: $category = '';
 		}
diff --git a/src/Module/ParseUrl.php b/src/Module/ParseUrl.php
index 8da72c742c..d023368f10 100644
--- a/src/Module/ParseUrl.php
+++ b/src/Module/ParseUrl.php
@@ -11,12 +11,13 @@ use Friendica\App\Arguments;
 use Friendica\App\BaseURL;
 use Friendica\BaseModule;
 use Friendica\Content\Text\BBCode;
-use Friendica\Core\Hook;
 use Friendica\Core\L10n;
 use Friendica\Core\Session\Capability\IHandleUserSessions;
+use Friendica\Event\ArrayFilterEvent;
 use Friendica\Network\HTTPException\BadRequestException;
 use Friendica\Util;
 use Friendica\Util\Profiler;
+use Psr\EventDispatcher\EventDispatcherInterface;
 use Psr\Log\LoggerInterface;
 
 class ParseUrl extends BaseModule
@@ -24,11 +25,14 @@ class ParseUrl extends BaseModule
 	/** @var IHandleUserSessions */
 	protected $userSession;
 
-	public function __construct(L10n $l10n, BaseURL $baseUrl, Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, IHandleUserSessions $userSession, $server, array $parameters = [])
+	private EventDispatcherInterface $eventDispatcher;
+
+	public function __construct(L10n $l10n, BaseURL $baseUrl, Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, IHandleUserSessions $userSession, EventDispatcherInterface $eventDispatcher, $server, array $parameters = [])
 	{
 		parent::__construct($l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters);
 
-		$this->userSession = $userSession;
+		$this->userSession     = $userSession;
+		$this->eventDispatcher = $eventDispatcher;
 	}
 
 	protected function rawContent(array $request = [])
@@ -37,10 +41,10 @@ class ParseUrl extends BaseModule
 			throw new \Friendica\Network\HTTPException\ForbiddenException();
 		}
 
-		$format = '';
-		$title = '';
+		$format      = '';
+		$title       = '';
 		$description = '';
-		$ret = ['success' => false, 'contentType' => ''];
+		$ret         = ['success' => false, 'contentType' => ''];
 
 		if (!empty($_GET['binurl']) && Util\Strings::isHex($_GET['binurl'])) {
 			$url = trim(hex2bin($_GET['binurl']));
@@ -80,15 +84,21 @@ class ParseUrl extends BaseModule
 			}
 		}
 
-		$arr = ['url' => $url, 'format' => $format, 'text' => null];
+		$hook_data = [
+			'url'    => $url,
+			'format' => $format,
+			'text'   => null,
+		];
 
-		Hook::callAll('parse_link', $arr);
+		$hook_data = $this->eventDispatcher->dispatch(
+			new ArrayFilterEvent(ArrayFilterEvent::PARSE_LINK, $hook_data),
+		)->getArray();
 
-		if ($arr['text']) {
+		if ($hook_data['text']) {
 			if ($format == 'json') {
-				$this->jsonExit($arr['text']);
+				$this->jsonExit($hook_data['text']);
 			} else {
-				$this->httpExit($arr['text']);
+				$this->httpExit($hook_data['text']);
 			}
 		}
 
@@ -109,14 +119,14 @@ class ParseUrl extends BaseModule
 				}
 
 				$ret['contentType'] = $content_type;
-				$ret['data'] = ['url' => $url];
-				$ret['success'] = true;
+				$ret['data']        = ['url' => $url];
+				$ret['success']     = true;
 			} else {
 				unset($siteinfo['keywords']);
 
-				$ret['data'] = $siteinfo;
+				$ret['data']        = $siteinfo;
 				$ret['contentType'] = 'attachment';
-				$ret['success'] = true;
+				$ret['success']     = true;
 			}
 
 			$this->jsonExit($ret);
diff --git a/src/Module/Photo.php b/src/Module/Photo.php
index 8faef5aa6d..ddd7b1994d 100644
--- a/src/Module/Photo.php
+++ b/src/Module/Photo.php
@@ -308,7 +308,9 @@ class Photo extends BaseApi
 
 				// For local users directly use the photo record that is marked as the profile
 				if (DI::baseUrl()->isLocalUrl($contact['url'])) {
-					$contact = Contact::selectFirst($fields, ['nurl' => $contact['nurl'], 'self' => true]);
+					$nurl    = $contact['nurl'];
+					$contact = Contact::selectFirst($fields, ['nurl' => $nurl, 'self' => true]);
+
 					if (!empty($contact)) {
 						if ($customsize <= Proxy::PIXEL_MICRO) {
 							$scale = 6;
@@ -324,7 +326,7 @@ class Photo extends BaseApi
 							$this->logger->notice('Profile photo was not loaded', ['scale' => $scale, 'uid' => $contact['uid']]);
 						}
 					} else {
-						$this->logger->notice('Local Contact was not found', ['url' => $contact['nurl']]);
+						$this->logger->notice('Local Contact was not found', ['url' => $nurl]);
 					}
 				}
 
diff --git a/src/Module/Ping/Network.php b/src/Module/Ping/Network.php
index f8c41a2e1c..520a5e66a6 100644
--- a/src/Module/Ping/Network.php
+++ b/src/Module/Ping/Network.php
@@ -31,6 +31,7 @@ use Friendica\Module\Conversation\Network as NetworkModule;
 use Friendica\Module\Response;
 use Friendica\Navigation\SystemMessages;
 use Friendica\Util\Profiler;
+use Psr\EventDispatcher\EventDispatcherInterface;
 use Psr\Log\LoggerInterface;
 
 class Network extends NetworkModule
@@ -40,9 +41,61 @@ class Network extends NetworkModule
 	 */
 	private $lock;
 
-	public function __construct(ICanLock $lock, UserDefinedChannelFactory $userDefinedChannel, NetworkFactory $network, CommunityFactory $community, ChannelFactory $channelFactory, UserDefinedChannel $channel, AppHelper $appHelper, TimelineFactory $timeline, SystemMessages $systemMessages, Mode $mode, Conversation $conversation, Page $page, IHandleUserSessions $session, Database $database, IManagePersonalConfigValues $pConfig, IManageConfigValues $config, ICanCache $cache, L10n $l10n, BaseURL $baseUrl, Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, array $server, array $parameters = [])
-	{
-		parent::__construct($userDefinedChannel, $network, $community, $channelFactory, $channel, $appHelper, $timeline, $systemMessages, $mode, $conversation, $page, $session, $database, $pConfig, $config, $cache, $l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters);
+	public function __construct(
+		ICanLock $lock,
+		UserDefinedChannelFactory $userDefinedChannel,
+		NetworkFactory $network,
+		CommunityFactory $community,
+		ChannelFactory $channelFactory,
+		UserDefinedChannel $channel,
+		AppHelper $appHelper,
+		EventDispatcherInterface $eventDispatcher,
+		TimelineFactory $timeline,
+		SystemMessages $systemMessages,
+		Mode $mode,
+		Conversation $conversation,
+		Page $page,
+		IHandleUserSessions $session,
+		Database $database,
+		IManagePersonalConfigValues $pConfig,
+		IManageConfigValues $config,
+		ICanCache $cache,
+		L10n $l10n,
+		BaseURL $baseUrl,
+		Arguments $args,
+		LoggerInterface $logger,
+		Profiler $profiler,
+		Response $response,
+		array $server,
+		array $parameters = []
+	) {
+		parent::__construct(
+			$userDefinedChannel,
+			$network,
+			$community,
+			$channelFactory,
+			$channel,
+			$appHelper,
+			$eventDispatcher,
+			$timeline,
+			$systemMessages,
+			$mode,
+			$conversation,
+			$page,
+			$session,
+			$database,
+			$pConfig,
+			$config,
+			$cache,
+			$l10n,
+			$baseUrl,
+			$args,
+			$logger,
+			$profiler,
+			$response,
+			$server,
+			$parameters
+		);
 
 		$this->lock = $lock;
 	}
diff --git a/src/Module/Post/Tag/Add.php b/src/Module/Post/Tag/Add.php
index bf704a50c0..a145c36e90 100644
--- a/src/Module/Post/Tag/Add.php
+++ b/src/Module/Post/Tag/Add.php
@@ -153,10 +153,16 @@ EOT;
 
 		$post['id'] = $post_id;
 
-		$post = $this->eventDispatcher->dispatch(
-			new ArrayFilterEvent(ArrayFilterEvent::POST_LOCAL_END, $post)
+		$hook_data = [
+			'item' => $post,
+		];
+
+		$hook_data = $this->eventDispatcher->dispatch(
+			new ArrayFilterEvent(ArrayFilterEvent::INSERT_POST_LOCAL_END, $hook_data)
 		)->getArray();
 
+		$post = $hook_data['item'] ?? $post;
+
 		$post = Post::selectFirst(['uri-id', 'uid'], ['id' => $post_id]);
 
 		Worker::add(Worker::PRIORITY_HIGH, 'Notifier', Delivery::POST, $post['uri-id'], $post['uid']);
diff --git a/src/Module/Post/Tag/Remove.php b/src/Module/Post/Tag/Remove.php
index e5652c79a8..4426c48b7f 100644
--- a/src/Module/Post/Tag/Remove.php
+++ b/src/Module/Post/Tag/Remove.php
@@ -54,7 +54,7 @@ class Remove extends \Friendica\BaseModule
 
 	protected function content(array $request = []): string
 	{
-		$returnUrl = hex2bin($request['return'] ?? '');
+		$returnUrl = $request['return'] ?? '';
 
 		if (!$this->session->getLocalUserId()) {
 			$this->baseUrl->redirect($returnUrl);
@@ -80,7 +80,7 @@ class Remove extends \Friendica\BaseModule
 		if ($tag_text === '') {
 			$this->baseUrl->redirect($returnUrl);
 		}
-		
+
 		$tags = explode(',', $tag_text);
 
 		$tag_checkboxes = array_map(function ($tag_text) {
@@ -97,7 +97,7 @@ class Remove extends \Friendica\BaseModule
 			],
 
 			'$item_id'        => $item_id,
-			'$return'         => $returnUrl,
+			'$return'         => urlencode($returnUrl),
 			'$tag_checkboxes' => $tag_checkboxes,
 		]);
 	}
diff --git a/src/Module/Privacy/PermissionTooltip.php b/src/Module/Privacy/PermissionTooltip.php
index ef4ba9f7bd..e6656b95e9 100644
--- a/src/Module/Privacy/PermissionTooltip.php
+++ b/src/Module/Privacy/PermissionTooltip.php
@@ -7,14 +7,16 @@
 
 namespace Friendica\Module\Privacy;
 
-use Friendica\App;
+use Friendica\App\Arguments;
+use Friendica\App\BaseURL;
+use Friendica\BaseModule;
 use Friendica\Core\Config\Capability\IManageConfigValues;
-use Friendica\Core\Hook;
 use Friendica\Core\L10n;
 use Friendica\Core\Protocol;
 use Friendica\Core\Renderer;
 use Friendica\Core\Session\Capability\IHandleUserSessions;
 use Friendica\Database\Database;
+use Friendica\Event\ArrayFilterEvent;
 use Friendica\Model;
 use Friendica\Module\Response;
 use Friendica\Network\HTTPException;
@@ -23,33 +25,50 @@ use Friendica\Privacy\Entity;
 use Friendica\Security\PermissionSet\Repository\PermissionSet;
 use Friendica\Util\ACLFormatter;
 use Friendica\Util\Profiler;
+use Psr\EventDispatcher\EventDispatcherInterface;
 use Psr\Log\LoggerInterface;
 
 /**
  * Outputs the permission tooltip HTML content for the provided item, photo or event id.
  */
-class PermissionTooltip extends \Friendica\BaseModule
+class PermissionTooltip extends BaseModule
 {
 	private Database $dba;
 	private ACLFormatter $aclFormatter;
 	private IHandleUserSessions $session;
 	private IManageConfigValues $config;
 	private PermissionSet $permissionSet;
+	private EventDispatcherInterface $eventDispatcher;
 
-	public function __construct(PermissionSet $permissionSet, IManageConfigValues $config, IHandleUserSessions $session, ACLFormatter $aclFormatter, Database $dba, L10n $l10n, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, array $server, array $parameters = [])
-	{
+	public function __construct(
+		PermissionSet $permissionSet,
+		IManageConfigValues $config,
+		IHandleUserSessions $session,
+		ACLFormatter $aclFormatter,
+		Database $dba,
+		EventDispatcherInterface $eventDispatcher,
+		L10n $l10n,
+		BaseURL $baseUrl,
+		Arguments $args,
+		LoggerInterface $logger,
+		Profiler $profiler,
+		Response $response,
+		array $server,
+		array $parameters = []
+	) {
 		parent::__construct($l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters);
 
-		$this->dba = $dba;
-		$this->aclFormatter = $aclFormatter;
-		$this->session = $session;
-		$this->config = $config;
-		$this->permissionSet = $permissionSet;
+		$this->dba             = $dba;
+		$this->aclFormatter    = $aclFormatter;
+		$this->session         = $session;
+		$this->config          = $config;
+		$this->permissionSet   = $permissionSet;
+		$this->eventDispatcher = $eventDispatcher;
 	}
 
 	protected function rawContent(array $request = [])
 	{
-		$type = $this->parameters['type'];
+		$type        = $this->parameters['type'];
 		$referenceId = $this->parameters['id'];
 
 		$expectedTypes = ['item', 'photo', 'event'];
@@ -60,10 +79,10 @@ class PermissionTooltip extends \Friendica\BaseModule
 		$condition = ['id' => $referenceId, 'uid' => [0, $this->session->getLocalUserId()]];
 		if ($type == 'item') {
 			$fields = ['uid', 'psid', 'private', 'uri-id', 'origin', 'network'];
-			$model = Model\Post::selectFirst($fields, $condition, ['order' => ['uid' => true]]);
+			$model  = Model\Post::selectFirst($fields, $condition, ['order' => ['uid' => true]]);
 
 			if ($model['origin'] || ($model['network'] != Protocol::ACTIVITYPUB)) {
-				$permissionSet = $this->permissionSet->selectOneById($model['psid'], $model['uid']);
+				$permissionSet      = $this->permissionSet->selectOneById($model['psid'], $model['uid']);
 				$model['allow_cid'] = $permissionSet->allow_cid;
 				$model['allow_gid'] = $permissionSet->allow_gid;
 				$model['deny_cid']  = $permissionSet->deny_cid;
@@ -75,8 +94,8 @@ class PermissionTooltip extends \Friendica\BaseModule
 				$model['deny_gid']  = [];
 			}
 		} else {
-			$fields = ['uid', 'allow_cid', 'allow_gid', 'deny_cid', 'deny_gid'];
-			$model = $this->dba->selectFirst($type, $fields, $condition);
+			$fields             = ['uid', 'allow_cid', 'allow_gid', 'deny_cid', 'deny_gid'];
+			$model              = $this->dba->selectFirst($type, $fields, $condition);
 			$model['allow_cid'] = $this->aclFormatter->expand($model['allow_cid']);
 			$model['allow_gid'] = $this->aclFormatter->expand($model['allow_gid']);
 			$model['deny_cid']  = $this->aclFormatter->expand($model['deny_cid']);
@@ -87,10 +106,17 @@ class PermissionTooltip extends \Friendica\BaseModule
 			throw new HttpException\NotFoundException($this->t('Model not found'));
 		}
 
-		// Kept for backwards compatibility
-		Hook::callAll('lockview_content', $model);
+		$hook_data = [
+			'model' => $model,
+		];
 
-		$aclReceivers = new Entity\AclReceivers();
+		$hook_data = $this->eventDispatcher->dispatch(
+			new ArrayFilterEvent(ArrayFilterEvent::PERMISSION_TOOLTIP_CONTENT, $hook_data),
+		)->getArray();
+
+		$model = $hook_data['model'] ?? $model;
+
+		$aclReceivers       = new Entity\AclReceivers();
 		$addressedReceivers = new Entity\AddressedReceivers();
 		if (!empty($model['allow_cid']) || !empty($model['allow_gid']) || !empty($model['deny_cid']) || !empty($model['deny_gid'])) {
 			$aclReceivers = $this->fetchReceiversFromACL($model);
@@ -100,30 +126,35 @@ class PermissionTooltip extends \Friendica\BaseModule
 
 		$privacy = '';
 		switch ($model['private'] ?? null) {
-			case Model\Item::PUBLIC:   $privacy = $this->t('Public'); break;
-			case Model\Item::UNLISTED: $privacy = $this->t('Unlisted'); break;
-			case Model\Item::PRIVATE:  $privacy = $this->t('Limited/Private'); break;
+			case Model\Item::PUBLIC:
+				$privacy = $this->t('Public');
+				break;
+			case Model\Item::UNLISTED:
+				$privacy = $this->t('Unlisted');
+				break;
+			case Model\Item::PRIVATE:
+				$privacy = $this->t('Limited/Private');
+				break;
 		}
 
-		if ($aclReceivers->isEmpty() && $addressedReceivers->isEmpty() && empty($privacy))
-		{
+		if ($aclReceivers->isEmpty() && $addressedReceivers->isEmpty() && empty($privacy)) {
 			echo $this->t('Remote privacy information not available.');
 			exit;
 		}
 
-		$tpl = Renderer::getMarkupTemplate('privacy/permission_tooltip.tpl');
+		$tpl    = Renderer::getMarkupTemplate('privacy/permission_tooltip.tpl');
 		$output = Renderer::replaceMacros($tpl, [
 			'$l10n' => [
 				'visible_to' => $this->t('Visible to:'),
-				'to' => $this->t('To:'),
-				'cc' => $this->t('CC:'),
-				'bcc' => $this->t('BCC:'),
-				'audience' => $this->t('Audience:'),
+				'to'         => $this->t('To:'),
+				'cc'         => $this->t('CC:'),
+				'bcc'        => $this->t('BCC:'),
+				'audience'   => $this->t('Audience:'),
 				'attributed' => $this->t('Attributed To:'),
 			],
-			'$aclReceivers' => $aclReceivers,
+			'$aclReceivers'       => $aclReceivers,
 			'$addressedReceivers' => $addressedReceivers,
-			'$privacy' => $privacy,
+			'$privacy'            => $privacy,
 		]);
 
 		$this->httpExit($output);
@@ -197,7 +228,7 @@ class PermissionTooltip extends \Friendica\BaseModule
 	private function fetchAddressedReceivers(int $uriId): Entity\AddressedReceivers
 	{
 		$own_url = '';
-		$uid = $this->session->getLocalUserId();
+		$uid     = $this->session->getLocalUserId();
 		if ($uid) {
 			$owner = Model\User::getOwnerDataById($uid);
 			if (!empty($owner['url'])) {
@@ -220,11 +251,11 @@ class PermissionTooltip extends \Friendica\BaseModule
 					$receivers[$receiver['type']][] = $this->t('Collection (%s)', $receiver['name']);
 					break;
 				case Model\Tag::FOLLOWER_COLLECTION:
-					$apcontact = $this->dba->selectFirst('apcontact', ['name'], ['followers' => $receiver['url']]);
+					$apcontact                      = $this->dba->selectFirst('apcontact', ['name'], ['followers' => $receiver['url']]);
 					$receivers[$receiver['type']][] = $this->t('Followers (%s)', $apcontact['name'] ?? $receiver['name']);
 					break;
 				case Model\Tag::ACCOUNT:
-					$apcontact = Model\APContact::getByURL($receiver['url'], false);
+					$apcontact                      = Model\APContact::getByURL($receiver['url'], false);
 					$receivers[$receiver['type']][] = $apcontact['name'] ?? $receiver['name'];
 					break;
 				default:
@@ -234,19 +265,19 @@ class PermissionTooltip extends \Friendica\BaseModule
 		}
 
 		foreach ($receivers as $type => $receiver) {
-			$max = $this->config->get('system', 'max_receivers');
+			$max   = $this->config->get('system', 'max_receivers');
 			$total = count($receiver);
 			if ($total > $max) {
-				$receivers[$type] = array_slice($receiver, 0, $max);
+				$receivers[$type]   = array_slice($receiver, 0, $max);
 				$receivers[$type][] = $this->t('%d more', $total - $max);
 			}
 		}
 
 		return new Entity\AddressedReceivers(
-			$receivers[Model\Tag::TO] ?? [],
-			$receivers[Model\Tag::CC] ?? [],
-			$receivers[Model\Tag::BCC] ?? [],
-			$receivers[Model\Tag::AUDIENCE] ?? [],
+			$receivers[Model\Tag::TO]         ?? [],
+			$receivers[Model\Tag::CC]         ?? [],
+			$receivers[Model\Tag::BCC]        ?? [],
+			$receivers[Model\Tag::AUDIENCE]   ?? [],
 			$receivers[Model\Tag::ATTRIBUTED] ?? [],
 		);
 	}
diff --git a/src/Module/Profile/Index.php b/src/Module/Profile/Index.php
index 699466f0e5..e7d02863e8 100644
--- a/src/Module/Profile/Index.php
+++ b/src/Module/Profile/Index.php
@@ -23,6 +23,7 @@ use Friendica\Module\Response;
 use Friendica\Profile\ProfileField\Repository\ProfileField;
 use Friendica\Util\DateTimeFormat;
 use Friendica\Util\Profiler;
+use Psr\EventDispatcher\EventDispatcherInterface;
 use Psr\Log\LoggerInterface;
 
 /**
@@ -56,26 +57,47 @@ class Index extends BaseModule
 	private $pConfig;
 	/** @var Mode */
 	private $mode;
+	private EventDispatcherInterface $eventDispatcher;
 
-	public function __construct(Mode $mode, IManagePersonalConfigValues $pConfig, Conversation $conversation, DateTimeFormat $dateTimeFormat, ProfileField $profileField, Page $page, IManageConfigValues $config, IHandleUserSessions $session, AppHelper $appHelper, Database $database, L10n $l10n, BaseURL $baseUrl, Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, array $server, array $parameters = [])
-	{
+	public function __construct(
+		Mode $mode,
+		IManagePersonalConfigValues $pConfig,
+		Conversation $conversation,
+		DateTimeFormat $dateTimeFormat,
+		ProfileField $profileField,
+		Page $page,
+		IManageConfigValues $config,
+		IHandleUserSessions $session,
+		AppHelper $appHelper,
+		Database $database,
+		EventDispatcherInterface $eventDispatcher,
+		L10n $l10n,
+		BaseURL $baseUrl,
+		Arguments $args,
+		LoggerInterface $logger,
+		Profiler $profiler,
+		Response $response,
+		array $server,
+		array $parameters = []
+	) {
 		parent::__construct($l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters);
 
-		$this->database       = $database;
-		$this->appHelper      = $appHelper;
-		$this->session        = $session;
-		$this->config         = $config;
-		$this->page           = $page;
-		$this->profileField   = $profileField;
-		$this->dateTimeFormat = $dateTimeFormat;
-		$this->conversation   = $conversation;
-		$this->pConfig        = $pConfig;
-		$this->mode           = $mode;
+		$this->database        = $database;
+		$this->appHelper       = $appHelper;
+		$this->session         = $session;
+		$this->config          = $config;
+		$this->page            = $page;
+		$this->profileField    = $profileField;
+		$this->dateTimeFormat  = $dateTimeFormat;
+		$this->conversation    = $conversation;
+		$this->pConfig         = $pConfig;
+		$this->mode            = $mode;
+		$this->eventDispatcher = $eventDispatcher;
 	}
 
 	protected function rawContent(array $request = [])
 	{
-		(new Profile($this->profileField, $this->page, $this->config, $this->session, $this->appHelper, $this->database, $this->l10n, $this->baseUrl, $this->args, $this->logger, $this->profiler, $this->response, $this->server, $this->parameters))->rawContent();
+		(new Profile($this->profileField, $this->page, $this->config, $this->session, $this->appHelper, $this->database, $this->eventDispatcher, $this->l10n, $this->baseUrl, $this->args, $this->logger, $this->profiler, $this->response, $this->server, $this->parameters))->rawContent();
 	}
 
 	protected function content(array $request = []): string
diff --git a/src/Module/Profile/Photos.php b/src/Module/Profile/Photos.php
index a377cd6dc3..5fe798730c 100644
--- a/src/Module/Profile/Photos.php
+++ b/src/Module/Profile/Photos.php
@@ -14,12 +14,12 @@ use Friendica\AppHelper;
 use Friendica\Content\Feature;
 use Friendica\Content\Pager;
 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\Core\System;
 use Friendica\Database\Database;
+use Friendica\Event\ArrayFilterEvent;
 use Friendica\Model\Contact;
 use Friendica\Model\Item;
 use Friendica\Model\Photo;
@@ -34,6 +34,7 @@ use Friendica\Util\DateTimeFormat;
 use Friendica\Util\Images;
 use Friendica\Util\Profiler;
 use Friendica\Util\Strings;
+use Psr\EventDispatcher\EventDispatcherInterface;
 use Psr\Log\LoggerInterface;
 
 class Photos extends \Friendica\Module\BaseProfile
@@ -52,20 +53,38 @@ class Photos extends \Friendica\Module\BaseProfile
 	private $systemMessages;
 	/** @var ACLFormatter */
 	private $aclFormatter;
+	private EventDispatcherInterface $eventDispatcher;
 	/** @var array owner-view record */
 	private $owner;
 
-	public function __construct(ACLFormatter $aclFormatter, SystemMessages $systemMessages, Database $database, AppHelper $appHelper, IManageConfigValues $config, Page $page, IHandleUserSessions $session, L10n $l10n, BaseURL $baseUrl, Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, array $server, array $parameters = [])
-	{
+	public function __construct(
+		ACLFormatter $aclFormatter,
+		SystemMessages $systemMessages,
+		Database $database,
+		AppHelper $appHelper,
+		IManageConfigValues $config,
+		Page $page,
+		IHandleUserSessions $session,
+		EventDispatcherInterface $eventDispatcher,
+		L10n $l10n,
+		BaseURL $baseUrl,
+		Arguments $args,
+		LoggerInterface $logger,
+		Profiler $profiler,
+		Response $response,
+		array $server,
+		array $parameters = []
+	) {
 		parent::__construct($l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters);
 
-		$this->session        = $session;
-		$this->page           = $page;
-		$this->config         = $config;
-		$this->appHelper      = $appHelper;
-		$this->database       = $database;
-		$this->systemMessages = $systemMessages;
-		$this->aclFormatter   = $aclFormatter;
+		$this->session         = $session;
+		$this->page            = $page;
+		$this->config          = $config;
+		$this->appHelper       = $appHelper;
+		$this->database        = $database;
+		$this->systemMessages  = $systemMessages;
+		$this->aclFormatter    = $aclFormatter;
+		$this->eventDispatcher = $eventDispatcher;
 
 		$owner = Profile::load($this->appHelper, $this->parameters['nickname'] ?? '', false);
 		if (!$owner || $owner['account_removed'] || $owner['account_expired']) {
@@ -90,19 +109,27 @@ class Photos extends \Friendica\Module\BaseProfile
 		if ($visibility === 'public') {
 			// The ACL selector introduced in version 2019.12 sends ACL input data even when the Public visibility is selected
 			$str_contact_allow = $str_circle_allow = $str_contact_deny = $str_circle_deny = '';
-		} else if ($visibility === 'custom') {
+		} elseif ($visibility === 'custom') {
 			// Since we know from the visibility parameter the item should be private, we have to prevent the empty ACL
 			// case that would make it public. So we always append the author's contact id to the allowed contacts.
 			// See https://github.com/friendica/friendica/issues/9672
 			$str_contact_allow .= $this->aclFormatter->toString(Contact::getPublicIdByUserId($this->owner['uid']));
 		}
 
+		$hook_data = [
+			'request' => $request,
+		];
+
 		// default post action - upload a photo
-		Hook::callAll('photo_post_init', $request);
+		$hook_data = $this->eventDispatcher->dispatch(
+			new ArrayFilterEvent(ArrayFilterEvent::PHOTO_UPLOAD_START, $hook_data),
+		)->getArray();
+
+		$request = $hook_data['request'] ?? $request;
 
 		// Determine the album to use
-		$album    = trim($request['album'] ?? '');
-		$newalbum = trim($request['newalbum'] ?? '');
+		$album    = strip_tags(trim($request['album'] ?? ''));
+		$newalbum = strip_tags(trim($request['newalbum'] ?? ''));
 
 		$this->logger->debug('album= ' . $album . ' newalbum= ' . $newalbum);
 
@@ -127,19 +154,27 @@ class Photos extends \Friendica\Module\BaseProfile
 			$visible = 0;
 		}
 
-		$ret      = ['src' => '', 'filename' => '', 'filesize' => 0, 'type' => ''];
+		$hook_data = [
+			'src'      => '',
+			'filename' => '',
+			'filesize' => 0,
+			'type'     => '',
+		];
+
+		$hook_data = $this->eventDispatcher->dispatch(
+			new ArrayFilterEvent(ArrayFilterEvent::PHOTO_UPLOAD, $hook_data),
+		)->getArray();
+
 		$src      = null;
 		$filename = '';
 		$filesize = 0;
 		$type     = '';
 
-		Hook::callAll('photo_post_file', $ret);
-
-		if (!empty($ret['src']) && !empty($ret['filesize'])) {
-			$src      = $ret['src'];
-			$filename = $ret['filename'];
-			$filesize = $ret['filesize'];
-			$type     = $ret['type'];
+		if (!empty($hook_data['src']) && !empty($hook_data['filesize'])) {
+			$src      = $hook_data['src'];
+			$filename = $hook_data['filename'];
+			$filesize = $hook_data['filesize'];
+			$type     = $hook_data['type'];
 			$error    = UPLOAD_ERR_OK;
 		} elseif (!empty($_FILES['userfile'])) {
 			$src      = $_FILES['userfile']['tmp_name'];
@@ -148,7 +183,7 @@ class Photos extends \Friendica\Module\BaseProfile
 			$type     = $_FILES['userfile']['type'];
 			$error    = $_FILES['userfile']['error'];
 		} else {
-			$error    = UPLOAD_ERR_NO_FILE;
+			$error = UPLOAD_ERR_NO_FILE;
 		}
 
 		if ($error !== UPLOAD_ERR_OK) {
@@ -176,8 +211,10 @@ class Photos extends \Friendica\Module\BaseProfile
 				@unlink($src);
 			}
 
-			$foo = 0;
-			Hook::callAll('photo_post_end', $foo);
+			$this->eventDispatcher->dispatch(
+				new ArrayFilterEvent(ArrayFilterEvent::PHOTO_UPLOAD_END, ['id' => 0]),
+			);
+
 			return;
 		}
 
@@ -188,16 +225,22 @@ class Photos extends \Friendica\Module\BaseProfile
 		if ($maximagesize && ($filesize > $maximagesize)) {
 			$this->systemMessages->addNotice($this->t('Image exceeds size limit of %s', Strings::formatBytes($maximagesize)));
 			@unlink($src);
-			$foo = 0;
-			Hook::callAll('photo_post_end', $foo);
+
+			$this->eventDispatcher->dispatch(
+				new ArrayFilterEvent(ArrayFilterEvent::PHOTO_UPLOAD_END, ['id' => 0]),
+			);
+
 			return;
 		}
 
 		if (!$filesize) {
 			$this->systemMessages->addNotice($this->t('Image file is empty.'));
 			@unlink($src);
-			$foo = 0;
-			Hook::callAll('photo_post_end', $foo);
+
+			$this->eventDispatcher->dispatch(
+				new ArrayFilterEvent(ArrayFilterEvent::PHOTO_UPLOAD_END, ['id' => 0]),
+			);
+
 			return;
 		}
 
@@ -211,8 +254,11 @@ class Photos extends \Friendica\Module\BaseProfile
 			$this->logger->notice('unable to process image');
 			$this->systemMessages->addNotice($this->t('Unable to process image.'));
 			@unlink($src);
-			$foo = 0;
-			Hook::callAll('photo_post_end',$foo);
+
+			$this->eventDispatcher->dispatch(
+				new ArrayFilterEvent(ArrayFilterEvent::PHOTO_UPLOAD_END, ['id' => 0]),
+			);
+
 			return;
 		}
 
@@ -268,16 +314,17 @@ class Photos extends \Friendica\Module\BaseProfile
 		$arr['visible']       = $visible;
 		$arr['origin']        = 1;
 
-		$arr['body']          = Images::getBBCodeByResource($resource_id, $this->owner['nickname'], $preview, $image->getExt());
+		$arr['body'] = Images::getBBCodeByResource($resource_id, $this->owner['nickname'], $preview, $image->getExt());
 
 		$item_id = Item::insert($arr);
 		// Update the photo albums cache
 		Photo::clearAlbumCache($this->owner['uid']);
 
-		Hook::callAll('photo_post_end', $item_id);
-
-		// addon uploaders should call "exit()" within the photo_post_end hook
+		// addon uploaders should call "exit()" within the PHOTO_UPLOAD_END event
 		// if they do not wish to be redirected
+		$this->eventDispatcher->dispatch(
+			new ArrayFilterEvent(ArrayFilterEvent::PHOTO_UPLOAD_END, ['id' => $item_id]),
+		);
 
 		$this->baseUrl->redirect($this->session->get('photo_return') ?? 'profile/' . $this->owner['nickname'] . '/photos');
 	}
@@ -336,7 +383,7 @@ class Photos extends \Friendica\Module\BaseProfile
 			$pager->getItemsPerPage()
 		));
 
-		$photos = array_map(function ($photo){
+		$photos = array_map(function ($photo) {
 			return [
 				'id'    => $photo['id'],
 				'link'  => 'photos/' . $this->owner['nickname'] . '/image/' . $photo['resource-id'],
diff --git a/src/Module/Profile/Profile.php b/src/Module/Profile/Profile.php
index e84003e4dc..58f829907e 100644
--- a/src/Module/Profile/Profile.php
+++ b/src/Module/Profile/Profile.php
@@ -17,13 +17,13 @@ use Friendica\Content\Nav;
 use Friendica\Content\Text\BBCode;
 use Friendica\Content\Text\HTML;
 use Friendica\Core\Config\Capability\IManageConfigValues;
-use Friendica\Core\Hook;
 use Friendica\Core\L10n;
 use Friendica\Core\Protocol;
 use Friendica\Core\Renderer;
 use Friendica\Core\Session\Capability\IHandleUserSessions;
 use Friendica\Database\Database;
 use Friendica\Database\DBA;
+use Friendica\Event\HtmlFilterEvent;
 use Friendica\Model\Contact;
 use Friendica\Model\Profile as ProfileModel;
 use Friendica\Model\Tag;
@@ -40,6 +40,7 @@ use Friendica\Util\Network;
 use Friendica\Util\Profiler;
 use Friendica\Util\Temporal;
 use GuzzleHttp\Psr7\Uri;
+use Psr\EventDispatcher\EventDispatcherInterface;
 use Psr\Log\LoggerInterface;
 
 class Profile extends BaseProfile
@@ -56,17 +57,34 @@ class Profile extends BaseProfile
 	private $page;
 	/** @var ProfileField */
 	private $profileField;
+	private EventDispatcherInterface $eventDispatcher;
 
-	public function __construct(ProfileField $profileField, Page $page, IManageConfigValues $config, IHandleUserSessions $session, AppHelper $appHelper, Database $database, L10n $l10n, BaseURL $baseUrl, Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, array $server, array $parameters = [])
-	{
+	public function __construct(
+		ProfileField $profileField,
+		Page $page,
+		IManageConfigValues $config,
+		IHandleUserSessions $session,
+		AppHelper $appHelper,
+		Database $database,
+		EventDispatcherInterface $eventDispatcher,
+		L10n $l10n,
+		BaseURL $baseUrl,
+		Arguments $args,
+		LoggerInterface $logger,
+		Profiler $profiler,
+		Response $response,
+		array $server,
+		array $parameters = []
+	) {
 		parent::__construct($l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters);
 
-		$this->database     = $database;
-		$this->appHelper    = $appHelper;
-		$this->session      = $session;
-		$this->config       = $config;
-		$this->page         = $page;
-		$this->profileField = $profileField;
+		$this->database        = $database;
+		$this->appHelper       = $appHelper;
+		$this->session         = $session;
+		$this->config          = $config;
+		$this->page            = $page;
+		$this->profileField    = $profileField;
+		$this->eventDispatcher = $eventDispatcher;
 	}
 
 	protected function rawContent(array $request = [])
@@ -255,7 +273,7 @@ class Profile extends BaseProfile
 		}
 
 		$tpl = Renderer::getMarkupTemplate('profile/profile.tpl');
-		$o   .= Renderer::replaceMacros($tpl, [
+		$o .= Renderer::replaceMacros($tpl, [
 			'$title'                 => $this->t('Profile'),
 			'$yourself'              => $this->t('Yourself'),
 			'$view_as_contacts'      => $view_as_contacts,
@@ -275,14 +293,16 @@ class Profile extends BaseProfile
 				'title' => '',
 				'label' => $this->t('Edit profile')
 			],
-			'$viewas_link'           => [
+			'$viewas_link' => [
 				'url'   => $this->args->getQueryString() . '#viewas',
 				'title' => '',
 				'label' => $this->t('View as')
 			],
 		]);
 
-		Hook::callAll('profile_advanced', $o);
+		$o = $this->eventDispatcher->dispatch(
+			new HTmlFilterEvent(HtmlFilterEvent::MOD_PROFILE_CONTENT, $o),
+		)->getHtml();
 
 		return $o;
 	}
@@ -342,7 +362,7 @@ class Profile extends BaseProfile
 		$htmlhead .= '' . "\n";
 		$htmlhead .= '' . "\n";
 		$htmlhead .= '' . "\n";
-		$uri      = urlencode('acct:' . $profile['nickname'] . '@' . $this->baseUrl->getHost() . ($this->baseUrl->getPath() ? '/' . $this->baseUrl->getPath() : ''));
+		$uri = urlencode('acct:' . $profile['nickname'] . '@' . $this->baseUrl->getHost() . ($this->baseUrl->getPath() ? '/' . $this->baseUrl->getPath() : ''));
 		$htmlhead .= '' . "\n";
 		header('Link: <' . $this->baseUrl . '/xrd/?uri=' . $uri . '>; rel="lrdd"; type="application/xrd+xml"', false);
 
diff --git a/src/Module/Register.php b/src/Module/Register.php
index 1cea2ba9a4..39ec42cd84 100644
--- a/src/Module/Register.php
+++ b/src/Module/Register.php
@@ -12,18 +12,19 @@ use Friendica\App\BaseURL;
 use Friendica\BaseModule;
 use Friendica\Content\Text\BBCode;
 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\Core\Worker;
 use Friendica\Database\DBA;
 use Friendica\DI;
+use Friendica\Event\ArrayFilterEvent;
 use Friendica\Model;
 use Friendica\Model\User;
 use Friendica\Util\DateTimeFormat;
 use Friendica\Util\Profiler;
 use Friendica\Util\Proxy;
+use Psr\EventDispatcher\EventDispatcherInterface;
 use Psr\Log\LoggerInterface;
 
 /**
@@ -41,13 +42,16 @@ class Register extends BaseModule
 	/** @var IHandleUserSessions */
 	private $session;
 
-	public function __construct(IHandleUserSessions $session, L10n $l10n, BaseURL $baseUrl, Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, IManageConfigValues $config, array $server, array $parameters = [])
+	private EventDispatcherInterface $eventDispatcher;
+
+	public function __construct(IHandleUserSessions $session, EventDispatcherInterface $eventDispatcher, L10n $l10n, BaseURL $baseUrl, Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, IManageConfigValues $config, array $server, array $parameters = [])
 	{
 		parent::__construct($l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters);
 
 		$this->tos = new Tos($l10n, $baseUrl, $args, $logger, $profiler, $response, $config, $server, $parameters);
 
-		$this->session = $session;
+		$this->session         = $session;
+		$this->eventDispatcher = $eventDispatcher;
 	}
 
 	/**
@@ -129,11 +133,15 @@ class Register extends BaseModule
 
 		$tpl = Renderer::getMarkupTemplate('register.tpl');
 
-		$arr = ['template' => $tpl];
+		$hook_data = [
+			'template' => $tpl,
+		];
 
-		Hook::callAll('register_form', $arr);
+		$hook_data = $this->eventDispatcher->dispatch(
+			new ArrayFilterEvent(ArrayFilterEvent::ACCOUNT_REGISTER_FORM, $hook_data),
+		)->getArray();
 
-		$tpl = $arr['template'];
+		$tpl = $hook_data['template'] ?? $tpl;
 
 		$o = Renderer::replaceMacros($tpl, [
 			'$invitations'           => DI::config()->get('system', 'invitation_only'),
@@ -190,8 +198,13 @@ class Register extends BaseModule
 	{
 		BaseModule::checkFormSecurityTokenRedirectOnError('/register', 'register');
 
-		$arr = ['post' => $_POST];
-		Hook::callAll('register_post', $arr);
+		$arr = [
+			'post' => $_POST,
+		];
+
+		$arr = $this->eventDispatcher->dispatch(
+			new ArrayFilterEvent(ArrayFilterEvent::ACCOUNT_REGISTER_POST, $arr),
+		)->getArray();
 
 		$additional_account = false;
 
diff --git a/src/Module/Search/Acl.php b/src/Module/Search/Acl.php
index ad1dc180be..410bae9a5e 100644
--- a/src/Module/Search/Acl.php
+++ b/src/Module/Search/Acl.php
@@ -7,22 +7,23 @@
 
 namespace Friendica\Module\Search;
 
-use Friendica\App;
+use Friendica\App\Arguments;
+use Friendica\App\BaseURL;
 use Friendica\BaseModule;
 use Friendica\Content\Widget;
-use Friendica\Core\Hook;
 use Friendica\Core\L10n;
 use Friendica\Core\Protocol;
 use Friendica\Core\Search;
 use Friendica\Core\Session\Capability\IHandleUserSessions;
-use Friendica\Core\System;
 use Friendica\Database\Database;
 use Friendica\Database\DBA;
+use Friendica\Event\ArrayFilterEvent;
 use Friendica\Model\Contact;
 use Friendica\Model\Post;
 use Friendica\Module\Response;
-use Friendica\Network\HTTPException;
+use Friendica\Network\HTTPException\UnauthorizedException;
 use Friendica\Util\Profiler;
+use Psr\EventDispatcher\EventDispatcherInterface;
 use Psr\Log\LoggerInterface;
 
 /**
@@ -44,13 +45,26 @@ class Acl extends BaseModule
 	private $session;
 	/** @var Database */
 	private $database;
+	private EventDispatcherInterface $eventDispatcher;
 
-	public function __construct(Database $database, IHandleUserSessions $session, L10n $l10n, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, array $server, array $parameters = [])
-	{
+	public function __construct(
+		Database $database,
+		IHandleUserSessions $session,
+		EventDispatcherInterface $eventDispatcher,
+		L10n $l10n,
+		BaseURL $baseUrl,
+		Arguments $args,
+		LoggerInterface $logger,
+		Profiler $profiler,
+		Response $response,
+		array $server,
+		array $parameters = []
+	) {
 		parent::__construct($l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters);
 
-		$this->session  = $session;
-		$this->database = $database;
+		$this->session         = $session;
+		$this->database        = $database;
+		$this->eventDispatcher = $eventDispatcher;
 	}
 
 	protected function post(array $request = [])
@@ -61,7 +75,7 @@ class Acl extends BaseModule
 	protected function rawContent(array $request = [])
 	{
 		if (!$this->session->getLocalUserId()) {
-			throw new HTTPException\UnauthorizedException($this->t('You must be logged in to use this module.'));
+			throw new UnauthorizedException($this->t('You must be logged in to use this module.'));
 		}
 
 		$type = $request['type'] ?? self::TYPE_MENTION_CONTACT_CIRCLE;
@@ -104,9 +118,9 @@ class Acl extends BaseModule
 
 	private function regularContactSearch(array $request, string $type): array
 	{
-		$start   = $request['start'] ?? 0;
-		$count   = $request['count'] ?? 100;
-		$search  = $request['search'] ?? '';
+		$start   = $request['start']        ?? 0;
+		$count   = $request['count']        ?? 100;
+		$search  = $request['search']       ?? '';
 		$conv_id = $request['conversation'] ?? null;
 
 		// For use with jquery.textcomplete for private mail completion
@@ -124,9 +138,9 @@ class Acl extends BaseModule
 		$condition_circle = ["`uid` = ? AND NOT `deleted`", $this->session->getLocalUserId()];
 
 		if ($search != '') {
-			$sql_extra        = "AND `name` LIKE '%%" . $this->database->escape($search) . "%%'";
-			$condition        = DBA::mergeConditions($condition, ["(`attag` LIKE ? OR `name` LIKE ? OR `nick` LIKE ?)",
-			                                                     '%' . $search . '%', '%' . $search . '%', '%' . $search . '%']);
+			$sql_extra = "AND `name` LIKE '%%" . $this->database->escape($search) . "%%'";
+			$condition = DBA::mergeConditions($condition, ["(`attag` LIKE ? OR `name` LIKE ? OR `nick` LIKE ?)",
+				'%' . $search . '%', '%' . $search . '%', '%' . $search . '%']);
 			$condition_circle = DBA::mergeConditions($condition_circle, ["`name` LIKE ?", '%' . $search . '%']);
 		}
 
@@ -142,21 +156,27 @@ class Acl extends BaseModule
 		switch ($type) {
 			case self::TYPE_MENTION_CONTACT_CIRCLE:
 			case self::TYPE_MENTION_CONTACT:
-				$condition = DBA::mergeConditions($condition,
+				$condition = DBA::mergeConditions(
+					$condition,
 					["NOT `self` AND NOT `blocked`",
-					]);
+					]
+				);
 				break;
 
 			case self::TYPE_MENTION_GROUP:
-				$condition = DBA::mergeConditions($condition,
+				$condition = DBA::mergeConditions(
+					$condition,
 					["NOT `self` AND NOT `blocked` AND (NOT `ap-posting-restricted` OR `ap-posting-restricted` IS NULL) AND `contact-type` = ?", Contact::TYPE_COMMUNITY
-					]);
+					]
+				);
 				break;
 
 			case self::TYPE_PRIVATE_MESSAGE:
-				$condition = DBA::mergeConditions($condition,
+				$condition = DBA::mergeConditions(
+					$condition,
 					["NOT `self` AND NOT `blocked` AND `network` IN (?, ?, ?)", Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::DIASPORA
-					]);
+					]
+				);
 				break;
 		}
 
@@ -170,7 +190,8 @@ class Acl extends BaseModule
 		if ($type == self::TYPE_MENTION_CONTACT_CIRCLE || $type == self::TYPE_MENTION_CIRCLE) {
 			/// @todo We should cache this query.
 			// This can be done when we can delete cache entries via wildcard
-			$circles = $this->database->toArray($this->database->p("SELECT `circle`.`id`, `circle`.`name`, GROUP_CONCAT(DISTINCT `circle_member`.`contact-id` SEPARATOR ',') AS uids
+			$circles = $this->database->toArray($this->database->p(
+				"SELECT `circle`.`id`, `circle`.`name`, GROUP_CONCAT(DISTINCT `circle_member`.`contact-id` SEPARATOR ',') AS uids
 				FROM `group` AS `circle`
 				INNER JOIN `group_member` AS `circle_member` ON `circle_member`.`gid` = `circle`.`id`
 				WHERE NOT `circle`.`deleted` AND `circle`.`uid` = ?
@@ -280,7 +301,7 @@ class Acl extends BaseModule
 			$resultTotal += count($unknown_contacts);
 		}
 
-		$results = [
+		$hook_data = [
 			'tot'      => $resultTotal,
 			'start'    => $start,
 			'count'    => $count,
@@ -291,13 +312,15 @@ class Acl extends BaseModule
 			'search'   => $search,
 		];
 
-		Hook::callAll('acl_lookup_end', $results);
+		$hook_data = $this->eventDispatcher->dispatch(
+			new ArrayFilterEvent(ArrayFilterEvent::ACL_LOOKUP_END, $hook_data),
+		)->getArray();
 
 		$o = [
-			'tot'   => $results['tot'],
-			'start' => $results['start'],
-			'count' => $results['count'],
-			'items' => $results['items'],
+			'tot'   => $hook_data['tot'],
+			'start' => $hook_data['start'],
+			'count' => $hook_data['count'],
+			'items' => $hook_data['items'],
 		];
 
 		$this->logger->info('ACL {action} - {subaction} - done', ['module' => 'acl', 'action' => 'content', 'subaction' => 'search', 'search' => $search, 'type' => $type, 'conversation' => $conv_id]);
diff --git a/src/Module/Settings/Channels.php b/src/Module/Settings/Channels.php
index 85ee2ccefa..0049dece27 100644
--- a/src/Module/Settings/Channels.php
+++ b/src/Module/Settings/Channels.php
@@ -192,6 +192,11 @@ class Channels extends BaseSettings
 		}
 
 		$t = Renderer::getMarkupTemplate('settings/channels.tpl');
+
+		$exclude_tags_translation = $this->t('Comma separated list of tags. If a post contain any of these tags, then it will not be part of this channel.');
+		// @deprecated 2025.04 this translation is scheduled for removal as a new translation has been added without the typo
+		$exclude_tags_translation = $this->t('Comma separated list of tags. If a post contain any of these tags, then it will not be part of nthis channel.');
+
 		return Renderer::replaceMacros($t, [
 			'open'         => count($channels) == 0,
 			'label'        => ["new_label", $this->t('Label'), '', $this->t('Short name for the channel. It is displayed on the channels widget.'), $this->t('Required')],
@@ -199,7 +204,7 @@ class Channels extends BaseSettings
 			'access_key'   => ["new_access_key", $this->t("Access Key"), '', $this->t('When you want to access this channel via an access key, you can define it here. Pay attention to not use an already used one.')],
 			'circle'       => ['new_circle', $this->t('Circle/Channel'), 0, $this->t('Select a circle or channel, that your channel should be based on.'), $circles],
 			'include_tags' => ["new_include_tags", $this->t("Include Tags"), '', $this->t('Comma separated list of tags. A post will be used when it contains any of the listed tags.')],
-			'exclude_tags' => ["new_exclude_tags", $this->t("Exclude Tags"), '', $this->t('Comma separated list of tags. If a post contain any of these tags, then it will not be part of nthis channel.')],
+			'exclude_tags' => ["new_exclude_tags", $this->t("Exclude Tags"), '', $exclude_tags_translation],
 			'min_size'     => ["new_min_size", $this->t("Minimum Size"), '', $this->t('Minimum post size. Leave empty for no minimum size. The size is calculated without links, attached posts, mentions or hashtags.')],
 			'max_size'     => ["new_max_size", $this->t("Maximum Size"), '', $this->t('Maximum post size. Leave empty for no maximum size. The size is calculated without links, attached posts, mentions or hashtags.')],
 			'text_search'  => ["new_text_search", $this->t("Full Text Search"), '', $this->t('Search terms for the body, supports the "boolean mode" operators from MariaDB. See the help for a complete list of operators and additional keywords: %s', 'help/Channels')],
diff --git a/src/Module/Settings/Profile/Index.php b/src/Module/Settings/Profile/Index.php
index f63ae3b135..9a342aa3b3 100644
--- a/src/Module/Settings/Profile/Index.php
+++ b/src/Module/Settings/Profile/Index.php
@@ -7,30 +7,33 @@
 
 namespace Friendica\Module\Settings\Profile;
 
-use Friendica\App;
+use Friendica\App\Arguments;
+use Friendica\App\BaseURL;
+use Friendica\App\Page;
 use Friendica\Core\ACL;
-use Friendica\Core\Hook;
 use Friendica\Core\L10n;
 use Friendica\Core\Protocol;
 use Friendica\Core\Renderer;
 use Friendica\Core\Session\Capability\IHandleUserSessions;
 use Friendica\Core\Theme;
+use Friendica\Core\Worker;
 use Friendica\Database\DBA;
+use Friendica\Event\ArrayFilterEvent;
 use Friendica\Model\Contact;
 use Friendica\Model\Profile;
 use Friendica\Module\Response;
-use Friendica\Navigation\SystemMessages;
-use Friendica\Profile\ProfileField;
 use Friendica\Model\User;
 use Friendica\Module\BaseSettings;
 use Friendica\Module\Security\Login;
+use Friendica\Navigation\SystemMessages;
 use Friendica\Network\HTTPException;
+use Friendica\Profile\ProfileField;
 use Friendica\Security\PermissionSet;
 use Friendica\Util\ACLFormatter;
 use Friendica\Util\DateTimeFormat;
 use Friendica\Util\Profiler;
 use Friendica\Util\Temporal;
-use Friendica\Core\Worker;
+use Psr\EventDispatcher\EventDispatcherInterface;
 use Psr\Log\LoggerInterface;
 
 class Index extends BaseSettings
@@ -47,9 +50,27 @@ class Index extends BaseSettings
 	private $permissionSetFactory;
 	/** @var ACLFormatter */
 	private $aclFormatter;
+	private EventDispatcherInterface $eventDispatcher;
 
-	public function __construct(ACLFormatter $aclFormatter, PermissionSet\Factory\PermissionSet $permissionSetFactory, PermissionSet\Repository\PermissionSet $permissionSetRepo, SystemMessages $systemMessages, ProfileField\Factory\ProfileField $profileFieldFactory, ProfileField\Repository\ProfileField $profileFieldRepo, IHandleUserSessions $session, App\Page $page, L10n $l10n, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, array $server, array $parameters = [])
-	{
+	public function __construct(
+		ACLFormatter $aclFormatter,
+		PermissionSet\Factory\PermissionSet $permissionSetFactory,
+		PermissionSet\Repository\PermissionSet $permissionSetRepo,
+		SystemMessages $systemMessages,
+		ProfileField\Factory\ProfileField $profileFieldFactory,
+		ProfileField\Repository\ProfileField $profileFieldRepo,
+		EventDispatcherInterface $eventDispatcher,
+		IHandleUserSessions $session,
+		Page $page,
+		L10n $l10n,
+		BaseURL $baseUrl,
+		Arguments $args,
+		LoggerInterface $logger,
+		Profiler $profiler,
+		Response $response,
+		array $server,
+		array $parameters = []
+	) {
 		parent::__construct($session, $page, $l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters);
 
 		$this->profileFieldRepo     = $profileFieldRepo;
@@ -58,6 +79,7 @@ class Index extends BaseSettings
 		$this->permissionSetRepo    = $permissionSetRepo;
 		$this->permissionSetFactory = $permissionSetFactory;
 		$this->aclFormatter         = $aclFormatter;
+		$this->eventDispatcher      = $eventDispatcher;
 	}
 
 	protected function post(array $request = [])
@@ -73,9 +95,11 @@ class Index extends BaseSettings
 
 		self::checkFormSecurityTokenRedirectOnError('/settings/profile', 'settings_profile');
 
-		Hook::callAll('profile_post', $request);
+		$request = $this->eventDispatcher->dispatch(
+			new ArrayFilterEvent(ArrayFilterEvent::PROFILE_SETTINGS_POST, $request),
+		)->getArray();
 
-		$dob = trim($request['dob'] ?? '');
+		$dob = $this->cleanInput($request['dob'] ?? '');
 
 		if ($dob && !in_array($dob, ['0000-00-00', DBA::NULL_DATE])) {
 			$y = substr($dob, 0, 4);
@@ -87,7 +111,7 @@ class Index extends BaseSettings
 
 			if (strpos($dob, '0000-') === 0 || strpos($dob, '0001-') === 0) {
 				$ignore_year = true;
-				$dob = substr($dob, 5);
+				$dob         = substr($dob, 5);
 			}
 
 			if ($ignore_year) {
@@ -97,18 +121,18 @@ class Index extends BaseSettings
 			}
 		}
 
-		$username = trim($request['username'] ?? '');
+		$username = $this->cleanInputText($request['username'] ?? '');
 		if (!$username) {
 			$this->systemMessages->addNotice($this->t('Display Name is required.'));
 			return;
 		}
 
-		$about        = trim($request['about']);
-		$address      = trim($request['address']);
-		$locality     = trim($request['locality']);
-		$region       = trim($request['region']);
-		$postal_code  = trim($request['postal_code']);
-		$country_name = trim($request['country_name']);
+		$about        = $this->cleanInputText($request['about']);
+		$address      = $this->cleanInputText($request['address']);
+		$locality     = $this->cleanInputText($request['locality']);
+		$region       = $this->cleanInputText($request['region']);
+		$postal_code  = $this->cleanInputText($request['postal_code']);
+		$country_name = $this->cleanInputText($request['country_name']);
 		$pub_keywords = self::cleanKeywords(trim($request['pub_keywords']));
 		$prv_keywords = self::cleanKeywords(trim($request['prv_keywords']));
 		$xmpp         = $this->cleanInput(trim($request['xmpp']));
@@ -221,7 +245,7 @@ class Index extends BaseSettings
 					$this->session->getLocalUserId(),
 					false,
 					['allow_cid' => []],
-					['network' => Protocol::DFRN],
+					['network'   => Protocol::DFRN],
 					'profile_field[new]'
 				),
 			],
@@ -254,7 +278,8 @@ class Index extends BaseSettings
 				'miscellaneous_section'     => $this->t('Miscellaneous'),
 				'custom_fields_section'     => $this->t('Custom Profile Fields'),
 				'profile_photo'             => $this->t('Upload Profile Photo'),
-				'custom_fields_description' => $this->t('

Custom fields appear on your profile page.

+ 'custom_fields_description' => $this->t( + '

Custom fields appear on your profile page.

You can use BBCodes in the field values.

Reorder by dragging the field title.

Empty the label field to remove a custom field.

@@ -288,8 +313,16 @@ class Index extends BaseSettings '$custom_fields' => $custom_fields, ]); - $arr = ['profile' => $owner, 'entry' => $o]; - Hook::callAll('profile_edit', $arr); + $hook_data = [ + 'profile' => $owner, + 'entry' => $o, + ]; + + $hook_data = $this->eventDispatcher->dispatch( + new ArrayFilterEvent(ArrayFilterEvent::PROFILE_SETTINGS_FORM, $hook_data), + )->getArray(); + + $o = $hook_data['entry'] ?? $o; return $o; } @@ -344,9 +377,14 @@ class Index extends BaseSettings return $profileFields; } + private function cleanInputText(string $input): string + { + return trim(strip_tags($input)); + } + private function cleanInput(string $input): string { - return str_replace(['<', '>', '"', ' '], '', $input); + return str_replace(['<', '>', '"', "'", ' '], '', $input); } private static function cleanKeywords($keywords): string @@ -356,7 +394,7 @@ class Index extends BaseSettings $cleaned = []; foreach ($keywords as $keyword) { - $keyword = trim($keyword); + $keyword = trim(str_replace(['<', '>', '"', "'"], '', $keyword)); $keyword = trim($keyword, '#'); if ($keyword != '') { $cleaned[] = $keyword; diff --git a/src/Module/Statistics.php b/src/Module/Statistics.php index 6a5996dac9..6540730e01 100644 --- a/src/Module/Statistics.php +++ b/src/Module/Statistics.php @@ -8,8 +8,10 @@ namespace Friendica\Module; use Friendica\App; +use Friendica\App\Arguments; +use Friendica\App\BaseURL; use Friendica\BaseModule; -use Friendica\Core\Addon; +use Friendica\Core\Addon\AddonHelper; use Friendica\Core\Config\Capability\IManageConfigValues; use Friendica\Core\KeyValueStorage\Capability\IManageKeyValuePairs; use Friendica\Core\L10n; @@ -23,13 +25,27 @@ class Statistics extends BaseModule protected $config; /** @var IManageKeyValuePairs */ protected $keyValue; + private AddonHelper $addonHelper; - public function __construct(L10n $l10n, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, IManageConfigValues $config, IManageKeyValuePairs $keyValue, Response $response, array $server, array $parameters = []) - { + public function __construct( + L10n $l10n, + BaseURL $baseUrl, + Arguments $args, + LoggerInterface $logger, + Profiler $profiler, + IManageConfigValues $config, + IManageKeyValuePairs $keyValue, + AddonHelper $addonHelper, + Response $response, + array $server, + array $parameters = [] + ) { parent::__construct($l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters); - $this->config = $config; - $this->keyValue = $keyValue; + $this->config = $config; + $this->keyValue = $keyValue; + $this->addonHelper = $addonHelper; + if (!$this->config->get("system", "nodeinfo")) { throw new NotFoundException(); } @@ -37,22 +53,21 @@ class Statistics extends BaseModule protected function rawContent(array $request = []) { - $registration_open = - Register::getPolicy() !== Register::CLOSED + $registration_open = Register::getPolicy() !== Register::CLOSED && !$this->config->get('config', 'invitation_only'); /// @todo mark the "service" addons and load them dynamically here $services = [ - 'appnet' => Addon::isEnabled('appnet'), - 'bluesky' => Addon::isEnabled('bluesky'), - 'dreamwidth' => Addon::isEnabled('dreamwidth'), - 'gnusocial' => Addon::isEnabled('gnusocial'), - 'libertree' => Addon::isEnabled('libertree'), - 'livejournal' => Addon::isEnabled('livejournal'), - 'pumpio' => Addon::isEnabled('pumpio'), - 'twitter' => Addon::isEnabled('twitter'), - 'tumblr' => Addon::isEnabled('tumblr'), - 'wordpress' => Addon::isEnabled('wordpress'), + 'appnet' => $this->addonHelper->isAddonEnabled('appnet'), + 'bluesky' => $this->addonHelper->isAddonEnabled('bluesky'), + 'dreamwidth' => $this->addonHelper->isAddonEnabled('dreamwidth'), + 'gnusocial' => $this->addonHelper->isAddonEnabled('gnusocial'), + 'libertree' => $this->addonHelper->isAddonEnabled('libertree'), + 'livejournal' => $this->addonHelper->isAddonEnabled('livejournal'), + 'pumpio' => $this->addonHelper->isAddonEnabled('pumpio'), + 'twitter' => $this->addonHelper->isAddonEnabled('twitter'), + 'tumblr' => $this->addonHelper->isAddonEnabled('tumblr'), + 'wordpress' => $this->addonHelper->isAddonEnabled('wordpress'), ]; $statistics = array_merge([ diff --git a/src/Module/Stats.php b/src/Module/Stats.php index 4bc9d95711..c8122177eb 100644 --- a/src/Module/Stats.php +++ b/src/Module/Stats.php @@ -8,8 +8,10 @@ namespace Friendica\Module; use Friendica\App; +use Friendica\App\Arguments; +use Friendica\App\BaseURL; use Friendica\BaseModule; -use Friendica\Core\Addon; +use Friendica\Core\Addon\AddonHelper; use Friendica\Core\Config\Capability\IManageConfigValues; use Friendica\Core\KeyValueStorage\Capability\IManageKeyValuePairs; use Friendica\Core\L10n; @@ -40,14 +42,28 @@ class Stats extends BaseModule protected $logger; /** @var IManageKeyValuePairs */ protected $keyValue; + private AddonHelper $addonHelper; - public function __construct(L10n $l10n, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, IManageConfigValues $config, IManageKeyValuePairs $keyValue, Database $dba, Response $response, array $server, array $parameters = []) - { + public function __construct( + L10n $l10n, + BaseURL $baseUrl, + Arguments $args, + LoggerInterface $logger, + Profiler $profiler, + IManageConfigValues $config, + IManageKeyValuePairs $keyValue, + Database $dba, + AddonHelper $addonHelper, + Response $response, + array $server, + array $parameters = [] + ) { parent::__construct($l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters); - $this->config = $config; - $this->keyValue = $keyValue; - $this->dba = $dba; + $this->config = $config; + $this->keyValue = $keyValue; + $this->dba = $dba; + $this->addonHelper = $addonHelper; } protected function content(array $request = []): string @@ -85,14 +101,14 @@ class Stats extends BaseModule 'datetime' => DateTimeFormat::utc($this->keyValue->get('last_worker_execution'), DateTimeFormat::JSON), 'timestamp' => strtotime($this->keyValue->get('last_worker_execution')), ], - 'jpm' => [ + 'jpm' => [ 1 => $this->dba->count('workerqueue', ["`done` AND `executed` > ?", DateTimeFormat::utc('now - 1 minute')]), 3 => round($this->dba->count('workerqueue', ["`done` AND `executed` > ?", DateTimeFormat::utc('now - 3 minute')]) / 3), 5 => round($this->dba->count('workerqueue', ["`done` AND `executed` > ?", DateTimeFormat::utc('now - 5 minute')]) / 5), ], - 'active' => [], - 'deferred' => [], - 'total' => [], + 'active' => [], + 'deferred' => [], + 'total' => [], ], 'jetstream' => [ 'drift' => intval($this->keyValue->get('jetstream_drift')), @@ -145,14 +161,14 @@ class Stats extends BaseModule 'closed' => $this->dba->count('report', ['status' => Report::STATUS_CLOSED]), ], 'update' => [ - 'available' => Update::isAvailable(), + 'available' => Update::isAvailable(), 'available_version' => Update::getAvailableVersion(), 'status' => Update::getStatus(), - 'db_status' => DBStructure::getUpdateStatus(), + 'db_status' => DBStructure::getUpdateStatus(), ], 'server' => [ - 'version' => App::VERSION, - 'php' => [ + 'version' => App::VERSION, + 'php' => [ 'version' => phpversion(), 'upload_max_filesize' => ini_get('upload_max_filesize'), 'post_max_size' => ini_get('post_max_size'), @@ -164,12 +180,12 @@ class Stats extends BaseModule ], ]; - if (Addon::isEnabled('bluesky')) { - $statistics['packets']['inbound'][Protocol::BLUESKY] = intval($this->keyValue->get('stats_packets_inbound_' . Protocol::BLUESKY) ?? 0); + if ($this->addonHelper->isAddonEnabled('bluesky')) { + $statistics['packets']['inbound'][Protocol::BLUESKY] = intval($this->keyValue->get('stats_packets_inbound_' . Protocol::BLUESKY) ?? 0); $statistics['packets']['outbound'][Protocol::BLUESKY] = intval($this->keyValue->get('stats_packets_outbound_' . Protocol::BLUESKY) ?? 0); } - if (Addon::isEnabled('tumblr')) { - $statistics['packets']['inbound'][Protocol::TUMBLR] = intval($this->keyValue->get('stats_packets_inbound_' . Protocol::TUMBLR) ?? 0); + if ($this->addonHelper->isAddonEnabled('tumblr')) { + $statistics['packets']['inbound'][Protocol::TUMBLR] = intval($this->keyValue->get('stats_packets_inbound_' . Protocol::TUMBLR) ?? 0); $statistics['packets']['outbound'][Protocol::TUMBLR] = intval($this->keyValue->get('stats_packets_outbound_' . Protocol::TUMBLR) ?? 0); } @@ -202,7 +218,7 @@ class Stats extends BaseModule $jobs = $this->dba->p("SELECT COUNT(*) AS `entries`, `priority` FROM `workerqueue` WHERE NOT `done` AND `retrial` = ? GROUP BY `priority`", 0); while ($entry = $this->dba->fetch($jobs)) { - $running = $this->dba->count('workerqueue-view', ['priority' => $entry['priority']]); + $running = $this->dba->count('workerqueue-view', ['priority' => $entry['priority']]); $statistics['worker']['active']['total'] += $running; $statistics['worker']['active'][$entry['priority']] = $running; $statistics['worker']['total']['total'] += $entry['entries']; diff --git a/src/Module/StatsCaching.php b/src/Module/StatsCaching.php new file mode 100644 index 0000000000..668d26e021 --- /dev/null +++ b/src/Module/StatsCaching.php @@ -0,0 +1,108 @@ +config = $config; + $this->cache = $cache; + $this->lock = $lock; + } + + private function isAllowed(array $request): bool + { + return !empty($request['key']) && $request['key'] == $this->config->get('system', 'stats_key'); + } + + /** + * @throws NotFoundException In case the rquest isn't allowed + */ + protected function content(array $request = []): string + { + if (!$this->isAllowed($request)) { + throw new HTTPException\NotFoundException($this->l10n->t('Page not found.')); + } + return ''; + } + + protected function rawContent(array $request = []) + { + if (!$this->isAllowed($request)) { + return; + } + + $data = []; + + // OPcache + if (function_exists('opcache_get_status')) { + $status = opcache_get_status(false); + $data['opcache'] = [ + 'enabled' => $status['opcache_enabled'] ?? false, + 'hit_rate' => $status['opcache_statistics']['opcache_hit_rate'] ?? null, + 'used_memory' => $status['memory_usage']['used_memory'] ?? null, + 'free_memory' => $status['memory_usage']['free_memory'] ?? null, + 'num_cached_scripts' => $status['opcache_statistics']['num_cached_scripts'] ?? null, + ]; + } else { + $data['opcache'] = [ + 'enabled' => false, + ]; + } + + if ($this->cache instanceof ICanCacheInMemory) { + $data['cache'] = [ + 'type' => $this->cache->getName(), + 'stats' => $this->cache->getStats(), + ]; + } else { + $data['cache'] = [ + 'type' => $this->cache->getName(), + ]; + } + + if ($this->lock instanceof CacheLock) { + $data['lock'] = [ + 'type' => $this->lock->getName(), + 'stats' => $this->lock->getCacheStats(), + ]; + } else { + $data['lock'] = [ + 'type' => $this->lock->getName(), + ]; + } + + $this->response->setType('json', 'application/json; charset=utf-8'); + $this->response->addContent(json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT)); + } +} diff --git a/src/Navigation/Notifications/Collection/Notifications.php b/src/Navigation/Notifications/Collection/Notifications.php index 2174b213a9..e422283891 100644 --- a/src/Navigation/Notifications/Collection/Notifications.php +++ b/src/Navigation/Notifications/Collection/Notifications.php @@ -8,36 +8,55 @@ namespace Friendica\Navigation\Notifications\Collection; use Friendica\BaseCollection; -use Friendica\Navigation\Notifications\Entity; +use Friendica\Navigation\Notifications\Entity\Notification as NotificationEntity; class Notifications extends BaseCollection { - /** - * @return Entity\Notification - */ - public function current(): Entity\Notification + public function current(): NotificationEntity { return parent::current(); } public function setSeen(): Notifications { - return $this->map(function (Entity\Notification $Notification) { - $Notification->setSeen(); + $notifications = $this->map(function (NotificationEntity $notification) { + $notification->setSeen(); }); + + if (!$notifications instanceof Notifications) { + // Show the possible error explicitly + throw new \Exception(sprintf( + 'BaseCollection::map() should return instance of %s, but returns %s instead.', + Notifications::class, + get_class($notifications), + )); + } + + return $notifications; } public function setDismissed(): Notifications { - return $this->map(function (Entity\Notification $Notification) { - $Notification->setDismissed(); + $notifications = $this->map(function (NotificationEntity $notification) { + $notification->setDismissed(); }); + + if (!$notifications instanceof Notifications) { + // Show the possible error explicitly + throw new \Exception(sprintf( + 'BaseCollection::map() should return instance of %s, but returns %s instead.', + Notifications::class, + get_class($notifications), + )); + } + + return $notifications; } public function countUnseen(): int { - return array_reduce($this->getArrayCopy(), function (int $carry, Entity\Notification $Notification) { - return $carry + ($Notification->seen ? 0 : 1); + return array_reduce($this->getArrayCopy(), function (int $carry, NotificationEntity $notification) { + return $carry + ($notification->seen ? 0 : 1); }, 0); } } diff --git a/src/Navigation/Notifications/Collection/Notifies.php b/src/Navigation/Notifications/Collection/Notifies.php index 4f41d2e261..9c2f5dcc74 100644 --- a/src/Navigation/Notifications/Collection/Notifies.php +++ b/src/Navigation/Notifications/Collection/Notifies.php @@ -8,22 +8,30 @@ namespace Friendica\Navigation\Notifications\Collection; use Friendica\BaseCollection; -use Friendica\Navigation\Notifications\Entity; +use Friendica\Navigation\Notifications\Entity\Notify as NotifyEntity; class Notifies extends BaseCollection { - /** - * @return Entity\Notify - */ - public function current(): Entity\Notify + public function current(): NotifyEntity { return parent::current(); } public function setSeen(): Notifies { - return $this->map(function (Entity\Notify $Notify) { - $Notify->setSeen(); + $notifies = $this->map(function (NotifyEntity $notify) { + $notify->setSeen(); }); + + if (!$notifies instanceof Notifies) { + // Show the possible error explicitly + throw new \Exception(sprintf( + 'BaseCollection::map() should return instance of %s, but returns %s instead.', + Notifies::class, + get_class($notifies), + )); + } + + return $notifies; } } diff --git a/src/Navigation/Notifications/Entity/Notify.php b/src/Navigation/Notifications/Entity/Notify.php index 3ff478633e..71308772a9 100644 --- a/src/Navigation/Notifications/Entity/Notify.php +++ b/src/Navigation/Notifications/Entity/Notify.php @@ -14,7 +14,7 @@ use Friendica\Core\Renderer; use Psr\Http\Message\UriInterface; /** - * @property-read string $type + * @property-read int $type * @property-read string $name * @property-read UriInterface $url * @property-read UriInterface $photo @@ -33,7 +33,8 @@ use Psr\Http\Message\UriInterface; * @property-read int|null $parentUriId * @property-read int|null $id * - * @deprecated since 2022.05 Use \Friendica\Navigation\Notifications\Entity\Notification instead + * @deprecated 2022.05 Use \Friendica\Navigation\Notifications\Entity\Notification instead + * @see \Friendica\Navigation\Notifications\Entity\Notification */ class Notify extends BaseEntity { diff --git a/src/Navigation/Notifications/Repository/Notification.php b/src/Navigation/Notifications/Repository/Notification.php index 11d3bd6b54..85c8895135 100644 --- a/src/Navigation/Notifications/Repository/Notification.php +++ b/src/Navigation/Notifications/Repository/Notification.php @@ -15,8 +15,8 @@ use Friendica\Database\Database; use Friendica\Database\DBA; use Friendica\Model\Post\UserNotification; use Friendica\Model\Verb; -use Friendica\Navigation\Notifications\Collection; -use Friendica\Navigation\Notifications\Entity; +use Friendica\Navigation\Notifications\Collection\Notifications as NotificationsCollection; +use Friendica\Navigation\Notifications\Entity\Notification as NotificationEntity; use Friendica\Navigation\Notifications\Factory; use Friendica\Network\HTTPException\NotFoundException; use Friendica\Protocol\Activity; @@ -41,19 +41,18 @@ class Notification extends BaseRepository } /** - * @param array $condition - * @param array $params - * @return Entity\Notification * @throws NotFoundException */ - private function selectOne(array $condition, array $params = []): Entity\Notification + private function selectOne(array $condition, array $params = []): NotificationEntity { - return parent::_selectOne($condition, $params); + $fields = $this->_selectFirstRowAsArray( $condition, $params); + + return $this->factory->createFromTableRow($fields); } - private function select(array $condition, array $params = []): Collection\Notifications + private function select(array $condition, array $params = []): NotificationsCollection { - return new Collection\Notifications(parent::_select($condition, $params)->getArrayCopy()); + return new NotificationsCollection(parent::_select($condition, $params)->getArrayCopy()); } public function countForUser($uid, array $condition, array $params = []): int @@ -71,23 +70,21 @@ class Notification extends BaseRepository } /** - * @param int $id - * @return Entity\Notification * @throws NotFoundException */ - public function selectOneById(int $id): Entity\Notification + public function selectOneById(int $id): NotificationEntity { return $this->selectOne(['id' => $id]); } - public function selectOneForUser(int $uid, array $condition, array $params = []): Entity\Notification + public function selectOneForUser(int $uid, array $condition, array $params = []): NotificationEntity { $condition = DBA::mergeConditions($condition, ['uid' => $uid]); return $this->selectOne($condition, $params); } - public function selectForUser(int $uid, array $condition = [], array $params = []): Collection\Notifications + public function selectForUser(int $uid, array $condition = [], array $params = []): NotificationsCollection { $condition = DBA::mergeConditions($condition, ['uid' => $uid]); @@ -98,12 +95,9 @@ class Notification extends BaseRepository /** * Returns only the most recent notifications for the same conversation or contact * - * @param int $uid - * - * @return Collection\Notifications * @throws Exception */ - public function selectDetailedForUser(int $uid): Collection\Notifications + public function selectDetailedForUser(int $uid): NotificationsCollection { $notify_type = $this->pconfig->get($uid, 'system', 'notify_type'); if (!is_null($notify_type)) { @@ -113,11 +107,11 @@ class Notification extends BaseRepository } if (!$this->pconfig->get($uid, 'system', 'notify_like')) { - $condition = DBA::mergeConditions($condition, ['NOT `vid` IN (?, ?)', Verb::getID(\Friendica\Protocol\Activity::LIKE), Verb::getID(\Friendica\Protocol\Activity::DISLIKE)]); + $condition = DBA::mergeConditions($condition, ['NOT `vid` IN (?, ?)', Verb::getID(Activity::LIKE), Verb::getID(Activity::DISLIKE)]); } if (!$this->pconfig->get($uid, 'system', 'notify_announce')) { - $condition = DBA::mergeConditions($condition, ['`vid` != ?', Verb::getID(\Friendica\Protocol\Activity::ANNOUNCE)]); + $condition = DBA::mergeConditions($condition, ['`vid` != ?', Verb::getID(Activity::ANNOUNCE)]); } return $this->selectForUser($uid, $condition, ['limit' => 50, 'order' => ['id' => true]]); @@ -126,33 +120,30 @@ class Notification extends BaseRepository /** * Returns only the most recent notifications for the same conversation or contact * - * @param int $uid - * - * @return Collection\Notifications * @throws Exception */ - public function selectDigestForUser(int $uid): Collection\Notifications + public function selectDigestForUser(int $uid): NotificationsCollection { $values = [$uid]; $type_condition = ''; - $notify_type = $this->pconfig->get($uid, 'system', 'notify_type'); + $notify_type = $this->pconfig->get($uid, 'system', 'notify_type'); if (!is_null($notify_type)) { $type_condition = 'AND `type` & ? != 0'; - $values[] = $notify_type | UserNotification::TYPE_SHARED | UserNotification::TYPE_FOLLOW; + $values[] = $notify_type | UserNotification::TYPE_SHARED | UserNotification::TYPE_FOLLOW; } $like_condition = ''; if (!$this->pconfig->get($uid, 'system', 'notify_like')) { $like_condition = 'AND NOT `vid` IN (?, ?)'; - $values[] = Verb::getID(\Friendica\Protocol\Activity::LIKE); - $values[] = Verb::getID(\Friendica\Protocol\Activity::DISLIKE); + $values[] = Verb::getID(Activity::LIKE); + $values[] = Verb::getID(Activity::DISLIKE); } $announce_condition = ''; if (!$this->pconfig->get($uid, 'system', 'notify_announce')) { $announce_condition = 'AND vid != ?'; - $values[] = Verb::getID(\Friendica\Protocol\Activity::ANNOUNCE); + $values[] = Verb::getID(Activity::ANNOUNCE); } $rows = $this->db->p(" @@ -171,15 +162,20 @@ class Notification extends BaseRepository LIMIT 50 ", ...$values); - $Entities = new Collection\Notifications(); - foreach ($rows as $fields) { - $Entities[] = $this->factory->createFromTableRow($fields); + $entities = new NotificationsCollection(); + + if (!is_iterable($rows)) { + return $entities; } - return $Entities; + foreach ($rows as $fields) { + $entities[] = $this->factory->createFromTableRow($fields); + } + + return $entities; } - public function selectAllForUser(int $uid): Collection\Notifications + public function selectAllForUser(int $uid): NotificationsCollection { return $this->selectForUser($uid); } @@ -199,7 +195,7 @@ class Notification extends BaseRepository { $BaseCollection = parent::_selectByBoundaries($condition, $params, $min_id, $max_id, $limit); - return new Collection\Notifications($BaseCollection->getArrayCopy(), $BaseCollection->getTotalCount()); + return new NotificationsCollection($BaseCollection->getArrayCopy(), $BaseCollection->getTotalCount()); } public function setAllSeenForUser(int $uid, array $condition = []): bool @@ -217,11 +213,9 @@ class Notification extends BaseRepository } /** - * @param Entity\Notification $Notification - * @return Entity\Notification * @throws Exception */ - public function save(Entity\Notification $Notification): Entity\Notification + public function save(NotificationEntity $Notification): NotificationEntity { $fields = [ 'uid' => $Notification->uid, @@ -259,12 +253,12 @@ class Notification extends BaseRepository public function deleteForItem(int $itemUriId): bool { $conditionTarget = [ - 'vid' => Verb::getID(Activity::POST), + 'vid' => Verb::getID(Activity::POST), 'target-uri-id' => $itemUriId, ]; $conditionParent = [ - 'vid' => Verb::getID(Activity::POST), + 'vid' => Verb::getID(Activity::POST), 'parent-uri-id' => $itemUriId, ]; diff --git a/src/Navigation/Notifications/Repository/Notify.php b/src/Navigation/Notifications/Repository/Notify.php index 1c793faaee..40caa29437 100644 --- a/src/Navigation/Notifications/Repository/Notify.php +++ b/src/Navigation/Notifications/Repository/Notify.php @@ -13,14 +13,15 @@ use Friendica\Content\Text\BBCode; use Friendica\Content\Text\Plaintext; use Friendica\Core\Config\Capability\IManageConfigValues; use Friendica\Core\PConfig\Capability\IManagePersonalConfigValues; -use Friendica\Core\Hook; use Friendica\Core\L10n; use Friendica\Database\Database; use Friendica\Database\DBA; +use Friendica\Event\ArrayFilterEvent; use Friendica\Factory\Api\Mastodon\Notification as NotificationFactory; use Friendica\Model; use Friendica\Navigation\Notifications\Collection; -use Friendica\Navigation\Notifications\Entity; +use Friendica\Navigation\Notifications\Entity\Notification as NotificationEntity; +use Friendica\Navigation\Notifications\Entity\Notify as NotifyEntity; use Friendica\Navigation\Notifications\Exception; use Friendica\Navigation\Notifications\Factory; use Friendica\Network\HTTPException; @@ -28,10 +29,11 @@ use Friendica\Object\Api\Mastodon\Notification; use Friendica\Protocol\Activity; use Friendica\Util\DateTimeFormat; use Friendica\Util\Emailer; +use Psr\EventDispatcher\EventDispatcherInterface; use Psr\Log\LoggerInterface; /** - * @deprecated since 2022.05 Use \Friendica\Navigation\Notifications\Repository\Notification instead + * @deprecated 2022.05 Use `\Friendica\Navigation\Notifications\Repository\Notification` instead */ class Notify extends BaseRepository { @@ -56,30 +58,41 @@ class Notify extends BaseRepository /** @var Factory\Notification */ protected $notification; + private EventDispatcherInterface $eventDispatcher; + protected static $table_name = 'notify'; - public function __construct(Database $database, LoggerInterface $logger, L10n $l10n, BaseURL $baseUrl, IManageConfigValues $config, IManagePersonalConfigValues $pConfig, Emailer $emailer, Factory\Notification $notification, Factory\Notify $factory = null) - { - $this->l10n = $l10n; - $this->baseUrl = $baseUrl; - $this->config = $config; - $this->pConfig = $pConfig; - $this->emailer = $emailer; - $this->notification = $notification; + public function __construct( + Database $database, + LoggerInterface $logger, + L10n $l10n, + BaseURL $baseUrl, + IManageConfigValues $config, + IManagePersonalConfigValues $pConfig, + Emailer $emailer, + Factory\Notification $notification, + EventDispatcherInterface $eventDispatcher, + Factory\Notify $factory = null + ) { + $this->l10n = $l10n; + $this->baseUrl = $baseUrl; + $this->config = $config; + $this->pConfig = $pConfig; + $this->emailer = $emailer; + $this->notification = $notification; + $this->eventDispatcher = $eventDispatcher; parent::__construct($database, $logger, $factory ?? new Factory\Notify($logger)); } /** - * @param array $condition - * @param array $params - * - * @return Entity\Notify * @throws HTTPException\NotFoundException */ - private function selectOne(array $condition, array $params = []): Entity\Notify + private function selectOne(array $condition, array $params = []): NotifyEntity { - return parent::_selectOne($condition, $params); + $fields = $this->_selectFirstRowAsArray( $condition, $params); + + return $this->factory->createFromTableRow($fields); } private function select(array $condition, array $params = []): Collection\Notifies @@ -104,10 +117,9 @@ class Notify extends BaseRepository /** * @param int $id * - * @return Entity\Notify * @throws HTTPException\NotFoundException */ - public function selectOneById(int $id): Entity\Notify + public function selectOneById(int $id): NotifyEntity { return $this->selectOne(['id' => $id]); } @@ -139,14 +151,11 @@ class Notify extends BaseRepository } /** - * @param Entity\Notify $Notify - * - * @return Entity\Notify * @throws HTTPException\NotFoundException * @throws HTTPException\InternalServerErrorException * @throws Exception\NotificationCreationInterceptedException */ - public function save(Entity\Notify $Notify): Entity\Notify + public function save(NotifyEntity $Notify): NotifyEntity { $fields = [ 'type' => $Notify->type, @@ -171,7 +180,10 @@ class Notify extends BaseRepository $this->db->update(self::$table_name, $fields, ['id' => $Notify->id]); } else { $fields['date'] = DateTimeFormat::utcNow(); - Hook::callAll('enotify_store', $fields); + + $fields = $this->eventDispatcher->dispatch( + new ArrayFilterEvent(ArrayFilterEvent::ENOTIFY_STORE, $fields), + )->getArray(); $this->db->insert(self::$table_name, $fields); @@ -181,7 +193,7 @@ class Notify extends BaseRepository return $Notify; } - public function setAllSeenForRelatedNotify(Entity\Notify $Notify): bool + public function setAllSeenForRelatedNotify(NotifyEntity $Notify): bool { $condition = [ '(`link` = ? OR (`parent` != 0 AND `parent` = ? AND `otype` = ?)) AND `uid` = ?', @@ -549,7 +561,7 @@ class Notify extends BaseRepository $subject .= " (".$nickname."@".$hostname.")"; - $h = [ + $hook_data = [ 'params' => $params, 'subject' => $subject, 'preamble' => $preamble, @@ -561,18 +573,20 @@ class Notify extends BaseRepository 'itemlink' => $itemlink ]; - Hook::callAll('enotify', $h); + $hook_data = $this->eventDispatcher->dispatch( + new ArrayFilterEvent(ArrayFilterEvent::ENOTIFY, $hook_data), + )->getArray(); - $subject = $h['subject']; + $subject = $hook_data['subject']; - $preamble = $h['preamble']; - $epreamble = $h['epreamble']; + $preamble = $hook_data['preamble']; + $epreamble = $hook_data['epreamble']; - $body = $h['body']; + $body = $hook_data['body']; - $tsitelink = $h['tsitelink']; - $hsitelink = $h['hsitelink']; - $itemlink = $h['itemlink']; + $tsitelink = $hook_data['tsitelink']; + $hsitelink = $hook_data['hsitelink']; + $itemlink = $hook_data['itemlink']; $notify_id = 0; @@ -620,7 +634,7 @@ class Notify extends BaseRepository } } - $datarray = [ + $hook_data = [ 'preamble' => $preamble, 'type' => $params['type'], 'parent' => $parent_id, @@ -637,31 +651,33 @@ class Notify extends BaseRepository 'headers' => $emailBuilder->getHeaders(), ]; - Hook::callAll('enotify_mail', $datarray); + $hook_data = $this->eventDispatcher->dispatch( + new ArrayFilterEvent(ArrayFilterEvent::ENOTIFY_MAIL, $hook_data), + )->getArray(); $emailBuilder - ->withHeaders($datarray['headers']) + ->withHeaders($hook_data['headers']) ->withRecipient($params['to_email']) ->forUser([ - 'uid' => $datarray['uid'], + 'uid' => $hook_data['uid'], 'language' => $params['language'], ]) - ->withNotification($datarray['subject'], $datarray['preamble'], $datarray['title'], $datarray['body']) - ->withSiteLink($datarray['tsitelink'], $datarray['hsitelink']) - ->withItemLink($datarray['itemlink']); + ->withNotification($hook_data['subject'], $hook_data['preamble'], $hook_data['title'], $hook_data['body']) + ->withSiteLink($hook_data['tsitelink'], $hook_data['hsitelink']) + ->withItemLink($hook_data['itemlink']); // If a photo is present, add it to the email - if (!empty($datarray['source_photo'])) { + if (!empty($hook_data['source_photo'])) { $emailBuilder->withPhoto( - $datarray['source_photo'], - $datarray['source_link'] ?? $sitelink, - $datarray['source_name'] ?? $sitename + $hook_data['source_photo'], + $hook_data['source_link'] ?? $sitelink, + $hook_data['source_name'] ?? $sitename ); } $email = $emailBuilder->build(); - $this->logger->debug('Send mail', $datarray); + $this->logger->debug('Send mail', $hook_data); // use the Emailer class to send the message return $this->emailer->send($email); @@ -670,7 +686,7 @@ class Notify extends BaseRepository return false; } - public function shouldShowOnDesktop(Entity\Notification $Notification, string $type = null): bool + public function shouldShowOnDesktop(NotificationEntity $Notification, string $type = null): bool { if (is_null($type)) { $type = NotificationFactory::getType($Notification); @@ -702,7 +718,7 @@ class Notify extends BaseRepository return false; } - public function createFromNotification(Entity\Notification $Notification): bool + public function createFromNotification(NotificationEntity $Notification): bool { $this->logger->info('Start', ['uid' => $Notification->uid, 'id' => $Notification->id, 'type' => $Notification->type]); diff --git a/src/Navigation/Notifications/ValueObject/Introduction.php b/src/Navigation/Notifications/ValueObject/Introduction.php index 587daa8f4e..36e490d0be 100644 --- a/src/Navigation/Notifications/ValueObject/Introduction.php +++ b/src/Navigation/Notifications/ValueObject/Introduction.php @@ -61,6 +61,34 @@ class Introduction implements \JsonSerializable /** @var string */ private $about; + public function __construct(array $data = []) + { + $this->label = $data['label'] ?? ''; + $this->type = $data['str_type'] ?? ''; + $this->intro_id = $data['intro_id'] ?? -1; + $this->madeBy = $data['madeBy'] ?? ''; + $this->madeByUrl = $data['madeByUrl'] ?? ''; + $this->madeByZrl = $data['madeByZrl'] ?? ''; + $this->madeByAddr = $data['madeByAddr'] ?? ''; + $this->contactId = $data['contactId'] ?? -1; + $this->photo = $data['photo'] ?? ''; + $this->name = $data['name'] ?? ''; + $this->url = $data['url'] ?? ''; + $this->zrl = $data['zrl'] ?? ''; + $this->hidden = $data['hidden'] ?? false; + $this->postNewFriend = $data['postNewFriend'] ?? ''; + $this->knowYou = $data['knowYou'] ?? false; + $this->note = $data['note'] ?? ''; + $this->request = $data['request'] ?? ''; + $this->dfrnId = -1; + $this->addr = $data['addr'] ?? ''; + $this->network = $data['network'] ?? ''; + $this->uid = $data['uid'] ?? -1; + $this->keywords = $data['keywords'] ?? ''; + $this->location = $data['location'] ?? ''; + $this->about = $data['about'] ?? ''; + } + public function getLabel(): string { return $this->label; @@ -131,7 +159,7 @@ class Introduction implements \JsonSerializable return $this->postNewFriend; } - public function getKnowYou(): string + public function getKnowYou(): bool { return $this->knowYou; } @@ -181,34 +209,6 @@ class Introduction implements \JsonSerializable return $this->about; } - public function __construct(array $data = []) - { - $this->label = $data['label'] ?? ''; - $this->type = $data['str_type'] ?? ''; - $this->intro_id = $data['intro_id'] ?? -1; - $this->madeBy = $data['madeBy'] ?? ''; - $this->madeByUrl = $data['madeByUrl'] ?? ''; - $this->madeByZrl = $data['madeByZrl'] ?? ''; - $this->madeByAddr = $data['madeByAddr'] ?? ''; - $this->contactId = $data['contactId'] ?? -1; - $this->photo = $data['photo'] ?? ''; - $this->name = $data['name'] ?? ''; - $this->url = $data['url'] ?? ''; - $this->zrl = $data['zrl'] ?? ''; - $this->hidden = $data['hidden'] ?? false; - $this->postNewFriend = $data['postNewFriend'] ?? ''; - $this->knowYou = $data['knowYou'] ?? false; - $this->note = $data['note'] ?? ''; - $this->request = $data['request'] ?? ''; - $this->dfrnId = -1; - $this->addr = $data['addr'] ?? ''; - $this->network = $data['network'] ?? ''; - $this->uid = $data['uid'] ?? -1; - $this->keywords = $data['keywords'] ?? ''; - $this->location = $data['location'] ?? ''; - $this->about = $data['about'] ?? ''; - } - /** * @inheritDoc */ diff --git a/src/Network/HTTPClient/Response/CurlResult.php b/src/Network/HTTPClient/Response/CurlResult.php index a79023132a..a38de01336 100644 --- a/src/Network/HTTPClient/Response/CurlResult.php +++ b/src/Network/HTTPClient/Response/CurlResult.php @@ -225,11 +225,11 @@ class CurlResult implements ICanHandleHttpResponses } } - $this->redirectUrl = (string)Uri::fromParts((array)$redirect_parts); - $this->isRedirectUrl = true; + $this->redirectUrl = (string)Uri::fromParts((array)$redirect_parts); + $this->isRedirectUrl = true; $this->redirectIsPermanent = $this->returnCode == 301 || $this->returnCode == 308; } else { - $this->isRedirectUrl = false; + $this->isRedirectUrl = false; $this->redirectIsPermanent = false; } } @@ -246,7 +246,7 @@ class CurlResult implements ICanHandleHttpResponses /** {@inheritDoc} */ public function getReturnCode(): string { - return $this->returnCode; + return (string) $this->returnCode; } /** {@inheritDoc} */ diff --git a/src/Network/HTTPClient/Response/GuzzleResponse.php b/src/Network/HTTPClient/Response/GuzzleResponse.php index 0e246a0cb0..fde67cdd8c 100644 --- a/src/Network/HTTPClient/Response/GuzzleResponse.php +++ b/src/Network/HTTPClient/Response/GuzzleResponse.php @@ -100,7 +100,7 @@ class GuzzleResponse extends Response implements ICanHandleHttpResponses, Respon /** {@inheritDoc} */ public function getReturnCode(): string { - return $this->getStatusCode(); + return (string) $this->getStatusCode(); } /** {@inheritDoc} */ diff --git a/src/Object/Api/Friendica/Notification.php b/src/Object/Api/Friendica/Notification.php index bff10accae..2a49ea770c 100644 --- a/src/Object/Api/Friendica/Notification.php +++ b/src/Object/Api/Friendica/Notification.php @@ -62,37 +62,37 @@ class Notification extends BaseDataTransferObject /** @var string Message (Plaintext) */ protected $msg_plain; - public function __construct(Notify $Notify) + public function __construct(Notify $notify) { - $this->id = $Notify->id; - $this->type = $Notify->type; - $this->name = $Notify->name; - $this->url = $Notify->url->__toString(); - $this->photo = $Notify->photo->__toString(); - $this->date = DateTimeFormat::local($Notify->date->format(DateTimeFormat::MYSQL)); - $this->msg = $Notify->msg; - $this->uid = $Notify->uid; - $this->link = $Notify->link->__toString(); - $this->iid = $Notify->itemId; - $this->parent = $Notify->parent; - $this->seen = $Notify->seen; - $this->verb = $Notify->verb; - $this->otype = $Notify->otype; - $this->name_cache = $Notify->name_cache; - $this->msg_cache = $Notify->msg_cache; - $this->timestamp = $Notify->date->format('U'); + $this->id = $notify->id; + $this->type = $notify->type; + $this->name = $notify->name; + $this->url = $notify->url->__toString(); + $this->photo = $notify->photo->__toString(); + $this->date = DateTimeFormat::local($notify->date->format(DateTimeFormat::MYSQL)); + $this->msg = $notify->msg; + $this->uid = $notify->uid; + $this->link = $notify->link->__toString(); + $this->iid = $notify->itemId; + $this->parent = $notify->parent; + $this->seen = $notify->seen; + $this->verb = $notify->verb; + $this->otype = $notify->otype; + $this->name_cache = $notify->name_cache; + $this->msg_cache = $notify->msg_cache; + $this->timestamp = (int) $notify->date->format('U'); $this->date_rel = Temporal::getRelativeDate($this->date); try { - $this->msg_html = BBCode::convertForUriId($Notify->uriId, $this->msg, BBCode::EXTERNAL); + $this->msg_html = BBCode::convertForUriId($notify->uriId, $this->msg, BBCode::EXTERNAL); } catch (\Exception $e) { - $this->msg_html = ''; + $this->msg_html = ''; } try { $this->msg_plain = explode("\n", trim(HTML::toPlaintext($this->msg_html, 0)))[0]; } catch (\Exception $e) { - $this->msg_plain = ''; + $this->msg_plain = ''; } } } diff --git a/src/Object/Api/Mastodon/Field.php b/src/Object/Api/Mastodon/Field.php index cf1983f2fe..092f33b361 100644 --- a/src/Object/Api/Mastodon/Field.php +++ b/src/Object/Api/Mastodon/Field.php @@ -20,12 +20,12 @@ class Field extends BaseDataTransferObject protected $name; /** @var string (HTML) */ protected $value; - /** @var string (Datetime)*/ + /** @var string|null (Datetime)*/ protected $verified_at; public function __construct(string $name, string $value) { - $this->name = $name; + $this->name = $name; $this->value = $value; // Link verification unsupported $this->verified_at = null; diff --git a/src/Object/Api/Mastodon/Notification.php b/src/Object/Api/Mastodon/Notification.php index e5593bfc82..b18a7f1e9e 100644 --- a/src/Object/Api/Mastodon/Notification.php +++ b/src/Object/Api/Mastodon/Notification.php @@ -44,9 +44,9 @@ class Notification extends BaseDataTransferObject protected $created_at; /** @var bool */ protected $dismissed; - /** @var Account */ + /** @var array */ protected $account; - /** @var Status|null */ + /** @var array|null */ protected $status = null; /** diff --git a/src/Object/Api/Mastodon/Poll.php b/src/Object/Api/Mastodon/Poll.php index 3d2e6c8f25..a17e8c57b5 100644 --- a/src/Object/Api/Mastodon/Poll.php +++ b/src/Object/Api/Mastodon/Poll.php @@ -32,7 +32,7 @@ class Poll extends BaseDataTransferObject /** @var bool|null */ protected $voted = false; /** @var array|null */ - protected $own_votes = false; + protected $own_votes = null; /** @var array */ protected $options = []; /** @var Emoji[] */ diff --git a/src/Object/Api/Mastodon/Relationship.php b/src/Object/Api/Mastodon/Relationship.php index 1158aa5459..7a179dea3e 100644 --- a/src/Object/Api/Mastodon/Relationship.php +++ b/src/Object/Api/Mastodon/Relationship.php @@ -18,7 +18,7 @@ use Friendica\Util\Network; */ class Relationship extends BaseDataTransferObject { - /** @var int */ + /** @var string */ protected $id; /** @var bool */ protected $following = false; @@ -93,7 +93,5 @@ class Relationship extends BaseDataTransferObject $this->blocked_by = $isBlocked; $this->note = $contactRecord['info']; } - - return $this; } } diff --git a/src/Object/Api/Mastodon/ScheduledStatus.php b/src/Object/Api/Mastodon/ScheduledStatus.php index ac4bf029c2..630ebd7839 100644 --- a/src/Object/Api/Mastodon/ScheduledStatus.php +++ b/src/Object/Api/Mastodon/ScheduledStatus.php @@ -35,7 +35,7 @@ class ScheduledStatus extends BaseDataTransferObject 'in_reply_to_id' => null, 'application_id' => '' ]; - /** @var Attachment */ + /** @var array */ protected $media_attachments = []; /** diff --git a/src/Object/Api/Mastodon/Status.php b/src/Object/Api/Mastodon/Status.php index 9f68d27d14..652e6dde4d 100644 --- a/src/Object/Api/Mastodon/Status.php +++ b/src/Object/Api/Mastodon/Status.php @@ -30,7 +30,7 @@ class Status extends BaseDataTransferObject protected $edited_at; /** @var string|null */ protected $in_reply_to_id = null; - /** @var Status|null - Fedilab extension, see issue https://github.com/friendica/friendica/issues/12672 */ + /** @var Status[]|null - Fedilab extension, see issue https://github.com/friendica/friendica/issues/12672 */ protected $in_reply_to_status = null; /** @var string|null */ protected $in_reply_to_account_id = null; @@ -66,25 +66,25 @@ class Status extends BaseDataTransferObject protected $content; /** @var array */ protected $filtered = []; - /** @var Status|null */ + /** @var Status[]|null */ protected $reblog = null; - /** @var Status|null - Akkoma extension, see issue https://github.com/friendica/friendica/issues/12603 */ + /** @var Status[]|null - Akkoma extension, see issue https://github.com/friendica/friendica/issues/12603 */ protected $quote = null; - /** @var Application */ + /** @var array */ protected $application = null; - /** @var Account */ + /** @var array */ protected $account; - /** @var Attachment */ + /** @var Attachment[] */ protected $media_attachments = []; - /** @var Mention */ + /** @var Mention[] */ protected $mentions = []; - /** @var Tag */ + /** @var Tag[] */ protected $tags = []; /** @var Emoji[] */ protected $emojis = []; - /** @var Card|null */ + /** @var array|null */ protected $card = null; - /** @var Poll|null */ + /** @var array|null */ protected $poll = null; /** @var FriendicaExtension */ protected $friendica; diff --git a/src/Object/Api/Twitter/DirectMessage.php b/src/Object/Api/Twitter/DirectMessage.php index 10bea34290..199d9462ed 100644 --- a/src/Object/Api/Twitter/DirectMessage.php +++ b/src/Object/Api/Twitter/DirectMessage.php @@ -29,9 +29,9 @@ class DirectMessage extends BaseDataTransferObject protected $sender_screen_name = null; /** @var string */ protected $recipient_screen_name = null; - /** @var User */ + /** @var array */ protected $sender; - /** @var User */ + /** @var array */ protected $recipient; /** @var string|null */ protected $title; diff --git a/src/Object/Api/Twitter/Media.php b/src/Object/Api/Twitter/Media.php index 19f98f1a29..589aebcfc1 100644 --- a/src/Object/Api/Twitter/Media.php +++ b/src/Object/Api/Twitter/Media.php @@ -31,7 +31,7 @@ class Media extends BaseDataTransferObject protected $media_url; /** @var string */ protected $media_url_https; - /** @var string */ + /** @var array> */ protected $sizes; /** @var string */ protected $type; diff --git a/src/Object/Api/Twitter/Mention.php b/src/Object/Api/Twitter/Mention.php index e5d257e9d3..d2a4d5065f 100644 --- a/src/Object/Api/Twitter/Mention.php +++ b/src/Object/Api/Twitter/Mention.php @@ -7,7 +7,6 @@ namespace Friendica\Object\Api\Twitter; -use Friendica\App\BaseURL; use Friendica\BaseDataTransferObject; /** @@ -37,7 +36,7 @@ class Mention extends BaseDataTransferObject */ public function __construct(array $tag, array $contact, array $indices) { - $this->id = (string)($contact['id'] ?? 0); + $this->id = (int)($contact['id'] ?? 0); $this->id_str = (string)($contact['id'] ?? 0); $this->indices = $indices; $this->name = $tag['name']; diff --git a/src/Object/Api/Twitter/Status.php b/src/Object/Api/Twitter/Status.php index 793f32fb01..1c8a4ef7ae 100644 --- a/src/Object/Api/Twitter/Status.php +++ b/src/Object/Api/Twitter/Status.php @@ -45,11 +45,11 @@ class Status extends BaseDataTransferObject protected $geo; /** @var bool */ protected $favorited = false; - /** @var User */ + /** @var array */ protected $user; - /** @var User */ + /** @var array */ protected $friendica_author; - /** @var User */ + /** @var array */ protected $friendica_owner; /** @var bool */ protected $friendica_private; @@ -67,9 +67,9 @@ class Status extends BaseDataTransferObject protected $friendica_html; /** @var int */ protected $friendica_comments; - /** @var Status|null */ + /** @var array|null */ protected $retweeted_status = null; - /** @var Status|null */ + /** @var array|null */ protected $quoted_status = null; /** @var array */ protected $attachments; diff --git a/src/Object/Log/ParsedLogLine.php b/src/Object/Log/ParsedLogLine.php index 1d9802bb32..8e552008a4 100644 --- a/src/Object/Log/ParsedLogLine.php +++ b/src/Object/Log/ParsedLogLine.php @@ -29,7 +29,7 @@ class ParsedLogLine /** @var string */ public $message = null; - /** @var string */ + /** @var string|null */ public $data = null; /** @var string */ diff --git a/src/Object/Post.php b/src/Object/Post.php index d4b381a115..b4fcfcc094 100644 --- a/src/Object/Post.php +++ b/src/Object/Post.php @@ -9,7 +9,6 @@ namespace Friendica\Object; use Friendica\Content\ContactSelector; use Friendica\Content\Feature; -use Friendica\Core\Addon; use Friendica\Core\Protocol; use Friendica\Core\Renderer; use Friendica\DI; @@ -49,7 +48,7 @@ class Post private $parent = null; /** - * @var Thread + * @var Thread|null */ private $thread = null; private $redirect_url = null; @@ -71,7 +70,7 @@ class Post $this->setTemplate('wall'); $this->toplevel = $this->getId() == $this->getDataValue('parent'); - if (!empty(DI::userSession()->getUserIDForVisitorContactID($this->getDataValue('contact-id')))) { + if (DI::userSession()->getUserIDForVisitorContactID($this->getDataValue('contact-id')) !== 0) { $this->visiting = true; } @@ -825,7 +824,7 @@ class Post * Get a child by its ID * * @param integer $id The child id - * @return Thread|null Thread or NULL if not found + * @return Post|null Post or NULL if not found */ public function getChild(int $id) { @@ -1118,12 +1117,14 @@ class Post $conv = $this->getThread(); if ($conv->isWritable() && $this->isWritable()) { + $addonHelper = DI::addonHelper(); + /* * Hmmm, code depending on the presence of a particular addon? * This should be better if done by a hook */ $qcomment = null; - if (Addon::isEnabled('qcomment')) { + if ($addonHelper->isAddonEnabled('qcomment')) { $words = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'qcomment', 'words'); $qcomment = $words ? explode("\n", $words) : []; } diff --git a/src/Object/Thread.php b/src/Object/Thread.php index 453e4c3421..070d8c1281 100644 --- a/src/Object/Thread.php +++ b/src/Object/Thread.php @@ -77,8 +77,7 @@ class Thread break; default: DI::logger()->info('[ERROR] Conversation::setMode : Unhandled mode ('. $mode .').'); - return false; - break; + return; } $this->mode = $mode; } diff --git a/src/Profile/ProfileField/Collection/ProfileFields.php b/src/Profile/ProfileField/Collection/ProfileFields.php index fa39a6f355..44f13673d7 100644 --- a/src/Profile/ProfileField/Collection/ProfileFields.php +++ b/src/Profile/ProfileField/Collection/ProfileFields.php @@ -8,31 +8,33 @@ namespace Friendica\Profile\ProfileField\Collection; use Friendica\BaseCollection; -use Friendica\Profile\ProfileField\Entity; +use Friendica\Profile\ProfileField\Entity\ProfileField as ProfileFieldEntity; class ProfileFields extends BaseCollection { - public function current(): Entity\ProfileField + public function current(): ProfileFieldEntity { return parent::current(); } - /** - * @param callable $callback - * @return ProfileFields (as an extended form of BaseCollection) - */ - public function map(callable $callback): BaseCollection + public function map(callable $callback): ProfileFields { - return parent::map($callback); + $profileFields = parent::map($callback); + + if (!$profileFields instanceof ProfileFields) { + // Show the possible error explicitly + throw new \Exception(sprintf( + 'BaseCollection::map() should return instance of %s, but returns %s instead.', + ProfileFields::class, + get_class($profileFields), + )); + } + + return $profileFields; } - /** - * @param callable|null $callback - * @param int $flag - * @return ProfileFields as an extended version of BaseCollection - */ - public function filter(callable $callback = null, int $flag = 0): BaseCollection + public function filter(?callable $callback = null, int $flag = 0): ProfileFields { - return parent::filter($callback, $flag); + return new self(array_filter($this->getArrayCopy(), $callback, $flag)); } } diff --git a/src/Protocol/ATProtocol/Actor.php b/src/Protocol/ATProtocol/Actor.php index db310813b3..38da819dfc 100755 --- a/src/Protocol/ATProtocol/Actor.php +++ b/src/Protocol/ATProtocol/Actor.php @@ -145,9 +145,11 @@ class Actor $fields['gsid'] = GServer::getRealID($fields['baseurl'], true); } - foreach ($directory->verificationMethod as $method) { - if (!empty($method->publicKeyMultibase)) { - $fields['pubkey'] = $method->publicKeyMultibase; + if (!empty($directory->verificationMethod)) { + foreach ($directory->verificationMethod as $method) { + if (!empty($method->publicKeyMultibase)) { + $fields['pubkey'] = $method->publicKeyMultibase; + } } } } diff --git a/src/Protocol/ATProtocol/Jetstream.php b/src/Protocol/ATProtocol/Jetstream.php index cb37fabeec..8051e47126 100755 --- a/src/Protocol/ATProtocol/Jetstream.php +++ b/src/Protocol/ATProtocol/Jetstream.php @@ -105,8 +105,12 @@ class Jetstream $last_timeout = time(); while (true) { try { - $message = $this->client->receive(); - $data = json_decode($message); + $message = @$this->client->receive(); + if (empty($message)) { + $this->logger->notice('Empty message received'); + break; + } + $data = json_decode($message); if (is_object($data)) { $timestamp = $data->time_us; $this->route($data); diff --git a/src/Protocol/ATProtocol/Processor.php b/src/Protocol/ATProtocol/Processor.php index d7a26f78cf..1ac2a9f00c 100755 --- a/src/Protocol/ATProtocol/Processor.php +++ b/src/Protocol/ATProtocol/Processor.php @@ -327,7 +327,7 @@ class Processor private function getHeaderFromJetstream(stdClass $data, int $uid, int $protocol = Conversation::PARCEL_JETSTREAM): array { - $contact = $this->actor->getContactByDID($data->did, $uid, 0); + $contact = $this->actor->getContactByDID($data->did, $uid, 0, true); if (empty($contact)) { $this->logger->info('Contact not found for user', ['did' => $data->did, 'uid' => $uid]); return []; @@ -392,7 +392,7 @@ class Processor if (empty($post->author) || empty($post->cid) || empty($parts->rkey)) { return []; } - $contact = $this->actor->getContactByDID($post->author->did, $uid, 0); + $contact = $this->actor->getContactByDID($post->author->did, $uid, 0, true); if (empty($contact)) { $this->logger->info('Contact not found for user', ['did' => $post->author->did, 'uid' => $uid]); return []; @@ -506,13 +506,10 @@ class Processor break; case 'app.bsky.richtext.facet#mention': - $contact = Contact::getByURL($feature->did, null, ['id']); - if (!empty($contact['id'])) { - $url = $this->baseURL . '/contact/' . $contact['id']; - if (substr($linktext, 0, 1) == '@') { - $prefix .= '@'; - $linktext = substr($linktext, 1); - } + $url = $feature->did; + if (substr($linktext, 0, 1) == '@') { + $prefix .= '@'; + $linktext = substr($linktext, 1); } break; @@ -848,17 +845,17 @@ class Processor return $class; } - public function fetchUriId(string $uri, int $uid): string + public function fetchUriId(string $uri, int $uid): int { $reply = Post::selectFirst(['uri-id'], ['uri' => $uri, 'uid' => [$uid, 0]]); if (!empty($reply['uri-id'])) { $this->logger->debug('Post exists', ['uri' => $uri]); - return $reply['uri-id']; + return (int) $reply['uri-id']; } $reply = Post::selectFirst(['uri-id'], ['extid' => $uri, 'uid' => [$uid, 0]]); if (!empty($reply['uri-id'])) { $this->logger->debug('Post with extid exists', ['uri' => $uri]); - return $reply['uri-id']; + return (int) $reply['uri-id']; } return 0; } diff --git a/src/Protocol/ActivityPub/Receiver.php b/src/Protocol/ActivityPub/Receiver.php index 34732022b5..c217d491ca 100644 --- a/src/Protocol/ActivityPub/Receiver.php +++ b/src/Protocol/ActivityPub/Receiver.php @@ -250,7 +250,7 @@ class Receiver * @param string $object_id Object ID of the provided object * @param integer $uid User ID * - * @return string with object type or NULL + * @return string|null string with object type or NULL * @throws \Friendica\Network\HTTPException\InternalServerErrorException * @throws \ImagickException */ @@ -2065,7 +2065,7 @@ class Receiver } foreach ($object_data['tags'] as $tag) { - if (HTTPSignature::isValidContentType($tag['mediaType'] ?? '', $tag['href'])) { + if (HTTPSignature::isValidContentType($tag['mediaType'] ?? '', $tag['href'] ?? '')) { $object_data['quote-url'] = $tag['href']; } } diff --git a/src/Protocol/DFRN.php b/src/Protocol/DFRN.php index be6dfc206d..56cafc1a97 100644 --- a/src/Protocol/DFRN.php +++ b/src/Protocol/DFRN.php @@ -607,64 +607,64 @@ class DFRN * @param string $element Element name for the activity * @param string $activity activity value * @param int $uriid Uri-Id of the post - * @return DOMElement XML activity object + * @return DOMElement|false XML activity object or false on error * @throws \Friendica\Network\HTTPException\InternalServerErrorException * @todo Find proper type-hints */ private static function createActivity(DOMDocument $doc, string $element, string $activity, int $uriid) { - if ($activity) { - $entry = $doc->createElement($element); - - $r = XML::parseString($activity); - if (!$r) { - return false; - } - - if ($r->type) { - XML::addElement($doc, $entry, "activity:object-type", $r->type); - } - - if ($r->id) { - XML::addElement($doc, $entry, "id", $r->id); - } - - if ($r->title) { - XML::addElement($doc, $entry, "title", $r->title); - } - - if ($r->link) { - if (substr($r->link, 0, 1) == '<') { - if (strstr($r->link, '&') && (! strstr($r->link, '&'))) { - $r->link = str_replace('&', '&', $r->link); - } - - $r->link = preg_replace('/\/', '', $r->link); - - // XML does need a single element as root element so we add a dummy element here - $data = XML::parseString("" . $r->link . ""); - if (is_object($data)) { - foreach ($data->link as $link) { - $attributes = []; - foreach ($link->attributes() as $parameter => $value) { - $attributes[$parameter] = $value; - } - XML::addElement($doc, $entry, "link", "", $attributes); - } - } - } else { - $attributes = ["rel" => "alternate", "type" => "text/html", "href" => $r->link]; - XML::addElement($doc, $entry, "link", "", $attributes); - } - } - if ($r->content) { - XML::addElement($doc, $entry, "content", BBCode::convertForUriId($uriid, $r->content, BBCode::EXTERNAL), ["type" => "html"]); - } - - return $entry; + if (!$activity) { + return false; } - return false; + $entry = $doc->createElement($element); + + $r = XML::parseString($activity); + if (!$r) { + return false; + } + + if ($r->type) { + XML::addElement($doc, $entry, "activity:object-type", $r->type); + } + + if ($r->id) { + XML::addElement($doc, $entry, "id", $r->id); + } + + if ($r->title) { + XML::addElement($doc, $entry, "title", $r->title); + } + + if ($r->link) { + if (substr($r->link, 0, 1) == '<') { + if (strstr($r->link, '&') && (! strstr($r->link, '&'))) { + $r->link = str_replace('&', '&', $r->link); + } + + $r->link = preg_replace('/\/', '', $r->link); + + // XML does need a single element as root element so we add a dummy element here + $data = XML::parseString("" . $r->link . ""); + if (is_object($data)) { + foreach ($data->link as $link) { + $attributes = []; + foreach ($link->attributes() as $parameter => $value) { + $attributes[$parameter] = $value; + } + XML::addElement($doc, $entry, "link", "", $attributes); + } + } + } else { + $attributes = ["rel" => "alternate", "type" => "text/html", "href" => $r->link]; + XML::addElement($doc, $entry, "link", "", $attributes); + } + } + if ($r->content) { + XML::addElement($doc, $entry, "content", BBCode::convertForUriId($uriid, $r->content, BBCode::EXTERNAL), ["type" => "html"]); + } + + return $entry; } /** @@ -1925,7 +1925,7 @@ class DFRN // Check if the message is wanted if (!self::isSolicitedMessage($item, $importer)) { DBA::delete('item-uri', ['uri' => $item['uri']]); - return 403; + return; } // Get the type of the item (Top level post, reply or remote reply) @@ -2051,7 +2051,7 @@ class DFRN Item::distribute($posted_id); } - return true; + return; } } else { // $entrytype == self::TOP_LEVEL if (($item['uid'] != 0) && !Contact::isSharing($item['owner-id'], $item['uid']) && !Contact::isSharing($item['author-id'], $item['uid'])) { @@ -2099,7 +2099,7 @@ class DFRN } if (!$uri || !$importer['id']) { - return false; + return; } $condition = ['uri' => $uri, 'uid' => $importer['importer_uid']]; diff --git a/src/Protocol/Diaspora.php b/src/Protocol/Diaspora.php index a8db02f045..0166e65f9d 100644 --- a/src/Protocol/Diaspora.php +++ b/src/Protocol/Diaspora.php @@ -337,7 +337,7 @@ class Diaspora * @param string $xml urldecoded Diaspora salmon * @param string $privKey The private key of the importer * - * @return array + * @return array|false array with decoded data or false on error * 'message' -> decoded Diaspora XML message * 'author' -> author diaspora handle * 'key' -> author public key (converted to pkcs#8) @@ -1051,7 +1051,7 @@ class Diaspora * @param string $server The url of the server * @param int $level Endless loop prevention * - * @return array + * @return array|false The message as array or false on error * 'message' => The message XML * 'author' => The author handle * 'key' => The public key of the author @@ -2795,7 +2795,7 @@ class Diaspora // without a public key nothing will work if (!$pubkey) { DI::logger()->notice('pubkey missing: contact id: ' . $contact['id']); - return false; + return ''; } $aes_key = random_bytes(32); @@ -2809,7 +2809,7 @@ class Diaspora $encrypted_key_bundle = ''; if (!@openssl_public_encrypt($json, $encrypted_key_bundle, $pubkey)) { - return false; + return ''; } $json_object = json_encode( diff --git a/src/Protocol/Diaspora/Repository/DiasporaContact.php b/src/Protocol/Diaspora/Repository/DiasporaContact.php index b680fedea1..5cf05b8aa0 100644 --- a/src/Protocol/Diaspora/Repository/DiasporaContact.php +++ b/src/Protocol/Diaspora/Repository/DiasporaContact.php @@ -7,6 +7,9 @@ namespace Friendica\Protocol\Diaspora\Repository; +use DateTime; +use DateTimeZone; +use Exception; use Friendica\BaseRepository; use Friendica\Database\Database; use Friendica\Database\Definition\DbaDefinition; @@ -14,11 +17,12 @@ use Friendica\Model\APContact; use Friendica\Model\Contact; use Friendica\Model\Item; use Friendica\Model\ItemURI; -use Friendica\Network\HTTPException; -use Friendica\Protocol\Diaspora\Entity; -use Friendica\Protocol\Diaspora\Factory; +use Friendica\Network\HTTPException\NotFoundException; +use Friendica\Protocol\Diaspora\Entity\DiasporaContact as DiasporaContactEntity; +use Friendica\Protocol\Diaspora\Factory\DiasporaContact as DiasporaContactFactory; use Friendica\Protocol\WebFingerUri; use Friendica\Util\DateTimeFormat; +use InvalidArgumentException; use Psr\Http\Message\UriInterface; use Psr\Log\LoggerInterface; @@ -30,12 +34,12 @@ class DiasporaContact extends BaseRepository protected static $table_name = 'diaspora-contact-view'; - /** @var Factory\DiasporaContact */ + /** @var DiasporaContactFactory */ protected $factory; /** @var DbaDefinition */ private $definition; - public function __construct(DbaDefinition $definition, Database $database, LoggerInterface $logger, Factory\DiasporaContact $factory) + public function __construct(DbaDefinition $definition, Database $database, LoggerInterface $logger, DiasporaContactFactory $factory) { parent::__construct($database, $logger, $factory); @@ -43,67 +47,58 @@ class DiasporaContact extends BaseRepository } /** - * @param array $condition - * @param array $params - * @return Entity\DiasporaContact - * @throws HTTPException\NotFoundException + * @throws NotFoundException */ - public function selectOne(array $condition, array $params = []): Entity\DiasporaContact + public function selectOne(array $condition, array $params = []): DiasporaContactEntity { - return parent::_selectOne($condition, $params); + $fields = $this->_selectFirstRowAsArray( $condition, $params); + + return $this->factory->createFromTableRow($fields); } /** - * @param int $uriId - * @return Entity\DiasporaContact - * @throws HTTPException\NotFoundException + * @throws NotFoundException */ - public function selectOneByUriId(int $uriId): Entity\DiasporaContact + public function selectOneByUriId(int $uriId): DiasporaContactEntity { return $this->selectOne(['uri-id' => $uriId]); } /** - * @param UriInterface $uri - * @return Entity\DiasporaContact - * @throws HTTPException\NotFoundException + * @throws NotFoundException */ - public function selectOneByUri(UriInterface $uri): Entity\DiasporaContact + public function selectOneByUri(UriInterface $uri): DiasporaContactEntity { try { return $this->selectOne(['url' => (string) $uri]); - } catch (HTTPException\NotFoundException $e) { + } catch (NotFoundException $e) { } try { return $this->selectOne(['addr' => (string) $uri]); - } catch (HTTPException\NotFoundException $e) { + } catch (NotFoundException $e) { } return $this->selectOne(['alias' => (string) $uri]); } /** - * @param WebFingerUri $uri - * @return Entity\DiasporaContact - * @throws HTTPException\NotFoundException + * @throws NotFoundException */ - public function selectOneByAddr(WebFingerUri $uri): Entity\DiasporaContact + public function selectOneByAddr(WebFingerUri $uri): DiasporaContactEntity { return $this->selectOne(['addr' => $uri->getAddr()]); } /** - * @param int $uriId - * @return bool - * @throws \Exception + * @throws Exception */ public function existsByUriId(int $uriId): bool { return $this->db->exists(self::$table_name, ['uri-id' => $uriId]); } - public function save(Entity\DiasporaContact $DiasporaContact): Entity\DiasporaContact + public function save(DiasporaContactEntity $DiasporaContact): DiasporaContactEntity { $uriId = $DiasporaContact->uriId ?? ItemURI::insert(['uri' => $DiasporaContact->url, 'guid' => $DiasporaContact->guid]); @@ -145,10 +140,9 @@ class DiasporaContact extends BaseRepository * * @param WebFingerUri $uri Profile address * @param boolean $update true = always update, false = never update, null = update when not found or outdated - * @return Entity\DiasporaContact - * @throws HTTPException\NotFoundException + * @throws NotFoundException */ - public function getByAddr(WebFingerUri $uri, ?bool $update = self::UPDATE_IF_MISSING_OR_OUTDATED): Entity\DiasporaContact + public function getByAddr(WebFingerUri $uri, ?bool $update = self::UPDATE_IF_MISSING_OR_OUTDATED): DiasporaContactEntity { if ($update !== self::ALWAYS_UPDATE) { try { @@ -156,7 +150,7 @@ class DiasporaContact extends BaseRepository if ($update === self::NEVER_UPDATE) { return $dcontact; } - } catch (HTTPException\NotFoundException $e) { + } catch (NotFoundException $e) { if ($update === self::NEVER_UPDATE) { throw $e; } @@ -169,7 +163,7 @@ class DiasporaContact extends BaseRepository $contact = Contact::getByURL($uri, $update, ['uri-id']); if (empty($contact['uri-id'])) { - throw new HTTPException\NotFoundException('Diaspora profile with URI ' . $uri . ' not found'); + throw new NotFoundException('Diaspora profile with URI ' . $uri . ' not found'); } return self::selectOneByUriId($contact['uri-id']); @@ -180,10 +174,9 @@ class DiasporaContact extends BaseRepository * * @param UriInterface $uri Profile URL * @param boolean $update true = always update, false = never update, null = update when not found or outdated - * @return Entity\DiasporaContact - * @throws HTTPException\NotFoundException + * @throws NotFoundException */ - public function getByUrl(UriInterface $uri, ?bool $update = self::UPDATE_IF_MISSING_OR_OUTDATED): Entity\DiasporaContact + public function getByUrl(UriInterface $uri, ?bool $update = self::UPDATE_IF_MISSING_OR_OUTDATED): DiasporaContactEntity { if ($update !== self::ALWAYS_UPDATE) { try { @@ -191,7 +184,7 @@ class DiasporaContact extends BaseRepository if ($update === self::NEVER_UPDATE) { return $dcontact; } - } catch (HTTPException\NotFoundException $e) { + } catch (NotFoundException $e) { if ($update === self::NEVER_UPDATE) { throw $e; } @@ -204,7 +197,7 @@ class DiasporaContact extends BaseRepository $contact = Contact::getByURL($uri, $update, ['uri-id']); if (empty($contact['uri-id'])) { - throw new HTTPException\NotFoundException('Diaspora profile with URI ' . $uri . ' not found'); + throw new NotFoundException('Diaspora profile with URI ' . $uri . ' not found'); } return self::selectOneByUriId($contact['uri-id']); @@ -214,27 +207,27 @@ class DiasporaContact extends BaseRepository * Update or create a diaspora-contact entry via a probe array * * @param array $data Probe array - * @return Entity\DiasporaContact - * @throws \Exception + * @throws Exception */ - public function updateFromProbeArray(array $data): Entity\DiasporaContact + public function updateFromProbeArray(array $data): DiasporaContactEntity { if (empty($data['url'])) { - throw new \InvalidArgumentException('Missing url key in Diaspora probe data array'); + throw new InvalidArgumentException('Missing url key in Diaspora probe data array'); } if (empty($data['guid'])) { - throw new \InvalidArgumentException('Missing guid key in Diaspora probe data array'); + throw new InvalidArgumentException('Missing guid key in Diaspora probe data array'); } if (empty($data['pubkey'])) { - throw new \InvalidArgumentException('Missing pubkey key in Diaspora probe data array'); + throw new InvalidArgumentException('Missing pubkey key in Diaspora probe data array'); } $uriId = ItemURI::insert(['uri' => $data['url'], 'guid' => $data['guid']]); $contact = Contact::getByUriId($uriId, ['id', 'created']); $apcontact = APContact::getByURL($data['url'], false); + if (!empty($apcontact)) { $interacting_count = $apcontact['followers_count']; $interacted_count = $apcontact['following_count']; @@ -250,10 +243,10 @@ class DiasporaContact extends BaseRepository $DiasporaContact = $this->factory->createfromProbeData( $data, $uriId, - new \DateTime($contact['created'] ?? 'now', new \DateTimeZone('UTC')), + new DateTime($contact['created'] ?? 'now', new DateTimeZone('UTC')), $interacting_count ?? 0, - $interacted_count ?? 0, - $post_count ?? 0 + $interacted_count ?? 0, + $post_count ?? 0 ); $DiasporaContact = $this->save($DiasporaContact); @@ -269,7 +262,7 @@ class DiasporaContact extends BaseRepository * @param string $guid Hexadecimal string guid * * @return string the contact url or null - * @throws \Exception + * @throws Exception */ public function getUrlByGuid(string $guid): ?string { diff --git a/src/Security/BasicAuth.php b/src/Security/BasicAuth.php index fe89bf452e..52a0f75026 100644 --- a/src/Security/BasicAuth.php +++ b/src/Security/BasicAuth.php @@ -11,6 +11,7 @@ use Exception; use Friendica\Core\Hook; use Friendica\Database\DBA; use Friendica\DI; +use Friendica\Event\ArrayFilterEvent; use Friendica\Model\User; use Friendica\Network\HTTPException\UnauthorizedException; @@ -136,12 +137,16 @@ class BasicAuth 'user_record' => null, ]; - /* - * An addon indicates successful login by setting 'authenticated' to non-zero value and returning a user record - * Addons should never set 'authenticated' except to indicate success - as hooks may be chained - * and later addons should not interfere with an earlier one that succeeded. - */ - Hook::callAll('authenticate', $addon_auth); + $eventDispatcher = DI::eventDispatcher(); + + /** + * An addon indicates successful login by setting 'authenticated' to non-zero value and returning a user record + * Addons should never set 'authenticated' except to indicate success - as hooks may be chained + * and later addons should not interfere with an earlier one that succeeded. + */ + $addon_auth = $eventDispatcher->dispatch( + new ArrayFilterEvent(ArrayFilterEvent::ACCOUNT_AUTHENTICATE, $addon_auth), + )->getArray(); if ($addon_auth['authenticated'] && !empty($addon_auth['user_record'])) { $record = $addon_auth['user_record']; diff --git a/src/Security/PermissionSet/Repository/PermissionSet.php b/src/Security/PermissionSet/Repository/PermissionSet.php index ec3e2745ec..42b6219e9c 100644 --- a/src/Security/PermissionSet/Repository/PermissionSet.php +++ b/src/Security/PermissionSet/Repository/PermissionSet.php @@ -15,9 +15,9 @@ use Friendica\Model\Circle; use Friendica\Network\HTTPException\NotFoundException; use Friendica\Security\PermissionSet\Exception\PermissionSetNotFoundException; use Friendica\Security\PermissionSet\Exception\PermissionSetPersistenceException; -use Friendica\Security\PermissionSet\Factory; -use Friendica\Security\PermissionSet\Collection; -use Friendica\Security\PermissionSet\Entity; +use Friendica\Security\PermissionSet\Factory\PermissionSet as PermissionSetFactory; +use Friendica\Security\PermissionSet\Collection\PermissionSets as PermissionSetsCollection; +use Friendica\Security\PermissionSet\Entity\PermissionSet as PermissionSetEntity; use Friendica\Util\ACLFormatter; use Psr\Log\LoggerInterface; @@ -26,7 +26,7 @@ class PermissionSet extends BaseRepository /** @var int Virtual permission set id for public permission */ const PUBLIC = 0; - /** @var Factory\PermissionSet */ + /** @var PermissionSetFactory */ protected $factory; protected static $table_name = 'permissionset'; @@ -34,7 +34,7 @@ class PermissionSet extends BaseRepository /** @var ACLFormatter */ private $aclFormatter; - public function __construct(Database $database, LoggerInterface $logger, Factory\PermissionSet $factory, ACLFormatter $aclFormatter) + public function __construct(Database $database, LoggerInterface $logger, PermissionSetFactory $factory, ACLFormatter $aclFormatter) { parent::__construct($database, $logger, $factory); @@ -42,34 +42,28 @@ class PermissionSet extends BaseRepository } /** - * @param array $condition - * @param array $params - * - * @return Entity\PermissionSet * @throws NotFoundException * @throws Exception */ - private function selectOne(array $condition, array $params = []): Entity\PermissionSet + private function selectOne(array $condition, array $params = []): PermissionSetEntity { - return parent::_selectOne($condition, $params); + $fields = parent::_selectFirstRowAsArray($condition, $params); + + return $this->factory->createFromTableRow($fields); } /** * @throws Exception */ - private function select(array $condition, array $params = []): Collection\PermissionSets + private function select(array $condition, array $params = []): PermissionSetsCollection { - return new Collection\PermissionSets(parent::_select($condition, $params)->getArrayCopy()); + return new PermissionSetsCollection(parent::_select($condition, $params)->getArrayCopy()); } /** * Converts a given PermissionSet into a DB compatible row array - * - * @param Entity\PermissionSet $permissionSet - * - * @return array */ - protected function convertToTableRow(Entity\PermissionSet $permissionSet): array + protected function convertToTableRow(PermissionSetEntity $permissionSet): array { return [ 'uid' => $permissionSet->uid, @@ -83,12 +77,11 @@ class PermissionSet extends BaseRepository /** * @param int $id A PermissionSet table row id or self::PUBLIC * @param int $uid The owner of the PermissionSet - * @return Entity\PermissionSet * * @throws PermissionSetNotFoundException * @throws PermissionSetPersistenceException */ - public function selectOneById(int $id, int $uid): Entity\PermissionSet + public function selectOneById(int $id, int $uid): PermissionSetEntity { if ($id === self::PUBLIC) { return $this->factory->createFromString($uid); @@ -109,11 +102,9 @@ class PermissionSet extends BaseRepository * @param int $cid Contact id of the visitor * @param int $uid User id whom the items belong, used for ownership check. * - * @return Collection\PermissionSets - * * @throws PermissionSetPersistenceException */ - public function selectByContactId(int $cid, int $uid): Collection\PermissionSets + public function selectByContactId(int $cid, int $uid): PermissionSetsCollection { try { $cdata = Contact::getPublicAndUserContactID($cid, $uid); @@ -128,8 +119,8 @@ class PermissionSet extends BaseRepository $circle_ids = []; if (!empty($user_contact_str) && $this->db->exists('contact', [ - 'id' => $cid, - 'uid' => $uid, + 'id' => $cid, + 'uid' => $uid, 'blocked' => false ])) { $circle_ids = Circle::getIdsByContactId($cid); @@ -162,16 +153,14 @@ class PermissionSet extends BaseRepository * * @param int $uid * - * @return Entity\PermissionSet - * * @throws PermissionSetPersistenceException */ - public function selectDefaultForUser(int $uid): Entity\PermissionSet + public function selectDefaultForUser(int $uid): PermissionSetEntity { try { $self_contact = Contact::selectFirst(['id'], ['uid' => $uid, 'self' => true]); } catch (Exception $exception) { - throw new PermissionSetPersistenceException(sprintf('Cannot select Contact for user %d', $uid)); + throw new PermissionSetPersistenceException(sprintf('Cannot select Contact for user %d', $uid), $exception); } if (!$this->db->isResult($self_contact)) { @@ -188,10 +177,8 @@ class PermissionSet extends BaseRepository * Fetch the public PermissionSet * * @param int $uid - * - * @return Entity\PermissionSet */ - public function selectPublicForUser(int $uid): Entity\PermissionSet + public function selectPublicForUser(int $uid): PermissionSetEntity { return $this->factory->createFromString($uid, '', '', '', '', self::PUBLIC); } @@ -199,13 +186,9 @@ class PermissionSet extends BaseRepository /** * Selects or creates a PermissionSet based on its fields * - * @param Entity\PermissionSet $permissionSet - * - * @return Entity\PermissionSet - * * @throws PermissionSetPersistenceException */ - public function selectOrCreate(Entity\PermissionSet $permissionSet): Entity\PermissionSet + public function selectOrCreate(PermissionSetEntity $permissionSet): PermissionSetEntity { if ($permissionSet->id) { return $permissionSet; @@ -226,13 +209,9 @@ class PermissionSet extends BaseRepository } /** - * @param Entity\PermissionSet $permissionSet - * - * @return Entity\PermissionSet - * * @throws PermissionSetPersistenceException */ - public function save(Entity\PermissionSet $permissionSet): Entity\PermissionSet + public function save(PermissionSetEntity $permissionSet): PermissionSetEntity { // Don't save/update the common public PermissionSet if ($permissionSet->isPublic()) { diff --git a/src/Security/Security.php b/src/Security/Security.php index 6b0deb75ec..f04af42061 100644 --- a/src/Security/Security.php +++ b/src/Security/Security.php @@ -45,25 +45,25 @@ class Security return true; } elseif ($verified === 1) { return false; + } + + $user = User::getById($owner); + if (!$user || $user['blockwall']) { + $verified = 1; + return false; + } + + $contact = Contact::getById($cid); + if (!is_array($contact) || $contact['blocked'] || $contact['readonly'] || $contact['pending']) { + $verified = 1; + return false; + } + + if (in_array($contact['rel'], [Contact::SHARING, Contact::FRIEND]) || ($user['page-flags'] == User::PAGE_FLAGS_COMMUNITY)) { + $verified = 2; + return true; } else { - $user = User::getById($owner); - if (!$user || $user['blockwall']) { - $verified = 1; - return false; - } - - $contact = Contact::getById($cid); - if ($contact || $contact['blocked'] || $contact['readonly'] || $contact['pending']) { - $verified = 1; - return false; - } - - if (in_array($contact['rel'], [Contact::SHARING, Contact::FRIEND]) || ($user['page-flags'] == User::PAGE_FLAGS_COMMUNITY)) { - $verified = 2; - return true; - } else { - $verified = 1; - } + $verified = 1; } } @@ -79,33 +79,25 @@ class Security */ public static function getPermissionsSQLByUserId(int $owner_id, bool $accessible = false) { - $local_user = DI::userSession()->getLocalUserId(); + $local_user = DI::userSession()->getLocalUserId(); $remote_contact = DI::userSession()->getRemoteContactID($owner_id); - $acc_sql = ''; + $acc_sql = ''; if ($accessible) { $acc_sql = ' OR `accessible`'; } - /* - * Construct permissions - * - * default permissions - anonymous user - */ + // Construct permissions: default permissions - anonymous user $sql = " AND (allow_cid = '' AND allow_gid = '' AND deny_cid = '' AND deny_gid = ''" . $acc_sql . ") "; - /* - * Profile owner - everything is visible - */ if ($local_user && $local_user == $owner_id) { + // Profile owner - everything is visible $sql = ''; - /* - * Authenticated visitor. Load the circles the visitor belongs to. - */ } elseif ($remote_contact) { + // Authenticated visitor. Load the circles the visitor belongs to. $circleIds = '<<>>'; // should be impossible to match foreach (Circle::getIdsByContactId($remote_contact) as $circleId) { diff --git a/src/User/Settings/Repository/UserGServer.php b/src/User/Settings/Repository/UserGServer.php index dd097c7af2..016f2ea70d 100644 --- a/src/User/Settings/Repository/UserGServer.php +++ b/src/User/Settings/Repository/UserGServer.php @@ -8,28 +8,27 @@ namespace Friendica\User\Settings\Repository; use Exception; -use Friendica\BaseCollection; -use Friendica\BaseEntity; +use Friendica\BaseRepository; use Friendica\Content\Pager; use Friendica\Database\Database; use Friendica\Federation\Repository\GServer; use Friendica\Network\HTTPException\InternalServerErrorException; use Friendica\Network\HTTPException\NotFoundException; -use Friendica\User\Settings\Collection; -use Friendica\User\Settings\Entity; -use Friendica\User\Settings\Factory; +use Friendica\User\Settings\Collection\UserGServers as UserGServersCollection; +use Friendica\User\Settings\Entity\UserGServer as UserGServerEntity; +use Friendica\User\Settings\Factory\UserGServer as UserGServerFactory; use Psr\Log\LoggerInterface; -class UserGServer extends \Friendica\BaseRepository +class UserGServer extends BaseRepository { protected static $table_name = 'user-gserver'; - /** @var Factory\UserGServer */ + /** @var UserGServerFactory */ protected $factory; /** @var GServer */ protected $gserverRepository; - public function __construct(GServer $gserverRepository, Database $database, LoggerInterface $logger, Factory\UserGServer $factory) + public function __construct(GServer $gserverRepository, Database $database, LoggerInterface $logger, UserGServerFactory $factory) { parent::__construct($database, $logger, $factory); @@ -39,12 +38,9 @@ class UserGServer extends \Friendica\BaseRepository /** * Returns an existing UserGServer entity or create one on the fly * - * @param int $uid - * @param int $gsid * @param bool $hydrate Populate the related GServer entity - * @return Entity\UserGServer */ - public function getOneByUserAndServer(int $uid, int $gsid, bool $hydrate = true): Entity\UserGServer + public function getOneByUserAndServer(int $uid, int $gsid, bool $hydrate = true): UserGServerEntity { try { return $this->selectOneByUserAndServer($uid, $gsid, $hydrate); @@ -54,18 +50,15 @@ class UserGServer extends \Friendica\BaseRepository } /** - * @param int $uid - * @param int $gsid * @param bool $hydrate Populate the related GServer entity - * @return Entity\UserGServer * @throws NotFoundException */ - public function selectOneByUserAndServer(int $uid, int $gsid, bool $hydrate = true): Entity\UserGServer + public function selectOneByUserAndServer(int $uid, int $gsid, bool $hydrate = true): UserGServerEntity { return $this->_selectOne(['uid' => $uid, 'gsid' => $gsid], [], $hydrate); } - public function save(Entity\UserGServer $userGServer): Entity\UserGServer + public function save(UserGServerEntity $userGServer): UserGServerEntity { $fields = [ 'uid' => $userGServer->uid, @@ -78,7 +71,7 @@ class UserGServer extends \Friendica\BaseRepository return $userGServer; } - public function selectByUserWithPagination(int $uid, Pager $pager): Collection\UserGServers + public function selectByUserWithPagination(int $uid, Pager $pager): UserGServersCollection { return $this->_select(['uid' => $uid], ['limit' => [$pager->getStart(), $pager->getItemsPerPage()]]); } @@ -94,20 +87,18 @@ class UserGServer extends \Friendica\BaseRepository } /** - * @param Entity\UserGServer $userGServer - * @return bool * @throws InternalServerErrorException in case the underlying storage cannot delete the record */ - public function delete(Entity\UserGServer $userGServer): bool + public function delete(UserGServerEntity $userGServer): bool { try { return $this->db->delete(self::$table_name, ['uid' => $userGServer->uid, 'gsid' => $userGServer->gsid]); - } catch (\Exception $exception) { + } catch (Exception $exception) { throw new InternalServerErrorException('Cannot delete the UserGServer', $exception); } } - protected function _selectOne(array $condition, array $params = [], bool $hydrate = true): BaseEntity + protected function _selectOne(array $condition, array $params = [], bool $hydrate = true): UserGServerEntity { $fields = $this->db->selectFirst(static::$table_name, [], $condition, $params); if (!$this->db->isResult($fields)) { @@ -118,16 +109,13 @@ class UserGServer extends \Friendica\BaseRepository } /** - * @param array $condition - * @param array $params - * @return Collection\UserGServers * @throws Exception */ - protected function _select(array $condition, array $params = [], bool $hydrate = true): BaseCollection + protected function _select(array $condition, array $params = [], bool $hydrate = true): UserGServersCollection { $rows = $this->db->selectToArray(static::$table_name, [], $condition, $params); - $Entities = new Collection\UserGServers(); + $Entities = new UserGServersCollection(); foreach ($rows as $fields) { $Entities[] = $this->factory->createFromTableRow($fields, $hydrate ? $this->gserverRepository->selectOneById($fields['gsid']) : null); } @@ -135,7 +123,7 @@ class UserGServer extends \Friendica\BaseRepository return $Entities; } - public function listIgnoredByUser(int $uid): Collection\UserGServers + public function listIgnoredByUser(int $uid): UserGServersCollection { return $this->_select(['uid' => $uid, 'ignored' => 1], [], false); } diff --git a/src/Util/Crypto.php b/src/Util/Crypto.php index 084ae567ea..084bd21808 100644 --- a/src/Util/Crypto.php +++ b/src/Util/Crypto.php @@ -225,19 +225,18 @@ class Crypto } /** - * * Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/crypto.php * * @param array $data ['iv' => $iv, 'key' => $key, 'alg' => $alg, 'data' => $data] * @param string $prvkey The private key used for decryption. * - * @return string|boolean The decrypted string or false on failure. + * @return string|false The decrypted string or false on failure. * @throws \Exception */ public static function unencapsulate(array $data, $prvkey) { if (!$data) { - return; + return false; } $alg = $data['alg'] ?? 'aes256cbc'; diff --git a/src/Util/HTTPSignature.php b/src/Util/HTTPSignature.php index bb5d8c6b5a..a779a59a86 100644 --- a/src/Util/HTTPSignature.php +++ b/src/Util/HTTPSignature.php @@ -7,6 +7,7 @@ namespace Friendica\Util; +use Exception; use Friendica\Core\Protocol; use Friendica\Database\Database; use Friendica\Database\DBA; @@ -213,7 +214,7 @@ class HTTPSignature $headers = []; foreach ($matches as $match) { - $headers[$match[1]] = trim($match[2] ?: $match[3], '"'); + $headers[$match[1]] = trim((string) $match[2], '"'); } // if the header is encrypted, decrypt with (default) site private key and continue @@ -247,7 +248,7 @@ class HTTPSignature private static function decryptSigheader(array $headers, string $prvkey): string { if (!empty($headers['iv']) && !empty($headers['key']) && !empty($headers['data'])) { - return Crypto::unencapsulate($headers, $prvkey); + return (string) Crypto::unencapsulate($headers, $prvkey); } return ''; @@ -537,14 +538,12 @@ class HTTPSignature if (!empty($uid)) { $owner = User::getOwnerDataById($uid); - if (!$owner) { - return; - } } else { $owner = User::getSystemAccount(); - if (!$owner) { - return; - } + } + + if (!$owner) { + throw new Exception('Could not find owner for uid ' . $uid); } if (!empty($owner['uprvkey'])) { diff --git a/src/Util/JsonLD.php b/src/Util/JsonLD.php index 3301ca1b40..afca8d79ba 100644 --- a/src/Util/JsonLD.php +++ b/src/Util/JsonLD.php @@ -243,14 +243,11 @@ class JsonLD return json_decode(json_encode($json, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)); } + /** * Fetches an element array from a JSON array * - * @param $array - * @param $element - * @param $key - * - * @return array fetched element + * @return array|null fetched element or null */ public static function fetchElementArray($array, $element, $key = null, $type = null, $type_value = null) { diff --git a/src/Util/Network.php b/src/Util/Network.php index 1584faea3f..2172c5a177 100644 --- a/src/Util/Network.php +++ b/src/Util/Network.php @@ -7,8 +7,8 @@ namespace Friendica\Util; -use Friendica\Core\Hook; use Friendica\DI; +use Friendica\Event\ArrayFilterEvent; use Friendica\Model\Contact; use Friendica\Network\HTTPClient\Client\HttpClientAccept; use Friendica\Network\HTTPClient\Client\HttpClientOptions; @@ -302,7 +302,11 @@ class Network $avatar['url'] = ''; $avatar['success'] = false; - Hook::callAll('avatar_lookup', $avatar); + $eventDispatcher = DI::eventDispatcher(); + + $avatar = $eventDispatcher->dispatch( + new ArrayFilterEvent(ArrayFilterEvent::AVATAR_LOOKUP, $avatar), + )->getArray(); if (! $avatar['success']) { $avatar['url'] = DI::baseUrl() . Contact::DEFAULT_AVATAR_PHOTO; diff --git a/src/Util/ReversedFileReader.php b/src/Util/ReversedFileReader.php index 779933cbc6..57800ab561 100644 --- a/src/Util/ReversedFileReader.php +++ b/src/Util/ReversedFileReader.php @@ -26,13 +26,13 @@ class ReversedFileReader implements \Iterator /** @var int */ private $pos = -1; - /** @var array */ + /** @var array|null */ private $buffer = null; /** @var int */ private $key = -1; - /** @var string */ + /** @var string|null */ private $value = null; /** @@ -53,6 +53,7 @@ class ReversedFileReader implements \Iterator $this->buffer = null; $this->key = -1; $this->value = null; + return $this; } diff --git a/src/Util/Strings.php b/src/Util/Strings.php index efa0994ec3..9dbf361761 100644 --- a/src/Util/Strings.php +++ b/src/Util/Strings.php @@ -209,7 +209,7 @@ class Strings { // If this method is called for an infinite (== unlimited) amount of bytes: if ($bytes == INF) { - return INF; + return 'INF'; } $units = ['B', 'KiB', 'MiB', 'GiB', 'TiB']; @@ -509,7 +509,7 @@ class Strings function ($matches) use ($blocks) { $return = $matches[0]; if (isset($blocks[intval($matches[1])])) { - $return = $blocks[$matches[1]]; + $return = $blocks[intval($matches[1])]; } return $return; }, diff --git a/src/Util/Temporal.php b/src/Util/Temporal.php index 3e9ea157d5..cc9baa53a9 100644 --- a/src/Util/Temporal.php +++ b/src/Util/Temporal.php @@ -405,7 +405,7 @@ class Temporal */ public static function getDaysInMonth(int $y, int $m): int { - return date('t', mktime(0, 0, 0, $m, 1, $y)); + return (int) date('t', mktime(0, 0, 0, $m, 1, $y)); } /** diff --git a/src/Util/XML.php b/src/Util/XML.php index cf5689c361..4c099fb78e 100644 --- a/src/Util/XML.php +++ b/src/Util/XML.php @@ -184,7 +184,7 @@ class XML * @param integer $recursion_depth recursion counter for internal use - default 0 * internal use, recursion counter * - * @return array | string The array from the xml element or the string + * @return array|string|null The array from the xml element or the string */ public static function elementToArray($xml_element, int &$recursion_depth = 0) { diff --git a/static/defaults.config.php b/static/defaults.config.php index d821ff1813..ada2aa3eac 100644 --- a/static/defaults.config.php +++ b/static/defaults.config.php @@ -126,6 +126,11 @@ return [ // Display "Emoji Only" posts in big. 'big_emojis' => true, + // basepath (String) + // Absolute file path to your Friendica install + // Examples: /var/www, /home/user/friendica... + 'basepath' => '', + // bulk_delivery (Boolean) // Delivers AP messages in a bulk (experimental) 'bulk_delivery' => false, @@ -334,7 +339,8 @@ return [ 'lock_driver' => '', // logger_config (String) - // Sets the logging adapter of Friendica globally (monolog, syslog, stream) + // Sets the logging adapter of Friendica globally (syslog, stream) + // @deprecated 2025.02 The value `monolog` is deprecated, please use `stream` or `syslog` instead. 'logger_config' => 'stream', // syslog_flags (Integer) @@ -576,6 +582,12 @@ return [ // Transmit pending events upon accepted contact request for groups 'transmit_pending_events' => false, + // url (String) + // The absolute URL used to access your Friendica node. It should include the scheme, the domain name, and the + // sub-folder if any. Used by command-line processes to send correct links to your Friendica server. + // Example: https://example.com/friendica + 'url' => '', + // username_min_length (Integer) // The minimum character length a username can be. // This length is checked once the username has been trimmed and multiple spaces have been collapsed into one. diff --git a/static/dependencies.config.php b/static/dependencies.config.php index 5924ae512e..644cb5f765 100644 --- a/static/dependencies.config.php +++ b/static/dependencies.config.php @@ -171,11 +171,24 @@ return (function(string $basepath, array $getVars, array $serverVars, array $coo ], \Friendica\Core\Logger\LoggerManager::class => [ 'substitutions' => [ - \Friendica\Core\Logger\Factory\LoggerFactory::class => \Friendica\Core\Logger\Factory\LegacyLoggerFactory::class, + \Friendica\Core\Logger\Factory\LoggerFactory::class => \Friendica\Core\Logger\Factory\DelegatingLoggerFactory::class, ], ], \Friendica\Core\Logger\Factory\LoggerFactory::class => [ - 'instanceOf' => \Friendica\Core\Logger\Factory\LegacyLoggerFactory::class, + 'instanceOf' => \Friendica\Core\Logger\Factory\DelegatingLoggerFactory::class, + 'call' => [ + ['registerFactory', ['stream', [Dice::INSTANCE => '$StreamLoggerFactory']]], + ['registerFactory', ['syslog', [Dice::INSTANCE => '$SyslogLoggerFactory']]], + ], + ], + '$StreamLoggerFactory' => [ + 'instanceOf' => \Friendica\Core\Logger\Factory\StreamLoggerFactory::class, + 'substitutions' => [ + \Friendica\Core\Logger\Util\FileSystemUtil::class => \Friendica\Core\Logger\Util\FileSystem::class, + ], + ], + '$SyslogLoggerFactory' => [ + 'instanceOf' => \Friendica\Core\Logger\Factory\SyslogLoggerFactory::class, ], \Friendica\Core\Logger\Type\SyslogLogger::class => [ 'instanceOf' => \Friendica\Core\Logger\Factory\SyslogLogger::class, diff --git a/static/routes.config.php b/static/routes.config.php index 88e642c27a..4c7b6949ec 100644 --- a/static/routes.config.php +++ b/static/routes.config.php @@ -642,7 +642,8 @@ return [ ], ], - '/stats' => [Module\Stats::class, [R::GET]], + '/stats' => [Module\Stats::class, [R::GET]], + '/stats/caching' => [Module\StatsCaching::class, [R::GET]], '/network' => [ '[/{content}]' => [Module\Conversation\Network::class, [R::GET]], diff --git a/tests/ApiTestCase.php b/tests/ApiTestCase.php index 6124bf9f32..45a84c280a 100644 --- a/tests/ApiTestCase.php +++ b/tests/ApiTestCase.php @@ -11,6 +11,7 @@ use Friendica\Capabilities\ICanCreateResponses; use Friendica\Core\Addon\AddonHelper; use Friendica\Core\Config\Capability\IManageConfigValues; use Friendica\Core\Hook; +use Friendica\Core\Hooks\HookEventBridge; use Friendica\DI; use Friendica\Module\Special\HTTPException; use Friendica\Security\Authentication; @@ -159,6 +160,13 @@ abstract class ApiTestCase extends FixtureTestCase ; DI::init($this->dice); + /** @var \Friendica\Event\EventDispatcher */ + $eventDispatcher = DI::eventDispatcher(); + + foreach (HookEventBridge::getStaticSubscribedEvents() as $eventName => $methodName) { + $eventDispatcher->addListener($eventName, [HookEventBridge::class, $methodName]); + } + $this->httpExceptionMock = $this->dice->create(HTTPException::class); AuthTestConfig::$authenticated = true; diff --git a/tests/CacheLockTestCase.php b/tests/CacheLockTestCase.php new file mode 100644 index 0000000000..1599391ece --- /dev/null +++ b/tests/CacheLockTestCase.php @@ -0,0 +1,26 @@ +getCache()->getStats()), array_keys($this->instance->getCacheStats())); + } +} diff --git a/tests/LockTestCase.php b/tests/LockTestCase.php index 9ce86497b7..92abf0ca7b 100644 --- a/tests/LockTestCase.php +++ b/tests/LockTestCase.php @@ -8,21 +8,17 @@ namespace Friendica\Test; use Friendica\Core\Lock\Capability\ICanLock; -use Friendica\Test\MockedTestCase; abstract class LockTestCase extends MockedTestCase { /** - * @var int Start time of the mock (used for time operations) + * Start time of the mock (used for time operations) */ - protected $startTime = 1417011228; + protected int $startTime = 1417011228; + protected ICanLock $instance; - /** - * @var ICanLock - */ - protected $instance; + abstract protected function getInstance(): ICanLock; - abstract protected function getInstance(); protected function setUp(): void { @@ -205,4 +201,6 @@ abstract class LockTestCase extends MockedTestCase self::assertFalse($this->instance->isLocked('wrongLock')); self::assertFalse($this->instance->release('wrongLock')); } + + } diff --git a/tests/Unit/Core/Hooks/HookEventBridgeTest.php b/tests/Unit/Core/Hooks/HookEventBridgeTest.php index cdd3e0d61b..436e1435bc 100644 --- a/tests/Unit/Core/Hooks/HookEventBridgeTest.php +++ b/tests/Unit/Core/Hooks/HookEventBridgeTest.php @@ -32,15 +32,45 @@ class HookEventBridgeTest extends TestCase ArrayFilterEvent::NAV_INFO => 'onArrayFilterEvent', ArrayFilterEvent::FEATURE_ENABLED => 'onArrayFilterEvent', ArrayFilterEvent::FEATURE_GET => 'onArrayFilterEvent', - ArrayFilterEvent::POST_LOCAL_START => 'onArrayFilterEvent', - ArrayFilterEvent::POST_LOCAL => 'onArrayFilterEvent', - ArrayFilterEvent::POST_LOCAL_END => 'onArrayFilterEvent', + ArrayFilterEvent::PERMISSION_TOOLTIP_CONTENT => 'onPermissionTooltipContentEvent', + ArrayFilterEvent::INSERT_POST_LOCAL_START => 'onArrayFilterEvent', + ArrayFilterEvent::INSERT_POST_LOCAL => 'onInsertPostLocalEvent', + ArrayFilterEvent::INSERT_POST_LOCAL_END => 'onInsertPostLocalEndEvent', + ArrayFilterEvent::INSERT_POST_REMOTE => 'onArrayFilterEvent', + ArrayFilterEvent::INSERT_POST_REMOTE_END => 'onArrayFilterEvent', + ArrayFilterEvent::PREPARE_POST_START => 'onPreparePostStartEvent', + ArrayFilterEvent::PREPARE_POST_FILTER_CONTENT => 'onArrayFilterEvent', + ArrayFilterEvent::PREPARE_POST => 'onArrayFilterEvent', + ArrayFilterEvent::PREPARE_POST_END => 'onArrayFilterEvent', ArrayFilterEvent::PHOTO_UPLOAD_FORM => 'onArrayFilterEvent', + ArrayFilterEvent::PHOTO_UPLOAD_START => 'onPhotoUploadStartEvent', + ArrayFilterEvent::PHOTO_UPLOAD => 'onArrayFilterEvent', + ArrayFilterEvent::PHOTO_UPLOAD_END => 'onPhotoUploadEndEvent', ArrayFilterEvent::NETWORK_TO_NAME => 'onArrayFilterEvent', + ArrayFilterEvent::NETWORK_CONTENT_START => 'onArrayFilterEvent', + ArrayFilterEvent::NETWORK_CONTENT_TABS => 'onArrayFilterEvent', + ArrayFilterEvent::PARSE_LINK => 'onArrayFilterEvent', ArrayFilterEvent::CONVERSATION_START => 'onArrayFilterEvent', + ArrayFilterEvent::FETCH_ITEM_BY_LINK => 'onArrayFilterEvent', + ArrayFilterEvent::ITEM_TAGGED => 'onArrayFilterEvent', ArrayFilterEvent::DISPLAY_ITEM => 'onArrayFilterEvent', + ArrayFilterEvent::CACHE_ITEM => 'onArrayFilterEvent', + ArrayFilterEvent::CHECK_ITEM_NOTIFICATION => 'onArrayFilterEvent', + ArrayFilterEvent::ENOTIFY => 'onArrayFilterEvent', + ArrayFilterEvent::ENOTIFY_STORE => 'onArrayFilterEvent', + ArrayFilterEvent::ENOTIFY_MAIL => 'onArrayFilterEvent', + ArrayFilterEvent::DETECT_LANGUAGES => 'onArrayFilterEvent', ArrayFilterEvent::RENDER_LOCATION => 'onArrayFilterEvent', ArrayFilterEvent::ITEM_PHOTO_MENU => 'onArrayFilterEvent', + ArrayFilterEvent::DIRECTORY_ITEM => 'onArrayFilterEvent', + ArrayFilterEvent::CONTACT_PHOTO_MENU => 'onArrayFilterEvent', + ArrayFilterEvent::PROFILE_SIDEBAR_ENTRY => 'onProfileSidebarEntryEvent', + ArrayFilterEvent::PROFILE_SIDEBAR => 'onArrayFilterEvent', + ArrayFilterEvent::PROFILE_TABS => 'onArrayFilterEvent', + ArrayFilterEvent::PROFILE_SETTINGS_FORM => 'onArrayFilterEvent', + ArrayFilterEvent::PROFILE_SETTINGS_POST => 'onArrayFilterEvent', + ArrayFilterEvent::MODERATION_USERS_TABS => 'onArrayFilterEvent', + ArrayFilterEvent::ACL_LOOKUP_END => 'onArrayFilterEvent', ArrayFilterEvent::OEMBED_FETCH_END => 'onOembedFetchEndEvent', ArrayFilterEvent::PAGE_INFO => 'onArrayFilterEvent', ArrayFilterEvent::SMILEY_LIST => 'onArrayFilterEvent', @@ -51,11 +81,34 @@ class HookEventBridgeTest extends TestCase ArrayFilterEvent::PROTOCOL_SUPPORTS_FOLLOW => 'onArrayFilterEvent', ArrayFilterEvent::PROTOCOL_SUPPORTS_REVOKE_FOLLOW => 'onArrayFilterEvent', ArrayFilterEvent::PROTOCOL_SUPPORTS_PROBE => 'onArrayFilterEvent', + ArrayFilterEvent::FOLLOW_CONTACT => 'onArrayFilterEvent', + ArrayFilterEvent::UNFOLLOW_CONTACT => 'onArrayFilterEvent', + ArrayFilterEvent::REVOKE_FOLLOW_CONTACT => 'onArrayFilterEvent', + ArrayFilterEvent::BLOCK_CONTACT => 'onArrayFilterEvent', + ArrayFilterEvent::UNBLOCK_CONTACT => 'onArrayFilterEvent', + ArrayFilterEvent::EDIT_CONTACT_FORM => 'onArrayFilterEvent', + ArrayFilterEvent::EDIT_CONTACT_POST => 'onArrayFilterEvent', + ArrayFilterEvent::AVATAR_LOOKUP => 'onArrayFilterEvent', + ArrayFilterEvent::ACCOUNT_AUTHENTICATE => 'onArrayFilterEvent', + ArrayFilterEvent::ACCOUNT_REGISTER_FORM => 'onArrayFilterEvent', + ArrayFilterEvent::ACCOUNT_REGISTER_POST => 'onArrayFilterEvent', + ArrayFilterEvent::ACCOUNT_REGISTER => 'onAccountRegisterEvent', + ArrayFilterEvent::ACCOUNT_REMOVE => 'onAccountRemoveEvent', + ArrayFilterEvent::EVENT_CREATED => 'onEventCreatedEvent', + ArrayFilterEvent::EVENT_UPDATED => 'onEventUpdatedEvent', + ArrayFilterEvent::ADD_WORKER_TASK => 'onArrayFilterEvent', + ArrayFilterEvent::STORAGE_CONFIG => 'onArrayFilterEvent', + ArrayFilterEvent::STORAGE_INSTANCE => 'onArrayFilterEvent', + ArrayFilterEvent::DB_STRUCTURE_DEFINITION => 'onArrayFilterEvent', + ArrayFilterEvent::DB_VIEW_DEFINITION => 'onArrayFilterEvent', HtmlFilterEvent::HEAD => 'onHtmlFilterEvent', HtmlFilterEvent::FOOTER => 'onHtmlFilterEvent', HtmlFilterEvent::PAGE_HEADER => 'onHtmlFilterEvent', HtmlFilterEvent::PAGE_CONTENT_TOP => 'onHtmlFilterEvent', HtmlFilterEvent::PAGE_END => 'onHtmlFilterEvent', + HtmlFilterEvent::MOD_HOME_CONTENT => 'onHtmlFilterEvent', + HtmlFilterEvent::MOD_ABOUT_CONTENT => 'onHtmlFilterEvent', + HtmlFilterEvent::MOD_PROFILE_CONTENT => 'onHtmlFilterEvent', HtmlFilterEvent::JOT_TOOL => 'onHtmlFilterEvent', HtmlFilterEvent::CONTACT_BLOCK_END => 'onHtmlFilterEvent', ]; @@ -167,6 +220,155 @@ class HookEventBridgeTest extends TestCase HookEventBridge::onCollectRoutesEvent($event); } + public function testOnPermissionTooltipContentEventCallsHookWithCorrectValue(): void + { + $event = new ArrayFilterEvent(ArrayFilterEvent::PERMISSION_TOOLTIP_CONTENT, ['model' => ['uid' => -1]]); + + $reflectionProperty = new \ReflectionProperty(HookEventBridge::class, 'mockedCallHook'); + $reflectionProperty->setAccessible(true); + + $reflectionProperty->setValue(null, function (string $name, array $data): array { + $this->assertSame('lockview_content', $name); + $this->assertSame(['uid' => -1], $data); + + return ['uid' => 123]; + }); + + HookEventBridge::onPermissionTooltipContentEvent($event); + + $this->assertSame( + ['model' => ['uid' => 123]], + $event->getArray(), + ); + } + + public function testOnInsertPostLocalEventCallsHookWithCorrectValue(): void + { + $event = new ArrayFilterEvent(ArrayFilterEvent::INSERT_POST_LOCAL, ['item' => ['id' => -1]]); + + $reflectionProperty = new \ReflectionProperty(HookEventBridge::class, 'mockedCallHook'); + $reflectionProperty->setAccessible(true); + + $reflectionProperty->setValue(null, function (string $name, array $data): array { + $this->assertSame('post_local', $name); + $this->assertSame(['id' => -1], $data); + + return ['id' => 123]; + }); + + HookEventBridge::onInsertPostLocalEvent($event); + + $this->assertSame( + ['item' => ['id' => 123]], + $event->getArray(), + ); + } + + public function testOnInsertPostLocalEndEventCallsHookWithCorrectValue(): void + { + $event = new ArrayFilterEvent(ArrayFilterEvent::INSERT_POST_LOCAL_END, ['item' => ['id' => -1]]); + + $reflectionProperty = new \ReflectionProperty(HookEventBridge::class, 'mockedCallHook'); + $reflectionProperty->setAccessible(true); + + $reflectionProperty->setValue(null, function (string $name, array $data): array { + $this->assertSame('post_local_end', $name); + $this->assertSame(['id' => -1], $data); + + return ['id' => 123]; + }); + + HookEventBridge::onInsertPostLocalEndEvent($event); + + $this->assertSame( + ['item' => ['id' => 123]], + $event->getArray(), + ); + } + + public function testOnPreparePostStartEventCallsHookWithCorrectValue(): void + { + $event = new ArrayFilterEvent(ArrayFilterEvent::PREPARE_POST_START, ['item' => ['id' => -1]]); + + $reflectionProperty = new \ReflectionProperty(HookEventBridge::class, 'mockedCallHook'); + $reflectionProperty->setAccessible(true); + + $reflectionProperty->setValue(null, function (string $name, array $data): array { + $this->assertSame('prepare_body_init', $name); + $this->assertSame(['id' => -1], $data); + + return ['id' => 123]; + }); + + HookEventBridge::onPreparePostStartEvent($event); + + $this->assertSame( + ['item' => ['id' => 123]], + $event->getArray(), + ); + } + + public function testOnPhotoUploadStartEventCallsHookWithCorrectValue(): void + { + $event = new ArrayFilterEvent(ArrayFilterEvent::PHOTO_UPLOAD_START, ['request' => ['album' => -1]]); + + $reflectionProperty = new \ReflectionProperty(HookEventBridge::class, 'mockedCallHook'); + $reflectionProperty->setAccessible(true); + + $reflectionProperty->setValue(null, function (string $name, array $data): array { + $this->assertSame('photo_post_init', $name); + $this->assertSame(['album' => -1], $data); + + return ['album' => 123]; + }); + + HookEventBridge::onPhotoUploadStartEvent($event); + + $this->assertSame( + ['request' => ['album' => 123]], + $event->getArray(), + ); + } + + public function testOnPhotoUploadEndEventCallsHookWithCorrectValue(): void + { + $event = new ArrayFilterEvent(ArrayFilterEvent::PHOTO_UPLOAD_END, ['id' => -1]); + + $reflectionProperty = new \ReflectionProperty(HookEventBridge::class, 'mockedCallHook'); + $reflectionProperty->setAccessible(true); + + $reflectionProperty->setValue(null, function (string $name, int $data): int { + $this->assertSame('photo_post_end', $name); + $this->assertSame(-1, $data); + + return 123; + }); + + HookEventBridge::onPhotoUploadEndEvent($event); + } + + public function testOnProfileSidebarEntryEventCallsHookWithCorrectValue(): void + { + $event = new ArrayFilterEvent(ArrayFilterEvent::PROFILE_SIDEBAR_ENTRY, ['profile' => ['uid' => 0, 'name' => 'original']]); + + $reflectionProperty = new \ReflectionProperty(HookEventBridge::class, 'mockedCallHook'); + $reflectionProperty->setAccessible(true); + + $reflectionProperty->setValue(null, function (string $name, array $data): array { + $this->assertSame('profile_sidebar_enter', $name); + $this->assertSame(['uid' => 0, 'name' => 'original'], $data); + + return ['uid' => 0, 'name' => 'changed']; + }); + + HookEventBridge::onProfileSidebarEntryEvent($event); + + $this->assertSame( + ['profile' => ['uid' => 0, 'name' => 'changed']], + $event->getArray(), + ); + } + public function testOnOembedFetchEndEventCallsHookWithCorrectValue(): void { $event = new ArrayFilterEvent(ArrayFilterEvent::OEMBED_FETCH_END, ['url' => 'original_url']); @@ -255,6 +457,74 @@ class HookEventBridgeTest extends TestCase ); } + public function testOnEventCreatedEventCallsHookWithCorrectValue(): void + { + $event = new ArrayFilterEvent(ArrayFilterEvent::EVENT_CREATED, ['event' => ['id' => 123]]); + + $reflectionProperty = new \ReflectionProperty(HookEventBridge::class, 'mockedCallHook'); + $reflectionProperty->setAccessible(true); + + $reflectionProperty->setValue(null, function (string $name, int $data): int { + $this->assertSame('event_created', $name); + $this->assertSame(123, $data); + + return 123; + }); + + HookEventBridge::onEventCreatedEvent($event); + } + + public function testOnAccountRegisterEventCallsHookWithCorrectValue(): void + { + $event = new ArrayFilterEvent(ArrayFilterEvent::ACCOUNT_REGISTER, ['uid' => 123]); + + $reflectionProperty = new \ReflectionProperty(HookEventBridge::class, 'mockedCallHook'); + $reflectionProperty->setAccessible(true); + + $reflectionProperty->setValue(null, function (string $name, int $data): int { + $this->assertSame('register_account', $name); + $this->assertSame(123, $data); + + return $data; + }); + + HookEventBridge::onAccountRegisterEvent($event); + } + + public function testOnAccountRemoveEventCallsHookWithCorrectValue(): void + { + $event = new ArrayFilterEvent(ArrayFilterEvent::ACCOUNT_REMOVE, ['user' => ['uid' => 123]]); + + $reflectionProperty = new \ReflectionProperty(HookEventBridge::class, 'mockedCallHook'); + $reflectionProperty->setAccessible(true); + + $reflectionProperty->setValue(null, function (string $name, array $data): array { + $this->assertSame('remove_user', $name); + $this->assertSame(['uid' => 123], $data); + + return $data; + }); + + HookEventBridge::onAccountRemoveEvent($event); + } + + public function testOnEventUpdatedEventCallsHookWithCorrectValue(): void + { + $event = new ArrayFilterEvent(ArrayFilterEvent::EVENT_UPDATED, ['event' => ['id' => 123]]); + + $reflectionProperty = new \ReflectionProperty(HookEventBridge::class, 'mockedCallHook'); + $reflectionProperty->setAccessible(true); + + $reflectionProperty->setValue(null, function (string $name, int $data): int { + $this->assertSame('event_updated', $name); + $this->assertSame(123, $data); + + return 123; + }); + + HookEventBridge::onEventUpdatedEvent($event); + } + public static function getArrayFilterEventData(): array { return [ @@ -263,21 +533,64 @@ class HookEventBridgeTest extends TestCase [ArrayFilterEvent::NAV_INFO, 'nav_info'], [ArrayFilterEvent::FEATURE_ENABLED, 'isEnabled'], [ArrayFilterEvent::FEATURE_GET, 'get'], - [ArrayFilterEvent::POST_LOCAL_START, 'post_local_start'], - [ArrayFilterEvent::POST_LOCAL, 'post_local'], - [ArrayFilterEvent::POST_LOCAL_END, 'post_local_end'], + [ArrayFilterEvent::INSERT_POST_LOCAL_START, 'post_local_start'], + [ArrayFilterEvent::INSERT_POST_REMOTE, 'post_remote'], + [ArrayFilterEvent::INSERT_POST_REMOTE_END, 'post_remote_end'], + [ArrayFilterEvent::PREPARE_POST_FILTER_CONTENT, 'prepare_body_content_filter'], + [ArrayFilterEvent::PREPARE_POST, 'prepare_body'], + [ArrayFilterEvent::PREPARE_POST_END, 'prepare_body_final'], [ArrayFilterEvent::PHOTO_UPLOAD_FORM, 'photo_upload_form'], + [ArrayFilterEvent::PHOTO_UPLOAD, 'photo_post_file'], [ArrayFilterEvent::NETWORK_TO_NAME, 'network_to_name'], + [ArrayFilterEvent::NETWORK_CONTENT_START, 'network_content_init'], + [ArrayFilterEvent::NETWORK_CONTENT_TABS, 'network_tabs'], + [ArrayFilterEvent::PARSE_LINK, 'parse_link'], [ArrayFilterEvent::CONVERSATION_START, 'conversation_start'], + [ArrayFilterEvent::FETCH_ITEM_BY_LINK, 'item_by_link'], + [ArrayFilterEvent::ITEM_TAGGED, 'tagged'], [ArrayFilterEvent::DISPLAY_ITEM, 'display_item'], + [ArrayFilterEvent::CACHE_ITEM, 'put_item_in_cache'], + [ArrayFilterEvent::CHECK_ITEM_NOTIFICATION, 'check_item_notification'], + [ArrayFilterEvent::ENOTIFY, 'enotify'], + [ArrayFilterEvent::ENOTIFY_STORE, 'enotify_store'], + [ArrayFilterEvent::ENOTIFY_MAIL, 'enotify_mail'], + [ArrayFilterEvent::DETECT_LANGUAGES, 'detect_languages'], [ArrayFilterEvent::RENDER_LOCATION, 'render_location'], [ArrayFilterEvent::ITEM_PHOTO_MENU, 'item_photo_menu'], + [ArrayFilterEvent::DIRECTORY_ITEM, 'directory_item'], + [ArrayFilterEvent::CONTACT_PHOTO_MENU, 'contact_photo_menu'], + [ArrayFilterEvent::PROFILE_SIDEBAR, 'profile_sidebar'], + [ArrayFilterEvent::PROFILE_TABS, 'profile_tabs'], + [ArrayFilterEvent::PROFILE_SETTINGS_FORM, 'profile_edit'], + [ArrayFilterEvent::PROFILE_SETTINGS_POST, 'profile_post'], + [ArrayFilterEvent::MODERATION_USERS_TABS, 'moderation_users_tabs'], + [ArrayFilterEvent::ACL_LOOKUP_END, 'acl_lookup_end'], [ArrayFilterEvent::PAGE_INFO, 'page_info_data'], [ArrayFilterEvent::SMILEY_LIST, 'smilie'], [ArrayFilterEvent::JOT_NETWORKS, 'jot_networks'], [ArrayFilterEvent::PROTOCOL_SUPPORTS_FOLLOW, 'support_follow'], [ArrayFilterEvent::PROTOCOL_SUPPORTS_REVOKE_FOLLOW, 'support_revoke_follow'], [ArrayFilterEvent::PROTOCOL_SUPPORTS_PROBE, 'support_probe'], + [ArrayFilterEvent::FOLLOW_CONTACT, 'follow'], + [ArrayFilterEvent::UNFOLLOW_CONTACT, 'unfollow'], + [ArrayFilterEvent::REVOKE_FOLLOW_CONTACT, 'revoke_follow'], + [ArrayFilterEvent::BLOCK_CONTACT, 'block'], + [ArrayFilterEvent::UNBLOCK_CONTACT, 'unblock'], + [ArrayFilterEvent::EDIT_CONTACT_FORM, 'contact_edit'], + [ArrayFilterEvent::EDIT_CONTACT_POST, 'contact_edit_post'], + [ArrayFilterEvent::AVATAR_LOOKUP, 'avatar_lookup'], + [ArrayFilterEvent::ACCOUNT_AUTHENTICATE, 'authenticate'], + [ArrayFilterEvent::ACCOUNT_REGISTER_FORM, 'register_form'], + [ArrayFilterEvent::ACCOUNT_REGISTER_POST, 'register_post'], + [ArrayFilterEvent::ACCOUNT_REGISTER, 'register_account'], + [ArrayFilterEvent::ACCOUNT_REMOVE, 'remove_user'], + [ArrayFilterEvent::EVENT_CREATED, 'event_created'], + [ArrayFilterEvent::EVENT_UPDATED, 'event_updated'], + [ArrayFilterEvent::ADD_WORKER_TASK, 'proc_run'], + [ArrayFilterEvent::STORAGE_CONFIG, 'storage_config'], + [ArrayFilterEvent::STORAGE_INSTANCE, 'storage_instance'], + [ArrayFilterEvent::DB_STRUCTURE_DEFINITION, 'dbstructure_definition'], + [ArrayFilterEvent::DB_VIEW_DEFINITION, 'dbview_definition'], ]; } @@ -310,6 +623,9 @@ class HookEventBridgeTest extends TestCase [HtmlFilterEvent::PAGE_HEADER, 'page_header'], [HtmlFilterEvent::PAGE_CONTENT_TOP, 'page_content_top'], [HtmlFilterEvent::PAGE_END, 'page_end'], + [HtmlFilterEvent::MOD_HOME_CONTENT, 'home_content'], + [HtmlFilterEvent::MOD_ABOUT_CONTENT, 'about_hook'], + [HtmlFilterEvent::MOD_PROFILE_CONTENT, 'profile_advanced'], [HtmlFilterEvent::JOT_TOOL, 'jot_tool'], [HtmlFilterEvent::CONTACT_BLOCK_END, 'contact_block_end'], ]; diff --git a/tests/Unit/Core/Logger/Factory/DelegatingLoggerFactoryTest.php b/tests/Unit/Core/Logger/Factory/DelegatingLoggerFactoryTest.php new file mode 100644 index 0000000000..b0fd92cafd --- /dev/null +++ b/tests/Unit/Core/Logger/Factory/DelegatingLoggerFactoryTest.php @@ -0,0 +1,75 @@ +createStub(IManageConfigValues::class); + $config->method('get')->willReturnMap([ + ['system', 'logger_config', null, 'test'], + ]); + + $factory = new DelegatingLoggerFactory($config); + + $factory->registerFactory('test', $this->createStub(LoggerFactory::class)); + + $this->assertInstanceOf( + LoggerInterface::class, + $factory->createLogger(LogLevel::DEBUG, LogChannel::DEFAULT) + ); + } + + public function testCreateLoggerWithoutRegisteredFactoryReturnsNullLogger(): void + { + $config = $this->createStub(IManageConfigValues::class); + $config->method('get')->willReturnMap([ + ['system', 'logger_config', null, 'not-existing-factory'], + ]); + + $factory = new DelegatingLoggerFactory($config); + + $this->assertInstanceOf( + NullLogger::class, + $factory->createLogger(LogLevel::DEBUG, LogChannel::DEFAULT) + ); + } + + public function testCreateLoggerWithExceptionThrowingFactoryReturnsNullLogger(): void + { + $config = $this->createStub(IManageConfigValues::class); + $config->method('get')->willReturnMap([ + ['system', 'logger_config', null, 'test'], + ]); + + $factory = new DelegatingLoggerFactory($config); + + $brokenFactory = $this->createStub(LoggerFactory::class); + $brokenFactory->method('createLogger')->willThrowException(new Exception()); + + $factory->registerFactory('test', $brokenFactory); + + $this->assertInstanceOf( + NullLogger::class, + $factory->createLogger(LogLevel::DEBUG, LogChannel::DEFAULT) + ); + } +} diff --git a/tests/Unit/Core/Logger/Factory/LegacyLoggerFactoryTest.php b/tests/Unit/Core/Logger/Factory/LegacyLoggerFactoryTest.php deleted file mode 100644 index 9ef920c71f..0000000000 --- a/tests/Unit/Core/Logger/Factory/LegacyLoggerFactoryTest.php +++ /dev/null @@ -1,36 +0,0 @@ -createStub(ICanCreateInstances::class), - $this->createStub(IManageConfigValues::class), - $this->createStub(Profiler::class), - ); - - $this->assertInstanceOf( - LoggerInterface::class, - $factory->createLogger(LogLevel::DEBUG, LogChannel::DEFAULT) - ); - } -} diff --git a/tests/Unit/Core/Logger/Factory/StreamLoggerFactoryTest.php b/tests/Unit/Core/Logger/Factory/StreamLoggerFactoryTest.php new file mode 100644 index 0000000000..744d597f19 --- /dev/null +++ b/tests/Unit/Core/Logger/Factory/StreamLoggerFactoryTest.php @@ -0,0 +1,81 @@ +createStub(IManageConfigValues::class); + $config->method('get')->willReturnMap([ + ['system', 'logfile', null, dirname(__DIR__, 4) . '/datasets/log/empty.friendica.log.txt'], + ]); + + $factory = new StreamLoggerFactory( + $config, + $this->createStub(IHaveCallIntrospections::class), + $this->createStub(FileSystemUtil::class), + ); + + $this->assertInstanceOf( + LoggerInterface::class, + $factory->createLogger(LogLevel::DEBUG, LogChannel::DEFAULT) + ); + } + + public function testCreateLoggerWithInvalidLogfileThrowsException(): void + { + $config = $this->createStub(IManageConfigValues::class); + $config->method('get')->willReturnMap([ + ['system', 'logfile', null, dirname(__DIR__, 1) . '/not-existing-logfile.txt'], + ]); + + $factory = new StreamLoggerFactory( + $config, + $this->createStub(IHaveCallIntrospections::class), + $this->createStub(FileSystemUtil::class), + ); + + $this->expectException(LoggerArgumentException::class); + $this->expectExceptionMessage('tests/Unit/Core/Logger/not-existing-logfile.txt" is not a valid logfile.'); + + $factory->createLogger(LogLevel::DEBUG, LogChannel::DEFAULT); + } + + public function testCreateLoggerWithInvalidLoglevelThrowsException(): void + { + $config = $this->createStub(IManageConfigValues::class); + $config->method('get')->willReturnMap([ + ['system', 'logfile', null, dirname(__DIR__, 4) . '/datasets/log/empty.friendica.log.txt'], + ]); + + $factory = new StreamLoggerFactory( + $config, + $this->createStub(IHaveCallIntrospections::class), + $this->createStub(FileSystemUtil::class), + ); + + $this->expectException(LogLevelException::class); + $this->expectExceptionMessage('The log level "unsupported-loglevel" is not supported by "Friendica\Core\Logger\Type\StreamLogger".'); + + $factory->createLogger('unsupported-loglevel', LogChannel::DEFAULT); + } +} diff --git a/tests/Unit/Core/Logger/Factory/SyslogLoggerFactoryTest.php b/tests/Unit/Core/Logger/Factory/SyslogLoggerFactoryTest.php new file mode 100644 index 0000000000..7f94c66fcd --- /dev/null +++ b/tests/Unit/Core/Logger/Factory/SyslogLoggerFactoryTest.php @@ -0,0 +1,61 @@ +createStub(IManageConfigValues::class); + $config->method('get')->willReturnMap([ + ['system', 'syslog_flags', null, SyslogLogger::DEFAULT_FLAGS], + ['system', 'syslog_facility', null, SyslogLogger::DEFAULT_FACILITY], + ]); + + $factory = new SyslogLoggerFactory( + $config, + $this->createStub(IHaveCallIntrospections::class), + ); + + $this->assertInstanceOf( + LoggerInterface::class, + $factory->createLogger(LogLevel::DEBUG, LogChannel::DEFAULT) + ); + } + + public function testCreateLoggerWithInvalidLoglevelThrowsException(): void + { + $config = $this->createStub(IManageConfigValues::class); + $config->method('get')->willReturnMap([ + ['system', 'syslog_flags', null, SyslogLogger::DEFAULT_FLAGS], + ['system', 'syslog_facility', null, SyslogLogger::DEFAULT_FACILITY], + ]); + + $factory = new SyslogLoggerFactory( + $config, + $this->createStub(IHaveCallIntrospections::class), + ); + + $this->expectException(LogLevelException::class); + $this->expectExceptionMessage('The log level "unsupported-loglevel" is not supported by "Friendica\Core\Logger\Type\SyslogLogger".'); + + $factory->createLogger('unsupported-loglevel', LogChannel::DEFAULT); + } +} diff --git a/tests/Unit/Event/ArrayFilterEventTest.php b/tests/Unit/Event/ArrayFilterEventTest.php index 2d396e6760..85f9307a0d 100644 --- a/tests/Unit/Event/ArrayFilterEventTest.php +++ b/tests/Unit/Event/ArrayFilterEventTest.php @@ -29,24 +29,75 @@ class ArrayFilterEventTest extends TestCase [ArrayFilterEvent::NAV_INFO, 'friendica.data.nav_info'], [ArrayFilterEvent::FEATURE_ENABLED, 'friendica.data.feature_enabled'], [ArrayFilterEvent::FEATURE_GET, 'friendica.data.feature_get'], - [ArrayFilterEvent::POST_LOCAL_START, 'friendica.data.post_local_start'], - [ArrayFilterEvent::POST_LOCAL, 'friendica.data.post_local'], - [ArrayFilterEvent::POST_LOCAL_END, 'friendica.data.post_local_end'], + [ArrayFilterEvent::PERMISSION_TOOLTIP_CONTENT, 'friendica.data.permission_tooltip_content'], + [ArrayFilterEvent::INSERT_POST_LOCAL_START, 'friendica.data.insert_post_local_start'], + [ArrayFilterEvent::INSERT_POST_LOCAL, 'friendica.data.insert_post_local'], + [ArrayFilterEvent::INSERT_POST_LOCAL_END, 'friendica.data.insert_post_local_end'], + [ArrayFilterEvent::INSERT_POST_REMOTE, 'friendica.data.insert_post_remote'], + [ArrayFilterEvent::INSERT_POST_REMOTE_END, 'friendica.data.insert_post_remote_end'], + [ArrayFilterEvent::PREPARE_POST_START, 'friendica.data.prepare_post_start'], + [ArrayFilterEvent::PREPARE_POST_FILTER_CONTENT, 'friendica.data.prepare_post_filter_content'], + [ArrayFilterEvent::PREPARE_POST, 'friendica.data.prepare_post'], + [ArrayFilterEvent::PREPARE_POST_END, 'friendica.data.prepare_post_end'], [ArrayFilterEvent::PHOTO_UPLOAD_FORM, 'friendica.data.photo_upload_form'], + [ArrayFilterEvent::PHOTO_UPLOAD_START, 'friendica.data.photo_upload_start'], + [ArrayFilterEvent::PHOTO_UPLOAD, 'friendica.data.photo_upload'], + [ArrayFilterEvent::PHOTO_UPLOAD_END, 'friendica.data.photo_upload_end'], [ArrayFilterEvent::NETWORK_TO_NAME, 'friendica.data.network_to_name'], + [ArrayFilterEvent::NETWORK_CONTENT_START, 'friendica.data.network_content_start'], + [ArrayFilterEvent::NETWORK_CONTENT_TABS, 'friendica.data.network_content_tabs'], + [ArrayFilterEvent::PARSE_LINK, 'friendica.data.parse_link'], [ArrayFilterEvent::CONVERSATION_START, 'friendica.data.conversation_start'], + [ArrayFilterEvent::FETCH_ITEM_BY_LINK, 'friendica.data.fetch_item_by_link'], + [ArrayFilterEvent::ITEM_TAGGED, 'friendica.data.item_tagged'], [ArrayFilterEvent::DISPLAY_ITEM, 'friendica.data.display_item'], + [ArrayFilterEvent::CACHE_ITEM, 'friendica.data.cache_item'], + [ArrayFilterEvent::CHECK_ITEM_NOTIFICATION, 'friendica.data.check_item_notification'], + [ArrayFilterEvent::ENOTIFY, 'friendica.data.enotify'], + [ArrayFilterEvent::ENOTIFY_STORE, 'friendica.data.enotify_store'], + [ArrayFilterEvent::ENOTIFY_MAIL, 'friendica.data.enotify_mail'], + [ArrayFilterEvent::DETECT_LANGUAGES, 'friendica.data.detect_languages'], [ArrayFilterEvent::RENDER_LOCATION, 'friendica.data.render_location'], [ArrayFilterEvent::ITEM_PHOTO_MENU, 'friendica.data.item_photo_menu'], + [ArrayFilterEvent::DIRECTORY_ITEM, 'friendica.data.directory_item'], + [ArrayFilterEvent::CONTACT_PHOTO_MENU, 'friendica.data.contact_photo_menu'], + [ArrayFilterEvent::PROFILE_SIDEBAR_ENTRY, 'friendica.data.profile_sidebar_entry'], + [ArrayFilterEvent::PROFILE_SIDEBAR, 'friendica.data.profile_sidebar'], + [ArrayFilterEvent::PROFILE_TABS, 'friendica.data.profile_tabs'], + [ArrayFilterEvent::PROFILE_SETTINGS_FORM, 'friendica.data.profile_settings_form'], + [ArrayFilterEvent::PROFILE_SETTINGS_POST, 'friendica.data.profile_settings_post'], + [ArrayFilterEvent::MODERATION_USERS_TABS, 'friendica.data.moderation_users_tabs'], + [ArrayFilterEvent::ACL_LOOKUP_END, 'friendica.data.acl_lookup_end'], [ArrayFilterEvent::OEMBED_FETCH_END, 'friendica.data.oembed_fetch_end'], [ArrayFilterEvent::PAGE_INFO, 'friendica.data.page_info'], [ArrayFilterEvent::SMILEY_LIST, 'friendica.data.smiley_list'], [ArrayFilterEvent::BBCODE_TO_HTML_START, 'friendica.data.bbcode_to_html_start'], + [ArrayFilterEvent::HTML_TO_BBCODE_END, 'friendica.data.html_to_bbcode_end'], [ArrayFilterEvent::BBCODE_TO_MARKDOWN_END, 'friendica.data.bbcode_to_markdown_end'], [ArrayFilterEvent::JOT_NETWORKS, 'friendica.data.jot_networks'], [ArrayFilterEvent::PROTOCOL_SUPPORTS_FOLLOW, 'friendica.data.protocol_supports_follow'], [ArrayFilterEvent::PROTOCOL_SUPPORTS_REVOKE_FOLLOW, 'friendica.data.protocol_supports_revoke_follow'], [ArrayFilterEvent::PROTOCOL_SUPPORTS_PROBE, 'friendica.data.protocol_supports_probe'], + [ArrayFilterEvent::FOLLOW_CONTACT, 'friendica.data.follow_contact'], + [ArrayFilterEvent::UNFOLLOW_CONTACT, 'friendica.data.unfollow_contact'], + [ArrayFilterEvent::REVOKE_FOLLOW_CONTACT, 'friendica.data.revoke_follow_contact'], + [ArrayFilterEvent::BLOCK_CONTACT, 'friendica.data.block_contact'], + [ArrayFilterEvent::UNBLOCK_CONTACT, 'friendica.data.unblock_contact'], + [ArrayFilterEvent::EDIT_CONTACT_FORM, 'friendica.data.edit_contact_form'], + [ArrayFilterEvent::EDIT_CONTACT_POST, 'friendica.data.edit_contact_post'], + [ArrayFilterEvent::AVATAR_LOOKUP, 'friendica.data.avatar_lookup'], + [ArrayFilterEvent::ACCOUNT_AUTHENTICATE, 'friendica.data.account_authenticate'], + [ArrayFilterEvent::ACCOUNT_REGISTER_FORM, 'friendica.data.account_register_form'], + [ArrayFilterEvent::ACCOUNT_REGISTER_POST, 'friendica.data.account_register_post'], + [ArrayFilterEvent::ACCOUNT_REGISTER, 'friendica.data.account_register'], + [ArrayFilterEvent::ACCOUNT_REMOVE, 'friendica.data.account_remove'], + [ArrayFilterEvent::EVENT_CREATED, 'friendica.data.event_created'], + [ArrayFilterEvent::EVENT_UPDATED, 'friendica.data.event_updated'], + [ArrayFilterEvent::ADD_WORKER_TASK, 'friendica.data.add_worker_task'], + [ArrayFilterEvent::STORAGE_CONFIG, 'friendica.data.storage_config'], + [ArrayFilterEvent::STORAGE_INSTANCE, 'friendica.data.storage_instance'], + [ArrayFilterEvent::DB_STRUCTURE_DEFINITION, 'friendica.data.db_structure_definition'], + [ArrayFilterEvent::DB_VIEW_DEFINITION, 'friendica.data.db_view_definition'], ]; } diff --git a/tests/Unit/Event/HtmlFilterEventTest.php b/tests/Unit/Event/HtmlFilterEventTest.php index ae1d27a5ad..bae1669ca0 100644 --- a/tests/Unit/Event/HtmlFilterEventTest.php +++ b/tests/Unit/Event/HtmlFilterEventTest.php @@ -27,8 +27,14 @@ class HtmlFilterEventTest extends TestCase return [ [HtmlFilterEvent::HEAD, 'friendica.html.head'], [HtmlFilterEvent::FOOTER, 'friendica.html.footer'], + [HtmlFilterEvent::PAGE_HEADER, 'friendica.html.page_header'], [HtmlFilterEvent::PAGE_CONTENT_TOP, 'friendica.html.page_content_top'], [HtmlFilterEvent::PAGE_END, 'friendica.html.page_end'], + [HtmlFilterEvent::MOD_HOME_CONTENT, 'friendica.html.mod_home_content'], + [HtmlFilterEvent::MOD_ABOUT_CONTENT, 'friendica.html.mod_about_content'], + [HtmlFilterEvent::MOD_PROFILE_CONTENT, 'friendica.html.mod_profile_content'], + [HtmlFilterEvent::JOT_TOOL, 'friendica.html.jot_tool'], + [HtmlFilterEvent::CONTACT_BLOCK_END, 'friendica.html.contact_block_end'], ]; } diff --git a/tests/Util/FakeEventDispatcher.php b/tests/Util/FakeEventDispatcher.php new file mode 100644 index 0000000000..434e9ba5a6 --- /dev/null +++ b/tests/Util/FakeEventDispatcher.php @@ -0,0 +1,31 @@ + [ [ - 'url' => 'https://friendica.local', - 'nurl' => 'http://friendica.local', - 'register_policy' => 0, + 'url' => 'https://friendica.local', + 'nurl' => 'http://friendica.local', + 'register_policy' => 0, 'registered-users' => 0, - 'network' => 'unkn', + 'network' => 'unkn', ], ], // Base test config to avoid notice messages @@ -88,6 +88,11 @@ return [ 'uri' => 'https://friendica.local/profile/mutualcontact', 'guid' => '46', ], + [ + 'id' => 49, + 'uri' => 'https://domain.tld/profile/remotecontact', + 'guid' => '49', + ], [ 'id' => 100, 'uri' => 'https://friendica.local/posts/100', @@ -214,6 +219,23 @@ return [ 'network' => Protocol::DFRN, 'location' => 'DFRN', ], + [ + 'id' => 49, + 'uid' => 0, + 'uri-id' => 43, + 'name' => 'Remote user', + 'nick' => 'remotecontact', + 'self' => 0, + 'nurl' => 'http://domain.tld/profile/remotecontact', + 'url' => 'https://domain.tld/profile/remotecontact', + 'alias' => 'https://domain.tld/~remotecontact', + 'about' => 'User used in tests', + 'pending' => 0, + 'blocked' => 0, + 'rel' => Contact::FOLLOWER, + 'network' => Protocol::ACTIVITYPUB, + 'location' => 'AP', + ], ], 'apcontact' => [ [ @@ -343,7 +365,7 @@ return [ 'suscipit aut facilis ut inventore omnis exercitationem quo magnam ' . 'consequatur maxime aut illum soluta quaerat natus unde aspernatur ' . 'et sed beatae nihil ullam temporibus corporis ratione blanditiis', - 'plink' => 'https://friendica.local/display/6', + 'plink' => 'https://friendica.local/display/6', ], [ 'uri-id' => 100, @@ -912,8 +934,8 @@ return [ ], 'profile' => [ [ - 'id' => 1, - 'uid' => 42, + 'id' => 1, + 'uid' => 42, 'locality' => 'DFRN', ], ], @@ -933,18 +955,18 @@ return [ ], 'group_member' => [ [ - 'id' => 1, - 'gid' => 1, + 'id' => 1, + 'gid' => 1, 'contact-id' => 43, ], [ - 'id' => 2, - 'gid' => 1, + 'id' => 2, + 'gid' => 1, 'contact-id' => 43, ], [ - 'id' => 3, - 'gid' => 2, + 'id' => 3, + 'gid' => 2, 'contact-id' => 43, ], ], diff --git a/tests/datasets/config/transformer/C.node.config.php b/tests/datasets/config/transformer/C.node.config.php index 44969d1eb5..0b2964acd2 100644 --- a/tests/datasets/config/transformer/C.node.config.php +++ b/tests/datasets/config/transformer/C.node.config.php @@ -17,7 +17,6 @@ return [ 'temppath' => '/tmp/friendica.local', 'theme' => 'frio', 'url' => 'https://friendica.local', - 'urlpath' => '', 'build' => 1508, 'maintenance' => false, 'dbupdate' => 1, diff --git a/tests/src/Console/AutomaticInstallationConsoleTest.php b/tests/src/Console/AutomaticInstallationConsoleTest.php index 7fe56bb152..f3466c0203 100644 --- a/tests/src/Console/AutomaticInstallationConsoleTest.php +++ b/tests/src/Console/AutomaticInstallationConsoleTest.php @@ -129,7 +129,6 @@ class AutomaticInstallationConsoleTest extends ConsoleTestCase ], 'system' => [ 'basepath' => '', - 'urlpath' => '', 'url' => 'http://friendica.local', 'ssl_policy' => 0, 'default_timezone' => '', @@ -152,7 +151,6 @@ class AutomaticInstallationConsoleTest extends ConsoleTestCase 'admin_email' => 'admin@philipp.info', ], 'system' => [ - 'urlpath' => 'test/it', 'url' => 'http://friendica.local/test/it', 'basepath' => '', 'ssl_policy' => '2', @@ -176,7 +174,6 @@ class AutomaticInstallationConsoleTest extends ConsoleTestCase 'admin_email' => 'admin@philipp.info', ], 'system' => [ - 'urlpath' => 'test/it', 'url' => 'https://friendica.local/test/it', 'basepath' => '', 'ssl_policy' => '1', @@ -352,7 +349,6 @@ FIN; self::assertConfigEntry('system', 'default_timezone', $assertion, ($default) ? Installer::DEFAULT_TZ : null); self::assertConfigEntry('system', 'language', $assertion, ($default) ? Installer::DEFAULT_LANG : null); self::assertConfigEntry('system', 'url', $assertion); - self::assertConfigEntry('system', 'urlpath', $assertion); self::assertConfigEntry('system', 'ssl_policy', $assertion, ($default) ? App\BaseURL::DEFAULT_SSL_SCHEME : null); self::assertConfigEntry('system', 'basepath', ($realBasepath) ? $this->root->url() : $assertion); } @@ -446,7 +442,6 @@ return [ ], 'system' => [ 'basepath' => '{$conf('system', 'basepath')}', - 'urlpath' => '{$conf('system', 'urlpath')}', 'url' => '{$conf('system', 'url')}', 'ssl_policy' => '{$conf('system', 'ssl_policy')}', 'default_timezone' => '{$conf('system', 'default_timezone')}', @@ -604,7 +599,7 @@ CONF; self::assertStuckDB($txt); self::assertTrue($this->root->hasChild('config' . DIRECTORY_SEPARATOR . 'local.config.php')); - self::assertConfig(['config' => ['hostname' => 'friendica.local'], 'system' => ['url' => 'http://friendica.local', 'ssl_policy' => 0, 'urlpath' => '']], false, true, false, true); + self::assertConfig(['config' => ['hostname' => 'friendica.local'], 'system' => ['url' => 'http://friendica.local', 'ssl_policy' => 0]], false, true, false, true); } public function testGetHelp() diff --git a/tests/src/Content/Text/BBCodeTest.php b/tests/src/Content/Text/BBCodeTest.php index 17f48ddae6..e51bb997d0 100644 --- a/tests/src/Content/Text/BBCodeTest.php +++ b/tests/src/Content/Text/BBCodeTest.php @@ -73,51 +73,51 @@ class BBCodeTest extends FixtureTestCase ], 'no-protocol' => [ 'data' => 'example.com/path', - 'assertHTML' => false + 'assertHTML' => false, ], 'wrong-protocol' => [ 'data' => 'ftp://example.com', - 'assertHTML' => false + 'assertHTML' => false, ], 'wrong-domain-without-path' => [ 'data' => 'http://example', - 'assertHTML' => false + 'assertHTML' => false, ], 'wrong-domain-with-path' => [ 'data' => 'http://example/path', - 'assertHTML' => false + 'assertHTML' => false, ], 'bug-6857-domain-start' => [ 'data' => "http://\nexample.com", - 'assertHTML' => false + 'assertHTML' => false, ], 'bug-6857-domain-end' => [ 'data' => "http://example\n.com", - 'assertHTML' => false + 'assertHTML' => false, ], 'bug-6857-tld' => [ 'data' => "http://example.\ncom", - 'assertHTML' => false + 'assertHTML' => false, ], 'bug-6857-end' => [ 'data' => "http://example.com\ntest", - 'assertHTML' => false + 'assertHTML' => false, ], 'bug-6901' => [ 'data' => "http://example.com
    ", - 'assertHTML' => false + 'assertHTML' => false, ], 'bug-7150' => [ 'data' => html_entity_decode('http://example.com ', ENT_QUOTES, 'UTF-8'), - 'assertHTML' => false + 'assertHTML' => false, ], 'bug-7271-query-string-brackets' => [ 'data' => 'https://example.com/search?q=square+brackets+[url]', - 'assertHTML' => true + 'assertHTML' => true, ], 'bug-7271-path-brackets' => [ 'data' => 'http://example.com/path/to/file[3].html', - 'assertHTML' => true + 'assertHTML' => true, ], ]; } @@ -215,19 +215,19 @@ class BBCodeTest extends FixtureTestCase ], 'bug-9611-purify-xss-nobb' => [ 'expectedHTML' => 'dare to move your mouse here', - 'text' => '[nobb]dare to move your mouse here[/nobb]' + 'text' => '[nobb]dare to move your mouse here[/nobb]', ], 'bug-9611-purify-xss-noparse' => [ 'expectedHTML' => 'dare to move your mouse here', - 'text' => '[noparse]dare to move your mouse here[/noparse]' + 'text' => '[noparse]dare to move your mouse here[/noparse]', ], 'bug-9611-purify-xss-attributes' => [ 'expectedHTML' => 'dare to move your mouse here', - 'text' => '[color="onmouseover=alert(0) style="]dare to move your mouse here[/color]' + 'text' => '[color="onmouseover=alert(0) style="]dare to move your mouse here[/color]', ], 'bug-9611-purify-attributes-correct' => [ 'expectedHTML' => 'dare to move your mouse here', - 'text' => '[color=FFFFFF]dare to move your mouse here[/color]' + 'text' => '[color=FFFFFF]dare to move your mouse here[/color]', ], 'bug-9639-span-classes' => [ 'expectedHTML' => 'Test', @@ -308,11 +308,11 @@ Karl Marx - Die ursprĂĽngliche Akkumulation ], 'bug-12701-quotes' => [ 'expected' => '[![abc"fgh](https://domain.tld/photo/86912721086415cdc8e0a03226831581-1.png)](https://domain.tld/photos/user/image/86912721086415cdc8e0a03226831581)', - 'text' => '[url=https://domain.tld/photos/user/image/86912721086415cdc8e0a03226831581][img=https://domain.tld/photo/86912721086415cdc8e0a03226831581-1.png]abc"fgh[/img][/url]' + 'text' => '[url=https://domain.tld/photos/user/image/86912721086415cdc8e0a03226831581][img=https://domain.tld/photo/86912721086415cdc8e0a03226831581-1.png]abc"fgh[/img][/url]', ], 'bug-12701-no-quotes' => [ 'expected' => '[![abcfgh](https://domain.tld/photo/86912721086415cdc8e0a03226831581-1.png "abcfgh")](https://domain.tld/photos/user/image/86912721086415cdc8e0a03226831581)', - 'text' => '[url=https://domain.tld/photos/user/image/86912721086415cdc8e0a03226831581][img=https://domain.tld/photo/86912721086415cdc8e0a03226831581-1.png]abcfgh[/img][/url]' + 'text' => '[url=https://domain.tld/photos/user/image/86912721086415cdc8e0a03226831581][img=https://domain.tld/photo/86912721086415cdc8e0a03226831581-1.png]abcfgh[/img][/url]', ], /** @see https://github.com/friendica/friendica/pull/14908 */ 'task-14908-strip-tags' => [ @@ -350,7 +350,7 @@ Karl Marx - Die ursprĂĽngliche Akkumulation 'bug-10692-start-line' => [ '#[url=https://friendica.local/search?tag=L160]L160[/url]', '#L160', - ] + ], ]; } @@ -367,6 +367,58 @@ Karl Marx - Die ursprĂĽngliche Akkumulation self::assertEquals($expected, $actual); } + public function dataExpandVideoLinks(): array + { + return [ + /** @see https://github.com/friendica/friendica/pull/14940 */ + 'task-14940-youtube-watch-with-www' => [ + 'expectedBBCode' => '[url=https://www.youtube.com/watch?v=hfwbmTzBFT0]https://www.youtube.com/watch?v=hfwbmTzBFT0[/url]', + 'text' => '[youtube]https://www.youtube.com/watch?v=hfwbmTzBFT0[/youtube]', + ], + 'task-14940-youtube-watch-without-www' => [ + 'expectedBBCode' => '[url=https://www.youtube.com/watch?v=hfwbmTzBFT0]https://www.youtube.com/watch?v=hfwbmTzBFT0[/url]', + 'text' => '[youtube]https://youtube.com/watch?v=hfwbmTzBFT0[/youtube]', + ], + 'task-14940-youtube-shorts-with-www' => [ + 'expectedBBCode' => '[url=https://www.youtube.com/watch?v=hfwbmTzBFT0]https://www.youtube.com/watch?v=hfwbmTzBFT0[/url]', + 'text' => '[youtube]https://www.youtube.com/shorts/hfwbmTzBFT0[/youtube]', + ], + 'task-14940-youtube-shorts-without-www' => [ + 'expectedBBCode' => '[url=https://www.youtube.com/watch?v=hfwbmTzBFT0]https://www.youtube.com/watch?v=hfwbmTzBFT0[/url]', + 'text' => '[youtube]https://youtube.com/shorts/hfwbmTzBFT0[/youtube]', + ], + 'task-14940-youtube-embed-with-www' => [ + 'expectedBBCode' => '[url=https://www.youtube.com/watch?v=hfwbmTzBFT0]https://www.youtube.com/watch?v=hfwbmTzBFT0[/url]', + 'text' => '[youtube]https://www.youtube.com/embed/hfwbmTzBFT0[/youtube]', + ], + 'task-14940-youtube-embed-without-www' => [ + 'expectedBBCode' => '[url=https://www.youtube.com/watch?v=hfwbmTzBFT0]https://www.youtube.com/watch?v=hfwbmTzBFT0[/url]', + 'text' => '[youtube]https://youtube.com/embed/hfwbmTzBFT0[/youtube]', + ], + 'task-14940-vimeo' => [ + 'expectedBBCode' => '[url=https://vimeo.com/2345345]https://vimeo.com/2345345[/url]', + 'text' => '[vimeo]https://vimeo.com/2345345[/vimeo]', + ], + 'task-14940-player-vimeo' => [ + 'expectedBBCode' => '[url=https://vimeo.com/2345345]https://vimeo.com/2345345[/url]', + 'text' => '[vimeo]https://player.vimeo.com/video/2345345[/vimeo]', + ], + ]; + } + + /** + * @dataProvider dataExpandVideoLinks + * + * @param string $expected Expected BBCode output + * @param string $text Input text + */ + public function testExpandVideoLinks(string $expected, string $text) + { + $actual = BBCode::expandVideoLinks($text); + + self::assertEquals($expected, $actual); + } + public function dataGetAbstract(): array { return [ @@ -620,4 +672,27 @@ Lucas: For the right price, yes.[/share]', self::assertEquals($expected, $actual); } + + public function dataProfileLink(): array + { + return [ + 'mention' => [ + 'expected' => 'Test 1: @Remote contact', + 'text' => 'Test 1: @[url=https://domain.tld/profile/remotecontact]Remote contact[/url]', + ], + ]; + } + + /** + * @dataProvider dataProfileLink + * + * @param string $expected Expected BBCode output + * @param string $text Input text + */ + public function testProfileLink(string $expected, string $text) + { + $actual = BBCode::convertForUriId(0, $text); + + self::assertEquals($expected, $actual); + } } diff --git a/tests/src/Content/Text/MarkdownTest.php b/tests/src/Content/Text/MarkdownTest.php index 6882bec7f1..c372180df5 100644 --- a/tests/src/Content/Text/MarkdownTest.php +++ b/tests/src/Content/Text/MarkdownTest.php @@ -51,7 +51,45 @@ class MarkdownTest extends FixtureTestCase return [ 'bug-8358-double-decode' => [ 'expectedBBCode' => 'with the and tag', - 'markdown' => 'with the <sup> and </sup> tag', + 'markdown' => 'with the <sup> and </sup> tag', + ], + /** @see https://github.com/friendica/friendica/pull/14940 */ + 'task-14940-youtube-watch-with-www' => [ + 'expectedBBCode' => '[youtube]hfwbmTzBFT0[/youtube]', + 'markdown' => '[url=https://www.youtube.com/watch?v=hfwbmTzBFT0]https://www.youtube.com/watch?v=hfwbmTzBFT0[/url]', + ], + 'task-14940-youtube-watch-without-www' => [ + 'expectedBBCode' => '[youtube]hfwbmTzBFT0[/youtube]', + 'markdown' => '[url=https://youtube.com/watch?v=hfwbmTzBFT0]https://youtube.com/watch?v=hfwbmTzBFT0[/url]', + ], + 'task-14940-youtube-shorts-with-www' => [ + 'expectedBBCode' => '[youtube]hfwbmTzBFT0[/youtube]', + 'markdown' => '[url=https://www.youtube.com/shorts/hfwbmTzBFT0]https://www.youtube.com/shorts/hfwbmTzBFT0[/url]', + ], + 'task-14940-youtube-shorts-without-www' => [ + 'expectedBBCode' => '[youtube]hfwbmTzBFT0[/youtube]', + 'markdown' => '[url=https://youtube.com/shorts/hfwbmTzBFT0]https://youtube.com/shorts/hfwbmTzBFT0[/url]', + ], + 'task-14940-youtube-embed-with-www' => [ + 'expectedBBCode' => '[youtube]hfwbmTzBFT0[/youtube]', + 'markdown' => '[url=https://www.youtube.com/embed/hfwbmTzBFT0]https://www.youtube.com/embed/hfwbmTzBFT0[/url]', + ], + 'task-14940-youtube-embed-without-www' => [ + 'expectedBBCode' => '[youtube]hfwbmTzBFT0[/youtube]', + 'markdown' => '[url=https://youtube.com/embed/hfwbmTzBFT0]https://youtube.com/embed/hfwbmTzBFT0[/url]', + ], + // @todo - should we really ignore the URL content in favor of parsing the link of the body? + 'task-14940-vimeo-custom-url' => [ + 'expectedBBCode' => '[vimeo]2345345[/vimeo]', + 'markdown' => '[url=https://no.thing]https://vimeo.com/2345345[/url]', + ], + 'task-14940-vimeo-custom-text' => [ + 'expectedBBCode' => '[vimeo]2345345[/vimeo]', + 'markdown' => '[url=https://vimeo.com/2345345]CustomText[/url]', + ], + 'task-14940-player-vimeo' => [ + 'expectedBBCode' => '[vimeo]2345345[/vimeo]', + 'markdown' => '[url=https://player.vimeo.com/video/2345345]https://player.vimeo.com/video/2345345[/url]', ], ]; } diff --git a/tests/src/Core/Cache/APCuCacheTest.php b/tests/src/Core/Cache/APCuCacheTest.php index 117c211b04..47e660b26a 100644 --- a/tests/src/Core/Cache/APCuCacheTest.php +++ b/tests/src/Core/Cache/APCuCacheTest.php @@ -35,4 +35,18 @@ class APCuCacheTest extends MemoryCacheTestCase $this->cache->clear(false); parent::tearDown(); } + + /** + * @small + */ + public function testStats() + { + $stats = $this->instance->getStats(); + + self::assertNotNull($stats['entries']); + self::assertNotNull($stats['used_memory']); + self::assertNotNull($stats['hits']); + self::assertNotNull($stats['misses']); + self::assertNotNull($stats['avail_mem']); + } } diff --git a/tests/src/Core/Cache/ArrayCacheTest.php b/tests/src/Core/Cache/ArrayCacheTest.php index 967cb07bce..50226b0907 100644 --- a/tests/src/Core/Cache/ArrayCacheTest.php +++ b/tests/src/Core/Cache/ArrayCacheTest.php @@ -33,4 +33,12 @@ class ArrayCacheTest extends MemoryCacheTestCase self::markTestSkipped("Array Cache doesn't support TTL"); return true; } + + /** + * @small + */ + public function testGetStats() + { + self::assertEmpty($this->cache->getStats()); + } } diff --git a/tests/src/Core/Cache/MemcacheCacheTest.php b/tests/src/Core/Cache/MemcacheCacheTest.php index abd073f483..c622f22216 100644 --- a/tests/src/Core/Cache/MemcacheCacheTest.php +++ b/tests/src/Core/Cache/MemcacheCacheTest.php @@ -59,4 +59,21 @@ class MemcacheCacheTest extends MemoryCacheTestCase { static::markTestIncomplete('Race condition because of too fast getAllKeys() which uses a workaround'); } + + /** + * @small + */ + public function testStats() + { + $stats = $this->instance->getStats(); + + self::assertNotNull($stats['version']); + self::assertIsNumeric($stats['hits']); + self::assertIsNumeric($stats['misses']); + self::assertIsNumeric($stats['evictions']); + self::assertIsNumeric($stats['entries']); + self::assertIsNumeric($stats['used_memory']); + self::assertGreaterThan(0, $stats['connected_clients']); + self::assertGreaterThan(0, $stats['uptime']); + } } diff --git a/tests/src/Core/Cache/MemcachedCacheTest.php b/tests/src/Core/Cache/MemcachedCacheTest.php index f3b6107b5b..a1c3653f1b 100644 --- a/tests/src/Core/Cache/MemcachedCacheTest.php +++ b/tests/src/Core/Cache/MemcachedCacheTest.php @@ -58,4 +58,21 @@ class MemcachedCacheTest extends MemoryCacheTestCase { static::markTestIncomplete('Race condition because of too fast getAllKeys() which uses a workaround'); } + + /** + * @small + */ + public function testStats() + { + $stats = $this->instance->getStats(); + + self::assertNotNull($stats['version']); + self::assertIsNumeric($stats['hits']); + self::assertIsNumeric($stats['misses']); + self::assertIsNumeric($stats['evictions']); + self::assertIsNumeric($stats['entries']); + self::assertIsNumeric($stats['used_memory']); + self::assertGreaterThan(0, $stats['connected_clients']); + self::assertGreaterThan(0, $stats['uptime']); + } } diff --git a/tests/src/Core/Cache/ProfilerCacheDecoratorTest.php b/tests/src/Core/Cache/ProfilerCacheDecoratorTest.php new file mode 100644 index 0000000000..3f44bcd0bf --- /dev/null +++ b/tests/src/Core/Cache/ProfilerCacheDecoratorTest.php @@ -0,0 +1,56 @@ +shouldReceive('get')->with('system', 'profiler')->once()->andReturn(false); + $config->shouldReceive('get')->with('rendertime', 'callstack')->once()->andReturn(false); + + $this->cache = new ProfilerCacheDecorator(new ArrayCache('localhost'), new Profiler($config)); + return $this->cache; + } + + protected function tearDown(): void + { + $this->cache->clear(false); + parent::tearDown(); + } + + /** + * @doesNotPerformAssertions + */ + public function testTTL() + { + // Array Cache doesn't support TTL + self::markTestSkipped("Array Cache doesn't support TTL"); + return true; + } + + /** + * @small + */ + public function testGetStats() + { + self::assertEmpty($this->cache->getStats()); + } + + public function testGetName() + { + self::assertStringEndsWith(' (with profiler)', $this->instance->getName()); + } +} diff --git a/tests/src/Core/Cache/RedisCacheTest.php b/tests/src/Core/Cache/RedisCacheTest.php index 6169171f40..d16bf5a64c 100644 --- a/tests/src/Core/Cache/RedisCacheTest.php +++ b/tests/src/Core/Cache/RedisCacheTest.php @@ -57,4 +57,21 @@ class RedisCacheTest extends MemoryCacheTestCase $this->cache->clear(false); parent::tearDown(); } + + /** + * @small + */ + public function testStats() + { + $stats = $this->instance->getStats(); + + self::assertNotNull($stats['version']); + self::assertIsNumeric($stats['hits']); + self::assertIsNumeric($stats['misses']); + self::assertIsNumeric($stats['evictions']); + self::assertIsNumeric($stats['entries']); + self::assertIsNumeric($stats['used_memory']); + self::assertGreaterThan(0, $stats['connected_clients']); + self::assertGreaterThan(0, $stats['uptime']); + } } diff --git a/tests/src/Core/Lock/APCuCacheLockTest.php b/tests/src/Core/Lock/APCuCacheLockTest.php index 3ee0d09661..3b6c7904b4 100644 --- a/tests/src/Core/Lock/APCuCacheLockTest.php +++ b/tests/src/Core/Lock/APCuCacheLockTest.php @@ -7,26 +7,39 @@ namespace Friendica\Test\src\Core\Lock; +use Friendica\Core\Cache\Capability\ICanCacheInMemory; use Friendica\Core\Cache\Type\APCuCache; +use Friendica\Core\Lock\Capability\ICanLock; use Friendica\Core\Lock\Type\CacheLock; -use Friendica\Test\LockTestCase; +use Friendica\Test\CacheLockTestCase; /** * @group APCU */ -class APCuCacheLockTest extends LockTestCase +class APCuCacheLockTest extends CacheLockTestCase { + private APCuCache $cache; + private ICanLock $lock; + protected function setUp(): void { if (!APCuCache::isAvailable()) { static::markTestSkipped('APCu is not available'); } + $this->cache = new APCuCache('localhost'); + $this->lock = new CacheLock($this->cache); + parent::setUp(); } - protected function getInstance() + protected function getInstance(): CacheLock { - return new CacheLock(new APCuCache('localhost')); + return $this->lock; + } + + protected function getCache(): ICanCacheInMemory + { + return $this->cache; } } diff --git a/tests/src/Core/Lock/ArrayCacheLockTest.php b/tests/src/Core/Lock/ArrayCacheLockTest.php index 19ac7925c6..07cd88dd1c 100644 --- a/tests/src/Core/Lock/ArrayCacheLockTest.php +++ b/tests/src/Core/Lock/ArrayCacheLockTest.php @@ -7,15 +7,32 @@ namespace Friendica\Test\src\Core\Lock; +use Friendica\Core\Cache\Capability\ICanCacheInMemory; use Friendica\Core\Cache\Type\ArrayCache; use Friendica\Core\Lock\Type\CacheLock; -use Friendica\Test\LockTestCase; +use Friendica\Test\CacheLockTestCase; -class ArrayCacheLockTest extends LockTestCase +class ArrayCacheLockTest extends CacheLockTestCase { - protected function getInstance() + private CacheLock $lock; + private ArrayCache $cache; + + protected function setUp(): void { - return new CacheLock(new ArrayCache('localhost')); + $this->cache = new ArrayCache('localhost'); + $this->lock = new CacheLock($this->cache); + + parent::setUp(); + } + + protected function getInstance(): CacheLock + { + return $this->lock; + } + + protected function getCache(): ICanCacheInMemory + { + return $this->cache; } /** diff --git a/tests/src/Core/Lock/DatabaseLockDriverTest.php b/tests/src/Core/Lock/DatabaseLockDriverTest.php index ebc2b0090f..fbfe61762e 100644 --- a/tests/src/Core/Lock/DatabaseLockDriverTest.php +++ b/tests/src/Core/Lock/DatabaseLockDriverTest.php @@ -7,6 +7,7 @@ namespace Friendica\Test\src\Core\Lock; +use Friendica\Core\Lock\Capability\ICanLock; use Friendica\Core\Lock\Type\DatabaseLock; use Friendica\Test\LockTestCase; use Friendica\Test\Util\CreateDatabaseTrait; @@ -26,7 +27,7 @@ class DatabaseLockDriverTest extends LockTestCase parent::setUp(); } - protected function getInstance() + protected function getInstance(): ICanLock { return new DatabaseLock($this->getDbInstance(), $this->pid); } diff --git a/tests/src/Core/Lock/MemcacheCacheLockTest.php b/tests/src/Core/Lock/MemcacheCacheLockTest.php index 2bb0595cff..8915e6d37c 100644 --- a/tests/src/Core/Lock/MemcacheCacheLockTest.php +++ b/tests/src/Core/Lock/MemcacheCacheLockTest.php @@ -8,19 +8,23 @@ namespace Friendica\Test\src\Core\Lock; use Exception; +use Friendica\Core\Cache\Capability\ICanCacheInMemory; use Friendica\Core\Cache\Type\MemcacheCache; use Friendica\Core\Config\Capability\IManageConfigValues; use Friendica\Core\Lock\Type\CacheLock; -use Friendica\Test\LockTestCase; +use Friendica\Test\CacheLockTestCase; use Mockery; /** * @requires extension Memcache * @group MEMCACHE */ -class MemcacheCacheLockTest extends LockTestCase +class MemcacheCacheLockTest extends CacheLockTestCase { - protected function getInstance() + private CacheLock $lock; + private MemcacheCache $cache; + + protected function setUp(): void { $configMock = Mockery::mock(IManageConfigValues::class); @@ -36,16 +40,24 @@ class MemcacheCacheLockTest extends LockTestCase ->with('system', 'memcache_port') ->andReturn($port); - $lock = null; - try { - $cache = new MemcacheCache($host, $configMock); - $lock = new CacheLock($cache); + $this->cache = new MemcacheCache($host, $configMock); + $this->lock = new CacheLock($this->cache); } catch (Exception $e) { static::markTestSkipped('Memcache is not available'); } - return $lock; + parent::setUp(); + } + + protected function getInstance(): CacheLock + { + return $this->lock; + } + + protected function getCache(): ICanCacheInMemory + { + return $this->cache; } /** diff --git a/tests/src/Core/Lock/MemcachedCacheLockTest.php b/tests/src/Core/Lock/MemcachedCacheLockTest.php index fb38ec3312..522f60c64a 100644 --- a/tests/src/Core/Lock/MemcachedCacheLockTest.php +++ b/tests/src/Core/Lock/MemcachedCacheLockTest.php @@ -8,10 +8,11 @@ namespace Friendica\Test\src\Core\Lock; use Exception; +use Friendica\Core\Cache\Capability\ICanCacheInMemory; use Friendica\Core\Cache\Type\MemcachedCache; use Friendica\Core\Config\Capability\IManageConfigValues; use Friendica\Core\Lock\Type\CacheLock; -use Friendica\Test\LockTestCase; +use Friendica\Test\CacheLockTestCase; use Mockery; use Psr\Log\NullLogger; @@ -19,9 +20,12 @@ use Psr\Log\NullLogger; * @requires extension memcached * @group MEMCACHED */ -class MemcachedCacheLockTest extends LockTestCase +class MemcachedCacheLockTest extends CacheLockTestCase { - protected function getInstance() + private MemcachedCache $cache; + private CacheLock $lock; + + protected function setUp(): void { $configMock = Mockery::mock(IManageConfigValues::class); @@ -35,16 +39,24 @@ class MemcachedCacheLockTest extends LockTestCase $logger = new NullLogger(); - $lock = null; - try { - $cache = new MemcachedCache($host, $configMock, $logger); - $lock = new CacheLock($cache); + $this->cache = new MemcachedCache($host, $configMock, $logger); + $this->lock = new CacheLock($this->cache); } catch (Exception $e) { static::markTestSkipped('Memcached is not available'); } - return $lock; + parent::setUp(); + } + + protected function getInstance(): CacheLock + { + return $this->lock; + } + + protected function getCache(): ICanCacheInMemory + { + return $this->cache; } /** diff --git a/tests/src/Core/Lock/RedisCacheLockTest.php b/tests/src/Core/Lock/RedisCacheLockTest.php index d0237682c3..1136b80c4b 100644 --- a/tests/src/Core/Lock/RedisCacheLockTest.php +++ b/tests/src/Core/Lock/RedisCacheLockTest.php @@ -8,19 +8,20 @@ namespace Friendica\Test\src\Core\Lock; use Exception; +use Friendica\Core\Cache\Capability\ICanCacheInMemory; use Friendica\Core\Cache\Type\RedisCache; use Friendica\Core\Config\Capability\IManageConfigValues; use Friendica\Core\Lock\Type\CacheLock; -use Friendica\Test\LockTestCase; +use Friendica\Test\CacheLockTestCase; use Mockery; /** * @requires extension redis * @group REDIS */ -class RedisCacheLockTest extends LockTestCase +class RedisCacheLockTest extends CacheLockTestCase { - protected function getInstance() + protected function setUp(): void { $configMock = Mockery::mock(IManageConfigValues::class); @@ -45,15 +46,23 @@ class RedisCacheLockTest extends LockTestCase ->with('system', 'redis_password') ->andReturn(null); - $lock = null; - try { - $cache = new RedisCache($host, $configMock); - $lock = new CacheLock($cache); + $this->cache = new RedisCache($host, $configMock); + $this->lock = new CacheLock($this->cache); } catch (Exception $e) { static::markTestSkipped('Redis is not available. Error: ' . $e->getMessage()); } - return $lock; + parent::setUp(); + } + + protected function getInstance(): CAcheLock + { + return $this->lock; + } + + protected function getCache(): ICanCacheInMemory + { + return $this->cache; } } diff --git a/tests/src/Core/Lock/SemaphoreLockTest.php b/tests/src/Core/Lock/SemaphoreLockTest.php index 06b4e02f46..30152ac427 100644 --- a/tests/src/Core/Lock/SemaphoreLockTest.php +++ b/tests/src/Core/Lock/SemaphoreLockTest.php @@ -12,6 +12,7 @@ use Friendica\App; use Friendica\Core\Config\Capability\IManageConfigValues; use Friendica\Core\Config\Model\ReadOnlyFileConfig; use Friendica\Core\Config\ValueObject\Cache; +use Friendica\Core\Lock\Capability\ICanLock; use Friendica\Core\Lock\Type\SemaphoreLock; use Friendica\Core\System; use Friendica\DI; @@ -31,7 +32,7 @@ class SemaphoreLockTest extends LockTestCase $dice->shouldReceive('create')->with(App::class)->andReturn($app); $configCache = new Cache(['system' => ['temppath' => '/tmp']]); - $configMock = new ReadOnlyFileConfig($configCache); + $configMock = new ReadOnlyFileConfig($configCache); $dice->shouldReceive('create')->with(IManageConfigValues::class)->andReturn($configMock); // @todo Because "get_temppath()" is using static methods, we have to initialize the BaseObject @@ -40,7 +41,7 @@ class SemaphoreLockTest extends LockTestCase parent::setUp(); } - protected function getInstance() + protected function getInstance(): ICanLock { return new SemaphoreLock(); } diff --git a/tests/src/Core/Logger/SyslogLoggerFactoryWrapper.php b/tests/src/Core/Logger/SyslogLoggerFactoryWrapper.php index 10b19d1991..6bc30bc439 100644 --- a/tests/src/Core/Logger/SyslogLoggerFactoryWrapper.php +++ b/tests/src/Core/Logger/SyslogLoggerFactoryWrapper.php @@ -17,16 +17,16 @@ class SyslogLoggerFactoryWrapper extends SyslogLogger { public function create(IManageConfigValues $config): LoggerInterface { - $logOpts = $config->get('system', 'syslog_flags') ?? SyslogLoggerClass::DEFAULT_FLAGS; - $logFacility = $config->get('system', 'syslog_facility') ?? SyslogLoggerClass::DEFAULT_FACILITY; + $logOpts = (int) $config->get('system', 'syslog_flags') ?? SyslogLoggerClass::DEFAULT_FLAGS; + $logFacility = (int) $config->get('system', 'syslog_facility') ?? SyslogLoggerClass::DEFAULT_FACILITY; $loglevel = SyslogLogger::mapLegacyConfigDebugLevel($config->get('system', 'loglevel')); - if (array_key_exists($loglevel, SyslogLoggerClass::logLevels)) { - $loglevel = SyslogLoggerClass::logLevels[$loglevel]; - } else { + if (!array_key_exists($loglevel, SyslogLoggerClass::logLevels)) { throw new LogLevelException(sprintf('The level "%s" is not valid.', $loglevel)); } + $loglevel = SyslogLoggerClass::logLevels[$loglevel]; + return new SyslogLoggerWrapper($this->channel, $this->introspection, $loglevel, $logOpts, $logFacility); } } diff --git a/tests/src/Core/Logger/SyslogLoggerWrapper.php b/tests/src/Core/Logger/SyslogLoggerWrapper.php index e0e360a50f..df2944c4c5 100644 --- a/tests/src/Core/Logger/SyslogLoggerWrapper.php +++ b/tests/src/Core/Logger/SyslogLoggerWrapper.php @@ -17,7 +17,7 @@ class SyslogLoggerWrapper extends SyslogLogger { private $content; - public function __construct(string $channel, IHaveCallIntrospections $introspection, string $logLevel, string $logOptions, string $logFacility) + public function __construct(string $channel, IHaveCallIntrospections $introspection, int $logLevel, int $logOptions, int $logFacility) { parent::__construct($channel, $introspection, $logLevel, $logOptions, $logFacility); diff --git a/tests/src/Core/Session/UserSessionTest.php b/tests/src/Core/Session/UserSessionTest.php index 64c7d8b950..cecae1d90c 100644 --- a/tests/src/Core/Session/UserSessionTest.php +++ b/tests/src/Core/Session/UserSessionTest.php @@ -152,13 +152,13 @@ class UserSessionTest extends MockedTestCase 'data' => [ 'remote' => ['3' => '21'], ], - 'expected' => false, + 'expected' => 0, ], 'empty' => [ 'cid' => 21, 'data' => [ ], - 'expected' => false, + 'expected' => 0, ], ]; } @@ -167,7 +167,7 @@ class UserSessionTest extends MockedTestCase public function testGetUserIdForVisitorContactID(int $cid, array $data, $expected) { $userSession = new UserSession(new ArraySession($data)); - $this->assertEquals($expected, $userSession->getUserIDForVisitorContactID($cid)); + $this->assertSame($expected, $userSession->getUserIDForVisitorContactID($cid)); } public function dataAuthenticated() diff --git a/tests/src/Core/Storage/Repository/StorageManagerTest.php b/tests/src/Core/Storage/Repository/StorageManagerTest.php index 7697f59381..8af22c4b78 100644 --- a/tests/src/Core/Storage/Repository/StorageManagerTest.php +++ b/tests/src/Core/Storage/Repository/StorageManagerTest.php @@ -23,12 +23,13 @@ use Friendica\Core\Storage\Type\SystemResource; use Friendica\Database\Database; use Friendica\DI; use Friendica\Core\Config\Factory\Config; +use Friendica\Core\Hooks\HookEventBridge; use Friendica\Core\Storage\Type; use Friendica\Test\DatabaseTestCase; use Friendica\Test\Util\CreateDatabaseTrait; use Friendica\Test\Util\Database\StaticDatabase; +use Friendica\Test\Util\FakeEventDispatcher; use org\bovigo\vfs\vfsStream; -use Psr\Log\LoggerInterface; use Psr\Log\NullLogger; use Friendica\Test\Util\SampleStorageBackend; @@ -38,8 +39,6 @@ class StorageManagerTest extends DatabaseTestCase /** @var IManageConfigValues */ private $config; - /** @var LoggerInterface */ - private $logger; /** @var L10n */ private $l10n; @@ -56,7 +55,6 @@ class StorageManagerTest extends DatabaseTestCase vfsStream::newDirectory(Type\FilesystemConfig::DEFAULT_BASE_FOLDER, 0777)->at($this->root); - $this->logger = new NullLogger(); $this->database = $this->getDbInstance(); $configFactory = new Config(); @@ -87,7 +85,14 @@ class StorageManagerTest extends DatabaseTestCase */ public function testInstance() { - $storageManager = new StorageManager($this->database, $this->config, $this->logger, $this->l10n, false); + $storageManager = new StorageManager( + $this->database, + $this->config, + new NullLogger(), + new FakeEventDispatcher(), + $this->l10n, + false + ); self::assertInstanceOf(StorageManager::class, $storageManager); } @@ -149,7 +154,14 @@ class StorageManagerTest extends DatabaseTestCase $this->config->set('storage', 'name', $name); } - $storageManager = new StorageManager($this->database, $this->config, $this->logger, $this->l10n, false); + $storageManager = new StorageManager( + $this->database, + $this->config, + new NullLogger(), + new FakeEventDispatcher(), + $this->l10n, + false + ); if ($interface === ICanWriteToStorage::class) { $storage = $storageManager->getWritableStorageByName($name); @@ -169,7 +181,14 @@ class StorageManagerTest extends DatabaseTestCase */ public function testIsValidBackend($name, $valid, $interface, $assert, $assertName) { - $storageManager = new StorageManager($this->database, $this->config, $this->logger, $this->l10n, false); + $storageManager = new StorageManager( + $this->database, + $this->config, + new NullLogger(), + new FakeEventDispatcher(), + $this->l10n, + false + ); // true in every of the backends self::assertEquals(!empty($assertName), $storageManager->isValidBackend($name)); @@ -183,7 +202,14 @@ class StorageManagerTest extends DatabaseTestCase */ public function testListBackends() { - $storageManager = new StorageManager($this->database, $this->config, $this->logger, $this->l10n, false); + $storageManager = new StorageManager( + $this->database, + $this->config, + new NullLogger(), + new FakeEventDispatcher(), + $this->l10n, + false + ); self::assertEquals(StorageManager::DEFAULT_BACKENDS, $storageManager->listBackends()); } @@ -199,7 +225,14 @@ class StorageManagerTest extends DatabaseTestCase static::markTestSkipped('only works for ICanWriteToStorage'); } - $storageManager = new StorageManager($this->database, $this->config, $this->logger, $this->l10n, false); + $storageManager = new StorageManager( + $this->database, + $this->config, + new NullLogger(), + new FakeEventDispatcher(), + $this->l10n, + false + ); $selBackend = $storageManager->getWritableStorageByName($name); $storageManager->setBackend($selBackend); @@ -219,7 +252,14 @@ class StorageManagerTest extends DatabaseTestCase $this->expectException(InvalidClassStorageException::class); } - $storageManager = new StorageManager($this->database, $this->config, $this->logger, $this->l10n, false); + $storageManager = new StorageManager( + $this->database, + $this->config, + new NullLogger(), + new FakeEventDispatcher(), + $this->l10n, + false + ); self::assertInstanceOf($assert, $storageManager->getBackend()); } @@ -240,7 +280,14 @@ class StorageManagerTest extends DatabaseTestCase ->addRule(IHandleSessions::class, ['instanceOf' => Memory::class, 'shared' => true, 'call' => null]); DI::init($dice); - $storageManager = new StorageManager($this->database, $this->config, $this->logger, $this->l10n, false); + $storageManager = new StorageManager( + $this->database, + $this->config, + new NullLogger(), + new FakeEventDispatcher(), + $this->l10n, + false + ); self::assertTrue($storageManager->register(SampleStorageBackend::class)); @@ -268,7 +315,21 @@ class StorageManagerTest extends DatabaseTestCase ->addRule(IHandleSessions::class, ['instanceOf' => Memory::class, 'shared' => true, 'call' => null]); DI::init($dice); - $storageManager = new StorageManager($this->database, $this->config, $this->logger, $this->l10n, false); + /** @var \Friendica\Event\EventDispatcher */ + $eventDispatcher = DI::eventDispatcher(); + + foreach (HookEventBridge::getStaticSubscribedEvents() as $eventName => $methodName) { + $eventDispatcher->addListener($eventName, [HookEventBridge::class, $methodName]); + } + + $storageManager = new StorageManager( + $this->database, + $this->config, + new NullLogger(), + $eventDispatcher, + $this->l10n, + false + ); self::assertTrue($storageManager->register(SampleStorageBackend::class)); @@ -307,8 +368,15 @@ class StorageManagerTest extends DatabaseTestCase $this->loadFixture(__DIR__ . '/../../../../datasets/storage/database.fixture.php', $this->database); - $storageManager = new StorageManager($this->database, $this->config, $this->logger, $this->l10n, false); - $storage = $storageManager->getWritableStorageByName($name); + $storageManager = new StorageManager( + $this->database, + $this->config, + new NullLogger(), + new FakeEventDispatcher(), + $this->l10n, + false + ); + $storage = $storageManager->getWritableStorageByName($name); $storageManager->move($storage); $photos = $this->database->select('photo', ['backend-ref', 'backend-class', 'id', 'data']); @@ -331,8 +399,15 @@ class StorageManagerTest extends DatabaseTestCase $this->expectException(InvalidClassStorageException::class); $this->expectExceptionMessage('Backend SystemResource is not valid'); - $storageManager = new StorageManager($this->database, $this->config, $this->logger, $this->l10n, false); - $storage = $storageManager->getWritableStorageByName(SystemResource::getName()); + $storageManager = new StorageManager( + $this->database, + $this->config, + new NullLogger(), + new FakeEventDispatcher(), + $this->l10n, + false + ); + $storage = $storageManager->getWritableStorageByName(SystemResource::getName()); $storageManager->move($storage); } } diff --git a/tests/src/Module/StatsCachingTest.php b/tests/src/Module/StatsCachingTest.php new file mode 100644 index 0000000000..58226769a9 --- /dev/null +++ b/tests/src/Module/StatsCachingTest.php @@ -0,0 +1,203 @@ +httpExceptionMock = \Mockery::mock(HTTPException::class); + $this->config = \Mockery::mock(IManageConfigValues::class); + $this->cache = new ArrayCache('localhost'); + $this->lock = new CacheLock($this->cache); + } + + public function testStatsCachingNotAllowed() + { + $this->httpExceptionMock->shouldReceive('content')->andReturn('failed')->once(); + + $response = (new StatsCaching(DI::l10n(), DI::baseUrl(), DI::args(), DI::logger(), DI::profiler(), DI::apiResponse(), [], $this->config, $this->cache, $this->lock, [])) + ->run($this->httpExceptionMock); + + self::assertEquals('404', $response->getStatusCode()); + self::assertEquals('Page not found', $response->getReasonPhrase()); + self::assertEquals('failed', $response->getBody()); + } + + public function testStatsCachingWitMinimumCache() + { + $request = [ + 'key' => '12345', + ]; + $this->config->shouldReceive('get')->with('system', 'stats_key')->twice()->andReturn('12345'); + PHPMockery::mock("Friendica\\Module", "function_exists")->with('opcache_get_status')->once()->andReturn(false); + + $response = (new StatsCaching(DI::l10n(), DI::baseUrl(), DI::args(), DI::logger(), DI::profiler(), DI::apiResponse(), [], $this->config, $this->cache, $this->lock, [])) + ->run($this->httpExceptionMock, $request); + + self::assertJson($response->getBody()); + self::assertEquals(['Content-type' => ['application/json; charset=utf-8'], ICanCreateResponses::X_HEADER => ['json']], $response->getHeaders()); + + $json = json_decode($response->getBody(), true); + + self::assertEquals([ + 'type' => 'array', + 'stats' => [], + ], $json['cache']); + self::assertEquals([ + 'type' => 'array', + 'stats' => [], + ], $json['lock']); + } + + public function testStatsCachingWithDatabase() + { + $request = [ + 'key' => '12345', + ]; + $this->config->shouldReceive('get')->with('system', 'stats_key')->twice()->andReturn('12345'); + + $this->cache = new DatabaseCache('localhost', DI::dba()); + $this->lock = new DatabaseLock(DI::dba()); + PHPMockery::mock("Friendica\\Module", "function_exists")->with('opcache_get_status')->once()->andReturn(false); + + $response = (new StatsCaching(DI::l10n(), DI::baseUrl(), DI::args(), DI::logger(), DI::profiler(), DI::apiResponse(), [], $this->config, $this->cache, $this->lock, [])) + ->run($this->httpExceptionMock, $request); + + self::assertJson($response->getBody()); + self::assertEquals(['Content-type' => ['application/json; charset=utf-8'], ICanCreateResponses::X_HEADER => ['json']], $response->getHeaders()); + + $json = json_decode($response->getBody(), true); + + self::assertEquals(['enabled' => false], $json['opcache']); + self::assertEquals(['type' => 'database'], $json['cache']); + self::assertEquals(['type' => 'database'], $json['lock']); + } + + public function testStatsCachingWithCache() + { + $request = [ + 'key' => '12345', + ]; + $this->config->shouldReceive('get')->with('system', 'stats_key')->twice()->andReturn('12345'); + + $this->cache = new DatabaseCache('localhost', DI::dba()); + $this->lock = new DatabaseLock(DI::dba()); + PHPMockery::mock("Friendica\\Module", "function_exists")->with('opcache_get_status')->once()->andReturn(false); + + $response = (new StatsCaching(DI::l10n(), DI::baseUrl(), DI::args(), DI::logger(), DI::profiler(), DI::apiResponse(), [], $this->config, $this->cache, $this->lock, [])) + ->run($this->httpExceptionMock, $request); + + self::assertJson($response->getBody()); + self::assertEquals(['Content-type' => ['application/json; charset=utf-8'], ICanCreateResponses::X_HEADER => ['json']], $response->getHeaders()); + + $json = json_decode($response->getBody(), true); + + self::assertEquals(['enabled' => false], $json['opcache']); + self::assertEquals(['type' => 'database'], $json['cache']); + self::assertEquals(['type' => 'database'], $json['lock']); + } + + public function testStatsCachingWithOpcacheAndNull() + { + $request = [ + 'key' => '12345', + ]; + $this->config->shouldReceive('get')->with('system', 'stats_key')->twice()->andReturn('12345'); + + $this->cache = new DatabaseCache('localhost', DI::dba()); + $this->lock = new DatabaseLock(DI::dba()); + PHPMockery::mock("Friendica\\Module", "function_exists")->with('opcache_get_status')->once()->andReturn(true); + PHPMockery::mock("Friendica\\Module", "opcache_get_status")->with(false)->once()->andReturn(false); + + $response = (new StatsCaching(DI::l10n(), DI::baseUrl(), DI::args(), DI::logger(), DI::profiler(), DI::apiResponse(), [], $this->config, $this->cache, $this->lock, [])) + ->run($this->httpExceptionMock, $request); + + self::assertJson($response->getBody()); + self::assertEquals(['Content-type' => ['application/json; charset=utf-8'], ICanCreateResponses::X_HEADER => ['json']], $response->getHeaders()); + + $json = json_decode($response->getBody(), true); + + self::assertEquals([ + 'enabled' => false, + 'hit_rate' => null, + 'used_memory' => null, + 'free_memory' => null, + 'num_cached_scripts' => null, + ], $json['opcache']); + self::assertEquals(['type' => 'database'], $json['cache']); + self::assertEquals(['type' => 'database'], $json['lock']); + } + + public function testStatsCachingWithOpcacheAndValues() + { + $request = [ + 'key' => '12345', + ]; + $this->config->shouldReceive('get')->with('system', 'stats_key')->twice()->andReturn('12345'); + + $this->cache = new DatabaseCache('localhost', DI::dba()); + $this->lock = new DatabaseLock(DI::dba()); + PHPMockery::mock("Friendica\\Module", "function_exists")->with('opcache_get_status')->once()->andReturn(true); + PHPMockery::mock("Friendica\\Module", "opcache_get_status")->with(false)->once()->andReturn([ + 'opcache_enabled' => true, + 'opcache_statistics' => [ + 'opcache_hit_rate' => 1, + 'num_cached_scripts' => 2, + ], + 'memory_usage' => [ + 'used_memory' => 3, + 'free_memory' => 4, + ] + ]); + + $response = (new StatsCaching(DI::l10n(), DI::baseUrl(), DI::args(), DI::logger(), DI::profiler(), DI::apiResponse(), [], $this->config, $this->cache, $this->lock, [])) + ->run($this->httpExceptionMock, $request); + + self::assertJson($response->getBody()); + self::assertEquals(['Content-type' => ['application/json; charset=utf-8'], ICanCreateResponses::X_HEADER => ['json']], $response->getHeaders()); + + $json = json_decode($response->getBody(), true); + + self::assertEquals([ + 'enabled' => true, + 'hit_rate' => 1, + 'used_memory' => 3, + 'free_memory' => 4, + 'num_cached_scripts' => 2, + ], $json['opcache']); + self::assertEquals(['type' => 'database'], $json['cache']); + self::assertEquals(['type' => 'database'], $json['lock']); + } +} diff --git a/view/lang/C/messages.po b/view/lang/C/messages.po index 2cfc48efbc..417322fd6c 100644 --- a/view/lang/C/messages.po +++ b/view/lang/C/messages.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: 2025.02-dev\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2025-02-11 07:41+0000\n" +"POT-Creation-Date: 2025-05-17 21:51+0200\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -18,34 +18,34 @@ msgstr "" "Content-Transfer-Encoding: 8bit\n" -#: mod/item.php:87 mod/item.php:90 mod/item.php:157 mod/item.php:160 +#: mod/item.php:91 mod/item.php:94 mod/item.php:161 mod/item.php:164 msgid "Unable to locate original post." msgstr "" -#: mod/item.php:125 +#: mod/item.php:129 msgid "Post updated." msgstr "" -#: mod/item.php:190 mod/item.php:194 +#: mod/item.php:194 mod/item.php:198 msgid "Item wasn't stored." msgstr "" -#: mod/item.php:204 +#: mod/item.php:208 msgid "Item couldn't be fetched." msgstr "" -#: mod/item.php:248 mod/item.php:252 +#: mod/item.php:252 mod/item.php:256 msgid "Empty post discarded." msgstr "" -#: mod/item.php:423 src/Module/Admin/Themes/Details.php:31 +#: mod/item.php:437 src/Module/Admin/Themes/Details.php:31 #: src/Module/Admin/Themes/Index.php:51 src/Module/Debug/ItemBody.php:34 #: src/Module/Debug/ItemBody.php:42 src/Module/Item/Feed.php:66 msgid "Item not found." msgstr "" -#: mod/item.php:447 mod/message.php:54 mod/message.php:100 mod/notes.php:34 -#: mod/photos.php:131 mod/photos.php:623 src/Model/Event.php:506 +#: mod/item.php:461 mod/message.php:54 mod/message.php:100 mod/notes.php:34 +#: mod/photos.php:131 mod/photos.php:623 src/Model/Event.php:511 #: src/Module/Attach.php:40 src/Module/BaseApi.php:90 #: src/Module/BaseNotifications.php:83 src/Module/BaseSettings.php:38 #: src/Module/Calendar/Event/API.php:75 src/Module/Calendar/Event/Form.php:70 @@ -59,12 +59,12 @@ msgstr "" #: src/Module/Invite.php:28 src/Module/Invite.php:116 #: src/Module/Notifications/Notification.php:62 #: src/Module/Notifications/Notification.php:93 -#: src/Module/OStatus/Subscribe.php:54 src/Module/Post/Edit.php:67 +#: src/Module/OStatus/Subscribe.php:54 src/Module/Post/Edit.php:72 #: src/Module/Profile/Common.php:63 src/Module/Profile/Contacts.php:66 -#: src/Module/Profile/Photos.php:81 src/Module/Profile/Schedule.php:25 -#: src/Module/Profile/Schedule.php:42 src/Module/Register.php:70 -#: src/Module/Register.php:83 src/Module/Register.php:199 -#: src/Module/Register.php:238 src/Module/Search/Directory.php:23 +#: src/Module/Profile/Photos.php:100 src/Module/Profile/Schedule.php:25 +#: src/Module/Profile/Schedule.php:42 src/Module/Register.php:74 +#: src/Module/Register.php:87 src/Module/Register.php:212 +#: src/Module/Register.php:251 src/Module/Search/Directory.php:23 #: src/Module/Settings/Account.php:34 src/Module/Settings/Account.php:337 #: src/Module/Settings/Channels.php:52 src/Module/Settings/Channels.php:127 #: src/Module/Settings/ContactImport.php:49 @@ -77,7 +77,7 @@ msgstr "" #: src/Module/Settings/UserExport.php:100 #: src/Module/Settings/UserExport.php:199 #: src/Module/Settings/UserExport.php:219 -#: src/Module/Settings/UserExport.php:284 src/Module/User/Delegation.php:142 +#: src/Module/Settings/UserExport.php:284 src/Module/User/Delegation.php:146 #: src/Module/User/Import.php:71 src/Module/User/Import.php:78 msgid "Permission denied." msgstr "" @@ -261,7 +261,7 @@ msgid "Send Private Message" msgstr "" #: mod/message.php:178 mod/message.php:333 -#: src/Module/Privacy/PermissionTooltip.php:118 +#: src/Module/Privacy/PermissionTooltip.php:149 msgid "To:" msgstr "" @@ -273,41 +273,41 @@ msgstr "" msgid "Your message:" msgstr "" -#: mod/message.php:186 mod/message.php:341 src/Content/Conversation.php:358 -#: src/Module/Post/Edit.php:122 +#: mod/message.php:186 mod/message.php:341 src/Content/Conversation.php:362 +#: src/Module/Post/Edit.php:127 msgid "Upload photo" msgstr "" -#: mod/message.php:187 mod/message.php:342 src/Module/Post/Edit.php:126 +#: mod/message.php:187 mod/message.php:342 src/Module/Post/Edit.php:131 msgid "Insert web link" msgstr "" -#: mod/message.php:188 mod/message.php:344 mod/photos.php:1252 -#: src/Content/Conversation.php:389 src/Content/Conversation.php:1565 -#: src/Module/Item/Compose.php:202 src/Module/Post/Edit.php:136 -#: src/Object/Post.php:608 +#: mod/message.php:188 mod/message.php:344 mod/photos.php:1256 +#: src/Content/Conversation.php:393 src/Content/Conversation.php:1572 +#: src/Module/Item/Compose.php:205 src/Module/Post/Edit.php:141 +#: src/Object/Post.php:613 msgid "Please wait" msgstr "" -#: mod/message.php:189 mod/message.php:343 mod/photos.php:654 -#: mod/photos.php:774 mod/photos.php:1051 mod/photos.php:1093 -#: mod/photos.php:1149 mod/photos.php:1229 +#: mod/message.php:189 mod/message.php:343 mod/photos.php:658 +#: mod/photos.php:778 mod/photos.php:1055 mod/photos.php:1097 +#: mod/photos.php:1153 mod/photos.php:1233 #: src/Module/Calendar/Event/Form.php:236 src/Module/Contact/Advanced.php:118 -#: src/Module/Contact/Profile.php:376 +#: src/Module/Contact/Profile.php:407 #: src/Module/Debug/ActivityPubConversion.php:128 #: src/Module/Debug/Babel.php:279 src/Module/Debug/Localtime.php:50 #: src/Module/Debug/Probe.php:40 src/Module/Debug/WebFinger.php:37 #: src/Module/FriendSuggest.php:132 src/Module/Install.php:219 #: src/Module/Install.php:259 src/Module/Install.php:296 -#: src/Module/Invite.php:162 src/Module/Item/Compose.php:185 +#: src/Module/Invite.php:162 src/Module/Item/Compose.php:188 #: src/Module/Moderation/Item/Source.php:74 -#: src/Module/Moderation/Report/Create.php:154 -#: src/Module/Moderation/Report/Create.php:169 -#: src/Module/Moderation/Report/Create.php:197 -#: src/Module/Moderation/Report/Create.php:249 -#: src/Module/Profile/Profile.php:265 src/Module/Settings/Profile/Index.php:248 -#: src/Module/Settings/Server/Action.php:65 src/Module/User/Delegation.php:177 -#: src/Object/Post.php:1149 view/theme/duepuntozero/config.php:73 +#: src/Module/Moderation/Report/Create.php:153 +#: src/Module/Moderation/Report/Create.php:168 +#: src/Module/Moderation/Report/Create.php:196 +#: src/Module/Moderation/Report/Create.php:256 +#: src/Module/Profile/Profile.php:283 src/Module/Settings/Profile/Index.php:272 +#: src/Module/Settings/Server/Action.php:65 src/Module/User/Delegation.php:181 +#: src/Object/Post.php:1159 view/theme/duepuntozero/config.php:73 #: view/theme/frio/config.php:155 view/theme/quattro/config.php:75 #: view/theme/vier/config.php:123 msgid "Submit" @@ -371,41 +371,41 @@ msgstr "" msgid "Personal notes are visible only by yourself." msgstr "" -#: mod/notes.php:46 src/Content/Text/HTML.php:847 +#: mod/notes.php:46 src/Content/Text/HTML.php:855 #: src/Module/Admin/Storage.php:128 src/Module/Filer/SaveTag.php:60 -#: src/Module/Post/Edit.php:120 src/Module/Settings/Channels.php:215 +#: src/Module/Post/Edit.php:125 src/Module/Settings/Channels.php:220 msgid "Save" msgstr "" #: mod/photos.php:50 mod/photos.php:113 mod/photos.php:533 -#: src/Model/Event.php:498 src/Model/Profile.php:211 +#: src/Model/Event.php:503 src/Model/Profile.php:211 #: src/Module/Calendar/Export.php:60 src/Module/Calendar/Show.php:62 #: src/Module/Feed.php:52 src/Module/HCard.php:37 #: src/Module/Profile/Common.php:50 src/Module/Profile/Common.php:59 #: src/Module/Profile/Contacts.php:52 src/Module/Profile/Contacts.php:60 #: src/Module/Profile/Conversations.php:81 src/Module/Profile/Media.php:58 -#: src/Module/Profile/Photos.php:72 src/Module/Profile/RemoteFollow.php:57 -#: src/Module/Register.php:260 +#: src/Module/Profile/Photos.php:91 src/Module/Profile/RemoteFollow.php:57 +#: src/Module/Register.php:273 msgid "User not found." msgstr "" #: mod/photos.php:87 src/Module/BaseProfile.php:53 -#: src/Module/Profile/Photos.php:372 +#: src/Module/Profile/Photos.php:419 msgid "Photo Albums" msgstr "" -#: mod/photos.php:88 src/Module/Profile/Photos.php:373 -#: src/Module/Profile/Photos.php:393 +#: mod/photos.php:88 src/Module/Profile/Photos.php:420 +#: src/Module/Profile/Photos.php:440 msgid "Recent Photos" msgstr "" -#: mod/photos.php:90 mod/photos.php:822 src/Module/Profile/Photos.php:375 -#: src/Module/Profile/Photos.php:395 +#: mod/photos.php:90 mod/photos.php:826 src/Module/Profile/Photos.php:422 +#: src/Module/Profile/Photos.php:442 msgid "Upload New Photos" msgstr "" #: mod/photos.php:102 src/Module/BaseSettings.php:60 -#: src/Module/Profile/Photos.php:356 +#: src/Module/Profile/Photos.php:403 msgid "everybody" msgstr "" @@ -439,7 +439,7 @@ msgid "%1$s was tagged in %2$s by %3$s" msgstr "" #: mod/photos.php:537 src/Module/Conversation/Community.php:148 -#: src/Module/Directory.php:34 src/Module/Profile/Photos.php:290 +#: src/Module/Directory.php:34 src/Module/Profile/Photos.php:337 #: src/Module/Search/Index.php:50 msgid "Public access denied." msgstr "" @@ -448,176 +448,176 @@ msgstr "" msgid "No photos selected" msgstr "" -#: mod/photos.php:670 +#: mod/photos.php:674 #, php-format msgid "The maximum accepted image size is %s" msgstr "" -#: mod/photos.php:677 +#: mod/photos.php:681 msgid "Upload Photos" msgstr "" -#: mod/photos.php:681 mod/photos.php:770 +#: mod/photos.php:685 mod/photos.php:774 msgid "New album name: " msgstr "" -#: mod/photos.php:682 +#: mod/photos.php:686 msgid "or select existing album:" msgstr "" -#: mod/photos.php:683 +#: mod/photos.php:687 msgid "Do not show a status post for this upload" msgstr "" -#: mod/photos.php:686 mod/photos.php:1047 src/Content/Conversation.php:391 -#: src/Module/Calendar/Event/Form.php:239 src/Module/Post/Edit.php:174 +#: mod/photos.php:690 mod/photos.php:1051 src/Content/Conversation.php:395 +#: src/Module/Calendar/Event/Form.php:239 src/Module/Post/Edit.php:179 msgid "Permissions" msgstr "" -#: mod/photos.php:751 +#: mod/photos.php:755 msgid "Do you really want to delete this photo album and all its photos?" msgstr "" -#: mod/photos.php:752 mod/photos.php:775 +#: mod/photos.php:756 mod/photos.php:779 msgid "Delete Album" msgstr "" -#: mod/photos.php:753 mod/photos.php:853 src/Content/Conversation.php:406 +#: mod/photos.php:757 mod/photos.php:857 src/Content/Conversation.php:410 #: src/Module/Contact/Follow.php:158 src/Module/Contact/Revoke.php:92 #: src/Module/Contact/Unfollow.php:112 #: src/Module/Media/Attachment/Browser.php:64 -#: src/Module/Media/Photo/Browser.php:76 src/Module/Post/Edit.php:158 +#: src/Module/Media/Photo/Browser.php:76 src/Module/Post/Edit.php:163 #: src/Module/Post/Tag/Remove.php:96 src/Module/Profile/RemoteFollow.php:120 #: src/Module/Security/TwoFactor/SignOut.php:111 msgid "Cancel" msgstr "" -#: mod/photos.php:779 +#: mod/photos.php:783 msgid "Edit Album" msgstr "" -#: mod/photos.php:780 +#: mod/photos.php:784 msgid "Drop Album" msgstr "" -#: mod/photos.php:784 +#: mod/photos.php:788 msgid "Show Newest First" msgstr "" -#: mod/photos.php:786 +#: mod/photos.php:790 msgid "Show Oldest First" msgstr "" -#: mod/photos.php:807 src/Module/Profile/Photos.php:343 +#: mod/photos.php:811 src/Module/Profile/Photos.php:390 msgid "View Photo" msgstr "" -#: mod/photos.php:839 +#: mod/photos.php:843 msgid "Permission denied. Access to this item may be restricted." msgstr "" -#: mod/photos.php:841 +#: mod/photos.php:845 msgid "Photo not available" msgstr "" -#: mod/photos.php:851 +#: mod/photos.php:855 msgid "Do you really want to delete this photo?" msgstr "" -#: mod/photos.php:852 mod/photos.php:1052 +#: mod/photos.php:856 mod/photos.php:1056 msgid "Delete Photo" msgstr "" -#: mod/photos.php:950 +#: mod/photos.php:954 msgid "View photo" msgstr "" -#: mod/photos.php:952 +#: mod/photos.php:956 msgid "Edit photo" msgstr "" -#: mod/photos.php:953 +#: mod/photos.php:957 msgid "Delete photo" msgstr "" -#: mod/photos.php:954 +#: mod/photos.php:958 msgid "Use as profile photo" msgstr "" -#: mod/photos.php:961 +#: mod/photos.php:965 msgid "Private Photo" msgstr "" -#: mod/photos.php:967 +#: mod/photos.php:971 msgid "View Full Size" msgstr "" -#: mod/photos.php:1020 +#: mod/photos.php:1024 msgid "Tags: " msgstr "" -#: mod/photos.php:1023 +#: mod/photos.php:1027 msgid "[Select tags to remove]" msgstr "" -#: mod/photos.php:1038 +#: mod/photos.php:1042 msgid "New album name" msgstr "" -#: mod/photos.php:1039 +#: mod/photos.php:1043 msgid "Caption" msgstr "" -#: mod/photos.php:1040 +#: mod/photos.php:1044 msgid "Add a Tag" msgstr "" -#: mod/photos.php:1040 +#: mod/photos.php:1044 msgid "Example: @bob, @Barbara_Jensen, @jim@example.com, #California, #camping" msgstr "" -#: mod/photos.php:1041 +#: mod/photos.php:1045 msgid "Do not rotate" msgstr "" -#: mod/photos.php:1042 +#: mod/photos.php:1046 msgid "Rotate CW (right)" msgstr "" -#: mod/photos.php:1043 +#: mod/photos.php:1047 msgid "Rotate CCW (left)" msgstr "" -#: mod/photos.php:1090 mod/photos.php:1146 mod/photos.php:1226 -#: src/Module/Contact.php:599 src/Module/Item/Compose.php:184 -#: src/Object/Post.php:1146 +#: mod/photos.php:1094 mod/photos.php:1150 mod/photos.php:1230 +#: src/Module/Contact.php:599 src/Module/Item/Compose.php:187 +#: src/Object/Post.php:1156 msgid "This is you" msgstr "" -#: mod/photos.php:1092 mod/photos.php:1148 mod/photos.php:1228 -#: src/Module/Moderation/Reports.php:105 src/Object/Post.php:602 -#: src/Object/Post.php:1148 +#: mod/photos.php:1096 mod/photos.php:1152 mod/photos.php:1232 +#: src/Module/Moderation/Reports.php:105 src/Object/Post.php:607 +#: src/Object/Post.php:1158 msgid "Comment" msgstr "" -#: mod/photos.php:1094 mod/photos.php:1150 mod/photos.php:1230 -#: src/Content/Conversation.php:403 src/Module/Calendar/Event/Form.php:234 -#: src/Module/Item/Compose.php:197 src/Module/Post/Edit.php:156 -#: src/Object/Post.php:1162 +#: mod/photos.php:1098 mod/photos.php:1154 mod/photos.php:1234 +#: src/Content/Conversation.php:407 src/Module/Calendar/Event/Form.php:234 +#: src/Module/Item/Compose.php:200 src/Module/Post/Edit.php:161 +#: src/Object/Post.php:1172 msgid "Preview" msgstr "" -#: mod/photos.php:1095 src/Content/Conversation.php:357 -#: src/Module/Post/Edit.php:121 src/Object/Post.php:1150 +#: mod/photos.php:1099 src/Content/Conversation.php:361 +#: src/Module/Post/Edit.php:126 src/Object/Post.php:1160 msgid "Loading..." msgstr "" -#: mod/photos.php:1187 src/Content/Conversation.php:1487 -#: src/Object/Post.php:260 +#: mod/photos.php:1191 src/Content/Conversation.php:1494 +#: src/Object/Post.php:259 msgid "Select" msgstr "" -#: mod/photos.php:1188 src/Content/Conversation.php:1488 +#: mod/photos.php:1192 src/Content/Conversation.php:1495 #: src/Module/Moderation/Users/Active.php:92 #: src/Module/Moderation/Users/Blocked.php:92 #: src/Module/Moderation/Users/Index.php:100 @@ -626,23 +626,23 @@ msgstr "" msgid "Delete" msgstr "" -#: mod/photos.php:1249 src/Object/Post.php:426 +#: mod/photos.php:1253 src/Object/Post.php:431 msgid "Like" msgstr "" -#: mod/photos.php:1250 src/Object/Post.php:426 +#: mod/photos.php:1254 src/Object/Post.php:431 msgid "I like this (toggle)" msgstr "" -#: mod/photos.php:1251 src/Object/Post.php:427 +#: mod/photos.php:1255 src/Object/Post.php:432 msgid "Dislike" msgstr "" -#: mod/photos.php:1253 src/Object/Post.php:427 +#: mod/photos.php:1257 src/Object/Post.php:432 msgid "I don't like this (toggle)" msgstr "" -#: mod/photos.php:1275 +#: mod/photos.php:1279 msgid "Map" msgstr "" @@ -752,17 +752,17 @@ msgstr "" msgid "Close" msgstr "" -#: src/App/Router.php:287 +#: src/App/Router.php:294 #, php-format msgid "Method not allowed for this module. Allowed method(s): %s" msgstr "" -#: src/App/Router.php:289 src/Module/HTTPException/PageNotFound.php:35 -#: src/Module/Stats.php:56 +#: src/App/Router.php:296 src/Module/HTTPException/PageNotFound.php:35 +#: src/Module/Stats.php:72 src/Module/StatsCaching.php:54 msgid "Page not found." msgstr "" -#: src/App/Router.php:301 +#: src/App/Router.php:308 msgid "You must be logged in to use addons. " msgstr "" @@ -779,9 +779,9 @@ msgid "All contacts" msgstr "" #: src/BaseModule.php:440 src/Content/Conversation/Factory/Channel.php:32 -#: src/Content/Widget.php:257 src/Core/ACL.php:182 src/Module/Contact.php:394 -#: src/Module/Privacy/PermissionTooltip.php:150 -#: src/Module/Privacy/PermissionTooltip.php:172 +#: src/Content/Widget.php:257 src/Core/ACL.php:185 src/Module/Contact.php:394 +#: src/Module/Privacy/PermissionTooltip.php:181 +#: src/Module/Privacy/PermissionTooltip.php:203 #: src/Module/Settings/Channels.php:146 msgid "Followers" msgstr "" @@ -949,7 +949,7 @@ msgstr "" msgid "Enter user nickname: " msgstr "" -#: src/Console/User.php:168 src/Model/User.php:833 +#: src/Console/User.php:168 src/Model/User.php:837 #: src/Module/Api/Twitter/ContactEndpoint.php:62 #: src/Module/Moderation/Users/Active.php:136 #: src/Module/Moderation/Users/Blocked.php:135 @@ -1049,19 +1049,19 @@ msgstr "" msgid "Monthly" msgstr "" -#: src/Content/ContactSelector.php:117 +#: src/Content/ContactSelector.php:119 msgid "DFRN" msgstr "" -#: src/Content/ContactSelector.php:118 +#: src/Content/ContactSelector.php:120 msgid "OStatus" msgstr "" -#: src/Content/ContactSelector.php:119 +#: src/Content/ContactSelector.php:121 msgid "RSS/Atom" msgstr "" -#: src/Content/ContactSelector.php:120 +#: src/Content/ContactSelector.php:122 #: src/Module/Moderation/Users/Active.php:82 #: src/Module/Moderation/Users/Blocked.php:82 #: src/Module/Moderation/Users/Create.php:58 @@ -1072,67 +1072,67 @@ msgstr "" msgid "Email" msgstr "" -#: src/Content/ContactSelector.php:121 src/Module/Debug/Babel.php:273 +#: src/Content/ContactSelector.php:123 src/Module/Debug/Babel.php:273 msgid "Diaspora" msgstr "" -#: src/Content/ContactSelector.php:122 +#: src/Content/ContactSelector.php:124 msgid "Zot!" msgstr "" -#: src/Content/ContactSelector.php:123 +#: src/Content/ContactSelector.php:125 msgid "LinkedIn" msgstr "" -#: src/Content/ContactSelector.php:124 +#: src/Content/ContactSelector.php:126 msgid "XMPP/IM" msgstr "" -#: src/Content/ContactSelector.php:125 +#: src/Content/ContactSelector.php:127 msgid "MySpace" msgstr "" -#: src/Content/ContactSelector.php:126 +#: src/Content/ContactSelector.php:128 msgid "Google+" msgstr "" -#: src/Content/ContactSelector.php:127 +#: src/Content/ContactSelector.php:129 msgid "pump.io" msgstr "" -#: src/Content/ContactSelector.php:128 +#: src/Content/ContactSelector.php:130 msgid "Twitter" msgstr "" -#: src/Content/ContactSelector.php:129 +#: src/Content/ContactSelector.php:131 msgid "Discourse" msgstr "" -#: src/Content/ContactSelector.php:130 +#: src/Content/ContactSelector.php:132 msgid "Diaspora Connector" msgstr "" -#: src/Content/ContactSelector.php:131 +#: src/Content/ContactSelector.php:133 msgid "GNU Social Connector" msgstr "" -#: src/Content/ContactSelector.php:132 +#: src/Content/ContactSelector.php:134 msgid "ActivityPub" msgstr "" -#: src/Content/ContactSelector.php:133 +#: src/Content/ContactSelector.php:135 msgid "pnut" msgstr "" -#: src/Content/ContactSelector.php:134 +#: src/Content/ContactSelector.php:136 msgid "Tumblr" msgstr "" -#: src/Content/ContactSelector.php:135 +#: src/Content/ContactSelector.php:137 msgid "Bluesky" msgstr "" -#: src/Content/ContactSelector.php:161 +#: src/Content/ContactSelector.php:165 #, php-format msgid "%s (via %s)" msgstr "" @@ -1195,316 +1195,309 @@ msgid_plural " like this" msgstr[0] "" msgstr[1] "" -#: src/Content/Conversation.php:268 -#, php-format -msgid " doesn't like this" -msgid_plural " don't like this" -msgstr[0] "" -msgstr[1] "" - -#: src/Content/Conversation.php:271 +#: src/Content/Conversation.php:274 #, php-format msgid " attends" msgid_plural " attend" msgstr[0] "" msgstr[1] "" -#: src/Content/Conversation.php:274 +#: src/Content/Conversation.php:277 #, php-format msgid " doesn't attend" msgid_plural " don't attend" msgstr[0] "" msgstr[1] "" -#: src/Content/Conversation.php:277 +#: src/Content/Conversation.php:280 #, php-format msgid " attends maybe" msgid_plural " attend maybe" msgstr[0] "" msgstr[1] "" -#: src/Content/Conversation.php:280 +#: src/Content/Conversation.php:283 #, php-format msgid " reshared this" msgid_plural " reshared this" msgstr[0] "" msgstr[1] "" -#: src/Content/Conversation.php:326 +#: src/Content/Conversation.php:329 msgid "Visible to everybody" msgstr "" -#: src/Content/Conversation.php:327 src/Module/Item/Compose.php:196 -#: src/Object/Post.php:1161 +#: src/Content/Conversation.php:330 src/Module/Item/Compose.php:199 +#: src/Object/Post.php:1171 msgid "Please enter a image/video/audio/webpage URL:" msgstr "" -#: src/Content/Conversation.php:328 +#: src/Content/Conversation.php:331 msgid "Tag term:" msgstr "" -#: src/Content/Conversation.php:329 +#: src/Content/Conversation.php:332 msgid "Save to Folder" msgstr "" -#: src/Content/Conversation.php:330 +#: src/Content/Conversation.php:333 msgid "Where are you right now?" msgstr "" -#: src/Content/Conversation.php:331 +#: src/Content/Conversation.php:334 msgid "Delete item(s)?" msgstr "" -#: src/Content/Conversation.php:343 src/Module/Item/Compose.php:171 +#: src/Content/Conversation.php:347 src/Module/Item/Compose.php:174 msgid "Created at" msgstr "" -#: src/Content/Conversation.php:353 +#: src/Content/Conversation.php:357 msgid "New Post" msgstr "" -#: src/Content/Conversation.php:356 +#: src/Content/Conversation.php:360 msgid "Share" msgstr "" -#: src/Content/Conversation.php:359 src/Module/Post/Edit.php:123 +#: src/Content/Conversation.php:363 src/Module/Post/Edit.php:128 msgid "upload photo" msgstr "" -#: src/Content/Conversation.php:360 src/Module/Post/Edit.php:124 +#: src/Content/Conversation.php:364 src/Module/Post/Edit.php:129 msgid "Attach file" msgstr "" -#: src/Content/Conversation.php:361 src/Module/Post/Edit.php:125 +#: src/Content/Conversation.php:365 src/Module/Post/Edit.php:130 msgid "attach file" msgstr "" -#: src/Content/Conversation.php:362 src/Module/Item/Compose.php:186 -#: src/Module/Post/Edit.php:162 src/Object/Post.php:1151 +#: src/Content/Conversation.php:366 src/Module/Item/Compose.php:189 +#: src/Module/Post/Edit.php:167 src/Object/Post.php:1161 msgid "Bold" msgstr "" -#: src/Content/Conversation.php:363 src/Module/Item/Compose.php:187 -#: src/Module/Post/Edit.php:163 src/Object/Post.php:1152 +#: src/Content/Conversation.php:367 src/Module/Item/Compose.php:190 +#: src/Module/Post/Edit.php:168 src/Object/Post.php:1162 msgid "Italic" msgstr "" -#: src/Content/Conversation.php:364 src/Module/Item/Compose.php:188 -#: src/Module/Post/Edit.php:164 src/Object/Post.php:1153 +#: src/Content/Conversation.php:368 src/Module/Item/Compose.php:191 +#: src/Module/Post/Edit.php:169 src/Object/Post.php:1163 msgid "Underline" msgstr "" -#: src/Content/Conversation.php:365 src/Module/Item/Compose.php:189 -#: src/Module/Post/Edit.php:165 src/Object/Post.php:1155 +#: src/Content/Conversation.php:369 src/Module/Item/Compose.php:192 +#: src/Module/Post/Edit.php:170 src/Object/Post.php:1165 msgid "Quote" msgstr "" -#: src/Content/Conversation.php:366 src/Module/Item/Compose.php:190 -#: src/Module/Post/Edit.php:166 src/Object/Post.php:1156 +#: src/Content/Conversation.php:370 src/Module/Item/Compose.php:193 +#: src/Module/Post/Edit.php:171 src/Object/Post.php:1166 msgid "Add emojis" msgstr "" -#: src/Content/Conversation.php:367 src/Module/Item/Compose.php:191 -#: src/Object/Post.php:1154 +#: src/Content/Conversation.php:371 src/Module/Item/Compose.php:194 +#: src/Object/Post.php:1164 msgid "Content Warning" msgstr "" -#: src/Content/Conversation.php:368 src/Module/Item/Compose.php:192 -#: src/Module/Post/Edit.php:167 src/Object/Post.php:1157 +#: src/Content/Conversation.php:372 src/Module/Item/Compose.php:195 +#: src/Module/Post/Edit.php:172 src/Object/Post.php:1167 msgid "Code" msgstr "" -#: src/Content/Conversation.php:369 src/Module/Item/Compose.php:193 -#: src/Object/Post.php:1158 +#: src/Content/Conversation.php:373 src/Module/Item/Compose.php:196 +#: src/Object/Post.php:1168 msgid "Image" msgstr "" -#: src/Content/Conversation.php:370 src/Module/Item/Compose.php:194 -#: src/Module/Post/Edit.php:168 src/Object/Post.php:1159 +#: src/Content/Conversation.php:374 src/Module/Item/Compose.php:197 +#: src/Module/Post/Edit.php:173 src/Object/Post.php:1169 msgid "Link" msgstr "" -#: src/Content/Conversation.php:371 src/Module/Item/Compose.php:195 -#: src/Module/Post/Edit.php:169 src/Object/Post.php:1160 +#: src/Content/Conversation.php:375 src/Module/Item/Compose.php:198 +#: src/Module/Post/Edit.php:174 src/Object/Post.php:1170 msgid "Link or Media" msgstr "" -#: src/Content/Conversation.php:372 +#: src/Content/Conversation.php:376 msgid "Video" msgstr "" -#: src/Content/Conversation.php:373 src/Module/Item/Compose.php:198 -#: src/Module/Post/Edit.php:132 +#: src/Content/Conversation.php:377 src/Module/Item/Compose.php:201 +#: src/Module/Post/Edit.php:137 msgid "Set your location" msgstr "" -#: src/Content/Conversation.php:374 src/Module/Post/Edit.php:133 +#: src/Content/Conversation.php:378 src/Module/Post/Edit.php:138 msgid "set location" msgstr "" -#: src/Content/Conversation.php:375 src/Module/Post/Edit.php:134 +#: src/Content/Conversation.php:379 src/Module/Post/Edit.php:139 msgid "Clear browser location" msgstr "" -#: src/Content/Conversation.php:376 src/Module/Post/Edit.php:135 +#: src/Content/Conversation.php:380 src/Module/Post/Edit.php:140 msgid "clear location" msgstr "" -#: src/Content/Conversation.php:378 src/Module/Item/Compose.php:203 -#: src/Module/Post/Edit.php:148 +#: src/Content/Conversation.php:382 src/Module/Item/Compose.php:206 +#: src/Module/Post/Edit.php:153 msgid "Set title" msgstr "" -#: src/Content/Conversation.php:380 src/Module/Item/Compose.php:204 -#: src/Module/Post/Edit.php:150 +#: src/Content/Conversation.php:384 src/Module/Item/Compose.php:207 +#: src/Module/Post/Edit.php:155 msgid "Categories (comma-separated list)" msgstr "" -#: src/Content/Conversation.php:385 src/Module/Item/Compose.php:220 +#: src/Content/Conversation.php:389 src/Module/Item/Compose.php:227 msgid "Scheduled at" msgstr "" -#: src/Content/Conversation.php:390 src/Module/Post/Edit.php:137 +#: src/Content/Conversation.php:394 src/Module/Post/Edit.php:142 msgid "Permission settings" msgstr "" -#: src/Content/Conversation.php:399 src/Module/Post/Edit.php:146 +#: src/Content/Conversation.php:403 src/Module/Post/Edit.php:151 msgid "Public post" msgstr "" -#: src/Content/Conversation.php:413 src/Content/Widget/VCard.php:120 -#: src/Model/Profile.php:458 src/Module/Admin/Logs/View.php:80 -#: src/Module/Post/Edit.php:172 +#: src/Content/Conversation.php:417 src/Content/Widget/VCard.php:120 +#: src/Model/Profile.php:463 src/Module/Admin/Logs/View.php:80 +#: src/Module/Post/Edit.php:177 msgid "Message" msgstr "" -#: src/Content/Conversation.php:414 src/Module/Post/Edit.php:173 +#: src/Content/Conversation.php:418 src/Module/Post/Edit.php:178 #: src/Module/Settings/TwoFactor/Trusted.php:129 msgid "Browser" msgstr "" -#: src/Content/Conversation.php:416 src/Module/Post/Edit.php:176 +#: src/Content/Conversation.php:420 src/Module/Post/Edit.php:181 msgid "Open Compose page" msgstr "" -#: src/Content/Conversation.php:583 +#: src/Content/Conversation.php:590 msgid "remove" msgstr "" -#: src/Content/Conversation.php:587 +#: src/Content/Conversation.php:594 msgid "Delete Selected Items" msgstr "" -#: src/Content/Conversation.php:715 src/Content/Conversation.php:718 -#: src/Content/Conversation.php:721 src/Content/Conversation.php:724 -#: src/Content/Conversation.php:727 +#: src/Content/Conversation.php:718 src/Content/Conversation.php:721 +#: src/Content/Conversation.php:724 src/Content/Conversation.php:727 +#: src/Content/Conversation.php:730 #, php-format msgid "You had been addressed (%s)." msgstr "" -#: src/Content/Conversation.php:730 +#: src/Content/Conversation.php:733 #, php-format msgid "You are following %s." msgstr "" -#: src/Content/Conversation.php:735 +#: src/Content/Conversation.php:738 #, php-format msgid "You subscribed to %s." msgstr "" -#: src/Content/Conversation.php:737 +#: src/Content/Conversation.php:740 msgid "You subscribed to one or more tags in this post." msgstr "" -#: src/Content/Conversation.php:757 +#: src/Content/Conversation.php:760 #, php-format msgid "%s reshared this." msgstr "" -#: src/Content/Conversation.php:759 +#: src/Content/Conversation.php:762 msgid "Reshared" msgstr "" -#: src/Content/Conversation.php:759 -#, php-format -msgid "Reshared by %s <%s>" -msgstr "" - #: src/Content/Conversation.php:762 #, php-format -msgid "%s is participating in this thread." +msgid "Reshared by %s <%s>" msgstr "" #: src/Content/Conversation.php:765 -msgid "Stored for general reasons" +#, php-format +msgid "%s is participating in this thread." msgstr "" #: src/Content/Conversation.php:768 +msgid "Stored for general reasons" +msgstr "" + +#: src/Content/Conversation.php:771 msgid "Global post" msgstr "" -#: src/Content/Conversation.php:771 +#: src/Content/Conversation.php:774 msgid "Sent via an relay server" msgstr "" -#: src/Content/Conversation.php:771 +#: src/Content/Conversation.php:774 #, php-format msgid "Sent via the relay server %s <%s>" msgstr "" -#: src/Content/Conversation.php:774 +#: src/Content/Conversation.php:777 msgid "Fetched" msgstr "" -#: src/Content/Conversation.php:774 +#: src/Content/Conversation.php:777 #, php-format msgid "Fetched because of %s <%s>" msgstr "" -#: src/Content/Conversation.php:777 +#: src/Content/Conversation.php:780 msgid "Stored because of a child post to complete this thread." msgstr "" -#: src/Content/Conversation.php:780 +#: src/Content/Conversation.php:783 msgid "Local delivery" msgstr "" -#: src/Content/Conversation.php:783 +#: src/Content/Conversation.php:786 msgid "Stored because of your activity (like, comment, star, ...)" msgstr "" -#: src/Content/Conversation.php:786 +#: src/Content/Conversation.php:789 msgid "Distributed" msgstr "" -#: src/Content/Conversation.php:789 +#: src/Content/Conversation.php:792 msgid "Pushed to us" msgstr "" -#: src/Content/Conversation.php:1507 src/Object/Post.php:247 +#: src/Content/Conversation.php:1514 src/Object/Post.php:246 msgid "Pinned item" msgstr "" -#: src/Content/Conversation.php:1524 src/Object/Post.php:543 -#: src/Object/Post.php:544 +#: src/Content/Conversation.php:1531 src/Object/Post.php:548 +#: src/Object/Post.php:549 #, php-format msgid "View %s's profile @ %s" msgstr "" -#: src/Content/Conversation.php:1538 src/Object/Post.php:531 +#: src/Content/Conversation.php:1545 src/Object/Post.php:536 msgid "Categories:" msgstr "" -#: src/Content/Conversation.php:1539 src/Object/Post.php:532 +#: src/Content/Conversation.php:1546 src/Object/Post.php:537 msgid "Filed under:" msgstr "" -#: src/Content/Conversation.php:1547 src/Object/Post.php:559 +#: src/Content/Conversation.php:1554 src/Object/Post.php:564 #, php-format msgid "%s from %s" msgstr "" -#: src/Content/Conversation.php:1563 +#: src/Content/Conversation.php:1570 msgid "View in context" msgstr "" @@ -1558,7 +1551,7 @@ msgid "Posts from accounts that you follow but who don't post very often" msgstr "" #: src/Content/Conversation/Factory/Channel.php:35 -#: src/Module/Settings/Channels.php:185 src/Module/Settings/Channels.php:206 +#: src/Module/Settings/Channels.php:185 src/Module/Settings/Channels.php:211 msgid "Images" msgstr "" @@ -1567,7 +1560,7 @@ msgid "Posts with images" msgstr "" #: src/Content/Conversation/Factory/Channel.php:36 -#: src/Module/Settings/Channels.php:187 src/Module/Settings/Channels.php:208 +#: src/Module/Settings/Channels.php:187 src/Module/Settings/Channels.php:213 msgid "Audio" msgstr "" @@ -1576,7 +1569,7 @@ msgid "Posts with audio" msgstr "" #: src/Content/Conversation/Factory/Channel.php:37 -#: src/Module/Settings/Channels.php:186 src/Module/Settings/Channels.php:207 +#: src/Module/Settings/Channels.php:186 src/Module/Settings/Channels.php:212 msgid "Videos" msgstr "" @@ -1629,7 +1622,7 @@ msgid "Sort by post creation date" msgstr "" #: src/Content/Conversation/Factory/Network.php:27 -#: src/Module/Settings/Profile/Index.php:251 +#: src/Module/Settings/Profile/Index.php:275 msgid "Personal" msgstr "" @@ -1637,7 +1630,7 @@ msgstr "" msgid "Posts that mention or involve you" msgstr "" -#: src/Content/Conversation/Factory/Network.php:28 src/Object/Post.php:397 +#: src/Content/Conversation/Factory/Network.php:28 src/Object/Post.php:402 msgid "Starred" msgstr "" @@ -1712,8 +1705,8 @@ msgid "Display posts that have been created by accounts of the selected circle." msgstr "" #: src/Content/Feature.php:127 src/Content/GroupManager.php:128 -#: src/Content/Nav.php:274 src/Content/Text/HTML.php:868 -#: src/Content/Widget.php:558 src/Model/User.php:1393 +#: src/Content/Nav.php:274 src/Content/Text/HTML.php:876 +#: src/Content/Widget.php:558 src/Model/User.php:1397 msgid "Groups" msgstr "" @@ -1748,7 +1741,7 @@ msgstr "" #: src/Content/Feature.php:131 src/Content/Widget.php:613 #: src/Module/Admin/Site.php:463 src/Module/BaseSettings.php:113 -#: src/Module/Settings/Channels.php:211 src/Module/Settings/Display.php:313 +#: src/Module/Settings/Channels.php:216 src/Module/Settings/Display.php:313 msgid "Channels" msgstr "" @@ -1837,98 +1830,98 @@ msgstr "" msgid "Create new group" msgstr "" -#: src/Content/Item.php:321 src/Model/Item.php:2980 +#: src/Content/Item.php:324 src/Model/Item.php:3012 msgid "event" msgstr "" -#: src/Content/Item.php:324 src/Content/Item.php:334 +#: src/Content/Item.php:327 src/Content/Item.php:337 msgid "status" msgstr "" -#: src/Content/Item.php:330 src/Model/Item.php:2982 -#: src/Module/Post/Tag/Add.php:109 +#: src/Content/Item.php:333 src/Model/Item.php:3014 +#: src/Module/Post/Tag/Add.php:112 msgid "photo" msgstr "" -#: src/Content/Item.php:344 src/Module/Post/Tag/Add.php:127 +#: src/Content/Item.php:347 src/Module/Post/Tag/Add.php:130 #, php-format msgid "%1$s tagged %2$s's %3$s with %4$s" msgstr "" -#: src/Content/Item.php:418 view/theme/frio/theme.php:251 +#: src/Content/Item.php:421 view/theme/frio/theme.php:251 msgid "Follow Thread" msgstr "" -#: src/Content/Item.php:419 src/Model/Contact.php:1253 +#: src/Content/Item.php:422 src/Model/Contact.php:1293 msgid "View Status" msgstr "" -#: src/Content/Item.php:420 src/Content/Item.php:443 src/Model/Contact.php:1188 -#: src/Model/Contact.php:1244 src/Model/Contact.php:1254 -#: src/Module/Directory.php:143 src/Module/Settings/Profile/Index.php:250 +#: src/Content/Item.php:423 src/Content/Item.php:446 src/Model/Contact.php:1228 +#: src/Model/Contact.php:1284 src/Model/Contact.php:1294 +#: src/Module/Directory.php:143 src/Module/Settings/Profile/Index.php:274 msgid "View Profile" msgstr "" -#: src/Content/Item.php:421 src/Model/Contact.php:1255 +#: src/Content/Item.php:424 src/Model/Contact.php:1295 msgid "View Photos" msgstr "" -#: src/Content/Item.php:422 src/Model/Contact.php:1222 -#: src/Model/Profile.php:443 +#: src/Content/Item.php:425 src/Model/Contact.php:1262 +#: src/Model/Profile.php:448 msgid "Network Posts" msgstr "" -#: src/Content/Item.php:423 src/Model/Contact.php:1246 -#: src/Model/Contact.php:1257 +#: src/Content/Item.php:426 src/Model/Contact.php:1286 +#: src/Model/Contact.php:1297 msgid "View Contact" msgstr "" -#: src/Content/Item.php:424 src/Model/Contact.php:1258 +#: src/Content/Item.php:427 src/Model/Contact.php:1298 msgid "Send PM" msgstr "" -#: src/Content/Item.php:425 src/Module/Contact.php:448 -#: src/Module/Contact/Profile.php:524 +#: src/Content/Item.php:428 src/Module/Contact.php:448 +#: src/Module/Contact/Profile.php:560 #: src/Module/Moderation/Blocklist/Contact.php:104 #: src/Module/Moderation/Users/Active.php:93 #: src/Module/Moderation/Users/Index.php:101 msgid "Block" msgstr "" -#: src/Content/Item.php:426 src/Module/Contact.php:449 -#: src/Module/Contact/Profile.php:532 +#: src/Content/Item.php:429 src/Module/Contact.php:449 +#: src/Module/Contact/Profile.php:568 #: src/Module/Notifications/Introductions.php:126 #: src/Module/Notifications/Introductions.php:199 #: src/Module/Notifications/Notification.php:75 msgid "Ignore" msgstr "" -#: src/Content/Item.php:427 src/Module/Contact.php:450 -#: src/Module/Contact/Profile.php:540 +#: src/Content/Item.php:430 src/Module/Contact.php:450 +#: src/Module/Contact/Profile.php:576 msgid "Collapse" msgstr "" -#: src/Content/Item.php:428 src/Object/Post.php:288 +#: src/Content/Item.php:431 src/Object/Post.php:287 #, php-format msgid "Ignore %s server" msgstr "" -#: src/Content/Item.php:432 src/Module/Settings/Channels.php:188 -#: src/Module/Settings/Channels.php:209 src/Object/Post.php:503 +#: src/Content/Item.php:435 src/Module/Settings/Channels.php:188 +#: src/Module/Settings/Channels.php:214 src/Object/Post.php:508 msgid "Languages" msgstr "" -#: src/Content/Item.php:435 src/Object/Post.php:586 +#: src/Content/Item.php:438 src/Object/Post.php:591 msgid "Search Text" msgstr "" -#: src/Content/Item.php:440 src/Content/Widget.php:65 -#: src/Model/Contact.php:1247 src/Model/Contact.php:1259 -#: src/Module/Contact/Follow.php:152 view/theme/vier/theme.php:183 +#: src/Content/Item.php:443 src/Content/Widget.php:65 +#: src/Model/Contact.php:1287 src/Model/Contact.php:1299 +#: src/Module/Contact/Follow.php:152 view/theme/vier/theme.php:182 msgid "Connect/Follow" msgstr "" -#: src/Content/Item.php:869 +#: src/Content/Item.php:874 msgid "Unable to fetch user." msgstr "" @@ -1948,7 +1941,7 @@ msgstr "" msgid "Clear notifications" msgstr "" -#: src/Content/Nav.php:119 src/Content/Text/HTML.php:855 +#: src/Content/Nav.php:119 src/Content/Text/HTML.php:863 msgid "@name, !group, #tags, content" msgstr "" @@ -1980,7 +1973,7 @@ msgstr "" #: src/Content/Nav.php:226 src/Module/BaseProfile.php:34 #: src/Module/BaseSettings.php:86 src/Module/Contact.php:484 -#: src/Module/Contact/Profile.php:431 src/Module/Profile/Profile.php:259 +#: src/Module/Contact/Profile.php:462 src/Module/Profile/Profile.php:277 #: src/Module/Welcome.php:43 view/theme/frio/theme.php:219 msgid "Profile" msgstr "" @@ -2032,7 +2025,7 @@ msgstr "" msgid "Home Page" msgstr "" -#: src/Content/Nav.php:251 src/Module/Register.php:161 +#: src/Content/Nav.php:251 src/Module/Register.php:169 #: src/Module/Security/Login.php:113 msgid "Register" msgstr "" @@ -2045,7 +2038,7 @@ msgstr "" #: src/Module/Settings/TwoFactor/AppSpecific.php:118 #: src/Module/Settings/TwoFactor/Index.php:125 #: src/Module/Settings/TwoFactor/Recovery.php:96 -#: src/Module/Settings/TwoFactor/Verify.php:135 view/theme/vier/theme.php:228 +#: src/Module/Settings/TwoFactor/Verify.php:135 view/theme/vier/theme.php:227 msgid "Help" msgstr "" @@ -2061,7 +2054,7 @@ msgstr "" msgid "Addon applications, utilities, games" msgstr "" -#: src/Content/Nav.php:265 src/Content/Text/HTML.php:853 +#: src/Content/Nav.php:265 src/Content/Text/HTML.php:861 #: src/Module/Admin/Logs/View.php:74 src/Module/Search/Index.php:99 msgid "Search" msgstr "" @@ -2070,17 +2063,17 @@ msgstr "" msgid "Search site content" msgstr "" -#: src/Content/Nav.php:268 src/Content/Text/HTML.php:862 +#: src/Content/Nav.php:268 src/Content/Text/HTML.php:870 msgid "Full Text" msgstr "" -#: src/Content/Nav.php:269 src/Content/Text/HTML.php:863 +#: src/Content/Nav.php:269 src/Content/Text/HTML.php:871 #: src/Content/Widget/TagCloud.php:54 msgid "Tags" msgstr "" #: src/Content/Nav.php:270 src/Content/Nav.php:325 -#: src/Content/Text/HTML.php:864 src/Module/BaseProfile.php:112 +#: src/Content/Text/HTML.php:872 src/Module/BaseProfile.php:112 #: src/Module/BaseProfile.php:115 src/Module/Contact.php:406 #: src/Module/Contact.php:516 view/theme/frio/theme.php:232 msgid "Contacts" @@ -2112,7 +2105,7 @@ msgid "Information about this friendica instance" msgstr "" #: src/Content/Nav.php:297 src/Module/Admin/Tos.php:64 -#: src/Module/BaseAdmin.php:80 src/Module/Register.php:169 +#: src/Module/BaseAdmin.php:80 src/Module/Register.php:177 #: src/Module/Tos.php:87 msgid "Terms of Service" msgstr "" @@ -2178,7 +2171,7 @@ msgstr "" msgid "Manage other pages" msgstr "" -#: src/Content/Nav.php:323 src/Module/Admin/Addons/Details.php:101 +#: src/Content/Nav.php:323 src/Module/Admin/Addons/Details.php:131 #: src/Module/Admin/Themes/Details.php:85 src/Module/BaseSettings.php:177 #: src/Module/Welcome.php:38 view/theme/frio/theme.php:231 msgid "Settings" @@ -2247,41 +2240,41 @@ msgstr "" msgid "%2$s %3$s" msgstr "" -#: src/Content/Text/BBCode.php:926 src/Model/Item.php:3787 -#: src/Model/Item.php:3793 src/Model/Item.php:3794 +#: src/Content/Text/BBCode.php:926 src/Model/Item.php:3850 +#: src/Model/Item.php:3856 src/Model/Item.php:3857 msgid "Link to source" msgstr "" -#: src/Content/Text/BBCode.php:1740 src/Content/Text/HTML.php:892 +#: src/Content/Text/BBCode.php:1748 src/Content/Text/HTML.php:900 msgid "Click to open/close" msgstr "" -#: src/Content/Text/BBCode.php:1795 +#: src/Content/Text/BBCode.php:1803 msgid "$1 wrote:" msgstr "" -#: src/Content/Text/BBCode.php:1869 src/Content/Text/BBCode.php:1870 +#: src/Content/Text/BBCode.php:1877 src/Content/Text/BBCode.php:1878 msgid "Encrypted content" msgstr "" -#: src/Content/Text/BBCode.php:2203 +#: src/Content/Text/BBCode.php:2211 msgid "Invalid source protocol" msgstr "" -#: src/Content/Text/BBCode.php:2222 +#: src/Content/Text/BBCode.php:2230 msgid "Invalid link protocol" msgstr "" -#: src/Content/Text/HTML.php:770 +#: src/Content/Text/HTML.php:778 msgid "Loading more entries..." msgstr "" -#: src/Content/Text/HTML.php:771 +#: src/Content/Text/HTML.php:779 msgid "The end" msgstr "" -#: src/Content/Text/HTML.php:847 src/Content/Widget/VCard.php:116 -#: src/Model/Profile.php:452 src/Module/Contact/Profile.php:484 +#: src/Content/Text/HTML.php:855 src/Content/Widget/VCard.php:116 +#: src/Model/Profile.php:457 src/Module/Contact/Profile.php:520 msgid "Follow" msgstr "" @@ -2308,46 +2301,46 @@ msgid_plural "%d invitations available" msgstr[0] "" msgstr[1] "" -#: src/Content/Widget.php:63 view/theme/vier/theme.php:181 +#: src/Content/Widget.php:63 view/theme/vier/theme.php:180 msgid "Find People" msgstr "" -#: src/Content/Widget.php:64 view/theme/vier/theme.php:182 +#: src/Content/Widget.php:64 view/theme/vier/theme.php:181 msgid "Enter name or interest" msgstr "" -#: src/Content/Widget.php:66 view/theme/vier/theme.php:184 +#: src/Content/Widget.php:66 view/theme/vier/theme.php:183 msgid "Examples: Robert Morgenstein, Fishing" msgstr "" #: src/Content/Widget.php:67 src/Module/Contact.php:440 -#: src/Module/Directory.php:82 view/theme/vier/theme.php:185 +#: src/Module/Directory.php:82 view/theme/vier/theme.php:184 msgid "Find" msgstr "" #: src/Content/Widget.php:68 src/Module/Contact/Suggestions.php:59 -#: view/theme/vier/theme.php:186 +#: view/theme/vier/theme.php:185 msgid "Friend Suggestions" msgstr "" -#: src/Content/Widget.php:69 view/theme/vier/theme.php:187 +#: src/Content/Widget.php:69 view/theme/vier/theme.php:186 msgid "Similar Interests" msgstr "" -#: src/Content/Widget.php:70 view/theme/vier/theme.php:188 +#: src/Content/Widget.php:70 view/theme/vier/theme.php:187 msgid "Random Profile" msgstr "" -#: src/Content/Widget.php:71 view/theme/vier/theme.php:189 +#: src/Content/Widget.php:71 view/theme/vier/theme.php:188 msgid "Invite Friends" msgstr "" #: src/Content/Widget.php:72 src/Module/Directory.php:74 -#: view/theme/vier/theme.php:190 +#: view/theme/vier/theme.php:189 msgid "Global Directory" msgstr "" -#: src/Content/Widget.php:74 view/theme/vier/theme.php:192 +#: src/Content/Widget.php:74 view/theme/vier/theme.php:191 msgid "Local Directory" msgstr "" @@ -2399,7 +2392,7 @@ msgstr "" msgid "Organisations" msgstr "" -#: src/Content/Widget.php:557 src/Model/Contact.php:1757 +#: src/Content/Widget.php:557 src/Model/Contact.php:1805 msgid "News" msgstr "" @@ -2407,7 +2400,7 @@ msgstr "" msgid "Relays" msgstr "" -#: src/Content/Widget.php:566 src/Module/Moderation/BaseUsers.php:58 +#: src/Content/Widget.php:566 src/Module/Moderation/BaseUsers.php:76 msgid "All" msgstr "" @@ -2423,18 +2416,18 @@ msgstr "" msgid "Export calendar as csv" msgstr "" -#: src/Content/Widget/ContactBlock.php:64 +#: src/Content/Widget/ContactBlock.php:63 msgid "No contacts" msgstr "" -#: src/Content/Widget/ContactBlock.php:95 +#: src/Content/Widget/ContactBlock.php:94 #, php-format msgid "%d Contact" msgid_plural "%d Contacts" msgstr[0] "" msgstr[1] "" -#: src/Content/Widget/ContactBlock.php:112 +#: src/Content/Widget/ContactBlock.php:111 msgid "View Contacts" msgstr "" @@ -2453,101 +2446,109 @@ msgstr[1] "" msgid "More Trending Tags" msgstr "" -#: src/Content/Widget/VCard.php:94 src/Model/Contact.php:1216 -#: src/Model/Profile.php:437 +#: src/Content/Widget/TrendingTags.php:41 +msgid "Show More" +msgstr "" + +#: src/Content/Widget/TrendingTags.php:42 +msgid "Show Less" +msgstr "" + +#: src/Content/Widget/VCard.php:94 src/Model/Contact.php:1256 +#: src/Model/Profile.php:442 msgid "Post to group" msgstr "" -#: src/Content/Widget/VCard.php:99 src/Model/Contact.php:1220 -#: src/Model/Profile.php:441 src/Module/Moderation/Item/Source.php:80 +#: src/Content/Widget/VCard.php:99 src/Model/Contact.php:1260 +#: src/Model/Profile.php:446 src/Module/Moderation/Item/Source.php:80 msgid "Mention" msgstr "" -#: src/Content/Widget/VCard.php:109 src/Model/Profile.php:356 -#: src/Module/Contact/Profile.php:420 src/Module/Profile/Profile.php:190 +#: src/Content/Widget/VCard.php:109 src/Model/Profile.php:361 +#: src/Module/Contact/Profile.php:451 src/Module/Profile/Profile.php:208 msgid "XMPP:" msgstr "" -#: src/Content/Widget/VCard.php:110 src/Model/Profile.php:357 -#: src/Module/Contact/Profile.php:422 src/Module/Profile/Profile.php:194 +#: src/Content/Widget/VCard.php:110 src/Model/Profile.php:362 +#: src/Module/Contact/Profile.php:453 src/Module/Profile/Profile.php:212 msgid "Matrix:" msgstr "" #: src/Content/Widget/VCard.php:111 src/Model/Event.php:68 -#: src/Model/Event.php:95 src/Model/Event.php:457 src/Model/Event.php:945 -#: src/Model/Profile.php:351 src/Module/Contact/Profile.php:418 +#: src/Model/Event.php:95 src/Model/Event.php:462 src/Model/Event.php:950 +#: src/Model/Profile.php:356 src/Module/Contact/Profile.php:449 #: src/Module/Directory.php:133 src/Module/Notifications/Introductions.php:180 -#: src/Module/Profile/Profile.php:212 +#: src/Module/Profile/Profile.php:230 msgid "Location:" msgstr "" -#: src/Content/Widget/VCard.php:114 src/Model/Profile.php:465 +#: src/Content/Widget/VCard.php:114 src/Model/Profile.php:470 #: src/Module/Notifications/Introductions.php:194 msgid "Network:" msgstr "" -#: src/Content/Widget/VCard.php:118 src/Model/Contact.php:1248 -#: src/Model/Contact.php:1260 src/Model/Profile.php:454 -#: src/Module/Contact/Profile.php:476 +#: src/Content/Widget/VCard.php:118 src/Model/Contact.php:1288 +#: src/Model/Contact.php:1300 src/Model/Profile.php:459 +#: src/Module/Contact/Profile.php:512 msgid "Unfollow" msgstr "" -#: src/Content/Widget/VCard.php:124 src/Model/Contact.php:1218 -#: src/Model/Profile.php:439 +#: src/Content/Widget/VCard.php:124 src/Model/Contact.php:1258 +#: src/Model/Profile.php:444 msgid "View group" msgstr "" -#: src/Core/ACL.php:153 src/Module/Profile/Profile.php:260 +#: src/Core/ACL.php:154 src/Module/Profile/Profile.php:278 msgid "Yourself" msgstr "" -#: src/Core/ACL.php:189 src/Module/Privacy/PermissionTooltip.php:156 -#: src/Module/Privacy/PermissionTooltip.php:178 +#: src/Core/ACL.php:192 src/Module/Privacy/PermissionTooltip.php:187 +#: src/Module/Privacy/PermissionTooltip.php:209 msgid "Mutuals" msgstr "" -#: src/Core/ACL.php:281 +#: src/Core/ACL.php:284 msgid "Post to Email" msgstr "" -#: src/Core/ACL.php:308 src/Module/Privacy/PermissionTooltip.php:103 -#: src/Module/Privacy/PermissionTooltip.php:217 +#: src/Core/ACL.php:316 src/Module/Privacy/PermissionTooltip.php:130 +#: src/Module/Privacy/PermissionTooltip.php:248 msgid "Public" msgstr "" -#: src/Core/ACL.php:309 +#: src/Core/ACL.php:317 msgid "This content will be shown to all your followers and can be seen in the community pages and by anyone with its link." msgstr "" -#: src/Core/ACL.php:310 src/Module/Privacy/PermissionTooltip.php:105 +#: src/Core/ACL.php:318 src/Module/Privacy/PermissionTooltip.php:136 msgid "Limited/Private" msgstr "" -#: src/Core/ACL.php:311 +#: src/Core/ACL.php:319 msgid "This content will be shown only to the people in the first box, to the exception of the people mentioned in the second box. It won't appear anywhere public." msgstr "" -#: src/Core/ACL.php:311 +#: src/Core/ACL.php:319 msgid "Start typing the name of a contact or a circle to show a filtered list. You can also mention the special circles \"Followers\" and \"Mutuals\"." msgstr "" -#: src/Core/ACL.php:312 +#: src/Core/ACL.php:320 msgid "Show to:" msgstr "" -#: src/Core/ACL.php:313 +#: src/Core/ACL.php:321 msgid "Except to:" msgstr "" -#: src/Core/ACL.php:314 src/Module/Post/Edit.php:145 +#: src/Core/ACL.php:322 src/Module/Post/Edit.php:150 msgid "CC: email addresses" msgstr "" -#: src/Core/ACL.php:315 src/Module/Post/Edit.php:151 +#: src/Core/ACL.php:323 src/Module/Post/Edit.php:156 msgid "Example: bob@example.com, mary@example.com" msgstr "" -#: src/Core/ACL.php:316 +#: src/Core/ACL.php:324 msgid "Connectors" msgstr "" @@ -2836,167 +2837,167 @@ msgstr "" msgid "Could not connect to database." msgstr "" -#: src/Core/L10n.php:426 src/Model/Item.php:2023 +#: src/Core/L10n.php:453 src/Model/Item.php:2051 msgid "Undetermined" msgstr "" -#: src/Core/L10n.php:433 +#: src/Core/L10n.php:460 #, php-format msgid "%s (%s)" msgstr "" -#: src/Core/L10n.php:481 src/Model/Event.php:416 +#: src/Core/L10n.php:509 src/Model/Event.php:421 #: src/Module/Settings/Display.php:282 msgid "Monday" msgstr "" -#: src/Core/L10n.php:481 src/Model/Event.php:417 +#: src/Core/L10n.php:509 src/Model/Event.php:422 #: src/Module/Settings/Display.php:283 msgid "Tuesday" msgstr "" -#: src/Core/L10n.php:481 src/Model/Event.php:418 +#: src/Core/L10n.php:509 src/Model/Event.php:423 #: src/Module/Settings/Display.php:284 msgid "Wednesday" msgstr "" -#: src/Core/L10n.php:481 src/Model/Event.php:419 +#: src/Core/L10n.php:509 src/Model/Event.php:424 #: src/Module/Settings/Display.php:285 msgid "Thursday" msgstr "" -#: src/Core/L10n.php:481 src/Model/Event.php:420 +#: src/Core/L10n.php:509 src/Model/Event.php:425 #: src/Module/Settings/Display.php:286 msgid "Friday" msgstr "" -#: src/Core/L10n.php:481 src/Model/Event.php:421 +#: src/Core/L10n.php:509 src/Model/Event.php:426 #: src/Module/Settings/Display.php:287 msgid "Saturday" msgstr "" -#: src/Core/L10n.php:481 src/Model/Event.php:415 +#: src/Core/L10n.php:509 src/Model/Event.php:420 #: src/Module/Settings/Display.php:281 msgid "Sunday" msgstr "" -#: src/Core/L10n.php:485 src/Model/Event.php:436 +#: src/Core/L10n.php:515 src/Model/Event.php:441 msgid "January" msgstr "" -#: src/Core/L10n.php:485 src/Model/Event.php:437 +#: src/Core/L10n.php:515 src/Model/Event.php:442 msgid "February" msgstr "" -#: src/Core/L10n.php:485 src/Model/Event.php:438 +#: src/Core/L10n.php:515 src/Model/Event.php:443 msgid "March" msgstr "" -#: src/Core/L10n.php:485 src/Model/Event.php:439 +#: src/Core/L10n.php:515 src/Model/Event.php:444 msgid "April" msgstr "" -#: src/Core/L10n.php:485 src/Core/L10n.php:504 src/Model/Event.php:427 +#: src/Core/L10n.php:515 src/Core/L10n.php:538 src/Model/Event.php:432 msgid "May" msgstr "" -#: src/Core/L10n.php:485 src/Model/Event.php:440 +#: src/Core/L10n.php:515 src/Model/Event.php:445 msgid "June" msgstr "" -#: src/Core/L10n.php:485 src/Model/Event.php:441 +#: src/Core/L10n.php:515 src/Model/Event.php:446 msgid "July" msgstr "" -#: src/Core/L10n.php:485 src/Model/Event.php:442 +#: src/Core/L10n.php:515 src/Model/Event.php:447 msgid "August" msgstr "" -#: src/Core/L10n.php:485 src/Model/Event.php:443 +#: src/Core/L10n.php:515 src/Model/Event.php:448 msgid "September" msgstr "" -#: src/Core/L10n.php:485 src/Model/Event.php:444 +#: src/Core/L10n.php:515 src/Model/Event.php:449 msgid "October" msgstr "" -#: src/Core/L10n.php:485 src/Model/Event.php:445 +#: src/Core/L10n.php:515 src/Model/Event.php:450 msgid "November" msgstr "" -#: src/Core/L10n.php:485 src/Model/Event.php:446 +#: src/Core/L10n.php:515 src/Model/Event.php:451 msgid "December" msgstr "" -#: src/Core/L10n.php:500 src/Model/Event.php:408 +#: src/Core/L10n.php:532 src/Model/Event.php:413 msgid "Mon" msgstr "" -#: src/Core/L10n.php:500 src/Model/Event.php:409 +#: src/Core/L10n.php:532 src/Model/Event.php:414 msgid "Tue" msgstr "" -#: src/Core/L10n.php:500 src/Model/Event.php:410 +#: src/Core/L10n.php:532 src/Model/Event.php:415 msgid "Wed" msgstr "" -#: src/Core/L10n.php:500 src/Model/Event.php:411 +#: src/Core/L10n.php:532 src/Model/Event.php:416 msgid "Thu" msgstr "" -#: src/Core/L10n.php:500 src/Model/Event.php:412 +#: src/Core/L10n.php:532 src/Model/Event.php:417 msgid "Fri" msgstr "" -#: src/Core/L10n.php:500 src/Model/Event.php:413 +#: src/Core/L10n.php:532 src/Model/Event.php:418 msgid "Sat" msgstr "" -#: src/Core/L10n.php:500 src/Model/Event.php:407 +#: src/Core/L10n.php:532 src/Model/Event.php:412 msgid "Sun" msgstr "" -#: src/Core/L10n.php:504 src/Model/Event.php:423 +#: src/Core/L10n.php:538 src/Model/Event.php:428 msgid "Jan" msgstr "" -#: src/Core/L10n.php:504 src/Model/Event.php:424 +#: src/Core/L10n.php:538 src/Model/Event.php:429 msgid "Feb" msgstr "" -#: src/Core/L10n.php:504 src/Model/Event.php:425 +#: src/Core/L10n.php:538 src/Model/Event.php:430 msgid "Mar" msgstr "" -#: src/Core/L10n.php:504 src/Model/Event.php:426 +#: src/Core/L10n.php:538 src/Model/Event.php:431 msgid "Apr" msgstr "" -#: src/Core/L10n.php:504 src/Model/Event.php:428 +#: src/Core/L10n.php:538 src/Model/Event.php:433 msgid "Jun" msgstr "" -#: src/Core/L10n.php:504 src/Model/Event.php:429 +#: src/Core/L10n.php:538 src/Model/Event.php:434 msgid "Jul" msgstr "" -#: src/Core/L10n.php:504 src/Model/Event.php:430 +#: src/Core/L10n.php:538 src/Model/Event.php:435 msgid "Aug" msgstr "" -#: src/Core/L10n.php:504 +#: src/Core/L10n.php:538 msgid "Sep" msgstr "" -#: src/Core/L10n.php:504 src/Model/Event.php:432 +#: src/Core/L10n.php:538 src/Model/Event.php:437 msgid "Oct" msgstr "" -#: src/Core/L10n.php:504 src/Model/Event.php:433 +#: src/Core/L10n.php:538 src/Model/Event.php:438 msgid "Nov" msgstr "" -#: src/Core/L10n.php:504 src/Model/Event.php:434 +#: src/Core/L10n.php:538 src/Model/Event.php:439 msgid "Dec" msgstr "" @@ -3196,84 +3197,84 @@ msgstr "" msgid "Edit circles" msgstr "" -#: src/Model/Contact.php:1267 src/Module/Moderation/Users/Pending.php:88 +#: src/Model/Contact.php:1307 src/Module/Moderation/Users/Pending.php:88 #: src/Module/Notifications/Introductions.php:124 #: src/Module/Notifications/Introductions.php:197 msgid "Approve" msgstr "" -#: src/Model/Contact.php:1601 src/Model/Contact.php:1673 -#: src/Module/Contact/Profile.php:360 +#: src/Model/Contact.php:1649 src/Model/Contact.php:1721 +#: src/Module/Contact/Profile.php:391 #, php-format msgid "%s has blocked you" msgstr "" -#: src/Model/Contact.php:1753 +#: src/Model/Contact.php:1801 msgid "Organisation" msgstr "" -#: src/Model/Contact.php:1761 +#: src/Model/Contact.php:1809 msgid "Group" msgstr "" -#: src/Model/Contact.php:1765 src/Module/Moderation/BaseUsers.php:122 +#: src/Model/Contact.php:1813 src/Module/Moderation/BaseUsers.php:150 msgid "Relay" msgstr "" -#: src/Model/Contact.php:3091 +#: src/Model/Contact.php:3143 msgid "Disallowed profile URL." msgstr "" -#: src/Model/Contact.php:3096 src/Module/Friendica.php:90 +#: src/Model/Contact.php:3148 src/Module/Friendica.php:106 msgid "Blocked domain" msgstr "" -#: src/Model/Contact.php:3101 +#: src/Model/Contact.php:3153 msgid "Connect URL missing." msgstr "" -#: src/Model/Contact.php:3110 +#: src/Model/Contact.php:3166 msgid "The contact could not be added. Please check the relevant network credentials in your Settings -> Social Networks page." msgstr "" -#: src/Model/Contact.php:3128 +#: src/Model/Contact.php:3184 #, php-format msgid "Expected network %s does not match actual network %s" msgstr "" -#: src/Model/Contact.php:3145 +#: src/Model/Contact.php:3201 msgid "This seems to be a relay account. They can't be followed by users." msgstr "" -#: src/Model/Contact.php:3152 +#: src/Model/Contact.php:3208 msgid "The profile address specified does not provide adequate information." msgstr "" -#: src/Model/Contact.php:3154 +#: src/Model/Contact.php:3210 msgid "No compatible communication protocols or feeds were discovered." msgstr "" -#: src/Model/Contact.php:3157 +#: src/Model/Contact.php:3213 msgid "An author or name was not found." msgstr "" -#: src/Model/Contact.php:3160 +#: src/Model/Contact.php:3216 msgid "No browser URL could be matched to this address." msgstr "" -#: src/Model/Contact.php:3163 +#: src/Model/Contact.php:3219 msgid "Unable to match @-style Identity Address with a known protocol or email contact." msgstr "" -#: src/Model/Contact.php:3164 +#: src/Model/Contact.php:3220 msgid "Use mailto: in front of address to force email check." msgstr "" -#: src/Model/Contact.php:3170 +#: src/Model/Contact.php:3226 msgid "Limited profile. This person will be unable to receive direct/personal notifications from you." msgstr "" -#: src/Model/Contact.php:3229 +#: src/Model/Contact.php:3285 msgid "Unable to retrieve contact information." msgstr "" @@ -3281,189 +3282,189 @@ msgstr "" msgid "l F d, Y \\@ g:i A \\G\\M\\TP (e)" msgstr "" -#: src/Model/Event.php:61 src/Model/Event.php:78 src/Model/Event.php:455 -#: src/Model/Event.php:927 +#: src/Model/Event.php:61 src/Model/Event.php:78 src/Model/Event.php:460 +#: src/Model/Event.php:932 msgid "Starts:" msgstr "" -#: src/Model/Event.php:64 src/Model/Event.php:84 src/Model/Event.php:456 -#: src/Model/Event.php:931 +#: src/Model/Event.php:64 src/Model/Event.php:84 src/Model/Event.php:461 +#: src/Model/Event.php:936 msgid "Finishes:" msgstr "" -#: src/Model/Event.php:405 +#: src/Model/Event.php:410 msgid "all-day" msgstr "" -#: src/Model/Event.php:431 +#: src/Model/Event.php:436 msgid "Sept" msgstr "" -#: src/Model/Event.php:448 src/Module/Calendar/Show.php:116 +#: src/Model/Event.php:453 src/Module/Calendar/Show.php:116 #: src/Util/Temporal.php:333 msgid "today" msgstr "" -#: src/Model/Event.php:449 src/Module/Calendar/Show.php:117 +#: src/Model/Event.php:454 src/Module/Calendar/Show.php:117 #: src/Module/Settings/Display.php:292 src/Util/Temporal.php:343 msgid "month" msgstr "" -#: src/Model/Event.php:450 src/Module/Calendar/Show.php:118 +#: src/Model/Event.php:455 src/Module/Calendar/Show.php:118 #: src/Module/Settings/Display.php:293 src/Util/Temporal.php:344 msgid "week" msgstr "" -#: src/Model/Event.php:451 src/Module/Calendar/Show.php:119 +#: src/Model/Event.php:456 src/Module/Calendar/Show.php:119 #: src/Module/Settings/Display.php:294 src/Util/Temporal.php:345 msgid "day" msgstr "" -#: src/Model/Event.php:453 +#: src/Model/Event.php:458 msgid "No events to display" msgstr "" -#: src/Model/Event.php:502 src/Module/Feed.php:56 +#: src/Model/Event.php:507 src/Module/Feed.php:56 #: src/Module/Update/Profile.php:42 msgid "Access to this profile has been restricted." msgstr "" -#: src/Model/Event.php:543 src/Module/Calendar/Event/Show.php:52 +#: src/Model/Event.php:548 src/Module/Calendar/Event/Show.php:52 msgid "Event not found." msgstr "" -#: src/Model/Event.php:622 +#: src/Model/Event.php:627 msgid "l, F j" msgstr "" -#: src/Model/Event.php:649 +#: src/Model/Event.php:654 msgid "Edit event" msgstr "" -#: src/Model/Event.php:650 +#: src/Model/Event.php:655 msgid "Duplicate event" msgstr "" -#: src/Model/Event.php:651 +#: src/Model/Event.php:656 msgid "Delete event" msgstr "" -#: src/Model/Event.php:881 src/Module/Debug/Localtime.php:24 +#: src/Model/Event.php:886 src/Module/Debug/Localtime.php:24 msgid "l F d, Y \\@ g:i A" msgstr "" -#: src/Model/Event.php:882 +#: src/Model/Event.php:887 msgid "D g:i A" msgstr "" -#: src/Model/Event.php:883 +#: src/Model/Event.php:888 msgid "g:i A" msgstr "" -#: src/Model/Event.php:946 src/Model/Event.php:948 +#: src/Model/Event.php:951 src/Model/Event.php:953 msgid "Show map" msgstr "" -#: src/Model/Event.php:947 +#: src/Model/Event.php:952 msgid "Hide map" msgstr "" -#: src/Model/Event.php:1040 +#: src/Model/Event.php:1045 #, php-format msgid "%s's birthday" msgstr "" -#: src/Model/Event.php:1041 +#: src/Model/Event.php:1046 #, php-format msgid "Happy Birthday %s" msgstr "" -#: src/Model/Item.php:2030 +#: src/Model/Item.php:2058 #, php-format msgid "%s (%s - %s): %s" msgstr "" -#: src/Model/Item.php:2032 +#: src/Model/Item.php:2060 #, php-format msgid "%s (%s): %s" msgstr "" -#: src/Model/Item.php:2035 +#: src/Model/Item.php:2063 #, php-format msgid "" "Detected languages in this post:\n" "%s" msgstr "" -#: src/Model/Item.php:2984 +#: src/Model/Item.php:3016 msgid "activity" msgstr "" -#: src/Model/Item.php:2986 +#: src/Model/Item.php:3018 msgid "comment" msgstr "" -#: src/Model/Item.php:2989 src/Module/Post/Tag/Add.php:109 +#: src/Model/Item.php:3021 src/Module/Post/Tag/Add.php:112 msgid "post" msgstr "" -#: src/Model/Item.php:3162 +#: src/Model/Item.php:3210 #, php-format msgid "%s is blocked" msgstr "" -#: src/Model/Item.php:3164 +#: src/Model/Item.php:3212 #, php-format msgid "%s is ignored" msgstr "" -#: src/Model/Item.php:3166 +#: src/Model/Item.php:3214 #, php-format msgid "Content from %s is collapsed" msgstr "" -#: src/Model/Item.php:3170 +#: src/Model/Item.php:3218 msgid "Sensitive content" msgstr "" -#: src/Model/Item.php:3687 +#: src/Model/Item.php:3750 msgid "bytes" msgstr "" -#: src/Model/Item.php:3718 +#: src/Model/Item.php:3781 #, php-format msgid "%2$s (%3$d%%, %1$d vote)" msgid_plural "%2$s (%3$d%%, %1$d votes)" msgstr[0] "" msgstr[1] "" -#: src/Model/Item.php:3720 +#: src/Model/Item.php:3783 #, php-format msgid "%2$s (%1$d vote)" msgid_plural "%2$s (%1$d votes)" msgstr[0] "" msgstr[1] "" -#: src/Model/Item.php:3725 +#: src/Model/Item.php:3788 #, php-format msgid "%d voter. Poll end: %s" msgid_plural "%d voters. Poll end: %s" msgstr[0] "" msgstr[1] "" -#: src/Model/Item.php:3727 +#: src/Model/Item.php:3790 #, php-format msgid "%d voter." msgid_plural "%d voters." msgstr[0] "" msgstr[1] "" -#: src/Model/Item.php:3729 +#: src/Model/Item.php:3792 #, php-format msgid "Poll end: %s" msgstr "" -#: src/Model/Item.php:3770 src/Model/Item.php:3771 +#: src/Model/Item.php:3833 src/Model/Item.php:3834 msgid "View on separate page" msgstr "" @@ -3471,288 +3472,288 @@ msgstr "" msgid "[no subject]" msgstr "" -#: src/Model/Photo.php:1194 src/Module/Media/Photo/Upload.php:154 +#: src/Model/Photo.php:1193 src/Module/Media/Photo/Upload.php:154 msgid "Wall Photos" msgstr "" -#: src/Model/Profile.php:339 src/Module/Profile/Profile.php:274 -#: src/Module/Profile/Profile.php:276 +#: src/Model/Profile.php:344 src/Module/Profile/Profile.php:292 +#: src/Module/Profile/Profile.php:294 msgid "Edit profile" msgstr "" -#: src/Model/Profile.php:341 +#: src/Model/Profile.php:346 msgid "Change profile photo" msgstr "" -#: src/Model/Profile.php:354 src/Module/Directory.php:138 -#: src/Module/Profile/Profile.php:200 +#: src/Model/Profile.php:359 src/Module/Directory.php:138 +#: src/Module/Profile/Profile.php:218 msgid "Homepage:" msgstr "" -#: src/Model/Profile.php:355 src/Module/Contact/Profile.php:424 +#: src/Model/Profile.php:360 src/Module/Contact/Profile.php:455 #: src/Module/Notifications/Introductions.php:182 msgid "About:" msgstr "" -#: src/Model/Profile.php:456 +#: src/Model/Profile.php:461 msgid "Atom feed" msgstr "" -#: src/Model/Profile.php:463 +#: src/Model/Profile.php:468 msgid "This website has been verified to belong to the same person." msgstr "" -#: src/Model/Profile.php:514 +#: src/Model/Profile.php:527 msgid "F d" msgstr "" -#: src/Model/Profile.php:578 src/Model/Profile.php:659 +#: src/Model/Profile.php:591 src/Model/Profile.php:672 msgid "[today]" msgstr "" -#: src/Model/Profile.php:587 +#: src/Model/Profile.php:600 msgid "Birthday Reminders" msgstr "" -#: src/Model/Profile.php:588 +#: src/Model/Profile.php:601 msgid "Birthdays this week:" msgstr "" -#: src/Model/Profile.php:604 +#: src/Model/Profile.php:617 msgid "g A l F d" msgstr "" -#: src/Model/Profile.php:646 +#: src/Model/Profile.php:659 msgid "[No description]" msgstr "" -#: src/Model/Profile.php:672 +#: src/Model/Profile.php:685 msgid "Event Reminders" msgstr "" -#: src/Model/Profile.php:673 +#: src/Model/Profile.php:686 msgid "Upcoming events the next 7 days:" msgstr "" -#: src/Model/Profile.php:783 +#: src/Model/Profile.php:796 msgid "Hometown:" msgstr "" -#: src/Model/Profile.php:784 +#: src/Model/Profile.php:797 msgid "Marital Status:" msgstr "" -#: src/Model/Profile.php:785 +#: src/Model/Profile.php:798 msgid "With:" msgstr "" -#: src/Model/Profile.php:786 +#: src/Model/Profile.php:799 msgid "Since:" msgstr "" -#: src/Model/Profile.php:787 +#: src/Model/Profile.php:800 msgid "Sexual Preference:" msgstr "" -#: src/Model/Profile.php:788 +#: src/Model/Profile.php:801 msgid "Political Views:" msgstr "" -#: src/Model/Profile.php:789 +#: src/Model/Profile.php:802 msgid "Religious Views:" msgstr "" -#: src/Model/Profile.php:790 +#: src/Model/Profile.php:803 msgid "Likes:" msgstr "" -#: src/Model/Profile.php:791 +#: src/Model/Profile.php:804 msgid "Dislikes:" msgstr "" -#: src/Model/Profile.php:792 +#: src/Model/Profile.php:805 msgid "Title/Description:" msgstr "" -#: src/Model/Profile.php:793 src/Module/Admin/Summary.php:184 -#: src/Module/Moderation/Report/Create.php:266 +#: src/Model/Profile.php:806 src/Module/Admin/Summary.php:184 +#: src/Module/Moderation/Report/Create.php:278 #: src/Module/Moderation/Summary.php:65 msgid "Summary" msgstr "" -#: src/Model/Profile.php:794 +#: src/Model/Profile.php:807 msgid "Musical interests" msgstr "" -#: src/Model/Profile.php:795 +#: src/Model/Profile.php:808 msgid "Books, literature" msgstr "" -#: src/Model/Profile.php:796 +#: src/Model/Profile.php:809 msgid "Television" msgstr "" -#: src/Model/Profile.php:797 +#: src/Model/Profile.php:810 msgid "Film/dance/culture/entertainment" msgstr "" -#: src/Model/Profile.php:798 +#: src/Model/Profile.php:811 msgid "Hobbies/Interests" msgstr "" -#: src/Model/Profile.php:799 +#: src/Model/Profile.php:812 msgid "Love/romance" msgstr "" -#: src/Model/Profile.php:800 +#: src/Model/Profile.php:813 msgid "Work/employment" msgstr "" -#: src/Model/Profile.php:801 +#: src/Model/Profile.php:814 msgid "School/education" msgstr "" -#: src/Model/Profile.php:802 +#: src/Model/Profile.php:815 msgid "Contact information and Social Networks" msgstr "" -#: src/Model/Profile.php:850 +#: src/Model/Profile.php:863 #, php-format msgid "Responsible account: %s" msgstr "" -#: src/Model/User.php:216 src/Model/User.php:1313 +#: src/Model/User.php:216 src/Model/User.php:1317 msgid "SERIOUS ERROR: Generation of security keys failed." msgstr "" -#: src/Model/User.php:739 src/Model/User.php:772 +#: src/Model/User.php:739 src/Model/User.php:776 msgid "Login failed" msgstr "" -#: src/Model/User.php:805 +#: src/Model/User.php:809 msgid "Not enough information to authenticate" msgstr "" -#: src/Model/User.php:932 +#: src/Model/User.php:936 msgid "Password can't be empty" msgstr "" -#: src/Model/User.php:974 +#: src/Model/User.php:978 msgid "Empty passwords are not allowed." msgstr "" -#: src/Model/User.php:978 +#: src/Model/User.php:982 msgid "The new password has been exposed in a public data dump, please choose another." msgstr "" -#: src/Model/User.php:982 +#: src/Model/User.php:986 msgid "The password length is limited to 72 characters." msgstr "" -#: src/Model/User.php:986 +#: src/Model/User.php:990 msgid "The password can't contain white spaces nor accentuated letters" msgstr "" -#: src/Model/User.php:1195 +#: src/Model/User.php:1199 msgid "Passwords do not match. Password unchanged." msgstr "" -#: src/Model/User.php:1202 +#: src/Model/User.php:1206 msgid "An invitation is required." msgstr "" -#: src/Model/User.php:1206 +#: src/Model/User.php:1210 msgid "Invitation could not be verified." msgstr "" -#: src/Model/User.php:1214 +#: src/Model/User.php:1218 msgid "Invalid OpenID url" msgstr "" -#: src/Model/User.php:1228 src/Security/Authentication.php:231 +#: src/Model/User.php:1232 src/Security/Authentication.php:231 msgid "We encountered a problem while logging in with the OpenID you provided. Please check the correct spelling of the ID." msgstr "" -#: src/Model/User.php:1228 src/Security/Authentication.php:231 +#: src/Model/User.php:1232 src/Security/Authentication.php:231 msgid "The error message was:" msgstr "" -#: src/Model/User.php:1234 +#: src/Model/User.php:1238 msgid "Please enter the required information." msgstr "" -#: src/Model/User.php:1248 +#: src/Model/User.php:1252 #, php-format msgid "system.username_min_length (%s) and system.username_max_length (%s) are excluding each other, swapping values." msgstr "" -#: src/Model/User.php:1255 +#: src/Model/User.php:1259 #, php-format msgid "Username should be at least %s character." msgid_plural "Username should be at least %s characters." msgstr[0] "" msgstr[1] "" -#: src/Model/User.php:1259 +#: src/Model/User.php:1263 #, php-format msgid "Username should be at most %s character." msgid_plural "Username should be at most %s characters." msgstr[0] "" msgstr[1] "" -#: src/Model/User.php:1267 +#: src/Model/User.php:1271 msgid "That doesn't appear to be your full (First Last) name." msgstr "" -#: src/Model/User.php:1272 +#: src/Model/User.php:1276 msgid "Your email domain is not among those allowed on this site." msgstr "" -#: src/Model/User.php:1276 +#: src/Model/User.php:1280 msgid "Not a valid email address." msgstr "" -#: src/Model/User.php:1279 +#: src/Model/User.php:1283 msgid "The nickname was blocked from registration by the nodes admin." msgstr "" -#: src/Model/User.php:1283 src/Model/User.php:1289 +#: src/Model/User.php:1287 src/Model/User.php:1293 msgid "Cannot use that email." msgstr "" -#: src/Model/User.php:1295 +#: src/Model/User.php:1299 msgid "Your nickname can only contain a-z, 0-9 and _." msgstr "" -#: src/Model/User.php:1303 src/Model/User.php:1353 +#: src/Model/User.php:1307 src/Model/User.php:1357 msgid "Nickname is already registered. Please choose another." msgstr "" -#: src/Model/User.php:1340 src/Model/User.php:1344 +#: src/Model/User.php:1344 src/Model/User.php:1348 msgid "An error occurred during registration. Please try again." msgstr "" -#: src/Model/User.php:1367 +#: src/Model/User.php:1371 msgid "An error occurred creating your default profile. Please try again." msgstr "" -#: src/Model/User.php:1374 +#: src/Model/User.php:1378 msgid "An error occurred creating your self contact. Please try again." msgstr "" -#: src/Model/User.php:1379 +#: src/Model/User.php:1383 msgid "Friends" msgstr "" -#: src/Model/User.php:1383 +#: src/Model/User.php:1387 msgid "An error occurred creating your default contact circle. Please try again." msgstr "" -#: src/Model/User.php:1431 +#: src/Model/User.php:1435 msgid "Profile Photos" msgstr "" -#: src/Model/User.php:1619 +#: src/Model/User.php:1632 #, php-format msgid "" "\n" @@ -3760,7 +3761,7 @@ msgid "" "\t\t\tthe administrator of %2$s has set up an account for you." msgstr "" -#: src/Model/User.php:1622 +#: src/Model/User.php:1635 #, php-format msgid "" "\n" @@ -3791,12 +3792,12 @@ msgid "" "\t\tThank you and welcome to %4$s." msgstr "" -#: src/Model/User.php:1654 src/Model/User.php:1760 +#: src/Model/User.php:1667 src/Model/User.php:1773 #, php-format msgid "Registration details for %s" msgstr "" -#: src/Model/User.php:1674 +#: src/Model/User.php:1687 #, php-format msgid "" "\n" @@ -3811,12 +3812,12 @@ msgid "" "\t\t" msgstr "" -#: src/Model/User.php:1693 +#: src/Model/User.php:1706 #, php-format msgid "Registration at %s" msgstr "" -#: src/Model/User.php:1717 +#: src/Model/User.php:1730 #, php-format msgid "" "\n" @@ -3825,7 +3826,7 @@ msgid "" "\t\t\t" msgstr "" -#: src/Model/User.php:1725 +#: src/Model/User.php:1738 #, php-format msgid "" "\n" @@ -3856,35 +3857,35 @@ msgid "" "\t\t\tThank you and welcome to %2$s." msgstr "" -#: src/Model/User.php:1787 +#: src/Model/User.php:1800 msgid "User with delegates can't be removed, please remove delegate users first" msgstr "" -#: src/Module/Admin/Addons/Details.php:48 +#: src/Module/Admin/Addons/Details.php:49 msgid "Addon not found." msgstr "" -#: src/Module/Admin/Addons/Details.php:59 src/Module/Admin/Addons/Index.php:43 +#: src/Module/Admin/Addons/Details.php:60 src/Module/Admin/Addons/Index.php:43 #, php-format msgid "Addon %s disabled." msgstr "" -#: src/Module/Admin/Addons/Details.php:62 src/Module/Admin/Addons/Index.php:45 +#: src/Module/Admin/Addons/Details.php:63 src/Module/Admin/Addons/Index.php:45 #, php-format msgid "Addon %s enabled." msgstr "" -#: src/Module/Admin/Addons/Details.php:71 +#: src/Module/Admin/Addons/Details.php:72 #: src/Module/Admin/Themes/Details.php:38 msgid "Disable" msgstr "" -#: src/Module/Admin/Addons/Details.php:74 +#: src/Module/Admin/Addons/Details.php:75 #: src/Module/Admin/Themes/Details.php:41 src/Module/Settings/Display.php:341 msgid "Enable" msgstr "" -#: src/Module/Admin/Addons/Details.php:98 src/Module/Admin/Addons/Index.php:77 +#: src/Module/Admin/Addons/Details.php:128 src/Module/Admin/Addons/Index.php:77 #: src/Module/Admin/Federation.php:213 src/Module/Admin/Logs/Settings.php:74 #: src/Module/Admin/Logs/View.php:71 src/Module/Admin/Queue.php:59 #: src/Module/Admin/Site.php:446 src/Module/Admin/Storage.php:124 @@ -3895,22 +3896,22 @@ msgstr "" msgid "Administration" msgstr "" -#: src/Module/Admin/Addons/Details.php:99 src/Module/Admin/Addons/Index.php:78 +#: src/Module/Admin/Addons/Details.php:129 src/Module/Admin/Addons/Index.php:78 #: src/Module/BaseAdmin.php:77 src/Module/BaseSettings.php:127 msgid "Addons" msgstr "" -#: src/Module/Admin/Addons/Details.php:100 +#: src/Module/Admin/Addons/Details.php:130 #: src/Module/Admin/Themes/Details.php:84 msgid "Toggle" msgstr "" -#: src/Module/Admin/Addons/Details.php:113 +#: src/Module/Admin/Addons/Details.php:143 #: src/Module/Admin/Themes/Details.php:92 msgid "Author: " msgstr "" -#: src/Module/Admin/Addons/Details.php:114 +#: src/Module/Admin/Addons/Details.php:144 #: src/Module/Admin/Themes/Details.php:93 msgid "Maintainer: " msgstr "" @@ -4005,14 +4006,14 @@ msgstr "" #: src/Module/Admin/Features.php:53 #: src/Module/Notifications/Introductions.php:136 -#: src/Module/OAuth/Acknowledge.php:41 src/Module/Register.php:124 +#: src/Module/OAuth/Acknowledge.php:41 src/Module/Register.php:128 #: src/Module/Settings/TwoFactor/Trusted.php:115 msgid "No" msgstr "" #: src/Module/Admin/Features.php:53 src/Module/Contact/Revoke.php:91 #: src/Module/Notifications/Introductions.php:136 -#: src/Module/OAuth/Acknowledge.php:40 src/Module/Register.php:123 +#: src/Module/OAuth/Acknowledge.php:40 src/Module/Register.php:127 #: src/Module/Settings/TwoFactor/Trusted.php:115 msgid "Yes" msgstr "" @@ -4026,8 +4027,8 @@ msgid "Manage Additional Features" msgstr "" #: src/Module/Admin/Federation.php:70 -#: src/Module/Moderation/Report/Create.php:177 -#: src/Module/Moderation/Report/Create.php:302 +#: src/Module/Moderation/Report/Create.php:176 +#: src/Module/Moderation/Report/Create.php:319 msgid "Other" msgstr "" @@ -4363,7 +4364,7 @@ msgstr "" msgid "Republish users to directory" msgstr "" -#: src/Module/Admin/Site.php:451 src/Module/Register.php:145 +#: src/Module/Admin/Site.php:451 src/Module/Register.php:153 msgid "Registration" msgstr "" @@ -4376,7 +4377,7 @@ msgid "Policies" msgstr "" #: src/Module/Admin/Site.php:454 src/Module/Calendar/Event/Form.php:238 -#: src/Module/Contact.php:527 src/Module/Profile/Profile.php:267 +#: src/Module/Contact.php:527 src/Module/Profile/Profile.php:285 msgid "Advanced" msgstr "" @@ -5188,7 +5189,7 @@ msgstr "" msgid "Can be \"all\" or \"tags\". \"all\" means that every public post should be received. \"tags\" means that only posts with selected tags should be received." msgstr "" -#: src/Module/Admin/Site.php:584 src/Module/Contact/Profile.php:315 +#: src/Module/Admin/Site.php:584 src/Module/Contact/Profile.php:346 #: src/Module/Settings/Display.php:249 #: src/Module/Settings/TwoFactor/Index.php:132 msgid "Disabled" @@ -5741,11 +5742,11 @@ msgstr "" msgid "Tips for New Members" msgstr "" -#: src/Module/BaseProfile.php:137 src/Module/Contact.php:390 +#: src/Module/BaseProfile.php:141 src/Module/Contact.php:390 #: src/Module/Contact.php:537 src/Module/Conversation/Channel.php:98 #: src/Module/Conversation/Community.php:91 -#: src/Module/Conversation/Network.php:295 -#: src/Module/Moderation/BaseUsers.php:102 src/Object/Post.php:606 +#: src/Module/Conversation/Network.php:349 +#: src/Module/Moderation/BaseUsers.php:130 src/Object/Post.php:611 msgid "More" msgstr "" @@ -5864,9 +5865,9 @@ msgstr "" #: src/Module/Moderation/Blocklist/Server/Index.php:76 #: src/Module/Moderation/Blocklist/Server/Index.php:104 #: src/Module/Moderation/Blocklist/Server/Index.php:105 -#: src/Module/Moderation/Item/Delete.php:53 src/Module/Register.php:141 +#: src/Module/Moderation/Item/Delete.php:53 src/Module/Register.php:149 #: src/Module/Security/TwoFactor/Verify.php:87 -#: src/Module/Settings/Channels.php:176 src/Module/Settings/Channels.php:197 +#: src/Module/Settings/Channels.php:176 src/Module/Settings/Channels.php:202 #: src/Module/Settings/TwoFactor/Index.php:147 #: src/Module/Settings/TwoFactor/Verify.php:144 msgid "Required" @@ -5900,7 +5901,7 @@ msgstr "" msgid "Share this event" msgstr "" -#: src/Module/Calendar/Event/Form.php:237 src/Module/Profile/Profile.php:266 +#: src/Module/Calendar/Event/Form.php:237 src/Module/Profile/Profile.php:284 msgid "Basic" msgstr "" @@ -5954,8 +5955,8 @@ msgstr "" #: src/Module/Contact/Conversations.php:78 #: src/Module/Contact/Conversations.php:83 src/Module/Contact/Media.php:47 #: src/Module/Contact/Posts.php:64 src/Module/Contact/Posts.php:69 -#: src/Module/Contact/Posts.php:74 src/Module/Contact/Profile.php:146 -#: src/Module/Contact/Profile.php:151 src/Module/Contact/Profile.php:170 +#: src/Module/Contact/Posts.php:74 src/Module/Contact/Profile.php:172 +#: src/Module/Contact/Profile.php:177 src/Module/Contact/Profile.php:196 #: src/Module/Contact/Redir.php:79 src/Module/Contact/Redir.php:133 #: src/Module/FriendSuggest.php:58 src/Module/FriendSuggest.php:96 msgid "Contact not found." @@ -6045,7 +6046,7 @@ msgid "Show all contacts" msgstr "" #: src/Module/Contact.php:332 src/Module/Contact.php:411 -#: src/Module/Moderation/BaseUsers.php:74 +#: src/Module/Moderation/BaseUsers.php:92 msgid "Pending" msgstr "" @@ -6054,7 +6055,7 @@ msgid "Only show pending contacts" msgstr "" #: src/Module/Contact.php:340 src/Module/Contact.php:414 -#: src/Module/Moderation/BaseUsers.php:82 +#: src/Module/Moderation/BaseUsers.php:100 msgid "Blocked" msgstr "" @@ -6063,7 +6064,7 @@ msgid "Only show blocked contacts" msgstr "" #: src/Module/Contact.php:348 src/Module/Contact.php:420 -#: src/Module/Settings/Server/Index.php:93 src/Object/Post.php:385 +#: src/Module/Settings/Server/Index.php:93 src/Object/Post.php:390 msgid "Ignored" msgstr "" @@ -6112,18 +6113,18 @@ msgstr "" msgid "Update" msgstr "" -#: src/Module/Contact.php:448 src/Module/Contact/Profile.php:524 +#: src/Module/Contact.php:448 src/Module/Contact/Profile.php:560 #: src/Module/Moderation/Blocklist/Contact.php:105 #: src/Module/Moderation/Users/Blocked.php:94 #: src/Module/Moderation/Users/Index.php:103 msgid "Unblock" msgstr "" -#: src/Module/Contact.php:449 src/Module/Contact/Profile.php:532 +#: src/Module/Contact.php:449 src/Module/Contact/Profile.php:568 msgid "Unignore" msgstr "" -#: src/Module/Contact.php:450 src/Module/Contact/Profile.php:540 +#: src/Module/Contact.php:450 src/Module/Contact/Profile.php:576 msgid "Uncollapse" msgstr "" @@ -6175,7 +6176,7 @@ msgstr "" msgid "Pending incoming contact request" msgstr "" -#: src/Module/Contact.php:607 src/Module/Contact/Profile.php:383 +#: src/Module/Contact.php:607 src/Module/Contact/Profile.php:414 #, php-format msgid "Visit %s's profile [%s]" msgstr "" @@ -6306,7 +6307,7 @@ msgstr "" msgid "Your Identity Address:" msgstr "" -#: src/Module/Contact/Follow.php:155 src/Module/Contact/Profile.php:414 +#: src/Module/Contact/Follow.php:155 src/Module/Contact/Profile.php:445 #: src/Module/Contact/Unfollow.php:115 #: src/Module/Moderation/Blocklist/Contact.php:119 #: src/Module/Moderation/Reports.php:112 @@ -6315,9 +6316,9 @@ msgstr "" msgid "Profile URL" msgstr "" -#: src/Module/Contact/Follow.php:156 src/Module/Contact/Profile.php:426 +#: src/Module/Contact/Follow.php:156 src/Module/Contact/Profile.php:457 #: src/Module/Notifications/Introductions.php:184 -#: src/Module/Profile/Profile.php:225 +#: src/Module/Profile/Profile.php:243 msgid "Tags:" msgstr "" @@ -6354,301 +6355,301 @@ msgstr "" msgid "Profile Match" msgstr "" -#: src/Module/Contact/Profile.php:131 +#: src/Module/Contact/Profile.php:157 msgid "Failed to update contact record." msgstr "" -#: src/Module/Contact/Profile.php:196 +#: src/Module/Contact/Profile.php:222 msgid "Contact has been unblocked" msgstr "" -#: src/Module/Contact/Profile.php:200 +#: src/Module/Contact/Profile.php:226 msgid "Contact has been blocked" msgstr "" -#: src/Module/Contact/Profile.php:212 +#: src/Module/Contact/Profile.php:238 msgid "Contact has been unignored" msgstr "" -#: src/Module/Contact/Profile.php:216 +#: src/Module/Contact/Profile.php:242 msgid "Contact has been ignored" msgstr "" -#: src/Module/Contact/Profile.php:228 +#: src/Module/Contact/Profile.php:254 msgid "Contact has been uncollapsed" msgstr "" -#: src/Module/Contact/Profile.php:232 +#: src/Module/Contact/Profile.php:258 msgid "Contact has been collapsed" msgstr "" -#: src/Module/Contact/Profile.php:260 +#: src/Module/Contact/Profile.php:287 #, php-format msgid "You are mutual friends with %s" msgstr "" -#: src/Module/Contact/Profile.php:261 +#: src/Module/Contact/Profile.php:290 #, php-format msgid "You are sharing with %s" msgstr "" -#: src/Module/Contact/Profile.php:262 +#: src/Module/Contact/Profile.php:293 #, php-format msgid "%s is sharing with you" msgstr "" -#: src/Module/Contact/Profile.php:278 +#: src/Module/Contact/Profile.php:310 msgid "Private communications are not available for this contact." msgstr "" -#: src/Module/Contact/Profile.php:288 +#: src/Module/Contact/Profile.php:319 msgid "This contact is on a server you ignored." msgstr "" -#: src/Module/Contact/Profile.php:291 +#: src/Module/Contact/Profile.php:322 msgid "Never" msgstr "" -#: src/Module/Contact/Profile.php:294 +#: src/Module/Contact/Profile.php:325 msgid "(Update was not successful)" msgstr "" -#: src/Module/Contact/Profile.php:294 +#: src/Module/Contact/Profile.php:325 msgid "(Update was successful)" msgstr "" -#: src/Module/Contact/Profile.php:296 src/Module/Contact/Profile.php:495 +#: src/Module/Contact/Profile.php:327 src/Module/Contact/Profile.php:531 msgid "Suggest friends" msgstr "" -#: src/Module/Contact/Profile.php:300 +#: src/Module/Contact/Profile.php:331 #, php-format msgid "Network type: %s" msgstr "" -#: src/Module/Contact/Profile.php:305 +#: src/Module/Contact/Profile.php:336 msgid "Communications lost with this contact!" msgstr "" -#: src/Module/Contact/Profile.php:311 +#: src/Module/Contact/Profile.php:342 msgid "Fetch further information for feeds" msgstr "" -#: src/Module/Contact/Profile.php:313 +#: src/Module/Contact/Profile.php:344 msgid "Fetch information like preview pictures, title and teaser from the feed item. You can activate this if the feed doesn't contain much text. Keywords are taken from the meta header in the feed item and are posted as hash tags." msgstr "" -#: src/Module/Contact/Profile.php:316 +#: src/Module/Contact/Profile.php:347 msgid "Fetch information" msgstr "" -#: src/Module/Contact/Profile.php:317 +#: src/Module/Contact/Profile.php:348 msgid "Fetch keywords" msgstr "" -#: src/Module/Contact/Profile.php:318 +#: src/Module/Contact/Profile.php:349 msgid "Fetch information and keywords" msgstr "" -#: src/Module/Contact/Profile.php:328 src/Module/Contact/Profile.php:333 -#: src/Module/Contact/Profile.php:338 src/Module/Contact/Profile.php:344 +#: src/Module/Contact/Profile.php:359 src/Module/Contact/Profile.php:364 +#: src/Module/Contact/Profile.php:369 src/Module/Contact/Profile.php:375 msgid "No mirroring" msgstr "" -#: src/Module/Contact/Profile.php:329 src/Module/Contact/Profile.php:339 -#: src/Module/Contact/Profile.php:345 +#: src/Module/Contact/Profile.php:360 src/Module/Contact/Profile.php:370 +#: src/Module/Contact/Profile.php:376 msgid "Mirror as my own posting" msgstr "" -#: src/Module/Contact/Profile.php:334 src/Module/Contact/Profile.php:340 +#: src/Module/Contact/Profile.php:365 src/Module/Contact/Profile.php:371 msgid "Native reshare" msgstr "" -#: src/Module/Contact/Profile.php:365 +#: src/Module/Contact/Profile.php:396 msgid "Contact Information / Notes" msgstr "" -#: src/Module/Contact/Profile.php:366 +#: src/Module/Contact/Profile.php:397 msgid "Contact Settings" msgstr "" -#: src/Module/Contact/Profile.php:374 +#: src/Module/Contact/Profile.php:405 msgid "Contact" msgstr "" -#: src/Module/Contact/Profile.php:378 +#: src/Module/Contact/Profile.php:409 msgid "Their personal note" msgstr "" -#: src/Module/Contact/Profile.php:380 +#: src/Module/Contact/Profile.php:411 msgid "Edit contact notes" msgstr "" -#: src/Module/Contact/Profile.php:384 +#: src/Module/Contact/Profile.php:415 msgid "Block/Unblock contact" msgstr "" -#: src/Module/Contact/Profile.php:385 -#: src/Module/Moderation/Report/Create.php:279 +#: src/Module/Contact/Profile.php:416 +#: src/Module/Moderation/Report/Create.php:291 msgid "Ignore contact" msgstr "" -#: src/Module/Contact/Profile.php:386 +#: src/Module/Contact/Profile.php:417 msgid "View conversations" msgstr "" -#: src/Module/Contact/Profile.php:391 +#: src/Module/Contact/Profile.php:422 msgid "Last update:" msgstr "" -#: src/Module/Contact/Profile.php:393 +#: src/Module/Contact/Profile.php:424 msgid "Update public posts" msgstr "" -#: src/Module/Contact/Profile.php:395 src/Module/Contact/Profile.php:505 +#: src/Module/Contact/Profile.php:426 src/Module/Contact/Profile.php:541 msgid "Update now" msgstr "" -#: src/Module/Contact/Profile.php:397 +#: src/Module/Contact/Profile.php:428 msgid "Awaiting connection acknowledge" msgstr "" -#: src/Module/Contact/Profile.php:398 +#: src/Module/Contact/Profile.php:429 msgid "Currently blocked" msgstr "" -#: src/Module/Contact/Profile.php:399 +#: src/Module/Contact/Profile.php:430 msgid "Currently ignored" msgstr "" -#: src/Module/Contact/Profile.php:400 +#: src/Module/Contact/Profile.php:431 msgid "Currently collapsed" msgstr "" -#: src/Module/Contact/Profile.php:401 +#: src/Module/Contact/Profile.php:432 msgid "Currently archived" msgstr "" -#: src/Module/Contact/Profile.php:404 +#: src/Module/Contact/Profile.php:435 msgid "Manage remote servers" msgstr "" -#: src/Module/Contact/Profile.php:406 +#: src/Module/Contact/Profile.php:437 #: src/Module/Notifications/Introductions.php:185 msgid "Hide this contact from others" msgstr "" -#: src/Module/Contact/Profile.php:406 +#: src/Module/Contact/Profile.php:437 msgid "Replies/likes to your public posts may still be visible" msgstr "" -#: src/Module/Contact/Profile.php:407 +#: src/Module/Contact/Profile.php:438 msgid "Notification for new posts" msgstr "" -#: src/Module/Contact/Profile.php:407 +#: src/Module/Contact/Profile.php:438 msgid "Send a notification of every new post of this contact" msgstr "" -#: src/Module/Contact/Profile.php:409 +#: src/Module/Contact/Profile.php:440 msgid "Keyword Deny List" msgstr "" -#: src/Module/Contact/Profile.php:409 +#: src/Module/Contact/Profile.php:440 msgid "Comma separated list of keywords that should not be converted to hashtags, when \"Fetch information and keywords\" is selected" msgstr "" -#: src/Module/Contact/Profile.php:427 +#: src/Module/Contact/Profile.php:458 #: src/Module/Settings/TwoFactor/Index.php:146 msgid "Actions" msgstr "" -#: src/Module/Contact/Profile.php:429 +#: src/Module/Contact/Profile.php:460 #: src/Module/Settings/TwoFactor/Index.php:126 view/theme/frio/theme.php:218 msgid "Status" msgstr "" -#: src/Module/Contact/Profile.php:435 +#: src/Module/Contact/Profile.php:466 msgid "Mirror postings from this contact" msgstr "" -#: src/Module/Contact/Profile.php:437 +#: src/Module/Contact/Profile.php:468 msgid "Mark this contact as remote_self, this will cause friendica to repost new entries from this contact." msgstr "" -#: src/Module/Contact/Profile.php:440 +#: src/Module/Contact/Profile.php:471 msgid "Channel Settings" msgstr "" -#: src/Module/Contact/Profile.php:441 +#: src/Module/Contact/Profile.php:472 msgid "Frequency of this contact in relevant channels" msgstr "" -#: src/Module/Contact/Profile.php:442 +#: src/Module/Contact/Profile.php:473 msgid "Depending on the type of the channel not all posts from this contact are displayed. By default, posts need to have a minimum amount of interactions (comments, likes) to show in your channels. On the other hand there can be contacts who flood the channel, so you might want to see only some of their posts. Or you don't want to see their content at all, but you don't want to block or hide the contact completely." msgstr "" -#: src/Module/Contact/Profile.php:443 +#: src/Module/Contact/Profile.php:474 msgid "Default frequency" msgstr "" -#: src/Module/Contact/Profile.php:443 +#: src/Module/Contact/Profile.php:474 msgid "Posts by this contact are displayed in the \"for you\" channel if you interact often with this contact or if a post reached some level of interaction." msgstr "" -#: src/Module/Contact/Profile.php:444 +#: src/Module/Contact/Profile.php:475 msgid "Display all posts of this contact" msgstr "" -#: src/Module/Contact/Profile.php:444 +#: src/Module/Contact/Profile.php:475 msgid "All posts from this contact will appear on the \"for you\" channel" msgstr "" -#: src/Module/Contact/Profile.php:445 +#: src/Module/Contact/Profile.php:476 msgid "Display only few posts" msgstr "" -#: src/Module/Contact/Profile.php:445 +#: src/Module/Contact/Profile.php:476 msgid "When a contact creates a lot of posts in a short period, this setting reduces the number of displayed posts in every channel." msgstr "" -#: src/Module/Contact/Profile.php:446 +#: src/Module/Contact/Profile.php:477 msgid "Never display posts" msgstr "" -#: src/Module/Contact/Profile.php:446 +#: src/Module/Contact/Profile.php:477 msgid "Posts from this contact will never be displayed in any channel" msgstr "" -#: src/Module/Contact/Profile.php:447 +#: src/Module/Contact/Profile.php:478 msgid "Channel Only" msgstr "" -#: src/Module/Contact/Profile.php:447 +#: src/Module/Contact/Profile.php:478 msgid "If enabled, posts from this contact will only appear in channels and network streams in circles, but not in the general network stream." msgstr "" -#: src/Module/Contact/Profile.php:515 +#: src/Module/Contact/Profile.php:551 msgid "Refetch contact data" msgstr "" -#: src/Module/Contact/Profile.php:526 +#: src/Module/Contact/Profile.php:562 msgid "Toggle Blocked status" msgstr "" -#: src/Module/Contact/Profile.php:534 +#: src/Module/Contact/Profile.php:570 msgid "Toggle Ignored status" msgstr "" -#: src/Module/Contact/Profile.php:542 +#: src/Module/Contact/Profile.php:578 msgid "Toggle Collapsed status" msgstr "" -#: src/Module/Contact/Profile.php:549 src/Module/Contact/Revoke.php:89 +#: src/Module/Contact/Profile.php:585 src/Module/Contact/Revoke.php:89 msgid "Revoke Follow" msgstr "" -#: src/Module/Contact/Profile.php:551 +#: src/Module/Contact/Profile.php:587 msgid "Revoke the follow from this contact" msgstr "" @@ -6714,21 +6715,21 @@ msgstr "" msgid "Not available." msgstr "" -#: src/Module/Conversation/Network.php:205 +#: src/Module/Conversation/Network.php:254 msgid "No such circle" msgstr "" -#: src/Module/Conversation/Network.php:209 +#: src/Module/Conversation/Network.php:258 #, php-format msgid "Circle: %s" msgstr "" -#: src/Module/Conversation/Network.php:229 +#: src/Module/Conversation/Network.php:278 #, php-format msgid "Error %d (%s) while fetching the timeline." msgstr "" -#: src/Module/Conversation/Network.php:307 +#: src/Module/Conversation/Network.php:361 msgid "Network feed not available." msgstr "" @@ -6941,7 +6942,7 @@ msgid "Twitter Source / Tweet URL (requires API key)" msgstr "" #: src/Module/Debug/Feed.php:39 src/Module/Filer/SaveTag.php:33 -#: src/Module/Settings/Profile/Index.php:166 +#: src/Module/Settings/Profile/Index.php:190 msgid "You must be logged in to use this module" msgstr "" @@ -7016,11 +7017,11 @@ msgstr "" msgid "Site Directory" msgstr "" -#: src/Module/Filer/RemoveTag.php:91 +#: src/Module/Filer/RemoveTag.php:90 msgid "Item was not deleted" msgstr "" -#: src/Module/Filer/RemoveTag.php:101 +#: src/Module/Filer/RemoveTag.php:100 msgid "Item was not removed" msgstr "" @@ -7049,52 +7050,52 @@ msgstr "" msgid "Suggest a friend for %s" msgstr "" -#: src/Module/Friendica.php:71 +#: src/Module/Friendica.php:87 msgid "Installed addons/apps:" msgstr "" -#: src/Module/Friendica.php:76 +#: src/Module/Friendica.php:92 msgid "No installed addons/apps" msgstr "" -#: src/Module/Friendica.php:81 +#: src/Module/Friendica.php:97 #, php-format msgid "Read about the Terms of Service of this node." msgstr "" -#: src/Module/Friendica.php:88 +#: src/Module/Friendica.php:104 msgid "On this server the following remote servers are blocked." msgstr "" -#: src/Module/Friendica.php:91 +#: src/Module/Friendica.php:107 #: src/Module/Moderation/Blocklist/Server/Index.php:76 #: src/Module/Moderation/Blocklist/Server/Index.php:100 -#: src/Module/Settings/Channels.php:218 +#: src/Module/Settings/Channels.php:223 msgid "Reason for the block" msgstr "" -#: src/Module/Friendica.php:93 +#: src/Module/Friendica.php:109 msgid "Download this list in CSV format" msgstr "" -#: src/Module/Friendica.php:108 +#: src/Module/Friendica.php:126 #, php-format msgid "This is Friendica, version %s that is running at the web location %s. The database version is %s, the post update version is %s." msgstr "" -#: src/Module/Friendica.php:114 +#: src/Module/Friendica.php:132 msgid "Please visit Friendi.ca to learn more about the Friendica project." msgstr "" -#: src/Module/Friendica.php:115 +#: src/Module/Friendica.php:133 msgid "Bug reports and issues: please visit" msgstr "" -#: src/Module/Friendica.php:115 +#: src/Module/Friendica.php:133 msgid "the bugtracker at github" msgstr "" -#: src/Module/Friendica.php:116 +#: src/Module/Friendica.php:134 msgid "Suggestions, praise, etc. - please email \"info\" at \"friendi - dot - ca" msgstr "" @@ -7110,7 +7111,7 @@ msgstr "" msgid "Help:" msgstr "" -#: src/Module/Home.php:52 +#: src/Module/Home.php:53 #, php-format msgid "Welcome to %s" msgstr "" @@ -7330,39 +7331,39 @@ msgstr "" msgid "For more information about the Friendica project and why we feel it is important, please visit http://friendi.ca" msgstr "" -#: src/Module/Item/Compose.php:83 +#: src/Module/Item/Compose.php:86 msgid "Please enter a post body." msgstr "" -#: src/Module/Item/Compose.php:94 +#: src/Module/Item/Compose.php:97 msgid "This feature is only available with the frio theme." msgstr "" -#: src/Module/Item/Compose.php:118 +#: src/Module/Item/Compose.php:121 msgid "Compose new personal note" msgstr "" -#: src/Module/Item/Compose.php:127 +#: src/Module/Item/Compose.php:130 msgid "Compose new post" msgstr "" -#: src/Module/Item/Compose.php:183 +#: src/Module/Item/Compose.php:186 msgid "Visibility" msgstr "" -#: src/Module/Item/Compose.php:199 +#: src/Module/Item/Compose.php:202 msgid "Clear the location" msgstr "" -#: src/Module/Item/Compose.php:200 +#: src/Module/Item/Compose.php:203 msgid "Location services are unavailable on your device" msgstr "" -#: src/Module/Item/Compose.php:201 +#: src/Module/Item/Compose.php:204 msgid "Location services are disabled. Please check the website's permissions on your device" msgstr "" -#: src/Module/Item/Compose.php:207 +#: src/Module/Item/Compose.php:214 msgid "You can make this page always open when you use the New Post button in the Theme Customization settings." msgstr "" @@ -7419,84 +7420,84 @@ msgid "File upload failed." msgstr "" #: src/Module/Media/Photo/Upload.php:136 src/Module/Media/Photo/Upload.php:137 -#: src/Module/Profile/Photos.php:212 +#: src/Module/Profile/Photos.php:255 #: src/Module/Settings/Profile/Photo/Index.php:53 msgid "Unable to process image." msgstr "" -#: src/Module/Media/Photo/Upload.php:162 src/Module/Profile/Photos.php:232 +#: src/Module/Media/Photo/Upload.php:162 src/Module/Profile/Photos.php:278 #: src/Module/Settings/Profile/Photo/Index.php:80 msgid "Image upload failed." msgstr "" -#: src/Module/Moderation/BaseUsers.php:61 +#: src/Module/Moderation/BaseUsers.php:79 msgid "List of all users" msgstr "" -#: src/Module/Moderation/BaseUsers.php:66 +#: src/Module/Moderation/BaseUsers.php:84 msgid "Active" msgstr "" -#: src/Module/Moderation/BaseUsers.php:69 +#: src/Module/Moderation/BaseUsers.php:87 msgid "List of active accounts" msgstr "" -#: src/Module/Moderation/BaseUsers.php:77 +#: src/Module/Moderation/BaseUsers.php:95 msgid "List of pending registrations" msgstr "" -#: src/Module/Moderation/BaseUsers.php:85 +#: src/Module/Moderation/BaseUsers.php:103 msgid "List of blocked users" msgstr "" -#: src/Module/Moderation/BaseUsers.php:90 +#: src/Module/Moderation/BaseUsers.php:108 msgid "Deleted" msgstr "" -#: src/Module/Moderation/BaseUsers.php:93 +#: src/Module/Moderation/BaseUsers.php:111 msgid "List of pending user deletions" msgstr "" -#: src/Module/Moderation/BaseUsers.php:110 src/Module/Settings/Account.php:431 +#: src/Module/Moderation/BaseUsers.php:138 src/Module/Settings/Account.php:431 msgid "Normal Account Page" msgstr "" -#: src/Module/Moderation/BaseUsers.php:111 src/Module/Settings/Account.php:438 +#: src/Module/Moderation/BaseUsers.php:139 src/Module/Settings/Account.php:438 msgid "Soapbox Page" msgstr "" -#: src/Module/Moderation/BaseUsers.php:112 src/Module/Settings/Account.php:445 +#: src/Module/Moderation/BaseUsers.php:140 src/Module/Settings/Account.php:445 msgid "Public Group" msgstr "" -#: src/Module/Moderation/BaseUsers.php:113 src/Module/Settings/Account.php:452 +#: src/Module/Moderation/BaseUsers.php:141 src/Module/Settings/Account.php:452 msgid "Public Group - Restricted" msgstr "" -#: src/Module/Moderation/BaseUsers.php:114 src/Module/Settings/Account.php:459 +#: src/Module/Moderation/BaseUsers.php:142 src/Module/Settings/Account.php:459 msgid "Automatic Friend Page" msgstr "" -#: src/Module/Moderation/BaseUsers.php:115 +#: src/Module/Moderation/BaseUsers.php:143 msgid "Private Group" msgstr "" -#: src/Module/Moderation/BaseUsers.php:118 src/Module/Moderation/Summary.php:42 +#: src/Module/Moderation/BaseUsers.php:146 src/Module/Moderation/Summary.php:42 #: src/Module/Settings/Account.php:402 msgid "Personal Page" msgstr "" -#: src/Module/Moderation/BaseUsers.php:119 src/Module/Moderation/Summary.php:43 +#: src/Module/Moderation/BaseUsers.php:147 src/Module/Moderation/Summary.php:43 #: src/Module/Settings/Account.php:409 msgid "Organisation Page" msgstr "" -#: src/Module/Moderation/BaseUsers.php:120 src/Module/Moderation/Summary.php:44 +#: src/Module/Moderation/BaseUsers.php:148 src/Module/Moderation/Summary.php:44 #: src/Module/Settings/Account.php:416 msgid "News Page" msgstr "" -#: src/Module/Moderation/BaseUsers.php:121 src/Module/Moderation/Summary.php:45 +#: src/Module/Moderation/BaseUsers.php:149 src/Module/Moderation/Summary.php:45 #: src/Module/Settings/Account.php:423 msgid "Community Group" msgstr "" @@ -7752,7 +7753,7 @@ msgstr "" #: src/Module/Moderation/Blocklist/Server/Index.php:75 #: src/Module/Moderation/Blocklist/Server/Index.php:99 -#: src/Module/Settings/Channels.php:217 +#: src/Module/Settings/Channels.php:222 msgid "Blocked server domain pattern" msgstr "" @@ -7875,176 +7876,180 @@ msgstr "" msgid "Item Guid" msgstr "" -#: src/Module/Moderation/Report/Create.php:81 +#: src/Module/Moderation/Report/Create.php:80 msgid "Contact not found or their server is already blocked on this node." msgstr "" -#: src/Module/Moderation/Report/Create.php:122 +#: src/Module/Moderation/Report/Create.php:121 msgid "Please login to access this page." msgstr "" -#: src/Module/Moderation/Report/Create.php:151 -#: src/Module/Moderation/Report/Create.php:166 -#: src/Module/Moderation/Report/Create.php:194 -#: src/Module/Moderation/Report/Create.php:246 -#: src/Module/Moderation/Report/Create.php:265 +#: src/Module/Moderation/Report/Create.php:150 +#: src/Module/Moderation/Report/Create.php:165 +#: src/Module/Moderation/Report/Create.php:193 +#: src/Module/Moderation/Report/Create.php:253 +#: src/Module/Moderation/Report/Create.php:277 msgid "Create Moderation Report" msgstr "" -#: src/Module/Moderation/Report/Create.php:152 +#: src/Module/Moderation/Report/Create.php:151 msgid "Pick Contact" msgstr "" -#: src/Module/Moderation/Report/Create.php:153 +#: src/Module/Moderation/Report/Create.php:152 msgid "Please enter below the contact address or profile URL you would like to create a moderation report about." msgstr "" -#: src/Module/Moderation/Report/Create.php:157 +#: src/Module/Moderation/Report/Create.php:156 msgid "Contact address/URL" msgstr "" -#: src/Module/Moderation/Report/Create.php:167 +#: src/Module/Moderation/Report/Create.php:166 msgid "Pick Category" msgstr "" -#: src/Module/Moderation/Report/Create.php:168 +#: src/Module/Moderation/Report/Create.php:167 msgid "Please pick below the category of your report." msgstr "" -#: src/Module/Moderation/Report/Create.php:172 -#: src/Module/Moderation/Report/Create.php:297 +#: src/Module/Moderation/Report/Create.php:171 +#: src/Module/Moderation/Report/Create.php:309 msgid "Spam" msgstr "" -#: src/Module/Moderation/Report/Create.php:172 +#: src/Module/Moderation/Report/Create.php:171 msgid "This contact is publishing many repeated/overly long posts/replies or advertising their product/websites in otherwise irrelevant conversations." msgstr "" -#: src/Module/Moderation/Report/Create.php:173 -#: src/Module/Moderation/Report/Create.php:298 +#: src/Module/Moderation/Report/Create.php:172 +#: src/Module/Moderation/Report/Create.php:311 msgid "Illegal Content" msgstr "" -#: src/Module/Moderation/Report/Create.php:173 +#: src/Module/Moderation/Report/Create.php:172 msgid "This contact is publishing content that is considered illegal in this node's hosting juridiction." msgstr "" -#: src/Module/Moderation/Report/Create.php:174 -#: src/Module/Moderation/Report/Create.php:299 +#: src/Module/Moderation/Report/Create.php:173 +#: src/Module/Moderation/Report/Create.php:313 msgid "Community Safety" msgstr "" -#: src/Module/Moderation/Report/Create.php:174 +#: src/Module/Moderation/Report/Create.php:173 msgid "This contact aggravated you or other people, by being provocative or insensitive, intentionally or not. This includes disclosing people's private information (doxxing), posting threats or offensive pictures in posts or replies." msgstr "" -#: src/Module/Moderation/Report/Create.php:175 -#: src/Module/Moderation/Report/Create.php:300 +#: src/Module/Moderation/Report/Create.php:174 +#: src/Module/Moderation/Report/Create.php:315 msgid "Unwanted Content/Behavior" msgstr "" -#: src/Module/Moderation/Report/Create.php:175 +#: src/Module/Moderation/Report/Create.php:174 msgid "This contact has repeatedly published content irrelevant to the node's theme or is openly criticizing the node's administration/moderation without directly engaging with the relevant people for example or repeatedly nitpicking on a sensitive topic." msgstr "" -#: src/Module/Moderation/Report/Create.php:176 -#: src/Module/Moderation/Report/Create.php:301 +#: src/Module/Moderation/Report/Create.php:175 +#: src/Module/Moderation/Report/Create.php:317 msgid "Rules Violation" msgstr "" -#: src/Module/Moderation/Report/Create.php:176 +#: src/Module/Moderation/Report/Create.php:175 msgid "This contact violated one or more rules of this node. You will be able to pick which one(s) in the next step." msgstr "" -#: src/Module/Moderation/Report/Create.php:177 +#: src/Module/Moderation/Report/Create.php:176 msgid "Please elaborate below why you submitted this report. The more details you provide, the better your report can be handled." msgstr "" -#: src/Module/Moderation/Report/Create.php:179 +#: src/Module/Moderation/Report/Create.php:178 msgid "Additional Information" msgstr "" -#: src/Module/Moderation/Report/Create.php:179 +#: src/Module/Moderation/Report/Create.php:178 msgid "Please provide any additional information relevant to this particular report. You will be able to attach posts by this contact in the next step, but any context is welcome." msgstr "" -#: src/Module/Moderation/Report/Create.php:195 +#: src/Module/Moderation/Report/Create.php:194 msgid "Pick Rules" msgstr "" -#: src/Module/Moderation/Report/Create.php:196 +#: src/Module/Moderation/Report/Create.php:195 msgid "Please pick below the node rules you believe this contact violated." msgstr "" -#: src/Module/Moderation/Report/Create.php:247 +#: src/Module/Moderation/Report/Create.php:254 msgid "Pick Posts" msgstr "" -#: src/Module/Moderation/Report/Create.php:248 +#: src/Module/Moderation/Report/Create.php:255 msgid "Please optionally pick posts to attach to your report." msgstr "" -#: src/Module/Moderation/Report/Create.php:267 -msgid "Submit Report" +#: src/Module/Moderation/Report/Create.php:271 +msgid "Would you like to forward this report to the remote server?" msgstr "" -#: src/Module/Moderation/Report/Create.php:268 -msgid "Further Action" -msgstr "" - -#: src/Module/Moderation/Report/Create.php:269 -msgid "You can also perform one of the following action on the contact you reported:" -msgstr "" - -#: src/Module/Moderation/Report/Create.php:277 -msgid "Nothing" -msgstr "" - -#: src/Module/Moderation/Report/Create.php:278 -msgid "Collapse contact" -msgstr "" - -#: src/Module/Moderation/Report/Create.php:278 -msgid "Their posts and replies will keep appearing in your Network page but their content will be collapsed by default." -msgstr "" - -#: src/Module/Moderation/Report/Create.php:279 -msgid "Their posts won't appear in your Network page anymore, but their replies can appear in forum threads. They still can follow you." -msgstr "" - -#: src/Module/Moderation/Report/Create.php:280 -msgid "Block contact" -msgstr "" - -#: src/Module/Moderation/Report/Create.php:280 -msgid "Their posts won't appear in your Network page anymore, but their replies can appear in forum threads, with their content collapsed by default. They cannot follow you but still can have access to your public posts by other means." -msgstr "" - -#: src/Module/Moderation/Report/Create.php:283 -msgid "Forward report" -msgstr "" - -#: src/Module/Moderation/Report/Create.php:283 +#: src/Module/Moderation/Report/Create.php:273 msgid "Would you ike to forward this report to the remote server?" msgstr "" -#: src/Module/Moderation/Report/Create.php:316 +#: src/Module/Moderation/Report/Create.php:279 +msgid "Submit Report" +msgstr "" + +#: src/Module/Moderation/Report/Create.php:280 +msgid "Further Action" +msgstr "" + +#: src/Module/Moderation/Report/Create.php:281 +msgid "You can also perform one of the following action on the contact you reported:" +msgstr "" + +#: src/Module/Moderation/Report/Create.php:289 +msgid "Nothing" +msgstr "" + +#: src/Module/Moderation/Report/Create.php:290 +msgid "Collapse contact" +msgstr "" + +#: src/Module/Moderation/Report/Create.php:290 +msgid "Their posts and replies will keep appearing in your Network page but their content will be collapsed by default." +msgstr "" + +#: src/Module/Moderation/Report/Create.php:291 +msgid "Their posts won't appear in your Network page anymore, but their replies can appear in forum threads. They still can follow you." +msgstr "" + +#: src/Module/Moderation/Report/Create.php:292 +msgid "Block contact" +msgstr "" + +#: src/Module/Moderation/Report/Create.php:292 +msgid "Their posts won't appear in your Network page anymore, but their replies can appear in forum threads, with their content collapsed by default. They cannot follow you but still can have access to your public posts by other means." +msgstr "" + +#: src/Module/Moderation/Report/Create.php:295 +msgid "Forward report" +msgstr "" + +#: src/Module/Moderation/Report/Create.php:334 msgid "1. Pick a contact" msgstr "" -#: src/Module/Moderation/Report/Create.php:317 +#: src/Module/Moderation/Report/Create.php:335 msgid "2. Pick a category" msgstr "" -#: src/Module/Moderation/Report/Create.php:318 +#: src/Module/Moderation/Report/Create.php:336 msgid "2a. Pick rules" msgstr "" -#: src/Module/Moderation/Report/Create.php:319 +#: src/Module/Moderation/Report/Create.php:337 msgid "2b. Add comment" msgstr "" -#: src/Module/Moderation/Report/Create.php:320 +#: src/Module/Moderation/Report/Create.php:338 msgid "3. Pick posts" msgstr "" @@ -8458,46 +8463,46 @@ msgstr "" msgid "The Photo is not available." msgstr "" -#: src/Module/Photo.php:133 +#: src/Module/Photo.php:134 #, php-format msgid "The Photo with id %s is not available." msgstr "" -#: src/Module/Photo.php:178 +#: src/Module/Photo.php:181 #, php-format msgid "Invalid external resource with url %s." msgstr "" -#: src/Module/Photo.php:180 +#: src/Module/Photo.php:183 #, php-format msgid "Invalid photo with id %s." msgstr "" -#: src/Module/Post/Edit.php:73 src/Module/Post/Edit.php:87 +#: src/Module/Post/Edit.php:78 src/Module/Post/Edit.php:92 msgid "Post not found." msgstr "" -#: src/Module/Post/Edit.php:93 +#: src/Module/Post/Edit.php:98 msgid "Edit post" msgstr "" -#: src/Module/Post/Edit.php:127 +#: src/Module/Post/Edit.php:132 msgid "web link" msgstr "" -#: src/Module/Post/Edit.php:128 +#: src/Module/Post/Edit.php:133 msgid "Insert video link" msgstr "" -#: src/Module/Post/Edit.php:129 +#: src/Module/Post/Edit.php:134 msgid "video link" msgstr "" -#: src/Module/Post/Edit.php:130 +#: src/Module/Post/Edit.php:135 msgid "Insert audio link" msgstr "" -#: src/Module/Post/Edit.php:131 +#: src/Module/Post/Edit.php:136 msgid "audio link" msgstr "" @@ -8514,54 +8519,54 @@ msgstr "" msgid "Remove" msgstr "" -#: src/Module/Privacy/PermissionTooltip.php:57 +#: src/Module/Privacy/PermissionTooltip.php:76 #, php-format msgid "Wrong type \"%s\", expected one of: %s" msgstr "" -#: src/Module/Privacy/PermissionTooltip.php:87 +#: src/Module/Privacy/PermissionTooltip.php:106 msgid "Model not found" msgstr "" -#: src/Module/Privacy/PermissionTooltip.php:104 +#: src/Module/Privacy/PermissionTooltip.php:133 msgid "Unlisted" msgstr "" -#: src/Module/Privacy/PermissionTooltip.php:110 +#: src/Module/Privacy/PermissionTooltip.php:141 msgid "Remote privacy information not available." msgstr "" -#: src/Module/Privacy/PermissionTooltip.php:117 +#: src/Module/Privacy/PermissionTooltip.php:148 msgid "Visible to:" msgstr "" -#: src/Module/Privacy/PermissionTooltip.php:119 +#: src/Module/Privacy/PermissionTooltip.php:150 msgid "CC:" msgstr "" -#: src/Module/Privacy/PermissionTooltip.php:120 +#: src/Module/Privacy/PermissionTooltip.php:151 msgid "BCC:" msgstr "" -#: src/Module/Privacy/PermissionTooltip.php:121 +#: src/Module/Privacy/PermissionTooltip.php:152 msgid "Audience:" msgstr "" -#: src/Module/Privacy/PermissionTooltip.php:122 +#: src/Module/Privacy/PermissionTooltip.php:153 msgid "Attributed To:" msgstr "" -#: src/Module/Privacy/PermissionTooltip.php:220 +#: src/Module/Privacy/PermissionTooltip.php:251 #, php-format msgid "Collection (%s)" msgstr "" -#: src/Module/Privacy/PermissionTooltip.php:224 +#: src/Module/Privacy/PermissionTooltip.php:255 #, php-format msgid "Followers (%s)" msgstr "" -#: src/Module/Privacy/PermissionTooltip.php:241 +#: src/Module/Privacy/PermissionTooltip.php:272 #, php-format msgid "%d more" msgstr "" @@ -8570,86 +8575,86 @@ msgstr "" msgid "No contacts." msgstr "" -#: src/Module/Profile/Conversations.php:96 src/Module/Profile/Profile.php:342 -#: src/Protocol/Feed.php:1114 +#: src/Module/Profile/Conversations.php:96 src/Module/Profile/Profile.php:362 +#: src/Protocol/Feed.php:1124 #, php-format msgid "%s's posts" msgstr "" -#: src/Module/Profile/Conversations.php:97 src/Module/Profile/Profile.php:343 -#: src/Protocol/Feed.php:1117 +#: src/Module/Profile/Conversations.php:97 src/Module/Profile/Profile.php:363 +#: src/Protocol/Feed.php:1127 #, php-format msgid "%s's comments" msgstr "" -#: src/Module/Profile/Conversations.php:98 src/Module/Profile/Profile.php:344 -#: src/Protocol/Feed.php:1110 +#: src/Module/Profile/Conversations.php:98 src/Module/Profile/Profile.php:364 +#: src/Protocol/Feed.php:1120 #, php-format msgid "%s's timeline" msgstr "" -#: src/Module/Profile/Photos.php:157 src/Module/Profile/Photos.php:160 -#: src/Module/Profile/Photos.php:189 +#: src/Module/Profile/Photos.php:192 src/Module/Profile/Photos.php:195 +#: src/Module/Profile/Photos.php:226 #: src/Module/Settings/Profile/Photo/Index.php:44 #, php-format msgid "Image exceeds size limit of %s" msgstr "" -#: src/Module/Profile/Photos.php:163 +#: src/Module/Profile/Photos.php:198 msgid "Image upload didn't complete, please try again" msgstr "" -#: src/Module/Profile/Photos.php:166 +#: src/Module/Profile/Photos.php:201 msgid "Image file is missing" msgstr "" -#: src/Module/Profile/Photos.php:171 +#: src/Module/Profile/Photos.php:206 msgid "Server can't accept new file upload at this time, please contact your administrator" msgstr "" -#: src/Module/Profile/Photos.php:197 +#: src/Module/Profile/Photos.php:237 msgid "Image file is empty." msgstr "" -#: src/Module/Profile/Photos.php:349 +#: src/Module/Profile/Photos.php:396 msgid "View Album" msgstr "" -#: src/Module/Profile/Profile.php:103 src/Module/Profile/Restricted.php:38 +#: src/Module/Profile/Profile.php:121 src/Module/Profile/Restricted.php:38 msgid "Profile not found." msgstr "" -#: src/Module/Profile/Profile.php:149 +#: src/Module/Profile/Profile.php:167 #, php-format msgid "You're currently viewing your profile as %s Cancel" msgstr "" -#: src/Module/Profile/Profile.php:158 +#: src/Module/Profile/Profile.php:176 msgid "Full Name:" msgstr "" -#: src/Module/Profile/Profile.php:163 +#: src/Module/Profile/Profile.php:181 msgid "Member since:" msgstr "" -#: src/Module/Profile/Profile.php:169 +#: src/Module/Profile/Profile.php:187 msgid "j F, Y" msgstr "" -#: src/Module/Profile/Profile.php:170 +#: src/Module/Profile/Profile.php:188 msgid "j F" msgstr "" -#: src/Module/Profile/Profile.php:178 src/Util/Temporal.php:155 +#: src/Module/Profile/Profile.php:196 src/Util/Temporal.php:155 msgid "Birthday:" msgstr "" -#: src/Module/Profile/Profile.php:181 src/Module/Settings/Profile/Index.php:282 +#: src/Module/Profile/Profile.php:199 src/Module/Settings/Profile/Index.php:307 #: src/Util/Temporal.php:157 msgid "Age: " msgstr "" -#: src/Module/Profile/Profile.php:181 src/Module/Settings/Profile/Index.php:282 +#: src/Module/Profile/Profile.php:199 src/Module/Settings/Profile/Index.php:307 #: src/Util/Temporal.php:157 #, php-format msgid "%d year old" @@ -8657,19 +8662,19 @@ msgid_plural "%d years old" msgstr[0] "" msgstr[1] "" -#: src/Module/Profile/Profile.php:186 src/Module/Settings/Profile/Index.php:275 +#: src/Module/Profile/Profile.php:204 src/Module/Settings/Profile/Index.php:300 msgid "Description:" msgstr "" -#: src/Module/Profile/Profile.php:252 +#: src/Module/Profile/Profile.php:270 msgid "Groups:" msgstr "" -#: src/Module/Profile/Profile.php:264 +#: src/Module/Profile/Profile.php:282 msgid "View profile as:" msgstr "" -#: src/Module/Profile/Profile.php:281 +#: src/Module/Profile/Profile.php:299 msgid "View as" msgstr "" @@ -8727,160 +8732,160 @@ msgstr "" msgid "Remove post" msgstr "" -#: src/Module/Register.php:77 +#: src/Module/Register.php:81 msgid "Only parent users can create additional accounts." msgstr "" -#: src/Module/Register.php:92 src/Module/User/Import.php:98 +#: src/Module/Register.php:96 src/Module/User/Import.php:98 msgid "This site has exceeded the number of allowed daily account registrations. Please try again tomorrow." msgstr "" -#: src/Module/Register.php:109 +#: src/Module/Register.php:113 msgid "You may (optionally) fill in this form via OpenID by supplying your OpenID and clicking \"Register\"." msgstr "" -#: src/Module/Register.php:110 +#: src/Module/Register.php:114 msgid "If you are not familiar with OpenID, please leave that field blank and fill in the rest of the items." msgstr "" -#: src/Module/Register.php:111 +#: src/Module/Register.php:115 msgid "Your OpenID (optional): " msgstr "" -#: src/Module/Register.php:120 +#: src/Module/Register.php:124 msgid "Include your profile in member directory?" msgstr "" -#: src/Module/Register.php:141 +#: src/Module/Register.php:149 msgid "Note for the admin" msgstr "" -#: src/Module/Register.php:141 +#: src/Module/Register.php:149 msgid "Leave a message for the admin, why you want to join this node" msgstr "" -#: src/Module/Register.php:142 +#: src/Module/Register.php:150 msgid "Membership on this site is by invitation only." msgstr "" -#: src/Module/Register.php:143 +#: src/Module/Register.php:151 msgid "Your invitation code: " msgstr "" -#: src/Module/Register.php:151 +#: src/Module/Register.php:159 msgid "Your Display Name (as you would like it to be displayed on this system):" msgstr "" -#: src/Module/Register.php:152 +#: src/Module/Register.php:160 msgid "Your Email Address (initial information will be sent there, so this must be a valid address):" msgstr "" -#: src/Module/Register.php:153 +#: src/Module/Register.php:161 msgid "Please repeat your e-mail address:" msgstr "" -#: src/Module/Register.php:155 src/Module/Security/PasswordTooLong.php:86 +#: src/Module/Register.php:163 src/Module/Security/PasswordTooLong.php:86 #: src/Module/Settings/Account.php:513 msgid "New Password:" msgstr "" -#: src/Module/Register.php:155 +#: src/Module/Register.php:163 msgid "Leave empty for an auto generated password." msgstr "" -#: src/Module/Register.php:156 src/Module/Security/PasswordTooLong.php:87 +#: src/Module/Register.php:164 src/Module/Security/PasswordTooLong.php:87 #: src/Module/Settings/Account.php:514 msgid "Confirm:" msgstr "" -#: src/Module/Register.php:157 +#: src/Module/Register.php:165 #, php-format msgid "Choose a profile nickname. This must begin with a text character. Your profile address on this site will then be \"nickname@%s\"." msgstr "" -#: src/Module/Register.php:158 +#: src/Module/Register.php:166 msgid "Choose a nickname: " msgstr "" -#: src/Module/Register.php:166 src/Module/User/Import.php:104 +#: src/Module/Register.php:174 src/Module/User/Import.php:104 msgid "Import" msgstr "" -#: src/Module/Register.php:167 +#: src/Module/Register.php:175 msgid "Import your profile to this friendica instance" msgstr "" -#: src/Module/Register.php:174 +#: src/Module/Register.php:182 msgid "Note: This node explicitly contains adult content" msgstr "" -#: src/Module/Register.php:176 src/Module/Settings/Delegation.php:167 +#: src/Module/Register.php:184 src/Module/Settings/Delegation.php:167 msgid "Parent Password:" msgstr "" -#: src/Module/Register.php:176 src/Module/Settings/Delegation.php:167 +#: src/Module/Register.php:184 src/Module/Settings/Delegation.php:167 msgid "Please enter the password of the parent account to legitimize your request." msgstr "" -#: src/Module/Register.php:205 +#: src/Module/Register.php:218 msgid "Password doesn't match." msgstr "" -#: src/Module/Register.php:211 +#: src/Module/Register.php:224 msgid "Please enter your password." msgstr "" -#: src/Module/Register.php:253 +#: src/Module/Register.php:266 msgid "You have entered too much information." msgstr "" -#: src/Module/Register.php:276 +#: src/Module/Register.php:289 msgid "Please enter the identical mail address in the second field." msgstr "" -#: src/Module/Register.php:284 +#: src/Module/Register.php:297 msgid "Nickname cannot start with a digit." msgstr "" -#: src/Module/Register.php:286 +#: src/Module/Register.php:299 msgid "Nickname can only contain US-ASCII characters." msgstr "" -#: src/Module/Register.php:315 +#: src/Module/Register.php:328 msgid "The additional account was created." msgstr "" -#: src/Module/Register.php:340 +#: src/Module/Register.php:353 msgid "Registration successful. Please check your email for further instructions." msgstr "" -#: src/Module/Register.php:348 +#: src/Module/Register.php:361 #, php-format msgid "Failed to send email message. Here your accout details:
    login: %s
    password: %s

    You can change your password after login." msgstr "" -#: src/Module/Register.php:355 +#: src/Module/Register.php:368 msgid "Registration successful." msgstr "" -#: src/Module/Register.php:364 src/Module/Register.php:371 -#: src/Module/Register.php:381 +#: src/Module/Register.php:377 src/Module/Register.php:384 +#: src/Module/Register.php:394 msgid "Your registration can not be processed." msgstr "" -#: src/Module/Register.php:370 +#: src/Module/Register.php:383 msgid "You have to leave a request note for the admin." msgstr "" -#: src/Module/Register.php:380 +#: src/Module/Register.php:393 msgid "An internal error occured." msgstr "" -#: src/Module/Register.php:402 +#: src/Module/Register.php:415 msgid "Your registration is pending approval by the site owner." msgstr "" -#: src/Module/Search/Acl.php:64 +#: src/Module/Search/Acl.php:78 msgid "You must be logged in to use this module." msgstr "" @@ -9255,7 +9260,7 @@ msgid "Basic Settings" msgstr "" #: src/Module/Settings/Account.php:522 -#: src/Module/Settings/Profile/Index.php:274 +#: src/Module/Settings/Profile/Index.php:299 msgid "Display name:" msgstr "" @@ -9555,42 +9560,42 @@ msgstr "" msgid "When selected, the channel results are reshared. This only works for public ActivityPub posts from the public timeline or the user defined circles." msgstr "" -#: src/Module/Settings/Channels.php:176 src/Module/Settings/Channels.php:197 +#: src/Module/Settings/Channels.php:176 src/Module/Settings/Channels.php:202 #: src/Module/Settings/Display.php:339 msgid "Label" msgstr "" -#: src/Module/Settings/Channels.php:177 src/Module/Settings/Channels.php:198 +#: src/Module/Settings/Channels.php:177 src/Module/Settings/Channels.php:203 #: src/Module/Settings/Display.php:340 #: src/Module/Settings/TwoFactor/AppSpecific.php:123 msgid "Description" msgstr "" -#: src/Module/Settings/Channels.php:178 src/Module/Settings/Channels.php:199 +#: src/Module/Settings/Channels.php:178 src/Module/Settings/Channels.php:204 msgid "Access Key" msgstr "" -#: src/Module/Settings/Channels.php:179 src/Module/Settings/Channels.php:200 +#: src/Module/Settings/Channels.php:179 src/Module/Settings/Channels.php:205 msgid "Circle/Channel" msgstr "" -#: src/Module/Settings/Channels.php:180 src/Module/Settings/Channels.php:201 +#: src/Module/Settings/Channels.php:180 src/Module/Settings/Channels.php:206 msgid "Include Tags" msgstr "" -#: src/Module/Settings/Channels.php:181 src/Module/Settings/Channels.php:202 +#: src/Module/Settings/Channels.php:181 src/Module/Settings/Channels.php:207 msgid "Exclude Tags" msgstr "" -#: src/Module/Settings/Channels.php:182 src/Module/Settings/Channels.php:203 +#: src/Module/Settings/Channels.php:182 src/Module/Settings/Channels.php:208 msgid "Minimum Size" msgstr "" -#: src/Module/Settings/Channels.php:183 src/Module/Settings/Channels.php:204 +#: src/Module/Settings/Channels.php:183 src/Module/Settings/Channels.php:209 msgid "Maximum Size" msgstr "" -#: src/Module/Settings/Channels.php:184 src/Module/Settings/Channels.php:205 +#: src/Module/Settings/Channels.php:184 src/Module/Settings/Channels.php:210 msgid "Full Text Search" msgstr "" @@ -9606,76 +9611,80 @@ msgstr "" msgid "Check to delete this entry from the channel list" msgstr "" -#: src/Module/Settings/Channels.php:197 -msgid "Short name for the channel. It is displayed on the channels widget." +#: src/Module/Settings/Channels.php:196 +msgid "Comma separated list of tags. If a post contain any of these tags, then it will not be part of this channel." msgstr "" #: src/Module/Settings/Channels.php:198 -msgid "This should describe the content of the channel in a few word." -msgstr "" - -#: src/Module/Settings/Channels.php:199 -msgid "When you want to access this channel via an access key, you can define it here. Pay attention to not use an already used one." -msgstr "" - -#: src/Module/Settings/Channels.php:200 -msgid "Select a circle or channel, that your channel should be based on." -msgstr "" - -#: src/Module/Settings/Channels.php:201 -msgid "Comma separated list of tags. A post will be used when it contains any of the listed tags." -msgstr "" - -#: src/Module/Settings/Channels.php:202 msgid "Comma separated list of tags. If a post contain any of these tags, then it will not be part of nthis channel." msgstr "" +#: src/Module/Settings/Channels.php:202 +msgid "Short name for the channel. It is displayed on the channels widget." +msgstr "" + #: src/Module/Settings/Channels.php:203 -msgid "Minimum post size. Leave empty for no minimum size. The size is calculated without links, attached posts, mentions or hashtags." +msgid "This should describe the content of the channel in a few word." msgstr "" #: src/Module/Settings/Channels.php:204 -msgid "Maximum post size. Leave empty for no maximum size. The size is calculated without links, attached posts, mentions or hashtags." +msgid "When you want to access this channel via an access key, you can define it here. Pay attention to not use an already used one." msgstr "" #: src/Module/Settings/Channels.php:205 +msgid "Select a circle or channel, that your channel should be based on." +msgstr "" + +#: src/Module/Settings/Channels.php:206 +msgid "Comma separated list of tags. A post will be used when it contains any of the listed tags." +msgstr "" + +#: src/Module/Settings/Channels.php:208 +msgid "Minimum post size. Leave empty for no minimum size. The size is calculated without links, attached posts, mentions or hashtags." +msgstr "" + +#: src/Module/Settings/Channels.php:209 +msgid "Maximum post size. Leave empty for no maximum size. The size is calculated without links, attached posts, mentions or hashtags." +msgstr "" + +#: src/Module/Settings/Channels.php:210 #, php-format msgid "Search terms for the body, supports the \"boolean mode\" operators from MariaDB. See the help for a complete list of operators and additional keywords: %s" msgstr "" -#: src/Module/Settings/Channels.php:206 +#: src/Module/Settings/Channels.php:211 msgid "Check to display images in the channel." msgstr "" -#: src/Module/Settings/Channels.php:207 +#: src/Module/Settings/Channels.php:212 msgid "Check to display videos in the channel." msgstr "" -#: src/Module/Settings/Channels.php:208 +#: src/Module/Settings/Channels.php:213 msgid "Check to display audio in the channel." msgstr "" -#: src/Module/Settings/Channels.php:209 +#: src/Module/Settings/Channels.php:214 msgid "Select all languages that you want to see in this channel." msgstr "" -#: src/Module/Settings/Channels.php:213 +#: src/Module/Settings/Channels.php:218 msgid "Add new entry to the channel list" msgstr "" -#: src/Module/Settings/Channels.php:214 +#: src/Module/Settings/Channels.php:219 msgid "Add" msgstr "" -#: src/Module/Settings/Channels.php:216 +#: src/Module/Settings/Channels.php:221 msgid "Current Entries in the channel list" msgstr "" -#: src/Module/Settings/Channels.php:219 +#: src/Module/Settings/Channels.php:224 msgid "Delete entry from the channel list" msgstr "" -#: src/Module/Settings/Channels.php:220 +#: src/Module/Settings/Channels.php:225 msgid "Delete entry from the channel list?" msgstr "" @@ -10167,81 +10176,81 @@ msgstr "" msgid "Remove authorization" msgstr "" -#: src/Module/Settings/Profile/Index.php:102 +#: src/Module/Settings/Profile/Index.php:126 msgid "Display Name is required." msgstr "" -#: src/Module/Settings/Profile/Index.php:156 +#: src/Module/Settings/Profile/Index.php:180 msgid "Profile couldn't be updated." msgstr "" -#: src/Module/Settings/Profile/Index.php:196 -#: src/Module/Settings/Profile/Index.php:217 +#: src/Module/Settings/Profile/Index.php:220 +#: src/Module/Settings/Profile/Index.php:241 msgid "Label:" msgstr "" -#: src/Module/Settings/Profile/Index.php:197 -#: src/Module/Settings/Profile/Index.php:218 +#: src/Module/Settings/Profile/Index.php:221 +#: src/Module/Settings/Profile/Index.php:242 msgid "Value:" msgstr "" -#: src/Module/Settings/Profile/Index.php:208 -#: src/Module/Settings/Profile/Index.php:229 +#: src/Module/Settings/Profile/Index.php:232 +#: src/Module/Settings/Profile/Index.php:253 msgid "Field Permissions" msgstr "" -#: src/Module/Settings/Profile/Index.php:209 -#: src/Module/Settings/Profile/Index.php:230 +#: src/Module/Settings/Profile/Index.php:233 +#: src/Module/Settings/Profile/Index.php:254 msgid "(click to open/close)" msgstr "" -#: src/Module/Settings/Profile/Index.php:215 +#: src/Module/Settings/Profile/Index.php:239 msgid "Add a new profile field" msgstr "" -#: src/Module/Settings/Profile/Index.php:238 +#: src/Module/Settings/Profile/Index.php:262 msgid "The homepage is verified. A rel=\"me\" link back to your Friendica profile page was found on the homepage." msgstr "" -#: src/Module/Settings/Profile/Index.php:240 +#: src/Module/Settings/Profile/Index.php:264 #, php-format msgid "To verify your homepage, add a rel=\"me\" link to it, pointing to your profile URL (%s)." msgstr "" -#: src/Module/Settings/Profile/Index.php:246 +#: src/Module/Settings/Profile/Index.php:270 msgid "Profile Actions" msgstr "" -#: src/Module/Settings/Profile/Index.php:247 +#: src/Module/Settings/Profile/Index.php:271 msgid "Edit Profile Details" msgstr "" -#: src/Module/Settings/Profile/Index.php:249 +#: src/Module/Settings/Profile/Index.php:273 msgid "Change Profile Photo" msgstr "" -#: src/Module/Settings/Profile/Index.php:252 +#: src/Module/Settings/Profile/Index.php:276 msgid "Profile picture" msgstr "" -#: src/Module/Settings/Profile/Index.php:253 +#: src/Module/Settings/Profile/Index.php:277 msgid "Location" msgstr "" -#: src/Module/Settings/Profile/Index.php:254 src/Util/Temporal.php:83 +#: src/Module/Settings/Profile/Index.php:278 src/Util/Temporal.php:83 #: src/Util/Temporal.php:85 msgid "Miscellaneous" msgstr "" -#: src/Module/Settings/Profile/Index.php:255 +#: src/Module/Settings/Profile/Index.php:279 msgid "Custom Profile Fields" msgstr "" -#: src/Module/Settings/Profile/Index.php:256 src/Module/Welcome.php:44 +#: src/Module/Settings/Profile/Index.php:280 src/Module/Welcome.php:44 msgid "Upload Profile Photo" msgstr "" -#: src/Module/Settings/Profile/Index.php:257 +#: src/Module/Settings/Profile/Index.php:282 #, php-format msgid "" "

    Custom fields appear on your profile page.

    \n" @@ -10251,59 +10260,59 @@ msgid "" "\t\t\t\t

    Non-public fields can only be seen by the selected Friendica contacts or the Friendica contacts in the selected circles.

    " msgstr "" -#: src/Module/Settings/Profile/Index.php:277 +#: src/Module/Settings/Profile/Index.php:302 msgid "Street Address:" msgstr "" -#: src/Module/Settings/Profile/Index.php:278 +#: src/Module/Settings/Profile/Index.php:303 msgid "Locality/City:" msgstr "" -#: src/Module/Settings/Profile/Index.php:279 +#: src/Module/Settings/Profile/Index.php:304 msgid "Region/State:" msgstr "" -#: src/Module/Settings/Profile/Index.php:280 +#: src/Module/Settings/Profile/Index.php:305 msgid "Postal/Zip Code:" msgstr "" -#: src/Module/Settings/Profile/Index.php:281 +#: src/Module/Settings/Profile/Index.php:306 msgid "Country:" msgstr "" -#: src/Module/Settings/Profile/Index.php:283 +#: src/Module/Settings/Profile/Index.php:308 msgid "XMPP (Jabber) address:" msgstr "" -#: src/Module/Settings/Profile/Index.php:283 +#: src/Module/Settings/Profile/Index.php:308 msgid "The XMPP address will be published so that people can follow you there." msgstr "" -#: src/Module/Settings/Profile/Index.php:284 +#: src/Module/Settings/Profile/Index.php:309 msgid "Matrix (Element) address:" msgstr "" -#: src/Module/Settings/Profile/Index.php:284 +#: src/Module/Settings/Profile/Index.php:309 msgid "The Matrix address will be published so that people can follow you there." msgstr "" -#: src/Module/Settings/Profile/Index.php:285 +#: src/Module/Settings/Profile/Index.php:310 msgid "Homepage URL:" msgstr "" -#: src/Module/Settings/Profile/Index.php:286 +#: src/Module/Settings/Profile/Index.php:311 msgid "Public Keywords:" msgstr "" -#: src/Module/Settings/Profile/Index.php:286 +#: src/Module/Settings/Profile/Index.php:311 msgid "(Used for suggesting potential friends, can be seen by others)" msgstr "" -#: src/Module/Settings/Profile/Index.php:287 +#: src/Module/Settings/Profile/Index.php:312 msgid "Private Keywords:" msgstr "" -#: src/Module/Settings/Profile/Index.php:287 +#: src/Module/Settings/Profile/Index.php:312 msgid "(Used for searching profiles, never shown to others)" msgstr "" @@ -10385,8 +10394,8 @@ msgid "If this error persists, please contact your administrator." msgstr "" #: src/Module/Settings/RemoveMe.php:76 -#: src/Navigation/Notifications/Repository/Notify.php:463 -#: src/Navigation/Notifications/Repository/Notify.php:487 +#: src/Navigation/Notifications/Repository/Notify.php:475 +#: src/Navigation/Notifications/Repository/Notify.php:499 msgid "[Friendica System Notify]" msgstr "" @@ -10806,24 +10815,24 @@ msgstr "" msgid "The requested item doesn't exist or has been deleted." msgstr "" -#: src/Module/User/Delegation.php:134 +#: src/Module/User/Delegation.php:138 #, php-format msgid "You are now logged in as %s" msgstr "" -#: src/Module/User/Delegation.php:173 +#: src/Module/User/Delegation.php:177 msgid "Switch between your accounts" msgstr "" -#: src/Module/User/Delegation.php:174 +#: src/Module/User/Delegation.php:178 msgid "Manage your accounts" msgstr "" -#: src/Module/User/Delegation.php:175 +#: src/Module/User/Delegation.php:179 msgid "Toggle between different identities or community/group pages which share your account details or which you have been granted \"manage\" permissions" msgstr "" -#: src/Module/User/Delegation.php:176 +#: src/Module/User/Delegation.php:180 msgid "Select an identity to manage: " msgstr "" @@ -11189,217 +11198,217 @@ msgstr "" msgid "%1$s commented on your thread %2$s" msgstr "" -#: src/Navigation/Notifications/Repository/Notify.php:210 -#: src/Navigation/Notifications/Repository/Notify.php:754 +#: src/Navigation/Notifications/Repository/Notify.php:222 +#: src/Navigation/Notifications/Repository/Notify.php:770 msgid "[Friendica:Notify]" msgstr "" -#: src/Navigation/Notifications/Repository/Notify.php:278 +#: src/Navigation/Notifications/Repository/Notify.php:290 #, php-format msgid "%s New mail received at %s" msgstr "" -#: src/Navigation/Notifications/Repository/Notify.php:280 +#: src/Navigation/Notifications/Repository/Notify.php:292 #, php-format msgid "%1$s sent you a new private message at %2$s." msgstr "" -#: src/Navigation/Notifications/Repository/Notify.php:281 +#: src/Navigation/Notifications/Repository/Notify.php:293 msgid "a private message" msgstr "" -#: src/Navigation/Notifications/Repository/Notify.php:281 +#: src/Navigation/Notifications/Repository/Notify.php:293 #, php-format msgid "%1$s sent you %2$s." msgstr "" -#: src/Navigation/Notifications/Repository/Notify.php:283 +#: src/Navigation/Notifications/Repository/Notify.php:295 #, php-format msgid "Please visit %s to view and/or reply to your private messages." msgstr "" -#: src/Navigation/Notifications/Repository/Notify.php:313 +#: src/Navigation/Notifications/Repository/Notify.php:325 #, php-format msgid "%1$s commented on %2$s's %3$s %4$s" msgstr "" -#: src/Navigation/Notifications/Repository/Notify.php:318 +#: src/Navigation/Notifications/Repository/Notify.php:330 #, php-format msgid "%1$s commented on your %2$s %3$s" msgstr "" -#: src/Navigation/Notifications/Repository/Notify.php:322 +#: src/Navigation/Notifications/Repository/Notify.php:334 #, php-format msgid "%1$s commented on their %2$s %3$s" msgstr "" -#: src/Navigation/Notifications/Repository/Notify.php:326 -#: src/Navigation/Notifications/Repository/Notify.php:788 +#: src/Navigation/Notifications/Repository/Notify.php:338 +#: src/Navigation/Notifications/Repository/Notify.php:804 #, php-format msgid "%1$s Comment to conversation #%2$d by %3$s" msgstr "" -#: src/Navigation/Notifications/Repository/Notify.php:328 +#: src/Navigation/Notifications/Repository/Notify.php:340 #, php-format msgid "%s commented on an item/conversation you have been following." msgstr "" -#: src/Navigation/Notifications/Repository/Notify.php:332 -#: src/Navigation/Notifications/Repository/Notify.php:348 -#: src/Navigation/Notifications/Repository/Notify.php:814 +#: src/Navigation/Notifications/Repository/Notify.php:344 +#: src/Navigation/Notifications/Repository/Notify.php:360 +#: src/Navigation/Notifications/Repository/Notify.php:830 #, php-format msgid "Please visit %s to view and/or reply to the conversation." msgstr "" -#: src/Navigation/Notifications/Repository/Notify.php:339 +#: src/Navigation/Notifications/Repository/Notify.php:351 #, php-format msgid "%s %s posted to your profile wall" msgstr "" -#: src/Navigation/Notifications/Repository/Notify.php:341 +#: src/Navigation/Notifications/Repository/Notify.php:353 #, php-format msgid "%1$s posted to your profile wall at %2$s" msgstr "" -#: src/Navigation/Notifications/Repository/Notify.php:343 +#: src/Navigation/Notifications/Repository/Notify.php:355 #, php-format msgid "%1$s posted to [url=%2$s]your wall[/url]" msgstr "" -#: src/Navigation/Notifications/Repository/Notify.php:356 +#: src/Navigation/Notifications/Repository/Notify.php:368 #, php-format msgid "%s Introduction received" msgstr "" -#: src/Navigation/Notifications/Repository/Notify.php:358 +#: src/Navigation/Notifications/Repository/Notify.php:370 #, php-format msgid "You've received an introduction from '%1$s' at %2$s" msgstr "" -#: src/Navigation/Notifications/Repository/Notify.php:360 +#: src/Navigation/Notifications/Repository/Notify.php:372 #, php-format msgid "You've received [url=%1$s]an introduction[/url] from %2$s." msgstr "" -#: src/Navigation/Notifications/Repository/Notify.php:365 -#: src/Navigation/Notifications/Repository/Notify.php:414 +#: src/Navigation/Notifications/Repository/Notify.php:377 +#: src/Navigation/Notifications/Repository/Notify.php:426 #, php-format msgid "You may visit their profile at %s" msgstr "" -#: src/Navigation/Notifications/Repository/Notify.php:367 +#: src/Navigation/Notifications/Repository/Notify.php:379 #, php-format msgid "Please visit %s to approve or reject the introduction." msgstr "" -#: src/Navigation/Notifications/Repository/Notify.php:374 +#: src/Navigation/Notifications/Repository/Notify.php:386 #, php-format msgid "%s A new person is sharing with you" msgstr "" -#: src/Navigation/Notifications/Repository/Notify.php:376 -#: src/Navigation/Notifications/Repository/Notify.php:378 +#: src/Navigation/Notifications/Repository/Notify.php:388 +#: src/Navigation/Notifications/Repository/Notify.php:390 #, php-format msgid "%1$s is sharing with you at %2$s" msgstr "" -#: src/Navigation/Notifications/Repository/Notify.php:385 +#: src/Navigation/Notifications/Repository/Notify.php:397 #, php-format msgid "%s You have a new follower" msgstr "" -#: src/Navigation/Notifications/Repository/Notify.php:387 -#: src/Navigation/Notifications/Repository/Notify.php:389 +#: src/Navigation/Notifications/Repository/Notify.php:399 +#: src/Navigation/Notifications/Repository/Notify.php:401 #, php-format msgid "You have a new follower at %2$s : %1$s" msgstr "" -#: src/Navigation/Notifications/Repository/Notify.php:402 +#: src/Navigation/Notifications/Repository/Notify.php:414 #, php-format msgid "%s Friend suggestion received" msgstr "" -#: src/Navigation/Notifications/Repository/Notify.php:404 +#: src/Navigation/Notifications/Repository/Notify.php:416 #, php-format msgid "You've received a friend suggestion from '%1$s' at %2$s" msgstr "" -#: src/Navigation/Notifications/Repository/Notify.php:406 +#: src/Navigation/Notifications/Repository/Notify.php:418 #, php-format msgid "You've received [url=%1$s]a friend suggestion[/url] for %2$s from %3$s." msgstr "" -#: src/Navigation/Notifications/Repository/Notify.php:412 +#: src/Navigation/Notifications/Repository/Notify.php:424 msgid "Name:" msgstr "" -#: src/Navigation/Notifications/Repository/Notify.php:413 +#: src/Navigation/Notifications/Repository/Notify.php:425 msgid "Photo:" msgstr "" -#: src/Navigation/Notifications/Repository/Notify.php:416 +#: src/Navigation/Notifications/Repository/Notify.php:428 #, php-format msgid "Please visit %s to approve or reject the suggestion." msgstr "" -#: src/Navigation/Notifications/Repository/Notify.php:424 -#: src/Navigation/Notifications/Repository/Notify.php:440 +#: src/Navigation/Notifications/Repository/Notify.php:436 +#: src/Navigation/Notifications/Repository/Notify.php:452 #, php-format msgid "%s Connection accepted" msgstr "" -#: src/Navigation/Notifications/Repository/Notify.php:426 -#: src/Navigation/Notifications/Repository/Notify.php:442 +#: src/Navigation/Notifications/Repository/Notify.php:438 +#: src/Navigation/Notifications/Repository/Notify.php:454 #, php-format msgid "'%1$s' has accepted your connection request at %2$s" msgstr "" -#: src/Navigation/Notifications/Repository/Notify.php:428 -#: src/Navigation/Notifications/Repository/Notify.php:444 +#: src/Navigation/Notifications/Repository/Notify.php:440 +#: src/Navigation/Notifications/Repository/Notify.php:456 #, php-format msgid "%2$s has accepted your [url=%1$s]connection request[/url]." msgstr "" -#: src/Navigation/Notifications/Repository/Notify.php:433 +#: src/Navigation/Notifications/Repository/Notify.php:445 msgid "You are now mutual friends and may exchange status updates, photos, and email without restriction." msgstr "" -#: src/Navigation/Notifications/Repository/Notify.php:435 +#: src/Navigation/Notifications/Repository/Notify.php:447 #, php-format msgid "Please visit %s if you wish to make any changes to this relationship." msgstr "" -#: src/Navigation/Notifications/Repository/Notify.php:449 +#: src/Navigation/Notifications/Repository/Notify.php:461 #, php-format msgid "'%1$s' has chosen to accept you a fan, which restricts some forms of communication - such as private messaging and some profile interactions. If this is a celebrity or community page, these settings were applied automatically." msgstr "" -#: src/Navigation/Notifications/Repository/Notify.php:451 +#: src/Navigation/Notifications/Repository/Notify.php:463 #, php-format msgid "'%1$s' may choose to extend this into a two-way or more permissive relationship in the future." msgstr "" -#: src/Navigation/Notifications/Repository/Notify.php:453 +#: src/Navigation/Notifications/Repository/Notify.php:465 #, php-format msgid "Please visit %s if you wish to make any changes to this relationship." msgstr "" -#: src/Navigation/Notifications/Repository/Notify.php:463 +#: src/Navigation/Notifications/Repository/Notify.php:475 msgid "registration request" msgstr "" -#: src/Navigation/Notifications/Repository/Notify.php:465 +#: src/Navigation/Notifications/Repository/Notify.php:477 #, php-format msgid "You've received a registration request from '%1$s' at %2$s" msgstr "" -#: src/Navigation/Notifications/Repository/Notify.php:467 +#: src/Navigation/Notifications/Repository/Notify.php:479 #, php-format msgid "You've received a [url=%1$s]registration request[/url] from %2$s." msgstr "" -#: src/Navigation/Notifications/Repository/Notify.php:473 -#: src/Navigation/Notifications/Repository/Notify.php:497 +#: src/Navigation/Notifications/Repository/Notify.php:485 +#: src/Navigation/Notifications/Repository/Notify.php:509 #, php-format msgid "" "Display Name:\t%s\n" @@ -11407,46 +11416,46 @@ msgid "" "Login Name:\t%s (%s)" msgstr "" -#: src/Navigation/Notifications/Repository/Notify.php:480 +#: src/Navigation/Notifications/Repository/Notify.php:492 #, php-format msgid "Please visit %s to approve or reject the request." msgstr "" -#: src/Navigation/Notifications/Repository/Notify.php:487 +#: src/Navigation/Notifications/Repository/Notify.php:499 msgid "new registration" msgstr "" -#: src/Navigation/Notifications/Repository/Notify.php:489 +#: src/Navigation/Notifications/Repository/Notify.php:501 #, php-format msgid "You've received a new registration from '%1$s' at %2$s" msgstr "" -#: src/Navigation/Notifications/Repository/Notify.php:491 +#: src/Navigation/Notifications/Repository/Notify.php:503 #, php-format msgid "You've received a [url=%1$s]new registration[/url] from %2$s." msgstr "" -#: src/Navigation/Notifications/Repository/Notify.php:504 +#: src/Navigation/Notifications/Repository/Notify.php:516 #, php-format msgid "Please visit %s to have a look at the new registration." msgstr "" -#: src/Navigation/Notifications/Repository/Notify.php:782 +#: src/Navigation/Notifications/Repository/Notify.php:798 #, php-format msgid "%s %s tagged you" msgstr "" -#: src/Navigation/Notifications/Repository/Notify.php:785 +#: src/Navigation/Notifications/Repository/Notify.php:801 #, php-format msgid "%s %s shared a new post" msgstr "" -#: src/Navigation/Notifications/Repository/Notify.php:793 +#: src/Navigation/Notifications/Repository/Notify.php:809 #, php-format msgid "%1$s %2$s liked your post #%3$d" msgstr "" -#: src/Navigation/Notifications/Repository/Notify.php:796 +#: src/Navigation/Notifications/Repository/Notify.php:812 #, php-format msgid "%1$s %2$s liked your comment on #%3$d" msgstr "" @@ -11470,282 +11479,282 @@ msgstr "" msgid "%s posted an update." msgstr "" -#: src/Object/Post.php:124 +#: src/Object/Post.php:123 msgid "Private Message" msgstr "" -#: src/Object/Post.php:128 +#: src/Object/Post.php:127 msgid "Public Message" msgstr "" -#: src/Object/Post.php:132 +#: src/Object/Post.php:131 msgid "Unlisted Message" msgstr "" -#: src/Object/Post.php:168 +#: src/Object/Post.php:167 msgid "This entry was edited" msgstr "" -#: src/Object/Post.php:196 +#: src/Object/Post.php:195 msgid "Connector Message" msgstr "" -#: src/Object/Post.php:225 src/Object/Post.php:227 +#: src/Object/Post.php:224 src/Object/Post.php:226 msgid "Edit" msgstr "" -#: src/Object/Post.php:261 +#: src/Object/Post.php:260 msgid "Delete globally" msgstr "" -#: src/Object/Post.php:261 +#: src/Object/Post.php:260 msgid "Remove locally" msgstr "" -#: src/Object/Post.php:268 +#: src/Object/Post.php:267 #, php-format msgid "Block %s" msgstr "" -#: src/Object/Post.php:273 +#: src/Object/Post.php:272 #, php-format msgid "Ignore %s" msgstr "" -#: src/Object/Post.php:278 +#: src/Object/Post.php:277 #, php-format msgid "Collapse %s" msgstr "" -#: src/Object/Post.php:282 +#: src/Object/Post.php:281 msgid "Report post" msgstr "" -#: src/Object/Post.php:293 +#: src/Object/Post.php:292 msgid "Save to folder" msgstr "" -#: src/Object/Post.php:333 +#: src/Object/Post.php:338 msgid "I will attend" msgstr "" -#: src/Object/Post.php:333 +#: src/Object/Post.php:338 msgid "I will not attend" msgstr "" -#: src/Object/Post.php:333 +#: src/Object/Post.php:338 msgid "I might attend" msgstr "" -#: src/Object/Post.php:380 +#: src/Object/Post.php:385 msgid "Ignore thread" msgstr "" -#: src/Object/Post.php:381 +#: src/Object/Post.php:386 msgid "Unignore thread" msgstr "" -#: src/Object/Post.php:382 +#: src/Object/Post.php:387 msgid "Toggle ignore status" msgstr "" -#: src/Object/Post.php:392 +#: src/Object/Post.php:397 msgid "Add star" msgstr "" -#: src/Object/Post.php:393 +#: src/Object/Post.php:398 msgid "Remove star" msgstr "" -#: src/Object/Post.php:394 +#: src/Object/Post.php:399 msgid "Toggle star status" msgstr "" -#: src/Object/Post.php:405 +#: src/Object/Post.php:410 msgid "Pin" msgstr "" -#: src/Object/Post.php:406 +#: src/Object/Post.php:411 msgid "Unpin" msgstr "" -#: src/Object/Post.php:407 +#: src/Object/Post.php:412 msgid "Toggle pin status" msgstr "" -#: src/Object/Post.php:410 +#: src/Object/Post.php:415 msgid "Pinned" msgstr "" -#: src/Object/Post.php:415 +#: src/Object/Post.php:420 msgid "Add tag" msgstr "" -#: src/Object/Post.php:430 +#: src/Object/Post.php:435 msgid "Quote share this" msgstr "" -#: src/Object/Post.php:430 +#: src/Object/Post.php:435 msgid "Quote Share" msgstr "" -#: src/Object/Post.php:433 +#: src/Object/Post.php:438 msgid "Reshare this" msgstr "" -#: src/Object/Post.php:433 +#: src/Object/Post.php:438 msgid "Reshare" msgstr "" -#: src/Object/Post.php:434 +#: src/Object/Post.php:439 msgid "Cancel your Reshare" msgstr "" -#: src/Object/Post.php:434 +#: src/Object/Post.php:439 msgid "Unshare" msgstr "" -#: src/Object/Post.php:478 +#: src/Object/Post.php:483 #, php-format msgid "%s (Received %s)" msgstr "" -#: src/Object/Post.php:484 +#: src/Object/Post.php:489 msgid "Comment this item on your system" msgstr "" -#: src/Object/Post.php:484 +#: src/Object/Post.php:489 msgid "Remote comment" msgstr "" -#: src/Object/Post.php:508 +#: src/Object/Post.php:513 msgid "Share via ..." msgstr "" -#: src/Object/Post.php:508 +#: src/Object/Post.php:513 msgid "Share via external services" msgstr "" -#: src/Object/Post.php:515 +#: src/Object/Post.php:520 msgid "Unknown parent" msgstr "" -#: src/Object/Post.php:519 +#: src/Object/Post.php:524 #, php-format msgid "in reply to %s" msgstr "" -#: src/Object/Post.php:521 +#: src/Object/Post.php:526 msgid "Parent is probably private or not federated." msgstr "" -#: src/Object/Post.php:545 +#: src/Object/Post.php:550 msgid "to" msgstr "" -#: src/Object/Post.php:546 +#: src/Object/Post.php:551 msgid "via" msgstr "" -#: src/Object/Post.php:547 +#: src/Object/Post.php:552 msgid "Wall-to-Wall" msgstr "" -#: src/Object/Post.php:548 +#: src/Object/Post.php:553 msgid "via Wall-To-Wall:" msgstr "" -#: src/Object/Post.php:603 +#: src/Object/Post.php:608 #, php-format msgid "Reply to %s" msgstr "" -#: src/Object/Post.php:625 +#: src/Object/Post.php:630 msgid "Notifier task is pending" msgstr "" -#: src/Object/Post.php:626 +#: src/Object/Post.php:631 msgid "Delivery to remote servers is pending" msgstr "" -#: src/Object/Post.php:627 +#: src/Object/Post.php:632 msgid "Delivery to remote servers is underway" msgstr "" -#: src/Object/Post.php:628 +#: src/Object/Post.php:633 msgid "Delivery to remote servers is mostly done" msgstr "" -#: src/Object/Post.php:629 +#: src/Object/Post.php:634 msgid "Delivery to remote servers is done" msgstr "" -#: src/Object/Post.php:651 +#: src/Object/Post.php:659 #, php-format msgid "%d comment" msgid_plural "%d comments" msgstr[0] "" msgstr[1] "" -#: src/Object/Post.php:652 +#: src/Object/Post.php:660 msgid "Show more" msgstr "" -#: src/Object/Post.php:653 +#: src/Object/Post.php:661 msgid "Show fewer" msgstr "" -#: src/Object/Post.php:690 +#: src/Object/Post.php:698 #, php-format msgid "Reshared by: %s" msgstr "" -#: src/Object/Post.php:695 +#: src/Object/Post.php:703 #, php-format msgid "Viewed by: %s" msgstr "" -#: src/Object/Post.php:700 +#: src/Object/Post.php:708 #, php-format msgid "Read by: %s" msgstr "" -#: src/Object/Post.php:705 +#: src/Object/Post.php:713 #, php-format msgid "Liked by: %s" msgstr "" -#: src/Object/Post.php:710 +#: src/Object/Post.php:718 #, php-format msgid "Disliked by: %s" msgstr "" -#: src/Object/Post.php:715 +#: src/Object/Post.php:723 #, php-format msgid "Attended by: %s" msgstr "" -#: src/Object/Post.php:720 +#: src/Object/Post.php:728 #, php-format msgid "Maybe attended by: %s" msgstr "" -#: src/Object/Post.php:725 +#: src/Object/Post.php:733 #, php-format msgid "Not attended by: %s" msgstr "" -#: src/Object/Post.php:730 +#: src/Object/Post.php:738 #, php-format msgid "Commented by: %s" msgstr "" -#: src/Object/Post.php:735 +#: src/Object/Post.php:743 #, php-format msgid "Reacted with %s by: %s" msgstr "" -#: src/Object/Post.php:758 +#: src/Object/Post.php:766 #, php-format msgid "Quote shared by: %s" msgstr "" @@ -12115,7 +12124,7 @@ msgstr "" msgid "Community Pages" msgstr "" -#: view/theme/vier/config.php:127 view/theme/vier/theme.php:136 +#: view/theme/vier/config.php:127 view/theme/vier/theme.php:135 msgid "Community Profiles" msgstr "" @@ -12123,7 +12132,7 @@ msgstr "" msgid "Help or @NewHere ?" msgstr "" -#: view/theme/vier/config.php:129 view/theme/vier/theme.php:307 +#: view/theme/vier/config.php:129 view/theme/vier/theme.php:308 msgid "Connect Services" msgstr "" @@ -12131,10 +12140,10 @@ msgstr "" msgid "Find Friends" msgstr "" -#: view/theme/vier/config.php:131 view/theme/vier/theme.php:163 +#: view/theme/vier/config.php:131 view/theme/vier/theme.php:162 msgid "Last users" msgstr "" -#: view/theme/vier/theme.php:222 +#: view/theme/vier/theme.php:221 msgid "Quick Start" msgstr "" diff --git a/view/templates/widget/trending_tags.tpl b/view/templates/widget/trending_tags.tpl index 9058da5480..186b6f4018 100644 --- a/view/templates/widget/trending_tags.tpl +++ b/view/templates/widget/trending_tags.tpl @@ -27,7 +27,7 @@