diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 54e2fe4bb1..4867640812 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,29 +1,26 @@ -FROM php:8.4-apache +ARG VARIANT="8.2-apache-bullseye" +FROM mcr.microsoft.com/vscode/devcontainers/php:${VARIANT} ARG DEBIAN_FRONTEND=noninteractive +ARG apcu_version=5.1.23 +ARG memcached_version=3.2.0 +ARG redis_version=6.0.2 +ARG imagick_version=3.7.0 + +RUN apt-get update -y; # Install MariaDB client -RUN apt-get update \ - && apt-get install -y --no-install-recommends mariadb-client \ -; +RUN apt-get install -y mariadb-client # Base packages -RUN apt-get install -y \ - vim \ - sudo \ - nano \ - git \ - gnupg2 \ - pinentry-curses \ -; +RUN apt install -y vim software-properties-common sudo nano gnupg2 # entrypoint.sh and cron.sh dependencies RUN apt-get install -y --no-install-recommends \ rsync \ bzip2 \ msmtp \ - tini \ -; + tini RUN apt-get install -y --no-install-recommends \ bash \ @@ -41,40 +38,36 @@ RUN apt-get install -y --no-install-recommends \ libzip-dev \ libldap2-dev \ libgmp-dev \ - # libmagickcore-6.q16-6-extra \ -; - -RUN docker-php-ext-configure gd \ - --with-freetype \ - --with-jpeg \ - --with-webp \ -; - -RUN docker-php-ext-install -j "$(nproc)" \ - pdo_mysql \ - gd \ - exif \ - zip \ - opcache \ - ctype \ - pcntl \ - ldap \ - gmp \ - intl \ - ; + libmagickcore-6.q16-6-extra \ + ; \ + \ + docker-php-ext-configure gd \ + --with-freetype \ + --with-jpeg \ + --with-webp \ + ; \ + docker-php-ext-install -j "$(nproc)" \ + pdo_mysql \ + gd \ + exif \ + zip \ + opcache \ + ctype \ + pcntl \ + ldap \ + gmp \ + intl # pecl will claim success even if one install fails, so we need to perform each install separately -RUN pecl install apcu-5.1.27; -RUN pecl install memcached-3.4.0; -RUN pecl install redis-6.2.0; -RUN pecl install imagick-3.8.0; - -RUN docker-php-ext-enable \ - apcu \ - memcached \ - redis \ - imagick \ -; +RUN pecl install apcu-${apcu_version}; \ + pecl install memcached-${memcached_version}; \ + pecl install redis-${redis_version}; \ + pecl install imagick-${imagick_version}; \ + docker-php-ext-enable \ + apcu \ + memcached \ + redis \ + imagick RUN apt-get clean -y && rm -rf /var/lib/apt/lists/* @@ -96,8 +89,8 @@ RUN { \ echo 'memory_limit=${PHP_MEMORY_LIMIT}'; \ echo 'upload_max_filesize=${PHP_UPLOAD_LIMIT}'; \ echo 'post_max_size=${PHP_UPLOAD_LIMIT}'; \ - } > /usr/local/etc/php/conf.d/friendica.ini; - -RUN ln -s /usr/local/etc/php/php.ini-production /usr/local/etc/php/php.ini -RUN mkdir /var/www/data -RUN chmod -R g=u /var/www + } > /usr/local/etc/php/conf.d/friendica.ini; \ + ln -s /usr/local/etc/php/php.ini-production /usr/local/etc/php/php.ini; \ + \ + mkdir /var/www/data; \ + chmod -R g=u /var/www diff --git a/.devcontainer/docker-compose.yml b/.devcontainer/docker-compose.yml index 9a4313eef5..4463ebad86 100644 --- a/.devcontainer/docker-compose.yml +++ b/.devcontainer/docker-compose.yml @@ -1,4 +1,6 @@ -services: +version: '3.8' + +services: app: build: context: . @@ -12,14 +14,16 @@ services: command: sleep infinity ports: - - ${ServerPort:-8080}:80 - - 8443:443 - - # Use "forwardPorts" in **devcontainer.json** to forward an app port locally. + - 80:80 + - 443:443 + - 8080:8080 + - 3306:3306 + + # Use "forwardPorts" in **devcontainer.json** to forward an app port locally. # (Adding the "ports" property to this file will not forward from a Codespace.) extra_hosts: - - "${ServerAlias}:127.0.0.1" + - "${ServerAlias}:127.0.0.1" db: image: mariadb:10.4 @@ -45,3 +49,4 @@ volumes: networks: default: + \ No newline at end of file diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml index 729829159c..770c748289 100644 --- a/.github/workflows/code-quality.yml +++ b/.github/workflows/code-quality.yml @@ -35,14 +35,14 @@ jobs: tools: none - name: Clone addon repository - run: git clone -b develop --single-branch https://git.friendi.ca/friendica/friendica-addons.git addon + run: git clone -b 2025.07-rc --single-branch https://git.friendi.ca/friendica/friendica-addons.git addon - - name: Install Composer dependencies - uses: "ramsey/composer-install@v2" + - name: Install PHP-CS-Fixer + run: composer install --working-dir=bin/dev/php-cs-fixer - name: Run PHP-CS-Fixer continue-on-error: true - run: vendor/bin/php-cs-fixer fix --diff --dry-run + run: bin/dev/php-cs-fixer/vendor/bin/php-cs-fixer fix --diff --dry-run phpstan: name: PHPStan (PHP ${{ matrix.php }}) @@ -68,7 +68,7 @@ jobs: tools: none - name: Clone addon repository - run: git clone -b develop --single-branch https://git.friendi.ca/friendica/friendica-addons.git addon + run: git clone -b 2025.07-rc --single-branch https://git.friendi.ca/friendica/friendica-addons.git addon - name: Install Composer dependencies uses: "ramsey/composer-install@v2" @@ -100,7 +100,7 @@ jobs: tools: none - name: Clone addon repository - run: git clone -b develop --single-branch https://git.friendi.ca/friendica/friendica-addons.git addon + run: git clone -b 2025.07-rc --single-branch https://git.friendi.ca/friendica/friendica-addons.git addon - name: Install Composer dependencies uses: "ramsey/composer-install@v2" @@ -132,7 +132,7 @@ jobs: tools: none - name: Clone addon repository - run: git clone -b develop --single-branch https://git.friendi.ca/friendica/friendica-addons.git addon + run: git clone -b 2025.07-rc --single-branch https://git.friendi.ca/friendica/friendica-addons.git addon - name: Install Composer dependencies uses: "ramsey/composer-install@v2" diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index d0aa623842..31b92fefd6 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -34,7 +34,7 @@ jobs: tools: none - name: Clone addon repository - run: git clone -b develop --single-branch https://git.friendi.ca/friendica/friendica-addons.git addon + run: git clone -b 2025.07-rc --single-branch https://git.friendi.ca/friendica/friendica-addons.git addon - name: Install Composer dependencies uses: "ramsey/composer-install@v2" @@ -88,7 +88,7 @@ jobs: ini-values: apc.enabled=1, apc.enable_cli=1 - name: Clone addon repository - run: git clone -b develop --single-branch https://git.friendi.ca/friendica/friendica-addons.git addon + run: git clone -b 2025.07-rc --single-branch https://git.friendi.ca/friendica/friendica-addons.git addon # Install composer dependencies and handle caching in one go. # @link https://github.com/marketplace/actions/install-php-dependencies-with-composer diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index 320ee5c036..d376a525f2 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -1,5 +1,4 @@ in(__DIR__) - ->exclude([ - 'addon', - 'bin/dev', - 'config', - 'doc', - 'images', - 'mods', - 'spec', - 'vendor', - 'view/asset', - 'lang', - 'view/smarty3/compiled', - ]) - ->append([ - '.php-cs-fixer.dist.php', - ]) -; + ->notPath('addon') + ->notPath('bin/dev') + ->notPath('config') + ->notPath('doc') + ->notPath('images') + ->notPath('mods') + ->notPath('spec') + ->notPath('vendor') + ->notPath('view/asset') + ->notPath('lang') + ->notPath('view/smarty3/compiled'); $config = new PhpCsFixer\Config(); return $config ->setRules([ - '@PER-CS3x0' => true, + '@PSR1' => true, + '@PSR2' => true, + '@PSR12' => true, 'align_multiline_comment' => true, - 'binary_operator_spaces' => [ + 'array_indentation' => true, + 'array_syntax' => [ + 'syntax' => 'short', + ], + 'binary_operator_spaces' => [ 'default' => 'single_space', 'operators' => [ '=>' => 'align_single_space_minimal', @@ -41,31 +42,40 @@ return $config '??' => 'align_single_space_minimal', ], ], - 'braces_position' => [ + 'blank_line_after_namespace' => true, + 'braces_position' => [ 'anonymous_classes_opening_brace' => 'same_line', 'control_structures_opening_brace' => 'same_line', 'functions_opening_brace' => 'next_line_unless_newline_at_signature_end', ], + 'elseif' => true, + 'encoding' => true, + 'full_opening_tag' => true, 'function_declaration' => [ 'closure_function_spacing' => 'one', ], - 'list_syntax' => [ - 'syntax' => 'short', + 'indentation_type' => true, + 'line_ending' => true, + 'list_syntax' => [ + 'syntax' => 'long', ], - 'new_with_parentheses' => true, - 'no_unused_imports' => true, - 'single_import_per_statement' => true, - 'ternary_operator_spaces' => false, - 'trailing_comma_in_multiline' => [ - 'after_heredoc' => true, - 'elements' => [ - 'arguments', - 'array_destructuring', - 'arrays', - // 'match', /* activate `match` after PHP 7.4 support is dropped */ - // 'parameters', /* activate `arguments` after PHP 7.4 support is dropped */ - ], + 'lowercase_keywords' => true, + 'no_closing_tag' => true, + 'no_spaces_after_function_name' => true, + 'spaces_inside_parentheses' => false, + 'no_trailing_whitespace' => true, + 'no_trailing_whitespace_in_comment' => true, + 'no_unused_imports' => true, + 'single_blank_line_at_eof' => true, + 'single_class_element_per_statement' => true, + 'single_import_per_statement' => true, + 'single_line_after_imports' => true, + 'switch_case_space' => true, + 'ternary_operator_spaces' => false, + 'visibility_required' => [ + 'elements' => ['property', 'method'] ], + 'new_with_parentheses' => true, ]) ->setFinder($finder) ->setIndent("\t"); diff --git a/.rector.php b/.rector.php deleted file mode 100644 index fdfa43139e..0000000000 --- a/.rector.php +++ /dev/null @@ -1,31 +0,0 @@ -withPaths([ - __DIR__ . '/config', - __DIR__ . '/mod', - __DIR__ . '/src', - __DIR__ . '/static', - __DIR__ . '/tests', - __DIR__ . '/view', - ]) - ->withIndent("\t", 4) - ->withPhpVersion(70400) - // ->withTypeCoverageLevel(0) - // ->withDeadCodeLevel(0) - // ->withCodeQualityLevel(0) - ->withSets([ - \Rector\Set\ValueObject\LevelSetList::UP_TO_PHP_74, - ]) - ->withSkip([ - \Rector\Php56\Rector\FuncCall\PowToExpRector::class, - \Rector\Php74\Rector\Closure\ClosureToArrowFunctionRector::class, - ]) -; diff --git a/.woodpecker/.code_standards_check.yml b/.woodpecker/.code_standards_check.yml index 3b87e1d4be..d147e8fc69 100644 --- a/.woodpecker/.code_standards_check.yml +++ b/.woodpecker/.code_standards_check.yml @@ -20,14 +20,11 @@ steps: - '.composer' volumes: - /tmp/drone-cache:/tmp/cache - composer_install: - image: friendicaci/php8.3:php8.3.17 + image: composer commands: - - mkdir addon # create empty addon folder to appease composer - export COMPOSER_HOME=.composer - - ./bin/composer.phar install --prefer-dist - + - ./bin/composer.phar run cs:install rebuild_cache: image: meltwater/drone-cache:dev settings: @@ -39,7 +36,6 @@ steps: - '.composer' volumes: - /tmp/drone-cache:/tmp/cache - check: image: php:8.3 commands: @@ -53,4 +49,4 @@ steps: CHANGED_FILES="$(git diff --name-only --diff-filter=ACMRTUXB ${CI_COMMIT_SHA})"; fi - EXTRA_ARGS="--path-mode=intersection -- $${CHANGED_FILES}"; - - ./vendor/bin/php-cs-fixer check --config=.php-cs-fixer.dist.php -v --diff --using-cache=no $${EXTRA_ARGS} + - ./bin/dev/php-cs-fixer/vendor/bin/php-cs-fixer check --config=.php-cs-fixer.dist.php -v --diff --using-cache=no $${EXTRA_ARGS} diff --git a/.woodpecker/.phpunit.yml b/.woodpecker/.phpunit.yml index 997d50a52e..c13f00b589 100644 --- a/.woodpecker/.phpunit.yml +++ b/.woodpecker/.phpunit.yml @@ -57,7 +57,7 @@ steps: composer_install: image: friendicaci/php${PHP_MAJOR_VERSION}:php${PHP_VERSION} commands: - - git clone -b develop --single-branch https://git.friendi.ca/friendica/friendica-addons.git addon + - git clone -b 2025.07-rc --single-branch https://git.friendi.ca/friendica/friendica-addons.git addon - export COMPOSER_HOME=.composer - ./bin/composer.phar validate - ./bin/composer.phar install --prefer-dist diff --git a/.woodpecker/.releaser-allinone.yml b/.woodpecker/.releaser-allinone.yml deleted file mode 100644 index d23def1bd5..0000000000 --- a/.woodpecker/.releaser-allinone.yml +++ /dev/null @@ -1,96 +0,0 @@ -# SPDX-FileCopyrightText: 2010 - 2024 the Friendica project -# -# SPDX-License-Identifier: CC0-1.0 - -# This prevents executing this pipeline at other servers than ci.friendi.ca -labels: - location: friendica - type: releaser - -skip_clone: true - -when: - repo: friendica/friendica - branch: stable - event: tag - -steps: - clone_friendica_base: - image: alpine/git - commands: - - git config --global user.email "no-reply@friendi.ca" - - git config --global user.name "Friendica" - - git config --global --add safe.directory $CI_WORKSPACE - - git clone $CI_REPO_CLONE_URL . - - git checkout $CI_COMMIT_BRANCH - - git fetch origin $CI_COMMIT_REF - - git merge $CI_COMMIT_SHA - clone_friendica_addon: - image: alpine/git - commands: - - git config --global user.email "no-reply@friendi.ca" - - git config --global user.name "Friendica" - - git clone https://github.com/friendica/friendica-addons.git addon - - cd addon/ - - git checkout $CI_COMMIT_BRANCH - restore_cache: - image: meltwater/drone-cache:dev - settings: - backend: "filesystem" - restore: true - cache_key: "{{ .Repo.Name }}_php7.4_{{ arch }}_{{ os }}" - archive_format: "gzip" - mount: - - '.composer' - volumes: - - /tmp/drone-cache:/tmp/cache - composer_install: - image: friendicaci/php8.2:php8.2.28 - commands: - - export COMPOSER_HOME=.composer - - composer validate - - composer install --no-dev --optimize-autoloader - volumes: - - /etc/hosts:/etc/hosts - create_artifacts: - image: debian - commands: - - apt-get update - - apt-get install bzip2 - - mkdir ./build - - export VERSION="$(cat VERSION)" - - export RELEASE="friendica-all-in-one-$VERSION" - - export ARTIFACT="$RELEASE.tar.gz" - - tar - --transform "s,^,$RELEASE/,S" - -X mods/release-list-exclude.txt - -T mods/release-list-include.txt - -cvzf ./build/$ARTIFACT - - cd ./build - - sha256sum "$ARTIFACT" > "$ARTIFACT.sum256" - - chmod 664 ./* - - ls -lh - - cat "$ARTIFACT.sum256" - - sha256sum "$ARTIFACT" - sign_artifacts: - image: plugins/gpgsign - settings: - key: - from_secret: gpg_key - passphrase: - from_secret: gpg_password - files: - - build/* - exclude: - - build/*.sum256 - detach_sign: true - when: - repo: friendica/friendica - branch: stable - event: tag - publish_artifacts: - image: alpine - commands: - - cp -fr build/* /tmp/friendica_files/ - volumes: - - files:/tmp/friendica_files diff --git a/CHANGELOG b/CHANGELOG index 0daeb9eb3d..a33ce98aff 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,10 +1,3 @@ -Version 2026.04 (unreleased) - Friendica Core - - Friendica Addons - - Closed Issues - Version 2026.01 (2026-01-27) Friendica Core Updates to the translations diff --git a/README.md b/README.md index c608ccf331..d4a49330e4 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ You can control the privacy scope of your content. Being part of the Fediverse allows you to be free from data-harvesting corporations. Enjoy open social communication, independent of any specific provider. -[Join Friendica](https://dir.friendica.social/servers) today or set up [your own Friendica instance](doc/en/admin/install.md). +[Join Friendica](https://dir.friendica.social/servers) today or set up [your own Friendica instance](doc/Install.md). ### Friendica on desktop diff --git a/REUSE.toml b/REUSE.toml index 50892222a1..8f305ad303 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -7,6 +7,7 @@ SPDX-PackageDownloadLocation = "https://friendi.ca" path = [ "database.sql", "composer.*", + "bin/dev/php-cs-fixer/composer.*", "doc/**", "spec/*", "tests/**", diff --git a/VERSION b/VERSION index 0a016e46f1..62f5eb2b61 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2026.04-dev +2026.01 diff --git a/bin/console.php b/bin/console.php index a4cc9e2ae6..88382e1665 100755 --- a/bin/console.php +++ b/bin/console.php @@ -13,7 +13,7 @@ if (php_sapi_name() !== 'cli') { exit(); } -// Ensure console is executed from the base path of the installation +// Ensure that te console is executed from the base path of the installation chdir(dirname(__DIR__)); require dirname(__DIR__) . '/vendor/autoload.php'; diff --git a/bin/daemon.php b/bin/daemon.php index db9ed04028..dfcdc9d27b 100755 --- a/bin/daemon.php +++ b/bin/daemon.php @@ -1,13 +1,12 @@ #!/usr/bin/env php =5.3", + "react/stream": "^1.2" + }, + "require-dev": { + "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35", + "react/event-loop": "^1.2" + }, + "type": "library", + "autoload": { + "psr-4": { + "Clue\\React\\NDJson\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering" + } + ], + "description": "Streaming newline-delimited JSON (NDJSON) parser and encoder for ReactPHP.", + "homepage": "https://github.com/clue/reactphp-ndjson", + "keywords": [ + "NDJSON", + "json", + "jsonlines", + "newline", + "reactphp", + "streaming" + ], + "funding": [ + { + "url": "https://clue.engineering/support", + "type": "custom" + }, + { + "url": "https://github.com/clue", + "type": "github" + } + ], + "time": "2022-12-23T10:58:28+00:00" + }, + { + "name": "composer/pcre", + "version": "3.3.0", + "source": { + "type": "git", + "url": "https://github.com/composer/pcre.git", + "reference": "1637e067347a0c40bbb1e3cd786b20dcab556a81" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/pcre/zipball/1637e067347a0c40bbb1e3cd786b20dcab556a81", + "reference": "1637e067347a0c40bbb1e3cd786b20dcab556a81", + "shasum": "" + }, + "require": { + "php": "^7.4 || ^8.0" + }, + "conflict": { + "phpstan/phpstan": "<1.11.10" + }, + "require-dev": { + "phpstan/phpstan": "^1.11.10", + "phpstan/phpstan-strict-rules": "^1.1", + "phpunit/phpunit": "^8 || ^9" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + }, + "phpstan": { + "includes": [ + "extension.neon" + ] + } + }, + "autoload": { + "psr-4": { + "Composer\\Pcre\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "PCRE wrapping library that offers type-safe preg_* replacements.", + "keywords": [ + "PCRE", + "preg", + "regex", + "regular expression" + ], + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-08-19T19:43:53+00:00" + }, + { + "name": "composer/semver", + "version": "3.4.2", + "source": { + "type": "git", + "url": "https://github.com/composer/semver.git", + "reference": "c51258e759afdb17f1fd1fe83bc12baaef6309d6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/semver/zipball/c51258e759afdb17f1fd1fe83bc12baaef6309d6", + "reference": "c51258e759afdb17f1fd1fe83bc12baaef6309d6", + "shasum": "" + }, + "require": { + "php": "^5.3.2 || ^7.0 || ^8.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.4", + "symfony/phpunit-bridge": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Composer\\Semver\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nils Adermann", + "email": "naderman@naderman.de", + "homepage": "http://www.naderman.de" + }, + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + }, + { + "name": "Rob Bast", + "email": "rob.bast@gmail.com", + "homepage": "http://robbast.nl" + } + ], + "description": "Semver library that offers utilities, version constraint parsing and validation.", + "keywords": [ + "semantic", + "semver", + "validation", + "versioning" + ], + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-07-12T11:35:52+00:00" + }, + { + "name": "composer/xdebug-handler", + "version": "3.0.5", + "source": { + "type": "git", + "url": "https://github.com/composer/xdebug-handler.git", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/6c1925561632e83d60a44492e0b344cf48ab85ef", + "reference": "6c1925561632e83d60a44492e0b344cf48ab85ef", + "shasum": "" + }, + "require": { + "composer/pcre": "^1 || ^2 || ^3", + "php": "^7.2.5 || ^8.0", + "psr/log": "^1 || ^2 || ^3" + }, + "require-dev": { + "phpstan/phpstan": "^1.0", + "phpstan/phpstan-strict-rules": "^1.1", + "phpunit/phpunit": "^8.5 || ^9.6 || ^10.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Composer\\XdebugHandler\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "John Stevenson", + "email": "john-stevenson@blueyonder.co.uk" + } + ], + "description": "Restarts a process without Xdebug.", + "keywords": [ + "Xdebug", + "performance" + ], + "funding": [ + { + "url": "https://packagist.com", + "type": "custom" + }, + { + "url": "https://github.com/composer", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/composer/composer", + "type": "tidelift" + } + ], + "time": "2024-05-06T16:37:16+00:00" + }, + { + "name": "evenement/evenement", + "version": "v3.0.2", + "source": { + "type": "git", + "url": "https://github.com/igorw/evenement.git", + "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/igorw/evenement/zipball/0a16b0d71ab13284339abb99d9d2bd813640efbc", + "reference": "0a16b0d71ab13284339abb99d9d2bd813640efbc", + "shasum": "" + }, + "require": { + "php": ">=7.0" + }, + "require-dev": { + "phpunit/phpunit": "^9 || ^6" + }, + "type": "library", + "autoload": { + "psr-4": { + "Evenement\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Igor Wiedler", + "email": "igor@wiedler.ch" + } + ], + "description": "Événement is a very simple event dispatching library for PHP", + "keywords": [ + "event-dispatcher", + "event-emitter" + ], + "time": "2023-08-08T05:53:35+00:00" + }, + { + "name": "fidry/cpu-core-counter", + "version": "1.1.0", + "source": { + "type": "git", + "url": "https://github.com/theofidry/cpu-core-counter.git", + "reference": "f92996c4d5c1a696a6a970e20f7c4216200fcc42" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/f92996c4d5c1a696a6a970e20f7c4216200fcc42", + "reference": "f92996c4d5c1a696a6a970e20f7c4216200fcc42", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "require-dev": { + "fidry/makefile": "^0.2.0", + "fidry/php-cs-fixer-config": "^1.1.2", + "phpstan/extension-installer": "^1.2.0", + "phpstan/phpstan": "^1.9.2", + "phpstan/phpstan-deprecation-rules": "^1.0.0", + "phpstan/phpstan-phpunit": "^1.2.2", + "phpstan/phpstan-strict-rules": "^1.4.4", + "phpunit/phpunit": "^8.5.31 || ^9.5.26", + "webmozarts/strict-phpunit": "^7.5" + }, + "type": "library", + "autoload": { + "psr-4": { + "Fidry\\CpuCoreCounter\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Théo FIDRY", + "email": "theo.fidry@gmail.com" + } + ], + "description": "Tiny utility to get the number of CPU cores.", + "keywords": [ + "CPU", + "core" + ], + "funding": [ + { + "url": "https://github.com/theofidry", + "type": "github" + } + ], + "time": "2024-02-07T09:43:46+00:00" + }, + { + "name": "friendsofphp/php-cs-fixer", + "version": "v3.62.0", + "source": { + "type": "git", + "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", + "reference": "627692f794d35c43483f34b01d94740df2a73507" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/627692f794d35c43483f34b01d94740df2a73507", + "reference": "627692f794d35c43483f34b01d94740df2a73507", + "shasum": "" + }, + "require": { + "clue/ndjson-react": "^1.0", + "composer/semver": "^3.4", + "composer/xdebug-handler": "^3.0.3", + "ext-filter": "*", + "ext-json": "*", + "ext-tokenizer": "*", + "fidry/cpu-core-counter": "^1.0", + "php": "^7.4 || ^8.0", + "react/child-process": "^0.6.5", + "react/event-loop": "^1.0", + "react/promise": "^2.0 || ^3.0", + "react/socket": "^1.0", + "react/stream": "^1.0", + "sebastian/diff": "^4.0 || ^5.0 || ^6.0", + "symfony/console": "^5.4 || ^6.0 || ^7.0", + "symfony/event-dispatcher": "^5.4 || ^6.0 || ^7.0", + "symfony/filesystem": "^5.4 || ^6.0 || ^7.0", + "symfony/finder": "^5.4 || ^6.0 || ^7.0", + "symfony/options-resolver": "^5.4 || ^6.0 || ^7.0", + "symfony/polyfill-mbstring": "^1.28", + "symfony/polyfill-php80": "^1.28", + "symfony/polyfill-php81": "^1.28", + "symfony/process": "^5.4 || ^6.0 || ^7.0", + "symfony/stopwatch": "^5.4 || ^6.0 || ^7.0" + }, + "require-dev": { + "facile-it/paraunit": "^1.3 || ^2.3", + "infection/infection": "^0.29.5", + "justinrainbow/json-schema": "^5.2", + "keradus/cli-executor": "^2.1", + "mikey179/vfsstream": "^1.6.11", + "php-coveralls/php-coveralls": "^2.7", + "php-cs-fixer/accessible-object": "^1.1", + "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.5", + "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.5", + "phpunit/phpunit": "^9.6.19 || ^10.5.21 || ^11.2", + "symfony/var-dumper": "^5.4 || ^6.0 || ^7.0", + "symfony/yaml": "^5.4 || ^6.0 || ^7.0" + }, + "suggest": { + "ext-dom": "For handling output formats in XML", + "ext-mbstring": "For handling non-UTF8 characters." + }, + "bin": [ + "php-cs-fixer" + ], + "type": "application", + "autoload": { + "psr-4": { + "PhpCsFixer\\": "src/" + }, + "exclude-from-classmap": [ + "src/Fixer/Internal/*" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Dariusz Rumiński", + "email": "dariusz.ruminski@gmail.com" + } + ], + "description": "A tool to automatically fix PHP code style", + "keywords": [ + "Static code analysis", + "fixer", + "standards", + "static analysis" + ], + "funding": [ + { + "url": "https://github.com/keradus", + "type": "github" + } + ], + "time": "2024-08-07T17:03:09+00:00" + }, + { + "name": "psr/container", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/container.git", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/container/zipball/c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "reference": "c71ecc56dfe541dbd90c5360474fbc405f8d5963", + "shasum": "" + }, + "require": { + "php": ">=7.4.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Container\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common Container Interface (PHP FIG PSR-11)", + "homepage": "https://github.com/php-fig/container", + "keywords": [ + "PSR-11", + "container", + "container-interface", + "container-interop", + "psr" + ], + "time": "2021-11-05T16:47:00+00:00" + }, + { + "name": "psr/event-dispatcher", + "version": "1.0.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/event-dispatcher.git", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/event-dispatcher/zipball/dbefd12671e8a14ec7f180cab83036ed26714bb0", + "reference": "dbefd12671e8a14ec7f180cab83036ed26714bb0", + "shasum": "" + }, + "require": { + "php": ">=7.2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\EventDispatcher\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Standard interfaces for event handling.", + "keywords": [ + "events", + "psr", + "psr-14" + ], + "time": "2019-01-08T18:20:26+00:00" + }, + { + "name": "psr/log", + "version": "3.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/log.git", + "reference": "79dff0b268932c640297f5208d6298f71855c03e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/log/zipball/79dff0b268932c640297f5208d6298f71855c03e", + "reference": "79dff0b268932c640297f5208d6298f71855c03e", + "shasum": "" + }, + "require": { + "php": ">=8.0.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Log\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for logging libraries", + "homepage": "https://github.com/php-fig/log", + "keywords": [ + "log", + "psr", + "psr-3" + ], + "time": "2024-08-21T13:31:24+00:00" + }, + { + "name": "react/cache", + "version": "v1.2.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/cache.git", + "reference": "d47c472b64aa5608225f47965a484b75c7817d5b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/cache/zipball/d47c472b64aa5608225f47965a484b75c7817d5b", + "reference": "d47c472b64aa5608225f47965a484b75c7817d5b", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "react/promise": "^3.0 || ^2.0 || ^1.1" + }, + "require-dev": { + "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Cache\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Async, Promise-based cache interface for ReactPHP", + "keywords": [ + "cache", + "caching", + "promise", + "reactphp" + ], + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2022-11-30T15:59:55+00:00" + }, + { + "name": "react/child-process", + "version": "v0.6.5", + "source": { + "type": "git", + "url": "https://github.com/reactphp/child-process.git", + "reference": "e71eb1aa55f057c7a4a0d08d06b0b0a484bead43" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/child-process/zipball/e71eb1aa55f057c7a4a0d08d06b0b0a484bead43", + "reference": "e71eb1aa55f057c7a4a0d08d06b0b0a484bead43", + "shasum": "" + }, + "require": { + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "php": ">=5.3.0", + "react/event-loop": "^1.2", + "react/stream": "^1.2" + }, + "require-dev": { + "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.35", + "react/socket": "^1.8", + "sebastian/environment": "^5.0 || ^3.0 || ^2.0 || ^1.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\ChildProcess\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Event-driven library for executing child processes with ReactPHP.", + "keywords": [ + "event-driven", + "process", + "reactphp" + ], + "funding": [ + { + "url": "https://github.com/WyriHaximus", + "type": "github" + }, + { + "url": "https://github.com/clue", + "type": "github" + } + ], + "time": "2022-09-16T13:41:56+00:00" + }, + { + "name": "react/dns", + "version": "v1.13.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/dns.git", + "reference": "eb8ae001b5a455665c89c1df97f6fb682f8fb0f5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/dns/zipball/eb8ae001b5a455665c89c1df97f6fb682f8fb0f5", + "reference": "eb8ae001b5a455665c89c1df97f6fb682f8fb0f5", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "react/cache": "^1.0 || ^0.6 || ^0.5", + "react/event-loop": "^1.2", + "react/promise": "^3.2 || ^2.7 || ^1.2.1" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", + "react/async": "^4.3 || ^3 || ^2", + "react/promise-timer": "^1.11" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Dns\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Async DNS resolver for ReactPHP", + "keywords": [ + "async", + "dns", + "dns-resolver", + "reactphp" + ], + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2024-06-13T14:18:03+00:00" + }, + { + "name": "react/event-loop", + "version": "v1.5.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/event-loop.git", + "reference": "bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/event-loop/zipball/bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354", + "reference": "bbe0bd8c51ffc05ee43f1729087ed3bdf7d53354", + "shasum": "" + }, + "require": { + "php": ">=5.3.0" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" + }, + "suggest": { + "ext-pcntl": "For signal handling support when using the StreamSelectLoop" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\EventLoop\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "ReactPHP's core reactor event loop that libraries can use for evented I/O.", + "keywords": [ + "asynchronous", + "event-loop" + ], + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2023-11-13T13:48:05+00:00" + }, + { + "name": "react/promise", + "version": "v3.2.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/promise.git", + "reference": "8a164643313c71354582dc850b42b33fa12a4b63" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/promise/zipball/8a164643313c71354582dc850b42b33fa12a4b63", + "reference": "8a164643313c71354582dc850b42b33fa12a4b63", + "shasum": "" + }, + "require": { + "php": ">=7.1.0" + }, + "require-dev": { + "phpstan/phpstan": "1.10.39 || 1.4.10", + "phpunit/phpunit": "^9.6 || ^7.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "React\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "A lightweight implementation of CommonJS Promises/A for PHP", + "keywords": [ + "promise", + "promises" + ], + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2024-05-24T10:39:05+00:00" + }, + { + "name": "react/socket", + "version": "v1.16.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/socket.git", + "reference": "23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/socket/zipball/23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1", + "reference": "23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1", + "shasum": "" + }, + "require": { + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "php": ">=5.3.0", + "react/dns": "^1.13", + "react/event-loop": "^1.2", + "react/promise": "^3.2 || ^2.6 || ^1.2.1", + "react/stream": "^1.4" + }, + "require-dev": { + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", + "react/async": "^4.3 || ^3.3 || ^2", + "react/promise-stream": "^1.4", + "react/promise-timer": "^1.11" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Socket\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Async, streaming plaintext TCP/IP and secure TLS socket server and client connections for ReactPHP", + "keywords": [ + "Connection", + "Socket", + "async", + "reactphp", + "stream" + ], + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2024-07-26T10:38:09+00:00" + }, + { + "name": "react/stream", + "version": "v1.4.0", + "source": { + "type": "git", + "url": "https://github.com/reactphp/stream.git", + "reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/reactphp/stream/zipball/1e5b0acb8fe55143b5b426817155190eb6f5b18d", + "reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d", + "shasum": "" + }, + "require": { + "evenement/evenement": "^3.0 || ^2.0 || ^1.0", + "php": ">=5.3.8", + "react/event-loop": "^1.2" + }, + "require-dev": { + "clue/stream-filter": "~1.2", + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" + }, + "type": "library", + "autoload": { + "psr-4": { + "React\\Stream\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Christian Lück", + "email": "christian@clue.engineering", + "homepage": "https://clue.engineering/" + }, + { + "name": "Cees-Jan Kiewiet", + "email": "reactphp@ceesjankiewiet.nl", + "homepage": "https://wyrihaximus.net/" + }, + { + "name": "Jan Sorgalla", + "email": "jsorgalla@gmail.com", + "homepage": "https://sorgalla.com/" + }, + { + "name": "Chris Boden", + "email": "cboden@gmail.com", + "homepage": "https://cboden.dev/" + } + ], + "description": "Event-driven readable and writable streams for non-blocking I/O in ReactPHP", + "keywords": [ + "event-driven", + "io", + "non-blocking", + "pipe", + "reactphp", + "readable", + "stream", + "writable" + ], + "funding": [ + { + "url": "https://opencollective.com/reactphp", + "type": "open_collective" + } + ], + "time": "2024-06-11T12:45:25+00:00" + }, + { + "name": "sebastian/diff", + "version": "5.1.1", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/c41e007b4b62af48218231d6c2275e4c9b975b2e", + "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "phpunit/phpunit": "^10.0", + "symfony/process": "^6.4" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "5.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2024-03-02T07:15:17+00:00" + }, + { + "name": "symfony/console", + "version": "v6.4.10", + "source": { + "type": "git", + "url": "https://github.com/symfony/console.git", + "reference": "504974cbe43d05f83b201d6498c206f16fc0cdbc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/console/zipball/504974cbe43d05f83b201d6498c206f16fc0cdbc", + "reference": "504974cbe43d05f83b201d6498c206f16fc0cdbc", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/string": "^5.4|^6.0|^7.0" + }, + "conflict": { + "symfony/dependency-injection": "<5.4", + "symfony/dotenv": "<5.4", + "symfony/event-dispatcher": "<5.4", + "symfony/lock": "<5.4", + "symfony/process": "<5.4" + }, + "provide": { + "psr/log-implementation": "1.0|2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^5.4|^6.0|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/event-dispatcher": "^5.4|^6.0|^7.0", + "symfony/http-foundation": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/lock": "^5.4|^6.0|^7.0", + "symfony/messenger": "^5.4|^6.0|^7.0", + "symfony/process": "^5.4|^6.0|^7.0", + "symfony/stopwatch": "^5.4|^6.0|^7.0", + "symfony/var-dumper": "^5.4|^6.0|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Console\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Eases the creation of beautiful and testable command line interfaces", + "homepage": "https://symfony.com", + "keywords": [ + "cli", + "command-line", + "console", + "terminal" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-07-26T12:30:32+00:00" + }, + { + "name": "symfony/deprecation-contracts", + "version": "v3.5.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", + "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "files": [ + "function.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A generic function and convention to trigger deprecation notices", + "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-04-18T09:32:20+00:00" + }, + { + "name": "symfony/event-dispatcher", + "version": "v6.4.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher.git", + "reference": "8d7507f02b06e06815e56bb39aa0128e3806208b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/8d7507f02b06e06815e56bb39aa0128e3806208b", + "reference": "8d7507f02b06e06815e56bb39aa0128e3806208b", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/event-dispatcher-contracts": "^2.5|^3" + }, + "conflict": { + "symfony/dependency-injection": "<5.4", + "symfony/service-contracts": "<2.5" + }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "2.0|3.0" + }, + "require-dev": { + "psr/log": "^1|^2|^3", + "symfony/config": "^5.4|^6.0|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/error-handler": "^5.4|^6.0|^7.0", + "symfony/expression-language": "^5.4|^6.0|^7.0", + "symfony/http-foundation": "^5.4|^6.0|^7.0", + "symfony/service-contracts": "^2.5|^3", + "symfony/stopwatch": "^5.4|^6.0|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\EventDispatcher\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", + "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T14:49:08+00:00" + }, + { + "name": "symfony/event-dispatcher-contracts", + "version": "v3.5.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "8f93aec25d41b72493c6ddff14e916177c9efc50" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/8f93aec25d41b72493c6ddff14e916177c9efc50", + "reference": "8f93aec25d41b72493c6ddff14e916177c9efc50", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/event-dispatcher": "^1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\EventDispatcher\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to dispatching event", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-04-18T09:32:20+00:00" + }, + { + "name": "symfony/filesystem", + "version": "v6.4.9", + "source": { + "type": "git", + "url": "https://github.com/symfony/filesystem.git", + "reference": "b51ef8059159330b74a4d52f68e671033c0fe463" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/b51ef8059159330b74a4d52f68e671033c0fe463", + "reference": "b51ef8059159330b74a4d52f68e671033c0fe463", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-mbstring": "~1.8" + }, + "require-dev": { + "symfony/process": "^5.4|^6.4|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Filesystem\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides basic utilities for the filesystem", + "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-06-28T09:49:33+00:00" + }, + { + "name": "symfony/finder", + "version": "v6.4.10", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "af29198d87112bebdd397bd7735fbd115997824c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/af29198d87112bebdd397bd7735fbd115997824c", + "reference": "af29198d87112bebdd397bd7735fbd115997824c", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "require-dev": { + "symfony/filesystem": "^6.0|^7.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Finds files and directories via an intuitive fluent interface", + "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-07-24T07:06:38+00:00" + }, + { + "name": "symfony/options-resolver", + "version": "v6.4.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/options-resolver.git", + "reference": "22ab9e9101ab18de37839074f8a1197f55590c1b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/22ab9e9101ab18de37839074f8a1197f55590c1b", + "reference": "22ab9e9101ab18de37839074f8a1197f55590c1b", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\OptionsResolver\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an improved replacement for the array_replace PHP function", + "homepage": "https://symfony.com", + "keywords": [ + "config", + "configuration", + "options" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T14:49:08+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.30.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "0424dff1c58f028c451efff2045f5d92410bd540" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/0424dff1c58f028c451efff2045f5d92410bd540", + "reference": "0424dff1c58f028c451efff2045f5d92410bd540", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Ctype\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for ctype functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "ctype", + "polyfill", + "portable" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T15:07:36+00:00" + }, + { + "name": "symfony/polyfill-intl-grapheme", + "version": "v1.30.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-grapheme.git", + "reference": "64647a7c30b2283f5d49b874d84a18fc22054b7a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/64647a7c30b2283f5d49b874d84a18fc22054b7a", + "reference": "64647a7c30b2283f5d49b874d84a18fc22054b7a", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Grapheme\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's grapheme_* functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "grapheme", + "intl", + "polyfill", + "portable", + "shim" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T15:07:36+00:00" + }, + { + "name": "symfony/polyfill-intl-normalizer", + "version": "v1.30.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-intl-normalizer.git", + "reference": "a95281b0be0d9ab48050ebd988b967875cdb9fdb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/a95281b0be0d9ab48050ebd988b967875cdb9fdb", + "reference": "a95281b0be0d9ab48050ebd988b967875cdb9fdb", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "suggest": { + "ext-intl": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Intl\\Normalizer\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for intl's Normalizer class and related functions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "intl", + "normalizer", + "polyfill", + "portable", + "shim" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T15:07:36+00:00" + }, + { + "name": "symfony/polyfill-mbstring", + "version": "v1.30.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fd22ab50000ef01661e2a31d850ebaa297f8e03c", + "reference": "fd22ab50000ef01661e2a31d850ebaa297f8e03c", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Mbstring\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill for the Mbstring extension", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "mbstring", + "polyfill", + "portable", + "shim" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-06-19T12:30:46+00:00" + }, + { + "name": "symfony/polyfill-php80", + "version": "v1.30.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "77fa7995ac1b21ab60769b7323d600a991a90433" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/77fa7995ac1b21ab60769b7323d600a991a90433", + "reference": "77fa7995ac1b21ab60769b7323d600a991a90433", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T15:07:36+00:00" + }, + { + "name": "symfony/polyfill-php81", + "version": "v1.30.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-php81.git", + "reference": "3fb075789fb91f9ad9af537c4012d523085bd5af" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/3fb075789fb91f9ad9af537c4012d523085bd5af", + "reference": "3fb075789fb91f9ad9af537c4012d523085bd5af", + "shasum": "" + }, + "require": { + "php": ">=7.1" + }, + "type": "library", + "extra": { + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php81\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-06-19T12:30:46+00:00" + }, + { + "name": "symfony/process", + "version": "v6.4.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "8d92dd79149f29e89ee0f480254db595f6a6a2c5" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/8d92dd79149f29e89ee0f480254db595f6a6a2c5", + "reference": "8d92dd79149f29e89ee0f480254db595f6a6a2c5", + "shasum": "" + }, + "require": { + "php": ">=8.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Executes commands in sub-processes", + "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T14:49:08+00:00" + }, + { + "name": "symfony/service-contracts", + "version": "v3.5.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/service-contracts.git", + "reference": "bd1d9e59a81d8fa4acdcea3f617c581f7475a80f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/bd1d9e59a81d8fa4acdcea3f617c581f7475a80f", + "reference": "bd1d9e59a81d8fa4acdcea3f617c581f7475a80f", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "psr/container": "^1.1|^2.0", + "symfony/deprecation-contracts": "^2.5|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "3.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + }, + "exclude-from-classmap": [ + "/Test/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-04-18T09:32:20+00:00" + }, + { + "name": "symfony/stopwatch", + "version": "v6.4.8", + "source": { + "type": "git", + "url": "https://github.com/symfony/stopwatch.git", + "reference": "63e069eb616049632cde9674c46957819454b8aa" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/63e069eb616049632cde9674c46957819454b8aa", + "reference": "63e069eb616049632cde9674c46957819454b8aa", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/service-contracts": "^2.5|^3" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Stopwatch\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides a way to profile code", + "homepage": "https://symfony.com", + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-05-31T14:49:08+00:00" + }, + { + "name": "symfony/string", + "version": "v6.4.10", + "source": { + "type": "git", + "url": "https://github.com/symfony/string.git", + "reference": "ccf9b30251719567bfd46494138327522b9a9446" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/string/zipball/ccf9b30251719567bfd46494138327522b9a9446", + "reference": "ccf9b30251719567bfd46494138327522b9a9446", + "shasum": "" + }, + "require": { + "php": ">=8.1", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-intl-grapheme": "~1.0", + "symfony/polyfill-intl-normalizer": "~1.0", + "symfony/polyfill-mbstring": "~1.0" + }, + "conflict": { + "symfony/translation-contracts": "<2.5" + }, + "require-dev": { + "symfony/error-handler": "^5.4|^6.0|^7.0", + "symfony/http-client": "^5.4|^6.0|^7.0", + "symfony/intl": "^6.2|^7.0", + "symfony/translation-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^5.4|^6.0|^7.0" + }, + "type": "library", + "autoload": { + "files": [ + "Resources/functions.php" + ], + "psr-4": { + "Symfony\\Component\\String\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", + "homepage": "https://symfony.com", + "keywords": [ + "grapheme", + "i18n", + "string", + "unicode", + "utf-8", + "utf8" + ], + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2024-07-22T10:21:14+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [], + "plugin-api-version": "1.1.0" +} diff --git a/bin/jetstream.php b/bin/jetstream.php index 80b2500bb9..3c1b603ce2 100755 --- a/bin/jetstream.php +++ b/bin/jetstream.php @@ -1,13 +1,12 @@ #!/usr/bin/env php =7.4", "ext-ctype": "*", "ext-curl": "*", "ext-dom": "*", @@ -154,16 +154,14 @@ }, "require-dev": { "dms/phpunit-arraysubset-asserts": "^0.3.1", - "friendsofphp/php-cs-fixer": "^3.94", "mikey179/vfsstream": "^1.6", "mockery/mockery": "^1.3", "php-mock/php-mock-mockery": "^1.5", - "php-mock/php-mock-phpunit": "^2.15", + "php-mock/php-mock-phpunit": "^2.10", "phpmd/phpmd": "^2.15", - "phpstan/phpstan": "^2.1.39", - "phpstan/phpstan-strict-rules": "^2.0.10", - "phpunit/phpunit": "^9", - "rector/rector": "^2.2" + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^9" }, "scripts": { "test": "phpunit", @@ -174,16 +172,16 @@ "lint": "find . -name \\*.php -not -path './vendor/*' -not -path './view/asset/*' -print0 | xargs -0 -n1 php -l", "docker:translate": "docker run --rm -v $PWD:/data -w /data friendicaci/transifex bin/run_xgettext.sh", "lang:recreate": "bin/run_xgettext.sh", - "cs:check": "php-cs-fixer check --diff", - "cs:fix": "php-cs-fixer fix", - "cs:fix-develop": "TARGET_BRANCH=develop COMMAND=fix bin/dev/fix-codestyle.sh", - "rector:check": "@rector:run --dry-run", - "rector:run": "rector --config=\".rector.php\"", - "rectify": [ - "@rector:run", - "@lang:recreate", - "@cs:fix-develop" + "cs:install": "@composer install --working-dir=bin/dev/php-cs-fixer", + "cs:check": [ + "@cs:install", + "bin/dev/php-cs-fixer/vendor/bin/php-cs-fixer check --diff" ], + "cs:fix": [ + "@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", "db:update-structure": "bin/console.php dbstructure dumpsql > database.sql", "install:prod": "@composer install -o --no-dev" } diff --git a/composer.lock b/composer.lock index 0dd31a9544..81394d02d0 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "2b4d6540f8af879e01ed418f2d5af7ab", + "content-hash": "b1e969f4be6ba6ed11d303a326333125", "packages": [ { "name": "alchemy/binary-driver", @@ -5987,70 +5987,6 @@ } ], "packages-dev": [ - { - "name": "clue/ndjson-react", - "version": "v1.3.0", - "source": { - "type": "git", - "url": "https://github.com/clue/reactphp-ndjson.git", - "reference": "392dc165fce93b5bb5c637b67e59619223c931b0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/clue/reactphp-ndjson/zipball/392dc165fce93b5bb5c637b67e59619223c931b0", - "reference": "392dc165fce93b5bb5c637b67e59619223c931b0", - "shasum": "" - }, - "require": { - "php": ">=5.3", - "react/stream": "^1.2" - }, - "require-dev": { - "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35", - "react/event-loop": "^1.2" - }, - "type": "library", - "autoload": { - "psr-4": { - "Clue\\React\\NDJson\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Christian Lück", - "email": "christian@clue.engineering" - } - ], - "description": "Streaming newline-delimited JSON (NDJSON) parser and encoder for ReactPHP.", - "homepage": "https://github.com/clue/reactphp-ndjson", - "keywords": [ - "NDJSON", - "json", - "jsonlines", - "newline", - "reactphp", - "streaming" - ], - "support": { - "issues": "https://github.com/clue/reactphp-ndjson/issues", - "source": "https://github.com/clue/reactphp-ndjson/tree/v1.3.0" - }, - "funding": [ - { - "url": "https://clue.engineering/support", - "type": "custom" - }, - { - "url": "https://github.com/clue", - "type": "github" - } - ], - "time": "2022-12-23T10:58:28+00:00" - }, { "name": "composer/pcre", "version": "3.3.2", @@ -6130,83 +6066,6 @@ ], "time": "2024-11-12T16:29:46+00:00" }, - { - "name": "composer/semver", - "version": "3.4.4", - "source": { - "type": "git", - "url": "https://github.com/composer/semver.git", - "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/198166618906cb2de69b95d7d47e5fa8aa1b2b95", - "reference": "198166618906cb2de69b95d7d47e5fa8aa1b2b95", - "shasum": "" - }, - "require": { - "php": "^5.3.2 || ^7.0 || ^8.0" - }, - "require-dev": { - "phpstan/phpstan": "^1.11", - "symfony/phpunit-bridge": "^3 || ^7" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Composer\\Semver\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nils Adermann", - "email": "naderman@naderman.de", - "homepage": "http://www.naderman.de" - }, - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" - }, - { - "name": "Rob Bast", - "email": "rob.bast@gmail.com", - "homepage": "http://robbast.nl" - } - ], - "description": "Semver library that offers utilities, version constraint parsing and validation.", - "keywords": [ - "semantic", - "semver", - "validation", - "versioning" - ], - "support": { - "irc": "ircs://irc.libera.chat:6697/composer", - "issues": "https://github.com/composer/semver/issues", - "source": "https://github.com/composer/semver/tree/3.4.4" - }, - "funding": [ - { - "url": "https://packagist.com", - "type": "custom" - }, - { - "url": "https://github.com/composer", - "type": "github" - } - ], - "time": "2025-08-20T19:15:30+00:00" - }, { "name": "composer/xdebug-handler", "version": "3.0.5", @@ -6388,171 +6247,6 @@ ], "time": "2022-12-30T00:15:36+00:00" }, - { - "name": "fidry/cpu-core-counter", - "version": "1.3.0", - "source": { - "type": "git", - "url": "https://github.com/theofidry/cpu-core-counter.git", - "reference": "db9508f7b1474469d9d3c53b86f817e344732678" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/theofidry/cpu-core-counter/zipball/db9508f7b1474469d9d3c53b86f817e344732678", - "reference": "db9508f7b1474469d9d3c53b86f817e344732678", - "shasum": "" - }, - "require": { - "php": "^7.2 || ^8.0" - }, - "require-dev": { - "fidry/makefile": "^0.2.0", - "fidry/php-cs-fixer-config": "^1.1.2", - "phpstan/extension-installer": "^1.2.0", - "phpstan/phpstan": "^2.0", - "phpstan/phpstan-deprecation-rules": "^2.0.0", - "phpstan/phpstan-phpunit": "^2.0", - "phpstan/phpstan-strict-rules": "^2.0", - "phpunit/phpunit": "^8.5.31 || ^9.5.26", - "webmozarts/strict-phpunit": "^7.5" - }, - "type": "library", - "autoload": { - "psr-4": { - "Fidry\\CpuCoreCounter\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Théo FIDRY", - "email": "theo.fidry@gmail.com" - } - ], - "description": "Tiny utility to get the number of CPU cores.", - "keywords": [ - "CPU", - "core" - ], - "support": { - "issues": "https://github.com/theofidry/cpu-core-counter/issues", - "source": "https://github.com/theofidry/cpu-core-counter/tree/1.3.0" - }, - "funding": [ - { - "url": "https://github.com/theofidry", - "type": "github" - } - ], - "time": "2025-08-14T07:29:31+00:00" - }, - { - "name": "friendsofphp/php-cs-fixer", - "version": "v3.94.2", - "source": { - "type": "git", - "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", - "reference": "7787ceff91365ba7d623ec410b8f429cdebb4f63" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/7787ceff91365ba7d623ec410b8f429cdebb4f63", - "reference": "7787ceff91365ba7d623ec410b8f429cdebb4f63", - "shasum": "" - }, - "require": { - "clue/ndjson-react": "^1.3", - "composer/semver": "^3.4", - "composer/xdebug-handler": "^3.0.5", - "ext-filter": "*", - "ext-hash": "*", - "ext-json": "*", - "ext-tokenizer": "*", - "fidry/cpu-core-counter": "^1.3", - "php": "^7.4 || ^8.0", - "react/child-process": "^0.6.6", - "react/event-loop": "^1.5", - "react/socket": "^1.16", - "react/stream": "^1.4", - "sebastian/diff": "^4.0.6 || ^5.1.1 || ^6.0.2 || ^7.0 || ^8.0", - "symfony/console": "^5.4.47 || ^6.4.24 || ^7.0 || ^8.0", - "symfony/event-dispatcher": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0", - "symfony/filesystem": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0", - "symfony/finder": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0", - "symfony/options-resolver": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0", - "symfony/polyfill-mbstring": "^1.33", - "symfony/polyfill-php80": "^1.33", - "symfony/polyfill-php81": "^1.33", - "symfony/polyfill-php84": "^1.33", - "symfony/process": "^5.4.47 || ^6.4.24 || ^7.2 || ^8.0", - "symfony/stopwatch": "^5.4.45 || ^6.4.24 || ^7.0 || ^8.0" - }, - "require-dev": { - "facile-it/paraunit": "^1.3.1 || ^2.7.1", - "infection/infection": "^0.32.3", - "justinrainbow/json-schema": "^6.6.4", - "keradus/cli-executor": "^2.3", - "mikey179/vfsstream": "^1.6.12", - "php-coveralls/php-coveralls": "^2.9.1", - "php-cs-fixer/phpunit-constraint-isidenticalstring": "^1.7", - "php-cs-fixer/phpunit-constraint-xmlmatchesxsd": "^1.7", - "phpunit/phpunit": "^9.6.34 || ^10.5.63 || ^11.5.51", - "symfony/polyfill-php85": "^1.33", - "symfony/var-dumper": "^5.4.48 || ^6.4.32 || ^7.4.4 || ^8.0.4", - "symfony/yaml": "^5.4.45 || ^6.4.30 || ^7.4.1 || ^8.0.1" - }, - "suggest": { - "ext-dom": "For handling output formats in XML", - "ext-mbstring": "For handling non-UTF8 characters." - }, - "bin": [ - "php-cs-fixer" - ], - "type": "application", - "autoload": { - "psr-4": { - "PhpCsFixer\\": "src/" - }, - "exclude-from-classmap": [ - "src/**/Internal/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Dariusz Rumiński", - "email": "dariusz.ruminski@gmail.com" - } - ], - "description": "A tool to automatically fix PHP code style", - "keywords": [ - "Static code analysis", - "fixer", - "standards", - "static analysis" - ], - "support": { - "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", - "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.94.2" - }, - "funding": [ - { - "url": "https://github.com/keradus", - "type": "github" - } - ], - "time": "2026-02-20T16:13:53+00:00" - }, { "name": "hamcrest/hamcrest-php", "version": "v2.1.1", @@ -6801,16 +6495,16 @@ }, { "name": "nikic/php-parser", - "version": "v5.7.0", + "version": "v5.6.1", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82" + "reference": "f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dca41cd15c2ac9d055ad70dbfd011130757d1f82", - "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2", + "reference": "f103601b29efebd7ff4a1ca7b3eeea9e3336a2a2", "shasum": "" }, "require": { @@ -6853,9 +6547,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.7.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.1" }, - "time": "2025-12-06T11:56:16+00:00" + "time": "2025-08-13T20:13:15+00:00" }, { "name": "pdepend/pdepend", @@ -7040,27 +6734,27 @@ }, { "name": "php-mock/php-mock", - "version": "2.7.0", + "version": "2.6.2", "source": { "type": "git", "url": "https://github.com/php-mock/php-mock.git", - "reference": "b59734f19765296bb0311942850d02288a224890" + "reference": "e134d210e4707c29724ebc7fe50d220123f0fdd9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-mock/php-mock/zipball/b59734f19765296bb0311942850d02288a224890", - "reference": "b59734f19765296bb0311942850d02288a224890", + "url": "https://api.github.com/repos/php-mock/php-mock/zipball/e134d210e4707c29724ebc7fe50d220123f0fdd9", + "reference": "e134d210e4707c29724ebc7fe50d220123f0fdd9", "shasum": "" }, "require": { "php": "^5.6 || ^7.0 || ^8.0", - "phpunit/php-text-template": "^1 || ^2 || ^3 || ^4 || ^5 || ^6" + "phpunit/php-text-template": "^1 || ^2 || ^3 || ^4 || ^5" }, "replace": { "malkusch/php-mock": "*" }, "require-dev": { - "phpunit/phpunit": "^5.7 || ^6.5 || ^7.5 || ^8.0 || ^9.0 || ^10.0 || ^11.0 || ^12.0 || ^13.0", + "phpunit/phpunit": "^5.7 || ^6.5 || ^7.5 || ^8.0 || ^9.0 || ^10.0 || ^11.0 || ^12.0", "squizlabs/php_codesniffer": "^3.8" }, "suggest": { @@ -7104,7 +6798,7 @@ ], "support": { "issues": "https://github.com/php-mock/php-mock/issues", - "source": "https://github.com/php-mock/php-mock/tree/2.7.0" + "source": "https://github.com/php-mock/php-mock/tree/2.6.2" }, "funding": [ { @@ -7112,29 +6806,29 @@ "type": "github" } ], - "time": "2026-02-06T07:39:37+00:00" + "time": "2025-08-18T19:59:14+00:00" }, { "name": "php-mock/php-mock-integration", - "version": "3.1.0", + "version": "3.0.0", "source": { "type": "git", "url": "https://github.com/php-mock/php-mock-integration.git", - "reference": "cbbf39705ec13dece5b04133cef4e2fd3137a345" + "reference": "8ceb860f343a143af604efeb66a7a124381cc52e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-mock/php-mock-integration/zipball/cbbf39705ec13dece5b04133cef4e2fd3137a345", - "reference": "cbbf39705ec13dece5b04133cef4e2fd3137a345", + "url": "https://api.github.com/repos/php-mock/php-mock-integration/zipball/8ceb860f343a143af604efeb66a7a124381cc52e", + "reference": "8ceb860f343a143af604efeb66a7a124381cc52e", "shasum": "" }, "require": { "php": ">=5.6", "php-mock/php-mock": "^2.5", - "phpunit/php-text-template": "^1 || ^2 || ^3 || ^4 || ^5 || ^6" + "phpunit/php-text-template": "^1 || ^2 || ^3 || ^4 || ^5" }, "require-dev": { - "phpunit/phpunit": "^5.7.27 || ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || ^13" + "phpunit/phpunit": "^5.7.27 || ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12" }, "type": "library", "autoload": { @@ -7167,7 +6861,7 @@ ], "support": { "issues": "https://github.com/php-mock/php-mock-integration/issues", - "source": "https://github.com/php-mock/php-mock-integration/tree/3.1.0" + "source": "https://github.com/php-mock/php-mock-integration/tree/3.0.0" }, "funding": [ { @@ -7175,7 +6869,7 @@ "type": "github" } ], - "time": "2026-02-06T07:44:43+00:00" + "time": "2025-03-08T19:22:38+00:00" }, { "name": "php-mock/php-mock-mockery", @@ -7244,22 +6938,22 @@ }, { "name": "php-mock/php-mock-phpunit", - "version": "2.15.0", + "version": "2.13.1", "source": { "type": "git", "url": "https://github.com/php-mock/php-mock-phpunit.git", - "reference": "701df15b183f25af663af134eb71353cd838b955" + "reference": "29f90fe44a04105959d6ae835b10c9e0da2fcaa7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-mock/php-mock-phpunit/zipball/701df15b183f25af663af134eb71353cd838b955", - "reference": "701df15b183f25af663af134eb71353cd838b955", + "url": "https://api.github.com/repos/php-mock/php-mock-phpunit/zipball/29f90fe44a04105959d6ae835b10c9e0da2fcaa7", + "reference": "29f90fe44a04105959d6ae835b10c9e0da2fcaa7", "shasum": "" }, "require": { "php": ">=7", "php-mock/php-mock-integration": "^3.0", - "phpunit/phpunit": "^6 || ^7 || ^8 || ^9 || ^10.0.17 || ^11 || ^12.0.9 || ^13" + "phpunit/phpunit": "^6 || ^7 || ^8 || ^9 || ^10.0.17 || ^11 || ^12.0.9" }, "require-dev": { "mockery/mockery": "^1.3.6" @@ -7300,7 +6994,7 @@ ], "support": { "issues": "https://github.com/php-mock/php-mock-phpunit/issues", - "source": "https://github.com/php-mock/php-mock-phpunit/tree/2.15.0" + "source": "https://github.com/php-mock/php-mock-phpunit/tree/2.13.1" }, "funding": [ { @@ -7308,7 +7002,7 @@ "type": "github" } ], - "time": "2026-02-06T09:12:10+00:00" + "time": "2025-09-23T06:00:08+00:00" }, { "name": "phpmd/phpmd", @@ -7395,11 +7089,16 @@ }, { "name": "phpstan/phpstan", - "version": "2.1.40", + "version": "2.1.29", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan-phar-composer-source.git", + "reference": "git" + }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/9b2c7aeb83a75d8680ea5e7c9b7fca88052b766b", - "reference": "9b2c7aeb83a75d8680ea5e7c9b7fca88052b766b", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/d618573eed4a1b6b75e37b2e0b65ac65c885d88e", + "reference": "d618573eed4a1b6b75e37b2e0b65ac65c885d88e", "shasum": "" }, "require": { @@ -7444,25 +7143,25 @@ "type": "github" } ], - "time": "2026-02-23T15:04:35+00:00" + "time": "2025-09-25T06:58:18+00:00" }, { "name": "phpstan/phpstan-strict-rules", - "version": "2.0.10", + "version": "2.0.6", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan-strict-rules.git", - "reference": "1aba28b697c1e3b6bbec8a1725f8b11b6d3e5a5f" + "reference": "f9f77efa9de31992a832ff77ea52eb42d675b094" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/1aba28b697c1e3b6bbec8a1725f8b11b6d3e5a5f", - "reference": "1aba28b697c1e3b6bbec8a1725f8b11b6d3e5a5f", + "url": "https://api.github.com/repos/phpstan/phpstan-strict-rules/zipball/f9f77efa9de31992a832ff77ea52eb42d675b094", + "reference": "f9f77efa9de31992a832ff77ea52eb42d675b094", "shasum": "" }, "require": { "php": "^7.4 || ^8.0", - "phpstan/phpstan": "^2.1.39" + "phpstan/phpstan": "^2.0.4" }, "require-dev": { "php-parallel-lint/php-parallel-lint": "^1.2", @@ -7488,14 +7187,11 @@ "MIT" ], "description": "Extra strict and opinionated rules for PHPStan", - "keywords": [ - "static analysis" - ], "support": { "issues": "https://github.com/phpstan/phpstan-strict-rules/issues", - "source": "https://github.com/phpstan/phpstan-strict-rules/tree/2.0.10" + "source": "https://github.com/phpstan/phpstan-strict-rules/tree/2.0.6" }, - "time": "2026-02-11T14:17:32+00:00" + "time": "2025-07-21T12:19:29+00:00" }, { "name": "phpunit/php-code-coverage", @@ -7818,16 +7514,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.34", + "version": "9.6.29", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "b36f02317466907a230d3aa1d34467041271ef4a" + "reference": "9ecfec57835a5581bc888ea7e13b51eb55ab9dd3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/b36f02317466907a230d3aa1d34467041271ef4a", - "reference": "b36f02317466907a230d3aa1d34467041271ef4a", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/9ecfec57835a5581bc888ea7e13b51eb55ab9dd3", + "reference": "9ecfec57835a5581bc888ea7e13b51eb55ab9dd3", "shasum": "" }, "require": { @@ -7849,7 +7545,7 @@ "phpunit/php-timer": "^5.0.3", "sebastian/cli-parser": "^1.0.2", "sebastian/code-unit": "^1.0.8", - "sebastian/comparator": "^4.0.10", + "sebastian/comparator": "^4.0.9", "sebastian/diff": "^4.0.6", "sebastian/environment": "^5.1.5", "sebastian/exporter": "^4.0.8", @@ -7901,7 +7597,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.34" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.29" }, "funding": [ { @@ -7925,593 +7621,7 @@ "type": "tidelift" } ], - "time": "2026-01-27T05:45:00+00:00" - }, - { - "name": "react/cache", - "version": "v1.2.0", - "source": { - "type": "git", - "url": "https://github.com/reactphp/cache.git", - "reference": "d47c472b64aa5608225f47965a484b75c7817d5b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/reactphp/cache/zipball/d47c472b64aa5608225f47965a484b75c7817d5b", - "reference": "d47c472b64aa5608225f47965a484b75c7817d5b", - "shasum": "" - }, - "require": { - "php": ">=5.3.0", - "react/promise": "^3.0 || ^2.0 || ^1.1" - }, - "require-dev": { - "phpunit/phpunit": "^9.5 || ^5.7 || ^4.8.35" - }, - "type": "library", - "autoload": { - "psr-4": { - "React\\Cache\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Christian Lück", - "email": "christian@clue.engineering", - "homepage": "https://clue.engineering/" - }, - { - "name": "Cees-Jan Kiewiet", - "email": "reactphp@ceesjankiewiet.nl", - "homepage": "https://wyrihaximus.net/" - }, - { - "name": "Jan Sorgalla", - "email": "jsorgalla@gmail.com", - "homepage": "https://sorgalla.com/" - }, - { - "name": "Chris Boden", - "email": "cboden@gmail.com", - "homepage": "https://cboden.dev/" - } - ], - "description": "Async, Promise-based cache interface for ReactPHP", - "keywords": [ - "cache", - "caching", - "promise", - "reactphp" - ], - "support": { - "issues": "https://github.com/reactphp/cache/issues", - "source": "https://github.com/reactphp/cache/tree/v1.2.0" - }, - "funding": [ - { - "url": "https://opencollective.com/reactphp", - "type": "open_collective" - } - ], - "time": "2022-11-30T15:59:55+00:00" - }, - { - "name": "react/child-process", - "version": "v0.6.7", - "source": { - "type": "git", - "url": "https://github.com/reactphp/child-process.git", - "reference": "970f0e71945556422ee4570ccbabaedc3cf04ad3" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/reactphp/child-process/zipball/970f0e71945556422ee4570ccbabaedc3cf04ad3", - "reference": "970f0e71945556422ee4570ccbabaedc3cf04ad3", - "shasum": "" - }, - "require": { - "evenement/evenement": "^3.0 || ^2.0 || ^1.0", - "php": ">=5.3.0", - "react/event-loop": "^1.2", - "react/stream": "^1.4" - }, - "require-dev": { - "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", - "react/socket": "^1.16", - "sebastian/environment": "^5.0 || ^3.0 || ^2.0 || ^1.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "React\\ChildProcess\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Christian Lück", - "email": "christian@clue.engineering", - "homepage": "https://clue.engineering/" - }, - { - "name": "Cees-Jan Kiewiet", - "email": "reactphp@ceesjankiewiet.nl", - "homepage": "https://wyrihaximus.net/" - }, - { - "name": "Jan Sorgalla", - "email": "jsorgalla@gmail.com", - "homepage": "https://sorgalla.com/" - }, - { - "name": "Chris Boden", - "email": "cboden@gmail.com", - "homepage": "https://cboden.dev/" - } - ], - "description": "Event-driven library for executing child processes with ReactPHP.", - "keywords": [ - "event-driven", - "process", - "reactphp" - ], - "support": { - "issues": "https://github.com/reactphp/child-process/issues", - "source": "https://github.com/reactphp/child-process/tree/v0.6.7" - }, - "funding": [ - { - "url": "https://opencollective.com/reactphp", - "type": "open_collective" - } - ], - "time": "2025-12-23T15:25:20+00:00" - }, - { - "name": "react/dns", - "version": "v1.14.0", - "source": { - "type": "git", - "url": "https://github.com/reactphp/dns.git", - "reference": "7562c05391f42701c1fccf189c8225fece1cd7c3" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/reactphp/dns/zipball/7562c05391f42701c1fccf189c8225fece1cd7c3", - "reference": "7562c05391f42701c1fccf189c8225fece1cd7c3", - "shasum": "" - }, - "require": { - "php": ">=5.3.0", - "react/cache": "^1.0 || ^0.6 || ^0.5", - "react/event-loop": "^1.2", - "react/promise": "^3.2 || ^2.7 || ^1.2.1" - }, - "require-dev": { - "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", - "react/async": "^4.3 || ^3 || ^2", - "react/promise-timer": "^1.11" - }, - "type": "library", - "autoload": { - "psr-4": { - "React\\Dns\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Christian Lück", - "email": "christian@clue.engineering", - "homepage": "https://clue.engineering/" - }, - { - "name": "Cees-Jan Kiewiet", - "email": "reactphp@ceesjankiewiet.nl", - "homepage": "https://wyrihaximus.net/" - }, - { - "name": "Jan Sorgalla", - "email": "jsorgalla@gmail.com", - "homepage": "https://sorgalla.com/" - }, - { - "name": "Chris Boden", - "email": "cboden@gmail.com", - "homepage": "https://cboden.dev/" - } - ], - "description": "Async DNS resolver for ReactPHP", - "keywords": [ - "async", - "dns", - "dns-resolver", - "reactphp" - ], - "support": { - "issues": "https://github.com/reactphp/dns/issues", - "source": "https://github.com/reactphp/dns/tree/v1.14.0" - }, - "funding": [ - { - "url": "https://opencollective.com/reactphp", - "type": "open_collective" - } - ], - "time": "2025-11-18T19:34:28+00:00" - }, - { - "name": "react/event-loop", - "version": "v1.6.0", - "source": { - "type": "git", - "url": "https://github.com/reactphp/event-loop.git", - "reference": "ba276bda6083df7e0050fd9b33f66ad7a4ac747a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/reactphp/event-loop/zipball/ba276bda6083df7e0050fd9b33f66ad7a4ac747a", - "reference": "ba276bda6083df7e0050fd9b33f66ad7a4ac747a", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "require-dev": { - "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" - }, - "suggest": { - "ext-pcntl": "For signal handling support when using the StreamSelectLoop" - }, - "type": "library", - "autoload": { - "psr-4": { - "React\\EventLoop\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Christian Lück", - "email": "christian@clue.engineering", - "homepage": "https://clue.engineering/" - }, - { - "name": "Cees-Jan Kiewiet", - "email": "reactphp@ceesjankiewiet.nl", - "homepage": "https://wyrihaximus.net/" - }, - { - "name": "Jan Sorgalla", - "email": "jsorgalla@gmail.com", - "homepage": "https://sorgalla.com/" - }, - { - "name": "Chris Boden", - "email": "cboden@gmail.com", - "homepage": "https://cboden.dev/" - } - ], - "description": "ReactPHP's core reactor event loop that libraries can use for evented I/O.", - "keywords": [ - "asynchronous", - "event-loop" - ], - "support": { - "issues": "https://github.com/reactphp/event-loop/issues", - "source": "https://github.com/reactphp/event-loop/tree/v1.6.0" - }, - "funding": [ - { - "url": "https://opencollective.com/reactphp", - "type": "open_collective" - } - ], - "time": "2025-11-17T20:46:25+00:00" - }, - { - "name": "react/promise", - "version": "v3.3.0", - "source": { - "type": "git", - "url": "https://github.com/reactphp/promise.git", - "reference": "23444f53a813a3296c1368bb104793ce8d88f04a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/reactphp/promise/zipball/23444f53a813a3296c1368bb104793ce8d88f04a", - "reference": "23444f53a813a3296c1368bb104793ce8d88f04a", - "shasum": "" - }, - "require": { - "php": ">=7.1.0" - }, - "require-dev": { - "phpstan/phpstan": "1.12.28 || 1.4.10", - "phpunit/phpunit": "^9.6 || ^7.5" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions_include.php" - ], - "psr-4": { - "React\\Promise\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jan Sorgalla", - "email": "jsorgalla@gmail.com", - "homepage": "https://sorgalla.com/" - }, - { - "name": "Christian Lück", - "email": "christian@clue.engineering", - "homepage": "https://clue.engineering/" - }, - { - "name": "Cees-Jan Kiewiet", - "email": "reactphp@ceesjankiewiet.nl", - "homepage": "https://wyrihaximus.net/" - }, - { - "name": "Chris Boden", - "email": "cboden@gmail.com", - "homepage": "https://cboden.dev/" - } - ], - "description": "A lightweight implementation of CommonJS Promises/A for PHP", - "keywords": [ - "promise", - "promises" - ], - "support": { - "issues": "https://github.com/reactphp/promise/issues", - "source": "https://github.com/reactphp/promise/tree/v3.3.0" - }, - "funding": [ - { - "url": "https://opencollective.com/reactphp", - "type": "open_collective" - } - ], - "time": "2025-08-19T18:57:03+00:00" - }, - { - "name": "react/socket", - "version": "v1.17.0", - "source": { - "type": "git", - "url": "https://github.com/reactphp/socket.git", - "reference": "ef5b17b81f6f60504c539313f94f2d826c5faa08" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/reactphp/socket/zipball/ef5b17b81f6f60504c539313f94f2d826c5faa08", - "reference": "ef5b17b81f6f60504c539313f94f2d826c5faa08", - "shasum": "" - }, - "require": { - "evenement/evenement": "^3.0 || ^2.0 || ^1.0", - "php": ">=5.3.0", - "react/dns": "^1.13", - "react/event-loop": "^1.2", - "react/promise": "^3.2 || ^2.6 || ^1.2.1", - "react/stream": "^1.4" - }, - "require-dev": { - "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", - "react/async": "^4.3 || ^3.3 || ^2", - "react/promise-stream": "^1.4", - "react/promise-timer": "^1.11" - }, - "type": "library", - "autoload": { - "psr-4": { - "React\\Socket\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Christian Lück", - "email": "christian@clue.engineering", - "homepage": "https://clue.engineering/" - }, - { - "name": "Cees-Jan Kiewiet", - "email": "reactphp@ceesjankiewiet.nl", - "homepage": "https://wyrihaximus.net/" - }, - { - "name": "Jan Sorgalla", - "email": "jsorgalla@gmail.com", - "homepage": "https://sorgalla.com/" - }, - { - "name": "Chris Boden", - "email": "cboden@gmail.com", - "homepage": "https://cboden.dev/" - } - ], - "description": "Async, streaming plaintext TCP/IP and secure TLS socket server and client connections for ReactPHP", - "keywords": [ - "Connection", - "Socket", - "async", - "reactphp", - "stream" - ], - "support": { - "issues": "https://github.com/reactphp/socket/issues", - "source": "https://github.com/reactphp/socket/tree/v1.17.0" - }, - "funding": [ - { - "url": "https://opencollective.com/reactphp", - "type": "open_collective" - } - ], - "time": "2025-11-19T20:47:34+00:00" - }, - { - "name": "react/stream", - "version": "v1.4.0", - "source": { - "type": "git", - "url": "https://github.com/reactphp/stream.git", - "reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/reactphp/stream/zipball/1e5b0acb8fe55143b5b426817155190eb6f5b18d", - "reference": "1e5b0acb8fe55143b5b426817155190eb6f5b18d", - "shasum": "" - }, - "require": { - "evenement/evenement": "^3.0 || ^2.0 || ^1.0", - "php": ">=5.3.8", - "react/event-loop": "^1.2" - }, - "require-dev": { - "clue/stream-filter": "~1.2", - "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" - }, - "type": "library", - "autoload": { - "psr-4": { - "React\\Stream\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Christian Lück", - "email": "christian@clue.engineering", - "homepage": "https://clue.engineering/" - }, - { - "name": "Cees-Jan Kiewiet", - "email": "reactphp@ceesjankiewiet.nl", - "homepage": "https://wyrihaximus.net/" - }, - { - "name": "Jan Sorgalla", - "email": "jsorgalla@gmail.com", - "homepage": "https://sorgalla.com/" - }, - { - "name": "Chris Boden", - "email": "cboden@gmail.com", - "homepage": "https://cboden.dev/" - } - ], - "description": "Event-driven readable and writable streams for non-blocking I/O in ReactPHP", - "keywords": [ - "event-driven", - "io", - "non-blocking", - "pipe", - "reactphp", - "readable", - "stream", - "writable" - ], - "support": { - "issues": "https://github.com/reactphp/stream/issues", - "source": "https://github.com/reactphp/stream/tree/v1.4.0" - }, - "funding": [ - { - "url": "https://opencollective.com/reactphp", - "type": "open_collective" - } - ], - "time": "2024-06-11T12:45:25+00:00" - }, - { - "name": "rector/rector", - "version": "2.3.8", - "source": { - "type": "git", - "url": "https://github.com/rectorphp/rector.git", - "reference": "bbd37aedd8df749916cffa2a947cfc4714d1ba2c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/rectorphp/rector/zipball/bbd37aedd8df749916cffa2a947cfc4714d1ba2c", - "reference": "bbd37aedd8df749916cffa2a947cfc4714d1ba2c", - "shasum": "" - }, - "require": { - "php": "^7.4|^8.0", - "phpstan/phpstan": "^2.1.38" - }, - "conflict": { - "rector/rector-doctrine": "*", - "rector/rector-downgrade-php": "*", - "rector/rector-phpunit": "*", - "rector/rector-symfony": "*" - }, - "suggest": { - "ext-dom": "To manipulate phpunit.xml via the custom-rule command" - }, - "bin": [ - "bin/rector" - ], - "type": "library", - "autoload": { - "files": [ - "bootstrap.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "Instant Upgrade and Automated Refactoring of any PHP code", - "homepage": "https://getrector.com/", - "keywords": [ - "automation", - "dev", - "migration", - "refactoring" - ], - "support": { - "issues": "https://github.com/rectorphp/rector/issues", - "source": "https://github.com/rectorphp/rector/tree/2.3.8" - }, - "funding": [ - { - "url": "https://github.com/tomasvotruba", - "type": "github" - } - ], - "time": "2026-02-22T09:45:50+00:00" + "time": "2025-09-24T06:29:11+00:00" }, { "name": "sebastian/cli-parser", @@ -8682,16 +7792,16 @@ }, { "name": "sebastian/comparator", - "version": "4.0.10", + "version": "4.0.9", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "e4df00b9b3571187db2831ae9aada2c6efbd715d" + "reference": "67a2df3a62639eab2cc5906065e9805d4fd5dfc5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/e4df00b9b3571187db2831ae9aada2c6efbd715d", - "reference": "e4df00b9b3571187db2831ae9aada2c6efbd715d", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/67a2df3a62639eab2cc5906065e9805d4fd5dfc5", + "reference": "67a2df3a62639eab2cc5906065e9805d4fd5dfc5", "shasum": "" }, "require": { @@ -8744,7 +7854,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", - "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.10" + "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.9" }, "funding": [ { @@ -8764,7 +7874,7 @@ "type": "tidelift" } ], - "time": "2026-01-24T09:22:56+00:00" + "time": "2025-08-10T06:51:50+00:00" }, { "name": "sebastian/complexity", @@ -9603,105 +8713,6 @@ ], "time": "2024-10-30T07:58:02+00:00" }, - { - "name": "symfony/console", - "version": "v5.4.47", - "source": { - "type": "git", - "url": "https://github.com/symfony/console.git", - "reference": "c4ba980ca61a9eb18ee6bcc73f28e475852bb1ed" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/c4ba980ca61a9eb18ee6bcc73f28e475852bb1ed", - "reference": "c4ba980ca61a9eb18ee6bcc73f28e475852bb1ed", - "shasum": "" - }, - "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php73": "^1.9", - "symfony/polyfill-php80": "^1.16", - "symfony/service-contracts": "^1.1|^2|^3", - "symfony/string": "^5.1|^6.0" - }, - "conflict": { - "psr/log": ">=3", - "symfony/dependency-injection": "<4.4", - "symfony/dotenv": "<5.1", - "symfony/event-dispatcher": "<4.4", - "symfony/lock": "<4.4", - "symfony/process": "<4.4" - }, - "provide": { - "psr/log-implementation": "1.0|2.0" - }, - "require-dev": { - "psr/log": "^1|^2", - "symfony/config": "^4.4|^5.0|^6.0", - "symfony/dependency-injection": "^4.4|^5.0|^6.0", - "symfony/event-dispatcher": "^4.4|^5.0|^6.0", - "symfony/lock": "^4.4|^5.0|^6.0", - "symfony/process": "^4.4|^5.0|^6.0", - "symfony/var-dumper": "^4.4|^5.0|^6.0" - }, - "suggest": { - "psr/log": "For using the console logger", - "symfony/event-dispatcher": "", - "symfony/lock": "", - "symfony/process": "" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Console\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Eases the creation of beautiful and testable command line interfaces", - "homepage": "https://symfony.com", - "keywords": [ - "cli", - "command-line", - "console", - "terminal" - ], - "support": { - "source": "https://github.com/symfony/console/tree/v5.4.47" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-11-06T11:30:55+00:00" - }, { "name": "symfony/dependency-injection", "version": "v5.4.48", @@ -9791,305 +8802,6 @@ ], "time": "2024-11-20T10:51:57+00:00" }, - { - "name": "symfony/finder", - "version": "v5.4.45", - "source": { - "type": "git", - "url": "https://github.com/symfony/finder.git", - "reference": "63741784cd7b9967975eec610b256eed3ede022b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/63741784cd7b9967975eec610b256eed3ede022b", - "reference": "63741784cd7b9967975eec610b256eed3ede022b", - "shasum": "" - }, - "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/polyfill-php80": "^1.16" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Finder\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Finds files and directories via an intuitive fluent interface", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/finder/tree/v5.4.45" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-09-28T13:32:08+00:00" - }, - { - "name": "symfony/options-resolver", - "version": "v5.4.45", - "source": { - "type": "git", - "url": "https://github.com/symfony/options-resolver.git", - "reference": "74e5b6f0db3e8589e6cfd5efb317a1fc2bb52fb6" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/74e5b6f0db3e8589e6cfd5efb317a1fc2bb52fb6", - "reference": "74e5b6f0db3e8589e6cfd5efb317a1fc2bb52fb6", - "shasum": "" - }, - "require": { - "php": ">=7.2.5", - "symfony/deprecation-contracts": "^2.1|^3", - "symfony/polyfill-php73": "~1.0", - "symfony/polyfill-php80": "^1.16" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\OptionsResolver\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides an improved replacement for the array_replace PHP function", - "homepage": "https://symfony.com", - "keywords": [ - "config", - "configuration", - "options" - ], - "support": { - "source": "https://github.com/symfony/options-resolver/tree/v5.4.45" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-09-25T14:11:13+00:00" - }, - { - "name": "symfony/polyfill-intl-grapheme", - "version": "v1.33.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/380872130d3a5dd3ace2f4010d95125fde5d5c70", - "reference": "380872130d3a5dd3ace2f4010d95125fde5d5c70", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "suggest": { - "ext-intl": "For best performance" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Intl\\Grapheme\\": "" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for intl's grapheme_* functions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "grapheme", - "intl", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.33.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2025-06-27T09:58:17+00:00" - }, - { - "name": "symfony/polyfill-intl-normalizer", - "version": "v1.33.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "3833d7255cc303546435cb650316bff708a1c75c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/3833d7255cc303546435cb650316bff708a1c75c", - "reference": "3833d7255cc303546435cb650316bff708a1c75c", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "suggest": { - "ext-intl": "For best performance" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Intl\\Normalizer\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill for intl's Normalizer class and related functions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "intl", - "normalizer", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.33.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-09-09T11:45:10+00:00" - }, { "name": "symfony/polyfill-php81", "version": "v1.33.0", @@ -10170,246 +8882,18 @@ ], "time": "2024-09-09T11:45:10+00:00" }, - { - "name": "symfony/polyfill-php84", - "version": "v1.33.0", - "source": { - "type": "git", - "url": "https://github.com/symfony/polyfill-php84.git", - "reference": "d8ced4d875142b6a7426000426b8abc631d6b191" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php84/zipball/d8ced4d875142b6a7426000426b8abc631d6b191", - "reference": "d8ced4d875142b6a7426000426b8abc631d6b191", - "shasum": "" - }, - "require": { - "php": ">=7.2" - }, - "type": "library", - "extra": { - "thanks": { - "url": "https://github.com/symfony/polyfill", - "name": "symfony/polyfill" - } - }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Php84\\": "" - }, - "classmap": [ - "Resources/stubs" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Symfony polyfill backporting some PHP 8.4+ features to lower PHP versions", - "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], - "support": { - "source": "https://github.com/symfony/polyfill-php84/tree/v1.33.0" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://github.com/nicolas-grekas", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2025-06-24T13:30:11+00:00" - }, - { - "name": "symfony/stopwatch", - "version": "v5.4.45", - "source": { - "type": "git", - "url": "https://github.com/symfony/stopwatch.git", - "reference": "fb2c199cf302eb207f8c23e7ee174c1c31a5c004" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/fb2c199cf302eb207f8c23e7ee174c1c31a5c004", - "reference": "fb2c199cf302eb207f8c23e7ee174c1c31a5c004", - "shasum": "" - }, - "require": { - "php": ">=7.2.5", - "symfony/service-contracts": "^1|^2|^3" - }, - "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\Stopwatch\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides a way to profile code", - "homepage": "https://symfony.com", - "support": { - "source": "https://github.com/symfony/stopwatch/tree/v5.4.45" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-09-25T14:11:13+00:00" - }, - { - "name": "symfony/string", - "version": "v5.4.47", - "source": { - "type": "git", - "url": "https://github.com/symfony/string.git", - "reference": "136ca7d72f72b599f2631aca474a4f8e26719799" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/136ca7d72f72b599f2631aca474a4f8e26719799", - "reference": "136ca7d72f72b599f2631aca474a4f8e26719799", - "shasum": "" - }, - "require": { - "php": ">=7.2.5", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-intl-grapheme": "~1.0", - "symfony/polyfill-intl-normalizer": "~1.0", - "symfony/polyfill-mbstring": "~1.0", - "symfony/polyfill-php80": "~1.15" - }, - "conflict": { - "symfony/translation-contracts": ">=3.0" - }, - "require-dev": { - "symfony/error-handler": "^4.4|^5.0|^6.0", - "symfony/http-client": "^4.4|^5.0|^6.0", - "symfony/translation-contracts": "^1.1|^2", - "symfony/var-exporter": "^4.4|^5.0|^6.0" - }, - "type": "library", - "autoload": { - "files": [ - "Resources/functions.php" - ], - "psr-4": { - "Symfony\\Component\\String\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "Provides an object-oriented API to strings and deals with bytes, UTF-8 code points and grapheme clusters in a unified way", - "homepage": "https://symfony.com", - "keywords": [ - "grapheme", - "i18n", - "string", - "unicode", - "utf-8", - "utf8" - ], - "support": { - "source": "https://github.com/symfony/string/tree/v5.4.47" - }, - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2024-11-10T20:33:58+00:00" - }, { "name": "theseer/tokenizer", - "version": "1.3.1", + "version": "1.2.3", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "b7489ce515e168639d17feec34b8847c326b0b3c" + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b7489ce515e168639d17feec34b8847c326b0b3c", - "reference": "b7489ce515e168639d17feec34b8847c326b0b3c", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", "shasum": "" }, "require": { @@ -10438,7 +8922,7 @@ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "support": { "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/1.3.1" + "source": "https://github.com/theseer/tokenizer/tree/1.2.3" }, "funding": [ { @@ -10446,7 +8930,7 @@ "type": "github" } ], - "time": "2025-11-17T20:03:58+00:00" + "time": "2024-03-03T12:36:25+00:00" } ], "aliases": [], @@ -10457,7 +8941,7 @@ "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": "7.4.* || 8.0.* || 8.1.* || 8.2.* || 8.3.* || 8.4.* || 8.5.*", + "php": ">=7.4", "ext-ctype": "*", "ext-curl": "*", "ext-dom": "*", diff --git a/database.sql b/database.sql index 4aa9a008e5..b537f0cf21 100644 --- a/database.sql +++ b/database.sql @@ -1,6 +1,6 @@ -- ------------------------------------------ --- Friendica 2026.04-dev (Blutwurz) --- DB_UPDATE_VERSION 1590 +-- Friendica 2026.01 (Blutwurz) +-- DB_UPDATE_VERSION 1586 -- ------------------------------------------ @@ -236,7 +236,6 @@ CREATE TABLE IF NOT EXISTS `contact` ( INDEX `next-update` (`next-update`), INDEX `local-data-next-update` (`local-data`,`next-update`), INDEX `uid_lastitem` (`uid`,`last-item`), - INDEX `uid_created` (`uid`,`created`), INDEX `baseurl` (`baseurl`(64)), INDEX `uid_contact-type` (`uid`,`contact-type`), INDEX `uid_self_contact-type` (`uid`,`self`,`contact-type`), @@ -415,7 +414,7 @@ CREATE TABLE IF NOT EXISTS `application` ( `name` varchar(255) NOT NULL COMMENT '', `redirect_uri` varbinary(383) NOT NULL COMMENT '', `website` varbinary(383) COMMENT '', - `scopes` varchar(511) COMMENT '', + `scopes` varchar(255) COMMENT '', `read` boolean COMMENT 'Read scope', `write` boolean COMMENT 'Write scope', `follow` boolean COMMENT 'Follow scope', @@ -1354,7 +1353,6 @@ CREATE TABLE IF NOT EXISTS `post-engagement` ( `restricted` boolean NOT NULL DEFAULT '0' COMMENT 'If true, this post is either unlisted or not from a federated network', `comments` mediumint unsigned COMMENT 'Number of comments', `activities` mediumint unsigned COMMENT 'Number of activities (like, dislike, ...)', - `views` mediumint unsigned COMMENT 'Number of views', PRIMARY KEY(`uri-id`), INDEX `owner-id` (`owner-id`), INDEX `created` (`created`), @@ -1370,7 +1368,6 @@ CREATE TABLE IF NOT EXISTS `channel-post` ( `channel` int unsigned NOT NULL COMMENT 'Channel id', `uri-id` int unsigned NOT NULL COMMENT 'Post engagement entry', `uid` mediumint unsigned NOT NULL COMMENT 'User id', - `in-timeline` boolean NOT NULL DEFAULT '0' COMMENT 'If true, this post is in the user\'s main timeline', `created` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT '', `received` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT '', `commented` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT '', @@ -1392,14 +1389,13 @@ CREATE TABLE IF NOT EXISTS `system-channel-post` ( `channel` varchar(20) NOT NULL COMMENT 'System channel id', `uid` mediumint unsigned NOT NULL COMMENT 'User id', `uri-id` int unsigned NOT NULL COMMENT 'Post engagement entry', - `in-timeline` boolean NOT NULL DEFAULT '0' COMMENT 'If true, this post is in the user\'s main timeline', `created` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT '', `received` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT '', `commented` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT '', PRIMARY KEY(`channel`,`uid`,`uri-id`), INDEX `uri-id` (`uri-id`), INDEX `uid` (`uid`), - INDEX `channel_uid_created` (`channel`,`uid`,`created`), + INDEX `channel_created` (`channel`,`uid`,`created`), FOREIGN KEY (`uid`) REFERENCES `user` (`uid`) ON UPDATE RESTRICT ON DELETE CASCADE, FOREIGN KEY (`uri-id`) REFERENCES `post-engagement` (`uri-id`) ON UPDATE RESTRICT ON DELETE CASCADE ) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Posts in a system channel'; @@ -1498,47 +1494,6 @@ CREATE TABLE IF NOT EXISTS `post-media` ( FOREIGN KEY (`attach-id`) REFERENCES `attach` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE ) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Attached media'; --- --- TABLE post-media-exif --- -CREATE TABLE IF NOT EXISTS `post-media-exif` ( - `media-id` int unsigned NOT NULL COMMENT 'If of the post-media entry with EXIF data', - `uri-id` int unsigned NOT NULL COMMENT 'Id of the item-uri table entry that contains the item uri', - `raw-data` text COMMENT 'JSON array with the raw exif data', - `coord` varchar(255) COMMENT 'GPS coordinates (latitude and longitude) representing the location where the picture was taken.', - `FocalLength` varchar(16) COMMENT 'The focal length of the lens in mm.', - `ExposureTime` varchar(16) COMMENT 'The exposure time in fractions of 1/x or full seconds.', - `ApertureFNumber` varchar(16) COMMENT 'The lens aperture calculated as f number', - `ISOSpeedRatings` smallint unsigned COMMENT 'The ISO speed used to expose the image.', - `LensSpecification` varchar(32) COMMENT 'Lens specifications, for example 35mm f/2.8 or 70-200mm f/2.8-6.3', - `FocusDistance` varchar(16) COMMENT 'The distance to the subject, given in meters.', - `CCDWidth` varchar(16) COMMENT '', - `BodySerialNumber` varchar(255) COMMENT 'The serial number of the body of the camera.', - `Artist` varchar(255) COMMENT 'The name of the camera owner, photographer or image creator.', - `Copyright` varchar(255) COMMENT 'Copyright information. In this standard the tag is used to indicate both the photographer and editor copyrights.', - `DateTime` datetime COMMENT 'The date and time of image creation. In Exif standard, it is the time the file was changed.', - `DateTimeOriginal` datetime COMMENT 'The date and time when the original image data was generated.', - `DateTimeDigitized` datetime COMMENT 'The date and time when the image was stored as digital data.', - `ExpandFilm` varchar(255) COMMENT 'The type or brand of film used for the image, such as analog film types (e.g., Kodak E100SW).', - `ExpandLens` varchar(255) COMMENT 'The lens model or description used for the image (e.g., Nikkor 20-35mm f/2.8 zoom).', - `HostComputer` varchar(255) COMMENT 'Information about the host computer used to generate the image.', - `ImageDescription` text COMMENT 'A character string giving the title of the image.', - `ImageUniqueID` varchar(255) COMMENT 'A unique identifier for each image, typically in the form of a UUID or other unique string.', - `LensMake` varchar(255) COMMENT 'The name of the lens manufacturer.', - `LensModel` varchar(255) COMMENT 'The model name or model number of the lens used.', - `Make` varchar(255) COMMENT 'The manufacturer of the recording equipment.', - `MakerNote` varchar(255) COMMENT 'A tag for manufacturers of Exif writers to record any desired information. The contents are up to the manufacturer.', - `Model` varchar(255) COMMENT 'The model name or model number of the equipment.', - `OwnerName` varchar(255) COMMENT 'The owner of the camera.', - `Orientation` tinyint unsigned COMMENT 'The image orientation in terms of rows and columns.', - `Software` varchar(255) COMMENT 'The name and version of the software or firmware of the camera or image input device used to generate the image.', - `UserComment` text COMMENT 'A comment provided by the user about the image.', - PRIMARY KEY(`media-id`), - INDEX `uri-id` (`uri-id`), - FOREIGN KEY (`media-id`) REFERENCES `post-media` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE, - FOREIGN KEY (`uri-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE -) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Exif data for attached media, see https://exiv2.org/tags.html'; - -- -- TABLE post-origin -- @@ -2148,8 +2103,6 @@ CREATE VIEW `channel-post-view` AS SELECT `channel-post`.`channel` AS `channel`, `channel-post`.`uid` AS `uid`, `channel-post`.`uri-id` AS `uri-id`, - `channel-post`.`in-timeline` AS `in-timeline`, - `post-engagement`.`owner-id` AS `contact-id`, `post-engagement`.`owner-id` AS `owner-id`, `post-engagement`.`media-type` AS `media-type`, `post-engagement`.`language` AS `language`, @@ -2161,9 +2114,8 @@ CREATE VIEW `channel-post-view` AS SELECT `post-engagement`.`network` AS `network`, `ownercontact`.`contact-type` AS `contact-type`, `post-engagement`.`restricted` AS `restricted`, - `post-engagement`.`comments` AS `comments`, - `post-engagement`.`activities` AS `activities`, - `post-engagement`.`views` AS `views` + 0 AS `comments`, + 0 AS `activities` FROM `channel-post` INNER JOIN `post-engagement` ON `post-engagement`.`uri-id` = `channel-post`.`uri-id` INNER JOIN `post-thread` ON `post-thread`.`uri-id` = `channel-post`.`uri-id` @@ -2179,8 +2131,6 @@ CREATE VIEW `system-channel-post-view` AS SELECT `system-channel-post`.`channel` AS `channel`, `system-channel-post`.`uid` AS `uid`, `system-channel-post`.`uri-id` AS `uri-id`, - `system-channel-post`.`in-timeline` AS `in-timeline`, - `post-engagement`.`owner-id` AS `contact-id`, `post-engagement`.`owner-id` AS `owner-id`, `post-engagement`.`media-type` AS `media-type`, `post-engagement`.`language` AS `language`, @@ -2192,9 +2142,8 @@ CREATE VIEW `system-channel-post-view` AS SELECT `post-engagement`.`network` AS `network`, `ownercontact`.`contact-type` AS `contact-type`, `post-engagement`.`restricted` AS `restricted`, - `post-engagement`.`comments` AS `comments`, - `post-engagement`.`activities` AS `activities`, - `post-engagement`.`views` AS `views` + 0 AS `comments`, + 0 AS `activities` FROM `system-channel-post` INNER JOIN `post-engagement` ON `post-engagement`.`uri-id` = `system-channel-post`.`uri-id` INNER JOIN `post-thread` ON `post-thread`.`uri-id` = `system-channel-post`.`uri-id` @@ -2265,9 +2214,8 @@ CREATE VIEW `post-engagement-user-view` AS SELECT `post-thread-user`.`network` AS `network`, `post-user`.`protocol` AS `protocol`, `post-engagement`.`restricted` AS `restricted`, - `post-engagement`.`comments` AS `comments`, - `post-engagement`.`activities` AS `activities`, - `post-engagement`.`views` AS `views` + 0 AS `comments`, + 0 AS `activities` FROM `post-thread-user` INNER JOIN `post-engagement` ON `post-engagement`.`uri-id` = `post-thread-user`.`uri-id` INNER JOIN `post-user` ON `post-user`.`id` = `post-thread-user`.`post-user-id` @@ -2291,9 +2239,9 @@ CREATE VIEW `post-timeline-view` AS SELECT `post-user`.`gravity` AS `gravity`, `post-user`.`created` AS `created`, `post-user`.`edited` AS `edited`, - `post-thread`.`commented` AS `commented`, + `post-thread-user`.`commented` AS `commented`, `post-user`.`received` AS `received`, - `post-thread`.`changed` AS `changed`, + `post-thread-user`.`changed` AS `changed`, `post-user`.`private` AS `private`, `post-user`.`visible` AS `visible`, `post-user`.`deleted` AS `deleted`, @@ -2319,11 +2267,11 @@ CREATE VIEW `post-timeline-view` AS SELECT `post-user`.`causer-id` AS `causer-id`, `causer`.`blocked` AS `causer-blocked`, `causer`.`gsid` AS `causer-gsid`, - `post-thread`.`network` AS `parent-network`, - `post-thread`.`owner-id` AS `parent-owner-id`, - `post-thread`.`author-id` AS `parent-author-id` + `post-thread-user`.`network` AS `parent-network`, + `post-thread-user`.`owner-id` AS `parent-owner-id`, + `post-thread-user`.`author-id` AS `parent-author-id` FROM `post-user` - LEFT JOIN `post-thread` ON `post-thread`.`uri-id` = `post-user`.`parent-uri-id` + LEFT JOIN `post-thread-user` ON `post-thread-user`.`uri-id` = `post-user`.`parent-uri-id` AND `post-thread-user`.`uid` = `post-user`.`uid` STRAIGHT_JOIN `contact` ON `contact`.`id` = `post-user`.`contact-id` STRAIGHT_JOIN `contact` AS `author` ON `author`.`id` = `post-user`.`author-id` STRAIGHT_JOIN `contact` AS `owner` ON `owner`.`id` = `post-user`.`owner-id` @@ -2339,9 +2287,9 @@ CREATE VIEW `post-timeline-origin-view` AS SELECT `post-origin`.`gravity` AS `gravity`, `post-origin`.`created` AS `created`, `post-user`.`edited` AS `edited`, - `post-thread`.`commented` AS `commented`, + `post-thread-user`.`commented` AS `commented`, `post-origin`.`received` AS `received`, - `post-thread`.`changed` AS `changed`, + `post-thread-user`.`changed` AS `changed`, `post-origin`.`private` AS `private`, `post-user`.`visible` AS `visible`, `post-user`.`deleted` AS `deleted`, @@ -2369,7 +2317,7 @@ CREATE VIEW `post-timeline-origin-view` AS SELECT `causer`.`gsid` AS `causer-gsid` FROM `post-origin` INNER JOIN `post-user` ON `post-user`.`id` = `post-origin`.`id` - LEFT JOIN `post-thread` ON `post-thread`.`uri-id` = `post-origin`.`parent-uri-id` + LEFT JOIN `post-thread-user` ON `post-thread-user`.`uri-id` = `post-origin`.`parent-uri-id` AND `post-thread-user`.`uid` = `post-origin`.`uid` STRAIGHT_JOIN `contact` ON `contact`.`id` = `post-user`.`contact-id` STRAIGHT_JOIN `contact` AS `author` ON `author`.`id` = `post-user`.`author-id` STRAIGHT_JOIN `contact` AS `owner` ON `owner`.`id` = `post-user`.`owner-id` @@ -2394,8 +2342,7 @@ CREATE VIEW `post-searchindex-user-view` AS SELECT `post-user`.`protocol` AS `protocol`, `post-searchindex`.`restricted` AS `restricted`, 0 AS `comments`, - 0 AS `activities`, - 0 AS `views` + 0 AS `activities` FROM `post-thread-user` INNER JOIN `post-searchindex` ON `post-searchindex`.`uri-id` = `post-thread-user`.`uri-id` INNER JOIN `post-user` ON `post-user`.`id` = `post-thread-user`.`post-user-id` @@ -2814,9 +2761,9 @@ CREATE VIEW `post-user-view` AS SELECT `thr-parent-item-uri`.`uri` AS `thr-parent`, `post-user`.`thr-parent-id` AS `thr-parent-id`, `conversation-item-uri`.`uri` AS `conversation`, - `post-thread`.`conversation-id` AS `conversation-id`, + `post-thread-user`.`conversation-id` AS `conversation-id`, `context-item-uri`.`uri` AS `context`, - `post-thread`.`context-id` AS `context-id`, + `post-thread-user`.`context-id` AS `context-id`, `quote-item-uri`.`uri` AS `quote-uri`, `post-content`.`quote-uri-id` AS `quote-uri-id`, `item-uri`.`guid` AS `guid`, @@ -2828,9 +2775,9 @@ CREATE VIEW `post-user-view` AS SELECT `post-user`.`replies-id` AS `replies-id`, `post-user`.`created` AS `created`, `post-user`.`edited` AS `edited`, - `post-thread`.`commented` AS `commented`, + `post-thread-user`.`commented` AS `commented`, `post-user`.`received` AS `received`, - `post-thread`.`changed` AS `changed`, + `post-thread-user`.`changed` AS `changed`, `post-user`.`post-type` AS `post-type`, `post-user`.`post-reason` AS `post-reason`, `post-user`.`private` AS `private`, @@ -2962,16 +2909,15 @@ CREATE VIEW `post-user-view` AS SELECT EXISTS(SELECT `id` FROM `post-media` WHERE `post-media`.`uri-id` = `post-user`.`uri-id`) AS `has-media`, `diaspora-interaction`.`interaction` AS `signed_text`, `parent-item-uri`.`guid` AS `parent-guid`, - `post-thread`.`network` AS `parent-network`, - `post-thread`.`owner-id` AS `parent-owner-id`, - `post-thread`.`author-id` AS `parent-author-id`, + `post-thread-user`.`network` AS `parent-network`, + `post-thread-user`.`owner-id` AS `parent-owner-id`, + `post-thread-user`.`author-id` AS `parent-author-id`, `parent-post-author`.`url` AS `parent-author-link`, `parent-post-author`.`name` AS `parent-author-name`, `parent-post-author`.`nick` AS `parent-author-nick`, `parent-post-author`.`network` AS `parent-author-network` FROM `post-user` - LEFT JOIN `post-thread-user` ON `post-thread-user`.`uri-id` = `post-user`.`parent-uri-id` AND `post-thread-user`.`uid` = `post-user`.`uid` - INNER JOIN `post-thread` ON `post-thread`.`uri-id` = `post-user`.`parent-uri-id` + INNER JOIN `post-thread-user` ON `post-thread-user`.`uri-id` = `post-user`.`parent-uri-id` AND `post-thread-user`.`uid` = `post-user`.`uid` STRAIGHT_JOIN `contact` ON `contact`.`id` = `post-user`.`contact-id` STRAIGHT_JOIN `contact` AS `author` ON `author`.`id` = `post-user`.`author-id` STRAIGHT_JOIN `contact` AS `owner` ON `owner`.`id` = `post-user`.`owner-id` @@ -2979,8 +2925,8 @@ CREATE VIEW `post-user-view` AS SELECT LEFT JOIN `item-uri` ON `item-uri`.`id` = `post-user`.`uri-id` LEFT JOIN `item-uri` AS `thr-parent-item-uri` ON `thr-parent-item-uri`.`id` = `post-user`.`thr-parent-id` LEFT JOIN `item-uri` AS `parent-item-uri` ON `parent-item-uri`.`id` = `post-user`.`parent-uri-id` - LEFT JOIN `item-uri` AS `conversation-item-uri` ON `conversation-item-uri`.`id` = `post-thread`.`conversation-id` - LEFT JOIN `item-uri` AS `context-item-uri` ON `context-item-uri`.`id` = `post-thread`.`context-id` + LEFT JOIN `item-uri` AS `conversation-item-uri` ON `conversation-item-uri`.`id` = `post-thread-user`.`conversation-id` + LEFT JOIN `item-uri` AS `context-item-uri` ON `context-item-uri`.`id` = `post-thread-user`.`context-id` LEFT JOIN `item-uri` AS `external-item-uri` ON `external-item-uri`.`id` = `post-user`.`external-id` LEFT JOIN `item-uri` AS `replies-item-uri` ON `replies-item-uri`.`id` = `post-user`.`replies-id` LEFT JOIN `verb` ON `verb`.`id` = `post-user`.`vid` @@ -2991,7 +2937,7 @@ CREATE VIEW `post-user-view` AS SELECT LEFT JOIN `post-delivery-data` ON `post-delivery-data`.`uri-id` = `post-user`.`uri-id` AND `post-user`.`origin` LEFT JOIN `post-question` ON `post-question`.`uri-id` = `post-user`.`uri-id` LEFT JOIN `permissionset` ON `permissionset`.`id` = `post-user`.`psid` - LEFT JOIN `contact` AS `parent-post-author` ON `parent-post-author`.`id` = `post-thread`.`author-id`; + LEFT JOIN `contact` AS `parent-post-author` ON `parent-post-author`.`id` = `post-thread-user`.`author-id`; -- -- VIEW post-thread-user-view @@ -3571,7 +3517,6 @@ CREATE VIEW `tag-view` AS SELECT -- DROP VIEW IF EXISTS `network-thread-view`; CREATE VIEW `network-thread-view` AS SELECT - '' AS `channel`, `post-thread-user`.`uri-id` AS `uri-id`, `post-thread-user`.`post-user-id` AS `parent`, `post-thread-user`.`received` AS `received`, @@ -3601,7 +3546,6 @@ CREATE VIEW `network-thread-view` AS SELECT -- DROP VIEW IF EXISTS `network-thread-circle-view`; CREATE VIEW `network-thread-circle-view` AS SELECT - '' AS `channel`, `post-thread-user`.`uri-id` AS `uri-id`, `post-thread-user`.`post-user-id` AS `parent`, `post-thread-user`.`received` AS `received`, diff --git a/doc/de/admin/install.md b/doc/de/admin/install.md index 04b0905884..a5d9c5fb74 100644 --- a/doc/de/admin/install.md +++ b/doc/de/admin/install.md @@ -23,7 +23,7 @@ Wir planen, diese Einschränkung in einer zukünftigen Version zu beheben. * Apache mit einer aktiverten mod-rewrite-Funktion und dem Eintrag "Options All", so dass du die lokale `.htaccess`-Datei nutzen kannst * PHP 7.4+ * PHP *Kommandozeilen*-Zugang mit register_argc_argv auf "true" gesetzt in der php.ini-Datei - * Curl, GD, GMP, PDO, mbstrings, MySQLi, hash, xml, zip, Intl, IDN und OpenSSL-Erweiterung + * Curl, GD, GMP, PDO, mbstrings, MySQLi, hash, xml, zip, IntlChar, IDN und OpenSSL-Erweiterung * Das POSIX Modul muss aktiviert sein ([CentOS, RHEL](http://www.bigsoft.co.uk/blog/index.php/2014/12/08/posix-php-commands-not-working-under-centos-7http://www.bigsoft.co.uk/blog/index.php/2014/12/08/posix-php-commands-not-working-under-centos-7) haben dies z.B. deaktiviert) * Einen E-Mail Server, so dass PHP `mail()` funktioniert. Wenn kein eigener E-Mail Server zur Verfügung steht, kann alternativ das [phpmailer](https://github.com/friendica/friendica-addons/tree/develop/phpmailer) Addon mit einem externen SMTP Account verwendet werden. diff --git a/doc/en/admin/install.md b/doc/en/admin/install.md index 44b05b801b..f1981a0ffb 100644 --- a/doc/en/admin/install.md +++ b/doc/en/admin/install.md @@ -30,7 +30,7 @@ Due to the large variety of operating systems and PHP platforms in existence we * Apache with `mod_rewrite` enabled and "[AllowOverride All](https://httpd.apache.org/docs/2.4/mod/core.html#allowoverride)" so you can use a local `.htaccess` file * PHP 7.4+ * PHP *command line* access with register_argc_argv set to true in the php.ini file - * Curl, GD, GMP, PDO, mbstring, MySQLi, xml, zip, Intl, IDN and OpenSSL extensions + * Curl, GD, GMP, PDO, mbstring, MySQLi, xml, zip, IntlChar, IDN and OpenSSL extensions * The POSIX module of PHP needs to be activated (e.g. [RHEL, CentOS](http://www.bigsoft.co.uk/blog/index.php/2014/12/08/posix-php-commands-not-working-under-centos-7) have disabled it) * Some form of email server or email gateway such that PHP mail() works. If you cannot set up your own email server, you can use the [phpmailer](https://github.com/friendica/friendica-addons/tree/develop/phpmailer) addon and use a remote SMTP server. diff --git a/doc/en/developer/strategy-hooks.md b/doc/en/developer/strategy-hooks.md index 82bcfe3675..1e6bf1090b 100644 --- a/doc/en/developer/strategy-hooks.md +++ b/doc/en/developer/strategy-hooks.md @@ -80,7 +80,7 @@ return [ ## Addons -> ⚠️ Since Friendica 2026.01 the strategy hooks for addons are deprecated, please use PHP hooks instead. +> ⚠️ Since Friendica 2025.07 the strategy hooks for addons are deprecated, please use PHP hooks instead. The hook logic is useful for decoupling the Friendica core logic, but its primary goal is to modularize Friendica in creating addons. diff --git a/doc/en/spec/database/db-application.md b/doc/en/spec/database/db-application.md index 4457c519c4..2b13367e38 100644 --- a/doc/en/spec/database/db-application.md +++ b/doc/en/spec/database/db-application.md @@ -12,7 +12,7 @@ OAuth application | name | | varchar(255) | NO | | NULL | | | redirect_uri | | varbinary(383) | NO | | NULL | | | website | | varbinary(383) | YES | | NULL | | -| scopes | | varchar(511) | YES | | NULL | | +| scopes | | varchar(255) | YES | | NULL | | | read | Read scope | boolean | YES | | NULL | | | write | Write scope | boolean | YES | | NULL | | | follow | Follow scope | boolean | YES | | NULL | | diff --git a/doc/en/spec/database/db-channel-post.md b/doc/en/spec/database/db-channel-post.md index e23c2ede7b..5b661f3843 100644 --- a/doc/en/spec/database/db-channel-post.md +++ b/doc/en/spec/database/db-channel-post.md @@ -4,15 +4,14 @@ Posts in a user defined channel ## Fields -| Field | Description | Type | Null | Key | Default | Extra | -| ----------- | ------------------------------------------------- | ------------------ | ---- | --- | ------------------- | ----- | -| channel | Channel id | int unsigned | NO | PRI | NULL | | -| uri-id | Post engagement entry | int unsigned | NO | PRI | NULL | | -| uid | User id | mediumint unsigned | NO | | NULL | | -| in-timeline | If true, this post is in the user's main timeline | boolean | NO | | 0 | | -| created | | datetime | NO | | 0001-01-01 00:00:00 | | -| received | | datetime | NO | | 0001-01-01 00:00:00 | | -| commented | | datetime | NO | | 0001-01-01 00:00:00 | | +| Field | Description | Type | Null | Key | Default | Extra | +| --------- | --------------------- | ------------------ | ---- | --- | ------------------- | ----- | +| channel | Channel id | int unsigned | NO | PRI | NULL | | +| uri-id | Post engagement entry | int unsigned | NO | PRI | NULL | | +| uid | User id | mediumint unsigned | NO | | NULL | | +| created | | datetime | NO | | 0001-01-01 00:00:00 | | +| received | | datetime | NO | | 0001-01-01 00:00:00 | | +| commented | | datetime | NO | | 0001-01-01 00:00:00 | | ## Indexes diff --git a/doc/en/spec/database/db-contact.md b/doc/en/spec/database/db-contact.md index de19114e7b..c89eb41e41 100644 --- a/doc/en/spec/database/db-contact.md +++ b/doc/en/spec/database/db-contact.md @@ -114,7 +114,6 @@ contact table | next-update | next-update | | local-data-next-update | local-data, next-update | | uid_lastitem | uid, last-item | -| uid_created | uid, created | | baseurl | baseurl(64) | | uid_contact-type | uid, contact-type | | uid_self_contact-type | uid, self, contact-type | diff --git a/doc/en/spec/database/db-post-engagement.md b/doc/en/spec/database/db-post-engagement.md index be5d031520..e42381ac70 100644 --- a/doc/en/spec/database/db-post-engagement.md +++ b/doc/en/spec/database/db-post-engagement.md @@ -18,7 +18,6 @@ Engagement data per post | restricted | If true, this post is either unlisted or not from a federated network | boolean | NO | | 0 | | | comments | Number of comments | mediumint unsigned | YES | | NULL | | | activities | Number of activities (like, dislike, ...) | mediumint unsigned | YES | | NULL | | -| views | Number of views | mediumint unsigned | YES | | NULL | | ## Indexes diff --git a/doc/en/spec/database/db-post-media-exif.md b/doc/en/spec/database/db-post-media-exif.md deleted file mode 100644 index fb5e6cc015..0000000000 --- a/doc/en/spec/database/db-post-media-exif.md +++ /dev/null @@ -1,55 +0,0 @@ -# Table post-media-exif - -Exif data for attached media, see https://exiv2.org/tags.html - -## Fields - -| Field | Description | Type | Null | Key | Default | Extra | -| ----------------- | ------------------------------------------------------------------------------------------------------------------- | ----------------- | ---- | --- | ------- | ----- | -| media-id | If of the post-media entry with EXIF data | int unsigned | NO | PRI | NULL | | -| uri-id | Id of the item-uri table entry that contains the item uri | int unsigned | NO | | NULL | | -| raw-data | JSON array with the raw exif data | text | YES | | NULL | | -| coord | GPS coordinates (latitude and longitude) representing the location where the picture was taken. | varchar(255) | YES | | NULL | | -| FocalLength | The focal length of the lens in mm. | varchar(16) | YES | | NULL | | -| ExposureTime | The exposure time in fractions of 1/x or full seconds. | varchar(16) | YES | | NULL | | -| ApertureFNumber | The lens aperture calculated as f number | varchar(16) | YES | | NULL | | -| ISOSpeedRatings | The ISO speed used to expose the image. | smallint unsigned | YES | | NULL | | -| LensSpecification | Lens specifications, for example 35mm f/2.8 or 70-200mm f/2.8-6.3 | varchar(32) | YES | | NULL | | -| FocusDistance | The distance to the subject, given in meters. | varchar(16) | YES | | NULL | | -| CCDWidth | | varchar(16) | YES | | NULL | | -| BodySerialNumber | The serial number of the body of the camera. | varchar(255) | YES | | NULL | | -| Artist | The name of the camera owner, photographer or image creator. | varchar(255) | YES | | NULL | | -| Copyright | Copyright information. In this standard the tag is used to indicate both the photographer and editor copyrights. | varchar(255) | YES | | NULL | | -| DateTime | The date and time of image creation. In Exif standard, it is the time the file was changed. | datetime | YES | | NULL | | -| DateTimeOriginal | The date and time when the original image data was generated. | datetime | YES | | NULL | | -| DateTimeDigitized | The date and time when the image was stored as digital data. | datetime | YES | | NULL | | -| ExpandFilm | The type or brand of film used for the image, such as analog film types (e.g., Kodak E100SW). | varchar(255) | YES | | NULL | | -| ExpandLens | The lens model or description used for the image (e.g., Nikkor 20-35mm f/2.8 zoom). | varchar(255) | YES | | NULL | | -| HostComputer | Information about the host computer used to generate the image. | varchar(255) | YES | | NULL | | -| ImageDescription | A character string giving the title of the image. | text | YES | | NULL | | -| ImageUniqueID | A unique identifier for each image, typically in the form of a UUID or other unique string. | varchar(255) | YES | | NULL | | -| LensMake | The name of the lens manufacturer. | varchar(255) | YES | | NULL | | -| LensModel | The model name or model number of the lens used. | varchar(255) | YES | | NULL | | -| Make | The manufacturer of the recording equipment. | varchar(255) | YES | | NULL | | -| MakerNote | A tag for manufacturers of Exif writers to record any desired information. The contents are up to the manufacturer. | varchar(255) | YES | | NULL | | -| Model | The model name or model number of the equipment. | varchar(255) | YES | | NULL | | -| OwnerName | The owner of the camera. | varchar(255) | YES | | NULL | | -| Orientation | The image orientation in terms of rows and columns. | tinyint unsigned | YES | | NULL | | -| Software | The name and version of the software or firmware of the camera or image input device used to generate the image. | varchar(255) | YES | | NULL | | -| UserComment | A comment provided by the user about the image. | text | YES | | NULL | | - -## Indexes - -| Name | Fields | -| ------- | -------- | -| PRIMARY | media-id | -| uri-id | uri-id | - -## Foreign keys - -| Field | Target Table | Target Field | -|-------|--------------|--------------| -| media-id | [post-media](help/spec/database/db-post-media) | id | -| uri-id | [item-uri](help/spec/database/db-item-uri) | id | - -Return to [database documentation](help/spec/database/index) diff --git a/doc/en/spec/database/db-system-channel-post.md b/doc/en/spec/database/db-system-channel-post.md index ed1d4445f1..c2de82c152 100644 --- a/doc/en/spec/database/db-system-channel-post.md +++ b/doc/en/spec/database/db-system-channel-post.md @@ -4,24 +4,23 @@ Posts in a system channel ## Fields -| Field | Description | Type | Null | Key | Default | Extra | -| ----------- | ------------------------------------------------- | ------------------ | ---- | --- | ------------------- | ----- | -| channel | System channel id | varchar(20) | NO | PRI | NULL | | -| uid | User id | mediumint unsigned | NO | PRI | NULL | | -| uri-id | Post engagement entry | int unsigned | NO | PRI | NULL | | -| in-timeline | If true, this post is in the user's main timeline | boolean | NO | | 0 | | -| created | | datetime | NO | | 0001-01-01 00:00:00 | | -| received | | datetime | NO | | 0001-01-01 00:00:00 | | -| commented | | datetime | NO | | 0001-01-01 00:00:00 | | +| Field | Description | Type | Null | Key | Default | Extra | +| --------- | --------------------- | ------------------ | ---- | --- | ------------------- | ----- | +| channel | System channel id | varchar(20) | NO | PRI | NULL | | +| uid | User id | mediumint unsigned | NO | PRI | NULL | | +| uri-id | Post engagement entry | int unsigned | NO | PRI | NULL | | +| created | | datetime | NO | | 0001-01-01 00:00:00 | | +| received | | datetime | NO | | 0001-01-01 00:00:00 | | +| commented | | datetime | NO | | 0001-01-01 00:00:00 | | ## Indexes -| Name | Fields | -| ------------------- | --------------------- | -| PRIMARY | channel, uid, uri-id | -| uri-id | uri-id | -| uid | uid | -| channel_uid_created | channel, uid, created | +| Name | Fields | +| --------------- | --------------------- | +| PRIMARY | channel, uid, uri-id | +| uri-id | uri-id | +| uid | uid | +| channel_created | channel, uid, created | ## Foreign keys diff --git a/doc/en/spec/database/index.md b/doc/en/spec/database/index.md index 3ad69622bf..3c0e2e0df5 100644 --- a/doc/en/spec/database/index.md +++ b/doc/en/spec/database/index.md @@ -65,7 +65,6 @@ | [post-history](help/spec/database/db-post-history) | Post history | | [post-link](help/spec/database/db-post-link) | Post related external links | | [post-media](help/spec/database/db-post-media) | Attached media | -| [post-media-exif](help/spec/database/db-post-media-exif) | Exif data for attached media, see https://exiv2.org/tags.html | | [post-origin](help/spec/database/db-post-origin) | Posts from local users | | [post-question](help/spec/database/db-post-question) | Question | | [post-question-option](help/spec/database/db-post-question-option) | Question option | diff --git a/mod/item.php b/mod/item.php index 07179ebc67..98c9f69372 100644 --- a/mod/item.php +++ b/mod/item.php @@ -1,5 +1,4 @@ dispatch( - new ArrayFilterEvent(ArrayFilterEvent::INSERT_POST_LOCAL_START, $_REQUEST), + new ArrayFilterEvent(ArrayFilterEvent::INSERT_POST_LOCAL_START, $_REQUEST) )->getArray(); $return_path = $_REQUEST['return'] ?? ''; @@ -102,16 +101,14 @@ function item_edit(int $uid, array $request, bool $preview, string $return_path) $post = item_process($post, $request, $preview, $return_path); $fields = [ - 'title' => $post['title'], - 'content-warning' => $post['content-warning'], - 'sensitive' => $post['sensitive'], - 'body' => $post['body'], - 'attach' => $post['attach'], - 'file' => $post['file'], - 'location' => $post['location'], - 'coord' => $post['coord'], - 'edited' => DateTimeFormat::utcNow(), - 'changed' => DateTimeFormat::utcNow(), + 'title' => $post['title'], + 'body' => $post['body'], + 'attach' => $post['attach'], + 'file' => $post['file'], + 'location' => $post['location'], + 'coord' => $post['coord'], + 'edited' => DateTimeFormat::utcNow(), + 'changed' => DateTimeFormat::utcNow() ]; $fields['body'] = Item::setHashtags($fields['body']); @@ -291,7 +288,7 @@ function item_process(array $post, array $request, bool $preview, string $return ]; $hook_data = $eventDispatcher->dispatch( - new ArrayFilterEvent(ArrayFilterEvent::INSERT_POST_LOCAL, $hook_data), + new ArrayFilterEvent(ArrayFilterEvent::INSERT_POST_LOCAL, $hook_data) )->getArray(); $post = $hook_data['item'] ?? $post; diff --git a/mod/lostpass.php b/mod/lostpass.php new file mode 100644 index 0000000000..39c1e14a81 --- /dev/null +++ b/mod/lostpass.php @@ -0,0 +1,172 @@ +redirect(); + } + + $condition = ['(`email` = ? OR `nickname` = ?) AND `verified` AND NOT `blocked` AND NOT `account_removed` AND NOT `account_expired`', $loginame, $loginame]; + $user = DBA::selectFirst('user', ['uid', 'username', 'nickname', 'email', 'language'], $condition); + if (!DBA::isResult($user)) { + DI::sysmsg()->addNotice(DI::l10n()->t('No valid account found.')); + DI::baseUrl()->redirect(); + } + + $pwdreset_token = Strings::getRandomHex(32); + + $fields = [ + 'pwdreset' => hash('sha256', $pwdreset_token), + 'pwdreset_time' => DateTimeFormat::utcNow() + ]; + $result = DBA::update('user', $fields, ['uid' => $user['uid']]); + if ($result) { + DI::sysmsg()->addInfo(DI::l10n()->t('Password reset request issued. Check your email.')); + } + + $sitename = DI::config()->get('config', 'sitename'); + $resetlink = DI::baseUrl() . '/lostpass/' . $pwdreset_token; + + $preamble = Strings::deindent(DI::l10n()->t(' + Dear %1$s, + A request was recently received at "%2$s" to reset your account + password. In order to confirm this request, please select the verification link + below or paste it into your web browser address bar. + + If you did NOT request this change, please DO NOT follow the link + provided and ignore and/or delete this email, the request will expire shortly. + + Your password will not be changed unless we can verify that you + issued this request.', $user['username'], $sitename)); + $body = Strings::deindent(DI::l10n()->t(' + Follow this link soon to verify your identity: + + %1$s + + You will then receive a follow-up message containing the new password. + You may change that password from your account settings page after logging in. + + The login details are as follows: + + Site Location: %2$s + Login Name: %3$s', $resetlink, DI::baseUrl(), $user['nickname'])); + + $email = DI::emailer() + ->newSystemMail() + ->withMessage(DI::l10n()->t('Password reset requested at %s', $sitename), $preamble, $body) + ->forUser($user) + ->withRecipient($user['email']) + ->build(); + + DI::emailer()->send($email); + DI::baseUrl()->redirect(); +} + +function lostpass_content() +{ + if (DI::args()->getArgc() > 1) { + $pwdreset_token = DI::args()->getArgv()[1]; + + $user = DBA::selectFirst('user', ['uid', 'username', 'nickname', 'email', 'pwdreset_time', 'language'], ['pwdreset' => hash('sha256', $pwdreset_token)]); + if (!DBA::isResult($user)) { + DI::sysmsg()->addNotice(DI::l10n()->t("Request could not be verified. \x28You may have previously submitted it.\x29 Password reset failed.")); + + return lostpass_form(); + } + + // Password reset requests expire in 60 minutes + if ($user['pwdreset_time'] < DateTimeFormat::utc('now - 1 hour')) { + $fields = [ + 'pwdreset' => null, + 'pwdreset_time' => null + ]; + DBA::update('user', $fields, ['uid' => $user['uid']]); + + DI::sysmsg()->addNotice(DI::l10n()->t('Request has expired, please make a new one.')); + + return lostpass_form(); + } + + return lostpass_generate_password($user); + } else { + return lostpass_form(); + } +} + +function lostpass_form() +{ + $tpl = Renderer::getMarkupTemplate('lostpass.tpl'); + $o = Renderer::replaceMacros($tpl, [ + '$title' => DI::l10n()->t('Forgot your Password?'), + '$desc' => DI::l10n()->t('Enter your email address and submit to have your password reset. Then check your email for further instructions.'), + '$name' => DI::l10n()->t('Nickname or email'), + '$submit' => DI::l10n()->t('Reset my password') + ]); + + return $o; +} + +function lostpass_generate_password($user) +{ + $o = ''; + + $new_password = User::generateNewPassword(); + $result = User::updatePassword($user['uid'], $new_password); + if (DBA::isResult($result)) { + $tpl = Renderer::getMarkupTemplate('pwdreset.tpl'); + $o .= Renderer::replaceMacros($tpl, [ + '$lbl1' => DI::l10n()->t('Password Reset'), + '$lbl2' => DI::l10n()->t('Your password has been reset as requested.'), + '$lbl3' => DI::l10n()->t('Your new password is'), + '$lbl4' => DI::l10n()->t('Save or copy your new password - and then'), + '$lbl5' => '' . DI::l10n()->t('click here to login') . '.', + '$lbl6' => DI::l10n()->t('Your password may be changed from the Settings page after successful login.'), + '$newpass' => $new_password, + ]); + + DI::sysmsg()->addInfo(DI::l10n()->t("Your password has been reset.")); + + $sitename = DI::config()->get('config', 'sitename'); + $preamble = Strings::deindent(DI::l10n()->t(' + Dear %1$s, + Your password has been changed as requested. Please retain this + information for your records ' . "\x28" . 'or change your password immediately to + something that you will remember' . "\x29" . '. + ', $user['username'])); + $body = Strings::deindent(DI::l10n()->t(' + Your login details are as follows: + + Site Location: %1$s + Login Name: %2$s + Password: %3$s + + You may change that password from your account settings page after logging in. + ', DI::baseUrl(), $user['nickname'], $new_password)); + + $email = DI::emailer() + ->newSystemMail() + ->withMessage(DI::l10n()->t('Your password has been changed at %s', $sitename), $preamble, $body) + ->forUser($user) + ->withRecipient($user['email']) + ->build(); + DI::emailer()->send($email); + } + + return $o; +} diff --git a/mod/message.php b/mod/message.php index 821eb795ac..8d14b31209 100644 --- a/mod/message.php +++ b/mod/message.php @@ -46,7 +46,7 @@ function message_init() $head_tpl = Renderer::getMarkupTemplate('message-head.tpl'); DI::page()['htmlhead'] .= Renderer::replaceMacros($head_tpl, [ - '$base' => (string) DI::baseUrl(), + '$base' => (string)DI::baseUrl() ]); } @@ -147,7 +147,7 @@ function message_content() DI::baseUrl()->redirect('message'); } - DI::baseUrl()->redirect('message/' . $conversation['id']); + DI::baseUrl()->redirect('message/' . $conversation['id'] ); } else { $parentmail = DBA::selectFirst('mail', ['parent-uri'], ['id' => DI::args()->getArgv()[2], 'uid' => DI::userSession()->getLocalUserId()]); if (DBA::isResult($parentmail)) { @@ -167,7 +167,7 @@ function message_content() $tpl = Renderer::getMarkupTemplate('msg-header.tpl'); DI::page()['htmlhead'] .= Renderer::replaceMacros($tpl, [ '$nickname' => DI::userSession()->getLocalUserNickname(), - '$linkurl' => DI::l10n()->t('Please enter a link URL:'), + '$linkurl' => DI::l10n()->t('Please enter a link URL:') ]); $recipientId = DI::args()->getArgv()[2] ?? null; @@ -188,7 +188,7 @@ function message_content() '$upload' => DI::l10n()->t('Upload photo'), '$insert' => DI::l10n()->t('Insert web link'), '$wait' => DI::l10n()->t('Please wait'), - '$submit' => DI::l10n()->t('Send Message'), + '$submit' => DI::l10n()->t('Send Message') ]); return $o; } @@ -232,14 +232,14 @@ function message_content() WHERE `mail`.`uid` = ? AND `mail`.`id` = ? LIMIT 1", DI::userSession()->getLocalUserId(), - DI::args()->getArgv()[1], + DI::args()->getArgv()[1] ); if (DBA::isResult($message)) { $contact_id = $message['contact-id']; $params = [ DI::userSession()->getLocalUserId(), - $message['parent-uri'], + $message['parent-uri'] ]; if ($message['convid']) { @@ -256,7 +256,7 @@ function message_content() WHERE `mail`.`uid` = ? $sql_extra ORDER BY `mail`.`created` ASC", - ...$params, + ...$params ); $messages = DBA::toArray($messages_stmt); @@ -274,7 +274,7 @@ function message_content() $tpl = Renderer::getMarkupTemplate('msg-header.tpl'); DI::page()['htmlhead'] .= Renderer::replaceMacros($tpl, [ '$nickname' => DI::userSession()->getLocalUserNickname(), - '$linkurl' => DI::l10n()->t('Please enter a link URL:'), + '$linkurl' => DI::l10n()->t('Please enter a link URL:') ]); $mails = []; @@ -345,7 +345,7 @@ function message_content() '$upload' => DI::l10n()->t('Upload photo'), '$insert' => DI::l10n()->t('Insert web link'), '$submit' => DI::l10n()->t('Send Message'), - '$wait' => DI::l10n()->t('Please wait'), + '$wait' => DI::l10n()->t('Please wait') ]); return $o; diff --git a/mod/notes.php b/mod/notes.php new file mode 100644 index 0000000000..478e4c4aca --- /dev/null +++ b/mod/notes.php @@ -0,0 +1,83 @@ +getLocalUserId()) { + return; + } + + Nav::setSelected('home'); +} + + +function notes_content(bool $update = false) +{ + $contactId = DI::appHelper()->getContactId(); + + if (!DI::userSession()->getLocalUserId()) { + DI::sysmsg()->addNotice(DI::l10n()->t('Permission denied.')); + return; + } + + $o = BaseProfile::getTabsHTML('notes', true, DI::userSession()->getLocalUserNickname(), false); + + if (!$update) { + $o .= '

' . DI::l10n()->t('Personal Notes') . '

'; + + $x = [ + 'lockstate' => 'lock', + 'acl' => \Friendica\Core\ACL::getSelfOnlyHTML(DI::userSession()->getLocalUserId(), DI::l10n()->t('Personal notes are visible only by yourself.')), + 'button' => DI::l10n()->t('Save'), + 'acl_data' => '', + ]; + + $o .= DI::conversation()->statusEditor($x, $contactId); + } + + $condition = ['uid' => DI::userSession()->getLocalUserId(), 'post-type' => Item::PT_PERSONAL_NOTE, 'gravity' => Item::GRAVITY_PARENT, + 'contact-id'=> $contactId]; + + if (DI::mode()->isMobile()) { + $itemsPerPage = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'system', 'itemspage_mobile_network', + DI::config()->get('system', 'itemspage_network_mobile')); + } else { + $itemsPerPage = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'system', 'itemspage_network', + DI::config()->get('system', 'itemspage_network')); + } + + $pager = new Pager(DI::l10n(), DI::args()->getQueryString(), $itemsPerPage); + + $params = ['order' => ['created' => true], + 'limit' => [$pager->getStart(), $pager->getItemsPerPage()]]; + $r = Post::selectThreadForUser(DI::userSession()->getLocalUserId(), ['uri-id'], $condition, $params); + + $count = 0; + + if (DBA::isResult($r)) { + $notes = Post::toArray($r); + + $count = count($notes); + + $o .= DI::conversation()->render($notes, Conversation::MODE_NOTES, $update); + } + + $o .= $pager->renderMinimal($count); + + return $o; +} diff --git a/mod/photos.php b/mod/photos.php index 6937e70874..5d295a2ca9 100644 --- a/mod/photos.php +++ b/mod/photos.php @@ -10,6 +10,7 @@ use Friendica\Content\Nav; use Friendica\Content\Pager; +use Friendica\Content\Text\BBCode; use Friendica\Core\ACL; use Friendica\Core\Renderer; use Friendica\Core\System; @@ -18,15 +19,23 @@ use Friendica\Database\DBStructure; use Friendica\DI; use Friendica\Event\ArrayFilterEvent; use Friendica\Model\Contact; +use Friendica\Model\Item; use Friendica\Model\Photo; +use Friendica\Model\Post; use Friendica\Model\Profile; +use Friendica\Model\Tag; use Friendica\Model\User; use Friendica\Module\BaseProfile; use Friendica\Network\HTTPException; +use Friendica\Protocol\Activity; use Friendica\Security\Security; +use Friendica\Util\Crypto; use Friendica\Util\DateTimeFormat; use Friendica\Util\Images; +use Friendica\Util\Map; use Friendica\Util\Strings; +use Friendica\Util\Temporal; +use Friendica\Util\XML; function photos_init() { @@ -61,20 +70,26 @@ function photos_init() 'total' => $album['total'], 'url' => 'photos/' . $owner['nickname'] . '/album/' . bin2hex($album['album']), 'urlencode' => urlencode($album['album']), - 'bin2hex' => bin2hex($album['album']), + 'bin2hex' => bin2hex($album['album']) ]; $ret['albums'][] = $entry; } } + if (DI::userSession()->getLocalUserId() && $owner['uid'] == DI::userSession()->getLocalUserId()) { + $can_post = true; + } else { + $can_post = false; + } + if ($ret['success']) { $photo_albums_widget = Renderer::replaceMacros(Renderer::getMarkupTemplate('photo_albums.tpl'), [ '$nick' => $owner['nickname'], '$title' => DI::l10n()->t('Photo Albums'), '$recent' => DI::l10n()->t('Recent Photos'), '$albums' => $ret['albums'], - '$upload' => [DI::l10n()->t('Upload photo'), 'photos/' . $owner['nickname'] . '/upload'], - '$can_post' => (DI::userSession()->getLocalUserId() && $owner['uid'] === DI::userSession()->getLocalUserId()), + '$upload' => [DI::l10n()->t('Upload Photos'), 'photos/' . $owner['nickname'] . '/upload'], + '$can_post' => $can_post ]); } @@ -85,7 +100,7 @@ function photos_init() $tpl = Renderer::getMarkupTemplate("photos_head.tpl"); DI::page()['htmlhead'] .= Renderer::replaceMacros($tpl, [ - '$ispublic' => DI::l10n()->t('everybody'), + '$ispublic' => DI::l10n()->t('everybody') ]); } @@ -145,13 +160,13 @@ function photos_post() if (DI::args()->getArgc() > 3 && DI::args()->getArgv()[2] === 'album') { if (!Strings::isHex(DI::args()->getArgv()[3] ?? '')) { - DI::baseUrl()->redirect('profile/' . $user['nickname'] . '/photos'); + DI::baseUrl()->redirect('photos/' . $user['nickname'] . '/album'); } $album = hex2bin(DI::args()->getArgv()[3]); if (!DBA::exists('photo', ['album' => $album, 'uid' => $page_owner_uid, 'photo-type' => Photo::DEFAULT])) { DI::sysmsg()->addNotice(DI::l10n()->t('Album not found.')); - DI::baseUrl()->redirect('profile/' . $user['nickname'] . '/photos'); + DI::baseUrl()->redirect('photos/' . $user['nickname'] . '/album'); return; // NOTREACHED } @@ -183,13 +198,13 @@ function photos_post() "SELECT distinct(`resource-id`) AS `rid` FROM `photo` WHERE `contact-id` = ? AND `uid` = ? AND `album` = ?", $visitor, $page_owner_uid, - $album, + $album )); } else { $r = DBA::toArray(DBA::p( "SELECT distinct(`resource-id`) AS `rid` FROM `photo` WHERE `uid` = ? AND `album` = ?", DI::userSession()->getLocalUserId(), - $album, + $album )); } @@ -201,6 +216,9 @@ function photos_post() // remove the associated photos Photo::delete(['resource-id' => $res, 'uid' => $page_owner_uid]); + // find and delete the corresponding item with all the comments and likes/dislikes + Item::deleteForUser(['resource-id' => $res, 'uid' => $page_owner_uid], $page_owner_uid); + // Update the photo albums cache Photo::clearAlbumCache($page_owner_uid); DI::sysmsg()->addNotice(DI::l10n()->t('Album successfully deleted')); @@ -209,7 +227,7 @@ function photos_post() } } - DI::baseUrl()->redirect('profile/' . $user['nickname'] . '/photos'); + DI::baseUrl()->redirect('photos/' . $user['nickname'] . '/album'); } if (DI::args()->getArgc() > 3 && DI::args()->getArgv()[2] === 'image') { @@ -231,6 +249,8 @@ function photos_post() if (DBA::isResult($photo)) { Photo::delete(['uid' => $page_owner_uid, 'resource-id' => $photo['resource-id']]); + Item::deleteForUser(['resource-id' => $photo['resource-id'], 'uid' => $page_owner_uid], $page_owner_uid); + // Update the photo albums cache Photo::clearAlbumCache($page_owner_uid); } else { @@ -244,6 +264,8 @@ function photos_post() if (DI::args()->getArgc() > 2 && (!empty($_POST['desc']) || !empty($_POST['newtag']) || isset($_POST['albname']))) { $desc = !empty($_POST['desc']) ? trim($_POST['desc']) : ''; + $rawtags = !empty($_POST['newtag']) ? trim($_POST['newtag']) : ''; + $item_id = !empty($_POST['item_id']) ? intval($_POST['item_id']) : 0; $albname = !empty($_POST['albname']) ? trim($_POST['albname']) : ''; $origaname = !empty($_POST['origaname']) ? trim($_POST['origaname']) : ''; @@ -295,9 +317,10 @@ function photos_post() if (DBA::isResult($photos)) { $photo = $photos[0]; + $ext = Images::getExtensionByMimeType($photo['type']); Photo::update( ['desc' => $desc, 'album' => $albname, 'allow_cid' => $str_contact_allow, 'allow_gid' => $str_circle_allow, 'deny_cid' => $str_contact_deny, 'deny_gid' => $str_circle_deny], - ['resource-id' => $resource_id, 'uid' => $page_owner_uid], + ['resource-id' => $resource_id, 'uid' => $page_owner_uid] ); // Update the photo albums cache if album name was changed @@ -306,6 +329,189 @@ function photos_post() } } + if (DBA::isResult($photos) && !$item_id) { + // Create item container + $title = ''; + $uri = Item::newURI(); + + $arr = []; + $arr['guid'] = System::createUUID(); + $arr['uid'] = $page_owner_uid; + $arr['uri'] = $uri; + $arr['post-type'] = Item::PT_IMAGE; + $arr['wall'] = 1; + $arr['resource-id'] = $photo['resource-id']; + $arr['contact-id'] = $owner_record['id']; + $arr['owner-name'] = $owner_record['name']; + $arr['owner-link'] = $owner_record['url']; + $arr['owner-avatar'] = $owner_record['thumb']; + $arr['author-name'] = $owner_record['name']; + $arr['author-link'] = $owner_record['url']; + $arr['author-avatar'] = $owner_record['thumb']; + $arr['title'] = $title; + $arr['allow_cid'] = $photo['allow_cid']; + $arr['allow_gid'] = $photo['allow_gid']; + $arr['deny_cid'] = $photo['deny_cid']; + $arr['deny_gid'] = $photo['deny_gid']; + $arr['visible'] = 0; + $arr['origin'] = 1; + + $arr['body'] = Images::getBBCodeByResource($photo['resource-id'], $user['nickname'], $photo['scale'], $ext); + + $item_id = Item::insert($arr); + } + + if ($item_id) { + $item = Post::selectFirst(['inform', 'uri-id'], ['id' => $item_id, 'uid' => $page_owner_uid]); + + if (DBA::isResult($item)) { + $old_inform = $item['inform']; + } + } + + if (strlen($rawtags)) { + $inform = ''; + + // if the new tag doesn't have a namespace specifier (@foo or #foo) give it a hashtag + $x = substr($rawtags, 0, 1); + if ($x !== '@' && $x !== '#') { + $rawtags = '#' . $rawtags; + } + + $taginfo = []; + $tags = BBCode::getTags($rawtags); + + if (count($tags)) { + foreach ($tags as $tag) { + if (strpos($tag, '@') === 0) { + $profile = ''; + $name = substr($tag, 1); + $contact = Contact::getByURL($name); + if (empty($contact)) { + $newname = $name; + if (strrpos($newname, '+')) { + $tagcid = intval(substr($newname, strrpos($newname, '+') + 1)); + } else { + $tagcid = 0; + } + + if ($tagcid) { + $contact = DBA::selectFirst('contact', [], ['id' => $tagcid, 'uid' => $page_owner_uid]); + } else { + $newname = str_replace('_', ' ', $name); + + //select someone from this user's contacts by name + $contact = DBA::selectFirst('contact', [], ['name' => $newname, 'uid' => $page_owner_uid]); + if (!DBA::isResult($contact)) { + //select someone by attag or nick and the name passed in + $contact = DBA::selectFirst( + 'contact', + [], + ['(`attag` = ? OR `nick` = ?) AND `uid` = ?', $name, $name, $page_owner_uid], + ['order' => ['attag' => true]] + ); + } + } + } + + if (DBA::isResult($contact)) { + $newname = $contact['name']; + $profile = $contact['url']; + + $notify = 'cid:' . $contact['id']; + if (strlen($inform)) { + $inform .= ','; + } + $inform .= $notify; + } + + if ($profile) { + if (!empty($contact)) { + $taginfo[] = [$newname, $profile, $notify, $contact]; + } else { + $taginfo[] = [$newname, $profile, $notify, null]; + } + + $profile = str_replace(',', '%2c', $profile); + + if (!empty($item['uri-id'])) { + Tag::store($item['uri-id'], Tag::MENTION, $newname, $profile); + } + } + } elseif (strpos($tag, '#') === 0) { + $tagname = substr($tag, 1); + if (!empty($item['uri-id'])) { + Tag::store($item['uri-id'], Tag::HASHTAG, $tagname); + } + } + } + } + + $newinform = $old_inform ?? ''; + if (strlen($newinform) && strlen($inform)) { + $newinform .= ','; + } + $newinform .= $inform; + + $fields = ['inform' => $newinform, 'edited' => DateTimeFormat::utcNow(), 'changed' => DateTimeFormat::utcNow()]; + $condition = ['id' => $item_id]; + Item::update($fields, $condition); + + $best = 0; + foreach ($photos as $scales) { + if (intval($scales['scale']) == 2) { + $best = 2; + break; + } + + if (intval($scales['scale']) == 4) { + $best = 4; + break; + } + } + + if (count($taginfo)) { + foreach ($taginfo as $tagged) { + $uri = Item::newURI(); + + $arr = [ + 'guid' => System::createUUID(), + 'uid' => $page_owner_uid, + 'uri' => $uri, + 'wall' => 1, + 'contact-id' => $owner_record['id'], + 'owner-name' => $owner_record['name'], + 'owner-link' => $owner_record['url'], + 'owner-avatar' => $owner_record['thumb'], + 'author-name' => $owner_record['name'], + 'author-link' => $owner_record['url'], + 'author-avatar' => $owner_record['thumb'], + 'title' => '', + 'allow_cid' => $photo['allow_cid'], + 'allow_gid' => $photo['allow_gid'], + 'deny_cid' => $photo['deny_cid'], + 'deny_gid' => $photo['deny_gid'], + 'visible' => 0, + 'verb' => Activity::TAG, + 'gravity' => Item::GRAVITY_PARENT, + 'object-type' => Activity\ObjectType::PERSON, + 'target-type' => Activity\ObjectType::IMAGE, + 'inform' => $tagged[2], + 'origin' => 1, + 'body' => DI::l10n()->t('%1$s was tagged in %2$s by %3$s', '[url=' . $tagged[1] . ']' . $tagged[0] . '[/url]', '[url=' . DI::baseUrl() . '/photos/' . $owner_record['nickname'] . '/image/' . $photo['resource-id'] . ']' . DI::l10n()->t('a photo') . '[/url]', '[url=' . $owner_record['url'] . ']' . $owner_record['name'] . '[/url]') . "\n\n" . '[url=' . DI::baseUrl() . '/photos/' . $owner_record['nickname'] . '/image/' . $photo['resource-id'] . ']' . '[img]' . DI::baseUrl() . '/photo/' . $photo['resource-id'] . '-' . $best . '.' . $ext . '[/img][/url]' . "\n", + 'object' => '' . Activity\ObjectType::PERSON . '' . $tagged[0] . '' . $tagged[1] . '/' . $tagged[0] . '' . XML::escape('' . "\n"), + 'target' => '' . Activity\ObjectType::IMAGE . '' . $photo['desc'] . '' . DI::baseUrl() . '/photos/' . $owner_record['nickname'] . '/image/' . $photo['resource-id'] . '' . XML::escape('' . "\n" . '') . '', + ]; + + if ($tagged[3]) { + $arr['object'] .= XML::escape('' . "\n"); + } + $arr['object'] .= '' . "\n"; + + Item::insert($arr); + } + } + } DI::baseUrl()->redirect($_SESSION['photo_return']); return; // NOTREACHED } @@ -439,21 +645,18 @@ function photos_content() $ret = [ 'post_url' => 'profile/' . $user['nickname'] . '/photos', 'addon_text' => $uploader, - 'default_upload' => true, + 'default_upload' => true ]; $eventDispatcher = DI::eventDispatcher(); $eventDispatcher->dispatch( - new ArrayFilterEvent(ArrayFilterEvent::PHOTO_UPLOAD_FORM, $ret), + new ArrayFilterEvent(ArrayFilterEvent::PHOTO_UPLOAD_FORM, $ret) ); - // Determine if we're in album context (uploading to a specific album) - $is_album_context = !empty($selname); - $default_upload_box = Renderer::replaceMacros(Renderer::getMarkupTemplate('photos_default_uploader_box.tpl'), []); $default_upload_submit = Renderer::replaceMacros(Renderer::getMarkupTemplate('photos_default_uploader_submit.tpl'), [ - '$submit' => $is_album_context ? DI::l10n()->t('Upload photo to this album') : DI::l10n()->t('Upload selected photo'), + '$submit' => DI::l10n()->t('Upload selected picture'), ]); // Get the relevant size limits for uploads. Abbreviated var names: MaxImageSize -> mis; upload_max_filesize -> umf @@ -476,12 +679,13 @@ function photos_content() $aclselect_e = ($visitor ? '' : ACL::getFullSelectorHTML(DI::page(), DI::userSession()->getLocalUserId())); $o .= Renderer::replaceMacros($tpl, [ - '$pagename' => $is_album_context ? DI::l10n()->t('Upload Photos to %s', $selname) : DI::l10n()->t('Upload Photos'), + '$pagename' => DI::l10n()->t('Upload Photos'), '$sessid' => session_id(), '$usage' => $usage_message, '$nickname' => $user['nickname'], - '$albumtext_label' => DI::l10n()->t('Album name: '), - '$albumtext_description' => DI::l10n()->t('If you want to add this photo to an album, begin typing its name, and existing albums will be suggested, which you can select. If you choose something new, it will be created.'), + '$newalbum' => DI::l10n()->t('New album name: '), + '$existalbumtext' => DI::l10n()->t('or select existing album:'), + '$nosharetext' => DI::l10n()->t('Do not show a status post for this upload'), '$albumselect' => $albumselect, '$selname' => $selname, '$permissions' => DI::l10n()->t('Permissions'), @@ -491,8 +695,6 @@ function photos_content() '$default_upload_box' => ($ret['default_upload'] ? $default_upload_box : ''), '$default_upload_submit' => ($ret['default_upload'] ? $default_upload_submit : ''), '$uploadurl' => $ret['post_url'], - '$is_album_context' => $is_album_context, - '$preselected_album' => $selname, // ACL permissions box '$return_path' => DI::args()->getQueryString(), @@ -505,7 +707,7 @@ function photos_content() if ($datatype === 'album') { // if $datum is not a valid hex, redirect to the default page if (is_null($datum) || !Strings::isHex($datum)) { - DI::baseUrl()->redirect('profile/' . $user['nickname'] . '/photos'); + DI::baseUrl()->redirect('photos/' . $user['nickname'] . '/album'); } $album = hex2bin($datum); @@ -518,7 +720,7 @@ function photos_content() "SELECT `resource-id`, MAX(`scale`) AS `scale` FROM `photo` WHERE `uid` = ? AND `album` = ? AND `scale` <= 4 $sql_extra GROUP BY `resource-id`", $owner_uid, - $album, + $album )); if (DBA::isResult($r)) { $total = count($r); @@ -543,7 +745,7 @@ function photos_content() intval($owner_uid), DBA::escape($album), $pager->getStart(), - $pager->getItemsPerPage(), + $pager->getItemsPerPage() )); if ($cmd === 'drop') { @@ -603,7 +805,7 @@ function photos_content() $photos[] = [ 'id' => $rr['id'], - 'twist' => ' ' . ($twist ? 'rotleft' : 'rotright') . random_int(2, 4), + 'twist' => ' ' . ($twist ? 'rotleft' : 'rotright') . rand(2, 4), 'link' => 'photos/' . $user['nickname'] . '/image/' . $rr['resource-id'] . ($order_field === 'created' ? '?order=created' : ''), 'title' => DI::l10n()->t('View Photo'), @@ -621,7 +823,7 @@ function photos_content() '$photos' => $photos, '$album' => $album, '$can_post' => $can_post, - '$upload' => [DI::l10n()->t('Upload photo'), 'photos/' . $user['nickname'] . '/upload/' . bin2hex($album)], + '$upload' => [DI::l10n()->t('Upload Photos'), 'photos/' . $user['nickname'] . '/upload/' . bin2hex($album)], '$order' => $order, '$edit' => $edit, '$drop' => $drop, @@ -715,7 +917,7 @@ function photos_content() $tpl = Renderer::getMarkupTemplate('photo_edit_head.tpl'); DI::page()['htmlhead'] .= Renderer::replaceMacros($tpl, [ '$prevlink' => $prevlink, - '$nextlink' => $nextlink, + '$nextlink' => $nextlink ]); if ($prevlink) { @@ -774,7 +976,9 @@ function photos_content() 'filename' => $hires['filename'], ]; - $total = 0; + $map = null; + $link_item = []; + $total = 0; // Do we have an item for this photo? @@ -784,6 +988,46 @@ function photos_content() // The difference is that we won't be displaying the conversation head item // as a "post" but displaying instead the photo it is linked to + $link_item = Post::selectFirst([], ["`resource-id` = ?" . $sql_extra, $datum]); + + if (!empty($link_item['parent']) && !empty($link_item['uid'])) { + $condition = ["`parent` = ? AND `gravity` = ?", $link_item['parent'], Item::GRAVITY_COMMENT]; + $total = Post::count($condition); + + $pager = new Pager(DI::l10n(), DI::args()->getQueryString()); + + $params = ['order' => ['id'], 'limit' => [$pager->getStart(), $pager->getItemsPerPage()]]; + $items = Post::toArray(Post::selectForUser($link_item['uid'], array_merge(Item::ITEM_FIELDLIST, ['author-alias']), $condition, $params)); + + if (DI::userSession()->getLocalUserId() == $link_item['uid']) { + Item::update(['unseen' => false], ['parent' => $link_item['parent']]); + } + } + + if (!empty($link_item['coord'])) { + $map = Map::byCoordinates($link_item['coord']); + } + + $tags = null; + + if (!empty($link_item['id'])) { + // parse tags and add links + $tag_arr = []; + foreach (explode(',', Tag::getCSVByURIId($link_item['uri-id'])) as $tag_name) { + if ($tag_name) { + $tag_arr[] = [ + 'name' => BBCode::toPlaintext($tag_name), + 'removeurl' => 'post/' . $link_item['id'] . '/tag/remove/' . bin2hex($tag_name) . '?return=' . urlencode(DI::args()->getCommand()), + ]; + } + } + $tags = ['title' => DI::l10n()->t('Tags'), 'tags' => $tag_arr]; + if ($cmd === 'edit' && !empty($tag_arr)) { + $tags['removeanyurl'] = 'post/' . $link_item['id'] . '/tag/remove?return=' . urlencode(DI::args()->getCommand()); + $tags['removetitle'] = DI::l10n()->t('[Select tags to remove]'); + } + } + $edit = null; if ($cmd === 'edit' && $can_post) { @@ -797,6 +1041,7 @@ function photos_content() '$id' => $ph[0]['id'], '$album' => ['albname', DI::l10n()->t('New album name'), $album_e, ''], '$caption' => ['desc', DI::l10n()->t('Caption'), $caption_e, ''], + '$tags' => ['newtag', DI::l10n()->t('Add a Tag'), "", "", "", "", "", DI::l10n()->t('Example: @bob, @Barbara_Jensen, @jim@example.com, #California, #camping')], '$rotate_none' => ['rotate', DI::l10n()->t('Do not rotate'), 0, '', true], '$rotate_cw' => ['rotate', DI::l10n()->t("Rotate CW \x28right\x29"), 1, ''], '$rotate_ccw' => ['rotate', DI::l10n()->t("Rotate CCW \x28left\x29"), 2, ''], @@ -806,14 +1051,219 @@ function photos_content() '$permissions' => DI::l10n()->t('Permissions'), '$aclselect' => $aclselect_e, - '$submit' => DI::l10n()->t('Save changes'), - '$delete' => DI::l10n()->t('Delete Photo'), + '$item_id' => $link_item['id'] ?? 0, + '$submit' => DI::l10n()->t('Save changes'), + '$delete' => DI::l10n()->t('Delete Photo'), // ACL permissions box '$return_path' => DI::args()->getQueryString(), ]); } + $like = ''; + $dislike = ''; + $likebuttons = ''; + $comments = ''; + $paginate = ''; + + if (!empty($link_item['id']) && !empty($link_item['uri'])) { + $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))) { + /* + * Hmmm, code depending on the presence of a particular addon? + * This should be better if done by a hook + */ + $qcomment = null; + if ($addonHelper->isAddonEnabled('qcomment')) { + $words = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'qcomment', 'words'); + $qcomment = $words ? explode("\n", $words) : []; + } + + $comments .= Renderer::replaceMacros($cmnt_tpl, [ + '$return_path' => '', + '$jsreload' => $return_path, + '$id' => $link_item['id'], + '$parent' => $link_item['id'], + '$profile_uid' => $owner_uid, + '$mylink' => $contact['url'], + '$mytitle' => DI::l10n()->t('This is you'), + '$myphoto' => $contact['thumb'], + '$comment' => DI::l10n()->t('Comment'), + '$submit' => DI::l10n()->t('Comment'), + '$preview' => DI::l10n()->t('Preview'), + '$loading' => DI::l10n()->t('Loading...'), + '$qcomment' => $qcomment, + '$rand_num' => Crypto::randomDigits(12), + ]); + } + } + + $conv_responses = [ + 'like' => [], + 'dislike' => [], + 'attendyes' => [], + 'attendno' => [], + 'attendmaybe' => [] + ]; + + if (DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'system', 'hide_dislike')) { + unset($conv_responses['dislike']); + } + + // display comments + if (DBA::isResult($items)) { + foreach ($items as $item) { + DI::conversation()->builtinActivityPuller($item, $conv_responses); + } + + if (!empty($conv_responses['like'][$link_item['uri']])) { + $like = DI::conversation()->formatActivity($conv_responses['like'][$link_item['uri']]['links'], 'like', $link_item['id'], '', []); + } + + if (!empty($conv_responses['dislike'][$link_item['uri']])) { + $dislike = DI::conversation()->formatActivity($conv_responses['dislike'][$link_item['uri']]['links'], 'dislike', $link_item['id'], '', []); + } + + if (($can_post || Security::canWriteToUserWall($owner_uid))) { + /* + * Hmmm, code depending on the presence of a particular addon? + * This should be better if done by a hook + */ + $qcomment = null; + if ($addonHelper->isAddonEnabled('qcomment')) { + $words = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'qcomment', 'words'); + $qcomment = $words ? explode("\n", $words) : []; + } + + $comments .= Renderer::replaceMacros($cmnt_tpl, [ + '$return_path' => '', + '$jsreload' => $return_path, + '$id' => $link_item['id'], + '$parent' => $link_item['id'], + '$profile_uid' => $owner_uid, + '$mylink' => $contact['url'], + '$mytitle' => DI::l10n()->t('This is you'), + '$myphoto' => $contact['thumb'], + '$comment' => DI::l10n()->t('Comment'), + '$submit' => DI::l10n()->t('Comment'), + '$preview' => DI::l10n()->t('Preview'), + '$qcomment' => $qcomment, + '$rand_num' => Crypto::randomDigits(12), + ]); + } + + foreach ($items as $item) { + $comment = ''; + $template = $tpl; + + $activity = DI::activity(); + + if (($activity->match($item['verb'], Activity::LIKE) || + $activity->match($item['verb'], Activity::DISLIKE)) && + ($item['gravity'] != Item::GRAVITY_PARENT) + ) { + continue; + } + + $author = [ + 'uid' => 0, + 'id' => $item['author-id'], + 'network' => $item['author-network'], + 'url' => $item['author-link'], + 'alias' => $item['author-alias'] + ]; + $profile_url = Contact::magicLinkByContact($author); + if (strpos($profile_url, 'contact/redir/') === 0) { + $sparkle = ' sparkle'; + } else { + $sparkle = ''; + } + + $dropping = (($item['contact-id'] == $contact_id) || ($item['uid'] == DI::userSession()->getLocalUserId())); + $drop = [ + 'dropping' => $dropping, + 'pagedrop' => false, + 'select' => DI::l10n()->t('Select'), + 'delete' => DI::l10n()->t('Delete'), + ]; + + $title_e = $item['title']; + $body_e = BBCode::convertForUriId($item['uri-id'], $item['body']); + + $comments .= Renderer::replaceMacros($template, [ + '$id' => $item['id'], + '$profile_url' => $profile_url, + '$name' => $item['author-name'], + '$thumb' => $item['author-avatar'], + '$sparkle' => $sparkle, + '$title' => $title_e, + '$body' => $body_e, + '$ago' => Temporal::getRelativeDate($item['created']), + '$indent' => (($item['parent'] != $item['id']) ? ' comment' : ''), + '$drop' => $drop, + '$comment' => $comment + ]); + + if (($can_post || Security::canWriteToUserWall($owner_uid))) { + /* + * Hmmm, code depending on the presence of a particular addon? + * This should be better if done by a hook + */ + $qcomment = null; + if ($addonHelper->isAddonEnabled('qcomment')) { + $words = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'qcomment', 'words'); + $qcomment = $words ? explode("\n", $words) : []; + } + + $comments .= Renderer::replaceMacros($cmnt_tpl, [ + '$return_path' => '', + '$jsreload' => $return_path, + '$id' => $item['id'], + '$parent' => $item['parent'], + '$profile_uid' => $owner_uid, + '$mylink' => $contact['url'], + '$mytitle' => DI::l10n()->t('This is you'), + '$myphoto' => $contact['thumb'], + '$comment' => DI::l10n()->t('Comment'), + '$submit' => DI::l10n()->t('Comment'), + '$preview' => DI::l10n()->t('Preview'), + '$qcomment' => $qcomment, + '$rand_num' => Crypto::randomDigits(12), + ]); + } + } + } + + $responses = []; + foreach ($conv_responses as $verb => $activity) { + if (isset($activity[$link_item['uri']])) { + $responses[$verb] = $activity[$link_item['uri']]; + } + } + + if ($cmd === 'view' && ($can_post || Security::canWriteToUserWall($owner_uid))) { + $like_tpl = Renderer::getMarkupTemplate('like_noshare.tpl'); + $likebuttons = Renderer::replaceMacros($like_tpl, [ + '$id' => $link_item['id'], + '$like' => DI::l10n()->t('Like'), + '$like_title' => DI::l10n()->t('I like this (toggle)'), + '$dislike' => DI::l10n()->t('Dislike'), + '$wait' => DI::l10n()->t('Please wait'), + '$dislike_title' => DI::l10n()->t('I don\'t like this (toggle)'), + '$hide_dislike' => DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'system', 'hide_dislike'), + '$responses' => $responses, + '$return_path' => DI::args()->getQueryString(), + ]); + } + + $paginate = $pager->renderFull($total); + } + $photo_tpl = Renderer::getMarkupTemplate('photo_view.tpl'); $o .= Renderer::replaceMacros($photo_tpl, [ '$id' => $ph[0]['id'], @@ -823,11 +1273,19 @@ function photos_content() '$prevlink' => $prevlink, '$nextlink' => $nextlink, '$desc' => $ph[0]['desc'], + '$tags' => $tags, '$edit' => $edit, '$edit_text' => DI::l10n()->t('Edit'), '$delete_text' => DI::l10n()->t('Delete'), '$use_as_profile_picture_text' => DI::l10n()->t('Use as profile picture'), - '$back_text' => DI::l10n()->t('Back to viewing'), + '$back_to_viewing_text' => DI::l10n()->t('Back to viewing'), + '$map' => $map, + '$map_text' => DI::l10n()->t('Map'), + '$likebuttons' => $likebuttons, + '$like' => $like, + '$dislike' => $dislike, + '$comments' => $comments, + '$paginate' => $paginate, ]); DI::page()['htmlhead'] .= "\n" . '' . "\n"; diff --git a/mod/update_contact.php b/mod/update_contact.php new file mode 100644 index 0000000000..adfbc513d7 --- /dev/null +++ b/mod/update_contact.php @@ -0,0 +1,34 @@ +get(1)) && !empty($_GET['force'])) { + $contact = DBA::selectFirst('account-user-view', ['pid', 'deleted'], ['id' => DI::args()->get(1)]); + if (DBA::isResult($contact) && empty($contact['deleted'])) { + DI::page()['aside'] = ''; + + if (!empty($_GET['item'])) { + $item = Post::selectFirst(['parent'], ['id' => $_GET['item']]); + } + + $text = Contact::getThreadsFromId($contact['pid'], DI::userSession()->getLocalUserId(), true, $item['parent'] ?? 0, $_GET['last_received'] ?? ''); + } + } + + System::htmlUpdateExit($text ?? ''); +} diff --git a/mod/update_notes.php b/mod/update_notes.php new file mode 100644 index 0000000000..06bbac6930 --- /dev/null +++ b/mod/update_notes.php @@ -0,0 +1,32 @@ +container->create(ModuleHTTPException::class), $start_time, - $request, + $request ); } @@ -339,7 +339,7 @@ class App private function registerTemplateEngine(): void { - Renderer::registerTemplateEngine(\Friendica\Render\FriendicaSmartyEngine::class); + Renderer::registerTemplateEngine('Friendica\Render\FriendicaSmartyEngine'); } /** @@ -467,11 +467,11 @@ class App if (!$this->mode->isInstall()) { // Force SSL redirection - if ($this->config->get('system', 'force_ssl') - && (empty($serverVars['HTTPS']) || $serverVars['HTTPS'] === 'off') - && (empty($serverVars['HTTP_X_FORWARDED_PROTO']) || $serverVars['HTTP_X_FORWARDED_PROTO'] === 'http') - && !empty($serverVars['REQUEST_METHOD']) - && $serverVars['REQUEST_METHOD'] === 'GET') { + if ($this->config->get('system', 'force_ssl') && + (empty($serverVars['HTTPS']) || $serverVars['HTTPS'] === 'off') && + (empty($serverVars['HTTP_X_FORWARDED_PROTO']) || $serverVars['HTTP_X_FORWARDED_PROTO'] === 'http') && + !empty($serverVars['REQUEST_METHOD']) && + $serverVars['REQUEST_METHOD'] === 'GET') { System::externalRedirect($this->baseURL . '/' . $this->args->getQueryString()); } @@ -484,8 +484,8 @@ class App if (!empty($queryVars['zrl']) && $this->mode->isNormal() && !$this->mode->isBackend() && !$this->session->getLocalUserId()) { // Only continue when the given profile link seems valid. // Valid profile links contain a path with "/profile/" and no query parameters - if ((parse_url($queryVars['zrl'], PHP_URL_QUERY) == '') - && strpos(parse_url($queryVars['zrl'], PHP_URL_PATH) ?? '', '/profile/') !== false) { + if ((parse_url($queryVars['zrl'], PHP_URL_QUERY) == '') && + strpos(parse_url($queryVars['zrl'], PHP_URL_PATH) ?? '', '/profile/') !== false) { $this->auth->setUnauthenticatedVisitor($queryVars['zrl']); OpenWebAuth::zrlInit(); } else { @@ -616,8 +616,8 @@ class App /** @var Router $router */ $router = $this->container->create(Router::class); - $moduleClass ??= $router->getModuleClass(); - $parameters = $router->getParameters(); + $moduleClass = $moduleClass ?? $router->getModuleClass(); + $parameters = $router->getParameters(); $dice_profiler_threshold = $this->config->get('system', 'dice_profiler_threshold', 0); @@ -655,10 +655,10 @@ class App @file_put_contents( $logfile, - DateTimeFormat::utcNow() . "\t" . round($duration, 3) . "\t" - . $this->requestId . "\t" . $code . "\t" - . $request . "\t" . $agent . "\n", - FILE_APPEND, + DateTimeFormat::utcNow() . "\t" . round($duration, 3) . "\t" . + $this->requestId . "\t" . $code . "\t" . + $request . "\t" . $agent . "\n", + FILE_APPEND ); } } diff --git a/src/BaseCollection.php b/src/BaseCollection.php index 61bdc93d4b..d7d707960a 100644 --- a/src/BaseCollection.php +++ b/src/BaseCollection.php @@ -32,9 +32,9 @@ class BaseCollection extends \ArrayIterator } /** - * @param mixed $key - * @param mixed $value + * @inheritDoc */ + #[\ReturnTypeWillChange] public function offsetSet($key, $value): void { if (is_null($key)) { @@ -45,8 +45,9 @@ class BaseCollection extends \ArrayIterator } /** - * @param mixed $key + * @inheritDoc */ + #[\ReturnTypeWillChange] public function offsetUnset($key): void { if ($this->offsetExists($key)) { @@ -135,7 +136,7 @@ class BaseCollection extends \ArrayIterator return new $class($array); }, - array_chunk($this->getArrayCopy(), $length), + array_chunk($this->getArrayCopy(), $length) ); } diff --git a/src/BaseRepository.php b/src/BaseRepository.php index a957769cff..d5024ef4e1 100644 --- a/src/BaseRepository.php +++ b/src/BaseRepository.php @@ -26,7 +26,7 @@ use Psr\Log\LoggerInterface; */ abstract class BaseRepository { - public const LIMIT = 30; + const LIMIT = 30; /** * @var string This should be set to the main database table name the depository is using @@ -148,15 +148,15 @@ abstract class BaseRepository } /** - * @deprecated 2026.01 Use `\Friendica\BaseRepository::_selectFirstRowAsArray()` instead + * @deprecated 2025.07 Use `\Friendica\BaseRepository::_selectFirstRowAsArray()` instead * * @throws NotFoundException */ protected function _selectOne(array $condition, array $params = []): BaseEntity { - @trigger_error('`' . __METHOD__ . '()` is deprecated since 2026.01 and will be removed after 5 months, use `\Friendica\BaseRepository::_selectFirstRowAsArray()` instead.', E_USER_DEPRECATED); + @trigger_error('`' . __METHOD__ . '()` is deprecated since 2025.07 and will be removed after 5 months, use `\Friendica\BaseRepository::_selectFirstRowAsArray()` instead.', E_USER_DEPRECATED); - $fields = $this->_selectFirstRowAsArray($condition, $params); + $fields = $this->_selectFirstRowAsArray( $condition, $params); return $this->factory->createFromTableRow($fields); } diff --git a/src/Console/Addon.php b/src/Console/Addon.php index 10518e2a56..c279ef2976 100644 --- a/src/Console/Addon.php +++ b/src/Console/Addon.php @@ -72,7 +72,7 @@ HELP; protected function doExecute(): int { if ($this->getOption('v')) { - $this->out('Class: ' . self::class); + $this->out('Class: ' . __CLASS__); $this->out('Arguments: ' . var_export($this->args, true)); $this->out('Options: ' . var_export($this->options, true)); } diff --git a/src/Console/ArchiveContact.php b/src/Console/ArchiveContact.php index b5ec0f4d58..fef6065823 100644 --- a/src/Console/ArchiveContact.php +++ b/src/Console/ArchiveContact.php @@ -62,14 +62,14 @@ HELP; parent::__construct($argv); $this->appMode = $appMode; - $this->dba = $dba; - $this->l10n = $l10n; + $this->dba = $dba; + $this->l10n = $l10n; } protected function doExecute(): int { if ($this->getOption('v')) { - $this->out('Class: ' . self::class); + $this->out('Class: ' . __CLASS__); $this->out('Arguments: ' . var_export($this->args, true)); $this->out('Options: ' . var_export($this->options, true)); } diff --git a/src/Console/Cache.php b/src/Console/Cache.php index 14d322b23a..c019c18429 100644 --- a/src/Console/Cache.php +++ b/src/Console/Cache.php @@ -80,7 +80,7 @@ HELP; { if ($this->getOption('v')) { $this->out('Executable: ' . $this->executable); - $this->out('Class: ' . self::class); + $this->out('Class: ' . __CLASS__); $this->out('Arguments: ' . var_export($this->args, true)); $this->out('Options: ' . var_export($this->options, true)); } diff --git a/src/Console/Config.php b/src/Console/Config.php index 1ff4146bb0..7b222a4481 100644 --- a/src/Console/Config.php +++ b/src/Console/Config.php @@ -85,14 +85,14 @@ HELP; parent::__construct($argv); $this->appMode = $appMode; - $this->config = $config; + $this->config = $config; } protected function doExecute(): int { if ($this->getOption('v')) { $this->out('Executable: ' . $this->executable); - $this->out('Class: ' . self::class); + $this->out('Class: ' . __CLASS__); $this->out('Arguments: ' . var_export($this->args, true)); $this->out('Options: ' . var_export($this->options, true)); } @@ -102,8 +102,8 @@ HELP; } if (count($this->args) == 3) { - $cat = $this->getArgument(0); - $key = $this->getArgument(1); + $cat = $this->getArgument(0); + $key = $this->getArgument(1); $value = $this->getArgument(2); if (is_array($this->config->get($cat, $key))) { @@ -116,16 +116,16 @@ HELP; $result = $this->config->set($cat, $key, $value); if ($result) { - $this->out("{$cat}.{$key} <= " - . $this->config->get($cat, $key)); + $this->out("{$cat}.{$key} <= " . + $this->config->get($cat, $key)); } else { $this->out("Unable to set {$cat}.{$key}"); } } if (count($this->args) == 2) { - $cat = $this->getArgument(0); - $key = $this->getArgument(1); + $cat = $this->getArgument(0); + $key = $this->getArgument(1); $value = $this->config->get($this->getArgument(0), $this->getArgument(1)); if (is_array($value)) { diff --git a/src/Console/Contact.php b/src/Console/Contact.php index e8be074200..d54979c11b 100644 --- a/src/Console/Contact.php +++ b/src/Console/Contact.php @@ -66,7 +66,7 @@ HELP; protected function doExecute(): int { if ($this->getOption('v')) { - $this->out('Class: ' . self::class); + $this->out('Class: ' . __CLASS__); $this->out('Arguments: ' . var_export($this->args, true)); $this->out('Options: ' . var_export($this->options, true)); } diff --git a/src/Console/CreateDoxygen.php b/src/Console/CreateDoxygen.php index f575ca5de0..3dc3c7e3ec 100644 --- a/src/Console/CreateDoxygen.php +++ b/src/Console/CreateDoxygen.php @@ -34,7 +34,7 @@ HELP; protected function doExecute(): int { if ($this->getOption('v')) { - $this->out('Class: ' . self::class); + $this->out('Class: ' . __CLASS__); $this->out('Arguments: ' . var_export($this->args, true)); $this->out('Options: ' . var_export($this->options, true)); } @@ -92,7 +92,7 @@ HELP; $found = false; } - if ($found && (trim($previous) == "*/")) { + if ($found && ( trim($previous) == "*/")) { $found = false; } @@ -117,35 +117,33 @@ HELP; private function addDocumentation($line) { $trimmed = ltrim($line); - $length = strlen($line) - strlen($trimmed); - $space = substr($line, 0, $length); + $length = strlen($line) - strlen($trimmed); + $space = substr($line, 0, $length); - $block = $space . "/**\n" - . $space . " * \n" - . $space . " *\n"; /**/ + $block = $space . "/**\n" . + $space . " * \n" . + $space . " *\n"; /**/ $left = strpos($line, "("); $line = substr($line, $left + 1); $right = strpos($line, ")"); - $line = trim(substr($line, 0, $right)); + $line = trim(substr($line, 0, $right)); if ($line != "") { $parameters = explode(",", $line); foreach ($parameters as $parameter) { $parameter = trim($parameter); - $splitted = explode("=", $parameter); + $splitted = explode("=", $parameter); $block .= $space . " * @param " . trim($splitted[0], "& ") . "\n"; } - if (count($parameters) > 0) { - $block .= $space . " *\n"; - } + if (count($parameters) > 0) $block .= $space . " *\n"; } - $block .= $space . " * @return \n" - . $space . " */\n"; + $block .= $space . " * @return \n" . + $space . " */\n"; return $block; } diff --git a/src/Console/DatabaseStructure.php b/src/Console/DatabaseStructure.php index a9af4416bd..cdc46aa435 100644 --- a/src/Console/DatabaseStructure.php +++ b/src/Console/DatabaseStructure.php @@ -73,17 +73,17 @@ HELP; { parent::__construct($argv); - $this->dba = $dba; - $this->dbaDefinition = $dbaDefinition; + $this->dba = $dba; + $this->dbaDefinition = $dbaDefinition; $this->viewDefinition = $viewDefinition; - $this->config = $config; - $this->basePath = $basePath->getPath(); + $this->config = $config; + $this->basePath = $basePath->getPath(); } protected function doExecute(): int { if ($this->getOption('v')) { - $this->out('Class: ' . self::class); + $this->out('Class: ' . __CLASS__); $this->out('Arguments: ' . var_export($this->args, true)); $this->out('Options: ' . var_export($this->options, true)); } @@ -112,7 +112,7 @@ HELP; case "update": $force = $this->getOption(['f', 'force'], false); $override = $this->getOption(['o', 'override'], false); - $output = Update::run($basePath, $force, $override, true, false); + $output = Update::run($basePath, $force, $override,true, false); break; case "drop": $execute = $this->getOption(['e', 'execute'], false); diff --git a/src/Console/DocBloxErrorChecker.php b/src/Console/DocBloxErrorChecker.php index 63429a9183..694940c95e 100644 --- a/src/Console/DocBloxErrorChecker.php +++ b/src/Console/DocBloxErrorChecker.php @@ -30,6 +30,7 @@ use Friendica\AppHelper; */ class DocBloxErrorChecker extends \Asika\SimpleConsole\Console { + protected $helpOptions = ['h', 'help', '?']; /** @var string */ @@ -59,7 +60,7 @@ HELP; protected function doExecute(): int { if ($this->getOption('v')) { - $this->out('Class: ' . self::class); + $this->out('Class: ' . __CLASS__); $this->out('Arguments: ' . var_export($this->args, true)); $this->out('Options: ' . var_export($this->options, true)); } @@ -107,7 +108,7 @@ HELP; //check half of the set and discard if that half is okay $res = $filelist; - $i = count($res); + $i = count($res); do { $this->out($i . '/' . count($filelist) . ' elements remaining.'); $res = $this->reduce($res, count($res) / 2); @@ -134,7 +135,7 @@ HELP; private function commandExists($command) { - $prefix = strpos(strtolower(PHP_OS), 'win') > -1 ? 'where' : 'which'; + $prefix = strpos(strtolower(PHP_OS),'win') > -1 ? 'where' : 'which'; exec("{$prefix} {$command}", $output, $returnVal); return $returnVal === 0; } diff --git a/src/Console/Extract.php b/src/Console/Extract.php index 9f18fd8eb9..0c05a39705 100644 --- a/src/Console/Extract.php +++ b/src/Console/Extract.php @@ -7,8 +7,8 @@ namespace Friendica\Console; -use RecursiveDirectoryIterator; -use RecursiveIteratorIterator; +use \RecursiveDirectoryIterator; +use \RecursiveIteratorIterator; /** * Extracts translation strings from the Friendica project's files to be exported @@ -41,7 +41,7 @@ HELP; protected function doExecute(): int { if ($this->getOption('v')) { - $this->out('Class: ' . self::class); + $this->out('Class: ' . __CLASS__); $this->out('Arguments: ' . var_export($this->args, true)); $this->out('Options: ' . var_export($this->options, true)); } @@ -64,16 +64,16 @@ HELP; ['index.php'], glob('mod/*'), glob('addon/*/*'), - $this->globRecursive('src'), + $this->globRecursive('src') ); foreach ($files as $file) { $str = file_get_contents($file); - $pat = '|->t\(([^\)]*+)[\)]|'; + $pat = '|->t\(([^\)]*+)[\)]|'; $patt = '|->tt\(([^\)]*+)[\)]|'; - $matches = []; + $matches = []; $matchestt = []; preg_match_all($pat, $str, $matches); @@ -86,7 +86,7 @@ HELP; if (!empty($matches[1])) { foreach ($matches[1] as $long_match) { $match_arr = preg_split('/(?<=[\'"])\s*,/', $long_match); - $match = $match_arr[0]; + $match = $match_arr[0]; if (!in_array($match, $arr)) { if (substr($match, 0, 1) == '$') { continue; @@ -139,7 +139,7 @@ HELP; private function globRecursive(string $path): array { $dir_iterator = new RecursiveDirectoryIterator($path); - $iterator = new RecursiveIteratorIterator($dir_iterator, RecursiveIteratorIterator::SELF_FIRST); + $iterator = new RecursiveIteratorIterator($dir_iterator, RecursiveIteratorIterator::SELF_FIRST); $return = []; foreach ($iterator as $file) { diff --git a/src/Console/FixAPDeliveryWorkerTaskParameters.php b/src/Console/FixAPDeliveryWorkerTaskParameters.php index 1909179e25..9cced29dec 100644 --- a/src/Console/FixAPDeliveryWorkerTaskParameters.php +++ b/src/Console/FixAPDeliveryWorkerTaskParameters.php @@ -70,14 +70,14 @@ HELP; parent::__construct($argv); $this->appMode = $appMode; - $this->dba = $dba; - $this->l10n = $l10n; + $this->dba = $dba; + $this->l10n = $l10n; } protected function doExecute(): int { if ($this->getOption('v')) { - $this->out('Class: ' . self::class); + $this->out('Class: ' . __CLASS__); $this->out('Arguments: ' . var_export($this->args, true)); $this->out('Options: ' . var_export($this->options, true)); } @@ -90,9 +90,9 @@ HELP; throw new RuntimeException('Friendica isn\'t properly installed yet.'); } - $this->examined = 0; + $this->examined = 0; $this->processed = 0; - $this->errored = 0; + $this->errored = 0; do { $result = $this->dba->select('workerqueue', ['id', 'parameter'], ["`command` = ? AND `parameter` LIKE ?", "APDelivery", "[\"%\",\"\",%"], ['limit' => [$this->examined, 100]]); @@ -134,7 +134,7 @@ HELP; if (is_array($parameters[2])) { $parameters[4] = $parameters[2]; - $contact = Contact::getById(current($parameters[2]), ['url']); + $contact = Contact::getById(current($parameters[2]), ['url']); $parameters[2] = $contact['url']; } diff --git a/src/Console/GlobalCommunityBlock.php b/src/Console/GlobalCommunityBlock.php index a9dbdd40fa..2e85491455 100644 --- a/src/Console/GlobalCommunityBlock.php +++ b/src/Console/GlobalCommunityBlock.php @@ -53,13 +53,13 @@ HELP; parent::__construct($argv); $this->appMode = $appMode; - $this->l10n = $l10n; + $this->l10n = $l10n; } protected function doExecute(): int { if ($this->getOption('v')) { - $this->out('Class: ' . self::class); + $this->out('Class: ' . __CLASS__); $this->out('Arguments: ' . var_export($this->args, true)); $this->out('Options: ' . var_export($this->options, true)); } @@ -83,7 +83,7 @@ HELP; } $block_reason = $this->getArgument(1); - if (Contact::block($contact_id, $block_reason)) { + if(Contact::block($contact_id, $block_reason)) { $this->out($this->l10n->t('The contact has been blocked from the node')); } else { throw new \RuntimeException('The contact block failed.'); diff --git a/src/Console/GlobalCommunitySilence.php b/src/Console/GlobalCommunitySilence.php index adf56c10d0..9843af99cb 100644 --- a/src/Console/GlobalCommunitySilence.php +++ b/src/Console/GlobalCommunitySilence.php @@ -58,13 +58,13 @@ HELP; parent::__construct($argv); $this->appMode = $appMode; - $this->dba = $dba; + $this->dba =$dba; } protected function doExecute(): int { if ($this->getOption('v')) { - $this->out('Class: ' . self::class); + $this->out('Class: ' . __CLASS__); $this->out('Arguments: ' . var_export($this->args, true)); $this->out('Options: ' . var_export($this->options, true)); } diff --git a/src/Console/Lock.php b/src/Console/Lock.php index e38f42d10d..f883adfc6e 100644 --- a/src/Console/Lock.php +++ b/src/Console/Lock.php @@ -74,7 +74,7 @@ HELP; { if ($this->getOption('v')) { $this->out('Executable: ' . $this->executable); - $this->out('Class: ' . self::class); + $this->out('Class: ' . __CLASS__); $this->out('Arguments: ' . var_export($this->args, true)); $this->out('Options: ' . var_export($this->options, true)); } diff --git a/src/Console/Maintenance.php b/src/Console/Maintenance.php index 8dad4dd98e..168bccd164 100644 --- a/src/Console/Maintenance.php +++ b/src/Console/Maintenance.php @@ -60,13 +60,13 @@ HELP; parent::__construct($argv); $this->appMode = $appMode; - $this->config = $config; + $this->config = $config; } protected function doExecute(): int { if ($this->getOption('v')) { - $this->out('Class: ' . self::class); + $this->out('Class: ' . __CLASS__); $this->out('Arguments: ' . var_export($this->args, true)); $this->out('Options: ' . var_export($this->options, true)); } diff --git a/src/Console/PhpToPo.php b/src/Console/PhpToPo.php index c1ab617afd..ad377561a2 100644 --- a/src/Console/PhpToPo.php +++ b/src/Console/PhpToPo.php @@ -15,10 +15,11 @@ use stdClass; */ class PhpToPo extends \Asika\SimpleConsole\Console { + protected $helpOptions = ['h', 'help', '?']; - private $normBaseMsgIds = []; - public const NORM_REGEXP = "|[\\\]|"; + private $normBaseMsgIds = []; + const NORM_REGEXP = "|[\\\]|"; /** @var AppHelper */ private $appHelper; @@ -52,7 +53,7 @@ HELP; protected function doExecute(): int { if ($this->getOption('v')) { - $this->out('Class: ' . self::class); + $this->out('Class: ' . __CLASS__); $this->out('Arguments: ' . var_export($this->args, true)); $this->out('Options: ' . var_export($this->options, true)); } @@ -99,9 +100,9 @@ HELP; $out .= '"Content-Transfer-Encoding: 8bit\n"' . "\n"; // search for plural info - $lang = ""; + $lang = ""; $lang_logic = ""; - $lang_pnum = $this->getOption('p', 2); + $lang_pnum = $this->getOption('p', 2); $infile = file($phpfile); foreach ($infile as $l) { @@ -127,27 +128,27 @@ HELP; // load base messages.po and extract msgids $base_msgids = []; - $base_f = file($base_path); + $base_f = file($base_path); if (!$base_f) { throw new \RuntimeException('The base ' . $base_path . ' file is missing or unavailable to read.'); } $this->out('Loading base file ' . $base_path . '...'); - $_f = 0; - $_mid = ""; + $_f = 0; + $_mid = ""; $_mids = []; foreach ($base_f as $l) { $l = trim($l); if ($this->startsWith($l, 'msgstr')) { if ($_mid != '""') { - $base_msgids[$_mid] = $_mids; + $base_msgids[$_mid] = $_mids; $this->normBaseMsgIds[preg_replace(self::NORM_REGEXP, "", $_mid)] = $_mid; } - $_f = 0; - $_mid = ""; + $_f = 0; + $_mid = ""; $_mids = []; } @@ -155,7 +156,7 @@ HELP; $_mids[count($_mids) - 1] .= "\n" . $l; } if ($this->startsWith($l, 'msgid_plural ')) { - $_f = 2; + $_f = 2; $_mids[] = str_replace('msgid_plural ', '', $l); } @@ -164,8 +165,8 @@ HELP; $_mids[count($_mids) - 1] .= "\n" . $l; } if ($this->startsWith($l, 'msgid ')) { - $_f = 1; - $_mid = str_replace('msgid ', '', $l); + $_f = 1; + $_mid = str_replace('msgid ', '', $l); $_mids = [$_mid]; } } @@ -214,7 +215,7 @@ HELP; private function startsWith($haystack, $needle) { // search backwards starting from haystack length characters from the end - return $needle === "" || strrpos($haystack, (string) $needle, -strlen($haystack)) !== false; + return $needle === "" || strrpos($haystack, $needle, -strlen($haystack)) !== FALSE; } /** diff --git a/src/Console/PoToPhp.php b/src/Console/PoToPhp.php index 937d948343..480da23c46 100644 --- a/src/Console/PoToPhp.php +++ b/src/Console/PoToPhp.php @@ -17,7 +17,7 @@ class PoToPhp extends \Asika\SimpleConsole\Console { protected $helpOptions = ['h', 'help', '?']; - public const DQ_ESCAPE = "__DQ__"; + const DQ_ESCAPE = "__DQ__"; protected function getHelp() { @@ -39,7 +39,7 @@ HELP; protected function doExecute(): int { if ($this->getOption('v')) { - $this->out('Class: ' . self::class); + $this->out('Class: ' . __CLASS__); $this->out('Arguments: ' . var_export($this->args, true)); $this->out('Options: ' . var_export($this->options, true)); } @@ -116,7 +116,7 @@ HELP; $out .= self::escapePhpString($entry->getAsString(PoTokens::TRANSLATED)) . ';' . "\n"; } else { $out .= '[' . "\n"; - foreach ($entry->getAsStringArray(PoTokens::TRANSLATED) as $key => $msgstr) { + foreach($entry->getAsStringArray(PoTokens::TRANSLATED) as $key => $msgstr) { $out .= "\t" . $key . ' => ' . self::escapePhpString($msgstr) . ',' . "\n"; }; @@ -130,7 +130,7 @@ HELP; private function createPluralSelectFunctionString(string $pluralForms, string $lang): string { $return = $this->convertCPluralConditionToPhpReturnStatement( - $pluralForms, + $pluralForms ); $fnname = 'string_plural_select_' . $lang; @@ -191,14 +191,14 @@ HELP; } if ($q === false || $s < $q) { - [$then, $else] = explode(':', $string, 2); - $node['then'] = $then; - $parsedElse = []; + list($then, $else) = explode(':', $string, 2); + $node['then'] = $then; + $parsedElse = []; self::parse($else, $parsedElse); $node['else'] = $parsedElse; } else { - [$if, $thenelse] = explode('?', $string, 2); - $node['if'] = $if; + list($if, $thenelse) = explode('?', $string, 2); + $node['if'] = $if; self::parse($thenelse, $node); } } diff --git a/src/Console/Relay.php b/src/Console/Relay.php index 2a803006e0..3aeaad458f 100644 --- a/src/Console/Relay.php +++ b/src/Console/Relay.php @@ -67,7 +67,7 @@ HELP; { if ($this->getOption('v')) { $this->out('Executable: ' . $this->executable); - $this->out('Class: ' . self::class); + $this->out('Class: ' . __CLASS__); $this->out('Arguments: ' . var_export($this->args, true)); $this->out('Options: ' . var_export($this->options, true)); } @@ -87,7 +87,7 @@ HELP; } if (count($this->args) == 2) { - $mode = $this->getArgument(0); + $mode = $this->getArgument(0); $actor = $this->getArgument(1); $apcontact = APContact::getByURL($actor); diff --git a/src/Console/Storage.php b/src/Console/Storage.php index 6b07728dcf..58d20d06b2 100644 --- a/src/Console/Storage.php +++ b/src/Console/Storage.php @@ -65,7 +65,7 @@ HELP; { if ($this->getOption('v')) { $this->out('Executable: ' . $this->executable); - $this->out('Class: ' . self::class); + $this->out('Class: ' . __CLASS__); $this->out('Arguments: ' . var_export($this->args, true)); $this->out('Options: ' . var_export($this->options, true)); } diff --git a/src/Console/Test.php b/src/Console/Test.php index ffd89d3c8d..cd40b74cf8 100644 --- a/src/Console/Test.php +++ b/src/Console/Test.php @@ -31,7 +31,7 @@ HELP; protected function doExecute(): int { if ($this->getOption('v')) { - $this->out('Class: ' . self::class); + $this->out('Class: ' . __CLASS__); $this->out('Arguments: ' . var_export($this->args, true)); $this->out('Options: ' . var_export($this->options, true)); } diff --git a/src/Console/Typo.php b/src/Console/Typo.php index 247f441330..ed314197ea 100644 --- a/src/Console/Typo.php +++ b/src/Console/Typo.php @@ -49,7 +49,7 @@ HELP; protected function doExecute(): int { if ($this->getOption('v')) { - $this->out('Class: ' . self::class); + $this->out('Class: ' . __CLASS__); $this->out('Arguments: ' . var_export($this->args, true)); $this->out('Options: ' . var_export($this->options, true)); } @@ -128,7 +128,7 @@ HELP; } $output = []; - $ret = 0; + $ret = 0; exec("$php_path -l $file", $output, $ret); if ($ret !== 0) { throw new \RuntimeException('Parse error found in ' . $file . ', scan stopped.'); diff --git a/src/Console/User.php b/src/Console/User.php index 91c2f8e14a..5a8f62cc8c 100644 --- a/src/Console/User.php +++ b/src/Console/User.php @@ -86,7 +86,7 @@ HELP; protected function doExecute(): int { if ($this->getOption('v')) { - $this->out('Class: ' . self::class); + $this->out('Class: ' . __CLASS__); $this->out('Arguments: ' . var_export($this->args, true)); $this->out('Options: ' . var_export($this->options, true)); } @@ -490,9 +490,9 @@ HELP; } } - if (array_key_exists($category, $values) - and array_key_exists($key, $values[$category]) - and $values[$category][$key] == $value) { + if (array_key_exists($category, $values) and + array_key_exists($key, $values[$category]) and + $values[$category][$key] == $value) { throw new RuntimeException('Value not changed'); } diff --git a/src/Contact/Avatar.php b/src/Contact/Avatar.php index caba042906..677ab8e048 100644 --- a/src/Contact/Avatar.php +++ b/src/Contact/Avatar.php @@ -20,7 +20,7 @@ use Friendica\Util\Proxy; */ class Avatar { - public const BASE_PATH = '/avatar/'; + const BASE_PATH = '/avatar/'; /** * Returns a field array with locally cached avatar pictures @@ -215,7 +215,7 @@ class Avatar } $avatarpath = parse_url(self::baseUrl(), PHP_URL_PATH); - $pos = strpos($parts['path'], (string) $avatarpath); + $pos = strpos($parts['path'], $avatarpath); if ($pos !== 0) { return ''; } diff --git a/src/Contact/Introduction/Repository/Introduction.php b/src/Contact/Introduction/Repository/Introduction.php index fea17c18bc..a624017a2a 100644 --- a/src/Contact/Introduction/Repository/Introduction.php +++ b/src/Contact/Introduction/Repository/Introduction.php @@ -35,7 +35,7 @@ class Introduction extends BaseRepository */ private function selectOne(array $condition, array $params = []): IntroductionEntity { - $fields = $this->_selectFirstRowAsArray($condition, $params); + $fields = $this->_selectFirstRowAsArray( $condition, $params); return $this->factory->createFromTableRow($fields); } @@ -85,7 +85,7 @@ class Introduction extends BaseRepository ['order' => ['id' => 'DESC']], $min_id, $max_id, - $limit, + $limit ); } catch (\Exception $e) { throw new IntroductionPersistenceException(sprintf('Cannot select Introductions for used %d', $uid), $e); diff --git a/src/Content/Conversation.php b/src/Content/Conversation.php index 55bed4b75c..bd68272381 100644 --- a/src/Content/Conversation.php +++ b/src/Content/Conversation.php @@ -12,9 +12,6 @@ use Friendica\App\BaseURL; use Friendica\App\Mode; use Friendica\App\Page; use Friendica\BaseModule; -use Friendica\Content\Conversation\Factory\Channel; -use Friendica\Content\Conversation\Repository\UserDefinedChannel; -use Friendica\Content\Conversation\Entity\Channel as ChannelEntity; use Friendica\Core\ACL; use Friendica\Core\Config\Capability\IManageConfigValues; use Friendica\Core\L10n; @@ -50,16 +47,16 @@ use Psr\Log\LoggerInterface; class Conversation { - public const MODE_CHANNEL = 'channel'; - public const MODE_COMMUNITY = 'community'; - public const MODE_CONTACTS = 'contacts'; - public const MODE_CONTACT_POSTS = 'contact-posts'; - public const MODE_DISPLAY = 'display'; - public const MODE_FILED = 'filed'; - public const MODE_NETWORK = 'network'; - public const MODE_NOTES = 'notes'; - public const MODE_SEARCH = 'search'; - public const MODE_PROFILE = 'profile'; + const MODE_CHANNEL = 'channel'; + const MODE_COMMUNITY = 'community'; + const MODE_CONTACTS = 'contacts'; + const MODE_CONTACT_POSTS = 'contact-posts'; + const MODE_DISPLAY = 'display'; + const MODE_FILED = 'filed'; + const MODE_NETWORK = 'network'; + const MODE_NOTES = 'notes'; + const MODE_SEARCH = 'search'; + const MODE_PROFILE = 'profile'; /** @var Activity */ private $activity; @@ -88,27 +85,23 @@ class Conversation /** @var UserGServerRepository */ private $userGServer; private EventDispatcherInterface $eventDispatcher; - private Channel $channel; - private UserDefinedChannel $userDefinedChannel; - public function __construct(UserGServerRepository $userGServer, Channel $channel, UserDefinedChannel $userDefinedChannel, LoggerInterface $logger, Profiler $profiler, Activity $activity, L10n $l10n, Item $item, Arguments $args, BaseURL $baseURL, IManageConfigValues $config, IManagePersonalConfigValues $pConfig, Page $page, Mode $mode, EventDispatcherInterface $eventDispatcher, IHandleUserSessions $session) + public function __construct(UserGServerRepository $userGServer, LoggerInterface $logger, Profiler $profiler, Activity $activity, L10n $l10n, Item $item, Arguments $args, BaseURL $baseURL, IManageConfigValues $config, IManagePersonalConfigValues $pConfig, Page $page, Mode $mode, EventDispatcherInterface $eventDispatcher, IHandleUserSessions $session) { - $this->activity = $activity; - $this->item = $item; - $this->config = $config; - $this->mode = $mode; - $this->baseURL = $baseURL; - $this->profiler = $profiler; - $this->logger = $logger; - $this->l10n = $l10n; - $this->args = $args; - $this->pConfig = $pConfig; - $this->page = $page; - $this->eventDispatcher = $eventDispatcher; - $this->session = $session; - $this->userGServer = $userGServer; - $this->channel = $channel; - $this->userDefinedChannel = $userDefinedChannel; + $this->activity = $activity; + $this->item = $item; + $this->config = $config; + $this->mode = $mode; + $this->baseURL = $baseURL; + $this->profiler = $profiler; + $this->logger = $logger; + $this->l10n = $l10n; + $this->args = $args; + $this->pConfig = $pConfig; + $this->page = $page; + $this->eventDispatcher = $eventDispatcher; + $this->session = $session; + $this->userGServer = $userGServer; } /** @@ -273,7 +266,7 @@ class Conversation break; case 'dislike': $dislike_translation_plural = ' don\'t like this'; - // @deprecated 2026.01 this translation is scheduled for removal as a new translation has been added without the typo + // @deprecated 2025.07 this translation is scheduled for removal as a new translation has been added without the typo $dislike_translation_plural = ' don\'t like this'; $phrase = $this->l10n->tt(' doesn\'t like this', $dislike_translation_plural, $total, $spanatts); break; @@ -297,7 +290,7 @@ class Conversation $output = Renderer::replaceMacros(Renderer::getMarkupTemplate('voting_fakelink.tpl'), [ '$phrase' => $phrase, '$type' => $verb, - '$id' => $id, + '$id' => $id ]); $output .= $expanded; @@ -315,15 +308,15 @@ class Conversation $this->profiler->startRecording('rendering'); $o = ''; - $x['allow_location'] ??= $user['allow_location']; - $x['default_location'] ??= $user['default-location']; - $x['nickname'] ??= $user['nickname']; - $x['lockstate'] = $x['lockstate'] ?? ACL::getLockstateForUserId($user['uid']) ? 'lock' : 'unlock'; - $x['acl'] ??= ACL::getFullSelectorHTML($this->page, $user['uid'], true); - $x['bang'] ??= ''; - $x['visitor'] ??= 'block'; - $x['is_owner'] ??= true; - $x['profile_uid'] ??= $this->session->getLocalUserId(); + $x['allow_location'] = $x['allow_location'] ?? $user['allow_location']; + $x['default_location'] = $x['default_location'] ?? $user['default-location']; + $x['nickname'] = $x['nickname'] ?? $user['nickname']; + $x['lockstate'] = $x['lockstate'] ?? ACL::getLockstateForUserId($user['uid']) ? 'lock' : 'unlock'; + $x['acl'] = $x['acl'] ?? ACL::getFullSelectorHTML($this->page, $user['uid'], true); + $x['bang'] = $x['bang'] ?? ''; + $x['visitor'] = $x['visitor'] ?? 'block'; + $x['is_owner'] = $x['is_owner'] ?? true; + $x['profile_uid'] = $x['profile_uid'] ?? $this->session->getLocalUserId(); $geotag = !empty($x['allow_location']) ? Renderer::replaceMacros(Renderer::getMarkupTemplate('jot_geotag.tpl'), []) : ''; @@ -354,7 +347,7 @@ class Conversation new \DateTime('now'), null, $this->l10n->t('Created at'), - 'created_at', + 'created_at' ); } else { $created_at = ''; @@ -389,17 +382,14 @@ class Conversation '$shortnoloc' => $this->l10n->t('clear location'), '$title' => $x['title'] ?? '', '$placeholdertitle' => $this->l10n->t('Set title'), - '$summary' => $x['summary'] ?? '', - '$placeholdersummary' => Feature::isEnabled($this->session->getLocalUserId(), Feature::SUMMARY) ? $this->l10n->t('Set summary, abstract or spoiler text') : '', '$category' => $x['category'] ?? '', '$placeholdercategory' => Feature::isEnabled($this->session->getLocalUserId(), Feature::CATEGORIES) ? $this->l10n->t("Categories \x28comma-separated list\x29") : '', - '$sensitive' => ['sensitive', $this->l10n->t('Sensitive post'), $x['sensitive'] ?? false], '$scheduled_at' => Temporal::getDateTimeField( new \DateTime(), new \DateTime('now + 6 months'), null, $this->l10n->t('Scheduled at'), - 'scheduled_at', + 'scheduled_at' ), '$created_at' => $created_at, '$wait' => $this->l10n->t('Please wait'), @@ -490,17 +480,17 @@ class Conversation . "; var netargs = '" . substr($this->args->getCommand(), 8) . '?f=' . (!empty($_GET['contactid']) ? '&contactid=' . rawurlencode($_GET['contactid']) : '') - . (!empty($_GET['search']) ? '&search=' . rawurlencode($_GET['search']) : '') - . (!empty($_GET['star']) ? '&star=' . rawurlencode($_GET['star']) : '') - . (!empty($_GET['order']) ? '&order=' . rawurlencode($_GET['order']) : '') - . (!empty($_GET['bmark']) ? '&bmark=' . rawurlencode($_GET['bmark']) : '') - . (!empty($_GET['liked']) ? '&liked=' . rawurlencode($_GET['liked']) : '') - . (!empty($_GET['conv']) ? '&conv=' . rawurlencode($_GET['conv']) : '') - . (!empty($_GET['nets']) ? '&nets=' . rawurlencode($_GET['nets']) : '') - . (!empty($_GET['cmin']) ? '&cmin=' . rawurlencode($_GET['cmin']) : '') - . (!empty($_GET['cmax']) ? '&cmax=' . rawurlencode($_GET['cmax']) : '') - . (!empty($_GET['file']) ? '&file=' . rawurlencode($_GET['file']) : '') - . (!empty($_GET['channel']) ? '&channel=' . rawurlencode($_GET['channel']) : '') + . (!empty($_GET['search']) ? '&search=' . rawurlencode($_GET['search']) : '') + . (!empty($_GET['star']) ? '&star=' . rawurlencode($_GET['star']) : '') + . (!empty($_GET['order']) ? '&order=' . rawurlencode($_GET['order']) : '') + . (!empty($_GET['bmark']) ? '&bmark=' . rawurlencode($_GET['bmark']) : '') + . (!empty($_GET['liked']) ? '&liked=' . rawurlencode($_GET['liked']) : '') + . (!empty($_GET['conv']) ? '&conv=' . rawurlencode($_GET['conv']) : '') + . (!empty($_GET['nets']) ? '&nets=' . rawurlencode($_GET['nets']) : '') + . (!empty($_GET['cmin']) ? '&cmin=' . rawurlencode($_GET['cmin']) : '') + . (!empty($_GET['cmax']) ? '&cmax=' . rawurlencode($_GET['cmax']) : '') + . (!empty($_GET['file']) ? '&file=' . rawurlencode($_GET['file']) : '') + . (!empty($_GET['channel']) ? '&channel=' . rawurlencode($_GET['channel']) : '') . (!empty($_GET['no_sharer']) ? '&no_sharer=' . rawurlencode($_GET['no_sharer']) : '') . (!empty($_GET['accounttype']) ? '&accounttype=' . rawurlencode($_GET['accounttype']) : '') . "'; \r\n"; @@ -697,13 +687,10 @@ class Conversation * @param array $row Post row * @param array $activity Contact data of the resharer * @param array $thr_parent Thread parent row - * @param string $channel Channel information - * @param int $uid User ID - * @param array $channels Available channels for the user * * @return array items with parents and comments */ - private function addRowInformation(array $row, array $activity, array $thr_parent, string $channel, int $uid, array $channels): array + private function addRowInformation(array $row, array $activity, array $thr_parent): array { $this->profiler->startRecording('rendering'); @@ -721,18 +708,13 @@ class Conversation $row['causer-link'] = $contact['url']; $row['causer-avatar'] = $contact['thumb']; $row['causer-name'] = $contact['name']; - } elseif (($row['gravity'] == ItemModel::GRAVITY_ACTIVITY) && ($row['verb'] == Activity::ANNOUNCE) - && ($row['author-id'] == $activity['causer-id']) + } elseif (($row['gravity'] == ItemModel::GRAVITY_ACTIVITY) && ($row['verb'] == Activity::ANNOUNCE) && + ($row['author-id'] == $activity['causer-id']) ) { return $row; } } - if ($channel) { - $row['channel'] = $channel; - $row['post-reason'] = ItemModel::PR_CHANNEL; - } - switch ($row['post-reason']) { case ItemModel::PR_TO: $row['direction'] = ['direction' => 7, 'title' => $this->l10n->t('You had been addressed (%s).', 'to')]; @@ -811,16 +793,6 @@ class Conversation case ItemModel::PR_PUSHED: $row['direction'] = ['direction' => 1, 'title' => $this->l10n->t('Pushed to us')]; break; - case ItemModel::PR_CHANNEL: - $title = $channels[$channel]->label ?? $channel; - $description = $channels[$channel]->description ?? ''; - - if ($description) { - $row['direction'] = ['direction' => 11, 'title' => $this->l10n->t('Channel "%s": %s', $title, $description)]; - } else { - $row['direction'] = ['direction' => 11, 'title' => $this->l10n->t('Channel "%s"', $title)]; - } - break; } $row['thr-parent-row'] = $thr_parent; @@ -857,7 +829,6 @@ class Conversation $uriids = []; $commentcounter = []; $activitycounter = []; - $postchannels = []; foreach ($parents as $parent) { if (!empty($parent['thr-parent-id']) && !empty($parent['gravity']) && ($parent['gravity'] == ItemModel::GRAVITY_ACTIVITY)) { @@ -877,7 +848,6 @@ class Conversation $commentcounter[$uriid] = 0; $activitycounter[$uriid] = 0; - $postchannels[$uriid] = $parent['channel'] ?? ''; } $condition = ['parent-uri-id' => $uriids]; @@ -895,7 +865,7 @@ class Conversation $condition = DBA::mergeConditions( $condition, - ["`uid` IN (0, ?) AND (NOT `verb` IN (?, ?, ?) OR `verb` IS NULL)", $uid, Activity::FOLLOW, Activity::VIEW, Activity::READ], + ["`uid` IN (0, ?) AND (NOT `verb` IN (?, ?, ?) OR `verb` IS NULL)", $uid, Activity::FOLLOW, Activity::VIEW, Activity::READ] ); $condition = DBA::mergeConditions($condition, ["(`uid` != ? OR `private` != ?)", 0, ItemModel::PRIVATE]); @@ -905,8 +875,8 @@ class Conversation [ "`visible` AND NOT `deleted` AND NOT `author-blocked` AND NOT `owner-blocked` AND ((NOT `contact-pending` AND (`contact-rel` IN (?, ?))) OR `self` OR `contact-uid` = ?)", - Contact::SHARING, Contact::FRIEND, 0, - ], + Contact::SHARING, Contact::FRIEND, 0 + ] ); $thread_parents = Post::select(['uri-id', 'causer-id'], $condition, ['order' => ['uri-id' => false, 'uid']]); @@ -922,16 +892,6 @@ class Conversation $thread_items = Post::select(array_merge(ItemModel::DISPLAY_FIELDLIST, ['featured', 'contact-uid', 'gravity', 'post-type', 'post-reason']), $condition, $params); - $channels = []; - foreach ($this->userDefinedChannel->selectByUid($uid) as $userchannel) { - $channels[$userchannel->code] = $userchannel; - } - - /** @var ChannelEntity $systemchannel */ - foreach ($this->channel->getTimelines($uid) as $systemchannel) { - $channels[$systemchannel->code] = $systemchannel; - } - $items = []; $quote_uri_ids = []; $authors = []; @@ -974,7 +934,7 @@ class Conversation ]; } - $items[$row['uri-id']] = $this->addRowInformation($row, $activities[$row['uri-id']] ?? [], $thr_parent[$row['thr-parent-id']] ?? [], $postchannels[$row['thr-parent-id']] ?? '', $uid, $channels); + $items[$row['uri-id']] = $this->addRowInformation($row, $activities[$row['uri-id']] ?? [], $thr_parent[$row['thr-parent-id']] ?? []); } DBA::close($thread_items); @@ -995,7 +955,7 @@ class Conversation $authors[] = $row['author-id']; $authors[] = $row['owner-id']; - $items[$row['uri-id']] = $this->addRowInformation($row, [], [], $postchannels[$row['thr-parent-id']] ?? '', $uid, $channels); + $items[$row['uri-id']] = $this->addRowInformation($row, [], []); } DBA::close($quotes); @@ -1035,8 +995,8 @@ class Conversation $items[$key]['user-collapsed-owner'] = !$always_display && in_array($row['owner-id'], $collapses); if ( - in_array($mode, [self::MODE_CHANNEL, self::MODE_COMMUNITY, self::MODE_NETWORK]) - && (in_array($row['author-id'], $blocks) || in_array($row['owner-id'], $blocks) || in_array($row['author-id'], $ignores) || in_array($row['owner-id'], $ignores)) + in_array($mode, [self::MODE_CHANNEL, self::MODE_COMMUNITY, self::MODE_NETWORK]) && + (in_array($row['author-id'], $blocks) || in_array($row['owner-id'], $blocks) || in_array($row['author-id'], $ignores) || in_array($row['owner-id'], $ignores)) ) { unset($items[$key]); } @@ -1110,7 +1070,7 @@ class Conversation { $counts = []; - foreach (Post\Counts::get(['parent-uri-id' => $uriids, 'vid' => Verb::getID(Activity::POST)]) as $count) { + foreach (Post\Counts::get(['parent-uri-id' => $uriids, 'verb' => Activity::POST]) as $count) { $counts[$count['parent-uri-id']] = ($counts[$count['parent-uri-id']] ?? 0) + $count['count']; } @@ -1332,7 +1292,7 @@ class Conversation foreach ($parents as $i => $parent) { $parents[$i]['children'] = array_merge( $this->getItemChildren($item_array, $parent, true), - $this->getItemChildren($item_array, $parent, false), + $this->getItemChildren($item_array, $parent, false) ); } @@ -1550,7 +1510,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'); @@ -1591,9 +1551,9 @@ class Conversation 'categories' => $categories, 'folders' => $folders, 'text' => strip_tags($body_html), - 'localtime' => $this->l10n->fullDateTime($item['created']), + 'localtime' => DateTimeFormat::local($item['created'], 'r'), 'utc' => DateTimeFormat::utc($item['created'], 'c'), - 'ago' => (($item['app']) ? $this->l10n->t('%s from %s', $this->l10n->relativeDateTime($item['created']), $item['app']) : $this->l10n->relativeDateTime($item['created'])), + 'ago' => (($item['app']) ? $this->l10n->t('%s from %s', Temporal::getRelativeDate($item['created']), $item['app']) : Temporal::getRelativeDate($item['created'])), 'location_html' => $location_html, 'indent' => '', 'owner_name' => '', diff --git a/src/Content/Conversation/Entity/UserDefinedChannel.php b/src/Content/Conversation/Entity/UserDefinedChannel.php index 6cba387fb0..6ef6d67dac 100644 --- a/src/Content/Conversation/Entity/UserDefinedChannel.php +++ b/src/Content/Conversation/Entity/UserDefinedChannel.php @@ -9,10 +9,4 @@ namespace Friendica\Content\Conversation\Entity; class UserDefinedChannel extends Channel { - const CIRCLE_GLOBAL = 0; - const CIRCLE_ACTIVITY = -5; - const CIRCLE_POSTS = -4; - const CIRCLE_CREATION = -3; - const CIRCLE_FOLLOWING = -1; - const CIRCLE_FOLLOWERS = -2; } diff --git a/src/Content/Conversation/Factory/ChannelPost.php b/src/Content/Conversation/Factory/ChannelPost.php index e52a474ac5..c4631fefa7 100644 --- a/src/Content/Conversation/Factory/ChannelPost.php +++ b/src/Content/Conversation/Factory/ChannelPost.php @@ -9,16 +9,13 @@ declare(strict_types=1); namespace Friendica\Content\Conversation\Factory; -use Friendica\Content\Conversation\Entity\UserDefinedChannel as UserDefinedChannelEntity; use Friendica\Content\Conversation\Repository\UserDefinedChannel; use Friendica\Core\Config\Capability\IManageConfigValues; use Friendica\Core\L10n; use Friendica\Database\Database; use Friendica\Model\Contact; -use Friendica\Model\Item; use Friendica\Model\Post; use Friendica\Model\Tag; -use Friendica\Protocol\Activity; use Psr\Log\LoggerInterface; /** @@ -59,12 +56,11 @@ final class ChannelPost * system's channel caching is enabled and matching channels are found. * * @param array $engagement post-engagement record - * @param int $gravity Gravity of the post * @param int $uid User id context. * @param int $reshare_id Optional reshare id. * @return void */ - public function add(array $engagement, int $gravity, int $uid, int $reshare_id = 0): void + public function add(array $engagement, int $uid, int $reshare_id = 0): void { if (!$this->config->get('system', 'channel_cache')) { return; @@ -84,43 +80,35 @@ final class ChannelPost $language = $engagement['language'] !== L10n::UNDETERMINED_LANGUAGE ? $engagement['language'] : ''; $tags = array_column(Tag::getByURIId($engagement['uri-id'], [Tag::HASHTAG]), 'name'); - $circles = ($gravity != Item::GRAVITY_PARENT) ? [UserDefinedChannelEntity::CIRCLE_CREATION, UserDefinedChannelEntity::CIRCLE_POSTS, UserDefinedChannelEntity::CIRCLE_ACTIVITY] : []; - $channels = $this->channelRepository->getMatchingChannels($engagement['searchtext'], $language, $tags, $engagement['media-type'], $engagement['owner-id'], $reshare_id, $uids, $circles); + $channels = $this->channelRepository->getMatchingChannels($engagement['searchtext'], $language, $tags, $engagement['media-type'], $engagement['owner-id'], $reshare_id, $uids); if (!($channels instanceof \Friendica\Content\Conversation\Collection\UserDefinedChannels) || $channels->count() === 0) { $this->logger->debug('No channels found', ['uri-id' => $engagement['uri-id'], 'uids' => $uids, 'reshare_id' => $reshare_id]); return; } foreach ($channels as $channel) { - $in_timeline = Post::exists(["`parent-uri-id` = ? AND `uid` = ? AND NOT `verb` IN (?, ?, ?)", $engagement['uri-id'], $channel->uid, Activity::FOLLOW, Activity::VIEW, Activity::READ]); - - if ($engagement['restricted'] && !$in_timeline) { + if (in_array($channel->circle, [-3, -4, -5]) && !Post::exists(['parent-uri-id' => $engagement['uri-id'], 'uid' => $channel->uid])) { continue; } - if (in_array($channel->circle, [UserDefinedChannelEntity::CIRCLE_CREATION, UserDefinedChannelEntity::CIRCLE_POSTS, UserDefinedChannelEntity::CIRCLE_ACTIVITY]) && !$in_timeline) { + if ($channel->circle === -1 && !Contact::isSharing($engagement['owner-id'], $channel->uid)) { continue; } - if ($channel->circle === UserDefinedChannelEntity::CIRCLE_FOLLOWING && !Contact::isSharing($engagement['owner-id'], $channel->uid)) { - continue; - } - - if ($channel->circle === UserDefinedChannelEntity::CIRCLE_FOLLOWERS && (!Contact::isFollower($engagement['owner-id'], $channel->uid) || Contact::isSharing($engagement['owner-id'], $channel->uid))) { + if ($channel->circle === -2 && (!Contact::isFollower($engagement['owner-id'], $channel->uid) || Contact::isSharing($engagement['owner-id'], $channel->uid))) { continue; } $cache = [ - 'channel' => (int)$channel->code, - 'uid' => $channel->uid, - 'uri-id' => $engagement['uri-id'], - 'in-timeline' => $in_timeline, - 'created' => $post['created'], - 'received' => $post['received'], - 'commented' => $post['commented'], + 'channel' => (int)$channel->code, + 'uid' => $channel->uid, + 'uri-id' => $engagement['uri-id'], + 'created' => $post['created'], + 'received' => $post['received'], + 'commented' => $post['commented'], ]; - $ret = $this->dba->insert('channel-post', $cache, Database::INSERT_UPDATE); + $ret = $this->dba->insert('channel-post', $cache, Database::INSERT_IGNORE); $this->logger->debug('Added channel post', ['uri-id' => $engagement['uri-id'], 'cache' => $cache, 'ret' => $ret]); } } diff --git a/src/Content/Conversation/Factory/SystemChannelPost.php b/src/Content/Conversation/Factory/SystemChannelPost.php index a9e7d98f65..48689ec837 100644 --- a/src/Content/Conversation/Factory/SystemChannelPost.php +++ b/src/Content/Conversation/Factory/SystemChannelPost.php @@ -17,7 +17,6 @@ use Friendica\Model\Contact; use Friendica\Model\Item; use Friendica\Model\Post; use Friendica\Model\User; -use Friendica\Protocol\Activity; use Friendica\Util\DateTimeFormat; use Psr\Log\LoggerInterface; @@ -92,24 +91,14 @@ final class SystemChannelPost $uids = $this->channelRepository->getUsersForPost($engagement['uri-id'], $post_uid, $post['network'], $post['private']); foreach ($uids as $uid) { - if ($engagement['restricted'] && !Post::exists(['parent-uri-id' => $engagement['uri-id'], 'uid' => $uid])) { - continue; - } - foreach ($channels as $channel) { if ($this->dba->exists('system-channel-post', ['channel' => $channel, 'uid' => $uid, 'uri-id' => $engagement['uri-id']])) { continue; } - - $wanted = User::getWantedLanguages($uid); - if ($channel !== Channel::LANGUAGE && $wanted && !in_array($engagement['language'], $wanted)) { - continue; - } - $store = false; switch ($channel) { case Channel::WHATSHOT: - $store = ($engagement['comments'] > $this->channelRepository->getMedianComments($uid, 4, $network) || $engagement['activities'] > $this->channelRepository->getMedianActivities($uid, 4, $network) || $engagement['views'] > $this->channelRepository->getMedianViews($uid, 4, $network)) && $engagement['contact-type'] != Contact::TYPE_COMMUNITY; + $store = ($engagement['comments'] > $this->channelRepository->getMedianComments($uid, 4, $network) || $engagement['activities'] > $this->channelRepository->getMedianActivities($uid, 4, $network)) && $engagement['contact-type'] != Contact::TYPE_COMMUNITY; break; case Channel::FORYOU: @@ -118,7 +107,7 @@ final class SystemChannelPost if ($relation = $this->dba->selectFirst('contact-relation', ['relation-thread-score', 'follows'], ['relation-cid' => $cid, 'cid' => $owner])) { $store = $relation['relation-thread-score'] > $this->channelRepository->getMedianRelationThreadScore($cid, 4); if (!$store && $relation['follows']) { - $store = ($engagement['comments'] >= $this->channelRepository->getMedianComments($uid, 4, $network) || $engagement['activities'] >= $this->channelRepository->getMedianActivities($uid, 4, $network) || $engagement['views'] >= $this->channelRepository->getMedianViews($uid, 4, $network)); + $store = ($engagement['comments'] >= $this->channelRepository->getMedianComments($uid, 4, $network) || $engagement['activities'] >= $this->channelRepository->getMedianActivities($uid, 4, $network)); } } @@ -140,7 +129,7 @@ final class SystemChannelPost $store = $this->dba->exists('contact-relation', ["`relation-cid` = ? AND `cid` = ? AND `follows` AND `relation-thread-score` > ?", $owner, $cid, 0]); } - if (!$store && ($engagement['comments'] >= $this->channelRepository->getMedianComments($uid, 4, $network) || $engagement['activities'] >= $this->channelRepository->getMedianActivities($uid, 4, $network)) || $engagement['views'] >= $this->channelRepository->getMedianViews($uid, 4, $network)) { + if (!$store && ($engagement['comments'] >= $this->channelRepository->getMedianComments($uid, 4, $network) || $engagement['activities'] >= $this->channelRepository->getMedianActivities($uid, 4, $network))) { $store = $this->dba->exists('contact-relation', ["`relation-cid` = ? AND `cid` = ? AND `relation-thread-score` > ?", $owner, $cid, 0]); if (!$store) { $store = $this->dba->exists('contact-relation', ["`cid` = ? AND `relation-cid` = ? AND `relation-thread-score` > ?", $owner, $cid, 0]); @@ -206,15 +195,14 @@ final class SystemChannelPost } $cache = [ - 'channel' => $channel, - 'uid' => $uid, - 'uri-id' => $engagement['uri-id'], - 'in-timeline' => Post::exists(["`parent-uri-id` = ? AND `uid` = ? AND NOT `verb` IN (?, ?, ?)", $engagement['uri-id'], $uid, Activity::FOLLOW, Activity::VIEW, Activity::READ]), - 'created' => $post['created'], - 'received' => $post['received'], - 'commented' => $post['commented'], + 'channel' => $channel, + 'uid' => $uid, + 'uri-id' => $engagement['uri-id'], + 'created' => $post['created'], + 'received' => $post['received'], + 'commented' => $post['commented'], ]; - $ret = $this->dba->insert('system-channel-post', $cache, Database::INSERT_UPDATE); + $ret = $this->dba->insert('system-channel-post', $cache, Database::INSERT_IGNORE); $this->logger->debug('Added system channel post', ['uri-id' => $engagement['uri-id'], 'cache' => $cache, 'ret' => $ret]); } } diff --git a/src/Content/Conversation/Repository/UserDefinedChannel.php b/src/Content/Conversation/Repository/UserDefinedChannel.php index 8562904775..e1da39a2f6 100644 --- a/src/Content/Conversation/Repository/UserDefinedChannel.php +++ b/src/Content/Conversation/Repository/UserDefinedChannel.php @@ -316,10 +316,9 @@ class UserDefinedChannel extends BaseRepository * @param int $owner_id Owner contact id. * @param int $reshare_id Reshare contact id. * @param array $uids User IDs to filter channels. - * @param array $circles circle IDs to filter channels. * @return UserDefinedChannels|null Collection of matching channels or null. */ - public function getMatchingChannels(string $searchtext, string $language, array $tags, int $media_type, int $owner_id, int $reshare_id, array $uids, array $circles): ?UserDefinedChannels + public function getMatchingChannels(string $searchtext, string $language, array $tags, int $media_type, int $owner_id, int $reshare_id, array $uids): ?UserDefinedChannels { if (!in_array($language, User::getLanguages())) { $this->logger->debug('Unwanted language found. No matched channel found.', ['language' => $language, 'searchtext' => $searchtext]); @@ -328,12 +327,7 @@ class UserDefinedChannel extends BaseRepository $disposableFullTextSearch = new DisposableFullTextSearch($this->db, $searchtext); - $condition = ['uid' => $uids, 'valid' => true]; - if ($circles) { - $condition = DBA::mergeConditions($condition, ['circle' => $circles]); - } - - $filteredChannels = $this->select($condition)->filter( + $filteredChannels = $this->select(['uid' => $uids, 'valid' => true])->filter( function (UserDefinedChannelEntity $channel) use ($owner_id, $reshare_id, $language, $tags, $media_type, $disposableFullTextSearch, $searchtext) { if ( ($channel->circle > 0) @@ -484,9 +478,9 @@ class UserDefinedChannel extends BaseRepository $condition = []; if (!empty($channel->circle)) { - if ($channel->circle == UserDefinedChannelEntity::CIRCLE_FOLLOWING) { + if ($channel->circle == -1) { $condition = ["`owner-id` IN (SELECT `pid` FROM `account-user-view` WHERE `uid` = ? AND `rel` IN (?, ?))", $uid, Contact::SHARING, Contact::FRIEND]; - } elseif ($channel->circle == UserDefinedChannelEntity::CIRCLE_FOLLOWERS) { + } elseif ($channel->circle == -2) { $condition = ["`owner-id` IN (SELECT `pid` FROM `account-user-view` WHERE `uid` = ? AND `rel` = ?)", $uid, Contact::FOLLOWER]; } elseif ($channel->circle > 0) { $condition = DBA::mergeConditions($condition, ["`owner-id` IN (SELECT `pid` FROM `group_member` INNER JOIN `account-user-view` ON `group_member`.`contact-id` = `account-user-view`.`id` WHERE `gid` = ? AND `account-user-view`.`uid` = ?)", $channel->circle, $uid]); @@ -548,7 +542,7 @@ class UserDefinedChannel extends BaseRepository $condition = DBA::mergeConditions($condition, ["`size` <= ?", $channel->maxSize]); } - if (in_array($channel->circle, [UserDefinedChannelEntity::CIRCLE_CREATION, UserDefinedChannelEntity::CIRCLE_POSTS, UserDefinedChannelEntity::CIRCLE_ACTIVITY])) { + if (in_array($channel->circle, [-3, -4, -5])) { $condition = DBA::mergeConditions($condition, ['uid' => $uid]); } @@ -749,42 +743,6 @@ class UserDefinedChannel extends BaseRepository return $activities; } - /** - * Compute median views for a user's wanted languages. - * - * @param int $uid User id. - * @param int $divider Divider used to determine median index. - * @param string $network Optional network filter. - * @return int Median views value. - */ - public function getMedianViews(int $uid, int $divider, string $network = ''): int - { - $languages = User::getWantedLanguages($uid); - $cache_key = 'Channel:getMedianViews:' . $divider . ':' . implode(':', $languages) . $network; - $views = $this->cache->get($cache_key); - if (!empty($views)) { - return $views; - } - - $condition = ["`contact-type` != ? AND `views` > ? AND NOT `restricted`", Contact::TYPE_COMMUNITY, 0]; - $condition = $this->addLanguageCondition($uid, $condition); - - if ($network) { - $condition = DBA::mergeConditions($condition, ["`network` = ?", $network]); - } - - $limit = $this->db->count('post-engagement', $condition) / $divider; - $post = $this->db->selectToArray('post-engagement', ['views'], $condition, ['order' => ['views' => true], 'limit' => [$limit, 1]]); - $views = $post[0]['views'] ?? 0; - if (empty($views)) { - return 0; - } - - $this->cache->set($cache_key, $views, Duration::HALF_HOUR); - $this->logger->debug('Calculated median views', ['divider' => $divider, 'languages' => $languages, 'network' => $network, 'median' => $views]); - return $views; - } - /** * Compute median relation thread score for a contact id. * diff --git a/src/Content/Feature.php b/src/Content/Feature.php index 1f3e1e79f2..27a39978a7 100644 --- a/src/Content/Feature.php +++ b/src/Content/Feature.php @@ -12,25 +12,25 @@ use Friendica\Event\ArrayFilterEvent; class Feature { - public const ADD_ABSTRACT = 'add_abstract'; - public const CATEGORIES = 'categories'; - public const COMMUNITY = 'community'; - public const EXPLICIT_MENTIONS = 'explicit_mentions'; - public const MEMBER_SINCE = 'profile_membersince'; - public const PUBLIC_CALENDAR = 'public_calendar'; - public const SUMMARY = 'summary'; - public const TAGCLOUD = 'tagadelic'; + const ADD_ABSTRACT = 'add_abstract'; + const CATEGORIES = 'categories'; + const COMMUNITY = 'community'; + const EXPLICIT_MENTIONS = 'explicit_mentions'; + const MEMBER_SINCE = 'profile_membersince'; + const PHOTO_LOCATION = 'photo_location'; + const PUBLIC_CALENDAR = 'public_calendar'; + const TAGCLOUD = 'tagadelic'; // The different widgets: - public const ACCOUNTS = 'accounts'; - public const ARCHIVE = 'archive'; - public const CIRCLES = 'circles'; - public const CHANNELS = 'channels'; - public const FOLDERS = 'folders'; - public const GROUPS = 'forumlist_profile'; - public const NETWORKS = 'networks'; - public const NOSHARER = 'nosharer'; - public const SEARCHES = 'searches'; - public const TRENDING_TAGS = 'trending_tags'; + const ACCOUNTS = 'accounts'; + const ARCHIVE = 'archive'; + const CIRCLES = 'circles'; + const CHANNELS = 'channels'; + const FOLDERS = 'folders'; + const GROUPS = 'forumlist_profile'; + const NETWORKS = 'networks'; + const NOSHARER = 'nosharer'; + const SEARCHES = 'searches'; + const TRENDING_TAGS = 'trending_tags'; /** * check if feature is enabled @@ -56,10 +56,10 @@ class Feature $arr = ['uid' => $uid, 'feature' => $feature, 'enabled' => $enabled]; $arr = $eventDispatcher->dispatch( - new ArrayFilterEvent(ArrayFilterEvent::FEATURE_ENABLED, $arr), + new ArrayFilterEvent(ArrayFilterEvent::FEATURE_ENABLED, $arr) )->getArray(); - return (bool) $arr['enabled']; + return (bool)$arr['enabled']; } /** @@ -104,6 +104,7 @@ class Feature 'general' => [ $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)], ], @@ -118,7 +119,6 @@ class Feature 'tools' => [ $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)], - [self::SUMMARY, $l10n->t('Summary'), $l10n->t('Add a summary, abstract or spoiler text to your posts'), false, $config->get('feature_lock', self::SUMMARY, false)], ], // Widget visibility on the network stream @@ -147,7 +147,7 @@ class Feature 'advanced_calendar' => [ $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)], - ], + ] ]; // removed any locked features and remove the entire category if this makes it empty @@ -172,7 +172,7 @@ class Feature } $arr = $eventDispatcher->dispatch( - new ArrayFilterEvent(ArrayFilterEvent::FEATURE_GET, $arr), + new ArrayFilterEvent(ArrayFilterEvent::FEATURE_GET, $arr) )->getArray(); return $arr; diff --git a/src/Content/Item.php b/src/Content/Item.php index 72dcbfc0bc..826146f68d 100644 --- a/src/Content/Item.php +++ b/src/Content/Item.php @@ -151,7 +151,7 @@ class Item 'url' => $url, 'removeurl' => $this->userSession->getLocalUserId() == $uid ? 'filerm/' . $item['id'] . '?cat=' . rawurlencode($savedFolderName) : '', 'first' => $first, - 'last' => false, + 'last' => false ]; $first = false; } @@ -167,7 +167,7 @@ class Item 'url' => "#", 'removeurl' => $this->userSession->getLocalUserId() == $uid ? 'filerm/' . $item['id'] . '?term=' . rawurlencode($savedFolderName) : '', 'first' => $first, - 'last' => false, + 'last' => false ]; $first = false; } @@ -303,7 +303,7 @@ class Item if ($this->activity->match($item['verb'], Activity::TAG)) { $fields = [ 'author-id', 'author-link', 'author-name', 'author-network', 'author-link', 'author-alias', - 'verb', 'object-type', 'resource-id', 'body', 'plink', + 'verb', 'object-type', 'resource-id', 'body', 'plink' ]; $obj = Post::selectFirst($fields, ['uri' => $item['parent-uri']]); if (!DBA::isResult($obj)) { @@ -452,8 +452,8 @@ class Item $menu[$this->l10n->t('Raw content')] = 'javascript:displaySearchText(' . $item['uri-id'] . ');'; - if ((($cid == 0) || ($rel == Contact::FOLLOWER)) - && in_array($item['network'], Protocol::FEDERATED) + if ((($cid == 0) || ($rel == Contact::FOLLOWER)) && + in_array($item['network'], Protocol::FEDERATED) ) { $menu[$this->l10n->t('Connect/Follow')] = 'contact/follow?url=' . urlencode($item['author-link']) . '&auto=1'; } @@ -496,10 +496,10 @@ class Item } // Check conditions - return (!($this->activity->match($item['verb'], Activity::FOLLOW) - && $item['object-type'] === Activity\ObjectType::NOTE - && empty($item['self']) - && $item['uid'] == $this->userSession->getLocalUserId()) + return (!($this->activity->match($item['verb'], Activity::FOLLOW) && + $item['object-type'] === Activity\ObjectType::NOTE && + empty($item['self']) && + $item['uid'] == $this->userSession->getLocalUserId()) ); } @@ -741,7 +741,7 @@ class Item if (is_array($shared)) { return [ 'comment' => BBCode::removeSharedData($item['body'] ?? ''), - 'post' => $shared, + 'post' => $shared ]; } } @@ -752,7 +752,7 @@ class Item if (is_array($shared)) { return [ 'comment' => $attributes['comment'], - 'post' => $shared, + 'post' => $shared ]; } } @@ -845,8 +845,8 @@ class Item 0 => [ 'src' => $attachment_img_src, 'width' => $attachment_img_width, - 'height' => $attachment_img_height, - ], + 'height' => $attachment_img_height + ] ]; } else { unset($attachment['images']); @@ -940,16 +940,16 @@ class Item public function initializePost(array $post): array { - $post['network'] = Protocol::DFRN; - $post['protocol'] = Conversation::PARCEL_DIRECT; - $post['direction'] = Conversation::PUSH; - $post['received'] = DateTimeFormat::utcNow(); - $post['origin'] = true; - $post['wall'] ??= true; - $post['guid'] ??= System::createUUID(); - $post['verb'] ??= Activity::POST; - $post['uri'] ??= ItemModel::newURI($post['guid']); - $post['thr-parent'] ??= $post['uri']; + $post['network'] = Protocol::DFRN; + $post['protocol'] = Conversation::PARCEL_DIRECT; + $post['direction'] = Conversation::PUSH; + $post['received'] = DateTimeFormat::utcNow(); + $post['origin'] = true; + $post['wall'] = $post['wall'] ?? true; + $post['guid'] = $post['guid'] ?? System::createUUID(); + $post['verb'] = $post['verb'] ?? Activity::POST; + $post['uri'] = $post['uri'] ?? ItemModel::newURI($post['guid']); + $post['thr-parent'] = $post['thr-parent'] ?? $post['uri']; if (empty($post['gravity'])) { $post['gravity'] = ($post['uri'] == $post['thr-parent']) ? ItemModel::GRAVITY_PARENT : ItemModel::GRAVITY_COMMENT; @@ -1031,7 +1031,7 @@ class Item ]; $hook_data = $this->eventDispatcher->dispatch( - new ArrayFilterEvent(ArrayFilterEvent::INSERT_POST_LOCAL_END, $hook_data), + new ArrayFilterEvent(ArrayFilterEvent::INSERT_POST_LOCAL_END, $hook_data) )->getArray(); $post = $hook_data['item'] ?? $post; @@ -1050,7 +1050,7 @@ class Item $this->baseURL, $post, $address, - $author['thumb'] ?? '', + $author['thumb'] ?? '' )); } } @@ -1335,7 +1335,7 @@ class Item \IntlChar::BLOCK_CODE_BASIC_LATIN, \IntlChar::BLOCK_CODE_LATIN_1_SUPPLEMENT, \IntlChar::BLOCK_CODE_LATIN_EXTENDED_A, \IntlChar::BLOCK_CODE_LATIN_EXTENDED_B, \IntlChar::BLOCK_CODE_LATIN_EXTENDED_C, \IntlChar::BLOCK_CODE_LATIN_EXTENDED_D, - \IntlChar::BLOCK_CODE_LATIN_EXTENDED_E, \IntlChar::BLOCK_CODE_LATIN_EXTENDED_ADDITIONAL, + \IntlChar::BLOCK_CODE_LATIN_EXTENDED_E, \IntlChar::BLOCK_CODE_LATIN_EXTENDED_ADDITIONAL ]); } @@ -1363,24 +1363,4 @@ class Item $used_languages = $this->l10n->t("Detected languages in this post:\n%s", $used_languages); return $used_languages; } - - public function setViewed(int $uri_id, int $uid) - { - if (Post::exists(['thr-parent-id' => $uri_id, 'verb' => Activity::VIEW, 'uid' => $uid])) { - return; - } - - $item = Post::selectFirst(['id'], ['uri-id' => $uri_id, 'gravity' => ItemModel::GRAVITY_PARENT, 'uid' => [0, $uid]], ['order' => ['uid' => true]]); - if (!DBA::isResult($item)) { - return; - } - - $self = DBA::selectFirst('contact', ['id'], ['uid' => $uid, 'self' => true]); - if (!DBA::isResult($self)) { - return; - } - - $this->logger->debug('Marking item as viewed.', ['uri-id' => $uri_id, 'uid' => $uid]); - ItemModel::performActivity($item['id'], 'view', $uid, '<' . $self['id'] . '>', '', '', ''); - } } diff --git a/src/Content/Nav.php b/src/Content/Nav.php index 37f1a9e751..51d75a7d21 100644 --- a/src/Content/Nav.php +++ b/src/Content/Nav.php @@ -224,9 +224,10 @@ class Nav if ($this->session->isAuthenticated()) { // user menu - $nav['usermenu'][] = ['profile/' . $this->session->getLocalUserNickname() . '/profile', $this->l10n->t('Profile'), '', $this->l10n->t('Your profile page'), 'fa-user']; $nav['usermenu'][] = ['profile/' . $this->session->getLocalUserNickname(), $this->l10n->t('Conversations'), '', $this->l10n->t('Conversations you started'), 'fa-commenting']; + $nav['usermenu'][] = ['profile/' . $this->session->getLocalUserNickname() . '/profile', $this->l10n->t('Profile'), '', $this->l10n->t('Your profile page'), 'fa-user']; $nav['usermenu'][] = ['profile/' . $this->session->getLocalUserNickname() . '/photos', $this->l10n->t('Photos'), '', $this->l10n->t('Your photos'), 'fa-picture-o']; + $nav['usermenu'][] = ['profile/' . $this->session->getLocalUserNickname() . '/media', $this->l10n->t('Media'), '', $this->l10n->t('Your postings with media'), 'fa-edit']; $nav['usermenu'][] = ['calendar/', $this->l10n->t('Calendar'), '', $this->l10n->t('Your calendar'), 'fa-calendar']; $nav['usermenu'][] = ['notes/', $this->l10n->t('Personal notes'), '', $this->l10n->t('Your personal notes'), 'fa-book']; @@ -317,12 +318,8 @@ class Nav $nav['messages']['outbox'] = ['message/sent', $this->l10n->t('Outbox'), '', $this->l10n->t('Outbox')]; $nav['messages']['new'] = ['message/new', $this->l10n->t('New Message'), '', $this->l10n->t('New Message')]; - $nav_accounts_name = $this->l10n->t('Accounts'); - $nav_accounts_description = $this->l10n->t('Manage other accounts, including groups and pages'); if (User::hasIdentities($this->session->getSubManagedUserId() ?: $this->session->getLocalUserId())) { - $nav['delegation'] = ['delegation', $nav_accounts_name, '', $nav_accounts_description]; - } else { - $nav['delegation'] = ['settings/delegation', $nav_accounts_name, '', $nav_accounts_description]; + $nav['delegation'] = ['delegation', $this->l10n->t('Accounts'), '', $this->l10n->t('Manage other accounts, including groups and pages')]; } $nav['settings'] = ['settings', $this->l10n->t('Settings'), '', $this->l10n->t('Account settings')]; diff --git a/src/Content/PageInfo.php b/src/Content/PageInfo.php index 90f32c2fc2..a53c36b68c 100644 --- a/src/Content/PageInfo.php +++ b/src/Content/PageInfo.php @@ -103,12 +103,12 @@ class PageInfo // It maybe is a rich content, but if it does have everything that a link has, // then treat it that way - if (($data['type'] == 'rich') && is_string($data['title']) - && is_string($data['text']) && !empty($data['images'])) { + if (($data['type'] == 'rich') && is_string($data['title']) && + is_string($data['text']) && !empty($data['images'])) { $data['type'] = 'link'; } - $data['title'] ??= ''; + $data['title'] = $data['title'] ?? ''; if ((($data['type'] != 'link') && ($data['type'] != 'video') && ($data['type'] != 'photo')) || ($data['title'] == $data['url'])) { return ''; @@ -223,7 +223,7 @@ class PageInfo $hashtag = str_replace( [' ', '+', '/', '.', '#', "'"], ['', '', '', '', '', ''], - $keyword, + $keyword ); $taglist[] = $hashtag; diff --git a/src/Content/Post/Repository/PostMedia.php b/src/Content/Post/Repository/PostMedia.php index 1d0c5e3703..080bc74927 100644 --- a/src/Content/Post/Repository/PostMedia.php +++ b/src/Content/Post/Repository/PostMedia.php @@ -461,9 +461,6 @@ class PostMedia extends BaseRepository } else { $player = $this->getLinkAttachment($media); } - if ($player === '') { - continue; - } @$tmp->loadHTML(mb_convert_encoding($player, 'HTML-ENTITIES', "UTF-8"), LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD); $div = $tmp->documentElement; $imported = $doc->importNode($div, true); diff --git a/src/Content/Text/BBCode.php b/src/Content/Text/BBCode.php index eb448cc47d..874ca6de1f 100644 --- a/src/Content/Text/BBCode.php +++ b/src/Content/Text/BBCode.php @@ -36,29 +36,29 @@ use GuzzleHttp\Psr7\Uri; class BBCode { // Update this value to the current date whenever changes are made to BBCode::convert - public const VERSION = '2024-04-07'; + const VERSION = '2024-04-07'; - public const INTERNAL = 0; - public const EXTERNAL = 1; - public const MASTODON_API = 2; - public const DIASPORA = 3; - public const CONNECTORS = 4; - public const TWITTER_API = 5; - public const NPF = 6; - public const TWITTER = 8; - public const BACKLINK = 8; - public const ACTIVITYPUB = 9; - public const BLUESKY = 10; + const INTERNAL = 0; + const EXTERNAL = 1; + const MASTODON_API = 2; + const DIASPORA = 3; + const CONNECTORS = 4; + const TWITTER_API = 5; + const NPF = 6; + const TWITTER = 8; + const BACKLINK = 8; + const ACTIVITYPUB = 9; + const BLUESKY = 10; - public const SHARED_ANCHOR = '
'; - public const TOP_ANCHOR = '
'; - public const BOTTOM_ANCHOR = '
'; + const SHARED_ANCHOR = '
'; + const TOP_ANCHOR = '
'; + const BOTTOM_ANCHOR = '
'; - public const PREVIEW_NONE = 0; - public const PREVIEW_NO_IMAGE = 1; - public const PREVIEW_LARGE = 2; - public const PREVIEW_SMALL = 3; - public const PREVIEW_AUTO = 4; + const PREVIEW_NONE = 0; + const PREVIEW_NO_IMAGE = 1; + const PREVIEW_LARGE = 2; + const PREVIEW_SMALL = 3; + const PREVIEW_AUTO = 4; /** * Fetches attachment data that were generated with the "attachment" element @@ -183,7 +183,7 @@ class BBCode $attach_data = self::getAttachmentData($match[0]); if (empty($attach_data['url'])) { return $match[0]; - } elseif (strpos(str_replace($match[0], '', $body), (string) $attach_data['url']) !== false) { + } elseif (strpos(str_replace($match[0], '', $body), $attach_data['url']) !== false) { return ''; } elseif (empty($attach_data['title']) || $no_link_desc) { return " \n[url]" . $attach_data['url'] . "[/url]\n"; @@ -191,7 +191,7 @@ class BBCode return " \n[url=" . $attach_data['url'] . ']' . $attach_data['title'] . "[/url]\n"; } }, - $body, + $body ); } @@ -245,12 +245,12 @@ class BBCode // Add text from attached media if (!empty($uri_id)) { foreach (Post\Media::getByURIId($uri_id) as $media) { - if (!empty($media['description']) && (stripos($text, (string) $media['description']) === false)) { + if (!empty($media['description']) && (stripos($text, $media['description']) === false)) { $text .= ' ' . $media['description']; } if (in_array($media['type'], [Post\Media::HTML, Post\Media::ACTIVITY])) { foreach (['name', 'author-name', 'publisher-name'] as $key) { - if (!empty($media[$key] && stripos($text, (string) $media[$key]) === false)) { + if (!empty($media[$key] && stripos($text, $media[$key]) === false)) { $text .= ' ' . $media[$key]; } } @@ -473,19 +473,14 @@ class BBCode if (!empty($data['title']) && !empty($data['url'])) { $preview_class = in_array($preview_mode, [self::PREVIEW_AUTO, self::PREVIEW_LARGE]) ? 'attachment-image' : 'attachment-preview'; - - $is_photo = ($data['type'] === 'photo'); - $link_attributes = $is_photo ? 'data-fancybox="gallery" rel="noopener noreferrer"' : 'target="_blank" rel="noopener noreferrer"'; - - if (!empty($data['image']) && empty($data['text']) && $is_photo) { - $return .= sprintf('', $data['url'], $link_attributes, self::proxyUrl($data['image'], $simplehtml, $uriid), $data['title'], $preview_class); + if (!empty($data['image']) && empty($data['text']) && ($data['type'] == 'photo')) { + $return .= sprintf('', $data['url'], self::proxyUrl($data['image'], $simplehtml, $uriid), $data['title']); } else { if (!empty($data['image'])) { - $return .= sprintf('
', $data['url'], $link_attributes, self::proxyUrl($data['image'], $simplehtml, $uriid), $data['title'], $preview_class); + $return .= sprintf('
', $data['url'], self::proxyUrl($data['image'], $simplehtml, $uriid), $data['title']); } elseif (!empty($data['preview'])) { - $return .= sprintf('
', $data['url'], $link_attributes, self::proxyUrl($data['preview'], $simplehtml, $uriid), $data['title']); + $return .= sprintf('
', $data['url'], self::proxyUrl($data['preview'], $simplehtml, $uriid), $data['title']); } - $return .= sprintf('

%s

', $data['url'], $data['title']); } } @@ -540,7 +535,7 @@ class BBCode } // If the link already is included in the post, don't add it again - if (!empty($data['url']) && strpos($data['text'], (string) $data['url'])) { + if (!empty($data['url']) && strpos($data['text'], $data['url'])) { DI::profiler()->stopRecording(); return $data['text'] . $data['after']; } @@ -605,11 +600,11 @@ class BBCode $res = [ 'start' => [ 'open' => $start_open, - 'close' => $start_close, + 'close' => $start_close ], 'end' => [ 'open' => $end_open, - 'close' => $end_open + strlen('[/' . $name . ']'), + 'close' => $end_open + strlen('[/' . $name . ']') ], ]; @@ -706,7 +701,7 @@ class BBCode $newbody = str_replace( '[$#saved_image' . $cnt . '#$]', '', - $newbody, + $newbody ); $cnt++; } @@ -816,9 +811,9 @@ class BBCode function ($match) use ($callback, $uriid) { $attributes = self::extractShareAttributes($match[2]); - $author_contact = Contact::getByURL($attributes['profile'], false, ['id', 'url', 'addr', 'name', 'micro']); - $author_contact['url'] ??= $attributes['profile']; - $author_contact['addr'] ??= ''; + $author_contact = Contact::getByURL($attributes['profile'], false, ['id', 'url', 'addr', 'name', 'micro']); + $author_contact['url'] = ($author_contact['url'] ?? $attributes['profile']); + $author_contact['addr'] = ($author_contact['addr'] ?? ''); $attributes['author'] = ($author_contact['name'] ?? '') ?: $attributes['author']; $attributes['avatar'] = ($author_contact['micro'] ?? '') ?: $attributes['avatar']; @@ -834,7 +829,7 @@ class BBCode return $match[1] . $callback($attributes, $author_contact, $content ?? '', trim($match[1]) != ''); }, - $text, + $text ); DI::profiler()->stopRecording(); @@ -866,7 +861,7 @@ class BBCode $img_str .= ' ' . empty($attributes['alt']) ? 'class="empty-description"' : 'class="has-alt-description"'; return $img_str . '>'; }, - $text, + $text ); DI::profiler()->stopRecording(); @@ -895,9 +890,9 @@ class BBCode switch ($simplehtml) { case self::MASTODON_API: case self::TWITTER_API: - $text = ($is_quote_share ? '
' : '') - . '' . html_entity_decode('♲', ENT_QUOTES, 'UTF-8') . ' ' . $author_contact['addr'] . ":
\n" - . '
' . $content . '
'; + $text = ($is_quote_share ? '
' : '') . + '' . html_entity_decode('♲', ENT_QUOTES, 'UTF-8') . ' ' . $author_contact['addr'] . ":
\n" . + '
' . $content . '
'; break; case self::DIASPORA: if (stripos(Strings::normaliseLink($attributes['link']), 'http://twitter.com/') === 0) { @@ -1032,7 +1027,7 @@ class BBCode */ private static function expandLinksCallback(array $match): string { - if (($match[3] == '') || ($match[2] == $match[3]) || stristr($match[2], (string) $match[3])) { + if (($match[3] == '') || ($match[2] == $match[3]) || stristr($match[2], $match[3])) { return ($match[1] . '[url]' . $match[2] . '[/url]'); } else { return ($match[1] . $match[3] . ' [url]' . $match[2] . '[/url]'); @@ -1419,7 +1414,7 @@ class BBCode return $return; }, - $text, + $text ); if (strpos($text, '

') !== false || strpos($text, '

') !== false) { @@ -1490,11 +1485,11 @@ class BBCode if (DI::config()->get('system', 'remove_multiplicated_lines')) { $search = [ "\n\n\n", "[/quote]\n\n", "\n[/quote]", "\n[ul]", "[/ul]\n", "\n[ol]", "[/ol]\n", "\n\n[share ", "[/attachment]\n", - "\n[h1]", "[/h1]\n", "\n[h2]", "[/h2]\n", "\n[h3]", "[/h3]\n", "\n[h4]", "[/h4]\n", "\n[h5]", "[/h5]\n", "\n[h6]", "[/h6]\n", + "\n[h1]", "[/h1]\n", "\n[h2]", "[/h2]\n", "\n[h3]", "[/h3]\n", "\n[h4]", "[/h4]\n", "\n[h5]", "[/h5]\n", "\n[h6]", "[/h6]\n" ]; $replace = [ "\n\n", "[/quote]\n", "[/quote]", "[ul]", "[/ul]", "[ol]", "[/ol]", "\n[share ", "[/attachment]", - "[h1]", "[/h1]", "[h2]", "[/h2]", "[h3]", "[/h3]", "[h4]", "[/h4]", "[h5]", "[/h5]", "[h6]", "[/h6]", + "[h1]", "[/h1]", "[h2]", "[/h2]", "[h3]", "[/h3]", "[h4]", "[/h4]", "[h5]", "[/h5]", "[h6]", "[/h6]" ]; do { $oldtext = $text; @@ -1557,7 +1552,7 @@ class BBCode function ($match) use ($simple_html) { return str_replace($match[0], '

' . Map::byLocation($match[1], $simple_html) . '

', $match[0]); }, - $text, + $text ); } @@ -1567,7 +1562,7 @@ class BBCode function ($match) use ($simple_html) { return str_replace($match[0], '

' . Map::byCoordinates(str_replace('/', ' ', $match[1]), $simple_html) . '

', $match[0]); }, - $text, + $text ); } @@ -1636,7 +1631,7 @@ class BBCode $elements = [ 'del' => 's', 'ins' => 'em', 'kbd' => 'code', 'mark' => 'strong', - 'samp' => 'code', 'u' => 'em', 'var' => 'em', + 'samp' => 'code', 'u' => 'em', 'var' => 'em' ]; foreach ($elements as $bbcode => $html) { $text = preg_replace("(\[" . $bbcode . "\](.*?)\[\/" . $bbcode . "\])ism", '<' . $html . '>$1', $text); @@ -1647,7 +1642,7 @@ class BBCode // @todo add the new elements to the documentation by the end of 2024 so that most systems will support them. $elements = [ 'b', 'del', 'em', 'i', 'ins', 'kbd', 'mark', - 's', 'samp', 'small', 'strong', 'sub', 'sup', 'u', 'var', + 's', 'samp', 'small', 'strong', 'sub', 'sup', 'u', 'var' ]; foreach ($elements as $element) { $text = preg_replace("(\[" . $element . "\](.*?)\[\/" . $element . "\])ism", '<' . $element . '>$1', $text); @@ -1711,10 +1706,10 @@ class BBCode // handle nested lists $endlessloop = 0; - while ((((strpos($text, "[/list]") !== false) && (strpos($text, "[list") !== false)) - || ((strpos($text, "[/ol]") !== false) && (strpos($text, "[ol]") !== false)) - || ((strpos($text, "[/ul]") !== false) && (strpos($text, "[ul]") !== false)) - || ((strpos($text, "[/li]") !== false) && (strpos($text, "[li]") !== false))) && (++$endlessloop < 20)) { + while ((((strpos($text, "[/list]") !== false) && (strpos($text, "[list") !== false)) || + ((strpos($text, "[/ol]") !== false) && (strpos($text, "[ol]") !== false)) || + ((strpos($text, "[/ul]") !== false) && (strpos($text, "[ul]") !== false)) || + ((strpos($text, "[/li]") !== false) && (strpos($text, "[li]") !== false))) && (++$endlessloop < 20)) { $text = preg_replace("/\[list\](.*?)\[\/list\]/ism", '

', $text); $text = preg_replace("/\[list=\](.*?)\[\/list\]/ism", '

', $text); $text = preg_replace("/\[list=1\](.*?)\[\/list\]/ism", '

', $text); @@ -1754,7 +1749,7 @@ class BBCode $text = preg_replace( "/\[spoiler=[\"\']*(.*?)[\"\']*\](.*?)\[\/spoiler\]/ism", '

$1$2
', - $text, + $text ); } @@ -1800,7 +1795,7 @@ class BBCode $text = preg_replace( "/\[quote=[\"\']*(.*?)[\"\']*\](.*?)\[\/quote\]/ism", "

" . $t_wrote . "

$2
", - $text, + $text ); } @@ -1820,7 +1815,7 @@ class BBCode $matches[3] = self::proxyUrl($matches[3], $simple_html, $uriid); return "[img=" . $matches[1] . "x" . $matches[2] . "]" . $matches[3] . "[/img]"; }, - $text, + $text ); $text = preg_replace("/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/ism", '', $text); @@ -1838,7 +1833,7 @@ class BBCode return '' . $alt . ''; } }, - $text, + $text ); // Images @@ -1853,7 +1848,7 @@ class BBCode $matches[1] = self::proxyUrl($matches[1], $simple_html, $uriid); return "[img]" . $matches[1] . "[/img]"; }, - $text, + $text ); $text = preg_replace("/\[img\](.*?)\[\/img\]/ism", '', $text); @@ -1883,12 +1878,12 @@ class BBCode $text = preg_replace( "/\[video\](.*?)\[\/video\]/ism", '

', - $text, + $text ); $text = preg_replace( "/\[audio\](.*?)\[\/audio\]/ism", '

', - $text, + $text ); } else { $text = preg_replace("/\[video\](.*?)\[\/video\]/ism", '[embed]$1[/embed]', $text); @@ -1921,7 +1916,7 @@ class BBCode function ($match) use ($max_length) { return '' . Strings::getStyledURL(Network::sanitizeUrl($match[1]), $max_length) . ""; }, - $text, + $text ); return $text; @@ -1938,24 +1933,24 @@ class BBCode $text = preg_replace( "/([@!])\[url\=(.*?)\](.*?)\[\/url\]/ism", '@$3', - $text, + $text ); } elseif (in_array($simple_html, [self::ACTIVITYPUB])) { $text = preg_replace( "/([@!])\[url\=(.*?)\](.*?)\[\/url\]/ism", '$1$3', - $text, + $text ); $text = preg_replace( "/([#])\[url\=(.*?)\](.*?)\[\/url\]/ism", '', - $text, + $text ); } elseif (in_array($simple_html, [self::EXTERNAL, self::TWITTER_API])) { $text = preg_replace( "/([@!])\[url\=(.*?)\](.*?)\[\/url\]/ism", '$1$3', - $text, + $text ); } elseif ($simple_html == self::INTERNAL) { if (preg_match_all("/([@!])\[url\=(.*?)\](.*?)\[\/url\]/ism", $text, $matches, PREG_SET_ORDER)) { @@ -1974,12 +1969,12 @@ class BBCode $text = preg_replace( "/([@!])\[url\=(.*?)\](.*?)\[\/url\]/ism", '$1$3', - $text, + $text ); $text = preg_replace( "/([#])\[url\=(.*?)\](.*?)\[\/url\]/ism", '', - $text, + $text ); } else { $text = preg_replace("/([#@!])\[url\=(.*?)\](.*?)\[\/url\]/ism", '$1$3', $text); @@ -1996,7 +1991,7 @@ class BBCode $text = preg_replace( "/#\[url\=.*?\]\^\[\/url\]\[url\=(.*?)\](.*?)\[\/url\]/i", "[bookmark=$1]$2[/bookmark]", - $text, + $text ); if (in_array($simple_html, [self::TWITTER, self::BLUESKY])) { @@ -2013,7 +2008,7 @@ class BBCode function ($match) { return "[url=" . DI::baseUrl() . "/display/" . $match[1] . "]" . $match[2] . "[/url]"; }, - $text, + $text ); $text = preg_replace_callback( @@ -2021,7 +2016,7 @@ class BBCode function ($match) { return "[url=" . DI::baseUrl() . "/search?search=%40" . $match[1] . "]" . $match[2] . "[/url]"; }, - $text, + $text ); // Server independent link to posts and comments @@ -2088,7 +2083,7 @@ class BBCode $parts['host'] = idn_to_ascii(urldecode($parts['host'])); try { - return (string) Uri::fromParts($parts); + return (string)Uri::fromParts($parts); } catch (\Throwable $th) { DI::logger()->notice('Exception on unparsing url', ['url' => $url, 'parts' => $parts, 'code' => $th->getCode(), 'message' => $th->getMessage()]); return $url; @@ -2102,7 +2097,7 @@ class BBCode function ($match) { return "[url=" . self::escapeUrl($match[1]) . "]" . $match[1] . "[/url]"; }, - $text, + $text ); } @@ -2119,7 +2114,7 @@ class BBCode function ($match) use ($max_length) { return "[url=" . self::escapeUrl($match[1]) . "]" . Strings::getStyledURL($match[1], $max_length) . "[/url]"; }, - $text, + $text ); $text = preg_replace_callback( "/\[url\=(.*?)\](.*?)\[\/url\]/ism", @@ -2130,7 +2125,7 @@ class BBCode return "[url=" . self::escapeUrl($match[1]) . "]" . $match[2] . "[/url]"; } }, - $text, + $text ); return $text; } @@ -2156,7 +2151,7 @@ class BBCode function (array $attributes, array $author_contact, $content, $is_quote_share) use ($simple_html) { return self::convertShareCallback($attributes, $author_contact, $content, $is_quote_share, $simple_html); }, - $uriid, + $uriid ); return $text; @@ -2184,7 +2179,7 @@ class BBCode $text = preg_replace( '#<([^>]*?)(src)="(?!' . implode('|', $allowed_src_protocols) . ')(.*?)"(.*?)>#ism', '<$1$2=""$4 data-original-src="$3" class="invalid-src" title="' . DI::l10n()->t('Invalid source protocol') . '">', - $text, + $text ); // sanitize href attributes (only allowlisted protocols URLs) @@ -2307,7 +2302,7 @@ class BBCode function ($matches) { return '#' . str_replace(' ', '_', $matches[2]); }, - $text, + $text ); // Converting images with size parameters to simple images. Markdown doesn't know it. @@ -2353,7 +2348,7 @@ class BBCode $text = preg_replace_callback( "/([@!])\[(.*?)\]\(([$url_search_string]*?)\)/ism", [self::class, 'bbCodeMention2DiasporaCallback'], - $text, + $text ); } @@ -2468,7 +2463,7 @@ class BBCode return $match[1] . '[url=' . DI::baseUrl() . '/search?tag=' . urlencode($match[2]) . ']' . $match[2] . '[/url]'; } }, - $body, + $body ); } @@ -2549,11 +2544,11 @@ class BBCode public static function getShareOpeningTag(string $author, string $profile, string $avatar, string $link, string $posted, string $guid = null, string $uri = null): string { DI::profiler()->startRecording('rendering'); - $header = "[share author='" . str_replace(["'", "[", "]"], ["'", "[", "]"], $author) - . "' profile='" . str_replace(["'", "[", "]"], ["'", "[", "]"], $profile) - . "' avatar='" . str_replace(["'", "[", "]"], ["'", "[", "]"], $avatar) - . "' link='" . str_replace(["'", "[", "]"], ["'", "[", "]"], $link) - . "' posted='" . str_replace(["'", "[", "]"], ["'", "[", "]"], $posted); + $header = "[share author='" . str_replace(["'", "[", "]"], ["'", "[", "]"], $author) . + "' profile='" . str_replace(["'", "[", "]"], ["'", "[", "]"], $profile) . + "' avatar='" . str_replace(["'", "[", "]"], ["'", "[", "]"], $avatar) . + "' link='" . str_replace(["'", "[", "]"], ["'", "[", "]"], $link) . + "' posted='" . str_replace(["'", "[", "]"], ["'", "[", "]"], $posted); if ($guid) { $header .= "' guid='" . str_replace(["'", "[", "]"], ["'", "[", "]"], $guid); @@ -2604,7 +2599,7 @@ class BBCode } } - $result = sprintf('[bookmark=%s]%s[/bookmark]%s', $url, $title ?: $url, $description) . $str_tags; + $result = sprintf('[bookmark=%s]%s[/bookmark]%s', $url, ($title) ? $title : $url, $description) . $str_tags; DI::logger()->info('(unparsed): returns: ' . $result); diff --git a/src/Content/Text/HTML.php b/src/Content/Text/HTML.php index 54e4435e17..3d789e7fa0 100644 --- a/src/Content/Text/HTML.php +++ b/src/Content/Text/HTML.php @@ -153,7 +153,7 @@ class HTML "

  • ", "
  • ", ], - $message, + $message ); // remove namespaces @@ -209,7 +209,7 @@ class HTML 'div', ['style' => 'border:none;border-left:solid blue 1.5pt;padding:0cm 0cm 0cm 4.0pt'], '[quote]', - '[/quote]', + '[/quote]' ); // MyBB-Stuff @@ -244,7 +244,7 @@ class HTML $elements = [ 'b', 'del', 'em', 'i', 'ins', 'kbd', 'mark', - 's', 'samp', 'strong', 'sub', 'sup', 'u', 'var', + 's', 'samp', 'strong', 'sub', 'sup', 'u', 'var' ]; foreach ($elements as $element) { self::tagToBBCode($doc, $element, [], '[' . $element . ']', '[/' . $element . ']'); @@ -348,7 +348,7 @@ class HTML $message = str_replace( ['[b][b]', '[/b][/b]', '[i][i]', '[/i][/i]'], ['[b]', '[/b]', '[i]', '[/i]'], - $message, + $message ); // Handling Yahoo style of mails @@ -367,7 +367,7 @@ class HTML return $prefix . "\n" . html_entity_decode($matches[2]) . "\n" . '[/code]'; }, - $message, + $message ); $message = trim($message); @@ -402,7 +402,7 @@ class HTML } $parts = array_merge($base, parse_url($url)); - $url2 = (string) Uri::fromParts((array) $parts); + $url2 = (string)Uri::fromParts((array)$parts); return str_replace($url, $url2, $link); } @@ -436,7 +436,7 @@ class HTML function ($match) use ($basepath) { return self::qualifyURLsSub($match, $basepath); }, - $body, + $body ); } return $body; @@ -531,7 +531,7 @@ class HTML // A list of some links that should be ignored $list = [ "/user/", "/tag/", "/group/", "/circle/", "/profile/", "/search?search=", "/search?tag=", "mailto:", "/u/", "/node/", - "//plus.google.com/", "//twitter.com/", + "//plus.google.com/", "//twitter.com/" ]; foreach ($list as $listitem) { if (strpos($treffer[1], $listitem) !== false) { @@ -660,7 +660,7 @@ class HTML if (!$compact && ($message != '')) { foreach ($urls as $id => $url) { - if ($url != '' && strpos($message, (string) $url) === false) { + if ($url != '' && strpos($message, $url) === false) { $message .= "\n" . $url . ' '; } } @@ -708,19 +708,19 @@ class HTML $s = preg_replace( '#]+>(.*?)https?://www.youtube.com/((?:v|cp)/[A-Za-z0-9\-_=]+)(.*?)#ism', '[youtube]$2[/youtube]', - $s, + $s ); $s = preg_replace( '#](.*?)https?://www.youtube.com/embed/([A-Za-z0-9\-_=]+)(.*?)#ism', '[youtube]$2[/youtube]', - $s, + $s ); $s = preg_replace( '#](.*?)https?://player.vimeo.com/video/([0-9]+)(.*?)#ism', '[vimeo]$2[/vimeo]', - $s, + $s ); return $s; @@ -772,19 +772,12 @@ class HTML * @return string html for loader * @throws \Friendica\Network\HTTPException\InternalServerErrorException */ - public static function scrollLoader(array $request = []): string + public static function scrollLoader(): string { - if (in_array($request['mode'] ?? '', ['minimal', 'raw'])) { - return ''; - } - - $tpl = Renderer::getMarkupTemplate('infinite_scroll_head.tpl'); - $loader = Renderer::replaceMacros($tpl, ['$reload_uri' => DI::args()->getQueryString()]); - $tpl = Renderer::getMarkupTemplate("scroll_loader.tpl"); - return $loader . Renderer::replaceMacros($tpl, [ + return Renderer::replaceMacros($tpl, [ 'wait' => DI::l10n()->t('Loading more entries...'), - 'end' => DI::l10n()->t('The end'), + 'end' => DI::l10n()->t('The end') ]); } @@ -839,7 +832,7 @@ class HTML '$name' => $contact['name'], 'title' => $contact['name'] . ' [' . $contact['addr'] . ']', '$parkle' => $sparkle, - '$redir' => $redir, + '$redir' => $redir ]); } @@ -877,7 +870,7 @@ class HTML $values['$search_options'] = [ 'fulltext' => DI::l10n()->t('Full Text'), 'tags' => DI::l10n()->t('Tags'), - 'contacts' => DI::l10n()->t('Contacts'), + 'contacts' => DI::l10n()->t('Contacts') ]; if (DI::config()->get('system', 'poco_local_search')) { @@ -906,7 +899,7 @@ class HTML '$reasons' => $reasons, '$rnd' => Strings::getRandomHex(8), '$openclose' => DI::l10n()->t('Click to open/close'), - '$html' => $html, + '$html' => $html ]); } @@ -954,7 +947,7 @@ class HTML ' . implode('|', $allowedIframeDomains) . ' ) (?:/|$) # Prevents bogus domains like youtube.com.fake.tld - %xi', + %xi' ); $config->set('Attr.AllowedRel', [ @@ -1080,7 +1073,7 @@ class HTML * * @param string $html * @param string $className - * @return string the HTML without the removed HTML element + * @return string the HTML without the removed HTML element */ public static function removeElementByClass(string $html, string $className): string { diff --git a/src/Content/Text/MarkdownParser.php b/src/Content/Text/MarkdownParser.php index 8956d72ff1..084e582213 100644 --- a/src/Content/Text/MarkdownParser.php +++ b/src/Content/Text/MarkdownParser.php @@ -16,12 +16,9 @@ class MarkdownParser extends MarkdownExtra { $text = parent::doAutoLinks($text); - $text = preg_replace_callback( - Strings::autoLinkRegEx(), - [$this, '_doAutoLinks_url_callback'], - $text, - ); + $text = preg_replace_callback(Strings::autoLinkRegEx(), + array($this, '_doAutoLinks_url_callback'), $text); return $text; } -} +} \ No newline at end of file diff --git a/src/Content/Text/NPF.php b/src/Content/Text/NPF.php index 4fd0c5c9c6..3fd0956720 100644 --- a/src/Content/Text/NPF.php +++ b/src/Content/Text/NPF.php @@ -47,7 +47,7 @@ class NPF $element = $doc->getElementsByTagName('body')->item(0); - [$npf, $text, $formatting] = self::routeChildren($element, $uri_id, true, []); + list($npf, $text, $formatting) = self::routeChildren($element, $uri_id, true, []); return self::addLinkBlockForUriId($uri_id, 0, $npf); } @@ -130,7 +130,7 @@ class NPF private static function routeChildren(DOMElement $element, int $uri_id, bool $parse_structure, array $callstack, array $npf = [], string $text = '', array $formatting = []): array { if ($parse_structure && $text) { - [$npf, $text, $formatting] = self::addBlock($text, $formatting, $npf, $callstack); + list($npf, $text, $formatting) = self::addBlock($text, $formatting, $npf, $callstack); } $callstack[] = $element->nodeName; @@ -140,21 +140,21 @@ class NPF switch ($child->nodeName) { case 'b': case 'strong': - [$npf, $text, $formatting] = self::addFormatting($child, $uri_id, 'bold', $callstack, $npf, $text, $formatting); + list($npf, $text, $formatting) = self::addFormatting($child, $uri_id, 'bold', $callstack, $npf, $text, $formatting); break; case 'i': case 'em': - [$npf, $text, $formatting] = self::addFormatting($child, $uri_id, 'italic', $callstack, $npf, $text, $formatting); + list($npf, $text, $formatting) = self::addFormatting($child, $uri_id, 'italic', $callstack, $npf, $text, $formatting); break; case 's': - [$npf, $text, $formatting] = self::addFormatting($child, $uri_id, 'strikethrough', $callstack, $npf, $text, $formatting); + list($npf, $text, $formatting) = self::addFormatting($child, $uri_id, 'strikethrough', $callstack, $npf, $text, $formatting); break; case 'u': case 'span': - [$npf, $text, $formatting] = self::addFormatting($child, $uri_id, '', $callstack, $npf, $text, $formatting); + list($npf, $text, $formatting) = self::addFormatting($child, $uri_id, '', $callstack, $npf, $text, $formatting); break; case 'hr': @@ -174,7 +174,7 @@ class NPF break; case 'a': - [$npf, $text, $formatting] = self::addInlineLink($child, $uri_id, $callstack, $npf, $text, $formatting); + list($npf, $text, $formatting) = self::addInlineLink($child, $uri_id, $callstack, $npf, $text, $formatting); break; case 'img': @@ -187,13 +187,13 @@ class NPF break; default: - [$npf, $text, $formatting] = self::routeChildren($child, $uri_id, true, $callstack, $npf, $text, $formatting); + list($npf, $text, $formatting) = self::routeChildren($child, $uri_id, true, $callstack, $npf, $text, $formatting); break; } } if ($parse_structure && $text) { - [$npf, $text, $formatting] = self::addBlock($text, $formatting, $npf, $callstack); + list($npf, $text, $formatting) = self::addBlock($text, $formatting, $npf, $callstack); } return [$npf, $text, $formatting]; } @@ -291,7 +291,7 @@ class NPF { $start = mb_strlen($text); - [$npf, $text, $formatting] = self::routeChildren($element, $uri_id, false, $callstack, $npf, $text, $formatting); + list($npf, $text, $formatting) = self::routeChildren($element, $uri_id, false, $callstack, $npf, $text, $formatting); if (!empty($type)) { $formatting[] = [ @@ -318,7 +318,7 @@ class NPF { $start = mb_strlen($text); - [$npf, $text, $formatting] = self::routeChildren($element, $uri_id, false, $callstack, $npf, $text, $formatting); + list($npf, $text, $formatting) = self::routeChildren($element, $uri_id, false, $callstack, $npf, $text, $formatting); $attributes = []; foreach ($element->attributes as $attribute) { diff --git a/src/Content/Text/Plaintext.php b/src/Content/Text/Plaintext.php index 7bbd1dbfb3..8a1b479937 100644 --- a/src/Content/Text/Plaintext.php +++ b/src/Content/Text/Plaintext.php @@ -17,7 +17,7 @@ use IntlChar; class Plaintext { // Assumed length of an URL when shortened via the network's own url shortener (e.g. Twitter) - public const URL_LENGTH = 23; + const URL_LENGTH = 23; /** * Shortens message @@ -37,8 +37,8 @@ class Plaintext return mb_substr(mb_substr(trim($msg), 0, $limit), 0, -3) . $ellipsis; } - $lines = explode("\n", $msg); - $msg = ""; + $lines = explode("\n", $msg); + $msg = ""; $recycle = html_entity_decode("♲ ", ENT_QUOTES, 'UTF-8'); foreach ($lines as $row => $line) { if (mb_strlen(trim($msg . "\n" . $line)) <= $limit) { @@ -138,13 +138,13 @@ class Plaintext if ($post['type'] == 'text') { $post['type'] = 'link'; - $post['url'] = $item['plink']; + $post['url'] = $item['plink']; } } $html = BBCode::convertForUriId($item['uri-id'], $post['text'] . ($post['after'] ?? ''), $htmlmode); - $msg = HTML::toPlaintext($html, 0, true); - $msg = trim(html_entity_decode($msg, ENT_QUOTES, 'UTF-8')); + $msg = HTML::toPlaintext($html, 0, true); + $msg = trim(html_entity_decode($msg, ENT_QUOTES, 'UTF-8')); $complete_msg = $msg; @@ -170,8 +170,8 @@ class Plaintext // If the link is already contained in the post, then it needn't to be added again // But: if the link is beyond the limit, then it has to be added. - if (($link != '') && strstr($msg, (string) $link)) { - $pos = strpos($msg, (string) $link); + if (($link != '') && strstr($msg, $link)) { + $pos = strpos($msg, $link); // Will the text be shortened in the link? // Or is the link the last item in the post? @@ -200,7 +200,7 @@ class Plaintext $msg = str_replace(' ', ' ', $msg); } - if (!in_array($link, ['', $item['plink']]) && ($post['type'] != 'photo') && (strpos($complete_msg, (string) $link) === false)) { + if (!in_array($link, ['', $item['plink']]) && ($post['type'] != 'photo') && (strpos($complete_msg, $link) === false)) { $complete_msg .= "\n" . $link; } @@ -215,7 +215,7 @@ class Plaintext if (($post['type'] == 'text') && isset($post['url'])) { $post['url'] = $item['plink']; } elseif (!isset($post['url'])) { - $limit = $limit - self::URL_LENGTH; + $limit = $limit - self::URL_LENGTH; $post['url'] = $item['plink']; } elseif (strpos($item['body'], '[share') !== false) { $post['url'] = $item['plink']; @@ -250,7 +250,7 @@ class Plaintext $limit = $baselimit; while ($message) { - $pos_word = mb_strpos($message, ' '); + $pos_word = mb_strpos($message, ' '); $pos_paragraph = mb_strpos($message, "\n"); if (($pos_word !== false) && ($pos_paragraph !== false)) { @@ -260,8 +260,8 @@ class Plaintext } elseif ($pos_paragraph !== false) { $pos = $pos_paragraph + 1; } else { - $word = $message; - $message = ''; + $word = $message; + $message = ''; } if (trim($message)) { @@ -324,7 +324,7 @@ class Plaintext // Remove mentions and hashtag links $URLSearchString = '^\[\]'; - $post['text'] = preg_replace("/([#!@])\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", '$1$3', $item['body']); + $post['text'] = preg_replace("/([#!@])\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", '$1$3', $item['body']); // Remove abstract $post['text'] = BBCode::stripAbstract($post['text']); @@ -382,10 +382,10 @@ class Plaintext $page = Post\Media::getByURIId($item['quote-uri-id'], [Post\Media::HTML]); } if (!empty($page)) { - $post['type'] = 'link'; - $post['url'] = $page[0]['url']; - $post['description'] = $page[0]['description']; - $post['title'] = $page[0]['name']; + $post['type'] = 'link'; + $post['url'] = $page[0]['url']; + $post['description'] = $page[0]['description']; + $post['title'] = $page[0]['name']; if (empty($post['image']) && !empty($page[0]['preview'])) { $post['image'] = $page[0]['preview']; diff --git a/src/Content/Widget.php b/src/Content/Widget.php index 26e6cc53ce..6adb8c24b7 100644 --- a/src/Content/Widget.php +++ b/src/Content/Widget.php @@ -37,7 +37,7 @@ class Widget '$desc' => DI::l10n()->t('Enter address or web location'), '$hint' => DI::l10n()->t('user@x.tld, x.tld/user'), '$value' => $value, - '$follow' => DI::l10n()->t('Connect'), + '$follow' => DI::l10n()->t('Connect') ]); } @@ -48,12 +48,6 @@ class Widget */ public static function findPeople(): string { - // Langs in friendica are all lowercase and dash separated (e.g. da-dk) - in the directory - // they're underscore separated, with the latter part being uppercase (e.g. da_DK) - $directory_user_lang_string = ""; - if (DI::userSession()->get('language')) { - $directory_user_lang_string = "&lang=" . DI::l10n()->langToLocaleCode(DI::userSession()->get('language')); - } $global_dir = Search::getGlobalDirectory(); if (DI::config()->get('system', 'invitation_only')) { @@ -76,7 +70,7 @@ class Widget $nv['random'] = DI::l10n()->t('Random Profile'); $nv['inv'] = DI::l10n()->t('Invite Friends'); $nv['directory'] = DI::l10n()->t('Global Directory'); - $nv['global_dir'] = OpenWebAuth::getZrlUrl($global_dir, true) . $directory_user_lang_string; + $nv['global_dir'] = OpenWebAuth::getZrlUrl($global_dir, true); $nv['local_directory'] = DI::l10n()->t('Local Directory'); $aside = []; @@ -230,7 +224,7 @@ class Widget $options = array_map(function ($circle) { return [ 'ref' => $circle['id'], - 'name' => $circle['name'], + 'name' => $circle['name'] ]; }, Circle::getByUserId(DI::userSession()->getLocalUserId())); @@ -241,7 +235,7 @@ class Widget DI::l10n()->t('Everyone'), $baseurl, $options, - $selected, + $selected ); } @@ -273,7 +267,7 @@ class Widget DI::l10n()->t('All Contacts'), $baseurl, $options, - $selected, + $selected ); } @@ -314,7 +308,7 @@ class Widget DI::l10n()->t('All Protocols'), $baseurl, $nets, - $selected, + $selected ); } @@ -344,7 +338,7 @@ class Widget DI::l10n()->t('Everything'), $baseurl, $terms, - $selected, + $selected ); } @@ -375,7 +369,7 @@ class Widget DI::l10n()->t('Everything'), $baseurl, $terms, - $selected, + $selected ); } @@ -432,7 +426,7 @@ class Widget '$nickname' => $nickname, '$linkmore' => $total > 5 ? 'true' : '', '$more' => DI::l10n()->t('show more'), - '$contacts' => $entries, + '$contacts' => $entries ]); } @@ -481,7 +475,7 @@ class Widget $ret = []; - $cachekey = 'Widget::postedByYear' . $uid . '-' . (int) $wall; + $cachekey = 'Widget::postedByYear' . $uid . '-' . (int)$wall; $dthen = DI::cache()->get($cachekey); if (empty($dthen)) { $dthen = Item::firstPostDate($uid, $wall); @@ -511,7 +505,7 @@ class Widget $dend = substr($dnow, 0, 8) . Temporal::getDaysInMonth(intval($dnow), intval(substr($dnow, 5))); $start_month = DateTimeFormat::utc($dstart, 'Y-m-d'); $end_month = DateTimeFormat::utc($dend, 'Y-m-d'); - $str = DI::l10n()->formatDateTimeByPattern($dnow, 'LLLL'); + $str = DI::l10n()->getDay(DateTimeFormat::utc($dnow, 'F')); if (empty($ret[$dyear])) { $ret[$dyear] = []; @@ -541,7 +535,7 @@ class Widget '$onthisdate' => DI::l10n()->t('On this date'), '$thisday' => $thisday, '$nextday' => $nextday, - '$cutoffday' => $cutoffday, + '$cutoffday' => $cutoffday ]); return $o; @@ -572,7 +566,7 @@ class Widget DI::l10n()->t('All'), $base, $accounts, - $accounttype, + $accounttype ); } @@ -617,9 +611,9 @@ class Widget $widget_timelineorder = json_decode(DI::pConfig()->get($uid, 'system', 'widget_timeline_order')); if (!empty($widget_timelineorder)) { $tmp = []; - foreach ($widget_timelineorder as $order) { - foreach ($channels as $channel) { - if ($channel['ref'] == $order) { + foreach($widget_timelineorder as $order) { + foreach($channels as $channel) { + if($channel['ref'] == $order) { $tmp[] = $channel; } } @@ -634,7 +628,7 @@ class Widget '', $base, $channels, - $channelname, + $channelname ); } } diff --git a/src/Content/Widget/TagCloud.php b/src/Content/Widget/TagCloud.php index 5c6972ba93..c285b3cb83 100644 --- a/src/Content/Widget/TagCloud.php +++ b/src/Content/Widget/TagCloud.php @@ -38,7 +38,7 @@ class TagCloud $r = self::tagadelic($uid, $count, $owner_id, $flags, $type); if (count($r)) { $contact = DBA::selectFirst('contact', ['url'], ['uid' => $uid, 'self' => true]); - $url = DI::baseUrl()->remove($contact['url']); + $url = DI::baseUrl()->remove($contact['url']); $tags = []; foreach ($r as $rr) { @@ -50,9 +50,9 @@ class TagCloud } $tpl = Renderer::getMarkupTemplate('widget/tagcloud.tpl'); - $o = Renderer::replaceMacros($tpl, [ + $o = Renderer::replaceMacros($tpl, [ '$title' => DI::l10n()->t('Tags'), - '$tags' => $tags, + '$tags' => $tags ]); } return $o; @@ -74,7 +74,7 @@ class TagCloud private static function tagadelic($uid, $count = 0, $owner_id = 0, $flags = '', $type = Tag::HASHTAG) { $sql_options = Item::getPermissionsSQLByUserId($uid, 'post-user-view'); - $limit = $count ? sprintf('LIMIT %d', intval($count)) : ''; + $limit = $count ? sprintf('LIMIT %d', intval($count)) : ''; if ($flags) { if ($flags === 'wall') { @@ -87,14 +87,13 @@ class TagCloud } // Fetch tags - $tag_stmt = DBA::p( - "SELECT `name`, COUNT(`name`) AS `total` FROM `tag-search-view` + $tag_stmt = DBA::p("SELECT `name`, COUNT(`name`) AS `total` FROM `tag-search-view` LEFT JOIN `post-user-view` ON `tag-search-view`.`uri-id` = `post-user-view`.`uri-id` AND `tag-search-view`.`uid` = `post-user-view`.`uid` WHERE `tag-search-view`.`uid` = ? AND `post-user-view`.`visible` AND NOT `post-user-view`.`deleted` $sql_options GROUP BY `name` ORDER BY `total` DESC $limit", - $uid, + $uid ); if (!DBA::isResult($tag_stmt)) { return []; @@ -114,9 +113,9 @@ class TagCloud private static function tagCalc(array $arr) { $tags = []; - $min = 1000000000.0; - $max = -1000000000.0; - $x = 0; + $min = 1000000000.0; + $max = -1000000000.0; + $x = 0; if (!$arr) { return []; @@ -126,15 +125,15 @@ class TagCloud $tags[$x][0] = $rr['name']; $tags[$x][1] = log($rr['total']); $tags[$x][2] = 0; - $min = min($min, $tags[$x][1]); - $max = max($max, $tags[$x][1]); - $x++; + $min = min($min, $tags[$x][1]); + $max = max($max, $tags[$x][1]); + $x ++; } usort($tags, [self::class, 'tagsSort']); $range = max(0.01, $max - $min) * 1.0001; - for ($x = 0; $x < count($tags); $x++) { + for ($x = 0; $x < count($tags); $x ++) { $tags[$x][2] = 1 + floor(9 * ($tags[$x][1] - $min) / $range); } @@ -151,6 +150,9 @@ class TagCloud */ private static function tagsSort($a, $b) { - return strtolower($a[0]) <=> strtolower($b[0]); + if (strtolower($a[0]) == strtolower($b[0])) { + return 0; + } + return ((strtolower($a[0]) < strtolower($b[0])) ? -1 : 1); } } diff --git a/src/Core/Addon.php b/src/Core/Addon.php index 47489e89ca..3f3f15df6a 100644 --- a/src/Core/Addon.php +++ b/src/Core/Addon.php @@ -13,18 +13,18 @@ use Friendica\Util\Strings; /** * Some functions to handle addons * - * @deprecated 2026.01 Use implementation of `Friendica\Core\Addon\AddonHelper` instead + * @deprecated 2025.07 Use implementation of `Friendica\Core\Addon\AddonHelper` instead */ class Addon { /** * The addon sub-directory * - * @deprecated 2026.01 Use `Friendica\Core\Addon\AddonHelper::getAddonPath()` instead + * @deprecated 2025.07 Use `Friendica\Core\Addon\AddonHelper::getAddonPath()` instead * * @var string */ - public const DIRECTORY = 'addon'; + const DIRECTORY = 'addon'; /** * List of the names of enabled addons @@ -38,22 +38,22 @@ 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 2026.01 Use `Friendica\Core\Addon\AddonHelper::getAvailableAddons()` instead + * @deprecated 2025.07 Use `Friendica\Core\Addon\AddonHelper::getAvailableAddons()` instead * * @return array * @throws \Exception */ public static function getAvailableList(): array { - @trigger_error('Class `' . self::class . '` is deprecated since 2026.01 and will be removed after 5 months, use implementation of `Friendica\Core\Addon\AddonHelper` instead.', E_USER_DEPRECATED); + @trigger_error('Class `' . __CLASS__ . '` is deprecated since 2025.07 and will be removed after 5 months, use implementation of `Friendica\Core\Addon\AddonHelper` instead.', E_USER_DEPRECATED); $addons = []; $files = glob('addon/*/'); if (is_array($files)) { foreach ($files as $file) { if (is_dir($file)) { - [$tmp, $addon] = array_map('trim', explode('/', $file)); - $info = self::getInfo($addon); + list($tmp, $addon) = array_map('trim', explode('/', $file)); + $info = self::getInfo($addon); if (DI::config()->get('system', 'show_unsupported_addons') || strtolower($info['status']) != 'unsupported' @@ -72,14 +72,14 @@ 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 2026.01 Use `Friendica\Core\Addon\AddonHelper::getEnabledAddonsWithAdminSettings()` instead + * @deprecated 2025.07 Use `Friendica\Core\Addon\AddonHelper::getEnabledAddonsWithAdminSettings()` instead * * @return array * @throws \Exception */ public static function getAdminList(): array { - @trigger_error('Class `' . self::class . '` is deprecated since 2026.01 and will be removed after 5 months, use implementation of `Friendica\Core\Addon\AddonHelper` instead.', E_USER_DEPRECATED); + @trigger_error('Class `' . __CLASS__ . '` is deprecated since 2025.07 and will be removed after 5 months, use implementation of `Friendica\Core\Addon\AddonHelper` instead.', E_USER_DEPRECATED); $addons_admin = []; $addons = array_filter(DI::config()->get('addons') ?? []); @@ -93,7 +93,7 @@ class Addon $addons_admin[$name] = [ 'url' => 'admin/addons/' . $name, 'name' => $name, - 'class' => 'addon', + 'class' => 'addon' ]; } @@ -111,11 +111,11 @@ 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 2026.01 Use `Friendica\Core\Addon\AddonHelper::loadAddons()` instead + * @deprecated 2025.07 Use `Friendica\Core\Addon\AddonHelper::loadAddons()` instead */ public static function loadAddons() { - @trigger_error('Class `' . self::class . '` is deprecated since 2026.01 and will be removed after 5 months, use implementation of `Friendica\Core\Addon\AddonHelper` instead.', E_USER_DEPRECATED); + @trigger_error('Class `' . __CLASS__ . '` is deprecated since 2025.07 and will be removed after 5 months, use implementation of `Friendica\Core\Addon\AddonHelper` instead.', E_USER_DEPRECATED); self::$addons = array_keys(array_filter(DI::config()->get('addons') ?? [])); } @@ -123,7 +123,7 @@ class Addon /** * uninstalls an addon. * - * @deprecated 2026.01 Use `Friendica\Core\Addon\AddonHelper::uninstallAddon()` instead + * @deprecated 2025.07 Use `Friendica\Core\Addon\AddonHelper::uninstallAddon()` instead * * @param string $addon name of the addon * @return void @@ -131,7 +131,7 @@ class Addon */ public static function uninstall(string $addon) { - @trigger_error('Class `' . self::class . '` is deprecated since 2026.01 and will be removed after 5 months, use implementation of `Friendica\Core\Addon\AddonHelper` instead.', E_USER_DEPRECATED); + @trigger_error('Class `' . __CLASS__ . '` is deprecated since 2025.07 and will be removed after 5 months, use implementation of `Friendica\Core\Addon\AddonHelper` instead.', E_USER_DEPRECATED); $addon = Strings::sanitizeFilePathItem($addon); @@ -153,7 +153,7 @@ class Addon /** * installs an addon. * - * @deprecated 2026.01 Use `Friendica\Core\Addon\AddonHelper::installAddon()` instead + * @deprecated 2025.07 Use `Friendica\Core\Addon\AddonHelper::installAddon()` instead * * @param string $addon name of the addon * @return bool @@ -161,7 +161,7 @@ class Addon */ public static function install(string $addon): bool { - @trigger_error('Class `' . self::class . '` is deprecated since 2026.01 and will be removed after 5 months, use implementation of `Friendica\Core\Addon\AddonHelper` instead.', E_USER_DEPRECATED); + @trigger_error('Class `' . __CLASS__ . '` is deprecated since 2025.07 and will be removed after 5 months, use implementation of `Friendica\Core\Addon\AddonHelper` instead.', E_USER_DEPRECATED); $addon = Strings::sanitizeFilePathItem($addon); @@ -195,7 +195,7 @@ class Addon /** * reload all updated addons * - * @deprecated 2026.01 Use `Friendica\Core\Addon\AddonHelper::reloadAddons()` instead + * @deprecated 2025.07 Use `Friendica\Core\Addon\AddonHelper::reloadAddons()` instead * * @return void * @throws \Exception @@ -203,7 +203,7 @@ class Addon */ public static function reload() { - @trigger_error('Class `' . self::class . '` is deprecated since 2026.01 and will be removed after 5 months, use implementation of `Friendica\Core\Addon\AddonHelper` instead.', E_USER_DEPRECATED); + @trigger_error('Class `' . __CLASS__ . '` is deprecated since 2025.07 and will be removed after 5 months, use implementation of `Friendica\Core\Addon\AddonHelper` instead.', E_USER_DEPRECATED); $addons = array_filter(DI::config()->get('addons') ?? []); @@ -236,7 +236,7 @@ class Addon * * * *\endcode * - * @deprecated 2026.01 Use `Friendica\Core\Addon\AddonHelper::getAddonInfo()` instead + * @deprecated 2025.07 Use `Friendica\Core\Addon\AddonHelper::getAddonInfo()` instead * * @param string $addon the name of the addon * @return array with the addon information @@ -244,7 +244,7 @@ class Addon */ public static function getInfo(string $addon): array { - @trigger_error('Class `' . self::class . '` is deprecated since 2026.01 and will be removed after 5 months, use implementation of `Friendica\Core\Addon\AddonHelper` instead.', E_USER_DEPRECATED); + @trigger_error('Class `' . __CLASS__ . '` is deprecated since 2025.07 and will be removed after 5 months, use implementation of `Friendica\Core\Addon\AddonHelper` instead.', E_USER_DEPRECATED); $addon = Strings::sanitizeFilePathItem($addon); @@ -254,7 +254,7 @@ class Addon 'author' => [], 'maintainer' => [], 'version' => "", - 'status' => "", + 'status' => "" ]; if (!is_file("addon/$addon/$addon.php")) { @@ -277,8 +277,8 @@ class Addon continue; } - [$type, $v] = $addon_info; - $type = strtolower($type); + list($type, $v) = $addon_info; + $type = strtolower($type); if ($type == "author" || $type == "maintainer") { $r = preg_match("|([^<]+)<([^>]+)>|", $v, $m); if ($r) { @@ -300,14 +300,14 @@ class Addon /** * Checks if the provided addon is enabled * - * @deprecated 2026.01 Use `Friendica\Core\Addon\AddonHelper::isAddonEnabled()` instead + * @deprecated 2025.07 Use `Friendica\Core\Addon\AddonHelper::isAddonEnabled()` instead * * @param string $addon * @return boolean */ public static function isEnabled(string $addon): bool { - @trigger_error('Class `' . self::class . '` is deprecated since 2026.01 and will be removed after 5 months, use implementation of `Friendica\Core\Addon\AddonHelper` instead.', E_USER_DEPRECATED); + @trigger_error('Class `' . __CLASS__ . '` is deprecated since 2025.07 and will be removed after 5 months, use implementation of `Friendica\Core\Addon\AddonHelper` instead.', E_USER_DEPRECATED); return in_array($addon, self::$addons); } @@ -315,13 +315,13 @@ class Addon /** * Returns a list of the enabled addon names * - * @deprecated 2026.01 Use `Friendica\Core\Addon\AddonHelper::getEnabledAddons()` instead + * @deprecated 2025.07 Use `Friendica\Core\Addon\AddonHelper::getEnabledAddons()` instead * * @return array */ public static function getEnabledList(): array { - @trigger_error('Class `' . self::class . '` is deprecated since 2026.01 and will be removed after 5 months, use implementation of `Friendica\Core\Addon\AddonHelper` instead.', E_USER_DEPRECATED); + @trigger_error('Class `' . __CLASS__ . '` is deprecated since 2025.07 and will be removed after 5 months, use implementation of `Friendica\Core\Addon\AddonHelper` instead.', E_USER_DEPRECATED); return self::$addons; } @@ -329,14 +329,14 @@ class Addon /** * Returns the list of non-hidden enabled addon names * - * @deprecated 2026.01 Use `Friendica\Core\Addon\AddonHelper::getVisibleEnabledAddons()` instead + * @deprecated 2025.07 Use `Friendica\Core\Addon\AddonHelper::getVisibleEnabledAddons()` instead * * @return array * @throws \Exception */ public static function getVisibleList(): array { - @trigger_error('Class `' . self::class . '` is deprecated since 2026.01 and will be removed after 5 months, use implementation of `Friendica\Core\Addon\AddonHelper` instead.', E_USER_DEPRECATED); + @trigger_error('Class `' . __CLASS__ . '` is deprecated since 2025.07 and will be removed after 5 months, use implementation of `Friendica\Core\Addon\AddonHelper` instead.', E_USER_DEPRECATED); $visible_addons = []; $addons = array_filter(DI::config()->get('addons') ?? []); diff --git a/src/Core/Addon/AddonInfo.php b/src/Core/Addon/AddonInfo.php index 2792b3af4b..d0b46f11c6 100644 --- a/src/Core/Addon/AddonInfo.php +++ b/src/Core/Addon/AddonInfo.php @@ -51,8 +51,8 @@ final class AddonInfo continue; } - [$type, $v] = $addon_info; - $type = strtolower($type); + list($type, $v) = $addon_info; + $type = strtolower($type); if ($type === 'author' || $type === 'maintainer') { $r = preg_match("|([^<]+)<([^>]+)>|", $v, $m); diff --git a/src/Core/Addon/Capability/ICanLoadAddons.php b/src/Core/Addon/Capability/ICanLoadAddons.php index d3e0faabf2..6005d0f81e 100644 --- a/src/Core/Addon/Capability/ICanLoadAddons.php +++ b/src/Core/Addon/Capability/ICanLoadAddons.php @@ -10,14 +10,14 @@ namespace Friendica\Core\Addon\Capability; /** * Interface for loading Addons specific content * - * @deprecated 2026.01 Use implementation of `\Friendica\Core\Addon\AddonHelper` instead. + * @deprecated 2025.07 Use implementation of `\Friendica\Core\Addon\AddonHelper` instead. */ interface ICanLoadAddons { /** * Returns a merged config array of all active addons for a given config-name * - * @deprecated 2026.01 Use `\Friendica\Core\Addon\AddonHelper::getAddonDependencyConfig()` instead. + * @deprecated 2025.07 Use `\Friendica\Core\Addon\AddonHelper::getAddonDependencyConfig()` instead. * * @param string $configName The config-name (config-file at the static directory, like 'hooks' => '{addon}/static/hooks.config.php) * diff --git a/src/Core/Addon/Model/AddonLoader.php b/src/Core/Addon/Model/AddonLoader.php index 1c4f3f91a7..aab98c20d5 100644 --- a/src/Core/Addon/Model/AddonLoader.php +++ b/src/Core/Addon/Model/AddonLoader.php @@ -15,11 +15,11 @@ use Friendica\Util\Strings; use Psr\Log\LoggerInterface; /** - * @deprecated 2026.01 Use implementation of `\Friendica\Core\Addon\AddonHelper` instead. + * @deprecated 2025.07 Use implementation of `\Friendica\Core\Addon\AddonHelper` instead. */ class AddonLoader implements ICanLoadAddons { - public const STATIC_PATH = 'static'; + const STATIC_PATH = 'static'; /** @var string */ protected $basePath; /** @var IManageConfigValues */ @@ -27,18 +27,18 @@ class AddonLoader implements ICanLoadAddons public function __construct(string $basePath, IManageConfigValues $config) { - @trigger_error('Class `' . self::class . '` is deprecated since 2026.01 and will be removed after 5 months, use implementation of `Friendica\Core\Addon\AddonHelper` instead.', E_USER_DEPRECATED); + @trigger_error('Class `' . __CLASS__ . '` is deprecated since 2025.07 and will be removed after 5 months, use implementation of `Friendica\Core\Addon\AddonHelper` instead.', E_USER_DEPRECATED); $this->basePath = $basePath; $this->config = $config; } /** - * @deprecated 2026.01 Use `\Friendica\Core\Addon\AddonHelper::getAddonDependencyConfig()` instead. + * @deprecated 2025.07 Use `\Friendica\Core\Addon\AddonHelper::getAddonDependencyConfig()` instead. */ public function getActiveAddonConfig(string $configName): array { - @trigger_error('Class `' . self::class . '` is deprecated since 2026.01 and will be removed after 5 months, use `\Friendica\Core\Addon\AddonHelper::getAddonDependencyConfig()` instead.', E_USER_DEPRECATED); + @trigger_error('Class `' . __CLASS__ . '` is deprecated since 2025.07 and will be removed after 5 months, use `\Friendica\Core\Addon\AddonHelper::getAddonDependencyConfig()` instead.', E_USER_DEPRECATED); $addons = array_keys(array_filter($this->config->get('addons') ?? [])); $returnConfig = []; @@ -63,14 +63,14 @@ class AddonLoader implements ICanLoadAddons foreach ($config as $classname => $rule) { if ($classname === LoggerInterface::class) { @trigger_error(sprintf( - 'Providing a strategy for `%s` is deprecated since 2026.01 and will stop working in 5 months, please provide an implementation for `%s` via `dependency.config.php` and remove the `strategies.config.php` file in the `%s` addon.', + 'Providing a strategy for `%s` is deprecated since 2025.07 and will stop working in 5 months, please provide an implementation for `%s` via `dependency.config.php` and remove the `strategies.config.php` file in the `%s` addon.', $classname, LoggerFactory::class, $addonName, ), \E_USER_DEPRECATED); } else { @trigger_error(sprintf( - 'Providing strategies for `%s` via addons is deprecated since 2026.01 and will stop working in 5 months, please stop using this and remove the `strategies.config.php` file in the `%s` addon.', + 'Providing strategies for `%s` via addons is deprecated since 2025.07 and will stop working in 5 months, please stop using this and remove the `strategies.config.php` file in the `%s` addon.', $classname, $addonName, ), \E_USER_DEPRECATED); diff --git a/src/Core/Cache/Type/ArrayCache.php b/src/Core/Cache/Type/ArrayCache.php index 9391825b10..148210b4e8 100644 --- a/src/Core/Cache/Type/ArrayCache.php +++ b/src/Core/Cache/Type/ArrayCache.php @@ -16,7 +16,7 @@ use Friendica\Core\Cache\Enum; class ArrayCache extends AbstractCache implements ICanCacheInMemory { use CompareDeleteTrait; - public const NAME = 'array'; + const NAME = 'array'; /** @var array Array with the cached data */ protected $cachedData = []; @@ -34,7 +34,10 @@ class ArrayCache extends AbstractCache implements ICanCacheInMemory */ public function get(string $key) { - return $this->cachedData[$key] ?? null; + if (isset($this->cachedData[$key])) { + return $this->cachedData[$key]; + } + return null; } /** diff --git a/src/Core/Config/Util/ConfigFileManager.php b/src/Core/Config/Util/ConfigFileManager.php index 023555d5c3..96c416e4de 100644 --- a/src/Core/Config/Util/ConfigFileManager.php +++ b/src/Core/Config/Util/ConfigFileManager.php @@ -25,21 +25,21 @@ class ConfigFileManager * * @var string */ - public const CONFIG_HTCONFIG = 'htconfig'; + const CONFIG_HTCONFIG = 'htconfig'; /** * The config file, where overrides per admin page/console are saved at * * @var string */ - public const CONFIG_DATA_FILE = 'node.config.php'; + const CONFIG_DATA_FILE = 'node.config.php'; /** * The sample string inside the configs, which shouldn't get loaded * * @var string */ - public const SAMPLE_END = '-sample'; + const SAMPLE_END = '-sample'; /** * @var string @@ -161,10 +161,10 @@ class ConfigFileManager */ public function loadAddonConfig(string $name): array { - $filepath = $this->addonDir . DIRECTORY_SEPARATOR // /var/www/html/addon/ - . $name . DIRECTORY_SEPARATOR // openstreetmap/ - . 'config' . DIRECTORY_SEPARATOR // config/ - . $name . ".config.php"; // openstreetmap.config.php + $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 []; @@ -182,8 +182,8 @@ class ConfigFileManager */ protected function loadEnvConfig(): array { - $filepath = $this->staticDir . DIRECTORY_SEPARATOR // /var/www/html/static/ - . "env.config.php"; // env.config.php + $filepath = $this->staticDir . DIRECTORY_SEPARATOR . // /var/www/html/static/ + "env.config.php"; // env.config.php if (!file_exists($filepath)) { return []; @@ -220,9 +220,9 @@ class ConfigFileManager $sampleEnd = self::SAMPLE_END . ($ini ? '.ini.php' : '.config.php'); foreach ($files as $filename) { - if (fnmatch($filePattern, $filename) - && substr_compare($filename, $sampleEnd, -strlen($sampleEnd)) - && $filename !== self::CONFIG_DATA_FILE) { + if (fnmatch($filePattern, $filename) && + substr_compare($filename, $sampleEnd, -strlen($sampleEnd)) && + $filename !== self::CONFIG_DATA_FILE) { $found[] = $this->configDir . '/' . $filename; } } @@ -257,6 +257,7 @@ class ConfigFileManager // map the legacy configuration structure to the current structure foreach ($htConfigCategories as $htConfigCategory) { + /** @phpstan-ignore-next-line $a->config could be modified after `include $fullName` */ if (is_array($a->config[$htConfigCategory])) { $keys = array_keys($a->config[$htConfigCategory]); diff --git a/src/Core/Hooks/Util/StrategiesFileManager.php b/src/Core/Hooks/Util/StrategiesFileManager.php index cb4bcc1b52..2ea0e473a4 100644 --- a/src/Core/Hooks/Util/StrategiesFileManager.php +++ b/src/Core/Hooks/Util/StrategiesFileManager.php @@ -24,9 +24,9 @@ class StrategiesFileManager * The default hook-file-key of strategies * -> it's an empty string to cover empty/missing config values */ - public const STRATEGY_DEFAULT_KEY = ''; - public const STATIC_DIR = 'static'; - public const CONFIG_NAME = 'strategies'; + const STRATEGY_DEFAULT_KEY = ''; + const STATIC_DIR = 'static'; + const CONFIG_NAME = 'strategies'; private IManageConfigValues $configuration; protected array $config = []; @@ -84,7 +84,7 @@ class StrategiesFileManager } /** - * @deprecated 2026.01 Providing strategies via addons is deprecated and will be removed in 5 months. + * @deprecated 2025.07 Providing strategies via addons is deprecated and will be removed in 5 months. */ $this->config = array_merge_recursive($config, $this->getActiveAddonConfig()); } @@ -113,14 +113,14 @@ class StrategiesFileManager foreach ($config as $classname => $rule) { if ($classname === LoggerInterface::class) { @trigger_error(sprintf( - 'Providing a strategy for `%s` is deprecated since 2026.01 and will stop working in 5 months, please provide an implementation for `%s` via `dependency.config.php` and remove the `strategies.config.php` file in the `%s` addon.', + 'Providing a strategy for `%s` is deprecated since 2025.07 and will stop working in 5 months, please provide an implementation for `%s` via `dependency.config.php` and remove the `strategies.config.php` file in the `%s` addon.', $classname, LoggerFactory::class, $addonName, ), \E_USER_DEPRECATED); } else { @trigger_error(sprintf( - 'Providing strategies for `%s` via addons is deprecated since 2026.01 and will stop working in 5 months, please stop using this and remove the `strategies.config.php` file in the `%s` addon.', + 'Providing strategies for `%s` via addons is deprecated since 2025.07 and will stop working in 5 months, please stop using this and remove the `strategies.config.php` file in the `%s` addon.', $classname, $addonName, ), \E_USER_DEPRECATED); diff --git a/src/Core/L10n.php b/src/Core/L10n.php index 312ed1d5f7..33fa658836 100644 --- a/src/Core/L10n.php +++ b/src/Core/L10n.php @@ -7,13 +7,10 @@ namespace Friendica\Core; -use DateTime; use Friendica\Core\Config\Capability\IManageConfigValues; use Friendica\Core\Session\Capability\IHandleSessions; use Friendica\Database\Database; use Friendica\Util\Strings; -use IntlDateFormatter; -use Locale; /** * Provide Language, Translation, and Localization functions to the application @@ -22,9 +19,9 @@ use Locale; class L10n { /** @var string The default language */ - public const DEFAULT = 'en'; + const DEFAULT = 'en'; /** @var string[] The language names in their language */ - public const LANG_NAMES = [ + const LANG_NAMES = [ 'ar' => 'العربية', 'bg' => 'Български', 'ca' => 'Català', @@ -54,13 +51,13 @@ class L10n 'zh-cn' => '简体中文', ]; - public const LANG_PARENTS = [ + const LANG_PARENTS = [ 'en-gb' => 'en', 'da-dk' => 'da', 'fi-fi' => 'fi', - 'nb-no' => 'nb', 'pt-br' => 'pt', 'zh-cn' => 'zh', + 'nb-no' => 'nb', 'pt-br' => 'pt', 'zh-cn' => 'zh' ]; /** @var string Undetermined language */ - public const UNDETERMINED_LANGUAGE = 'un'; + const UNDETERMINED_LANGUAGE = 'un'; /** * A string indicating the current language used for translation: @@ -71,8 +68,6 @@ class L10n */ private $lang = ''; - private string $locale = ''; - /** * An array of translation strings whose key is the neutral english message. * @@ -88,16 +83,13 @@ class L10n * @var IManageConfigValues */ private $config; - private IHandleSessions $session; public function __construct(IManageConfigValues $config, Database $dba, IHandleSessions $session, array $server, array $get) { - $this->dba = $dba; - $this->config = $config; - $this->session = $session; + $this->dba = $dba; + $this->config = $config; $this->loadTranslationTable(L10n::detectLanguage($server, $get, $config->get('system', 'language', self::DEFAULT))); - $this->setLocale($server); $this->setSessionVariable($session); $this->setLangFromSession($session); } @@ -112,24 +104,6 @@ class L10n return $this->lang; } - /** - * Set the instance locale based on the HTTP Accept-Language header. - * - * Reads the `HTTP_ACCEPT_LANGUAGE` value from the provided server array - * and stores the accepted locale string in `$this->locale` using - * `Locale::acceptFromHttp()`. - * - * @param array $server The $_SERVER-like array containing HTTP headers - * @return void - */ - private function setLocale(array $server) - { - if (!isset($server['HTTP_ACCEPT_LANGUAGE'])) { - return; - } - $this->locale = Locale::acceptFromHttp($server['HTTP_ACCEPT_LANGUAGE']); - } - /** * Sets the language session variable */ @@ -137,7 +111,6 @@ class L10n { if ($session->get('authenticated') && !$session->get('language')) { $session->set('language', $this->lang); - $session->set('locale', $this->locale); // we haven't loaded user data yet, but we need user language if ($session->get('uid')) { $user = $this->dba->selectFirst('user', ['language'], ['uid' => $_SESSION['uid']]); @@ -246,7 +219,7 @@ class L10n $res = preg_match( '/^([a-z]{1,8}(?:-[a-z]{1,8})*)(?:;\s*q=(0(?:\.[0-9]{1,3})?|1(?:\.0{1,3})?))?$/i', $acceptedLanguage, - $matches, + $matches ); // Invalid language? -> skip @@ -259,7 +232,7 @@ class L10n // determine the quality of the guess if (isset($matches[2])) { - $lang_quality = (float) $matches[2]; + $lang_quality = (float)$matches[2]; } else { // fallback so without a quality parameter, it's probably the best $lang_quality = 1; @@ -456,7 +429,7 @@ class L10n if (in_array('cld2', get_loaded_extensions())) { $additional_langs = array_merge( $additional_langs, - ['dv', 'kn', 'lo', 'ml', 'or', 'pa', 'sd', 'si', 'te', 'yi'], + ['dv', 'kn', 'lo', 'ml', 'or', 'pa', 'sd', 'si', 'te', 'yi'] ); } @@ -496,17 +469,6 @@ class L10n return $languages; } - /** - * Converts e.g. en-gb to en_GB and da-dk to da_DK, which is the format some other systems expect - * - * @param string $lang - * @return string - * */ - public function langToLocaleCode($lang) - { - return preg_replace_callback("/([a-z]+)-([a-z]+)/", fn ($m) => $m[1] . "_" . strtoupper($m[2]), $lang); - } - /** * Convert the language code to ISO639-1 * It also converts old codes to their new counterparts. @@ -537,6 +499,52 @@ class L10n return $code; } + /** + * Translate days and months names. + * + * @param string $s String with day or month name. + * @return string Translated string. + */ + public function getDay(string $s): string + { + $ret = str_replace( + ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'], + [$this->t('Monday'), $this->t('Tuesday'), $this->t('Wednesday'), $this->t('Thursday'), $this->t('Friday'), $this->t('Saturday'), $this->t('Sunday')], + $s + ); + + $ret = str_replace( + ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], + [$this->t('January'), $this->t('February'), $this->t('March'), $this->t('April'), $this->t('May'), $this->t('June'), $this->t('July'), $this->t('August'), $this->t('September'), $this->t('October'), $this->t('November'), $this->t('December')], + $ret + ); + + return $ret; + } + + /** + * Translate short days and months names. + * + * @param string $s String with short day or month name. + * @return string Translated string. + */ + public function getDayShort(string $s): string + { + $ret = str_replace( + ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'], + [$this->t('Mon'), $this->t('Tue'), $this->t('Wed'), $this->t('Thu'), $this->t('Fri'), $this->t('Sat'), $this->t('Sun')], + $s + ); + + $ret = str_replace( + ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], + [$this->t('Jan'), $this->t('Feb'), $this->t('Mar'), $this->t('Apr'), $this->t('May'), $this->t('Jun'), $this->t('Jul'), $this->t('Aug'), $this->t('Sep'), $this->t('Oct'), $this->t('Nov'), $this->t('Dec')], + $ret + ); + + return $ret; + } + /** * Creates a new L10n instance based on the given langauge * @@ -556,126 +564,4 @@ class L10n $newL10n->loadTranslationTable($lang); return $newL10n; } - - /** - * Format a date/time string using RELATIVE_FULL date and SHORT time. - * - * This will produce relative strings where supported by the ICU implementation - * (for example "yesterday", "in 2 days") according to the current locale. - * - * @param string $datestring Date/time string (e.g. ISO 8601) - * @return string Formatted relative date/time string - * @throws \Exception If the date string cannot be parsed into a DateTime - */ - public function relativeDateTime(string $datestring): string - { - return $this->formatDateTime($datestring, IntlDateFormatter::RELATIVE_FULL, IntlDateFormatter::SHORT); - } - - /** - * Format a date/time string using FULL date and SHORT time according to current locale. - * - * @param string $datestring Date/time string (e.g. ISO 8601) - * @return string Formatted date/time string - * @throws \Exception If the date string cannot be parsed into a DateTime - */ - public function fullDateTime(string $datestring): string - { - return $this->formatDateTime($datestring, IntlDateFormatter::FULL, IntlDateFormatter::SHORT); - } - - /** - * Format a date/time string using MEDIUM date and SHORT time according to current locale. - * - * @param string $datestring Date/time string - * @return string Formatted date/time string - * @throws \Exception If the date string cannot be parsed into a DateTime - */ - public function dateTime(string $datestring): string - { - return $this->formatDateTime($datestring, IntlDateFormatter::MEDIUM, IntlDateFormatter::SHORT); - } - - /** - * Format a date string (date only) using FULL date format according to current locale. - * - * @param string $datestring Date string - * @return string Formatted date string - * @throws \Exception If the date string cannot be parsed into a DateTime - */ - public function fullDate(string $datestring): string - { - return $this->formatDateTime($datestring, IntlDateFormatter::FULL, IntlDateFormatter::NONE); - } - - /** - * Format a date string (date only) using MEDIUM date format according to current locale. - * - * @param string $datestring Date string - * @return string Formatted date string - * @throws \Exception If the date string cannot be parsed into a DateTime - */ - public function mediumDate(string $datestring): string - { - return $this->formatDateTime($datestring, IntlDateFormatter::MEDIUM, IntlDateFormatter::NONE); - } - - /** - * Format a date string (date only) using LONG date format according to current locale. - * - * @param string $datestring Date string - * @return string Formatted date string - * @throws \Exception If the date string cannot be parsed into a DateTime - */ - public function longDate(string $datestring): string - { - return $this->formatDateTime($datestring, IntlDateFormatter::LONG, IntlDateFormatter::NONE); - } - - /** - * Format a date/time string using a custom ICU pattern. - * @see https://unicode-org.github.io/icu/userguide/format_parse/datetime/#datetime-format-syntax - * - * The provided pattern is passed directly to the underlying - * `IntlDateFormatter` instance. This allows callers to specify - * arbitrary formatting rules beyond the standard date/time styles. - * - * @param string $datestring Date/time string (e.g. ISO 8601) - * @param string $pattern ICU date/time pattern - * @return string Formatted date/time string - * @throws \Exception If the date string cannot be parsed into a DateTime - */ - public function formatDateTimeByPattern(string $datestring, string $pattern): string - { - return $this->formatDateTime($datestring, IntlDateFormatter::NONE, IntlDateFormatter::NONE, $pattern); - } - - /** - * General date/time formatting helper. - * - * Creates an IntlDateFormatter using the instance locale and the timezone - * stored in session (if any) and formats the provided date/time string. - * - * @see https://unicode-org.github.io/icu/userguide/format_parse/datetime/#datetime-format-syntax for details on the supported pattern syntax when using the $pattern parameter. - * - * @param string $datestring Date/time string (e.g. ISO 8601) - * @param int $dateType One of IntlDateFormatter::SHORT|MEDIUM|LONG|FULL|NONE - * @param int $timeType One of IntlDateFormatter::SHORT|MEDIUM|LONG|FULL|NONE - * @param string $pattern Optional ICU date/time pattern to use instead of the standard styles - * @return string Formatted date/time string - * @throws \Exception If the date string cannot be parsed into a DateTime - */ - public function formatDateTime(string $datestring, int $dateType, int $timeType, ?string $pattern = null): string - { - $formatter = new IntlDateFormatter( - $this->session->get('language') ?? $this->locale ?: $this->config->get('system', 'language', 'en_US'), - $dateType, - $timeType, - $this->session->get('timezone') ?? null, - null, - $pattern, - ); - - return $formatter->format(new DateTime($datestring)); - } } diff --git a/src/Core/Lock/Type/CacheLock.php b/src/Core/Lock/Type/CacheLock.php index df40aec765..c7fa75e021 100644 --- a/src/Core/Lock/Type/CacheLock.php +++ b/src/Core/Lock/Type/CacheLock.php @@ -19,7 +19,7 @@ class CacheLock extends AbstractLock * * @var string */ - public const CACHE_PREFIX = 'lock:'; + const CACHE_PREFIX = 'lock:'; /** * @var ICanCacheInMemory @@ -50,7 +50,7 @@ class CacheLock extends AbstractLock do { $lock = $this->cache->get($lockKey); // When we do want to lock something that was already locked by us. - if ((int) $lock == getmypid()) { + if ((int)$lock == getmypid()) { $got_lock = true; } @@ -66,7 +66,7 @@ class CacheLock extends AbstractLock } if (!$got_lock && ($timeout > 0)) { - usleep(random_int(10000, 200000)); + usleep(rand(10000, 200000)); } } while (!$got_lock && ((time() - $start) < $timeout)); } catch (CachePersistenceException $exception) { diff --git a/src/Core/Lock/Type/DatabaseLock.php b/src/Core/Lock/Type/DatabaseLock.php index 39e33d2d4f..d05871689c 100644 --- a/src/Core/Lock/Type/DatabaseLock.php +++ b/src/Core/Lock/Type/DatabaseLock.php @@ -51,7 +51,7 @@ class DatabaseLock extends AbstractLock do { $this->dba->lock('locks'); $lock = $this->dba->selectFirst('locks', ['locked', 'pid'], [ - '`name` = ? AND `expires` >= ?', $key,DateTimeFormat::utcNow(), + '`name` = ? AND `expires` >= ?', $key,DateTimeFormat::utcNow() ]); if ($this->dba->isResult($lock)) { @@ -65,7 +65,7 @@ class DatabaseLock extends AbstractLock $this->dba->update('locks', [ 'locked' => true, 'pid' => $this->pid, - 'expires' => DateTimeFormat::utc('now + ' . $ttl . 'seconds'), + 'expires' => DateTimeFormat::utc('now + ' . $ttl . 'seconds') ], ['name' => $key]); $got_lock = true; } @@ -82,7 +82,7 @@ class DatabaseLock extends AbstractLock $this->dba->unlock(); if (!$got_lock && ($timeout > 0)) { - usleep(random_int(100000, 2000000)); + usleep(rand(100000, 2000000)); } } while (!$got_lock && ((time() - $start) < $timeout)); } catch (\Exception $exception) { diff --git a/src/Core/Logger.php b/src/Core/Logger.php index c0f5483eb1..6ab9c56519 100644 --- a/src/Core/Logger.php +++ b/src/Core/Logger.php @@ -13,7 +13,7 @@ use Psr\Log\LoggerInterface; /** * Logger functions * - * @deprecated 2026.01 Use constructor injection or `DI::logger()` instead + * @deprecated 2025.07 Use constructor injection or `DI::logger()` instead */ class Logger { @@ -34,7 +34,7 @@ class Logger */ public static function emergency(string $message, array $context = []) { - @trigger_error('Class `' . self::class . '` is deprecated since 2026.01 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.07 and will be removed after 5 months, use constructor injection or `DI::logger()` instead.', E_USER_DEPRECATED); self::getInstance()->emergency($message, $context); } @@ -53,7 +53,7 @@ class Logger */ public static function alert(string $message, array $context = []) { - @trigger_error('Class `' . self::class . '` is deprecated since 2026.01 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.07 and will be removed after 5 months, use constructor injection or `DI::logger()` instead.', E_USER_DEPRECATED); self::getInstance()->alert($message, $context); } @@ -71,7 +71,7 @@ class Logger */ public static function critical(string $message, array $context = []) { - @trigger_error('Class `' . self::class . '` is deprecated since 2026.01 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.07 and will be removed after 5 months, use constructor injection or `DI::logger()` instead.', E_USER_DEPRECATED); self::getInstance()->critical($message, $context); } @@ -88,7 +88,7 @@ class Logger */ public static function error(string $message, array $context = []) { - @trigger_error('Class `' . self::class . '` is deprecated since 2026.01 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.07 and will be removed after 5 months, use constructor injection or `DI::logger()` instead.', E_USER_DEPRECATED); self::getInstance()->error($message, $context); } @@ -107,7 +107,7 @@ class Logger */ public static function warning(string $message, array $context = []) { - @trigger_error('Class `' . self::class . '` is deprecated since 2026.01 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.07 and will be removed after 5 months, use constructor injection or `DI::logger()` instead.', E_USER_DEPRECATED); self::getInstance()->warning($message, $context); } @@ -123,7 +123,7 @@ class Logger */ public static function notice(string $message, array $context = []) { - @trigger_error('Class `' . self::class . '` is deprecated since 2026.01 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.07 and will be removed after 5 months, use constructor injection or `DI::logger()` instead.', E_USER_DEPRECATED); self::getInstance()->notice($message, $context); } @@ -142,7 +142,7 @@ class Logger */ public static function info(string $message, array $context = []) { - @trigger_error('Class `' . self::class . '` is deprecated since 2026.01 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.07 and will be removed after 5 months, use constructor injection or `DI::logger()` instead.', E_USER_DEPRECATED); self::getInstance()->info($message, $context); } @@ -158,7 +158,7 @@ class Logger */ public static function debug(string $message, array $context = []) { - @trigger_error('Class `' . self::class . '` is deprecated since 2026.01 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.07 and will be removed after 5 months, use constructor injection or `DI::logger()` instead.', E_USER_DEPRECATED); self::getInstance()->debug($message, $context); } diff --git a/src/Core/Logger/Factory/AbstractLoggerTypeFactory.php b/src/Core/Logger/Factory/AbstractLoggerTypeFactory.php index c5540c2398..47ad6e8d83 100644 --- a/src/Core/Logger/Factory/AbstractLoggerTypeFactory.php +++ b/src/Core/Logger/Factory/AbstractLoggerTypeFactory.php @@ -13,7 +13,7 @@ use Psr\Log\LogLevel; /** * Abstract class for creating logger types, which includes common necessary logic/content * - * @deprecated 2026.01 Implement `\Friendica\Core\Logger\Factory\LoggerFactory` instead + * @deprecated 2025.07 Implement `\Friendica\Core\Logger\Factory\LoggerFactory` instead */ abstract class AbstractLoggerTypeFactory { @@ -27,7 +27,7 @@ abstract class AbstractLoggerTypeFactory */ public function __construct(IHaveCallIntrospections $introspection, string $channel) { - @trigger_error('Class `' . self::class . '` is deprecated since 2026.01 and will be removed after 5 months, implement `\Friendica\Core\Logger\Factory\LoggerFactory` instead.', E_USER_DEPRECATED); + @trigger_error('Class `' . __CLASS__ . '` is deprecated since 2025.07 and will be removed after 5 months, implement `\Friendica\Core\Logger\Factory\LoggerFactory` instead.', E_USER_DEPRECATED); $this->channel = $channel; $this->introspection = $introspection; diff --git a/src/Core/Logger/Factory/DelegatingLoggerFactory.php b/src/Core/Logger/Factory/DelegatingLoggerFactory.php index b218fbf32a..1ee4a88ff3 100644 --- a/src/Core/Logger/Factory/DelegatingLoggerFactory.php +++ b/src/Core/Logger/Factory/DelegatingLoggerFactory.php @@ -48,10 +48,10 @@ final class DelegatingLoggerFactory implements LoggerFactory $factoryName = $this->config->get('system', 'logger_config') ?? ''; /** - * @deprecated 2026.01 The value `monolog` for `system.logger_config` inside the `config/local.config.php` file is deprecated, please use `stream` or `syslog` instead. + * @deprecated 2025.07 The value `monolog` for `system.logger_config` inside the `config/local.config.php` file is deprecated, please use `stream` or `syslog` instead. */ if ($factoryName === 'monolog') { - @trigger_error('The config `system.logger_config` with value `monolog` is deprecated since 2026.01 and will stop working in 5 months, please change the value to `stream` or `syslog` in the `config/local.config.php` file.', \E_USER_DEPRECATED); + @trigger_error('The config `system.logger_config` with value `monolog` is deprecated since 2025.07 and will stop working in 5 months, please change the value to `stream` or `syslog` in the `config/local.config.php` file.', \E_USER_DEPRECATED); $factoryName = 'stream'; } diff --git a/src/Core/Logger/Factory/Logger.php b/src/Core/Logger/Factory/Logger.php index 0d0b5cfd8f..f4427e6668 100644 --- a/src/Core/Logger/Factory/Logger.php +++ b/src/Core/Logger/Factory/Logger.php @@ -19,7 +19,7 @@ use Throwable; /** * The logger factory for the core logging instances * - * @deprecated 2026.01 Implement `\Friendica\Core\Logger\Factory\LoggerFactory` instead + * @deprecated 2025.07 Implement `\Friendica\Core\Logger\Factory\LoggerFactory` instead */ class Logger { @@ -28,7 +28,7 @@ class Logger public function __construct(string $channel = LogChannel::DEFAULT) { - @trigger_error('Class `' . self::class . '` is deprecated since 2026.01 and will be removed after 5 months, implement `\Friendica\Core\Logger\Factory\LoggerFactory` instead.', E_USER_DEPRECATED); + @trigger_error('Class `' . __CLASS__ . '` is deprecated since 2025.07 and will be removed after 5 months, implement `\Friendica\Core\Logger\Factory\LoggerFactory` instead.', E_USER_DEPRECATED); $this->channel = $channel; } diff --git a/src/Core/Logger/Factory/StreamLogger.php b/src/Core/Logger/Factory/StreamLogger.php index c0e65da364..75ff4a5613 100644 --- a/src/Core/Logger/Factory/StreamLogger.php +++ b/src/Core/Logger/Factory/StreamLogger.php @@ -20,7 +20,7 @@ use Psr\Log\NullLogger; /** * The logger factory for the StreamLogger instance * - * @deprecated 2026.01 Implement `\Friendica\Core\Logger\Factory\LoggerFactory` instead + * @deprecated 2025.07 Implement `\Friendica\Core\Logger\Factory\LoggerFactory` instead * @see StreamLoggerFactory * @see StreamLoggerClass */ @@ -40,11 +40,11 @@ class StreamLogger extends AbstractLoggerTypeFactory */ public function create(IManageConfigValues $config, string $logfile = null, string $channel = null): LoggerInterface { - @trigger_error('Class `' . self::class . '` is deprecated since 2026.01 and will be removed after 5 months, implement `\Friendica\Core\Logger\Factory\LoggerFactory` instead.', E_USER_DEPRECATED); + @trigger_error('Class `' . __CLASS__ . '` is deprecated since 2025.07 and will be removed after 5 months, implement `\Friendica\Core\Logger\Factory\LoggerFactory` instead.', E_USER_DEPRECATED); $fileSystem = new FileSystem(); - $logfile ??= $config->get('system', 'logfile'); + $logfile = $logfile ?? $config->get('system', 'logfile'); if (!@file_exists($logfile) || !@is_writable($logfile)) { throw new LoggerArgumentException(sprintf('%s is not a valid logfile', $logfile)); } @@ -80,8 +80,8 @@ class StreamLogger extends AbstractLoggerTypeFactory $logfile = $config->get('system', 'dlogfile'); $developerIp = $config->get('system', 'dlogip'); - if ((!isset($developerIp) || !$debugging) - && (!is_file($logfile) || is_writable($logfile))) { + if ((!isset($developerIp) || !$debugging) && + (!is_file($logfile) || is_writable($logfile))) { return new NullLogger(); } diff --git a/src/Core/Logger/Factory/SyslogLogger.php b/src/Core/Logger/Factory/SyslogLogger.php index d2a11070c7..d07cb53312 100644 --- a/src/Core/Logger/Factory/SyslogLogger.php +++ b/src/Core/Logger/Factory/SyslogLogger.php @@ -16,7 +16,7 @@ use Psr\Log\LoggerInterface; /** * The logger factory for the SyslogLogger instance * - * @deprecated 2026.01 Implement `\Friendica\Core\Logger\Factory\LoggerFactory` instead + * @deprecated 2025.07 Implement `\Friendica\Core\Logger\Factory\LoggerFactory` instead * @see SyslogLoggerFactory * @see SyslogLoggerClass */ @@ -33,7 +33,7 @@ class SyslogLogger extends AbstractLoggerTypeFactory */ public function create(IManageConfigValues $config): LoggerInterface { - @trigger_error('Class `' . self::class . '` is deprecated since 2026.01 and will be removed after 5 months, implement `\Friendica\Core\Logger\Factory\LoggerFactory` instead.', E_USER_DEPRECATED); + @trigger_error('Class `' . __CLASS__ . '` is deprecated since 2025.07 and will be removed after 5 months, implement `\Friendica\Core\Logger\Factory\LoggerFactory` instead.', E_USER_DEPRECATED); $logOpts = $config->get('system', 'syslog_flags') ?? SyslogLoggerClass::DEFAULT_FLAGS; $logFacility = $config->get('system', 'syslog_facility') ?? SyslogLoggerClass::DEFAULT_FACILITY; diff --git a/src/Core/Logger/Handler/ErrorHandler.php b/src/Core/Logger/Handler/ErrorHandler.php index b62a5a4502..6d47674581 100644 --- a/src/Core/Logger/Handler/ErrorHandler.php +++ b/src/Core/Logger/Handler/ErrorHandler.php @@ -160,7 +160,7 @@ class ErrorHandler register_shutdown_function([$this, 'handleFatalError']); $this->reservedMemory = str_repeat(' ', 1024 * $reservedMemorySize); - $this->fatalLevel = $level ?? LogLevel::ALERT; + $this->fatalLevel = null === $level ? LogLevel::ALERT : $level; $this->hasFatalErrorHandler = true; return $this; @@ -219,7 +219,7 @@ class ErrorHandler $this->logger->log( $level, sprintf('Uncaught Exception %s: "%s" at %s line %s', self::getClass($e), $e->getMessage(), $e->getFile(), $e->getLine()), - ['exception' => $e], + ['exception' => $e] ); if ($this->previousExceptionHandler) { @@ -262,14 +262,14 @@ class ErrorHandler $message .= sprintf( ' It was called in `%s`%s.', $calledPlace['file'], - isset($calledPlace['line']) ? ' in line ' . $calledPlace['line'] : '', + 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]); + $this->logger->log($level, self::codeToString($code).': '.$message, ['code' => $code, 'message' => $message, 'file' => $file, 'line' => $line]); } else { $this->lastFatalTrace = $trace; } @@ -294,8 +294,8 @@ class ErrorHandler if ($lastError && in_array($lastError['type'], self::$fatalErrors, true)) { $this->logger->log( $this->fatalLevel, - 'Fatal Error (' . self::codeToString($lastError['type']) . '): ' . $lastError['message'], - ['code' => $lastError['type'], 'message' => $lastError['message'], 'file' => $lastError['file'], 'line' => $lastError['line'], 'trace' => $this->lastFatalTrace], + 'Fatal Error ('.self::codeToString($lastError['type']).'): '.$lastError['message'], + ['code' => $lastError['type'], 'message' => $lastError['message'], 'file' => $lastError['file'], 'line' => $lastError['line'], 'trace' => $this->lastFatalTrace] ); } } diff --git a/src/Core/Logger/Util/Introspection.php b/src/Core/Logger/Util/Introspection.php index c73f0895bf..925031851b 100644 --- a/src/Core/Logger/Util/Introspection.php +++ b/src/Core/Logger/Util/Introspection.php @@ -73,7 +73,7 @@ class Introspection implements IHaveCallIntrospections 'line' => $trace[$i - 1]['line'] ?? null, 'function' => $trace[$i]['function'] ?? null, 'request-id' => $this->requestId, - 'stack' => System::callstack(15, 1, [\Friendica\Core\Logger\Type\StreamLogger::class, \Friendica\Core\Logger\Type\AbstractLogger::class, \Friendica\Core\Logger\Type\WorkerLogger::class, \Friendica\Core\Logger::class]), + 'stack' => System::callstack(15, 1, ['Friendica\Core\Logger\Type\StreamLogger', 'Friendica\Core\Logger\Type\AbstractLogger', 'Friendica\Core\Logger\Type\WorkerLogger', 'Friendica\Core\Logger']), ]; } diff --git a/src/Core/PConfig/Type/JitPConfig.php b/src/Core/PConfig/Type/JitPConfig.php index a0b9e605f2..3234446c5b 100644 --- a/src/Core/PConfig/Type/JitPConfig.php +++ b/src/Core/PConfig/Type/JitPConfig.php @@ -19,7 +19,7 @@ use Friendica\Core\PConfig\ValueObject; */ class JitPConfig extends AbstractPConfigValues { - public const NAME = 'jit'; + const NAME = 'jit'; /** * @var array Array of already loaded db values (even if there was no value) @@ -71,8 +71,8 @@ class JitPConfig extends AbstractPConfigValues } // if the value isn't loaded or refresh is needed, load it to the cache - if ($this->configModel->isConnected() - && (empty($this->db_loaded[$uid][$cat][$key]) || $refresh)) { + if ($this->configModel->isConnected() && + (empty($this->db_loaded[$uid][$cat][$key]) || $refresh)) { $dbValue = $this->configModel->get($uid, $cat, $key); if (isset($dbValue)) { @@ -86,7 +86,7 @@ class JitPConfig extends AbstractPConfigValues // use the config cache for return $result = $this->configCache->get($uid, $cat, $key); - return $result ?? $default_value; + return (isset($result)) ? $result : $default_value; } /** diff --git a/src/Core/PConfig/Type/PreloadPConfig.php b/src/Core/PConfig/Type/PreloadPConfig.php index 930235977d..1aa95d4a18 100644 --- a/src/Core/PConfig/Type/PreloadPConfig.php +++ b/src/Core/PConfig/Type/PreloadPConfig.php @@ -18,7 +18,7 @@ use Friendica\Core\PConfig\ValueObject; */ class PreloadPConfig extends AbstractPConfigValues { - public const NAME = 'preload'; + const NAME = 'preload'; /** @var array */ private $config_loaded; @@ -83,7 +83,7 @@ class PreloadPConfig extends AbstractPConfigValues // use the config cache for return $result = $this->configCache->get($uid, $cat, $key); - return $result ?? $default_value; + return (isset($result)) ? $result : $default_value; } /** diff --git a/src/Core/Protocol.php b/src/Core/Protocol.php index 6f7579da39..ef57c22f18 100644 --- a/src/Core/Protocol.php +++ b/src/Core/Protocol.php @@ -22,43 +22,43 @@ use Friendica\Protocol\Diaspora; class Protocol { // Native support - public const ACTIVITYPUB = 'apub'; // ActivityPub (Pleroma, Mastodon, Osada, ...) - public const DFRN = 'dfrn'; // Friendica, Mistpark, other DFRN implementations - public const DIASPORA = 'dspr'; // Diaspora, Hubzilla, Socialhome, Ganggo - public const FEED = 'feed'; // RSS/Atom feeds with no known "post/notify" protocol - public const MAIL = 'mail'; // IMAP/POP + const ACTIVITYPUB = 'apub'; // ActivityPub (Pleroma, Mastodon, Osada, ...) + const DFRN = 'dfrn'; // Friendica, Mistpark, other DFRN implementations + const DIASPORA = 'dspr'; // Diaspora, Hubzilla, Socialhome, Ganggo + const FEED = 'feed'; // RSS/Atom feeds with no known "post/notify" protocol + const MAIL = 'mail'; // IMAP/POP - public const NATIVE_SUPPORT = [self::DFRN, self::DIASPORA, self::FEED, self::MAIL, self::ACTIVITYPUB]; + const NATIVE_SUPPORT = [self::DFRN, self::DIASPORA, self::FEED, self::MAIL, self::ACTIVITYPUB]; - public const FEDERATED = [self::DFRN, self::DIASPORA, self::ACTIVITYPUB]; + const FEDERATED = [self::DFRN, self::DIASPORA, self::ACTIVITYPUB]; - public const SUPPORT_PRIVATE = [self::DFRN, self::DIASPORA, self::MAIL, self::ACTIVITYPUB, self::PUMPIO]; + const SUPPORT_PRIVATE = [self::DFRN, self::DIASPORA, self::MAIL, self::ACTIVITYPUB, self::PUMPIO]; // Supported through a connector - public const BLUESKY = 'bsky'; // Bluesky - public const DIASPORA2 = 'dspc'; // Diaspora connector - public const DISCOURSE = 'dscs'; // Discourse - public const PNUT = 'pnut'; // pnut.io - public const PUMPIO = 'pump'; // pump.io - public const TUMBLR = 'tmbl'; // Tumblr - public const TWITTER = 'twit'; // Twitter + const BLUESKY = 'bsky'; // Bluesky + const DIASPORA2 = 'dspc'; // Diaspora connector + const DISCOURSE = 'dscs'; // Discourse + const PNUT = 'pnut'; // pnut.io + const PUMPIO = 'pump'; // pump.io + const TUMBLR = 'tmbl'; // Tumblr + const TWITTER = 'twit'; // Twitter // Dead protocols - public const APPNET = 'apdn'; // app.net - Dead protocol - public const FACEBOOK = 'face'; // Facebook API - Not working anymore, API is closed - public const GPLUS = 'goog'; // Google+ - Dead in 2019 - public const OSTATUS = 'stat'; // GNU Social and other OStatus implementations - public const STATUSNET = 'stac'; // Statusnet connector + const APPNET = 'apdn'; // app.net - Dead protocol + const FACEBOOK = 'face'; // Facebook API - Not working anymore, API is closed + const GPLUS = 'goog'; // Google+ - Dead in 2019 + const OSTATUS = 'stat'; // GNU Social and other OStatus implementations + const STATUSNET = 'stac'; // Statusnet connector // Currently unsupported - public const ICALENDAR = 'ical'; // iCalendar - public const LINKEDIN = 'lnkd'; // LinkedIn - public const MYSPACE = 'mysp'; // MySpace - public const NEWS = 'nntp'; // Network News Transfer Protocol - public const XMPP = 'xmpp'; // XMPP - public const ZOT = 'zot!'; // Zot! + const ICALENDAR = 'ical'; // iCalendar + const LINKEDIN = 'lnkd'; // LinkedIn + const MYSPACE = 'mysp'; // MySpace + const NEWS = 'nntp'; // Network News Transfer Protocol + const XMPP = 'xmpp'; // XMPP + const ZOT = 'zot!'; // Zot! - public const PHANTOM = 'unkn'; // Place holder + const PHANTOM = 'unkn'; // Place holder /** * Returns whether the provided protocol supports following @@ -75,7 +75,7 @@ class Protocol $hook_data = [ 'protocol' => $protocol, - 'result' => null, + 'result' => null ]; $eventDispatcher = DI::eventDispatcher(); @@ -102,7 +102,7 @@ class Protocol $hook_data = [ 'protocol' => $protocol, - 'result' => null, + 'result' => null ]; $eventDispatcher = DI::eventDispatcher(); @@ -131,7 +131,7 @@ class Protocol return true; } - $protocol ??= $contact['protocol']; + $protocol = $protocol ?? $contact['protocol']; if ($protocol == self::DIASPORA) { $contact = Diaspora::sendShare($owner, $contact); @@ -340,7 +340,7 @@ class Protocol $hook_data = [ 'protocol' => $protocol, - 'result' => null, + 'result' => null ]; $eventDispatcher = DI::eventDispatcher(); diff --git a/src/Core/Storage/Repository/StorageManager.php b/src/Core/Storage/Repository/StorageManager.php index 681318a63c..36efa3a1bf 100644 --- a/src/Core/Storage/Repository/StorageManager.php +++ b/src/Core/Storage/Repository/StorageManager.php @@ -34,11 +34,11 @@ use Psr\Log\LoggerInterface; class StorageManager { // Default tables to look for data - public const TABLES = ['photo', 'attach']; + const TABLES = ['photo', 'attach']; // Default storage backends /** @var string[] */ - public const DEFAULT_BACKENDS = [ + const DEFAULT_BACKENDS = [ Type\Filesystem::NAME, Type\Database::NAME, ]; @@ -240,12 +240,12 @@ class StorageManager */ public function isValidBackend(string $name = null, array $validBackends = null): bool { - $validBackends ??= array_merge( + $validBackends = $validBackends ?? array_merge( $this->validBackends, [ Type\SystemResource::getName(), Type\ExternalResource::getName(), - ], + ] ); return in_array($name, $validBackends); } @@ -373,7 +373,7 @@ class StorageManager $table, ['id', 'data', 'backend-class', 'backend-ref'], ['`backend-class` IS NULL or `backend-class` != ?', $destination::getName()], - ['limit' => $limit], + ['limit' => $limit] ); while ($resource = $this->dba->fetch($resources)) { diff --git a/src/Core/Theme.php b/src/Core/Theme.php index 1ca3c9600c..8cd22a2e1c 100644 --- a/src/Core/Theme.php +++ b/src/Core/Theme.php @@ -83,8 +83,8 @@ class Theme foreach ($comment_lines as $comment_line) { $comment_line = trim($comment_line, "\t\n\r */"); if (strpos($comment_line, ':') !== false) { - [$key, $value] = array_map("trim", explode(":", $comment_line, 2)); - $key = strtolower($key); + list($key, $value) = array_map("trim", explode(":", $comment_line, 2)); + $key = strtolower($key); if ($key == "author") { $result = preg_match("|([^<]+)<([^>]+)>|", $value, $matches); if ($result) { diff --git a/src/Core/Worker.php b/src/Core/Worker.php index fdbf9e7bb9..1574cadbf5 100644 --- a/src/Core/Worker.php +++ b/src/Core/Worker.php @@ -27,25 +27,25 @@ class Worker * Process priority for the worker * @{ */ - public const PRIORITY_UNDEFINED = 0; - public const PRIORITY_CRITICAL = 10; - public const PRIORITY_HIGH = 20; - public const PRIORITY_MEDIUM = 30; - public const PRIORITY_LOW = 40; - public const PRIORITY_NEGLIGIBLE = 50; - public const PRIORITIES = [self::PRIORITY_CRITICAL, self::PRIORITY_HIGH, self::PRIORITY_MEDIUM, self::PRIORITY_LOW, self::PRIORITY_NEGLIGIBLE]; + const PRIORITY_UNDEFINED = 0; + const PRIORITY_CRITICAL = 10; + const PRIORITY_HIGH = 20; + const PRIORITY_MEDIUM = 30; + const PRIORITY_LOW = 40; + const PRIORITY_NEGLIGIBLE = 50; + const PRIORITIES = [self::PRIORITY_CRITICAL, self::PRIORITY_HIGH, self::PRIORITY_MEDIUM, self::PRIORITY_LOW, self::PRIORITY_NEGLIGIBLE]; /* @}*/ - public const STATE_STARTUP = 1; // Worker is in startup. This takes most time. - public const STATE_LONG_LOOP = 2; // Worker is processing the whole - long - loop. - public const STATE_REFETCH = 3; // Worker had refetched jobs in the execution loop. - public const STATE_SHORT_LOOP = 4; // Worker is processing preassigned jobs, thus saving much time. + const STATE_STARTUP = 1; // Worker is in startup. This takes most time. + const STATE_LONG_LOOP = 2; // Worker is processing the whole - long - loop. + const STATE_REFETCH = 3; // Worker had refetched jobs in the execution loop. + const STATE_SHORT_LOOP = 4; // Worker is processing preassigned jobs, thus saving much time. - public const FAST_COMMANDS = ['APDelivery', 'Delivery']; + const FAST_COMMANDS = ['APDelivery', 'Delivery']; - public const LOCK_PROCESS = 'worker_process'; - public const LOCK_WORKER = 'worker'; - public const LAST_CHECK = 'worker::check'; + const LOCK_PROCESS = 'worker_process'; + const LOCK_WORKER = 'worker'; + const LAST_CHECK = 'worker::check'; private static $up_start; private static $db_duration = 0; @@ -82,7 +82,7 @@ class Worker // Kill stale processes every 5 minutes $last_cleanup = DI::keyValue()->get('worker_last_cleaned') ?? 0; if (time() > ($last_cleanup + 300)) { - DI::keyValue()->set('worker_last_cleaned', time()); + DI::keyValue()->set( 'worker_last_cleaned', time()); Worker\Cron::killStaleWorkers(); } @@ -212,7 +212,7 @@ class Worker */ public static function entriesExists(): bool { - $stamp = (float) microtime(true); + $stamp = (float)microtime(true); $exists = DBA::exists('workerqueue', ["NOT `done` AND `pid` = 0 AND `next_try` < ?", DateTimeFormat::utcNow()]); self::$db_duration += (microtime(true) - $stamp); return $exists; @@ -226,7 +226,7 @@ class Worker */ public static function deferredEntries(): int { - $stamp = (float) microtime(true); + $stamp = (float)microtime(true); $count = DBA::count('workerqueue', ["NOT `done` AND `pid` = 0 AND `retrial` > ?", 0]); self::$db_duration += (microtime(true) - $stamp); self::$db_duration_count += (microtime(true) - $stamp); @@ -241,7 +241,7 @@ class Worker */ public static function totalEntries(): int { - $stamp = (float) microtime(true); + $stamp = (float)microtime(true); $count = DBA::count('workerqueue', ['done' => false, 'pid' => 0]); self::$db_duration += (microtime(true) - $stamp); self::$db_duration_count += (microtime(true) - $stamp); @@ -256,7 +256,7 @@ class Worker */ private static function highestPriority(): int { - $stamp = (float) microtime(true); + $stamp = (float)microtime(true); $condition = ["`pid` = 0 AND NOT `done` AND `next_try` < ?", DateTimeFormat::utcNow()]; $workerqueue = DBA::selectFirst('workerqueue', ['priority'], $condition, ['order' => ['priority']]); self::$db_duration += (microtime(true) - $stamp); @@ -294,7 +294,7 @@ class Worker $file = realpath($file); - if (strpos($file, (string) getcwd()) !== 0) { + if (strpos($file, getcwd()) !== 0) { return false; } @@ -367,7 +367,7 @@ class Worker self::$last_update = time(); if ($age > 1) { - $stamp = (float) microtime(true); + $stamp = (float)microtime(true); DBA::update('workerqueue', ['executed' => DateTimeFormat::utcNow()], ['pid' => $mypid, 'done' => false]); self::$db_duration += (microtime(true) - $stamp); self::$db_duration_write += (microtime(true) - $stamp); @@ -377,7 +377,7 @@ class Worker self::execFunction($queue, $include, $argv, true); - $stamp = (float) microtime(true); + $stamp = (float)microtime(true); $condition = ["`id` = ? AND `next_try` < ?", $queue['id'], DateTimeFormat::utcNow()]; if (DBA::update('workerqueue', ['done' => true], $condition)) { DI::keyValue()->set('last_worker_execution', DateTimeFormat::utcNow()); @@ -390,7 +390,7 @@ class Worker if (!self::validateInclude($include)) { DI::logger()->warning('Include file is not valid', ['file' => $argv[0]]); - $stamp = (float) microtime(true); + $stamp = (float)microtime(true); DBA::delete('workerqueue', ['id' => $queue['id']]); self::$db_duration = (microtime(true) - $stamp); self::$db_duration_write += (microtime(true) - $stamp); @@ -399,7 +399,7 @@ class Worker require_once $include; - $funcname = str_replace('.php', '', basename($argv[0])) . '_run'; + $funcname = str_replace('.php', '', basename($argv[0])) .'_run'; if (function_exists($funcname)) { // We constantly update the "executed" date every minute to avoid being killed too soon @@ -411,7 +411,7 @@ class Worker self::$last_update = time(); if ($age > 1) { - $stamp = (float) microtime(true); + $stamp = (float)microtime(true); DBA::update('workerqueue', ['executed' => DateTimeFormat::utcNow()], ['pid' => $mypid, 'done' => false]); self::$db_duration += (microtime(true) - $stamp); self::$db_duration_write += (microtime(true) - $stamp); @@ -419,7 +419,7 @@ class Worker self::execFunction($queue, $funcname, $argv, false); - $stamp = (float) microtime(true); + $stamp = (float)microtime(true); if (DBA::update('workerqueue', ['done' => true], ['id' => $queue['id']])) { DI::keyValue()->set('last_worker_execution', DateTimeFormat::utcNow()); } @@ -427,7 +427,7 @@ class Worker self::$db_duration_write += (microtime(true) - $stamp); } else { DI::logger()->warning('Function does not exist', ['function' => $funcname]); - $stamp = (float) microtime(true); + $stamp = (float)microtime(true); DBA::delete('workerqueue', ['id' => $queue['id']]); self::$db_duration = (microtime(true) - $stamp); self::$db_duration_write += (microtime(true) - $stamp); @@ -551,7 +551,7 @@ class Worker DI::logger()->info('Process start.', ['priority' => $queue['priority'], 'id' => $queue['id']]); - $stamp = (float) microtime(true); + $stamp = (float)microtime(true); // We use the callstack here to analyze the performance of executed worker entries. // For this reason the variables have to be initialized. @@ -648,7 +648,7 @@ class Worker $max = $r['Value']; } // Or it can be granted. This overrides the system variable - $stamp = (float) microtime(true); + $stamp = (float)microtime(true); $r = DBA::p('SHOW GRANTS'); self::$db_duration += (microtime(true) - $stamp); while ($grants = DBA::fetch($r)) { @@ -662,7 +662,7 @@ class Worker DBA::close($r); } - $stamp = (float) microtime(true); + $stamp = (float)microtime(true); $used = 0; $sleep = 0; $data = DBA::p("SHOW PROCESSLIST"); @@ -684,7 +684,7 @@ class Worker $level = ($used / $max) * 100; if ($level >= $maxlevel) { - DI::logger()->warning('Maximum level (' . $maxlevel . '%) of user connections reached: ' . $used . '/' . $max); + DI::logger()->warning('Maximum level (' . $maxlevel . '%) of user connections reached: ' . $used .'/' . $max); return true; } } @@ -756,16 +756,16 @@ class Worker if ($interval == 0) { continue; } else { - $interval = (int) $interval; + $interval = (int)$interval; } - $stamp = (float) microtime(true); + $stamp = (float)microtime(true); $jobs = DBA::count('workerqueue', ["`done` AND `executed` > ?", DateTimeFormat::utc('now - ' . $interval . ' minute')]); self::$db_duration += (microtime(true) - $stamp); self::$db_duration_stat += (microtime(true) - $stamp); $jobs_per_minute[$interval] = number_format($jobs / $interval, 0); } - $processlist = ' - jpm: ' . implode('/', $jobs_per_minute); + $processlist = ' - jpm: '.implode('/', $jobs_per_minute); } // Create a list of queue entries grouped by their priority @@ -778,12 +778,12 @@ class Worker if (DI::config()->get('system', 'worker_debug')) { $waiting_processes = 0; // Now adding all processes with workerqueue entries - $stamp = (float) microtime(true); + $stamp = (float)microtime(true); $jobs = DBA::p("SELECT COUNT(*) AS `entries`, `priority` FROM `workerqueue` WHERE NOT `done` GROUP BY `priority`"); self::$db_duration += (microtime(true) - $stamp); self::$db_duration_stat += (microtime(true) - $stamp); while ($entry = DBA::fetch($jobs)) { - $stamp = (float) microtime(true); + $stamp = (float)microtime(true); $running = DBA::count('workerqueue-view', ['priority' => $entry['priority']]); self::$db_duration += (microtime(true) - $stamp); self::$db_duration_stat += (microtime(true) - $stamp); @@ -794,7 +794,7 @@ class Worker DBA::close($jobs); } else { $waiting_processes = self::totalEntries(); - $stamp = (float) microtime(true); + $stamp = (float)microtime(true); $jobs = DBA::p("SELECT COUNT(*) AS `running`, `priority` FROM `workerqueue-view` GROUP BY `priority` ORDER BY `priority`"); self::$db_duration += (microtime(true) - $stamp); self::$db_duration_stat += (microtime(true) - $stamp); @@ -810,7 +810,7 @@ class Worker $listitem[0] = '0:' . max(0, $idle_workers); - $processlist .= ' (' . implode(', ', $listitem) . ')'; + $processlist .= ' ('.implode(', ', $listitem).')'; if (DI::config()->get('system', 'worker_fastlane', false) && ($queues > 0) && ($active >= $queues) && self::entriesExists()) { $top_priority = self::highestPriority(); @@ -866,7 +866,7 @@ class Worker */ public static function activeWorkers(): int { - $stamp = (float) microtime(true); + $stamp = (float)microtime(true); $count = DI::process()->countCommand('Worker.php'); self::$db_duration += (microtime(true) - $stamp); self::$db_duration_count += (microtime(true) - $stamp); @@ -882,7 +882,7 @@ class Worker private static function getWorkerPIDList(): array { $ids = []; - $stamp = (float) microtime(true); + $stamp = (float)microtime(true); $queues = DBA::p("SELECT `process`.`pid`, COUNT(`workerqueue`.`pid`) AS `entries` FROM `process` LEFT JOIN `workerqueue` ON `workerqueue`.`pid` = `process`.`pid` AND NOT `workerqueue`.`done` @@ -905,7 +905,7 @@ class Worker */ private static function getWaitingJobForPID() { - $stamp = (float) microtime(true); + $stamp = (float)microtime(true); $r = DBA::select('workerqueue', [], ['pid' => getmypid(), 'done' => false]); self::$db_duration += (microtime(true) - $stamp); if (DBA::isResult($r)) { @@ -931,7 +931,7 @@ class Worker } $ids = []; - $stamp = (float) microtime(true); + $stamp = (float)microtime(true); $condition = ["`priority` = ? AND `pid` = 0 AND NOT `done` AND `next_try` < ?", $priority, DateTimeFormat::utcNow()]; $tasks = DBA::select('workerqueue', ['id', 'command', 'parameter'], $condition, ['limit' => $limit, 'order' => ['retrial', 'created']]); self::$db_duration += (microtime(true) - $stamp); @@ -965,7 +965,7 @@ class Worker $waiting = []; $priorities = [self::PRIORITY_CRITICAL, self::PRIORITY_HIGH, self::PRIORITY_MEDIUM, self::PRIORITY_LOW, self::PRIORITY_NEGLIGIBLE]; foreach ($priorities as $priority) { - $stamp = (float) microtime(true); + $stamp = (float)microtime(true); if (DBA::exists('workerqueue', ["`priority` = ? AND `pid` = 0 AND NOT `done` AND `next_try` < ?", $priority, DateTimeFormat::utcNow()])) { $waiting[$priority] = true; } @@ -978,7 +978,7 @@ class Worker $running = []; $running_total = 0; - $stamp = (float) microtime(true); + $stamp = (float)microtime(true); $processes = DBA::p("SELECT COUNT(DISTINCT(`pid`)) AS `running`, `priority` FROM `workerqueue-view` GROUP BY `priority`"); self::$db_duration += (microtime(true) - $stamp); while ($process = DBA::fetch($processes)) { @@ -1056,7 +1056,7 @@ class Worker // If there is not enough results we check without priority limit if ($limit > 0) { - $stamp = (float) microtime(true); + $stamp = (float)microtime(true); $condition = ["`pid` = 0 AND NOT `done` AND `next_try` < ?", DateTimeFormat::utcNow()]; $tasks = DBA::select('workerqueue', ['id', 'command', 'parameter'], $condition, ['limit' => $limit, 'order' => ['priority', 'retrial', 'created']]); self::$db_duration += (microtime(true) - $stamp); @@ -1090,13 +1090,13 @@ class Worker $worker[$pid][] = $id; } - $stamp = (float) microtime(true); + $stamp = (float)microtime(true); foreach ($worker as $worker_pid => $worker_ids) { DI::logger()->info('Set queue entry', ['pid' => $worker_pid, 'ids' => $worker_ids]); DBA::update( 'workerqueue', ['executed' => DateTimeFormat::utcNow(), 'pid' => $worker_pid], - ['id' => $worker_ids, 'done' => false, 'pid' => 0], + ['id' => $worker_ids, 'done' => false, 'pid' => 0] ); } self::$db_duration += (microtime(true) - $stamp); @@ -1117,7 +1117,7 @@ class Worker return $waiting; } - $stamp = (float) microtime(true); + $stamp = (float)microtime(true); if (!DI::lock()->acquire(self::LOCK_PROCESS)) { return []; } @@ -1142,7 +1142,7 @@ class Worker */ public static function unclaimProcess(Process $process) { - $stamp = (float) microtime(true); + $stamp = (float)microtime(true); DBA::update('workerqueue', ['executed' => DBA::NULL_DATETIME, 'pid' => 0], ['pid' => $process->pid, 'done' => false]); self::$db_duration += (microtime(true) - $stamp); self::$db_duration_write += (microtime(true) - $stamp); @@ -1374,7 +1374,7 @@ class Worker $new_retrial = $queue['retrial'] + 1; $total = 0; for ($retrial = 0; $retrial <= $max_level + 1; ++$retrial) { - $delay = (($retrial + 3) ** 4) + (random_int(1, 30) * ($retrial + 1)); + $delay = (($retrial + 3) ** 4) + (rand(1, 30) * ($retrial + 1)); $total += $delay; if (($total < $retrial_time) && ($retrial > $queue['retrial'])) { $new_retrial = $retrial; @@ -1427,7 +1427,7 @@ class Worker } // Calculate the delay until the next trial - $delay = (($new_retrial + 2) ** 4) + (random_int(1, 30) * ($new_retrial)); + $delay = (($new_retrial + 2) ** 4) + (rand(1, 30) * ($new_retrial)); $next = DateTimeFormat::utc('now + ' . $delay . ' seconds'); if (($priority < self::PRIORITY_MEDIUM) && ($new_retrial > 3)) { @@ -1440,7 +1440,7 @@ class Worker DI::logger()->info('Deferred task', ['id' => $id, 'retrial' => $new_retrial, 'created' => $queue['created'], 'next_execution' => $next, 'old_prio' => $queue['priority'], 'new_prio' => $priority]); - $stamp = (float) microtime(true); + $stamp = (float)microtime(true); $fields = ['retrial' => $new_retrial, 'next_try' => $next, 'executed' => DBA::NULL_DATETIME, 'pid' => 0, 'priority' => $priority]; DBA::update('workerqueue', $fields, ['id' => $id]); self::$db_duration += (microtime(true) - $stamp); diff --git a/src/DI.php b/src/DI.php index 4733a185e1..4f825e18fb 100644 --- a/src/DI.php +++ b/src/DI.php @@ -259,11 +259,11 @@ abstract class DI } /** - * @deprecated 2026.01 Use `DI::loggerManager()` and `DI::logger()` instead + * @deprecated 2025.07 Use `DI::loggerManager()` and `DI::logger()` instead */ public static function workerLogger(): Core\Logger\Type\WorkerLogger { - @trigger_error('`' . __METHOD__ . '()` is deprecated since 2026.01 and will be removed after 5 months, use `DI::logger()` instead.', E_USER_DEPRECATED); + @trigger_error('`' . __METHOD__ . '()` is deprecated since 2025.07 and will be removed after 5 months, use `DI::logger()` instead.', E_USER_DEPRECATED); return self::$dice->create(Core\Logger\Type\WorkerLogger::class); } @@ -413,11 +413,6 @@ abstract class DI return self::$dice->create(Network\HTTPClient\Capability\ICanSendHttpRequests::class); } - public static function robotsTxt(): Network\RobotsTxt - { - return self::$dice->create(Network\RobotsTxt::class); - } - // // "Repository" namespace // @@ -632,7 +627,7 @@ abstract class DI /** * @internal The EventDispatcher should never called outside of the core, like in addons or themes - * @deprecated 2026.01 Use constructor injection instead + * @deprecated 2025.07 Use constructor injection instead */ public static function eventDispatcher(): \Psr\EventDispatcher\EventDispatcherInterface { diff --git a/src/Database/DBA.php b/src/Database/DBA.php index ae65370463..c001384f99 100644 --- a/src/Database/DBA.php +++ b/src/Database/DBA.php @@ -23,16 +23,16 @@ class DBA /** * Lowest possible date value */ - public const NULL_DATE = '0001-01-01'; + const NULL_DATE = '0001-01-01'; /** * Lowest possible datetime value */ - public const NULL_DATETIME = '0001-01-01 00:00:00'; + const NULL_DATETIME = '0001-01-01 00:00:00'; /** * Lowest possible datetime(6) value */ - public const NULL_DATETIME6 = '0001-01-01 00:00:00.000000'; + const NULL_DATETIME6 = '0001-01-01 00:00:00.000000'; public static function connect(): bool { @@ -130,11 +130,11 @@ class DBA */ public static function cleanQuery(string $sql): string { - $search = ["\t", "\n", "\r", " "]; + $search = ["\t", "\n", "\r", " "]; $replace = [' ', ' ', ' ', ' ']; do { $oldsql = $sql; - $sql = str_replace($search, $replace, $sql); + $sql = str_replace($search, $replace, $sql); } while ($oldsql != $sql); return $sql; @@ -518,8 +518,8 @@ class DBA '.', array_map( function (string $identifier) { return '`' . str_replace('`', '``', $identifier) . '`'; }, - explode('.', $identifier), - ), + explode('.', $identifier) + ) ); } @@ -571,14 +571,16 @@ class DBA if (count($condition) < 1) { return ['1']; } - $first_key = array_key_first($condition); + + reset($condition); + $first_key = key($condition); if (is_int($first_key)) { // Already collapsed return $condition; } - $values = []; + $values = []; $condition_string = ""; foreach ($condition as $field => $value) { if ($condition_string != "") { @@ -592,7 +594,7 @@ class DBA * In case of mixed types, cast all as string. * Logic needs to be consistent with DBA::p() data types. */ - $is_int = false; + $is_int = false; $is_alpha = false; foreach ($value as $single_value) { if (is_int($single_value)) { @@ -605,13 +607,13 @@ class DBA if ($is_int && $is_alpha) { foreach ($value as &$ref) { if (is_int($ref)) { - $ref = (string) $ref; + $ref = (string)$ref; } } unset($ref); //Prevent accidental re-use. } - $values = array_merge($values, array_values($value)); + $values = array_merge($values, array_values($value)); $placeholders = substr(str_repeat("?, ", count($value)), 0, -2); $condition_string .= self::quoteIdentifier($field) . " IN (" . $placeholders . ")"; } else { @@ -645,7 +647,7 @@ class DBA } $conditionStrings = []; - $result = []; + $result = []; foreach ($conditions as $key => $condition) { if (!$condition) { diff --git a/src/Database/Database.php b/src/Database/Database.php index 8fc6bbc550..82522a100a 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -31,14 +31,14 @@ use Psr\Log\NullLogger; */ class Database { - public const PDO = 'pdo'; - public const MYSQLI = 'mysqli'; + const PDO = 'pdo'; + const MYSQLI = 'mysqli'; - public const INSERT_DEFAULT = 0; - public const INSERT_UPDATE = 1; - public const INSERT_IGNORE = 2; + const INSERT_DEFAULT = 0; + const INSERT_UPDATE = 1; + const INSERT_IGNORE = 2; - public const LOCK_OPTIMIZE = 'database::optimize_tables'; + const LOCK_OPTIMIZE = 'database::optimize_tables'; protected $connected = false; @@ -145,9 +145,9 @@ class Database return false; } - $persistent = (bool) $this->config->get('database', 'persistent'); + $persistent = (bool)$this->config->get('database', 'persistent'); - $this->pdo_emulate_prepares = (bool) $this->config->get('database', 'pdo_emulate_prepares'); + $this->pdo_emulate_prepares = (bool)$this->config->get('database', 'pdo_emulate_prepares'); if (!$this->config->get('database', 'disable_pdo') && class_exists('\PDO') && in_array('mysql', PDO::getAvailableDrivers())) { $this->driver = self::PDO; @@ -340,8 +340,8 @@ class Database while ($row = $this->fetch($r)) { if ((intval($this->config->get('system', 'db_loglimit_index')) > 0)) { - $log = (in_array($row['key'], $watchlist) - && ($row['rows'] >= intval($this->config->get('system', 'db_loglimit_index')))); + $log = (in_array($row['key'], $watchlist) && + ($row['rows'] >= intval($this->config->get('system', 'db_loglimit_index')))); } else { $log = false; } @@ -358,12 +358,12 @@ class Database $backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS); @file_put_contents( $this->config->get('system', 'db_log_index'), - DateTimeFormat::utcNow() . "\t" - . $row['key'] . "\t" . $row['rows'] . "\t" . $row['Extra'] . "\t" - . basename($backtrace[1]["file"]) . "\t" - . $backtrace[1]["line"] . "\t" . $backtrace[2]["function"] . "\t" - . substr($query, 0, 4000) . "\n", - FILE_APPEND, + DateTimeFormat::utcNow() . "\t" . + $row['key'] . "\t" . $row['rows'] . "\t" . $row['Extra'] . "\t" . + basename($backtrace[1]["file"]) . "\t" . + $backtrace[1]["line"] . "\t" . $backtrace[2]["function"] . "\t" . + substr($query, 0, 4000) . "\n", + FILE_APPEND ); } } @@ -491,7 +491,7 @@ class Database foreach ($params as $param) { // Avoid problems with some MySQL servers and boolean values. See issue #3645 if (is_bool($param)) { - $param = (int) $param; + $param = (int)$param; } $args[++$i] = $param; } @@ -542,8 +542,8 @@ class Database if (count($args) == 0) { if (!$retval = $this->connection->query($this->replaceParameters($sql, $args))) { $errorInfo = $this->connection->errorInfo(); - $this->error = (string) $errorInfo[2]; - $this->errorno = (int) $errorInfo[1]; + $this->error = (string)$errorInfo[2]; + $this->errorno = (int)$errorInfo[1]; $retval = false; $is_error = true; break; @@ -557,8 +557,8 @@ class Database if (!$stmt) { $errorInfo = $this->connection->errorInfo(); - $this->error = (string) $errorInfo[2]; - $this->errorno = (int) $errorInfo[1]; + $this->error = (string)$errorInfo[2]; + $this->errorno = (int)$errorInfo[1]; $retval = false; $is_error = true; break; @@ -569,7 +569,7 @@ class Database if (is_int($args[$param])) { $data_type = PDO::PARAM_INT; } elseif ($args[$param] !== null) { - $args[$param] = (string) $args[$param]; + $args[$param] = (string)$args[$param]; } $stmt->bindParam($param, $args[$param], $data_type); @@ -577,8 +577,8 @@ class Database if (!$stmt->execute()) { $errorInfo = $stmt->errorInfo(); - $this->error = (string) $errorInfo[2]; - $this->errorno = (int) $errorInfo[1]; + $this->error = (string)$errorInfo[2]; + $this->errorno = (int)$errorInfo[1]; $retval = false; $is_error = true; } else { @@ -596,8 +596,8 @@ class Database if (!$can_be_prepared || (count($args) == 0)) { $retval = $this->connection->query($this->replaceParameters($sql, $args)); if ($this->connection->errno) { - $this->error = (string) $this->connection->error; - $this->errorno = (int) $this->connection->errno; + $this->error = (string)$this->connection->error; + $this->errorno = (int)$this->connection->errno; $retval = false; $is_error = true; } else { @@ -613,8 +613,8 @@ class Database $stmt = $this->connection->stmt_init(); if (!$stmt->prepare($sql)) { - $this->error = (string) $stmt->error; - $this->errorno = (int) $stmt->errno; + $this->error = (string)$stmt->error; + $this->errorno = (int)$stmt->errno; $retval = false; $is_error = true; break; @@ -631,7 +631,7 @@ class Database $param_types .= 's'; } elseif (is_object($args[$param]) && method_exists($args[$param], '__toString')) { $param_types .= 's'; - $args[$param] = (string) $args[$param]; + $args[$param] = (string)$args[$param]; } else { $param_types .= 'b'; } @@ -644,8 +644,8 @@ class Database } if (!$stmt->execute()) { - $this->error = (string) $this->connection->error; - $this->errorno = (int) $this->connection->errno; + $this->error = (string)$this->connection->error; + $this->errorno = (int)$this->connection->errno; $retval = false; $is_error = true; } else { @@ -711,15 +711,15 @@ class Database } } - $this->error = (string) $error; - $this->errorno = (int) $errorno; + $this->error = (string)$error; + $this->errorno = (int)$errorno; } $this->profiler->stopRecording(); if ($this->config->get('system', 'db_log')) { $stamp2 = microtime(true); - $duration = (float) ($stamp2 - $stamp1); + $duration = (float)($stamp2 - $stamp1); if (($duration > $this->config->get('system', 'db_loglimit'))) { $duration = round($duration, 3); @@ -727,11 +727,11 @@ class Database @file_put_contents( $this->config->get('system', 'db_log'), - DateTimeFormat::utcNow() . "\t" . $duration . "\t" - . basename($backtrace[0]['file']) . "\t" - . $backtrace[0]['line'] . "\t" . $backtrace[0]['function'] . "\t" - . substr($this->replaceParameters($sql, $args), 0, 4000) . "\n", - FILE_APPEND, + DateTimeFormat::utcNow() . "\t" . $duration . "\t" . + basename($backtrace[0]['file']) . "\t" . + $backtrace[0]['line'] . "\t" . $backtrace[0]['function'] . "\t" . + substr($this->replaceParameters($sql, $args), 0, 4000) . "\n", + FILE_APPEND ); } } @@ -801,8 +801,6 @@ class Database $this->error = $error; $this->errorno = $errorno; - } elseif (!$retval) { - $this->logger->warning('Database execution was unsuccessful', ['sql' => $this->replaceParameters($sql, $params), 'timeout' => $timeout]); } $this->profiler->stopRecording(); @@ -831,7 +829,9 @@ class Database if (empty($condition)) { return DBStructure::existsTable($table); } - $first_key = array_key_first($condition); + + reset($condition); + $first_key = key($condition); if (!is_int($first_key)) { $fields = [$first_key]; } @@ -1039,10 +1039,6 @@ class Database return $result; } - if ($this->affectedRows() === 0) { - $this->logger->info('affectedRows is 0.', ['table' => $table, 'fields' => $param, 'sql' => $this->replaceParameters($sql, $param)]); - } - return $this->affectedRows() != 0; } @@ -1092,7 +1088,7 @@ class Database $id = $this->connection->insert_id; break; } - return (int) $id; + return (int)$id; } /** @@ -1486,19 +1482,6 @@ class Database return false; } - $sql = $this->getSQL($table, $fields, $condition, $params); - DBA::buildCondition($condition); - $result = $this->p($sql, $condition); - - if ($this->driver == self::PDO && !empty($result)) { - $this->currentTable = $table; - } - - return $result; - } - - public function getSQL(string $table, array $fields = [], array $condition = [], array $params = []): string - { if (count($fields) > 0) { $fields = $this->escapeFields($fields, $params); $select_string = implode(', ', $fields); @@ -1512,7 +1495,15 @@ class Database $param_string = DBA::buildParameter($params); - return "SELECT " . $select_string . " FROM " . $table_string . $condition_string . $param_string; + $sql = "SELECT " . $select_string . " FROM " . $table_string . $condition_string . $param_string; + + $result = $this->p($sql, $condition); + + if ($this->driver == self::PDO && !empty($result)) { + $this->currentTable = $table; + } + + return $result; } /** @@ -1560,7 +1551,7 @@ class Database $this->logger->notice('Invalid count.', ['table' => $table, 'row' => $row, 'expression' => $expression, 'condition' => $condition_string, 'callstack' => System::callstack()]); return 0; } else { - return (int) $row['count']; + return (int)$row['count']; } } @@ -1647,13 +1638,13 @@ class Database continue; } - if ((substr($types[$field], 0, 7) == 'tinyint') || (substr($types[$field], 0, 8) == 'smallint') - || (substr($types[$field], 0, 9) == 'mediumint') || (substr($types[$field], 0, 3) == 'int') - || (substr($types[$field], 0, 6) == 'bigint') || (substr($types[$field], 0, 7) == 'boolean')) { - $fields[$field] = (int) $content; + if ((substr($types[$field], 0, 7) == 'tinyint') || (substr($types[$field], 0, 8) == 'smallint') || + (substr($types[$field], 0, 9) == 'mediumint') || (substr($types[$field], 0, 3) == 'int') || + (substr($types[$field], 0, 6) == 'bigint') || (substr($types[$field], 0, 7) == 'boolean')) { + $fields[$field] = (int)$content; } if ((substr($types[$field], 0, 5) == 'float') || (substr($types[$field], 0, 6) == 'double')) { - $fields[$field] = (float) $content; + $fields[$field] = (float)$content; } } @@ -1893,7 +1884,7 @@ class Database if (is_bool($value)) { $value = ($value ? 'true' : 'false'); } elseif (is_float($value) || is_integer($value)) { - $value = (string) $value; + $value = (string)$value; } else { $value = "'" . $this->escape($value) . "'"; } diff --git a/src/Database/Definition/DbaDefinition.php b/src/Database/Definition/DbaDefinition.php index 459a041387..c5d77d3a46 100644 --- a/src/Database/Definition/DbaDefinition.php +++ b/src/Database/Definition/DbaDefinition.php @@ -70,7 +70,7 @@ class DbaDefinition // Assign all field that are present in the table foreach ($fieldNames as $field) { - if (isset($data[$field]) || (!isset($definition[$table]['fields'][$field]['not null']) && array_key_exists($field, $data))) { + if (isset($data[$field])) { // Limit the length of varchar, varbinary, char and binary fields if (is_string($data[$field]) && preg_match("/char\((\d*)\)/", $definition[$table]['fields'][$field]['type'], $result)) { if ($charset == 'latin1') { diff --git a/src/Database/PostUpdate.php b/src/Database/PostUpdate.php index d14ce32343..031c2fec1d 100644 --- a/src/Database/PostUpdate.php +++ b/src/Database/PostUpdate.php @@ -35,9 +35,9 @@ use GuzzleHttp\Psr7\Uri; class PostUpdate { // Needed for the helper function to read from the legacy term table - public const OBJECT_TYPE_POST = 1; + const OBJECT_TYPE_POST = 1; - public const VERSION = 1550; + const VERSION = 1550; /** * Calls the post update functions @@ -185,7 +185,7 @@ class PostUpdate Protocol::DIASPORA, Protocol::OSTATUS, Protocol::ACTIVITYPUB, - 0, + 0 ); while ($contact = DBA::fetch($contacts)) { @@ -348,7 +348,7 @@ class PostUpdate Tag::MENTION, Tag::EXCLUSIVE_MENTION, Tag::IMPLICIT_MENTION, - $id, + $id ); if (DBA::errorNo() != 0) { @@ -357,7 +357,7 @@ class PostUpdate } while ($term = DBA::fetch($terms)) { - if (($term['type'] == Tag::MENTION) && !empty($term['url']) && !strstr($term['body'], (string) $term['url'])) { + if (($term['type'] == Tag::MENTION) && !empty($term['url']) && !strstr($term['body'], $term['url'])) { $condition = ['nurl' => Strings::normaliseLink($term['url']), 'uid' => 0, 'deleted' => false]; $contact = DBA::selectFirst('contact', ['url', 'alias'], $condition, ['order' => ['id']]); if (!DBA::isResult($contact)) { @@ -366,7 +366,7 @@ class PostUpdate $contact = DBA::selectFirst('contact', ['url', 'alias'], $condition, ['order' => ['id']]); } - if (DBA::isResult($contact) && (!strstr($term['body'], (string) $contact['url']) && (empty($contact['alias']) || !strstr($term['body'], (string) $contact['alias'])))) { + if (DBA::isResult($contact) && (!strstr($term['body'], $contact['url']) && (empty($contact['alias']) || !strstr($term['body'], $contact['alias'])))) { $term['type'] = Tag::IMPLICIT_MENTION; } } @@ -507,7 +507,7 @@ class PostUpdate 'term', ['oid'], ["`type` IN (?, ?) AND `oid` >= ?", Category::CATEGORY, Category::FILE, $id], - ['order' => ['oid'], 'limit' => 1000, 'group_by' => ['oid']], + ['order' => ['oid'], 'limit' => 1000, 'group_by' => ['oid']] ); if (DBA::errorNo() != 0) { @@ -651,7 +651,7 @@ class PostUpdate DBA::update( 'contact', ['gsid' => GServer::getRealID($contact['baseurl'], true), 'baseurl' => GServer::cleanURL($contact['baseurl'])], - ['id' => $contact['id']], + ['id' => $contact['id']] ); ++$rows; @@ -706,7 +706,7 @@ class PostUpdate DBA::update( 'apcontact', ['gsid' => GServer::getRealID($apcontact['baseurl'], true), 'baseurl' => GServer::cleanURL($apcontact['baseurl'])], - ['url' => $apcontact['url']], + ['url' => $apcontact['url']] ); ++$rows; @@ -1079,7 +1079,7 @@ class PostUpdate WHERE NOT `source` IS NULL AND `conversation`.`protocol` = ? AND `uri-id` > ? LIMIT ?", Conversation::PARCEL_ACTIVITYPUB, $id, - 1000, + 1000 ); if (DBA::errorNo() != 0) { @@ -1239,12 +1239,12 @@ class PostUpdate $parts = parse_url($contact['url']); unset($parts['path']); - $server = (string) Uri::fromParts($parts); + $server = (string)Uri::fromParts($parts); DBA::update( 'contact', ['gsid' => GServer::getRealID($server, true), 'baseurl' => GServer::cleanURL($server)], - ['id' => $contact['id']], + ['id' => $contact['id']] ); ++$rows; @@ -1333,10 +1333,10 @@ class PostUpdate return true; } - $id = (int) (DI::keyValue()->get('post_update_version_1544_id') ?? 0); + $id = (int)(DI::keyValue()->get('post_update_version_1544_id') ?? 0); if ($id == 0) { $post = Post::selectFirstPost(['uri-id'], [], ['order' => ['uri-id' => true]]); - $id = (int) ($post['uri-id'] ?? 0); + $id = (int)($post['uri-id'] ?? 0); } DI::logger()->info('Start', ['uri-id' => $id]); @@ -1398,10 +1398,10 @@ class PostUpdate } DBA::close($engagements); - $id = (int) (DI::keyValue()->get('post_update_version_1550_id') ?? 0); + $id = (int)(DI::keyValue()->get('post_update_version_1550_id') ?? 0); if ($id == 0) { $post = Post::selectFirstPost(['uri-id'], [], ['order' => ['uri-id' => true]]); - $id = (int) ($post['uri-id'] ?? 0); + $id = (int)($post['uri-id'] ?? 0); } DI::logger()->info('Start', ['uri-id' => $id]); diff --git a/src/Factory/Api/Mastodon/Notification.php b/src/Factory/Api/Mastodon/Notification.php index c235451a71..4b78a90b8a 100644 --- a/src/Factory/Api/Mastodon/Notification.php +++ b/src/Factory/Api/Mastodon/Notification.php @@ -33,11 +33,12 @@ class Notification extends BaseFactory /** * @param NotificationEntity $Notification + * @param bool $display_quotes Display quoted posts * * @return MstdnNotification * @throws UnexpectedNotificationTypeException */ - public function createFromNotification(NotificationEntity $Notification): MstdnNotification + public function createFromNotification(NotificationEntity $Notification, bool $display_quotes): MstdnNotification { $type = self::getType($Notification); @@ -49,7 +50,7 @@ class Notification extends BaseFactory if ($Notification->targetUriId) { try { - $status = $this->mstdnStatusFactory->createFromUriId($Notification->targetUriId, $Notification->uid); + $status = $this->mstdnStatusFactory->createFromUriId($Notification->targetUriId, $Notification->uid, $display_quotes); } catch (\Exception $exception) { $status = null; } diff --git a/src/Factory/Api/Mastodon/Status.php b/src/Factory/Api/Mastodon/Status.php index 7fa09affd7..54ca7ce008 100644 --- a/src/Factory/Api/Mastodon/Status.php +++ b/src/Factory/Api/Mastodon/Status.php @@ -87,6 +87,7 @@ class Status extends BaseFactory /** * @param int $uriId Uri-ID of the item * @param int $uid Item user + * @param bool $display_quote Display quoted posts * @param bool $reblog Check for reblogged post * @param bool $in_reply_status Add an "in_reply_status" element * @@ -94,7 +95,7 @@ class Status extends BaseFactory * @throws InternalServerErrorException * @throws ImagickException|NotFoundException */ - public function createFromUriId(int $uriId, int $uid = 0, bool $reblog = true, bool $in_reply_status = true): \Friendica\Object\Api\Mastodon\Status + public function createFromUriId(int $uriId, int $uid = 0, bool $display_quote = false, bool $reblog = true, bool $in_reply_status = true): \Friendica\Object\Api\Mastodon\Status { $fields = ['uri-id', 'uid', 'author-id', 'causer-id', 'author-uri-id', 'author-link', 'author-gsid', 'causer-uri-id', 'post-reason', 'starred', 'app', 'title', 'body', 'raw-body', 'content-warning', 'question-id', 'created', 'edited', 'commented', 'received', 'changed', 'network', 'thr-parent-id', 'parent-author-id', 'language', 'uri', 'plink', 'private', 'vid', 'gravity', 'featured', 'has-media', 'quote-uri-id', @@ -260,12 +261,56 @@ class Status extends BaseFactory $poll = null; } - $quote = self::createQuote($item, $uid); + if ($display_quote) { + $quote = self::createQuote($item, $uid); - $item['body'] = BBCode::removeSharedData($item['body']); + $item['body'] = BBCode::removeSharedData($item['body']); - if (!is_null($item['raw-body'])) { - $item['raw-body'] = BBCode::removeSharedData($item['raw-body']); + if (!is_null($item['raw-body'])) { + $item['raw-body'] = BBCode::removeSharedData($item['raw-body']); + } + } else { + // We can always safely add attached activities. Real quotes are added to the body via "addSharedPost". + if (empty($item['quote-uri-id'])) { + $quote = self::createQuote($item, $uid); + } else { + $quote = []; + } + + $shared = $this->contentItem->getSharedPost($item, ['uri-id']); + if (!empty($shared)) { + $shared_uri_id = $shared['post']['uri-id']; + + foreach ($this->mstdnMentionFactory->createFromUriId($shared_uri_id)->getArrayCopy() as $mention) { + if (!in_array($mention, $mentions)) { + $mentions[] = $mention; + } + } + + foreach ($this->mstdnTagFactory->createFromUriId($shared_uri_id) as $tag) { + if (!in_array($tag, $tags)) { + $tags[] = $tag; + } + } + + foreach ($this->mstdnAttachmentFactory->createFromUriId($shared_uri_id) as $attachment) { + if (!in_array($attachment, $attachments)) { + $attachments[] = $attachment; + } + } + + if (empty($card->toArray())) { + $card = $this->mstdnCardFactory->createFromUriId($shared_uri_id); + } + } + + if (!is_null($item['raw-body'])) { + $item['raw-body'] = $this->contentItem->addSharedPost($item, $item['raw-body']); + $item['raw-body'] = Post\Media::addHTMLLinkToBody($uriId, $item['raw-body']); + } else { + $item['body'] = $this->contentItem->addSharedPost($item); + $item['body'] = Post\Media::addHTMLLinkToBody($uriId, $item['body']); + } } $emojis = null; @@ -285,7 +330,7 @@ class Status extends BaseFactory if ($is_reshare) { try { - $reshare = $this->createFromUriId($uriId, $uid, false, false)->toArray(); + $reshare = $this->createFromUriId($uriId, $uid, $display_quote, false, false)->toArray(); } catch (\Exception $exception) { DI::logger()->info('Reshare not fetchable', ['uri-id' => $item['uri-id'], 'uid' => $uid, 'exception' => $exception]); $reshare = []; @@ -296,7 +341,7 @@ class Status extends BaseFactory if ($in_reply_status && ($item['gravity'] == Item::GRAVITY_COMMENT)) { try { - $in_reply = $this->createFromUriId($item['thr-parent-id'], $uid, false, false)->toArray(); + $in_reply = $this->createFromUriId($item['thr-parent-id'], $uid, $display_quote, false, false)->toArray(); } catch (\Exception $exception) { DI::logger()->info('Reply post not fetchable', ['uri-id' => $item['uri-id'], 'uid' => $uid, 'exception' => $exception]); $in_reply = []; @@ -337,7 +382,7 @@ class Status extends BaseFactory if (!empty($quote_id) && ($quote_id != $item['uri-id'])) { try { - $quoted_status = $this->createFromUriId($quote_id, $uid, false, false)->toArray(); + $quoted_status = $this->createFromUriId($quote_id, $uid, false, false, false)->toArray(); $quote = [ 'state' => 'accepted', 'quoted_status' => $quoted_status, diff --git a/src/Model/Contact.php b/src/Model/Contact.php index fd0041528d..34a317488a 100644 --- a/src/Model/Contact.php +++ b/src/Model/Contact.php @@ -15,6 +15,7 @@ use Friendica\Content\Conversation as ConversationContent; use Friendica\Content\Pager; use Friendica\Content\Text\HTML; use Friendica\Core\Protocol; +use Friendica\Core\Renderer; use Friendica\Core\System; use Friendica\Core\Worker; use Friendica\Database\Database; @@ -45,15 +46,15 @@ use Friendica\Worker\UpdateContact; */ class Contact { - public const DEFAULT_AVATAR_PHOTO = '/images/person-300.jpg'; - public const DEFAULT_AVATAR_THUMB = '/images/person-80.jpg'; - public const DEFAULT_AVATAR_MICRO = '/images/person-48.jpg'; + const DEFAULT_AVATAR_PHOTO = '/images/person-300.jpg'; + const DEFAULT_AVATAR_THUMB = '/images/person-80.jpg'; + const DEFAULT_AVATAR_MICRO = '/images/person-48.jpg'; /** * @} */ - public const LOCK_INSERT = 'contact-insert'; + const LOCK_INSERT = 'contact-insert'; /** * Account types @@ -76,12 +77,12 @@ class Contact * This will only be assigned to contacts, not to user accounts * @{ */ - public const TYPE_UNKNOWN = -1; - public const TYPE_PERSON = User::ACCOUNT_TYPE_PERSON; - public const TYPE_ORGANISATION = User::ACCOUNT_TYPE_ORGANISATION; - public const TYPE_NEWS = User::ACCOUNT_TYPE_NEWS; - public const TYPE_COMMUNITY = User::ACCOUNT_TYPE_COMMUNITY; - public const TYPE_RELAY = User::ACCOUNT_TYPE_RELAY; + const TYPE_UNKNOWN = -1; + const TYPE_PERSON = User::ACCOUNT_TYPE_PERSON; + const TYPE_ORGANISATION = User::ACCOUNT_TYPE_ORGANISATION; + const TYPE_NEWS = User::ACCOUNT_TYPE_NEWS; + const TYPE_COMMUNITY = User::ACCOUNT_TYPE_COMMUNITY; + const TYPE_RELAY = User::ACCOUNT_TYPE_RELAY; /** * @} */ @@ -92,11 +93,11 @@ class Contact * Relationship types * @{ */ - public const NOTHING = 0; // There is no relationship between the contact and the user - public const FOLLOWER = 1; // The contact is following this user (the contact is the subscriber) - public const SHARING = 2; // The contact shares their content with this user (the user is the subscriber) - public const FRIEND = 3; // There is a mutual relationship between the contact and the user - public const SELF = 4; // This is the user theirself + const NOTHING = 0; // There is no relationship between the contact and the user + const FOLLOWER = 1; // The contact is following this user (the contact is the subscriber) + const SHARING = 2; // The contact shares their content with this user (the user is the subscriber) + const FRIEND = 3; // There is a mutual relationship between the contact and the user + const SELF = 4; // This is the user theirself /** * @} */ @@ -186,14 +187,14 @@ class Contact 'failure_update', 'failed', 'term-date', 'last-item', 'last-discovery', 'local-data', 'readonly', 'contact-type', 'manually-approve', 'archive', 'unsearchable', 'sensitive', 'baseurl', 'gsid', 'bd', 'photo', 'thumb', 'micro', 'name-date', 'uri-date', 'avatar-date', - 'request', 'confirm', 'poco', 'writable', 'forum', 'prv', 'bdyear', + 'request', 'confirm', 'poco', 'writable', 'forum', 'prv', 'bdyear' ]; $contact = self::selectFirst($fields, ['id' => $cid]); if (empty($contact)) { return false; } $contact['uid'] = 0; - return (bool) self::insert($contact); + return (bool)self::insert($contact); } /** @@ -823,7 +824,7 @@ class Contact $user = DBA::selectFirst( 'user', ['uid', 'username', 'nickname', 'pubkey', 'prvkey'], - ['uid' => $uid, 'account_removed' => false, 'account_expired' => false], + ['uid' => $uid, 'account_removed' => false, 'account_expired' => false] ); if (!DBA::isResult($user)) { return false; @@ -851,14 +852,14 @@ class Contact 'uri-date' => DateTimeFormat::utcNow(), 'avatar-date' => DateTimeFormat::utcNow(), 'baseurl' => DI::baseUrl(), - 'closeness' => 0, + 'closeness' => 0 ]; $return = true; // Only create the entry if it doesn't exist yet if (!DBA::exists('contact', ['uid' => $uid, 'self' => true])) { - $return = (bool) self::insert($contact); + $return = (bool)self::insert($contact); } // Create the public contact @@ -886,7 +887,7 @@ class Contact $fields = [ 'id', 'uri-id', 'name', 'nick', 'location', 'about', 'keywords', 'avatar', 'prvkey', 'pubkey', 'manually-approve', 'xmpp', 'matrix', 'contact-type', 'forum', 'prv', 'avatar-date', 'url', 'nurl', 'unsearchable', - 'photo', 'thumb', 'micro', 'header', 'addr', 'request', 'notify', 'poll', 'confirm', 'poco', 'network', 'baseurl', 'gsid', + 'photo', 'thumb', 'micro', 'header', 'addr', 'request', 'notify', 'poll', 'confirm', 'poco', 'network', 'baseurl', 'gsid' ]; $self = DBA::selectFirst('contact', $fields, ['uid' => $uid, 'self' => true]); if (!DBA::isResult($self)) { @@ -901,7 +902,7 @@ class Contact $fields = [ 'name', 'photo', 'thumb', 'about', 'address', 'locality', 'region', - 'country-name', 'pub_keywords', 'xmpp', 'matrix', 'net-publish', + 'country-name', 'pub_keywords', 'xmpp', 'matrix', 'net-publish' ]; $profile = DBA::selectFirst('profile', $fields, ['uid' => $uid]); if (!DBA::isResult($profile)) { @@ -986,7 +987,7 @@ class Contact // Update the profile $fields = [ 'photo' => User::getAvatarUrl($user), - 'thumb' => User::getAvatarUrl($user, Proxy::SIZE_THUMB), + 'thumb' => User::getAvatarUrl($user, Proxy::SIZE_THUMB) ]; DBA::update('profile', $fields, ['uid' => $uid]); @@ -1422,7 +1423,7 @@ class Contact $fields = [ 'name', 'nick', 'url', 'addr', 'alias', 'avatar', 'header', 'contact-type', 'keywords', 'location', 'about', 'unsearchable', 'batch', 'notify', 'poll', - 'request', 'confirm', 'poco', 'subscribe', 'network', 'baseurl', 'gsid', + 'request', 'confirm', 'poco', 'subscribe', 'network', 'baseurl', 'gsid' ]; $personal_contact = DBA::selectFirst('contact', $fields, ["`addr` = ? AND `uid` != 0", $url]); @@ -1505,13 +1506,17 @@ class Contact if ($data['network'] == Protocol::DIASPORA) { try { DI::dsprContact()->updateFromProbeArray($data); - } catch (NotFoundException|\InvalidArgumentException $e) { + } catch (NotFoundException $e) { + DI::logger()->notice($e->getMessage(), ['url' => $url, 'data' => $data]); + } catch (\InvalidArgumentException $e) { DI::logger()->notice($e->getMessage(), ['url' => $url, 'data' => $data]); } } elseif (!empty($data['networks'][Protocol::DIASPORA])) { try { DI::dsprContact()->updateFromProbeArray($data['networks'][Protocol::DIASPORA]); - } catch (NotFoundException|\InvalidArgumentException $e) { + } catch (NotFoundException $e) { + DI::logger()->notice($e->getMessage(), ['url' => $url, 'data' => $data['networks'][Protocol::DIASPORA]]); + } catch (\InvalidArgumentException $e) { DI::logger()->notice($e->getMessage(), ['url' => $url, 'data' => $data['networks'][Protocol::DIASPORA]]); } } @@ -1620,29 +1625,29 @@ class Contact /** * Returns posts from a given contact url * - * @param string $contact_url Contact URL - * @param int $uid User ID - * @param bool $only_media Only display media content - * @param array $request Request variables + * @param string $contact_url Contact URL + * @param int $uid User ID + * @param bool $only_media Only display media content + * @param string $last_created Newest creation date, used for paging * @return string posts in HTML * @throws Exception */ - public static function getPostsFromUrl(string $contact_url, int $uid, bool $only_media = false, array $request = []): string + public static function getPostsFromUrl(string $contact_url, int $uid, bool $only_media = false, ?string $last_created = null): string { - return self::getPostsFromId(self::getIdForURL($contact_url), $uid, $only_media, $request); + return self::getPostsFromId(self::getIdForURL($contact_url), $uid, $only_media, $last_created); } /** * Returns posts from a given contact id * - * @param int $cid Contact ID - * @param int $uid User ID - * @param bool $only_media Only display media content - * @param array $request Request variables + * @param int $cid Contact ID + * @param int $uid User ID + * @param bool $only_media Only display media content + * @param string $last_created Newest creation date, used for paging * @return string posts in HTML * @throws Exception */ - public static function getPostsFromId(int $cid, int $uid, bool $only_media = false, array $request = []): string + public static function getPostsFromId(int $cid, int $uid, bool $only_media = false, ?string $last_created = null): string { $contact = DBA::selectFirst('contact', ['contact-type', 'network', 'name', 'nick'], ['id' => $cid]); if (!DBA::isResult($contact)) { @@ -1663,14 +1668,14 @@ class Contact $condition = DBA::mergeConditions($condition, ["`$contact_field` = ? AND `gravity` IN (?, ?)", $cid, Item::GRAVITY_PARENT, Item::GRAVITY_COMMENT]); - if (!empty($request['last_created'])) { - $condition = DBA::mergeConditions($condition, ["`created` < ?", $request['last_created']]); + if (!empty($last_created)) { + $condition = DBA::mergeConditions($condition, ["`created` < ?", $last_created]); } if ($only_media) { $condition = DBA::mergeConditions($condition, [ "`uri-id` IN (SELECT `uri-id` FROM `post-media` WHERE `type` IN (?, ?, ?, ?))", - Post\Media::AUDIO, Post\Media::IMAGE, Post\Media::VIDEO, Post\Media::HLS, + Post\Media::AUDIO, Post\Media::IMAGE, Post\Media::VIDEO, Post\Media::HLS ]); } @@ -1683,13 +1688,21 @@ class Contact $pager = new Pager(DI::l10n(), DI::args()->getQueryString(), $itemsPerPage); $params = ['order' => ['created' => true], 'limit' => [$pager->getStart(), $pager->getItemsPerPage()]]; + + if (DI::pConfig()->get($uid, 'system', 'infinite_scroll', true)) { + $tpl = Renderer::getMarkupTemplate('infinite_scroll_head.tpl'); + $o = Renderer::replaceMacros($tpl, ['$reload_uri' => DI::args()->getQueryString()]); + } else { + $o = ''; + } + $fields = array_merge(Item::DISPLAY_FIELDLIST, ['featured']); $items = Post::toArray(Post::selectForUser($uid, $fields, $condition, $params)); - $o = DI::conversation()->render($items, ConversationContent::MODE_CONTACT_POSTS, isset($request['mode']) && ($request['mode'] == 'raw')); + $o .= DI::conversation()->render($items, ConversationContent::MODE_CONTACT_POSTS); if (DI::pConfig()->get($uid, 'system', 'infinite_scroll', true)) { - $o .= HTML::scrollLoader($request); + $o .= HTML::scrollLoader(); } else { $o .= $pager->renderMinimal(count($items)); } @@ -1700,14 +1713,13 @@ class Contact /** * Returns threads from a given contact id * - * @param int $cid Contact ID - * @param int $update Update mode - * @param int $parent Item parent ID for the update mode - * @param array $request Request variables + * @param int $cid Contact ID + * @param int $update Update mode + * @param int $parent Item parent ID for the update mode * @return string posts in HTML * @throws Exception */ - public static function getThreadsFromId(int $cid, int $uid, int $update = 0, int $parent = 0, array $request = []): string + public static function getThreadsFromId(int $cid, int $uid, int $update = 0, int $parent = 0, string $last_created = ''): string { $contact = DBA::selectFirst('contact', ['contact-type', 'network', 'name', 'nick'], ['id' => $cid]); if (!DBA::isResult($contact)) { @@ -1726,8 +1738,8 @@ class Contact if (!empty($parent)) { $condition = DBA::mergeConditions($condition, ['parent' => $parent]); - } elseif (isset($request['last_created'])) { - $condition = DBA::mergeConditions($condition, ["`created` < ?", $request['last_created']]); + } elseif (!empty($last_created)) { + $condition = DBA::mergeConditions($condition, ["`created` < ?", $last_created]); } $contact_field = ((($contact["contact-type"] == self::TYPE_COMMUNITY) || ($contact['network'] == Protocol::MAIL)) ? 'owner-id' : 'author-id'); @@ -1740,11 +1752,18 @@ class Contact $pager = new Pager(DI::l10n(), DI::args()->getQueryString(), $itemsPerPage); + if (DI::pConfig()->get($uid, 'system', 'infinite_scroll', true)) { + $tpl = Renderer::getMarkupTemplate('infinite_scroll_head.tpl'); + $o = Renderer::replaceMacros($tpl, ['$reload_uri' => DI::args()->getQueryString()]); + } else { + $o = ''; + } + $condition1 = DBA::mergeConditions($condition, ["`$contact_field` = ? AND `gravity` = ?", $cid, Item::GRAVITY_PARENT]); $condition2 = DBA::mergeConditions($condition, [ "`author-id` = ? AND `gravity` = ? AND `vid` = ? AND `protocol` != ? AND `thr-parent-id` = `parent-uri-id`", - $cid, Item::GRAVITY_ACTIVITY, Verb::getID(Activity::ANNOUNCE), Conversation::PARCEL_DIASPORA, + $cid, Item::GRAVITY_ACTIVITY, Verb::getID(Activity::ANNOUNCE), Conversation::PARCEL_DIASPORA ]); $sql1 = "SELECT `uri-id`, `created` FROM `post-thread-user-view` WHERE " . array_shift($condition1); @@ -1757,19 +1776,17 @@ class Contact $union = array_merge($union, [$pager->getStart(), $pager->getItemsPerPage()]); $items = Post::toArray(DBA::p($sql, $union)); - $raw = isset($request['mode']) && ($request['mode'] == 'raw'); - - if (!$raw && !$update && ($pager->getStart() == 0)) { + if (empty($last_created) && ($pager->getStart() == 0)) { $fields = ['uri-id', 'thr-parent-id', 'gravity', 'author-id', 'created']; $pinned = Post\Collection::selectToArrayForContact($cid, Post\Collection::FEATURED, $fields); $items = array_merge($items, $pinned); } - $o = DI::conversation()->render($items, ConversationContent::MODE_CONTACTS, $update || $raw, false, 'pinned_created', $uid); + $o .= DI::conversation()->render($items, ConversationContent::MODE_CONTACTS, $update, false, 'pinned_created', $uid); if (!$update) { if (DI::pConfig()->get($uid, 'system', 'infinite_scroll', true)) { - $o .= HTML::scrollLoader($request); + $o .= HTML::scrollLoader(); } else { $o .= $pager->renderMinimal(count($items)); } @@ -2271,7 +2288,7 @@ class Contact { $condition = [ "`nurl` = ? AND ((`uid` = ? AND `network` IN (?, ?)) OR `uid` = ?)", - Strings::normaliseLink($url), $uid, Protocol::FEED, Protocol::MAIL, 0, + Strings::normaliseLink($url), $uid, Protocol::FEED, Protocol::MAIL, 0 ]; $contact = self::selectFirst(['id', 'updated'], $condition, ['order' => ['uid' => true]]); return self::getAvatarUrlForId($contact['id'] ?? 0, $size, $contact['updated'] ?? ''); @@ -2346,7 +2363,7 @@ class Contact $contact = DBA::selectFirst( 'contact', ['uid', 'avatar', 'photo', 'thumb', 'micro', 'blurhash', 'xmpp', 'addr', 'nurl', 'url', 'network', 'uri-id'], - ['id' => $cid, 'self' => false], + ['id' => $cid, 'self' => false] ); if (!DBA::isResult($contact)) { return; @@ -2428,7 +2445,7 @@ class Contact 'avatar-date' => DateTimeFormat::utcNow(), 'photo' => $avatar, 'thumb' => self::getDefaultAvatar($contact, Proxy::SIZE_THUMB), - 'micro' => self::getDefaultAvatar($contact, Proxy::SIZE_MICRO), + 'micro' => self::getDefaultAvatar($contact, Proxy::SIZE_MICRO) ]; DI::logger()->debug('Use default avatar', ['id' => $cid, 'uid' => $uid]); } @@ -2500,7 +2517,7 @@ class Contact $personal_contacts = DBA::select( 'contact', ['id', 'uid'], - ["`nurl` = ? AND `id` != ? AND NOT `self`", $contact['nurl'], $cid], + ["`nurl` = ? AND `id` != ? AND NOT `self`", $contact['nurl'], $cid] ); while ($personal_contact = DBA::fetch($personal_contacts)) { $cids[] = $personal_contact['id']; @@ -2697,9 +2714,9 @@ class Contact return true; } - $stamp = (float) microtime(true); + $stamp = (float)microtime(true); self::updateFromProbe($id); - DI::logger()->debug('Contact data is updated.', ['duration' => round((float) microtime(true) - $stamp, 3), 'id' => $id, 'url' => $contact['url']]); + DI::logger()->debug('Contact data is updated.', ['duration' => round((float)microtime(true) - $stamp, 3), 'id' => $id, 'url' => $contact['url']]); return true; } @@ -2715,7 +2732,7 @@ class Contact if (!empty($id)) { return self::updateByIdIfNeeded($id); } - return (bool) self::getIdForURL($url); + return (bool)self::getIdForURL($url); } /** @@ -2747,13 +2764,17 @@ class Contact if ($data['network'] == Protocol::DIASPORA) { try { DI::dsprContact()->updateFromProbeArray($data); - } catch (NotFoundException|\InvalidArgumentException $e) { + } catch (NotFoundException $e) { + DI::logger()->notice($e->getMessage(), ['id' => $id, 'network' => $network, 'contact' => $contact, 'data' => $data]); + } catch (\InvalidArgumentException $e) { DI::logger()->notice($e->getMessage(), ['id' => $id, 'network' => $network, 'contact' => $contact, 'data' => $data]); } } elseif (!empty($data['networks'][Protocol::DIASPORA])) { try { DI::dsprContact()->updateFromProbeArray($data['networks'][Protocol::DIASPORA]); - } catch (NotFoundException|\InvalidArgumentException $e) { + } catch (NotFoundException $e) { + DI::logger()->notice($e->getMessage(), ['id' => $id, 'network' => $network, 'contact' => $contact, 'data' => $data]); + } catch (\InvalidArgumentException $e) { DI::logger()->notice($e->getMessage(), ['id' => $id, 'network' => $network, 'contact' => $contact, 'data' => $data]); } } @@ -2824,7 +2845,7 @@ class Contact 'uid', 'uri-id', 'avatar', 'header', 'name', 'nick', 'location', 'keywords', 'about', 'subscribe', 'manually-approve', 'unsearchable', 'url', 'addr', 'batch', 'notify', 'poll', 'request', 'confirm', 'poco', 'network', 'alias', 'baseurl', 'gsid', 'forum', 'prv', 'contact-type', 'pubkey', 'last-item', 'xmpp', 'matrix', - 'created', 'last-update', + 'created', 'last-update' ]; /** @var array */ @@ -2898,8 +2919,8 @@ class Contact // We must not try to update relay contacts via probe. They are no real contacts. // See Relay::updateContact() for more details. // We check after the probing to be able to correct falsely detected contact types. - if (($contact['contact-type'] == self::TYPE_RELAY) && Strings::compareLink($contact['url'], $contact['baseurl'] ?? '') - && (!Strings::compareLink($ret['url'], $contact['url']) || in_array($ret['network'], [Protocol::FEED, Protocol::PHANTOM])) + if (($contact['contact-type'] == self::TYPE_RELAY) && Strings::compareLink($contact['url'], $contact['baseurl'] ?? '') && + (!Strings::compareLink($ret['url'], $contact['url']) || in_array($ret['network'], [Protocol::FEED, Protocol::PHANTOM])) ) { if (GServer::reachable($contact)) { self::updateContact($id, $uid, $uriid, $contact['url'], ['failed' => false, 'local-data' => $has_local_data, 'last-update' => $updated, 'next-update' => $success_next_update, 'success_update' => $updated, 'unsearchable' => true]); @@ -2934,8 +2955,8 @@ class Contact $ret['prv'] = false; $ret['contact-type'] = $ret['account-type']; if (($ret['contact-type'] == User::ACCOUNT_TYPE_COMMUNITY) && isset($ret['manually-approve'])) { - $ret['forum'] = (bool) !$ret['manually-approve']; - $ret['prv'] = (bool) !$ret['forum']; + $ret['forum'] = (bool)!$ret['manually-approve']; + $ret['prv'] = (bool)!$ret['forum']; } } @@ -3218,7 +3239,7 @@ class Contact $pending = false; if (($protocol == Protocol::ACTIVITYPUB) && isset($ret['manually-approve'])) { - $pending = (bool) $ret['manually-approve']; + $pending = (bool)$ret['manually-approve']; } $writeable = in_array($protocol, [Protocol::MAIL, Protocol::DIASPORA, Protocol::ACTIVITYPUB]); @@ -3353,8 +3374,8 @@ class Contact // Contact is blocked at user-level if ( - !empty($contact['id']) && !empty($importer['id']) - && Contact\User::isBlocked($contact['id'], $importer['id']) + !empty($contact['id']) && !empty($importer['id']) && + Contact\User::isBlocked($contact['id'], $importer['id']) ) { return false; } @@ -3367,7 +3388,7 @@ class Contact ) { self::update( ['rel' => self::FRIEND, 'writable' => true, 'pending' => false], - ['id' => $contact['id'], 'uid' => $importer['uid']], + ['id' => $contact['id'], 'uid' => $importer['uid']] ); } @@ -3429,7 +3450,7 @@ class Contact $intro = DI::introFactory()->createNew( $importer['uid'], $contact_record['id'], - $note, + $note ); DI::intro()->save($intro); } @@ -3546,7 +3567,7 @@ class Contact AND NOT `contact`.`deleted`', DBA::NULL_DATE, self::SHARING, - self::FRIEND, + self::FRIEND ]; $contacts = DBA::select('contact', ['id', 'uid', 'name', 'url', 'bd'], $condition); @@ -3561,7 +3582,7 @@ class Contact DBA::update( 'contact', ['bdyear' => substr($nextbd, 0, 4), 'bd' => $nextbd], - ['id' => $contact['id']], + ['id' => $contact['id']] ); } } @@ -3773,7 +3794,7 @@ class Contact 'failed' => false, 'deleted' => false, 'unsearchable' => false, - 'uid' => $uid, + 'uid' => $uid ]; if (!$show_blocked) { @@ -3803,7 +3824,7 @@ class Contact $condition = DBA::mergeConditions( $condition, - ["(`addr` LIKE ? OR `name` LIKE ? OR `nick` LIKE ?)", $search, $search, $search], + ["(`addr` LIKE ? OR `name` LIKE ? OR `nick` LIKE ?)", $search, $search, $search] ); return DBA::selectToArray('account-user-view', [], $condition, $params); diff --git a/src/Model/Contact/Relation.php b/src/Model/Contact/Relation.php index 397c25720c..89a1104f8a 100644 --- a/src/Model/Contact/Relation.php +++ b/src/Model/Contact/Relation.php @@ -829,23 +829,27 @@ class Relation DI::logger()->debug('Calculation - start', ['uid' => $uid, 'cid' => $contact_id, 'days' => $days]); $follow = Verb::getID(Activity::FOLLOW); + $view = Verb::getID(Activity::VIEW); + $read = Verb::getID(Activity::READ); DBA::update('contact-relation', ['score' => 0, 'relation-score' => 0, 'thread-score' => 0, 'relation-thread-score' => 0], ['relation-cid' => $contact_id]); $total = DBA::fetchFirst( - "SELECT count(*) AS `activity` FROM `post-user` INNER JOIN `post` ON `post`.`uri-id` = `post-user`.`thr-parent-id` WHERE `post-user`.`author-id` = ? AND `post-user`.`received` >= ? AND `post-user`.`uid` = ? AND `post`.`author-id` != ? AND `post`.`vid` != ?", + "SELECT count(*) AS `activity` FROM `post-user` INNER JOIN `post` ON `post`.`uri-id` = `post-user`.`thr-parent-id` WHERE `post-user`.`author-id` = ? AND `post-user`.`received` >= ? AND `post-user`.`uid` = ? AND `post`.`author-id` != ? AND NOT `post`.`vid` IN (?, ?, ?)", $contact_id, DateTimeFormat::utc('now - ' . $days . ' day'), $uid, $contact_id, - $follow + $follow, + $view, + $read ); DI::logger()->debug('Calculate relation-score', ['uid' => $uid, 'total' => $total['activity']]); $interactions = DBA::p( "SELECT `post`.`author-id`, count(*) AS `activity`, EXISTS(SELECT `pid` FROM `account-user-view` WHERE `pid` = `post`.`author-id` AND `uid` = ? AND `rel` IN (?, ?)) AS `follows` - FROM `post-user` INNER JOIN `post` ON `post`.`uri-id` = `post-user`.`thr-parent-id` WHERE `post-user`.`author-id` = ? AND `post-user`.`received` >= ? AND `post-user`.`uid` = ? AND `post`.`author-id` != ? AND `post`.`vid` != ? GROUP BY `post`.`author-id`", + FROM `post-user` INNER JOIN `post` ON `post`.`uri-id` = `post-user`.`thr-parent-id` WHERE `post-user`.`author-id` = ? AND `post-user`.`received` >= ? AND `post-user`.`uid` = ? AND `post`.`author-id` != ? AND NOT `post`.`vid` IN (?, ?, ?) GROUP BY `post`.`author-id`", $uid, Contact::SHARING, Contact::FRIEND, @@ -853,7 +857,9 @@ class Relation DateTimeFormat::utc('now - ' . $days . ' day'), $uid, $contact_id, - $follow + $follow, + $view, + $read ); while ($interaction = DBA::fetch($interactions)) { $score = min((int)(($interaction['activity'] / $total['activity']) * 65535), 65535); @@ -862,19 +868,21 @@ class Relation DBA::close($interactions); $total = DBA::fetchFirst( - "SELECT count(*) AS `activity` FROM `post-user` INNER JOIN `post` ON `post`.`uri-id` = `post-user`.`parent-uri-id` WHERE `post-user`.`author-id` = ? AND `post-user`.`received` >= ? AND `post-user`.`uid` = ? AND `post`.`author-id` != ? AND `post`.`vid` != ?", + "SELECT count(*) AS `activity` FROM `post-user` INNER JOIN `post` ON `post`.`uri-id` = `post-user`.`parent-uri-id` WHERE `post-user`.`author-id` = ? AND `post-user`.`received` >= ? AND `post-user`.`uid` = ? AND `post`.`author-id` != ? AND NOT `post`.`vid` IN (?, ?, ?)", $contact_id, DateTimeFormat::utc('now - ' . $days . ' day'), $uid, $contact_id, - $follow + $follow, + $view, + $read ); DI::logger()->debug('Calculate relation-thread-score', ['uid' => $uid, 'total' => $total['activity']]); $interactions = DBA::p( "SELECT `post`.`author-id`, count(*) AS `activity`, EXISTS(SELECT `pid` FROM `account-user-view` WHERE `pid` = `post`.`author-id` AND `uid` = ? AND `rel` IN (?, ?)) AS `follows` - FROM `post-user` INNER JOIN `post` ON `post`.`uri-id` = `post-user`.`parent-uri-id` WHERE `post-user`.`author-id` = ? AND `post-user`.`received` >= ? AND `post-user`.`uid` = ? AND `post`.`author-id` != ? AND `post`.`vid` != ? GROUP BY `post`.`author-id`", + FROM `post-user` INNER JOIN `post` ON `post`.`uri-id` = `post-user`.`parent-uri-id` WHERE `post-user`.`author-id` = ? AND `post-user`.`received` >= ? AND `post-user`.`uid` = ? AND `post`.`author-id` != ? AND NOT `post`.`vid` IN (?, ?, ?) GROUP BY `post`.`author-id`", $uid, Contact::SHARING, Contact::FRIEND, @@ -882,7 +890,9 @@ class Relation DateTimeFormat::utc('now - ' . $days . ' day'), $uid, $contact_id, - $follow + $follow, + $view, + $read ); while ($interaction = DBA::fetch($interactions)) { $score = min((int)(($interaction['activity'] / $total['activity']) * 65535), 65535); @@ -891,23 +901,27 @@ class Relation DBA::close($interactions); $total = DBA::fetchFirst( - "SELECT count(*) AS `activity` FROM `post-user` INNER JOIN `post` ON `post-user`.`uri-id` = `post`.`thr-parent-id` WHERE `post-user`.`author-id` = ? AND `post-user`.`received` >= ? AND `post-user`.`uid` = ? AND `post`.`author-id` != ? AND `post`.`vid` != ?", + "SELECT count(*) AS `activity` FROM `post-user` INNER JOIN `post` ON `post-user`.`uri-id` = `post`.`thr-parent-id` WHERE `post-user`.`author-id` = ? AND `post-user`.`received` >= ? AND `post-user`.`uid` = ? AND `post`.`author-id` != ? AND NOT `post`.`vid` IN (?, ?, ?)", $contact_id, DateTimeFormat::utc('now - ' . $days . ' day'), $uid, $contact_id, - $follow + $follow, + $view, + $read ); DI::logger()->debug('Calculate score', ['uid' => $uid, 'total' => $total['activity']]); $interactions = DBA::p( - "SELECT `post`.`author-id`, count(*) AS `activity` FROM `post-user` INNER JOIN `post` ON `post-user`.`uri-id` = `post`.`thr-parent-id` WHERE `post-user`.`author-id` = ? AND `post-user`.`received` >= ? AND `post-user`.`uid` = ? AND `post`.`author-id` != ? AND `post`.`vid` != ? GROUP BY `post`.`author-id`", + "SELECT `post`.`author-id`, count(*) AS `activity` FROM `post-user` INNER JOIN `post` ON `post-user`.`uri-id` = `post`.`thr-parent-id` WHERE `post-user`.`author-id` = ? AND `post-user`.`received` >= ? AND `post-user`.`uid` = ? AND `post`.`author-id` != ? AND NOT `post`.`vid` IN (?, ?, ?) GROUP BY `post`.`author-id`", $contact_id, DateTimeFormat::utc('now - ' . $days . ' day'), $uid, $contact_id, - $follow + $follow, + $view, + $read ); while ($interaction = DBA::fetch($interactions)) { $score = min((int)(($interaction['activity'] / $total['activity']) * 65535), 65535); @@ -916,23 +930,27 @@ class Relation DBA::close($interactions); $total = DBA::fetchFirst( - "SELECT count(*) AS `activity` FROM `post-user` INNER JOIN `post` ON `post-user`.`uri-id` = `post`.`parent-uri-id` WHERE `post-user`.`author-id` = ? AND `post-user`.`received` >= ? AND `post-user`.`uid` = ? AND `post`.`author-id` != ? AND `post`.`vid` != ?", + "SELECT count(*) AS `activity` FROM `post-user` INNER JOIN `post` ON `post-user`.`uri-id` = `post`.`parent-uri-id` WHERE `post-user`.`author-id` = ? AND `post-user`.`received` >= ? AND `post-user`.`uid` = ? AND `post`.`author-id` != ? AND NOT `post`.`vid` IN (?, ?, ?)", $contact_id, DateTimeFormat::utc('now - ' . $days . ' day'), $uid, $contact_id, - $follow + $follow, + $view, + $read ); DI::logger()->debug('Calculate thread-score', ['uid' => $uid, 'total' => $total['activity']]); $interactions = DBA::p( - "SELECT `post`.`author-id`, count(*) AS `activity` FROM `post-user` INNER JOIN `post` ON `post-user`.`uri-id` = `post`.`parent-uri-id` WHERE `post-user`.`author-id` = ? AND `post-user`.`received` >= ? AND `post-user`.`uid` = ? AND `post`.`author-id` != ? AND `post`.`vid` != ? GROUP BY `post`.`author-id`", + "SELECT `post`.`author-id`, count(*) AS `activity` FROM `post-user` INNER JOIN `post` ON `post-user`.`uri-id` = `post`.`parent-uri-id` WHERE `post-user`.`author-id` = ? AND `post-user`.`received` >= ? AND `post-user`.`uid` = ? AND `post`.`author-id` != ? AND NOT `post`.`vid` IN (?, ?, ?) GROUP BY `post`.`author-id`", $contact_id, DateTimeFormat::utc('now - ' . $days . ' day'), $uid, $contact_id, - $follow + $follow, + $view, + $read ); while ($interaction = DBA::fetch($interactions)) { $score = min((int)(($interaction['activity'] / $total['activity']) * 65535), 65535); diff --git a/src/Model/Event.php b/src/Model/Event.php index 4b880490a6..730d6082a3 100644 --- a/src/Model/Event.php +++ b/src/Model/Event.php @@ -23,7 +23,6 @@ use Friendica\Util\Map; use Friendica\Util\Strings; use Friendica\Util\Temporal; use Friendica\Util\XML; -use IntlDateFormatter; /** * functions for interacting with the event database table @@ -38,10 +37,12 @@ class Event $uriid = $event['uri-id'] ?? $uriid; - $event_start = DI::l10n()->formatDateTime($event['start'], IntlDateFormatter::FULL, IntlDateFormatter::LONG); + $bd_format = DI::l10n()->t('l F d, Y \@ g:i A \G\M\TP (e)'); // Friday October 29, 2021 @ 9:15 AM GMT-04:00 (America/New_York) + + $event_start = DI::l10n()->getDay(DateTimeFormat::local($event['start'], $bd_format)); if (!empty($event['finish'])) { - $event_end = DI::l10n()->formatDateTime($event['finish'], IntlDateFormatter::FULL, IntlDateFormatter::LONG); + $event_end = DI::l10n()->getDay(DateTimeFormat::local($event['finish'], $bd_format)); } else { $event_end = ''; } @@ -458,7 +459,7 @@ class Event 'dtstart_label' => DI::l10n()->t('Starts:'), 'dtend_label' => DI::l10n()->t('Finishes:'), - 'location_label' => DI::l10n()->t('Location:'), + 'location_label' => DI::l10n()->t('Location:') ]; } @@ -541,7 +542,7 @@ class Event AND `event`.`uid` = ? $sql_perms", $event_id, - $owner_uid, + $owner_uid )); if (empty($events)) { throw new NotFoundException(DI::l10n()->t('Event not found.')); @@ -606,7 +607,7 @@ class Event $owner_uid, $start, $start, - $finish, + $finish )); $events = self::removeDuplicates($events); @@ -635,7 +636,8 @@ class Event $start = DateTimeFormat::local($event['start'], 'c'); $j = DateTimeFormat::local($event['start'], 'j'); - $day = DI::l10n()->fullDate($event['start']); + $day = DateTimeFormat::local($event['start'], $fmt); + $day = DI::l10n()->getDay($day); if ($event['nofinish']) { $end = null; @@ -656,7 +658,7 @@ class Event $title = strip_tags(BBCode::convertForUriId($event['uri-id'], $event['summary'])); if (!$title) { - [$title, $_trash] = explode("t('l F d, Y \@ g:i A'); // Friday January 18, 2011 @ 8:01 AM. + $dformat_short = DI::l10n()->t('D g:i A'); // Fri 8:01 AM. + $tformat = DI::l10n()->t('g:i A'); // 8:01 AM. + $tzformat = DI::l10n()->t('e'); // Atlantic/Azores. + // Convert the time to different formats. - $dtstart_dt = DI::l10n()->fullDateTime($item['event-start']); + $dtstart_dt = DI::l10n()->getDay(DateTimeFormat::local($item['event-start'], $dformat)); $dtstart_title = DateTimeFormat::utc($item['event-start'], DateTimeFormat::ATOM); // Format: Jan till Dec. - $month_short = DI::l10n()->formatDateTimeByPattern($item['event-start'], 'MMM'); + $month_short = DI::l10n()->getDayShort(DateTimeFormat::local($item['event-start'], 'M')); // Format: 1 till 31. - $date_short = DI::l10n()->formatDateTimeByPattern($item['event-start'], 'd'); - $start_time = DI::l10n()->formatDateTime($item['event-start'], IntlDateFormatter::NONE, IntlDateFormatter::SHORT); - $start_day = DI::l10n()->formatDateTimeByPattern($item['event-start'], 'eeee'); - $start_short = $start_day . ' ' . $start_time; - $timezone = DI::l10n()->formatDateTimeByPattern($item['event-start'], 'vvvv'); + $date_short = DateTimeFormat::local($item['event-start'], 'j'); + $start_time = DateTimeFormat::local($item['event-start'], $tformat); + $start_short = DI::l10n()->getDayShort(DateTimeFormat::local($item['event-start'], $dformat_short)); + $timezone = DateTimeFormat::local($item['event-start'], $tzformat); // If the option 'nofinisch' isn't set, we need to format the finish date/time. if (!$item['event-nofinish']) { $finish = true; - $dtend_dt = DI::l10n()->fullDateTime($item['event-finish']); + $dtend_dt = DI::l10n()->getDay(DateTimeFormat::local($item['event-finish'], $dformat)); $dtend_title = DateTimeFormat::utc($item['event-finish'], DateTimeFormat::ATOM); - $end_time = DI::l10n()->formatDateTime($item['event-finish'], IntlDateFormatter::NONE, IntlDateFormatter::SHORT); - $end_day = DI::l10n()->formatDateTimeByPattern($item['event-finish'], 'eeee'); - $end_short = $end_day . ' ' . $end_time; + $end_short = DI::l10n()->getDayShort(DateTimeFormat::utc($item['event-finish'], $dformat_short)); + $end_time = DateTimeFormat::local($item['event-finish'], $tformat); // Check if start and finish time is at the same day. if (substr($dtstart_title, 0, 10) === substr($dtend_title, 0, 10)) { $same_date = true; @@ -917,7 +923,7 @@ class Event 'id' => $item['author-id'], 'network' => $item['author-network'], 'url' => $item['author-link'], - 'alias' => $item['author-alias'], + 'alias' => $item['author-alias'] ]; $profile_link = Contact::magicLinkByContact($author); @@ -1021,7 +1027,7 @@ class Event 'uid' => $contact['uid'], 'cid' => $contact['id'], 'start' => DateTimeFormat::utc($birthday), - 'type' => 'birthday', + 'type' => 'birthday' ]; if (DBA::exists('event', $condition)) { return false; diff --git a/src/Model/GServer.php b/src/Model/GServer.php index 91f1f2ea24..8ead599408 100644 --- a/src/Model/GServer.php +++ b/src/Model/GServer.php @@ -29,7 +29,6 @@ use Friendica\Util\Network; use Friendica\Util\Strings; use Friendica\Util\XML; use Friendica\Network\HTTPException; -use Friendica\Network\RobotsTxt; use Friendica\Worker\UpdateGServer; use GuzzleHttp\Psr7\Uri; use Psr\Http\Message\UriInterface; @@ -40,46 +39,44 @@ use Psr\Http\Message\UriInterface; class GServer { // Directory types - public const DT_NONE = 0; - public const DT_POCO = 1; - public const DT_MASTODON = 2; + const DT_NONE = 0; + const DT_POCO = 1; + const DT_MASTODON = 2; // Methods to detect server types // Non endpoint specific methods - public const DETECT_MANUAL = 0; - public const DETECT_HEADER = 1; - public const DETECT_BODY = 2; - public const DETECT_HOST_META = 3; - public const DETECT_CONTACTS = 4; - public const DETECT_AP_ACTOR = 5; - public const DETECT_AP_COLLECTION = 6; + const DETECT_MANUAL = 0; + const DETECT_HEADER = 1; + const DETECT_BODY = 2; + const DETECT_HOST_META = 3; + const DETECT_CONTACTS = 4; + const DETECT_AP_ACTOR = 5; + const DETECT_AP_COLLECTION = 6; - public const DETECT_UNSPECIFIC = [self::DETECT_MANUAL, self::DETECT_HEADER, self::DETECT_BODY, self::DETECT_HOST_META, self::DETECT_CONTACTS, self::DETECT_AP_ACTOR]; + const DETECT_UNSPECIFIC = [self::DETECT_MANUAL, self::DETECT_HEADER, self::DETECT_BODY, self::DETECT_HOST_META, self::DETECT_CONTACTS, self::DETECT_AP_ACTOR]; // Implementation specific endpoints // @todo Possibly add Lemmy detection via the endpoint /api/v3/site - public const DETECT_FRIENDIKA = 10; - public const DETECT_FRIENDICA = 11; - public const DETECT_STATUSNET = 12; - public const DETECT_GNUSOCIAL = 13; - public const DETECT_CONFIG_JSON = 14; // Statusnet, GNU Social, Older Hubzilla/Redmatrix - public const DETECT_SITEINFO_JSON = 15; // Newer Hubzilla - public const DETECT_MASTODON_API = 16; - public const DETECT_STATUS_PHP = 17; // Nextcloud - public const DETECT_V1_CONFIG = 18; - public const DETECT_SYSTEM_ACTOR = 20; // Mistpark, Osada, Roadhouse, Zap - public const DETECT_THREADS = 21; + const DETECT_FRIENDIKA = 10; + const DETECT_FRIENDICA = 11; + const DETECT_STATUSNET = 12; + const DETECT_GNUSOCIAL = 13; + const DETECT_CONFIG_JSON = 14; // Statusnet, GNU Social, Older Hubzilla/Redmatrix + const DETECT_SITEINFO_JSON = 15; // Newer Hubzilla + const DETECT_MASTODON_API = 16; + const DETECT_STATUS_PHP = 17; // Nextcloud + const DETECT_V1_CONFIG = 18; + const DETECT_SYSTEM_ACTOR = 20; // Mistpark, Osada, Roadhouse, Zap + const DETECT_THREADS = 21; // Standardized endpoints - public const DETECT_STATISTICS_JSON = 100; - public const DETECT_NODEINFO_10 = 101; // Nodeinfo Version 1.0 - public const DETECT_NODEINFO_20 = 102; // Nodeinfo Version 2.0 - public const DETECT_NODEINFO2_10 = 103; // Nodeinfo2 Version 1.0 - public const DETECT_NODEINFO_21 = 104; // Nodeinfo Version 2.1 - public const DETECT_NODEINFO_22 = 105; // Nodeinfo Version 2.2 - - private static RobotsTxt $robotsTxt; + const DETECT_STATISTICS_JSON = 100; + const DETECT_NODEINFO_10 = 101; // Nodeinfo Version 1.0 + const DETECT_NODEINFO_20 = 102; // Nodeinfo Version 2.0 + const DETECT_NODEINFO2_10 = 103; // Nodeinfo2 Version 1.0 + const DETECT_NODEINFO_21 = 104; // Nodeinfo Version 2.1 + const DETECT_NODEINFO_22 = 105; // Nodeinfo Version 2.2 /** * Check for the existence of a server and adds it in the background if not existant @@ -194,10 +191,10 @@ class GServer */ private static function isDefunct(array $gserver): bool { - return ($gserver['failed'] || in_array($gserver['network'], Protocol::FEDERATED)) - && ($gserver['last_contact'] >= $gserver['created']) - && ($gserver['last_contact'] < $gserver['last_failure']) - && ($gserver['last_contact'] < DateTimeFormat::utc('now - 90 days')); + return ($gserver['failed'] || in_array($gserver['network'], Protocol::FEDERATED)) && + ($gserver['last_contact'] >= $gserver['created']) && + ($gserver['last_contact'] < $gserver['last_failure']) && + ($gserver['last_contact'] < DateTimeFormat::utc('now - 90 days')); } /** @@ -256,9 +253,9 @@ class GServer } elseif (!empty($contact['baseurl'])) { $server = $contact['baseurl']; } elseif ($contact['network'] == Protocol::DIASPORA) { - $parts = (array) parse_url($contact['url']); + $parts = (array)parse_url($contact['url']); unset($parts['path']); - $server = (string) Uri::fromParts($parts); + $server = (string)Uri::fromParts($parts); } else { return true; } @@ -482,7 +479,7 @@ class GServer self::update( ['url' => $url, 'failed' => true, 'blocked' => Network::isUrlBlocked($url), 'last_failure' => DateTimeFormat::utcNow(), 'next_contact' => $next_update, 'network' => Protocol::PHANTOM, 'detection-method' => null], - ['nurl' => $nurl], + ['nurl' => $nurl] ); DI::logger()->info('Set failed status for existing server', ['url' => $url]); if (self::isDefunct($gserver)) { @@ -521,7 +518,7 @@ class GServer public static function cleanURL(string $dirtyUrl): string { try { - return (string) self::cleanUri(new Uri($dirtyUrl)); + return (string)self::cleanUri(new Uri($dirtyUrl)); } catch (\Throwable $e) { DI::logger()->warning('Invalid URL', ['dirtyUrl' => $dirtyUrl]); return ''; @@ -546,8 +543,8 @@ class GServer preg_replace( '#(?:^|/)index\.php#', '', - rtrim($dirtyUri->getPath(), '/'), - ), + rtrim($dirtyUri->getPath(), '/') + ) ); } @@ -594,8 +591,8 @@ class GServer if (!Strings::compareLink($url, $valid_url)) { // We only follow redirects when the path stays the same or the target url has no path. // Some systems have got redirects on their landing page to a single account page. This check handles it. - if (((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))) - || (((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)))) { + if (((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))) || + (((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); $target_id = self::getID($valid_url, true); @@ -609,12 +606,12 @@ class GServer return false; } - if ((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)) - && (parse_url($url, PHP_URL_PATH) == '')) { + if ((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)) && + (parse_url($url, PHP_URL_PATH) == '')) { DI::logger()->debug('Found redirect. Mark old entry as failure and redirect to the basepath.', ['old' => $url, 'new' => $valid_url]); - $parts = (array) parse_url($valid_url); + $parts = (array)parse_url($valid_url); unset($parts['path']); - $valid_url = (string) Uri::fromParts($parts); + $valid_url = (string)Uri::fromParts($parts); self::setFailureByUrl($url); if (!self::getID($valid_url, true) && !Network::isUrlBlocked($valid_url)) { @@ -625,17 +622,14 @@ class GServer DI::logger()->debug('Found redirect, but ignore it.', ['old' => $url, 'new' => $valid_url]); } - if ((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)) - && (parse_url($url, PHP_URL_SCHEME) != parse_url($valid_url, PHP_URL_SCHEME))) { + if ((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)) && + (parse_url($url, PHP_URL_SCHEME) != parse_url($valid_url, PHP_URL_SCHEME))) { $url = $valid_url; } $in_webroot = empty(parse_url($url, PHP_URL_PATH)); - self::$robotsTxt = DI::robotsTxt(); - self::$robotsTxt->load($url); - // When a nodeinfo is present, we don't need to dig further $curlResult = DI::httpClient()->get($url . '/.well-known/nodeinfo', HttpClientAccept::JSON, [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]); if ($curlResult->isTimeout()) { @@ -648,10 +642,6 @@ class GServer } else { $serverdata = self::parseNodeinfo($url, $curlResult); - if (!empty($serverdata)) { - $serverdata = self::clearStatisticalData($serverdata); - } - if (empty($serverdata) || !in_array($serverdata['detection-method'], [self::DETECT_NODEINFO_20, self::DETECT_NODEINFO_21, self::DETECT_NODEINFO_22])) { $curlResult = DI::httpClient()->get($url . '/.well-known/x-nodeinfo2', HttpClientAccept::JSON, [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]); $serverdata = self::parseNodeinfo2($curlResult) ?: $serverdata; @@ -668,7 +658,7 @@ class GServer // When there is no Nodeinfo, then use some protocol specific endpoints if ($serverdata['network'] == Protocol::PHANTOM) { - if ($in_webroot && self::$robotsTxt->isAllowed('/')) { + if ($in_webroot) { // Fetch the landing page, possibly it reveals some data $accept = 'application/activity+json,application/ld+json,application/json,*/*;q=0.9'; $curlResult = DI::httpClient()->get($url, $accept, [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]); @@ -703,17 +693,17 @@ class GServer return false; } + if (in_array($url, ['https://www.threads.net', 'https://threads.net'])) { + $serverdata['detection-method'] = self::DETECT_THREADS; + $serverdata['network'] = Protocol::ACTIVITYPUB; + $serverdata['platform'] = 'threads'; + } + if (($serverdata['network'] == Protocol::PHANTOM) || in_array($serverdata['detection-method'], self::DETECT_UNSPECIFIC)) { $serverdata = self::detectMastodonAlikes($url, $serverdata); } } - if (($serverdata['network'] === Protocol::PHANTOM) && in_array($url, ['https://www.threads.net', 'https://threads.net'])) { - $serverdata['detection-method'] = self::DETECT_THREADS; - $serverdata['network'] = Protocol::ACTIVITYPUB; - $serverdata['platform'] = 'threads'; - } - // All following checks are done for systems that always have got a "host-meta" endpoint. // With this check we don't have to waste time and resources for dead systems. // Also this hopefully prevents us from receiving abuse messages. @@ -789,8 +779,8 @@ class GServer // When a server is new, then there is no gserver entry yet. // But in "detectNetworkViaContacts" it could happen that a contact is updated, // and this can call this function here as well. - if (self::getID($url, true) && (in_array($serverdata['network'], [Protocol::PHANTOM, Protocol::FEED]) - || in_array($serverdata['detection-method'], [self::DETECT_MANUAL, self::DETECT_HEADER, self::DETECT_BODY, self::DETECT_HOST_META]))) { + if (self::getID($url, true) && (in_array($serverdata['network'], [Protocol::PHANTOM, Protocol::FEED]) || + in_array($serverdata['detection-method'], [self::DETECT_MANUAL, self::DETECT_HEADER, self::DETECT_BODY, self::DETECT_HOST_META]))) { $serverdata = self::detectNetworkViaContacts($url, $serverdata); } @@ -822,7 +812,12 @@ class GServer $serverdata = self::fetchWeeklyUsage($url, $serverdata); } - $serverdata['registered-users'] ??= 0; + $serverdata['registered-users'] = $serverdata['registered-users'] ?? 0; + + // Numbers above a reasonable value (10 millions) are ignored + if ($serverdata['registered-users'] > 10000000) { + $serverdata['registered-users'] = 0; + } // On an active server there has to be at least a single user if (!in_array($serverdata['network'], [Protocol::PHANTOM, Protocol::FEED]) && ($serverdata['registered-users'] <= 0)) { @@ -867,53 +862,6 @@ class GServer return $ret; } - /** - * Clear statistical data from nodeinfo when we are not allowed to access it or when the data seems to be faked. - * - * GotoSocial is known for having an option to provide fake statistical data, so we apply some checks for this platform in particular. - * - * @param array $serverdata - * @return array - */ - private static function clearStatisticalData(array $serverdata): array - { - // We don't clear the data when we are allowed to access the nodeinfo - $clear = !self::$robotsTxt->isAllowed('/.well-known/nodeinfo'); - - // Numbers above a reasonable value (10 millions) are a sign for faked data, so we clear the data in this case as well. - if (($serverdata['registered-users'] ?? 0) > 10000000) { - $clear = true; - } - - // All the following checks are only applied for GoToSocial, so we can skip them for other platforms. - if (!$clear && $serverdata['platform'] !== 'gotosocial') { - return $serverdata; - } - - // When the robots.txt couldn't be loaded, we clear the data as well. - if (!self::$robotsTxt->isLoaded()) { - $clear = true; - } - - // We clear the data when there are more than 10.000 registered users, since this is an indicator for faked data. - if (($serverdata['registered-users'] ?? 0) > 10000) { - $clear = true; - } - - if (!$clear) { - return $serverdata; - } - - $serverdata['registered-users'] = null; - $serverdata['active-week-users'] = null; - $serverdata['active-month-users'] = null; - $serverdata['active-halfyear-users'] = null; - $serverdata['local-posts'] = null; - $serverdata['local-comments'] = null; - - return $serverdata; - } - /** * Count the number of known contacts from this server */ @@ -957,10 +905,6 @@ class GServer */ private static function discoverRelay(string $server_url) { - if (!self::$robotsTxt->isAllowed('/.well-known/x-social-relay')) { - return; - } - DI::logger()->info('Discover relay data', ['server' => $server_url]); $curlResult = DI::httpClient()->get($server_url . '/.well-known/x-social-relay', HttpClientAccept::JSON, [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]); @@ -974,7 +918,7 @@ class GServer } // Sanitize incoming data, see https://github.com/friendica/friendica/issues/8565 - $data['subscribe'] = (bool) ($data['subscribe'] ?? false); + $data['subscribe'] = (bool)($data['subscribe'] ?? false); if (!$data['subscribe'] || empty($data['scope']) || !in_array(strtolower($data['scope']), ['all', 'tags'])) { $data['scope'] = ''; @@ -1058,10 +1002,6 @@ class GServer */ private static function fetchStatistics(string $url, array $serverdata): array { - if (!self::$robotsTxt->isAllowed('/statistics.json')) { - return $serverdata; - } - $curlResult = DI::httpClient()->get($url . '/statistics.json', HttpClientAccept::JSON, [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]); if (!$curlResult->isSuccess()) { return $serverdata; @@ -1241,7 +1181,7 @@ class GServer $server = [ 'detection-method' => self::DETECT_NODEINFO_10, - 'register_policy' => Register::CLOSED, + 'register_policy' => Register::CLOSED ]; if (!empty($nodeinfo['openRegistrations'])) { @@ -1472,7 +1412,7 @@ class GServer $server = [ 'detection-method' => self::DETECT_NODEINFO2_10, - 'register_policy' => Register::CLOSED, + 'register_policy' => Register::CLOSED ]; if (!empty($nodeinfo['openRegistrations'])) { @@ -1564,10 +1504,6 @@ class GServer */ private static function fetchSiteinfo(string $url, array $serverdata): array { - if (!self::$robotsTxt->isAllowed('/siteinfo.json')) { - return $serverdata; - } - $curlResult = DI::httpClient()->get($url . '/siteinfo.json', HttpClientAccept::JSON, [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]); if (!$curlResult->isSuccess()) { return $serverdata; @@ -1748,10 +1684,6 @@ class GServer */ private static function getNomadVersion(string $url): string { - if (!self::$robotsTxt->isAllowed('/api/z/1.0/version')) { - return ''; - } - $curlResult = DI::httpClient()->get($url . '/api/z/1.0/version', HttpClientAccept::JSON, [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]); if (!$curlResult->isSuccess() || ($curlResult->getBodyString() == '')) { return ''; @@ -1845,10 +1777,6 @@ class GServer */ private static function validHostMeta(string $url): bool { - if (!self::$robotsTxt->isAllowed(Probe::HOST_META)) { - return false; - } - $xrd_timeout = DI::config()->get('system', 'xrd_timeout'); $curlResult = DI::httpClient()->get($url . Probe::HOST_META, HttpClientAccept::XRD_XML, [HttpClientOptions::TIMEOUT => $xrd_timeout, HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]); if (!$curlResult->isSuccess()) { @@ -1938,10 +1866,6 @@ class GServer { $serverdata['poco'] = ''; - if (!self::$robotsTxt->isAllowed('/poco')) { - return $serverdata; - } - $curlResult = DI::httpClient()->get($url . '/poco', HttpClientAccept::JSON, [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]); if (!$curlResult->isSuccess()) { return $serverdata; @@ -1971,10 +1895,6 @@ class GServer */ public static function checkMastodonDirectory(string $url, array $serverdata): array { - if (!self::$robotsTxt->isAllowed('/api/v1/directory')) { - return $serverdata; - } - $curlResult = DI::httpClient()->get($url . '/api/v1/directory?limit=1', HttpClientAccept::JSON, [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]); if (!$curlResult->isSuccess()) { return $serverdata; @@ -2007,10 +1927,6 @@ class GServer */ private static function detectPeertube(string $url, array $serverdata): array { - if (!self::$robotsTxt->isAllowed('/api/v1/config')) { - return $serverdata; - } - $curlResult = DI::httpClient()->get($url . '/api/v1/config', HttpClientAccept::JSON, [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]); if (!$curlResult->isSuccess() || ($curlResult->getBodyString() == '')) { return $serverdata; @@ -2059,10 +1975,6 @@ class GServer */ private static function detectNextcloud(string $url, array $serverdata, bool $validHostMeta): array { - if (!self::$robotsTxt->isAllowed('/status.php')) { - return $serverdata; - } - $curlResult = DI::httpClient()->get($url . '/status.php', HttpClientAccept::JSON, [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]); if (!$curlResult->isSuccess() || ($curlResult->getBodyString() == '')) { return $serverdata; @@ -2099,10 +2011,6 @@ class GServer */ private static function fetchWeeklyUsage(string $url, array $serverdata): array { - if (!self::$robotsTxt->isAllowed('/api/v1/instance/activity')) { - return $serverdata; - } - $curlResult = DI::httpClient()->get($url . '/api/v1/instance/activity', HttpClientAccept::JSON, [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]); if (!$curlResult->isSuccess() || ($curlResult->getBodyString() == '')) { return $serverdata; @@ -2143,10 +2051,6 @@ class GServer */ private static function detectMastodonAlikes(string $url, array $serverdata): array { - if (!self::$robotsTxt->isAllowed('/api/v1/instance')) { - return $serverdata; - } - $curlResult = DI::httpClient()->get($url . '/api/v1/instance', HttpClientAccept::JSON, [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]); if (!$curlResult->isSuccess() || ($curlResult->getBodyString() == '')) { return $serverdata; @@ -2224,10 +2128,6 @@ class GServer */ private static function detectHubzilla(string $url, array $serverdata): array { - if (!self::$robotsTxt->isAllowed('/api/statusnet/config.json')) { - return $serverdata; - } - $curlResult = DI::httpClient()->get($url . '/api/statusnet/config.json', HttpClientAccept::JSON, [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]); if (!$curlResult->isSuccess() || ($curlResult->getBodyString() == '')) { return $serverdata; @@ -2323,48 +2223,44 @@ class GServer private static function detectGNUSocial(string $url, array $serverdata): array { // Test for GNU Social - if (self::$robotsTxt->isAllowed('/api/gnusocial/version.json')) { - $curlResult = DI::httpClient()->get($url . '/api/gnusocial/version.json', HttpClientAccept::JSON, [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]); - if ($curlResult->isSuccess() && ($curlResult->getBodyString() != '{"error":"not implemented"}') - && ($curlResult->getBodyString() != '') && (strlen($curlResult->getBodyString()) < 30)) { - $serverdata['platform'] = 'gnusocial'; - // Remove junk that some GNU Social servers return - $serverdata['version'] = str_replace(chr(239) . chr(187) . chr(191), '', $curlResult->getBodyString()); - $serverdata['version'] = str_replace(["\r", "\n", "\t"], '', $serverdata['version']); - $serverdata['version'] = trim($serverdata['version'], '"'); - $serverdata['network'] = Protocol::OSTATUS; + $curlResult = DI::httpClient()->get($url . '/api/gnusocial/version.json', HttpClientAccept::JSON, [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]); + if ($curlResult->isSuccess() && ($curlResult->getBodyString() != '{"error":"not implemented"}') && + ($curlResult->getBodyString() != '') && (strlen($curlResult->getBodyString()) < 30)) { + $serverdata['platform'] = 'gnusocial'; + // Remove junk that some GNU Social servers return + $serverdata['version'] = str_replace(chr(239) . chr(187) . chr(191), '', $curlResult->getBodyString()); + $serverdata['version'] = str_replace(["\r", "\n", "\t"], '', $serverdata['version']); + $serverdata['version'] = trim($serverdata['version'], '"'); + $serverdata['network'] = Protocol::OSTATUS; - if (in_array($serverdata['detection-method'], self::DETECT_UNSPECIFIC)) { - $serverdata['detection-method'] = self::DETECT_GNUSOCIAL; - } - - return $serverdata; + if (in_array($serverdata['detection-method'], self::DETECT_UNSPECIFIC)) { + $serverdata['detection-method'] = self::DETECT_GNUSOCIAL; } + + return $serverdata; } // Test for Statusnet - if (self::$robotsTxt->isAllowed('/api/statusnet/version.json')) { - $curlResult = DI::httpClient()->get($url . '/api/statusnet/version.json', HttpClientAccept::JSON, [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]); - if ($curlResult->isSuccess() && ($curlResult->getBodyString() != '{"error":"not implemented"}') - && ($curlResult->getBodyString() != '') && (strlen($curlResult->getBodyString()) < 30)) { + $curlResult = DI::httpClient()->get($url . '/api/statusnet/version.json', HttpClientAccept::JSON, [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]); + if ($curlResult->isSuccess() && ($curlResult->getBodyString() != '{"error":"not implemented"}') && + ($curlResult->getBodyString() != '') && (strlen($curlResult->getBodyString()) < 30)) { - // Remove junk that some GNU Social servers return - $serverdata['version'] = str_replace(chr(239) . chr(187) . chr(191), '', $curlResult->getBodyString()); - $serverdata['version'] = str_replace(["\r", "\n", "\t"], '', $serverdata['version']); - $serverdata['version'] = trim($serverdata['version'], '"'); + // Remove junk that some GNU Social servers return + $serverdata['version'] = str_replace(chr(239) . chr(187) . chr(191), '', $curlResult->getBodyString()); + $serverdata['version'] = str_replace(["\r", "\n", "\t"], '', $serverdata['version']); + $serverdata['version'] = trim($serverdata['version'], '"'); - if (!empty($serverdata['version']) && strtolower(substr($serverdata['version'], 0, 7)) == 'pleroma') { - $serverdata['platform'] = 'pleroma'; - $serverdata['version'] = trim(str_ireplace('pleroma', '', $serverdata['version'])); - $serverdata['network'] = Protocol::ACTIVITYPUB; - } else { - $serverdata['platform'] = 'statusnet'; - $serverdata['network'] = Protocol::OSTATUS; - } + if (!empty($serverdata['version']) && strtolower(substr($serverdata['version'], 0, 7)) == 'pleroma') { + $serverdata['platform'] = 'pleroma'; + $serverdata['version'] = trim(str_ireplace('pleroma', '', $serverdata['version'])); + $serverdata['network'] = Protocol::ACTIVITYPUB; + } else { + $serverdata['platform'] = 'statusnet'; + $serverdata['network'] = Protocol::OSTATUS; + } - if (in_array($serverdata['detection-method'], self::DETECT_UNSPECIFIC)) { - $serverdata['detection-method'] = self::DETECT_STATUSNET; - } + if (in_array($serverdata['detection-method'], self::DETECT_UNSPECIFIC)) { + $serverdata['detection-method'] = self::DETECT_STATUSNET; } } @@ -2381,10 +2277,6 @@ class GServer */ private static function detectFriendica(string $url, array $serverdata): array { - if (!self::$robotsTxt->isAllowed('/friendica/json')) { - return $serverdata; - } - // There is a bug in some versions of Friendica that will return an ActivityStream actor when the content type "application/json" is requested. // Because of this me must not use ACCEPT_JSON here. $curlResult = DI::httpClient()->get($url . '/friendica/json', HttpClientAccept::DEFAULT, [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]); @@ -2673,7 +2565,7 @@ class GServer 'gserver', ['id', 'url', 'nurl', 'network', 'poco', 'directory-type'], ["NOT `blocked` AND NOT `failed` AND `directory-type` != ? AND `last_poco_query` < ?", GServer::DT_NONE, $last_update], - ['order' => ['RAND()']], + ['order' => ['RAND()']] ); while ($gserver = DBA::fetch($gservers)) { diff --git a/src/Model/Item.php b/src/Model/Item.php index be2b73b2d1..0153ea093d 100644 --- a/src/Model/Item.php +++ b/src/Model/Item.php @@ -38,52 +38,52 @@ use Friendica\Util\Map; use Friendica\Util\Network; use Friendica\Util\Proxy; use Friendica\Util\Strings; +use Friendica\Util\Temporal; use GuzzleHttp\Psr7\Uri; class Item { // Posting types, inspired by https://www.w3.org/TR/activitystreams-vocabulary/#object-types - public const PT_ARTICLE = 0; - public const PT_NOTE = 1; - public const PT_PAGE = 2; - public const PT_IMAGE = 16; - public const PT_AUDIO = 17; - public const PT_VIDEO = 18; - public const PT_DOCUMENT = 19; - public const PT_EVENT = 32; - public const PT_POLL = 33; - public const PT_PERSONAL_NOTE = 128; + const PT_ARTICLE = 0; + const PT_NOTE = 1; + const PT_PAGE = 2; + const PT_IMAGE = 16; + const PT_AUDIO = 17; + const PT_VIDEO = 18; + const PT_DOCUMENT = 19; + const PT_EVENT = 32; + const PT_POLL = 33; + const PT_PERSONAL_NOTE = 128; // Posting reasons (Why had a post been stored for a user?) - public const PR_NONE = 0; - public const PR_TAG = 64; - public const PR_TO = 65; - public const PR_CC = 66; - public const PR_BTO = 67; - public const PR_BCC = 68; - public const PR_FOLLOWER = 69; - public const PR_ANNOUNCEMENT = 70; - public const PR_COMMENT = 71; - public const PR_STORED = 72; - public const PR_GLOBAL = 73; - public const PR_RELAY = 74; - public const PR_FETCHED = 75; - public const PR_COMPLETION = 76; - public const PR_DIRECT = 77; - public const PR_ACTIVITY = 78; - public const PR_DISTRIBUTE = 79; - public const PR_PUSHED = 80; - public const PR_LOCAL = 81; - public const PR_AUDIENCE = 82; - public const PR_CHANNEL = 83; + const PR_NONE = 0; + const PR_TAG = 64; + const PR_TO = 65; + const PR_CC = 66; + const PR_BTO = 67; + const PR_BCC = 68; + const PR_FOLLOWER = 69; + const PR_ANNOUNCEMENT = 70; + const PR_COMMENT = 71; + const PR_STORED = 72; + const PR_GLOBAL = 73; + const PR_RELAY = 74; + const PR_FETCHED = 75; + const PR_COMPLETION = 76; + const PR_DIRECT = 77; + const PR_ACTIVITY = 78; + const PR_DISTRIBUTE = 79; + const PR_PUSHED = 80; + const PR_LOCAL = 81; + const PR_AUDIENCE = 82; // system.accept_only_sharer setting values - public const COMPLETION_NONE = 1; - public const COMPLETION_COMMENT = 0; - public const COMPLETION_LIKE = 2; + const COMPLETION_NONE = 1; + const COMPLETION_COMMENT = 0; + const COMPLETION_LIKE = 2; // Field list that is used to display the items - public const DISPLAY_FIELDLIST = [ + const DISPLAY_FIELDLIST = [ 'uid', 'id', 'parent', 'guid', 'network', 'protocol', 'gravity', 'uri-id', 'uri', 'thr-parent-id', 'thr-parent', 'parent-uri-id', 'parent-uri', 'conversation', 'commented', 'created', 'edited', 'received', 'verb', 'object-type', 'postopts', 'plink', @@ -100,11 +100,11 @@ class Item 'event-nofinish', 'event-ignore', 'event-id', 'question-id', 'question-multiple', 'question-voters', 'question-end-time', 'has-categories', 'has-media', - 'delivery_queue_count', 'delivery_queue_done', 'delivery_queue_failed', + 'delivery_queue_count', 'delivery_queue_done', 'delivery_queue_failed' ]; // Field list that is used to deliver items via the protocols - public const DELIVER_FIELDLIST = [ + const DELIVER_FIELDLIST = [ 'uid', 'id', 'parent', 'uri-id', 'uri', 'thr-parent', 'parent-uri', 'guid', 'parent-guid', 'conversation', 'received', 'created', 'edited', 'verb', 'object-type', 'object', 'target', 'private', 'title', 'content-warning', 'body', 'raw-body', 'language', 'location', 'coord', 'app', 'sensitive', @@ -115,11 +115,11 @@ class Item 'thr-parent-id', 'parent-uri-id', 'quote-uri', 'quote-uri-id', 'postopts', 'pubmail', 'event-created', 'event-edited', 'event-start', 'event-finish', 'event-summary', 'event-desc', 'event-location', 'event-type', - 'event-nofinish', 'event-ignore', 'event-id', + 'event-nofinish', 'event-ignore', 'event-id' ]; // All fields in the item table - public const ITEM_FIELDLIST = [ + const ITEM_FIELDLIST = [ 'id', 'uid', 'parent', 'uri', 'parent-uri', 'thr-parent', 'guid', 'uri-id', 'parent-uri-id', 'thr-parent-id', 'conversation', 'vid', 'quote-uri', 'quote-uri-id', 'contact-id', 'wall', 'gravity', 'extid', 'psid', @@ -131,34 +131,34 @@ class Item 'title', 'content-warning', 'body', 'language', 'location', 'coord', 'app', 'rendered-hash', 'rendered-html', 'object-type', 'object', 'target-type', 'target', 'author-id', 'author-link', 'author-name', 'author-avatar', 'author-network', - 'owner-id', 'owner-link', 'owner-name', 'owner-avatar', 'causer-id', + 'owner-id', 'owner-link', 'owner-name', 'owner-avatar', 'causer-id' ]; // List of all verbs that don't need additional content data. // Never reorder or remove entries from this list. Just add new ones at the end, if needed. - public const ACTIVITIES = [ + const ACTIVITIES = [ Activity::LIKE, Activity::DISLIKE, Activity::ATTEND, Activity::ATTENDNO, Activity::ATTENDMAYBE, Activity::FOLLOW, - Activity::ANNOUNCE, + Activity::ANNOUNCE ]; // Privacy levels - public const PUBLIC = 0; - public const PRIVATE = 1; - public const UNLISTED = 2; + const PUBLIC = 0; + const PRIVATE = 1; + const UNLISTED = 2; // Item weight for query ordering - public const GRAVITY_PARENT = 0; - public const GRAVITY_ACTIVITY = 3; - public const GRAVITY_COMMENT = 6; - public const GRAVITY_UNKNOWN = 9; + const GRAVITY_PARENT = 0; + const GRAVITY_ACTIVITY = 3; + const GRAVITY_COMMENT = 6; + const GRAVITY_UNKNOWN = 9; // Restrictions - public const CANT_REPLY = 1; - public const CANT_LIKE = 2; - public const CANT_ANNOUNCE = 4; - public const CANT_QUOTE = 8; + const CANT_REPLY = 1; + const CANT_LIKE = 2; + const CANT_ANNOUNCE = 4; + const CANT_QUOTE = 8; /** * Update existing item entries @@ -208,8 +208,8 @@ class Item // We only need to call the line by line update for specific fields if ( - empty($fields['body']) && empty($fields['file']) - && empty($fields['attach']) && empty($fields['edited']) + empty($fields['body']) && empty($fields['file']) && + empty($fields['attach']) && empty($fields['edited']) ) { return $rows; } @@ -284,7 +284,7 @@ class Item } } - Worker::add(Worker::PRIORITY_HIGH, 'Notifier', Delivery::POST, (int) $post['uri-id'], (int) $post['uid']); + Worker::add(Worker::PRIORITY_HIGH, 'Notifier', Delivery::POST, (int)$post['uri-id'], (int)$post['uid']); } return $rows; @@ -352,7 +352,7 @@ class Item $fields = [ 'id', 'uri', 'uri-id', 'uid', 'parent', 'parent-uri-id', 'origin', 'thr-parent-id', 'deleted', 'resource-id', 'event-id', 'vid', 'body', - 'verb', 'object-type', 'object', 'target', 'contact-id', 'psid', 'gravity', + 'verb', 'object-type', 'object', 'target', 'contact-id', 'psid', 'gravity' ]; $item = Post::selectFirst($fields, ['id' => $item_id]); if (!DBA::isResult($item)) { @@ -402,7 +402,7 @@ class Item Post\DeliveryData::delete($item['uri-id']); // If it's the parent of a comment thread, kill all the kids - if ($item['gravity'] == self::GRAVITY_PARENT && !is_null($item['parent'])) { + if ($item['gravity'] == self::GRAVITY_PARENT) { self::markForDeletion(['parent' => $item['parent'], 'deleted' => false], $priority); } @@ -429,7 +429,7 @@ class Item // send the notification upstream/downstream if ($priority) { - Worker::add(['priority' => $priority, 'dont_fork' => true], 'Notifier', Delivery::DELETION, (int) $item['uri-id'], (int) $item['uid']); + Worker::add(['priority' => $priority, 'dont_fork' => true], 'Notifier', Delivery::DELETION, (int)$item['uri-id'], (int)$item['uid']); } } elseif ($item['uid'] != 0) { Post\User::update($item['uri-id'], $item['uid'], ['hidden' => true]); @@ -576,7 +576,7 @@ class Item $condition = [ 'verb' => Activity::FOLLOW, 'uid' => $item['uid'], - 'parent-uri' => $item['parent-uri'], 'author-id' => $item['author-id'], + 'parent-uri' => $item['parent-uri'], 'author-id' => $item['author-id'] ]; if (Post::exists($condition)) { // It happens that we receive multiple follow requests by the same author - we only store one. @@ -630,8 +630,7 @@ class Item $priority = Worker::PRIORITY_HIGH; - $copy_permissions = false; - $defined_permissions = isset($item['allow_cid']) && isset($item['allow_gid']) && isset($item['deny_cid']) && isset($item['deny_gid']) && isset($item['private']); + $copy_permissions = false; // If it is a posting where users should get notifications, then define it as wall posting if ($notify) { @@ -643,12 +642,8 @@ class Item } // Mastodon style API visibility - if (isset($item['visibility'])) { - $copy_permissions = $item['visibility'] === 'private'; - unset($item['visibility']); - } else { - $copy_permissions = !$defined_permissions; - } + $copy_permissions = ($item['visibility'] ?? 'private') == 'private'; + unset($item['visibility']); } else { $item['network'] = trim(($item['network'] ?? '') ?: Protocol::PHANTOM); } @@ -682,6 +677,8 @@ class Item $item['post-type'] = empty($item['title']) ? self::PT_NOTE : self::PT_ARTICLE; } + $defined_permissions = isset($item['allow_cid']) && isset($item['allow_gid']) && isset($item['deny_cid']) && isset($item['deny_gid']) && isset($item['private']); + $uid = intval($item['uid']); // Communities aren't working with the Diaspora protocol @@ -703,8 +700,8 @@ class Item $item['contact-id'] = self::contactId($item); if ( - !empty($item['direction']) && in_array($item['direction'], [Conversation::PUSH, Conversation::RELAY]) - && empty($item['origin']) && DI::contentItem()->isTooOld($item['created'], $item['uid']) + !empty($item['direction']) && in_array($item['direction'], [Conversation::PUSH, Conversation::RELAY]) && + empty($item['origin']) && DI::contentItem()->isTooOld($item['created'], $item['uid']) ) { DI::logger()->info('Item is too old', ['item' => $item]); return 0; @@ -796,7 +793,7 @@ class Item ]; $hook_data = $eventDispatcher->dispatch( - new ArrayFilterEvent(ArrayFilterEvent::INSERT_POST_LOCAL, $hook_data), + new ArrayFilterEvent(ArrayFilterEvent::INSERT_POST_LOCAL, $hook_data) )->getArray(); /** @var array */ @@ -809,7 +806,7 @@ class Item } elseif (!$notify) { /** @var array */ $item = $eventDispatcher->dispatch( - new ArrayFilterEvent(ArrayFilterEvent::INSERT_POST_REMOTE, $item), + new ArrayFilterEvent(ArrayFilterEvent::INSERT_POST_REMOTE, $item) )->getArray(); } @@ -829,8 +826,8 @@ class Item $item['allow_cid'], $item['allow_gid'], $item['deny_cid'], - $item['deny_gid'], - ), + $item['deny_gid'] + ) )->id; if (!empty($item['extid'])) { @@ -902,7 +899,37 @@ class Item } } - $item = self::storeEvent($item); + if (empty($item['event-id'])) { + if (array_key_exists('event-id', $item)) { + unset($item['event-id']); + } + + $ev = Event::fromBBCode($item['body']); + if ((!empty($ev['desc']) || !empty($ev['summary'])) && !empty($ev['start'])) { + DI::logger()->info('Event found.'); + $ev['cid'] = $item['contact-id']; + $ev['uid'] = $item['uid']; + $ev['uri'] = $item['uri']; + $ev['edited'] = $item['edited']; + $ev['private'] = $item['private']; + $ev['guid'] = $item['guid']; + $ev['plink'] = $item['plink']; + $ev['network'] = $item['network']; + $ev['protocol'] = $item['protocol'] ?? Conversation::PARCEL_UNKNOWN; + $ev['direction'] = $item['direction'] ?? Conversation::UNKNOWN; + $ev['source'] = $item['source'] ?? ''; + + $event = DBA::selectFirst('event', ['id'], ['uri' => $item['uri'], 'uid' => $item['uid']]); + if (DBA::isResult($event)) { + $ev['id'] = $event['id']; + } + + $event_id = Event::store($ev); + $item = Event::getItemArrayForImportedId($event_id, $item); + + DI::logger()->info('Event was stored', ['id' => $event_id]); + } + } if (empty($item['causer-id'])) { unset($item['causer-id']); @@ -929,16 +956,12 @@ class Item $inserted = Post::insert($item['uri-id'], $item); if ($item['gravity'] == self::GRAVITY_PARENT) { - if (!Post\Thread::insert($item['uri-id'], $item) && !Post\Thread::exists($item['uri-id'])) { - DI::logger()->error('Post-Thread entry was not inserted', ['uri-id' => $item['uri-id']]); - } + Post\Thread::insert($item['uri-id'], $item); } // The content of activities normally doesn't matter - except for likes from Misskey if (!in_array($item['verb'], self::ACTIVITIES) || in_array($item['verb'], [Activity::LIKE, Activity::DISLIKE]) && !empty($item['body']) && (mb_strlen($item['body']) == 1)) { - if (!Post\Content::insert($item['uri-id'], $item) && !Post\Content::exists($item['uri-id'])) { - DI::logger()->error('Post-Content entry was not inserted', ['uri-id' => $item['uri-id']]); - } + Post\Content::insert($item['uri-id'], $item); } $item['parent'] = $parent_id; @@ -985,9 +1008,7 @@ class Item if ($item['gravity'] == self::GRAVITY_PARENT) { $item['post-user-id'] = $post_user_id; - if (!Post\ThreadUser::insert($item['uri-id'], $item['uid'], $item)) { - DI::logger()->error('Post-Thread-User entry was not inserted', ['uri-id' => $item['uri-id']]); - } + Post\ThreadUser::insert($item['uri-id'], $item['uid'], $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']]); @@ -995,44 +1016,6 @@ class Item return self::handleCreatedItem($orig_item, $post_user_id, $uid, $notify, $copy_permissions, $parent_origin, $priority, $notify_type, $inserted, $source); } - private static function storeEvent(array $item): array - { - if (!empty($item['event-id'])) { - return $item; - } - - if (array_key_exists('event-id', $item)) { - unset($item['event-id']); - } - - $ev = Event::fromBBCode($item['body']); - if ((!empty($ev['desc']) || !empty($ev['summary'])) && !empty($ev['start'])) { - DI::logger()->info('Event found.'); - $ev['cid'] = $item['contact-id']; - $ev['uid'] = $item['uid']; - $ev['uri'] = $item['uri']; - $ev['edited'] = $item['edited']; - $ev['private'] = $item['private']; - $ev['guid'] = $item['guid']; - $ev['plink'] = $item['plink']; - $ev['network'] = $item['network']; - $ev['protocol'] = $item['protocol'] ?? Conversation::PARCEL_UNKNOWN; - $ev['direction'] = $item['direction'] ?? Conversation::UNKNOWN; - $ev['source'] = $item['source'] ?? ''; - - $event = DBA::selectFirst('event', ['id'], ['uri' => $item['uri'], 'uid' => $item['uid']]); - if (DBA::isResult($event)) { - $ev['id'] = $event['id']; - } - - $event_id = Event::store($ev); - $item = Event::getItemArrayForImportedId($event_id, $item); - - DI::logger()->info('Event was stored', ['id' => $event_id]); - } - return $item; - } - 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(array_merge(self::ITEM_FIELDLIST, ['author-contact-type']), ['post-user-id' => $post_user_id]); @@ -1046,10 +1029,10 @@ class Item DI::logger()->debug('Handle created item', ['id' => $post_user_id, 'uri-id' => $posted_item['uri-id'], 'uid' => $posted_item['uid']]); if ($posted_item['origin'] && $posted_item['gravity'] === self::GRAVITY_PARENT) { - $posts = (int) (DI::keyValue()->get('nodeinfo_local_posts') ?? 0); + $posts = (int)(DI::keyValue()->get('nodeinfo_local_posts') ?? 0); DI::keyValue()->set('nodeinfo_local_posts', $posts + 1); } elseif ($posted_item['origin'] && $posted_item['gravity'] == self::GRAVITY_COMMENT) { - $comments = (int) (DI::keyValue()->get('nodeinfo_local_comments') ?? 0); + $comments = (int)(DI::keyValue()->get('nodeinfo_local_comments') ?? 0); DI::keyValue()->set('nodeinfo_local_comments', $comments + 1); } @@ -1091,7 +1074,7 @@ class Item $eventDispatcher = DI::eventDispatcher(); $posted_item = $eventDispatcher->dispatch( - new ArrayFilterEvent(ArrayFilterEvent::INSERT_POST_REMOTE_END, $posted_item), + new ArrayFilterEvent(ArrayFilterEvent::INSERT_POST_REMOTE_END, $posted_item) )->getArray(); } @@ -1128,8 +1111,8 @@ class Item } // Don't relay participation messages - if (($posted_item['verb'] == Activity::FOLLOW) - && (!$posted_item['origin'] || ($posted_item['author-id'] != Contact::getPublicIdByUserId($uid))) + if (($posted_item['verb'] == Activity::FOLLOW) && + (!$posted_item['origin'] || ($posted_item['author-id'] != Contact::getPublicIdByUserId($uid))) ) { DI::logger()->info('Participation messages will not be relayed', ['item' => $posted_item['id'], 'uri' => $posted_item['uri'], 'verb' => $posted_item['verb']]); $transmit = false; @@ -1143,15 +1126,15 @@ class Item if ($transmit) { ActivityPub\Transmitter::storeReceiversForItem($posted_item); - Worker::add(['priority' => $priority, 'dont_fork' => true], 'Notifier', $notify_type, (int) $posted_item['uri-id'], (int) $posted_item['uid']); + Worker::add(['priority' => $priority, 'dont_fork' => true], 'Notifier', $notify_type, (int)$posted_item['uri-id'], (int)$posted_item['uid']); } if ($inserted) { if ($posted_item['gravity'] === self::GRAVITY_PARENT) { - $posts = (int) (DI::keyValue()->get('nodeinfo_total_posts') ?? 0); + $posts = (int)(DI::keyValue()->get('nodeinfo_total_posts') ?? 0); DI::keyValue()->set('nodeinfo_total_posts', $posts + 1); } elseif ($posted_item['gravity'] == self::GRAVITY_COMMENT) { - $comments = (int) (DI::keyValue()->get('nodeinfo_total_comments') ?? 0); + $comments = (int)(DI::keyValue()->get('nodeinfo_total_comments') ?? 0); DI::keyValue()->set('nodeinfo_total_comments', $comments + 1); } @@ -1178,9 +1161,6 @@ class Item self::reshareChannelPost($engagement_uri_id); } - if ($posted_item['origin'] && in_array($posted_item['verb'], [Activity::LIKE, Activity::DISLIKE, Activity::ATTEND, Activity::ATTENDNO, Activity::ATTENDMAYBE, Activity::ANNOUNCE, Activity::POST])) { - DI::contentItem()->setViewed($posted_item['thr-parent-id'], $posted_item['uid']); - } } if ($posted_item['uid'] !== 0 && ($posted_item['origin'] || !in_array($posted_item['network'], Protocol::FEDERATED))) { @@ -1216,13 +1196,13 @@ class Item DI::logger()->debug('Post accepted for channels', ['uri-id' => $uri_id, 'uid' => $uid, 'network' => $item['network']]); if (($item['gravity'] === self::GRAVITY_ACTIVITY) && ($item['verb'] === Activity::ANNOUNCE) && ($item['parent-uri-id'] === $item['thr-parent-id'])) { - DI::ChannelPost()->add($engagement, self::GRAVITY_PARENT, $uid, $item['author-id']); + DI::ChannelPost()->add($engagement, $uid, $item['author-id']); DI::SystemChannelPost()->add($engagement, self::GRAVITY_PARENT, $uid, $item['network'], $item['author-id']); } else { if ($item['origin']) { - DI::ChannelPost()->add($engagement, $item['gravity'], $uid, $item['author-id']); - } else { - DI::ChannelPost()->add($engagement, $item['gravity'], $uid); + DI::ChannelPost()->add($engagement, $uid, $item['author-id']); + } elseif ($item['gravity'] === self::GRAVITY_PARENT) { + DI::ChannelPost()->add($engagement, $uid); } DI::SystemChannelPost()->add($engagement, $item['gravity'], $uid, $item['network']); } @@ -1260,7 +1240,7 @@ class Item foreach (DI::userDefinedChannel()->getMatchingChannelUsers($engagement['searchtext'], $language, $tags, $engagement['media-type'], $item['owner-id'], $reshare_id) as $uid) { $condition = [ 'verb' => Activity::ANNOUNCE, 'deleted' => false, 'gravity' => self::GRAVITY_ACTIVITY, - 'author-id' => Contact::getPublicIdByUserId($uid), 'uid' => $uid, 'thr-parent-id' => $uri_id, + 'author-id' => Contact::getPublicIdByUserId($uid), 'uid' => $uid, 'thr-parent-id' => $uri_id ]; if (!Post::exists($condition)) { DI::logger()->debug('Reshare post', ['uid' => $uid, 'uri-id' => $uri_id]); @@ -1335,7 +1315,7 @@ class Item $parent = Post::selectFirst( ['id', 'causer-id', 'owner-id', 'author-id', 'author-link', 'origin', 'post-reason'], - ['uri-id' => $item['thr-parent-id'], 'uid' => $item['uid']], + ['uri-id' => $item['thr-parent-id'], 'uid' => $item['uid']] ); if (!DBA::isResult($parent)) { DI::logger()->error('Parent not found', ['uri-id' => $item['thr-parent-id'], 'uid' => $item['uid']]); @@ -1434,7 +1414,7 @@ class Item $condition = [ 'id' => $itemid, 'uid' => 0, 'network' => array_merge(Protocol::FEDERATED, ['']), - 'visible' => true, 'deleted' => false, 'private' => [self::PUBLIC, self::UNLISTED], + 'visible' => true, 'deleted' => false, 'private' => [self::PUBLIC, self::UNLISTED] ]; $item = Post::selectFirst(array_merge(self::ITEM_FIELDLIST, ['protocol']), $condition); if (!DBA::isResult($item)) { @@ -1554,9 +1534,9 @@ class Item $is_reshare = ($item['gravity'] == self::GRAVITY_ACTIVITY) && ($item['verb'] == Activity::ANNOUNCE); - if (($uid != 0) && (($item['gravity'] == self::GRAVITY_PARENT) || $is_reshare) - && DI::pConfig()->get($uid, 'system', 'accept_only_sharer') == self::COMPLETION_NONE - && !in_array($item['post-reason'], [self::PR_FOLLOWER, self::PR_TAG, self::PR_TO, self::PR_CC, self::PR_ACTIVITY, self::PR_AUDIENCE]) + if (($uid != 0) && (($item['gravity'] == self::GRAVITY_PARENT) || $is_reshare) && + DI::pConfig()->get($uid, 'system', 'accept_only_sharer') == self::COMPLETION_NONE && + !in_array($item['post-reason'], [self::PR_FOLLOWER, self::PR_TAG, self::PR_TO, self::PR_CC, self::PR_ACTIVITY, self::PR_AUDIENCE]) ) { DI::logger()->info('Contact is not a follower, thread will not be stored', ['author' => $item['author-link'], 'uid' => $uid, 'uri-id' => $uri_id, 'post-reason' => $item['post-reason']]); return 0; @@ -1707,7 +1687,7 @@ class Item $contact = DBA::selectFirst('contact', [], ['id' => $item['contact-id'], 'self' => false]); if (DBA::isResult($contact)) { $notify = self::isRemoteSelf($contact, $item); - $item['wall'] = (bool) $notify; + $item['wall'] = (bool)$notify; } } @@ -1919,7 +1899,7 @@ class Item // Glue it together to be able to make a hash from it if (!empty($parsed)) { - $host_id = implode('/', (array) $parsed); + $host_id = implode('/', (array)$parsed); } else { $host_id = $uri; } @@ -1994,13 +1974,13 @@ class Item if ($arr['private'] != self::PRIVATE) { Contact::update( ['failed' => false, 'local-data' => true, 'success_update' => $arr['received'], 'last-item' => $arr['received']], - ['id' => $arr['owner-id']], + ['id' => $arr['owner-id']] ); if ($arr['owner-id'] != $arr['author-id']) { Contact::update( ['failed' => false, 'local-data' => true, 'success_update' => $arr['received'], 'last-item' => $arr['received']], - ['id' => $arr['author-id']], + ['id' => $arr['author-id']] ); } } @@ -2030,7 +2010,7 @@ class Item $body = preg_replace( "/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", "#[url=" . DI::baseUrl() . "/search?tag=$2]$2[/url]", - $body, + $body ); } @@ -2040,7 +2020,7 @@ class Item function ($match) { return ("[url=" . str_replace("#", "#", $match[1]) . "]" . str_replace("#", "#", $match[2]) . "[/url]"); }, - $body, + $body ); $body = preg_replace_callback( @@ -2048,7 +2028,7 @@ class Item function ($match) { return ("[bookmark=" . str_replace("#", "#", $match[1]) . "]" . str_replace("#", "#", $match[2]) . "[/bookmark]"); }, - $body, + $body ); $body = preg_replace_callback( @@ -2056,14 +2036,14 @@ class Item function ($match) { return ("[attachment " . str_replace("#", "#", $match[1]) . "]" . $match[2] . "[/attachment]"); }, - $body, + $body ); // Repair recursive urls $body = preg_replace( "/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", "#$2", - $body, + $body ); foreach ($tags as $tag) { @@ -2245,7 +2225,7 @@ class Item $self = DBA::selectFirst( 'contact', ['id', 'name', 'url', 'thumb'], - ['uid' => $contact['uid'], 'self' => true], + ['uid' => $contact['uid'], 'self' => true] ); if (!DBA::isResult($self)) { DI::logger()->error('Self contact not found', ['uid' => $contact['uid']]); @@ -2292,7 +2272,7 @@ class Item unset($datarray['private']); } - return (bool) $result; + return (bool)$result; } /** @@ -2401,8 +2381,8 @@ class Item private static function hasPermissions(array $obj) { - return !empty($obj['allow_cid']) || !empty($obj['allow_gid']) - || !empty($obj['deny_cid']) || !empty($obj['deny_gid']); + return !empty($obj['allow_cid']) || !empty($obj['allow_gid']) || + !empty($obj['deny_cid']) || !empty($obj['deny_gid']); } // @TODO $uid is unused parameter @@ -2462,7 +2442,7 @@ class Item $condition = [ "`uid` = ? AND NOT `deleted` AND `gravity` = ?", - $uid, self::GRAVITY_PARENT, + $uid, self::GRAVITY_PARENT ]; /* @@ -2489,16 +2469,16 @@ class Item return 0; } - $expire_items = (bool) DI::pConfig()->get($uid, 'expire', 'items', true); + $expire_items = (bool)DI::pConfig()->get($uid, 'expire', 'items', true); // Forcing expiring of items - but not notes and marked items if ($force) { $expire_items = true; } - $expire_notes = (bool) DI::pConfig()->get($uid, 'expire', 'notes', true); - $expire_starred = (bool) DI::pConfig()->get($uid, 'expire', 'starred', true); - $expire_photos = (bool) DI::pConfig()->get($uid, 'expire', 'photos', false); + $expire_notes = (bool)DI::pConfig()->get($uid, 'expire', 'notes', true); + $expire_starred = (bool)DI::pConfig()->get($uid, 'expire', 'starred', true); + $expire_photos = (bool)DI::pConfig()->get($uid, 'expire', 'photos', false); $expired = 0; @@ -2540,7 +2520,7 @@ class Item $condition = [ "`uid` = ? AND `wall` = ? AND NOT `deleted` AND `visible` AND `received` >= ?", - $uid, $wall, $user['register_date'], + $uid, $wall, $user['register_date'] ]; $params = ['order' => ['received' => false]]; $thread = Post::selectFirstThread(['received'], $condition, $params); @@ -2591,7 +2571,7 @@ class Item return false; } - if (!in_array($verb, ['view', 'unview']) && !Post::exists(['uri-id' => $item['parent-uri-id'], 'uid' => $uid])) { + if (!Post::exists(['uri-id' => $item['parent-uri-id'], 'uid' => $uid])) { $stored = self::storeForUserByUriId($item['parent-uri-id'], $uid, ['post-reason' => Item::PR_ACTIVITY]); if (($item['parent-uri-id'] == $item['uri-id']) && !empty($stored)) { $item = Post::selectFirst(self::ITEM_FIELDLIST, ['id' => $stored]); @@ -2646,10 +2626,6 @@ class Item case 'unannounce': $activity = Activity::ANNOUNCE; break; - case 'view': - case 'unview': - $activity = Activity::VIEW; - break; default: DI::logger()->warning('unknown verb', ['verb' => $verb, 'item' => $item_id]); return false; @@ -2676,7 +2652,7 @@ class Item $condition = [ 'vid' => $vids, 'deleted' => false, 'gravity' => self::GRAVITY_ACTIVITY, - 'author-id' => $author_id, 'uid' => $uid, 'thr-parent-id' => $uri_id, + 'author-id' => $author_id, 'uid' => $uid, 'thr-parent-id' => $uri_id ]; $like_item = Post::selectFirst(['id', 'guid', 'verb'], $condition); @@ -2787,7 +2763,7 @@ class Item $condition = [ "(`private` != ? OR (`private` = ? AND `wall` AND `psid` IN (" . implode(', ', array_fill(0, count($permissionSets), '?')) . ")))", - self::PRIVATE, self::PRIVATE, + self::PRIVATE, self::PRIVATE ]; $condition = array_merge($condition, $permissionSets->column('id')); } @@ -2914,9 +2890,9 @@ class Item self::update( [ 'rendered-html' => $item['rendered-html'], - 'rendered-hash' => $item['rendered-hash'], + 'rendered-hash' => $item['rendered-hash'] ], - ['id' => $item['id']], + ['id' => $item['id']] ); } } @@ -3015,9 +2991,6 @@ class Item $quote_uri_id = $shared_item['uri-id']; } } - } elseif (!empty($item['quote-uri'])) { - DI::logger()->notice('Quote-uri specified, but it had not been found on the system.', ['uri-id' => $item['uri-id'], 'quote-uri' => $item['quote-uri']]); - $item['body'] .= "\n[hr]\nRE: [url]" . $item['quote-uri'] . '[/url]'; } if (!empty($quote_uri_id)) { @@ -3070,7 +3043,7 @@ class Item $hook_data = [ 'item' => $item, - 'filter_reasons' => $filter_reasons, + 'filter_reasons' => $filter_reasons ]; $hook_data = $eventDispatcher->dispatch( @@ -3093,7 +3066,7 @@ class Item 'item' => $item, 'html' => $s, 'preview' => $is_preview, - 'filter_reasons' => $filter_reasons, + 'filter_reasons' => $filter_reasons ]; $hook_data = $eventDispatcher->dispatch( @@ -3276,7 +3249,7 @@ class Item unset($urlparts['fragment']); try { - $url = (string) Uri::fromParts((array) $urlparts); + $url = (string)Uri::fromParts((array)$urlparts); } catch (\InvalidArgumentException $e) { DI::logger()->notice('Invalid URL', ['$url' => $url, '$urlparts' => $urlparts]); /* See https://github.com/friendica/friendica/issues/12113 @@ -3297,8 +3270,8 @@ class Item foreach ([0, 1, 2] as $size) { if ( - preg_match('#/photo/.*-' . $size . '\.#ism', $url) - && strpos(preg_replace('#(/photo/.*)-[012]\.#ism', '$1-' . $size . '.', $body), $url) + preg_match('#/photo/.*-' . $size . '\.#ism', $url) && + strpos(preg_replace('#(/photo/.*)-[012]\.#ism', '$1-' . $size . '.', $body), $url) ) { return true; } @@ -3470,10 +3443,10 @@ class Item } } // @todo Judge between the links to use the one with most information - if (!$found && (empty($attachment) || $PostMedia->authorName - || (!$attachment->name && $PostMedia->name) - || (!$attachment->description && $PostMedia->description) - || (!$attachment->preview && $PostMedia->preview))) { + if (!$found && (empty($attachment) || $PostMedia->authorName || + (!$attachment->name && $PostMedia->name) || + (!$attachment->description && $PostMedia->description) || + (!$attachment->preview && $PostMedia->preview))) { $attachment = $PostMedia; } } @@ -3482,17 +3455,17 @@ class Item $data = [ 'after' => '', 'author_name' => $attachment->authorName ?? '', - 'author_url' => (string) ($attachment->authorUrl ?? ''), + 'author_url' => (string)($attachment->authorUrl ?? ''), 'description' => $attachment->description ?? '', 'image' => '', 'preview' => '', 'provider_name' => $attachment->publisherName ?? '', - 'provider_url' => (string) ($attachment->publisherUrl ?? ''), + 'provider_url' => (string)($attachment->publisherUrl ?? ''), 'text' => '', 'title' => $attachment->name ?? '', 'type' => 'link', - 'url' => (string) $attachment->url, - 'player_url' => (string) $attachment->playerUrl, + 'url' => (string)$attachment->url, + 'player_url' => (string)$attachment->playerUrl, 'player_width' => $attachment->playerWidth, 'player_height' => $attachment->playerHeight, 'embed_type' => $attachment->embedType, @@ -3601,7 +3574,7 @@ class Item $trailing = ''; /** @var PostMedia $PostMedia */ foreach ($PostMedias as $PostMedia) { - if (strpos($item['body'], (string) $PostMedia->url)) { + if (strpos($item['body'], (string)$PostMedia->url)) { continue; } @@ -3610,7 +3583,7 @@ class Item 'id' => $item['author-id'], 'network' => $item['author-network'], 'url' => $item['author-link'], - 'alias' => $item['author-alias'], + 'alias' => $item['author-alias'] ]; $the_url = Contact::magicLinkByContact($author, $PostMedia->url); @@ -3671,7 +3644,7 @@ class Item 'id' => $item['question-id'], 'multiple' => $item['question-multiple'], 'voters' => $item['question-voters'], - 'endtime' => $item['question-end-time'], + 'endtime' => $item['question-end-time'] ]; $options = Post\QuestionOption::getByURIId($item['uri-id']); @@ -3685,11 +3658,11 @@ class Item } if (!empty($question['voters']) && !empty($question['endtime'])) { - $summary = DI::l10n()->tt('%d voter. Poll end: %s', '%d voters. Poll end: %s', $question['voters'] ?? 0, DI::l10n()->relativeDateTime($question['endtime'])); + $summary = DI::l10n()->tt('%d voter. Poll end: %s', '%d voters. Poll end: %s', $question['voters'] ?? 0, Temporal::getRelativeDate($question['endtime'])); } elseif (!empty($question['voters'])) { $summary = DI::l10n()->tt('%d voter.', '%d voters.', $question['voters'] ?? 0); } elseif (!empty($question['endtime'])) { - $summary = DI::l10n()->t('Poll end: %s', DI::l10n()->relativeDateTime($question['endtime'])); + $summary = DI::l10n()->t('Poll end: %s', Temporal::getRelativeDate($question['endtime'])); } else { $summary = ''; } @@ -3870,7 +3843,7 @@ class Item ]; $hook_data = $eventDispatcher->dispatch( - new ArrayFilterEvent(ArrayFilterEvent::FETCH_ITEM_BY_LINK, $hook_data), + new ArrayFilterEvent(ArrayFilterEvent::FETCH_ITEM_BY_LINK, $hook_data) )->getArray(); if (isset($hook_data['item_id'])) { @@ -3982,7 +3955,7 @@ class Item public static function incrementInbound(string $network) { - $packets = (int) (DI::keyValue()->get('stats_packets_inbound_' . $network) ?? 0); + $packets = (int)(DI::keyValue()->get('stats_packets_inbound_' . $network) ?? 0); if ($packets >= PHP_INT_MAX) { $packets = 0; } @@ -3991,7 +3964,7 @@ class Item public static function incrementOutbound(string $network) { - $packets = (int) (DI::keyValue()->get('stats_packets_outbound_' . $network) ?? 0); + $packets = (int)(DI::keyValue()->get('stats_packets_outbound_' . $network) ?? 0); if ($packets >= PHP_INT_MAX) { $packets = 0; } diff --git a/src/Model/ItemHelper.php b/src/Model/ItemHelper.php index 75ed1f279f..4b348e4964 100644 --- a/src/Model/ItemHelper.php +++ b/src/Model/ItemHelper.php @@ -92,7 +92,7 @@ final class ItemHelper $item['uid'], Protocol::ACTIVITYPUB, Protocol::DIASPORA, - Protocol::DFRN, + Protocol::DFRN ]; $existing = Post::selectFirst(['id', 'network'], $condition); @@ -105,7 +105,7 @@ final class ItemHelper 'uid' => $item['uid'], 'network' => $item['network'], 'existing_id' => $existing['id'], - 'existing_network' => $existing['network'], + 'existing_network' => $existing['network'] ]); } @@ -134,7 +134,7 @@ final class ItemHelper $condition = [ 'uri-id' => $item['uri-id'], 'uid' => $item['uid'], - 'network' => [$item['network'], Protocol::DFRN], + 'network' => [$item['network'], Protocol::DFRN] ]; if (Post::exists($condition)) { $this->logger->notice('duplicated item with the same uri found.', $condition); @@ -214,15 +214,15 @@ final class ItemHelper } // We haven't invented time travel by now. - if ($item['edited'] > $item['received']) { + if ($item['edited'] > $item['received'] ) { $item['edited'] = $item['received'] ; } - if ($item['changed'] > $item['received']) { + if ($item['changed'] > $item['received'] ) { $item['changed'] = $item['received'] ; } - if ($item['commented'] > $item['received']) { + if ($item['commented'] > $item['received'] ) { $item['commented'] = $item['received'] ; } @@ -236,13 +236,13 @@ final class ItemHelper $default = [ 'url' => $item['author-link'], 'name' => $item['author-name'], - 'photo' => $item['author-avatar'], 'network' => $item['network'], + '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'], + 'photo' => $item['owner-avatar'], 'network' => $item['network'] ]; $item['owner-id'] = ($item['owner-id'] ?? 0) ?: Contact::getIdForURL($item['owner-link'], 0, null, $default); @@ -264,12 +264,9 @@ final class ItemHelper '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', + 'wall', 'private', 'origin', 'author-id' ]; - - $uids = $item['verb'] === Activity::VIEW ? [0, $item['uid']] : $item['uid']; - - $condition = ['uri-id' => [$item['thr-parent-id'], $item['parent-uri-id']], 'uid' => $uids]; + $condition = ['uri-id' => [$item['thr-parent-id'], $item['parent-uri-id']], 'uid' => $item['uid']]; $params = ['order' => ['id' => false]]; $parent = Post::selectFirst($fields, $condition, $params); @@ -301,7 +298,7 @@ final class ItemHelper $condition = [ 'uri-id' => $parent['parent-uri-id'], 'parent-uri-id' => $parent['parent-uri-id'], - 'uid' => $parent['uid'], + 'uid' => $parent['uid'] ]; $params = ['order' => ['id' => false]]; $toplevel_parent = Post::selectFirst($fields, $condition, $params); @@ -357,7 +354,7 @@ final class ItemHelper } // If its a post that originated here then tag the thread as "mention" - if ($item['origin'] && $item['uid'] && $item['verb'] !== Activity::VIEW) { + 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']]); } @@ -368,7 +365,7 @@ final class ItemHelper return $item; } - private function hasRestrictions(array $item, int $author_id, ?int $restrictions = null): bool + private function hasRestrictions(array $item, int $author_id, int $restrictions = null): bool { if (empty($restrictions) || ($author_id == $item['author-id'])) { return false; @@ -396,16 +393,6 @@ final class ItemHelper return true; } - if ($item['uid'] != 0 && Contact\User::isBlocked($item['author-id'], $item['uid'])) { - $this->logger->debug('Author is blocked by the user, post is ignored.', ['author' => $item['author-id'], 'uid' => $item['uid']]); - return true; - } - - if ($item['uid'] != 0 && Contact\User::isIsBlocked($author_id, $item['uid'])) { - $this->logger->debug('User is blocked by the author, post is ignored.', ['author' => $author_id, 'uid' => $item['uid']]); - return true; - } - return false; } diff --git a/src/Model/Photo.php b/src/Model/Photo.php index 9830a1ae56..833834e771 100644 --- a/src/Model/Photo.php +++ b/src/Model/Photo.php @@ -908,7 +908,7 @@ class Photo $fields = [ 'allow_cid' => $str_contact_allow, 'allow_gid' => $str_circle_allow, 'deny_cid' => $str_contact_deny, 'deny_gid' => $str_circle_deny, - 'accessible' => (bool)DI::pConfig()->get($uid, 'system', 'accessible-photos', false) + 'accessible' => DI::pConfig()->get($uid, 'system', 'accessible-photos', false) ]; $condition = ['resource-id' => $image_rid, 'uid' => $uid]; diff --git a/src/Model/Post/Content.php b/src/Model/Post/Content.php index 052ed061e5..1b3dd3a0d8 100644 --- a/src/Model/Post/Content.php +++ b/src/Model/Post/Content.php @@ -7,7 +7,7 @@ namespace Friendica\Model\Post; -use BadMethodCallException; +use \BadMethodCallException; use Friendica\Database\Database; use Friendica\Database\DBA; use Friendica\DI; @@ -75,16 +75,6 @@ class Content return DBA::delete('post-content', $conditions); } - /** - * Check if a post-content entry exists for the given URI ID - * - * @param integer $uri_id - * @return boolean exists? - */ - public static function exists(int $uri_id): bool - { - return DBA::exists('post-content', ['uri-id' => $uri_id]); - } /** * Search posts for given content diff --git a/src/Model/Post/Counts.php b/src/Model/Post/Counts.php index 487b6e831a..3d971a5757 100644 --- a/src/Model/Post/Counts.php +++ b/src/Model/Post/Counts.php @@ -83,25 +83,25 @@ class Counts $counts = []; $activity_verbs = [ - Verb::getID(Activity::LIKE), - Verb::getID(Activity::DISLIKE), - Verb::getID(Activity::ATTEND), - Verb::getID(Activity::ATTENDMAYBE), - Verb::getID(Activity::ATTENDNO), - Verb::getID(Activity::ANNOUNCE), - Verb::getID(Activity::VIEW), - Verb::getID(Activity::READ), + Activity::LIKE, + Activity::DISLIKE, + Activity::ATTEND, + Activity::ATTENDMAYBE, + Activity::ATTENDNO, + Activity::ANNOUNCE, + Activity::VIEW, + Activity::READ, ]; - $verbs = array_merge($activity_verbs, [Verb::getID(Activity::EMOJIREACT), Verb::getID(Activity::POST)]); + $verbs = array_merge($activity_verbs, [Activity::EMOJIREACT, Activity::POST]); - $condition = DBA::mergeConditions($condition, ['vid' => $verbs]); + $condition = DBA::mergeConditions($condition, ['verb' => $verbs]); $countquery = DBA::select('post-counts-view', [], $condition); while ($count = DBA::fetch($countquery)) { if (!empty($count['reaction'])) { $count['verb'] = Activity::EMOJIREACT; $count['vid'] = Verb::getID($count['verb']); - } elseif (in_array($count['vid'], $activity_verbs)) { + } elseif (in_array($count['verb'], $activity_verbs)) { $count['reaction'] = $count['verb']; } $counts[] = $count; diff --git a/src/Model/Post/Engagement.php b/src/Model/Post/Engagement.php index e9e75acf69..7c8a302ac5 100644 --- a/src/Model/Post/Engagement.php +++ b/src/Model/Post/Engagement.php @@ -108,12 +108,11 @@ class Engagement 'network' => $parent['network'], 'restricted' => !in_array($item['network'], Protocol::FEDERATED) || ($parent['private'] != Item::PUBLIC), 'comments' => DBA::count('post', ['parent-uri-id' => $item['parent-uri-id'], 'gravity' => Item::GRAVITY_COMMENT]), - 'views' => DBA::count('post', ['parent-uri-id' => $item['parent-uri-id'], 'gravity' => Item::GRAVITY_ACTIVITY, 'vid' => [Verb::getID(Activity::VIEW), Verb::getID(Activity::READ)]]), 'activities' => DBA::count('post', [ "`parent-uri-id` = ? AND `gravity` = ? AND NOT `vid` IN (?, ?, ?)", $item['parent-uri-id'], Item::GRAVITY_ACTIVITY, Verb::getID(Activity::FOLLOW), Verb::getID(Activity::VIEW), Verb::getID(Activity::READ) - ]), + ]) ]; if (!$store && ($engagement['comments'] == 0) && ($engagement['activities'] == 0)) { DI::logger()->debug('No media, follower, subscribed tags, comments or activities. Engagement not stored', ['fields' => $engagement]); @@ -380,7 +379,7 @@ class Engagement DI::logger()->notice('Cleared expired engagements', ['limit' => $limit, 'rows' => DBA::affectedRows()]); } - public static function getCreationDateLimit(bool $forDeletion): string + private static function getCreationDateLimit(bool $forDeletion): string { $posts = DI::config()->get('channel', 'engagement_post_limit'); if (!empty($posts)) { diff --git a/src/Model/Post/Media.php b/src/Model/Post/Media.php index c384c57b3f..f75d8adfff 100644 --- a/src/Model/Post/Media.php +++ b/src/Model/Post/Media.php @@ -44,22 +44,22 @@ use GuzzleHttp\Psr7\Uri; */ class Media { - public const UNKNOWN = 0; - public const IMAGE = 1; - public const VIDEO = 2; - public const AUDIO = 3; - public const TEXT = 4; - public const APPLICATION = 5; - public const TORRENT = 16; - public const HTML = 17; - public const XML = 18; - public const PLAIN = 19; - public const ACTIVITY = 20; - public const ACCOUNT = 21; - public const HLS = 22; - public const JSON = 23; - public const LD = 24; - public const DOCUMENT = 128; + const UNKNOWN = 0; + const IMAGE = 1; + const VIDEO = 2; + const AUDIO = 3; + const TEXT = 4; + const APPLICATION = 5; + const TORRENT = 16; + const HTML = 17; + const XML = 18; + const PLAIN = 19; + const ACTIVITY = 20; + const ACCOUNT = 21; + const HLS = 22; + const JSON = 23; + const LD = 24; + const DOCUMENT = 128; /** * Insert a post-media record @@ -105,22 +105,15 @@ class Media $stored = $media; $media = self::fetchAdditionalData($media); - $exif = $media['exif'] ?? null; $media = self::unsetEmptyFields($media); $media = DI::dbaDefinition()->truncateFieldsForTable('post-media', $media); if (array_diff_assoc($media, $stored)) { $result = DBA::insert('post-media', $media, Database::INSERT_UPDATE); - $id = $media['id'] ?? DBA::lastInsertId(); - DI::logger()->info('Updated media', ['result' => $result, 'id' => $id, 'media' => $media]); + DI::logger()->info('Updated media', ['result' => $result, 'media' => $media]); } else { - $id = null; DI::logger()->info('Nothing to update', ['media' => $media]); } - - if (isset($id) && isset($exif)) { - MediaExif::insert($id, $media['uri-id'], $exif); - } return $result; } @@ -173,11 +166,11 @@ class Media 'url' => $href, 'size' => $length, 'mimetype' => $type, - 'description' => $title, + 'description' => $title ]); - return '[attach]href="' . $media['url'] . '" length="' . $media['size'] - . '" type="' . $media['mimetype'] . '" title="' . $media['description'] . '"[/attach]'; + return '[attach]href="' . $media['url'] . '" length="' . $media['size'] . + '" type="' . $media['mimetype'] . '" title="' . $media['description'] . '"[/attach]'; } private static function setModified(array $media, string $lastModified): array @@ -190,8 +183,8 @@ class Media return $media; } - $media['modified'] = DateTimeFormat::utc($lastModified); - $media['published'] ??= $media['modified']; + $media['modified'] = DateTimeFormat::utc($lastModified); + $media['published'] = $media['published'] ?? $media['modified']; return $media; } @@ -235,7 +228,7 @@ class Media $media['mimetype'] = $curlResult->getContentType(); } if (empty($media['size']) && $is_head) { - $media['size'] = (int) ($curlResult->getHeader('Content-Length')[0] ?? strlen($curlResult->getBodyString() ?? '')); + $media['size'] = (int)($curlResult->getHeader('Content-Length')[0] ?? strlen($curlResult->getBodyString() ?? '')); } $media = self::setModified($media, $curlResult->getHeader('Last-Modified')[0] ?? ''); } else { @@ -260,7 +253,6 @@ class Media $media['width'] = $imagedata[0]; $media['height'] = $imagedata[1]; $media['blurhash'] = $imagedata['blurhash'] ?? null; - $media['exif'] = $imagedata['exif'] ?? null; if (!empty($imagedata['description']) && empty($media['description'])) { $media['description'] = $imagedata['description']; DI::logger()->debug('Detected text for image', $media); @@ -373,8 +365,8 @@ class Media } if ( - !empty($item['plink']) && Strings::compareLink($item['plink'], $media['url']) - && parse_url($item['plink'], PHP_URL_HOST) != parse_url($item['uri'], PHP_URL_HOST) + !empty($item['plink']) && Strings::compareLink($item['plink'], $media['url']) && + parse_url($item['plink'], PHP_URL_HOST) != parse_url($item['uri'], PHP_URL_HOST) ) { DI::logger()->debug('Not a link to an activity', ['uri-id' => $media['uri-id'], 'url' => $media['url'], 'plink' => $item['plink'], 'uri' => $item['uri']]); $media['type'] = $media['type'] == self::ACTIVITY ? self::JSON : $media['type']; @@ -794,7 +786,7 @@ class Media foreach (explode("\n", $curlResult->getBodyString() ?? '') as $line) { if (strpos(trim($line), '#EXT-X-STREAM-INF') === 0) { if (preg_match('/RESOLUTION=([\d]+)x([\d]+)/', $line, $matches)) { - $resolutions[$matches[1]] = [(int) $matches[1], (int) $matches[2]]; + $resolutions[$matches[1]] = [(int)$matches[1], (int)$matches[2]]; } } } @@ -893,7 +885,7 @@ class Media 'type' => self::IMAGE, 'url' => $image, 'preview' => $picture[2], - 'description' => $picture[3], + 'description' => $picture[3] ]; } elseif (self::isLinkToPhoto($picture[1], $picture[2])) { $body = str_replace($picture[0], '', $body); @@ -903,7 +895,7 @@ class Media 'type' => self::IMAGE, 'url' => $picture[1], 'preview' => $picture[2], - 'description' => $picture[3], + 'description' => $picture[3] ]; } elseif ($removepicturelinks) { $body = str_replace($picture[0], '', $body); @@ -913,7 +905,7 @@ class Media 'type' => self::UNKNOWN, 'url' => $picture[1], 'preview' => $picture[2], - 'description' => $picture[3], + 'description' => $picture[3] ]; } } @@ -938,7 +930,7 @@ class Media 'type' => self::IMAGE, 'url' => $image, 'preview' => $picture[2], - 'description' => null, + 'description' => null ]; } elseif (self::isLinkToPhoto($picture[1], $picture[2])) { $body = str_replace($picture[0], '', $body); @@ -948,7 +940,7 @@ class Media 'type' => self::IMAGE, 'url' => $picture[1], 'preview' => $picture[2], - 'description' => null, + 'description' => null ]; } elseif ($removepicturelinks) { $body = str_replace($picture[0], '', $body); @@ -958,7 +950,7 @@ class Media 'type' => self::UNKNOWN, 'url' => $picture[1], 'preview' => $picture[2], - 'description' => null, + 'description' => null ]; } } @@ -1362,7 +1354,7 @@ class Media 'src' => $links[0]['preview'], 'height' => $links[0]['preview-height'], 'width' => $links[0]['preview-width'], - ]], + ]] ]; $body .= "\n" . PageInfo::getFooterFromData($data); @@ -1383,7 +1375,7 @@ class Media return $body; } - if (strpos($body, (string) $links[0]['url'])) { + if (strpos($body, $links[0]['url'])) { return $body; } @@ -1424,9 +1416,9 @@ class Media */ public static function getPreviewUrlForId(int $id, string $size = ''): string { - return DI::baseUrl() . '/photo/preview/' - . (Proxy::getPixelsFromSize($size) ? Proxy::getPixelsFromSize($size) . '/' : '') - . $id; + return DI::baseUrl() . '/photo/preview/' . + (Proxy::getPixelsFromSize($size) ? Proxy::getPixelsFromSize($size) . '/' : '') . + $id; } /** @@ -1438,9 +1430,9 @@ class Media */ public static function getUrlForId(int $id, string $size = ''): string { - return DI::baseUrl() . '/photo/media/' - . (Proxy::getPixelsFromSize($size) ? Proxy::getPixelsFromSize($size) . '/' : '') - . $id; + return DI::baseUrl() . '/photo/media/' . + (Proxy::getPixelsFromSize($size) ? Proxy::getPixelsFromSize($size) . '/' : '') . + $id; } /** diff --git a/src/Model/Post/MediaExif.php b/src/Model/Post/MediaExif.php deleted file mode 100644 index c5cbc3e37d..0000000000 --- a/src/Model/Post/MediaExif.php +++ /dev/null @@ -1,265 +0,0 @@ - $media_id, - 'uri-id' => $uri_id, - 'raw-data' => json_encode($data), - 'FocalLength' => self::getFocalLength($exif), - 'ExposureTime' => self::getExposureTime($exif), - 'LensSpecification' => self::getLensSpecification($exif), - 'ApertureFNumber' => $data['COMPUTED']['ApertureFNumber'] ?? null, - 'FocusDistance' => $data['COMPUTED']['FocusDistance'] ?? null, - 'CCDWidth' => $data['COMPUTED']['CCDWidth'] ?? null, - 'ISOSpeedRatings' => $exif['ISOSpeedRatings'] ?? null, - 'DateTime' => self::getDateTime($exif), - 'DateTimeOriginal' => self::getDateTimeOriginal($exif), - 'DateTimeDigitized' => self::getDateTimeDigitized($exif), - 'BodySerialNumber' => $exif['BodySerialNumber'] ?? null, - 'Orientation' => $exif['Orientation'] ?? null, - 'Artist' => $exif['Artist'] ?? null, - 'Copyright' => $data['COMPUTED']['Copyright'] ?? null, - 'ExpandFilm' => $exif['ExpandFilm'] ?? null, - 'ExpandLens' => $exif['ExpandLens'] ?? null, - 'HostComputer' => $exif['HostComputer'] ?? null, - 'ImageDescription' => $exif['ImageDescription'] ?? null, - "ImageUniqueID" => $exif['ImageUniqueID'] ?? null, - "LensMake" => $exif['LensMake'] ?? null, - "LensModel" => $exif['LensModel'] ?? null, - 'Make' => $exif['Make'] ?? null, - 'MakerNote' => $exif['MakerNote'] ?? null, - 'Model' => $exif['Model'] ?? null, - 'OwnerName' => $exif['OwnerName'] ?? null, - 'Software' => $exif['Software'] ?? null, - "UserComment" => $exif['UserComment'] ?? null, - ]; - - if (isset($exif['GPSLatitude']) && isset($exif['GPSLatitudeRef']) && isset($exif['GPSLongitude']) && isset($exif['GPSLongitudeRef'])) { - $row['coord'] = Photo::getGps($exif['GPSLatitude'], $exif['GPSLatitudeRef']) . ' ' . Photo::getGps($exif['GPSLongitude'], $exif['GPSLongitudeRef']); - } - - $row = DI::dbaDefinition()->truncateFieldsForTable('post-media-exif', $row); - - $result = DBA::insert('post-media-exif', $row, Database::INSERT_UPDATE); - DI::logger()->info('Updated media exif', ['result' => $result, 'row' => $row]); - return $result; - } - - /** - * Get the lens specification from exif data - * - * @param array $exif - * @return string|null - */ - private static function getLensSpecification(array $exif): ?string - { - if (!isset($exif['LensSpecification']) || !is_array($exif['LensSpecification']) || count($exif['LensSpecification']) != 4) { - return null; - } - - $vals = []; - foreach ($exif['LensSpecification'] as $val) { - $parts = explode('/', $val); - if (count($parts) == 2 && is_numeric($parts[0]) && is_numeric($parts[1]) && $parts[1] != 0) { - $vals[] = (float) $parts[0] / (float) $parts[1]; - } else { - $vals[] = null; - } - } - - $LensSpecification = null; - if (count($vals) == 4 && !in_array(null, $vals)) { - if (isset($vals[0]) && $vals[0] == $vals[1]) { - $LensSpecification = round($vals[0]) . 'mm'; - } elseif (isset($vals[0]) && isset($vals[1])) { - $LensSpecification = round($vals[0]) . '-' . round($vals[1]) . 'mm'; - } - if (isset($vals[2]) && $vals[2] == $vals[3]) { - $LensSpecification .= ' f/' . round($vals[2], 1); - } elseif (isset($vals[2]) && isset($vals[3])) { - $LensSpecification .= ' f/' . round($vals[2], 1) . '-' . round($vals[3], 1); - } - } - - return $LensSpecification; - } - - /** - * Get the focal length from exif data - * - * @param array $exif - * @return string|null - */ - private static function getFocalLength(array $exif): ?string - { - if (!isset($exif['FocalLength'])) { - return null; - } - - $parts = explode('/', $exif['FocalLength']); - if (count($parts) == 2 && is_numeric($parts[0]) && is_numeric($parts[1]) && $parts[1] != 0) { - return round((float) $parts[0] / (float) $parts[1], 1) . ' mm'; - } - - return null; - } - - /** - * Get the exposure time from exif data - * - * @param array $exif - * @return string|null - */ - private static function getExposureTime(array $exif): ?string - { - if (!isset($exif['ExposureTime'])) { - return null; - } - - $parts = explode('/', $exif['ExposureTime']); - if (count($parts) == 2 && is_numeric($parts[0]) && is_numeric($parts[1]) && $parts[1] != 0) { - $value = (float) $parts[0] / (float) $parts[1]; - if ($value >= 1) { - return (string) round($value, 0); - } elseif ($value > 0) { - return '1/' . round(1 / $value); - } - } - - return null; - } - - /** - * Get the DateTime from exif data - * - * @param array $exif - * @return string|null - */ - private static function getDateTime(array $exif): ?string - { - $dateTime = $exif['DateTime'] ?? null; - if ($dateTime && isset($exif['OffsetTime'])) { - $dateTime .= ' ' . $exif['OffsetTime']; - } - return $dateTime ? DateTimeFormat::utc($dateTime) : null; - } - - /** - * Get the DateTimeOriginal from exif data - * - * @param array $exif - * @return string|null - */ - private static function getDateTimeOriginal(array $exif): ?string - { - $dateTime = $exif['DateTimeOriginal'] ?? null; - if ($dateTime && isset($exif['OffsetTimeOriginal'])) { - $dateTime .= ' ' . $exif['OffsetTimeOriginal']; - } - return $dateTime ? DateTimeFormat::utc($dateTime) : null; - } - - /** - * Get the DateTimeDigitized from exif data - * - * @param array $exif - * @return string|null - */ - private static function getDateTimeDigitized(array $exif): ?string - { - $dateTime = $exif['DateTimeDigitized'] ?? null; - if ($dateTime && isset($exif['OffsetTimeDigitized'])) { - $dateTime .= ' ' . $exif['OffsetTimeDigitized']; - } - return $dateTime ? DateTimeFormat::utc($dateTime) : null; - } - - /** - * Translation for all tags that aren't known by "exif_read_data" - * @see https://exiv2.org/tags.html - * @param array $data - * @return array - */ - public static function translate(array $data): array - { - $translation = [ - 'UndefinedTag:0x4746' => 'Rating', - 'UndefinedTag:0x8830' => 'SensitivityType', - 'UndefinedTag:0x8831' => 'StandardOutputSensitivity', - 'UndefinedTag:0x8832' => 'RecommendedExposureIndex', - 'UndefinedTag:0x9010' => 'OffsetTime', - 'UndefinedTag:0x9011' => 'OffsetTimeOriginal', - 'UndefinedTag:0x9012' => 'OffsetTimeDigitized', - 'UndefinedTag:0x9402' => 'Pressure', - 'UndefinedTag:0xA431' => 'BodySerialNumber', - 'UndefinedTag:0xA432' => 'LensSpecification', - 'UndefinedTag:0xA433' => 'LensMake', - 'UndefinedTag:0xA434' => 'LensModel', - 'UndefinedTag:0xA460' => 'CompositeImage', - 'UndefinedTag:0xAFC1' => 'ExpandLens', - 'UndefinedTag:0xAFC2' => 'ExpandFilm', - 'UndefinedTag:0xC4A5' => 'PrintImageMatching', - ]; - $exif = []; - foreach ($data as $key => $value) { - if (isset($translation[$key])) { - $exif[$translation[$key]] = $value; - } else { - $exif[$key] = $value; - } - } - - $translation = [ - 'InternalSerialNumber' => 'BodySerialNumber', - 'Author' => 'Artist', - ]; - - foreach ($translation as $key => $value) { - if (!isset($exif[$value]) && isset($exif[$key])) { - $exif[$value] = $exif[$key]; - } - unset($exif[$key]); - } - - if (isset($data['COMPUTED']['UserComment'])) { - $exif['UserComment'] = $data['COMPUTED']['UserComment']; - } - - if (!isset($exif['UserComment']) && isset($data['COMMENT']) && is_array($data['COMMENT'])) { - $exif['UserComment'] = implode("\n", $data['COMMENT']); - unset($exif['COMMENT']); - } - return $exif; - } -} diff --git a/src/Model/Post/Thread.php b/src/Model/Post/Thread.php index e59d5f6506..d3952fe624 100644 --- a/src/Model/Post/Thread.php +++ b/src/Model/Post/Thread.php @@ -7,7 +7,7 @@ namespace Friendica\Model\Post; -use BadMethodCallException; +use \BadMethodCallException; use Friendica\Database\Database; use Friendica\Database\DBA; use Friendica\DI; @@ -73,15 +73,4 @@ class Thread { return DBA::delete('post-thread', $conditions); } - - /** - * Check if a post-thread entry exists for the given URI ID - * - * @param integer $uri_id - * @return boolean exists? - */ - public static function exists(int $uri_id): bool - { - return DBA::exists('post-thread', ['uri-id' => $uri_id]); - } } diff --git a/src/Model/Profile.php b/src/Model/Profile.php index a73531c9b6..897826ad22 100644 --- a/src/Model/Profile.php +++ b/src/Model/Profile.php @@ -9,7 +9,6 @@ namespace Friendica\Model; use Friendica\App\Mode; use Friendica\AppHelper; -use Friendica\Content\Feature; use Friendica\Content\Text\BBCode; use Friendica\Content\Widget\ContactBlock; use Friendica\Core\Cache\Enum\Duration; @@ -310,7 +309,7 @@ class Profile } $local_user_is_self = DI::userSession()->getMyUrl() && ($profile['url'] == DI::userSession()->getMyUrl()); - $visitor_is_authenticated = (bool) DI::userSession()->getMyUrl(); + $visitor_is_authenticated = (bool)DI::userSession()->getMyUrl(); $visitor_is_following = in_array($visitor_contact['rel'] ?? 0, [Contact::FOLLOWER, Contact::FRIEND]) || in_array($profile_contact['rel'] ?? 0, [Contact::SHARING, Contact::FRIEND]); $visitor_is_followed = in_array($visitor_contact['rel'] ?? 0, [Contact::SHARING, Contact::FRIEND]) @@ -320,7 +319,7 @@ class Profile if (!$local_user_is_self) { if (!$visitor_is_authenticated) { // Remote follow is only available for local profiles - if (!empty($profile['nickname']) && strpos($profile_url, (string) DI::baseUrl()) === 0) { + if (!empty($profile['nickname']) && strpos($profile_url, (string)DI::baseUrl()) === 0) { $follow_link = 'profile/' . $profile['nickname'] . '/remote_follow'; } } else { @@ -445,23 +444,12 @@ class Profile } $network_url = 'contact/' . $cid . '/conversations'; - $member_since = null; - if (isset($p['register_date']) && Feature::isEnabled($p['uid'], Feature::MEMBER_SINCE)) { - $member_since = [ DI::l10n()->t('Joined:'), DI::l10n()->mediumDate($p['register_date']) ]; - } - $tpl = Renderer::getMarkupTemplate('profile/vcard.tpl'); $o .= Renderer::replaceMacros($tpl, [ - '$is_owner' => DI::userSession()->getLocalUserId() == $profile['uid'], - '$profile' => $p, - '$edit_profile_link' => [ - 'url' => 'settings/profile', - 'text' => DI::l10n()->t('Edit profile'), - ], + '$profile' => $p, '$picture_dest_url' => $picture_dest_url, '$change_profile_picture_text' => $change_profile_picture_text, '$xmpp' => $xmpp, - '$member_since' => $member_since, '$matrix' => $matrix, '$follow' => DI::l10n()->t('Follow'), '$follow_link' => $follow_link, @@ -533,6 +521,8 @@ class Profile */ public static function getBirthdays(int $uid): string { + $bd_short = DI::l10n()->t('F d'); + $cacheKey = 'get_birthdays:' . $uid; $events = DI::cache()->get($cacheKey); if (is_null($events)) { @@ -552,7 +542,7 @@ class Profile Contact::FRIEND, $uid, DateTimeFormat::utc('now + 6 days'), - DateTimeFormat::utcNow(), + DateTimeFormat::utcNow() ); if (DBA::isResult($result)) { $events = DBA::toArray($result); @@ -595,7 +585,7 @@ class Profile 'id' => $event['id'], 'link' => Contact::magicLinkById($event['cid']), 'title' => $event['name'], - 'date' => DI::l10n()->longDate($event['start']) . (($today) ? ' ' . DI::l10n()->t('[today]') : ''), + 'date' => DI::l10n()->getDay(DateTimeFormat::local($event['start'], $bd_short)) . (($today) ? ' ' . DI::l10n()->t('[today]') : '') ]; } } @@ -608,7 +598,7 @@ class Profile '$event_title' => DI::l10n()->t('Birthdays this week:'), '$events' => $tpl_events, '$lbr' => '{', // raw brackets mess up if/endif macro processing - '$rbr' => '}', + '$rbr' => '}' ]); } @@ -621,11 +611,12 @@ class Profile */ public static function getEventsReminderHTML(int $uid, int $pcid): string { + $bd_format = DI::l10n()->t('g A l F d'); // 8 AM Friday January 18 $classtoday = ''; $condition = [ "`uid` = ? AND `type` != 'birthday' AND `start` < ? AND `start` >= ?", - $uid, DateTimeFormat::utc('now + 7 days'), DateTimeFormat::utc('now - 1 days'), + $uid, DateTimeFormat::utc('now + 7 days'), DateTimeFormat::utc('now - 1 days') ]; $s = DBA::select('event', [], $condition, ['order' => ['start']]); @@ -639,7 +630,7 @@ class Profile $condition = [ 'parent-uri' => $rr['uri'], 'uid' => $rr['uid'], 'author-id' => $pcid, 'vid' => [Verb::getID(Activity::ATTEND), Verb::getID(Activity::ATTENDMAYBE)], - 'visible' => true, 'deleted' => false, + 'visible' => true, 'deleted' => false ]; if (!Post::exists($condition)) { continue; @@ -675,7 +666,7 @@ class Profile $rr['title'] = $title; $rr['description'] = $description; - $rr['date'] = DI::l10n()->fullDateTime($rr['start']) . ($today ? ' ' . DI::l10n()->t('[today]') : ''); + $rr['date'] = DI::l10n()->getDay(DateTimeFormat::local($rr['start'], $bd_format)) . (($today) ? ' ' . DI::l10n()->t('[today]') : ''); $rr['startime'] = $strt; $rr['today'] = $today; @@ -739,7 +730,7 @@ class Profile (`pub_keywords` LIKE ?) OR (`prv_keywords` LIKE ?))", $searchTerm, $searchTerm, $searchTerm, $searchTerm, - $searchTerm, $searchTerm, $searchTerm, $searchTerm, + $searchTerm, $searchTerm, $searchTerm, $searchTerm ]; } else { $condition = ['verified' => true, 'blocked' => false, 'account_removed' => false, 'account_expired' => false]; @@ -781,7 +772,7 @@ class Profile if (!$profile['is-default']) { $contacts = Contact::selectToArray(['id'], [ 'uid' => $profile['uid'], - 'profile-id' => $profile['id'], + 'profile-id' => $profile['id'] ]); if (!count($contacts)) { // No contact visibility selected defaults to user-only permission @@ -792,8 +783,8 @@ class Profile $permissionSet = DI::permissionSet()->selectOrCreate( new PermissionSet( $profile['uid'], - array_column($contacts, 'id') ?? [], - ), + array_column($contacts, 'id') ?? [] + ) ); $order = 1; @@ -828,7 +819,7 @@ class Profile $order, trim($label, ':'), $profile[$field], - $permissionSet, + $permissionSet )); } @@ -860,7 +851,7 @@ class Profile } $parent = User::getOwnerDataById($parent_uid); - if (strpos($about, (string) $parent['addr']) || strpos($about, (string) $parent['url'])) { + if (strpos($about, $parent['addr']) || strpos($about, $parent['url'])) { return $about; } diff --git a/src/Model/User.php b/src/Model/User.php index 4e3d51d4ad..7923690ca6 100644 --- a/src/Model/User.php +++ b/src/Model/User.php @@ -54,13 +54,13 @@ class User * * @{ */ - public const PAGE_FLAGS_NORMAL = 0; - public const PAGE_FLAGS_SOAPBOX = 1; - public const PAGE_FLAGS_COMMUNITY = 2; - public const PAGE_FLAGS_FREELOVE = 3; - public const PAGE_FLAGS_BLOG = 4; - public const PAGE_FLAGS_PRVGROUP = 5; - public const PAGE_FLAGS_COMM_MAN = 6; + const PAGE_FLAGS_NORMAL = 0; + const PAGE_FLAGS_SOAPBOX = 1; + const PAGE_FLAGS_COMMUNITY = 2; + const PAGE_FLAGS_FREELOVE = 3; + const PAGE_FLAGS_BLOG = 4; + const PAGE_FLAGS_PRVGROUP = 5; + const PAGE_FLAGS_COMM_MAN = 6; /** * @} */ @@ -84,12 +84,12 @@ class User * This will only be assigned to contacts, not to user accounts * @{ */ - public const ACCOUNT_TYPE_PERSON = 0; - public const ACCOUNT_TYPE_ORGANISATION = 1; - public const ACCOUNT_TYPE_NEWS = 2; - public const ACCOUNT_TYPE_COMMUNITY = 3; - public const ACCOUNT_TYPE_RELAY = 4; - public const ACCOUNT_TYPE_DELETED = 127; + const ACCOUNT_TYPE_PERSON = 0; + const ACCOUNT_TYPE_ORGANISATION = 1; + const ACCOUNT_TYPE_NEWS = 2; + const ACCOUNT_TYPE_COMMUNITY = 3; + const ACCOUNT_TYPE_RELAY = 4; + const ACCOUNT_TYPE_DELETED = 127; /** * @} */ @@ -170,7 +170,7 @@ class User $system['region'] = ''; $system['postal-code'] = ''; $system['country-name'] = ''; - $system['homepage'] = (string) DI::baseUrl(); + $system['homepage'] = (string)DI::baseUrl(); $system['dob'] = '0000-00-00'; // Ensure that the user contains data @@ -503,7 +503,7 @@ class User // Check if the avatar field is filled and the photo directs to the correct path $avatar = Photo::selectFirst(['resource-id'], ['uid' => $uid, 'profile' => true]); if (DBA::isResult($avatar)) { - $repair = empty($owner['avatar']) || !strpos($owner['photo'], (string) $avatar['resource-id']); + $repair = empty($owner['avatar']) || !strpos($owner['photo'], $avatar['resource-id']); } } @@ -755,7 +755,7 @@ class User 'username' => $username, 'password' => $password, 'authenticated' => 0, - 'user_record' => null, + 'user_record' => null ]; $eventDispatcher = DI::eventDispatcher(); @@ -815,7 +815,7 @@ class User 'uid' => $user_info, 'account_expired' => false, 'account_removed' => false, - 'verified' => true, + 'verified' => true ]; if (!$with_blocked) { $condition = DBA::mergeConditions($condition, ['blocked' => false]); @@ -825,7 +825,7 @@ class User $condition = [ "(`email` = ? OR `username` = ? OR `nickname` = ?) AND `verified` AND NOT `account_removed` AND NOT `account_expired`", - $user_info, $user_info, $user_info, + $user_info, $user_info, $user_info ]; if (!$with_blocked) { $condition = DBA::mergeConditions($condition, ['blocked' => false]); @@ -905,7 +905,7 @@ class User 'code' => $e->getCode(), 'file' => $e->getFile(), 'line' => $e->getLine(), - 'trace' => $e->getTraceAsString(), + 'trace' => $e->getTraceAsString() ]); return false; @@ -1008,7 +1008,7 @@ class User 'password' => $password_hashed, 'pwdreset' => null, 'pwdreset_time' => null, - 'legacy_password' => false, + 'legacy_password' => false ]; return DBA::update('user', $fields, ['uid' => $uid]); } @@ -1025,7 +1025,7 @@ class User { return DBA::exists('user', [ 'uid' => $uid, - 'email' => self::getAdminEmailList(), + 'email' => self::getAdminEmailList() ]); } @@ -1337,7 +1337,7 @@ class User 'language' => $language, 'timezone' => 'UTC', 'register_date' => DateTimeFormat::utcNow(), - 'default-location' => '', + 'default-location' => '' ]); if ($insert_result) { @@ -1566,7 +1566,7 @@ class User $user, DI::config()->get('config', 'sitename'), DI::baseUrl(), - ($register['password'] ?? '') ?: 'Sent in a previous email', + ($register['password'] ?? '') ?: 'Sent in a previous email' ); } @@ -1597,8 +1597,8 @@ class User // Delete the avatar Photo::delete(['uid' => $register['uid']]); - return DBA::delete('user', ['uid' => $register['uid']]) - && Register::deleteByHash($register['hash']); + return DBA::delete('user', ['uid' => $register['uid']]) && + Register::deleteByHash($register['hash']); } /** @@ -1616,9 +1616,9 @@ class User */ public static function createMinimal(string $name, string $email, string $nick, string $lang = L10n::DEFAULT, string $avatar = ''): bool { - if (empty($name) - || empty($email) - || empty($nick)) { + if (empty($name) || + empty($email) || + empty($nick)) { throw new HTTPException\InternalServerErrorException('Invalid arguments.'); } @@ -1629,7 +1629,7 @@ class User 'nickname' => $nick, 'verified' => 1, 'language' => $lang, - 'photo' => $avatar, + 'photo' => $avatar ]); $user = $result['user']; @@ -1702,7 +1702,7 @@ class User $sitename, $siteurl, $user['nickname'], - $password, + $password )); $email = DI::emailer() @@ -1736,7 +1736,7 @@ class User Thank you for registering at %2$s. Your account has been created. ', $user['username'], - $sitename, + $sitename )); $body = Strings::deindent($l10n->t( ' @@ -1769,7 +1769,7 @@ class User $sitename, $siteurl, $user['username'], - $password, + $password )); $email = DI::emailer() @@ -1877,14 +1877,14 @@ class User $identities = [[ 'uid' => $user['uid'], 'username' => $user['username'], - 'nickname' => $user['nickname'], + 'nickname' => $user['nickname'] ]]; // Then add all the children $r = DBA::select( 'user', ['uid', 'username', 'nickname'], - ['parent-uid' => $user['uid'], 'verified' => true, 'blocked' => false, 'account_removed' => false, 'account_expired' => false], + ['parent-uid' => $user['uid'], 'verified' => true, 'blocked' => false, 'account_removed' => false, 'account_expired' => false] ); if (DBA::isResult($r)) { $identities = array_merge($identities, DBA::toArray($r)); @@ -1894,7 +1894,7 @@ class User $r = DBA::select( 'user', ['uid', 'username', 'nickname'], - ['uid' => $user['parent-uid'], 'verified' => true, 'blocked' => false, 'account_removed' => false, 'account_expired' => false], + ['uid' => $user['parent-uid'], 'verified' => true, 'blocked' => false, 'account_removed' => false, 'account_expired' => false] ); if (DBA::isResult($r)) { $identities = DBA::toArray($r); @@ -1904,7 +1904,7 @@ class User $r = DBA::select( 'user', ['uid', 'username', 'nickname'], - ['parent-uid' => $user['parent-uid'], 'verified' => true, 'blocked' => false, 'account_removed' => false, 'account_expired' => false], + ['parent-uid' => $user['parent-uid'], 'verified' => true, 'blocked' => false, 'account_removed' => false, 'account_expired' => false] ); if (DBA::isResult($r)) { $identities = array_merge($identities, DBA::toArray($r)); @@ -1916,7 +1916,7 @@ class User FROM `manage` INNER JOIN `user` ON `manage`.`mid` = `user`.`uid` WHERE NOT `user`.`account_removed` AND `manage`.`uid` = ?", - $user['uid'], + $user['uid'] ); if (DBA::isResult($r)) { $identities = array_merge($identities, DBA::toArray($r)); @@ -1978,7 +1978,7 @@ class User ['uid', 'last-activity', 'last-item'], ["`verified` AND `last-activity` > ? AND NOT `blocked` AND NOT `account_removed` AND NOT `account_expired`", - DBA::NULL_DATETIME], + DBA::NULL_DATETIME] ); if (!DBA::isResult($userStmt)) { return $statistics; diff --git a/src/Model/User/Cookie.php b/src/Model/User/Cookie.php index 33a78bb170..69270478f8 100644 --- a/src/Model/User/Cookie.php +++ b/src/Model/User/Cookie.php @@ -17,15 +17,15 @@ use Friendica\Core\Config\Capability\IManageConfigValues; class Cookie { /** @var int Default expire duration in days */ - public const DEFAULT_EXPIRE = 7; + const DEFAULT_EXPIRE = 7; /** @var string The name of the Friendica cookie */ - public const NAME = 'Friendica'; + const NAME = 'Friendica'; /** @var string The path of the Friendica cookie */ - public const PATH = '/'; + const PATH = '/'; /** @var string The domain name of the Friendica cookie */ - public const DOMAIN = ''; + const DOMAIN = ''; /** @var bool True, if the cookie should only be accessible through HTTP */ - public const HTTPONLY = true; + const HTTPONLY = true; /** @var string The remote address of this node */ private $remoteAddr; @@ -49,11 +49,8 @@ class Cookie $this->sslEnabled = $baseURL->getScheme() === 'https'; $this->sitePrivateKey = $config->get('system', 'site_prvkey'); - $authCookieDays = $config->get( - 'system', - 'auth_cookie_lifetime', - self::DEFAULT_EXPIRE, - ); + $authCookieDays = $config->get('system', 'auth_cookie_lifetime', + self::DEFAULT_EXPIRE); $this->lifetime = $authCookieDays * 24 * 60 * 60; $this->remoteAddr = $request->getRemoteAddress(); @@ -123,8 +120,8 @@ class Cookie */ public function reset(array $data): bool { - return $this->clear() - && $this->setMultiple($data); + return $this->clear() && + $this->setMultiple($data); } /** @@ -147,7 +144,7 @@ class Cookie return $this->setCookie( json_encode(['ip' => $this->remoteAddr] + $this->data), $this->lifetime + time(), - $this->sslEnabled, + $this->sslEnabled ); } @@ -163,12 +160,10 @@ class Cookie * @return bool If output exists prior to calling this function, * */ - protected function setCookie( - string $value = null, - int $expire = null, - bool $secure = null - ): bool { - return setcookie(self::NAME, $value, ['expires' => $expire, 'path' => self::PATH, 'domain' => self::DOMAIN, 'secure' => $secure, 'httponly' => self::HTTPONLY]); + protected function setCookie(string $value = null, int $expire = null, + bool $secure = null): bool + { + return setcookie(self::NAME, $value, $expire, self::PATH, self::DOMAIN, $secure, self::HTTPONLY); } /** @@ -185,7 +180,7 @@ class Cookie return hash_hmac( 'sha256', hash_hmac('sha256', $privateData, $privateKey), - $this->sitePrivateKey, + $this->sitePrivateKey ); } @@ -201,7 +196,7 @@ class Cookie { return hash_equals( $this->hashPrivateData($privateData, $privateKey), - $hash, + $hash ); } } diff --git a/src/Module/ActivityPub/Inbox.php b/src/Module/ActivityPub/Inbox.php index 001bd31157..f654b3853b 100644 --- a/src/Module/ActivityPub/Inbox.php +++ b/src/Module/ActivityPub/Inbox.php @@ -53,9 +53,6 @@ class Inbox extends BaseApi $inbox = ActivityPub\ClientToServer::getPublicInbox($uid, $page, $request['max_id'] ?? null); } - // Relaxed CORS header already authorized - header('Access-Control-Allow-Origin: *'); - $this->jsonExit($inbox, 'application/activity+json'); } diff --git a/src/Module/Admin/Logs/Settings.php b/src/Module/Admin/Logs/Settings.php index be0f8f01ae..a0dd009899 100644 --- a/src/Module/Admin/Logs/Settings.php +++ b/src/Module/Admin/Logs/Settings.php @@ -71,20 +71,20 @@ class Settings extends BaseAdmin $t = Renderer::getMarkupTemplate('admin/logs/settings.tpl'); return Renderer::replaceMacros($t, [ - '$title' => DI::l10n()->t('Administration'), - '$page' => DI::l10n()->t('Log settings'), - '$submit' => DI::l10n()->t('Save Settings'), - '$clear' => DI::l10n()->t('Clear'), + '$title' => DI::l10n()->t('Administration'), + '$page' => DI::l10n()->t('Logs'), + '$submit' => DI::l10n()->t('Save Settings'), + '$clear' => DI::l10n()->t('Clear'), '$logname' => DI::config()->get('system', 'logfile'), // see /help/smarty3-templates#1_1 on any Friendica node - '$debugging' => ['debugging', DI::l10n()->t('Enable Debugging'), DI::config()->get('system', 'debugging'), !DI::config()->isWritable('system', 'debugging') ? DI::l10n()->t('Read-only because it is set by an environment variable') : '', !DI::config()->isWritable('system', 'debugging') ? 'disabled' : ''], - '$logfile' => ['logfile', DI::l10n()->t('Log file'), DI::config()->get('system', 'logfile'), DI::l10n()->t('Must be writable by web server. Relative to your Friendica top-level directory.') . (!DI::config()->isWritable('system', 'logfile') ? '
    ' . DI::l10n()->t('Read-only because it is set by an environment variable') : ''), '', !DI::config()->isWritable('system', 'logfile') ? 'disabled' : ''], - '$loglevel' => ['loglevel', DI::l10n()->t("Log level"), DI::config()->get('system', 'loglevel'), !DI::config()->isWritable('system', 'loglevel') ? DI::l10n()->t('Read-only because it is set by an environment variable') : '', $log_choices, !DI::config()->isWritable('system', 'loglevel') ? 'disabled' : ''], + '$debugging' => ['debugging', DI::l10n()->t('Enable Debugging'), DI::config()->get('system', 'debugging'), !DI::config()->isWritable('system', 'debugging') ? DI::l10n()->t('Read-only because it is set by an environment variable') : '', !DI::config()->isWritable('system', 'debugging') ? 'disabled' : ''], + '$logfile' => ['logfile', DI::l10n()->t('Log file'), DI::config()->get('system', 'logfile'), DI::l10n()->t('Must be writable by web server. Relative to your Friendica top-level directory.') . (!DI::config()->isWritable('system', 'logfile') ? '
    ' . DI::l10n()->t('Read-only because it is set by an environment variable') : ''), '', !DI::config()->isWritable('system', 'logfile') ? 'disabled' : ''], + '$loglevel' => ['loglevel', DI::l10n()->t("Log level"), DI::config()->get('system', 'loglevel'), !DI::config()->isWritable('system', 'loglevel') ? DI::l10n()->t('Read-only because it is set by an environment variable') : '', $log_choices, !DI::config()->isWritable('system', 'loglevel') ? 'disabled' : ''], '$form_security_token' => self::getFormSecurityToken("admin_logs"), - '$phpheader' => DI::l10n()->t("PHP logging"), - '$phphint' => DI::l10n()->t("To temporarily enable logging of PHP errors and warnings you can prepend the following to the index.php file of your installation. The filename set in the 'error_log' line is relative to the friendica top-level directory and must be writeable by the web server. The option '1' for 'log_errors' and 'display_errors' is to enable these options, set to '0' to disable them."), - '$phplogcode' => "error_reporting(E_ERROR | E_WARNING | E_PARSE);\nini_set('error_log','php.out');\nini_set('log_errors','1');\nini_set('display_errors', '1');", - '$phplogenabled' => $phplogenabled, + '$phpheader' => DI::l10n()->t("PHP logging"), + '$phphint' => DI::l10n()->t("To temporarily enable logging of PHP errors and warnings you can prepend the following to the index.php file of your installation. The filename set in the 'error_log' line is relative to the friendica top-level directory and must be writeable by the web server. The option '1' for 'log_errors' and 'display_errors' is to enable these options, set to '0' to disable them."), + '$phplogcode' => "error_reporting(E_ERROR | E_WARNING | E_PARSE);\nini_set('error_log','php.out');\nini_set('log_errors','1');\nini_set('display_errors', '1');", + '$phplogenabled' => $phplogenabled, ]); } } diff --git a/src/Module/Admin/Logs/View.php b/src/Module/Admin/Logs/View.php index 2c1758dd43..87c7c9923e 100644 --- a/src/Module/Admin/Logs/View.php +++ b/src/Module/Admin/Logs/View.php @@ -45,7 +45,7 @@ class View extends BaseAdmin 'context' => ['', 'index', 'worker', 'daemon'], ]; $filters = [ - 'level' => $_GET['level'] ?? '', + 'level' => $_GET['level'] ?? '', 'context' => $_GET['context'] ?? '', ]; foreach ($filters as $k => $v) { @@ -68,10 +68,11 @@ class View extends BaseAdmin } } return Renderer::replaceMacros($t, [ - '$title' => DI::l10n()->t('Administration'), - '$page' => DI::l10n()->t('View Logs'), - '$l10n' => [ - 'Search' => DI::l10n()->t('Search in logs'), + '$title' => DI::l10n()->t('Administration'), + '$page' => DI::l10n()->t('View Logs'), + '$l10n' => [ + 'Search' => DI::l10n()->t('Search'), + 'Search_in_logs' => DI::l10n()->t('Search in logs'), 'Show_all' => DI::l10n()->t('Show all'), 'Date' => DI::l10n()->t('Date'), 'Level' => DI::l10n()->t('Level'), @@ -95,7 +96,7 @@ class View extends BaseAdmin '$filters' => $filters, '$filtersvalues' => $filters_valid_values, '$error' => $error, - '$logname' => DI::l10n()->t('Current log path: %s', DI::config()->get('system', 'logfile')), + '$logname' => DI::config()->get('system', 'logfile'), ]); } } diff --git a/src/Module/Admin/Queue.php b/src/Module/Admin/Queue.php index d62bccfbd5..50502362c8 100644 --- a/src/Module/Admin/Queue.php +++ b/src/Module/Admin/Queue.php @@ -34,11 +34,11 @@ class Queue extends BaseAdmin if ($status == 'deferred') { $condition = ["NOT `done` AND `retrial` > ?", 0]; $sub_title = DI::l10n()->t('Inspect Deferred Worker Queue'); - $info = DI::l10n()->t("This page lists the deferred worker jobs. This are jobs that couldn't be executed at the first time."); + $info = DI::l10n()->t("This page lists the deferred worker jobs. This are jobs that couldn't be executed at the first time."); } else { $condition = ["NOT `done` AND `retrial` = ?", 0]; $sub_title = DI::l10n()->t('Inspect Worker Queue'); - $info = DI::l10n()->t('This page lists the currently queued worker jobs. These jobs are handled by the worker cronjob you\'ve set up during install.'); + $info = DI::l10n()->t('This page lists the currently queued worker jobs. These jobs are handled by the worker cronjob you\'ve set up during install.'); } // @TODO Move to Model\WorkerQueue::getEntries() @@ -47,32 +47,27 @@ class Queue extends BaseAdmin $r = []; while ($entry = DBA::fetch($entries)) { // fix GH-5469. ref: src/Core/Worker.php:217 - $param = Arrays::recursiveImplode(json_decode($entry['parameter'], true), ': '); - // Truncate long parameters to prevent text overflow - if (strlen($param) > 300) { - $param = substr($param, 0, 300) . '...'; - } - $entry['parameter'] = $param; - $entry['created'] = DateTimeFormat::local($entry['created']); - $entry['next_try'] = DateTimeFormat::local($entry['next_try']); - $r[] = $entry; + $entry['parameter'] = Arrays::recursiveImplode(json_decode($entry['parameter'], true), ': '); + $entry['created'] = DateTimeFormat::local($entry['created']); + $entry['next_try'] = DateTimeFormat::local($entry['next_try']); + $r[] = $entry; } DBA::close($entries); $t = Renderer::getMarkupTemplate('admin/queue.tpl'); return Renderer::replaceMacros($t, [ - '$title' => DI::l10n()->t('Administration'), - '$page' => $sub_title, - '$count' => count($r), - '$id_header' => DI::l10n()->t('ID'), - '$command_header' => DI::l10n()->t('Command'), - '$param_header' => DI::l10n()->t('Job Parameters'), - '$created_header' => DI::l10n()->t('Created'), + '$title' => DI::l10n()->t('Administration'), + '$page' => $sub_title, + '$count' => count($r), + '$id_header' => DI::l10n()->t('ID'), + '$command_header' => DI::l10n()->t('Command'), + '$param_header' => DI::l10n()->t('Job Parameters'), + '$created_header' => DI::l10n()->t('Created'), '$next_try_header' => DI::l10n()->t('Next Try'), - '$prio_header' => DI::l10n()->t('Priority'), - '$info' => $info, - '$status' => $status, - '$entries' => $r, + '$prio_header' => DI::l10n()->t('Priority'), + '$info' => $info, + '$status' => $status, + '$entries' => $r, ]); } } diff --git a/src/Module/Admin/Storage.php b/src/Module/Admin/Storage.php index 758cd8d534..58ab37ed59 100644 --- a/src/Module/Admin/Storage.php +++ b/src/Module/Admin/Storage.php @@ -13,6 +13,7 @@ use Friendica\Core\Storage\Exception\InvalidClassStorageException; use Friendica\Core\Storage\Capability\ICanConfigureStorage; use Friendica\Core\Storage\Capability\ICanWriteToStorage; use Friendica\Module\BaseAdmin; +use Friendica\Util\Strings; class Storage extends BaseAdmin { @@ -61,7 +62,7 @@ class Storage extends BaseAdmin } } - if (!empty($_POST['submit_save_set']) && DI::config()->isWritable('storage', 'name')) { + if (!empty($_POST['submit_save_set']) && DI::config()->isWritable('storage', 'name') ) { try { $newstorage = DI::storageManager()->getWritableStorageByName($storagebackend); diff --git a/src/Module/Admin/Summary.php b/src/Module/Admin/Summary.php index 7a383a0ba6..2f6905103c 100644 --- a/src/Module/Admin/Summary.php +++ b/src/Module/Admin/Summary.php @@ -8,7 +8,6 @@ namespace Friendica\Module\Admin; use Friendica\App; -use Friendica\Core\Addon\Exception\InvalidAddonException; use Friendica\Core\Config\ValueObject\Cache; use Friendica\Core\Renderer; use Friendica\Core\Update; @@ -18,6 +17,7 @@ use Friendica\DI; use Friendica\Core\Config\Factory\Config; use Friendica\Module\BaseAdmin; use Friendica\Network\HTTPClient\Client\HttpClientAccept; +use Friendica\Network\Probe; use Friendica\Util\DateTimeFormat; class Summary extends BaseAdmin @@ -99,13 +99,13 @@ class Summary extends BaseAdmin } // Check server vitality - if (!self::checkSelfNodeinfo()) { - $well_known = DI::baseUrl() . '/.well-known/nodeinfo'; + if (!self::checkSelfHostMeta()) { + $well_known = DI::baseUrl() . Probe::HOST_META; $warningtext[] = DI::l10n()->t( '%s is not reachable on your system. This is a severe configuration issue that prevents server to server communication. See the installation page for help.', $well_known, $well_known, - DI::baseUrl() . '/help/admin/install', + DI::baseUrl() . '/help/admin/install' ); } @@ -132,7 +132,7 @@ class Summary extends BaseAdmin $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.', [ @@ -142,7 +142,7 @@ class Summary extends BaseAdmin $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.', [ @@ -152,7 +152,7 @@ class Summary extends BaseAdmin $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 ); } } @@ -171,56 +171,32 @@ class Summary extends BaseAdmin 'php.ini' => php_ini_loaded_file(), 'upload_max_filesize' => ini_get('upload_max_filesize'), 'post_max_size' => ini_get('post_max_size'), - 'memory_limit' => ini_get('memory_limit'), + 'memory_limit' => ini_get('memory_limit') ], 'mysql' => [ 'max_allowed_packet' => DBA::getVariable('max_allowed_packet'), - ], + ] ]; - $addons = []; - - $addonHelper = DI::addonHelper(); - foreach ($addonHelper->getEnabledAddons() as $addonId) { - try { - $addonInfo = $addonHelper->getAddonInfo($addonId); - } catch (InvalidAddonException $th) { - $this->logger->error('Invalid addon found: ' . $addonId, ['exception' => $th]); - continue; - } - - $info = [ - 'name' => $addonInfo->getName(), - 'description' => $addonInfo->getDescription(), - 'version' => $addonInfo->getVersion(), - ]; - - $addons[] = [ - $addonId, - $info, - ]; - } - $t = Renderer::getMarkupTemplate('admin/summary.tpl'); return Renderer::replaceMacros($t, [ - '$title' => DI::l10n()->t('Administration'), - '$page' => DI::l10n()->t('Summary'), - '$queues' => $queues, - '$version_label' => DI::l10n()->t('Version'), - '$platform' => App::PLATFORM, - '$codename' => App::CODENAME, - '$build' => DI::config()->get('system', 'build'), - '$addons' => [DI::l10n()->t('Active addons'), $addons], - '$serversettings' => $server_settings, - '$warningtext' => $warningtext, - '$link_enable_addons' => DI::l10n()->t('Enable new addons'), + '$title' => DI::l10n()->t('Administration'), + '$page' => DI::l10n()->t('Summary'), + '$queues' => $queues, + '$version_label' => DI::l10n()->t('Version'), + '$platform' => App::PLATFORM, + '$codename' => App::CODENAME, + '$build' => DI::config()->get('system', 'build'), + '$addons' => [DI::l10n()->t('Active addons'), DI::addonHelper()->getEnabledAddons()], + '$serversettings' => $server_settings, + '$warningtext' => $warningtext, ]); } - private static function checkSelfNodeinfo() + private static function checkSelfHostMeta() { - // Fetch the webfinger to check if this really is a vital server - return DI::httpClient()->get(DI::baseUrl() . '/.well-known/nodeinfo', HttpClientAccept::JSON)->isSuccess(); + // Fetch the host-meta to check if this really is a vital server + return DI::httpClient()->get(DI::baseUrl() . Probe::HOST_META, HttpClientAccept::XRD_XML)->isSuccess(); } } diff --git a/src/Module/Api/ApiResponse.php b/src/Module/Api/ApiResponse.php index 14f2031e58..0e139ccae3 100644 --- a/src/Module/Api/ApiResponse.php +++ b/src/Module/Api/ApiResponse.php @@ -40,12 +40,12 @@ class ApiResponse extends Response public function __construct(L10n $l10n, Arguments $args, LoggerInterface $logger, BaseURL $baseUrl, TwitterUser $twitterUser, array $server = [], string $jsonpCallback = '') { - $this->l10n = $l10n; - $this->args = $args; - $this->logger = $logger; - $this->baseUrl = $baseUrl; - $this->twitterUser = $twitterUser; - $this->server = $server; + $this->l10n = $l10n; + $this->args = $args; + $this->logger = $logger; + $this->baseUrl = $baseUrl; + $this->twitterUser = $twitterUser; + $this->server = $server; $this->jsonpCallback = $jsonpCallback; } @@ -67,7 +67,7 @@ class ApiResponse extends Response '' => 'http://api.twitter.com', 'statusnet' => 'http://status.net/schema/api/1/', 'friendica' => 'http://friendi.ca/schema/api/1/', - 'georss' => 'http://www.georss.org/georss', + 'georss' => 'http://www.georss.org/georss' ]; /// @todo Auto detection of needed namespaces @@ -77,7 +77,7 @@ class ApiResponse extends Response if (is_array($data2)) { $key = key($data2); - Arrays::walkRecursive($data2, [\Friendica\Module\Api\ApiResponse::class, 'reformatXML']); + Arrays::walkRecursive($data2, ['Friendica\Module\Api\ApiResponse', 'reformatXML']); if ($key == '0') { $data4 = []; @@ -114,7 +114,7 @@ class ApiResponse extends Response $user_info = $this->twitterUser->createFromContactId($cid)->toArray(); $arr['$user'] = $user_info; - $arr['$rss'] = [ + $arr['$rss'] = [ 'alternate' => $user_info['url'], 'self' => $this->baseUrl . '/' . $this->args->getQueryString(), 'base' => $this->baseUrl, @@ -143,7 +143,6 @@ class ApiResponse extends Response switch ($type) { case 'rss': $data = $this->addRSSValues($data, $cid); - // no break case 'atom': case 'xml': return $this->createXML($data, $root_element); @@ -192,7 +191,7 @@ class ApiResponse extends Response $error = [ 'error' => $message ?: $description, 'code' => $code . ' ' . $description, - 'request' => $this->args->getQueryString(), + 'request' => $this->args->getQueryString() ]; $this->setHeader(($this->server['SERVER_PROTOCOL'] ?? 'HTTP/1.1') . ' ' . $code . ' ' . $description); @@ -213,7 +212,7 @@ class ApiResponse extends Response */ public function addFormattedContent(string $root_element, array $data, string $format = null, int $cid = 0) { - $format ??= 'json'; + $format = $format ?? 'json'; $return = $this->formatData($root_element, $format, $data, $cid); @@ -270,15 +269,13 @@ class ApiResponse extends Response public function unsupported(string $method = 'all', array $request = []) { $path = $this->args->getQueryString(); - $this->logger->info( - 'Unimplemented API call', + $this->logger->info('Unimplemented API call', [ 'method' => $method, 'path' => $path, 'agent' => $this->server['HTTP_USER_AGENT'] ?? '', 'request' => $request, - ], - ); + ]); $error = $this->l10n->t('API endpoint %s %s is not implemented but might be in the future.', strtoupper($method), $path); $this->error(501, 'Not Implemented', $error); diff --git a/src/Module/Api/Friendica/Statuses/Dislike.php b/src/Module/Api/Friendica/Statuses/Dislike.php index 73a1d5ab04..400b085fc5 100644 --- a/src/Module/Api/Friendica/Statuses/Dislike.php +++ b/src/Module/Api/Friendica/Statuses/Dislike.php @@ -7,6 +7,7 @@ namespace Friendica\Module\Api\Friendica\Statuses; +use Friendica\Core\System; use Friendica\Database\DBA; use Friendica\DI; use Friendica\Model\Item; @@ -34,6 +35,6 @@ class Dislike extends BaseApi Item::performActivity($item['id'], 'dislike', $uid); - $this->jsonExit(DI::mstdnStatus()->createFromUriId($this->parameters['id'], $uid)->toArray()); + $this->jsonExit(DI::mstdnStatus()->createFromUriId($this->parameters['id'], $uid, self::appSupportsQuotes())->toArray()); } } diff --git a/src/Module/Api/Friendica/Statuses/Undislike.php b/src/Module/Api/Friendica/Statuses/Undislike.php index d00f066e58..bf332bee0e 100644 --- a/src/Module/Api/Friendica/Statuses/Undislike.php +++ b/src/Module/Api/Friendica/Statuses/Undislike.php @@ -7,6 +7,7 @@ namespace Friendica\Module\Api\Friendica\Statuses; +use Friendica\Core\System; use Friendica\Database\DBA; use Friendica\DI; use Friendica\Model\Item; @@ -34,6 +35,6 @@ class Undislike extends BaseApi Item::performActivity($item['id'], 'undislike', $uid); - $this->jsonExit(DI::mstdnStatus()->createFromUriId($this->parameters['id'], $uid)->toArray()); + $this->jsonExit(DI::mstdnStatus()->createFromUriId($this->parameters['id'], $uid, self::appSupportsQuotes())->toArray()); } } diff --git a/src/Module/Api/Mastodon/Accounts/Statuses.php b/src/Module/Api/Mastodon/Accounts/Statuses.php index bddd53bcd2..e16a9e521a 100644 --- a/src/Module/Api/Mastodon/Accounts/Statuses.php +++ b/src/Module/Api/Mastodon/Accounts/Statuses.php @@ -93,10 +93,12 @@ class Statuses extends BaseApi $items = Post::selectTimelineForUser($uid, ['uri-id'], $condition, $params); } + $display_quotes = self::appSupportsQuotes(); + $statuses = []; while ($item = Post::fetch($items)) { try { - $status = DI::mstdnStatus()->createFromUriId($item['uri-id'], $uid); + $status = DI::mstdnStatus()->createFromUriId($item['uri-id'], $uid, $display_quotes); $this->updateBoundaries($status, $item, $request['friendica_order']); $statuses[] = $status; } catch (\Throwable $th) { diff --git a/src/Module/Api/Mastodon/Apps/VerifyCredentials.php b/src/Module/Api/Mastodon/Apps/VerifyCredentials.php index 2c5aab2ea5..f3467278ee 100644 --- a/src/Module/Api/Mastodon/Apps/VerifyCredentials.php +++ b/src/Module/Api/Mastodon/Apps/VerifyCredentials.php @@ -11,13 +11,13 @@ use Friendica\DI; use Friendica\Module\BaseApi; /** - * @see https://docs.joinmastodon.org/methods/apps/#verify_credentials + * @see https://docs.joinmastodon.org/methods/apps/ */ class VerifyCredentials extends BaseApi { protected function rawContent(array $request = []) { - $this->checkAllowedScope(self::SCOPE_ANY); + $this->checkAllowedScope(self::SCOPE_READ); $application = self::getCurrentApplication(); if (empty($application['id'])) { diff --git a/src/Module/Api/Mastodon/Bookmarks.php b/src/Module/Api/Mastodon/Bookmarks.php index ab43a6102a..b0f0ca34a5 100644 --- a/src/Module/Api/Mastodon/Bookmarks.php +++ b/src/Module/Api/Mastodon/Bookmarks.php @@ -54,11 +54,13 @@ class Bookmarks extends BaseApi $items = Post::selectThreadForUser($uid, ['uri-id'], $condition, $params); + $display_quotes = self::appSupportsQuotes(); + $statuses = []; while ($item = Post::fetch($items)) { self::setBoundaries($item['uri-id']); try { - $statuses[] = DI::mstdnStatus()->createFromUriId($item['uri-id'], $uid); + $statuses[] = DI::mstdnStatus()->createFromUriId($item['uri-id'], $uid, $display_quotes); } catch (\Exception $exception) { $this->logger->info('Post not fetchable', ['uri-id' => $item['uri-id'], 'uid' => $uid, 'exception' => $exception]); } diff --git a/src/Module/Api/Mastodon/Directory.php b/src/Module/Api/Mastodon/Directory.php index 990da59bb0..b83341d00b 100644 --- a/src/Module/Api/Mastodon/Directory.php +++ b/src/Module/Api/Mastodon/Directory.php @@ -42,10 +42,8 @@ class Directory extends BaseApi $condition = ['uid' => 0, 'hidden' => false, 'network' => Protocol::FEDERATED]; } - $params = [ - 'limit' => [$request['offset'], $request['limit']], - 'order' => [($request['order'] == 'active') ? 'last-item' : 'created' => true] - ]; + $params = ['limit' => [$request['offset'], $request['limit']], + 'order' => [($request['order'] == 'active') ? 'last-item' : 'created' => true]]; $accounts = []; $contacts = DBA::select($table, ['id', 'uid'], $condition, $params); diff --git a/src/Module/Api/Mastodon/Favourited.php b/src/Module/Api/Mastodon/Favourited.php index f2070c5257..3d9ec883bc 100644 --- a/src/Module/Api/Mastodon/Favourited.php +++ b/src/Module/Api/Mastodon/Favourited.php @@ -56,11 +56,13 @@ class Favourited extends BaseApi $items = Post::selectForUser($uid, ['thr-parent-id'], $condition, $params); + $display_quotes = self::appSupportsQuotes(); + $statuses = []; while ($item = Post::fetch($items)) { self::setBoundaries($item['thr-parent-id']); try { - $statuses[] = DI::mstdnStatus()->createFromUriId($item['thr-parent-id'], $uid); + $statuses[] = DI::mstdnStatus()->createFromUriId($item['thr-parent-id'], $uid, $display_quotes); } catch (\Exception $exception) { $this->logger->info('Post not fetchable', ['uri-id' => $item['thr-parent-id'], 'uid' => $uid, 'exception' => $exception]); } diff --git a/src/Module/Api/Mastodon/Notifications.php b/src/Module/Api/Mastodon/Notifications.php index 2d7f56dd23..5e392be976 100644 --- a/src/Module/Api/Mastodon/Notifications.php +++ b/src/Module/Api/Mastodon/Notifications.php @@ -7,6 +7,7 @@ namespace Friendica\Module\Api\Mastodon; +use Friendica\Core\System; use Friendica\Database\DBA; use Friendica\DI; use Friendica\Model\Contact; @@ -33,7 +34,7 @@ class Notifications extends BaseApi $id = $this->parameters['id']; try { $notification = DI::notification()->selectOneForUser($uid, ['id' => $id]); - $this->jsonExit(DI::mstdnNotification()->createFromNotification($notification)); + $this->jsonExit(DI::mstdnNotification()->createFromNotification($notification, self::appSupportsQuotes())); } catch (\Exception $e) { $this->logAndJsonError(404, $this->errorFactory->RecordNotFound()); } @@ -131,7 +132,7 @@ class Notifications extends BaseApi foreach ($Notifications as $Notification) { try { - $mstdnNotifications[] = DI::mstdnNotification()->createFromNotification($Notification); + $mstdnNotifications[] = DI::mstdnNotification()->createFromNotification($Notification, self::appSupportsQuotes()); self::setBoundaries($Notification->id); } catch (\Exception $e) { // Skip this notification diff --git a/src/Module/Api/Mastodon/Search.php b/src/Module/Api/Mastodon/Search.php index d2b22a84c6..f46ea1e981 100644 --- a/src/Module/Api/Mastodon/Search.php +++ b/src/Module/Api/Mastodon/Search.php @@ -102,7 +102,7 @@ class Search extends BaseApi // If the user-specific search failed, we search and probe a public post $item_id = Item::fetchByLink($q, $uid) ?: Item::fetchByLink($q); if ($item_id && $item = Post::selectFirst(['uri-id'], ['id' => $item_id])) { - $result['statuses'] = [DI::mstdnStatus()->createFromUriId($item['uri-id'], $uid)]; + $result['statuses'] = [DI::mstdnStatus()->createFromUriId($item['uri-id'], $uid, self::appSupportsQuotes())]; $this->jsonExit($result); } } @@ -190,11 +190,13 @@ class Search extends BaseApi $items = DBA::select($table, ['uri-id'], $condition, $params); + $display_quotes = self::appSupportsQuotes(); + $statuses = []; while ($item = Post::fetch($items)) { self::setBoundaries($item['uri-id']); try { - $statuses[] = DI::mstdnStatus()->createFromUriId($item['uri-id'], $uid); + $statuses[] = DI::mstdnStatus()->createFromUriId($item['uri-id'], $uid, $display_quotes); } catch (\Exception $exception) { $this->logger->info('Post not fetchable', ['uri-id' => $item['uri-id'], 'uid' => $uid, 'exception' => $exception]); } diff --git a/src/Module/Api/Mastodon/Statuses.php b/src/Module/Api/Mastodon/Statuses.php index cf23446995..45e976e9fe 100644 --- a/src/Module/Api/Mastodon/Statuses.php +++ b/src/Module/Api/Mastodon/Statuses.php @@ -183,7 +183,7 @@ class Statuses extends BaseApi Item::updateDisplayCache($post['uri-id']); - $this->jsonExit(DI::mstdnStatus()->createFromUriId($post['uri-id'], $uid)); + $this->jsonExit(DI::mstdnStatus()->createFromUriId($post['uri-id'], $uid, self::appSupportsQuotes())); } protected function post(array $request = []) @@ -217,6 +217,7 @@ class Statuses extends BaseApi $item['body'] = $this->formatStatus($request['status'], $uid); $item['app'] = $this->getApp(); $item['sensitive'] = $request['sensitive']; + $item['visibility'] = $request['visibility']; switch ($request['visibility']) { case 'public': @@ -237,15 +238,11 @@ class Statuses extends BaseApi if ($request['in_reply_to_id']) { $parent_item = Post::selectFirst(Item::ITEM_FIELDLIST, ['uri-id' => $request['in_reply_to_id'], 'uid' => $uid, 'private' => Item::PRIVATE]); if (!empty($parent_item)) { - // When 'visibility' is set to 'private' we want the permissions be copied from the parent post in the "insert" function. - // But this must only happen when the parent post was private as well. - // In all other cases we want to keep the permissions we defined here. The copying is controlled via the 'visibility' field. - $item['visibility'] = $request['visibility']; - $item['allow_cid'] = $parent_item['allow_cid']; - $item['allow_gid'] = $parent_item['allow_gid']; - $item['deny_cid'] = $parent_item['deny_cid']; - $item['deny_gid'] = $parent_item['deny_gid']; - $item['private'] = $parent_item['private']; + $item['allow_cid'] = $parent_item['allow_cid']; + $item['allow_gid'] = $parent_item['allow_gid']; + $item['deny_cid'] = $parent_item['deny_cid']; + $item['deny_gid'] = $parent_item['deny_gid']; + $item['private'] = $parent_item['private']; break; } } @@ -348,7 +345,7 @@ class Statuses extends BaseApi if (!empty($id)) { $item = Post::selectFirst(['uri-id'], ['id' => $id]); if (!empty($item['uri-id'])) { - $this->jsonExit(DI::mstdnStatus()->createFromUriId($item['uri-id'], $uid)); + $this->jsonExit(DI::mstdnStatus()->createFromUriId($item['uri-id'], $uid, self::appSupportsQuotes())); } } @@ -397,10 +394,9 @@ class Statuses extends BaseApi if (Post::exists(['uri-id' => $this->parameters['id'], 'uid' => $uid, 'unseen' => true])) { Post::update(['unseen' => false], ['uri-id' => $this->parameters['id'], 'uid' => $uid, 'unseen' => true]); } - $this->item->setViewed($this->parameters['id'], $uid); } - $this->jsonExit(DI::mstdnStatus()->createFromUriId($this->parameters['id'], $uid, false)); + $this->jsonExit(DI::mstdnStatus()->createFromUriId($this->parameters['id'], $uid, self::appSupportsQuotes(), false)); } private function getApp(): string diff --git a/src/Module/Api/Mastodon/Statuses/Bookmark.php b/src/Module/Api/Mastodon/Statuses/Bookmark.php index 8f31dfe721..0e2a537fac 100644 --- a/src/Module/Api/Mastodon/Statuses/Bookmark.php +++ b/src/Module/Api/Mastodon/Statuses/Bookmark.php @@ -55,6 +55,6 @@ class Bookmark extends BaseApi // Issue tracking the behavior of createFromUriId: https://github.com/friendica/friendica/issues/13350 $isReblog = $item['uri-id'] != $this->parameters['id']; - $this->jsonExit(DI::mstdnStatus()->createFromUriId($this->parameters['id'], $uid, $isReblog)->toArray()); + $this->jsonExit(DI::mstdnStatus()->createFromUriId($this->parameters['id'], $uid, self::appSupportsQuotes(), $isReblog)->toArray()); } } diff --git a/src/Module/Api/Mastodon/Statuses/Context.php b/src/Module/Api/Mastodon/Statuses/Context.php index 57c3efe357..2d17eed232 100644 --- a/src/Module/Api/Mastodon/Statuses/Context.php +++ b/src/Module/Api/Mastodon/Statuses/Context.php @@ -7,6 +7,7 @@ namespace Friendica\Module\Api\Mastodon\Statuses; +use Friendica\Core\System; use Friendica\Database\DBA; use Friendica\DI; use Friendica\Model\Item; @@ -45,7 +46,7 @@ class Context extends BaseApi $parent = Post::selectOriginal(['uri-id', 'parent-uri-id'], ['uri-id' => $id]); if (DBA::isResult($parent)) { - $id = $parent['uri-id']; + $id = $parent['uri-id']; $params = ['order' => ['uri-id' => true]]; $condition = ['parent-uri-id' => $parent['parent-uri-id'], 'gravity' => [Item::GRAVITY_PARENT, Item::GRAVITY_COMMENT]]; @@ -58,7 +59,7 @@ class Context extends BaseApi } if (!empty($request['min_id'])) { - $condition = DBA::mergeConditions($condition, ["`uri-id` > ?", $request['min_id']]); + $condition = DBA::mergeConditions($condition, ["`uri-id` > ?", $request['min_id']]); $params['order'] = ['uri-id']; } @@ -111,9 +112,11 @@ class Context extends BaseApi asort($ancestors); + $display_quotes = self::appSupportsQuotes(); + foreach (array_slice($ancestors, 0, $request['limit']) as $ancestor) { try { - $statuses['ancestors'][] = DI::mstdnStatus()->createFromUriId($ancestor, $uid); + $statuses['ancestors'][] = DI::mstdnStatus()->createFromUriId($ancestor, $uid, $display_quotes); } catch (\Throwable $th) { $this->logger->info('Post not fetchable', ['uri-id' => $ancestor, 'uid' => $uid, 'error' => $th]); } @@ -125,7 +128,7 @@ class Context extends BaseApi foreach (array_slice($descendants, 0, $request['limit']) as $descendant) { try { - $statuses['descendants'][] = DI::mstdnStatus()->createFromUriId($descendant, $uid); + $statuses['descendants'][] = DI::mstdnStatus()->createFromUriId($descendant, $uid, $display_quotes); } catch (\Throwable $th) { $this->logger->info('Post not fetchable', ['uri-id' => $descendant, 'uid' => $uid, 'error' => $th]); } diff --git a/src/Module/Api/Mastodon/Statuses/Favourite.php b/src/Module/Api/Mastodon/Statuses/Favourite.php index 04f3a9921b..1cfe5ffd33 100644 --- a/src/Module/Api/Mastodon/Statuses/Favourite.php +++ b/src/Module/Api/Mastodon/Statuses/Favourite.php @@ -7,6 +7,7 @@ namespace Friendica\Module\Api\Mastodon\Statuses; +use Friendica\Core\System; use Friendica\Database\DBA; use Friendica\DI; use Friendica\Model\Item; @@ -39,6 +40,6 @@ class Favourite extends BaseApi // Issue tracking the behavior of createFromUriId: https://github.com/friendica/friendica/issues/13350 $isReblog = $item['uri-id'] != $this->parameters['id']; - $this->jsonExit(DI::mstdnStatus()->createFromUriId($this->parameters['id'], $uid, $isReblog)->toArray()); + $this->jsonExit(DI::mstdnStatus()->createFromUriId($this->parameters['id'], $uid, self::appSupportsQuotes(), $isReblog)->toArray()); } } diff --git a/src/Module/Api/Mastodon/Statuses/Mute.php b/src/Module/Api/Mastodon/Statuses/Mute.php index 81be3bbd84..d48363767c 100644 --- a/src/Module/Api/Mastodon/Statuses/Mute.php +++ b/src/Module/Api/Mastodon/Statuses/Mute.php @@ -7,6 +7,7 @@ namespace Friendica\Module\Api\Mastodon\Statuses; +use Friendica\Core\System; use Friendica\Database\DBA; use Friendica\DI; use Friendica\Model\Item; @@ -43,6 +44,6 @@ class Mute extends BaseApi // Issue tracking the behavior of createFromUriId: https://github.com/friendica/friendica/issues/13350 $isReblog = $item['uri-id'] != $this->parameters['id']; - $this->jsonExit(DI::mstdnStatus()->createFromUriId($this->parameters['id'], $uid, $isReblog)->toArray()); + $this->jsonExit(DI::mstdnStatus()->createFromUriId($this->parameters['id'], $uid, self::appSupportsQuotes(), $isReblog)->toArray()); } } diff --git a/src/Module/Api/Mastodon/Statuses/Pin.php b/src/Module/Api/Mastodon/Statuses/Pin.php index e2cb7d5b75..16a053b914 100644 --- a/src/Module/Api/Mastodon/Statuses/Pin.php +++ b/src/Module/Api/Mastodon/Statuses/Pin.php @@ -7,6 +7,7 @@ namespace Friendica\Module\Api\Mastodon\Statuses; +use Friendica\Core\System; use Friendica\Database\DBA; use Friendica\DI; use Friendica\Model\Post; @@ -38,6 +39,6 @@ class Pin extends BaseApi // Issue tracking the behavior of createFromUriId: https://github.com/friendica/friendica/issues/13350 $isReblog = $item['uri-id'] != $this->parameters['id']; - $this->jsonExit(DI::mstdnStatus()->createFromUriId($this->parameters['id'], $uid, $isReblog)->toArray()); + $this->jsonExit(DI::mstdnStatus()->createFromUriId($this->parameters['id'], $uid, self::appSupportsQuotes(),$isReblog)->toArray()); } } diff --git a/src/Module/Api/Mastodon/Statuses/Reblog.php b/src/Module/Api/Mastodon/Statuses/Reblog.php index 5f85385f53..0641143b81 100644 --- a/src/Module/Api/Mastodon/Statuses/Reblog.php +++ b/src/Module/Api/Mastodon/Statuses/Reblog.php @@ -9,6 +9,7 @@ namespace Friendica\Module\Api\Mastodon\Statuses; use Friendica\Content\ContactSelector; use Friendica\Core\Protocol; +use Friendica\Core\System; use Friendica\Database\DBA; use Friendica\DI; use Friendica\Model\Item; @@ -51,6 +52,6 @@ class Reblog extends BaseApi // Issue tracking the behavior of createFromUriId: https://github.com/friendica/friendica/issues/13350 $isReblog = $item['uri-id'] != $this->parameters['id']; - $this->jsonExit(DI::mstdnStatus()->createFromUriId($this->parameters['id'], $uid, $isReblog)->toArray()); + $this->jsonExit(DI::mstdnStatus()->createFromUriId($this->parameters['id'], $uid, self::appSupportsQuotes(), $isReblog)->toArray()); } } diff --git a/src/Module/Api/Mastodon/Statuses/Unbookmark.php b/src/Module/Api/Mastodon/Statuses/Unbookmark.php index 1377e7c9fa..d1447ecd12 100644 --- a/src/Module/Api/Mastodon/Statuses/Unbookmark.php +++ b/src/Module/Api/Mastodon/Statuses/Unbookmark.php @@ -7,6 +7,7 @@ namespace Friendica\Module\Api\Mastodon\Statuses; +use Friendica\Core\System; use Friendica\Database\DBA; use Friendica\DI; use Friendica\Model\Item; @@ -55,6 +56,6 @@ class Unbookmark extends BaseApi // Issue tracking the behavior of createFromUriId: https://github.com/friendica/friendica/issues/13350 $isReblog = $item['uri-id'] != $this->parameters['id']; - $this->jsonExit(DI::mstdnStatus()->createFromUriId($this->parameters['id'], $uid, $isReblog)->toArray()); + $this->jsonExit(DI::mstdnStatus()->createFromUriId($this->parameters['id'], $uid, self::appSupportsQuotes(), $isReblog)->toArray()); } } diff --git a/src/Module/Api/Mastodon/Statuses/Unfavourite.php b/src/Module/Api/Mastodon/Statuses/Unfavourite.php index 73fc270c6d..e59739a2a1 100644 --- a/src/Module/Api/Mastodon/Statuses/Unfavourite.php +++ b/src/Module/Api/Mastodon/Statuses/Unfavourite.php @@ -7,6 +7,7 @@ namespace Friendica\Module\Api\Mastodon\Statuses; +use Friendica\Core\System; use Friendica\Database\DBA; use Friendica\DI; use Friendica\Model\Item; @@ -39,6 +40,6 @@ class Unfavourite extends BaseApi // Issue tracking the behavior of createFromUriId: https://github.com/friendica/friendica/issues/13350 $isReblog = $item['uri-id'] != $this->parameters['id']; - $this->jsonExit(DI::mstdnStatus()->createFromUriId($this->parameters['id'], $uid, $isReblog)->toArray()); + $this->jsonExit(DI::mstdnStatus()->createFromUriId($this->parameters['id'], $uid, self::appSupportsQuotes(), $isReblog)->toArray()); } } diff --git a/src/Module/Api/Mastodon/Statuses/Unmute.php b/src/Module/Api/Mastodon/Statuses/Unmute.php index 67495fbc85..9e540f0e6b 100644 --- a/src/Module/Api/Mastodon/Statuses/Unmute.php +++ b/src/Module/Api/Mastodon/Statuses/Unmute.php @@ -7,6 +7,7 @@ namespace Friendica\Module\Api\Mastodon\Statuses; +use Friendica\Core\System; use Friendica\Database\DBA; use Friendica\DI; use Friendica\Model\Item; @@ -43,6 +44,6 @@ class Unmute extends BaseApi // Issue tracking the behavior of createFromUriId: https://github.com/friendica/friendica/issues/13350 $isReblog = $item['uri-id'] != $this->parameters['id']; - $this->jsonExit(DI::mstdnStatus()->createFromUriId($this->parameters['id'], $uid, $isReblog)->toArray()); + $this->jsonExit(DI::mstdnStatus()->createFromUriId($this->parameters['id'], $uid, self::appSupportsQuotes(), $isReblog)->toArray()); } } diff --git a/src/Module/Api/Mastodon/Statuses/Unpin.php b/src/Module/Api/Mastodon/Statuses/Unpin.php index 45b589bc64..de74fe8246 100644 --- a/src/Module/Api/Mastodon/Statuses/Unpin.php +++ b/src/Module/Api/Mastodon/Statuses/Unpin.php @@ -7,6 +7,7 @@ namespace Friendica\Module\Api\Mastodon\Statuses; +use Friendica\Core\System; use Friendica\Database\DBA; use Friendica\DI; use Friendica\Model\Post; @@ -38,6 +39,6 @@ class Unpin extends BaseApi // Issue tracking the behavior of createFromUriId: https://github.com/friendica/friendica/issues/13350 $isReblog = $item['uri-id'] != $this->parameters['id']; - $this->jsonExit(DI::mstdnStatus()->createFromUriId($this->parameters['id'], $uid, $isReblog)->toArray()); + $this->jsonExit(DI::mstdnStatus()->createFromUriId($this->parameters['id'], $uid, self::appSupportsQuotes(), $isReblog)->toArray()); } } diff --git a/src/Module/Api/Mastodon/Statuses/Unreblog.php b/src/Module/Api/Mastodon/Statuses/Unreblog.php index 95758c3be3..3650892fb7 100644 --- a/src/Module/Api/Mastodon/Statuses/Unreblog.php +++ b/src/Module/Api/Mastodon/Statuses/Unreblog.php @@ -9,6 +9,7 @@ namespace Friendica\Module\Api\Mastodon\Statuses; use Friendica\Content\ContactSelector; use Friendica\Core\Protocol; +use Friendica\Core\System; use Friendica\Database\DBA; use Friendica\DI; use Friendica\Model\Item; @@ -57,6 +58,6 @@ class Unreblog extends BaseApi // Issue tracking the behavior of createFromUriId: https://github.com/friendica/friendica/issues/13350 $isReblog = $item['uri-id'] != $this->parameters['id']; - $this->jsonExit(DI::mstdnStatus()->createFromUriId($this->parameters['id'], $uid, $isReblog)->toArray()); + $this->jsonExit(DI::mstdnStatus()->createFromUriId($this->parameters['id'], $uid, self::appSupportsQuotes(), $isReblog)->toArray()); } } diff --git a/src/Module/Api/Mastodon/Timelines/Home.php b/src/Module/Api/Mastodon/Timelines/Home.php index 2131061be1..fc1eaa6d01 100644 --- a/src/Module/Api/Mastodon/Timelines/Home.php +++ b/src/Module/Api/Mastodon/Timelines/Home.php @@ -69,10 +69,12 @@ class Home extends BaseApi $items = Post::selectTimelineForUser($uid, ['uri-id'], $condition, $params); + $display_quotes = self::appSupportsQuotes(); + $statuses = []; while ($item = Post::fetch($items)) { try { - $status = DI::mstdnStatus()->createFromUriId($item['uri-id'], $uid); + $status = DI::mstdnStatus()->createFromUriId($item['uri-id'], $uid, $display_quotes); $this->updateBoundaries($status, $item, $request['friendica_order']); $statuses[] = $status; } catch (\Throwable $th) { diff --git a/src/Module/Api/Mastodon/Timelines/ListTimeline.php b/src/Module/Api/Mastodon/Timelines/ListTimeline.php index 311a123234..093ed6ca35 100644 --- a/src/Module/Api/Mastodon/Timelines/ListTimeline.php +++ b/src/Module/Api/Mastodon/Timelines/ListTimeline.php @@ -66,6 +66,8 @@ class ListTimeline extends BaseApi 'friendica_order' => TimelineOrderByTypes::ID, // Sort order options (defaults to ID) ], $request); + $display_quotes = self::appSupportsQuotes(); + if (substr($this->parameters['id'], 0, 6) == 'group:') { $items = $this->getStatusesForGroup($uid, $request); } elseif (substr($this->parameters['id'], 0, 8) == 'channel:') { @@ -77,7 +79,7 @@ class ListTimeline extends BaseApi $statuses = []; foreach ($items as $item) { try { - $status = DI::mstdnStatus()->createFromUriId($item['uri-id'], $uid); + $status = DI::mstdnStatus()->createFromUriId($item['uri-id'], $uid, $display_quotes); $this->updateBoundaries($status, $item, $request['friendica_order']); $statuses[] = $status; } catch (\Throwable $th) { diff --git a/src/Module/Api/Mastodon/Timelines/PublicTimeline.php b/src/Module/Api/Mastodon/Timelines/PublicTimeline.php index 0465a7b469..acc4aac52c 100644 --- a/src/Module/Api/Mastodon/Timelines/PublicTimeline.php +++ b/src/Module/Api/Mastodon/Timelines/PublicTimeline.php @@ -103,10 +103,12 @@ class PublicTimeline extends BaseApi $items = Post::selectTimelineForUser($uid, ['uri-id'], $condition, $params); } + $display_quotes = self::appSupportsQuotes(); + $statuses = []; while ($item = Post::fetch($items)) { try { - $status = DI::mstdnStatus()->createFromUriId($item['uri-id'], $uid); + $status = DI::mstdnStatus()->createFromUriId($item['uri-id'], $uid, $display_quotes); $this->updateBoundaries($status, $item, $request['friendica_order']); $statuses[] = $status; } catch (\Throwable $th) { diff --git a/src/Module/Api/Mastodon/Timelines/Tag.php b/src/Module/Api/Mastodon/Timelines/Tag.php index 58ffa34e72..5eb26cad6a 100644 --- a/src/Module/Api/Mastodon/Timelines/Tag.php +++ b/src/Module/Api/Mastodon/Timelines/Tag.php @@ -97,11 +97,13 @@ class Tag extends BaseApi $items = DBA::select('tag-search-view', ['uri-id'], $condition, $params); + $display_quotes = self::appSupportsQuotes(); + $statuses = []; while ($item = Post::fetch($items)) { self::setBoundaries($item['uri-id']); try { - $statuses[] = DI::mstdnStatus()->createFromUriId($item['uri-id'], $uid); + $statuses[] = DI::mstdnStatus()->createFromUriId($item['uri-id'], $uid, $display_quotes); } catch (\Exception $exception) { $this->logger->info('Post not fetchable', ['uri-id' => $item['uri-id'], 'uid' => $uid, 'exception' => $exception]); } diff --git a/src/Module/Api/Mastodon/Trends/Links.php b/src/Module/Api/Mastodon/Trends/Links.php index 5f736c6578..bd288bda69 100644 --- a/src/Module/Api/Mastodon/Trends/Links.php +++ b/src/Module/Api/Mastodon/Trends/Links.php @@ -8,6 +8,7 @@ namespace Friendica\Module\Api\Mastodon\Trends; use Friendica\Core\Protocol; +use Friendica\Core\System; use Friendica\Database\DBA; use Friendica\DI; use Friendica\Model\Post; @@ -25,7 +26,7 @@ class Links extends BaseApi protected function rawContent(array $request = []) { $request = $this->getRequest([ - 'limit' => 10, // Maximum number of results to return. Defaults to 10. + 'limit' => 10, // Maximum number of results to return. Defaults to 10. 'offset' => 0, // Offset in set, Defaults to 0. ], $request); @@ -36,11 +37,8 @@ class Links extends BaseApi $trending = []; $statuses = Post::selectPostThread(['uri-id', 'total-comments', 'total-actors'], $condition, ['limit' => [$request['offset'], $request['limit']], 'offset' => $request['offset'], 'order' => ['total-actors' => true]]); while ($status = Post::fetch($statuses)) { - $history = [['day' => (string)time(), 'uses' => (string)$status['total-comments'], 'accounts' => (string)$status['total-actors']]]; - $link = DI::mstdnCard()->createFromUriId($status['uri-id'], $history)->toArray(); - if ($link) { - $trending[] = $link; - } + $history = [['day' => (string)time(), 'uses' => (string)$status['total-comments'], 'accounts' => (string)$status['total-actors']]]; + $trending[] = DI::mstdnCard()->createFromUriId($status['uri-id'], $history)->toArray(); } DBA::close($statuses); diff --git a/src/Module/Api/Mastodon/Trends/Statuses.php b/src/Module/Api/Mastodon/Trends/Statuses.php index d81802fe68..a64b83e130 100644 --- a/src/Module/Api/Mastodon/Trends/Statuses.php +++ b/src/Module/Api/Mastodon/Trends/Statuses.php @@ -58,11 +58,13 @@ class Statuses extends BaseApi $condition = ["NOT `private` AND `commented` > ? AND `created` > ?", DateTimeFormat::utc('now -1 day'), DateTimeFormat::utc('now -1 week')]; $condition = DBA::mergeConditions($condition, ['network' => Protocol::FEDERATED]); + $display_quotes = self::appSupportsQuotes(); + $trending = []; $statuses = Post::selectPostThread(['uri-id'], $condition, ['limit' => [$request['offset'], $request['limit']], 'order' => ['total-actors' => true]]); while ($status = Post::fetch($statuses)) { try { - $trending[] = DI::mstdnStatus()->createFromUriId($status['uri-id'], $uid); + $trending[] = DI::mstdnStatus()->createFromUriId($status['uri-id'], $uid, $display_quotes); } catch (\Exception $exception) { $this->logger->info('Post not fetchable', ['uri-id' => $status['uri-id'], 'uid' => $uid, 'exception' => $exception]); } diff --git a/src/Module/BaseAdmin.php b/src/Module/BaseAdmin.php index 10060671ae..ea03efd2a9 100644 --- a/src/Module/BaseAdmin.php +++ b/src/Module/BaseAdmin.php @@ -85,8 +85,8 @@ abstract class BaseAdmin extends BaseModule 'workerqueue' => ['admin/queue' , DI::l10n()->t('Inspect worker Queue') , 'workerqueue'], ]], 'logs' => [DI::l10n()->t('Logs'), [ - 'logsconfig' => ['admin/logs/', DI::l10n()->t('Settings') , 'logs'], - 'logsview' => ['admin/logs/view' , DI::l10n()->t('View') , 'viewlogs'], + 'logsconfig' => ['admin/logs/', DI::l10n()->t('Logs') , 'logs'], + 'logsview' => ['admin/logs/view' , DI::l10n()->t('View Logs') , 'viewlogs'], ]], 'diagnostics' => [DI::l10n()->t('Diagnostics'), [ 'phpinfo' => ['admin/phpinfo?t=' . self::getFormSecurityToken('phpinfo'), DI::l10n()->t('PHP Info') , 'phpinfo'], @@ -112,7 +112,7 @@ abstract class BaseAdmin extends BaseModule '$admin' => ['addons_admin' => $addons_admin], '$subpages' => $aside_sub, '$admtxt' => DI::l10n()->t('Admin'), - '$plugadmtxt' => DI::l10n()->t('Addon settings'), + '$plugadmtxt' => DI::l10n()->t('Addon Features'), '$h_pending' => DI::l10n()->t('User registrations waiting for confirmation'), '$admurl' => 'admin/' ]); diff --git a/src/Module/BaseApi.php b/src/Module/BaseApi.php index 139ce13795..e1566eec76 100644 --- a/src/Module/BaseApi.php +++ b/src/Module/BaseApi.php @@ -35,13 +35,12 @@ use Psr\Log\LoggerInterface; class BaseApi extends BaseModule { - public const LOG_PREFIX = 'API {action} - '; + const LOG_PREFIX = 'API {action} - '; - public const SCOPE_READ = 'read'; - public const SCOPE_WRITE = 'write'; - public const SCOPE_FOLLOW = 'follow'; - public const SCOPE_PUSH = 'push'; - public const SCOPE_ANY = 'any'; + const SCOPE_READ = 'read'; + const SCOPE_WRITE = 'write'; + const SCOPE_FOLLOW = 'follow'; + const SCOPE_PUSH = 'push'; /** * @var array @@ -357,6 +356,17 @@ class BaseApi extends BaseModule } } + /** + * Check if the app is known to support quoted posts + * + * @return bool + */ + public static function appSupportsQuotes(): bool + { + // @todo Clean up the whole functionality since it isn't of any use anymore. + return true; + } + /** * Get current application token * @@ -386,14 +396,14 @@ class BaseApi extends BaseModule $uid = BasicAuth::getCurrentUserID(false); } - return (int) $uid; + return (int)$uid; } /** * Check if the provided scope does exist. * halts execution on missing scope or when not logged in. * - * @param string $scope the requested scope (read, write, follow, push, any) + * @param string $scope the requested scope (read, write, follow, push) */ public function checkAllowedScope(string $scope) { @@ -404,10 +414,6 @@ class BaseApi extends BaseModule $this->logAndJsonError(403, $this->errorFactory->Forbidden()); } - if ($scope === self::SCOPE_ANY) { - return; - } - if (!isset($token[$scope])) { $this->logger->warning('The requested scope does not exist', ['scope' => $scope, 'application' => $token]); $this->logAndJsonError(403, $this->errorFactory->Forbidden()); diff --git a/src/Module/BaseNotifications.php b/src/Module/BaseNotifications.php index 0366ed0bb3..0445075995 100644 --- a/src/Module/BaseNotifications.php +++ b/src/Module/BaseNotifications.php @@ -155,7 +155,7 @@ abstract class BaseNotifications extends BaseModule foreach (self::URL_TYPES as $type => $url) { $tabs[] = [ 'label' => $this->t(self::PRINT_TYPES[$type]), - 'url' => 'notifications/' . $url . (($url == "personal") ? "?show=all" : ""), + 'url' => 'notifications/' . $url, 'sel' => (($selected == $url) ? 'active' : ''), 'id' => $type . '-tab', 'accesskey' => self::ACCESS_KEYS[$type], diff --git a/src/Module/BaseProfile.php b/src/Module/BaseProfile.php index 740cf592da..ce5d276861 100644 --- a/src/Module/BaseProfile.php +++ b/src/Module/BaseProfile.php @@ -76,7 +76,7 @@ class BaseProfile extends BaseModule ]; } else { $owner = User::getByNickname($nickname, ['uid']); - if (DI::userSession()->isAuthenticated() || $owner && Feature::isEnabled($owner['uid'], Feature::PUBLIC_CALENDAR)) { + if(DI::userSession()->isAuthenticated() || $owner && Feature::isEnabled($owner['uid'], Feature::PUBLIC_CALENDAR)) { $tabs[] = [ 'label' => DI::l10n()->t('Calendar'), 'url' => DI::baseUrl() . '/calendar/show/' . $nickname, @@ -90,7 +90,7 @@ class BaseProfile extends BaseModule if ($is_owner) { $tabs[] = [ - 'label' => DI::l10n()->t('Personal notes'), + 'label' => DI::l10n()->t('Personal Notes'), 'url' => DI::baseUrl() . '/notes', 'sel' => $current == 'notes' ? 'active' : '', 'title' => DI::l10n()->t('Only You Can See This'), diff --git a/src/Module/Calendar/Event/Form.php b/src/Module/Calendar/Event/Form.php index d525fac7f0..51f6c83bc9 100644 --- a/src/Module/Calendar/Event/Form.php +++ b/src/Module/Calendar/Event/Form.php @@ -108,10 +108,8 @@ class Form extends BaseModule $share_disabled = ''; if (empty($orig_event)) { - $orig_event = User::getById( - $this->session->getLocalUserId(), - ['allow_cid', 'allow_gid', 'deny_cid', 'deny_gid'] - ); + $orig_event = User::getById($this->session->getLocalUserId(), + ['allow_cid', 'allow_gid', 'deny_cid', 'deny_gid']); } elseif ($orig_event['allow_cid'] !== '<' . $this->session->getLocalUserId() . '>' || $orig_event['allow_gid'] || $orig_event['deny_cid'] @@ -208,14 +206,14 @@ class Form extends BaseModule true ), - '$n_text' => $this->t('End date/time is unknown or irrelevant'), + '$n_text' => $this->t('Finish date/time is not known or not relevant'), '$n_checked' => $n_checked, - '$f_text' => $this->t('Event ends:'), + '$f_text' => $this->t('Event Finishes:'), '$f_dsel' => Temporal::getDateTimeField( new \DateTime(), \DateTime::createFromFormat('Y', intval($fyear) + 5), \DateTime::createFromFormat('Y-m-d H:i', "$fyear-$fmonth-$fday $fhour:$fminute"), - $this->t('Event ends:'), + $this->t('Event Finishes:'), 'finish_text', true, true, @@ -232,7 +230,7 @@ class Form extends BaseModule '$sh_text' => $this->t('Share this event'), '$share' => ['share', $this->t('Share this event'), $share_checked, '', $share_disabled], '$sh_checked' => $share_checked, - '$nofinish' => ['nofinish', $this->t('End date/time is unknown or irrelevant'), $n_checked], + '$nofinish' => ['nofinish', $this->t('Finish date/time is not known or not relevant'), $n_checked], '$preview' => $this->t('Preview'), '$acl' => $acl, '$submit' => $this->t('Submit'), diff --git a/src/Module/Calendar/Show.php b/src/Module/Calendar/Show.php index 5d46df6298..7615426673 100644 --- a/src/Module/Calendar/Show.php +++ b/src/Module/Calendar/Show.php @@ -90,11 +90,11 @@ class Show extends BaseModule '$i18n' => $i18n, ]); + Nav::setSelected($is_owner ? 'home' : 'calendar'); + if ($is_owner) { // Removing the vCard added by Profile::load for owners $this->page['aside'] = ''; - // Only highlight the calendar link if you're on your own calendar - Nav::setSelected('calendar'); } $this->page['aside'] .= Widget\CalendarExport::getHTML($owner['uid']); @@ -110,7 +110,7 @@ class Show extends BaseModule $tpl = Renderer::getMarkupTemplate("calendar/calendar.tpl"); $o = Renderer::replaceMacros($tpl, [ '$tabs' => $tabs, - '$title' => $this->t('Calendar'), + '$title' => $this->t('Events'), '$view' => $this->t('View'), '$new_event' => ['calendar/event/new', $this->t('New Event'), '', ''], diff --git a/src/Module/Circle.php b/src/Module/Circle.php index 4ac94b5524..c4138ac4ab 100644 --- a/src/Module/Circle.php +++ b/src/Module/Circle.php @@ -33,7 +33,7 @@ class Circle extends BaseModule BaseModule::checkFormSecurityTokenRedirectOnError('/circle/new', 'circle_edit'); $name = trim($request['circle_name']); - $r = Model\Circle::create(DI::userSession()->getLocalUserId(), $name); + $r = Model\Circle::create(DI::userSession()->getLocalUserId(), $name); if ($r) { $r = Model\Circle::getIdByName(DI::userSession()->getLocalUserId(), $name); if ($r) { @@ -73,7 +73,7 @@ class Circle extends BaseModule $message = ''; if (isset($this->parameters['command'])) { - $circle_id = $this->parameters['circle']; + $circle_id = $this->parameters['circle']; $contact_id = $this->parameters['contact']; if (!Model\Circle::exists($circle_id, DI::userSession()->getLocalUserId())) { @@ -130,8 +130,8 @@ class Circle extends BaseModule protected function content(array $request = []): string { DI::page()['title'] = DI::l10n()->t("Circles"); - $change = false; - $relation = $request['rel'] ?? ''; + $change = false; + $relation = $request['rel'] ?? ''; if (!DI::userSession()->getLocalUserId()) { throw new \Friendica\Network\HTTPException\ForbiddenException(); @@ -147,25 +147,27 @@ class Circle extends BaseModule } // Switch to text mode interface if we have more than 'n' contacts or circle members - $switchtotext = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'system', 'circle_edit_image_limit') ?? DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'system', 'groupedit_image_limit'); + $switchtotext = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'system', 'circle_edit_image_limit') ?? + DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'system', 'groupedit_image_limit'); if (is_null($switchtotext)) { - $switchtotext = DI::config()->get('system', 'groupedit_image_limit') ?? DI::config()->get('system', 'circle_edit_image_limit'); + $switchtotext = DI::config()->get('system', 'groupedit_image_limit') ?? + DI::config()->get('system', 'circle_edit_image_limit'); } $tpl = Renderer::getMarkupTemplate('circle_edit.tpl'); $context = [ - '$submit' => DI::l10n()->t('Save Circle'), + '$submit' => DI::l10n()->t('Save Circle'), '$submit_filter' => DI::l10n()->t('Filter'), ]; // @TODO: Replace with parameter from router if ((DI::args()->getArgc() == 2) && (DI::args()->getArgv()[1] === 'new')) { return Renderer::replaceMacros($tpl, $context + [ - '$title' => DI::l10n()->t('Create a circle of contacts/friends.'), - '$gname' => ['circle_name', DI::l10n()->t('Circle Name: '), '', ''], - '$gid' => 'new', + '$title' => DI::l10n()->t('Create a circle of contacts/friends.'), + '$gname' => ['circle_name', DI::l10n()->t('Circle Name: '), '', ''], + '$gid' => 'new', '$form_security_token' => BaseModule::getFormSecurityToken('circle_edit'), ]); } @@ -177,17 +179,17 @@ class Circle extends BaseModule // @TODO: Replace with parameter from router if ((DI::args()->getArgc() == 2) && (DI::args()->getArgv()[1] === 'none') || (DI::args()->getArgc() == 1) && (DI::args()->getArgv()[0] === 'nocircle')) { - $id = -1; + $id = -1; $nocircle = true; - $circle = [ - 'id' => $id, + $circle = [ + 'id' => $id, 'name' => DI::l10n()->t('Contacts not in any circle'), ]; $context = $context + [ - '$title' => $circle['name'], - '$gname' => ['circle_name', DI::l10n()->t('Circle Name: '), $circle['name'], ''], - '$gid' => $id, + '$title' => $circle['name'], + '$gname' => ['circle_name', DI::l10n()->t('Circle Name: '), $circle['name'], ''], + '$gid' => $id, '$editable' => 0, ]; } @@ -210,17 +212,6 @@ class Circle extends BaseModule DI::baseUrl()->redirect('circle'); } - // @TODO: Replace with parameter from router - if ((DI::args()->getArgc() == 3) && (DI::args()->getArgv()[1] === 'markread')) { - BaseModule::checkFormSecurityTokenRedirectOnError('/circle', 'circle_markread', 't'); - - $circle_id = intval(DI::args()->getArgv()[2]); - if ($circle_id && Model\Circle::exists($circle_id, DI::userSession()->getLocalUserId())) { - DBA::update('post-user', ['unseen' => false], ["`uid` = ? AND `unseen` AND `contact-id` IN (SELECT `contact-id` FROM `group_member` WHERE `gid` = ?)", DI::userSession()->getLocalUserId(), $circle_id]); - } - DI::baseUrl()->redirect('circle/' . $circle_id); - } - // @TODO: Replace with parameter from router if ((DI::args()->getArgc() > 2) && intval(DI::args()->getArgv()[1]) && intval(DI::args()->getArgv()[2])) { BaseModule::checkFormSecurityTokenForbiddenOnError('circle_member_change', 't'); @@ -238,7 +229,7 @@ class Circle extends BaseModule DI::baseUrl()->redirect('contact'); } - $members = Model\Contact\Circle::getById($circle['id']); + $members = Model\Contact\Circle::getById($circle['id']); $preselected = []; if (count($members)) { @@ -254,7 +245,7 @@ class Circle extends BaseModule Model\Circle::addMember($circle['id'], $change); } - $members = Model\Contact\Circle::getById($circle['id']); + $members = Model\Contact\Circle::getById($circle['id']); $preselected = []; if (count($members)) { foreach ($members as $member) { @@ -265,21 +256,19 @@ class Circle extends BaseModule $drop_tpl = Renderer::getMarkupTemplate('circle_drop.tpl'); $drop_txt = Renderer::replaceMacros($drop_tpl, [ - '$id' => $circle['id'], - '$delete' => DI::l10n()->t('Delete Circle'), + '$id' => $circle['id'], + '$delete' => DI::l10n()->t('Delete Circle'), '$form_security_token' => BaseModule::getFormSecurityToken('circle_drop'), ]); $context = $context + [ - '$title' => $circle['name'], - '$gname' => ['circle_name', DI::l10n()->t('Circle Name: '), $circle['name'], ''], - '$gid' => $circle['id'], - '$drop' => $drop_txt, - '$form_security_token' => BaseModule::getFormSecurityToken('circle_edit'), - '$form_security_token_markread' => BaseModule::getFormSecurityToken('circle_markread'), - '$edit_name' => DI::l10n()->t('Edit Circle Name'), - '$markread_label' => DI::l10n()->t('Mark all as read'), - '$editable' => 1, + '$title' => $circle['name'], + '$gname' => ['circle_name', DI::l10n()->t('Circle Name: '), $circle['name'], ''], + '$gid' => $circle['id'], + '$drop' => $drop_txt, + '$form_security_token' => BaseModule::getFormSecurityToken('circle_edit'), + '$edit_name' => DI::l10n()->t('Edit Circle Name'), + '$editable' => 1, ]; } @@ -288,11 +277,11 @@ class Circle extends BaseModule } $circle_editor = [ - 'label_members' => DI::l10n()->t('Members'), - 'members' => [], - 'label_contacts' => DI::l10n()->t('All Contacts'), + 'label_members' => DI::l10n()->t('Members'), + 'members' => [], + 'label_contacts' => DI::l10n()->t('All Contacts'), 'circle_is_empty' => DI::l10n()->t('Circle is empty'), - 'contacts' => [], + 'contacts' => [], ]; $sec_token = addslashes(BaseModule::getFormSecurityToken('circle_member_change')); @@ -303,9 +292,9 @@ class Circle extends BaseModule continue; } if ($member['url']) { - $entry = Contact::getContactTemplateVars($member); - $entry['label'] = 'members'; - $entry['photo_menu'] = ''; + $entry = Contact::getContactTemplateVars($member); + $entry['label'] = 'members'; + $entry['photo_menu'] = ''; $entry['change_member'] = [ 'title' => DI::l10n()->t('Remove contact from circle'), 'gid' => $circle['id'], @@ -323,13 +312,13 @@ class Circle extends BaseModule $contacts = Model\Contact\Circle::listUncircled(DI::userSession()->getLocalUserId()); } else { $networks = Widget::unavailableNetworks(); - $query = "`uid` = ? AND NOT `self` AND NOT `deleted` AND NOT `blocked` AND NOT `pending` AND NOT `failed` + $query = "`uid` = ? AND NOT `self` AND NOT `deleted` AND NOT `blocked` AND NOT `pending` AND NOT `failed` AND `rel` IN (?, ?, ?) AND NOT `network` IN (" . substr(str_repeat('?, ', count($networks)), 0, -2) . ")"; $condition = array_merge([$query], [DI::userSession()->getLocalUserId(), Model\Contact::FOLLOWER, Model\Contact::FRIEND, Model\Contact::SHARING], $networks); - $contacts_stmt = DBA::select('contact', [], $condition, ['order' => ['name']]); - $contacts = DBA::toArray($contacts_stmt); + $contacts_stmt = DBA::select('contact', [], $condition, ['order' => ['name']]); + $contacts = DBA::toArray($contacts_stmt); $context['$desc'] = DI::l10n()->t('Click on a contact to add or remove.'); } @@ -340,11 +329,10 @@ class Circle extends BaseModule continue; } if (!in_array($member['id'], $preselected)) { - $entry = Contact::getContactTemplateVars($member); + $entry = Contact::getContactTemplateVars($member); $entry['label'] = 'contacts'; - if (!$nocircle) { + if (!$nocircle) $entry['photo_menu'] = []; - } if (!$nocircle) { $entry['change_member'] = [ @@ -363,7 +351,7 @@ class Circle extends BaseModule $context['$circle_editor'] = $circle_editor; // If there are to many contacts we could provide an alternative view mode - $total = count($circle_editor['members']) + count($circle_editor['contacts']); + $total = count($circle_editor['members']) + count($circle_editor['contacts']); $context['$shortmode'] = (($switchtotext && ($total > $switchtotext)) ? true : false); if ($change) { diff --git a/src/Module/Contact.php b/src/Module/Contact.php index 13baa3bb30..ac26295189 100644 --- a/src/Module/Contact.php +++ b/src/Module/Contact.php @@ -204,7 +204,7 @@ class Contact extends BaseModule DI::page()['htmlhead'] .= Renderer::replaceMacros($tpl, []); $o = ''; - Nav::setSelected('contacts'); + Nav::setSelected('contact'); $_SESSION['return_path'] = DI::args()->getQueryString(); diff --git a/src/Module/Contact/Conversations.php b/src/Module/Contact/Conversations.php index 66ea8dc0a8..28017f279c 100644 --- a/src/Module/Contact/Conversations.php +++ b/src/Module/Contact/Conversations.php @@ -88,23 +88,19 @@ class Conversations extends BaseModule $this->baseUrl->redirect('profile/' . $contact['nick']); } - $raw = isset($request['mode']) && ($request['mode'] == 'raw'); + // Load necessary libraries for the status editor + $this->page->registerFooterScript(Theme::getPathForFile('asset/typeahead.js/dist/typeahead.bundle.js')); + $this->page->registerFooterScript(Theme::getPathForFile('js/friendica-tagsinput/friendica-tagsinput.js')); + $this->page->registerStylesheet(Theme::getPathForFile('js/friendica-tagsinput/friendica-tagsinput.css')); + $this->page->registerStylesheet(Theme::getPathForFile('js/friendica-tagsinput/friendica-tagsinput-typeahead.css')); - if (!$raw) { - // Load necessary libraries for the status editor - $this->page->registerFooterScript(Theme::getPathForFile('asset/typeahead.js/dist/typeahead.bundle.js')); - $this->page->registerFooterScript(Theme::getPathForFile('js/friendica-tagsinput/friendica-tagsinput.js')); - $this->page->registerStylesheet(Theme::getPathForFile('js/friendica-tagsinput/friendica-tagsinput.css')); - $this->page->registerStylesheet(Theme::getPathForFile('js/friendica-tagsinput/friendica-tagsinput-typeahead.css')); + $this->page['aside'] .= VCard::getHTML($contact, true); - $this->page['aside'] .= VCard::getHTML($contact, true); - } - - Nav::setSelected('contacts'); + Nav::setSelected('contact'); $output = ''; - if (!$contact['ap-posting-restricted'] && !$raw) { + if (!$contact['ap-posting-restricted']) { $options = [ 'lockstate' => ACL::getLockstateForUserId($this->userSession->getLocalUserId()) ? 'lock' : 'unlock', 'acl' => ACL::getFullSelectorHTML($this->page, $this->userSession->getLocalUserId(), true, []), @@ -115,10 +111,8 @@ class Conversations extends BaseModule } Contact::setPageTitle($contact); - if (!$raw) { - $output .= Contact::getTabsHTML($contact, Contact::TAB_CONVERSATIONS); - } - $output .= ModelContact::getThreadsFromId($contact['id'], $this->userSession->getLocalUserId(), 0, 0, $request); + $output .= Contact::getTabsHTML($contact, Contact::TAB_CONVERSATIONS); + $output .= ModelContact::getThreadsFromId($contact['id'], $this->userSession->getLocalUserId(), 0, 0, $request['last_created'] ?? ''); return $output; } diff --git a/src/Module/Contact/Follow.php b/src/Module/Contact/Follow.php index 04f131acac..68c556aa52 100644 --- a/src/Module/Contact/Follow.php +++ b/src/Module/Contact/Follow.php @@ -175,11 +175,11 @@ class Follow extends BaseModule $output .= Renderer::replaceMacros( Renderer::getMarkupTemplate('section_title.tpl'), - ['$title' => $this->t('Posts and Replies')], + ['$title' => $this->t('Posts and Replies')] ); // Show last public posts - $output .= Contact::getPostsFromUrl($contact['url'], $this->session->getLocalUserId(), false, $request); + $output .= Contact::getPostsFromUrl($contact['url'], $this->session->getLocalUserId()); } return $output; diff --git a/src/Module/Contact/Media.php b/src/Module/Contact/Media.php index a054186cdd..892e4162c0 100644 --- a/src/Module/Contact/Media.php +++ b/src/Module/Contact/Media.php @@ -53,7 +53,7 @@ class Media extends BaseModule $o = Contact::getTabsHTML($contact, Contact::TAB_MEDIA); - $o .= ModelContact::getPostsFromUrl($contact['url'], $this->userSession->getLocalUserId(), true, $request); + $o .= ModelContact::getPostsFromUrl($contact['url'], $this->userSession->getLocalUserId(), true, $request['last_created'] ?? ''); return $o; } diff --git a/src/Module/Contact/Posts.php b/src/Module/Contact/Posts.php index 1a506606c5..26b855e670 100644 --- a/src/Module/Contact/Posts.php +++ b/src/Module/Contact/Posts.php @@ -81,13 +81,13 @@ class Posts extends BaseModule $this->page['aside'] .= Widget\VCard::getHTML($contact); - Nav::setSelected('contacts'); + Nav::setSelected('contact'); Contact::setPageTitle($contact); $o = Contact::getTabsHTML($contact, Contact::TAB_POSTS); - $o .= Model\Contact::getPostsFromId($contact['id'], $this->userSession->getLocalUserId(), false, $request); + $o .= Model\Contact::getPostsFromId($contact['id'], $this->userSession->getLocalUserId(), false, $request['last_created'] ?? ''); return $o; } diff --git a/src/Module/Contact/Profile.php b/src/Module/Contact/Profile.php index 985f077f91..d322c9bc55 100644 --- a/src/Module/Contact/Profile.php +++ b/src/Module/Contact/Profile.php @@ -187,7 +187,7 @@ class Profile extends BaseModule } } - if (empty($contact['network']) && ContactModel::isLocal($contact['url'])) { + if (empty($contact['network']) && ContactModel::isLocal($contact['url']) ) { $contact['network'] = Protocol::DFRN; $contact['protocol'] = Protocol::ACTIVITYPUB; } @@ -280,7 +280,7 @@ class Profile extends BaseModule $this->page['aside'] .= $vcard_widget . $circles_widget; $o = ''; - Nav::setSelected('contacts'); + Nav::setSelected('contact'); $_SESSION['return_path'] = $this->args->getQueryString(); @@ -320,9 +320,9 @@ class Profile extends BaseModule $this->logger->notice('Empty gsid for contact', ['contact' => $contact]); } - $serverIgnored = $contact['gsid'] - && $this->userGServer->isIgnoredByUser($this->session->getLocalUserId(), $contact['gsid']) - ? $this->t('This contact is on a server you ignored.') + $serverIgnored = $contact['gsid'] && + $this->userGServer->isIgnoredByUser($this->session->getLocalUserId(), $contact['gsid']) ? + $this->t('This contact is on a server you ignored.') : ''; $last_update = (($contact['last-update'] <= DBA::NULL_DATETIME) ? $this->t('Never') : DateTimeFormat::local($contact['last-update'], 'D, j M Y, g:i A')); @@ -354,8 +354,8 @@ class Profile extends BaseModule LocalRelationshipEntity::FFI_NONE => $this->t('Disabled'), LocalRelationshipEntity::FFI_INFORMATION => $this->t('Fetch information'), LocalRelationshipEntity::FFI_KEYWORD => $this->t('Fetch keywords'), - LocalRelationshipEntity::FFI_BOTH => $this->t('Fetch information and keywords'), - ], + LocalRelationshipEntity::FFI_BOTH => $this->t('Fetch information and keywords') + ] ]; } @@ -365,23 +365,23 @@ class Profile extends BaseModule if ($contact['network'] == Protocol::FEED) { $remote_self_options = [ LocalRelationshipEntity::MIRROR_DEACTIVATED => $this->t('No mirroring'), - LocalRelationshipEntity::MIRROR_OWN_POST => $this->t('Mirror as my own posting'), + LocalRelationshipEntity::MIRROR_OWN_POST => $this->t('Mirror as my own posting') ]; } elseif ($contact['network'] == Protocol::ACTIVITYPUB) { $remote_self_options = [ LocalRelationshipEntity::MIRROR_DEACTIVATED => $this->t('No mirroring'), - LocalRelationshipEntity::MIRROR_NATIVE_RESHARE => $this->t('Native reshare'), + LocalRelationshipEntity::MIRROR_NATIVE_RESHARE => $this->t('Native reshare') ]; } elseif ($contact['network'] == Protocol::DFRN) { $remote_self_options = [ LocalRelationshipEntity::MIRROR_DEACTIVATED => $this->t('No mirroring'), LocalRelationshipEntity::MIRROR_OWN_POST => $this->t('Mirror as my own posting'), - LocalRelationshipEntity::MIRROR_NATIVE_RESHARE => $this->t('Native reshare'), + LocalRelationshipEntity::MIRROR_NATIVE_RESHARE => $this->t('Native reshare') ]; } else { $remote_self_options = [ LocalRelationshipEntity::MIRROR_DEACTIVATED => $this->t('No mirroring'), - LocalRelationshipEntity::MIRROR_OWN_POST => $this->t('Mirror as my own posting'), + LocalRelationshipEntity::MIRROR_OWN_POST => $this->t('Mirror as my own posting') ]; } @@ -475,7 +475,7 @@ class Profile extends BaseModule $this->t('Mirror postings from this contact'), $localRelationship->remoteSelf, $this->t('Mark this contact as remote_self, this will cause friendica to repost new entries from this contact.'), - $remote_self_options, + $remote_self_options ], '$channel_settings_label' => $this->t('Channel Settings'), '$frequency_label' => $this->t('Frequency of this contact in relevant channels'), diff --git a/src/Module/Contact/Revoke.php b/src/Module/Contact/Revoke.php index db92cbf730..78574c4ef9 100644 --- a/src/Module/Contact/Revoke.php +++ b/src/Module/Contact/Revoke.php @@ -39,7 +39,7 @@ class Revoke extends BaseModule { parent::__construct($l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters); - $this->dba = $dba; + $this->dba = $dba; if (!DI::userSession()->getLocalUserId()) { return; @@ -82,7 +82,7 @@ class Revoke extends BaseModule return Login::form($_SERVER['REQUEST_URI']); } - Nav::setSelected('contacts'); + Nav::setSelected('contact'); return Renderer::replaceMacros(Renderer::getMarkupTemplate('contact_drop_confirm.tpl'), [ '$l10n' => [ diff --git a/src/Module/Contact/Unfollow.php b/src/Module/Contact/Unfollow.php index b50afaab71..6e0283cb3f 100644 --- a/src/Module/Contact/Unfollow.php +++ b/src/Module/Contact/Unfollow.php @@ -116,7 +116,7 @@ class Unfollow extends \Friendica\BaseModule '$myaddr' => $self['url'], '$action' => $this->baseUrl . '/contact/unfollow', '$keywords' => '', - '$keywords_label' => '', + '$keywords_label' => '' ]); $this->page['aside'] = Widget\VCard::getHTML(Contact::getByURL($contact['url'], false), false, true); @@ -124,7 +124,7 @@ class Unfollow extends \Friendica\BaseModule $o .= Renderer::replaceMacros(Renderer::getMarkupTemplate('section_title.tpl'), ['$title' => $this->t('Posts and Replies')]); // Show last public posts - $o .= Contact::getPostsFromUrl($contact['url'], $this->userSession->getLocalUserId(), false, $request); + $o .= Contact::getPostsFromUrl($contact['url'], $this->userSession->getLocalUserId()); return $o; } diff --git a/src/Module/Conversation/Channel.php b/src/Module/Conversation/Channel.php index 762fb0eaf7..108846034c 100644 --- a/src/Module/Conversation/Channel.php +++ b/src/Module/Conversation/Channel.php @@ -84,6 +84,11 @@ class Channel extends Timeline '$header' => '', ]); + if ($this->pConfig->get($this->session->getLocalUserId(), 'system', 'infinite_scroll', true)) { + $tpl = Renderer::getMarkupTemplate('infinite_scroll_head.tpl'); + $o .= Renderer::replaceMacros($tpl, ['$reload_uri' => $this->args->getQueryString()]); + } + if (!$this->raw) { $tabs = $this->getTabArray($this->channel->getTimelines($this->session->getLocalUserId()), 'channel'); $tabs = array_merge($tabs, $this->getTabArray($this->channelRepository->selectByUid($this->session->getLocalUserId()), 'channel')); @@ -121,18 +126,18 @@ class Channel extends Timeline return $o; } - $o .= $this->conversation->render($items, Conversation::MODE_CHANNEL, $this->raw, false, $order, $this->session->getLocalUserId()); + $o .= $this->conversation->render($items, Conversation::MODE_CHANNEL, false, false, $order, $this->session->getLocalUserId()); $pager = new BoundariesPager( $this->l10n, $this->args->getQueryString(), - $items[array_key_first($items)][$order], - $items[array_key_last($items)][$order], - $this->itemsPerPage, + $items[0][$order], + $items[count($items) - 1][$order], + $this->itemsPerPage ); if ($this->pConfig->get($this->session->getLocalUserId(), 'system', 'infinite_scroll', true)) { - $o .= HTML::scrollLoader($request); + $o .= HTML::scrollLoader(); } else { $o .= $pager->renderMinimal(count($items)); } diff --git a/src/Module/Conversation/Community.php b/src/Module/Conversation/Community.php index 4783cb769b..8787268e9f 100644 --- a/src/Module/Conversation/Community.php +++ b/src/Module/Conversation/Community.php @@ -41,11 +41,11 @@ class Community extends Timeline * Type of the community page * @{ */ - public const DISABLED = -2; - public const DISABLED_VISITOR = -1; - public const LOCAL = 0; - public const GLOBAL = 1; - public const LOCAL_AND_GLOBAL = 2; + const DISABLED = -2; + const DISABLED_VISITOR = -1; + const LOCAL = 0; + const GLOBAL = 1; + const LOCAL_AND_GLOBAL = 2; protected $pageStyle; @@ -77,9 +77,14 @@ class Community extends Timeline '$content' => '', '$header' => '', '$show_global_community_hint' => ($this->selectedTab == CommunityEntity::GLOBAL) && $this->config->get('system', 'show_global_community_hint'), - '$global_community_hint' => $this->l10n->t("This community stream shows all public posts received by this node. They may not reflect the opinions of this node’s users."), + '$global_community_hint' => $this->l10n->t("This community stream shows all public posts received by this node. They may not reflect the opinions of this node’s users.") ]); + if ($this->pConfig->get($this->session->getLocalUserId(), 'system', 'infinite_scroll', true)) { + $tpl = Renderer::getMarkupTemplate('infinite_scroll_head.tpl'); + $o .= Renderer::replaceMacros($tpl, ['$reload_uri' => $this->args->getQueryString()]); + } + if (!$this->raw) { $tabs = $this->getTabArray($this->community->getTimelines($this->session->isAuthenticated()), 'community'); $tab_tpl = Renderer::getMarkupTemplate('common_tabs.tpl'); @@ -107,23 +112,23 @@ class Community extends Timeline if (!$this->database->isResult($items)) { $o .= Renderer::replaceMacros(Renderer::getMarkupTemplate('section_title.tpl'), [ - '$title' => $this->l10n->t('No results.'), + '$title' => $this->l10n->t('No results.') ]); return $o; } - $o .= $this->conversation->render($items, Conversation::MODE_COMMUNITY, $this->raw, false, 'received', $this->session->getLocalUserId()); + $o .= $this->conversation->render($items, Conversation::MODE_COMMUNITY, false, false, 'received', $this->session->getLocalUserId()); $pager = new BoundariesPager( $this->l10n, $this->args->getQueryString(), $items[array_key_first($items)]['received'], $items[array_key_last($items)]['received'], - $this->itemsPerPage, + $this->itemsPerPage ); if ($this->pConfig->get($this->session->getLocalUserId(), 'system', 'infinite_scroll', true)) { - $o .= HTML::scrollLoader($request); + $o .= HTML::scrollLoader(); } else { $o .= $pager->renderMinimal(count($items)); } diff --git a/src/Module/Conversation/Network.php b/src/Module/Conversation/Network.php index af943fc179..7281f60153 100644 --- a/src/Module/Conversation/Network.php +++ b/src/Module/Conversation/Network.php @@ -163,7 +163,7 @@ class Network extends Timeline ]; $this->eventDispatcher->dispatch( - new ArrayFilterEvent(ArrayFilterEvent::NETWORK_CONTENT_START, $hook_data), + new ArrayFilterEvent(ArrayFilterEvent::NETWORK_CONTENT_START, $hook_data) ); $o = ''; @@ -180,7 +180,7 @@ class Network extends Timeline Feature::SEARCHES, Feature::FOLDERS, Feature::NOSHARER, - Feature::TRENDING_TAGS, + Feature::TRENDING_TAGS ]; } @@ -215,8 +215,8 @@ class Network extends Timeline $this->page['aside'] .= TrendingTags::getHTML($this->selectedTab); break; case Feature::NOSHARER: - if (($this->channel->isTimeline($this->selectedTab) || $this->userDefinedChannel->isTimeline($this->selectedTab, $this->session->getLocalUserId())) - && !in_array($this->selectedTab, [Channel::FOLLOWERS, Channel::FORYOU, Channel::DISCOVER])) { + if (($this->channel->isTimeline($this->selectedTab) || $this->userDefinedChannel->isTimeline($this->selectedTab, $this->session->getLocalUserId())) && + !in_array($this->selectedTab, [Channel::FOLLOWERS, Channel::FORYOU, Channel::DISCOVER])) { $this->page['aside'] .= $this->getNoSharerWidget('network'); } break; @@ -224,6 +224,11 @@ class Network extends Timeline } } + if ($this->pConfig->get($this->session->getLocalUserId(), 'system', 'infinite_scroll', true) && ($_GET['mode'] ?? '') != 'minimal') { + $tpl = Renderer::getMarkupTemplate('infinite_scroll_head.tpl'); + $o .= Renderer::replaceMacros($tpl, ['$reload_uri' => $this->args->getQueryString()]); + } + if (!$this->raw) { $o .= $this->getTabsHTML(); @@ -272,7 +277,7 @@ class Network extends Timeline } $o = Renderer::replaceMacros(Renderer::getMarkupTemplate('section_title.tpl'), [ - '$title' => $this->l10n->t('Circle: %s', $circle['name']), + '$title' => $this->l10n->t('Circle: %s', $circle['name']) ]) . $o; } elseif (Profile::shouldDisplayEventList($this->session->getLocalUserId(), $this->mode)) { $o .= Profile::getBirthdays($this->session->getLocalUserId()); @@ -289,7 +294,7 @@ class Network extends Timeline $items = $this->getItems(); } - $o .= $this->conversation->render($items, Conversation::MODE_NETWORK, $this->raw, false, $this->getOrder(), $this->session->getLocalUserId()); + $o .= $this->conversation->render($items, Conversation::MODE_NETWORK, false, false, $this->getOrder(), $this->session->getLocalUserId()); } catch (\Exception $e) { $this->logger->error('Exception when fetching items', ['code' => $e->getCode(), 'message' => $e->getMessage()]); $o .= $this->l10n->t('Error %d (%s) while fetching the timeline.', $e->getCode(), $e->getMessage()); @@ -297,14 +302,14 @@ class Network extends Timeline } if ($this->pConfig->get($this->session->getLocalUserId(), 'system', 'infinite_scroll', true)) { - $o .= HTML::scrollLoader($request); + $o .= HTML::scrollLoader(); } else { $pager = new BoundariesPager( $this->l10n, $this->args->getQueryString(), - $items[array_key_first($items)][$this->order] ?? null, - $items[array_key_last($items)][$this->order] ?? null, - $this->itemsPerPage, + $items[0][$this->order] ?? null, + $items[count($items) - 1][$this->order] ?? null, + $this->itemsPerPage ); $o .= $pager->renderMinimal(count($items)); @@ -359,7 +364,7 @@ class Network extends Timeline ]; $hook_data = $this->eventDispatcher->dispatch( - new ArrayFilterEvent(ArrayFilterEvent::NETWORK_CONTENT_TABS, $hook_data), + new ArrayFilterEvent(ArrayFilterEvent::NETWORK_CONTENT_TABS, $hook_data) )->getArray(); if (!empty($network_timelines)) { @@ -383,7 +388,7 @@ class Network extends Timeline { parent::parseRequest($request); - $this->circleId = (int) ($this->parameters['circle_id'] ?? 0); + $this->circleId = (int)($this->parameters['circle_id'] ?? 0); if (!$this->selectedTab) { $this->selectedTab = $this->getTimelineOrderBySession(); @@ -422,7 +427,7 @@ class Network extends Timeline $this->order = 'commented'; } - $this->selectedTab ??= $this->order; + $this->selectedTab = $this->selectedTab ?? $this->order; // Upon updates in the background and order by last comment we order by received date, // since otherwise the feed will optically jump, when some already visible thread has been updated. @@ -463,51 +468,51 @@ class Network extends Timeline protected function getItems() { - $timelineCondition = []; - $commonCondition = ['uid' => $this->session->getLocalUserId()]; + $conditionFields = ['uid' => $this->session->getLocalUserId()]; + $conditionStrings = []; if (!is_null($this->accountType)) { - $commonCondition['contact-type'] = $this->accountType; + $conditionFields['contact-type'] = $this->accountType; } if ($this->star) { - $timelineCondition['starred'] = true; + $conditionFields['starred'] = true; } if ($this->mention) { - $timelineCondition['mention'] = true; + $conditionFields['mention'] = true; } if ($this->network) { - $commonCondition['network'] = $this->network; + $conditionFields['network'] = $this->network; } if ($this->dateFrom) { - $commonCondition = DBA::mergeConditions($commonCondition, ["`received` <= ? ", DateTimeFormat::convert($this->dateFrom, 'UTC', $this->appHelper->getTimeZone())]); + $conditionStrings = DBA::mergeConditions($conditionStrings, ["`received` <= ? ", DateTimeFormat::convert($this->dateFrom, 'UTC', $this->appHelper->getTimeZone())]); } if ($this->dateTo) { - $commonCondition = DBA::mergeConditions($commonCondition, ["`received` >= ? ", DateTimeFormat::convert($this->dateTo, 'UTC', $this->appHelper->getTimeZone())]); + $conditionStrings = DBA::mergeConditions($conditionStrings, ["`received` >= ? ", DateTimeFormat::convert($this->dateTo, 'UTC', $this->appHelper->getTimeZone())]); } if ($this->circleId) { - $commonCondition = DBA::mergeConditions($commonCondition, ["`contact-id` IN (SELECT `contact-id` FROM `group_member` WHERE `gid` = ?)", $this->circleId]); + $conditionStrings = DBA::mergeConditions($conditionStrings, ["`contact-id` IN (SELECT `contact-id` FROM `group_member` WHERE `gid` = ?)", $this->circleId]); } // Currently only the order modes "received" and "commented" are in use if (!empty($this->itemUriId)) { - $commonCondition = DBA::mergeConditions($commonCondition, ['uri-id' => $this->itemUriId]); + $conditionStrings = DBA::mergeConditions($conditionStrings, ['uri-id' => $this->itemUriId]); } else { if (isset($this->maxId)) { switch ($this->order) { case 'received': - $commonCondition = DBA::mergeConditions($commonCondition, ["`received` < ?", $this->maxId]); + $conditionStrings = DBA::mergeConditions($conditionStrings, ["`received` < ?", $this->maxId]); break; case 'commented': - $commonCondition = DBA::mergeConditions($commonCondition, ["`commented` < ?", $this->maxId]); + $conditionStrings = DBA::mergeConditions($conditionStrings, ["`commented` < ?", $this->maxId]); break; case 'created': - $commonCondition = DBA::mergeConditions($commonCondition, ["`created` < ?", $this->maxId]); + $conditionStrings = DBA::mergeConditions($conditionStrings, ["`created` < ?", $this->maxId]); break; case 'uriid': - $commonCondition = DBA::mergeConditions($commonCondition, ["`uri-id` < ?", $this->maxId]); + $conditionStrings = DBA::mergeConditions($conditionStrings, ["`uri-id` < ?", $this->maxId]); break; } } @@ -515,16 +520,16 @@ class Network extends Timeline if (isset($this->minId)) { switch ($this->order) { case 'received': - $commonCondition = DBA::mergeConditions($commonCondition, ["`received` > ?", $this->minId]); + $conditionStrings = DBA::mergeConditions($conditionStrings, ["`received` > ?", $this->minId]); break; case 'commented': - $commonCondition = DBA::mergeConditions($commonCondition, ["`commented` > ?", $this->minId]); + $conditionStrings = DBA::mergeConditions($conditionStrings, ["`commented` > ?", $this->minId]); break; case 'created': - $commonCondition = DBA::mergeConditions($commonCondition, ["`created` > ?", $this->minId]); + $conditionStrings = DBA::mergeConditions($conditionStrings, ["`created` > ?", $this->minId]); break; case 'uriid': - $commonCondition = DBA::mergeConditions($commonCondition, ["`uri-id` > ?", $this->minId]); + $conditionStrings = DBA::mergeConditions($conditionStrings, ["`uri-id` > ?", $this->minId]); break; } } @@ -541,65 +546,13 @@ class Network extends Timeline $params['order'] = [$this->order => true]; } - $filterchannels = $this->pConfig->get($this->session->getLocalUserId(), 'channel', 'filter_channels') ?? []; - if ($filterchannels) { - $query = "`uri-id` NOT IN (SELECT `uri-id` FROM `channel-post-view` WHERE `uid` = ? AND `channel` IN (" . substr(str_repeat('?, ', count($filterchannels)), 0, -2) . "))"; - $commonCondition = DBA::mergeConditions($commonCondition, array_merge([$query], [$this->session->getLocalUserId()], $filterchannels)); - } - - $fields = ['uri-id', 'created', 'received', 'commented', 'channel', 'contact-id']; - $condition = DBA::mergeConditions($timelineCondition, $commonCondition); - - $timeline = $this->database->getSQL($this->circleId ? 'network-thread-circle-view' : 'network-thread-view', $fields, $condition, $params); - array_shift($condition); - $sql = '(' . $timeline . ')'; - - $systemchannels = []; - $userchannels = []; - - if (!$this->mention && !$this->star) { - foreach ($this->pConfig->get($this->session->getLocalUserId(), 'channel', 'timeline_channels') ?? [] as $channel) { - if (is_numeric($channel)) { - $userchannels[] = (int) $channel; - } else { - $systemchannels[] = $channel; - } - } - } - - if ($systemchannels) { - $channel_condition = DBA::mergeConditions(['channel' => $systemchannels, 'in-timeline' => false], $commonCondition); - $sql .= ' UNION ALL (' . $this->database->getSQL('system-channel-post-view', $fields, $channel_condition, $params) . ')'; - array_shift($channel_condition); - $condition = array_merge($condition, $channel_condition); - } - - if ($userchannels) { - $channel_condition = DBA::mergeConditions(['channel' => $userchannels, 'in-timeline' => false], $commonCondition); - $sql .= ' UNION ALL (' . $this->database->getSQL('channel-post-view', $fields, $channel_condition, $params) . ')'; - array_shift($channel_condition); - $condition = array_merge($condition, $channel_condition); - } - - if ($systemchannels || $userchannels) { - $sql .= DBA::buildParameter($params); - } - - $result = $this->database->p($sql, $condition); - $items = $this->database->toArray($result); + $items = $this->database->selectToArray($this->circleId ? 'network-thread-circle-view' : 'network-thread-view', [], DBA::mergeConditions($conditionFields, $conditionStrings), $params); // min_id quirk, continued if (isset($this->minId) && !isset($this->maxId)) { $items = array_reverse($items); } - // Avoid duplicates - $posts = []; - foreach ($items as $item) { - $posts[$item['uri-id']] = $item; - } - $items = $posts; - if ($this->ping || !$this->database->isResult($items)) { return $items; } diff --git a/src/Module/Conversation/Timeline.php b/src/Module/Conversation/Timeline.php index 6fb3d9336f..1c91409098 100644 --- a/src/Module/Conversation/Timeline.php +++ b/src/Module/Conversation/Timeline.php @@ -14,7 +14,6 @@ use Friendica\BaseModule; use Friendica\Content\Conversation\Collection\Timelines; use Friendica\Content\Conversation\Entity\Channel as ChannelEntity; use Friendica\Content\Conversation\Entity\Community; -use Friendica\Content\Conversation\Entity\UserDefinedChannel as EntityUserDefinedChannel; use Friendica\Content\Conversation\Repository\UserDefinedChannel; use Friendica\Core\Cache\Capability\ICanCache; use Friendica\Core\Config\Capability\IManageConfigValues; @@ -30,7 +29,6 @@ use Friendica\Database\DBA; use Friendica\Model\Item; use Friendica\Model\Post; use Friendica\Model\Post\SearchIndex; -use Friendica\Model\Verb; use Friendica\Module\Response; use Friendica\Network\HTTPException\BadRequestException; use Friendica\Network\HTTPException\ForbiddenException; @@ -117,14 +115,14 @@ class Timeline extends BaseModule $this->session->getLocalUserId(), 'system', 'itemspage_mobile_network', - $this->config->get('system', 'itemspage_network_mobile'), + $this->config->get('system', 'itemspage_network_mobile') ); } else { $this->itemsPerPage = $this->pConfig->get( $this->session->getLocalUserId(), 'system', 'itemspage_network', - $this->config->get('system', 'itemspage_network'), + $this->config->get('system', 'itemspage_network') ); } @@ -321,9 +319,9 @@ class Timeline extends BaseModule if ($this->selectedTab == ChannelEntity::WHATSHOT) { if (!$cache) { if (!is_null($this->accountType)) { - $condition = ["(`comments` > ? OR `activities` > ? OR `views` > ?) AND `contact-type` = ?", $this->channelRepository->getMedianComments($uid, 4), $this->channelRepository->getMedianActivities($uid, 4), $this->channelRepository->getMedianViews($uid, 4), $this->accountType]; + $condition = ["(`comments` > ? OR `activities` > ?) AND `contact-type` = ?", $this->channelRepository->getMedianComments($uid, 4), $this->channelRepository->getMedianActivities($uid, 4), $this->accountType]; } else { - $condition = ["(`comments` > ? OR `activities` > ? OR `views` > ?) AND `contact-type` != ?", $this->channelRepository->getMedianComments($uid, 4), $this->channelRepository->getMedianActivities($uid, 4), $this->channelRepository->getMedianViews($uid, 4), Contact::TYPE_COMMUNITY]; + $condition = ["(`comments` > ? OR `activities` > ?) AND `contact-type` != ?", $this->channelRepository->getMedianComments($uid, 4), $this->channelRepository->getMedianActivities($uid, 4), Contact::TYPE_COMMUNITY]; } } } elseif ($this->selectedTab == ChannelEntity::FORYOU) { @@ -332,10 +330,10 @@ class Timeline extends BaseModule $condition = [ "(`owner-id` IN (SELECT `cid` FROM `contact-relation` WHERE `relation-cid` = ? AND `relation-thread-score` > ?) OR - ((`comments` >= ? OR `activities` >= ? OR `views` >= ?) AND `owner-id` IN (SELECT `cid` FROM `contact-relation` WHERE `follows` AND `relation-cid` = ?)) OR + ((`comments` >= ? OR `activities` >= ?) AND `owner-id` IN (SELECT `cid` FROM `contact-relation` WHERE `follows` AND `relation-cid` = ?)) OR (`owner-id` IN (SELECT `cid` FROM `user-contact` WHERE `uid` = ? AND (`notify_new_posts` OR `channel-frequency` = ?))))", - $cid, $this->channelRepository->getMedianRelationThreadScore($cid, 4), $this->channelRepository->getMedianComments($uid, 4), $this->channelRepository->getMedianActivities($uid, 4), $this->channelRepository->getMedianViews($uid, 4), $cid, - $uid, Contact\User::FREQUENCY_ALWAYS, + $cid, $this->channelRepository->getMedianRelationThreadScore($cid, 4), $this->channelRepository->getMedianComments($uid, 4), $this->channelRepository->getMedianActivities($uid, 4), $cid, + $uid, Contact\User::FREQUENCY_ALWAYS ]; } } elseif ($this->selectedTab == ChannelEntity::DISCOVER) { @@ -346,11 +344,11 @@ class Timeline extends BaseModule "`owner-id` IN (SELECT `cid` FROM `contact-relation` WHERE `relation-cid` = ? AND NOT `follows`) AND (`owner-id` IN (SELECT `cid` FROM `contact-relation` WHERE `relation-cid` = ? AND NOT `follows` AND `relation-thread-score` > ?) OR `owner-id` IN (SELECT `cid` FROM `contact-relation` WHERE `cid` = ? AND `relation-thread-score` > ?) OR - ((`comments` >= ? OR `activities` >= ? OR `views` >= ?) AND + ((`comments` >= ? OR `activities` >= ?) AND (`owner-id` IN (SELECT `cid` FROM `contact-relation` WHERE `cid` = ? AND `relation-thread-score` > ?)) OR (`owner-id` IN (SELECT `cid` FROM `contact-relation` WHERE `relation-cid` = ? AND `relation-thread-score` > ?))))", $cid, $cid, $this->channelRepository->getMedianRelationThreadScore($cid, 4), $cid, $this->channelRepository->getMedianRelationThreadScore($cid, 4), - $this->channelRepository->getMedianComments($uid, 4), $this->channelRepository->getMedianActivities($uid, 4), $this->channelRepository->getMedianViews($uid, 4), $cid, 0, $cid, 0, + $this->channelRepository->getMedianComments($uid, 4), $this->channelRepository->getMedianActivities($uid, 4), $cid, 0, $cid, 0 ]; } } elseif ($this->selectedTab == ChannelEntity::FOLLOWERS) { @@ -366,7 +364,7 @@ class Timeline extends BaseModule "`owner-id` IN (SELECT `cid` FROM `contact-relation` WHERE `follows` AND `last-interaction` > ? AND `relation-cid` IN (SELECT `cid` FROM `contact-relation` WHERE `follows` AND `relation-cid` = ? AND `relation-thread-score` >= ?) AND NOT `cid` IN (SELECT `cid` FROM `contact-relation` WHERE `follows` AND `relation-cid` = ?))", - DateTimeFormat::utc('now - ' . $this->config->get('channel', 'sharer_interaction_days') . ' day'), $cid, $this->channelRepository->getMedianRelationThreadScore($cid, 4), $cid, + DateTimeFormat::utc('now - ' . $this->config->get('channel', 'sharer_interaction_days') . ' day'), $cid, $this->channelRepository->getMedianRelationThreadScore($cid, 4), $cid ]; } } elseif ($this->selectedTab == ChannelEntity::QUIETSHARERS) { @@ -375,7 +373,7 @@ class Timeline extends BaseModule $condition = [ "`owner-id` IN (SELECT `cid` FROM `contact-relation` WHERE `follows` AND `relation-cid` = ? AND `post-score` <= ?)", - $cid, $this->channelRepository->getMedianPostScore($cid, 2), + $cid, $this->channelRepository->getMedianPostScore($cid, 2) ]; } } elseif ($this->selectedTab == ChannelEntity::IMAGE) { @@ -397,7 +395,7 @@ class Timeline extends BaseModule } elseif (is_numeric($this->selectedTab) && !empty($channel = $this->channelRepository->selectById($this->selectedTab, $uid))) { if (!$this->config->get('system', 'channel_cache')) { $condition = $this->channelRepository->getCondition($channel, $uid); - if (in_array($channel->circle, [EntityUserDefinedChannel::CIRCLE_CREATION, EntityUserDefinedChannel::CIRCLE_POSTS, EntityUserDefinedChannel::CIRCLE_ACTIVITY])) { + if (in_array($channel->circle, [-3, -4, -5])) { $table = SearchIndex::getSearchView(); $condition = DBA::mergeConditions($condition, ['uid' => $uid]); } @@ -405,13 +403,9 @@ class Timeline extends BaseModule $condition = ['channel' => $this->selectedTab]; $table = 'channel-post-view'; } - if (in_array($channel->circle, [EntityUserDefinedChannel::CIRCLE_CREATION, EntityUserDefinedChannel::CIRCLE_POSTS, EntityUserDefinedChannel::CIRCLE_ACTIVITY])) { - $orders = [ - EntityUserDefinedChannel::CIRCLE_CREATION => 'created', - EntityUserDefinedChannel::CIRCLE_POSTS => 'received', - EntityUserDefinedChannel::CIRCLE_ACTIVITY => 'commented', - ]; - $this->order = $orders[(int) $channel->circle]; + if (in_array($channel->circle, [-3, -4, -5])) { + $orders = ['-3' => 'created', '-4' => 'received', '-5' => 'commented']; + $this->order = $orders[$channel->circle]; } } @@ -499,10 +493,10 @@ class Timeline extends BaseModule $maxpostperauthor = 0; if ($this->selectedTab == Community::LOCAL) { - $maxpostperauthor = (int) $this->config->get('system', 'max_author_posts_community_page'); + $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'); + $maxpostperauthor = (int)$this->config->get('system', 'max_server_posts_community_page'); $key = 'author-gsid'; } @@ -618,7 +612,7 @@ class Timeline extends BaseModule $uriids = array_keys($items); - foreach (Post\Counts::get(['parent-uri-id' => $uriids, 'vid' => Verb::getID(Activity::POST)]) as $count) { + foreach (Post\Counts::get(['parent-uri-id' => $uriids, 'verb' => Activity::POST]) as $count) { $items[$count['parent-uri-id']]['comments'] += $count['count']; } diff --git a/src/Module/Debug/Babel.php b/src/Module/Debug/Babel.php index 7565d16113..706a317c42 100644 --- a/src/Module/Debug/Babel.php +++ b/src/Module/Debug/Babel.php @@ -42,19 +42,19 @@ class Babel extends BaseModule $bbcode = $request['text']; $results[] = [ 'title' => 'Source input', - 'content' => $visible_whitespace($bbcode), + 'content' => $visible_whitespace($bbcode) ]; $plain = Text\BBCode::toPlaintext($bbcode, false); $results[] = [ 'title' => 'BBCode::toPlaintext', - 'content' => $visible_whitespace($plain), + 'content' => $visible_whitespace($plain) ]; $html = Text\BBCode::convertForUriId(0, $bbcode); $results[] = [ 'title' => 'BBCode::convert (raw HTML)', - 'content' => $visible_whitespace($html), + 'content' => $visible_whitespace($html) ]; $results[] = [ @@ -64,41 +64,41 @@ class Babel extends BaseModule $results[] = [ 'title' => 'BBCode::convert', - 'content' => $html, + 'content' => $html ]; $bbcode2 = Text\HTML::toBBCode($html); $results[] = [ 'title' => 'BBCode::convert => HTML::toBBCode', - 'content' => $visible_whitespace($bbcode2), + 'content' => $visible_whitespace($bbcode2) ]; $markdown = Text\BBCode::toMarkdown($bbcode); $results[] = [ 'title' => 'BBCode::toMarkdown', - 'content' => $visible_whitespace($markdown), + 'content' => $visible_whitespace($markdown) ]; $html2 = Text\Markdown::convert($markdown); $results[] = [ 'title' => 'BBCode::toMarkdown => Markdown::convert (raw HTML)', - 'content' => $visible_whitespace($html2), + 'content' => $visible_whitespace($html2) ]; $results[] = [ 'title' => 'BBCode::toMarkdown => Markdown::convert', - 'content' => $html2, + 'content' => $html2 ]; $bbcode3 = Text\Markdown::toBBCode($markdown); $results[] = [ 'title' => 'BBCode::toMarkdown => Markdown::toBBCode', - 'content' => $visible_whitespace($bbcode3), + 'content' => $visible_whitespace($bbcode3) ]; $bbcode4 = Text\HTML::toBBCode($html2); $results[] = [ 'title' => 'BBCode::toMarkdown => Markdown::convert => HTML::toBBCode', - 'content' => $visible_whitespace($bbcode4), + 'content' => $visible_whitespace($bbcode4) ]; $tags = Text\BBCode::getTags($bbcode); @@ -106,7 +106,7 @@ class Babel extends BaseModule $body = Item::setHashtags($bbcode); $results[] = [ 'title' => 'Item Body', - 'content' => $visible_whitespace($body), + 'content' => $visible_whitespace($body) ]; $results[] = [ 'title' => 'Item Tags', @@ -116,16 +116,16 @@ class Babel extends BaseModule $body2 = PageInfo::searchAndAppendToBody($bbcode, true); $results[] = [ 'title' => 'PageInfo::appendToBody', - 'content' => $visible_whitespace($body2), + 'content' => $visible_whitespace($body2) ]; $html3 = Text\BBCode::convertForUriId(0, $body2); $results[] = [ 'title' => 'PageInfo::appendToBody => BBCode::convert (raw HTML)', - 'content' => $visible_whitespace($html3), + 'content' => $visible_whitespace($html3) ]; $results[] = [ 'title' => 'PageInfo::appendToBody => BBCode::convert', - 'content' => $html3, + 'content' => $html3 ]; break; case 'diaspora': @@ -138,7 +138,7 @@ class Babel extends BaseModule $markdown = XML::unescape($diaspora); // no break case 'markdown': - $markdown ??= trim($request['text']); + $markdown = $markdown ?? trim($request['text']); $results[] = [ 'title' => 'Source input (Markdown)', @@ -153,7 +153,7 @@ class Babel extends BaseModule $results[] = [ 'title' => 'Markdown::convert', - 'content' => $html, + 'content' => $html ]; $bbcode = Text\Markdown::toBBCode($markdown); @@ -171,7 +171,7 @@ class Babel extends BaseModule $results[] = [ 'title' => 'HTML Input', - 'content' => $html, + 'content' => $html ]; $purified = Text\HTML::purify($html); @@ -194,18 +194,18 @@ class Babel extends BaseModule $bbcode = Text\HTML::toBBCode($html); $results[] = [ 'title' => 'HTML::toBBCode', - 'content' => $visible_whitespace($bbcode), + 'content' => $visible_whitespace($bbcode) ]; $html2 = Text\BBCode::convertForUriId(0, $bbcode); $results[] = [ 'title' => 'HTML::toBBCode => BBCode::convert', - 'content' => $html2, + 'content' => $html2 ]; $results[] = [ 'title' => 'HTML::toBBCode => BBCode::convert (raw HTML)', - 'content' => htmlspecialchars($html2), + 'content' => htmlspecialchars($html2) ]; $bbcode2plain = Text\BBCode::toPlaintext($bbcode); @@ -217,7 +217,7 @@ class Babel extends BaseModule $markdown = Text\HTML::toMarkdown($html); $results[] = [ 'title' => 'HTML::toMarkdown', - 'content' => $visible_whitespace($markdown), + 'content' => $visible_whitespace($markdown) ]; $text = Text\HTML::toPlaintext($html, 0); diff --git a/src/Module/Debug/WebFinger.php b/src/Module/Debug/WebFinger.php index c995374f40..6645c2c93c 100644 --- a/src/Module/Debug/WebFinger.php +++ b/src/Module/Debug/WebFinger.php @@ -27,7 +27,7 @@ class WebFinger extends BaseModule $res = ''; if (!empty($addr)) { - $res = Probe::getWebfingerArray($addr); + $res = Probe::lrdd($addr); $res = print_r($res, true); } diff --git a/src/Module/Directory.php b/src/Module/Directory.php index 37c04bc2d3..786c92dcd0 100644 --- a/src/Module/Directory.php +++ b/src/Module/Directory.php @@ -29,8 +29,8 @@ class Directory extends BaseModule { $config = DI::config(); - if (($config->get('system', 'block_public') && !DI::userSession()->isAuthenticated()) - || ($config->get('system', 'block_local_dir') && !DI::userSession()->isAuthenticated())) { + if (($config->get('system', 'block_public') && !DI::userSession()->isAuthenticated()) || + ($config->get('system', 'block_local_dir') && !DI::userSession()->isAuthenticated())) { throw new HTTPException\ForbiddenException(DI::l10n()->t('Public access denied.')); } @@ -56,7 +56,9 @@ class Directory extends BaseModule $profiles = Profile::searchProfiles($pager->getStart(), $pager->getItemsPerPage(), $search); - if ($profiles['total'] !== 0) { + if ($profiles['total'] === 0) { + DI::sysmsg()->addNotice(DI::l10n()->t('No entries (some entries may be hidden).')); + } else { foreach ($profiles['entries'] as $entry) { $contact = Model\Contact::getByURLForUser($entry['url'], DI::userSession()->getLocalUserId()); if (!empty($contact)) { @@ -68,19 +70,17 @@ class Directory extends BaseModule $tpl = Renderer::getMarkupTemplate('directory_header.tpl'); $output .= Renderer::replaceMacros($tpl, [ - '$search' => $search, - '$globaldir' => DI::l10n()->t('Global Directory'), - '$gDirPath' => $gDirPath, - '$desc' => DI::l10n()->t('Find on this site'), - '$contacts' => $entries, - '$no_results' => DI::l10n()->t('No accounts found (some may be hidden).'), - '$finding' => DI::l10n()->t('Results for:'), - '$findterm' => (strlen($search) ? $search : ""), - '$title' => DI::l10n()->t('Site Directory'), - '$search_mod' => 'directory', - '$submit' => DI::l10n()->t('Find'), - '$paginate' => $pager->renderFull($profiles['total']), - '$num_results_text' => DI::l10n()->t("Accounts listed: %s", $profiles['total']), + '$search' => $search, + '$globaldir' => DI::l10n()->t('Global Directory'), + '$gDirPath' => $gDirPath, + '$desc' => DI::l10n()->t('Find on this site'), + '$contacts' => $entries, + '$finding' => DI::l10n()->t('Results for:'), + '$findterm' => (strlen($search) ? $search : ""), + '$title' => DI::l10n()->t('Site Directory'), + '$search_mod' => 'directory', + '$submit' => DI::l10n()->t('Find'), + '$paginate' => $pager->renderFull($profiles['total']), ]); return $output; @@ -140,7 +140,7 @@ class Directory extends BaseModule $location_e = $location; $photo_menu = [ - 'profile' => [DI::l10n()->t("View Profile"), Model\Contact::magicLink($profile_link)], + 'profile' => [DI::l10n()->t("View Profile"), Model\Contact::magicLink($profile_link)] ]; $entry = [ diff --git a/src/Module/Item/Compose.php b/src/Module/Item/Compose.php index 07c2ce6023..af58c8ead7 100644 --- a/src/Module/Item/Compose.php +++ b/src/Module/Item/Compose.php @@ -78,7 +78,7 @@ class Compose extends BaseModule protected function post(array $request = []) { - if (!empty($request['body'])) { + if (!empty($_REQUEST['body'])) { $_REQUEST['return'] = 'network'; require_once 'mod/item.php'; item_post(); @@ -131,10 +131,10 @@ class Compose extends BaseModule $type = 'post'; $doesFederate = true; - $contact_allow = $request['contact_allow'] ?? ''; - $circle_allow = $request['circle_allow'] ?? ''; - $contact_deny = $request['contact_deny'] ?? ''; - $circle_deny = $request['circle_deny'] ?? ''; + $contact_allow = $_REQUEST['contact_allow'] ?? ''; + $circle_allow = $_REQUEST['circle_allow'] ?? ''; + $contact_deny = $_REQUEST['contact_deny'] ?? ''; + $circle_deny = $_REQUEST['circle_deny'] ?? ''; if ($contact_allow . $circle_allow @@ -149,13 +149,11 @@ class Compose extends BaseModule break; } - $title = $request['title'] ?? ''; - $summary = $request['summary'] ?? ''; - $sensitive = $request['sensitive'] ?? false; - $category = $request['category'] ?? ''; - $body = $request['body'] ?? ''; - $location = $request['location'] ?? $user['default-location']; - $wall = $request['wall'] ?? $type == 'post'; + $title = $_REQUEST['title'] ?? ''; + $category = $_REQUEST['category'] ?? ''; + $body = $_REQUEST['body'] ?? ''; + $location = $_REQUEST['location'] ?? $user['default-location']; + $wall = $_REQUEST['wall'] ?? $type == 'post'; $jotplugins = $this->eventDispatcher->dispatch( new HtmlFilterEvent(HtmlFilterEvent::JOT_TOOL, ''), @@ -174,7 +172,7 @@ class Compose extends BaseModule new DateTime('now'), null, $this->l10n->t('Created at'), - 'created_at', + 'created_at' ); } else { $created_at = ''; @@ -185,7 +183,6 @@ class Compose extends BaseModule '$l10n' => [ 'compose_title' => $compose_title, 'default' => '', - 'summary' => $this->l10n->t('Summary'), 'visibility_title' => $this->l10n->t('Visibility'), 'mytitle' => $this->l10n->t('This is you'), 'submit' => $this->l10n->t('Submit'), @@ -207,15 +204,14 @@ class Compose extends BaseModule 'location_disabled' => $this->l10n->t('Location services are disabled. Please check the website\'s permissions on your device'), 'wait' => $this->l10n->t('Please wait'), 'placeholdertitle' => $this->l10n->t('Set title'), - 'placeholdersummary' => Feature::isEnabled($this->session->getLocalUserId(), Feature::SUMMARY) ? $this->l10n->t('Set summary, abstract or spoiler text') : '', 'placeholdercategory' => Feature::isEnabled($this->session->getLocalUserId(), Feature::CATEGORIES) ? $this->l10n->t('Categories (comma-separated list)') : '', 'always_open_compose' => $this->pConfig->get( $this->session->getLocalUserId(), 'frio', 'always_open_compose', - $this->config->get('frio', 'always_open_compose', false), - ) ? '' - : $this->l10n->t('If you want to always use this editor for posting, you can configure the New Post button to always open it in your Theme settings.'), + $this->config->get('frio', 'always_open_compose', false) + ) ? '' : + $this->l10n->t('You can make this page always open when you use the New Post button in the Theme Customization settings.'), ], '$id' => 0, @@ -224,18 +220,15 @@ class Compose extends BaseModule '$wall' => $wall, '$mylink' => $this->baseUrl->remove($contact['url']), '$myphoto' => $this->baseUrl->remove($contact['thumb']), - '$sensitive' => ['sensitive', $this->l10n->t('Sensitive post'), $request['sensitive'] ?? false], '$scheduled_at' => Temporal::getDateTimeField( new DateTime(), new DateTime('now + 6 months'), null, $this->l10n->t('Scheduled at'), - 'scheduled_at', + 'scheduled_at' ), '$created_at' => $created_at, '$title' => $title, - '$summary' => $summary, - 'sensitive' => $sensitive, '$category' => $category, '$body' => $body, '$location' => $location, diff --git a/src/Module/Item/Display.php b/src/Module/Item/Display.php index ede4bace2a..609cc12f77 100644 --- a/src/Module/Item/Display.php +++ b/src/Module/Item/Display.php @@ -89,7 +89,7 @@ class Display extends BaseModule $item = null; $itemUid = $this->session->getLocalUserId(); - $fields = ['id', 'uri-id', 'parent-uri-id', 'author-id', 'author-link', 'contact-id', 'contact-contact-type', 'body', 'uid', 'guid', 'gravity', + $fields = ['uri-id', 'parent-uri-id', 'author-id', 'author-link', 'contact-id', 'contact-contact-type', 'body', 'uid', 'guid', 'gravity', 'plink', 'origin', 'uri', 'post-reason', 'owner-contact-type', 'owner-network', 'owner-id', 'guid', 'author-network', 'author-alias', 'private']; @@ -148,10 +148,6 @@ class Display extends BaseModule $this->notify->setAllSeenForUser($this->session->getLocalUserId(), ['parent-uri-id' => $item['parent-uri-id']]); } - if ($this->session->getLocalUserId() != 0) { - $this->contentItem->setViewed($item['uri-id'], $this->session->getLocalUserId()); - } - $this->displaySidebar($item); $this->displayHead($item['uri-id'], $item['parent-uri-id']); diff --git a/src/Module/LostPass.php b/src/Module/LostPass.php deleted file mode 100644 index d14856c841..0000000000 --- a/src/Module/LostPass.php +++ /dev/null @@ -1,236 +0,0 @@ -sysMessages = $sysMessages; - $this->config = $config; - $this->emailer = $emailer; - } - - /** - * Handle POST requests for password reset form submission - * - * @param array $request - * @return void - */ - protected function post(array $request = []) - { - $loginame = trim($request['login-name'] ?? ''); - if (!$loginame) { - $this->baseUrl->redirect(); - } - - $condition = ['(`email` = ? OR `nickname` = ?) AND `verified` AND NOT `blocked` AND NOT `account_removed` AND NOT `account_expired`', $loginame, $loginame]; - $user = DBA::selectFirst('user', ['uid', 'username', 'nickname', 'email', 'language'], $condition); - if (!DBA::isResult($user)) { - $this->sysMessages->addNotice($this->l10n->t('No valid account found.')); - $this->baseUrl->redirect(); - } - - $pwdreset_token = Strings::getRandomHex(32); - - $fields = [ - 'pwdreset' => hash('sha256', $pwdreset_token), - 'pwdreset_time' => DateTimeFormat::utcNow(), - ]; - $result = DBA::update('user', $fields, ['uid' => $user['uid']]); - if ($result) { - $this->sysMessages->addInfo($this->l10n->t('Password reset request issued. Check your email.')); - } - - $sitename = $this->config->get('config', 'sitename'); - $resetlink = $this->baseUrl . '/lostpass/' . $pwdreset_token; - - $preamble = Strings::deindent($this->l10n->t(' - Dear %1$s, - A request was recently received at "%2$s" to reset your account - password. In order to confirm this request, please select the verification link - below or paste it into your web browser address bar. - - If you did NOT request this change, please DO NOT follow the link - provided and ignore and/or delete this email, the request will expire shortly. - - Your password will not be changed unless we can verify that you - issued this request.', $user['username'], $sitename)); - $body = Strings::deindent($this->l10n->t(' - Follow this link soon to verify your identity: - - %1$s - - You will then receive a follow-up message containing the new password. - You may change that password from your account settings page after logging in. - - The login details are as follows: - - Site Location: %2$s - Login Name: %3$s', $resetlink, $this->baseUrl, $user['nickname'])); - - $email = $this->emailer->newSystemMail() - ->withMessage($this->l10n->t('Password reset requested at %s', $sitename), $preamble, $body) - ->forUser($user) - ->withRecipient($user['email']) - ->build(); - - $this->emailer->send($email); - $this->baseUrl->redirect(); - } - - /** - * Render page content - * - * @param array $request - * @return string Rendered page content - */ - protected function content(array $request = []): string - { - if (isset($this->parameters['token'])) { - $pwdreset_token = $this->parameters['token']; - - $user = DBA::selectFirst('user', ['uid', 'username', 'nickname', 'email', 'pwdreset_time', 'language'], ['pwdreset' => hash('sha256', $pwdreset_token)]); - if (!DBA::isResult($user)) { - $this->sysMessages->addNotice($this->l10n->t("Request could not be verified. \x28You may have previously submitted it.\x29 Password reset failed.")); - - return $this->form(); - } - - // Password reset requests expire in 60 minutes - if ($user['pwdreset_time'] < DateTimeFormat::utc('now - 1 hour')) { - $fields = [ - 'pwdreset' => null, - 'pwdreset_time' => null, - ]; - DBA::update('user', $fields, ['uid' => $user['uid']]); - - $this->sysMessages->addNotice($this->l10n->t('Request has expired, please make a new one.')); - - return $this->form(); - } - - return $this->generatePassword($user); - } else { - return $this->form(); - } - } - - /** - * Render the password reset form - * - * @return string Rendered form HTML - */ - private function form(): string - { - $tpl = Renderer::getMarkupTemplate('lostpass.tpl'); - $o = Renderer::replaceMacros($tpl, [ - '$title' => $this->l10n->t('Forgot your Password?'), - '$desc' => $this->l10n->t('Enter your email address and submit to have your password reset. Then check your email for further instructions.'), - '$name' => $this->l10n->t('Nickname or email'), - '$submit' => $this->l10n->t('Reset my password'), - ]); - - return $o; - } - - /** - * Generate and send a new password to the user - * - * @param array $user User data array - * @return string Rendered success message HTML - */ - private function generatePassword(array $user): string - { - $o = ''; - - $new_password = User::generateNewPassword(); - $result = User::updatePassword($user['uid'], $new_password); - if (DBA::isResult($result)) { - $tpl = Renderer::getMarkupTemplate('pwdreset.tpl'); - $o .= Renderer::replaceMacros($tpl, [ - '$lbl1' => $this->l10n->t('Password Reset'), - '$lbl2' => $this->l10n->t('Your password has been reset as requested.'), - '$lbl3' => $this->l10n->t('Your new password is'), - '$lbl4' => $this->l10n->t('Save or copy your new password - and then'), - '$lbl5' => '' . $this->l10n->t('click here to login') . '.', - '$lbl6' => $this->l10n->t('Your password may be changed from the Settings page after successful login.'), - '$newpass' => $new_password, - ]); - - $this->sysMessages->addInfo($this->l10n->t("Your password has been reset.")); - - $sitename = $this->config->get('config', 'sitename'); - $preamble = Strings::deindent($this->l10n->t(' - Dear %1$s, - Your password has been changed as requested. Please retain this - information for your records ' . "\x28" . 'or change your password immediately to - something that you will remember' . "\x29" . '. - ', $user['username'])); - $body = Strings::deindent($this->l10n->t(' - Your login details are as follows: - - Site Location: %1$s - Login Name: %2$s - Password: %3$s - - You may change that password from your account settings page after logging in. - ', $this->baseUrl, $user['nickname'], $new_password)); - - $email = $this->emailer->newSystemMail() - ->withMessage($this->l10n->t('Your password has been changed at %s', $sitename), $preamble, $body) - ->forUser($user) - ->withRecipient($user['email']) - ->build(); - $this->emailer->send($email); - } - - return $o; - } -} diff --git a/src/Module/Media/Attachment/Browser.php b/src/Module/Media/Attachment/Browser.php index 991079fa11..45162ed794 100644 --- a/src/Module/Media/Attachment/Browser.php +++ b/src/Module/Media/Attachment/Browser.php @@ -57,20 +57,13 @@ class Browser extends BaseModule $tpl = Renderer::getMarkupTemplate('media/browser.tpl'); $output = Renderer::replaceMacros($tpl, [ - '$type' => 'attachment', - '$path' => ['' => $this->t('Files')], - '$folders' => false, - '$files' => $fileArray, - '$cancel' => $this->t('Cancel'), - '$nickname' => $this->session->getLocalUserNickname(), - '$upload' => $this->t('Upload'), - '$photos_text' => $this->t('Photos'), - '$files_text' => $this->t('Files'), - '$aria_close' => $this->t('Close'), - '$aria_breadcrumb' => $this->t('Breadcrumb'), - '$aria_mode_switch' => $this->t('Switch between photo and attachment mode'), - '$aria_album_nav' => $this->t('Album navigation'), - '$aria_browser_content' => $this->t('Browser content'), + '$type' => 'attachment', + '$path' => ['' => $this->t('Files')], + '$folders' => false, + '$files' => $fileArray, + '$cancel' => $this->t('Cancel'), + '$nickname' => $this->session->getLocalUserNickname(), + '$upload' => $this->t('Upload'), ]); if (empty($request['mode'])) { @@ -82,8 +75,8 @@ class Browser extends BaseModule protected function map_files(array $record): array { - [$m1, $m2] = explode('/', $record['filetype']); - $filetype = file_exists(sprintf('images/icons/%s.png', $m1)) ? $m1 : 'text'; + list($m1, $m2) = explode('/', $record['filetype']); + $filetype = file_exists(sprintf('images/icons/%s.png', $m1)) ? $m1 : 'text'; return [ sprintf('%s/attach/%s', $this->baseUrl, $record['id']), diff --git a/src/Module/Media/Photo/Browser.php b/src/Module/Media/Photo/Browser.php index 1322a4515d..408ba575d1 100644 --- a/src/Module/Media/Photo/Browser.php +++ b/src/Module/Media/Photo/Browser.php @@ -69,20 +69,13 @@ class Browser extends BaseModule $tpl = Renderer::getMarkupTemplate('media/browser.tpl'); $output = Renderer::replaceMacros($tpl, [ - '$type' => 'photo', - '$path' => $path, - '$folders' => $albums, - '$files' => $photosArray, - '$cancel' => $this->t('Cancel'), - '$nickname' => $this->session->getLocalUserNickname(), - '$upload' => $this->t('Upload'), - '$photos_text' => $this->t('Photos'), - '$files_text' => $this->t('Files'), - '$aria_close' => $this->t('Close'), - '$aria_breadcrumb' => $this->t('Breadcrumb'), - '$aria_mode_switch' => $this->t('Switch between photo and attachment mode'), - '$aria_album_nav' => $this->t('Album navigation'), - '$aria_browser_content' => $this->t('Browser content'), + '$type' => 'photo', + '$path' => $path, + '$folders' => $albums, + '$files' => $photosArray, + '$cancel' => $this->t('Cancel'), + '$nickname' => $this->session->getLocalUserNickname(), + '$upload' => $this->t('Upload'), ]); if (empty($request['mode'])) { @@ -106,8 +99,7 @@ class Browser extends BaseModule Proxy::PIXEL_MEDIUM, Proxy::PIXEL_MEDIUM ], - ['order' => ['scale']] - ); + ['order' => ['scale']]); $scale = $photo['scale'] ?? $record['loq']; return [ diff --git a/src/Module/Moderation/Report/Create.php b/src/Module/Moderation/Report/Create.php index 5a855698b2..d75e8e0fab 100644 --- a/src/Module/Moderation/Report/Create.php +++ b/src/Module/Moderation/Report/Create.php @@ -32,10 +32,10 @@ use Psr\Log\LoggerInterface; class Create extends BaseModule { - public const CONTACT_ACTION_NONE = 0; - public const CONTACT_ACTION_COLLAPSE = 1; - public const CONTACT_ACTION_IGNORE = 2; - public const CONTACT_ACTION_BLOCK = 3; + const CONTACT_ACTION_NONE = 0; + const CONTACT_ACTION_COLLAPSE = 1; + const CONTACT_ACTION_IGNORE = 2; + const CONTACT_ACTION_BLOCK = 3; /** @var SystemMessages */ private $systemMessages; @@ -99,7 +99,7 @@ class Create extends BaseModule !empty($request['rule-ids']) ? explode(',', $request['rule-ids']) : [], $this->session->get('report_comment') ?? '', !empty($request['uri-ids']) ? explode(',', $request['uri-ids']) : [], - (bool) ($request['forward'] ?? false), + (bool)($request['forward'] ?? false), ); $this->repository->save($report); @@ -228,14 +228,14 @@ class Create extends BaseModule DI::userSession()->getLocalUserId(), 'system', 'itemspage_mobile_network', - DI::config()->get('system', 'itemspage_network_mobile'), + DI::config()->get('system', 'itemspage_network_mobile') ); } else { $itemsPerPage = DI::pConfig()->get( DI::userSession()->getLocalUserId(), 'system', 'itemspage_network', - DI::config()->get('system', 'itemspage_network'), + DI::config()->get('system', 'itemspage_network') ); } @@ -273,7 +273,7 @@ class Create extends BaseModule $tpl = Renderer::getMarkupTemplate('moderation/report/create/summary.tpl'); $forward_translation = $this->t('Would you like to forward this report to the remote server?'); - // @deprecated 2026.01 this translation is scheduled for removal as a new translation has been added without the typo + // @deprecated 2025.07 this translation is scheduled for removal as a new translation has been added without the typo $forward_translation = $this->t('Would you ike to forward this report to the remote server?'); return Renderer::replaceMacros($tpl, [ diff --git a/src/Module/Moderation/Summary.php b/src/Module/Moderation/Summary.php index 8a8e48b928..99ab2ee296 100644 --- a/src/Module/Moderation/Summary.php +++ b/src/Module/Moderation/Summary.php @@ -61,13 +61,12 @@ class Summary extends BaseModeration $t = Renderer::getMarkupTemplate('moderation/summary.tpl'); return Renderer::replaceMacros($t, [ - '$title' => $this->t('Moderation'), - '$page' => $this->t('Summary'), - '$users' => [$this->t('Registered users'), $users], - '$accounts' => $accounts, - '$pending' => [$this->t('Pending registrations'), $pending], - '$warningtext' => [], - '$account_type_header' => $this->t('Registered accounts by type'), + '$title' => $this->t('Moderation'), + '$page' => $this->t('Summary'), + '$users' => [$this->t('Registered users'), $users], + '$accounts' => $accounts, + '$pending' => [$this->t('Pending registrations'), $pending], + '$warningtext' => [], ]); } } diff --git a/src/Module/Notifications/Notifications.php b/src/Module/Notifications/Notifications.php index 859bff76a7..b2105dd88e 100644 --- a/src/Module/Notifications/Notifications.php +++ b/src/Module/Notifications/Notifications.php @@ -44,32 +44,32 @@ class Notifications extends BaseNotifications public function getNotifications() { $notificationHeader = ''; - $notifications = []; + $notifications = []; $factory = $this->formattedNotifyFactory; if (($this->args->get(1) == 'network')) { $notificationHeader = $this->t('Network Notifications'); $notifications = [ - 'ident' => FormattedNotify::NETWORK, + 'ident' => FormattedNotify::NETWORK, 'notifications' => $factory->getNetworkList($this->showAll, $this->firstItemNum, self::ITEMS_PER_PAGE), ]; } elseif (($this->args->get(1) == 'system')) { $notificationHeader = $this->t('System Notifications'); $notifications = [ - 'ident' => FormattedNotify::SYSTEM, + 'ident' => FormattedNotify::SYSTEM, 'notifications' => $factory->getSystemList($this->showAll, $this->firstItemNum, self::ITEMS_PER_PAGE), ]; } elseif (($this->args->get(1) == 'personal')) { $notificationHeader = $this->t('Personal Notifications'); $notifications = [ - 'ident' => FormattedNotify::PERSONAL, + 'ident' => FormattedNotify::PERSONAL, 'notifications' => $factory->getPersonalList($this->showAll, $this->firstItemNum, self::ITEMS_PER_PAGE), ]; } elseif (($this->args->get(1) == 'home')) { $notificationHeader = $this->t('Home Notifications'); $notifications = [ - 'ident' => FormattedNotify::HOME, + 'ident' => FormattedNotify::HOME, 'notifications' => $factory->getHomeList($this->showAll, $this->firstItemNum, self::ITEMS_PER_PAGE), ]; } else { @@ -97,7 +97,7 @@ class Notifications extends BaseNotifications $notificationResult = $this->getNotifications(); $notifications = $notificationResult['notifications'] ?? []; - $notificationHeader = $notificationResult['header'] ?? ''; + $notificationHeader = $notificationResult['header'] ?? ''; if (!empty($notifications['notifications'])) { $notificationTemplates = [ @@ -127,13 +127,10 @@ class Notifications extends BaseNotifications $notificationNoContent = $this->t('No more %s notifications.', $notificationResult['ident']); } - $notificationShowLink = []; - if ($notifications['ident'] != "personal") { - $notificationShowLink = [ - 'href' => ($this->showAll ? 'notifications/' . $notifications['ident'] : 'notifications/' . $notifications['ident'] . '?show=all'), - 'text' => ($this->showAll ? $this->t('Show unread') : $this->t('Show all')), - ]; - } + $notificationShowLink = [ + 'href' => ($this->showAll ? 'notifications/' . $notifications['ident'] : 'notifications/' . $notifications['ident'] . '?show=all'), + 'text' => ($this->showAll ? $this->t('Show unread') : $this->t('Show all')), + ]; return $this->printContent($notificationHeader, $notificationContent, $notificationNoContent, $notificationShowLink); } diff --git a/src/Module/Notifications/Ping.php b/src/Module/Notifications/Ping.php index d9bc92897d..8e280ef0cb 100644 --- a/src/Module/Notifications/Ping.php +++ b/src/Module/Notifications/Ping.php @@ -92,7 +92,7 @@ class Ping extends BaseModule $home_count = 0; $register_count = 0; $sysnotify_count = 0; - $circles_unseen = []; + $circles_unseen = []; $groups_unseen = []; $event_count = 0; @@ -110,12 +110,12 @@ class Ping extends BaseModule $condition = [ "`unseen` AND `uid` = ? AND NOT `origin` AND `wall` AND (`vid` != ? OR `vid` IS NULL)", - $this->session->getLocalUserId(), Verb::getID(Activity::FOLLOW), + $this->session->getLocalUserId(), Verb::getID(Activity::FOLLOW) ]; $home_count = Post::count($condition); - if ($this->config->get('system', 'compute_circle_counts')) { + if ($this->config->get('system','compute_circle_counts')) { // Find out how unseen network posts are spread across circles foreach (Circle::countUnseen($this->session->getLocalUserId()) as $circle_count) { if ($circle_count['count'] > 0) { @@ -138,19 +138,16 @@ class Ping extends BaseModule $mail_count = $this->database->count('mail', ["`uid` = ? AND NOT `seen` AND `from-url` != ?", $this->session->getLocalUserId(), $myurl]); if (Register::getPolicy() === Register::APPROVE && $this->session->isSiteAdmin()) { - $registrations = \Friendica\Model\Register::getPending(); + $registrations = \Friendica\Model\Register::getPending(); $register_count = count($registrations); } $cachekey = 'ping:events:' . $this->session->getLocalUserId(); $events = $this->cache->get($cachekey); if (is_null($events)) { - $events = $this->database->selectToArray( - 'event', - ['type', 'start'], + $events = $this->database->selectToArray('event', ['type', 'start'], ["`uid` = ? AND `start` < ? AND `finish` > ? AND NOT `ignore`", - $this->session->getLocalUserId(), DateTimeFormat::utc('now + 7 days'), DateTimeFormat::utcNow()], - ); + $this->session->getLocalUserId(), DateTimeFormat::utc('now + 7 days'), DateTimeFormat::utcNow()]); $this->cache->set($cachekey, $events, Duration::HOUR); } @@ -210,7 +207,7 @@ class Ping extends BaseModule $registration['url'], $this->l10n->t('{0} requested registration'), new \DateTime($registration['created'], new \DateTimeZone('UTC')), - new Uri($this->baseUrl . '/moderation/users/pending'), + new Uri($this->baseUrl . '/moderation/users/pending') ); } } else { @@ -219,7 +216,7 @@ class Ping extends BaseModule $registrations[0]['url'], $this->l10n->t('{0} and %d others requested registration', count($registrations) - 1), new \DateTime($registrations[0]['created'], new \DateTimeZone('UTC')), - new Uri($this->baseUrl . '/moderation/users/pending'), + new Uri($this->baseUrl . '/moderation/users/pending') ); } @@ -230,7 +227,11 @@ class Ping extends BaseModule // Unseen messages are kept at the top if ($a['seen'] == $b['seen']) { - return $b['timestamp'] <=> $a['timestamp']; + if ($a['timestamp'] == $b['timestamp']) { + return 0; + } else { + return $a['timestamp'] < $b['timestamp'] ? 1 : -1; + } } else { return $a['seen'] ? 1 : -1; } diff --git a/src/Module/Photo.php b/src/Module/Photo.php index 3acb154b8c..0901d0c05d 100644 --- a/src/Module/Photo.php +++ b/src/Module/Photo.php @@ -142,7 +142,7 @@ class Photo extends BaseApi throw new HTTPException\NotFoundException(); } - $cacheable = ($photo['allow_cid'] . $photo['allow_gid'] . $photo['deny_cid'] . $photo['deny_gid'] === '') && ($photo['cacheable'] ?? true); + $cacheable = ($photo['allow_cid'] . $photo['allow_gid'] . $photo['deny_cid'] . $photo['deny_gid'] === '') && (isset($photo['cacheable']) ? $photo['cacheable'] : true); $stamp = microtime(true); $imgdata = ''; @@ -239,7 +239,7 @@ class Photo extends BaseApi 'scale' => $scale, 'resource' => $photo['resource-id'], 'total' => number_format($total, 3), 'fetch' => number_format($fetch, 3), 'data' => number_format($data, 3), 'checksum' => number_format($checksum, 3), - 'output' => number_format($output, 3), 'rest' => number_format($rest, 3), + 'output' => number_format($output, 3), 'rest' => number_format($rest, 3) ]); } @@ -294,7 +294,7 @@ class Photo extends BaseApi return MPhoto::getPhoto($matches[1], $matches[2], self::getCurrentUserID()); } - return MPhoto::createPhotoForExternalResource($url, (int) DI::userSession()->getLocalUserId(), $media['mimetype'] ?? '', $media['blurhash'], $width, $height); + return MPhoto::createPhotoForExternalResource($url, (int)DI::userSession()->getLocalUserId(), $media['mimetype'] ?? '', $media['blurhash'], $width, $height); case 'media': $media = DBA::selectFirst('post-media', ['url', 'height', 'width', 'mimetype', 'uri-id', 'blurhash'], ['id' => $id, 'type' => Post\Media::IMAGE]); if (empty($media)) { @@ -305,14 +305,14 @@ class Photo extends BaseApi return MPhoto::getPhoto($matches[1], $matches[2], self::getCurrentUserID()); } - return MPhoto::createPhotoForExternalResource($media['url'], (int) DI::userSession()->getLocalUserId(), $media['mimetype'], $media['blurhash'], $media['width'], $media['height']); + return MPhoto::createPhotoForExternalResource($media['url'], (int)DI::userSession()->getLocalUserId(), $media['mimetype'], $media['blurhash'], $media['width'], $media['height']); case 'link': $link = DBA::selectFirst('post-link', ['url', 'mimetype', 'blurhash', 'width', 'height'], ['id' => $id]); if (empty($link)) { return false; } - return MPhoto::createPhotoForExternalResource($link['url'], (int) DI::userSession()->getLocalUserId(), $link['mimetype'] ?? '', $link['blurhash'] ?? '', $link['width'] ?? 0, $link['height'] ?? 0); + return MPhoto::createPhotoForExternalResource($link['url'], (int)DI::userSession()->getLocalUserId(), $link['mimetype'] ?? '', $link['blurhash'] ?? '', $link['width'] ?? 0, $link['height'] ?? 0); case 'contact': $fields = ['uid', 'uri-id', 'url', 'nurl', 'avatar', 'photo', 'blurhash', 'xmpp', 'addr', 'network', 'failed', 'updated', 'next-update']; $contact = Contact::getById($id, $fields); diff --git a/src/Module/Post/Edit.php b/src/Module/Post/Edit.php index 96c654e737..daac9aaf8d 100644 --- a/src/Module/Post/Edit.php +++ b/src/Module/Post/Edit.php @@ -79,8 +79,8 @@ class Edit extends BaseModule } $fields = [ - 'allow_cid', 'allow_gid', 'deny_cid', 'deny_gid', 'gravity', 'sensitive', - 'body', 'title', 'content-warning', 'uri-id', 'wall', 'post-type', 'guid', + 'allow_cid', 'allow_gid', 'deny_cid', 'deny_gid', 'gravity', + 'body', 'title', 'uri-id', 'wall', 'post-type', 'guid' ]; $item = Post::selectFirstForUser($this->session->getLocalUserId(), $fields, [ @@ -153,8 +153,6 @@ class Edit extends BaseModule '$public' => $this->t('Public post'), '$title' => $item['title'], '$placeholdertitle' => $this->t('Set title'), - '$summary' => $item['content-warning'], - '$placeholdersummary' => (Feature::isEnabled($this->session->getLocalUserId(), Feature::SUMMARY) ? $this->t('Set summary, abstract or spoiler text') : ''), '$category' => Post\Category::getCSVByURIId($item['uri-id'], $this->session->getLocalUserId(), Post\Category::CATEGORY), '$placeholdercategory' => (Feature::isEnabled($this->session->getLocalUserId(), Feature::CATEGORIES) ? $this->t("Categories \x28comma-separated list\x29") : ''), '$emtitle' => $this->t('Example: bob@example.com, mary@example.com'), diff --git a/src/Module/Profile/Media.php b/src/Module/Profile/Media.php index 64015cea05..489d93eeaa 100644 --- a/src/Module/Profile/Media.php +++ b/src/Module/Profile/Media.php @@ -47,7 +47,7 @@ class Media extends BaseProfile ) { parent::__construct($l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters); - $this->appHelper = $appHelper; + $this->appHelper = $appHelper; $this->userSession = $userSession; } @@ -66,7 +66,7 @@ class Media extends BaseProfile $o = self::getTabsHTML('media', $is_owner, $profile['nickname'], $profile['hide-friends']); - $o .= Contact::getPostsFromUrl($profile['url'], $this->userSession->getLocalUserId(), true, $request); + $o .= Contact::getPostsFromUrl($profile['url'], $this->userSession->getLocalUserId(), true, $request['last_created'] ?? ''); return $o; } diff --git a/src/Module/Profile/Notes.php b/src/Module/Profile/Notes.php deleted file mode 100644 index da4a9173b1..0000000000 --- a/src/Module/Profile/Notes.php +++ /dev/null @@ -1,147 +0,0 @@ -appHelper = $appHelper; - $this->userSession = $userSession; - $this->mode = $mode; - $this->pConfig = $pConfig; - $this->config = $config; - $this->conversation = $conversation; - } - - /** - * Render the notes page content. - * - * @param array $request Optional request parameters - * @return string Rendered HTML for the notes page - * @throws ForbiddenException If no local user is logged in - */ - protected function content(array $request = []): string - { - if (!$this->userSession->getLocalUserId()) { - throw new ForbiddenException(); - } - - Nav::setSelected('home'); - - $contactId = $this->appHelper->getContactId(); - - $o = parent::getTabsHTML('notes', true, $this->userSession->getLocalUserNickname(), false); - - $o .= '

    ' . $this->l10n->t('Personal notes') . '

    '; - - $x = [ - 'lockstate' => 'lock', - 'acl' => ACL::getSelfOnlyHTML($this->userSession->getLocalUserId(), $this->l10n->t('Personal notes are visible only by yourself.')), - 'button' => $this->l10n->t('Save'), - 'acl_data' => '', - ]; - - $o .= $this->conversation->statusEditor($x, $contactId); - - $condition = [ - 'uid' => $this->userSession->getLocalUserId(), - 'post-type' => Item::PT_PERSONAL_NOTE, - 'gravity' => Item::GRAVITY_PARENT, - 'contact-id' => $contactId, - ]; - - if ($this->mode->isMobile()) { - $itemsPerPage = $this->pConfig->get( - $this->userSession->getLocalUserId(), - 'system', - 'itemspage_mobile_network', - $this->config->get('system', 'itemspage_network_mobile'), - ); - } else { - $itemsPerPage = $this->pConfig->get( - $this->userSession->getLocalUserId(), - 'system', - 'itemspage_network', - $this->config->get('system', 'itemspage_network'), - ); - } - - $pager = new Pager($this->l10n, $this->args->getQueryString(), $itemsPerPage); - - $params = [ - 'order' => ['created' => true], - 'limit' => [$pager->getStart(), $pager->getItemsPerPage()], - ]; - $r = Post::selectThreadForUser($this->userSession->getLocalUserId(), ['uri-id'], $condition, $params); - - $count = 0; - if (DBA::isResult($r)) { - $notes = Post::toArray($r); - $count = count($notes); - $o .= $this->conversation->render($notes, Conversation::MODE_NOTES, false); - } - - $o .= $pager->renderMinimal($count); - return $o; - } -} diff --git a/src/Module/Profile/Photos.php b/src/Module/Profile/Photos.php index 70ab405723..63320fb05c 100644 --- a/src/Module/Profile/Photos.php +++ b/src/Module/Profile/Photos.php @@ -11,14 +11,17 @@ use Friendica\App\Arguments; use Friendica\App\BaseURL; use Friendica\App\Page; use Friendica\AppHelper; +use Friendica\Content\Feature; use Friendica\Content\Pager; use Friendica\Core\Config\Capability\IManageConfigValues; use Friendica\Core\L10n; use Friendica\Core\Renderer; use Friendica\Core\Session\Capability\IHandleUserSessions; +use Friendica\Core\System; use Friendica\Database\Database; use Friendica\Event\ArrayFilterEvent; use Friendica\Model\Contact; +use Friendica\Model\Item; use Friendica\Model\Photo; use Friendica\Model\Profile; use Friendica\Module\Response; @@ -132,6 +135,25 @@ class Photos extends \Friendica\Module\BaseProfile $album = $album ?: $newalbum ?: DateTimeFormat::localNow('Y'); + /* + * We create a wall item for every photo, but we don't want to + * overwhelm the data stream with a hundred newly uploaded photos. + * So we will make the first photo uploaded to this album in the last several hours + * visible by default, the rest will become visible over time when and if + * they acquire comments, likes, dislikes, and/or tags + */ + + $r = Photo::selectToArray([], ['`album` = ? AND `uid` = ? AND `created` > ?', $album, $this->owner['uid'], DateTimeFormat::utc('now - 3 hours')]); + if (!$r || ($album == $this->t(Photo::PROFILE_PHOTOS))) { + $visible = 1; + } else { + $visible = 0; + } + + if (!empty($request['not_visible']) && $request['not_visible'] !== 'false') { + $visible = 0; + } + $hook_data = [ 'src' => '', 'filename' => '', @@ -196,7 +218,7 @@ class Photos extends \Friendica\Module\BaseProfile return; } - $this->logger->info('photos: upload: received file: ' . $filename . ' as ' . $src . ' (' . $type . ') ' . $filesize . ' bytes'); + $this->logger->info('photos: upload: received file: ' . $filename . ' as ' . $src . ' ('. $type . ') ' . $filesize . ' bytes'); $maximagesize = Strings::getBytesFromShorthand($this->config->get('system', 'maximagesize')); @@ -240,6 +262,7 @@ class Photos extends \Friendica\Module\BaseProfile return; } + $exif = $image->orient($src); @unlink($src); $max_length = $this->config->get('system', 'max_image_length'); @@ -256,9 +279,53 @@ class Photos extends \Friendica\Module\BaseProfile return; } + $uri = Item::newURI(); + + // Create item container + $lat = $lon = null; + if (!empty($exif['GPS']) && Feature::isEnabled($this->owner['uid'], Feature::PHOTO_LOCATION)) { + $lat = Photo::getGps($exif['GPS']['GPSLatitude'], $exif['GPS']['GPSLatitudeRef']); + $lon = Photo::getGps($exif['GPS']['GPSLongitude'], $exif['GPS']['GPSLongitudeRef']); + } + + $arr = []; + if ($lat && $lon) { + $arr['coord'] = $lat . ' ' . $lon; + } + + $arr['guid'] = System::createUUID(); + $arr['uid'] = $this->owner['uid']; + $arr['uri'] = $uri; + $arr['post-type'] = Item::PT_IMAGE; + $arr['wall'] = 1; + $arr['resource-id'] = $resource_id; + $arr['contact-id'] = $this->owner['id']; + $arr['owner-name'] = $this->owner['name']; + $arr['owner-link'] = $this->owner['url']; + $arr['owner-avatar'] = $this->owner['thumb']; + $arr['author-name'] = $this->owner['name']; + $arr['author-link'] = $this->owner['url']; + $arr['author-avatar'] = $this->owner['thumb']; + $arr['title'] = ''; + $arr['allow_cid'] = $str_contact_allow; + $arr['allow_gid'] = $str_circle_allow; + $arr['deny_cid'] = $str_contact_deny; + $arr['deny_gid'] = $str_circle_deny; + $arr['visible'] = $visible; + $arr['origin'] = 1; + + $arr['body'] = Images::getBBCodeByResource($resource_id, $this->owner['nickname'], $preview, $image->getExt()); + + $item_id = Item::insert($arr); // Update the photo albums cache Photo::clearAlbumCache($this->owner['uid']); + // addon uploaders should call "exit()" within the PHOTO_UPLOAD_END event + // if they do not wish to be redirected + $this->eventDispatcher->dispatch( + new ArrayFilterEvent(ArrayFilterEvent::PHOTO_UPLOAD_END, ['id' => $item_id]), + ); + $this->baseUrl->redirect($this->session->get('photo_return') ?? 'profile/' . $this->owner['nickname'] . '/photos'); } @@ -313,7 +380,7 @@ class Photos extends \Friendica\Module\BaseProfile $this->owner['uid'], Photo::DEFAULT, $pager->getStart(), - $pager->getItemsPerPage(), + $pager->getItemsPerPage() )); $photos = array_map(function ($photo) { @@ -333,7 +400,7 @@ class Photos extends \Friendica\Module\BaseProfile $tpl = Renderer::getMarkupTemplate('photos_head.tpl'); $this->page['htmlhead'] .= Renderer::replaceMacros($tpl, [ - '$ispublic' => $this->t('everybody'), + '$ispublic' => $this->t('everybody') ]); if ($albums = Photo::getAlbums($this->owner['uid'])) { @@ -343,7 +410,7 @@ class Photos extends \Friendica\Module\BaseProfile 'total' => $album['total'], 'url' => 'photos/' . $this->owner['nickname'] . '/album/' . bin2hex($album['album']), 'urlencode' => urlencode($album['album']), - 'bin2hex' => bin2hex($album['album']), + 'bin2hex' => bin2hex($album['album']) ]; }, $albums); @@ -352,7 +419,7 @@ class Photos extends \Friendica\Module\BaseProfile '$title' => $this->t('Photo Albums'), '$recent' => $this->t('Recent Photos'), '$albums' => $albums, - '$upload' => [$this->t('Upload photo'), 'photos/' . $this->owner['nickname'] . '/upload'], + '$upload' => [$this->t('Upload Photos'), 'photos/' . $this->owner['nickname'] . '/upload'], '$can_post' => $this->session->getLocalUserId() && $this->owner['uid'] == $this->session->getLocalUserId(), ]); } @@ -372,10 +439,10 @@ class Photos extends \Friendica\Module\BaseProfile $o .= Renderer::replaceMacros($tpl, [ '$title' => $this->t('Recent Photos'), '$can_post' => $is_owner, - '$upload' => [$this->t('Upload photo'), 'photos/' . $this->owner['nickname'] . '/upload'], + '$upload' => [$this->t('Upload Photos'), 'photos/' . $this->owner['nickname'] . '/upload'], '$photos' => $photos, '$paginate' => $pager->renderFull($total), - 'upload_text' => $this->t('Upload photo'), + 'upload_text' => $this->t('Upload Photos'), ]); return $o; diff --git a/src/Module/Profile/Profile.php b/src/Module/Profile/Profile.php index 297f3ebb7d..1b562cb888 100644 --- a/src/Module/Profile/Profile.php +++ b/src/Module/Profile/Profile.php @@ -35,6 +35,7 @@ use Friendica\Network\HTTPException; use Friendica\Network\HTTPException\InternalServerErrorException; use Friendica\Profile\ProfileField\Repository\ProfileField; use Friendica\Protocol\ActivityPub; +use Friendica\Util\DateTimeFormat; use Friendica\Util\Network; use Friendica\Util\Profiler; use Friendica\Util\Temporal; @@ -145,7 +146,7 @@ class Profile extends BaseProfile $view_as_contact_alert = $this->t( 'You\'re currently viewing your profile as %s Cancel', htmlentities($view_as_contacts[$key]['name'], ENT_COMPAT, 'UTF-8'), - 'profile/' . $this->parameters['nickname'] . '/profile', + 'profile/' . $this->parameters['nickname'] . '/profile' ); } } @@ -182,16 +183,20 @@ class Profile extends BaseProfile if (Feature::isEnabled($profile['uid'], Feature::MEMBER_SINCE)) { $basic_fields += self::buildField( 'membersince', - $this->t('Joined:'), - $this->l10n->mediumDate($profile['register_date']), + $this->t('Member since:'), + DateTimeFormat::local($profile['register_date']) ); } if (!empty($profile['dob']) && $profile['dob'] > DBA::NULL_DATE) { - $short_bd_format = $this->t('d MMMM'); - $dob = intval($profile['dob']) - ? $this->l10n->longDate($profile['dob'] . ' 00:00 +00:00') - : $this->l10n->formatDateTimeByPattern('2001-' . substr($profile['dob'], 5) . ' 00:00 +00:00', $short_bd_format); + $year_bd_format = $this->t('j F, Y'); + $short_bd_format = $this->t('j F'); + + $dob = $this->l10n->getDay( + intval($profile['dob']) ? + DateTimeFormat::utc($profile['dob'] . ' 00:00 +00:00', $year_bd_format) + : DateTimeFormat::utc('2001-' . substr($profile['dob'], 5) . ' 00:00 +00:00', $short_bd_format) + ); $basic_fields += self::buildField('dob', $this->t('Birthday:'), $dob); @@ -216,7 +221,7 @@ class Profile extends BaseProfile $basic_fields += self::buildField( 'homepage', $this->t('Homepage:'), - $this->tryRelMe($profile['homepage']) ?: $this->cleanInput($profile['uri-id'], $profile['homepage']), + $this->tryRelMe($profile['homepage']) ?: $this->cleanInput($profile['uri-id'], $profile['homepage']) ); } @@ -259,7 +264,7 @@ class Profile extends BaseProfile 'custom_' . $profile_field->order, $profile_field->label, $this->tryRelMe($profile_field->value) ?: BBCode::convertForUriId($profile['uri-id'], $profile_field->value), - 'aprofile custom', + 'aprofile custom' ); } @@ -268,7 +273,7 @@ class Profile extends BaseProfile $custom_fields += self::buildField( 'group_list', $this->t('Groups:'), - GroupManager::profileAdvanced($profile['uid']), + GroupManager::profileAdvanced($profile['uid']) ); } @@ -289,9 +294,13 @@ class Profile extends BaseProfile '$custom_fields' => $custom_fields, '$profile' => $profile, '$homepage_verified' => $this->l10n->t('This website has been verified to belong to the same person.'), - '$viewas_link' => [ + '$edit_link' => [ + 'url' => 'settings/profile', $this->t('Edit profile'), + 'label' => $this->t('Edit profile') + ], + '$viewas_link' => [ 'url' => $this->args->getQueryString() . '#viewas', - 'label' => $this->t('View as'), + 'label' => $this->t('View as') ], ]); @@ -380,7 +389,7 @@ class Profile extends BaseProfile $input = trim($input); if (Network::isValidHttpUrl($input)) { try { - $input = (string) Uri::fromParts(parse_url($input)); + $input = (string)Uri::fromParts(parse_url($input)); return '' . $input . ''; } catch (\Throwable $th) { return ''; diff --git a/src/Module/Register.php b/src/Module/Register.php index 50227bfb6c..48c4e6b6e8 100644 --- a/src/Module/Register.php +++ b/src/Module/Register.php @@ -84,13 +84,8 @@ class Register extends BaseModule } if (!DI::userSession()->getLocalUserId() && self::getPolicy() === self::CLOSED) { - $tpl = Renderer::getMarkupTemplate('register_closed.tpl'); - return Renderer::replaceMacros($tpl, [ - '$title' => DI::l10n()->t('Registration Closed'), - '$message' => DI::l10n()->t('Registration is currently closed on this node.'), - '$explanation' => DI::l10n()->t('The administrators have decided to limit new registrations. This could be temporary or permanent.'), - '$find_server' => BBCode::convertForUriId(User::getSystemUriId(), DI::l10n()->t('You can find other open Friendica servers at %s where you can register.', '[url=https://dir.friendica.social/servers]dir.friendica.social/servers[/url]')), - ]); + DI::sysmsg()->addNotice(DI::l10n()->t('Permission denied.')); + return ''; } $max_dailies = intval(DI::config()->get('system', 'max_daily_registrations')); diff --git a/src/Module/Response.php b/src/Module/Response.php index c696699c3f..87f2af3b66 100644 --- a/src/Module/Response.php +++ b/src/Module/Response.php @@ -83,19 +83,19 @@ class Response implements ICanCreateResponses switch ($type) { case static::TYPE_HTML: - $content_type ??= 'text/html; charset=utf-8'; + $content_type = $content_type ?? 'text/html; charset=utf-8'; break; case static::TYPE_JSON: - $content_type ??= 'application/json'; + $content_type = $content_type ?? 'application/json'; break; case static::TYPE_XML: - $content_type ??= 'text/xml'; + $content_type = $content_type ?? 'text/xml'; break; case static::TYPE_RSS: - $content_type ??= 'application/rss+xml'; + $content_type = $content_type ?? 'application/rss+xml'; break; case static::TYPE_ATOM: - $content_type ??= 'application/atom+xml'; + $content_type = $content_type ?? 'application/atom+xml'; break; } diff --git a/src/Module/Search/Filed.php b/src/Module/Search/Filed.php index 0615faa483..b90dd7d923 100644 --- a/src/Module/Search/Filed.php +++ b/src/Module/Search/Filed.php @@ -12,6 +12,7 @@ use Friendica\Content\Nav; use Friendica\Content\Pager; use Friendica\Content\Text\HTML; use Friendica\Content\Widget; +use Friendica\Core\Renderer; use Friendica\Database\DBA; use Friendica\DI; use Friendica\Model\Item; @@ -30,6 +31,13 @@ class Filed extends BaseSearch DI::page()['aside'] .= Widget::fileAs(DI::args()->getCommand(), $_GET['file'] ?? ''); + if (DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'system', 'infinite_scroll', true) && ($_GET['mode'] ?? '') != 'minimal') { + $tpl = Renderer::getMarkupTemplate('infinite_scroll_head.tpl'); + $o = Renderer::replaceMacros($tpl, ['$reload_uri' => DI::args()->getQueryString()]); + } else { + $o = ''; + } + $file = $_GET['file'] ?? ''; // Rawmode is used for fetching new content at the end of the page @@ -42,14 +50,14 @@ class Filed extends BaseSearch DI::userSession()->getLocalUserId(), 'system', 'itemspage_mobile_network', - DI::config()->get('system', 'itemspage_network_mobile'), + DI::config()->get('system', 'itemspage_network_mobile') ); } else { $itemspage_network = DI::pConfig()->get( DI::userSession()->getLocalUserId(), 'system', 'itemspage_network', - DI::config()->get('system', 'itemspage_network'), + DI::config()->get('system', 'itemspage_network') ); } @@ -85,10 +93,10 @@ class Filed extends BaseSearch $items = Post::toArray(Post::selectForUser(DI::userSession()->getLocalUserId(), Item::DISPLAY_FIELDLIST, $item_condition, $item_params)); - $o = DI::conversation()->render($items, Conversation::MODE_FILED, false, false, '', DI::userSession()->getLocalUserId()); + $o .= DI::conversation()->render($items, Conversation::MODE_FILED, false, false, '', DI::userSession()->getLocalUserId()); if (DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'system', 'infinite_scroll', true)) { - $o .= HTML::scrollLoader($request); + $o .= HTML::scrollLoader(); } else { $o .= $pager->renderMinimal($count); } diff --git a/src/Module/Search/Index.php b/src/Module/Search/Index.php index 018fca1383..7fcb6a6b94 100644 --- a/src/Module/Search/Index.php +++ b/src/Module/Search/Index.php @@ -98,7 +98,7 @@ class Index extends BaseSearch 'name' => 'search-header', '$title' => DI::l10n()->t('Search'), '$title_size' => 3, - '$content' => HTML::search($search, 'search-box', false), + '$content' => HTML::search($search, 'search-box', false) ]); if (!$search) { @@ -137,7 +137,7 @@ class Index extends BaseSearch if (parse_url($search, PHP_URL_SCHEME) && parse_url($search, PHP_URL_HOST)) { $this->logger->info('Skipping tag and fulltext search since the search looks like a URL.', ['q' => $search]); $o .= Renderer::replaceMacros(Renderer::getMarkupTemplate('section_title.tpl'), [ - '$title' => DI::l10n()->t('No results.'), + '$title' => DI::l10n()->t('No results.') ]); return $o; } @@ -154,14 +154,14 @@ class Index extends BaseSearch DI::userSession()->getLocalUserId(), 'system', 'itemspage_mobile_network', - DI::config()->get('system', 'itemspage_network_mobile'), + DI::config()->get('system', 'itemspage_network_mobile') ); } else { $itemsPerPage = DI::pConfig()->get( DI::userSession()->getLocalUserId(), 'system', 'itemspage_network', - DI::config()->get('system', 'itemspage_network'), + DI::config()->get('system', 'itemspage_network') ); } @@ -189,12 +189,17 @@ class Index extends BaseSearch if (empty($items)) { if (empty($last_uriid)) { $o .= Renderer::replaceMacros(Renderer::getMarkupTemplate('section_title.tpl'), [ - '$title' => DI::l10n()->t('No results.'), + '$title' => DI::l10n()->t('No results.') ]); } return $o; } + if (DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'system', 'infinite_scroll', true)) { + $tpl = Renderer::getMarkupTemplate('infinite_scroll_head.tpl'); + $o .= Renderer::replaceMacros($tpl, ['$reload_uri' => DI::args()->getQueryString()]); + } + if ($tag) { $title = DI::l10n()->t('Items tagged with: %s', $search); } else { @@ -202,7 +207,7 @@ class Index extends BaseSearch } $o .= Renderer::replaceMacros(Renderer::getMarkupTemplate('section_title.tpl'), [ - '$title' => $title, + '$title' => $title ]); $this->logger->info('Start Conversation.', ['q' => $search]); @@ -210,7 +215,7 @@ class Index extends BaseSearch $o .= DI::conversation()->render($items, Conversation::MODE_SEARCH, false, false, 'commented', DI::userSession()->getLocalUserId()); if (DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'system', 'infinite_scroll', true)) { - $o .= HTML::scrollLoader($request); + $o .= HTML::scrollLoader(); } else { $o .= $pager->renderMinimal($count); } @@ -238,7 +243,7 @@ class Index extends BaseSearch { $search = Network::convertToIdn($search); $isUrl = !empty(parse_url($search, PHP_URL_SCHEME)); - $isAddr = (bool) preg_match('/^@?([a-z0-9.-_]+@[a-z0-9.-_:]+)$/i', trim($search), $matches); + $isAddr = (bool)preg_match('/^@?([a-z0-9.-_]+@[a-z0-9.-_:]+)$/i', trim($search), $matches); if (!$isUrl && !$isAddr) { return; diff --git a/src/Module/Settings/Account.php b/src/Module/Settings/Account.php index 67f0788df4..b65ac03101 100644 --- a/src/Module/Settings/Account.php +++ b/src/Module/Settings/Account.php @@ -504,7 +504,7 @@ class Account extends BaseSettings '$ptitle' => DI::l10n()->t('Account Settings'), '$desc' => DI::l10n()->t("Your Identity Address is '%s' or '%s'.", $nickname . '@' . DI::baseUrl()->getHost() . DI::baseUrl()->getPath(), DI::baseUrl() . '/profile/' . $nickname), - '$submit' => DI::l10n()->t('Save settings'), + '$submit' => DI::l10n()->t('Save Settings'), '$uid' => DI::userSession()->getLocalUserId(), '$form_security_token' => self::getFormSecurityToken('settings'), '$open' => $this->parameters['open'] ?? 'password', @@ -543,7 +543,7 @@ class Account extends BaseSettings '$aclselect' => ACL::getFullSelectorHTML(DI::page(), $this->session->getLocalUserId()), '$expire' => [ - 'label' => DI::l10n()->t('Post expiration'), + 'label' => DI::l10n()->t('Expiration settings'), 'days' => ['expire', DI::l10n()->t("Automatically expire posts after this many days:"), $expire, DI::l10n()->t('If empty, posts will not expire. Expired posts will be deleted')], 'items' => ['expire_items', DI::l10n()->t('Expire posts'), $expire_items, DI::l10n()->t('When activated, posts and comments will be expired.')], 'notes' => ['expire_notes', DI::l10n()->t('Expire personal notes'), $expire_notes, DI::l10n()->t('When activated, the personal notes on your profile page will be expired.')], @@ -551,17 +551,17 @@ class Account extends BaseSettings 'network_only' => ['expire_network_only', DI::l10n()->t('Only expire posts by others'), $expire_network_only, DI::l10n()->t('When activated, your own posts never expire. Then the settings above are only valid for posts you received.')], ], - '$h_not' => DI::l10n()->t('Notifications'), - '$lbl_not' => DI::l10n()->t('Send an email when:'), + '$h_not' => DI::l10n()->t('Notification Settings'), + '$lbl_not' => DI::l10n()->t('Send a notification email when:'), '$notify1' => ['notify1', DI::l10n()->t('You receive an introduction'), ($notify & Notification\Type::INTRO), Notification\Type::INTRO, ''], '$notify2' => ['notify2', DI::l10n()->t('Your introductions are confirmed'), ($notify & Notification\Type::CONFIRM), Notification\Type::CONFIRM, ''], - '$notify3' => ['notify3', DI::l10n()->t('Someone writes on your wall'), ($notify & Notification\Type::WALL), Notification\Type::WALL, ''], + '$notify3' => ['notify3', DI::l10n()->t('Someone writes on your profile wall'), ($notify & Notification\Type::WALL), Notification\Type::WALL, ''], '$notify4' => ['notify4', DI::l10n()->t('Someone writes a followup comment'), ($notify & Notification\Type::COMMENT), Notification\Type::COMMENT, ''], '$notify5' => ['notify5', DI::l10n()->t('You receive a private message'), ($notify & Notification\Type::MAIL), Notification\Type::MAIL, ''], '$notify6' => ['notify6', DI::l10n()->t('You receive a friend suggestion'), ($notify & Notification\Type::SUGGEST), Notification\Type::SUGGEST, ''], '$notify7' => ['notify7', DI::l10n()->t('You are tagged in a post'), ($notify & Notification\Type::TAG_SELF), Notification\Type::TAG_SELF, ''], - '$lbl_notify' => DI::l10n()->t('Notify when:'), + '$lbl_notify' => DI::l10n()->t('Create a desktop notification when:'), '$notify_tagged' => ['notify_tagged', DI::l10n()->t('Someone tagged you'), is_null($notify_type) || $notify_type & UserNotification::TYPE_EXPLICIT_TAGGED, ''], '$notify_direct_comment' => ['notify_direct_comment', DI::l10n()->t('Someone directly commented on your post'), is_null($notify_type) || $notify_type & (UserNotification::TYPE_IMPLICIT_TAGGED + UserNotification::TYPE_DIRECT_COMMENT + UserNotification::TYPE_DIRECT_THREAD_COMMENT), ''], '$notify_like' => ['notify_like', DI::l10n()->t('Someone liked your content'), DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'system', 'notify_like'), DI::l10n()->t('Can only be enabled, when the direct comment notification is enabled.')], diff --git a/src/Module/Settings/Channels.php b/src/Module/Settings/Channels.php index 2f058ccf3f..393d4bf1a7 100644 --- a/src/Module/Settings/Channels.php +++ b/src/Module/Settings/Channels.php @@ -8,7 +8,6 @@ namespace Friendica\Module\Settings; use Friendica\App; -use Friendica\Content\Conversation\Entity\UserDefinedChannel as EntityUserDefinedChannel; use Friendica\Content\Conversation\Factory; use Friendica\Content\Conversation\Repository\UserDefinedChannel; use Friendica\Core\Config\Capability\IManageConfigValues; @@ -63,7 +62,7 @@ class Channels extends BaseSettings $channel_languages = User::getWantedLanguages($uid); if (!empty($request['add_channel'])) { - if (!array_diff((array) $request['new_languages'], $channel_languages)) { + if (!array_diff((array)$request['new_languages'], $channel_languages)) { $request['new_languages'] = null; } @@ -72,11 +71,11 @@ class Channels extends BaseSettings 'description' => $request['new_description'], 'access-key' => substr(mb_strtolower($request['new_access_key']), 0, 1), 'uid' => $uid, - 'circle' => (int) $request['new_circle'], + 'circle' => (int)$request['new_circle'], 'include-tags' => Strings::cleanTags($request['new_include_tags']), 'exclude-tags' => Strings::cleanTags($request['new_exclude_tags']), - 'min-size' => $request['new_min_size'] != '' ? (int) $request['new_min_size'] : null, - 'max-size' => $request['new_max_size'] != '' ? (int) $request['new_max_size'] : null, + 'min-size' => $request['new_min_size'] != '' ? (int)$request['new_min_size'] : null, + 'max-size' => $request['new_max_size'] != '' ? (int)$request['new_max_size'] : null, 'full-text-search' => $request['new_text_search'], 'media-type' => ($request['new_image'] ? 1 : 0) | ($request['new_video'] ? 2 : 0) | ($request['new_audio'] ? 4 : 0), 'languages' => $request['new_languages'], @@ -90,14 +89,14 @@ class Channels extends BaseSettings return; } - foreach (array_keys((array) $request['label']) as $id) { + foreach (array_keys((array)$request['label']) as $id) { if ($request['delete'][$id]) { $success = $this->channel->deleteById($id, $uid); $this->logger->debug('Channel deleted', ['id' => $id, 'success' => $success]); continue; } - if (!array_diff((array) $request['languages'][$id], $channel_languages) && (count((array) $request['languages'][$id]) == count($channel_languages))) { + if (!array_diff((array)$request['languages'][$id], $channel_languages) && (count((array)$request['languages'][$id]) == count($channel_languages))) { $request['languages'][$id] = null; } @@ -107,11 +106,11 @@ class Channels extends BaseSettings 'description' => $request['description'][$id], 'access-key' => substr(mb_strtolower($request['access_key'][$id]), 0, 1), 'uid' => $uid, - 'circle' => (int) $request['circle'][$id], + 'circle' => (int)$request['circle'][$id], 'include-tags' => Strings::cleanTags($request['include_tags'][$id]), 'exclude-tags' => Strings::cleanTags($request['exclude_tags'][$id]), - 'min-size' => $request['min_size'][$id] != '' ? (int) $request['min_size'][$id] : null, - 'max-size' => $request['max_size'][$id] != '' ? (int) $request['max_size'][$id] : null, + 'min-size' => $request['min_size'][$id] != '' ? (int)$request['min_size'][$id] : null, + 'max-size' => $request['max_size'][$id] != '' ? (int)$request['max_size'][$id] : null, 'full-text-search' => $request['text_search'][$id], 'media-type' => ($request['image'][$id] ? 1 : 0) | ($request['video'][$id] ? 2 : 0) | ($request['audio'][$id] ? 4 : 0), 'languages' => $request['languages'][$id], @@ -141,17 +140,17 @@ class Channels extends BaseSettings if (in_array($account_type, [User::ACCOUNT_TYPE_COMMUNITY, User::ACCOUNT_TYPE_RELAY])) { $intro = $this->t('This page can be used to define the channels that will automatically be reshared by your account.'); $circles = [ - EntityUserDefinedChannel::CIRCLE_GLOBAL => $this->l10n->t('Global Community'), + 0 => $this->l10n->t('Global Community') ]; } else { $intro = $this->t('This page can be used to define your own channels.'); $circles = [ - EntityUserDefinedChannel::CIRCLE_GLOBAL => $this->l10n->t('Global Community'), - EntityUserDefinedChannel::CIRCLE_ACTIVITY => $this->l10n->t('Latest Activity'), - EntityUserDefinedChannel::CIRCLE_POSTS => $this->l10n->t('Latest Posts'), - EntityUserDefinedChannel::CIRCLE_CREATION => $this->l10n->t('Latest Creation'), - EntityUserDefinedChannel::CIRCLE_FOLLOWING => $this->l10n->t('Following'), - EntityUserDefinedChannel::CIRCLE_FOLLOWERS => $this->l10n->t('Followers'), + 0 => $this->l10n->t('Global Community'), + -5 => $this->l10n->t('Latest Activity'), + -4 => $this->l10n->t('Latest Posts'), + -3 => $this->l10n->t('Latest Creation'), + -1 => $this->l10n->t('Following'), + -2 => $this->l10n->t('Followers'), ]; } @@ -195,14 +194,14 @@ class Channels extends BaseSettings '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. "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")], + 'delete' => ["delete[$channel->code]", $this->t("Delete channel") . ' (' . $channel->label . ')', false, $this->t("Check to delete this entry from the channel list")] ]; } $t = Renderer::getMarkupTemplate('settings/channels.tpl'); $exclude_tags_translation = $this->t('Comma separated list of tags. If a post contain any of these tags, then it will not be part of this channel.'); - // @deprecated 2026.01 this translation is scheduled for removal as a new translation has been added without the typo + // @deprecated 2025.07 this translation is scheduled for removal as a new translation has been added without the typo $exclude_tags_translation = $this->t('Comma separated list of tags. If a post contain any of these tags, then it will not be part of nthis channel.'); return Renderer::replaceMacros($t, [ diff --git a/src/Module/Settings/Display.php b/src/Module/Settings/Display.php index 85f5f07b9d..183e84c0f6 100644 --- a/src/Module/Settings/Display.php +++ b/src/Module/Settings/Display.php @@ -13,8 +13,6 @@ use Friendica\App\Page; use Friendica\AppHelper; use Friendica\Content\ContactSelector; use Friendica\Content\Conversation\Collection\Timelines; -use Friendica\Content\Conversation\Entity\Channel; -use Friendica\Content\Conversation\Entity\UserDefinedChannel; use Friendica\Content\Text\BBCode; use Friendica\Content\Conversation\Factory\Channel as ChannelFactory; use Friendica\Content\Conversation\Factory\Community as CommunityFactory; @@ -28,7 +26,6 @@ use Friendica\Core\PConfig\Capability\IManagePersonalConfigValues; use Friendica\Core\Renderer; use Friendica\Core\Session\Capability\IHandleUserSessions; use Friendica\Core\Theme; -use Friendica\Model\Post\Engagement; use Friendica\Model\User; use Friendica\Module\BaseSettings; use Friendica\Module\Response; @@ -90,32 +87,30 @@ class Display extends BaseSettings $theme = trim($request['theme']); $mobile_theme = trim($request['mobile_theme'] ?? ''); - $enable_smile = (bool) $request['enable_smile']; - $enable = (array) $request['enable']; - $bookmark = (array) $request['bookmark']; - $channel_languages = (array) $request['channel_languages']; - $timeline_channels = isset($request['timeline_channels']) ? (array) $request['timeline_channels'] : null; - $filter_channels = isset($request['filter_channels']) ? (array) $request['filter_channels'] : null; - $first_day_of_week = (int) $request['first_day_of_week']; + $enable_smile = (bool)$request['enable_smile']; + $enable = (array)$request['enable']; + $bookmark = (array)$request['bookmark']; + $channel_languages = (array)$request['channel_languages']; + $first_day_of_week = (int)$request['first_day_of_week']; $calendar_default_view = trim($request['calendar_default_view']); - $infinite_scroll = (bool) $request['infinite_scroll']; - $enable_smart_threading = (bool) $request['enable_smart_threading']; - $enable_dislike = (bool) $request['enable_dislike']; - $display_resharer = (bool) $request['display_resharer']; - $stay_local = (bool) $request['stay_local']; - $hide_empty_descriptions = (bool) $request['hide_empty_descriptions']; - $hide_custom_emojis = (bool) $request['hide_custom_emojis']; - $platform_icon_style = (int) $request['platform_icon_style']; - $show_page_drop = (bool) $request['show_page_drop']; - $display_eventlist = (bool) $request['display_eventlist']; - $preview_mode = (int) $request['preview_mode']; - $update_content = (int) $request['update_content']; - $embed_remote_media = (bool) $request['embed_remote_media']; - $embed_media = (bool) $request['embed_media']; + $infinite_scroll = (bool)$request['infinite_scroll']; + $enable_smart_threading = (bool)$request['enable_smart_threading']; + $enable_dislike = (bool)$request['enable_dislike']; + $display_resharer = (bool)$request['display_resharer']; + $stay_local = (bool)$request['stay_local']; + $hide_empty_descriptions = (bool)$request['hide_empty_descriptions']; + $hide_custom_emojis = (bool)$request['hide_custom_emojis']; + $platform_icon_style = (int)$request['platform_icon_style']; + $show_page_drop = (bool)$request['show_page_drop']; + $display_eventlist = (bool)$request['display_eventlist']; + $preview_mode = (int)$request['preview_mode']; + $update_content = (int)$request['update_content']; + $embed_remote_media = (bool)$request['embed_remote_media']; + $embed_media = (bool)$request['embed_media']; $widget_timelineorder = trim($request['widget_timelineorder']); $menu_timelineorder = trim($request['menu_timelineorder']); - $widget_timeline_reset = (bool) $request['widget_timeline_reset']; - $menu_timeline_reset = (bool) $request['menu_timeline_reset']; + $widget_timeline_reset = (bool)$request['widget_timeline_reset']; + $menu_timeline_reset = (bool)$request['menu_timeline_reset']; $enabled_timelines = []; foreach ($enable as $code => $enabled) { @@ -131,15 +126,15 @@ class Display extends BaseSettings } } - $itemspage_network = !empty($request['itemspage_network']) - ? intval($request['itemspage_network']) - : $this->config->get('system', 'itemspage_network'); + $itemspage_network = !empty($request['itemspage_network']) ? + intval($request['itemspage_network']) : + $this->config->get('system', 'itemspage_network'); if ($itemspage_network > 100) { $itemspage_network = 100; } - $itemspage_mobile_network = !empty($request['itemspage_mobile_network']) - ? intval($request['itemspage_mobile_network']) - : $this->config->get('system', 'itemspage_network_mobile'); + $itemspage_mobile_network = !empty($request['itemspage_mobile_network']) ? + intval($request['itemspage_mobile_network']) : + $this->config->get('system', 'itemspage_network_mobile'); if ($itemspage_mobile_network > 100) { $itemspage_mobile_network = 100; } @@ -176,13 +171,6 @@ class Display extends BaseSettings $this->pConfig->set($uid, 'system', 'enabled_timelines', $enabled_timelines); $this->pConfig->set($uid, 'channel', 'languages', $channel_languages); - if (!is_null($timeline_channels)) { - $this->pConfig->set($uid, 'channel', 'timeline_channels', $timeline_channels); - } - if (!is_null($filter_channels)) { - $this->pConfig->set($uid, 'channel', 'filter_channels', $filter_channels); - } - $this->pConfig->set($uid, 'accessibility', 'hide_empty_descriptions', $hide_empty_descriptions); $this->pConfig->set($uid, 'accessibility', 'hide_custom_emojis', $hide_custom_emojis); $this->pConfig->set($uid, 'accessibility', 'platform_icon_style', $platform_icon_style); @@ -298,27 +286,6 @@ class Display extends BaseSettings $enabled_timelines = $this->pConfig->get($uid, 'system', 'enabled_timelines', $this->getAvailableTimelines($uid, false)->column('code')); $channel_languages = User::getWantedLanguages($uid); $languages = $this->l10n->getLanguageCodes(true, true); - $timeline_channels = $this->pConfig->get($uid, 'channel', 'timeline_channels') ?? []; - $filter_channels = $this->pConfig->get($uid, 'channel', 'filter_channels') ?? []; - - $channels = []; - if ($this->config->get('system', 'system_channel_cache')) { - foreach ($this->channel->getTimelines($uid) as $channel) { - if (!in_array($channel->code, [Channel::FORYOU, Channel::QUIETSHARERS])) { - $channels[$channel->code] = $channel->label; - } - } - } - - $filter = []; - if ($this->config->get('system', 'channel_cache')) { - foreach ($this->userDefinedChannel->selectByUid($uid) as $channel) { - $filter[$channel->code] = $channel->label; - if (in_array($channel->circle, [UserDefinedChannel::CIRCLE_GLOBAL, UserDefinedChannel::CIRCLE_FOLLOWERS])) { - $channels[$channel->code] = $channel->label; - } - } - } $timelines = []; foreach ($this->getAvailableTimelines($uid) as $timeline) { @@ -403,7 +370,7 @@ class Display extends BaseSettings 3 => $this->t('Wednesday'), 4 => $this->t('Thursday'), 5 => $this->t('Friday'), - 6 => $this->t('Saturday'), + 6 => $this->t('Saturday') ]; $calendar_default_view = $this->pConfig->get($uid, 'calendar', 'default_view', 'month'); @@ -411,7 +378,7 @@ class Display extends BaseSettings 'month' => $this->t('month'), 'agendaWeek' => $this->t('week'), 'agendaDay' => $this->t('day'), - 'listMonth' => $this->t('list'), + 'listMonth' => $this->t('list') ]; $theme_config = ''; @@ -432,27 +399,27 @@ class Display extends BaseSettings '$timeline_title' => $this->t('Timelines'), '$channel_title' => $this->t('Channels'), '$calendar_title' => $this->t('Calendar'), - '$sortable' => $this->t('Drag to reorder, use arrow buttons on each item, or tab to item with keyboard and move up/down with arrow keys'), + '$sortable' => $this->t('Drag to reorder or tab to item with keyboard and move up/down with arrow keys'), '$reset_widget' => [ '0' => 'widget_timeline_reset', - '1' => $this->t('Reset order'), + '1' => $this->t('Reset order') ], '$reset_menu' => [ '0' => 'menu_timeline_reset', - '1' => $this->t('Reset order'), + '1' => $this->t('Reset order') ], '$form_security_token' => self::getFormSecurityToken('settings_display'), '$uid' => $uid, - '$theme' => ['theme', $this->t('Theme'), $theme_selected, '', $themes, true], + '$theme' => ['theme', $this->t('Display theme'), $theme_selected, '', $themes, true], '$mobile_theme' => ['mobile_theme', $this->t('Mobile theme'), $mobile_theme_selected, '', $mobile_themes, false], '$theme_config' => $theme_config, '$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')], '$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 emojis'), $enable_smile, $this->t('When enabled, emoticons are replaced with matching emojis.')], + '$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.')], '$enable_dislike' => ['enable_dislike', $this->t('Display the Dislike feature'), $enable_dislike, $this->t('Display the Dislike button and dislike reactions on posts and comments.')], @@ -474,11 +441,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 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'], - '$timeline_channels' => ['timeline_channels[]', $this->t('Timeline channels:'), $timeline_channels, $this->t('Select all the channels that you want to see in your network timeline.'), $channels, 'multiple'], - '$has_timeline_channels' => !empty($channels), - '$filter_channels' => ['filter_channels[]', $this->t('Filter channels:'), $filter_channels, $this->t('Select all the channels that you want to use as a filter for your network timeline. All posts from these channels will be hidden. For technical reasons postings that are older than %s will not be filtered.', $this->l10n->dateTime(Engagement::getCreationDateLimit(false)), 'r'), $filter, 'multiple'], - '$has_filter_channels' => !empty($filter), + '$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], diff --git a/src/Module/Settings/Features.php b/src/Module/Settings/Features.php index 20cf3c7fea..6cc388428e 100644 --- a/src/Module/Settings/Features.php +++ b/src/Module/Settings/Features.php @@ -67,7 +67,7 @@ class Features extends BaseSettings // iterate through widgetorder and network items foreach ($widgetorder as $widget) { foreach ($arr['network'][1] as $list_item) { - if ($list_item[0] == 'feature_' . $widget) { + if ($list_item[0] == 'feature_'.$widget) { $tmp[] = $list_item; } } @@ -80,11 +80,11 @@ class Features extends BaseSettings return Renderer::replaceMacros($tpl, [ '$form_security_token' => BaseSettings::getFormSecurityToken('settings_features'), '$title' => $this->t('Additional Features'), - '$sortable' => $this->t('Drag to reorder, use arrow buttons on each item, or tab to item with keyboard and move up/down with arrow keys'), + '$sortable' => $this->t('Drag to reorder or tab to item with keyboard and move up/down with arrow keys'), '$network_mode' => Conversation::MODE_NETWORK, '$reset' => [ '0' => 'feature_resetorder', - '1' => $this->t('Reset order'), + '1' => $this->t('Reset order') ], '$features' => $arr, '$submit' => $this->t('Save Settings'), diff --git a/src/Module/Settings/Profile/Index.php b/src/Module/Settings/Profile/Index.php index 46d40b06b9..4a2b3420bf 100644 --- a/src/Module/Settings/Profile/Index.php +++ b/src/Module/Settings/Profile/Index.php @@ -115,7 +115,7 @@ class Index extends BaseSettings } if ($ignore_year) { - $dob = '0000-' . DateTimeFormat::utc('1904-' . $dob, 'm-d'); + $dob = '0000-' . DateTimeFormat::utc('1900-' . $dob, 'm-d'); } else { $dob = DateTimeFormat::utc($dob, 'Y-m-d'); } @@ -148,8 +148,8 @@ class Index extends BaseSettings $profileFieldsNew = $this->getProfileFieldsFromInput( $this->session->getLocalUserId(), - (array) $request['profile_field'], - (array) $request['profile_field_order'], + (array)$request['profile_field'], + (array)$request['profile_field_order'] ); $this->profileFieldRepo->saveCollectionForUser($this->session->getLocalUserId(), $profileFieldsNew); @@ -171,7 +171,7 @@ class Index extends BaseSettings 'pub_keywords' => $pub_keywords, 'prv_keywords' => $prv_keywords, ], - $this->session->getLocalUserId(), + $this->session->getLocalUserId() ); Worker::add(Worker::PRIORITY_MEDIUM, 'CheckRelMeProfileLink', $this->session->getLocalUserId()); @@ -210,7 +210,7 @@ class Index extends BaseSettings $profileFields = $this->profileFieldRepo->selectByUserId($this->session->getLocalUserId()); foreach ($profileFields as $profileField) { $defaultPermissions = $profileField->permissionSet->withAllowedContacts( - Contact::pruneUnavailable($profileField->permissionSet->allow_cid), + Contact::pruneUnavailable($profileField->permissionSet->allow_cid) ); $custom_fields[] = [ @@ -225,7 +225,7 @@ class Index extends BaseSettings false, $defaultPermissions->toArray(), ['network' => Protocol::DFRN], - 'profile_field[' . $profileField->id . ']', + 'profile_field[' . $profileField->id . ']' ), ], @@ -246,7 +246,7 @@ class Index extends BaseSettings false, ['allow_cid' => []], ['network' => Protocol::DFRN], - 'profile_field[new]', + 'profile_field[new]' ), ], @@ -288,7 +288,7 @@ class Index extends BaseSettings

    Reorder by dragging the field title.

    Empty the label field to remove a custom field.

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

    ', - 'profile/' . $owner['nickname'] . '/profile', + 'profile/' . $owner['nickname'] . '/profile' ), ], @@ -313,8 +313,8 @@ class Index extends BaseSettings '$xmpp' => ['xmpp', $this->t('XMPP (Jabber) address:'), $owner['xmpp'], $this->t('The XMPP address will be published so that people can follow you there.')], '$matrix' => ['matrix', $this->t('Matrix (Element) address:'), $owner['matrix'], $this->t('The Matrix address will be published so that people can follow you there.')], '$homepage' => ['homepage', $this->t('Homepage URL:'), $owner['homepage'], $homepage_help_text], - '$pub_keywords' => ['pub_keywords', $this->t('Public Keywords:'), $owner['pub_keywords'], $this->t('Used for suggesting potential friends, can be seen by others.')], - '$prv_keywords' => ['prv_keywords', $this->t('Private Keywords:'), $owner['prv_keywords'], $this->t('Used for searching profiles, never shown to others.')], + '$pub_keywords' => ['pub_keywords', $this->t('Public Keywords:'), $owner['pub_keywords'], $this->t('(Used for suggesting potential friends, can be seen by others)')], + '$prv_keywords' => ['prv_keywords', $this->t('Private Keywords:'), $owner['prv_keywords'], $this->t('(Used for searching profiles, never shown to others)')], '$custom_fields' => $custom_fields, ]); @@ -346,7 +346,7 @@ class Index extends BaseSettings $this->aclFormatter->toString($profileFieldInputs['new']['contact_allow'] ?? ''), $this->aclFormatter->toString($profileFieldInputs['new']['circle_allow'] ?? ''), $this->aclFormatter->toString($profileFieldInputs['new']['contact_deny'] ?? ''), - $this->aclFormatter->toString($profileFieldInputs['new']['circle_deny'] ?? ''), + $this->aclFormatter->toString($profileFieldInputs['new']['circle_deny'] ?? '') )); $profileFields->append($this->profileFieldFactory->createFromValues( @@ -354,7 +354,7 @@ class Index extends BaseSettings $profileFieldOrder['new'], $profileFieldInputs['new']['label'], $profileFieldInputs['new']['value'], - $permissionSet, + $permissionSet )); } @@ -372,7 +372,7 @@ class Index extends BaseSettings $this->aclFormatter->toString($profileFieldInput['contact_allow'] ?? ''), $this->aclFormatter->toString($profileFieldInput['circle_allow'] ?? ''), $this->aclFormatter->toString($profileFieldInput['contact_deny'] ?? ''), - $this->aclFormatter->toString($profileFieldInput['circle_deny'] ?? ''), + $this->aclFormatter->toString($profileFieldInput['circle_deny'] ?? '') )); $profileFields->append($this->profileFieldFactory->createFromValues( @@ -380,7 +380,7 @@ class Index extends BaseSettings $profileFieldOrder[$id], $profileFieldInput['label'], $profileFieldInput['value'], - $permissionSet, + $permissionSet )); } diff --git a/src/Module/Settings/Profile/Photo/Crop.php b/src/Module/Settings/Profile/Photo/Crop.php index a350f5e6ce..53486fe001 100644 --- a/src/Module/Settings/Profile/Photo/Crop.php +++ b/src/Module/Settings/Profile/Photo/Crop.php @@ -25,10 +25,10 @@ class Crop extends BaseSettings } $photo_prefix = $this->parameters['guid']; - $resource_id = $photo_prefix; - $scale = 0; + $resource_id = $photo_prefix; + $scale = 0; if (substr($photo_prefix, -2, 1) == '-') { - [$resource_id, $scale] = explode('-', $photo_prefix); + list($resource_id, $scale) = explode('-', $photo_prefix); } self::checkFormSecurityTokenRedirectOnError('settings/profile/photo/crop/' . $photo_prefix, 'settings_profile_photo_crop'); @@ -38,7 +38,7 @@ class Crop extends BaseSettings // Image selection origin is top left $selectionX = intval($_POST['xstart'] ?? 0); $selectionY = intval($_POST['ystart'] ?? 0); - $selectionW = intval($_POST['width'] ?? 0); + $selectionW = intval($_POST['width'] ?? 0); $selectionH = intval($_POST['height'] ?? 0); $path = 'profile/' . DI::userSession()->getLocalUserNickname(); @@ -54,14 +54,14 @@ class Crop extends BaseSettings // Normalizing expected square crop parameters $selectionW = $selectionH = min($selectionW, $selectionH); - $imageIsSquare = $Image->getWidth() === $Image->getHeight(); + $imageIsSquare = $Image->getWidth() === $Image->getHeight(); $selectionIsFullImage = $selectionX === 0 && $selectionY === 0 && $selectionW === $Image->getWidth() && $selectionH === $Image->getHeight(); // Bypassed UI with a rectangle image, we force a square cropped image if (!$imageIsSquare && $action == 'skip') { $selectionX = $selectionY = 0; $selectionW = $selectionH = min($Image->getWidth(), $Image->getHeight()); - $action = 'crop'; + $action = 'crop'; } // Selective crop if it was asked and the selection isn't the full image @@ -158,9 +158,9 @@ class Crop extends BaseSettings } $havescale = false; - $smallest = 0; + $smallest = 0; foreach ($photos as $photo) { - $smallest = $photo['scale'] == 1 ? 1 : $smallest; + $smallest = $photo['scale'] == 1 ? 1 : $smallest; $havescale = $havescale || $photo['scale'] == 5; } @@ -197,16 +197,16 @@ class Crop extends BaseSettings DI::page()['htmlhead'] .= Renderer::replaceMacros(Renderer::getMarkupTemplate('settings/profile/photo/crop_head.tpl'), []); $filename = $imagecrop['resource-id'] . '-' . $imagecrop['scale'] . $imagecrop['ext']; - $tpl = Renderer::getMarkupTemplate('settings/profile/photo/crop.tpl'); - $o = Renderer::replaceMacros($tpl, [ - '$filename' => $filename, - '$resource' => $imagecrop['resource-id'] . '-' . $imagecrop['scale'], - '$image_url' => DI::baseUrl() . '/photo/' . $filename, - '$title' => DI::l10n()->t('Crop Image'), - '$desc' => DI::l10n()->t('Please adjust the image cropping for optimum viewing.'), + $tpl = Renderer::getMarkupTemplate('settings/profile/photo/crop.tpl'); + $o = Renderer::replaceMacros($tpl, [ + '$filename' => $filename, + '$resource' => $imagecrop['resource-id'] . '-' . $imagecrop['scale'], + '$image_url' => DI::baseUrl() . '/photo/' . $filename, + '$title' => DI::l10n()->t('Crop Image'), + '$desc' => DI::l10n()->t('Please adjust the image cropping for optimum viewing.'), '$form_security_token' => self::getFormSecurityToken('settings_profile_photo_crop'), - '$skip' => $isSquare ? DI::l10n()->t('Use Image As Is') : '', - '$crop' => DI::l10n()->t('Crop Image'), + '$skip' => $isSquare ? DI::l10n()->t('Use Image As Is') : '', + '$crop' => DI::l10n()->t('Crop Image'), ]); return $o; diff --git a/src/Module/Update/Contact.php b/src/Module/Update/Contact.php deleted file mode 100644 index 0fbf603177..0000000000 --- a/src/Module/Update/Contact.php +++ /dev/null @@ -1,82 +0,0 @@ -userSession = $userSession; - } - - /** - * Return the HTML update for a contact thread. - * - * @param array $request Request parameters; expects 'item' and optional 'last_received' - * @return void Sends HTML update and exits - * @throws ForbiddenException If no local user is logged in - * @throws NotFoundException If the contact or item cannot be found - */ - protected function rawContent(array $request = []) - { - if (!$this->userSession->getLocalUserId()) { - throw new ForbiddenException(); - } - - $pcid = ModelContact::getPublicContactId(intval($this->parameters['id']), $this->userSession->getLocalUserId()); - if (!$pcid) { - throw new NotFoundException(); - } - - $item = Post::selectFirst(['parent'], ['id' => $request['item']]); - if (!DBA::isResult($item)) { - throw new NotFoundException(); - } - - System::htmlUpdateExit(ModelContact::getThreadsFromId($pcid, $this->userSession->getLocalUserId(), true, $item['parent'] ?? 0, $request)); - } -} diff --git a/src/Module/Update/Notes.php b/src/Module/Update/Notes.php deleted file mode 100644 index 871b2b18ed..0000000000 --- a/src/Module/Update/Notes.php +++ /dev/null @@ -1,62 +0,0 @@ -userSession->getLocalUserId()) { - throw new ForbiddenException(); - } - - if (!isset($request['item'])) { - throw new NotFoundException(); - } - - $condition = [ - 'uid' => $this->userSession->getLocalUserId(), - 'post-type' => Item::PT_PERSONAL_NOTE, - 'gravity' => Item::GRAVITY_PARENT, - 'id' => $request['item'], - ]; - - $r = Post::selectThreadForUser($this->userSession->getLocalUserId(), ['uri-id'], $condition); - if (!DBA::isResult($r)) { - throw new NotFoundException(); - } - - System::htmlUpdateExit($this->conversation->render(Post::toArray($r), Conversation::MODE_NOTES, true)); - } -} diff --git a/src/Module/User/Import.php b/src/Module/User/Import.php index 895b98f1d7..03b3938645 100644 --- a/src/Module/User/Import.php +++ b/src/Module/User/Import.php @@ -34,8 +34,8 @@ use Psr\Log\LoggerInterface; class Import extends \Friendica\BaseModule { - public const IMPORT_DEBUG = false; - public const MEMORY_LIMIT = 67108864; // 64MB + const IMPORT_DEBUG = false; + const MEMORY_LIMIT = 67108864; // 64MB /** @var IManageConfigValues */ private $config; @@ -233,11 +233,11 @@ class Import extends \Friendica\BaseModule } // Backward compatibility - $account['circle'] ??= $account['group']; - $account['circle_member'] ??= $account['group_member']; + $account['circle'] = $account['circle'] ?? $account['group']; + $account['circle_member'] = $account['circle_member'] ?? $account['group_member']; $oldBaseUrl = $account['baseurl']; - $newBaseUrl = (string) $this->baseUrl; + $newBaseUrl = (string)$this->baseUrl; $oldAddr = str_replace('http://', '@', Strings::normaliseLink($oldBaseUrl)); $newAddr = str_replace('http://', '@', Strings::normaliseLink($newBaseUrl)); @@ -404,7 +404,7 @@ class Import extends \Friendica\BaseModule $photo['allow_cid'], $photo['allow_gid'], $photo['deny_cid'], - $photo['deny_gid'], + $photo['deny_gid'] ); if ($r === false) { diff --git a/src/Module/WellKnown/HostMeta.php b/src/Module/WellKnown/HostMeta.php new file mode 100644 index 0000000000..938d31a03e --- /dev/null +++ b/src/Module/WellKnown/HostMeta.php @@ -0,0 +1,72 @@ +get('system', 'site_pubkey', false)) { + $res = Crypto::newKeypair(1024); + + $config->set('system', 'site_prvkey', $res['prvkey']); + $config->set('system', 'site_pubkey', $res['pubkey']); + } + + $domain = (string)DI::baseUrl(); + + XML::fromArray([ + 'XRD' => [ + '@attributes' => [ + 'xmlns' => 'http://docs.oasis-open.org/ns/xri/xrd-1.0', + ], + 'hm:Host' => DI::baseUrl()->getHost(), + '1:link' => [ + '@attributes' => [ + 'rel' => 'lrdd', + 'type' => 'application/xrd+xml', + 'template' => $domain . '/xrd?uri={uri}' + ] + ], + '2:link' => [ + '@attributes' => [ + 'rel' => 'lrdd', + 'type' => 'application/json', + 'template' => $domain . '/.well-known/webfinger?resource={uri}' + ] + ], + '3:link' => [ + '@attributes' => [ + 'rel' => 'acct-mgmt', + 'href' => $domain . '/amcd' + ] + ], + '4:link' => [ + '@attributes' => [ + 'rel' => 'http://services.mozilla.com/amcd/0.1', + 'href' => $domain . '/amcd' + ] + ], + ], + ], $xml, false, ['hm' => 'http://host-meta.net/xrd/1.0']); + + $this->httpExit($xml->saveXML(), Response::TYPE_XML, 'application/xrd+xml'); + } +} diff --git a/src/Module/Xrd.php b/src/Module/Xrd.php index b6caec7121..39b4e2538c 100644 --- a/src/Module/Xrd.php +++ b/src/Module/Xrd.php @@ -32,27 +32,27 @@ class Xrd extends BaseModule throw new BadRequestException(); } - $uri = urldecode(trim($_GET['uri'])); + $uri = urldecode(trim($_GET['uri'])); $mode = self::getAcceptedContentType($_SERVER['HTTP_ACCEPT'] ?? '', Response::TYPE_XML); } else { if (empty($_GET['resource'])) { throw new BadRequestException(); } - $uri = urldecode(trim($_GET['resource'])); + $uri = urldecode(trim($_GET['resource'])); $mode = self::getAcceptedContentType($_SERVER['HTTP_ACCEPT'] ?? '', Response::TYPE_JSON); } if (Network::isValidHttpUrl($uri)) { $name = ltrim(basename($uri), '~'); $host = parse_url($uri, PHP_URL_HOST); - } elseif (preg_match('/^[[:alpha:]][[:alnum:]+-.]+:/', $uri)) { + } else if (preg_match('/^[[:alpha:]][[:alnum:]+-.]+:/', $uri)) { $local = str_replace('acct:', '', $uri); if (substr($local, 0, 2) == '//') { $local = substr($local, 2); } - [$name, $host] = explode('@', $local); + list($name, $host) = explode('@', $local); } else { throw new BadRequestException(); } @@ -128,7 +128,7 @@ class Xrd extends BaseModule private function printSystemJSON(array $owner) { $baseURL = (string)$this->baseUrl; - $json = [ + $json = [ 'subject' => 'acct:' . $owner['addr'], 'aliases' => [$owner['url']], 'links' => [ @@ -181,7 +181,7 @@ class Xrd extends BaseModule $alias, $owner['url'], ], - 'links' => [ + 'links' => [ [ 'rel' => ActivityNamespace::DFRN, 'href' => $owner['url'], @@ -243,12 +243,12 @@ class Xrd extends BaseModule $xmlString = XML::fromArray([ 'XRD' => [ '@attributes' => [ - 'xmlns' => 'http://docs.oasis-open.org/ns/xri/xrd-1.0', + 'xmlns' => 'http://docs.oasis-open.org/ns/xri/xrd-1.0', ], 'Subject' => 'acct:' . $owner['addr'], '1:Alias' => $owner['url'], '2:Alias' => $alias, - '1:link' => [ + '1:link' => [ '@attributes' => [ 'rel' => ActivityNamespace::DFRN, 'href' => $owner['url'] diff --git a/src/Navigation/Notifications/Repository/Notification.php b/src/Navigation/Notifications/Repository/Notification.php index 887ec440de..85c8895135 100644 --- a/src/Navigation/Notifications/Repository/Notification.php +++ b/src/Navigation/Notifications/Repository/Notification.php @@ -45,7 +45,7 @@ class Notification extends BaseRepository */ private function selectOne(array $condition, array $params = []): NotificationEntity { - $fields = $this->_selectFirstRowAsArray($condition, $params); + $fields = $this->_selectFirstRowAsArray( $condition, $params); return $this->factory->createFromTableRow($fields); } diff --git a/src/Navigation/Notifications/Repository/Notify.php b/src/Navigation/Notifications/Repository/Notify.php index 9c085f09f3..1f3385a366 100644 --- a/src/Navigation/Notifications/Repository/Notify.php +++ b/src/Navigation/Notifications/Repository/Notify.php @@ -200,7 +200,7 @@ class Notify extends BaseRepository $Notify->link, $Notify->parent, $Notify->otype, - $Notify->uid, + $Notify->uid ]; return $this->db->update(self::$table_name, ['seen' => true], $condition); } @@ -261,13 +261,13 @@ class Notify extends BaseRepository } } - $siteurl = (string) $this->baseUrl; + $siteurl = (string)$this->baseUrl; $sitename = $this->config->get('config', 'sitename'); // with $params['show_in_notification_page'] == false, the notification isn't inserted into // the database, and an email is sent if applicable. // default, if not specified: true - $show_in_notification_page = $params['show_in_notification_page'] ?? true; + $show_in_notification_page = isset($params['show_in_notification_page']) ? $params['show_in_notification_page'] : true; $title = $params['item']['title'] ?? ''; $body = $params['item']['body'] ?? ''; @@ -354,7 +354,7 @@ class Notify extends BaseRepository $epreamble = $l10n->t( '%1$s posted to [url=%2$s]your wall[/url]', '[url=' . $params['source_link'] . ']' . $params['source_name'] . '[/url]', - $params['link'], + $params['link'] ); $sitelink = $l10n->t('Please visit %s to view and/or reply to the conversation.'); @@ -371,7 +371,7 @@ class Notify extends BaseRepository $epreamble = $l10n->t( 'You\'ve received [url=%1$s]an introduction[/url] from %2$s.', $itemlink, - '[url=' . $params['source_link'] . ']' . $params['source_name'] . '[/url]', + '[url=' . $params['source_link'] . ']' . $params['source_name'] . '[/url]' ); $body = $l10n->t('You may visit their profile at %s', $params['source_link']); @@ -389,7 +389,7 @@ class Notify extends BaseRepository $epreamble = $l10n->t( '%1$s is your friend at %2$s', '[url=' . $params['source_link'] . ']' . $params['source_name'] . '[/url]', - $sitename, + $sitename ); break; case Activity::FOLLOW: @@ -400,7 +400,7 @@ class Notify extends BaseRepository $epreamble = $l10n->t( 'You have a new follower at %2$s : %1$s', '[url=' . $params['source_link'] . ']' . $params['source_name'] . '[/url]', - $sitename, + $sitename ); break; default: @@ -418,7 +418,7 @@ class Notify extends BaseRepository '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]', + '[url=' . $params['source_link'] . ']' . $params['source_name'] . '[/url]' ); $body = $l10n->t('Name:') . ' ' . $params['item']['name'] . "\n"; @@ -439,7 +439,7 @@ class Notify extends BaseRepository $epreamble = $l10n->t( '%2$s has accepted your [url=%1$s]connection request[/url].', $itemlink, - '[url=' . $params['source_link'] . ']' . $params['source_name'] . '[/url]', + '[url=' . $params['source_link'] . ']' . $params['source_name'] . '[/url]' ); $body = $l10n->t('You are now friends and may exchange status updates, photos, and messages without restriction.'); @@ -455,7 +455,7 @@ class Notify extends BaseRepository $epreamble = $l10n->t( '%2$s has accepted your [url=%1$s]connection request[/url].', $itemlink, - '[url=' . $params['source_link'] . ']' . $params['source_name'] . '[/url]', + '[url=' . $params['source_link'] . ']' . $params['source_name'] . '[/url]' ); $body = $l10n->t('\'%1$s\' has chosen to accept you a fan, which restricts some forms of communication - such as private messaging and some profile interactions. If this is a celebrity or community page, these settings were applied automatically.', $params['source_name']); @@ -478,7 +478,7 @@ class Notify extends BaseRepository $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]', + '[url=' . $params['source_link'] . ']' . $params['source_name'] . '[/url]' ); $body = $l10n->t( @@ -486,7 +486,7 @@ class Notify extends BaseRepository $params['source_name'], $siteurl, $params['source_mail'], - $params['source_nick'], + $params['source_nick'] ); $sitelink = $l10n->t('Please visit %s to approve or reject the request.'); @@ -502,7 +502,7 @@ class Notify extends BaseRepository $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]', + '[url=' . $params['source_link'] . ']' . $params['source_name'] . '[/url]' ); $body = $l10n->t( @@ -510,7 +510,7 @@ class Notify extends BaseRepository $params['source_name'], $siteurl, $params['source_mail'], - $params['source_nick'], + $params['source_nick'] ); $sitelink = $l10n->t('Please visit %s to have a look at the new registration.'); @@ -570,7 +570,7 @@ class Notify extends BaseRepository 'sitelink' => $sitelink, 'tsitelink' => $tsitelink, 'hsitelink' => $hsitelink, - 'itemlink' => $itemlink, + 'itemlink' => $itemlink ]; $hook_data = $this->eventDispatcher->dispatch( @@ -625,7 +625,7 @@ class Notify extends BaseRepository 'notify-id' => $notify_id, 'master-parent-uri-id' => $parent_uri_id, 'receiver-uid' => $params['uid'], - 'parent-item' => 0, + 'parent-item' => 0 ]; DBA::insert('notify-threads', $fields); @@ -676,7 +676,7 @@ class Notify extends BaseRepository $emailBuilder->withPhoto( $hook_data['source_photo'], $hook_data['source_link'] ?? $sitelink, - $hook_data['source_name'] ?? $sitename, + $hook_data['source_name'] ?? $sitename ); } @@ -766,7 +766,7 @@ class Notify extends BaseRepository $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]); @@ -789,7 +789,7 @@ class Notify extends BaseRepository $condition = [ 'type' => [Model\Notification\Type::TAG_SELF, Model\Notification\Type::COMMENT, Model\Notification\Type::SHARE], 'link' => $params['link'], - 'verb' => Activity::POST, + 'verb' => Activity::POST ]; if ($this->existsForUser($Notification->uid, $condition)) { $this->logger->info('Duplicate found, quitting', $condition + ['uid' => $Notification->uid]); @@ -838,7 +838,7 @@ class Notify extends BaseRepository $epreamble = $msg['rich']; $sitename = $this->config->get('config', 'sitename'); - $siteurl = (string) $this->baseUrl; + $siteurl = (string)$this->baseUrl; $sitelink = $l10n->t('Please visit %s to view and/or reply to the conversation.'); $tsitelink = sprintf($sitelink, $siteurl); diff --git a/src/Network/HTTPClient/Client/HttpClient.php b/src/Network/HTTPClient/Client/HttpClient.php index a1dc8bba69..113facc04b 100644 --- a/src/Network/HTTPClient/Client/HttpClient.php +++ b/src/Network/HTTPClient/Client/HttpClient.php @@ -93,7 +93,7 @@ class HttpClient implements ICanSendHttpRequests } } $parts['path'] = implode('/', $parts2); - $url = (string) Uri::fromParts((array) $parts); + $url = (string)Uri::fromParts((array)$parts); if (Network::isUrlBlocked($url)) { $this->logger->info('Domain is blocked.', ['url' => $url]); @@ -147,8 +147,8 @@ class HttpClient implements ICanSendHttpRequests $conf[RequestOptions::ON_HEADERS] = function (ResponseInterface $response) use ($opts) { if ( - !empty($opts[HttpClientOptions::CONTENT_LENGTH]) - && (int) $response->getHeaderLine('Content-Length') > $opts[HttpClientOptions::CONTENT_LENGTH] + !empty($opts[HttpClientOptions::CONTENT_LENGTH]) && + (int)$response->getHeaderLine('Content-Length') > $opts[HttpClientOptions::CONTENT_LENGTH] ) { throw new TransferException('The file is too big!'); } @@ -168,14 +168,14 @@ class HttpClient implements ICanSendHttpRequests return new GuzzleResponse($response, $url); } catch (TransferException $exception) { if ( - $exception instanceof RequestException - && $exception->hasResponse() + $exception instanceof RequestException && + $exception->hasResponse() ) { return new GuzzleResponse($exception->getResponse(), $url, $exception->getCode(), ''); } else { return new CurlResult($this->logger, $url, '', ['http_code' => 500], $exception->getCode(), ''); } - } catch (InvalidArgumentException|\InvalidArgumentException $argumentException) { + } catch (InvalidArgumentException | \InvalidArgumentException $argumentException) { $this->logger->info('Invalid Argument for HTTP call.', ['url' => $url, 'method' => $method, 'exception' => $argumentException]); return new CurlResult($this->logger, $url, '', ['http_code' => 500], $argumentException->getCode(), $argumentException->getMessage()); } finally { @@ -198,7 +198,7 @@ class HttpClient implements ICanSendHttpRequests public function get(string $url, string $accept_content = HttpClientAccept::DEFAULT, array $opts = []): ICanHandleHttpResponses { // In case there is no - $opts[HttpClientOptions::ACCEPT_CONTENT] ??= $accept_content; + $opts[HttpClientOptions::ACCEPT_CONTENT] = $opts[HttpClientOptions::ACCEPT_CONTENT] ?? $accept_content; return $this->request('get', $url, $opts); } @@ -279,7 +279,7 @@ class HttpClient implements ICanSendHttpRequests HttpClientOptions::TIMEOUT => $timeout, HttpClientOptions::COOKIEJAR => $cookiejar, HttpClientOptions::REQUEST => $request, - ], + ] ); return $ret->getBodyString(); } catch (\Throwable $th) { @@ -291,7 +291,7 @@ class HttpClient implements ICanSendHttpRequests private function getUserAgent(string $type = ''): string { // @see https://developers.whatismybrowser.com/learn/browser-detection/user-agents/user-agent-best-practices - $userAgent = App::PLATFORM . '/' . App::VERSION . ' DatabaseVersion/' . DB_UPDATE_VERSION; + $userAgent = App::PLATFORM . '/' . App::VERSION . ' DatabaseVersion/' . DB_UPDATE_VERSION; if ($type != '') { $userAgent .= ' Request/' . $type; } diff --git a/src/Network/HTTPClient/Response/CurlResult.php b/src/Network/HTTPClient/Response/CurlResult.php index f50b0fa637..a38de01336 100644 --- a/src/Network/HTTPClient/Response/CurlResult.php +++ b/src/Network/HTTPClient/Response/CurlResult.php @@ -225,7 +225,7 @@ class CurlResult implements ICanHandleHttpResponses } } - $this->redirectUrl = (string) Uri::fromParts((array) $redirect_parts); + $this->redirectUrl = (string)Uri::fromParts((array)$redirect_parts); $this->isRedirectUrl = true; $this->redirectIsPermanent = $this->returnCode == 301 || $this->returnCode == 308; } else { @@ -266,7 +266,11 @@ class CurlResult implements ICanHandleHttpResponses $headers = $this->getHeaderArray(); - return $headers[$header] ?? []; + if (isset($headers[$header])) { + return $headers[$header]; + } + + return []; } /** {@inheritDoc} */ diff --git a/src/Network/Probe.php b/src/Network/Probe.php index 90a130e7ad..92ee97b816 100644 --- a/src/Network/Probe.php +++ b/src/Network/Probe.php @@ -28,6 +28,7 @@ use Friendica\Protocol\ATProtocol; use Friendica\Protocol\Diaspora; use Friendica\Protocol\Email; use Friendica\Protocol\Feed; +use Friendica\Protocol\Salmon; use Friendica\Util\Crypto; use Friendica\Util\DateTimeFormat; use Friendica\Util\HTTPSignature; @@ -41,8 +42,8 @@ use GuzzleHttp\Psr7\Uri; */ class Probe { - public const HOST_META = '/.well-known/host-meta'; - public const WEBFINGER = '/.well-known/webfinger?resource={uri}'; + const HOST_META = '/.well-known/host-meta'; + const WEBFINGER = '/.well-known/webfinger?resource={uri}'; /** * @var string Base URL @@ -104,7 +105,7 @@ class Probe 'account-type', 'community', 'keywords', 'location', 'about', 'xmpp', 'matrix', 'hide', 'batch', 'notify', 'poll', 'request', 'confirm', 'subscribe', 'poco', 'openwebauth', 'following', 'followers', 'inbox', 'outbox', 'sharedinbox', - 'priority', 'network', 'pubkey', 'manually-approve', 'baseurl', 'gsid', + 'priority', 'network', 'pubkey', 'manually-approve', 'baseurl', 'gsid' ]; $numeric_fields = ['gsid', 'account-type']; @@ -123,9 +124,9 @@ class Probe foreach ($fields as $field) { if (isset($data[$field])) { if (in_array($field, $numeric_fields)) { - $newdata[$field] = (int) $data[$field]; + $newdata[$field] = (int)$data[$field]; } elseif (in_array($field, $boolean_fields)) { - $newdata[$field] = (bool) $data[$field]; + $newdata[$field] = (bool)$data[$field]; } else { $newdata[$field] = trim($data[$field]); } @@ -184,6 +185,161 @@ class Probe return $parts['host'] == $own_host; } + /** + * Probes for webfinger path via "host-meta" + * + * We have to check if the servers in the future still will offer this. + * It seems as if it was dropped from the standard. + * + * @param string $host The host part of an url + * + * @return array with template and type of the webfinger template for JSON or XML + * @throws HTTPException\InternalServerErrorException + */ + private static function hostMeta(string $host): array + { + // Reset the static variable + self::$baseurl = ''; + + // Handles the case when the hostname contains the scheme + if (!parse_url($host, PHP_URL_SCHEME)) { + $ssl_url = 'https://' . $host . self::HOST_META; + $url = 'http://' . $host . self::HOST_META; + } else { + $ssl_url = $host . self::HOST_META; + $url = ''; + } + + $xrd_timeout = DI::config()->get('system', 'xrd_timeout', 20); + + DI::logger()->info('Probing', ['host' => $host, 'ssl_url' => $ssl_url, 'url' => $url]); + $xrd = null; + + try { + $curlResult = DI::httpClient()->get($ssl_url, HttpClientAccept::XRD_XML, [HttpClientOptions::TIMEOUT => $xrd_timeout, HttpClientOptions::REQUEST => HttpClientRequest::CONTACTINFO]); + } catch (\Throwable $th) { + DI::logger()->notice('Got exception', ['code' => $th->getCode(), 'message' => $th->getMessage()]); + return []; + } + + $ssl_connection_error = ($curlResult->getErrorNumber() == CURLE_COULDNT_CONNECT) || ($curlResult->getReturnCode() == 0); + + $host_url = $host; + + if ($curlResult->isSuccess()) { + $xml = $curlResult->getBodyString(); + $xrd = XML::parseString($xml, true); + if (!empty($url)) { + $host_url = 'https://' . $host; + } + } elseif ($curlResult->isTimeout()) { + DI::logger()->info('Probing timeout', ['url' => $ssl_url]); + self::$isTimeout = true; + return []; + } + + if ($ssl_connection_error && !is_object($xrd) && !empty($url)) { + try { + $curlResult = DI::httpClient()->get($url, HttpClientAccept::XRD_XML, [HttpClientOptions::TIMEOUT => $xrd_timeout, HttpClientOptions::REQUEST => HttpClientRequest::CONTACTINFO]); + } catch (\Throwable $th) { + DI::logger()->notice('Got exception', ['code' => $th->getCode(), 'message' => $th->getMessage()]); + return []; + } + $connection_error = ($curlResult->getErrorNumber() == CURLE_COULDNT_CONNECT) || ($curlResult->getReturnCode() == 0); + if ($curlResult->isTimeout()) { + DI::logger()->info('Probing timeout', ['url' => $url]); + self::$isTimeout = true; + return []; + } elseif ($connection_error && $ssl_connection_error) { + self::$isTimeout = true; + return []; + } + + $xml = $curlResult->getBodyString(); + $xrd = XML::parseString($xml, true); + $host_url = 'http://' . $host; + } + if (!is_object($xrd)) { + DI::logger()->info('No xrd object found', ['host' => $host]); + return []; + } + + $links = XML::elementToArray($xrd); + if (!isset($links['xrd']['link'])) { + DI::logger()->info('No xrd data found', ['host' => $host]); + return []; + } + + $lrdd = []; + + foreach ($links['xrd']['link'] as $value => $link) { + if (!empty($link['@attributes'])) { + $attributes = $link['@attributes']; + } elseif ($value == '@attributes') { + $attributes = $link; + } else { + continue; + } + + if (!empty($attributes['rel']) && $attributes['rel'] == 'lrdd' && !empty($attributes['template'])) { + $type = (empty($attributes['type']) ? '' : $attributes['type']); + + $lrdd[$type] = $attributes['template']; + } + } + + if (Network::isUrlBlocked($host_url)) { + DI::logger()->info('Domain is blocked', ['url' => $host]); + return []; + } + + self::$baseurl = $host_url; + + DI::logger()->info('Probing successful', ['host' => $host]); + + return $lrdd; + } + + /** + * Check an URI for LRDD data + * + * @param string $uri Address that should be probed + * @return array uri data + * @throws HTTPException\InternalServerErrorException + */ + public static function lrdd(string $uri): array + { + $data = self::getWebfingerArray($uri); + if (empty($data)) { + return []; + } + $webfinger = $data['webfinger']; + + if (empty($webfinger['links'])) { + DI::logger()->info('No webfinger links found', ['uri' => $uri]); + return []; + } + + $data = []; + + foreach ($webfinger['links'] as $link) { + $data[] = ['@attributes' => $link]; + } + + if (!empty($webfinger['aliases']) && is_array($webfinger['aliases'])) { + foreach ($webfinger['aliases'] as $alias) { + $data[] = [ + '@attributes' => [ + 'rel' => 'alias', + 'href' => $alias, + ] + ]; + } + } + + return $data; + } + /** * Fetch information (protocol endpoints and user information) about a given uri * @@ -227,12 +383,12 @@ class Probe unset($data['networks']); if (!empty($data['network'])) { $networks[$data['network']] = $data; - $ap_profile['guid'] ??= $data['guid'] ?? null; - $ap_profile['about'] ??= $data['about'] ?? null; - $ap_profile['keywords'] = $data['keywords'] ?? null; - $ap_profile['location'] = $data['location'] ?? null; - $ap_profile['poco'] = $data['poco'] ?? null; - $ap_profile['openwebauth'] = $data['openwebauth'] ?? null; + $ap_profile['guid'] = $ap_profile['guid'] ?? $data['guid'] ?? null; + $ap_profile['about'] = $ap_profile['about'] ?? $data['about'] ?? null; + $ap_profile['keywords'] = $data['keywords'] ?? null; + $ap_profile['location'] = $data['location'] ?? null; + $ap_profile['poco'] = $data['poco'] ?? null; + $ap_profile['openwebauth'] = $data['openwebauth'] ?? null; } $data = $ap_profile; $data['networks'] = $networks; @@ -397,6 +553,7 @@ class Probe public static function getWebfingerArray(string $uri): array { $parts = parse_url($uri); + $lrdd = []; if (!empty($parts['scheme']) && !empty($parts['host'])) { $host = $parts['host']; @@ -419,9 +576,12 @@ class Probe } $webfinger = self::getWebfinger($parts['scheme'] . '://' . $host . self::WEBFINGER, HttpClientAccept::JRD_JSON, $uri, $addr); + if (empty($webfinger) && !is_null($webfinger)) { + $lrdd = self::hostMeta($host); + } - if (empty($webfinger)) { - while (empty($webfinger) && (count($path_parts) > 1)) { + if (empty($webfinger) && empty($lrdd)) { + while (empty($lrdd) && empty($webfinger) && (count($path_parts) > 1)) { $host .= '/' . array_shift($path_parts); $baseurl = $parts['scheme'] . '://' . $host; @@ -430,9 +590,12 @@ class Probe } $webfinger = self::getWebfinger($parts['scheme'] . '://' . $host . self::WEBFINGER, HttpClientAccept::JRD_JSON, $uri, $addr); + if (empty($webfinger) && !is_null($webfinger)) { + $lrdd = self::hostMeta($host); + } } - if (empty($webfinger)) { + if (empty($lrdd) && empty($webfinger)) { return []; } } @@ -460,6 +623,10 @@ class Probe } if (empty($webfinger)) { + $lrdd = self::hostMeta($host); + if (self::$isTimeout) { + return []; + } $baseurl = self::$baseurl; } } else { @@ -467,6 +634,16 @@ class Probe return []; } + if (empty($webfinger)) { + foreach ($lrdd as $type => $template) { + if ($webfinger) { + continue; + } + + $webfinger = self::getWebfinger($template, $type, $uri, $addr); + } + } + if (empty($webfinger)) { return []; } @@ -609,6 +786,12 @@ class Probe } else { $result['networks'][Protocol::DIASPORA] = self::diaspora($webfinger); } + if ((!$result && ($network == '')) || ($network == Protocol::OSTATUS)) { + $result = self::ostatus($webfinger); + } + if (in_array($network, ['', Protocol::ZOT])) { + $result = self::zot($webfinger, $result); + } if (empty($result['network']) && empty($ap_profile['network']) || ($network == Protocol::FEED)) { $result = self::feed($uri); } else { @@ -642,6 +825,177 @@ class Probe return $result; } + /** + * Check for Zot contact + * + * @param array $webfinger Webfinger data + * @param array $data previously probed data + * + * @return array Zot data + * @throws HTTPException\InternalServerErrorException + */ + private static function zot(array $webfinger, array $data): array + { + $zot_url = ''; + + foreach ($webfinger['links'] as $link) { + if (($link['rel'] == 'http://purl.org/zot/protocol/6.0') && !empty($link['href'])) { + $zot_url = $link['href']; + } + } + + if ($zot_url === '') { + return $data; + } + + foreach ($webfinger['aliases'] as $alias) { + if (substr($alias, 0, 5) == 'acct:') { + $data['addr'] = substr($alias, 5); + } + } + + if (!empty($webfinger['subject']) && (substr($webfinger['subject'], 0, 5) == 'acct:')) { + $data['addr'] = substr($webfinger['subject'], 5); + } + + if (!empty($webfinger['properties'])) { + if (!empty($webfinger['properties']['http://webfinger.net/ns/name'])) { + $data['name'] = $webfinger['properties']['http://webfinger.net/ns/name']; + } + if (!empty($webfinger['properties']['http://xmlns.com/foaf/0.1/name'])) { + $data['name'] = $webfinger['properties']['http://xmlns.com/foaf/0.1/name']; + } + if (!empty($webfinger['properties']['https://w3id.org/security/v1#publicKeyPem'])) { + $data['pubkey'] = $webfinger['properties']['https://w3id.org/security/v1#publicKeyPem']; + } + + if (empty($data['network']) && !empty($webfinger['properties']['http://purl.org/zot/federation'])) { + $networks = explode(',', $webfinger['properties']['http://purl.org/zot/federation']); + if (in_array('zot6', $networks)) { + $data['network'] = Protocol::ZOT; + } + } + } + + foreach ($webfinger['links'] as $link) { + if (($link['rel'] == ActivityNamespace::WEBFINGERAVATAR) && !empty($link['href'])) { + $data['photo'] = $link['href']; + } elseif (($link['rel'] == 'http://openid.net/specs/connect/1.0/issuer') && !empty($link['href'])) { + $data['baseurl'] = trim($link['href'], '/'); + } elseif (($link['rel'] == 'http://webfinger.net/rel/blog') && !empty($link['href'])) { + $data['url'] = $link['href']; + } + } + + $data = self::pollZot($zot_url, $data); + + if (!empty($data['url']) && !empty($webfinger['aliases']) && is_array($webfinger['aliases'])) { + foreach ($webfinger['aliases'] as $alias) { + if (Network::isValidHttpUrl($alias) && !Strings::compareLink($alias, $data['url'])) { + $data['alias'] = $alias; + } + } + } + + return $data; + } + + private static function pollZot(string $url, array $data): array + { + try { + $curlResult = DI::httpClient()->get($url, 'application/x-zot+json', [HttpClientOptions::REQUEST => HttpClientRequest::CONTACTINFO]); + } catch (\Throwable $th) { + DI::logger()->notice('Got exception', ['code' => $th->getCode(), 'message' => $th->getMessage()]); + return $data; + } + if ($curlResult->isTimeout()) { + return $data; + } + $content = $curlResult->getBodyString(); + if (!$content) { + return $data; + } + + $json = json_decode($content, true); + if (!is_array($json)) { + return $data; + } + + if (empty($data['network'])) { + if (!empty($json['protocols']) && in_array('zot6', $json['protocols'])) { + $data['network'] = Protocol::ZOT; + } + } + + if (!empty($json['public_key'])) { + $data['pubkey'] = $json['public_key']; + } + if (!empty($json['name'])) { + $data['name'] = $json['name']; + } + if (!empty($json['username'])) { + $data['nick'] = $json['username']; + } + if (!empty($json['photo']) && !empty($json['photo']['url'])) { + $data['photo'] = $json['photo']['url']; + } + if (!empty($json['locations'])) { + foreach ($json['locations'] as $location) { + if ($location['deleted'] || (parse_url($url, PHP_URL_HOST) != $location['host'])) { + continue; + } + if (!empty($location['address'])) { + $data['addr'] = $location['address']; + } + if (!empty($location['id_url'])) { + $data['url'] = $location['id_url']; + } + if (!empty($location['callback'])) { + $data['confirm'] = $location['callback']; + } + } + } + if (!empty($json['primary_location']) && !empty($json['primary_location']['connections_url'])) { + $data['poco'] = $json['primary_location']['connections_url']; + } + if (isset($json['searchable'])) { + $data['hide'] = !$json['searchable']; + } + if (!empty($json['public_forum'])) { + $data['community'] = $json['public_forum']; + $data['account-type'] = User::ACCOUNT_TYPE_COMMUNITY; + } elseif (($json['channel_type'] ?? '') == 'normal') { + $data['account-type'] = User::ACCOUNT_TYPE_PERSON; + } + + if (!empty($json['profile'])) { + $profile = $json['profile']; + if (!empty($profile['description'])) { + $data['about'] = $profile['description']; + } + if (!empty($profile['keywords'])) { + $keywords = implode(', ', $profile['keywords']); + if (!empty($keywords)) { + $data['keywords'] = $keywords; + } + } + + $loc = []; + if (!empty($profile['region'])) { + $loc['region'] = $profile['region']; + } + if (!empty($profile['country'])) { + $loc['country-name'] = $profile['country']; + } + $location = Profile::formatLocation($loc); + if (!empty($location)) { + $data['location'] = $location; + } + } + + return $data; + } + /** * Perform a webfinger request. * @@ -659,7 +1013,7 @@ class Probe $curlResult = DI::httpClient()->get( $url, $type, - [HttpClientOptions::TIMEOUT => DI::config()->get('system', 'xrd_timeout', 20), HttpClientOptions::REQUEST => HttpClientRequest::CONTACTINFO], + [HttpClientOptions::TIMEOUT => DI::config()->get('system', 'xrd_timeout', 20), HttpClientOptions::REQUEST => HttpClientRequest::CONTACTINFO] ); } catch (\Throwable $e) { DI::logger()->notice($e->getMessage(), ['url' => $url, 'type' => $type, 'class' => get_class($e)]); @@ -817,7 +1171,7 @@ class Probe } if (isset($json['hide'])) { - $data['hide'] = (bool) $json['hide']; + $data['hide'] = (bool)$json['hide']; } else { $data['hide'] = false; } @@ -855,6 +1209,12 @@ class Probe $data['baseurl'] = trim($link['href'], '/'); } elseif (($link['rel'] == ActivityNamespace::DIASPORA_GUID) && !empty($link['href'])) { $data['guid'] = $link['href']; + } elseif (($link['rel'] == 'diaspora-public-key') && !empty($link['href'])) { + $data['pubkey'] = base64_decode($link['href']); + + if (strstr($data['pubkey'], 'RSA ')) { + $data['pubkey'] = Crypto::rsaToPem($data['pubkey']); + } } } @@ -1029,17 +1389,31 @@ class Probe $hcard_url = $link['href']; } elseif (($link['rel'] == ActivityNamespace::DIASPORA_SEED) && !empty($link['href'])) { $data['baseurl'] = trim($link['href'], '/'); - } elseif (($link['rel'] == ActivityNamespace::WEBFINGERPROFILE) && !empty($link['href'])) { + } elseif (($link['rel'] == ActivityNamespace::DIASPORA_GUID) && !empty($link['href'])) { + $data['guid'] = $link['href']; + } elseif (($link['rel'] == ActivityNamespace::WEBFINGERPROFILE) && (($link['type'] ?? '') == 'text/html') && !empty($link['href'])) { $data['url'] = $link['href']; + } elseif (($link['rel'] == ActivityNamespace::WEBFINGERPROFILE) && empty($link['type']) && !empty($link['href'])) { + $profile_url = $link['href']; } elseif (($link['rel'] == ActivityNamespace::FEED) && !empty($link['href'])) { $data['poll'] = $link['href']; } elseif (($link['rel'] == ActivityNamespace::POCO) && !empty($link['href'])) { $data['poco'] = $link['href']; } elseif (($link['rel'] == 'salmon') && !empty($link['href'])) { $data['notify'] = $link['href']; + } elseif (($link['rel'] == 'diaspora-public-key') && !empty($link['href'])) { + $data['pubkey'] = base64_decode($link['href']); + + if (strstr($data['pubkey'], 'RSA ')) { + $data['pubkey'] = Crypto::rsaToPem($data['pubkey']); + } } } + if (empty($data['url']) && !empty($profile_url)) { + $data['url'] = $profile_url; + } + if (empty($data['url']) || empty($hcard_url)) { return []; } @@ -1090,6 +1464,138 @@ class Probe return $data; } + /** + * Check for OStatus contact + * + * @param array $webfinger Webfinger data + * @param bool $short Short detection mode + * + * @return array|bool OStatus data or "false" on error or "true" on short mode + * @throws HTTPException\InternalServerErrorException + */ + private static function ostatus(array $webfinger, bool $short = false) + { + $data = []; + + if (!empty($webfinger['aliases']) && is_array($webfinger['aliases'])) { + foreach ($webfinger['aliases'] as $alias) { + if (strstr($alias, '@') && !Network::isValidHttpUrl($alias)) { + $data['addr'] = str_replace('acct:', '', $alias); + } + } + } + + if ( + !empty($webfinger['subject']) && strstr($webfinger['subject'], '@') + && !Network::isValidHttpUrl($webfinger['subject']) + ) { + $data['addr'] = str_replace('acct:', '', $webfinger['subject']); + } + + if (!empty($webfinger['links'])) { + // The array is reversed to take into account the order of preference for same-rel links + // See: https://tools.ietf.org/html/rfc7033#section-4.4.4 + foreach (array_reverse($webfinger['links']) as $link) { + if (($link['rel'] == ActivityNamespace::WEBFINGERPROFILE) + && (($link['type'] ?? '') == 'text/html') + && ($link['href'] != '') + ) { + $data['url'] = $data['alias'] = $link['href']; + } elseif (($link['rel'] == 'salmon') && !empty($link['href'])) { + $data['notify'] = $link['href']; + } elseif (($link['rel'] == ActivityNamespace::FEED) && !empty($link['href'])) { + $data['poll'] = $link['href']; + } elseif (($link['rel'] == 'magic-public-key') && !empty($link['href'])) { + $pubkey = $link['href']; + + if (substr($pubkey, 0, 5) === 'data:') { + if (strstr($pubkey, ',')) { + $pubkey = substr($pubkey, strpos($pubkey, ',') + 1); + } else { + $pubkey = substr($pubkey, 5); + } + } elseif (Strings::normaliseLink($pubkey) == 'http://') { + $curlResult = DI::httpClient()->get($pubkey, HttpClientAccept::MAGIC_KEY, [HttpClientOptions::REQUEST => HttpClientRequest::CONTACTINFO]); + if ($curlResult->isTimeout()) { + self::$isTimeout = true; + return $short ? false : []; + } + DI::logger()->debug('Fetched public key', ['Content-Type' => $curlResult->getHeader('Content-Type'), 'url' => $pubkey]); + $pubkey = $curlResult->getBodyString(); + } + + try { + $data['pubkey'] = Salmon::magicKeyToPem($pubkey); + } catch (\Throwable $e) { + } + } + } + } + + if ( + isset($data['notify']) && isset($data['pubkey']) + && isset($data['poll']) + && isset($data['url']) + ) { + $data['network'] = Protocol::OSTATUS; + $data['manually-approve'] = false; + } else { + return $short ? false : []; + } + + if ($short) { + return true; + } + + // Fetch all additional data from the feed + try { + $curlResult = DI::httpClient()->get($data['poll'], HttpClientAccept::FEED_XML, [HttpClientOptions::REQUEST => HttpClientRequest::CONTACTINFO]); + } catch (\Throwable $th) { + DI::logger()->notice('Got exception', ['code' => $th->getCode(), 'message' => $th->getMessage()]); + return []; + } + if ($curlResult->isTimeout()) { + self::$isTimeout = true; + return []; + } + $feed = $curlResult->getBodyString(); + $feed_data = Feed::import($feed); + if (!$feed_data) { + return []; + } + + if (!empty($feed_data['header']['author-name'])) { + $data['name'] = $feed_data['header']['author-name']; + } + if (!empty($feed_data['header']['author-nick'])) { + $data['nick'] = $feed_data['header']['author-nick']; + } + if (!empty($feed_data['header']['author-avatar'])) { + $data['photo'] = self::fixAvatar($feed_data['header']['author-avatar'], $data['url']); + } + if (!empty($feed_data['header']['author-id'])) { + $data['alias'] = $feed_data['header']['author-id']; + } + if (!empty($feed_data['header']['author-location'])) { + $data['location'] = $feed_data['header']['author-location']; + } + if (!empty($feed_data['header']['author-about'])) { + $data['about'] = $feed_data['header']['author-about']; + } + // OStatus has serious issues when the url doesn't fit (ssl vs. non ssl) + // So we take the value that we just fetched, although the other one worked as well + if (!empty($feed_data['header']['author-link'])) { + $data['url'] = $feed_data['header']['author-link']; + } + + if ($data['url'] == $data['alias']) { + $data['alias'] = ''; + } + + /// @todo Fetch location and "about" from the feed as well + return $data; + } + /** * Checks HTML page for RSS feed link * @@ -1185,7 +1691,7 @@ class Probe unset($baseParts['query']); unset($baseParts['fragment']); - return (string) Uri::fromParts((array) (array) $baseParts); + return (string)Uri::fromParts((array)(array)$baseParts); } /** @@ -1424,7 +1930,7 @@ class Probe * @return string fixed avatar path * @throws \Exception */ - private static function fixAvatar(string $avatar, string $base): string + public static function fixAvatar(string $avatar, string $base): string { $base_parts = parse_url($base); @@ -1440,9 +1946,9 @@ class Probe // And put them together again $scheme = isset($parts['scheme']) ? $parts['scheme'] . '://' : ''; - $host = $parts['host'] ?? ''; + $host = isset($parts['host']) ? $parts['host'] : ''; $port = isset($parts['port']) ? ':' . $parts['port'] : ''; - $path = $parts['path'] ?? ''; + $path = isset($parts['path']) ? $parts['path'] : ''; $query = isset($parts['query']) ? '?' . $parts['query'] : ''; $fragment = isset($parts['fragment']) ? '#' . $parts['fragment'] : ''; @@ -1501,7 +2007,7 @@ class Probe // Check the 'noscrape' endpoint when it is a Friendica server $gserver = DBA::selectFirst('gserver', ['noscrape'], [ "`nurl` = ? AND `noscrape` != ''", - Strings::normaliseLink($data['baseurl']), + Strings::normaliseLink($data['baseurl']) ]); if (!DBA::isResult($gserver)) { return ''; @@ -1697,14 +2203,14 @@ class Probe 'poco' => $owner['poco'], 'network' => Protocol::DIASPORA, 'pubkey' => $owner['upubkey'], - ], - ], + ] + ] ]; } catch (Exception $e) { // Default values for nonexistent targets $data = [ 'name' => $url, 'nick' => $url, 'url' => $url, 'network' => Protocol::PHANTOM, - 'photo' => DI::baseUrl() . Contact::DEFAULT_AVATAR_PHOTO, + 'photo' => DI::baseUrl() . Contact::DEFAULT_AVATAR_PHOTO ]; } diff --git a/src/Network/RobotsTxt.php b/src/Network/RobotsTxt.php deleted file mode 100644 index 999148039c..0000000000 --- a/src/Network/RobotsTxt.php +++ /dev/null @@ -1,193 +0,0 @@ -httpClient = $httpClient; - } - - /** - * Loads the RobotsTxt parser with a server URL - * - * @param string $url The base URL of the server - * @return bool True if robots.txt was successfully loaded, false otherwise - */ - public function load(string $url): bool - { - $this->url = rtrim($url, '/'); - $this->disallowRules = []; - $this->allowRules = []; - $this->isLoaded = false; - - $robotsUrl = $this->url . '/robots.txt'; - - try { - $curlResult = $this->httpClient->get( - $robotsUrl, - HttpClientAccept::TEXT, - [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO] - ); - - if (!$curlResult->isSuccess()) { - return false; - } - - if (empty($curlResult->getBodyString())) { - return false; - } - - $this->isLoaded = $this->parseRobotsTxt($curlResult->getBodyString()); - return $this->isLoaded; - } catch (\Exception $e) { - return false; - } - } - - /** - * Check if the robots.txt has been loaded successfully - * - * @return bool True if loaded, false otherwise - */ - public function isLoaded(): bool - { - return $this->isLoaded; - } - - /** - * Parse the robots.txt content and extract rules for user-agent * - * - * @param string $content The content of robots.txt - * @return bool True if parsing was successful - */ - private function parseRobotsTxt(string $content): bool - { - $lines = explode("\n", $content); - - $isRelevantSection = false; - - foreach ($lines as $line) { - $line = preg_replace('~\s*#.*$~', '', $line); - $line = trim($line); - - if (empty($line)) { - continue; - } - - if (stripos($line, 'User-Agent:') === 0) { - $agent = trim(substr($line, strlen('User-Agent:'))); - - $isRelevantSection = ($agent === '*'); - continue; - } - - if (!$isRelevantSection) { - continue; - } - - if (stripos($line, 'Disallow:') === 0) { - $path = trim(substr($line, strlen('Disallow:'))); - if (!empty($path)) { - $this->disallowRules[] = $path; - } - continue; - } - - if (stripos($line, 'Allow:') === 0) { - $path = trim(substr($line, strlen('Allow:'))); - if (!empty($path)) { - $this->allowRules[] = $path; - } - continue; - } - - if (stripos($line, 'User-Agent:') === 0 && !$isRelevantSection) { - break; - } - } - - return sizeof($this->disallowRules) > 0 || sizeof($this->allowRules) > 0; - } - - /** - * Check if a given path is allowed according to robots.txt rules - * - * @param string $path The path to check (e.g., "/api/v1/accounts") - * @return bool True if the path is allowed, false otherwise - */ - public function isAllowed(string $path): bool - { - $path = '/' . ltrim(parse_url($path, PHP_URL_PATH), '/'); - $allowed = true; - $length = 0; - - foreach ($this->allowRules as $rule) { - if (strlen($rule) > $length && $this->pathMatches($path, $rule)) { - $length = strlen($rule); - } - } - - foreach ($this->disallowRules as $rule) { - if (strlen($rule) > $length && $this->pathMatches($path, $rule)) { - $allowed = false; - } - } - - return $allowed; - } - - /** - * Check if a path matches a robots.txt rule - * Rules use prefix matching: /admin will block /admin, /admin/, /admin/page, etc. - * - * @param string $path The path to check - * @param string $rule The robots.txt rule - * @return bool True if the path matches the rule - */ - private function pathMatches(string $path, string $rule): bool - { - if (substr_compare($rule, '*', -strlen('*')) === 0) { - $rule = substr($rule, 0, -1); - return strncmp($path, $rule, strlen($rule)) === 0; - } - - return strncmp($path, $rule, strlen($rule)) === 0; - } -} diff --git a/src/Object/Api/Mastodon/Status.php b/src/Object/Api/Mastodon/Status.php index 742f43ab84..652e6dde4d 100644 --- a/src/Object/Api/Mastodon/Status.php +++ b/src/Object/Api/Mastodon/Status.php @@ -98,14 +98,14 @@ class Status extends BaseDataTransferObject public function __construct(array $item, Account $account, Counts $counts, UserAttributes $userAttributes, bool $sensitive, Application $application, array $mentions, array $tags, Card $card, array $attachments, array $in_reply, array $reblog, FriendicaExtension $friendica, array $quote = null, array $poll = null, array $emojis = null) { $reblogged = !empty($reblog); - $this->id = (string) $item['uri-id']; + $this->id = (string)$item['uri-id']; $this->created_at = DateTimeFormat::utc($item['created'], DateTimeFormat::JSON); $this->edited_at = DateTimeFormat::utc($item['edited'], DateTimeFormat::JSON); if ($item['gravity'] == Item::GRAVITY_COMMENT) { - $this->in_reply_to_id = (string) $item['thr-parent-id']; + $this->in_reply_to_id = (string)$item['thr-parent-id']; $this->in_reply_to_status = $in_reply; - $this->in_reply_to_account_id = (string) $item['parent-author-id']; + $this->in_reply_to_account_id = (string)$item['parent-author-id']; } $this->sensitive = $sensitive; @@ -116,7 +116,8 @@ class Status extends BaseDataTransferObject $languages = json_decode($item['language'] ?? '', true); if (is_array($languages)) { - $this->language = array_key_first($languages); + reset($languages); + $this->language = key($languages); } else { $this->language = null; } diff --git a/src/Object/Image.php b/src/Object/Image.php index 8d598e8ccb..c2db564bf8 100644 --- a/src/Object/Image.php +++ b/src/Object/Image.php @@ -473,7 +473,7 @@ class Image return; } - $ort = $exif['IFD0']['Orientation'] ?? 1; + $ort = isset($exif['IFD0']['Orientation']) ? $exif['IFD0']['Orientation'] : 1; switch ($ort) { case 1: // nothing @@ -840,7 +840,7 @@ class Image for ($y = 0; $y < $scaled['height']; ++$y) { for ($x = 0; $x < $scaled['width']; ++$x) { - [$r, $g, $b] = $pixels[$y][$x]; + list($r, $g, $b) = $pixels[$y][$x]; if ($draw !== null) { $draw->setFillColor("rgb($r, $g, $b)"); $draw->point($x, $y); diff --git a/src/Object/Log/ParsedLogLine.php b/src/Object/Log/ParsedLogLine.php index 87f7981bf5..8e552008a4 100644 --- a/src/Object/Log/ParsedLogLine.php +++ b/src/Object/Log/ParsedLogLine.php @@ -12,7 +12,7 @@ namespace Friendica\Object\Log; */ class ParsedLogLine { - public const REGEXP = '/^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}[^ ]*) (\w+) \[(\w*)\]: (.*)/'; + const REGEXP = '/^(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}[^ ]*) (\w+) \[(\w*)\]: (.*)/'; /** @var int */ public $id = 0; @@ -62,13 +62,13 @@ class ParsedLogLine $jsonsource = null; } else { // here we hope that there will not be the string ' - {' inside the $jsonsource value - [$logline, $jsonsource] = explode(' - {', $logline); - $jsonsource = '{' . $jsonsource; + list($logline, $jsonsource) = explode(' - {', $logline); + $jsonsource = '{' . $jsonsource; } $jsondata = null; if (strpos($logline, '{"') > 0) { - [$logline, $jsondata] = explode('{"', $logline, 2); + list($logline, $jsondata) = explode('{"', $logline, 2); $jsondata = '{"' . $jsondata; } diff --git a/src/Object/Post.php b/src/Object/Post.php index f462c58fd0..1a4e1f57de 100644 --- a/src/Object/Post.php +++ b/src/Object/Post.php @@ -23,6 +23,7 @@ use Friendica\Protocol\Activity; use Friendica\Util\Crypto; use Friendica\Util\DateTimeFormat; use Friendica\Util\Strings; +use Friendica\Util\Temporal; use GuzzleHttp\Psr7\Uri; use InvalidArgumentException; @@ -35,7 +36,7 @@ class Post private $template = null; private $available_templates = [ 'wall' => 'wall_thread.tpl', - 'wall2wall' => 'wallwall_thread.tpl', + 'wall2wall' => 'wallwall_thread.tpl' ]; private $comment_box_template = 'comment_item.tpl'; private $toplevel = false; @@ -79,7 +80,7 @@ class Post 'id' => $this->getDataValue('author-id'), 'network' => $this->getDataValue('author-network'), 'url' => $this->getDataValue('author-link'), - 'alias' => $this->getDataValue('author-alias'), + 'alias' => $this->getDataValue('author-alias') ]; $this->redirect_url = Contact::magicLinkByContact($author); if (!$this->isToplevel()) { @@ -164,8 +165,8 @@ class Post if (strtotime($item['edited']) - strtotime($item['created']) > 1) { $edited = [ 'label' => DI::l10n()->t('This entry was edited'), - 'date' => DI::l10n()->fullDateTime($item['edited']), - 'relative' => DI::l10n()->relativeDateTime($item['edited']), + 'date' => DateTimeFormat::local($item['edited'], 'r'), + 'relative' => Temporal::getRelativeDate($item['edited']), ]; } $sparkle = ''; @@ -222,10 +223,6 @@ class Post $edpost = false; if (DI::userSession()->getLocalUserId()) { - if ($commentable && Contact\User::isIsBlocked($item['author-id'], DI::userSession()->getLocalUserId())) { - $commentable = false; - } - if (Strings::compareLink(DI::session()->get('my_url'), $item['author-link'])) { if ($item['event-id'] != 0) { $edpost = ['calendar/event/edit/' . $item['event-id'], DI::l10n()->t('Edit')]; @@ -461,7 +458,7 @@ class Post $body_html = Item::prepareBody($item, true); - [$categories, $folders] = DI::contentItem()->determineCategoriesTerms($item, DI::userSession()->getLocalUserId()); + list($categories, $folders) = DI::contentItem()->determineCategoriesTerms($item, DI::userSession()->getLocalUserId()); $hide_dislike = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'system', 'hide_dislike'); if ($hide_dislike) { @@ -484,8 +481,8 @@ class Post $tags = Tag::populateFromItem($item); - $ago = DI::l10n()->relativeDateTime($item['created']); - $ago_received = DI::l10n()->relativeDateTime($item['received']); + $ago = Temporal::getRelativeDate($item['created']); + $ago_received = Temporal::getRelativeDate($item['received']); if (DI::config()->get('system', 'show_received') && (abs(strtotime($item['created']) - strtotime($item['received'])) > DI::config()->get('system', 'show_received_seconds')) && ($ago != $ago_received)) { $ago = DI::l10n()->t('%s (Received %s)', $ago, $ago_received); } @@ -494,7 +491,7 @@ class Post if (!DI::userSession()->getLocalUserId() && ($item['network'] != Protocol::DIASPORA) && !empty(DI::session()->get('remote_comment'))) { $remote_comment = [ DI::l10n()->t('Comment this item on your system'), DI::l10n()->t('Remote comment'), - str_replace('{uri}', urlencode($item['uri']), DI::session()->get('remote_comment')), + str_replace('{uri}', urlencode($item['uri']), DI::session()->get('remote_comment')) ]; // Ensure to either display the remote comment or the local activities @@ -566,7 +563,7 @@ class Post 'sparkle' => $sparkle, 'title' => $item['title'], 'summary' => $item['content-warning'], - 'localtime' => DI::l10n()->fullDateTime($item['created']), + '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'], @@ -814,8 +811,8 @@ class Post DI::logger()->warning('Post object does not belong to local user', ['post' => $item, 'local_user' => DI::userSession()->getLocalUserId()]); return false; } elseif ( - DI::activity()->match($item->getDataValue('verb'), Activity::LIKE) - || DI::activity()->match($item->getDataValue('verb'), Activity::DISLIKE) + DI::activity()->match($item->getDataValue('verb'), Activity::LIKE) || + DI::activity()->match($item->getDataValue('verb'), Activity::DISLIKE) ) { DI::logger()->warning('Post objects is a like/dislike', ['post' => $item]); return false; @@ -1099,8 +1096,8 @@ class Post $profile = Contact::getByURL($term['url'], false, ['addr', 'contact-type']); if ( - !empty($profile['addr']) && (($profile['contact-type'] ?? Contact::TYPE_UNKNOWN) != Contact::TYPE_COMMUNITY) - && ($profile['addr'] != $owner['addr']) && !strstr($text, (string) $profile['addr']) + !empty($profile['addr']) && (($profile['contact-type'] ?? Contact::TYPE_UNKNOWN) != Contact::TYPE_COMMUNITY) && + ($profile['addr'] != $owner['addr']) && !strstr($text, $profile['addr']) ) { $text .= '@' . $profile['addr'] . ' '; } @@ -1178,7 +1175,7 @@ class Post '$prompttext' => DI::l10n()->t('Please enter a image/video/audio/webpage URL:'), '$preview' => DI::l10n()->t('Preview'), '$indent' => $indent, - '$rand_num' => Crypto::randomDigits(12), + '$rand_num' => Crypto::randomDigits(12) ]); } diff --git a/src/Protocol/ATProtocol/Jetstream.php b/src/Protocol/ATProtocol/Jetstream.php index 487806dad1..d37b8b3720 100755 --- a/src/Protocol/ATProtocol/Jetstream.php +++ b/src/Protocol/ATProtocol/Jetstream.php @@ -155,9 +155,6 @@ class Jetstream $this->logger->error('Error while trying to receive a message', ['code' => $e->getCode(), 'message' => $e->getMessage(), 'file' => $e->getFile(), 'line' => $e->getLine()]); break; } - } catch (\Exception $e) { - $this->logger->error('General error while trying to receive a message', ['capped' => $this->capped, 'code' => $e->getCode(), 'message' => $e->getMessage(), 'file' => $e->getFile(), 'line' => $e->getLine()]); - break; } $last_timeout = time(); } diff --git a/src/Protocol/ATProtocol/Processor.php b/src/Protocol/ATProtocol/Processor.php index 4f5f4f05bc..e073057358 100755 --- a/src/Protocol/ATProtocol/Processor.php +++ b/src/Protocol/ATProtocol/Processor.php @@ -61,7 +61,7 @@ class Processor $fields = [ 'archive' => !$data->account->active, 'failed' => !$data->account->active, - 'updated' => DateTimeFormat::utc($data->account->time, DateTimeFormat::MYSQL), + 'updated' => DateTimeFormat::utc($data->account->time, DateTimeFormat::MYSQL) ]; $this->logger->notice('Process account', ['did' => $data->identity->did, 'fields' => $fields]); @@ -71,11 +71,6 @@ class Processor public function processIdentity(stdClass $data) { - if (!isset($data->identity->handle)) { - $this->logger->warning('No handle provided', ['data' => $data]); - return; - } - $fields = [ 'alias' => ATProtocol::WEB . '/profile/' . $data->identity->did, 'nick' => $data->identity->handle, diff --git a/src/Protocol/ActivityPub/Processor.php b/src/Protocol/ActivityPub/Processor.php index 58e21dc759..63d5ec1c28 100644 --- a/src/Protocol/ActivityPub/Processor.php +++ b/src/Protocol/ActivityPub/Processor.php @@ -38,7 +38,6 @@ use Friendica\Util\HTTPSignature; use Friendica\Util\JsonLD; use Friendica\Util\Network; use Friendica\Util\Strings; -use Friendica\Worker\FetchMissingActivity; /** * ActivityPub Processor Protocol class @@ -230,7 +229,7 @@ class Processor */ public static function updateItem(array $activity) { - $item = Post::selectFirst(['uri', 'uri-id', 'guid', 'thr-parent', 'gravity', 'post-type', 'private', 'author-id', 'owner-id', 'author-link', 'owner-link', 'parent-uri-id', 'thr-parent-id', 'replies'], ['uri' => $activity['id']]); + $item = Post::selectFirst(['uri', 'uri-id', 'guid', 'thr-parent', 'gravity', 'post-type', 'private'], ['uri' => $activity['id']]); if (!DBA::isResult($item)) { DI::logger()->notice('No existing item, item will be created', ['uri' => $activity['id']]); $item = self::createItem($activity, false); @@ -901,7 +900,7 @@ class Processor */ public static function createEvent(array $activity, array $item): int { - $event['summary'] = HTML::toBBCode($activity['name'] ?: $activity['summary'] ?? ''); + $event['summary'] = HTML::toBBCode($activity['name'] ?: $activity['summary']); $event['desc'] = HTML::toBBCode($activity['content'] ?? ''); if (!empty($activity['start-time'])) { $event['start'] = DateTimeFormat::utc($activity['start-time']); @@ -973,25 +972,19 @@ class Processor $content = self::addMentionLinks($content, $activity['tags']); if (!empty($activity['quote-url'])) { - $id = Item::searchByLink($activity['quote-url']); + $id = Item::fetchByLink($activity['quote-url'], 0, ActivityPub\Receiver::COMPLETION_ASYNC); if ($id) { $shared_item = Post::selectFirst(['uri-id'], ['id' => $id]); $item['quote-uri-id'] = $shared_item['uri-id']; - DI::logger()->debug('Quoted post exists', ['uri' => $item['uri'], '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', ['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('Quoted post 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']]); - } elseif (Fetch::hasWorker($activity['quote-url'])) { - $item['quote-uri-id'] = ItemURI::getIdByURI($activity['quote-url']); - DI::logger()->info('Quoted post will be fetched via a worker.', ['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('Quoted post was not fetched but the uri-id exists', ['uri' => $item['uri'], 'uri-id' => $item['uri-id'], 'quote' => $activity['quote-url'], 'quote-uri-id' => $uri_id]); - $item['quote-uri-id'] = $uri_id; - FetchMissingActivity::add(Worker::PRIORITY_HIGH, $activity['quote-url'], [], '', Receiver::COMPLETION_ASYNC); + 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 { - $item['quote-uri-id'] = ItemURI::getIdByURI($activity['quote-url']); - DI::logger()->notice('Quoted post was not fetched by now, a worker will be raised to fetch it.', ['uri' => $item['uri'], 'uri-id' => $item['uri-id'], 'quote' => $activity['quote-url']]); - FetchMissingActivity::add(Worker::PRIORITY_HIGH, $activity['quote-url'], [], '', Receiver::COMPLETION_ASYNC); + DI::logger()->notice('Quote was not fetched', ['uri' => $item['uri'], 'uri-id' => $item['uri-id'], 'quote' => $activity['quote-url']]); } } @@ -1973,7 +1966,7 @@ class Processor if (is_array($reply)) { $ldobject = JsonLD::compact($reply); $id = JsonLD::fetchElement($ldobject, '@id'); - if (is_null($id) || self::alreadyKnown($id, $child['id'] ?? '')) { + if (Processor::alreadyKnown($id, $child['id'] ?? '')) { continue; } if (!empty($child['children']) && in_array($id, $child['children'])) { diff --git a/src/Protocol/ActivityPub/Transmitter.php b/src/Protocol/ActivityPub/Transmitter.php index 3f0aabe93e..bbfd52e0e4 100644 --- a/src/Protocol/ActivityPub/Transmitter.php +++ b/src/Protocol/ActivityPub/Transmitter.php @@ -43,8 +43,8 @@ use Friendica\Util\XML; */ class Transmitter { - public const CACHEKEY_FEATURED = 'transmitter:getFeatured:'; - public const CACHEKEY_CONTACTS = 'transmitter:getContacts:'; + const CACHEKEY_FEATURED = 'transmitter:getFeatured:'; + const CACHEKEY_CONTACTS = 'transmitter:getContacts:'; /** * Add relay servers to the list of inboxes @@ -248,7 +248,7 @@ class Transmitter $condition = [ "`uri-id` IN (SELECT `uri-id` FROM `collection-view` WHERE `cid` = ? AND `type` = ?)", - $owner_cid, Post\Collection::FEATURED, + $owner_cid, Post\Collection::FEATURED ]; $condition = DBA::mergeConditions($condition, [ @@ -260,7 +260,7 @@ class Transmitter 'parent-network' => Protocol::FEDERATED, 'origin' => true, 'deleted' => false, - 'visible' => true, + 'visible' => true ]); $count = Post::count($condition); @@ -315,10 +315,10 @@ class Transmitter public static function getService(): array { return [ - 'id' => (string) DI::baseUrl() . '/friendica', + 'id' => (string)DI::baseUrl() . '/friendica', 'type' => 'Application', 'name' => App::PLATFORM . " '" . App::CODENAME . "' " . App::VERSION . '-' . DB_UPDATE_VERSION, - 'url' => (string) DI::baseUrl(), + 'url' => (string)DI::baseUrl(), ]; } @@ -365,7 +365,7 @@ class Transmitter if ($full && !empty($owner['country-name'] . $owner['region'] . $owner['locality'])) { $data['vcard:hasAddress'] = [ '@type' => 'vcard:Home', 'vcard:country-name' => $owner['country-name'], - 'vcard:region' => $owner['region'], 'vcard:locality' => $owner['locality'], + 'vcard:region' => $owner['region'], 'vcard:locality' => $owner['locality'] ]; } @@ -386,11 +386,11 @@ class Transmitter $data['url'] = $owner['url']; $data['manuallyApprovesFollowers'] = in_array($owner['page-flags'], [User::PAGE_FLAGS_NORMAL, User::PAGE_FLAGS_PRVGROUP]); - $data['discoverable'] = (bool) $owner['net-publish'] && $full; + $data['discoverable'] = (bool)$owner['net-publish'] && $full; $data['publicKey'] = [ 'id' => $owner['url'] . '#main-key', 'owner' => $owner['url'], - 'publicKeyPem' => $owner['pubkey'], + 'publicKeyPem' => $owner['pubkey'] ]; $data['endpoints'] = ['sharedInbox' => DI::baseUrl() . '/inbox']; if ($full && $uid != 0) { @@ -422,7 +422,7 @@ class Transmitter $custom_fields[] = [ 'type' => 'PropertyValue', 'name' => $profile_field->label, - 'value' => BBCode::convertForUriId($owner['uri-id'], $profile_field->value), + 'value' => BBCode::convertForUriId($owner['uri-id'], $profile_field->value) ]; }; @@ -454,7 +454,7 @@ class Transmitter 'name' => $contact['name'], 'icon' => ['type' => 'Image', 'url' => Contact::getAvatarUrlForId($cid, '', $contact['updated'])], 'image' => ['type' => 'Image', 'url' => Contact::getHeaderUrlForId($cid, '', $contact['updated'])], - 'manuallyApprovesFollowers' => (bool) $contact['manually-approve'], + 'manuallyApprovesFollowers' => (bool)$contact['manually-approve'], 'discoverable' => !$contact['unsearchable'], ]; @@ -766,12 +766,7 @@ class Transmitter if (($profile['type'] == 'Group') && ($profile['url'] != ($actor_profile['url'] ?? ''))) { $data['to'][] = $profile['url']; } else { - // Event participation (Accept/Reject/TentativeAccept) must be directly addressed to the event organizer - if (in_array($item['verb'] ?? '', [Activity::ATTEND, Activity::ATTENDNO, Activity::ATTENDMAYBE])) { - $data['to'][] = $profile['url']; - } else { - $data['cc'][] = $profile['url']; - } + $data['cc'][] = $profile['url']; if (($item['private'] != Item::PRIVATE) && !empty($actor_profile['followers']) && (!$exclusive || !$is_group_thread)) { $data['cc'][] = $actor_profile['followers']; } @@ -891,13 +886,13 @@ class Transmitter * Store the receivers for the given item * * @param array $item - * @return array List of receiers + * @return void */ - public static function storeReceiversForItem(array $item): array + public static function storeReceiversForItem(array $item) { $receivers = self::createPermissionBlockForItem($item, true); if (empty($receivers)) { - return []; + return; } foreach (['to' => Tag::TO, 'cc' => Tag::CC, 'bto' => Tag::BTO, 'bcc' => Tag::BCC, 'audience' => Tag::AUDIENCE] as $element => $type) { @@ -912,7 +907,6 @@ class Transmitter } } } - return $receivers; } /** @@ -1311,7 +1305,7 @@ class Transmitter */ public static function createCachedActivityFromItem(int $item_id, bool $force = false, bool $object_mode = false, $announce_activity = false) { - $cachekey = 'APDelivery:createActivity:' . $item_id . ':' . (int) $object_mode . ':' . (int) $announce_activity; + $cachekey = 'APDelivery:createActivity:' . $item_id . ':' . (int)$object_mode . ':' . (int)$announce_activity; if (!$force) { $data = DI::cache()->get($cachekey); @@ -1701,7 +1695,7 @@ class Transmitter // otherwise we just return the link return '[url]' . $match[1] . '[/url]'; }, - $text, + $text ); // Remove all pictures @@ -1834,7 +1828,7 @@ class Transmitter } else { $data['attributedTo'] = $item['author-link']; } - $data['sensitive'] = (bool) $item['sensitive']; + $data['sensitive'] = (bool)$item['sensitive']; if (!empty($item['context']) && ($item['context'] != './')) { $data['context'] = $item['context']; @@ -1955,8 +1949,8 @@ class Transmitter if ($item['private'] != Item::PRIVATE) { $data['interactionPolicy'] = [ 'canQuote' => [ - 'automaticApproval' => [ActivityPub::PUBLIC_COLLECTION], - ], + 'automaticApproval' => [ActivityPub::PUBLIC_COLLECTION] + ] ]; } @@ -1966,7 +1960,7 @@ class Transmitter $data['replies'] = [ 'id' => $item['uri'] . '/replies', 'type' => 'Collection', - 'totalItems' => Post::countPosts(['thr-parent-id' => $item['uri-id'], 'gravity' => Item::GRAVITY_COMMENT, 'deleted' => false, 'private' => [Item::PUBLIC, Item::UNLISTED]]), + 'totalItems' => Post::countPosts(['thr-parent-id' => $item['uri-id'], 'gravity' => Item::GRAVITY_COMMENT, 'deleted' => false, 'private' => [Item::PUBLIC, Item::UNLISTED]]) ]; if (empty($data['location']) && (!empty($item['coord']) || !empty($item['location']))) { @@ -2026,9 +2020,9 @@ class Transmitter $activity['diaspora:guid'] = $item['guid']; $activity['actor'] = $item['author-link']; - $activity['target'] = (string) $target->id; + $activity['target'] = (string)$target->id; $activity['summary'] = BBCode::toPlaintext($item['body']); - $activity['object'] = ['id' => (string) $object->id, 'type' => 'tag', 'name' => (string) $object->title, 'content' => (string) $object->content]; + $activity['object'] = ['id' => (string)$object->id, 'type' => 'tag', 'name' => (string)$object->title, 'content' => (string)$object->content]; return $activity; } @@ -2159,7 +2153,7 @@ class Transmitter 'content' => $suggestion->note, 'instrument' => self::getService(), 'to' => [ActivityPub::PUBLIC_COLLECTION], - 'cc' => [], + 'cc' => [] ]; $signed = LDSignature::sign($data, $owner); @@ -2187,7 +2181,7 @@ class Transmitter 'published' => DateTimeFormat::utcNow(DateTimeFormat::ATOM), 'instrument' => self::getService(), 'to' => [ActivityPub::PUBLIC_COLLECTION], - 'cc' => [], + 'cc' => [] ]; $signed = LDSignature::sign($data, $owner); @@ -2220,7 +2214,7 @@ class Transmitter 'published' => DateTimeFormat::utcNow(DateTimeFormat::ATOM), 'instrument' => self::getService(), 'to' => [ActivityPub::PUBLIC_COLLECTION], - 'cc' => [], + 'cc' => [] ]; $signed = LDSignature::sign($data, $owner); @@ -2252,7 +2246,7 @@ class Transmitter 'published' => DateTimeFormat::utcNow(DateTimeFormat::ATOM), 'instrument' => self::getService(), 'to' => [$profile['followers']], - 'cc' => [], + 'cc' => [] ]; $signed = LDSignature::sign($data, $owner); @@ -2340,7 +2334,7 @@ class Transmitter $condition = [ 'verb' => Activity::FOLLOW, 'uid' => 0, 'parent-uri' => $object, - 'author-id' => Contact::getPublicIdByUserId($uid), + 'author-id' => Contact::getPublicIdByUserId($uid) ]; if (Post::exists($condition)) { DI::logger()->info('Follow for ' . $object . ' for user ' . $uid . ' does already exist.'); @@ -2398,7 +2392,7 @@ class Transmitter 'id' => $id, 'type' => 'Follow', 'actor' => $profile['url'], - 'object' => $owner['url'], + 'object' => $owner['url'] ], 'instrument' => self::getService(), 'to' => [$profile['url']], @@ -2437,7 +2431,7 @@ class Transmitter 'id' => $objectId, 'type' => 'Follow', 'actor' => $profile['url'], - 'object' => $owner['url'], + 'object' => $owner['url'] ], 'instrument' => self::getService(), 'to' => [$profile['url']], @@ -2484,7 +2478,7 @@ class Transmitter 'id' => $object_id, 'type' => 'Follow', 'actor' => $owner['url'], - 'object' => $profile['url'], + 'object' => $profile['url'] ], 'instrument' => self::getService(), 'to' => [$profile['url']], @@ -2531,7 +2525,7 @@ class Transmitter 'id' => $object_id, 'type' => 'Block', 'actor' => $owner['url'], - 'object' => $profile['url'], + 'object' => $profile['url'] ], 'instrument' => self::getService(), 'to' => [$profile['url']], @@ -2607,8 +2601,8 @@ class Transmitter if ( !empty($profile['addr']) && $profile['contact-type'] != Contact::TYPE_COMMUNITY - && !strstr($body, (string) $profile['addr']) - && !strstr($body, (string) $tag['url']) + && !strstr($body, $profile['addr']) + && !strstr($body, $tag['url']) && $tag['url'] !== $authorLink ) { $mentions[] = '@[url=' . $tag['url'] . ']' . $profile['nick'] . '[/url]'; diff --git a/src/Protocol/DFRN.php b/src/Protocol/DFRN.php index 0a1bfc71c1..baf1c756c1 100644 --- a/src/Protocol/DFRN.php +++ b/src/Protocol/DFRN.php @@ -45,9 +45,9 @@ use GuzzleHttp\Psr7\Uri; */ class DFRN { - public const TOP_LEVEL = 0; // Top level posting - public const REPLY = 1; // Regular reply that is stored locally - public const REPLY_RC = 2; // Reply that will be relayed + const TOP_LEVEL = 0; // Top level posting + const REPLY = 1; // Regular reply that is stored locally + const REPLY_RC = 2; // Reply that will be relayed /** * Generates an array of contact and user for DFRN imports @@ -117,7 +117,7 @@ class DFRN // These values aren't sent when sending from the queue. /// @todo Check if we can set these values from the queue or if they are needed at all. $item['entry:comment-allow'] = ($item['entry:comment-allow'] ?? '') ?: true; - $item['entry:cid'] ??= 0; + $item['entry:cid'] = $item['entry:cid'] ?? 0; $entry = self::entry($doc, 'text', $item, $owner, $item['entry:comment-allow'], $item['entry:cid']); if (isset($entry)) { @@ -442,8 +442,8 @@ class DFRN $author = $doc->createElement($authorelement); - $namdate = DateTimeFormat::utc($owner['name-date'] . '+00:00', DateTimeFormat::ATOM); - $picdate = DateTimeFormat::utc($owner['avatar-date'] . '+00:00', DateTimeFormat::ATOM); + $namdate = DateTimeFormat::utc($owner['name-date'].'+00:00', DateTimeFormat::ATOM); + $picdate = DateTimeFormat::utc($owner['avatar-date'].'+00:00', DateTimeFormat::ATOM); $attributes = []; @@ -452,7 +452,7 @@ class DFRN } XML::addElement($doc, $author, 'name', $owner['name'], $attributes); - XML::addElement($doc, $author, 'uri', DI::baseUrl() . '/profile/' . $owner['nickname'], $attributes); + XML::addElement($doc, $author, 'uri', DI::baseUrl().'/profile/' . $owner['nickname'], $attributes); XML::addElement($doc, $author, 'dfrn:handle', $owner['addr'], $attributes); $attributes = [ @@ -491,14 +491,14 @@ class DFRN $profile = DBA::selectFirst( 'owner-view', ['about', 'name', 'homepage', 'nickname', 'timezone', 'locality', 'region', 'country-name', 'pub_keywords', 'xmpp', 'dob'], - ['uid' => $owner['uid'], 'hidewall' => false], + ['uid' => $owner['uid'], 'hidewall' => false] ); if (DBA::isResult($profile)) { XML::addElement($doc, $author, 'poco:displayName', $profile['name']); XML::addElement($doc, $author, 'poco:updated', $namdate); if (trim($profile['dob']) > DBA::NULL_DATE) { - XML::addElement($doc, $author, 'poco:birthday', '0000-' . date('m-d', strtotime($profile['dob']))); + XML::addElement($doc, $author, 'poco:birthday', '0000-'.date('m-d', strtotime($profile['dob']))); } XML::addElement($doc, $author, 'poco:note', $profile['about']); @@ -809,7 +809,7 @@ class DFRN [ 'rel' => 'alternate', 'type' => 'text/html', - 'href' => DI::baseUrl() . '/display/' . $item['guid'], + 'href' => DI::baseUrl() . '/display/' . $item['guid'] ], ); @@ -968,7 +968,7 @@ class DFRN $path_parts = explode('/', $parts['path']); array_pop($path_parts); $parts['path'] = implode('/', $path_parts); - $contact['batch'] = (string) Uri::fromParts($parts); + $contact['batch'] = (string)Uri::fromParts($parts); } $dest_url = ($public_batch ? $contact['batch'] : $contact['notify']); @@ -1021,7 +1021,7 @@ class DFRN } if (!empty($res->message)) { - DI::logger()->info('Transmit to ' . $dest_url . ' returned status ' . $res->status . ' - ' . $res->message); + DI::logger()->info('Transmit to ' . $dest_url . ' returned status '.$res->status.' - '.$res->message); } return intval($res->status); @@ -1045,8 +1045,8 @@ class DFRN private static function fetchauthor(\DOMXPath $xpath, \DOMNode $context, array $importer, string $element, bool $onlyfetch, string $xml = ''): array { $author = []; - $author["name"] = XML::getFirstNodeValue($xpath, $element . "/atom:name/text()", $context); - $author["link"] = XML::getFirstNodeValue($xpath, $element . "/atom:uri/text()", $context); + $author["name"] = XML::getFirstNodeValue($xpath, $element."/atom:name/text()", $context); + $author["link"] = XML::getFirstNodeValue($xpath, $element."/atom:uri/text()", $context); $fields = ['id', 'uid', 'url', 'network', 'avatar-date', 'avatar', 'name-date', 'uri-date', 'addr', 'name', 'nick', 'about', 'location', 'keywords', 'xmpp', 'bdyear', 'bd', 'hidden', 'contact-type']; @@ -1271,7 +1271,7 @@ class DFRN $obj_doc = new DOMDocument("1.0", "utf-8"); $obj_doc->formatOutput = true; - $obj_element = $obj_doc->createElementNS(ActivityNamespace::ATOM1, $element); + $obj_element = $obj_doc->createElementNS( ActivityNamespace::ATOM1, $element); $activity_type = $xpath->query("activity:object-type/text()", $activity)->item(0)->nodeValue; XML::addElement($obj_doc, $obj_element, "type", $activity_type); @@ -1303,7 +1303,7 @@ class DFRN $objxml = $obj_doc->saveXML($obj_element); /// @todo This isn't totally clean. We should find a way to transform the namespaces - $objxml = str_replace("<" . $element . ' xmlns="http://www.w3.org/2005/Atom">', "<" . $element . ">", $objxml); + $objxml = str_replace("<".$element.' xmlns="http://www.w3.org/2005/Atom">', "<".$element.">", $objxml); return($objxml); } @@ -1393,7 +1393,7 @@ class DFRN $suggest['cid'], $suggest['body'], null, - $cid, + $cid )); DI::notify()->createFromArray([ @@ -1403,7 +1403,7 @@ class DFRN 'uid' => $owner['uid'], 'cid' => $from_contact['uid'], 'item' => $suggest, - 'link' => DI::baseUrl() . '/notifications/intros', + 'link' => DI::baseUrl().'/notifications/intros', ]); return true; @@ -1753,7 +1753,7 @@ class DFRN $current = Post::selectFirst( ['id', 'uid', 'edited', 'body'], - ['uri' => $item['uri'], 'uid' => $importer['importer_uid']], + ['uri' => $item['uri'], 'uid' => $importer['importer_uid']] ); // Is there an existing item? if (DBA::isResult($current) && !self::isEditedTimestampNewer($current, $item)) { @@ -2063,7 +2063,7 @@ class DFRN // This is my contact on another system, but it's really me. // Turn this into a wall post. $notify = Item::isRemoteSelf($importer, $item); - $item['wall'] = (bool) $notify; + $item['wall'] = (bool)$notify; $posted_id = Item::insert($item, $notify); @@ -2135,7 +2135,7 @@ class DFRN return; } - DI::logger()->info('deleting item ' . $item['id'] . ' uri=' . $uri); + DI::logger()->info('deleting item '.$item['id'].' uri='.$uri); Item::markForDeletion(['id' => $item['id']]); } diff --git a/src/Protocol/Diaspora.php b/src/Protocol/Diaspora.php index 142ee18331..1d8838837f 100644 --- a/src/Protocol/Diaspora.php +++ b/src/Protocol/Diaspora.php @@ -47,9 +47,9 @@ use SimpleXMLElement; */ class Diaspora { - public const PUSHED = 0; - public const FETCHED = 1; - public const FORCED_FETCH = 2; + const PUSHED = 0; + const FETCHED = 1; + const FORCED_FETCH = 2; /** * Return a list of participating contacts for a thread @@ -73,18 +73,18 @@ class Diaspora $items = Post::select( ['author-id', 'author-link', 'parent-author-link', 'parent-guid', 'guid'], - ['parent' => $item['parent'], 'gravity' => [Item::GRAVITY_COMMENT, Item::GRAVITY_ACTIVITY]], + ['parent' => $item['parent'], 'gravity' => [Item::GRAVITY_COMMENT, Item::GRAVITY_ACTIVITY]] ); while ($item = Post::fetch($items)) { $contact = DBA::selectFirst( 'contact', ['id', 'url', 'name', 'protocol', 'batch', 'network'], - ['id' => $item['author-id']], + ['id' => $item['author-id']] ); if ( - !DBA::isResult($contact) || empty($contact['batch']) - || ($contact['network'] != Protocol::DIASPORA) - || Strings::compareLink($item['parent-author-link'], $item['author-link']) + !DBA::isResult($contact) || empty($contact['batch']) || + ($contact['network'] != Protocol::DIASPORA) || + Strings::compareLink($item['parent-author-link'], $item['author-link']) ) { continue; } @@ -325,9 +325,9 @@ class Diaspora } return [ - 'message' => (string) Strings::base64UrlDecode($base->data), + 'message' => (string)Strings::base64UrlDecode($base->data), 'author' => $author->getAddr(), - 'key' => (string) $key, + 'key' => (string)$key ]; } @@ -467,7 +467,7 @@ class Diaspora return [ 'message' => $inner_decrypted, 'author' => $author->getAddr(), - 'key' => $key, + 'key' => $key ]; } @@ -496,7 +496,7 @@ class Diaspora $importer = [ 'uid' => 0, - 'page-flags' => User::PAGE_FLAGS_FREELOVE, + 'page-flags' => User::PAGE_FLAGS_FREELOVE ]; $success = self::dispatch($importer, $msg, $fields, $direction); @@ -787,7 +787,7 @@ class Diaspora DI::logger()->info('Fetching diaspora key', ['handle' => $uri->getAddr()]); try { return DI::dsprContact()->getByAddr($uri)->pubKey; - } catch (NotFoundException|\InvalidArgumentException $e) { + } catch (NotFoundException | \InvalidArgumentException $e) { return ''; } } @@ -945,7 +945,7 @@ class Diaspora function ($match) use ($item) { self::fetchGuidSub($match, $item); }, - $item['body'], + $item['body'] ); preg_replace_callback( @@ -953,7 +953,7 @@ class Diaspora function ($match) use ($item) { self::fetchGuidSub($match, $item); }, - $item['body'], + $item['body'] ); } @@ -987,7 +987,7 @@ class Diaspora return $return; }, - $body, + $body ); return $return; @@ -1105,9 +1105,9 @@ class Diaspora // Fetch the author - for the old and the new Diaspora version if ($source_xml->post->status_message && $source_xml->post->status_message->diaspora_handle) { - $author_handle = (string) $source_xml->post->status_message->diaspora_handle; + $author_handle = (string)$source_xml->post->status_message->diaspora_handle; } elseif ($source_xml->author && ($source_xml->getName() == 'status_message')) { - $author_handle = (string) $source_xml->author; + $author_handle = (string)$source_xml->author; } try { @@ -1121,7 +1121,7 @@ class Diaspora return [ 'message' => $x, 'author' => $author->getAddr(), - 'key' => self::key($author), + 'key' => self::key($author) ]; } @@ -1182,7 +1182,7 @@ class Diaspora 'id', 'parent', 'body', 'wall', 'uri', 'guid', 'private', 'origin', 'allow_cid', 'allow_gid', 'deny_cid', 'deny_gid', 'author-name', 'author-link', 'author-avatar', 'gravity', - 'owner-name', 'owner-link', 'owner-avatar', + 'owner-name', 'owner-link', 'owner-avatar' ]; $condition = ['uid' => $uid, 'guid' => $guid]; @@ -1242,7 +1242,7 @@ class Diaspora return [ 'cid' => $cid, - 'network' => $network, + 'network' => $network ]; } @@ -1431,7 +1431,7 @@ class Diaspora } elseif ($person_uri) { try { return DI::dsprContact()->selectOneByAddr($person_uri)->baseurl . '/objects/' . $guid; - } catch (HTTPException\NotFoundException|\InvalidArgumentException $e) { + } catch (HTTPException\NotFoundException | \InvalidArgumentException $e) { return ''; } } @@ -1525,8 +1525,8 @@ class Diaspora } try { - $author_url = (string) DI::dsprContact()->getByAddr($author)->url; - } catch (HTTPException\NotFoundException|\InvalidArgumentException $e) { + $author_url = (string)DI::dsprContact()->getByAddr($author)->url; + } catch (HTTPException\NotFoundException | \InvalidArgumentException $e) { DI::logger()->notice('Unable to find author details', ['author' => $author->getAddr()]); return false; } @@ -1664,14 +1664,14 @@ class Diaspora 'guid' => $msg_guid, 'convid' => $conversation['id'], 'from-name' => $msg_author->name, - 'from-photo' => (string) $msg_author->photo, - 'from-url' => (string) $msg_author->url, + 'from-photo' => (string)$msg_author->photo, + 'from-url' => (string)$msg_author->url, 'contact-id' => $contact['id'], 'title' => $subject, 'body' => Markdown::toBBCode($msg_text), 'uri' => $msg_author_handle . ':' . $msg_guid, 'parent-uri' => $author_handle . ':' . $guid, - 'created' => $msg_created_at, + 'created' => $msg_created_at ]); } @@ -1718,7 +1718,7 @@ class Diaspora 'created' => $created_at, 'updated' => DateTimeFormat::utcNow(), 'subject' => $subject, - 'recips' => $participants, + 'recips' => $participants ]); if ($r) { @@ -1783,8 +1783,8 @@ class Diaspora } try { - $author_url = (string) DI::dsprContact()->getByAddr($author)->url; - } catch (HTTPException\NotFoundException|\InvalidArgumentException $e) { + $author_url = (string)DI::dsprContact()->getByAddr($author)->url; + } catch (HTTPException\NotFoundException | \InvalidArgumentException $e) { DI::logger()->notice('Unable to find author details', ['author' => $author->getAddr()]); return false; } @@ -1904,7 +1904,7 @@ class Diaspora try { $author = DI::dsprContact()->getByAddr($author_uri); - } catch (HTTPException\NotFoundException|\InvalidArgumentException $e) { + } catch (HTTPException\NotFoundException | \InvalidArgumentException $e) { DI::logger()->notice('Unable to find author details', ['author' => $author_uri->getAddr()]); return false; } @@ -1918,15 +1918,15 @@ class Diaspora 'guid' => $guid, 'convid' => $conversation['id'], 'from-name' => $author->name, - 'from-photo' => (string) $author->photo, - 'from-url' => (string) $author->url, + 'from-photo' => (string)$author->photo, + 'from-url' => (string)$author->url, 'contact-id' => $contact['id'], 'title' => $conversation['subject'], 'body' => $body, 'reply' => 1, 'uri' => $author_uri . ':' . $guid, 'parent-uri' => $author_uri . ':' . $conversation['guid'], - 'created' => $created_at, + 'created' => $created_at ]); } @@ -1975,8 +1975,8 @@ class Diaspora } try { - $author_url = (string) DI::dsprContact()->getByAddr($author)->url; - } catch (HTTPException\NotFoundException|\InvalidArgumentException $e) { + $author_url = (string)DI::dsprContact()->getByAddr($author)->url; + } catch (HTTPException\NotFoundException | \InvalidArgumentException $e) { DI::logger()->notice('unable to find author details', ['author' => $author->getAddr()]); return false; } @@ -2023,7 +2023,7 @@ class Diaspora // Send all existing comments and likes to the requesting server $comments = Post::select( ['id', 'uri-id', 'parent-author-network', 'author-network', 'verb', 'gravity'], - ['parent' => $toplevel_parent_item['id'], 'gravity' => [Item::GRAVITY_COMMENT, Item::GRAVITY_ACTIVITY]], + ['parent' => $toplevel_parent_item['id'], 'gravity' => [Item::GRAVITY_COMMENT, Item::GRAVITY_ACTIVITY]] ); while ($comment = Post::fetch($comments)) { if (($comment['gravity'] == Item::GRAVITY_ACTIVITY) && !in_array($comment['verb'], [Activity::LIKE, Activity::DISLIKE])) { @@ -2150,7 +2150,7 @@ class Diaspora 'name' => $name, 'location' => $location, 'name-date' => DateTimeFormat::utcNow(), 'about' => $about, 'addr' => $author->getAddr(), 'nick' => $author->getUser(), 'keywords' => $keywords, - 'unsearchable' => !$searchable, 'sensitive' => $nsfw, + 'unsearchable' => !$searchable, 'sensitive' => $nsfw ]; if (!empty($birthday)) { @@ -2177,7 +2177,7 @@ class Diaspora if ($contact['rel'] == Contact::SHARING) { Contact::update( ['rel' => Contact::FRIEND, 'writable' => true], - ['id' => $contact['id'], 'uid' => $importer['uid']], + ['id' => $contact['id'], 'uid' => $importer['uid']] ); } } @@ -2260,8 +2260,8 @@ class Diaspora } try { - $author_url = (string) DI::dsprContact()->getByAddr($author)->url; - } catch (HTTPException\NotFoundException|\InvalidArgumentException $e) { + $author_url = (string)DI::dsprContact()->getByAddr($author)->url; + } catch (HTTPException\NotFoundException | \InvalidArgumentException $e) { DI::logger()->notice('Cannot resolve diaspora handle for recipient', ['author' => $author->getAddr(), 'recipient' => $recipient]); return false; } @@ -2275,7 +2275,7 @@ class Diaspora $item = [ 'author-id' => Contact::getIdForURL($author_url), - 'author-link' => $author_url, + 'author-link' => $author_url ]; $result = Contact::addRelationship($importer, $contact, $item, false); @@ -2439,12 +2439,12 @@ class Diaspora try { $author = DI::dsprContact()->getByAddr($author_uri); - } catch (HTTPException\NotFoundException|\InvalidArgumentException $e) { + } catch (HTTPException\NotFoundException | \InvalidArgumentException $e) { DI::logger()->notice('Unable to find details for author', ['author' => $author_uri->getAddr()]); return false; } - $contact_url = $contact['url'] ?? '' ?: (string) $author->url; + $contact_url = $contact['url'] ?? '' ?: (string)$author->url; // Fetch items that are about to be deleted $fields = ['uid', 'id', 'parent', 'author-link', 'uri-id']; @@ -2580,8 +2580,8 @@ class Diaspora 'uri-id' => $uriid, 'type' => Post\Media::IMAGE, 'url' => XML::unescape($photo->remote_photo_path) . XML::unescape($photo->remote_photo_name), - 'height' => (int) XML::unescape($photo->height ?? 0), - 'width' => (int) XML::unescape($photo->width ?? 0), + 'height' => (int)XML::unescape($photo->height ?? 0), + 'width' => (int)XML::unescape($photo->width ?? 0), 'description' => XML::unescape($photo->text ?? ''), ]; @@ -2817,8 +2817,8 @@ class Diaspora $json_object = json_encode( [ 'aes_key' => base64_encode($encrypted_key_bundle), - 'encrypted_magic_envelope' => base64_encode($ciphertext), - ], + 'encrypted_magic_envelope' => base64_encode($ciphertext) + ] ); return $json_object; @@ -2859,8 +2859,8 @@ class Diaspora 'me:encoding' => $encoding, 'me:alg' => $alg, 'me:sig' => $sig, - '@attributes2' => ['key_id' => $key_id], - ], + '@attributes2' => ['key_id' => $key_id] + ] ]; $namespaces = ['me' => ActivityNamespace::SALMON_ME]; @@ -2939,7 +2939,7 @@ class Diaspora try { $target = DI::dsprContact()->getByAddr(WebFingerUri::fromString($contact['addr'])); $dest_url = $public_batch ? $target->batch : $target->notify; - } catch (HTTPException\NotFoundException|\InvalidArgumentException $e) { + } catch (HTTPException\NotFoundException | \InvalidArgumentException $e) { } if (empty($dest_url)) { @@ -2977,7 +2977,7 @@ class Diaspora DI::logger()->info('transmit: ' . $logid . '-' . $guid . ' to ' . $dest_url . ' returns: ' . $return_code); - return $return_code ?: -1; + return $return_code ? $return_code : -1; } @@ -3025,7 +3025,7 @@ class Diaspora if (!empty($contact['addr'])) { try { $pubkey = DI::dsprContact()->getByAddr(WebFingerUri::fromString($contact['addr']))->pubKey; - } catch (HTTPException\NotFoundException|\InvalidArgumentException $e) { + } catch (HTTPException\NotFoundException | \InvalidArgumentException $e) { } } else { // The "addr" field should always be filled. @@ -3084,7 +3084,7 @@ class Diaspora 'author' => $author_handle, 'guid' => System::createUUID(), 'parent_type' => 'Post', - 'parent_guid' => $item['guid'], + 'parent_guid' => $item['guid'] ]; DI::logger()->info('Send participation for ' . $item['guid'] . ' by ' . $author_handle); @@ -3117,7 +3117,7 @@ class Diaspora $message = [ 'author' => $old_handle, 'profile' => $profile, - 'signature' => $signature, + 'signature' => $signature ]; DI::logger()->info('Send account migration', ['msg' => $message]); @@ -3163,7 +3163,7 @@ class Diaspora 'author' => self::myHandle($owner), 'recipient' => $contact['addr'], 'following' => 'true', - 'sharing' => 'true', + 'sharing' => 'true' ]; DI::logger()->info('Send share', ['msg' => $message]); @@ -3186,7 +3186,7 @@ class Diaspora 'author' => self::myHandle($owner), 'recipient' => $contact['addr'], 'following' => 'false', - 'sharing' => 'false', + 'sharing' => 'false' ]; DI::logger()->info('Send unshare', ['msg' => $message]); @@ -3325,7 +3325,7 @@ class Diaspora 'root_author' => $ret['root_handle'], 'root_guid' => $ret['root_guid'], 'provider_display_name' => $item['app'], - 'public' => $public, + 'public' => $public ]; $type = 'reshare'; @@ -3386,7 +3386,7 @@ class Diaspora 'public' => $public, 'text' => $body, 'provider_display_name' => $item['app'], - 'location' => $location, + 'location' => $location ]; if ($native_photos) { @@ -3404,9 +3404,9 @@ class Diaspora $message['event'] = $event; if ( - !empty($event['location']['address']) - && !empty($event['location']['lat']) - && !empty($event['location']['lng']) + !empty($event['location']['address']) && + !empty($event['location']['lat']) && + !empty($event['location']['lng']) ) { $message['location'] = $event['location']; } @@ -3421,7 +3421,7 @@ class Diaspora $msg = [ 'type' => $type, - 'message' => $message, + 'message' => $message ]; DI::cache()->set($cachekey, $msg, Duration::QUARTER_HOUR); @@ -3472,7 +3472,7 @@ class Diaspora $profile = Contact::getByURL($profile_url, false, ['addr', 'name']); if ( !empty($profile['addr']) - && !strstr($body, (string) $profile['addr']) + && !strstr($body, $profile['addr']) && !strstr($body, $profile_url) ) { $body = '@[url=' . $profile_url . ']' . $profile['name'] . '[/url] ' . $body; @@ -3570,7 +3570,7 @@ class Diaspora 'guid' => $item['guid'], 'parent_guid' => $parent['guid'], 'status' => $attend_answer, - 'author_signature' => '', + 'author_signature' => '' ]; } @@ -3758,7 +3758,7 @@ class Diaspora $message = [ 'author' => $itemaddr, 'target_guid' => $item['guid'], - 'target_type' => $target_type, + 'target_type' => $target_type ]; DI::logger()->info('Got message', ['msg' => $message]); @@ -3808,7 +3808,7 @@ class Diaspora 'subject' => $cnv['subject'], 'created_at' => DateTimeFormat::utc($cnv['created'], DateTimeFormat::ATOM), 'participants' => $cnv['recips'], - 'message' => $msg, + 'message' => $msg ]; $type = 'conversation'; @@ -3915,7 +3915,7 @@ class Diaspora $data['birthday'] = ''; if ($profile['dob'] && ($profile['dob'] > '0000-00-00')) { - [$year, $month, $day] = sscanf($profile['dob'], '%4d-%2d-%2d'); + list($year, $month, $day) = sscanf($profile['dob'], '%4d-%2d-%2d'); if ($year < 1004) { $year = 1004; } diff --git a/src/Protocol/Diaspora/Repository/DiasporaContact.php b/src/Protocol/Diaspora/Repository/DiasporaContact.php index b41cb1ac6b..5cf05b8aa0 100644 --- a/src/Protocol/Diaspora/Repository/DiasporaContact.php +++ b/src/Protocol/Diaspora/Repository/DiasporaContact.php @@ -28,9 +28,9 @@ use Psr\Log\LoggerInterface; class DiasporaContact extends BaseRepository { - public const ALWAYS_UPDATE = true; - public const NEVER_UPDATE = false; - public const UPDATE_IF_MISSING_OR_OUTDATED = null; + const ALWAYS_UPDATE = true; + const NEVER_UPDATE = false; + const UPDATE_IF_MISSING_OR_OUTDATED = null; protected static $table_name = 'diaspora-contact-view'; @@ -51,7 +51,7 @@ class DiasporaContact extends BaseRepository */ public function selectOne(array $condition, array $params = []): DiasporaContactEntity { - $fields = $this->_selectFirstRowAsArray($condition, $params); + $fields = $this->_selectFirstRowAsArray( $condition, $params); return $this->factory->createFromTableRow($fields); } @@ -105,18 +105,18 @@ class DiasporaContact extends BaseRepository $fields = [ 'uri-id' => $uriId, 'addr' => $DiasporaContact->addr, - 'alias' => (string) $DiasporaContact->alias, + 'alias' => (string)$DiasporaContact->alias, 'nick' => $DiasporaContact->nick, 'name' => $DiasporaContact->name, 'given-name' => $DiasporaContact->givenName, 'family-name' => $DiasporaContact->familyName, - 'photo' => (string) $DiasporaContact->photo, - 'photo-medium' => (string) $DiasporaContact->photoMedium, - 'photo-small' => (string) $DiasporaContact->photoSmall, - 'batch' => (string) $DiasporaContact->batch, - 'notify' => (string) $DiasporaContact->notify, - 'poll' => (string) $DiasporaContact->poll, - 'subscribe' => (string) $DiasporaContact->subscribe, + 'photo' => (string)$DiasporaContact->photo, + 'photo-medium' => (string)$DiasporaContact->photoMedium, + 'photo-small' => (string)$DiasporaContact->photoSmall, + 'batch' => (string)$DiasporaContact->batch, + 'notify' => (string)$DiasporaContact->notify, + 'poll' => (string)$DiasporaContact->poll, + 'subscribe' => (string)$DiasporaContact->subscribe, 'searchable' => $DiasporaContact->searchable, 'pubkey' => $DiasporaContact->pubKey, 'gsid' => $DiasporaContact->gsid, @@ -246,7 +246,7 @@ class DiasporaContact extends BaseRepository new DateTime($contact['created'] ?? 'now', new DateTimeZone('UTC')), $interacting_count ?? 0, $interacted_count ?? 0, - $post_count ?? 0, + $post_count ?? 0 ); $DiasporaContact = $this->save($DiasporaContact); diff --git a/src/Protocol/Feed.php b/src/Protocol/Feed.php index 140b38ec28..e691c408de 100644 --- a/src/Protocol/Feed.php +++ b/src/Protocol/Feed.php @@ -495,7 +495,7 @@ class Feed if (!$dryRun) { $condition = [ "`uid` = ? AND `uri` = ? AND `network` IN (?, ?)", - $importer['uid'], $item['uri'], Protocol::FEED, Protocol::DFRN, + $importer['uid'], $item['uri'], Protocol::FEED, Protocol::DFRN ]; $previous = Post::selectFirst(['id', 'created'], $condition); if (DBA::isResult($previous)) { @@ -544,7 +544,7 @@ class Feed if (in_array($attribute->name, ['url', 'href'])) { $href = $attribute->textContent; } elseif ($attribute->name == 'length') { - $length = (int) $attribute->textContent; + $length = (int)$attribute->textContent; } elseif ($attribute->name == 'type') { $type = $attribute->textContent; } @@ -665,7 +665,7 @@ class Feed $item['plink'], false, $fetch_further_information == LocalRelationship::FFI_BOTH, - $contact['ffi_keyword_denylist'] ?? '', + $contact['ffi_keyword_denylist'] ?? '' ); if (!empty($data)) { @@ -712,7 +712,7 @@ class Feed DI::logger()->info('Stored feed', ['item' => $item]); $notify = Item::isRemoteSelf($contact, $item); - $item['wall'] = (bool) $notify; + $item['wall'] = (bool)$notify; // Distributed items should have a well-formatted URI. // Additionally, we have to avoid conflicts with identical URI between imported feeds and these items. @@ -734,7 +734,7 @@ class Feed } else { $postings[] = [ 'item' => $item, 'notify' => $notify, - 'taglist' => $taglist, 'attachments' => $attachments, + 'taglist' => $taglist, 'attachments' => $attachments ]; } } else { @@ -1027,7 +1027,7 @@ class Feed $owner['uid'], $check_date, Item::GRAVITY_PARENT, Item::GRAVITY_COMMENT, Item::GRAVITY_ACTIVITY, Activity::ANNOUNCE, Item::PRIVATE, Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::DIASPORA, - $authorid, + $authorid ]; if ($filter === 'comments') { @@ -1208,8 +1208,8 @@ class Feed '', [ 'rel' => 'alternate', 'type' => 'text/html', - 'href' => DI::baseUrl() . '/display/' . $item['guid'], - ], + 'href' => DI::baseUrl() . '/display/' . $item['guid'] + ] ); XML::addElement($doc, $entry, 'published', DateTimeFormat::utc($item['created'] . '+00:00', DateTimeFormat::ATOM)); @@ -1250,13 +1250,13 @@ class Feed if (isset($parent_plink)) { $attributes = [ 'ref' => $item['thr-parent'], - 'href' => $parent_plink, + 'href' => $parent_plink ]; XML::addElement($doc, $entry, 'thr:in-reply-to', '', $attributes); $attributes = [ 'rel' => 'related', - 'href' => $parent_plink, + 'href' => $parent_plink ]; XML::addElement($doc, $entry, 'link', '', $attributes); } @@ -1410,9 +1410,9 @@ class Feed if ($owner['contact-type'] == Contact::TYPE_COMMUNITY) { $entry->setAttribute('xmlns:activity', ActivityNamespace::ACTIVITY); - $contact = Contact::getByURL($item['author-link']) ?: $owner; - $contact['nickname'] ??= $contact['nick']; - $author = self::addAuthor($doc, $contact); + $contact = Contact::getByURL($item['author-link']) ?: $owner; + $contact['nickname'] = $contact['nickname'] ?? $contact['nick']; + $author = self::addAuthor($doc, $contact); $entry->appendChild($author); } } else { diff --git a/src/Protocol/HTTP/MediaType.php b/src/Protocol/HTTP/MediaType.php index de13f860ff..3c7e3af42a 100644 --- a/src/Protocol/HTTP/MediaType.php +++ b/src/Protocol/HTTP/MediaType.php @@ -16,18 +16,18 @@ namespace Friendica\Protocol\HTTP; */ final class MediaType { - public const DQUOTE = '"'; - public const DIGIT = '0-9'; - public const ALPHA = 'a-zA-Z'; + const DQUOTE = '"'; + const DIGIT = '0-9'; + const ALPHA = 'a-zA-Z'; // @see https://www.charset.org/charsets/us-ascii - public const VCHAR = "\\x21-\\x7E"; + const VCHAR = "\\x21-\\x7E"; - public const SYMBOL_NO_DELIM = "!#$%&'*+-.^_`|~"; + const SYMBOL_NO_DELIM = "!#$%&'*+-.^_`|~"; - public const OBSTEXT = "\\x80-\\xFF"; + const OBSTEXT = "\\x80-\\xFF"; - public const QDTEXT = "\t \\x21\\x23-\\x5B\\x5D-\\x7E" . self::OBSTEXT; + const QDTEXT = "\t \\x21\\x23-\\x5B\\x5D-\\x7E" . self::OBSTEXT; /** * @var string @@ -90,7 +90,7 @@ final class MediaType throw new \InvalidArgumentException('Provided string doesn\'t look like a MIME type: ' . $contentType); } - [$type, $subType] = $mimeTypeParts; + list($type, $subType) = $mimeTypeParts; $parameters = []; foreach ($parts as $parameterString) { @@ -108,7 +108,7 @@ final class MediaType throw new \InvalidArgumentException('Parameter has too many values: ' . $parameterString); } - [$key, $value] = $parameterParts; + list($key, $value) = $parameterParts; if (!self::isToken($value) && !self::isQuotedString($value)) { throw new \InvalidArgumentException("Parameter value isn't a valid token or a quoted string: \"" . $value . '"'); diff --git a/src/Protocol/Salmon.php b/src/Protocol/Salmon.php new file mode 100644 index 0000000000..5d8673e8a6 --- /dev/null +++ b/src/Protocol/Salmon.php @@ -0,0 +1,43 @@ +toString('Magic'); + } + + /** + * @param string $magic Magic key format starting with "RSA." + * @return string + */ + public static function magicKeyToPem(string $magic): string + { + \phpseclib3\Crypt\RSA::addFileFormat(Magic::class); + + return (string) PublicKeyLoader::load($magic); + } +} diff --git a/src/Protocol/Salmon/Format/Magic.php b/src/Protocol/Salmon/Format/Magic.php new file mode 100644 index 0000000000..c8e812739e --- /dev/null +++ b/src/Protocol/Salmon/Format/Magic.php @@ -0,0 +1,63 @@ + new BigInteger($m, 256), + 'publicExponent' => new BigInteger($e, 256), + 'isPublicKey' => true, + ]; + } + + public static function savePublicKey(BigInteger $n, BigInteger $e, array $options = []): string + { + return 'RSA.' . Strings::base64UrlEncode($n->toBytes(), true) . '.' . Strings::base64UrlEncode($e->toBytes(), true); + } +} diff --git a/src/Security/Authentication.php b/src/Security/Authentication.php index b532f61054..2f383cfefb 100644 --- a/src/Security/Authentication.php +++ b/src/Security/Authentication.php @@ -131,14 +131,12 @@ class Authentication 'account_expired' => false, 'account_removed' => false, 'verified' => true, - ], + ] ); if ($this->dba->isResult($user)) { - if (!$this->cookie->comparePrivateDataHash( - $this->cookie->get('hash'), + if (!$this->cookie->comparePrivateDataHash($this->cookie->get('hash'), $user['password'] ?? '', - $user['prvkey'] ?? '', - ) + $user['prvkey'] ?? '') ) { $this->logger->notice("Hash doesn't fit.", ['user' => $this->cookie->get('uid')]); $this->session->clear(); @@ -172,12 +170,10 @@ class Authentication $check = $this->config->get('system', 'paranoia'); // extra paranoia - if the IP changed, log them out if ($check && ($this->session->get('addr') != $this->remoteAddress)) { - $this->logger->notice( - 'Session address changed. Paranoid setting in effect, blocking session. ', - [ - 'addr' => $this->session->get('addr'), - 'remote_addr' => $this->remoteAddress, - ], + $this->logger->notice('Session address changed. Paranoid setting in effect, blocking session. ', [ + 'addr' => $this->session->get('addr'), + 'remote_addr' => $this->remoteAddress + ] ); $this->session->clear(); $this->baseUrl->redirect(); @@ -192,7 +188,7 @@ class Authentication 'account_expired' => false, 'account_removed' => false, 'verified' => true, - ], + ] ); if (!$this->dba->isResult($user)) { $this->session->clear(); @@ -223,7 +219,7 @@ class Authentication // Otherwise it's probably an openid. try { - $openid = new LightOpenID($this->baseUrl->getHost()); + $openid = new LightOpenID($this->baseUrl->getHost()); /** @phpstan-ignore-next-line $openid->identity is private, but will be set via magic setter */ $openid->identity = $openid_url; $this->session->set('openid', $openid_url); @@ -258,7 +254,7 @@ class Authentication $record = $this->dba->selectFirst( 'user', [], - ['uid' => User::getIdFromPasswordAuthentication($username, $password, false, true)], + ['uid' => User::getIdFromPasswordAuthentication($username, $password, false, true)] ); } catch (Exception $e) { $this->logger->warning('authenticate: failed login attempt', ['action' => 'login', 'username' => $username, 'ip' => $this->remoteAddress]); @@ -314,7 +310,7 @@ class Authentication * @throws HTTPException\MovedPermanentlyException * @throws HTTPException\TemporaryRedirectException * @throws HTTPException\ForbiddenException - * + * @throws HTTPException\InternalServerErrorException In case of Friendica specific exceptions * */ @@ -341,7 +337,6 @@ class Authentication if (strlen($user_record['timezone'])) { $this->appHelper->setTimeZone($user_record['timezone']); - $this->session->set('timezone', $user_record['timezone']); } $contact = $this->dba->selectFirst('contact', ['id'], ['uid' => $user_record['uid'], 'self' => true]); diff --git a/src/Security/BasicAuth.php b/src/Security/BasicAuth.php index 5c0a8ec9a8..52a0f75026 100644 --- a/src/Security/BasicAuth.php +++ b/src/Security/BasicAuth.php @@ -70,7 +70,7 @@ class BasicAuth // Support for known clients that doesn't send a source name if (empty($source) && !empty($_SERVER['HTTP_USER_AGENT'])) { - if (strpos($_SERVER['HTTP_USER_AGENT'], "Twidere") !== false) { + if(strpos($_SERVER['HTTP_USER_AGENT'], "Twidere") !== false) { $source = 'Twidere'; } @@ -112,7 +112,7 @@ class BasicAuth if (!empty($_SERVER['REDIRECT_REMOTE_USER'])) { $userpass = base64_decode(substr($_SERVER["REDIRECT_REMOTE_USER"], 6)); if (!empty($userpass) && strpos($userpass, ':')) { - [$name, $password] = explode(':', $userpass); + list($name, $password) = explode(':', $userpass); $_SERVER['PHP_AUTH_USER'] = $name; $_SERVER['PHP_AUTH_PW'] = $password; } diff --git a/src/Security/OAuth1/OAuthRequest.php b/src/Security/OAuth1/OAuthRequest.php index 5318e12f1e..b3564d2d1b 100644 --- a/src/Security/OAuth1/OAuthRequest.php +++ b/src/Security/OAuth1/OAuthRequest.php @@ -16,16 +16,16 @@ class OAuthRequest private $http_url; // for debug purposes public $base_string; - public static $version = '1.0'; + public static $version = '1.0'; public static $POST_INPUT = 'php://input'; - public function __construct($http_method, $http_url, $parameters = null) + function __construct($http_method, $http_url, $parameters = null) { @$parameters or $parameters = []; - $parameters = array_merge(OAuthUtil::parse_parameters(parse_url($http_url, PHP_URL_QUERY)), $parameters); - $this->parameters = $parameters; - $this->http_method = $http_method; - $this->http_url = $http_url; + $parameters = array_merge(OAuthUtil::parse_parameters(parse_url($http_url, PHP_URL_QUERY)), $parameters); + $this->parameters = $parameters; + $this->http_method = $http_method; + $this->http_url = $http_url; } @@ -43,11 +43,11 @@ class OAuthRequest $scheme = (!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] != "on") ? 'http' : 'https'; - @$http_url or $http_url = $scheme - . '://' . $_SERVER['HTTP_HOST'] - . ':' - . $_SERVER['SERVER_PORT'] - . $_SERVER['REQUEST_URI']; + @$http_url or $http_url = $scheme . + '://' . $_SERVER['HTTP_HOST'] . + ':' . + $_SERVER['SERVER_PORT'] . + $_SERVER['REQUEST_URI']; @$http_method or $http_method = $_SERVER['REQUEST_METHOD']; // We weren't handed any parameters, so let's find the ones relevant to @@ -67,11 +67,11 @@ class OAuthRequest $http_method == "POST" && @strstr( $request_headers["Content-Type"], - "application/x-www-form-urlencoded", + "application/x-www-form-urlencoded" ) ) { - $post_data = OAuthUtil::parse_parameters( - file_get_contents(self::$POST_INPUT), + $post_data = OAuthUtil::parse_parameters( + file_get_contents(self::$POST_INPUT) ); $parameters = array_merge($parameters, $post_data); } @@ -80,14 +80,14 @@ class OAuthRequest // and add those overriding any duplicates from GET or POST if (@substr($request_headers['Authorization'], 0, 6) == "OAuth ") { $header_parameters = OAuthUtil::split_header( - $request_headers['Authorization'], + $request_headers['Authorization'] ); - $parameters = array_merge($parameters, $header_parameters); + $parameters = array_merge($parameters, $header_parameters); } } // fix for friendica redirect system - $http_url = substr($http_url, 0, strpos($http_url, (string) $parameters['pagename']) + strlen($parameters['pagename'])); + $http_url = substr($http_url, 0, strpos($http_url, $parameters['pagename']) + strlen($parameters['pagename'])); unset($parameters['pagename']); return new OAuthRequest($http_method, $http_url, $parameters); @@ -107,15 +107,14 @@ class OAuthRequest public static function from_consumer_and_token(OAuthConsumer $consumer, $http_method, $http_url, array $parameters = null, OAuthToken $token = null) { @$parameters or $parameters = []; - $defaults = [ + $defaults = [ "oauth_version" => OAuthRequest::$version, "oauth_nonce" => OAuthRequest::generate_nonce(), "oauth_timestamp" => OAuthRequest::generate_timestamp(), "oauth_consumer_key" => $consumer->key, ]; - if ($token) { + if ($token) $defaults['oauth_token'] = $token->key; - } $parameters = array_merge($defaults, $parameters); @@ -140,7 +139,7 @@ class OAuthRequest public function get_parameter($name) { - return $this->parameters[$name] ?? null; + return isset($this->parameters[$name]) ? $this->parameters[$name] : null; } public function get_parameters() @@ -245,11 +244,10 @@ class OAuthRequest */ public function to_postdata(bool $raw = false) { - if ($raw) { + if ($raw) return $this->parameters; - } else { + else return OAuthUtil::build_http_query($this->parameters); - } } /** @@ -266,22 +264,19 @@ class OAuthRequest if ($realm) { $out = 'Authorization: OAuth realm="' . OAuthUtil::urlencode_rfc3986($realm) . '"'; $first = false; - } else { + } else $out = 'Authorization: OAuth'; - } foreach ($this->parameters as $k => $v) { - if (substr($k, 0, 5) != "oauth") { - continue; - } + if (substr($k, 0, 5) != "oauth") continue; if (is_array($v)) { throw new OAuthException('Arrays not supported in headers'); } - $out .= ($first) ? ' ' : ','; - $out .= OAuthUtil::urlencode_rfc3986($k) - . '="' - . OAuthUtil::urlencode_rfc3986($v) - . '"'; + $out .= ($first) ? ' ' : ','; + $out .= OAuthUtil::urlencode_rfc3986($k) . + '="' . + OAuthUtil::urlencode_rfc3986($v) . + '"'; $first = false; } return $out; @@ -298,7 +293,7 @@ class OAuthRequest $this->set_parameter( "oauth_signature_method", $signature_method->get_name(), - false, + false ); $signature = $this->build_signature($signature_method, $consumer, $token); $this->set_parameter("oauth_signature", $signature, false); diff --git a/src/Security/OAuth1/OAuthUtil.php b/src/Security/OAuth1/OAuthUtil.php index 1ae8bda6ec..e58522117a 100644 --- a/src/Security/OAuth1/OAuthUtil.php +++ b/src/Security/OAuth1/OAuthUtil.php @@ -12,12 +12,12 @@ class OAuthUtil public static function urlencode_rfc3986($input) { if (is_array($input)) { - return array_map([\Friendica\Security\OAuth1\OAuthUtil::class, 'urlencode_rfc3986'], $input); - } elseif (is_scalar($input)) { + return array_map(['Friendica\Security\OAuth1\OAuthUtil', 'urlencode_rfc3986'], $input); + } else if (is_scalar($input)) { return str_replace( '+', ' ', - str_replace('%7E', '~', rawurlencode($input)), + str_replace('%7E', '~', rawurlencode($input)) ); } else { return ''; @@ -72,10 +72,10 @@ class OAuthUtil // request $out = []; foreach ($headers as $key => $value) { - $key = str_replace( + $key = str_replace( " ", "-", - ucwords(strtolower(str_replace("-", " ", $key))), + ucwords(strtolower(str_replace("-", " ", $key))) ); $out[$key] = $value; } @@ -83,22 +83,20 @@ class OAuthUtil // otherwise we don't have apache and are just going to have to hope // that $_SERVER actually contains what we need $out = []; - if (isset($_SERVER['CONTENT_TYPE'])) { + if (isset($_SERVER['CONTENT_TYPE'])) $out['Content-Type'] = $_SERVER['CONTENT_TYPE']; - } - if (isset($_ENV['CONTENT_TYPE'])) { + if (isset($_ENV['CONTENT_TYPE'])) $out['Content-Type'] = $_ENV['CONTENT_TYPE']; - } foreach ($_SERVER as $key => $value) { if (substr($key, 0, 5) == "HTTP_") { // this is chaos, basically it is just there to capitalize the first // letter of every word that is not an initial HTTP and strip HTTP // code from przemek - $key = str_replace( + $key = str_replace( " ", "-", - ucwords(strtolower(str_replace("_", " ", substr($key, 5)))), + ucwords(strtolower(str_replace("_", " ", substr($key, 5)))) ); $out[$key] = $value; } @@ -112,9 +110,7 @@ class OAuthUtil // array('a' => array('b','c'), 'd' => 'e') public static function parse_parameters($input) { - if (!isset($input) || !$input) { - return []; - } + if (!isset($input) || !$input) return []; $pairs = explode('&', $input); diff --git a/src/Security/PermissionSet/Entity/PermissionSet.php b/src/Security/PermissionSet/Entity/PermissionSet.php index 892ec73019..4a3abdbe00 100644 --- a/src/Security/PermissionSet/Entity/PermissionSet.php +++ b/src/Security/PermissionSet/Entity/PermissionSet.php @@ -60,18 +60,20 @@ class PermissionSet extends BaseEntity */ public function isPublic(): bool { - return (($this->id === PermissionSetRepository::PUBLIC) - || (is_null($this->id) - && empty($this->allow_cid) - && empty($this->allow_gid) - && empty($this->deny_cid) - && empty($this->deny_gid))); + return (($this->id === PermissionSetRepository::PUBLIC) || + (is_null($this->id) && + empty($this->allow_cid) && + empty($this->allow_gid) && + empty($this->deny_cid) && + empty($this->deny_gid))); } /** * Creates a new Entity with a new allowed_cid list (wipes the id because it isn't the same entity anymore) * * @param array $allow_cid + * + * @return $this */ public function withAllowedContacts(array $allow_cid): PermissionSet { diff --git a/src/Util/Crypto.php b/src/Util/Crypto.php index 511ca2005f..084bd21808 100644 --- a/src/Util/Crypto.php +++ b/src/Util/Crypto.php @@ -56,7 +56,7 @@ class Crypto */ public static function rsaToPem(string $key) { - return (string) PublicKeyLoader::load($key); + return (string)PublicKeyLoader::load($key); } /** @@ -69,7 +69,7 @@ class Crypto $openssl_options = [ 'digest_alg' => 'sha1', 'private_key_bits' => $bits, - 'encrypt_key' => false, + 'encrypt_key' => false ]; $conf = DI::config()->get('system', 'openssl_conf_file'); @@ -160,10 +160,10 @@ class Crypto private static function encapsulateOther($data, $pubkey, $alg) { if (!$pubkey) { - DI::logger()->notice('no key. data: ' . $data); + DI::logger()->notice('no key. data: '.$data); } $fn = 'encrypt' . strtoupper($alg); - if (method_exists(self::class, $fn)) { + if (method_exists(__CLASS__, $fn)) { $result = ['encrypted' => true]; $key = random_bytes(256); $iv = random_bytes(256); @@ -262,7 +262,7 @@ class Crypto { $fn = 'decrypt' . strtoupper($alg); - if (method_exists(self::class, $fn)) { + if (method_exists(__CLASS__, $fn)) { openssl_private_decrypt(Strings::base64UrlDecode($data['key']), $k, $prvkey); openssl_private_decrypt(Strings::base64UrlDecode($data['iv']), $i, $prvkey); diff --git a/src/Util/EMailer/MailBuilder.php b/src/Util/EMailer/MailBuilder.php index a5e4497460..55f5d1ca67 100644 --- a/src/Util/EMailer/MailBuilder.php +++ b/src/Util/EMailer/MailBuilder.php @@ -25,7 +25,7 @@ use Psr\Log\LoggerInterface; abstract class MailBuilder { /** @var string The default email banner in case nothing else is defined */ - public const DEFAULT_EMAIL_BANNER = 'images/friendica-32.png'; + const DEFAULT_EMAIL_BANNER = 'images/friendica-32.png'; /** @var L10n */ protected $l10n; @@ -199,7 +199,7 @@ abstract class MailBuilder * * @return static */ - public function setHeader(string $name, string $value) + public function setHeader(string $name, string $value) { $this->headers[$name] = [$value]; @@ -218,8 +218,8 @@ abstract class MailBuilder */ public function build(bool $raw = false) { - if ((empty($this->recipientAddress)) - && !empty($this->recipientUid)) { + if ((empty($this->recipientAddress)) && + !empty($this->recipientUid)) { $user = User::getById($this->recipientUid, ['email']); if (!empty($user['email'])) { @@ -235,7 +235,7 @@ abstract class MailBuilder throw new UnprocessableEntityException('Sender address or name is missing.'); } - $this->senderNoReply ??= $this->senderAddress; + $this->senderNoReply = $this->senderNoReply ?? $this->senderAddress; $msgHtml = $this->getHtmlMessage() ?? ''; @@ -247,11 +247,8 @@ abstract class MailBuilder '$product' => App::PLATFORM, '$htmlversion' => $msgHtml, '$sitename' => $this->config->get('config', 'sitename'), - '$banner' => $this->config->get( - 'system', - 'email_banner', - $this->baseUrl . DIRECTORY_SEPARATOR . self::DEFAULT_EMAIL_BANNER, - ), + '$banner' => $this->config->get('system', 'email_banner', + $this->baseUrl . DIRECTORY_SEPARATOR . self::DEFAULT_EMAIL_BANNER), ]); } @@ -264,7 +261,6 @@ abstract class MailBuilder $msgHtml, $this->getPlaintextMessage() ?? '', $this->headers, - $this->recipientUid ?? null, - ); + $this->recipientUid ?? null); } } diff --git a/src/Util/Emailer.php b/src/Util/Emailer.php index e8f8b05ba6..3ced3e8afc 100644 --- a/src/Util/Emailer.php +++ b/src/Util/Emailer.php @@ -99,7 +99,7 @@ class Emailer $this->config, $this->logger, $this->getSiteEmailAddress(), - $this->getSiteEmailName(), + $this->getSiteEmailName() ); } @@ -116,7 +116,7 @@ class Emailer $this->config, $this->logger, $this->getSiteEmailAddress(), - $this->getSiteEmailName(), + $this->getSiteEmailName() ); } @@ -158,10 +158,10 @@ class Emailer $messageSubject = Email::encodeHeader(html_entity_decode($email->getSubject(), ENT_QUOTES, 'UTF-8'), 'UTF-8'); // generate a mime boundary - $mimeBoundary = random_int(0, 9) . '-' - . random_int(100000000, 999999999) . '-' - . random_int(100000000, 999999999) . '=:' - . random_int(10000, 99999); + $mimeBoundary = rand(0, 9) . '-' + . rand(100000000, 999999999) . '-' + . rand(100000000, 999999999) . '=:' + . rand(10000, 99999); $messageHeader = $email->getAdditionalMailHeaderString(); if ($countMessageId === 0) { @@ -169,29 +169,29 @@ class Emailer } // generate a multipart/alternative message header - $messageHeader - .= "From: $fromName <{$fromAddress}>\r\n" - . "Reply-To: $fromName <{$replyTo}>\r\n" - . "MIME-Version: 1.0\r\n" - . "Content-Type: multipart/alternative; boundary=\"{$mimeBoundary}\""; + $messageHeader .= + "From: $fromName <{$fromAddress}>\r\n" . + "Reply-To: $fromName <{$replyTo}>\r\n" . + "MIME-Version: 1.0\r\n" . + "Content-Type: multipart/alternative; boundary=\"{$mimeBoundary}\""; // assemble the final multipart message body with the text and html types included $textBody = chunk_split(base64_encode($email->getMessage(true))); $htmlBody = chunk_split(base64_encode($email->getMessage())); - $multipartMessageBody = "--" . $mimeBoundary . "\n" // plain text section - . "Content-Type: text/plain; charset=UTF-8\n" - . "Content-Transfer-Encoding: base64\n\n" - . $textBody . "\n"; + $multipartMessageBody = "--" . $mimeBoundary . "\n" . // plain text section + "Content-Type: text/plain; charset=UTF-8\n" . + "Content-Transfer-Encoding: base64\n\n" . + $textBody . "\n"; if (!$email_textonly && !is_null($email->getMessage())) { - $multipartMessageBody - .= "--" . $mimeBoundary . "\n" // text/html section - . "Content-Type: text/html; charset=UTF-8\n" - . "Content-Transfer-Encoding: base64\n\n" - . $htmlBody . "\n"; + $multipartMessageBody .= + "--" . $mimeBoundary . "\n" . // text/html section + "Content-Type: text/html; charset=UTF-8\n" . + "Content-Transfer-Encoding: base64\n\n" . + $htmlBody . "\n"; } - $multipartMessageBody - .= "--" . $mimeBoundary . "--\n"; // message ending + $multipartMessageBody .= + "--" . $mimeBoundary . "--\n"; // message ending if ($this->config->get('system', 'sendmail_params', true)) { $sendmail_params = '-f ' . $fromAddress; @@ -220,7 +220,7 @@ class Emailer $hookdata['subject'], $hookdata['body'], $hookdata['headers'], - $hookdata['parameters'], + $hookdata['parameters'] ); $this->logger->debug('Email message header', ['To' => $email->getToAddress(), 'messageHeader' => $messageHeader, 'return' => ($res) ? 'true' : 'false']); diff --git a/src/Util/Images.php b/src/Util/Images.php index 0a4d2505cf..5fcdb29381 100644 --- a/src/Util/Images.php +++ b/src/Util/Images.php @@ -20,7 +20,7 @@ use Friendica\Object\Image; class Images { // @todo add IMAGETYPE_AVIF once our minimal supported PHP version is 8.1.0 - public const IMAGETYPES = [IMAGETYPE_WEBP, IMAGETYPE_PNG, IMAGETYPE_JPEG, IMAGETYPE_GIF, IMAGETYPE_BMP]; + const IMAGETYPES = [IMAGETYPE_WEBP, IMAGETYPE_PNG, IMAGETYPE_JPEG, IMAGETYPE_GIF, IMAGETYPE_BMP]; /** * Get the Imagick format for the given image type @@ -377,21 +377,6 @@ class Images $data['description'] = $media['description']; } } - - if (function_exists('exif_read_data')) { - try { - $stream = fopen('php://memory', 'r+'); - fwrite($stream, $img_str); - rewind($stream); - $exif = @exif_read_data($stream, 'ANY_TAG, IFD0, EXIF, APP12, GPS, INTEROP'); - fclose($stream); - if (is_array($exif)) { - $data['exif'] = self::sanitizeExifArray($exif); - } - } catch (\Exception $e) { - DI::logger()->error('Exception when trying to read EXIF data', ['code' => $e->getCode(), 'message' => $e->getMessage()]); - } - } } $data['size'] = $filesize; @@ -399,22 +384,6 @@ class Images return $data; } - /** - * Sanitize EXIF array by removing invalid UTF-8 characters from strings - * - * @param array $exif - * @return array - */ - private static function sanitizeExifArray(array $exif): array - { - array_walk_recursive($exif, function (&$value) { - if (is_string($value)) { - $value = trim(iconv('UTF-8', 'UTF-8//IGNORE', $value)); - } - }); - return $exif; - } - /** * Returns scaling information * @@ -485,7 +454,7 @@ class Images return self::getBBCodeByUrl( DI::baseUrl() . '/photos/' . $nickname . '/image/' . $resource_id, DI::baseUrl() . '/photo/' . $resource_id . '-' . $preview . $ext, - $description, + $description ); } diff --git a/src/Util/Map.php b/src/Util/Map.php index 5d5c5d2a8b..f0ebad3cbc 100644 --- a/src/Util/Map.php +++ b/src/Util/Map.php @@ -12,26 +12,22 @@ use Friendica\Core\Hook; /** * Leaflet Map related functions */ -class Map -{ - public static function byCoordinates($coord, $html_mode = 0) - { +class Map { + public static function byCoordinates($coord, $html_mode = 0) { $coord = trim($coord); - $coord = str_replace([',','/',' '], [' ',' ',' '], $coord); - $arr = ['lat' => trim(substr($coord, 0, strpos($coord, ' '))), 'lon' => trim(substr($coord, strpos($coord, ' ') + 1)), 'mode' => $html_mode, 'html' => '']; - Hook::callAll('generate_map', $arr); - return $arr['html'] ?: $coord; + $coord = str_replace([',','/',' '],[' ',' ',' '],$coord); + $arr = ['lat' => trim(substr($coord,0,strpos($coord,' '))), 'lon' => trim(substr($coord,strpos($coord,' ')+1)), 'mode' => $html_mode, 'html' => '']; + Hook::callAll('generate_map',$arr); + return ($arr['html']) ? $arr['html'] : $coord; } - public static function byLocation($location, $html_mode = 0) - { + public static function byLocation($location, $html_mode = 0) { $arr = ['location' => $location, 'mode' => $html_mode, 'html' => '']; - Hook::callAll('generate_named_map', $arr); - return $arr['html'] ?: $location; + Hook::callAll('generate_named_map',$arr); + return ($arr['html']) ? $arr['html'] : $location; } - public static function getCoordinates($location) - { + public static function getCoordinates($location) { $arr = ['location' => $location, 'lat' => false, 'lon' => false]; Hook::callAll('Map::getCoordinates', $arr); return $arr; diff --git a/src/Util/ParseUrl.php b/src/Util/ParseUrl.php index d30e77490e..58b622b0a8 100644 --- a/src/Util/ParseUrl.php +++ b/src/Util/ParseUrl.php @@ -32,18 +32,18 @@ use Friendica\Model\Post; */ class ParseUrl { - public const DEFAULT_EXPIRATION_FAILURE = 'now + 1 day'; - public const DEFAULT_EXPIRATION_SUCCESS = 'now + 3 months'; + const DEFAULT_EXPIRATION_FAILURE = 'now + 1 day'; + const DEFAULT_EXPIRATION_SUCCESS = 'now + 3 months'; /** * Maximum number of characters for the description */ - public const MAX_DESC_COUNT = 250; + const MAX_DESC_COUNT = 250; /** * Minimum number of characters for the description */ - public const MIN_DESC_COUNT = 100; + const MIN_DESC_COUNT = 100; /** * Fetch the content type of the given url @@ -147,7 +147,7 @@ class ParseUrl 'created' => DateTimeFormat::utcNow(), 'expires' => $expires, ], - Database::INSERT_UPDATE, + Database::INSERT_UPDATE ); return $data; @@ -268,7 +268,7 @@ class ParseUrl if ($cacheControlHeader = $curlResult->getHeader('Cache-Control')[0] ?? '') { if (preg_match('/max-age=([0-9]+)/i', $cacheControlHeader, $matches)) { - $maxAge = max(86400, (int) array_pop($matches)); + $maxAge = max(86400, (int)array_pop($matches)); $siteinfo['expires'] = DateTimeFormat::utc("now + $maxAge seconds"); } @@ -285,7 +285,7 @@ class ParseUrl if (isset($mediaType->parameters['charset'])) { $charset = $mediaType->parameters['charset']; } - } catch (\InvalidArgumentException $e) { + } catch(\InvalidArgumentException $e) { } $siteinfo['charset'] = $charset; @@ -546,7 +546,7 @@ class ParseUrl } if (!empty($siteinfo['image'])) { - $siteinfo['images'] ??= []; + $siteinfo['images'] = $siteinfo['images'] ?? []; array_unshift($siteinfo['images'], ['url' => $siteinfo['image']]); unset($siteinfo['image']); } @@ -1282,22 +1282,22 @@ class ParseUrl } $content = JsonLD::fetchElement($jsonld, 'height'); - if (!empty($content) && is_string($content) && is_numeric($content)) { + if (!empty($content) && is_string($content)) { $media['height'] = trim($content); } $content = JsonLD::fetchElement($jsonld, 'width'); - if (!empty($content) && is_string($content) && is_numeric($content)) { + if (!empty($content) && is_string($content)) { $media['width'] = trim($content); } $content = JsonLD::fetchElement($jsonld, 'duration'); - if (!empty($content) && is_string($content) && is_numeric($content)) { + if (!empty($content) && is_string($content)) { $media['duration'] = trim($content); } $content = JsonLD::fetchElement($jsonld, 'contentSize'); - if (!empty($content) && is_string($content) && is_numeric($content)) { + if (!empty($content) && is_string($content)) { $media['size'] = trim($content); } @@ -1394,7 +1394,7 @@ class ParseUrl DI::logger()->debug('Found oEmbed JSON from Embera', ['url' => $url]); } - if (!isset($data['type']) || !isset($data['provider_url'])) { + if (!isset($data['type']) || !isset($data['provider_url'])) { return []; } @@ -1751,10 +1751,6 @@ class ParseUrl return $siteinfo; } - if ($data['html'] == '') { - return $siteinfo; - } - $dom = new DOMDocument(); if (!@$dom->loadHTML($data['html'])) { return $siteinfo; diff --git a/src/Util/Strings.php b/src/Util/Strings.php index a7998d4290..e971d4651b 100644 --- a/src/Util/Strings.php +++ b/src/Util/Strings.php @@ -96,17 +96,17 @@ class Strings 'v', 'w', 'wh', 'x', - 'z', 'zh', + 'z', 'zh' ]; $midcons = [ 'ck', 'ct', 'gn', 'ld', 'lf', 'lm', 'lt', 'mb', 'mm', 'mn', 'mp', - 'nd', 'ng', 'nk', 'nt', 'rn', 'rp', 'rt', + 'nd', 'ng', 'nk', 'nt', 'rn', 'rp', 'rt' ]; $noend = [ 'bl', 'br', 'cl', 'cr', 'dr', 'fl', 'fr', 'gl', 'gr', - 'kh', 'kl', 'kr', 'mn', 'pl', 'pr', 'rh', 'tr', 'qu', 'wh', 'q', + 'kh', 'kl', 'kr', 'mn', 'pl', 'pr', 'rh', 'tr', 'qu', 'wh', 'q' ]; $start = mt_rand(0, 2); @@ -444,7 +444,7 @@ class Strings { $string_length = mb_strlen($string); - $length ??= $string_length; + $length = $length ?? $string_length; if ($start < 0) { $start = max(0, $string_length + $start); @@ -494,7 +494,7 @@ class Strings return $return; }, - $text, + $text ); if (is_null($return)) { @@ -513,7 +513,7 @@ class Strings } return $return; }, - $text, + $text ); return $text; diff --git a/src/Util/Temporal.php b/src/Util/Temporal.php index ccf708522a..cc9baa53a9 100644 --- a/src/Util/Temporal.php +++ b/src/Util/Temporal.php @@ -61,7 +61,7 @@ class Temporal $o = ' {{include file="field_checkbox.tpl" field=$debugging}} {{include file="field_input.tpl" field=$logfile}} {{include file="field_select.tpl" field=$loglevel}} - -
    - + +
    +

    {{$phpheader}}

    @@ -24,5 +24,5 @@

    {{$phphint}}

    {{$phplogcode}}
    - + diff --git a/view/templates/admin/logs/view.tpl b/view/templates/admin/logs/view.tpl index 65fc11a5ac..a94f1284be 100644 --- a/view/templates/admin/logs/view.tpl +++ b/view/templates/admin/logs/view.tpl @@ -7,7 +7,7 @@

    {{$title}} - {{$page}}

    -

    {{$logname}}

    +

    {{$logname}}

    {{if $error }}

    {{$error nofilter}}

    diff --git a/view/templates/admin/queue.tpl b/view/templates/admin/queue.tpl index ad00b12e40..7c35edf18c 100644 --- a/view/templates/admin/queue.tpl +++ b/view/templates/admin/queue.tpl @@ -4,19 +4,11 @@ * * SPDX-License-Identifier: AGPL-3.0-or-later *}} -
    +

    {{$title}} - {{$page}} ({{$count}})

    {{$info}}

    - - - - - - {{if ($status == "deferred") }}{{/if}} - - diff --git a/view/templates/admin/summary.tpl b/view/templates/admin/summary.tpl index d498bc5661..117a06600f 100644 --- a/view/templates/admin/summary.tpl +++ b/view/templates/admin/summary.tpl @@ -23,8 +23,8 @@
    {{$addons.0}}
    - {{foreach $addons.1 as $a}} -
    {{$a.1.name}}
    + {{foreach $addons.1 as $p}} +
    {{$p}}
    {{/foreach}}
    diff --git a/view/templates/album_edit.tpl b/view/templates/album_edit.tpl index 6e6614ab82..59a042abce 100644 --- a/view/templates/album_edit.tpl +++ b/view/templates/album_edit.tpl @@ -10,7 +10,7 @@ - +
    diff --git a/view/templates/auto_request.tpl b/view/templates/auto_request.tpl index 1a41c39008..50b7ee2617 100644 --- a/view/templates/auto_request.tpl +++ b/view/templates/auto_request.tpl @@ -35,7 +35,7 @@ {{$myaddr}} {{else}} - + {{/if}}
    diff --git a/view/templates/circle_edit.tpl b/view/templates/circle_edit.tpl index b520ebaeb1..fc2ec01a1a 100644 --- a/view/templates/circle_edit.tpl +++ b/view/templates/circle_edit.tpl @@ -17,9 +17,6 @@ {{if $drop}}{{$drop nofilter}}{{/if}}
    -
    diff --git a/view/templates/contact_edit.tpl b/view/templates/contact_edit.tpl index aa1b174611..b6c22a63b2 100644 --- a/view/templates/contact_edit.tpl +++ b/view/templates/contact_edit.tpl @@ -27,7 +27,7 @@ {{if $lblsuggest}}
  • {{$contact_actions.suggest.label}}
  • {{/if}} {{if $poll_enabled}}
  • {{$contact_actions.update.label}}
  • {{/if}} {{if $contact_actions.updateprofile}}
  • {{$contact_actions.updateprofile.label}}
  • {{/if}} - {{if $contact_actions.fetchoutbox}}
  • {{$contact_actions.fetchoutbox.label}}
  • {{/if}} + {{if $contact_actions.}}
  • {{$contact_actions.fetchoutbox.label}}
  • {{/if}}
  • {{$contact_actions.block.label}}
  • {{$contact_actions.ignore.label}}
  • diff --git a/view/templates/directory_header.tpl b/view/templates/directory_header.tpl index 84a8f0d5f1..0eb62a3367 100644 --- a/view/templates/directory_header.tpl +++ b/view/templates/directory_header.tpl @@ -18,7 +18,6 @@ {{$desc}} -

    {{$num_results_text}}

    @@ -30,8 +29,6 @@ {{foreach $contacts as $contact}} {{include file="contact/entry.tpl"}} -{{foreachelse}} -
    {{$no_results}}
    {{/foreach}}
    diff --git a/view/templates/item/compose.tpl b/view/templates/item/compose.tpl index 6364c471e7..cf54b3258a 100644 --- a/view/templates/item/compose.tpl +++ b/view/templates/item/compose.tpl @@ -1,237 +1,226 @@ {{* - * Copyright (C) 2010-2026, the Friendica project + * Copyright (C) 2010-2024, the Friendica project * SPDX-FileCopyrightText: 2010-2024 the Friendica project * * SPDX-License-Identifier: AGPL-3.0-or-later *}}
    -

    {{$l10n.compose_title}}

    - {{if $l10n.always_open_compose}} -

    {{$l10n.always_open_compose nofilter}}

    - {{/if}} -
    -
    - - - +

    {{$l10n.compose_title}}

    + {{if $l10n.always_open_compose}} +

    {{$l10n.always_open_compose nofilter}}

    + {{/if}} +
    + + {{**}} + + + -
    - -
    - {{if $l10n.placeholdersummary}} -
    - +
    +
    + {{if $l10n.placeholdercategory}} +
    + +
    {{/if}} - {{if $l10n.placeholdercategory}} -
    - -
    - {{/if}} - + -
    -

    - -

    -
    -
    - {{if $type == 'post'}} -
    - - - -
    - {{/if}} -
    - - - - - - -
    -
    +
    diff --git a/view/templates/jot.tpl b/view/templates/jot.tpl index 80468d83a8..274c937018 100644 --- a/view/templates/jot.tpl +++ b/view/templates/jot.tpl @@ -24,9 +24,6 @@ {{/if}}
    - {{if $placeholdersummary}} -
    - {{/if}} {{if $placeholdercategory}}
    {{/if}} @@ -90,7 +87,6 @@
    {{$acl nofilter}} - {{include file="field_checkbox.tpl" field=$sensitive}} {{if $scheduled_at}}{{$scheduled_at nofilter}}{{/if}} {{if $created_at}}{{$created_at nofilter}}{{/if}}
    diff --git a/view/templates/message_side.tpl b/view/templates/message_side.tpl index 45d8eb8d2e..2f06c7ca59 100644 --- a/view/templates/message_side.tpl +++ b/view/templates/message_side.tpl @@ -4,14 +4,15 @@ * * SPDX-License-Identifier: AGPL-3.0-or-later *}} - -{{if $tabs}} - diff --git a/view/templates/photo_edit.tpl b/view/templates/photo_edit.tpl index 3c514b92d0..b1d8c7fb43 100644 --- a/view/templates/photo_edit.tpl +++ b/view/templates/photo_edit.tpl @@ -7,10 +7,12 @@
    + {{include file="field_input.tpl" field=$album}} {{include file="field_input.tpl" field=$caption}} + {{include file="field_input.tpl" field=$tags}} {{include file="field_radio.tpl" field=$rotate_none}} {{include file="field_radio.tpl" field=$rotate_cw}} diff --git a/view/templates/photo_view.tpl b/view/templates/photo_view.tpl index d9b0d2939d..c732760af3 100644 --- a/view/templates/photo_view.tpl +++ b/view/templates/photo_view.tpl @@ -33,5 +33,23 @@ {{if $nextlink}}{{/if}}
    {{$desc nofilter}}
    +{{if $tags}} +
    {{$tags.0}}
    +
    {{$tags.1}}
    +{{/if}} +{{if $tags.2}}{{/if}} + {{if $edit}}{{$edit nofilter}}{{/if}} +{{if $likebuttons}} +
    + {{$likebuttons nofilter}} + {{$like nofilter}} + {{$dislike nofilter}} +
    +{{/if}} + +{{$comments nofilter}} + +{{$paginate nofilter}} + diff --git a/view/templates/photos_default_uploader_box.tpl b/view/templates/photos_default_uploader_box.tpl index a7ecbd4c89..c03aa2b822 100644 --- a/view/templates/photos_default_uploader_box.tpl +++ b/view/templates/photos_default_uploader_box.tpl @@ -4,4 +4,5 @@ * * SPDX-License-Identifier: AGPL-3.0-or-later *}} - + + diff --git a/view/templates/photos_upload.tpl b/view/templates/photos_upload.tpl index 20d8090e0d..c7ce82e529 100644 --- a/view/templates/photos_upload.tpl +++ b/view/templates/photos_upload.tpl @@ -10,24 +10,26 @@
    {{$usage}}
    -{{if $is_album_context}} - -{{else}}
    - +
    -
    {{$albumtext_description}}
    +
    {{$existalbumtext}}
    -{{/if}} + +
    + + +
    + {{/if}} diff --git a/view/templates/prv_message.tpl b/view/templates/prv_message.tpl index 2962da9efa..8bc4a443f9 100644 --- a/view/templates/prv_message.tpl +++ b/view/templates/prv_message.tpl @@ -17,7 +17,7 @@ {{$select nofilter}}
    {{$subject}}
    - +
    {{$yourmessage}}
    @@ -27,13 +27,13 @@
    -
    +
    +
    -
    +
    diff --git a/view/templates/register.tpl b/view/templates/register.tpl index d8654e95c9..a32ddb81db 100644 --- a/view/templates/register.tpl +++ b/view/templates/register.tpl @@ -28,7 +28,7 @@ {{if $oidlabel}}
    - +
    {{/if}} @@ -37,14 +37,14 @@

    {{$invite_desc nofilter}}

    - +
    {{/if}}
    - +
    @@ -52,13 +52,13 @@ {{if !$additional}}
    - +
    - +
    {{/if}} @@ -72,7 +72,7 @@
    -
    @{{$sitename}}
    +
    @{{$sitename}}
    diff --git a/view/templates/register_closed.tpl b/view/templates/register_closed.tpl deleted file mode 100644 index a2e69fcf75..0000000000 --- a/view/templates/register_closed.tpl +++ /dev/null @@ -1,12 +0,0 @@ -{{* - * Copyright (C) 2010-2024, the Friendica project - * SPDX-FileCopyrightText: 2010-2024 the Friendica project - * - * SPDX-License-Identifier: AGPL-3.0-or-later - *}} -
    -

    {{$title}}

    -

    {{$message}}

    -

    {{$explanation}}

    -

    {{$find_server nofilter}}

    -
    diff --git a/view/templates/settings/display.tpl b/view/templates/settings/display.tpl index 4025041d67..ce9fce88ab 100644 --- a/view/templates/settings/display.tpl +++ b/view/templates/settings/display.tpl @@ -59,13 +59,6 @@

    {{$channel_title}}

    {{include file="field_select.tpl" field=$channel_languages}} - {{if $has_timeline_channels}} - {{include file="field_select.tpl" field=$timeline_channels}} - {{/if}} - {{if $has_filter_channels}} - {{include file="field_select.tpl" field=$filter_channels}} - {{/if}} -

    {{$calendar_title}}

    {{include file="field_select.tpl" field=$first_day_of_week}} {{include file="field_select.tpl" field=$calendar_default_view}} diff --git a/view/templates/settings/head.tpl b/view/templates/settings/head.tpl index 9e11aea61a..cb8c2e4a2a 100644 --- a/view/templates/settings/head.tpl +++ b/view/templates/settings/head.tpl @@ -8,82 +8,10 @@ diff --git a/view/templates/settings/profile/photo/index.tpl b/view/templates/settings/profile/photo/index.tpl index 2b079564af..e298a1bb52 100644 --- a/view/templates/settings/profile/photo/index.tpl +++ b/view/templates/settings/profile/photo/index.tpl @@ -15,7 +15,7 @@
    - +
    diff --git a/view/templates/widget/follow.tpl b/view/templates/widget/follow.tpl index c91c6ecfce..3e447f2480 100644 --- a/view/templates/widget/follow.tpl +++ b/view/templates/widget/follow.tpl @@ -9,6 +9,6 @@

    {{$connect}}

    {{$desc nofilter}}
    - + diff --git a/view/templates/widget/peoplefind.tpl b/view/templates/widget/peoplefind.tpl index 0c45b4455f..86c1040bae 100644 --- a/view/templates/widget/peoplefind.tpl +++ b/view/templates/widget/peoplefind.tpl @@ -9,7 +9,7 @@

    {{$nv.findpeople}}

    {{$nv.desc}}
    - + @@ -20,4 +20,4 @@ {{if $nv.inv}} {{/if}} - + \ No newline at end of file diff --git a/view/theme/frio/config.php b/view/theme/frio/config.php index c0798b19ca..6d36fa443f 100644 --- a/view/theme/frio/config.php +++ b/view/theme/frio/config.php @@ -150,7 +150,7 @@ function frio_form($arr) $scheme_info = get_scheme_info($arr['scheme']); $disable = $scheme_info['overwrites']; - $background_image_help = '' . DI::l10n()->t('Note') . ': ' . DI::l10n()->t('Ensure that the image has the correct permissions, allowing all users to view it.'); + $background_image_help = '' . DI::l10n()->t('Note') . ': ' . DI::l10n()->t('Check image permissions if all users are allowed to see the image'); $t = Renderer::getMarkupTemplate('theme_settings.tpl'); $ctx = [ @@ -159,7 +159,7 @@ function frio_form($arr) '$title' => DI::l10n()->t('Theme settings'), '$scheme' => ['frio_scheme', DI::l10n()->t('Appearance'), $arr['scheme'], frio_scheme_get_list()], '$scheme_accent' => !$scheme_info['accented'] ? '' : ['frio_scheme_accent', DI::l10n()->t('Accent color'), $arr['scheme_accent']], - '$share_string' => $arr['scheme'] != FRIO_CUSTOM_SCHEME ? '' : ['frio_share_string', DI::l10n()->t('Copy or paste theme settings'), $arr['share_string'], DI::l10n()->t('You can copy this text to share your theme settings with others. Pasting here updates the theme settings below. Afterwards, if you want, click the save button below to use the new settings.'), false, false], + '$share_string' => $arr['scheme'] != FRIO_CUSTOM_SCHEME ? '' : ['frio_share_string', DI::l10n()->t('Copy or paste schemestring'), $arr['share_string'], DI::l10n()->t('You can copy this string to share your theme with others. Pasting here applies the schemestring'), false, false], '$nav_bg' => array_key_exists('nav_bg', $disable) ? '' : ['frio_nav_bg', DI::l10n()->t('Navigation bar background color'), $arr['nav_bg'], '', false], '$nav_icon_color' => array_key_exists('nav_icon_color', $disable) ? '' : ['frio_nav_icon_color', DI::l10n()->t('Navigation bar icon color '), $arr['nav_icon_color'], '', false], '$link_color' => array_key_exists('link_color', $disable) ? '' : ['frio_link_color', DI::l10n()->t('Link color'), $arr['link_color'], '', false], @@ -169,7 +169,7 @@ function frio_form($arr) '$bg_image_options_title' => DI::l10n()->t('Background image style'), '$bg_image_options' => Image::get_options($arr), - '$always_open_compose' => ['frio_always_open_compose', DI::l10n()->t('Always open Compose page'), $arr['always_open_compose'], DI::l10n()->t('If enabled, the button to make a new post always opens a dedicated page (the Compose page) instead of a small window on top of the current page. When disabled, the "Compose page" can be accessed with a middle click on the button to make a new post, or via a button in the small window.')], + '$always_open_compose' => ['frio_always_open_compose', DI::l10n()->t('Always open Compose page'), $arr['always_open_compose'], DI::l10n()->t('The New Post button always open the Compose page instead of the modal form. When this is disabled, the Compose page can be accessed with a middle click on the link or from the modal.')], ]; if (array_key_exists('login_bg_image', $arr) && !array_key_exists('login_bg_image', $disable)) { @@ -177,7 +177,7 @@ function frio_form($arr) } if (array_key_exists('login_bg_color', $arr) && !array_key_exists('login_bg_color', $disable)) { - $ctx['$login_bg_color'] = ['frio_login_bg_color', DI::l10n()->t('Login page background color'), $arr['login_bg_color'], DI::l10n()->t('Leave background image and color empty to use theme defaults.'), false]; + $ctx['$login_bg_color'] = ['frio_login_bg_color', DI::l10n()->t('Login page background color'), $arr['login_bg_color'], DI::l10n()->t('Leave background image and color empty for theme defaults'), false]; } return Renderer::replaceMacros($t, $ctx); diff --git a/view/theme/frio/css/style.css b/view/theme/frio/css/style.css index c5fdd262c6..f251230581 100644 --- a/view/theme/frio/css/style.css +++ b/view/theme/frio/css/style.css @@ -645,16 +645,6 @@ nav.navbar .nav > li > button:hover, nav.navbar .nav > li > button:focus { background-color: $nav_icon_hover_color; } -/* Current section highlighting */ -#topbar-first .nav > li > a.selected i, -#nav-notification.dropdown.open { - border-bottom: 3px solid $nav_icon_color; - padding-bottom: 7px; -} -/* Offset the overflow caused by the current section highlighting padding above by a lowered height */ -#topbar-first .nav > li > a.selected:hover { - height: 50px; -} #topbar-first .nav > .account { height: 50px; margin-left: 20px; @@ -933,7 +923,7 @@ nav.navbar .nav > li > button:focus { font-size: 1.6em; } -#search-box #nav-search-input-field { +#nav-search-input-field { width: 296px; } @@ -1292,7 +1282,7 @@ aside .vcard .fn { } aside .vcard .p-addr { font-style: italic; - padding-bottom: 7px; + padding-bottom: 2px; text-align: center; word-wrap: break-word; } @@ -1306,9 +1296,6 @@ aside .vcard .detail { aside .xmpp, aside .matrix { display: table; } -aside .member-since { - padding-top: 5px; -} aside .vcard .icon { display: table-cell; padding-right: 10px; @@ -1534,9 +1521,8 @@ section #jotOpen { margin-right: 20px; } #jot-title-wrap, -#jot-summary-wrap, #jot-category-wrap { - margin-bottom: 10px; + margin-bottom: 5px; } #jot-text-wrap { margin-top: 20px; @@ -1565,14 +1551,6 @@ textarea.comment-edit-text:focus + .comment-edit-form .preview { border: 2px solid #6fdbe8; border-top: none; } -#compose-additional-settings-location { - align-items: center; - display: flex; - flex-grow: 1; - gap: 5px; - padding-bottom: 15px; -} - .preview hr.previewseparator { margin-top: 0px; border-color: #d2d2d2; @@ -1709,9 +1687,6 @@ textarea.comment-edit-text:focus + .comment-edit-form .preview { .fbrowser.photo .photo-album-image-wrapper .jg-caption { pointer-events: none; } -.fbswitcher { - margin-top: -3px; -} /* Photo album hover effect using jg-caption */ .photo-top-image-wrapper { @@ -2329,9 +2304,7 @@ wall-item-comment-wrapper.well hr { } .comment-edit-submit-wrapper { - display: flex; - flex-wrap: wrap; - justify-content: flex-end; + text-align: right; margin-bottom: 0; } @@ -2809,11 +2782,6 @@ ul li:hover .contact-wrapper .contact-action-link:hover { padding-top: 10px; } -#num-results { - padding-top: 20px; - margin-bottom: 0; -} - /* circle edit page */ .circle-actions { margin-top: 4px; @@ -3000,10 +2968,6 @@ ul li:hover .contact-wrapper .contact-action-link:hover { margin-top: 15px; } -#photos-usage-message { - padding-bottom: 15px; -} - /* Events page */ .fc .fc-month-view .fc-content .fc-title .item-desc:hover { @@ -3552,14 +3516,6 @@ section.help-content-wrapper li { #admin-summary-wrapper { padding-top: 10px; } -#admin-summary-wrapper h2 { - font-size: 21px; - padding-top: 13px; - padding-bottom: 8px; -} -#admin-summary-addons tr td:first-child { - width: clamp(30px, 20vw, 200px); -} #adminpage ul#addonslist, li.addon { list-style: none; @@ -3608,10 +3564,6 @@ li.addon { #adminpage h2 { word-break: break-all; } -#adminpage .form-group-search { - display: flex; - align-items: center; -} #adminpage .table-logs thead tr { display: grid; grid-auto-flow: row; @@ -3630,10 +3582,6 @@ li.addon { #logdetail td.log-message { width: 80%; } -#adminpage #nav-search-input-field { - display: inline; - max-width: 296px; -} @media (min-width: 600px) { #adminpage .table-logs thead tr { grid-auto-flow: column; @@ -3642,17 +3590,6 @@ li.addon { width: 60%; } } -#adminpage td, -.adminpage td, -#adminpage .table td, -.adminpage .table td, -#adminpage.table > tbody > tr > td, -.adminpage.table > tbody > tr > td, -.table#adminpage > tbody > tr > td { - word-break: break-all !important; - white-space: normal !important; - vertical-align: top !important; -} #admin-users #users tr.blocked { background-color: #f8efc0; } @@ -4138,7 +4075,7 @@ div.login-form, .login-content-wrapper, .mod-home.is-not-singleuser .login-form, .mod-login #content .login-form { - background-color: $background_color; + background-color: #fff; padding: 1.5em; position: relative; } @@ -4353,4 +4290,3 @@ div.login-form, .login-content-wrapper, padding-left: 3px; } } - diff --git a/view/theme/frio/js/compose.js b/view/theme/frio/js/compose.js index 91445b2790..9b2dc50e45 100644 --- a/view/theme/frio/js/compose.js +++ b/view/theme/frio/js/compose.js @@ -3,374 +3,63 @@ // SPDX-License-Identifier: AGPL-3.0-or-later // @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPLv3-or-later - -/** - * @file view/theme/frio/js/compose.js - * JavaScript for the compose page (/compose). - * - * Handles: - * - Link preview attachment - * - Character counter - * - ACL autocomplete (@-mentions) - * - BBCode autocomplete - * - Location button with geolocation support - * - * @requires jQuery - * @requires linkPreview.js - * @requires autocomplete.js - */ - -(function ($, window, document) { - "use strict"; - - // DOM element references (cached) - let $textarea = null; - let locationButton = null; - let locationInput = null; - - /** - * Initialize the compose page functionality. - */ - function init() { - initTextarea(); - initLocation(); - initFormReset(); - } - - /** - * Handle form reset after successful submission and draft saving. - * Clears the form when a post was successfully submitted to prevent - * showing old content on the next visit to /compose. - * Also saves/restores scheduling settings as draft. - */ - function initFormReset() { - const $form = $("form.comment-edit-form"); - - // Check if sessionStorage is available and if a post was submitted - // This flag is set in the submit handler below - if (isStorageAvailable() && sessionStorage.getItem("compose_post_submitted") === "true") { - // Clear the flag - sessionStorage.removeItem("compose_post_submitted"); - - // Clear saved draft data - sessionStorage.removeItem("compose_draft"); - - // Clear the form after a short delay to let browser autofill complete - setTimeout(function () { - if ($textarea && $textarea.length) { - $textarea.val(""); - updateCharacterCounter(0); - } - - // Also clear location if set - if (locationInput) { - locationInput.value = ""; - updateLocationButtonDisplay(locationButton, locationInput); - } - - // Clear any link preview - if (typeof window.linkPreview === "object" && window.linkPreview !== null) { - window.linkPreview.destroy(); - window.linkPreview = null; - } - - // Reset scheduled time - $('[name="scheduled_at"]').val(""); - }, 50); - } else { - // No submission - try to restore draft if available - restoreDraft(); - } - - // Save draft when form values change (but not on submit) - $form.on("change.compose-draft input.compose-draft", function (e) { - // Don't save if this is triggered by programmatic changes - if (e.isTrigger) { - return; - } - saveDraft(); - }); - - // Listen for form submission - $form.on("submit.compose", function () { - // Mark that the form was submitted - // This flag will be checked when the page loads next time - if (isStorageAvailable()) { - sessionStorage.setItem("compose_post_submitted", "true"); - } - }); - } - - /** - * Check if sessionStorage is available. - * @returns {boolean} - */ - function isStorageAvailable() { - try { - const test = "__test__"; - sessionStorage.setItem(test, test); - sessionStorage.removeItem(test); - return true; - } catch (e) { - return false; - } - } - - /** - * Save current form state as draft to sessionStorage. - */ - function saveDraft() { - if (!$textarea || !$textarea.length || !isStorageAvailable()) { - return; - } - - const draft = { - body: $textarea.val(), - location: locationInput ? locationInput.value : "", - scheduled_at: $('[name="scheduled_at"]').val(), - }; - - try { - sessionStorage.setItem("compose_draft", JSON.stringify(draft)); - } catch (e) { - // Ignore storage errors (e.g., quota exceeded, private mode) - } - } - - /** - * Restore form state from sessionStorage draft. - */ - function restoreDraft() { - if (!isStorageAvailable()) { - return; - } - - try { - const draftJson = sessionStorage.getItem("compose_draft"); - if (!draftJson) { - return; - } - - const draft = JSON.parse(draftJson); - - // Restore values after a short delay to override browser autofill - setTimeout(function () { - if (typeof draft.body !== "undefined" && $textarea && $textarea.length) { - $textarea.val(draft.body); - updateCharacterCounter(draft.body.length); - } - - // Restore location (check for undefined, not falsy, so empty string works) - if (typeof draft.location !== "undefined" && locationInput) { - locationInput.value = draft.location; - updateLocationButtonDisplay(locationButton, locationInput); - // Trigger change event so other scripts can react - $(locationInput).trigger("change"); - } - - // Restore scheduled time (check for undefined, not falsy) - const $scheduledAt = $('[name="scheduled_at"]'); - if (typeof draft.scheduled_at !== "undefined" && $scheduledAt.length) { - $scheduledAt.val(draft.scheduled_at); - // Trigger change event for datepicker plugins - $scheduledAt.trigger("change"); - } - }, 100); - } catch (e) { - // Ignore parse errors - sessionStorage.removeItem("compose_draft"); - } - } - - /** - * Initialize textarea features: link preview, autocomplete, character counter. - */ - function initTextarea() { - $textarea = $("textarea[name=body]"); - - if (!$textarea.length) { - return; - } - - // Initialize link preview plugin - if (typeof $.fn.linkPreview === "function") { - $textarea.linkPreview(); - } - - // Initialize ACL autocomplete (@-mentions) - if (typeof $.fn.editor_autocomplete === "function" && typeof baseurl !== "undefined") { - $textarea.editor_autocomplete(baseurl + "/search/acl"); - } - - // Initialize BBCode autocomplete - if (typeof $.fn.bbco_autocomplete === "function") { - $textarea.bbco_autocomplete("bbcode"); - } - - // Character counter - use input event to catch all changes (paste, cut, etc.) - $textarea.on("input.compose", function () { - updateCharacterCounter($(this).val().length); - }); - - // Initial count - updateCharacterCounter($textarea.val().length); - } - - /** - * Update the character counter display. - * - * @param {number} count - The character count - */ - function updateCharacterCounter(count) { - $("#character-counter").text(count); - } - - /** - * Initialize location button functionality. - */ - function initLocation() { - locationButton = document.getElementById("profile-location"); - locationInput = document.getElementById("jot-location"); - - if (!locationButton || !locationInput) { - return; - } - - // Set initial button state - updateLocationButtonDisplay(locationButton, locationInput); - - // Bind to both input and change events for robustness - $(locationInput) - .on("input.compose change.compose", function () { - updateLocationButtonDisplay(locationButton, locationInput); - }); - - // Location button click handler - $(locationButton).on("click.compose", function () { - handleLocationButtonClick(); - }); - } - - /** - * Handle the location button click. - */ - function handleLocationButtonClick() { - if (!locationButton || !locationInput) { - return; - } - - // If location is already set, clear it - if (locationInput.value) { - locationInput.value = ""; - updateLocationButtonDisplay(locationButton, locationInput); - // Trigger change event to save draft - $(locationInput).trigger("change"); - return; - } - - // Otherwise, try to get geolocation - if (!("geolocation" in navigator)) { - // Geolocation not supported - button should already be disabled - return; - } - - // Temporarily disable button while getting position - locationButton.disabled = true; - - navigator.geolocation.getCurrentPosition( - handleGeolocationSuccess, - handleGeolocationError, - { - enableHighAccuracy: false, - timeout: 10000, - maximumAge: 600000, // 10 minutes - } - ); - } - - /** - * Handle successful geolocation retrieval. - * - * @param {GeolocationPosition} position - */ - function handleGeolocationSuccess(position) { - if (!locationInput || !locationButton) { - return; - } - - const coords = position.coords; - locationInput.value = coords.latitude + ", " + coords.longitude; - locationButton.disabled = false; - updateLocationButtonDisplay(locationButton, locationInput); - // Trigger change event to save draft - $(locationInput).trigger("change"); - } - - /** - * Handle geolocation error. - * - * @param {GeolocationPositionError} error - */ - function handleGeolocationError(error) { - if (!locationButton) { - return; - } - - // Re-enable button so user can try again (unless geolocation is unsupported) - if ("geolocation" in navigator) { - locationButton.disabled = false; - } - - updateLocationButtonDisplay(locationButton, locationInput); - - // Log error for debugging (non-critical) - if (typeof console !== "undefined" && console.warn) { - console.warn("Geolocation error:", error.message); - } - } - - /** - * Update the location button display based on current state. - * - * @param {HTMLButtonElement} button - The location button element - * @param {HTMLInputElement} input - The location input element - */ - function updateLocationButtonDisplay(button, input) { - if (!button || !input) { - return; - } - - const hasValue = !!input.value; - const hasGeolocation = "geolocation" in navigator; - - // Remove primary class first (will be re-added if needed) - button.classList.remove("btn-primary"); - - if (hasValue) { - // Location is set - button clears it - button.disabled = false; - button.classList.add("btn-primary"); - button.title = button.dataset.titleClear || "Clear location"; - } else if (!hasGeolocation) { - // Geolocation not supported - button.disabled = true; - button.title = button.dataset.titleUnavailable || "Geolocation not available"; - } else if (button.disabled) { - // Geolocation supported but button disabled (error state) - button.title = button.dataset.titleDisabled || "Location unavailable"; - } else { - // Ready to get location - button.title = button.dataset.titleSet || "Set location"; - } - } - - // Initialize on DOM ready - $(function () { - init(); +$(function () { + // Jot attachment live preview. + let $textarea = $("textarea[name=body]"); + $textarea.linkPreview(); + $textarea.keyup(function () { + var textlen = $(this).val().length; + $("#character-counter").text(textlen); }); + $textarea.editor_autocomplete(baseurl + "/search/acl"); + $textarea.bbco_autocomplete("bbcode"); - // Expose public API - window.updateLocationButtonDisplay = updateLocationButtonDisplay; + let location_button = document.getElementById("profile-location"); + let location_input = document.getElementById("jot-location"); -})(jQuery, window, document); + if (location_button && location_input) { + updateLocationButtonDisplay(location_button, location_input); + + location_input.addEventListener("change", function () { + updateLocationButtonDisplay(location_button, location_input); + }); + location_input.addEventListener("keyup", function () { + updateLocationButtonDisplay(location_button, location_input); + }); + + location_button.addEventListener("click", function () { + if (location_input.value) { + location_input.value = ""; + updateLocationButtonDisplay(location_button, location_input); + } else if ("geolocation" in navigator) { + navigator.geolocation.getCurrentPosition( + function (position) { + location_input.value = position.coords.latitude + ", " + position.coords.longitude; + updateLocationButtonDisplay(location_button, location_input); + }, + function (error) { + location_button.disabled = true; + updateLocationButtonDisplay(location_button, location_input); + }, + ); + } + }); + } +}); + +function updateLocationButtonDisplay(location_button, location_input) { + location_button.classList.remove("btn-primary"); + if (location_input.value) { + location_button.disabled = false; + location_button.classList.add("btn-primary"); + location_button.title = location_button.dataset.titleClear; + } else if (!"geolocation" in navigator) { + location_button.disabled = true; + location_button.title = location_button.dataset.titleUnavailable; + } else if (location_button.disabled) { + location_button.title = location_button.dataset.titleDisabled; + } else { + location_button.title = location_button.dataset.titleSet; + } +} // @license-end diff --git a/view/theme/frio/js/jot.js b/view/theme/frio/js/jot.js index 16f05664e1..275b23f53c 100644 --- a/view/theme/frio/js/jot.js +++ b/view/theme/frio/js/jot.js @@ -3,84 +3,43 @@ // SPDX-License-Identifier: AGPL-3.0-or-later // @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPLv3-or-later - -/** - * @file view/theme/frio/js/jot.js - * JavaScript for link attachment in the jot (post composer). - * - * Handles inserting links into posts with automatic preview generation. - * - * @requires jQuery - * @requires linkPreview.js - * @requires autosize - * - * Note: The linkPreview variable is intentionally global for cross-module access. - */ - -// Global linkPreview instance for cross-module access -// eslint-disable-next-line no-var +// We append the linkPreview to a global Variable to make linkPreview +// accessable on other places. Note: search on other places before you +// delete or move the variable. var linkPreview; -(function ($, window) { - "use strict"; - - /** - * Prompt for a link URL and insert it into the jot with preview. - * - * @returns {void} - */ - window.jotGetLink = function () { - // Check for required global variables and functions - if (typeof window.aStr === "undefined" || !window.aStr.linkurl) { - return; - } - - const reply = prompt(window.aStr.linkurl); - - if (!reply || !reply.length) { - return; - } - - const $textarea = $("#profile-jot-text"); - - if (!$textarea.length) { - return; - } - - const currentText = $textarea.val(); - - // Clear previous attachment preview +/** + * Insert a link into friendica jot. + * + * @returns {void} + */ +function jotGetLink() { + var currentText = $("#profile-jot-text").val(); + var noAttachment = ""; + reply = prompt(aStr.linkurl); + if (reply && reply.length) { + // There should be only one attachment per post. + // So we need to remove the old one. $("#jot-attachment-preview").empty(); $("#profile-rotator").show(); + if (currentText.includes("[attachment") && currentText.includes("[/attachment]")) { + noAttachment = "&noAttachment=1"; + } - // Check if post already has an attachment - const hasAttachment = currentText.includes("[attachment") && - currentText.includes("[/attachment]"); - const noAttachment = hasAttachment ? "&noAttachment=1" : ""; - - // Use linkPreview library if available - if (typeof linkPreview === "object" && linkPreview !== null) { + // We use the linkPreview library to have a preview + // of the attachments. + if (typeof linkPreview === "object") { linkPreview.crawlText(reply + noAttachment); - } else if (typeof window.bin2hex === "function") { - // Fallback: directly fetch and insert BBCode - const hexReply = window.bin2hex(reply); - $.get("parseurl?binurl=" + hexReply + noAttachment, function (data) { - if (typeof window.addeditortext === "function") { - window.addeditortext(data); - } - $("#profile-rotator").hide(); - }).fail(function () { + + // Fallback: insert the attachment bbcode directly into the textarea + // if the attachment live preview isn't available + } else { + $.get("parseurl?binurl=" + bin2hex(reply) + noAttachment, function (data) { + addeditortext(data); $("#profile-rotator").hide(); }); - } else { - $("#profile-rotator").hide(); } - - // Update textarea autosize if available - if (typeof window.autosize === "function") { - window.autosize.update($textarea); - } - }; - -})(jQuery, window); + autosize.update($("#profile-jot-text")); + } +} // @license-end diff --git a/view/theme/frio/js/modal.js b/view/theme/frio/js/modal.js index 7bab02a6d9..b62736a86a 100644 --- a/view/theme/frio/js/modal.js +++ b/view/theme/frio/js/modal.js @@ -3,419 +3,369 @@ // SPDX-License-Identifier: AGPL-3.0-or-later // @license magnet:?xt=urn:btih:0b31508aeb0634b347b8270c7bee4d411b5d4109&dn=agpl-3.0.txt AGPLv3-or-later - /** - * @file view/theme/frio/js/modal.js - * Bootstrap modal handling for the Frio theme. - * - * This module provides functions for modal operations including: - * - Generic modal content loading - * - Jot (post composition) modal handling - * - File browser integration - * - * @requires jQuery - * @requires Bootstrap Modal + * Contains functions for bootstrap modal handling. */ - -(function ($, window, document) { - "use strict"; - - // Module state - let isJotResetBound = false; - let isEditJotClosing = false; - - /** - * Initialize modal handlers on document ready. - */ - $(function () { - const $body = $("body"); - - // Clear bs modal on close - prevent old content display. - // Using a single namespaced event handler for all modals. - $body.on("hidden.bs.modal.frio", ".modal", function () { - const $modal = $(this); - $modal.removeData("bs.modal"); - $("#modal-title").empty(); - $("#modal-body").empty(); - // Clean up file browser elements - $(".fbrowser").remove(); - $(".ajaxbutton-wrapper").remove(); - }); - - // Special handling for jot-modal close - restore cached jot content - $body.on("hidden.bs.modal.frio", "#jot-modal", function () { - // Restore cached jot at its hidden position ("#jot-content") - if (window.jotcache && window.jotcache.length) { - $("#jot-content").append(window.jotcache); - window.jotcache = ""; - } - // Destroy the attachment linkPreview for Jot - if (typeof window.linkPreview === "object" && window.linkPreview !== null) { - window.linkPreview.destroy(); - window.linkPreview = null; - } - }); - - // Navbar login - $body.on("click.frio", "#nav-login", function (e) { - e.preventDefault(); - if (window.Dialog && typeof window.Dialog.show === "function") { - window.Dialog.show(this.href, this.dataset.originalTitle || this.title); - } - }); - - // Jot nav menu tabs - $body.on("click.frio", "#jot-modal .jot-nav li .jot-nav-lnk", function (e) { - e.preventDefault(); - toggleJotNav(this); - }); - - // Bookmarklet page needs a jot modal which appears automatically - if (window.location.pathname.indexOf("/bookmarklet") >= 0 && $("#jot-modal").length) { - // jotShow is defined in jot-header.tpl - if (typeof window.jotShow === "function") { - window.jotShow(); - } - } - - // Open filebrowser for elements with the class "image-select" - $body.on("click.frio", ".image-select", function () { - this.setAttribute("image-input", "select"); - if (window.Dialog && typeof window.Dialog.doImageBrowser === "function") { - window.Dialog.doImageBrowser("input"); - } - }); - - // Insert filebrowser images into the input field - $body.on("fbrowser.photo.input.frio", function (e, filename, embedcode, id, img) { - const $elm = $("[image-input='select']"); - const $input = $elm.closest(".input-group").find("input"); - $elm.removeAttr("image-input"); - $input.val(img); - }); - - // Generic delegated event to open an anchor URL in a modal - $body.on("click.frio", "a.add-to-modal", function (e) { - e.preventDefault(); - addToModal(this.href); - }); - - // Bind the edit-jot reset handler exactly once using event delegation - bindJotResetOnce(); +$(document).ready(function () { + // Clear bs modal on close. + // We need this to prevent that the modal displays old content. + $("body, footer").on("hidden.bs.modal", ".modal", function () { + $(this).removeData("bs.modal"); + $("#modal-title").empty(); + $("#modal-body").empty(); + // Remove the file browser from jot (else we would have problems + // with AjaxUpload. + $(".fbrowser").remove(); + // Remove the AjaxUpload element. + $(".ajaxbutton-wrapper").remove(); }); - /** - * Bind the jot reset handler exactly once to prevent duplicate event registration. - * This fixes the critical bug where calling editpost() multiple times would - * register multiple handlers, causing the reset code to run multiple times. - */ - function bindJotResetOnce() { - if (isJotResetBound) { - return; + // Clear bs modal on close. + // We need this to prevent that the modal displays old content. + $("body").on("hidden.bs.modal", "#jot-modal", function () { + // Restore cached jot at its hidden position ("#jot-content"). + $("#jot-content").append(jotcache); + // Clear the jotcache. + jotcache = ""; + // Destroy the attachment linkPreview for Jot. + if (typeof linkPreview === "object") { + linkPreview.destroy(); } - isJotResetBound = true; + }); - $("body").on("hidden.bs.modal.frio-edit", "#jot-modal.edit-jot", function () { - // Prevent concurrent execution - if (isEditJotClosing) { - return; - } - isEditJotClosing = true; + // Navbar login. + $("body").on("click", "#nav-login", function (e) { + e.preventDefault(); + Dialog.show(this.href, this.dataset.originalTitle || this.title); + }); - const $modal = $(this); - $modal.removeData("bs.modal"); - $(".jot-nav .jot-perms-lnk").parent("li").removeClass("hidden"); - $("#profile-jot-form #jot-title-wrap, #profile-jot-form #jot-category-wrap").show(); - $modal.removeClass("edit-jot"); - $("#jot-modal-content").empty(); + // Jot nav menu.. + $("body").on("click", "#jot-modal .jot-nav li .jot-nav-lnk", function (e) { + e.preventDefault(); + toggleJotNav(this); + }); - // Reset flag after a short delay to allow Bootstrap to finish its cleanup - setTimeout(function () { - isEditJotClosing = false; - }, 0); - }); + // Bookmarklet page needs an jot modal which appears automatically. + if (window.location.pathname.indexOf("/bookmarklet") >= 0 && $("#jot-modal").length) { + jotShow(); } - /** - * Overwrite Dialog.show from main js to load the filebrowser into a bs modal. - * - * @param {string} url - The URL to load - * @param {string} [title] - Optional modal title - */ - window.Dialog.show = function (url, title) { - title = title || ""; - const $modal = $("#modal").modal(); - $modal.find("#modal-header h4").html(title); - $modal.find("#modal-body").load(url, function (responseText, textStatus) { - if (textStatus === "success" || textStatus === "notmodified") { - $modal.show(); - if (typeof window.Dialog._load === "function") { - window.Dialog._load(url); - } + // Open filebrowser for elements with the class "image-select" + // The following part handles the filebrowser for field_fileinput.tpl. + $("body").on("click", ".image-select", function () { + // Set a extra attribute to mark the clicked button. + this.setAttribute("image-input", "select"); + Dialog.doImageBrowser("input"); + }); + + // Insert filebrowser images into the input field (field_fileinput.tpl). + $("body").on("fbrowser.photo.input", function (e, filename, embedcode, id, img) { + // Select the clicked button by it's attribute. + var elm = $("[image-input='select']"); + // Select the input field which belongs to this button. + var input = elm.parent(".input-group").children("input"); + // Remove the special indicator attribut from the button. + elm.removeAttr("image-input"); + // Insert the link from the image into the input field. + input.val(img); + }); + + // Generic delegated event to open an anchor URL in a modal. + // Used in the hovercard. + document.getElementsByTagName("body")[0].addEventListener("click", function (e) { + var target = e.target; + while (target) { + if (target.matches && target.matches("a.add-to-modal")) { + addToModal(target.href); + e.preventDefault(); + return false; } - }); - }; - /** - * Overwrite the function _get_url from main.js. - * - * @param {string} type - The browser type - * @param {string} name - The browser name - * @param {string|number} [id] - Optional ID - * @returns {string} The constructed URL - */ - window.Dialog._get_url = function (type, name, id) { - let hash = name; - if (id !== undefined) { - hash = hash + "-" + id; + target = target.parentNode || null; } - return "media/" + type + "/browser?mode=none&theme=frio#" + hash; - }; + }); +}); - /** - * Load the filebrowser into the jot modal. - */ - window.Dialog.showJot = function () { - const type = "photo"; - const name = "main"; - const url = window.Dialog._get_url(type, name); +// Overwrite Dialog.show from main js to load the filebrowser into a bs modal. +Dialog.show = function (url, title) { + if (typeof title === "undefined") { + title = ""; + } - if ($(".modal-body #jot-fbrowser-wrapper .fbrowser").length < 1) { - $("#jot-fbrowser-wrapper").load(url, function (responseText, textStatus) { - if (textStatus === "success" || textStatus === "notmodified") { - if (typeof window.Dialog._load === "function") { - window.Dialog._load(url); - } - } + var modal = $("#modal").modal(); + modal.find("#modal-header h4").html(title); + modal.find("#modal-body").load(url, function (responseText, textStatus) { + if (textStatus === "success" || textStatus === "notmodified") { + modal.show(); + + $(function () { + Dialog._load(url); }); } - }; + }); +}; - /** - * Initialize the filebrowser after page load. - * - * @param {string} url - The URL being loaded - */ - window.Dialog._load = function (url) { - const filebrowser = document.getElementById("filebrowser"); - const match = url.match(/media\/[a-z]+\/.*(#.*)/); +// Overwrite the function _get_url from main.js. +Dialog._get_url = function (type, name, id) { + var hash = name; + if (id !== undefined) hash = hash + "-" + id; + return 'media/' + type + '/browser?mode=none&theme=frio#' + hash; +}; - if (!filebrowser || match === null) { - return; // not fbrowser - } +// Does load the filebrowser into the jot modal. +Dialog.showJot = function () { + var type = "photo"; + var name = "main"; - // Initialize the filebrowser - if (typeof window.loadScript === "function") { - window.loadScript("view/js/ajaxupload.js"); - window.loadScript("view/theme/frio/js/module/media/browser.js", function () { - if (typeof window.Browser !== "undefined" && typeof window.Browser.init === "function") { - window.Browser.init(filebrowser.dataset.nickname, filebrowser.dataset.type, match[1]); - } - }); - } - }; - - /** - * Add first element with the class "heading" as modal title. - * Note: This should ideally be done in the template. - */ - function loadModalTitle() { - $("#modal-title").empty(); - const $heading = $("#modal-body .heading").first(); - $heading.hide(); - - let title = ""; - - // Special handling for event modals - if ($("#modal-body .event-wrapper .event-summary").length) { - const eventsum = $("#modal-body .event-wrapper .event-summary").html(); - title = ' ' + eventsum; - } else { - title = $heading.html(); - } - - if (title) { - $("#modal-title").append(title); - } - } - - /** - * Load HTML content from a Friendica page into a modal. - * - * @param {string} url - The URL with HTML content - * @param {string} [id] - Optional ID of a specific HTML element to show - */ - function addToModal(url, id) { - const char = window.qOrAmp ? window.qOrAmp(url) : (url.indexOf("?") < 0 ? "?" : "&"); - url = url + char + "mode=none"; - - if (typeof id !== "undefined") { - url = url + " div#" + id; - } - - const $modal = $("#modal").modal(); - $modal.find("#modal-body").load(url, function (responseText, textStatus) { + var url = Dialog._get_url(type, name); + if ($(".modal-body #jot-fbrowser-wrapper .fbrowser").length < 1) { + // Load new content to fbrowser window. + $("#jot-fbrowser-wrapper").load(url, function (responseText, textStatus) { if (textStatus === "success" || textStatus === "notmodified") { - $modal.show(); - loadModalTitle(); - // Re-initialize autosize for new modal content - if (typeof window.autosize === "function") { - window.autosize($(".modal .text-autosize")); - } + $(function () { + Dialog._load(url); + }); } }); } +}; - /** - * Add an element (by its id) to a bootstrap modal. - * - * @param {string} id - The element ID selector - */ - function addElmToModal(id) { - const elm = $(id).html(); - const $modal = $("#modal").modal(); +// Init the filebrowser after page load. +Dialog._load = function (url) { + // Get nickname & filebrowser type from the modal content. + let filebrowser = document.getElementById("filebrowser"); - $modal.find("#modal-body").append(elm); - loadModalTitle(); + // Try to fetch the hash form the url. + let match = url.match(/media\/[a-z]+\/.*(#.*)/); + if (!filebrowser || match === null) { + return; //not fbrowser } - /** - * Load the HTML from the edit post page into the jot modal. - * - * @param {string} url - The edit post URL - */ - function editpost(url) { - // Check if this is an event post - const splitURL = window.parseUrl ? window.parseUrl(url) : { path: "" }; - if (splitURL.path && splitURL.path.indexOf("calendar/event/show") > -1) { - addToModal(splitURL.path); - return; - } + // Initialize the filebrowser. + loadScript("view/js/ajaxupload.js"); + loadScript("view/theme/frio/js/module/media/browser.js", function () { + Browser.init(filebrowser.dataset.nickname, filebrowser.dataset.type, match[1]); + }); +}; - const $modal = $("#jot-modal").modal(); - const loadUrl = url + " #jot-sections"; +/** + * Add first element with the class "heading" as modal title + * + * Note: this should be really done in the template + * and is the solution where we havent done it until this + * moment or where it isn't possible because of design + */ +function loadModalTitle() { + // Clear the text of the title. + $("#modal-title").empty(); - $(".jot-nav .jot-perms-lnk").parent("li").addClass("hidden"); + // Hide the first element with the class "heading" of the modal body. + $("#modal-body .heading").first().hide(); - // Store original jot and remove it to avoid conflicts - window.jotcache = $("#jot-content > #jot-sections"); - window.jotcache.detach(); + var title = ""; - $("#jot-modal").addClass("edit-jot"); + // Get the text of the first element with "heading" class. + title = $("#modal-body .heading").first().html(); - // Handler is bound once in document.ready via bindJotResetOnce() - // No need to call jotreset() here anymore - - $modal.find("#jot-modal-content").load(loadUrl, function (responseText, textStatus) { - if (textStatus === "success" || textStatus === "notmodified") { - const type = $(responseText).find("#profile-jot-form input[name='type']").val(); - - if (type === "wall-comment" || type === "remote-comment") { - $("#profile-jot-form #jot-title-wrap").hide(); - $("#profile-jot-form #jot-category-wrap").hide(); - } - - // Setup dropzone for comment editing - if ($("#jot-text-wrap").length && typeof window.dzFactory !== "undefined") { - window.dzFactory.setupDropzone("#jot-text-wrap", "profile-jot-text"); - } - - $modal.show(); - $("#jot-popup").show(); - - const $profileJotText = $("#profile-jot-text"); - if ($profileJotText.length && typeof $profileJotText.linkPreview === "function") { - window.linkPreview = $profileJotText.linkPreview(); - } - } - }); + // for event modals we need some special handling + if ($("#modal-body .event-wrapper .event-summary").length) { + title = ' '; + var eventsum = $("#modal-body .event-wrapper .event-summary").html(); + title = title + eventsum; } - /** - * Give the active "jot-nav" list element the class "active". - * - * @param {HTMLElement} elm - The navigation link element - */ - function toggleJotNav(elm) { - const tabpanel = elm.getAttribute("aria-controls"); - const isMobile = elm.classList.contains("jot-nav-lnk-mobile"); + // And append it to modal title. + if (title !== "") { + $("#modal-title").append(title); + } +} - // Toggle active class - $(elm).closest("li").siblings("li").removeClass("active"); - $(elm).closest("li").addClass("active"); +/** + * This function loads html content from a friendica page into a modal. + * + * @param {string} url The url with html content. + * @param {string} id The ID of a html element (can be undefined). + * @returns {void} + */ +function addToModal(url, id) { + var char = qOrAmp(url); - // Toggle tab panels - $("#profile-jot-form > [role=tabpanel]") - .addClass("minimize") - .attr("aria-hidden", "true"); - $("#" + tabpanel) - .removeClass("minimize") - .attr("aria-hidden", "false"); + url = url + char + "mode=none"; + var modal = $("#modal").modal(); - // Set aria-selected states - $("#jot-modal .modal-header .nav-tabs .jot-nav-lnk").attr("aria-selected", "false"); - elm.setAttribute("aria-selected", "true"); - - // Handle specific tab panels - if (tabpanel === "jot-preview-content") { - if (typeof window.preview_post === "function") { - window.preview_post(); - } - $("#jot-preview-share").removeClass("minimize").attr("aria-hidden", "false"); - } else if (tabpanel === "jot-fbrowser-wrapper") { - window.Dialog.showJot(); - } - - // Update mobile dropdown button text - if (isMobile && typeof window.toggleDropdownText === "function") { - window.toggleDropdownText(elm); - } + // Only search for an element if we have an ID. + if (typeof id !== "undefined") { + url = url + " div#" + id; } - /** - * Wall Message special handling - redirects to own server if needed. - * - * @param {string} url - The wall message URL - */ - function openWallMessage(url) { - const parts = window.parseUrl ? window.parseUrl(url) : {}; + modal.find("#modal-body").load(url, function (responseText, textStatus) { + if (textStatus === "success" || textStatus === "notmodified") { + modal.show(); - if (parts.host && parts.host !== window.location.host) { - window.location.href = url; - } else { - addToModal(url); - } - } - - /** - * Load the content of an edit URL into a modal. - * - * @param {string} url - The event edit URL - */ - function eventEdit(url) { - const char = window.qOrAmp ? window.qOrAmp(url) : (url.indexOf("?") < 0 ? "?" : "&"); - const fullUrl = url + char + "mode=none"; - - $.get(fullUrl, function (data) { - $("#modal-body").empty().append(data); - }).done(function () { + //Get first element with the class "heading" + //and use it as title. loadModalTitle(); + + // We need to initialize autosize again for new + // modal content. + autosize($(".modal .text-autosize")); + } + }); +} + +// Add an element (by its id) to a bootstrap modal. +function addElmToModal(id) { + var elm = $(id).html(); + var modal = $("#modal").modal(); + + modal.find("#modal-body").append(elm).modal.show; + + loadModalTitle(); +} + +// Function to load the html from the edit post page into +// the jot modal. +function editpost(url) { + // Next to normel posts the post can be an event post. The event posts don't + // use the normal Jot modal. For event posts we will use a normal modal + // But first we have to test if the url links to an event. So we will split up + // the url in its parts. + var splitURL = parseUrl(url); + // Test if in the url path containing "calendar/event/show". If the path containing this + // expression then we will call the addToModal function and exit this function at + // this point. + if (splitURL.path.indexOf("calendar/event/show") > -1) { + addToModal(splitURL.path); + return; + } + + var modal = $("#jot-modal").modal(); + url = url + " #jot-sections"; + + $(".jot-nav .jot-perms-lnk").parent("li").addClass("hidden"); + + // For editpost we load the modal html of "jot-sections" of the edit page. So we would have two jot forms in + // the page html. To avoid js conflicts we store the original jot in the variable jotcache. + // After closing the modal original jot should be restored at its original position in the html structure. + jotcache = $("#jot-content > #jot-sections"); + + // Remove the original Jot as long as the edit Jot is open. + jotcache.detach(); + + // Add the class "edit" to the modal to have some kind of identifier to + // have the possibility to e.g. put special event-listener. + $("#jot-modal").addClass("edit-jot"); + + jotreset(); + + modal.find("#jot-modal-content").load(url, function (responseText, textStatus) { + if (textStatus === "success" || textStatus === "notmodified") { + // get the item type and hide the input for title and category if it isn't needed. + var type = $(responseText).find("#profile-jot-form input[name='type']").val(); + if (type === "wall-comment" || type === "remote-comment") { + // Hide title and category input fields because we don't. + $("#profile-jot-form #jot-title-wrap").hide(); + $("#profile-jot-form #jot-category-wrap").hide(); + } + + // To make dropzone fileupload work on editing a comment, we need to + // attach a new dropzone to modal + if ($('#jot-text-wrap').length > 0) { + dzFactory.setupDropzone('#jot-text-wrap', 'profile-jot-text'); + } + + modal.show(); + $("#jot-popup").show(); + if ($("#profile-jot-text").length > 0) { + linkPreview = $("#profile-jot-text").linkPreview(); + } + } + }); +} + +// Remove content from the jot modal. +function jotreset() { + // Clear bs modal on close. + // We need this to prevent that the modal displays old content. + $("body").on("hidden.bs.modal", "#jot-modal.edit-jot", function () { + $(this).removeData("bs.modal"); + $(".jot-nav .jot-perms-lnk").parent("li").removeClass("hidden"); + $("#profile-jot-form #jot-title-wrap").show(); + $("#profile-jot-form #jot-category-wrap").show(); + + // Remove the "edit-jot" class so we can the standard behavior on close. + $("#jot-modal.edit-jot").removeClass("edit-jot"); + $("#jot-modal-content").empty(); + }); +} + +// Give the active "jot-nav" list element the class "active". +function toggleJotNav(elm) { + // Get the ID of the tab panel which should be activated. + var tabpanel = elm.getAttribute("aria-controls"); + var cls = hasClass(elm, "jot-nav-lnk-mobile"); + + // Select all li of jot-nav and remove the active class. + $(elm).parent("li").siblings("li").removeClass("active"); + // Add the active class to the parent of the link which was selected. + $(elm).parent("li").addClass("active"); + + // Minimize all tab content wrapper and activate only the selected + // tab panel. + $("#profile-jot-form > [role=tabpanel]").addClass("minimize").attr("aria-hidden", "true"); + $("#" + tabpanel) + .removeClass("minimize") + .attr("aria-hidden", "false"); + + // Set the aria-selected states + $("#jot-modal .modal-header .nav-tabs .jot-nav-lnk").attr("aria-selected", "false"); + elm.setAttribute("aria-selected", "true"); + + // For some tab panels we need to execute other js functions. + if (tabpanel === "jot-preview-content") { + preview_post(); + // Make Share button visible in preview + $("#jot-preview-share").removeClass("minimize").attr("aria-hidden", "false"); + } else if (tabpanel === "jot-fbrowser-wrapper") { + $(function () { + Dialog.showJot(); }); } - // Expose functions to global scope - window.addToModal = addToModal; - window.addElmToModal = addElmToModal; - window.editpost = editpost; - window.toggleJotNav = toggleJotNav; - window.openWallMessage = openWallMessage; - window.eventEdit = eventEdit; - // jotreset is no longer needed as a public function since we bind the handler once - // But keep it for backward compatibility just in case - window.jotreset = function () { - // Handler is now bound automatically in document.ready - // This function is kept for backward compatibility - }; + // If element is a mobile dropdown nav menu we need to change the button text. + if (cls) { + toggleDropdownText(elm); + } +} -})(jQuery, window, document); +// Wall Message needs a special handling because in some cases +// it redirects you to your own server. In such cases we can't +// load it into a modal. +function openWallMessage(url) { + // Split the url in its parts. + var parts = parseUrl(url); + + // If the host isn't the same we can't load it in a modal. + // So we will go to to the url directly. + if ("host" in parts && parts.host !== window.location.host) { + window.location.href = url; + } else { + // Otherwise load the wall message into a modal. + addToModal(url); + } +} + +// This function load the content of the edit url into a modal. +/// @todo Rename this function because it can be used for more than events. +function eventEdit(url) { + var char = qOrAmp(url); + url = url + char + "mode=none"; + + $.get(url, function (data) { + $("#modal-body").empty(); + $("#modal-body").append(data); + }).done(function () { + loadModalTitle(); + }); +} // @license-end diff --git a/view/theme/frio/js/module/media/browser.js b/view/theme/frio/js/module/media/browser.js index 294ebaa4ca..756e4c97e5 100644 --- a/view/theme/frio/js/module/media/browser.js +++ b/view/theme/frio/js/module/media/browser.js @@ -67,7 +67,6 @@ var Browser = { event: '', folder: '', id: null, - currentRequest: null, init: function (nickname, type, hash) { Browser.nickname = nickname; @@ -92,12 +91,8 @@ var Browser = { $('.error').addClass('hidden'); }); - // Remove existing delegated event handlers first to prevent multiple registrations - // when the filebrowser is opened multiple times - $('body').off('click.fbrowser'); - - // Click on album link - use delegated event on body with namespace - $('body').on('click.fbrowser', '.fbrowser .folders button, .fbrowser .path button', function (e) { + // Click on album link + $('.fbrowser').on('click', '.folders button, .path button', function (e) { e.preventDefault(); let url = Browser._getUrl("none", this.dataset.folder); Browser.folder = this.dataset.folder; @@ -105,8 +100,8 @@ var Browser = { Browser.loadContent(url); }); - //Embed on click - use delegated event on body with namespace - $('body').on('click.fbrowser', '.fbrowser .photo-album-photo-link', function (e) { + //Embed on click + $('.fbrowser').on('click', '.photo-album-photo-link', function (e) { e.preventDefault(); let embed = ''; @@ -142,8 +137,8 @@ var Browser = { autosize.update($('.text-autosize')); }); - // EventListener for switching between photo and file mode - use delegated event on body with namespace - $('body').on('click.fbrowser', '.fbrowser .fbswitcher .btn', function (e) { + // EventListener for switching between photo and file mode + $('.fbrowser').on('click', '.fbswitcher .btn', function (e) { e.preventDefault(); Browser.type = this.getAttribute('data-mode'); $('.fbrowser') @@ -222,31 +217,16 @@ var Browser = { // Load new content (e.g. change photo album) loadContent: function (url) { - // Abort any pending request to prevent race conditions - if (Browser.currentRequest) { - Browser.currentRequest.abort(); - } - $('.fbrowser-content').hide(); $('.fbrowser .profile-rotator-wrapper').show(); - // Use $.ajax instead of .load() to get better control over the request - Browser.currentRequest = $.ajax({ - url: url, - type: 'GET' - }).done(function(data) { - $('.fbrowser').html(data); - Browser.postLoad(); - }).fail(function(xhr, status) { - // Only show error if it's not an abort (abort is intentional when switching quickly) - if (status !== 'abort') { - $('.error span').html('Failed to load content'); - $('.error').removeClass('hidden'); - } - }).always(function() { + // load new content to fbrowser window + $('.fbrowser').load(url, function (responseText, textStatus) { $('.profile-rotator-wrapper').hide(); - $('.fbrowser-content').show(); - Browser.currentRequest = null; + if (textStatus === 'success') { + $(".fbrowser_content").show(); + Browser.postLoad(); + } }); }, diff --git a/view/theme/frio/php/Image.php b/view/theme/frio/php/Image.php index dbfc13ac47..6e8fa5028f 100644 --- a/view/theme/frio/php/Image.php +++ b/view/theme/frio/php/Image.php @@ -26,7 +26,7 @@ class Image $bg_image_options = [ 'stretch' => ['frio_bg_image_option', DI::l10n()->t('Top Banner'), 'stretch', DI::l10n()->t('Resize image to the width of the screen and show background color below on long pages.'), ($arr['bg_image_option'] == 'stretch')], 'cover' => ['frio_bg_image_option', DI::l10n()->t('Full screen'), 'cover', DI::l10n()->t('Resize image to fill entire screen, clipping either the right or the bottom.'), ($arr['bg_image_option'] == 'cover')], - 'contain' => ['frio_bg_image_option', DI::l10n()->t('Single row mosaic'), 'contain', DI::l10n()->t('Resize image to repeat it in a single direction, either vertical or horizontal.'), ($arr['bg_image_option'] == 'contain')], + 'contain' => ['frio_bg_image_option', DI::l10n()->t('Single row mosaic'), 'contain', DI::l10n()->t('Resize image to repeat it on a single row, either vertical or horizontal.'), ($arr['bg_image_option'] == 'contain')], 'repeat' => ['frio_bg_image_option', DI::l10n()->t('Mosaic'), 'repeat', DI::l10n()->t('Repeat image to fill the screen.'), ($arr['bg_image_option'] == 'repeat')], ]; diff --git a/view/theme/frio/php/PHPColors/Color.php b/view/theme/frio/php/PHPColors/Color.php index 198475f771..30de579966 100644 --- a/view/theme/frio/php/PHPColors/Color.php +++ b/view/theme/frio/php/PHPColors/Color.php @@ -10,513 +10,484 @@ /** * A color utility that helps manipulate HEX colors */ -class Color -{ - private $_hex; - private $_hsl; - private $_rgb; - - /** - * Auto darkens/lightens by 10% for sexily-subtle gradients. - * Set this to FALSE to adjust automatic shade to be between given color - * and black (for darken) or white (for lighten) - */ - public const DEFAULT_ADJUST = 10; - - /** - * Instantiates the class with a HEX value - * @param string $hex - * @throws Exception "Bad color format" - */ - public function __construct($hex) - { - // Strip # sign is present - $color = str_replace("#", "", $hex); - - // Make sure it's 6 digits - if (strlen($color) === 3) { - $color = $color[0] . $color[0] . $color[1] . $color[1] . $color[2] . $color[2]; - } elseif (strlen($color) != 6) { - throw new Exception("HEX color needs to be 6 or 3 digits long"); - } - - $this->_hsl = self::hexToHsl($color); - $this->_hex = $color; - $this->_rgb = self::hexToRgb($color); - } - - // ==================== - // = Public Interface = - // ==================== - - /** - * Given a HEX string returns a HSL array equivalent. - * @param string $color - * @return array HSL associative array - */ - public static function hexToHsl($color) - { - - // Sanity check - $color = self::_checkHex($color); - - // Convert HEX to DEC - $R = hexdec($color[0] . $color[1]); - $G = hexdec($color[2] . $color[3]); - $B = hexdec($color[4] . $color[5]); - - $HSL = []; - - $var_R = ($R / 255); - $var_G = ($G / 255); - $var_B = ($B / 255); - - $var_Min = min($var_R, $var_G, $var_B); - $var_Max = max($var_R, $var_G, $var_B); - $del_Max = $var_Max - $var_Min; - - $L = ($var_Max + $var_Min) / 2; - - if ($del_Max == 0) { - $H = 0; - $S = 0; - } else { - if ($L < 0.5) { - $S = $del_Max / ($var_Max + $var_Min); - } else { - $S = $del_Max / (2 - $var_Max - $var_Min); - } - - $del_R = ((($var_Max - $var_R) / 6) + ($del_Max / 2)) / $del_Max; - $del_G = ((($var_Max - $var_G) / 6) + ($del_Max / 2)) / $del_Max; - $del_B = ((($var_Max - $var_B) / 6) + ($del_Max / 2)) / $del_Max; - - if ($var_R == $var_Max) { - $H = $del_B - $del_G; - } elseif ($var_G == $var_Max) { - $H = (1 / 3) + $del_R - $del_B; - } elseif ($var_B == $var_Max) { - $H = (2 / 3) + $del_G - $del_R; - } - - if ($H < 0) { - $H++; - } - if ($H > 1) { - $H--; - } - } - - $HSL['H'] = ($H * 360); - $HSL['S'] = $S; - $HSL['L'] = $L; - - return $HSL; - } - - /** - * Given a HSL associative array returns the equivalent HEX string - * @param array $hsl - * @return string HEX string - * @throws Exception "Bad HSL Array" - */ - public static function hslToHex($hsl = []) - { - // Make sure it's HSL - if (empty($hsl) || !isset($hsl["H"]) || !isset($hsl["S"]) || !isset($hsl["L"])) { - throw new Exception("Param was not an HSL array"); - } - - [$H, $S, $L] = [ $hsl['H'] / 360,$hsl['S'],$hsl['L'] ]; - - if ($S == 0) { - $r = $L * 255; - $g = $L * 255; - $b = $L * 255; - } else { - - if ($L < 0.5) { - $var_2 = $L * (1 + $S); - } else { - $var_2 = ($L + $S) - ($S * $L); - } - - $var_1 = 2 * $L - $var_2; - - $r = 255 * self::_huetorgb($var_1, $var_2, $H + (1 / 3)); - $g = 255 * self::_huetorgb($var_1, $var_2, $H); - $b = 255 * self::_huetorgb($var_1, $var_2, $H - (1 / 3)); - - } - - // Convert to hex - $r = dechex(round($r)); - $g = dechex(round($g)); - $b = dechex(round($b)); - - // Make sure we get 2 digits for decimals - $r = (strlen("" . $r) === 1) ? "0" . $r:$r; - $g = (strlen("" . $g) === 1) ? "0" . $g:$g; - $b = (strlen("" . $b) === 1) ? "0" . $b:$b; - - return $r . $g . $b; - } - - - /** - * Given a HEX string returns a RGB array equivalent. - * @param string $color - * @return array RGB associative array - */ - public static function hexToRgb($color) - { - - // Sanity check - $color = self::_checkHex($color); - - // Convert HEX to DEC - $R = hexdec($color[0] . $color[1]); - $G = hexdec($color[2] . $color[3]); - $B = hexdec($color[4] . $color[5]); - - $RGB['R'] = $R; - $RGB['G'] = $G; - $RGB['B'] = $B; - - return $RGB; - } - - - /** - * Given an RGB associative array returns the equivalent HEX string - * @param array $rgb - * @return string RGB string - * @throws Exception "Bad RGB Array" - */ - public static function rgbToHex($rgb = []) - { - // Make sure it's RGB - if (empty($rgb) || !isset($rgb["R"]) || !isset($rgb["G"]) || !isset($rgb["B"])) { - throw new Exception("Param was not an RGB array"); - } - - // https://github.com/mexitek/phpColors/issues/25#issuecomment-88354815 - // Convert RGB to HEX - $hex[0] = str_pad(dechex($rgb['R']), 2, '0', STR_PAD_LEFT); - $hex[1] = str_pad(dechex($rgb['G']), 2, '0', STR_PAD_LEFT); - $hex[2] = str_pad(dechex($rgb['B']), 2, '0', STR_PAD_LEFT); - - return implode('', $hex); - - } - - - /** - * Given a HEX value, returns a darker color. If no desired amount provided, then the color halfway between - * given HEX and black will be returned. - * @param int $amount - * @return string Darker HEX value - */ - public function darken($amount = self::DEFAULT_ADJUST) - { - // Darken - $darkerHSL = $this->_darken($this->_hsl, $amount); - // Return as HEX - return self::hslToHex($darkerHSL); - } - - /** - * Given a HEX value, returns a lighter color. If no desired amount provided, then the color halfway between - * given HEX and white will be returned. - * @param int $amount - * @return string Lighter HEX value - */ - public function lighten($amount = self::DEFAULT_ADJUST) - { - // Lighten - $lighterHSL = $this->_lighten($this->_hsl, $amount); - // Return as HEX - return self::hslToHex($lighterHSL); - } - - /** - * Given a HEX value, returns a mixed color. If no desired amount provided, then the color mixed by this ratio - * @param string $hex2 Secondary HEX value to mix with - * @param int $amount = -100..0..+100 - * @return string mixed HEX value - */ - public function mix($hex2, $amount = 0) - { - $rgb2 = self::hexToRgb($hex2); - $mixed = $this->_mix($this->_rgb, $rgb2, $amount); - // Return as HEX - return self::rgbToHex($mixed); - } - - /** - * Creates an array with two shades that can be used to make a gradient - * @param int $amount Optional percentage amount you want your contrast color - * @return array An array with a 'light' and 'dark' index - */ - public function makeGradient($amount = self::DEFAULT_ADJUST) - { - // Decide which color needs to be made - if ($this->isLight()) { - $lightColor = $this->_hex; - $darkColor = $this->darken($amount); - } else { - $lightColor = $this->lighten($amount); - $darkColor = $this->_hex; - } - - // Return our gradient array - return [ "light" => $lightColor, "dark" => $darkColor ]; - } - - - /** - * Returns whether or not given color is considered "light" - * @param string|Boolean $color - * @param int $lighterThan - * @return boolean - */ - public function isLight($color = false, $lighterThan = 130) - { - // Get our color - $color = $color ?: $this->_hex; - - // Calculate straight from rbg - $r = hexdec($color[0] . $color[1]); - $g = hexdec($color[2] . $color[3]); - $b = hexdec($color[4] . $color[5]); - - return (($r * 299 + $g * 587 + $b * 114) / 1000 > $lighterThan); - } - - /** - * Returns whether or not a given color is considered "dark" - * @param string|Boolean $color - * @param int $darkerThan - * @return boolean - */ - public function isDark($color = false, $darkerThan = 130) - { - // Get our color - $color = $color ?: $this->_hex; - - // Calculate straight from rbg - $r = hexdec($color[0] . $color[1]); - $g = hexdec($color[2] . $color[3]); - $b = hexdec($color[4] . $color[5]); - - return (($r * 299 + $g * 587 + $b * 114) / 1000 <= $darkerThan); - } - - /** - * Returns the complimentary color - * @return string Complementary hex color - * - */ - public function complementary() - { - // Get our HSL - $hsl = $this->_hsl; - - // Adjust Hue 180 degrees - $hsl['H'] += ($hsl['H'] > 180) ? -180:180; - - // Return the new value in HEX - return self::hslToHex($hsl); - } - - /** - * Returns your color's HSL array - */ - public function getHsl() - { - return $this->_hsl; - } - /** - * Returns your original color - */ - public function getHex() - { - return $this->_hex; - } - /** - * Returns your color's RGB array - */ - public function getRgb() - { - return $this->_rgb; - } - - /** - * Returns the cross browser CSS3 gradient - * @param int $amount Optional: percentage amount to light/darken the gradient - * @param boolean $vintageBrowsers Optional: include vendor prefixes for browsers that almost died out already - * @param string $prefix Optional: prefix for every lines - * @param string $suffix Optional: suffix for every lines - * @link http://caniuse.com/css-gradients Resource for the browser support - * @return string CSS3 gradient for chrome, safari, firefox, opera and IE10 - */ - public function getCssGradient($amount = self::DEFAULT_ADJUST, $vintageBrowsers = false, $suffix = "", $prefix = "") - { - - // Get the recommended gradient - $g = $this->makeGradient($amount); - - $css = ""; - /* fallback/image non-cover color */ - $css .= "{$prefix}background-color: #" . $this->_hex . ";{$suffix}"; - - /* IE Browsers */ - $css .= "{$prefix}filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#" . $g['light'] . "', endColorstr='#" . $g['dark'] . "');{$suffix}"; - - /* Safari 4+, Chrome 1-9 */ - if ($vintageBrowsers) { - $css .= "{$prefix}background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#" . $g['light'] . "), to(#" . $g['dark'] . "));{$suffix}"; - } - - /* Safari 5.1+, Mobile Safari, Chrome 10+ */ - $css .= "{$prefix}background-image: -webkit-linear-gradient(top, #" . $g['light'] . ", #" . $g['dark'] . ");{$suffix}"; - - /* Firefox 3.6+ */ - if ($vintageBrowsers) { - $css .= "{$prefix}background-image: -moz-linear-gradient(top, #" . $g['light'] . ", #" . $g['dark'] . ");{$suffix}"; - } - - /* Opera 11.10+ */ - if ($vintageBrowsers) { - $css .= "{$prefix}background-image: -o-linear-gradient(top, #" . $g['light'] . ", #" . $g['dark'] . ");{$suffix}"; - } - - /* Unprefixed version (standards): FF 16+, IE10+, Chrome 26+, Safari 7+, Opera 12.1+ */ - $css .= "{$prefix}background-image: linear-gradient(to bottom, #" . $g['light'] . ", #" . $g['dark'] . ");{$suffix}"; - - // Return our CSS - return $css; - } - - // =========================== - // = Private Functions Below = - // =========================== - - - /** - * Darkens a given HSL array - * @param array $hsl - * @param int $amount - * @return array $hsl - */ - private function _darken($hsl, $amount = self::DEFAULT_ADJUST) - { - // Check if we were provided a number - if ($amount) { - $hsl['L'] = ($hsl['L'] * 100) - $amount; - $hsl['L'] = ($hsl['L'] < 0) ? 0:$hsl['L'] / 100; - } else { - // We need to find out how much to darken - $hsl['L'] = $hsl['L'] / 2; - } - - return $hsl; - } - - /** - * Lightens a given HSL array - * @param array $hsl - * @param int $amount - * @return array $hsl - */ - private function _lighten($hsl, $amount = self::DEFAULT_ADJUST) - { - // Check if we were provided a number - if ($amount) { - $hsl['L'] = ($hsl['L'] * 100) + $amount; - $hsl['L'] = ($hsl['L'] > 100) ? 1:$hsl['L'] / 100; - } else { - // We need to find out how much to lighten - $hsl['L'] += (1 - $hsl['L']) / 2; - } - - return $hsl; - } - - /** - * Mix 2 rgb colors and return an rgb color - * @param array $rgb1 - * @param array $rgb2 - * @param int $amount ranged -100..0..+100 - * @return array $rgb - * - * ported from http://phpxref.pagelines.com/nav.html?includes/class.colors.php.source.html - */ - private function _mix($rgb1, $rgb2, $amount = 0) - { - - $r1 = ($amount + 100) / 100; - $r2 = 2 - $r1; - - $rmix = (($rgb1['R'] * $r1) + ($rgb2['R'] * $r2)) / 2; - $gmix = (($rgb1['G'] * $r1) + ($rgb2['G'] * $r2)) / 2; - $bmix = (($rgb1['B'] * $r1) + ($rgb2['B'] * $r2)) / 2; - - return ['R' => $rmix, 'G' => $gmix, 'B' => $bmix]; - } - - /** - * Given a Hue, returns corresponding RGB value - * @param int $v1 - * @param int $v2 - * @param int $vH - * @return int - */ - private static function _huetorgb($v1, $v2, $vH) - { - if ($vH < 0) { - $vH += 1; - } - - if ($vH > 1) { - $vH -= 1; - } - - if ((6 * $vH) < 1) { - return ($v1 + ($v2 - $v1) * 6 * $vH); - } - - if ((2 * $vH) < 1) { - return $v2; - } - - if ((3 * $vH) < 2) { - return ($v1 + ($v2 - $v1) * ((2 / 3) - $vH) * 6); - } - - return $v1; - - } - - /** - * You need to check if you were given a good hex string - * @param string $hex - * @return string Color - * @throws Exception "Bad color format" - */ - private static function _checkHex($hex) - { - // Strip # sign is present - $color = str_replace("#", "", $hex); - - // Make sure it's 6 digits - if (strlen($color) == 3) { - $color = $color[0] . $color[0] . $color[1] . $color[1] . $color[2] . $color[2]; - } elseif (strlen($color) != 6) { - throw new Exception("HEX color needs to be 6 or 3 digits long"); - } - - return $color; - } +class Color { + + private $_hex; + private $_hsl; + private $_rgb; + + /** + * Auto darkens/lightens by 10% for sexily-subtle gradients. + * Set this to FALSE to adjust automatic shade to be between given color + * and black (for darken) or white (for lighten) + */ + const DEFAULT_ADJUST = 10; + + /** + * Instantiates the class with a HEX value + * @param string $hex + * @throws Exception "Bad color format" + */ + function __construct( $hex ) { + // Strip # sign is present + $color = str_replace("#", "", $hex); + + // Make sure it's 6 digits + if( strlen($color) === 3 ) { + $color = $color[0].$color[0].$color[1].$color[1].$color[2].$color[2]; + } else if( strlen($color) != 6 ) { + throw new Exception("HEX color needs to be 6 or 3 digits long"); + } + + $this->_hsl = self::hexToHsl( $color ); + $this->_hex = $color; + $this->_rgb = self::hexToRgb( $color ); + } + + // ==================== + // = Public Interface = + // ==================== + + /** + * Given a HEX string returns a HSL array equivalent. + * @param string $color + * @return array HSL associative array + */ + public static function hexToHsl( $color ){ + + // Sanity check + $color = self::_checkHex($color); + + // Convert HEX to DEC + $R = hexdec($color[0].$color[1]); + $G = hexdec($color[2].$color[3]); + $B = hexdec($color[4].$color[5]); + + $HSL = []; + + $var_R = ($R / 255); + $var_G = ($G / 255); + $var_B = ($B / 255); + + $var_Min = min($var_R, $var_G, $var_B); + $var_Max = max($var_R, $var_G, $var_B); + $del_Max = $var_Max - $var_Min; + + $L = ($var_Max + $var_Min)/2; + + if ($del_Max == 0) + { + $H = 0; + $S = 0; + } + else + { + if ( $L < 0.5 ) $S = $del_Max / ( $var_Max + $var_Min ); + else $S = $del_Max / ( 2 - $var_Max - $var_Min ); + + $del_R = ( ( ( $var_Max - $var_R ) / 6 ) + ( $del_Max / 2 ) ) / $del_Max; + $del_G = ( ( ( $var_Max - $var_G ) / 6 ) + ( $del_Max / 2 ) ) / $del_Max; + $del_B = ( ( ( $var_Max - $var_B ) / 6 ) + ( $del_Max / 2 ) ) / $del_Max; + + if ($var_R == $var_Max) $H = $del_B - $del_G; + else if ($var_G == $var_Max) $H = ( 1 / 3 ) + $del_R - $del_B; + else if ($var_B == $var_Max) $H = ( 2 / 3 ) + $del_G - $del_R; + + if ($H<0) $H++; + if ($H>1) $H--; + } + + $HSL['H'] = ($H*360); + $HSL['S'] = $S; + $HSL['L'] = $L; + + return $HSL; + } + + /** + * Given a HSL associative array returns the equivalent HEX string + * @param array $hsl + * @return string HEX string + * @throws Exception "Bad HSL Array" + */ + public static function hslToHex( $hsl = [] ){ + // Make sure it's HSL + if(empty($hsl) || !isset($hsl["H"]) || !isset($hsl["S"]) || !isset($hsl["L"]) ) { + throw new Exception("Param was not an HSL array"); + } + + list($H,$S,$L) = [ $hsl['H']/360,$hsl['S'],$hsl['L'] ]; + + if( $S == 0 ) { + $r = $L * 255; + $g = $L * 255; + $b = $L * 255; + } else { + + if($L<0.5) { + $var_2 = $L*(1+$S); + } else { + $var_2 = ($L+$S) - ($S*$L); + } + + $var_1 = 2 * $L - $var_2; + + $r = 255 * self::_huetorgb( $var_1, $var_2, $H + (1/3) ); + $g = 255 * self::_huetorgb( $var_1, $var_2, $H ); + $b = 255 * self::_huetorgb( $var_1, $var_2, $H - (1/3) ); + + } + + // Convert to hex + $r = dechex(round($r)); + $g = dechex(round($g)); + $b = dechex(round($b)); + + // Make sure we get 2 digits for decimals + $r = (strlen("".$r)===1) ? "0".$r:$r; + $g = (strlen("".$g)===1) ? "0".$g:$g; + $b = (strlen("".$b)===1) ? "0".$b:$b; + + return $r.$g.$b; + } + + + /** + * Given a HEX string returns a RGB array equivalent. + * @param string $color + * @return array RGB associative array + */ + public static function hexToRgb( $color ){ + + // Sanity check + $color = self::_checkHex($color); + + // Convert HEX to DEC + $R = hexdec($color[0].$color[1]); + $G = hexdec($color[2].$color[3]); + $B = hexdec($color[4].$color[5]); + + $RGB['R'] = $R; + $RGB['G'] = $G; + $RGB['B'] = $B; + + return $RGB; + } + + + /** + * Given an RGB associative array returns the equivalent HEX string + * @param array $rgb + * @return string RGB string + * @throws Exception "Bad RGB Array" + */ + public static function rgbToHex( $rgb = [] ){ + // Make sure it's RGB + if(empty($rgb) || !isset($rgb["R"]) || !isset($rgb["G"]) || !isset($rgb["B"]) ) { + throw new Exception("Param was not an RGB array"); + } + + // https://github.com/mexitek/phpColors/issues/25#issuecomment-88354815 + // Convert RGB to HEX + $hex[0] = str_pad(dechex($rgb['R']), 2, '0', STR_PAD_LEFT); + $hex[1] = str_pad(dechex($rgb['G']), 2, '0', STR_PAD_LEFT); + $hex[2] = str_pad(dechex($rgb['B']), 2, '0', STR_PAD_LEFT); + + return implode( '', $hex ); + + } + + + /** + * Given a HEX value, returns a darker color. If no desired amount provided, then the color halfway between + * given HEX and black will be returned. + * @param int $amount + * @return string Darker HEX value + */ + public function darken( $amount = self::DEFAULT_ADJUST ){ + // Darken + $darkerHSL = $this->_darken($this->_hsl, $amount); + // Return as HEX + return self::hslToHex($darkerHSL); + } + + /** + * Given a HEX value, returns a lighter color. If no desired amount provided, then the color halfway between + * given HEX and white will be returned. + * @param int $amount + * @return string Lighter HEX value + */ + public function lighten( $amount = self::DEFAULT_ADJUST ){ + // Lighten + $lighterHSL = $this->_lighten($this->_hsl, $amount); + // Return as HEX + return self::hslToHex($lighterHSL); + } + + /** + * Given a HEX value, returns a mixed color. If no desired amount provided, then the color mixed by this ratio + * @param string $hex2 Secondary HEX value to mix with + * @param int $amount = -100..0..+100 + * @return string mixed HEX value + */ + public function mix($hex2, $amount = 0){ + $rgb2 = self::hexToRgb($hex2); + $mixed = $this->_mix($this->_rgb, $rgb2, $amount); + // Return as HEX + return self::rgbToHex($mixed); + } + + /** + * Creates an array with two shades that can be used to make a gradient + * @param int $amount Optional percentage amount you want your contrast color + * @return array An array with a 'light' and 'dark' index + */ + public function makeGradient( $amount = self::DEFAULT_ADJUST ) { + // Decide which color needs to be made + if( $this->isLight() ) { + $lightColor = $this->_hex; + $darkColor = $this->darken($amount); + } else { + $lightColor = $this->lighten($amount); + $darkColor = $this->_hex; + } + + // Return our gradient array + return [ "light" => $lightColor, "dark" => $darkColor ]; + } + + + /** + * Returns whether or not given color is considered "light" + * @param string|Boolean $color + * @param int $lighterThan + * @return boolean + */ + public function isLight( $color = FALSE, $lighterThan = 130 ){ + // Get our color + $color = ($color) ? $color : $this->_hex; + + // Calculate straight from rbg + $r = hexdec($color[0].$color[1]); + $g = hexdec($color[2].$color[3]); + $b = hexdec($color[4].$color[5]); + + return (( $r*299 + $g*587 + $b*114 )/1000 > $lighterThan); + } + + /** + * Returns whether or not a given color is considered "dark" + * @param string|Boolean $color + * @param int $darkerThan + * @return boolean + */ + public function isDark( $color = FALSE, $darkerThan = 130 ){ + // Get our color + $color = ($color) ? $color:$this->_hex; + + // Calculate straight from rbg + $r = hexdec($color[0].$color[1]); + $g = hexdec($color[2].$color[3]); + $b = hexdec($color[4].$color[5]); + + return (( $r*299 + $g*587 + $b*114 )/1000 <= $darkerThan); + } + + /** + * Returns the complimentary color + * @return string Complementary hex color + * + */ + public function complementary() { + // Get our HSL + $hsl = $this->_hsl; + + // Adjust Hue 180 degrees + $hsl['H'] += ($hsl['H']>180) ? -180:180; + + // Return the new value in HEX + return self::hslToHex($hsl); + } + + /** + * Returns your color's HSL array + */ + public function getHsl() { + return $this->_hsl; + } + /** + * Returns your original color + */ + public function getHex() { + return $this->_hex; + } + /** + * Returns your color's RGB array + */ + public function getRgb() { + return $this->_rgb; + } + + /** + * Returns the cross browser CSS3 gradient + * @param int $amount Optional: percentage amount to light/darken the gradient + * @param boolean $vintageBrowsers Optional: include vendor prefixes for browsers that almost died out already + * @param string $prefix Optional: prefix for every lines + * @param string $suffix Optional: suffix for every lines + * @link http://caniuse.com/css-gradients Resource for the browser support + * @return string CSS3 gradient for chrome, safari, firefox, opera and IE10 + */ + public function getCssGradient( $amount = self::DEFAULT_ADJUST, $vintageBrowsers = FALSE, $suffix = "" , $prefix = "" ) { + + // Get the recommended gradient + $g = $this->makeGradient($amount); + + $css = ""; + /* fallback/image non-cover color */ + $css .= "{$prefix}background-color: #".$this->_hex.";{$suffix}"; + + /* IE Browsers */ + $css .= "{$prefix}filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#".$g['light']."', endColorstr='#".$g['dark']."');{$suffix}"; + + /* Safari 4+, Chrome 1-9 */ + if ( $vintageBrowsers ) { + $css .= "{$prefix}background-image: -webkit-gradient(linear, 0% 0%, 0% 100%, from(#".$g['light']."), to(#".$g['dark']."));{$suffix}"; + } + + /* Safari 5.1+, Mobile Safari, Chrome 10+ */ + $css .= "{$prefix}background-image: -webkit-linear-gradient(top, #".$g['light'].", #".$g['dark'].");{$suffix}"; + + /* Firefox 3.6+ */ + if ( $vintageBrowsers ) { + $css .= "{$prefix}background-image: -moz-linear-gradient(top, #".$g['light'].", #".$g['dark'].");{$suffix}"; + } + + /* Opera 11.10+ */ + if ( $vintageBrowsers ) { + $css .= "{$prefix}background-image: -o-linear-gradient(top, #".$g['light'].", #".$g['dark'].");{$suffix}"; + } + + /* Unprefixed version (standards): FF 16+, IE10+, Chrome 26+, Safari 7+, Opera 12.1+ */ + $css .= "{$prefix}background-image: linear-gradient(to bottom, #".$g['light'].", #".$g['dark'].");{$suffix}"; + + // Return our CSS + return $css; + } + + // =========================== + // = Private Functions Below = + // =========================== + + + /** + * Darkens a given HSL array + * @param array $hsl + * @param int $amount + * @return array $hsl + */ + private function _darken( $hsl, $amount = self::DEFAULT_ADJUST){ + // Check if we were provided a number + if( $amount ) { + $hsl['L'] = ($hsl['L'] * 100) - $amount; + $hsl['L'] = ($hsl['L'] < 0) ? 0:$hsl['L']/100; + } else { + // We need to find out how much to darken + $hsl['L'] = $hsl['L']/2; + } + + return $hsl; + } + + /** + * Lightens a given HSL array + * @param array $hsl + * @param int $amount + * @return array $hsl + */ + private function _lighten( $hsl, $amount = self::DEFAULT_ADJUST){ + // Check if we were provided a number + if( $amount ) { + $hsl['L'] = ($hsl['L'] * 100) + $amount; + $hsl['L'] = ($hsl['L'] > 100) ? 1:$hsl['L']/100; + } else { + // We need to find out how much to lighten + $hsl['L'] += (1-$hsl['L'])/2; + } + + return $hsl; + } + + /** + * Mix 2 rgb colors and return an rgb color + * @param array $rgb1 + * @param array $rgb2 + * @param int $amount ranged -100..0..+100 + * @return array $rgb + * + * ported from http://phpxref.pagelines.com/nav.html?includes/class.colors.php.source.html + */ + private function _mix($rgb1, $rgb2, $amount = 0) { + + $r1 = ($amount + 100) / 100; + $r2 = 2 - $r1; + + $rmix = (($rgb1['R'] * $r1) + ($rgb2['R'] * $r2)) / 2; + $gmix = (($rgb1['G'] * $r1) + ($rgb2['G'] * $r2)) / 2; + $bmix = (($rgb1['B'] * $r1) + ($rgb2['B'] * $r2)) / 2; + + return ['R' => $rmix, 'G' => $gmix, 'B' => $bmix]; + } + + /** + * Given a Hue, returns corresponding RGB value + * @param int $v1 + * @param int $v2 + * @param int $vH + * @return int + */ + private static function _huetorgb( $v1,$v2,$vH ) { + if( $vH < 0 ) { + $vH += 1; + } + + if( $vH > 1 ) { + $vH -= 1; + } + + if( (6*$vH) < 1 ) { + return ($v1 + ($v2 - $v1) * 6 * $vH); + } + + if( (2*$vH) < 1 ) { + return $v2; + } + + if( (3*$vH) < 2 ) { + return ($v1 + ($v2-$v1) * ( (2/3)-$vH ) * 6); + } + + return $v1; + + } + + /** + * You need to check if you were given a good hex string + * @param string $hex + * @return string Color + * @throws Exception "Bad color format" + */ + private static function _checkHex( $hex ) { + // Strip # sign is present + $color = str_replace("#", "", $hex); + + // Make sure it's 6 digits + if( strlen($color) == 3 ) { + $color = $color[0].$color[0].$color[1].$color[1].$color[2].$color[2]; + } else if( strlen($color) != 6 ) { + throw new Exception("HEX color needs to be 6 or 3 digits long"); + } + + return $color; + } } diff --git a/view/theme/frio/php/default.php b/view/theme/frio/php/default.php index 4fa80fbb59..d569b190ed 100644 --- a/view/theme/frio/php/default.php +++ b/view/theme/frio/php/default.php @@ -36,7 +36,7 @@ $is_singleuser_class = $is_singleuser ? "is-singleuser" : "is-not-singleuser"; echo $page['title']; } ?> - + get($uid, 'frio', 'scheme_accent') - ?: DI::config()->get('frio', 'scheme_accent') ?: FRIO_SCHEME_ACCENT_BLUE; + $scheme_accent = DI::pConfig()->get($uid, 'frio', 'scheme_accent') ?: + DI::config()->get('frio', 'scheme_accent') ?: FRIO_SCHEME_ACCENT_BLUE; require_once $schemefile; } } -$nav_bg ??= DI::pConfig()->get($uid, 'frio', 'nav_bg', DI::config()->get('frio', 'nav_bg', '#708fa0')); +$nav_bg = $nav_bg ?? DI::pConfig()->get($uid, 'frio', 'nav_bg', DI::config()->get('frio', 'nav_bg', '#708fa0')); echo ''; ?> @@ -76,8 +76,8 @@ echo ''; str_replace( "~system.banner~", DI::config()->get('system', 'banner'), - $page['nav'], - ), + $page['nav'] + ) ); }; diff --git a/view/theme/frio/php/frio_boot.php b/view/theme/frio/php/frio_boot.php index ea9a88c454..2a1734bda0 100644 --- a/view/theme/frio/php/frio_boot.php +++ b/view/theme/frio/php/frio_boot.php @@ -1,5 +1,4 @@ getCurrentTheme() . '/' - . ((DI::page()['template'] ?? '') ?: 'default') . '.php'; + . ((DI::page()['template'] ?? '') ?: 'default' ) . '.php'; if (file_exists($template)) { require_once $template; } else { @@ -43,13 +42,12 @@ function load_page(AppHelper $appHelper) * * @return bool */ -function is_modal() -{ - $is_modal = false; +function is_modal() { + $is_modal = false; $modalpages = get_modalpage_list(); foreach ($modalpages as $r => $value) { - if (strpos($_REQUEST['pagename'], (string) $value) !== false) { + if(strpos($_REQUEST['pagename'],$value) !== false) { $is_modal = true; } } @@ -65,14 +63,13 @@ function is_modal() * * @return array Page names as path */ -function get_modalpage_list() -{ +function get_modalpage_list() { //Array of pages which getting bootstrap modal dialogs $modalpages = [ 'message/new', 'settings/oauth/add', 'calendar/event/new', - // 'fbrowser/image/' +// 'fbrowser/image/' ]; return $modalpages; @@ -86,11 +83,10 @@ function get_modalpage_list() * * @return array Pagenames as path */ -function get_standard_page_list() -{ +function get_standard_page_list() { //Arry of pages wich getting the standard page template $standardpages = [//'profile', - // 'fbrowser/image/' +// 'fbrowser/image/' ]; return $standardpages; @@ -105,13 +101,12 @@ function get_standard_page_list() * @param string $pagetitle Title of the actual page * @return bool */ -function is_standard_page($pagetitle) -{ +function is_standard_page($pagetitle) { $is_standard_page = false; - $standardpages = get_standard_page_list(); + $standardpages = get_standard_page_list(); foreach ($standardpages as $r => $value) { - if (strpos($pagetitle, (string) $value) !== false) { + if(strpos($pagetitle,$value) !== false) { $is_standard_page = true; } } @@ -124,20 +119,16 @@ function is_standard_page($pagetitle) * @param type $pagetitle * @return string */ -function get_page_type($pagetitle) -{ +function get_page_type($pagetitle) { $page_type = ""; - if (is_modal()) { + if(is_modal()) $page_type = "modal"; - } - if (is_standard_page($pagetitle)) { + if(is_standard_page($pagetitle)) $page_type = "standard_page"; - } - if ($page_type) { + if($page_type) return $page_type; - } } diff --git a/view/theme/frio/php/scheme.php b/view/theme/frio/php/scheme.php index 50797cc42d..5c11c8a5d1 100644 --- a/view/theme/frio/php/scheme.php +++ b/view/theme/frio/php/scheme.php @@ -1,5 +1,4 @@ getCurrentTheme(); + $theme = DI::appHelper()->getCurrentTheme(); $themepath = 'view/theme/' . $theme . '/'; - $scheme = Strings::sanitizeFilePathItem($scheme) ?: FRIO_DEFAULT_SCHEME; + $scheme = Strings::sanitizeFilePathItem($scheme) ?: FRIO_DEFAULT_SCHEME; $info = [ - 'name' => $scheme, + 'name' => $scheme, 'description' => '', - 'author' => [], - 'version' => '', - 'overwrites' => [], - 'accented' => false, + 'author' => [], + 'version' => '', + 'overwrites' => [], + 'accented' => false, ]; if (!is_file($themepath . 'scheme/' . $scheme . '.php')) { @@ -60,8 +59,8 @@ function get_scheme_info($scheme) if (count($values) < 2) { continue; } - [$k, $v] = $values; - $k = strtolower($k); + list($k, $v) = $values; + $k = strtolower($k); if ($k == 'author') { $r = preg_match('|([^<]+)<([^>]+)>|', $v, $m); if ($r) { @@ -99,7 +98,7 @@ function frio_scheme_get_list(): array foreach (glob('view/theme/frio/scheme/*.php') ?: [] as $file) { $scheme = basename($file, '.php'); if (!in_array($scheme, ['default', 'light', 'dark', 'black'])) { - $scheme_info = get_scheme_info($scheme); + $scheme_info = get_scheme_info($scheme); $schemes[$scheme] = $scheme_info['name'] ?? ucfirst($scheme); } } @@ -126,7 +125,8 @@ function frio_scheme_get_current_for_user(int $uid) { $available = array_keys(frio_scheme_get_list()); - $scheme = DI::pConfig()->get($uid, 'frio', 'scheme') ?: + $scheme = + DI::pConfig()->get($uid, 'frio', 'scheme') ?: DI::pConfig()->get($uid, 'frio', 'schema') ?: DI::config()->get('frio', 'scheme') ?: DI::config()->get('frio', 'schema'); diff --git a/view/theme/frio/scheme/dark.css b/view/theme/frio/scheme/dark.css index 06ab486dc8..9f760baaca 100644 --- a/view/theme/frio/scheme/dark.css +++ b/view/theme/frio/scheme/dark.css @@ -180,11 +180,6 @@ main .nav-tabs > li.active > a:hover { background: none; color: $font_color_darker; } - -.wall-item-actions .btn { - box-shadow: none; - -webkit-box-shadow: none; -} .btn.focus, .btn:focus, .btn:hover { diff --git a/view/theme/frio/scheme/gnome.css b/view/theme/frio/scheme/gnome.css deleted file mode 100644 index 0c9f252727..0000000000 --- a/view/theme/frio/scheme/gnome.css +++ /dev/null @@ -1,507 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2010-2024 the Friendica project - * - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -/* - Licence : AGPL - Created on : 18.11.2025 - Author : Daniel Buck tealk@friendica.rollenspiel.monster -*/ -:root { - color-scheme: light dark; - - /* Accent Colors (from PHP) */ - --accent-color: $link_color; - --accent-hover: $menu_background_hover_color; - - /* Light Mode - GNOME Adwaita */ - --bg-primary: #ffffff; - --bg-secondary: #ebebed; - --bg-tertiary: #f3f3f5; - - --text-primary: #1d1d20; - --text-secondary: #5e5c64; - --text-disabled: #9a9996; - - --border-color: #cdc7c2; - --shadow: rgba(0, 0, 0, 0.1); -} - -/* Dark Mode - GNOME Adwaita Dark */ -@media (prefers-color-scheme: dark) { - :root { - --bg-primary: #1d1d20; - --bg-secondary: #2e2e32; - --bg-tertiary: #28282c; - - --text-primary: #ffffff; - --text-secondary: #c9c0c0; - --text-disabled: #8c8c8d; - - --border-color: #1b1b1b; - --shadow: rgba(0, 0, 0, 0.3); - } -} - -/* Body */ -body { - background-color: var(--bg-primary); - color: var(--text-primary); - transition: background-color 0.3s ease, color 0.3s ease; -} - -section > .generic-page-wrapper, -.videos-content-wrapper, -.suggest-content-wrapper, -.help-content-wrapper, -.match-content-wrapper, -.dirfind-content-wrapper, -.delegation-content-wrapper, -.notes-content-wrapper, -.message-content-wrapper, -.apps-content-wrapper, -#adminpage, -.delegate-content-wrapper, -.uexport-content-wrapper, -.dfrn_request-content-wrapper, -.friendica-content-wrapper, -.credits-content-wrapper, -.nocircle-content-wrapper, -.profperm-content-wrapper, -.invite-content-wrapper, -.tos-content-wrapper, -.fsuggest-content-wrapper, -.help-block { - color: var(--text-primary); -} - -div.login-form, -div.login-form label { - background-color: var(--bg-secondary) !important; - color: var(--text-primary) !important; -} - -/* Navigation */ -header #banner #logo-img, -.navbar-brand #logo-img { - background-color: var(--text-primary); -} - -#topbar-first, -.navbar { - background-color: var(--bg-secondary) !important; - border-bottom: 1px solid var(--border-color); - - button { - color: var(--text-primary) !important; - } - - #nav-notifications-menu { - - .dropdown-header, - a, - .time { - color: var(--text-primary) !important; - } - - li.notif-entry { - color: var(--text-primary); - border-bottom: 1px solid var(--accent-color); - - &:hover { - background-color: var(--bg-tertiary); - border-left: 3px solid var(--accent-hover); - } - } - - li.notification-unseen { - border-left: 3px solid var(--accent-color); - background-color: unset; - } - - li.notif-entry:hover { - background-color: unset; - border-left: 3px solid var(--accent-hover); - } - } - - .nav > .open > a, - .nav > .open > button, - .nav > .open > button:focus { - background-color: var(--bg-tertiary) !important; - } - - .nav > li > button:not(#main-menu):hover { - background-color: var(--bg-tertiary) !important; - } - - .nav > li { - & > a, - & > button { - color: var(--text-primary) !important; - - &:hover { - background-color: var(--bg-tertiary); - } - } - } -} - -nav.navbar { - .nav > li { - & > a, - & > button { - color: var(--text-primary) !important; - - &:hover { - background-color: var(--bg-tertiary) - } - } - } -} - -#topbar-second { - background-color: var(--bg-secondary); - border-color: 1px solid var(--border-color); -} - -/* Nav Icons */ -.nav-link, -.navbar-brand, -#nav-user-linkmenu { - i { - color: var(--text-primary); - } -} - -/* Links */ -a { - color: var(--accent-color); - transition: color 0.2s; - - &:hover { - color: var(--accent-hover); - } -} - -.desktop-view .wall-item-container .fakelink, -.desktop-view .toplevel_item .fakelink { - color: var(--text-primary); -} - -.media .time { - color: var(--text-secondary); -} - -/* Buttons */ -.btn-default { - background-color: var(--bg-secondary) !important; - border-color: var(--bg-secondary) !important; - - &:hover, - &:focus { - background-color: var(--bg-tertiary) !important; - border-color: var(--bg-tertiary) !important; - } -} - -.btn-primary, -button.btn-primary { - background-color: var(--accent-color) !important; - border-color: var(--accent-color) !important; - - &:hover, - &:focus { - background-color: var(--accent-hover) !important; - border-color: var(--accent-hover) !important; - } -} - -.btn { - background-color: var(--bg-tertiary); - color: var(--text-primary) !important; -} - -/* Cards & Panels */ -.panel, -.wall-item-container, -.card { - background-color: var(--bg-secondary); - border-color: var(--border-color); - border-radius: 12px; - transition: background-color 0.3s, border-color 0.3s; -} - -.desktop-view .wall-item-container .wall-item-content a { - color: var(--text-secondary); -} - -.wall-item-container .wall-item-responses a { - color: var(--text-secondary) !important; -} - -.wall-item-container:hover .wall-item-content a { - color: var(--accent-color); -} - -.panel-footer { - background-color: var(--bg-secondary); - border-top: 1px solid var(--border-color); -} - -.event-card { - .event-card-title { - color: var(--text-primary); - } - - .event-card-content { - color: var(--text-secondary); - } -} - -/* Input Fields */ -input, -textarea, -select, -.form-control, -.form-control[disabled], -.form-control[readonly], -fieldset[disabled] { - background-color: var(--bg-primary); - color: var(--text-primary); - border-color: var(--accent-color); - transition: background-color 0.3s, color 0.3s, border-color 0.3s; - - &:focus { - border-color: var(--accent-color); - box-shadow: 0 0 0 2px rgba(53, 132, 228, 0.2); - } -} - -/* Comments */ -.comment-fake-form, -.wall-item-comment-wrapper { - padding: 10px; - border-top: 1px solid var(--accent-color); - background-color: var(--bg-secondary); - border-radius: 0 0 4px 4px; - margin-bottom: 0; -} - -/* Well */ -.well { - background-image: none; - border: none; - background-color: var(--bg-tertiary); -} - -/* Sidebar */ -#aside_spacer { - background-color: var(--bg-secondary); - transition: background-color 0.3s; -} - -aside { - background-color: transparent; - - nav { - background-color: var(--bg-secondary); - } - - .widget, - .contact-block-content { - color: var(--text-secondary); - - li a, - li a:hover { - color: var(--text-primary); - } - } -} - -#offcanvasUsermenu { - background-color: var(--bg-secondary); - - li, - a { - background-color: transparent; - color: var(--text-primary); - } - - li.list-group-item { - border: none; - } - - li.divider { - background-color: var(--accent-color); - - hr { - display: none; - border-color: var(--accent-color); - } - } -} - -@media screen and (max-width: 990px) { - aside { - background-color: var(--bg-secondary); - } -} - -/* Text Colors */ -.text-muted { - color: var(--text-secondary) !important; -} -.hljs, .hljs-subst { - color: var(--text-primary) !important; -} - -/* Dropdowns */ -.dropdown-menu { - background-color: var(--bg-secondary) !important; - border-color: var(--border-color) !important; - - li a, - li .btn-link { - color: var(--text-primary) !important; - } - - li > a { - border-left: 3px solid var(--accent-color) !important; - } -} - -.dropdown-item { - color: var(--text-primary); - - &:hover { - background-color: var(--bg-tertiary); - } -} - -/* Modals */ -.modal-content { - background-color: var(--bg-primary); - border-color: var(--border-color); -} - -.modal-header { - border-bottom-color: var(--border-color); -} - -.modal-footer { - border-top-color: var(--border-color); -} - -#colorbox { - #cboxContent { - background-color: var(--bg-primary); - color: var(--text-primary); - } - #cboxTopLeft, - #cboxTopCenter, - #cboxTopRight, - #cboxMiddleLeft, - #cboxMiddleRight, - #cboxBottomCenter, - #cboxBottomLeft, - #cboxBottomRight { - background-image: none; - background-color: var(--bg-secondary); - } -} - -/* Navigation Links */ -.nav > li > a { - &:hover { - text-decoration: none; - background-color: var(--bg-tertiary); - } -} - -/* Wall Items */ -.wall-item-name-link { - color: var(--text-primary); - font-weight: bold; - - span.wall-item-name { - color: var(--text-primary); - } -} - -.wall-item-ago { - color: var(--text-primary); - - time { - color: var(--text-primary) !important; - } -} - -.wall-item-actions { - button { - color: var(--text-primary); - } -} - -.comment-edit-form .preview { - background-color: var(--bg-secondary) !important; - border: none !important; -} - -pre, code { - color: var(--text-primary); - background-color: var(--bg-tertiary); - border-color: var(--border-color); -} - -.acpopup { - color: var(--text-primary); - background-color: var(--bg-secondary); - - .textcomplete-item.active > a { - background-color: var(--bg-tertiary) !important; - } -} - -.table-striped > tbody > tr:nth-of-type(2n+1) { - background-color: var(--bg-tertiary); -} - -.fg-emoji-nav { - background-color: var(--bg-primary) !important; - - ul li a:hover { - background-color: var(--bg-tertiary) !important; - } -} - -.fg-emoji-container { - background-color: var(--bg-primary) !important; - - input { - background-color: var(--bg-secondary) !important; - } -} - -.fg-picker-special-buttons a { - background-color: var(--accent-color) !important; -} - -.fg-emoji-picker-search .fg-emoji-picker-search-icon { - background-color: var(--bg-secondary) !important; -} - -.fg-emoji-list li a { - background-color: var(--bg-primary) !important; - - &:hover { - background-color: var(--accent-hover) !important; - } -} - -.adminpage .table-hover > tbody > tr:hover + tr.details, -.table-hover > tbody > tr:hover { - background-color: var(--bg-tertiary); -} diff --git a/view/theme/frio/scheme/gnome.php b/view/theme/frio/scheme/gnome.php deleted file mode 100644 index 0b9eca39b2..0000000000 --- a/view/theme/frio/scheme/gnome.php +++ /dev/null @@ -1,29 +0,0 @@ - tealk@friendica.rollenspiel.monster - * Overwrites: nav_bg, nav_icon_color, link_color, background_color, background_image, login_bg_color, contentbg_transp - * Accented: yes - */ - -require_once 'view/theme/frio/php/PHPColors/Color.php'; - -$accentColor = new Color($scheme_accent); - -$menu_background_hover_color = '#' . $accentColor->darken(10); -$nav_bg = '#2c2c34'; -$link_color = '#' . $accentColor->lighten(1); -$nav_icon_color = '#d4d4d4'; -$background_color = '#1b1b1b'; -$contentbg_transp = '0'; -$font_color = '#cccccc'; -$font_color_darker = '#acacac'; -$font_color_lighter = '#444444'; -$background_image = ''; diff --git a/view/theme/frio/scheme/light.css b/view/theme/frio/scheme/light.css deleted file mode 100644 index 126c5a4281..0000000000 --- a/view/theme/frio/scheme/light.css +++ /dev/null @@ -1,15 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2010-2024 the Friendica project - * - * SPDX-License-Identifier: AGPL-3.0-or-later - */ - -/* - Licence : AGPL - Created on : 12.02.2026 - Author : Marcus Funch -*/ - -.mod-home.is-not-singleuser .login-form { - background: #fcfcfc; -} diff --git a/view/theme/frio/templates/admin/logs/view.tpl b/view/theme/frio/templates/admin/logs/view.tpl index b8f7bc8a26..2ef960d7af 100644 --- a/view/theme/frio/templates/admin/logs/view.tpl +++ b/view/theme/frio/templates/admin/logs/view.tpl @@ -7,23 +7,25 @@

    {{$title}} - {{$page}}

    -

    {{$logname}}

    +

    {{$logname}}

    {{if $error }}

    {{$error nofilter}}

    {{else}}
    -
    -
    {{$id_header}} {{$command_header}}
    diff --git a/view/theme/frio/templates/admin/queue.tpl b/view/theme/frio/templates/admin/queue.tpl index 74db4c8dad..18470cca43 100644 --- a/view/theme/frio/templates/admin/queue.tpl +++ b/view/theme/frio/templates/admin/queue.tpl @@ -4,19 +4,11 @@ * * SPDX-License-Identifier: AGPL-3.0-or-later *}} -
    +

    {{$title}} - {{$page}} ({{$count}})

    {{$info}}

    - - - - - - {{if ($status == "deferred") }}{{/if}} - - diff --git a/view/theme/frio/templates/admin/summary.tpl b/view/theme/frio/templates/admin/summary.tpl index b8e1ee3589..d96785430f 100644 --- a/view/theme/frio/templates/admin/summary.tpl +++ b/view/theme/frio/templates/admin/summary.tpl @@ -5,7 +5,7 @@ * SPDX-License-Identifier: AGPL-3.0-or-later *}} -
    +

    {{$title}} - {{$page}}

    {{if $warningtext|count}} @@ -17,36 +17,35 @@ {{/if}}
    - {{* The work queues short statistic and the friendica version. *}} -
    {{$id_header}} {{$command_header}}
    - - - - - - - - -
    {{$queues.label}}{{$queues.deferred}} - {{$queues.workerq}}
    {{$version_label}}{{$platform}} '{{$codename}}' {{$VERSION}} - {{$build}}
    + {{* The work queues short statistic. *}} +
    +
    {{$queues.label}}
    +
    {{$queues.deferred}} - {{$queues.workerq}}
    +
    {{* List enabled addons. *}} -
    -

    {{$addons.0}}

    - - {{foreach $addons.1 as $a}} - - - - +
    +
    +
    {{$addons.0}}
    +
    + {{foreach $addons.1 as $p}} + {{$p}}
    {{/foreach}} -
    {{$a.1.name}}{{$a.1.description}}
    +
    +
    + + {{* The Friendica version. *}} +
    +
    +
    {{$version_label}}
    +
    {{$platform}} '{{$codename}}' {{$VERSION}} - {{$build}}
    -

    {{$link_enable_addons}}

    {{* Server Settings. *}} -
    -

    {{$serversettings.label}}

    -
    +
    +
    +
    {{$serversettings.label}}
    +
    @@ -64,4 +63,6 @@ +
    + diff --git a/view/theme/frio/templates/album_edit.tpl b/view/theme/frio/templates/album_edit.tpl index cc637bf050..508a21131d 100644 --- a/view/theme/frio/templates/album_edit.tpl +++ b/view/theme/frio/templates/album_edit.tpl @@ -8,7 +8,7 @@
    - +
    diff --git a/view/theme/frio/templates/auto_request.tpl b/view/theme/frio/templates/auto_request.tpl index 8c795a21a9..8f0496d84a 100644 --- a/view/theme/frio/templates/auto_request.tpl +++ b/view/theme/frio/templates/auto_request.tpl @@ -35,7 +35,7 @@ {{$myaddr}} {{else}} - + {{/if}}
    diff --git a/view/theme/frio/templates/circle_edit.tpl b/view/theme/frio/templates/circle_edit.tpl index e36a9bb7af..049165cb6b 100644 --- a/view/theme/frio/templates/circle_edit.tpl +++ b/view/theme/frio/templates/circle_edit.tpl @@ -18,9 +18,6 @@ - - - {{if $drop}}{{$drop nofilter}}{{/if}}
    {{/if}} diff --git a/view/theme/frio/templates/directory_header.tpl b/view/theme/frio/templates/directory_header.tpl index 93690376f2..c84c754f4c 100644 --- a/view/theme/frio/templates/directory_header.tpl +++ b/view/theme/frio/templates/directory_header.tpl @@ -23,7 +23,6 @@
    @@ -39,8 +38,6 @@
      {{foreach $contacts as $contact}}
    • {{include file="contact/entry.tpl"}}
    • - {{foreachelse}} -
    • {{$no_results}}
    • {{/foreach}}
    diff --git a/view/theme/frio/templates/jot.tpl b/view/theme/frio/templates/jot.tpl index a28aabb100..10f20019c1 100644 --- a/view/theme/frio/templates/jot.tpl +++ b/view/theme/frio/templates/jot.tpl @@ -105,9 +105,6 @@ {{/if}}
    - {{if $placeholdersummary}} -
    - {{/if}} {{if $placeholdercategory}}
    {{/if}} @@ -150,7 +147,6 @@ diff --git a/view/theme/frio/templates/media/browser.tpl b/view/theme/frio/templates/media/browser.tpl index 0ee2e9f686..366bbc8944 100644 --- a/view/theme/frio/templates/media/browser.tpl +++ b/view/theme/frio/templates/media/browser.tpl @@ -10,11 +10,11 @@
    {{* The breadcrumb navigation *}} -
    PHP
    - {{* Number of pending registrations. *}} - - - - + {{* Number of pending registrations. *}} +
    +
    +
    {{$pending.0}}
    +
    {{$pending.1}}
    +
    - {{* Number of registered users *}} - - - - -
    {{$pending.0}}{{$pending.1}}
    {{$users.0}}{{$users.1}}
    + {{* Number of registered users *}} +
    +
    +
    {{$users.0}}
    +
    {{$users.1}}
    +
    {{* Account types of registered users. *}} -

    {{$account_type_header}}

    - - {{foreach $accounts as $p}} - - - - - {{/foreach}} -
    {{$p.0}}{{if $p.1}}{{$p.1}}{{else}}0{{/if}}
    + {{foreach $accounts as $p}} +
    +
    +
    {{$p.0}}
    +
    {{if $p.1}}{{$p.1}}{{else}}0{{/if}}
    +
    + {{/foreach}} +
    + +
    +
    diff --git a/view/theme/frio/templates/nav.tpl b/view/theme/frio/templates/nav.tpl index 2149953f06..00619a0f51 100644 --- a/view/theme/frio/templates/nav.tpl +++ b/view/theme/frio/templates/nav.tpl @@ -97,7 +97,7 @@ {{if $nav.calendar}} {{/if}} @@ -197,16 +197,24 @@ {{/if}} {{foreach $nav.usermenu as $usermenu}}
  • - {{$usermenu.1}}
  • {{/foreach}} -

  • +

  • + {{if $nav.notifications}} +
  • + + + {{$nav.notifications.1}} + +
  • + {{/if}} {{if $nav.messages}} -
  • +
  • @@ -216,8 +224,9 @@
  • {{/if}} +

  • {{if $nav.contacts}} -
  • +
  • @@ -353,9 +362,17 @@
  • {{/foreach}} - {{if $nav.contacts || $nav.messages || $nav.delegation}} + {{if $nav.notifications || $nav.contacts || $nav.messages || $nav.delegation}}

  • {{/if}} + {{if $nav.notifications}} +
  • + {{$nav.notifications.1}} + +
  • + {{/if}} {{if $nav.contacts}}
  • + {{include file="field_input.tpl" field=$album}} {{include file="field_textarea.tpl" field=$caption}} + {{include file="field_input.tpl" field=$tags}} {{include file="field_radio.tpl" field=$rotate_none}} {{include file="field_radio.tpl" field=$rotate_cw}} diff --git a/view/theme/frio/templates/photo_view.tpl b/view/theme/frio/templates/photo_view.tpl index 4db4f5d8d2..4ad373ba45 100644 --- a/view/theme/frio/templates/photo_view.tpl +++ b/view/theme/frio/templates/photo_view.tpl @@ -21,7 +21,7 @@ {{if $tools.view}} - {{$back_text}} + {{$back_to_viewing_text}} {{/if}} {{if $tools.edit}} @@ -71,9 +71,44 @@ {{* The photo description *}}
    {{$desc}}
    + {{* Tags and mentions *}} + {{if $tags.tags}} +
    +

    {{$tags.title}}

    + {{foreach $tags.tags as $t}} + + {{$t.name nofilter}} + {{if $t.removeurl}} (X) {{/if}} + + {{/foreach}} +
    + {{/if}} + + {{if $tags.removeanyurl}} + + {{/if}} + {{* The part for editing the photo - only available for the edit subpage *}} {{if $edit}}{{$edit nofilter}}{{/if}} + {{if $likebuttons}} +
    + {{$likebuttons nofilter}} + {{$like nofilter}} + {{$dislike nofilter}} +
    + {{/if}}
  • + +{{if !$edit}} + {{* Insert the comments *}} +
    + {{$comments nofilter}} +
    + + {{$paginate nofilter}} +{{/if}}
    diff --git a/view/theme/frio/templates/photos_default_uploader_box.tpl b/view/theme/frio/templates/photos_default_uploader_box.tpl index a7ecbd4c89..1c5b9c9758 100644 --- a/view/theme/frio/templates/photos_default_uploader_box.tpl +++ b/view/theme/frio/templates/photos_default_uploader_box.tpl @@ -4,4 +4,4 @@ * * SPDX-License-Identifier: AGPL-3.0-or-later *}} - + diff --git a/view/theme/frio/templates/photos_upload.tpl b/view/theme/frio/templates/photos_upload.tpl index 8ba753e2d3..d315a050c5 100644 --- a/view/theme/frio/templates/photos_upload.tpl +++ b/view/theme/frio/templates/photos_upload.tpl @@ -11,21 +11,22 @@
    {{$usage}}
    -{{if $is_album_context}} - -{{else}}
    - - + + + {{foreach $albumselect as $value => $name}} {{/foreach}} -

    {{$albumtext_description}}

    -{{/if}} + +
    + + +
    {{if $alt_uploader}}
    diff --git a/view/theme/frio/templates/profile/vcard.tpl b/view/theme/frio/templates/profile/vcard.tpl index 5773dd5515..865f9a3675 100644 --- a/view/theme/frio/templates/profile/vcard.tpl +++ b/view/theme/frio/templates/profile/vcard.tpl @@ -5,11 +5,10 @@ * SPDX-License-Identifier: AGPL-3.0-or-later *}}
    -
    {{$profile.name}} - {{if $is_owner }} + {{if $change_profile_picture_text }}
    {{$change_profile_picture_text}}
    {{/if}}
    @@ -33,14 +32,7 @@

    {{$profile.name}}

    - {{if $profile.addr}}
    {{include file="sub/punct_wrap.tpl" text=$profile.addr}}
    {{/if}} - {{if $is_owner }} - + {{if $profile.addr}}
    {{include file="sub/punct_wrap.tpl" text=$profile.addr}}
    {{/if}} {{if $profile.about}}
    {{$profile.about nofilter}}
    {{/if}} @@ -141,13 +133,6 @@
    {{/if}} - {{if $member_since}} -

    - {{$member_since.0}} - {{$member_since.1}} -

    - {{/if}} - {{if $about}}