Merge remote-tracking branch 'origin/develop' into develop.randompenguin1

This commit is contained in:
Philipp Holzer 2025-05-29 19:11:42 +02:00
commit 7ca99aa194
Signed by: nupplaPhil
GPG key ID: 24A7501396EB5432
204 changed files with 5656 additions and 2724 deletions

View file

@ -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

52
.phpstan-addons.neon Normal file
View file

@ -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

View file

@ -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

View file

@ -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}

View file

@ -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:

View file

@ -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",

227
composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "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"
}

View file

@ -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;
</tr>
<tr>
<td><pre>
$a->path = "value";
</pre></td>
<td><pre>
'system' => [
'urlpath' => 'value',
],
</pre></td>
</tr>
<tr>
<td><pre>
$default_timezone = "value";
</pre></td>
<td><pre>
@ -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.

View file

@ -218,7 +218,6 @@ All options will be saved in the `config/local.config.php` and are overruling th
- `-U|--dbuser <username>` The username of the mysql/mariadb database login (env `MYSQL_USER` or `MYSQL_USERNAME`)
- `-P|--dbpass <password>` The password of the mysql/mariadb database login (env `MYSQL_PASSWORD`)
- `-d|--dbdata <database>` The name of the mysql/mariadb database (env `MYSQL_DATABASE`)
- `-u|--urlpath <url_path>` The URL path of Friendica - f.e. '/friendica' (env `FRIENDICA_URL_PATH`)
- `-b|--phppath <php_path>` The path of the PHP binary (env `FRIENDICA_PHP_PATH`)
- `-A|--admin <mail>` The admin email address of Friendica (env `FRIENDICA_ADMIN_MAIL`)
- `-T|--tz <timezone>` The timezone of Friendica (env `FRIENDICA_TZ`)

View file

@ -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

View file

@ -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

35
doc/stats.md Normal file
View file

@ -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

View file

@ -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

View file

@ -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']);

View file

@ -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]');
}

View file

@ -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'] ?? '') ?:

View file

@ -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

View file

@ -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)
);
}

View file

@ -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);
}

View file

@ -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;
}
/**

View file

@ -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']);

View file

@ -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);

View file

@ -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');

View file

@ -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);

View file

@ -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);
}
}
}

View file

@ -265,7 +265,10 @@ class Conversation
$phrase = $this->l10n->tt('<button type="button" %2$s>%1$d person</button> likes this', '<button type="button" %2$s>%1$d people</button> like this', $total, $spanatts);
break;
case 'dislike':
$phrase = $this->l10n->tt('<button type="button" %2$s>%1$d person</button> doesn\'t like this', '<button type="button" %2$s>%1$d peiple</button> don\'t like this', $total, $spanatts);
$dislike_translation_plural = '<button type="button" %2$s>%1$d people</button> 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 = '<button type="button" %2$s>%1$d peiple</button> don\'t like this';
$phrase = $this->l10n->tt('<button type="button" %2$s>%1$d person</button> doesn\'t like this', $dislike_translation_plural, $total, $spanatts);
break;
case 'attendyes':
$phrase = $this->l10n->tt('<button type="button" %2$s>%1$d person</button> attends', '<button type="button" %2$s>%1$d people</button> attend', $total, $spanatts);

View file

@ -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

View file

@ -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) {

View file

@ -92,6 +92,7 @@ class PageInfo
{
$eventDispatcher = DI::eventDispatcher();
/** @var array<string,mixed> */
$data = $eventDispatcher->dispatch(
new ArrayFilterEvent(ArrayFilterEvent::PAGE_INFO, $data),
)->getArray();

View file

@ -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(

View file

@ -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,

View file

@ -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;

View file

@ -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
'<a href="$2" class="mention hashtag" rel="tag">$1<span>$3</span></a>',
$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",
'<bdi>$1<a href="$2" class="userinfo mention" title="$3">$3</a></bdi>',
$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], '<bdi>' . $match[1] . '<a href="' . $url . '" class="userinfo mention" title="' . $match[3] . '">' . $match[3] . '</a></bdi>', $text);
}
}
} elseif ($simple_html == self::MASTODON_API) {
$text = preg_replace(
"/([@!])\[url\=(.*?)\](.*?)\[\/url\]/ism",

View file

@ -121,11 +121,13 @@ class Markdown
$s = str_replace('&#x2672;', html_entity_decode('&#x2672;', 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);

View file

@ -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;

View file

@ -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];

View file

@ -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;
}

View file

@ -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,
];
}
}

View file

@ -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 [];
}
}

View file

@ -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,
];
}
}

View file

@ -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,
];
}
}

View file

@ -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 [];
}
}
}

View file

@ -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,
];
}
}

View file

@ -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]);

View file

@ -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
*/

View file

@ -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)
{

View file

@ -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
*

View file

@ -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;
}

View file

@ -0,0 +1,73 @@
<?php
// Copyright (C) 2010-2024, the Friendica project
// SPDX-FileCopyrightText: 2010-2024 the Friendica project
//
// SPDX-License-Identifier: AGPL-3.0-or-later
declare(strict_types=1);
namespace Friendica\Core\Logger\Factory;
use Friendica\Core\Config\Capability\IManageConfigValues;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
/**
* Delegates the creation of a logger based on config to other factories
*
* @internal
*/
final class DelegatingLoggerFactory implements LoggerFactory
{
private IManageConfigValues $config;
/** @var array<string,LoggerFactory> */
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;
}
}

View file

@ -1,61 +0,0 @@
<?php
// Copyright (C) 2010-2024, the Friendica project
// SPDX-FileCopyrightText: 2010-2024 the Friendica project
//
// SPDX-License-Identifier: AGPL-3.0-or-later
declare(strict_types=1);
namespace Friendica\Core\Logger\Factory;
use Friendica\Core\Config\Capability\IManageConfigValues;
use Friendica\Core\Hooks\Capability\ICanCreateInstances;
use Friendica\Util\Profiler;
use Psr\Log\LoggerInterface;
/**
* Bridge for the legacy Logger factory.
*
* This class can be removed after the following classes are replaced or
* refactored implementing the `\Friendica\Core\Logger\Factory\LoggerFactory`:
*
* - Friendica\Core\Logger\Factory\StreamLogger
* - Friendica\Core\Logger\Factory\SyslogLogger
* - monolog addon: Friendica\Addon\monolog\src\Factory\Monolog
*
* @see \Friendica\Core\Logger\Factory\StreamLogger
* @see \Friendica\Core\Logger\Factory\SyslogLogger
*
* @internal
*/
final class LegacyLoggerFactory implements LoggerFactory
{
private ICanCreateInstances $instanceCreator;
private IManageConfigValues $config;
private Profiler $profiler;
public function __construct(ICanCreateInstances $instanceCreator, IManageConfigValues $config, Profiler $profiler)
{
$this->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);
}
}

View file

@ -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;
}

View file

@ -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');

View file

@ -0,0 +1,76 @@
<?php
// Copyright (C) 2010-2024, the Friendica project
// SPDX-FileCopyrightText: 2010-2024 the Friendica project
//
// SPDX-License-Identifier: AGPL-3.0-or-later
declare(strict_types=1);
namespace Friendica\Core\Logger\Factory;
use Friendica\Core\Config\Capability\IManageConfigValues;
use Friendica\Core\Logger\Capability\IHaveCallIntrospections;
use Friendica\Core\Logger\Exception\LoggerArgumentException;
use Friendica\Core\Logger\Exception\LogLevelException;
use Friendica\Core\Logger\Type\StreamLogger;
use Friendica\Core\Logger\Util\FileSystemUtil;
use Psr\Log\LoggerInterface;
/**
* The logger factory for the StreamLogger instance
*
* @see StreamLogger
*
* @internal
*/
final class StreamLoggerFactory implements LoggerFactory
{
private IManageConfigValues $config;
private IHaveCallIntrospections $introspection;
private FileSystemUtil $fileSystem;
public function __construct(
IManageConfigValues $config,
IHaveCallIntrospections $introspection,
FileSystemUtil $fileSystem
) {
$this->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()
);
}
}

View file

@ -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'));

View file

