Merge pull request #14622 from Art4/introduce-phpmd

Introduce PHPMD
This commit is contained in:
Hypolite Petovan 2025-02-12 11:22:26 -05:00 committed by GitHub
commit 6e70ad1f0e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 1737 additions and 643 deletions

View file

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

1
.gitignore vendored
View file

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

26
.phpmd-ruleset.xml Normal file
View file

@ -0,0 +1,26 @@
<?xml version="1.0"?>
<ruleset name="Friendica Ruleset"
xmlns="http://pmd.sf.net/ruleset/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://pmd.sf.net/ruleset/1.0.0
http://pmd.sf.net/ruleset_xml_schema.xsd"
xsi:noNamespaceSchemaLocation="
http://pmd.sf.net/ruleset_xml_schema.xsd">
<description>
PHPMD ruleset for friendica code.
</description>
<rule ref="rulesets/codesize.xml/ExcessiveClassComplexity">
<priority>3</priority>
<properties>
<property name="maximum" value="800" />
</properties>
</rule>
<rule ref="rulesets/codesize.xml/CyclomaticComplexity">
<priority>3</priority>
<properties>
<property name="reportLevel" value="100" />
</properties>
</rule>
</ruleset>

View file

@ -0,0 +1,3 @@
SPDX-FileCopyrightText: 2010-2024 the Friendica project
SPDX-License-Identifier: CC0-1.0

View file

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

View file

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

892
composer.lock generated
View file

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

View file

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

View file

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

View file

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

404
src/Model/ItemHelper.php Normal file
View file

@ -0,0 +1,404 @@
<?php
// Copyright (C) 2010-2024, the Friendica project
// SPDX-FileCopyrightText: 2010-2024 the Friendica project
//
// SPDX-License-Identifier: AGPL-3.0-or-later
namespace Friendica\Model;
use Friendica\App\BaseURL;
use Friendica\Content\Item as ItemContent;
use Friendica\Core\Protocol;
use Friendica\Database\Database;
use Friendica\Protocol\Activity;
use Friendica\Util\DateTimeFormat;
use Psr\Log\LoggerInterface;
/**
* A helper class for handling an Item Model
*
* @internal ONLY for use in Friendica\Content\Item class
*
* @see Item::insert()
*/
final class ItemHelper
{
private ItemContent $itemContent;
private Activity $activity;
private LoggerInterface $logger;
private Database $database;
private string $baseUrl;
public function __construct(
ItemContent $itemContent,
Activity $activity,
LoggerInterface $logger,
Database $database,
BaseURL $baseURL
) {
$this->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
}
}

View file

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

View file

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

File diff suppressed because it is too large Load diff