mirror of
https://git.friendi.ca/friendica/friendica.git
synced 2025-06-07 23:34:27 +02:00
Merge branch 'develop' into new-changelog
This commit is contained in:
commit
4584510e76
228 changed files with 27170 additions and 23425 deletions
|
@ -19,7 +19,7 @@ cd $DocumentRoot
|
|||
# copy the .htaccess-dist file to .htaccess so that rewrite rules work
|
||||
cp $DocumentRoot/.htaccess-dist $DocumentRoot/.htaccess
|
||||
|
||||
bin/composer.phar --no-dev install
|
||||
bin/composer.phar install
|
||||
|
||||
# install friendica
|
||||
bin/console autoinstall -f /tmp/autoinstall.config.php
|
||||
|
|
32
.github/workflows/code-quality.yml
vendored
32
.github/workflows/code-quality.yml
vendored
|
@ -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
|
||||
|
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -72,7 +72,6 @@ venv/
|
|||
/.idea
|
||||
|
||||
#ignore addons directory
|
||||
/addons
|
||||
/addon
|
||||
|
||||
#ignore base .htaccess
|
||||
|
@ -90,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
26
.phpmd-ruleset.xml
Normal 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>
|
3
.phpmd-ruleset.xml.license
Normal file
3
.phpmd-ruleset.xml.license
Normal file
|
@ -0,0 +1,3 @@
|
|||
SPDX-FileCopyrightText: 2010-2024 the Friendica project
|
||||
|
||||
SPDX-License-Identifier: CC0-1.0
|
46
.woodpecker/.phpmd_check.yml
Normal file
46
.woodpecker/.phpmd_check.yml
Normal 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
|
|
@ -1,14 +1,14 @@
|
|||
#!/bin/bash
|
||||
#!/bin/sh
|
||||
|
||||
# SPDX-FileCopyrightText: 2010 - 2024 the Friendica project
|
||||
#
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
dir=$(cd "${0%[/\\]*}" > /dev/null; pwd)
|
||||
dir=$(cd "$(dirname "$0")" > /dev/null 2>&1; pwd)
|
||||
|
||||
if [[ -d /proc/cygdrive && $(which php) == $(readlink -n /proc/cygdrive)/* ]]; then
|
||||
if [ -d /proc/cygdrive ] && [ "$(which php)" = "$(readlink -n /proc/cygdrive)/*" ]; then
|
||||
# We are in Cygwin using Windows php, so the path must be translated
|
||||
dir=$(cygpath -m "$dir");
|
||||
dir=$(cygpath -m "$dir")
|
||||
fi
|
||||
|
||||
php "${dir}/console.php" "$@"
|
||||
|
|
|
@ -24,7 +24,7 @@ chdir(dirname(__DIR__));
|
|||
|
||||
require dirname(__DIR__) . '/vendor/autoload.php';
|
||||
|
||||
fwrite(STDOUT, '`bin/daemon.php` is deprecated since 2024.02 and will be removed in 5 months, please use `bin/console.php daemon` instead.' . \PHP_EOL);
|
||||
fwrite(STDOUT, '`bin/daemon.php` is deprecated since 2025.02 and will be removed in 5 months, please use `bin/console.php daemon` instead.' . \PHP_EOL);
|
||||
|
||||
// BC: Add console command as second argument
|
||||
$argv = $_SERVER['argv'] ?? [];
|
||||
|
|
|
@ -19,7 +19,7 @@ chdir(dirname(__DIR__));
|
|||
|
||||
require dirname(__DIR__) . '/vendor/autoload.php';
|
||||
|
||||
fwrite(STDOUT, '`bin/jetstream.php` is deprecated since 2024.02 and will be removed in 5 months, please use `bin/console.php jetstream` instead.' . \PHP_EOL);
|
||||
fwrite(STDOUT, '`bin/jetstream.php` is deprecated since 2025.02 and will be removed in 5 months, please use `bin/console.php jetstream` instead.' . \PHP_EOL);
|
||||
|
||||
// BC: Add console command as second argument
|
||||
$argv = $_SERVER['argv'] ?? [];
|
||||
|
|
|
@ -1,13 +1,20 @@
|
|||
#!/bin/bash
|
||||
#!/bin/sh
|
||||
|
||||
# SPDX-FileCopyrightText: 2010 - 2024 the Friendica project
|
||||
#
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
set -eo pipefail
|
||||
set -e
|
||||
|
||||
function resolve {
|
||||
if [ "$(uname)" == "Darwin" ]
|
||||
# Custom function to handle pipefail behavior
|
||||
pipefail() {
|
||||
local cmd="$1"
|
||||
shift
|
||||
{ eval "$cmd"; } || exit 1
|
||||
}
|
||||
|
||||
resolve() {
|
||||
if [ "$(uname)" = "Darwin" ]
|
||||
then
|
||||
realpath "$1"
|
||||
else
|
||||
|
@ -17,7 +24,7 @@ function resolve {
|
|||
|
||||
FULLPATH=$(dirname "$(resolve "$0")")
|
||||
|
||||
if [ "$1" == "--help" ] || [ "$1" == "-h" ]
|
||||
if [ "$1" = "--help" ] || [ "$1" = "-h" ]
|
||||
then
|
||||
echo "$(basename "$(resolve "$0")") [options]"
|
||||
echo
|
||||
|
@ -28,15 +35,15 @@ fi
|
|||
|
||||
MODE='default'
|
||||
ADDONNAME=
|
||||
if [ "$1" == "--addon" ] || [ "$1" == "-a" ]
|
||||
if [ "$1" = "--addon" ] || [ "$1" = "-a" ]
|
||||
then
|
||||
MODE='addon'
|
||||
if [ -z "$2" ]; then echo -e "ERROR: missing addon name\n\nrun_xgettext.sh -a <addonname>"; exit 1; fi
|
||||
if [ -z "$2" ]; then echo "ERROR: missing addon name\n\nrun_xgettext.sh -a <addonname>"; exit 1; fi
|
||||
ADDONNAME=$2
|
||||
if [ ! -d "$FULLPATH/../addon/$ADDONNAME" ]; then echo "ERROR: addon '$ADDONNAME' not found"; exit 2; fi
|
||||
fi
|
||||
|
||||
if [ "$1" == "--single" ] || [ "$1" == "-s" ]
|
||||
if [ "$1" = "--single" ] || [ "$1" = "-s" ]
|
||||
then
|
||||
MODE='single'
|
||||
fi
|
||||
|
@ -70,7 +77,6 @@ case "$MODE" in
|
|||
;;
|
||||
esac
|
||||
|
||||
|
||||
KEYWORDS="-k -kt -ktt:1,2"
|
||||
|
||||
echo "Extract strings to $OUTFILE.."
|
||||
|
@ -79,13 +85,13 @@ echo "Extract strings to $OUTFILE.."
|
|||
# shellcheck disable=SC2086 # $FINDOPTS is meant to be split
|
||||
find_result=$(find "$FINDSTARTDIR" $FINDOPTS -name "*.php" -type f | LC_ALL=C sort -s)
|
||||
|
||||
total_files=$(wc -l <<< "${find_result}")
|
||||
total_files=$(echo "${find_result}" | wc -l)
|
||||
|
||||
count=1
|
||||
for file in $find_result
|
||||
do
|
||||
echo -ne " \r"
|
||||
echo -ne "Reading file $count/$total_files..."
|
||||
printf " \r"
|
||||
printf "Reading file %d/%d..." "$count" "$total_files"
|
||||
|
||||
# On Windows, find still outputs the name of pruned folders
|
||||
if [ ! -d "$file" ]
|
||||
|
@ -94,9 +100,8 @@ do
|
|||
xgettext $KEYWORDS --no-wrap -j -o "$OUTFILE" --from-code=UTF-8 "$file" || exit 1
|
||||
sed -i.bkp "s/CHARSET/UTF-8/g" "$OUTFILE"
|
||||
fi
|
||||
(( count++ ))
|
||||
count=$((count + 1))
|
||||
done
|
||||
echo -ne "\n"
|
||||
|
||||
echo "Interpolate metadata.."
|
||||
|
||||
|
@ -119,7 +124,7 @@ case "$MODE" in
|
|||
;;
|
||||
esac
|
||||
|
||||
if [ "" != "$1" ] && [ "$MODE" == "default" ]
|
||||
if [ -n "$1" ] && [ "$MODE" = "default" ]
|
||||
then
|
||||
UPDATEFILE="$(resolve "${FULLPATH}/$1")"
|
||||
echo "Merging new strings to $UPDATEFILE.."
|
||||
|
|
|
@ -21,7 +21,7 @@ chdir(dirname(__DIR__));
|
|||
|
||||
require dirname(__DIR__) . '/vendor/autoload.php';
|
||||
|
||||
fwrite(STDOUT, '`bin/worker.php` is deprecated since 2024.02 and will be removed in 5 months, please use `bin/console.php worker` instead.' . \PHP_EOL);
|
||||
fwrite(STDOUT, '`bin/worker.php` is deprecated since 2025.02 and will be removed in 5 months, please use `bin/console.php worker` instead.' . \PHP_EOL);
|
||||
|
||||
// BC: Add console command as second argument
|
||||
$argv = $_SERVER['argv'] ?? [];
|
||||
|
|
|
@ -70,10 +70,12 @@
|
|||
"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",
|
||||
"smarty/smarty": "^4",
|
||||
"symfony/event-dispatcher": "^5.4",
|
||||
"textalk/websocket": "^1.6",
|
||||
"ua-parser/uap-php": "^3.9",
|
||||
"xemlock/htmlpurifier-html5": "^0.1.11"
|
||||
|
@ -114,7 +116,6 @@
|
|||
},
|
||||
"sort-packages": true,
|
||||
"autoloader-suffix": "Friendica",
|
||||
"optimize-autoloader": true,
|
||||
"preferred-install": "dist",
|
||||
"allow-plugins": {
|
||||
"composer/installers": true,
|
||||
|
@ -153,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",
|
||||
|
@ -172,6 +175,8 @@
|
|||
"@cs:install",
|
||||
"bin/dev/php-cs-fixer/vendor/bin/php-cs-fixer fix"
|
||||
],
|
||||
"cs:fix-develop": "TARGET_BRANCH=develop COMMAND=fix bin/dev/fix-codestyle.sh"
|
||||
"cs:fix-develop": "TARGET_BRANCH=develop COMMAND=fix bin/dev/fix-codestyle.sh",
|
||||
"db:update-structure": "bin/console.php dbstructure dumpsql > database.sql",
|
||||
"install:prod": "@composer install -o --no-dev"
|
||||
}
|
||||
}
|
||||
|
|
1186
composer.lock
generated
1186
composer.lock
generated
File diff suppressed because it is too large
Load diff
12
database.sql
12
database.sql
|
@ -1,6 +1,6 @@
|
|||
-- ------------------------------------------
|
||||
-- Friendica 2025.02-dev (Interrupted Fern)
|
||||
-- DB_UPDATE_VERSION 1576
|
||||
-- DB_UPDATE_VERSION 1579
|
||||
-- ------------------------------------------
|
||||
|
||||
|
||||
|
@ -41,10 +41,13 @@ CREATE TABLE IF NOT EXISTS `gserver` (
|
|||
`blocked` boolean COMMENT 'Server is blocked',
|
||||
`failed` boolean COMMENT 'Connection failed',
|
||||
`next_contact` datetime DEFAULT '0001-01-01 00:00:00' COMMENT 'Next connection request',
|
||||
`redirect-gsid` int unsigned COMMENT 'Target Gserver id in case of a redirect',
|
||||
PRIMARY KEY(`id`),
|
||||
UNIQUE INDEX `nurl` (`nurl`(190)),
|
||||
INDEX `next_contact` (`next_contact`),
|
||||
INDEX `network` (`network`)
|
||||
INDEX `network` (`network`),
|
||||
INDEX `redirect-gsid` (`redirect-gsid`),
|
||||
FOREIGN KEY (`redirect-gsid`) REFERENCES `gserver` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE
|
||||
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Global servers';
|
||||
|
||||
--
|
||||
|
@ -1609,7 +1612,7 @@ CREATE TABLE IF NOT EXISTS `post-user` (
|
|||
`psid` int unsigned COMMENT 'ID of the permission set of this post',
|
||||
PRIMARY KEY(`id`),
|
||||
UNIQUE INDEX `uid_uri-id` (`uid`,`uri-id`),
|
||||
INDEX `uri-id` (`uri-id`),
|
||||
INDEX `uri-id_origin_deleted` (`uri-id`,`origin`,`deleted`),
|
||||
INDEX `parent-uri-id` (`parent-uri-id`),
|
||||
INDEX `thr-parent-id` (`thr-parent-id`),
|
||||
INDEX `external-id` (`external-id`),
|
||||
|
@ -3809,7 +3812,8 @@ CREATE VIEW `pending-view` AS SELECT
|
|||
`contact`.`nick` AS `nick`
|
||||
FROM `register`
|
||||
INNER JOIN `contact` ON `register`.`uid` = `contact`.`uid`
|
||||
INNER JOIN `user` ON `register`.`uid` = `user`.`uid`;
|
||||
INNER JOIN `user` ON `register`.`uid` = `user`.`uid`
|
||||
WHERE `register`.`uid` != 0;
|
||||
|
||||
--
|
||||
-- VIEW tag-search-view
|
||||
|
|
|
@ -62,7 +62,7 @@ If you want to have git automatically update the dependencies with composer, you
|
|||
}
|
||||
# `composer install` if the `composer.lock` file gets changed
|
||||
# to update all the php dependencies
|
||||
check_run composer.lock "bin/composer.phar install --no-dev"
|
||||
check_run composer.lock "bin/composer.phar install"
|
||||
|
||||
just place it into `.git/hooks/post-merge` and make it executable.
|
||||
|
||||
|
@ -210,9 +210,9 @@ If the deprecated code is no longer used inside Friendica or the official addons
|
|||
The code MUST NOT be deleted.
|
||||
Starting from the next release, it MUST be stay for at least 5 months.
|
||||
Hard deprecated code COULD remain longer than 5 months, depending on when a release appears.
|
||||
Addon developer MUST NOT consider that they have more than 5 months to adjust their code.
|
||||
Addon developer SHOULD NOT consider that they have more than 5 months to adjust their code.
|
||||
|
||||
Hard deprecation code means that the code triggers an `E_USER_DEPRECATION` error if it is called.
|
||||
Hard deprecation code means that the code triggers a muted `E_USER_DEPRECATION` error if it is called.
|
||||
For instance with the deprecated class `Friendica\Core\Logger` the call of every method should trigger an error:
|
||||
|
||||
```php
|
||||
|
@ -224,7 +224,7 @@ For instance with the deprecated class `Friendica\Core\Logger` the call of every
|
|||
class Logger {
|
||||
public static function info(string $message, array $context = [])
|
||||
{
|
||||
trigger_error('Class `' . __CLASS__ . '` is deprecated since 2025.05 and will be removed after 5 months, use constructor injection or `DI::logger()` instead.', E_USER_DEPRECATED);
|
||||
@trigger_error('Class `' . __CLASS__ . '` is deprecated since 2025.05 and will be removed after 5 months, use constructor injection or `DI::logger()` instead.', E_USER_DEPRECATED);
|
||||
|
||||
self::getInstance()->info($message, $context);
|
||||
}
|
||||
|
@ -233,7 +233,7 @@ class Logger {
|
|||
}
|
||||
```
|
||||
|
||||
This way the maintainer or users of addons will be notified that the addon will stop working in one of the next releases.
|
||||
This way the maintainer or users of addons will be notified in the logs that the addon will stop working in one of the next releases.
|
||||
The addon maintainer now has at least 5 months or at least one release to fix the deprecations.
|
||||
|
||||
Please note that the deprecation message contains the release that will be released next.
|
||||
|
|
11
doc/FAQ.md
11
doc/FAQ.md
|
@ -177,15 +177,18 @@ Example: Friendica Support
|
|||
Friendica supports [Mastodon API](help/API-Mastodon) and [Twitter API | gnusocial](help/api).
|
||||
This means you can use some of the Mastodon and Twitter clients for Friendica.
|
||||
The available features are client specific and may differ.
|
||||
Clients dedicated to Friendica are marked in **bold**.
|
||||
|
||||
#### Android
|
||||
|
||||
* [AndStatus](http://andstatus.org) ([F-Droid](https://f-droid.org/repository/browse/?fdid=org.andstatus.app), [Google Play](https://play.google.com/store/apps/details?id=org.andstatus.app))
|
||||
* [Fedilab](https://fedilab.app) ([F-Droid](https://f-droid.org/app/fr.gouv.etalab.mastodon), [Google Play](https://play.google.com/store/apps/details?id=app.fedilab.android))
|
||||
* [Friendiqa](https://git.friendi.ca/lubuwest/Friendiqa) ([F-Droid](https://git.friendi.ca/lubuwest/Friendiqa#install), [Google Play](https://play.google.com/store/apps/details?id=org.qtproject.friendiqa))
|
||||
* **[Friendiqa](https://git.friendi.ca/lubuwest/Friendiqa)** ([F-Droid](https://git.friendi.ca/lubuwest/Friendiqa#install), [Google Play](https://play.google.com/store/apps/details?id=org.qtproject.friendiqa))
|
||||
* [Husky](https://codeberg.org/husky/husky) ([F-Droid](https://f-droid.org/repository/browse/?fdid=su.xash.husky), [Google Play](https://play.google.com/store/apps/details?id=su.xash.husky))
|
||||
* [Mastodon](https://github.com/mastodon/mastodon-android) ([F-Droid](https://f-droid.org/en/packages/org.joinmastodon.android/), [Google Play](https://play.google.com/store/apps/details?id=org.joinmastodon.android))
|
||||
* [Pachli](https://pachli.app/) ([F-Droid](https://f-droid.org/en/packages/app.pachli/), [Google Play](https://play.google.com/store/apps/details?id=app.pachli))
|
||||
* **[Raccoon for Friendica](https://github.com/LiveFastEatTrashRaccoon/RaccoonForFriendica)** ([F-Droid](https://f-droid.org/packages/com.livefast.eattrash.raccoonforfriendica), [Google Play](https://play.google.com/apps/testing/com.livefast.eattrash.raccoonforfriendica))
|
||||
* **[Relatica](https://gitlab.com/mysocialportal/relatica)**
|
||||
* [Subway Tooter](https://github.com/tateisu/SubwayTooter) ([F-Droid via Izzy](https://android.izzysoft.de/repo/apk/jp.juggler.subwaytooter.noFcm))
|
||||
* [Tooot](https://tooot.app/) ([Google Play](https://play.google.com/store/apps/details?id=com.xmflsct.app.tooot))
|
||||
* [Tusky](https://tusky.app) ([F-Droid](https://f-droid.org/repository/browse/?fdid=com.keylesspalace.tusky), [Google Play](https://play.google.com/store/apps/details?id=com.keylesspalace.tusky))
|
||||
|
@ -195,6 +198,7 @@ The available features are client specific and may differ.
|
|||
#### iOS
|
||||
|
||||
* [Mastodon](https://joinmastodon.org/apps) ([App Store](https://apps.apple.com/us/app/mastodon-for-iphone/id1571998974))
|
||||
* **[Relatica](https://gitlab.com/mysocialportal/relatica)**
|
||||
* [Stella*](https://www.stella-app.net/) ([App Store](https://apps.apple.com/us/app/stella-for-mastodon-twitter/id921372048))
|
||||
* [Tooot](https://github.com/tooot-app) ([App Store](https://apps.apple.com/app/id1549772269))
|
||||
* [TwidereX](https://github.com/TwidereProject/TwidereX-iOS) ([App Store](https://apps.apple.com/app/twidere-x/id1530314034))
|
||||
|
@ -202,17 +206,20 @@ The available features are client specific and may differ.
|
|||
#### Linux
|
||||
|
||||
* [Choqok](https://choqok.kde.org)
|
||||
* [Whalebird](https://whalebird.social/en/desktop/contents) ([GitHub](https://github.com/h3poteto/whalebird-desktop))
|
||||
* **[Relatica](https://gitlab.com/mysocialportal/relatica)**
|
||||
* [TheDesk](https://thedesk.top/en/) ([GitHub](https://github.com/cutls/TheDesk))
|
||||
* [Toot](https://toot.readthedocs.io/en/latest/)
|
||||
* [Whalebird](https://whalebird.social/en/desktop/contents) ([GitHub](https://github.com/h3poteto/whalebird-desktop))
|
||||
|
||||
#### macOS
|
||||
|
||||
* **[Relatica](https://gitlab.com/mysocialportal/relatica)**
|
||||
* [TheDesk](https://thedesk.top/en/) ([GitHub](https://github.com/cutls/TheDesk))
|
||||
* [Whalebird](https://whalebird.social/en/desktop/contents) ([App Store](https://apps.apple.com/de/app/whalebird/id1378283354), [GitHub](https://github.com/h3poteto/whalebird-desktop))
|
||||
|
||||
#### Windows
|
||||
|
||||
* **[Relatica](https://gitlab.com/mysocialportal/relatica)**
|
||||
* [TheDesk](https://thedesk.top/en/) ([GitHub](https://github.com/cutls/TheDesk))
|
||||
* [Whalebird](https://whalebird.social/en/desktop/contents) ([Microsoft Store](https://apps.microsoft.com/detail/9nbw4csdv5hc), [GitHub](https://github.com/h3poteto/whalebird-desktop))
|
||||
|
||||
|
|
|
@ -76,14 +76,6 @@ This makes the software much easier to update.
|
|||
The Linux commands to clone the repository into a directory "mywebsite" would be
|
||||
|
||||
git clone https://github.com/friendica/friendica.git -b stable mywebsite
|
||||
cd mywebsite
|
||||
bin/composer.phar install --no-dev
|
||||
|
||||
Make sure the folder *view/smarty3* exists and is writable by the webserver user, in this case *www-data*
|
||||
|
||||
mkdir -p view/smarty3
|
||||
chown www-data:www-data view/smarty3
|
||||
chmod 775 view/smarty3
|
||||
|
||||
Get the addons by going into your website folder.
|
||||
|
||||
|
@ -93,10 +85,20 @@ Clone the addon repository (separately):
|
|||
|
||||
git clone https://github.com/friendica/friendica-addons.git -b stable addon
|
||||
|
||||
Install the dependencies:
|
||||
|
||||
bin/composer.phar run install:prod
|
||||
|
||||
Make sure the folder *view/smarty3* exists and is writable by the webserver user, in this case *www-data*
|
||||
|
||||
mkdir -p view/smarty3
|
||||
chown www-data:www-data view/smarty3
|
||||
chmod 775 view/smarty3
|
||||
|
||||
If you want to use the development version of Friendica you can switch to the develop branch in the repository by running
|
||||
|
||||
git checkout develop
|
||||
bin/composer.phar install
|
||||
bin/composer.phar run install:prod
|
||||
cd addon
|
||||
git checkout develop
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@ You can get the latest changes at any time with
|
|||
|
||||
cd path/to/friendica
|
||||
git pull
|
||||
bin/composer.phar install --no-dev
|
||||
bin/composer.phar run install:prod
|
||||
|
||||
The addon tree has to be updated separately like so:
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ What you need to do:
|
|||
Please use an up-to-date vagrant version from https://www.vagrantup.com/downloads.html.
|
||||
2. Git clone your Friendica repository.
|
||||
Inside, you'll find a `Vagrantfile` and some scripts in the `bin/dev` folder.
|
||||
Pull the PHP requirements with `bin/composer install`.
|
||||
Pull the PHP requirements with `bin/composer.phar install`.
|
||||
3. Run `vagrant up` from inside the friendica clone.
|
||||
This will start the virtual machine.
|
||||
Be patient: When it runs for the first time, it downloads a Debian Server image and installs Friendica.
|
||||
|
|
|
@ -41,16 +41,24 @@ Fields
|
|||
| blocked | Server is blocked | boolean | YES | | NULL | |
|
||||
| failed | Connection failed | boolean | YES | | NULL | |
|
||||
| next_contact | Next connection request | datetime | YES | | 0001-01-01 00:00:00 | |
|
||||
| redirect-gsid | Target Gserver id in case of a redirect | int unsigned | YES | | NULL | |
|
||||
|
||||
Indexes
|
||||
------------
|
||||
|
||||
| Name | Fields |
|
||||
| ------------ | ----------------- |
|
||||
| ------------- | ----------------- |
|
||||
| PRIMARY | id |
|
||||
| nurl | UNIQUE, nurl(190) |
|
||||
| next_contact | next_contact |
|
||||
| network | network |
|
||||
| redirect-gsid | redirect-gsid |
|
||||
|
||||
Foreign Keys
|
||||
------------
|
||||
|
||||
| Field | Target Table | Target Field |
|
||||
|-------|--------------|--------------|
|
||||
| redirect-gsid | [gserver](help/database/db_gserver) | id |
|
||||
|
||||
Return to [database documentation](help/database)
|
||||
|
|
|
@ -45,10 +45,10 @@ Indexes
|
|||
------------
|
||||
|
||||
| Name | Fields |
|
||||
| -------------------- | ----------------------- |
|
||||
| --------------------- | ----------------------- |
|
||||
| PRIMARY | id |
|
||||
| uid_uri-id | UNIQUE, uid, uri-id |
|
||||
| uri-id | uri-id |
|
||||
| uri-id_origin_deleted | uri-id, origin, deleted |
|
||||
| parent-uri-id | parent-uri-id |
|
||||
| thr-parent-id | thr-parent-id |
|
||||
| external-id | external-id |
|
||||
|
|
|
@ -59,7 +59,7 @@ Der Linux-Code, mit dem man die Dateien direkt in ein Verzeichnis wie "meinewebs
|
|||
|
||||
git clone https://github.com/friendica/friendica.git -b stable mywebsite
|
||||
cd mywebsite
|
||||
bin/composer.phar install
|
||||
bin/composer.phar run install:prod
|
||||
|
||||
Stelle sicher, dass der Ordner *view/smarty3* existiert and von dem Webserver-Benutzer beschreibbar ist
|
||||
|
||||
|
@ -85,7 +85,7 @@ Wenn du die Entwickler Version von Friendica verwenden möchtest kannst du auf d
|
|||
Dies tust du mit den folgenden Befehlen
|
||||
|
||||
git checkout develop
|
||||
bin/composer.phar install
|
||||
bin/composer.phar run install:prod
|
||||
cd addon
|
||||
git checkout develop
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
$start_time = microtime(true);
|
||||
|
||||
if (!file_exists(__DIR__ . '/vendor/autoload.php')) {
|
||||
die('Vendor path not found. Please execute "bin/composer.phar --no-dev install" on the command line in the web root.');
|
||||
die('Vendor path not found. Please execute "bin/composer.phar run install:prod" on the command line in the web root.');
|
||||
}
|
||||
|
||||
require __DIR__ . '/vendor/autoload.php';
|
||||
|
|
|
@ -11,7 +11,6 @@ use Friendica\Content\Nav;
|
|||
use Friendica\Content\Pager;
|
||||
use Friendica\Content\Text\BBCode;
|
||||
use Friendica\Core\ACL;
|
||||
use Friendica\Core\Addon;
|
||||
use Friendica\Core\Hook;
|
||||
use Friendica\Core\Renderer;
|
||||
use Friendica\Core\System;
|
||||
|
@ -1067,6 +1066,7 @@ function photos_content()
|
|||
$cmnt_tpl = Renderer::getMarkupTemplate('comment_item.tpl');
|
||||
$tpl = Renderer::getMarkupTemplate('photo_item.tpl');
|
||||
$return_path = DI::args()->getCommand();
|
||||
$addonHelper = DI::addonHelper();
|
||||
|
||||
if (!DBA::isResult($items)) {
|
||||
if (($can_post || Security::canWriteToUserWall($owner_uid))) {
|
||||
|
@ -1075,7 +1075,7 @@ function photos_content()
|
|||
* This should be better if done by a hook
|
||||
*/
|
||||
$qcomment = null;
|
||||
if (Addon::isEnabled('qcomment')) {
|
||||
if ($addonHelper->isAddonEnabled('qcomment')) {
|
||||
$words = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'qcomment', 'words');
|
||||
$qcomment = $words ? explode("\n", $words) : [];
|
||||
}
|
||||
|
@ -1131,7 +1131,7 @@ function photos_content()
|
|||
* This should be better if done by a hook
|
||||
*/
|
||||
$qcomment = null;
|
||||
if (Addon::isEnabled('qcomment')) {
|
||||
if ($addonHelper->isAddonEnabled('qcomment')) {
|
||||
$words = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'qcomment', 'words');
|
||||
$qcomment = $words ? explode("\n", $words) : [];
|
||||
}
|
||||
|
@ -1211,7 +1211,7 @@ function photos_content()
|
|||
* This should be better if done by a hook
|
||||
*/
|
||||
$qcomment = null;
|
||||
if (Addon::isEnabled('qcomment')) {
|
||||
if ($addonHelper->isAddonEnabled('qcomment')) {
|
||||
$words = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'qcomment', 'words');
|
||||
$qcomment = $words ? explode("\n", $words) : [];
|
||||
}
|
||||
|
|
69
src/App.php
69
src/App.php
|
@ -17,16 +17,14 @@ use Friendica\App\Router;
|
|||
use Friendica\Capabilities\ICanCreateResponses;
|
||||
use Friendica\Capabilities\ICanHandleRequests;
|
||||
use Friendica\Content\Nav;
|
||||
use Friendica\Core\Addon\AddonHelper;
|
||||
use Friendica\Core\Addon\Capability\ICanLoadAddons;
|
||||
use Friendica\Core\Config\Factory\Config;
|
||||
use Friendica\Core\Container;
|
||||
use Friendica\Core\Hooks\HookEventBridge;
|
||||
use Friendica\Core\Logger\LoggerManager;
|
||||
use Friendica\Core\Renderer;
|
||||
use Friendica\Core\Session\Capability\IHandleUserSessions;
|
||||
use Friendica\Database\Definition\DbaDefinition;
|
||||
use Friendica\Database\Definition\ViewDefinition;
|
||||
use Friendica\Module\Maintenance;
|
||||
use Friendica\Security\Authentication;
|
||||
use Friendica\Core\Config\Capability\IManageConfigValues;
|
||||
use Friendica\Core\DiceContainer;
|
||||
use Friendica\Core\L10n;
|
||||
|
@ -35,9 +33,15 @@ use Friendica\Core\Logger\Handler\ErrorHandler;
|
|||
use Friendica\Core\PConfig\Capability\IManagePersonalConfigValues;
|
||||
use Friendica\Core\System;
|
||||
use Friendica\Core\Update;
|
||||
use Friendica\Database\Definition\DbaDefinition;
|
||||
use Friendica\Database\Definition\ViewDefinition;
|
||||
use Friendica\Event\ConfigLoadedEvent;
|
||||
use Friendica\Event\Event;
|
||||
use Friendica\Module\Maintenance;
|
||||
use Friendica\Module\Special\HTTPException as ModuleHTTPException;
|
||||
use Friendica\Network\HTTPException;
|
||||
use Friendica\Protocol\ATProtocol\DID;
|
||||
use Friendica\Security\Authentication;
|
||||
use Friendica\Security\ExAuth;
|
||||
use Friendica\Security\OpenWebAuth;
|
||||
use Friendica\Util\BasePath;
|
||||
|
@ -45,6 +49,7 @@ use Friendica\Util\DateTimeFormat;
|
|||
use Friendica\Util\HTTPInputData;
|
||||
use Friendica\Util\HTTPSignature;
|
||||
use Friendica\Util\Profiler;
|
||||
use Psr\EventDispatcher\EventDispatcherInterface;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
|
@ -153,6 +158,8 @@ class App
|
|||
|
||||
$this->registerErrorHandler();
|
||||
|
||||
$this->registerEventDispatcher();
|
||||
|
||||
$this->requestId = $this->container->create(Request::class)->getRequestId();
|
||||
$this->auth = $this->container->create(Authentication::class);
|
||||
$this->config = $this->container->create(IManageConfigValues::class);
|
||||
|
@ -165,6 +172,8 @@ class App
|
|||
$this->session = $this->container->create(IHandleUserSessions::class);
|
||||
$this->appHelper = $this->container->create(AppHelper::class);
|
||||
|
||||
$addonHelper = $this->container->create(AddonHelper::class);
|
||||
|
||||
$this->load(
|
||||
$request->getServerParams(),
|
||||
$this->container->create(DbaDefinition::class),
|
||||
|
@ -172,15 +181,19 @@ class App
|
|||
$this->mode,
|
||||
$this->config,
|
||||
$this->profiler,
|
||||
$this->container->create(EventDispatcherInterface::class),
|
||||
$this->appHelper,
|
||||
$addonHelper,
|
||||
);
|
||||
|
||||
$this->registerTemplateEngine();
|
||||
|
||||
$this->runFrontend(
|
||||
$this->container->create(EventDispatcherInterface::class),
|
||||
$this->container->create(IManagePersonalConfigValues::class),
|
||||
$this->container->create(Page::class),
|
||||
$this->container->create(Nav::class),
|
||||
$addonHelper,
|
||||
$this->container->create(ModuleHTTPException::class),
|
||||
$start_time,
|
||||
$request
|
||||
|
@ -202,6 +215,8 @@ class App
|
|||
|
||||
$this->registerErrorHandler();
|
||||
|
||||
$this->registerEventDispatcher();
|
||||
|
||||
$this->load(
|
||||
$serverParams,
|
||||
$this->container->create(DbaDefinition::class),
|
||||
|
@ -209,7 +224,9 @@ class App
|
|||
$this->container->create(Mode::class),
|
||||
$this->container->create(IManageConfigValues::class),
|
||||
$this->container->create(Profiler::class),
|
||||
$this->container->create(EventDispatcherInterface::class),
|
||||
$this->container->create(AppHelper::class),
|
||||
$this->container->create(AddonHelper::class),
|
||||
);
|
||||
|
||||
$this->registerTemplateEngine();
|
||||
|
@ -230,6 +247,8 @@ class App
|
|||
|
||||
$this->registerErrorHandler();
|
||||
|
||||
$this->registerEventDispatcher();
|
||||
|
||||
$this->load(
|
||||
$serverParams,
|
||||
$this->container->create(DbaDefinition::class),
|
||||
|
@ -237,7 +256,9 @@ class App
|
|||
$this->container->create(Mode::class),
|
||||
$this->container->create(IManageConfigValues::class),
|
||||
$this->container->create(Profiler::class),
|
||||
$this->container->create(EventDispatcherInterface::class),
|
||||
$this->container->create(AppHelper::class),
|
||||
$this->container->create(AddonHelper::class),
|
||||
);
|
||||
|
||||
/** @var BasePath */
|
||||
|
@ -301,6 +322,16 @@ class App
|
|||
ErrorHandler::register($this->container->create(LoggerInterface::class));
|
||||
}
|
||||
|
||||
private function registerEventDispatcher(): void
|
||||
{
|
||||
/** @var \Friendica\Event\EventDispatcher */
|
||||
$eventDispatcher = $this->container->create(EventDispatcherInterface::class);
|
||||
|
||||
foreach (HookEventBridge::getStaticSubscribedEvents() as $eventName => $methodName) {
|
||||
$eventDispatcher->addListener($eventName, [HookEventBridge::class, $methodName]);
|
||||
}
|
||||
}
|
||||
|
||||
private function registerTemplateEngine(): void
|
||||
{
|
||||
Renderer::registerTemplateEngine('Friendica\Render\FriendicaSmartyEngine');
|
||||
|
@ -316,7 +347,9 @@ class App
|
|||
Mode $mode,
|
||||
IManageConfigValues $config,
|
||||
Profiler $profiler,
|
||||
AppHelper $appHelper
|
||||
EventDispatcherInterface $eventDispatcher,
|
||||
AppHelper $appHelper,
|
||||
AddonHelper $addonHelper
|
||||
): void {
|
||||
if ($config->get('system', 'ini_max_execution_time') !== false) {
|
||||
set_time_limit((int) $config->get('system', 'ini_max_execution_time'));
|
||||
|
@ -338,8 +371,9 @@ class App
|
|||
|
||||
if ($mode->has(Mode::DBAVAILABLE)) {
|
||||
Core\Hook::loadHooks();
|
||||
$loader = (new Config())->createConfigFileManager($appHelper->getBasePath(), $serverParams);
|
||||
Core\Hook::callAll('load_config', $loader);
|
||||
$loader = (new Config())->createConfigFileManager($appHelper->getBasePath(), $addonHelper->getAddonPath(), $serverParams);
|
||||
|
||||
$eventDispatcher->dispatch(new ConfigLoadedEvent(ConfigLoadedEvent::CONFIG_LOADED, $loader));
|
||||
|
||||
// Hooks are now working, reload the whole definitions with hook enabled
|
||||
$dbaDefinition->load(true);
|
||||
|
@ -385,9 +419,11 @@ class App
|
|||
* @throws \ImagickException
|
||||
*/
|
||||
private function runFrontend(
|
||||
EventDispatcherInterface $eventDispatcher,
|
||||
IManagePersonalConfigValues $pconfig,
|
||||
Page $page,
|
||||
Nav $nav,
|
||||
AddonHelper $addonHelper,
|
||||
ModuleHTTPException $httpException,
|
||||
float $start_time,
|
||||
ServerRequestInterface $request
|
||||
|
@ -424,7 +460,8 @@ class App
|
|||
$serverVars['REQUEST_METHOD'] === 'GET') {
|
||||
System::externalRedirect($this->baseURL . '/' . $this->args->getQueryString());
|
||||
}
|
||||
Core\Hook::callAll('init_1');
|
||||
|
||||
$eventDispatcher->dispatch(new Event(Event::INIT));
|
||||
}
|
||||
|
||||
DID::routeRequest($this->args->getCommand(), $serverVars);
|
||||
|
@ -475,12 +512,12 @@ class App
|
|||
// but we need "view" module for stylesheet
|
||||
if ($this->mode->isInstall() && $moduleName !== 'install') {
|
||||
$this->baseURL->redirect('install');
|
||||
} else {
|
||||
Core\Update::check($this->appHelper->getBasePath(), false);
|
||||
Core\Addon::loadAddons();
|
||||
Core\Hook::loadHooks();
|
||||
}
|
||||
|
||||
Core\Update::check($this->appHelper->getBasePath(), false);
|
||||
$addonHelper->loadAddons();
|
||||
Core\Hook::loadHooks();
|
||||
|
||||
// Compatibility with Hubzilla
|
||||
if ($moduleName == 'rpost') {
|
||||
$this->baseURL->redirect('compose');
|
||||
|
@ -536,6 +573,14 @@ class App
|
|||
|
||||
// Processes data from GET requests
|
||||
$httpinput = $httpInput->process();
|
||||
|
||||
if (!is_array($httpinput['variables'])) {
|
||||
$httpinput['variables'] = [];
|
||||
}
|
||||
if (!is_array($httpinput['files'])) {
|
||||
$httpinput['files'] = [];
|
||||
}
|
||||
|
||||
$input = array_merge($httpinput['variables'], $httpinput['files'], $request);
|
||||
|
||||
// Let the module run its internal process (init, get, post, ...)
|
||||
|
|
|
@ -14,7 +14,6 @@ use Friendica\App;
|
|||
use Friendica\AppHelper;
|
||||
use Friendica\Content\Nav;
|
||||
use Friendica\Core\Config\Capability\IManageConfigValues;
|
||||
use Friendica\Core\Hook;
|
||||
use Friendica\Core\L10n;
|
||||
use Friendica\Core\PConfig\Capability\IManagePersonalConfigValues;
|
||||
use Friendica\Core\Renderer;
|
||||
|
@ -22,12 +21,14 @@ use Friendica\Core\Session\Model\UserSession;
|
|||
use Friendica\Core\System;
|
||||
use Friendica\Core\Theme;
|
||||
use Friendica\DI;
|
||||
use Friendica\Event\HtmlFilterEvent;
|
||||
use Friendica\Network\HTTPException;
|
||||
use Friendica\Util\Images;
|
||||
use Friendica\Util\Network;
|
||||
use Friendica\Util\Profiler;
|
||||
use Friendica\Util\Strings;
|
||||
use GuzzleHttp\Psr7\Utils;
|
||||
use Psr\EventDispatcher\EventDispatcherInterface;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
|
@ -70,6 +71,8 @@ class Page implements ArrayAccess
|
|||
*/
|
||||
private $basePath;
|
||||
|
||||
private EventDispatcherInterface $eventDispatcher;
|
||||
|
||||
private $timestamp = 0;
|
||||
private $method = '';
|
||||
private $module = '';
|
||||
|
@ -78,10 +81,11 @@ class Page implements ArrayAccess
|
|||
/**
|
||||
* @param string $basepath The Page basepath
|
||||
*/
|
||||
public function __construct(string $basepath)
|
||||
public function __construct(string $basepath, EventDispatcherInterface $eventDispatcher)
|
||||
{
|
||||
$this->timestamp = microtime(true);
|
||||
$this->basePath = $basepath;
|
||||
$this->eventDispatcher = $eventDispatcher;
|
||||
}
|
||||
|
||||
public function setLogging(string $method, string $module, string $command)
|
||||
|
@ -192,17 +196,6 @@ class Page implements ArrayAccess
|
|||
IManagePersonalConfigValues $pConfig,
|
||||
int $localUID
|
||||
) {
|
||||
$interval = ($localUID ? $pConfig->get($localUID, 'system', 'update_interval') : 40000);
|
||||
|
||||
// If the update is 'deactivated' set it to the highest integer number (~24 days)
|
||||
if ($interval < 0) {
|
||||
$interval = 2147483647;
|
||||
}
|
||||
|
||||
if ($interval < 10000) {
|
||||
$interval = 40000;
|
||||
}
|
||||
|
||||
// Default title: current module called
|
||||
if (empty($this->page['title']) && $args->getModuleName()) {
|
||||
$this->page['title'] = ucfirst($args->getModuleName());
|
||||
|
@ -229,7 +222,9 @@ class Page implements ArrayAccess
|
|||
$touch_icon = 'images/friendica-192.png';
|
||||
}
|
||||
|
||||
Hook::callAll('head', $this->page['htmlhead']);
|
||||
$this->page['htmlhead'] = $this->eventDispatcher->dispatch(
|
||||
new HtmlFilterEvent(HtmlFilterEvent::HEAD, $this->page['htmlhead'])
|
||||
)->getHtml();
|
||||
|
||||
$tpl = Renderer::getMarkupTemplate('head.tpl');
|
||||
/* put the head template at the beginning of page['htmlhead']
|
||||
|
@ -268,7 +263,7 @@ class Page implements ArrayAccess
|
|||
|
||||
'$local_user' => $localUID,
|
||||
'$generator' => 'Friendica' . ' ' . App::VERSION,
|
||||
'$update_interval' => $interval,
|
||||
'$update_content' => (int)$pConfig->get($localUID, 'system', 'update_content'),
|
||||
'$shortcut_icon' => $shortcut_icon,
|
||||
'$touch_icon' => $touch_icon,
|
||||
'$block_public' => intval($config->get('system', 'block_public')),
|
||||
|
@ -351,11 +346,14 @@ class Page implements ArrayAccess
|
|||
]);
|
||||
}
|
||||
|
||||
Hook::callAll('footer', $this->page['footer']);
|
||||
$this->page['footer'] = $this->eventDispatcher->dispatch(
|
||||
new HtmlFilterEvent(HtmlFilterEvent::FOOTER, $this->page['footer'])
|
||||
)->getHtml();
|
||||
|
||||
$tpl = Renderer::getMarkupTemplate('footer.tpl');
|
||||
$this->page['footer'] = Renderer::replaceMacros($tpl, [
|
||||
'$footerScripts' => array_unique($this->footerScripts),
|
||||
'$close' => $l10n->t('Close'),
|
||||
]) . $this->page['footer'];
|
||||
}
|
||||
|
||||
|
@ -375,7 +373,9 @@ class Page implements ArrayAccess
|
|||
{
|
||||
// initialise content region
|
||||
if ($mode->isNormal()) {
|
||||
Hook::callAll('page_content_top', $this->page['content']);
|
||||
$this->page['content'] = $this->eventDispatcher->dispatch(
|
||||
new HtmlFilterEvent(HtmlFilterEvent::PAGE_CONTENT_TOP, $this->page['content'])
|
||||
)->getHtml();
|
||||
}
|
||||
|
||||
$this->page['content'] .= (string)$response->getBody();
|
||||
|
@ -473,7 +473,9 @@ class Page implements ArrayAccess
|
|||
$profiler->set(microtime(true) - $timestamp, 'aftermath');
|
||||
|
||||
if (!$mode->isAjax()) {
|
||||
Hook::callAll('page_end', $this->page['content']);
|
||||
$this->page['content'] = $this->eventDispatcher->dispatch(
|
||||
new HtmlFilterEvent(HtmlFilterEvent::PAGE_END, $this->page['content'])
|
||||
)->getHtml();
|
||||
}
|
||||
|
||||
// Add the navigation (menu) template
|
||||
|
|
|
@ -7,13 +7,11 @@
|
|||
|
||||
namespace Friendica\App;
|
||||
|
||||
use Dice\Dice;
|
||||
use FastRoute\DataGenerator\GroupCountBased;
|
||||
use FastRoute\Dispatcher;
|
||||
use FastRoute\RouteCollector;
|
||||
use FastRoute\RouteParser\Std;
|
||||
use Friendica\Capabilities\ICanHandleRequests;
|
||||
use Friendica\Core\Addon;
|
||||
use Friendica\Core\Addon\AddonHelper;
|
||||
use Friendica\Core\Cache\Enum\Duration;
|
||||
use Friendica\Core\Cache\Capability\ICanCache;
|
||||
use Friendica\Core\Config\Capability\IManageConfigValues;
|
||||
|
@ -86,6 +84,8 @@ class Router
|
|||
/** @var LoggerInterface */
|
||||
private $logger;
|
||||
|
||||
private AddonHelper $addonHelper;
|
||||
|
||||
/** @var bool */
|
||||
private $isLocalUser;
|
||||
|
||||
|
@ -110,7 +110,7 @@ class Router
|
|||
* @param IHandleUserSessions $userSession
|
||||
* @param RouteCollector|null $routeCollector
|
||||
*/
|
||||
public function __construct(array $server, string $baseRoutesFilepath, L10n $l10n, ICanCache $cache, ICanLock $lock, IManageConfigValues $config, Arguments $args, LoggerInterface $logger, IHandleUserSessions $userSession, RouteCollector $routeCollector = null)
|
||||
public function __construct(array $server, string $baseRoutesFilepath, L10n $l10n, ICanCache $cache, ICanLock $lock, IManageConfigValues $config, Arguments $args, LoggerInterface $logger, AddonHelper $addonHelper, IHandleUserSessions $userSession, RouteCollector $routeCollector = null)
|
||||
{
|
||||
$this->baseRoutesFilepath = $baseRoutesFilepath;
|
||||
$this->l10n = $l10n;
|
||||
|
@ -120,6 +120,7 @@ class Router
|
|||
$this->config = $config;
|
||||
$this->server = $server;
|
||||
$this->logger = $logger;
|
||||
$this->addonHelper = $addonHelper;
|
||||
$this->isLocalUser = !empty($userSession->getLocalUserId());
|
||||
|
||||
$this->routeCollector = $routeCollector ?? new RouteCollector(new Std(), new GroupCountBased());
|
||||
|
@ -293,7 +294,7 @@ class Router
|
|||
} catch (NotFoundException $e) {
|
||||
$moduleName = $this->args->getModuleName();
|
||||
// Then we try addon-provided modules that we wrap in the LegacyModule class
|
||||
if (Addon::isEnabled($moduleName) && file_exists("addon/{$moduleName}/{$moduleName}.php")) {
|
||||
if ($this->addonHelper->isAddonEnabled($moduleName) && file_exists("addon/{$moduleName}/{$moduleName}.php")) {
|
||||
//Check if module is an app and if public access to apps is allowed or not
|
||||
$privateapps = $this->config->get('config', 'private_addons', false);
|
||||
if (!$this->isLocalUser && Hook::isAddonApp($moduleName) && $privateapps) {
|
||||
|
|
|
@ -10,7 +10,7 @@ namespace Friendica\Console;
|
|||
use Console_Table;
|
||||
use Friendica\App\Mode;
|
||||
use Friendica\Core\L10n;
|
||||
use Friendica\Core\Addon as AddonCore;
|
||||
use Friendica\Core\Addon\AddonHelper;
|
||||
use Friendica\Database\Database;
|
||||
use Friendica\Util\Strings;
|
||||
use RuntimeException;
|
||||
|
@ -34,6 +34,7 @@ class Addon extends \Asika\SimpleConsole\Console
|
|||
* @var Database
|
||||
*/
|
||||
private $dba;
|
||||
private AddonHelper $addonHelper;
|
||||
|
||||
protected function getHelp()
|
||||
{
|
||||
|
@ -56,15 +57,16 @@ HELP;
|
|||
return $help;
|
||||
}
|
||||
|
||||
public function __construct(Mode $appMode, L10n $l10n, Database $dba, array $argv = null)
|
||||
public function __construct(Mode $appMode, L10n $l10n, Database $dba, AddonHelper $addonHelper, array $argv = null)
|
||||
{
|
||||
parent::__construct($argv);
|
||||
|
||||
$this->appMode = $appMode;
|
||||
$this->l10n = $l10n;
|
||||
$this->dba = $dba;
|
||||
$this->addonHelper = $addonHelper;
|
||||
|
||||
AddonCore::loadAddons();
|
||||
$this->addonHelper->loadAddons();
|
||||
}
|
||||
|
||||
protected function doExecute(): int
|
||||
|
@ -122,23 +124,22 @@ HELP;
|
|||
return false;
|
||||
}
|
||||
|
||||
foreach (AddonCore::getAvailableList() as $addon) {
|
||||
$addon_name = $addon[0];
|
||||
$enabled = AddonCore::isEnabled($addon_name);
|
||||
foreach ($this->addonHelper->getAvailableAddons() as $addonId) {
|
||||
$enabled = $this->addonHelper->isAddonEnabled($addonId);
|
||||
|
||||
if ($subCmd === 'all') {
|
||||
$table->addRow([$addon_name, $enabled ? 'enabled' : 'disabled']);
|
||||
$table->addRow([$addonId, $enabled ? 'enabled' : 'disabled']);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($subCmd === 'enabled' && $enabled === true) {
|
||||
$table->addRow([$addon_name]);
|
||||
$table->addRow([$addonId]);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($subCmd === 'disabled' && $enabled === false) {
|
||||
$table->addRow([$addon_name]);
|
||||
$table->addRow([$addonId]);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
@ -163,11 +164,11 @@ HELP;
|
|||
throw new RuntimeException($this->l10n->t('Addon not found'));
|
||||
}
|
||||
|
||||
if (AddonCore::isEnabled($addon)) {
|
||||
if ($this->addonHelper->isAddonEnabled($addon)) {
|
||||
throw new RuntimeException($this->l10n->t('Addon already enabled'));
|
||||
}
|
||||
|
||||
AddonCore::install($addon);
|
||||
$this->addonHelper->installAddon($addon);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -187,11 +188,11 @@ HELP;
|
|||
throw new RuntimeException($this->l10n->t('Addon not found'));
|
||||
}
|
||||
|
||||
if (!AddonCore::isEnabled($addon)) {
|
||||
if (!$this->addonHelper->isAddonEnabled($addon)) {
|
||||
throw new RuntimeException($this->l10n->t('Addon already disabled'));
|
||||
}
|
||||
|
||||
AddonCore::uninstall($addon);
|
||||
$this->addonHelper->uninstallAddon($addon);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ namespace Friendica\Console;
|
|||
|
||||
use Asika\SimpleConsole\Console;
|
||||
use Friendica\App\Mode;
|
||||
use Friendica\Core\Addon;
|
||||
use Friendica\Core\Addon\AddonHelper;
|
||||
use Friendica\Core\Config\Capability\IManageConfigValues;
|
||||
use Friendica\Core\Hook;
|
||||
use Friendica\Core\KeyValueStorage\Capability\IManageKeyValuePairs;
|
||||
|
@ -29,6 +29,7 @@ final class JetstreamDaemon extends Console
|
|||
private IManageKeyValuePairs $keyValue;
|
||||
private SysDaemon $daemon;
|
||||
private Jetstream $jetstream;
|
||||
private AddonHelper $addonHelper;
|
||||
|
||||
/**
|
||||
* @param Mode $mode
|
||||
|
@ -38,7 +39,7 @@ final class JetstreamDaemon extends Console
|
|||
* @param Jetstream $jetstream
|
||||
* @param array|null $argv
|
||||
*/
|
||||
public function __construct(Mode $mode, IManageConfigValues $config, IManageKeyValuePairs $keyValue, SysDaemon $daemon, Jetstream $jetstream, array $argv = null)
|
||||
public function __construct(Mode $mode, IManageConfigValues $config, IManageKeyValuePairs $keyValue, SysDaemon $daemon, Jetstream $jetstream, AddonHelper $addonHelper, array $argv = null)
|
||||
{
|
||||
parent::__construct($argv);
|
||||
|
||||
|
@ -47,6 +48,7 @@ final class JetstreamDaemon extends Console
|
|||
$this->keyValue = $keyValue;
|
||||
$this->jetstream = $jetstream;
|
||||
$this->daemon = $daemon;
|
||||
$this->addonHelper = $addonHelper;
|
||||
}
|
||||
|
||||
protected function getHelp(): string
|
||||
|
@ -95,10 +97,10 @@ HELP;
|
|||
);
|
||||
}
|
||||
|
||||
Addon::loadAddons();
|
||||
$this->addonHelper->loadAddons();
|
||||
Hook::loadHooks();
|
||||
|
||||
if (!Addon::isEnabled('bluesky')) {
|
||||
if (!$this->addonHelper->isAddonEnabled('bluesky')) {
|
||||
throw new RuntimeException("Bluesky has to be enabled.\n");
|
||||
}
|
||||
|
||||
|
|
|
@ -192,5 +192,7 @@ HELP;
|
|||
} while ($moved);
|
||||
|
||||
$this->out(sprintf(date('[Y-m-d H:i:s] ') . 'Moved %d files total', $total));
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -326,7 +326,7 @@ class Conversation
|
|||
'$ispublic' => $this->l10n->t('Visible to <strong>everybody</strong>'),
|
||||
'$linkurl' => $this->l10n->t('Please enter a image/video/audio/webpage URL:'),
|
||||
'$term' => $this->l10n->t('Tag term:'),
|
||||
'$fileas' => $this->l10n->t('Save to Folder:'),
|
||||
'$fileas' => $this->l10n->t('Save to Folder'),
|
||||
'$whereareu' => $this->l10n->t('Where are you right now?'),
|
||||
'$delitems' => $this->l10n->t("Delete item\x28s\x29?"),
|
||||
'$is_mobile' => $this->mode->isMobile(),
|
||||
|
@ -1501,7 +1501,7 @@ class Conversation
|
|||
|
||||
$body_html = ItemModel::prepareBody($item, true, $preview);
|
||||
|
||||
[$categories, $folders] = $this->item->determineCategoriesTerms($item, $this->session->getLocalUserId());
|
||||
list($categories, $folders) = $this->item->determineCategoriesTerms($item, $this->session->getLocalUserId());
|
||||
|
||||
if (!empty($item['featured'])) {
|
||||
$pinned = $this->l10n->t('Pinned item');
|
||||
|
|
|
@ -7,8 +7,8 @@
|
|||
|
||||
namespace Friendica\Content;
|
||||
|
||||
use Friendica\Core\Hook;
|
||||
use Friendica\DI;
|
||||
use Friendica\Event\ArrayFilterEvent;
|
||||
|
||||
class Feature
|
||||
{
|
||||
|
@ -41,15 +41,23 @@ class Feature
|
|||
*/
|
||||
public static function isEnabled(int $uid, $feature): bool
|
||||
{
|
||||
if (!DI::config()->get('feature_lock', $feature, false)) {
|
||||
$enabled = DI::config()->get('feature', $feature) ?? self::getDefault($feature);
|
||||
$enabled = DI::pConfig()->get($uid, 'feature', $feature) ?? $enabled;
|
||||
$config = DI::config();
|
||||
$pConfig = DI::pConfig();
|
||||
$eventDispatcher = DI::eventDispatcher();
|
||||
|
||||
if (!$config->get('feature_lock', $feature, false)) {
|
||||
$enabled = $config->get('feature', $feature) ?? self::getDefault($feature);
|
||||
$enabled = $pConfig->get($uid, 'feature', $feature) ?? $enabled;
|
||||
} else {
|
||||
$enabled = true;
|
||||
}
|
||||
|
||||
$arr = ['uid' => $uid, 'feature' => $feature, 'enabled' => $enabled];
|
||||
Hook::callAll('isEnabled', $arr);
|
||||
|
||||
$arr = $eventDispatcher->dispatch(
|
||||
new ArrayFilterEvent(ArrayFilterEvent::FEATURE_ENABLED, $arr)
|
||||
)->getArray();
|
||||
|
||||
return (bool)$arr['enabled'];
|
||||
}
|
||||
|
||||
|
@ -86,55 +94,58 @@ class Feature
|
|||
*/
|
||||
public static function get($filtered = true)
|
||||
{
|
||||
$arr = [
|
||||
$l10n = DI::l10n();
|
||||
$config = DI::config();
|
||||
$eventDispatcher = DI::eventDispatcher();
|
||||
|
||||
$arr = [
|
||||
// General
|
||||
'general' => [
|
||||
DI::l10n()->t('General Features'),
|
||||
//array('expire', DI::l10n()->t('Content Expiration'), DI::l10n()->t('Remove old posts/comments after a period of time')),
|
||||
[self::PHOTO_LOCATION, DI::l10n()->t('Photo Location'), DI::l10n()->t("Photo metadata is normally stripped. This extracts the location \x28if present\x29 prior to stripping metadata and links it to a map."), false, DI::config()->get('feature_lock', self::PHOTO_LOCATION, false)],
|
||||
[self::COMMUNITY, DI::l10n()->t('Display the community in the navigation'), DI::l10n()->t('If enabled, the community can be accessed via the navigation menu. Independent from this setting, the community timelines can always be accessed via the channels.'), true, DI::config()->get('feature_lock', self::COMMUNITY, false)],
|
||||
$l10n->t('General Features'),
|
||||
//array('expire', $l10n->t('Content Expiration'), $l10n->t('Remove old posts/comments after a period of time')),
|
||||
[self::PHOTO_LOCATION, $l10n->t('Photo Location'), $l10n->t("Photo metadata is normally stripped. This extracts the location \x28if present\x29 prior to stripping metadata and links it to a map."), false, $config->get('feature_lock', self::PHOTO_LOCATION, false)],
|
||||
[self::COMMUNITY, $l10n->t('Display the community in the navigation'), $l10n->t('If enabled, the community can be accessed via the navigation menu. Independent from this setting, the community timelines can always be accessed via the channels.'), true, $config->get('feature_lock', self::COMMUNITY, false)],
|
||||
],
|
||||
|
||||
// Post composition
|
||||
'composition' => [
|
||||
DI::l10n()->t('Post Composition Features'),
|
||||
[self::EXPLICIT_MENTIONS, DI::l10n()->t('Explicit Mentions'), DI::l10n()->t('Add explicit mentions to comment box for manual control over who gets mentioned in replies.'), false, DI::config()->get('feature_lock', Feature::EXPLICIT_MENTIONS, false)],
|
||||
[self::ADD_ABSTRACT, DI::l10n()->t('Add an abstract from ActivityPub content warnings'), DI::l10n()->t('Add an abstract when commenting on ActivityPub posts with a content warning. Abstracts are displayed as content warning on systems like Mastodon or Pleroma.'), false, DI::config()->get('feature_lock', self::ADD_ABSTRACT, false)],
|
||||
$l10n->t('Post Composition Features'),
|
||||
[self::EXPLICIT_MENTIONS, $l10n->t('Explicit Mentions'), $l10n->t('Add explicit mentions to comment box for manual control over who gets mentioned in replies.'), false, $config->get('feature_lock', Feature::EXPLICIT_MENTIONS, false)],
|
||||
[self::ADD_ABSTRACT, $l10n->t('Add an abstract from ActivityPub content warnings'), $l10n->t('Add an abstract when commenting on ActivityPub posts with a content warning. Abstracts are displayed as content warning on systems like Mastodon or Pleroma.'), false, $config->get('feature_lock', self::ADD_ABSTRACT, false)],
|
||||
],
|
||||
|
||||
// Item tools
|
||||
'tools' => [
|
||||
DI::l10n()->t('Post/Comment Tools'),
|
||||
[self::CATEGORIES, DI::l10n()->t('Post Categories'), DI::l10n()->t('Add categories to your posts'), false, DI::config()->get('feature_lock', self::CATEGORIES, false)],
|
||||
$l10n->t('Post/Comment Tools'),
|
||||
[self::CATEGORIES, $l10n->t('Post Categories'), $l10n->t('Add categories to your posts'), false, $config->get('feature_lock', self::CATEGORIES, false)],
|
||||
],
|
||||
|
||||
// Widget visibility on the network stream
|
||||
'network' => [
|
||||
DI::l10n()->t('Network Widgets'),
|
||||
[self::CIRCLES, DI::l10n()->t('Circles'), DI::l10n()->t('Display posts that have been created by accounts of the selected circle.'), true, DI::config()->get('feature_lock', self::CIRCLES, false)],
|
||||
[self::GROUPS, DI::l10n()->t('Groups'), DI::l10n()->t('Display posts that have been distributed by the selected group.'), true, DI::config()->get('feature_lock', self::GROUPS, false)],
|
||||
[self::ARCHIVE, DI::l10n()->t('Archives'), DI::l10n()->t('Display an archive where posts can be selected by month and year.'), true, DI::config()->get('feature_lock', self::ARCHIVE, false)],
|
||||
[self::NETWORKS, DI::l10n()->t('Protocols'), DI::l10n()->t('Display posts with the selected protocols.'), true, DI::config()->get('feature_lock', self::NETWORKS, false)],
|
||||
[self::ACCOUNTS, DI::l10n()->t('Account Types'), DI::l10n()->t('Display posts done by accounts with the selected account type.'), true, DI::config()->get('feature_lock', self::ACCOUNTS, false)],
|
||||
[self::CHANNELS, DI::l10n()->t('Channels'), DI::l10n()->t('Display posts in the system channels and user defined channels.'), true, DI::config()->get('feature_lock', self::CHANNELS, false)],
|
||||
[self::SEARCHES, DI::l10n()->t('Saved Searches'), DI::l10n()->t('Display posts that contain subscribed hashtags.'), true, DI::config()->get('feature_lock', self::SEARCHES, false)],
|
||||
[self::FOLDERS, DI::l10n()->t('Saved Folders'), DI::l10n()->t('Display a list of folders in which posts are stored.'), true, DI::config()->get('feature_lock', self::FOLDERS, false)],
|
||||
[self::NOSHARER, DI::l10n()->t('Own Contacts'), DI::l10n()->t('Include or exclude posts from subscribed accounts. This widget is not visible on all channels.'), true, DI::config()->get('feature_lock', self::NOSHARER, false)],
|
||||
[self::TRENDING_TAGS, DI::l10n()->t('Trending Tags'), DI::l10n()->t('Display a list of the most popular tags in recent public posts.'), false, DI::config()->get('feature_lock', self::TRENDING_TAGS, false)],
|
||||
$l10n->t('Network Widgets'),
|
||||
[self::CIRCLES, $l10n->t('Circles'), $l10n->t('Display posts that have been created by accounts of the selected circle.'), true, $config->get('feature_lock', self::CIRCLES, false)],
|
||||
[self::GROUPS, $l10n->t('Groups'), $l10n->t('Display posts that have been distributed by the selected group.'), true, $config->get('feature_lock', self::GROUPS, false)],
|
||||
[self::ARCHIVE, $l10n->t('Archives'), $l10n->t('Display an archive where posts can be selected by month and year.'), true, $config->get('feature_lock', self::ARCHIVE, false)],
|
||||
[self::NETWORKS, $l10n->t('Protocols'), $l10n->t('Display posts with the selected protocols.'), true, $config->get('feature_lock', self::NETWORKS, false)],
|
||||
[self::ACCOUNTS, $l10n->t('Account Types'), $l10n->t('Display posts done by accounts with the selected account type.'), true, $config->get('feature_lock', self::ACCOUNTS, false)],
|
||||
[self::CHANNELS, $l10n->t('Channels'), $l10n->t('Display posts in the system channels and user defined channels.'), true, $config->get('feature_lock', self::CHANNELS, false)],
|
||||
[self::SEARCHES, $l10n->t('Saved Searches'), $l10n->t('Display posts that contain subscribed hashtags.'), true, $config->get('feature_lock', self::SEARCHES, false)],
|
||||
[self::FOLDERS, $l10n->t('Saved Folders'), $l10n->t('Display a list of folders in which posts are stored.'), true, $config->get('feature_lock', self::FOLDERS, false)],
|
||||
[self::NOSHARER, $l10n->t('Own Contacts'), $l10n->t('Include or exclude posts from subscribed accounts. This widget is not visible on all channels.'), true, $config->get('feature_lock', self::NOSHARER, false)],
|
||||
[self::TRENDING_TAGS, $l10n->t('Trending Tags'), $l10n->t('Display a list of the most popular tags in recent public posts.'), false, $config->get('feature_lock', self::TRENDING_TAGS, false)],
|
||||
],
|
||||
|
||||
// Advanced Profile Settings
|
||||
'advanced_profile' => [
|
||||
DI::l10n()->t('Advanced Profile Settings'),
|
||||
[self::TAGCLOUD, DI::l10n()->t('Tag Cloud'), DI::l10n()->t('Provide a personal tag cloud on your profile page'), false, DI::config()->get('feature_lock', self::TAGCLOUD, false)],
|
||||
[self::MEMBER_SINCE, DI::l10n()->t('Display Membership Date'), DI::l10n()->t('Display membership date in profile'), false, DI::config()->get('feature_lock', self::MEMBER_SINCE, false)],
|
||||
$l10n->t('Advanced Profile Settings'),
|
||||
[self::TAGCLOUD, $l10n->t('Tag Cloud'), $l10n->t('Provide a personal tag cloud on your profile page'), false, $config->get('feature_lock', self::TAGCLOUD, false)],
|
||||
[self::MEMBER_SINCE, $l10n->t('Display Membership Date'), $l10n->t('Display membership date in profile'), false, $config->get('feature_lock', self::MEMBER_SINCE, false)],
|
||||
],
|
||||
|
||||
//Advanced Calendar Settings
|
||||
'advanced_calendar' => [
|
||||
DI::l10n()->t('Advanced Calendar Settings'),
|
||||
[self::PUBLIC_CALENDAR, DI::l10n()->t('Allow anonymous access to your calendar'), DI::l10n()->t('Allows anonymous visitors to consult your calendar and your public events. Contact birthday events are private to you.'), false, DI::config()->get('feature_lock', self::PUBLIC_CALENDAR, false)],
|
||||
$l10n->t('Advanced Calendar Settings'),
|
||||
[self::PUBLIC_CALENDAR, $l10n->t('Allow anonymous access to your calendar'), $l10n->t('Allows anonymous visitors to consult your calendar and your public events. Contact birthday events are private to you.'), false, $config->get('feature_lock', self::PUBLIC_CALENDAR, false)],
|
||||
]
|
||||
];
|
||||
|
||||
|
@ -159,7 +170,10 @@ class Feature
|
|||
}
|
||||
}
|
||||
|
||||
Hook::callAll('get', $arr);
|
||||
$arr = $eventDispatcher->dispatch(
|
||||
new ArrayFilterEvent(ArrayFilterEvent::FEATURE_GET, $arr)
|
||||
)->getArray();
|
||||
|
||||
return $arr;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -108,8 +108,6 @@ class GroupManager
|
|||
|
||||
$entries = [];
|
||||
|
||||
$contacts = [];
|
||||
|
||||
foreach ($contacts as $contact) {
|
||||
$entry = [
|
||||
'url' => 'contact/' . $contact['id'] . '/conversations',
|
||||
|
|
|
@ -607,7 +607,7 @@ class Item
|
|||
} else {
|
||||
$owner_avatar = $item['owner-id'];
|
||||
$owner_updated = $item['owner-updated'];
|
||||
$owner_thumb = $item['owner-avatar'];
|
||||
$owner_thumb = $item['owner-avatar'] ?? '';
|
||||
}
|
||||
|
||||
if (empty($owner_thumb) || Photo::isPhotoURI($owner_thumb)) {
|
||||
|
|
|
@ -10,11 +10,12 @@ namespace Friendica\Content;
|
|||
use Friendica\App\BaseURL;
|
||||
use Friendica\App\Router;
|
||||
use Friendica\Core\Config\Capability\IManageConfigValues;
|
||||
use Friendica\Core\Hook;
|
||||
use Friendica\Core\L10n;
|
||||
use Friendica\Core\Renderer;
|
||||
use Friendica\Core\Session\Capability\IHandleUserSessions;
|
||||
use Friendica\Database\Database;
|
||||
use Friendica\Event\ArrayFilterEvent;
|
||||
use Friendica\Event\HtmlFilterEvent;
|
||||
use Friendica\Model\Contact;
|
||||
use Friendica\Model\User;
|
||||
use Friendica\Module\Conversation\Community;
|
||||
|
@ -22,6 +23,7 @@ use Friendica\Module\Home;
|
|||
use Friendica\Module\Security\Login;
|
||||
use Friendica\Network\HTTPException;
|
||||
use Friendica\Security\OpenWebAuth;
|
||||
use Psr\EventDispatcher\EventDispatcherInterface;
|
||||
|
||||
class Nav
|
||||
{
|
||||
|
@ -63,7 +65,9 @@ class Nav
|
|||
/** @var Router */
|
||||
private $router;
|
||||
|
||||
public function __construct(BaseURL $baseUrl, L10n $l10n, IHandleUserSessions $session, Database $database, IManageConfigValues $config, Router $router)
|
||||
private EventDispatcherInterface $eventDispatcher;
|
||||
|
||||
public function __construct(BaseURL $baseUrl, L10n $l10n, IHandleUserSessions $session, Database $database, IManageConfigValues $config, Router $router, EventDispatcherInterface $eventDispatcher)
|
||||
{
|
||||
$this->baseUrl = $baseUrl;
|
||||
$this->l10n = $l10n;
|
||||
|
@ -71,6 +75,7 @@ class Nav
|
|||
$this->database = $database;
|
||||
$this->config = $config;
|
||||
$this->router = $router;
|
||||
$this->eventDispatcher = $eventDispatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -108,12 +113,15 @@ class Nav
|
|||
'$userinfo' => $nav_info['userinfo'],
|
||||
'$sel' => self::$selected,
|
||||
'$apps' => $this->getAppMenu(),
|
||||
'$home' => $this->l10n->t('Go back'),
|
||||
'$home' => $this->l10n->t('Home'),
|
||||
'$skip' => $this->l10n->t('Skip to main content'),
|
||||
'$clear_notifs' => $this->l10n->t('Clear notifications'),
|
||||
'$search_hint' => $this->l10n->t('@name, !group, #tags, content')
|
||||
]);
|
||||
|
||||
Hook::callAll('page_header', $nav);
|
||||
$nav = $this->eventDispatcher->dispatch(
|
||||
new HtmlFilterEvent(HtmlFilterEvent::PAGE_HEADER, $nav)
|
||||
)->getHtml();
|
||||
|
||||
return $nav;
|
||||
}
|
||||
|
@ -150,9 +158,11 @@ class Nav
|
|||
) {
|
||||
$arr = ['app_menu' => $appMenu];
|
||||
|
||||
Hook::callAll('app_menu', $arr);
|
||||
$arr = $this->eventDispatcher->dispatch(
|
||||
new ArrayFilterEvent(ArrayFilterEvent::APP_MENU, $arr)
|
||||
)->getArray();
|
||||
|
||||
$appMenu = $arr['app_menu'];
|
||||
$appMenu = $arr['app_menu'] ?? [];
|
||||
}
|
||||
|
||||
return $appMenu;
|
||||
|
@ -336,7 +346,9 @@ class Nav
|
|||
'userinfo' => $userinfo,
|
||||
];
|
||||
|
||||
Hook::callAll('nav_info', $nav_info);
|
||||
$nav_info = $this->eventDispatcher->dispatch(
|
||||
new ArrayFilterEvent(ArrayFilterEvent::NAV_INFO, $nav_info)
|
||||
)->getArray();
|
||||
|
||||
return $nav_info;
|
||||
}
|
||||
|
|
|
@ -1583,7 +1583,7 @@ class BBCode
|
|||
// Check for headers
|
||||
|
||||
if ($simple_html == self::INTERNAL) {
|
||||
//Ensure to always start with <h4> if possible
|
||||
//Ensure to always start with <h3> if possible
|
||||
$heading_count = 0;
|
||||
for ($level = 6; $level > 0; $level--) {
|
||||
if (preg_match("(\[h$level\].*?\[\/h$level\])ism", $text)) {
|
||||
|
@ -1591,7 +1591,7 @@ class BBCode
|
|||
}
|
||||
}
|
||||
if ($heading_count > 0) {
|
||||
$heading = min($heading_count + 3, 6);
|
||||
$heading = min($heading_count + 2, 6);
|
||||
for ($level = 6; $level > 0; $level--) {
|
||||
if (preg_match("(\[h$level\].*?\[\/h$level\])ism", $text)) {
|
||||
$text = preg_replace("(\[h$level\](.*?)\[\/h$level\])ism", "</p><h$heading>$1</h$heading><p>", $text);
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
|
||||
namespace Friendica\Content;
|
||||
|
||||
use Friendica\Core\Addon;
|
||||
use Friendica\Core\Cache\Enum\Duration;
|
||||
use Friendica\Core\Protocol;
|
||||
use Friendica\Core\Renderer;
|
||||
|
@ -33,13 +32,13 @@ class Widget
|
|||
*/
|
||||
public static function follow(string $value = ''): string
|
||||
{
|
||||
return Renderer::replaceMacros(Renderer::getMarkupTemplate('widget/follow.tpl'), array(
|
||||
return Renderer::replaceMacros(Renderer::getMarkupTemplate('widget/follow.tpl'), [
|
||||
'$connect' => DI::l10n()->t('Add New Contact'),
|
||||
'$desc' => DI::l10n()->t('Enter address or web location'),
|
||||
'$hint' => DI::l10n()->t('Example: bob@example.com, http://example.com/barbara'),
|
||||
'$value' => $value,
|
||||
'$follow' => DI::l10n()->t('Connect')
|
||||
));
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -87,19 +86,21 @@ class Widget
|
|||
*/
|
||||
public static function unavailableNetworks(): array
|
||||
{
|
||||
$addonHelper = DI::addonHelper();
|
||||
|
||||
// Always hide content from these networks
|
||||
$networks = [Protocol::PHANTOM, Protocol::FACEBOOK, Protocol::APPNET, Protocol::TWITTER, Protocol::ZOT, Protocol::OSTATUS, Protocol::STATUSNET];
|
||||
Addon::loadAddons();
|
||||
$addonHelper->loadAddons();
|
||||
|
||||
if (!Addon::isEnabled('discourse')) {
|
||||
if (!$addonHelper->isAddonEnabled('discourse')) {
|
||||
$networks[] = Protocol::DISCOURSE;
|
||||
}
|
||||
|
||||
if (!Addon::isEnabled('pumpio')) {
|
||||
if (!$addonHelper->isAddonEnabled('pumpio')) {
|
||||
$networks[] = Protocol::PUMPIO;
|
||||
}
|
||||
|
||||
if (!Addon::isEnabled('tumblr')) {
|
||||
if (!$addonHelper->isAddonEnabled('tumblr')) {
|
||||
$networks[] = Protocol::TUMBLR;
|
||||
}
|
||||
|
||||
|
@ -107,7 +108,7 @@ class Widget
|
|||
$networks[] = Protocol::DIASPORA;
|
||||
}
|
||||
|
||||
if (!Addon::isEnabled('pnut')) {
|
||||
if (!$addonHelper->isAddonEnabled('pnut')) {
|
||||
$networks[] = Protocol::PNUT;
|
||||
}
|
||||
return $networks;
|
||||
|
@ -120,18 +121,20 @@ class Widget
|
|||
*/
|
||||
public static function availableNetworks(): array
|
||||
{
|
||||
$networks = [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::FEED];
|
||||
Addon::loadAddons();
|
||||
$addonHelper = DI::addonHelper();
|
||||
|
||||
if (Addon::isEnabled('discourse')) {
|
||||
$networks = [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::FEED];
|
||||
$addonHelper->loadAddons();
|
||||
|
||||
if ($addonHelper->isAddonEnabled('discourse')) {
|
||||
$networks[] = Protocol::DISCOURSE;
|
||||
}
|
||||
|
||||
if (Addon::isEnabled('pumpio')) {
|
||||
if ($addonHelper->isAddonEnabled('pumpio')) {
|
||||
$networks[] = Protocol::PUMPIO;
|
||||
}
|
||||
|
||||
if (Addon::isEnabled('tumblr')) {
|
||||
if ($addonHelper->isAddonEnabled('tumblr')) {
|
||||
$networks[] = Protocol::TUMBLR;
|
||||
}
|
||||
|
||||
|
@ -143,7 +146,7 @@ class Widget
|
|||
$networks[] = Protocol::MAIL;
|
||||
}
|
||||
|
||||
if (Addon::isEnabled('pnut')) {
|
||||
if ($addonHelper->isAddonEnabled('pnut')) {
|
||||
$networks[] = Protocol::PNUT;
|
||||
}
|
||||
return $networks;
|
||||
|
|
|
@ -18,6 +18,9 @@ class Addon
|
|||
{
|
||||
/**
|
||||
* The addon sub-directory
|
||||
*
|
||||
* @deprecated 2025.02 Use `Friendica\Core\Addon\AddonHelper::getAddonPath()` instead
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const DIRECTORY = 'addon';
|
||||
|
@ -34,6 +37,8 @@ class Addon
|
|||
* This list is made from scanning the addon/ folder.
|
||||
* Unsupported addons are excluded unless they already are enabled or system.show_unsupported_addon is set.
|
||||
*
|
||||
* @deprecated 2025.02 Use `Friendica\Core\Addon\AddonHelper::getAvailableAddons()` instead
|
||||
*
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
|
@ -64,6 +69,8 @@ class Addon
|
|||
* Returns a list of addons that can be configured at the node level.
|
||||
* The list is formatted for display in the admin panel aside.
|
||||
*
|
||||
* @deprecated 2025.02 Use `Friendica\Core\Addon\AddonHelper::getEnabledAddonsWithAdminSettings()` instead
|
||||
*
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
|
@ -88,7 +95,6 @@ class Addon
|
|||
return $addons_admin;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Synchronize addons:
|
||||
*
|
||||
|
@ -100,6 +106,7 @@ class Addon
|
|||
* Then go through the config list and if we have a addon that isn't installed,
|
||||
* call the install procedure and add it to the database.
|
||||
*
|
||||
* @deprecated 2025.02 Use `Friendica\Core\Addon\AddonHelper::loadAddons()` instead
|
||||
*/
|
||||
public static function loadAddons()
|
||||
{
|
||||
|
@ -109,6 +116,8 @@ class Addon
|
|||
/**
|
||||
* uninstalls an addon.
|
||||
*
|
||||
* @deprecated 2025.02 Use `Friendica\Core\Addon\AddonHelper::uninstallAddon()` instead
|
||||
*
|
||||
* @param string $addon name of the addon
|
||||
* @return void
|
||||
* @throws \Exception
|
||||
|
@ -135,6 +144,8 @@ class Addon
|
|||
/**
|
||||
* installs an addon.
|
||||
*
|
||||
* @deprecated 2025.02 Use `Friendica\Core\Addon\AddonHelper::installAddon()` instead
|
||||
*
|
||||
* @param string $addon name of the addon
|
||||
* @return bool
|
||||
* @throws \Exception
|
||||
|
@ -173,6 +184,8 @@ class Addon
|
|||
/**
|
||||
* reload all updated addons
|
||||
*
|
||||
* @deprecated 2025.02 Use `Friendica\Core\Addon\AddonHelper::reloadAddons()` instead
|
||||
*
|
||||
* @return void
|
||||
* @throws \Exception
|
||||
*
|
||||
|
@ -209,6 +222,9 @@ class Addon
|
|||
* * Maintainer: Jess <email>
|
||||
* *
|
||||
* *\endcode
|
||||
*
|
||||
* @deprecated 2025.02 Use `Friendica\Core\Addon\AddonHelper::getAddonInfo()` instead
|
||||
*
|
||||
* @param string $addon the name of the addon
|
||||
* @return array with the addon information
|
||||
* @throws \Exception
|
||||
|
@ -275,6 +291,8 @@ class Addon
|
|||
/**
|
||||
* Checks if the provided addon is enabled
|
||||
*
|
||||
* @deprecated 2025.02 Use `Friendica\Core\Addon\AddonHelper::isAddonEnabled()` instead
|
||||
*
|
||||
* @param string $addon
|
||||
* @return boolean
|
||||
*/
|
||||
|
@ -286,6 +304,8 @@ class Addon
|
|||
/**
|
||||
* Returns a list of the enabled addon names
|
||||
*
|
||||
* @deprecated 2025.02 Use `Friendica\Core\Addon\AddonHelper::getEnabledAddons()` instead
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function getEnabledList(): array
|
||||
|
@ -296,6 +316,8 @@ class Addon
|
|||
/**
|
||||
* Returns the list of non-hidden enabled addon names
|
||||
*
|
||||
* @deprecated 2025.02 Use `Friendica\Core\Addon\AddonHelper::getVisibleEnabledAddons()` instead
|
||||
*
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
|
|
92
src/Core/Addon/AddonHelper.php
Normal file
92
src/Core/Addon/AddonHelper.php
Normal file
|
@ -0,0 +1,92 @@
|
|||
<?php
|
||||
|
||||
// Copyright (C) 2010-2024, the Friendica project
|
||||
// SPDX-FileCopyrightText: 2010-2024 the Friendica project
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Friendica\Core\Addon;
|
||||
|
||||
/**
|
||||
* Some functions to handle addons
|
||||
*/
|
||||
interface AddonHelper
|
||||
{
|
||||
/**
|
||||
* Returns the absolute path to the addon folder
|
||||
*
|
||||
* e.g. `/var/www/html/addon`
|
||||
*/
|
||||
public function getAddonPath(): string;
|
||||
|
||||
/**
|
||||
* Returns the list of available addons.
|
||||
*
|
||||
* This list is made from scanning the addon/ folder.
|
||||
* Unsupported addons are excluded unless they already are enabled or system.show_unsupported_addon is set.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getAvailableAddons(): array;
|
||||
|
||||
/**
|
||||
* Installs an addon.
|
||||
*
|
||||
* @param string $addonId name of the addon
|
||||
*
|
||||
* @return bool true on success or false on failure
|
||||
*/
|
||||
public function installAddon(string $addonId): bool;
|
||||
|
||||
/**
|
||||
* Uninstalls an addon.
|
||||
*
|
||||
* @param string $addonId name of the addon
|
||||
*/
|
||||
public function uninstallAddon(string $addonId): void;
|
||||
|
||||
/**
|
||||
* Load addons.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function loadAddons(): void;
|
||||
|
||||
/**
|
||||
* Reload (uninstall and install) all updated addons.
|
||||
*/
|
||||
public function reloadAddons(): void;
|
||||
|
||||
/**
|
||||
* Get the comment block of an addon as value object.
|
||||
*/
|
||||
public function getAddonInfo(string $addonId): AddonInfo;
|
||||
|
||||
/**
|
||||
* Checks if the provided addon is enabled
|
||||
*/
|
||||
public function isAddonEnabled(string $addonId): bool;
|
||||
|
||||
/**
|
||||
* Returns a list with the IDs of the enabled addons
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getEnabledAddons(): array;
|
||||
|
||||
/**
|
||||
* Returns a list with the IDs of the non-hidden enabled addons
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getVisibleEnabledAddons(): array;
|
||||
|
||||
/**
|
||||
* Returns a list with the IDs of the enabled addons that provides admin settings.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getEnabledAddonsWithAdminSettings(): array;
|
||||
}
|
140
src/Core/Addon/AddonInfo.php
Normal file
140
src/Core/Addon/AddonInfo.php
Normal file
|
@ -0,0 +1,140 @@
|
|||
<?php
|
||||
|
||||
// Copyright (C) 2010-2024, the Friendica project
|
||||
// SPDX-FileCopyrightText: 2010-2024 the Friendica project
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Friendica\Core\Addon;
|
||||
|
||||
/**
|
||||
* Information about an addon
|
||||
*/
|
||||
final class AddonInfo
|
||||
{
|
||||
/**
|
||||
* @internal Never create this object by yourself, use `Friendica\Core\Addon\AddonHelper::getAddonInfo()` instead.
|
||||
*
|
||||
* @see Friendica\Core\Addon\AddonHelper::getAddonInfo()
|
||||
*/
|
||||
public static function fromArray(array $info): self
|
||||
{
|
||||
$id = array_key_exists('id', $info) ? (string) $info['id'] : '';
|
||||
$name = array_key_exists('name', $info) ? (string) $info['name'] : '';
|
||||
$description = array_key_exists('description', $info) ? (string) $info['description'] : '';
|
||||
$authors = array_key_exists('authors', $info) ? self::parseContributors($info['authors']) : [];
|
||||
$maintainers = array_key_exists('maintainers', $info) ? self::parseContributors($info['maintainers']) : [];
|
||||
$version = array_key_exists('version', $info) ? (string) $info['version'] : '';
|
||||
$status = array_key_exists('status', $info) ? (string) $info['status'] : '';
|
||||
|
||||
return new self(
|
||||
$id,
|
||||
$name,
|
||||
$description,
|
||||
$authors,
|
||||
$maintainers,
|
||||
$version,
|
||||
$status
|
||||
);
|
||||
}
|
||||
|
||||
private static function parseContributors($entries): array
|
||||
{
|
||||
if (!is_array($entries)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$contributors = [];
|
||||
|
||||
foreach ($entries as $entry) {
|
||||
if (!is_array($entry)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!array_key_exists('name', $entry)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$contributor = [
|
||||
'name' => (string) $entry['name'],
|
||||
];
|
||||
|
||||
if (array_key_exists('link', $entry)) {
|
||||
$contributor['link'] = (string) $entry['link'];
|
||||
}
|
||||
|
||||
$contributors[] = $contributor;
|
||||
}
|
||||
|
||||
return $contributors;
|
||||
}
|
||||
|
||||
private string $id = '';
|
||||
|
||||
private string $name = '';
|
||||
|
||||
private string $description = '';
|
||||
|
||||
private array $authors = [];
|
||||
|
||||
private array $maintainers = [];
|
||||
|
||||
private string $version = '';
|
||||
|
||||
private string $status = '';
|
||||
|
||||
private function __construct(
|
||||
string $id,
|
||||
string $name,
|
||||
string $description,
|
||||
array $authors,
|
||||
array $maintainers,
|
||||
string $version,
|
||||
string $status
|
||||
) {
|
||||
$this->id = $id;
|
||||
$this->name = $name;
|
||||
$this->description = $description;
|
||||
$this->authors = $authors;
|
||||
$this->maintainers = $maintainers;
|
||||
$this->version = $version;
|
||||
$this->status = $status;
|
||||
}
|
||||
|
||||
public function getId(): string
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function getDescription(): string
|
||||
{
|
||||
return $this->description;
|
||||
}
|
||||
|
||||
public function getAuthors(): array
|
||||
{
|
||||
return $this->authors;
|
||||
}
|
||||
|
||||
public function getMaintainers(): array
|
||||
{
|
||||
return $this->maintainers;
|
||||
}
|
||||
|
||||
public function getVersion(): string
|
||||
{
|
||||
return $this->version;
|
||||
}
|
||||
|
||||
public function getStatus(): string
|
||||
{
|
||||
return $this->status;
|
||||
}
|
||||
}
|
154
src/Core/Addon/AddonProxy.php
Normal file
154
src/Core/Addon/AddonProxy.php
Normal file
|
@ -0,0 +1,154 @@
|
|||
<?php
|
||||
|
||||
// Copyright (C) 2010-2024, the Friendica project
|
||||
// SPDX-FileCopyrightText: 2010-2024 the Friendica project
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Friendica\Core\Addon;
|
||||
|
||||
use Friendica\Core\Addon;
|
||||
|
||||
/**
|
||||
* Proxy to the Addon class
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class AddonProxy implements AddonHelper
|
||||
{
|
||||
private string $addonPath;
|
||||
|
||||
public function __construct(string $addonPath)
|
||||
{
|
||||
$this->addonPath = $addonPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the absolute path to the addon folder
|
||||
*
|
||||
* e.g. `/var/www/html/addon`
|
||||
*/
|
||||
public function getAddonPath(): string
|
||||
{
|
||||
return $this->addonPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the list of available addons.
|
||||
*
|
||||
* This list is made from scanning the addon/ folder.
|
||||
* Unsupported addons are excluded unless they already are enabled or system.show_unsupported_addon is set.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getAvailableAddons(): array
|
||||
{
|
||||
return array_map(
|
||||
function (array $item) {
|
||||
return $item[0];
|
||||
},
|
||||
Addon::getAvailableList()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Installs an addon.
|
||||
*
|
||||
* @param string $addonId name of the addon
|
||||
*
|
||||
* @return bool true on success or false on failure
|
||||
*/
|
||||
public function installAddon(string $addonId): bool
|
||||
{
|
||||
return Addon::install($addonId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Uninstalls an addon.
|
||||
*
|
||||
* @param string $addonId name of the addon
|
||||
*/
|
||||
public function uninstallAddon(string $addonId): void
|
||||
{
|
||||
Addon::uninstall($addonId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load addons.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public function loadAddons(): void
|
||||
{
|
||||
Addon::loadAddons();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reload (uninstall and install) all updated addons.
|
||||
*/
|
||||
public function reloadAddons(): void
|
||||
{
|
||||
Addon::reload();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the comment block of an addon as value object.
|
||||
*/
|
||||
public function getAddonInfo(string $addonId): AddonInfo
|
||||
{
|
||||
$data = Addon::getInfo($addonId);
|
||||
|
||||
// add addon ID
|
||||
$data['id'] = $addonId;
|
||||
|
||||
// rename author to authors
|
||||
$data['authors'] = $data['author'];
|
||||
unset($data['author']);
|
||||
|
||||
// rename maintainer to maintainers
|
||||
$data['maintainers'] = $data['maintainer'];
|
||||
unset($data['maintainer']);
|
||||
|
||||
return AddonInfo::fromArray($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the provided addon is enabled
|
||||
*/
|
||||
public function isAddonEnabled(string $addonId): bool
|
||||
{
|
||||
return Addon::isEnabled($addonId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list with the IDs of the enabled addons
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getEnabledAddons(): array
|
||||
{
|
||||
return Addon::getEnabledList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list with the IDs of the non-hidden enabled addons
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getVisibleEnabledAddons(): array
|
||||
{
|
||||
return Addon::getVisibleList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list with the IDs of the enabled addons that provides admin settings.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function getEnabledAddonsWithAdminSettings(): array
|
||||
{
|
||||
return array_keys(Addon::getAdminList());
|
||||
}
|
||||
}
|
|
@ -42,7 +42,7 @@ class Config
|
|||
*
|
||||
* @return Util\ConfigFileManager
|
||||
*/
|
||||
public function createConfigFileManager(string $basePath, array $server = []): Util\ConfigFileManager
|
||||
public function createConfigFileManager(string $basePath, string $addonPath, array $server = []): Util\ConfigFileManager
|
||||
{
|
||||
if (!empty($server[self::CONFIG_DIR_ENV]) && is_dir($server[self::CONFIG_DIR_ENV])) {
|
||||
$configDir = $server[self::CONFIG_DIR_ENV];
|
||||
|
@ -51,7 +51,7 @@ class Config
|
|||
}
|
||||
$staticDir = $basePath . DIRECTORY_SEPARATOR . self::STATIC_DIR;
|
||||
|
||||
return new Util\ConfigFileManager($basePath, $configDir, $staticDir, $server);
|
||||
return new Util\ConfigFileManager($basePath, $addonPath, $configDir, $staticDir, $server);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
|
||||
namespace Friendica\Core\Config\Util;
|
||||
|
||||
use Friendica\Core\Addon;
|
||||
use Friendica\Core\Config\Exception\ConfigFileException;
|
||||
use Friendica\Core\Config\ValueObject\Cache;
|
||||
|
||||
|
@ -46,6 +45,7 @@ class ConfigFileManager
|
|||
* @var string
|
||||
*/
|
||||
private $baseDir;
|
||||
private string $addonDir;
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
|
@ -65,9 +65,10 @@ class ConfigFileManager
|
|||
* @param string $configDir
|
||||
* @param string $staticDir
|
||||
*/
|
||||
public function __construct(string $baseDir, string $configDir, string $staticDir, array $server = [])
|
||||
public function __construct(string $baseDir, string $addonDir, string $configDir, string $staticDir, array $server = [])
|
||||
{
|
||||
$this->baseDir = $baseDir;
|
||||
$this->addonDir = $addonDir;
|
||||
$this->configDir = $configDir;
|
||||
$this->staticDir = $staticDir;
|
||||
$this->server = $server;
|
||||
|
@ -160,17 +161,16 @@ class ConfigFileManager
|
|||
*/
|
||||
public function loadAddonConfig(string $name): array
|
||||
{
|
||||
$filepath = $this->baseDir . DIRECTORY_SEPARATOR . // /var/www/html/
|
||||
Addon::DIRECTORY . DIRECTORY_SEPARATOR . // addon/
|
||||
$filepath = $this->addonDir . DIRECTORY_SEPARATOR . // /var/www/html/addon/
|
||||
$name . DIRECTORY_SEPARATOR . // openstreetmap/
|
||||
'config' . DIRECTORY_SEPARATOR . // config/
|
||||
$name . ".config.php"; // openstreetmap.config.php
|
||||
|
||||
if (file_exists($filepath)) {
|
||||
return $this->loadConfigFile($filepath);
|
||||
} else {
|
||||
if (!file_exists($filepath)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $this->loadConfigFile($filepath);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
113
src/Core/Hooks/HookEventBridge.php
Normal file
113
src/Core/Hooks/HookEventBridge.php
Normal file
|
@ -0,0 +1,113 @@
|
|||
<?php
|
||||
|
||||
// Copyright (C) 2010-2024, the Friendica project
|
||||
// SPDX-FileCopyrightText: 2010-2024 the Friendica project
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Friendica\Core\Hooks;
|
||||
|
||||
use Friendica\Core\Hook;
|
||||
use Friendica\Event\ArrayFilterEvent;
|
||||
use Friendica\Event\ConfigLoadedEvent;
|
||||
use Friendica\Event\Event;
|
||||
use Friendica\Event\HtmlFilterEvent;
|
||||
use Friendica\Event\NamedEvent;
|
||||
|
||||
/**
|
||||
* Bridge between the EventDispatcher and the Hook class.
|
||||
*
|
||||
* @internal Provides BC
|
||||
*/
|
||||
final class HookEventBridge
|
||||
{
|
||||
/**
|
||||
* @internal This allows us to mock the Hook call in tests.
|
||||
*
|
||||
* @var \Closure|null
|
||||
*/
|
||||
private static $mockedCallHook = null;
|
||||
|
||||
/**
|
||||
* This maps the new event names to the legacy Hook names.
|
||||
*/
|
||||
private static array $eventMapper = [
|
||||
Event::INIT => 'init_1',
|
||||
ConfigLoadedEvent::CONFIG_LOADED => 'load_config',
|
||||
ArrayFilterEvent::APP_MENU => 'app_menu',
|
||||
ArrayFilterEvent::NAV_INFO => 'nav_info',
|
||||
ArrayFilterEvent::FEATURE_ENABLED => 'isEnabled',
|
||||
ArrayFilterEvent::FEATURE_GET => 'get',
|
||||
HtmlFilterEvent::HEAD => 'head',
|
||||
HtmlFilterEvent::FOOTER => 'footer',
|
||||
HtmlFilterEvent::PAGE_HEADER => 'page_header',
|
||||
HtmlFilterEvent::PAGE_CONTENT_TOP => 'page_content_top',
|
||||
HtmlFilterEvent::PAGE_END => 'page_end',
|
||||
];
|
||||
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public static function getStaticSubscribedEvents(): array
|
||||
{
|
||||
return [
|
||||
Event::INIT => 'onNamedEvent',
|
||||
ConfigLoadedEvent::CONFIG_LOADED => 'onConfigLoadedEvent',
|
||||
ArrayFilterEvent::APP_MENU => 'onArrayFilterEvent',
|
||||
ArrayFilterEvent::NAV_INFO => 'onArrayFilterEvent',
|
||||
ArrayFilterEvent::FEATURE_ENABLED => 'onArrayFilterEvent',
|
||||
ArrayFilterEvent::FEATURE_GET => 'onArrayFilterEvent',
|
||||
HtmlFilterEvent::HEAD => 'onHtmlFilterEvent',
|
||||
HtmlFilterEvent::FOOTER => 'onHtmlFilterEvent',
|
||||
HtmlFilterEvent::PAGE_HEADER => 'onHtmlFilterEvent',
|
||||
HtmlFilterEvent::PAGE_CONTENT_TOP => 'onHtmlFilterEvent',
|
||||
HtmlFilterEvent::PAGE_END => 'onHtmlFilterEvent',
|
||||
];
|
||||
}
|
||||
|
||||
public static function onNamedEvent(NamedEvent $event): void
|
||||
{
|
||||
static::callHook($event->getName(), '');
|
||||
}
|
||||
|
||||
public static function onConfigLoadedEvent(ConfigLoadedEvent $event): void
|
||||
{
|
||||
static::callHook($event->getName(), $event->getConfig());
|
||||
}
|
||||
|
||||
public static function onArrayFilterEvent(ArrayFilterEvent $event): void
|
||||
{
|
||||
$event->setArray(
|
||||
static::callHook($event->getName(), $event->getArray())
|
||||
);
|
||||
}
|
||||
|
||||
public static function onHtmlFilterEvent(HtmlFilterEvent $event): void
|
||||
{
|
||||
$event->setHtml(
|
||||
static::callHook($event->getName(), $event->getHtml())
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string|array|object $data
|
||||
*
|
||||
* @return string|array|object
|
||||
*/
|
||||
private static function callHook(string $name, $data)
|
||||
{
|
||||
// If possible, map the event name to the legacy Hook name
|
||||
$name = static::$eventMapper[$name] ?? $name;
|
||||
|
||||
// Little hack to allow mocking the Hook call in tests.
|
||||
if (static::$mockedCallHook instanceof \Closure) {
|
||||
return (static::$mockedCallHook)->__invoke($name, $data);
|
||||
}
|
||||
|
||||
Hook::callAll($name, $data);
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
|
@ -8,7 +8,6 @@
|
|||
namespace Friendica\Core;
|
||||
|
||||
use Friendica\DI;
|
||||
use Friendica\Core\Logger\Type\WorkerLogger;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
|
@ -18,10 +17,7 @@ use Psr\Log\LoggerInterface;
|
|||
*/
|
||||
class Logger
|
||||
{
|
||||
/**
|
||||
* @return LoggerInterface|WorkerLogger
|
||||
*/
|
||||
private static function getInstance()
|
||||
private static function getInstance(): LoggerInterface
|
||||
{
|
||||
return DI::logger();
|
||||
}
|
||||
|
@ -38,6 +34,8 @@ class Logger
|
|||
*/
|
||||
public static function emergency(string $message, array $context = [])
|
||||
{
|
||||
@trigger_error('Class `' . __CLASS__ . '` is deprecated since 2025.02 and will be removed after 5 months, use constructor injection or `DI::logger()` instead.', E_USER_DEPRECATED);
|
||||
|
||||
self::getInstance()->emergency($message, $context);
|
||||
}
|
||||
|
||||
|
@ -55,6 +53,8 @@ class Logger
|
|||
*/
|
||||
public static function alert(string $message, array $context = [])
|
||||
{
|
||||
@trigger_error('Class `' . __CLASS__ . '` is deprecated since 2025.02 and will be removed after 5 months, use constructor injection or `DI::logger()` instead.', E_USER_DEPRECATED);
|
||||
|
||||
self::getInstance()->alert($message, $context);
|
||||
}
|
||||
|
||||
|
@ -71,6 +71,8 @@ class Logger
|
|||
*/
|
||||
public static function critical(string $message, array $context = [])
|
||||
{
|
||||
@trigger_error('Class `' . __CLASS__ . '` is deprecated since 2025.02 and will be removed after 5 months, use constructor injection or `DI::logger()` instead.', E_USER_DEPRECATED);
|
||||
|
||||
self::getInstance()->critical($message, $context);
|
||||
}
|
||||
|
||||
|
@ -86,6 +88,8 @@ class Logger
|
|||
*/
|
||||
public static function error(string $message, array $context = [])
|
||||
{
|
||||
@trigger_error('Class `' . __CLASS__ . '` is deprecated since 2025.02 and will be removed after 5 months, use constructor injection or `DI::logger()` instead.', E_USER_DEPRECATED);
|
||||
|
||||
self::getInstance()->error($message, $context);
|
||||
}
|
||||
|
||||
|
@ -103,6 +107,8 @@ class Logger
|
|||
*/
|
||||
public static function warning(string $message, array $context = [])
|
||||
{
|
||||
@trigger_error('Class `' . __CLASS__ . '` is deprecated since 2025.02 and will be removed after 5 months, use constructor injection or `DI::logger()` instead.', E_USER_DEPRECATED);
|
||||
|
||||
self::getInstance()->warning($message, $context);
|
||||
}
|
||||
|
||||
|
@ -117,6 +123,8 @@ class Logger
|
|||
*/
|
||||
public static function notice(string $message, array $context = [])
|
||||
{
|
||||
@trigger_error('Class `' . __CLASS__ . '` is deprecated since 2025.02 and will be removed after 5 months, use constructor injection or `DI::logger()` instead.', E_USER_DEPRECATED);
|
||||
|
||||
self::getInstance()->notice($message, $context);
|
||||
}
|
||||
|
||||
|
@ -134,6 +142,8 @@ class Logger
|
|||
*/
|
||||
public static function info(string $message, array $context = [])
|
||||
{
|
||||
@trigger_error('Class `' . __CLASS__ . '` is deprecated since 2025.02 and will be removed after 5 months, use constructor injection or `DI::logger()` instead.', E_USER_DEPRECATED);
|
||||
|
||||
self::getInstance()->info($message, $context);
|
||||
}
|
||||
|
||||
|
@ -148,6 +158,8 @@ class Logger
|
|||
*/
|
||||
public static function debug(string $message, array $context = [])
|
||||
{
|
||||
@trigger_error('Class `' . __CLASS__ . '` is deprecated since 2025.02 and will be removed after 5 months, use constructor injection or `DI::logger()` instead.', E_USER_DEPRECATED);
|
||||
|
||||
self::getInstance()->debug($message, $context);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -197,7 +197,7 @@ class ErrorHandler
|
|||
E_STRICT => LogLevel::NOTICE,
|
||||
E_RECOVERABLE_ERROR => LogLevel::ERROR,
|
||||
E_DEPRECATED => LogLevel::NOTICE,
|
||||
E_USER_DEPRECATED => LogLevel::NOTICE,
|
||||
E_USER_DEPRECATED => LogLevel::WARNING,
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -248,17 +248,29 @@ class ErrorHandler
|
|||
*/
|
||||
public function handleError(int $code, string $message, string $file = '', int $line = 0, ?array $context = []): bool
|
||||
{
|
||||
if ($this->handleOnlyReportedErrors && !(error_reporting() & $code)) {
|
||||
if ($this->handleOnlyReportedErrors && !(error_reporting() & $code) && $code !== E_USER_DEPRECATED) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
|
||||
|
||||
array_shift($trace); // Exclude handleError from trace
|
||||
|
||||
if ($code === E_USER_DEPRECATED && $trace[0]['function'] ?? '' === 'trigger_error') {
|
||||
$calledPlace = $trace[1] ?? [];
|
||||
|
||||
$message .= sprintf(
|
||||
' It was called in `%s`%s.',
|
||||
$calledPlace['file'],
|
||||
isset($calledPlace['line']) ? ' in line ' . $calledPlace['line'] : ''
|
||||
);
|
||||
}
|
||||
|
||||
// fatal error codes are ignored if a fatal error handler is present as well to avoid duplicate log entries
|
||||
if (!$this->hasFatalErrorHandler || !in_array($code, self::$fatalErrors, true)) {
|
||||
$level = $this->errorLevelMap[$code] ?? LogLevel::CRITICAL;
|
||||
$this->logger->log($level, self::codeToString($code).': '.$message, ['code' => $code, 'message' => $message, 'file' => $file, 'line' => $line]);
|
||||
} else {
|
||||
$trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
|
||||
array_shift($trace); // Exclude handleError from trace
|
||||
$this->lastFatalTrace = $trace;
|
||||
}
|
||||
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
namespace Friendica\Core\Storage\Repository;
|
||||
|
||||
use Exception;
|
||||
use Friendica\Core\Addon;
|
||||
use Friendica\Core\Config\Capability\IManageConfigValues;
|
||||
use Friendica\Core\Hook;
|
||||
use Friendica\Core\L10n;
|
||||
|
@ -20,6 +19,7 @@ use Friendica\Core\Storage\Capability\ICanConfigureStorage;
|
|||
use Friendica\Core\Storage\Capability\ICanWriteToStorage;
|
||||
use Friendica\Database\Database;
|
||||
use Friendica\Core\Storage\Type;
|
||||
use Friendica\DI;
|
||||
use Friendica\Network\HTTPException\InternalServerErrorException;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
|
@ -84,7 +84,7 @@ class StorageManager
|
|||
/// @fixme Loading the addons & hooks here is really bad practice, but solves https://github.com/friendica/friendica/issues/11178
|
||||
/// clean solution = Making Addon & Hook dynamic and load them inside the constructor, so there's no custom load logic necessary anymore
|
||||
if ($includeAddon) {
|
||||
Addon::loadAddons();
|
||||
DI::addonHelper()->loadAddons();
|
||||
Hook::loadHooks();
|
||||
}
|
||||
|
||||
|
@ -228,11 +228,13 @@ class StorageManager
|
|||
*/
|
||||
public function isValidBackend(string $name = null, array $validBackends = null): bool
|
||||
{
|
||||
$validBackends = $validBackends ?? array_merge($this->validBackends,
|
||||
$validBackends = $validBackends ?? array_merge(
|
||||
$this->validBackends,
|
||||
[
|
||||
Type\SystemResource::getName(),
|
||||
Type\ExternalResource::getName(),
|
||||
]);
|
||||
]
|
||||
);
|
||||
return in_array($name, $validBackends);
|
||||
}
|
||||
|
||||
|
|
22
src/DI.php
22
src/DI.php
|
@ -8,6 +8,7 @@
|
|||
namespace Friendica;
|
||||
|
||||
use Dice\Dice;
|
||||
use Friendica\Core\Addon\AddonHelper;
|
||||
use Friendica\Core\Logger\Capability\ICheckLoggerSettings;
|
||||
use Friendica\Core\Logger\LoggerManager;
|
||||
use Friendica\Core\Logger\Util\LoggerSettingsCheck;
|
||||
|
@ -280,6 +281,11 @@ abstract class DI
|
|||
return self::$dice->create(Core\Storage\Repository\StorageManager::class);
|
||||
}
|
||||
|
||||
public static function addonHelper(): AddonHelper
|
||||
{
|
||||
return self::$dice->create(AddonHelper::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Friendica\Core\System
|
||||
*/
|
||||
|
@ -332,9 +338,16 @@ abstract class DI
|
|||
*/
|
||||
public static function workerLogger()
|
||||
{
|
||||
@trigger_error('`' . __METHOD__ . '()` is deprecated since 2025.02 and will be removed after 5 months, use `DI::logger()` instead.', E_USER_DEPRECATED);
|
||||
|
||||
return self::$dice->create(Core\Logger\Type\WorkerLogger::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal Only for use in Friendica\Core\Worker class
|
||||
*
|
||||
* @see Friendica\Core\Worker::execFunction()
|
||||
*/
|
||||
public static function loggerManager(): LoggerManager
|
||||
{
|
||||
return self::$dice->create(LoggerManager::class);
|
||||
|
@ -782,4 +795,13 @@ abstract class DI
|
|||
{
|
||||
return self::$dice->create(Content\Post\Repository\PostMedia::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal The EventDispatcher should never called outside of the core, like in addons or themes
|
||||
* @deprecated 2025.02 Use constructor injection instead
|
||||
*/
|
||||
public static function eventDispatcher(): \Psr\EventDispatcher\EventDispatcherInterface
|
||||
{
|
||||
return self::$dice->create(\Psr\EventDispatcher\EventDispatcherInterface::class);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -650,7 +650,7 @@ class PostUpdate
|
|||
|
||||
DBA::update(
|
||||
'contact',
|
||||
['gsid' => GServer::getID($contact['baseurl'], true), 'baseurl' => GServer::cleanURL($contact['baseurl'])],
|
||||
['gsid' => GServer::getRealID($contact['baseurl'], true), 'baseurl' => GServer::cleanURL($contact['baseurl'])],
|
||||
['id' => $contact['id']]
|
||||
);
|
||||
|
||||
|
@ -705,7 +705,7 @@ class PostUpdate
|
|||
|
||||
DBA::update(
|
||||
'apcontact',
|
||||
['gsid' => GServer::getID($apcontact['baseurl'], true), 'baseurl' => GServer::cleanURL($apcontact['baseurl'])],
|
||||
['gsid' => GServer::getRealID($apcontact['baseurl'], true), 'baseurl' => GServer::cleanURL($apcontact['baseurl'])],
|
||||
['url' => $apcontact['url']]
|
||||
);
|
||||
|
||||
|
@ -1243,7 +1243,7 @@ class PostUpdate
|
|||
|
||||
DBA::update(
|
||||
'contact',
|
||||
['gsid' => GServer::getID($server, true), 'baseurl' => GServer::cleanURL($server)],
|
||||
['gsid' => GServer::getRealID($server, true), 'baseurl' => GServer::cleanURL($server)],
|
||||
['id' => $contact['id']]
|
||||
);
|
||||
|
||||
|
|
45
src/Event/ArrayFilterEvent.php
Normal file
45
src/Event/ArrayFilterEvent.php
Normal file
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
// Copyright (C) 2010-2024, the Friendica project
|
||||
// SPDX-FileCopyrightText: 2010-2024 the Friendica project
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Friendica\Event;
|
||||
|
||||
/**
|
||||
* Allow Event listener to modify an array.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class ArrayFilterEvent extends Event
|
||||
{
|
||||
public const APP_MENU = 'friendica.data.app_menu';
|
||||
|
||||
public const NAV_INFO = 'friendica.data.nav_info';
|
||||
|
||||
public const FEATURE_ENABLED = 'friendica.data.feature_enabled';
|
||||
|
||||
public const FEATURE_GET = 'friendica.data.feature_get';
|
||||
|
||||
private array $array;
|
||||
|
||||
public function __construct(string $name, array $array)
|
||||
{
|
||||
parent::__construct($name);
|
||||
|
||||
$this->array = $array;
|
||||
}
|
||||
|
||||
public function getArray(): array
|
||||
{
|
||||
return $this->array;
|
||||
}
|
||||
|
||||
public function setArray(array $array): void
|
||||
{
|
||||
$this->array = $array;
|
||||
}
|
||||
}
|
36
src/Event/ConfigLoadedEvent.php
Normal file
36
src/Event/ConfigLoadedEvent.php
Normal file
|
@ -0,0 +1,36 @@
|
|||
<?php
|
||||
|
||||
// Copyright (C) 2010-2024, the Friendica project
|
||||
// SPDX-FileCopyrightText: 2010-2024 the Friendica project
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Friendica\Event;
|
||||
|
||||
use Friendica\Core\Config\Util\ConfigFileManager;
|
||||
|
||||
/**
|
||||
* Notify that the config was loaded
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class ConfigLoadedEvent extends Event
|
||||
{
|
||||
public const CONFIG_LOADED = 'friendica.config_loaded';
|
||||
|
||||
private ConfigFileManager $config;
|
||||
|
||||
public function __construct(string $name, ConfigFileManager $config)
|
||||
{
|
||||
parent::__construct($name);
|
||||
|
||||
$this->config = $config;
|
||||
}
|
||||
|
||||
public function getConfig(): ConfigFileManager
|
||||
{
|
||||
return $this->config;
|
||||
}
|
||||
}
|
35
src/Event/Event.php
Normal file
35
src/Event/Event.php
Normal file
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
// Copyright (C) 2010-2024, the Friendica project
|
||||
// SPDX-FileCopyrightText: 2010-2024 the Friendica project
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Friendica\Event;
|
||||
|
||||
/**
|
||||
* One-way Event to inform listener about something happend.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
class Event implements NamedEvent
|
||||
{
|
||||
/**
|
||||
* Friendica is initialized.
|
||||
*/
|
||||
public const INIT = 'friendica.init';
|
||||
|
||||
private string $name;
|
||||
|
||||
public function __construct(string $name)
|
||||
{
|
||||
$this->name = $name;
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
}
|
37
src/Event/EventDispatcher.php
Normal file
37
src/Event/EventDispatcher.php
Normal file
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
// Copyright (C) 2010-2024, the Friendica project
|
||||
// SPDX-FileCopyrightText: 2010-2024 the Friendica project
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Friendica\Event;
|
||||
|
||||
use Symfony\Component\EventDispatcher\EventDispatcher as SymfonyEventDispatcher;
|
||||
|
||||
/**
|
||||
* Modified Event Dispatcher.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class EventDispatcher extends SymfonyEventDispatcher
|
||||
{
|
||||
/**
|
||||
* Add support for named events.
|
||||
*
|
||||
* @template T of object
|
||||
* @param T $event
|
||||
*
|
||||
* @return T The passed $event MUST be returned
|
||||
*/
|
||||
public function dispatch(object $event, ?string $eventName = null): object
|
||||
{
|
||||
if ($eventName === null && $event instanceof NamedEvent) {
|
||||
$eventName = $event->getName();
|
||||
}
|
||||
|
||||
return parent::dispatch($event, $eventName);
|
||||
}
|
||||
}
|
47
src/Event/HtmlFilterEvent.php
Normal file
47
src/Event/HtmlFilterEvent.php
Normal file
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
|
||||
// Copyright (C) 2010-2024, the Friendica project
|
||||
// SPDX-FileCopyrightText: 2010-2024 the Friendica project
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Friendica\Event;
|
||||
|
||||
/**
|
||||
* Allow Event listener to modify HTML.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class HtmlFilterEvent extends Event
|
||||
{
|
||||
public const HEAD = 'friendica.html.head';
|
||||
|
||||
public const FOOTER = 'friendica.html.footer';
|
||||
|
||||
public const PAGE_HEADER = 'friendica.html.page_header';
|
||||
|
||||
public const PAGE_CONTENT_TOP = 'friendica.html.page_content_top';
|
||||
|
||||
public const PAGE_END = 'friendica.html.page_end';
|
||||
|
||||
private string $html;
|
||||
|
||||
public function __construct(string $name, string $html)
|
||||
{
|
||||
parent::__construct($name);
|
||||
|
||||
$this->html = $html;
|
||||
}
|
||||
|
||||
public function getHtml(): string
|
||||
{
|
||||
return $this->html;
|
||||
}
|
||||
|
||||
public function setHtml(string $html): void
|
||||
{
|
||||
$this->html = $html;
|
||||
}
|
||||
}
|
20
src/Event/NamedEvent.php
Normal file
20
src/Event/NamedEvent.php
Normal file
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
// Copyright (C) 2010-2024, the Friendica project
|
||||
// SPDX-FileCopyrightText: 2010-2024 the Friendica project
|
||||
//
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Friendica\Event;
|
||||
|
||||
/**
|
||||
* Interface for named events.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
interface NamedEvent
|
||||
{
|
||||
public function getName(): string;
|
||||
}
|
|
@ -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'));
|
||||
|
@ -415,7 +426,7 @@ class APContact
|
|||
}
|
||||
|
||||
if (!empty($apcontact['baseurl']) && empty($fetched_contact['gsid'])) {
|
||||
$apcontact['gsid'] = GServer::getID($apcontact['baseurl']);
|
||||
$apcontact['gsid'] = GServer::getRealID($apcontact['baseurl']);
|
||||
} elseif (!empty($fetched_contact['gsid'])) {
|
||||
$apcontact['gsid'] = $fetched_contact['gsid'];
|
||||
} else {
|
||||
|
|
|
@ -167,7 +167,7 @@ class Contact
|
|||
public static function insert(array $fields, int $duplicate_mode = Database::INSERT_DEFAULT): int
|
||||
{
|
||||
if (!empty($fields['baseurl']) && empty($fields['gsid'])) {
|
||||
$fields['gsid'] = GServer::getID($fields['baseurl'], true);
|
||||
$fields['gsid'] = GServer::getRealID($fields['baseurl'], true);
|
||||
}
|
||||
|
||||
$fields['uri-id'] = ItemURI::getIdByURI($fields['url']);
|
||||
|
@ -913,7 +913,7 @@ class Contact
|
|||
$fields['unsearchable'] = !$profile['net-publish'];
|
||||
$fields['manually-approve'] = in_array($user['page-flags'], [User::PAGE_FLAGS_NORMAL, User::PAGE_FLAGS_PRVGROUP, User::PAGE_FLAGS_COMM_MAN]);
|
||||
$fields['baseurl'] = DI::baseUrl();
|
||||
$fields['gsid'] = GServer::getID($fields['baseurl'], true);
|
||||
$fields['gsid'] = GServer::getRealID($fields['baseurl'], true);
|
||||
|
||||
$update = false;
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ namespace Friendica\Model\Contact;
|
|||
use Exception;
|
||||
use Friendica\Content\Widget;
|
||||
use Friendica\Core\Protocol;
|
||||
use Friendica\Core\Worker;
|
||||
use Friendica\Database\Database;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\DI;
|
||||
|
@ -22,6 +23,7 @@ use Friendica\Protocol\Activity;
|
|||
use Friendica\Protocol\ActivityPub;
|
||||
use Friendica\Util\DateTimeFormat;
|
||||
use Friendica\Util\Strings;
|
||||
use Friendica\Worker\AddContact;
|
||||
|
||||
/**
|
||||
* This class provides relationship information based on the `contact-relation` table.
|
||||
|
@ -162,20 +164,22 @@ class Relation
|
|||
$following_counter = 0;
|
||||
|
||||
DI::logger()->info('Discover contacts', ['id' => $target, 'url' => $url, 'contacts' => count($contacts)]);
|
||||
foreach ($contacts as $contact) {
|
||||
$actor = Contact::getIdForURL($contact);
|
||||
if (!empty($actor)) {
|
||||
if (in_array($contact, $followers)) {
|
||||
$fields = ['cid' => $target, 'relation-cid' => $actor, 'follows' => true, 'follow-updated' => DateTimeFormat::utcNow()];
|
||||
foreach ($contacts as $contact_url) {
|
||||
$contact = Contact::getByURL($contact_url, false, ['id']);
|
||||
if (!empty($contact['id'])) {
|
||||
if (in_array($contact_url, $followers)) {
|
||||
$fields = ['cid' => $target, 'relation-cid' => $contact['id'], 'follows' => true, 'follow-updated' => DateTimeFormat::utcNow()];
|
||||
DBA::insert('contact-relation', $fields, Database::INSERT_UPDATE);
|
||||
$follower_counter++;
|
||||
}
|
||||
|
||||
if (in_array($contact, $followings)) {
|
||||
$fields = ['cid' => $actor, 'relation-cid' => $target, 'follows' => true, 'follow-updated' => DateTimeFormat::utcNow()];
|
||||
if (in_array($contact_url, $followings)) {
|
||||
$fields = ['cid' => $contact['id'], 'relation-cid' => $target, 'follows' => true, 'follow-updated' => DateTimeFormat::utcNow()];
|
||||
DBA::insert('contact-relation', $fields, Database::INSERT_UPDATE);
|
||||
$following_counter++;
|
||||
}
|
||||
} else {
|
||||
AddContact::add(Worker::PRIORITY_LOW, 0, $contact_url);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -131,6 +131,32 @@ class GServer
|
|||
return self::getID($url, true);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the real ID for the given server URL, follows redirects.
|
||||
*
|
||||
* @param string $url
|
||||
* @param boolean $no_check Don't check if the server hadn't been found
|
||||
*
|
||||
* @return int|null gserver id or NULL on empty URL or failed check
|
||||
*/
|
||||
public static function getRealID(string $url, bool $no_check = false): ?int
|
||||
{
|
||||
$gsid = self::getID($url, $no_check);
|
||||
if (empty($gsid)) {
|
||||
return $gsid;
|
||||
}
|
||||
|
||||
$gserver = DBA::selectFirst('gserver', ['redirect-gsid'], ['id' => $gsid]);
|
||||
if (!empty($gserver['redirect-gsid'])) {
|
||||
$redirect = DBA::selectFirst('gserver', ['failed'], ['id' => $gserver['redirect-gsid']]);
|
||||
if (isset($redirect['failed']) && !$redirect['failed']) {
|
||||
return $gserver['redirect-gsid'];
|
||||
}
|
||||
}
|
||||
return $gsid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all the servers which base domain are matching the provided domain pattern
|
||||
*
|
||||
|
@ -569,8 +595,13 @@ class GServer
|
|||
(((parse_url($url, PHP_URL_HOST) != parse_url($valid_url, PHP_URL_HOST)) || (parse_url($url, PHP_URL_PATH) != parse_url($valid_url, PHP_URL_PATH))) && empty(parse_url($valid_url, PHP_URL_PATH)))) {
|
||||
DI::logger()->debug('Found redirect. Mark old entry as failure', ['old' => $url, 'new' => $valid_url]);
|
||||
self::setFailureByUrl($url);
|
||||
if (!self::getID($valid_url, true) && !Network::isUrlBlocked($valid_url)) {
|
||||
$target_id = self::getID($valid_url, true);
|
||||
if (!$target_id && !Network::isUrlBlocked($valid_url)) {
|
||||
self::detect($valid_url, $network, $only_nodeinfo);
|
||||
$target_id = self::getID($valid_url, true);
|
||||
}
|
||||
if ($target_id) {
|
||||
self::update(['redirect-gsid' => $target_id], ['nurl' => Strings::normaliseLink($url)]);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
@ -817,6 +848,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 +892,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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
@ -3498,6 +3182,10 @@ class Item
|
|||
}
|
||||
|
||||
if (!empty($shared_item['uri-id'])) {
|
||||
if (!$s) {
|
||||
DI::logger()->notice('Unexpected empty item HTML', ['item' => $item]);
|
||||
}
|
||||
|
||||
$s = self::replacePlatformIcon($s, $shared_item, $uid);
|
||||
}
|
||||
|
||||
|
@ -3574,6 +3262,10 @@ class Item
|
|||
*/
|
||||
private static function replacePlatformIcon(string $html, array $item, int $uid): string
|
||||
{
|
||||
if ($html === '') {
|
||||
return $html;
|
||||
}
|
||||
|
||||
$dom = new \DOMDocument();
|
||||
if (!@$dom->loadHTML($html)) {
|
||||
return $html;
|
||||
|
|
404
src/Model/ItemHelper.php
Normal file
404
src/Model/ItemHelper.php
Normal 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
|
||||
}
|
||||
}
|
|
@ -10,6 +10,7 @@ namespace Friendica\Model\Post;
|
|||
use Friendica\Content\PageInfo;
|
||||
use Friendica\Content\Text\BBCode;
|
||||
use Friendica\Core\Protocol;
|
||||
use Friendica\Core\System;
|
||||
use Friendica\Database\Database;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\DI;
|
||||
|
@ -255,6 +256,7 @@ class Media
|
|||
|
||||
private static function isFederatedServer(string $url): bool
|
||||
{
|
||||
try {
|
||||
$baseurl = Network::getBaseUrl(new Uri($url));
|
||||
if (empty($baseurl)) {
|
||||
return false;
|
||||
|
@ -265,6 +267,10 @@ class Media
|
|||
}
|
||||
|
||||
return DBA::exists('gserver', ['nurl' => Strings::normaliseLink($baseurl), 'network' => Protocol::FEDERATED]);
|
||||
} catch(\Throwable $e) {
|
||||
DI::logger()->notice('Invalid URL provided', ['url' => $url, 'exception' => $e, 'callstack' => System::callstack(10)]);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static function addPreviewData(array $media): array
|
||||
|
@ -790,7 +796,7 @@ class Media
|
|||
}
|
||||
|
||||
// Search for links with descriptions
|
||||
if (preg_match_all("/\[url\=(https?:.*?)\].*?\[\/url\]/ism", $body, $matches)) {
|
||||
if (preg_match_all("#\[url=(https?://.+?)].+?\[/url]#ism", $body, $matches)) {
|
||||
foreach ($matches[1] as $url) {
|
||||
DI::logger()->info('Got page url (link with description)', ['uri-id' => $uriid, 'url' => $url]);
|
||||
$result = self::insert(['uri-id' => $uriid, 'type' => self::UNKNOWN, 'url' => $url], false);
|
||||
|
|
|
@ -241,7 +241,7 @@ class User
|
|||
$system['thumb'] = Contact::getDefaultAvatar($system, Proxy::SIZE_THUMB);
|
||||
$system['micro'] = Contact::getDefaultAvatar($system, Proxy::SIZE_MICRO);
|
||||
$system['nurl'] = Strings::normaliseLink($system['url']);
|
||||
$system['gsid'] = GServer::getID($system['baseurl']);
|
||||
$system['gsid'] = GServer::getRealID($system['baseurl']);
|
||||
|
||||
Contact::insert($system);
|
||||
}
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
namespace Friendica\Module\Admin\Addons;
|
||||
|
||||
use Friendica\Content\Text\Markdown;
|
||||
use Friendica\Core\Addon;
|
||||
use Friendica\Core\Renderer;
|
||||
use Friendica\DI;
|
||||
use Friendica\Module\BaseAdmin;
|
||||
|
@ -42,12 +41,12 @@ class Details extends BaseAdmin
|
|||
{
|
||||
parent::content();
|
||||
|
||||
$addons_admin = Addon::getAdminList();
|
||||
$addonHelper = DI::addonHelper();
|
||||
|
||||
$addon = Strings::sanitizeFilePathItem($this->parameters['addon']);
|
||||
if (!is_file("addon/$addon/$addon.php")) {
|
||||
DI::sysmsg()->addNotice(DI::l10n()->t('Addon not found.'));
|
||||
Addon::uninstall($addon);
|
||||
$addonHelper->uninstallAddon($addon);
|
||||
DI::baseUrl()->redirect('admin/addons');
|
||||
}
|
||||
|
||||
|
@ -55,11 +54,11 @@ class Details extends BaseAdmin
|
|||
self::checkFormSecurityTokenRedirectOnError('/admin/addons', 'admin_addons_details', 't');
|
||||
|
||||
// Toggle addon status
|
||||
if (Addon::isEnabled($addon)) {
|
||||
Addon::uninstall($addon);
|
||||
if ($addonHelper->isAddonEnabled($addon)) {
|
||||
$addonHelper->uninstallAddon($addon);
|
||||
DI::sysmsg()->addInfo(DI::l10n()->t('Addon %s disabled.', $addon));
|
||||
} else {
|
||||
Addon::install($addon);
|
||||
$addonHelper->installAddon($addon);
|
||||
DI::sysmsg()->addInfo(DI::l10n()->t('Addon %s enabled.', $addon));
|
||||
}
|
||||
|
||||
|
@ -67,7 +66,7 @@ class Details extends BaseAdmin
|
|||
}
|
||||
|
||||
// display addon details
|
||||
if (Addon::isEnabled($addon)) {
|
||||
if ($addonHelper->isAddonEnabled($addon)) {
|
||||
$status = 'on';
|
||||
$action = DI::l10n()->t('Disable');
|
||||
} else {
|
||||
|
@ -82,13 +81,17 @@ class Details extends BaseAdmin
|
|||
$readme = '<pre>' . file_get_contents("addon/$addon/README") . '</pre>';
|
||||
}
|
||||
|
||||
$addons_admin = $addonHelper->getEnabledAddonsWithAdminSettings();
|
||||
|
||||
$admin_form = '';
|
||||
if (array_key_exists($addon, $addons_admin)) {
|
||||
if (in_array($addon, $addons_admin)) {
|
||||
require_once "addon/$addon/$addon.php";
|
||||
$func = $addon . '_addon_admin';
|
||||
$func($admin_form);
|
||||
}
|
||||
|
||||
$addonInfo = $addonHelper->getAddonInfo($addon);
|
||||
|
||||
$t = Renderer::getMarkupTemplate('admin/addons/details.tpl');
|
||||
|
||||
return Renderer::replaceMacros($t, [
|
||||
|
@ -100,7 +103,13 @@ class Details extends BaseAdmin
|
|||
'$addon' => $addon,
|
||||
'$status' => $status,
|
||||
'$action' => $action,
|
||||
'$info' => Addon::getInfo($addon),
|
||||
'$info' => [
|
||||
'name' => $addonInfo->getName(),
|
||||
'version' => $addonInfo->getVersion(),
|
||||
'description' => $addonInfo->getDescription(),
|
||||
'author' => $addonInfo->getAuthors(),
|
||||
'maintainer' => $addonInfo->getMaintainers(),
|
||||
],
|
||||
'$str_author' => DI::l10n()->t('Author: '),
|
||||
'$str_maintainer' => DI::l10n()->t('Maintainer: '),
|
||||
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
|
||||
namespace Friendica\Module\Admin\Addons;
|
||||
|
||||
use Friendica\Core\Addon;
|
||||
use Friendica\Core\Renderer;
|
||||
use Friendica\DI;
|
||||
use Friendica\Module\BaseAdmin;
|
||||
|
@ -24,22 +23,25 @@ class Index extends BaseAdmin
|
|||
{
|
||||
parent::content();
|
||||
|
||||
$addonHelper = DI::addonHelper();
|
||||
|
||||
// reload active themes
|
||||
if (!empty($_GET['action'])) {
|
||||
self::checkFormSecurityTokenRedirectOnError('/admin/addons', 'admin_addons', 't');
|
||||
|
||||
switch ($_GET['action']) {
|
||||
case 'reload':
|
||||
Addon::reload();
|
||||
$addonHelper->reloadAddons();
|
||||
DI::sysmsg()->addInfo(DI::l10n()->t('Addons reloaded'));
|
||||
break;
|
||||
|
||||
case 'toggle':
|
||||
$addon = $_GET['addon'] ?? '';
|
||||
if (Addon::isEnabled($addon)) {
|
||||
Addon::uninstall($addon);
|
||||
|
||||
if ($addonHelper->isAddonEnabled($addon)) {
|
||||
$addonHelper->uninstallAddon($addon);
|
||||
DI::sysmsg()->addInfo(DI::l10n()->t('Addon %s disabled.', $addon));
|
||||
} elseif (Addon::install($addon)) {
|
||||
} elseif ($addonHelper->installAddon($addon)) {
|
||||
DI::sysmsg()->addInfo(DI::l10n()->t('Addon %s enabled.', $addon));
|
||||
} else {
|
||||
DI::sysmsg()->addNotice(DI::l10n()->t('Addon %s failed to install.', $addon));
|
||||
|
@ -52,7 +54,23 @@ class Index extends BaseAdmin
|
|||
DI::baseUrl()->redirect('admin/addons');
|
||||
}
|
||||
|
||||
$addons = Addon::getAvailableList();
|
||||
$addons = [];
|
||||
|
||||
foreach ($addonHelper->getAvailableAddons() as $addonId) {
|
||||
$addonInfo = $addonHelper->getAddonInfo($addonId);
|
||||
|
||||
$info = [
|
||||
'name' => $addonInfo->getName(),
|
||||
'description' => $addonInfo->getDescription(),
|
||||
'version' => $addonInfo->getVersion(),
|
||||
];
|
||||
|
||||
$addons[] = [
|
||||
$addonId,
|
||||
($addonHelper->isAddonEnabled($addonId) ? 'on' : 'off'),
|
||||
$info,
|
||||
];
|
||||
}
|
||||
|
||||
$t = Renderer::getMarkupTemplate('admin/addons/index.tpl');
|
||||
return Renderer::replaceMacros($t, [
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
namespace Friendica\Module\Admin;
|
||||
|
||||
use Friendica\App;
|
||||
use Friendica\Core\Addon;
|
||||
use Friendica\Core\Config\ValueObject\Cache;
|
||||
use Friendica\Core\Renderer;
|
||||
use Friendica\Core\Update;
|
||||
|
@ -28,6 +27,7 @@ class Summary extends BaseAdmin
|
|||
parent::content();
|
||||
|
||||
$basePath = DI::appHelper()->getBasePath();
|
||||
$addonPath = DI::addonHelper()->getAddonPath();
|
||||
|
||||
// are there MyISAM tables in the DB? If so, trigger a warning message
|
||||
$warningtext = [];
|
||||
|
@ -101,8 +101,12 @@ class Summary extends BaseAdmin
|
|||
// Check server vitality
|
||||
if (!self::checkSelfHostMeta()) {
|
||||
$well_known = DI::baseUrl() . Probe::HOST_META;
|
||||
$warningtext[] = DI::l10n()->t('<a href="%s">%s</a> is not reachable on your system. This is a severe configuration issue that prevents server to server communication. See <a href="%s">the installation page</a> for help.',
|
||||
$well_known, $well_known, DI::baseUrl() . '/help/Install');
|
||||
$warningtext[] = DI::l10n()->t(
|
||||
'<a href="%s">%s</a> is not reachable on your system. This is a severe configuration issue that prevents server to server communication. See <a href="%s">the installation page</a> for help.',
|
||||
$well_known,
|
||||
$well_known,
|
||||
DI::baseUrl() . '/help/Install'
|
||||
);
|
||||
}
|
||||
|
||||
// Check logfile permission
|
||||
|
@ -114,7 +118,7 @@ class Summary extends BaseAdmin
|
|||
}
|
||||
|
||||
// check legacy basepath settings
|
||||
$configLoader = (new Config())->createConfigFileManager($basePath, $_SERVER);
|
||||
$configLoader = (new Config())->createConfigFileManager($basePath, $addonPath, $_SERVER);
|
||||
$configCache = new Cache();
|
||||
$configLoader->setupCache($configCache);
|
||||
$confBasepath = $configCache->get('system', 'basepath');
|
||||
|
@ -125,25 +129,31 @@ class Summary extends BaseAdmin
|
|||
'from' => $currBasepath,
|
||||
'to' => $confBasepath,
|
||||
]);
|
||||
$warningtext[] = DI::l10n()->t('Friendica\'s system.basepath was updated from \'%s\' to \'%s\'. Please remove the system.basepath from your db to avoid differences.',
|
||||
$warningtext[] = DI::l10n()->t(
|
||||
'Friendica\'s system.basepath was updated from \'%s\' to \'%s\'. Please remove the system.basepath from your db to avoid differences.',
|
||||
$currBasepath,
|
||||
$confBasepath);
|
||||
$confBasepath
|
||||
);
|
||||
} elseif (!is_dir($currBasepath)) {
|
||||
DI::logger()->alert('Friendica\'s system.basepath is wrong.', [
|
||||
'from' => $currBasepath,
|
||||
'to' => $confBasepath,
|
||||
]);
|
||||
$warningtext[] = DI::l10n()->t('Friendica\'s current system.basepath \'%s\' is wrong and the config file \'%s\' isn\'t used.',
|
||||
$warningtext[] = DI::l10n()->t(
|
||||
'Friendica\'s current system.basepath \'%s\' is wrong and the config file \'%s\' isn\'t used.',
|
||||
$currBasepath,
|
||||
$confBasepath);
|
||||
$confBasepath
|
||||
);
|
||||
} else {
|
||||
DI::logger()->alert('Friendica\'s system.basepath is wrong.', [
|
||||
'from' => $currBasepath,
|
||||
'to' => $confBasepath,
|
||||
]);
|
||||
$warningtext[] = DI::l10n()->t('Friendica\'s current system.basepath \'%s\' is not equal to the config file \'%s\'. Please fix your configuration.',
|
||||
$warningtext[] = DI::l10n()->t(
|
||||
'Friendica\'s current system.basepath \'%s\' is not equal to the config file \'%s\'. Please fix your configuration.',
|
||||
$currBasepath,
|
||||
$confBasepath);
|
||||
$confBasepath
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -177,7 +187,7 @@ class Summary extends BaseAdmin
|
|||
'$platform' => App::PLATFORM,
|
||||
'$codename' => App::CODENAME,
|
||||
'$build' => DI::config()->get('system', 'build'),
|
||||
'$addons' => [DI::l10n()->t('Active addons'), Addon::getEnabledList()],
|
||||
'$addons' => [DI::l10n()->t('Active addons'), DI::addonHelper()->getEnabledAddons()],
|
||||
'$serversettings' => $server_settings,
|
||||
'$warningtext' => $warningtext,
|
||||
]);
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
namespace Friendica\Module;
|
||||
|
||||
use Friendica\BaseModule;
|
||||
use Friendica\Core\Addon;
|
||||
use Friendica\Core\Renderer;
|
||||
use Friendica\DI;
|
||||
use Friendica\Network\HTTPException;
|
||||
|
@ -98,9 +97,19 @@ abstract class BaseAdmin extends BaseModule
|
|||
]],
|
||||
];
|
||||
|
||||
$addons_admin = [];
|
||||
|
||||
foreach (DI::addonHelper()->getEnabledAddonsWithAdminSettings() as $addonId) {
|
||||
$addons_admin[$addonId] = [
|
||||
'url' => 'admin/addons/' . $addonId,
|
||||
'name' => $addonId,
|
||||
'class' => 'addon',
|
||||
];
|
||||
}
|
||||
|
||||
$t = Renderer::getMarkupTemplate('admin/aside.tpl');
|
||||
DI::page()['aside'] .= Renderer::replaceMacros($t, [
|
||||
'$admin' => ['addons_admin' => Addon::getAdminList()],
|
||||
'$admin' => ['addons_admin' => $addons_admin],
|
||||
'$subpages' => $aside_sub,
|
||||
'$admtxt' => DI::l10n()->t('Admin'),
|
||||
'$plugadmtxt' => DI::l10n()->t('Addon Features'),
|
||||
|
|
|
@ -134,6 +134,6 @@ class BaseProfile extends BaseModule
|
|||
|
||||
$tpl = Renderer::getMarkupTemplate('common_tabs.tpl');
|
||||
|
||||
return Renderer::replaceMacros($tpl, ['$tabs' => $arr['tabs']]);
|
||||
return Renderer::replaceMacros($tpl, ['$tabs' => $arr['tabs'], '$more' => DI::l10n()->t('More')]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -154,7 +154,7 @@ class BaseSettings extends BaseModule
|
|||
$tabs[] = [
|
||||
'label' => $this->t('Import Contacts'),
|
||||
'url' => 'settings/importcontacts',
|
||||
'selected' => static::class == Settings\UserExport::class ? 'active' : '',
|
||||
'selected' => static::class == Settings\ContactImport::class ? 'active' : '',
|
||||
'accesskey' => '',
|
||||
];
|
||||
|
||||
|
|
|
@ -21,7 +21,6 @@ use Friendica\Core\Session\Capability\IHandleUserSessions;
|
|||
use Friendica\Core\Theme;
|
||||
use Friendica\Model\Event;
|
||||
use Friendica\Model\Profile;
|
||||
use Friendica\Model\User;
|
||||
use Friendica\Module\BaseProfile;
|
||||
use Friendica\Module\Response;
|
||||
use Friendica\Module\Security\Login;
|
||||
|
@ -119,6 +118,8 @@ class Show extends BaseModule
|
|||
'$week' => $this->t('week'),
|
||||
'$day' => $this->t('day'),
|
||||
'$list' => $this->t('list'),
|
||||
'$prev' => $this->t('prev'),
|
||||
'$next' => $this->t('next'),
|
||||
]);
|
||||
|
||||
return $o;
|
||||
|
|
|
@ -387,7 +387,7 @@ class Contact extends BaseModule
|
|||
];
|
||||
|
||||
$tabs_tpl = Renderer::getMarkupTemplate('common_tabs.tpl');
|
||||
$tabs_html = Renderer::replaceMacros($tabs_tpl, ['$tabs' => $tabs]);
|
||||
$tabs_html = Renderer::replaceMacros($tabs_tpl, ['$tabs' => $tabs, '$more' => DI::l10n()->t('More')]);
|
||||
|
||||
switch ($rel) {
|
||||
case 'followers':
|
||||
|
@ -534,7 +534,7 @@ class Contact extends BaseModule
|
|||
}
|
||||
|
||||
$tab_tpl = Renderer::getMarkupTemplate('common_tabs.tpl');
|
||||
$tab_str = Renderer::replaceMacros($tab_tpl, ['$tabs' => $tabs]);
|
||||
$tab_str = Renderer::replaceMacros($tab_tpl, ['$tabs' => $tabs, '$more' => DI::l10n()->t('More')]);
|
||||
|
||||
return $tab_str;
|
||||
}
|
||||
|
|
|
@ -95,7 +95,7 @@ class Channel extends Timeline
|
|||
$tabs = array_merge($tabs, $this->getTabArray($this->community->getTimelines(true), 'channel'));
|
||||
|
||||
$tab_tpl = Renderer::getMarkupTemplate('common_tabs.tpl');
|
||||
$o .= Renderer::replaceMacros($tab_tpl, ['$tabs' => $tabs]);
|
||||
$o .= Renderer::replaceMacros($tab_tpl, ['$tabs' => $tabs, '$more' => $this->l10n->t('More')]);
|
||||
|
||||
Nav::setSelected('channel');
|
||||
|
||||
|
|
|
@ -88,7 +88,7 @@ class Community extends Timeline
|
|||
if (!$this->raw) {
|
||||
$tabs = $this->getTabArray($this->community->getTimelines($this->session->isAuthenticated()), 'community');
|
||||
$tab_tpl = Renderer::getMarkupTemplate('common_tabs.tpl');
|
||||
$o .= Renderer::replaceMacros($tab_tpl, ['$tabs' => $tabs]);
|
||||
$o .= Renderer::replaceMacros($tab_tpl, ['$tabs' => $tabs, '$more' => $this->l10n->t('More')]);
|
||||
|
||||
Nav::setSelected('community');
|
||||
|
||||
|
|
|
@ -292,7 +292,7 @@ class Network extends Timeline
|
|||
|
||||
$tpl = Renderer::getMarkupTemplate('common_tabs.tpl');
|
||||
|
||||
return Renderer::replaceMacros($tpl, ['$tabs' => $tabs]);
|
||||
return Renderer::replaceMacros($tpl, ['$tabs' => $tabs, '$more' => $this->l10n->t('More')]);
|
||||
}
|
||||
|
||||
protected function parseRequest(array $request)
|
||||
|
@ -344,6 +344,7 @@ class Network extends Timeline
|
|||
// since otherwise the feed will optically jump, when some already visible thread has been updated.
|
||||
if ($this->update && ($this->selectedTab == NetworkEntity::COMMENTED)) {
|
||||
$this->order = 'received';
|
||||
|
||||
$request['last_received'] = $request['last_commented'] ?? null;
|
||||
$request['first_received'] = $request['first_commented'] ?? null;
|
||||
}
|
||||
|
|
|
@ -691,13 +691,16 @@ class Timeline extends BaseModule
|
|||
$items = $this->selectItems();
|
||||
$key = '';
|
||||
|
||||
$maxpostperauthor = 0;
|
||||
if ($this->selectedTab == Community::LOCAL) {
|
||||
$maxpostperauthor = (int)$this->config->get('system', 'max_author_posts_community_page');
|
||||
$key = 'author-id';
|
||||
} elseif ($this->selectedTab == Community::GLOBAL) {
|
||||
$maxpostperauthor = (int)$this->config->get('system', 'max_server_posts_community_page');
|
||||
$key = 'author-gsid';
|
||||
} else {
|
||||
}
|
||||
|
||||
if ($maxpostperauthor === 0) {
|
||||
$this->setItemsSeenByCondition([
|
||||
'unseen' => true,
|
||||
'uid' => $this->session->getLocalUserId(),
|
||||
|
|
|
@ -56,7 +56,7 @@ class SaveTag extends BaseModule
|
|||
|
||||
$tpl = Renderer::getMarkupTemplate("filer_dialog.tpl");
|
||||
echo Renderer::replaceMacros($tpl, [
|
||||
'$field' => ['term', $this->t("Save to Folder:"), '', '', $filetags, $this->t('- select -')],
|
||||
'$field' => ['term', $this->t("Folder:"), '', '', $filetags, $this->t('- select -')],
|
||||
'$submit' => $this->t('Save'),
|
||||
]);
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ use Friendica\App;
|
|||
use Friendica\App\Arguments;
|
||||
use Friendica\App\BaseURL;
|
||||
use Friendica\BaseModule;
|
||||
use Friendica\Core\Addon;
|
||||
use Friendica\Core\Addon\AddonHelper;
|
||||
use Friendica\Core\Config\Capability\IManageConfigValues;
|
||||
use Friendica\Core\Hook;
|
||||
use Friendica\Core\KeyValueStorage\Capability\IManageKeyValuePairs;
|
||||
|
@ -31,6 +31,7 @@ use Psr\Log\LoggerInterface;
|
|||
*/
|
||||
class Friendica extends BaseModule
|
||||
{
|
||||
private AddonHelper $addonHelper;
|
||||
/** @var IManageConfigValues */
|
||||
private $config;
|
||||
/** @var IManageKeyValuePairs */
|
||||
|
@ -38,18 +39,19 @@ class Friendica extends BaseModule
|
|||
/** @var IHandleUserSessions */
|
||||
private $session;
|
||||
|
||||
public function __construct(IHandleUserSessions $session, IManageKeyValuePairs $keyValue, IManageConfigValues $config, L10n $l10n, BaseURL $baseUrl, Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, array $server, array $parameters = [])
|
||||
public function __construct(AddonHelper $addonHelper, IHandleUserSessions $session, IManageKeyValuePairs $keyValue, IManageConfigValues $config, L10n $l10n, BaseURL $baseUrl, Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, array $server, array $parameters = [])
|
||||
{
|
||||
parent::__construct($l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters);
|
||||
|
||||
$this->config = $config;
|
||||
$this->keyValue = $keyValue;
|
||||
$this->session = $session;
|
||||
$this->addonHelper = $addonHelper;
|
||||
}
|
||||
|
||||
protected function content(array $request = []): string
|
||||
{
|
||||
$visibleAddonList = Addon::getVisibleList();
|
||||
$visibleAddonList = $this->addonHelper->getVisibleEnabledAddons();
|
||||
if (!empty($visibleAddonList)) {
|
||||
|
||||
$sorted = $visibleAddonList;
|
||||
|
@ -102,11 +104,13 @@ class Friendica extends BaseModule
|
|||
$tpl = Renderer::getMarkupTemplate('friendica.tpl');
|
||||
|
||||
return Renderer::replaceMacros($tpl, [
|
||||
'about' => $this->t('This is Friendica, version %s that is running at the web location %s. The database version is %s, the post update version is %s.',
|
||||
'about' => $this->t(
|
||||
'This is Friendica, version %s that is running at the web location %s. The database version is %s, the post update version is %s.',
|
||||
'<strong>' . App::VERSION . '</strong>',
|
||||
$this->baseUrl,
|
||||
'<strong>' . $this->config->get('system', 'build') . '/' . DB_UPDATE_VERSION . '</strong>',
|
||||
'<strong>' . $this->keyValue->get('post_update_version') . '/' . PostUpdate::VERSION . '</strong>'),
|
||||
'<strong>' . $this->keyValue->get('post_update_version') . '/' . PostUpdate::VERSION . '</strong>'
|
||||
),
|
||||
'friendica' => $this->t('Please visit <a href="https://friendi.ca">Friendi.ca</a> to learn more about the Friendica project.'),
|
||||
'bugs' => $this->t('Bug reports and issues: please visit') . ' ' . '<a href="https://github.com/friendica/friendica/issues?state=open">' . $this->t('the bugtracker at github') . '</a>',
|
||||
'info' => $this->t('Suggestions, praise, etc. - please email "info" at "friendi - dot - ca'),
|
||||
|
@ -157,7 +161,7 @@ class Friendica extends BaseModule
|
|||
];
|
||||
}
|
||||
|
||||
$visible_addons = Addon::getVisibleList();
|
||||
$visible_addons = $this->addonHelper->getVisibleEnabledAddons();
|
||||
|
||||
$this->config->reload();
|
||||
$locked_features = [];
|
||||
|
|
|
@ -235,22 +235,22 @@ class Install extends BaseModule
|
|||
'$system_url' => $this->configCache->get('system', 'url'),
|
||||
'$dbhost' => ['database-hostname',
|
||||
$this->t('Database Server Name'),
|
||||
$this->configCache->get('database', 'hostname'),
|
||||
$this->configCache->get('database', 'hostname') ? : getenv('MYSQL_HOST') ? : 'localhost',
|
||||
'',
|
||||
$this->t('Required')],
|
||||
'$dbuser' => ['database-username',
|
||||
$this->t('Database Login Name'),
|
||||
$this->configCache->get('database', 'username'),
|
||||
$this->configCache->get('database', 'username') ? : getenv('MYSQL_USER') ? : '',
|
||||
'',
|
||||
$this->t('Required'),
|
||||
'autofocus'],
|
||||
'$dbpass' => ['database-password',
|
||||
$this->t('Database Login Password'),
|
||||
$this->configCache->get('database', 'password'),
|
||||
$this->configCache->get('database', 'password') ? : getenv('MYSQL_PASSWORD') ? : '',
|
||||
$this->t("For security reasons the password must not be empty"),
|
||||
$this->t('Required')],
|
||||
'$dbdata' => ['database-database',
|
||||
$this->t('Database Name'),
|
||||
$this->t('Database Name') ? : getenv('MYSQL_DATABASE') ? : '',
|
||||
$this->configCache->get('database', 'database'),
|
||||
'',
|
||||
$this->t('Required')],
|
||||
|
|
|
@ -99,7 +99,7 @@ abstract class BaseUsers extends BaseModeration
|
|||
Hook::callAll('moderation_users_tabs', $tabs_arr);
|
||||
|
||||
$tpl = Renderer::getMarkupTemplate('common_tabs.tpl');
|
||||
return Renderer::replaceMacros($tpl, ['$tabs' => $tabs_arr['tabs']]);
|
||||
return Renderer::replaceMacros($tpl, ['$tabs' => $tabs_arr['tabs'], '$more' => $this->t('More')]);
|
||||
}
|
||||
|
||||
protected function setupUserCallback(): \Closure
|
||||
|
|
|
@ -124,8 +124,8 @@ class Active extends BaseUsers
|
|||
*/
|
||||
private function processGetActions(): void
|
||||
{
|
||||
$action = (string)$this->parameters['action'] ?? '';
|
||||
$uid = (int)$this->parameters['uid'] ?? 0;
|
||||
$action = (string) ($this->parameters['action'] ?? '');
|
||||
$uid = (int) ($this->parameters['uid'] ?? 0);
|
||||
|
||||
if ($uid === 0) {
|
||||
return;
|
||||
|
@ -150,6 +150,7 @@ class Active extends BaseUsers
|
|||
}
|
||||
|
||||
$this->baseUrl->redirect('moderation/users/active');
|
||||
// no break
|
||||
case 'block':
|
||||
self::checkFormSecurityTokenRedirectOnError('moderation/users/active', 'moderation_users_active', 't');
|
||||
User::block($uid);
|
||||
|
|
|
@ -123,8 +123,8 @@ class Blocked extends BaseUsers
|
|||
*/
|
||||
private function processGetActions(): void
|
||||
{
|
||||
$action = (string)$this->parameters['action'] ?? '';
|
||||
$uid = (int)$this->parameters['uid'] ?? 0;
|
||||
$action = (string) ($this->parameters['action'] ?? '');
|
||||
$uid = (int) ($this->parameters['uid'] ?? 0);
|
||||
|
||||
if ($uid === 0) {
|
||||
return;
|
||||
|
@ -148,6 +148,7 @@ class Blocked extends BaseUsers
|
|||
$this->systemMessages->addNotice($this->t('You can\'t remove yourself'));
|
||||
}
|
||||
$this->baseUrl->redirect('moderation/users/blocked');
|
||||
// no break
|
||||
case 'unblock':
|
||||
self::checkFormSecurityTokenRedirectOnError('/moderation/users/blocked', 'moderation_users_blocked', 't');
|
||||
User::block($uid, false);
|
||||
|
|
|
@ -135,8 +135,8 @@ class Index extends BaseUsers
|
|||
*/
|
||||
private function processGetActions(): void
|
||||
{
|
||||
$action = (string) $this->parameters['action'] ?? '';
|
||||
$uid = (int) $this->parameters['uid'] ?? 0;
|
||||
$action = (string) ($this->parameters['action'] ?? '');
|
||||
$uid = (int) ($this->parameters['uid'] ?? 0);
|
||||
|
||||
if ($uid === 0) {
|
||||
return;
|
||||
|
@ -161,11 +161,13 @@ class Index extends BaseUsers
|
|||
}
|
||||
|
||||
$this->baseUrl->redirect('moderation/users');
|
||||
// no break
|
||||
case 'block':
|
||||
self::checkFormSecurityTokenRedirectOnError('moderation/users', 'moderation_users', 't');
|
||||
User::block($uid);
|
||||
$this->systemMessages->addNotice($this->t('User "%s" blocked', $user['username']));
|
||||
$this->baseUrl->redirect('moderation/users');
|
||||
// no break
|
||||
case 'unblock':
|
||||
self::checkFormSecurityTokenRedirectOnError('moderation/users', 'moderation_users', 't');
|
||||
User::block($uid, false);
|
||||
|
|
|
@ -148,8 +148,8 @@ class Register extends BaseModule
|
|||
'$fillext' => $fillext,
|
||||
'$oidlabel' => $oidlabel,
|
||||
'$openid' => $openid_url,
|
||||
'$namelabel' => DI::l10n()->t('Your Display Name (as you would like it to be displayed on this system'),
|
||||
'$addrlabel' => DI::l10n()->t('Your Email Address: (Initial information will be send there, so this has to be an existing address.)'),
|
||||
'$namelabel' => DI::l10n()->t('Your Display Name (as you would like it to be displayed on this system):'),
|
||||
'$addrlabel' => DI::l10n()->t('Your Email Address (initial information will be sent there, so this must be a valid address):'),
|
||||
'$addrlabel2' => DI::l10n()->t('Please repeat your e-mail address:'),
|
||||
'$ask_password' => $ask_password,
|
||||
'$password1' => ['password1', DI::l10n()->t('New Password:'), '', DI::l10n()->t('Leave empty for an auto generated password.')],
|
||||
|
|
|
@ -185,7 +185,7 @@ class Channels extends BaseSettings
|
|||
'image' => ["image[$channel->code]", $this->t("Images"), $channel->mediaType & 1],
|
||||
'video' => ["video[$channel->code]", $this->t("Videos"), $channel->mediaType & 2],
|
||||
'audio' => ["audio[$channel->code]", $this->t("Audio"), $channel->mediaType & 4],
|
||||
'languages' => ["languages[$channel->code][]", $this->t('Languages'), $channel->languages ?? $channel_languages, $this->t('Select all languages that you want to see in this channel.'), $languages, 'multiple'],
|
||||
'languages' => ["languages[$channel->code][]", $this->t('Languages'), $channel->languages ?? $channel_languages, $this->t('Select all languages that you want to see in this channel. "Unspecified" describes all posts for which no language information was detected (e.g. posts with just an image or too little text to be sure of the language). If you want to see all languages, you will need to select all items in the list.'), $languages, 'multiple'],
|
||||
'publish' => $publish,
|
||||
'delete' => ["delete[$channel->code]", $this->t("Delete channel") . ' (' . $channel->label . ')', false, $this->t("Check to delete this entry from the channel list")]
|
||||
];
|
||||
|
|
|
@ -103,13 +103,7 @@ class Display extends BaseSettings
|
|||
$show_page_drop = (bool)$request['show_page_drop'];
|
||||
$display_eventlist = (bool)$request['display_eventlist'];
|
||||
$preview_mode = (int)$request['preview_mode'];
|
||||
$browser_update = (int)$request['browser_update'];
|
||||
if ($browser_update != -1) {
|
||||
$browser_update = $browser_update * 1000;
|
||||
if ($browser_update < 10000) {
|
||||
$browser_update = 10000;
|
||||
}
|
||||
}
|
||||
$update_content = (int)$request['update_content'];
|
||||
|
||||
$enabled_timelines = [];
|
||||
foreach ($enable as $code => $enabled) {
|
||||
|
@ -144,7 +138,7 @@ class Display extends BaseSettings
|
|||
|
||||
$this->pConfig->set($uid, 'system', 'itemspage_network', $itemspage_network);
|
||||
$this->pConfig->set($uid, 'system', 'itemspage_mobile_network', $itemspage_mobile_network);
|
||||
$this->pConfig->set($uid, 'system', 'update_interval', $browser_update);
|
||||
$this->pConfig->set($uid, 'system', 'update_content', $update_content);
|
||||
$this->pConfig->set($uid, 'system', 'no_smilies', !$enable_smile);
|
||||
$this->pConfig->set($uid, 'system', 'infinite_scroll', $infinite_scroll);
|
||||
$this->pConfig->set($uid, 'system', 'no_smart_threading', !$enable_smart_threading);
|
||||
|
@ -238,11 +232,7 @@ class Display extends BaseSettings
|
|||
$itemspage_mobile_network = intval($this->pConfig->get($uid, 'system', 'itemspage_mobile_network'));
|
||||
$itemspage_mobile_network = (($itemspage_mobile_network > 0 && $itemspage_mobile_network < 101) ? $itemspage_mobile_network : $this->config->get('system', 'itemspage_network_mobile'));
|
||||
|
||||
$browser_update = intval($this->pConfig->get($uid, 'system', 'update_interval'));
|
||||
if ($browser_update != -1) {
|
||||
$browser_update = (($browser_update == 0) ? 40 : $browser_update / 1000); // default if not set: 40 seconds
|
||||
}
|
||||
|
||||
$update_content = $this->pConfig->get($uid, 'system', 'update_content') ?? false;
|
||||
$enable_smile = !$this->pConfig->get($uid, 'system', 'no_smilies', false);
|
||||
$infinite_scroll = $this->pConfig->get($uid, 'system', 'infinite_scroll', false);
|
||||
$enable_smart_threading = !$this->pConfig->get($uid, 'system', 'no_smart_threading', false);
|
||||
|
@ -332,7 +322,7 @@ class Display extends BaseSettings
|
|||
|
||||
'$itemspage_network' => ['itemspage_network', $this->t('Number of items to display per page:'), $itemspage_network, $this->t('Maximum of 100 items')],
|
||||
'$itemspage_mobile_network' => ['itemspage_mobile_network', $this->t('Number of items to display per page when viewed from mobile device:'), $itemspage_mobile_network, $this->t('Maximum of 100 items')],
|
||||
'$ajaxint' => ['browser_update', $this->t('Update browser every xx seconds'), $browser_update, $this->t('Minimum of 10 seconds. Enter -1 to disable it.')],
|
||||
'$update_content' => ['update_content', $this->t('Regularly update the page content'), $update_content, $this->t('When enabled, new content on network, community and channels are added on top.')],
|
||||
'$enable_smile' => ['enable_smile', $this->t('Display emoticons'), $enable_smile, $this->t('When enabled, emoticons are replaced with matching symbols.')],
|
||||
'$infinite_scroll' => ['infinite_scroll', $this->t('Infinite scroll'), $infinite_scroll, $this->t('Automatic fetch new items when reaching the page end.')],
|
||||
'$enable_smart_threading' => ['enable_smart_threading', $this->t('Enable Smart Threading'), $enable_smart_threading, $this->t('Enable the automatic suppression of extraneous thread indentation.')],
|
||||
|
@ -353,7 +343,7 @@ class Display extends BaseSettings
|
|||
'$timelines' => $timelines,
|
||||
'$timeline_explanation' => $this->t('Enable timelines that you want to see in the channels widget. Bookmark timelines that you want to see in the top menu.'),
|
||||
|
||||
'$channel_languages' => ['channel_languages[]', $this->t('Channel languages:'), $channel_languages, $this->t('Select all languages that you want to see in your channels.'), $languages, 'multiple'],
|
||||
'$channel_languages' => ['channel_languages[]', $this->t('Channel languages:'), $channel_languages, $this->t('Select all the languages you want to see in your channels. "Unspecified" describes all posts for which no language information was detected (e.g. posts with just an image or too little text to be sure of the language). If you want to see all languages, you will need to select all items in the list.'), $languages, 'multiple'],
|
||||
|
||||
'$first_day_of_week' => ['first_day_of_week', $this->t('Beginning of week:'), $first_day_of_week, '', $weekdays, false],
|
||||
'$calendar_default_view' => ['calendar_default_view', $this->t('Default calendar view:'), $calendar_default_view, '', $calendarViews, false],
|
||||
|
|
|
@ -45,19 +45,16 @@ class Display extends DisplayModule
|
|||
$parentUriId = $item['parent-uri-id'];
|
||||
|
||||
if (empty($force)) {
|
||||
$browserUpdate = intval($this->pConfig->get($profileUid, 'system', 'update_interval') ?? 40000);
|
||||
if ($browserUpdate >= 1000) {
|
||||
$updateDate = date(DateTimeFormat::MYSQL, time() - ($browserUpdate * 2 / 1000));
|
||||
if ($this->pConfig->get($profileUid, 'system', 'update_content')) {
|
||||
$updateDate = date(DateTimeFormat::MYSQL, time() - 120);
|
||||
if (!Post::exists([
|
||||
"`parent-uri-id` = ? AND `uid` IN (?, ?) AND `received` > ?",
|
||||
$parentUriId, 0,
|
||||
$profileUid, $updateDate])) {
|
||||
$this->logger->debug('No updated content. Ending process',
|
||||
['uri-id' => $uriId, 'uid' => $profileUid, 'updated' => $updateDate]);
|
||||
$this->logger->debug('No updated content. Ending process', ['uri-id' => $uriId, 'uid' => $profileUid, 'updated' => $updateDate]);
|
||||
return '';
|
||||
} else {
|
||||
$this->logger->debug('Updated content found.',
|
||||
['uri-id' => $uriId, 'uid' => $profileUid, 'updated' => $updateDate]);
|
||||
$this->logger->debug('Updated content found.', ['uri-id' => $uriId, 'uid' => $profileUid, 'updated' => $updateDate]);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -15,7 +15,6 @@ use Friendica\Core\Config\Capability\IManageConfigValues;
|
|||
use Friendica\Core\PConfig\Capability\IManagePersonalConfigValues;
|
||||
use Friendica\Core\Hook;
|
||||
use Friendica\Core\L10n;
|
||||
use Friendica\Core\System;
|
||||
use Friendica\Database\Database;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\Factory\Api\Mastodon\Notification as NotificationFactory;
|
||||
|
@ -205,7 +204,7 @@ class Notify extends BaseRepository
|
|||
* @return bool
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
*/
|
||||
function createFromArray($params)
|
||||
public function createFromArray($params)
|
||||
{
|
||||
/** @var string the common prefix of a notification subject */
|
||||
$subjectPrefix = $this->l10n->t('[Friendica:Notify]');
|
||||
|
@ -340,7 +339,8 @@ class Notify extends BaseRepository
|
|||
$subject = $l10n->t('%s %s posted to your profile wall', $subjectPrefix, $params['source_name']);
|
||||
|
||||
$preamble = $l10n->t('%1$s posted to your profile wall at %2$s', $params['source_name'], $sitename);
|
||||
$epreamble = $l10n->t('%1$s posted to [url=%2$s]your wall[/url]',
|
||||
$epreamble = $l10n->t(
|
||||
'%1$s posted to [url=%2$s]your wall[/url]',
|
||||
'[url='.$params['source_link'].']'.$params['source_name'].'[/url]',
|
||||
$params['link']
|
||||
);
|
||||
|
@ -356,7 +356,8 @@ class Notify extends BaseRepository
|
|||
$subject = $l10n->t('%s Introduction received', $subjectPrefix);
|
||||
|
||||
$preamble = $l10n->t('You\'ve received an introduction from \'%1$s\' at %2$s', $params['source_name'], $sitename);
|
||||
$epreamble = $l10n->t('You\'ve received [url=%1$s]an introduction[/url] from %2$s.',
|
||||
$epreamble = $l10n->t(
|
||||
'You\'ve received [url=%1$s]an introduction[/url] from %2$s.',
|
||||
$itemlink,
|
||||
'[url='.$params['source_link'].']'.$params['source_name'].'[/url]'
|
||||
);
|
||||
|
@ -373,7 +374,8 @@ class Notify extends BaseRepository
|
|||
$subject = $l10n->t('%s A new person is sharing with you', $subjectPrefix);
|
||||
|
||||
$preamble = $l10n->t('%1$s is sharing with you at %2$s', $params['source_name'], $sitename);
|
||||
$epreamble = $l10n->t('%1$s is sharing with you at %2$s',
|
||||
$epreamble = $l10n->t(
|
||||
'%1$s is sharing with you at %2$s',
|
||||
'[url='.$params['source_link'].']'.$params['source_name'].'[/url]',
|
||||
$sitename
|
||||
);
|
||||
|
@ -383,7 +385,8 @@ class Notify extends BaseRepository
|
|||
$subject = $l10n->t('%s You have a new follower', $subjectPrefix);
|
||||
|
||||
$preamble = $l10n->t('You have a new follower at %2$s : %1$s', $params['source_name'], $sitename);
|
||||
$epreamble = $l10n->t('You have a new follower at %2$s : %1$s',
|
||||
$epreamble = $l10n->t(
|
||||
'You have a new follower at %2$s : %1$s',
|
||||
'[url='.$params['source_link'].']'.$params['source_name'].'[/url]',
|
||||
$sitename
|
||||
);
|
||||
|
@ -399,7 +402,8 @@ class Notify extends BaseRepository
|
|||
$subject = $l10n->t('%s Friend suggestion received', $subjectPrefix);
|
||||
|
||||
$preamble = $l10n->t('You\'ve received a friend suggestion from \'%1$s\' at %2$s', $params['source_name'], $sitename);
|
||||
$epreamble = $l10n->t('You\'ve received [url=%1$s]a friend suggestion[/url] for %2$s from %3$s.',
|
||||
$epreamble = $l10n->t(
|
||||
'You\'ve received [url=%1$s]a friend suggestion[/url] for %2$s from %3$s.',
|
||||
$itemlink,
|
||||
'[url='.$params['item']['url'].']'.$params['item']['name'].'[/url]',
|
||||
'[url='.$params['source_link'].']'.$params['source_name'].'[/url]'
|
||||
|
@ -420,7 +424,8 @@ class Notify extends BaseRepository
|
|||
$subject = $l10n->t('%s Connection accepted', $subjectPrefix);
|
||||
|
||||
$preamble = $l10n->t('\'%1$s\' has accepted your connection request at %2$s', $params['source_name'], $sitename);
|
||||
$epreamble = $l10n->t('%2$s has accepted your [url=%1$s]connection request[/url].',
|
||||
$epreamble = $l10n->t(
|
||||
'%2$s has accepted your [url=%1$s]connection request[/url].',
|
||||
$itemlink,
|
||||
'[url='.$params['source_link'].']'.$params['source_name'].'[/url]'
|
||||
);
|
||||
|
@ -435,7 +440,8 @@ class Notify extends BaseRepository
|
|||
$subject = $l10n->t('%s Connection accepted', $subjectPrefix);
|
||||
|
||||
$preamble = $l10n->t('\'%1$s\' has accepted your connection request at %2$s', $params['source_name'], $sitename);
|
||||
$epreamble = $l10n->t('%2$s has accepted your [url=%1$s]connection request[/url].',
|
||||
$epreamble = $l10n->t(
|
||||
'%2$s has accepted your [url=%1$s]connection request[/url].',
|
||||
$itemlink,
|
||||
'[url='.$params['source_link'].']'.$params['source_name'].'[/url]'
|
||||
);
|
||||
|
@ -457,14 +463,17 @@ class Notify extends BaseRepository
|
|||
$subject = $l10n->t('[Friendica System Notify]') . ' ' . $l10n->t('registration request');
|
||||
|
||||
$preamble = $l10n->t('You\'ve received a registration request from \'%1$s\' at %2$s', $params['source_name'], $sitename);
|
||||
$epreamble = $l10n->t('You\'ve received a [url=%1$s]registration request[/url] from %2$s.',
|
||||
$epreamble = $l10n->t(
|
||||
'You\'ve received a [url=%1$s]registration request[/url] from %2$s.',
|
||||
$itemlink,
|
||||
'[url='.$params['source_link'].']'.$params['source_name'].'[/url]'
|
||||
);
|
||||
|
||||
$body = $l10n->t("Display Name: %s\nSite Location: %s\nLogin Name: %s (%s)",
|
||||
$body = $l10n->t(
|
||||
"Display Name: %s\nSite Location: %s\nLogin Name: %s (%s)",
|
||||
$params['source_name'],
|
||||
$siteurl, $params['source_mail'],
|
||||
$siteurl,
|
||||
$params['source_mail'],
|
||||
$params['source_nick']
|
||||
);
|
||||
|
||||
|
@ -478,14 +487,17 @@ class Notify extends BaseRepository
|
|||
$subject = $l10n->t('[Friendica System Notify]') . ' ' . $l10n->t('new registration');
|
||||
|
||||
$preamble = $l10n->t('You\'ve received a new registration from \'%1$s\' at %2$s', $params['source_name'], $sitename);
|
||||
$epreamble = $l10n->t('You\'ve received a [url=%1$s]new registration[/url] from %2$s.',
|
||||
$epreamble = $l10n->t(
|
||||
'You\'ve received a [url=%1$s]new registration[/url] from %2$s.',
|
||||
$itemlink,
|
||||
'[url='.$params['source_link'].']'.$params['source_name'].'[/url]'
|
||||
);
|
||||
|
||||
$body = $l10n->t("Display Name: %s\nSite Location: %s\nLogin Name: %s (%s)",
|
||||
$body = $l10n->t(
|
||||
"Display Name: %s\nSite Location: %s\nLogin Name: %s (%s)",
|
||||
$params['source_name'],
|
||||
$siteurl, $params['source_mail'],
|
||||
$siteurl,
|
||||
$params['source_mail'],
|
||||
$params['source_nick']
|
||||
);
|
||||
|
||||
|
@ -599,9 +611,7 @@ class Notify extends BaseRepository
|
|||
DBA::insert('notify-threads', $fields);
|
||||
|
||||
$emailBuilder->setHeader('Message-ID', $message_id);
|
||||
$log_msg = "No previous notification found for this parent:\n" .
|
||||
" parent: {$params['parent']}\n" . " uid : {$params['uid']}\n";
|
||||
$this->logger->info($log_msg);
|
||||
$this->logger->info('No previous notification found for this parent', ['parent' => $params['parent'], 'uid' => $params['uid']]);
|
||||
} else {
|
||||
// If not, just "follow" the thread.
|
||||
$emailBuilder->setHeader('References', $message_id);
|
||||
|
@ -645,7 +655,8 @@ class Notify extends BaseRepository
|
|||
$emailBuilder->withPhoto(
|
||||
$datarray['source_photo'],
|
||||
$datarray['source_link'] ?? $sitelink,
|
||||
$datarray['source_name'] ?? $sitename);
|
||||
$datarray['source_name'] ?? $sitename
|
||||
);
|
||||
}
|
||||
|
||||
$email = $emailBuilder->build();
|
||||
|
@ -725,9 +736,12 @@ class Notify extends BaseRepository
|
|||
$params['source_photo'] = $contact['photo'];
|
||||
}
|
||||
|
||||
$item = Model\Post::selectFirstForUser($Notification->uid, Model\Item::ITEM_FIELDLIST,
|
||||
$item = Model\Post::selectFirstForUser(
|
||||
$Notification->uid,
|
||||
Model\Item::ITEM_FIELDLIST,
|
||||
['uid' => [0, $Notification->uid], 'uri-id' => $Notification->targetUriId, 'deleted' => false],
|
||||
['order' => ['uid' => true]]);
|
||||
['order' => ['uid' => true]]
|
||||
);
|
||||
if (empty($item)) {
|
||||
$this->logger->info('Item not found', ['uri-id' => $Notification->targetUriId, 'type' => $Notification->type]);
|
||||
return false;
|
||||
|
|
|
@ -147,7 +147,7 @@ class Probe
|
|||
$newdata['baseurl'] = $data['networks'][$network]['baseurl'];
|
||||
}
|
||||
if (!empty($newdata['baseurl'])) {
|
||||
$newdata['gsid'] = $data['networks'][$network]['gsid'] = GServer::getID($newdata['baseurl']);
|
||||
$newdata['gsid'] = $data['networks'][$network]['gsid'] = GServer::getRealID($newdata['baseurl']);
|
||||
} else {
|
||||
$newdata['gsid'] = $data['networks'][$network]['gsid'] = null;
|
||||
}
|
||||
|
@ -436,7 +436,7 @@ class Probe
|
|||
}
|
||||
|
||||
if (!empty($data['baseurl']) && empty($data['gsid'])) {
|
||||
$data['gsid'] = GServer::getID($data['baseurl']);
|
||||
$data['gsid'] = GServer::getRealID($data['baseurl']);
|
||||
}
|
||||
|
||||
// Ensure that local connections always are DFRN
|
||||
|
@ -2164,7 +2164,7 @@ class Probe
|
|||
$split_name = Diaspora::splitName($owner['name']);
|
||||
|
||||
if (empty($owner['gsid'])) {
|
||||
$owner['gsid'] = GServer::getID($approfile['generator']['url']);
|
||||
$owner['gsid'] = GServer::getRealID($approfile['generator']['url']);
|
||||
}
|
||||
|
||||
$data = [
|
||||
|
|
|
@ -24,40 +24,24 @@ use Friendica\Object\Api\Mastodon\InstanceV2\Configuration;
|
|||
*/
|
||||
class Instance extends BaseDataTransferObject
|
||||
{
|
||||
/** @var string (URL) */
|
||||
protected $uri;
|
||||
/** @var string */
|
||||
protected $title;
|
||||
/** @var string */
|
||||
protected $short_description;
|
||||
/** @var string */
|
||||
protected $description;
|
||||
/** @var string */
|
||||
protected $email;
|
||||
/** @var string */
|
||||
protected $version;
|
||||
/** @var array */
|
||||
protected $urls;
|
||||
/** @var Stats */
|
||||
protected $stats;
|
||||
/** @var string|null This is meant as a server banner, default Mastodon "thumbnail" is 1600×620px */
|
||||
protected $thumbnail = null;
|
||||
/** @var array */
|
||||
protected $languages;
|
||||
/** @var int */
|
||||
protected $max_toot_chars;
|
||||
/** @var bool */
|
||||
protected $registrations;
|
||||
/** @var bool */
|
||||
protected $approval_required;
|
||||
/** @var bool */
|
||||
protected $invites_enabled;
|
||||
/** @var Configuration */
|
||||
protected $configuration;
|
||||
/** @var Account|null */
|
||||
protected $contact_account = null;
|
||||
/** @var array */
|
||||
protected $rules = [];
|
||||
protected string $uri;
|
||||
protected string $title;
|
||||
protected string $short_description;
|
||||
protected string $description;
|
||||
protected string $email;
|
||||
protected string $version;
|
||||
protected array $urls;
|
||||
protected Stats $stats;
|
||||
/** This is meant as a server banner, default Mastodon "thumbnail" is 1600×620px */
|
||||
protected ?string $thumbnail = null;
|
||||
protected array $languages;
|
||||
protected int $max_toot_chars;
|
||||
protected bool $registrations;
|
||||
protected bool $approval_required;
|
||||
protected bool $invites_enabled;
|
||||
protected Configuration $configuration;
|
||||
protected ?Account $contact_account = null;
|
||||
protected array $rules = [];
|
||||
|
||||
public function __construct(IManageConfigValues $config, BaseURL $baseUrl, Database $database, Configuration $configuration, ?Account $contact_account, array $rules)
|
||||
{
|
||||
|
@ -77,7 +61,7 @@ class Instance extends BaseDataTransferObject
|
|||
$this->approval_required = ($register_policy === Register::APPROVE);
|
||||
$this->invites_enabled = false;
|
||||
$this->configuration = $configuration;
|
||||
$this->contact_account = $contact_account ?? [];
|
||||
$this->contact_account = $contact_account;
|
||||
$this->rules = $rules;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -498,8 +498,10 @@ class Post
|
|||
}
|
||||
|
||||
$languages = [];
|
||||
$language = '';
|
||||
if (!empty($item['language'])) {
|
||||
$languages = DI::l10n()->t('Languages');
|
||||
$language = array_key_first(json_decode($item['language'], true));
|
||||
}
|
||||
|
||||
if (in_array($item['private'], [Item::PUBLIC, Item::UNLISTED]) && in_array($item['network'], Protocol::FEDERATED)) {
|
||||
|
@ -553,6 +555,7 @@ class Post
|
|||
'title' => $item['title'],
|
||||
'summary' => $item['content-warning'],
|
||||
'localtime' => DateTimeFormat::local($item['created'], 'r'),
|
||||
'utc' => DateTimeFormat::utc($item['created']),
|
||||
'ago' => $item['app'] ? DI::l10n()->t('%s from %s', $ago, $item['app']) : $ago,
|
||||
'app' => $item['app'],
|
||||
'created' => $ago,
|
||||
|
@ -579,6 +582,7 @@ class Post
|
|||
'tagger' => $tagger,
|
||||
'filer' => $filer,
|
||||
'language' => $languages,
|
||||
'lang' => $language,
|
||||
'searchtext' => DI::l10n()->t('Search Text'),
|
||||
'drop' => $drop,
|
||||
'block' => $block,
|
||||
|
|
|
@ -142,7 +142,7 @@ class Actor
|
|||
|
||||
if (!empty($fields['baseurl'])) {
|
||||
GServer::check($fields['baseurl'], Protocol::BLUESKY);
|
||||
$fields['gsid'] = GServer::getID($fields['baseurl'], true);
|
||||
$fields['gsid'] = GServer::getRealID($fields['baseurl'], true);
|
||||
}
|
||||
|
||||
foreach ($directory->verificationMethod as $method) {
|
||||
|
|
|
@ -906,15 +906,15 @@ class Processor
|
|||
if ($id) {
|
||||
$shared_item = Post::selectFirst(['uri-id'], ['id' => $id]);
|
||||
$item['quote-uri-id'] = $shared_item['uri-id'];
|
||||
DI::logger()->debug('Quote is found', ['guid' => $item['guid'], 'uri-id' => $item['uri-id'], 'quote' => $activity['quote-url'], 'quote-uri-id' => $item['quote-uri-id']]);
|
||||
DI::logger()->debug('Quote is found', ['uri' => $item['uri'], 'uri-id' => $item['uri-id'], 'quote' => $activity['quote-url'], 'quote-uri-id' => $item['quote-uri-id']]);
|
||||
} elseif ($uri_id = ItemURI::getIdByURI($activity['quote-url'], false)) {
|
||||
DI::logger()->info('Quote was not fetched but the uri-id existed', ['guid' => $item['guid'], 'uri-id' => $item['uri-id'], 'quote' => $activity['quote-url'], 'quote-uri-id' => $uri_id]);
|
||||
DI::logger()->info('Quote was not fetched but the uri-id existed', ['uri' => $item['uri'], 'uri-id' => $item['uri-id'], 'quote' => $activity['quote-url'], 'quote-uri-id' => $uri_id]);
|
||||
$item['quote-uri-id'] = $uri_id;
|
||||
} elseif (Queue::exists($activity['quote-url'], 'as:Create')) {
|
||||
$item['quote-uri-id'] = ItemURI::getIdByURI($activity['quote-url']);
|
||||
DI::logger()->info('Quote is queued but not processed yet', ['guid' => $item['guid'], 'uri-id' => $item['uri-id'], 'quote' => $activity['quote-url'], 'quote-uri-id' => $item['quote-uri-id']]);
|
||||
DI::logger()->info('Quote is queued but not processed yet', ['uri' => $item['uri'], 'uri-id' => $item['uri-id'], 'quote' => $activity['quote-url'], 'quote-uri-id' => $item['quote-uri-id']]);
|
||||
} else {
|
||||
DI::logger()->notice('Quote was not fetched', ['guid' => $item['guid'], 'uri-id' => $item['uri-id'], 'quote' => $activity['quote-url']]);
|
||||
DI::logger()->notice('Quote was not fetched', ['uri' => $item['uri'], 'uri-id' => $item['uri-id'], 'quote' => $activity['quote-url']]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -799,31 +799,53 @@ class Transmitter
|
|||
|
||||
if (!empty($item['quote-uri-id']) && in_array($item['private'], [Item::PUBLIC, Item::UNLISTED])) {
|
||||
$quoted = Post::selectFirst(['author-link'], ['uri-id' => $item['quote-uri-id']]);
|
||||
if (!empty($quoted['author-link'])) {
|
||||
$profile = APContact::getByURL($quoted['author-link'], false);
|
||||
if (!empty($profile)) {
|
||||
$data['cc'][] = $profile['url'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$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]);
|
||||
}
|
||||
|
||||
|
@ -857,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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -310,7 +310,7 @@ class Delivery
|
|||
$atom = DFRN::entries($msgitems, $owner);
|
||||
}
|
||||
|
||||
DI::logger()->debug('Notifier entry: ' . $contact['url'] . ' ' . ($target_item_id ?? 'relocation') . ' entry: ' . $atom);
|
||||
DI::logger()->debug('Notifier entry', ['url' => $contact['url'], 'target_item_id' => ($target_item_id ?? 'relocation'), 'entry' => $atom]);
|
||||
|
||||
$protocol = Post\DeliveryData::DFRN;
|
||||
|
||||
|
|
|
@ -2790,7 +2790,7 @@ class Diaspora
|
|||
*/
|
||||
public static function encodePrivateData(string $msg, array $user, array $contact, string $prvkey, string $pubkey): string
|
||||
{
|
||||
DI::logger()->debug('Message: ' . $msg);
|
||||
DI::logger()->debug('Diaspora message', ['msg' => $msg]);
|
||||
|
||||
// without a public key nothing will work
|
||||
if (!$pubkey) {
|
||||
|
|
|
@ -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 [];
|
||||
}
|
||||
|
||||
if (!empty($contact['poll'])) {
|
||||
$basepath = $contact['poll'];
|
||||
} elseif (!empty($contact['url'])) {
|
||||
$basepath = $contact['url'];
|
||||
} else {
|
||||
$basepath = '';
|
||||
|
||||
if (!empty($contact['poll'])) {
|
||||
$basepath = (string) $contact['poll'];
|
||||
} elseif (!empty($contact['url'])) {
|
||||
$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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
namespace Friendica\Protocol;
|
||||
|
||||
use Friendica\App;
|
||||
use Friendica\Core\Addon;
|
||||
use Friendica\DI;
|
||||
use Friendica\Module;
|
||||
use Friendica\Module\Register;
|
||||
|
@ -43,6 +42,11 @@ class ZOT
|
|||
*/
|
||||
public static function getSiteInfo(): array
|
||||
{
|
||||
$baseUrl = (string) DI::baseUrl();
|
||||
$keyValue = DI::keyValue();
|
||||
$addonHelper = DI::addonHelper();
|
||||
$config = DI::config();
|
||||
|
||||
$policies = [
|
||||
Module\Register::OPEN => 'open',
|
||||
Module\Register::APPROVE => 'approve',
|
||||
|
@ -50,14 +54,14 @@ class ZOT
|
|||
];
|
||||
|
||||
return [
|
||||
'url' => (string)DI::baseUrl(),
|
||||
'openWebAuth' => (string)DI::baseUrl() . '/owa',
|
||||
'authRedirect' => (string)DI::baseUrl() . '/magic',
|
||||
'url' => $baseUrl,
|
||||
'openWebAuth' => $baseUrl . '/owa',
|
||||
'authRedirect' => $baseUrl . '/magic',
|
||||
'register_policy' => $policies[Register::getPolicy()],
|
||||
'accounts' => DI::keyValue()->get('nodeinfo_total_users'),
|
||||
'plugins' => Addon::getVisibleList(),
|
||||
'sitename' => DI::config()->get('config', 'sitename'),
|
||||
'about' => DI::config()->get('config', 'info'),
|
||||
'accounts' => $keyValue->get('nodeinfo_total_users'),
|
||||
'plugins' => $addonHelper->getVisibleEnabledAddons(),
|
||||
'sitename' => $config->get('config', 'sitename'),
|
||||
'about' => $config->get('config', 'info'),
|
||||
'project' => App::PLATFORM,
|
||||
'version' => App::VERSION,
|
||||
];
|
||||
|
|
|
@ -299,12 +299,19 @@ class Crypto
|
|||
* Creates cryptographic secure random digits
|
||||
*
|
||||
* @param string $digits The count of digits
|
||||
* @return int The random Digits
|
||||
* @return string The random Digits
|
||||
*
|
||||
* @throws \Exception In case 'random_int' isn't usable
|
||||
*/
|
||||
public static function randomDigits($digits)
|
||||
public static function randomDigits($digits): string
|
||||
{
|
||||
return random_int(0, 10 ** $digits - 1);
|
||||
$rn = '';
|
||||
|
||||
// generating cryptographically secure pseudo-random integers
|
||||
for ($i = 0; $i < $digits; $i++) {
|
||||
$rn .= random_int(0, 9);
|
||||
}
|
||||
|
||||
return $rn;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,9 +40,13 @@ class Emailer
|
|||
/** @var string */
|
||||
private $siteEmailName;
|
||||
|
||||
public function __construct(IManageConfigValues $config, IManagePersonalConfigValues $pConfig, BaseURL $baseURL, LoggerInterface $logger,
|
||||
L10n $defaultLang)
|
||||
{
|
||||
public function __construct(
|
||||
IManageConfigValues $config,
|
||||
IManagePersonalConfigValues $pConfig,
|
||||
BaseURL $baseURL,
|
||||
LoggerInterface $logger,
|
||||
L10n $defaultLang
|
||||
) {
|
||||
$this->config = $config;
|
||||
$this->pConfig = $pConfig;
|
||||
$this->logger = $logger;
|
||||
|
@ -89,8 +93,14 @@ class Emailer
|
|||
*/
|
||||
public function newSystemMail()
|
||||
{
|
||||
return new SystemMailBuilder($this->l10n, $this->baseUrl, $this->config, $this->logger,
|
||||
$this->getSiteEmailAddress(), $this->getSiteEmailName());
|
||||
return new SystemMailBuilder(
|
||||
$this->l10n,
|
||||
$this->baseUrl,
|
||||
$this->config,
|
||||
$this->logger,
|
||||
$this->getSiteEmailAddress(),
|
||||
$this->getSiteEmailName()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -100,8 +110,14 @@ class Emailer
|
|||
*/
|
||||
public function newNotifyMail()
|
||||
{
|
||||
return new NotifyMailBuilder($this->l10n, $this->baseUrl, $this->config, $this->logger,
|
||||
$this->getSiteEmailAddress(), $this->getSiteEmailName());
|
||||
return new NotifyMailBuilder(
|
||||
$this->l10n,
|
||||
$this->baseUrl,
|
||||
$this->config,
|
||||
$this->logger,
|
||||
$this->getSiteEmailAddress(),
|
||||
$this->getSiteEmailName()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -207,8 +223,7 @@ class Emailer
|
|||
$hookdata['parameters']
|
||||
);
|
||||
|
||||
$this->logger->debug('header ' . 'To: ' . $email->getToAddress() . '\n' . $messageHeader);
|
||||
$this->logger->debug('return value ' . (($res) ? 'true' : 'false'));
|
||||
$this->logger->debug('Email message header', ['To' => $email->getToAddress(), 'messageHeader' => $messageHeader, 'return' => ($res) ? 'true' : 'false']);
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
|
|
@ -147,7 +147,8 @@ class Temporal
|
|||
}
|
||||
|
||||
$tpl = Renderer::getMarkupTemplate("field_input.tpl");
|
||||
$o = Renderer::replaceMacros($tpl,
|
||||
$o = Renderer::replaceMacros(
|
||||
$tpl,
|
||||
[
|
||||
'$field' => [
|
||||
'dob',
|
||||
|
@ -157,7 +158,8 @@ class Temporal
|
|||
'',
|
||||
'placeholder="' . DI::l10n()->t('YYYY-MM-DD or MM-DD') . '"'
|
||||
]
|
||||
]);
|
||||
]
|
||||
);
|
||||
|
||||
return $o;
|
||||
}
|
||||
|
@ -223,17 +225,19 @@ class Temporal
|
|||
bool $picktime = true,
|
||||
string $minfrom = '',
|
||||
string $maxfrom = '',
|
||||
bool $required = false): string
|
||||
{
|
||||
bool $required = false
|
||||
): string {
|
||||
// First day of the week (0 = Sunday)
|
||||
$firstDay = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'calendar', 'first_day_of_week', 0);
|
||||
$firstDay = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'calendar', 'first_day_of_week') ?: 0;
|
||||
|
||||
$lang = DI::l10n()->toISO6391(DI::l10n()->getCurrentLang());
|
||||
|
||||
// Check if the detected language is supported by the picker
|
||||
if (!in_array($lang,
|
||||
if (!in_array(
|
||||
$lang,
|
||||
['ar', 'ro', 'id', 'bg', 'fa', 'ru', 'uk', 'en', 'el', 'de', 'nl', 'tr', 'fr', 'es', 'th', 'pl', 'pt', 'ch', 'se', 'kr',
|
||||
'it', 'da', 'no', 'ja', 'vi', 'sl', 'cs', 'hu'])) {
|
||||
'it', 'da', 'no', 'ja', 'vi', 'sl', 'cs', 'hu']
|
||||
)) {
|
||||
$lang = 'en';
|
||||
}
|
||||
|
||||
|
@ -351,8 +355,7 @@ class Temporal
|
|||
// translators - e.g. 22 hours ago, 1 minute ago
|
||||
if($isfuture) {
|
||||
$format = DI::l10n()->t('in %1$d %2$s');
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
$format = DI::l10n()->t('%1$d %2$s ago');
|
||||
}
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue