diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml
index dad0aec9e1..9c8e20ee5f 100644
--- a/.github/workflows/code-quality.yml
+++ b/.github/workflows/code-quality.yml
@@ -75,3 +75,35 @@ jobs:
- name: Run PHPStan
run: composer run phpstan
+
+ phpmd:
+ name: PHPMD (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: none
+ 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 PHPMD
+ run: vendor/bin/phpmd src/ text .phpmd-ruleset.xml --color
diff --git a/.gitignore b/.gitignore
index bfc5094d9a..b60e96e8df 100644
--- a/.gitignore
+++ b/.gitignore
@@ -89,6 +89,7 @@ venv/
#Ignore cache files
.php_cs.cache
.php-cs-fixer.cache
+.phpmd.result-cache.php
#ignore avatar picture cache path
/avatar
diff --git a/.phpmd-ruleset.xml b/.phpmd-ruleset.xml
new file mode 100644
index 0000000000..3067bea712
--- /dev/null
+++ b/.phpmd-ruleset.xml
@@ -0,0 +1,26 @@
+
+
+
+ PHPMD ruleset for friendica code.
+
+
+
+ 3
+
+
+
+
+
+ 3
+
+
+
+
+
+
diff --git a/.phpmd-ruleset.xml.license b/.phpmd-ruleset.xml.license
new file mode 100644
index 0000000000..985c307f25
--- /dev/null
+++ b/.phpmd-ruleset.xml.license
@@ -0,0 +1,3 @@
+SPDX-FileCopyrightText: 2010-2024 the Friendica project
+
+SPDX-License-Identifier: CC0-1.0
diff --git a/.woodpecker/.phpmd_check.yml b/.woodpecker/.phpmd_check.yml
new file mode 100644
index 0000000000..f03e44eec9
--- /dev/null
+++ b/.woodpecker/.phpmd_check.yml
@@ -0,0 +1,46 @@
+# SPDX-FileCopyrightText: 2010 - 2024 the Friendica project
+#
+# SPDX-License-Identifier: CC0-1.0
+
+# The phpmd check is just triggered for PRs and pushes to non-stable branches of Friendica
+when:
+ branch:
+ exclude: [ stable ]
+ event: [ pull_request, push ]
+
+steps:
+ restore_cache:
+ image: meltwater/drone-cache:dev
+ settings:
+ backend: "filesystem"
+ restore: true
+ cache_key: "{{ .Repo.Name }}_php${PHP_MAJOR_VERSION}_{{ arch }}_{{ os }}"
+ archive_format: "gzip"
+ mount:
+ - '.composer'
+ volumes:
+ - /tmp/drone-cache:/tmp/cache
+
+ composer_install:
+ image: friendicaci/php8.3:php8.3.3
+ commands:
+ - mkdir addon # create empty addon folder to appease composer
+ - export COMPOSER_HOME=.composer
+ - ./bin/composer.phar install --prefer-dist
+
+ rebuild_cache:
+ image: meltwater/drone-cache:dev
+ settings:
+ backend: "filesystem"
+ rebuild: true
+ cache_key: "{{ .Repo.Name }}_php${PHP_MAJOR_VERSION}_{{ arch }}_{{ os }}"
+ archive_format: "gzip"
+ mount:
+ - '.composer'
+ volumes:
+ - /tmp/drone-cache:/tmp/cache
+
+ phpmd:
+ image: friendicaci/php8.3:php8.3.3
+ commands:
+ - ./bin/composer.phar run phpmd
diff --git a/composer.json b/composer.json
index 35cd757aec..a2c9eea3c9 100644
--- a/composer.json
+++ b/composer.json
@@ -70,7 +70,7 @@
"pragmarx/google2fa": "^5.0",
"pragmarx/recovery": "^0.2",
"psr/clock": "^1.0",
- "psr/container": "^2.0",
+ "psr/container": "^1.1|^2.0",
"psr/event-dispatcher": "^1.0",
"psr/log": "^1.1",
"seld/cli-prompt": "^1.0",
@@ -154,12 +154,14 @@
"mikey179/vfsstream": "^1.6",
"mockery/mockery": "^1.3",
"php-mock/php-mock-phpunit": "^2.10",
+ "phpmd/phpmd": "^2.15",
"phpstan/phpstan": "^2.0",
"phpunit/phpunit": "^9"
},
"scripts": {
"test": "phpunit",
"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",
"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",
diff --git a/composer.lock b/composer.lock
index 3086ed7be9..e12cc6533c 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
- "content-hash": "8ee8f9186d271b65b83c2ddbd12c5c03",
+ "content-hash": "b77bf714197f04022a5feb001bf07852",
"packages": [
{
"name": "asika/simple-console",
@@ -3139,6 +3139,9 @@
"psr",
"psr-6"
],
+ "support": {
+ "source": "https://github.com/php-fig/cache/tree/master"
+ },
"time": "2016-08-06T20:24:11+00:00"
},
{
@@ -3187,27 +3190,22 @@
},
{
"name": "psr/container",
- "version": "2.0.2",
+ "version": "1.1.2",
"source": {
"type": "git",
"url": "https://github.com/php-fig/container.git",
- "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963"
+ "reference": "513e0666f7216c7459170d56df27dfcefe1689ea"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963",
- "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963",
+ "url": "https://api.github.com/repos/php-fig/container/zipball/513e0666f7216c7459170d56df27dfcefe1689ea",
+ "reference": "513e0666f7216c7459170d56df27dfcefe1689ea",
"shasum": ""
},
"require": {
"php": ">=7.4.0"
},
"type": "library",
- "extra": {
- "branch-alias": {
- "dev-master": "2.0.x-dev"
- }
- },
"autoload": {
"psr-4": {
"Psr\\Container\\": "src/"
@@ -3232,7 +3230,11 @@
"container-interop",
"psr"
],
- "time": "2021-11-05T16:47:00+00:00"
+ "support": {
+ "issues": "https://github.com/php-fig/container/issues",
+ "source": "https://github.com/php-fig/container/tree/1.1.2"
+ },
+ "time": "2021-11-05T16:50:12+00:00"
},
{
"name": "psr/event-dispatcher",
@@ -3480,6 +3482,9 @@
"psr",
"psr-3"
],
+ "support": {
+ "source": "https://github.com/php-fig/log/tree/1.1.4"
+ },
"time": "2021-05-03T11:20:27+00:00"
},
{
@@ -3697,16 +3702,16 @@
},
{
"name": "symfony/deprecation-contracts",
- "version": "v2.5.2",
+ "version": "v2.5.4",
"source": {
"type": "git",
"url": "https://github.com/symfony/deprecation-contracts.git",
- "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66"
+ "reference": "605389f2a7e5625f273b53960dc46aeaf9c62918"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/e8b495ea28c1d97b5e0c121748d6f9b53d075c66",
- "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66",
+ "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/605389f2a7e5625f273b53960dc46aeaf9c62918",
+ "reference": "605389f2a7e5625f273b53960dc46aeaf9c62918",
"shasum": ""
},
"require": {
@@ -3714,12 +3719,12 @@
},
"type": "library",
"extra": {
+ "thanks": {
+ "url": "https://github.com/symfony/contracts",
+ "name": "symfony/contracts"
+ },
"branch-alias": {
"dev-main": "2.5-dev"
- },
- "thanks": {
- "name": "symfony/contracts",
- "url": "https://github.com/symfony/contracts"
}
},
"autoload": {
@@ -3743,6 +3748,9 @@
],
"description": "A generic function and convention to trigger deprecation notices",
"homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.4"
+ },
"funding": [
{
"url": "https://symfony.com/sponsor",
@@ -3757,7 +3765,7 @@
"type": "tidelift"
}
],
- "time": "2022-01-02T09:53:40+00:00"
+ "time": "2024-09-25T14:11:13+00:00"
},
{
"name": "symfony/event-dispatcher",
@@ -4591,6 +4599,151 @@
}
],
"packages-dev": [
+ {
+ "name": "composer/pcre",
+ "version": "3.3.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/composer/pcre.git",
+ "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/composer/pcre/zipball/b2bed4734f0cc156ee1fe9c0da2550420d99a21e",
+ "reference": "b2bed4734f0cc156ee1fe9c0da2550420d99a21e",
+ "shasum": ""
+ },
+ "require": {
+ "php": "^7.4 || ^8.0"
+ },
+ "conflict": {
+ "phpstan/phpstan": "<1.11.10"
+ },
+ "require-dev": {
+ "phpstan/phpstan": "^1.12 || ^2",
+ "phpstan/phpstan-strict-rules": "^1 || ^2",
+ "phpunit/phpunit": "^8 || ^9"
+ },
+ "type": "library",
+ "extra": {
+ "phpstan": {
+ "includes": [
+ "extension.neon"
+ ]
+ },
+ "branch-alias": {
+ "dev-main": "3.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Composer\\Pcre\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Jordi Boggiano",
+ "email": "j.boggiano@seld.be",
+ "homepage": "http://seld.be"
+ }
+ ],
+ "description": "PCRE wrapping library that offers type-safe preg_* replacements.",
+ "keywords": [
+ "PCRE",
+ "preg",
+ "regex",
+ "regular expression"
+ ],
+ "support": {
+ "issues": "https://github.com/composer/pcre/issues",
+ "source": "https://github.com/composer/pcre/tree/3.3.2"
+ },
+ "funding": [
+ {
+ "url": "https://packagist.com",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/composer",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/composer/composer",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-11-12T16:29:46+00:00"
+ },
+ {
+ "name": "composer/xdebug-handler",
+ "version": "3.0.5",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/composer/xdebug-handler.git",
+ "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/6c1925561632e83d60a44492e0b344cf48ab85ef",
+ "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef",
+ "shasum": ""
+ },
+ "require": {
+ "composer/pcre": "^1 || ^2 || ^3",
+ "php": "^7.2.5 || ^8.0",
+ "psr/log": "^1 || ^2 || ^3"
+ },
+ "require-dev": {
+ "phpstan/phpstan": "^1.0",
+ "phpstan/phpstan-strict-rules": "^1.1",
+ "phpunit/phpunit": "^8.5 || ^9.6 || ^10.5"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Composer\\XdebugHandler\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "John Stevenson",
+ "email": "john-stevenson@blueyonder.co.uk"
+ }
+ ],
+ "description": "Restarts a process without Xdebug.",
+ "keywords": [
+ "Xdebug",
+ "performance"
+ ],
+ "support": {
+ "irc": "ircs://irc.libera.chat:6697/composer",
+ "issues": "https://github.com/composer/xdebug-handler/issues",
+ "source": "https://github.com/composer/xdebug-handler/tree/3.0.5"
+ },
+ "funding": [
+ {
+ "url": "https://packagist.com",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/composer",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/composer/composer",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-05-06T16:37:16+00:00"
+ },
{
"name": "dms/phpunit-arraysubset-asserts",
"version": "v0.3.1",
@@ -4976,6 +5129,69 @@
],
"time": "2024-03-05T20:51:40+00:00"
},
+ {
+ "name": "pdepend/pdepend",
+ "version": "2.16.2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/pdepend/pdepend.git",
+ "reference": "f942b208dc2a0868454d01b29f0c75bbcfc6ed58"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/pdepend/pdepend/zipball/f942b208dc2a0868454d01b29f0c75bbcfc6ed58",
+ "reference": "f942b208dc2a0868454d01b29f0c75bbcfc6ed58",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=5.3.7",
+ "symfony/config": "^2.3.0|^3|^4|^5|^6.0|^7.0",
+ "symfony/dependency-injection": "^2.3.0|^3|^4|^5|^6.0|^7.0",
+ "symfony/filesystem": "^2.3.0|^3|^4|^5|^6.0|^7.0",
+ "symfony/polyfill-mbstring": "^1.19"
+ },
+ "require-dev": {
+ "easy-doc/easy-doc": "0.0.0|^1.2.3",
+ "gregwar/rst": "^1.0",
+ "squizlabs/php_codesniffer": "^2.0.0"
+ },
+ "bin": [
+ "src/bin/pdepend"
+ ],
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "PDepend\\": "src/main/php/PDepend"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "description": "Official version of pdepend to be handled with Composer",
+ "keywords": [
+ "PHP Depend",
+ "PHP_Depend",
+ "dev",
+ "pdepend"
+ ],
+ "support": {
+ "issues": "https://github.com/pdepend/pdepend/issues",
+ "source": "https://github.com/pdepend/pdepend/tree/2.16.2"
+ },
+ "funding": [
+ {
+ "url": "https://tidelift.com/funding/github/packagist/pdepend/pdepend",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2023-12-17T18:09:59+00:00"
+ },
{
"name": "phar-io/manifest",
"version": "2.0.4",
@@ -5293,6 +5509,89 @@
],
"time": "2024-02-11T07:24:16+00:00"
},
+ {
+ "name": "phpmd/phpmd",
+ "version": "2.15.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/phpmd/phpmd.git",
+ "reference": "74a1f56e33afad4128b886e334093e98e1b5e7c0"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/phpmd/phpmd/zipball/74a1f56e33afad4128b886e334093e98e1b5e7c0",
+ "reference": "74a1f56e33afad4128b886e334093e98e1b5e7c0",
+ "shasum": ""
+ },
+ "require": {
+ "composer/xdebug-handler": "^1.0 || ^2.0 || ^3.0",
+ "ext-xml": "*",
+ "pdepend/pdepend": "^2.16.1",
+ "php": ">=5.3.9"
+ },
+ "require-dev": {
+ "easy-doc/easy-doc": "0.0.0 || ^1.3.2",
+ "ext-json": "*",
+ "ext-simplexml": "*",
+ "gregwar/rst": "^1.0",
+ "mikey179/vfsstream": "^1.6.8",
+ "squizlabs/php_codesniffer": "^2.9.2 || ^3.7.2"
+ },
+ "bin": [
+ "src/bin/phpmd"
+ ],
+ "type": "library",
+ "autoload": {
+ "psr-0": {
+ "PHPMD\\": "src/main/php"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "BSD-3-Clause"
+ ],
+ "authors": [
+ {
+ "name": "Manuel Pichler",
+ "email": "github@manuel-pichler.de",
+ "homepage": "https://github.com/manuelpichler",
+ "role": "Project Founder"
+ },
+ {
+ "name": "Marc Würth",
+ "email": "ravage@bluewin.ch",
+ "homepage": "https://github.com/ravage84",
+ "role": "Project Maintainer"
+ },
+ {
+ "name": "Other contributors",
+ "homepage": "https://github.com/phpmd/phpmd/graphs/contributors",
+ "role": "Contributors"
+ }
+ ],
+ "description": "PHPMD is a spin-off project of PHP Depend and aims to be a PHP equivalent of the well known Java tool PMD.",
+ "homepage": "https://phpmd.org/",
+ "keywords": [
+ "dev",
+ "mess detection",
+ "mess detector",
+ "pdepend",
+ "phpmd",
+ "pmd"
+ ],
+ "support": {
+ "irc": "irc://irc.freenode.org/phpmd",
+ "issues": "https://github.com/phpmd/phpmd/issues",
+ "source": "https://github.com/phpmd/phpmd/tree/2.15.0"
+ },
+ "funding": [
+ {
+ "url": "https://tidelift.com/funding/github/packagist/phpmd/phpmd",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2023-12-11T08:22:20+00:00"
+ },
{
"name": "phpstan/phpstan",
"version": "2.0.1",
@@ -6647,6 +6946,559 @@
],
"time": "2020-09-28T06:39:44+00:00"
},
+ {
+ "name": "symfony/config",
+ "version": "v5.4.46",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/config.git",
+ "reference": "977c88a02d7d3f16904a81907531b19666a08e78"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/config/zipball/977c88a02d7d3f16904a81907531b19666a08e78",
+ "reference": "977c88a02d7d3f16904a81907531b19666a08e78",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2.5",
+ "symfony/deprecation-contracts": "^2.1|^3",
+ "symfony/filesystem": "^4.4|^5.0|^6.0",
+ "symfony/polyfill-ctype": "~1.8",
+ "symfony/polyfill-php80": "^1.16",
+ "symfony/polyfill-php81": "^1.22"
+ },
+ "conflict": {
+ "symfony/finder": "<4.4"
+ },
+ "require-dev": {
+ "symfony/event-dispatcher": "^4.4|^5.0|^6.0",
+ "symfony/finder": "^4.4|^5.0|^6.0",
+ "symfony/messenger": "^4.4|^5.0|^6.0",
+ "symfony/service-contracts": "^1.1|^2|^3",
+ "symfony/yaml": "^4.4|^5.0|^6.0"
+ },
+ "suggest": {
+ "symfony/yaml": "To use the yaml reference dumper"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Config\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Helps you find, load, combine, autofill and validate configuration values of any kind",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/config/tree/v5.4.46"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-10-30T07:58:02+00:00"
+ },
+ {
+ "name": "symfony/dependency-injection",
+ "version": "v5.4.48",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/dependency-injection.git",
+ "reference": "e5ca16dee39ef7d63e552ff0bf0a2526a1142c92"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/e5ca16dee39ef7d63e552ff0bf0a2526a1142c92",
+ "reference": "e5ca16dee39ef7d63e552ff0bf0a2526a1142c92",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2.5",
+ "psr/container": "^1.1.1",
+ "symfony/deprecation-contracts": "^2.1|^3",
+ "symfony/polyfill-php80": "^1.16",
+ "symfony/polyfill-php81": "^1.22",
+ "symfony/service-contracts": "^1.1.6|^2"
+ },
+ "conflict": {
+ "ext-psr": "<1.1|>=2",
+ "symfony/config": "<5.3",
+ "symfony/finder": "<4.4",
+ "symfony/proxy-manager-bridge": "<4.4",
+ "symfony/yaml": "<4.4.26"
+ },
+ "provide": {
+ "psr/container-implementation": "1.0",
+ "symfony/service-implementation": "1.0|2.0"
+ },
+ "require-dev": {
+ "symfony/config": "^5.3|^6.0",
+ "symfony/expression-language": "^4.4|^5.0|^6.0",
+ "symfony/yaml": "^4.4.26|^5.0|^6.0"
+ },
+ "suggest": {
+ "symfony/config": "",
+ "symfony/expression-language": "For using expressions in service container configuration",
+ "symfony/finder": "For using double-star glob patterns or when GLOB_BRACE portability is required",
+ "symfony/proxy-manager-bridge": "Generate service proxies to lazy load them",
+ "symfony/yaml": ""
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\DependencyInjection\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Allows you to standardize and centralize the way objects are constructed in your application",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/dependency-injection/tree/v5.4.48"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-11-20T10:51:57+00:00"
+ },
+ {
+ "name": "symfony/filesystem",
+ "version": "v5.4.45",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/filesystem.git",
+ "reference": "57c8294ed37d4a055b77057827c67f9558c95c54"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/filesystem/zipball/57c8294ed37d4a055b77057827c67f9558c95c54",
+ "reference": "57c8294ed37d4a055b77057827c67f9558c95c54",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2.5",
+ "symfony/polyfill-ctype": "~1.8",
+ "symfony/polyfill-mbstring": "~1.8",
+ "symfony/polyfill-php80": "^1.16"
+ },
+ "require-dev": {
+ "symfony/process": "^5.4|^6.4"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Component\\Filesystem\\": ""
+ },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Provides basic utilities for the filesystem",
+ "homepage": "https://symfony.com",
+ "support": {
+ "source": "https://github.com/symfony/filesystem/tree/v5.4.45"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-10-22T13:05:35+00:00"
+ },
+ {
+ "name": "symfony/polyfill-ctype",
+ "version": "v1.31.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-ctype.git",
+ "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/a3cc8b044a6ea513310cbd48ef7333b384945638",
+ "reference": "a3cc8b044a6ea513310cbd48ef7333b384945638",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2"
+ },
+ "provide": {
+ "ext-ctype": "*"
+ },
+ "suggest": {
+ "ext-ctype": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "url": "https://github.com/symfony/polyfill",
+ "name": "symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Polyfill\\Ctype\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Gert de Pagter",
+ "email": "BackEndTea@gmail.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill for ctype functions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "ctype",
+ "polyfill",
+ "portable"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-ctype/tree/v1.31.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-09-09T11:45:10+00:00"
+ },
+ {
+ "name": "symfony/polyfill-mbstring",
+ "version": "v1.31.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-mbstring.git",
+ "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/85181ba99b2345b0ef10ce42ecac37612d9fd341",
+ "reference": "85181ba99b2345b0ef10ce42ecac37612d9fd341",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2"
+ },
+ "provide": {
+ "ext-mbstring": "*"
+ },
+ "suggest": {
+ "ext-mbstring": "For best performance"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "url": "https://github.com/symfony/polyfill",
+ "name": "symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Polyfill\\Mbstring\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill for the Mbstring extension",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "mbstring",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.31.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-09-09T11:45:10+00:00"
+ },
+ {
+ "name": "symfony/polyfill-php81",
+ "version": "v1.31.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/polyfill-php81.git",
+ "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c",
+ "reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2"
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "url": "https://github.com/symfony/polyfill",
+ "name": "symfony/polyfill"
+ }
+ },
+ "autoload": {
+ "files": [
+ "bootstrap.php"
+ ],
+ "psr-4": {
+ "Symfony\\Polyfill\\Php81\\": ""
+ },
+ "classmap": [
+ "Resources/stubs"
+ ]
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "compatibility",
+ "polyfill",
+ "portable",
+ "shim"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/polyfill-php81/tree/v1.31.0"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-09-09T11:45:10+00:00"
+ },
+ {
+ "name": "symfony/service-contracts",
+ "version": "v2.5.4",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/symfony/service-contracts.git",
+ "reference": "f37b419f7aea2e9abf10abd261832cace12e3300"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f37b419f7aea2e9abf10abd261832cace12e3300",
+ "reference": "f37b419f7aea2e9abf10abd261832cace12e3300",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2.5",
+ "psr/container": "^1.1",
+ "symfony/deprecation-contracts": "^2.1|^3"
+ },
+ "conflict": {
+ "ext-psr": "<1.1|>=2"
+ },
+ "suggest": {
+ "symfony/service-implementation": ""
+ },
+ "type": "library",
+ "extra": {
+ "thanks": {
+ "url": "https://github.com/symfony/contracts",
+ "name": "symfony/contracts"
+ },
+ "branch-alias": {
+ "dev-main": "2.5-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Symfony\\Contracts\\Service\\": ""
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "description": "Generic abstractions related to writing services",
+ "homepage": "https://symfony.com",
+ "keywords": [
+ "abstractions",
+ "contracts",
+ "decoupling",
+ "interfaces",
+ "interoperability",
+ "standards"
+ ],
+ "support": {
+ "source": "https://github.com/symfony/service-contracts/tree/v2.5.4"
+ },
+ "funding": [
+ {
+ "url": "https://symfony.com/sponsor",
+ "type": "custom"
+ },
+ {
+ "url": "https://github.com/fabpot",
+ "type": "github"
+ },
+ {
+ "url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
+ "type": "tidelift"
+ }
+ ],
+ "time": "2024-09-25T14:11:13+00:00"
+ },
{
"name": "theseer/tokenizer",
"version": "1.2.3",
diff --git a/src/Model/APContact.php b/src/Model/APContact.php
index a364344a51..9c20b5b283 100644
--- a/src/Model/APContact.php
+++ b/src/Model/APContact.php
@@ -162,6 +162,8 @@ class APContact
DI::cache()->set($cachekey, System::callstack(20), Duration::FIVE_MINUTES);
}
+ $local_owner = [];
+
if (DI::baseUrl()->isLocalUrl($url) && ($local_uid = User::getIdForURL($url))) {
try {
$data = Transmitter::getProfile($local_uid);
@@ -205,6 +207,15 @@ class APContact
return $fetched_contact;
}
+ return self::compactProfile($apcontact, $compacted, $url, $fetched_contact, $webfinger, $local_owner);
+ }
+
+ /**
+ * @param array|bool $fetched_contact
+ * @param array|bool $local_owner
+ */
+ private static function compactProfile(array $apcontact, array $compacted, string $url, $fetched_contact, bool $webfinger, $local_owner): array
+ {
$apcontact['url'] = $compacted['@id'];
$apcontact['uuid'] = JsonLD::fetchElement($compacted, 'diaspora:guid', '@value');
$apcontact['type'] = str_replace('as:', '', JsonLD::fetchElement($compacted, '@type'));
diff --git a/src/Model/GServer.php b/src/Model/GServer.php
index 20f79f8de7..efbe706844 100644
--- a/src/Model/GServer.php
+++ b/src/Model/GServer.php
@@ -817,6 +817,25 @@ class GServer
}
// Count the number of known contacts from this server
+ self::countNumberOfKnownContacts((int) $id, $serverdata);
+
+ if (in_array($serverdata['network'], [Protocol::DFRN, Protocol::DIASPORA])) {
+ self::discoverRelay($url);
+ }
+
+ if (!empty($systemactor)) {
+ $contact = Contact::getByURL($systemactor, true, ['gsid', 'baseurl', 'id', 'network', 'url', 'name']);
+ DI::logger()->debug('Fetched system actor', ['url' => $url, 'gsid' => $id, 'contact' => $contact]);
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Count the number of known contacts from this server
+ */
+ private static function countNumberOfKnownContacts(int $id, array $serverdata): void
+ {
if (!empty($id) && !in_array($serverdata['network'], [Protocol::PHANTOM, Protocol::FEED])) {
$apcontacts = DBA::count('apcontact', ['gsid' => $id]);
$contacts = DBA::count('contact', ['uid' => 0, 'gsid' => $id, 'failed' => false]);
@@ -842,17 +861,6 @@ class GServer
}
}
}
-
- if (in_array($serverdata['network'], [Protocol::DFRN, Protocol::DIASPORA])) {
- self::discoverRelay($url);
- }
-
- if (!empty($systemactor)) {
- $contact = Contact::getByURL($systemactor, true, ['gsid', 'baseurl', 'id', 'network', 'url', 'name']);
- DI::logger()->debug('Fetched system actor', ['url' => $url, 'gsid' => $id, 'contact' => $contact]);
- }
-
- return $ret;
}
/**
diff --git a/src/Model/Item.php b/src/Model/Item.php
index 00435e2d4d..53184bb1d7 100644
--- a/src/Model/Item.php
+++ b/src/Model/Item.php
@@ -580,52 +580,6 @@ class Item
}
}
- /**
- * Check if the item array is a duplicate
- *
- * @param array $item Item record
- * @return boolean is it a duplicate?
- */
- private static function isDuplicate(array $item): bool
- {
- // Checking if there is already an item with the same guid
- $condition = ['guid' => $item['guid'], 'network' => $item['network'], 'uid' => $item['uid']];
- if (Post::exists($condition)) {
- DI::logger()->notice('Found already existing item', $condition);
- return true;
- }
-
- $condition = [
- 'uri-id' => $item['uri-id'], 'uid' => $item['uid'],
- 'network' => [$item['network'], Protocol::DFRN]
- ];
- if (Post::exists($condition)) {
- DI::logger()->notice('duplicated item with the same uri found.', $condition);
- return true;
- }
-
- // On Friendica and Diaspora the GUID is unique
- if (in_array($item['network'], [Protocol::DFRN, Protocol::DIASPORA])) {
- $condition = ['guid' => $item['guid'], 'uid' => $item['uid']];
- if (Post::exists($condition)) {
- DI::logger()->notice('duplicated item with the same guid found.', $condition);
- return true;
- }
- }
-
- /*
- * Check for already added items.
- * There is a timing issue here that sometimes creates double postings.
- * An unique index would help - but the limitations of MySQL (maximum size of index values) prevent this.
- */
- if (($item['uid'] == 0) && Post::exists(['uri-id' => $item['uri-id'], 'uid' => 0])) {
- DI::logger()->notice('Global item already stored.', ['uri-id' => $item['uri-id'], 'network' => $item['network']]);
- return true;
- }
-
- return false;
- }
-
/**
* Check if the item array is valid
*
@@ -694,42 +648,6 @@ class Item
return true;
}
- /**
- * Return the id of the given item array if it has been stored before
- *
- * @param array $item Item record
- * @return integer Item id or zero on error
- */
- private static function getDuplicateID(array $item): int
- {
- if (empty($item['network']) || in_array($item['network'], Protocol::FEDERATED)) {
- $condition = [
- '`uri-id` = ? AND `uid` = ? AND `network` IN (?, ?, ?)',
- $item['uri-id'],
- $item['uid'],
- Protocol::ACTIVITYPUB,
- Protocol::DIASPORA,
- Protocol::DFRN
- ];
- $existing = Post::selectFirst(['id', 'network'], $condition);
- if (DBA::isResult($existing)) {
- // We only log the entries with a different user id than 0. Otherwise we would have too many false positives
- if ($item['uid'] != 0) {
- DI::logger()->notice('Item already existed for user', [
- 'uri-id' => $item['uri-id'],
- 'uid' => $item['uid'],
- 'network' => $item['network'],
- 'existing_id' => $existing['id'],
- 'existing_network' => $existing['network']
- ]);
- }
-
- return $existing['id'];
- }
- }
- return 0;
- }
-
/**
* Fetch the uri-id of the parent for the given uri-id
*
@@ -750,106 +668,6 @@ class Item
return self::getParent($thread_parent['thr-parent-id']);
}
- /**
- * Fetch top-level parent data for the given item array
- *
- * @param array $item
- * @return array item array with parent data
- * @throws \Exception
- */
- private static function getTopLevelParent(array $item): array
- {
- $fields = [
- 'uid', 'uri', 'parent-uri', 'id', 'deleted',
- 'uri-id', 'parent-uri-id', 'restrictions', 'verb',
- 'allow_cid', 'allow_gid', 'deny_cid', 'deny_gid',
- 'wall', 'private', 'origin', 'author-id'
- ];
- $condition = ['uri-id' => [$item['thr-parent-id'], $item['parent-uri-id']], 'uid' => $item['uid']];
- $params = ['order' => ['id' => false]];
- $parent = Post::selectFirst($fields, $condition, $params);
-
- if (!DBA::isResult($parent) && Post::exists(['uri-id' => [$item['thr-parent-id'], $item['parent-uri-id']], 'uid' => 0])) {
- $stored = Item::storeForUserByUriId($item['thr-parent-id'], $item['uid'], ['post-reason' => Item::PR_COMPLETION]);
- if (!$stored && ($item['thr-parent-id'] != $item['parent-uri-id'])) {
- $stored = Item::storeForUserByUriId($item['parent-uri-id'], $item['uid'], ['post-reason' => Item::PR_COMPLETION]);
- }
- if ($stored) {
- DI::logger()->info('Stored thread parent item for user', ['uri-id' => $item['thr-parent-id'], 'uid' => $item['uid'], 'stored' => $stored]);
- $parent = Post::selectFirst($fields, $condition, $params);
- }
- }
-
- if (!DBA::isResult($parent)) {
- DI::logger()->notice('item parent was not found - ignoring item', ['uri-id' => $item['uri-id'], 'thr-parent-id' => $item['thr-parent-id'], 'uid' => $item['uid']]);
- return [];
- }
-
- if (self::hasRestrictions($item, $parent['author-id'], $parent['restrictions'])) {
- DI::logger()->notice('Restrictions apply - ignoring item', ['restrictions' => $parent['restrictions'], 'verb' => $parent['verb'], 'uri-id' => $item['uri-id'], 'thr-parent-id' => $item['thr-parent-id'], 'uid' => $item['uid']]);
- return [];
- }
-
- if ($parent['uri-id'] == $parent['parent-uri-id']) {
- return $parent;
- }
-
- $condition = [
- 'uri-id' => $parent['parent-uri-id'],
- 'parent-uri-id' => $parent['parent-uri-id'],
- 'uid' => $parent['uid']
- ];
- $params = ['order' => ['id' => false]];
- $toplevel_parent = Post::selectFirst($fields, $condition, $params);
-
- if (!DBA::isResult($toplevel_parent) && $item['origin']) {
- $stored = Item::storeForUserByUriId($item['parent-uri-id'], $item['uid'], ['post-reason' => Item::PR_COMPLETION]);
- DI::logger()->info('Stored parent item for user', ['uri-id' => $item['parent-uri-id'], 'uid' => $item['uid'], 'stored' => $stored]);
- $toplevel_parent = Post::selectFirst($fields, $condition, $params);
- }
-
- if (!DBA::isResult($toplevel_parent)) {
- DI::logger()->notice('item top level parent was not found - ignoring item', ['parent-uri-id' => $parent['parent-uri-id'], 'uid' => $parent['uid']]);
- return [];
- }
-
- return $toplevel_parent;
- }
-
- /**
- * Get the gravity for the given item array
- *
- * @param array $item
- * @return integer gravity
- */
- private static function getGravity(array $item): int
- {
- $activity = DI::activity();
-
- if (isset($item['gravity'])) {
- return intval($item['gravity']);
- } elseif ($item['parent-uri-id'] === $item['uri-id']) {
- return self::GRAVITY_PARENT;
- } elseif ($activity->match($item['verb'], Activity::POST)) {
- return self::GRAVITY_COMMENT;
- } elseif ($activity->match($item['verb'], Activity::FOLLOW)) {
- return self::GRAVITY_ACTIVITY;
- } elseif ($activity->match($item['verb'], Activity::ANNOUNCE)) {
- return self::GRAVITY_ACTIVITY;
- }
-
- DI::logger()->info('Unknown gravity for verb', ['verb' => $item['verb']]);
- return self::GRAVITY_UNKNOWN; // Should not happen
- }
-
- private static function prepareOriginPost(array $item): array
- {
- $item = DI::contentItem()->initializePost($item);
- $item = DI::contentItem()->finalizePost($item, false);
-
- return $item;
- }
-
/**
* Inserts item record
*
@@ -860,6 +678,14 @@ class Item
*/
public static function insert(array $item, int $notify = 0, bool $post_local = true): int
{
+ $itemHelper = new ItemHelper(
+ DI::contentItem(),
+ DI::activity(),
+ DI::logger(),
+ DI::dba(),
+ DI::baseUrl(),
+ );
+
$orig_item = $item;
$priority = Worker::PRIORITY_HIGH;
@@ -868,7 +694,7 @@ class Item
// If it is a posting where users should get notifications, then define it as wall posting
if ($notify) {
- $item = self::prepareOriginPost($item);
+ $item = $itemHelper->prepareOriginPost($item);
if (is_int($notify) && in_array($notify, Worker::PRIORITIES)) {
$priority = $notify;
@@ -881,23 +707,7 @@ class Item
$item['network'] = trim(($item['network'] ?? '') ?: Protocol::PHANTOM);
}
- $uid = intval($item['uid']);
-
- $item['guid'] = self::guid($item, $notify);
- $item['uri'] = substr(trim($item['uri'] ?? '') ?: self::newURI($item['guid']), 0, 255);
-
- // Store URI data
- $item['uri-id'] = ItemURI::insert(['uri' => $item['uri'], 'guid' => $item['guid']]);
-
- // Backward compatibility: parent-uri used to be the direct parent uri.
- // If it is provided without a thr-parent, it probably is the old behavior.
- if (empty($item['thr-parent']) || empty($item['parent-uri'])) {
- $item['thr-parent'] = trim($item['thr-parent'] ?? $item['parent-uri'] ?? $item['uri']);
- $item['parent-uri'] = $item['thr-parent'];
- }
-
- $item['thr-parent-id'] = ItemURI::getIdByURI($item['thr-parent']);
- $item['parent-uri-id'] = ItemURI::getIdByURI($item['parent-uri']);
+ $item = $itemHelper->prepareItemData($item, (bool) $notify);
// Store conversation data
$source = $item['source'] ?? '';
@@ -910,14 +720,14 @@ class Item
* We have to check several networks since Friendica posts could be repeated
* via Diaspora.
*/
- $duplicate = self::getDuplicateID($item);
+ $duplicate = $itemHelper->getDuplicateID($item);
if ($duplicate) {
return $duplicate;
}
// Additional duplicate checks
/// @todo Check why the first duplication check returns the item number and the second a 0
- if (self::isDuplicate($item)) {
+ if ($itemHelper->isDuplicate($item)) {
return 0;
}
@@ -927,44 +737,7 @@ class Item
$defined_permissions = isset($item['allow_cid']) && isset($item['allow_gid']) && isset($item['deny_cid']) && isset($item['deny_gid']) && isset($item['private']);
- $item['wall'] = intval($item['wall'] ?? 0);
- $item['extid'] = trim($item['extid'] ?? '');
- $item['author-name'] = trim($item['author-name'] ?? '');
- $item['author-link'] = trim($item['author-link'] ?? '');
- $item['author-avatar'] = trim($item['author-avatar'] ?? '');
- $item['owner-name'] = trim($item['owner-name'] ?? '');
- $item['owner-link'] = trim($item['owner-link'] ?? '');
- $item['owner-avatar'] = trim($item['owner-avatar'] ?? '');
- $item['received'] = (isset($item['received']) ? DateTimeFormat::utc($item['received']) : DateTimeFormat::utcNow());
- $item['created'] = (isset($item['created']) ? DateTimeFormat::utc($item['created']) : $item['received']);
- $item['edited'] = (isset($item['edited']) ? DateTimeFormat::utc($item['edited']) : $item['created']);
- $item['changed'] = (isset($item['changed']) ? DateTimeFormat::utc($item['changed']) : $item['created']);
- $item['commented'] = (isset($item['commented']) ? DateTimeFormat::utc($item['commented']) : $item['created']);
- $item['title'] = substr(trim($item['title'] ?? ''), 0, 255);
- $item['location'] = trim($item['location'] ?? '');
- $item['coord'] = trim($item['coord'] ?? '');
- $item['visible'] = (isset($item['visible']) ? intval($item['visible']) : 1);
- $item['deleted'] = 0;
- $item['verb'] = trim($item['verb'] ?? '');
- $item['object-type'] = trim($item['object-type'] ?? '');
- $item['object'] = trim($item['object'] ?? '');
- $item['target-type'] = trim($item['target-type'] ?? '');
- $item['target'] = trim($item['target'] ?? '');
- $item['plink'] = substr(trim($item['plink'] ?? ''), 0, 255);
- $item['allow_cid'] = trim($item['allow_cid'] ?? '');
- $item['allow_gid'] = trim($item['allow_gid'] ?? '');
- $item['deny_cid'] = trim($item['deny_cid'] ?? '');
- $item['deny_gid'] = trim($item['deny_gid'] ?? '');
- $item['private'] = intval($item['private'] ?? self::PUBLIC);
- $item['body'] = trim($item['body'] ?? '');
- $item['raw-body'] = trim($item['raw-body'] ?? $item['body']);
- $item['app'] = trim($item['app'] ?? '');
- $item['origin'] = intval($item['origin'] ?? 0);
- $item['postopts'] = trim($item['postopts'] ?? '');
- $item['resource-id'] = trim($item['resource-id'] ?? '');
- $item['event-id'] = intval($item['event-id'] ?? 0);
- $item['inform'] = trim($item['inform'] ?? '');
- $item['file'] = trim($item['file'] ?? '');
+ $uid = intval($item['uid']);
// Communities aren't working with the Diaspora protocol
if (($uid != 0) && ($item['network'] == Protocol::DIASPORA)) {
@@ -975,33 +748,7 @@ class Item
}
}
- // Items cannot be stored before they happen ...
- if ($item['created'] > DateTimeFormat::utcNow()) {
- $item['created'] = DateTimeFormat::utcNow();
- }
-
- // We haven't invented time travel by now.
- if ($item['edited'] > DateTimeFormat::utcNow()) {
- $item['edited'] = DateTimeFormat::utcNow();
- }
-
- $item['plink'] = ($item['plink'] ?? '') ?: DI::baseUrl() . '/display/' . urlencode($item['guid']);
-
- $item['gravity'] = self::getGravity($item);
-
- $default = [
- 'url' => $item['author-link'], 'name' => $item['author-name'],
- 'photo' => $item['author-avatar'], 'network' => $item['network']
- ];
- $item['author-id'] = ($item['author-id'] ?? 0) ?: Contact::getIdForURL($item['author-link'], 0, null, $default);
-
- $default = [
- 'url' => $item['owner-link'], 'name' => $item['owner-name'],
- 'photo' => $item['owner-avatar'], 'network' => $item['network']
- ];
- $item['owner-id'] = ($item['owner-id'] ?? 0) ?: Contact::getIdForURL($item['owner-link'], 0, null, $default);
-
- $item['post-reason'] = self::getPostReason($item);
+ $item = $itemHelper->validateItemData($item);
// Ensure that there is an avatar cache
Contact::checkAvatarCache($item['author-id']);
@@ -1022,55 +769,14 @@ class Item
}
if ($item['gravity'] !== self::GRAVITY_PARENT) {
- $toplevel_parent = self::getTopLevelParent($item);
+ $toplevel_parent = $itemHelper->getTopLevelParent($item);
if (empty($toplevel_parent)) {
return 0;
}
- $parent_id = $toplevel_parent['id'];
- $item['parent-uri'] = $toplevel_parent['uri'];
- $item['parent-uri-id'] = $toplevel_parent['uri-id'];
- $item['deleted'] = $toplevel_parent['deleted'];
- $item['wall'] = $toplevel_parent['wall'];
-
- // Reshares have to keep their permissions to allow groups to work
- if (!$defined_permissions && (!$item['origin'] || ($item['verb'] != Activity::ANNOUNCE))) {
- // Don't store the permissions on pure AP posts
- $store_permissions = ($item['network'] != Protocol::ACTIVITYPUB) || $item['origin'] || !empty($item['diaspora_signed_text']);
- $item['allow_cid'] = $store_permissions ? $toplevel_parent['allow_cid'] : '';
- $item['allow_gid'] = $store_permissions ? $toplevel_parent['allow_gid'] : '';
- $item['deny_cid'] = $store_permissions ? $toplevel_parent['deny_cid'] : '';
- $item['deny_gid'] = $store_permissions ? $toplevel_parent['deny_gid'] : '';
- }
-
+ $parent_id = (int) $toplevel_parent['id'];
+ $item = $itemHelper->handleToplevelParent($item, $toplevel_parent, $defined_permissions);
$parent_origin = $toplevel_parent['origin'];
-
- // Don't federate received participation messages
- if ($item['verb'] != Activity::FOLLOW) {
- $item['wall'] = $toplevel_parent['wall'];
- } else {
- $item['wall'] = false;
- // Participations are technical messages, so they are set to "seen" automatically
- $item['unseen'] = false;
- }
-
- /*
- * If the parent is private, force privacy for the entire conversation
- * This differs from the above settings as it subtly allows comments from
- * email correspondents to be private even if the overall thread is not.
- */
- if (!$defined_permissions && $toplevel_parent['private']) {
- $item['private'] = $toplevel_parent['private'];
- }
-
- // If its a post that originated here then tag the thread as "mention"
- if ($item['origin'] && $item['uid']) {
- DBA::update('post-thread-user', ['mention' => true], ['uri-id' => $item['parent-uri-id'], 'uid' => $item['uid']]);
- DI::logger()->info('tagged thread as mention', ['parent' => $parent_id, 'parent-uri-id' => $item['parent-uri-id'], 'uid' => $item['uid']]);
- }
-
- // Update the contact relations
- Contact\Relation::store($toplevel_parent['author-id'], $item['author-id'], $item['created']);
} else {
$parent_id = 0;
$parent_origin = $item['origin'];
@@ -1344,6 +1050,11 @@ class Item
DI::logger()->notice('created item', ['post-id' => $post_user_id, 'uid' => $item['uid'], 'network' => $item['network'], 'uri-id' => $item['uri-id'], 'guid' => $item['guid']]);
+ return self::handleCreatedItem($orig_item, $post_user_id, $uid, $notify, $copy_permissions, $parent_origin, $priority, $notify_type, $inserted, $source);
+ }
+
+ private static function handleCreatedItem(array $orig_item, int $post_user_id, int $uid, int $notify, bool $copy_permissions, $parent_origin, int $priority, string $notify_type, bool $inserted, $source): int
+ {
$posted_item = Post::selectFirst(self::ITEM_FIELDLIST, ['post-user-id' => $post_user_id]);
if (!DBA::isResult($posted_item)) {
// On failure store the data into a spool file so that the "SpoolPost" worker can try again later.
@@ -1484,33 +1195,6 @@ class Item
return $post_user_id;
}
- private static function hasRestrictions(array $item, int $author_id, int $restrictions = null): bool
- {
- if (empty($restrictions) || ($author_id == $item['author-id'])) {
- return false;
- }
-
- // We only have to apply restrictions if the post originates from our server or is federated.
- // Every other time we can trust the remote system.
- if (!in_array($item['network'], Protocol::FEDERATED) && !$item['origin']) {
- return false;
- }
-
- if (($restrictions & self::CANT_REPLY) && ($item['verb'] == Activity::POST)) {
- return true;
- }
-
- if (($restrictions & self::CANT_ANNOUNCE) && ($item['verb'] == Activity::ANNOUNCE)) {
- return true;
- }
-
- if (($restrictions & self::CANT_LIKE) && in_array($item['verb'], [Activity::LIKE, Activity::DISLIKE, Activity::ATTEND, Activity::ATTENDMAYBE, Activity::ATTENDNO])) {
- return true;
- }
-
- return false;
- }
-
private static function reshareChannelPost(int $uri_id, int $reshare_id = 0)
{
if (!DI::config()->get('system', 'allow_relay_channels')) {
@@ -1890,7 +1574,7 @@ class Item
*
* @param int $uriid
* @param int $uid
- * @return int
+ * @return int|null
*/
private static function GetOriginUidForUriId(int $uriid, int $uid)
{
diff --git a/src/Model/ItemHelper.php b/src/Model/ItemHelper.php
new file mode 100644
index 0000000000..9b585fd544
--- /dev/null
+++ b/src/Model/ItemHelper.php
@@ -0,0 +1,404 @@
+itemContent = $itemContent;
+ $this->activity = $activity;
+ $this->logger = $logger;
+ $this->database = $database;
+ $this->baseUrl = $baseURL->__toString();
+ }
+
+ public function prepareOriginPost(array $item): array
+ {
+ $item = $this->itemContent->initializePost($item);
+ $item = $this->itemContent->finalizePost($item, false);
+
+ return $item;
+ }
+
+ public function prepareItemData(array $item, bool $notify): array
+ {
+ $item['guid'] = Item::guid($item, $notify);
+ $item['uri'] = substr(trim($item['uri'] ?? '') ?: Item::newURI($item['guid']), 0, 255);
+
+ // Store URI data
+ $item['uri-id'] = ItemURI::insert(['uri' => $item['uri'], 'guid' => $item['guid']]);
+
+ // Backward compatibility: parent-uri used to be the direct parent uri.
+ // If it is provided without a thr-parent, it probably is the old behavior.
+ if (empty($item['thr-parent']) || empty($item['parent-uri'])) {
+ $item['thr-parent'] = trim($item['thr-parent'] ?? $item['parent-uri'] ?? $item['uri']);
+ $item['parent-uri'] = $item['thr-parent'];
+ }
+
+ $item['thr-parent-id'] = ItemURI::getIdByURI($item['thr-parent']);
+ $item['parent-uri-id'] = ItemURI::getIdByURI($item['parent-uri']);
+
+ return $item;
+ }
+
+ /**
+ * Return the id of the given item array if it has been stored before
+ *
+ * @param array $item Item record
+ * @return integer Item id or zero on error
+ */
+ public function getDuplicateID(array $item): int
+ {
+ if (empty($item['network']) || in_array($item['network'], Protocol::FEDERATED)) {
+ $condition = [
+ '`uri-id` = ? AND `uid` = ? AND `network` IN (?, ?, ?)',
+ $item['uri-id'],
+ $item['uid'],
+ Protocol::ACTIVITYPUB,
+ Protocol::DIASPORA,
+ Protocol::DFRN
+ ];
+
+ $existing = Post::selectFirst(['id', 'network'], $condition);
+
+ if ($this->database->isResult($existing)) {
+ // We only log the entries with a different user id than 0. Otherwise we would have too many false positives
+ if ($item['uid'] != 0) {
+ $this->logger->notice('Item already existed for user', [
+ 'uri-id' => $item['uri-id'],
+ 'uid' => $item['uid'],
+ 'network' => $item['network'],
+ 'existing_id' => $existing['id'],
+ 'existing_network' => $existing['network']
+ ]);
+ }
+
+ return $existing['id'];
+ }
+ }
+ return 0;
+ }
+
+ /**
+ * Check if the item array is a duplicate
+ *
+ * @private
+ *
+ * @param array $item Item record
+ * @return boolean is it a duplicate?
+ */
+ public function isDuplicate(array $item): bool
+ {
+ // Checking if there is already an item with the same guid
+ $condition = ['guid' => $item['guid'], 'network' => $item['network'], 'uid' => $item['uid']];
+ if (Post::exists($condition)) {
+ $this->logger->notice('Found already existing item', $condition);
+ return true;
+ }
+
+ $condition = [
+ 'uri-id' => $item['uri-id'], 'uid' => $item['uid'],
+ 'network' => [$item['network'], Protocol::DFRN]
+ ];
+ if (Post::exists($condition)) {
+ $this->logger->notice('duplicated item with the same uri found.', $condition);
+ return true;
+ }
+
+ // On Friendica and Diaspora the GUID is unique
+ if (in_array($item['network'], [Protocol::DFRN, Protocol::DIASPORA])) {
+ $condition = ['guid' => $item['guid'], 'uid' => $item['uid']];
+ if (Post::exists($condition)) {
+ $this->logger->notice('duplicated item with the same guid found.', $condition);
+ return true;
+ }
+ }
+
+ /*
+ * Check for already added items.
+ * There is a timing issue here that sometimes creates double postings.
+ * An unique index would help - but the limitations of MySQL (maximum size of index values) prevent this.
+ */
+ if (($item['uid'] == 0) && Post::exists(['uri-id' => $item['uri-id'], 'uid' => 0])) {
+ $this->logger->notice('Global item already stored.', ['uri-id' => $item['uri-id'], 'network' => $item['network']]);
+ return true;
+ }
+
+ return false;
+ }
+
+ public function validateItemData(array $item): array
+ {
+ $item['wall'] = intval($item['wall'] ?? 0);
+ $item['extid'] = trim($item['extid'] ?? '');
+ $item['author-name'] = trim($item['author-name'] ?? '');
+ $item['author-link'] = trim($item['author-link'] ?? '');
+ $item['author-avatar'] = trim($item['author-avatar'] ?? '');
+ $item['owner-name'] = trim($item['owner-name'] ?? '');
+ $item['owner-link'] = trim($item['owner-link'] ?? '');
+ $item['owner-avatar'] = trim($item['owner-avatar'] ?? '');
+ $item['received'] = (isset($item['received']) ? DateTimeFormat::utc($item['received']) : DateTimeFormat::utcNow());
+ $item['created'] = (isset($item['created']) ? DateTimeFormat::utc($item['created']) : $item['received']);
+ $item['edited'] = (isset($item['edited']) ? DateTimeFormat::utc($item['edited']) : $item['created']);
+ $item['changed'] = (isset($item['changed']) ? DateTimeFormat::utc($item['changed']) : $item['created']);
+ $item['commented'] = (isset($item['commented']) ? DateTimeFormat::utc($item['commented']) : $item['created']);
+ $item['title'] = substr(trim($item['title'] ?? ''), 0, 255);
+ $item['location'] = trim($item['location'] ?? '');
+ $item['coord'] = trim($item['coord'] ?? '');
+ $item['visible'] = (isset($item['visible']) ? intval($item['visible']) : 1);
+ $item['deleted'] = 0;
+ $item['verb'] = trim($item['verb'] ?? '');
+ $item['object-type'] = trim($item['object-type'] ?? '');
+ $item['object'] = trim($item['object'] ?? '');
+ $item['target-type'] = trim($item['target-type'] ?? '');
+ $item['target'] = trim($item['target'] ?? '');
+ $item['plink'] = substr(trim($item['plink'] ?? ''), 0, 255);
+ $item['allow_cid'] = trim($item['allow_cid'] ?? '');
+ $item['allow_gid'] = trim($item['allow_gid'] ?? '');
+ $item['deny_cid'] = trim($item['deny_cid'] ?? '');
+ $item['deny_gid'] = trim($item['deny_gid'] ?? '');
+ $item['private'] = intval($item['private'] ?? Item::PUBLIC);
+ $item['body'] = trim($item['body'] ?? '');
+ $item['raw-body'] = trim($item['raw-body'] ?? $item['body']);
+ $item['app'] = trim($item['app'] ?? '');
+ $item['origin'] = intval($item['origin'] ?? 0);
+ $item['postopts'] = trim($item['postopts'] ?? '');
+ $item['resource-id'] = trim($item['resource-id'] ?? '');
+ $item['event-id'] = intval($item['event-id'] ?? 0);
+ $item['inform'] = trim($item['inform'] ?? '');
+ $item['file'] = trim($item['file'] ?? '');
+
+ // Items cannot be stored before they happen ...
+ if ($item['created'] > DateTimeFormat::utcNow()) {
+ $item['created'] = DateTimeFormat::utcNow();
+ }
+
+ // We haven't invented time travel by now.
+ if ($item['edited'] > DateTimeFormat::utcNow()) {
+ $item['edited'] = DateTimeFormat::utcNow();
+ }
+
+ $item['plink'] = ($item['plink'] ?? '') ?: $this->baseUrl . '/display/' . urlencode($item['guid']);
+
+ $item['gravity'] = $this->getGravity($item);
+
+ if ($item['gravity'] === Item::GRAVITY_UNKNOWN) {
+ $this->logger->info('Unknown gravity for verb', ['verb' => $item['verb']]);
+ }
+
+ $default = [
+ 'url' => $item['author-link'], 'name' => $item['author-name'],
+ 'photo' => $item['author-avatar'], 'network' => $item['network']
+ ];
+ $item['author-id'] = ($item['author-id'] ?? 0) ?: Contact::getIdForURL($item['author-link'], 0, null, $default);
+
+ $default = [
+ 'url' => $item['owner-link'], 'name' => $item['owner-name'],
+ 'photo' => $item['owner-avatar'], 'network' => $item['network']
+ ];
+ $item['owner-id'] = ($item['owner-id'] ?? 0) ?: Contact::getIdForURL($item['owner-link'], 0, null, $default);
+
+ $item['post-reason'] = Item::getPostReason($item);
+
+ return $item;
+ }
+
+ /**
+ * Fetch top-level parent data for the given item array
+ *
+ * @param array $item
+ * @return array item array with parent data
+ * @throws \Exception
+ */
+ public function getTopLevelParent(array $item): array
+ {
+ $fields = [
+ 'uid', 'uri', 'parent-uri', 'id', 'deleted',
+ 'uri-id', 'parent-uri-id', 'restrictions', 'verb',
+ 'allow_cid', 'allow_gid', 'deny_cid', 'deny_gid',
+ 'wall', 'private', 'origin', 'author-id'
+ ];
+ $condition = ['uri-id' => [$item['thr-parent-id'], $item['parent-uri-id']], 'uid' => $item['uid']];
+ $params = ['order' => ['id' => false]];
+ $parent = Post::selectFirst($fields, $condition, $params);
+
+ if (!$this->database->isResult($parent) && Post::exists(['uri-id' => [$item['thr-parent-id'], $item['parent-uri-id']], 'uid' => 0])) {
+ $stored = Item::storeForUserByUriId($item['thr-parent-id'], $item['uid'], ['post-reason' => Item::PR_COMPLETION]);
+ if (!$stored && ($item['thr-parent-id'] != $item['parent-uri-id'])) {
+ $stored = Item::storeForUserByUriId($item['parent-uri-id'], $item['uid'], ['post-reason' => Item::PR_COMPLETION]);
+ }
+ if ($stored) {
+ $this->logger->info('Stored thread parent item for user', ['uri-id' => $item['thr-parent-id'], 'uid' => $item['uid'], 'stored' => $stored]);
+ $parent = Post::selectFirst($fields, $condition, $params);
+ }
+ }
+
+ if (!$this->database->isResult($parent)) {
+ $this->logger->notice('item parent was not found - ignoring item', ['uri-id' => $item['uri-id'], 'thr-parent-id' => $item['thr-parent-id'], 'uid' => $item['uid']]);
+ return [];
+ }
+
+ if ($this->hasRestrictions($item, $parent['author-id'], $parent['restrictions'])) {
+ $this->logger->notice('Restrictions apply - ignoring item', ['restrictions' => $parent['restrictions'], 'verb' => $parent['verb'], 'uri-id' => $item['uri-id'], 'thr-parent-id' => $item['thr-parent-id'], 'uid' => $item['uid']]);
+ return [];
+ }
+
+ if ($parent['uri-id'] == $parent['parent-uri-id']) {
+ return $parent;
+ }
+
+ $condition = [
+ 'uri-id' => $parent['parent-uri-id'],
+ 'parent-uri-id' => $parent['parent-uri-id'],
+ 'uid' => $parent['uid']
+ ];
+ $params = ['order' => ['id' => false]];
+ $toplevel_parent = Post::selectFirst($fields, $condition, $params);
+
+ if (!$this->database->isResult($toplevel_parent) && $item['origin']) {
+ $stored = Item::storeForUserByUriId($item['parent-uri-id'], $item['uid'], ['post-reason' => Item::PR_COMPLETION]);
+ $this->logger->info('Stored parent item for user', ['uri-id' => $item['parent-uri-id'], 'uid' => $item['uid'], 'stored' => $stored]);
+ $toplevel_parent = Post::selectFirst($fields, $condition, $params);
+ }
+
+ if (!$this->database->isResult($toplevel_parent)) {
+ $this->logger->notice('item top level parent was not found - ignoring item', ['parent-uri-id' => $parent['parent-uri-id'], 'uid' => $parent['uid']]);
+ return [];
+ }
+
+ return $toplevel_parent;
+ }
+
+ public function handleToplevelParent(array $item, array $toplevel_parent, bool $defined_permissions): array
+ {
+ $parent_id = (int) $toplevel_parent['id'];
+ $item['parent-uri'] = $toplevel_parent['uri'];
+ $item['parent-uri-id'] = $toplevel_parent['uri-id'];
+ $item['deleted'] = $toplevel_parent['deleted'];
+ $item['wall'] = $toplevel_parent['wall'];
+
+ // Reshares have to keep their permissions to allow groups to work
+ if (!$defined_permissions && (!$item['origin'] || ($item['verb'] != Activity::ANNOUNCE))) {
+ // Don't store the permissions on pure AP posts
+ $store_permissions = ($item['network'] != Protocol::ACTIVITYPUB) || $item['origin'] || !empty($item['diaspora_signed_text']);
+ $item['allow_cid'] = $store_permissions ? $toplevel_parent['allow_cid'] : '';
+ $item['allow_gid'] = $store_permissions ? $toplevel_parent['allow_gid'] : '';
+ $item['deny_cid'] = $store_permissions ? $toplevel_parent['deny_cid'] : '';
+ $item['deny_gid'] = $store_permissions ? $toplevel_parent['deny_gid'] : '';
+ }
+
+ // Don't federate received participation messages
+ if ($item['verb'] != Activity::FOLLOW) {
+ $item['wall'] = $toplevel_parent['wall'];
+ } else {
+ $item['wall'] = false;
+ // Participations are technical messages, so they are set to "seen" automatically
+ $item['unseen'] = false;
+ }
+
+ /*
+ * If the parent is private, force privacy for the entire conversation
+ * This differs from the above settings as it subtly allows comments from
+ * email correspondents to be private even if the overall thread is not.
+ */
+ if (!$defined_permissions && $toplevel_parent['private']) {
+ $item['private'] = $toplevel_parent['private'];
+ }
+
+ // If its a post that originated here then tag the thread as "mention"
+ if ($item['origin'] && $item['uid']) {
+ $this->database->update('post-thread-user', ['mention' => true], ['uri-id' => $item['parent-uri-id'], 'uid' => $item['uid']]);
+ $this->logger->info('tagged thread as mention', ['parent' => $parent_id, 'parent-uri-id' => $item['parent-uri-id'], 'uid' => $item['uid']]);
+ }
+
+ // Update the contact relations
+ Contact\Relation::store($toplevel_parent['author-id'], $item['author-id'], $item['created']);
+
+ return $item;
+ }
+
+ private function hasRestrictions(array $item, int $author_id, int $restrictions = null): bool
+ {
+ if (empty($restrictions) || ($author_id == $item['author-id'])) {
+ return false;
+ }
+
+ // We only have to apply restrictions if the post originates from our server or is federated.
+ // Every other time we can trust the remote system.
+ if (!in_array($item['network'], Protocol::FEDERATED) && !$item['origin']) {
+ return false;
+ }
+
+ if (($restrictions & Item::CANT_REPLY) && ($item['verb'] == Activity::POST)) {
+ return true;
+ }
+
+ if (($restrictions & Item::CANT_ANNOUNCE) && ($item['verb'] == Activity::ANNOUNCE)) {
+ return true;
+ }
+
+ if (($restrictions & Item::CANT_LIKE) && in_array($item['verb'], [Activity::LIKE, Activity::DISLIKE, Activity::ATTEND, Activity::ATTENDMAYBE, Activity::ATTENDNO])) {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Get the gravity for the given item array
+ *
+ * @return int gravity
+ */
+ private function getGravity(array $item): int
+ {
+ if (isset($item['gravity'])) {
+ return intval($item['gravity']);
+ } elseif ($item['parent-uri-id'] === $item['uri-id']) {
+ return Item::GRAVITY_PARENT;
+ } elseif ($this->activity->match($item['verb'], Activity::POST)) {
+ return Item::GRAVITY_COMMENT;
+ } elseif ($this->activity->match($item['verb'], Activity::FOLLOW)) {
+ return Item::GRAVITY_ACTIVITY;
+ } elseif ($this->activity->match($item['verb'], Activity::ANNOUNCE)) {
+ return Item::GRAVITY_ACTIVITY;
+ }
+
+ return Item::GRAVITY_UNKNOWN; // Should not happen
+ }
+}
diff --git a/src/Protocol/ActivityPub/Transmitter.php b/src/Protocol/ActivityPub/Transmitter.php
index 890ae4f529..712b8954d4 100644
--- a/src/Protocol/ActivityPub/Transmitter.php
+++ b/src/Protocol/ActivityPub/Transmitter.php
@@ -807,25 +807,45 @@ class Transmitter
}
}
+ $data = self::filterReceiverData($data, $item['author-link']);
+
+ $receivers = ['to' => array_values($data['to']), 'cc' => array_values($data['cc']), 'bto' => array_values($data['bto']), 'bcc' => array_values($data['bcc']), 'audience' => array_values($data['audience'])];
+
+ if (!$blindcopy) {
+ unset($receivers['bto']);
+ unset($receivers['bcc']);
+ }
+
+ if (!$blindcopy && count($receivers['audience']) == 1) {
+ $receivers['audience'] = $receivers['audience'][0];
+ } elseif (!$receivers['audience']) {
+ unset($receivers['audience']);
+ }
+
+ return $receivers;
+ }
+
+ private static function filterReceiverData(array $data, string $author_link): array
+ {
$data['to'] = array_unique($data['to']);
$data['cc'] = array_unique($data['cc']);
$data['bto'] = array_unique($data['bto']);
$data['bcc'] = array_unique($data['bcc']);
$data['audience'] = array_unique($data['audience']);
- if (($key = array_search($item['author-link'], $data['to'])) !== false) {
+ if (($key = array_search($author_link, $data['to'])) !== false) {
unset($data['to'][$key]);
}
- if (($key = array_search($item['author-link'], $data['cc'])) !== false) {
+ if (($key = array_search($author_link, $data['cc'])) !== false) {
unset($data['cc'][$key]);
}
- if (($key = array_search($item['author-link'], $data['bto'])) !== false) {
+ if (($key = array_search($author_link, $data['bto'])) !== false) {
unset($data['bto'][$key]);
}
- if (($key = array_search($item['author-link'], $data['bcc'])) !== false) {
+ if (($key = array_search($author_link, $data['bcc'])) !== false) {
unset($data['bcc'][$key]);
}
@@ -859,20 +879,7 @@ class Transmitter
}
}
- $receivers = ['to' => array_values($data['to']), 'cc' => array_values($data['cc']), 'bto' => array_values($data['bto']), 'bcc' => array_values($data['bcc']), 'audience' => array_values($data['audience'])];
-
- if (!$blindcopy) {
- unset($receivers['bto']);
- unset($receivers['bcc']);
- }
-
- if (!$blindcopy && count($receivers['audience']) == 1) {
- $receivers['audience'] = $receivers['audience'][0];
- } elseif (!$receivers['audience']) {
- unset($receivers['audience']);
- }
-
- return $receivers;
+ return $data;
}
/**
diff --git a/src/Protocol/Feed.php b/src/Protocol/Feed.php
index fa1ea3b302..2f9be305f1 100644
--- a/src/Protocol/Feed.php
+++ b/src/Protocol/Feed.php
@@ -9,6 +9,8 @@ namespace Friendica\Protocol;
use DOMDocument;
use DOMElement;
+use DOMNode;
+use DOMNodeList;
use DOMXPath;
use Friendica\App;
use Friendica\Contact\LocalRelationship\Entity\LocalRelationship;
@@ -67,12 +69,12 @@ class Feed
return [];
}
+ $basepath = '';
+
if (!empty($contact['poll'])) {
- $basepath = $contact['poll'];
+ $basepath = (string) $contact['poll'];
} elseif (!empty($contact['url'])) {
- $basepath = $contact['url'];
- } else {
- $basepath = '';
+ $basepath = (string) $contact['url'];
}
$doc = new DOMDocument();
@@ -287,6 +289,77 @@ class Feed
$total_items = $max_items;
}
+ $postings = self::importOlderEntries($entries, $total_items, $header, $author, $contact, $importer, $xpath, $atomns, $basepath, $dryRun);
+
+ if (!empty($postings)) {
+ $min_posting = DI::config()->get('system', 'minimum_posting_interval', 0);
+ $total = count($postings);
+ if ($total > 1) {
+ // Posts shouldn't be delayed more than a day
+ $interval = min(1440, self::getPollInterval($contact));
+ $delay = max(round(($interval * 60) / $total), 60 * $min_posting);
+ DI::logger()->info('Got posting delay', ['delay' => $delay, 'interval' => $interval, 'items' => $total, 'cid' => $contact['id'], 'url' => $contact['url']]);
+ } else {
+ $delay = 0;
+ }
+
+ $post_delay = 0;
+
+ foreach ($postings as $posting) {
+ if ($delay > 0) {
+ $publish_time = time() + $post_delay;
+ $post_delay += $delay;
+ } else {
+ $publish_time = time();
+ }
+
+ $last_publish = DI::pConfig()->get($posting['item']['uid'], 'system', 'last_publish', 0, true);
+ $next_publish = max($last_publish + (60 * $min_posting), time());
+ if ($publish_time < $next_publish) {
+ $publish_time = $next_publish;
+ }
+ $publish_at = date(DateTimeFormat::MYSQL, $publish_time);
+
+ if (Post\Delayed::add($posting['item']['uri'], $posting['item'], $posting['notify'], Post\Delayed::PREPARED, $publish_at, $posting['taglist'], $posting['attachments'])) {
+ DI::pConfig()->set($posting['item']['uid'], 'system', 'last_publish', $publish_time);
+ }
+ }
+ }
+
+ if (!$dryRun && DI::config()->get('system', 'adjust_poll_frequency')) {
+ self::adjustPollFrequency($contact, $creation_dates);
+ }
+
+ return ['header' => $author, 'items' => $items];
+ }
+
+ private static function getTitleFromItemOrEntry(array $item, DOMXPath $xpath, string $atomns, ?DOMNode $entry): string
+ {
+ $title = (string) $item['title'];
+
+ if (empty($title)) {
+ $title = XML::getFirstNodeValue($xpath, $atomns . ':title/text()', $entry);
+ }
+
+ if (empty($title)) {
+ $title = XML::getFirstNodeValue($xpath, 'title/text()', $entry);
+ }
+
+ if (empty($title)) {
+ $title = XML::getFirstNodeValue($xpath, 'rss:title/text()', $entry);
+ }
+
+ if (empty($title)) {
+ $title = XML::getFirstNodeValue($xpath, 'itunes:title/text()', $entry);
+ }
+
+ $title = trim(html_entity_decode($title, ENT_QUOTES, 'UTF-8'));
+
+ return $title;
+ }
+
+ private static function importOlderEntries(DOMNodeList $entries, int $total_items, array $header, array $author, array $contact, array $importer, DOMXPath $xpath, string $atomns, string $basepath, bool $dryRun): array
+ {
$postings = [];
// Importing older entries first
@@ -386,23 +459,7 @@ class Feed
}
}
- if (empty($item['title'])) {
- $item['title'] = XML::getFirstNodeValue($xpath, $atomns . ':title/text()', $entry);
- }
-
- if (empty($item['title'])) {
- $item['title'] = XML::getFirstNodeValue($xpath, 'title/text()', $entry);
- }
-
- if (empty($item['title'])) {
- $item['title'] = XML::getFirstNodeValue($xpath, 'rss:title/text()', $entry);
- }
-
- if (empty($item['title'])) {
- $item['title'] = XML::getFirstNodeValue($xpath, 'itunes:title/text()', $entry);
- }
-
- $item['title'] = trim(html_entity_decode($item['title'], ENT_QUOTES, 'UTF-8'));
+ $item['title'] = self::getTitleFromItemOrEntry($item, $xpath, $atomns, $entry);
$published = XML::getFirstNodeValue($xpath, $atomns . ':published/text()', $entry);
@@ -705,46 +762,7 @@ class Feed
}
}
- if (!empty($postings)) {
- $min_posting = DI::config()->get('system', 'minimum_posting_interval', 0);
- $total = count($postings);
- if ($total > 1) {
- // Posts shouldn't be delayed more than a day
- $interval = min(1440, self::getPollInterval($contact));
- $delay = max(round(($interval * 60) / $total), 60 * $min_posting);
- DI::logger()->info('Got posting delay', ['delay' => $delay, 'interval' => $interval, 'items' => $total, 'cid' => $contact['id'], 'url' => $contact['url']]);
- } else {
- $delay = 0;
- }
-
- $post_delay = 0;
-
- foreach ($postings as $posting) {
- if ($delay > 0) {
- $publish_time = time() + $post_delay;
- $post_delay += $delay;
- } else {
- $publish_time = time();
- }
-
- $last_publish = DI::pConfig()->get($posting['item']['uid'], 'system', 'last_publish', 0, true);
- $next_publish = max($last_publish + (60 * $min_posting), time());
- if ($publish_time < $next_publish) {
- $publish_time = $next_publish;
- }
- $publish_at = date(DateTimeFormat::MYSQL, $publish_time);
-
- if (Post\Delayed::add($posting['item']['uri'], $posting['item'], $posting['notify'], Post\Delayed::PREPARED, $publish_at, $posting['taglist'], $posting['attachments'])) {
- DI::pConfig()->set($posting['item']['uid'], 'system', 'last_publish', $publish_time);
- }
- }
- }
-
- if (!$dryRun && DI::config()->get('system', 'adjust_poll_frequency')) {
- self::adjustPollFrequency($contact, $creation_dates);
- }
-
- return ['header' => $author, 'items' => $items];
+ return $postings;
}
/**
diff --git a/view/lang/C/messages.po b/view/lang/C/messages.po
index b89c94b993..2cfc48efbc 100644
--- a/view/lang/C/messages.po
+++ b/view/lang/C/messages.po
@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: 2025.02-dev\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2025-02-09 20:13+0000\n"
+"POT-Creation-Date: 2025-02-11 07:41+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME \n"
"Language-Team: LANGUAGE \n"
@@ -45,7 +45,7 @@ msgid "Item not found."
msgstr ""
#: mod/item.php:447 mod/message.php:54 mod/message.php:100 mod/notes.php:34
-#: mod/photos.php:132 mod/photos.php:624 src/Model/Event.php:506
+#: mod/photos.php:131 mod/photos.php:623 src/Model/Event.php:506
#: src/Module/Attach.php:40 src/Module/BaseApi.php:90
#: src/Module/BaseNotifications.php:83 src/Module/BaseSettings.php:38
#: src/Module/Calendar/Event/API.php:75 src/Module/Calendar/Event/Form.php:70
@@ -289,8 +289,8 @@ msgstr ""
msgid "Please wait"
msgstr ""
-#: mod/message.php:189 mod/message.php:343 mod/photos.php:655
-#: mod/photos.php:775 mod/photos.php:1052 mod/photos.php:1093
+#: mod/message.php:189 mod/message.php:343 mod/photos.php:654
+#: mod/photos.php:774 mod/photos.php:1051 mod/photos.php:1093
#: mod/photos.php:1149 mod/photos.php:1229
#: src/Module/Calendar/Event/Form.php:236 src/Module/Contact/Advanced.php:118
#: src/Module/Contact/Profile.php:376
@@ -377,7 +377,7 @@ msgstr ""
msgid "Save"
msgstr ""
-#: mod/photos.php:51 mod/photos.php:114 mod/photos.php:534
+#: mod/photos.php:50 mod/photos.php:113 mod/photos.php:533
#: src/Model/Event.php:498 src/Model/Profile.php:211
#: src/Module/Calendar/Export.php:60 src/Module/Calendar/Show.php:62
#: src/Module/Feed.php:52 src/Module/HCard.php:37
@@ -389,100 +389,100 @@ msgstr ""
msgid "User not found."
msgstr ""
-#: mod/photos.php:88 src/Module/BaseProfile.php:53
+#: mod/photos.php:87 src/Module/BaseProfile.php:53
#: src/Module/Profile/Photos.php:372
msgid "Photo Albums"
msgstr ""
-#: mod/photos.php:89 src/Module/Profile/Photos.php:373
+#: mod/photos.php:88 src/Module/Profile/Photos.php:373
#: src/Module/Profile/Photos.php:393
msgid "Recent Photos"
msgstr ""
-#: mod/photos.php:91 mod/photos.php:823 src/Module/Profile/Photos.php:375
+#: mod/photos.php:90 mod/photos.php:822 src/Module/Profile/Photos.php:375
#: src/Module/Profile/Photos.php:395
msgid "Upload New Photos"
msgstr ""
-#: mod/photos.php:103 src/Module/BaseSettings.php:60
+#: mod/photos.php:102 src/Module/BaseSettings.php:60
#: src/Module/Profile/Photos.php:356
msgid "everybody"
msgstr ""
-#: mod/photos.php:139
+#: mod/photos.php:138
msgid "Contact information unavailable"
msgstr ""
-#: mod/photos.php:168
+#: mod/photos.php:167
msgid "Album not found."
msgstr ""
-#: mod/photos.php:224
+#: mod/photos.php:223
msgid "Album successfully deleted"
msgstr ""
-#: mod/photos.php:226
+#: mod/photos.php:225
msgid "Album was empty."
msgstr ""
-#: mod/photos.php:257
+#: mod/photos.php:256
msgid "Failed to delete the photo."
msgstr ""
-#: mod/photos.php:501
+#: mod/photos.php:500
msgid "a photo"
msgstr ""
-#: mod/photos.php:501
+#: mod/photos.php:500
#, php-format
msgid "%1$s was tagged in %2$s by %3$s"
msgstr ""
-#: mod/photos.php:538 src/Module/Conversation/Community.php:148
+#: mod/photos.php:537 src/Module/Conversation/Community.php:148
#: src/Module/Directory.php:34 src/Module/Profile/Photos.php:290
#: src/Module/Search/Index.php:50
msgid "Public access denied."
msgstr ""
-#: mod/photos.php:543
+#: mod/photos.php:542
msgid "No photos selected"
msgstr ""
-#: mod/photos.php:671
+#: mod/photos.php:670
#, php-format
msgid "The maximum accepted image size is %s"
msgstr ""
-#: mod/photos.php:678
+#: mod/photos.php:677
msgid "Upload Photos"
msgstr ""
-#: mod/photos.php:682 mod/photos.php:771
+#: mod/photos.php:681 mod/photos.php:770
msgid "New album name: "
msgstr ""
-#: mod/photos.php:683
+#: mod/photos.php:682
msgid "or select existing album:"
msgstr ""
-#: mod/photos.php:684
+#: mod/photos.php:683
msgid "Do not show a status post for this upload"
msgstr ""
-#: mod/photos.php:687 mod/photos.php:1048 src/Content/Conversation.php:391
+#: mod/photos.php:686 mod/photos.php:1047 src/Content/Conversation.php:391
#: src/Module/Calendar/Event/Form.php:239 src/Module/Post/Edit.php:174
msgid "Permissions"
msgstr ""
-#: mod/photos.php:752
+#: mod/photos.php:751
msgid "Do you really want to delete this photo album and all its photos?"
msgstr ""
-#: mod/photos.php:753 mod/photos.php:776
+#: mod/photos.php:752 mod/photos.php:775
msgid "Delete Album"
msgstr ""
-#: mod/photos.php:754 mod/photos.php:854 src/Content/Conversation.php:406
+#: mod/photos.php:753 mod/photos.php:853 src/Content/Conversation.php:406
#: src/Module/Contact/Follow.php:158 src/Module/Contact/Revoke.php:92
#: src/Module/Contact/Unfollow.php:112
#: src/Module/Media/Attachment/Browser.php:64
@@ -492,99 +492,99 @@ msgstr ""
msgid "Cancel"
msgstr ""
-#: mod/photos.php:780
+#: mod/photos.php:779
msgid "Edit Album"
msgstr ""
-#: mod/photos.php:781
+#: mod/photos.php:780
msgid "Drop Album"
msgstr ""
-#: mod/photos.php:785
+#: mod/photos.php:784
msgid "Show Newest First"
msgstr ""
-#: mod/photos.php:787
+#: mod/photos.php:786
msgid "Show Oldest First"
msgstr ""
-#: mod/photos.php:808 src/Module/Profile/Photos.php:343
+#: mod/photos.php:807 src/Module/Profile/Photos.php:343
msgid "View Photo"
msgstr ""
-#: mod/photos.php:840
+#: mod/photos.php:839
msgid "Permission denied. Access to this item may be restricted."
msgstr ""
-#: mod/photos.php:842
+#: mod/photos.php:841
msgid "Photo not available"
msgstr ""
-#: mod/photos.php:852
+#: mod/photos.php:851
msgid "Do you really want to delete this photo?"
msgstr ""
-#: mod/photos.php:853 mod/photos.php:1053
+#: mod/photos.php:852 mod/photos.php:1052
msgid "Delete Photo"
msgstr ""
-#: mod/photos.php:951
+#: mod/photos.php:950
msgid "View photo"
msgstr ""
-#: mod/photos.php:953
+#: mod/photos.php:952
msgid "Edit photo"
msgstr ""
-#: mod/photos.php:954
+#: mod/photos.php:953
msgid "Delete photo"
msgstr ""
-#: mod/photos.php:955
+#: mod/photos.php:954
msgid "Use as profile photo"
msgstr ""
-#: mod/photos.php:962
+#: mod/photos.php:961
msgid "Private Photo"
msgstr ""
-#: mod/photos.php:968
+#: mod/photos.php:967
msgid "View Full Size"
msgstr ""
-#: mod/photos.php:1021
+#: mod/photos.php:1020
msgid "Tags: "
msgstr ""
-#: mod/photos.php:1024
+#: mod/photos.php:1023
msgid "[Select tags to remove]"
msgstr ""
-#: mod/photos.php:1039
+#: mod/photos.php:1038
msgid "New album name"
msgstr ""
-#: mod/photos.php:1040
+#: mod/photos.php:1039
msgid "Caption"
msgstr ""
-#: mod/photos.php:1041
+#: mod/photos.php:1040
msgid "Add a Tag"
msgstr ""
-#: mod/photos.php:1041
+#: mod/photos.php:1040
msgid "Example: @bob, @Barbara_Jensen, @jim@example.com, #California, #camping"
msgstr ""
-#: mod/photos.php:1042
+#: mod/photos.php:1041
msgid "Do not rotate"
msgstr ""
-#: mod/photos.php:1043
+#: mod/photos.php:1042
msgid "Rotate CW (right)"
msgstr ""
-#: mod/photos.php:1044
+#: mod/photos.php:1043
msgid "Rotate CCW (left)"
msgstr ""
@@ -646,7 +646,7 @@ msgstr ""
msgid "Map"
msgstr ""
-#: src/App.php:442
+#: src/App.php:451
msgid "Apologies but the website is unavailable at the moment."
msgstr ""
@@ -752,17 +752,17 @@ msgstr ""
msgid "Close"
msgstr ""
-#: src/App/Router.php:286
+#: src/App/Router.php:287
#, php-format
msgid "Method not allowed for this module. Allowed method(s): %s"
msgstr ""
-#: src/App/Router.php:288 src/Module/HTTPException/PageNotFound.php:35
+#: src/App/Router.php:289 src/Module/HTTPException/PageNotFound.php:35
#: src/Module/Stats.php:56
msgid "Page not found."
msgstr ""
-#: src/App/Router.php:300
+#: src/App/Router.php:301
msgid "You must be logged in to use addons. "
msgstr ""
@@ -779,19 +779,19 @@ msgid "All contacts"
msgstr ""
#: src/BaseModule.php:440 src/Content/Conversation/Factory/Channel.php:32
-#: src/Content/Widget.php:254 src/Core/ACL.php:182 src/Module/Contact.php:394
+#: src/Content/Widget.php:257 src/Core/ACL.php:182 src/Module/Contact.php:394
#: src/Module/Privacy/PermissionTooltip.php:150
#: src/Module/Privacy/PermissionTooltip.php:172
#: src/Module/Settings/Channels.php:146
msgid "Followers"
msgstr ""
-#: src/BaseModule.php:445 src/Content/Widget.php:255 src/Module/Contact.php:397
+#: src/BaseModule.php:445 src/Content/Widget.php:258 src/Module/Contact.php:397
#: src/Module/Settings/Channels.php:145
msgid "Following"
msgstr ""
-#: src/BaseModule.php:450 src/Content/Widget.php:256 src/Module/Contact.php:400
+#: src/BaseModule.php:450 src/Content/Widget.php:259 src/Module/Contact.php:400
msgid "Mutual friends"
msgstr ""
@@ -799,15 +799,15 @@ msgstr ""
msgid "Common"
msgstr ""
-#: src/Console/Addon.php:163 src/Console/Addon.php:187
+#: src/Console/Addon.php:164 src/Console/Addon.php:188
msgid "Addon not found"
msgstr ""
-#: src/Console/Addon.php:167
+#: src/Console/Addon.php:168
msgid "Addon already enabled"
msgstr ""
-#: src/Console/Addon.php:191
+#: src/Console/Addon.php:192
msgid "Addon already disabled"
msgstr ""
@@ -1701,7 +1701,7 @@ msgstr ""
msgid "Network Widgets"
msgstr ""
-#: src/Content/Feature.php:126 src/Content/Widget.php:230
+#: src/Content/Feature.php:126 src/Content/Widget.php:233
#: src/Model/Circle.php:587 src/Module/Contact.php:380
#: src/Module/Welcome.php:62
msgid "Circles"
@@ -1713,7 +1713,7 @@ msgstr ""
#: src/Content/Feature.php:127 src/Content/GroupManager.php:128
#: src/Content/Nav.php:274 src/Content/Text/HTML.php:868
-#: src/Content/Widget.php:555 src/Model/User.php:1393
+#: src/Content/Widget.php:558 src/Model/User.php:1393
msgid "Groups"
msgstr ""
@@ -1721,7 +1721,7 @@ msgstr ""
msgid "Display posts that have been distributed by the selected group."
msgstr ""
-#: src/Content/Feature.php:128 src/Content/Widget.php:524
+#: src/Content/Feature.php:128 src/Content/Widget.php:527
msgid "Archives"
msgstr ""
@@ -1729,7 +1729,7 @@ msgstr ""
msgid "Display an archive where posts can be selected by month and year."
msgstr ""
-#: src/Content/Feature.php:129 src/Content/Widget.php:303
+#: src/Content/Feature.php:129 src/Content/Widget.php:306
msgid "Protocols"
msgstr ""
@@ -1737,7 +1737,7 @@ msgstr ""
msgid "Display posts with the selected protocols."
msgstr ""
-#: src/Content/Feature.php:130 src/Content/Widget.php:561
+#: src/Content/Feature.php:130 src/Content/Widget.php:564
#: src/Module/Settings/Account.php:391
msgid "Account Types"
msgstr ""
@@ -1746,7 +1746,7 @@ msgstr ""
msgid "Display posts done by accounts with the selected account type."
msgstr ""
-#: src/Content/Feature.php:131 src/Content/Widget.php:610
+#: src/Content/Feature.php:131 src/Content/Widget.php:613
#: src/Module/Admin/Site.php:463 src/Module/BaseSettings.php:113
#: src/Module/Settings/Channels.php:211 src/Module/Settings/Display.php:313
msgid "Channels"
@@ -1764,7 +1764,7 @@ msgstr ""
msgid "Display posts that contain subscribed hashtags."
msgstr ""
-#: src/Content/Feature.php:133 src/Content/Widget.php:333
+#: src/Content/Feature.php:133 src/Content/Widget.php:336
msgid "Saved Folders"
msgstr ""
@@ -1824,12 +1824,12 @@ msgstr ""
msgid "External link to group"
msgstr ""
-#: src/Content/GroupManager.php:134 src/Content/Widget.php:530
+#: src/Content/GroupManager.php:134 src/Content/Widget.php:533
msgid "show less"
msgstr ""
-#: src/Content/GroupManager.php:135 src/Content/Widget.php:425
-#: src/Content/Widget.php:531
+#: src/Content/GroupManager.php:135 src/Content/Widget.php:428
+#: src/Content/Widget.php:534
msgid "show more"
msgstr ""
@@ -1837,7 +1837,7 @@ msgstr ""
msgid "Create new group"
msgstr ""
-#: src/Content/Item.php:321 src/Model/Item.php:3296
+#: src/Content/Item.php:321 src/Model/Item.php:2980
msgid "event"
msgstr ""
@@ -1845,7 +1845,7 @@ msgstr ""
msgid "status"
msgstr ""
-#: src/Content/Item.php:330 src/Model/Item.php:3298
+#: src/Content/Item.php:330 src/Model/Item.php:2982
#: src/Module/Post/Tag/Add.php:109
msgid "photo"
msgstr ""
@@ -1922,7 +1922,7 @@ msgstr ""
msgid "Search Text"
msgstr ""
-#: src/Content/Item.php:440 src/Content/Widget.php:66
+#: src/Content/Item.php:440 src/Content/Widget.php:65
#: src/Model/Contact.php:1247 src/Model/Contact.php:1259
#: src/Module/Contact/Follow.php:152 view/theme/vier/theme.php:183
msgid "Connect/Follow"
@@ -2102,7 +2102,7 @@ msgstr ""
msgid "People directory"
msgstr ""
-#: src/Content/Nav.php:294 src/Module/BaseAdmin.php:71
+#: src/Content/Nav.php:294 src/Module/BaseAdmin.php:70
#: src/Module/BaseModeration.php:97
msgid "Information"
msgstr ""
@@ -2112,7 +2112,7 @@ msgid "Information about this friendica instance"
msgstr ""
#: src/Content/Nav.php:297 src/Module/Admin/Tos.php:64
-#: src/Module/BaseAdmin.php:81 src/Module/Register.php:169
+#: src/Module/BaseAdmin.php:80 src/Module/Register.php:169
#: src/Module/Tos.php:87
msgid "Terms of Service"
msgstr ""
@@ -2178,7 +2178,7 @@ msgstr ""
msgid "Manage other pages"
msgstr ""
-#: src/Content/Nav.php:323 src/Module/Admin/Addons/Details.php:98
+#: src/Content/Nav.php:323 src/Module/Admin/Addons/Details.php:101
#: src/Module/Admin/Themes/Details.php:85 src/Module/BaseSettings.php:177
#: src/Module/Welcome.php:38 view/theme/frio/theme.php:231
msgid "Settings"
@@ -2192,7 +2192,7 @@ msgstr ""
msgid "Manage/edit friends and contacts"
msgstr ""
-#: src/Content/Nav.php:330 src/Module/BaseAdmin.php:105
+#: src/Content/Nav.php:330 src/Module/BaseAdmin.php:114
msgid "Admin"
msgstr ""
@@ -2247,8 +2247,8 @@ msgstr ""
msgid "%2$s %3$s"
msgstr ""
-#: src/Content/Text/BBCode.php:926 src/Model/Item.php:4103
-#: src/Model/Item.php:4109 src/Model/Item.php:4110
+#: src/Content/Text/BBCode.php:926 src/Model/Item.php:3787
+#: src/Model/Item.php:3793 src/Model/Item.php:3794
msgid "Link to source"
msgstr ""
@@ -2285,129 +2285,129 @@ msgstr ""
msgid "Follow"
msgstr ""
-#: src/Content/Widget.php:37
+#: src/Content/Widget.php:36
msgid "Add New Contact"
msgstr ""
-#: src/Content/Widget.php:38
+#: src/Content/Widget.php:37
msgid "Enter address or web location"
msgstr ""
-#: src/Content/Widget.php:39
+#: src/Content/Widget.php:38
msgid "Example: bob@example.com, http://example.com/barbara"
msgstr ""
-#: src/Content/Widget.php:41
+#: src/Content/Widget.php:40
msgid "Connect"
msgstr ""
-#: src/Content/Widget.php:58
+#: src/Content/Widget.php:57
#, php-format
msgid "%d invitation available"
msgid_plural "%d invitations available"
msgstr[0] ""
msgstr[1] ""
-#: src/Content/Widget.php:64 view/theme/vier/theme.php:181
+#: src/Content/Widget.php:63 view/theme/vier/theme.php:181
msgid "Find People"
msgstr ""
-#: src/Content/Widget.php:65 view/theme/vier/theme.php:182
+#: src/Content/Widget.php:64 view/theme/vier/theme.php:182
msgid "Enter name or interest"
msgstr ""
-#: src/Content/Widget.php:67 view/theme/vier/theme.php:184
+#: src/Content/Widget.php:66 view/theme/vier/theme.php:184
msgid "Examples: Robert Morgenstein, Fishing"
msgstr ""
-#: src/Content/Widget.php:68 src/Module/Contact.php:440
+#: src/Content/Widget.php:67 src/Module/Contact.php:440
#: src/Module/Directory.php:82 view/theme/vier/theme.php:185
msgid "Find"
msgstr ""
-#: src/Content/Widget.php:69 src/Module/Contact/Suggestions.php:59
+#: src/Content/Widget.php:68 src/Module/Contact/Suggestions.php:59
#: view/theme/vier/theme.php:186
msgid "Friend Suggestions"
msgstr ""
-#: src/Content/Widget.php:70 view/theme/vier/theme.php:187
+#: src/Content/Widget.php:69 view/theme/vier/theme.php:187
msgid "Similar Interests"
msgstr ""
-#: src/Content/Widget.php:71 view/theme/vier/theme.php:188
+#: src/Content/Widget.php:70 view/theme/vier/theme.php:188
msgid "Random Profile"
msgstr ""
-#: src/Content/Widget.php:72 view/theme/vier/theme.php:189
+#: src/Content/Widget.php:71 view/theme/vier/theme.php:189
msgid "Invite Friends"
msgstr ""
-#: src/Content/Widget.php:73 src/Module/Directory.php:74
+#: src/Content/Widget.php:72 src/Module/Directory.php:74
#: view/theme/vier/theme.php:190
msgid "Global Directory"
msgstr ""
-#: src/Content/Widget.php:75 view/theme/vier/theme.php:192
+#: src/Content/Widget.php:74 view/theme/vier/theme.php:192
msgid "Local Directory"
msgstr ""
-#: src/Content/Widget.php:232
+#: src/Content/Widget.php:235
msgid "Everyone"
msgstr ""
-#: src/Content/Widget.php:257 src/Module/Contact.php:403
+#: src/Content/Widget.php:260 src/Module/Contact.php:403
msgid "No relationship"
msgstr ""
-#: src/Content/Widget.php:262
+#: src/Content/Widget.php:265
msgid "Relationships"
msgstr ""
-#: src/Content/Widget.php:264 src/Module/Circle.php:281
+#: src/Content/Widget.php:267 src/Module/Circle.php:281
#: src/Module/Contact.php:324
msgid "All Contacts"
msgstr ""
-#: src/Content/Widget.php:305
+#: src/Content/Widget.php:308
msgid "All Protocols"
msgstr ""
-#: src/Content/Widget.php:335 src/Content/Widget.php:366
+#: src/Content/Widget.php:338 src/Content/Widget.php:369
msgid "Everything"
msgstr ""
-#: src/Content/Widget.php:364
+#: src/Content/Widget.php:367
msgid "Categories"
msgstr ""
-#: src/Content/Widget.php:421
+#: src/Content/Widget.php:424
#, php-format
msgid "%d contact in common"
msgid_plural "%d contacts in common"
msgstr[0] ""
msgstr[1] ""
-#: src/Content/Widget.php:532
+#: src/Content/Widget.php:535
msgid "On this date"
msgstr ""
-#: src/Content/Widget.php:552
+#: src/Content/Widget.php:555
msgid "Persons"
msgstr ""
-#: src/Content/Widget.php:553
+#: src/Content/Widget.php:556
msgid "Organisations"
msgstr ""
-#: src/Content/Widget.php:554 src/Model/Contact.php:1757
+#: src/Content/Widget.php:557 src/Model/Contact.php:1757
msgid "News"
msgstr ""
-#: src/Content/Widget.php:556
+#: src/Content/Widget.php:559
msgid "Relays"
msgstr ""
-#: src/Content/Widget.php:563 src/Module/Moderation/BaseUsers.php:58
+#: src/Content/Widget.php:566 src/Module/Moderation/BaseUsers.php:58
msgid "All"
msgstr ""
@@ -2836,7 +2836,7 @@ msgstr ""
msgid "Could not connect to database."
msgstr ""
-#: src/Core/L10n.php:426 src/Model/Item.php:2339
+#: src/Core/L10n.php:426 src/Model/Item.php:2023
msgid "Undetermined"
msgstr ""
@@ -3224,7 +3224,7 @@ msgstr ""
msgid "Disallowed profile URL."
msgstr ""
-#: src/Model/Contact.php:3096 src/Module/Friendica.php:88
+#: src/Model/Contact.php:3096 src/Module/Friendica.php:90
msgid "Blocked domain"
msgstr ""
@@ -3378,92 +3378,92 @@ msgstr ""
msgid "Happy Birthday %s"
msgstr ""
-#: src/Model/Item.php:2346
+#: src/Model/Item.php:2030
#, php-format
msgid "%s (%s - %s): %s"
msgstr ""
-#: src/Model/Item.php:2348
+#: src/Model/Item.php:2032
#, php-format
msgid "%s (%s): %s"
msgstr ""
-#: src/Model/Item.php:2351
+#: src/Model/Item.php:2035
#, php-format
msgid ""
"Detected languages in this post:\n"
"%s"
msgstr ""
-#: src/Model/Item.php:3300
+#: src/Model/Item.php:2984
msgid "activity"
msgstr ""
-#: src/Model/Item.php:3302
+#: src/Model/Item.php:2986
msgid "comment"
msgstr ""
-#: src/Model/Item.php:3305 src/Module/Post/Tag/Add.php:109
+#: src/Model/Item.php:2989 src/Module/Post/Tag/Add.php:109
msgid "post"
msgstr ""
-#: src/Model/Item.php:3478
+#: src/Model/Item.php:3162
#, php-format
msgid "%s is blocked"
msgstr ""
-#: src/Model/Item.php:3480
+#: src/Model/Item.php:3164
#, php-format
msgid "%s is ignored"
msgstr ""
-#: src/Model/Item.php:3482
+#: src/Model/Item.php:3166
#, php-format
msgid "Content from %s is collapsed"
msgstr ""
-#: src/Model/Item.php:3486
+#: src/Model/Item.php:3170
msgid "Sensitive content"
msgstr ""
-#: src/Model/Item.php:4003
+#: src/Model/Item.php:3687
msgid "bytes"
msgstr ""
-#: src/Model/Item.php:4034
+#: src/Model/Item.php:3718
#, php-format
msgid "%2$s (%3$d%%, %1$d vote)"
msgid_plural "%2$s (%3$d%%, %1$d votes)"
msgstr[0] ""
msgstr[1] ""
-#: src/Model/Item.php:4036
+#: src/Model/Item.php:3720
#, php-format
msgid "%2$s (%1$d vote)"
msgid_plural "%2$s (%1$d votes)"
msgstr[0] ""
msgstr[1] ""
-#: src/Model/Item.php:4041
+#: src/Model/Item.php:3725
#, php-format
msgid "%d voter. Poll end: %s"
msgid_plural "%d voters. Poll end: %s"
msgstr[0] ""
msgstr[1] ""
-#: src/Model/Item.php:4043
+#: src/Model/Item.php:3727
#, php-format
msgid "%d voter."
msgid_plural "%d voters."
msgstr[0] ""
msgstr[1] ""
-#: src/Model/Item.php:4045
+#: src/Model/Item.php:3729
#, php-format
msgid "Poll end: %s"
msgstr ""
-#: src/Model/Item.php:4086 src/Model/Item.php:4087
+#: src/Model/Item.php:3770 src/Model/Item.php:3771
msgid "View on separate page"
msgstr ""
@@ -3574,7 +3574,7 @@ msgstr ""
msgid "Title/Description:"
msgstr ""
-#: src/Model/Profile.php:793 src/Module/Admin/Summary.php:174
+#: src/Model/Profile.php:793 src/Module/Admin/Summary.php:184
#: src/Module/Moderation/Report/Create.php:266
#: src/Module/Moderation/Summary.php:65
msgid "Summary"
@@ -3860,71 +3860,71 @@ msgstr ""
msgid "User with delegates can't be removed, please remove delegate users first"
msgstr ""
-#: src/Module/Admin/Addons/Details.php:49
+#: src/Module/Admin/Addons/Details.php:48
msgid "Addon not found."
msgstr ""
-#: src/Module/Admin/Addons/Details.php:60 src/Module/Admin/Addons/Index.php:41
+#: src/Module/Admin/Addons/Details.php:59 src/Module/Admin/Addons/Index.php:43
#, php-format
msgid "Addon %s disabled."
msgstr ""
-#: src/Module/Admin/Addons/Details.php:63 src/Module/Admin/Addons/Index.php:43
+#: src/Module/Admin/Addons/Details.php:62 src/Module/Admin/Addons/Index.php:45
#, php-format
msgid "Addon %s enabled."
msgstr ""
-#: src/Module/Admin/Addons/Details.php:72
+#: src/Module/Admin/Addons/Details.php:71
#: src/Module/Admin/Themes/Details.php:38
msgid "Disable"
msgstr ""
-#: src/Module/Admin/Addons/Details.php:75
+#: src/Module/Admin/Addons/Details.php:74
#: src/Module/Admin/Themes/Details.php:41 src/Module/Settings/Display.php:341
msgid "Enable"
msgstr ""
-#: src/Module/Admin/Addons/Details.php:95 src/Module/Admin/Addons/Index.php:59
+#: src/Module/Admin/Addons/Details.php:98 src/Module/Admin/Addons/Index.php:77
#: src/Module/Admin/Federation.php:213 src/Module/Admin/Logs/Settings.php:74
#: src/Module/Admin/Logs/View.php:71 src/Module/Admin/Queue.php:59
#: src/Module/Admin/Site.php:446 src/Module/Admin/Storage.php:124
-#: src/Module/Admin/Summary.php:173 src/Module/Admin/Themes/Details.php:82
+#: src/Module/Admin/Summary.php:183 src/Module/Admin/Themes/Details.php:82
#: src/Module/Admin/Themes/Index.php:103 src/Module/Admin/Tos.php:63
#: src/Module/Moderation/Users/Create.php:47
#: src/Module/Moderation/Users/Pending.php:82
msgid "Administration"
msgstr ""
-#: src/Module/Admin/Addons/Details.php:96 src/Module/Admin/Addons/Index.php:60
-#: src/Module/BaseAdmin.php:78 src/Module/BaseSettings.php:127
+#: src/Module/Admin/Addons/Details.php:99 src/Module/Admin/Addons/Index.php:78
+#: src/Module/BaseAdmin.php:77 src/Module/BaseSettings.php:127
msgid "Addons"
msgstr ""
-#: src/Module/Admin/Addons/Details.php:97
+#: src/Module/Admin/Addons/Details.php:100
#: src/Module/Admin/Themes/Details.php:84
msgid "Toggle"
msgstr ""
-#: src/Module/Admin/Addons/Details.php:104
+#: src/Module/Admin/Addons/Details.php:113
#: src/Module/Admin/Themes/Details.php:92
msgid "Author: "
msgstr ""
-#: src/Module/Admin/Addons/Details.php:105
+#: src/Module/Admin/Addons/Details.php:114
#: src/Module/Admin/Themes/Details.php:93
msgid "Maintainer: "
msgstr ""
-#: src/Module/Admin/Addons/Index.php:34
+#: src/Module/Admin/Addons/Index.php:35
msgid "Addons reloaded"
msgstr ""
-#: src/Module/Admin/Addons/Index.php:45
+#: src/Module/Admin/Addons/Index.php:47
#, php-format
msgid "Addon %s failed to install."
msgstr ""
-#: src/Module/Admin/Addons/Index.php:61 src/Module/Admin/Features.php:69
+#: src/Module/Admin/Addons/Index.php:79 src/Module/Admin/Features.php:69
#: src/Module/Admin/Logs/Settings.php:76 src/Module/Admin/Site.php:449
#: src/Module/Admin/Themes/Index.php:105 src/Module/Admin/Tos.php:72
#: src/Module/Settings/Account.php:507 src/Module/Settings/Addons.php:64
@@ -3936,11 +3936,11 @@ msgstr ""
msgid "Save Settings"
msgstr ""
-#: src/Module/Admin/Addons/Index.php:62
+#: src/Module/Admin/Addons/Index.php:80
msgid "Reload active addons"
msgstr ""
-#: src/Module/Admin/Addons/Index.php:66
+#: src/Module/Admin/Addons/Index.php:84
#, php-format
msgid "There are currently no addons available on your node. You can find the official addon repository at %1$s."
msgstr ""
@@ -4088,7 +4088,7 @@ msgstr[1] ""
msgid "This page offers you some numbers to the known part of the federated social network your Friendica node is part of. These numbers are not complete but only reflect the part of the network your node is aware of."
msgstr ""
-#: src/Module/Admin/Federation.php:214 src/Module/BaseAdmin.php:73
+#: src/Module/Admin/Federation.php:214 src/Module/BaseAdmin.php:72
msgid "Federation Statistics"
msgstr ""
@@ -4112,8 +4112,8 @@ msgstr ""
msgid "PHP log currently disabled."
msgstr ""
-#: src/Module/Admin/Logs/Settings.php:75 src/Module/BaseAdmin.php:88
-#: src/Module/BaseAdmin.php:89
+#: src/Module/Admin/Logs/Settings.php:75 src/Module/BaseAdmin.php:87
+#: src/Module/BaseAdmin.php:88
msgid "Logs"
msgstr ""
@@ -4161,7 +4161,7 @@ msgstr ""
msgid "Couldn't open %1$s log file.
Check to see if file %1$s is readable."
msgstr ""
-#: src/Module/Admin/Logs/View.php:72 src/Module/BaseAdmin.php:90
+#: src/Module/Admin/Logs/View.php:72 src/Module/BaseAdmin.php:89
msgid "View Logs"
msgstr ""
@@ -4351,7 +4351,7 @@ msgstr ""
msgid "Interactors"
msgstr ""
-#: src/Module/Admin/Site.php:447 src/Module/BaseAdmin.php:76
+#: src/Module/Admin/Site.php:447 src/Module/BaseAdmin.php:75
msgid "Site"
msgstr ""
@@ -5324,7 +5324,7 @@ msgstr ""
msgid "Storage Configuration"
msgstr ""
-#: src/Module/Admin/Storage.php:127 src/Module/BaseAdmin.php:77
+#: src/Module/Admin/Storage.php:127 src/Module/BaseAdmin.php:76
msgid "Storage"
msgstr ""
@@ -5408,39 +5408,39 @@ msgstr ""
msgid "Friendica's configuration now is stored in config/local.config.php, please copy config/local-sample.config.php and move your config from config/local.ini.php
. See the Config help page for help with the transition."
msgstr ""
-#: src/Module/Admin/Summary.php:104
+#: src/Module/Admin/Summary.php:105
#, php-format
msgid "%s is not reachable on your system. This is a severe configuration issue that prevents server to server communication. See the installation page for help."
msgstr ""
-#: src/Module/Admin/Summary.php:128
+#: src/Module/Admin/Summary.php:133
#, php-format
msgid "Friendica's system.basepath was updated from '%s' to '%s'. Please remove the system.basepath from your db to avoid differences."
msgstr ""
-#: src/Module/Admin/Summary.php:136
+#: src/Module/Admin/Summary.php:143
#, php-format
msgid "Friendica's current system.basepath '%s' is wrong and the config file '%s' isn't used."
msgstr ""
-#: src/Module/Admin/Summary.php:144
+#: src/Module/Admin/Summary.php:153
#, php-format
msgid "Friendica's current system.basepath '%s' is not equal to the config file '%s'. Please fix your configuration."
msgstr ""
-#: src/Module/Admin/Summary.php:155
+#: src/Module/Admin/Summary.php:165
msgid "Message queues"
msgstr ""
-#: src/Module/Admin/Summary.php:158
+#: src/Module/Admin/Summary.php:168
msgid "Server Settings"
msgstr ""
-#: src/Module/Admin/Summary.php:176
+#: src/Module/Admin/Summary.php:186
msgid "Version"
msgstr ""
-#: src/Module/Admin/Summary.php:180
+#: src/Module/Admin/Summary.php:190
msgid "Active addons"
msgstr ""
@@ -5464,7 +5464,7 @@ msgid "Screenshot"
msgstr ""
#: src/Module/Admin/Themes/Details.php:83 src/Module/Admin/Themes/Index.php:104
-#: src/Module/BaseAdmin.php:79
+#: src/Module/BaseAdmin.php:78
msgid "Themes"
msgstr ""
@@ -5581,76 +5581,76 @@ msgstr ""
msgid "Item was not found."
msgstr ""
-#: src/Module/BaseAdmin.php:40 src/Module/BaseAdmin.php:44
+#: src/Module/BaseAdmin.php:39 src/Module/BaseAdmin.php:43
#: src/Module/BaseModeration.php:66 src/Module/BaseModeration.php:70
msgid "Please login to continue."
msgstr ""
-#: src/Module/BaseAdmin.php:49
+#: src/Module/BaseAdmin.php:48
msgid "You don't have access to administration pages."
msgstr ""
-#: src/Module/BaseAdmin.php:53
+#: src/Module/BaseAdmin.php:52
msgid "Submanaged account can't access the administration pages. Please log back in as the main account."
msgstr ""
-#: src/Module/BaseAdmin.php:72 src/Module/BaseModeration.php:98
+#: src/Module/BaseAdmin.php:71 src/Module/BaseModeration.php:98
msgid "Overview"
msgstr ""
-#: src/Module/BaseAdmin.php:75 src/Module/BaseModeration.php:101
+#: src/Module/BaseAdmin.php:74 src/Module/BaseModeration.php:101
msgid "Configuration"
msgstr ""
-#: src/Module/BaseAdmin.php:80 src/Module/BaseSettings.php:98
+#: src/Module/BaseAdmin.php:79 src/Module/BaseSettings.php:98
msgid "Additional features"
msgstr ""
-#: src/Module/BaseAdmin.php:83
+#: src/Module/BaseAdmin.php:82
msgid "Database"
msgstr ""
-#: src/Module/BaseAdmin.php:84
+#: src/Module/BaseAdmin.php:83
msgid "DB updates"
msgstr ""
-#: src/Module/BaseAdmin.php:85
+#: src/Module/BaseAdmin.php:84
msgid "Inspect Deferred Workers"
msgstr ""
-#: src/Module/BaseAdmin.php:86
+#: src/Module/BaseAdmin.php:85
msgid "Inspect worker Queue"
msgstr ""
-#: src/Module/BaseAdmin.php:92 src/Module/BaseModeration.php:109
+#: src/Module/BaseAdmin.php:91 src/Module/BaseModeration.php:109
msgid "Diagnostics"
msgstr ""
-#: src/Module/BaseAdmin.php:93
+#: src/Module/BaseAdmin.php:92
msgid "PHP Info"
msgstr ""
-#: src/Module/BaseAdmin.php:94
+#: src/Module/BaseAdmin.php:93
msgid "probe address"
msgstr ""
-#: src/Module/BaseAdmin.php:95
+#: src/Module/BaseAdmin.php:94
msgid "check webfinger"
msgstr ""
-#: src/Module/BaseAdmin.php:96
+#: src/Module/BaseAdmin.php:95
msgid "Babel"
msgstr ""
-#: src/Module/BaseAdmin.php:97 src/Module/Debug/ActivityPubConversion.php:125
+#: src/Module/BaseAdmin.php:96 src/Module/Debug/ActivityPubConversion.php:125
msgid "ActivityPub Conversion"
msgstr ""
-#: src/Module/BaseAdmin.php:106
+#: src/Module/BaseAdmin.php:115
msgid "Addon Features"
msgstr ""
-#: src/Module/BaseAdmin.php:107 src/Module/BaseModeration.php:118
+#: src/Module/BaseAdmin.php:116 src/Module/BaseModeration.php:118
msgid "User registrations waiting for confirmation"
msgstr ""
@@ -7049,52 +7049,52 @@ msgstr ""
msgid "Suggest a friend for %s"
msgstr ""
-#: src/Module/Friendica.php:69
+#: src/Module/Friendica.php:71
msgid "Installed addons/apps:"
msgstr ""
-#: src/Module/Friendica.php:74
+#: src/Module/Friendica.php:76
msgid "No installed addons/apps"
msgstr ""
-#: src/Module/Friendica.php:79
+#: src/Module/Friendica.php:81
#, php-format
msgid "Read about the Terms of Service of this node."
msgstr ""
-#: src/Module/Friendica.php:86
+#: src/Module/Friendica.php:88
msgid "On this server the following remote servers are blocked."
msgstr ""
-#: src/Module/Friendica.php:89
+#: src/Module/Friendica.php:91
#: src/Module/Moderation/Blocklist/Server/Index.php:76
#: src/Module/Moderation/Blocklist/Server/Index.php:100
#: src/Module/Settings/Channels.php:218
msgid "Reason for the block"
msgstr ""
-#: src/Module/Friendica.php:91
+#: src/Module/Friendica.php:93
msgid "Download this list in CSV format"
msgstr ""
-#: src/Module/Friendica.php:105
+#: src/Module/Friendica.php:108
#, php-format
msgid "This is Friendica, version %s that is running at the web location %s. The database version is %s, the post update version is %s."
msgstr ""
-#: src/Module/Friendica.php:110
+#: src/Module/Friendica.php:114
msgid "Please visit Friendi.ca to learn more about the Friendica project."
msgstr ""
-#: src/Module/Friendica.php:111
+#: src/Module/Friendica.php:115
msgid "Bug reports and issues: please visit"
msgstr ""
-#: src/Module/Friendica.php:111
+#: src/Module/Friendica.php:115
msgid "the bugtracker at github"
msgstr ""
-#: src/Module/Friendica.php:112
+#: src/Module/Friendica.php:116
msgid "Suggestions, praise, etc. - please email \"info\" at \"friendi - dot - ca"
msgstr ""
@@ -8571,19 +8571,19 @@ msgid "No contacts."
msgstr ""
#: src/Module/Profile/Conversations.php:96 src/Module/Profile/Profile.php:342
-#: src/Protocol/Feed.php:1096
+#: src/Protocol/Feed.php:1114
#, php-format
msgid "%s's posts"
msgstr ""
#: src/Module/Profile/Conversations.php:97 src/Module/Profile/Profile.php:343
-#: src/Protocol/Feed.php:1099
+#: src/Protocol/Feed.php:1117
#, php-format
msgid "%s's comments"
msgstr ""
#: src/Module/Profile/Conversations.php:98 src/Module/Profile/Profile.php:344
-#: src/Protocol/Feed.php:1092
+#: src/Protocol/Feed.php:1110
#, php-format
msgid "%s's timeline"
msgstr ""