@ -0,0 +1,66 @@
<?php
// Copyright (C) 2010-2024, the Friendica project
// SPDX-FileCopyrightText: 2010-2024 the Friendica project
//
// SPDX-License-Identifier: AGPL-3.0-or-later
declare(strict_types=1);
namespace Friendica\Core\Logger\Factory;
use Friendica\Core\Config\Capability\IManageConfigValues;
use Friendica\Core\Logger\Capability\IHaveCallIntrospections;
use Friendica\Core\Logger\Exception\LogLevelException;
use Friendica\Core\Logger\Type\SyslogLogger;
use Psr\Log\LoggerInterface;
/**
* The logger factory for the SyslogLogger instance
*
* @see SyslogLogger
*
* @internal
*/
final class SyslogLoggerFactory implements LoggerFactory
{
private IManageConfigValues $config;
private IHaveCallIntrospections $introspection;
public function __construct(
IManageConfigValues $config,
IHaveCallIntrospections $introspection
) {
$this->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
);
}
}

View file

@ -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

View file

@ -29,7 +29,7 @@ class SyslogLogger extends AbstractLogger
/**
* Translates LogLevel log levels to syslog log priorities.
* @var array
* @var array<string,int>
*/
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));
}
}
}

View file

@ -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));

View file

@ -0,0 +1,40 @@
<?php
// Copyright (C) 2010-2024, the Friendica project
// SPDX-FileCopyrightText: 2010-2024 the Friendica project
//
// SPDX-License-Identifier: AGPL-3.0-or-later
namespace Friendica\Core\Logger\Util;
use Friendica\Core\Logger\Exception\LoggerUnusableException;
/**
* interface for Util class for filesystem manipulation for Logger classes
*
* @internal
*/
interface FileSystemUtil
{
/**
* Creates a directory based on a file, which gets accessed
*
* @param string $file The file
*
* @return string The directory name (empty if no directory is found, like urls)
*
* @throws LoggerUnusableException
*/
public function createDir(string $file): string;
/**
* Creates a stream based on a URL (could be a local file or a real URL)
*
* @param string $url The file/url
*
* @return resource the open stream resource
*
* @throws LoggerUnusableException
*/
public function createStream(string $url);
}

View file

@ -37,7 +37,7 @@ class PConfig
*/
public function isConnected(): bool
{
return $this->db->isConnected() & !$this->mode->isInstall();
return $this->db->isConnected() && !$this->mode->isInstall();
}
/**

View file

@ -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'];
}

View file

@ -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)
{

View file

@ -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));
}

View file

@ -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();
}
}

View file

@ -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);

View file

@ -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]);

View file

@ -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;
}

View file

@ -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, [

View file

@ -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) {

View file

@ -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;

View file

@ -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;

View file

@ -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)

View file

@ -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';

View file

@ -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') {

View file

@ -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
{

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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.');

View file

@ -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]);

View file

@ -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,

View file

@ -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;
}
/**

View file

@ -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<string,mixed> $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<string,mixed> */
$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<string,mixed> */
$item = $itemHelper->prepareItemData($item, (bool) $notify);
// Store conversation data
@ -749,6 +750,7 @@ class Item
}
}
/** @var array<string,mixed> */
$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<string,mixed> */
$item = $hook_data['item'] ?? $item;
if ($dummy_session) {
unset($_SESSION['authenticated']);
unset($_SESSION['uid']);
}
} elseif (!$notify) {
Hook::callAll('post_remote', $item);
/** @var array<string,mixed> */
$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) {

View file

@ -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 */

View file

@ -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';
}

View file

@ -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);

View file

@ -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;

View file

@ -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) {

View file

@ -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;
}

View file

@ -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.

View file

@ -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);
}

View file

@ -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: '),

View file

@ -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);

View file

@ -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 = [])

View file

@ -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']]);

View file

@ -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'];

View file

@ -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']);
}

View file

@ -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')]);
}
}

View file

@ -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);
}
}

View file

@ -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');

View file

@ -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;
}
}

View file

@ -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;

View file

@ -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');

View file

@ -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, [

View file

@ -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

View file

@ -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 = '';
}

View file

@ -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);

View file

@ -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]);
}
}

View file

@ -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;
}

View file

