diff --git a/.codecov.yml b/.codecov.yml index 35509a879..0c54747c9 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -1,10 +1,14 @@ +codecov: + branch: develop + ci: + - drone.friendi.ca coverage: + precision: 2 + round: down + range: "70...100" status: - project: - default: - target: auto - threshold: null - base: auto + project: off + patch: off comment: off diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 000000000..65211da2c --- /dev/null +++ b/.drone.yml @@ -0,0 +1,439 @@ +kind: pipeline +name: mysql8.0-php7.1 + +steps: +- name: mysql8.0-php7.1 + image: friendicaci/php7.1:php7.1.32 + commands: + - NOCOVERAGE=true ./autotest.sh mysql + environment: + MYSQL_USERNAME: friendica + MYSQL_PASSWORD: friendica + MYSQL_DATABASE: friendica + MYSQL_HOST: mysql + +services: +- name: mysql + image: mysql:8.0 + command: [ "--default-authentication-plugin=mysql_native_password" ] + environment: + MYSQL_ROOT_PASSWORD: friendica + MYSQL_USER: friendica + MYSQL_PASSWORD: friendica + MYSQL_DATABASE: friendica + volumes: + - name: cache + path: /var/lib/mysql + +volumes: +- name: cache + temp: {} + +trigger: + branch: + - master + - develop + - "*-rc" + event: + - pull_request + - push +--- +kind: pipeline +name: mysql8.0-php7.2 + +steps: +- name: mysql8.0-php7.2 + image: friendicaci/php7.2:php7.2.22 + commands: + - NOCOVERAGE=true ./autotest.sh mysql + environment: + MYSQL_USERNAME: friendica + MYSQL_PASSWORD: friendica + MYSQL_DATABASE: friendica + MYSQL_HOST: mysql + +services: +- name: mysql + image: mysql:8.0 + command: [ "--default-authentication-plugin=mysql_native_password" ] + environment: + MYSQL_ROOT_PASSWORD: friendica + MYSQL_USER: friendica + MYSQL_PASSWORD: friendica + MYSQL_DATABASE: friendica + volumes: + - name: cache + path: /var/lib/mysql + +volumes: + - name: cache + temp: {} + +trigger: + branch: + - master + - develop + - "*-rc" + event: + - pull_request + - push +--- +kind: pipeline +name: mysql8.0-php7.3 + +steps: +- name: mysql8.0-php7.3 + image: friendicaci/php7.3:php7.3.9 + commands: + - NOCOVERAGE=true ./autotest.sh mysql + environment: + MYSQL_USERNAME: friendica + MYSQL_PASSWORD: friendica + MYSQL_DATABASE: friendica + MYSQL_HOST: mysql + +services: +- name: mysql + image: mysql:8.0 + command: [ "--default-authentication-plugin=mysql_native_password" ] + environment: + MYSQL_ROOT_PASSWORD: friendica + MYSQL_USER: friendica + MYSQL_PASSWORD: friendica + MYSQL_DATABASE: friendica + volumes: + - name: cache + path: /var/lib/mysql + +volumes: + - name: cache + temp: {} + +trigger: + branch: + - master + - develop + - "*-rc" + event: + - pull_request + - push +--- +kind: pipeline +name: mariadb10.1-php7.1 + +steps: +- name: mariadb10.1-php7.1 + image: friendicaci/php7.1:php7.1.32 + commands: + - phpenmod xdebug + - sleep 20 + - ./autotest.sh mariadb + - wget https://codecov.io/bash -O codecov.sh + - sh -c "if [ '$DRONE_BUILD_EVENT' = 'pull_request' ]; then bash codecov.sh -B $DRONE_BRANCH -C $DRONE_COMMIT -P $DRONE_PULL_REQUEST -t 5ce7d64e-07b4-4adf-8700-e2eae27e14ec -f tests/autotest-clover.xml; fi" + - sh -c "if [ '$DRONE_BUILD_EVENT' != 'pull_request' ]; then bash codecov.sh -B $DRONE_BRANCH -C $DRONE_COMMIT -t 5ce7d64e-07b4-4adf-8700-e2eae27e14ec -f tests/autotest-clover.xml; fi" + environment: + MYSQL_USER: friendica + MYSQL_PASSWORD: friendica + MYSQL_DATABASE: friendica + MYSQL_HOST: mariadb + +services: +- name: mariadb + image: mariadb:10.1 + environment: + MYSQL_ROOT_PASSWORD: friendica + MYSQL_USER: friendica + MYSQL_PASSWORD: friendica + MYSQL_DATABASE: friendica + volumes: + - name: cache + path: /var/lib/mysql + +volumes: + - name: cache + temp: {} + +trigger: + branch: + - master + - develop + - "*-rc" + event: + - pull_request + - push +--- +kind: pipeline +name: mariadb10.1-php7.2 + +steps: +- name: mariadb10.1-php7.2 + image: friendicaci/php7.2:php7.2.22 + commands: + - NOCOVERAGE=true ./autotest.sh mariadb + environment: + MYSQL_USER: friendica + MYSQL_PASSWORD: friendica + MYSQL_DATABASE: friendica + MYSQL_HOST: mariadb + +services: +- name: mariadb + image: mariadb:10.1 + environment: + MYSQL_ROOT_PASSWORD: friendica + MYSQL_USER: friendica + MYSQL_PASSWORD: friendica + MYSQL_DATABASE: friendica + volumes: + - name: cache + path: /var/lib/mysql + +volumes: + - name: cache + temp: {} + +trigger: + branch: + - master + - develop + - "*-rc" + event: + - pull_request + - push +--- +kind: pipeline +name: mariadb10.1-php7.3 + +steps: +- name: mariadb10.1-php7.3 + image: friendicaci/php7.3:php7.3.9 + commands: + - NOCOVERAGE=true ./autotest.sh mariadb + environment: + MYSQL_USER: friendica + MYSQL_PASSWORD: friendica + MYSQL_DATABASE: friendica + MYSQL_HOST: mariadb + +services: +- name: mariadb + image: mariadb:10.1 + environment: + MYSQL_ROOT_PASSWORD: friendica + MYSQL_USER: friendica + MYSQL_PASSWORD: friendica + MYSQL_DATABASE: friendica + volumes: + - name: cache + path: /var/lib/mysql + +volumes: + - name: cache + temp: {} + +--- +kind: pipeline +name: redis-php7.1 + +steps: +- name: redis-php7.1 + image: friendicaci/php7.1:php7.1.32 + commands: + - phpenmod xdebug + - sleep 20 + - NOINSTALL=true TEST_SELECTION=REDIS ./autotest.sh mysql + - wget https://codecov.io/bash -O codecov.sh + - sh -c "if [ '$DRONE_BUILD_EVENT' = 'pull_request' ]; then bash codecov.sh -B $DRONE_BRANCH -C $DRONE_COMMIT -P $DRONE_PULL_REQUEST -t 5ce7d64e-07b4-4adf-8700-e2eae27e14ec -f tests/autotest-clover.xml; fi" + - sh -c "if [ '$DRONE_BUILD_EVENT' != 'pull_request' ]; then bash codecov.sh -B $DRONE_BRANCH -C $DRONE_COMMIT -t 5ce7d64e-07b4-4adf-8700-e2eae27e14ec -f tests/autotest-clover.xml; fi" + environment: + REDIS_HOST: redis + +services: +- name: redis + image: redis + +trigger: + branch: + - master + - develop + - "*-rc" + event: + - pull_request + - push +--- +kind: pipeline +name: redis-php7.2 + +steps: +- name: redis-php7.2 + image: friendicaci/php7.2:php7.2.22 + commands: + - NOCOVERAGE=true NOINSTALL=true TEST_SELECTION=REDIS ./autotest.sh mysql + environment: + REDIS_HOST: redis + +services: +- name: redis + image: redis + +trigger: + branch: + - master + - develop + - "*-rc" + event: + - pull_request + - push +--- +kind: pipeline +name: redis-php7.3 + +steps: +- name: redis-php7.3 + image: friendicaci/php7.3:php7.3.9 + commands: + - NOCOVERAGE=true NOINSTALL=true TEST_SELECTION=REDIS ./autotest.sh mysql + environment: + REDIS_HOST: redis + +services: +- name: redis + image: redis + +--- +kind: pipeline +name: memcache-php7.1 + +steps: +- name: memcache-php7.1 + image: friendicaci/php7.1:php7.1.32 + commands: + - phpenmod xdebug + - sleep 20 + - NOINSTALL=true TEST_SELECTION=MEMCACHE ./autotest.sh mysql + - wget https://codecov.io/bash -O codecov.sh + - sh -c "if [ '$DRONE_BUILD_EVENT' = 'pull_request' ]; then bash codecov.sh -B $DRONE_BRANCH -C $DRONE_COMMIT -P $DRONE_PULL_REQUEST -t 5ce7d64e-07b4-4adf-8700-e2eae27e14ec -f tests/autotest-clover.xml; fi" + - sh -c "if [ '$DRONE_BUILD_EVENT' != 'pull_request' ]; then bash codecov.sh -B $DRONE_BRANCH -C $DRONE_COMMIT -t 5ce7d64e-07b4-4adf-8700-e2eae27e14ec -f tests/autotest-clover.xml; fi" + environment: + MEMCACHE_HOST: memcached + +services: +- name: memcached + image: memcached + +trigger: + branch: + - master + - develop + - "*-rc" + event: + - pull_request + - push +--- +kind: pipeline +name: memcache-php7.2 + +steps: +- name: memcache-php7.2 + image: friendicaci/php7.2:php7.2.22 + commands: + - NOCOVERAGE=true NOINSTALL=true TEST_SELECTION=MEMCACHE ./autotest.sh mysql + environment: + MEMCACHE_HOST: memcached + +services: +- name: memcached + image: memcached + +trigger: + branch: + - master + - develop + - "*-rc" + event: + - pull_request + - push +--- +kind: pipeline +name: memcache-php7.3 + +steps: +- name: memcache-php7.3 + image: friendicaci/php7.3:php7.3.9 + commands: + - NOCOVERAGE=true NOINSTALL=true TEST_SELECTION=MEMCACHE ./autotest.sh mysql + environment: + MEMCACHE_HOST: memcached + +services: +- name: memcached + image: memcached + +--- +kind: pipeline +name: memcached-php7.1 + +steps: +- name: memcached-php7.1 + image: friendicaci/php7.1:php7.1.32 + commands: + - phpenmod xdebug + - sleep 20 + - NOINSTALL=true TEST_SELECTION=MEMCACHED ./autotest.sh mysql + - wget https://codecov.io/bash -O codecov.sh + - sh -c "if [ '$DRONE_BUILD_EVENT' = 'pull_request' ]; then bash codecov.sh -B $DRONE_BRANCH -C $DRONE_COMMIT -P $DRONE_PULL_REQUEST -t 5ce7d64e-07b4-4adf-8700-e2eae27e14ec -f tests/autotest-clover.xml; fi" + - sh -c "if [ '$DRONE_BUILD_EVENT' != 'pull_request' ]; then bash codecov.sh -B $DRONE_BRANCH -C $DRONE_COMMIT -t 5ce7d64e-07b4-4adf-8700-e2eae27e14ec -f tests/autotest-clover.xml; fi" + environment: + MEMCACHED_HOST: memcached + +services: +- name: memcached + image: memcached + +trigger: + branch: + - master + - develop + - "*-rc" + event: + - pull_request + - push +--- +kind: pipeline +name: memcached-php7.2 + +steps: +- name: memcached-php7.2 + image: friendicaci/php7.2:php7.2.22 + commands: + - NOCOVERAGE=true NOINSTALL=true TEST_SELECTION=MEMCACHED ./autotest.sh mysql + environment: + MEMCACHED_HOST: memcached + +services: +- name: memcached + image: memcached + +trigger: + branch: + - master + - develop + - "*-rc" + event: + - pull_request + - push +--- +kind: pipeline +name: memcached-php7.3 + +steps: +- name: memcached-php7.3 + image: friendicaci/php7.3:php7.3.9 + commands: + - NOCOVERAGE=true NOINSTALL=true TEST_SELECTION=MEMCACHED ./autotest.sh mysql + environment: + MEMCACHED_HOST: memcached + +services: +- name: memcached + image: memcached diff --git a/.travis.yml b/.travis.yml index e2aa84f5c..376748bcb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -26,4 +26,4 @@ before_script: - phpenv config-add .travis/redis.ini - phpenv config-add .travis/memcached.ini -after_success: bash <(curl -s https://codecov.io/bash) +script: vendor/bin/phpunit --configuration tests/phpunit.xml diff --git a/CHANGELOG b/CHANGELOG index 4046e6fb6..9f253c810 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,24 +1,44 @@ -Version 2019.09-dev (UNRELEASED) +Version 2019.12-dev (unreleased) Friendica Core: - Update to the translations (CS, DE, FR, JA, NL) [translation teams] + Enhanced the manage functionality [annando] + Fixed some problems with the remote auth functionality [annando] + Added router configuration file [nupplaphil] + Added drone.io as CI service [nupplaphil] + + Friendica Addons: + mailstream: + Support for new img format was added [mexon] + BB Code is now included as plaintext [mexon] + Logging format is enhanced [mexon] + ActivityPub "announce" notifications are not included [mexon] + + Closed Issues: + 1071, 7548, 7657, 7681 + +Version 2019.09 (2019-09-29) + Friendica Core: + Update to the translations (CS, DE, EN GB, EN US, FR, JA, NL, PL) [translation teams] Update to the themes (frio, vier) [JeroenED, MrPetovan, tobiasd, vinzv] - Update to the documentation [guzzisti, vinzv] + Update to the documentation [annando, tobiasd, guzzisti, vinzv] Enhanced the log output of the background process [annando] - Enhanced the vcard translation in the profile (JeroenED) + Enhanced the vcard translation in the profile [JeroenED] Enhanced the delivery count [annando] Enhanced ActivityPub envelopes [MrPetovan] + Enhanced communication about deleted accounts via AP [annando] Enhanced the following process [annando] Enhanced the tests [nupplaphil] - Enhanced the frontend worker [annando] + Enhanced the front-end worker [annando] Enhanced the img format to allow alternative texts [annando] Enhanced the detection of supported protocols for contacts [annando] - Enhanced the reshare of items [annando] + Enhanced the re-share of items [annando] Enhanced 2FA process [MrPetovan] Enhanced server wide theme settings [MrPetovan] Enhanced config loading process [MrPetovan, nupplaphil] Enhanced handling of emoticons [MrPetovan] - Fixed a bug in the admin panel leading to orphand options [tobiasd] + Enhanced performance [annando] + Fixed a bug in the admin panel leading to orphaned options [tobiasd] Fixed a problem that could lead to duplicated Pleroma contacts [annando] + Fixed a problem with the hide profile setting [annando] Fixed the problem sending out the post when hitting the enter key in the ACL dialog [MrPetovan] Fixed a bug in HTML special character escaping of event titles [MrPetovan] Fixed a bug in HTML special character conversion in item titles [MrPetovan] @@ -26,7 +46,19 @@ Version 2019.09-dev (UNRELEASED) Fixed a bug that prevented the display of images in some postings from diaspora* [MrPetovan] Fixed a bug in setting the permissions on uploaded images [annando] Fixed a bug that lead to potentially unwanted importing threads started by contacts of contacts [annando] + Fixed implicit self mentions [MrPetovan] Fixed display of register links on closed nodes landing pages [MrPetovan] + Fixed the display of [spoiler] tags [MrPetovan] + Fixed an issue with photo permissions in private mails [annando] + Fixed a bug in the process of following Pleroma accounts [annando] + Fixed a bug that caused notifications about locally deleted items [annando] + Fixed the link to the source of an event [MrPetovan] + Fixed a problem that caused authors from twitter postings having no profile pic [annando] + Fixed a bug in BBCode -> Markdown conversation for font size [annando] + Fixed a BBCode parser problem with the audio tag [MrPetovan] + Fixed a session problem [annando] + Fixed a problem with the auto-installer [nupplaphil] + Fixed a bug with magic links redirection for non profiles [annando] General code cleaning [annando, MrPetovan, nupplaphil] Removed contacts auto completion (in /contacts [MrPetovan] Replaced FontAwesome by ForkAwesome in frio theme [vinzv] @@ -37,19 +69,29 @@ Version 2019.09-dev (UNRELEASED) Added support of wildcards to server block lists [MrPetovan] Added app specific passwords when using 2FA [MrPetovan] Added fetching of postings via URL to interact with public postings [annando] + Added opt-out flag for federated search engines and associated header information for profiles [annando] Friendica Addons: - Update to the translation (CS, DE, FR, JA, NL SV) [translation teams] + Update to the translation (CS, DE, EN GB, EN US, ES, FR, JA, NL SV) [translation teams] General code cleanup [nupplaphil, Quix0r] + blockbot: + Added translations + Added more bots [annando] + Added admin panel settings [annando] + tumblr: + Changed used URLs to https adopting tumblrs change [annando] twitter: Enhanced handling of multi image postings [annando] + Enhanced display of quoted tweets [MrPetovan] Added alternative text support for images [annando] Closed Issues: - 3816, 4815, 6384, 6675, 7235, 7293, 7314, 7317, 7337, 7338, 7346, - 7350, 7367, 7383, 7396, 7397, 7401, 7406, 7408, 7426, 7428, 7456, - 7442, 7457, 7468, 7471, 7473, 7488, 7497, 7498, 7501, 7507, 7522, - 7527, 7536, 7542, 7545 + 870, 1605, 2199, 3239, 3816, 4117, 4815, 5721, 6384, 6521, 6553, + 6675, 7212, 7235, 7285, 7293, 7314, 7317, 7337, 7338, 7346, 7350, + 7367, 7383, 7396, 7397, 7401, 7406, 7408, 7426, 7428, 7456, 7442, + 7457, 7468, 7471, 7473, 7488, 7497, 7498, 7501, 7507, 7521, 7526, + 7527, 7536, 7542, 7545, 7576, 7586, 7594, 7597, 7603, 7610, 7618, + 7629, 7635, 7638, 7663, 7665, 7672 Version 2019.06 (2019-06-23) Friendica Core: diff --git a/VERSION b/VERSION index 3193eddcd..41367d09e 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2019.09-rc +2019.12-dev diff --git a/Vagrantfile b/Vagrantfile index e5f30b307..f9ffe0c75 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -6,8 +6,8 @@ server_timezone = "UTC" public_folder = "/vagrant" Vagrant.configure(2) do |config| - # Set server to Ubuntu 16.04 - config.vm.box = "ubuntu/xenial64" + # Set server to Debian 10 / Buster 64bit + config.vm.box = "debian/buster64" # Disable automatic box update checking. If you disable this, then # boxes will only be checked for updates when the user runs diff --git a/autotest.sh b/autotest.sh new file mode 100755 index 000000000..15067bf9d --- /dev/null +++ b/autotest.sh @@ -0,0 +1,279 @@ +#!/usr/bin/env bash +# +# This script is used for autotesting the Friendica codebase with different +# types of tests and environments. +# +# Currently, there are three types of autotesting possibilities: +# - "USEDOCKER=true ./autotest.sh" will start a database docker container for testing +# - "./autotest.sh" on the Drone CI environment will use the database container of the drone CI pipeline +# - "./autotest.sh" on a local environment will try to use the local database instance for testing +# +# You can specify a database (mysql, mariadb currently) for the db backend of Friendica ("./autotest.sh mysql") +# And you can specify some parameters for the test, like: +# - NOCOVERAGE=true ... Don't create a coverage XML (this is only useful if you will send coverage to codecov.io) +# - NOINSTALL=true ... Skip the whole Friendica installation process (e.g. you just test Caching drivers) +# - TEST_SELECTION= ... Specify which tests are used to run (based on the test-labeling) +# - XDEBUG_CONFIG= ... Set some XDEBUG specific environment settings for development + +DATABASENAME=${MYSQL_DATABASE:-test} +DATABASEUSER=${MYSQL_USERNAME:-friendica} +DATABASEHOST=${MYSQL_HOST:-localhost} +BASEDIR=$PWD + +DBCONFIGS="mysql mariadb" +TESTS="REDIS MEMCACHE MEMCACHED APCU NODB" + +export MYSQL_DATABASE="$DATABASENAME" +export MYSQL_USERNAME="$DATABASEUSER" +export MYSQL_PASSWORD="friendica" + +if [ -z "$PHP_EXE" ]; then + PHP_EXE=php +fi +PHP=$(which "$PHP_EXE") +# Use the Friendica internal composer +COMPOSER="$BASEDIR/bin/composer.phar" + +set -e + +_XDEBUG_CONFIG=$XDEBUG_CONFIG +unset XDEBUG_CONFIG + +function show_syntax() { + echo -e "Syntax: ./autotest.sh [dbconfigname] [testfile]\n" >&2 + echo -e "\t\"dbconfigname\" can be one of: $DBCONFIGS" >&2 + echo -e "\t\"testfile\" is the name of a test file, for example lib/template.php" >&2 + echo -e "\nDatabase environment variables:\n" >&2 + echo -e "\t\"MYSQL_HOST\" Mysql Hostname (Default: localhost)" >&2 + echo -e "\t\"MYSQL_USDRNAME\" Mysql Username (Default: friendica)" >&2 + echo -e "\t\"MYSQL_DATABASE\" Mysql Database (Default: test)" >&2 + echo -e "\nOther environment variables:\n" >&2 + echo -e "\t\"TEST_SELECTION\" test a specific group of tests, can be one of: $TESTS" >&2 + echo -e "\t\"NOINSTALL\" If set to true, skip the db and install process" >&2 + echo -e "\t\"NOCOVERAGE\" If set to true, don't create a coverage output" >&2 + echo -e "\t\"USEDOCKER\" If set to true, the DB server will be executed inside a docker container" >&2 + echo -e "\nExample: NOCOVERAGE=true ./autotest.sh mysql src/Core/Cache/MemcacheTest.php" >&2 + echo "will run the test suite from \"tests/src/Core/Cache/MemcacheTest.php\" without a Coverage" >&2 + echo -e "\nIf no arguments are specified, all tests will be run with all database configs" >&2 +} + +if [ -x "$PHP" ]; then + echo "Using PHP executable $PHP" +else + echo "Could not find PHP executable $PHP_EXE" >&2 + exit 3 +fi + +echo "Installing depdendencies" +$PHP "$COMPOSER" install + +PHPUNIT="$BASEDIR/vendor/bin/phpunit" + +if [ -x "$PHPUNIT" ]; then + echo "Using PHPUnit executable $PHPUNIT" +else + echo "Could not find PHPUnit executable after composer $PHPUNIT" >&2 + exit 3 +fi + +if ! [ \( -w config -a ! -f config/local.config.php \) -o \( -f config/local.config.php -a -w config/local.config.php \) ]; then + echo "Please enable write permissions on config and config/config.php" >&2 + exit 1 +fi + +if [ "$1" ]; then + FOUND=0 + for DBCONFIG in $DBCONFIGS; do + if [ "$1" = "$DBCONFIG" ]; then + FOUND=1 + break + fi + done + if [ $FOUND = 0 ]; then + echo -e "Unknown database config name \"$1\"\n" >&2 + show_syntax + exit 2 + fi +fi + +# Back up existing (dev) config if one exists and backup not already there +if [ -f config/local.config.php ] && [ ! -f config/local.config-autotest-backup.php ]; then + mv config/local.config.php config/local.config-autotest-backup.php +fi + +function cleanup_config() { + + if [ -n "$DOCKER_CONTAINER_ID" ]; then + echo "Kill the docker $DOCKER_CONTAINER_ID" + docker stop "$DOCKER_CONTAINER_ID" + docker rm -f "$DOCKER_CONTAINER_ID" + fi + + cd "$BASEDIR" + + # Restore existing config + if [ -f config/local.config-autotest-backup.php ]; then + mv config/local.config-autotest-backup.php config/local.config.php + fi +} + +# restore config on exit +trap cleanup_config EXIT + +function execute_tests() { + DB=$1 + echo "Setup environment for $DB testing ..." + # back to root folder + cd "$BASEDIR" + + # backup current config + if [ -f config/local.config.php ]; then + mv config/local.config.php config/local.config-autotest-backup.php + fi + + if [ -z "$NOINSTALL" ]; then + #drop database + if [ "$DB" == "mysql" ]; then + if [ -n "$USEDOCKER" ]; then + echo "Fire up the mysql docker" + DOCKER_CONTAINER_ID=$(docker run \ + -e MYSQL_ROOT_PASSWORD=friendica \ + -e MYSQL_USER="$DATABASEUSER" \ + -e MYSQL_PASSWORD=friendica \ + -e MYSQL_DATABASE="$DATABASENAME" \ + -d mysql) + DATABASEHOST=$(docker inspect --format="{{.NetworkSettings.IPAddress}}" "$DOCKER_CONTAINER_ID") + + else + if [ -z "$DRONE" ]; then # no need to drop the DB when we are on CI + if [ "mysql" != "$(mysql --version | grep -o mysql)" ]; then + echo "Your mysql binary is not provided by mysql" + echo "To use the docker container set the USEDOCKER environment variable" + exit 3 + fi + mysql -u "$DATABASEUSER" -pfriendica -e "DROP DATABASE IF EXISTS $DATABASENAME" -h $DATABASEHOST || true + mysql -u "$DATABASEUSER" -pfriendica -e "CREATE DATABASE $DATABASENAME DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci" -h $DATABASEHOST + else + DATABASEHOST=mysql + fi + fi + + echo "Waiting for MySQL $DATABASEHOST initialization..." + if ! bin/wait-for-connection $DATABASEHOST 3306 300; then + echo "[ERROR] Waited 300 seconds, no response" >&2 + exit 1 + fi + + echo "MySQL is up." + fi + if [ "$DB" == "mariadb" ]; then + if [ -n "$USEDOCKER" ]; then + echo "Fire up the mariadb docker" + DOCKER_CONTAINER_ID=$(docker run \ + -e MYSQL_ROOT_PASSWORD=friendica \ + -e MYSQL_USER="$DATABASEUSER" \ + -e MYSQL_PASSWORD=friendica \ + -e MYSQL_DATABASE="$DATABASENAME" \ + -d mariadb) + DATABASEHOST=$(docker inspect --format="{{.NetworkSettings.IPAddress}}" "$DOCKER_CONTAINER_ID") + + else + if [ -z "$DRONE" ]; then # no need to drop the DB when we are on CI + if [ "MariaDB" != "$(mysql --version | grep -o MariaDB)" ]; then + echo "Your mysql binary is not provided by mysql" + echo "To use the docker container set the USEDOCKER environment variable" + exit 3 + fi + mysql -u "$DATABASEUSER" -pfriendica -e "DROP DATABASE IF EXISTS $DATABASENAME" -h $DATABASEHOST || true + mysql -u "$DATABASEUSER" -pfriendica -e "CREATE DATABASE $DATABASENAME DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci" -h $DATABASEHOST + else + DATABASEHOST=mariadb + fi + fi + + echo "Waiting for MariaDB $DATABASEHOST initialization..." + if ! bin/wait-for-connection $DATABASEHOST 3306 300; then + echo "[ERROR] Waited 300 seconds, no response" >&2 + exit 1 + fi + + echo "MariaDB is up." + fi + + if [ -n "$USEDOCKER" ]; then + echo "Initialize database..." + docker exec $DOCKER_CONTAINER_ID mysql -u root -pfriendica -e 'CREATE DATABASE IF NOT EXISTS $DATABASENAME;' + fi + + export MYSQL_HOST="$DATABASEHOST" + + #call installer + echo "Installing Friendica..." + "$PHP" ./bin/console.php autoinstall --dbuser="$DATABASEUSER" --dbpass=friendica --dbdata="$DATABASENAME" --dbhost="$DATABASEHOST" --url=https://friendica.local --admin=admin@friendica.local + fi + + #test execution + echo "Testing..." + rm -fr "coverage-html" + mkdir "coverage-html" + if [[ "$_XDEBUG_CONFIG" ]]; then + export XDEBUG_CONFIG=$_XDEBUG_CONFIG + fi + + COVER='' + if [ -z "$NOCOVERAGE" ]; then + COVER="--coverage-clover tests/autotest-clover.xml" + else + echo "No coverage" + fi + + # per default, there is no cache installed + GROUP='--exclude-group REDIS,MEMCACHE,MEMCACHED,APCU' + if [ "$TEST_SELECTION" == "REDIS" ]; then + GROUP="--group REDIS" + fi + if [ "$TEST_SELECTION" == "MEMCACHE" ]; then + GROUP="--group MEMCACHE" + fi + if [ "$TEST_SELECTION" == "MEMCACHED" ]; then + GROUP="--group MEMCACHED" + fi + if [ "$TEST_SELECTION" == "APCU" ]; then + GROUP="--group APCU" + fi + if [ "$TEST_SELECTION" == "NODB" ]; then + GROUP="--exclude-group DB,SLOWDB" + fi + + INPUT="$BASEDIR/tests" + if [ -n "$2" ]; then + INPUT="$INPUT/$2" + fi + + echo "${PHPUNIT[@]}" --configuration tests/phpunit.xml $GROUP $COVER --log-junit "autotest-results.xml" "$INPUT" "$3" + "${PHPUNIT[@]}" --configuration tests/phpunit.xml $GROUP $COVER --log-junit "autotest-results.xml" "$INPUT" "$3" + RESULT=$? + + if [ -n "$DOCKER_CONTAINER_ID" ]; then + echo "Kill the docker $DOCKER_CONTAINER_ID" + docker stop $DOCKER_CONTAINER_ID + docker rm -f $DOCKER_CONTAINER_ID + unset $DOCKER_CONTAINER_ID + fi +} + +# +# Start the test execution +# +if [ -z "$1" ] && [ -n "$TEST_SELECTION" ]; then + # run all known database configs + for DBCONFIG in $DBCONFIGS; do + execute_tests "$DBCONFIG" + done +else + FILENAME="$2" + if [ -n "$2" ] && [ ! -f "tests/$FILENAME" ] && [ "${FILENAME:0:2}" != "--" ]; then + FILENAME="../$FILENAME" + fi + execute_tests "$1" "$FILENAME" "$3" +fi diff --git a/bin/auth_ejabberd.php b/bin/auth_ejabberd.php index 206e48447..5ccdd0174 100755 --- a/bin/auth_ejabberd.php +++ b/bin/auth_ejabberd.php @@ -36,6 +36,7 @@ use Dice\Dice; use Friendica\App\Mode; use Friendica\BaseObject; use Friendica\Util\ExAuth; +use Psr\Log\LoggerInterface; if (sizeof($_SERVER["argv"]) == 0) { die(); @@ -54,6 +55,8 @@ chdir($directory); require dirname(__DIR__) . '/vendor/autoload.php'; $dice = (new Dice())->addRules(include __DIR__ . '/../static/dependencies.config.php'); +$dice = $dice->addRule(LoggerInterface::class,['constructParams' => ['auth_ejabberd']]); + BaseObject::setDependencyInjection($dice); $appMode = $dice->create(Mode::class); diff --git a/bin/console.php b/bin/console.php index 4c396854d..a6513a2e8 100755 --- a/bin/console.php +++ b/bin/console.php @@ -2,9 +2,11 @@ addRules(include __DIR__ . '/../static/dependencies.config.php'); +$dice = $dice->addRule(LoggerInterface::class,['constructParams' => ['console']]); (new Friendica\Core\Console($dice, $argv))->execute(); diff --git a/bin/daemon.php b/bin/daemon.php index 8ea60fa9a..948829c1b 100755 --- a/bin/daemon.php +++ b/bin/daemon.php @@ -12,6 +12,7 @@ use Friendica\Core\Config; use Friendica\Core\Logger; use Friendica\Core\Worker; use Friendica\Database\DBA; +use Psr\Log\LoggerInterface; // Get options $shortopts = 'f'; @@ -33,6 +34,7 @@ if (!file_exists("boot.php") && (sizeof($_SERVER["argv"]) != 0)) { require dirname(__DIR__) . '/vendor/autoload.php'; $dice = (new Dice())->addRules(include __DIR__ . '/../static/dependencies.config.php'); +$dice = $dice->addRule(LoggerInterface::class,['constructParams' => ['daemon']]); \Friendica\BaseObject::setDependencyInjection($dice); $a = \Friendica\BaseObject::getApp(); diff --git a/bin/dev/vagrant_provision.sh b/bin/dev/vagrant_provision.sh index 7ebbdcafd..dc24f8799 100755 --- a/bin/dev/vagrant_provision.sh +++ b/bin/dev/vagrant_provision.sh @@ -37,9 +37,9 @@ sudo apt-get install -y apache2 sudo a2enmod rewrite actions ssl sudo cp /vagrant/bin/dev/vagrant_vhost.sh /usr/local/bin/vhost sudo chmod guo+x /usr/local/bin/vhost - sudo vhost -s 192.168.22.10.xip.io -d /var/www -p /etc/ssl/xip.io -c xip.io -a friendica.local - sudo a2dissite 000-default - sudo service apache2 restart +sudo vhost -s 192.168.22.10.xip.io -d /var/www -p /etc/ssl/xip.io -c xip.io -a friendica.local +sudo a2dissite 000-default +sudo service apache2 restart #Install php echo ">>> Installing PHP7" @@ -48,9 +48,9 @@ sudo systemctl restart apache2 #Install mysql echo ">>> Installing Mysql" -sudo debconf-set-selections <<< "mysql-server mysql-server/root_password password root" -sudo debconf-set-selections <<< "mysql-server mysql-server/root_password_again password root" -sudo apt-get install -qq mysql-server +sudo debconf-set-selections <<< "mariadb-server mariadb-server/root_password password root" +sudo debconf-set-selections <<< "mariadb-server mariadb-server/root_password_again password root" +sudo apt-get install -qq mariadb-server # enable remote access # setting the mysql bind-address to allow connections from everywhere sed -i "s/bind-address.*/bind-address = 0.0.0.0/" /etc/mysql/my.cnf @@ -76,6 +76,9 @@ debconf-set-selections <<< "postfix postfix/main_mailer_type string 'Local Only' sudo apt-get install -y postfix mailutils libmailutils-dev sudo echo -e "friendica1: vagrant\nfriendica2: vagrant\nfriendica3: vagrant\nfriendica4: vagrant\nfriendica5: vagrant" >> /etc/aliases && sudo newaliases +# Friendica needs git for fetching some dependencies +sudo apt-get install -y git + #make the vagrant directory the docroot sudo rm -rf /var/www/ sudo ln -fs /vagrant /var/www @@ -83,7 +86,7 @@ sudo ln -fs /vagrant /var/www # install deps with composer sudo apt install unzip cd /var/www -php bin/composer.phar install +sudo -u www-data php bin/composer.phar install # initial config file for friendica in vagrant cp /vagrant/mods/local.config.vagrant.php /vagrant/config/local.config.php diff --git a/bin/wait-for-connection b/bin/wait-for-connection new file mode 100755 index 000000000..eeb9ba980 --- /dev/null +++ b/bin/wait-for-connection @@ -0,0 +1,45 @@ +#!/usr/bin/php +# +# This script tries to connect to a database for a given interval +# Useful in case of installation e.g. to wait for the database to not generate unnecessary errors +# +# Usage: php bin/wait-for-connection {HOST} {PORT} [{TIMEOUT}] + + $timeout) { + $socketTimeout = $timeout; +} +$stopTime = time() + $timeout; +do { + $sock = @fsockopen($host, $port, $errno, $errstr, $socketTimeout); + if ($sock !== false) { + fclose($sock); + fwrite(STDOUT, "\n"); + exit(0); + } + sleep(1); + fwrite(STDOUT, '.'); +} while (time() < $stopTime); +fwrite(STDOUT, "\n"); +exit(1); diff --git a/bin/worker.php b/bin/worker.php index f6b2d90a5..469dcb001 100755 --- a/bin/worker.php +++ b/bin/worker.php @@ -11,6 +11,7 @@ use Friendica\BaseObject; use Friendica\Core\Config; use Friendica\Core\Update; use Friendica\Core\Worker; +use Psr\Log\LoggerInterface; // Get options $shortopts = 'sn'; @@ -32,6 +33,7 @@ if (!file_exists("boot.php") && (sizeof($_SERVER["argv"]) != 0)) { require dirname(__DIR__) . '/vendor/autoload.php'; $dice = (new Dice())->addRules(include __DIR__ . '/../static/dependencies.config.php'); +$dice = $dice->addRule(LoggerInterface::class,['constructParams' => ['worker']]); BaseObject::setDependencyInjection($dice); $a = BaseObject::getApp(); diff --git a/boot.php b/boot.php index 028ba1e82..3571a77c1 100644 --- a/boot.php +++ b/boot.php @@ -23,6 +23,7 @@ use Friendica\Core\Config; use Friendica\Core\PConfig; use Friendica\Core\Protocol; use Friendica\Core\System; +use Friendica\Core\Session; use Friendica\Database\DBA; use Friendica\Model\Contact; use Friendica\Model\Term; @@ -31,7 +32,7 @@ use Friendica\Util\DateTimeFormat; define('FRIENDICA_PLATFORM', 'Friendica'); define('FRIENDICA_CODENAME', 'Dalmatian Bellflower'); -define('FRIENDICA_VERSION', '2019.09-rc'); +define('FRIENDICA_VERSION', '2019.12-dev'); define('DFRN_PROTOCOL_VERSION', '2.23'); define('NEW_UPDATE_ROUTINE_VERSION', 1170); @@ -321,47 +322,6 @@ function get_app() return BaseObject::getApp(); } -/** - * Return the provided variable value if it exists and is truthy or the provided - * default value instead. - * - * Works with initialized variables and potentially uninitialized array keys - * - * Usages: - * - defaults($var, $default) - * - defaults($array, 'key', $default) - * - * @param array $args - * @brief Returns a defaut value if the provided variable or array key is falsy - * @return mixed - * @deprecated since version 2019.06, use native coalesce operator (??) instead - */ -function defaults(...$args) -{ - if (count($args) < 2) { - throw new BadFunctionCallException('defaults() requires at least 2 parameters'); - } - if (count($args) > 3) { - throw new BadFunctionCallException('defaults() cannot use more than 3 parameters'); - } - if (count($args) === 3 && is_null($args[1])) { - throw new BadFunctionCallException('defaults($arr, $key, $def) $key is null'); - } - - // The default value always is the last argument - $return = array_pop($args); - - if (count($args) == 2 && is_array($args[0]) && !empty($args[0][$args[1]])) { - $return = $args[0][$args[1]]; - } - - if (count($args) == 1 && !empty($args[0])) { - $return = $args[0]; - } - - return $return; -} - /** * @brief Used to end the current process, after saving session state. * @deprecated @@ -415,20 +375,14 @@ function public_contact() */ function remote_user() { - // You cannot be both local and remote. - // Unncommented by rabuzarus because remote authentication to local - // profiles wasn't possible anymore (2018-04-12). -// if (local_user()) { -// return false; -// } - - if (empty($_SESSION)) { + if (empty($_SESSION['authenticated'])) { return false; } - if (!empty($_SESSION['authenticated']) && !empty($_SESSION['visitor_id'])) { + if (!empty($_SESSION['visitor_id'])) { return intval($_SESSION['visitor_id']); } + return false; } @@ -532,7 +486,7 @@ function is_site_admin() $adminlist = explode(',', str_replace(' ', '', $admin_email)); - return local_user() && $admin_email && in_array(defaults($a->user, 'email', ''), $adminlist); + return local_user() && $admin_email && in_array($a->user['email'] ?? '', $adminlist); } function explode_querystring($query) diff --git a/composer.lock b/composer.lock index 73cc38f80..3aad3a154 100644 --- a/composer.lock +++ b/composer.lock @@ -87,16 +87,16 @@ }, { "name": "bower-asset/Chart-js", - "version": "v2.7.2", + "version": "v2.8.0", "source": { "type": "git", "url": "https://github.com/chartjs/Chart.js.git", - "reference": "98f104cdd03617f1300b417b3d60c23d4e3e3403" + "reference": "947d8a7ccfbfc76dd9d384ea75436fa4a7aeefb1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/chartjs/Chart.js/zipball/98f104cdd03617f1300b417b3d60c23d4e3e3403", - "reference": "98f104cdd03617f1300b417b3d60c23d4e3e3403", + "url": "https://api.github.com/repos/chartjs/Chart.js/zipball/947d8a7ccfbfc76dd9d384ea75436fa4a7aeefb1", + "reference": "947d8a7ccfbfc76dd9d384ea75436fa4a7aeefb1", "shasum": "" }, "type": "bower-asset-library", @@ -115,20 +115,20 @@ "MIT" ], "description": "Simple HTML5 charts using the canvas element.", - "time": "2018-03-01T21:45:21+00:00" + "time": "2019-03-14T13:03:00+00:00" }, { "name": "bower-asset/base64", - "version": "1.0.1", + "version": "1.0.2", "source": { "type": "git", "url": "https://github.com/davidchambers/Base64.js.git", - "reference": "b2d49f347ed1bce61000a82769bffc837b7c79dc" + "reference": "10f0e9990dab0a73009fc106ff2b88102a0a13cf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/davidchambers/Base64.js/zipball/b2d49f347ed1bce61000a82769bffc837b7c79dc", - "reference": "b2d49f347ed1bce61000a82769bffc837b7c79dc", + "url": "https://api.github.com/repos/davidchambers/Base64.js/zipball/10f0e9990dab0a73009fc106ff2b88102a0a13cf", + "reference": "10f0e9990dab0a73009fc106ff2b88102a0a13cf", "shasum": "" }, "type": "bower-asset-library", @@ -146,20 +146,20 @@ "WTFPL" ], "description": "Base64 encoding and decoding", - "time": "2017-03-25T21:16:21+00:00" + "time": "2019-02-12T17:19:36+00:00" }, { "name": "bower-asset/dompurify", - "version": "1.0.10", + "version": "1.0.11", "source": { "type": "git", "url": "https://github.com/cure53/DOMPurify.git", - "reference": "b537cab466329b1b077e0e5e3c14edad2b7142f7" + "reference": "3c1c0d7e11cda896b0c69cf82e0ca6e0c0e7dd38" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/cure53/DOMPurify/zipball/b537cab466329b1b077e0e5e3c14edad2b7142f7", - "reference": "b537cab466329b1b077e0e5e3c14edad2b7142f7", + "url": "https://api.github.com/repos/cure53/DOMPurify/zipball/3c1c0d7e11cda896b0c69cf82e0ca6e0c0e7dd38", + "reference": "3c1c0d7e11cda896b0c69cf82e0ca6e0c0e7dd38", "shasum": "" }, "type": "bower-asset-library", @@ -191,7 +191,7 @@ "svg", "xss" ], - "time": "2019-02-19T13:27:01+00:00" + "time": "2019-06-18T13:33:05+00:00" }, { "name": "bower-asset/fork-awesome", @@ -270,35 +270,37 @@ }, { "name": "bower-asset/vue", - "version": "v2.5.17", + "version": "v2.6.10", "source": { "type": "git", "url": "https://github.com/vuejs/vue.git", - "reference": "636c9b4ef17f2062720b677cbbe613f146f4d4db" + "reference": "e90cc60c4718a69e2c919275a999b7370141f3bf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vuejs/vue/zipball/636c9b4ef17f2062720b677cbbe613f146f4d4db", - "reference": "636c9b4ef17f2062720b677cbbe613f146f4d4db", + "url": "https://api.github.com/repos/vuejs/vue/zipball/e90cc60c4718a69e2c919275a999b7370141f3bf", + "reference": "e90cc60c4718a69e2c919275a999b7370141f3bf", "shasum": "" }, "type": "bower-asset-library" }, { "name": "divineomega/do-file-cache", - "version": "v2.0.2", + "version": "v2.0.6", "source": { "type": "git", "url": "https://github.com/DivineOmega/DO-File-Cache.git", - "reference": "261c6e30a0de8cd325f826d08b2e51b2e367a1a3" + "reference": "23696a8a4c3ebe2ab3d68a35b2698fa103f69334" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/DivineOmega/DO-File-Cache/zipball/261c6e30a0de8cd325f826d08b2e51b2e367a1a3", - "reference": "261c6e30a0de8cd325f826d08b2e51b2e367a1a3", + "url": "https://api.github.com/repos/DivineOmega/DO-File-Cache/zipball/23696a8a4c3ebe2ab3d68a35b2698fa103f69334", + "reference": "23696a8a4c3ebe2ab3d68a35b2698fa103f69334", "shasum": "" }, "require": { + "ext-json": "*", + "ext-zlib": "*", "php": ">=5.6" }, "require-dev": { @@ -329,7 +331,7 @@ "library", "php" ], - "time": "2018-09-12T23:08:34+00:00" + "time": "2018-12-31T09:36:51+00:00" }, { "name": "divineomega/do-file-cache-psr-6", @@ -375,28 +377,28 @@ }, { "name": "divineomega/password_exposed", - "version": "v2.5.3", + "version": "v2.8.0", "source": { "type": "git", "url": "https://github.com/DivineOmega/password_exposed.git", - "reference": "1f1b49e3ec55b0f07115d342b145091368b081c4" + "reference": "908ed8e62ef95411bd0f866e29c69cef2bbca880" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/DivineOmega/password_exposed/zipball/1f1b49e3ec55b0f07115d342b145091368b081c4", - "reference": "1f1b49e3ec55b0f07115d342b145091368b081c4", + "url": "https://api.github.com/repos/DivineOmega/password_exposed/zipball/908ed8e62ef95411bd0f866e29c69cef2bbca880", + "reference": "908ed8e62ef95411bd0f866e29c69cef2bbca880", "shasum": "" }, "require": { "divineomega/do-file-cache-psr-6": "^2.0", "guzzlehttp/guzzle": "^6.3", - "paragonie/certainty": "^1", + "paragonie/certainty": "^1|^2", "php": ">=5.6" }, "require-dev": { "fzaninotto/faker": "^1.7", "php-coveralls/php-coveralls": "^2.1", - "phpunit/phpunit": "^5.7", + "phpunit/phpunit": "^6.5", "vimeo/psalm": "^1" }, "type": "library", @@ -419,7 +421,7 @@ } ], "description": "This PHP package provides a `password_exposed` helper function, that uses the haveibeenpwned.com API to check if a password has been exposed in a data breach.", - "time": "2018-07-12T22:09:43+00:00" + "time": "2019-01-25T12:00:28+00:00" }, { "name": "ezyang/htmlpurifier", @@ -511,16 +513,16 @@ }, { "name": "fxp/composer-asset-plugin", - "version": "v1.4.4", + "version": "v1.4.6", "source": { "type": "git", "url": "https://github.com/fxpio/composer-asset-plugin.git", - "reference": "0d07328eef6e6f3753aa835fd2faef7fed1717bf" + "reference": "886ece037849d3935c5a34cdcd984e46f2de5fae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/fxpio/composer-asset-plugin/zipball/0d07328eef6e6f3753aa835fd2faef7fed1717bf", - "reference": "0d07328eef6e6f3753aa835fd2faef7fed1717bf", + "url": "https://api.github.com/repos/fxpio/composer-asset-plugin/zipball/886ece037849d3935c5a34cdcd984e46f2de5fae", + "reference": "886ece037849d3935c5a34cdcd984e46f2de5fae", "shasum": "" }, "require": { @@ -566,7 +568,7 @@ "npm", "package" ], - "time": "2018-07-02T11:37:17+00:00" + "time": "2019-08-08T18:36:07+00:00" }, { "name": "guzzlehttp/guzzle", @@ -686,32 +688,37 @@ }, { "name": "guzzlehttp/psr7", - "version": "1.4.2", + "version": "1.6.1", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c" + "reference": "239400de7a173fe9901b9ac7c06497751f00727a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/f5b8a8512e2b58b0071a7280e39f14f72e05d87c", - "reference": "f5b8a8512e2b58b0071a7280e39f14f72e05d87c", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/239400de7a173fe9901b9ac7c06497751f00727a", + "reference": "239400de7a173fe9901b9ac7c06497751f00727a", "shasum": "" }, "require": { "php": ">=5.4.0", - "psr/http-message": "~1.0" + "psr/http-message": "~1.0", + "ralouphie/getallheaders": "^2.0.5 || ^3.0.0" }, "provide": { "psr/http-message-implementation": "1.0" }, "require-dev": { - "phpunit/phpunit": "~4.0" + "ext-zlib": "*", + "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.8" + }, + "suggest": { + "zendframework/zend-httphandlerrunner": "Emit PSR-7 responses" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.4-dev" + "dev-master": "1.6-dev" } }, "autoload": { @@ -741,26 +748,27 @@ "keywords": [ "http", "message", + "psr-7", "request", "response", "stream", "uri", "url" ], - "time": "2017-03-20T17:10:46+00:00" + "time": "2019-07-01T23:21:34+00:00" }, { "name": "league/html-to-markdown", - "version": "4.8.0", + "version": "4.8.2", "source": { "type": "git", "url": "https://github.com/thephpleague/html-to-markdown.git", - "reference": "f9a879a068c68ff47b722de63f58bec79e448f9d" + "reference": "e747489191f8e9144a7270eb61f8b9516e99e413" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/html-to-markdown/zipball/f9a879a068c68ff47b722de63f58bec79e448f9d", - "reference": "f9a879a068c68ff47b722de63f58bec79e448f9d", + "url": "https://api.github.com/repos/thephpleague/html-to-markdown/zipball/e747489191f8e9144a7270eb61f8b9516e99e413", + "reference": "e747489191f8e9144a7270eb61f8b9516e99e413", "shasum": "" }, "require": { @@ -792,17 +800,17 @@ "MIT" ], "authors": [ - { - "name": "Nick Cernis", - "email": "nick@cern.is", - "homepage": "http://modernnerd.net", - "role": "Original Author" - }, { "name": "Colin O'Dell", "email": "colinodell@gmail.com", "homepage": "https://www.colinodell.com", "role": "Lead Developer" + }, + { + "name": "Nick Cernis", + "email": "nick@cern.is", + "homepage": "http://modernnerd.net", + "role": "Original Author" } ], "description": "An HTML-to-markdown conversion helper for PHP", @@ -811,7 +819,7 @@ "html", "markdown" ], - "time": "2018-09-18T12:18:08+00:00" + "time": "2019-08-02T11:57:39+00:00" }, { "name": "level-2/dice", @@ -940,16 +948,16 @@ }, { "name": "mobiledetect/mobiledetectlib", - "version": "2.8.33", + "version": "2.8.34", "source": { "type": "git", "url": "https://github.com/serbanghita/Mobile-Detect.git", - "reference": "cd385290f9a0d609d2eddd165a1e44ec1bf12102" + "reference": "6f8113f57a508494ca36acbcfa2dc2d923c7ed5b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/serbanghita/Mobile-Detect/zipball/cd385290f9a0d609d2eddd165a1e44ec1bf12102", - "reference": "cd385290f9a0d609d2eddd165a1e44ec1bf12102", + "url": "https://api.github.com/repos/serbanghita/Mobile-Detect/zipball/6f8113f57a508494ca36acbcfa2dc2d923c7ed5b", + "reference": "6f8113f57a508494ca36acbcfa2dc2d923c7ed5b", "shasum": "" }, "require": { @@ -988,20 +996,20 @@ "mobile detector", "php mobile detect" ], - "time": "2018-09-01T15:05:15+00:00" + "time": "2019-09-18T18:44:20+00:00" }, { "name": "monolog/monolog", - "version": "1.24.0", + "version": "1.25.1", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "bfc9ebb28f97e7a24c45bdc3f0ff482e47bb0266" + "reference": "70e65a5470a42cfec1a7da00d30edb6e617e8dcf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/bfc9ebb28f97e7a24c45bdc3f0ff482e47bb0266", - "reference": "bfc9ebb28f97e7a24c45bdc3f0ff482e47bb0266", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/70e65a5470a42cfec1a7da00d30edb6e617e8dcf", + "reference": "70e65a5470a42cfec1a7da00d30edb6e617e8dcf", "shasum": "" }, "require": { @@ -1066,7 +1074,7 @@ "logging", "psr-3" ], - "time": "2018-11-05T09:00:11+00:00" + "time": "2019-09-06T13:49:17+00:00" }, { "name": "nikic/fast-route", @@ -1120,7 +1128,6 @@ "dist": { "type": "tar", "url": "https://registry.npmjs.org/cropperjs/-/cropperjs-1.2.2.tgz", - "reference": null, "shasum": "30dc7a7ce872155b23a33bd10ad4c76c0d613f55" }, "require-dev": { @@ -1214,7 +1221,6 @@ "dist": { "type": "tar", "url": "https://registry.npmjs.org/ev-emitter/-/ev-emitter-1.1.1.tgz", - "reference": null, "shasum": "8f18b0ce5c76a5d18017f71c0a795c65b9138f2a" }, "type": "npm-asset-library", @@ -1253,12 +1259,11 @@ }, { "name": "npm-asset/fullcalendar", - "version": "3.10.0", + "version": "3.10.1", "dist": { "type": "tar", - "url": "https://registry.npmjs.org/fullcalendar/-/fullcalendar-3.10.0.tgz", - "reference": null, - "shasum": "cc5e87d518fd6550e142816a31dd191664847919" + "url": "https://registry.npmjs.org/fullcalendar/-/fullcalendar-3.10.1.tgz", + "shasum": "cca3f9a2656a7e978a3f3facb7f35934a91185db" }, "type": "npm-asset-library", "extra": { @@ -1296,7 +1301,7 @@ "full-sized", "jquery-plugin" ], - "time": "2019-01-11T02:39:12+00:00" + "time": "2019-08-10T16:05:46+00:00" }, { "name": "npm-asset/imagesloaded", @@ -1304,12 +1309,27 @@ "dist": { "type": "tar", "url": "https://registry.npmjs.org/imagesloaded/-/imagesloaded-4.1.4.tgz", - "reference": null, "shasum": "1376efcd162bb768c34c3727ac89cc04051f3cc7" }, "require": { "npm-asset/ev-emitter": ">=1.0.0,<2.0.0" }, + "require-dev": { + "npm-asset/chalk": ">=1.1.1,<2.0.0", + "npm-asset/cheerio": ">=0.19.0,<0.20.0", + "npm-asset/gulp": ">=3.9.0,<4.0.0", + "npm-asset/gulp-jshint": ">=1.11.2,<2.0.0", + "npm-asset/gulp-json-lint": ">=0.1.0,<0.2.0", + "npm-asset/gulp-rename": ">=1.2.2,<2.0.0", + "npm-asset/gulp-replace": ">=0.5.4,<0.6.0", + "npm-asset/gulp-requirejs-optimize": "dev-github:metafizzy/gulp-requirejs-optimize", + "npm-asset/gulp-uglify": ">=1.4.2,<2.0.0", + "npm-asset/gulp-util": ">=3.0.7,<4.0.0", + "npm-asset/highlight.js": ">=8.9.1,<9.0.0", + "npm-asset/marked": ">=0.3.5,<0.4.0", + "npm-asset/minimist": ">=1.2.0,<2.0.0", + "npm-asset/transfob": ">=1.0.0,<2.0.0" + }, "type": "npm-asset-library", "extra": { "npm-asset-bugs": { @@ -1352,9 +1372,16 @@ "dist": { "type": "tar", "url": "https://registry.npmjs.org/jgrowl/-/jgrowl-1.4.6.tgz", - "reference": null, "shasum": "2736e332aaee73ccf0a14a5f0066391a0a13f4a3" }, + "require-dev": { + "npm-asset/grunt": "~0.4.2", + "npm-asset/grunt-contrib-cssmin": "~0.9.0", + "npm-asset/grunt-contrib-jshint": "~0.6.3", + "npm-asset/grunt-contrib-less": "~0.11.0", + "npm-asset/grunt-contrib-uglify": "~0.4.0", + "npm-asset/grunt-contrib-watch": "~0.6.1" + }, "type": "npm-asset-library", "extra": { "npm-asset-bugs": { @@ -1385,9 +1412,34 @@ "dist": { "type": "tar", "url": "https://registry.npmjs.org/jquery/-/jquery-2.2.4.tgz", - "reference": null, "shasum": "2c89d6889b5eac522a7eea32c14521559c6cbf02" }, + "require-dev": { + "npm-asset/commitplease": "2.0.0", + "npm-asset/core-js": "0.9.17", + "npm-asset/grunt": "0.4.5", + "npm-asset/grunt-babel": "5.0.1", + "npm-asset/grunt-cli": "0.1.13", + "npm-asset/grunt-compare-size": "0.4.0", + "npm-asset/grunt-contrib-jshint": "0.11.2", + "npm-asset/grunt-contrib-uglify": "0.9.2", + "npm-asset/grunt-contrib-watch": "0.6.1", + "npm-asset/grunt-git-authors": "2.0.1", + "npm-asset/grunt-jscs": "2.1.0", + "npm-asset/grunt-jsonlint": "1.0.4", + "npm-asset/grunt-npmcopy": "0.1.0", + "npm-asset/gzip-js": "0.3.2", + "npm-asset/jsdom": "5.6.1", + "npm-asset/load-grunt-tasks": "1.0.0", + "npm-asset/qunit-assert-step": "1.0.3", + "npm-asset/qunitjs": "1.17.1", + "npm-asset/requirejs": "2.1.17", + "npm-asset/sinon": "1.10.3", + "npm-asset/sizzle": "2.2.1", + "npm-asset/strip-json-comments": "1.0.3", + "npm-asset/testswarm": "1.1.0", + "npm-asset/win-spawn": "2.0.0" + }, "type": "npm-asset-library", "extra": { "npm-asset-bugs": { @@ -1430,7 +1482,6 @@ "dist": { "type": "tar", "url": "https://registry.npmjs.org/jquery-colorbox/-/jquery-colorbox-1.6.4.tgz", - "reference": null, "shasum": "799452523a6c494839224ef702e807deb9c06cc5" }, "require": { @@ -1473,12 +1524,11 @@ }, { "name": "npm-asset/jquery-datetimepicker", - "version": "2.5.20", + "version": "2.5.21", "dist": { "type": "tar", - "url": "https://registry.npmjs.org/jquery-datetimepicker/-/jquery-datetimepicker-2.5.20.tgz", - "reference": null, - "shasum": "687d6204b90b03dc93f725f8df036e1d061f37ac" + "url": "https://registry.npmjs.org/jquery-datetimepicker/-/jquery-datetimepicker-2.5.21.tgz", + "shasum": "00c388a78df2732fedfdb5c6529b6e84d53e0235" }, "require": { "npm-asset/jquery": ">=1.7.2", @@ -1527,7 +1577,7 @@ "time", "timepicker" ], - "time": "2018-03-21T16:26:39+00:00" + "time": "2019-02-23T11:25:30+00:00" }, { "name": "npm-asset/jquery-mousewheel", @@ -1535,9 +1585,14 @@ "dist": { "type": "tar", "url": "https://registry.npmjs.org/jquery-mousewheel/-/jquery-mousewheel-3.1.13.tgz", - "reference": null, "shasum": "06f0335f16e353a695e7206bf50503cb523a6ee5" }, + "require-dev": { + "npm-asset/grunt": "~0.4.1", + "npm-asset/grunt-contrib-connect": "~0.5.0", + "npm-asset/grunt-contrib-jshint": "~0.7.1", + "npm-asset/grunt-contrib-uglify": "~0.2.7" + }, "type": "npm-asset-library", "extra": { "npm-asset-bugs": { @@ -1580,12 +1635,11 @@ }, { "name": "npm-asset/moment", - "version": "2.22.2", + "version": "2.24.0", "dist": { "type": "tar", - "url": "https://registry.npmjs.org/moment/-/moment-2.22.2.tgz", - "reference": null, - "shasum": "3c257f9839fc0e93ff53149632239eb90783ff66" + "url": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", + "shasum": "0d055d53f5052aa653c9f6eb68bb5d12bf5c2b5b" }, "type": "npm-asset-library", "extra": { @@ -1655,7 +1709,7 @@ "time", "validate" ], - "time": "2018-06-01T06:58:41+00:00" + "time": "2019-01-21T21:10:34+00:00" }, { "name": "npm-asset/php-date-formatter", @@ -1701,7 +1755,6 @@ "dist": { "type": "tar", "url": "https://registry.npmjs.org/typeahead.js/-/typeahead.js-0.11.1.tgz", - "reference": null, "shasum": "4e64e671b22310a8606f4aec805924ba84b015b8" }, "require": { @@ -1750,27 +1803,29 @@ }, { "name": "paragonie/certainty", - "version": "v1.0.4", + "version": "v2.5.0", "source": { "type": "git", "url": "https://github.com/paragonie/certainty.git", - "reference": "d0f22c0fe579cf0e4f8ee301de5bc97ab124faac" + "reference": "cc39b91595e577fdff6128d7ce787892bd117274" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/certainty/zipball/d0f22c0fe579cf0e4f8ee301de5bc97ab124faac", - "reference": "d0f22c0fe579cf0e4f8ee301de5bc97ab124faac", + "url": "https://api.github.com/repos/paragonie/certainty/zipball/cc39b91595e577fdff6128d7ce787892bd117274", + "reference": "cc39b91595e577fdff6128d7ce787892bd117274", "shasum": "" }, "require": { + "ext-curl": "*", + "ext-json": "*", "guzzlehttp/guzzle": "^6", "paragonie/constant_time_encoding": "^1|^2", - "paragonie/sodium_compat": "^1.6", - "php": "^5.6|^7" + "paragonie/sodium_compat": "^1.11", + "php": "^5.5|^7" }, "require-dev": { - "phpunit/phpunit": "^5|^6", - "vimeo/psalm": "^1" + "composer/composer": "^1", + "phpunit/phpunit": "^4|^5|^6" }, "bin": [ "bin/certainty-cert-symlink" @@ -1806,7 +1861,7 @@ "ssl", "tls" ], - "time": "2018-04-09T07:21:55+00:00" + "time": "2019-09-27T22:26:33+00:00" }, { "name": "paragonie/constant_time_encoding", @@ -1966,21 +2021,21 @@ }, { "name": "paragonie/sodium_compat", - "version": "v1.7.0", + "version": "v1.11.1", "source": { "type": "git", "url": "https://github.com/paragonie/sodium_compat.git", - "reference": "7b73005be3c224f12c47bd75a23ce24b762e47e8" + "reference": "a9f968bc99485f85f9303a8524c3485a7e87bc15" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/sodium_compat/zipball/7b73005be3c224f12c47bd75a23ce24b762e47e8", - "reference": "7b73005be3c224f12c47bd75a23ce24b762e47e8", + "url": "https://api.github.com/repos/paragonie/sodium_compat/zipball/a9f968bc99485f85f9303a8524c3485a7e87bc15", + "reference": "a9f968bc99485f85f9303a8524c3485a7e87bc15", "shasum": "" }, "require": { "paragonie/random_compat": ">=1", - "php": "^5.2.4|^5.3|^5.4|^5.5|^5.6|^7" + "php": "^5.2.4|^5.3|^5.4|^5.5|^5.6|^7|^8" }, "require-dev": { "phpunit/phpunit": "^3|^4|^5" @@ -2044,7 +2099,7 @@ "secret-key cryptography", "side-channel resistant" ], - "time": "2018-09-22T03:59:58+00:00" + "time": "2019-09-12T12:05:58+00:00" }, { "name": "pear/console_table", @@ -2520,6 +2575,46 @@ ], "time": "2018-11-20T15:27:04+00:00" }, + { + "name": "ralouphie/getallheaders", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/getallheaders.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "time": "2019-03-08T08:55:37+00:00" + }, { "name": "seld/cli-prompt", "version": "1.0.3", @@ -2623,16 +2718,16 @@ }, { "name": "symfony/polyfill-php56", - "version": "v1.11.0", + "version": "v1.12.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php56.git", - "reference": "f4dddbc5c3471e1b700a147a20ae17cdb72dbe42" + "reference": "0e3b212e96a51338639d8ce175c046d7729c3403" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php56/zipball/f4dddbc5c3471e1b700a147a20ae17cdb72dbe42", - "reference": "f4dddbc5c3471e1b700a147a20ae17cdb72dbe42", + "url": "https://api.github.com/repos/symfony/polyfill-php56/zipball/0e3b212e96a51338639d8ce175c046d7729c3403", + "reference": "0e3b212e96a51338639d8ce175c046d7729c3403", "shasum": "" }, "require": { @@ -2642,7 +2737,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.11-dev" + "dev-master": "1.12-dev" } }, "autoload": { @@ -2675,20 +2770,20 @@ "portable", "shim" ], - "time": "2019-02-06T07:57:58+00:00" + "time": "2019-08-06T08:03:45+00:00" }, { "name": "symfony/polyfill-util", - "version": "v1.11.0", + "version": "v1.12.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-util.git", - "reference": "b46c6cae28a3106735323f00a0c38eccf2328897" + "reference": "4317de1386717b4c22caed7725350a8887ab205c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-util/zipball/b46c6cae28a3106735323f00a0c38eccf2328897", - "reference": "b46c6cae28a3106735323f00a0c38eccf2328897", + "url": "https://api.github.com/repos/symfony/polyfill-util/zipball/4317de1386717b4c22caed7725350a8887ab205c", + "reference": "4317de1386717b4c22caed7725350a8887ab205c", "shasum": "" }, "require": { @@ -2697,7 +2792,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.11-dev" + "dev-master": "1.12-dev" } }, "autoload": { @@ -2727,80 +2822,40 @@ "polyfill", "shim" ], - "time": "2019-02-08T14:16:39+00:00" + "time": "2019-08-06T08:03:45+00:00" } ], "packages-dev": [ - { - "name": "dasprid/enum", - "version": "1.0.0", - "source": { - "type": "git", - "url": "https://github.com/DASPRiD/Enum.git", - "reference": "631ef6e638e9494b0310837fa531bedd908fc22b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/DASPRiD/Enum/zipball/631ef6e638e9494b0310837fa531bedd908fc22b", - "reference": "631ef6e638e9494b0310837fa531bedd908fc22b", - "shasum": "" - }, - "require-dev": { - "phpunit/phpunit": "^6.4", - "squizlabs/php_codesniffer": "^3.1" - }, - "type": "library", - "autoload": { - "psr-4": { - "DASPRiD\\Enum\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-2-Clause" - ], - "authors": [ - { - "name": "Ben Scholzen 'DASPRiD'", - "email": "mail@dasprids.de", - "homepage": "https://dasprids.de/" - } - ], - "description": "PHP 7.1 enum implementation", - "keywords": [ - "enum", - "map" - ], - "time": "2017-10-25T22:45:27+00:00" - }, { "name": "doctrine/instantiator", - "version": "1.0.5", + "version": "1.2.0", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d" + "reference": "a2c590166b2133a4633738648b6b064edae0814a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d", - "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/a2c590166b2133a4633738648b6b064edae0814a", + "reference": "a2c590166b2133a4633738648b6b064edae0814a", "shasum": "" }, "require": { - "php": ">=5.3,<8.0-DEV" + "php": "^7.1" }, "require-dev": { - "athletic/athletic": "~0.1.8", + "doctrine/coding-standard": "^6.0", "ext-pdo": "*", "ext-phar": "*", - "phpunit/phpunit": "~4.0", - "squizlabs/php_codesniffer": "~2.0" + "phpbench/phpbench": "^0.13", + "phpstan/phpstan-phpunit": "^0.11", + "phpstan/phpstan-shim": "^0.11", + "phpunit/phpunit": "^7.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "1.2.x-dev" } }, "autoload": { @@ -2820,12 +2875,12 @@ } ], "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", - "homepage": "https://github.com/doctrine/instantiator", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", "keywords": [ "constructor", "instantiate" ], - "time": "2015-06-14T21:17:01+00:00" + "time": "2019-03-17T17:37:11+00:00" }, { "name": "hamcrest/hamcrest-php", @@ -2925,23 +2980,23 @@ }, { "name": "mikey179/vfsstream", - "version": "v1.6.5", + "version": "v1.6.7", "source": { "type": "git", "url": "https://github.com/bovigo/vfsStream.git", - "reference": "d5fec95f541d4d71c4823bb5e30cf9b9e5b96145" + "reference": "2b544ac3a21bcc4dde5d90c4ae8d06f4319055fb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bovigo/vfsStream/zipball/d5fec95f541d4d71c4823bb5e30cf9b9e5b96145", - "reference": "d5fec95f541d4d71c4823bb5e30cf9b9e5b96145", + "url": "https://api.github.com/repos/bovigo/vfsStream/zipball/2b544ac3a21bcc4dde5d90c4ae8d06f4319055fb", + "reference": "2b544ac3a21bcc4dde5d90c4ae8d06f4319055fb", "shasum": "" }, "require": { "php": ">=5.3.0" }, "require-dev": { - "phpunit/phpunit": "~4.5" + "phpunit/phpunit": "^4.5|^5.0" }, "type": "library", "extra": { @@ -2961,26 +3016,26 @@ "authors": [ { "name": "Frank Kleine", - "role": "Developer", - "homepage": "http://frankkleine.de/" + "homepage": "http://frankkleine.de/", + "role": "Developer" } ], "description": "Virtual file system to mock the real file system in unit tests.", "homepage": "http://vfs.bovigo.org/", - "time": "2017-08-01T08:02:14+00:00" + "time": "2019-08-01T01:38:37+00:00" }, { "name": "mockery/mockery", - "version": "1.2.0", + "version": "1.2.3", "source": { "type": "git", "url": "https://github.com/mockery/mockery.git", - "reference": "100633629bf76d57430b86b7098cd6beb996a35a" + "reference": "4eff936d83eb809bde2c57a3cea0ee9643769031" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/mockery/mockery/zipball/100633629bf76d57430b86b7098cd6beb996a35a", - "reference": "100633629bf76d57430b86b7098cd6beb996a35a", + "url": "https://api.github.com/repos/mockery/mockery/zipball/4eff936d83eb809bde2c57a3cea0ee9643769031", + "reference": "4eff936d83eb809bde2c57a3cea0ee9643769031", "shasum": "" }, "require": { @@ -2989,7 +3044,7 @@ "php": ">=5.6.0" }, "require-dev": { - "phpunit/phpunit": "~5.7.10|~6.5|~7.0" + "phpunit/phpunit": "~5.7.10|~6.5|~7.0|~8.0" }, "type": "library", "extra": { @@ -3032,29 +3087,32 @@ "test double", "testing" ], - "time": "2018-10-02T21:52:37+00:00" + "time": "2019-08-07T15:01:07+00:00" }, { "name": "myclabs/deep-copy", - "version": "1.7.0", + "version": "1.9.3", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e" + "reference": "007c053ae6f31bba39dfa19a7726f56e9763bbea" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e", - "reference": "3b8a3a99ba1f6a3952ac2747d989303cbd6b7a3e", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/007c053ae6f31bba39dfa19a7726f56e9763bbea", + "reference": "007c053ae6f31bba39dfa19a7726f56e9763bbea", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0" + "php": "^7.1" + }, + "replace": { + "myclabs/deep-copy": "self.version" }, "require-dev": { "doctrine/collections": "^1.0", "doctrine/common": "^2.6", - "phpunit/phpunit": "^4.1" + "phpunit/phpunit": "^7.1" }, "type": "library", "autoload": { @@ -3077,7 +3135,7 @@ "object", "object graph" ], - "time": "2017-10-19T19:58:43+00:00" + "time": "2019-08-09T12:45:53+00:00" }, { "name": "phpdocumentor/reflection-common", @@ -3227,16 +3285,16 @@ }, { "name": "phpspec/prophecy", - "version": "1.8.0", + "version": "1.8.1", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06" + "reference": "1927e75f4ed19131ec9bcc3b002e07fb1173ee76" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/4ba436b55987b4bf311cb7c6ba82aa528aac0a06", - "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/1927e75f4ed19131ec9bcc3b002e07fb1173ee76", + "reference": "1927e75f4ed19131ec9bcc3b002e07fb1173ee76", "shasum": "" }, "require": { @@ -3257,8 +3315,8 @@ } }, "autoload": { - "psr-0": { - "Prophecy\\": "src/" + "psr-4": { + "Prophecy\\": "src/Prophecy" } }, "notification-url": "https://packagist.org/downloads/", @@ -3286,63 +3344,7 @@ "spy", "stub" ], - "time": "2018-08-05T17:53:17+00:00" - }, - { - "name": "phpunit/dbunit", - "version": "2.0.3", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/dbunit.git", - "reference": "5c35d74549c21ba55d0ea74ba89d191a51f8cf25" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/dbunit/zipball/5c35d74549c21ba55d0ea74ba89d191a51f8cf25", - "reference": "5c35d74549c21ba55d0ea74ba89d191a51f8cf25", - "shasum": "" - }, - "require": { - "ext-pdo": "*", - "ext-simplexml": "*", - "php": "^5.4 || ^7.0", - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0", - "symfony/yaml": "^2.1 || ^3.0" - }, - "bin": [ - "dbunit" - ], - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sb@sebastian-bergmann.de", - "role": "lead" - } - ], - "description": "DbUnit port for PHP/PHPUnit to support database interaction testing.", - "homepage": "https://github.com/sebastianbergmann/dbunit/", - "keywords": [ - "database", - "testing", - "xunit" - ], - "abandoned": true, - "time": "2016-12-02T14:39:14+00:00" + "time": "2019-06-13T12:50:23+00:00" }, { "name": "phpunit/php-code-coverage", @@ -3394,8 +3396,8 @@ "authors": [ { "name": "Sebastian Bergmann", - "role": "lead", - "email": "sb@sebastian-bergmann.de" + "email": "sb@sebastian-bergmann.de", + "role": "lead" } ], "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", @@ -3662,8 +3664,8 @@ "authors": [ { "name": "Sebastian Bergmann", - "role": "lead", - "email": "sebastian@phpunit.de" + "email": "sebastian@phpunit.de", + "role": "lead" } ], "description": "The PHP Unit Testing framework.", @@ -4250,16 +4252,16 @@ }, { "name": "symfony/polyfill-ctype", - "version": "v1.9.0", + "version": "v1.12.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "e3d826245268269cd66f8326bd8bc066687b4a19" + "reference": "550ebaac289296ce228a706d0867afc34687e3f4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/e3d826245268269cd66f8326bd8bc066687b4a19", - "reference": "e3d826245268269cd66f8326bd8bc066687b4a19", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/550ebaac289296ce228a706d0867afc34687e3f4", + "reference": "550ebaac289296ce228a706d0867afc34687e3f4", "shasum": "" }, "require": { @@ -4271,7 +4273,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.9-dev" + "dev-master": "1.12-dev" } }, "autoload": { @@ -4287,13 +4289,13 @@ "MIT" ], "authors": [ - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - }, { "name": "Gert de Pagter", "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], "description": "Symfony polyfill for ctype functions", @@ -4304,24 +4306,24 @@ "polyfill", "portable" ], - "time": "2018-08-06T14:22:27+00:00" + "time": "2019-08-06T08:03:45+00:00" }, { "name": "symfony/yaml", - "version": "v3.4.16", + "version": "v4.3.4", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "61973ecda60e9f3561e929e19c07d4878b960fc1" + "reference": "5a0b7c32dc3ec56fd4abae8a4a71b0cf05013686" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/61973ecda60e9f3561e929e19c07d4878b960fc1", - "reference": "61973ecda60e9f3561e929e19c07d4878b960fc1", + "url": "https://api.github.com/repos/symfony/yaml/zipball/5a0b7c32dc3ec56fd4abae8a4a71b0cf05013686", + "reference": "5a0b7c32dc3ec56fd4abae8a4a71b0cf05013686", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8", + "php": "^7.1.3", "symfony/polyfill-ctype": "~1.8" }, "conflict": { @@ -4336,7 +4338,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.4-dev" + "dev-master": "4.3-dev" } }, "autoload": { @@ -4363,28 +4365,28 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", - "time": "2018-09-24T08:15:45+00:00" + "time": "2019-08-20T14:27:59+00:00" }, { "name": "webmozart/assert", - "version": "1.3.0", + "version": "1.5.0", "source": { "type": "git", "url": "https://github.com/webmozart/assert.git", - "reference": "0df1908962e7a3071564e857d86874dad1ef204a" + "reference": "88e6d84706d09a236046d686bbea96f07b3a34f4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/webmozart/assert/zipball/0df1908962e7a3071564e857d86874dad1ef204a", - "reference": "0df1908962e7a3071564e857d86874dad1ef204a", + "url": "https://api.github.com/repos/webmozart/assert/zipball/88e6d84706d09a236046d686bbea96f07b3a34f4", + "reference": "88e6d84706d09a236046d686bbea96f07b3a34f4", "shasum": "" }, "require": { - "php": "^5.3.3 || ^7.0" + "php": "^5.3.3 || ^7.0", + "symfony/polyfill-ctype": "^1.8" }, "require-dev": { - "phpunit/phpunit": "^4.6", - "sebastian/version": "^1.0.1" + "phpunit/phpunit": "^4.8.36 || ^7.5.13" }, "type": "library", "extra": { @@ -4413,7 +4415,7 @@ "check", "validate" ], - "time": "2018-01-29T19:49:41+00:00" + "time": "2019-08-24T08:43:50+00:00" } ], "aliases": [], diff --git a/doc/Addons.md b/doc/Addons.md index 47d16085a..0382cee49 100644 --- a/doc/Addons.md +++ b/doc/Addons.md @@ -533,7 +533,7 @@ Here is a complete list of all hook callbacks with file locations (as of 24-Sep- Hook::callAll("parse_link", $arr); -### mod/manage.php +### src/Module/Delegation.php Hook::callAll('home_init', $ret); diff --git a/doc/BBCode.md b/doc/BBCode.md index a1c736cc0..b13d08119 100644 --- a/doc/BBCode.md +++ b/doc/BBCode.md @@ -602,6 +602,9 @@ While taking pictures in the woods I had a really strange encounter... The [abstract] element is not working with connectors where we post HTML directly, like Tumblr, Wordpress or Pump.io. For the native connections--that is to e.g. Friendica, Hubzilla, Diaspora or GNU Social--the full posting is used and the contacts instance will display the posting as desired. +For postings that are delivered via ActivityPub, the text from the abstract is placed in the summary field. +On Mastodon this field is used for the content warning. + ## Special diff --git a/doc/Developers-Intro.md b/doc/Developers-Intro.md index d7f3f81fe..ae9a856b8 100644 --- a/doc/Developers-Intro.md +++ b/doc/Developers-Intro.md @@ -81,6 +81,7 @@ Here's a few primers if you are new to Friendica or to the PSR-2 coding standard * No closing PHP tag * No trailing spaces * Array declarations use the new square brackets syntax +* Quoting style is single quotes by default, except for needed string interpolation, SQL query strings by convention and comments that should stay in natural language. Don't worry, you don't have to know by heart the PSR-2 coding standards to start contributing to Friendica. There are a few tools you can use to check or fix your files before you commit. diff --git a/doc/Forums.md b/doc/Forums.md index add1601c5..03657b6af 100644 --- a/doc/Forums.md +++ b/doc/Forums.md @@ -40,7 +40,7 @@ You are not required to do this, but the alternative is to log out and log back This could get cumbersome if you manage several different forums/identities. You may also appoint a delegate to manage your forum. -Do this by visiting the [Delegation Setup Page](delegate). +Do this by visiting the [Delegation Setup Page](settings/delegation). This will provide you with a list of contacts on this system under "Potential Delegates". Selecting one or more persons will give them access to manage your forum. They will be able to edit contacts, profiles, and all content for this account/page. diff --git a/doc/Remove-Account.md b/doc/Remove-Account.md index eab4ab059..21e0ebf7e 100644 --- a/doc/Remove-Account.md +++ b/doc/Remove-Account.md @@ -11,13 +11,13 @@ with your web browser. You will need to be logged in at the time. You will be asked for your password to confirm the request. -If this matches your stored password, your account will immediately be blocked to all probing. -Unlike some social networks we do **not** hold onto it for a grace period in case you change your mind. -All your content and user data, etc is instantly removed. -For all intents and purposes, the account is gone in moments. +If this matches your stored password, your account will immediately be marked as deleted. +There is no grace period, this action cannot be reverted. +Most of your content and user data will be deleted shortly in the background. -We then send out an "unfriend" signal to all of your contacts. -This signal deletes all content on those networks. -Unfortunately, due to limitations of the other networks, this only works well with Friendica contacts. -We allow four days for this, in case some servers were down and the unfriend signal was queued. -After this, we finish off deleting the account. +We then send out a notification about the account removal to all of your contacts so that they can do the same with their copy of your data. + +For technical reasons some of your user data is still needed to transmit this removal message. +This remaining data will be deleted after a period of around seven days. + +To disallow impersonation we have to save your used nickname, so that it can't be used again to register on this node. diff --git a/doc/de/Addons.md b/doc/de/Addons.md index 35ce0e28b..3cbbb4b0b 100644 --- a/doc/de/Addons.md +++ b/doc/de/Addons.md @@ -256,7 +256,7 @@ Eine komplette Liste aller Hook-Callbacks mit den zugehörigen Dateien (am 01-Ap Hook::callAll("parse_link", $arr); -### mod/manage.php +### src/Module/Delegation.php Hook::callAll('home_init', $ret); diff --git a/doc/de/BBCode.md b/doc/de/BBCode.md index fe51bec64..5789be2e5 100644 --- a/doc/de/BBCode.md +++ b/doc/de/BBCode.md @@ -580,6 +580,9 @@ Für Verbindungen zu Netzwerken, zu denen Friendica den HTML Code postet, wi Bei nativen Verbindungen; das heißt zu z.B. Friendica, Hubzilla, Diaspora oder GNU Social Kontakten; wird der ungekürzte Beitrag übertragen. Die Instanz des Kontakts kümmert sich um die Darstellung. +Wird ein Beitrag über das ActivityPub Protokoll übermittelt, wird der Text des Abstracts für das "summary" (Zusammenfassung) Feld verwendet. +Dieses Feld wird von Mastodon für die Inhaltswarnung (content warning) verwendet. + ## Special
diff --git a/doc/de/Forums.md b/doc/de/Forums.md index ec3ad1400..a6ddf2efd 100644 --- a/doc/de/Forums.md +++ b/doc/de/Forums.md @@ -38,7 +38,7 @@ Du musst das nicht machen, die Alternative ist allerdings, Dich immer wieder aus Und das kann umständlich sein, wenn Du mehrere verschiedene Foren/Identitäten verwaltest. Du kannst ebenso jemanden wählen, der Dein Forum verwaltet. -Mach das, indem Du die [Delegations-Setup-Seite](/delegate) besuchst. +Mach das, indem Du die [Delegations-Setup-Seite](/settings/delegation) besuchst. Dort wird Dir eine Liste an "Potentiellen Bevollmächtigen" angezeigt. Die Auswahl einer oder mehrerer Personen gibt diesen die Möglichkeit, Dein Forum zu verwalten. Sie können Kontakte, Profile und alle Inhalte Deines Accounts/deiner Seite bearbeiten. diff --git a/doc/de/Remove-Account.md b/doc/de/Remove-Account.md index 9c6e0403b..dccb0655a 100644 --- a/doc/de/Remove-Account.md +++ b/doc/de/Remove-Account.md @@ -10,24 +10,14 @@ Wir freuen uns nicht, wenn Leute Friendica verlassen, aber wenn du deinen Accoun in deinem Webbrowser. Du musst dabei eingeloggt sein. Du wirst nach deinem Passwort gefragt, um die Anfrage zu bestätigen. -Wenn dieses mit deinem gespeichertem Passwort übereinstimmt, dann wird dein Account sofort gelöscht. -Anders als andere Netzwerke, behalten wir die Daten **nicht** für eine gewisse Zeit, falls du deine Meinung noch änderst. -Deine Nutzerdetails, deine Unterhaltungen, deine Photos, deine Freunde - alles; wird sofort gelöscht und du wirst ausgeloggt. +Wenn dieses mit deinem gespeichertem Passwort übereinstimmt, dann wird dein Account als "gelöscht" markiert. +Dies passiert sofort und kann nicht rückgängig gemacht werden. +Die meisten Deiner Inhalte und Benutzerdaten werden kurzfristig danach durch Hintergrundprozesse gelöscht. -Wenn Beiträge ablaufen, schicken wir Mitteilungen an Friendica, um diese zu löschen. -Diaspora hat keine automatische Löschfunktion, so dass diese Funktion in dem Netzwerk deaktiviert ist. -Und hoffentlich ist klar, dass das Löschen auch in anderen Netzwerken nicht funktioniert. -Wenn du manuell einen Beitrag bzw. eine Reihe von Beiträgen löschst, dann senden wir individuelle Mitteilungen zu Friendica und Diaspora für jeden gelöschten Post. +Parallel dazu senden wir eine Mitteilung an die Server deiner Kontakte, damit sie deine dort vorliegenden Daten ebenfalls löschen. +Wir haben keinen Einfluss darauf, wie sorgfältig und ob überhaupt diese Systeme der Löschaufforderung nachgehen. -Diaspora versäumt dieses oft. +Aus technischen Gründen benötigen wir für die Übetragung dieser Mitteilung ein paar Benutzerdaten. +Diese Daten werden dann nach einer Frist von etwa sieben Tagen ebenfalls gelöscht. -Wenn du einen Beitrag löscht, aber jemand diesem Beitrag folgt, wird es trotzdem gelöscht. -Dein Wunsch hat Priorität. - -Wenn du deinen Account löscht, dann löschen wir alle Beiträge, dein Profil, die Nutzerdaten etc. sofort. - -Um einen Gesamtlöschauftrag zu versenden, bräuchten wir zunächst noch deinen Account; auch, um deinen Freunden zu zeigen, wer diese Anfrage stellt. -Das können wir nicht tun, wenn du keinen Account mehr hast. - -Deine Freunde können möglicherweise noch deine Beiträge sehen, wenn dein Account gelöscht wurde, aber es gibt keinen öffentlichen Ort in Friendica mehr, wo diese angeschaut werden können. -Wenn du Freunde bei Diaspora hast, kann es sein, dass deine Beiträge weiterhin vorhanden und für andere aus diesem Netzwerk sichtbar sind. +Wir speichern deinen Benutzernamen dauerhaft, damit sich niemand einen Account unter deinem Spitznamen anlegen kann. diff --git a/doc/smarty3-templates.md b/doc/smarty3-templates.md index 599e3597e..f174f2164 100644 --- a/doc/smarty3-templates.md +++ b/doc/smarty3-templates.md @@ -177,6 +177,7 @@ Field parameter: 1. Label for the input box, 2. Current text for the box, 3. Help text for the input box. +4. if set to "required" modern browser will check that this input box is filled when submitting the form, ### field_yesno.tpl diff --git a/include/api.php b/include/api.php index 4be0ff24a..7daf13455 100644 --- a/include/api.php +++ b/include/api.php @@ -48,9 +48,9 @@ use Friendica\Util\Proxy as ProxyUtils; use Friendica\Util\Strings; use Friendica\Util\XML; -require_once 'mod/share.php'; -require_once 'mod/item.php'; -require_once 'mod/wall_upload.php'; +require_once __DIR__ . '/../mod/share.php'; +require_once __DIR__ . '/../mod/item.php'; +require_once __DIR__ . '/../mod/wall_upload.php'; define('API_METHOD_ANY', '*'); define('API_METHOD_GET', 'GET'); @@ -162,6 +162,7 @@ function api_register_func($path, $func, $auth = false, $method = API_METHOD_ANY * @brief Login API user * * @param App $a App + * @throws ForbiddenException * @throws InternalServerErrorException * @throws UnauthorizedException * @hook 'authenticate' @@ -170,8 +171,6 @@ function api_register_func($path, $func, $auth = false, $method = API_METHOD_ANY * 'password' => password from login form * 'authenticated' => return status, * 'user_record' => return authenticated user record - * @hook 'logged_in' - * array $user logged user record */ function api_login(App $a) { @@ -182,7 +181,7 @@ function api_login(App $a) list($consumer, $token) = $oauth1->verify_request($request); if (!is_null($token)) { $oauth1->loginUser($token->uid); - Hook::callAll('logged_in', $a->user); + Session::set('allow_api', true); return; } echo __FILE__.__LINE__.__FUNCTION__ . "
";
@@ -208,8 +207,8 @@ function api_login(App $a)
 		throw new UnauthorizedException("This API requires login");
 	}
 
-	$user = defaults($_SERVER, 'PHP_AUTH_USER', '');
-	$password = defaults($_SERVER, 'PHP_AUTH_PW', '');
+	$user = $_SERVER['PHP_AUTH_USER'] ?? '';
+	$password = $_SERVER['PHP_AUTH_PW'] ?? '';
 
 	// allow "user@server" login (but ignore 'server' part)
 	$at = strstr($user, "@", true);
@@ -273,7 +272,7 @@ function api_check_method($method)
 	if ($method == "*") {
 		return true;
 	}
-	return (stripos($method, defaults($_SERVER, 'REQUEST_METHOD', 'GET')) !== false);
+	return (stripos($method, $_SERVER['REQUEST_METHOD'] ?? 'GET') !== false);
 }
 
 /**
@@ -323,7 +322,7 @@ function api_call(App $a)
 
 				$stamp =  microtime(true);
 				$return = call_user_func($info['func'], $type);
-				$duration = (float) (microtime(true) - $stamp);
+				$duration = floatval(microtime(true) - $stamp);
 
 				Logger::info(API_LOG_PREFIX . 'username {username}', ['module' => 'api', 'action' => 'call', 'username' => $a->user['username'], 'duration' => round($duration, 2)]);
 
@@ -776,14 +775,14 @@ function api_get_user(App $a, $contact_id = null)
  */
 function api_item_get_user(App $a, $item)
 {
-	$status_user = api_get_user($a, defaults($item, 'author-id', null));
+	$status_user = api_get_user($a, $item['author-id'] ?? null);
 
 	$author_user = $status_user;
 
-	$status_user["protected"] = defaults($item, 'private', 0);
+	$status_user["protected"] = $item['private'] ?? 0;
 
-	if (defaults($item, 'thr-parent', '') == defaults($item, 'uri', '')) {
-		$owner_user = api_get_user($a, defaults($item, 'owner-id', null));
+	if (($item['thr-parent'] ?? '') == ($item['uri'] ?? '')) {
+		$owner_user = api_get_user($a, $item['owner-id'] ?? null);
 	} else {
 		$owner_user = $author_user;
 	}
@@ -947,7 +946,7 @@ function api_account_verify_credentials($type)
 	unset($_REQUEST["screen_name"]);
 	unset($_GET["screen_name"]);
 
-	$skip_status = defaults($_REQUEST, 'skip_status', false);
+	$skip_status = $_REQUEST['skip_status'] ?? false;
 
 	$user_info = api_get_user($a);
 
@@ -1518,10 +1517,12 @@ function api_search($type)
 		$count = $_REQUEST['count'];
 	}
 	
-	$since_id = defaults($_REQUEST, 'since_id', 0);
-	$max_id = defaults($_REQUEST, 'max_id', 0);
-	$page = (!empty($_REQUEST['page']) ? $_REQUEST['page'] - 1 : 0);
-	$start = $page * $count;
+	$since_id = $_REQUEST['since_id'] ?? 0;
+	$max_id = $_REQUEST['max_id'] ?? 0;
+	$page = $_REQUEST['page'] ?? 1;
+
+	$start = max(0, ($page - 1) * $count);
+
 	$params = ['order' => ['id' => true], 'limit' => [$start, $count]];
 	if (preg_match('/^#(\w+)$/', $searchTerm, $matches) === 1 && isset($matches[1])) {
 		$searchTerm = $matches[1];
@@ -1609,17 +1610,14 @@ function api_statuses_home_timeline($type)
 	// get last network messages
 
 	// params
-	$count = defaults($_REQUEST, 'count', 20);
-	$page = (!empty($_REQUEST['page']) ? $_REQUEST['page'] - 1 : 0);
-	if ($page < 0) {
-		$page = 0;
-	}
-	$since_id = defaults($_REQUEST, 'since_id', 0);
-	$max_id = defaults($_REQUEST, 'max_id', 0);
+	$count = $_REQUEST['count'] ?? 20;
+	$page = $_REQUEST['page']?? 0;
+	$since_id = $_REQUEST['since_id'] ?? 0;
+	$max_id = $_REQUEST['max_id'] ?? 0;
 	$exclude_replies = !empty($_REQUEST['exclude_replies']);
-	$conversation_id = defaults($_REQUEST, 'conversation_id', 0);
+	$conversation_id = $_REQUEST['conversation_id'] ?? 0;
 
-	$start = $page * $count;
+	$start = max(0, ($page - 1) * $count);
 
 	$condition = ["`uid` = ? AND `gravity` IN (?, ?) AND `item`.`id` > ?",
 		api_user(), GRAVITY_PARENT, GRAVITY_COMMENT, $since_id];
@@ -1699,17 +1697,14 @@ function api_statuses_public_timeline($type)
 	// get last network messages
 
 	// params
-	$count = defaults($_REQUEST, 'count', 20);
-	$page = (!empty($_REQUEST['page']) ? $_REQUEST['page'] -1 : 0);
-	if ($page < 0) {
-		$page = 0;
-	}
-	$since_id = defaults($_REQUEST, 'since_id', 0);
-	$max_id = defaults($_REQUEST, 'max_id', 0);
+	$count = $_REQUEST['count'] ?? 20;
+	$page = $_REQUEST['page'] ?? 1;
+	$since_id = $_REQUEST['since_id'] ?? 0;
+	$max_id = $_REQUEST['max_id'] ?? 0;
 	$exclude_replies = (!empty($_REQUEST['exclude_replies']) ? 1 : 0);
-	$conversation_id = defaults($_REQUEST, 'conversation_id', 0);
+	$conversation_id = $_REQUEST['conversation_id'] ?? 0;
 
-	$start = $page * $count;
+	$start = max(0, ($page - 1) * $count);
 
 	if ($exclude_replies && !$conversation_id) {
 		$condition = ["`gravity` IN (?, ?) AND `iid` > ? AND NOT `private` AND `wall` AND NOT `user`.`hidewall` AND NOT `author`.`hidden`",
@@ -1784,16 +1779,14 @@ function api_statuses_networkpublic_timeline($type)
 		throw new ForbiddenException();
 	}
 
-	$since_id        = defaults($_REQUEST, 'since_id', 0);
-	$max_id          = defaults($_REQUEST, 'max_id', 0);
+	$since_id        = $_REQUEST['since_id'] ?? 0;
+	$max_id          = $_REQUEST['max_id'] ?? 0;
 
 	// pagination
-	$count = defaults($_REQUEST, 'count', 20);
-	$page  = defaults($_REQUEST, 'page', 1);
-	if ($page < 1) {
-		$page = 1;
-	}
-	$start = ($page - 1) * $count;
+	$count = $_REQUEST['count'] ?? 20;
+	$page  = $_REQUEST['page'] ?? 1;
+
+	$start = max(0, ($page - 1) * $count);
 
 	$condition = ["`uid` = 0 AND `gravity` IN (?, ?) AND `thread`.`iid` > ? AND NOT `private`",
 		GRAVITY_PARENT, GRAVITY_COMMENT, $since_id];
@@ -1848,15 +1841,15 @@ function api_statuses_show($type)
 	}
 
 	// params
-	$id = intval(defaults($a->argv, 3, 0));
+	$id = intval($a->argv[3] ?? 0);
 
 	if ($id == 0) {
-		$id = intval(defaults($_REQUEST, 'id', 0));
+		$id = intval($_REQUEST['id'] ?? 0);
 	}
 
 	// Hotot workaround
 	if ($id == 0) {
-		$id = intval(defaults($a->argv, 4, 0));
+		$id = intval($a->argv[4] ?? 0);
 	}
 
 	Logger::log('API: api_statuses_show: ' . $id);
@@ -1927,24 +1920,21 @@ function api_conversation_show($type)
 	}
 
 	// params
-	$id       = intval(defaults($a->argv , 3         , 0));
-	$since_id = intval(defaults($_REQUEST, 'since_id', 0));
-	$max_id   = intval(defaults($_REQUEST, 'max_id'  , 0));
-	$count    = intval(defaults($_REQUEST, 'count'   , 20));
-	$page     = intval(defaults($_REQUEST, 'page'    , 1)) - 1;
-	if ($page < 0) {
-		$page = 0;
-	}
+	$id       = intval($a->argv[3]           ?? 0);
+	$since_id = intval($_REQUEST['since_id'] ?? 0);
+	$max_id   = intval($_REQUEST['max_id']   ?? 0);
+	$count    = intval($_REQUEST['count']    ?? 20);
+	$page     = intval($_REQUEST['page']     ?? 1);
 
-	$start = $page * $count;
+	$start = max(0, ($page - 1) * $count);
 
 	if ($id == 0) {
-		$id = intval(defaults($_REQUEST, 'id', 0));
+		$id = intval($_REQUEST['id'] ?? 0);
 	}
 
 	// Hotot workaround
 	if ($id == 0) {
-		$id = intval(defaults($a->argv, 4, 0));
+		$id = intval($a->argv[4] ?? 0);
 	}
 
 	Logger::info(API_LOG_PREFIX . '{subaction}', ['module' => 'api', 'action' => 'conversation', 'subaction' => 'show', 'id' => $id]);
@@ -2013,15 +2003,15 @@ function api_statuses_repeat($type)
 	api_get_user($a);
 
 	// params
-	$id = intval(defaults($a->argv, 3, 0));
+	$id = intval($a->argv[3] ?? 0);
 
 	if ($id == 0) {
-		$id = intval(defaults($_REQUEST, 'id', 0));
+		$id = intval($_REQUEST['id'] ?? 0);
 	}
 
 	// Hotot workaround
 	if ($id == 0) {
-		$id = intval(defaults($a->argv, 4, 0));
+		$id = intval($a->argv[4] ?? 0);
 	}
 
 	Logger::log('API: api_statuses_repeat: '.$id);
@@ -2084,15 +2074,15 @@ function api_statuses_destroy($type)
 	api_get_user($a);
 
 	// params
-	$id = intval(defaults($a->argv, 3, 0));
+	$id = intval($a->argv[3] ?? 0);
 
 	if ($id == 0) {
-		$id = intval(defaults($_REQUEST, 'id', 0));
+		$id = intval($_REQUEST['id'] ?? 0);
 	}
 
 	// Hotot workaround
 	if ($id == 0) {
-		$id = intval(defaults($a->argv, 4, 0));
+		$id = intval($a->argv[4] ?? 0);
 	}
 
 	Logger::log('API: api_statuses_destroy: '.$id);
@@ -2138,15 +2128,12 @@ function api_statuses_mentions($type)
 	// get last network messages
 
 	// params
-	$since_id = defaults($_REQUEST, 'since_id', 0);
-	$max_id   = defaults($_REQUEST, 'max_id'  , 0);
-	$count    = defaults($_REQUEST, 'count'   , 20);
-	$page     = defaults($_REQUEST, 'page'    , 1);
-	if ($page < 1) {
-		$page = 1;
-	}
+	$since_id = $_REQUEST['since_id'] ?? 0;
+	$max_id   = $_REQUEST['max_id']   ?? 0;
+	$count    = $_REQUEST['count']    ?? 20;
+	$page     = $_REQUEST['page']     ?? 1;
 
-	$start = ($page - 1) * $count;
+	$start = max(0, ($page - 1) * $count);
 
 	$condition = ["`uid` = ? AND `gravity` IN (?, ?) AND `item`.`id` > ? AND `author-id` != ?
 		AND `item`.`parent` IN (SELECT `iid` FROM `thread` WHERE `thread`.`uid` = ? AND `thread`.`mention` AND NOT `thread`.`ignored`)",
@@ -2208,18 +2195,16 @@ function api_statuses_user_timeline($type)
 		Logger::DEBUG
 	);
 
-	$since_id        = defaults($_REQUEST, 'since_id', 0);
-	$max_id          = defaults($_REQUEST, 'max_id', 0);
+	$since_id        = $_REQUEST['since_id'] ?? 0;
+	$max_id          = $_REQUEST['max_id'] ?? 0;
 	$exclude_replies = !empty($_REQUEST['exclude_replies']);
-	$conversation_id = defaults($_REQUEST, 'conversation_id', 0);
+	$conversation_id = $_REQUEST['conversation_id'] ?? 0;
 
 	// pagination
-	$count = defaults($_REQUEST, 'count', 20);
-	$page  = defaults($_REQUEST, 'page', 1);
-	if ($page < 1) {
-		$page = 1;
-	}
-	$start = ($page - 1) * $count;
+	$count = $_REQUEST['count'] ?? 20;
+	$page  = $_REQUEST['page'] ?? 1;
+
+	$start = max(0, ($page - 1) * $count);
 
 	$condition = ["`uid` = ? AND `gravity` IN (?, ?) AND `item`.`id` > ? AND `item`.`contact-id` = ?",
 		api_user(), GRAVITY_PARENT, GRAVITY_COMMENT, $since_id, $user_info['cid']];
@@ -2298,9 +2283,9 @@ function api_favorites_create_destroy($type)
 	}
 	$action = str_replace("." . $type, "", $a->argv[$action_argv_id]);
 	if ($a->argc == $action_argv_id + 2) {
-		$itemid = intval(defaults($a->argv, $action_argv_id + 1, 0));
+		$itemid = intval($a->argv[$action_argv_id + 1] ?? 0);
 	} else {
-		$itemid = intval(defaults($_REQUEST, 'id', 0));
+		$itemid = intval($_REQUEST['id'] ?? 0);
 	}
 
 	$item = Item::selectFirstForUser(api_user(), [], ['id' => $itemid, 'uid' => api_user()]);
@@ -2380,15 +2365,12 @@ function api_favorites($type)
 		$ret = [];
 	} else {
 		// params
-		$since_id = defaults($_REQUEST, 'since_id', 0);
-		$max_id = defaults($_REQUEST, 'max_id', 0);
-		$count = defaults($_GET, 'count', 20);
-		$page = (!empty($_REQUEST['page']) ? $_REQUEST['page'] -1 : 0);
-		if ($page < 0) {
-			$page = 0;
-		}
+		$since_id = $_REQUEST['since_id'] ?? 0;
+		$max_id = $_REQUEST['max_id'] ?? 0;
+		$count = $_GET['count'] ?? 20;
+		$page = $_REQUEST['page'] ?? 1;
 
-		$start = $page*$count;
+		$start = max(0, ($page - 1) * $count);
 
 		$condition = ["`uid` = ? AND `gravity` IN (?, ?) AND `id` > ? AND `starred`",
 			api_user(), GRAVITY_PARENT, GRAVITY_COMMENT, $since_id];
@@ -2439,14 +2421,14 @@ function api_format_messages($item, $recipient, $sender)
 		'sender_id'             => $sender['id'],
 		'text'                  => "",
 		'recipient_id'          => $recipient['id'],
-		'created_at'            => api_date(defaults($item, 'created', DateTimeFormat::utcNow())),
+		'created_at'            => api_date($item['created'] ?? DateTimeFormat::utcNow()),
 		'sender_screen_name'    => $sender['screen_name'],
 		'recipient_screen_name' => $recipient['screen_name'],
 		'sender'                => $sender,
 		'recipient'             => $recipient,
 		'title'                 => "",
-		'friendica_seen'        => defaults($item, 'seen', 0),
-		'friendica_parent_uri'  => defaults($item, 'parent-uri', ''),
+		'friendica_seen'        => $item['seen'] ?? 0,
+		'friendica_parent_uri'  => $item['parent-uri'] ?? '',
 	];
 
 	// "uid" and "self" are only needed for some internal stuff, so remove it from here
@@ -2509,8 +2491,8 @@ function api_convert_item($item)
 		$statustext = trim($statustitle."\n\n".$statusbody);
 	}
 
-	if ((defaults($item, 'network', Protocol::PHANTOM) == Protocol::FEED) && (mb_strlen($statustext)> 1000)) {
-		$statustext = mb_substr($statustext, 0, 1000) . "... \n" . defaults($item, 'plink', '');
+	if ((($item['network'] ?? Protocol::PHANTOM) == Protocol::FEED) && (mb_strlen($statustext)> 1000)) {
+		$statustext = mb_substr($statustext, 0, 1000) . "... \n" . ($item['plink'] ?? '');
 	}
 
 	$statushtml = BBCode::convert(api_clean_attachments($body), false);
@@ -2544,7 +2526,7 @@ function api_convert_item($item)
 	}
 
 	// feeds without body should contain the link
-	if ((defaults($item, 'network', Protocol::PHANTOM) == Protocol::FEED) && (strlen($item['body']) == 0)) {
+	if ((($item['network'] ?? Protocol::PHANTOM) == Protocol::FEED) && (strlen($item['body']) == 0)) {
 		$statushtml .= BBCode::convert($item['plink']);
 	}
 
@@ -2587,7 +2569,7 @@ function api_get_attachments(&$body)
 		}
 	}
 
-	if (strstr(defaults($_SERVER, 'HTTP_USER_AGENT', ''), "AndStatus")) {
+	if (strstr($_SERVER['HTTP_USER_AGENT'] ?? '', 'AndStatus')) {
 		foreach ($images[0] as $orig) {
 			$body = str_replace($orig, "", $body);
 		}
@@ -2607,7 +2589,7 @@ function api_get_attachments(&$body)
  */
 function api_get_entitities(&$text, $bbcode)
 {
-	$include_entities = strtolower(defaults($_REQUEST, 'include_entities', "false"));
+	$include_entities = strtolower($_REQUEST['include_entities'] ?? 'false');
 
 	if ($include_entities != "true") {
 		preg_match_all("/\[img](.*?)\[\/img\]/ism", $bbcode, $images);
@@ -3040,6 +3022,8 @@ function api_format_item($item, $type = "json", $status_user = null, $author_use
 		'statusnet_conversation_id' => $item['parent'],
 		'external_url' => System::baseUrl() . "/display/" . $item['guid'],
 		'friendica_activities' => api_format_items_activities($item, $type),
+		'friendica_title' => $item['title'],
+		'friendica_html' => BBCode::convert($item['body'], false)
 	];
 
 	if (count($converted["attachments"]) > 0) {
@@ -3310,17 +3294,14 @@ function api_lists_statuses($type)
 	}
 
 	// params
-	$count = defaults($_REQUEST, 'count', 20);
-	$page = (!empty($_REQUEST['page']) ? $_REQUEST['page'] - 1 : 0);
-	if ($page < 0) {
-		$page = 0;
-	}
-	$since_id = defaults($_REQUEST, 'since_id', 0);
-	$max_id = defaults($_REQUEST, 'max_id', 0);
+	$count = $_REQUEST['count'] ?? 20;
+	$page = $_REQUEST['page'] ?? 1;
+	$since_id = $_REQUEST['since_id'] ?? 0;
+	$max_id = $_REQUEST['max_id'] ?? 0;
 	$exclude_replies = (!empty($_REQUEST['exclude_replies']) ? 1 : 0);
-	$conversation_id = defaults($_REQUEST, 'conversation_id', 0);
+	$conversation_id = $_REQUEST['conversation_id'] ?? 0;
 
-	$start = $page * $count;
+	$start = max(0, ($page - 1) * $count);
 
 	$condition = ["`uid` = ? AND `gravity` IN (?, ?) AND `id` > ? AND `group_member`.`gid` = ?",
 		api_user(), GRAVITY_PARENT, GRAVITY_COMMENT, $since_id, $_REQUEST['list_id']];
@@ -3380,12 +3361,10 @@ function api_statuses_f($qtype)
 	}
 
 	// pagination
-	$count = defaults($_GET, 'count', 20);
-	$page = defaults($_GET, 'page', 1);
-	if ($page < 1) {
-		$page = 1;
-	}
-	$start = ($page - 1) * $count;
+	$count = $_GET['count'] ?? 20;
+	$page = $_GET['page'] ?? 1;
+
+	$start = max(0, ($page - 1) * $count);
 
 	$user_info = api_get_user($a);
 
@@ -3632,7 +3611,7 @@ function api_ff_ids($type)
 
 	api_get_user($a);
 
-	$stringify_ids = defaults($_REQUEST, 'stringify_ids', false);
+	$stringify_ids = $_REQUEST['stringify_ids'] ?? false;
 
 	$r = q(
 		"SELECT `pcontact`.`id` FROM `contact`
@@ -3807,9 +3786,9 @@ function api_direct_messages_destroy($type)
 	// params
 	$user_info = api_get_user($a);
 	//required
-	$id = defaults($_REQUEST, 'id', 0);
+	$id = $_REQUEST['id'] ?? 0;
 	// optional
-	$parenturi = defaults($_REQUEST, 'friendica_parenturi', "");
+	$parenturi = $_REQUEST['friendica_parenturi'] ?? '';
 	$verbose = (!empty($_GET['friendica_verbose']) ? strtolower($_GET['friendica_verbose']) : "false");
 	/// @todo optional parameter 'include_entities' from Twitter API not yet implemented
 
@@ -3890,7 +3869,7 @@ function api_friendships_destroy($type)
 		throw new ForbiddenException();
 	}
 
-	$contact_id = defaults($_REQUEST, 'user_id');
+	$contact_id = $_REQUEST['user_id'] ?? 0;
 
 	if (empty($contact_id)) {
 		Logger::notice(API_LOG_PREFIX . 'No user_id specified', ['module' => 'api', 'action' => 'friendships_destroy']);
@@ -3971,17 +3950,14 @@ function api_direct_messages_box($type, $box, $verbose)
 		throw new ForbiddenException();
 	}
 	// params
-	$count = defaults($_GET, 'count', 20);
-	$page = defaults($_REQUEST, 'page', 1) - 1;
-	if ($page < 0) {
-		$page = 0;
-	}
+	$count = $_GET['count'] ?? 20;
+	$page = $_REQUEST['page'] ?? 1;
 
-	$since_id = defaults($_REQUEST, 'since_id', 0);
-	$max_id = defaults($_REQUEST, 'max_id', 0);
+	$since_id = $_REQUEST['since_id'] ?? 0;
+	$max_id = $_REQUEST['max_id'] ?? 0;
 
-	$user_id = defaults($_REQUEST, 'user_id', '');
-	$screen_name = defaults($_REQUEST, 'screen_name', '');
+	$user_id = $_REQUEST['user_id'] ?? '';
+	$screen_name = $_REQUEST['screen_name'] ?? '';
 
 	//  caller user info
 	unset($_REQUEST["user_id"]);
@@ -3997,7 +3973,7 @@ function api_direct_messages_box($type, $box, $verbose)
 	$profile_url = $user_info["url"];
 
 	// pagination
-	$start = $page * $count;
+	$start = max(0, ($page - 1) * $count);
 
 	$sql_extra = "";
 
@@ -4005,7 +3981,7 @@ function api_direct_messages_box($type, $box, $verbose)
 	if ($box=="sentbox") {
 		$sql_extra = "`mail`.`from-url`='" . DBA::escape($profile_url) . "'";
 	} elseif ($box == "conversation") {
-		$sql_extra = "`mail`.`parent-uri`='" . DBA::escape(defaults($_GET, 'uri', ''))  . "'";
+		$sql_extra = "`mail`.`parent-uri`='" . DBA::escape($_GET['uri'] ?? '')  . "'";
 	} elseif ($box == "all") {
 		$sql_extra = "true";
 	} elseif ($box == "inbox") {
@@ -4185,7 +4161,7 @@ function api_fr_photoalbum_delete($type)
 		throw new ForbiddenException();
 	}
 	// input params
-	$album = defaults($_REQUEST, 'album', "");
+	$album = $_REQUEST['album'] ?? '';
 
 	// we do not allow calls without album string
 	if ($album == "") {
@@ -4240,8 +4216,8 @@ function api_fr_photoalbum_update($type)
 		throw new ForbiddenException();
 	}
 	// input params
-	$album = defaults($_REQUEST, 'album', "");
-	$album_new = defaults($_REQUEST, 'album_new', "");
+	$album = $_REQUEST['album'] ?? '';
+	$album_new = $_REQUEST['album_new'] ?? '';
 
 	// we do not allow calls without album string
 	if ($album == "") {
@@ -4332,14 +4308,14 @@ function api_fr_photo_create_update($type)
 		throw new ForbiddenException();
 	}
 	// input params
-	$photo_id = defaults($_REQUEST, 'photo_id', null);
-	$desc = defaults($_REQUEST, 'desc', (array_key_exists('desc', $_REQUEST) ? "" : null)) ; // extra check necessary to distinguish between 'not provided' and 'empty string'
-	$album = defaults($_REQUEST, 'album', null);
-	$album_new = defaults($_REQUEST, 'album_new', null);
-	$allow_cid = defaults($_REQUEST, 'allow_cid', (array_key_exists('allow_cid', $_REQUEST) ? " " : null));
-	$deny_cid  = defaults($_REQUEST, 'deny_cid' , (array_key_exists('deny_cid' , $_REQUEST) ? " " : null));
-	$allow_gid = defaults($_REQUEST, 'allow_gid', (array_key_exists('allow_gid', $_REQUEST) ? " " : null));
-	$deny_gid  = defaults($_REQUEST, 'deny_gid' , (array_key_exists('deny_gid' , $_REQUEST) ? " " : null));
+	$photo_id  = $_REQUEST['photo_id']  ?? null;
+	$desc      = $_REQUEST['desc']      ?? null;
+	$album     = $_REQUEST['album']     ?? null;
+	$album_new = $_REQUEST['album_new'] ?? null;
+	$allow_cid = $_REQUEST['allow_cid'] ?? null;
+	$deny_cid  = $_REQUEST['deny_cid' ] ?? null;
+	$allow_gid = $_REQUEST['allow_gid'] ?? null;
+	$deny_gid  = $_REQUEST['deny_gid' ] ?? null;
 	$visibility = !empty($_REQUEST['visibility']) && $_REQUEST['visibility'] !== "false";
 
 	// do several checks on input parameters
@@ -4470,7 +4446,7 @@ function api_fr_photo_delete($type)
 	}
 
 	// input params
-	$photo_id = defaults($_REQUEST, 'photo_id', null);
+	$photo_id = $_REQUEST['photo_id'] ?? null;
 
 	// do several checks on input parameters
 	// we do not allow calls without photo id
@@ -4557,7 +4533,7 @@ function api_account_update_profile_image($type)
 		throw new ForbiddenException();
 	}
 	// input params
-	$profile_id = defaults($_REQUEST, 'profile_id', 0);
+	$profile_id = $_REQUEST['profile_id'] ?? 0;
 
 	// error if image data is missing
 	if (empty($_FILES['image'])) {
@@ -4689,9 +4665,10 @@ api_register_func('api/account/update_profile', 'api_account_update_profile', tr
  */
 function check_acl_input($acl_string)
 {
-	if ($acl_string == null || $acl_string == " ") {
+	if (empty($acl_string)) {
 		return false;
 	}
+
 	$contact_not_found = false;
 
 	// split  into array of cid's
@@ -4709,7 +4686,6 @@ function check_acl_input($acl_string)
 }
 
 /**
- *
  * @param string  $mediatype
  * @param array   $media
  * @param string  $type
@@ -4728,6 +4704,7 @@ function check_acl_input($acl_string)
  * @throws ImagickException
  * @throws InternalServerErrorException
  * @throws NotFoundException
+ * @throws UnauthorizedException
  */
 function save_media_to_database($mediatype, $media, $type, $album, $allow_cid, $deny_cid, $allow_gid, $deny_gid, $desc, $profile = 0, $visibility = false, $photo_id = null)
 {
@@ -5062,8 +5039,8 @@ function prepare_photo_data($type, $scale, $photo_id)
  */
 function api_friendica_remoteauth()
 {
-	$url = defaults($_GET, 'url', '');
-	$c_url = defaults($_GET, 'c_url', '');
+	$url = $_GET['url'] ?? '';
+	$c_url = $_GET['c_url'] ?? '';
 
 	if ($url === '' || $c_url === '') {
 		throw new BadRequestException("Wrong parameters.");
@@ -5081,7 +5058,7 @@ function api_friendica_remoteauth()
 
 	$cid = $contact['id'];
 
-	$dfrn_id = defaults($contact, 'issued-id', $contact['dfrn-id']);
+	$dfrn_id = $contact['issued-id'] ?? $contact['dfrn-id'];
 
 	if ($contact['duplex'] && $contact['issued-id']) {
 		$orig_id = $contact['issued-id'];
@@ -5416,7 +5393,7 @@ function api_in_reply_to($item)
  */
 function api_clean_plain_items($text)
 {
-	$include_entities = strtolower(defaults($_REQUEST, 'include_entities', "false"));
+	$include_entities = strtolower($_REQUEST['include_entities'] ?? 'false');
 
 	$text = BBCode::cleanPictureLinks($text);
 	$URLSearchString = "^\[\]";
@@ -5554,7 +5531,7 @@ function api_friendica_group_show($type)
 
 	// params
 	$user_info = api_get_user($a);
-	$gid = defaults($_REQUEST, 'gid', 0);
+	$gid = $_REQUEST['gid'] ?? 0;
 	$uid = $user_info['uid'];
 
 	// get data of the specified group id or all groups if not specified
@@ -5624,8 +5601,8 @@ function api_friendica_group_delete($type)
 
 	// params
 	$user_info = api_get_user($a);
-	$gid = defaults($_REQUEST, 'gid', 0);
-	$name = defaults($_REQUEST, 'name', "");
+	$gid = $_REQUEST['gid'] ?? 0;
+	$name = $_REQUEST['name'] ?? '';
 	$uid = $user_info['uid'];
 
 	// error if no gid specified
@@ -5691,7 +5668,7 @@ function api_lists_destroy($type)
 
 	// params
 	$user_info = api_get_user($a);
-	$gid = defaults($_REQUEST, 'list_id', 0);
+	$gid = $_REQUEST['list_id'] ?? 0;
 	$uid = $user_info['uid'];
 
 	// error if no gid specified
@@ -5813,7 +5790,7 @@ function api_friendica_group_create($type)
 
 	// params
 	$user_info = api_get_user($a);
-	$name = defaults($_REQUEST, 'name', "");
+	$name = $_REQUEST['name'] ?? '';
 	$uid = $user_info['uid'];
 	$json = json_decode($_POST['json'], true);
 	$users = $json['user'];
@@ -5847,7 +5824,7 @@ function api_lists_create($type)
 
 	// params
 	$user_info = api_get_user($a);
-	$name = defaults($_REQUEST, 'name', "");
+	$name = $_REQUEST['name'] ?? '';
 	$uid = $user_info['uid'];
 
 	$success = group_create($name, $uid);
@@ -5887,8 +5864,8 @@ function api_friendica_group_update($type)
 	// params
 	$user_info = api_get_user($a);
 	$uid = $user_info['uid'];
-	$gid = defaults($_REQUEST, 'gid', 0);
-	$name = defaults($_REQUEST, 'name', "");
+	$gid = $_REQUEST['gid'] ?? 0;
+	$name = $_REQUEST['name'] ?? '';
 	$json = json_decode($_POST['json'], true);
 	$users = $json['user'];
 
@@ -5965,8 +5942,8 @@ function api_lists_update($type)
 
 	// params
 	$user_info = api_get_user($a);
-	$gid = defaults($_REQUEST, 'list_id', 0);
-	$name = defaults($_REQUEST, 'name', "");
+	$gid = $_REQUEST['list_id'] ?? 0;
+	$name = $_REQUEST['name'] ?? '';
 	$uid = $user_info['uid'];
 
 	// error if no gid specified
@@ -6015,7 +5992,7 @@ function api_friendica_activity($type)
 	$verb = strtolower($a->argv[3]);
 	$verb = preg_replace("|\..*$|", "", $verb);
 
-	$id = defaults($_REQUEST, 'id', 0);
+	$id = $_REQUEST['id'] ?? 0;
 
 	$res = Item::performLike($id, $verb);
 
@@ -6152,7 +6129,7 @@ function api_friendica_direct_messages_setseen($type)
 	// params
 	$user_info = api_get_user($a);
 	$uid = $user_info['uid'];
-	$id = defaults($_REQUEST, 'id', 0);
+	$id = $_REQUEST['id'] ?? 0;
 
 	// return error if id is zero
 	if ($id == "") {
@@ -6206,7 +6183,7 @@ function api_friendica_direct_messages_search($type, $box = "")
 
 	// params
 	$user_info = api_get_user($a);
-	$searchstring = defaults($_REQUEST, 'searchstring', "");
+	$searchstring = $_REQUEST['searchstring'] ?? '';
 	$uid = $user_info['uid'];
 
 	// error if no searchstring specified
@@ -6273,7 +6250,7 @@ function api_friendica_profile_show($type)
 	}
 
 	// input params
-	$profile_id = defaults($_REQUEST, 'profile_id', 0);
+	$profile_id = $_REQUEST['profile_id'] ?? 0;
 
 	// retrieve general information about profiles for user
 	$multi_profiles = Feature::isEnabled(api_user(), 'multi_profiles');
diff --git a/include/conversation.php b/include/conversation.php
index 66b6d2a11..b6faa4d2c 100644
--- a/include/conversation.php
+++ b/include/conversation.php
@@ -365,7 +365,7 @@ function localize_item(&$item)
 		'network' => $item['author-network'], 'url' => $item['author-link']];
 
 	// Only create a redirection to a magic link when logged in
-	if (!empty($item['plink']) && (local_user() || remote_user())) {
+	if (!empty($item['plink']) && Session::isAuthenticated()) {
 		$item['plink'] = Contact::magicLinkByContact($author, $item['plink']);
 	}
 }
@@ -1208,7 +1208,7 @@ function status_editor(App $a, $x, $notes_cid = 0, $popup = false)
 		'$new_post' => L10n::t('New Post'),
 		'$return_path'  => $query_str,
 		'$action'       => 'item',
-		'$share'        => defaults($x, 'button', L10n::t('Share')),
+		'$share'        => ($x['button'] ?? '') ?: L10n::t('Share'),
 		'$upload'       => L10n::t('Upload photo'),
 		'$shortupload'  => L10n::t('upload photo'),
 		'$attach'       => L10n::t('Attach file'),
@@ -1225,17 +1225,17 @@ function status_editor(App $a, $x, $notes_cid = 0, $popup = false)
 		'$shortsetloc'  => L10n::t('set location'),
 		'$noloc'        => L10n::t('Clear browser location'),
 		'$shortnoloc'   => L10n::t('clear location'),
-		'$title'        => defaults($x, 'title', ''),
+		'$title'        => $x['title'] ?? '',
 		'$placeholdertitle' => L10n::t('Set title'),
-		'$category'     => defaults($x, 'category', ''),
+		'$category'     => $x['category'] ?? '',
 		'$placeholdercategory' => Feature::isEnabled(local_user(), 'categories') ? L10n::t("Categories \x28comma-separated list\x29") : '',
 		'$wait'         => L10n::t('Please wait'),
 		'$permset'      => L10n::t('Permission settings'),
 		'$shortpermset' => L10n::t('permissions'),
 		'$wall'         => $notes_cid ? 0 : 1,
 		'$posttype'     => $notes_cid ? Item::PT_PERSONAL_NOTE : Item::PT_ARTICLE,
-		'$content'      => defaults($x, 'content', ''),
-		'$post_id'      => defaults($x, 'post_id', ''),
+		'$content'      => $x['content'] ?? '',
+		'$post_id'      => $x['post_id'] ?? '',
 		'$baseurl'      => System::baseUrl(true),
 		'$defloc'       => $x['default_location'],
 		'$visitor'      => $x['visitor'],
@@ -1527,9 +1527,9 @@ function get_responses(array $conv_responses, array $response_verbs, array $item
 	$ret = [];
 	foreach ($response_verbs as $v) {
 		$ret[$v] = [];
-		$ret[$v]['count'] = defaults($conv_responses[$v], $item['uri'], 0);
-		$ret[$v]['list']  = defaults($conv_responses[$v], $item['uri'] . '-l', []);
-		$ret[$v]['self']  = defaults($conv_responses[$v], $item['uri'] . '-self', '0');
+		$ret[$v]['count'] = $conv_responses[$v][$item['uri']] ?? 0;
+		$ret[$v]['list']  = $conv_responses[$v][$item['uri'] . '-l'] ?? [];
+		$ret[$v]['self']  = $conv_responses[$v][$item['uri'] . '-self'] ?? '0';
 		if (count($ret[$v]['list']) > MAX_LIKERS) {
 			$ret[$v]['list_part'] = array_slice($ret[$v]['list'], 0, MAX_LIKERS);
 			array_push($ret[$v]['list_part'], ''.$itemlink.'';
@@ -783,7 +783,7 @@ function check_item_notification($itemid, $uid, $defaulttype = "") {
 	$fields = ['id', 'mention', 'tag', 'parent', 'title', 'body',
 		'author-link', 'author-name', 'author-avatar', 'author-id',
 		'guid', 'parent-uri', 'uri', 'contact-id', 'network'];
-	$condition = ['id' => $itemid, 'gravity' => [GRAVITY_PARENT, GRAVITY_COMMENT]];
+	$condition = ['id' => $itemid, 'gravity' => [GRAVITY_PARENT, GRAVITY_COMMENT], 'deleted' => false];
 	$item = Item::selectFirstForUser($uid, $fields, $condition);
 	if (!DBA::isResult($item) || in_array($item['author-id'], $contacts)) {
 		return false;
@@ -840,7 +840,7 @@ function check_item_notification($itemid, $uid, $defaulttype = "") {
 
 	// Is it a post that the user had started?
 	$fields = ['ignored', 'mention'];
-	$thread = Item::selectFirstThreadForUser($params['uid'], $fields, ['iid' => $item["parent"]]);
+	$thread = Item::selectFirstThreadForUser($params['uid'], $fields, ['iid' => $item["parent"], 'deleted' => false]);
 
 	if ($thread['mention'] && !$thread['ignored'] && !isset($params["type"])) {
 		$params["type"] = NOTIFY_COMMENT;
@@ -848,7 +848,7 @@ function check_item_notification($itemid, $uid, $defaulttype = "") {
 	}
 
 	// And now we check for participation of one of our contacts in the thread
-	$condition = ['parent' => $item["parent"], 'author-id' => $contacts];
+	$condition = ['parent' => $item["parent"], 'author-id' => $contacts, 'deleted' => false];
 
 	if (!$thread['ignored'] && !isset($params["type"]) && Item::exists($condition)) {
 		$params["type"] = NOTIFY_COMMENT;
diff --git a/include/items.php b/include/items.php
index 25c857f11..3868db40a 100644
--- a/include/items.php
+++ b/include/items.php
@@ -13,6 +13,7 @@ use Friendica\Core\PConfig;
 use Friendica\Core\Protocol;
 use Friendica\Core\Renderer;
 use Friendica\Core\System;
+use Friendica\Core\Session;
 use Friendica\Database\DBA;
 use Friendica\Model\Item;
 use Friendica\Protocol\DFRN;
@@ -41,7 +42,7 @@ function add_page_info_data(array $data, $no_photos = false)
 		$data["type"] = "link";
 	}
 
-	$data["title"] = defaults($data, "title", "");
+	$data["title"] = $data["title"] ?? '';
 
 	if ((($data["type"] != "link") && ($data["type"] != "video") && ($data["type"] != "photo")) || ($data["title"] == $data["url"])) {
 		return "";
@@ -326,7 +327,7 @@ function drop_items(array $items)
 {
 	$uid = 0;
 
-	if (!local_user() && !remote_user()) {
+	if (!Session::isAuthenticated()) {
 		return;
 	}
 
@@ -362,14 +363,8 @@ function drop_item($id, $return = '')
 	$contact_id = 0;
 
 	// check if logged in user is either the author or owner of this item
-
-	if (!empty($_SESSION['remote'])) {
-		foreach ($_SESSION['remote'] as $visitor) {
-			if ($visitor['uid'] == $item['uid'] && $visitor['cid'] == $item['contact-id']) {
-				$contact_id = $visitor['cid'];
-				break;
-			}
-		}
+	if (Session::getRemoteContactID($item['uid']) == $item['contact-id']) {
+		$contact_id = $item['contact-id'];
 	}
 
 	if ((local_user() == $item['uid']) || $contact_id) {
diff --git a/include/text.php b/include/text.php
index 3d6bf6a56..2050e5702 100644
--- a/include/text.php
+++ b/include/text.php
@@ -122,7 +122,7 @@ function redir_private_images($a, &$item)
 			}
 
 			if ((local_user() == $item['uid']) && ($item['private'] == 1) && ($item['contact-id'] != $a->contact['id']) && ($item['network'] == Protocol::DFRN)) {
-				$img_url = 'redir?f=1&quiet=1&url=' . urlencode($mtch[1]) . '&conurl=' . urlencode($item['author-link']);
+				$img_url = 'redir/' . $item['contact-id'] . '?url=' . urlencode($mtch[1]);
 				$item['body'] = str_replace($mtch[0], '[img]' . $img_url . '[/img]', $item['body']);
 			}
 		}
diff --git a/index.php b/index.php
index 1e6439e03..5407532d4 100644
--- a/index.php
+++ b/index.php
@@ -13,6 +13,7 @@ if (!file_exists(__DIR__ . '/vendor/autoload.php')) {
 require __DIR__ . '/vendor/autoload.php';
 
 $dice = (new Dice())->addRules(include __DIR__ . '/static/dependencies.config.php');
+$dice = $dice->addRule(Friendica\App\Mode::class, ['call' => [['determineRunMode', [false, $_SERVER], Dice::CHAIN_CALL]]]);
 
 \Friendica\BaseObject::setDependencyInjection($dice);
 
diff --git a/library/OAuth1.php b/library/OAuth1.php
index 27ee090b1..e3ca24646 100644
--- a/library/OAuth1.php
+++ b/library/OAuth1.php
@@ -4,27 +4,30 @@
 /* Generic exception class
  */
 if (!class_exists('OAuthException', false)) {
-  class OAuthException extends Exception {
-    // pass
-  }
+  class OAuthException extends Exception
+  { }
 }
 
-class OAuthConsumer {
+class OAuthConsumer
+{
   public $key;
   public $secret;
 
-  function __construct($key, $secret, $callback_url=NULL) {
+  function __construct($key, $secret, $callback_url = NULL)
+  {
     $this->key = $key;
     $this->secret = $secret;
     $this->callback_url = $callback_url;
   }
 
-  function __toString() {
+  function __toString()
+  {
     return "OAuthConsumer[key=$this->key,secret=$this->secret]";
   }
 }
 
-class OAuthToken {
+class OAuthToken
+{
   // access tokens and request tokens
   public $key;
   public $secret;
@@ -37,7 +40,8 @@ class OAuthToken {
    * key = the token
    * secret = the token secret
    */
-  function __construct($key, $secret) {
+  function __construct($key, $secret)
+  {
     $this->key = $key;
     $this->secret = $secret;
   }
@@ -46,14 +50,16 @@ class OAuthToken {
    * generates the basic string serialization of a token that a server
    * would respond to request_token and access_token calls with
    */
-  function to_string() {
+  function to_string()
+  {
     return "oauth_token=" .
-           OAuthUtil::urlencode_rfc3986($this->key) .
-           "&oauth_token_secret=" .
-           OAuthUtil::urlencode_rfc3986($this->secret);
+      OAuthUtil::urlencode_rfc3986($this->key) .
+      "&oauth_token_secret=" .
+      OAuthUtil::urlencode_rfc3986($this->secret);
   }
 
-  function __toString() {
+  function __toString()
+  {
     return $this->to_string();
   }
 }
@@ -62,7 +68,8 @@ class OAuthToken {
  * A class for implementing a Signature Method
  * See section 9 ("Signing Requests") in the spec
  */
-abstract class OAuthSignatureMethod {
+abstract class OAuthSignatureMethod
+{
   /**
    * Needs to return the name of the Signature Method (ie HMAC-SHA1)
    * @return string
@@ -89,25 +96,29 @@ abstract class OAuthSignatureMethod {
    * @param string $signature
    * @return bool
    */
-  public function check_signature($request, $consumer, $token, $signature) {
+  public function check_signature($request, $consumer, $token, $signature)
+  {
     $built = $this->build_signature($request, $consumer, $token);
     return ($built == $signature);
   }
 }
 
 /**
- * The HMAC-SHA1 signature method uses the HMAC-SHA1 signature algorithm as defined in [RFC2104] 
- * where the Signature Base String is the text and the key is the concatenated values (each first 
- * encoded per Parameter Encoding) of the Consumer Secret and Token Secret, separated by an '&' 
+ * The HMAC-SHA1 signature method uses the HMAC-SHA1 signature algorithm as defined in [RFC2104]
+ * where the Signature Base String is the text and the key is the concatenated values (each first
+ * encoded per Parameter Encoding) of the Consumer Secret and Token Secret, separated by an '&'
  * character (ASCII code 38) even if empty.
  *   - Chapter 9.2 ("HMAC-SHA1")
  */
-class OAuthSignatureMethod_HMAC_SHA1 extends OAuthSignatureMethod {
-  function get_name() {
+class OAuthSignatureMethod_HMAC_SHA1 extends OAuthSignatureMethod
+{
+  function get_name()
+  {
     return "HMAC-SHA1";
   }
 
-  public function build_signature($request, $consumer, $token) {
+  public function build_signature($request, $consumer, $token)
+  {
     $base_string = $request->get_signature_base_string();
     $request->base_string = $base_string;
 
@@ -126,25 +137,28 @@ class OAuthSignatureMethod_HMAC_SHA1 extends OAuthSignatureMethod {
 }
 
 /**
- * The PLAINTEXT method does not provide any security protection and SHOULD only be used 
+ * The PLAINTEXT method does not provide any security protection and SHOULD only be used
  * over a secure channel such as HTTPS. It does not use the Signature Base String.
  *   - Chapter 9.4 ("PLAINTEXT")
  */
-class OAuthSignatureMethod_PLAINTEXT extends OAuthSignatureMethod {
-  public function get_name() {
+class OAuthSignatureMethod_PLAINTEXT extends OAuthSignatureMethod
+{
+  public function get_name()
+  {
     return "PLAINTEXT";
   }
 
   /**
-   * oauth_signature is set to the concatenated encoded values of the Consumer Secret and 
-   * Token Secret, separated by a '&' character (ASCII code 38), even if either secret is 
+   * oauth_signature is set to the concatenated encoded values of the Consumer Secret and
+   * Token Secret, separated by a '&' character (ASCII code 38), even if either secret is
    * empty. The result MUST be encoded again.
    *   - Chapter 9.4.1 ("Generating Signatures")
    *
    * Please note that the second encoding MUST NOT happen in the SignatureMethod, as
    * OAuthRequest handles this!
    */
-  public function build_signature($request, $consumer, $token) {
+  public function build_signature($request, $consumer, $token)
+  {
     $key_parts = array(
       $consumer->secret,
       ($token) ? $token->secret : ""
@@ -159,15 +173,17 @@ class OAuthSignatureMethod_PLAINTEXT extends OAuthSignatureMethod {
 }
 
 /**
- * The RSA-SHA1 signature method uses the RSASSA-PKCS1-v1_5 signature algorithm as defined in 
- * [RFC3447] section 8.2 (more simply known as PKCS#1), using SHA-1 as the hash function for 
- * EMSA-PKCS1-v1_5. It is assumed that the Consumer has provided its RSA public key in a 
- * verified way to the Service Provider, in a manner which is beyond the scope of this 
+ * The RSA-SHA1 signature method uses the RSASSA-PKCS1-v1_5 signature algorithm as defined in
+ * [RFC3447] section 8.2 (more simply known as PKCS#1), using SHA-1 as the hash function for
+ * EMSA-PKCS1-v1_5. It is assumed that the Consumer has provided its RSA public key in a
+ * verified way to the Service Provider, in a manner which is beyond the scope of this
  * specification.
  *   - Chapter 9.3 ("RSA-SHA1")
  */
-abstract class OAuthSignatureMethod_RSA_SHA1 extends OAuthSignatureMethod {
-  public function get_name() {
+abstract class OAuthSignatureMethod_RSA_SHA1 extends OAuthSignatureMethod
+{
+  public function get_name()
+  {
     return "RSA-SHA1";
   }
 
@@ -185,7 +201,8 @@ abstract class OAuthSignatureMethod_RSA_SHA1 extends OAuthSignatureMethod {
   // Either way should return a string representation of the certificate
   protected abstract function fetch_private_cert(&$request);
 
-  public function build_signature($request, $consumer, $token) {
+  public function build_signature($request, $consumer, $token)
+  {
     $base_string = $request->get_signature_base_string();
     $request->base_string = $base_string;
 
@@ -204,7 +221,8 @@ abstract class OAuthSignatureMethod_RSA_SHA1 extends OAuthSignatureMethod {
     return base64_encode($signature);
   }
 
-  public function check_signature($request, $consumer, $token, $signature) {
+  public function check_signature($request, $consumer, $token, $signature)
+  {
     $decoded_sig = base64_decode($signature);
 
     $base_string = $request->get_signature_base_string();
@@ -225,7 +243,8 @@ abstract class OAuthSignatureMethod_RSA_SHA1 extends OAuthSignatureMethod {
   }
 }
 
-class OAuthRequest {
+class OAuthRequest
+{
   private $parameters;
   private $http_method;
   private $http_url;
@@ -234,9 +253,10 @@ class OAuthRequest {
   public static $version = '1.0';
   public static $POST_INPUT = 'php://input';
 
-  function __construct($http_method, $http_url, $parameters=NULL) {
+  function __construct($http_method, $http_url, $parameters = NULL)
+  {
     @$parameters or $parameters = array();
-    $parameters = array_merge( OAuthUtil::parse_parameters(parse_url($http_url, PHP_URL_QUERY)), $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;
@@ -246,15 +266,16 @@ class OAuthRequest {
   /**
    * attempt to build up a request from what was passed to the server
    */
-  public static function from_request($http_method=NULL, $http_url=NULL, $parameters=NULL) {
+  public static function from_request($http_method = NULL, $http_url = NULL, $parameters = NULL)
+  {
     $scheme = (!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] != "on")
-              ? 'http'
-              : 'https';
+      ? 'http'
+      : 'https';
     @$http_url or $http_url = $scheme .
-                              '://' . $_SERVER['HTTP_HOST'] .
-                              ':' .
-                              $_SERVER['SERVER_PORT'] .
-                              $_SERVER['REQUEST_URI'];
+      '://' . $_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
@@ -270,10 +291,13 @@ class OAuthRequest {
 
       // It's a POST request of the proper content-type, so parse POST
       // parameters and add those overriding any duplicates from GET
-      if ($http_method == "POST"
-          && @strstr($request_headers["Content-Type"],
-                     "application/x-www-form-urlencoded")
-          ) {
+      if (
+        $http_method == "POST"
+        && @strstr(
+          $request_headers["Content-Type"],
+          "application/x-www-form-urlencoded"
+        )
+      ) {
         $post_data = OAuthUtil::parse_parameters(
           file_get_contents(self::$POST_INPUT)
         );
@@ -288,25 +312,27 @@ class OAuthRequest {
         );
         $parameters = array_merge($parameters, $header_parameters);
       }
-
     }
     // fix for friendica redirect system
-    
-    $http_url =  substr($http_url, 0, strpos($http_url,$parameters['pagename'])+strlen($parameters['pagename']));
-    unset( $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);
   }
 
   /**
    * pretty much a helper function to set up the request
    */
-  public static function from_consumer_and_token($consumer, $token, $http_method, $http_url, $parameters=NULL) {
+  public static function from_consumer_and_token($consumer, $token, $http_method, $http_url, $parameters = NULL)
+  {
     @$parameters or $parameters = array();
-    $defaults = array("oauth_version" => OAuthRequest::$version,
-                      "oauth_nonce" => OAuthRequest::generate_nonce(),
-                      "oauth_timestamp" => OAuthRequest::generate_timestamp(),
-                      "oauth_consumer_key" => $consumer->key);
+    $defaults = array(
+      "oauth_version" => OAuthRequest::$version,
+      "oauth_nonce" => OAuthRequest::generate_nonce(),
+      "oauth_timestamp" => OAuthRequest::generate_timestamp(),
+      "oauth_consumer_key" => $consumer->key
+    );
     if ($token)
       $defaults['oauth_token'] = $token->key;
 
@@ -315,7 +341,8 @@ class OAuthRequest {
     return new OAuthRequest($http_method, $http_url, $parameters);
   }
 
-  public function set_parameter($name, $value, $allow_duplicates = true) {
+  public function set_parameter($name, $value, $allow_duplicates = true)
+  {
     if ($allow_duplicates && isset($this->parameters[$name])) {
       // We have already added parameter(s) with this name, so add to the list
       if (is_scalar($this->parameters[$name])) {
@@ -330,15 +357,18 @@ class OAuthRequest {
     }
   }
 
-  public function get_parameter($name) {
+  public function get_parameter($name)
+  {
     return isset($this->parameters[$name]) ? $this->parameters[$name] : null;
   }
 
-  public function get_parameters() {
+  public function get_parameters()
+  {
     return $this->parameters;
   }
 
-  public function unset_parameter($name) {
+  public function unset_parameter($name)
+  {
     unset($this->parameters[$name]);
   }
 
@@ -346,7 +376,8 @@ class OAuthRequest {
    * The request parameters, sorted and concatenated into a normalized string.
    * @return string
    */
-  public function get_signable_parameters() {
+  public function get_signable_parameters()
+  {
     // Grab all parameters
     $params = $this->parameters;
 
@@ -366,7 +397,8 @@ class OAuthRequest {
    * and the parameters (normalized), each urlencoded
    * and the concated with &.
    */
-  public function get_signature_base_string() {
+  public function get_signature_base_string()
+  {
     $parts = array(
       $this->get_normalized_http_method(),
       $this->get_normalized_http_url(),
@@ -381,7 +413,8 @@ class OAuthRequest {
   /**
    * just uppercases the http method
    */
-  public function get_normalized_http_method() {
+  public function get_normalized_http_method()
+  {
     return strtoupper($this->http_method);
   }
 
@@ -389,7 +422,8 @@ class OAuthRequest {
    * parses the url and rebuilds it to be
    * scheme://host/path
    */
-  public function get_normalized_http_url() {
+  public function get_normalized_http_url()
+  {
     $parts = parse_url($this->http_url);
 
     $port = @$parts['port'];
@@ -400,7 +434,8 @@ class OAuthRequest {
     $port or $port = ($scheme == 'https') ? '443' : '80';
 
     if (($scheme == 'https' && $port != '443')
-        || ($scheme == 'http' && $port != '80')) {
+      || ($scheme == 'http' && $port != '80')
+    ) {
       $host = "$host:$port";
     }
     return "$scheme://$host$path";
@@ -409,11 +444,12 @@ class OAuthRequest {
   /**
    * builds a url usable for a GET request
    */
-  public function to_url() {
+  public function to_url()
+  {
     $post_data = $this->to_postdata();
     $out = $this->get_normalized_http_url();
     if ($post_data) {
-      $out .= '?'.$post_data;
+      $out .= '?' . $post_data;
     }
     return $out;
   }
@@ -421,9 +457,10 @@ class OAuthRequest {
   /**
    * builds the data one would send in a POST request
    */
-  public function to_postdata($raw = false) {
+  public function to_postdata($raw = false)
+  {
     if ($raw)
-      return($this->parameters);
+      return $this->parameters;
     else
       return OAuthUtil::build_http_query($this->parameters);
   }
@@ -431,15 +468,15 @@ class OAuthRequest {
   /**
    * builds the Authorization: header
    */
-  public function to_header($realm=null) {
+  public function to_header($realm = null)
+  {
     $first = true;
-	if($realm) {
+    if ($realm) {
       $out = 'Authorization: OAuth realm="' . OAuthUtil::urlencode_rfc3986($realm) . '"';
       $first = false;
     } else
       $out = 'Authorization: OAuth';
 
-    $total = array();
     foreach ($this->parameters as $k => $v) {
       if (substr($k, 0, 5) != "oauth") continue;
       if (is_array($v)) {
@@ -447,20 +484,22 @@ class OAuthRequest {
       }
       $out .= ($first) ? ' ' : ',';
       $out .= OAuthUtil::urlencode_rfc3986($k) .
-              '="' .
-              OAuthUtil::urlencode_rfc3986($v) .
-              '"';
+        '="' .
+        OAuthUtil::urlencode_rfc3986($v) .
+        '"';
       $first = false;
     }
     return $out;
   }
 
-  public function __toString() {
+  public function __toString()
+  {
     return $this->to_url();
   }
 
 
-  public function sign_request($signature_method, $consumer, $token) {
+  public function sign_request($signature_method, $consumer, $token)
+  {
     $this->set_parameter(
       "oauth_signature_method",
       $signature_method->get_name(),
@@ -470,7 +509,8 @@ class OAuthRequest {
     $this->set_parameter("oauth_signature", $signature, false);
   }
 
-  public function build_signature($signature_method, $consumer, $token) {
+  public function build_signature($signature_method, $consumer, $token)
+  {
     $signature = $signature_method->build_signature($this, $consumer, $token);
     return $signature;
   }
@@ -478,33 +518,35 @@ class OAuthRequest {
   /**
    * util function: current timestamp
    */
-  private static function generate_timestamp() {
+  private static function generate_timestamp()
+  {
     return time();
   }
 
   /**
    * util function: current nonce
    */
-  private static function generate_nonce() {
-    $mt = microtime();
-    $rand = mt_rand();
-
-    return md5($mt . $rand); // md5s look nicer than numbers
+  private static function generate_nonce()
+  {
+    return Friendica\Util\Strings::getRandomHex(32);
   }
 }
 
-class OAuthServer {
+class OAuthServer
+{
   protected $timestamp_threshold = 300; // in seconds, five minutes
   protected $version = '1.0';             // hi blaine
   protected $signature_methods = array();
 
   protected $data_store;
 
-  function __construct($data_store) {
+  function __construct($data_store)
+  {
     $this->data_store = $data_store;
   }
 
-  public function add_signature_method($signature_method) {
+  public function add_signature_method($signature_method)
+  {
     $this->signature_methods[$signature_method->get_name()] =
       $signature_method;
   }
@@ -515,7 +557,8 @@ class OAuthServer {
    * process a request_token request
    * returns the request token on success
    */
-  public function fetch_request_token(&$request) {
+  public function fetch_request_token(&$request)
+  {
     $this->get_version($request);
 
     $consumer = $this->get_consumer($request);
@@ -536,7 +579,8 @@ class OAuthServer {
    * process an access_token request
    * returns the access token on success
    */
-  public function fetch_access_token(&$request) {
+  public function fetch_access_token(&$request)
+  {
     $this->get_version($request);
 
     $consumer = $this->get_consumer($request);
@@ -556,22 +600,24 @@ class OAuthServer {
   /**
    * verify an api call, checks all the parameters
    */
-  public function verify_request(&$request) {
+  public function verify_request(&$request)
+  {
     $this->get_version($request);
     $consumer = $this->get_consumer($request);
     $token = $this->get_token($request, $consumer, "access");
     $this->check_signature($request, $consumer, $token);
-    return array($consumer, $token);
+    return [$consumer, $token];
   }
 
   // Internals from here
   /**
    * version 1
    */
-  private function get_version(&$request) {
+  private function get_version(&$request)
+  {
     $version = $request->get_parameter("oauth_version");
     if (!$version) {
-      // Service Providers MUST assume the protocol version to be 1.0 if this parameter is not present. 
+      // Service Providers MUST assume the protocol version to be 1.0 if this parameter is not present.
       // Chapter 7.0 ("Accessing Protected Ressources")
       $version = '1.0';
     }
@@ -584,9 +630,10 @@ class OAuthServer {
   /**
    * figure out the signature with some defaults
    */
-  private function get_signature_method(&$request) {
+  private function get_signature_method(&$request)
+  {
     $signature_method =
-        @$request->get_parameter("oauth_signature_method");
+      @$request->get_parameter("oauth_signature_method");
 
     if (!$signature_method) {
       // According to chapter 7 ("Accessing Protected Ressources") the signature-method
@@ -594,12 +641,14 @@ class OAuthServer {
       throw new OAuthException('No signature method parameter. This parameter is required');
     }
 
-    if (!in_array($signature_method,
-                  array_keys($this->signature_methods))) {
+    if (!in_array(
+      $signature_method,
+      array_keys($this->signature_methods)
+    )) {
       throw new OAuthException(
         "Signature method '$signature_method' not supported " .
-        "try one of the following: " .
-        implode(", ", array_keys($this->signature_methods))
+          "try one of the following: " .
+          implode(", ", array_keys($this->signature_methods))
       );
     }
     return $this->signature_methods[$signature_method];
@@ -608,7 +657,8 @@ class OAuthServer {
   /**
    * try to find the consumer for the provided request's consumer key
    */
-  private function get_consumer(&$request) {
+  private function get_consumer(&$request)
+  {
     $consumer_key = @$request->get_parameter("oauth_consumer_key");
     if (!$consumer_key) {
       throw new OAuthException("Invalid consumer key");
@@ -625,10 +675,13 @@ class OAuthServer {
   /**
    * try to find the token for the provided request's token key
    */
-  private function get_token(&$request, $consumer, $token_type="access") {
+  private function get_token(&$request, $consumer, $token_type = "access")
+  {
     $token_field = @$request->get_parameter('oauth_token');
     $token = $this->data_store->lookup_token(
-      $consumer, $token_type, $token_field
+      $consumer,
+      $token_type,
+      $token_field
     );
     if (!$token) {
       throw new OAuthException("Invalid $token_type token: $token_field");
@@ -640,7 +693,8 @@ class OAuthServer {
    * all-in-one function to check the signature on a request
    * should guess the signature method appropriately
    */
-  private function check_signature(&$request, $consumer, $token) {
+  private function check_signature(&$request, $consumer, $token)
+  {
     // this should probably be in a different method
     $timestamp = @$request->get_parameter('oauth_timestamp');
     $nonce = @$request->get_parameter('oauth_nonce');
@@ -657,7 +711,7 @@ class OAuthServer {
       $token,
       $signature
     );
-	
+
 
     if (!$valid_sig) {
       throw new OAuthException("Invalid signature");
@@ -667,12 +721,13 @@ class OAuthServer {
   /**
    * check that the timestamp is new enough
    */
-  private function check_timestamp($timestamp) {
-    if( ! $timestamp )
+  private function check_timestamp($timestamp)
+  {
+    if (!$timestamp)
       throw new OAuthException(
         'Missing timestamp parameter. The parameter is required'
       );
-    
+
     // verify that timestamp is recentish
     $now = time();
     if (abs($now - $timestamp) > $this->timestamp_threshold) {
@@ -685,8 +740,9 @@ class OAuthServer {
   /**
    * check that the nonce is not repeated
    */
-  private function check_nonce($consumer, $token, $nonce, $timestamp) {
-    if( ! $nonce )
+  private function check_nonce($consumer, $token, $nonce, $timestamp)
+  {
+    if (!$nonce)
       throw new OAuthException(
         'Missing nonce parameter. The parameter is required'
       );
@@ -702,65 +758,73 @@ class OAuthServer {
       throw new OAuthException("Nonce already used: $nonce");
     }
   }
-
 }
 
-class OAuthDataStore {
-  function lookup_consumer($consumer_key) {
+class OAuthDataStore
+{
+  function lookup_consumer($consumer_key)
+  {
     // implement me
   }
 
-  function lookup_token($consumer, $token_type, $token) {
+  function lookup_token($consumer, $token_type, $token)
+  {
     // implement me
   }
 
-  function lookup_nonce($consumer, $token, $nonce, $timestamp) {
+  function lookup_nonce($consumer, $token, $nonce, $timestamp)
+  {
     // implement me
   }
 
-  function new_request_token($consumer, $callback = null) {
+  function new_request_token($consumer, $callback = null)
+  {
     // return a new token attached to this consumer
   }
 
-  function new_access_token($token, $consumer, $verifier = null) {
+  function new_access_token($token, $consumer, $verifier = null)
+  {
     // return a new access token attached to this consumer
     // for the user associated with this token if the request token
     // is authorized
     // should also invalidate the request token
   }
-
 }
 
-class OAuthUtil {
-  public static function urlencode_rfc3986($input) {
-  if (is_array($input)) {
-    return array_map(array('OAuthUtil', 'urlencode_rfc3986'), $input);
-  } else if (is_scalar($input)) {
-    return str_replace(
-      '+',
-      ' ',
-      str_replace('%7E', '~', rawurlencode($input))
-    );
-  } else {
-    return '';
+class OAuthUtil
+{
+  public static function urlencode_rfc3986($input)
+  {
+    if (is_array($input)) {
+      return array_map(['OAuthUtil', 'urlencode_rfc3986'], $input);
+    } else if (is_scalar($input)) {
+      return str_replace(
+        '+',
+        ' ',
+        str_replace('%7E', '~', rawurlencode($input))
+      );
+    } else {
+      return '';
+    }
   }
-}
 
 
   // This decode function isn't taking into consideration the above
   // modifications to the encoding process. However, this method doesn't
   // seem to be used anywhere so leaving it as is.
-  public static function urldecode_rfc3986($string) {
+  public static function urldecode_rfc3986($string)
+  {
     return urldecode($string);
   }
 
   // Utility function for turning the Authorization: header into
   // parameters, has to do some unescaping
   // Can filter out any non-oauth parameters if needed (default behaviour)
-  public static function split_header($header, $only_allow_oauth_parameters = true) {
+  public static function split_header($header, $only_allow_oauth_parameters = true)
+  {
     $pattern = '/(([-_a-z]*)=("([^"]*)"|([^,]*)),?)/';
     $offset = 0;
-    $params = array();
+    $params = [];
     while (preg_match($pattern, $header, $matches, PREG_OFFSET_CAPTURE, $offset) > 0) {
       $match = $matches[0];
       $header_name = $matches[2][0];
@@ -779,7 +843,8 @@ class OAuthUtil {
   }
 
   // helper to try to sort out headers for people who aren't running apache
-  public static function get_headers() {
+  public static function get_headers()
+  {
     if (function_exists('apache_request_headers')) {
       // we need this to get the actual Authorization: header
       // because apache tends to tell us it doesn't exist
@@ -789,22 +854,22 @@ class OAuthUtil {
       // we always want the keys to be Cased-Like-This and arh()
       // returns the headers in the same case as they are in the
       // request
-      $out = array();
-      foreach( $headers AS $key => $value ) {
+      $out = [];
+      foreach ($headers as $key => $value) {
         $key = str_replace(
-            " ",
-            "-",
-            ucwords(strtolower(str_replace("-", " ", $key)))
-          );
+          " ",
+          "-",
+          ucwords(strtolower(str_replace("-", " ", $key)))
+        );
         $out[$key] = $value;
       }
     } else {
       // otherwise we don't have apache and are just going to have to hope
       // that $_SERVER actually contains what we need
-      $out = array();
-      if( isset($_SERVER['CONTENT_TYPE']) )
+      $out = [];
+      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) {
@@ -827,12 +892,13 @@ class OAuthUtil {
   // This function takes a input like a=b&a=c&d=e and returns the parsed
   // parameters like this
   // array('a' => array('b','c'), 'd' => 'e')
-  public static function parse_parameters( $input ) {
+  public static function parse_parameters($input)
+  {
     if (!isset($input) || !$input) return array();
 
     $pairs = explode('&', $input);
 
-    $parsed_parameters = array();
+    $parsed_parameters = [];
     foreach ($pairs as $pair) {
       $split = explode('=', $pair, 2);
       $parameter = OAuthUtil::urldecode_rfc3986($split[0]);
@@ -845,7 +911,7 @@ class OAuthUtil {
         if (is_scalar($parsed_parameters[$parameter])) {
           // This is the first duplicate, so transform scalar (string) into an array
           // so we can add the duplicates
-          $parsed_parameters[$parameter] = array($parsed_parameters[$parameter]);
+          $parsed_parameters[$parameter] = [$parsed_parameters[$parameter]];
         }
 
         $parsed_parameters[$parameter][] = $value;
@@ -856,7 +922,8 @@ class OAuthUtil {
     return $parsed_parameters;
   }
 
-  public static function build_http_query($params) {
+  public static function build_http_query($params)
+  {
     if (!$params) return '';
 
     // Urlencode both keys and values
@@ -868,7 +935,7 @@ class OAuthUtil {
     // Ref: Spec: 9.1.1 (1)
     uksort($params, 'strcmp');
 
-    $pairs = array();
+    $pairs = [];
     foreach ($params as $parameter => $value) {
       if (is_array($value)) {
         // If two or more parameters share the same name, they are sorted by their value
@@ -886,5 +953,3 @@ class OAuthUtil {
     return implode('&', $pairs);
   }
 }
-
-?>
diff --git a/mod/acl.php b/mod/acl.php
deleted file mode 100644
index 3649b03a3..000000000
--- a/mod/acl.php
+++ /dev/null
@@ -1,316 +0,0 @@
- 'acl', 'action' => 'content', 'subaction' => 'search', 'search' => $search, 'type' => $type, 'conversation' => $conv_id]);
-
-	if ($search != '') {
-		$sql_extra = "AND `name` LIKE '%%" . DBA::escape($search) . "%%'";
-		$sql_extra2 = "AND (`attag` LIKE '%%" . DBA::escape($search) . "%%' OR `name` LIKE '%%" . DBA::escape($search) . "%%' OR `nick` LIKE '%%" . DBA::escape($search) . "%%')";
-	} else {
-		/// @TODO Avoid these needless else blocks by putting variable-initialization atop of if()
-		$sql_extra = $sql_extra2 = '';
-	}
-
-	// count groups and contacts
-	$group_count = 0;
-	if ($type == '' || $type == 'g') {
-		$r = q("SELECT COUNT(*) AS g FROM `group` WHERE NOT `deleted` AND `uid` = %d $sql_extra",
-			intval(local_user())
-		);
-		$group_count = (int) $r[0]['g'];
-	}
-
-	$sql_extra2 .= ' ' . Widget::unavailableNetworks();
-
-	$contact_count = 0;
-	if ($type == '' || $type == 'c') {
-		// autocomplete for editor mentions
-		$r = q("SELECT COUNT(*) AS c FROM `contact`
-				WHERE `uid` = %d AND NOT `self` AND NOT `deleted`
-				AND NOT `blocked` AND NOT `pending` AND NOT `archive`
-				AND `notify` != '' $sql_extra2",
-			intval(local_user())
-		);
-		$contact_count = (int) $r[0]['c'];
-	} elseif ($type == 'f') {
-		// autocomplete for editor mentions of forums
-		$r = q("SELECT COUNT(*) AS c FROM `contact`
-				WHERE `uid` = %d AND NOT `self` AND NOT `deleted`
-				AND NOT `blocked` AND NOT `pending` AND NOT `archive`
-				AND (`forum` OR `prv`)
-				AND `notify` != '' $sql_extra2",
-			intval(local_user())
-		);
-		$contact_count = (int) $r[0]['c'];
-	} elseif ($type == 'm') {
-		// autocomplete for Private Messages
-		$r = q("SELECT COUNT(*) AS c FROM `contact`
-				WHERE `uid` = %d AND NOT `self` AND NOT `deleted`
-				AND NOT `blocked` AND NOT `pending` AND NOT `archive`
-				AND `network` IN ('%s', '%s', '%s') $sql_extra2",
-			intval(local_user()),
-			DBA::escape(Protocol::ACTIVITYPUB),
-			DBA::escape(Protocol::DFRN),
-			DBA::escape(Protocol::DIASPORA)
-		);
-		$contact_count = (int) $r[0]['c'];
-	} elseif ($type == 'a') {
-		// autocomplete for Contacts
-		$r = q("SELECT COUNT(*) AS c FROM `contact`
-				WHERE `uid` = %d AND NOT `self`
-				AND NOT `pending` AND NOT `deleted` $sql_extra2",
-			intval(local_user())
-		);
-		$contact_count = (int) $r[0]['c'];
-	}
-
-	$tot = $group_count + $contact_count;
-
-	$groups = [];
-	$contacts = [];
-
-	if ($type == '' || $type == 'g') {
-		/// @todo We should cache this query.
-		// This can be done when we can delete cache entries via wildcard
-		$r = q("SELECT `group`.`id`, `group`.`name`, GROUP_CONCAT(DISTINCT `group_member`.`contact-id` SEPARATOR ',') AS uids
-				FROM `group`
-				INNER JOIN `group_member` ON `group_member`.`gid`=`group`.`id`
-				WHERE NOT `group`.`deleted` AND `group`.`uid` = %d
-					$sql_extra
-				GROUP BY `group`.`name`, `group`.`id`
-				ORDER BY `group`.`name`
-				LIMIT %d,%d",
-			intval(local_user()),
-			intval($start),
-			intval($count)
-		);
-
-		foreach ($r as $g) {
-			$groups[] = [
-				'type'  => 'g',
-				'photo' => 'images/twopeople.png',
-				'name'  => htmlspecialchars($g['name']),
-				'id'    => intval($g['id']),
-				'uids'  => array_map('intval', explode(',', $g['uids'])),
-				'link'  => '',
-				'forum' => '0'
-			];
-		}
-		if ((count($groups) > 0) && ($search == '')) {
-			$groups[] = ['separator' => true];
-		}
-	}
-
-	$r = [];
-	if ($type == '') {
-		$r = q("SELECT `id`, `name`, `nick`, `micro`, `network`, `url`, `attag`, `addr`, `forum`, `prv`, (`prv` OR `forum`) AS `frm` FROM `contact`
-				WHERE `uid` = %d AND NOT `self` AND NOT `deleted` AND NOT `blocked` AND NOT `pending` AND NOT `archive` AND `notify` != ''
-				AND NOT (`network` IN ('%s', '%s'))
-				$sql_extra2
-				ORDER BY `name` ASC ",
-			intval(local_user()),
-			DBA::escape(Protocol::OSTATUS),
-			DBA::escape(Protocol::STATUSNET)
-		);
-	} elseif ($type == 'c') {
-		$r = q("SELECT `id`, `name`, `nick`, `micro`, `network`, `url`, `attag`, `addr`, `forum`, `prv` FROM `contact`
-				WHERE `uid` = %d AND NOT `self` AND NOT `deleted` AND NOT `blocked` AND NOT `pending` AND NOT `archive` AND `notify` != ''
-				AND NOT (`network` IN ('%s'))
-				$sql_extra2
-				ORDER BY `name` ASC ",
-			intval(local_user()),
-			DBA::escape(Protocol::STATUSNET)
-		);
-	} elseif ($type == 'f') {
-		$r = q("SELECT `id`, `name`, `nick`, `micro`, `network`, `url`, `attag`, `addr`, `forum`, `prv` FROM `contact`
-				WHERE `uid` = %d AND NOT `self` AND NOT `deleted` AND NOT `blocked` AND NOT `pending` AND NOT `archive` AND `notify` != ''
-				AND NOT (`network` IN ('%s'))
-				AND (`forum` OR `prv`)
-				$sql_extra2
-				ORDER BY `name` ASC ",
-			intval(local_user()),
-			DBA::escape(Protocol::STATUSNET)
-		);
-	} elseif ($type == 'm') {
-		$r = q("SELECT `id`, `name`, `nick`, `micro`, `network`, `url`, `attag`, `addr` FROM `contact`
-				WHERE `uid` = %d AND NOT `self` AND NOT `deleted` AND NOT `blocked` AND NOT `pending` AND NOT `archive`
-				AND `network` IN ('%s', '%s', '%s')
-				$sql_extra2
-				ORDER BY `name` ASC ",
-			intval(local_user()),
-			DBA::escape(Protocol::ACTIVITYPUB),
-			DBA::escape(Protocol::DFRN),
-			DBA::escape(Protocol::DIASPORA)
-		);
-	} elseif ($type == 'a') {
-		$r = q("SELECT `id`, `name`, `nick`, `micro`, `network`, `url`, `attag`, `addr`, `forum`, `prv` FROM `contact`
-				WHERE `uid` = %d AND NOT `deleted` AND NOT `pending` AND NOT `archive`
-				$sql_extra2
-				ORDER BY `name` ASC ",
-			intval(local_user())
-		);
-	} elseif ($type == 'x') {
-		// autocomplete for global contact search (e.g. navbar search)
-		$search = Strings::escapeTags(trim($_REQUEST['search']));
-		$mode = $_REQUEST['smode'];
-
-		$r = ACL::contactAutocomplete($search, $mode);
-
-		$contacts = [];
-		foreach ($r as $g) {
-			$contacts[] = [
-				'photo'   => ProxyUtils::proxifyUrl($g['photo'], false, ProxyUtils::SIZE_MICRO),
-				'name'    => htmlspecialchars($g['name']),
-				'nick'    => defaults($g, 'addr', $g['url']),
-				'network' => $g['network'],
-				'link'    => $g['url'],
-				'forum'   => !empty($g['community']) ? 1 : 0,
-			];
-		}
-		$o = [
-			'start' => $start,
-			'count' => $count,
-			'items' => $contacts,
-		];
-		echo json_encode($o);
-		exit;
-	}
-
-	if (DBA::isResult($r)) {
-		$forums = [];
-		foreach ($r as $g) {
-			$entry = [
-				'type'    => 'c',
-				'photo'   => ProxyUtils::proxifyUrl($g['micro'], false, ProxyUtils::SIZE_MICRO),
-				'name'    => htmlspecialchars($g['name']),
-				'id'      => intval($g['id']),
-				'network' => $g['network'],
-				'link'    => $g['url'],
-				'nick'    => htmlentities(defaults($g, 'attag', $g['nick'])),
-				'addr'    => htmlentities(defaults($g, 'addr', $g['url'])),
-				'forum'   => !empty($g['forum']) || !empty($g['prv']) ? 1 : 0,
-			];
-			if ($entry['forum']) {
-				$forums[] = $entry;
-			} else {
-				$contacts[] = $entry;
-			}
-		}
-		if (count($forums) > 0) {
-			if ($search == '') {
-				$forums[] = ['separator' => true];
-			}
-			$contacts = array_merge($forums, $contacts);
-		}
-	}
-
-	$items = array_merge($groups, $contacts);
-
-	if ($conv_id) {
-		// In multi threaded posts the conv_id is not the parent of the whole thread
-		$parent_item = Item::selectFirst(['parent'], ['id' => $conv_id]);
-		if (DBA::isResult($parent_item)) {
-			$conv_id = $parent_item['parent'];
-		}
-
-		/*
-		 * if $conv_id is set, get unknown contacts in thread
-		 * but first get known contacts url to filter them out
-		 */
-		$known_contacts = array_map(function ($i) {
-			return $i['link'];
-		}, $contacts);
-
-		$unknown_contacts = [];
-
-		$condition = ["`parent` = ?", $conv_id];
-		$params = ['order' => ['author-name' => true]];
-		$authors = Item::selectForUser(local_user(), ['author-link'], $condition, $params);
-		$item_authors = [];
-		while ($author = Item::fetch($authors)) {
-			$item_authors[$author['author-link']] = $author['author-link'];
-		}
-		DBA::close($authors);
-
-		foreach ($item_authors as $author) {
-			if (in_array($author, $known_contacts)) {
-				continue;
-			}
-
-			$contact = Contact::getDetailsByURL($author);
-
-			if (count($contact) > 0) {
-				$unknown_contacts[] = [
-					'type'    => 'c',
-					'photo'   => ProxyUtils::proxifyUrl($contact['micro'], false, ProxyUtils::SIZE_MICRO),
-					'name'    => htmlspecialchars($contact['name']),
-					'id'      => intval($contact['cid']),
-					'network' => $contact['network'],
-					'link'    => $contact['url'],
-					'nick'    => htmlentities(defaults($contact, 'nick', $contact['addr'])),
-					'addr'    => htmlentities(defaults($contact, 'addr', $contact['url'])),
-					'forum'   => $contact['forum']
-				];
-			}
-		}
-
-		$items = array_merge($items, $unknown_contacts);
-		$tot += count($unknown_contacts);
-	}
-
-	$results = [
-		'tot'      => $tot,
-		'start'    => $start,
-		'count'    => $count,
-		'groups'   => $groups,
-		'contacts' => $contacts,
-		'items'    => $items,
-		'type'     => $type,
-		'search'   => $search,
-	];
-
-	Hook::callAll('acl_lookup_end', $results);
-
-	$o = [
-		'tot'   => $results['tot'],
-		'start' => $results['start'],
-		'count' => $results['count'],
-		'items' => $results['items'],
-	];
-
-	echo json_encode($o);
-	exit;
-}
diff --git a/mod/api.php b/mod/api.php
index 4a1db1be5..9a802b515 100644
--- a/mod/api.php
+++ b/mod/api.php
@@ -2,6 +2,7 @@
 /**
  * @file mod/api.php
  */
+
 use Friendica\App;
 use Friendica\Core\Config;
 use Friendica\Core\L10n;
@@ -9,7 +10,7 @@ use Friendica\Core\Renderer;
 use Friendica\Database\DBA;
 use Friendica\Module\Login;
 
-require_once 'include/api.php';
+require_once __DIR__ . '/../include/api.php';
 
 function oauth_get_client(OAuthRequest $request)
 {
diff --git a/mod/cal.php b/mod/cal.php
index 05ad314b0..51d17a10a 100644
--- a/mod/cal.php
+++ b/mod/cal.php
@@ -14,6 +14,7 @@ use Friendica\Core\Config;
 use Friendica\Core\L10n;
 use Friendica\Core\Renderer;
 use Friendica\Core\System;
+use Friendica\Core\Session;
 use Friendica\Database\DBA;
 use Friendica\Model\Contact;
 use Friendica\Model\Event;
@@ -26,11 +27,7 @@ use Friendica\Util\Temporal;
 
 function cal_init(App $a)
 {
-	if ($a->argc > 1) {
-		DFRN::autoRedir($a, $a->argv[1]);
-	}
-
-	if (Config::get('system', 'block_public') && !local_user() && !remote_user()) {
+	if (Config::get('system', 'block_public') && !Session::isAuthenticated()) {
 		throw new \Friendica\Network\HTTPException\ForbiddenException(L10n::t('Access denied.'));
 	}
 
@@ -113,18 +110,11 @@ function cal_content(App $a)
 	$owner_uid = intval($a->data['user']['uid']);
 	$nick = $a->data['user']['nickname'];
 
-	if (!empty($_SESSION['remote']) && is_array($_SESSION['remote'])) {
-		foreach ($_SESSION['remote'] as $v) {
-			if ($v['uid'] == $a->profile['profile_uid']) {
-				$contact_id = $v['cid'];
-				break;
-			}
-		}
+	if (!empty(Session::getRemoteContactID($a->profile['profile_uid']))) {
+		$contact_id = Session::getRemoteContactID($a->profile['profile_uid']);
 	}
 
-	$groups = [];
 	if ($contact_id) {
-		$groups = Group::getIdsByContactId($contact_id);
 		$r = q("SELECT * FROM `contact` WHERE `id` = %d AND `uid` = %d LIMIT 1",
 			intval($contact_id),
 			intval($a->profile['profile_uid'])
@@ -142,7 +132,7 @@ function cal_content(App $a)
 	}
 
 	// get the permissions
-	$sql_perms = Item::getPermissionsSQLByUserId($owner_uid, $remote_contact, $groups);
+	$sql_perms = Item::getPermissionsSQLByUserId($owner_uid);
 	// we only want to have the events of the profile owner
 	$sql_extra = " AND `event`.`cid` = 0 " . $sql_perms;
 
@@ -210,7 +200,7 @@ function cal_content(App $a)
 
 		// put the event parametes in an array so we can better transmit them
 		$event_params = [
-			'event_id'      => intval(defaults($_GET, 'id', 0)),
+			'event_id'      => intval($_GET['id'] ?? 0),
 			'start'         => $start,
 			'finish'        => $finish,
 			'adjust_start'  => $adjust_start,
diff --git a/mod/common.php b/mod/common.php
index a2821921c..9d441f18a 100644
--- a/mod/common.php
+++ b/mod/common.php
@@ -118,7 +118,7 @@ function common_content(App $a)
 
 		$entry = [
 			'url'          => Model\Contact::magicLink($common_friend['url']),
-			'itemurl'      => defaults($contact_details, 'addr', $common_friend['url']),
+			'itemurl'      => ($contact_details['addr'] ?? '') ?: $common_friend['url'],
 			'name'         => $contact_details['name'],
 			'thumb'        => ProxyUtils::proxifyUrl($contact_details['thumb'], false, ProxyUtils::SIZE_THUMB),
 			'img_hover'    => $contact_details['name'],
diff --git a/mod/community.php b/mod/community.php
index 5ffb00729..81857c6d3 100644
--- a/mod/community.php
+++ b/mod/community.php
@@ -17,19 +17,11 @@ use Friendica\Database\DBA;
 use Friendica\Model\Item;
 use Friendica\Model\User;
 
-function community_init(App $a)
-{
-	if (!local_user()) {
-		unset($_SESSION['theme']);
-		unset($_SESSION['mobile-theme']);
-	}
-}
-
 function community_content(App $a, $update = 0)
 {
 	$o = '';
 
-	if (Config::get('system', 'block_public') && !local_user() && !remote_user()) {
+	if (Config::get('system', 'block_public') && !Session::isAuthenticated()) {
 		notice(L10n::t('Public access denied.') . EOL);
 		return;
 	}
@@ -227,6 +219,7 @@ function community_getitems($start, $itemspage, $content, $accounttype)
 			$values = [$start, $itemspage];
 		}
 
+		/// @todo Use "unsearchable" here as well (instead of "hidewall")
 		$r = DBA::p("SELECT `item`.`uri`, `author`.`url` AS `author-link` FROM `thread`
 			STRAIGHT_JOIN `user` ON `user`.`uid` = `thread`.`uid` AND NOT `user`.`hidewall`
 			STRAIGHT_JOIN `item` ON `item`.`id` = `thread`.`iid`
@@ -237,9 +230,9 @@ function community_getitems($start, $itemspage, $content, $accounttype)
 		return DBA::toArray($r);
 	} elseif ($content == 'global') {
 		if (!is_null($accounttype)) {
-			$condition = ["`uid` = ? AND `owner`.`contact-type` = ?", 0, $accounttype];
+			$condition = ["`uid` = ? AND NOT `author`.`unsearchable` AND NOT `owner`.`unsearchable` AND `owner`.`contact-type` = ?", 0, $accounttype];
 		} else {
-			$condition = ['uid' => 0];
+			$condition = ["`uid` = ? AND NOT `author`.`unsearchable` AND NOT `owner`.`unsearchable`", 0];
 		}
 
 		$r = Item::selectThreadForUser(0, ['uri'], $condition, ['order' => ['commented' => true], 'limit' => [$start, $itemspage]]);
diff --git a/mod/crepair.php b/mod/crepair.php
index ce27b4498..84cb458fa 100644
--- a/mod/crepair.php
+++ b/mod/crepair.php
@@ -38,17 +38,17 @@ function crepair_post(App $a)
 		return;
 	}
 
-	$name        = defaults($_POST, 'name'       , $contact['name']);
-	$nick        = defaults($_POST, 'nick'       , '');
-	$url         = defaults($_POST, 'url'        , '');
-	$alias       = defaults($_POST, 'alias'      , '');
-	$request     = defaults($_POST, 'request'    , '');
-	$confirm     = defaults($_POST, 'confirm'    , '');
-	$notify      = defaults($_POST, 'notify'     , '');
-	$poll        = defaults($_POST, 'poll'       , '');
-	$attag       = defaults($_POST, 'attag'      , '');
-	$photo       = defaults($_POST, 'photo'      , '');
-	$remote_self = defaults($_POST, 'remote_self', false);
+	$name        = ($_POST['name']        ?? '') ?: $contact['name'];
+	$nick        =  $_POST['nick']        ?? '';
+	$url         =  $_POST['url']         ?? '';
+	$alias       =  $_POST['alias']       ?? '';
+	$request     =  $_POST['request']     ?? '';
+	$confirm     =  $_POST['confirm']     ?? '';
+	$notify      =  $_POST['notify']      ?? '';
+	$poll        =  $_POST['poll']        ?? '';
+	$attag       =  $_POST['attag']       ?? '';
+	$photo       =  $_POST['photo']       ?? '';
+	$remote_self =  $_POST['remote_self'] ?? false;
 	$nurl        = Strings::normaliseLink($url);
 
 	$r = DBA::update(
diff --git a/mod/delegate.php b/mod/delegate.php
deleted file mode 100644
index 456078451..000000000
--- a/mod/delegate.php
+++ /dev/null
@@ -1,191 +0,0 @@
-user) && !empty($a->user['uid']) && $a->user['uid'] != local_user()) {
-		notice(L10n::t('Permission denied.') . EOL);
-		return;
-	}
-
-	BaseModule::checkFormSecurityTokenRedirectOnError('/delegate', 'delegate');
-
-	$parent_uid = defaults($_POST, 'parent_user', 0);
-	$parent_password = defaults($_POST, 'parent_password', '');
-
-	if ($parent_uid != 0) {
-		$user = DBA::selectFirst('user', ['nickname'], ['uid' => $parent_uid]);
-		if (!DBA::isResult($user)) {
-			notice(L10n::t('Parent user not found.') . EOL);
-			return;
-		}
-
-		$success = User::authenticate($user['nickname'], trim($parent_password));
-		if (!$success) {
-			notice(L10n::t('Permission denied.') . EOL);
-			return;
-		}
-	}
-
-	DBA::update('user', ['parent-uid' => $parent_uid], ['uid' => local_user()]);
-}
-
-function delegate_content(App $a)
-{
-	if (!local_user()) {
-		notice(L10n::t('Permission denied.') . EOL);
-		return;
-	}
-
-	if ($a->argc > 2 && $a->argv[1] === 'add' && intval($a->argv[2])) {
-		// delegated admins can view but not change delegation permissions
-		if (!empty($_SESSION['submanage'])) {
-			$a->internalRedirect('delegate');
-		}
-
-		$user_id = $a->argv[2];
-
-		$user = DBA::selectFirst('user', ['nickname'], ['uid' => $user_id]);
-		if (DBA::isResult($user)) {
-			$condition = [
-				'uid' => local_user(),
-				'nurl' => Strings::normaliseLink(System::baseUrl() . '/profile/' . $user['nickname'])
-			];
-			if (DBA::exists('contact', $condition)) {
-				DBA::insert('manage', ['uid' => $user_id, 'mid' => local_user()]);
-			}
-		}
-		$a->internalRedirect('delegate');
-	}
-
-	if ($a->argc > 2 && $a->argv[1] === 'remove' && intval($a->argv[2])) {
-		// delegated admins can view but not change delegation permissions
-		if (!empty($_SESSION['submanage'])) {
-			$a->internalRedirect('delegate');
-		}
-
-		DBA::delete('manage', ['uid' => $a->argv[2], 'mid' => local_user()]);
-		$a->internalRedirect('delegate');
-	}
-
-	// find everybody that currently has delegated management to this account/page
-	$delegates = [];
-	$r = q("SELECT * FROM `user` WHERE `uid` IN (SELECT `uid` FROM `manage` WHERE `mid` = %d)",
-		intval(local_user())
-	);
-	if (DBA::isResult($r)) {
-		$delegates = $r;
-	}
-
-	$uids = [];
-	foreach ($delegates as $rr) {
-		$uids[] = $rr['uid'];
-	}
-
-	// find every contact who might be a candidate for delegation
-	$potentials = [];
-
-	$r = q("SELECT `nurl`
-		FROM `contact`
-		WHERE `self` = 0
-		AND SUBSTRING_INDEX(`nurl`, '/', 3) = '%s'
-		AND `uid` = %d
-		AND `network` = '%s' ",
-		DBA::escape(Strings::normaliseLink(System::baseUrl())),
-		intval(local_user()),
-		DBA::escape(Protocol::DFRN)
-	);
-	if (DBA::isResult($r)) {
-		$nicknames = [];
-		foreach ($r as $rr) {
-			$nicknames[] = "'" . DBA::escape(basename($rr['nurl'])) . "'";
-		}
-
-		$nicks = implode(',', $nicknames);
-
-		// get user records for all potential page delegates who are not already delegates or managers
-		$r = q("SELECT `uid`, `username`, `nickname` FROM `user` WHERE `nickname` IN ($nicks)");
-		if (DBA::isResult($r)) {
-			foreach ($r as $rr) {
-				if (!in_array($rr['uid'], $uids)) {
-					$potentials[] = $rr;
-				}
-			}
-		}
-	}
-
-	settings_init($a);
-
-	$user = DBA::selectFirst('user', ['parent-uid', 'email'], ['uid' => local_user()]);
-
-	$parent_user = null;
-
-	if (DBA::isResult($user)) {
-		if (!DBA::exists('user', ['parent-uid' => local_user()])) {
-			$parent_uid = $user['parent-uid'];
-			$parents = [0 => L10n::t('No parent user')];
-
-			$fields = ['uid', 'username', 'nickname'];
-			$condition = ['email' => $user['email'], 'verified' => true, 'blocked' => false, 'parent-uid' => 0];
-			$parent_users = DBA::select('user', $fields, $condition);
-			while ($parent = DBA::fetch($parent_users)) {
-				if ($parent['uid'] != local_user()) {
-					$parents[$parent['uid']] = sprintf('%s (%s)', $parent['username'], $parent['nickname']);
-				}
-			}
-			$parent_user = ['parent_user', '', $parent_uid, '', $parents];
-		}
-	}
-
-	if (!is_null($parent_user)) {
-		$parent_password = ['parent_password', L10n::t('Parent Password:'), '', L10n::t('Please enter the password of the parent account to legitimize your request.')];
-	} else {
-		$parent_password = '';
-	}
-
-	$o = Renderer::replaceMacros(Renderer::getMarkupTemplate('delegate.tpl'), [
-		'$form_security_token' => BaseModule::getFormSecurityToken('delegate'),
-		'$parent_header' => L10n::t('Parent User'),
-		'$parent_user' => $parent_user,
-		'$parent_password' => $parent_password,
-		'$parent_desc' => L10n::t('Parent users have total control about this account, including the account settings. Please double check whom you give this access.'),
-		'$submit' => L10n::t('Save Settings'),
-		'$header' => L10n::t('Delegate Page Management'),
-		'$delegates_header' => L10n::t('Delegates'),
-		'$base' => System::baseUrl(),
-		'$desc' => L10n::t('Delegates are able to manage all aspects of this account/page except for basic account settings. Please do not delegate your personal account to anybody that you do not trust completely.'),
-		'$head_delegates' => L10n::t('Existing Page Delegates'),
-		'$delegates' => $delegates,
-		'$head_potentials' => L10n::t('Potential Delegates'),
-		'$potentials' => $potentials,
-		'$remove' => L10n::t('Remove'),
-		'$add' => L10n::t('Add'),
-		'$none' => L10n::t('No entries.')
-	]);
-
-
-	return $o;
-}
diff --git a/mod/dfrn_confirm.php b/mod/dfrn_confirm.php
index 9f9684e09..944ba98be 100644
--- a/mod/dfrn_confirm.php
+++ b/mod/dfrn_confirm.php
@@ -59,7 +59,7 @@ function dfrn_confirm_post(App $a, $handsfree = null)
 	 * since we are operating on behalf of our registered user to approve a friendship.
 	 */
 	if (empty($_POST['source_url'])) {
-		$uid = defaults($handsfree, 'uid', local_user());
+		$uid = ($handsfree['uid'] ?? 0) ?: local_user();
 		if (!$uid) {
 			notice(L10n::t('Permission denied.') . EOL);
 			return;
@@ -78,13 +78,13 @@ function dfrn_confirm_post(App $a, $handsfree = null)
 			$intro_id = $handsfree['intro_id'];
 			$duplex   = $handsfree['duplex'];
 			$cid      = 0;
-			$hidden   = intval(defaults($handsfree, 'hidden'  , 0));
+			$hidden   = intval($handsfree['hidden'] ?? 0);
 		} else {
-			$dfrn_id  = Strings::escapeTags(trim(defaults($_POST, 'dfrn_id'   , '')));
-			$intro_id =      intval(defaults($_POST, 'intro_id'  , 0));
-			$duplex   =      intval(defaults($_POST, 'duplex'    , 0));
-			$cid      =      intval(defaults($_POST, 'contact_id', 0));
-			$hidden   =      intval(defaults($_POST, 'hidden'    , 0));
+			$dfrn_id  = Strings::escapeTags(trim($_POST['dfrn_id'] ?? ''));
+			$intro_id = intval($_POST['intro_id']   ?? 0);
+			$duplex   = intval($_POST['duplex']     ?? 0);
+			$cid      = intval($_POST['contact_id'] ?? 0);
+			$hidden   = intval($_POST['hidden']     ?? 0);
 		}
 
 		/*
@@ -347,12 +347,12 @@ function dfrn_confirm_post(App $a, $handsfree = null)
 	 */
 	if (!empty($_POST['source_url'])) {
 		// We are processing an external confirmation to an introduction created by our user.
-		$public_key =         defaults($_POST, 'public_key', '');
-		$dfrn_id    = hex2bin(defaults($_POST, 'dfrn_id'   , ''));
-		$source_url = hex2bin(defaults($_POST, 'source_url', ''));
-		$aes_key    =         defaults($_POST, 'aes_key'   , '');
-		$duplex     =  intval(defaults($_POST, 'duplex'    , 0));
-		$page       =  intval(defaults($_POST, 'page'      , 0));
+		$public_key =         $_POST['public_key'] ?? '';
+		$dfrn_id    = hex2bin($_POST['dfrn_id']    ?? '');
+		$source_url = hex2bin($_POST['source_url'] ?? '');
+		$aes_key    =         $_POST['aes_key']    ?? '';
+		$duplex     =  intval($_POST['duplex']     ?? 0);
+		$page       =  intval($_POST['page']       ?? 0);
 
 		$forum = (($page == 1) ? 1 : 0);
 		$prv   = (($page == 2) ? 1 : 0);
diff --git a/mod/dfrn_notify.php b/mod/dfrn_notify.php
index 3f0ecba00..dee6bad77 100644
--- a/mod/dfrn_notify.php
+++ b/mod/dfrn_notify.php
@@ -26,7 +26,7 @@ function dfrn_notify_post(App $a) {
 	if (empty($_POST) || !empty($postdata)) {
 		$data = json_decode($postdata);
 		if (is_object($data)) {
-			$nick = defaults($a->argv, 1, '');
+			$nick = $a->argv[1] ?? '';
 
 			$user = DBA::selectFirst('user', [], ['nickname' => $nick, 'account_expired' => false, 'account_removed' => false]);
 			if (!DBA::isResult($user)) {
@@ -42,8 +42,8 @@ function dfrn_notify_post(App $a) {
 	$dfrn_id      = (!empty($_POST['dfrn_id'])      ? Strings::escapeTags(trim($_POST['dfrn_id']))   : '');
 	$dfrn_version = (!empty($_POST['dfrn_version']) ? (float) $_POST['dfrn_version']    : 2.0);
 	$challenge    = (!empty($_POST['challenge'])    ? Strings::escapeTags(trim($_POST['challenge'])) : '');
-	$data         = defaults($_POST, 'data', '');
-	$key          = defaults($_POST, 'key', '');
+	$data         = $_POST['data'] ?? '';
+	$key          = $_POST['key'] ?? '';
 	$rino_remote  = (!empty($_POST['rino'])         ? intval($_POST['rino'])            :  0);
 	$dissolve     = (!empty($_POST['dissolve'])     ? intval($_POST['dissolve'])        :  0);
 	$perm         = (!empty($_POST['perm'])         ? Strings::escapeTags(trim($_POST['perm']))      : 'r');
diff --git a/mod/dfrn_poll.php b/mod/dfrn_poll.php
index 6c849cb80..ca60cc87a 100644
--- a/mod/dfrn_poll.php
+++ b/mod/dfrn_poll.php
@@ -9,6 +9,7 @@ use Friendica\Core\Config;
 use Friendica\Core\L10n;
 use Friendica\Core\Logger;
 use Friendica\Core\System;
+use Friendica\Core\Session;
 use Friendica\Database\DBA;
 use Friendica\Module\Login;
 use Friendica\Protocol\DFRN;
@@ -21,17 +22,17 @@ function dfrn_poll_init(App $a)
 {
 	Login::sessionAuth();
 
-	$dfrn_id         = defaults($_GET, 'dfrn_id'        , '');
-	$type            = defaults($_GET, 'type'           , 'data');
-	$last_update     = defaults($_GET, 'last_update'    , '');
-	$destination_url = defaults($_GET, 'destination_url', '');
-	$challenge       = defaults($_GET, 'challenge'      , '');
-	$sec             = defaults($_GET, 'sec'            , '');
-	$dfrn_version    = (float) defaults($_GET, 'dfrn_version'   , 2.0);
+	$dfrn_id         =  $_GET['dfrn_id']         ?? '';
+	$type            = ($_GET['type']            ?? '') ?: 'data';
+	$last_update     =  $_GET['last_update']     ?? '';
+	$destination_url =  $_GET['destination_url'] ?? '';
+	$challenge       =  $_GET['challenge']       ?? '';
+	$sec             =  $_GET['sec']             ?? '';
+	$dfrn_version    = floatval(($_GET['dfrn_version'] ?? 0.0) ?: 2.0);
 	$quiet			 = !empty($_GET['quiet']);
 
 	// Possibly it is an OStatus compatible server that requests a user feed
-	$user_agent = defaults($_SERVER, 'HTTP_USER_AGENT', '');
+	$user_agent = $_SERVER['HTTP_USER_AGENT'] ?? '';
 	if (($a->argc > 1) && ($dfrn_id == '') && !strstr($user_agent, 'Friendica')) {
 		$nickname = $a->argv[1];
 		header("Content-type: application/atom+xml");
@@ -49,7 +50,7 @@ function dfrn_poll_init(App $a)
 	$hidewall = false;
 
 	if (($dfrn_id === '') && empty($_POST['dfrn_id'])) {
-		if (Config::get('system', 'block_public') && !local_user() && !remote_user()) {
+		if (Config::get('system', 'block_public') && !Session::isAuthenticated()) {
 			throw new \Friendica\Network\HTTPException\ForbiddenException();
 		}
 
@@ -110,17 +111,14 @@ function dfrn_poll_init(App $a)
 
 				if ((int)$xml->status === 1) {
 					$_SESSION['authenticated'] = 1;
-					if (empty($_SESSION['remote'])) {
-						$_SESSION['remote'] = [];
-					}
-
-					$_SESSION['remote'][] = ['cid' => $r[0]['id'], 'uid' => $r[0]['uid'], 'url' => $r[0]['url']];
-
 					$_SESSION['visitor_id'] = $r[0]['id'];
 					$_SESSION['visitor_home'] = $r[0]['url'];
 					$_SESSION['visitor_handle'] = $r[0]['addr'];
 					$_SESSION['visitor_visiting'] = $r[0]['uid'];
 					$_SESSION['my_url'] = $r[0]['url'];
+
+					Session::setVisitorsContacts();
+
 					if (!$quiet) {
 						info(L10n::t('%1$s welcomes %2$s', $r[0]['username'], $r[0]['name']) . EOL);
 					}
@@ -227,13 +225,13 @@ function dfrn_poll_init(App $a)
 
 function dfrn_poll_post(App $a)
 {
-	$dfrn_id      = defaults($_POST, 'dfrn_id'  , '');
-	$challenge    = defaults($_POST, 'challenge', '');
-	$url          = defaults($_POST, 'url'      , '');
-	$sec          = defaults($_POST, 'sec'      , '');
-	$ptype        = defaults($_POST, 'type'     , '');
-	$perm         = defaults($_POST, 'perm'     , 'r');
-	$dfrn_version = !empty($_POST['dfrn_version']) ? (float) $_POST['dfrn_version'] : 2.0;
+	$dfrn_id      =  $_POST['dfrn_id']   ?? '';
+	$challenge    =  $_POST['challenge'] ?? '';
+	$url          =  $_POST['url']       ?? '';
+	$sec          =  $_POST['sec']       ?? '';
+	$ptype        =  $_POST['type']      ?? '';
+	$perm         = ($_POST['perm']      ?? '') ?: 'r';
+	$dfrn_version = floatval(($_GET['dfrn_version'] ?? 0.0) ?: 2.0);
 
 	if ($ptype === 'profile-check') {
 		if (strlen($challenge) && strlen($sec)) {
@@ -393,12 +391,12 @@ function dfrn_poll_post(App $a)
 
 function dfrn_poll_content(App $a)
 {
-	$dfrn_id         = defaults($_GET, 'dfrn_id'        , '');
-	$type            = defaults($_GET, 'type'           , 'data');
-	$last_update     = defaults($_GET, 'last_update'    , '');
-	$destination_url = defaults($_GET, 'destination_url', '');
-	$sec             = defaults($_GET, 'sec'            , '');
-	$dfrn_version    = !empty($_GET['dfrn_version'])    ? (float) $_GET['dfrn_version'] : 2.0;
+	$dfrn_id         =  $_GET['dfrn_id']         ?? '';
+	$type            = ($_GET['type']            ?? '') ?: 'data';
+	$last_update     =  $_GET['last_update']     ?? '';
+	$destination_url =  $_GET['destination_url'] ?? '';
+	$sec             =  $_GET['sec']             ?? '';
+	$dfrn_version    = floatval(($_GET['dfrn_version'] ?? 0.0) ?: 2.0);
 	$quiet           = !empty($_GET['quiet']);
 
 	$direction = -1;
@@ -517,15 +515,13 @@ function dfrn_poll_content(App $a)
 
 				if (((int) $xml->status == 0) && ($xml->challenge == $hash) && ($xml->sec == $sec)) {
 					$_SESSION['authenticated'] = 1;
-					if (empty($_SESSION['remote'])) {
-						$_SESSION['remote'] = [];
-					}
-
-					$_SESSION['remote'][] = ['cid' => $r[0]['id'], 'uid' => $r[0]['uid'], 'url' => $r[0]['url']];
 					$_SESSION['visitor_id'] = $r[0]['id'];
 					$_SESSION['visitor_home'] = $r[0]['url'];
 					$_SESSION['visitor_visiting'] = $r[0]['uid'];
 					$_SESSION['my_url'] = $r[0]['url'];
+
+					Session::setVisitorsContacts();
+
 					if (!$quiet) {
 						info(L10n::t('%1$s welcomes %2$s', $r[0]['username'], $r[0]['name']) . EOL);
 					}
diff --git a/mod/dfrn_request.php b/mod/dfrn_request.php
index 19879c21b..f37064573 100644
--- a/mod/dfrn_request.php
+++ b/mod/dfrn_request.php
@@ -19,6 +19,7 @@ use Friendica\Core\Logger;
 use Friendica\Core\Protocol;
 use Friendica\Core\Renderer;
 use Friendica\Core\System;
+use Friendica\Core\Session;
 use Friendica\Database\DBA;
 use Friendica\Model\Contact;
 use Friendica\Model\Group;
@@ -79,7 +80,7 @@ function dfrn_request_post(App $a)
 		if (local_user() && ($a->user['nickname'] == $a->argv[1]) && !empty($_POST['dfrn_url'])) {
 			$dfrn_url    = Strings::escapeTags(trim($_POST['dfrn_url']));
 			$aes_allow   = !empty($_POST['aes_allow']);
-			$confirm_key = defaults($_POST, 'confirm_key', "");
+			$confirm_key = $_POST['confirm_key'] ?? '';
 			$hidden      = (!empty($_POST['hidden-contact']) ? intval($_POST['hidden-contact']) : 0);
 			$contact_record = null;
 			$blocked     = 1;
@@ -168,7 +169,7 @@ function dfrn_request_post(App $a)
 				$r = q("SELECT `id`, `network` FROM `contact` WHERE `uid` = %d AND `url` = '%s' AND `site-pubkey` = '%s' LIMIT 1",
 					intval(local_user()),
 					DBA::escape($dfrn_url),
-					defaults($parms, 'key', '') // Potentially missing
+					$parms['key'] ?? '' // Potentially missing
 				);
 				if (DBA::isResult($r)) {
 					Group::addMember(User::getDefaultGroup(local_user(), $r[0]["network"]), $r[0]['id']);
@@ -422,7 +423,7 @@ function dfrn_request_post(App $a)
 					intval($uid),
 					intval($contact_record['id']),
 					intval(!empty($_POST['knowyou'])),
-					DBA::escape(Strings::escapeTags(trim(defaults($_POST, 'dfrn-request-message', '')))),
+					DBA::escape(Strings::escapeTags(trim($_POST['dfrn-request-message'] ?? ''))),
 					DBA::escape($hash),
 					DBA::escape(DateTimeFormat::utcNow())
 				);
@@ -498,7 +499,7 @@ function dfrn_request_content(App $a)
 
 		$dfrn_url = Strings::escapeTags(trim(hex2bin($_GET['dfrn_url'])));
 		$aes_allow = !empty($_GET['aes_allow']);
-		$confirm_key = defaults($_GET, 'confirm_key', "");
+		$confirm_key = $_GET['confirm_key'] ?? '';
 
 		// Checking fastlane for validity
 		if (!empty($_SESSION['fastlane']) && (Strings::normaliseLink($_SESSION["fastlane"]) == Strings::normaliseLink($dfrn_url))) {
@@ -592,7 +593,7 @@ function dfrn_request_content(App $a)
 		exit();
 	} else {
 		// Normal web request. Display our user's introduction form.
-		if ((Config::get('system', 'block_public')) && (!local_user()) && (!remote_user())) {
+		if (Config::get('system', 'block_public') && !Session::isAuthenticated()) {
 			if (!Config::get('system', 'local_block')) {
 				notice(L10n::t('Public access denied.') . EOL);
 				return;
diff --git a/mod/display.php b/mod/display.php
index 54d479259..12fa8d7ec 100644
--- a/mod/display.php
+++ b/mod/display.php
@@ -14,6 +14,7 @@ use Friendica\Core\Logger;
 use Friendica\Core\Protocol;
 use Friendica\Core\Renderer;
 use Friendica\Core\System;
+use Friendica\Core\Session;
 use Friendica\Database\DBA;
 use Friendica\Model\Contact;
 use Friendica\Model\Group;
@@ -31,7 +32,7 @@ function display_init(App $a)
 		Objects::rawContent();
 	}
 
-	if (Config::get('system', 'block_public') && !local_user() && !remote_user()) {
+	if (Config::get('system', 'block_public') && !Session::isAuthenticated()) {
 		return;
 	}
 
@@ -52,9 +53,11 @@ function display_init(App $a)
 			if (DBA::isResult($item)) {
 				$nick = $a->user["nickname"];
 			}
+		}
+
 		// Is this item private but could be visible to the remove visitor?
-		} elseif (remote_user()) {
-			$item = Item::selectFirst($fields, ['guid' => $a->argv[1], 'private' => 1]);
+		if (!DBA::isResult($item) && remote_user()) {
+			$item = Item::selectFirst($fields, ['guid' => $a->argv[1], 'private' => 1, 'origin' => true]);
 			if (DBA::isResult($item)) {
 				if (!Contact::isFollower(remote_user(), $item['uid'])) {
 					$item = null;
@@ -84,10 +87,6 @@ function display_init(App $a)
 		displayShowFeed($item['id'], $a->argc > 3 && $a->argv[3] == 'conversation.atom');
 	}
 
-	if ($a->argc >= 3 && $nick == 'feed-item') {
-		displayShowFeed($item['id'], $a->argc > 3 && $a->argv[3] == 'conversation.atom');
-	}
-
 	if (!empty($_SERVER['HTTP_ACCEPT']) && strstr($_SERVER['HTTP_ACCEPT'], 'application/atom+xml')) {
 		Logger::log('Directly serving XML for id '.$item["id"], Logger::DEBUG);
 		displayShowFeed($item["id"], false);
@@ -102,7 +101,7 @@ function display_init(App $a)
 	if (strstr(Strings::normaliseLink($profiledata["url"]), Strings::normaliseLink(System::baseUrl()))) {
 		$nickname = str_replace(Strings::normaliseLink(System::baseUrl())."/profile/", "", Strings::normaliseLink($profiledata["url"]));
 
-		if (($nickname != $a->user["nickname"])) {
+		if ($nickname != $a->user["nickname"]) {
 			$profile = DBA::fetchFirst("SELECT `profile`.`uid` AS `profile_uid`, `profile`.* , `contact`.`avatar-date` AS picdate, `user`.* FROM `profile`
 				INNER JOIN `contact` on `contact`.`uid` = `profile`.`uid` INNER JOIN `user` ON `profile`.`uid` = `user`.`uid`
 				WHERE `user`.`nickname` = ? AND `profile`.`is-default` AND `contact`.`self` LIMIT 1",
@@ -188,14 +187,16 @@ function display_fetchauthor($a, $item)
 
 	$profiledata = Contact::getDetailsByURL($profiledata["url"], local_user(), $profiledata);
 
-	$profiledata["photo"] = System::removedBaseUrl($profiledata["photo"]);
+	if (!empty($profiledata["photo"])) {
+		$profiledata["photo"] = System::removedBaseUrl($profiledata["photo"]);
+	}
 
 	return $profiledata;
 }
 
 function display_content(App $a, $update = false, $update_uid = 0)
 {
-	if (Config::get('system','block_public') && !local_user() && !remote_user()) {
+	if (Config::get('system','block_public') && !Session::isAuthenticated()) {
 		throw new HTTPException\ForbiddenException(L10n::t('Public access denied.'));
 	}
 
@@ -227,8 +228,10 @@ function display_content(App $a, $update = false, $update_uid = 0)
 					$item_parent = $item["parent"];
 					$item_parent_uri = $item['parent-uri'];
 				}
-			} elseif (remote_user()) {
-				$item = Item::selectFirst($fields, ['guid' => $a->argv[1], 'private' => 1]);
+			}
+
+			if (($item_parent == 0) && remote_user()) {
+				$item = Item::selectFirst($fields, ['guid' => $a->argv[1], 'private' => 1, 'origin' => true]);
 				if (DBA::isResult($item) && Contact::isFollower(remote_user(), $item['uid'])) {
 					$item_id = $item["id"];
 					$item_parent = $item["parent"];
@@ -267,34 +270,26 @@ function display_content(App $a, $update = false, $update_uid = 0)
 				['$alternate' => $alternate,
 					'$conversation' => $conversation]);
 
-	$groups = [];
-	$remote_cid = null;
 	$is_remote_contact = false;
 	$item_uid = local_user();
 
 	if (isset($item_parent_uri)) {
 		$parent = Item::selectFirst(['uid'], ['uri' => $item_parent_uri, 'wall' => true]);
 		if (DBA::isResult($parent)) {
-			$a->profile['uid'] = defaults($a->profile, 'uid', $parent['uid']);
-			$a->profile['profile_uid'] = defaults($a->profile, 'profile_uid', $parent['uid']);
-			$is_remote_contact = Contact::isFollower(remote_user(), $a->profile['profile_uid']);
-
+			$a->profile['uid'] = ($a->profile['uid'] ?? 0) ?: $parent['uid'];
+			$a->profile['profile_uid'] = ($a->profile['profile_uid'] ?? 0) ?: $parent['uid'];
+			$is_remote_contact = Session::getRemoteContactID($a->profile['profile_uid']);
 			if ($is_remote_contact) {
-				$cdata = Contact::getPublicAndUserContacID(remote_user(), $a->profile['profile_uid']);
-				if (!empty($cdata['user'])) {
-					$groups = Group::getIdsByContactId($cdata['user']);
-					$remote_cid = $cdata['user'];
-					$item_uid = $parent['uid'];
-				}
+				$item_uid = $parent['uid'];
 			}
 		}
 	}
 
-
 	$page_contact = DBA::selectFirst('contact', [], ['self' => true, 'uid' => $a->profile['uid']]);
 	if (DBA::isResult($page_contact)) {
 		$a->page_contact = $page_contact;
 	}
+
 	$is_owner = (local_user() && (in_array($a->profile['profile_uid'], [local_user(), 0])) ? true : false);
 
 	if (!empty($a->profile['hidewall']) && !$is_owner && !$is_remote_contact) {
@@ -316,7 +311,7 @@ function display_content(App $a, $update = false, $update_uid = 0)
 		];
 		$o .= status_editor($a, $x, 0, true);
 	}
-	$sql_extra = Item::getPermissionsSQLByUserId($a->profile['profile_uid'], $is_remote_contact, $groups, $remote_cid);
+	$sql_extra = Item::getPermissionsSQLByUserId($a->profile['profile_uid']);
 
 	if (local_user() && (local_user() == $a->profile['profile_uid'])) {
 		$condition = ['parent-uri' => $item_parent_uri, 'uid' => local_user(), 'unseen' => true];
@@ -330,8 +325,8 @@ function display_content(App $a, $update = false, $update_uid = 0)
 	}
 
 	$condition = ["`id` = ? AND `item`.`uid` IN (0, ?) " . $sql_extra, $item_id, $item_uid];
-	$fields = ['parent-uri', 'body', 'title', 'author-name', 'author-avatar', 'plink'];
-	$item = Item::selectFirstForUser(local_user(), $fields, $condition);
+	$fields = ['parent-uri', 'body', 'title', 'author-name', 'author-avatar', 'plink', 'author-id', 'owner-id', 'contact-id'];
+	$item = Item::selectFirstForUser($a->profile['profile_uid'], $fields, $condition);
 
 	if (!DBA::isResult($item)) {
 		throw new HTTPException\NotFoundException(L10n::t('The requested item doesn\'t exist or has been deleted.'));
@@ -370,7 +365,10 @@ function display_content(App $a, $update = false, $update_uid = 0)
 	$title = htmlspecialchars($title, ENT_COMPAT, 'UTF-8', true); // allow double encoding here
 	$author_name = htmlspecialchars($author_name, ENT_COMPAT, 'UTF-8', true); // allow double encoding here
 
-	//
+	if (DBA::exists('contact', ['unsearchable' => true, 'id' => [$item['contact-id'], $item['author-id'], $item['owner-id']]])) {
+		$a->page['htmlhead'] .= '' . "\n";
+	}
+
 	$a->page['htmlhead'] .= ''."\n";
 	$a->page['htmlhead'] .= ''."\n";
 	$a->page['htmlhead'] .= ''."\n";
diff --git a/mod/events.php b/mod/events.php
index 86cec9a7d..649a25ab1 100644
--- a/mod/events.php
+++ b/mod/events.php
@@ -59,11 +59,11 @@ function events_post(App $a)
 	$cid = !empty($_POST['cid']) ? intval($_POST['cid']) : 0;
 	$uid = local_user();
 
-	$start_text  = Strings::escapeHtml(defaults($_REQUEST, 'start_text', ''));
-	$finish_text = Strings::escapeHtml(defaults($_REQUEST, 'finish_text', ''));
+	$start_text  = Strings::escapeHtml($_REQUEST['start_text'] ?? '');
+	$finish_text = Strings::escapeHtml($_REQUEST['finish_text'] ?? '');
 
-	$adjust   = intval(defaults($_POST, 'adjust', 0));
-	$nofinish = intval(defaults($_POST, 'nofinish', 0));
+	$adjust   = intval($_POST['adjust'] ?? 0);
+	$nofinish = intval($_POST['nofinish'] ?? 0);
 
 	// The default setting for the `private` field in event_store() is false, so mirror that
 	$private_event = false;
@@ -96,9 +96,9 @@ function events_post(App $a)
 	// and we'll waste a bunch of time responding to it. Time that
 	// could've been spent doing something else.
 
-	$summary  = trim(defaults($_POST, 'summary' , ''));
-	$desc     = trim(defaults($_POST, 'desc'    , ''));
-	$location = trim(defaults($_POST, 'location', ''));
+	$summary  = trim($_POST['summary']  ?? '');
+	$desc     = trim($_POST['desc']     ?? '');
+	$location = trim($_POST['location'] ?? '');
 	$type     = 'event';
 
 	$params = [
@@ -132,7 +132,7 @@ function events_post(App $a)
 		$a->internalRedirect($onerror_path);
 	}
 
-	$share = intval(defaults($_POST, 'share', 0));
+	$share = intval($_POST['share'] ?? 0);
 
 	$c = q("SELECT `id` FROM `contact` WHERE `uid` = %d AND `self` LIMIT 1",
 		intval(local_user())
@@ -146,10 +146,10 @@ function events_post(App $a)
 
 
 	if ($share) {
-		$str_group_allow   = perms2str(defaults($_POST, 'group_allow'  , ''));
-		$str_contact_allow = perms2str(defaults($_POST, 'contact_allow', ''));
-		$str_group_deny    = perms2str(defaults($_POST, 'group_deny'   , ''));
-		$str_contact_deny  = perms2str(defaults($_POST, 'contact_deny' , ''));
+		$str_group_allow   = perms2str($_POST['group_allow']   ?? '');
+		$str_contact_allow = perms2str($_POST['contact_allow'] ?? '');
+		$str_group_deny    = perms2str($_POST['group_deny']    ?? '');
+		$str_contact_deny  = perms2str($_POST['contact_deny']  ?? '');
 
 		// Undo the pseudo-contact of self, since there are real contacts now
 		if (strpos($str_contact_allow, '<' . $self . '>') !== false) {
@@ -321,7 +321,7 @@ function events_content(App $a)
 
 		// put the event parametes in an array so we can better transmit them
 		$event_params = [
-			'event_id'      => intval(defaults($_GET, 'id', 0)),
+			'event_id'      => intval($_GET['id'] ?? 0),
 			'start'         => $start,
 			'finish'        => $finish,
 			'adjust_start'  => $adjust_start,
diff --git a/mod/fbrowser.php b/mod/fbrowser.php
index f2bccb085..102d0c613 100644
--- a/mod/fbrowser.php
+++ b/mod/fbrowser.php
@@ -29,7 +29,7 @@ function fbrowser_content(App $a)
 	}
 
 	// Needed to match the correct template in a module that uses a different theme than the user/site/default
-	$theme = Strings::sanitizeFilePathItem(defaults($_GET, 'theme', null));
+	$theme = Strings::sanitizeFilePathItem($_GET['theme'] ?? null);
 	if ($theme && is_file("view/theme/$theme/config.php")) {
 		$a->setCurrentTheme($theme);
 	}
diff --git a/mod/follow.php b/mod/follow.php
index c7a96f734..31b92aa0d 100644
--- a/mod/follow.php
+++ b/mod/follow.php
@@ -62,7 +62,7 @@ function follow_content(App $a)
 	$uid = local_user();
 
 	// Issue 4815: Silently removing a prefixing @
-	$url = ltrim(Strings::escapeTags(trim(defaults($_REQUEST, 'url', ''))), '@!');
+	$url = ltrim(Strings::escapeTags(trim($_REQUEST['url'] ?? '')), '@!');
 
 	// Issue 6874: Allow remote following from Peertube
 	if (strpos($url, 'acct:') === 0) {
diff --git a/mod/fsuggest.php b/mod/fsuggest.php
index 2bddf4813..d41363ad7 100644
--- a/mod/fsuggest.php
+++ b/mod/fsuggest.php
@@ -45,7 +45,7 @@ function fsuggest_post(App $a)
 		return;
 	}
 
-	$note = Strings::escapeHtml(trim(defaults($_POST, 'note', '')));
+	$note = Strings::escapeHtml(trim($_POST['note'] ?? ''));
 
 	$fields = ['uid' => local_user(),'cid' => $contact_id, 'name' => $contact['name'],
 		'url' => $contact['url'], 'request' => $contact['request'],
diff --git a/mod/hcard.php b/mod/hcard.php
index 828eeaf09..013619bcb 100644
--- a/mod/hcard.php
+++ b/mod/hcard.php
@@ -6,13 +6,14 @@ use Friendica\App;
 use Friendica\Core\Config;
 use Friendica\Core\L10n;
 use Friendica\Core\System;
+use Friendica\Core\Session;
 use Friendica\Model\Contact;
 use Friendica\Model\Profile;
 use Friendica\Model\User;
 
 function hcard_init(App $a)
 {
-	$blocked = Config::get('system', 'block_public') && !local_user() && !remote_user();
+	$blocked = Config::get('system', 'block_public') && !Session::isAuthenticated();
 
 	if ($a->argc > 1) {
 		$which = $a->argv[1];
@@ -40,7 +41,7 @@ function hcard_init(App $a)
 	}
 
 	if (!$blocked) {
-		$keywords = defaults($a->profile, 'pub_keywords', '');
+		$keywords = $a->profile['pub_keywords'] ?? '';
 		$keywords = str_replace([',',' ',',,'], [' ',',',','], $keywords);
 		if (strlen($keywords)) {
 			$a->page['htmlhead'] .= '' . "\r\n";
diff --git a/mod/hovercard.php b/mod/hovercard.php
index ca3991963..d5951dbe0 100644
--- a/mod/hovercard.php
+++ b/mod/hovercard.php
@@ -26,8 +26,8 @@ function hovercard_init(App $a)
 
 function hovercard_content()
 {
-	$profileurl = defaults($_REQUEST, 'profileurl', '');
-	$datatype   = defaults($_REQUEST, 'datatype'  , 'json');
+	$profileurl =  $_REQUEST['profileurl'] ?? '';
+	$datatype   = ($_REQUEST['datatype']   ?? '') ?: 'json';
 
 	// Get out if the system doesn't have public access allowed
 	if (intval(Config::get('system', 'block_public'))) {
@@ -50,7 +50,7 @@ function hovercard_content()
 	if (strpos($profileurl, 'redir/') === 0) {
 		$cid = intval(substr($profileurl, 6));
 		$remote_contact = DBA::selectFirst('contact', ['nurl'], ['id' => $cid]);
-		$profileurl = defaults($remote_contact, 'nurl', '');
+		$profileurl = $remote_contact['nurl'] ?? '';
 	}
 
 	$contact = [];
@@ -97,7 +97,7 @@ function hovercard_content()
 	$profile = [
 		'name'         => $contact['name'],
 		'nick'         => $contact['nick'],
-		'addr'         => defaults($contact, 'addr', $contact['url']),
+		'addr'         => ($contact['addr'] ?? '') ?: $contact['url'],
 		'thumb'        => ProxyUtils::proxifyUrl($contact['thumb'], false, ProxyUtils::SIZE_THUMB),
 		'url'          => Contact::magicLink($contact['url']),
 		'nurl'         => $contact['nurl'], // We additionally store the nurl as identifier
diff --git a/mod/ignored.php b/mod/ignored.php
index 64edf6e15..6e0cf92a6 100644
--- a/mod/ignored.php
+++ b/mod/ignored.php
@@ -33,7 +33,7 @@ function ignored_init(App $a)
 	}
 
 	// See if we've been passed a return path to redirect to
-	$return_path = defaults($_REQUEST, 'return', '');
+	$return_path = $_REQUEST['return'] ?? '';
 	if ($return_path) {
 		$rand = '_=' . time();
 		if (strpos($return_path, '?')) {
diff --git a/mod/item.php b/mod/item.php
index 51bbc76e7..7c8ebee4a 100644
--- a/mod/item.php
+++ b/mod/item.php
@@ -25,6 +25,7 @@ use Friendica\Core\L10n;
 use Friendica\Core\Logger;
 use Friendica\Core\Protocol;
 use Friendica\Core\System;
+use Friendica\Core\Session;
 use Friendica\Core\Worker;
 use Friendica\Database\DBA;
 use Friendica\Model\Attach;
@@ -42,10 +43,10 @@ use Friendica\Util\Security;
 use Friendica\Util\Strings;
 use Friendica\Worker\Delivery;
 
-require_once 'include/items.php';
+require_once __DIR__ . '/../include/items.php';
 
 function item_post(App $a) {
-	if (!local_user() && !remote_user()) {
+	if (!Session::isAuthenticated()) {
 		return 0;
 	}
 
@@ -63,12 +64,12 @@ function item_post(App $a) {
 
 	Logger::log('postvars ' . print_r($_REQUEST, true), Logger::DATA);
 
-	$api_source = defaults($_REQUEST, 'api_source', false);
+	$api_source = $_REQUEST['api_source'] ?? false;
 
 	$message_id = ((!empty($_REQUEST['message_id']) && $api_source) ? strip_tags($_REQUEST['message_id']) : '');
 
-	$return_path = defaults($_REQUEST, 'return', '');
-	$preview = intval(defaults($_REQUEST, 'preview', 0));
+	$return_path = $_REQUEST['return'] ?? '';
+	$preview = intval($_REQUEST['preview'] ?? 0);
 
 	/*
 	 * Check for doubly-submitted posts, and reject duplicates
@@ -85,8 +86,8 @@ function item_post(App $a) {
 	}
 
 	// Is this a reply to something?
-	$toplevel_item_id = intval(defaults($_REQUEST, 'parent', 0));
-	$thr_parent_uri = trim(defaults($_REQUEST, 'parent_uri', ''));
+	$toplevel_item_id = intval($_REQUEST['parent'] ?? 0);
+	$thr_parent_uri = trim($_REQUEST['parent_uri'] ?? '');
 
 	$thread_parent_id = 0;
 	$thread_parent_contact = null;
@@ -97,8 +98,8 @@ function item_post(App $a) {
 	$parent_contact = null;
 
 	$objecttype = null;
-	$profile_uid = defaults($_REQUEST, 'profile_uid', local_user());
-	$posttype = defaults($_REQUEST, 'post_type', Item::PT_ARTICLE);
+	$profile_uid = ($_REQUEST['profile_uid'] ?? 0) ?: local_user();
+	$posttype = ($_REQUEST['post_type'] ?? '') ?: Item::PT_ARTICLE;
 
 	if ($toplevel_item_id || $thr_parent_uri) {
 		if ($toplevel_item_id) {
@@ -137,10 +138,10 @@ function item_post(App $a) {
 		Logger::info('mod_item: item_post parent=' . $toplevel_item_id);
 	}
 
-	$post_id     = intval(defaults($_REQUEST, 'post_id', 0));
-	$app         = strip_tags(defaults($_REQUEST, 'source', ''));
-	$extid       = strip_tags(defaults($_REQUEST, 'extid', ''));
-	$object      = defaults($_REQUEST, 'object', '');
+	$post_id     = intval($_REQUEST['post_id'] ?? 0);
+	$app         = strip_tags($_REQUEST['source'] ?? '');
+	$extid       = strip_tags($_REQUEST['extid'] ?? '');
+	$object      = $_REQUEST['object'] ?? '';
 
 	// Don't use "defaults" here. It would turn 0 to 1
 	if (!isset($_REQUEST['wall'])) {
@@ -193,20 +194,20 @@ function item_post(App $a) {
 	$categories = '';
 	$postopts = '';
 	$emailcc = '';
-	$body = defaults($_REQUEST, 'body', '');
-	$has_attachment = defaults($_REQUEST, 'has_attachment', 0);
+	$body = $_REQUEST['body'] ?? '';
+	$has_attachment = $_REQUEST['has_attachment'] ?? 0;
 
 	// If we have a speparate attachment, we need to add it to the body.
 	if (!empty($has_attachment)) {
-		$attachment_type  = defaults($_REQUEST, 'attachment_type',  '');
-		$attachment_title = defaults($_REQUEST, 'attachment_title', '');
-		$attachment_text  = defaults($_REQUEST, 'attachment_text',  '');
+		$attachment_type  = $_REQUEST['attachment_type'] ??  '';
+		$attachment_title = $_REQUEST['attachment_title'] ?? '';
+		$attachment_text  = $_REQUEST['attachment_text'] ??  '';
 
-		$attachment_url     = hex2bin(defaults($_REQUEST, 'attachment_url',     ''));
-		$attachment_img_src = hex2bin(defaults($_REQUEST, 'attachment_img_src', ''));
+		$attachment_url     = hex2bin($_REQUEST['attachment_url'] ??     '');
+		$attachment_img_src = hex2bin($_REQUEST['attachment_img_src'] ?? '');
 
-		$attachment_img_width  = defaults($_REQUEST, 'attachment_img_width',  0);
-		$attachment_img_height = defaults($_REQUEST, 'attachment_img_height', 0);
+		$attachment_img_width  = $_REQUEST['attachment_img_width'] ??  0;
+		$attachment_img_height = $_REQUEST['attachment_img_height'] ?? 0;
 		$attachment = [
 			'type'   => $attachment_type,
 			'title'  => $attachment_title,
@@ -228,6 +229,9 @@ function item_post(App $a) {
 		$body .= $att_bbcode;
 	}
 
+	// Convert links with empty descriptions to links without an explicit description
+	$body = preg_replace('#\[url=([^\]]*?)\]\[/url\]#ism', '[url]$1[/url]', $body);
+
 	if (!empty($orig_post)) {
 		$str_group_allow   = $orig_post['allow_gid'];
 		$str_contact_allow = $orig_post['allow_cid'];
@@ -265,29 +269,25 @@ function item_post(App $a) {
 			$str_contact_deny  = $user['deny_cid'];
 		} else {
 			// use the posted permissions
-			$str_group_allow   = perms2str(defaults($_REQUEST, 'group_allow', ''));
-			$str_contact_allow = perms2str(defaults($_REQUEST, 'contact_allow', ''));
-			$str_group_deny    = perms2str(defaults($_REQUEST, 'group_deny', ''));
-			$str_contact_deny  = perms2str(defaults($_REQUEST, 'contact_deny', ''));
+			$str_group_allow   = perms2str($_REQUEST['group_allow'] ?? '');
+			$str_contact_allow = perms2str($_REQUEST['contact_allow'] ?? '');
+			$str_group_deny    = perms2str($_REQUEST['group_deny'] ?? '');
+			$str_contact_deny  = perms2str($_REQUEST['contact_deny'] ?? '');
 		}
 
-		$title             = Strings::escapeTags(trim(defaults($_REQUEST, 'title'   , '')));
-		$location          = Strings::escapeTags(trim(defaults($_REQUEST, 'location', '')));
-		$coord             = Strings::escapeTags(trim(defaults($_REQUEST, 'coord'   , '')));
-		$verb              = Strings::escapeTags(trim(defaults($_REQUEST, 'verb'    , '')));
-		$emailcc           = Strings::escapeTags(trim(defaults($_REQUEST, 'emailcc' , '')));
+		$title             = Strings::escapeTags(trim($_REQUEST['title']    ?? ''));
+		$location          = Strings::escapeTags(trim($_REQUEST['location'] ?? ''));
+		$coord             = Strings::escapeTags(trim($_REQUEST['coord']    ?? ''));
+		$verb              = Strings::escapeTags(trim($_REQUEST['verb']     ?? ''));
+		$emailcc           = Strings::escapeTags(trim($_REQUEST['emailcc']  ?? ''));
 		$body              = Strings::escapeHtml(trim($body));
-		$network           = Strings::escapeTags(trim(defaults($_REQUEST, 'network' , Protocol::DFRN)));
+		$network           = Strings::escapeTags(trim(($_REQUEST['network']  ?? '') ?: Protocol::DFRN));
 		$guid              = System::createUUID();
 
-		$postopts = defaults($_REQUEST, 'postopts', '');
+		$postopts = $_REQUEST['postopts'] ?? '';
 
 		$private = ((strlen($str_group_allow) || strlen($str_contact_allow) || strlen($str_group_deny) || strlen($str_contact_deny)) ? 1 : 0);
 
-		if ($user['hidewall']) {
-			$private = 2;
-		}
-
 		// If this is a comment, set the permissions from the parent.
 
 		if ($toplevel_item) {
@@ -307,7 +307,7 @@ function item_post(App $a) {
 			$wall              = $toplevel_item['wall'];
 		}
 
-		$pubmail_enabled = defaults($_REQUEST, 'pubmail_enable', false) && !$private;
+		$pubmail_enabled = ($_REQUEST['pubmail_enable'] ?? false) && !$private;
 
 		// if using the API, we won't see pubmail_enable - figure out if it should be set
 		if ($api_source && $profile_uid && $profile_uid == local_user() && !$private) {
@@ -335,7 +335,7 @@ function item_post(App $a) {
 
 	// save old and new categories, so we can determine what needs to be deleted from pconfig
 	$categories_old = $categories;
-	$categories = FileTag::listToFile(trim(defaults($_REQUEST, 'category', '')), 'category');
+	$categories = FileTag::listToFile(trim($_REQUEST['category'] ?? ''), 'category');
 	$categories_new = $categories;
 
 	if (!empty($filedas) && is_array($filedas)) {
@@ -352,18 +352,8 @@ function item_post(App $a) {
 	if (local_user() && ((local_user() == $profile_uid) || $allow_comment)) {
 		$self = true;
 		$author = DBA::selectFirst('contact', [], ['uid' => local_user(), 'self' => true]);
-	} elseif (remote_user()) {
-		if (!empty($_SESSION['remote']) && is_array($_SESSION['remote'])) {
-			foreach ($_SESSION['remote'] as $v) {
-				if ($v['uid'] == $profile_uid) {
-					$contact_id = $v['cid'];
-					break;
-				}
-			}
-		}
-		if ($contact_id) {
-			$author = DBA::selectFirst('contact', [], ['id' => $contact_id]);
-		}
+	} elseif (!empty(Session::getRemoteContactID($profile_uid))) {
+		$author = DBA::selectFirst('contact', [], ['id' => Session::getRemoteContactID($profile_uid)]);
 	}
 
 	if (DBA::isResult($author)) {
@@ -874,7 +864,7 @@ function item_post_return($baseurl, $api_source, $return_path)
 
 function item_content(App $a)
 {
-	if (!local_user() && !remote_user()) {
+	if (!Session::isAuthenticated()) {
 		return;
 	}
 
@@ -1025,7 +1015,7 @@ function handle_tag(&$body, &$inform, &$str_tags, $profile_uid, $tag, $network =
 
 			$profile = $contact["url"];
 			$alias   = $contact["alias"];
-			$newname = defaults($contact, "name", $contact["nick"]);
+			$newname = ($contact["name"] ?? '') ?: $contact["nick"];
 		}
 
 		//if there is an url for this persons profile
diff --git a/mod/lostpass.php b/mod/lostpass.php
index 01e84268b..ecab0982c 100644
--- a/mod/lostpass.php
+++ b/mod/lostpass.php
@@ -1,4 +1,5 @@
 internalRedirect();
 	}
 
-	$pwdreset_token = Strings::getRandomName(12) . mt_rand(1000, 9999);
+	$pwdreset_token = Strings::getRandomName(12) . random_int(1000, 9999);
 
 	$fields = [
 		'pwdreset' => $pwdreset_token,
diff --git a/mod/manage.php b/mod/manage.php
deleted file mode 100644
index 58590264a..000000000
--- a/mod/manage.php
+++ /dev/null
@@ -1,188 +0,0 @@
-user;
-
-	if(!empty($_SESSION['submanage'])) {
-		$r = q("select * from user where uid = %d limit 1",
-			intval($_SESSION['submanage'])
-		);
-		if (DBA::isResult($r)) {
-			$uid = intval($r[0]['uid']);
-			$orig_record = $r[0];
-		}
-	}
-
-	$r = q("SELECT * FROM `manage` WHERE `uid` = %d",
-		intval($uid)
-	);
-
-	$submanage = $r;
-
-	$identity = (!empty($_POST['identity']) ? intval($_POST['identity']) : 0);
-	if (!$identity) {
-		return;
-	}
-
-	$limited_id = 0;
-	$original_id = $uid;
-
-	if (DBA::isResult($submanage)) {
-		foreach ($submanage as $m) {
-			if ($identity == $m['mid']) {
-				$limited_id = $m['mid'];
-				break;
-			}
-		}
-	}
-
-	if ($limited_id) {
-		$r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
-			intval($limited_id)
-		);
-	} else {
-		// Check if the target user is one of our children
-		$r = q("SELECT * FROM `user` WHERE `uid` = %d AND `parent-uid` = %d LIMIT 1",
-			intval($identity),
-			DBA::escape($orig_record['uid'])
-		);
-
-		// Check if the target user is one of our siblings
-		if (!DBA::isResult($r) && ($orig_record['parent-uid'] != 0)) {
-			$r = q("SELECT * FROM `user` WHERE `uid` = %d AND `parent-uid` = %d LIMIT 1",
-				intval($identity),
-				DBA::escape($orig_record['parent-uid'])
-			);
-		}
-
-		// Check if it's our parent
-		if (!DBA::isResult($r) && ($orig_record['parent-uid'] != 0) && ($orig_record['parent-uid'] == $identity)) {
-			$r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
-				intval($identity)
-			);
-		}
-
-		// Finally check if it's out own user
-		if (!DBA::isResult($r) && ($orig_record['uid'] != 0) && ($orig_record['uid'] == $identity)) {
-			$r = q("SELECT * FROM `user` WHERE `uid` = %d LIMIT 1",
-				intval($identity)
-			);
-		}
-	}
-
-	if (!DBA::isResult($r)) {
-		return;
-	}
-
-	unset($_SESSION['authenticated']);
-	unset($_SESSION['uid']);
-	unset($_SESSION['visitor_id']);
-	unset($_SESSION['administrator']);
-	unset($_SESSION['cid']);
-	unset($_SESSION['theme']);
-	unset($_SESSION['mobile-theme']);
-	unset($_SESSION['page_flags']);
-	unset($_SESSION['return_path']);
-	if (!empty($_SESSION['submanage'])) {
-		unset($_SESSION['submanage']);
-	}
-	if (!empty($_SESSION['sysmsg'])) {
-		unset($_SESSION['sysmsg']);
-	}
-	if (!empty($_SESSION['sysmsg_info'])) {
-		unset($_SESSION['sysmsg_info']);
-	}
-
-	Session::setAuthenticatedForUser($a, $r[0], true, true);
-
-	if ($limited_id) {
-		$_SESSION['submanage'] = $original_id;
-	}
-
-	$ret = [];
-	Hook::callAll('home_init',$ret);
-
-	$a->internalRedirect('profile/' . $a->user['nickname'] );
-	// NOTREACHED
-}
-
-
-
-function manage_content(App $a) {
-
-	if (! local_user()) {
-		notice(L10n::t('Permission denied.') . EOL);
-		return;
-	}
-
-	if (!empty($_GET['identity'])) {
-		$_POST['identity'] = $_GET['identity'];
-		manage_post($a);
-		return;
-	}
-
-	$identities = $a->identities;
-
-	//getting additinal information for each identity
-	foreach ($identities as $key=>$id) {
-		$thumb = q("SELECT `thumb` FROM `contact` WHERE `uid` = '%s' AND `self` = 1",
-			DBA::escape($id['uid'])
-		);
-
-		$identities[$key]['thumb'] = $thumb[0]['thumb'];
-
-		$identities[$key]['selected'] = ($id['nickname'] === $a->user['nickname']);
-
-		$notifications = 0;
-
-		$r = q("SELECT DISTINCT(`parent`) FROM `notify` WHERE `uid` = %d AND NOT `seen` AND NOT (`type` IN (%d, %d))",
-			intval($id['uid']), intval(NOTIFY_INTRO), intval(NOTIFY_MAIL));
-
-		if (DBA::isResult($r)) {
-			$notifications = sizeof($r);
-		}
-
-		$r = q("SELECT DISTINCT(`convid`) FROM `mail` WHERE `uid` = %d AND NOT `seen`",
-			intval($id['uid']));
-
-		if (DBA::isResult($r)) {
-			$notifications = $notifications + sizeof($r);
-		}
-
-		$r = q("SELECT COUNT(*) AS `introductions` FROM `intro` WHERE NOT `blocked` AND NOT `ignore` AND `uid` = %d",
-			intval($id['uid']));
-
-		if (DBA::isResult($r)) {
-			$notifications = $notifications + $r[0]["introductions"];
-		}
-
-		$identities[$key]['notifications'] = $notifications;
-	}
-
-	$o = Renderer::replaceMacros(Renderer::getMarkupTemplate('manage.tpl'), [
-		'$title' => L10n::t('Manage Identities and/or Pages'),
-		'$desc' => L10n::t('Toggle between different identities or community/group pages which share your account details or which you have been granted "manage" permissions'),
-		'$choose' => L10n::t('Select an identity to manage: '),
-		'$identities' => $identities,
-		'$submit' => L10n::t('Submit'),
-	]);
-
-	return $o;
-
-}
diff --git a/mod/match.php b/mod/match.php
index 2b3c7ca52..41346bc89 100644
--- a/mod/match.php
+++ b/mod/match.php
@@ -66,7 +66,7 @@ function match_content(App $a)
 
 	$msearch = json_decode($msearch_json);
 
-	$start = defaults($_GET, 'start', 0);
+	$start = $_GET['start'] ?? 0;
 	$entries = [];
 	$paginate = '';
 
@@ -92,11 +92,11 @@ function match_content(App $a)
 
 			$entry = [
 				'url'          => Contact::magicLink($profile->url),
-				'itemurl'      => defaults($contact_details, 'addr', $profile->url),
+				'itemurl'      => $contact_details['addr'] ?? $profile->url,
 				'name'         => $profile->name,
-				'details'      => defaults($contact_details, 'location', ''),
-				'tags'         => defaults($contact_details, 'keywords', ''),
-				'about'        => defaults($contact_details, 'about', ''),
+				'details'      => $contact_details['location'] ?? '',
+				'tags'         => $contact_details['keywords'] ?? '',
+				'about'        => $contact_details['about'] ?? '',
 				'account_type' => Contact::getAccountType($contact_details),
 				'thumb'        => ProxyUtils::proxifyUrl($profile->photo, false, ProxyUtils::SIZE_THUMB),
 				'conntxt'      => L10n::t('Connect'),
diff --git a/mod/message.php b/mod/message.php
index fe4429e00..393d5d276 100644
--- a/mod/message.php
+++ b/mod/message.php
@@ -249,8 +249,8 @@ function message_content(App $a)
 			'$prefill'    => $prefill,
 			'$preid'      => $preid,
 			'$subject'    => L10n::t('Subject:'),
-			'$subjtxt'    => defaults($_REQUEST, 'subject', ''),
-			'$text'       => defaults($_REQUEST, 'body', ''),
+			'$subjtxt'    => $_REQUEST['subject'] ?? '',
+			'$text'       => $_REQUEST['body'] ?? '',
 			'$readonly'   => '',
 			'$yourmessage'=> L10n::t('Your message:'),
 			'$select'     => $select,
@@ -530,7 +530,7 @@ function render_messages(array $msg, $t)
 			'$id' => $rr['id'],
 			'$from_name' => $participants,
 			'$from_url' => Contact::magicLink($rr['url']),
-			'$from_addr' => defaults($contact, 'addr', ''),
+			'$from_addr' => $contact['addr'] ?? '',
 			'$sparkle' => ' sparkle',
 			'$from_photo' => ProxyUtils::proxifyUrl($from_photo, false, ProxyUtils::SIZE_THUMB),
 			'$subject' => $rr['title'],
diff --git a/mod/msearch.php b/mod/msearch.php
index fcb93a32f..e87a8f522 100644
--- a/mod/msearch.php
+++ b/mod/msearch.php
@@ -6,9 +6,9 @@ use Friendica\Database\DBA;
 
 function msearch_post(App $a)
 {
-	$search = defaults($_POST, 's', '');
-	$perpage  = intval(defaults($_POST, 'n', 80));
-	$page     = intval(defaults($_POST, 'p', 1));
+	$search = $_POST['s'] ?? '';
+	$perpage  = intval(($_POST['n'] ?? 0) ?: 80);
+	$page     = intval(($_POST['p'] ?? 0) ?: 1);
 	$startrec = ($page - 1) * $perpage;
 
 	$total = 0;
diff --git a/mod/network.php b/mod/network.php
index fddec60c8..0438be705 100644
--- a/mod/network.php
+++ b/mod/network.php
@@ -40,22 +40,6 @@ function network_init(App $a)
 
 	Hook::add('head', __FILE__, 'network_infinite_scroll_head');
 
-	$search = (!empty($_GET['search']) ? Strings::escapeHtml($_GET['search']) : '');
-
-	if (($search != '') && !empty($_GET['submit'])) {
-		$a->internalRedirect('search?search=' . urlencode($search));
-	}
-
-	if (!empty($_GET['save'])) {
-		$exists = DBA::exists('search', ['uid' => local_user(), 'term' => $search]);
-		if (!$exists) {
-			DBA::insert('search', ['uid' => local_user(), 'term' => $search]);
-		}
-	}
-	if (!empty($_GET['remove'])) {
-		DBA::delete('search', ['uid' => local_user(), 'term' => $search]);
-	}
-
 	$is_a_date_query = false;
 
 	$group_id = (($a->argc > 1 && is_numeric($a->argv[1])) ? intval($a->argv[1]) : 0);
@@ -82,7 +66,7 @@ function network_init(App $a)
 
 	// fetch last used network view and redirect if needed
 	if (!$is_a_date_query) {
-		$sel_nets = defaults($_GET, 'nets', '');
+		$sel_nets = $_GET['nets'] ?? '';
 		$sel_tabs = network_query_get_sel_tab($a);
 		$sel_groups = network_query_get_sel_group($a);
 		$last_sel_tabs = PConfig::get(local_user(), 'network.view', 'tab.selected');
@@ -154,46 +138,9 @@ function network_init(App $a)
 	$a->page['aside'] .= Group::sidebarWidget('network/0', 'network', 'standard', $group_id);
 	$a->page['aside'] .= ForumManager::widget(local_user(), $cid);
 	$a->page['aside'] .= Widget::postedByYear('network', local_user(), false);
-	$a->page['aside'] .= Widget::networks('network', defaults($_GET, 'nets', '') );
-	$a->page['aside'] .= saved_searches($search);
-	$a->page['aside'] .= Widget::fileAs('network', defaults($_GET, 'file', '') );
-}
-
-function saved_searches($search)
-{
-	$srchurl = '/network?f='
-		. (!empty($_GET['cid'])   ? '&cid='   . rawurlencode($_GET['cid'])   : '')
-		. (!empty($_GET['star'])  ? '&star='  . rawurlencode($_GET['star'])  : '')
-		. (!empty($_GET['bmark']) ? '&bmark=' . rawurlencode($_GET['bmark']) : '')
-		. (!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'])  : '');
-	;
-
-	$terms = DBA::select('search', ['id', 'term'], ['uid' => local_user()]);
-	$saved = [];
-
-	while ($rr = DBA::fetch($terms)) {
-		$saved[] = [
-			'id'          => $rr['id'],
-			'term'        => $rr['term'],
-			'encodedterm' => urlencode($rr['term']),
-			'delete'      => L10n::t('Remove term'),
-			'selected'    => ($search == $rr['term']),
-		];
-	}
-
-	$tpl = Renderer::getMarkupTemplate('saved_searches_aside.tpl');
-	$o = Renderer::replaceMacros($tpl, [
-		'$title'     => L10n::t('Saved Searches'),
-		'$add'       => L10n::t('add'),
-		'$searchbox' => HTML::search($search, 'netsearch-box', $srchurl),
-		'$saved'     => $saved,
-	]);
-
-	return $o;
+	$a->page['aside'] .= Widget::networks('network', $_GET['nets'] ?? '');
+	$a->page['aside'] .= Widget\SavedSearches::getHTML($a->query_string);
+	$a->page['aside'] .= Widget::fileAs('network', $_GET['file'] ?? '');
 }
 
 /**
@@ -409,7 +356,7 @@ function networkFlatView(App $a, $update = 0)
 
 	$o = '';
 
-	$file = defaults($_GET, 'file', '');
+	$file = $_GET['file'] ?? '';
 
 	if (!$update && !$rawmode) {
 		$tabs = network_tabs($a);
@@ -532,12 +479,12 @@ function networkThreadedView(App $a, $update, $parent)
 
 	$o = '';
 
-	$cid   = intval(defaults($_GET, 'cid'  , 0));
-	$star  = intval(defaults($_GET, 'star' , 0));
-	$bmark = intval(defaults($_GET, 'bmark', 0));
-	$conv  = intval(defaults($_GET, 'conv' , 0));
-	$order = Strings::escapeTags(defaults($_GET, 'order', 'comment'));
-	$nets  =        defaults($_GET, 'nets' , '');
+	$cid   = intval($_GET['cid']   ?? 0);
+	$star  = intval($_GET['star']  ?? 0);
+	$bmark = intval($_GET['bmark'] ?? 0);
+	$conv  = intval($_GET['conv']  ?? 0);
+	$order = Strings::escapeTags(($_GET['order'] ?? '') ?: 'comment');
+	$nets  =        $_GET['nets']  ?? '';
 
 	$allowedCids = [];
 	if ($cid) {
@@ -676,7 +623,7 @@ function networkThreadedView(App $a, $update, $parent)
 			$entries[0] = [
 				'id' => 'network',
 				'name' => $contact['name'],
-				'itemurl' => defaults($contact, 'addr', $contact['nurl']),
+				'itemurl' => ($contact['addr'] ?? '') ?: $contact['nurl'],
 				'thumb' => ProxyUtils::proxifyUrl($contact['thumb'], false, ProxyUtils::SIZE_THUMB),
 				'details' => $contact['location'],
 			];
@@ -1066,7 +1013,7 @@ function network_infinite_scroll_head(App $a, &$htmlhead)
 	global $pager;
 
 	if (PConfig::get(local_user(), 'system', 'infinite_scroll')
-		&& defaults($_GET, 'mode', '') != 'minimal'
+		&& ($_GET['mode'] ?? '') != 'minimal'
 	) {
 		$tpl = Renderer::getMarkupTemplate('infinite_scroll_head.tpl');
 		$htmlhead .= Renderer::replaceMacros($tpl, [
diff --git a/mod/noscrape.php b/mod/noscrape.php
index 5761df3ff..7b8c0bdeb 100644
--- a/mod/noscrape.php
+++ b/mod/noscrape.php
@@ -49,7 +49,7 @@ function noscrape_init(App $a)
 		exit;
 	}
 
-	$keywords = defaults($a->profile, 'pub_keywords', '');
+	$keywords = $a->profile['pub_keywords'] ?? '';
 	$keywords = str_replace(['#',',',' ',',,'], ['',' ',',',','], $keywords);
 	$keywords = explode(',', $keywords);
 
diff --git a/mod/notifications.php b/mod/notifications.php
index 8bc9a76c3..8fbc5dac4 100644
--- a/mod/notifications.php
+++ b/mod/notifications.php
@@ -12,9 +12,11 @@ use Friendica\Core\L10n;
 use Friendica\Core\NotificationsManager;
 use Friendica\Core\Protocol;
 use Friendica\Core\Renderer;
+use Friendica\Core\Logger;
 use Friendica\Core\System;
 use Friendica\Database\DBA;
 use Friendica\Module\Login;
+use Friendica\Model\Contact;
 
 function notifications_post(App $a)
 {
@@ -46,13 +48,18 @@ function notifications_post(App $a)
 
 		if ($_POST['submit'] == L10n::t('Discard')) {
 			DBA::delete('intro', ['id' => $intro_id]);
-
 			if (!$fid) {
-				// The check for blocked and pending is in case the friendship was already approved
-				// and we just want to get rid of the now pointless notification
+				// When the contact entry had been created just for that intro, we want to get rid of it now
 				$condition = ['id' => $contact_id, 'uid' => local_user(),
-					'self' => false, 'blocked' => true, 'pending' => true];
-				DBA::delete('contact', $condition);
+					'self' => false, 'pending' => true, 'rel' => [0, Contact::FOLLOWER]];
+				$contact_pending = DBA::exists('contact', $condition);
+
+				// Remove the "pending" to stop the reappearing in any case
+				DBA::update('contact', ['pending' => false], ['id' => $contact_id]);
+
+				if ($contact_pending) {
+					Contact::remove($contact_id);
+				}
 			}
 			$a->internalRedirect('notifications/intros');
 		}
@@ -71,8 +78,8 @@ function notifications_content(App $a)
 		return Login::form();
 	}
 
-	$page = defaults($_REQUEST, 'page', 1);
-	$show = defaults($_REQUEST, 'show', 0);
+	$page = ($_REQUEST['page'] ?? 0) ?: 1;
+	$show =  $_REQUEST['show'] ?? 0;
 
 	Nav::setSelected('notifications');
 
@@ -98,9 +105,14 @@ function notifications_content(App $a)
 	if ((($a->argc > 1) && ($a->argv[1] == 'intros')) || (($a->argc == 1))) {
 		Nav::setSelected('introductions');
 
+		$id = 0;
+		if (!empty($a->argv[2]) && intval($a->argv[2]) != 0) {
+			$id = (int)$a->argv[2];
+		}
+
 		$all = (($a->argc > 2) && ($a->argv[2] == 'all'));
 
-		$notifs = $nm->introNotifs($all, $startrec, $perpage);
+		$notifs = $nm->introNotifs($all, $startrec, $perpage, $id);
 
 	// Get the network notifications
 	} elseif (($a->argc > 1) && ($a->argv[1] == 'network')) {
@@ -146,7 +158,7 @@ function notifications_content(App $a)
 	];
 
 	// Process the data for template creation
-	if (defaults($notifs, 'ident', '') === 'introductions') {
+	if (($notifs['ident'] ?? '') == 'introductions') {
 		$sugg = Renderer::getMarkupTemplate('suggestions.tpl');
 		$tpl = Renderer::getMarkupTemplate('intros.tpl');
 
diff --git a/mod/openid.php b/mod/openid.php
index def34ff08..2bb7f4954 100644
--- a/mod/openid.php
+++ b/mod/openid.php
@@ -13,21 +13,20 @@ use Friendica\Util\Strings;
 
 function openid_content(App $a) {
 
-	$noid = Config::get('system','no_openid');
-	if($noid)
+	if (Config::get('system','no_openid')) {
 		$a->internalRedirect();
+	}
 
 	Logger::log('mod_openid ' . print_r($_REQUEST,true), Logger::DATA);
 
-	if(!empty($_GET['openid_mode']) && !empty($_SESSION['openid'])) {
+	if (!empty($_GET['openid_mode']) && !empty($_SESSION['openid'])) {
 
 		$openid = new LightOpenID($a->getHostName());
 
-		if($openid->validate()) {
+		if ($openid->validate()) {
+			$authid = $openid->identity;
 
-			$authid = $_REQUEST['openid_identity'];
-
-			if(! strlen($authid)) {
+			if (empty($authid)) {
 				Logger::log(L10n::t('OpenID protocol error. No ID returned.') . EOL);
 				$a->internalRedirect();
 			}
@@ -37,22 +36,16 @@ function openid_content(App $a) {
 			//       mod/settings.php in 8367cad so it might have left mixed
 			//       records in the user table
 			//
-			$r = q("SELECT *
-				FROM `user`
-				WHERE ( `openid` = '%s' OR `openid` = '%s' )
-				AND `blocked` = 0 AND `account_expired` = 0
-				AND `account_removed` = 0 AND `verified` = 1
-				LIMIT 1",
-				DBA::escape($authid), DBA::escape(Strings::normaliseOpenID($authid))
-			);
-
-			if (DBA::isResult($r)) {
+			$condition = ['blocked' => false, 'account_expired' => false, 'account_removed' => false, 'verified' => true,
+				'openid' => [$authid, Strings::normaliseOpenID($authid)]];
+			$user  = DBA::selectFirst('user', [], $condition);
+			if (DBA::isResult($user)) {
 
 				// successful OpenID login
 
 				unset($_SESSION['openid']);
 
-				Session::setAuthenticatedForUser($a, $r[0],true,true);
+				Session::setAuthenticatedForUser($a, $user, true, true);
 
 				// just in case there was no return url set
 				// and we fell through
@@ -76,10 +69,10 @@ function openid_content(App $a) {
 					if ($k === 'namePerson/friendly') {
 						$nick = Strings::escapeTags(trim($v));
 					}
-					if($k === 'namePerson/first') {
+					if ($k === 'namePerson/first') {
 						$first = Strings::escapeTags(trim($v));
 					}
-					if($k === 'namePerson') {
+					if ($k === 'namePerson') {
 						$args .= '&username=' . urlencode(Strings::escapeTags(trim($v)));
 					}
 					if ($k === 'contact/email') {
@@ -95,15 +88,13 @@ function openid_content(App $a) {
 			}
 			if (!empty($nick)) {
 				$args .= '&nickname=' . urlencode($nick);
-			}
-			elseif (!empty($first)) {
+			} elseif (!empty($first)) {
 				$args .= '&nickname=' . urlencode($first);
 			}
 
 			if (!empty($photosq)) {
 				$args .= '&photo=' . urlencode($photosq);
-			}
-			elseif (!empty($photo)) {
+			} elseif (!empty($photo)) {
 				$args .= '&photo=' . urlencode($photo);
 			}
 
diff --git a/mod/photos.php b/mod/photos.php
index fb43619b6..1789c0710 100644
--- a/mod/photos.php
+++ b/mod/photos.php
@@ -15,6 +15,7 @@ use Friendica\Core\L10n;
 use Friendica\Core\Logger;
 use Friendica\Core\Renderer;
 use Friendica\Core\System;
+use Friendica\Core\Session;
 use Friendica\Database\DBA;
 use Friendica\Model\Contact;
 use Friendica\Model\Group;
@@ -35,11 +36,7 @@ use Friendica\Util\XML;
 
 function photos_init(App $a) {
 
-	if ($a->argc > 1) {
-		DFRN::autoRedir($a, $a->argv[1]);
-	}
-
-	if (Config::get('system', 'block_public') && !local_user() && !remote_user()) {
+	if (Config::get('system', 'block_public') && !Session::isAuthenticated()) {
 		return;
 	}
 
@@ -66,14 +63,14 @@ function photos_init(App $a) {
 		$vcard_widget = Renderer::replaceMacros($tpl, [
 			'$name' => $profile['name'],
 			'$photo' => $profile['photo'],
-			'$addr' => defaults($profile, 'addr', ''),
+			'$addr' => $profile['addr'] ?? '',
 			'$account_type' => $account_type,
-			'$pdesc' => defaults($profile, 'pdesc', ''),
+			'$pdesc' => $profile['pdesc'] ?? '',
 		]);
 
 		$albums = Photo::getAlbums($a->data['user']['uid']);
 
-		$albums_visible = ((intval($a->data['user']['hidewall']) && !local_user() && !remote_user()) ? false : true);
+		$albums_visible = ((intval($a->data['user']['hidewall']) && !Session::isAuthenticated()) ? false : true);
 
 		// add various encodings to the array so we can just loop through and pick them out in a template
 		$ret = ['success' => false];
@@ -88,7 +85,7 @@ function photos_init(App $a) {
 			$ret['albums'] = [];
 			foreach ($albums as $k => $album) {
 				//hide profile photos to others
-				if (!$is_owner && !remote_user() && ($album['album'] == L10n::t('Profile Photos')))
+				if (!$is_owner && !Session::getRemoteContactID($a->profile_uid) && ($album['album'] == L10n::t('Profile Photos')))
 					continue;
 				$entry = [
 					'text'      => $album['album'],
@@ -154,24 +151,10 @@ function photos_post(App $a)
 
 	if (local_user() && (local_user() == $page_owner_uid)) {
 		$can_post = true;
-	} elseif ($community_page && remote_user()) {
-		$contact_id = 0;
-
-		if (!empty($_SESSION['remote']) && is_array($_SESSION['remote'])) {
-			foreach ($_SESSION['remote'] as $v) {
-				if ($v['uid'] == $page_owner_uid) {
-					$contact_id = $v['cid'];
-					break;
-				}
-			}
-		}
-
-		if ($contact_id > 0) {
-			if (DBA::exists('contact', ['id' => $contact_id, 'uid' => $page_owner_uid, 'blocked' => false, 'pending' => false])) {
-				$can_post = true;
-				$visitor = $contact_id;
-			}
-		}
+	} elseif ($community_page && !empty(Session::getRemoteContactID($page_owner_uid))) {
+		$contact_id = Session::getRemoteContactID($page_owner_uid);
+		$can_post = true;
+		$visitor = $contact_id;
 	}
 
 	if (!$can_post) {
@@ -647,10 +630,10 @@ function photos_post(App $a)
 		$visible = 0;
 	}
 
-	$group_allow   = defaults($_REQUEST, 'group_allow'  , []);
-	$contact_allow = defaults($_REQUEST, 'contact_allow', []);
-	$group_deny    = defaults($_REQUEST, 'group_deny'   , []);
-	$contact_deny  = defaults($_REQUEST, 'contact_deny' , []);
+	$group_allow   = $_REQUEST['group_allow']   ?? [];
+	$contact_allow = $_REQUEST['contact_allow'] ?? [];
+	$group_deny    = $_REQUEST['group_deny']    ?? [];
+	$contact_deny  = $_REQUEST['contact_deny']  ?? [];
 
 	$str_group_allow   = perms2str(is_array($group_allow)   ? $group_allow   : explode(',', $group_allow));
 	$str_contact_allow = perms2str(is_array($contact_allow) ? $contact_allow : explode(',', $contact_allow));
@@ -683,7 +666,7 @@ function photos_post(App $a)
 				notice(L10n::t('Image exceeds size limit of %s', ini_get('upload_max_filesize')) . EOL);
 				break;
 			case UPLOAD_ERR_FORM_SIZE:
-				notice(L10n::t('Image exceeds size limit of %s', Strings::formatBytes(defaults($_REQUEST, 'MAX_FILE_SIZE', 0))) . EOL);
+				notice(L10n::t('Image exceeds size limit of %s', Strings::formatBytes($_REQUEST['MAX_FILE_SIZE'] ?? 0)) . EOL);
 				break;
 			case UPLOAD_ERR_PARTIAL:
 				notice(L10n::t('Image upload didn\'t complete, please try again') . EOL);
@@ -846,7 +829,7 @@ function photos_content(App $a)
 	// photos/name/image/xxxxx/edit
 	// photos/name/image/xxxxx/drop
 
-	if (Config::get('system', 'block_public') && !local_user() && !remote_user()) {
+	if (Config::get('system', 'block_public') && !Session::isAuthenticated()) {
 		notice(L10n::t('Public access denied.') . EOL);
 		return;
 	}
@@ -892,50 +875,24 @@ function photos_content(App $a)
 
 	if (local_user() && (local_user() == $owner_uid)) {
 		$can_post = true;
-	} else {
-		if ($community_page && remote_user()) {
-			if (is_array($_SESSION['remote'])) {
-				foreach ($_SESSION['remote'] as $v) {
-					if ($v['uid'] == $owner_uid) {
-						$contact_id = $v['cid'];
-						break;
-					}
-				}
-			}
+	} elseif ($community_page && !empty(Session::getRemoteContactID($owner_uid))) {
+		$contact_id = Session::getRemoteContactID($owner_uid);
+		$contact = DBA::selectFirst('contact', [], ['id' => $contact_id, 'uid' => $owner_uid, 'blocked' => false, 'pending' => false]);
 
-			if ($contact_id) {
-				$contact = DBA::selectFirst('contact', [], ['id' => $contact_id, 'uid' => $owner_uid, 'blocked' => false, 'pending' => false]);
-
-				if (DBA::isResult($contact)) {
-					$can_post = true;
-					$remote_contact = true;
-					$visitor = $contact_id;
-				}
-			}
+		if (DBA::isResult($contact)) {
+			$can_post = true;
+			$remote_contact = true;
+			$visitor = $contact_id;
 		}
 	}
 
-	$groups = [];
-
 	// perhaps they're visiting - but not a community page, so they wouldn't have write access
-	if (remote_user() && !$visitor) {
-		$contact_id = 0;
-		if (is_array($_SESSION['remote'])) {
-			foreach ($_SESSION['remote'] as $v) {
-				if ($v['uid'] == $owner_uid) {
-					$contact_id = $v['cid'];
-					break;
-				}
-			}
-		}
+	if (!empty(Session::getRemoteContactID($owner_uid)) && !$visitor) {
+		$contact_id = Session::getRemoteContactID($owner_uid);
 
-		if ($contact_id) {
-			$groups = Group::getIdsByContactId($contact_id);
+		$contact = DBA::selectFirst('contact', [], ['id' => $contact_id, 'uid' => $owner_uid, 'blocked' => false, 'pending' => false]);
 
-			$contact = DBA::selectFirst('contact', [], ['id' => $contact_id, 'uid' => $owner_uid, 'blocked' => false, 'pending' => false]);
-
-			$remote_contact = DBA::isResult($contact);
-		}
+		$remote_contact = DBA::isResult($contact);
 	}
 
 	if (!$remote_contact && local_user()) {
@@ -948,7 +905,7 @@ function photos_content(App $a)
 		return;
 	}
 
-	$sql_extra = Security::getPermissionsSQLByUserId($owner_uid, $remote_contact, $groups);
+	$sql_extra = Security::getPermissionsSQLByUserId($owner_uid);
 
 	$o = "";
 
@@ -1049,7 +1006,7 @@ function photos_content(App $a)
 		$pager = new Pager($a->query_string, 20);
 
 		/// @TODO I have seen this many times, maybe generalize it script-wide and encapsulate it?
-		$order_field = defaults($_GET, 'order', '');
+		$order_field = $_GET['order'] ?? '';
 		if ($order_field === 'posted') {
 			$order = 'ASC';
 		} else {
@@ -1201,7 +1158,7 @@ function photos_content(App $a)
 		 * By now we hide it if someone wants to.
 		 */
 		if ($cmd === 'view' && !Config::get('system', 'no_count', false)) {
-			$order_field = defaults($_GET, 'order', '');
+			$order_field = $_GET['order'] ?? '';
 
 			if ($order_field === 'posted') {
 				$order = 'ASC';
@@ -1607,7 +1564,7 @@ function photos_content(App $a)
 		$twist = false;
 		foreach ($r as $rr) {
 			//hide profile photos to others
-			if (!$is_owner && !remote_user() && ($rr['album'] == L10n::t('Profile Photos'))) {
+			if (!$is_owner && !Session::getRemoteContactID($owner_uid) && ($rr['album'] == L10n::t('Profile Photos'))) {
 				continue;
 			}
 
diff --git a/mod/poco.php b/mod/poco.php
index c288f6b63..2ed871285 100644
--- a/mod/poco.php
+++ b/mod/poco.php
@@ -36,7 +36,7 @@ function poco_init(App $a) {
 		$system_mode = true;
 	}
 
-	$format = defaults($_GET, 'format', 'json');
+	$format = ($_GET['format'] ?? '') ?: 'json';
 
 	$justme = false;
 	$global = false;
diff --git a/mod/profiles.php b/mod/profiles.php
index 58f23e463..72bcac49f 100644
--- a/mod/profiles.php
+++ b/mod/profiles.php
@@ -594,7 +594,7 @@ function profiles_content(App $a) {
 			'$default' => (($is_default) ? '

' . L10n::t('This is your public profile.
It may be visible to anybody using the internet.') . '

' : ""), '$name' => ['name', L10n::t('Your Full Name:'), $r[0]['name']], '$pdesc' => ['pdesc', L10n::t('Title/Description:'), $r[0]['pdesc']], - '$dob' => Temporal::getDateofBirthField($r[0]['dob']), + '$dob' => Temporal::getDateofBirthField($r[0]['dob'], $a->user['timezone']), '$hide_friends' => $hide_friends, '$address' => ['address', L10n::t('Street Address:'), $r[0]['address']], '$locality' => ['locality', L10n::t('Locality/City:'), $r[0]['locality']], diff --git a/mod/pubsub.php b/mod/pubsub.php index d10d7031d..c008fb09d 100644 --- a/mod/pubsub.php +++ b/mod/pubsub.php @@ -33,10 +33,10 @@ function pubsub_init(App $a) $contact_id = (($a->argc > 2) ? intval($a->argv[2]) : 0 ); if ($_SERVER['REQUEST_METHOD'] === 'GET') { - $hub_mode = Strings::escapeTags(trim(defaults($_GET, 'hub_mode', ''))); - $hub_topic = Strings::escapeTags(trim(defaults($_GET, 'hub_topic', ''))); - $hub_challenge = Strings::escapeTags(trim(defaults($_GET, 'hub_challenge', ''))); - $hub_verify = Strings::escapeTags(trim(defaults($_GET, 'hub_verify_token', ''))); + $hub_mode = Strings::escapeTags(trim($_GET['hub_mode'] ?? '')); + $hub_topic = Strings::escapeTags(trim($_GET['hub_topic'] ?? '')); + $hub_challenge = Strings::escapeTags(trim($_GET['hub_challenge'] ?? '')); + $hub_verify = Strings::escapeTags(trim($_GET['hub_verify_token'] ?? '')); Logger::log('Subscription from ' . $_SERVER['REMOTE_ADDR'] . ' Mode: ' . $hub_mode . ' Nick: ' . $nick); Logger::log('Data: ' . print_r($_GET,true), Logger::DATA); diff --git a/mod/redir.php b/mod/redir.php index c99e1823c..9d86f27a9 100644 --- a/mod/redir.php +++ b/mod/redir.php @@ -13,18 +13,18 @@ use Friendica\Util\Strings; function redir_init(App $a) { - $url = defaults($_GET, 'url', ''); + $url = $_GET['url'] ?? ''; $quiet = !empty($_GET['quiet']) ? '&quiet=1' : ''; - $con_url = defaults($_GET, 'conurl', ''); if ($a->argc > 1 && intval($a->argv[1])) { $cid = intval($a->argv[1]); - } elseif (local_user() && !empty($con_url)) { - $cid = Contact::getIdForURL($con_url, local_user()); } else { $cid = 0; } + // Try magic auth before the legacy stuff + redir_magic($a, $cid, $url); + if (!empty($cid)) { $fields = ['id', 'uid', 'nurl', 'url', 'addr', 'name', 'network', 'poll', 'issued-id', 'dfrn-id', 'duplex', 'pending']; $contact = DBA::selectFirst('contact', $fields, ['id' => $cid, 'uid' => [0, local_user()]]); @@ -35,10 +35,10 @@ function redir_init(App $a) { $contact_url = $contact['url']; - if ((!local_user() && !remote_user()) // Visitors (not logged in or not remotes) can't authenticate. + if (!Session::isAuthenticated() // Visitors (not logged in or not remotes) can't authenticate. || (!empty($a->contact['id']) && $a->contact['id'] == $cid)) // Local user is already authenticated. { - $a->redirect(defaults($url, $contact_url)); + $a->redirect($url ?: $contact_url); } if ($contact['uid'] == 0 && local_user()) { @@ -52,7 +52,7 @@ function redir_init(App $a) { if (!empty($a->contact['id']) && $a->contact['id'] == $cid) { // Local user is already authenticated. - $target_url = defaults($url, $contact_url); + $target_url = $url ?: $contact_url; Logger::log($contact['name'] . " is already authenticated. Redirecting to " . $target_url, Logger::DEBUG); $a->redirect($target_url); } @@ -66,34 +66,16 @@ function redir_init(App $a) { // with the local contact. Otherwise the local user would ask the local contact // for authentification everytime he/she is visiting a profile page of the local // contact. - if ($host == $remotehost - && !empty($_SESSION['remote']) - && is_array($_SESSION['remote'])) - { - foreach ($_SESSION['remote'] as $v) { - if (!empty($v['uid']) && !empty($v['cid']) && - $v['uid'] == Session::get('visitor_visiting') && - $v['cid'] == Session::get('visitor_id')) { - // Remote user is already authenticated. - $target_url = defaults($url, $contact_url); - Logger::log($contact['name'] . " is already authenticated. Redirecting to " . $target_url, Logger::DEBUG); - $a->redirect($target_url); - } - } + if (($host == $remotehost) && (Session::getRemoteContactID(Session::get('visitor_visiting')) == Session::get('visitor_id'))) { + // Remote user is already authenticated. + $target_url = $url ?: $contact_url; + Logger::log($contact['name'] . " is already authenticated. Redirecting to " . $target_url, Logger::DEBUG); + $a->redirect($target_url); } } - // When the remote page does support OWA, then we enforce the use of it - $basepath = Contact::getBasepath($contact_url); - if (Strings::compareLink($basepath, System::baseUrl())) { - $use_magic = true; - } else { - $serverret = Network::curl($basepath . '/magic'); - $use_magic = $serverret->isSuccess(); - } - // Doing remote auth with dfrn. - if (local_user() && !$use_magic && (!empty($contact['dfrn-id']) || !empty($contact['issued-id'])) && empty($contact['pending'])) { + if (local_user() && (!empty($contact['dfrn-id']) || !empty($contact['issued-id'])) && empty($contact['pending'])) { $dfrn_id = $orig_id = (($contact['issued-id']) ? $contact['issued-id'] : $contact['dfrn-id']); if ($contact['duplex'] && $contact['issued-id']) { @@ -119,7 +101,7 @@ function redir_init(App $a) { . '&dfrn_version=' . DFRN_PROTOCOL_VERSION . '&type=profile&sec=' . $sec . $dest . $quiet); } - $url = defaults($url, $contact_url); + $url = $url ?: $contact_url; } // If we don't have a connected contact, redirect with @@ -140,3 +122,46 @@ function redir_init(App $a) { notice(L10n::t('Contact not found.')); $a->internalRedirect(); } + +function redir_magic($a, $cid, $url) +{ + $visitor = Profile::getMyURL(); + if (!empty($visitor)) { + Logger::info('Got my url', ['visitor' => $visitor]); + } + + $contact = DBA::selectFirst('contact', ['url'], ['id' => $cid]); + if (!DBA::isResult($contact)) { + Logger::info('Contact not found', ['id' => $cid]); + // Shouldn't happen under normal conditions + notice(L10n::t('Contact not found.')); + if (!empty($url)) { + $a->redirect($url); + } else { + $a->internalRedirect(); + } + } else { + $contact_url = $contact['url']; + $target_url = $url ?: $contact_url; + } + + $basepath = Contact::getBasepath($contact_url); + + // We don't use magic auth when there is no visitor, we are on the same system or we visit our own stuff + if (empty($visitor) || Strings::compareLink($basepath, System::baseUrl()) || Strings::compareLink($contact_url, $visitor)) { + Logger::info('Redirecting without magic', ['target' => $target_url, 'visitor' => $visitor, 'contact' => $contact_url]); + $a->redirect($target_url); + } + + // Test for magic auth on the target system + $serverret = Network::curl($basepath . '/magic'); + if ($serverret->isSuccess()) { + $separator = strpos($target_url, '?') ? '&' : '?'; + $target_url .= $separator . 'zrl=' . urlencode($visitor) . '&addr=' . urlencode($contact_url); + + Logger::info('Redirecting with magic', ['target' => $target_url, 'visitor' => $visitor, 'contact' => $contact_url]); + $a->redirect($target_url); + } else { + Logger::info('No magic for contact', ['contact' => $contact_url]); + } +} diff --git a/mod/regmod.php b/mod/regmod.php index 6cf4c8836..295d8df25 100644 --- a/mod/regmod.php +++ b/mod/regmod.php @@ -44,7 +44,7 @@ function user_allow($hash) $user, Config::get('config', 'sitename'), $a->getBaseUrl(), - defaults($register, 'password', 'Sent in a previous email') + ($register['password'] ?? '') ?: 'Sent in a previous email' ); L10n::popLang(); diff --git a/mod/search.php b/mod/search.php deleted file mode 100644 index 4a911b4fd..000000000 --- a/mod/search.php +++ /dev/null @@ -1,245 +0,0 @@ - $rr['id'], - 'term' => $rr['term'], - 'encodedterm' => urlencode($rr['term']), - 'delete' => L10n::t('Remove term'), - 'selected' => ($search==$rr['term']), - ]; - } - - - $tpl = Renderer::getMarkupTemplate("saved_searches_aside.tpl"); - - $o .= Renderer::replaceMacros($tpl, [ - '$title' => L10n::t('Saved Searches'), - '$add' => '', - '$searchbox' => '', - '$saved' => $saved, - ]); - } - - return $o; -} - - -function search_init(App $a) { - $search = (!empty($_GET['search']) ? Strings::escapeTags(trim(rawurldecode($_GET['search']))) : ''); - - if (local_user()) { - if (!empty($_GET['save']) && $search) { - $r = q("SELECT * FROM `search` WHERE `uid` = %d AND `term` = '%s' LIMIT 1", - intval(local_user()), - DBA::escape($search) - ); - if (!DBA::isResult($r)) { - DBA::insert('search', ['uid' => local_user(), 'term' => $search]); - } - } - if (!empty($_GET['remove']) && $search) { - DBA::delete('search', ['uid' => local_user(), 'term' => $search]); - } - - /// @todo Check if there is a case at all that "aside" is prefilled here - if (!isset($a->page['aside'])) { - $a->page['aside'] = ''; - } - - $a->page['aside'] .= search_saved_searches(); - - } else { - unset($_SESSION['theme']); - unset($_SESSION['mobile-theme']); - } -} - -function search_content(App $a) { - if (Config::get('system','block_public') && !local_user() && !remote_user()) { - notice(L10n::t('Public access denied.') . EOL); - return; - } - - if (Config::get('system','local_search') && !local_user() && !remote_user()) { - $e = new \Friendica\Network\HTTPException\ForbiddenException(L10n::t("Only logged in users are permitted to perform a search.")); - $e->httpdesc = L10n::t("Public access denied."); - throw $e; - } - - if (Config::get('system','permit_crawling') && !local_user() && !remote_user()) { - // Default values: - // 10 requests are "free", after the 11th only a call per minute is allowed - - $free_crawls = intval(Config::get('system','free_crawls')); - if ($free_crawls == 0) - $free_crawls = 10; - - $crawl_permit_period = intval(Config::get('system','crawl_permit_period')); - if ($crawl_permit_period == 0) - $crawl_permit_period = 10; - - $remote = $_SERVER["REMOTE_ADDR"]; - $result = Cache::get("remote_search:".$remote); - if (!is_null($result)) { - $resultdata = json_decode($result); - if (($resultdata->time > (time() - $crawl_permit_period)) && ($resultdata->accesses > $free_crawls)) { - throw new \Friendica\Network\HTTPException\TooManyRequestsException(L10n::t("Only one search per minute is permitted for not logged in users.")); - } - Cache::set("remote_search:".$remote, json_encode(["time" => time(), "accesses" => $resultdata->accesses + 1]), Cache::HOUR); - } else - Cache::set("remote_search:".$remote, json_encode(["time" => time(), "accesses" => 1]), Cache::HOUR); - } - - Nav::setSelected('search'); - - $search = (!empty($_REQUEST['search']) ? Strings::escapeTags(trim(rawurldecode($_REQUEST['search']))) : ''); - - $tag = false; - if (!empty($_GET['tag'])) { - $tag = true; - $search = (!empty($_GET['tag']) ? '#' . Strings::escapeTags(trim(rawurldecode($_GET['tag']))) : ''); - } - - // contruct a wrapper for the search header - $o = Renderer::replaceMacros(Renderer::getMarkupTemplate("content_wrapper.tpl"),[ - 'name' => "search-header", - '$title' => L10n::t("Search"), - '$title_size' => 3, - '$content' => HTML::search($search,'search-box','search', false) - ]); - - if (strpos($search,'#') === 0) { - $tag = true; - $search = substr($search,1); - } - if (strpos($search,'@') === 0) { - return BaseSearchModule::performSearch(); - } - if (strpos($search,'!') === 0) { - return BaseSearchModule::performSearch(); - } - - if (parse_url($search, PHP_URL_SCHEME) != '') { - $id = Item::fetchByLink($search); - if (!empty($id)) { - $item = Item::selectFirst(['guid'], ['id' => $id]); - if (DBA::isResult($item)) { - $a->internalRedirect('display/' . $item['guid']); - } - } - } - - if (!empty($_GET['search-option'])) - switch($_GET['search-option']) { - case 'fulltext': - break; - case 'tags': - $tag = true; - break; - case 'contacts': - return BaseSearchModule::performSearch('@'); - case 'forums': - return BaseSearchModule::performSearch('!'); - } - - if (!$search) - return $o; - - if (Config::get('system','only_tag_search')) - $tag = true; - - // Here is the way permissions work in the search module... - // Only public posts can be shown - // OR your own posts if you are a logged in member - // No items will be shown if the member has a blocked profile wall. - - $pager = new Pager($a->query_string); - - if ($tag) { - Logger::log("Start tag search for '".$search."'", Logger::DEBUG); - - $condition = ["(`uid` = 0 OR (`uid` = ? AND NOT `global`)) - AND `otype` = ? AND `type` = ? AND `term` = ?", - local_user(), TERM_OBJ_POST, TERM_HASHTAG, $search]; - $params = ['order' => ['received' => true], - 'limit' => [$pager->getStart(), $pager->getItemsPerPage()]]; - $terms = DBA::select('term', ['oid'], $condition, $params); - - $itemids = []; - while ($term = DBA::fetch($terms)) { - $itemids[] = $term['oid']; - } - DBA::close($terms); - - if (!empty($itemids)) { - $params = ['order' => ['id' => true]]; - $items = Item::selectForUser(local_user(), [], ['id' => $itemids], $params); - $r = Item::inArray($items); - } else { - $r = []; - } - } else { - Logger::log("Start fulltext search for '".$search."'", Logger::DEBUG); - - $condition = ["(`uid` = 0 OR (`uid` = ? AND NOT `global`)) - AND `body` LIKE CONCAT('%',?,'%')", - local_user(), $search]; - $params = ['order' => ['id' => true], - 'limit' => [$pager->getStart(), $pager->getItemsPerPage()]]; - $items = Item::selectForUser(local_user(), [], $condition, $params); - $r = Item::inArray($items); - } - - if (!DBA::isResult($r)) { - info(L10n::t('No results.') . EOL); - return $o; - } - - - if ($tag) { - $title = L10n::t('Items tagged with: %s', $search); - } else { - $title = L10n::t('Results for: %s', $search); - } - - $o .= Renderer::replaceMacros(Renderer::getMarkupTemplate("section_title.tpl"),[ - '$title' => $title - ]); - - Logger::log("Start Conversation for '".$search."'", Logger::DEBUG); - $o .= conversation($a, $r, $pager, 'search', false, false, 'commented', local_user()); - - $o .= $pager->renderMinimal(count($r)); - - Logger::log("Done '".$search."'", Logger::DEBUG); - - return $o; -} diff --git a/mod/settings.php b/mod/settings.php index af4f7e2ca..b5011881c 100644 --- a/mod/settings.php +++ b/mod/settings.php @@ -35,7 +35,7 @@ function get_theme_config_file($theme) $theme = Strings::sanitizeFilePathItem($theme); $a = \get_app(); - $base_theme = defaults($a->theme_info, 'extends'); + $base_theme = $a->theme_info['extends'] ?? ''; if (file_exists("view/theme/$theme/config.php")) { return "view/theme/$theme/config.php"; @@ -115,8 +115,8 @@ function settings_init(App $a) $tabs[] = [ 'label' => L10n::t('Delegations'), - 'url' => 'delegate', - 'selected' => (($a->argc == 1) && ($a->argv[0] === 'delegate')?'active':''), + 'url' => 'settings/delegation', + 'selected' => (($a->argc > 1) && ($a->argv[1] === 'delegation')?'active':''), 'accesskey' => 'd', ]; @@ -180,11 +180,11 @@ function settings_post(App $a) if (($a->argc > 2) && ($a->argv[1] === 'oauth') && ($a->argv[2] === 'edit'||($a->argv[2] === 'add')) && !empty($_POST['submit'])) { BaseModule::checkFormSecurityTokenRedirectOnError('/settings/oauth', 'settings_oauth'); - $name = defaults($_POST, 'name' , ''); - $key = defaults($_POST, 'key' , ''); - $secret = defaults($_POST, 'secret' , ''); - $redirect = defaults($_POST, 'redirect', ''); - $icon = defaults($_POST, 'icon' , ''); + $name = $_POST['name'] ?? ''; + $key = $_POST['key'] ?? ''; + $secret = $_POST['secret'] ?? ''; + $redirect = $_POST['redirect'] ?? ''; + $icon = $_POST['icon'] ?? ''; if ($name == "" || $key == "" || $secret == "") { notice(L10n::t("Missing some important data!")); @@ -241,24 +241,21 @@ function settings_post(App $a) PConfig::set(local_user(), 'ostatus', 'default_group', $_POST['group-selection']); PConfig::set(local_user(), 'ostatus', 'legacy_contact', $_POST['legacy_contact']); } elseif (!empty($_POST['imap-submit'])) { + $mail_server = $_POST['mail_server'] ?? ''; + $mail_port = $_POST['mail_port'] ?? ''; + $mail_ssl = strtolower(trim($_POST['mail_ssl'] ?? '')); + $mail_user = $_POST['mail_user'] ?? ''; + $mail_pass = trim($_POST['mail_pass'] ?? ''); + $mail_action = trim($_POST['mail_action'] ?? ''); + $mail_movetofolder = trim($_POST['mail_movetofolder'] ?? ''); + $mail_replyto = $_POST['mail_replyto'] ?? ''; + $mail_pubmail = $_POST['mail_pubmail'] ?? ''; - $mail_server = defaults($_POST, 'mail_server', ''); - $mail_port = defaults($_POST, 'mail_port', ''); - $mail_ssl = (!empty($_POST['mail_ssl']) ? strtolower(trim($_POST['mail_ssl'])) : ''); - $mail_user = defaults($_POST, 'mail_user', ''); - $mail_pass = (!empty($_POST['mail_pass']) ? trim($_POST['mail_pass']) : ''); - $mail_action = (!empty($_POST['mail_action']) ? trim($_POST['mail_action']) : ''); - $mail_movetofolder = (!empty($_POST['mail_movetofolder']) ? trim($_POST['mail_movetofolder']) : ''); - $mail_replyto = defaults($_POST, 'mail_replyto', ''); - $mail_pubmail = defaults($_POST, 'mail_pubmail', ''); - - - $mail_disabled = ((function_exists('imap_open') && (!Config::get('system', 'imap_disabled'))) ? 0 : 1); - if (Config::get('system', 'dfrn_only')) { - $mail_disabled = 1; - } - - if (!$mail_disabled) { + if ( + !Config::get('system', 'dfrn_only') + && function_exists('imap_open') + && !Config::get('system', 'imap_disabled') + ) { $failed = false; $r = q("SELECT * FROM `mailacct` WHERE `uid` = %d LIMIT 1", intval(local_user()) @@ -1092,7 +1089,7 @@ function settings_content(App $a) if (strlen(Config::get('system', 'directory'))) { $profile_in_net_dir = Renderer::replaceMacros($opt_tpl, [ - '$field' => ['profile_in_netdirectory', L10n::t('Publish your default profile in the global social directory?'), $profile['net-publish'], L10n::t('Your profile will be published in the global friendica directories (e.g. %s). Your profile will be visible in public.', Config::get('system', 'directory'), Config::get('system', 'directory')), [L10n::t('No'), L10n::t('Yes')]] + '$field' => ['profile_in_netdirectory', L10n::t('Publish your default profile in the global social directory?'), $profile['net-publish'], L10n::t('Your profile will be published in the global friendica directories (e.g. %s). Your profile will be visible in public.', Config::get('system', 'directory'), Config::get('system', 'directory')) . " " . L10n::t("This setting also determines whether Friendica will inform search engines that your profile should be indexed or not. Third-party search engines may or may not respect this setting."), [L10n::t('No'), L10n::t('Yes')]] ]); } else { $profile_in_net_dir = ''; diff --git a/mod/subthread.php b/mod/subthread.php index 9fa1a410d..0399ac0ce 100644 --- a/mod/subthread.php +++ b/mod/subthread.php @@ -6,6 +6,7 @@ use Friendica\App; use Friendica\Core\Hook; use Friendica\Core\L10n; use Friendica\Core\Logger; +use Friendica\Core\Session; use Friendica\Core\System; use Friendica\Database\DBA; use Friendica\Model\Item; @@ -15,7 +16,7 @@ use Friendica\Util\XML; function subthread_content(App $a) { - if (!local_user() && !remote_user()) { + if (!Session::isAuthenticated()) { return; } diff --git a/mod/tagger.php b/mod/tagger.php index 2c15cdd28..bc8b71297 100644 --- a/mod/tagger.php +++ b/mod/tagger.php @@ -7,6 +7,7 @@ use Friendica\Core\Hook; use Friendica\Core\L10n; use Friendica\Core\Logger; use Friendica\Core\System; +use Friendica\Core\Session; use Friendica\Core\Worker; use Friendica\Database\DBA; use Friendica\Model\Item; @@ -16,7 +17,7 @@ use Friendica\Worker\Delivery; function tagger_content(App $a) { - if (!local_user() && !remote_user()) { + if (!Session::isAuthenticated()) { return; } diff --git a/mod/tagrm.php b/mod/tagrm.php index f6f2a9a29..3f091f298 100644 --- a/mod/tagrm.php +++ b/mod/tagrm.php @@ -22,11 +22,11 @@ function tagrm_post(App $a) } $tags = []; - foreach (defaults($_POST, 'tag', []) as $tag) { + foreach ($_POST['tag'] ?? [] as $tag) { $tags[] = hex2bin(Strings::escapeTags(trim($tag))); } - $item_id = defaults($_POST,'item', 0); + $item_id = $_POST['item'] ?? 0; update_tags($item_id, $tags); info(L10n::t('Tag(s) removed') . EOL); diff --git a/mod/uimport.php b/mod/uimport.php index 22a316155..436802265 100644 --- a/mod/uimport.php +++ b/mod/uimport.php @@ -41,14 +41,6 @@ function uimport_content(App $a) } } - - if (!empty($_SESSION['theme'])) { - unset($_SESSION['theme']); - } - if (!empty($_SESSION['mobile-theme'])) { - unset($_SESSION['mobile-theme']); - } - $tpl = Renderer::getMarkupTemplate("uimport.tpl"); return Renderer::replaceMacros($tpl, [ '$regbutt' => L10n::t('Import'), diff --git a/mod/unfollow.php b/mod/unfollow.php index a66c88aef..7afd82c98 100644 --- a/mod/unfollow.php +++ b/mod/unfollow.php @@ -25,7 +25,7 @@ function unfollow_post(App $a) } $uid = local_user(); - $url = Strings::escapeTags(trim(defaults($_REQUEST, 'url', ''))); + $url = Strings::escapeTags(trim($_REQUEST['url'] ?? '')); $condition = ["`uid` = ? AND (`rel` = ? OR `rel` = ?) AND (`nurl` = ? OR `alias` = ? OR `alias` = ?)", $uid, Contact::SHARING, Contact::FRIEND, Strings::normaliseLink($url), diff --git a/mod/videos.php b/mod/videos.php index 9e19ecf11..4174c7f48 100644 --- a/mod/videos.php +++ b/mod/videos.php @@ -10,6 +10,7 @@ use Friendica\Core\Config; use Friendica\Core\L10n; use Friendica\Core\Renderer; use Friendica\Core\System; +use Friendica\Core\Session; use Friendica\Database\DBA; use Friendica\Model\Attach; use Friendica\Model\Contact; @@ -22,11 +23,7 @@ use Friendica\Util\Security; function videos_init(App $a) { - if ($a->argc > 1) { - DFRN::autoRedir($a, $a->argv[1]); - } - - if ((Config::get('system', 'block_public')) && (!local_user()) && (!remote_user())) { + if (Config::get('system', 'block_public') && !Session::isAuthenticated()) { return; } @@ -54,9 +51,9 @@ function videos_init(App $a) $vcard_widget = Renderer::replaceMacros($tpl, [ '$name' => $profile['name'], '$photo' => $profile['photo'], - '$addr' => defaults($profile, 'addr', ''), + '$addr' => $profile['addr'] ?? '', '$account_type' => $account_type, - '$pdesc' => defaults($profile, 'pdesc', ''), + '$pdesc' => $profile['pdesc'] ?? '', ]); // If not there, create 'aside' empty @@ -114,7 +111,7 @@ function videos_content(App $a) // videos/name/video/xxxxx/edit - if ((Config::get('system', 'block_public')) && (!local_user()) && (!remote_user())) { + if (Config::get('system', 'block_public') && !Session::isAuthenticated()) { notice(L10n::t('Public access denied.') . EOL); return; } @@ -154,64 +151,25 @@ function videos_content(App $a) if ((local_user()) && (local_user() == $owner_uid)) { $can_post = true; - } elseif ($community_page && remote_user()) { - if (!empty($_SESSION['remote'])) { - foreach ($_SESSION['remote'] as $v) { - if ($v['uid'] == $owner_uid) { - $contact_id = $v['cid']; - break; - } - } - } - - if ($contact_id > 0) { - $r = q("SELECT `uid` FROM `contact` WHERE `blocked` = 0 AND `pending` = 0 AND `id` = %d AND `uid` = %d LIMIT 1", - intval($contact_id), - intval($owner_uid) - ); - - if (DBA::isResult($r)) { - $can_post = true; - $remote_contact = true; - $visitor = $contact_id; - } - } + } elseif ($community_page && !empty(Session::getRemoteContactID($owner_uid))) { + $contact_id = Session::getRemoteContactID($owner_uid); + $can_post = true; + $remote_contact = true; + $visitor = $contact_id; } - $groups = []; - // perhaps they're visiting - but not a community page, so they wouldn't have write access - if (remote_user() && (!$visitor)) { - $contact_id = 0; - - if (!empty($_SESSION['remote'])) { - foreach($_SESSION['remote'] as $v) { - if($v['uid'] == $owner_uid) { - $contact_id = $v['cid']; - break; - } - } - } - - if ($contact_id > 0) { - $groups = Group::getIdsByContactId($contact_id); - $r = q("SELECT * FROM `contact` WHERE `blocked` = 0 AND `pending` = 0 AND `id` = %d AND `uid` = %d LIMIT 1", - intval($contact_id), - intval($owner_uid) - ); - - if (DBA::isResult($r)) { - $remote_contact = true; - } - } + if (!empty(Session::getRemoteContactID($owner_uid)) && !$visitor) { + $contact_id = Session::getRemoteContactID($owner_uid); + $remote_contact = true; } - if ($a->data['user']['hidewall'] && (local_user() != $owner_uid) && (!$remote_contact)) { + if ($a->data['user']['hidewall'] && (local_user() != $owner_uid) && !$remote_contact) { notice(L10n::t('Access to this item is restricted.') . EOL); return; } - $sql_extra = Security::getPermissionsSQLByUserId($owner_uid, $remote_contact, $groups); + $sql_extra = Security::getPermissionsSQLByUserId($owner_uid); $o = ""; diff --git a/mod/wall_attach.php b/mod/wall_attach.php index c4ee33bd1..0324a5581 100644 --- a/mod/wall_attach.php +++ b/mod/wall_attach.php @@ -6,6 +6,7 @@ use Friendica\App; use Friendica\Core\Config; use Friendica\Core\L10n; +use Friendica\Core\Session; use Friendica\Database\DBA; use Friendica\Model\Attach; use Friendica\Model\User; @@ -43,35 +44,21 @@ function wall_attach_post(App $a) { $page_owner_cid = $r[0]['id']; $community_page = (($r[0]['page-flags'] == User::PAGE_FLAGS_COMMUNITY) ? true : false); - if ((local_user()) && (local_user() == $page_owner_uid)) { + if (local_user() && (local_user() == $page_owner_uid)) { $can_post = true; - } else { - if ($community_page && remote_user()) { - $contact_id = 0; + } elseif ($community_page && !empty(Session::getRemoteContactID($page_owner_uid))) { + $contact_id = Session::getRemoteContactID($page_owner_uid); + $r = q("SELECT `uid` FROM `contact` WHERE `blocked` = 0 AND `pending` = 0 AND `id` = %d AND `uid` = %d LIMIT 1", + intval($contact_id), + intval($page_owner_uid) + ); - if (is_array($_SESSION['remote'])) { - foreach ($_SESSION['remote'] as $v) { - if ($v['uid'] == $page_owner_uid) { - $contact_id = $v['cid']; - break; - } - } - } - - if ($contact_id > 0) { - $r = q("SELECT `uid` FROM `contact` WHERE `blocked` = 0 AND `pending` = 0 AND `id` = %d AND `uid` = %d LIMIT 1", - intval($contact_id), - intval($page_owner_uid) - ); - - if (DBA::isResult($r)) { - $can_post = true; - } - } + if (DBA::isResult($r)) { + $can_post = true; } } - if (! $can_post) { + if (!$can_post) { if ($r_json) { echo json_encode(['error' => L10n::t('Permission denied.')]); exit(); diff --git a/mod/wall_upload.php b/mod/wall_upload.php index a245ca739..1224b6dab 100644 --- a/mod/wall_upload.php +++ b/mod/wall_upload.php @@ -12,6 +12,7 @@ use Friendica\App; use Friendica\Core\L10n; use Friendica\Core\Logger; use Friendica\Core\System; +use Friendica\Core\Session; use Friendica\Core\Config; use Friendica\Database\DBA; use Friendica\Model\Contact; @@ -74,34 +75,21 @@ function wall_upload_post(App $a, $desktopmode = true) if ((local_user()) && (local_user() == $page_owner_uid)) { $can_post = true; - } else { - if ($community_page && remote_user()) { - $contact_id = 0; - if (is_array($_SESSION['remote'])) { - foreach ($_SESSION['remote'] as $v) { - if ($v['uid'] == $page_owner_uid) { - $contact_id = $v['cid']; - break; - } - } - } + } elseif ($community_page && !empty(Session::getRemoteContactID($page_owner_uid))) { + $contact_id = Session::getRemoteContactID($page_owner_uid); - if ($contact_id) { - $r = q("SELECT `uid` FROM `contact` - WHERE `blocked` = 0 AND `pending` = 0 - AND `id` = %d AND `uid` = %d LIMIT 1", - intval($contact_id), - intval($page_owner_uid) - ); - if (DBA::isResult($r)) { - $can_post = true; - $visitor = $contact_id; - } - } + $r = q("SELECT `uid` FROM `contact` + WHERE `blocked` = 0 AND `pending` = 0 + AND `id` = %d AND `uid` = %d LIMIT 1", + intval($contact_id), + intval($page_owner_uid) + ); + if (DBA::isResult($r)) { + $can_post = true; + $visitor = $contact_id; } } - if (!$can_post) { if ($r_json) { echo json_encode(['error' => L10n::t('Permission denied.')]); diff --git a/mod/wallmessage.php b/mod/wallmessage.php index 780230b8c..ad8ca9667 100644 --- a/mod/wallmessage.php +++ b/mod/wallmessage.php @@ -131,8 +131,8 @@ function wallmessage_content(App $a) { '$subject' => L10n::t('Subject:'), '$recipname' => $user['username'], '$nickname' => $user['nickname'], - '$subjtxt' => defaults($_REQUEST, 'subject', ''), - '$text' => defaults($_REQUEST, 'body', ''), + '$subjtxt' => $_REQUEST['subject'] ?? '', + '$text' => $_REQUEST['body'] ?? '', '$readonly' => '', '$yourmessage'=> L10n::t('Your message:'), '$parent' => '', diff --git a/mods/sample-Lighttpd.config b/mods/sample-Lighttpd.config index 1c8370060..fb8ef0b2a 100644 --- a/mods/sample-Lighttpd.config +++ b/mods/sample-Lighttpd.config @@ -102,8 +102,8 @@ $HTTP["scheme"] == "https" { # Got the following 'Drupal Clean URL'after Mike suggested trying # something along those lines, from http://drupal.org/node/1414950 url.rewrite-if-not-file = ( - "^\/([^\?]*)\?(.*)$" => "/index.php?q=$1&$2", - "^\/(.*)$" => "/index.php?q=$1" + "^\/([^\?]*)\?(.*)$" => "/index.php?pagename=$1&$2", + "^\/(.*)$" => "/index.php?pagename=$1" ) } else $HTTP["host"] !~ "(friendica.example.com|wordpress.example.com)" { diff --git a/src/App.php b/src/App.php index c7305c8c1..c6ed818dd 100644 --- a/src/App.php +++ b/src/App.php @@ -92,10 +92,10 @@ class App */ private $baseURL; - /** - * @var string The name of the current theme - */ + /** @var string The name of the current theme */ private $currentTheme; + /** @var string The name of the current mobile theme */ + private $currentMobileTheme; /** * @var Configuration The config @@ -450,10 +450,10 @@ class App } /** - * Returns the current theme name. + * Returns the current theme name. May be overriden by the mobile theme name. * - * @return string the name of the current theme - * @throws HTTPException\InternalServerErrorException + * @return string + * @throws Exception */ public function getCurrentTheme() { @@ -461,6 +461,16 @@ class App return ''; } + // Specific mobile theme override + if (($this->mode->isMobile() || $this->mode->isTablet()) && Core\Session::get('show-mobile', true)) { + $user_mobile_theme = $this->getCurrentMobileTheme(); + + // --- means same mobile theme as desktop + if (!empty($user_mobile_theme) && $user_mobile_theme !== '---') { + return $user_mobile_theme; + } + } + if (!$this->currentTheme) { $this->computeCurrentTheme(); } @@ -468,13 +478,37 @@ class App return $this->currentTheme; } + /** + * Returns the current mobile theme name. + * + * @return string + * @throws Exception + */ + public function getCurrentMobileTheme() + { + if ($this->mode->isInstall()) { + return ''; + } + + if (is_null($this->currentMobileTheme)) { + $this->computeCurrentMobileTheme(); + } + + return $this->currentMobileTheme; + } + public function setCurrentTheme($theme) { $this->currentTheme = $theme; } + public function setCurrentMobileTheme($theme) + { + $this->currentMobileTheme = $theme; + } + /** - * Computes the current theme name based on the node settings, the user settings and the device type + * Computes the current theme name based on the node settings, the page owner settings and the user settings * * @throws Exception */ @@ -486,7 +520,7 @@ class App } // Sane default - $this->currentTheme = $system_theme; + $this->setCurrentTheme($system_theme); $page_theme = null; // Find the theme that belongs to the user whose stuff we are looking at @@ -499,24 +533,7 @@ class App } } - $user_theme = Core\Session::get('theme', $system_theme); - - // Specific mobile theme override - if (($this->is_mobile || $this->is_tablet) && Core\Session::get('show-mobile', true)) { - $system_mobile_theme = $this->config->get('system', 'mobile-theme'); - $user_mobile_theme = Core\Session::get('mobile-theme', $system_mobile_theme); - - // --- means same mobile theme as desktop - if (!empty($user_mobile_theme) && $user_mobile_theme !== '---') { - $user_theme = $user_mobile_theme; - } - } - - if ($page_theme) { - $theme_name = $page_theme; - } else { - $theme_name = $user_theme; - } + $theme_name = $page_theme ?: Core\Session::get('theme', $system_theme); $theme_name = Strings::sanitizeFilePathItem($theme_name); if ($theme_name @@ -524,7 +541,40 @@ class App && (file_exists('view/theme/' . $theme_name . '/style.css') || file_exists('view/theme/' . $theme_name . '/style.php')) ) { - $this->currentTheme = $theme_name; + $this->setCurrentTheme($theme_name); + } + } + + /** + * Computes the current mobile theme name based on the node settings, the page owner settings and the user settings + */ + private function computeCurrentMobileTheme() + { + $system_mobile_theme = $this->config->get('system', 'mobile-theme', ''); + + // Sane default + $this->setCurrentMobileTheme($system_mobile_theme); + + $page_mobile_theme = null; + // Find the theme that belongs to the user whose stuff we are looking at + if ($this->profile_uid && ($this->profile_uid != local_user())) { + // Allow folks to override user themes and always use their own on their own site. + // This works only if the user is on the same server + if (!Core\PConfig::get(local_user(), 'system', 'always_my_theme')) { + $page_mobile_theme = Core\PConfig::get($this->profile_uid, 'system', 'mobile-theme'); + } + } + + $mobile_theme_name = $page_mobile_theme ?: Core\Session::get('mobile-theme', $system_mobile_theme); + + $mobile_theme_name = Strings::sanitizeFilePathItem($mobile_theme_name); + if ($mobile_theme_name == '---' + || + in_array($mobile_theme_name, Theme::getAllowedList()) + && (file_exists('view/theme/' . $mobile_theme_name . '/style.css') + || file_exists('view/theme/' . $mobile_theme_name . '/style.php')) + ) { + $this->setCurrentMobileTheme($mobile_theme_name); } } @@ -534,7 +584,7 @@ class App * Provide a sane default if nothing is chosen or the specified theme does not exist. * * @return string - * @throws HTTPException\InternalServerErrorException + * @throws Exception */ public function getCurrentThemeStylesheetPath() { @@ -587,7 +637,11 @@ class App * * This probably should change to limit the size of this monster method. * - * @param App\Module $module The determined module + * @param App\Module $module The determined module + * @param App\Router $router + * @param PConfiguration $pconfig + * @throws HTTPException\InternalServerErrorException + * @throws \ImagickException */ public function runFrontend(App\Module $module, App\Router $router, PConfiguration $pconfig) { @@ -733,8 +787,7 @@ class App $module = $module->determineClass($this->args, $router, $this->config); // Let the module run it's internal process (init, get, post, ...) - $module->run($this->l10n, $this, $this->logger, $this->getCurrentTheme(), $_SERVER, $_POST); - + $module->run($this->l10n, $this, $this->logger, $_SERVER, $_POST); } catch (HTTPException $e) { ModuleHTTPException::rawContent($e); } diff --git a/src/App/Arguments.php b/src/App/Arguments.php index 8047186a0..e65309f6b 100644 --- a/src/App/Arguments.php +++ b/src/App/Arguments.php @@ -70,7 +70,7 @@ class Arguments /** * Returns the value of a argv key - * @todo there are a lot of $a->argv usages in combination with defaults() which can be replaced with this method + * @todo there are a lot of $a->argv usages in combination with ?? which can be replaced with this method * * @param int $position the position of the argument * @param mixed $default the default value if not found diff --git a/src/App/BaseURL.php b/src/App/BaseURL.php index ad5fd0d4e..8d76a0d2d 100644 --- a/src/App/BaseURL.php +++ b/src/App/BaseURL.php @@ -338,12 +338,12 @@ class BaseURL /* Relative script path to the web server root * Not all of those $_SERVER properties can be present, so we do by inverse priority order */ - $relative_script_path = ''; - $relative_script_path = defaults($this->server, 'REDIRECT_URL', $relative_script_path); - $relative_script_path = defaults($this->server, 'REDIRECT_URI', $relative_script_path); - $relative_script_path = defaults($this->server, 'REDIRECT_SCRIPT_URL', $relative_script_path); - $relative_script_path = defaults($this->server, 'SCRIPT_URL', $relative_script_path); - $relative_script_path = defaults($this->server, 'REQUEST_URI', $relative_script_path); + $relative_script_path = + ($this->server['REDIRECT_URL'] ?? '') ?: + ($this->server['REDIRECT_URI'] ?? '') ?: + ($this->server['REDIRECT_SCRIPT_URL'] ?? '') ?: + ($this->server['SCRIPT_URL'] ?? '') ?: + $this->server['REQUEST_URI'] ?? ''; /* $relative_script_path gives /relative/path/to/friendica/module/parameter * QUERY_STRING gives pagename=module/parameter diff --git a/src/App/Mode.php b/src/App/Mode.php index f3f269b87..0a5f98e24 100644 --- a/src/App/Mode.php +++ b/src/App/Mode.php @@ -106,15 +106,16 @@ class Mode /** * Checks if the site is called via a backend process * + * @param bool $isBackend True, if the call is from a backend script (daemon, worker, ...) * @param Module $module The pre-loaded module (just name, not class!) * @param array $server The $_SERVER variable * @param MobileDetect $mobileDetect The mobile detection library * * @return Mode returns the determined mode */ - public function determineRunMode(Module $module, array $server, MobileDetect $mobileDetect) + public function determineRunMode(bool $isBackend, Module $module, array $server, MobileDetect $mobileDetect) { - $isBackend = basename(($server['PHP_SELF'] ?? ''), '.php') !== 'index' || + $isBackend = $isBackend || $module->isBackend(); $isMobile = $mobileDetect->isMobile(); $isTablet = $mobileDetect->isTablet(); diff --git a/src/App/Module.php b/src/App/Module.php index 726c8c00a..33a9b2fc2 100644 --- a/src/App/Module.php +++ b/src/App/Module.php @@ -7,7 +7,10 @@ use Friendica\BaseObject; use Friendica\Core; use Friendica\LegacyModule; use Friendica\Module\Home; -use Friendica\Module\PageNotFound; +use Friendica\Module\HTTPException\MethodNotAllowed; +use Friendica\Module\HTTPException\PageNotFound; +use Friendica\Network\HTTPException\MethodNotAllowedException; +use Friendica\Network\HTTPException\NotFoundException; use Psr\Log\LoggerInterface; /** @@ -138,51 +141,49 @@ class Module * * @return Module The determined module of this call * - * @throws \Friendica\Network\HTTPException\InternalServerErrorException + * @throws \Exception */ public function determineClass(Arguments $args, Router $router, Core\Config\Configuration $config) { $printNotAllowedAddon = false; + $module_class = null; /** * ROUTING * * From the request URL, routing consists of obtaining the name of a BaseModule-extending class of which the * post() and/or content() static methods can be respectively called to produce a data change or an output. **/ - - // First we try explicit routes defined in App\Router - $router->collectRoutes(); - - $data = $router->getRouteCollector(); - Core\Hook::callAll('route_collection', $data); - - $module_class = $router->getModuleClass($args->getCommand()); - - // Then we try addon-provided modules that we wrap in the LegacyModule class - if (!$module_class && Core\Addon::isEnabled($this->module) && file_exists("addon/{$this->module}/{$this->module}.php")) { - //Check if module is an app and if public access to apps is allowed or not - $privateapps = $config->get('config', 'private_addons', false); - if ((!local_user()) && Core\Hook::isAddonApp($this->module) && $privateapps) { - $printNotAllowedAddon = true; - } else { - include_once "addon/{$this->module}/{$this->module}.php"; - if (function_exists($this->module . '_module')) { - LegacyModule::setModuleFile("addon/{$this->module}/{$this->module}.php"); - $module_class = LegacyModule::class; + try { + $module_class = $router->getModuleClass($args->getCommand()); + } catch (MethodNotAllowedException $e) { + $module_class = MethodNotAllowed::class; + } catch (NotFoundException $e) { + // Then we try addon-provided modules that we wrap in the LegacyModule class + if (Core\Addon::isEnabled($this->module) && file_exists("addon/{$this->module}/{$this->module}.php")) { + //Check if module is an app and if public access to apps is allowed or not + $privateapps = $config->get('config', 'private_addons', false); + if ((!local_user()) && Core\Hook::isAddonApp($this->module) && $privateapps) { + $printNotAllowedAddon = true; + } else { + include_once "addon/{$this->module}/{$this->module}.php"; + if (function_exists($this->module . '_module')) { + LegacyModule::setModuleFile("addon/{$this->module}/{$this->module}.php"); + $module_class = LegacyModule::class; + } } } - } - /* Finally, we look for a 'standard' program module in the 'mod' directory - * We emulate a Module class through the LegacyModule class - */ - if (!$module_class && file_exists("mod/{$this->module}.php")) { - LegacyModule::setModuleFile("mod/{$this->module}.php"); - $module_class = LegacyModule::class; - } + /* Finally, we look for a 'standard' program module in the 'mod' directory + * We emulate a Module class through the LegacyModule class + */ + if (!$module_class && file_exists("mod/{$this->module}.php")) { + LegacyModule::setModuleFile("mod/{$this->module}.php"); + $module_class = LegacyModule::class; + } - $module_class = !isset($module_class) ? PageNotFound::class : $module_class; + $module_class = $module_class ?: PageNotFound::class; + } return new Module($this->module, $module_class, $this->isBackend, $printNotAllowedAddon); } @@ -193,13 +194,12 @@ class Module * @param Core\L10n\L10n $l10n The L10n instance * @param App $app The whole Friendica app (for method arguments) * @param LoggerInterface $logger The Friendica logger - * @param string $currentTheme The chosen theme * @param array $server The $_SERVER variable * @param array $post The $_POST variables * * @throws \Friendica\Network\HTTPException\InternalServerErrorException */ - public function run(Core\L10n\L10n $l10n, App $app, LoggerInterface $logger, string $currentTheme, array $server, array $post) + public function run(Core\L10n\L10n $l10n, App $app, LoggerInterface $logger, array $server, array $post) { if ($this->printNotAllowedAddon) { info($l10n->t("You must be logged in to use addons. ")); @@ -239,17 +239,6 @@ class Module // This endpoint doesn't need any theme initialization or other comparable stuff. call_user_func([$this->module_class, 'rawContent']); - // Load current theme info after module has been initialized as theme could have been set in module - $theme_info_file = 'view/theme/' . $currentTheme . '/theme.php'; - if (file_exists($theme_info_file)) { - require_once $theme_info_file; - } - - if (function_exists(str_replace('-', '_', $currentTheme) . '_init')) { - $func = str_replace('-', '_', $currentTheme) . '_init'; - $func($app); - } - if ($server['REQUEST_METHOD'] === 'POST') { Core\Hook::callAll($this->module . '_mod_post', $post); call_user_func([$this->module_class, 'post']); diff --git a/src/App/Page.php b/src/App/Page.php index 0bccbd739..ea94f9cfe 100644 --- a/src/App/Page.php +++ b/src/App/Page.php @@ -364,6 +364,18 @@ class Page implements ArrayAccess */ $this->initContent($module, $mode); + // Load current theme info after module has been initialized as theme could have been set in module + $currentTheme = $app->getCurrentTheme(); + $theme_info_file = 'view/theme/' . $currentTheme . '/theme.php'; + if (file_exists($theme_info_file)) { + require_once $theme_info_file; + } + + if (function_exists(str_replace('-', '_', $currentTheme) . '_init')) { + $func = str_replace('-', '_', $currentTheme) . '_init'; + $func($app); + } + /* Create the page head after setting the language * and getting any auth credentials. * diff --git a/src/App/Router.php b/src/App/Router.php index 50b208792..f723321ac 100644 --- a/src/App/Router.php +++ b/src/App/Router.php @@ -7,7 +7,9 @@ use FastRoute\DataGenerator\GroupCountBased; use FastRoute\Dispatcher; use FastRoute\RouteCollector; use FastRoute\RouteParser\Std; -use Friendica\Module; +use Friendica\Core\Hook; +use Friendica\Core\L10n; +use Friendica\Network\HTTPException; /** * Wrapper for FastRoute\Router @@ -21,212 +23,129 @@ use Friendica\Module; */ class Router { + const POST = 'POST'; + const GET = 'GET'; + + const ALLOWED_METHODS = [ + self::POST, + self::GET, + ]; + /** @var RouteCollector */ protected $routeCollector; /** - * Static declaration of Friendica routes. - * - * Supports: - * - Route groups - * - Variable parts - * Disregards: - * - HTTP method other than GET - * - Named parameters - * - * Handler must be the name of a class extending Friendica\BaseModule. - * - * @brief Static declaration of Friendica routes. + * @var string The HTTP method */ - public function collectRoutes() + private $httpMethod; + + /** + * @param array $server The $_SERVER variable + * @param RouteCollector|null $routeCollector Optional the loaded Route collector + */ + public function __construct(array $server, RouteCollector $routeCollector = null) { - $this->routeCollector->addRoute(['GET'], '[/]', Module\Home::class); - $this->routeCollector->addGroup('/.well-known', function (RouteCollector $collector) { - $collector->addRoute(['GET'], '/host-meta' , Module\WellKnown\HostMeta::class); - $collector->addRoute(['GET'], '/nodeinfo[/1.0]' , Module\NodeInfo::class); - $collector->addRoute(['GET'], '/webfinger' , Module\Xrd::class); - $collector->addRoute(['GET'], '/x-social-relay' , Module\WellKnown\XSocialRelay::class); - }); - $this->routeCollector->addGroup('/2fa', function (RouteCollector $collector) { - $collector->addRoute(['GET', 'POST'], '[/]' , Module\TwoFactor\Verify::class); - $collector->addRoute(['GET', 'POST'], '/recovery' , Module\TwoFactor\Recovery::class); - }); - $this->routeCollector->addGroup('/admin', function (RouteCollector $collector) { - $collector->addRoute(['GET'] , '[/]' , Module\Admin\Summary::class); + $httpMethod = $server['REQUEST_METHOD'] ?? self::GET; + $this->httpMethod = in_array($httpMethod, self::ALLOWED_METHODS) ? $httpMethod : self::GET; - $collector->addRoute(['GET', 'POST'], '/addons' , Module\Admin\Addons\Index::class); - $collector->addRoute(['GET', 'POST'], '/addons/{addon}' , Module\Admin\Addons\Details::class); - - $collector->addRoute(['GET', 'POST'], '/blocklist/contact' , Module\Admin\Blocklist\Contact::class); - $collector->addRoute(['GET', 'POST'], '/blocklist/server' , Module\Admin\Blocklist\Server::class); - - $collector->addRoute(['GET'] , '/dbsync[/check]' , Module\Admin\DBSync::class); - $collector->addRoute(['GET'] , '/dbsync/{update:\d+}' , Module\Admin\DBSync::class); - $collector->addRoute(['GET'] , '/dbsync/mark/{update:\d+}', Module\Admin\DBSync::class); - - $collector->addRoute(['GET', 'POST'], '/features' , Module\Admin\Features::class); - $collector->addRoute(['GET'] , '/federation' , Module\Admin\Federation::class); - - $collector->addRoute(['GET', 'POST'], '/item/delete' , Module\Admin\Item\Delete::class); - $collector->addRoute(['GET', 'POST'], '/item/source[/{guid}]' , Module\Admin\Item\Source::class); - - $collector->addRoute(['GET'] , '/logs/view' , Module\Admin\Logs\View::class); - $collector->addRoute(['GET', 'POST'], '/logs' , Module\Admin\Logs\Settings::class); - - $collector->addRoute(['GET'] , '/phpinfo' , Module\Admin\PhpInfo::class); - - $collector->addRoute(['GET'] , '/queue[/deferred]' , Module\Admin\Queue::class); - - $collector->addRoute(['GET', 'POST'], '/site' , Module\Admin\Site::class); - - $collector->addRoute(['GET', 'POST'], '/themes' , Module\Admin\Themes\Index::class); - $collector->addRoute(['GET', 'POST'], '/themes/{theme}' , Module\Admin\Themes\Details::class); - $collector->addRoute(['GET', 'POST'], '/themes/{theme}/embed' , Module\Admin\Themes\Embed::class); - - $collector->addRoute(['GET', 'POST'], '/tos' , Module\Admin\Tos::class); - - $collector->addRoute(['GET', 'POST'], '/users[/{action}/{uid}]' , Module\Admin\Users::class); - }); - $this->routeCollector->addRoute(['GET'], '/amcd', Module\AccountManagementControlDocument::class); - $this->routeCollector->addRoute(['GET'], '/acctlink', Module\Acctlink::class); - $this->routeCollector->addRoute(['GET'], '/allfriends/{id:\d+}', Module\AllFriends::class); - $this->routeCollector->addRoute(['GET'], '/apps', Module\Apps::class); - $this->routeCollector->addRoute(['GET'], '/attach/{item:\d+}', Module\Attach::class); - $this->routeCollector->addRoute(['GET'], '/babel', Module\Debug\Babel::class); - $this->routeCollector->addRoute(['GET'], '/bookmarklet', Module\Bookmarklet::class); - $this->routeCollector->addRoute(['GET', 'POST'], '/compose[/{type}]', Module\Item\Compose::class); - $this->routeCollector->addGroup('/contact', function (RouteCollector $collector) { - $collector->addRoute(['GET'], '[/]', Module\Contact::class); - $collector->addRoute(['GET', 'POST'], '/{id:\d+}[/]', Module\Contact::class); - $collector->addRoute(['GET'], '/{id:\d+}/archive', Module\Contact::class); - $collector->addRoute(['GET'], '/{id:\d+}/block', Module\Contact::class); - $collector->addRoute(['GET'], '/{id:\d+}/conversations', Module\Contact::class); - $collector->addRoute(['GET'], '/{id:\d+}/drop', Module\Contact::class); - $collector->addRoute(['GET'], '/{id:\d+}/ignore', Module\Contact::class); - $collector->addRoute(['GET'], '/{id:\d+}/posts', Module\Contact::class); - $collector->addRoute(['GET'], '/{id:\d+}/update', Module\Contact::class); - $collector->addRoute(['GET'], '/{id:\d+}/updateprofile', Module\Contact::class); - $collector->addRoute(['GET'], '/archived', Module\Contact::class); - $collector->addRoute(['GET', 'POST'], '/batch', Module\Contact::class); - $collector->addRoute(['GET'], '/blocked', Module\Contact::class); - $collector->addRoute(['GET'], '/hidden', Module\Contact::class); - $collector->addRoute(['GET'], '/ignored', Module\Contact::class); - }); - $this->routeCollector->addRoute(['GET'], '/credits', Module\Credits::class); - $this->routeCollector->addRoute(['GET'], '/dirfind', Module\Search\Directory::class); - $this->routeCollector->addRoute(['GET'], '/directory', Module\Directory::class); - $this->routeCollector->addGroup('/feed', function (RouteCollector $collector) { - $collector->addRoute(['GET'], '/{nickname}', Module\Feed::class); - $collector->addRoute(['GET'], '/{nickname}/posts', Module\Feed::class); - $collector->addRoute(['GET'], '/{nickname}/comments', Module\Feed::class); - $collector->addRoute(['GET'], '/{nickname}/replies', Module\Feed::class); - $collector->addRoute(['GET'], '/{nickname}/activity', Module\Feed::class); - }); - $this->routeCollector->addRoute(['GET'], '/feedtest', Module\Debug\Feed::class); - $this->routeCollector->addGroup('/fetch', function (RouteCollector $collector) { - $collector->addRoute(['GET'], '/post/{guid}', Module\Diaspora\Fetch::class); - $collector->addRoute(['GET'], '/status_message/{guid}', Module\Diaspora\Fetch::class); - $collector->addRoute(['GET'], '/reshare/{guid}', Module\Diaspora\Fetch::class); - }); - $this->routeCollector->addRoute(['GET'], '/filer[/{id:\d+}]', Module\Filer\SaveTag::class); - $this->routeCollector->addRoute(['GET'], '/filerm/{id:\d+}', Module\Filer\RemoveTag::class); - $this->routeCollector->addRoute(['GET', 'POST'], '/follow_confirm', Module\FollowConfirm::class); - $this->routeCollector->addRoute(['GET'], '/followers/{owner}', Module\Followers::class); - $this->routeCollector->addRoute(['GET'], '/following/{owner}', Module\Following::class); - $this->routeCollector->addRoute(['GET'], '/friendica[/json]', Module\Friendica::class); - $this->routeCollector->addGroup('/group', function (RouteCollector $collector) { - $collector->addRoute(['GET', 'POST'], '[/]', Module\Group::class); - $collector->addRoute(['GET', 'POST'], '/{group:\d+}', Module\Group::class); - $collector->addRoute(['GET', 'POST'], '/none', Module\Group::class); - $collector->addRoute(['GET', 'POST'], '/new', Module\Group::class); - $collector->addRoute(['GET', 'POST'], '/drop/{group:\d+}', Module\Group::class); - $collector->addRoute(['GET', 'POST'], '/{group:\d+}/{contact:\d+}', Module\Group::class); - - $collector->addRoute(['GET', 'POST'], '/{group:\d+}/add/{contact:\d+}', Module\Group::class); - $collector->addRoute(['GET', 'POST'], '/{group:\d+}/remove/{contact:\d+}', Module\Group::class); - }); - $this->routeCollector->addRoute(['GET'], '/hashtag', Module\Hashtag::class); - $this->routeCollector->addRoute(['GET'], '/home', Module\Home::class); - $this->routeCollector->addRoute(['GET'], '/help[/{doc:.+}]', Module\Help::class); - $this->routeCollector->addRoute(['GET'], '/inbox[/{nickname}]', Module\Inbox::class); - $this->routeCollector->addRoute(['GET', 'POST'], '/invite', Module\Invite::class); - $this->routeCollector->addGroup('/install', function (RouteCollector $collector) { - $collector->addRoute(['GET', 'POST'], '[/]', Module\Install::class); - $collector->addRoute(['GET'], '/testrewrite', Module\Install::class); - }); - $this->routeCollector->addRoute(['GET'], '/like/{item:\d+}', Module\Like::class); - $this->routeCollector->addRoute(['GET', 'POST'], '/localtime', Module\Debug\Localtime::class); - $this->routeCollector->addRoute(['GET', 'POST'], '/login', Module\Login::class); - $this->routeCollector->addRoute(['GET', 'POST'], '/logout', Module\Logout::class); - $this->routeCollector->addRoute(['GET'], '/magic', Module\Magic::class); - $this->routeCollector->addRoute(['GET'], '/maintenance', Module\Maintenance::class); - $this->routeCollector->addRoute(['GET'], '/manifest', Module\Manifest::class); - $this->routeCollector->addRoute(['GET'], '/modexp/{nick}', Module\PublicRSAKey::class); - $this->routeCollector->addRoute(['GET'], '/newmember', Module\Welcome::class); - $this->routeCollector->addRoute(['GET'], '/nodeinfo/1.0', Module\NodeInfo::class); - $this->routeCollector->addRoute(['GET'], '/nogroup', Module\Group::class); - $this->routeCollector->addGroup('/notify', function (RouteCollector $collector) { - $collector->addRoute(['GET'], '[/]', Module\Notifications\Notify::class); - $collector->addRoute(['GET'], '/view/{id:\d+}', Module\Notifications\Notify::class); - $collector->addRoute(['GET'], '/mark/all', Module\Notifications\Notify::class); - }); - $this->routeCollector->addRoute(['GET'], '/objects/{guid}', Module\Objects::class); - $this->routeCollector->addGroup('/oembed', function (RouteCollector $collector) { - $collector->addRoute(['GET'], '/b2h', Module\Oembed::class); - $collector->addRoute(['GET'], '/h2b', Module\Oembed::class); - $collector->addRoute(['GET'], '/{hash}', Module\Oembed::class); - }); - $this->routeCollector->addRoute(['GET'], '/outbox/{owner}', Module\Outbox::class); - $this->routeCollector->addRoute(['GET'], '/owa', Module\Owa::class); - $this->routeCollector->addRoute(['GET'], '/opensearch', Module\OpenSearch::class); - $this->routeCollector->addGroup('/photo', function (RouteCollector $collector) { - $collector->addRoute(['GET'], '/{name}', Module\Photo::class); - $collector->addRoute(['GET'], '/{type}/{name}', Module\Photo::class); - $collector->addRoute(['GET'], '/{type}/{customize}/{name}', Module\Photo::class); - }); - $this->routeCollector->addRoute(['GET'], '/pretheme', Module\ThemeDetails::class); - $this->routeCollector->addRoute(['GET'], '/probe', Module\Debug\Probe::class); - $this->routeCollector->addGroup('/profile', function (RouteCollector $collector) { - $collector->addRoute(['GET'], '/{nickname}', Module\Profile::class); - $collector->addRoute(['GET'], '/{nickname}/{to:\d{4}-\d{2}-\d{2}}/{from:\d{4}-\d{2}-\d{2}}', Module\Profile::class); - $collector->addRoute(['GET'], '/{nickname}/contacts[/{type}]', Module\Profile\Contacts::class); - $collector->addRoute(['GET'], '/{profile:\d+}/view', Module\Profile::class); - }); - $this->routeCollector->addGroup('/proxy', function (RouteCollector $collector) { - $collector->addRoute(['GET'], '[/]' , Module\Proxy::class); - $collector->addRoute(['GET'], '/{url}' , Module\Proxy::class); - $collector->addRoute(['GET'], '/{sub1}/{url}' , Module\Proxy::class); - $collector->addRoute(['GET'], '/{sub1}/{sub2}/{url}' , Module\Proxy::class); - }); - - $this->routeCollector->addGroup('/settings', function (RouteCollector $collector) { - $collector->addGroup('/2fa', function (RouteCollector $collector) { - $collector->addRoute(['GET', 'POST'], '[/]' , Module\Settings\TwoFactor\Index::class); - $collector->addRoute(['GET', 'POST'], '/recovery' , Module\Settings\TwoFactor\Recovery::class); - $collector->addRoute(['GET', 'POST'], '/app_specific' , Module\Settings\TwoFactor\AppSpecific::class); - $collector->addRoute(['GET', 'POST'], '/verify' , Module\Settings\TwoFactor\Verify::class); - }); - }); - $this->routeCollector->addRoute(['GET'], '/randprof', Module\RandomProfile::class); - $this->routeCollector->addRoute(['GET', 'POST'], '/register', Module\Register::class); - $this->routeCollector->addRoute(['GET'], '/robots.txt', Module\RobotsTxt::class); - $this->routeCollector->addRoute(['GET'], '/rsd.xml', Module\ReallySimpleDiscovery::class); - $this->routeCollector->addRoute(['GET'], '/smilies[/json]', Module\Smilies::class); - $this->routeCollector->addRoute(['GET'], '/statistics.json', Module\Statistics::class); - $this->routeCollector->addRoute(['GET'], '/starred/{item:\d+}', Module\Starred::class); - $this->routeCollector->addRoute(['GET'], '/toggle_mobile', Module\ToggleMobile::class); - $this->routeCollector->addRoute(['GET'], '/tos', Module\Tos::class); - $this->routeCollector->addRoute(['GET'], '/view/theme/{theme}/style.pcss', Module\Theme::class); - $this->routeCollector->addRoute(['GET'], '/viewsrc/{item:\d+}', Module\Debug\ItemBody::class); - $this->routeCollector->addRoute(['GET'], '/webfinger', Module\Debug\WebFinger::class); - $this->routeCollector->addRoute(['GET'], '/xrd', Module\Xrd::class); + $this->routeCollector = isset($routeCollector) ? + $routeCollector : + new RouteCollector(new Std(), new GroupCountBased()); } - public function __construct() + /** + * @param array $routes The routes to add to the Router + * + * @return self The router instance with the loaded routes + * + * @throws HTTPException\InternalServerErrorException In case of invalid configs + */ + public function addRoutes(array $routes) { - $this->routeCollector = new RouteCollector(new Std(), new GroupCountBased()); + $routeCollector = (isset($this->routeCollector) ? + $this->routeCollector : + new RouteCollector(new Std(), new GroupCountBased())); + + foreach ($routes as $route => $config) { + if ($this->isGroup($config)) { + $this->addGroup($route, $config, $routeCollector); + } elseif ($this->isRoute($config)) { + $routeCollector->addRoute($config[1], $route, $config[0]); + } else { + throw new HTTPException\InternalServerErrorException("Wrong route config for route '" . print_r($route, true) . "'"); + } + } + + $this->routeCollector = $routeCollector; + + return $this; } + /** + * Adds a group of routes to a given group + * + * @param string $groupRoute The route of the group + * @param array $routes The routes of the group + * @param RouteCollector $routeCollector The route collector to add this group + */ + private function addGroup(string $groupRoute, array $routes, RouteCollector $routeCollector) + { + $routeCollector->addGroup($groupRoute, function (RouteCollector $routeCollector) use ($routes) { + foreach ($routes as $route => $config) { + if ($this->isGroup($config)) { + $this->addGroup($route, $config, $routeCollector); + } elseif ($this->isRoute($config)) { + $routeCollector->addRoute($config[1], $route, $config[0]); + }else { + throw new HTTPException\InternalServerErrorException("Wrong route config for route '" . print_r($route, true) . "'"); + } + } + }); + } + + /** + * Returns true in case the config is a group config + * + * @param array $config + * + * @return bool + */ + private function isGroup(array $config) + { + return + is_array($config) && + is_string(array_keys($config)[0]) && + // This entry should NOT be a BaseModule + (substr(array_keys($config)[0], 0, strlen('Friendica\Module')) !== 'Friendica\Module') && + // The second argument is an array (another routes) + is_array(array_values($config)[0]); + } + + /** + * Returns true in case the config is a route config + * + * @param array $config + * + * @return bool + */ + private function isRoute(array $config) + { + return + // The config array should at least have one entry + !empty($config[0]) && + // This entry should be a BaseModule + (substr($config[0], 0, strlen('Friendica\Module')) === 'Friendica\Module') && + // Either there is no other argument + (empty($config[1]) || + // Or the second argument is an array (HTTP-Methods) + is_array($config[1])); + } + + /** + * The current route collector + * + * @return RouteCollector|null + */ public function getRouteCollector() { return $this->routeCollector; @@ -236,21 +155,31 @@ class Router * Returns the relevant module class name for the given page URI or NULL if no route rule matched. * * @param string $cmd The path component of the request URL without the query string - * @return string|null A Friendica\BaseModule-extending class name if a route rule matched + * + * @return string A Friendica\BaseModule-extending class name if a route rule matched + * + * @throws HTTPException\InternalServerErrorException + * @throws HTTPException\MethodNotAllowedException If a rule matched but the method didn't + * @throws HTTPException\NotFoundException If no rule matched */ public function getModuleClass($cmd) { + // Add routes from addons + Hook::callAll('route_collection', $this->routeCollector); + $cmd = '/' . ltrim($cmd, '/'); $dispatcher = new \FastRoute\Dispatcher\GroupCountBased($this->routeCollector->getData()); $moduleClass = null; - // @TODO: Enable method-specific modules - $httpMethod = 'GET'; - $routeInfo = $dispatcher->dispatch($httpMethod, $cmd); + $routeInfo = $dispatcher->dispatch($this->httpMethod, $cmd); if ($routeInfo[0] === Dispatcher::FOUND) { $moduleClass = $routeInfo[1]; + } elseif ($routeInfo[0] === Dispatcher::METHOD_NOT_ALLOWED) { + throw new HTTPException\MethodNotAllowedException(L10n::t('Method not allowed for this module. Allowed method(s): %s', implode(', ', $routeInfo[1]))); + } else { + throw new HTTPException\NotFoundException(L10n::t('Page not found.')); } return $moduleClass; diff --git a/src/BaseModule.php b/src/BaseModule.php index dd9059bfb..5185771d1 100644 --- a/src/BaseModule.php +++ b/src/BaseModule.php @@ -4,7 +4,6 @@ namespace Friendica; use Friendica\Core\L10n; use Friendica\Core\Logger; -use Friendica\Core\System; /** * All modules in Friendica should extend BaseModule, although not all modules @@ -121,7 +120,7 @@ abstract class BaseModule extends BaseObject $a = \get_app(); $x = explode('.', $hash); - if (time() > (IntVal($x[0]) + $max_livetime)) { + if (time() > (intval($x[0]) + $max_livetime)) { return false; } diff --git a/src/BaseObject.php b/src/BaseObject.php index fcec89bb4..996824f4a 100644 --- a/src/BaseObject.php +++ b/src/BaseObject.php @@ -60,7 +60,7 @@ class BaseObject throw new InternalServerErrorException('DICE isn\'t initialized.'); } - if (class_exists($name) || interface_exists($name )) { + if (class_exists($name) || interface_exists($name)) { return self::$dice->create($name); } else { throw new InternalServerErrorException('Class \'' . $name . '\' isn\'t valid.'); diff --git a/src/Console/AutomaticInstallation.php b/src/Console/AutomaticInstallation.php index 0f1e7a742..8815b9879 100644 --- a/src/Console/AutomaticInstallation.php +++ b/src/Console/AutomaticInstallation.php @@ -129,16 +129,13 @@ HELP; $config_file = $this->getOption(['f', 'file']); if (!empty($config_file)) { - if ($config_file != 'config' . DIRECTORY_SEPARATOR . 'local.config.php') { - // Copy config file - $this->out("Copying config file...\n"); - if (!copy($basePathConf . DIRECTORY_SEPARATOR . $config_file, $basePathConf . DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR . 'local.config.php')) { - throw new RuntimeException("ERROR: Saving config file failed. Please copy '$config_file' to '" . $basePathConf . "'" . DIRECTORY_SEPARATOR . "config" . DIRECTORY_SEPARATOR . "local.config.php' manually.\n"); - } + + if (!file_exists($config_file)) { + throw new RuntimeException("ERROR: Config file does not exist.\n"); } //reload the config cache - $loader = new ConfigFileLoader($basePathConf); + $loader = new ConfigFileLoader($config_file); $loader->setupCache($configCache); } else { @@ -195,7 +192,7 @@ HELP; $installer->createConfig($configCache); } - $this->out(" Complete!\n\n"); + $this->out("Complete!\n\n"); // Check database connection $this->out("Checking database...\n"); @@ -219,6 +216,14 @@ HELP; throw new RuntimeException($errorMessage); } + if (!empty($config_file) && $config_file != 'config' . DIRECTORY_SEPARATOR . 'local.config.php') { + // Copy config file + $this->out("Copying config file...\n"); + if (!copy($basePathConf . DIRECTORY_SEPARATOR . $config_file, $basePathConf . DIRECTORY_SEPARATOR . 'config' . DIRECTORY_SEPARATOR . 'local.config.php')) { + throw new RuntimeException("ERROR: Saving config file failed. Please copy '$config_file' to '" . $basePathConf . "'" . DIRECTORY_SEPARATOR . "config" . DIRECTORY_SEPARATOR . "local.config.php' manually.\n"); + } + } + $this->out(" Complete!\n\n"); // Install theme diff --git a/src/Content/ForumManager.php b/src/Content/ForumManager.php index 9ea8cc449..1b2927316 100644 --- a/src/Content/ForumManager.php +++ b/src/Content/ForumManager.php @@ -43,7 +43,7 @@ class ForumManager $params = ['order' => ['name']]; } - $condition_str = "`network` = ? AND `uid` = ? AND NOT `blocked` AND NOT `pending` AND NOT `archive` AND "; + $condition_str = "`network` IN (?, ?) AND `uid` = ? AND NOT `blocked` AND NOT `pending` AND NOT `archive` AND "; if ($showprivate) { $condition_str .= '(`forum` OR `prv`)'; @@ -58,7 +58,7 @@ class ForumManager $forumlist = []; $fields = ['id', 'url', 'name', 'micro', 'thumb']; - $condition = [$condition_str, Protocol::DFRN, $uid]; + $condition = [$condition_str, Protocol::DFRN, Protocol::ACTIVITYPUB, $uid]; $contacts = DBA::select('contact', $fields, $condition, $params); if (!$contacts) { return($forumlist); diff --git a/src/Content/Nav.php b/src/Content/Nav.php index ea5c0bbc0..ff1680ab3 100644 --- a/src/Content/Nav.php +++ b/src/Content/Nav.php @@ -29,7 +29,7 @@ class Nav 'directory' => null, 'settings' => null, 'contacts' => null, - 'manage' => null, + 'delegation'=> null, 'events' => null, 'register' => null ]; @@ -149,7 +149,7 @@ class Nav $nav['usermenu'] = []; $userinfo = null; - if (local_user() || remote_user()) { + if (Session::isAuthenticated()) { $nav['logout'] = ['logout', L10n::t('Logout'), '', L10n::t('End this session')]; } else { $nav['login'] = ['login', L10n::t('Login'), ($a->module == 'login' ? 'selected' : ''), L10n::t('Sign in')]; @@ -182,7 +182,7 @@ class Nav $nav['home'] = [$homelink, L10n::t('Home'), '', L10n::t('Home Page')]; } - if (intval(Config::get('config', 'register_policy')) === \Friendica\Module\Register::OPEN && !local_user() && !remote_user()) { + if (intval(Config::get('config', 'register_policy')) === \Friendica\Module\Register::OPEN && !Session::isAuthenticated()) { $nav['register'] = ['register', L10n::t('Register'), '', L10n::t('Create an account')]; } @@ -257,11 +257,9 @@ class Nav $nav['messages']['new'] = ['message/new', L10n::t('New Message'), '', L10n::t('New Message')]; if (is_array($a->identities) && count($a->identities) > 1) { - $nav['manage'] = ['manage', L10n::t('Manage'), '', L10n::t('Manage other pages')]; + $nav['delegation'] = ['delegation', L10n::t('Delegation'), '', L10n::t('Manage other pages')]; } - $nav['delegations'] = ['delegate', L10n::t('Delegations'), '', L10n::t('Delegate Page Management')]; - $nav['settings'] = ['settings', L10n::t('Settings'), '', L10n::t('Account settings')]; if (Feature::isEnabled(local_user(), 'multi_profiles')) { diff --git a/src/Content/Pager.php b/src/Content/Pager.php index c9acb63f2..c12608d45 100644 --- a/src/Content/Pager.php +++ b/src/Content/Pager.php @@ -39,7 +39,7 @@ class Pager { $this->setQueryString($queryString); $this->setItemsPerPage($itemsPerPage); - $this->setPage(defaults($_GET, 'page', 1)); + $this->setPage(($_GET['page'] ?? 0) ?: 1); } /** diff --git a/src/Content/Text/BBCode.php b/src/Content/Text/BBCode.php index a60e17c90..75f5d506e 100644 --- a/src/Content/Text/BBCode.php +++ b/src/Content/Text/BBCode.php @@ -38,7 +38,7 @@ class BBCode extends BaseObject * * @param string $body Message body * @return array - * 'type' -> Message type ("link", "video", "photo") + * 'type' -> Message type ('link', 'video', 'photo') * 'text' -> Text before the shared message * 'after' -> Text after the shared message * 'image' -> Preview image of the message @@ -56,19 +56,19 @@ class BBCode extends BaseObject if (preg_match_all("(\[class=(.*?)\](.*?)\[\/class\])ism", $body, $attached, PREG_SET_ORDER)) { foreach ($attached as $data) { - if (!in_array($data[1], ["type-link", "type-video", "type-photo"])) { + if (!in_array($data[1], ['type-link', 'type-video', 'type-photo'])) { continue; } - $post["type"] = substr($data[1], 5); + $post['type'] = substr($data[1], 5); $pos = strpos($body, $data[0]); if ($pos > 0) { - $post["text"] = trim(substr($body, 0, $pos)); - $post["after"] = trim(substr($body, $pos + strlen($data[0]))); + $post['text'] = trim(substr($body, 0, $pos)); + $post['after'] = trim(substr($body, $pos + strlen($data[0]))); } else { - $post["text"] = trim(str_replace($data[0], "", $body)); - $post["after"] = ''; + $post['text'] = trim(str_replace($data[0], '', $body)); + $post['after'] = ''; } $attacheddata = $data[2]; @@ -79,25 +79,25 @@ class BBCode extends BaseObject if ($picturedata) { if (($picturedata[0] >= 500) && ($picturedata[0] >= $picturedata[1])) { - $post["image"] = $matches[1]; + $post['image'] = $matches[1]; } else { - $post["preview"] = $matches[1]; + $post['preview'] = $matches[1]; } } } if (preg_match("/\[bookmark\=(.*?)\](.*?)\[\/bookmark\]/ism", $attacheddata, $matches)) { - $post["url"] = $matches[1]; - $post["title"] = $matches[2]; + $post['url'] = $matches[1]; + $post['title'] = $matches[2]; } - if (!empty($post["url"]) && (in_array($post["type"], ["link", "video"])) + if (!empty($post['url']) && (in_array($post['type'], ['link', 'video'])) && preg_match("/\[url\=(.*?)\](.*?)\[\/url\]/ism", $attacheddata, $matches)) { - $post["url"] = $matches[1]; + $post['url'] = $matches[1]; } // Search for description if (preg_match("/\[quote\](.*?)\[\/quote\]/ism", $attacheddata, $matches)) { - $post["description"] = $matches[1]; + $post['description'] = $matches[1]; } } } @@ -109,7 +109,7 @@ class BBCode extends BaseObject * * @param string $body Message body * @return array - * 'type' -> Message type ("link", "video", "photo") + * 'type' -> Message type ('link', 'video', 'photo') * 'text' -> Text before the shared message * 'after' -> Text after the shared message * 'image' -> Preview image of the message @@ -136,9 +136,9 @@ class BBCode extends BaseObject $attributes = $match[2]; - $data["text"] = trim($match[1]); + $data['text'] = trim($match[1]); - $type = ""; + $type = ''; preg_match("/type='(.*?)'/ism", $attributes, $matches); if (!empty($matches[1])) { $type = strtolower($matches[1]); @@ -149,19 +149,19 @@ class BBCode extends BaseObject $type = strtolower($matches[1]); } - if ($type == "") { + if ($type == '') { return []; } - if (!in_array($type, ["link", "audio", "photo", "video"])) { + if (!in_array($type, ['link', 'audio', 'photo', 'video'])) { return []; } - if ($type != "") { - $data["type"] = $type; + if ($type != '') { + $data['type'] = $type; } - $url = ""; + $url = ''; preg_match("/url='(.*?)'/ism", $attributes, $matches); if (!empty($matches[1])) { $url = $matches[1]; @@ -172,11 +172,11 @@ class BBCode extends BaseObject $url = $matches[1]; } - if ($url != "") { - $data["url"] = html_entity_decode($url, ENT_QUOTES, 'UTF-8'); + if ($url != '') { + $data['url'] = html_entity_decode($url, ENT_QUOTES, 'UTF-8'); } - $title = ""; + $title = ''; preg_match("/title='(.*?)'/ism", $attributes, $matches); if (!empty($matches[1])) { $title = $matches[1]; @@ -187,14 +187,14 @@ class BBCode extends BaseObject $title = $matches[1]; } - if ($title != "") { + if ($title != '') { $title = self::convert(html_entity_decode($title, ENT_QUOTES, 'UTF-8'), false, true); $title = html_entity_decode($title, ENT_QUOTES, 'UTF-8'); - $title = str_replace(["[", "]"], ["[", "]"], $title); - $data["title"] = $title; + $title = str_replace(['[', ']'], ['[', ']'], $title); + $data['title'] = $title; } - $image = ""; + $image = ''; preg_match("/image='(.*?)'/ism", $attributes, $matches); if (!empty($matches[1])) { $image = $matches[1]; @@ -205,11 +205,11 @@ class BBCode extends BaseObject $image = $matches[1]; } - if ($image != "") { - $data["image"] = html_entity_decode($image, ENT_QUOTES, 'UTF-8'); + if ($image != '') { + $data['image'] = html_entity_decode($image, ENT_QUOTES, 'UTF-8'); } - $preview = ""; + $preview = ''; preg_match("/preview='(.*?)'/ism", $attributes, $matches); if (!empty($matches[1])) { $preview = $matches[1]; @@ -220,13 +220,13 @@ class BBCode extends BaseObject $preview = $matches[1]; } - if ($preview != "") { - $data["preview"] = html_entity_decode($preview, ENT_QUOTES, 'UTF-8'); + if ($preview != '') { + $data['preview'] = html_entity_decode($preview, ENT_QUOTES, 'UTF-8'); } - $data["description"] = trim($match[3]); + $data['description'] = trim($match[3]); - $data["after"] = trim($match[4]); + $data['after'] = trim($match[4]); return $data; } @@ -244,7 +244,7 @@ class BBCode extends BaseObject */ $has_title = !empty($item['title']); - $plink = defaults($item, 'plink', ''); + $plink = $item['plink'] ?? ''; $post = self::getAttachmentData($body); // Get all linked images with alternative image description @@ -268,11 +268,11 @@ class BBCode extends BaseObject } // if nothing is found, it maybe having an image. - if (!isset($post["type"])) { + if (!isset($post['type'])) { // Simplify image codes $body = preg_replace("/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/ism", '[img]$3[/img]', $body); $body = preg_replace("/\[img\=(.*?)\](.*?)\[\/img\]/ism", '[img]$1[/img]', $body); - $post["text"] = $body; + $post['text'] = $body; if (preg_match_all("(\[url=(.*?)\]\s*\[img\](.*?)\[\/img\]\s*\[\/url\])ism", $body, $pictures, PREG_SET_ORDER)) { if ((count($pictures) == 1) && !$has_title) { @@ -288,75 +288,75 @@ class BBCode extends BaseObject // Workaround: // Sometimes photo posts to the own album are not detected at the start. // So we seem to cannot use the cache for these cases. That's strange. - if (($data["type"] != "photo") && strstr($pictures[0][1], "/photos/")) { + if (($data['type'] != 'photo') && strstr($pictures[0][1], "/photos/")) { $data = ParseUrl::getSiteinfo($pictures[0][1], true); } - if ($data["type"] == "photo") { - $post["type"] = "photo"; - if (isset($data["images"][0])) { - $post["image"] = $data["images"][0]["src"]; - $post["url"] = $data["url"]; + if ($data['type'] == 'photo') { + $post['type'] = 'photo'; + if (isset($data['images'][0])) { + $post['image'] = $data['images'][0]['src']; + $post['url'] = $data['url']; } else { - $post["image"] = $data["url"]; + $post['image'] = $data['url']; } - $post["preview"] = $pictures[0][2]; - $post["text"] = trim(str_replace($pictures[0][0], "", $body)); + $post['preview'] = $pictures[0][2]; + $post['text'] = trim(str_replace($pictures[0][0], '', $body)); } else { $imgdata = Image::getInfoFromURL($pictures[0][1]); - if ($imgdata && substr($imgdata["mime"], 0, 6) == "image/") { - $post["type"] = "photo"; - $post["image"] = $pictures[0][1]; - $post["preview"] = $pictures[0][2]; - $post["text"] = trim(str_replace($pictures[0][0], "", $body)); + if ($imgdata && substr($imgdata['mime'], 0, 6) == 'image/') { + $post['type'] = 'photo'; + $post['image'] = $pictures[0][1]; + $post['preview'] = $pictures[0][2]; + $post['text'] = trim(str_replace($pictures[0][0], '', $body)); } } } elseif (count($pictures) > 0) { - $post["type"] = "link"; - $post["url"] = $plink; - $post["image"] = $pictures[0][2]; - $post["text"] = $body; + $post['type'] = 'link'; + $post['url'] = $plink; + $post['image'] = $pictures[0][2]; + $post['text'] = $body; foreach ($pictures as $picture) { - $post["text"] = trim(str_replace($picture[0], "", $post["text"])); + $post['text'] = trim(str_replace($picture[0], '', $post['text'])); } } } elseif (preg_match_all("(\[img\](.*?)\[\/img\])ism", $body, $pictures, PREG_SET_ORDER)) { if ((count($pictures) == 1) && !$has_title) { - $post["type"] = "photo"; - $post["image"] = $pictures[0][1]; - $post["text"] = str_replace($pictures[0][0], "", $body); + $post['type'] = 'photo'; + $post['image'] = $pictures[0][1]; + $post['text'] = str_replace($pictures[0][0], '', $body); } elseif (count($pictures) > 0) { - $post["type"] = "link"; - $post["url"] = $plink; - $post["image"] = $pictures[0][1]; - $post["text"] = $body; + $post['type'] = 'link'; + $post['url'] = $plink; + $post['image'] = $pictures[0][1]; + $post['text'] = $body; foreach ($pictures as $picture) { - $post["text"] = trim(str_replace($picture[0], "", $post["text"])); + $post['text'] = trim(str_replace($picture[0], '', $post['text'])); } } } // Test for the external links - preg_match_all("(\[url\](.*?)\[\/url\])ism", $post["text"], $links1, PREG_SET_ORDER); - preg_match_all("(\[url\=(.*?)\].*?\[\/url\])ism", $post["text"], $links2, PREG_SET_ORDER); + preg_match_all("(\[url\](.*?)\[\/url\])ism", $post['text'], $links1, PREG_SET_ORDER); + preg_match_all("(\[url\=(.*?)\].*?\[\/url\])ism", $post['text'], $links2, PREG_SET_ORDER); $links = array_merge($links1, $links2); // If there is only a single one, then use it. // This should cover link posts via API. - if ((count($links) == 1) && !isset($post["preview"]) && !$has_title) { - $post["type"] = "link"; - $post["url"] = $links[0][1]; + if ((count($links) == 1) && !isset($post['preview']) && !$has_title) { + $post['type'] = 'link'; + $post['url'] = $links[0][1]; } // Now count the number of external media links - preg_match_all("(\[vimeo\](.*?)\[\/vimeo\])ism", $post["text"], $links1, PREG_SET_ORDER); - preg_match_all("(\[youtube\\](.*?)\[\/youtube\\])ism", $post["text"], $links2, PREG_SET_ORDER); - preg_match_all("(\[video\\](.*?)\[\/video\\])ism", $post["text"], $links3, PREG_SET_ORDER); - preg_match_all("(\[audio\\](.*?)\[\/audio\\])ism", $post["text"], $links4, PREG_SET_ORDER); + preg_match_all("(\[vimeo\](.*?)\[\/vimeo\])ism", $post['text'], $links1, PREG_SET_ORDER); + preg_match_all("(\[youtube\\](.*?)\[\/youtube\\])ism", $post['text'], $links2, PREG_SET_ORDER); + preg_match_all("(\[video\\](.*?)\[\/video\\])ism", $post['text'], $links3, PREG_SET_ORDER); + preg_match_all("(\[audio\\](.*?)\[\/audio\\])ism", $post['text'], $links4, PREG_SET_ORDER); // Add them to the other external links $links = array_merge($links, $links1, $links2, $links3, $links4); @@ -364,19 +364,19 @@ class BBCode extends BaseObject // Are there more than one? if (count($links) > 1) { // The post will be the type "text", which means a blog post - unset($post["type"]); - $post["url"] = $plink; + unset($post['type']); + $post['url'] = $plink; } - if (!isset($post["type"])) { - $post["type"] = "text"; - $post["text"] = trim($body); + if (!isset($post['type'])) { + $post['type'] = "text"; + $post['text'] = trim($body); } - } elseif (isset($post["url"]) && ($post["type"] == "video")) { - $data = ParseUrl::getSiteinfoCached($post["url"], true); + } elseif (isset($post['url']) && ($post['type'] == 'video')) { + $data = ParseUrl::getSiteinfoCached($post['url'], true); - if (isset($data["images"][0])) { - $post["image"] = $data["images"][0]["src"]; + if (isset($data['images'][0])) { + $post['image'] = $data['images'][0]['src']; } } @@ -581,27 +581,27 @@ class BBCode extends BaseObject private static function convertAttachment($return, $simplehtml = false, $tryoembed = true) { $data = self::getAttachmentData($return); - if (empty($data) || empty($data["url"])) { + if (empty($data) || empty($data['url'])) { return $return; } - if (isset($data["title"])) { - $data["title"] = strip_tags($data["title"]); - $data["title"] = str_replace(["http://", "https://"], "", $data["title"]); + if (isset($data['title'])) { + $data['title'] = strip_tags($data['title']); + $data['title'] = str_replace(['http://', 'https://'], '', $data['title']); } else { - $data["title"] = null; + $data['title'] = null; } - if (((strpos($data["text"], "[img=") !== false) || (strpos($data["text"], "[img]") !== false) || Config::get('system', 'always_show_preview')) && !empty($data["image"])) { - $data["preview"] = $data["image"]; - $data["image"] = ""; + if (((strpos($data['text'], "[img=") !== false) || (strpos($data['text'], "[img]") !== false) || Config::get('system', 'always_show_preview')) && !empty($data['image'])) { + $data['preview'] = $data['image']; + $data['image'] = ''; } $return = ''; if (in_array($simplehtml, [7, 9])) { - $return = self::convertUrlForOStatus($data["url"]); + $return = self::convertUrlForActivityPub($data['url']); } elseif (($simplehtml != 4) && ($simplehtml != 0)) { - $return = sprintf('%s
', $data["url"], $data["title"]); + $return = sprintf('%s
', $data['url'], $data['title']); } else { try { if ($tryoembed && OEmbed::isAllowedURL($data['url'])) { @@ -610,28 +610,28 @@ class BBCode extends BaseObject throw new Exception('OEmbed is disabled for this attachment.'); } } catch (Exception $e) { - $data["title"] = defaults($data, 'title', $data['url']); + $data['title'] = ($data['title'] ?? '') ?: $data['url']; if ($simplehtml != 4) { - $return = sprintf('
', $data["type"]); + $return = sprintf('
', $data['type']); } if (!empty($data['title']) && !empty($data['url'])) { - if (!empty($data["image"]) && empty($data["text"]) && ($data["type"] == "photo")) { - $return .= sprintf('', $data["url"], self::proxyUrl($data["image"], $simplehtml), $data["title"]); + if (!empty($data['image']) && empty($data['text']) && ($data['type'] == 'photo')) { + $return .= sprintf('', $data['url'], self::proxyUrl($data['image'], $simplehtml), $data['title']); } else { - if (!empty($data["image"])) { - $return .= sprintf('
', $data["url"], self::proxyUrl($data["image"], $simplehtml), $data["title"]); - } elseif (!empty($data["preview"])) { - $return .= sprintf('
', $data["url"], self::proxyUrl($data["preview"], $simplehtml), $data["title"]); + if (!empty($data['image'])) { + $return .= sprintf('
', $data['url'], self::proxyUrl($data['image'], $simplehtml), $data['title']); + } elseif (!empty($data['preview'])) { + $return .= sprintf('
', $data['url'], self::proxyUrl($data['preview'], $simplehtml), $data['title']); } $return .= sprintf('

%s

', $data['url'], $data['title']); } } - if (!empty($data["description"]) && $data["description"] != $data["title"]) { + if (!empty($data['description']) && $data['description'] != $data['title']) { // Sanitize the HTML by converting it to BBCode - $bbcode = HTML::toBBCode($data["description"]); + $bbcode = HTML::toBBCode($data['description']); $return .= sprintf('
%s
', trim(self::convert($bbcode))); } @@ -645,7 +645,7 @@ class BBCode extends BaseObject } } - return trim(defaults($data, 'text', '') . ' ' . $return . ' ' . defaults($data, 'after', '')); + return trim(($data['text'] ?? '') . ' ' . $return . ' ' . ($data['after'] ?? '')); } public static function removeShareInformation($Text, $plaintext = false, $nolink = false) @@ -655,36 +655,36 @@ class BBCode extends BaseObject if (!$data) { return $Text; } elseif ($nolink) { - return $data["text"] . defaults($data, 'after', ''); + return $data['text'] . ($data['after'] ?? ''); } - $title = htmlentities(defaults($data, 'title', ''), ENT_QUOTES, 'UTF-8', false); - $text = htmlentities($data["text"], ENT_QUOTES, 'UTF-8', false); - if ($plaintext || (($title != "") && strstr($text, $title))) { - $data["title"] = $data["url"]; - } elseif (($text != "") && strstr($title, $text)) { - $data["text"] = $data["title"]; - $data["title"] = $data["url"]; + $title = htmlentities($data['title'] ?? '', ENT_QUOTES, 'UTF-8', false); + $text = htmlentities($data['text'], ENT_QUOTES, 'UTF-8', false); + if ($plaintext || (($title != '') && strstr($text, $title))) { + $data['title'] = $data['url']; + } elseif (($text != '') && strstr($title, $text)) { + $data['text'] = $data['title']; + $data['title'] = $data['url']; } - if (empty($data["text"]) && !empty($data["title"]) && empty($data["url"])) { - return $data["title"] . $data["after"]; + if (empty($data['text']) && !empty($data['title']) && empty($data['url'])) { + return $data['title'] . $data['after']; } // If the link already is included in the post, don't add it again - if (!empty($data["url"]) && strpos($data["text"], $data["url"])) { - return $data["text"] . $data["after"]; + if (!empty($data['url']) && strpos($data['text'], $data['url'])) { + return $data['text'] . $data['after']; } - $text = $data["text"]; + $text = $data['text']; - if (!empty($data["url"]) && !empty($data["title"])) { - $text .= "\n[url=" . $data["url"] . "]" . $data["title"] . "[/url]"; - } elseif (!empty($data["url"])) { - $text .= "\n[url]" . $data["url"] . "[/url]"; + if (!empty($data['url']) && !empty($data['title'])) { + $text .= "\n[url=" . $data['url'] . ']' . $data['title'] . '[/url]'; + } elseif (!empty($data['url'])) { + $text .= "\n[url]" . $data['url'] . '[/url]'; } - return $text . "\n" . $data["after"]; + return $text . "\n" . $data['after']; } /** @@ -694,7 +694,7 @@ class BBCode extends BaseObject * @param array $match Array with the matching values * @return string reformatted link including HTML codes */ - private static function convertUrlForOStatusCallback($match) + private static function convertUrlForActivityPubCallback($match) { $url = $match[1]; @@ -707,15 +707,26 @@ class BBCode extends BaseObject return $match[0]; } - return self::convertUrlForOStatus($url); + return self::convertUrlForActivityPub($url); } /** - * @brief Converts [url] BBCodes in a format that looks fine on OStatus systems. + * @brief Converts [url] BBCodes in a format that looks fine on ActivityPub systems. * @param string $url URL that is about to be reformatted * @return string reformatted link including HTML codes */ - private static function convertUrlForOStatus($url) + private static function convertUrlForActivityPub($url) + { + $html = '%s'; + return sprintf($html, $url, self::getStyledURL($url)); + } + + /** + * Converts an URL in a nicer format (without the scheme and possibly shortened) + * @param string $url URL that is about to be reformatted + * @return string reformatted link + */ + private static function getStyledURL($url) { $parts = parse_url($url); $scheme = $parts['scheme'] . '://'; @@ -725,9 +736,7 @@ class BBCode extends BaseObject $styled_url = substr($styled_url, 0, 30) . "…"; } - $html = '%s'; - - return sprintf($html, $url, $styled_url); + return $styled_url; } /* @@ -932,7 +941,7 @@ class BBCode extends BaseObject $attributes = []; foreach(['author', 'profile', 'avatar', 'link', 'posted'] as $field) { preg_match("/$field=(['\"])(.+?)\\1/ism", $attribute_string, $matches); - $attributes[$field] = html_entity_decode(defaults($matches, 2, ''), ENT_QUOTES, 'UTF-8'); + $attributes[$field] = html_entity_decode($matches[2] ?? '', ENT_QUOTES, 'UTF-8'); } // We only call this so that a previously unknown contact can be added. @@ -951,11 +960,11 @@ class BBCode extends BaseObject Contact::getIdForURL($attributes['profile'], 0, true, $default); $author_contact = Contact::getDetailsByURL($attributes['profile']); - $author_contact['addr'] = defaults($author_contact, 'addr' , Protocol::getAddrFromProfileUrl($attributes['profile'])); + $author_contact['addr'] = ($author_contact['addr'] ?? '') ?: Protocol::getAddrFromProfileUrl($attributes['profile']); - $attributes['author'] = defaults($author_contact, 'name' , $attributes['author']); - $attributes['avatar'] = defaults($author_contact, 'micro', $attributes['avatar']); - $attributes['profile'] = defaults($author_contact, 'url' , $attributes['profile']); + $attributes['author'] = ($author_contact['name'] ?? '') ?: $attributes['author']; + $attributes['avatar'] = ($author_contact['micro'] ?? '') ?: $attributes['avatar']; + $attributes['profile'] = ($author_contact['url'] ?? '') ?: $attributes['profile']; if ($attributes['avatar']) { $attributes['avatar'] = ProxyUtils::proxifyUrl($attributes['avatar'], false, ProxyUtils::SIZE_THUMB); @@ -1056,7 +1065,8 @@ class BBCode extends BaseObject private static function removePictureLinksCallback($match) { - $text = Cache::get($match[1]); + $cache_key = 'remove:' . $match[1]; + $text = Cache::get($cache_key); if (is_null($text)) { $a = self::getApp(); @@ -1072,10 +1082,10 @@ class BBCode extends BaseObject $a->getProfiler()->saveTimestamp($stamp1, "network", System::callstack()); - if (substr($curl_info["content_type"], 0, 6) == "image/") { - $text = "[url=" . $match[1] . "]" . $match[1] . "[/url]"; + if (substr($curl_info['content_type'], 0, 6) == 'image/') { + $text = "[url=" . $match[1] . ']' . $match[1] . "[/url]"; } else { - $text = "[url=" . $match[2] . "]" . $match[2] . "[/url]"; + $text = "[url=" . $match[2] . ']' . $match[2] . "[/url]"; // if its not a picture then look if its a page that contains a picture link $body = Network::fetchUrl($match[1]); @@ -1093,12 +1103,12 @@ class BBCode extends BaseObject } } - if (strtolower($attr["name"]) == "twitter:image") { - $text = "[url=" . $attr["content"] . "]" . $attr["content"] . "[/url]"; + if (strtolower($attr['name']) == 'twitter:image') { + $text = '[url=' . $attr['content'] . ']' . $attr['content'] . '[/url]'; } } } - Cache::set($match[1], $text); + Cache::set($cache_key, $text); } return $text; @@ -1106,7 +1116,7 @@ class BBCode extends BaseObject private static function expandLinksCallback($match) { - if (($match[3] == "") || ($match[2] == $match[3]) || stristr($match[2], $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]"); @@ -1115,58 +1125,72 @@ class BBCode extends BaseObject private static function cleanPictureLinksCallback($match) { - $text = Cache::get($match[1]); + $a = self::getApp(); - if (is_null($text)) { - $a = self::getApp(); - - $stamp1 = microtime(true); - - $ch = @curl_init($match[1]); - @curl_setopt($ch, CURLOPT_NOBODY, true); - @curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - @curl_setopt($ch, CURLOPT_USERAGENT, $a->getUserAgent()); - @curl_exec($ch); - $curl_info = @curl_getinfo($ch); - - $a->getProfiler()->saveTimestamp($stamp1, "network", System::callstack()); - - // if its a link to a picture then embed this picture - if (substr($curl_info["content_type"], 0, 6) == "image/") { - $text = "[img]" . $match[1] . "[/img]"; + // When the picture link is the own photo path then we can avoid fetching the link + $own_photo_url = preg_quote(Strings::normaliseLink($a->getBaseURL()) . '/photos/'); + if (preg_match('|' . $own_photo_url . '.*?/image/|', Strings::normaliseLink($match[1]))) { + if (!empty($match[3])) { + $text = '[img=' . str_replace('-1.', '-0.', $match[2]) . ']' . $match[3] . '[/img]'; } else { - if (!empty($match[3])) { - $text = "[img=" . $match[2] . "]" . $match[3] . "[/img]"; - } else { - $text = "[img]" . $match[2] . "[/img]"; + $text = '[img]' . str_replace('-1.', '-0.', $match[2]) . '[/img]'; + } + return $text; + } + + $cache_key = 'clean:' . $match[1]; + $text = Cache::get($cache_key); + if (!is_null($text)) { + return $text; + } + + // Only fetch the header, not the content + $stamp1 = microtime(true); + + $ch = @curl_init($match[1]); + @curl_setopt($ch, CURLOPT_NOBODY, true); + @curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + @curl_setopt($ch, CURLOPT_USERAGENT, $a->getUserAgent()); + @curl_exec($ch); + $curl_info = @curl_getinfo($ch); + + $a->getProfiler()->saveTimestamp($stamp1, "network", System::callstack()); + + // if its a link to a picture then embed this picture + if (substr($curl_info['content_type'], 0, 6) == 'image/') { + $text = '[img]' . $match[1] . '[/img]'; + } else { + if (!empty($match[3])) { + $text = '[img=' . $match[2] . ']' . $match[3] . '[/img]'; + } else { + $text = '[img]' . $match[2] . '[/img]'; + } + + // if its not a picture then look if its a page that contains a picture link + $body = Network::fetchUrl($match[1]); + + $doc = new DOMDocument(); + @$doc->loadHTML($body); + $xpath = new DOMXPath($doc); + $list = $xpath->query("//meta[@name]"); + foreach ($list as $node) { + $attr = []; + if ($node->attributes->length) { + foreach ($node->attributes as $attribute) { + $attr[$attribute->name] = $attribute->value; + } } - // if its not a picture then look if its a page that contains a picture link - $body = Network::fetchUrl($match[1]); - - $doc = new DOMDocument(); - @$doc->loadHTML($body); - $xpath = new DOMXPath($doc); - $list = $xpath->query("//meta[@name]"); - foreach ($list as $node) { - $attr = []; - if ($node->attributes->length) { - foreach ($node->attributes as $attribute) { - $attr[$attribute->name] = $attribute->value; - } - } - - if (strtolower($attr["name"]) == "twitter:image") { - if (!empty($match[3])) { - $text = "[img=" . $attr["content"] . "]" . $match[3] . "[/img]"; - } else { - $text = "[img]" . $attr["content"] . "[/img]"; - } + if (strtolower($attr['name']) == "twitter:image") { + if (!empty($match[3])) { + $text = "[img=" . $attr['content'] . "]" . $match[3] . "[/img]"; + } else { + $text = "[img]" . $attr['content'] . "[/img]"; } } } - Cache::set($match[1], $text); } + Cache::set($cache_key, $text); return $text; } @@ -1217,7 +1241,7 @@ class BBCode extends BaseObject $try_oembed_callback = function ($match) { $url = $match[1]; - $title = defaults($match, 2, null); + $title = $match[2] ?? null; try { $return = OEmbed::getHTML($url, $title); @@ -1318,7 +1342,7 @@ class BBCode extends BaseObject $text = str_replace($search, $replace, $text); // removing multiplicated newlines - if (Config::get("system", "remove_multiplicated_lines")) { + if (Config::get('system', 'remove_multiplicated_lines')) { $search = ["\n\n\n", "\n ", " \n", "[/quote]\n\n", "\n[/quote]", "[/li]\n", "\n[li]", "\n[ul]", "[/ul]\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"]; $replace = ["\n\n", "\n", "\n", "[/quote]\n", "[/quote]", "[/li]", "[li]", "[ul]", "[/ul]", "\n[share ", "[/attachment]", @@ -1389,8 +1413,14 @@ class BBCode extends BaseObject // Check for sized text // [size=50] --> font-size: 50px (with the unit). - $text = preg_replace("(\[size=(\d*?)\](.*?)\[\/size\])ism", "$2", $text); - $text = preg_replace("(\[size=(.*?)\](.*?)\[\/size\])ism", "$2", $text); + if ($simple_html != 3) { + $text = preg_replace("(\[size=(\d*?)\](.*?)\[\/size\])ism", "$2", $text); + $text = preg_replace("(\[size=(.*?)\](.*?)\[\/size\])ism", "$2", $text); + } else { + // Issue 2199: Diaspora doesn't interpret the construct above, nor the or element + $text = preg_replace("(\[size=(.*?)\](.*?)\[\/size\])ism", "$2", $text); + } + // Check for centered text $text = preg_replace("(\[center\](.*?)\[\/center\])ism", "
$1
", $text); @@ -1445,6 +1475,11 @@ class BBCode extends BaseObject $text = str_replace('[hr]', '
', $text); + if (!$for_plaintext) { + // Autolinker for isolated URLs + $text = preg_replace(Strings::autoLinkRegEx(), '[url]$1[/url]', $text); + } + // This is actually executed in Item::prepareBody() $nosmile = strpos($text, '[nosmile]') !== false; @@ -1454,24 +1489,22 @@ class BBCode extends BaseObject $text = preg_replace("/\[font=(.*?)\](.*?)\[\/font\]/sm", "$2", $text); // Declare the format for [spoiler] layout - $SpoilerLayout = '
$1
'; + $SpoilerLayout = '
' . L10n::t('Click to open/close') . '$1
'; // Check for [spoiler] text // handle nested quotes $endlessloop = 0; while ((strpos($text, "[/spoiler]") !== false) && (strpos($text, "[spoiler]") !== false) && (++$endlessloop < 20)) { - $text = preg_replace("/\[spoiler\](.*?)\[\/spoiler\]/ism", "$SpoilerLayout", $text); + $text = preg_replace("/\[spoiler\](.*?)\[\/spoiler\]/ism", $SpoilerLayout, $text); } - // Check for [spoiler=Author] text - - $t_wrote = L10n::t('$1 wrote:'); + // Check for [spoiler=Title] text // handle nested quotes $endlessloop = 0; while ((strpos($text, "[/spoiler]")!== false) && (strpos($text, "[spoiler=") !== false) && (++$endlessloop < 20)) { $text = preg_replace("/\[spoiler=[\"\']*(.*?)[\"\']*\](.*?)\[\/spoiler\]/ism", - "
" . $t_wrote . "
$2
", + '
$1$2
', $text); } @@ -1629,12 +1662,10 @@ class BBCode extends BaseObject $text = Smilies::replace($text); } - // if the HTML is used to generate plain text, then don't do this search, but replace all URL of that kind to text if (!$for_plaintext) { - $text = preg_replace(Strings::autoLinkRegEx(), '[url]$1[/url]', $text); if (in_array($simple_html, [7, 9])) { - $text = preg_replace_callback("/\[url\](.*?)\[\/url\]/ism", 'self::convertUrlForOStatusCallback', $text); - $text = preg_replace_callback("/\[url\=(.*?)\](.*?)\[\/url\]/ism", 'self::convertUrlForOStatusCallback', $text); + $text = preg_replace_callback("/\[url\](.*?)\[\/url\]/ism", 'self::convertUrlForActivityPubCallback', $text); + $text = preg_replace_callback("/\[url\=(.*?)\](.*?)\[\/url\]/ism", 'self::convertUrlForActivityPubCallback', $text); } } else { $text = preg_replace("(\[url\](.*?)\[\/url\])ism", " $1 ", $text); @@ -1810,7 +1841,7 @@ class BBCode extends BaseObject // Clean up the HTML by loading and saving the HTML with the DOM. // Bad structured html can break a whole page. // For performance reasons do it only with activated item cache or at export. - if (!$try_oembed || (get_itemcachepath() != "")) { + if (!$try_oembed || (get_itemcachepath() != '')) { $doc = new DOMDocument(); $doc->preserveWhiteSpace = false; @@ -1818,10 +1849,10 @@ class BBCode extends BaseObject $doctype = ''; $encoding = ''; - @$doc->loadHTML($encoding.$doctype."".$text.""); + @$doc->loadHTML($encoding . $doctype . '' . $text . ''); $doc->encoding = 'UTF-8'; $text = $doc->saveHTML(); - $text = str_replace(["", "", $doctype, $encoding], ["", "", "", ""], $text); + $text = str_replace(['', '', $doctype, $encoding], ['', '', '', ''], $text); $text = str_replace('
', '', $text); @@ -1861,9 +1892,9 @@ class BBCode extends BaseObject * @param string $addon The addon for which the abstract is meant for * @return string The abstract */ - public static function getAbstract($text, $addon = "") + public static function getAbstract($text, $addon = '') { - $abstract = ""; + $abstract = ''; $abstracts = []; $addon = strtolower($addon); @@ -1877,7 +1908,7 @@ class BBCode extends BaseObject $abstract = $abstracts[$addon]; } - if ($abstract == "" && preg_match("/\[abstract\](.*?)\[\/abstract\]/ism", $text, $result)) { + if ($abstract == '' && preg_match("/\[abstract\](.*?)\[\/abstract\]/ism", $text, $result)) { $abstract = $result[1]; } @@ -1953,7 +1984,7 @@ class BBCode extends BaseObject // Add all tags that maybe were removed if (preg_match_all("/#\[url\=([$url_search_string]*)\](.*?)\[\/url\]/ism", $original_text, $tags)) { - $tagline = ""; + $tagline = ''; foreach ($tags[2] as $tag) { $tag = html_entity_decode($tag, ENT_QUOTES, 'UTF-8'); if (!strpos(html_entity_decode($text, ENT_QUOTES, 'UTF-8'), '#' . $tag)) { @@ -1971,7 +2002,7 @@ class BBCode extends BaseObject // If a link is followed by a quote then there should be a newline before it // Maybe we should make this newline at every time before a quote. - $text = str_replace(["
"], ["
"], $text); + $text = str_replace(['
'], ['
'], $text); $stamp1 = microtime(true); diff --git a/src/Content/Text/HTML.php b/src/Content/Text/HTML.php index ddc587d72..390a97f0f 100644 --- a/src/Content/Text/HTML.php +++ b/src/Content/Text/HTML.php @@ -42,14 +42,32 @@ class HTML return $cleaned; } - private static function tagToBBCode(DOMDocument $doc, $tag, $attributes, $startbb, $endbb) + /** + * Search all instances of a specific HTML tag node in the provided DOM document and replaces them with BBCode text nodes. + * + * @see HTML::tagToBBCodeSub() + */ + private static function tagToBBCode(DOMDocument $doc, string $tag, array $attributes, string $startbb, string $endbb, bool $ignoreChildren = false) { do { - $done = self::tagToBBCodeSub($doc, $tag, $attributes, $startbb, $endbb); + $done = self::tagToBBCodeSub($doc, $tag, $attributes, $startbb, $endbb, $ignoreChildren); } while ($done); } - private static function tagToBBCodeSub(DOMDocument $doc, $tag, $attributes, $startbb, $endbb) + /** + * Search the first specific HTML tag node in the provided DOM document and replaces it with BBCode text nodes. + * + * @param DOMDocument $doc + * @param string $tag HTML tag name + * @param array $attributes Array of attributes to match and optionally use the value from + * @param string $startbb BBCode tag opening + * @param string $endbb BBCode tag closing + * @param bool $ignoreChildren If set to false, the HTML tag children will be appended as text inside the BBCode tag + * Otherwise, they will be entirely ignored. Useful for simple BBCode that draw their + * inner value from an attribute value and disregard the tag children. + * @return bool Whether a replacement was done + */ + private static function tagToBBCodeSub(DOMDocument $doc, string $tag, array $attributes, string $startbb, string $endbb, bool $ignoreChildren = false) { $savestart = str_replace('$', '\x01', $startbb); $replace = false; @@ -98,7 +116,7 @@ class HTML $node->parentNode->insertBefore($StartCode, $node); - if ($node->hasChildNodes()) { + if (!$ignoreChildren && $node->hasChildNodes()) { /** @var \DOMNode $child */ foreach ($node->childNodes as $key => $child) { /* Remove empty text nodes at the start or at the end of the children list */ @@ -296,14 +314,14 @@ class HTML self::tagToBBCode($doc, 'a', ['href' => '/mailto:(.+)/'], '[mail=$1]', '[/mail]'); self::tagToBBCode($doc, 'a', ['href' => '/(.+)/'], '[url=$1]', '[/url]'); - self::tagToBBCode($doc, 'img', ['src' => '/(.+)/', 'alt' => '/(.+)/'], '[img=$1]$2', '[/img]'); - self::tagToBBCode($doc, 'img', ['src' => '/(.+)/', 'width' => '/(\d+)/', 'height' => '/(\d+)/'], '[img=$2x$3]$1', '[/img]'); - self::tagToBBCode($doc, 'img', ['src' => '/(.+)/'], '[img]$1', '[/img]'); + self::tagToBBCode($doc, 'img', ['src' => '/(.+)/', 'alt' => '/(.+)/'], '[img=$1]$2', '[/img]', true); + self::tagToBBCode($doc, 'img', ['src' => '/(.+)/', 'width' => '/(\d+)/', 'height' => '/(\d+)/'], '[img=$2x$3]$1', '[/img]', true); + self::tagToBBCode($doc, 'img', ['src' => '/(.+)/'], '[img]$1', '[/img]', true); - self::tagToBBCode($doc, 'video', ['src' => '/(.+)/'], '[video]$1', '[/video]'); - self::tagToBBCode($doc, 'audio', ['src' => '/(.+)/'], '[audio]$1', '[/audio]'); - self::tagToBBCode($doc, 'iframe', ['src' => '/(.+)/'], '[iframe]$1', '[/iframe]'); + self::tagToBBCode($doc, 'video', ['src' => '/(.+)/'], '[video]$1', '[/video]', true); + self::tagToBBCode($doc, 'audio', ['src' => '/(.+)/'], '[audio]$1', '[/audio]', true); + self::tagToBBCode($doc, 'iframe', ['src' => '/(.+)/'], '[iframe]$1', '[/iframe]', true); self::tagToBBCode($doc, 'key', [], '[code]', '[/code]'); self::tagToBBCode($doc, 'code', [], '[code]', '[/code]'); @@ -854,8 +872,8 @@ class HTML $url = ''; } - return Renderer::replaceMacros(Renderer::getMarkupTemplate(($textmode)?'micropro_txt.tpl':'micropro_img.tpl'), [ - '$click' => defaults($contact, 'click', ''), + return Renderer::replaceMacros(Renderer::getMarkupTemplate($textmode ? 'micropro_txt.tpl' : 'micropro_img.tpl'), [ + '$click' => $contact['click'] ?? '', '$class' => $class, '$url' => $url, '$photo' => ProxyUtils::proxifyUrl($contact['thumb'], false, ProxyUtils::SIZE_THUMB), @@ -875,9 +893,9 @@ class HTML * @param bool $aside Display the search widgit aside. * * @return string Formatted HTML. - * @throws \Friendica\Network\HTTPException\InternalServerErrorException + * @throws \Exception */ - public static function search($s, $id = 'search-box', $url = 'search', $aside = true) + public static function search($s, $id = 'search-box', $aside = true) { $mode = 'text'; @@ -887,24 +905,25 @@ class HTML $save_label = $mode === 'text' ? L10n::t('Save') : L10n::t('Follow'); $values = [ - '$s' => $s, - '$id' => $id, - '$action_url' => $url, - '$search_label' => L10n::t('Search'), - '$save_label' => $save_label, - '$savedsearch' => 'savedsearch', - '$search_hint' => L10n::t('@name, !forum, #tags, content'), - '$mode' => $mode - ]; + '$s' => $s, + '$q' => urlencode($s), + '$id' => $id, + '$search_label' => L10n::t('Search'), + '$save_label' => $save_label, + '$search_hint' => L10n::t('@name, !forum, #tags, content'), + '$mode' => $mode, + '$return_url' => urlencode('search?q=' . urlencode($s)), + ]; if (!$aside) { - $values['$searchoption'] = [ - L10n::t("Full Text"), - L10n::t("Tags"), - L10n::t("Contacts")]; + $values['$search_options'] = [ + 'fulltext' => L10n::t('Full Text'), + 'tags' => L10n::t('Tags'), + 'contacts' => L10n::t('Contacts') + ]; if (Config::get('system', 'poco_local_search')) { - $values['$searchoption'][] = L10n::t("Forums"); + $values['$searchoption']['forums'] = L10n::t('Forums'); } } diff --git a/src/Content/Widget.php b/src/Content/Widget.php index a1482ae94..09a5fc634 100644 --- a/src/Content/Widget.php +++ b/src/Content/Widget.php @@ -11,6 +11,7 @@ use Friendica\Core\PConfig; use Friendica\Core\Protocol; use Friendica\Core\Renderer; use Friendica\Core\System; +use Friendica\Core\Session; use Friendica\Database\DBA; use Friendica\Model\Contact; use Friendica\Model\FileTag; @@ -337,16 +338,9 @@ class Widget return; } - $cid = $zcid = 0; + $zcid = 0; - if (!empty($_SESSION['remote'])) { - foreach ($_SESSION['remote'] as $visitor) { - if ($visitor['uid'] == $profile_uid) { - $cid = $visitor['cid']; - break; - } - } - } + $cid = Session::getRemoteContactID($profile_uid); if (!$cid) { if (Profile::getMyURL()) { diff --git a/src/Content/Widget/CalendarExport.php b/src/Content/Widget/CalendarExport.php index 829d267d8..c78ca21eb 100644 --- a/src/Content/Widget/CalendarExport.php +++ b/src/Content/Widget/CalendarExport.php @@ -57,7 +57,7 @@ class CalendarExport // $a->data is only available if the profile page is visited. If the visited page is not part // of the profile page it should be the personal /events page. So we can use $a->user. - $user = defaults($a->data['user'], 'nickname', $a->user['nickname']); + $user = ($a->data['user']['nickname'] ?? '') ?: $a->user['nickname']; $tpl = Renderer::getMarkupTemplate("widget/events.tpl"); $return = Renderer::replaceMacros($tpl, [ diff --git a/src/Content/Widget/ContactBlock.php b/src/Content/Widget/ContactBlock.php index ef152f900..ec78dae92 100644 --- a/src/Content/Widget/ContactBlock.php +++ b/src/Content/Widget/ContactBlock.php @@ -61,7 +61,7 @@ class ContactBlock if ($total) { // Only show followed for personal accounts, followers for pages - if (defaults($profile, 'account-type', User::ACCOUNT_TYPE_PERSON) == User::ACCOUNT_TYPE_PERSON) { + if ((($profile['account-type'] ?? '') ?: User::ACCOUNT_TYPE_PERSON) == User::ACCOUNT_TYPE_PERSON) { $rel = [Contact::SHARING, Contact::FRIEND]; } else { $rel = [Contact::FOLLOWER, Contact::FRIEND]; diff --git a/src/Content/Widget/SavedSearches.php b/src/Content/Widget/SavedSearches.php new file mode 100644 index 000000000..7f7c171e0 --- /dev/null +++ b/src/Content/Widget/SavedSearches.php @@ -0,0 +1,47 @@ + local_user()]); + if (DBA::isResult($saved_searches)) { + $saved = []; + foreach ($saved_searches as $saved_search) { + $saved[] = [ + 'id' => $saved_search['id'], + 'term' => $saved_search['term'], + 'encodedterm' => urlencode($saved_search['term']), + 'delete' => L10n::t('Remove term'), + 'selected' => $search == $saved_search['term'], + ]; + } + + $tpl = Renderer::getMarkupTemplate('widget/saved_searches.tpl'); + + $o = Renderer::replaceMacros($tpl, [ + '$title' => L10n::t('Saved Searches'), + '$add' => '', + '$searchbox' => '', + '$saved' => $saved, + '$return_url' => urlencode($return_url), + ]); + } + + return $o; + } +} diff --git a/src/Core/ACL.php b/src/Core/ACL.php index ec31ddb7c..df2f86e2b 100644 --- a/src/Core/ACL.php +++ b/src/Core/ACL.php @@ -11,6 +11,7 @@ use Friendica\Content\Feature; use Friendica\Database\DBA; use Friendica\Model\Contact; use Friendica\Model\GContact; +use Friendica\Core\Session; use Friendica\Util\Network; /** @@ -40,12 +41,12 @@ class ACL extends BaseObject $networks = null; - $size = defaults($options, 'size', 4); + $size = ($options['size'] ?? 0) ?: 4; $mutual = !empty($options['mutual_friends']); $single = !empty($options['single']) && empty($options['multiple']); - $exclude = defaults($options, 'exclude', false); + $exclude = $options['exclude'] ?? false; - switch (defaults($options, 'networks', Protocol::PHANTOM)) { + switch (($options['networks'] ?? '') ?: Protocol::PHANTOM) { case 'DFRN_ONLY': $networks = [Protocol::DFRN]; break; @@ -225,13 +226,13 @@ class ACL extends BaseObject $acl_regex = '/<([0-9]+)>/i'; - preg_match_all($acl_regex, defaults($user, 'allow_cid', ''), $matches); + preg_match_all($acl_regex, $user['allow_cid'] ?? '', $matches); $allow_cid = $matches[1]; - preg_match_all($acl_regex, defaults($user, 'allow_gid', ''), $matches); + preg_match_all($acl_regex, $user['allow_gid'] ?? '', $matches); $allow_gid = $matches[1]; - preg_match_all($acl_regex, defaults($user, 'deny_cid', ''), $matches); + preg_match_all($acl_regex, $user['deny_cid'] ?? '', $matches); $deny_cid = $matches[1]; - preg_match_all($acl_regex, defaults($user, 'deny_gid', ''), $matches); + preg_match_all($acl_regex, $user['deny_gid'] ?? '', $matches); $deny_gid = $matches[1]; // Reformats the ACL data so that it is accepted by the JS frontend @@ -300,10 +301,10 @@ class ACL extends BaseObject '$showall' => L10n::t('Visible to everybody'), '$show' => L10n::t('show'), '$hide' => L10n::t('don\'t show'), - '$allowcid' => json_encode(defaults($default_permissions, 'allow_cid', [])), // we need arrays for Javascript since we call .remove() and .push() on this values - '$allowgid' => json_encode(defaults($default_permissions, 'allow_gid', [])), - '$denycid' => json_encode(defaults($default_permissions, 'deny_cid', [])), - '$denygid' => json_encode(defaults($default_permissions, 'deny_gid', [])), + '$allowcid' => json_encode(($default_permissions['allow_cid'] ?? '') ?: []), // We need arrays for + '$allowgid' => json_encode(($default_permissions['allow_gid'] ?? '') ?: []), // Javascript since we + '$denycid' => json_encode(($default_permissions['deny_cid'] ?? '') ?: []), // call .remove() and + '$denygid' => json_encode(($default_permissions['deny_gid'] ?? '') ?: []), // .push() on these values '$networks' => $show_jotnets, '$emailcc' => L10n::t('CC: email addresses'), '$emtitle' => L10n::t('Example: bob@example.com, mary@example.com'), @@ -320,46 +321,4 @@ class ACL extends BaseObject return $o; } - - /** - * Searching for global contacts for autocompletion - * - * @brief Searching for global contacts for autocompletion - * @param string $search Name or part of a name or nick - * @param string $mode Search mode (e.g. "community") - * @return array with the search results - * @throws \Friendica\Network\HTTPException\InternalServerErrorException - */ - public static function contactAutocomplete($search, $mode) - { - if (Config::get('system', 'block_public') && !local_user() && !remote_user()) { - return []; - } - - // don't search if search term has less than 2 characters - if (!$search || mb_strlen($search) < 2) { - return []; - } - - if (substr($search, 0, 1) === '@') { - $search = substr($search, 1); - } - - // check if searching in the local global contact table is enabled - if (Config::get('system', 'poco_local_search')) { - $return = GContact::searchByName($search, $mode); - } else { - $p = defaults($_GET, 'page', 1) != 1 ? '&p=' . defaults($_GET, 'page', 1) : ''; - - $curlResult = Network::curl(get_server() . '/lsearch?f=' . $p . '&search=' . urlencode($search)); - if ($curlResult->isSuccess()) { - $lsearch = json_decode($curlResult->getBody(), true); - if (!empty($lsearch['results'])) { - $return = $lsearch['results']; - } - } - } - - return defaults($return, []); - } } diff --git a/src/Core/Authentication.php b/src/Core/Authentication.php index 59061c04c..6d017664a 100644 --- a/src/Core/Authentication.php +++ b/src/Core/Authentication.php @@ -1,4 +1,5 @@ $user["uid"], + $value = json_encode([ + "uid" => $user["uid"], "hash" => self::getCookieHashForUser($user), - "ip" => defaults($_SERVER, 'REMOTE_ADDR', '0.0.0.0')]); + "ip" => ($_SERVER['REMOTE_ADDR'] ?? '') ?: '0.0.0.0' + ]); } else { $value = ""; } @@ -88,4 +93,3 @@ class Authentication extends BaseObject } } } - diff --git a/src/Core/Cache/MemcacheCache.php b/src/Core/Cache/MemcacheCache.php index 717166952..6797a70c2 100644 --- a/src/Core/Cache/MemcacheCache.php +++ b/src/Core/Cache/MemcacheCache.php @@ -15,6 +15,7 @@ class MemcacheCache extends Cache implements IMemoryCache { use TraitCompareSet; use TraitCompareDelete; + use TraitMemcacheCommand; /** * @var Memcache @@ -34,11 +35,11 @@ class MemcacheCache extends Cache implements IMemoryCache $this->memcache = new Memcache(); - $memcache_host = $config->get('system', 'memcache_host'); - $memcache_port = $config->get('system', 'memcache_port'); + $this->server = $config->get('system', 'memcache_host');; + $this->port = $config->get('system', 'memcache_port'); - if (!$this->memcache->connect($memcache_host, $memcache_port)) { - throw new Exception('Expected Memcache server at ' . $memcache_host . ':' . $memcache_port . ' isn\'t available'); + if (!@$this->memcache->connect($this->server, $this->port)) { + throw new Exception('Expected Memcache server at ' . $this->server . ':' . $this->port . ' isn\'t available'); } } @@ -47,21 +48,7 @@ class MemcacheCache extends Cache implements IMemoryCache */ public function getAllKeys($prefix = null) { - $keys = []; - $allSlabs = $this->memcache->getExtendedStats('slabs'); - foreach ($allSlabs as $slabs) { - foreach (array_keys($slabs) as $slabId) { - $cachedump = $this->memcache->getExtendedStats('cachedump', (int)$slabId); - foreach ($cachedump as $key => $arrVal) { - if (!is_array($arrVal)) { - continue; - } - $keys = array_merge($keys, array_keys($arrVal)); - } - } - } - - $keys = $this->getOriginalKeys($keys); + $keys = $this->getOriginalKeys($this->getMemcacheKeys()); return $this->filterArrayKeysByPrefix($keys, $prefix); } diff --git a/src/Core/Cache/MemcachedCache.php b/src/Core/Cache/MemcachedCache.php index 69f6b9a0a..95bfae39f 100644 --- a/src/Core/Cache/MemcachedCache.php +++ b/src/Core/Cache/MemcachedCache.php @@ -16,6 +16,7 @@ class MemcachedCache extends Cache implements IMemoryCache { use TraitCompareSet; use TraitCompareDelete; + use TraitMemcacheCommand; /** * @var \Memcached @@ -27,17 +28,6 @@ class MemcachedCache extends Cache implements IMemoryCache */ private $logger; - /** - * @var string First server address - */ - - private $firstServer; - - /** - * @var int First server port - */ - private $firstPort; - /** * Due to limitations of the INI format, the expected configuration for Memcached servers is the following: * array { @@ -69,8 +59,8 @@ class MemcachedCache extends Cache implements IMemoryCache } }); - $this->firstServer = $memcached_hosts[0][0] ?? 'localhost'; - $this->firstPort = $memcached_hosts[0][1] ?? 11211; + $this->server = $memcached_hosts[0][0] ?? 'localhost'; + $this->port = $memcached_hosts[0][1] ?? 11211; $this->memcached->addServers($memcached_hosts); @@ -84,97 +74,11 @@ class MemcachedCache extends Cache implements IMemoryCache */ public function getAllKeys($prefix = null) { - $keys = $this->getOriginalKeys($this->getMemcachedKeys()); + $keys = $this->getOriginalKeys($this->getMemcacheKeys()); return $this->filterArrayKeysByPrefix($keys, $prefix); } - /** - * Get all memcached keys. - * Special function because getAllKeys() is broken since memcached 1.4.23. - * - * cleaned up version of code found on Stackoverflow.com by Maduka Jayalath - * @see https://stackoverflow.com/a/34724821 - * - * @return array|int - all retrieved keys (or negative number on error) - */ - private function getMemcachedKeys() - { - $mem = @fsockopen($this->firstServer, $this->firstPort); - if ($mem === false) { - return -1; - } - - // retrieve distinct slab - $r = @fwrite($mem, 'stats items' . chr(10)); - if ($r === false) { - return -2; - } - - $slab = []; - while (($l = @fgets($mem, 1024)) !== false) { - // finished? - $l = trim($l); - if ($l == 'END') { - break; - } - - $m = []; - // - $r = preg_match('/^STAT\sitems\:(\d+)\:/', $l, $m); - if ($r != 1) { - return -3; - } - $a_slab = $m[1]; - - if (!array_key_exists($a_slab, $slab)) { - $slab[$a_slab] = []; - } - } - - reset($slab); - foreach ($slab as $a_slab_key => &$a_slab) { - $r = @fwrite($mem, 'stats cachedump ' . $a_slab_key . ' 100' . chr(10)); - if ($r === false) { - return -4; - } - - while (($l = @fgets($mem, 1024)) !== false) { - // finished? - $l = trim($l); - if ($l == 'END') { - break; - } - - $m = []; - // ITEM 42 [118 b; 1354717302 s] - $r = preg_match('/^ITEM\s([^\s]+)\s/', $l, $m); - if ($r != 1) { - return -5; - } - $a_key = $m[1]; - - $a_slab[] = $a_key; - } - } - - // close the connection - @fclose($mem); - unset($mem); - - $keys = []; - reset($slab); - foreach ($slab AS &$a_slab) { - reset($a_slab); - foreach ($a_slab AS &$a_key) { - $keys[] = $a_key; - } - } - unset($slab); - - return $keys; - } - /** * (@inheritdoc) */ diff --git a/src/Core/Cache/RedisCache.php b/src/Core/Cache/RedisCache.php index b2638c49f..3558a3846 100644 --- a/src/Core/Cache/RedisCache.php +++ b/src/Core/Cache/RedisCache.php @@ -37,9 +37,9 @@ class RedisCache extends Cache implements IMemoryCache $redis_pw = $config->get('system', 'redis_password'); $redis_db = $config->get('system', 'redis_db', 0); - if (isset($redis_port) && !$this->redis->connect($redis_host, $redis_port)) { + if (isset($redis_port) && !@$this->redis->connect($redis_host, $redis_port)) { throw new Exception('Expected Redis server at ' . $redis_host . ':' . $redis_port . ' isn\'t available'); - } elseif (!$this->redis->connect($redis_host)) { + } elseif (!@$this->redis->connect($redis_host)) { throw new Exception('Expected Redis server at ' . $redis_host . ' isn\'t available'); } diff --git a/src/Core/Cache/TraitMemcacheCommand.php b/src/Core/Cache/TraitMemcacheCommand.php new file mode 100644 index 000000000..0bbab79b2 --- /dev/null +++ b/src/Core/Cache/TraitMemcacheCommand.php @@ -0,0 +1,104 @@ +memcache(d) adds a key + * - $this->getMemcacheKeys is called directly "after" + * - But $this->memcache(d) isn't finished adding the key, so getMemcacheKeys doesn't find it + * + * @return array All keys of the memcache instance + * + * @throws InternalServerErrorException + */ + protected function getMemcacheKeys() + { + $string = $this->sendMemcacheCommand("stats items"); + $lines = explode("\r\n", $string); + $slabs = []; + $keys = []; + + foreach ($lines as $line) { + + if (preg_match("/STAT items:([\d]+):number ([\d]+)/", $line, $matches) && + isset($matches[1]) && + !in_array($matches[1], $keys)) { + + $slabs[] = $matches[1]; + $string = $this->sendMemcacheCommand("stats cachedump " . $matches[1] . " " . $matches[2]); + preg_match_all("/ITEM (.*?) /", $string, $matches); + $keys = array_merge($keys, $matches[1]); + } + } + + return $keys; + } + + /** + * Taken directly from memcache PECL source + * Sends a command to the memcache instance and returns the result + * as a string + * + * http://pecl.php.net/package/memcache + * + * @param string $command The command to send to the Memcache server + * + * @return string The returned buffer result + * + * @throws InternalServerErrorException In case the memcache server isn't available (anymore) + */ + protected function sendMemcacheCommand(string $command) + { + $s = @fsockopen($this->server, $this->port); + if (!$s) { + throw new InternalServerErrorException("Cant connect to:" . $this->server . ':' . $this->port); + } + + fwrite($s, $command . "\r\n"); + $buf = ''; + + while (!feof($s)) { + + $buf .= fgets($s, 256); + + if (strpos($buf, "END\r\n") !== false) { // stat says end + break; + } + + if (strpos($buf, "DELETED\r\n") !== false || strpos($buf, "NOT_FOUND\r\n") !== false) { // delete says these + break; + } + + if (strpos($buf, "OK\r\n") !== false) { // flush_all says ok + break; + } + } + + fclose($s); + return ($buf); + } +} diff --git a/src/Core/Config/Configuration.php b/src/Core/Config/Configuration.php index f904f369b..c54fbb27e 100644 --- a/src/Core/Config/Configuration.php +++ b/src/Core/Config/Configuration.php @@ -7,8 +7,8 @@ use Friendica\Model; /** * This class is responsible for all system-wide configuration values in Friendica * There are two types of storage - * - The Config-Files (loaded into the FileCache @see Cache\ConfigCache ) - * - The Config-DB-Table (per Config-DB-model @see Model\Config\Config ) + * - The Config-Files (loaded into the FileCache @see Cache\ConfigCache) + * - The Config-DB-Table (per Config-DB-model @see Model\Config\Config) */ abstract class Configuration { @@ -59,7 +59,7 @@ abstract class Configuration * * Get a particular config value from the given category ($cat) * and the $key from a cached storage either from the $this->configAdapter - * (@see IConfigAdapter ) or from the $this->configCache (@see ConfigCache ). + * (@see IConfigAdapter) or from the $this->configCache (@see ConfigCache). * * @param string $cat The category of the configuration value * @param string $key The configuration key to query @@ -89,7 +89,7 @@ abstract class Configuration * @brief Deletes the given key from the system configuration. * * Removes the configured value from the stored cache in $this->configCache - * (@see ConfigCache ) and removes it from the database (@see IConfigAdapter ). + * (@see ConfigCache) and removes it from the database (@see IConfigAdapter). * * @param string $cat The category of the configuration value * @param string $key The configuration key to delete diff --git a/src/Core/Config/PConfiguration.php b/src/Core/Config/PConfiguration.php index badec9dfc..c54fc3122 100644 --- a/src/Core/Config/PConfiguration.php +++ b/src/Core/Config/PConfiguration.php @@ -8,7 +8,7 @@ use Friendica\Model; * This class is responsible for the user-specific configuration values in Friendica * The values are set through the Config-DB-Table (per Config-DB-model @see Model\Config\PConfig) * - * The configuration cache (@see Cache\PConfigCache ) is used for temporary caching of database calls. This will + * The configuration cache (@see Cache\PConfigCache) is used for temporary caching of database calls. This will * increase the performance. */ abstract class PConfiguration @@ -52,7 +52,7 @@ abstract class PConfiguration * @param string $cat The category of the configuration value * * @return void - * @see PConfigCache ) + * @see PConfigCache * */ abstract public function load(int $uid, string $cat = 'config'); @@ -63,7 +63,7 @@ abstract class PConfiguration * * Get a particular user's config value from the given category ($cat) * and the $key with the $uid from a cached storage either from the $this->configAdapter - * (@see IConfigAdapter ) or from the $this->configCache (@see PConfigCache ). + * (@see IConfigAdapter) or from the $this->configCache (@see PConfigCache). * * @param int $uid The user_id * @param string $cat The category of the configuration value @@ -96,7 +96,7 @@ abstract class PConfiguration * Deletes the given key from the users's configuration. * * Removes the configured value from the stored cache in $this->configCache - * (@see ConfigCache ) and removes it from the database (@see IConfigAdapter ) + * (@see ConfigCache) and removes it from the database (@see IConfigAdapter) * with the given $uid. * * @param int $uid The user_id diff --git a/src/Core/L10n.php b/src/Core/L10n.php index db008d6a9..0b76fc639 100644 --- a/src/Core/L10n.php +++ b/src/Core/L10n.php @@ -31,10 +31,11 @@ class L10n extends BaseObject * * If called repeatedly, it won't save the translation strings again, just load the new ones. * + * @param string $lang Language code + * + * @throws \Exception * @see popLang() * @brief Stores the current language strings and load a different language. - * @param string $lang Language code - * @throws \Exception */ public static function pushLang($lang) { @@ -63,6 +64,7 @@ class L10n extends BaseObject * * @param string $s * @param array $vars Variables to interpolate in the translation string + * * @return string */ public static function t($s, ...$vars) @@ -86,6 +88,7 @@ class L10n extends BaseObject * @param string $singular * @param string $plural * @param int $count + * * @return string * @throws \Exception */ @@ -114,6 +117,7 @@ class L10n extends BaseObject * @brief Translate days and months names. * * @param string $s String with day or month name. + * * @return string Translated string. */ public static function getDay($s) @@ -125,10 +129,23 @@ class L10n extends BaseObject * @brief Translate short days and months names. * * @param string $s String with short day or month name. + * * @return string Translated string. */ public static function getDayShort($s) { return self::getClass(L10nClass::class)->getDayShort($s); } + + /** + * Load poke verbs + * + * @return array index is present tense verb + * value is array containing past tense verb, translation of present, translation of past + * @hook poke_verbs pokes array + */ + public static function getPokeVerbs() + { + return self::getClass(L10nClass::class)->getPokeVerbs(); + } } diff --git a/src/Core/L10n/L10n.php b/src/Core/L10n/L10n.php index f4e14c78e..ce930b402 100644 --- a/src/Core/L10n/L10n.php +++ b/src/Core/L10n/L10n.php @@ -53,12 +53,12 @@ class L10n */ private $logger; - public function __construct(Configuration $config, Database $dba, LoggerInterface $logger) + public function __construct(Configuration $config, Database $dba, LoggerInterface $logger, array $server, array $get) { $this->dba = $dba; $this->logger = $logger; - $this->loadTranslationTable(L10n::detectLanguage($config->get('system', 'language', 'en'))); + $this->loadTranslationTable(L10n::detectLanguage($server, $get, $config->get('system', 'language', 'en'))); } /** @@ -140,7 +140,7 @@ class L10n $this->lang = $this->langSave; $this->stringsSave = null; - $this->langSave = null; + $this->langSave = null; } /** @@ -158,6 +158,11 @@ class L10n { $lang = Strings::sanitizeFilePathItem($lang); + // Don't override the language setting with empty languages + if (empty($lang)) { + return; + } + $a = new \stdClass(); $a->strings = []; @@ -166,12 +171,12 @@ class L10n while ($p = $this->dba->fetch($addons)) { $name = Strings::sanitizeFilePathItem($p['name']); if (file_exists("addon/$name/lang/$lang/strings.php")) { - include "addon/$name/lang/$lang/strings.php"; + include __DIR__ . "/../../../addon/$name/lang/$lang/strings.php"; } } - if (file_exists("view/lang/$lang/strings.php")) { - include "view/lang/$lang/strings.php"; + if (file_exists(__DIR__ . "/../../../view/lang/$lang/strings.php")) { + include __DIR__ . "/../../../view/lang/$lang/strings.php"; } $this->lang = $lang; @@ -184,49 +189,78 @@ class L10n * @brief Returns the preferred language from the HTTP_ACCEPT_LANGUAGE header * * @param string $sysLang The default fallback language + * @param array $server The $_SERVER array + * @param array $get The $_GET array * * @return string The two-letter language code */ - public static function detectLanguage(string $sysLang = 'en') + public static function detectLanguage(array $server, array $get, string $sysLang = 'en') { - $lang_list = []; + $lang_variable = $server['HTTP_ACCEPT_LANGUAGE'] ?? null; - if (!empty($_SERVER['HTTP_ACCEPT_LANGUAGE'])) { - // break up string into pieces (languages and q factors) - preg_match_all('/([a-z]{1,8}(-[a-z]{1,8})?)\s*(;\s*q\s*=\s*(1|0\.[0-9]+))?/i', $_SERVER['HTTP_ACCEPT_LANGUAGE'], $lang_parse); + $acceptedLanguages = preg_split('/,\s*/', $lang_variable); - if (count($lang_parse[1])) { - // go through the list of prefered languages and add a generic language - // for sub-linguas (e.g. de-ch will add de) if not already in array - for ($i = 0; $i < count($lang_parse[1]); $i++) { - $lang_list[] = strtolower($lang_parse[1][$i]); - if (strlen($lang_parse[1][$i]) > 3) { - $dashpos = strpos($lang_parse[1][$i], '-'); - if (!in_array(substr($lang_parse[1][$i], 0, $dashpos), $lang_list)) { - $lang_list[] = strtolower(substr($lang_parse[1][$i], 0, $dashpos)); - } + if (empty($acceptedLanguages)) { + $acceptedLanguages = []; + } + + // Add get as absolute quality accepted language (except this language isn't valid) + if (!empty($get['lang'])) { + $acceptedLanguages[] = $get['lang']; + } + + // return the sys language in case there's nothing to do + if (empty($acceptedLanguages)) { + return $sysLang; + } + + // Set the syslang as default fallback + $current_lang = $sysLang; + // start with quality zero (every guessed language is more acceptable ..) + $current_q = 0; + + foreach ($acceptedLanguages as $acceptedLanguage) { + $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 + ); + + // Invalid language? -> skip + if (!$res) { + continue; + } + + // split language codes based on it's "-" + $lang_code = explode('-', $matches[1]); + + // determine the quality of the guess + if (isset($matches[2])) { + $lang_quality = (float)$matches[2]; + } else { + // fallback so without a quality parameter, it's probably the best + $lang_quality = 1; + } + + // loop through each part of the code-parts + while (count($lang_code)) { + // try to mix them so we can get double-code parts too + $match_lang = strtolower(join('-', $lang_code)); + if (file_exists(__DIR__ . "/../../../view/lang/$match_lang") && + is_dir(__DIR__ . "/../../../view/lang/$match_lang")) { + if ($lang_quality > $current_q) { + $current_lang = $match_lang; + $current_q = $lang_quality; + break; } } + + // remove the most right code-part + array_pop($lang_code); } } - if (isset($_GET['lang'])) { - $lang_list = [$_GET['lang']]; - } - - // check if we have translations for the preferred languages and pick the 1st that has - foreach ($lang_list as $lang) { - if ($lang === 'en' || (file_exists("view/lang/$lang") && is_dir("view/lang/$lang"))) { - $preferred = $lang; - break; - } - } - if (isset($preferred)) { - return $preferred; - } - - // in case none matches, get the system wide configured language, or fall back to English - return $sysLang; + return $current_lang; } /** diff --git a/src/Core/Logger.php b/src/Core/Logger.php index e376485e5..e8d95fa85 100644 --- a/src/Core/Logger.php +++ b/src/Core/Logger.php @@ -64,7 +64,6 @@ class Logger extends BaseObject self::TRACE => 'Trace', self::DEBUG => 'Debug', self::DATA => 'Data', - self::ALL => 'All', ]; /** diff --git a/src/Core/NotificationsManager.php b/src/Core/NotificationsManager.php index 8ac5d93c7..3c8367c91 100644 --- a/src/Core/NotificationsManager.php +++ b/src/Core/NotificationsManager.php @@ -137,7 +137,7 @@ class NotificationsManager extends BaseObject */ public function getTabs() { - $selected = defaults(self::getApp()->argv, 1, ''); + $selected = self::getApp()->argv[1] ?? ''; $tabs = [ [ @@ -549,6 +549,7 @@ class NotificationsManager extends BaseObject * which aren't marked as ignored * @param int $start Start the query at this point * @param int $limit Maximum number of query results + * @param int $id When set, only the introduction with this id is displayed * * @return array with * string 'ident' => Notification identifier @@ -556,14 +557,20 @@ class NotificationsManager extends BaseObject * @throws \Friendica\Network\HTTPException\InternalServerErrorException * @throws \ImagickException */ - public function introNotifs($all = false, $start = 0, $limit = 80) + public function introNotifs($all = false, $start = 0, $limit = 80, $id = 0) { $ident = 'introductions'; $notifs = []; $sql_extra = ""; - if (!$all) { - $sql_extra = " AND NOT `ignore` "; + if (empty($id)) { + if (!$all) { + $sql_extra = " AND NOT `ignore` "; + } + + $sql_extra .= " AND NOT `intro`.`blocked` "; + } else { + $sql_extra = sprintf(" AND `intro`.`id` = %d ", intval($id)); } /// @todo Fetch contact details by "Contact::getDetailsByUrl" instead of queries to contact, fcontact and gcontact @@ -578,7 +585,7 @@ class NotificationsManager extends BaseObject LEFT JOIN `contact` ON `contact`.`id` = `intro`.`contact-id` LEFT JOIN `gcontact` ON `gcontact`.`nurl` = `contact`.`nurl` LEFT JOIN `fcontact` ON `intro`.`fid` = `fcontact`.`id` - WHERE `intro`.`uid` = ? $sql_extra AND `intro`.`blocked` = 0 + WHERE `intro`.`uid` = ? $sql_extra LIMIT ?, ?", $_SESSION['uid'], $start, diff --git a/src/Core/Renderer.php b/src/Core/Renderer.php index fd5e73302..d9d913050 100644 --- a/src/Core/Renderer.php +++ b/src/Core/Renderer.php @@ -136,7 +136,7 @@ class Renderer extends BaseObject */ public static function getTemplateEngine() { - $template_engine = defaults(self::$theme, 'template_engine', 'smarty3'); + $template_engine = (self::$theme['template_engine'] ?? '') ?: 'smarty3'; if (isset(self::$template_engines[$template_engine])) { if (isset(self::$template_engine_instance[$template_engine])) { diff --git a/src/Core/Search.php b/src/Core/Search.php index 9f8375da1..9700c6472 100644 --- a/src/Core/Search.php +++ b/src/Core/Search.php @@ -5,6 +5,7 @@ namespace Friendica\Core; use Friendica\BaseObject; use Friendica\Database\DBA; use Friendica\Model\Contact; +use Friendica\Model\GContact; use Friendica\Network\HTTPException; use Friendica\Network\Probe; use Friendica\Object\Search\ContactResult; @@ -44,28 +45,31 @@ class Search extends BaseObject if ((filter_var($user, FILTER_VALIDATE_EMAIL) && Network::isEmailDomainValid($user)) || (substr(Strings::normaliseLink($user), 0, 7) == "http://")) { + /// @todo Possibly use "getIdForURL" instead? $user_data = Probe::uri($user); if (empty($user_data)) { return $emptyResultList; } - if (!(in_array($user_data["network"], Protocol::FEDERATED))) { + if (!in_array($user_data["network"], Protocol::FEDERATED)) { return $emptyResultList; } - $contactDetails = Contact::getDetailsByURL(defaults($user_data, 'url', ''), local_user()); - $itemUrl = defaults($contactDetails, 'addr', defaults($user_data, 'url', '')); + // Ensure that we do have a contact entry + Contact::getIdForURL($user_data['url'] ?? ''); + + $contactDetails = Contact::getDetailsByURL($user_data['url'] ?? '', local_user()); $result = new ContactResult( - defaults($user_data, 'name', ''), - defaults($user_data, 'addr', ''), - $itemUrl, - defaults($user_data, 'url', ''), - defaults($user_data, 'photo', ''), - defaults($user_data, 'network', ''), - defaults($contactDetails, 'cid', 0), + $user_data['name'] ?? '', + $user_data['addr'] ?? '', + ($contactDetails['addr'] ?? '') ?: ($user_data['url'] ?? ''), + $user_data['url'] ?? '', + $user_data['photo'] ?? '', + $user_data['network'] ?? '', + $contactDetails['id'] ?? 0, 0, - defaults($user_data, 'tags', '') + $user_data['tags'] ?? '' ); return new ResultList(1, 1, 1, [$result]); @@ -112,27 +116,28 @@ class Search extends BaseObject $results = json_decode($resultJson, true); $resultList = new ResultList( - defaults($results, 'page', 1), - defaults($results, 'count', 0), - defaults($results, 'itemsperpage', 30) + ($results['page'] ?? 0) ?: 1, + $results['count'] ?? 0, + ($results['itemsperpage'] ?? 0) ?: 30 ); - $profiles = defaults($results, 'profiles', []); + $profiles = $results['profiles'] ?? []; foreach ($profiles as $profile) { - $contactDetails = Contact::getDetailsByURL(defaults($profile, 'profile_url', ''), local_user()); - $itemUrl = defaults($contactDetails, 'addr', defaults($profile, 'profile_url', '')); + $profile_url = $profile['profile_url'] ?? ''; + $contactDetails = Contact::getDetailsByURL($profile_url, local_user()); $result = new ContactResult( - defaults($profile, 'name', ''), - defaults($profile, 'addr', ''), - $itemUrl, - defaults($profile, 'profile_url', ''), - defaults($profile, 'photo', ''), + $profile['name'] ?? '', + $profile['addr'] ?? '', + ($contactDetails['addr'] ?? '') ?: $profile_url, + $profile_url, + $profile['photo'] ?? '', Protocol::DFRN, - defaults($contactDetails, 'cid', 0), + $contactDetails['cid'] ?? 0, 0, - defaults($profile, 'tags', '')); + $profile['tags'] ?? '' + ); $resultList->addResult($result); } @@ -241,4 +246,46 @@ class Search extends BaseObject return $resultList; } + + /** + * Searching for global contacts for autocompletion + * + * @brief Searching for global contacts for autocompletion + * @param string $search Name or part of a name or nick + * @param string $mode Search mode (e.g. "community") + * @param int $page Page number (starts at 1) + * @return array with the search results + * @throws HTTPException\InternalServerErrorException + */ + public static function searchGlobalContact($search, $mode, int $page = 1) + { + if (Config::get('system', 'block_public') && !Session::isAuthenticated()) { + return []; + } + + // don't search if search term has less than 2 characters + if (!$search || mb_strlen($search) < 2) { + return []; + } + + if (substr($search, 0, 1) === '@') { + $search = substr($search, 1); + } + + // check if searching in the local global contact table is enabled + if (Config::get('system', 'poco_local_search')) { + $return = GContact::searchByName($search, $mode); + } else { + $p = $page > 1 ? 'p=' . $page : ''; + $curlResult = Network::curl(get_server() . '/search/people?' . $p . '&q=' . urlencode($search), false, ['accept_content' => 'application/json']); + if ($curlResult->isSuccess()) { + $searchResult = json_decode($curlResult->getBody(), true); + if (!empty($searchResult['profiles'])) { + $return = $searchResult['profiles']; + } + } + } + + return $return ?? []; + } } diff --git a/src/Core/Session.php b/src/Core/Session.php index 22909a6e6..aaead868a 100644 --- a/src/Core/Session.php +++ b/src/Core/Session.php @@ -9,8 +9,10 @@ use Friendica\App; use Friendica\Core\Session\CacheSessionHandler; use Friendica\Core\Session\DatabaseSessionHandler; use Friendica\Database\DBA; +use Friendica\Model\Contact; use Friendica\Model\User; use Friendica\Util\DateTimeFormat; +use Friendica\Util\Strings; /** * High-level Session service class @@ -51,7 +53,7 @@ class Session /** * Retrieves a key from the session super global or the defaults if the key is missing or the value is falsy. - * + * * Handle the case where session_start() hasn't been called and the super global isn't available. * * @param string $name @@ -97,6 +99,14 @@ class Session unset($_SESSION[$name]); } + /** + * Clears the current session array + */ + public static function clear() + { + $_SESSION = []; + } + /** * @brief Sets the provided user's authenticated session * @@ -105,6 +115,7 @@ class Session * @param bool $login_initial * @param bool $interactive * @param bool $login_refresh + * @throws \Friendica\Network\HTTPException\ForbiddenException * @throws \Friendica\Network\HTTPException\InternalServerErrorException */ public static function setAuthenticatedForUser(App $a, array $user_record, $login_initial = false, $interactive = false, $login_refresh = false) @@ -117,9 +128,11 @@ class Session 'page_flags' => $user_record['page-flags'], 'my_url' => $a->getBaseURL() . '/profile/' . $user_record['nickname'], 'my_address' => $user_record['nickname'] . '@' . substr($a->getBaseURL(), strpos($a->getBaseURL(), '://') + 3), - 'addr' => defaults($_SERVER, 'REMOTE_ADDR', '0.0.0.0'), + 'addr' => ($_SERVER['REMOTE_ADDR'] ?? '') ?: '0.0.0.0' ]); + self::setVisitorsContacts(); + $member_since = strtotime($user_record['register_date']); self::set('new_member', time() < ($member_since + ( 60 * 60 * 24 * 14))); @@ -201,4 +214,68 @@ class Session } } } + + /** + * Returns contact ID for given user ID + * + * @param integer $uid User ID + * @return integer Contact ID of visitor for given user ID + */ + public static function getRemoteContactID($uid) + { + if (empty($_SESSION['remote'][$uid])) { + return false; + } + + return $_SESSION['remote'][$uid]; + } + + /** + * Returns User ID for given contact ID of the visitor + * + * @param integer $cid Contact ID + * @return integer User ID for given contact ID of the visitor + */ + public static function getUserIDForVisitorContactID($cid) + { + if (empty($_SESSION['remote'])) { + return false; + } + + return array_search($cid, $_SESSION['remote']); + } + + /** + * Set the session variable that contains the contact IDs for the visitor's contact URL + * + * @param string $url Contact URL + */ + public static function setVisitorsContacts() + { + $_SESSION['remote'] = []; + + $remote_contacts = DBA::select('contact', ['id', 'uid'], ['nurl' => Strings::normaliseLink($_SESSION['my_url']), 'rel' => [Contact::FOLLOWER, Contact::FRIEND], 'self' => false]); + while ($contact = DBA::fetch($remote_contacts)) { + if (($contact['uid'] == 0) || Contact::isBlockedByUser($contact['id'], $contact['uid'])) { + continue; + } + + $_SESSION['remote'][$contact['uid']] = $contact['id']; + } + DBA::close($remote_contacts); + } + + /** + * Returns if the current visitor is authenticated + * + * @return boolean "true" when visitor is either a local or remote user + */ + public static function isAuthenticated() + { + if (empty($_SESSION['authenticated'])) { + return false; + } + + return $_SESSION['authenticated']; + } } diff --git a/src/Core/StorageManager.php b/src/Core/StorageManager.php index 8cd7d4395..832d9819c 100644 --- a/src/Core/StorageManager.php +++ b/src/Core/StorageManager.php @@ -48,7 +48,7 @@ class StorageManager public static function getByName($name) { self::setup(); - return defaults(self::$backends, $name, ''); + return self::$backends[$name] ?? ''; } /** diff --git a/src/Database/DBA.php b/src/Database/DBA.php index 7f3e071fa..6f746ff64 100644 --- a/src/Database/DBA.php +++ b/src/Database/DBA.php @@ -449,6 +449,7 @@ class DBA extends BaseObject * * @param string|array $table Table name or array [schema => table] * @param array $condition array of fields for condition + * @param array $params Array of several parameters * * @return int * @@ -462,9 +463,9 @@ class DBA extends BaseObject * $count = DBA::count($table, $condition); * @throws \Exception */ - public static function count($table, array $condition = []) + public static function count($table, array $condition = [], array $params = []) { - return self::getClass(Database::class)->count($table, $condition); + return self::getClass(Database::class)->count($table, $condition, $params); } /** diff --git a/src/Database/DBStructure.php b/src/Database/DBStructure.php index cf707f205..218cab950 100644 --- a/src/Database/DBStructure.php +++ b/src/Database/DBStructure.php @@ -12,7 +12,7 @@ use Friendica\Core\L10n; use Friendica\Core\Logger; use Friendica\Util\DateTimeFormat; -require_once 'include/dba.php'; +require_once __DIR__ . '/../../include/dba.php'; /** * @brief This class contain functions for the database management @@ -421,7 +421,7 @@ class DBStructure } if (isset($database[$name]["table_status"]["Comment"])) { - $structurecomment = defaults($structure, "comment", ""); + $structurecomment = $structure["comment"] ?? ''; if ($database[$name]["table_status"]["Comment"] != $structurecomment) { $sql2 = "COMMENT = '" . DBA::escape($structurecomment) . "'"; @@ -465,7 +465,7 @@ class DBStructure // Compare the field structure field by field foreach ($structure["fields"] AS $fieldname => $parameters) { // Compare the field definition - $field_definition = defaults($database[$name]["fields"], $fieldname, ['Collation' => '']); + $field_definition = ($database[$name]["fields"][$fieldname] ?? '') ?: ['Collation' => '']; // Define the default collation if not given if (!isset($parameters['Collation']) && !empty($field_definition['Collation'])) { @@ -717,8 +717,8 @@ class DBStructure * @todo You cannot rename a primary key if "auto increment" is set * * @param string $table Table name - * @param array $columns Columns Syntax for Rename: [ $old1 => [ $new1, $type1 ], $old2 => [ $new2, $type2 ], ... ] ) - * Syntax for Primary Key: [ $col1, $col2, ...] ) + * @param array $columns Columns Syntax for Rename: [ $old1 => [ $new1, $type1 ], $old2 => [ $new2, $type2 ], ... ] + * Syntax for Primary Key: [ $col1, $col2, ...] * @param int $type The type of renaming (Default is Column) * * @return boolean Was the renaming successful? diff --git a/src/Database/Database.php b/src/Database/Database.php index d13b52848..72ce6bbe6 100644 --- a/src/Database/Database.php +++ b/src/Database/Database.php @@ -67,7 +67,7 @@ class Database { // Use environment variables for mysql if they are set beforehand if (!empty($server['MYSQL_HOST']) - && !empty($server['MYSQL_USERNAME'] || !empty($server['MYSQL_USER'])) + && (!empty($server['MYSQL_USERNAME'] || !empty($server['MYSQL_USER']))) && $server['MYSQL_PASSWORD'] !== false && !empty($server['MYSQL_DATABASE'])) { @@ -90,9 +90,12 @@ class Database public function connect() { if (!is_null($this->connection) && $this->connected()) { - return true; + return $this->connected; } + // Reset connected state + $this->connected = false; + $port = 0; $serveraddr = trim($this->configCache->get('database', 'hostname')); $serverdata = explode(':', $serveraddr); @@ -187,19 +190,20 @@ class Database */ public function disconnect() { - if (is_null($this->connection)) { - return; + if (!is_null($this->connection)) { + switch ($this->driver) { + case 'pdo': + $this->connection = null; + break; + case 'mysqli': + $this->connection->close(); + $this->connection = null; + break; + } } - switch ($this->driver) { - case 'pdo': - $this->connection = null; - break; - case 'mysqli': - $this->connection->close(); - $this->connection = null; - break; - } + $this->driver = null; + $this->connected = false; } /** @@ -369,6 +373,7 @@ class Database $connected = $this->connection->ping(); break; } + return $connected; } @@ -1465,6 +1470,7 @@ class Database * * @param string|array $table Table name or array [schema => table] * @param array $condition Array of fields for condition + * @param array $params Array of several parameters * * @return int * @@ -1478,7 +1484,7 @@ class Database * $count = DBA::count($table, $condition); * @throws \Exception */ - public function count($table, array $condition = []) + public function count($table, array $condition = [], array $params = []) { if (empty($table)) { return false; @@ -1488,7 +1494,15 @@ class Database $condition_string = DBA::buildCondition($condition); - $sql = "SELECT COUNT(*) AS `count` FROM " . $table_string . $condition_string; + if (empty($params['expression'])) { + $expression = '*'; + } elseif (!empty($params['distinct'])) { + $expression = "DISTINCT " . DBA::quoteIdentifier($params['expression']); + } else { + $expression = DBA::quoteIdentifier($params['expression']); + } + + $sql = "SELECT COUNT(" . $expression . ") AS `count` FROM " . $table_string . $condition_string; $row = $this->fetchFirst($sql, $condition); diff --git a/src/Factory/LoggerFactory.php b/src/Factory/LoggerFactory.php index 0feb3b2f7..55091a487 100644 --- a/src/Factory/LoggerFactory.php +++ b/src/Factory/LoggerFactory.php @@ -38,19 +38,17 @@ class LoggerFactory 'Friendica\\Util\\Logger', ]; - /** - * Retrieve the channel based on the __FILE__ - * - * @return string - */ - private function findChannel() + private $channel; + + public function __construct(string $channel) { - return basename($_SERVER['PHP_SELF'], '.php'); + $this->channel = $channel; } /** * Creates a new PSR-3 compliant logger instances * + * @param Database $database The Friendica Database instance * @param Configuration $config The config * @param Profiler $profiler The profiler of the app * @@ -59,7 +57,7 @@ class LoggerFactory * @throws \Exception * @throws InternalServerErrorException */ - public function create(Database $database, Configuration $config, Profiler $profiler) + public function create( Database $database, Configuration $config, Profiler $profiler) { if (empty($config->get('system', 'debugging', false))) { $logger = new VoidLogger(); @@ -76,7 +74,7 @@ class LoggerFactory $loggerTimeZone = new \DateTimeZone('UTC'); Monolog\Logger::setTimezone($loggerTimeZone); - $logger = new Monolog\Logger($this->findChannel()); + $logger = new Monolog\Logger($this->channel); $logger->pushProcessor(new Monolog\Processor\PsrLogMessageProcessor()); $logger->pushProcessor(new Monolog\Processor\ProcessIdProcessor()); $logger->pushProcessor(new Monolog\Processor\UidProcessor()); @@ -91,7 +89,7 @@ class LoggerFactory break; case 'syslog': - $logger = new SyslogLogger($this->findChannel(), $introspection, $loglevel); + $logger = new SyslogLogger($this->channel, $introspection, $loglevel); break; case 'stream': @@ -99,7 +97,7 @@ class LoggerFactory $stream = $config->get('system', 'logfile'); // just add a stream in case it's either writable or not file if (!is_file($stream) || is_writable($stream)) { - $logger = new StreamLogger($this->findChannel(), $stream, $introspection, $loglevel); + $logger = new StreamLogger($this->channel, $stream, $introspection, $loglevel); } else { $logger = new VoidLogger(); } diff --git a/src/Model/Contact.php b/src/Model/Contact.php index ffdee6aa0..3033bb90b 100644 --- a/src/Model/Contact.php +++ b/src/Model/Contact.php @@ -13,6 +13,7 @@ use Friendica\Core\L10n; use Friendica\Core\Logger; use Friendica\Core\Protocol; use Friendica\Core\System; +use Friendica\Core\Session; use Friendica\Core\Worker; use Friendica\Database\DBA; use Friendica\Network\Probe; @@ -270,14 +271,17 @@ class Contact extends BaseObject * @param string $url The contact link * * @return string basepath + * @return boolean $dont_update Don't update the contact * @throws \Friendica\Network\HTTPException\InternalServerErrorException * @throws \ImagickException */ - public static function getBasepath($url) + public static function getBasepath($url, $dont_update = false) { $contact = DBA::selectFirst('contact', ['baseurl'], ['uid' => 0, 'nurl' => Strings::normaliseLink($url)]); if (!empty($contact['baseurl'])) { return $contact['baseurl']; + } elseif ($dont_update) { + return ''; } self::updateFromProbeByURL($url, true); @@ -290,6 +294,18 @@ class Contact extends BaseObject return ''; } + /** + * Check if the given contact url is on the same server + * + * @param string $url The contact link + * + * @return boolean Is it the same server? + */ + public static function isLocal($url) + { + return Strings::compareLink(self::getBasepath($url, true), System::baseUrl()); + } + /** * Returns the public contact id of the given user id * @@ -668,21 +684,21 @@ class Contact extends BaseObject public static function updateSelfFromUserID($uid, $update_avatar = false) { $fields = ['id', 'name', 'nick', 'location', 'about', 'keywords', 'gender', 'avatar', - 'xmpp', 'contact-type', 'forum', 'prv', 'avatar-date', 'url', 'nurl', + 'xmpp', 'contact-type', 'forum', 'prv', 'avatar-date', 'url', 'nurl', 'unsearchable', 'photo', 'thumb', 'micro', 'addr', 'request', 'notify', 'poll', 'confirm', 'poco']; $self = DBA::selectFirst('contact', $fields, ['uid' => $uid, 'self' => true]); if (!DBA::isResult($self)) { return; } - $fields = ['nickname', 'page-flags', 'account-type']; + $fields = ['nickname', 'page-flags', 'account-type', 'hidewall']; $user = DBA::selectFirst('user', $fields, ['uid' => $uid]); if (!DBA::isResult($user)) { return; } $fields = ['name', 'photo', 'thumb', 'about', 'address', 'locality', 'region', - 'country-name', 'gender', 'pub_keywords', 'xmpp']; + 'country-name', 'gender', 'pub_keywords', 'xmpp', 'net-publish']; $profile = DBA::selectFirst('profile', $fields, ['uid' => $uid, 'is-default' => true]); if (!DBA::isResult($profile)) { return; @@ -727,6 +743,7 @@ class Contact extends BaseObject $fields['avatar'] = System::baseUrl() . '/photo/profile/' .$uid . '.' . $file_suffix; $fields['forum'] = $user['page-flags'] == User::PAGE_FLAGS_COMMUNITY; $fields['prv'] = $user['page-flags'] == User::PAGE_FLAGS_PRVGROUP; + $fields['unsearchable'] = $user['hidewall'] || !$profile['net-publish']; // it seems as if ported accounts can have wrong values, so we make sure that now everything is fine. $fields['url'] = System::baseUrl() . '/profile/' . $user['nickname']; @@ -1061,14 +1078,14 @@ class Contact extends BaseObject $profile["micro"] = $profile["thumb"]; } - if ((empty($profile["addr"]) || empty($profile["name"])) && (defaults($profile, "gid", 0) != 0) + if ((empty($profile["addr"]) || empty($profile["name"])) && !empty($profile["gid"]) && in_array($profile["network"], Protocol::FEDERATED) ) { Worker::add(PRIORITY_LOW, "UpdateGContact", $url); } // Show contact details of Diaspora contacts only if connected - if ((defaults($profile, "cid", 0) == 0) && (defaults($profile, "network", "") == Protocol::DIASPORA)) { + if (empty($profile["cid"]) && ($profile["network"] ?? "") == Protocol::DIASPORA) { $profile["location"] = ""; $profile["about"] = ""; $profile["gender"] = ""; @@ -1175,9 +1192,9 @@ class Contact extends BaseObject } $sparkle = false; - if (($contact['network'] === Protocol::DFRN) && !$contact['self']) { + if (($contact['network'] === Protocol::DFRN) && !$contact['self'] && empty($contact['pending'])) { $sparkle = true; - $profile_link = System::baseUrl() . '/redir/' . $contact['id'] . '?url=' . $contact['url']; + $profile_link = System::baseUrl() . '/redir/' . $contact['id']; } else { $profile_link = $contact['url']; } @@ -1192,12 +1209,12 @@ class Contact extends BaseObject $profile_link = $profile_link . '?tab=profile'; } - if (self::canReceivePrivateMessages($contact)) { + if (self::canReceivePrivateMessages($contact) && empty($contact['pending'])) { $pm_url = System::baseUrl() . '/message/new/' . $contact['id']; } - if (($contact['network'] == Protocol::DFRN) && !$contact['self']) { - $poke_link = System::baseUrl() . '/poke/?f=&c=' . $contact['id']; + if (($contact['network'] == Protocol::DFRN) && !$contact['self'] && empty($contact['pending'])) { + $poke_link = System::baseUrl() . '/poke/?c=' . $contact['id']; } $contact_url = System::baseUrl() . '/contact/' . $contact['id']; @@ -1231,6 +1248,13 @@ class Contact extends BaseObject 'pm' => [L10n::t('Send PM'), $pm_url, false], 'poke' => [L10n::t('Poke'), $poke_link, false], ]; + + if (!empty($contact['pending'])) { + $intro = DBA::selectFirst('intro', ['id'], ['contact-id' => $contact['id']]); + if (DBA::isResult($intro)) { + $menu['follow'] = [L10n::t('Approve'), 'notifications/intros/' . $intro['id'], true]; + } + } } $args = ['contact' => $contact, 'menu' => &$menu]; @@ -1455,10 +1479,9 @@ class Contact extends BaseObject if (empty($data)) { $data = Probe::uri($url, "", $uid); - // Ensure that there is a gserver entry if (!empty($data['baseurl']) && ($data['network'] != Protocol::PHANTOM)) { - PortableContact::checkServer($data['baseurl']); + GServer::check($data['baseurl']); } } @@ -1481,25 +1504,25 @@ class Contact extends BaseObject 'created' => DateTimeFormat::utcNow(), 'url' => $data['url'], 'nurl' => Strings::normaliseLink($data['url']), - 'addr' => defaults($data, 'addr', ''), - 'alias' => defaults($data, 'alias', ''), - 'notify' => defaults($data, 'notify', ''), - 'poll' => defaults($data, 'poll', ''), - 'name' => defaults($data, 'name', ''), - 'nick' => defaults($data, 'nick', ''), - 'photo' => defaults($data, 'photo', ''), - 'keywords' => defaults($data, 'keywords', ''), - 'location' => defaults($data, 'location', ''), - 'about' => defaults($data, 'about', ''), + 'addr' => $data['addr'] ?? '', + 'alias' => $data['alias'] ?? '', + 'notify' => $data['notify'] ?? '', + 'poll' => $data['poll'] ?? '', + 'name' => $data['name'] ?? '', + 'nick' => $data['nick'] ?? '', + 'photo' => $data['photo'] ?? '', + 'keywords' => $data['keywords'] ?? '', + 'location' => $data['location'] ?? '', + 'about' => $data['about'] ?? '', 'network' => $data['network'], - 'pubkey' => defaults($data, 'pubkey', ''), + 'pubkey' => $data['pubkey'] ?? '', 'rel' => self::SHARING, - 'priority' => defaults($data, 'priority', 0), - 'batch' => defaults($data, 'batch', ''), - 'request' => defaults($data, 'request', ''), - 'confirm' => defaults($data, 'confirm', ''), - 'poco' => defaults($data, 'poco', ''), - 'baseurl' => defaults($data, 'baseurl', ''), + 'priority' => $data['priority'] ?? 0, + 'batch' => $data['batch'] ?? '', + 'request' => $data['request'] ?? '', + 'confirm' => $data['confirm'] ?? '', + 'poco' => $data['poco'] ?? '', + 'baseurl' => $data['baseurl'] ?? '', 'name-date' => DateTimeFormat::utcNow(), 'uri-date' => DateTimeFormat::utcNow(), 'avatar-date' => DateTimeFormat::utcNow(), @@ -1566,7 +1589,7 @@ class Contact extends BaseObject $fields = ['addr', 'alias', 'name', 'nick', 'keywords', 'location', 'about', 'baseurl']; foreach ($fields as $field) { - $updated[$field] = defaults($data, $field, $contact[$field]); + $updated[$field] = ($data[$field] ?? '') ?: $contact[$field]; } if (($updated['addr'] != $contact['addr']) || (!empty($data['alias']) && ($data['alias'] != $contact['alias']))) { @@ -2014,9 +2037,8 @@ class Contact extends BaseObject return true; } - // If Probe::uri fails the network code will be different (mostly "feed" or "unkn") - if (!in_array($ret['network'], Protocol::NATIVE_SUPPORT) || - (in_array($ret['network'], [Protocol::FEED, Protocol::PHANTOM]) && ($ret['network'] != $contact['network']))) { + // If Probe::uri fails the network code will be different ("feed" or "unkn") + if (in_array($ret['network'], [Protocol::FEED, Protocol::PHANTOM]) && ($ret['network'] != $contact['network'])) { if ($force && ($uid == 0)) { self::updateContact($id, $uid, $ret['url'], ['last-update' => $updated, 'failure_update' => $updated]); } @@ -2056,7 +2078,7 @@ class Contact extends BaseObject } } - if ($ret['network'] != Protocol::FEED) { + if (!empty($ret['photo']) && ($ret['network'] != Protocol::FEED)) { self::updateAvatar($ret['photo'], $uid, $id, $update || $force); } @@ -2447,9 +2469,9 @@ class Contact extends BaseObject return false; } - $url = defaults($datarray, 'author-link', $pub_contact['url']); + $url = ($datarray['author-link'] ?? '') ?: $pub_contact['url']; $name = $pub_contact['name']; - $photo = defaults($pub_contact, 'avatar', $pub_contact["photo"]); + $photo = ($pub_contact['avatar'] ?? '') ?: $pub_contact["photo"]; $nick = $pub_contact['nick']; $network = $pub_contact['network']; @@ -2480,6 +2502,9 @@ class Contact extends BaseObject ['id' => $contact['id'], 'uid' => $importer['uid']]); } + // Ensure to always have the correct network type, independent from the connection request method + self::updateFromProbe($contact['id'], '', true); + return true; } else { // send email notification to owner? @@ -2505,15 +2530,14 @@ class Contact extends BaseObject 'writable' => 1, ]); - $contact_record = [ - 'id' => DBA::lastInsertId(), - 'network' => $network, - 'name' => $name, - 'url' => $url, - 'photo' => $photo - ]; + $contact_id = DBA::lastInsertId(); - Contact::updateAvatar($photo, $importer["uid"], $contact_record["id"], true); + // Ensure to always have the correct network type, independent from the connection request method + self::updateFromProbe($contact_id, '', true); + + Contact::updateAvatar($photo, $importer["uid"], $contact_id, true); + + $contact_record = DBA::selectFirst('contact', ['id', 'network', 'name', 'url', 'photo'], ['id' => $contact_id]); /// @TODO Encapsulate this into a function/method $fields = ['uid', 'username', 'email', 'page-flags', 'notify-flags', 'language']; @@ -2656,7 +2680,7 @@ class Contact extends BaseObject */ public static function magicLink($contact_url, $url = '') { - if (!local_user() && !remote_user()) { + if (!Session::isAuthenticated()) { return $url ?: $contact_url; // Equivalent to: ($url != '') ? $url : $contact_url; } @@ -2668,7 +2692,7 @@ class Contact extends BaseObject // Prevents endless loop in case only a non-public contact exists for the contact URL unset($data['uid']); - return self::magicLinkByContact($data, $contact_url); + return self::magicLinkByContact($data, $url ?: $contact_url); } /** @@ -2700,8 +2724,10 @@ class Contact extends BaseObject */ public static function magicLinkByContact($contact, $url = '') { - if ((!local_user() && !remote_user()) || ($contact['network'] != Protocol::DFRN)) { - return $url ?: $contact['url']; // Equivalent to ($url != '') ? $url : $contact['url']; + $destination = $url ?: $contact['url']; // Equivalent to ($url != '') ? $url : $contact['url']; + + if (!Session::isAuthenticated() || ($contact['network'] != Protocol::DFRN)) { + return $destination; } // Only redirections to the same host do make sense @@ -2714,12 +2740,12 @@ class Contact extends BaseObject } if (empty($contact['id'])) { - return $url ?: $contact['url']; + return $destination; } $redirect = 'redir/' . $contact['id']; - if ($url != '') { + if (($url != '') && !Strings::compareLink($contact['url'], $url)) { $redirect .= '?url=' . $url; } diff --git a/src/Model/Conversation.php b/src/Model/Conversation.php index 06d3ba536..2ef58636a 100644 --- a/src/Model/Conversation.php +++ b/src/Model/Conversation.php @@ -39,7 +39,7 @@ class Conversation */ public static function insert(array $arr) { - if (in_array(defaults($arr, 'network', Protocol::PHANTOM), + if (in_array(($arr['network'] ?? '') ?: Protocol::PHANTOM, [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::DIASPORA, Protocol::OSTATUS, Protocol::TWITTER]) && !empty($arr['uri'])) { $conversation = ['item-uri' => $arr['uri'], 'received' => DateTimeFormat::utcNow()]; @@ -76,8 +76,13 @@ class Conversation unset($old_conv['source']); } // Update structure data all the time but the source only when its from a better protocol. - if (empty($conversation['source']) || (!empty($old_conv['source']) && - ($old_conv['protocol'] < defaults($conversation, 'protocol', self::PARCEL_UNKNOWN)))) { + if ( + empty($conversation['source']) + || ( + !empty($old_conv['source']) + && ($old_conv['protocol'] < (($conversation['protocol'] ?? '') ?: self::PARCEL_UNKNOWN)) + ) + ) { unset($conversation['protocol']); unset($conversation['source']); } diff --git a/src/Model/Event.php b/src/Model/Event.php index 42742f18e..915218084 100644 --- a/src/Model/Event.php +++ b/src/Model/Event.php @@ -242,30 +242,30 @@ class Event extends BaseObject public static function store($arr) { $event = []; - $event['id'] = intval(defaults($arr, 'id' , 0)); - $event['uid'] = intval(defaults($arr, 'uid' , 0)); - $event['cid'] = intval(defaults($arr, 'cid' , 0)); - $event['guid'] = defaults($arr, 'guid' , System::createUUID()); - $event['uri'] = defaults($arr, 'uri' , Item::newURI($event['uid'], $event['guid'])); - $event['type'] = defaults($arr, 'type' , 'event'); - $event['summary'] = defaults($arr, 'summary' , ''); - $event['desc'] = defaults($arr, 'desc' , ''); - $event['location'] = defaults($arr, 'location' , ''); - $event['allow_cid'] = defaults($arr, 'allow_cid', ''); - $event['allow_gid'] = defaults($arr, 'allow_gid', ''); - $event['deny_cid'] = defaults($arr, 'deny_cid' , ''); - $event['deny_gid'] = defaults($arr, 'deny_gid' , ''); - $event['adjust'] = intval(defaults($arr, 'adjust' , 0)); - $event['nofinish'] = intval(defaults($arr, 'nofinish' , !empty($event['start']) && empty($event['finish']))); + $event['id'] = intval($arr['id'] ?? 0); + $event['uid'] = intval($arr['uid'] ?? 0); + $event['cid'] = intval($arr['cid'] ?? 0); + $event['guid'] = ($arr['guid'] ?? '') ?: System::createUUID(); + $event['uri'] = ($arr['uri'] ?? '') ?: Item::newURI($event['uid'], $event['guid']); + $event['type'] = ($arr['type'] ?? '') ?: 'event'; + $event['summary'] = $arr['summary'] ?? ''; + $event['desc'] = $arr['desc'] ?? ''; + $event['location'] = $arr['location'] ?? ''; + $event['allow_cid'] = $arr['allow_cid'] ?? ''; + $event['allow_gid'] = $arr['allow_gid'] ?? ''; + $event['deny_cid'] = $arr['deny_cid'] ?? ''; + $event['deny_gid'] = $arr['deny_gid'] ?? ''; + $event['adjust'] = intval($arr['adjust'] ?? 0); + $event['nofinish'] = intval(!empty($arr['nofinish'] || !empty($event['start']) && empty($event['finish']))); - $event['created'] = DateTimeFormat::utc(defaults($arr, 'created' , 'now')); - $event['edited'] = DateTimeFormat::utc(defaults($arr, 'edited' , 'now')); - $event['start'] = DateTimeFormat::utc(defaults($arr, 'start' , DBA::NULL_DATETIME)); - $event['finish'] = DateTimeFormat::utc(defaults($arr, 'finish' , DBA::NULL_DATETIME)); + $event['created'] = DateTimeFormat::utc(($arr['created'] ?? '') ?: 'now'); + $event['edited'] = DateTimeFormat::utc(($arr['edited'] ?? '') ?: 'now'); + $event['start'] = DateTimeFormat::utc(($arr['start'] ?? '') ?: DBA::NULL_DATETIME); + $event['finish'] = DateTimeFormat::utc(($arr['finish'] ?? '') ?: DBA::NULL_DATETIME); if ($event['finish'] < DBA::NULL_DATETIME) { $event['finish'] = DBA::NULL_DATETIME; } - $private = intval(defaults($arr, 'private', 0)); + $private = intval($arr['private'] ?? 0); $conditions = ['uid' => $event['uid']]; if ($event['cid']) { @@ -333,7 +333,7 @@ class Event extends BaseObject $item_arr['uri'] = $event['uri']; $item_arr['parent-uri'] = $event['uri']; $item_arr['guid'] = $event['guid']; - $item_arr['plink'] = defaults($arr, 'plink', ''); + $item_arr['plink'] = $arr['plink'] ?? ''; $item_arr['post-type'] = Item::PT_EVENT; $item_arr['wall'] = $event['cid'] ? 0 : 1; $item_arr['contact-id'] = $contact['id']; diff --git a/src/Model/GContact.php b/src/Model/GContact.php index 6b0ebab7a..3caabdd19 100644 --- a/src/Model/GContact.php +++ b/src/Model/GContact.php @@ -6,6 +6,8 @@ */ namespace Friendica\Model; +use DOMDocument; +use DOMXPath; use Exception; use Friendica\Core\Config; use Friendica\Core\Logger; @@ -14,6 +16,7 @@ use Friendica\Core\System; use Friendica\Core\Worker; use Friendica\Database\DBA; use Friendica\Network\Probe; +use Friendica\Protocol\ActivityPub; use Friendica\Protocol\PortableContact; use Friendica\Util\DateTimeFormat; use Friendica\Util\Network; @@ -190,7 +193,7 @@ class GContact } if ((!isset($gcontact['network']) || !isset($gcontact['name']) || !isset($gcontact['addr']) || !isset($gcontact['photo']) || !isset($gcontact['server_url']) || $alternate) - && PortableContact::reachable($gcontact['url'], $gcontact['server_url'], $gcontact['network'], false) + && GServer::reachable($gcontact['url'], $gcontact['server_url'], $gcontact['network'], false) ) { $data = Probe::uri($gcontact['url']); @@ -222,7 +225,7 @@ class GContact if (!isset($gcontact['server_url'])) { // We check the server url to be sure that it is a real one - $server_url = PortableContact::detectServer($gcontact['url']); + $server_url = Contact::getBasepath($gcontact['url']); // We are now sure that it is a correct URL. So we use it in the future if ($server_url != "") { @@ -231,7 +234,7 @@ class GContact } // The server URL doesn't seem to be valid, so we don't store it. - if (!PortableContact::checkServer($gcontact['server_url'], $gcontact['network'])) { + if (!GServer::check($gcontact['server_url'], $gcontact['network'])) { $gcontact['server_url'] = ""; } @@ -252,7 +255,7 @@ class GContact WHERE `glink`.`cid` = %d AND `glink`.`uid` = %d AND ((`gcontact`.`last_contact` >= `gcontact`.`last_failure`) OR (`gcontact`.`updated` >= `gcontact`.`last_failure`)) - AND `gcontact`.`nurl` IN (select nurl from contact where uid = %d and self = 0 and blocked = 0 and hidden = 0 and id != %d ) ", + AND `gcontact`.`nurl` IN (select nurl from contact where uid = %d and self = 0 and blocked = 0 and hidden = 0 and id != %d) ", intval($cid), intval($uid), intval($uid), @@ -278,7 +281,7 @@ class GContact "SELECT count(*) as `total` FROM `glink` INNER JOIN `gcontact` on `glink`.`gcid` = `gcontact`.`id` where `glink`.`zcid` = %d - and `gcontact`.`nurl` in (select nurl from contact where uid = %d and self = 0 and blocked = 0 and hidden = 0 ) ", + and `gcontact`.`nurl` in (select nurl from contact where uid = %d and self = 0 and blocked = 0 and hidden = 0) ", intval($zcid), intval($uid) ); @@ -350,7 +353,7 @@ class GContact "SELECT `gcontact`.* FROM `glink` INNER JOIN `gcontact` on `glink`.`gcid` = `gcontact`.`id` where `glink`.`zcid` = %d - and `gcontact`.`nurl` in (select nurl from contact where uid = %d and self = 0 and blocked = 0 and hidden = 0 ) + and `gcontact`.`nurl` in (select nurl from contact where uid = %d and self = 0 and blocked = 0 and hidden = 0) $sql_extra limit %d, %d", intval($zcid), intval($uid), @@ -541,7 +544,7 @@ class GContact $j = json_decode($x); if (!empty($j->entries)) { foreach ($j->entries as $entry) { - PortableContact::checkServer($entry->url); + GServer::check($entry->url); $url = $entry->url . '/poco'; if (!in_array($url, $done)) { @@ -683,9 +686,9 @@ class GContact $doprobing = (((time() - $last_contact) > (90 * 86400)) && ((time() - $last_failure) > (90 * 86400))); } } else { - $contact['location'] = defaults($contact, 'location', ''); - $contact['about'] = defaults($contact, 'about', ''); - $contact['generation'] = defaults($contact, 'generation', 0); + $contact['location'] = $contact['location'] ?? ''; + $contact['about'] = $contact['about'] ?? ''; + $contact['generation'] = $contact['generation'] ?? 0; q( "INSERT INTO `gcontact` (`name`, `nick`, `addr` , `network`, `url`, `nurl`, `photo`, `created`, `updated`, `location`, `about`, `hide`, `generation`) @@ -860,6 +863,170 @@ class GContact return $gcontact_id; } + /** + * Set the last date that the contact had posted something + * + * @param string $data Probing result + * @param bool $force force updating + */ + public static function setLastUpdate(array $data, bool $force = false) + { + // Fetch the global contact + $gcontact = DBA::selectFirst('gcontact', ['created', 'updated', 'last_contact', 'last_failure'], + ['nurl' => Strings::normaliseLink($data['url'])]); + if (!DBA::isResult($gcontact)) { + return; + } + + if (!$force && !PortableContact::updateNeeded($gcontact['created'], $gcontact['updated'], $gcontact['last_failure'], $gcontact['last_contact'])) { + Logger::info("Don't update profile", ['url' => $data['url'], 'updated' => $gcontact['updated']]); + return; + } + + if (self::updateFromNoScrape($data)) { + return; + } + + // When the profile doesn't have got a feed, then we exit here + if (empty($data['poll'])) { + return; + } + + if ($data['network'] == Protocol::ACTIVITYPUB) { + self::updateFromOutbox($data['poll'], $data); + } else { + self::updateFromFeed($data); + } + } + + /** + * Update a global contact via the "noscrape" endpoint + * + * @param string $data Probing result + * + * @return bool 'true' if update was successful or the server was unreachable + */ + private static function updateFromNoScrape(array $data) + { + // Check the 'noscrape' endpoint when it is a Friendica server + $gserver = DBA::selectFirst('gserver', ['noscrape'], ["`nurl` = ? AND `noscrape` != ''", + Strings::normaliseLink($data['baseurl'])]); + if (!DBA::isResult($gserver)) { + return false; + } + + $curlResult = Network::curl($gserver['noscrape'] . '/' . $data['nick']); + + if ($curlResult->isSuccess() && !empty($curlResult->getBody())) { + $noscrape = json_decode($curlResult->getBody(), true); + if (!empty($noscrape)) { + $noscrape['updated'] = DateTimeFormat::utc($noscrape['updated'], DateTimeFormat::MYSQL); + $fields = ['last_contact' => DateTimeFormat::utcNow(), 'updated' => $noscrape['updated']]; + DBA::update('gcontact', $fields, ['nurl' => Strings::normaliseLink($data['url'])]); + return true; + } + } elseif ($curlResult->isTimeout()) { + // On a timeout return the existing value, but mark the contact as failure + $fields = ['last_failure' => DateTimeFormat::utcNow()]; + DBA::update('gcontact', $fields, ['nurl' => Strings::normaliseLink($data['url'])]); + return true; + } + return false; + } + + /** + * Update a global contact via an ActivityPub Outbox + * + * @param string $data Probing result + */ + private static function updateFromOutbox(string $feed, array $data) + { + $outbox = ActivityPub::fetchContent($feed); + if (empty($outbox)) { + return; + } + + if (!empty($outbox['orderedItems'])) { + $items = $outbox['orderedItems']; + } elseif (!empty($outbox['first']['orderedItems'])) { + $items = $outbox['first']['orderedItems']; + } elseif (!empty($outbox['first'])) { + self::updateFromOutbox($outbox['first'], $data); + return; + } else { + $items = []; + } + + $last_updated = ''; + + foreach ($items as $activity) { + if ($last_updated < $activity['published']) { + $last_updated = $activity['published']; + } + } + + if (empty($last_updated)) { + return; + } + + $fields = ['last_contact' => DateTimeFormat::utcNow(), 'updated' => $last_updated]; + DBA::update('gcontact', $fields, ['nurl' => Strings::normaliseLink($data['url'])]); + } + + /** + * Update a global contact via an XML feed + * + * @param string $data Probing result + */ + private static function updateFromFeed(array $data) + { + // Search for the newest entry in the feed + $curlResult = Network::curl($data['poll']); + if (!$curlResult->isSuccess()) { + $fields = ['last_failure' => DateTimeFormat::utcNow()]; + DBA::update('gcontact', $fields, ['nurl' => Strings::normaliseLink($profile)]); + + Logger::info("Profile wasn't reachable (no feed)", ['url' => $data['url']]); + return; + } + + $doc = new DOMDocument(); + @$doc->loadXML($curlResult->getBody()); + + $xpath = new DOMXPath($doc); + $xpath->registerNamespace('atom', 'http://www.w3.org/2005/Atom'); + + $entries = $xpath->query('/atom:feed/atom:entry'); + + $last_updated = ''; + + foreach ($entries as $entry) { + $published_item = $xpath->query('atom:published/text()', $entry)->item(0); + $updated_item = $xpath->query('atom:updated/text()' , $entry)->item(0); + $published = !empty($published_item->nodeValue) ? DateTimeFormat::utc($published_item->nodeValue) : null; + $updated = !empty($updated_item->nodeValue) ? DateTimeFormat::utc($updated_item->nodeValue) : null; + + if (empty($published) || empty($updated)) { + Logger::notice('Invalid entry for XPath.', ['entry' => $entry, 'url' => $data['url']]); + continue; + } + + if ($last_updated < $published) { + $last_updated = $published; + } + + if ($last_updated < $updated) { + $last_updated = $updated; + } + } + + if (empty($last_updated)) { + return; + } + + $fields = ['last_contact' => DateTimeFormat::utcNow(), 'updated' => $last_updated]; + DBA::update('gcontact', $fields, ['nurl' => Strings::normaliseLink($data['url'])]); + } /** * @brief Updates the gcontact entry from a given public contact id * @@ -976,7 +1143,9 @@ class GContact * * @param string $url profile link * @param boolean $force Optional forcing of network probing (otherwise we use the cached data) - * @return void + * + * @return boolean 'true' when contact had been updated + * * @throws \Friendica\Network\HTTPException\InternalServerErrorException * @throws \ImagickException */ @@ -985,13 +1154,20 @@ class GContact $data = Probe::uri($url, $force); if (in_array($data["network"], [Protocol::PHANTOM])) { - Logger::log("Invalid network for contact url ".$data["url"]." - Called by: ".System::callstack(), Logger::DEBUG); - return; + $fields = ['last_failure' => DateTimeFormat::utcNow()]; + DBA::update('gcontact', $fields, ['nurl' => Strings::normaliseLink($url)]); + Logger::info('Invalid network for contact', ['url' => $data['url'], 'callstack' => System::callstack()]); + return false; } $data["server_url"] = $data["baseurl"]; self::update($data); + + // Set the date of the latest post + self::setLastUpdate($data, $force); + + return true; } /** diff --git a/src/Model/GServer.php b/src/Model/GServer.php new file mode 100644 index 000000000..d759a24df --- /dev/null +++ b/src/Model/GServer.php @@ -0,0 +1,1187 @@ + Strings::normaliseLink($server_url)]); + if (DBA::isResult($gserver)) { + if ($gserver['created'] <= DBA::NULL_DATETIME) { + $fields = ['created' => DateTimeFormat::utcNow()]; + $condition = ['nurl' => Strings::normaliseLink($server_url)]; + DBA::update('gserver', $fields, $condition); + } + + $last_contact = $gserver['last_contact']; + $last_failure = $gserver['last_failure']; + + // See discussion under https://forum.friendi.ca/display/0b6b25a8135aabc37a5a0f5684081633 + // It can happen that a zero date is in the database, but storing it again is forbidden. + if ($last_contact < DBA::NULL_DATETIME) { + $last_contact = DBA::NULL_DATETIME; + } + + if ($last_failure < DBA::NULL_DATETIME) { + $last_failure = DBA::NULL_DATETIME; + } + + if (!$force && !PortableContact::updateNeeded($gserver['created'], '', $last_failure, $last_contact)) { + Logger::info('No update needed', ['server' => $server_url]); + return ($last_contact >= $last_failure); + } + Logger::info('Server is outdated. Start discovery.', ['Server' => $server_url, 'Force' => $force, 'Created' => $gserver['created'], 'Failure' => $last_failure, 'Contact' => $last_contact]); + } else { + Logger::info('Server is unknown. Start discovery.', ['Server' => $server_url]); + } + + return self::detect($server_url, $network); + } + + /** + * Detect server data (type, protocol, version number, ...) + * The detected data is then updated or inserted in the gserver table. + * + * @param string $url URL of the given server + * @param string $network Network value that is used, when detection failed + * + * @return boolean 'true' if server could be detected + */ + public static function detect(string $url, string $network = '') + { + $serverdata = []; + + // When a nodeinfo is present, we don't need to dig further + $xrd_timeout = Config::get('system', 'xrd_timeout'); + $curlResult = Network::curl($url . '/.well-known/nodeinfo', false, ['timeout' => $xrd_timeout]); + if ($curlResult->isTimeout()) { + DBA::update('gserver', ['last_failure' => DateTimeFormat::utcNow()], ['nurl' => Strings::normaliseLink($url)]); + return false; + } + + $nodeinfo = self::fetchNodeinfo($url, $curlResult); + + // When nodeinfo isn't present, we use the older 'statistics.json' endpoint + if (empty($nodeinfo)) { + $nodeinfo = self::fetchStatistics($url); + } + + // If that didn't work out well, we use some protocol specific endpoints + // For Friendica and Zot based networks we have to dive deeper to reveal more details + if (empty($nodeinfo['network']) || in_array($nodeinfo['network'], [Protocol::DFRN, Protocol::ZOT])) { + // Fetch the landing page, possibly it reveals some data + if (empty($nodeinfo['network'])) { + $curlResult = Network::curl($url, false, ['timeout' => $xrd_timeout]); + if ($curlResult->isSuccess()) { + $serverdata = self::analyseRootHeader($curlResult, $serverdata); + $serverdata = self::analyseRootBody($curlResult, $serverdata, $url); + } + + if (!$curlResult->isSuccess() || empty($curlResult->getBody())) { + DBA::update('gserver', ['last_failure' => DateTimeFormat::utcNow()], ['nurl' => Strings::normaliseLink($url)]); + return false; + } + } + + if (empty($serverdata['network']) || ($serverdata['network'] == Protocol::ACTIVITYPUB)) { + $serverdata = self::detectMastodonAlikes($url, $serverdata); + } + + // 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 ressources for dead systems. + // Also this hopefully prevents us from receiving abuse messages. + if (empty($serverdata['network']) && !self::validHostMeta($url)) { + DBA::update('gserver', ['last_failure' => DateTimeFormat::utcNow()], ['nurl' => Strings::normaliseLink($url)]); + return false; + } + + if (empty($serverdata['network']) || in_array($serverdata['network'], [Protocol::DFRN, Protocol::ACTIVITYPUB])) { + $serverdata = self::detectFriendica($url, $serverdata); + } + + // the 'siteinfo.json' is some specific endpoint of Hubzilla and Red + if (empty($serverdata['network']) || ($serverdata['network'] == Protocol::ZOT)) { + $serverdata = self::fetchSiteinfo($url, $serverdata); + } + + // The 'siteinfo.json' doesn't seem to be present on older Hubzilla installations + if (empty($serverdata['network'])) { + $serverdata = self::detectHubzilla($url, $serverdata); + } + + if (empty($serverdata['network'])) { + $serverdata = self::detectNextcloud($url, $serverdata); + } + + if (empty($serverdata['network'])) { + $serverdata = self::detectGNUSocial($url, $serverdata); + } + } else { + $serverdata = $nodeinfo; + } + + $serverdata = self::checkPoCo($url, $serverdata); + + // We can't detect the network type. Possibly it is some system that we don't know yet + if (empty($serverdata['network'])) { + $serverdata['network'] = Protocol::PHANTOM; + } + + // When we hadn't been able to detect the network type, we use the hint from the parameter + if (($serverdata['network'] == Protocol::PHANTOM) && !empty($network)) { + $serverdata['network'] = $network; + } + + $serverdata['url'] = $url; + $serverdata['nurl'] = Strings::normaliseLink($url); + + // We take the highest number that we do find + $registeredUsers = $serverdata['registered-users'] ?? 0; + + // On an active server there has to be at least a single user + if (($serverdata['network'] != Protocol::PHANTOM) && ($registeredUsers == 0)) { + $registeredUsers = 1; + } + + if ($serverdata['network'] != Protocol::PHANTOM) { + $gcontacts = DBA::count('gcontact', ['server_url' => [$url, $serverdata['nurl']]]); + $apcontacts = DBA::count('apcontact', ['baseurl' => [$url, $serverdata['nurl']]]); + $contacts = DBA::count('contact', ['uid' => 0, 'baseurl' => [$url, $serverdata['nurl']]]); + $serverdata['registered-users'] = max($gcontacts, $apcontacts, $contacts, $registeredUsers); + } else { + $serverdata['registered-users'] = $registeredUsers; + $serverdata = self::detectNetworkViaContacts($url, $serverdata); + } + + $serverdata['last_contact'] = DateTimeFormat::utcNow(); + + $gserver = DBA::selectFirst('gserver', ['network'], ['nurl' => Strings::normaliseLink($url)]); + if (!DBA::isResult($gserver)) { + $serverdata['created'] = DateTimeFormat::utcNow(); + $ret = DBA::insert('gserver', $serverdata); + } else { + // Don't override the network with 'unknown' when there had been a valid entry before + if (($serverdata['network'] == Protocol::PHANTOM) && !empty($gserver['network'])) { + unset($serverdata['network']); + } + + $ret = DBA::update('gserver', $serverdata, ['nurl' => $serverdata['nurl']]); + } + + if (!empty($serverdata['network']) && in_array($serverdata['network'], [Protocol::DFRN, Protocol::DIASPORA])) { + self::discoverRelay($url); + } + + return $ret; + } + + /** + * Fetch relay data from a given server url + * + * @param string $server_url address of the server + * @throws \Friendica\Network\HTTPException\InternalServerErrorException + */ + private static function discoverRelay(string $server_url) + { + Logger::info('Discover relay data', ['server' => $server_url]); + + $curlResult = Network::curl($server_url . '/.well-known/x-social-relay'); + if (!$curlResult->isSuccess()) { + return; + } + + $data = json_decode($curlResult->getBody(), true); + if (!is_array($data)) { + return; + } + + $gserver = DBA::selectFirst('gserver', ['id', 'relay-subscribe', 'relay-scope'], ['nurl' => Strings::normaliseLink($server_url)]); + if (!DBA::isResult($gserver)) { + return; + } + + if (($gserver['relay-subscribe'] != $data['subscribe']) || ($gserver['relay-scope'] != $data['scope'])) { + $fields = ['relay-subscribe' => $data['subscribe'], 'relay-scope' => $data['scope']]; + DBA::update('gserver', $fields, ['id' => $gserver['id']]); + } + + DBA::delete('gserver-tag', ['gserver-id' => $gserver['id']]); + + if ($data['scope'] == 'tags') { + // Avoid duplicates + $tags = []; + foreach ($data['tags'] as $tag) { + $tag = mb_strtolower($tag); + if (strlen($tag) < 100) { + $tags[$tag] = $tag; + } + } + + foreach ($tags as $tag) { + DBA::insert('gserver-tag', ['gserver-id' => $gserver['id'], 'tag' => $tag], true); + } + } + + // Create or update the relay contact + $fields = []; + if (isset($data['protocols'])) { + if (isset($data['protocols']['diaspora'])) { + $fields['network'] = Protocol::DIASPORA; + + if (isset($data['protocols']['diaspora']['receive'])) { + $fields['batch'] = $data['protocols']['diaspora']['receive']; + } elseif (is_string($data['protocols']['diaspora'])) { + $fields['batch'] = $data['protocols']['diaspora']; + } + } + + if (isset($data['protocols']['dfrn'])) { + $fields['network'] = Protocol::DFRN; + + if (isset($data['protocols']['dfrn']['receive'])) { + $fields['batch'] = $data['protocols']['dfrn']['receive']; + } elseif (is_string($data['protocols']['dfrn'])) { + $fields['batch'] = $data['protocols']['dfrn']; + } + } + } + Diaspora::setRelayContact($server_url, $fields); + } + + /** + * Fetch server data from '/statistics.json' on the given server + * + * @param string $url URL of the given server + * + * @return array server data + */ + private static function fetchStatistics(string $url) + { + $curlResult = Network::curl($url . '/statistics.json'); + if (!$curlResult->isSuccess()) { + return []; + } + + $data = json_decode($curlResult->getBody(), true); + if (empty($data)) { + return []; + } + + $serverdata = []; + + if (!empty($data['version'])) { + $serverdata['version'] = $data['version']; + // Version numbers on statistics.json are presented with additional info, e.g.: + // 0.6.3.0-p1702cc1c, 0.6.99.0-p1b9ab160 or 3.4.3-2-1191. + $serverdata['version'] = preg_replace('=(.+)-(.{4,})=ism', '$1', $serverdata['version']); + } + + if (!empty($data['name'])) { + $serverdata['site_name'] = $data['name']; + } + + if (!empty($data['network'])) { + $serverdata['platform'] = $data['network']; + + if ($serverdata['platform'] == 'Diaspora') { + $serverdata['network'] = Protocol::DIASPORA; + } elseif ($serverdata['platform'] == 'Friendica') { + $serverdata['network'] = Protocol::DFRN; + } elseif ($serverdata['platform'] == 'hubzilla') { + $serverdata['network'] = Protocol::ZOT; + } elseif ($serverdata['platform'] == 'redmatrix') { + $serverdata['network'] = Protocol::ZOT; + } + } + + + if (!empty($data['registrations_open'])) { + $serverdata['register_policy'] = Register::OPEN; + } else { + $serverdata['register_policy'] = Register::CLOSED; + } + + return $serverdata; + } + + /** + * Detect server type by using the nodeinfo data + * + * @param string $url address of the server + * @return array Server data + * @throws \Friendica\Network\HTTPException\InternalServerErrorException + */ + private static function fetchNodeinfo(string $url, $curlResult) + { + $nodeinfo = json_decode($curlResult->getBody(), true); + + if (!is_array($nodeinfo) || empty($nodeinfo['links'])) { + return []; + } + + $nodeinfo1_url = ''; + $nodeinfo2_url = ''; + + foreach ($nodeinfo['links'] as $link) { + if (!is_array($link) || empty($link['rel']) || empty($link['href'])) { + Logger::info('Invalid nodeinfo format', ['url' => $url]); + continue; + } + if ($link['rel'] == 'http://nodeinfo.diaspora.software/ns/schema/1.0') { + $nodeinfo1_url = $link['href']; + } elseif ($link['rel'] == 'http://nodeinfo.diaspora.software/ns/schema/2.0') { + $nodeinfo2_url = $link['href']; + } + } + + if ($nodeinfo1_url . $nodeinfo2_url == '') { + return []; + } + + $server = []; + + // When the nodeinfo url isn't on the same host, then there is obviously something wrong + if (!empty($nodeinfo2_url) && (parse_url($url, PHP_URL_HOST) == parse_url($nodeinfo2_url, PHP_URL_HOST))) { + $server = self::parseNodeinfo2($nodeinfo2_url); + } + + // When the nodeinfo url isn't on the same host, then there is obviously something wrong + if (empty($server) && !empty($nodeinfo1_url) && (parse_url($url, PHP_URL_HOST) == parse_url($nodeinfo1_url, PHP_URL_HOST))) { + $server = self::parseNodeinfo1($nodeinfo1_url); + } + + return $server; + } + + /** + * Parses Nodeinfo 1 + * + * @param string $nodeinfo_url address of the nodeinfo path + * @return array Server data + * @throws \Friendica\Network\HTTPException\InternalServerErrorException + */ + private static function parseNodeinfo1(string $nodeinfo_url) + { + $curlResult = Network::curl($nodeinfo_url); + + if (!$curlResult->isSuccess()) { + return []; + } + + $nodeinfo = json_decode($curlResult->getBody(), true); + + if (!is_array($nodeinfo)) { + return []; + } + + $server = []; + + $server['register_policy'] = Register::CLOSED; + + if (!empty($nodeinfo['openRegistrations'])) { + $server['register_policy'] = Register::OPEN; + } + + if (is_array($nodeinfo['software'])) { + if (!empty($nodeinfo['software']['name'])) { + $server['platform'] = $nodeinfo['software']['name']; + } + + if (!empty($nodeinfo['software']['version'])) { + $server['version'] = $nodeinfo['software']['version']; + // Version numbers on Nodeinfo are presented with additional info, e.g.: + // 0.6.3.0-p1702cc1c, 0.6.99.0-p1b9ab160 or 3.4.3-2-1191. + $server['version'] = preg_replace('=(.+)-(.{4,})=ism', '$1', $server['version']); + } + } + + if (!empty($nodeinfo['metadata']['nodeName'])) { + $server['site_name'] = $nodeinfo['metadata']['nodeName']; + } + + if (!empty($nodeinfo['usage']['users']['total'])) { + $server['registered-users'] = $nodeinfo['usage']['users']['total']; + } + + if (!empty($nodeinfo['protocols']['inbound']) && is_array($nodeinfo['protocols']['inbound'])) { + $protocols = []; + foreach ($nodeinfo['protocols']['inbound'] as $protocol) { + $protocols[$protocol] = true; + } + + if (!empty($protocols['friendica'])) { + $server['network'] = Protocol::DFRN; + } elseif (!empty($protocols['activitypub'])) { + $server['network'] = Protocol::ACTIVITYPUB; + } elseif (!empty($protocols['diaspora'])) { + $server['network'] = Protocol::DIASPORA; + } elseif (!empty($protocols['ostatus'])) { + $server['network'] = Protocol::OSTATUS; + } elseif (!empty($protocols['gnusocial'])) { + $server['network'] = Protocol::OSTATUS; + } elseif (!empty($protocols['zot'])) { + $server['network'] = Protocol::ZOT; + } + } + + if (empty($server)) { + return []; + } + + return $server; + } + + /** + * Parses Nodeinfo 2 + * + * @param string $nodeinfo_url address of the nodeinfo path + * @return array Server data + * @throws \Friendica\Network\HTTPException\InternalServerErrorException + */ + private static function parseNodeinfo2(string $nodeinfo_url) + { + $curlResult = Network::curl($nodeinfo_url); + if (!$curlResult->isSuccess()) { + return []; + } + + $nodeinfo = json_decode($curlResult->getBody(), true); + + if (!is_array($nodeinfo)) { + return []; + } + + $server = []; + + $server['register_policy'] = Register::CLOSED; + + if (!empty($nodeinfo['openRegistrations'])) { + $server['register_policy'] = Register::OPEN; + } + + if (is_array($nodeinfo['software'])) { + if (!empty($nodeinfo['software']['name'])) { + $server['platform'] = $nodeinfo['software']['name']; + } + + if (!empty($nodeinfo['software']['version'])) { + $server['version'] = $nodeinfo['software']['version']; + // Version numbers on Nodeinfo are presented with additional info, e.g.: + // 0.6.3.0-p1702cc1c, 0.6.99.0-p1b9ab160 or 3.4.3-2-1191. + $server['version'] = preg_replace('=(.+)-(.{4,})=ism', '$1', $server['version']); + } + } + + if (!empty($nodeinfo['metadata']['nodeName'])) { + $server['site_name'] = $nodeinfo['metadata']['nodeName']; + } + + if (!empty($nodeinfo['usage']['users']['total'])) { + $server['registered-users'] = $nodeinfo['usage']['users']['total']; + } + + if (!empty($nodeinfo['protocols'])) { + $protocols = []; + foreach ($nodeinfo['protocols'] as $protocol) { + $protocols[$protocol] = true; + } + + if (!empty($protocols['friendica'])) { + $server['network'] = Protocol::DFRN; + } elseif (!empty($protocols['activitypub'])) { + $server['network'] = Protocol::ACTIVITYPUB; + } elseif (!empty($protocols['diaspora'])) { + $server['network'] = Protocol::DIASPORA; + } elseif (!empty($protocols['ostatus'])) { + $server['network'] = Protocol::OSTATUS; + } elseif (!empty($protocols['gnusocial'])) { + $server['network'] = Protocol::OSTATUS; + } elseif (!empty($protocols['zot'])) { + $server['network'] = Protocol::ZOT; + } + } + + if (empty($server)) { + return []; + } + + return $server; + } + + /** + * Fetch server information from a 'siteinfo.json' file on the given server + * + * @param string $url URL of the given server + * @param array $serverdata array with server data + * + * @return array server data + */ + private static function fetchSiteinfo(string $url, array $serverdata) + { + $curlResult = Network::curl($url . '/siteinfo.json'); + if (!$curlResult->isSuccess()) { + return $serverdata; + } + + $data = json_decode($curlResult->getBody(), true); + if (empty($data)) { + return $serverdata; + } + + if (!empty($data['url'])) { + $serverdata['platform'] = $data['platform']; + $serverdata['version'] = $data['version']; + } + + if (!empty($data['plugins'])) { + if (in_array('pubcrawl', $data['plugins'])) { + $serverdata['network'] = Protocol::ACTIVITYPUB; + } elseif (in_array('diaspora', $data['plugins'])) { + $serverdata['network'] = Protocol::DIASPORA; + } elseif (in_array('gnusoc', $data['plugins'])) { + $serverdata['network'] = Protocol::OSTATUS; + } else { + $serverdata['network'] = Protocol::ZOT; + } + } + + if (!empty($data['site_name'])) { + $serverdata['site_name'] = $data['site_name']; + } + + if (!empty($data['channels_total'])) { + $serverdata['registered-users'] = $data['channels_total']; + } + + if (!empty($data['register_policy'])) { + switch ($data['register_policy']) { + case 'REGISTER_OPEN': + $serverdata['register_policy'] = Register::OPEN; + break; + + case 'REGISTER_APPROVE': + $serverdata['register_policy'] = Register::APPROVE; + break; + + case 'REGISTER_CLOSED': + default: + $serverdata['register_policy'] = Register::CLOSED; + break; + } + } + + return $serverdata; + } + + /** + * Checks if the server contains a valid host meta file + * + * @param string $url URL of the given server + * + * @return boolean 'true' if the server seems to be vital + */ + private static function validHostMeta(string $url) + { + $xrd_timeout = Config::get('system', 'xrd_timeout'); + $curlResult = Network::curl($url . '/.well-known/host-meta', false, ['timeout' => $xrd_timeout]); + if (!$curlResult->isSuccess()) { + return false; + } + + $xrd = XML::parseString($curlResult->getBody(), false); + if (!is_object($xrd)) { + return false; + } + + $elements = XML::elementToArray($xrd); + if (empty($elements) || empty($elements['xrd']) || empty($elements['xrd']['link'])) { + return false; + } + + $valid = false; + foreach ($elements['xrd']['link'] as $link) { + // When there is more than a single "link" element, the array looks slightly different + if (!empty($link['@attributes'])) { + $link = $link['@attributes']; + } + + if (empty($link['rel']) || empty($link['template'])) { + continue; + } + + if ($link['rel'] == 'lrdd') { + // When the webfinger host is the same like the system host, it should be ok. + $valid = (parse_url($url, PHP_URL_HOST) == parse_url($link['template'], PHP_URL_HOST)); + } + } + + return $valid; + } + + /** + * Detect the network of the given server via their known contacts + * + * @param string $url URL of the given server + * @param array $serverdata array with server data + * + * @return array server data + */ + private static function detectNetworkViaContacts(string $url, array $serverdata) + { + $contacts = []; + + $gcontacts = DBA::select('gcontact', ['url', 'nurl'], ['server_url' => [$url, $serverdata['nurl']]]); + while ($gcontact = DBA::fetch($gcontacts)) { + $contacts[$gcontact['nurl']] = $gcontact['url']; + } + DBA::close($gcontacts); + + $apcontacts = DBA::select('apcontact', ['url'], ['baseurl' => [$url, $serverdata['nurl']]]); + while ($gcontact = DBA::fetch($gcontacts)) { + $contacts[Strings::normaliseLink($apcontact['url'])] = $apcontact['url']; + } + DBA::close($apcontacts); + + $pcontacts = DBA::select('contact', ['url', 'nurl'], ['uid' => 0, 'baseurl' => [$url, $serverdata['nurl']]]); + while ($gcontact = DBA::fetch($gcontacts)) { + $contacts[$pcontact['nurl']] = $pcontact['url']; + } + DBA::close($pcontacts); + + if (empty($contacts)) { + return $serverdata; + } + + foreach ($contacts as $contact) { + $probed = Probe::uri($contact); + if (in_array($probed['network'], Protocol::FEDERATED)) { + $serverdata['network'] = $probed['network']; + break; + } + } + + $serverdata['registered-users'] = max($serverdata['registered-users'], count($contacts)); + + return $serverdata; + } + + /** + * Checks if the given server does have a '/poco' endpoint. + * This is used for the 'PortableContact' functionality, + * which is used by both Friendica and Hubzilla. + * + * @param string $url URL of the given server + * @param array $serverdata array with server data + * + * @return array server data + */ + private static function checkPoCo(string $url, array $serverdata) + { + $curlResult = Network::curl($url. '/poco'); + if (!$curlResult->isSuccess()) { + return $serverdata; + } + + $data = json_decode($curlResult->getBody(), true); + if (empty($data)) { + return $serverdata; + } + + if (!empty($data['totalResults'])) { + $registeredUsers = $serverdata['registered-users'] ?? 0; + $serverdata['registered-users'] = max($data['totalResults'], $registeredUsers); + $serverdata['poco'] = $url . '/poco'; + } else { + $serverdata['poco'] = ''; + } + + return $serverdata; + } + + /** + * Detects the version number of a given server when it was a NextCloud installation + * + * @param string $url URL of the given server + * @param array $serverdata array with server data + * + * @return array server data + */ + private static function detectNextcloud(string $url, array $serverdata) + { + $curlResult = Network::curl($url . '/status.php'); + + if (!$curlResult->isSuccess() || ($curlResult->getBody() == '')) { + return $serverdata; + } + + $data = json_decode($curlResult->getBody(), true); + if (empty($data)) { + return $serverdata; + } + + if (!empty($data['version'])) { + $serverdata['platform'] = 'nextcloud'; + $serverdata['version'] = $data['version']; + $serverdata['network'] = Protocol::ACTIVITYPUB; + } + + return $serverdata; + } + + /** + * Detects data from a given server url if it was a mastodon alike system + * + * @param string $url URL of the given server + * @param array $serverdata array with server data + * + * @return array server data + */ + private static function detectMastodonAlikes(string $url, array $serverdata) + { + $curlResult = Network::curl($url . '/api/v1/instance'); + + if (!$curlResult->isSuccess() || ($curlResult->getBody() == '')) { + return $serverdata; + } + + $data = json_decode($curlResult->getBody(), true); + if (empty($data)) { + return $serverdata; + } + + if (!empty($data['version'])) { + $serverdata['platform'] = 'mastodon'; + $serverdata['version'] = $data['version'] ?? ''; + $serverdata['network'] = Protocol::ACTIVITYPUB; + } + + if (!empty($data['title'])) { + $serverdata['site_name'] = $data['title']; + } + + if (!empty($data['description'])) { + $serverdata['info'] = trim($data['description']); + } + + if (!empty($data['stats']['user_count'])) { + $serverdata['registered-users'] = $data['stats']['user_count']; + } + + if (!empty($serverdata['version']) && preg_match('/.*?\(compatible;\s(.*)\s(.*)\)/ism', $serverdata['version'], $matches)) { + $serverdata['platform'] = $matches[1]; + $serverdata['version'] = $matches[2]; + } + + if (!empty($serverdata['version']) && strstr($serverdata['version'], 'Pleroma')) { + $serverdata['platform'] = 'pleroma'; + $serverdata['version'] = trim(str_replace('Pleroma', '', $serverdata['version'])); + } + + return $serverdata; + } + + /** + * Detects data from typical Hubzilla endpoints + * + * @param string $url URL of the given server + * @param array $serverdata array with server data + * + * @return array server data + */ + private static function detectHubzilla(string $url, array $serverdata) + { + $curlResult = Network::curl($url . '/api/statusnet/config.json'); + if (!$curlResult->isSuccess() || ($curlResult->getBody() == '')) { + return $serverdata; + } + + $data = json_decode($curlResult->getBody(), true); + if (empty($data)) { + return $serverdata; + } + + if (!empty($data['site']['name'])) { + $serverdata['site_name'] = $data['site']['name']; + } + + if (!empty($data['site']['platform'])) { + $serverdata['platform'] = $data['site']['platform']['PLATFORM_NAME']; + $serverdata['version'] = $data['site']['platform']['STD_VERSION']; + $serverdata['network'] = Protocol::ZOT; + } + + if (!empty($data['site']['hubzilla'])) { + $serverdata['platform'] = $data['site']['hubzilla']['PLATFORM_NAME']; + $serverdata['version'] = $data['site']['hubzilla']['RED_VERSION']; + $serverdata['network'] = Protocol::ZOT; + } + + if (!empty($data['site']['redmatrix'])) { + if (!empty($data['site']['redmatrix']['PLATFORM_NAME'])) { + $serverdata['platform'] = $data['site']['redmatrix']['PLATFORM_NAME']; + } elseif (!empty($data['site']['redmatrix']['RED_PLATFORM'])) { + $serverdata['platform'] = $data['site']['redmatrix']['RED_PLATFORM']; + } + + $serverdata['version'] = $data['site']['redmatrix']['RED_VERSION']; + $serverdata['network'] = Protocol::ZOT; + } + + $private = false; + $inviteonly = false; + $closed = false; + + if (!empty($data['site']['closed'])) { + $closed = self::toBoolean($data['site']['closed']); + } + + if (!empty($data['site']['private'])) { + $private = self::toBoolean($data['site']['private']); + } + + if (!empty($data['site']['inviteonly'])) { + $inviteonly = self::toBoolean($data['site']['inviteonly']); + } + + if (!$closed && !$private and $inviteonly) { + $register_policy = Register::APPROVE; + } elseif (!$closed && !$private) { + $register_policy = Register::OPEN; + } else { + $register_policy = Register::CLOSED; + } + + return $serverdata; + } + + /** + * Converts input value to a boolean value + * + * @param string|integer $val + * + * @return boolean + */ + private static function toBoolean($val) + { + if (($val == 'true') || ($val == 1)) { + return true; + } elseif (($val == 'false') || ($val == 0)) { + return false; + } + + return $val; + } + + /** + * Detect if the URL belongs to a GNU Social server + * + * @param string $url URL of the given server + * @param array $serverdata array with server data + * + * @return array server data + */ + private static function detectGNUSocial(string $url, array $serverdata) + { + // Test for GNU Social + $curlResult = Network::curl($url . '/api/gnusocial/version.json'); + if ($curlResult->isSuccess() && ($curlResult->getBody() != '{"error":"not implemented"}') && + ($curlResult->getBody() != '') && (strlen($curlResult->getBody()) < 30)) { + $serverdata['platform'] = 'gnusocial'; + // Remove junk that some GNU Social servers return + $serverdata['version'] = str_replace(chr(239) . chr(187) . chr(191), '', $curlResult->getBody()); + $serverdata['version'] = trim($serverdata['version'], '"'); + $serverdata['network'] = Protocol::OSTATUS; + return $serverdata; + } + + // Test for Statusnet + $curlResult = Network::curl($url . '/api/statusnet/version.json'); + if ($curlResult->isSuccess() && ($curlResult->getBody() != '{"error":"not implemented"}') && + ($curlResult->getBody() != '') && (strlen($curlResult->getBody()) < 30)) { + $serverdata['platform'] = 'statusnet'; + // Remove junk that some GNU Social servers return + $serverdata['version'] = str_replace(chr(239).chr(187).chr(191), '', $curlResult->getBody()); + $serverdata['version'] = trim($serverdata['version'], '"'); + $serverdata['network'] = Protocol::OSTATUS; + } + + return $serverdata; + } + + /** + * Detect if the URL belongs to a Friendica server + * + * @param string $url URL of the given server + * @param array $serverdata array with server data + * + * @return array server data + */ + private static function detectFriendica(string $url, array $serverdata) + { + $curlResult = Network::curl($url . '/friendica/json'); + if (!$curlResult->isSuccess()) { + $curlResult = Network::curl($url . '/friendika/json'); + } + + if (!$curlResult->isSuccess()) { + return $serverdata; + } + + $data = json_decode($curlResult->getBody(), true); + if (empty($data) || empty($data['version'])) { + return $serverdata; + } + + $serverdata['network'] = Protocol::DFRN; + $serverdata['version'] = $data['version']; + + if (!empty($data['no_scrape_url'])) { + $serverdata['noscrape'] = $data['no_scrape_url']; + } + + if (!empty($data['site_name'])) { + $serverdata['site_name'] = $data['site_name']; + } + + if (!empty($data['info'])) { + $serverdata['info'] = trim($data['info']); + } + + $register_policy = ($data['register_policy'] ?? '') ?: 'REGISTER_CLOSED'; + switch ($register_policy) { + case 'REGISTER_OPEN': + $serverdata['register_policy'] = Register::OPEN; + break; + + case 'REGISTER_APPROVE': + $serverdata['register_policy'] = Register::APPROVE; + break; + + case 'REGISTER_CLOSED': + case 'REGISTER_INVITATION': + $serverdata['register_policy'] = Register::CLOSED; + break; + default: + Logger::info('Register policy is invalid', ['policy' => $register_policy, 'server' => $url]); + $serverdata['register_policy'] = Register::CLOSED; + break; + } + + $serverdata['platform'] = $data['platform'] ?? ''; + + return $serverdata; + } + + /** + * Analyses the landing page of a given server for hints about type and system of that server + * + * @param object $curlResult result of curl execution + * @param array $serverdata array with server data + * @param string $url Server URL + * + * @return array server data + */ + private static function analyseRootBody($curlResult, array $serverdata, string $url) + { + $doc = new DOMDocument(); + @$doc->loadHTML($curlResult->getBody()); + $xpath = new DOMXPath($doc); + + $title = trim(XML::getFirstNodeValue($xpath, '//head/title/text()')); + if (!empty($title)) { + $serverdata['site_name'] = $title; + } + + $list = $xpath->query('//meta[@name]'); + + foreach ($list as $node) { + $attr = []; + if ($node->attributes->length) { + foreach ($node->attributes as $attribute) { + $attribute->value = trim($attribute->value); + if (empty($attribute->value)) { + continue; + } + + $attr[$attribute->name] = $attribute->value; + } + + if (empty($attr['name']) || empty($attr['content'])) { + continue; + } + } + + if ($attr['name'] == 'description') { + $serverdata['info'] = $attr['content']; + } + + if ($attr['name'] == 'application-name') { + $serverdata['platform'] = $attr['content']; + if (in_array($attr['content'], ['Misskey', 'Write.as'])) { + $serverdata['network'] = Protocol::ACTIVITYPUB; + } + } + + if ($attr['name'] == 'generator') { + $serverdata['platform'] = $attr['content']; + + $version_part = explode(' ', $attr['content']); + + if (count($version_part) == 2) { + if (in_array($version_part[0], ['WordPress'])) { + $serverdata['platform'] = $version_part[0]; + $serverdata['version'] = $version_part[1]; + + // We still do need a reliable test if some AP plugin is activated + if (DBA::exists('apcontact', ['baseurl' => $url])) { + $serverdata['network'] = Protocol::ACTIVITYPUB; + } else { + $serverdata['network'] = Protocol::FEED; + } + } + if (in_array($version_part[0], ['Friendika', 'Friendica'])) { + $serverdata['platform'] = $version_part[0]; + $serverdata['version'] = $version_part[1]; + $serverdata['network'] = Protocol::DFRN; + } + } + } + } + + $list = $xpath->query('//meta[@property]'); + + foreach ($list as $node) { + $attr = []; + if ($node->attributes->length) { + foreach ($node->attributes as $attribute) { + $attribute->value = trim($attribute->value); + if (empty($attribute->value)) { + continue; + } + + $attr[$attribute->name] = $attribute->value; + } + + if (empty($attr['property']) || empty($attr['content'])) { + continue; + } + } + + if ($attr['property'] == 'og:site_name') { + $serverdata['site_name'] = $attr['content']; + } + + if ($attr['property'] == 'og:description') { + $serverdata['info'] = $attr['content']; + } + + if ($attr['property'] == 'og:platform') { + $serverdata['platform'] = $attr['content']; + + if (in_array($attr['content'], ['PeerTube'])) { + $serverdata['network'] = Protocol::ACTIVITYPUB; + } + } + + if ($attr['property'] == 'generator') { + $serverdata['platform'] = $attr['content']; + + if (in_array($attr['content'], ['hubzilla'])) { + // We later check which compatible protocol modules are loaded. + $serverdata['network'] = Protocol::ZOT; + } + } + } + + return $serverdata; + } + + /** + * Analyses the header data of a given server for hints about type and system of that server + * + * @param object $curlResult result of curl execution + * @param array $serverdata array with server data + * + * @return array server data + */ + private static function analyseRootHeader($curlResult, array $serverdata) + { + if ($curlResult->getHeader('server') == 'Mastodon') { + $serverdata['platform'] = 'mastodon'; + $serverdata['network'] = $network = Protocol::ACTIVITYPUB; + } elseif ($curlResult->inHeader('x-diaspora-version')) { + $serverdata['platform'] = 'diaspora'; + $serverdata['network'] = $network = Protocol::DIASPORA; + $serverdata['version'] = $curlResult->getHeader('x-diaspora-version'); + + } elseif ($curlResult->inHeader('x-friendica-version')) { + $serverdata['platform'] = 'friendica'; + $serverdata['network'] = $network = Protocol::DFRN; + $serverdata['version'] = $curlResult->getHeader('x-friendica-version'); + } + return $serverdata; + } +} diff --git a/src/Model/Item.php b/src/Model/Item.php index 3a421e96c..ff0f46676 100644 --- a/src/Model/Item.php +++ b/src/Model/Item.php @@ -18,6 +18,7 @@ use Friendica\Core\PConfig; use Friendica\Core\Protocol; use Friendica\Core\Renderer; use Friendica\Core\System; +use Friendica\Core\Session; use Friendica\Core\Worker; use Friendica\Database\DBA; use Friendica\Protocol\ActivityPub; @@ -1312,11 +1313,11 @@ class Item extends BaseObject $priority = $notify; } } else { - $item['network'] = trim(defaults($item, 'network', Protocol::PHANTOM)); + $item['network'] = trim(($item['network'] ?? '') ?: Protocol::PHANTOM); } $item['guid'] = self::guid($item, $notify); - $item['uri'] = Strings::escapeTags(trim(defaults($item, 'uri', self::newURI($item['uid'], $item['guid'])))); + $item['uri'] = Strings::escapeTags(trim(($item['uri'] ?? '') ?: self::newURI($item['uid'], $item['guid']))); // Store URI data $item['uri-id'] = ItemURI::insert(['uri' => $item['uri'], 'guid' => $item['guid']]); @@ -1398,9 +1399,9 @@ class Item extends BaseObject * via OStatus (maybe Diasporsa as well) */ if (empty($item['network']) || in_array($item['network'], Protocol::FEDERATED)) { - $condition = ["`uri` = ? AND `uid` = ? AND `network` IN (?, ?, ?)", + $condition = ["`uri` = ? AND `uid` = ? AND `network` IN (?, ?, ?, ?)", trim($item['uri']), $item['uid'], - Protocol::DIASPORA, Protocol::DFRN, Protocol::OSTATUS]; + Protocol::ACTIVITYPUB, Protocol::DIASPORA, Protocol::DFRN, Protocol::OSTATUS]; $existing = self::selectFirst(['id', 'network'], $condition); if (DBA::isResult($existing)) { // We only log the entries with a different user id than 0. Otherwise we would have too many false positives @@ -1418,47 +1419,47 @@ class Item extends BaseObject } } - $item['wall'] = intval(defaults($item, 'wall', 0)); - $item['extid'] = trim(defaults($item, 'extid', '')); - $item['author-name'] = trim(defaults($item, 'author-name', '')); - $item['author-link'] = trim(defaults($item, 'author-link', '')); - $item['author-avatar'] = trim(defaults($item, 'author-avatar', '')); - $item['owner-name'] = trim(defaults($item, 'owner-name', '')); - $item['owner-link'] = trim(defaults($item, 'owner-link', '')); - $item['owner-avatar'] = trim(defaults($item, 'owner-avatar', '')); + $item['wall'] = intval($item['wall'] ?? 0); + $item['extid'] = trim($item['extid'] ?? ''); + $item['author-name'] = trim($item['author-name'] ?? ''); + $item['author-link'] = trim($item['author-link'] ?? ''); + $item['author-avatar'] = trim($item['author-avatar'] ?? ''); + $item['owner-name'] = trim($item['owner-name'] ?? ''); + $item['owner-link'] = trim($item['owner-link'] ?? ''); + $item['owner-avatar'] = trim($item['owner-avatar'] ?? ''); $item['received'] = (isset($item['received']) ? DateTimeFormat::utc($item['received']) : DateTimeFormat::utcNow()); $item['created'] = (isset($item['created']) ? DateTimeFormat::utc($item['created']) : $item['received']); $item['edited'] = (isset($item['edited']) ? DateTimeFormat::utc($item['edited']) : $item['created']); $item['changed'] = (isset($item['changed']) ? DateTimeFormat::utc($item['changed']) : $item['created']); $item['commented'] = (isset($item['commented']) ? DateTimeFormat::utc($item['commented']) : $item['created']); - $item['title'] = trim(defaults($item, 'title', '')); - $item['location'] = trim(defaults($item, 'location', '')); - $item['coord'] = trim(defaults($item, 'coord', '')); + $item['title'] = trim($item['title'] ?? ''); + $item['location'] = trim($item['location'] ?? ''); + $item['coord'] = trim($item['coord'] ?? ''); $item['visible'] = (isset($item['visible']) ? intval($item['visible']) : 1); $item['deleted'] = 0; - $item['parent-uri'] = trim(defaults($item, 'parent-uri', $item['uri'])); - $item['post-type'] = defaults($item, 'post-type', self::PT_ARTICLE); - $item['verb'] = trim(defaults($item, 'verb', '')); - $item['object-type'] = trim(defaults($item, 'object-type', '')); - $item['object'] = trim(defaults($item, 'object', '')); - $item['target-type'] = trim(defaults($item, 'target-type', '')); - $item['target'] = trim(defaults($item, 'target', '')); - $item['plink'] = trim(defaults($item, 'plink', '')); - $item['allow_cid'] = trim(defaults($item, 'allow_cid', '')); - $item['allow_gid'] = trim(defaults($item, 'allow_gid', '')); - $item['deny_cid'] = trim(defaults($item, 'deny_cid', '')); - $item['deny_gid'] = trim(defaults($item, 'deny_gid', '')); - $item['private'] = intval(defaults($item, 'private', 0)); - $item['body'] = trim(defaults($item, 'body', '')); - $item['tag'] = trim(defaults($item, 'tag', '')); - $item['attach'] = trim(defaults($item, 'attach', '')); - $item['app'] = trim(defaults($item, 'app', '')); - $item['origin'] = intval(defaults($item, 'origin', 0)); - $item['postopts'] = trim(defaults($item, 'postopts', '')); - $item['resource-id'] = trim(defaults($item, 'resource-id', '')); - $item['event-id'] = intval(defaults($item, 'event-id', 0)); - $item['inform'] = trim(defaults($item, 'inform', '')); - $item['file'] = trim(defaults($item, 'file', '')); + $item['parent-uri'] = trim(($item['parent-uri'] ?? '') ?: $item['uri']); + $item['post-type'] = ($item['post-type'] ?? '') ?: self::PT_ARTICLE; + $item['verb'] = trim($item['verb'] ?? ''); + $item['object-type'] = trim($item['object-type'] ?? ''); + $item['object'] = trim($item['object'] ?? ''); + $item['target-type'] = trim($item['target-type'] ?? ''); + $item['target'] = trim($item['target'] ?? ''); + $item['plink'] = trim($item['plink'] ?? ''); + $item['allow_cid'] = trim($item['allow_cid'] ?? ''); + $item['allow_gid'] = trim($item['allow_gid'] ?? ''); + $item['deny_cid'] = trim($item['deny_cid'] ?? ''); + $item['deny_gid'] = trim($item['deny_gid'] ?? ''); + $item['private'] = intval($item['private'] ?? 0); + $item['body'] = trim($item['body'] ?? ''); + $item['tag'] = trim($item['tag'] ?? ''); + $item['attach'] = trim($item['attach'] ?? ''); + $item['app'] = trim($item['app'] ?? ''); + $item['origin'] = intval($item['origin'] ?? 0); + $item['postopts'] = trim($item['postopts'] ?? ''); + $item['resource-id'] = trim($item['resource-id'] ?? ''); + $item['event-id'] = intval($item['event-id'] ?? 0); + $item['inform'] = trim($item['inform'] ?? ''); + $item['file'] = trim($item['file'] ?? ''); // When there is no content then we don't post it if ($item['body'].$item['title'] == '') { @@ -1478,12 +1479,12 @@ class Item extends BaseObject $item['edited'] = DateTimeFormat::utcNow(); } - $item['plink'] = defaults($item, 'plink', System::baseUrl() . '/display/' . urlencode($item['guid'])); + $item['plink'] = ($item['plink'] ?? '') ?: System::baseUrl() . '/display/' . urlencode($item['guid']); $default = ['url' => $item['author-link'], 'name' => $item['author-name'], 'photo' => $item['author-avatar'], 'network' => $item['network']]; - $item['author-id'] = defaults($item, 'author-id', Contact::getIdForURL($item['author-link'], 0, false, $default)); + $item['author-id'] = ($item['author-id'] ?? 0) ?: Contact::getIdForURL($item['author-link'], 0, false, $default); if (Contact::isBlocked($item['author-id'])) { Logger::notice('Author is blocked node-wide', ['author-link' => $item['author-link'], 'item-uri' => $item['uri']]); @@ -1503,7 +1504,7 @@ class Item extends BaseObject $default = ['url' => $item['owner-link'], 'name' => $item['owner-name'], 'photo' => $item['owner-avatar'], 'network' => $item['network']]; - $item['owner-id'] = defaults($item, 'owner-id', Contact::getIdForURL($item['owner-link'], 0, false, $default)); + $item['owner-id'] = ($item['owner-id'] ?? 0) ?: Contact::getIdForURL($item['owner-link'], 0, false, $default); if (Contact::isBlocked($item['owner-id'])) { Logger::notice('Owner is blocked node-wide', ['owner-link' => $item['owner-link'], 'item-uri' => $item['uri']]); @@ -2452,7 +2453,7 @@ class Item extends BaseObject Contact::unmarkForArchival($contact); } - $update = (!$arr['private'] && ((defaults($arr, 'author-link', '') === defaults($arr, 'owner-link', '')) || ($arr["parent-uri"] === $arr["uri"]))); + $update = (!$arr['private'] && ((($arr['author-link'] ?? '') === ($arr['owner-link'] ?? '')) || ($arr["parent-uri"] === $arr["uri"]))); // Is it a forum? Then we don't care about the rules from above if (!$update && in_array($arr["network"], [Protocol::ACTIVITYPUB, Protocol::DFRN]) && ($arr["parent-uri"] === $arr["uri"])) { @@ -3028,7 +3029,7 @@ class Item extends BaseObject */ public static function performLike($item_id, $verb) { - if (!local_user() && !remote_user()) { + if (!Session::isAuthenticated()) { return false; } @@ -3260,10 +3261,10 @@ class Item extends BaseObject } } - public static function getPermissionsSQLByUserId($owner_id, $remote_verified = false, $groups = null, $remote_cid = null) + public static function getPermissionsSQLByUserId($owner_id) { $local_user = local_user(); - $remote_user = remote_user(); + $remote_user = Session::getRemoteContactID($owner_id); /* * Construct permissions @@ -3283,7 +3284,7 @@ class Item extends BaseObject * If pre-verified, the caller is expected to have already * done this and passed the groups into this function. */ - $set = PermissionSet::get($owner_id, $remote_cid, $groups); + $set = PermissionSet::get($owner_id, $remote_user); if (!empty($set)) { $sql_set = " OR (`item`.`private` IN (1,2) AND `item`.`wall` AND `item`.`psid` IN (" . implode(',', $set) . "))"; @@ -3333,8 +3334,8 @@ class Item extends BaseObject { $body = $item["body"]; - $rendered_hash = defaults($item, 'rendered-hash', ''); - $rendered_html = defaults($item, 'rendered-html', ''); + $rendered_hash = $item['rendered-hash'] ?? ''; + $rendered_html = $item['rendered-html'] ?? ''; if ($rendered_hash == '' || $rendered_html == "" @@ -3427,7 +3428,7 @@ class Item extends BaseObject } // Update the cached values if there is no "zrl=..." on the links. - $update = (!local_user() && !remote_user() && ($item["uid"] == 0)); + $update = (!Session::isAuthenticated() && ($item["uid"] == 0)); // Or update it if the current viewer is the intented viewer. if (($item["uid"] == local_user()) && ($item["uid"] != 0)) { @@ -3490,7 +3491,7 @@ class Item extends BaseObject $filesubtype = 'unkn'; } - $title = Strings::escapeHtml(trim(defaults($mtch, 4, $mtch[1]))); + $title = Strings::escapeHtml(trim(($mtch[4] ?? '') ?: $mtch[1])); $title .= ' ' . $mtch[2] . ' ' . L10n::t('bytes'); $icon = '
'; @@ -3509,37 +3510,6 @@ class Item extends BaseObject } } - - // Look for spoiler. - $spoilersearch = '
'; - - // Remove line breaks before the spoiler. - while ((strpos($s, "\n" . $spoilersearch) !== false)) { - $s = str_replace("\n" . $spoilersearch, $spoilersearch, $s); - } - while ((strpos($s, "
" . $spoilersearch) !== false)) { - $s = str_replace("
" . $spoilersearch, $spoilersearch, $s); - } - - while ((strpos($s, $spoilersearch) !== false)) { - $pos = strpos($s, $spoilersearch); - $rnd = Strings::getRandomHex(8); - $spoilerreplace = '
' . L10n::t('Click to open/close') . ''. - '
diff --git a/view/theme/frio/templates/jot-header.tpl b/view/theme/frio/templates/jot-header.tpl index db2c9afeb..9f226cda4 100644 --- a/view/theme/frio/templates/jot-header.tpl +++ b/view/theme/frio/templates/jot-header.tpl @@ -15,7 +15,7 @@ $("#jot-category").show(); $("#jot-category").addClass("jot-category-ex"); $("#jot-profile-jot-wrapper").show(); - $("#profile-jot-text").editor_autocomplete(baseurl+"/acl"); + $("#profile-jot-text").editor_autocomplete(baseurl + '/search/acl'); $("#profile-jot-text").bbco_autocomplete('bbcode'); $("a#jot-perms-icon").colorbox({ 'inline' : true, diff --git a/view/theme/frio/templates/msg-header.tpl b/view/theme/frio/templates/msg-header.tpl index 209010d7a..a1290c6ca 100644 --- a/view/theme/frio/templates/msg-header.tpl +++ b/view/theme/frio/templates/msg-header.tpl @@ -1,6 +1,6 @@ diff --git a/view/theme/frio/templates/saved_searches_aside.tpl b/view/theme/frio/templates/saved_searches_aside.tpl deleted file mode 100644 index 48b07bcde..000000000 --- a/view/theme/frio/templates/saved_searches_aside.tpl +++ /dev/null @@ -1,18 +0,0 @@ - -{{if $saved}} -
- - - -
-
-{{/if}} diff --git a/view/theme/frio/templates/searchbox.tpl b/view/theme/frio/templates/searchbox.tpl index c7d06d107..ced6fbbd3 100644 --- a/view/theme/frio/templates/searchbox.tpl +++ b/view/theme/frio/templates/searchbox.tpl @@ -4,34 +4,32 @@ Some parts of this template will be moved by js to other places (see theme.js) -
-
+
{{* The button to save searches *}} - {{if $savedsearch}} - + {{if $s}} + {{$save_label}} {{/if}} {{* The select popup menu to select what kind of results the user would like to search for *}} - {{if $searchoption}} + {{if $search_options}}
-
{{/if}} @@ -47,17 +45,14 @@ Some parts of this template will be moved by js to other places (see theme.js) -
-{{if $savedsearch}} -
- - - + {{$save_label}} + {{/if}}
diff --git a/view/theme/frio/templates/widget/saved_searches.tpl b/view/theme/frio/templates/widget/saved_searches.tpl new file mode 100644 index 000000000..1981508cf --- /dev/null +++ b/view/theme/frio/templates/widget/saved_searches.tpl @@ -0,0 +1,18 @@ + +{{if $saved}} +
+ + + +
+
+{{/if}} diff --git a/view/theme/quattro/dark/style.css b/view/theme/quattro/dark/style.css index bfb078e56..6b4d2b880 100644 --- a/view/theme/quattro/dark/style.css +++ b/view/theme/quattro/dark/style.css @@ -2136,7 +2136,7 @@ ul.tabs li .active { .identity-match-photo { position: relative; } -.identity-match-photo .manage-notify { +.identity-match-photo .delegation-notify { background-color: #19AEFF; border-radius: 5px; font-size: 10px; diff --git a/view/theme/quattro/green/style.css b/view/theme/quattro/green/style.css index 1dce844ac..326a2fbfd 100644 --- a/view/theme/quattro/green/style.css +++ b/view/theme/quattro/green/style.css @@ -2136,7 +2136,7 @@ ul.tabs li .active { .identity-match-photo { position: relative; } -.identity-match-photo .manage-notify { +.identity-match-photo .delegation-notify { background-color: #19AEFF; border-radius: 5px; font-size: 10px; diff --git a/view/theme/quattro/js/quattro.js b/view/theme/quattro/js/quattro.js index 5a5390737..89407569b 100644 --- a/view/theme/quattro/js/quattro.js +++ b/view/theme/quattro/js/quattro.js @@ -60,28 +60,6 @@ $(document).ready(function(){ }); }); -function insertFormatting(BBcode, id) { - var tmpStr = $("#comment-edit-text-" + id).val(); - if (tmpStr == "") { - $("#comment-edit-text-" + id).addClass("comment-edit-text-full"); - $("#comment-edit-text-" + id).removeClass("comment-edit-text-empty"); - openMenu("comment-edit-submit-wrapper-" + id); - } - - textarea = document.getElementById("comment-edit-text-" +id); - if (document.selection) { - textarea.focus(); - selected = document.selection.createRange(); - selected.text = "["+BBcode+"]" + selected.text + "[/"+BBcode+"]"; - } else if (textarea.selectionStart || textarea.selectionStart == "0") { - var start = textarea.selectionStart; - var end = textarea.selectionEnd; - textarea.value = textarea.value.substring(0, start) + "["+BBcode+"]" + textarea.value.substring(start, end) + "[/"+BBcode+"]" + textarea.value.substring(end, textarea.value.length); - } - - return true; -} - function showThread(id) { $("#collapsed-comments-" + id).show() $("#collapsed-comments-" + id + " .collapsed-comments").show() diff --git a/view/theme/quattro/lilac/style.css b/view/theme/quattro/lilac/style.css index 870fe6ce2..064b60ba6 100644 --- a/view/theme/quattro/lilac/style.css +++ b/view/theme/quattro/lilac/style.css @@ -2136,7 +2136,7 @@ ul.tabs li .active { .identity-match-photo { position: relative; } -.identity-match-photo .manage-notify { +.identity-match-photo .delegation-notify { background-color: #19AEFF; border-radius: 5px; font-size: 10px; diff --git a/view/theme/quattro/quattro.less b/view/theme/quattro/quattro.less index 415eda83c..0c1c21a7b 100644 --- a/view/theme/quattro/quattro.less +++ b/view/theme/quattro/quattro.less @@ -1419,7 +1419,7 @@ ul.tabs { /* manage page */ .identity-match-photo { position: relative; - .manage-notify { + .delegation-notify { background-color: #19AEFF; border-radius: 5px; font-size: 10px; diff --git a/view/theme/quattro/style.php b/view/theme/quattro/style.php index cca458a25..84fa0b973 100644 --- a/view/theme/quattro/style.php +++ b/view/theme/quattro/style.php @@ -6,7 +6,7 @@ use Friendica\Core\Config; use Friendica\Core\PConfig; use Friendica\Model\Profile; -$uid = defaults($_REQUEST, 'puid', 0); +$uid = $_REQUEST['puid'] ?? 0; $color = false; $quattro_align = false; diff --git a/view/theme/quattro/templates/nav.tpl b/view/theme/quattro/templates/nav.tpl index cb5412c16..c42e570f5 100644 --- a/view/theme/quattro/templates/nav.tpl +++ b/view/theme/quattro/templates/nav.tpl @@ -71,7 +71,7 @@