@ -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']);

View file

@ -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,
]);
}

View file

@ -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] ?? [],
);
}

View file

@ -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

View file

@ -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'],

View file

@ -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 .= '<link rel="alternate" type="application/atom+xml" href="' . $this->baseUrl . '/feed/' . $nickname . '/" title="' . $this->t('%s\'s posts', htmlspecialchars($profile['name'], ENT_COMPAT, 'UTF-8', true)) . '"/>' . "\n";
$htmlhead .= '<link rel="alternate" type="application/atom+xml" href="' . $this->baseUrl . '/feed/' . $nickname . '/comments" title="' . $this->t('%s\'s comments', htmlspecialchars($profile['name'], ENT_COMPAT, 'UTF-8', true)) . '"/>' . "\n";
$htmlhead .= '<link rel="alternate" type="application/atom+xml" href="' . $this->baseUrl . '/feed/' . $nickname . '/activity" title="' . $this->t('%s\'s timeline', htmlspecialchars($profile['name'], ENT_COMPAT, 'UTF-8', true)) . '"/>' . "\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 .= '<link rel="lrdd" type="application/xrd+xml" href="' . $this->baseUrl . '/xrd/?uri=' . $uri . '" />' . "\n";
header('Link: <' . $this->baseUrl . '/xrd/?uri=' . $uri . '>; rel="lrdd"; type="application/xrd+xml"', false);

View file

@ -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;

View file

@ -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]);

View file

@ -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', '<a href="help/Channels">help/Channels</a>')],

View file

@ -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('<p>Custom fields appear on <a href="%s">your profile page</a>.</p>
'custom_fields_description' => $this->t(
'<p>Custom fields appear on <a href="%s">your profile page</a>.</p>
<p>You can use BBCodes in the field values.</p>
<p>Reorder by dragging the field title.</p>
<p>Empty the label field to remove a custom field.</p>
@ -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;

View file

@ -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([

View file

@ -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'];

108
src/Module/StatsCaching.php Normal file
View file

@ -0,0 +1,108 @@
<?php
// Copyright (C) 2010-2024, the Friendica project
// SPDX-FileCopyrightText: 2010-2024 the Friendica project
//
// SPDX-License-Identifier: AGPL-3.0-or-later
namespace Friendica\Module;
use Friendica\App;
use Friendica\BaseModule;
use Friendica\Core\Cache\Capability\ICanCache;
use Friendica\Core\Cache\Capability\ICanCacheInMemory;
use Friendica\Core\Config\Capability\IManageConfigValues;
use Friendica\Core\L10n;
use Friendica\Core\Lock\Capability\ICanLock;
use Friendica\Core\Lock\Type\CacheLock;
use Friendica\Network\HTTPException\NotFoundException;
use Friendica\Util\Profiler;
use Psr\Log\LoggerInterface;
use Friendica\Network\HTTPException;
/**
* Returns statistics of Cache / Lock instances
*
* @todo Currently not possible to get distributed cache statistics in case the distributed cache (for sessions) is different to the normal cache (not possible to get the distributed cache instance yet)
*/
class StatsCaching extends BaseModule
{
private IManageConfigValues $config;
private ICanCache $cache;
private ICanLock $lock;
public function __construct(L10n $l10n, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, array $server, IManageConfigValues $config, ICanCache $cache, ICanLock $lock, array $parameters = [])
{
parent::__construct($l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters);
$this->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));
}
}

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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
{

View file

@ -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,
];

View file

@ -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]);

View file

@ -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
*/

View file

@ -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} */

View file

@ -100,7 +100,7 @@ class GuzzleResponse extends Response implements ICanHandleHttpResponses, Respon
/** {@inheritDoc} */
public function getReturnCode(): string
{
return $this->getStatusCode();
return (string) $this->getStatusCode();
}
/** {@inheritDoc} */

View file

@ -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 = '';
}
}
}

View file

@ -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;

View file

@ -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;
/**

View file

@ -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[] */

View file

@ -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;
}
}

View file

@ -35,7 +35,7 @@ class ScheduledStatus extends BaseDataTransferObject
'in_reply_to_id' => null,
'application_id' => ''
];
/** @var Attachment */
/** @var array */
protected $media_attachments = [];
/**

View file

@ -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;

View file

@ -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;

View file

@ -31,7 +31,7 @@ class Media extends BaseDataTransferObject
protected $media_url;
/** @var string */
protected $media_url_https;
/** @var string */
/** @var array<string, array<string, mixed>> */
protected $sizes;
/** @var string */
protected $type;

View file

@ -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'];

View file

@ -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;

View file

@ -29,7 +29,7 @@ class ParsedLogLine
/** @var string */
public $message = null;
/** @var string */
/** @var string|null */
public $data = null;
/** @var string */

View file

@ -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) : [];
}

View file

@ -77,8 +77,7 @@ class Thread
break;
default:
DI::logger()->info('[ERROR] Conversation::setMode : Unhandled mode ('. $mode .').');
return false;
break;
return;
}
$this->mode = $mode;
}

View file

@ -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));
}
}

View file

@ -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;
}
}
}
}

View file

@ -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);

View file

@ -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;
}

View file

@ -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'];
}
}

View file

@ -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, '&amp;'))) {
$r->link = str_replace('&', '&amp;', $r->link);
}
$r->link = preg_replace('/\<link(.*?)\"\>/', '<link$1"/>', $r->link);
// XML does need a single element as root element so we add a dummy element here
$data = XML::parseString("<dummy>" . $r->link . "</dummy>");
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, '&amp;'))) {
$r->link = str_replace('&', '&amp;', $r->link);
}
$r->link = preg_replace('/\<link(.*?)\"\>/', '<link$1"/>', $r->link);
// XML does need a single element as root element so we add a dummy element here
$data = XML::parseString("<dummy>" . $r->link . "</dummy>");
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']];

View file

@ -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(

View file

@ -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
{

View file

@ -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'];

View file

@ -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()) {

View file

@ -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) {

View file

@ -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);
}

View file

@ -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';

View file

@ -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'])) {

View file

@ -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)
{

View file

@ -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;

View file

@ -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;
}

View file

@ -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;
},

View file

@ -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));
}
/**

View file

@ -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)
{

View file

@ -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.

View file

@ -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,

View file

@ -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]],

View file

@ -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;

View file

@ -0,0 +1,26 @@
<?php
// Copyright (C) 2010-2024, the Friendica project
// SPDX-FileCopyrightText: 2010-2024 the Friendica project
//
// SPDX-License-Identifier: AGPL-3.0-or-later
namespace Friendica\Test;
use Friendica\Core\Cache\Capability\ICanCacheInMemory;
use Friendica\Core\Lock\Capability\ICanLock;
abstract class CacheLockTestCase extends LockTestCase
{
abstract protected function getCache(): ICanCacheInMemory;
abstract protected function getInstance(): ICanLock;
/**
* Test if the getStats() result is identically to the getCacheStats()
*/
public function testGetStats()
{
self::assertSame(array_keys($this->getCache()->getStats()), array_keys($this->instance->getCacheStats()));
}
}

View file

@ -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'));
}
}

View file

@ -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'],
];

View file

@ -0,0 +1,75 @@
<?php
// Copyright (C) 2010-2024, the Friendica project
// SPDX-FileCopyrightText: 2010-2024 the Friendica project
//
// SPDX-License-Identifier: AGPL-3.0-or-later
declare(strict_types=1);
namespace Friendica\Test\Unit\Core\Logger\Factory;
use Exception;
use Friendica\Core\Config\Capability\IManageConfigValues;
use Friendica\Core\Logger\Capability\LogChannel;
use Friendica\Core\Logger\Factory\DelegatingLoggerFactory;
use Friendica\Core\Logger\Factory\LoggerFactory;
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
use Psr\Log\NullLogger;
class DelegatingLoggerFactoryTest extends TestCase
{
public function testCreateLoggerReturnsPsrLogger(): void
{
$config = $this->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)
);
}
}

View file

@ -1,36 +0,0 @@
<?php
// Copyright (C) 2010-2024, the Friendica project
// SPDX-FileCopyrightText: 2010-2024 the Friendica project
//
// SPDX-License-Identifier: AGPL-3.0-or-later
declare(strict_types=1);
namespace Friendica\Test\Unit\Core\Logger\Factory;
use Friendica\Core\Config\Capability\IManageConfigValues;
use Friendica\Core\Hooks\Capability\ICanCreateInstances;
use Friendica\Core\Logger\Capability\LogChannel;
use Friendica\Core\Logger\Factory\LegacyLoggerFactory;
use Friendica\Util\Profiler;
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
class LegacyLoggerFactoryTest extends TestCase
{
public function testCreateLoggerReturnsPsrLogger(): void
{
$factory = new LegacyLoggerFactory(
$this->createStub(ICanCreateInstances::class),
$this->createStub(IManageConfigValues::class),
$this->createStub(Profiler::class),
);
$this->assertInstanceOf(
LoggerInterface::class,
$factory->createLogger(LogLevel::DEBUG, LogChannel::DEFAULT)
);
}
}

View file

@ -0,0 +1,81 @@
<?php
// Copyright (C) 2010-2024, the Friendica project
// SPDX-FileCopyrightText: 2010-2024 the Friendica project
//
// SPDX-License-Identifier: AGPL-3.0-or-later
declare(strict_types=1);
namespace Friendica\Test\Unit\Core\Logger\Factory;
use Friendica\Core\Config\Capability\IManageConfigValues;
use Friendica\Core\Logger\Capability\IHaveCallIntrospections;
use Friendica\Core\Logger\Capability\LogChannel;
use Friendica\Core\Logger\Exception\LoggerArgumentException;
use Friendica\Core\Logger\Exception\LogLevelException;
use Friendica\Core\Logger\Factory\StreamLoggerFactory;
use Friendica\Core\Logger\Util\FileSystemUtil;
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
class StreamLoggerFactoryTest extends TestCase
{
public function testCreateLoggerReturnsPsrLogger(): 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->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);
}
}

View file

@ -0,0 +1,61 @@
<?php
// Copyright (C) 2010-2024, the Friendica project
// SPDX-FileCopyrightText: 2010-2024 the Friendica project
//
// SPDX-License-Identifier: AGPL-3.0-or-later
declare(strict_types=1);
namespace Friendica\Test\Unit\Core\Logger\Factory;
use Friendica\Core\Config\Capability\IManageConfigValues;
use Friendica\Core\Logger\Capability\IHaveCallIntrospections;
use Friendica\Core\Logger\Capability\LogChannel;
use Friendica\Core\Logger\Exception\LogLevelException;
use Friendica\Core\Logger\Factory\SyslogLoggerFactory;
use Friendica\Core\Logger\Type\SyslogLogger;
use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
class SyslogLoggerFactoryTest extends TestCase
{
public function testCreateLoggerReturnsPsrLogger(): 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->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);
}
}

View file

@ -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'],
];
}

View file

@ -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'],
];
}

View file

@ -0,0 +1,31 @@
<?php
// Copyright (C) 2010-2024, the Friendica project
// SPDX-FileCopyrightText: 2010-2024 the Friendica project
//
// SPDX-License-Identifier: AGPL-3.0-or-later
declare(strict_types=1);
namespace Friendica\Test\Util;
use Psr\EventDispatcher\EventDispatcherInterface;
/**
* Defines a dispatcher for events.
*/
final class FakeEventDispatcher implements EventDispatcherInterface
{
/**
* Provide all relevant listeners with an event to process.
*
* @template T of object
* @param T $event
*
* @return T The passed $event MUST be returned
*/
public function dispatch(object $event): object
{
return $event;
}
}

View file

@ -13,11 +13,11 @@ use Friendica\Model\Notification;
return [
'gserver' => [
[
'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,
],
],

View file

@ -17,7 +17,6 @@ return [
'temppath' => '/tmp/friendica.local',
'theme' => 'frio',
'url' => 'https://friendica.local',
'urlpath' => '',
'build' => 1508,
'maintenance' => false,
'dbupdate' => 1,

View file

@ -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()

View file

@ -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<ul>",
'assertHTML' => false
'assertHTML' => false,
],
'bug-7150' => [
'data' => html_entity_decode('http://example.com&nbsp;', 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' => '<span>dare to move your mouse here</span>',
'text' => '[nobb]<span onmouseover="alert(0)">dare to move your mouse here</span>[/nobb]'
'text' => '[nobb]<span onmouseover="alert(0)">dare to move your mouse here</span>[/nobb]',
],
'bug-9611-purify-xss-noparse' => [
'expectedHTML' => '<span>dare to move your mouse here</span>',
'text' => '[noparse]<span onmouseover="alert(0)">dare to move your mouse here</span>[/noparse]'
'text' => '[noparse]<span onmouseover="alert(0)">dare to move your mouse here</span>[/noparse]',
],
'bug-9611-purify-xss-attributes' => [
'expectedHTML' => '<span>dare to move your mouse here</span>',
'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' => '<span style="color:#FFFFFF;">dare to move your mouse here</span>',
'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' => '<span class="arbitrary classes">Test</span>',
@ -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: <bdi>@<a href="https://domain.tld/~remotecontact" class="userinfo mention" title="Remote contact">Remote contact</a></bdi>',
'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);
}
}

View file

@ -51,7 +51,45 @@ class MarkdownTest extends FixtureTestCase
return [
'bug-8358-double-decode' => [
'expectedBBCode' => 'with the <sup> and </sup> tag',
'markdown' => 'with the &lt;sup&gt; and &lt;/sup&gt; tag',
'markdown' => 'with the &lt;sup&gt; and &lt;/sup&gt; 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]',
],
];
}

View file

@ -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']);
}
}

View file

@ -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());
}
}

View file

@ -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']);
}
}

View file

@ -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']);
}
}

View file

@ -0,0 +1,56 @@
<?php
// Copyright (C) 2010-2024, the Friendica project
// SPDX-FileCopyrightText: 2010-2024 the Friendica project
//
// SPDX-License-Identifier: AGPL-3.0-or-later
namespace Core\Cache;
use Friendica\Core\Cache\Type\ArrayCache;
use Friendica\Core\Cache\Type\ProfilerCacheDecorator;
use Friendica\Core\Config\Capability\IManageConfigValues;
use Friendica\Test\MemoryCacheTestCase;
use Friendica\Util\Profiler;
class ProfilerCacheDecoratorTest extends MemoryCacheTestCase
{
protected function getInstance()
{
$config = \Mockery::mock(IManageConfigValues::class);
$config->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());
}
}

View file

@ -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']);
}
}

View file

@ -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;
}
}

View file

@ -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;
}
/**

View file

@ -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);
}

View file

@ -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;
}
/**

View file

@ -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;
}
/**

View file

@ -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;
}
}

View file

@ -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();
}

View file

@ -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);
}
}

View file

@ -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);

View file

@ -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()

View file

@ -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);
}
}

View file

@ -0,0 +1,203 @@
<?php
// Copyright (C) 2010-2024, the Friendica project
// SPDX-FileCopyrightText: 2010-2024 the Friendica project
//
// SPDX-License-Identifier: AGPL-3.0-or-later
namespace Friendica\Test\src\Module;
use Friendica\Capabilities\ICanCreateResponses;
use Friendica\Core\Cache\Capability\ICanCache;
use Friendica\Core\Cache\Type\ArrayCache;
use Friendica\Core\Cache\Type\DatabaseCache;
use Friendica\Core\Config\Capability\IManageConfigValues;
use Friendica\Core\Lock\Capability\ICanLock;
use Friendica\Core\Lock\Type\CacheLock;
use Friendica\Core\Lock\Type\DatabaseLock;
use Friendica\DI;
use Friendica\Module\Special\HTTPException;
use Friendica\Module\StatsCaching;
use Friendica\Test\FixtureTestCase;
use Mockery\MockInterface;
use phpmock\mockery\PHPMockery;
class StatsCachingTest extends FixtureTestCase
{
/** @var MockInterface|HTTPException */
protected $httpExceptionMock;
protected ICanCache $cache;
protected ICanLock $lock;
/** @var MockInterface|IManageConfigValues */
protected $config;
protected function setUp(): void
{
parent::setUp();
$this->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']);
}
}

Some files were not shown because too many files have changed in this diff Show more