1
0
Fork 0

Merge remote-tracking branch 'origin/2022.12-rc' into fixes

This commit is contained in:
Michael 2022-12-11 19:00:59 +00:00
commit 2f3f41ed9c
714 changed files with 34811 additions and 27839 deletions

258
.ddev/config.yaml Normal file
View file

@ -0,0 +1,258 @@
name: my-friendica
type: php
docroot: ""
php_version: "7.3"
webserver_type: apache-fpm
router_http_port: "80"
router_https_port: "443"
xdebug_enabled: false
additional_hostnames: []
additional_fqdns: []
database:
type: mariadb
version: "10.4"
nfs_mount_enabled: false
mutagen_enabled: false
use_dns_when_possible: true
composer_version: "1"
web_environment: []
nodejs_version: "16"
webimage_extra_packages: [php7.3-gmp]
# Key features of ddev's config.yaml:
# name: <projectname> # Name of the project, automatically provides
# http://projectname.ddev.site and https://projectname.ddev.site
# type: <projecttype> # drupal6/7/8, backdrop, typo3, wordpress, php
# docroot: <relative_path> # Relative path to the directory containing index.php.
# php_version: "7.4" # PHP version to use, "5.6", "7.0", "7.1", "7.2", "7.3", "7.4", "8.0", "8.1", "8.2"
# You can explicitly specify the webimage but this
# is not recommended, as the images are often closely tied to ddev's' behavior,
# so this can break upgrades.
# webimage: <docker_image> # nginx/php docker image.
# database:
# type: <dbtype> # mysql, mariadb
# version: <version> # database version, like "10.3" or "8.0"
# Note that mariadb_version or mysql_version from v1.18 and earlier
# will automatically be converted to this notation with just a "ddev config --auto"
# router_http_port: <port> # Port to be used for http (defaults to port 80)
# router_https_port: <port> # Port for https (defaults to 443)
# xdebug_enabled: false # Set to true to enable xdebug and "ddev start" or "ddev restart"
# Note that for most people the commands
# "ddev xdebug" to enable xdebug and "ddev xdebug off" to disable it work better,
# as leaving xdebug enabled all the time is a big performance hit.
# xhprof_enabled: false # Set to true to enable xhprof and "ddev start" or "ddev restart"
# Note that for most people the commands
# "ddev xhprof" to enable xhprof and "ddev xhprof off" to disable it work better,
# as leaving xhprof enabled all the time is a big performance hit.
# webserver_type: nginx-fpm # or apache-fpm
# timezone: Europe/Berlin
# This is the timezone used in the containers and by PHP;
# it can be set to any valid timezone,
# see https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
# For example Europe/Dublin or MST7MDT
# composer_root: <relative_path>
# Relative path to the composer root directory from the project root. This is
# the directory which contains the composer.json and where all Composer related
# commands are executed.
# composer_version: "2"
# You can set it to "" or "2" (default) for Composer v2 or "1" for Composer v1
# to use the latest major version available at the time your container is built.
# It is also possible to use each other Composer version channel. This includes:
# - 2.2 (latest Composer LTS version)
# - stable
# - preview
# - snapshot
# Alternatively, an explicit Composer version may be specified, for example "2.2.18".
# To reinstall Composer after the image was built, run "ddev debug refresh".
# nodejs_version: "16"
# change from the default system Node.js version to another supported version, like 12, 14, 17, 18.
# Note that you can use 'ddev nvm' or nvm inside the web container to provide nearly any
# Node.js version, including v6, etc.
# additional_hostnames:
# - somename
# - someothername
# would provide http and https URLs for "somename.ddev.site"
# and "someothername.ddev.site".
# additional_fqdns:
# - example.com
# - sub1.example.com
# would provide http and https URLs for "example.com" and "sub1.example.com"
# Please take care with this because it can cause great confusion.
# upload_dir: custom/upload/dir
# would set the destination path for ddev import-files to <docroot>/custom/upload/dir
# When mutagen is enabled this path is bind-mounted so that all the files
# in the upload_dir don't have to be synced into mutagen
# working_dir:
# web: /var/www/html
# db: /home
# would set the default working directory for the web and db services.
# These values specify the destination directory for ddev ssh and the
# directory in which commands passed into ddev exec are run.
# omit_containers: [db, dba, ddev-ssh-agent]
# Currently only these containers are supported. Some containers can also be
# omitted globally in the ~/.ddev/global_config.yaml. Note that if you omit
# the "db" container, several standard features of ddev that access the
# database container will be unusable. In the global configuration it is also
# possible to omit ddev-router, but not here.
# nfs_mount_enabled: false
# Great performance improvement but requires host configuration first.
# See https://ddev.readthedocs.io/en/latest/users/install/performance/#nfs
# mutagen_enabled: false
# Performance improvement using mutagen asynchronous updates.
# See https://ddev.readthedocs.io/en/latest/users/install/performance/#mutagen
# fail_on_hook_fail: False
# Decide whether 'ddev start' should be interrupted by a failing hook
# host_https_port: "59002"
# The host port binding for https can be explicitly specified. It is
# dynamic unless otherwise specified.
# This is not used by most people, most people use the *router* instead
# of the localhost port.
# host_webserver_port: "59001"
# The host port binding for the ddev-webserver can be explicitly specified. It is
# dynamic unless otherwise specified.
# This is not used by most people, most people use the *router* instead
# of the localhost port.
# host_db_port: "59002"
# The host port binding for the ddev-dbserver can be explicitly specified. It is dynamic
# unless explicitly specified.
# phpmyadmin_port: "8036"
# phpmyadmin_https_port: "8037"
# The PHPMyAdmin ports can be changed from the default 8036 and 8037
# host_phpmyadmin_port: "8036"
# The phpmyadmin (dba) port is not normally bound on the host at all, instead being routed
# through ddev-router, but it can be specified and bound.
# mailhog_port: "8025"
# mailhog_https_port: "8026"
# The MailHog ports can be changed from the default 8025 and 8026
# host_mailhog_port: "8025"
# The mailhog port is not normally bound on the host at all, instead being routed
# through ddev-router, but it can be bound directly to localhost if specified here.
# webimage_extra_packages: [php7.4-tidy, php-bcmath]
# Extra Debian packages that are needed in the webimage can be added here
# dbimage_extra_packages: [telnet,netcat]
# Extra Debian packages that are needed in the dbimage can be added here
# use_dns_when_possible: true
# If the host has internet access and the domain configured can
# successfully be looked up, DNS will be used for hostname resolution
# instead of editing /etc/hosts
# Defaults to true
# project_tld: ddev.site
# The top-level domain used for project URLs
# The default "ddev.site" allows DNS lookup via a wildcard
# If you prefer you can change this to "ddev.local" to preserve
# pre-v1.9 behavior.
# ngrok_args: --basic-auth username:pass1234
# Provide extra flags to the "ngrok http" command, see
# https://ngrok.com/docs#http or run "ngrok http -h"
# disable_settings_management: false
# If true, ddev will not create CMS-specific settings files like
# Drupal's settings.php/settings.ddev.php or TYPO3's AdditionalConfiguration.php
# In this case the user must provide all such settings.
# You can inject environment variables into the web container with:
# web_environment:
# - SOMEENV=somevalue
# - SOMEOTHERENV=someothervalue
# no_project_mount: false
# (Experimental) If true, ddev will not mount the project into the web container;
# the user is responsible for mounting it manually or via a script.
# This is to enable experimentation with alternate file mounting strategies.
# For advanced users only!
# bind_all_interfaces: false
# If true, host ports will be bound on all network interfaces,
# not just the localhost interface. This means that ports
# will be available on the local network if the host firewall
# allows it.
# default_container_timeout: 120
# The default time that ddev waits for all containers to become ready can be increased from
# the default 120. This helps in importing huge databases, for example.
#web_extra_exposed_ports:
#- name: nodejs
# container_port: 3000
# http_port: 2999
# https_port: 3000
#- name: something
# container_port: 4000
# https_port: 4000
# http_port: 3999
# Allows a set of extra ports to be exposed via ddev-router
# The port behavior on the ddev-webserver must be arranged separately, for example
# using web_extra_daemons.
# For example, with a web app on port 3000 inside the container, this config would
# expose that web app on https://<project>.ddev.site:9999 and http://<project>.ddev.site:9998
# web_extra_exposed_ports:
# - container_port: 3000
# http_port: 9998
# https_port: 9999
#web_extra_daemons:
#- name: "http-1"
# command: "/var/www/html/node_modules/.bin/http-server -p 3000"
# directory: /var/www/html
#- name: "http-2"
# command: "/var/www/html/node_modules/.bin/http-server /var/www/html/sub -p 3000"
# directory: /var/www/html
# override_config: false
# By default, config.*.yaml files are *merged* into the configuration
# But this means that some things can't be overridden
# For example, if you have 'nfs_mount_enabled: true'' you can't override it with a merge
# and you can't erase existing hooks or all environment variables.
# However, with "override_config: true" in a particular config.*.yaml file,
# 'nfs_mount_enabled: false' can override the existing values, and
# hooks:
# post-start: []
# or
# web_environment: []
# or
# additional_hostnames: []
# can have their intended affect. 'override_config' affects only behavior of the
# config.*.yaml file it exists in.
# Many ddev commands can be extended to run tasks before or after the
# ddev command is executed, for example "post-start", "post-import-db",
# "pre-composer", "post-composer"
# See https://ddev.readthedocs.io/en/stable/users/extend/custom-commands/ for more
# information on the commands that can be extended and the tasks you can define
# for them. Example:
#hooks:

7
.gitignore vendored
View file

@ -10,10 +10,9 @@ home.html
robots.txt
#ignore local config
/config/local.config.php
/config/addon.config.php
/config/local.ini.php
/config/addon.ini.php
!/config/local-sample.config.php
/config/*.config.php
/config/*.ini.php
#ignore documentation, it should be newly built
/doc/api

View file

@ -51,6 +51,6 @@ AddType audio/ogg .oga
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*)$ index.php?pagename=$1 [E=REMOTE_USER:%{HTTP:Authorization},L,QSA]
RewriteRule ^(.*)$ index.php?pagename=$1 [E=REMOTE_USER:%{HTTP:Authorization},L,QSA,B]
</IfModule>

View file

@ -39,7 +39,7 @@ pipeline:
branch: [ develop, '*-rc' ]
event: push
composer_install:
image: friendicaci/php7.4:php7.4.18
image: friendicaci/php7.4:php7.4.33
commands:
- export COMPOSER_HOME=.composer
- composer validate

View file

@ -1,7 +1,7 @@
matrix:
include:
- PHP_MAJOR_VERSION: 7.4
PHP_VERSION: 7.4.18
PHP_VERSION: 7.4.33
branches:
exclude: [ stable ]

View file

@ -1,11 +1,17 @@
matrix:
include:
- PHP_MAJOR_VERSION: 7.3
PHP_VERSION: 7.3.28
PHP_VERSION: 7.3.33
- PHP_MAJOR_VERSION: 7.4
PHP_VERSION: 7.4.18
PHP_VERSION: 7.4.33
- PHP_MAJOR_VERSION: 8.0
PHP_VERSION: 8.0.5
PHP_VERSION: 8.0.25
- PHP_MAJOR_VERSION: 8.1
PHP_VERSION: 8.1.12
# This forces PHP Unit executions at the "opensocial" labeled location (because of much more power...)
labels:
location: opensocial
pipeline:
php-lint:

View file

@ -37,7 +37,7 @@ pipeline:
branch: stable
event: tag
composer_install:
image: friendicaci/php7.4:php7.4.18
image: friendicaci/php7.4:php7.4.33
commands:
- export COMPOSER_HOME=.composer
- composer validate

View file

@ -1,10 +1,59 @@
Version 2022.09 (unreleased)
Version 2022.12 (unreleased)
Friendica Core
The rewrite rule in .htaccess-dist has been changed. The change has to be applied manually to the existing .htaccess
Friendica Addons
BREAKING: The functions from the boot.php file have been moved into better fitting classes
this may break your custom addons. See the pull requests #1293 and #1294 in the
addon repository about the needed changes to your addons.
Closed Issues
Version 2022.10 (2022-10-14)
Friendica Core
Added GD translation, updates to the translations AR, DE, FR, HU, PL, SV, ZH CN
Added a check for too long passwords (due Blowfish hashing algorithm) [MrPetovan]
Added an API endpoint to create events [MrPetovan, pankraz]
Added the possibility to store profile avatars in a separate directory [annando]
Added an option to not fetch parent postings [annando]
Added an option to reject postings by language received by the relay [annando]
Added a notification mail to all users when the server block list is updated [MrPetovan]
Added a download link to the CSV file of the server block list on the about page [MrPetovan]
Added support for youtube short URLs [annando]
Updates to the themes (frio, smoothly) [AlessandroLorenzi, HankG, MrPetovan, tobiasd]
General code cleanup [annando, fabrixxm, Quix0r, tobiasd]
Enhanced the performance (cache, database, rendering) [annando, Quix0r]
Enhanced the language detection [annando]
Enhanced the display of the reason why a posting is shown to a user [annando]
Enhanced the fetching of missing postings [annando]
Enhanced the server detection [annando]
Enhanced the UI for 2FA logins [nupplaphil]
Enhanced the Woodpecker integration [nupplaphil]
Enhanced integration with ejabberd [nupplaphil]
Fixed a federation problem with Diaspora* during the author signature check [annando]
Fixed a problem with Forwarded-For headers [nupplaphil]
Fixed a problem with the encoding of mails send [MrPetovan]
Fixed a problem with weird formatted date notations [annando, MrPetovan]
Fixed a problem following some RSS feeds [mexon]
Fixed a problem with quoted reshares from Twitter [annando]
Updated dependencies [MrPetovan]
Replace SFTP-publish with docker-publish [nupplaphil]
Removed the poke functionality [MrPetovan]
Friendica Addons
Added GD translation, updates to the translation AR, FR
Removed the addons: morechoice, morepokes
Marked the addon whindowsphonepush as unsupported
twitter:
Valid post body can be empty [MrPetovan]
Support of Twitter threads was added [annando]
Closed Issues
11177, 11317, 11458, 11471, 11566, 11614, 11625, 11635, 11636, 11638,
11651, 11661, 11666, 11695, 11700, 11704, 11706, 11708, 11712, 11716,
11722, 11723, 11724, 11726, 11731, 11732, 11751, 11765, 11775, 11778,
11779, 11794, 11798, 11799, 11800, 11824, 11826, 11851, 11861, 11870,
11909, 11920, 11931, 11938, 11943, 11952, 11953, 11969, 11975
Version 2022.06 (2022-06-11)
Friendica Core

View file

@ -1,4 +1,4 @@
INPUT = README.md index.php boot.php update.php bin/ mod/ include/ view/ src/ VERSION
INPUT = README.md index.php update.php bin/ mod/ view/ src/ VERSION
RECURSIVE = YES
PROJECT_NAME = "Friendica"
PROJECT_LOGO = images/friendica-64.png

View file

@ -9,7 +9,7 @@ Our mission is to free friends, family and colleagues from data-harvesting corpo
Friendica connects you effortlessly to a federated communications network of several thousand servers, with more than half a million user registrations. You can directly connect to anyone on [Friendica]( https://friendi.ca), [Mastodon](https://joinmastodon.org/), [Diaspora](https://diasporafoundation.org/), [GnuSocial](https://gnu.io/social/), [Pleroma](https://pleroma.social/), or [Hubzilla](https://hubzilla.org/), regardless where each user profile is hosted.
With Friendica, you can also fully interact with anyone on Twitter, post on Facebook and receive any content on Tumblr, Wordpress or RSS. Friendica allows you to integrate most things on the web via a range of addons such as ITTT, Buffer; you will be able to easily control your own data as you decide.
With Friendica, you can also fully interact with anyone on Twitter and receive any content from Tumblr, Wordpress or RSS. Friendica allows you to integrate most things on the web via a range of addons such as ITTT, Buffer; you will be able to easily control your own data as you decide.
Join today and [get your Friendica profile!](https://dir.friendica.social/servers 'Join Friendica today!')

View file

@ -1 +1 @@
2022.09-rc
2022.12-rc

2
Vagrantfile vendored
View file

@ -1,5 +1,5 @@
server_ip = "192.168.22.10"
server_ip = "192.168.56.10"
server_memory = "2048" # MB
server_timezone = "UTC"

View file

@ -26,6 +26,7 @@ if (php_sapi_name() !== 'cli') {
}
use Dice\Dice;
use Friendica\DI;
use Psr\Log\LoggerInterface;
require dirname(__DIR__) . '/vendor/autoload.php';
@ -33,6 +34,8 @@ require dirname(__DIR__) . '/vendor/autoload.php';
$dice = (new Dice())->addRules(include __DIR__ . '/../static/dependencies.config.php');
$dice = $dice->addRule(LoggerInterface::class,['constructParams' => ['console']]);
/// @fixme Necessary until Hooks inside the Logger can get loaded without the DI-class
DI::init($dice);
\Friendica\Core\Logger\Handler\ErrorHandler::register($dice->create(\Psr\Log\LoggerInterface::class));
(new Friendica\Core\Console($dice, $argv))->execute();

View file

@ -45,7 +45,7 @@ $longopts = ['foreground'];
$options = getopt($shortopts, $longopts);
// Ensure that daemon.php is executed from the base path of the installation
if (!file_exists('boot.php') && (sizeof($_SERVER['argv']) != 0)) {
if (!file_exists('index.php') && (sizeof($_SERVER['argv']) != 0)) {
$directory = dirname($_SERVER['argv'][0]);
if (substr($directory, 0, 1) != '/') {

View file

@ -45,13 +45,13 @@ apt-get install -qq apache2
a2enmod rewrite actions ssl
cp /vagrant/bin/dev/vagrant_vhost.sh /usr/local/bin/vhost
chmod guo+x /usr/local/bin/vhost
vhost -s 192.168.22.10.xip.io -d /var/www -p /etc/ssl/xip.io -c xip.io -a friendica.local
vhost -s 192.168.56.10.xip.io -d /var/www -p /etc/ssl/xip.io -c xip.io -a friendica.local
a2dissite 000-default
service apache2 restart
#Install php
echo ">>> Installing PHP7"
apt-get install -qq php libapache2-mod-php php-cli php-mysql php-curl php-gd php-mbstring php-xml imagemagick php-imagick php-zip
apt-get install -qq php libapache2-mod-php php-cli php-mysql php-curl php-gd php-mbstring php-xml imagemagick php-imagick php-zip php-gmp
systemctl restart apache2
echo ">>> Installing PHP8"
@ -59,7 +59,7 @@ apt-get install -qq -y lsb-release ca-certificates apt-transport-https software-
echo "deb https://packages.sury.org/php/ $(lsb_release -sc) main" | sudo tee /etc/apt/sources.list.d/sury-php.list
wget -qO - https://packages.sury.org/php/apt.gpg | sudo apt-key add -
apt update
apt-get install -qq php8.0 php8.0-cli php8.0-mysql php8.0-curl php8.0-gd php8.0-mbstring php8.0-xml php8.0-imagick php8.0-zip
apt-get install -qq php8.0 php8.0-cli php8.0-mysql php8.0-curl php8.0-gd php8.0-mbstring php8.0-xml php8.0-imagick php8.0-zip php8.0-gmp
systemctl restart apache2
#Install mysql

View file

@ -40,7 +40,7 @@ $longopts = ['spawn', 'no_cron'];
$options = getopt($shortopts, $longopts);
// Ensure that worker.php is executed from the base path of the installation
if (!file_exists("boot.php") && (sizeof($_SERVER["argv"]) != 0)) {
if (!file_exists("index.php") && (sizeof($_SERVER["argv"]) != 0)) {
$directory = dirname($_SERVER["argv"][0]);
if (substr($directory, 0, 1) != '/') {

194
boot.php
View file

@ -1,194 +0,0 @@
<?php
/**
* @copyright Copyright (C) 2010-2022, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* Friendica is a communications platform for integrated social communications
* utilising decentralised communications and linkage to several indie social
* projects - as well as popular mainstream providers.
*
* Our mission is to free our friends and families from the clutches of
* data-harvesting corporations, and pave the way to a future where social
* communications are free and open and flow between alternate providers as
* easily as email does today.
*/
use Friendica\Model\Contact;
define('FRIENDICA_PLATFORM', 'Friendica');
define('FRIENDICA_CODENAME', 'Giant Rhubarb');
define('FRIENDICA_VERSION', '2022.09-rc');
define('DFRN_PROTOCOL_VERSION', '2.23');
define('NEW_TABLE_STRUCTURE_VERSION', 1288);
/**
* Constant with a HTML line break.
*
* Contains a HTML line break (br) element and a real carriage return with line
* feed for the source.
* This can be used in HTML and JavaScript where needed a line break.
*/
define('EOL', "<br />\r\n");
/**
* @name CP
*
* Type of the community page
* @{
*/
define('CP_NO_INTERNAL_COMMUNITY', -2);
define('CP_NO_COMMUNITY_PAGE', -1);
define('CP_USERS_ON_SERVER', 0);
define('CP_GLOBAL_COMMUNITY', 1);
define('CP_USERS_AND_GLOBAL', 2);
/**
* @}
*/
/**
* @name Gravity
*
* Item weight for query ordering
* @{
*/
define('GRAVITY_PARENT', 0);
define('GRAVITY_ACTIVITY', 3);
define('GRAVITY_COMMENT', 6);
define('GRAVITY_UNKNOWN', 9);
/* @}*/
/**
* @name Priority
*
* Process priority for the worker
* @{
*/
define('PRIORITY_UNDEFINED', 0);
define('PRIORITY_CRITICAL', 10);
define('PRIORITY_HIGH', 20);
define('PRIORITY_MEDIUM', 30);
define('PRIORITY_LOW', 40);
define('PRIORITY_NEGLIGIBLE', 50);
define('PRIORITIES', [PRIORITY_CRITICAL, PRIORITY_HIGH, PRIORITY_MEDIUM, PRIORITY_LOW, PRIORITY_NEGLIGIBLE]);
/* @}*/
// Normally this constant is defined - but not if "pcntl" isn't installed
if (!defined('SIGTERM')) {
define('SIGTERM', 15);
}
/**
* Depending on the PHP version this constant does exist - or not.
* See here: http://php.net/manual/en/curl.constants.php#117928
*/
if (!defined('CURLE_OPERATION_TIMEDOUT')) {
define('CURLE_OPERATION_TIMEDOUT', CURLE_OPERATION_TIMEOUTED);
}
if (!function_exists('exif_imagetype')) {
function exif_imagetype($file)
{
$size = getimagesize($file);
return $size[2];
}
}
/**
* Returns the user id of locally logged in user or false.
*
* @return int|bool user id or false
*/
function local_user()
{
if (!empty($_SESSION['authenticated']) && !empty($_SESSION['uid'])) {
return intval($_SESSION['uid']);
}
return false;
}
/**
* Returns the public contact id of logged in user or false.
*
* @return int|bool public contact id or false
*/
function public_contact()
{
static $public_contact_id = false;
if (!$public_contact_id && !empty($_SESSION['authenticated'])) {
if (!empty($_SESSION['my_address'])) {
// Local user
$public_contact_id = intval(Contact::getIdForURL($_SESSION['my_address'], 0, false));
} elseif (!empty($_SESSION['visitor_home'])) {
// Remote user
$public_contact_id = intval(Contact::getIdForURL($_SESSION['visitor_home'], 0, false));
}
} elseif (empty($_SESSION['authenticated'])) {
$public_contact_id = false;
}
return $public_contact_id;
}
/**
* Returns public contact id of authenticated site visitor or false
*
* @return int|bool visitor_id or false
*/
function remote_user()
{
if (empty($_SESSION['authenticated'])) {
return false;
}
if (!empty($_SESSION['visitor_id'])) {
return intval($_SESSION['visitor_id']);
}
return false;
}
/**
* Show an error message to user.
*
* This function save text in session, to be shown to the user at next page load
*
* @param string $s - Text of notice
*
* @return void
* @deprecated since version 2022.09, use \Friendica\Navigation\SystemMessages instead
*/
function notice(string $s)
{
\Friendica\DI::sysmsg()->addNotice($s);
}
/**
* Show an info message to user.
*
* This function save text in session, to be shown to the user at next page load
*
* @param string $s - Text of notice
*
* @return void
* @deprecated since version 2022.09, use \Friendica\Navigation\SystemMessages instead
*/
function info(string $s)
{
\Friendica\DI::sysmsg()->addInfo($s);
}

View file

@ -41,15 +41,15 @@
"michelf/php-markdown": "^1.7",
"minishlink/web-push": "^6.0",
"mobiledetect/mobiledetectlib": "^2.8",
"monolog/monolog": "^1.25",
"nikic/fast-route": "^1.3",
"paragonie/hidden-string": "^1.0",
"patrickschur/language-detection": "^5.0.0",
"pear/console_table": "^1.3",
"phpseclib/phpseclib": "^2.0",
"phpseclib/phpseclib": "^3.0",
"pragmarx/google2fa": "^5.0",
"pragmarx/recovery": "^0.2",
"psr/container": "^1.0",
"psr/log": "^1.1",
"seld/cli-prompt": "^1.0",
"smarty/smarty": "^4",
"ua-parser/uap-php": "^3.9",
@ -59,7 +59,6 @@
"bower-asset/chart-js": "^2.8",
"bower-asset/dompurify": "^1.0",
"bower-asset/fork-awesome": "^1.1",
"bower-asset/vue": "^2.6",
"npm-asset/cropperjs": "1.2.2",
"npm-asset/es-jquery-sortable": "^0.9.13",
"npm-asset/fullcalendar": "^3.10",
@ -71,7 +70,8 @@
"npm-asset/moment": "^2.24",
"npm-asset/perfect-scrollbar": "0.6.16",
"npm-asset/textcomplete": "^0.18.2",
"npm-asset/typeahead.js": "^0.11.1"
"npm-asset/typeahead.js": "^0.11.1",
"kornrunner/blurhash": "^1.2"
},
"repositories": [
{
@ -83,10 +83,7 @@
"psr-4": {
"Friendica\\": "src/",
"Friendica\\Addon\\": "addon/"
},
"files": [
"boot.php"
]
}
},
"autoload-dev": {
"psr-4": {

168
composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "f5922f03b367e68a5930df6ed80c5c2f",
"content-hash": "f8e7baec685d20e6aee56978c275d64c",
"packages": [
{
"name": "asika/simple-console",
@ -241,22 +241,6 @@
],
"time": "2021-08-26T18:46:39+00:00"
},
{
"name": "bower-asset/vue",
"version": "v2.7.10",
"source": {
"type": "git",
"url": "https://github.com/vuejs/vue.git",
"reference": "ee57d9fd1d51abe245c6c37e6f8f2d45977b929e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/vuejs/vue/zipball/ee57d9fd1d51abe245c6c37e6f8f2d45977b929e",
"reference": "ee57d9fd1d51abe245c6c37e6f8f2d45977b929e",
"shasum": ""
},
"type": "bower-asset-library"
},
{
"name": "brick/math",
"version": "0.9.3",
@ -1132,6 +1116,50 @@
],
"time": "2022-06-20T21:43:03+00:00"
},
{
"name": "kornrunner/blurhash",
"version": "v1.2.2",
"source": {
"type": "git",
"url": "https://github.com/kornrunner/php-blurhash.git",
"reference": "bc8a4596cb0a49874f0158696a382ab3933fefe4"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/kornrunner/php-blurhash/zipball/bc8a4596cb0a49874f0158696a382ab3933fefe4",
"reference": "bc8a4596cb0a49874f0158696a382ab3933fefe4",
"shasum": ""
},
"require": {
"php": "^7.3|^8.0"
},
"require-dev": {
"ext-gd": "*",
"ocramius/package-versions": "^1.4|^2.0",
"phpstan/phpstan": "^0.12",
"phpunit/phpunit": "^9",
"vimeo/psalm": "^4.3"
},
"type": "library",
"autoload": {
"psr-4": {
"kornrunner\\Blurhash\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Boris Momčilović",
"email": "boris.momcilovic@gmail.com"
}
],
"description": "Pure PHP implementation of Blurhash",
"homepage": "https://github.com/kornrunner/php-blurhash",
"time": "2022-07-13T19:38:39+00:00"
},
{
"name": "league/html-to-markdown",
"version": "4.10.0",
@ -1543,88 +1571,6 @@
],
"time": "2022-02-17T19:24:25+00:00"
},
{
"name": "monolog/monolog",
"version": "1.27.1",
"source": {
"type": "git",
"url": "https://github.com/Seldaek/monolog.git",
"reference": "904713c5929655dc9b97288b69cfeedad610c9a1"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Seldaek/monolog/zipball/904713c5929655dc9b97288b69cfeedad610c9a1",
"reference": "904713c5929655dc9b97288b69cfeedad610c9a1",
"shasum": ""
},
"require": {
"php": ">=5.3.0",
"psr/log": "~1.0"
},
"provide": {
"psr/log-implementation": "1.0.0"
},
"require-dev": {
"aws/aws-sdk-php": "^2.4.9 || ^3.0",
"doctrine/couchdb": "~1.0@dev",
"graylog2/gelf-php": "~1.0",
"php-amqplib/php-amqplib": "~2.4",
"php-console/php-console": "^3.1.3",
"phpstan/phpstan": "^0.12.59",
"phpunit/phpunit": "~4.5",
"ruflin/elastica": ">=0.90 <3.0",
"sentry/sentry": "^0.13",
"swiftmailer/swiftmailer": "^5.3|^6.0"
},
"suggest": {
"aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB",
"doctrine/couchdb": "Allow sending log messages to a CouchDB server",
"ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)",
"ext-mongo": "Allow sending log messages to a MongoDB server",
"graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server",
"mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver",
"php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib",
"php-console/php-console": "Allow sending log messages to Google Chrome",
"rollbar/rollbar": "Allow sending log messages to Rollbar",
"ruflin/elastica": "Allow sending log messages to an Elastic Search server",
"sentry/sentry": "Allow sending log messages to a Sentry server"
},
"type": "library",
"autoload": {
"psr-4": {
"Monolog\\": "src/Monolog"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jordi Boggiano",
"email": "j.boggiano@seld.be",
"homepage": "http://seld.be"
}
],
"description": "Sends your logs to files, sockets, inboxes, databases and various web services",
"homepage": "http://github.com/Seldaek/monolog",
"keywords": [
"log",
"logging",
"psr-3"
],
"funding": [
{
"url": "https://github.com/Seldaek",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/monolog/monolog",
"type": "tidelift"
}
],
"time": "2022-06-09T08:53:42+00:00"
},
{
"name": "nikic/fast-route",
"version": "v1.3.0",
@ -3050,32 +2996,32 @@
},
{
"name": "phpseclib/phpseclib",
"version": "2.0.38",
"version": "3.0.17",
"source": {
"type": "git",
"url": "https://github.com/phpseclib/phpseclib.git",
"reference": "b03536539f43a4f9aa33c4f0b2f3a1c752088fcd"
"reference": "dbc2307d5c69aeb22db136c52e91130d7f2ca761"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/b03536539f43a4f9aa33c4f0b2f3a1c752088fcd",
"reference": "b03536539f43a4f9aa33c4f0b2f3a1c752088fcd",
"url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/dbc2307d5c69aeb22db136c52e91130d7f2ca761",
"reference": "dbc2307d5c69aeb22db136c52e91130d7f2ca761",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
"paragonie/constant_time_encoding": "^1|^2",
"paragonie/random_compat": "^1.4|^2.0|^9.99.99",
"php": ">=5.6.1"
},
"require-dev": {
"phing/phing": "~2.7",
"phpunit/phpunit": "^4.8.35|^5.7|^6.0|^9.4",
"squizlabs/php_codesniffer": "~2.0"
"phpunit/phpunit": "*"
},
"suggest": {
"ext-dom": "Install the DOM extension to load XML formatted public keys.",
"ext-gmp": "Install the GMP (GNU Multiple Precision) extension in order to speed up arbitrary precision integer arithmetic operations.",
"ext-libsodium": "SSH2/SFTP can make use of some algorithms provided by the libsodium-php extension.",
"ext-mcrypt": "Install the Mcrypt extension in order to speed up a few other cryptographic operations.",
"ext-openssl": "Install the OpenSSL extension in order to speed up a wide variety of cryptographic operations.",
"ext-xml": "Install the XML extension to load XML formatted public keys."
"ext-openssl": "Install the OpenSSL extension in order to speed up a wide variety of cryptographic operations."
},
"type": "library",
"autoload": {
@ -3083,7 +3029,7 @@
"phpseclib/bootstrap.php"
],
"psr-4": {
"phpseclib\\": "phpseclib/"
"phpseclib3\\": "phpseclib/"
}
},
"notification-url": "https://packagist.org/downloads/",
@ -3152,7 +3098,7 @@
"type": "tidelift"
}
],
"time": "2022-09-02T17:04:26+00:00"
"time": "2022-10-24T10:51:50+00:00"
},
{
"name": "pragmarx/google2fa",

View file

@ -1,12 +0,0 @@
<?php
// Addon configuration
// Copy this configuration file to addon.config.php and edit it if you want to configure addons, see below example for the twitter addon
return [
'twitter' => [
'consumerkey' => '1234567890',
'consumersecret' => 'ABCDEFGHIJKLMONPQRSTUVWXYZ',
],
];

View file

@ -1,6 +1,6 @@
-- ------------------------------------------
-- Friendica 2022.09-rc (Giant Rhubarb)
-- DB_UPDATE_VERSION 1484
-- Friendica 2022.12-rc (Giant Rhubarb)
-- DB_UPDATE_VERSION 1500
-- ------------------------------------------
@ -59,6 +59,7 @@ CREATE TABLE IF NOT EXISTS `user` (
`language` varchar(32) NOT NULL DEFAULT 'en' COMMENT 'default language',
`register_date` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT 'timestamp of registration',
`login_date` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT 'timestamp of last login',
`last-activity` date COMMENT 'Day of the last activity',
`default-location` varchar(255) NOT NULL DEFAULT '' COMMENT 'Default for item.location',
`allow_location` boolean NOT NULL DEFAULT '0' COMMENT '1 allows to display the location',
`theme` varchar(255) NOT NULL DEFAULT '' COMMENT 'user theme preference',
@ -80,7 +81,7 @@ CREATE TABLE IF NOT EXISTS `user` (
`pwdreset` varchar(255) COMMENT 'Password reset request token',
`pwdreset_time` datetime COMMENT 'Timestamp of the last password reset request',
`maxreq` int unsigned NOT NULL DEFAULT 10 COMMENT '',
`expire` int unsigned NOT NULL DEFAULT 0 COMMENT '',
`expire` int unsigned NOT NULL DEFAULT 0 COMMENT 'Delay in days before deleting user-related posts. Scope is controlled by pConfig.',
`account_removed` boolean NOT NULL DEFAULT '0' COMMENT 'if 1 the account is removed',
`account_expired` boolean NOT NULL DEFAULT '0' COMMENT '',
`account_expires_on` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT 'timestamp when account expires and will be deleted',
@ -309,6 +310,20 @@ CREATE TABLE IF NOT EXISTS `2fa_trusted_browser` (
FOREIGN KEY (`uid`) REFERENCES `user` (`uid`) ON UPDATE RESTRICT ON DELETE CASCADE
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Two-factor authentication trusted browsers';
--
-- TABLE account-suggestion
--
CREATE TABLE IF NOT EXISTS `account-suggestion` (
`uri-id` int unsigned NOT NULL COMMENT 'Id of the item-uri table entry that contains the account url',
`uid` mediumint unsigned NOT NULL COMMENT 'User ID',
`level` smallint unsigned COMMENT 'level of closeness',
`ignore` boolean NOT NULL DEFAULT '0' COMMENT 'If set, this account will not be suggested again',
PRIMARY KEY(`uid`,`uri-id`),
INDEX `uri-id_uid` (`uri-id`,`uid`),
FOREIGN KEY (`uri-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE,
FOREIGN KEY (`uid`) REFERENCES `user` (`uid`) ON UPDATE RESTRICT ON DELETE CASCADE
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Account suggestion';
--
-- TABLE account-user
--
@ -563,6 +578,40 @@ CREATE TABLE IF NOT EXISTS `delayed-post` (
FOREIGN KEY (`wid`) REFERENCES `workerqueue` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Posts that are about to be distributed at a later time';
--
-- TABLE diaspora-contact
--
CREATE TABLE IF NOT EXISTS `diaspora-contact` (
`uri-id` int unsigned NOT NULL COMMENT 'Id of the item-uri table entry that contains the contact URL',
`addr` varchar(255) COMMENT '',
`alias` varchar(255) COMMENT '',
`nick` varchar(255) COMMENT '',
`name` varchar(255) COMMENT '',
`given-name` varchar(255) COMMENT '',
`family-name` varchar(255) COMMENT '',
`photo` varchar(255) COMMENT '',
`photo-medium` varchar(255) COMMENT '',
`photo-small` varchar(255) COMMENT '',
`batch` varchar(255) COMMENT '',
`notify` varchar(255) COMMENT '',
`poll` varchar(255) COMMENT '',
`subscribe` varchar(255) COMMENT '',
`searchable` boolean COMMENT '',
`pubkey` text COMMENT '',
`gsid` int unsigned COMMENT 'Global Server ID',
`created` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT '',
`updated` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT '',
`interacting_count` int unsigned DEFAULT 0 COMMENT 'Number of contacts this contact interactes with',
`interacted_count` int unsigned DEFAULT 0 COMMENT 'Number of contacts that interacted with this contact',
`post_count` int unsigned DEFAULT 0 COMMENT 'Number of posts and comments',
PRIMARY KEY(`uri-id`),
UNIQUE INDEX `addr` (`addr`),
INDEX `alias` (`alias`),
INDEX `gsid` (`gsid`),
FOREIGN KEY (`uri-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE,
FOREIGN KEY (`gsid`) REFERENCES `gserver` (`id`) ON UPDATE RESTRICT ON DELETE RESTRICT
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Diaspora compatible contacts - used in the Diaspora implementation';
--
-- TABLE diaspora-interaction
--
@ -618,39 +667,6 @@ CREATE TABLE IF NOT EXISTS `event` (
FOREIGN KEY (`uri-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Events';
--
-- TABLE fcontact
--
CREATE TABLE IF NOT EXISTS `fcontact` (
`id` int unsigned NOT NULL auto_increment COMMENT 'sequential ID',
`guid` varbinary(255) NOT NULL DEFAULT '' COMMENT 'unique id',
`url` varbinary(383) NOT NULL DEFAULT '' COMMENT '',
`uri-id` int unsigned COMMENT 'Id of the item-uri table entry that contains the fcontact url',
`name` varchar(255) NOT NULL DEFAULT '' COMMENT '',
`photo` varbinary(383) NOT NULL DEFAULT '' COMMENT '',
`request` varbinary(383) NOT NULL DEFAULT '' COMMENT '',
`nick` varchar(255) NOT NULL DEFAULT '' COMMENT '',
`addr` varchar(255) NOT NULL DEFAULT '' COMMENT '',
`batch` varbinary(383) NOT NULL DEFAULT '' COMMENT '',
`notify` varbinary(383) NOT NULL DEFAULT '' COMMENT '',
`poll` varbinary(383) NOT NULL DEFAULT '' COMMENT '',
`confirm` varbinary(383) NOT NULL DEFAULT '' COMMENT '',
`priority` tinyint unsigned NOT NULL DEFAULT 0 COMMENT '',
`network` char(4) NOT NULL DEFAULT '' COMMENT '',
`alias` varbinary(383) NOT NULL DEFAULT '' COMMENT '',
`pubkey` text COMMENT '',
`created` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT '',
`updated` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT '',
`interacting_count` int unsigned DEFAULT 0 COMMENT 'Number of contacts this contact interactes with',
`interacted_count` int unsigned DEFAULT 0 COMMENT 'Number of contacts that interacted with this contact',
`post_count` int unsigned DEFAULT 0 COMMENT 'Number of posts and comments',
PRIMARY KEY(`id`),
INDEX `addr` (`addr`(32)),
UNIQUE INDEX `url` (`url`(190)),
UNIQUE INDEX `uri-id` (`uri-id`),
FOREIGN KEY (`uri-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Diaspora compatible contacts - used in the Diaspora implementation';
--
-- TABLE fetch-entry
--
@ -1073,6 +1089,7 @@ CREATE TABLE IF NOT EXISTS `photo` (
`height` smallint unsigned NOT NULL DEFAULT 0 COMMENT '',
`width` smallint unsigned NOT NULL DEFAULT 0 COMMENT '',
`datasize` int unsigned NOT NULL DEFAULT 0 COMMENT '',
`blurhash` varbinary(255) COMMENT 'BlurHash representation of the photo',
`data` mediumblob NOT NULL COMMENT '',
`scale` tinyint unsigned NOT NULL DEFAULT 0 COMMENT '',
`profile` boolean NOT NULL DEFAULT '0' COMMENT '',
@ -1186,6 +1203,7 @@ CREATE TABLE IF NOT EXISTS `post-content` (
`content-warning` varchar(255) NOT NULL DEFAULT '' COMMENT '',
`body` mediumtext COMMENT 'item body content',
`raw-body` mediumtext COMMENT 'Body without embedded media links',
`quote-uri-id` int unsigned COMMENT 'Id of the item-uri table that contains the quoted uri',
`location` varchar(255) NOT NULL DEFAULT '' COMMENT 'text location where this item originated',
`coord` varchar(255) NOT NULL DEFAULT '' COMMENT 'longitude/latitude pair representing location where this item originated',
`language` text COMMENT 'Language information about this post',
@ -1202,7 +1220,9 @@ CREATE TABLE IF NOT EXISTS `post-content` (
INDEX `plink` (`plink`(191)),
INDEX `resource-id` (`resource-id`),
FULLTEXT INDEX `title-content-warning-body` (`title`,`content-warning`,`body`),
FOREIGN KEY (`uri-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE
INDEX `quote-uri-id` (`quote-uri-id`),
FOREIGN KEY (`uri-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE,
FOREIGN KEY (`quote-uri-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Content for all posts';
--
@ -1289,11 +1309,13 @@ CREATE TABLE IF NOT EXISTS `post-media` (
`id` int unsigned NOT NULL auto_increment COMMENT 'sequential ID',
`uri-id` int unsigned NOT NULL COMMENT 'Id of the item-uri table entry that contains the item uri',
`url` varbinary(1024) NOT NULL COMMENT 'Media URL',
`media-uri-id` int unsigned COMMENT 'Id of the item-uri table entry that contains the activities uri-id',
`type` tinyint unsigned NOT NULL DEFAULT 0 COMMENT 'Media type',
`mimetype` varchar(60) COMMENT '',
`height` smallint unsigned COMMENT 'Height of the media',
`width` smallint unsigned COMMENT 'Width of the media',
`size` bigint unsigned COMMENT 'Media size',
`blurhash` varbinary(255) COMMENT 'BlurHash representation of the image',
`preview` varbinary(512) COMMENT 'Preview URL',
`preview-height` smallint unsigned COMMENT 'Height of the preview picture',
`preview-width` smallint unsigned COMMENT 'Width of the preview picture',
@ -1308,7 +1330,9 @@ CREATE TABLE IF NOT EXISTS `post-media` (
PRIMARY KEY(`id`),
UNIQUE INDEX `uri-id-url` (`uri-id`,`url`(512)),
INDEX `uri-id-id` (`uri-id`,`id`),
FOREIGN KEY (`uri-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE
INDEX `media-uri-id` (`media-uri-id`),
FOREIGN KEY (`uri-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE,
FOREIGN KEY (`media-uri-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Attached media';
--
@ -1571,6 +1595,7 @@ CREATE TABLE IF NOT EXISTS `profile` (
`education` text COMMENT 'Deprecated',
`contact` text COMMENT 'Deprecated',
`homepage` varchar(255) NOT NULL DEFAULT '' COMMENT '',
`homepage_verified` boolean NOT NULL DEFAULT '0' COMMENT 'was the homepage verified by a rel-me link back to the profile',
`xmpp` varchar(255) NOT NULL DEFAULT '' COMMENT 'XMPP address',
`matrix` varchar(255) NOT NULL DEFAULT '' COMMENT 'Matrix address',
`photo` varbinary(383) NOT NULL DEFAULT '' COMMENT '',
@ -1639,6 +1664,37 @@ CREATE TABLE IF NOT EXISTS `register` (
FOREIGN KEY (`uid`) REFERENCES `user` (`uid`) ON UPDATE RESTRICT ON DELETE CASCADE
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='registrations requiring admin approval';
--
-- TABLE report
--
CREATE TABLE IF NOT EXISTS `report` (
`id` int unsigned NOT NULL auto_increment COMMENT 'sequential ID',
`uid` mediumint unsigned COMMENT 'Reporting user',
`cid` int unsigned NOT NULL COMMENT 'Reported contact',
`comment` text COMMENT 'Report',
`forward` boolean COMMENT 'Forward the report to the remote server',
`created` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT '',
`status` tinyint unsigned COMMENT 'Status of the report',
PRIMARY KEY(`id`),
INDEX `uid` (`uid`),
INDEX `cid` (`cid`),
FOREIGN KEY (`uid`) REFERENCES `user` (`uid`) ON UPDATE RESTRICT ON DELETE CASCADE,
FOREIGN KEY (`cid`) REFERENCES `contact` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='';
--
-- TABLE report-post
--
CREATE TABLE IF NOT EXISTS `report-post` (
`rid` int unsigned NOT NULL COMMENT 'Report id',
`uri-id` int unsigned NOT NULL COMMENT 'Uri-id of the reported post',
`status` tinyint unsigned COMMENT 'Status of the reported post',
PRIMARY KEY(`rid`,`uri-id`),
INDEX `uri-id` (`uri-id`),
FOREIGN KEY (`rid`) REFERENCES `report` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE,
FOREIGN KEY (`uri-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='';
--
-- TABLE search
--
@ -1807,6 +1863,8 @@ CREATE VIEW `post-user-view` AS SELECT
`post-user`.`thr-parent-id` AS `thr-parent-id`,
`conversation-item-uri`.`uri` AS `conversation`,
`post-thread-user`.`conversation-id` AS `conversation-id`,
`quote-item-uri`.`uri` AS `quote-uri`,
`post-content`.`quote-uri-id` AS `quote-uri-id`,
`item-uri`.`guid` AS `guid`,
`post-user`.`wall` AS `wall`,
`post-user`.`gravity` AS `gravity`,
@ -1944,9 +2002,7 @@ CREATE VIEW `post-user-view` AS SELECT
`parent-post-author`.`url` AS `parent-author-link`,
`parent-post-author`.`name` AS `parent-author-name`,
`parent-post-author`.`nick` AS `parent-author-nick`,
`parent-post-author`.`network` AS `parent-author-network`,
`parent-post-author`.`blocked` AS `parent-author-blocked`,
`parent-post-author`.`hidden` AS `parent-author-hidden`
`parent-post-author`.`network` AS `parent-author-network`
FROM `post-user`
STRAIGHT_JOIN `post-thread-user` ON `post-thread-user`.`uri-id` = `post-user`.`parent-uri-id` AND `post-thread-user`.`uid` = `post-user`.`uid`
STRAIGHT_JOIN `contact` ON `contact`.`id` = `post-user`.`contact-id`
@ -1962,6 +2018,7 @@ CREATE VIEW `post-user-view` AS SELECT
LEFT JOIN `event` ON `event`.`id` = `post-user`.`event-id`
LEFT JOIN `diaspora-interaction` ON `diaspora-interaction`.`uri-id` = `post-user`.`uri-id`
LEFT JOIN `post-content` ON `post-content`.`uri-id` = `post-user`.`uri-id`
LEFT JOIN `item-uri` AS `quote-item-uri` ON `quote-item-uri`.`id` = `post-content`.`quote-uri-id`
LEFT JOIN `post-delivery-data` ON `post-delivery-data`.`uri-id` = `post-user`.`uri-id` AND `post-user`.`origin`
LEFT JOIN `post-question` ON `post-question`.`uri-id` = `post-user`.`uri-id`
LEFT JOIN `permissionset` ON `permissionset`.`id` = `post-user`.`psid`
@ -1985,6 +2042,8 @@ CREATE VIEW `post-thread-user-view` AS SELECT
`post-user`.`thr-parent-id` AS `thr-parent-id`,
`conversation-item-uri`.`uri` AS `conversation`,
`post-thread-user`.`conversation-id` AS `conversation-id`,
`quote-item-uri`.`uri` AS `quote-uri`,
`post-content`.`quote-uri-id` AS `quote-uri-id`,
`item-uri`.`guid` AS `guid`,
`post-thread-user`.`wall` AS `wall`,
`post-user`.`gravity` AS `gravity`,
@ -2120,9 +2179,7 @@ CREATE VIEW `post-thread-user-view` AS SELECT
`parent-post`.`author-id` AS `parent-author-id`,
`parent-post-author`.`url` AS `parent-author-link`,
`parent-post-author`.`name` AS `parent-author-name`,
`parent-post-author`.`network` AS `parent-author-network`,
`parent-post-author`.`blocked` AS `parent-author-blocked`,
`parent-post-author`.`hidden` AS `parent-author-hidden`
`parent-post-author`.`network` AS `parent-author-network`
FROM `post-thread-user`
INNER JOIN `post-user` ON `post-user`.`id` = `post-thread-user`.`post-user-id`
STRAIGHT_JOIN `contact` ON `contact`.`id` = `post-thread-user`.`contact-id`
@ -2138,6 +2195,7 @@ CREATE VIEW `post-thread-user-view` AS SELECT
LEFT JOIN `event` ON `event`.`id` = `post-user`.`event-id`
LEFT JOIN `diaspora-interaction` ON `diaspora-interaction`.`uri-id` = `post-thread-user`.`uri-id`
LEFT JOIN `post-content` ON `post-content`.`uri-id` = `post-thread-user`.`uri-id`
LEFT JOIN `item-uri` AS `quote-item-uri` ON `quote-item-uri`.`id` = `post-content`.`quote-uri-id`
LEFT JOIN `post-delivery-data` ON `post-delivery-data`.`uri-id` = `post-thread-user`.`uri-id` AND `post-thread-user`.`origin`
LEFT JOIN `post-question` ON `post-question`.`uri-id` = `post-thread-user`.`uri-id`
LEFT JOIN `permissionset` ON `permissionset`.`id` = `post-thread-user`.`psid`
@ -2157,6 +2215,8 @@ CREATE VIEW `post-view` AS SELECT
`post`.`thr-parent-id` AS `thr-parent-id`,
`conversation-item-uri`.`uri` AS `conversation`,
`post-thread`.`conversation-id` AS `conversation-id`,
`quote-item-uri`.`uri` AS `quote-uri`,
`post-content`.`quote-uri-id` AS `quote-uri-id`,
`item-uri`.`guid` AS `guid`,
`post`.`gravity` AS `gravity`,
`external-item-uri`.`uri` AS `extid`,
@ -2263,9 +2323,7 @@ CREATE VIEW `post-view` AS SELECT
`parent-post`.`author-id` AS `parent-author-id`,
`parent-post-author`.`url` AS `parent-author-link`,
`parent-post-author`.`name` AS `parent-author-name`,
`parent-post-author`.`network` AS `parent-author-network`,
`parent-post-author`.`blocked` AS `parent-author-blocked`,
`parent-post-author`.`hidden` AS `parent-author-hidden`
`parent-post-author`.`network` AS `parent-author-network`
FROM `post`
STRAIGHT_JOIN `post-thread` ON `post-thread`.`uri-id` = `post`.`parent-uri-id`
STRAIGHT_JOIN `contact` AS `author` ON `author`.`id` = `post`.`author-id`
@ -2279,6 +2337,7 @@ CREATE VIEW `post-view` AS SELECT
LEFT JOIN `verb` ON `verb`.`id` = `post`.`vid`
LEFT JOIN `diaspora-interaction` ON `diaspora-interaction`.`uri-id` = `post`.`uri-id`
LEFT JOIN `post-content` ON `post-content`.`uri-id` = `post`.`uri-id`
LEFT JOIN `item-uri` AS `quote-item-uri` ON `quote-item-uri`.`id` = `post-content`.`quote-uri-id`
LEFT JOIN `post-question` ON `post-question`.`uri-id` = `post`.`uri-id`
LEFT JOIN `post` AS `parent-post` ON `parent-post`.`uri-id` = `post`.`parent-uri-id`
LEFT JOIN `contact` AS `parent-post-author` ON `parent-post-author`.`id` = `parent-post`.`author-id`;
@ -2296,6 +2355,8 @@ CREATE VIEW `post-thread-view` AS SELECT
`post`.`thr-parent-id` AS `thr-parent-id`,
`conversation-item-uri`.`uri` AS `conversation`,
`post-thread`.`conversation-id` AS `conversation-id`,
`quote-item-uri`.`uri` AS `quote-uri`,
`post-content`.`quote-uri-id` AS `quote-uri-id`,
`item-uri`.`guid` AS `guid`,
`post`.`gravity` AS `gravity`,
`external-item-uri`.`uri` AS `extid`,
@ -2396,15 +2457,15 @@ CREATE VIEW `post-thread-view` AS SELECT
`post-question`.`end-time` AS `question-end-time`,
0 AS `has-categories`,
EXISTS(SELECT `id` FROM `post-media` WHERE `post-media`.`uri-id` = `post-thread`.`uri-id`) AS `has-media`,
(SELECT COUNT(*) FROM `post` WHERE `parent-uri-id` = `post-thread`.`uri-id` AND `gravity` = 6) AS `total-comments`,
(SELECT COUNT(DISTINCT(`author-id`)) FROM `post` WHERE `parent-uri-id` = `post-thread`.`uri-id` AND `gravity` = 6) AS `total-actors`,
`diaspora-interaction`.`interaction` AS `signed_text`,
`parent-item-uri`.`guid` AS `parent-guid`,
`parent-post`.`network` AS `parent-network`,
`parent-post`.`author-id` AS `parent-author-id`,
`parent-post-author`.`url` AS `parent-author-link`,
`parent-post-author`.`name` AS `parent-author-name`,
`parent-post-author`.`network` AS `parent-author-network`,
`parent-post-author`.`blocked` AS `parent-author-blocked`,
`parent-post-author`.`hidden` AS `parent-author-hidden`
`parent-post-author`.`network` AS `parent-author-network`
FROM `post-thread`
INNER JOIN `post` ON `post`.`uri-id` = `post-thread`.`uri-id`
STRAIGHT_JOIN `contact` AS `author` ON `author`.`id` = `post-thread`.`author-id`
@ -2418,6 +2479,7 @@ CREATE VIEW `post-thread-view` AS SELECT
LEFT JOIN `verb` ON `verb`.`id` = `post`.`vid`
LEFT JOIN `diaspora-interaction` ON `diaspora-interaction`.`uri-id` = `post-thread`.`uri-id`
LEFT JOIN `post-content` ON `post-content`.`uri-id` = `post-thread`.`uri-id`
LEFT JOIN `item-uri` AS `quote-item-uri` ON `quote-item-uri`.`id` = `post-content`.`quote-uri-id`
LEFT JOIN `post-question` ON `post-question`.`uri-id` = `post-thread`.`uri-id`
LEFT JOIN `post` AS `parent-post` ON `parent-post`.`uri-id` = `post`.`parent-uri-id`
LEFT JOIN `contact` AS `parent-post-author` ON `parent-post-author`.`id` = `parent-post`.`author-id`;
@ -2457,6 +2519,24 @@ CREATE VIEW `collection-view` AS SELECT
INNER JOIN `post` ON `post-collection`.`uri-id` = `post`.`uri-id`
INNER JOIN `post-thread` ON `post-thread`.`uri-id` = `post`.`parent-uri-id`;
--
-- VIEW media-view
--
DROP VIEW IF EXISTS `media-view`;
CREATE VIEW `media-view` AS SELECT
`post-media`.`uri-id` AS `uri-id`,
`post-media`.`type` AS `type`,
`post`.`received` AS `received`,
`post`.`created` AS `created`,
`post`.`private` AS `private`,
`post`.`visible` AS `visible`,
`post`.`deleted` AS `deleted`,
`post`.`thr-parent-id` AS `thr-parent-id`,
`post`.`author-id` AS `author-id`,
`post`.`gravity` AS `gravity`
FROM `post-media`
INNER JOIN `post` ON `post-media`.`uri-id` = `post`.`uri-id`;
--
-- VIEW tag-view
--
@ -2616,6 +2696,7 @@ CREATE VIEW `owner-view` AS SELECT
`user`.`language` AS `language`,
`user`.`register_date` AS `register_date`,
`user`.`login_date` AS `login_date`,
`user`.`last-activity` AS `last-activity`,
`user`.`default-location` AS `default-location`,
`user`.`allow_location` AS `allow_location`,
`user`.`theme` AS `theme`,
@ -2656,6 +2737,7 @@ CREATE VIEW `owner-view` AS SELECT
`profile`.`postal-code` AS `postal-code`,
`profile`.`country-name` AS `country-name`,
`profile`.`homepage` AS `homepage`,
`profile`.`homepage_verified` AS `homepage_verified`,
`profile`.`dob` AS `dob`
FROM `user`
INNER JOIN `contact` ON `contact`.`uid` = `user`.`uid` AND `contact`.`self`
@ -2721,11 +2803,11 @@ CREATE VIEW `account-view` AS SELECT
`contact`.`blocked` AS `blocked`,
`contact`.`notify` AS `dfrn-notify`,
`contact`.`poll` AS `dfrn-poll`,
`fcontact`.`guid` AS `diaspora-guid`,
`fcontact`.`batch` AS `diaspora-batch`,
`fcontact`.`notify` AS `diaspora-notify`,
`fcontact`.`poll` AS `diaspora-poll`,
`fcontact`.`alias` AS `diaspora-alias`,
`item-uri`.`guid` AS `diaspora-guid`,
`diaspora-contact`.`batch` AS `diaspora-batch`,
`diaspora-contact`.`notify` AS `diaspora-notify`,
`diaspora-contact`.`poll` AS `diaspora-poll`,
`diaspora-contact`.`alias` AS `diaspora-alias`,
`apcontact`.`uuid` AS `ap-uuid`,
`apcontact`.`type` AS `ap-type`,
`apcontact`.`following` AS `ap-following`,
@ -2743,7 +2825,7 @@ CREATE VIEW `account-view` AS SELECT
FROM `contact`
LEFT JOIN `item-uri` ON `item-uri`.`id` = `contact`.`uri-id`
LEFT JOIN `apcontact` ON `apcontact`.`uri-id` = `contact`.`uri-id`
LEFT JOIN `fcontact` ON `fcontact`.`uri-id` = contact.`uri-id`
LEFT JOIN `diaspora-contact` ON `diaspora-contact`.`uri-id` = contact.`uri-id`
LEFT JOIN `gserver` ON `gserver`.`id` = contact.`gsid`
WHERE `contact`.`uid` = 0;
@ -2822,14 +2904,14 @@ CREATE VIEW `account-user-view` AS SELECT
`ucontact`.`reason` AS `reason`,
`contact`.`notify` AS `dfrn-notify`,
`contact`.`poll` AS `dfrn-poll`,
`fcontact`.`guid` AS `diaspora-guid`,
`fcontact`.`batch` AS `diaspora-batch`,
`fcontact`.`notify` AS `diaspora-notify`,
`fcontact`.`poll` AS `diaspora-poll`,
`fcontact`.`alias` AS `diaspora-alias`,
`fcontact`.`interacting_count` AS `diaspora-interacting_count`,
`fcontact`.`interacted_count` AS `diaspora-interacted_count`,
`fcontact`.`post_count` AS `diaspora-post_count`,
`item-uri`.`guid` AS `diaspora-guid`,
`diaspora-contact`.`batch` AS `diaspora-batch`,
`diaspora-contact`.`notify` AS `diaspora-notify`,
`diaspora-contact`.`poll` AS `diaspora-poll`,
`diaspora-contact`.`alias` AS `diaspora-alias`,
`diaspora-contact`.`interacting_count` AS `diaspora-interacting_count`,
`diaspora-contact`.`interacted_count` AS `diaspora-interacted_count`,
`diaspora-contact`.`post_count` AS `diaspora-post_count`,
`apcontact`.`uuid` AS `ap-uuid`,
`apcontact`.`type` AS `ap-type`,
`apcontact`.`following` AS `ap-following`,
@ -2848,7 +2930,7 @@ CREATE VIEW `account-user-view` AS SELECT
INNER JOIN `contact` ON `contact`.`uri-id` = `ucontact`.`uri-id` AND `contact`.`uid` = 0
LEFT JOIN `item-uri` ON `item-uri`.`id` = `ucontact`.`uri-id`
LEFT JOIN `apcontact` ON `apcontact`.`uri-id` = `ucontact`.`uri-id`
LEFT JOIN `fcontact` ON `fcontact`.`uri-id` = `ucontact`.`uri-id` AND `fcontact`.`network` = 'dspr'
LEFT JOIN `diaspora-contact` ON `diaspora-contact`.`uri-id` = `ucontact`.`uri-id`
LEFT JOIN `gserver` ON `gserver`.`id` = contact.`gsid`;
--
@ -2925,3 +3007,37 @@ CREATE VIEW `profile_field-view` AS SELECT
`profile_field`.`edited` AS `edited`
FROM `profile_field`
INNER JOIN `permissionset` ON `permissionset`.`id` = `profile_field`.`psid`;
--
-- VIEW diaspora-contact-view
--
DROP VIEW IF EXISTS `diaspora-contact-view`;
CREATE VIEW `diaspora-contact-view` AS SELECT
`diaspora-contact`.`uri-id` AS `uri-id`,
`item-uri`.`uri` AS `url`,
`item-uri`.`guid` AS `guid`,
`diaspora-contact`.`addr` AS `addr`,
`diaspora-contact`.`alias` AS `alias`,
`diaspora-contact`.`nick` AS `nick`,
`diaspora-contact`.`name` AS `name`,
`diaspora-contact`.`given-name` AS `given-name`,
`diaspora-contact`.`family-name` AS `family-name`,
`diaspora-contact`.`photo` AS `photo`,
`diaspora-contact`.`photo-medium` AS `photo-medium`,
`diaspora-contact`.`photo-small` AS `photo-small`,
`diaspora-contact`.`batch` AS `batch`,
`diaspora-contact`.`notify` AS `notify`,
`diaspora-contact`.`poll` AS `poll`,
`diaspora-contact`.`subscribe` AS `subscribe`,
`diaspora-contact`.`searchable` AS `searchable`,
`diaspora-contact`.`pubkey` AS `pubkey`,
`gserver`.`url` AS `baseurl`,
`diaspora-contact`.`gsid` AS `gsid`,
`diaspora-contact`.`created` AS `created`,
`diaspora-contact`.`updated` AS `updated`,
`diaspora-contact`.`interacting_count` AS `interacting_count`,
`diaspora-contact`.`interacted_count` AS `interacted_count`,
`diaspora-contact`.`post_count` AS `post_count`
FROM `diaspora-contact`
INNER JOIN `item-uri` ON `item-uri`.`id` = `diaspora-contact`.`uri-id`
LEFT JOIN `gserver` ON `gserver`.`id` = `diaspora-contact`.`gsid`;

View file

@ -47,6 +47,7 @@ These endpoints use the [Mastodon API entities](https://docs.joinmastodon.org/en
- [`POST /api/v1/accounts/:id/unmute`](https://docs.joinmastodon.org/methods/accounts/)
- [`GET /api/v1/accounts/relationships`](https://docs.joinmastodon.org/methods/accounts/)
- [`GET /api/v1/accounts/search`](https://docs.joinmastodon.org/methods/accounts)
- [`PATCH /api/v1/accounts/update_credentials`](https://docs.joinmastodon.org/methods/accounts/#update_credentials)
- [`GET /api/v1/accounts/verify_credentials`](https://docs.joinmastodon.org/methods/accounts)
- [`POST /api/v1/apps`](https://docs.joinmastodon.org/methods/apps/)
- [`GET /api/v1/apps/verify_credentials`](https://docs.joinmastodon.org/methods/apps/)
@ -72,7 +73,7 @@ These endpoints use the [Mastodon API entities](https://docs.joinmastodon.org/en
- `:id` is a follow request ID, not a regular account id
- Returns a [Relationship](https://docs.joinmastodon.org/entities/relationship) object.
- [`GET /api/v1/followed_tags'](https://docs.joinmastodon.org/methods/followed_tags/)
- [`GET /api/v1/instance`](https://docs.joinmastodon.org/methods/instance#fetch-instance)
- `GET /api/v1/instance/rules` Undocumented, returns Terms of Service
- [`GET /api/v1/instance/peers`](https://docs.joinmastodon.org/methods/instance#list-of-connected-domains)
@ -100,35 +101,44 @@ These endpoints use the [Mastodon API entities](https://docs.joinmastodon.org/en
- [`GET /api/v1/push/subscription`](https://docs.joinmastodon.org/methods/notifications/push/)
- [`PUSH /api/v1/push/subscription`](https://docs.joinmastodon.org/methods/notifications/push/)
- [`PUT /api/v1/push/subscription`](https://docs.joinmastodon.org/methods/notifications/push/)
- [`POST /api/v1/reports`](https://docs.joinmastodon.org/methods/accounts/reports/)
- [`GET /api/v1/scheduled_statuses`](https://docs.joinmastodon.org/methods/statuses/scheduled_statuses/)
- [`DELETE /api/v1/scheduled_statuses/:id`](https://docs.joinmastodon.org/methods/statuses/scheduled_statuses/)
- [`GET /api/v1/scheduled_statuses/:id`](https://docs.joinmastodon.org/methods/statuses/scheduled_statuses/)
- [`GET /api/v1/search`](https://docs.joinmastodon.org/methods/search/)
- [`POST /api/v1/statuses`](https://docs.joinmastodon.org/methods/statuses/)
- [`POST /api/v1/statuses`](https://docs.joinmastodon.org/methods/statuses/#create)
- Additionally to the static values `public`, `unlisted` and `private`, the `visibility` parameter can contain a numeric value with a group id.
- [`GET /api/v1/statuses/:id`](https://docs.joinmastodon.org/methods/statuses/)
- [`DELETE /api/v1/statuses/:id`](https://docs.joinmastodon.org/methods/statuses/)
- [`GET /api/v1/statuses/:id/card`](https://docs.joinmastodon.org/methods/statuses/)
- [`GET /api/v1/statuses/:id/context`](https://docs.joinmastodon.org/methods/statuses/)
- [`GET /api/v1/statuses/:id/reblogged_by`](https://docs.joinmastodon.org/methods/statuses/)
- [`GET /api/v1/statuses/:id/favourited_by`](https://docs.joinmastodon.org/methods/statuses/)
- [`POST /api/v1/statuses/:id/favourite`](https://docs.joinmastodon.org/methods/statuses/)
- [`POST /api/v1/statuses/:id/unfavourite`](https://docs.joinmastodon.org/methods/statuses/)
- [`POST /api/v1/statuses/:id/reblog`](https://docs.joinmastodon.org/methods/statuses/)
- [`POST /api/v1/statuses/:id/unreblog`](https://docs.joinmastodon.org/methods/statuses/)
- [`POST /api/v1/statuses/:id/bookmark`](https://docs.joinmastodon.org/methods/statuses/)
- [`POST /api/v1/statuses/:id/unbookmark`](https://docs.joinmastodon.org/methods/statuses/)
- [`POST /api/v1/statuses/:id/mute`](https://docs.joinmastodon.org/methods/statuses/)
- [`POST /api/v1/statuses/:id/unmute`](https://docs.joinmastodon.org/methods/statuses/)
- [`POST /api/v1/statuses/:id/pin`](https://docs.joinmastodon.org/methods/statuses/)
- [`POST /api/v1/statuses/:id/unpin`](https://docs.joinmastodon.org/methods/statuses/)
- [`GET /api/v1/statuses/:id`](https://docs.joinmastodon.org/methods/statuses/#get)
- [`DELETE /api/v1/statuses/:id`](https://docs.joinmastodon.org/methods/statuses/#delete)
- [`GET /api/v1/statuses/:id/context`](https://docs.joinmastodon.org/methods/statuses/#context)
- [`GET /api/v1/statuses/:id/reblogged_by`](https://docs.joinmastodon.org/methods/statuses/#reblogged_by)
- [`GET /api/v1/statuses/:id/favourited_by`](https://docs.joinmastodon.org/methods/statuses/#favourited_by)
- [`POST /api/v1/statuses/:id/favourite`](https://docs.joinmastodon.org/methods/statuses/#favourite)
- [`POST /api/v1/statuses/:id/unfavourite`](https://docs.joinmastodon.org/methods/statuses/#unfavourite)
- [`POST /api/v1/statuses/:id/reblog`](https://docs.joinmastodon.org/methods/statuses/#boost)
- [`POST /api/v1/statuses/:id/unreblog`](https://docs.joinmastodon.org/methods/statuses/#unreblog)
- [`POST /api/v1/statuses/:id/bookmark`](https://docs.joinmastodon.org/methods/statuses/#bookmark)
- [`POST /api/v1/statuses/:id/unbookmark`](https://docs.joinmastodon.org/methods/statuses/#unbookmark)
- [`POST /api/v1/statuses/:id/mute`](https://docs.joinmastodon.org/methods/statuses/#mute)
- [`POST /api/v1/statuses/:id/unmute`](https://docs.joinmastodon.org/methods/statuses/#unmute)
- [`POST /api/v1/statuses/:id/pin`](https://docs.joinmastodon.org/methods/statuses/#pin)
- [`POST /api/v1/statuses/:id/unpin`](https://docs.joinmastodon.org/methods/statuses/#unpin)
- [`POST /api/v1/statuses/:id`](https://docs.joinmastodon.org/methods/statuses/#edit)
- [`GET /api/v1/statuses/:id/source`](https://docs.joinmastodon.org/methods/statuses/#source)
- [`GET /api/v1/statuses/:id/card`](https://docs.joinmastodon.org/methods/statuses/#card)
- [`GET /api/v1/suggestions`](https://docs.joinmastodon.org/methods/accounts/suggestions/)
- [`GET /api/v1/tags/:id`](https://docs.joinmastodon.org/methods/tags/#get)
- [`GET /api/v1/tags/:id/follow`](https://docs.joinmastodon.org/methods/tags/#follow)
- [`GET /api/v1/tags/:id/unfollow`](https://docs.joinmastodon.org/methods/tags/#unfollow)
- [`GET /api/v1/timelines/direct`](https://docs.joinmastodon.org/methods/timelines/)
- [`GET /api/v1/timelines/home`](https://docs.joinmastodon.org/methods/timelines/)
- [`GET /api/v1/timelines/list/:id`](https://docs.joinmastodon.org/methods/timelines/)
- [`GET /api/v1/timelines/public`](https://docs.joinmastodon.org/methods/timelines/)
- [`GET /api/v1/timelines/tag/:hashtag`](https://docs.joinmastodon.org/methods/timelines/)
- [`GET /api/v1/trends`](https://docs.joinmastodon.org/methods/instance/trends/)
- [`GET /api/v1/trends/links`](https://github.com/mastodon/mastodon/pull/16917)
- [`GET /api/v1/trends/statuses`](https://docs.joinmastodon.org/methods/trends/#statuses)
- [`GET /api/v1/trends/tags`](https://docs.joinmastodon.org/methods/trends/#tags)
- [`GET /api/v2/search`](https://docs.joinmastodon.org/methods/search/)
@ -136,15 +146,10 @@ These endpoints use the [Mastodon API entities](https://docs.joinmastodon.org/en
These emdpoints are planned to be implemented somewhere in the future.
- [`PATCH /api/v1/accounts/update_credentials`](https://docs.joinmastodon.org/methods/accounts/)
- [`POST /api/v1/accounts/:id/remove_from_followers`](https://github.com/mastodon/mastodon/pull/16864)
- [`GET /api/v1/accounts/familiar_followers`](https://github.com/mastodon/mastodon/pull/17700)
- [`GET /api/v1/accounts/lookup`](https://github.com/mastodon/mastodon/pull/15740)
- [`GET /api/v1/trends/links`](https://github.com/mastodon/mastodon/pull/16917)
- [`GET /api/v1/trends/statuses`](https://github.com/mastodon/mastodon/pull/17431)
- [`GET /api/v1/trends/tags`](https://github.com/mastodon/mastodon/pull/16917)
- [`POST /api/v1/polls/:id/votes`](https://docs.joinmastodon.org/methods/statuses/polls/)
- [`GET /api/v1/statuses/{id:\d+}/source`](https://github.com/mastodon/mastodon/pull/16697)
- [`GET /api/v1/featured_tags`](https://docs.joinmastodon.org/methods/accounts/featured_tags/)
- [`POST /api/v1/featured_tags`](https://docs.joinmastodon.org/methods/accounts/featured_tags/)
- [`DELETE /api/v1/featured_tags/:id`](https://docs.joinmastodon.org/methods/accounts/featured_tags/)
@ -189,7 +194,6 @@ They refer to features or data that don't exist in Friendica yet.
- [`DELETE /api/v1/filters/:id`](https://docs.joinmastodon.org/methods/accounts/filters/)
- [`GET /api/v1/instance/activity`](https://docs.joinmastodon.org/methods/instance#weekly-activity)
- [`POST /api/v1/markers`](https://docs.joinmastodon.org/methods/timelines/markers/)
- [`POST /api/v1/reports`](https://docs.joinmastodon.org/methods/accounts/reports/)
- [`PUT /api/v1/scheduled_statuses/:id`](https://docs.joinmastodon.org/methods/statuses/scheduled_statuses/)
- [`GET /api/v1/statuses/{id:\d+}/history`](https://github.com/mastodon/mastodon/pull/16697)
- [`GET /api/v1/streaming`](https://docs.joinmastodon.org/methods/timelines/streaming/)

View file

@ -19,6 +19,7 @@ General
* c - Community
* s - Search
* a - Admin
* m - Moderation
* f - Notifications
* u - User menu

View file

@ -281,7 +281,7 @@ $data = [
'submit' => [
'catavatar-usecat' => DI::l10n()->t('Use Cat as Avatar'),
'catavatar-morecat' => DI::l10n()->t('Another random Cat!'),
'catavatar-emailcat' => DI::pConfig()->get(local_user(), 'catavatar', 'seed', false) ? DI::l10n()->t('Reset to email Cat') : null,
'catavatar-emailcat' => DI::pConfig()->get(Session::getLocalUser(), 'catavatar', 'seed', false) ? DI::l10n()->t('Reset to email Cat') : null,
],
];
```
@ -790,10 +790,6 @@ Here is a complete list of all hook callbacks with file locations (as of 24-Sep-
Hook::callAll('post_local', $datarray);
Hook::callAll('post_local_end', $datarray);
### mod/editpost.php
Hook::callAll('jot_tool', $jotplugins);
### src/Render/FriendicaSmartyEngine.php
Hook::callAll("template_vars", $arr);
@ -855,6 +851,10 @@ Here is a complete list of all hook callbacks with file locations (as of 24-Sep-
Hook::callAll('lockview_content', $item);
### src/Module/Post/Edit.php
Hook::callAll('jot_tool', $jotplugins);
### src/Module/Settings/Delegation.php
Hook::callAll('authenticate', $addon_auth);
@ -919,6 +919,10 @@ Here is a complete list of all hook callbacks with file locations (as of 24-Sep-
Hook::callAll('block', $hook_data);
Hook::callAll('unblock', $hook_data);
### src/Core/Logger/Factory.php
Hook::callAll('logger_instance', $data);
### src/Core/StorageManager
Hook::callAll('storage_instance', $data);

View file

@ -6,7 +6,7 @@ Using Composer
Friendica uses [Composer](https://getcomposer.org) to manage dependencies libraries and the class autoloader both for libraries and namespaced Friendica classes.
It's a command-line tool that downloads required libraries into the `vendor` folder and makes any namespaced class in `src` available through the whole application through `boot.php`.
It's a command-line tool that downloads required libraries into the `vendor` folder and makes any namespaced class in `src` available through the whole application.
* [Class autoloading](help/autoloader)

View file

@ -37,8 +37,8 @@ The `config` directory holds key configuration files and can have different conf
All of them have to end with `.config.php` and must not include `-sample` in their name.
Some examples of common known configuration files:
- `local.config.php` holds the current node custom configuration.
- `addon.config.php` is optional and holds the custom configuration for specific addons.
- `local.config.php` holds the base node custom configuration.
- Any other file in this folder is meant for additional configuration (e.g. for addons).
Addons can define their own default configuration values in `addon/[addon]/config/[addon].config.php` which is loaded when the addon is activated.
@ -59,7 +59,7 @@ Currently, the following configurations are included:
The legacy `.htconfig.php` configuration file is still supported, but is deprecated and will be removed in a subsequent Friendica release.
The migration is pretty straightforward:
If you had any addon-specific configuration in your `.htconfig.php`, just copy `config/addon-sample.config.php` to `config/addon.config.php` and move your configuration values.
If you had any addon-specific configuration in your `.htconfig.php`, copy `config/local-sample.config.php` to `config/addon.config.php` and move your configuration values.
Afterwards, copy `config/local-sample.config.php` to `config/local.config.php`, move the remaining configuration values to it according to the following conversion chart, then rename your `.htconfig.php` to check your node is working as expected before deleting it.
<style>
@ -206,7 +206,7 @@ $lang = "value";
The legacy `config/local.ini.php` configuration file is still supported, but is deprecated and will be removed in a subsequent Friendica release.
The migration is pretty straightforward:
If you had any addon-specific configuration in your `config/addon.ini.php`, just copy `config/addon-sample.config.php` to `config/addon.config.php` and move your configuration values.
If you had any addon-specific configuration in your `config/addon.ini.php`, copy `config/local-sample.config.php` to `config/addon.config.php` and move your configuration values.
Afterwards, copy `config/local-sample.config.php` to `config/local.config.php`, move the remaining configuration values to it according to the following conversion chart, then rename your `config/local.ini.php` file to check your node is working as expected before deleting it.
<table class="config">
@ -278,16 +278,16 @@ key[] = value3
### Database Settings
The configuration variables database.hostname, database.username, database.password, database.database and database.charset are holding your credentials for the database connection.
If you need to specify a port to access the database, you can do so by appending ":portnumber" to the database.hostname variable.
The configuration variables `database.hostname` (or `database.socket`), `database.username`, `database.password`, `database.database` and optionally `database.charset` are holding your credentials for the database connection.
If you need to specify a port to access the database, you can do so by appending ":portnumber" to the `database.hostname` variable.
'database' => [
'hostname' => 'your.mysqlhost.com:123456',
]
If all of the following environment variables are set, Friendica will use them instead of the previously configured variables for the db:
If all the following environment variables are set, Friendica will use them instead of the previously configured variables for the db:
MYSQL_HOST
MYSQL_HOST or MYSQL_SOCKET
MYSQL_PORT
MYSQL_USERNAME
MYSQL_PASSWORD
@ -316,7 +316,7 @@ Enabling the admin panel for an account, and thus making the account holder admi
Where you have to match the email address used for the account with the one you enter to the `config/local.config.php` file.
If more then one account should be able to access the admin panel, separate the email addresses with a comma.
If more than one account should be able to access the admin panel, separate the email addresses with a comma.
'config' => [
'admin_email' => 'someone@example.com,someoneelse@example.com',

View file

@ -9,8 +9,8 @@ There is also a connector for accessing your email INBOX.
If the following network connectors are installed on your system, select the following links to visit the appropriate settings page and configure them for your account:
* [Twitter](/settings/addon)
* [GNU Social](/settings/addon)
* [Twitter](/settings/addons)
* [GNU Social](/settings/addons)
* [Email](/settings)
Instructions For Connecting To People On Specific Services

View file

@ -30,7 +30,7 @@ function doSomething(array $intros)
}
}
$intros = \Friendica\Database\DBA::selectToArray('intros', [], ['uid' => local_user()]);
$intros = \Friendica\Database\DBA::selectToArray('intros', [], ['uid' => Session::getLocalUser()]);
doSomething($intros);
```
@ -47,7 +47,7 @@ function doSomething(\Friendica\Contact\Introductions\Collection\Introductions $
}
/** @var $intros \Friendica\Contact\Introductions\Collection\Introductions */
$intros = \Friendica\DI::intro()->selecForUser(local_user());
$intros = \Friendica\DI::intro()->selecForUser(Session::getLocalUser());
doSomething($intros);
```

View file

@ -47,7 +47,7 @@ Friendica uses an implementation of [Domain-Driven-Design](help/Developer-Domain
Friendica uses [Composer](https://getcomposer.org) to manage dependencies libraries and the class autoloader both for libraries and namespaced Friendica classes.
It's a command-line tool that downloads required libraries into the `vendor` folder and makes any namespaced class in `src` available through the whole application through `boot.php`.
It's a command-line tool that downloads required libraries into the `vendor` folder and makes any namespaced class in `src` available through the whole application.
If you want to have git automatically update the dependencies with composer, you can use the `post-merge` [git-hook](https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks) with a script similar to this one:

View file

@ -50,7 +50,7 @@ We recommend to talk to the admin(s) of the affected friendica server. (Admins,
### How can I upload images, files, links, videos and sound files to posts?
You can upload images from your computer using the [editor](help/Text_editor).
An overview of all uploaded images is listed at *yourpage.com/photos/profilename*.
An overview of all uploaded images is listed at *yourpage.com/profile/profilename/photos*.
On that page, you can also upload images directly and choose if your contacts will receive a message about this upload.
Generally, you can attach any kind of file to a post.
@ -178,47 +178,40 @@ The available features are client specific and may differ.
#### Android
* [AndStatus](http://andstatus.org) ([F-Droid](https://f-droid.org/repository/browse/?fdid=org.andstatus.app), [Google Play](https://play.google.com/store/apps/details?id=org.andstatus.app))
* [B4X for Pleroma & Mastodon](https://github.com/AnywhereSoftware/B4X-Pleroma)
* [Fedi](https://play.google.com/store/apps/details?id=com.fediverse.app)
* [Fedi](https://github.com/Big-Fig/Fediverse.app) ([Google Play](https://play.google.com/store/apps/details?id=com.fediverse.app))
* [Fedilab](https://fedilab.app) ([F-Droid](https://f-droid.org/app/fr.gouv.etalab.mastodon), [Google Play](https://play.google.com/store/apps/details?id=app.fedilab.android))
* [Friendiqa](https://git.friendi.ca/lubuwest/Friendiqa) ([F-Droid](https://git.friendi.ca/lubuwest/Friendiqa#install), [Google Play](https://play.google.com/store/apps/details?id=org.qtproject.friendiqa))
* [Husky](https://git.sr.ht/~captainepoch/husky) ([F-Droid](https://f-droid.org/repository/browse/?fdid=su.xash.husky), [Google Play](https://play.google.com/store/apps/details?id=su.xash.husky))
* [Mastodon for Android](https://github.com/mastodon/mastodon-android) (F-Droid: Pending, [Google-Play](https://play.google.com/store/apps/details?id=org.joinmastodon.android))
* [Subway Tooter](https://github.com/tateisu/SubwayTooter)
* [Tooot](https://tooot.app/)
* [Mastodon](https://github.com/mastodon/mastodon-android) ([F-Droid](https://f-droid.org/en/packages/org.joinmastodon.android/), [Google Play](https://play.google.com/store/apps/details?id=org.joinmastodon.android))
* [Subway Tooter](https://github.com/tateisu/SubwayTooter) ([F-Droid](https://android.izzysoft.de/repo/apk/jp.juggler.subwaytooter))
* [Tooot](https://tooot.app/) ([Google Play](https://play.google.com/store/apps/details?id=com.xmflsct.app.tooot))
* [Tusky](https://tusky.app) ([F-Droid](https://f-droid.org/repository/browse/?fdid=com.keylesspalace.tusky), [Google Play](https://play.google.com/store/apps/details?id=com.keylesspalace.tusky))
* [Twidere](https://github.com/TwidereProject/Twidere-Android) ([F-Droid](https://f-droid.org/repository/browse/?fdid=org.mariotaku.twidere), [Google Play](https://play.google.com/store/apps/details?id=com.twidere.twiderex))
* [TwidereX](https://github.com/TwidereProject/TwidereX-Android) ([F-Droid](https://f-droid.org/en/packages/com.twidere.twiderex/), [Google Play](https://play.google.com/store/apps/details?id=com.twidere.twiderex))
* [Yuito](https://github.com/accelforce/Yuito) ([Google Play](https://play.google.com/store/apps/details?id=net.accelf.yuito))
#### SailfishOS
* [Friendly](https://openrepos.net/content/fabrixxm/friendly), last update: 2018
#### iOS
* [B4X for Pleroma & Mastodon](https://github.com/AnywhereSoftware/B4X-Pleroma) ([AppStore](https://apps.apple.com/app/b4x-pleroma/id1538396871))
* [Fedi](https://fediapp.com) ([AppStore](https://apps.apple.com/de/app/fedi-for-pleroma-and-mastodon/id1478806281))
* [Mastodon for iPhone and iPad](https://joinmastodon.org/apps) ([AppStore](https://apps.apple.com/us/app/mastodon-for-iphone/id1571998974))
* [Fedi](https://github.com/Big-Fig/Fediverse.app) ([AppStore](https://apps.apple.com/de/app/fedi-for-pleroma-and-mastodon/id1478806281))
* [Mastodon](https://joinmastodon.org/apps) ([AppStore](https://apps.apple.com/us/app/mastodon-for-iphone/id1571998974))
* [Stella*](https://www.stella-app.net/) ([AppStore](https://apps.apple.com/us/app/stella-for-mastodon-twitter/id921372048))
* [Tooot](https://github.com/tooot-app) ([AppStore](https://apps.apple.com/app/id1549772269), Data collection (not linked to identity)
* [Tootle](https://mastodon.cloud/@tootleapp) ([AppStore](https://apps.apple.com/de/app/tootle-for-mastodon/id1236013466)), last update: 2020
* [Tooot](https://github.com/tooot-app) ([AppStore](https://apps.apple.com/app/id1549772269)
* [TwidereX](https://github.com/TwidereProject/TwidereX-iOS) ([AppStore](https://apps.apple.com/app/twidere-x/id1530314034))
#### Linux
* [Choqok](https://choqok.kde.org)
* [Whalebird](https://whalebird.social)
* [TheDesk](https://ja.mstdn.wiki/TheDesk)
* [Whalebird](https://whalebird.social/en/desktop/contents) ([GitHub](https://github.com/h3poteto/whalebird-desktop))
* [TheDesk](https://thedesk.top/en/) ([GitHub](https://github.com/cutls/TheDesk))
* [Toot](https://toot.readthedocs.io/en/latest/)
* [Tootle](https://github.com/bleakgrey/tootle)
#### macOS
* [Mastonaut](https://mastonaut.app/) ([AppStore](https://apps.apple.com/us/app/mastonaut/id1450757574)), closed source
* [TheDesk](https://thedesk.top/en/) ([GitHub](https://github.com/cutls/TheDesk))
* [Whalebird](https://whalebird.social/en/desktop/contents) ([AppStore](https://apps.apple.com/de/app/whalebird/id1378283354), [GitHub](https://github.com/h3poteto/whalebird-desktop))
#### Windows
* [TheDesk](https://thedesk.top/en/) ([GitHub](https://github.com/cutls/TheDesk))
* [Whalebird](https://whalebird.social/en/desktop/contents) ([Website Download](https://whalebird.social/en/desktop/contents/downloads#windows), [GitHub](https://github.com/h3poteto/whalebird-desktop))
#### Web Frontend

View file

@ -34,7 +34,7 @@ Due to the large variety of operating systems and PHP platforms in existence we
* The POSIX module of PHP needs to be activated (e.g. [RHEL, CentOS](http://www.bigsoft.co.uk/blog/index.php/2014/12/08/posix-php-commands-not-working-under-centos-7) have disabled it)
* Some form of email server or email gateway such that PHP mail() works.
If you cannot set up your own email server, you can use the [phpmailer](https://github.com/friendica/friendica-addons/tree/develop/phpmailer) addon and use a remote SMTP server.
* MySQL 5.6+ or an equivalent alternative for MySQL (MariaDB, Percona Server etc.)
* MySQL with support of InnoDB and Barracuda (we suggest a MariaDB server as all development is done using these, but alternatives like MySQL or Percona Server etc. might work as well)
* ability to schedule jobs with cron (Linux/Mac) or Scheduled Tasks (Windows)
* installation into a top-level domain or sub-domain (without a directory/path component in the URL) is RECOMMENDED. Directory paths will not be as convenient to use and have not been thoroughly tested. This is REQUIRED if you wish to communicate with the Diaspora network.

View file

@ -9,8 +9,8 @@ How to move your account between servers
* Go to "Settings" -> "[Export personal data](uexport)"
* Click on "Export account" to save your account data.
* **Save the file in a secure place!** It contains your details, your contacts, groups, and personal settings. It also contains your secret keys to authenticate yourself to your contacts.
* Go to your new server, and open *http://newserver.com/uimport* (there is not a direct link to this page at the moment). Please consider that this is only possible on servers with open registration. On other systems only the administrator can add accounts with an uploaded file.
* Do NOT create a new account prior to importing your old settings - uimport should be used *instead* of register.
* Go to your new server, and open *http://newserver.com/user/import* (there is not a direct link to this page at the moment). Please consider that this is only possible on servers with open registration. On other systems only the administrator can add accounts with an uploaded file.
* Do NOT create a new account prior to importing your old settings - user import should be used *instead* of register.
* Load your saved account file and click "Import".
* After the move, the account on the old server will not work reliably anymore, and should be not used.

View file

@ -5,7 +5,7 @@ Remove Account
We don't like to see people leave Friendica, but if you need to remove your account, you should visit the URL
http://sitename/removeme
http://sitename/settings/removeme
with your web browser.
You will need to be logged in at the time.

View file

@ -30,13 +30,3 @@ Here you can find an overview of the different ways to comment and sort existing
<P style="clear: both;"></p>
<img src="doc/img/post_choose.png" width="27" height="32" alt="post_choose.png" align="left"> This symbol is used to choose more than one post to delete in a single step. After selecting all posts, go to the end of the page and click "Delete Selected Items".<P style="clear: both;"></p>
**Symbols of other themes**
Darkbubble <img src="doc/img/darkbubble.png" alt="darkbubble.png" style="padding-left: 20px; vertical-align:middle;">
Darkzero <img src="doc/img/darkzero.png" alt="darkzero.png" style="padding-left: 35px; vertical-align:middle;">
<span style="padding-left: 10px; font-style:italic;">(incl. more "zero"-themes, slackr, comix, easterbunny, facepark)</span>
Dispy <img src="doc/img/dispy.png" alt="dispy.png" style="padding-left: 57px; vertical-align:middle;"> <i>(incl. smoothly, testbubble)</i>

View file

@ -24,10 +24,6 @@ Below are examples of the post editor in 3 of Friendica's common themes:
<figcaption>Post editor, with the <b>Vier</b> theme.</figcaption>
</figure>
<p style="clear:both;"></p>
<figure>
<img src="doc/img/editor_dpzero.png" alt="duepuntozero editor">
<figcaption>Post editor, with the <b>Duepuntozero</b> theme.</figcaption>
</figure>
Post title is optional, you can set it by clicking on "Set title".
@ -73,11 +69,6 @@ These icons can change depending on the theme. Some examples:
<td><img src="doc/img/vier_icons.png" alt="vier.png" style="vertical-align:middle;"></td>
<td>&nbsp;</td>
</tr>
<tr>
<td>Smoothly: </td>
<td><img src="doc/img/editor_darkbubble.png" alt="darkbubble.png" style="vertical-align:middle;"></td>
<td>&nbsp;</td>
</tr>
</table>
<i><b>*</b> how to [upload](help/FAQ#upload) files</i>
<p style="clear:both;">&nbsp;</p>

View file

@ -10,7 +10,7 @@ Getting started
No need to setup up a webserver, database etc. before actually starting.
Vagrant creates a virtual machine for you that you can just run inside VirtualBox and start to work directly on Friendica.
It brings an Debian Bullseye with PHP 7.4 and MariaDB 10.5.11.
It brings an Debian Bullseye with PHP 8.0 and MariaDB 10.5.11.
What you need to do:
@ -24,7 +24,7 @@ This will start the virtual machine.
Be patient: When it runs for the first time, it downloads a Debian Server image and installs Friendica.
4. Run `vagrant ssh` to log into the virtual machine to log in to the VM in case you need to debug something on the server.
5. Open you test installation in a browser.
Go to friendica.local (or 192.168.22.10).
Go to friendica.local (or 192.168.56.10).
friendica.local is using a self-signed TLS certificate, so you will need to add an exception to trust the certificate the first time you are visiting the page.
The mysql database is called "friendica", the mysql user and password both are "friendica".
6. Work on Friendica's code in your git clone on your machine (not in the VM).
@ -66,4 +66,4 @@ On the next Vagrant up, the version problem should be fixed.
If `friendica.local` is not resolved, you may need to add an entry to the `/etc/hosts` file (or similar configuration depending on the OS you are using).
For further documentation of vagrant, please see [the vagrant*docs*](https://docs.vagrantup.com/v2/).
For further documentation of vagrant, please see [the vagrant*docs*](https://docs.vagrantup.com/v2/).

View file

@ -6,7 +6,7 @@ Autoloader with Composer
Friendica uses [Composer](https://getcomposer.org) to manage dependencies libraries and the class autoloader both for libraries and namespaced Friendica classes.
It's a command-line tool that downloads required libraries into the `vendor` folder and makes any namespaced class in `src` available through the whole application through `boot.php`.
It's a command-line tool that downloads required libraries into the `vendor` folder and makes any namespaced class in `src` available through the whole application.
* [Using Composer](help/Composer)
@ -39,7 +39,6 @@ Namespaces are useful to keep classes separated and avoid names conflicts (could
Let's say now that you need to load some items in a view, maybe in a fictional `mod/network.php`.
In order for the Composer autoloader to work, it must first be included.
In Friendica this is already done at the top of `boot.php`, with `require_once('vendor/autoload.php');`.
The code will be something like:

View file

@ -8,6 +8,7 @@ Database Tables
| [2fa_app_specific_password](help/database/db_2fa_app_specific_password) | Two-factor app-specific _password |
| [2fa_recovery_codes](help/database/db_2fa_recovery_codes) | Two-factor authentication recovery codes |
| [2fa_trusted_browser](help/database/db_2fa_trusted_browser) | Two-factor authentication trusted browsers |
| [account-suggestion](help/database/db_account-suggestion) | Account suggestion |
| [account-user](help/database/db_account-user) | Remote and local accounts |
| [addon](help/database/db_addon) | registered addons |
| [apcontact](help/database/db_apcontact) | ActivityPub compatible contacts - used in the ActivityPub implementation |
@ -22,10 +23,10 @@ Database Tables
| [contact-relation](help/database/db_contact-relation) | Contact relations |
| [conv](help/database/db_conv) | private messages |
| [delayed-post](help/database/db_delayed-post) | Posts that are about to be distributed at a later time |
| [diaspora-contact](help/database/db_diaspora-contact) | Diaspora compatible contacts - used in the Diaspora implementation |
| [diaspora-interaction](help/database/db_diaspora-interaction) | Signed Diaspora Interaction |
| [endpoint](help/database/db_endpoint) | ActivityPub endpoints - used in the ActivityPub implementation |
| [event](help/database/db_event) | Events |
| [fcontact](help/database/db_fcontact) | Diaspora compatible contacts - used in the Diaspora implementation |
| [fetch-entry](help/database/db_fetch-entry) | |
| [fetched-activity](help/database/db_fetched-activity) | Id of fetched activities |
| [fsuggest](help/database/db_fsuggest) | friend suggestion stuff |
@ -74,6 +75,8 @@ Database Tables
| [profile_field](help/database/db_profile_field) | Custom profile fields |
| [push_subscriber](help/database/db_push_subscriber) | Used for OStatus: Contains feed subscribers |
| [register](help/database/db_register) | registrations requiring admin approval |
| [report](help/database/db_report) | |
| [report-post](help/database/db_report-post) | |
| [search](help/database/db_search) | |
| [session](help/database/db_session) | web session storage |
| [storage](help/database/db_storage) | Data stored by Database storage backend |

View file

@ -0,0 +1,32 @@
Table account-suggestion
===========
Account suggestion
Fields
------
| Field | Description | Type | Null | Key | Default | Extra |
| ------ | ------------------------------------------------------------ | ------------------ | ---- | --- | ------- | ----- |
| uri-id | Id of the item-uri table entry that contains the account url | int unsigned | NO | PRI | NULL | |
| uid | User ID | mediumint unsigned | NO | PRI | NULL | |
| level | level of closeness | smallint unsigned | YES | | NULL | |
| ignore | If set, this account will not be suggested again | boolean | NO | | 0 | |
Indexes
------------
| Name | Fields |
| ---------- | ----------- |
| PRIMARY | uid, uri-id |
| uri-id_uid | uri-id, uid |
Foreign Keys
------------
| Field | Target Table | Target Field |
|-------|--------------|--------------|
| uri-id | [item-uri](help/database/db_item-uri) | id |
| uid | [user](help/database/db_user) | uid |
Return to [database documentation](help/database)

View file

@ -0,0 +1,52 @@
Table diaspora-contact
===========
Diaspora compatible contacts - used in the Diaspora implementation
Fields
------
| Field | Description | Type | Null | Key | Default | Extra |
| ----------------- | ------------------------------------------------------------ | ------------ | ---- | --- | ------------------- | ----- |
| uri-id | Id of the item-uri table entry that contains the contact URL | int unsigned | NO | PRI | NULL | |
| addr | | varchar(255) | YES | | NULL | |
| alias | | varchar(255) | YES | | NULL | |
| nick | | varchar(255) | YES | | NULL | |
| name | | varchar(255) | YES | | NULL | |
| given-name | | varchar(255) | YES | | NULL | |
| family-name | | varchar(255) | YES | | NULL | |
| photo | | varchar(255) | YES | | NULL | |
| photo-medium | | varchar(255) | YES | | NULL | |
| photo-small | | varchar(255) | YES | | NULL | |
| batch | | varchar(255) | YES | | NULL | |
| notify | | varchar(255) | YES | | NULL | |
| poll | | varchar(255) | YES | | NULL | |
| subscribe | | varchar(255) | YES | | NULL | |
| searchable | | boolean | YES | | NULL | |
| pubkey | | text | YES | | NULL | |
| gsid | Global Server ID | int unsigned | YES | | NULL | |
| created | | datetime | NO | | 0001-01-01 00:00:00 | |
| updated | | datetime | NO | | 0001-01-01 00:00:00 | |
| interacting_count | Number of contacts this contact interactes with | int unsigned | YES | | 0 | |
| interacted_count | Number of contacts that interacted with this contact | int unsigned | YES | | 0 | |
| post_count | Number of posts and comments | int unsigned | YES | | 0 | |
Indexes
------------
| Name | Fields |
| ------- | ------------ |
| PRIMARY | uri-id |
| addr | UNIQUE, addr |
| alias | alias |
| gsid | gsid |
Foreign Keys
------------
| Field | Target Table | Target Field |
|-------|--------------|--------------|
| uri-id | [item-uri](help/database/db_item-uri) | id |
| gsid | [gserver](help/database/db_gserver) | id |
Return to [database documentation](help/database)

View file

@ -1,51 +0,0 @@
Table fcontact
===========
Diaspora compatible contacts - used in the Diaspora implementation
Fields
------
| Field | Description | Type | Null | Key | Default | Extra |
| ----------------- | ------------------------------------------------------------- | ---------------- | ---- | --- | ------------------- | -------------- |
| id | sequential ID | int unsigned | NO | PRI | NULL | auto_increment |
| guid | unique id | varbinary(255) | NO | | | |
| url | | varbinary(383) | NO | | | |
| uri-id | Id of the item-uri table entry that contains the fcontact url | int unsigned | YES | | NULL | |
| name | | varchar(255) | NO | | | |
| photo | | varbinary(383) | NO | | | |
| request | | varbinary(383) | NO | | | |
| nick | | varchar(255) | NO | | | |
| addr | | varchar(255) | NO | | | |
| batch | | varbinary(383) | NO | | | |
| notify | | varbinary(383) | NO | | | |
| poll | | varbinary(383) | NO | | | |
| confirm | | varbinary(383) | NO | | | |
| priority | | tinyint unsigned | NO | | 0 | |
| network | | char(4) | NO | | | |
| alias | | varbinary(383) | NO | | | |
| pubkey | | text | YES | | NULL | |
| created | | datetime | NO | | 0001-01-01 00:00:00 | |
| updated | | datetime | NO | | 0001-01-01 00:00:00 | |
| interacting_count | Number of contacts this contact interactes with | int unsigned | YES | | 0 | |
| interacted_count | Number of contacts that interacted with this contact | int unsigned | YES | | 0 | |
| post_count | Number of posts and comments | int unsigned | YES | | 0 | |
Indexes
------------
| Name | Fields |
| ------- | ---------------- |
| PRIMARY | id |
| addr | addr(32) |
| url | UNIQUE, url(190) |
| uri-id | UNIQUE, uri-id |
Foreign Keys
------------
| Field | Target Table | Target Field |
|-------|--------------|--------------|
| uri-id | [item-uri](help/database/db_item-uri) | id |
Return to [database documentation](help/database)

View file

@ -25,6 +25,7 @@ Fields
| height | | smallint unsigned | NO | | 0 | |
| width | | smallint unsigned | NO | | 0 | |
| datasize | | int unsigned | NO | | 0 | |
| blurhash | BlurHash representation of the photo | varbinary(255) | YES | | NULL | |
| data | | mediumblob | NO | | NULL | |
| scale | | tinyint unsigned | NO | | 0 | |
| profile | | boolean | NO | | 0 | |

View file

@ -13,6 +13,7 @@ Fields
| content-warning | | varchar(255) | NO | | | |
| body | item body content | mediumtext | YES | | NULL | |
| raw-body | Body without embedded media links | mediumtext | YES | | NULL | |
| quote-uri-id | Id of the item-uri table that contains the quoted uri | int unsigned | YES | | NULL | |
| location | text location where this item originated | varchar(255) | NO | | | |
| coord | longitude/latitude pair representing location where this item originated | varchar(255) | NO | | | |
| language | Language information about this post | text | YES | | NULL | |
@ -35,6 +36,7 @@ Indexes
| plink | plink(191) |
| resource-id | resource-id |
| title-content-warning-body | FULLTEXT, title, content-warning, body |
| quote-uri-id | quote-uri-id |
Foreign Keys
------------
@ -42,5 +44,6 @@ Foreign Keys
| Field | Target Table | Target Field |
|-------|--------------|--------------|
| uri-id | [item-uri](help/database/db_item-uri) | id |
| quote-uri-id | [item-uri](help/database/db_item-uri) | id |
Return to [database documentation](help/database)

View file

@ -6,36 +6,39 @@ Attached media
Fields
------
| Field | Description | Type | Null | Key | Default | Extra |
| --------------- | --------------------------------------------------------- | ----------------- | ---- | --- | ------- | -------------- |
| id | sequential ID | int unsigned | NO | PRI | NULL | auto_increment |
| uri-id | Id of the item-uri table entry that contains the item uri | int unsigned | NO | | NULL | |
| url | Media URL | varbinary(1024) | NO | | NULL | |
| type | Media type | tinyint unsigned | NO | | 0 | |
| mimetype | | varchar(60) | YES | | NULL | |
| height | Height of the media | smallint unsigned | YES | | NULL | |
| width | Width of the media | smallint unsigned | YES | | NULL | |
| size | Media size | bigint unsigned | YES | | NULL | |
| preview | Preview URL | varbinary(512) | YES | | NULL | |
| preview-height | Height of the preview picture | smallint unsigned | YES | | NULL | |
| preview-width | Width of the preview picture | smallint unsigned | YES | | NULL | |
| description | | text | YES | | NULL | |
| name | Name of the media | varchar(255) | YES | | NULL | |
| author-url | URL of the author of the media | varbinary(383) | YES | | NULL | |
| author-name | Name of the author of the media | varchar(255) | YES | | NULL | |
| author-image | Image of the author of the media | varbinary(383) | YES | | NULL | |
| publisher-url | URL of the publisher of the media | varbinary(383) | YES | | NULL | |
| publisher-name | Name of the publisher of the media | varchar(255) | YES | | NULL | |
| publisher-image | Image of the publisher of the media | varbinary(383) | YES | | NULL | |
| Field | Description | Type | Null | Key | Default | Extra |
| --------------- | ------------------------------------------------------------------ | ----------------- | ---- | --- | ------- | -------------- |
| id | sequential ID | int unsigned | NO | PRI | NULL | auto_increment |
| uri-id | Id of the item-uri table entry that contains the item uri | int unsigned | NO | | NULL | |
| url | Media URL | varbinary(1024) | NO | | NULL | |
| media-uri-id | Id of the item-uri table entry that contains the activities uri-id | int unsigned | YES | | NULL | |
| type | Media type | tinyint unsigned | NO | | 0 | |
| mimetype | | varchar(60) | YES | | NULL | |
| height | Height of the media | smallint unsigned | YES | | NULL | |
| width | Width of the media | smallint unsigned | YES | | NULL | |
| size | Media size | bigint unsigned | YES | | NULL | |
| blurhash | BlurHash representation of the image | varbinary(255) | YES | | NULL | |
| preview | Preview URL | varbinary(512) | YES | | NULL | |
| preview-height | Height of the preview picture | smallint unsigned | YES | | NULL | |
| preview-width | Width of the preview picture | smallint unsigned | YES | | NULL | |
| description | | text | YES | | NULL | |
| name | Name of the media | varchar(255) | YES | | NULL | |
| author-url | URL of the author of the media | varbinary(383) | YES | | NULL | |
| author-name | Name of the author of the media | varchar(255) | YES | | NULL | |
| author-image | Image of the author of the media | varbinary(383) | YES | | NULL | |
| publisher-url | URL of the publisher of the media | varbinary(383) | YES | | NULL | |
| publisher-name | Name of the publisher of the media | varchar(255) | YES | | NULL | |
| publisher-image | Image of the publisher of the media | varbinary(383) | YES | | NULL | |
Indexes
------------
| Name | Fields |
| ---------- | ------------------------ |
| PRIMARY | id |
| uri-id-url | UNIQUE, uri-id, url(512) |
| uri-id-id | uri-id, id |
| Name | Fields |
| ------------ | ------------------------ |
| PRIMARY | id |
| uri-id-url | UNIQUE, uri-id, url(512) |
| uri-id-id | uri-id, id |
| media-uri-id | media-uri-id |
Foreign Keys
------------
@ -43,5 +46,6 @@ Foreign Keys
| Field | Target Table | Target Field |
|-------|--------------|--------------|
| uri-id | [item-uri](help/database/db_item-uri) | id |
| media-uri-id | [item-uri](help/database/db_item-uri) | id |
Return to [database documentation](help/database)

View file

@ -6,51 +6,52 @@ user profiles data
Fields
------
| Field | Description | Type | Null | Key | Default | Extra |
| ------------ | --------------------------------------------- | ------------------ | ---- | --- | ---------- | -------------- |
| id | sequential ID | int unsigned | NO | PRI | NULL | auto_increment |
| uid | Owner User id | mediumint unsigned | NO | | 0 | |
| profile-name | Deprecated | varchar(255) | YES | | NULL | |
| is-default | Deprecated | boolean | YES | | NULL | |
| hide-friends | Hide friend list from viewers of this profile | boolean | NO | | 0 | |
| name | | varchar(255) | NO | | | |
| pdesc | Deprecated | varchar(255) | YES | | NULL | |
| dob | Day of birth | varchar(32) | NO | | 0000-00-00 | |
| address | | varchar(255) | NO | | | |
| locality | | varchar(255) | NO | | | |
| region | | varchar(255) | NO | | | |
| postal-code | | varchar(32) | NO | | | |
| country-name | | varchar(255) | NO | | | |
| hometown | Deprecated | varchar(255) | YES | | NULL | |
| gender | Deprecated | varchar(32) | YES | | NULL | |
| marital | Deprecated | varchar(255) | YES | | NULL | |
| with | Deprecated | text | YES | | NULL | |
| howlong | Deprecated | datetime | YES | | NULL | |
| sexual | Deprecated | varchar(255) | YES | | NULL | |
| politic | Deprecated | varchar(255) | YES | | NULL | |
| religion | Deprecated | varchar(255) | YES | | NULL | |
| pub_keywords | | text | YES | | NULL | |
| prv_keywords | | text | YES | | NULL | |
| likes | Deprecated | text | YES | | NULL | |
| dislikes | Deprecated | text | YES | | NULL | |
| about | Profile description | text | YES | | NULL | |
| summary | Deprecated | varchar(255) | YES | | NULL | |
| music | Deprecated | text | YES | | NULL | |
| book | Deprecated | text | YES | | NULL | |
| tv | Deprecated | text | YES | | NULL | |
| film | Deprecated | text | YES | | NULL | |
| interest | Deprecated | text | YES | | NULL | |
| romance | Deprecated | text | YES | | NULL | |
| work | Deprecated | text | YES | | NULL | |
| education | Deprecated | text | YES | | NULL | |
| contact | Deprecated | text | YES | | NULL | |
| homepage | | varchar(255) | NO | | | |
| xmpp | XMPP address | varchar(255) | NO | | | |
| matrix | Matrix address | varchar(255) | NO | | | |
| photo | | varbinary(383) | NO | | | |
| thumb | | varbinary(383) | NO | | | |
| publish | publish default profile in local directory | boolean | NO | | 0 | |
| net-publish | publish profile in global directory | boolean | NO | | 0 | |
| Field | Description | Type | Null | Key | Default | Extra |
| ----------------- | -------------------------------------------------------------- | ------------------ | ---- | --- | ---------- | -------------- |
| id | sequential ID | int unsigned | NO | PRI | NULL | auto_increment |
| uid | Owner User id | mediumint unsigned | NO | | 0 | |
| profile-name | Deprecated | varchar(255) | YES | | NULL | |
| is-default | Deprecated | boolean | YES | | NULL | |
| hide-friends | Hide friend list from viewers of this profile | boolean | NO | | 0 | |
| name | | varchar(255) | NO | | | |
| pdesc | Deprecated | varchar(255) | YES | | NULL | |
| dob | Day of birth | varchar(32) | NO | | 0000-00-00 | |
| address | | varchar(255) | NO | | | |
| locality | | varchar(255) | NO | | | |
| region | | varchar(255) | NO | | | |
| postal-code | | varchar(32) | NO | | | |
| country-name | | varchar(255) | NO | | | |
| hometown | Deprecated | varchar(255) | YES | | NULL | |
| gender | Deprecated | varchar(32) | YES | | NULL | |
| marital | Deprecated | varchar(255) | YES | | NULL | |
| with | Deprecated | text | YES | | NULL | |
| howlong | Deprecated | datetime | YES | | NULL | |
| sexual | Deprecated | varchar(255) | YES | | NULL | |
| politic | Deprecated | varchar(255) | YES | | NULL | |
| religion | Deprecated | varchar(255) | YES | | NULL | |
| pub_keywords | | text | YES | | NULL | |
| prv_keywords | | text | YES | | NULL | |
| likes | Deprecated | text | YES | | NULL | |
| dislikes | Deprecated | text | YES | | NULL | |
| about | Profile description | text | YES | | NULL | |
| summary | Deprecated | varchar(255) | YES | | NULL | |
| music | Deprecated | text | YES | | NULL | |
| book | Deprecated | text | YES | | NULL | |
| tv | Deprecated | text | YES | | NULL | |
| film | Deprecated | text | YES | | NULL | |
| interest | Deprecated | text | YES | | NULL | |
| romance | Deprecated | text | YES | | NULL | |
| work | Deprecated | text | YES | | NULL | |
| education | Deprecated | text | YES | | NULL | |
| contact | Deprecated | text | YES | | NULL | |
| homepage | | varchar(255) | NO | | | |
| homepage_verified | was the homepage verified by a rel-me link back to the profile | boolean | NO | | 0 | |
| xmpp | XMPP address | varchar(255) | NO | | | |
| matrix | Matrix address | varchar(255) | NO | | | |
| photo | | varbinary(383) | NO | | | |
| thumb | | varbinary(383) | NO | | | |
| publish | publish default profile in local directory | boolean | NO | | 0 | |
| net-publish | publish profile in global directory | boolean | NO | | 0 | |
Indexes
------------

View file

@ -0,0 +1,31 @@
Table report-post
===========
Fields
------
| Field | Description | Type | Null | Key | Default | Extra |
| ------ | --------------------------- | ---------------- | ---- | --- | ------- | ----- |
| rid | Report id | int unsigned | NO | PRI | NULL | |
| uri-id | Uri-id of the reported post | int unsigned | NO | PRI | NULL | |
| status | Status of the reported post | tinyint unsigned | YES | | NULL | |
Indexes
------------
| Name | Fields |
| ------- | ----------- |
| PRIMARY | rid, uri-id |
| uri-id | uri-id |
Foreign Keys
------------
| Field | Target Table | Target Field |
|-------|--------------|--------------|
| rid | [report](help/database/db_report) | id |
| uri-id | [item-uri](help/database/db_item-uri) | id |
Return to [database documentation](help/database)

36
doc/database/db_report.md Normal file
View file

@ -0,0 +1,36 @@
Table report
===========
Fields
------
| Field | Description | Type | Null | Key | Default | Extra |
| ------- | --------------------------------------- | ------------------ | ---- | --- | ------------------- | -------------- |
| id | sequential ID | int unsigned | NO | PRI | NULL | auto_increment |
| uid | Reporting user | mediumint unsigned | YES | | NULL | |
| cid | Reported contact | int unsigned | NO | | NULL | |
| comment | Report | text | YES | | NULL | |
| forward | Forward the report to the remote server | boolean | YES | | NULL | |
| created | | datetime | NO | | 0001-01-01 00:00:00 | |
| status | Status of the report | tinyint unsigned | YES | | NULL | |
Indexes
------------
| Name | Fields |
| ------- | ------ |
| PRIMARY | id |
| uid | uid |
| cid | cid |
Foreign Keys
------------
| Field | Target Table | Target Field |
|-------|--------------|--------------|
| uid | [user](help/database/db_user) | uid |
| cid | [contact](help/database/db_contact) | id |
Return to [database documentation](help/database)

View file

@ -6,53 +6,54 @@ The local users
Fields
------
| Field | Description | Type | Null | Key | Default | Extra |
| ------------------------ | --------------------------------------------------------- | ------------------ | ---- | --- | ------------------- | -------------- |
| uid | sequential ID | mediumint unsigned | NO | PRI | NULL | auto_increment |
| parent-uid | The parent user that has full control about this user | mediumint unsigned | YES | | NULL | |
| guid | A unique identifier for this user | varchar(64) | NO | | | |
| username | Name that this user is known by | varchar(255) | NO | | | |
| password | encrypted password | varchar(255) | NO | | | |
| legacy_password | Is the password hash double-hashed? | boolean | NO | | 0 | |
| nickname | nick- and user name | varchar(255) | NO | | | |
| email | the users email address | varchar(255) | NO | | | |
| openid | | varchar(255) | NO | | | |
| timezone | PHP-legal timezone | varchar(128) | NO | | | |
| language | default language | varchar(32) | NO | | en | |
| register_date | timestamp of registration | datetime | NO | | 0001-01-01 00:00:00 | |
| login_date | timestamp of last login | datetime | NO | | 0001-01-01 00:00:00 | |
| default-location | Default for item.location | varchar(255) | NO | | | |
| allow_location | 1 allows to display the location | boolean | NO | | 0 | |
| theme | user theme preference | varchar(255) | NO | | | |
| pubkey | RSA public key 4096 bit | text | YES | | NULL | |
| prvkey | RSA private key 4096 bit | text | YES | | NULL | |
| spubkey | | text | YES | | NULL | |
| sprvkey | | text | YES | | NULL | |
| verified | user is verified through email | boolean | NO | | 0 | |
| blocked | 1 for user is blocked | boolean | NO | | 0 | |
| blockwall | Prohibit contacts to post to the profile page of the user | boolean | NO | | 0 | |
| hidewall | Hide profile details from unkown viewers | boolean | NO | | 0 | |
| blocktags | Prohibit contacts to tag the post of this user | boolean | NO | | 0 | |
| unkmail | Permit unknown people to send private mails to this user | boolean | NO | | 0 | |
| cntunkmail | | int unsigned | NO | | 10 | |
| notify-flags | email notification options | smallint unsigned | NO | | 65535 | |
| page-flags | page/profile type | tinyint unsigned | NO | | 0 | |
| account-type | | tinyint unsigned | NO | | 0 | |
| prvnets | | boolean | NO | | 0 | |
| pwdreset | Password reset request token | varchar(255) | YES | | NULL | |
| pwdreset_time | Timestamp of the last password reset request | datetime | YES | | NULL | |
| maxreq | | int unsigned | NO | | 10 | |
| expire | | int unsigned | NO | | 0 | |
| account_removed | if 1 the account is removed | boolean | NO | | 0 | |
| account_expired | | boolean | NO | | 0 | |
| account_expires_on | timestamp when account expires and will be deleted | datetime | NO | | 0001-01-01 00:00:00 | |
| expire_notification_sent | timestamp of last warning of account expiration | datetime | NO | | 0001-01-01 00:00:00 | |
| def_gid | | int unsigned | NO | | 0 | |
| allow_cid | default permission for this user | mediumtext | YES | | NULL | |
| allow_gid | default permission for this user | mediumtext | YES | | NULL | |
| deny_cid | default permission for this user | mediumtext | YES | | NULL | |
| deny_gid | default permission for this user | mediumtext | YES | | NULL | |
| openidserver | | text | YES | | NULL | |
| Field | Description | Type | Null | Key | Default | Extra |
| ------------------------ | --------------------------------------------------------------------------------- | ------------------ | ---- | --- | ------------------- | -------------- |
| uid | sequential ID | mediumint unsigned | NO | PRI | NULL | auto_increment |
| parent-uid | The parent user that has full control about this user | mediumint unsigned | YES | | NULL | |
| guid | A unique identifier for this user | varchar(64) | NO | | | |
| username | Name that this user is known by | varchar(255) | NO | | | |
| password | encrypted password | varchar(255) | NO | | | |
| legacy_password | Is the password hash double-hashed? | boolean | NO | | 0 | |
| nickname | nick- and user name | varchar(255) | NO | | | |
| email | the users email address | varchar(255) | NO | | | |
| openid | | varchar(255) | NO | | | |
| timezone | PHP-legal timezone | varchar(128) | NO | | | |
| language | default language | varchar(32) | NO | | en | |
| register_date | timestamp of registration | datetime | NO | | 0001-01-01 00:00:00 | |
| login_date | timestamp of last login | datetime | NO | | 0001-01-01 00:00:00 | |
| last-activity | Day of the last activity | date | YES | | NULL | |
| default-location | Default for item.location | varchar(255) | NO | | | |
| allow_location | 1 allows to display the location | boolean | NO | | 0 | |
| theme | user theme preference | varchar(255) | NO | | | |
| pubkey | RSA public key 4096 bit | text | YES | | NULL | |
| prvkey | RSA private key 4096 bit | text | YES | | NULL | |
| spubkey | | text | YES | | NULL | |
| sprvkey | | text | YES | | NULL | |
| verified | user is verified through email | boolean | NO | | 0 | |
| blocked | 1 for user is blocked | boolean | NO | | 0 | |
| blockwall | Prohibit contacts to post to the profile page of the user | boolean | NO | | 0 | |
| hidewall | Hide profile details from unkown viewers | boolean | NO | | 0 | |
| blocktags | Prohibit contacts to tag the post of this user | boolean | NO | | 0 | |
| unkmail | Permit unknown people to send private mails to this user | boolean | NO | | 0 | |
| cntunkmail | | int unsigned | NO | | 10 | |
| notify-flags | email notification options | smallint unsigned | NO | | 65535 | |
| page-flags | page/profile type | tinyint unsigned | NO | | 0 | |
| account-type | | tinyint unsigned | NO | | 0 | |
| prvnets | | boolean | NO | | 0 | |
| pwdreset | Password reset request token | varchar(255) | YES | | NULL | |
| pwdreset_time | Timestamp of the last password reset request | datetime | YES | | NULL | |
| maxreq | | int unsigned | NO | | 10 | |
| expire | Delay in days before deleting user-related posts. Scope is controlled by pConfig. | int unsigned | NO | | 0 | |
| account_removed | if 1 the account is removed | boolean | NO | | 0 | |
| account_expired | | boolean | NO | | 0 | |
| account_expires_on | timestamp when account expires and will be deleted | datetime | NO | | 0001-01-01 00:00:00 | |
| expire_notification_sent | timestamp of last warning of account expiration | datetime | NO | | 0001-01-01 00:00:00 | |
| def_gid | | int unsigned | NO | | 0 | |
| allow_cid | default permission for this user | mediumtext | YES | | NULL | |
| allow_gid | default permission for this user | mediumtext | YES | | NULL | |
| deny_cid | default permission for this user | mediumtext | YES | | NULL | |
| deny_gid | default permission for this user | mediumtext | YES | | NULL | |
| openidserver | | text | YES | | NULL | |
Indexes
------------

View file

@ -309,10 +309,6 @@ Eine komplette Liste aller Hook-Callbacks mit den zugehörigen Dateien (am 01-Ap
Hook::callAll('post_local', $datarray);
Hook::callAll('post_local_end', $datarray);
### mod/editpost.php
Hook::callAll('jot_tool', $jotplugins);
### src/Network/FKOAuth1.php
Hook::callAll('logged_in', $a->user);
@ -405,6 +401,10 @@ Eine komplette Liste aller Hook-Callbacks mit den zugehörigen Dateien (am 01-Ap
Hook::callAll('block', $hook_data);
Hook::callAll('unblock', $hook_data);
### src/Core/Logger/Factory.php
Hook::callAll('logger_instance', $data);
### src/Core/StorageManager
Hook::callAll('storage_instance', $data);
@ -418,6 +418,10 @@ Eine komplette Liste aller Hook-Callbacks mit den zugehörigen Dateien (am 01-Ap
Hook::callAll('lockview_content', $item);
### src/Module/Post/Edit.php
Hook::callAll('jot_tool', $jotplugins);
### src/Worker/Directory.php
Hook::callAll('globaldir_update', $arr);

View file

@ -69,7 +69,7 @@ Andere erlauben nur kostenpflichtige Zertifikate als eigenes Angebot bzw. von an
### Wie kann ich Bilder, Dateien, Links, Video und Audio in Beiträge einfügen?
Bilder können direkt im [Beitragseditor](help/Text_editor) vom Computer hochgeladen werden.
Eine Übersicht aller Bilder, die auf Deinem Server liegen, findest Du unter <i>deineSeite.de/photos/profilname</i>.
Eine Übersicht aller Bilder, die auf Deinem Server liegen, findest Du unter <i>deineSeite.de/profile/profilname/photos</i>.
Dort kannst Du auch direkt Bilder hochladen und festlegen, ob Deine Kontakte eine Nachricht über das neue Bild bekommen.
Alle Arten von Dateien können grundsätzlich als Anhang in Friendica hochgeladen werden.

View file

@ -31,7 +31,7 @@ Requirements
* Das POSIX Modul muss aktiviert sein ([CentOS, RHEL](http://www.bigsoft.co.uk/blog/index.php/2014/12/08/posix-php-commands-not-working-under-centos-7http://www.bigsoft.co.uk/blog/index.php/2014/12/08/posix-php-commands-not-working-under-centos-7) haben dies z.B. deaktiviert)
* Einen E-Mail Server, so dass PHP `mail()` funktioniert.
Wenn kein eigener E-Mail Server zur Verfügung steht, kann alternativ das [phpmailer](https://github.com/friendica/friendica-addons/tree/develop/phpmailer) Addon mit einem externen SMTP Account verwendet werden.
* Mysql 5.6+ (oder eine äquivalente Alternative: MariaDB, Percona Server etc.)
* Mysql Server mit Unterstützung vom InnoDB und Barracuda (wir empfehlen MariaDB da die Entwicklung mit solchen Server erfolgt, aber Alternativen wie MySQL, Percona Server etc. sollten auch funktionieren)
* die Möglichkeit, wiederkehrende Aufgaben mit cron (Linux/Mac) oder "Scheduled Tasks" einzustellen (Windows) [Beachte: andere Optionen sind in Abschnitt 7 dieser Dokumentation zu finden]
* Installation in einer Top-Level-Domain oder Subdomain (ohne eine Verzeichnis/Pfad-Komponente in der URL) wird bevorzugt. Verzeichnispfade sind für diesen Zweck nicht so günstig und wurden auch nicht ausführlich getestet.

View file

@ -15,14 +15,14 @@ Außerdem enthält sie deinen geheimen Schlüssel mit dem du dich deinen Kontakt
**Speichere diese Datei an einem sicheren Ort**!
Rufe nun dem neuen Server die Seite *http://newserver.com/uimport* auf (es gibt derzeit keinen direkten Link auf diese Seite).
Rufe nun dem neuen Server die Seite *http://newserver.com/user/import* auf (es gibt derzeit keinen direkten Link auf diese Seite).
Bitte beachte, dass dies nur auf Servern möglich ist, an denen man sich offen anmelden kann.
Bei Servern, bei denen der Administrator Accounts freigeben muss, ist das Hochladen nicht möglich.
Hier kann dies nur der Administrator selber durchführen.
Lege auf dem neuen Server auf keinen Fall einen gleichnamigen Account an!
uimport muss anstelle des Registrierens verwendet werden.
user import muss anstelle des Registrierens verwendet werden.
Wähle die gesicherte Account Datei aus und klicke "Importieren".

View file

@ -5,7 +5,7 @@ Accounts löschen
Wir freuen uns nicht, wenn Leute Friendica verlassen, aber wenn du deinen Account löschen willst, dann besuche die folgende URL
[Lösche mich (http://NamederSeite/removeme)](../removeme)
[Lösche mich (http://NamederSeite/settings/removeme)](../settings/removeme)
in deinem Webbrowser. Du musst dabei eingeloggt sein.

View file

@ -47,13 +47,3 @@ Wähle eine vorhandene Gruppe oder gib einen neuen Namen ein. Die erstellten Gru
<img src="doc/img/post_choose.png" width="27" height="32" alt="post_choose.png" align="left"> Mit diesem Symbol kannst du mehrere Beiträge auswählen und gesammelt löschen.
Hierfür gehst du nach dem Markieren aller gewünschten Beiträge auf "Lösche die markierten Beiträge" am Ende der Seite mit allen Beiträgen.
<P style="clear: both;"></p>
**Im Folgenden findest du Symbole weiterer Themen**
Darkbubble <img src="doc/img/darkbubble.png" alt="darkbubble.png" style="padding-left: 20px; vertical-align:middle;">
Darkzero <img src="doc/img/darkzero.png" alt="darkzero.png" style="padding-left: 35px; vertical-align:middle;">
<span style="padding-left: 10px; font-style:italic;">(inkl. weiterer "zero"-Themen, slackr, comix, easterbunny, facepark)</span>
Dispy <img src="doc/img/dispy.png" alt="dispy.png" style="padding-left: 57px; vertical-align:middle;"> <i>(inkl. smoothly, testbubble)</i>

View file

@ -9,35 +9,35 @@ Achtung: für dieses Beispiel wurde das Thema <b>"Diabook"</b> genutzt.
Wenn du ein anderes Design benutzt, wirst du manche dieser Symbole gar nicht oder in anderer Form vorfinden.
</span>
<img src="doc/img/friendica_editor.png" width="538" height="218" alt="editor">
<img src="doc/img/friendica_rich_editor.png" width="538" height="218" alt="editor">
<i>Die einzelnen Symbole</i>
<img src="doc/img/camera.png" width="44" height="33" alt="editor" align="left" style="padding-bottom: 20px;"> Wenn du auf dieses Symbol klickst, dann kannst du ein Bild von deinem Computer hinzufügen.
<img src="doc/img/camera.png" alt="editor" align="left" style="margin: 0 10px 10px 0;"> Wenn du auf dieses Symbol klickst, dann kannst du ein Bild von deinem Computer hinzufügen.
Wenn du eine Internetadresse (URL) eingeben willst, dann kannst du das "Baum"-Symbol im oberen Teil des Editors nutzen.
Wenn du ein Bild ausgewählt hast, dann erscheint eine Miniaturdarstellung des Bildes im Editor.*
<p style="clear:both;"></p>
<img src="doc/img/paper_clip.png" width="44" height="33" alt="paper_clip" align="left"> Wenn du dieses Symbol anklickst, dann kannst du weitere Dateien von deinem Computer einfügen. Eine Vorschau des Dateiinhalts erfolgt nicht.*
<img src="doc/img/paper_clip.png" alt="paper_clip" align="left" style="margin: 0 10px 10px 0;"> Wenn du dieses Symbol anklickst, dann kannst du weitere Dateien von deinem Computer einfügen. Eine Vorschau des Dateiinhalts erfolgt nicht.*
<p style="clear:both;"></p>
<img src="doc/img/chain.png" width="44" height="33" alt="chain" align="left"> Wenn du die Kette anklickst, dann kannst du eine Internetadresse (URL) einfügen.
<img src="doc/img/chain.png" alt="chain" align="left" style="margin: 0 10px 10px 0;"> Wenn du die Kette anklickst, dann kannst du eine Internetadresse (URL) einfügen.
Im Editor erscheint automatisch eine kurze Information zum eingefügten Link.*
<p style="clear:both;"></p>
<img src="doc/img/video.png" width="44" height="33" alt="video" align="left" style="padding-bottom: 40px;"> Mit dieser Funktion kannst du die Internetadresse (URL) einer Videodatei einfügen.
<img src="doc/img/video.png" alt="video" align="left" style="margin: 0 10px 10px 0;"> Mit dieser Funktion kannst du die Internetadresse (URL) einer Videodatei einfügen.
Das Video erscheint dann mit einem Player in deinem Beitrag.
Da Friendica zur Einbindung [HTML5](http://en.wikipedia.org/wiki/HTML5_video) verwendet, werden je nach Browser verschiedene Videoformate unterstützt (z.B. WebM oder MP4).
Außerdem kannst du hier die URLs von Videos auf Youtube, Vimeo und manchen anderen Videohostern eingeben.
Die Videos werden dann mit Vorschaubild angezeigt, nach einem Klick öffnet sich ein eingebetteter Player.*
<p style="clear:both;"></p>
<img src="doc/img/mic.png" width="44" height="33" alt="mic" align="left" style="padding-bottom: 40px;"> Mit dieser Funktion kannst du die Internetadresse (URL) einer Sound-Datei einfügen.
<img src="doc/img/mic.png" alt="mic" align="left" style="margin: 0 10px 10px 0;"> Mit dieser Funktion kannst du die Internetadresse (URL) einer Sound-Datei einfügen.
Da Friendica zur Einbindung [HTML5](http://en.wikipedia.org/wiki/HTML5_video) verwendet, werden je nach Browser und Betriebssystem MP3, Ogg oder AAC unterstützt.
Außerdem kannst du hier auch URLs von manchen Audiohostern wie Soundcloud eingeben, um eine dort gespeicherte Audiodatei mit Player in deinem Beitrag anzuzeigen.*
<p style="clear:both;"></p>
<img src="doc/img/globe.png" width="44" height="33" alt="globe" align="left"> Wenn du dieses Symbol wählst, dann kannst du deinen Standort festlegen.
<img src="doc/img/globe.png" alt="globe" align="left" style="margin: 0 10px 10px 0;"> Wenn du dieses Symbol wählst, dann kannst du deinen Standort festlegen.
Hier reicht schon eine Angabe wie "Berlin" oder "10775".
Dieser Eintrag führt anschließend zu einer Suchanfrage bei Google Maps.
<p style="clear:both;"></p>
@ -46,12 +46,6 @@ Dieser Eintrag führt anschließend zu einer Suchanfrage bei Google Maps.
**Im Folgenden findest du Symbole weiterer Themen**
Cleanzero <img src="doc/img/editor_zero.png" alt="cleanzero.png" style="padding-left: 20px; vertical-align:middle;">
<span style="padding-left: 10px; font-style:italic;">(inkl. weiterer "zero"-Themen, comix, easterbunny, facepark, slackr </span>
Darkbubble <img src="doc/img/editor_darkbubble.png" alt="darkbubble.png" style="padding-left: 14px; vertical-align:middle;"> <i>(inkl. smoothly, testbubble)</i>
Frio <img src="doc/img/editor_frio.png" alt="frio.png" style="padding-left: 44px; vertical-align:middle;">
Vier <img src="doc/img/editor_vier.png" alt="vier.png" style="padding-left: 44px; vertical-align:middle;"> <i>(inkl. dispy)</i>
Vier <img src="doc/img/editor_vier.png" alt="vier.png" style="padding-left: 44px; vertical-align:middle;">

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

View file

@ -17,7 +17,7 @@ The below for a more detailed description of theme heritage.
Some themes also allow users to select *variants* of the theme.
Those theme variants most often contain an additional [CSS](https://en.wikipedia.org/wiki/CSS) file to override some styling of the default theme values.
From the themes in the main repository *duepunto zero* and *vier* are using this methods for variations.
From the themes in the main repository *vier* and *vier* are using this methods for variations.
Quattro is using a slightly different approach.
Third you can start your theme from scratch.
@ -60,145 +60,6 @@ they will be overwritten by files in
/view/theme/**your-theme-name**/js.
## Expand an existing Theme
### Theme Variations
Many themes are more *theme families* than only one theme.
*duepunto zero* and *vier* allow easily to add new theme variation.
We will go through the process of creating a new variation for *duepunto zero*.
The same (well almost, some names change) procedure applies to the *vier* theme.
And similar steps are needed for *quattro* but this theme is using [lesscss](http://lesscss.org/#docs) to maintain the CSS files..
In
/view/theme/duepuntozero/deriv
you find a couple of CSS files that define color derivations from the duepunto theme.
These resemble some of the now as unsupported marked themes, that were inherited by the duepunto theme.
Darkzero and Easter Bunny for example.
The selection of the colorset is done in a combination of a template for a new form in the settings and aome functions in the theme.php file.
The template (theme_settings.tpl)
{{include file="field_select.tpl" field=$colorset}}
<div class="settings-submit-wrapper">
<input type="submit" value="{{$submit}}" class="settings-submit" name="duepuntozero-settings-submit" />
</div>
defines a formular consisting of a [select](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/select) pull-down which contains all aviable variants and s submit button.
See the documentation about [SMARTY3 templates](/help/snarty3-templates.md) for a summary of friendica specific blocks other than the select element.
But we don't really need to change anything at the template itself.
The template alone wont work though.
You make friendica aware of its existance and tell it how to use the template file, by defining a config.php file.
It needs to define at least the following functions
* theme_content
* theme_post
and may also define functions for the admin interface
* theme_admin
* theme_admin_post.
theme_content and theme_admin are used to make the form available in the settings, repectively the admin panel.
The _post functions handle the processing of the send form, in this case they save to selected variand in friendicas database.
To make your own variation appear in the menu, all you need to do is to create a new CSS file in the deriv directoy and include it in the array in the config.php:
$colorset = array(
'default'=>DI::l10n()->t('default'),
'greenzero'=>DI::l10n()->t('greenzero'),
'purplezero'=>DI::l10n()->t('purplezero'),
'easterbunny'=>DI::l10n()->t('easterbunny'),
'darkzero'=>DI::l10n()->t('darkzero'),
'comix'=>DI::l10n()->t('comix'),
'slackr'=>DI::l10n()->t('slackr'),
);
the 1st part of the line is the name of the CSS file (without the .css) the 2nd part is the common name of the variant.
Calling the DI::l10n()->t() function with the common name makes the string translateable.
The selected 1st part will be saved in the database by the theme_post function.
function theme_post(App $a){
// non local users shall not pass
if (! local_user()) {
return;
}
// if the one specific submit button was pressed then proceed
if (isset($_POST['duepuntozero-settings-submit'])){
// and save the selection key into the personal config of the user
DI::pConfig()->set(local_user(), 'duepuntozero', 'colorset', $_POST['duepuntozero_colorset']);
}
}
Now that this information is set in the database, what should friendica do with it?
For this, have a look at the theme.php file of the *duepunto zero*.
There you'll find somethink alike
$colorset = DI::pConfig()->get( local_user(), 'duepuntozero','colorset');
if (!$colorset)
$colorset = DI::config()->get('duepuntozero', 'colorset');
if ($colorset) {
if ($colorset == 'greenzero')
DI::page()['htmlhead'] .= '<link rel="stylesheet" href="view/theme/duepuntozero/deriv/greenzero.css" type="text/css" media="screen" />'."\n";
/* some more variants */
}
which tells friendica to get the personal config of a user.
Check if it is set and if not look for the global config.
And finally if a config for the colorset was found, apply it by adding a link to the CSS file into the HTML header of the page.
So you'll just need to add a if selection, fitting your variant keyword and link to the CSS file of it.
Done.
Now you can use the variant on your system.
But remember once the theme.php or the config.php you have to readd your variant to them.
If you think your color variation could be benifical for other friendica users as well, feel free to generate a pull request at github so we can include your work into the repository.
### Inheritation
Say, you like the duepuntozero but you want to have the content of the outer columns left and right exchanged.
That would be not a color variation as shown above.
Instead we will create a new theme, duepuntozero_lr, inherit the properties of duepuntozero and make small changes to the underlying php files.
So create a directory called duepunto_lr and create a file called theme.php with your favorite text editor.
The content of this file should be something like
<?php
/* meta informations for the theme, see below */
use Friendica\App;
function duepuntozero_lr_init(App $a) {
$a->setThemeInfoValue('extends', 'duepuntozero');
$a->set_template_engine('smarty3');
/* and more stuff e.g. the JavaScript function for the header */
}
Next take the default.php file found in the /view direcotry and exchange the aside and right_aside elements.
So the central part of the file now looks like this:
<body>
<?php if(!empty($page['nav'])) echo $page['nav']; ?>
<aside><?php if(!empty($page['right_aside'])) echo $page['right_aside']; ?></aside>
<section><?php if(!empty($page['content'])) echo $page['content']; ?>
<div id="page-footer"></div>
</section>
<right_aside><?php if(!empty($page['aside'])) echo $page['aside']; ?></right_aside>
<footer><?php if(!empty($page['footer'])) echo $page['footer']; ?></footer>
</body>
Finally we need a style.css file, inheriting the definitions from the parent theme and containing out changes for the new theme.
***Note***:You need to create the style.css and at lest import the base CSS file from the parent theme.
@import url('../duepuntozero/style.css');
Done.
But I agree it is not really useful at this state.
Nevertheless, to use it, you just need to activate in the admin panel.
That done, you can select it in the settings like any other activated theme.
## Creating a Theme from Scratch
Keep patient.
@ -231,15 +92,19 @@ Supported formats are PNG and JPEG.
This is the main definition file of the theme.
In the header of that file, some meta information is stored.
For example, have a look at the theme.php of the *quattro* theme:
For example, have a look at the theme.php of the *vier* theme:
<?php
/**
* Name: Quattro
* Version: 0.6
* [Licence]
*
* Name: Vier
* Version: 1.2
* Author: Fabio <http://kirgroup.com/profile/fabrixxm>
* Maintainer: Fabio <http://kirgroup.com/profile/fabrixxm>
* Maintainer: Tobias <https://f.diekershoff.de/profile/tobias>
* Author: Ike <http://pirati.ca/profile/heluecht>
* Author: Beanow <https://fc.oscp.info/profile/beanow>
* Maintainer: Ike <http://pirati.ca/profile/heluecht>
* Description: "Vier" is a very compact and modern theme. It uses the font awesome font library: http://fortawesome.github.com/Font-Awesome/
*/
You see the definition of the theme's name, it's version and the initial author of the theme.
@ -255,11 +120,11 @@ This will make our job a little easier, as we don't have to specify the full nam
The next crucial part of the theme.php file is a definition of an init function.
The name of the function is <theme-name>_init.
So in the case of quattro it is
So in the case of vier it is
function quattro_init(App $a) {
$a->theme_info = array();
$a->set_template_engine('smarty3');
function vier_init(App $a) {
$a->theme_info = array();
$a->set_template_engine('smarty3');
}
Here we have set the basic theme information, in this case they are empty.
@ -269,12 +134,6 @@ At the moment you should use the *smarty3* engine.
There once was a friendica specific templating engine as well but that is not used anymore.
If you like to use another templating engine, please implement it.
When you want to inherit stuff from another theme you have to *announce* this in the theme_info:
$a->setThemeInfoValue('extends', 'duepuntozero');
which declares *duepuntozero* as parent of the theme.
If you want to add something to the HTML header of the theme, one way to do so is by adding it to the theme.php file.
To do so, add something alike
@ -282,7 +141,6 @@ To do so, add something alike
/* stuff you want to add to the header */
EOT;
The $a variable holds the friendica application.
So you can access the properties of this friendica session from the theme.php file as well.
### default.php

View file

@ -1,298 +0,0 @@
<?php
/**
* @copyright Copyright (C) 2010-2022, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The calendar module
*
* This calendar is for profile visitors and contains only the events
* of the profile owner
*/
use Friendica\App;
use Friendica\Content\Nav;
use Friendica\Content\Widget;
use Friendica\Core\Renderer;
use Friendica\Core\Session;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\Event;
use Friendica\Model\Item;
use Friendica\Model\User;
use Friendica\Module\BaseProfile;
use Friendica\Module\Response;
use Friendica\Network\HTTPException;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\Temporal;
function cal_init(App $a)
{
if (DI::config()->get('system', 'block_public') && !Session::isAuthenticated()) {
throw new HTTPException\ForbiddenException(DI::l10n()->t('Access denied.'));
}
if (DI::args()->getArgc() < 2) {
throw new HTTPException\ForbiddenException(DI::l10n()->t('Access denied.'));
}
Nav::setSelected('events');
// if it's a json request abort here becaus we don't
// need the widget data
if (!empty(DI::args()->getArgv()[2]) && (DI::args()->getArgv()[2] === 'json')) {
return;
}
$owner = User::getOwnerDataByNick(DI::args()->getArgv()[1]);
if (empty($owner)) {
throw new HTTPException\NotFoundException(DI::l10n()->t('User not found.'));
}
if (empty(DI::page()['aside'])) {
DI::page()['aside'] = '';
}
DI::page()['aside'] .= Widget\VCard::getHTML($owner);
DI::page()['aside'] .= Widget\CalendarExport::getHTML($owner['uid']);
return;
}
function cal_content(App $a)
{
$owner = User::getOwnerDataByNick(DI::args()->getArgv()[1]);
if (empty($owner)) {
throw new HTTPException\NotFoundException(DI::l10n()->t('User not found.'));
}
Nav::setSelected('events');
// get the translation strings for the callendar
$i18n = Event::getStrings();
DI::page()->registerStylesheet('view/asset/fullcalendar/dist/fullcalendar.min.css');
DI::page()->registerStylesheet('view/asset/fullcalendar/dist/fullcalendar.print.min.css', 'print');
DI::page()->registerFooterScript('view/asset/moment/min/moment-with-locales.min.js');
DI::page()->registerFooterScript('view/asset/fullcalendar/dist/fullcalendar.min.js');
$htpl = Renderer::getMarkupTemplate('event_head.tpl');
DI::page()['htmlhead'] .= Renderer::replaceMacros($htpl, [
'$module_url' => '/cal/' . $owner['nickname'],
'$modparams' => 2,
'$i18n' => $i18n,
]);
$mode = 'view';
$y = 0;
$m = 0;
$ignored = (!empty($_REQUEST['ignored']) ? intval($_REQUEST['ignored']) : 0);
$format = 'ical';
if (DI::args()->getArgc() == 4 && DI::args()->getArgv()[2] == 'export') {
$mode = 'export';
$format = DI::args()->getArgv()[3];
}
// Setup permissions structures
$owner_uid = intval($owner['uid']);
$nick = $owner['nickname'];
$contact_id = Session::getRemoteContactID($owner['uid']);
$remote_contact = $contact_id && DBA::exists('contact', ['id' => $contact_id, 'uid' => $owner['uid']]);
$is_owner = local_user() == $owner['uid'];
if ($owner['hidewall'] && !$is_owner && !$remote_contact) {
notice(DI::l10n()->t('Access to this profile has been restricted.'));
return;
}
// get the permissions
$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;
// get the tab navigation bar
$tabs = BaseProfile::getTabsHTML($a, 'cal', false, $owner['nickname'], $owner['hide-friends']);
// The view mode part is similiar to /mod/events.php
if ($mode == 'view') {
$thisyear = DateTimeFormat::localNow('Y');
$thismonth = DateTimeFormat::localNow('m');
if (!$y) {
$y = intval($thisyear);
}
if (!$m) {
$m = intval($thismonth);
}
// Put some limits on dates. The PHP date functions don't seem to do so well before 1900.
// An upper limit was chosen to keep search engines from exploring links millions of years in the future.
if ($y < 1901) {
$y = 1900;
}
if ($y > 2099) {
$y = 2100;
}
$nextyear = $y;
$nextmonth = $m + 1;
if ($nextmonth > 12) {
$nextmonth = 1;
$nextyear ++;
}
$prevyear = $y;
if ($m > 1) {
$prevmonth = $m - 1;
} else {
$prevmonth = 12;
$prevyear --;
}
$dim = Temporal::getDaysInMonth($y, $m);
$start = sprintf('%d-%d-%d %d:%d:%d', $y, $m, 1, 0, 0, 0);
$finish = sprintf('%d-%d-%d %d:%d:%d', $y, $m, $dim, 23, 59, 59);
if (!empty(DI::args()->getArgv()[2]) && (DI::args()->getArgv()[2] === 'json')) {
if (!empty($_GET['start'])) {
$start = $_GET['start'];
}
if (!empty($_GET['end'])) {
$finish = $_GET['end'];
}
}
$start = DateTimeFormat::utc($start);
$finish = DateTimeFormat::utc($finish);
// put the event parametes in an array so we can better transmit them
$event_params = [
'event_id' => intval($_GET['id'] ?? 0),
'start' => $start,
'finish' => $finish,
'ignore' => $ignored,
];
// get events by id or by date
if ($event_params['event_id']) {
$r = Event::getListById($owner_uid, $event_params['event_id'], $sql_extra);
} else {
$r = Event::getListByDate($owner_uid, $event_params, $sql_extra);
}
$links = [];
if (DBA::isResult($r)) {
$r = Event::sortByDate($r);
foreach ($r as $rr) {
$j = DateTimeFormat::local($rr['start'], 'j');
if (empty($links[$j])) {
$links[$j] = DI::baseUrl() . '/' . DI::args()->getCommand() . '#link-' . $j;
}
}
}
// transform the event in a usable array
$events = Event::prepareListForTemplate($r);
if (!empty(DI::args()->getArgv()[2]) && (DI::args()->getArgv()[2] === 'json')) {
System::jsonExit($events);
}
// links: array('href', 'text', 'extra css classes', 'title')
if (!empty($_GET['id'])) {
$tpl = Renderer::getMarkupTemplate("event.tpl");
} else {
$tpl = Renderer::getMarkupTemplate("events_js.tpl");
}
// Get rid of dashes in key names, Smarty3 can't handle them
foreach ($events as $key => $event) {
$event_item = [];
foreach ($event['item'] as $k => $v) {
$k = str_replace('-', '_', $k);
$event_item[$k] = $v;
}
$events[$key]['item'] = $event_item;
}
$o = Renderer::replaceMacros($tpl, [
'$tabs' => $tabs,
'$title' => DI::l10n()->t('Events'),
'$view' => DI::l10n()->t('View'),
'$previous' => [DI::baseUrl() . "/events/$prevyear/$prevmonth", DI::l10n()->t('Previous'), '', ''],
'$next' => [DI::baseUrl() . "/events/$nextyear/$nextmonth", DI::l10n()->t('Next'), '', ''],
'$calendar' => Temporal::getCalendarTable($y, $m, $links, ' eventcal'),
'$events' => $events,
"today" => DI::l10n()->t("today"),
"month" => DI::l10n()->t("month"),
"week" => DI::l10n()->t("week"),
"day" => DI::l10n()->t("day"),
"list" => DI::l10n()->t("list"),
]);
if (!empty($_GET['id'])) {
System::httpExit($o);
}
return $o;
}
if ($mode == 'export') {
if (!$owner_uid) {
notice(DI::l10n()->t('User not found'));
return;
}
// Get the export data by uid
$evexport = Event::exportListByUserId($owner_uid, $format);
if (!$evexport["success"]) {
if ($evexport["content"]) {
notice(DI::l10n()->t('This calendar format is not supported'));
} else {
notice(DI::l10n()->t('No exportable data found'));
}
// If it the own calendar return to the events page
// otherwise to the profile calendar page
if (local_user() === $owner_uid) {
$return_path = "events";
} else {
$return_path = "cal/" . $nick;
}
DI::baseUrl()->redirect($return_path);
}
// If nothing went wrong we can echo the export content
if ($evexport["success"]) {
header('content-disposition: attachment; filename="' . DI::l10n()->t('calendar') . '-' . $nick . '.' . $evexport["extension"] . '"');
System::httpExit($evexport["content"], Response::TYPE_BLANK, 'text/calendar');
}
return;
}
}

View file

@ -1,371 +0,0 @@
<?php
/**
* @copyright Copyright (C) 2010-2022, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
use Friendica\App;
use Friendica\Content\Text\BBCode;
use Friendica\Content\Widget;
use Friendica\Core\Logger;
use Friendica\Core\Renderer;
use Friendica\Core\Session;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\Contact;
use Friendica\Model\Item;
use Friendica\Model\Post;
use Friendica\Model\User;
use Friendica\Module\ActivityPub\Objects;
use Friendica\Module\Response;
use Friendica\Network\HTTPException;
use Friendica\Protocol\ActivityPub;
use Friendica\Protocol\DFRN;
use Friendica\Protocol\Diaspora;
use Friendica\Util\DateTimeFormat;
function display_init(App $a)
{
if (ActivityPub::isRequest()) {
(new Objects(DI::l10n(), DI::baseUrl(), DI::args(), DI::logger(), DI::profiler(), DI::apiResponse(), $_SERVER, ['guid' => DI::args()->getArgv()[1] ?? null]))->run();
}
if (DI::config()->get('system', 'block_public') && !Session::isAuthenticated()) {
return;
}
$nick = ((DI::args()->getArgc() > 1) ? DI::args()->getArgv()[1] : '');
$item = null;
$item_user = local_user();
$fields = ['uri-id', 'parent-uri-id', 'author-id', 'author-link', 'body', 'uid', 'guid', 'gravity'];
// If there is only one parameter, then check if this parameter could be a guid
if (DI::args()->getArgc() == 2) {
$nick = '';
// Does the local user have this item?
if (local_user()) {
$item = Post::selectFirstForUser(local_user(), $fields, ['guid' => DI::args()->getArgv()[1], 'uid' => local_user()]);
if (DBA::isResult($item)) {
$nick = $a->getLoggedInUserNickname();
}
}
// Is this item private but could be visible to the remove visitor?
if (!DBA::isResult($item) && remote_user()) {
$item = Post::selectFirst($fields, ['guid' => DI::args()->getArgv()[1], 'private' => Item::PRIVATE, 'origin' => true]);
if (DBA::isResult($item)) {
if (!Contact::isFollower(remote_user(), $item['uid'])) {
$item = null;
} else {
$item_user = $item['uid'];
}
}
}
// Is it an item with uid=0?
if (!DBA::isResult($item)) {
$item = Post::selectFirstForUser(local_user(), $fields, ['guid' => DI::args()->getArgv()[1], 'private' => [Item::PUBLIC, Item::UNLISTED], 'uid' => 0]);
}
} elseif (DI::args()->getArgc() >= 3 && $nick == 'feed-item') {
$uri_id = DI::args()->getArgv()[2];
if (substr($uri_id, -5) == '.atom') {
$uri_id = substr($uri_id, 0, -5);
}
$item = Post::selectFirstForUser(local_user(), $fields, ['uri-id' => $uri_id, 'private' => [Item::PUBLIC, Item::UNLISTED], 'uid' => 0]);
}
if (!DBA::isResult($item)) {
return;
}
if (DI::args()->getArgc() >= 3 && $nick == 'feed-item') {
displayShowFeed($item['uri-id'], $item['uid'], DI::args()->getArgc() > 3 && DI::args()->getArgv()[3] == 'conversation.atom');
}
if (!empty($_SERVER['HTTP_ACCEPT']) && strstr($_SERVER['HTTP_ACCEPT'], 'application/atom+xml')) {
Logger::debug('Directly serving XML', ['uri-id' => $item['uri-id']]);
displayShowFeed($item['uri-id'], $item['uid'], false);
}
if ($item['gravity'] != GRAVITY_PARENT) {
$parent = Post::selectFirstForUser($item_user, $fields, ['uid' => [0, $item_user], 'uri-id' => $item['parent-uri-id']], ['order' => ['uid' => true]]);
$item = $parent ?: $item;
}
$author = display_fetchauthor($item);
if (\Friendica\Util\Network::isLocalLink($author['url'])) {
\Friendica\Model\Profile::load(DI::app(), $author['nick'], false);
} else {
DI::page()['aside'] = Widget\VCard::getHTML($author);
}
$a->setProfileOwner($item['uid']);
}
function display_fetchauthor($item)
{
if (Diaspora::isReshare($item['body'], true)) {
$shared = Item::getShareArray($item);
if (!empty($shared['profile'])) {
$contact = Contact::getByURLForUser($shared['profile'], local_user());
}
}
if (empty($contact)) {
$contact = Contact::getById($item['author-id']);
}
return $contact;
}
function display_content(App $a, $update = false, $update_uid = 0)
{
if (DI::config()->get('system','block_public') && !Session::isAuthenticated()) {
throw new HTTPException\ForbiddenException(DI::l10n()->t('Public access denied.'));
}
$o = '';
$item = null;
$force = (bool)($_REQUEST['force'] ?? false);
if ($update) {
$uri_id = $_REQUEST['uri_id'];
$item = Post::selectFirst(['uid', 'parent-uri-id'], ['uri-id' => $uri_id, 'uid' => [0, $update_uid]], ['order' => ['uid' => true]]);
if (!empty($item)) {
if ($item['uid'] != 0) {
$a->setProfileOwner($item['uid']);
} else {
$a->setProfileOwner($update_uid);
}
$parent_uri_id = $item['parent-uri-id'];
}
if (empty($_REQUEST['force'])) {
$browser_update = intval(DI::pConfig()->get($update_uid, 'system', 'update_interval'));
if (!empty($browser_update)) {
$update_date = date(DateTimeFormat::MYSQL, time() - ($browser_update / 500));
if (!Post::exists(["`parent-uri-id` = ? AND `uid` IN (?, ?) AND `received` > ?", $parent_uri_id, 0, $update_uid, $update_date])) {
Logger::debug('No updated content', ['uri-id' => $uri_id, 'uid' => $update_uid, 'updated' => $update_date]);
return '';
} else {
Logger::debug('Updated content found', ['uri-id' => $uri_id, 'uid' => $update_uid, 'updated' => $update_date]);
}
}
} else {
Logger::debug('Forced content update', ['uri-id' => $uri_id, 'uid' => $update_uid]);
}
} else {
$uri_id = ((DI::args()->getArgc() > 2) ? DI::args()->getArgv()[2] : 0);
$parent_uri_id = $uri_id;
if (DI::args()->getArgc() == 2) {
$fields = ['uri-id', 'parent-uri-id', 'uid'];
if (local_user()) {
$condition = ['guid' => DI::args()->getArgv()[1], 'uid' => [0, local_user()]];
$item = Post::selectFirstForUser(local_user(), $fields, $condition, ['order' => ['uid' => true]]);
if (DBA::isResult($item)) {
$uri_id = $item['uri-id'];
$parent_uri_id = $item['parent-uri-id'];
}
}
if (($parent_uri_id == 0) && remote_user()) {
$item = Post::selectFirst($fields, ['guid' => DI::args()->getArgv()[1], 'private' => Item::PRIVATE, 'origin' => true]);
if (DBA::isResult($item) && Contact::isFollower(remote_user(), $item['uid'])) {
$uri_id = $item['uri-id'];
$parent_uri_id = $item['parent-uri-id'];
}
}
if ($parent_uri_id == 0) {
$condition = ['private' => [Item::PUBLIC, Item::UNLISTED], 'guid' => DI::args()->getArgv()[1], 'uid' => 0];
$item = Post::selectFirstForUser(local_user(), $fields, $condition);
if (DBA::isResult($item)) {
$uri_id = $item['uri-id'];
$parent_uri_id = $item['parent-uri-id'];
}
}
}
}
if (empty($item)) {
throw new HTTPException\NotFoundException(DI::l10n()->t('The requested item doesn\'t exist or has been deleted.'));
}
if (!DI::pConfig()->get(local_user(), 'system', 'detailed_notif')) {
DI::notification()->setAllSeenForUser(local_user(), ['parent-uri-id' => $item['parent-uri-id']]);
DI::notify()->setAllSeenForUser(local_user(), ['parent-uri-id' => $item['parent-uri-id']]);
}
// We are displaying an "alternate" link if that post was public. See issue 2864
$is_public = Post::exists(['uri-id' => $uri_id, 'private' => [Item::PUBLIC, Item::UNLISTED]]);
if ($is_public) {
// For the atom feed the nickname doesn't matter at all, we only need the item id.
$alternate = DI::baseUrl().'/display/feed-item/'.$uri_id.'.atom';
$conversation = DI::baseUrl().'/display/feed-item/' . $parent_uri_id . '/conversation.atom';
} else {
$alternate = '';
$conversation = '';
}
DI::page()['htmlhead'] .= Renderer::replaceMacros(Renderer::getMarkupTemplate('display-head.tpl'),
['$alternate' => $alternate,
'$conversation' => $conversation]);
$is_remote_contact = false;
$item_uid = local_user();
$page_uid = 0;
$parent = null;
if (!local_user() && !empty($parent_uri_id)) {
$parent = Post::selectFirst(['uid'], ['uri-id' => $parent_uri_id, 'wall' => true]);
}
if (DBA::isResult($parent)) {
$page_uid = $page_uid ?? 0 ?: $parent['uid'];
$is_remote_contact = Session::getRemoteContactID($page_uid);
if ($is_remote_contact) {
$item_uid = $parent['uid'];
}
} else {
$page_uid = $item['uid'];
}
if (!empty($page_uid) && ($page_uid != local_user())) {
$page_user = User::getById($page_uid);
}
$is_owner = local_user() && (in_array($page_uid, [local_user(), 0]));
if (!empty($page_user['hidewall']) && !$is_owner && !$is_remote_contact) {
throw new HTTPException\ForbiddenException(DI::l10n()->t('Access to this profile has been restricted.'));
}
// We need the editor here to be able to reshare an item.
if ($is_owner && !$update) {
$o .= DI::conversation()->statusEditor([], 0, true);
}
$sql_extra = Item::getPermissionsSQLByUserId($page_uid);
if (local_user() && (local_user() == $page_uid)) {
$condition = ['parent-uri-id' => $parent_uri_id, 'uid' => local_user(), 'unseen' => true];
$unseen = Post::exists($condition);
} else {
$unseen = false;
}
if ($update && !$unseen && !$force) {
return '';
}
$condition = ["`uri-id` = ? AND `uid` IN (0, ?) " . $sql_extra, $uri_id, $item_uid];
$fields = ['parent-uri-id', 'body', 'title', 'author-name', 'author-avatar', 'plink', 'author-id', 'owner-id', 'contact-id'];
$item = Post::selectFirstForUser($page_uid, $fields, $condition);
if (!DBA::isResult($item)) {
throw new HTTPException\NotFoundException(DI::l10n()->t('The requested item doesn\'t exist or has been deleted.'));
}
$item['uri-id'] = $item['parent-uri-id'];
if ($unseen) {
$condition = ['parent-uri-id' => $parent_uri_id, 'uid' => local_user(), 'unseen' => true];
Item::update(['unseen' => false], $condition);
}
if (!$update && local_user()) {
$o .= "<script> var netargs = '?uri_id=" . $item['uri-id'] . "'; </script>";
}
$o .= DI::conversation()->create([$item], 'display', $update_uid, false, 'commented', $item_uid);
// Preparing the meta header
$description = trim(BBCode::toPlaintext($item['body']));
$title = trim(BBCode::toPlaintext($item['title'] ?? ''));
$author_name = $item['author-name'];
$image = DI::baseUrl()->remove($item['author-avatar']);
if ($title == '') {
$title = $author_name;
}
// Limit the description to 160 characters
if (strlen($description) > 160) {
$description = substr($description, 0, 157) . '...';
}
$description = htmlspecialchars($description, ENT_COMPAT, 'UTF-8', true); // allow double encoding here
$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
$page = DI::page();
if (DBA::exists('contact', ['unsearchable' => true, 'id' => [$item['contact-id'], $item['author-id'], $item['owner-id']]])) {
$page['htmlhead'] .= '<meta content="noindex, noarchive" name="robots" />' . "\n";
}
DI::page()['htmlhead'] .= '<meta name="author" content="'.$author_name.'" />'."\n";
$page['htmlhead'] .= '<meta name="title" content="'.$title.'" />'."\n";
$page['htmlhead'] .= '<meta name="fulltitle" content="'.$title.'" />'."\n";
$page['htmlhead'] .= '<meta name="description" content="'.$description.'" />'."\n";
// Schema.org microdata
$page['htmlhead'] .= '<meta itemprop="name" content="'.$title.'" />'."\n";
$page['htmlhead'] .= '<meta itemprop="description" content="'.$description.'" />'."\n";
$page['htmlhead'] .= '<meta itemprop="image" content="'.$image.'" />'."\n";
$page['htmlhead'] .= '<meta itemprop="author" content="'.$author_name.'" />'."\n";
// Twitter cards
$page['htmlhead'] .= '<meta name="twitter:card" content="summary" />'."\n";
$page['htmlhead'] .= '<meta name="twitter:title" content="'.$title.'" />'."\n";
$page['htmlhead'] .= '<meta name="twitter:description" content="'.$description.'" />'."\n";
$page['htmlhead'] .= '<meta name="twitter:image" content="'.DI::baseUrl().'/'.$image.'" />'."\n";
$page['htmlhead'] .= '<meta name="twitter:url" content="'.$item["plink"].'" />'."\n";
// Dublin Core
$page['htmlhead'] .= '<meta name="DC.title" content="'.$title.'" />'."\n";
$page['htmlhead'] .= '<meta name="DC.description" content="'.$description.'" />'."\n";
// Open Graph
$page['htmlhead'] .= '<meta property="og:type" content="website" />'."\n";
$page['htmlhead'] .= '<meta property="og:title" content="'.$title.'" />'."\n";
$page['htmlhead'] .= '<meta property="og:image" content="'.DI::baseUrl().'/'.$image.'" />'."\n";
$page['htmlhead'] .= '<meta property="og:url" content="'.$item["plink"].'" />'."\n";
$page['htmlhead'] .= '<meta property="og:description" content="'.$description.'" />'."\n";
$page['htmlhead'] .= '<meta name="og:article:author" content="'.$author_name.'" />'."\n";
// article:tag
return $o;
}
function displayShowFeed(int $uri_id, int $uid, bool $conversation)
{
$xml = DFRN::itemFeed($uri_id, $uid, $conversation);
if ($xml == '') {
throw new HTTPException\InternalServerErrorException(DI::l10n()->t('The feed for this item is unavailable.'));
}
System::httpExit($xml, Response::TYPE_ATOM);
}

View file

@ -1,166 +0,0 @@
<?php
/**
* @copyright Copyright (C) 2010-2022, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
use Friendica\App;
use Friendica\Content\Feature;
use Friendica\Core\Hook;
use Friendica\Core\Renderer;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\Contact;
use Friendica\Model\Post;
use Friendica\Model\User;
use Friendica\Util\Crypto;
function editpost_content(App $a)
{
$o = '';
if (!local_user()) {
notice(DI::l10n()->t('Permission denied.'));
return;
}
$post_id = ((DI::args()->getArgc() > 1) ? intval(DI::args()->getArgv()[1]) : 0);
if (!$post_id) {
notice(DI::l10n()->t('Item not found'));
return;
}
$fields = ['allow_cid', 'allow_gid', 'deny_cid', 'deny_gid',
'body', 'title', 'uri-id', 'wall', 'post-type', 'guid'];
$item = Post::selectFirstForUser(local_user(), $fields, ['id' => $post_id, 'uid' => local_user()]);
if (!DBA::isResult($item)) {
notice(DI::l10n()->t('Item not found'));
return;
}
$user = User::getById(local_user());
$geotag = '';
$o .= Renderer::replaceMacros(Renderer::getMarkupTemplate("section_title.tpl"), [
'$title' => DI::l10n()->t('Edit post')
]);
$tpl = Renderer::getMarkupTemplate('jot-header.tpl');
DI::page()['htmlhead'] .= Renderer::replaceMacros($tpl, [
'$ispublic' => '&nbsp;', // DI::l10n()->t('Visible to <strong>everybody</strong>'),
'$geotag' => $geotag,
'$nickname' => $a->getLoggedInUserNickname(),
'$is_mobile' => DI::mode()->isMobile(),
]);
if (strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid'])) {
$lockstate = 'lock';
} else {
$lockstate = 'unlock';
}
$jotplugins = '';
$jotnets = '';
Hook::callAll('jot_tool', $jotplugins);
$tpl = Renderer::getMarkupTemplate('jot.tpl');
$o .= Renderer::replaceMacros($tpl, [
'$is_edit' => true,
'$return_path' => '/display/' . $item['guid'],
'$action' => 'item',
'$share' => DI::l10n()->t('Save'),
'$loading' => DI::l10n()->t('Loading...'),
'$upload' => DI::l10n()->t('Upload photo'),
'$shortupload' => DI::l10n()->t('upload photo'),
'$attach' => DI::l10n()->t('Attach file'),
'$shortattach' => DI::l10n()->t('attach file'),
'$weblink' => DI::l10n()->t('Insert web link'),
'$shortweblink' => DI::l10n()->t('web link'),
'$video' => DI::l10n()->t('Insert video link'),
'$shortvideo' => DI::l10n()->t('video link'),
'$audio' => DI::l10n()->t('Insert audio link'),
'$shortaudio' => DI::l10n()->t('audio link'),
'$setloc' => DI::l10n()->t('Set your location'),
'$shortsetloc' => DI::l10n()->t('set location'),
'$noloc' => DI::l10n()->t('Clear browser location'),
'$shortnoloc' => DI::l10n()->t('clear location'),
'$wait' => DI::l10n()->t('Please wait'),
'$permset' => DI::l10n()->t('Permission settings'),
'$wall' => $item['wall'],
'$posttype' => $item['post-type'],
'$content' => undo_post_tagging($item['body']),
'$post_id' => $post_id,
'$defloc' => $user['default-location'],
'$visitor' => 'none',
'$pvisit' => 'none',
'$emailcc' => DI::l10n()->t('CC: email addresses'),
'$public' => DI::l10n()->t('Public post'),
'$jotnets' => $jotnets,
'$title' => $item['title'],
'$placeholdertitle' => DI::l10n()->t('Set title'),
'$category' => Post\Category::getCSVByURIId($item['uri-id'], local_user(), Post\Category::CATEGORY),
'$placeholdercategory' => (Feature::isEnabled(local_user(),'categories') ? DI::l10n()->t("Categories \x28comma-separated list\x29") : ''),
'$emtitle' => DI::l10n()->t('Example: bob@example.com, mary@example.com'),
'$lockstate' => $lockstate,
'$acl' => '', // populate_acl((($group) ? $group_acl : $a->user)),
'$bang' => ($lockstate === 'lock' ? '!' : ''),
'$profile_uid' => $_SESSION['uid'],
'$preview' => DI::l10n()->t('Preview'),
'$jotplugins' => $jotplugins,
'$cancel' => DI::l10n()->t('Cancel'),
'$rand_num' => Crypto::randomDigits(12),
// Formatting button labels
'$edbold' => DI::l10n()->t('Bold'),
'$editalic' => DI::l10n()->t('Italic'),
'$eduline' => DI::l10n()->t('Underline'),
'$edquote' => DI::l10n()->t('Quote'),
'$edcode' => DI::l10n()->t('Code'),
'$edurl' => DI::l10n()->t('Link'),
'$edattach' => DI::l10n()->t('Link or Media'),
//jot nav tab (used in some themes)
'$message' => DI::l10n()->t('Message'),
'$browser' => DI::l10n()->t('Browser'),
'$shortpermset' => DI::l10n()->t('Permissions'),
'$compose_link_title' => DI::l10n()->t('Open Compose page'),
]);
return $o;
}
function undo_post_tagging($s) {
$matches = null;
$cnt = preg_match_all('/([!#@])\[url=(.*?)\](.*?)\[\/url\]/ism', $s, $matches, PREG_SET_ORDER);
if ($cnt) {
foreach ($matches as $mtch) {
if (in_array($mtch[1], ['!', '@'])) {
$contact = Contact::getByURL($mtch[2], false, ['addr']);
$mtch[3] = empty($contact['addr']) ? $mtch[2] : $contact['addr'];
}
$s = str_replace($mtch[0], $mtch[1] . $mtch[3],$s);
}
}
return $s;
}

View file

@ -1,539 +0,0 @@
<?php
/**
* @copyright Copyright (C) 2010-2022, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* The events module
*/
use Friendica\App;
use Friendica\Content\Nav;
use Friendica\Content\Widget\CalendarExport;
use Friendica\Core\ACL;
use Friendica\Core\Logger;
use Friendica\Core\Protocol;
use Friendica\Core\Renderer;
use Friendica\Core\System;
use Friendica\Core\Theme;
use Friendica\Core\Worker;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\Conversation;
use Friendica\Model\Event;
use Friendica\Model\Item;
use Friendica\Model\Post;
use Friendica\Model\User;
use Friendica\Module\BaseProfile;
use Friendica\Module\Security\Login;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\Strings;
use Friendica\Util\Temporal;
use Friendica\Worker\Delivery;
function events_init(App $a)
{
if (!local_user()) {
return;
}
if (empty(DI::page()['aside'])) {
DI::page()['aside'] = '';
}
$cal_widget = CalendarExport::getHTML(local_user());
DI::page()['aside'] .= $cal_widget;
return;
}
function events_post(App $a)
{
Logger::debug('post', ['request' => $_REQUEST]);
if (!local_user()) {
return;
}
$event_id = !empty($_POST['event_id']) ? intval($_POST['event_id']) : 0;
$cid = !empty($_POST['cid']) ? intval($_POST['cid']) : 0;
$uid = local_user();
$start_text = Strings::escapeHtml($_REQUEST['start_text'] ?? '');
$finish_text = Strings::escapeHtml($_REQUEST['finish_text'] ?? '');
$nofinish = intval($_POST['nofinish'] ?? 0);
$share = intval($_POST['share'] ?? 0);
// The default setting for the `private` field in event_store() is false, so mirror that
$private_event = false;
$start = DBA::NULL_DATETIME;
$finish = DBA::NULL_DATETIME;
if ($start_text) {
$start = $start_text;
}
if ($finish_text) {
$finish = $finish_text;
}
$start = DateTimeFormat::convert($start, 'UTC', $a->getTimeZone());
if (!$nofinish) {
$finish = DateTimeFormat::convert($finish, 'UTC', $a->getTimeZone());
}
// Don't allow the event to finish before it begins.
// It won't hurt anything, but somebody will file a bug report
// and we'll waste a bunch of time responding to it. Time that
// could've been spent doing something else.
$summary = trim($_POST['summary'] ?? '');
$desc = trim($_POST['desc'] ?? '');
$location = trim($_POST['location'] ?? '');
$type = 'event';
$params = [
'summary' => $summary,
'description' => $desc,
'location' => $location,
'start' => $start_text,
'finish' => $finish_text,
'nofinish' => $nofinish,
];
$action = ($event_id == '') ? 'new' : 'event/' . $event_id;
$onerror_path = 'events/' . $action . '?' . http_build_query($params, '', '&', PHP_QUERY_RFC3986);
if (strcmp($finish, $start) < 0 && !$nofinish) {
notice(DI::l10n()->t('Event can not end before it has started.'));
if (intval($_REQUEST['preview'])) {
System::httpExit(DI::l10n()->t('Event can not end before it has started.'));
}
DI::baseUrl()->redirect($onerror_path);
}
if (!$summary || ($start === DBA::NULL_DATETIME)) {
notice(DI::l10n()->t('Event title and start time are required.'));
if (intval($_REQUEST['preview'])) {
System::httpExit(DI::l10n()->t('Event title and start time are required.'));
}
DI::baseUrl()->redirect($onerror_path);
}
$self = \Friendica\Model\Contact::getPublicIdByUserId($uid);
$aclFormatter = DI::aclFormatter();
if ($share) {
$user = User::getById($uid, ['allow_cid', 'allow_gid', 'deny_cid', 'deny_gid']);
if (!DBA::isResult($user)) {
return;
}
$str_contact_allow = isset($_REQUEST['contact_allow']) ? $aclFormatter->toString($_REQUEST['contact_allow']) : $user['allow_cid'] ?? '';
$str_group_allow = isset($_REQUEST['group_allow']) ? $aclFormatter->toString($_REQUEST['group_allow']) : $user['allow_gid'] ?? '';
$str_contact_deny = isset($_REQUEST['contact_deny']) ? $aclFormatter->toString($_REQUEST['contact_deny']) : $user['deny_cid'] ?? '';
$str_group_deny = isset($_REQUEST['group_deny']) ? $aclFormatter->toString($_REQUEST['group_deny']) : $user['deny_gid'] ?? '';
$visibility = $_REQUEST['visibility'] ?? '';
if ($visibility === 'public') {
// The ACL selector introduced in version 2019.12 sends ACL input data even when the Public visibility is selected
$str_contact_allow = $str_group_allow = $str_contact_deny = $str_group_deny = '';
} else if ($visibility === 'custom') {
// Since we know from the visibility parameter the item should be private, we have to prevent the empty ACL
// case that would make it public. So we always append the author's contact id to the allowed contacts.
// See https://github.com/friendica/friendica/issues/9672
$str_contact_allow .= $aclFormatter->toString($self);
}
} else {
$str_contact_allow = $aclFormatter->toString($self);
$str_group_allow = $str_contact_deny = $str_group_deny = '';
}
// Make sure to set the `private` field as true. This is necessary to
// have the posts show up correctly in Diaspora if an event is created
// as visible only to self at first, but then edited to display to others.
if (strlen($str_group_allow) || strlen($str_contact_allow) || strlen($str_group_deny) || strlen($str_contact_deny)) {
$private_event = true;
}
$datarray = [];
$datarray['start'] = $start;
$datarray['finish'] = $finish;
$datarray['summary'] = $summary;
$datarray['desc'] = $desc;
$datarray['location'] = $location;
$datarray['type'] = $type;
$datarray['nofinish'] = $nofinish;
$datarray['uid'] = $uid;
$datarray['cid'] = $cid;
$datarray['allow_cid'] = $str_contact_allow;
$datarray['allow_gid'] = $str_group_allow;
$datarray['deny_cid'] = $str_contact_deny;
$datarray['deny_gid'] = $str_group_deny;
$datarray['private'] = $private_event;
$datarray['id'] = $event_id;
if (intval($_REQUEST['preview'])) {
System::httpExit(Event::getHTML($datarray));
}
$event_id = Event::store($datarray);
$item = ['network' => Protocol::DFRN, 'protocol' => Conversation::PARCEL_DIRECT, 'direction' => Conversation::PUSH];
$item = Event::getItemArrayForId($event_id, $item);
if (Item::insert($item)) {
$uri_id = $item['uri-id'];
} else {
$uri_id = 0;
}
if (!$cid && $uri_id) {
Worker::add(PRIORITY_HIGH, "Notifier", Delivery::POST, (int)$uri_id, (int)$uid);
}
DI::baseUrl()->redirect('events');
}
function events_content(App $a)
{
if (!local_user()) {
notice(DI::l10n()->t('Permission denied.'));
return Login::form();
}
if (DI::args()->getArgc() == 1) {
$_SESSION['return_path'] = DI::args()->getCommand();
}
if ((DI::args()->getArgc() > 2) && (DI::args()->getArgv()[1] === 'ignore') && intval(DI::args()->getArgv()[2])) {
DBA::update('event', ['ignore' => true], ['id' => DI::args()->getArgv()[2], 'uid' => local_user()]);
}
if ((DI::args()->getArgc() > 2) && (DI::args()->getArgv()[1] === 'unignore') && intval(DI::args()->getArgv()[2])) {
DBA::update('event', ['ignore' => false], ['id' => DI::args()->getArgv()[2], 'uid' => local_user()]);
}
if ($a->getThemeInfoValue('events_in_profile')) {
Nav::setSelected('home');
} else {
Nav::setSelected('events');
}
// get the translation strings for the callendar
$i18n = Event::getStrings();
DI::page()->registerStylesheet('view/asset/fullcalendar/dist/fullcalendar.min.css');
DI::page()->registerStylesheet('view/asset/fullcalendar/dist/fullcalendar.print.min.css', 'print');
DI::page()->registerFooterScript('view/asset/moment/min/moment-with-locales.min.js');
DI::page()->registerFooterScript('view/asset/fullcalendar/dist/fullcalendar.min.js');
$htpl = Renderer::getMarkupTemplate('event_head.tpl');
DI::page()['htmlhead'] .= Renderer::replaceMacros($htpl, [
'$module_url' => '/events',
'$modparams' => 1,
'$i18n' => $i18n,
]);
$o = '';
$tabs = '';
// tabs
if ($a->getThemeInfoValue('events_in_profile')) {
$tabs = BaseProfile::getTabsHTML($a, 'events', true, $a->getLoggedInUserNickname(), false);
}
$mode = 'view';
$y = 0;
$m = 0;
$ignored = !empty($_REQUEST['ignored']) ? intval($_REQUEST['ignored']) : 0;
if (DI::args()->getArgc() > 1) {
if (DI::args()->getArgc() > 2 && DI::args()->getArgv()[1] == 'event') {
$mode = 'edit';
$event_id = intval(DI::args()->getArgv()[2]);
}
if (DI::args()->getArgc() > 2 && DI::args()->getArgv()[1] == 'drop') {
$mode = 'drop';
$event_id = intval(DI::args()->getArgv()[2]);
}
if (DI::args()->getArgc() > 2 && DI::args()->getArgv()[1] == 'copy') {
$mode = 'copy';
$event_id = intval(DI::args()->getArgv()[2]);
}
if (DI::args()->getArgv()[1] === 'new') {
$mode = 'new';
$event_id = 0;
}
if (DI::args()->getArgc() > 2 && intval(DI::args()->getArgv()[1]) && intval(DI::args()->getArgv()[2])) {
$mode = 'view';
$y = intval(DI::args()->getArgv()[1]);
$m = intval(DI::args()->getArgv()[2]);
}
}
// The view mode part is similiar to /mod/cal.php
if ($mode == 'view') {
$thisyear = DateTimeFormat::localNow('Y');
$thismonth = DateTimeFormat::localNow('m');
if (!$y) {
$y = intval($thisyear);
}
if (!$m) {
$m = intval($thismonth);
}
// Put some limits on dates. The PHP date functions don't seem to do so well before 1900.
// An upper limit was chosen to keep search engines from exploring links millions of years in the future.
if ($y < 1901) {
$y = 1900;
}
if ($y > 2099) {
$y = 2100;
}
$dim = Temporal::getDaysInMonth($y, $m);
$start = sprintf('%d-%d-%d %d:%d:%d', $y, $m, 1, 0, 0, 0);
$finish = sprintf('%d-%d-%d %d:%d:%d', $y, $m, $dim, 23, 59, 59);
// put the event parametes in an array so we can better transmit them
$event_params = [
'event_id' => intval($_GET['id'] ?? 0),
'start' => $start,
'finish' => $finish,
'ignore' => $ignored,
];
// get events by id or by date
if ($event_params['event_id']) {
$r = Event::getListById(local_user(), $event_params['event_id']);
} else {
$r = Event::getListByDate(local_user(), $event_params);
}
$links = [];
if (DBA::isResult($r)) {
$r = Event::sortByDate($r);
foreach ($r as $rr) {
$j = DateTimeFormat::local($rr['start'], 'j');
if (empty($links[$j])) {
$links[$j] = DI::baseUrl() . '/' . DI::args()->getCommand() . '#link-' . $j;
}
}
}
$events = [];
// transform the event in a usable array
if (DBA::isResult($r)) {
$r = Event::sortByDate($r);
$events = Event::prepareListForTemplate($r);
}
if (!empty($_GET['id'])) {
$tpl = Renderer::getMarkupTemplate("event.tpl");
} else {
$tpl = Renderer::getMarkupTemplate("events_js.tpl");
}
// Get rid of dashes in key names, Smarty3 can't handle them
foreach ($events as $key => $event) {
$event_item = [];
foreach ($event['item'] as $k => $v) {
$k = str_replace('-', '_', $k);
$event_item[$k] = $v;
}
$events[$key]['item'] = $event_item;
}
// ACL blocks are loaded in modals in frio
DI::page()->registerFooterScript(Theme::getPathForFile('asset/typeahead.js/dist/typeahead.bundle.js'));
DI::page()->registerFooterScript(Theme::getPathForFile('js/friendica-tagsinput/friendica-tagsinput.js'));
DI::page()->registerStylesheet(Theme::getPathForFile('js/friendica-tagsinput/friendica-tagsinput.css'));
DI::page()->registerStylesheet(Theme::getPathForFile('js/friendica-tagsinput/friendica-tagsinput-typeahead.css'));
$o = Renderer::replaceMacros($tpl, [
'$tabs' => $tabs,
'$title' => DI::l10n()->t('Events'),
'$view' => DI::l10n()->t('View'),
'$new_event' => [DI::baseUrl() . '/events/new', DI::l10n()->t('Create New Event'), '', ''],
'$previous' => [DI::baseUrl() . '/events/$prevyear/$prevmonth', DI::l10n()->t('Previous'), '', ''],
'$next' => [DI::baseUrl() . '/events/$nextyear/$nextmonth', DI::l10n()->t('Next'), '', ''],
'$calendar' => Temporal::getCalendarTable($y, $m, $links, ' eventcal'),
'$events' => $events,
'$today' => DI::l10n()->t('today'),
'$month' => DI::l10n()->t('month'),
'$week' => DI::l10n()->t('week'),
'$day' => DI::l10n()->t('day'),
'$list' => DI::l10n()->t('list'),
]);
if (!empty($_GET['id'])) {
System::httpExit($o);
}
return $o;
}
if (($mode === 'edit' || $mode === 'copy') && $event_id) {
$orig_event = DBA::selectFirst('event', [], ['id' => $event_id, 'uid' => local_user()]);
}
// Passed parameters overrides anything found in the DB
if (in_array($mode, ['edit', 'new', 'copy'])) {
$share_checked = '';
$share_disabled = '';
if (empty($orig_event)) {
$orig_event = User::getById(local_user(), ['allow_cid', 'allow_gid', 'deny_cid', 'deny_gid']);;
} elseif ($orig_event['allow_cid'] !== '<' . local_user() . '>'
|| $orig_event['allow_gid']
|| $orig_event['deny_cid']
|| $orig_event['deny_gid']) {
$share_checked = ' checked="checked" ';
}
// In case of an error the browser is redirected back here, with these parameters filled in with the previous values
if (!empty($_REQUEST['nofinish'])) {$orig_event['nofinish'] = $_REQUEST['nofinish'];}
if (!empty($_REQUEST['summary'])) {$orig_event['summary'] = $_REQUEST['summary'];}
if (!empty($_REQUEST['desc'])) {$orig_event['desc'] = $_REQUEST['desc'];}
if (!empty($_REQUEST['location'])) {$orig_event['location'] = $_REQUEST['location'];}
if (!empty($_REQUEST['start'])) {$orig_event['start'] = $_REQUEST['start'];}
if (!empty($_REQUEST['finish'])) {$orig_event['finish'] = $_REQUEST['finish'];}
$n_checked = (!empty($orig_event['nofinish']) ? ' checked="checked" ' : '');
$t_orig = $orig_event['summary'] ?? '';
$d_orig = $orig_event['desc'] ?? '';
$l_orig = $orig_event['location'] ?? '';
$eid = $orig_event['id'] ?? 0;
$cid = $orig_event['cid'] ?? 0;
$uri = $orig_event['uri'] ?? '';
if ($cid || $mode === 'edit') {
$share_disabled = 'disabled="disabled"';
}
$sdt = $orig_event['start'] ?? 'now';
$fdt = $orig_event['finish'] ?? 'now';
$syear = DateTimeFormat::local($sdt, 'Y');
$smonth = DateTimeFormat::local($sdt, 'm');
$sday = DateTimeFormat::local($sdt, 'd');
$shour = !empty($orig_event) ? DateTimeFormat::local($sdt, 'H') : '00';
$sminute = !empty($orig_event) ? DateTimeFormat::local($sdt, 'i') : '00';
$fyear = DateTimeFormat::local($fdt, 'Y');
$fmonth = DateTimeFormat::local($fdt, 'm');
$fday = DateTimeFormat::local($fdt, 'd');
$fhour = !empty($orig_event) ? DateTimeFormat::local($fdt, 'H') : '00';
$fminute = !empty($orig_event) ? DateTimeFormat::local($fdt, 'i') : '00';
if (!$cid && in_array($mode, ['new', 'copy'])) {
$acl = ACL::getFullSelectorHTML(DI::page(), $a->getLoggedInUserId(), false, ACL::getDefaultUserPermissions($orig_event));
} else {
$acl = '';
}
// If we copy an old event, we need to remove the ID and URI
// from the original event.
if ($mode === 'copy') {
$eid = 0;
$uri = '';
}
$tpl = Renderer::getMarkupTemplate('event_form.tpl');
$o .= Renderer::replaceMacros($tpl, [
'$post' => DI::baseUrl() . '/events',
'$eid' => $eid,
'$cid' => $cid,
'$uri' => $uri,
'$title' => DI::l10n()->t('Event details'),
'$desc' => DI::l10n()->t('Starting date and Title are required.'),
'$s_text' => DI::l10n()->t('Event Starts:') . ' <span class="required" title="' . DI::l10n()->t('Required') . '">*</span>',
'$s_dsel' => Temporal::getDateTimeField(
new DateTime(),
DateTime::createFromFormat('Y', intval($syear) + 5),
DateTime::createFromFormat('Y-m-d H:i', "$syear-$smonth-$sday $shour:$sminute"),
DI::l10n()->t('Event Starts:'),
'start_text',
true,
true,
'',
'',
true
),
'$n_text' => DI::l10n()->t('Finish date/time is not known or not relevant'),
'$n_checked' => $n_checked,
'$f_text' => DI::l10n()->t('Event Finishes:'),
'$f_dsel' => Temporal::getDateTimeField(
new DateTime(),
DateTime::createFromFormat('Y', intval($fyear) + 5),
DateTime::createFromFormat('Y-m-d H:i', "$fyear-$fmonth-$fday $fhour:$fminute"),
DI::l10n()->t('Event Finishes:'),
'finish_text',
true,
true,
'start_text'
),
'$d_text' => DI::l10n()->t('Description:'),
'$d_orig' => $d_orig,
'$l_text' => DI::l10n()->t('Location:'),
'$l_orig' => $l_orig,
'$t_text' => DI::l10n()->t('Title:') . ' <span class="required" title="' . DI::l10n()->t('Required') . '">*</span>',
'$t_orig' => $t_orig,
'$summary' => ['summary', DI::l10n()->t('Title:'), $t_orig, '', '*'],
'$sh_text' => DI::l10n()->t('Share this event'),
'$share' => ['share', DI::l10n()->t('Share this event'), $share_checked, '', $share_disabled],
'$sh_checked' => $share_checked,
'$nofinish' => ['nofinish', DI::l10n()->t('Finish date/time is not known or not relevant'), $n_checked],
'$preview' => DI::l10n()->t('Preview'),
'$acl' => $acl,
'$submit' => DI::l10n()->t('Submit'),
'$basic' => DI::l10n()->t('Basic'),
'$advanced' => DI::l10n()->t('Advanced'),
'$permissions' => DI::l10n()->t('Permissions'),
]);
return $o;
}
// Remove an event from the calendar and its related items
if ($mode === 'drop' && $event_id) {
$ev = Event::getListById(local_user(), $event_id);
// Delete only real events (no birthdays)
if (DBA::isResult($ev) && $ev[0]['type'] == 'event') {
Item::deleteForUser(['id' => $ev[0]['itemid']], local_user());
}
if (Post::exists(['id' => $ev[0]['itemid']])) {
notice(DI::l10n()->t('Failed to remove event'));
}
DI::baseUrl()->redirect('events');
}
}

View file

@ -1,160 +0,0 @@
<?php
/**
* @copyright Copyright (C) 2010-2022, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* @package Friendica\modules
* @subpackage FileBrowser
* @author Fabio Comuni <fabrixxm@kirgroup.com>
*/
use Friendica\App;
use Friendica\Core\Renderer;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\Photo;
use Friendica\Util\Images;
use Friendica\Util\Strings;
/**
* @param App $a
* @return string
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
function fbrowser_content(App $a)
{
if (!local_user()) {
System::exit();
}
if (DI::args()->getArgc() == 1) {
System::exit();
}
// Needed to match the correct template in a module that uses a different theme than the user/site/default
$theme = Strings::sanitizeFilePathItem($_GET['theme'] ?? '');
if ($theme && is_file("view/theme/$theme/config.php")) {
$a->setCurrentTheme($theme);
}
$template_file = "filebrowser.tpl";
$o = '';
switch (DI::args()->getArgv()[1]) {
case "image":
$path = ['' => DI::l10n()->t('Photos')];
$albums = false;
$sql_extra = "";
$sql_extra2 = " ORDER BY created DESC LIMIT 0, 10";
if (DI::args()->getArgc() == 2) {
$photos = DBA::toArray(DBA::p("SELECT distinct(`album`) AS `album` FROM `photo` WHERE `uid` = ? AND NOT `photo-type` IN (?, ?)",
local_user(),
Photo::CONTACT_AVATAR,
Photo::CONTACT_BANNER
));
$albums = array_column($photos, 'album');
}
if (DI::args()->getArgc() == 3) {
$album = DI::args()->getArgv()[2];
$sql_extra = sprintf("AND `album` = '%s' ", DBA::escape($album));
$sql_extra2 = "";
$path[$album] = $album;
}
$r = DBA::toArray(DBA::p("SELECT `resource-id`, ANY_VALUE(`id`) AS `id`, ANY_VALUE(`filename`) AS `filename`, ANY_VALUE(`type`) AS `type`,
min(`scale`) AS `hiq`, max(`scale`) AS `loq`, ANY_VALUE(`desc`) AS `desc`, ANY_VALUE(`created`) AS `created`
FROM `photo` WHERE `uid` = ? $sql_extra AND NOT `photo-type` IN (?, ?)
GROUP BY `resource-id` $sql_extra2",
local_user(),
Photo::CONTACT_AVATAR,
Photo::CONTACT_BANNER
));
function _map_files1($rr)
{
$a = DI::app();
$types = Images::supportedTypes();
$ext = $types[$rr['type']];
$filename_e = $rr['filename'];
// Take the largest picture that is smaller or equal 640 pixels
$photo = Photo::selectFirst(['scale'], ["`resource-id` = ? AND `height` <= ? AND `width` <= ?", $rr['resource-id'], 640, 640], ['order' => ['scale']]);
$scale = $photo['scale'] ?? $rr['loq'];
return [
DI::baseUrl() . '/photos/' . $a->getLoggedInUserNickname() . '/image/' . $rr['resource-id'],
$filename_e,
DI::baseUrl() . '/photo/' . $rr['resource-id'] . '-' . $scale . '.'. $ext,
$rr['desc']
];
}
$files = array_map("_map_files1", $r);
$tpl = Renderer::getMarkupTemplate($template_file);
$o = Renderer::replaceMacros($tpl, [
'$type' => 'image',
'$path' => $path,
'$folders' => $albums,
'$files' => $files,
'$cancel' => DI::l10n()->t('Cancel'),
'$nickname' => $a->getLoggedInUserNickname(),
'$upload' => DI::l10n()->t('Upload')
]);
break;
case "file":
if (DI::args()->getArgc()==2) {
$files = DBA::selectToArray('attach', ['id', 'filename', 'filetype'], ['uid' => local_user()]);
function _map_files2($rr)
{
list($m1, $m2) = explode("/", $rr['filetype']);
$filetype = ( (file_exists("images/icons/$m1.png"))?$m1:"zip");
$filename_e = $rr['filename'];
return [DI::baseUrl() . '/attach/' . $rr['id'], $filename_e, DI::baseUrl() . '/images/icons/16/' . $filetype . '.png'];
}
$files = array_map("_map_files2", $files);
$tpl = Renderer::getMarkupTemplate($template_file);
$o = Renderer::replaceMacros($tpl, [
'$type' => 'file',
'$path' => ['' => DI::l10n()->t('Files')],
'$folders' => false,
'$files' => $files,
'$cancel' => DI::l10n()->t('Cancel'),
'$nickname' => $a->getLoggedInUserNickname(),
'$upload' => DI::l10n()->t('Upload')
]);
}
break;
}
if (!empty($_GET['mode'])) {
return $o;
} else {
System::httpExit($o);
}
}

View file

@ -1,210 +0,0 @@
<?php
/**
* @copyright Copyright (C) 2010-2022, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
use Friendica\App;
use Friendica\Content\Widget;
use Friendica\Core\Protocol;
use Friendica\Core\Renderer;
use Friendica\DI;
use Friendica\Model\Contact;
use Friendica\Model\Profile;
use Friendica\Model\Item;
use Friendica\Network\Probe;
use Friendica\Database\DBA;
use Friendica\Model\Post;
use Friendica\Model\User;
use Friendica\Util\Strings;
function follow_post(App $a)
{
if (!local_user()) {
throw new \Friendica\Network\HTTPException\ForbiddenException(DI::l10n()->t('Access denied.'));
}
if (isset($_REQUEST['cancel'])) {
DI::baseUrl()->redirect('contact');
}
$url = Probe::cleanURI($_REQUEST['url']);
follow_process($a, $url);
}
function follow_content(App $a)
{
$return_path = 'contact';
if (!local_user()) {
notice(DI::l10n()->t('Permission denied.'));
DI::baseUrl()->redirect($return_path);
// NOTREACHED
}
$uid = local_user();
$url = Probe::cleanURI(trim($_REQUEST['url'] ?? ''));
// Issue 6874: Allow remote following from Peertube
if (strpos($url, 'acct:') === 0) {
$url = str_replace('acct:', '', $url);
}
if (!$url) {
DI::baseUrl()->redirect($return_path);
}
$submit = DI::l10n()->t('Submit Request');
// Don't try to add a pending contact
$user_contact = DBA::selectFirst('contact', ['pending'], ["`uid` = ? AND ((`rel` != ?) OR (`network` = ?)) AND
(`nurl` = ? OR `alias` = ? OR `alias` = ?) AND `network` != ?",
$uid, Contact::FOLLOWER, Protocol::DFRN, Strings::normaliseLink($url),
Strings::normaliseLink($url), $url, Protocol::STATUSNET]);
if (DBA::isResult($user_contact)) {
if ($user_contact['pending']) {
notice(DI::l10n()->t('You already added this contact.'));
$submit = '';
}
}
$contact = Contact::getByURL($url, true);
// Possibly it is a mail contact
if (empty($contact)) {
$contact = Probe::uri($url, Protocol::MAIL, $uid);
}
if (empty($contact) || ($contact['network'] == Protocol::PHANTOM)) {
// Possibly it is a remote item and not an account
follow_remote_item($url);
notice(DI::l10n()->t("The network type couldn't be detected. Contact can't be added."));
$submit = '';
$contact = ['url' => $url, 'network' => Protocol::PHANTOM, 'name' => $url, 'keywords' => ''];
}
$protocol = Contact::getProtocol($contact['url'], $contact['network']);
if (($protocol == Protocol::DIASPORA) && !DI::config()->get('system', 'diaspora_enabled')) {
notice(DI::l10n()->t("Diaspora support isn't enabled. Contact can't be added."));
$submit = '';
}
if (($protocol == Protocol::OSTATUS) && DI::config()->get('system', 'ostatus_disabled')) {
notice(DI::l10n()->t("OStatus support is disabled. Contact can't be added."));
$submit = '';
}
if ($protocol == Protocol::MAIL) {
$contact['url'] = $contact['addr'];
}
if (!empty($_REQUEST['auto'])) {
follow_process($a, $contact['url']);
}
$request = DI::baseUrl() . '/follow';
$tpl = Renderer::getMarkupTemplate('auto_request.tpl');
$owner = User::getOwnerDataById($uid);
if (empty($owner)) {
notice(DI::l10n()->t('Permission denied.'));
DI::baseUrl()->redirect($return_path);
// NOTREACHED
}
$myaddr = $owner['url'];
$o = Renderer::replaceMacros($tpl, [
'$header' => DI::l10n()->t('Connect/Follow'),
'$pls_answer' => DI::l10n()->t('Please answer the following:'),
'$your_address' => DI::l10n()->t('Your Identity Address:'),
'$url_label' => DI::l10n()->t('Profile URL'),
'$keywords_label'=> DI::l10n()->t('Tags:'),
'$submit' => $submit,
'$cancel' => DI::l10n()->t('Cancel'),
'$request' => $request,
'$name' => $contact['name'],
'$url' => $contact['url'],
'$zrl' => Profile::zrl($contact['url']),
'$myaddr' => $myaddr,
'$keywords' => $contact['keywords'],
'$does_know_you' => ['knowyou', DI::l10n()->t('%s knows you', $contact['name'])],
'$addnote_field' => ['dfrn-request-message', DI::l10n()->t('Add a personal note:')],
]);
DI::page()['aside'] = '';
if (!in_array($protocol, [Protocol::PHANTOM, Protocol::MAIL])) {
DI::page()['aside'] = Widget\VCard::getHTML($contact);
$o .= Renderer::replaceMacros(Renderer::getMarkupTemplate('section_title.tpl'),
['$title' => DI::l10n()->t('Status Messages and Posts')]
);
// Show last public posts
$o .= Contact::getPostsFromUrl($contact['url']);
}
return $o;
}
function follow_process(App $a, string $url)
{
$return_path = 'follow?url=' . urlencode($url);
$result = Contact::createFromProbeForUser($a->getLoggedInUserId(), $url);
if ($result['success'] == false) {
// Possibly it is a remote item and not an account
follow_remote_item($url);
if ($result['message']) {
notice($result['message']);
}
DI::baseUrl()->redirect($return_path);
} elseif ($result['cid']) {
DI::baseUrl()->redirect('contact/' . $result['cid']);
}
notice(DI::l10n()->t('The contact could not be added.'));
DI::baseUrl()->redirect($return_path);
}
function follow_remote_item($url)
{
$item_id = Item::fetchByLink($url, local_user());
if (!$item_id) {
// If the user-specific search failed, we search and probe a public post
$item_id = Item::fetchByLink($url);
}
if (!empty($item_id)) {
$item = Post::selectFirst(['guid'], ['id' => $item_id]);
if (DBA::isResult($item)) {
DI::baseUrl()->redirect('display/' . $item['guid']);
}
}
}

View file

@ -34,8 +34,8 @@ use Friendica\Content\Text\BBCode;
use Friendica\Core\Hook;
use Friendica\Core\Logger;
use Friendica\Core\Protocol;
use Friendica\Core\Session;
use Friendica\Core\System;
use Friendica\Core\Worker;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\Attach;
@ -57,11 +57,11 @@ use Friendica\Util\DateTimeFormat;
use Friendica\Util\ParseUrl;
function item_post(App $a) {
if (!Session::isAuthenticated()) {
if (!DI::userSession()->isAuthenticated()) {
throw new HTTPException\ForbiddenException();
}
$uid = local_user();
$uid = DI::userSession()->getLocalUserId();
if (!empty($_REQUEST['dropitems'])) {
$arr_drop = explode(',', $_REQUEST['dropitems']);
@ -77,8 +77,6 @@ function item_post(App $a) {
Logger::debug('postvars', ['_REQUEST' => $_REQUEST]);
$api_source = $_REQUEST['api_source'] ?? false;
$return_path = $_REQUEST['return'] ?? '';
$preview = intval($_REQUEST['preview'] ?? 0);
@ -90,7 +88,7 @@ function item_post(App $a) {
if (!$preview && !empty($_REQUEST['post_id_random'])) {
if (!empty($_SESSION['post-random']) && $_SESSION['post-random'] == $_REQUEST['post_id_random']) {
Logger::warning('duplicate post');
item_post_return(DI::baseUrl(), $api_source, $return_path);
item_post_return(DI::baseUrl(), $return_path);
} else {
$_SESSION['post-random'] = $_REQUEST['post_id_random'];
}
@ -106,7 +104,7 @@ function item_post(App $a) {
$toplevel_user_id = null;
$objecttype = null;
$profile_uid = ($_REQUEST['profile_uid'] ?? 0) ?: local_user();
$profile_uid = DI::userSession()->getLocalUserId();
$posttype = ($_REQUEST['post_type'] ?? '') ?: Item::PT_ARTICLE;
if ($parent_item_id || $thr_parent_uri) {
@ -122,13 +120,13 @@ function item_post(App $a) {
$thr_parent_uri = $parent_item['uri'];
$toplevel_item = $parent_item;
if ($parent_item['gravity'] != GRAVITY_PARENT) {
if ($parent_item['gravity'] != Item::GRAVITY_PARENT) {
$toplevel_item = Post::selectFirst(Item::ITEM_FIELDLIST, ['id' => $toplevel_item['parent']]);
}
}
if (!DBA::isResult($toplevel_item)) {
notice(DI::l10n()->t('Unable to locate original post.'));
DI::sysmsg()->addNotice(DI::l10n()->t('Unable to locate original post.'));
if ($return_path) {
DI::baseUrl()->redirect($return_path);
}
@ -138,7 +136,7 @@ function item_post(App $a) {
// When commenting on a public post then store the post for the current user
// This enables interaction like starring and saving into folders
if ($toplevel_item['uid'] == 0) {
$stored = Item::storeForUserByUriId($toplevel_item['uri-id'], local_user(), ['post-reason' => Item::PR_ACTIVITY]);
$stored = Item::storeForUserByUriId($toplevel_item['uri-id'], DI::userSession()->getLocalUserId(), ['post-reason' => Item::PR_ACTIVITY]);
Logger::info('Public item stored for user', ['uri-id' => $toplevel_item['uri-id'], 'uid' => $uid, 'stored' => $stored]);
if ($stored) {
$toplevel_item = Post::selectFirst(Item::ITEM_FIELDLIST, ['id' => $stored]);
@ -168,17 +166,17 @@ function item_post(App $a) {
}
// Ensure that the user id in a thread always stay the same
if (!is_null($toplevel_user_id) && in_array($toplevel_user_id, [local_user(), 0])) {
if (!is_null($toplevel_user_id) && in_array($toplevel_user_id, [DI::userSession()->getLocalUserId(), 0])) {
$profile_uid = $toplevel_user_id;
}
// Allow commenting if it is an answer to a public post
$allow_comment = local_user() && $toplevel_item_id && in_array($toplevel_item['private'], [Item::PUBLIC, Item::UNLISTED]) && in_array($toplevel_item['network'], Protocol::FEDERATED);
$allow_comment = DI::userSession()->getLocalUserId() && $toplevel_item_id && in_array($toplevel_item['private'], [Item::PUBLIC, Item::UNLISTED]) && in_array($toplevel_item['network'], Protocol::FEDERATED);
// Now check that valid personal details have been provided
if (!Security::canWriteToUserWall($profile_uid) && !$allow_comment) {
Logger::warning('Permission denied.', ['local' => local_user(), 'profile_uid' => $profile_uid, 'toplevel_item_id' => $toplevel_item_id, 'network' => $toplevel_item['network']]);
notice(DI::l10n()->t('Permission denied.'));
Logger::warning('Permission denied.', ['local' => DI::userSession()->getLocalUserId(), 'toplevel_item_id' => $toplevel_item_id, 'network' => $toplevel_item['network']]);
DI::sysmsg()->addNotice(DI::l10n()->t('Permission denied.'));
if ($return_path) {
DI::baseUrl()->redirect($return_path);
}
@ -241,6 +239,8 @@ function item_post(App $a) {
$att_bbcode = "\n" . PageInfo::getFooterFromData($attachment);
$body .= $att_bbcode;
} elseif (preg_match("/\[attachment\](.*?)\[\/attachment\]/ism", $body, $matches)) {
$body = preg_replace("/\[attachment].*?\[\/attachment\]/ism", PageInfo::getFooterFromUrl($matches[1]), $body);
}
// Convert links with empty descriptions to links without an explicit description
@ -307,7 +307,7 @@ function item_post(App $a) {
// for non native networks use the network of the original post as network of the item
if (($toplevel_item['network'] != Protocol::DIASPORA)
&& ($toplevel_item['network'] != Protocol::OSTATUS)
&& ($network == "")) {
&& ($network == '')) {
$network = $toplevel_item['network'];
}
@ -322,19 +322,12 @@ function item_post(App $a) {
$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) {
if (function_exists('imap_open') && !DI::config()->get('system', 'imap_disabled')) {
$pubmail_enabled = DBA::exists('mailacct', ["`uid` = ? AND `server` != ? AND `pubmail`", local_user(), '']);
}
}
if (!strlen($body)) {
if ($preview) {
System::jsonExit(['preview' => '']);
}
notice(DI::l10n()->t('Empty post discarded.'));
DI::sysmsg()->addNotice(DI::l10n()->t('Empty post discarded.'));
if ($return_path) {
DI::baseUrl()->redirect($return_path);
}
@ -362,11 +355,11 @@ function item_post(App $a) {
$self = false;
$contact_id = 0;
if (local_user() && ((local_user() == $profile_uid) || $allow_comment)) {
if (DI::userSession()->getLocalUserId() && ((DI::userSession()->getLocalUserId() == $profile_uid) || $allow_comment)) {
$self = true;
$author = DBA::selectFirst('contact', [], ['uid' => local_user(), 'self' => true]);
} elseif (!empty(Session::getRemoteContactID($profile_uid))) {
$author = DBA::selectFirst('contact', [], ['id' => Session::getRemoteContactID($profile_uid)]);
$author = DBA::selectFirst('contact', [], ['uid' => DI::userSession()->getLocalUserId(), 'self' => true]);
} elseif (!empty(DI::userSession()->getRemoteContactID($profile_uid))) {
$author = DBA::selectFirst('contact', [], ['id' => DI::userSession()->getRemoteContactID($profile_uid)]);
}
if (DBA::isResult($author)) {
@ -374,7 +367,7 @@ function item_post(App $a) {
}
// get contact info for owner
if ($profile_uid == local_user() || $allow_comment) {
if ($profile_uid == DI::userSession()->getLocalUserId() || $allow_comment) {
$contact_record = $author ?: [];
} else {
$contact_record = DBA::selectFirst('contact', [], ['uid' => $profile_uid, 'self' => true]) ?: [];
@ -384,8 +377,8 @@ function item_post(App $a) {
if ($posttype != Item::PT_PERSONAL_NOTE) {
// Look for any tags and linkify them
$item = [
'uid' => local_user() ? local_user() : $profile_uid,
'gravity' => $toplevel_item_id ? GRAVITY_COMMENT : GRAVITY_PARENT,
'uid' => DI::userSession()->getLocalUserId() ? DI::userSession()->getLocalUserId() : $profile_uid,
'gravity' => $toplevel_item_id ? Item::GRAVITY_COMMENT : Item::GRAVITY_PARENT,
'network' => $network,
'body' => $body,
'postopts' => $postopts,
@ -462,7 +455,7 @@ function item_post(App $a) {
$data = BBCode::getAttachmentData($body);
$match = [];
if ((preg_match_all("/\[bookmark\=([^\]]*)\](.*?)\[\/bookmark\]/ism", $body, $match, PREG_SET_ORDER) || isset($data["type"]))
if ((preg_match_all("/\[bookmark\=([^\]]*)\](.*?)\[\/bookmark\]/ism", $body, $match, PREG_SET_ORDER) || isset($data['type']))
&& ($posttype != Item::PT_PERSONAL_NOTE)) {
$posttype = Item::PT_PAGE;
$objecttype = Activity\ObjectType::BOOKMARK;
@ -477,11 +470,11 @@ function item_post(App $a) {
$objecttype = Activity\ObjectType::NOTE; // Default value
$objectdata = BBCode::getAttachedData($body);
if ($objectdata["type"] == "link") {
if ($objectdata['type'] == 'link') {
$objecttype = Activity\ObjectType::BOOKMARK;
} elseif ($objectdata["type"] == "video") {
} elseif ($objectdata['type'] == 'video') {
$objecttype = Activity\ObjectType::VIDEO;
} elseif ($objectdata["type"] == "photo") {
} elseif ($objectdata['type'] == 'photo') {
$objecttype = Activity\ObjectType::IMAGE;
}
@ -509,11 +502,11 @@ function item_post(App $a) {
$verb = Activity::POST;
}
if ($network == "") {
if ($network == '') {
$network = Protocol::DFRN;
}
$gravity = ($toplevel_item_id ? GRAVITY_COMMENT : GRAVITY_PARENT);
$gravity = ($toplevel_item_id ? Item::GRAVITY_COMMENT : Item::GRAVITY_PARENT);
// even if the post arrived via API we are considering that it
// originated on this site by default for determining relayability.
@ -532,68 +525,65 @@ function item_post(App $a) {
$thr_parent_uri = $uri;
}
$datarray = [];
$datarray['uid'] = $profile_uid;
$datarray['wall'] = $wall;
$datarray['gravity'] = $gravity;
$datarray['network'] = $network;
$datarray['contact-id'] = $contact_id;
$datarray['owner-name'] = $contact_record['name'] ?? '';
$datarray['owner-link'] = $contact_record['url'] ?? '';
$datarray['owner-avatar'] = $contact_record['thumb'] ?? '';
$datarray['owner-id'] = Contact::getIdForURL($datarray['owner-link']);
$datarray['author-name'] = $author['name'];
$datarray['author-link'] = $author['url'];
$datarray['author-avatar'] = $author['thumb'];
$datarray['author-id'] = Contact::getIdForURL($datarray['author-link']);
$datarray['created'] = empty($_REQUEST['created_at']) ? DateTimeFormat::utcNow() : $_REQUEST['created_at'];
$datarray['edited'] = $datarray['created'];
$datarray['commented'] = $datarray['created'];
$datarray['changed'] = $datarray['created'];
$datarray['received'] = DateTimeFormat::utcNow();
$datarray['extid'] = $extid;
$datarray['guid'] = $guid;
$datarray['uri'] = $uri;
$datarray['title'] = $title;
$datarray['body'] = $body;
$datarray['app'] = $app;
$datarray['location'] = $location;
$datarray['coord'] = $coord;
$datarray['file'] = $categories;
$datarray['inform'] = $inform;
$datarray['verb'] = $verb;
$datarray['post-type'] = $posttype;
$datarray['object-type'] = $objecttype;
$datarray['allow_cid'] = $str_contact_allow;
$datarray['allow_gid'] = $str_group_allow;
$datarray['deny_cid'] = $str_contact_deny;
$datarray['deny_gid'] = $str_group_deny;
$datarray['private'] = $private;
$datarray['pubmail'] = $pubmail_enabled;
$datarray['attach'] = $attachments;
$datarray = [
'uid' => $profile_uid,
'wall' => $wall,
'gravity' => $gravity,
'network' => $network,
'contact-id' => $contact_id,
'owner-name' => $contact_record['name'] ?? '',
'owner-link' => $contact_record['url'] ?? '',
'owner-avatar' => $contact_record['thumb'] ?? '',
'author-name' => $author['name'],
'author-link' => $author['url'],
'author-avatar' => $author['thumb'],
'created' => empty($_REQUEST['created_at']) ? DateTimeFormat::utcNow() : $_REQUEST['created_at'],
'received' => DateTimeFormat::utcNow(),
'extid' => $extid,
'guid' => $guid,
'uri' => $uri,
'title' => $title,
'body' => $body,
'app' => $app,
'location' => $location,
'coord' => $coord,
'file' => $categories,
'inform' => $inform,
'verb' => $verb,
'post-type' => $posttype,
'object-type' => $objecttype,
'allow_cid' => $str_contact_allow,
'allow_gid' => $str_group_allow,
'deny_cid' => $str_contact_deny,
'deny_gid' => $str_group_deny,
'private' => $private,
'pubmail' => $pubmail_enabled,
'attach' => $attachments,
'thr-parent' => $thr_parent_uri,
'postopts' => $postopts,
'origin' => $origin,
'object' => $object,
'attachments' => $_REQUEST['attachments'] ?? [],
/*
* These fields are for the convenience of addons...
* 'self' if true indicates the owner is posting on their own wall
* If parent is 0 it is a top-level post.
*/
'parent' => $toplevel_item_id,
'self' => $self,
// This triggers posts via API and the mirror functions
'api_source' => false,
// This field is for storing the raw conversation data
'protocol' => Conversation::PARCEL_DIRECT,
'direction' => Conversation::PUSH,
];
$datarray['thr-parent'] = $thr_parent_uri;
$datarray['postopts'] = $postopts;
$datarray['origin'] = $origin;
$datarray['object'] = $object;
$datarray['attachments'] = $_REQUEST['attachments'] ?? [];
/*
* These fields are for the convenience of addons...
* 'self' if true indicates the owner is posting on their own wall
* If parent is 0 it is a top-level post.
*/
$datarray['parent'] = $toplevel_item_id;
$datarray['self'] = $self;
// This triggers posts via API and the mirror functions
$datarray['api_source'] = $api_source;
// This field is for storing the raw conversation data
$datarray['protocol'] = Conversation::PARCEL_DIRECT;
$datarray['direction'] = Conversation::PUSH;
// These cannot be part of above initialization ...
$datarray['edited'] = $datarray['created'];
$datarray['commented'] = $datarray['created'];
$datarray['changed'] = $datarray['created'];
$datarray['owner-id'] = Contact::getIdForURL($datarray['owner-link']);
$datarray['author-id'] = Contact::getIdForURL($datarray['author-link']);
$datarray['edit'] = $orig_post;
@ -606,15 +596,16 @@ function item_post(App $a) {
if ($preview) {
// We set the datarray ID to -1 because in preview mode the dataray
// doesn't have an ID.
$datarray["id"] = -1;
$datarray["uri-id"] = -1;
$datarray["author-network"] = Protocol::DFRN;
$datarray["author-updated"] = '';
$datarray["author-gsid"] = 0;
$datarray["author-uri-id"] = ItemURI::getIdByURI($datarray["author-link"]);
$datarray["owner-updated"] = '';
$datarray["has-media"] = false;
$datarray['body'] = Item::improveSharedDataInBody($datarray);
$datarray['id'] = -1;
$datarray['uri-id'] = -1;
$datarray['author-network'] = Protocol::DFRN;
$datarray['author-updated'] = '';
$datarray['author-gsid'] = 0;
$datarray['author-uri-id'] = ItemURI::getIdByURI($datarray['author-link']);
$datarray['owner-updated'] = '';
$datarray['has-media'] = false;
$datarray['quote-uri-id'] = Item::getQuoteUriId($datarray['body'], $datarray['uid']);
$datarray['body'] = BBCode::removeSharedData($datarray['body']);
$o = DI::conversation()->create([array_merge($contact_record, $datarray)], 'search', false, true);
@ -635,8 +626,8 @@ function item_post(App $a) {
unset($datarray['self']);
unset($datarray['api_source']);
Post\Delayed::add($datarray['uri'], $datarray, PRIORITY_HIGH, Post\Delayed::PREPARED_NO_HOOK, $scheduled_at);
item_post_return(DI::baseUrl(), $api_source, $return_path);
Post\Delayed::add($datarray['uri'], $datarray, Worker::PRIORITY_HIGH, Post\Delayed::PREPARED_NO_HOOK, $scheduled_at);
item_post_return(DI::baseUrl(), $return_path);
}
}
@ -655,15 +646,20 @@ function item_post(App $a) {
}
$datarray['uri-id'] = ItemURI::getIdByURI($datarray['uri']);
$datarray['body'] = Item::improveSharedDataInBody($datarray);
if ($orig_post) {
$quote_uri_id = Item::getQuoteUriId($datarray['body'], $datarray['uid']);
if (!empty($quote_uri_id)) {
$datarray['quote-uri-id'] = $quote_uri_id;
$datarray['body'] = BBCode::removeSharedData($datarray['body']);
}
if ($orig_post) {
$fields = [
'title' => $datarray['title'],
'body' => $datarray['body'],
'attach' => $datarray['attach'],
'file' => $datarray['file'],
'edited' => DateTimeFormat::utcNow(),
'title' => $datarray['title'],
'body' => $datarray['body'],
'attach' => $datarray['attach'],
'file' => $datarray['file'],
'edited' => DateTimeFormat::utcNow(),
'changed' => DateTimeFormat::utcNow()
];
@ -684,7 +680,7 @@ function item_post(App $a) {
$post_id = Item::insert($datarray);
if (!$post_id) {
notice(DI::l10n()->t('Item wasn\'t stored.'));
DI::sysmsg()->addNotice(DI::l10n()->t('Item wasn\'t stored.'));
if ($return_path) {
DI::baseUrl()->redirect($return_path);
}
@ -705,7 +701,7 @@ function item_post(App $a) {
Tag::storeFromBody($datarray['uri-id'], $datarray['body']);
if (!\Friendica\Content\Feature::isEnabled($uid, 'explicit_mentions') && ($datarray['gravity'] == GRAVITY_COMMENT)) {
if (!\Friendica\Content\Feature::isEnabled($uid, 'explicit_mentions') && ($datarray['gravity'] == Item::GRAVITY_COMMENT)) {
Tag::createImplicitMentions($datarray['uri-id'], $datarray['thr-parent-id']);
}
@ -736,7 +732,7 @@ function item_post(App $a) {
Hook::callAll('post_local_end', $datarray);
if (strlen($emailcc) && $profile_uid == local_user()) {
if (strlen($emailcc) && $profile_uid == DI::userSession()->getLocalUserId()) {
$recipients = explode(',', $emailcc);
if (count($recipients)) {
foreach ($recipients as $recipient) {
@ -752,20 +748,12 @@ function item_post(App $a) {
Logger::debug('post_complete');
if ($api_source) {
return $post_id;
}
item_post_return(DI::baseUrl(), $api_source, $return_path);
item_post_return(DI::baseUrl(), $return_path);
// NOTREACHED
}
function item_post_return($baseurl, $api_source, $return_path)
function item_post_return($baseurl, $return_path)
{
if ($api_source) {
return;
}
if ($return_path) {
DI::baseUrl()->redirect($return_path);
}
@ -782,7 +770,7 @@ function item_post_return($baseurl, $api_source, $return_path)
function item_content(App $a)
{
if (!Session::isAuthenticated()) {
if (!DI::userSession()->isAuthenticated()) {
throw new HTTPException\UnauthorizedException();
}
@ -796,9 +784,9 @@ function item_content(App $a)
switch ($args->get(1)) {
case 'drop':
if (DI::mode()->isAjax()) {
Item::deleteForUser(['id' => $args->get(2)], local_user());
Item::deleteForUser(['id' => $args->get(2)], DI::userSession()->getLocalUserId());
// ajax return: [<item id>, 0 (no perm) | <owner id>]
System::jsonExit([intval($args->get(2)), local_user()]);
System::jsonExit([intval($args->get(2)), DI::userSession()->getLocalUserId()]);
} else {
if (!empty($args->get(3))) {
$o = drop_item($args->get(2), $args->get(3));
@ -807,17 +795,18 @@ function item_content(App $a)
}
}
break;
case 'block':
$item = Post::selectFirstForUser(local_user(), ['guid', 'author-id', 'parent', 'gravity'], ['id' => $args->get(2)]);
$item = Post::selectFirstForUser(DI::userSession()->getLocalUserId(), ['guid', 'author-id', 'parent', 'gravity'], ['id' => $args->get(2)]);
if (empty($item['author-id'])) {
throw new HTTPException\NotFoundException('Item not found');
}
Contact\User::setBlocked($item['author-id'], local_user(), true);
Contact\User::setBlocked($item['author-id'], DI::userSession()->getLocalUserId(), true);
if (DI::mode()->isAjax()) {
// ajax return: [<item id>, 0 (no perm) | <owner id>]
System::jsonExit([intval($args->get(2)), local_user()]);
System::jsonExit([intval($args->get(2)), DI::userSession()->getLocalUserId()]);
} else {
item_redirect_after_action($item, $args->get(3));
}
@ -833,15 +822,15 @@ function item_content(App $a)
* @return string
* @throws HTTPException\InternalServerErrorException
*/
function drop_item(int $id, string $return = '')
function drop_item(int $id, string $return = ''): string
{
// locate item to be deleted
$fields = ['id', 'uid', 'guid', 'contact-id', 'deleted', 'gravity', 'parent'];
$item = Post::selectFirstForUser(local_user(), $fields, ['id' => $id]);
// Locate item to be deleted
$item = Post::selectFirstForUser(DI::userSession()->getLocalUserId(), ['id', 'uid', 'guid', 'contact-id', 'deleted', 'gravity', 'parent'], ['id' => $id]);
if (!DBA::isResult($item)) {
notice(DI::l10n()->t('Item not found.'));
DI::sysmsg()->addNotice(DI::l10n()->t('Item not found.'));
DI::baseUrl()->redirect('network');
//NOTREACHED
}
if ($item['deleted']) {
@ -851,18 +840,19 @@ function drop_item(int $id, string $return = '')
$contact_id = 0;
// check if logged in user is either the author or owner of this item
if (Session::getRemoteContactID($item['uid']) == $item['contact-id']) {
if (DI::userSession()->getRemoteContactID($item['uid']) == $item['contact-id']) {
$contact_id = $item['contact-id'];
}
if ((local_user() == $item['uid']) || $contact_id) {
if ((DI::userSession()->getLocalUserId() == $item['uid']) || $contact_id) {
// delete the item
Item::deleteForUser(['id' => $item['id']], local_user());
Item::deleteForUser(['id' => $item['id']], DI::userSession()->getLocalUserId());
item_redirect_after_action($item, $return);
//NOTREACHED
} else {
Logger::warning('Permission denied.', ['local' => local_user(), 'uid' => $item['uid'], 'cid' => $contact_id]);
notice(DI::l10n()->t('Permission denied.'));
Logger::warning('Permission denied.', ['local' => DI::userSession()->getLocalUserId(), 'uid' => $item['uid'], 'cid' => $contact_id]);
DI::sysmsg()->addNotice(DI::l10n()->t('Permission denied.'));
DI::baseUrl()->redirect('display/' . $item['guid']);
//NOTREACHED
}
@ -870,17 +860,17 @@ function drop_item(int $id, string $return = '')
return '';
}
function item_redirect_after_action($item, $returnUrlHex)
function item_redirect_after_action(array $item, string $returnUrlHex)
{
$return_url = hex2bin($returnUrlHex);
// removes update_* from return_url to ignore Ajax refresh
$return_url = str_replace("update_", "", $return_url);
$return_url = str_replace('update_', '', $return_url);
// Check if delete a comment
if ($item['gravity'] == GRAVITY_COMMENT) {
if ($item['gravity'] == Item::GRAVITY_COMMENT) {
if (!empty($item['parent'])) {
$parentitem = Post::selectFirstForUser(local_user(), ['guid'], ['id' => $item['parent']]);
$parentitem = Post::selectFirstForUser(DI::userSession()->getLocalUserId(), ['guid'], ['id' => $item['parent']]);
}
// Return to parent guid

View file

@ -34,10 +34,10 @@ function lostpass_post(App $a)
DI::baseUrl()->redirect();
}
$condition = ['(`email` = ? OR `nickname` = ?) AND `verified` = 1 AND `blocked` = 0', $loginame, $loginame];
$condition = ['(`email` = ? OR `nickname` = ?) AND `verified` = 1 AND `blocked` = 0 AND `account_removed` = 0 AND `account_expired` = 0', $loginame, $loginame];
$user = DBA::selectFirst('user', ['uid', 'username', 'nickname', 'email', 'language'], $condition);
if (!DBA::isResult($user)) {
notice(DI::l10n()->t('No valid account found.'));
DI::sysmsg()->addNotice(DI::l10n()->t('No valid account found.'));
DI::baseUrl()->redirect();
}
@ -49,7 +49,7 @@ function lostpass_post(App $a)
];
$result = DBA::update('user', $fields, ['uid' => $user['uid']]);
if ($result) {
info(DI::l10n()->t('Password reset request issued. Check your email.'));
DI::sysmsg()->addInfo(DI::l10n()->t('Password reset request issued. Check your email.'));
}
$sitename = DI::config()->get('config', 'sitename');
@ -97,7 +97,7 @@ function lostpass_content(App $a)
$user = DBA::selectFirst('user', ['uid', 'username', 'nickname', 'email', 'pwdreset_time', 'language'], ['pwdreset' => hash('sha256', $pwdreset_token)]);
if (!DBA::isResult($user)) {
notice(DI::l10n()->t("Request could not be verified. \x28You may have previously submitted it.\x29 Password reset failed."));
DI::sysmsg()->addNotice(DI::l10n()->t("Request could not be verified. \x28You may have previously submitted it.\x29 Password reset failed."));
return lostpass_form();
}
@ -110,7 +110,7 @@ function lostpass_content(App $a)
];
DBA::update('user', $fields, ['uid' => $user['uid']]);
notice(DI::l10n()->t('Request has expired, please make a new one.'));
DI::sysmsg()->addNotice(DI::l10n()->t('Request has expired, please make a new one.'));
return lostpass_form();
}
@ -152,7 +152,7 @@ function lostpass_generate_password($user)
'$newpass' => $new_password,
]);
info(DI::l10n()->t("Your password has been reset."));
DI::sysmsg()->addInfo(DI::l10n()->t("Your password has been reset."));
$sitename = DI::config()->get('config', 'sitename');
$preamble = Strings::deindent(DI::l10n()->t('

View file

@ -1,132 +0,0 @@
<?php
/**
* @copyright Copyright (C) 2010-2022, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
use Friendica\App;
use Friendica\Content\Widget;
use Friendica\Core\Renderer;
use Friendica\Core\Search;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\Contact;
use Friendica\Model\Profile;
use Friendica\Module\Contact as ModuleContact;
/**
* Controller for /match.
*
* It takes keywords from your profile and queries the directory server for
* matching keywords from other profiles.
*
* @param App $a App
*
* @return string
* @throws ImagickException
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
* @throws Exception
*/
function match_content(App $a)
{
if (!local_user()) {
return '';
}
DI::page()['aside'] .= Widget::findPeople();
DI::page()['aside'] .= Widget::follow();
$_SESSION['return_path'] = DI::args()->getCommand();
$profile = Profile::getByUID(local_user());
if (!DBA::isResult($profile)) {
return '';
}
if (!$profile['pub_keywords'] && (!$profile['prv_keywords'])) {
notice(DI::l10n()->t('No keywords to match. Please add keywords to your profile.'));
return '';
}
$params = [];
$tags = trim($profile['pub_keywords'] . ' ' . $profile['prv_keywords']);
if (DI::mode()->isMobile()) {
$limit = DI::pConfig()->get(local_user(), 'system', 'itemspage_mobile_network',
DI::config()->get('system', 'itemspage_network_mobile'));
} else {
$limit = DI::pConfig()->get(local_user(), 'system', 'itemspage_network',
DI::config()->get('system', 'itemspage_network'));
}
$params['s'] = $tags;
$params['n'] = 100;
$entries = [];
foreach ([Search::getGlobalDirectory(), DI::baseUrl()] as $server) {
if (empty($server)) {
continue;
}
$msearch = json_decode(DI::httpClient()->post($server . '/msearch', $params)->getBody());
if (!empty($msearch)) {
$entries = match_get_contacts($msearch, $entries, $limit);
}
}
if (empty($entries)) {
info(DI::l10n()->t('No matches'));
}
$tpl = Renderer::getMarkupTemplate('viewcontact_template.tpl');
$o = Renderer::replaceMacros($tpl, [
'$title' => DI::l10n()->t('Profile Match'),
'$contacts' => array_slice($entries, 0, $limit),
]);
return $o;
}
function match_get_contacts($msearch, $entries, $limit)
{
if (empty($msearch->results)) {
return $entries;
}
foreach ($msearch->results as $profile) {
if (!$profile) {
continue;
}
// Already known contact
$contact = Contact::getByURL($profile->url, null, ['rel'], local_user());
if (!empty($contact) && in_array($contact['rel'], [Contact::FRIEND, Contact::SHARING])) {
continue;
}
$contact = Contact::getByURLForUser($profile->url, local_user());
if (!empty($contact)) {
$entries[$contact['id']] = ModuleContact::getContactTemplateVars($contact);
}
if (count($entries) == $limit) {
break;
}
}
return $entries;
}

View file

@ -39,34 +39,34 @@ function message_init(App $a)
$tabs = '';
if (DI::args()->getArgc() > 1 && is_numeric(DI::args()->getArgv()[1])) {
$tabs = render_messages(get_messages(local_user(), 0, 5), 'mail_list.tpl');
$tabs = render_messages(get_messages(DI::userSession()->getLocalUserId(), 0, 5), 'mail_list.tpl');
}
$new = [
'label' => DI::l10n()->t('New Message'),
'url' => 'message/new',
'sel' => DI::args()->getArgc() > 1 && DI::args()->getArgv()[1] == 'new',
'label' => DI::l10n()->t('New Message'),
'url' => 'message/new',
'sel' => DI::args()->getArgc() > 1 && DI::args()->getArgv()[1] == 'new',
'accesskey' => 'm',
];
$tpl = Renderer::getMarkupTemplate('message_side.tpl');
DI::page()['aside'] = Renderer::replaceMacros($tpl, [
'$tabs' => $tabs,
'$new' => $new,
'$new' => $new,
]);
$base = DI::baseUrl();
$head_tpl = Renderer::getMarkupTemplate('message-head.tpl');
DI::page()['htmlhead'] .= Renderer::replaceMacros($head_tpl, [
'$baseurl' => DI::baseUrl()->get(true),
'$base' => $base
'$base' => $base
]);
}
function message_post(App $a)
{
if (!local_user()) {
notice(DI::l10n()->t('Permission denied.'));
if (!DI::userSession()->getLocalUserId()) {
DI::sysmsg()->addNotice(DI::l10n()->t('Permission denied.'));
return;
}
@ -80,17 +80,20 @@ function message_post(App $a)
switch ($ret) {
case -1:
notice(DI::l10n()->t('No recipient selected.'));
DI::sysmsg()->addNotice(DI::l10n()->t('No recipient selected.'));
$norecip = true;
break;
case -2:
notice(DI::l10n()->t('Unable to locate contact information.'));
DI::sysmsg()->addNotice(DI::l10n()->t('Unable to locate contact information.'));
break;
case -3:
notice(DI::l10n()->t('Message could not be sent.'));
DI::sysmsg()->addNotice(DI::l10n()->t('Message could not be sent.'));
break;
case -4:
notice(DI::l10n()->t('Message collection failure.'));
DI::sysmsg()->addNotice(DI::l10n()->t('Message collection failure.'));
break;
}
@ -107,8 +110,8 @@ function message_content(App $a)
$o = '';
Nav::setSelected('messages');
if (!local_user()) {
notice(DI::l10n()->t('Permission denied.'));
if (!DI::userSession()->getLocalUserId()) {
DI::sysmsg()->addNotice(DI::l10n()->t('Permission denied.'));
return Login::form();
}
@ -118,20 +121,20 @@ function message_content(App $a)
if (DI::args()->getArgc() > 1 && DI::args()->getArgv()[1] == 'new') {
$button = [
'label' => DI::l10n()->t('Discard'),
'url' => '/message',
'sel' => 'close',
'url' => '/message',
'sel' => 'close',
];
} else {
$button = [
'label' => DI::l10n()->t('New Message'),
'url' => '/message/new',
'sel' => 'new',
'label' => DI::l10n()->t('New Message'),
'url' => '/message/new',
'sel' => 'new',
'accesskey' => 'm',
];
}
$header = Renderer::replaceMacros($tpl, [
'$messages' => DI::l10n()->t('Messages'),
'$button' => $button,
'$button' => $button,
]);
if ((DI::args()->getArgc() == 3) && (DI::args()->getArgv()[1] === 'drop' || DI::args()->getArgv()[1] === 'dropconv')) {
@ -141,29 +144,29 @@ function message_content(App $a)
$cmd = DI::args()->getArgv()[1];
if ($cmd === 'drop') {
$message = DBA::selectFirst('mail', ['convid'], ['id' => DI::args()->getArgv()[2], 'uid' => local_user()]);
$message = DBA::selectFirst('mail', ['convid'], ['id' => DI::args()->getArgv()[2], 'uid' => DI::userSession()->getLocalUserId()]);
if(!DBA::isResult($message)){
notice(DI::l10n()->t('Conversation not found.'));
DI::sysmsg()->addNotice(DI::l10n()->t('Conversation not found.'));
DI::baseUrl()->redirect('message');
}
if (!DBA::delete('mail', ['id' => DI::args()->getArgv()[2], 'uid' => local_user()])) {
notice(DI::l10n()->t('Message was not deleted.'));
if (!DBA::delete('mail', ['id' => DI::args()->getArgv()[2], 'uid' => DI::userSession()->getLocalUserId()])) {
DI::sysmsg()->addNotice(DI::l10n()->t('Message was not deleted.'));
}
$conversation = DBA::selectFirst('mail', ['id'], ['convid' => $message['convid'], 'uid' => local_user()]);
$conversation = DBA::selectFirst('mail', ['id'], ['convid' => $message['convid'], 'uid' => DI::userSession()->getLocalUserId()]);
if(!DBA::isResult($conversation)){
DI::baseUrl()->redirect('message');
}
DI::baseUrl()->redirect('message/' . $conversation['id'] );
} else {
$parentmail = DBA::selectFirst('mail', ['parent-uri'], ['id' => DI::args()->getArgv()[2], 'uid' => local_user()]);
$parentmail = DBA::selectFirst('mail', ['parent-uri'], ['id' => DI::args()->getArgv()[2], 'uid' => DI::userSession()->getLocalUserId()]);
if (DBA::isResult($parentmail)) {
$parent = $parentmail['parent-uri'];
if (!DBA::delete('mail', ['parent-uri' => $parent, 'uid' => local_user()])) {
notice(DI::l10n()->t('Conversation was not removed.'));
if (!DBA::delete('mail', ['parent-uri' => $parent, 'uid' => DI::userSession()->getLocalUserId()])) {
DI::sysmsg()->addNotice(DI::l10n()->t('Conversation was not removed.'));
}
}
DI::baseUrl()->redirect('message');
@ -186,19 +189,19 @@ function message_content(App $a)
$tpl = Renderer::getMarkupTemplate('prv_message.tpl');
$o .= Renderer::replaceMacros($tpl, [
'$header' => DI::l10n()->t('Send Private Message'),
'$to' => DI::l10n()->t('To:'),
'$subject' => DI::l10n()->t('Subject:'),
'$subjtxt' => $_REQUEST['subject'] ?? '',
'$text' => $_REQUEST['body'] ?? '',
'$readonly' => '',
'$yourmessage'=> DI::l10n()->t('Your message:'),
'$select' => $select,
'$parent' => '',
'$upload' => DI::l10n()->t('Upload photo'),
'$insert' => DI::l10n()->t('Insert web link'),
'$wait' => DI::l10n()->t('Please wait'),
'$submit' => DI::l10n()->t('Submit')
'$header' => DI::l10n()->t('Send Private Message'),
'$to' => DI::l10n()->t('To:'),
'$subject' => DI::l10n()->t('Subject:'),
'$subjtxt' => $_REQUEST['subject'] ?? '',
'$text' => $_REQUEST['body'] ?? '',
'$readonly' => '',
'$yourmessage' => DI::l10n()->t('Your message:'),
'$select' => $select,
'$parent' => '',
'$upload' => DI::l10n()->t('Upload photo'),
'$insert' => DI::l10n()->t('Insert web link'),
'$wait' => DI::l10n()->t('Please wait'),
'$submit' => DI::l10n()->t('Submit')
]);
return $o;
}
@ -212,14 +215,14 @@ function message_content(App $a)
$o .= $header;
$total = DBA::count('mail', ['uid' => local_user()], ['distinct' => true, 'expression' => 'parent-uri']);
$total = DBA::count('mail', ['uid' => DI::userSession()->getLocalUserId()], ['distinct' => true, 'expression' => 'parent-uri']);
$pager = new Pager(DI::l10n(), DI::args()->getQueryString());
$r = get_messages(local_user(), $pager->getStart(), $pager->getItemsPerPage());
$r = get_messages(DI::userSession()->getLocalUserId(), $pager->getStart(), $pager->getItemsPerPage());
if (!DBA::isResult($r)) {
notice(DI::l10n()->t('No messages.'));
DI::sysmsg()->addNotice(DI::l10n()->t('No messages.'));
return $o;
}
@ -240,14 +243,14 @@ function message_content(App $a)
LEFT JOIN `contact` ON `mail`.`contact-id` = `contact`.`id`
WHERE `mail`.`uid` = ? AND `mail`.`id` = ?
LIMIT 1",
local_user(),
DI::userSession()->getLocalUserId(),
DI::args()->getArgv()[1]
);
if (DBA::isResult($message)) {
$contact_id = $message['contact-id'];
$params = [
local_user(),
DI::userSession()->getLocalUserId(),
$message['parent-uri']
];
@ -269,13 +272,13 @@ function message_content(App $a)
$messages = DBA::toArray($messages_stmt);
DBA::update('mail', ['seen' => 1], ['parent-uri' => $message['parent-uri'], 'uid' => local_user()]);
DBA::update('mail', ['seen' => 1], ['parent-uri' => $message['parent-uri'], 'uid' => DI::userSession()->getLocalUserId()]);
} else {
$messages = false;
}
if (!DBA::isResult($messages)) {
notice(DI::l10n()->t('Message not available.'));
DI::sysmsg()->addNotice(DI::l10n()->t('Message not available.'));
return $o;
}
@ -312,18 +315,18 @@ function message_content(App $a)
$from_photo = Contact::getThumb($contact);
$mails[] = [
'id' => $message['id'],
'from_name' => $from_name_e,
'from_url' => $from_url,
'from_addr' => $contact['addr'] ?? $from_url,
'sparkle' => $sparkle,
'id' => $message['id'],
'from_name' => $from_name_e,
'from_url' => $from_url,
'from_addr' => $contact['addr'] ?? $from_url,
'sparkle' => $sparkle,
'from_photo' => $from_photo,
'subject' => $subject_e,
'body' => $body_e,
'delete' => DI::l10n()->t('Delete message'),
'to_name' => $to_name_e,
'date' => DateTimeFormat::local($message['created'], DI::l10n()->t('D, d M Y - g:i A')),
'ago' => Temporal::getRelativeDate($message['created']),
'subject' => $subject_e,
'body' => $body_e,
'delete' => DI::l10n()->t('Delete message'),
'to_name' => $to_name_e,
'date' => DateTimeFormat::local($message['created'], DI::l10n()->t('D, d M Y - g:i A')),
'ago' => Temporal::getRelativeDate($message['created']),
];
$seen = $message['seen'];
@ -334,28 +337,27 @@ function message_content(App $a)
$tpl = Renderer::getMarkupTemplate('mail_display.tpl');
$o = Renderer::replaceMacros($tpl, [
'$thread_id' => DI::args()->getArgv()[1],
'$thread_id' => DI::args()->getArgv()[1],
'$thread_subject' => $message['title'],
'$thread_seen' => $seen,
'$delete' => DI::l10n()->t('Delete conversation'),
'$canreply' => (($unknown) ? false : '1'),
'$unknown_text' => DI::l10n()->t("No secure communications available. You <strong>may</strong> be able to respond from the sender's profile page."),
'$mails' => $mails,
'$thread_seen' => $seen,
'$delete' => DI::l10n()->t('Delete conversation'),
'$canreply' => (($unknown) ? false : '1'),
'$unknown_text' => DI::l10n()->t("No secure communications available. You <strong>may</strong> be able to respond from the sender's profile page."),
'$mails' => $mails,
// reply
'$header' => DI::l10n()->t('Send Reply'),
'$to' => DI::l10n()->t('To:'),
'$subject' => DI::l10n()->t('Subject:'),
'$subjtxt' => $message['title'],
'$readonly' => ' readonly="readonly" style="background: #BBBBBB;" ',
'$yourmessage' => DI::l10n()->t('Your message:'),
'$text' => '',
'$select' => $select,
'$parent' => $parent,
'$upload' => DI::l10n()->t('Upload photo'),
'$insert' => DI::l10n()->t('Insert web link'),
'$submit' => DI::l10n()->t('Submit'),
'$wait' => DI::l10n()->t('Please wait')
'$header' => DI::l10n()->t('Send Reply'),
'$to' => DI::l10n()->t('To:'),
'$subject' => DI::l10n()->t('Subject:'),
'$subjtxt' => $message['title'],
'$readonly' => ' readonly="readonly" style="background: #BBBBBB;" ',
'$yourmessage' => DI::l10n()->t('Your message:'),
'$text' => '',
'$select' => $select,
'$parent' => $parent,
'$upload' => DI::l10n()->t('Upload photo'),
'$insert' => DI::l10n()->t('Insert web link'),
'$submit' => DI::l10n()->t('Submit'),
'$wait' => DI::l10n()->t('Please wait')
]);
return $o;
@ -368,7 +370,7 @@ function message_content(App $a)
* @param int $limit
* @return array
*/
function get_messages(int $uid, int $start, int $limit)
function get_messages(int $uid, int $start, int $limit): array
{
return DBA::toArray(DBA::p('SELECT
m.`id`,
@ -392,21 +394,21 @@ function get_messages(int $uid, int $start, int $limit)
c.`url`,
c.`thumb`,
c.`network`,
m2.`count`,
m2.`mailcreated`,
m2.`mailseen`
FROM `mail` m
JOIN (
SELECT
`parent-uri`,
MIN(`id`) AS `id`,
COUNT(*) AS `count`,
MAX(`created`) AS `mailcreated`,
MIN(`seen`) AS `mailseen`
FROM `mail`
WHERE `uid` = ?
GROUP BY `parent-uri`
) m2 ON m.`parent-uri` = m2.`parent-uri` AND m.`id` = m2.`id`
m2.`count`,
m2.`mailcreated`,
m2.`mailseen`
FROM `mail` m
JOIN (
SELECT
`parent-uri`,
MIN(`id`) AS `id`,
COUNT(*) AS `count`,
MAX(`created`) AS `mailcreated`,
MIN(`seen`) AS `mailseen`
FROM `mail`
WHERE `uid` = ?
GROUP BY `parent-uri`
) m2 ON m.`parent-uri` = m2.`parent-uri` AND m.`id` = m2.`id`
LEFT JOIN `contact` c ON m.`contact-id` = c.`id`
WHERE m.`uid` = ?
ORDER BY m2.`mailcreated` DESC
@ -414,7 +416,7 @@ function get_messages(int $uid, int $start, int $limit)
, $uid, $uid, $start, $limit));
}
function render_messages(array $msg, $t)
function render_messages(array $msg, string $t): string
{
$a = DI::app();
@ -444,20 +446,20 @@ function render_messages(array $msg, $t)
$from_photo = Contact::getThumb($contact);
$rslt .= Renderer::replaceMacros($tpl, [
'$id' => $rr['id'],
'$from_name' => $participants,
'$from_url' => Contact::magicLink($rr['url']),
'$from_addr' => $contact['addr'] ?? '',
'$sparkle' => ' sparkle',
'$id' => $rr['id'],
'$from_name' => $participants,
'$from_url' => Contact::magicLink($rr['url']),
'$from_addr' => $contact['addr'] ?? '',
'$sparkle' => ' sparkle',
'$from_photo' => $from_photo,
'$subject' => $rr['title'],
'$delete' => DI::l10n()->t('Delete conversation'),
'$body' => $body_e,
'$to_name' => $to_name_e,
'$date' => DateTimeFormat::local($rr['mailcreated'], DI::l10n()->t('D, d M Y - g:i A')),
'$ago' => Temporal::getRelativeDate($rr['mailcreated']),
'$seen' => $rr['mailseen'],
'$count' => DI::l10n()->tt('%d message', '%d messages', $rr['count']),
'$subject' => $rr['title'],
'$delete' => DI::l10n()->t('Delete conversation'),
'$body' => $body_e,
'$to_name' => $to_name_e,
'$date' => DateTimeFormat::local($rr['mailcreated'], DI::l10n()->t('D, d M Y - g:i A')),
'$ago' => Temporal::getRelativeDate($rr['mailcreated']),
'$seen' => $rr['mailseen'],
'$count' => DI::l10n()->tt('%d message', '%d messages', $rr['count']),
]);
}

View file

@ -1,64 +0,0 @@
<?php
/**
* @copyright Copyright (C) 2010-2022, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
use Friendica\App;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\User;
use Friendica\Util\Proxy;
function msearch_post(App $a)
{
$search = $_POST['s'] ?? '';
$perpage = intval(($_POST['n'] ?? 0) ?: 80);
$page = intval(($_POST['p'] ?? 0) ?: 1);
$startrec = ($page - 1) * $perpage;
$total = 0;
$results = [];
if (!strlen($search)) {
$output = ['total' => 0, 'items_page' => $perpage, 'page' => $page, 'results' => $results];
System::jsonExit($output);
}
$total = 0;
$condition = ["`net-publish` AND MATCH(`pub_keywords`) AGAINST (?)", $search];
$total = DBA::count('owner-view', $condition);
$search_stmt = DBA::select('owner-view', ['pub_keywords', 'name', 'nickname', 'uid'], $condition, ['limit' => [$startrec, $perpage]]);
while ($search_result = DBA::fetch($search_stmt)) {
$results[] = [
'name' => $search_result['name'],
'url' => DI::baseUrl() . '/profile/' . $search_result['nickname'],
'photo' => User::getAvatarUrl($search_result, Proxy::SIZE_THUMB),
'tags' => str_replace([',', ' '], [' ', ' '], $search_result['pub_keywords'])
];
}
DBA::close($search_stmt);
$output = ['total' => $total, 'items_page' => $perpage, 'page' => $page, 'results' => $results];
System::jsonExit($output);
}

View file

@ -30,7 +30,7 @@ use Friendica\Module\BaseProfile;
function notes_init(App $a)
{
if (! local_user()) {
if (! DI::userSession()->getLocalUserId()) {
return;
}
@ -38,21 +38,21 @@ function notes_init(App $a)
}
function notes_content(App $a, $update = false)
function notes_content(App $a, bool $update = false)
{
if (!local_user()) {
notice(DI::l10n()->t('Permission denied.'));
if (!DI::userSession()->getLocalUserId()) {
DI::sysmsg()->addNotice(DI::l10n()->t('Permission denied.'));
return;
}
$o = BaseProfile::getTabsHTML($a, 'notes', true, $a->getLoggedInUserNickname(), false);
$o = BaseProfile::getTabsHTML('notes', true, $a->getLoggedInUserNickname(), false);
if (!$update) {
$o .= '<h3>' . DI::l10n()->t('Personal Notes') . '</h3>';
$x = [
'lockstate' => 'lock',
'acl' => \Friendica\Core\ACL::getSelfOnlyHTML(local_user(), DI::l10n()->t('Personal notes are visible only by yourself.')),
'acl' => \Friendica\Core\ACL::getSelfOnlyHTML(DI::userSession()->getLocalUserId(), DI::l10n()->t('Personal notes are visible only by yourself.')),
'button' => DI::l10n()->t('Save'),
'acl_data' => '',
];
@ -60,14 +60,14 @@ function notes_content(App $a, $update = false)
$o .= DI::conversation()->statusEditor($x, $a->getContactId());
}
$condition = ['uid' => local_user(), 'post-type' => Item::PT_PERSONAL_NOTE, 'gravity' => GRAVITY_PARENT,
$condition = ['uid' => DI::userSession()->getLocalUserId(), 'post-type' => Item::PT_PERSONAL_NOTE, 'gravity' => Item::GRAVITY_PARENT,
'contact-id'=> $a->getContactId()];
if (DI::mode()->isMobile()) {
$itemsPerPage = DI::pConfig()->get(local_user(), 'system', 'itemspage_mobile_network',
$itemsPerPage = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'system', 'itemspage_mobile_network',
DI::config()->get('system', 'itemspage_network_mobile'));
} else {
$itemsPerPage = DI::pConfig()->get(local_user(), 'system', 'itemspage_network',
$itemsPerPage = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'system', 'itemspage_network',
DI::config()->get('system', 'itemspage_network'));
}
@ -75,7 +75,7 @@ function notes_content(App $a, $update = false)
$params = ['order' => ['created' => true],
'limit' => [$pager->getStart(), $pager->getItemsPerPage()]];
$r = Post::selectThreadForUser(local_user(), ['uri-id'], $condition, $params);
$r = Post::selectThreadForUser(DI::userSession()->getLocalUserId(), ['uri-id'], $condition, $params);
$count = 0;

View file

@ -20,6 +20,8 @@
*/
use Friendica\App;
use Friendica\Content\Text\BBCode;
use Friendica\Content\Text\HTML;
use Friendica\Core\System;
use Friendica\DI;
use Friendica\Module\Response;
@ -93,9 +95,9 @@ function oexchange_init(App $a)
System::httpExit($xml->saveXML(), Response::TYPE_XML, 'application/xrd+xml');
}
function oexchange_content(App $a) {
if (!local_user()) {
function oexchange_content(App $a)
{
if (!DI::userSession()->getLocalUserId()) {
$o = Login::form();
return $o;
}
@ -109,7 +111,7 @@ function oexchange_content(App $a) {
$description = !empty($_REQUEST['description']) ? trim($_REQUEST['description']) : '';
$tags = !empty($_REQUEST['tags']) ? trim($_REQUEST['tags']) : '';
$s = \Friendica\Content\Text\BBCode::embedURL($url, true, $title, $description, $tags);
$s = BBCode::embedURL($url, true, $title, $description, $tags);
if (!strlen($s)) {
return;
@ -117,11 +119,10 @@ function oexchange_content(App $a) {
$post = [];
$post['profile_uid'] = local_user();
$post['return'] = '/oexchange/done';
$post['body'] = Friendica\Content\Text\HTML::toBBCode($s);
$post['body'] = HTML::toBBCode($s);
$_REQUEST = $post;
require_once('mod/item.php');
require_once 'mod/item.php';
item_post($a);
}

View file

@ -1,132 +0,0 @@
<?php
/**
* @copyright Copyright (C) 2010-2022, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
use Friendica\App;
use Friendica\Core\Protocol;
use Friendica\DI;
use Friendica\Model\APContact;
use Friendica\Model\Contact;
use Friendica\Network\HTTPClient\Client\HttpClientAccept;
use Friendica\Protocol\ActivityPub;
function ostatus_subscribe_content(App $a)
{
if (!local_user()) {
notice(DI::l10n()->t('Permission denied.'));
DI::baseUrl()->redirect('ostatus_subscribe');
// NOTREACHED
}
$o = '<h2>' . DI::l10n()->t('Subscribing to contacts') . '</h2>';
$uid = local_user();
$counter = intval($_REQUEST['counter'] ?? 0);
if (DI::pConfig()->get($uid, 'ostatus', 'legacy_friends') == '') {
if ($_REQUEST['url'] == '') {
DI::pConfig()->delete($uid, 'ostatus', 'legacy_contact');
return $o . DI::l10n()->t('No contact provided.');
}
$contact = Contact::getByURL($_REQUEST['url']);
if (!$contact) {
DI::pConfig()->delete($uid, 'ostatus', 'legacy_contact');
return $o . DI::l10n()->t('Couldn\'t fetch information for contact.');
}
if ($contact['network'] == Protocol::OSTATUS) {
$api = $contact['baseurl'] . '/api/';
// Fetching friends
$curlResult = DI::httpClient()->get($api . 'statuses/friends.json?screen_name=' . $contact['nick'], HttpClientAccept::JSON);
if (!$curlResult->isSuccess()) {
DI::pConfig()->delete($uid, 'ostatus', 'legacy_contact');
return $o . DI::l10n()->t('Couldn\'t fetch friends for contact.');
}
$friends = $curlResult->getBody();
if (empty($friends)) {
DI::pConfig()->delete($uid, 'ostatus', 'legacy_contact');
return $o . DI::l10n()->t('Couldn\'t fetch following contacts.');
}
DI::pConfig()->set($uid, 'ostatus', 'legacy_friends', $friends);
} elseif ($apcontact = APContact::getByURL($contact['url'])) {
if (empty($apcontact['following'])) {
DI::pConfig()->delete($uid, 'ostatus', 'legacy_contact');
return $o . DI::l10n()->t('Couldn\'t fetch remote profile.');
}
$followings = ActivityPub::fetchItems($apcontact['following']);
if (empty($followings)) {
DI::pConfig()->delete($uid, 'ostatus', 'legacy_contact');
return $o . DI::l10n()->t('Couldn\'t fetch following contacts.');
}
DI::pConfig()->set($uid, 'ostatus', 'legacy_friends', json_encode($followings));
} else {
DI::pConfig()->delete($uid, 'ostatus', 'legacy_contact');
return $o . DI::l10n()->t('Unsupported network');
}
}
$friends = json_decode(DI::pConfig()->get($uid, 'ostatus', 'legacy_friends'));
if (empty($friends)) {
$friends = [];
}
$total = sizeof($friends);
if ($counter >= $total) {
DI::page()['htmlhead'] = '<meta http-equiv="refresh" content="0; URL=' . DI::baseUrl() . '/settings/connectors">';
DI::pConfig()->delete($uid, 'ostatus', 'legacy_friends');
DI::pConfig()->delete($uid, 'ostatus', 'legacy_contact');
$o .= DI::l10n()->t('Done');
return $o;
}
$friend = $friends[$counter++];
$url = $friend->statusnet_profile_url ?? $friend;
$o .= '<p>' . $counter . '/' . $total . ': ' . $url;
$probed = Contact::getByURL($url);
if (in_array($probed['network'], Protocol::FEDERATED)) {
$result = Contact::createFromProbeForUser($a->getLoggedInUserId(), $probed['url']);
if ($result['success']) {
$o .= ' - ' . DI::l10n()->t('success');
} else {
$o .= ' - ' . DI::l10n()->t('failed');
}
} else {
$o .= ' - ' . DI::l10n()->t('ignored');
}
$o .= '</p>';
$o .= '<p>' . DI::l10n()->t('Keep this window open until done.') . '</p>';
DI::page()['htmlhead'] = '<meta http-equiv="refresh" content="0; URL=' . DI::baseUrl() . '/ostatus_subscribe?counter=' . $counter . '">';
return $o;
}

View file

@ -20,7 +20,6 @@
*/
use Friendica\App;
use Friendica\Content\Feature;
use Friendica\Content\Nav;
use Friendica\Content\Pager;
use Friendica\Content\Text\BBCode;
@ -30,7 +29,6 @@ use Friendica\Core\Addon;
use Friendica\Core\Hook;
use Friendica\Core\Logger;
use Friendica\Core\Renderer;
use Friendica\Core\Session;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\DI;
@ -42,22 +40,21 @@ use Friendica\Model\Profile;
use Friendica\Model\Tag;
use Friendica\Model\User;
use Friendica\Module\BaseProfile;
use Friendica\Network\HTTPException;
use Friendica\Network\Probe;
use Friendica\Object\Image;
use Friendica\Protocol\Activity;
use Friendica\Security\Security;
use Friendica\Util\Crypto;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\Images;
use Friendica\Util\Map;
use Friendica\Security\Security;
use Friendica\Util\Strings;
use Friendica\Util\Temporal;
use Friendica\Util\XML;
use Friendica\Network\HTTPException;
function photos_init(App $a) {
if (DI::config()->get('system', 'block_public') && !Session::isAuthenticated()) {
function photos_init(App $a)
{
if (DI::config()->get('system', 'block_public') && !DI::userSession()->isAuthenticated()) {
return;
}
@ -69,11 +66,11 @@ function photos_init(App $a) {
throw new HTTPException\NotFoundException(DI::l10n()->t('User not found.'));
}
$is_owner = (local_user() && (local_user() == $owner['uid']));
$is_owner = (DI::userSession()->getLocalUserId() && (DI::userSession()->getLocalUserId() == $owner['uid']));
$albums = Photo::getAlbums($owner['uid']);
$albums_visible = ((intval($owner['hidewall']) && !Session::isAuthenticated()) ? false : true);
$albums_visible = ((intval($owner['hidewall']) && !DI::userSession()->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];
@ -96,7 +93,7 @@ function photos_init(App $a) {
}
}
if (local_user() && $owner['uid'] == local_user()) {
if (DI::userSession()->getLocalUserId() && $owner['uid'] == DI::userSession()->getLocalUserId()) {
$can_post = true;
} else {
$can_post = false;
@ -148,23 +145,23 @@ function photos_post(App $a)
$page_owner_uid = intval($user['uid']);
$community_page = $user['page-flags'] == User::PAGE_FLAGS_COMMUNITY;
if (local_user() && (local_user() == $page_owner_uid)) {
if (DI::userSession()->getLocalUserId() && (DI::userSession()->getLocalUserId() == $page_owner_uid)) {
$can_post = true;
} elseif ($community_page && !empty(Session::getRemoteContactID($page_owner_uid))) {
$contact_id = Session::getRemoteContactID($page_owner_uid);
} elseif ($community_page && !empty(DI::userSession()->getRemoteContactID($page_owner_uid))) {
$contact_id = DI::userSession()->getRemoteContactID($page_owner_uid);
$can_post = true;
$visitor = $contact_id;
}
if (!$can_post) {
notice(DI::l10n()->t('Permission denied.'));
DI::sysmsg()->addNotice(DI::l10n()->t('Permission denied.'));
System::exit();
}
$owner_record = User::getOwnerDataById($page_owner_uid);
if (!$owner_record) {
notice(DI::l10n()->t('Contact information unavailable'));
DI::sysmsg()->addNotice(DI::l10n()->t('Contact information unavailable'));
DI::logger()->info('photos_post: unable to locate contact record for page owner. uid=' . $page_owner_uid);
System::exit();
}
@ -193,7 +190,7 @@ function photos_post(App $a)
$album = hex2bin(DI::args()->getArgv()[3]);
if (!DBA::exists('photo', ['album' => $album, 'uid' => $page_owner_uid, 'photo-type' => Photo::DEFAULT])) {
notice(DI::l10n()->t('Album not found.'));
DI::sysmsg()->addNotice(DI::l10n()->t('Album not found.'));
DI::baseUrl()->redirect('photos/' . $user['nickname'] . '/album');
return; // NOTREACHED
}
@ -229,7 +226,7 @@ function photos_post(App $a)
));
} else {
$r = DBA::toArray(DBA::p("SELECT distinct(`resource-id`) as `rid` FROM `photo` WHERE `uid` = ? AND `album` = ?",
local_user(),
DI::userSession()->getLocalUserId(),
$album
));
}
@ -247,9 +244,9 @@ function photos_post(App $a)
// Update the photo albums cache
Photo::clearAlbumCache($page_owner_uid);
notice(DI::l10n()->t('Album successfully deleted'));
DI::sysmsg()->addNotice(DI::l10n()->t('Album successfully deleted'));
} else {
notice(DI::l10n()->t('Album was empty.'));
DI::sysmsg()->addNotice(DI::l10n()->t('Album was empty.'));
}
}
@ -268,7 +265,7 @@ function photos_post(App $a)
$condition = ['contact-id' => $visitor, 'uid' => $page_owner_uid, 'resource-id' => DI::args()->getArgv()[3]];
} else {
$condition = ['uid' => local_user(), 'resource-id' => DI::args()->getArgv()[3]];
$condition = ['uid' => DI::userSession()->getLocalUserId(), 'resource-id' => DI::args()->getArgv()[3]];
}
$photo = DBA::selectFirst('photo', ['resource-id'], $condition);
@ -281,12 +278,11 @@ function photos_post(App $a)
// Update the photo albums cache
Photo::clearAlbumCache($page_owner_uid);
} else {
notice(DI::l10n()->t('Failed to delete the photo.'));
DI::sysmsg()->addNotice(DI::l10n()->t('Failed to delete the photo.'));
DI::baseUrl()->redirect('photos/' . DI::args()->getArgv()[1] . '/image/' . DI::args()->getArgv()[3]);
}
DI::baseUrl()->redirect('photos/' . DI::args()->getArgv()[1]);
return; // NOTREACHED
DI::baseUrl()->redirect('profile/' . DI::args()->getArgv()[1] . '/photos');
}
}
@ -526,44 +522,40 @@ function photos_post(App $a)
foreach ($taginfo as $tagged) {
$uri = Item::newURI();
$arr = [];
$arr['guid'] = System::createUUID();
$arr['uid'] = $page_owner_uid;
$arr['uri'] = $uri;
$arr['wall'] = 1;
$arr['contact-id'] = $owner_record['id'];
$arr['owner-name'] = $owner_record['name'];
$arr['owner-link'] = $owner_record['url'];
$arr['owner-avatar'] = $owner_record['thumb'];
$arr['author-name'] = $owner_record['name'];
$arr['author-link'] = $owner_record['url'];
$arr['author-avatar'] = $owner_record['thumb'];
$arr['title'] = '';
$arr['allow_cid'] = $photo['allow_cid'];
$arr['allow_gid'] = $photo['allow_gid'];
$arr['deny_cid'] = $photo['deny_cid'];
$arr['deny_gid'] = $photo['deny_gid'];
$arr['visible'] = 0;
$arr['verb'] = Activity::TAG;
$arr['gravity'] = GRAVITY_PARENT;
$arr['object-type'] = Activity\ObjectType::PERSON;
$arr['target-type'] = Activity\ObjectType::IMAGE;
$arr['inform'] = $tagged[2];
$arr['origin'] = 1;
$arr['body'] = DI::l10n()->t('%1$s was tagged in %2$s by %3$s', '[url=' . $tagged[1] . ']' . $tagged[0] . '[/url]', '[url=' . DI::baseUrl() . '/photos/' . $owner_record['nickname'] . '/image/' . $photo['resource-id'] . ']' . DI::l10n()->t('a photo') . '[/url]', '[url=' . $owner_record['url'] . ']' . $owner_record['name'] . '[/url]') ;
$arr['body'] .= "\n\n" . '[url=' . DI::baseUrl() . '/photos/' . $owner_record['nickname'] . '/image/' . $photo['resource-id'] . ']' . '[img]' . DI::baseUrl() . "/photo/" . $photo['resource-id'] . '-' . $best . '.' . $ext . '[/img][/url]' . "\n" ;
$arr = [
'guid' => System::createUUID(),
'uid' => $page_owner_uid,
'uri' => $uri,
'wall' => 1,
'contact-id' => $owner_record['id'],
'owner-name' => $owner_record['name'],
'owner-link' => $owner_record['url'],
'owner-avatar' => $owner_record['thumb'],
'author-name' => $owner_record['name'],
'author-link' => $owner_record['url'],
'author-avatar' => $owner_record['thumb'],
'title' => '',
'allow_cid' => $photo['allow_cid'],
'allow_gid' => $photo['allow_gid'],
'deny_cid' => $photo['deny_cid'],
'deny_gid' => $photo['deny_gid'],
'visible' => 0,
'verb' => Activity::TAG,
'gravity' => Item::GRAVITY_PARENT,
'object-type' => Activity\ObjectType::PERSON,
'target-type' => Activity\ObjectType::IMAGE,
'inform' => $tagged[2],
'origin' => 1,
'body' => DI::l10n()->t('%1$s was tagged in %2$s by %3$s', '[url=' . $tagged[1] . ']' . $tagged[0] . '[/url]', '[url=' . DI::baseUrl() . '/photos/' . $owner_record['nickname'] . '/image/' . $photo['resource-id'] . ']' . DI::l10n()->t('a photo') . '[/url]', '[url=' . $owner_record['url'] . ']' . $owner_record['name'] . '[/url]') . "\n\n" . '[url=' . DI::baseUrl() . '/photos/' . $owner_record['nickname'] . '/image/' . $photo['resource-id'] . ']' . '[img]' . DI::baseUrl() . '/photo/' . $photo['resource-id'] . '-' . $best . '.' . $ext . '[/img][/url]' . "\n",
'object' => '<object><type>' . Activity\ObjectType::PERSON . '</type><title>' . $tagged[0] . '</title><id>' . $tagged[1] . '/' . $tagged[0] . '</id><link>' . XML::escape('<link rel="alternate" type="text/html" href="' . $tagged[1] . '" />' . "\n"),
'target' => '<target><type>' . Activity\ObjectType::IMAGE . '</type><title>' . $photo['desc'] . '</title><id>' . DI::baseUrl() . '/photos/' . $owner_record['nickname'] . '/image/' . $photo['resource-id'] . '</id><link>' . XML::escape('<link rel="alternate" type="text/html" href="' . DI::baseUrl() . '/photos/' . $owner_record['nickname'] . '/image/' . $photo['resource-id'] . '" />' . "\n" . '<link rel="preview" type="' . $photo['type'] . '" href="' . DI::baseUrl() . '/photo/' . $photo['resource-id'] . '-' . $best . '.' . $ext . '" />') . '</link></target>',
];
$arr['object'] = '<object><type>' . Activity\ObjectType::PERSON . '</type><title>' . $tagged[0] . '</title><id>' . $tagged[1] . '/' . $tagged[0] . '</id>';
$arr['object'] .= '<link>' . XML::escape('<link rel="alternate" type="text/html" href="' . $tagged[1] . '" />' . "\n");
if ($tagged[3]) {
$arr['object'] .= XML::escape('<link rel="photo" type="' . $photo['type'] . '" href="' . $tagged[3]['photo'] . '" />' . "\n");
}
$arr['object'] .= '</link></object>' . "\n";
$arr['target'] = '<target><type>' . Activity\ObjectType::IMAGE . '</type><title>' . $photo['desc'] . '</title><id>'
. DI::baseUrl() . '/photos/' . $owner_record['nickname'] . '/image/' . $photo['resource-id'] . '</id>';
$arr['target'] .= '<link>' . XML::escape('<link rel="alternate" type="text/html" href="' . DI::baseUrl() . '/photos/' . $owner_record['nickname'] . '/image/' . $photo['resource-id'] . '" />' . "\n" . '<link rel="preview" type="' . $photo['type'] . '" href="' . DI::baseUrl() . "/photo/" . $photo['resource-id'] . '-' . $best . '.' . $ext . '" />') . '</link></target>';
Item::insert($arr);
}
}
@ -571,219 +563,11 @@ function photos_post(App $a)
DI::baseUrl()->redirect($_SESSION['photo_return']);
return; // NOTREACHED
}
// default post action - upload a photo
Hook::callAll('photo_post_init', $_POST);
// Determine the album to use
$album = trim($_REQUEST['album'] ?? '');
$newalbum = trim($_REQUEST['newalbum'] ?? '');
Logger::debug('album= ' . $album . ' newalbum= ' . $newalbum);
if (!strlen($album)) {
if (strlen($newalbum)) {
$album = $newalbum;
} else {
$album = DateTimeFormat::localNow('Y');
}
}
/*
* We create a wall item for every photo, but we don't want to
* overwhelm the data stream with a hundred newly uploaded photos.
* So we will make the first photo uploaded to this album in the last several hours
* visible by default, the rest will become visible over time when and if
* they acquire comments, likes, dislikes, and/or tags
*/
$r = Photo::selectToArray([], ['`album` = ? AND `uid` = ? AND `created` > ?', $album, $page_owner_uid, DateTimeFormat::utc('now - 3 hours')]);
if (!DBA::isResult($r) || ($album == DI::l10n()->t(Photo::PROFILE_PHOTOS))) {
$visible = 1;
} else {
$visible = 0;
}
if (!empty($_REQUEST['not_visible']) && $_REQUEST['not_visible'] !== 'false') {
$visible = 0;
}
$ret = ['src' => '', 'filename' => '', 'filesize' => 0, 'type' => ''];
Hook::callAll('photo_post_file', $ret);
if (!empty($ret['src']) && !empty($ret['filesize'])) {
$src = $ret['src'];
$filename = $ret['filename'];
$filesize = $ret['filesize'];
$type = $ret['type'];
$error = UPLOAD_ERR_OK;
} elseif (!empty($_FILES['userfile'])) {
$src = $_FILES['userfile']['tmp_name'];
$filename = basename($_FILES['userfile']['name']);
$filesize = intval($_FILES['userfile']['size']);
$type = $_FILES['userfile']['type'];
$error = $_FILES['userfile']['error'];
} else {
$error = UPLOAD_ERR_NO_FILE;
}
if ($error !== UPLOAD_ERR_OK) {
switch ($error) {
case UPLOAD_ERR_INI_SIZE:
notice(DI::l10n()->t('Image exceeds size limit of %s', ini_get('upload_max_filesize')));
break;
case UPLOAD_ERR_FORM_SIZE:
notice(DI::l10n()->t('Image exceeds size limit of %s', Strings::formatBytes($_REQUEST['MAX_FILE_SIZE'] ?? 0)));
break;
case UPLOAD_ERR_PARTIAL:
notice(DI::l10n()->t('Image upload didn\'t complete, please try again'));
break;
case UPLOAD_ERR_NO_FILE:
notice(DI::l10n()->t('Image file is missing'));
break;
case UPLOAD_ERR_NO_TMP_DIR:
case UPLOAD_ERR_CANT_WRITE:
case UPLOAD_ERR_EXTENSION:
notice(DI::l10n()->t('Server can\'t accept new file upload at this time, please contact your administrator'));
break;
}
@unlink($src);
$foo = 0;
Hook::callAll('photo_post_end', $foo);
return;
}
$type = Images::getMimeTypeBySource($src, $filename, $type);
Logger::info('photos: upload: received file: ' . $filename . ' as ' . $src . ' ('. $type . ') ' . $filesize . ' bytes');
$maximagesize = DI::config()->get('system', 'maximagesize');
if ($maximagesize && ($filesize > $maximagesize)) {
notice(DI::l10n()->t('Image exceeds size limit of %s', Strings::formatBytes($maximagesize)));
@unlink($src);
$foo = 0;
Hook::callAll('photo_post_end', $foo);
return;
}
if (!$filesize) {
notice(DI::l10n()->t('Image file is empty.'));
@unlink($src);
$foo = 0;
Hook::callAll('photo_post_end', $foo);
return;
}
Logger::debug('loading contents', ['src' => $src]);
$imagedata = @file_get_contents($src);
$image = new Image($imagedata, $type);
if (!$image->isValid()) {
Logger::notice('unable to process image');
notice(DI::l10n()->t('Unable to process image.'));
@unlink($src);
$foo = 0;
Hook::callAll('photo_post_end',$foo);
return;
}
$exif = $image->orient($src);
@unlink($src);
$max_length = DI::config()->get('system', 'max_image_length');
if ($max_length > 0) {
$image->scaleDown($max_length);
}
$width = $image->getWidth();
$height = $image->getHeight();
$smallest = 0;
$resource_id = Photo::newResource();
$r = Photo::store($image, $page_owner_uid, $visitor, $resource_id, $filename, $album, 0 , Photo::DEFAULT, $str_contact_allow, $str_group_allow, $str_contact_deny, $str_group_deny);
if (!$r) {
Logger::warning('image store failed');
notice(DI::l10n()->t('Image upload failed.'));
return;
}
if ($width > 640 || $height > 640) {
$image->scaleDown(640);
Photo::store($image, $page_owner_uid, $visitor, $resource_id, $filename, $album, 1, Photo::DEFAULT, $str_contact_allow, $str_group_allow, $str_contact_deny, $str_group_deny);
$smallest = 1;
}
if ($width > 320 || $height > 320) {
$image->scaleDown(320);
Photo::store($image, $page_owner_uid, $visitor, $resource_id, $filename, $album, 2, Photo::DEFAULT, $str_contact_allow, $str_group_allow, $str_contact_deny, $str_group_deny);
$smallest = 2;
}
$uri = Item::newURI();
// Create item container
$lat = $lon = null;
if (!empty($exif['GPS']) && Feature::isEnabled($page_owner_uid, 'photo_location')) {
$lat = Photo::getGps($exif['GPS']['GPSLatitude'], $exif['GPS']['GPSLatitudeRef']);
$lon = Photo::getGps($exif['GPS']['GPSLongitude'], $exif['GPS']['GPSLongitudeRef']);
}
$arr = [];
if ($lat && $lon) {
$arr['coord'] = $lat . ' ' . $lon;
}
$arr['guid'] = System::createUUID();
$arr['uid'] = $page_owner_uid;
$arr['uri'] = $uri;
$arr['post-type'] = Item::PT_IMAGE;
$arr['wall'] = 1;
$arr['resource-id'] = $resource_id;
$arr['contact-id'] = $owner_record['id'];
$arr['owner-name'] = $owner_record['name'];
$arr['owner-link'] = $owner_record['url'];
$arr['owner-avatar'] = $owner_record['thumb'];
$arr['author-name'] = $owner_record['name'];
$arr['author-link'] = $owner_record['url'];
$arr['author-avatar'] = $owner_record['thumb'];
$arr['title'] = '';
$arr['allow_cid'] = $str_contact_allow;
$arr['allow_gid'] = $str_group_allow;
$arr['deny_cid'] = $str_contact_deny;
$arr['deny_gid'] = $str_group_deny;
$arr['visible'] = $visible;
$arr['origin'] = 1;
$arr['body'] = '[url=' . DI::baseUrl() . '/photos/' . $owner_record['nickname'] . '/image/' . $resource_id . ']'
. '[img]' . DI::baseUrl() . "/photo/{$resource_id}-{$smallest}.".$image->getExt() . '[/img]'
. '[/url]';
$item_id = Item::insert($arr);
// Update the photo albums cache
Photo::clearAlbumCache($page_owner_uid);
Hook::callAll('photo_post_end', $item_id);
// addon uploaders should call "exit()" within the photo_post_end hook
// if they do not wish to be redirected
DI::baseUrl()->redirect($_SESSION['photo_return']);
// NOTREACHED
}
function photos_content(App $a)
{
// URLs:
// photos/name
// photos/name/upload
// photos/name/upload/xxxxx (xxxxx is album name)
// photos/name/album/xxxxx
@ -793,18 +577,18 @@ function photos_content(App $a)
// photos/name/image/xxxxx/edit
// photos/name/image/xxxxx/drop
$user = User::getByNickname(DI::args()->getArgv()[1]);
$user = User::getByNickname(DI::args()->getArgv()[1] ?? '');
if (!DBA::isResult($user)) {
throw new HTTPException\NotFoundException(DI::l10n()->t('User not found.'));
}
if (DI::config()->get('system', 'block_public') && !Session::isAuthenticated()) {
notice(DI::l10n()->t('Public access denied.'));
if (DI::config()->get('system', 'block_public') && !DI::userSession()->isAuthenticated()) {
DI::sysmsg()->addNotice(DI::l10n()->t('Public access denied.'));
return;
}
if (empty($user)) {
notice(DI::l10n()->t('No photos selected'));
DI::sysmsg()->addNotice(DI::l10n()->t('No photos selected'));
return;
}
@ -844,10 +628,10 @@ function photos_content(App $a)
$community_page = (($user['page-flags'] == User::PAGE_FLAGS_COMMUNITY) ? true : false);
if (local_user() && (local_user() == $owner_uid)) {
if (DI::userSession()->getLocalUserId() && (DI::userSession()->getLocalUserId() == $owner_uid)) {
$can_post = true;
} elseif ($community_page && !empty(Session::getRemoteContactID($owner_uid))) {
$contact_id = Session::getRemoteContactID($owner_uid);
} elseif ($community_page && !empty(DI::userSession()->getRemoteContactID($owner_uid))) {
$contact_id = DI::userSession()->getRemoteContactID($owner_uid);
$contact = DBA::selectFirst('contact', [], ['id' => $contact_id, 'uid' => $owner_uid, 'blocked' => false, 'pending' => false]);
if (DBA::isResult($contact)) {
@ -858,23 +642,22 @@ function photos_content(App $a)
}
// perhaps they're visiting - but not a community page, so they wouldn't have write access
if (!empty(Session::getRemoteContactID($owner_uid)) && !$visitor) {
$contact_id = Session::getRemoteContactID($owner_uid);
if (!empty(DI::userSession()->getRemoteContactID($owner_uid)) && !$visitor) {
$contact_id = DI::userSession()->getRemoteContactID($owner_uid);
$contact = DBA::selectFirst('contact', [], ['id' => $contact_id, 'uid' => $owner_uid, 'blocked' => false, 'pending' => false]);
$remote_contact = DBA::isResult($contact);
}
if (!$remote_contact && local_user()) {
if (!$remote_contact && DI::userSession()->getLocalUserId()) {
$contact_id = $_SESSION['cid'];
$contact = DBA::selectFirst('contact', [], ['id' => $contact_id, 'uid' => $owner_uid, 'blocked' => false, 'pending' => false]);
}
if ($user['hidewall'] && (local_user() != $owner_uid) && !$remote_contact) {
notice(DI::l10n()->t('Access to this item is restricted.'));
return;
if ($user['hidewall'] && !DI::userSession()->isAuthenticated()) {
DI::baseUrl()->redirect('profile/' . $user['nickname'] . '/restricted');
}
$sql_extra = Security::getPermissionsSQLByUserId($owner_uid);
@ -882,16 +665,19 @@ function photos_content(App $a)
$o = "";
// tabs
$is_owner = (local_user() && (local_user() == $owner_uid));
$o .= BaseProfile::getTabsHTML($a, 'photos', $is_owner, $user['nickname'], $profile['hide-friends']);
$is_owner = (DI::userSession()->getLocalUserId() && (DI::userSession()->getLocalUserId() == $owner_uid));
$o .= BaseProfile::getTabsHTML('photos', $is_owner, $user['nickname'], $profile['hide-friends']);
// Display upload form
if ($datatype === 'upload') {
if (!$can_post) {
notice(DI::l10n()->t('Permission denied.'));
DI::sysmsg()->addNotice(DI::l10n()->t('Permission denied.'));
return;
}
// This prevents the photo upload form to return to itself without a hint the picture has been correctly uploaded.
DI::session()->remove('photo_return');
$selname = (!is_null($datum) && Strings::isHex($datum)) ? hex2bin($datum) : '';
$albumselect = '';
@ -910,7 +696,7 @@ function photos_content(App $a)
$uploader = '';
$ret = ['post_url' => 'photos/' . $user['nickname'],
$ret = ['post_url' => 'profile/' . $user['nickname'] . '/photos',
'addon_text' => $uploader,
'default_upload' => true];
@ -921,7 +707,20 @@ function photos_content(App $a)
'$submit' => DI::l10n()->t('Submit'),
]);
$usage_message = '';
// Get the relevant size limits for uploads. Abbreviated var names: MaxImageSize -> mis; upload_max_filesize -> umf
$mis_bytes = Strings::getBytesFromShorthand(DI::config()->get('system', 'maximagesize'));
$umf_bytes = Strings::getBytesFromShorthand(ini_get('upload_max_filesize'));
// Per Friendica definition a value of '0' means unlimited:
If ($mis_bytes == 0) {
$mis_bytes = INF;
}
// When PHP is configured with upload_max_filesize less than maximagesize provide this lower limit.
$maximagesize_bytes = (is_numeric($mis_bytes) && ($mis_bytes < $umf_bytes) ? $mis_bytes : $umf_bytes);
// @todo We may be want to use appropriate binary prefixed dynamicly
$usage_message = DI::l10n()->t('The maximum accepted image size is %s', Strings::formatBytes($maximagesize_bytes));
$tpl = Renderer::getMarkupTemplate('photos_upload.tpl');
@ -1088,9 +887,9 @@ function photos_content(App $a)
if (!DBA::isResult($ph)) {
if (DBA::exists('photo', ['resource-id' => $datum, 'uid' => $owner_uid])) {
notice(DI::l10n()->t('Permission denied. Access to this item may be restricted.'));
DI::sysmsg()->addNotice(DI::l10n()->t('Permission denied. Access to this item may be restricted.'));
} else {
notice(DI::l10n()->t('Photo not available'));
DI::sysmsg()->addNotice(DI::l10n()->t('Photo not available'));
}
return;
}
@ -1201,7 +1000,7 @@ function photos_content(App $a)
}
if (
$ph[0]['uid'] == local_user()
$ph[0]['uid'] == DI::userSession()->getLocalUserId()
&& (strlen($ph[0]['allow_cid']) || strlen($ph[0]['allow_gid']) || strlen($ph[0]['deny_cid']) || strlen($ph[0]['deny_gid']))
) {
$tools['lock'] = DI::l10n()->t('Private Photo');
@ -1233,7 +1032,7 @@ function photos_content(App $a)
$link_item = Post::selectFirst([], ["`resource-id` = ?" . $sql_extra, $datum]);
if (!empty($link_item['parent']) && !empty($link_item['uid'])) {
$condition = ["`parent` = ? AND `gravity` = ?", $link_item['parent'], GRAVITY_COMMENT];
$condition = ["`parent` = ? AND `gravity` = ?", $link_item['parent'], Item::GRAVITY_COMMENT];
$total = Post::count($condition);
$pager = new Pager(DI::l10n(), DI::args()->getQueryString());
@ -1241,7 +1040,7 @@ function photos_content(App $a)
$params = ['order' => ['id'], 'limit' => [$pager->getStart(), $pager->getItemsPerPage()]];
$items = Post::toArray(Post::selectForUser($link_item['uid'], Item::ITEM_FIELDLIST, $condition, $params));
if (local_user() == $link_item['uid']) {
if (DI::userSession()->getLocalUserId() == $link_item['uid']) {
Item::update(['unseen' => false], ['parent' => $link_item['parent']]);
}
}
@ -1255,15 +1054,17 @@ function photos_content(App $a)
if (!empty($link_item['id'])) {
// parse tags and add links
$tag_arr = [];
foreach (Tag::getByURIId($link_item['uri-id']) as $tag) {
$tag_arr[] = [
'name' => $tag['name'],
'removeurl' => '/tagrm/' . $link_item['id'] . '/' . bin2hex($tag['name'])
];
foreach (explode(',', Tag::getCSVByURIId($link_item['uri-id'])) as $tag_name) {
if ($tag_name) {
$tag_arr[] = [
'name' => BBCode::toPlaintext($tag_name),
'removeurl' => 'post/' . $link_item['id'] . '/tag/remove/' . bin2hex($tag_name) . '?return=' . urlencode(DI::args()->getCommand()),
];
}
}
$tags = ['title' => DI::l10n()->t('Tags: '), 'tags' => $tag_arr];
if ($cmd === 'edit') {
$tags['removeanyurl'] = 'tagrm/' . $link_item['id'];
$tags['removeanyurl'] = 'post/' . $link_item['id'] . '/tag/remove?return=' . urlencode(DI::args()->getCommand());
$tags['removetitle'] = DI::l10n()->t('[Select tags to remove]');
}
}
@ -1319,7 +1120,7 @@ function photos_content(App $a)
*/
$qcomment = null;
if (Addon::isEnabled('qcomment')) {
$words = DI::pConfig()->get(local_user(), 'qcomment', 'words');
$words = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'qcomment', 'words');
$qcomment = $words ? explode("\n", $words) : [];
}
@ -1350,7 +1151,7 @@ function photos_content(App $a)
'attendmaybe' => []
];
if (DI::pConfig()->get(local_user(), 'system', 'hide_dislike')) {
if (DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'system', 'hide_dislike')) {
unset($conv_responses['dislike']);
}
@ -1375,7 +1176,7 @@ function photos_content(App $a)
*/
$qcomment = null;
if (Addon::isEnabled('qcomment')) {
$words = DI::pConfig()->get(local_user(), 'qcomment', 'words');
$words = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'qcomment', 'words');
$qcomment = $words ? explode("\n", $words) : [];
}
@ -1404,42 +1205,42 @@ function photos_content(App $a)
if (($activity->match($item['verb'], Activity::LIKE) ||
$activity->match($item['verb'], Activity::DISLIKE)) &&
($item['gravity'] != GRAVITY_PARENT)) {
($item['gravity'] != Item::GRAVITY_PARENT)) {
continue;
}
$author = ['uid' => 0, 'id' => $item['author-id'],
'network' => $item['author-network'], 'url' => $item['author-link']];
$profile_url = Contact::magicLinkByContact($author);
if (strpos($profile_url, 'redir/') === 0) {
if (strpos($profile_url, 'contact/redir/') === 0) {
$sparkle = ' sparkle';
} else {
$sparkle = '';
}
$dropping = (($item['contact-id'] == $contact_id) || ($item['uid'] == local_user()));
$dropping = (($item['contact-id'] == $contact_id) || ($item['uid'] == DI::userSession()->getLocalUserId()));
$drop = [
'dropping' => $dropping,
'pagedrop' => false,
'select' => DI::l10n()->t('Select'),
'delete' => DI::l10n()->t('Delete'),
'select' => DI::l10n()->t('Select'),
'delete' => DI::l10n()->t('Delete'),
];
$title_e = $item['title'];
$body_e = BBCode::convertForUriId($item['uri-id'], $item['body']);
$comments .= Renderer::replaceMacros($template,[
'$id' => $item['id'],
'$id' => $item['id'],
'$profile_url' => $profile_url,
'$name' => $item['author-name'],
'$thumb' => $item['author-avatar'],
'$sparkle' => $sparkle,
'$title' => $title_e,
'$body' => $body_e,
'$ago' => Temporal::getRelativeDate($item['created']),
'$indent' => (($item['parent'] != $item['id']) ? ' comment' : ''),
'$drop' => $drop,
'$comment' => $comment
'$name' => $item['author-name'],
'$thumb' => $item['author-avatar'],
'$sparkle' => $sparkle,
'$title' => $title_e,
'$body' => $body_e,
'$ago' => Temporal::getRelativeDate($item['created']),
'$indent' => (($item['parent'] != $item['id']) ? ' comment' : ''),
'$drop' => $drop,
'$comment' => $comment
]);
if (($can_post || Security::canWriteToUserWall($owner_uid))) {
@ -1449,7 +1250,7 @@ function photos_content(App $a)
*/
$qcomment = null;
if (Addon::isEnabled('qcomment')) {
$words = DI::pConfig()->get(local_user(), 'qcomment', 'words');
$words = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'qcomment', 'words');
$qcomment = $words ? explode("\n", $words) : [];
}
@ -1488,7 +1289,7 @@ function photos_content(App $a)
'$dislike' => DI::l10n()->t('Dislike'),
'$wait' => DI::l10n()->t('Please wait'),
'$dislike_title' => DI::l10n()->t('I don\'t like this (toggle)'),
'$hide_dislike' => DI::pConfig()->get(local_user(), 'system', 'hide_dislike'),
'$hide_dislike' => DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'system', 'hide_dislike'),
'$responses' => $responses,
'$return_path' => DI::args()->getQueryString(),
]);
@ -1525,68 +1326,4 @@ function photos_content(App $a)
return $o;
}
// Default - show recent photos with upload link (if applicable)
//$o = '';
$total = 0;
$r = DBA::toArray(DBA::p("SELECT `resource-id`, max(`scale`) AS `scale` FROM `photo` WHERE `uid` = ? AND `photo-type` = ?
$sql_extra GROUP BY `resource-id`",
$user['uid'],
Photo::DEFAULT,
));
if (DBA::isResult($r)) {
$total = count($r);
}
$pager = new Pager(DI::l10n(), DI::args()->getQueryString(), 20);
$r = DBA::toArray(DBA::p("SELECT `resource-id`, ANY_VALUE(`id`) AS `id`, ANY_VALUE(`filename`) AS `filename`,
ANY_VALUE(`type`) AS `type`, ANY_VALUE(`album`) AS `album`, max(`scale`) AS `scale`,
ANY_VALUE(`created`) AS `created` FROM `photo`
WHERE `uid` = ? AND `photo-type` = ?
$sql_extra GROUP BY `resource-id` ORDER BY `created` DESC LIMIT ? , ?",
$user['uid'],
Photo::DEFAULT,
$pager->getStart(),
$pager->getItemsPerPage()
));
$photos = [];
if (DBA::isResult($r)) {
// "Twist" is only used for the duepunto theme with style "slackr"
$twist = false;
foreach ($r as $rr) {
$twist = !$twist;
$ext = $phototypes[$rr['type']];
$alt_e = $rr['filename'];
$name_e = $rr['album'];
$photos[] = [
'id' => $rr['id'],
'twist' => ' ' . ($twist ? 'rotleft' : 'rotright') . rand(2,4),
'link' => 'photos/' . $user['nickname'] . '/image/' . $rr['resource-id'],
'title' => DI::l10n()->t('View Photo'),
'src' => 'photo/' . $rr['resource-id'] . '-' . ((($rr['scale']) == 6) ? 4 : $rr['scale']) . '.' . $ext,
'alt' => $alt_e,
'album' => [
'link' => 'photos/' . $user['nickname'] . '/album/' . bin2hex($rr['album']),
'name' => $name_e,
'alt' => DI::l10n()->t('View Album'),
],
];
}
}
$tpl = Renderer::getMarkupTemplate('photos_recent.tpl');
$o .= Renderer::replaceMacros($tpl, [
'$title' => DI::l10n()->t('Recent Photos'),
'$can_post' => $can_post,
'$upload' => [DI::l10n()->t('Upload New Photos'), 'photos/' . $user['nickname'] . '/upload'],
'$photos' => $photos,
'$paginate' => $pager->renderFull($total),
]);
return $o;
}

View file

@ -1,234 +0,0 @@
<?php
/**
* @copyright Copyright (C) 2010-2022, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* @see https://web.archive.org/web/20160405005550/http://portablecontacts.net/draft-spec.html
*/
use Friendica\App;
use Friendica\Content\Text\BBCode;
use Friendica\Core\Logger;
use Friendica\Core\Protocol;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Util\DateTimeFormat;
function poco_init(App $a) {
if (intval(DI::config()->get('system', 'block_public')) || (DI::config()->get('system', 'block_local_dir'))) {
throw new \Friendica\Network\HTTPException\ForbiddenException();
}
if (DI::args()->getArgc() > 1) {
// Only the system mode is supported
throw new \Friendica\Network\HTTPException\NotFoundException();
}
$format = ($_GET['format'] ?? '') ?: 'json';
$totalResults = DBA::count('profile', ['net-publish' => true]);
if ($totalResults == 0) {
throw new \Friendica\Network\HTTPException\ForbiddenException();
}
if (!empty($_GET['startIndex'])) {
$startIndex = intval($_GET['startIndex']);
} else {
$startIndex = 0;
}
$itemsPerPage = (!empty($_GET['count']) ? intval($_GET['count']) : $totalResults);
Logger::info("Start system mode query");
$contacts = DBA::selectToArray('owner-view', [], ['net-publish' => true], ['limit' => [$startIndex, $itemsPerPage]]);
Logger::info("Query done");
$ret = [];
if (!empty($_GET['sorted'])) {
$ret['sorted'] = false;
}
if (!empty($_GET['filtered'])) {
$ret['filtered'] = false;
}
if (!empty($_GET['updatedSince'])) {
$ret['updatedSince'] = false;
}
$ret['startIndex'] = (int) $startIndex;
$ret['itemsPerPage'] = (int) $itemsPerPage;
$ret['totalResults'] = (int) $totalResults;
$ret['entry'] = [];
$fields_ret = [
'id' => false,
'displayName' => false,
'urls' => false,
'updated' => false,
'preferredUsername' => false,
'photos' => false,
'aboutMe' => false,
'currentLocation' => false,
'network' => false,
'tags' => false,
'address' => false,
'contactType' => false,
'generation' => false
];
if (empty($_GET['fields'])) {
foreach ($fields_ret as $k => $v) {
$fields_ret[$k] = true;
}
} else {
$fields_req = explode(',', $_GET['fields']);
foreach ($fields_req as $f) {
$fields_ret[trim($f)] = true;
}
}
if (!is_array($contacts)) {
throw new \Friendica\Network\HTTPException\InternalServerErrorException();
}
if (DBA::isResult($contacts)) {
foreach ($contacts as $contact) {
if (!isset($contact['updated'])) {
$contact['updated'] = '';
}
if (! isset($contact['generation'])) {
$contact['generation'] = 1;
}
if (($contact['keywords'] == "") && isset($contact['pub_keywords'])) {
$contact['keywords'] = $contact['pub_keywords'];
}
if (isset($contact['account-type'])) {
$contact['contact-type'] = $contact['account-type'];
}
$cacheKey = 'about:' . $contact['nick'] . ':' . DateTimeFormat::utc($contact['updated'], DateTimeFormat::ATOM);
$about = DI::cache()->get($cacheKey);
if (is_null($about)) {
$about = BBCode::convertForUriId($contact['uri-id'], $contact['about']);
DI::cache()->set($cacheKey, $about);
}
// Non connected persons can only see the keywords of a Diaspora account
if ($contact['network'] == Protocol::DIASPORA) {
$contact['location'] = "";
$about = "";
}
$entry = [];
if ($fields_ret['id']) {
$entry['id'] = (int)$contact['id'];
}
if ($fields_ret['displayName']) {
$entry['displayName'] = $contact['name'];
}
if ($fields_ret['aboutMe']) {
$entry['aboutMe'] = $about;
}
if ($fields_ret['currentLocation']) {
$entry['currentLocation'] = $contact['location'];
}
if ($fields_ret['generation']) {
$entry['generation'] = (int)$contact['generation'];
}
if ($fields_ret['urls']) {
$entry['urls'] = [['value' => $contact['url'], 'type' => 'profile']];
if ($contact['addr'] && ($contact['network'] !== Protocol::MAIL)) {
$entry['urls'][] = ['value' => 'acct:' . $contact['addr'], 'type' => 'webfinger'];
}
}
if ($fields_ret['preferredUsername']) {
$entry['preferredUsername'] = $contact['nick'];
}
if ($fields_ret['updated']) {
$entry['updated'] = $contact['success_update'];
if ($contact['name-date'] > $entry['updated']) {
$entry['updated'] = $contact['name-date'];
}
if ($contact['uri-date'] > $entry['updated']) {
$entry['updated'] = $contact['uri-date'];
}
if ($contact['avatar-date'] > $entry['updated']) {
$entry['updated'] = $contact['avatar-date'];
}
$entry['updated'] = date("c", strtotime($entry['updated']));
}
if ($fields_ret['photos']) {
$entry['photos'] = [['value' => $contact['photo'], 'type' => 'profile']];
}
if ($fields_ret['network']) {
$entry['network'] = $contact['network'];
if ($entry['network'] == Protocol::STATUSNET) {
$entry['network'] = Protocol::OSTATUS;
}
if (($entry['network'] == "") && ($contact['self'])) {
$entry['network'] = Protocol::DFRN;
}
}
if ($fields_ret['tags']) {
$tags = str_replace(",", " ", $contact['keywords']);
$tags = explode(" ", $tags);
$cleaned = [];
foreach ($tags as $tag) {
$tag = trim(strtolower($tag));
if ($tag != "") {
$cleaned[] = $tag;
}
}
$entry['tags'] = [$cleaned];
}
if ($fields_ret['address']) {
$entry['address'] = [];
if (isset($contact['locality'])) {
$entry['address']['locality'] = $contact['locality'];
}
if (isset($contact['region'])) {
$entry['address']['region'] = $contact['region'];
}
if (isset($contact['country'])) {
$entry['address']['country'] = $contact['country'];
}
}
if ($fields_ret['contactType']) {
$entry['contactType'] = intval($contact['contact-type']);
}
$ret['entry'][] = $entry;
}
} else {
$ret['entry'][] = [];
}
Logger::info("End of poco");
if ($format === 'json') {
System::jsonExit($ret);
} else {
throw new \Friendica\Network\HTTPException\UnsupportedMediaTypeException();
}
}

View file

@ -1,158 +0,0 @@
<?php
/**
* @copyright Copyright (C) 2010-2022, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
use Friendica\App;
use Friendica\Core\Logger;
use Friendica\Core\Protocol;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\Contact;
use Friendica\Protocol\OStatus;
use Friendica\Util\Strings;
use Friendica\Util\Network;
use Friendica\Model\GServer;
use Friendica\Model\Post;
function hub_return($valid, $body)
{
if ($valid) {
echo $body;
} else {
throw new \Friendica\Network\HTTPException\NotFoundException();
}
System::exit();
}
// when receiving an XML feed, always return OK
function hub_post_return()
{
throw new \Friendica\Network\HTTPException\OKException();
}
function pubsub_init(App $a)
{
$nick = ((DI::args()->getArgc() > 1) ? trim(DI::args()->getArgv()[1]) : '');
$contact_id = ((DI::args()->getArgc() > 2) ? intval(DI::args()->getArgv()[2]) : 0 );
if (DI::args()->getMethod() === App\Router::GET) {
$hub_mode = trim($_GET['hub_mode'] ?? '');
$hub_topic = trim($_GET['hub_topic'] ?? '');
$hub_challenge = trim($_GET['hub_challenge'] ?? '');
$hub_verify = trim($_GET['hub_verify_token'] ?? '');
Logger::notice('Subscription from ' . $_SERVER['REMOTE_ADDR'] . ' Mode: ' . $hub_mode . ' Nick: ' . $nick);
Logger::debug('Data: ', ['get' => $_GET]);
$subscribe = (($hub_mode === 'subscribe') ? 1 : 0);
$owner = DBA::selectFirst('user', ['uid'], ['nickname' => $nick, 'account_expired' => false, 'account_removed' => false]);
if (!DBA::isResult($owner)) {
Logger::notice('Local account not found: ' . $nick);
hub_return(false, '');
}
$condition = ['uid' => $owner['uid'], 'id' => $contact_id, 'blocked' => false, 'pending' => false];
if (!empty($hub_verify)) {
$condition['hub-verify'] = $hub_verify;
}
$contact = DBA::selectFirst('contact', ['id', 'poll'], $condition);
if (!DBA::isResult($contact)) {
Logger::notice('Contact ' . $contact_id . ' not found.');
hub_return(false, '');
}
if (!empty($hub_topic) && !Strings::compareLink($hub_topic, $contact['poll'])) {
Logger::notice('Hub topic ' . $hub_topic . ' != ' . $contact['poll']);
hub_return(false, '');
}
// We must initiate an unsubscribe request with a verify_token.
// Don't allow outsiders to unsubscribe us.
if (($hub_mode === 'unsubscribe') && empty($hub_verify)) {
Logger::notice('Bogus unsubscribe');
hub_return(false, '');
}
if (!empty($hub_mode)) {
Contact::update(['subhub' => $subscribe], ['id' => $contact['id']]);
Logger::notice($hub_mode . ' success for contact ' . $contact_id . '.');
}
hub_return(true, $hub_challenge);
}
}
function pubsub_post(App $a)
{
$xml = Network::postdata();
Logger::info('Feed arrived from ' . $_SERVER['REMOTE_ADDR'] . ' for ' . DI::args()->getCommand() . ' with user-agent: ' . $_SERVER['HTTP_USER_AGENT']);
Logger::debug('Data: ' . $xml);
$nick = ((DI::args()->getArgc() > 1) ? trim(DI::args()->getArgv()[1]) : '');
$contact_id = ((DI::args()->getArgc() > 2) ? intval(DI::args()->getArgv()[2]) : 0 );
$importer = DBA::selectFirst('user', [], ['nickname' => $nick, 'account_expired' => false, 'account_removed' => false]);
if (!DBA::isResult($importer)) {
hub_post_return();
}
$condition = ['id' => $contact_id, 'uid' => $importer['uid'], 'subhub' => true, 'blocked' => false];
$contact = DBA::selectFirst('contact', [], $condition);
if (!DBA::isResult($contact)) {
$author = OStatus::salmonAuthor($xml, $importer);
if (!empty($author['contact-id'])) {
$condition = ['id' => $author['contact-id'], 'uid' => $importer['uid'], 'subhub' => true, 'blocked' => false];
$contact = DBA::selectFirst('contact', [], $condition);
Logger::notice('No record for ' . $nick .' with contact id ' . $contact_id . ' - using '.$author['contact-id'].' instead.');
}
if (!DBA::isResult($contact)) {
Logger::notice('Contact ' . $author["author-link"] . ' (' . $contact_id . ') for user ' . $nick . " wasn't found - ignored. XML: " . $xml);
hub_post_return();
}
}
if (!empty($contact['gsid'])) {
GServer::setProtocol($contact['gsid'], Post\DeliveryData::OSTATUS);
}
if (!in_array($contact['rel'], [Contact::SHARING, Contact::FRIEND]) && ($contact['network'] != Protocol::FEED)) {
Logger::notice('Contact ' . $contact['id'] . ' is not expected to share with us - ignored.');
hub_post_return();
}
// We only import feeds from OStatus here
if (!in_array($contact['network'], [Protocol::ACTIVITYPUB, Protocol::OSTATUS])) {
Logger::warning('Unexpected network', ['contact' => $contact]);
hub_post_return();
}
Logger::info('Import item for ' . $nick . ' from ' . $contact['nick'] . ' (' . $contact['id'] . ')');
$feedhub = '';
OStatus::import($xml, $importer, $contact, $feedhub);
hub_post_return();
}

View file

@ -1,147 +0,0 @@
<?php
/**
* @copyright Copyright (C) 2010-2022, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
use Friendica\App;
use Friendica\Core\Logger;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\PushSubscriber;
use Friendica\Util\Strings;
function pubsubhubbub_init(App $a) {
// PuSH subscription must be considered "public" so just block it
// if public access isn't enabled.
if (DI::config()->get('system', 'block_public')) {
throw new \Friendica\Network\HTTPException\ForbiddenException();
}
// Subscription request from subscriber
// https://pubsubhubbub.googlecode.com/git/pubsubhubbub-core-0.4.html#anchor4
// Example from GNU Social:
// [hub_mode] => subscribe
// [hub_callback] => http://status.local/main/push/callback/1
// [hub_verify] => sync
// [hub_verify_token] => af11...
// [hub_secret] => af11...
// [hub_topic] => http://friendica.local/dfrn_poll/sazius
if (DI::args()->getMethod() === App\Router::POST) {
$hub_mode = $_POST['hub_mode'] ?? '';
$hub_callback = $_POST['hub_callback'] ?? '';
$hub_verify_token = $_POST['hub_verify_token'] ?? '';
$hub_secret = $_POST['hub_secret'] ?? '';
$hub_topic = $_POST['hub_topic'] ?? '';
// check for valid hub_mode
if ($hub_mode === 'subscribe') {
$subscribe = 1;
} elseif ($hub_mode === 'unsubscribe') {
$subscribe = 0;
} else {
Logger::notice("Invalid hub_mode=$hub_mode, ignoring.");
throw new \Friendica\Network\HTTPException\NotFoundException();
}
Logger::info("$hub_mode request from " . $_SERVER['REMOTE_ADDR']);
if (DI::args()->getArgc() > 1) {
// Normally the url should now contain the nick name as last part of the url
$nick = DI::args()->getArgv()[1];
} else {
// Get the nick name from the topic as a fallback
$nick = $hub_topic;
}
// Extract nick name and strip any .atom extension
$nick = basename($nick, '.atom');
if (!$nick) {
Logger::notice('Bad hub_topic=$hub_topic, ignoring.');
throw new \Friendica\Network\HTTPException\NotFoundException();
}
// fetch user from database given the nickname
$condition = ['nickname' => $nick, 'account_expired' => false, 'account_removed' => false];
$owner = DBA::selectFirst('user', ['uid', 'nickname'], $condition);
if (!DBA::isResult($owner)) {
Logger::notice('Local account not found: ' . $nick . ' - topic: ' . $hub_topic . ' - callback: ' . $hub_callback);
throw new \Friendica\Network\HTTPException\NotFoundException();
}
// get corresponding row from contact table
$condition = ['uid' => $owner['uid'], 'blocked' => false,
'pending' => false, 'self' => true];
$contact = DBA::selectFirst('contact', ['poll'], $condition);
if (!DBA::isResult($contact)) {
Logger::notice('Self contact for user ' . $owner['uid'] . ' not found.');
throw new \Friendica\Network\HTTPException\NotFoundException();
}
// sanity check that topic URLs are the same
$hub_topic2 = str_replace('/feed/', '/dfrn_poll/', $hub_topic);
$self = DI::baseUrl() . '/api/statuses/user_timeline/' . $owner['nickname'] . '.atom';
if (!Strings::compareLink($hub_topic, $contact['poll']) && !Strings::compareLink($hub_topic2, $contact['poll']) && !Strings::compareLink($hub_topic, $self)) {
Logger::notice('Hub topic ' . $hub_topic . ' != ' . $contact['poll']);
throw new \Friendica\Network\HTTPException\NotFoundException();
}
// do subscriber verification according to the PuSH protocol
$hub_challenge = Strings::getRandomHex(40);
$params = http_build_query([
'hub.mode' => $subscribe == 1 ? 'subscribe' : 'unsubscribe',
'hub.topic' => $hub_topic,
'hub.challenge' => $hub_challenge,
'hub.verify_token' => $hub_verify_token,
// lease time is hard coded to one week (in seconds)
// we don't actually enforce the lease time because GNU
// Social/StatusNet doesn't honour it (yet)
'hub.lease_seconds' => 604800,
]);
$hub_callback = rtrim($hub_callback, ' ?&#');
$separator = parse_url($hub_callback, PHP_URL_QUERY) === null ? '?' : '&';
$fetchResult = DI::httpClient()->fetchFull($hub_callback . $separator . $params);
$body = $fetchResult->getBody();
$ret = $fetchResult->getReturnCode();
// give up if the HTTP return code wasn't a success (2xx)
if ($ret < 200 || $ret > 299) {
Logger::notice("Subscriber verification for $hub_topic at $hub_callback returned $ret, ignoring.");
throw new \Friendica\Network\HTTPException\NotFoundException();
}
// check that the correct hub_challenge code was echoed back
if (trim($body) !== $hub_challenge) {
Logger::notice("Subscriber did not echo back hub.challenge, ignoring.");
Logger::notice("\"$hub_challenge\" != \"".trim($body)."\"");
throw new \Friendica\Network\HTTPException\NotFoundException();
}
PushSubscriber::renew($owner['uid'], $nick, $subscribe, $hub_callback, $hub_topic, $hub_secret);
throw new \Friendica\Network\HTTPException\AcceptedException();
}
System::exit();
}

View file

@ -1,178 +0,0 @@
<?php
/**
* @copyright Copyright (C) 2010-2022, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
use Friendica\App;
use Friendica\Core\Logger;
use Friendica\Core\Session;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\Contact;
use Friendica\Model\Profile;
use Friendica\Network\HTTPClient\Client\HttpClientAccept;
use Friendica\Network\HTTPClient\Client\HttpClientOptions;
use Friendica\Util\Strings;
function redir_init(App $a) {
if (!Session::isAuthenticated()) {
throw new \Friendica\Network\HTTPException\ForbiddenException(DI::l10n()->t('Access denied.'));
}
$url = $_GET['url'] ?? '';
if (DI::args()->getArgc() > 1 && intval(DI::args()->getArgv()[1])) {
$cid = intval(DI::args()->getArgv()[1]);
} else {
$cid = 0;
}
// Try magic auth before the legacy stuff
redir_magic($a, $cid, $url);
if (empty($cid)) {
throw new \Friendica\Network\HTTPException\BadRequestException(DI::l10n()->t('Bad Request.'));
}
$fields = ['id', 'uid', 'nurl', 'url', 'addr', 'name'];
$contact = DBA::selectFirst('contact', $fields, ['id' => $cid, 'uid' => [0, local_user()]]);
if (!DBA::isResult($contact)) {
throw new \Friendica\Network\HTTPException\NotFoundException(DI::l10n()->t('Contact not found.'));
}
$contact_url = $contact['url'];
if (!empty($a->getContactId()) && $a->getContactId() == $cid) {
// Local user is already authenticated.
redir_check_url($contact_url, $url);
$a->redirect($url ?: $contact_url);
}
if ($contact['uid'] == 0 && local_user()) {
// Let's have a look if there is an established connection
// between the public contact we have found and the local user.
$contact = DBA::selectFirst('contact', $fields, ['nurl' => $contact['nurl'], 'uid' => local_user()]);
if (DBA::isResult($contact)) {
$cid = $contact['id'];
}
if (!empty($a->getContactId()) && $a->getContactId() == $cid) {
// Local user is already authenticated.
redir_check_url($contact_url, $url);
$target_url = $url ?: $contact_url;
Logger::info($contact['name'] . " is already authenticated. Redirecting to " . $target_url);
$a->redirect($target_url);
}
}
if (remote_user()) {
$host = substr(DI::baseUrl()->getUrlPath() . (DI::baseUrl()->getUrlPath() ? '/' . DI::baseUrl()->getUrlPath() : ''), strpos(DI::baseUrl()->getUrlPath(), '://') + 3);
$remotehost = substr($contact['addr'], strpos($contact['addr'], '@') + 1);
// On a local instance we have to check if the local user has already authenticated
// 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) && (Session::getRemoteContactID(Session::get('visitor_visiting')) == Session::get('visitor_id'))) {
// Remote user is already authenticated.
redir_check_url($contact_url, $url);
$target_url = $url ?: $contact_url;
Logger::info($contact['name'] . " is already authenticated. Redirecting to " . $target_url);
$a->redirect($target_url);
}
}
if (empty($url)) {
throw new \Friendica\Network\HTTPException\BadRequestException(DI::l10n()->t('Bad Request.'));
}
// If we don't have a connected contact, redirect with
// the 'zrl' parameter.
$my_profile = Profile::getMyURL();
if (!empty($my_profile) && !Strings::compareLink($my_profile, $url)) {
$separator = strpos($url, '?') ? '&' : '?';
$url .= $separator . 'zrl=' . urlencode($my_profile);
}
Logger::info('redirecting to ' . $url);
$a->redirect($url);
}
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]);
throw new \Friendica\Network\HTTPException\NotFoundException(DI::l10n()->t('Contact not found.'));
} else {
$contact_url = $contact['url'];
redir_check_url($contact_url, $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, DI::baseUrl()) || Strings::compareLink($contact_url, $visitor)) {
Logger::info('Redirecting without magic', ['target' => $target_url, 'visitor' => $visitor, 'contact' => $contact_url]);
DI::app()->redirect($target_url);
}
// Test for magic auth on the target system
$serverret = DI::httpClient()->head($basepath . '/magic', [HttpClientOptions::ACCEPT_CONTENT => HttpClientAccept::HTML]);
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]);
System::externalRedirect($target_url);
} else {
Logger::info('No magic for contact', ['contact' => $contact_url]);
}
}
function redir_check_url(string $contact_url, string $url)
{
if (empty($contact_url) || empty($url)) {
return;
}
$url_host = parse_url($url, PHP_URL_HOST);
if (empty($url_host)) {
$url_host = parse_url(DI::baseUrl(), PHP_URL_HOST);
}
$contact_url_host = parse_url($contact_url, PHP_URL_HOST);
if ($url_host == $contact_url_host) {
return;
}
Logger::error('URL check host mismatch', ['contact' => $contact_url, 'url' => $url]);
throw new \Friendica\Network\HTTPException\ForbiddenException(DI::l10n()->t('Access denied.'));
}

View file

@ -1,108 +0,0 @@
<?php
/**
* @copyright Copyright (C) 2010-2022, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
use Friendica\App;
use Friendica\Core\Renderer;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\User;
use Friendica\Util\Strings;
function removeme_post(App $a)
{
if (!local_user()) {
return;
}
if (!empty($_SESSION['submanage'])) {
return;
}
if (empty($_POST['qxz_password'])) {
return;
}
if (empty($_POST['verify'])) {
return;
}
if ($_POST['verify'] !== $_SESSION['remove_account_verify']) {
return;
}
// send notification to admins so that they can clean um the backups
// send email to admins
$admin_mails = explode(",", str_replace(" ", "", DI::config()->get('config', 'admin_email')));
foreach ($admin_mails as $mail) {
$admin = DBA::selectFirst('user', ['uid', 'language', 'email', 'username'], ['email' => $mail]);
if (!DBA::isResult($admin)) {
continue;
}
$l10n = DI::l10n()->withLang($admin['language']);
$email = DI::emailer()
->newSystemMail()
->withMessage(
$l10n->t('[Friendica System Notify]') . ' ' . $l10n->t('User deleted their account'),
$l10n->t('On your Friendica node an user deleted their account. Please ensure that their data is removed from the backups.'),
$l10n->t('The user id is %d', local_user()))
->forUser($admin)
->withRecipient($admin['email'])
->build();
DI::emailer()->send($email);
}
if (User::getIdFromPasswordAuthentication($a->getLoggedInUserId(), trim($_POST['qxz_password']))) {
User::remove($a->getLoggedInUserId());
unset($_SESSION['authenticated']);
unset($_SESSION['uid']);
DI::baseUrl()->redirect();
// NOTREACHED
}
}
function removeme_content(App $a)
{
if (!local_user()) {
DI::baseUrl()->redirect();
}
$hash = Strings::getRandomHex();
require_once("mod/settings.php");
settings_init($a);
$_SESSION['remove_account_verify'] = $hash;
$tpl = Renderer::getMarkupTemplate('removeme.tpl');
$o = Renderer::replaceMacros($tpl, [
'$basedir' => DI::baseUrl()->get(),
'$hash' => $hash,
'$title' => DI::l10n()->t('Remove My Account'),
'$desc' => DI::l10n()->t('This will completely remove your account. Once this has been done it is not recoverable.'),
'$passwd' => DI::l10n()->t('Please enter your password for verification:'),
'$submit' => DI::l10n()->t('Remove My Account')
]);
return $o;
}

View file

@ -1,64 +0,0 @@
<?php
/**
* @copyright Copyright (C) 2010-2022, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
use Friendica\App;
use Friendica\Core\Protocol;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\Contact;
function repair_ostatus_content(App $a) {
if (!local_user()) {
notice(DI::l10n()->t('Permission denied.'));
DI::baseUrl()->redirect('ostatus_repair');
// NOTREACHED
}
$o = '<h2>' . DI::l10n()->t('Resubscribing to OStatus contacts') . '</h2>';
$uid = local_user();
$counter = intval($_REQUEST['counter'] ?? 0);
$condition = ['uid' => $uid, 'network' => Protocol::OSTATUS, 'rel' => [Contact::FRIEND, Contact::SHARING]];
$total = DBA::count('contact', $condition);
if (!$total) {
return ($o . DI::l10n()->t('Error'));
}
$contact = Contact::selectToArray(['url'], $condition, ['order' => ['url'], 'limit' => [$counter++, 1]]);
if (!DBA::isResult($contact)) {
$o .= DI::l10n()->t('Done');
return $o;
}
$o .= '<p>' . $counter . '/' . $total . ': ' . $contact[0]['url'] . '</p>';
$o .= '<p>' . DI::l10n()->t('Keep this window open until done.') . '</p>';
Contact::createFromProbeForUser($a->getLoggedInUserId(), $contact[0]['url']);
DI::page()['htmlhead'] = '<meta http-equiv="refresh" content="1; URL=' . DI::baseUrl() . '/repair_ostatus?counter=' . $counter . '">';
return $o;
}

View file

@ -1,183 +0,0 @@
<?php
/**
* @copyright Copyright (C) 2010-2022, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
use Friendica\App;
use Friendica\Core\Logger;
use Friendica\Core\Protocol;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\GServer;
use Friendica\Model\Post;
use Friendica\Protocol\ActivityNamespace;
use Friendica\Protocol\OStatus;
use Friendica\Protocol\Salmon;
use Friendica\Util\Crypto;
use Friendica\Util\Network;
use Friendica\Util\Strings;
function salmon_post(App $a, $xml = '') {
if (empty($xml)) {
$xml = Network::postdata();
}
Logger::debug('new salmon ' . $xml);
$nick = trim(DI::args()->getArgv()[1] ?? '');
$importer = DBA::selectFirst('user', [], ['nickname' => $nick, 'account_expired' => false, 'account_removed' => false]);
if (! DBA::isResult($importer)) {
throw new \Friendica\Network\HTTPException\InternalServerErrorException();
}
// parse the xml
$dom = simplexml_load_string($xml,'SimpleXMLElement',0, ActivityNamespace::SALMON_ME);
$base = null;
// figure out where in the DOM tree our data is hiding
if (!empty($dom->provenance->data))
$base = $dom->provenance;
elseif (!empty($dom->env->data))
$base = $dom->env;
elseif (!empty($dom->data))
$base = $dom;
if (empty($base)) {
Logger::notice('unable to locate salmon data in xml');
throw new \Friendica\Network\HTTPException\BadRequestException();
}
// Stash the signature away for now. We have to find their key or it won't be good for anything.
$signature = Strings::base64UrlDecode($base->sig);
// unpack the data
// strip whitespace so our data element will return to one big base64 blob
$data = str_replace([" ","\t","\r","\n"],["","","",""],$base->data);
// stash away some other stuff for later
$type = $base->data[0]->attributes()->type[0];
$keyhash = $base->sig[0]->attributes()->keyhash[0] ?? '';
$encoding = $base->encoding;
$alg = $base->alg;
// Salmon magic signatures have evolved and there is no way of knowing ahead of time which
// flavour we have. We'll try and verify it regardless.
$stnet_signed_data = $data;
$signed_data = $data . '.' . Strings::base64UrlEncode($type) . '.' . Strings::base64UrlEncode($encoding) . '.' . Strings::base64UrlEncode($alg);
$compliant_format = str_replace('=', '', $signed_data);
// decode the data
$data = Strings::base64UrlDecode($data);
$author = OStatus::salmonAuthor($data, $importer);
$author_link = $author["author-link"];
if(! $author_link) {
Logger::notice('Could not retrieve author URI.');
throw new \Friendica\Network\HTTPException\BadRequestException();
}
// Once we have the author URI, go to the web and try to find their public key
Logger::notice('Fetching key for ' . $author_link);
$key = Salmon::getKey($author_link, $keyhash);
if(! $key) {
Logger::notice('Could not retrieve author key.');
throw new \Friendica\Network\HTTPException\BadRequestException();
}
$key_info = explode('.',$key);
$m = Strings::base64UrlDecode($key_info[1]);
$e = Strings::base64UrlDecode($key_info[2]);
Logger::info('key details', ['info' => $key_info]);
$pubkey = Crypto::meToPem($m, $e);
// We should have everything we need now. Let's see if it verifies.
// Try GNU Social format
$verify = Crypto::rsaVerify($signed_data, $signature, $pubkey);
$mode = 1;
if (! $verify) {
Logger::notice('message did not verify using protocol. Trying compliant format.');
$verify = Crypto::rsaVerify($compliant_format, $signature, $pubkey);
$mode = 2;
}
if (! $verify) {
Logger::notice('message did not verify using padding. Trying old statusnet format.');
$verify = Crypto::rsaVerify($stnet_signed_data, $signature, $pubkey);
$mode = 3;
}
if (! $verify) {
Logger::notice('Message did not verify. Discarding.');
throw new \Friendica\Network\HTTPException\BadRequestException();
}
Logger::notice('Message verified with mode '.$mode);
/*
*
* If we reached this point, the message is good. Now let's figure out if the author is allowed to send us stuff.
*
*/
$contact = DBA::selectFirst('contact', [], ["`network` IN (?, ?) AND (`nurl` = ? OR `alias` = ? OR `alias` = ?) AND `uid` = ?",
Protocol::OSTATUS, Protocol::DFRN, Strings::normaliseLink($author_link), $author_link, Strings::normaliseLink($author_link), $importer['uid']]);
if (!empty($contact['gsid'])) {
GServer::setProtocol($contact['gsid'], Post\DeliveryData::OSTATUS);
}
// Have we ignored the person?
// If so we can not accept this post.
if (!empty($contact['blocked'])) {
Logger::notice('Ignoring this author.');
throw new \Friendica\Network\HTTPException\AcceptedException();
}
// Placeholder for hub discovery.
$hub = '';
$contact = $contact ?: [];
OStatus::import($data, $importer, $contact, $hub);
throw new \Friendica\Network\HTTPException\OKException();
}

View file

@ -1,358 +0,0 @@
<?php
/**
* @copyright Copyright (C) 2010-2022, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
use Friendica\App;
use Friendica\BaseModule;
use Friendica\Content\Feature;
use Friendica\Content\Nav;
use Friendica\Core\Hook;
use Friendica\Core\Logger;
use Friendica\Core\Renderer;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\Item;
use Friendica\Model\User;
use Friendica\Module\BaseSettings;
use Friendica\Module\Security\Login;
use Friendica\Protocol\Email;
function settings_init(App $a)
{
if (!local_user()) {
notice(DI::l10n()->t('Permission denied.'));
return;
}
BaseSettings::createAside();
}
function settings_post(App $a)
{
if (!$a->isLoggedIn()) {
notice(DI::l10n()->t('Permission denied.'));
return;
}
if (!empty($_SESSION['submanage'])) {
return;
}
if ((DI::args()->getArgc() > 1) && (DI::args()->getArgv()[1] == 'addon')) {
BaseModule::checkFormSecurityTokenRedirectOnError(DI::args()->getQueryString(), 'settings_addon');
Hook::callAll('addon_settings_post', $_POST);
DI::baseUrl()->redirect(DI::args()->getQueryString());
return;
}
$user = User::getById($a->getLoggedInUserId());
if ((DI::args()->getArgc() > 1) && (DI::args()->getArgv()[1] == 'connectors')) {
BaseModule::checkFormSecurityTokenRedirectOnError(DI::args()->getQueryString(), 'settings_connectors');
if (!empty($_POST['general-submit'])) {
DI::pConfig()->set(local_user(), 'system', 'accept_only_sharer', intval($_POST['accept_only_sharer']));
DI::pConfig()->set(local_user(), 'system', 'disable_cw', !intval($_POST['enable_cw']));
DI::pConfig()->set(local_user(), 'system', 'no_intelligent_shortening', !intval($_POST['enable_smart_shortening']));
DI::pConfig()->set(local_user(), 'system', 'simple_shortening', intval($_POST['simple_shortening']));
DI::pConfig()->set(local_user(), 'system', 'attach_link_title', intval($_POST['attach_link_title']));
DI::pConfig()->set(local_user(), 'ostatus', 'legacy_contact', $_POST['legacy_contact']);
} elseif (!empty($_POST['mail-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'] ?? '';
if (function_exists('imap_open') && !DI::config()->get('system', 'imap_disabled')) {
if (!DBA::exists('mailacct', ['uid' => local_user()])) {
DBA::insert('mailacct', ['uid' => local_user()]);
}
if (strlen($mail_pass)) {
$pass = '';
openssl_public_encrypt($mail_pass, $pass, $user['pubkey']);
DBA::update('mailacct', ['pass' => bin2hex($pass)], ['uid' => local_user()]);
}
$r = DBA::update('mailacct', [
'server' => $mail_server,
'port' => $mail_port,
'ssltype' => $mail_ssl,
'user' => $mail_user,
'action' => $mail_action,
'movetofolder' => $mail_movetofolder,
'mailbox' => 'INBOX',
'reply_to' => $mail_replyto,
'pubmail' => $mail_pubmail
], ['uid' => local_user()]);
Logger::debug('updating mailaccount', ['response' => $r]);
$mailacct = DBA::selectFirst('mailacct', [], ['uid' => local_user()]);
if (DBA::isResult($mailacct)) {
$mb = Email::constructMailboxName($mailacct);
if (strlen($mailacct['server'])) {
$dcrpass = '';
openssl_private_decrypt(hex2bin($mailacct['pass']), $dcrpass, $user['prvkey']);
$mbox = Email::connect($mb, $mail_user, $dcrpass);
unset($dcrpass);
if (!$mbox) {
notice(DI::l10n()->t('Failed to connect with email account using the settings provided.'));
}
}
}
}
}
Hook::callAll('connector_settings_post', $_POST);
DI::baseUrl()->redirect(DI::args()->getQueryString());
return;
}
if ((DI::args()->getArgc() > 1) && (DI::args()->getArgv()[1] === 'features')) {
BaseModule::checkFormSecurityTokenRedirectOnError('/settings/features', 'settings_features');
foreach ($_POST as $k => $v) {
if (strpos($k, 'feature_') === 0) {
DI::pConfig()->set(local_user(), 'feature', substr($k, 8), ((intval($v)) ? 1 : 0));
}
}
return;
}
}
function settings_content(App $a)
{
$o = '';
Nav::setSelected('settings');
if (!local_user()) {
//notice(DI::l10n()->t('Permission denied.'));
return Login::form();
}
if (!empty($_SESSION['submanage'])) {
notice(DI::l10n()->t('Permission denied.'));
return '';
}
if ((DI::args()->getArgc() > 1) && (DI::args()->getArgv()[1] === 'oauth')) {
if ((DI::args()->getArgc() > 3) && (DI::args()->getArgv()[2] === 'delete')) {
BaseModule::checkFormSecurityTokenRedirectOnError('/settings/oauth', 'settings_oauth', 't');
DBA::delete('application-token', ['application-id' => DI::args()->getArgv()[3], 'uid' => local_user()]);
DI::baseUrl()->redirect('settings/oauth/', true);
return '';
}
$applications = DBA::selectToArray('application-view', ['id', 'uid', 'name', 'website', 'scopes', 'created_at'], ['uid' => local_user()]);
$tpl = Renderer::getMarkupTemplate('settings/oauth.tpl');
$o .= Renderer::replaceMacros($tpl, [
'$form_security_token' => BaseModule::getFormSecurityToken("settings_oauth"),
'$baseurl' => DI::baseUrl()->get(true),
'$title' => DI::l10n()->t('Connected Apps'),
'$name' => DI::l10n()->t('Name'),
'$website' => DI::l10n()->t('Home Page'),
'$created_at' => DI::l10n()->t('Created'),
'$delete' => DI::l10n()->t('Remove authorization'),
'$apps' => $applications,
]);
return $o;
}
if ((DI::args()->getArgc() > 1) && (DI::args()->getArgv()[1] === 'addon')) {
$addon_settings_forms = [];
foreach (DI::dba()->selectToArray('hook', ['file', 'function'], ['hook' => 'addon_settings']) as $hook) {
$data = [];
Hook::callSingle(DI::app(), 'addon_settings', [$hook['file'], $hook['function']], $data);
if (!empty($data['href'])) {
$tpl = Renderer::getMarkupTemplate('settings/addon/link.tpl');
$addon_settings_forms[] = Renderer::replaceMacros($tpl, [
'$addon' => $data['addon'],
'$title' => $data['title'],
'$href' => $data['href'],
]);
} elseif(!empty($data['addon'])) {
$tpl = Renderer::getMarkupTemplate('settings/addon/panel.tpl');
$addon_settings_forms[$data['addon']] = Renderer::replaceMacros($tpl, [
'$addon' => $data['addon'],
'$title' => $data['title'],
'$open' => (DI::args()->getArgv()[2] ?? '') === $data['addon'],
'$html' => $data['html'] ?? '',
'$submit' => $data['submit'] ?? DI::l10n()->t('Save Settings'),
]);
}
}
$tpl = Renderer::getMarkupTemplate('settings/addons.tpl');
$o .= Renderer::replaceMacros($tpl, [
'$form_security_token' => BaseModule::getFormSecurityToken("settings_addon"),
'$title' => DI::l10n()->t('Addon Settings'),
'$no_addons_settings_configured' => DI::l10n()->t('No Addon settings configured'),
'$addon_settings_forms' => $addon_settings_forms,
]);
return $o;
}
if ((DI::args()->getArgc() > 1) && (DI::args()->getArgv()[1] === 'features')) {
$arr = [];
$features = Feature::get();
foreach ($features as $fname => $fdata) {
$arr[$fname] = [];
$arr[$fname][0] = $fdata[0];
foreach (array_slice($fdata,1) as $f) {
$arr[$fname][1][] = ['feature_' . $f[0], $f[1], Feature::isEnabled(local_user(), $f[0]), $f[2]];
}
}
$tpl = Renderer::getMarkupTemplate('settings/features.tpl');
$o .= Renderer::replaceMacros($tpl, [
'$form_security_token' => BaseModule::getFormSecurityToken("settings_features"),
'$title' => DI::l10n()->t('Additional Features'),
'$features' => $arr,
'$submit' => DI::l10n()->t('Save Settings'),
]);
return $o;
}
if ((DI::args()->getArgc() > 1) && (DI::args()->getArgv()[1] === 'connectors')) {
$accept_only_sharer = intval(DI::pConfig()->get(local_user(), 'system', 'accept_only_sharer'));
$enable_cw = !intval(DI::pConfig()->get(local_user(), 'system', 'disable_cw'));
$enable_smart_shortening = !intval(DI::pConfig()->get(local_user(), 'system', 'no_intelligent_shortening'));
$simple_shortening = intval(DI::pConfig()->get(local_user(), 'system', 'simple_shortening'));
$attach_link_title = intval(DI::pConfig()->get(local_user(), 'system', 'attach_link_title'));
$legacy_contact = DI::pConfig()->get(local_user(), 'ostatus', 'legacy_contact');
if (!empty($legacy_contact)) {
/// @todo Isn't it supposed to be a $a->internalRedirect() call?
DI::page()['htmlhead'] = '<meta http-equiv="refresh" content="0; URL=' . DI::baseUrl().'/ostatus_subscribe?url=' . urlencode($legacy_contact) . '">';
}
$connector_settings_forms = [];
foreach (DI::dba()->selectToArray('hook', ['file', 'function'], ['hook' => 'connector_settings']) as $hook) {
$data = [];
Hook::callSingle(DI::app(), 'connector_settings', [$hook['file'], $hook['function']], $data);
$tpl = Renderer::getMarkupTemplate('settings/addon/connector.tpl');
$connector_settings_forms[$data['connector']] = Renderer::replaceMacros($tpl, [
'$connector' => $data['connector'],
'$title' => $data['title'],
'$image' => $data['image'] ?? '',
'$enabled' => $data['enabled'] ?? true,
'$open' => (DI::args()->getArgv()[2] ?? '') === $data['connector'],
'$html' => $data['html'] ?? '',
'$submit' => $data['submit'] ?? DI::l10n()->t('Save Settings'),
]);
}
if ($a->isSiteAdmin()) {
$diasp_enabled = DI::l10n()->t('Built-in support for %s connectivity is %s', DI::l10n()->t('Diaspora (Socialhome, Hubzilla)'), ((DI::config()->get('system', 'diaspora_enabled')) ? DI::l10n()->t('enabled') : DI::l10n()->t('disabled')));
$ostat_enabled = DI::l10n()->t('Built-in support for %s connectivity is %s', DI::l10n()->t('OStatus (GNU Social)'), ((DI::config()->get('system', 'ostatus_disabled')) ? DI::l10n()->t('disabled') : DI::l10n()->t('enabled')));
} else {
$diasp_enabled = "";
$ostat_enabled = "";
}
$mail_disabled = ((function_exists('imap_open') && (!DI::config()->get('system', 'imap_disabled'))) ? 0 : 1);
if (!$mail_disabled) {
$mailacct = DBA::selectFirst('mailacct', [], ['uid' => local_user()]);
} else {
$mailacct = null;
}
$mail_server = $mailacct['server'] ?? '';
$mail_port = (!empty($mailacct['port']) && is_numeric($mailacct['port'])) ? (int)$mailacct['port'] : '';
$mail_ssl = $mailacct['ssltype'] ?? '';
$mail_user = $mailacct['user'] ?? '';
$mail_replyto = $mailacct['reply_to'] ?? '';
$mail_pubmail = $mailacct['pubmail'] ?? 0;
$mail_action = $mailacct['action'] ?? 0;
$mail_movetofolder = $mailacct['movetofolder'] ?? '';
$mail_chk = $mailacct['last_check'] ?? DBA::NULL_DATETIME;
$tpl = Renderer::getMarkupTemplate('settings/connectors.tpl');
$mail_disabled_message = ($mail_disabled ? DI::l10n()->t('Email access is disabled on this site.') : '');
$ssl_options = ['TLS' => 'TLS', 'SSL' => 'SSL'];
if (DI::config()->get('system', 'insecure_imap')) {
$ssl_options['notls'] = DI::l10n()->t('None');
}
$o .= Renderer::replaceMacros($tpl, [
'$form_security_token' => BaseModule::getFormSecurityToken("settings_connectors"),
'$title' => DI::l10n()->t('Social Networks'),
'$diasp_enabled' => $diasp_enabled,
'$ostat_enabled' => $ostat_enabled,
'$general_settings' => DI::l10n()->t('General Social Media Settings'),
'$accept_only_sharer' => [
'accept_only_sharer',
DI::l10n()->t('Followed content scope'),
$accept_only_sharer,
DI::l10n()->t('By default, conversations in which your follows participated but didn\'t start will be shown in your timeline. You can turn this behavior off, or expand it to the conversations in which your follows liked a post.'),
[
Item::COMPLETION_NONE => DI::l10n()->t('Only conversations my follows started'),
Item::COMPLETION_COMMENT => DI::l10n()->t('Conversations my follows started or commented on (default)'),
Item::COMPLETION_LIKE => DI::l10n()->t('Any conversation my follows interacted with, including likes'),
]
],
'$enable_cw' => ['enable_cw', DI::l10n()->t('Enable Content Warning'), $enable_cw, DI::l10n()->t('Users on networks like Mastodon or Pleroma are able to set a content warning field which collapse their post by default. This enables the automatic collapsing instead of setting the content warning as the post title. Doesn\'t affect any other content filtering you eventually set up.')],
'$enable_smart_shortening' => ['enable_smart_shortening', DI::l10n()->t('Enable intelligent shortening'), $enable_smart_shortening, DI::l10n()->t('Normally the system tries to find the best link to add to shortened posts. If disabled, every shortened post will always point to the original friendica post.')],
'$simple_shortening' => ['simple_shortening', DI::l10n()->t('Enable simple text shortening'), $simple_shortening, DI::l10n()->t('Normally the system shortens posts at the next line feed. If this option is enabled then the system will shorten the text at the maximum character limit.')],
'$attach_link_title' => ['attach_link_title', DI::l10n()->t('Attach the link title'), $attach_link_title, DI::l10n()->t('When activated, the title of the attached link will be added as a title on posts to Diaspora. This is mostly helpful with "remote-self" contacts that share feed content.')],
'$legacy_contact' => ['legacy_contact', DI::l10n()->t('Your legacy ActivityPub/GNU Social account'), $legacy_contact, DI::l10n()->t("If you enter your old account name from an ActivityPub based system or your GNU Social/Statusnet account name here (in the format user@domain.tld), your contacts will be added automatically. The field will be emptied when done.")],
'$repair_ostatus_url' => DI::baseUrl() . '/repair_ostatus',
'$repair_ostatus_text' => DI::l10n()->t('Repair OStatus subscriptions'),
'$connector_settings_forms' => $connector_settings_forms,
'$h_mail' => DI::l10n()->t('Email/Mailbox Setup'),
'$mail_desc' => DI::l10n()->t("If you wish to communicate with email contacts using this service \x28optional\x29, please specify how to connect to your mailbox."),
'$mail_lastcheck' => ['mail_lastcheck', DI::l10n()->t('Last successful email check:'), $mail_chk, ''],
'$mail_disabled' => $mail_disabled_message,
'$mail_server' => ['mail_server', DI::l10n()->t('IMAP server name:'), $mail_server, ''],
'$mail_port' => ['mail_port', DI::l10n()->t('IMAP port:'), $mail_port, ''],
'$mail_ssl' => ['mail_ssl', DI::l10n()->t('Security:'), strtoupper($mail_ssl), '', $ssl_options],
'$mail_user' => ['mail_user', DI::l10n()->t('Email login name:'), $mail_user, ''],
'$mail_pass' => ['mail_pass', DI::l10n()->t('Email password:'), '', ''],
'$mail_replyto' => ['mail_replyto', DI::l10n()->t('Reply-to address:'), $mail_replyto, 'Optional'],
'$mail_pubmail' => ['mail_pubmail', DI::l10n()->t('Send public posts to all email contacts:'), $mail_pubmail, ''],
'$mail_action' => ['mail_action', DI::l10n()->t('Action after import:'), $mail_action, '', [0 => DI::l10n()->t('None'), 1 => DI::l10n()->t('Delete'), 2 => DI::l10n()->t('Mark as seen'), 3 => DI::l10n()->t('Move to folder')]],
'$mail_movetofolder' => ['mail_movetofolder', DI::l10n()->t('Move to folder:'), $mail_movetofolder, ''],
'$submit' => DI::l10n()->t('Save Settings'),
]);
Hook::callAll('display_settings', $o);
return $o;
}
}

View file

@ -1,58 +0,0 @@
<?php
/**
* @copyright Copyright (C) 2010-2022, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
use Friendica\App;
use Friendica\Content\Widget;
use Friendica\Core\Renderer;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\Contact;
use Friendica\Module\Contact as ModuleContact;
use Friendica\Network\HTTPException;
function suggest_content(App $a)
{
if (!local_user()) {
throw new HTTPException\ForbiddenException(DI::l10n()->t('Permission denied.'));
}
$_SESSION['return_path'] = DI::args()->getCommand();
DI::page()['aside'] .= Widget::findPeople();
DI::page()['aside'] .= Widget::follow();
$contacts = Contact\Relation::getSuggestions(local_user());
if (!DBA::isResult($contacts)) {
return DI::l10n()->t('No suggestions available. If this is a new site, please try again in 24 hours.');
}
$entries = [];
foreach ($contacts as $contact) {
$entries[] = ModuleContact::getContactTemplateVars($contact);
}
$tpl = Renderer::getMarkupTemplate('viewcontact_template.tpl');
return Renderer::replaceMacros($tpl,[
'$title' => DI::l10n()->t('Friend Suggestions'),
'$contacts' => $entries,
]);
}

View file

@ -1,171 +0,0 @@
<?php
/**
* @copyright Copyright (C) 2010-2022, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
use Friendica\App;
use Friendica\Core\Hook;
use Friendica\Core\Logger;
use Friendica\Core\Session;
use Friendica\Core\System;
use Friendica\Core\Worker;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\Contact;
use Friendica\Model\Item;
use Friendica\Model\Post;
use Friendica\Model\Tag;
use Friendica\Protocol\Activity;
use Friendica\Util\XML;
use Friendica\Worker\Delivery;
function tagger_content(App $a) {
if (!Session::isAuthenticated()) {
return;
}
$term = trim($_GET['term'] ?? '');
// no commas allowed
$term = str_replace([',',' ', '<', '>'],['','_', '', ''], $term);
if (!$term) {
return;
}
$item_id = ((DI::args()->getArgc() > 1) ? trim(DI::args()->getArgv()[1]) : 0);
Logger::info('tagger: tag', ['term' => $term, 'item' => $item_id]);
$item = Post::selectFirst([], ['id' => $item_id]);
if (!$item_id || !DBA::isResult($item)) {
Logger::notice('tagger: no item ' . $item_id);
return;
}
$owner_uid = $item['uid'];
if (local_user() != $owner_uid) {
return;
}
$contact = Contact::selectFirst([], ['self' => true, 'uid' => local_user()]);
if (!DBA::isResult($contact)) {
Logger::warning('Self contact not found.', ['uid' => local_user()]);
return;
}
$uri = Item::newURI();
$xterm = XML::escape($term);
$post_type = (($item['resource-id']) ? DI::l10n()->t('photo') : DI::l10n()->t('status'));
$targettype = (($item['resource-id']) ? Activity\ObjectType::IMAGE : Activity\ObjectType::NOTE );
$href = DI::baseUrl() . '/display/' . $item['guid'];
$link = XML::escape('<link rel="alternate" type="text/html" href="'. $href . '" />' . "\n");
$body = XML::escape($item['body']);
$target = <<< EOT
<target>
<type>$targettype</type>
<local>1</local>
<id>{$item['uri']}</id>
<link>$link</link>
<title></title>
<content>$body</content>
</target>
EOT;
$tagid = DI::baseUrl() . '/search?tag=' . $xterm;
$objtype = Activity\ObjectType::TAGTERM;
$obj = <<< EOT
<object>
<type>$objtype</type>
<local>1</local>
<id>$tagid</id>
<link>$tagid</link>
<title>$xterm</title>
<content>$xterm</content>
</object>
EOT;
$bodyverb = DI::l10n()->t('%1$s tagged %2$s\'s %3$s with %4$s');
if (!isset($bodyverb)) {
return;
}
$termlink = html_entity_decode('&#x2317;') . '[url=' . DI::baseUrl() . '/search?tag=' . $term . ']'. $term . '[/url]';
$arr = [];
$arr['guid'] = System::createUUID();
$arr['uri'] = $uri;
$arr['uid'] = $owner_uid;
$arr['contact-id'] = $contact['id'];
$arr['wall'] = $item['wall'];
$arr['gravity'] = GRAVITY_COMMENT;
$arr['parent'] = $item['id'];
$arr['thr-parent'] = $item['uri'];
$arr['owner-name'] = $item['author-name'];
$arr['owner-link'] = $item['author-link'];
$arr['owner-avatar'] = $item['author-avatar'];
$arr['author-name'] = $contact['name'];
$arr['author-link'] = $contact['url'];
$arr['author-avatar'] = $contact['thumb'];
$ulink = '[url=' . $contact['url'] . ']' . $contact['name'] . '[/url]';
$alink = '[url=' . $item['author-link'] . ']' . $item['author-name'] . '[/url]';
$plink = '[url=' . $item['plink'] . ']' . $post_type . '[/url]';
$arr['body'] = sprintf( $bodyverb, $ulink, $alink, $plink, $termlink );
$arr['verb'] = Activity::TAG;
$arr['target-type'] = $targettype;
$arr['target'] = $target;
$arr['object-type'] = $objtype;
$arr['object'] = $obj;
$arr['private'] = $item['private'];
$arr['allow_cid'] = $item['allow_cid'];
$arr['allow_gid'] = $item['allow_gid'];
$arr['deny_cid'] = $item['deny_cid'];
$arr['deny_gid'] = $item['deny_gid'];
$arr['visible'] = 1;
$arr['unseen'] = 1;
$arr['origin'] = 1;
$post_id = Item::insert($arr);
if (!$item['visible']) {
Item::update(['visible' => true], ['id' => $item['id']]);
}
Tag::store($item['uri-id'], Tag::HASHTAG, $term);
$arr['id'] = $post_id;
Hook::callAll('post_local_end', $arr);
$post = Post::selectFirst(['uri-id', 'uid'], ['id' => $post_id]);
Worker::add(PRIORITY_HIGH, "Notifier", Delivery::POST, $post['uri-id'], $post['uid']);
System::exit();
}

View file

@ -1,131 +0,0 @@
<?php
/**
* @copyright Copyright (C) 2010-2022, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
use Friendica\App;
use Friendica\Content\Text\BBCode;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\Post;
use Friendica\Model\Tag;
function tagrm_post(App $a)
{
if (!local_user()) {
DI::baseUrl()->redirect($_SESSION['photo_return']);
}
if (!empty($_POST['submit']) && ($_POST['submit'] === DI::l10n()->t('Cancel'))) {
DI::baseUrl()->redirect($_SESSION['photo_return']);
}
$tags = [];
foreach ($_POST['tag'] ?? [] as $tag) {
$tags[] = hex2bin(trim($tag));
}
$item_id = $_POST['item'] ?? 0;
update_tags($item_id, $tags);
DI::baseUrl()->redirect($_SESSION['photo_return']);
// NOTREACHED
}
/**
* Updates tags from an item
*
* @param $item_id
* @param $tags array
* @throws Exception
*/
function update_tags($item_id, $tags)
{
if (empty($item_id) || empty($tags)) {
return;
}
$item = Post::selectFirst(['uri-id'], ['id' => $item_id, 'uid' => local_user()]);
if (!DBA::isResult($item)) {
return;
}
foreach ($tags as $new_tag) {
if (preg_match_all('/([#@!])\[url\=([^\[\]]*)\]([^\[\]]*)\[\/url\]/ism', $new_tag, $results, PREG_SET_ORDER)) {
foreach ($results as $tag) {
Tag::removeByHash($item['uri-id'], $tag[1], $tag[3], $tag[2]);
}
}
}
}
function tagrm_content(App $a)
{
$o = '';
$photo_return = $_SESSION['photo_return'] ?? '';
if (!local_user()) {
DI::baseUrl()->redirect($photo_return);
// NOTREACHED
}
if (DI::args()->getArgc()== 3) {
update_tags(DI::args()->getArgv()[1], [trim(hex2bin(DI::args()->getArgv()[2]))]);
DI::baseUrl()->redirect($photo_return);
}
$item_id = ((DI::args()->getArgc()> 1) ? intval(DI::args()->getArgv()[1]) : 0);
if (!$item_id) {
DI::baseUrl()->redirect($photo_return);
// NOTREACHED
}
$item = Post::selectFirst(['uri-id'], ['id' => $item_id, 'uid' => local_user()]);
if (!DBA::isResult($item)) {
DI::baseUrl()->redirect($photo_return);
}
$tag_text = Tag::getCSVByURIId($item['uri-id']);
$arr = explode(',', $tag_text);
if (empty($arr)) {
DI::baseUrl()->redirect($photo_return);
}
$o .= '<h3>' . DI::l10n()->t('Remove Item Tag') . '</h3>';
$o .= '<p id="tag-remove-desc">' . DI::l10n()->t('Select a tag to remove: ') . '</p>';
$o .= '<form id="tagrm" action="tagrm" method="post" >';
$o .= '<input type="hidden" name="item" value="' . $item_id . '" />';
$o .= '<ul>';
foreach ($arr as $x) {
$o .= '<li><input type="checkbox" name="tag[]" value="' . bin2hex($x) . '" >' . BBCode::convert($x) . '</input></li>';
}
$o .= '</ul>';
$o .= '<input id="tagrm-submit" type="submit" name="submit" value="' . DI::l10n()->t('Remove') .'" />';
$o .= '<input id="tagrm-cancel" type="submit" name="submit" value="' . DI::l10n()->t('Cancel') .'" />';
$o .= '</form>';
return $o;
}

View file

@ -1,71 +0,0 @@
<?php
/**
* @copyright Copyright (C) 2010-2022, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* View for user import
*/
use Friendica\App;
use Friendica\Core\Logger;
use Friendica\Core\UserImport;
use Friendica\Core\Renderer;
use Friendica\Database\DBA;
use Friendica\DI;
function uimport_post(App $a)
{
if ((DI::config()->get('config', 'register_policy') != \Friendica\Module\Register::OPEN) && !$a->isSiteAdmin()) {
notice(DI::l10n()->t('Permission denied.'));
return;
}
if (!empty($_FILES['accountfile'])) {
UserImport::importAccount($_FILES['accountfile']);
return;
}
}
function uimport_content(App $a)
{
if ((DI::config()->get('config', 'register_policy') != \Friendica\Module\Register::OPEN) && !$a->isSiteAdmin()) {
notice(DI::l10n()->t('User imports on closed servers can only be done by an administrator.'));
return;
}
$max_dailies = intval(DI::config()->get('system', 'max_daily_registrations'));
if ($max_dailies) {
$total = DBA::count('user', ["`register_date` > UTC_TIMESTAMP - INTERVAL 1 DAY"]);
if ($total >= $max_dailies) {
Logger::notice('max daily registrations exceeded.');
notice(DI::l10n()->t('This site has exceeded the number of allowed daily account registrations. Please try again tomorrow.'));
return;
}
}
$tpl = Renderer::getMarkupTemplate("uimport.tpl");
return Renderer::replaceMacros($tpl, [
'$regbutt' => DI::l10n()->t('Import'),
'$import' => [
'title' => DI::l10n()->t("Move account"),
'intro' => DI::l10n()->t("You can import an account from another Friendica server."),
'instruct' => DI::l10n()->t("You need to export your account from the old server and upload it here. We will recreate your old account here with all your contacts. We will try also to inform your friends that you moved here."),
'warn' => DI::l10n()->t("This feature is experimental. We can't import contacts from the OStatus network \x28GNU Social/Statusnet\x29 or from Diaspora"),
'field' => ['accountfile', DI::l10n()->t('Account file'), '<input id="id_accountfile" name="accountfile" type="file">', DI::l10n()->t('To export your account, go to "Settings->Export your personal data" and select "Export account"')],
],
]);
}

View file

@ -1,151 +0,0 @@
<?php
/**
* @copyright Copyright (C) 2010-2022, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
use Friendica\App;
use Friendica\Content\Widget;
use Friendica\Core\Protocol;
use Friendica\Core\Renderer;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\Contact;
use Friendica\Model\User;
use Friendica\Util\Strings;
function unfollow_post(App $a)
{
if (!local_user()) {
notice(DI::l10n()->t('Permission denied.'));
DI::baseUrl()->redirect('login');
// NOTREACHED
}
$url = trim($_REQUEST['url'] ?? '');
unfollow_process($url);
}
function unfollow_content(App $a)
{
$base_return_path = 'contact';
if (!local_user()) {
notice(DI::l10n()->t('Permission denied.'));
DI::baseUrl()->redirect('login');
// NOTREACHED
}
$uid = local_user();
$url = trim($_REQUEST['url']);
$condition = ["`uid` = ? AND (`rel` = ? OR `rel` = ?) AND (`nurl` = ? OR `alias` = ? OR `alias` = ?)",
local_user(), Contact::SHARING, Contact::FRIEND, Strings::normaliseLink($url),
Strings::normaliseLink($url), $url];
$contact = DBA::selectFirst('contact', ['url', 'id', 'uid', 'network', 'addr', 'name'], $condition);
if (!DBA::isResult($contact)) {
notice(DI::l10n()->t("You aren't following this contact."));
DI::baseUrl()->redirect($base_return_path);
// NOTREACHED
}
if (!Protocol::supportsFollow($contact['network'])) {
notice(DI::l10n()->t('Unfollowing is currently not supported by your network.'));
DI::baseUrl()->redirect($base_return_path . '/' . $contact['id']);
// NOTREACHED
}
$request = DI::baseUrl() . '/unfollow';
$tpl = Renderer::getMarkupTemplate('auto_request.tpl');
$self = DBA::selectFirst('contact', ['url'], ['uid' => $uid, 'self' => true]);
if (!DBA::isResult($self)) {
notice(DI::l10n()->t('Permission denied.'));
DI::baseUrl()->redirect($base_return_path);
// NOTREACHED
}
if (!empty($_REQUEST['auto'])) {
unfollow_process($contact['url']);
}
$o = Renderer::replaceMacros($tpl, [
'$header' => DI::l10n()->t('Disconnect/Unfollow'),
'$page_desc' => '',
'$your_address' => DI::l10n()->t('Your Identity Address:'),
'$invite_desc' => '',
'$submit' => DI::l10n()->t('Submit Request'),
'$cancel' => DI::l10n()->t('Cancel'),
'$url' => $contact['url'],
'$zrl' => Contact::magicLinkByContact($contact),
'$url_label' => DI::l10n()->t('Profile URL'),
'$myaddr' => $self['url'],
'$request' => $request,
'$keywords' => '',
'$keywords_label'=> ''
]);
DI::page()['aside'] = Widget\VCard::getHTML(Contact::getByURL($contact['url'], false));
$o .= Renderer::replaceMacros(Renderer::getMarkupTemplate('section_title.tpl'), ['$title' => DI::l10n()->t('Status Messages and Posts')]);
// Show last public posts
$o .= Contact::getPostsFromUrl($contact['url']);
return $o;
}
function unfollow_process(string $url)
{
$base_return_path = 'contact';
$uid = local_user();
$owner = User::getOwnerDataById($uid);
if (!$owner) {
throw new \Friendica\Network\HTTPException\NotFoundException();
}
$condition = ["`uid` = ? AND (`rel` = ? OR `rel` = ?) AND (`nurl` = ? OR `alias` = ? OR `alias` = ?)",
$uid, Contact::SHARING, Contact::FRIEND, Strings::normaliseLink($url),
Strings::normaliseLink($url), $url];
$contact = DBA::selectFirst('contact', [], $condition);
if (!DBA::isResult($contact)) {
notice(DI::l10n()->t("You aren't following this contact."));
DI::baseUrl()->redirect($base_return_path);
// NOTREACHED
}
$return_path = $base_return_path . '/' . $contact['id'];
try {
Contact::unfollow($contact);
$notice_message = DI::l10n()->t('Contact was successfully unfollowed');
} catch (Exception $e) {
DI::logger()->error($e->getMessage(), ['contact' => $contact]);
$notice_message = DI::l10n()->t('Unable to unfollow this contact, please contact your administrator');
}
notice($notice_message);
DI::baseUrl()->redirect($return_path);
}

View file

@ -30,7 +30,7 @@ use Friendica\Model\Contact;
function update_contact_content(App $a)
{
if (!empty(DI::args()->get(1)) && (!empty($_GET['force']) || !DI::pConfig()->get(local_user(), 'system', 'no_auto_update'))) {
if (!empty(DI::args()->get(1)) && (!empty($_GET['force']) || !DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'system', 'no_auto_update'))) {
$contact = Contact::getById(DI::args()->get(1), ['id', 'deleted']);
if (DBA::isResult($contact) && empty($contact['deleted'])) {
DI::page()['aside'] = '';

View file

@ -24,11 +24,11 @@ use Friendica\App;
use Friendica\Core\System;
use Friendica\DI;
require_once("mod/notes.php");
require_once 'mod/notes.php';
function update_notes_content(App $a) {
$profile_uid = intval($_GET["p"]);
function update_notes_content(App $a)
{
$profile_uid = intval($_GET['p']);
/**
*

View file

@ -1,137 +0,0 @@
<?php
/**
* @copyright Copyright (C) 2010-2022, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
use Friendica\App;
use Friendica\Core\Session;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\Attach;
use Friendica\Model\User;
use Friendica\Util\Strings;
function wall_attach_post(App $a) {
$r_json = (!empty($_GET['response']) && $_GET['response']=='json');
if (DI::args()->getArgc() > 1) {
$nick = DI::args()->getArgv()[1];
$owner = User::getOwnerDataByNick($nick);
if (!DBA::isResult($owner)) {
if ($r_json) {
System::jsonExit(['error' => DI::l10n()->t('Invalid request.')]);
}
return;
}
} else {
if ($r_json) {
System::jsonExit(['error' => DI::l10n()->t('Invalid request.')]);
}
return;
}
$can_post = false;
$page_owner_uid = $owner['uid'];
$page_owner_cid = $owner['id'];
$community_page = $owner['page-flags'] == User::PAGE_FLAGS_COMMUNITY;
if (local_user() && (local_user() == $page_owner_uid)) {
$can_post = true;
} elseif ($community_page && !empty(Session::getRemoteContactID($page_owner_uid))) {
$contact_id = Session::getRemoteContactID($page_owner_uid);
$can_post = DBA::exists('contact', ['blocked' => false, 'pending' => false, 'id' => $contact_id, 'uid' => $page_owner_uid]);
}
if (!$can_post) {
if ($r_json) {
System::jsonExit(['error' => DI::l10n()->t('Permission denied.')]);
}
notice(DI::l10n()->t('Permission denied.') . EOL );
System::exit();
}
if (empty($_FILES['userfile'])) {
if ($r_json) {
System::jsonExit(['error' => DI::l10n()->t('Invalid request.')]);
}
System::exit();
}
$src = $_FILES['userfile']['tmp_name'];
$filename = basename($_FILES['userfile']['name']);
$filesize = intval($_FILES['userfile']['size']);
$maxfilesize = DI::config()->get('system','maxfilesize');
/* Found html code written in text field of form,
* when trying to upload a file with filesize
* greater than upload_max_filesize. Cause is unknown.
* Then Filesize gets <= 0.
*/
if ($filesize <= 0) {
$msg = DI::l10n()->t('Sorry, maybe your upload is bigger than the PHP configuration allows') . EOL .(DI::l10n()->t('Or - did you try to upload an empty file?'));
@unlink($src);
if ($r_json) {
System::jsonExit(['error' => $msg]);
} else {
notice($msg);
}
System::exit();
}
if ($maxfilesize && $filesize > $maxfilesize) {
$msg = DI::l10n()->t('File exceeds size limit of %s', Strings::formatBytes($maxfilesize));
@unlink($src);
if ($r_json) {
System::jsonExit(['error' => $msg]);
} else {
echo $msg . EOL;
}
System::exit();
}
$newid = Attach::storeFile($src, $page_owner_uid, $filename, '<' . $page_owner_cid . '>');
@unlink($src);
if ($newid === false) {
$msg = DI::l10n()->t('File upload failed.');
if ($r_json) {
System::jsonExit(['error' => $msg]);
} else {
echo $msg . EOL;
}
System::exit();
}
if ($r_json) {
System::jsonExit(['ok' => true, 'id' => $newid]);
}
$lf = "\n";
echo $lf . $lf . '[attachment]' . $newid . '[/attachment]' . $lf;
System::exit();
// NOTREACHED
}

View file

@ -1,286 +0,0 @@
<?php
/**
* @copyright Copyright (C) 2010-2022, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
* Module for uploading a picture to the profile wall
*
* By default the picture will be stored in the photo album with the name Wall Photos.
* You can specify a different album by adding an optional query string "album="
* to the url
*
*/
use Friendica\App;
use Friendica\Core\Logger;
use Friendica\Core\Session;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\Photo;
use Friendica\Model\User;
use Friendica\Module\BaseApi;
use Friendica\Object\Image;
use Friendica\Util\Images;
use Friendica\Util\Strings;
function wall_upload_post(App $a, $desktopmode = true)
{
Logger::info("wall upload: starting new upload");
$r_json = (!empty($_GET['response']) && $_GET['response'] == 'json');
$album = trim($_GET['album'] ?? '');
if (DI::args()->getArgc() > 1) {
if (empty($_FILES['media'])) {
$nick = DI::args()->getArgv()[1];
$user = DBA::selectFirst('owner-view', ['id', 'uid', 'nickname', 'page-flags'], ['nickname' => $nick, 'blocked' => false]);
if (!DBA::isResult($user)) {
if ($r_json) {
System::jsonExit(['error' => DI::l10n()->t('Invalid request.')]);
}
return;
}
} else {
$user = DBA::selectFirst('owner-view', ['id', 'uid', 'nickname', 'page-flags'], ['uid' => BaseApi::getCurrentUserID(), 'blocked' => false]);
}
} else {
if ($r_json) {
System::jsonExit(['error' => DI::l10n()->t('Invalid request.')]);
}
return;
}
/*
* Setup permissions structures
*/
$can_post = false;
$visitor = 0;
$page_owner_uid = $user['uid'];
$default_cid = $user['id'];
$page_owner_nick = $user['nickname'];
$community_page = (($user['page-flags'] == User::PAGE_FLAGS_COMMUNITY) ? true : false);
if ((local_user()) && (local_user() == $page_owner_uid)) {
$can_post = true;
} elseif ($community_page && !empty(Session::getRemoteContactID($page_owner_uid))) {
$contact_id = Session::getRemoteContactID($page_owner_uid);
$can_post = DBA::exists('contact', ['blocked' => false, 'pending' => false, 'id' => $contact_id, 'uid' => $page_owner_uid]);
$visitor = $contact_id;
}
if (!$can_post) {
if ($r_json) {
System::jsonExit(['error' => DI::l10n()->t('Permission denied.')]);
}
notice(DI::l10n()->t('Permission denied.'));
System::exit();
}
if (empty($_FILES['userfile']) && empty($_FILES['media'])) {
if ($r_json) {
System::jsonExit(['error' => DI::l10n()->t('Invalid request.')]);
}
System::exit();
}
$src = '';
$filename = '';
$filesize = 0;
$filetype = '';
if (!empty($_FILES['userfile'])) {
$src = $_FILES['userfile']['tmp_name'];
$filename = basename($_FILES['userfile']['name']);
$filesize = intval($_FILES['userfile']['size']);
$filetype = $_FILES['userfile']['type'];
} elseif (!empty($_FILES['media'])) {
if (!empty($_FILES['media']['tmp_name'])) {
if (is_array($_FILES['media']['tmp_name'])) {
$src = $_FILES['media']['tmp_name'][0];
} else {
$src = $_FILES['media']['tmp_name'];
}
}
if (!empty($_FILES['media']['name'])) {
if (is_array($_FILES['media']['name'])) {
$filename = basename($_FILES['media']['name'][0]);
} else {
$filename = basename($_FILES['media']['name']);
}
}
if (!empty($_FILES['media']['size'])) {
if (is_array($_FILES['media']['size'])) {
$filesize = intval($_FILES['media']['size'][0]);
} else {
$filesize = intval($_FILES['media']['size']);
}
}
if (!empty($_FILES['media']['type'])) {
if (is_array($_FILES['media']['type'])) {
$filetype = $_FILES['media']['type'][0];
} else {
$filetype = $_FILES['media']['type'];
}
}
}
if ($src == "") {
if ($r_json) {
System::jsonExit(['error' => DI::l10n()->t('Invalid request.')]);
}
notice(DI::l10n()->t('Invalid request.'));
System::exit();
}
$filetype = Images::getMimeTypeBySource($src, $filename, $filetype);
Logger::info("File upload src: " . $src . " - filename: " . $filename .
" - size: " . $filesize . " - type: " . $filetype);
$imagedata = @file_get_contents($src);
$image = new Image($imagedata, $filetype);
if (!$image->isValid()) {
$msg = DI::l10n()->t('Unable to process image.');
@unlink($src);
if ($r_json) {
System::jsonExit(['error' => $msg]);
} else {
echo $msg. EOL;
}
System::exit();
}
$image->orient($src);
@unlink($src);
$max_length = DI::config()->get('system', 'max_image_length');
if ($max_length > 0) {
$image->scaleDown($max_length);
$filesize = strlen($image->asString());
Logger::info("File upload: Scaling picture to new size " . $max_length);
}
$width = $image->getWidth();
$height = $image->getHeight();
$maximagesize = DI::config()->get('system', 'maximagesize');
if (!empty($maximagesize) && ($filesize > $maximagesize)) {
// Scale down to multiples of 640 until the maximum size isn't exceeded anymore
foreach ([5120, 2560, 1280, 640] as $pixels) {
if (($filesize > $maximagesize) && (max($width, $height) > $pixels)) {
Logger::info('Resize', ['size' => $filesize, 'width' => $width, 'height' => $height, 'max' => $maximagesize, 'pixels' => $pixels]);
$image->scaleDown($pixels);
$filesize = strlen($image->asString());
$width = $image->getWidth();
$height = $image->getHeight();
}
}
if ($filesize > $maximagesize) {
Logger::notice('Image size is too big', ['size' => $filesize, 'max' => $maximagesize]);
$msg = DI::l10n()->t('Image exceeds size limit of %s', Strings::formatBytes($maximagesize));
@unlink($src);
if ($r_json) {
System::jsonExit(['error' => $msg]);
} else {
echo $msg. EOL;
}
System::exit();
}
}
$resource_id = Photo::newResource();
$smallest = 0;
// If we don't have an album name use the Wall Photos album
if (!strlen($album)) {
$album = DI::l10n()->t('Wall Photos');
}
$defperm = '<' . $default_cid . '>';
$r = Photo::store($image, $page_owner_uid, $visitor, $resource_id, $filename, $album, 0, Photo::DEFAULT, $defperm);
if (!$r) {
$msg = DI::l10n()->t('Image upload failed.');
if ($r_json) {
System::jsonExit(['error' => $msg]);
} else {
echo $msg. EOL;
}
System::exit();
}
if ($width > 640 || $height > 640) {
$image->scaleDown(640);
$r = Photo::store($image, $page_owner_uid, $visitor, $resource_id, $filename, $album, 1, Photo::DEFAULT, $defperm);
if ($r) {
$smallest = 1;
}
}
if ($width > 320 || $height > 320) {
$image->scaleDown(320);
$r = Photo::store($image, $page_owner_uid, $visitor, $resource_id, $filename, $album, 2, Photo::DEFAULT, $defperm);
if ($r && ($smallest == 0)) {
$smallest = 2;
}
}
if (!$desktopmode) {
$photo = Photo::selectFirst(['id', 'datasize', 'width', 'height', 'type'], ['resource-id' => $resource_id], ['order' => ['width']]);
if (!$photo) {
if ($r_json) {
System::jsonExit(['error' => '']);
}
return false;
}
$picture = [];
$picture["id"] = $photo["id"];
$picture["size"] = $photo["datasize"];
$picture["width"] = $photo["width"];
$picture["height"] = $photo["height"];
$picture["type"] = $photo["type"];
$picture["albumpage"] = DI::baseUrl() . '/photos/' . $page_owner_nick . '/image/' . $resource_id;
$picture["picture"] = DI::baseUrl() . "/photo/{$resource_id}-0." . $image->getExt();
$picture["preview"] = DI::baseUrl() . "/photo/{$resource_id}-{$smallest}." . $image->getExt();
if ($r_json) {
System::jsonExit(['picture' => $picture]);
}
Logger::info("upload done");
return $picture;
}
Logger::info("upload done");
if ($r_json) {
System::jsonExit(['ok' => true]);
}
echo "\n\n" . '[url=' . DI::baseUrl() . '/photos/' . $page_owner_nick . '/image/' . $resource_id . '][img]' . DI::baseUrl() . "/photo/{$resource_id}-{$smallest}." . $image->getExt() . "[/img][/url]\n\n";
System::exit();
// NOTREACHED
}

View file

@ -1,146 +0,0 @@
<?php
/**
* @copyright Copyright (C) 2010-2022, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
use Friendica\App;
use Friendica\Core\Logger;
use Friendica\Core\Renderer;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\Mail;
use Friendica\Model\Profile;
use Friendica\Model\User;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\Strings;
function wallmessage_post(App $a) {
$replyto = Profile::getMyURL();
if (!$replyto) {
notice(DI::l10n()->t('Permission denied.'));
return;
}
$subject = trim($_REQUEST['subject'] ?? '');
$body = Strings::escapeHtml(trim($_REQUEST['body'] ?? ''));
$recipient = ((DI::args()->getArgc() > 1) ? DI::args()->getArgv()[1] : '');
if ((! $recipient) || (! $body)) {
return;
}
$user = User::getByNickname($recipient);
if (empty($r)) {
Logger::notice('wallmessage: no recipient');
return;
}
if (!$user['unkmail']) {
notice(DI::l10n()->t('Permission denied.'));
return;
}
$total = DBA::count('mail', ["`uid` = ? AND `created` > ? AND `unknown`", $user['uid'], DateTimeFormat::utc('now - 1 day')]);
if ($total > $user['cntunkmail']) {
notice(DI::l10n()->t('Number of daily wall messages for %s exceeded. Message failed.', $user['username']));
return;
}
$ret = Mail::sendWall($user, $body, $subject, $replyto);
switch ($ret) {
case -1:
notice(DI::l10n()->t('No recipient selected.'));
break;
case -2:
notice(DI::l10n()->t('Unable to check your home location.'));
break;
case -3:
notice(DI::l10n()->t('Message could not be sent.'));
break;
case -4:
notice(DI::l10n()->t('Message collection failure.'));
break;
}
DI::baseUrl()->redirect('profile/'.$user['nickname']);
}
function wallmessage_content(App $a) {
if (!Profile::getMyURL()) {
notice(DI::l10n()->t('Permission denied.'));
return;
}
$recipient = ((DI::args()->getArgc() > 1) ? DI::args()->getArgv()[1] : '');
if (!$recipient) {
notice(DI::l10n()->t('No recipient.'));
return;
}
$user = User::getByNickname($recipient);
if (empty($user)) {
notice(DI::l10n()->t('No recipient.'));
Logger::notice('wallmessage: no recipient');
return;
}
if (!$user['unkmail']) {
notice(DI::l10n()->t('Permission denied.'));
return;
}
$total = DBA::count('mail', ["`uid` = ? AND `created` > ? AND `unknown`", $user['uid'], DateTimeFormat::utc('now - 1 day')]);
if ($total > $user['cntunkmail']) {
notice(DI::l10n()->t('Number of daily wall messages for %s exceeded. Message failed.', $user['username']));
return;
}
$tpl = Renderer::getMarkupTemplate('wallmsg-header.tpl');
DI::page()['htmlhead'] .= Renderer::replaceMacros($tpl, [
'$baseurl' => DI::baseUrl()->get(true),
'$nickname' => $user['nickname'],
'$linkurl' => DI::l10n()->t('Please enter a link URL:')
]);
$tpl = Renderer::getMarkupTemplate('wallmessage.tpl');
$o = Renderer::replaceMacros($tpl, [
'$header' => DI::l10n()->t('Send Private Message'),
'$subheader' => DI::l10n()->t('If you wish for %s to respond, please check that the privacy settings on your site allow private mail from unknown senders.', $user['username']),
'$to' => DI::l10n()->t('To:'),
'$subject' => DI::l10n()->t('Subject:'),
'$recipname' => $user['username'],
'$nickname' => $user['nickname'],
'$subjtxt' => $_REQUEST['subject'] ?? '',
'$text' => $_REQUEST['body'] ?? '',
'$readonly' => '',
'$yourmessage'=> DI::l10n()->t('Your message:'),
'$parent' => '',
'$upload' => DI::l10n()->t('Upload photo'),
'$insert' => DI::l10n()->t('Insert web link'),
'$wait' => DI::l10n()->t('Please wait')
]);
return $o;
}

View file

@ -29,7 +29,7 @@ return [
// ****************************************************************
'config' => [
'hostname' => 'friendica.local',
'hostname' => '192.168.56.10',
'admin_email' => 'admin@friendica.local',
'sitename' => 'Friendica Social Network',
'register_policy' => \Friendica\Module\Register::OPEN,

View file

@ -7,7 +7,6 @@ bin/daemon.php
bin/testargs.php
bin/wait-for-connection
bin/worker.php
config/addon-sample.config.php
config/local-sample.config.php
doc/
images/
@ -19,7 +18,6 @@ static/
vendor/
view/
.htaccess-dist
boot.php
CHANGELOG
CREDITS.txt
database.sql

View file

@ -0,0 +1,128 @@
##
# Friendica Nginx configuration template to be autoconfigured with certbot
# based on sample-nginx.config by Olaf Conradi
#
# On Debian based distributions you can add this file to
# /etc/nginx/sites-available
#
# Then customize it to your needs. At least replace the server_name in line 41.
#
# Enable the configuration by
# symlink it to /etc/nginx/sites-enabled
#
# and run
# certbot --nginx -d friendica.example.net
#
# Then reload Nginx using
# systemctl nginx reload
#
##
##
# You should look at the following URL's in order to grasp a solid understanding
# of Nginx configuration files in order to fully unleash the power of Nginx.
#
# http://wiki.nginx.org/Pitfalls
# http://wiki.nginx.org/QuickStart
# http://wiki.nginx.org/Configuration
##
##
# This configuration assumes your domain is example.net
# You have a separate subdomain friendica.example.net
# You want all Friendica traffic to be https using letsencrypt with cerbot
# You have an SSL certificate and key for your subdomain
# You have PHP FastCGI Process Manager (php7.4-fpm) running on localhost
# You have Friendica installed in /var/www/friendica
##
server {
listen 80;
server_name friendica.example.net;
# Point here to the path where your friendica files are located
root /var/www/friendica;
# Logging
access_log /var/log/nginx/friendica_access.log;
# uncomment the following line if you would like to log errors in a separate file for friendica
#error_log /var/log/nginx/friendica_error.log;
index index.php;
charset utf-8;
# Uncomment the following line to include a standard configuration file Note
# that the most specific rule wins and your standard configuration will
# therefore *add* to this file, but not override it.
#include standard.conf
# allow uploads up to 20MB in size
client_max_body_size 20m;
client_body_buffer_size 128k;
# rewrite to front controller as default rule
location / {
try_files $uri /index.php?pagename=$uri&$args;
}
# make sure webfinger and other well known services aren't blocked
# by denying dot files and rewrite request to the front controller
location ^~ /.well-known/ {
allow all;
rewrite ^ /index.php?pagename=$uri;
}
include mime.types;
# statically serve these file types when possible otherwise fall back to
# front controller allow browser to cache them added .htm for advanced source
# code editor library
#location ~* \.(jpg|jpeg|gif|png|ico|css|js|htm|html|ttf|woff|svg)$ {
# expires 30d;
# try_files $uri /index.php?pagename=$uri&$args;
#}
# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
# or a unix socket
location ~* \.php$ {
# Zero-day exploit defense.
# http://forum.nginx.org/read.php?2,88845,page=3
# Won't work properly (404 error) if the file is not stored on this
# server, which is entirely possible with php-fpm/php-fcgi.
# Comment the 'try_files' line out if you set up php-fpm/php-fcgi on
# another machine. And then cross your fingers that you won't get hacked.
try_files $uri =404;
# NOTE: You should have "cgi.fix_pathinfo = 0;" in php.ini
fastcgi_split_path_info ^(.+\.php)(/.+)$;
# With php5-cgi alone:
# fastcgi_pass 127.0.0.1:9000;
# With php7.4-fpm:
fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
include fastcgi_params;
fastcgi_index index.php;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_buffers 16 16k;
fastcgi_buffer_size 32k;
}
# block these file types
location ~* \.(tpl|md|tgz|log|out)$ {
deny all;
}
# deny access to all dot files
location ~ /\. {
deny all;
}
# deny access to the CLI scripts
location ^~ /bin {
deny all;
}
}

View file

@ -35,7 +35,7 @@ server {
index index.php;
root /var/www/friendica;
rewrite ^ https://friendica.example.net$request_uri? permanent;
rewrite ^ https://$server_name$request_uri? permanent;
}
##
@ -51,8 +51,6 @@ server {
listen 443 ssl;
server_name friendica.example.net;
ssl on;
#Traditional SSL
ssl_certificate /etc/nginx/ssl/friendica.example.net.chain.pem;
ssl_certificate_key /etc/nginx/ssl/example.net.key;
@ -122,7 +120,7 @@ server {
# fastcgi_pass 127.0.0.1:9000;
# With php7.0-fpm:
fastcgi_pass unix:/var/run/php/php7.0-fpm.sock;
fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
include fastcgi_params;
fastcgi_index index.php;

View file

@ -1,6 +1,6 @@
Contact: mailto:info@friendi.ca
Expires: Sun, 31 Dec 2022 23:59 +0000
Expires: Sun, 30 Jun 2023 23:59 +0000
Preferred-Languages: en

View file

@ -26,6 +26,8 @@ use Friendica\App\Arguments;
use Friendica\App\BaseURL;
use Friendica\Capabilities\ICanCreateResponses;
use Friendica\Core\Config\Factory\Config;
use Friendica\Core\Session\Capability\IHandleUserSessions;
use Friendica\Model\User;
use Friendica\Module\Maintenance;
use Friendica\Security\Authentication;
use Friendica\Core\Config\ValueObject\Cache;
@ -58,12 +60,15 @@ use Psr\Log\LoggerInterface;
*/
class App
{
const PLATFORM = 'Friendica';
const CODENAME = 'Giant Rhubarb';
const VERSION = '2022.12-rc';
// Allow themes to control internal parameters
// by changing App values in theme.php
private $theme_info = [
'videowidth' => 425,
'videoheight' => 350,
'events_in_profile' => true
];
private $user_id = 0;
@ -123,6 +128,11 @@ class App
*/
private $pConfig;
/**
* @var IHandleUserSessions
*/
private $session;
/**
* Set the user ID
*
@ -147,21 +157,23 @@ class App
public function isLoggedIn(): bool
{
return local_user() && $this->user_id && ($this->user_id == local_user());
return $this->session->getLocalUserId() && $this->user_id && ($this->user_id == $this->session->getLocalUserId());
}
/**
* Check if current user has admin role.
*
* @return bool true if user is an admin
* @throws Exception
*/
public function isSiteAdmin(): bool
{
$admin_email = $this->config->get('config', 'admin_email');
$adminlist = explode(',', str_replace(' ', '', $admin_email));
return local_user() && $admin_email && $this->database->exists('user', ['uid' => $this->getLoggedInUserId(), 'email' => $adminlist]);
return
$this->session->getLocalUserId()
&& $this->database->exists('user', [
'uid' => $this->getLoggedInUserId(),
'email' => User::getAdminEmailList()
]);
}
/**
@ -249,8 +261,8 @@ class App
/**
* Set workerqueue information
*
* @param array $queue
* @return void
* @param array $queue
* @return void
*/
public function setQueue(array $queue)
{
@ -324,8 +336,9 @@ class App
* @param L10n $l10n The translator instance
* @param App\Arguments $args The Friendica Arguments of the call
* @param IManagePersonalConfigValues $pConfig Personal configuration
* @param IHandleUserSessions $session The (User)Session handler
*/
public function __construct(Database $database, IManageConfigValues $config, App\Mode $mode, BaseURL $baseURL, LoggerInterface $logger, Profiler $profiler, L10n $l10n, Arguments $args, IManagePersonalConfigValues $pConfig)
public function __construct(Database $database, IManageConfigValues $config, App\Mode $mode, BaseURL $baseURL, LoggerInterface $logger, Profiler $profiler, L10n $l10n, Arguments $args, IManagePersonalConfigValues $pConfig, IHandleUserSessions $session)
{
$this->database = $database;
$this->config = $config;
@ -336,6 +349,7 @@ class App
$this->l10n = $l10n;
$this->args = $args;
$this->pConfig = $pConfig;
$this->session = $session;
$this->load();
}
@ -347,6 +361,11 @@ class App
{
set_time_limit(0);
// Normally this constant is defined - but not if "pcntl" isn't installed
if (!defined('SIGTERM')) {
define('SIGTERM', 15);
}
// Ensure that all "strtotime" operations do run timezone independent
date_default_timezone_set('UTC');
@ -406,7 +425,7 @@ class App
}
// Specific mobile theme override
if (($this->mode->isMobile() || $this->mode->isTablet()) && Core\Session::get('show-mobile', true)) {
if (($this->mode->isMobile() || $this->mode->isTablet()) && $this->session->get('show-mobile', true)) {
$user_mobile_theme = $this->getCurrentMobileTheme();
// --- means same mobile theme as desktop
@ -478,16 +497,16 @@ class App
$page_theme = null;
// Find the theme that belongs to the user whose stuff we are looking at
if (!empty($this->profile_owner) && ($this->profile_owner != local_user())) {
if (!empty($this->profile_owner) && ($this->profile_owner != $this->session->getLocalUserId())) {
// 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
$user = $this->database->selectFirst('user', ['theme'], ['uid' => $this->profile_owner]);
if ($this->database->isResult($user) && !local_user()) {
if ($this->database->isResult($user) && !$this->session->getLocalUserId()) {
$page_theme = $user['theme'];
}
}
$theme_name = $page_theme ?: Core\Session::get('theme', $system_theme);
$theme_name = $page_theme ?: $this->session->get('theme', $system_theme);
$theme_name = Strings::sanitizeFilePathItem($theme_name);
if ($theme_name
@ -511,15 +530,15 @@ class App
$page_mobile_theme = null;
// Find the theme that belongs to the user whose stuff we are looking at
if (!empty($this->profile_owner) && ($this->profile_owner != local_user())) {
if (!empty($this->profile_owner) && ($this->profile_owner != $this->session->getLocalUserId())) {
// 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 (!local_user()) {
if (!$this->session->getLocalUserId()) {
$page_mobile_theme = $this->pConfig->get($this->profile_owner, 'system', 'mobile-theme');
}
}
$mobile_theme_name = $page_mobile_theme ?: Core\Session::get('mobile-theme', $system_mobile_theme);
$mobile_theme_name = $page_mobile_theme ?: $this->session->get('mobile-theme', $system_mobile_theme);
$mobile_theme_name = Strings::sanitizeFilePathItem($mobile_theme_name);
if ($mobile_theme_name == '---'
@ -611,14 +630,14 @@ class App
}
// ZRL
if (!empty($_GET['zrl']) && $this->mode->isNormal() && !$this->mode->isBackend() && !local_user()) {
// Only continue when the given profile link seems valid
if (!empty($_GET['zrl']) && $this->mode->isNormal() && !$this->mode->isBackend() && !$this->session->getLocalUserId()) {
// Only continue when the given profile link seems valid.
// Valid profile links contain a path with "/profile/" and no query parameters
if ((parse_url($_GET['zrl'], PHP_URL_QUERY) == '') &&
strstr(parse_url($_GET['zrl'], PHP_URL_PATH), '/profile/')) {
if (Core\Session::get('visitor_home') != $_GET['zrl']) {
Core\Session::set('my_url', $_GET['zrl']);
Core\Session::set('authenticated', 0);
strpos(parse_url($_GET['zrl'], PHP_URL_PATH) ?? '', '/profile/') !== false) {
if ($this->session->get('visitor_home') != $_GET['zrl']) {
$this->session->set('my_url', $_GET['zrl']);
$this->session->set('authenticated', 0);
$remote_contact = Contact::getByURL($_GET['zrl'], false, ['subscribe']);
if (!empty($remote_contact['subscribe'])) {
@ -719,7 +738,7 @@ class App
$response = $module->run($input);
$this->profiler->set(microtime(true) - $timestamp, 'content');
if ($response->getHeaderLine(ICanCreateResponses::X_HEADER) === ICanCreateResponses::TYPE_HTML) {
$page->run($this, $this->baseURL, $this->args, $this->mode, $response, $this->l10n, $this->profiler, $this->config, $pconfig);
$page->run($this, $this->baseURL, $this->args, $this->mode, $response, $this->l10n, $this->profiler, $this->config, $pconfig, $this->session->getLocalUserId());
} else {
$page->exit($response);
}

View file

@ -85,6 +85,8 @@ class Arguments
/**
* @return string The module name based on the arguments
* @deprecated 2022.12 - With the new (sub-)routes, it's not trustworthy anymore, use the ModuleClass instead
* @see Router::getModuleClass()
*/
public function getModuleName(): string
{

View file

@ -68,7 +68,6 @@ class Mode
'objects',
'outbox',
'poco',
'post',
'pubsub',
'pubsubhubbub',
'receive',

View file

@ -115,76 +115,40 @@ class Page implements ArrayAccess
}
}
// ArrayAccess interface
/**
* Whether a offset exists
*
* @link https://php.net/manual/en/arrayaccess.offsetexists.php
*
* @param mixed $offset <p>
* An offset to check for.
* </p>
*
* @return boolean true on success or false on failure.
* </p>
* <p>
* The return value will be casted to boolean if non-boolean was returned.
* @since 5.0.0
* @inheritDoc
*/
public function offsetExists($offset)
#[\ReturnTypeWillChange]
public function offsetExists($offset): bool
{
return isset($this->page[$offset]);
}
/**
* Offset to retrieve
*
* @link https://php.net/manual/en/arrayaccess.offsetget.php
*
* @param mixed $offset <p>
* The offset to retrieve.
* </p>
*
* @return mixed Can return all value types.
* @since 5.0.0
* @inheritDoc
*/
#[\ReturnTypeWillChange]
public function offsetGet($offset)
{
return $this->page[$offset] ?? null;
}
/**
* Offset to set
*
* @link https://php.net/manual/en/arrayaccess.offsetset.php
*
* @param mixed $offset <p>
* The offset to assign the value to.
* </p>
* @param mixed $value <p>
* The value to set.
* </p>
*
* @return void
* @since 5.0.0
* @inheritDoc
*/
public function offsetSet($offset, $value)
#[\ReturnTypeWillChange]
public function offsetSet($offset, $value): void
{
$this->page[$offset] = $value;
}
/**
* Offset to unset
*
* @link https://php.net/manual/en/arrayaccess.offsetunset.php
*
* @param mixed $offset <p>
* The offset to unset.
* </p>
*
* @return void
* @since 5.0.0
* @inheritDoc
*/
public function offsetUnset($offset)
#[\ReturnTypeWillChange]
public function offsetUnset($offset): void
{
if (isset($this->page[$offset])) {
unset($this->page[$offset]);
@ -202,7 +166,7 @@ class Page implements ArrayAccess
*/
public function registerStylesheet(string $path, string $media = 'screen')
{
$path = Network::appendQueryParam($path, ['v' => FRIENDICA_VERSION]);
$path = Network::appendQueryParam($path, ['v' => App::VERSION]);
if (mb_strpos($path, $this->basePath . DIRECTORY_SEPARATOR) === 0) {
$path = mb_substr($path, mb_strlen($this->basePath . DIRECTORY_SEPARATOR));
@ -221,17 +185,18 @@ class Page implements ArrayAccess
* - Infinite scroll data
* - head.tpl template
*
* @param App $app The Friendica App instance
* @param Arguments $args The Friendica App Arguments
* @param L10n $l10n The l10n language instance
* @param IManageConfigValues $config The Friendica configuration
* @param IManagePersonalConfigValues $pConfig The Friendica personal configuration (for user)
* @param App $app The Friendica App instance
* @param Arguments $args The Friendica App Arguments
* @param L10n $l10n The l10n language instance
* @param IManageConfigValues $config The Friendica configuration
* @param IManagePersonalConfigValues $pConfig The Friendica personal configuration (for user)
* @param int $localUID The local user id
*
* @throws HTTPException\InternalServerErrorException
*/
private function initHead(App $app, Arguments $args, L10n $l10n, IManageConfigValues $config, IManagePersonalConfigValues $pConfig)
private function initHead(App $app, Arguments $args, L10n $l10n, IManageConfigValues $config, IManagePersonalConfigValues $pConfig, int $localUID)
{
$interval = ((local_user()) ? $pConfig->get(local_user(), 'system', 'update_interval') : 40000);
$interval = ($localUID ? $pConfig->get($localUID, 'system', 'update_interval') : 40000);
// If the update is 'deactivated' set it to the highest integer number (~24 days)
if ($interval < 0) {
@ -276,8 +241,8 @@ class Page implements ArrayAccess
* being first
*/
$this->page['htmlhead'] = Renderer::replaceMacros($tpl, [
'$local_user' => local_user(),
'$generator' => 'Friendica' . ' ' . FRIENDICA_VERSION,
'$local_user' => $localUID,
'$generator' => 'Friendica' . ' ' . App::VERSION,
'$delitem' => $l10n->t('Delete this item?'),
'$blockAuthor' => $l10n->t('Block this author? They won\'t be able to follow you nor see your public posts, and you won\'t be able to see their posts and their notifications.'),
'$update_interval' => $interval,
@ -309,7 +274,7 @@ class Page implements ArrayAccess
}
return $pageURL;
}
/**
* Initializes Page->page['footer'].
*
@ -395,7 +360,7 @@ class Page implements ArrayAccess
*/
public function registerFooterScript($path)
{
$path = Network::appendQueryParam($path, ['v' => FRIENDICA_VERSION]);
$path = Network::appendQueryParam($path, ['v' => App::VERSION]);
$url = str_replace($this->basePath . DIRECTORY_SEPARATOR, '', $path);
@ -443,10 +408,11 @@ class Page implements ArrayAccess
* @param L10n $l10n The l10n language class
* @param IManageConfigValues $config The Configuration of this node
* @param IManagePersonalConfigValues $pconfig The personal/user configuration
* @param int $localUID The UID of the local user
*
* @throws HTTPException\InternalServerErrorException|HTTPException\ServiceUnavailableException
*/
public function run(App $app, BaseURL $baseURL, Arguments $args, Mode $mode, ResponseInterface $response, L10n $l10n, Profiler $profiler, IManageConfigValues $config, IManagePersonalConfigValues $pconfig)
public function run(App $app, BaseURL $baseURL, Arguments $args, Mode $mode, ResponseInterface $response, L10n $l10n, Profiler $profiler, IManageConfigValues $config, IManagePersonalConfigValues $pconfig, int $localUID)
{
$moduleName = $args->getModuleName();
@ -480,7 +446,7 @@ class Page implements ArrayAccess
* all the module functions have executed so that all
* theme choices made by the modules can take effect.
*/
$this->initHead($app, $args, $l10n, $config, $pconfig);
$this->initHead($app, $args, $l10n, $config, $pconfig, $localUID);
/* Build the page ending -- this is stuff that goes right before
* the closing </body> tag
@ -543,7 +509,7 @@ class Page implements ArrayAccess
$page = $this->page;
header("X-Friendica-Version: " . FRIENDICA_VERSION);
header("X-Friendica-Version: " . App::VERSION);
header("Content-type: text/html; charset=utf-8");
if ($config->get('system', 'hsts') && ($baseURL->getSSLPolicy() == BaseURL::SSL_POLICY_FULL)) {

View file

@ -34,11 +34,13 @@ use Friendica\Core\Config\Capability\IManageConfigValues;
use Friendica\Core\Hook;
use Friendica\Core\L10n;
use Friendica\Core\Lock\Capability\ICanLock;
use Friendica\Core\Session\Capability\IHandleUserSessions;
use Friendica\LegacyModule;
use Friendica\Module\HTTPException\MethodNotAllowed;
use Friendica\Module\HTTPException\PageNotFound;
use Friendica\Module\Special\Options;
use Friendica\Network\HTTPException;
use Friendica\Network\HTTPException\InternalServerErrorException;
use Friendica\Network\HTTPException\MethodNotAllowedException;
use Friendica\Network\HTTPException\NotFoundException;
use Friendica\Util\Router\FriendicaGroupCountBased;
@ -78,7 +80,7 @@ class Router
/**
* @var array Module parameters
*/
private $parameters = [];
protected $parameters = [];
/** @var L10n */
private $l10n;
@ -98,6 +100,9 @@ class Router
/** @var LoggerInterface */
private $logger;
/** @var bool */
private $isLocalUser;
/** @var float */
private $dice_profiler_threshold;
@ -110,6 +115,9 @@ class Router
/** @var array */
private $server;
/** @var string|null */
protected $moduleClass = null;
/**
* @param array $server The $_SERVER variable
* @param string $baseRoutesFilepath The path to a base routes file to leverage cache, can be empty
@ -120,9 +128,10 @@ class Router
* @param Arguments $args
* @param LoggerInterface $logger
* @param Dice $dice
* @param IHandleUserSessions $userSession
* @param RouteCollector|null $routeCollector
*/
public function __construct(array $server, string $baseRoutesFilepath, L10n $l10n, ICanCache $cache, ICanLock $lock, IManageConfigValues $config, Arguments $args, LoggerInterface $logger, Dice $dice, RouteCollector $routeCollector = null)
public function __construct(array $server, string $baseRoutesFilepath, L10n $l10n, ICanCache $cache, ICanLock $lock, IManageConfigValues $config, Arguments $args, LoggerInterface $logger, Dice $dice, IHandleUserSessions $userSession, RouteCollector $routeCollector = null)
{
$this->baseRoutesFilepath = $baseRoutesFilepath;
$this->l10n = $l10n;
@ -133,6 +142,7 @@ class Router
$this->dice = $dice;
$this->server = $server;
$this->logger = $logger;
$this->isLocalUser = !empty($userSession->getLocalUserId());
$this->dice_profiler_threshold = $config->get('system', 'dice_profiler_threshold', 0);
$this->routeCollector = $routeCollector ?? new RouteCollector(new Std(), new GroupCountBased());
@ -140,6 +150,8 @@ class Router
if ($this->baseRoutesFilepath && !file_exists($this->baseRoutesFilepath)) {
throw new HTTPException\InternalServerErrorException('Routes file path does\'n exist.');
}
$this->parameters = [$this->server];
}
/**
@ -210,7 +222,7 @@ class Router
*
* @return bool
*/
private function isGroup(array $config)
private function isGroup(array $config): bool
{
return
is_array($config) &&
@ -246,75 +258,74 @@ class Router
*
* @return RouteCollector|null
*/
public function getRouteCollector()
public function getRouteCollector(): ?RouteCollector
{
return $this->routeCollector;
}
/**
* Returns the Friendica\BaseModule-extending class name if a route rule matched
*
* @return string
*
* @throws InternalServerErrorException
* @throws MethodNotAllowedException
*/
public function getModuleClass(): string
{
if (empty($this->moduleClass)) {
$this->determineModuleClass();
}
return $this->moduleClass;
}
/**
* Returns the relevant module class name for the given page URI or NULL if no route rule matched.
*
* @return string A Friendica\BaseModule-extending class name if a route rule matched
* @return void
*
* @throws HTTPException\InternalServerErrorException
* @throws HTTPException\MethodNotAllowedException If a rule matched but the method didn't
* @throws HTTPException\NotFoundException If no rule matched
* @throws HTTPException\InternalServerErrorException Unexpected exceptions
* @throws HTTPException\MethodNotAllowedException If a rule is private only
*/
private function getModuleClass(): string
private function determineModuleClass(): void
{
$cmd = $this->args->getCommand();
$cmd = '/' . ltrim($cmd, '/');
$dispatcher = new FriendicaGroupCountBased($this->getCachedDispatchData());
$this->parameters = [];
// Check if the HTTP method is OPTIONS and return the special Options Module with the possible HTTP methods
if ($this->args->getMethod() === static::OPTIONS) {
$moduleClass = Options::class;
$this->parameters = ['allowedMethods' => $dispatcher->getOptions($cmd)];
} else {
$routeInfo = $dispatcher->dispatch($this->args->getMethod(), $cmd);
if ($routeInfo[0] === Dispatcher::FOUND) {
$moduleClass = $routeInfo[1];
$this->parameters = $routeInfo[2];
} elseif ($routeInfo[0] === Dispatcher::METHOD_NOT_ALLOWED) {
throw new HTTPException\MethodNotAllowedException($this->l10n->t('Method not allowed for this module. Allowed method(s): %s', implode(', ', $routeInfo[1])));
} else {
throw new HTTPException\NotFoundException($this->l10n->t('Page not found.'));
}
}
return $moduleClass;
}
public function getModule(?string $module_class = null): ICanHandleRequests
{
$module_parameters = [$this->server];
/**
* 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.
**/
try {
$module_class = $module_class ?? $this->getModuleClass();
$module_parameters[] = $this->parameters;
// Check if the HTTP method is OPTIONS and return the special Options Module with the possible HTTP methods
if ($this->args->getMethod() === static::OPTIONS) {
$this->moduleClass = Options::class;
$this->parameters[] = ['AllowedMethods' => $dispatcher->getOptions($cmd)];
} else {
$routeInfo = $dispatcher->dispatch($this->args->getMethod(), $cmd);
if ($routeInfo[0] === Dispatcher::FOUND) {
$this->moduleClass = $routeInfo[1];
$this->parameters[] = $routeInfo[2];
} else if ($routeInfo[0] === Dispatcher::METHOD_NOT_ALLOWED) {
throw new HTTPException\MethodNotAllowedException($this->l10n->t('Method not allowed for this module. Allowed method(s): %s', implode(', ', $routeInfo[1])));
} else {
throw new HTTPException\NotFoundException($this->l10n->t('Page not found.'));
}
}
} catch (MethodNotAllowedException $e) {
$module_class = MethodNotAllowed::class;
$this->moduleClass = MethodNotAllowed::class;
} catch (NotFoundException $e) {
$moduleName = $this->args->getModuleName();
// Then we try addon-provided modules that we wrap in the LegacyModule class
if (Addon::isEnabled($moduleName) && file_exists("addon/{$moduleName}/{$moduleName}.php")) {
//Check if module is an app and if public access to apps is allowed or not
$privateapps = $this->config->get('config', 'private_addons', false);
if ((!local_user()) && Hook::isAddonApp($moduleName) && $privateapps) {
if (!$this->isLocalUser && Hook::isAddonApp($moduleName) && $privateapps) {
throw new MethodNotAllowedException($this->l10n->t("You must be logged in to use addons. "));
} else {
include_once "addon/{$moduleName}/{$moduleName}.php";
if (function_exists($moduleName . '_module')) {
$module_parameters[] = "addon/{$moduleName}/{$moduleName}.php";
$module_class = LegacyModule::class;
$this->parameters[] = "addon/{$moduleName}/{$moduleName}.php";
$this->moduleClass = LegacyModule::class;
}
}
}
@ -322,24 +333,29 @@ class Router
/* 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/{$moduleName}.php")) {
$module_parameters[] = "mod/{$moduleName}.php";
$module_class = LegacyModule::class;
if (!$this->moduleClass && file_exists("mod/{$moduleName}.php")) {
$this->parameters[] = "mod/{$moduleName}.php";
$this->moduleClass = LegacyModule::class;
}
$module_class = $module_class ?: PageNotFound::class;
$this->moduleClass = $this->moduleClass ?: PageNotFound::class;
}
}
public function getModule(?string $module_class = null): ICanHandleRequests
{
$moduleClass = $module_class ?? $this->getModuleClass();
$stamp = microtime(true);
try {
/** @var ICanHandleRequests $module */
return $this->dice->create($module_class, $module_parameters);
return $this->dice->create($moduleClass, $this->parameters);
} finally {
if ($this->dice_profiler_threshold > 0) {
$dur = floatval(microtime(true) - $stamp);
if ($dur >= $this->dice_profiler_threshold) {
$this->logger->notice('Dice module creation lasts too long.', ['duration' => round($dur, 3), 'module' => $module_class, 'parameters' => $module_parameters]);
$this->logger->notice('Dice module creation lasts too long.', ['duration' => round($dur, 3), 'module' => $moduleClass, 'parameters' => $this->parameters]);
}
}
}

View file

@ -48,25 +48,27 @@ class BaseCollection extends \ArrayIterator
/**
* @inheritDoc
*/
public function offsetSet($offset, $value)
#[\ReturnTypeWillChange]
public function offsetSet($key, $value): void
{
if (is_null($offset)) {
if (is_null($key)) {
$this->totalCount++;
}
parent::offsetSet($offset, $value);
parent::offsetSet($key, $value);
}
/**
* @inheritDoc
*/
public function offsetUnset($offset)
#[\ReturnTypeWillChange]
public function offsetUnset($key): void
{
if ($this->offsetExists($offset)) {
if ($this->offsetExists($key)) {
$this->totalCount--;
}
parent::offsetUnset($offset);
parent::offsetUnset($key);
}
/**

View file

@ -389,7 +389,7 @@ abstract class BaseModule implements ICanHandleRequests
public static function getFormSecurityStandardErrorMessage(): string
{
return DI::l10n()->t("The form security token was not correct. This probably happened because the form has been opened for too long \x28>3 hours\x29 before submitting it.") . EOL;
return DI::l10n()->t("The form security token was not correct. This probably happened because the form has been opened for too long \x28>3 hours\x29 before submitting it.");
}
public static function checkFormSecurityTokenRedirectOnError(string $err_redirect, string $typename = '', string $formname = 'form_security_token')
@ -397,7 +397,7 @@ abstract class BaseModule implements ICanHandleRequests
if (!self::checkFormSecurityToken($typename, $formname)) {
Logger::notice('checkFormSecurityToken failed: user ' . DI::app()->getLoggedInUserNickname() . ' - form element ' . $typename);
Logger::debug('checkFormSecurityToken failed', ['request' => $_REQUEST]);
notice(self::getFormSecurityStandardErrorMessage());
DI::sysmsg()->addNotice(self::getFormSecurityStandardErrorMessage());
DI::baseUrl()->redirect($err_redirect);
}
}

View file

@ -75,9 +75,8 @@ HELP;
$arr = [];
$files = array_merge(
['index.php', 'boot.php'],
['index.php'],
glob('mod/*'),
glob('include/*'),
glob('addon/*/*'),
$this->globRecursive('src')
);

View file

@ -131,9 +131,9 @@ HELP;
$this->out('Updating event table fields');
$this->database->replaceInTableFields('event', ['uri'], $old_url, $new_url);
$this->out('Updating fcontact table fields');
$this->database->replaceInTableFields('fcontact', ['url', 'photo', 'request', 'batch', 'poll', 'confirm', 'alias'], $old_url, $new_url);
$this->database->replaceInTableFields('fcontact', ['addr'], $old_host, $new_host);
$this->out('Updating diaspora-contact table fields');
$this->database->replaceInTableFields('diaspora-contact', ['alias', 'photo', 'photo-medium', 'photo-small', 'batch', 'notify', 'poll', 'subscribe'], $old_url, $new_url);
$this->database->replaceInTableFields('diaspora-contact', ['addr'], $old_host, $new_host);
$this->out('Updating fsuggest table fields');
$this->database->replaceInTableFields('fsuggest', ['url', 'request', 'photo'], $old_url, $new_url);
@ -197,7 +197,7 @@ HELP;
$this->out('Schedule relocation messages to remote Friendica and Diaspora hosts');
$users = $this->database->selectToArray('user', ['uid'], ['account_removed' => false, 'account_expired' => false]);
foreach ($users as $user) {
Worker::add(PRIORITY_HIGH, 'Notifier', Delivery::RELOCATION, $user['uid']);
Worker::add(Worker::PRIORITY_HIGH, 'Notifier', Delivery::RELOCATION, $user['uid']);
}
return 0;

View file

@ -105,13 +105,6 @@ HELP;
$files = glob('mod/*.php');
$this->checkFiles($php_path, $files);
if ($this->getOption('v')) {
$this->out('Directory: include');
}
$files = glob('include/*.php');
$this->checkFiles($php_path, $files);
if ($this->getOption('v')) {
$this->out('Directory: addon');
}

View file

@ -370,7 +370,7 @@ HELP;
$contact['url'],
$contact['email'],
Temporal::getRelativeDate($contact['created']),
Temporal::getRelativeDate($contact['login_date']),
Temporal::getRelativeDate($contact['last-activity']),
Temporal::getRelativeDate($contact['last-item']),
]);
}
@ -396,7 +396,7 @@ HELP;
'nickname',
'email',
'register_date',
'login_date',
'last-activity',
'verified',
'blocked',
];

View file

@ -125,7 +125,7 @@ class Avatar
private static function getFilename(string $url): string
{
$guid = Item::guidFromUri($url, parse_url($url, PHP_URL_HOST));
$guid = Item::guidFromUri($url);
return substr($guid, 0, 2) . '/' . substr($guid, 3, 2) . '/' . substr($guid, 5, 3) . '/' .
substr($guid, 9, 2) .'/' . substr($guid, 11, 2) . '/' . substr($guid, 13, 4). '/' . substr($guid, 18) . '-';

View file

@ -32,7 +32,7 @@ use Friendica\Core\L10n;
use Friendica\Core\PConfig\Capability\IManagePersonalConfigValues;
use Friendica\Core\Protocol;
use Friendica\Core\Renderer;
use Friendica\Core\Session;
use Friendica\Core\Session\Capability\IHandleUserSessions;
use Friendica\Core\Theme;
use Friendica\Database\DBA;
use Friendica\Model\Contact;
@ -77,8 +77,10 @@ class Conversation
private $page;
/** @var App\Mode */
private $mode;
/** @var IHandleUserSessions */
private $session;
public function __construct(LoggerInterface $logger, Profiler $profiler, Activity $activity, L10n $l10n, Item $item, Arguments $args, BaseURL $baseURL, IManageConfigValues $config, IManagePersonalConfigValues $pConfig, App\Page $page, App\Mode $mode, App $app)
public function __construct(LoggerInterface $logger, Profiler $profiler, Activity $activity, L10n $l10n, Item $item, Arguments $args, BaseURL $baseURL, IManageConfigValues $config, IManagePersonalConfigValues $pConfig, App\Page $page, App\Mode $mode, App $app, IHandleUserSessions $session)
{
$this->activity = $activity;
$this->item = $item;
@ -92,6 +94,7 @@ class Conversation
$this->pConfig = $pConfig;
$this->page = $page;
$this->app = $app;
$this->session = $session;
}
/**
@ -135,7 +138,7 @@ class Conversation
return;
}
if (!empty($activity['verb']) && $this->activity->match($activity['verb'], $verb) && ($activity['gravity'] != GRAVITY_PARENT)) {
if (!empty($activity['verb']) && $this->activity->match($activity['verb'], $verb) && ($activity['gravity'] != ItemModel::GRAVITY_PARENT)) {
$author = [
'uid' => 0,
'id' => $activity['author-id'],
@ -143,7 +146,7 @@ class Conversation
'url' => $activity['author-link']
];
$url = Contact::magicLinkByContact($author);
if (strpos($url, 'redir/') === 0) {
if (strpos($url, 'contact/redir/') === 0) {
$sparkle = ' class="sparkle" ';
}
@ -168,7 +171,7 @@ class Conversation
continue;
}
if (public_contact() == $activity['author-id']) {
if ($this->session->getPublicContactId() == $activity['author-id']) {
$conv_responses[$mode][$activity['thr-parent-id']]['self'] = 1;
}
@ -261,7 +264,7 @@ class Conversation
break;
}
$expanded .= "\t" . '<p class="wall-item-' . $verb . '-expanded" id="' . $verb . 'list-' . $id . '" style="display: none;" >' . $explikers . EOL . '</p>';
$expanded .= "\t" . '<p class="wall-item-' . $verb . '-expanded" id="' . $verb . 'list-' . $id . '" style="display: none;" >' . $explikers . '</p>';
}
$o .= Renderer::replaceMacros(Renderer::getMarkupTemplate('voting_fakelink.tpl'), [
@ -293,7 +296,7 @@ class Conversation
$x['bang'] = $x['bang'] ?? '';
$x['visitor'] = $x['visitor'] ?? 'block';
$x['is_owner'] = $x['is_owner'] ?? true;
$x['profile_uid'] = $x['profile_uid'] ?? local_user();
$x['profile_uid'] = $x['profile_uid'] ?? $this->session->getLocalUserId();
$geotag = !empty($x['allow_location']) ? Renderer::replaceMacros(Renderer::getMarkupTemplate('jot_geotag.tpl'), []) : '';
@ -328,7 +331,7 @@ class Conversation
$created_at = '';
}
$tpl = Renderer::getMarkupTemplate("jot.tpl");
$tpl = Renderer::getMarkupTemplate('jot.tpl');
$o .= Renderer::replaceMacros($tpl, [
'$new_post' => $this->l10n->t('New Post'),
@ -356,7 +359,7 @@ class Conversation
'$title' => $x['title'] ?? '',
'$placeholdertitle' => $this->l10n->t('Set title'),
'$category' => $x['category'] ?? '',
'$placeholdercategory' => Feature::isEnabled(local_user(), 'categories') ? $this->l10n->t("Categories \x28comma-separated list\x29") : '',
'$placeholdercategory' => Feature::isEnabled($this->session->getLocalUserId(), 'categories') ? $this->l10n->t("Categories \x28comma-separated list\x29") : '',
'$scheduled_at' => Temporal::getDateTimeField(
new \DateTime(),
new \DateTime('now + 6 months'),
@ -393,7 +396,8 @@ class Conversation
'$message' => $this->l10n->t('Message'),
'$browser' => $this->l10n->t('Browser'),
'$compose_link_title' => $this->l10n->t('Open Compose page'),
'$compose_link_title' => $this->l10n->t('Open Compose page'),
'$always_open_compose' => $this->pConfig->get($this->session->getLocalUserId(), 'frio', 'always_open_compose', false),
]);
@ -432,7 +436,7 @@ class Conversation
$this->page->registerStylesheet(Theme::getPathForFile('js/friendica-tagsinput/friendica-tagsinput.css'));
$this->page->registerStylesheet(Theme::getPathForFile('js/friendica-tagsinput/friendica-tagsinput-typeahead.css'));
$ssl_state = (local_user() ? true : false);
$ssl_state = (bool)$this->session->getLocalUserId();
$live_update_div = '';
@ -484,19 +488,19 @@ class Conversation
}
}
} elseif ($mode === 'notes') {
$items = $this->addChildren($items, false, $order, local_user(), $mode);
$items = $this->addChildren($items, false, $order, $this->session->getLocalUserId(), $mode);
if (!$update) {
$live_update_div = '<div id="live-notes"></div>' . "\r\n"
. "<script> var profile_uid = " . local_user()
. "; var netargs = '/?f='; </script>\r\n";
. "<script> var profile_uid = " . $this->session->getLocalUserId()
. "; var netargs = '?f='; </script>\r\n";
}
} elseif ($mode === 'display') {
$items = $this->addChildren($items, false, $order, $uid, $mode);
if (!$update) {
$live_update_div = '<div id="live-display"></div>' . "\r\n"
. "<script> var profile_uid = " . Session::get('uid', 0) . ";"
. "<script> var profile_uid = " . ($this->session->getLocalUserId() ?: 0) . ";"
. "</script>";
}
} elseif ($mode === 'community') {
@ -516,13 +520,13 @@ class Conversation
if (!$update) {
$live_update_div = '<div id="live-contact"></div>' . "\r\n"
. "<script> var profile_uid = -1; var netargs = '" . substr($this->args->getCommand(), 8)
."/?f='; </script>\r\n";
."?f='; </script>\r\n";
}
} elseif ($mode === 'search') {
$live_update_div = '<div id="live-search"></div>' . "\r\n";
}
$page_dropping = ((local_user() && local_user() == $uid) ? true : false);
$page_dropping = $this->session->getLocalUserId() && $this->session->getLocalUserId() == $uid && $mode != 'search';
if (!$update) {
$_SESSION['return_path'] = $this->args->getQueryString();
@ -542,7 +546,7 @@ class Conversation
'announce' => [],
];
if ($this->pConfig->get(local_user(), 'system', 'hide_dislike')) {
if ($this->pConfig->get($this->session->getLocalUserId(), 'system', 'hide_dislike')) {
unset($conv_responses['dislike']);
}
@ -560,7 +564,7 @@ class Conversation
$writable = $items[0]['writable'] || ($items[0]['uid'] == 0) && in_array($items[0]['network'], Protocol::FEDERATED);
}
if (!local_user()) {
if (!$this->session->getLocalUserId()) {
$writable = false;
}
@ -593,7 +597,7 @@ class Conversation
$threadsid++;
// prevent private email from leaking.
if ($item['network'] === Protocol::MAIL && local_user() != $item['uid']) {
if ($item['network'] === Protocol::MAIL && $this->session->getLocalUserId() != $item['uid']) {
continue;
}
@ -608,7 +612,7 @@ class Conversation
$profile_link = Contact::magicLinkByContact($author);
$sparkle = '';
if (strpos($profile_link, 'redir/') === 0) {
if (strpos($profile_link, 'contact/redir/') === 0) {
$sparkle = ' sparkle';
}
@ -637,17 +641,17 @@ class Conversation
'announce' => null,
];
if ($this->pConfig->get(local_user(), 'system', 'hide_dislike')) {
if ($this->pConfig->get($this->session->getLocalUserId(), 'system', 'hide_dislike')) {
unset($likebuttons['dislike']);
}
$body_html = ItemModel::prepareBody($item, true, $preview);
[$categories, $folders] = $this->item->determineCategoriesTerms($item, local_user());
[$categories, $folders] = $this->item->determineCategoriesTerms($item, $this->session->getLocalUserId());
if (!empty($item['title'])) {
$title = $item['title'];
} elseif (!empty($item['content-warning']) && $this->pConfig->get(local_user(), 'system', 'disable_cw', false)) {
} elseif (!empty($item['content-warning']) && $this->pConfig->get($this->session->getLocalUserId(), 'system', 'disable_cw', false)) {
$title = ucfirst($item['content-warning']);
} else {
$title = '';
@ -741,7 +745,7 @@ class Conversation
$this->builtinActivityPuller($item, $conv_responses);
// Only add what is visible
if ($item['network'] === Protocol::MAIL && local_user() != $item['uid']) {
if ($item['network'] === Protocol::MAIL && $this->session->getLocalUserId() != $item['uid']) {
continue;
}
@ -755,7 +759,7 @@ class Conversation
$item['pagedrop'] = $page_dropping;
if ($item['gravity'] == GRAVITY_PARENT) {
if ($item['gravity'] == ItemModel::GRAVITY_PARENT) {
$item_object = new PostObject($item);
$conv->addParent($item_object);
}
@ -786,11 +790,11 @@ class Conversation
private function getBlocklist(): array
{
if (!local_user()) {
if (!$this->session->getLocalUserId()) {
return [];
}
$str_blocked = str_replace(["\n", "\r"], ",", $this->pConfig->get(local_user(), 'system', 'blocked'));
$str_blocked = str_replace(["\n", "\r"], ",", $this->pConfig->get($this->session->getLocalUserId(), 'system', 'blocked') ?? '');
if (empty($str_blocked)) {
return [];
}
@ -825,7 +829,7 @@ class Conversation
}
if (!empty($activity)) {
if (($row['gravity'] == GRAVITY_PARENT)) {
if (($row['gravity'] == ItemModel::GRAVITY_PARENT)) {
$row['post-reason'] = ItemModel::PR_ANNOUNCEMENT;
$row = array_merge($row, $activity);
@ -834,7 +838,7 @@ class Conversation
$row['causer-link'] = $contact['url'];
$row['causer-avatar'] = $contact['thumb'];
$row['causer-name'] = $contact['name'];
} elseif (($row['gravity'] == GRAVITY_ACTIVITY) && ($row['verb'] == Activity::ANNOUNCE) &&
} elseif (($row['gravity'] == ItemModel::GRAVITY_ACTIVITY) && ($row['verb'] == Activity::ANNOUNCE) &&
($row['author-id'] == $activity['causer-id'])) {
return $row;
}
@ -860,14 +864,14 @@ class Conversation
$row['direction'] = ['direction' => 4, 'title' => $this->l10n->t('You subscribed to one or more tags in this post.')];
break;
case ItemModel::PR_ANNOUNCEMENT:
if (!empty($row['causer-id']) && $this->pConfig->get(local_user(), 'system', 'display_resharer')) {
if (!empty($row['causer-id']) && $this->pConfig->get($this->session->getLocalUserId(), 'system', 'display_resharer')) {
$row['owner-id'] = $row['causer-id'];
$row['owner-link'] = $row['causer-link'];
$row['owner-avatar'] = $row['causer-avatar'];
$row['owner-name'] = $row['causer-name'];
}
if (in_array($row['gravity'], [GRAVITY_PARENT, GRAVITY_COMMENT]) && !empty($row['causer-id'])) {
if (in_array($row['gravity'], [ItemModel::GRAVITY_PARENT, ItemModel::GRAVITY_COMMENT]) && !empty($row['causer-id'])) {
$causer = ['uid' => 0, 'id' => $row['causer-id'], 'network' => $row['causer-network'], 'url' => $row['causer-link']];
$row['reshared'] = $this->l10n->t('%s reshared this.', '<a href="'. htmlentities(Contact::magicLinkByContact($causer)) .'">' . htmlentities($row['causer-name']) . '</a>');
@ -941,7 +945,7 @@ class Conversation
$activitycounter = [];
foreach ($parents as $parent) {
if (!empty($parent['thr-parent-id']) && !empty($parent['gravity']) && ($parent['gravity'] == GRAVITY_ACTIVITY)) {
if (!empty($parent['thr-parent-id']) && !empty($parent['gravity']) && ($parent['gravity'] == ItemModel::GRAVITY_ACTIVITY)) {
$uriid = $parent['thr-parent-id'];
if (!empty($parent['author-id'])) {
$activities[$uriid] = ['causer-id' => $parent['author-id']];
@ -966,7 +970,7 @@ class Conversation
}
$condition = DBA::mergeConditions($condition,
["`uid` IN (0, ?) AND (`vid` != ? OR `vid` IS NULL)", $uid, Verb::getID(Activity::FOLLOW)]);
["`uid` IN (0, ?) AND (NOT `vid` IN (?, ?) OR `vid` IS NULL)", $uid, Verb::getID(Activity::FOLLOW), Verb::getID(Activity::VIEW)]);
$thread_parents = Post::select(['uri-id', 'causer-id'], $condition, ['order' => ['uri-id' => false, 'uid']]);
@ -993,10 +997,10 @@ class Conversation
}
if ($max_comments > 0) {
if (($row['gravity'] == GRAVITY_COMMENT) && (++$commentcounter[$row['parent-uri-id']] > $max_comments)) {
if (($row['gravity'] == ItemModel::GRAVITY_COMMENT) && (++$commentcounter[$row['parent-uri-id']] > $max_comments)) {
continue;
}
if (($row['gravity'] == GRAVITY_ACTIVITY) && (++$activitycounter[$row['parent-uri-id']] > $max_comments)) {
if (($row['gravity'] == ItemModel::GRAVITY_ACTIVITY) && (++$activitycounter[$row['parent-uri-id']] > $max_comments)) {
continue;
}
}
@ -1025,7 +1029,7 @@ class Conversation
$this->profiler->startRecording('rendering');
$children = [];
foreach ($item_list as $i => $item) {
if ($item['gravity'] != GRAVITY_PARENT) {
if ($item['gravity'] != ItemModel::GRAVITY_PARENT) {
if ($recursive) {
// Fallback to parent-uri if thr-parent is not set
$thr_parent = $item['thr-parent-id'];
@ -1182,7 +1186,7 @@ class Conversation
// Extract the top level items
foreach ($item_array as $item) {
if ($item['gravity'] == GRAVITY_PARENT) {
if ($item['gravity'] == ItemModel::GRAVITY_PARENT) {
$parents[] = $item;
}
}
@ -1212,7 +1216,7 @@ class Conversation
$parents[$i]['children'] = $this->sortItemChildren($parents[$i]['children']);
}
if (!$this->pConfig->get(local_user(), 'system', 'no_smart_threading', 0)) {
if (!$this->pConfig->get($this->session->getLocalUserId(), 'system', 'no_smart_threading', 0)) {
foreach ($parents as $i => $parent) {
$parents[$i] = $this->smartFlattenConversation($parent);
}

View file

@ -120,6 +120,12 @@ class Feature
['tagadelic', DI::l10n()->t('Tag Cloud'), DI::l10n()->t('Provide a personal tag cloud on your profile page'), false, DI::config()->get('feature_lock', 'tagadelic', false)],
['profile_membersince', DI::l10n()->t('Display Membership Date'), DI::l10n()->t('Display membership date in profile'), false, DI::config()->get('feature_lock', 'profile_membersince', false)],
],
//Advanced Calendar Settings
'advanced_calendar' => [
DI::l10n()->t('Advanced Calendar Settings'),
['public_calendar', DI::l10n()->t('Allow anonymous access to your calendar'), DI::l10n()->t('Allows anonymous visitors to consult your calendar and your public events. Contact birthday events are private to you.'), false, DI::config()->get('feature_lock', 'public_calendar', false)],
]
];
// removed any locked features and remove the entire category if this makes it empty

View file

@ -223,7 +223,7 @@ class ForumManager
AND NOT `contact`.`pending` AND NOT `contact`.`archive`
AND `contact`.`uid` = ?
GROUP BY `contact`.`id`",
local_user(), Protocol::DFRN, Protocol::ACTIVITYPUB, Contact::TYPE_COMMUNITY, local_user()
DI::userSession()->getLocalUserId(), Protocol::DFRN, Protocol::ACTIVITYPUB, Contact::TYPE_COMMUNITY, DI::userSession()->getLocalUserId()
);
return DBA::toArray($stmtContacts);

View file

@ -22,21 +22,21 @@
namespace Friendica\Content;
use Friendica\Content\Text\BBCode;
use Friendica\Content\Text\Markdown;
use Friendica\Content\Text\HTML;
use Friendica\Core\Hook;
use Friendica\Core\L10n;
use Friendica\Core\Logger;
use Friendica\Core\Protocol;
use Friendica\Core\Session\Capability\IHandleUserSessions;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\Model\Contact;
use Friendica\Model\Group;
use Friendica\Model\Item as ModelItem;
use Friendica\Model\Item as ItemModel;
use Friendica\Model\Photo;
use Friendica\Model\Tag;
use Friendica\Model\Post;
use Friendica\Protocol\Activity;
use Friendica\Protocol\Diaspora;
use Friendica\Util\Profiler;
use Friendica\Util\Proxy;
use Friendica\Util\XML;
@ -52,12 +52,15 @@ class Item
private $l10n;
/** @var Profiler */
private $profiler;
/** @var IHandleUserSessions */
private $userSession;
public function __construct(Profiler $profiler, Activity $activity, L10n $l10n)
public function __construct(Profiler $profiler, Activity $activity, L10n $l10n, IHandleUserSessions $userSession)
{
$this->profiler = $profiler;
$this->activity = $activity;
$this->l10n = $l10n;
$this->profiler = $profiler;
$this->activity = $activity;
$this->l10n = $l10n;
$this->userSession = $userSession;
}
/**
@ -109,7 +112,7 @@ class Item
$categories[] = [
'name' => $savedFolderName,
'url' => $url,
'removeurl' => local_user() == $uid ? 'filerm/' . $item['id'] . '?cat=' . rawurlencode($savedFolderName) : '',
'removeurl' => $this->userSession->getLocalUserId() == $uid ? 'filerm/' . $item['id'] . '?cat=' . rawurlencode($savedFolderName) : '',
'first' => $first,
'last' => false
];
@ -120,12 +123,12 @@ class Item
$categories[count($categories) - 1]['last'] = true;
}
if (local_user() == $uid) {
if ($this->userSession->getLocalUserId() == $uid) {
foreach (Post\Category::getArrayByURIId($item['uri-id'], $uid, Post\Category::FILE) as $savedFolderName) {
$folders[] = [
'name' => $savedFolderName,
'url' => "#",
'removeurl' => local_user() == $uid ? 'filerm/' . $item['id'] . '?term=' . rawurlencode($savedFolderName) : '',
'removeurl' => $this->userSession->getLocalUserId() == $uid ? 'filerm/' . $item['id'] . '?term=' . rawurlencode($savedFolderName) : '',
'first' => $first,
'last' => false
];
@ -331,7 +334,7 @@ class Item
$sub_link = $contact_url = $pm_url = $status_link = '';
$photos_link = $posts_link = $block_link = $ignore_link = '';
if (local_user() && local_user() == $item['uid'] && $item['gravity'] == GRAVITY_PARENT && !$item['self'] && !$item['mention']) {
if ($this->userSession->getLocalUserId() && $this->userSession->getLocalUserId() == $item['uid'] && $item['gravity'] == ItemModel::GRAVITY_PARENT && !$item['self'] && !$item['mention']) {
$sub_link = 'javascript:doFollowThread(' . $item['id'] . '); return false;';
}
@ -342,13 +345,13 @@ class Item
'url' => $item['author-link'],
];
$profile_link = Contact::magicLinkByContact($author, $item['author-link']);
$sparkle = (strpos($profile_link, 'redir/') === 0);
$sparkle = (strpos($profile_link, 'contact/redir/') === 0);
$cid = 0;
$pcid = $item['author-id'];
$network = '';
$rel = 0;
$condition = ['uid' => local_user(), 'uri-id' => $item['author-uri-id']];
$condition = ['uid' => $this->userSession->getLocalUserId(), 'uri-id' => $item['author-uri-id']];
$contact = DBA::selectFirst('contact', ['id', 'network', 'rel'], $condition);
if (DBA::isResult($contact)) {
$cid = $contact['id'];
@ -358,7 +361,7 @@ class Item
if ($sparkle) {
$status_link = $profile_link . '/status';
$photos_link = str_replace('/profile/', '/photos/', $profile_link);
$photos_link = $profile_link . '/photos';
$profile_link = $profile_link . '/profile';
}
@ -378,7 +381,7 @@ class Item
}
}
if (local_user()) {
if ($this->userSession->getLocalUserId()) {
$menu = [
$this->l10n->t('Follow Thread') => $sub_link,
$this->l10n->t('View Status') => $status_link,
@ -392,12 +395,12 @@ class Item
];
if (!empty($item['language'])) {
$menu[$this->l10n->t('Languages')] = 'javascript:alert(\'' . ModelItem::getLanguageMessage($item) . '\');';
$menu[$this->l10n->t('Languages')] = 'javascript:alert(\'' . ItemModel::getLanguageMessage($item) . '\');';
}
if ((($cid == 0) || ($rel == Contact::FOLLOWER)) &&
in_array($item['network'], Protocol::FEDERATED)) {
$menu[$this->l10n->t('Connect/Follow')] = 'follow?url=' . urlencode($item['author-link']) . '&auto=1';
$menu[$this->l10n->t('Connect/Follow')] = 'contact/follow?url=' . urlencode($item['author-link']) . '&auto=1';
}
} else {
$menu = [$this->l10n->t('View Profile') => $item['author-link']];
@ -439,7 +442,7 @@ class Item
return (!($this->activity->match($item['verb'], Activity::FOLLOW) &&
$item['object-type'] === Activity\ObjectType::NOTE &&
empty($item['self']) &&
$item['uid'] == local_user())
$item['uid'] == $this->userSession->getLocalUserId())
);
}
@ -470,7 +473,7 @@ class Item
}
$item['inform'] .= 'cid:' . $contact['id'];
if (($item['gravity'] == GRAVITY_COMMENT) || empty($contact['cid']) || ($contact['contact-type'] != Contact::TYPE_COMMUNITY)) {
if (($item['gravity'] == ItemModel::GRAVITY_COMMENT) || empty($contact['cid']) || ($contact['contact-type'] != Contact::TYPE_COMMUNITY)) {
continue;
}
@ -492,9 +495,9 @@ class Item
}
Logger::info('Got inform', ['inform' => $item['inform']]);
if (($item['gravity'] == GRAVITY_PARENT) && !empty($forum_contact) && ($private_forum || $only_to_forum)) {
if (($item['gravity'] == ItemModel::GRAVITY_PARENT) && !empty($forum_contact) && ($private_forum || $only_to_forum)) {
// we tagged a forum in a top level post. Now we change the post
$item['private'] = $private_forum ? ModelItem::PRIVATE : ModelItem::UNLISTED;
$item['private'] = $private_forum ? ItemModel::PRIVATE : ItemModel::UNLISTED;
if ($only_to_forum) {
$item['postopts'] = '';
@ -510,14 +513,14 @@ class Item
$item['allow_cid'] = '';
$item['allow_gid'] = '';
}
} elseif ($setPermissions && ($item['gravity'] == GRAVITY_PARENT)) {
} elseif ($setPermissions && ($item['gravity'] == ItemModel::GRAVITY_PARENT)) {
if (empty($receivers)) {
// For security reasons direct posts without any receiver will be posts to yourself
$self = Contact::selectFirst(['id'], ['uid' => $item['uid'], 'self' => true]);
$receivers[] = $self['id'];
}
$item['private'] = ModelItem::PRIVATE;
$item['private'] = ItemModel::PRIVATE;
$item['allow_cid'] = '';
$item['allow_gid'] = '';
$item['deny_cid'] = '';
@ -569,56 +572,31 @@ class Item
return $owner_thumb;
}
/**
* Add a share block for the given url
*
* @param string $url
* @param integer $uid
* @return string
*/
public function createSharedPostByUrl(string $url, int $uid = 0): string
{
if (!empty($uid)) {
$id = ModelItem::searchByLink($url, $uid);
}
if (empty($id)) {
$id = ModelItem::fetchByLink($url);
}
if (!$id) {
Logger::notice('Post could not be fetched.', ['url' => $url, 'uid' => $uid, 'callstack' => System::callstack()]);
return '';
}
Logger::debug('Fetched shared post', ['id' => $id, 'url' => $url, 'uid' => $uid, 'callstack' => System::callstack()]);
$shared_item = Post::selectFirst(['uri-id', 'uri', 'body', 'title', 'author-name', 'author-link', 'author-avatar', 'guid', 'created', 'plink', 'network'], ['id' => $id]);
if (!DBA::isResult($shared_item)) {
Logger::warning('Post does not exist.', ['id' => $id, 'url' => $url, 'uid' => $uid]);
return '';
}
return $this->createSharedBlockByArray($shared_item);
}
/**
* Add a share block for the given uri-id
*
* @param integer $UriId
* @param integer $uid
* @param array $item
* @param string $body
* @return string
*/
public function createSharedPostByUriId(int $UriId, int $uid = 0): string
public function addSharedPost(array $item, string $body = ''): string
{
$fields = ['uri-id', 'uri', 'body', 'title', 'author-name', 'author-link', 'author-avatar', 'guid', 'created', 'plink', 'network'];
$shared_item = Post::selectFirst($fields, ['uri-id' => $UriId, 'uid' => [$uid, 0], 'private' => [ModelItem::PUBLIC, ModelItem::UNLISTED]]);
if (!DBA::isResult($shared_item)) {
Logger::notice('Post does not exist.', ['uri-id' => $UriId, 'uid' => $uid]);
return '';
if (empty($body)) {
$body = $item['body'];
}
return $this->createSharedBlockByArray($shared_item);
if (empty($item['quote-uri-id'])) {
return $body;
}
$fields = ['uri-id', 'uri', 'body', 'title', 'author-name', 'author-link', 'author-avatar', 'guid', 'created', 'plink', 'network', 'quote-uri-id'];
$shared_item = Post::selectFirst($fields, ['uri-id' => $item['quote-uri-id'], 'uid' => [$item['uid'], 0], 'private' => [ItemModel::PUBLIC, ItemModel::UNLISTED]]);
if (!DBA::isResult($shared_item)) {
Logger::notice('Post does not exist.', ['uri-id' => $item['quote-uri-id'], 'uid' => $item['uid']]);
return $body;
}
return trim(BBCode::removeSharedData($body) . "\n" . $this->createSharedBlockByArray($shared_item, true));
}
/**
@ -626,55 +604,156 @@ class Item
*
* @param string $guid
* @param integer $uid
* @param bool $add_media
* @return string
*/
public function createSharedPostByGuid(string $guid, int $uid = 0, string $host = ''): string
private function createSharedPostByGuid(string $guid, bool $add_media): string
{
$fields = ['uri-id', 'uri', 'body', 'title', 'author-name', 'author-link', 'author-avatar', 'guid', 'created', 'plink', 'network'];
$shared_item = Post::selectFirst($fields, ['guid' => $guid, 'uid' => [$uid, 0], 'private' => [ModelItem::PUBLIC, ModelItem::UNLISTED]]);
if (!DBA::isResult($shared_item) && !empty($host) && Diaspora::storeByGuid($guid, $host, true)) {
Logger::debug('Fetched post', ['guid' => $guid, 'host' => $host, 'uid' => $uid]);
$shared_item = Post::selectFirst($fields, ['guid' => $guid, 'uid' => [$uid, 0], 'private' => [ModelItem::PUBLIC, ModelItem::UNLISTED]]);
} elseif (DBA::isResult($shared_item)) {
Logger::debug('Found existing post', ['guid' => $guid, 'host' => $host, 'uid' => $uid]);
}
$shared_item = Post::selectFirst($fields, ['guid' => $guid, 'uid' => 0, 'private' => [ItemModel::PUBLIC, ItemModel::UNLISTED]]);
if (!DBA::isResult($shared_item)) {
Logger::notice('Post does not exist.', ['guid' => $guid, 'host' => $host, 'uid' => $uid]);
Logger::notice('Post does not exist.', ['guid' => $guid]);
return '';
}
return $this->createSharedBlockByArray($shared_item);
return $this->createSharedBlockByArray($shared_item, $add_media);
}
/**
* Add a share block for the given item array
*
* @param array $item
* @param bool $add_media
* @return string
*/
public function createSharedBlockByArray(array $item): string
public function createSharedBlockByArray(array $item, bool $add_media = false): string
{
if (!in_array($item['network'] ?? '', Protocol::FEDERATED)) {
if ($item['network'] == Protocol::FEED) {
return PageInfo::getFooterFromUrl($item['plink']);
} elseif (!in_array($item['network'] ?? '', Protocol::FEDERATED)) {
$item['guid'] = '';
$item['uri'] = '';
}
if ($add_media) {
$item['body'] = Post\Media::addAttachmentsToBody($item['uri-id'], $item['body']);
}
$shared_content = BBCode::getShareOpeningTag($item['author-name'], $item['author-link'], $item['author-avatar'], $item['plink'], $item['created'], $item['guid'], $item['uri']);
if (!empty($item['title'])) {
$shared_content .= '[h3]' . $item['title'] . "[/h3]\n";
}
$shared = $this->getShareArray($item);
// If it is a reshared post then reformat it to avoid display problems with two share elements
if (Diaspora::isReshare($item['body'], false)) {
$item['body'] = Markdown::toBBCode(BBCode::toMarkdown($item['body']));
$item['body'] = Diaspora::replacePeopleGuid($item['body'], $item['author-link']);
if (!empty($shared)) {
if (!empty($shared['guid']) && ($encaspulated_share = $this->createSharedPostByGuid($shared['guid'], true))) {
if (!empty(BBCode::fetchShareAttributes($item['body']))) {
$item['body'] = preg_replace("/\[share.*?\](.*)\[\/share\]/ism", $encaspulated_share, $item['body']);
} else {
$item['body'] .= $encaspulated_share;
}
}
$item['body'] = HTML::toBBCode(BBCode::convertForUriId($item['uri-id'], $item['body'], BBCode::ACTIVITYPUB));
}
$shared_content .= $item['body'] . '[/share]';
return $shared_content;
}
/**
* Return the shared post from an item array (if the item is shared item)
*
* @param array $item
* @param array $fields
*
* @return array with the shared post
*/
public function getSharedPost(array $item, array $fields = []): array
{
if (!empty($item['quote-uri-id'])) {
$shared = Post::selectFirst($fields, ['uri-id' => $item['quote-uri-id'], 'uid' => [0, $item['uid'] ?? 0]]);
if (is_array($shared)) {
return [
'comment' => BBCode::removeSharedData($item['body'] ?? ''),
'post' => $shared
];
}
}
$attributes = BBCode::fetchShareAttributes($item['body'] ?? '');
if (!empty($attributes)) {
$shared = Post::selectFirst($fields, ['guid' => $attributes['guid'], 'uid' => [0, $item['uid'] ?? 0]]);
if (is_array($shared)) {
return [
'comment' => $attributes['comment'],
'post' => $shared
];
}
}
return [];
}
/**
* Return share data from an item array (if the item is shared item)
* We are providing the complete Item array, because at some time in the future
* we hopefully will define these values not in the body anymore but in some item fields.
* This function is meant to replace all similar functions in the system.
*
* @param array $item
*
* @return array with share information
*/
private function getShareArray(array $item): array
{
$attributes = BBCode::fetchShareAttributes($item['body'] ?? '');
if (!empty($attributes)) {
return $attributes;
}
if (!empty($item['quote-uri-id'])) {
$shared = Post::selectFirst(['author-name', 'author-link', 'author-avatar', 'plink', 'created', 'guid', 'uri', 'body'], ['uri-id' => $item['quote-uri-id']]);
if (!empty($shared)) {
return [
'author' => $shared['author-name'],
'profile' => $shared['author-link'],
'avatar' => $shared['author-avatar'],
'link' => $shared['plink'],
'posted' => $shared['created'],
'guid' => $shared['guid'],
'message_id' => $shared['uri'],
'comment' => $item['body'],
'shared' => $shared['body'],
];
}
}
return [];
}
/**
* Add a link to a shared post at the end of the post
*
* @param string $body
* @param integer $quote_uri_id
* @return string
*/
public function addShareLink(string $body, int $quote_uri_id): string
{
$post = Post::selectFirstPost(['uri', 'plink'], ['uri-id' => $quote_uri_id]);
if (empty($post)) {
return $body;
}
$body = BBCode::removeSharedData($body);
$body .= "\n" . ($post['plink'] ?: $post['uri']);
return $body;
}
}

View file

@ -24,12 +24,12 @@ namespace Friendica\Content;
use Friendica\App;
use Friendica\Core\Hook;
use Friendica\Core\Renderer;
use Friendica\Core\Session;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\Contact;
use Friendica\Model\Profile;
use Friendica\Model\User;
use Friendica\Module\Conversation\Community;
class Nav
{
@ -46,7 +46,7 @@ class Nav
'settings' => null,
'contacts' => null,
'delegation'=> null,
'events' => null,
'calendar' => null,
'register' => null
];
@ -126,7 +126,7 @@ class Nav
//Don't populate apps_menu if apps are private
$privateapps = DI::config()->get('config', 'private_addons', false);
if (local_user() || !$privateapps) {
if (DI::userSession()->getLocalUserId() || !$privateapps) {
$arr = ['app_menu' => self::$app_menu];
Hook::callAll('app_menu', $arr);
@ -148,7 +148,7 @@ class Nav
*/
private static function getInfo(App $a): array
{
$ssl_state = (bool) local_user();
$ssl_state = (bool) DI::userSession()->getLocalUserId();
/*
* Our network is distributed, and as you visit friends some of the
@ -162,10 +162,11 @@ class Nav
$nav = [
'admin' => null,
'moderation' => null,
'apps' => null,
'community' => null,
'home' => null,
'events' => null,
'calendar' => null,
'login' => null,
'logout' => null,
'langselector' => null,
@ -181,7 +182,7 @@ class Nav
$userinfo = null;
// nav links: array of array('href', 'text', 'extra css classes', 'title')
if (Session::isAuthenticated()) {
if (DI::userSession()->isAuthenticated()) {
$nav['logout'] = ['logout', DI::l10n()->t('Logout'), '', DI::l10n()->t('End this session')];
} else {
$nav['login'] = ['login', DI::l10n()->t('Login'), (DI::args()->getModuleName() == 'login' ? 'selected' : ''), DI::l10n()->t('Sign in')];
@ -191,9 +192,9 @@ class Nav
// user menu
$nav['usermenu'][] = ['profile/' . $a->getLoggedInUserNickname(), DI::l10n()->t('Status'), '', DI::l10n()->t('Your posts and conversations')];
$nav['usermenu'][] = ['profile/' . $a->getLoggedInUserNickname() . '/profile', DI::l10n()->t('Profile'), '', DI::l10n()->t('Your profile page')];
$nav['usermenu'][] = ['photos/' . $a->getLoggedInUserNickname(), DI::l10n()->t('Photos'), '', DI::l10n()->t('Your photos')];
$nav['usermenu'][] = ['profile/' . $a->getLoggedInUserNickname() . '/photos', DI::l10n()->t('Photos'), '', DI::l10n()->t('Your photos')];
$nav['usermenu'][] = ['profile/' . $a->getLoggedInUserNickname() . '/media', DI::l10n()->t('Media'), '', DI::l10n()->t('Your postings with media')];
$nav['usermenu'][] = ['events/', DI::l10n()->t('Events'), '', DI::l10n()->t('Your events')];
$nav['usermenu'][] = ['calendar/', DI::l10n()->t('Calendar'), '', DI::l10n()->t('Your calendar')];
$nav['usermenu'][] = ['notes/', DI::l10n()->t('Personal notes'), '', DI::l10n()->t('Your personal notes')];
// user info
@ -207,14 +208,14 @@ class Nav
// "Home" should also take you home from an authenticated remote profile connection
$homelink = Profile::getMyURL();
if (! $homelink) {
$homelink = Session::get('visitor_home', '');
$homelink = DI::session()->get('visitor_home', '');
}
if ((DI::args()->getModuleName() != 'home') && (! (local_user()))) {
if (DI::args()->getModuleName() != 'home' && ! DI::userSession()->getLocalUserId()) {
$nav['home'] = [$homelink, DI::l10n()->t('Home'), '', DI::l10n()->t('Home Page')];
}
if (intval(DI::config()->get('config', 'register_policy')) === \Friendica\Module\Register::OPEN && !Session::isAuthenticated()) {
if (intval(DI::config()->get('config', 'register_policy')) === \Friendica\Module\Register::OPEN && !DI::userSession()->isAuthenticated()) {
$nav['register'] = ['register', DI::l10n()->t('Register'), '', DI::l10n()->t('Create an account')];
}
@ -228,7 +229,7 @@ class Nav
$nav['apps'] = ['apps', DI::l10n()->t('Apps'), '', DI::l10n()->t('Addon applications, utilities, games')];
}
if (local_user() || !DI::config()->get('system', 'local_search')) {
if (DI::userSession()->getLocalUserId() || !DI::config()->get('system', 'local_search')) {
$nav['search'] = ['search', DI::l10n()->t('Search'), '', DI::l10n()->t('Search site content')];
$nav['searchoption'] = [
@ -243,21 +244,17 @@ class Nav
}
$gdirpath = 'directory';
if (strlen(DI::config()->get('system', 'singleuser'))) {
$gdir = DI::config()->get('system', 'directory');
if (strlen($gdir)) {
$gdirpath = Profile::zrl($gdir, true);
}
if (DI::config()->get('system', 'singleuser') && DI::config()->get('system', 'directory')) {
$gdirpath = Profile::zrl(DI::config()->get('system', 'directory'), true);
}
if ((local_user() || DI::config()->get('system', 'community_page_style') != CP_NO_COMMUNITY_PAGE) &&
!(DI::config()->get('system', 'community_page_style') == CP_NO_INTERNAL_COMMUNITY)) {
if ((DI::userSession()->getLocalUserId() || DI::config()->get('system', 'community_page_style') != Community::DISABLED_VISITOR) &&
!(DI::config()->get('system', 'community_page_style') == Community::DISABLED)) {
$nav['community'] = ['community', DI::l10n()->t('Community'), '', DI::l10n()->t('Conversations on this and other servers')];
}
if (local_user()) {
$nav['events'] = ['events', DI::l10n()->t('Events'), '', DI::l10n()->t('Events and Calendar')];
if (DI::userSession()->getLocalUserId()) {
$nav['calendar'] = ['calendar', DI::l10n()->t('Calendar'), '', DI::l10n()->t('Calendar')];
}
$nav['directory'] = [$gdirpath, DI::l10n()->t('Directory'), '', DI::l10n()->t('People directory')];
@ -269,13 +266,13 @@ class Nav
}
// The following nav links are only show to logged in users
if (local_user() && !empty($a->getLoggedInUserNickname())) {
if (DI::userSession()->getLocalUserId() && !empty($a->getLoggedInUserNickname())) {
$nav['network'] = ['network', DI::l10n()->t('Network'), '', DI::l10n()->t('Conversations from your friends')];
$nav['home'] = ['profile/' . $a->getLoggedInUserNickname(), DI::l10n()->t('Home'), '', DI::l10n()->t('Your posts and conversations')];
// Don't show notifications for public communities
if (Session::get('page_flags', '') != User::PAGE_FLAGS_COMMUNITY) {
if (DI::session()->get('page_flags', '') != User::PAGE_FLAGS_COMMUNITY) {
$nav['introductions'] = ['notifications/intros', DI::l10n()->t('Introductions'), '', DI::l10n()->t('Friend Requests')];
$nav['notifications'] = ['notifications', DI::l10n()->t('Notifications'), '', DI::l10n()->t('Notifications')];
$nav['notifications']['all'] = ['notifications/system', DI::l10n()->t('See all notifications'), '', ''];
@ -287,7 +284,7 @@ class Nav
$nav['messages']['outbox'] = ['message/sent', DI::l10n()->t('Outbox'), '', DI::l10n()->t('Outbox')];
$nav['messages']['new'] = ['message/new', DI::l10n()->t('New Message'), '', DI::l10n()->t('New Message')];
if (User::hasIdentities(DI::session()->get('submanage') ?: local_user())) {
if (User::hasIdentities(DI::userSession()->getSubManagedUserId() ?: DI::userSession()->getLocalUserId())) {
$nav['delegation'] = ['delegation', DI::l10n()->t('Accounts'), '', DI::l10n()->t('Manage other pages')];
}
@ -298,7 +295,8 @@ class Nav
// Show the link to the admin configuration page if user is admin
if ($a->isSiteAdmin()) {
$nav['admin'] = ['admin/', DI::l10n()->t('Admin'), '', DI::l10n()->t('Site setup and configuration')];
$nav['admin'] = ['admin/', DI::l10n()->t('Admin'), '', DI::l10n()->t('Site setup and configuration')];
$nav['moderation'] = ['moderation/', DI::l10n()->t('Moderation'), '', DI::l10n()->t('Content and user moderation')];
}
$nav['navigation'] = ['navigation/', DI::l10n()->t('Navigation'), '', DI::l10n()->t('Site map')];

View file

@ -391,7 +391,7 @@ class OEmbed
* @param string $title Optional title (default: what comes from OEmbed object)
* @return string Formatted HTML
*/
public static function getHTML(string $url, string $title = '')
public static function getHTML(string $url, string $title = ''): string
{
$o = self::fetchURL($url, !self::isAllowedURL($url));

View file

@ -213,7 +213,7 @@ class Smilies
public static function replaceFromArray(string $text, array $smilies, bool $no_images = false): string
{
if (intval(DI::config()->get('system', 'no_smilies'))
|| (local_user() && intval(DI::pConfig()->get(local_user(), 'system', 'no_smilies')))
|| (DI::userSession()->getLocalUserId() && intval(DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'system', 'no_smilies')))
) {
return $text;
}

View file

@ -55,18 +55,25 @@ class BBCode
// Update this value to the current date whenever changes are made to BBCode::convert
const VERSION = '2021-07-28';
const INTERNAL = 0;
const EXTERNAL = 1;
const API = 2;
const DIASPORA = 3;
const CONNECTORS = 4;
const OSTATUS = 7;
const TWITTER = 8;
const BACKLINK = 8;
const ACTIVITYPUB = 9;
const INTERNAL = 0;
const EXTERNAL = 1;
const MASTODON_API = 2;
const DIASPORA = 3;
const CONNECTORS = 4;
const TWITTER_API = 5;
const OSTATUS = 7;
const TWITTER = 8;
const BACKLINK = 8;
const ACTIVITYPUB = 9;
const TOP_ANCHOR = '<br class="top-anchor">';
const BOTTOM_ANCHOR = '<br class="button-anchor">';
const PREVIEW_NONE = 0;
const PREVIEW_NO_IMAGE = 1;
const PREVIEW_LARGE = 2;
const PREVIEW_SMALL = 3;
/**
* Fetches attachment data that were generated the old way
*
@ -266,8 +273,8 @@ class BBCode
// Get all linked images with alternative image description
if (preg_match_all("/\[img=(http[^\[\]]*)\]([^\[\]]*)\[\/img\]/Usi", $body, $pictures, PREG_SET_ORDER)) {
foreach ($pictures as $picture) {
if (Photo::isLocal($picture[1])) {
$post['images'][] = ['url' => str_replace('-1.', '-0.', $picture[1]), 'description' => $picture[2]];
if ($id = Photo::getIdForName($picture[1])) {
$post['images'][] = ['url' => str_replace('-1.', '-0.', $picture[1]), 'description' => $picture[2], 'id' => $id];
} else {
$post['remote_images'][] = ['url' => $picture[1], 'description' => $picture[2]];
}
@ -279,8 +286,8 @@ class BBCode
if (preg_match_all("/\[img\]([^\[\]]*)\[\/img\]/Usi", $body, $pictures, PREG_SET_ORDER)) {
foreach ($pictures as $picture) {
if (Photo::isLocal($picture[1])) {
$post['images'][] = ['url' => str_replace('-1.', '-0.', $picture[1]), 'description' => ''];
if ($id = Photo::getIdForName($picture[1])) {
$post['images'][] = ['url' => str_replace('-1.', '-0.', $picture[1]), 'description' => '', 'id' => $id];
} else {
$post['remote_images'][] = ['url' => $picture[1], 'description' => ''];
}
@ -473,7 +480,7 @@ class BBCode
private static function proxyUrl(string $image, int $simplehtml = self::INTERNAL, int $uriid = 0, string $size = ''): string
{
// Only send proxied pictures to API and for internal display
if (!in_array($simplehtml, [self::INTERNAL, self::API])) {
if (!in_array($simplehtml, [self::INTERNAL, self::MASTODON_API, self::TWITTER_API])) {
return $image;
} elseif ($uriid > 0) {
return Post\Link::getByLink($uriid, $image, $size);
@ -653,7 +660,7 @@ class BBCode
* @return string
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function convertAttachment(string $text, int $simplehtml = self::INTERNAL, bool $tryoembed = true, array $data = [], int $uriid = 0): string
public static function convertAttachment(string $text, int $simplehtml = self::INTERNAL, bool $tryoembed = true, array $data = [], int $uriid = 0, int $preview_mode = self::PREVIEW_LARGE): string
{
DI::profiler()->startRecording('rendering');
$data = $data ?: self::getAttachmentData($text);
@ -688,12 +695,18 @@ class BBCode
$return = sprintf('<div class="type-%s">', $data['type']);
}
if ($preview_mode == self::PREVIEW_NO_IMAGE) {
unset($data['image']);
unset($data['preview']);
}
if (!empty($data['title']) && !empty($data['url'])) {
$preview_class = $preview_mode == self::PREVIEW_LARGE ? 'attachment-image' : 'attachment-preview';
if (!empty($data['image']) && empty($data['text']) && ($data['type'] == 'photo')) {
$return .= sprintf('<a href="%s" target="_blank" rel="noopener noreferrer"><img src="%s" alt="" title="%s" class="attachment-image" /></a>', $data['url'], self::proxyUrl($data['image'], $simplehtml, $uriid), $data['title']);
$return .= sprintf('<a href="%s" target="_blank" rel="noopener noreferrer"><img src="%s" alt="" title="%s" class="' . $preview_class . '" /></a>', $data['url'], self::proxyUrl($data['image'], $simplehtml, $uriid), $data['title']);
} else {
if (!empty($data['image'])) {
$return .= sprintf('<a href="%s" target="_blank" rel="noopener noreferrer"><img src="%s" alt="" title="%s" class="attachment-image" /></a><br>', $data['url'], self::proxyUrl($data['image'], $simplehtml, $uriid), $data['title']);
$return .= sprintf('<a href="%s" target="_blank" rel="noopener noreferrer"><img src="%s" alt="" title="%s" class="' . $preview_class . '" /></a><br>', $data['url'], self::proxyUrl($data['image'], $simplehtml, $uriid), $data['title']);
} elseif (!empty($data['preview'])) {
$return .= sprintf('<a href="%s" target="_blank" rel="noopener noreferrer"><img src="%s" alt="" title="%s" class="attachment-preview" /></a><br>', $data['url'], self::proxyUrl($data['preview'], $simplehtml, $uriid), $data['title']);
}
@ -1023,6 +1036,7 @@ class BBCode
{
DI::profiler()->startRecording('rendering');
if (preg_match('~(.*?)\[share](.*)\[/share]~ism', $text, $matches)) {
DI::profiler()->stopRecording();
return [
'author' => '',
'profile' => '',
@ -1069,6 +1083,17 @@ class BBCode
return $attributes;
}
/**
* Remove the share block
*
* @param string $body
* @return string
*/
public static function removeSharedData(string $body): string
{
return trim(preg_replace("/\s*\[share.*?\].*?\[\/share\]\s*/ism", '', $body));
}
/**
* This function converts a [share] block to text according to a provided callback function whose signature is:
*
@ -1118,7 +1143,7 @@ class BBCode
);
DI::profiler()->stopRecording();
return $return;
return trim($return);
}
/**
@ -1178,7 +1203,8 @@ class BBCode
$mention = $attributes['author'] . ' (' . ($author_contact['addr'] ?? '') . ')';
switch ($simplehtml) {
case self::API:
case self::MASTODON_API:
case self::TWITTER_API:
$text = ($is_quote_share? '<br>' : '') .
'<b><a href="' . $attributes['link'] . '">' . html_entity_decode('&#x2672;', ENT_QUOTES, 'UTF-8') . ' ' . $author_contact['addr'] . "</a>:</b><br>\n" .
'<blockquote class="shared_content" dir="auto">' . $content . '</blockquote>';
@ -1293,7 +1319,7 @@ class BBCode
/**
* Callback: Expands links from given $match array
*
* @param arrat $match Array with link match
* @param array $match Array with link match
* @return string BBCode
*/
private static function expandLinksCallback(array $match): string
@ -1308,7 +1334,7 @@ class BBCode
/**
* Callback: Cleans picture links
*
* @param arrat $match Array with link match
* @param array $match Array with link match
* @return string BBCode
*/
private static function cleanPictureLinksCallback(array $match): string
@ -1574,8 +1600,8 @@ class BBCode
$text = str_replace(">", "&gt;", $text);
// remove some newlines before the general conversion
$text = preg_replace("/\s?\[share(.*?)\]\s?(.*?)\s?\[\/share\]\s?/ism", "[share$1]$2[/share]", $text);
$text = preg_replace("/\s?\[quote(.*?)\]\s?(.*?)\s?\[\/quote\]\s?/ism", "[quote$1]$2[/quote]", $text);
$text = preg_replace("/\s?\[share(.*?)\]\s?(.*?)\s?\[\/share\]\s?/ism", "\n[share$1]$2[/share]\n", $text);
$text = preg_replace("/\s?\[quote(.*?)\]\s?(.*?)\s?\[\/quote\]\s?/ism", "\n[quote$1]$2[/quote]\n", $text);
// when the content is meant exporting to other systems then remove the avatar picture since this doesn't really look good on these systems
if (!$try_oembed) {
@ -1622,7 +1648,7 @@ class BBCode
/// @todo Have a closer look at the different html modes
// Handle attached links or videos
if (in_array($simple_html, [self::API, self::ACTIVITYPUB])) {
if (in_array($simple_html, [self::MASTODON_API, self::TWITTER_API, self::ACTIVITYPUB])) {
$text = self::removeAttachment($text);
} elseif (!in_array($simple_html, [self::INTERNAL, self::EXTERNAL, self::CONNECTORS])) {
$text = self::removeAttachment($text, true);
@ -1959,16 +1985,23 @@ class BBCode
$text = preg_replace("/([#])\[url\=(.*?)\](.*?)\[\/url\]/ism",
'<a href="$2" class="mention hashtag" rel="tag">$1<span>$3</span></a>',
$text);
} elseif (in_array($simple_html, [self::INTERNAL, self::EXTERNAL, self::API])) {
} elseif (in_array($simple_html, [self::INTERNAL, self::EXTERNAL, self::TWITTER_API])) {
$text = preg_replace("/([@!])\[url\=(.*?)\](.*?)\[\/url\]/ism",
'<bdi>$1<a href="$2" class="userinfo mention" title="$3">$3</a></bdi>',
$text);
} elseif ($simple_html == self::MASTODON_API) {
$text = preg_replace("/([@!])\[url\=(.*?)\](.*?)\[\/url\]/ism",
'<a class="u-url mention status-link" href="$2" rel="nofollow noopener noreferrer" target="_blank" title="$3">$1<span>$3</span></a>',
$text);
$text = preg_replace("/([#])\[url\=(.*?)\](.*?)\[\/url\]/ism",
'<a class="mention hashtag status-link" href="$2" rel="tag">$1<span>$3</span></a>',
$text);
} else {
$text = preg_replace("/([#@!])\[url\=(.*?)\](.*?)\[\/url\]/ism", '$1$3', $text);
}
if (!$for_plaintext) {
if (in_array($simple_html, [self::OSTATUS, self::API, self::ACTIVITYPUB])) {
if (in_array($simple_html, [self::OSTATUS, self::MASTODON_API, self::TWITTER_API, self::ACTIVITYPUB])) {
$text = preg_replace_callback("/\[url\](.*?)\[\/url\]/ism", 'self::convertUrlForActivityPubCallback', $text);
$text = preg_replace_callback("/\[url\=(.*?)\](.*?)\[\/url\]/ism", 'self::convertUrlForActivityPubCallback', $text);
}
@ -2069,7 +2102,7 @@ class BBCode
$text = preg_replace('/\<([^>]*?)(src|href)=(.*?)\&amp\;(.*?)\>/ism', '<$1$2=$3&$4>', $text);
// sanitizes src attributes (http and redir URLs for displaying in a web page, cid used for inline images in emails)
$allowed_src_protocols = ['//', 'http://', 'https://', 'redir/', 'cid:'];
$allowed_src_protocols = ['//', 'http://', 'https://', 'contact/redir/', 'cid:'];
array_walk($allowed_src_protocols, function(&$value) { $value = preg_quote($value, '#');});
@ -2084,7 +2117,7 @@ class BBCode
$allowed_link_protocols[] = '//';
$allowed_link_protocols[] = 'http://';
$allowed_link_protocols[] = 'https://';
$allowed_link_protocols[] = 'redir/';
$allowed_link_protocols[] = 'contact/redir/';
array_walk($allowed_link_protocols, function(&$value) { $value = preg_quote($value, '#');});

View file

@ -840,7 +840,7 @@ class HTML
if ($redirect) {
$url = Contact::magicLinkByContact($contact);
if (strpos($url, 'redir/') === 0) {
if (strpos($url, 'contact/redir/') === 0) {
$sparkle = ' sparkle';
}
}

View file

@ -21,6 +21,8 @@
namespace Friendica\Content\Text;
use Friendica\Core\Logger;
use Friendica\Core\System;
use Friendica\DI;
use Friendica\Model\Contact;
@ -106,8 +108,18 @@ class Markdown
* So we'll use that to convert to HTML, then convert the HTML back to bbcode,
* and then clean up a few Diaspora specific constructs.
*/
public static function toBBCode($s)
public static function toBBCode($s): string
{
// @TODO Temporary until we find the source of the null value to finally set the correct type-hint
if (is_null($s)) {
Logger::warning('Received null value', ['callstack' => System::callstack()]);
return '';
}
if (!$s) {
return $s;
}
DI::profiler()->startRecording('rendering');
// The parser cannot handle paragraphs correctly

View file

@ -114,9 +114,8 @@ class Plaintext
* @return array Same array structure than \Friendica\Content\Text\BBCode::getAttachedData
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
* @see \Friendica\Content\Text\BBCode::getAttachedData
*
*/
public static function getPost($item, $limit = 0, $includedlinks = false, $htmlmode = BBCode::API, $target_network = '')
public static function getPost(array $item, int $limit = 0, bool $includedlinks = false, int $htmlmode = BBCode::MASTODON_API, string $target_network = '')
{
// Remove hashtags
$URLSearchString = '^\[\]';

View file

@ -66,7 +66,7 @@ class Widget
$global_dir = Search::getGlobalDirectory();
if (DI::config()->get('system', 'invitation_only')) {
$x = intval(DI::pConfig()->get(local_user(), 'system', 'invites_remaining'));
$x = intval(DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'system', 'invites_remaining'));
if ($x || DI::app()->isSiteAdmin()) {
DI::page()['aside'] .= '<div class="side-link widget" id="side-invite-remain">'
. DI::l10n()->tt('%d invitation available', '%d invitations available', $x)
@ -195,7 +195,7 @@ class Widget
*/
public static function groups(string $baseurl, string $selected = ''): string
{
if (!local_user()) {
if (!DI::userSession()->getLocalUserId()) {
return '';
}
@ -204,7 +204,7 @@ class Widget
'ref' => $group['id'],
'name' => $group['name']
];
}, Group::getByUserId(local_user()));
}, Group::getByUserId(DI::userSession()->getLocalUserId()));
return self::filter(
'group',
@ -227,7 +227,7 @@ class Widget
*/
public static function contactRels(string $baseurl, string $selected = ''): string
{
if (!local_user()) {
if (!DI::userSession()->getLocalUserId()) {
return '';
}
@ -258,13 +258,13 @@ class Widget
*/
public static function networks(string $baseurl, string $selected = ''): string
{
if (!local_user()) {
if (!DI::userSession()->getLocalUserId()) {
return '';
}
$networks = self::unavailableNetworks();
$query = "`uid` = ? AND NOT `deleted` AND `network` != '' AND NOT `network` IN (" . substr(str_repeat("?, ", count($networks)), 0, -2) . ")";
$condition = array_merge([$query], array_merge([local_user()], $networks));
$condition = array_merge([$query], array_merge([DI::userSession()->getLocalUserId()], $networks));
$r = DBA::select('contact', ['network'], $condition, ['group_by' => ['network'], 'order' => ['network']]);
@ -299,12 +299,12 @@ class Widget
*/
public static function fileAs(string $baseurl, string $selected = ''): string
{
if (!local_user()) {
if (!DI::userSession()->getLocalUserId()) {
return '';
}
$terms = [];
foreach (Post\Category::getArray(local_user(), Post\Category::FILE) as $savedFolderName) {
foreach (Post\Category::getArray(DI::userSession()->getLocalUserId(), Post\Category::FILE) as $savedFolderName) {
$terms[] = ['ref' => $savedFolderName, 'name' => $savedFolderName];
}
@ -361,11 +361,11 @@ class Widget
*/
public static function commonFriendsVisitor(int $uid, string $nickname): string
{
if (local_user() == $uid) {
if (DI::userSession()->getLocalUserId() == $uid) {
return '';
}
$visitorPCid = local_user() ? Contact::getPublicIdByUserId(local_user()) : remote_user();
$visitorPCid = DI::userSession()->getPublicContactId() ?: DI::userSession()->getRemoteUserId();
if (!$visitorPCid) {
return '';
}

View file

@ -34,12 +34,14 @@ class CalendarExport
{
/**
* Get the events widget.
*
* @param int $uid
*
* @return string Formated HTML of the calendar widget.
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function getHTML(int $uid = 0) {
public static function getHTML(int $uid = 0): string
{
if (empty($uid)) {
return '';
}
@ -49,11 +51,11 @@ class CalendarExport
return '';
}
$tpl = Renderer::getMarkupTemplate("widget/events.tpl");
$tpl = Renderer::getMarkupTemplate('widget/events.tpl');
$return = Renderer::replaceMacros($tpl, [
'$etitle' => DI::l10n()->t("Export"),
'$export_ical' => DI::l10n()->t("Export calendar as ical"),
'$export_csv' => DI::l10n()->t("Export calendar as csv"),
'$etitle' => DI::l10n()->t('Export'),
'$export_ical' => DI::l10n()->t('Export calendar as ical'),
'$export_csv' => DI::l10n()->t('Export calendar as csv'),
'$user' => $user['nickname']
]);

View file

@ -42,9 +42,9 @@ class ContactBlock
*
* @template widget/contacts.tpl
* @hook contact_block_end (contacts=>array, output=>string)
* @return string
* @return string Formatted HTML code or empty string
*/
public static function getHTML(array $profile, int $visitor_uid = null)
public static function getHTML(array $profile, int $visitor_uid = null): string
{
$o = '';
@ -66,13 +66,13 @@ class ContactBlock
$contacts = [];
$total = DBA::count('contact', [
'uid' => $profile['uid'],
'self' => false,
'uid' => $profile['uid'],
'self' => false,
'blocked' => false,
'pending' => false,
'hidden' => false,
'hidden' => false,
'archive' => false,
'failed' => false,
'failed' => false,
'network' => [Protocol::DFRN, Protocol::ACTIVITYPUB, Protocol::OSTATUS, Protocol::DIASPORA, Protocol::FEED],
]);
@ -89,15 +89,17 @@ class ContactBlock
}
$personal_contacts = DBA::selectToArray('contact', ['uri-id'], [
'uid' => $profile['uid'],
'self' => false,
'uid' => $profile['uid'],
'self' => false,
'blocked' => false,
'pending' => false,
'hidden' => false,
'hidden' => false,
'archive' => false,
'rel' => $rel,
'rel' => $rel,
'network' => Protocol::FEDERATED,
], ['limit' => $shown]);
], [
'limit' => $shown,
]);
$contact_uriids = array_column($personal_contacts, 'uri-id');

View file

@ -34,10 +34,10 @@ class SavedSearches
* @return string
* @throws \Exception
*/
public static function getHTML($return_url, $search = '')
public static function getHTML(string $return_url, string $search = ''): string
{
$saved = [];
$saved_searches = DBA::select('search', ['id', 'term'], ['uid' => local_user()]);
$saved_searches = DBA::select('search', ['id', 'term'], ['uid' => DI::userSession()->getLocalUserId()], ['order' => ['term']]);
while ($saved_search = DBA::fetch($saved_searches)) {
$saved[] = [
'id' => $saved_search['id'],

View file

@ -46,7 +46,7 @@ class TagCloud
* @return string HTML formatted output.
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function getHTML($uid, $count = 0, $owner_id = 0, $flags = '', $type = Tag::HASHTAG)
public static function getHTML(int $uid, int $count = 0, int $owner_id = 0, string $flags = '', int $type = Tag::HASHTAG): string
{
$o = '';
$r = self::tagadelic($uid, $count, $owner_id, $flags, $type);
@ -56,17 +56,17 @@ class TagCloud
$tags = [];
foreach ($r as $rr) {
$tag['level'] = $rr[2];
$tag['url'] = $url . '?tag=' . urlencode($rr[0]);
$tag['name'] = $rr[0];
$tags[] = $tag;
$tags[] = [
'level' => $rr[2],
'url' => $url . '?tag=' . urlencode($rr[0]),
'name' => $rr[0],
];
}
$tpl = Renderer::getMarkupTemplate('widget/tagcloud.tpl');
$o = Renderer::replaceMacros($tpl, [
'$title' => DI::l10n()->t('Tags'),
'$tags' => $tags
'$tags' => $tags
]);
}
return $o;

View file

@ -35,10 +35,11 @@ class TrendingTags
/**
* @param string $content 'global' (all posts) or 'local' (this node's posts only)
* @param int $period Period in hours to consider posts
* @return string
*
* @return string Formatted HTML code
* @throws \Exception
*/
public static function getHTML($content = 'global', int $period = 24)
public static function getHTML(string $content = 'global', int $period = 24): string
{
if ($content == 'local') {
$tags = Tag::getLocalTrendingHashtags($period, 20);
@ -49,8 +50,8 @@ class TrendingTags
$tpl = Renderer::getMarkupTemplate('widget/trending_tags.tpl');
$o = Renderer::replaceMacros($tpl, [
'$title' => DI::l10n()->tt('Trending Tags (last %d hour)', 'Trending Tags (last %d hours)', $period),
'$more' => DI::l10n()->t('More Trending Tags'),
'$tags' => $tags,
'$more' => DI::l10n()->t('More Trending Tags'),
'$tags' => $tags,
]);
return $o;

View file

@ -44,7 +44,7 @@ class VCard
* @template widget/vcard.tpl
* @return string
*/
public static function getHTML(array $contact)
public static function getHTML(array $contact): string
{
if (!isset($contact['network']) || !isset($contact['id'])) {
Logger::warning('Incomplete contact', ['contact' => $contact ?? [], 'callstack' => System::callstack(20)]);
@ -64,13 +64,13 @@ class VCard
$photo = Contact::getPhoto($contact);
if (local_user()) {
if (DI::userSession()->getLocalUserId()) {
if ($contact['uid']) {
$id = $contact['id'];
$rel = $contact['rel'];
$pending = $contact['pending'];
} else {
$pcontact = Contact::selectFirst([], ['uid' => local_user(), 'uri-id' => $contact['uri-id']]);
$pcontact = Contact::selectFirst([], ['uid' => DI::userSession()->getLocalUserId(), 'uri-id' => $contact['uri-id'], 'deleted' => false]);
$id = $pcontact['id'] ?? 0;
$rel = $pcontact['rel'] ?? Contact::NOTHING;
@ -83,9 +83,9 @@ class VCard
if (empty($contact['self']) && Protocol::supportsFollow($contact['network'])) {
if (in_array($rel, [Contact::SHARING, Contact::FRIEND])) {
$unfollow_link = 'unfollow?url=' . urlencode($contact['url']) . '&auto=1';
$unfollow_link = 'contact/unfollow?url=' . urlencode($contact['url']) . '&auto=1';
} elseif (!$pending) {
$follow_link = 'follow?url=' . urlencode($contact['url']) . '&auto=1';
$follow_link = 'contact/follow?url=' . urlencode($contact['url']) . '&auto=1';
}
}

View file

@ -62,7 +62,7 @@ class ACL
$page->registerStylesheet(Theme::getPathForFile('js/friendica-tagsinput/friendica-tagsinput.css'));
$page->registerStylesheet(Theme::getPathForFile('js/friendica-tagsinput/friendica-tagsinput-typeahead.css'));
$contacts = self::getValidMessageRecipientsForUser(local_user());
$contacts = self::getValidMessageRecipientsForUser(DI::userSession()->getLocalUserId());
$tpl = Renderer::getMarkupTemplate('acl/message_recipient.tpl');
$o = Renderer::replaceMacros($tpl, [

View file

@ -127,7 +127,7 @@ class MemcachedCache extends AbstractCache implements ICanCacheInMemory
if ($this->memcached->getResultCode() === Memcached::RES_SUCCESS) {
return $value;
} elseif ($this->memcached->getResultCode() === Memcached::RES_NOTFOUND) {
$this->logger->notice('Try to use unknown key.', ['key' => $key]);
$this->logger->debug('Try to use unknown key.', ['key' => $key]);
return null;
} else {
throw new CachePersistenceException(sprintf('Cannot get cache entry with key %s', $key), new \MemcachedException($this->memcached->getResultMessage(), $this->memcached->getResultCode()));

View file

@ -51,7 +51,7 @@ interface IManageConfigValues
*
* @param string $cat The category of the configuration value
* @param string $key The configuration key to query
* @param mixed $default_value optional, The value to return if key is not set (default: null)
* @param mixed $default_value Deprecated, use `Config->get($cat, $key, null, $refresh) ?? $default_value` instead
* @param boolean $refresh optional, If true the config is loaded from the db and not from the cache (default: false)
*
* @return mixed Stored value or null if it does not exist

View file

@ -23,6 +23,7 @@ namespace Friendica\Core;
use Dice\Dice;
use Friendica;
use Friendica\App;
/**
* Description of Console
@ -133,7 +134,7 @@ HELP;
$command = null;
if ($this->getOption('version')) {
$this->out('Friendica Console version ' . FRIENDICA_VERSION);
$this->out('Friendica Console version ' . App::VERSION);
return 0;
} elseif ((count($this->options) === 0 || $this->getOption($this->customHelpOptions) === true || $this->getOption($this->customHelpOptions) === 1) && count($this->args) === 0

View file

@ -197,7 +197,7 @@ class Installer
$result = DBStructure::install();
if ($result) {
$txt = DI::l10n()->t('You may need to import the file "database.sql" manually using phpmyadmin or mysql.') . EOL;
$txt = DI::l10n()->t('You may need to import the file "database.sql" manually using phpmyadmin or mysql.') . '<br />';
$txt .= DI::l10n()->t('Please see the file "doc/INSTALL.md".');
$this->addCheck($txt, false, true, htmlentities($result, ENT_COMPAT, 'UTF-8'));
@ -259,9 +259,9 @@ class Installer
$help = "";
if (!$passed) {
$help .= DI::l10n()->t('Could not find a command line version of PHP in the web server PATH.') . EOL;
$help .= DI::l10n()->t("If you don't have a command line version of PHP installed on your server, you will not be able to run the background processing. See <a href='https://github.com/friendica/friendica/blob/stable/doc/Install.md#set-up-the-worker'>'Setup the worker'</a>") . EOL;
$help .= EOL . EOL;
$help .= DI::l10n()->t('Could not find a command line version of PHP in the web server PATH.') . '<br />';
$help .= DI::l10n()->t("If you don't have a command line version of PHP installed on your server, you will not be able to run the background processing. See <a href='https://github.com/friendica/friendica/blob/stable/doc/Install.md#set-up-the-worker'>'Setup the worker'</a>") . '<br />';
$help .= '<br /><br />';
$tpl = Renderer::getMarkupTemplate('field_input.tpl');
/// @todo Separate backend Installer class and presentation layer/view
$help .= Renderer::replaceMacros($tpl, [
@ -279,7 +279,7 @@ class Installer
[$result] = explode("\n", $result);
$help = "";
if (!$passed2) {
$help .= DI::l10n()->t("PHP executable is not the php cli binary \x28could be cgi-fgci version\x29") . EOL;
$help .= DI::l10n()->t("PHP executable is not the php cli binary \x28could be cgi-fgci version\x29") . '<br />';
$help .= DI::l10n()->t('Found PHP version: ') . "<tt>$result</tt>";
}
$this->addCheck(DI::l10n()->t('PHP cli binary'), $passed2, true, $help);
@ -295,7 +295,7 @@ class Installer
$passed3 = $result == $str;
$help = "";
if (!$passed3) {
$help .= DI::l10n()->t('The command line version of PHP on your system does not have "register_argc_argv" enabled.') . EOL;
$help .= DI::l10n()->t('The command line version of PHP on your system does not have "register_argc_argv" enabled.') . '<br />';
$help .= DI::l10n()->t('This is required for message delivery to work.');
} else {
$this->phppath = $phppath;
@ -333,7 +333,7 @@ class Installer
// Get private key
if (!$res) {
$help .= DI::l10n()->t('Error: the "openssl_pkey_new" function on this system is not able to generate encryption keys') . EOL;
$help .= DI::l10n()->t('Error: the "openssl_pkey_new" function on this system is not able to generate encryption keys') . '<br />';
$help .= DI::l10n()->t('If running under Windows, please see "http://www.php.net/manual/en/openssl.installation.php".');
$status = false;
}
@ -511,10 +511,10 @@ class Installer
(!file_exists('config/local.config.php') && !is_writable('.'))) {
$status = false;
$help = DI::l10n()->t('The web installer needs to be able to create a file called "local.config.php" in the "config" folder of your web server and it is unable to do so.') . EOL;
$help .= DI::l10n()->t('This is most often a permission setting, as the web server may not be able to write files in your folder - even if you can.') . EOL;
$help .= DI::l10n()->t('At the end of this procedure, we will give you a text to save in a file named local.config.php in your Friendica "config" folder.') . EOL;
$help .= DI::l10n()->t('You can alternatively skip this procedure and perform a manual installation. Please see the file "doc/INSTALL.md" for instructions.') . EOL;
$help = DI::l10n()->t('The web installer needs to be able to create a file called "local.config.php" in the "config" folder of your web server and it is unable to do so.') . '<br />';
$help .= DI::l10n()->t('This is most often a permission setting, as the web server may not be able to write files in your folder - even if you can.') . '<br />';
$help .= DI::l10n()->t('At the end of this procedure, we will give you a text to save in a file named local.config.php in your Friendica "config" folder.') . '<br />';
$help .= DI::l10n()->t('You can alternatively skip this procedure and perform a manual installation. Please see the file "doc/INSTALL.md" for instructions.') . '<br />';
}
$this->addCheck(DI::l10n()->t('config/local.config.php is writable'), $status, false, $help);
@ -537,10 +537,10 @@ class Installer
if (!is_writable('view/smarty3')) {
$status = false;
$help = DI::l10n()->t('Friendica uses the Smarty3 template engine to render its web views. Smarty3 compiles templates to PHP to speed up rendering.') . EOL;
$help .= DI::l10n()->t('In order to store these compiled templates, the web server needs to have write access to the directory view/smarty3/ under the Friendica top level folder.') . EOL;
$help .= DI::l10n()->t("Please ensure that the user that your web server runs as \x28e.g. www-data\x29 has write access to this folder.") . EOL;
$help .= DI::l10n()->t("Note: as a security measure, you should give the web server write access to view/smarty3/ only--not the template files \x28.tpl\x29 that it contains.") . EOL;
$help = DI::l10n()->t('Friendica uses the Smarty3 template engine to render its web views. Smarty3 compiles templates to PHP to speed up rendering.') . '<br />';
$help .= DI::l10n()->t('In order to store these compiled templates, the web server needs to have write access to the directory view/smarty3/ under the Friendica top level folder.') . '<br />';
$help .= DI::l10n()->t("Please ensure that the user that your web server runs as \x28e.g. www-data\x29 has write access to this folder.") . '<br />';
$help .= DI::l10n()->t("Note: as a security measure, you should give the web server write access to view/smarty3/ only--not the template files \x28.tpl\x29 that it contains.") . '<br />';
}
$this->addCheck(DI::l10n()->t('view/smarty3 is writable'), $status, true, $help);
@ -571,7 +571,7 @@ class Installer
if ($fetchResult->getReturnCode() != 204) {
$status = false;
$help = DI::l10n()->t('Url rewrite in .htaccess seems not working. Make sure you copied .htaccess-dist to .htaccess.') . EOL;
$help = DI::l10n()->t('Url rewrite in .htaccess seems not working. Make sure you copied .htaccess-dist to .htaccess.') . '<br />';
$help .= DI::l10n()->t('In some circumstances (like running inside containers), you can skip this error.');
$error_msg = [];
$error_msg['head'] = DI::l10n()->t('Error message from Curl when fetching');

View file

@ -188,10 +188,10 @@ class L10n
{
$lang_variable = $server['HTTP_ACCEPT_LANGUAGE'] ?? null;
$acceptedLanguages = preg_split('/,\s*/', $lang_variable);
if (empty($acceptedLanguages)) {
if (empty($lang_variable)) {
$acceptedLanguages = [];
} else {
$acceptedLanguages = preg_split('/,\s*/', $lang_variable);
}
// Add get as absolute quality accepted language (except this language isn't valid)

View file

@ -22,19 +22,16 @@
namespace Friendica\Core\Logger\Factory;
use Friendica\Core\Config\Capability\IManageConfigValues;
use Friendica\Core\Logger\Exception\LoggerException;
use Friendica\Core;
use Friendica\Core\Logger\Exception\LogLevelException;
use Friendica\Database\Database;
use Friendica\Network\HTTPException\InternalServerErrorException;
use Friendica\Util\FileSystem;
use Friendica\Core\Logger\Util\Introspection;
use Friendica\Core\Logger\Type\Monolog\DevelopHandler;
use Friendica\Core\Logger\Type\Monolog\IntrospectionProcessor;
use Friendica\Core\Logger\Type\ProfilerLogger;
use Friendica\Core\Logger\Type\StreamLogger;
use Friendica\Core\Logger\Type\SyslogLogger;
use Friendica\Util\Profiler;
use Monolog;
use Psr\Log\LoggerInterface;
use Psr\Log\LogLevel;
use Psr\Log\NullLogger;
@ -60,9 +57,15 @@ class Logger
/** @var string The log-channel (app, worker, ...) */
private $channel;
public function __construct(string $channel)
public function __construct(string $channel, bool $includeAddon = true)
{
$this->channel = $channel;
/// @fixme clean solution = Making Addon & Hook dynamic and load them inside the constructor, so there's no custom load logic necessary anymore
if ($includeAddon) {
Core\Addon::loadAddons();
Core\Hook::loadHooks();
}
}
/**
@ -88,35 +91,9 @@ class Logger
$minLevel = $minLevel ?? $config->get('system', 'loglevel');
$loglevel = self::mapLegacyConfigDebugLevel((string)$minLevel);
switch ($config->get('system', 'logger_config', 'stream')) {
case 'monolog':
$loggerTimeZone = new \DateTimeZone('UTC');
Monolog\Logger::setTimezone($loggerTimeZone);
$logger = new Monolog\Logger($this->channel);
$logger->pushProcessor(new Monolog\Processor\PsrLogMessageProcessor());
$logger->pushProcessor(new Monolog\Processor\ProcessIdProcessor());
$logger->pushProcessor(new Monolog\Processor\UidProcessor());
$logger->pushProcessor(new IntrospectionProcessor($introspection, LogLevel::DEBUG));
$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)) {
try {
static::addStreamHandler($logger, $stream, $loglevel);
} catch (\Throwable $e) {
// No Logger ..
try {
$logger = new SyslogLogger($this->channel, $introspection, $loglevel);
} catch (\Throwable $e) {
// No logger ...
$logger = new NullLogger();
}
}
}
break;
$name = $config->get('system', 'logger_config', 'stream');
switch ($name) {
case 'syslog':
try {
$logger = new SyslogLogger($this->channel, $introspection, $loglevel, $config->get('system', 'syslog_flags', SyslogLogger::DEFAULT_FLAGS), $config->get('system', 'syslog_facility', SyslogLogger::DEFAULT_FACILITY));
@ -132,29 +109,48 @@ class Logger
case 'stream':
default:
$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)) {
try {
$logger = new StreamLogger($this->channel, $stream, $introspection, $fileSystem, $loglevel);
} catch (LogLevelException $exception) {
// If there's a wrong config value for loglevel, try again with standard
$logger = $this->create($database, $config, $profiler, $fileSystem, LogLevel::NOTICE);
$logger->warning('Invalid loglevel set in config.', ['loglevel' => $loglevel]);
} catch (\Throwable $t) {
// No logger ...
$logger = new NullLogger();
}
} else {
try {
$logger = new SyslogLogger($this->channel, $introspection, $loglevel);
} catch (LogLevelException $exception) {
// If there's a wrong config value for loglevel, try again with standard
$logger = $this->create($database, $config, $profiler, $fileSystem, LogLevel::NOTICE);
$logger->warning('Invalid loglevel set in config.', ['loglevel' => $loglevel]);
} catch (\Throwable $e) {
// No logger ...
$logger = new NullLogger();
$data = [
'name' => $name,
'channel' => $this->channel,
'introspection' => $introspection,
'loglevel' => $loglevel,
'logger' => null,
];
try {
Core\Hook::callAll('logger_instance', $data);
} catch (InternalServerErrorException $exception) {
$data['logger'] = null;
}
if (($data['logger'] ?? null) instanceof LoggerInterface) {
$logger = $data['logger'];
}
if (empty($logger)) {
$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)) {
try {
$logger = new StreamLogger($this->channel, $stream, $introspection, $fileSystem, $loglevel);
} catch (LogLevelException $exception) {
// If there's a wrong config value for loglevel, try again with standard
$logger = $this->create($database, $config, $profiler, $fileSystem, LogLevel::NOTICE);
$logger->warning('Invalid loglevel set in config.', ['loglevel' => $loglevel]);
} catch (\Throwable $t) {
// No logger ...
$logger = new NullLogger();
}
} else {
try {
$logger = new SyslogLogger($this->channel, $introspection, $loglevel);
} catch (LogLevelException $exception) {
// If there's a wrong config value for loglevel, try again with standard
$logger = $this->create($database, $config, $profiler, $fileSystem, LogLevel::NOTICE);
$logger->warning('Invalid loglevel set in config.', ['loglevel' => $loglevel]);
} catch (\Throwable $e) {
// No logger ...
$logger = new NullLogger();
}
}
}
break;
@ -197,27 +193,11 @@ class Logger
return new NullLogger();
}
$loggerTimeZone = new \DateTimeZone('UTC');
Monolog\Logger::setTimezone($loggerTimeZone);
$introspection = new Introspection(self::$ignoreClassList);
switch ($config->get('system', 'logger_config', 'stream')) {
$name = $config->get('system', 'logger_config', 'stream');
case 'monolog':
$loggerTimeZone = new \DateTimeZone('UTC');
Monolog\Logger::setTimezone($loggerTimeZone);
$logger = new Monolog\Logger(self::DEV_CHANNEL);
$logger->pushProcessor(new Monolog\Processor\PsrLogMessageProcessor());
$logger->pushProcessor(new Monolog\Processor\ProcessIdProcessor());
$logger->pushProcessor(new Monolog\Processor\UidProcessor());
$logger->pushProcessor(new IntrospectionProcessor($introspection, LogLevel::DEBUG));
$logger->pushHandler(new DevelopHandler($developerIp));
static::addStreamHandler($logger, $stream, LogLevel::DEBUG);
break;
switch ($name) {
case 'syslog':
$logger = new SyslogLogger(self::DEV_CHANNEL, $introspection, LogLevel::DEBUG);
@ -225,6 +205,23 @@ class Logger
case 'stream':
default:
$data = [
'name' => $name,
'channel' => self::DEV_CHANNEL,
'introspection' => $introspection,
'loglevel' => LogLevel::DEBUG,
'logger' => null,
];
try {
Core\Hook::callAll('logger_instance', $data);
} catch (InternalServerErrorException $exception) {
$data['logger'] = null;
}
if (($data['logger'] ?? null) instanceof LoggerInterface) {
return $data['logger'];
}
$logger = new StreamLogger(self::DEV_CHANNEL, $stream, $introspection, $fileSystem, LogLevel::DEBUG);
break;
}
@ -273,38 +270,4 @@ class Logger
return $level;
}
}
/**
* Adding a handler to a given logger instance
*
* @param LoggerInterface $logger The logger instance
* @param mixed $stream The stream which handles the logger output
* @param string $level The level, for which this handler at least should handle logging
*
* @return void
*
* @throws LoggerException
*/
public static function addStreamHandler(LoggerInterface $logger, $stream, string $level = LogLevel::NOTICE)
{
if ($logger instanceof Monolog\Logger) {
$loglevel = Monolog\Logger::toMonologLevel($level);
// fallback to notice if an invalid loglevel is set
if (!is_int($loglevel)) {
$loglevel = LogLevel::NOTICE;
}
try {
$fileHandler = new Monolog\Handler\StreamHandler($stream, $loglevel);
$formatter = new Monolog\Formatter\LineFormatter("%datetime% %channel% [%level_name%]: %message% %context% %extra%\n");
$fileHandler->setFormatter($formatter);
$logger->pushHandler($fileHandler);
} catch (\Exception $exception) {
throw new LoggerException('Cannot create Monolog Logger.', $exception);
}
}
}
}

View file

@ -1,76 +0,0 @@
<?php
/**
* @copyright Copyright (C) 2010-2022, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
namespace Friendica\Core\Logger\Type\Monolog;
use Friendica\App\Request;
use Monolog\Handler;
use Monolog\Logger;
/**
* Simple handler for Friendica developers to use for deeper logging
*
* If you want to debug only interactions from your IP or the IP of a remote server for federation debug,
* you'll use Logger::develop() for the duration of your work, and you clean it up when you're done before submitting your PR.
*/
class DevelopHandler extends Handler\AbstractHandler
{
/**
* @var string The IP of the developer who wants to debug
*/
private $developerIp;
/**
* @var string The IP of the current request
*/
private $remoteAddress;
/**
* @param Request $request The current http request
* @param string $developerIp The IP of the developer who wants to debug
* @param int $level The minimum logging level at which this handler will be triggered
* @param bool $bubble Whether the messages that are handled can bubble up the stack or not
*/
public function __construct(Request $request, $developerIp, int $level = Logger::DEBUG, bool $bubble = true)
{
parent::__construct($level, $bubble);
$this->developerIp = $developerIp;
$this->remoteAddress = $request->getRemoteAddress();
}
/**
* {@inheritdoc}
*/
public function handle(array $record): bool
{
if (!$this->isHandling($record)) {
return false;
}
/// Just in case the remote IP is the same as the developer IP log the output
if (!is_null($this->developerIp) && $this->remoteAddress != $this->developerIp) {
return false;
}
return false === $this->bubble;
}
}

View file

@ -1,62 +0,0 @@
<?php
/**
* @copyright Copyright (C) 2010-2022, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
namespace Friendica\Core\Logger\Type\Monolog;
use Friendica\Core\Logger\Util\Introspection;
use Monolog\Logger;
use Monolog\Processor\ProcessorInterface;
/**
* Injects line/file//function where the log message came from
*/
class IntrospectionProcessor implements ProcessorInterface
{
private $level;
private $introspection;
/**
* @param Introspection $introspection Holds the Introspection of the current call
* @param string|int $level The minimum logging level at which this Processor will be triggered
*/
public function __construct(Introspection $introspection, $level = Logger::DEBUG)
{
$this->level = Logger::toMonologLevel($level);
$introspection->addClasses(['Monolog\\']);
$this->introspection = $introspection;
}
public function __invoke(array $record): array
{
// return if the level is not high enough
if ($record['level'] < $this->level) {
return $record;
}
// we should have the call source now
$record['extra'] = array_merge(
$record['extra'],
$this->introspection->getRecord()
);
return $record;
}
}

View file

@ -5,7 +5,6 @@ This namespace contains the different implementations of a Logger.
### Configuration guideline
The following settings are possible for `logger_config`:
- `monolog`: A Logging framework with lots of additions (see [Monolog](https://github.com/Seldaek/monolog/)). There are just Friendica additions inside the Monolog directory
- [`stream`](StreamLogger.php): A small logger for files or streams
- [`syslog`](SyslogLogger.php): Prints the logging output into the syslog

View file

@ -51,7 +51,7 @@ interface IManagePersonalConfigValues
* @param int $uid The user_id
* @param string $cat The category of the configuration value
* @param string $key The configuration key to query
* @param mixed $default_value optional, The value to return if key is not set (default: null)
* @param mixed $default_value Deprecated, use `PConfig->get($uid, $cat, $key, null, $refresh) ?? $default_value` instead
* @param boolean $refresh optional, If true the config is loaded from the db and not from the cache (default: false)
*
* @return mixed Stored value or null if it does not exist

View file

@ -22,6 +22,7 @@
namespace Friendica\Core;
use Friendica\Database\DBA;
use Friendica\Model\Item;
use Friendica\Model\User;
use Friendica\Network\HTTPException;
use Friendica\Protocol\Activity;
@ -139,7 +140,7 @@ class Protocol
// create a follow slap
$item = [
'verb' => Activity::FOLLOW,
'gravity' => GRAVITY_ACTIVITY,
'gravity' => Item::GRAVITY_ACTIVITY,
'follow' => $contact['url'],
'body' => '',
'title' => '',
@ -181,7 +182,8 @@ class Protocol
public static function unfollow(array $contact, array $user): ?bool
{
if (empty($contact['network'])) {
throw new \InvalidArgumentException('Missing network key in contact array');
Logger::notice('Contact has got no network, we quit here', ['id' => $contact['id']]);
return null;
}
$protocol = $contact['network'];
@ -191,18 +193,21 @@ class Protocol
if (in_array($protocol, [Protocol::OSTATUS, Protocol::DFRN])) {
// create an unfollow slap
$item = [];
$item['verb'] = Activity::O_UNFOLLOW;
$item['gravity'] = GRAVITY_ACTIVITY;
$item['follow'] = $contact['url'];
$item['body'] = '';
$item['title'] = '';
$item['guid'] = '';
$item['uri-id'] = 0;
$item = [
'verb' => Activity::O_UNFOLLOW,
'gravity' => Item::GRAVITY_ACTIVITY,
'follow' => $contact['url'],
'body' => '',
'title' => '',
'guid' => '',
'uri-id' => 0,
];
$slap = OStatus::salmon($item, $user);
if (empty($contact['notify'])) {
throw new \InvalidArgumentException('Missing expected "notify" key in OStatus/DFRN contact');
Logger::notice('OStatus/DFRN Contact is missing notify, we quit here', ['id' => $contact['id']]);
return null;
}
return Salmon::slapper($user, $contact['notify'], $slap) === 0;

View file

@ -48,19 +48,19 @@ class Renderer
* beyond are used.
*/
public static $theme = [
'videowidth' => 425,
'videoheight' => 350,
'stylesheet' => '',
'videowidth' => 425,
'videoheight' => 350,
'stylesheet' => '',
'template_engine' => 'smarty3',
];
private static $ldelim = [
'internal' => '',
'smarty3' => '{{'
'smarty3' => '{{'
];
private static $rdelim = [
'internal' => '',
'smarty3' => '}}'
'smarty3' => '}}'
];
/**
@ -101,10 +101,10 @@ class Renderer
* @param string $file Template to load.
* @param string $subDir Subdirectory (Optional)
*
* @return string template.
* @return string Template
* @throws ServiceUnavailableException
*/
public static function getMarkupTemplate($file, $subDir = '')
public static function getMarkupTemplate(string $file, string $subDir = ''): string
{
DI::profiler()->startRecording('file');
$t = self::getTemplateEngine();
@ -128,9 +128,11 @@ class Renderer
* Register template engine class
*
* @param string $class
*
* @return void
* @throws ServiceUnavailableException
*/
public static function registerTemplateEngine($class)
public static function registerTemplateEngine(string $class)
{
$v = get_class_vars($class);
@ -156,7 +158,7 @@ class Renderer
* @return TemplateEngine Template Engine instance
* @throws ServiceUnavailableException
*/
public static function getTemplateEngine()
public static function getTemplateEngine(): TemplateEngine
{
$template_engine = (self::$theme['template_engine'] ?? '') ?: 'smarty3';
@ -185,7 +187,7 @@ class Renderer
*
* @return string the active template engine
*/
public static function getActiveTemplateEngine()
public static function getActiveTemplateEngine(): string
{
return self::$theme['template_engine'];
}
@ -194,8 +196,10 @@ class Renderer
* sets the active template engine
*
* @param string $engine the template engine (default is Smarty3)
*
* @return void
*/
public static function setActiveTemplateEngine($engine = 'smarty3')
public static function setActiveTemplateEngine(string $engine = 'smarty3')
{
self::$theme['template_engine'] = $engine;
}
@ -211,7 +215,7 @@ class Renderer
*
* @return string the right delimiter
*/
public static function getTemplateLeftDelimiter($engine = 'smarty3')
public static function getTemplateLeftDelimiter(string $engine = 'smarty3'): string
{
return self::$ldelim[$engine];
}
@ -227,7 +231,7 @@ class Renderer
*
* @return string the left delimiter
*/
public static function getTemplateRightDelimiter($engine = 'smarty3')
public static function getTemplateRightDelimiter(string $engine = 'smarty3'): string
{
return self::$rdelim[$engine];
}

View file

@ -70,7 +70,7 @@ class Search
return $emptyResultList;
}
$contactDetails = Contact::getByURLForUser($user_data['url'] ?? '', local_user());
$contactDetails = Contact::getByURLForUser($user_data['url'] ?? '', DI::userSession()->getLocalUserId());
$result = new ContactResult(
$user_data['name'] ?? '',
@ -136,7 +136,7 @@ class Search
foreach ($profiles as $profile) {
$profile_url = $profile['profile_url'] ?? '';
$contactDetails = Contact::getByURLForUser($profile_url, local_user());
$contactDetails = Contact::getByURLForUser($profile_url, DI::userSession()->getLocalUserId());
$result = new ContactResult(
$profile['name'] ?? '',
@ -192,7 +192,7 @@ class Search
}
// Add found profiles from the global directory to the local directory
Worker::add(PRIORITY_LOW, 'SearchDirectory', $search);
Worker::add(Worker::PRIORITY_LOW, 'SearchDirectory', $search);
return $resultList;
}
@ -211,7 +211,7 @@ class Search
{
Logger::info('Searching', ['search' => $search, 'mode' => $mode, 'page' => $page]);
if (DI::config()->get('system', 'block_public') && !Session::isAuthenticated()) {
if (DI::config()->get('system', 'block_public') && !DI::userSession()->isAuthenticated()) {
return [];
}

View file

@ -1,148 +0,0 @@
<?php
/**
* @copyright Copyright (C) 2010-2022, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
namespace Friendica\Core;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\Contact;
use Friendica\Util\Strings;
/**
* High-level Session service class
*/
class Session
{
public static $exists = false;
public static $expire = 180000;
public static function exists($name)
{
return DI::session()->exists($name);
}
public static function get($name, $defaults = null)
{
return DI::session()->get($name, $defaults);
}
public static function pop($name, $defaults = null)
{
return DI::session()->pop($name, $defaults);
}
public static function set($name, $value)
{
DI::session()->set($name, $value);
}
public static function setMultiple(array $values)
{
DI::session()->setMultiple($values);
}
public static function remove($name)
{
DI::session()->remove($name);
}
public static function clear()
{
DI::session()->clear();
}
/**
* Return the user contact ID of a visitor for the given user ID they are visiting
*
* @param integer $uid User ID
* @return integer
*/
public static function getRemoteContactID($uid)
{
$session = DI::session();
if (!empty($session->get('remote')[$uid])) {
$remote = $session->get('remote')[$uid];
} else {
$remote = 0;
}
$local_user = !empty($session->get('authenticated')) ? $session->get('uid') : 0;
if (empty($remote) && ($local_user != $uid) && !empty($my_address = $session->get('my_address'))) {
$remote = Contact::getIdForURL($my_address, $uid, false);
}
return $remote;
}
/**
* 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)
{
$session = DI::session();
if (empty($session->get('remote'))) {
return false;
}
return array_search($cid, $session->get('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 = DI::session();
$session->set('remote', []);
$remote = [];
$remote_contacts = DBA::select('contact', ['id', 'uid'], ['nurl' => Strings::normaliseLink($session->get('my_url')), 'rel' => [Contact::FOLLOWER, Contact::FRIEND], 'self' => false]);
while ($contact = DBA::fetch($remote_contacts)) {
if (($contact['uid'] == 0) || Contact\User::isBlocked($contact['id'], $contact['uid'])) {
continue;
}
$remote[$contact['uid']] = $contact['id'];
}
DBA::close($remote_contacts);
$session->set('remote', $remote);
}
/**
* Returns if the current visitor is authenticated
*
* @return boolean "true" when visitor is either a local or remote user
*/
public static function isAuthenticated()
{
$session = DI::session();
return $session->get('authenticated', false);
}
}

View file

@ -48,7 +48,7 @@ interface IHandleSessions
* Handle the case where session_start() hasn't been called and the super global isn't available.
*
* @param string $name
* @param mixed $defaults
* @param mixed $defaults Deprecated, use `Session->get($name) ?? $defaults` instead
*
* @return mixed
*/
@ -58,7 +58,7 @@ interface IHandleSessions
* Retrieves a value from the provided key if it exists and removes it from session
*
* @param string $name
* @param mixed $defaults
* @param mixed $defaults Deprecated, use `Session->pop($name) ?? $defaults` instead
*
* @return mixed
*/

View file

@ -0,0 +1,100 @@
<?php
/**
* @copyright Copyright (C) 2010-2022, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
namespace Friendica\Core\Session\Capability;
/**
* This interface handles UserSessions, which is directly extended from the global Session interface
*/
interface IHandleUserSessions extends IHandleSessions
{
/**
* Returns the user id of locally logged-in user or false.
*
* @return int|bool user id or false
*/
public function getLocalUserId();
/**
* Returns the public contact id of logged-in user or false.
*
* @return int|bool public contact id or false
*/
public function getPublicContactId();
/**
* Returns public contact id of authenticated site visitor or false
*
* @return int|bool visitor_id or false
*/
public function getRemoteUserId();
/**
* Return the user contact ID of a visitor for the given user ID they are visiting
*
* @param int $uid User ID
*
* @return int
*/
public function getRemoteContactID(int $uid): int;
/**
* Returns User ID for given contact ID of the visitor
*
* @param int $cid Contact ID
*
* @return int User ID for given contact ID of the visitor
*/
public function getUserIDForVisitorContactID(int $cid): int;
/**
* Returns the account URL of the currently logged in user
*
* @return string
*/
public function getMyUrl(): string;
/**
* Returns if the current visitor is authenticated
*
* @return bool "true" when visitor is either a local or remote user
*/
public function isAuthenticated(): bool;
/**
* Returns User ID of the managed user in case it's a different identity
*
* @return int|bool uid of the manager or false
*/
public function getSubManagedUserId();
/**
* Sets the User ID of the managed user in case it's a different identity
*
* @param int $managed_uid The user id of the managing user
*/
public function setSubManagedUserId(int $managed_uid): void;
/**
* Set the session variable that contains the contact IDs for the visitor's contact URL
*/
public function setVisitorsContacts();
}

View file

@ -55,10 +55,8 @@ class Session
* @param LoggerInterface $logger
* @param Profiler $profiler
* @param array $server
*
* @return IHandleSessions
*/
public function createSession(App\Mode $mode, App\BaseURL $baseURL, IManageConfigValues $config, Database $dba, Cache $cacheFactory, LoggerInterface $logger, Profiler $profiler, array $server = [])
public function createSession(App\Mode $mode, App\BaseURL $baseURL, IManageConfigValues $config, Database $dba, Cache $cacheFactory, LoggerInterface $logger, Profiler $profiler, array $server = []): IHandleSessions
{
$profiler->startRecording('session');
$session = null;

View file

@ -0,0 +1,28 @@
<?php
/**
* @copyright Copyright (C) 2010-2022, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
namespace Friendica\Core\Session\Handler;
abstract class AbstractSessionHandler implements \SessionHandlerInterface
{
/** @var int Duration of the Session */
public const EXPIRE = 180000;
}

View file

@ -23,14 +23,12 @@ namespace Friendica\Core\Session\Handler;
use Friendica\Core\Cache\Capability\ICanCache;
use Friendica\Core\Cache\Exception\CachePersistenceException;
use Friendica\Core\Session;
use Psr\Log\LoggerInterface;
use SessionHandlerInterface;
/**
* SessionHandler using Friendica Cache
*/
class Cache implements SessionHandlerInterface
class Cache extends AbstractSessionHandler
{
/** @var ICanCache */
private $cache;
@ -57,11 +55,10 @@ class Cache implements SessionHandlerInterface
try {
$data = $this->cache->get('session:' . $id);
if (!empty($data)) {
Session::$exists = true;
return $data;
}
} catch (CachePersistenceException $exception) {
$this->logger->warning('Cannot read session.'. ['id' => $id, 'exception' => $exception]);
$this->logger->warning('Cannot read session.', ['id' => $id, 'exception' => $exception]);
return '';
}
@ -91,7 +88,7 @@ class Cache implements SessionHandlerInterface
}
try {
return $this->cache->set('session:' . $id, $data, Session::$expire);
return $this->cache->set('session:' . $id, $data, static::EXPIRE);
} catch (CachePersistenceException $exception) {
$this->logger->warning('Cannot write session', ['id' => $id, 'exception' => $exception]);
return false;

View file

@ -21,15 +21,13 @@
namespace Friendica\Core\Session\Handler;
use Friendica\Core\Session;
use Friendica\Database\Database as DBA;
use Psr\Log\LoggerInterface;
use SessionHandlerInterface;
/**
* SessionHandler using database
*/
class Database implements SessionHandlerInterface
class Database extends AbstractSessionHandler
{
/** @var DBA */
private $dba;
@ -37,6 +35,8 @@ class Database implements SessionHandlerInterface
private $logger;
/** @var array The $_SERVER variable */
private $server;
/** @var bool global check, if the current Session exists */
private $sessionExists = false;
/**
* DatabaseSessionHandler constructor.
@ -57,6 +57,7 @@ class Database implements SessionHandlerInterface
return true;
}
#[\ReturnTypeWillChange]
public function read($id)
{
if (empty($id)) {
@ -66,11 +67,11 @@ class Database implements SessionHandlerInterface
try {
$session = $this->dba->selectFirst('session', ['data'], ['sid' => $id]);
if ($this->dba->isResult($session)) {
Session::$exists = true;
$this->sessionExists = true;
return $session['data'];
}
} catch (\Exception $exception) {
$this->logger->warning('Cannot read session.'. ['id' => $id, 'exception' => $exception]);
$this->logger->warning('Cannot read session.', ['id' => $id, 'exception' => $exception]);
return '';
}
@ -101,11 +102,11 @@ class Database implements SessionHandlerInterface
return $this->destroy($id);
}
$expire = time() + Session::$expire;
$expire = time() + static::EXPIRE;
$default_expire = time() + 300;
try {
if (Session::$exists) {
if ($this->sessionExists) {
$fields = ['data' => $data, 'expire' => $expire];
$condition = ["`sid` = ? AND (`data` != ? OR `expire` != ?)", $id, $data, $expire];
$this->dba->update('session', $fields, $condition);
@ -114,7 +115,7 @@ class Database implements SessionHandlerInterface
$this->dba->insert('session', $fields);
}
} catch (\Exception $exception) {
$this->logger->warning('Cannot write session.'. ['id' => $id, 'exception' => $exception]);
$this->logger->warning('Cannot write session.', ['id' => $id, 'exception' => $exception]);
return false;
}
@ -131,17 +132,18 @@ class Database implements SessionHandlerInterface
try {
return $this->dba->delete('session', ['sid' => $id]);
} catch (\Exception $exception) {
$this->logger->warning('Cannot destroy session.'. ['id' => $id, 'exception' => $exception]);
$this->logger->warning('Cannot destroy session.', ['id' => $id, 'exception' => $exception]);
return false;
}
}
#[\ReturnTypeWillChange]
public function gc($max_lifetime): bool
{
try {
return $this->dba->delete('session', ["`expire` < ?", time()]);
} catch (\Exception $exception) {
$this->logger->warning('Cannot use garbage collector.'. ['exception' => $exception]);
$this->logger->warning('Cannot use garbage collector.', ['exception' => $exception]);
return false;
}
}

View file

@ -0,0 +1,190 @@
<?php
/**
* @copyright Copyright (C) 2010-2022, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
namespace Friendica\Core\Session\Model;
use Friendica\Core\Session\Capability\IHandleSessions;
use Friendica\Core\Session\Capability\IHandleUserSessions;
use Friendica\Model\Contact;
/**
* This class handles user sessions, which is directly extended from regular session
*/
class UserSession implements IHandleUserSessions
{
/** @var IHandleSessions */
private $session;
/** @var int|bool saves the public Contact ID for later usage */
protected $publicContactId = false;
public function __construct(IHandleSessions $session)
{
$this->session = $session;
}
/** {@inheritDoc} */
public function getLocalUserId()
{
if (!empty($this->session->get('authenticated')) && !empty($this->session->get('uid'))) {
return intval($this->session->get('uid'));
}
return false;
}
/** {@inheritDoc} */
public function getPublicContactId()
{
if (empty($this->publicContactId) && !empty($this->session->get('authenticated'))) {
if (!empty($this->session->get('my_address'))) {
// Local user
$this->publicContactId = Contact::getIdForURL($this->session->get('my_address'), 0, false);
} elseif (!empty($this->session->get('visitor_home'))) {
// Remote user
$this->publicContactId = Contact::getIdForURL($this->session->get('visitor_home'), 0, false);
}
} elseif (empty($this->session->get('authenticated'))) {
$this->publicContactId = false;
}
return $this->publicContactId;
}
/** {@inheritDoc} */
public function getRemoteUserId()
{
if (empty($this->session->get('authenticated'))) {
return false;
}
if (!empty($this->session->get('visitor_id'))) {
return (int)$this->session->get('visitor_id');
}
return false;
}
/** {@inheritDoc} */
public function getRemoteContactID(int $uid): int
{
if (!empty($this->session->get('remote')[$uid])) {
$remote = $this->session->get('remote')[$uid];
} else {
$remote = 0;
}
$local_user = !empty($this->session->get('authenticated')) ? $this->session->get('uid') : 0;
if (empty($remote) && ($local_user != $uid) && !empty($my_address = $this->session->get('my_address'))) {
$remote = Contact::getIdForURL($my_address, $uid, false);
}
return $remote;
}
/** {@inheritDoc} */
public function getUserIDForVisitorContactID(int $cid): int
{
if (empty($this->session->get('remote'))) {
return false;
}
return array_search($cid, $this->session->get('remote'));
}
/** {@inheritDoc} */
public function getMyUrl(): string
{
return $this->session->get('my_url', '');
}
/** {@inheritDoc} */
public function isAuthenticated(): bool
{
return $this->session->get('authenticated', false);
}
/** {@inheritDoc} */
public function setVisitorsContacts()
{
$this->session->set('remote', Contact::getVisitorByUrl($this->session->get('my_url')));
}
/** {@inheritDoc} */
public function getSubManagedUserId()
{
return $this->session->get('submanage') ?? false;
}
/** {@inheritDoc} */
public function setSubManagedUserId(int $managed_uid): void
{
$this->session->set('submanage', $managed_uid);
}
/** {@inheritDoc} */
public function start(): IHandleSessions
{
return $this;
}
/** {@inheritDoc} */
public function exists(string $name): bool
{
return $this->session->exists($name);
}
/** {@inheritDoc} */
public function get(string $name, $defaults = null)
{
return $this->session->get($name, $defaults);
}
/** {@inheritDoc} */
public function pop(string $name, $defaults = null)
{
return $this->session->pop($name, $defaults);
}
/** {@inheritDoc} */
public function set(string $name, $value)
{
$this->session->set($name, $value);
}
/** {@inheritDoc} */
public function setMultiple(array $values)
{
$this->session->setMultiple($values);
}
/** {@inheritDoc} */
public function remove(string $name)
{
$this->session->remove($name);
}
/** {@inheritDoc} */
public function clear()
{
$this->session->clear();
}
}

View file

@ -0,0 +1,81 @@
<?php
/**
* @copyright Copyright (C) 2010-2022, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
namespace Friendica\Core\Session\Type;
use Friendica\Core\Session\Capability\IHandleSessions;
class ArraySession implements IHandleSessions
{
/** @var array */
protected $data = [];
public function __construct(array $data = [])
{
$this->data = $data;
}
public function start(): IHandleSessions
{
return $this;
}
public function exists(string $name): bool
{
return !empty($this->data[$name]);
}
public function get(string $name, $defaults = null)
{
return $this->data[$name] ?? $defaults;
}
public function pop(string $name, $defaults = null)
{
$value = $defaults;
if ($this->exists($name)) {
$value = $this->get($name);
$this->remove($name);
}
return $value;
}
public function set(string $name, $value)
{
$this->data[$name] = $value;
}
public function setMultiple(array $values)
{
$this->data = array_merge($values, $this->data);
}
public function remove(string $name)
{
unset($this->data[$name]);
}
public function clear()
{
$this->data = [];
}
}

View file

@ -21,6 +21,8 @@
namespace Friendica\Core;
use Friendica\Content\Text\BBCode;
use Friendica\Content\Text\HTML;
use Friendica\Core\Config\Capability\IManageConfigValues;
use Friendica\DI;
use Friendica\Module\Response;
@ -438,14 +440,18 @@ class System
/**
* Fetch the load and number of processes
*
* @param bool $get_processes
* @return array
*/
public static function getLoadAvg(): array
public static function getLoadAvg(bool $get_processes = true): array
{
$content = @file_get_contents('/proc/loadavg');
if (empty($content)) {
$content = shell_exec('cat /proc/loadavg');
if ($get_processes && @is_readable('/proc/loadavg')) {
$content = @file_get_contents('/proc/loadavg');
if (empty($content)) {
$content = shell_exec('uptime | sed "s/.*averages*: //" | sed "s/,//g"');
}
}
if (empty($content) || !preg_match("#([.\d]+)\s([.\d]+)\s([.\d]+)\s(\d+)/(\d+)#", $content, $matches)) {
$load_arr = sys_getloadavg();
if (empty($load_arr)) {
@ -656,4 +662,30 @@ class System
// Reaching this point means that the operating system is configured badly.
return "";
}
/**
* Fetch the system rules
*
* @return array
*/
public static function getRules(): array
{
$rules = [];
$id = 0;
if (DI::config()->get('system', 'tosdisplay')) {
$rulelist = DI::config()->get('system', 'tosrules') ?: DI::config()->get('system', 'tostext');
$html = BBCode::convert($rulelist, false, BBCode::EXTERNAL);
$msg = HTML::toPlaintext($html, 0, true);
foreach (explode("\n", $msg) as $line) {
$line = trim($line);
if ($line) {
$rules[] = ['id' => (string)++$id, 'text' => $line];
}
}
}
return $rules;
}
}

View file

@ -25,8 +25,6 @@ use Friendica\DI;
use Friendica\Model\Profile;
use Friendica\Util\Strings;
require_once 'boot.php';
/**
* Some functions to handle themes
*/

View file

@ -26,6 +26,7 @@ use Friendica\App\Mode;
use Friendica\Database\DBA;
use Friendica\Database\DBStructure;
use Friendica\DI;
use Friendica\Model\User;
use Friendica\Network\HTTPException\InternalServerErrorException;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\Strings;
@ -35,6 +36,8 @@ class Update
const SUCCESS = 0;
const FAILED = 1;
const NEW_TABLE_STRUCTURE_VERSION = 1288;
/**
* Function to check if the Database structure needs an update.
*
@ -63,7 +66,7 @@ class Update
}
// We don't support upgrading from very old versions anymore
if ($build < NEW_TABLE_STRUCTURE_VERSION) {
if ($build < self::NEW_TABLE_STRUCTURE_VERSION) {
$error = DI::l10n()->t('Updates from version %s are not supported. Please update at least to version 2021.01 and wait until the postupdate finished version 1383.', $build);
if (DI::mode()->getExecutor() == Mode::INDEX) {
die($error);
@ -73,8 +76,8 @@ class Update
}
// The postupdate has to completed version 1288 for the new post views to take over
$postupdate = DI::config()->get('system', 'post_update_version', NEW_TABLE_STRUCTURE_VERSION);
if ($postupdate < NEW_TABLE_STRUCTURE_VERSION) {
$postupdate = DI::config()->get('system', 'post_update_version', self::NEW_TABLE_STRUCTURE_VERSION);
if ($postupdate < self::NEW_TABLE_STRUCTURE_VERSION) {
$error = DI::l10n()->t('Updates from postupdate version %s are not supported. Please update at least to version 2021.01 and wait until the postupdate finished version 1383.', $postupdate);
if (DI::mode()->getExecutor() == Mode::INDEX) {
die($error);
@ -92,7 +95,7 @@ class Update
*/
self::run($basePath);
} else {
Worker::add(PRIORITY_CRITICAL, 'DBUpdate');
Worker::add(Worker::PRIORITY_CRITICAL, 'DBUpdate');
}
}
}
@ -146,7 +149,7 @@ class Update
}
DI::config()->set('system', 'maintenance', 1);
// run the pre_update_nnnn functions in update.php
for ($version = $stored + 1; $version <= $current; $version++) {
Logger::notice('Execute pre update.', ['version' => $version]);
@ -287,30 +290,16 @@ class Update
* @return void
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
private static function updateFailed(int $update_id, string $error_message) {
//send the administrators an e-mail
$condition = ['email' => explode(',', str_replace(' ', '', DI::config()->get('config', 'admin_email'))), 'parent-uid' => 0];
$adminlist = DBA::select('user', ['uid', 'language', 'email'], $condition, ['order' => ['uid']]);
// No valid result?
if (!DBA::isResult($adminlist)) {
private static function updateFailed(int $update_id, string $error_message)
{
$adminEmails = User::getAdminListForEmailing(['uid', 'language', 'email']);
if (!$adminEmails) {
Logger::warning('Cannot notify administrators .', ['update' => $update_id, 'message' => $error_message]);
// Don't continue
return;
}
$sent = [];
// every admin could had different language
while ($admin = DBA::fetch($adminlist)) {
if (in_array($admin['email'], $sent)) {
continue;
}
$sent[] = $admin['email'];
$lang = $admin['language'] ?? 'en';
$l10n = DI::l10n()->withLang($lang);
foreach($adminEmails as $admin) {
$l10n = DI::l10n()->withLang($admin['language'] ?: 'en');
$preamble = Strings::deindent($l10n->t("
The friendica developers released update %s recently,
@ -341,35 +330,20 @@ class Update
*/
private static function updateSuccessful(int $from_build, int $to_build)
{
//send the administrators an e-mail
$condition = ['email' => explode(',', str_replace(' ', '', DI::config()->get('config', 'admin_email'))), 'parent-uid' => 0];
$adminlist = DBA::select('user', ['uid', 'language', 'email'], $condition, ['order' => ['uid']]);
foreach(User::getAdminListForEmailing(['uid', 'language', 'email']) as $admin) {
$l10n = DI::l10n()->withLang($admin['language'] ?: 'en');
if (DBA::isResult($adminlist)) {
$sent = [];
$preamble = Strings::deindent($l10n->t('
The friendica database was successfully updated from %s to %s.',
$from_build, $to_build));
// every admin could had different language
while ($admin = DBA::fetch($adminlist)) {
if (in_array($admin['email'], $sent)) {
continue;
}
$sent[] = $admin['email'];
$lang = (($admin['language']) ? $admin['language'] : 'en');
$l10n = DI::l10n()->withLang($lang);
$preamble = Strings::deindent($l10n->t('
The friendica database was successfully updated from %s to %s.',
$from_build, $to_build));
$email = DI::emailer()
->newSystemMail()
->withMessage($l10n->t('[Friendica Notify] Database update'), $preamble)
->forUser($admin)
->withRecipient($admin['email'])
->build();
DI::emailer()->send($email);
}
$email = DI::emailer()
->newSystemMail()
->withMessage($l10n->t('[Friendica Notify] Database update'), $preamble)
->forUser($admin)
->withRecipient($admin['email'])
->build();
DI::emailer()->send($email);
}
Logger::debug('Database structure update successful.');

View file

@ -1,330 +0,0 @@
<?php
/**
* @copyright Copyright (C) 2010-2022, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
namespace Friendica\Core;
use Friendica\Database\DBA;
use Friendica\Database\DBStructure;
use Friendica\DI;
use Friendica\Model\Photo;
use Friendica\Model\Profile;
use Friendica\Object\Image;
use Friendica\Security\PermissionSet\Repository\PermissionSet;
use Friendica\Util\Strings;
use Friendica\Worker\Delivery;
/**
* UserImport class
*/
class UserImport
{
const IMPORT_DEBUG = false;
private static function lastInsertId()
{
if (self::IMPORT_DEBUG) {
return 1;
}
return DBA::lastInsertId();
}
/**
* Remove columns from array $arr that aren't in table $table
*
* @param string $table Table name
* @param array &$arr Column=>Value array from json (by ref)
* @throws \Exception
*/
private static function checkCols($table, &$arr)
{
$tableColumns = DBStructure::getColumns($table);
$tcols = [];
$ttype = [];
// get a plain array of column names
foreach ($tableColumns as $tcol) {
$tcols[] = $tcol['Field'];
$ttype[$tcol['Field']] = $tcol['Type'];
}
// remove inexistent columns
foreach ($arr as $icol => $ival) {
if (!in_array($icol, $tcols)) {
unset($arr[$icol]);
continue;
}
if ($ttype[$icol] === 'datetime') {
$arr[$icol] = $ival ?? DBA::NULL_DATETIME;
}
}
}
/**
* Import data into table $table
*
* @param string $table Table name
* @param array $arr Column=>Value array from json
* @return array|bool
* @throws \Exception
*/
private static function dbImportAssoc(string $table, array $arr)
{
if (isset($arr['id'])) {
unset($arr['id']);
}
self::checkCols($table, $arr);
if (self::IMPORT_DEBUG) {
return true;
}
return DBA::insert($table, $arr);
}
/**
* Import account file exported from mod/uexport
*
* @param array $file array from $_FILES
* @return void
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
* @throws \ImagickException
*/
public static function importAccount(array $file)
{
Logger::notice("Start user import from " . $file['tmp_name']);
/*
STEPS
1. checks
2. replace old baseurl with new baseurl
3. import data (look at user id and contacts id)
4. archive non-dfrn contacts
5. send message to dfrn contacts
*/
$account = json_decode(file_get_contents($file['tmp_name']), true);
if ($account === null) {
notice(DI::l10n()->t("Error decoding account file"));
return;
}
if (empty($account['version'])) {
notice(DI::l10n()->t("Error! No version data in file! This is not a Friendica account file?"));
return;
}
// check for username
// check if username matches deleted account
if (DBA::exists('user', ['nickname' => $account['user']['nickname']])
|| DBA::exists('userd', ['username' => $account['user']['nickname']])) {
notice(DI::l10n()->t("User '%s' already exists on this server!", $account['user']['nickname']));
return;
}
$oldbaseurl = $account['baseurl'];
$newbaseurl = DI::baseUrl();
$oldaddr = str_replace('http://', '@', Strings::normaliseLink($oldbaseurl));
$newaddr = str_replace('http://', '@', Strings::normaliseLink($newbaseurl));
if (!empty($account['profile']['addr'])) {
$old_handle = $account['profile']['addr'];
} else {
$old_handle = $account['user']['nickname'].$oldaddr;
}
// Creating a new guid to avoid problems with Diaspora
$account['user']['guid'] = System::createUUID();
$olduid = $account['user']['uid'];
unset($account['user']['uid']);
unset($account['user']['account_expired']);
unset($account['user']['account_expires_on']);
unset($account['user']['expire_notification_sent']);
$callback = function (&$value) use ($oldbaseurl, $oldaddr, $newbaseurl, $newaddr) {
$value = str_replace([$oldbaseurl, $oldaddr], [$newbaseurl, $newaddr], $value);
};
array_walk($account['user'], $callback);
// import user
$r = self::dbImportAssoc('user', $account['user']);
if ($r === false) {
Logger::warning("uimport:insert user : ERROR : " . DBA::errorMessage());
notice(DI::l10n()->t("User creation error"));
return;
}
$newuid = self::lastInsertId();
DI::pConfig()->set($newuid, 'system', 'previous_addr', $old_handle);
$errorcount = 0;
foreach ($account['contact'] as &$contact) {
if ($contact['uid'] == $olduid && $contact['self'] == '1') {
foreach ($contact as $k => &$v) {
$v = str_replace([$oldbaseurl, $oldaddr], [$newbaseurl, $newaddr], $v);
foreach (["profile", "avatar", "micro"] as $k) {
$v = str_replace($oldbaseurl . "/photo/" . $k . "/" . $olduid . ".jpg", $newbaseurl . "/photo/" . $k . "/" . $newuid . ".jpg", $v);
}
}
}
if ($contact['uid'] == $olduid && $contact['self'] == '0') {
// set contacts 'avatar-date' to NULL_DATE to let worker to update urls
$contact["avatar-date"] = DBA::NULL_DATETIME;
switch ($contact['network']) {
case Protocol::DFRN:
case Protocol::DIASPORA:
// send relocate message (below)
break;
case Protocol::FEED:
case Protocol::MAIL:
// Nothing to do
break;
default:
// archive other contacts
$contact['archive'] = "1";
}
}
$contact['uid'] = $newuid;
$r = self::dbImportAssoc('contact', $contact);
if ($r === false) {
Logger::warning("uimport:insert contact " . $contact['nick'] . "," . $contact['network'] . " : ERROR : " . DBA::errorMessage());
$errorcount++;
} else {
$contact['newid'] = self::lastInsertId();
}
}
if ($errorcount > 0) {
notice(DI::l10n()->tt("%d contact not imported", "%d contacts not imported", $errorcount));
}
foreach ($account['group'] as &$group) {
$group['uid'] = $newuid;
$r = self::dbImportAssoc('group', $group);
if ($r === false) {
Logger::warning("uimport:insert group " . $group['name'] . " : ERROR : " . DBA::errorMessage());
} else {
$group['newid'] = self::lastInsertId();
}
}
foreach ($account['group_member'] as &$group_member) {
$import = 0;
foreach ($account['group'] as $group) {
if ($group['id'] == $group_member['gid'] && isset($group['newid'])) {
$group_member['gid'] = $group['newid'];
$import++;
break;
}
}
foreach ($account['contact'] as $contact) {
if ($contact['id'] == $group_member['contact-id'] && isset($contact['newid'])) {
$group_member['contact-id'] = $contact['newid'];
$import++;
break;
}
}
if ($import == 2) {
$r = self::dbImportAssoc('group_member', $group_member);
if ($r === false) {
Logger::warning("uimport:insert group member " . $group_member['id'] . " : ERROR : " . DBA::errorMessage());
}
}
}
foreach ($account['profile'] as &$profile) {
unset($profile['id']);
$profile['uid'] = $newuid;
foreach ($profile as $k => &$v) {
$v = str_replace([$oldbaseurl, $oldaddr], [$newbaseurl, $newaddr], $v);
foreach (["profile", "avatar"] as $k) {
$v = str_replace($oldbaseurl . "/photo/" . $k . "/" . $olduid . ".jpg", $newbaseurl . "/photo/" . $k . "/" . $newuid . ".jpg", $v);
}
}
if (count($account['profile']) === 1 || $profile['is-default']) {
$r = self::dbImportAssoc('profile', $profile);
if ($r === false) {
Logger::warning("uimport:insert profile: ERROR : " . DBA::errorMessage());
notice(DI::l10n()->t("User profile creation error"));
DBA::delete('user', ['uid' => $newuid]);
DBA::delete('profile_field', ['uid' => $newuid]);
return;
}
$profile['id'] = DBA::lastInsertId();
}
Profile::migrate($profile);
}
$permissionSet = DI::permissionSet()->selectDefaultForUser($newuid);
foreach ($account['profile_fields'] ?? [] as $profile_field) {
$profile_field['uid'] = $newuid;
///@TODO Replace with permissionset import
$profile_field['psid'] = $profile_field['psid'] ? $permissionSet->uid : PermissionSet::PUBLIC;
if (self::dbImportAssoc('profile_field', $profile_field) === false) {
Logger::info("uimport:insert profile field " . $profile_field['id'] . " : ERROR : " . DBA::errorMessage());
}
}
foreach ($account['photo'] as &$photo) {
$photo['uid'] = $newuid;
$photo['data'] = hex2bin($photo['data']);
$Image = new Image($photo['data'], $photo['type']);
$r = Photo::store(
$Image,
$photo['uid'], $photo['contact-id'], //0
$photo['resource-id'], $photo['filename'], $photo['album'], $photo['scale'], $photo['profile'], //1
$photo['allow_cid'], $photo['allow_gid'], $photo['deny_cid'], $photo['deny_gid']
);
if ($r === false) {
Logger::warning("uimport:insert photo " . $photo['resource-id'] . "," . $photo['scale'] . " : ERROR : " . DBA::errorMessage());
}
}
foreach ($account['pconfig'] as &$pconfig) {
$pconfig['uid'] = $newuid;
$r = self::dbImportAssoc('pconfig', $pconfig);
if ($r === false) {
Logger::warning("uimport:insert pconfig " . $pconfig['id'] . " : ERROR : " . DBA::errorMessage());
}
}
// send relocate messages
Worker::add(PRIORITY_HIGH, 'Notifier', Delivery::RELOCATION, $newuid);
info(DI::l10n()->t("Done. You can now login with your username and password"));
DI::baseUrl()->redirect('login');
}
}

View file

@ -31,12 +31,20 @@ use Friendica\Util\DateTimeFormat;
*/
class Worker
{
const PRIORITY_UNDEFINED = PRIORITY_UNDEFINED;
const PRIORITY_CRITICAL = PRIORITY_CRITICAL;
const PRIORITY_HIGH = PRIORITY_HIGH;
const PRIORITY_MEDIUM = PRIORITY_MEDIUM;
const PRIORITY_LOW = PRIORITY_LOW;
const PRIORITY_NEGLIGIBLE = PRIORITY_NEGLIGIBLE;
/**
* @name Priority
*
* Process priority for the worker
* @{
*/
const PRIORITY_UNDEFINED = 0;
const PRIORITY_CRITICAL = 10;
const PRIORITY_HIGH = 20;
const PRIORITY_MEDIUM = 30;
const PRIORITY_LOW = 40;
const PRIORITY_NEGLIGIBLE = 50;
const PRIORITIES = [self::PRIORITY_CRITICAL, self::PRIORITY_HIGH, self::PRIORITY_MEDIUM, self::PRIORITY_LOW, self::PRIORITY_NEGLIGIBLE];
/* @}*/
const STATE_STARTUP = 1; // Worker is in startup. This takes most time.
const STATE_LONG_LOOP = 2; // Worker is processing the whole - long - loop.
@ -307,17 +315,7 @@ class Worker
return false;
}
$valid = false;
if (strpos($file, 'include/') === 0) {
$valid = true;
}
if (strpos($file, 'addon/') === 0) {
$valid = true;
}
// Simply return flag
return $valid;
return (strpos($file, 'addon/') === 0);
}
/**
@ -398,11 +396,6 @@ class Worker
return true;
}
// The script could be provided as full path or only with the function name
if ($include == basename($include)) {
$include = 'include/' . $include . '.php';
}
if (!self::validateInclude($include)) {
Logger::warning('Include file is not valid', ['file' => $argv[0]]);
$stamp = (float)microtime(true);
@ -461,11 +454,15 @@ class Worker
$load_cooldown = DI::config()->get('system', 'worker_load_cooldown');
$processes_cooldown = DI::config()->get('system', 'worker_processes_cooldown');
if ($load_cooldown == 0) {
$load_cooldown = DI::config()->get('system', 'maxloadavg');
}
if (($load_cooldown == 0) && ($processes_cooldown == 0)) {
return false;
}
$load = System::getLoadAvg();
$load = System::getLoadAvg($processes_cooldown != 0);
if (empty($load)) {
return false;
}
@ -501,13 +498,17 @@ class Worker
$load_cooldown = DI::config()->get('system', 'worker_load_cooldown');
$processes_cooldown = DI::config()->get('system', 'worker_processes_cooldown');
if ($load_cooldown == 0) {
$load_cooldown = DI::config()->get('system', 'maxloadavg');
}
if (($load_cooldown == 0) && ($processes_cooldown == 0)) {
return;
}
$sleeping = false;
while ($load = System::getLoadAvg()) {
while ($load = System::getLoadAvg($processes_cooldown != 0)) {
if (($load_cooldown > 0) && ($load['average1'] > $load_cooldown)) {
if (!$sleeping) {
Logger::notice('Load induced pre execution cooldown.', ['max' => $load_cooldown, 'load' => $load, 'called-by' => System::callstack(1)]);
@ -567,7 +568,15 @@ class Worker
// Set the workerLogger as new default logger
if ($method_call) {
call_user_func_array(sprintf('Friendica\Worker\%s::execute', $funcname), $argv);
try {
call_user_func_array(sprintf('Friendica\Worker\%s::execute', $funcname), $argv);
} catch (\TypeError $e) {
// No need to defer a worker queue entry if the arguments are invalid
Logger::notice('Wrong worker arguments', ['class' => $funcname, 'argv' => $argv, 'queue' => $queue, 'message' => $e->getMessage()]);
} catch (\Throwable $e) {
Logger::error('Uncaught exception in worker execution', ['class' => get_class($e), 'message' => $e->getMessage(), 'code' => $e->getCode(), 'file' => $e->getFile() . ':' . $e->getLine(), 'trace' => $e->getTraceAsString()]);
Worker::defer();
}
} else {
$funcname($argv, count($argv));
}
@ -799,7 +808,7 @@ class Worker
$top_priority = self::highestPriority();
$high_running = self::processWithPriorityActive($top_priority);
if (!$high_running && ($top_priority > PRIORITY_UNDEFINED) && ($top_priority < PRIORITY_NEGLIGIBLE)) {
if (!$high_running && ($top_priority > self::PRIORITY_UNDEFINED) && ($top_priority < self::PRIORITY_NEGLIGIBLE)) {
Logger::info('Jobs with a higher priority are waiting but none is executed. Open a fastlane.', ['priority' => $top_priority]);
$queues = $active + 1;
}
@ -931,7 +940,7 @@ class Worker
private static function nextPriority()
{
$waiting = [];
$priorities = [PRIORITY_CRITICAL, PRIORITY_HIGH, PRIORITY_MEDIUM, PRIORITY_LOW, PRIORITY_NEGLIGIBLE];
$priorities = [self::PRIORITY_CRITICAL, self::PRIORITY_HIGH, self::PRIORITY_MEDIUM, self::PRIORITY_LOW, self::PRIORITY_NEGLIGIBLE];
foreach ($priorities as $priority) {
$stamp = (float)microtime(true);
if (DBA::exists('workerqueue', ["`priority` = ? AND `pid` = 0 AND NOT `done` AND `next_try` < ?", $priority, DateTimeFormat::utcNow()])) {
@ -940,8 +949,8 @@ class Worker
self::$db_duration += (microtime(true) - $stamp);
}
if (!empty($waiting[PRIORITY_CRITICAL])) {
return PRIORITY_CRITICAL;
if (!empty($waiting[self::PRIORITY_CRITICAL])) {
return self::PRIORITY_CRITICAL;
}
$running = [];
@ -1198,8 +1207,8 @@ class Worker
* @param (integer|array) priority or parameter array, strings are deprecated and are ignored
*
* next args are passed as $cmd command line
* or: Worker::add(PRIORITY_HIGH, 'Notifier', Delivery::DELETION, $drop_id);
* or: Worker::add(array('priority' => PRIORITY_HIGH, 'dont_fork' => true), 'Delivery', $post_id);
* or: Worker::add(Worker::PRIORITY_HIGH, 'Notifier', Delivery::DELETION, $drop_id);
* or: Worker::add(array('priority' => Worker::PRIORITY_HIGH, 'dont_fork' => true), 'Delivery', $post_id);
*
* @return int '0' if worker queue entry already existed or there had been an error, otherwise the ID of the worker task
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
@ -1222,7 +1231,7 @@ class Worker
return 1;
}
$priority = PRIORITY_MEDIUM;
$priority = self::PRIORITY_MEDIUM;
// Don't fork from frontend tasks by default
$dont_fork = DI::config()->get('system', 'worker_dont_fork', false) || !DI::mode()->isBackend();
$created = DateTimeFormat::utcNow();
@ -1258,9 +1267,9 @@ class Worker
$found = DBA::exists('workerqueue', ['command' => $command, 'parameter' => $parameters, 'done' => false]);
$added = 0;
if (!is_int($priority) || !in_array($priority, PRIORITIES)) {
if (!is_int($priority) || !in_array($priority, self::PRIORITIES)) {
Logger::warning('Invalid priority', ['priority' => $priority, 'command' => $command, 'callstack' => System::callstack(20)]);
$priority = PRIORITY_MEDIUM;
$priority = self::PRIORITY_MEDIUM;
}
// Quit if there was a database error - a precaution for the update process to 3.5.3
@ -1375,12 +1384,12 @@ class Worker
$delay = (($new_retrial + 2) ** 4) + (rand(1, 30) * ($new_retrial));
$next = DateTimeFormat::utc('now + ' . $delay . ' seconds');
if (($priority < PRIORITY_MEDIUM) && ($new_retrial > 3)) {
$priority = PRIORITY_MEDIUM;
} elseif (($priority < PRIORITY_LOW) && ($new_retrial > 6)) {
$priority = PRIORITY_LOW;
} elseif (($priority < PRIORITY_NEGLIGIBLE) && ($new_retrial > 8)) {
$priority = PRIORITY_NEGLIGIBLE;
if (($priority < self::PRIORITY_MEDIUM) && ($new_retrial > 3)) {
$priority = self::PRIORITY_MEDIUM;
} elseif (($priority < self::PRIORITY_LOW) && ($new_retrial > 6)) {
$priority = self::PRIORITY_LOW;
} elseif (($priority < self::PRIORITY_NEGLIGIBLE) && ($new_retrial > 8)) {
$priority = self::PRIORITY_NEGLIGIBLE;
}
Logger::info('Deferred task', ['id' => $id, 'retrial' => $new_retrial, 'created' => $queue['created'], 'next_execution' => $next, 'old_prio' => $queue['priority'], 'new_prio' => $priority]);

View file

@ -47,10 +47,10 @@ class Cron
Logger::info('Add cron entries');
// Check for spooled items
Worker::add(['priority' => PRIORITY_HIGH, 'force_priority' => true], 'SpoolPost');
Worker::add(['priority' => Worker::PRIORITY_HIGH, 'force_priority' => true], 'SpoolPost');
// Run the cron job that calls all other jobs
Worker::add(['priority' => PRIORITY_MEDIUM, 'force_priority' => true], 'Cron');
Worker::add(['priority' => Worker::PRIORITY_MEDIUM, 'force_priority' => true], 'Cron');
// Cleaning dead processes
self::killStaleWorkers();
@ -112,12 +112,12 @@ class Cron
// To avoid a blocking situation we reschedule the process at the beginning of the queue.
// Additionally we are lowering the priority. (But not PRIORITY_CRITICAL)
$new_priority = $entry['priority'];
if ($entry['priority'] == PRIORITY_HIGH) {
$new_priority = PRIORITY_MEDIUM;
} elseif ($entry['priority'] == PRIORITY_MEDIUM) {
$new_priority = PRIORITY_LOW;
} elseif ($entry['priority'] != PRIORITY_CRITICAL) {
$new_priority = PRIORITY_NEGLIGIBLE;
if ($entry['priority'] == Worker::PRIORITY_HIGH) {
$new_priority = Worker::PRIORITY_MEDIUM;
} elseif ($entry['priority'] == Worker::PRIORITY_MEDIUM) {
$new_priority = Worker::PRIORITY_LOW;
} elseif ($entry['priority'] != Worker::PRIORITY_CRITICAL) {
$new_priority = Worker::PRIORITY_NEGLIGIBLE;
}
DBA::update('workerqueue', ['executed' => DBA::NULL_DATETIME, 'created' => DateTimeFormat::utcNow(), 'priority' => $new_priority, 'pid' => 0], ['id' => $entry["id"]]
);
@ -171,19 +171,26 @@ class Cron
Logger::info('Directly deliver inbox', ['inbox' => $delivery['inbox'], 'result' => $result['success']]);
continue;
} elseif ($delivery['failed'] < 3) {
$priority = PRIORITY_HIGH;
$priority = Worker::PRIORITY_HIGH;
} elseif ($delivery['failed'] < 6) {
$priority = PRIORITY_MEDIUM;
$priority = Worker::PRIORITY_MEDIUM;
} elseif ($delivery['failed'] < 8) {
$priority = PRIORITY_LOW;
$priority = Worker::PRIORITY_LOW;
} else {
$priority = PRIORITY_NEGLIGIBLE;
$priority = Worker::PRIORITY_NEGLIGIBLE;
}
if (Worker::add(['priority' => $priority, 'force_priority' => true], 'APDelivery', '', 0, $delivery['inbox'], 0)) {
Logger::info('Missing APDelivery worker added for inbox', ['inbox' => $delivery['inbox'], 'failed' => $delivery['failed'], 'priority' => $priority]);
}
}
// Optimizing this table only last seconds
if (DI::config()->get('system', 'optimize_tables')) {
Logger::info('Optimize start');
DBA::e("OPTIMIZE TABLE `post-delivery`");
Logger::info('Optimize end');
}
}
/**

View file

@ -22,6 +22,8 @@
namespace Friendica;
use Dice\Dice;
use Friendica\Core\Session\Capability\IHandleSessions;
use Friendica\Core\Session\Capability\IHandleUserSessions;
use Friendica\Navigation\SystemMessages;
use Psr\Log\LoggerInterface;
@ -211,14 +213,16 @@ abstract class DI
return self::$dice->create(Core\Worker\Repository\Process::class);
}
/**
* @return Core\Session\Capability\IHandleSessions
*/
public static function session()
public static function session(): IHandleSessions
{
return self::$dice->create(Core\Session\Capability\IHandleSessions::class);
}
public static function userSession(): IHandleUserSessions
{
return self::$dice->create(Core\Session\Capability\IHandleUserSessions::class);
}
/**
* @return \Friendica\Core\Storage\Repository\StorageManager
*/
@ -375,6 +379,14 @@ abstract class DI
return self::$dice->create(Factory\Api\Mastodon\Status::class);
}
/**
* @return Factory\Api\Mastodon\StatusSource
*/
public static function mstdnStatusSource()
{
return self::$dice->create(Factory\Api\Mastodon\StatusSource::class);
}
/**
* @return Factory\Api\Mastodon\ScheduledStatus
*/
@ -587,6 +599,11 @@ abstract class DI
return self::$dice->create(Protocol\Activity::class);
}
public static function dsprContact(): Protocol\Diaspora\Repository\DiasporaContact
{
return self::$dice->create(Protocol\Diaspora\Repository\DiasporaContact::class);
}
//
// "Security" namespace instances
//

View file

@ -218,7 +218,7 @@ class DBA
/**
* Check if data exists
*
* @param string $table Table name in format schema.table (while scheme is optiona)
* @param string $table Table name in format schema.table (where schema is optional)
* @param array $condition Array of fields for condition
* @return boolean Are there rows for that condition?
* @throws \Exception
@ -289,7 +289,7 @@ class DBA
/**
* Insert a row into a table
*
* @param string $table Table name in format schema.table (while scheme is optiona)
* @param string $table Table name in format schema.table (where schema is optional)
* @param array $param parameter array
* @param int $duplicate_mode What to do on a duplicated entry
* @return boolean was the insert successful?
@ -304,7 +304,7 @@ class DBA
* Inserts a row with the provided data in the provided table.
* If the data corresponds to an existing row through a UNIQUE or PRIMARY index constraints, it updates the row instead.
*
* @param string $table Table name in format schema.table (while scheme is optiona)
* @param string $table Table name in format schema.table (where schema is optional)
* @param array $param parameter array
* @return boolean was the insert successful?
* @throws \Exception
@ -329,7 +329,7 @@ class DBA
*
* This function can be extended in the future to accept a table array as well.
*
* @param string $table Table name in format schema.table (while scheme is optiona)
* @param string $table Table name in format schema.table (where schema is optional)
* @return boolean was the lock successful?
* @throws \Exception
*/
@ -414,7 +414,7 @@ class DBA
* Only set $old_fields to a boolean value when you are sure that you will update a single row.
* When you set $old_fields to "true" then $fields must contain all relevant fields!
*
* @param string $table Table name in format schema.table (while scheme is optiona)
* @param string $table Table name in format schema.table (where schema is optional)
* @param array $fields contains the fields that are updated
* @param array $condition condition array with the key values
* @param array|boolean $old_fields array with the old field values that are about to be replaced (true = update on duplicate, false = don't update identical fields)
@ -431,7 +431,7 @@ class DBA
/**
* Retrieve a single record from a table and returns it in an associative array
*
* @param string|array $table Table name in format schema.table (while scheme is optiona)
* @param string|array $table Table name in format schema.table (where schema is optional)
* @param array $fields
* @param array $condition
* @param array $params
@ -447,7 +447,7 @@ class DBA
/**
* Select rows from a table and fills an array with the data
*
* @param string $table Table name in format schema.table (while scheme is optiona)
* @param string $table Table name in format schema.table (where schema is optional)
* @param array $fields Array of selected fields, empty for all
* @param array $condition Array of fields for condition
* @param array $params Array of several parameters
@ -464,7 +464,7 @@ class DBA
/**
* Select rows from a table
*
* @param string $table Table name in format schema.table (while scheme is optiona)
* @param string $table Table name in format schema.table (where schema is optional)
* @param array $fields Array of selected fields, empty for all
* @param array $condition Array of fields for condition
* @param array $params Array of several parameters
@ -492,7 +492,7 @@ class DBA
/**
* Counts the rows from a table satisfying the provided condition
*
* @param string $table Table name in format schema.table (while scheme is optiona)
* @param string $table Table name in format schema.table (where schema is optional)
* @param array $condition array of fields for condition
* @param array $params Array of several parameters
*

View file

@ -74,7 +74,7 @@ class DBStructure
$old_tables = ['fserver', 'gcign', 'gcontact', 'gcontact-relation', 'gfollower' ,'glink', 'item-delivery-data',
'item-activity', 'item-content', 'item_id', 'participation', 'poll', 'poll_result', 'queue', 'retriever_rule',
'deliverq', 'dsprphotoq', 'ffinder', 'sign', 'spam', 'term', 'user-item', 'thread', 'item', 'challenge',
'auth_codes', 'tokens', 'clients', 'profile_check', 'host', 'conversation'];
'auth_codes', 'tokens', 'clients', 'profile_check', 'host', 'conversation', 'fcontact'];
$tables = DBA::selectToArray('INFORMATION_SCHEMA.TABLES', ['TABLE_NAME'],
['TABLE_SCHEMA' => DBA::databaseName(), 'TABLE_TYPE' => 'BASE TABLE']);
@ -150,7 +150,7 @@ class DBStructure
echo DI::l10n()->t("\nError %d occurred during database update:\n%s\n",
DBA::errorNo(), DBA::errorMessage());
return DI::l10n()->t('Errors encountered performing database changes: ') . $message . EOL;
return DI::l10n()->t('Errors encountered performing database changes: ') . $message . '<br />';
}
/**

View file

@ -36,18 +36,19 @@ use PDO;
use PDOException;
use PDOStatement;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
/**
* This class is for the low level database stuff that does driver specific things.
*/
class Database
{
const PDO = 'pdo';
const PDO = 'pdo';
const MYSQLI = 'mysqli';
const INSERT_DEFAULT = 0;
const INSERT_UPDATE = 1;
const INSERT_IGNORE = 2;
const INSERT_UPDATE = 1;
const INSERT_IGNORE = 2;
protected $connected = false;
@ -63,32 +64,34 @@ class Database
* @var LoggerInterface
*/
protected $logger;
protected $server_info = '';
protected $server_info = '';
/** @var PDO|mysqli */
protected $connection;
protected $driver = '';
protected $pdo_emulate_prepares = false;
private $error = '';
private $errorno = 0;
private $affected_rows = 0;
private $error = '';
private $errorno = 0;
private $affected_rows = 0;
protected $in_transaction = false;
protected $in_retrial = false;
protected $testmode = false;
private $relation = [];
protected $in_retrial = false;
protected $testmode = false;
private $relation = [];
/** @var DbaDefinition */
protected $dbaDefinition;
/** @var ViewDefinition */
protected $viewDefinition;
public function __construct(Cache $configCache, Profiler $profiler, DbaDefinition $dbaDefinition, ViewDefinition $viewDefinition, LoggerInterface $logger)
public function __construct(Cache $configCache, Profiler $profiler, DbaDefinition $dbaDefinition, ViewDefinition $viewDefinition)
{
// We are storing these values for being able to perform a reconnect
$this->configCache = $configCache;
$this->profiler = $profiler;
$this->logger = $logger;
$this->dbaDefinition = $dbaDefinition;
$this->viewDefinition = $viewDefinition;
// Temporary NullLogger until we can fetch the logger class from the config
$this->logger = new NullLogger();
$this->connect();
}
@ -107,25 +110,24 @@ class Database
$this->connected = false;
$port = 0;
$serveraddr = trim($this->configCache->get('database', 'hostname'));
$serveraddr = trim($this->configCache->get('database', 'hostname') ?? '');
$serverdata = explode(':', $serveraddr);
$server = $serverdata[0];
$host = trim($serverdata[0]);
if (count($serverdata) > 1) {
$port = trim($serverdata[1]);
}
if (!empty(trim($this->configCache->get('database', 'port')))) {
$port = trim($this->configCache->get('database', 'port'));
if (trim($this->configCache->get('database', 'port') ?? 0)) {
$port = trim($this->configCache->get('database', 'port') ?? 0);
}
$server = trim($server);
$user = trim($this->configCache->get('database', 'username'));
$pass = trim($this->configCache->get('database', 'password'));
$db = trim($this->configCache->get('database', 'database'));
$charset = trim($this->configCache->get('database', 'charset'));
$socket = trim($this->configCache->get('database', 'socket'));
$user = trim($this->configCache->get('database', 'username'));
$pass = trim($this->configCache->get('database', 'password'));
$database = trim($this->configCache->get('database', 'database'));
$charset = trim($this->configCache->get('database', 'charset'));
$socket = trim($this->configCache->get('database', 'socket'));
if (!(strlen($server) && strlen($user))) {
if (!$host && !$socket || !$user) {
return false;
}
@ -135,19 +137,20 @@ class Database
if (!$this->configCache->get('database', 'disable_pdo') && class_exists('\PDO') && in_array('mysql', PDO::getAvailableDrivers())) {
$this->driver = self::PDO;
$connect = "mysql:host=" . $server . ";dbname=" . $db;
if ($port > 0) {
$connect .= ";port=" . $port;
if ($socket) {
$connect = 'mysql:unix_socket=' . $socket;
} else {
$connect = 'mysql:host=' . $host;
if ($port > 0) {
$connect .= ';port=' . $port;
}
}
if ($charset) {
$connect .= ";charset=" . $charset;
$connect .= ';charset=' . $charset;
}
if ($socket) {
$connect .= ";$unix_socket=" . $socket;
}
$connect .= ';dbname=' . $database;
try {
$this->connection = @new PDO($connect, $user, $pass, [PDO::ATTR_PERSISTENT => $persistent]);
@ -162,10 +165,12 @@ class Database
if (!$this->connected && class_exists('\mysqli')) {
$this->driver = self::MYSQLI;
if ($port > 0) {
$this->connection = @new mysqli($server, $user, $pass, $db, $port);
if ($socket) {
$this->connection = @new mysqli(null, $user, $pass, $database, null, $socket);
} elseif ($port > 0) {
$this->connection = @new mysqli($host, $user, $pass, $database, $port);
} else {
$this->connection = @new mysqli($server, $user, $pass, $db);
$this->connection = @new mysqli($host, $user, $pass, $database);
}
if (!mysqli_connect_errno()) {
@ -174,11 +179,6 @@ class Database
if ($charset) {
$this->connection->set_charset($charset);
}
if ($socket) {
$this->connection->set_socket($socket);
}
}
}
@ -195,6 +195,7 @@ class Database
{
$this->testmode = $test;
}
/**
* Sets the logger for DBA
*
@ -219,6 +220,7 @@ class Database
{
$this->profiler = $profiler;
}
/**
* Disconnects the current database connection
*/
@ -335,12 +337,12 @@ class Database
}
$watchlist = explode(',', $this->configCache->get('system', 'db_log_index_watch'));
$denylist = explode(',', $this->configCache->get('system', 'db_log_index_denylist'));
$denylist = explode(',', $this->configCache->get('system', 'db_log_index_denylist'));
while ($row = $this->fetch($r)) {
if ((intval($this->configCache->get('system', 'db_loglimit_index')) > 0)) {
$log = (in_array($row['key'], $watchlist) &&
($row['rows'] >= intval($this->configCache->get('system', 'db_loglimit_index'))));
($row['rows'] >= intval($this->configCache->get('system', 'db_loglimit_index'))));
} else {
$log = false;
}
@ -355,11 +357,15 @@ class Database
if ($log) {
$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
@file_put_contents($this->configCache->get('system', 'db_log_index'), DateTimeFormat::utcNow() . "\t" .
$row['key'] . "\t" . $row['rows'] . "\t" . $row['Extra'] . "\t" .
basename($backtrace[1]["file"]) . "\t" .
$backtrace[1]["line"] . "\t" . $backtrace[2]["function"] . "\t" .
substr($query, 0, 4000) . "\n", FILE_APPEND);
@file_put_contents(
$this->configCache->get('system', 'db_log_index'),
DateTimeFormat::utcNow() . "\t" .
$row['key'] . "\t" . $row['rows'] . "\t" . $row['Extra'] . "\t" .
basename($backtrace[1]["file"]) . "\t" .
$backtrace[1]["line"] . "\t" . $backtrace[2]["function"] . "\t" .
substr($query, 0, 4000) . "\n",
FILE_APPEND
);
}
}
}
@ -446,7 +452,7 @@ class Database
{
$server_info = $this->serverInfo();
if (version_compare($server_info, '5.7.5', '<') ||
(stripos($server_info, 'MariaDB') !== false)) {
(stripos($server_info, 'MariaDB') !== false)) {
$sql = str_ireplace('ANY_VALUE(', 'MIN(', $sql);
}
return $sql;
@ -644,7 +650,7 @@ class Database
} elseif (is_string($args[$param])) {
$param_types .= 's';
} elseif (is_object($args[$param]) && method_exists($args[$param], '__toString')) {
$param_types .= 's';
$param_types .= 's';
$args[$param] = (string)$args[$param];
} else {
$param_types .= 'b';
@ -740,10 +746,14 @@ class Database
$duration = round($duration, 3);
$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
@file_put_contents($this->configCache->get('system', 'db_log'), DateTimeFormat::utcNow() . "\t" . $duration . "\t" .
basename($backtrace[1]["file"]) . "\t" .
$backtrace[1]["line"] . "\t" . $backtrace[2]["function"] . "\t" .
substr($this->replaceParameters($sql, $args), 0, 4000) . "\n", FILE_APPEND);
@file_put_contents(
$this->configCache->get('system', 'db_log'),
DateTimeFormat::utcNow() . "\t" . $duration . "\t" .
basename($backtrace[1]["file"]) . "\t" .
$backtrace[1]["line"] . "\t" . $backtrace[2]["function"] . "\t" .
substr($this->replaceParameters($sql, $args), 0, 4000) . "\n",
FILE_APPEND
);
}
}
return $retval;
@ -823,7 +833,7 @@ class Database
/**
* Check if data exists
*
* @param string $table Table name in format schema.table (while scheme is optiona)
* @param string $table Table name in format [schema.]table
* @param array $condition Array of fields for condition
*
* @return boolean Are there rows for that condition?
@ -1007,7 +1017,7 @@ class Database
/**
* Insert a row into a table. Field value objects will be cast as string.
*
* @param string $table Table name in format schema.table (while scheme is optiona)
* @param string $table Table name in format [schema.]table
* @param array $param parameter array
* @param int $duplicate_mode What to do on a duplicated entry
*
@ -1058,7 +1068,7 @@ class Database
* Inserts a row with the provided data in the provided table.
* If the data corresponds to an existing row through a UNIQUE or PRIMARY index constraints, it updates the row instead.
*
* @param string $table Table name in format schema.table (while scheme is optiona)
* @param string $table Table name in format [schema.]table
* @param array $param parameter array
* @return boolean was the insert successful?
* @throws \Exception
@ -1106,7 +1116,7 @@ class Database
*
* This function can be extended in the future to accept a table array as well.
*
* @param string $table Table name in format schema.table (while scheme is optiona)
* @param string $table Table name in format [schema.]table
* @return boolean was the lock successful?
* @throws \Exception
*/
@ -1304,7 +1314,7 @@ class Database
* Only set $old_fields to a boolean value when you are sure that you will update a single row.
* When you set $old_fields to "true" then $fields must contain all relevant fields!
*
* @param string $table Table name in format schema.table (while scheme is optiona)
* @param string $table Table name in format [schema.]table
* @param array $fields contains the fields that are updated
* @param array $condition condition array with the key values
* @param array|boolean $old_fields array with the old field values that are about to be replaced (true = update on duplicate, false = don't update identical fields)
@ -1362,7 +1372,7 @@ class Database
. $condition_string;
// Combines the updated fields parameter values with the condition parameter values
$params = array_merge(array_values($fields), $condition);
$params = array_merge(array_values($fields), $condition);
return $this->e($sql, $params);
}
@ -1370,7 +1380,7 @@ class Database
/**
* Retrieve a single record from a table and returns it in an associative array
*
* @param string $table Table name in format schema.table (while scheme is optiona)
* @param string $table Table name in format [schema.]table
* @param array $fields Array of selected fields, empty for all
* @param array $condition Array of fields for condition
* @param array $params Array of several parameters
@ -1396,7 +1406,7 @@ class Database
/**
* Select rows from a table and fills an array with the data
*
* @param string $table Table name in format schema.table (while scheme is optiona)
* @param string $table Table name in format [schema.]table
* @param array $fields Array of selected fields, empty for all
* @param array $condition Array of fields for condition
* @param array $params Array of several parameters
@ -1412,8 +1422,8 @@ class Database
/**
* Escape fields, adding special treatment for "group by" handling
*
* @param array $fields
* @param array $options
* @param array $fields
* @param array $options
* @return array Escaped fields
*/
private function escapeFields(array $fields, array $options): array
@ -1435,8 +1445,7 @@ class Database
}
}
array_walk($fields, function(&$value, $key) use ($options)
{
array_walk($fields, function (&$value, $key) use ($options) {
$field = $value;
$value = DBA::quoteIdentifier($field);
@ -1470,7 +1479,7 @@ class Database
*
* $data = DBA::select($table, $fields, $condition, $params);
*
* @param string $table Table name in format schema.table (while scheme is optiona)
* @param string $table Table name in format [schema.]table
* @param array $fields Array of selected fields, empty for all
* @param array $condition Array of fields for condition
* @param array $params Array of several parameters
@ -1484,7 +1493,7 @@ class Database
}
if (count($fields) > 0) {
$fields = $this->escapeFields($fields, $params);
$fields = $this->escapeFields($fields, $params);
$select_string = implode(', ', $fields);
} else {
$select_string = '*';
@ -1510,7 +1519,7 @@ class Database
/**
* Counts the rows from a table satisfying the provided condition
*
* @param string $table Table name in format schema.table (while scheme is optiona)
* @param string $table Table name in format [schema.]table
* @param array $condition Array of fields for condition
* @param array $params Array of several parameters
*
@ -1824,16 +1833,16 @@ class Database
/**
* Replaces a string in the provided fields of the provided table
*
* @param string $table Table name
* @param array $fields List of field names in the provided table
* @param string $search String to search for
* @param string $table Table name
* @param array $fields List of field names in the provided table
* @param string $search String to search for
* @param string $replace String to replace with
* @return void
* @throws \Exception
*/
public function replaceInTableFields(string $table, array $fields, string $search, string $replace)
{
$search = $this->escape($search);
$search = $this->escape($search);
$replace = $this->escape($replace);
$upd = [];

View file

@ -359,7 +359,7 @@ class PostUpdate
}
}
Tag::store($term['uri-id'], $term['type'], $term['term'], $term['url'], false);
Tag::store($term['uri-id'], $term['type'], $term['term'], $term['url']);
$id = $term['tid'];
++$rows;
@ -563,7 +563,7 @@ class PostUpdate
$items = DBA::p("SELECT `item`.`id`, `item`.`verb` AS `item-verb`, `item-content`.`verb`, `item-activity`.`activity`
FROM `item` LEFT JOIN `item-content` ON `item-content`.`uri-id` = `item`.`uri-id`
LEFT JOIN `item-activity` ON `item-activity`.`uri-id` = `item`.`uri-id` AND `item`.`gravity` = ?
WHERE `item`.`id` >= ? AND `item`.`vid` IS NULL ORDER BY `item`.`id` LIMIT 10000", GRAVITY_ACTIVITY, $id);
WHERE `item`.`id` >= ? AND `item`.`vid` IS NULL ORDER BY `item`.`id` LIMIT 10000", Item::GRAVITY_ACTIVITY, $id);
if (DBA::errorNo() != 0) {
Logger::error('Database error', ['no' => DBA::errorNo(), 'message' => DBA::errorMessage()]);
@ -899,6 +899,11 @@ class PostUpdate
return true;
}
if (!DBStructure::existsTable('fcontact')) {
DI::config()->set('system', 'post_update_version', 1425);
return true;
}
$condition = ["`uri-id` IS NULL"];
Logger::info('Start', ['rest' => DBA::count('fcontact', $condition)]);

View file

@ -23,6 +23,7 @@ namespace Friendica\Factory\Api\Friendica;
use Friendica\BaseFactory;
use Friendica\Database\DBA;
use Friendica\Model\Item;
use Friendica\Model\Post;
use Friendica\Network\HTTPException;
use Friendica\Protocol\Activity;
@ -62,7 +63,7 @@ class Activities extends BaseFactory
'announce' => [],
];
$condition = ['uid' => $uid, 'thr-parent-id' => $uriId, 'gravity' => GRAVITY_ACTIVITY];
$condition = ['uid' => $uid, 'thr-parent-id' => $uriId, 'gravity' => Item::GRAVITY_ACTIVITY];
$ret = Post::selectForUser($uid, ['author-id', 'verb'], $condition);

View file

@ -25,6 +25,7 @@ use Friendica\App\BaseURL;
use Friendica\BaseFactory;
use Friendica\Database\DBA;
use Friendica\Factory\Api\Twitter\Status;
use Friendica\Model\Item;
use Friendica\Model\Photo as ModelPhoto;
use Friendica\Model\Post;
use Friendica\Network\HTTPException;
@ -116,7 +117,7 @@ class Photo extends BaseFactory
// retrieve comments on photo
$condition = ["`parent` = ? AND `uid` = ? AND `gravity` IN (?, ?)",
$item['parent'], $uid, GRAVITY_PARENT, GRAVITY_COMMENT];
$item['parent'], $uid, Item::GRAVITY_PARENT, Item::GRAVITY_COMMENT];
$statuses = Post::selectForUser($uid, [], $condition);

View file

@ -94,12 +94,18 @@ class Attachment extends BaseFactory
*/
public function createFromPhoto(int $id): array
{
$photo = Photo::selectFirst(['resource-id', 'uid', 'id', 'title', 'type'], ['id' => $id]);
$photo = Photo::selectFirst(['resource-id', 'uid', 'id', 'title', 'type', 'width', 'height', 'blurhash'], ['id' => $id]);
if (empty($photo)) {
return [];
}
$attachment = ['id' => $photo['id'], 'description' => $photo['title']];
$attachment = [
'id' => $photo['id'],
'description' => $photo['title'],
'width' => $photo['width'],
'height' => $photo['height'],
'blurhash' => $photo['blurhash'],
];
$photoTypes = Images::supportedTypes();
$ext = $photoTypes[$photo['type']];
@ -113,7 +119,6 @@ class Attachment extends BaseFactory
$preview_url = '';
}
$object = new \Friendica\Object\Api\Mastodon\Attachment($attachment, 'image', $url, $preview_url, '');
return $object->toArray();
}

View file

@ -30,13 +30,14 @@ use Friendica\Util\Strings;
class Card extends BaseFactory
{
/**
* @param int $uriId Uri-ID of the item
* @param int $uriId Uri-ID of the item
* @param array $history Link request history
*
* @return \Friendica\Object\Api\Mastodon\Card
* @throws HTTPException\InternalServerErrorException
* @throws \ImagickException*@throws \Exception
*/
public function createFromUriId(int $uriId): \Friendica\Object\Api\Mastodon\Card
public function createFromUriId(int $uriId, array $history = []): \Friendica\Object\Api\Mastodon\Card
{
$item = Post::selectFirst(['body'], ['uri-id' => $uriId]);
if (!empty($item['body'])) {
@ -73,9 +74,10 @@ class Card extends BaseFactory
$data['image'] = $attached['preview'];
$data['width'] = $attached['preview-width'];
$data['height'] = $attached['preview-height'];
$data['blurhash'] = $attached['blurhash'];
}
}
return new \Friendica\Object\Api\Mastodon\Card($data);
return new \Friendica\Object\Api\Mastodon\Card($data, $history);
}
}

View file

@ -70,7 +70,7 @@ class Notification extends BaseFactory
$status = null;
}
return new MstdnNotification($Notification->id, $type, $Notification->created, $account, $status);
return new MstdnNotification($Notification->id, $type, $Notification->created, $account, $status, $Notification->dismissed);
}
/**
@ -83,7 +83,15 @@ class Notification extends BaseFactory
public static function getType(Entity\Notification $Notification): string
{
if (($Notification->verb == Activity::FOLLOW) && ($Notification->type === Post\UserNotification::TYPE_NONE)) {
$contact = Contact::getById($Notification->actorId, ['pending']);
$contact = Contact::getById($Notification->actorId, ['pending', 'uri-id', 'uid']);
if (($contact['uid'] == 0) && !empty($contact['uri-id'])) {
$contact = Contact::selectFirst(['pending'], ['uri-id' => $contact['uri-id'], 'uid' => $Notification->uid]);
}
if (!isset($contact['pending'])) {
return '';
}
$type = $contact['pending'] ? MstdnNotification::TYPE_INTRODUCTION : MstdnNotification::TYPE_FOLLOW;
} elseif (($Notification->verb == Activity::ANNOUNCE) &&
in_array($Notification->type, [Post\UserNotification::TYPE_DIRECT_COMMENT, Post\UserNotification::TYPE_DIRECT_THREAD_COMMENT])) {

View file

@ -39,12 +39,13 @@ class Relationship extends BaseFactory
{
$cdata = Contact::getPublicAndUserContactID($contactId, $uid);
if (!empty($cdata)) {
$cid = $cdata['user'];
$cid = $cdata['user'];
$pcid = $cdata['public'];
} else {
$cid = $contactId;
$pcid = $cid = $contactId;
}
return new RelationshipEntity($cdata['public'], Contact::getById($cid),
return new RelationshipEntity($pcid, Contact::getById($cid),
Contact\User::isBlocked($cid, $uid), Contact\User::isIgnored($cid, $uid));
}
}

View file

@ -24,6 +24,7 @@ namespace Friendica\Factory\Api\Mastodon;
use Friendica\BaseFactory;
use Friendica\Database\Database;
use Friendica\DI;
use Friendica\Model\Item;
use Friendica\Model\ItemURI;
use Friendica\Model\Photo;
use Friendica\Model\Post;
@ -69,7 +70,7 @@ class ScheduledStatus extends BaseFactory
$media_attachments[] = DI::mstdnAttachment()->createFromPhoto($id);
}
if (isset($parameters['item']['thr-parent']) && ($parameters['item']['gravity'] ?? GRAVITY_PARENT != GRAVITY_PARENT)) {
if (isset($parameters['item']['thr-parent']) && ($parameters['item']['gravity'] ?? Item::GRAVITY_PARENT != Item::GRAVITY_PARENT)) {
$in_reply_to_id = ItemURI::getIdByURI($parameters['item']['thr-parent']);
} else {
$in_reply_to_id = null;

View file

@ -23,9 +23,10 @@ namespace Friendica\Factory\Api\Mastodon;
use Friendica\BaseFactory;
use Friendica\Content\ContactSelector;
use Friendica\Content\Text\BBCode;
use Friendica\Content\Item as ContentItem;
use Friendica\Database\Database;
use Friendica\Database\DBA;
use Friendica\Model\Item;
use Friendica\Model\Post;
use Friendica\Model\Tag as TagModel;
use Friendica\Model\Verb;
@ -53,11 +54,14 @@ class Status extends BaseFactory
private $mstdnErrorFactory;
/** @var Poll */
private $mstdnPollFactory;
/** @var ContentItem */
private $contentItem;
public function __construct(LoggerInterface $logger, Database $dba,
Account $mstdnAccountFactory, Mention $mstdnMentionFactory,
Tag $mstdnTagFactory, Card $mstdnCardFactory,
Attachment $mstdnAttachementFactory, Error $mstdnErrorFactory, Poll $mstdnPollFactory)
Attachment $mstdnAttachementFactory, Error $mstdnErrorFactory,
Poll $mstdnPollFactory, ContentItem $contentItem)
{
parent::__construct($logger);
$this->dba = $dba;
@ -68,20 +72,22 @@ class Status extends BaseFactory
$this->mstdnAttachementFactory = $mstdnAttachementFactory;
$this->mstdnErrorFactory = $mstdnErrorFactory;
$this->mstdnPollFactory = $mstdnPollFactory;
$this->contentItem = $contentItem;
}
/**
* @param int $uriId Uri-ID of the item
* @param int $uid Item user
* @param int $uriId Uri-ID of the item
* @param int $uid Item user
* @param bool $reblog Check for reblogged post
*
* @return \Friendica\Object\Api\Mastodon\Status
* @throws HTTPException\InternalServerErrorException
* @throws ImagickException|HTTPException\NotFoundException
*/
public function createFromUriId(int $uriId, int $uid = 0): \Friendica\Object\Api\Mastodon\Status
public function createFromUriId(int $uriId, int $uid = 0, bool $reblog = true): \Friendica\Object\Api\Mastodon\Status
{
$fields = ['uri-id', 'uid', 'author-id', 'author-uri-id', 'author-link', 'starred', 'app', 'title', 'body', 'raw-body', 'content-warning', 'question-id',
'created', 'network', 'thr-parent-id', 'parent-author-id', 'language', 'uri', 'plink', 'private', 'vid', 'gravity', 'featured', 'has-media'];
$fields = ['uri-id', 'uid', 'author-id', 'causer-id', 'author-uri-id', 'author-link', 'causer-uri-id', 'post-reason', 'starred', 'app', 'title', 'body', 'raw-body', 'content-warning', 'question-id',
'created', 'network', 'thr-parent-id', 'parent-author-id', 'language', 'uri', 'plink', 'private', 'vid', 'gravity', 'featured', 'has-media', 'quote-uri-id'];
$item = Post::selectFirst($fields, ['uri-id' => $uriId, 'uid' => [0, $uid]], ['order' => ['uid' => true]]);
if (!$item) {
$mail = DBA::selectFirst('mail', ['id'], ['uri-id' => $uriId, 'uid' => $uid]);
@ -90,23 +96,35 @@ class Status extends BaseFactory
}
throw new HTTPException\NotFoundException('Item with URI ID ' . $uriId . ' not found' . ($uid ? ' for user ' . $uid : '.'));
}
$account = $this->mstdnAccountFactory->createFromUriId($item['author-uri-id'], $uid);
if (($item['gravity'] == Item::GRAVITY_ACTIVITY) && ($item['vid'] == Verb::getID(Activity::ANNOUNCE))) {
$is_reshare = true;
$account = $this->mstdnAccountFactory->createFromUriId($item['author-uri-id'], $uid);
$uriId = $item['thr-parent-id'];
$item = Post::selectFirst($fields, ['uri-id' => $uriId, 'uid' => [0, $uid]], ['order' => ['uid' => true]]);
if (!$item) {
throw new HTTPException\NotFoundException('Item with URI ID ' . $uriId . ' not found' . ($uid ? ' for user ' . $uid : '.'));
}
} else {
$is_reshare = $reblog && !is_null($item['causer-uri-id']) && ($item['causer-id'] != $item['author-id']) && ($item['post-reason'] == Item::PR_ANNOUNCEMENT);
$account = $this->mstdnAccountFactory->createFromUriId($is_reshare ? $item['causer-uri-id'] : $item['author-uri-id'], $uid);
}
$count_announce = Post::countPosts([
'thr-parent-id' => $uriId,
'gravity' => GRAVITY_ACTIVITY,
'gravity' => Item::GRAVITY_ACTIVITY,
'vid' => Verb::getID(Activity::ANNOUNCE),
'deleted' => false
], []);
$count_like = Post::countPosts([
'thr-parent-id' => $uriId,
'gravity' => GRAVITY_ACTIVITY,
'gravity' => Item::GRAVITY_ACTIVITY,
'vid' => Verb::getID(Activity::LIKE),
'deleted' => false
], []);
$counts = new \Friendica\Object\Api\Mastodon\Status\Counts(
Post::countPosts(['thr-parent-id' => $uriId, 'gravity' => GRAVITY_COMMENT, 'deleted' => false], []),
Post::countPosts(['thr-parent-id' => $uriId, 'gravity' => Item::GRAVITY_COMMENT, 'deleted' => false], []),
$count_announce,
$count_like
);
@ -115,7 +133,7 @@ class Status extends BaseFactory
'thr-parent-id' => $uriId,
'uid' => $uid,
'origin' => true,
'gravity' => GRAVITY_ACTIVITY,
'gravity' => Item::GRAVITY_ACTIVITY,
'vid' => Verb::getID(Activity::LIKE),
'deleted' => false
]);
@ -123,7 +141,7 @@ class Status extends BaseFactory
'thr-parent-id' => $uriId,
'uid' => $uid,
'origin' => true,
'gravity' => GRAVITY_ACTIVITY,
'gravity' => Item::GRAVITY_ACTIVITY,
'vid' => Verb::getID(Activity::ANNOUNCE),
'deleted' => false
]);
@ -131,7 +149,7 @@ class Status extends BaseFactory
$origin_like,
$origin_announce,
Post\ThreadUser::getIgnored($uriId, $uid),
(bool)($item['starred'] && ($item['gravity'] == GRAVITY_PARENT)),
(bool)($item['starred'] && ($item['gravity'] == Item::GRAVITY_PARENT)),
$item['featured']
);
@ -154,26 +172,38 @@ class Status extends BaseFactory
$poll = null;
}
$shared = BBCode::fetchShareAttributes($item['body']);
if (!empty($shared['guid'])) {
$shared_item = Post::selectFirst(['uri-id', 'plink'], ['guid' => $shared['guid']]);
$shared = $this->contentItem->getSharedPost($item, ['uri-id']);
if (!empty($shared)) {
$shared_uri_id = $shared['post']['uri-id'];
$shared_uri_id = $shared_item['uri-id'] ?? 0;
foreach ($this->mstdnMentionFactory->createFromUriId($shared_uri_id)->getArrayCopy() as $mention) {
if (!in_array($mention, $mentions)) {
$mentions[] = $mention;
}
}
$mentions = array_merge($mentions, $this->mstdnMentionFactory->createFromUriId($shared_uri_id)->getArrayCopy());
$tags = array_merge($tags, $this->mstdnTagFactory->createFromUriId($shared_uri_id));
$attachments = array_merge($attachments, $this->mstdnAttachementFactory->createFromUriId($shared_uri_id));
foreach ($this->mstdnTagFactory->createFromUriId($shared_uri_id) as $tag) {
if (!in_array($tag, $tags)) {
$tags[] = $tag;
}
}
foreach ($this->mstdnAttachementFactory->createFromUriId($shared_uri_id) as $attachment) {
if (!in_array($attachment, $attachments)) {
$attachments[] = $attachment;
}
}
if (empty($card->toArray())) {
$card = $this->mstdnCardFactory->createFromUriId($shared_uri_id);
}
}
if ($item['vid'] == Verb::getID(Activity::ANNOUNCE)) {
$reshare = $this->createFromUriId($item['thr-parent-id'], $uid)->toArray();
$reshared_item = Post::selectFirst(['title', 'body'], ['uri-id' => $item['thr-parent-id'],'uid' => [0, $uid]]);
$item['title'] = $reshared_item['title'] ?? $item['title'];
$item['body'] = $reshared_item['body'] ?? $item['body'];
$item['body'] = $this->contentItem->addSharedPost($item);
$item['raw-body'] = $this->contentItem->addSharedPost($item, $item['raw-body']);
if ($is_reshare) {
$reshare = $this->createFromUriId($uriId, $uid, false)->toArray();
} else {
$reshare = [];
}

View file

@ -0,0 +1,48 @@
<?php
/**
* @copyright Copyright (C) 2010-2022, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
namespace Friendica\Factory\Api\Mastodon;
use Friendica\BaseFactory;
use Friendica\Content\Text\BBCode;
use Friendica\Core\Protocol;
use Friendica\Model\Post;
use Friendica\Network\HTTPException;
class StatusSource extends BaseFactory
{
/**
* @param int $uriId Uri-ID of the item
*
* @return \Friendica\Object\Api\Mastodon\StatusSource
* @throws HTTPException\InternalServerErrorException
* @throws \ImagickException*@throws \Exception
*/
public function createFromUriId(int $uriId, int $uid): \Friendica\Object\Api\Mastodon\StatusSource
{
$post = Post::selectFirst(['uri-id', 'raw-body', 'body', 'title'], ['uri-id' => $uriId, 'uid' => [0, $uid]]);
$spoiler_text = $post['title'] ?: BBCode::toPlaintext(BBCode::getAbstract($post['body'], Protocol::ACTIVITYPUB));
$body = BBCode::toMarkdown($post['body']);
return new \Friendica\Object\Api\Mastodon\StatusSource($post['uri-id'], $body, $spoiler_text);
}
}

View file

@ -65,13 +65,13 @@ class DirectMessage extends BaseFactory
if (!empty($text_mode)) {
$title = $mail['title'];
if ($text_mode == 'html') {
$text = BBCode::convertForUriId($mail['uri-id'], $mail['body'], BBCode::API);
$text = BBCode::convertForUriId($mail['uri-id'], $mail['body'], BBCode::TWITTER_API);
} elseif ($text_mode == 'plain') {
$text = HTML::toPlaintext(BBCode::convertForUriId($mail['uri-id'], $mail['body'], BBCode::API), 0);
$text = HTML::toPlaintext(BBCode::convertForUriId($mail['uri-id'], $mail['body'], BBCode::TWITTER_API), 0);
}
} else {
$title = '';
$text = $mail['title'] . "\n" . HTML::toPlaintext(BBCode::convertForUriId($mail['uri-id'], $mail['body'], BBCode::API), 0);
$text = $mail['title'] . "\n" . HTML::toPlaintext(BBCode::convertForUriId($mail['uri-id'], $mail['body'], BBCode::TWITTER_API), 0);
}
$pcid = Contact::getPublicIdByUserId($uid);

View file

@ -22,6 +22,7 @@
namespace Friendica\Factory\Api\Twitter;
use Friendica\BaseFactory;
use Friendica\Content\Item as ContentItem;
use Friendica\Content\Text\BBCode;
use Friendica\Content\Text\HTML;
use Friendica\Database\Database;
@ -53,8 +54,10 @@ class Status extends BaseFactory
private $activities;
/** @var Activities entity */
private $attachment;
/** @var ContentItem */
private $contentItem;
public function __construct(LoggerInterface $logger, Database $dba, TwitterUser $twitteruser, Hashtag $hashtag, Media $media, Url $url, Mention $mention, Activities $activities, Attachment $attachment)
public function __construct(LoggerInterface $logger, Database $dba, TwitterUser $twitteruser, Hashtag $hashtag, Media $media, Url $url, Mention $mention, Activities $activities, Attachment $attachment, ContentItem $contentItem)
{
parent::__construct($logger);
$this->dba = $dba;
@ -65,6 +68,7 @@ class Status extends BaseFactory
$this->mention = $mention;
$this->activities = $activities;
$this->attachment = $attachment;
$this->contentItem = $contentItem;
}
/**
@ -80,7 +84,7 @@ class Status extends BaseFactory
{
$fields = ['parent-uri-id', 'uri-id', 'uid', 'author-id', 'author-link', 'author-network', 'owner-id', 'causer-id',
'starred', 'app', 'title', 'body', 'raw-body', 'created', 'network','post-reason', 'language', 'gravity',
'thr-parent-id', 'parent-author-id', 'parent-author-nick', 'uri', 'plink', 'private', 'vid', 'coord'];
'thr-parent-id', 'parent-author-id', 'parent-author-nick', 'uri', 'plink', 'private', 'vid', 'coord', 'quote-uri-id'];
$item = Post::selectFirst($fields, ['id' => $id], ['order' => ['uid' => true]]);
if (!$item) {
throw new HTTPException\NotFoundException('Item with ID ' . $id . ' not found.');
@ -128,7 +132,7 @@ class Status extends BaseFactory
$owner = $this->twitterUser->createFromContactId($item['owner-id'], $uid, true);
}
$friendica_comments = Post::countPosts(['thr-parent-id' => $item['uri-id'], 'deleted' => false, 'gravity' => GRAVITY_COMMENT]);
$friendica_comments = Post::countPosts(['thr-parent-id' => $item['uri-id'], 'deleted' => false, 'gravity' => Item::GRAVITY_COMMENT]);
$text = '';
$title = '';
@ -139,12 +143,12 @@ class Status extends BaseFactory
$title = sprintf("[h4]%s[/h4]\n", $item['title']);
}
$statusnetHtml = BBCode::convertForUriId($item['uri-id'], BBCode::setMentionsToNicknames($title . ($item['raw-body'] ?? $item['body'])), BBCode::API);
$statusnetHtml = BBCode::convertForUriId($item['uri-id'], BBCode::setMentionsToNicknames($title . ($item['raw-body'] ?? $item['body'])), BBCode::TWITTER_API);
$friendicaHtml = BBCode::convertForUriId($item['uri-id'], $title . $item['body'], BBCode::EXTERNAL);
$text .= Post\Media::addAttachmentsToBody($item['uri-id'], $item['body']);
$text .= Post\Media::addAttachmentsToBody($item['uri-id'], $this->contentItem->addSharedPost($item));
$text = trim(HTML::toPlaintext(BBCode::convertForUriId($item['uri-id'], $text, BBCode::API), 0));
$text = trim(HTML::toPlaintext(BBCode::convertForUriId($item['uri-id'], $text, BBCode::TWITTER_API), 0));
$geo = [];
@ -162,7 +166,7 @@ class Status extends BaseFactory
'thr-parent-id' => $item['uri-id'],
'uid' => $uid,
'origin' => true,
'gravity' => GRAVITY_ACTIVITY,
'gravity' => Item::GRAVITY_ACTIVITY,
'vid' => Verb::getID(Activity::LIKE),
'deleted' => false
]);
@ -178,11 +182,9 @@ class Status extends BaseFactory
$friendica_activities = $this->activities->createFromUriId($item['uri-id'], $uid);
$shared = BBCode::fetchShareAttributes($item['body']);
if (!empty($shared['guid'])) {
$shared_item = Post::selectFirst(['uri-id', 'plink'], ['guid' => $shared['guid']]);
$shared_uri_id = $shared_item['uri-id'] ?? 0;
$shared = $this->contentItem->getSharedPost($item, ['uri-id']);
if (!empty($shared)) {
$shared_uri_id = $shared['post']['uri-id'];
if ($include_entities) {
$hashtags = array_merge($hashtags, $this->hashtag->createFromUriId($shared_uri_id, $text));

View file

@ -69,7 +69,7 @@ class User extends BaseFactory
if (!$skip_status) {
$post = Post::selectFirstPost(['uri-id'],
['author-id' => $publicContact['id'], 'gravity' => [GRAVITY_COMMENT, GRAVITY_PARENT], 'private' => [Item::PUBLIC, Item::UNLISTED]],
['author-id' => $publicContact['id'], 'gravity' => [Item::GRAVITY_COMMENT, Item::GRAVITY_PARENT], 'private' => [Item::PUBLIC, Item::UNLISTED]],
['order' => ['uri-id' => true]]);
if (!empty($post['uri-id'])) {
$status = $this->status->createFromUriId($post['uri-id'], $uid)->toArray();

View file

@ -28,6 +28,7 @@ use Friendica\Core\Protocol;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\Item;
use Friendica\Network\HTTPClient\Client\HttpClientAccept;
use Friendica\Network\HTTPException;
use Friendica\Network\Probe;
@ -61,7 +62,7 @@ class APContact
'addr' => $local_owner['addr'],
'baseurl' => $local_owner['baseurl'],
'url' => $local_owner['url'],
'subscribe' => $local_owner['baseurl'] . '/follow?url={uri}'];
'subscribe' => $local_owner['baseurl'] . '/contact/follow?url={uri}'];
if (!empty($local_owner['alias']) && ($local_owner['url'] != $local_owner['alias'])) {
$data['alias'] = $local_owner['alias'];
@ -290,22 +291,19 @@ class APContact
return $fetched_contact;
}
$parts = parse_url($apcontact['url']);
unset($parts['scheme']);
unset($parts['path']);
if (empty($apcontact['addr'])) {
if (!empty($apcontact['nick']) && is_array($parts)) {
$apcontact['addr'] = $apcontact['nick'] . '@' . str_replace('//', '', Network::unparseURL($parts));
} else {
try {
$apcontact['addr'] = $apcontact['nick'] . '@' . (new Uri($apcontact['url']))->getAuthority();
} catch (\Throwable $e) {
Logger::warning('Unable to coerce APContact URL into a UriInterface object', ['url' => $apcontact['url'], 'error' => $e->getMessage()]);
$apcontact['addr'] = '';
}
}
$apcontact['pubkey'] = null;
if (!empty($compacted['w3id:publicKey'])) {
$apcontact['pubkey'] = trim(JsonLD::fetchElement($compacted['w3id:publicKey'], 'w3id:publicKeyPem', '@value'));
if (strstr($apcontact['pubkey'], 'RSA ')) {
$apcontact['pubkey'] = trim(JsonLD::fetchElement($compacted['w3id:publicKey'], 'w3id:publicKeyPem', '@value') ?? '');
if (strpos($apcontact['pubkey'], 'RSA ') !== false) {
$apcontact['pubkey'] = Crypto::rsaToPem($apcontact['pubkey']);
}
}
@ -382,7 +380,7 @@ class APContact
// kroeg:blocks, updated
// When the photo is too large, try to shorten it by removing parts
if (strlen($apcontact['photo']) > 255) {
if (strlen($apcontact['photo'] ?? '') > 255) {
$parts = parse_url($apcontact['photo']);
unset($parts['fragment']);
$apcontact['photo'] = (string)Uri::fromParts($parts);
@ -495,13 +493,13 @@ class APContact
private static function getStatusesCount(array $owner): int
{
$condition = [
'private' => [Item::PUBLIC, Item::UNLISTED],
'private' => [Item::PUBLIC, Item::UNLISTED],
'author-id' => Contact::getIdForURL($owner['url'], 0, false),
'gravity' => [GRAVITY_PARENT, GRAVITY_COMMENT],
'gravity' => [Item::GRAVITY_PARENT, Item::GRAVITY_COMMENT],
'network' => Protocol::DFRN,
'parent-network' => Protocol::FEDERATED,
'deleted' => false,
'visible' => true
'visible' => true,
];
$count = Post::countPosts($condition);
@ -573,7 +571,7 @@ class APContact
*
* @param array $apcontact
*
* @return bool
* @return bool
*/
public static function isRelay(array $apcontact): bool
{

View file

@ -29,7 +29,6 @@ use Friendica\Core\Hook;
use Friendica\Core\Logger;
use Friendica\Core\Protocol;
use Friendica\Core\Renderer;
use Friendica\Core\Session;
use Friendica\Core\System;
use Friendica\Core\Worker;
use Friendica\Database\Database;
@ -97,17 +96,17 @@ class Contact
* Relationship types
* @{
*/
const NOTHING = 0;
const FOLLOWER = 1;
const SHARING = 2;
const FRIEND = 3;
const SELF = 4;
const NOTHING = 0; // There is no relationship between the contact and the user
const FOLLOWER = 1; // The contact is following this user (the contact is the subscriber)
const SHARING = 2; // The contact shares their content with this user (the user is the subscriber)
const FRIEND = 3; // There is a mutual relationship between the contact and the user
const SELF = 4; // This is the user theirself
/**
* @}
*/
const MIRROR_DEACTIVATED = 0;
const MIRROR_FORWARDED = 1;
const MIRROR_FORWARDED = 1; // Deprecated, now does the same like MIRROR_OWN_POST
const MIRROR_OWN_POST = 2;
const MIRROR_NATIVE_RESHARE = 3;
@ -137,6 +136,18 @@ class Contact
return $contact;
}
/**
* @param array $fields Array of selected fields, empty for all
* @param array $condition Array of fields for condition
* @param array $params Array of several parameters
* @return array
* @throws \Exception
*/
public static function selectAccountToArray(array $fields = [], array $condition = [], array $params = []): array
{
return DBA::selectToArray('account-user-view', $fields, $condition, $params);
}
/**
* @param array $fields Array of selected fields, empty for all
* @param array $condition Array of fields for condition
@ -261,6 +272,32 @@ class Contact
return DBA::selectFirst('contact', $fields, ['uri-id' => $uri_id], ['order' => ['uid']]);
}
/**
* Fetch all remote contacts for a given contact url
*
* @param string $url The URL of the contact
* @param array $fields The wanted fields
*
* @return array all remote contacts
*
* @throws \Exception
*/
public static function getVisitorByUrl(string $url, array $fields = ['id', 'uid']): array
{
$remote = [];
$remote_contacts = DBA::select('contact', ['id', 'uid'], ['nurl' => Strings::normaliseLink($url), 'rel' => [Contact::FOLLOWER, Contact::FRIEND], 'self' => false]);
while ($contact = DBA::fetch($remote_contacts)) {
if (($contact['uid'] == 0) || Contact\User::isBlocked($contact['id'], $contact['uid'])) {
continue;
}
$remote[$contact['uid']] = $contact['id'];
}
DBA::close($remote_contacts);
return $remote;
}
/**
* Fetches a contact by a given url
*
@ -319,7 +356,7 @@ class Contact
// Update the contact in the background if needed
if (Probe::isProbable($contact['network']) && ($contact['next-update'] < DateTimeFormat::utcNow())) {
Worker::add(['priority' => PRIORITY_LOW, 'dont_fork' => true], 'UpdateContact', $contact['id']);
Worker::add(['priority' => Worker::PRIORITY_LOW, 'dont_fork' => true], 'UpdateContact', $contact['id']);
}
// Remove the internal fields
@ -704,7 +741,6 @@ class Contact
'notify' => DI::baseUrl() . '/dfrn_notify/' . $user['nickname'],
'poll' => DI::baseUrl() . '/dfrn_poll/' . $user['nickname'],
'confirm' => DI::baseUrl() . '/dfrn_confirm/' . $user['nickname'],
'poco' => DI::baseUrl() . '/poco/' . $user['nickname'],
'name-date' => DateTimeFormat::utcNow(),
'uri-date' => DateTimeFormat::utcNow(),
'avatar-date' => DateTimeFormat::utcNow(),
@ -786,7 +822,6 @@ class Contact
'notify' => DI::baseUrl() . '/dfrn_notify/' . $user['nickname'],
'poll' => DI::baseUrl() . '/dfrn_poll/'. $user['nickname'],
'confirm' => DI::baseUrl() . '/dfrn_confirm/' . $user['nickname'],
'poco' => DI::baseUrl() . '/poco/' . $user['nickname'],
];
@ -877,14 +912,14 @@ class Contact
self::clearFollowerFollowingEndpointCache($contact['uid']);
// Archive the contact
self::update(['archive' => true, 'network' => Protocol::PHANTOM, 'deleted' => true], ['id' => $id]);
self::update(['archive' => true, 'network' => Protocol::PHANTOM, 'rel' => self::NOTHING, 'deleted' => true], ['id' => $id]);
if (!DBA::exists('contact', ['uri-id' => $contact['uri-id'], 'deleted' => false])) {
Avatar::deleteCache($contact);
}
// Delete it in the background
Worker::add(PRIORITY_MEDIUM, 'Contact\Remove', $id);
Worker::add(Worker::PRIORITY_MEDIUM, 'Contact\Remove', $id);
}
/**
@ -908,7 +943,7 @@ class Contact
if (in_array($contact['rel'], [self::SHARING, self::FRIEND])) {
$cdata = self::getPublicAndUserContactID($contact['id'], $contact['uid']);
if (!empty($cdata['public'])) {
Worker::add(PRIORITY_HIGH, 'Contact\Unfollow', $cdata['public'], $contact['uid']);
Worker::add(Worker::PRIORITY_HIGH, 'Contact\Unfollow', $cdata['public'], $contact['uid']);
}
}
@ -938,7 +973,7 @@ class Contact
if (in_array($contact['rel'], [self::FOLLOWER, self::FRIEND])) {
$cdata = self::getPublicAndUserContactID($contact['id'], $contact['uid']);
if (!empty($cdata['public'])) {
Worker::add(PRIORITY_HIGH, 'Contact\RevokeFollow', $cdata['public'], $contact['uid']);
Worker::add(Worker::PRIORITY_HIGH, 'Contact\RevokeFollow', $cdata['public'], $contact['uid']);
}
}
@ -966,11 +1001,11 @@ class Contact
$cdata = self::getPublicAndUserContactID($contact['id'], $contact['uid']);
if (in_array($contact['rel'], [self::SHARING, self::FRIEND]) && !empty($cdata['public'])) {
Worker::add(PRIORITY_HIGH, 'Contact\Unfollow', $cdata['public'], $contact['uid']);
Worker::add(Worker::PRIORITY_HIGH, 'Contact\Unfollow', $cdata['public'], $contact['uid']);
}
if (in_array($contact['rel'], [self::FOLLOWER, self::FRIEND]) && !empty($cdata['public'])) {
Worker::add(PRIORITY_HIGH, 'Contact\RevokeFollow', $cdata['public'], $contact['uid']);
Worker::add(Worker::PRIORITY_HIGH, 'Contact\RevokeFollow', $cdata['public'], $contact['uid']);
}
self::remove($contact['id']);
@ -1103,7 +1138,7 @@ class Contact
$photos_link = '';
if ($uid == 0) {
$uid = local_user();
$uid = DI::userSession()->getLocalUserId();
}
if (empty($contact['uid']) || ($contact['uid'] != $uid)) {
@ -1124,7 +1159,7 @@ class Contact
$sparkle = false;
if (($contact['network'] === Protocol::DFRN) && !$contact['self'] && empty($contact['pending'])) {
$sparkle = true;
$profile_link = DI::baseUrl() . '/redir/' . $contact['id'];
$profile_link = 'contact/redir/' . $contact['id'];
} else {
$profile_link = $contact['url'];
}
@ -1135,25 +1170,25 @@ class Contact
if ($sparkle) {
$status_link = $profile_link . '/status';
$photos_link = str_replace('/profile/', '/photos/', $profile_link);
$photos_link = $profile_link . '/photos';
$profile_link = $profile_link . '/profile';
}
if (self::canReceivePrivateMessages($contact) && empty($contact['pending'])) {
$pm_url = DI::baseUrl() . '/message/new/' . $contact['id'];
$pm_url = 'message/new/' . $contact['id'];
}
$contact_url = DI::baseUrl() . '/contact/' . $contact['id'];
$contact_url = 'contact/' . $contact['id'];
$posts_link = DI::baseUrl() . '/contact/' . $contact['id'] . '/conversations';
$posts_link = 'contact/' . $contact['id'] . '/conversations';
$follow_link = '';
$unfollow_link = '';
if (!$contact['self'] && Protocol::supportsFollow($contact['network'])) {
if ($contact['uid'] && in_array($contact['rel'], [self::SHARING, self::FRIEND])) {
$unfollow_link = 'unfollow?url=' . urlencode($contact['url']) . '&auto=1';
$unfollow_link = 'contact/unfollow?url=' . urlencode($contact['url']) . '&auto=1';
} elseif(!$contact['pending']) {
$follow_link = 'follow?url=' . urlencode($contact['url']) . '&auto=1';
$follow_link = 'contact/follow?url=' . urlencode($contact['url']) . '&auto=1';
}
}
@ -1167,7 +1202,7 @@ class Contact
'network' => [DI::l10n()->t('Network Posts') , $posts_link , false],
'edit' => [DI::l10n()->t('View Contact') , $contact_url , false],
'follow' => [DI::l10n()->t('Connect/Follow'), $follow_link , true],
'unfollow'=> [DI::l10n()->t('UnFollow') , $unfollow_link, true],
'unfollow'=> [DI::l10n()->t('Unfollow') , $unfollow_link, true],
];
} else {
$menu = [
@ -1178,7 +1213,7 @@ class Contact
'edit' => [DI::l10n()->t('View Contact') , $contact_url , false],
'pm' => [DI::l10n()->t('Send PM') , $pm_url , false],
'follow' => [DI::l10n()->t('Connect/Follow'), $follow_link , true],
'unfollow'=> [DI::l10n()->t('UnFollow') , $unfollow_link , true],
'unfollow'=> [DI::l10n()->t('Unfollow') , $unfollow_link , true],
];
if (!empty($contact['pending'])) {
@ -1248,7 +1283,7 @@ class Contact
$contact_id = $contact['id'];
if (Probe::isProbable($contact['network']) && ($contact['next-update'] < DateTimeFormat::utcNow())) {
Worker::add(['priority' => PRIORITY_LOW, 'dont_fork' => true], 'UpdateContact', $contact['id']);
Worker::add(['priority' => Worker::PRIORITY_LOW, 'dont_fork' => true], 'UpdateContact', $contact['id']);
}
if (empty($update) && (!empty($contact['uri-id']) || is_bool($update))) {
@ -1334,9 +1369,10 @@ class Contact
'writable' => 1,
'blocked' => 0,
'readonly' => 0,
'pending' => 0];
'pending' => 0,
];
$condition = ['nurl' => Strings::normaliseLink($data["url"]), 'uid' => $uid, 'deleted' => false];
$condition = ['nurl' => Strings::normaliseLink($data['url']), 'uid' => $uid, 'deleted' => false];
// Before inserting we do check if the entry does exist now.
$contact = DBA::selectFirst('contact', ['id'], $condition, ['order' => ['id']]);
@ -1359,7 +1395,17 @@ class Contact
}
if ($data['network'] == Protocol::DIASPORA) {
FContact::updateFromProbeArray($data);
try {
DI::dsprContact()->updateFromProbeArray($data);
} catch (\InvalidArgumentException $e) {
Logger::error($e->getMessage(), ['url' => $url, 'data' => $data]);
}
} elseif (!empty($data['networks'][Protocol::DIASPORA])) {
try {
DI::dsprContact()->updateFromProbeArray($data['networks'][Protocol::DIASPORA]);
} catch (\InvalidArgumentException $e) {
Logger::error($e->getMessage(), ['url' => $url, 'data' => $data['networks'][Protocol::DIASPORA]]);
}
}
self::updateFromProbeArray($contact_id, $data);
@ -1506,10 +1552,10 @@ class Contact
if ($thread_mode) {
$condition = ["((`$contact_field` = ? AND `gravity` = ?) OR (`author-id` = ? AND `gravity` = ? AND `vid` = ? AND `thr-parent-id` = `parent-uri-id`)) AND " . $sql,
$cid, GRAVITY_PARENT, $cid, GRAVITY_ACTIVITY, Verb::getID(Activity::ANNOUNCE), local_user()];
$cid, Item::GRAVITY_PARENT, $cid, Item::GRAVITY_ACTIVITY, Verb::getID(Activity::ANNOUNCE), DI::userSession()->getLocalUserId()];
} else {
$condition = ["`$contact_field` = ? AND `gravity` IN (?, ?) AND " . $sql,
$cid, GRAVITY_PARENT, GRAVITY_COMMENT, local_user()];
$cid, Item::GRAVITY_PARENT, Item::GRAVITY_COMMENT, DI::userSession()->getLocalUserId()];
}
if (!empty($parent)) {
@ -1527,10 +1573,10 @@ class Contact
}
if (DI::mode()->isMobile()) {
$itemsPerPage = DI::pConfig()->get(local_user(), 'system', 'itemspage_mobile_network',
$itemsPerPage = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'system', 'itemspage_mobile_network',
DI::config()->get('system', 'itemspage_network_mobile'));
} else {
$itemsPerPage = DI::pConfig()->get(local_user(), 'system', 'itemspage_network',
$itemsPerPage = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'system', 'itemspage_network',
DI::config()->get('system', 'itemspage_network'));
}
@ -1538,7 +1584,7 @@ class Contact
$params = ['order' => ['received' => true], 'limit' => [$pager->getStart(), $pager->getItemsPerPage()]];
if (DI::pConfig()->get(local_user(), 'system', 'infinite_scroll')) {
if (DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'system', 'infinite_scroll')) {
$tpl = Renderer::getMarkupTemplate('infinite_scroll_head.tpl');
$o = Renderer::replaceMacros($tpl, ['$reload_uri' => DI::args()->getQueryString()]);
} else {
@ -1547,27 +1593,27 @@ class Contact
if ($thread_mode) {
$fields = ['uri-id', 'thr-parent-id', 'gravity', 'author-id', 'commented'];
$items = Post::toArray(Post::selectForUser(local_user(), $fields, $condition, $params));
$items = Post::toArray(Post::selectForUser(DI::userSession()->getLocalUserId(), $fields, $condition, $params));
if ($pager->getStart() == 0) {
$cdata = self::getPublicAndUserContactID($cid, local_user());
$cdata = self::getPublicAndUserContactID($cid, DI::userSession()->getLocalUserId());
if (!empty($cdata['public'])) {
$pinned = Post\Collection::selectToArrayForContact($cdata['public'], Post\Collection::FEATURED, $fields);
$items = array_merge($items, $pinned);
}
}
$o .= DI::conversation()->create($items, 'contacts', $update, false, 'pinned_commented', local_user());
$o .= DI::conversation()->create($items, 'contacts', $update, false, 'pinned_commented', DI::userSession()->getLocalUserId());
} else {
$fields = array_merge(Item::DISPLAY_FIELDLIST, ['featured']);
$items = Post::toArray(Post::selectForUser(local_user(), $fields, $condition, $params));
$items = Post::toArray(Post::selectForUser(DI::userSession()->getLocalUserId(), $fields, $condition, $params));
if ($pager->getStart() == 0) {
$cdata = self::getPublicAndUserContactID($cid, local_user());
$cdata = self::getPublicAndUserContactID($cid, DI::userSession()->getLocalUserId());
if (!empty($cdata['public'])) {
$condition = ["`uri-id` IN (SELECT `uri-id` FROM `collection-view` WHERE `cid` = ? AND `type` = ?)",
$cdata['public'], Post\Collection::FEATURED];
$pinned = Post::toArray(Post::selectForUser(local_user(), $fields, $condition, $params));
$pinned = Post::toArray(Post::selectForUser(DI::userSession()->getLocalUserId(), $fields, $condition, $params));
$items = array_merge($pinned, $items);
}
}
@ -1576,7 +1622,7 @@ class Contact
}
if (!$update) {
if (DI::pConfig()->get(local_user(), 'system', 'infinite_scroll')) {
if (DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'system', 'infinite_scroll')) {
$o .= HTML::scrollLoader();
} else {
$o .= $pager->renderMinimal(count($items));
@ -1680,11 +1726,9 @@ class Contact
/**
* Return the photo path for a given contact array in the given size
*
* @param array $contact contact array
* @param string $field Fieldname of the photo in the contact array
* @param array $contact contact array
* @param string $size Size of the avatar picture
* @param string $avatar Avatar path that is displayed when no photo had been found
* @param bool $no_update Don't perfom an update if no cached avatar was found
* @param bool $no_update Don't perfom an update if no cached avatar was found
* @return string photo path
*/
private static function getAvatarPath(array $contact, string $size, bool $no_update = false): string
@ -1711,7 +1755,7 @@ class Contact
}
}
return self::getAvatarUrlForId($contact['id'], $size, $contact['updated'] ?? '');
return self::getAvatarUrlForId($contact['id'] ?? 0, $size, $contact['updated'] ?? '');
}
/**
@ -2023,9 +2067,10 @@ class Contact
* @param integer $cid contact id
* @param string $size One of the Proxy::SIZE_* constants
* @param string $updated Contact update date
* @param bool $static If "true" a parameter is added to convert the avatar to a static one
* @return string avatar link
*/
public static function getAvatarUrlForId(int $cid, string $size = '', string $updated = '', string $guid = ''): string
public static function getAvatarUrlForId(int $cid, string $size = '', string $updated = '', string $guid = '', bool $static = false): string
{
// We have to fetch the "updated" variable when it wasn't provided
// The parameter can be provided to improve performance
@ -2055,7 +2100,15 @@ class Contact
$url .= Proxy::PIXEL_LARGE . '/';
break;
}
return $url . ($guid ?: $cid) . ($updated ? '?ts=' . strtotime($updated) : '');
$query_params = [];
if ($updated) {
$query_params['ts'] = strtotime($updated);
}
if ($static) {
$query_params['static'] = true;
}
return $url . ($guid ?: $cid) . (!empty($query_params) ? '?' . http_build_query($query_params) : '');
}
/**
@ -2080,9 +2133,10 @@ class Contact
* @param integer $cid contact id
* @param string $size One of the Proxy::SIZE_* constants
* @param string $updated Contact update date
* @param bool $static If "true" a parameter is added to convert the header to a static one
* @return string header link
*/
public static function getHeaderUrlForId(int $cid, string $size = '', string $updated = '', string $guid = ''): string
public static function getHeaderUrlForId(int $cid, string $size = '', string $updated = '', string $guid = '', bool $static = false): string
{
// We have to fetch the "updated" variable when it wasn't provided
// The parameter can be provided to improve performance
@ -2113,7 +2167,15 @@ class Contact
break;
}
return $url . ($guid ?: $cid) . ($updated ? '?ts=' . strtotime($updated) : '');
$query_params = [];
if ($updated) {
$query_params['ts'] = strtotime($updated);
}
if ($static) {
$query_params['static'] = true;
}
return $url . ($guid ?: $cid) . (!empty($query_params) ? '?' . http_build_query($query_params) : '');
}
/**
@ -2367,7 +2429,7 @@ class Contact
return;
}
Logger::warning('account-user exists for a different contact id', ['account_user' => $account_user, 'id' => $id, 'uid' => $uid, 'uri-id' => $uri_id, 'url' => $url]);
Worker::add(PRIORITY_HIGH, 'MergeContact', $account_user['id'], $id, $uid);
Worker::add(Worker::PRIORITY_HIGH, 'MergeContact', $account_user['id'], $id, $uid);
} elseif (DBA::insert('account-user', ['id' => $id, 'uri-id' => $uri_id, 'uid' => $uid], Database::INSERT_IGNORE)) {
Logger::notice('account-user was added', ['id' => $id, 'uid' => $uid, 'uri-id' => $uri_id, 'url' => $url]);
} else {
@ -2408,7 +2470,7 @@ class Contact
continue;
}
Worker::add(PRIORITY_HIGH, 'MergeContact', $first, $duplicate['id'], $uid);
Worker::add(Worker::PRIORITY_HIGH, 'MergeContact', $first, $duplicate['id'], $uid);
}
DBA::close($duplicates);
Logger::info('Duplicates handled', ['uid' => $uid, 'nurl' => $nurl, 'callstack' => System::callstack(20)]);
@ -2431,13 +2493,23 @@ class Contact
return false;
}
$ret = Probe::uri($contact['url'], $network, $contact['uid']);
$data = Probe::uri($contact['url'], $network, $contact['uid']);
if ($ret['network'] == Protocol::DIASPORA) {
FContact::updateFromProbeArray($ret);
if ($data['network'] == Protocol::DIASPORA) {
try {
DI::dsprContact()->updateFromProbeArray($data);
} catch (\InvalidArgumentException $e) {
Logger::error($e->getMessage(), ['id' => $id, 'network' => $network, 'contact' => $contact, 'data' => $data]);
}
} elseif (!empty($data['networks'][Protocol::DIASPORA])) {
try {
DI::dsprContact()->updateFromProbeArray($data['networks'][Protocol::DIASPORA]);
} catch (\InvalidArgumentException $e) {
Logger::error($e->getMessage(), ['id' => $id, 'network' => $network, 'contact' => $contact, 'data' => $data]);
}
}
return self::updateFromProbeArray($id, $ret);
return self::updateFromProbeArray($id, $data);
}
/**
@ -2610,7 +2682,7 @@ class Contact
if ($ret['network'] == Protocol::ACTIVITYPUB) {
$apcontact = APContact::getByURL($ret['url'], false);
if (!empty($apcontact['featured'])) {
Worker::add(PRIORITY_LOW, 'FetchFeaturedPosts', $ret['url']);
Worker::add(Worker::PRIORITY_LOW, 'FetchFeaturedPosts', $ret['url']);
}
}
@ -2619,7 +2691,7 @@ class Contact
}
$update = false;
$guid = ($ret['guid'] ?? '') ?: Item::guidFromUri($ret['url'], parse_url($ret['url'], PHP_URL_HOST));
$guid = ($ret['guid'] ?? '') ?: Item::guidFromUri($ret['url']);
// make sure to not overwrite existing values with blank entries except some technical fields
$keep = ['batch', 'notify', 'poll', 'request', 'confirm', 'poco', 'baseurl'];
@ -2651,7 +2723,7 @@ class Contact
self::updateContact($id, $uid, $uriid, $contact['url'], ['failed' => false, 'local-data' => $has_local_data, 'last-update' => $updated, 'next-update' => $success_next_update, 'success_update' => $updated]);
if (Contact\Relation::isDiscoverable($ret['url'])) {
Worker::add(PRIORITY_LOW, 'ContactDiscovery', $ret['url']);
Worker::add(Worker::PRIORITY_LOW, 'ContactDiscovery', $ret['url']);
}
// Update the public contact
@ -2695,7 +2767,7 @@ class Contact
self::updateContact($id, $uid, $ret['uri-id'], $ret['url'], $ret);
if (Contact\Relation::isDiscoverable($ret['url'])) {
Worker::add(PRIORITY_LOW, 'ContactDiscovery', $ret['url']);
Worker::add(Worker::PRIORITY_LOW, 'ContactDiscovery', $ret['url']);
}
return true;
@ -2855,30 +2927,30 @@ class Contact
// do we have enough information?
if (empty($protocol) || ($protocol == Protocol::PHANTOM) || (empty($ret['url']) && empty($ret['addr']))) {
$result['message'] .= DI::l10n()->t('The profile address specified does not provide adequate information.') . EOL;
$result['message'] .= DI::l10n()->t('The profile address specified does not provide adequate information.') . '<br />';
if (empty($ret['poll'])) {
$result['message'] .= DI::l10n()->t('No compatible communication protocols or feeds were discovered.') . EOL;
$result['message'] .= DI::l10n()->t('No compatible communication protocols or feeds were discovered.') . '<br />';
}
if (empty($ret['name'])) {
$result['message'] .= DI::l10n()->t('An author or name was not found.') . EOL;
$result['message'] .= DI::l10n()->t('An author or name was not found.') . '<br />';
}
if (empty($ret['url'])) {
$result['message'] .= DI::l10n()->t('No browser URL could be matched to this address.') . EOL;
$result['message'] .= DI::l10n()->t('No browser URL could be matched to this address.') . '<br />';
}
if (strpos($ret['url'], '@') !== false) {
$result['message'] .= DI::l10n()->t('Unable to match @-style Identity Address with a known protocol or email contact.') . EOL;
$result['message'] .= DI::l10n()->t('Use mailto: in front of address to force email check.') . EOL;
$result['message'] .= DI::l10n()->t('Unable to match @-style Identity Address with a known protocol or email contact.') . '<br />';
$result['message'] .= DI::l10n()->t('Use mailto: in front of address to force email check.') . '<br />';
}
return $result;
}
if ($protocol === Protocol::OSTATUS && DI::config()->get('system', 'ostatus_disabled')) {
$result['message'] .= DI::l10n()->t('The profile address specified belongs to a network which has been disabled on this site.') . EOL;
$result['message'] .= DI::l10n()->t('The profile address specified belongs to a network which has been disabled on this site.') . '<br />';
$ret['notify'] = '';
}
if (!$ret['notify']) {
$result['message'] .= DI::l10n()->t('Limited profile. This person will be unable to receive direct/personal notifications from you.') . EOL;
$result['message'] .= DI::l10n()->t('Limited profile. This person will be unable to receive direct/personal notifications from you.') . '<br />';
}
$writeable = ((($protocol === Protocol::OSTATUS) && ($ret['notify'])) ? 1 : 0);
@ -2937,7 +3009,7 @@ class Contact
$contact = DBA::selectFirst('contact', [], ['url' => $ret['url'], 'network' => $ret['network'], 'uid' => $uid]);
if (!DBA::isResult($contact)) {
$result['message'] .= DI::l10n()->t('Unable to retrieve contact information.') . EOL;
$result['message'] .= DI::l10n()->t('Unable to retrieve contact information.') . '<br />';
return $result;
}
@ -2951,13 +3023,13 @@ class Contact
// pull feed and consume it, which should subscribe to the hub.
if ($contact['network'] == Protocol::OSTATUS) {
Worker::add(PRIORITY_HIGH, 'OnePoll', $contact_id, 'force');
Worker::add(Worker::PRIORITY_HIGH, 'OnePoll', $contact_id, 'force');
}
if ($probed) {
self::updateFromProbeArray($contact_id, $ret);
} else {
Worker::add(PRIORITY_HIGH, 'UpdateContact', $contact_id);
Worker::add(Worker::PRIORITY_HIGH, 'UpdateContact', $contact_id);
}
$result['success'] = Protocol::follow($uid, $contact, $protocol);
@ -3132,11 +3204,14 @@ class Contact
return;
}
Worker::add(Worker::PRIORITY_LOW, 'ContactDiscoveryForUser', $contact['uid']);
self::clearFollowerFollowingEndpointCache($contact['uid']);
$cdata = self::getPublicAndUserContactID($contact['id'], $contact['uid']);
DI::notification()->deleteForUserByVerb($contact['uid'], Activity::FOLLOW, ['actor-id' => $cdata['public']]);
if (!empty($cdata['public'])) {
DI::notification()->deleteForUserByVerb($contact['uid'], Activity::FOLLOW, ['actor-id' => $cdata['public']]);
}
}
/**
@ -3155,6 +3230,8 @@ class Contact
} else {
self::update(['rel' => self::FOLLOWER], ['id' => $contact['id']]);
}
Worker::add(Worker::PRIORITY_LOW, 'ContactDiscoveryForUser', $contact['uid']);
}
/**
@ -3233,7 +3310,7 @@ class Contact
*/
public static function magicLink(string $contact_url, string $url = ''): string
{
if (!Session::isAuthenticated()) {
if (!DI::userSession()->isAuthenticated()) {
return $url ?: $contact_url; // Equivalent to: ($url != '') ? $url : $contact_url;
}
@ -3279,7 +3356,7 @@ class Contact
{
$destination = $url ?: $contact['url']; // Equivalent to ($url != '') ? $url : $contact['url'];
if (!Session::isAuthenticated()) {
if (!DI::userSession()->isAuthenticated()) {
return $destination;
}
@ -3288,7 +3365,7 @@ class Contact
return $url;
}
if (DI::pConfig()->get(local_user(), 'system', 'stay_local') && ($url == '')) {
if (DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'system', 'stay_local') && ($url == '')) {
return 'contact/' . $contact['id'] . '/conversations';
}
@ -3300,7 +3377,7 @@ class Contact
return $destination;
}
$redirect = 'redir/' . $contact['id'];
$redirect = 'contact/redir/' . $contact['id'];
if (($url != '') && !Strings::compareLink($contact['url'], $url)) {
$redirect .= '?url=' . $url;
@ -3349,11 +3426,13 @@ class Contact
* @param string $search Name or nick
* @param string $mode Search mode (e.g. "community")
* @param int $uid User ID
* @param int $limit Maximum amount of returned values
* @param int $offset Limit offset
*
* @return array with search results
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function searchByName(string $search, string $mode = '', int $uid = 0): array
public static function searchByName(string $search, string $mode = '', int $uid = 0, int $limit = 0, int $offset = 0): array
{
if (empty($search)) {
return [];
@ -3373,6 +3452,8 @@ class Contact
if ($uid == 0) {
$condition['blocked'] = false;
} else {
$condition['rel'] = [Contact::SHARING, Contact::FRIEND];
}
// check if we search only communities or every contact
@ -3382,12 +3463,19 @@ class Contact
$search .= '%';
$params = [];
if (!empty($limit) && !empty($offset)) {
$params['limit'] = [$offset, $limit];
} elseif (!empty($limit)) {
$params['limit'] = $limit;
}
$condition = DBA::mergeConditions($condition,
["(NOT `unsearchable` OR `nurl` IN (SELECT `nurl` FROM `owner-view` WHERE `publish` OR `net-publish`))
AND (`addr` LIKE ? OR `name` LIKE ? OR `nick` LIKE ?)", $search, $search, $search]);
$contacts = self::selectToArray([], $condition);
return $contacts;
return self::selectToArray([], $condition, $params);
}
/**
@ -3409,10 +3497,10 @@ class Contact
}
$contact = self::getByURL($url, false, ['id', 'network', 'next-update']);
if (empty($contact['id']) && Network::isValidHttpUrl($url)) {
Worker::add(PRIORITY_LOW, 'AddContact', 0, $url);
Worker::add(Worker::PRIORITY_LOW, 'AddContact', 0, $url);
++$added;
} elseif (!empty($contact['network']) && Probe::isProbable($contact['network']) && ($contact['next-update'] < DateTimeFormat::utcNow())) {
Worker::add(['priority' => PRIORITY_LOW, 'dont_fork' => true], 'UpdateContact', $contact['id']);
Worker::add(['priority' => Worker::PRIORITY_LOW, 'dont_fork' => true], 'UpdateContact', $contact['id']);
++$updated;
} else {
++$unchanged;
@ -3442,4 +3530,17 @@ class Contact
return [];
}
/**
* Checks, if contacts with the given condition exists
*
* @param array $condition
*
* @return bool
* @throws \Exception
*/
public static function exists(array $condition): bool
{
return DBA::exists('contact', $condition);
}
}

View file

@ -22,6 +22,7 @@
namespace Friendica\Model\Contact;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\Contact;
/**
@ -53,7 +54,7 @@ class Group
AND NOT `contact`.`pending`
ORDER BY `contact`.`name` ASC',
$gid,
local_user()
DI::userSession()->getLocalUserId()
);
if (DBA::isResult($stmt)) {
@ -78,7 +79,7 @@ class Group
{
return Contact::selectToArray([], ["`uid` = ? AND NOT `self` AND NOT `deleted` AND NOT `blocked` AND NOT `pending` AND NOT `failed`
AND `id` NOT IN (SELECT DISTINCT(`contact-id`) FROM `group_member` INNER JOIN `group` ON `group`.`id` = `group_member`.`gid`
WHERE `group`.`uid` = ?)", $uid, $uid]);
WHERE `group`.`uid` = ? AND `contact-id` = `contact`.`id`)", $uid, $uid]);
}
/**

View file

@ -68,6 +68,26 @@ class Relation
DBA::insert('contact-relation', ['last-interaction' => $interaction_date, 'cid' => $target, 'relation-cid' => $actor], Database::INSERT_UPDATE);
}
/**
* Fetch the followers of a given user
*
* @param integer $uid User ID
* @return void
*/
public static function discoverByUser(int $uid)
{
$contact = Contact::selectFirst(['id', 'url', 'network'], ['uid' => $uid, 'self' => true]);
if (empty($contact)) {
Logger::warning('Self contact for user not found', ['uid' => $uid]);
return;
}
$followers = self::getContacts($uid, [Contact::FOLLOWER, Contact::FRIEND]);
$followings = self::getContacts($uid, [Contact::SHARING, Contact::FRIEND]);
self::updateFollowersFollowings($contact, $followers, $followings);
}
/**
* Fetches the followers of a given profile and adds them
*
@ -113,13 +133,27 @@ class Relation
$followings = [];
}
self::updateFollowersFollowings($contact, $followers, $followings);
}
/**
* Update followers and followings for the given contact
*
* @param array $contact
* @param array $followers
* @param array $followings
* @return void
*/
private static function updateFollowersFollowings(array $contact, array $followers, array $followings)
{
if (empty($followers) && empty($followings)) {
Contact::update(['last-discovery' => DateTimeFormat::utcNow()], ['id' => $contact['id']]);
Logger::info('The contact does not offer discoverable data', ['id' => $contact['id'], 'url' => $url, 'network' => $contact['network']]);
Logger::info('The contact does not offer discoverable data', ['id' => $contact['id'], 'url' => $contact['url'], 'network' => $contact['network']]);
return;
}
$target = $contact['id'];
$url = $contact['url'];
if (!empty($followers)) {
// Clear the follower list, since it will be recreated in the next step
@ -260,6 +294,59 @@ class Relation
return true;
}
/**
* Check if the cached suggestion is outdated
*
* @param integer $uid
* @return boolean
*/
static public function areSuggestionsOutdated(int $uid): bool
{
return DI::pConfig()->get($uid, 'suggestion', 'last_update') + 3600 < time();
}
/**
* Update contact suggestions for a given user
*
* @param integer $uid
* @return void
*/
static public function updateCachedSuggestions(int $uid)
{
if (!self::areSuggestionsOutdated($uid)) {
return;
}
DBA::delete('account-suggestion', ['uid' => $uid, 'ignore' => false]);
foreach (self::getSuggestions($uid) as $contact) {
DBA::insert('account-suggestion', ['uri-id' => $contact['uri-id'], 'uid' => $uid, 'level' => 1], Database::INSERT_IGNORE);
}
DI::pConfig()->set($uid, 'suggestion', 'last_update', time());
}
/**
* Returns a cached array of suggested contacts for given user id
*
* @param int $uid User id
* @param int $start optional, default 0
* @param int $limit optional, default 80
* @return array
*/
static public function getCachedSuggestions(int $uid, int $start = 0, int $limit = 80): array
{
$condition = ["`uid` = ? AND `uri-id` IN (SELECT `uri-id` FROM `account-suggestion` WHERE NOT `ignore` AND `uid` = ?)", 0, $uid];
$params = ['limit' => [$start, $limit]];
$cached = DBA::selectToArray('contact', [], $condition, $params);
if (!empty($cached)) {
return $cached;
} else {
return self::getSuggestions($uid, $start, $limit);
}
}
/**
* Returns an array of suggested contacts for given user id
*
@ -270,6 +357,10 @@ class Relation
*/
static public function getSuggestions(int $uid, int $start = 0, int $limit = 80): array
{
if ($uid == 0) {
return [];
}
$cid = Contact::getPublicIdByUserId($uid);
$totallimit = $start + $limit;
$contacts = [];
@ -284,12 +375,13 @@ class Relation
$results = DBA::select('contact', [], ["`id` IN (SELECT `cid` FROM `contact-relation` WHERE `relation-cid` IN
(SELECT `cid` FROM `contact-relation` WHERE `relation-cid` = ?)
AND NOT `cid` IN (SELECT `id` FROM `contact` WHERE `uid` = ? AND `nurl` IN
(SELECT `nurl` FROM `contact` WHERE `uid` = ? AND `rel` IN (?, ?))))
AND NOT `hidden` AND `network` IN (?, ?, ?, ?)",
(SELECT `nurl` FROM `contact` WHERE `uid` = ? AND `rel` IN (?, ?))) AND `id` = `cid`)
AND NOT `hidden` AND `network` IN (?, ?, ?, ?)
AND NOT `uri-id` IN (SELECT `uri-id` FROM `account-suggestion` WHERE `uri-id` = `contact`.`uri-id` AND `uid` = ?)",
$cid,
0,
$uid, Contact::FRIEND, Contact::SHARING,
Protocol::ACTIVITYPUB, Protocol::DFRN, $diaspora, $ostatus,
Protocol::ACTIVITYPUB, Protocol::DFRN, $diaspora, $ostatus, $uid
], [
'order' => ['last-item' => true],
'limit' => $totallimit,
@ -314,10 +406,11 @@ class Relation
["`id` IN (SELECT `cid` FROM `contact-relation` WHERE `relation-cid` IN
(SELECT `relation-cid` FROM `contact-relation` WHERE `cid` = ?)
AND NOT `cid` IN (SELECT `id` FROM `contact` WHERE `uid` = ? AND `nurl` IN
(SELECT `nurl` FROM `contact` WHERE `uid` = ? AND `rel` IN (?, ?))))
AND NOT `hidden` AND `network` IN (?, ?, ?, ?)",
(SELECT `nurl` FROM `contact` WHERE `uid` = ? AND `rel` IN (?, ?))) AND `id` = `cid`)
AND NOT `hidden` AND `network` IN (?, ?, ?, ?)
AND NOT `uri-id` IN (SELECT `uri-id` FROM `account-suggestion` WHERE `uri-id` = `contact`.`uri-id` AND `uid` = ?)",
$cid, 0, $uid, Contact::FRIEND, Contact::SHARING,
Protocol::ACTIVITYPUB, Protocol::DFRN, $diaspora, $ostatus],
Protocol::ACTIVITYPUB, Protocol::DFRN, $diaspora, $ostatus, $uid],
['order' => ['last-item' => true], 'limit' => $totallimit]
);
@ -335,9 +428,10 @@ class Relation
// The query returns contacts that follow the given user but aren't followed by that user.
$results = DBA::select('contact', [],
["`nurl` IN (SELECT `nurl` FROM `contact` WHERE `uid` = ? AND `rel` = ?)
AND NOT `hidden` AND `uid` = ? AND `network` IN (?, ?, ?, ?)",
AND NOT `hidden` AND `uid` = ? AND `network` IN (?, ?, ?, ?)
AND NOT `uri-id` IN (SELECT `uri-id` FROM `account-suggestion` WHERE `uri-id` = `contact`.`uri-id` AND `uid` = ?)",
$uid, Contact::FOLLOWER, 0,
Protocol::ACTIVITYPUB, Protocol::DFRN, $diaspora, $ostatus],
Protocol::ACTIVITYPUB, Protocol::DFRN, $diaspora, $ostatus, $uid],
['order' => ['last-item' => true], 'limit' => $totallimit]
);
@ -354,10 +448,11 @@ class Relation
// The query returns any contact that isn't followed by that user.
$results = DBA::select('contact', [],
["NOT `nurl` IN (SELECT `nurl` FROM `contact` WHERE `uid` = ? AND `rel` IN (?, ?))
AND NOT `hidden` AND `uid` = ? AND `network` IN (?, ?, ?, ?)",
["NOT `nurl` IN (SELECT `nurl` FROM `contact` WHERE `uid` = ? AND `rel` IN (?, ?) AND `nurl` = `nurl`)
AND NOT `hidden` AND `uid` = ? AND `network` IN (?, ?, ?, ?)
AND NOT `uri-id` IN (SELECT `uri-id` FROM `account-suggestion` WHERE `uri-id` = `contact`.`uri-id` AND `uid` = ?)",
$uid, Contact::FRIEND, Contact::SHARING, 0,
Protocol::ACTIVITYPUB, Protocol::DFRN, $diaspora, $ostatus],
Protocol::ACTIVITYPUB, Protocol::DFRN, $diaspora, $ostatus, $uid],
['order' => ['last-item' => true], 'limit' => $totallimit]
);

View file

@ -21,18 +21,20 @@
namespace Friendica\Model;
use Friendica\Content\Feature;
use Friendica\Content\Text\BBCode;
use Friendica\Core\Hook;
use Friendica\Core\Logger;
use Friendica\Core\Protocol;
use Friendica\Core\Renderer;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Network\HTTPException;
use Friendica\Protocol\Activity;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\Map;
use Friendica\Util\Strings;
use Friendica\Util\Temporal;
use Friendica\Util\XML;
/**
@ -279,7 +281,7 @@ class Event
if (!DBA::isResult($existing_event)) {
return 0;
}
if ($existing_event['edited'] === $event['edited']) {
return $event['id'];
}
@ -410,7 +412,7 @@ class Event
public static function getStrings(): array
{
// First day of the week (0 = Sunday).
$firstDay = DI::pConfig()->get(local_user(), 'system', 'first_day_of_week', 0);
$firstDay = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'system', 'first_day_of_week', 0);
$i18n = [
"firstDay" => $firstDay,
@ -495,157 +497,197 @@ class Event
}
/**
* Get an event by its event ID.
* Returns the owner array of a given nickname
* Additionally, it can check if the owner array is selectable
*
* @param int $owner_uid The User ID of the owner of the event
* @param int $event_id The ID of the event in the event table
* @param string $sql_extra
* @param string $nickname
*
* @return array the owner array
* @throws HTTPException\InternalServerErrorException
* @throws HTTPException\NotFoundException The given nickname does not exist
* @throws HTTPException\UnauthorizedException The access for the given nickname is restricted
*/
public static function getOwnerForNickname(string $nickname): array
{
$owner = User::getOwnerDataByNick($nickname);
if (empty($owner) || $owner['account_removed'] || $owner['account_expired']) {
throw new HTTPException\NotFoundException(DI::l10n()->t('User not found.'));
}
if (!DI::userSession()->isAuthenticated() && $owner['hidewall']) {
throw new HTTPException\UnauthorizedException(DI::l10n()->t('Access to this profile has been restricted.'));
}
if (!DI::userSession()->isAuthenticated() && !Feature::isEnabled($owner['uid'], 'public_calendar')) {
throw new HTTPException\UnauthorizedException(DI::l10n()->t('Permission denied.'));
}
return $owner;
}
/**
* Get an event by its event ID. Checks permissions.
*
* @param int $owner_uid The User ID of the owner of the event
* @param int $event_id The ID of the event in the event table
* @param string|null $nickname a possible nickname to search for instead of the owner uid
* @return array Query result
* @throws \Exception
*/
public static function getListById(int $owner_uid, int $event_id, string $sql_extra = ''): array
public static function getByIdAndUid(int $owner_uid, int $event_id): array
{
$return = [];
// Ownly allow events if there is a valid owner_id.
// Only allow events if there is a valid owner_id.
if ($owner_uid == 0) {
return $return;
return [];
}
// get the permissions
$sql_perms = Item::getPermissionsSQLByUserId($owner_uid);
// Query for the event by event id
$events = DBA::toArray(DBA::p("SELECT `event`.*, `post-user`.`id` AS `itemid` FROM `event`
LEFT JOIN `post-user` ON `post-user`.`event-id` = `event`.`id` AND `post-user`.`uid` = `event`.`uid`
WHERE `event`.`uid` = ? AND `event`.`id` = ? $sql_extra",
$owner_uid, $event_id));
if (DBA::isResult($events)) {
$return = self::removeDuplicates($events);
$events = DBA::toArray(DBA::p(
"SELECT `event`.*, `post-user`.`id` AS `itemid` FROM `event`
LEFT JOIN `post-user`
ON `post-user`.`event-id` = `event`.`id`
AND `post-user`.`uid` = `event`.`uid`
WHERE `event`.`id` = ?
AND `event`.`uid` = ?
$sql_perms",
$event_id, $owner_uid
));
if (empty($events)) {
throw new HTTPException\NotFoundException(DI::l10n()->t('Event not found.'));
}
return $return;
return $events[0];
}
/**
* Get all events in a specific time frame.
*
* @param int $owner_uid The User ID of the owner of the events.
* @param array $event_params An associative array with
* int 'ignore' =>
* string 'start' => Start time of the timeframe.
* string 'finish' => Finish time of the timeframe.
*
* @param string $sql_extra Additional sql conditions (e.g. permission request).
* @param int $owner_uid The User ID of the owner of the events.
* @param string|null $start Start time of the timeframe.
* @param string|null $finish Finish time of the timeframe.
* @param bool|null $ignore Filters ignored events (false: unignored events, true: ignored events, null: all events)
*
* @return array Query results.
* @throws \Exception
* @throws HTTPException\NotFoundException
* @throws HTTPException\UnauthorizedException
*/
public static function getListByDate(int $owner_uid, array $event_params, string $sql_extra = ''): array
public static function getListByDate(int $owner_uid, string $start = null, string $finish = null, ?bool $ignore = false): array
{
$return = [];
// Only allow events if there is a valid owner_id.
if ($owner_uid == 0) {
return $return;
return [];
}
// get the permissions
$sql_perms = Item::getPermissionsSQLByUserId($owner_uid);
if (empty($start) || empty($finish)) {
$y = intval(DateTimeFormat::localNow('Y'));
$m = intval(DateTimeFormat::localNow('m'));
if (empty($start)) {
$start = sprintf('%d-%d-%d %d:%d:%d', $y, $m, 1, 0, 0, 0);
} else {
$dim = Temporal::getDaysInMonth($y, $m);
$finish = sprintf('%d-%d-%d %d:%d:%d', $y, $m, $dim, 23, 59, 59);
}
}
if ($ignore === true) {
$sql_ignore = " AND `event`.`ignore` = 1";
} elseif ($ignore === false) {
$sql_ignore = " AND `event`.`ignore` = 0";
} else {
$sql_ignore = "";
}
// Query for the event by date.
$events = DBA::toArray(DBA::p("SELECT `event`.*, `post-user`.`id` AS `itemid` FROM `event`
LEFT JOIN `post-user` ON `post-user`.`event-id` = `event`.`id` AND `post-user`.`uid` = `event`.`uid`
WHERE `event`.`uid` = ? AND `event`.`ignore` = ?
AND (`finish` >= ? OR (`nofinish` AND `start` >= ?)) AND `start` <= ?
" . $sql_extra,
$owner_uid, $event_params['ignore'],
$event_params['start'], $event_params['start'], $event_params['finish']
$events = DBA::toArray(DBA::p(
"SELECT `event`.*, `post-user`.`id` AS `itemid` FROM `event`
LEFT JOIN `post-user`
ON `post-user`.`event-id` = `event`.`id`
AND `post-user`.`uid` = `event`.`uid`
WHERE `event`.`uid` = ?
$sql_ignore
AND (`finish` >= ? OR (`nofinish` AND `start` >= ?))
AND `start` <= ?
$sql_perms",
$owner_uid,
$start, $start,
$finish
));
if (DBA::isResult($events)) {
$return = self::removeDuplicates($events);
}
return $return;
$events = self::removeDuplicates($events);
return self::sortByDate($events);
}
/**
* Convert an array query results in an array which could be used by the events template.
* Convert an event in an array which could be used by the event template.
*
* @param array $event_result Event query array.
* @param array $event Event query array.
* @return array Event array for the template.
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
* @throws \ImagickException
*/
public static function prepareListForTemplate(array $event_result): array
public static function prepareForItem(array $event): array
{
$event_list = [];
$last_date = '';
$fmt = DI::l10n()->t('l, F j');
foreach ($event_result as $event) {
$item = Post::selectFirst(['plink', 'author-name', 'author-avatar', 'author-link', 'private', 'uri-id'], ['id' => $event['itemid']]);
if (!DBA::isResult($item)) {
// Using default values when no item had been found
$item = ['plink' => '', 'author-name' => '', 'author-avatar' => '', 'author-link' => '', 'private' => Item::PUBLIC, 'uri-id' => ($event['uri-id'] ?? 0)];
}
$event = array_merge($event, $item);
$start = DateTimeFormat::local($event['start'], 'c');
$j = DateTimeFormat::local($event['start'], 'j');
$day = DateTimeFormat::local($event['start'], $fmt);
$day = DI::l10n()->getDay($day);
if ($event['nofinish']) {
$end = null;
} else {
$end = DateTimeFormat::local($event['finish'], 'c');
}
$is_first = ($day !== $last_date);
$last_date = $day;
// Show edit and drop actions only if the user is the owner of the event and the event
// is a real event (no bithdays).
$edit = null;
$copy = null;
$drop = null;
if (local_user() && local_user() == $event['uid'] && $event['type'] == 'event') {
$edit = !$event['cid'] ? [DI::baseUrl() . '/events/event/' . $event['id'], DI::l10n()->t('Edit event') , '', ''] : null;
$copy = !$event['cid'] ? [DI::baseUrl() . '/events/copy/' . $event['id'] , DI::l10n()->t('Duplicate event'), '', ''] : null;
$drop = [DI::baseUrl() . '/events/drop/' . $event['id'] , DI::l10n()->t('Delete event') , '', ''];
}
$title = BBCode::convertForUriId($event['uri-id'], Strings::escapeHtml($event['summary']));
if (!$title) {
list($title, $_trash) = explode("<br", BBCode::convertForUriId($event['uri-id'], Strings::escapeHtml($event['desc'])), BBCode::API);
}
$author_link = $event['author-link'];
$event['author-link'] = Contact::magicLink($author_link);
$html = self::getHTML($event);
$event['summary'] = BBCode::convertForUriId($event['uri-id'], Strings::escapeHtml($event['summary']));
$event['desc'] = BBCode::convertForUriId($event['uri-id'], Strings::escapeHtml($event['desc']));
$event['location'] = BBCode::convertForUriId($event['uri-id'], Strings::escapeHtml($event['location']));
$event_list[] = [
'id' => $event['id'],
'start' => $start,
'end' => $end,
'allDay' => false,
'title' => $title,
'j' => $j,
'd' => $day,
'edit' => $edit,
'drop' => $drop,
'copy' => $copy,
'is_first' => $is_first,
'item' => $event,
'html' => $html,
'plink' => Item::getPlink($event),
];
$item = Post::selectFirst(['plink', 'author-name', 'author-network', 'author-id', 'author-avatar', 'author-link', 'private', 'uri-id'], ['id' => $event['itemid']]);
if (empty($item)) {
// Using default values when no item had been found
$item = ['plink' => '', 'author-name' => '', 'author-avatar' => '', 'author-link' => '', 'private' => Item::PUBLIC, 'uri-id' => ($event['uri-id'] ?? 0)];
}
return $event_list;
$event = array_merge($event, $item);
$start = DateTimeFormat::local($event['start'], 'c');
$j = DateTimeFormat::local($event['start'], 'j');
$day = DateTimeFormat::local($event['start'], $fmt);
$day = DI::l10n()->getDay($day);
if ($event['nofinish']) {
$end = null;
} else {
$end = DateTimeFormat::local($event['finish'], 'c');
}
// Show edit and drop actions only if the user is the owner of the event and the event
// is a real event (no bithdays).
$edit = null;
$copy = null;
$drop = null;
if (DI::userSession()->getLocalUserId() && DI::userSession()->getLocalUserId() == $event['uid'] && $event['type'] == 'event') {
$edit = !$event['cid'] ? ['calendar/event/edit/' . $event['id'], DI::l10n()->t('Edit event') , '', ''] : null;
$copy = !$event['cid'] ? ['calendar/event/copy/' . $event['id'] , DI::l10n()->t('Duplicate event'), '', ''] : null;
$drop = ['calendar/api/delete/' . $event['id'] , DI::l10n()->t('Delete event') , '', ''];
}
$title = BBCode::convertForUriId($event['uri-id'], Strings::escapeHtml($event['summary']));
if (!$title) {
[$title, $_trash] = explode("<br", BBCode::convertForUriId($event['uri-id'], Strings::escapeHtml($event['desc'])), BBCode::TWITTER_API);
}
$event['author-link'] = Contact::magicLink($event['author-link']);
return [
'id' => $event['id'],
'start' => $start,
'end' => $end,
'allDay' => false,
'title' => $title,
'j' => $j,
'd' => $day,
'edit' => $edit,
'drop' => $drop,
'copy' => $copy,
'item' => $event,
'html' => self::getHTML($event),
'plink' => Item::getPlink($event),
];
}
/**
@ -653,8 +695,6 @@ class Event
*
* @param array $events Query result for events.
* @param string $format The output format (ical/csv).
*
* @param string $timezone Timezone (missing parameter!)
* @return string Content according to selected export format.
*
* @todo Implement timezone support
@ -670,14 +710,13 @@ class Event
switch ($format) {
// Format the exported data as a CSV file.
case "csv":
header("Content-type: text/csv");
$o .= '"Subject", "Start Date", "Start Time", "Description", "End Date", "End Time", "Location"' . PHP_EOL;
foreach ($events as $event) {
/// @todo The time / date entries don't include any information about the
/// timezone the event is scheduled in :-/
$tmp1 = strtotime($event['start']);
$tmp2 = strtotime($event['finish']);
$tmp1 = strtotime($event['start']);
$tmp2 = strtotime($event['finish']);
$time_format = "%H:%M:%S";
$date_format = "%Y-%m-%d";
@ -691,7 +730,6 @@ class Event
// Format the exported data as a ics file.
case "ical":
header("Content-type: text/ics");
$o = 'BEGIN:VCALENDAR' . PHP_EOL
. 'VERSION:2.0' . PHP_EOL
. 'PRODID:-//friendica calendar export//0.1//EN' . PHP_EOL;
@ -705,6 +743,8 @@ class Event
// also long lines SHOULD be split at 75 characters length
foreach ($events as $event) {
$o .= 'BEGIN:VEVENT' . PHP_EOL;
$o .= 'UID:' . $event['id'] . PHP_EOL;
$o .= 'DTSTAMP:' . DateTimeFormat::utc($event['created'], 'Ymd\THis\Z') . PHP_EOL;
if ($event['start']) {
$o .= 'DTSTART:' . DateTimeFormat::utc($event['start'], 'Ymd\THis\Z') . PHP_EOL;
@ -718,25 +758,24 @@ class Event
$tmp = $event['summary'];
$tmp = str_replace(PHP_EOL, PHP_EOL . ' ', $tmp);
$tmp = addcslashes($tmp, ',;');
$o .= 'SUMMARY:' . $tmp . PHP_EOL;
$o .= 'SUMMARY:' . $tmp . PHP_EOL;
}
if ($event['desc']) {
$tmp = $event['desc'];
$tmp = str_replace(PHP_EOL, PHP_EOL . ' ', $tmp);
$tmp = addcslashes($tmp, ',;');
$o .= 'DESCRIPTION:' . $tmp . PHP_EOL;
$o .= 'DESCRIPTION:' . $tmp . PHP_EOL;
}
if ($event['location']) {
$tmp = $event['location'];
$tmp = str_replace(PHP_EOL, PHP_EOL . ' ', $tmp);
$tmp = addcslashes($tmp, ',;');
$o .= 'LOCATION:' . $tmp . PHP_EOL;
$o .= 'LOCATION:' . $tmp . PHP_EOL;
}
$o .= 'END:VEVENT' . PHP_EOL;
$o .= PHP_EOL;
}
$o .= 'END:VCALENDAR' . PHP_EOL;
@ -768,14 +807,14 @@ class Event
return $return;
}
$fields = ['start', 'finish', 'summary', 'desc', 'location', 'nofinish'];
$fields = ['id', 'created', 'start', 'finish', 'summary', 'desc', 'location', 'nofinish'];
$conditions = ['uid' => $uid, 'cid' => 0];
// Does the user who requests happen to be the owner of the events
// requested? then show all of your events, otherwise only those that
// don't have limitations set in allow_cid and allow_gid.
if (local_user() != $uid) {
if (DI::userSession()->getLocalUserId() != $uid) {
$conditions += ['allow_cid' => '', 'allow_gid' => ''];
}
@ -859,42 +898,42 @@ class Event
$tformat = DI::l10n()->t('g:i A'); // 8:01 AM.
// Convert the time to different formats.
$dtstart_dt = DI::l10n()->getDay(DateTimeFormat::local($item['event-start'], $dformat));
$dtstart_dt = DI::l10n()->getDay(DateTimeFormat::local($item['event-start'], $dformat));
$dtstart_title = DateTimeFormat::utc($item['event-start'], DateTimeFormat::ATOM);
// Format: Jan till Dec.
$month_short = DI::l10n()->getDayShort(DateTimeFormat::local($item['event-start'], 'M'));
// Format: 1 till 31.
$date_short = DateTimeFormat::local($item['event-start'], 'j');
$start_time = DateTimeFormat::local($item['event-start'], $tformat);
$date_short = DateTimeFormat::local($item['event-start'], 'j');
$start_time = DateTimeFormat::local($item['event-start'], $tformat);
$start_short = DI::l10n()->getDayShort(DateTimeFormat::local($item['event-start'], $dformat_short));
// If the option 'nofinisch' isn't set, we need to format the finish date/time.
if (!$item['event-nofinish']) {
$finish = true;
$dtend_dt = DI::l10n()->getDay(DateTimeFormat::local($item['event-finish'], $dformat));
$finish = true;
$dtend_dt = DI::l10n()->getDay(DateTimeFormat::local($item['event-finish'], $dformat));
$dtend_title = DateTimeFormat::utc($item['event-finish'], DateTimeFormat::ATOM);
$end_short = DI::l10n()->getDayShort(DateTimeFormat::utc($item['event-finish'], $dformat_short));
$end_time = DateTimeFormat::local($item['event-finish'], $tformat);
$end_short = DI::l10n()->getDayShort(DateTimeFormat::utc($item['event-finish'], $dformat_short));
$end_time = DateTimeFormat::local($item['event-finish'], $tformat);
// Check if start and finish time is at the same day.
if (substr($dtstart_title, 0, 10) === substr($dtend_title, 0, 10)) {
$same_date = true;
}
} else {
$dtend_title = '';
$dtend_dt = '';
$end_time = '';
$end_short = '';
$dtend_dt = '';
$end_time = '';
$end_short = '';
}
// Format the event location.
$location = self::locationToArray($item['event-location']);
// Construct the profile link (magic-auth).
$author = ['uid' => 0, 'id' => $item['author-id'],
'network' => $item['author-network'], 'url' => $item['author-link']];
$author = ['uid' => 0, 'id' => $item['author-id'],
'network' => $item['author-network'], 'url' => $item['author-link']];
$profile_link = Contact::magicLinkByContact($author);
$tpl = Renderer::getMarkupTemplate('event_stream_item.tpl');
$tpl = Renderer::getMarkupTemplate('event_stream_item.tpl');
$return = Renderer::replaceMacros($tpl, [
'$id' => $item['event-id'],
'$title' => BBCode::convertForUriId($item['uri-id'], $item['event-summary']),
@ -952,15 +991,15 @@ class Event
if (strpos($s, '[/map]') !== false) {
$found = preg_match("/\[map\](.*?)\[\/map\]/ism", $s, $match);
if (intval($found) > 0 && array_key_exists(1, $match)) {
$location['address'] = $match[1];
$location['address'] = $match[1];
// Remove the map bbcode from the location name.
$location['name'] = str_replace($match[0], "", $s);
}
// Map tag with coordinates - e.g. [map=48.864716,2.349014].
// Map tag with coordinates - e.g. [map=48.864716,2.349014].
} elseif (strpos($s, '[map=') !== false) {
$found = preg_match("/\[map=(.*?)\]/ism", $s, $match);
if (intval($found) > 0 && array_key_exists(1, $match)) {
$location['coordinates'] = $match[1];
$location['coordinates'] = $match[1];
// Remove the map bbcode from the location name.
$location['name'] = str_replace($match[0], "", $s);
}
@ -990,10 +1029,10 @@ class Event
{
// Check for duplicates
$condition = [
'uid' => $contact['uid'],
'cid' => $contact['id'],
'uid' => $contact['uid'],
'cid' => $contact['id'],
'start' => DateTimeFormat::utc($birthday),
'type' => 'birthday'
'type' => 'birthday'
];
if (DBA::exists('event', $condition)) {
return false;
@ -1019,4 +1058,9 @@ class Event
// Check if self::store() was success
return (self::store($values) > 0);
}
public static function setIgnore(int $uid, int $eventId, bool $ignore = true)
{
DBA::update('event', ['ignore' => $ignore], ['id' => $eventId, 'uid' => $uid]);
}
}

View file

@ -1,160 +0,0 @@
<?php
/**
* @copyright Copyright (C) 2010-2022, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
namespace Friendica\Model;
use Friendica\Core\Logger;
use Friendica\Core\Protocol;
use Friendica\Core\Worker;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Network\Probe;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\Strings;
class FContact
{
/**
* Fetches data for a given handle
*
* @param string $handle The handle
* @param boolean $update true = always update, false = never update, null = update when not found or outdated
*
* @return array the queried data
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
* @throws \ImagickException
*/
public static function getByURL(string $handle, $update = null): array
{
Logger::debug('Fetch fcontact', ['handle' => $handle, 'update' => $update]);
$person = DBA::selectFirst('fcontact', [], ['network' => Protocol::DIASPORA, 'addr' => $handle]);
if (!DBA::isResult($person)) {
$urls = [$handle, str_replace('http://', 'https://', $handle), Strings::normaliseLink($handle)];
$person = DBA::selectFirst('fcontact', [], ['network' => Protocol::DIASPORA, 'url' => $urls]);
}
if (DBA::isResult($person)) {
Logger::debug('In cache', ['handle' => $handle]);
if (is_null($update)) {
$update = empty($person['guid']) || empty($person['uri-id']) || ($person['created'] <= DBA::NULL_DATETIME);
if (GServer::getNextUpdateDate(true, $person['created'], $person['updated'], false) < DateTimeFormat::utcNow()) {
Logger::debug('Start background update', ['handle' => $handle]);
Worker::add(['priority' => PRIORITY_LOW, 'dont_fork' => true], 'UpdateFContact', $handle);
}
}
} elseif (is_null($update)) {
$update = true;
} else {
$person = [];
}
if ($update) {
Logger::info('create or refresh', ['handle' => $handle]);
$data = Probe::uri($handle, Protocol::DIASPORA);
// Note that Friendica contacts will return a "Diaspora person"
// if Diaspora connectivity is enabled on their server
if ($data['network'] ?? '' === Protocol::DIASPORA) {
self::updateFromProbeArray($data);
$person = self::getByURL($handle, false);
}
}
return $person;
}
/**
* Updates the fcontact table
*
* @param array $arr The fcontact data
* @throws \Exception
*/
public static function updateFromProbeArray(array $arr)
{
$uriid = ItemURI::insert(['uri' => $arr['url'], 'guid' => $arr['guid']]);
$fcontact = DBA::selectFirst('fcontact', ['created'], ['url' => $arr['url'], 'network' => $arr['network']]);
$contact = Contact::getByUriId($uriid, ['id', 'created']);
$apcontact = APContact::getByURL($arr['url'], false);
if (!empty($apcontact)) {
$interacted = $apcontact['following_count'];
$interacting = $apcontact['followers_count'];
$posts = $apcontact['statuses_count'];
} elseif (!empty($contact['id'])) {
$last_interaction = DateTimeFormat::utc('now - 180 days');
$interacted = DBA::count('contact-relation', ["`cid` = ? AND NOT `follows` AND `last-interaction` > ?", $contact['id'], $last_interaction]);
$interacting = DBA::count('contact-relation', ["`relation-cid` = ? AND NOT `follows` AND `last-interaction` > ?", $contact['id'], $last_interaction]);
$posts = DBA::count('post', ['author-id' => $contact['id'], 'gravity' => [GRAVITY_PARENT, GRAVITY_COMMENT]]);
}
$fields = [
'name' => $arr['name'],
'photo' => $arr['photo'],
'request' => $arr['request'],
'nick' => $arr['nick'],
'addr' => strtolower($arr['addr']),
'guid' => $arr['guid'],
'batch' => $arr['batch'],
'notify' => $arr['notify'],
'poll' => $arr['poll'],
'confirm' => $arr['confirm'],
'alias' => $arr['alias'],
'pubkey' => $arr['pubkey'],
'uri-id' => $uriid,
'interacting_count' => $interacting ?? 0,
'interacted_count' => $interacted ?? 0,
'post_count' => $posts ?? 0,
'updated' => DateTimeFormat::utcNow(),
];
if (empty($fcontact['created'])) {
$fields['created'] = $fields['updated'];
} elseif (!empty($contact['created']) && ($fcontact['created'] <= DBA::NULL_DATETIME)) {
$fields['created'] = $contact['created'];
}
$fields = DI::dbaDefinition()->truncateFieldsForTable('fcontact', $fields);
DBA::update('fcontact', $fields, ['url' => $arr['url'], 'network' => $arr['network']], true);
}
/**
* get a url (scheme://domain.tld/u/user) from a given Diaspora*
* fcontact guid
*
* @param string $fcontact_guid Hexadecimal string guid
* @return string|null the contact url or null
* @throws \Exception
*/
public static function getUrlByGuid(string $fcontact_guid)
{
Logger::info('fcontact', ['guid' => $fcontact_guid]);
$fcontact = DBA::selectFirst('fcontact', ['url'], ["`url` != ? AND `network` = ? AND `guid` = ?", '', Protocol::DIASPORA, $fcontact_guid]);
if (DBA::isResult($fcontact)) {
return $fcontact['url'];
}
return null;
}
}

View file

@ -70,6 +70,7 @@ class GServer
const DETECT_UNSPECIFIC = [self::DETECT_MANUAL, self::DETECT_HEADER, self::DETECT_BODY, self::DETECT_HOST_META, self::DETECT_CONTACTS, self::DETECT_AP_ACTOR];
// Implementation specific endpoints
// @todo Possibly add Lemmy detection via the endpoint /api/v3/site
const DETECT_FRIENDIKA = 10;
const DETECT_FRIENDICA = 11;
const DETECT_STATUSNET = 12;
@ -102,7 +103,7 @@ class GServer
return;
}
Worker::add(PRIORITY_LOW, 'UpdateGServer', $url, $only_nodeinfo);
Worker::add(Worker::PRIORITY_LOW, 'UpdateGServer', $url, $only_nodeinfo);
}
/**
@ -115,12 +116,12 @@ class GServer
*/
public static function getID(string $url, bool $no_check = false): ?int
{
$url = self::cleanURL($url);
if (empty($url)) {
return null;
}
$url = self::cleanURL($url);
$gserver = DBA::selectFirst('gserver', ['id'], ['nurl' => Strings::normaliseLink($url)]);
if (DBA::isResult($gserver)) {
Logger::debug('Got ID for URL', ['id' => $gserver['id'], 'url' => $url, 'callstack' => System::callstack(20)]);
@ -323,6 +324,10 @@ class GServer
$url = str_replace('/index.php', '', $url);
$urlparts = parse_url($url);
if (empty($urlparts)) {
return '';
}
unset($urlparts['user']);
unset($urlparts['pass']);
unset($urlparts['query']);
@ -1209,7 +1214,7 @@ class GServer
if (!empty($data['url'])) {
$serverdata['platform'] = strtolower($data['platform']);
$serverdata['version'] = $data['version'];
$serverdata['version'] = $data['version'] ?? 'N/A';
}
if (!empty($data['plugins'])) {
@ -1325,7 +1330,7 @@ class GServer
private static function validHostMeta(string $url): bool
{
$xrd_timeout = DI::config()->get('system', 'xrd_timeout');
$curlResult = DI::httpClient()->get($url . '/.well-known/host-meta', HttpClientAccept::XRD_XML, [HttpClientOptions::TIMEOUT => $xrd_timeout]);
$curlResult = DI::httpClient()->get($url . Probe::HOST_META, HttpClientAccept::XRD_XML, [HttpClientOptions::TIMEOUT => $xrd_timeout]);
if (!$curlResult->isSuccess()) {
return false;
}
@ -2108,10 +2113,10 @@ class GServer
while ($gserver = DBA::fetch($gservers)) {
Logger::info('Update peer list', ['server' => $gserver['url'], 'id' => $gserver['id']]);
Worker::add(PRIORITY_LOW, 'UpdateServerPeers', $gserver['url']);
Worker::add(Worker::PRIORITY_LOW, 'UpdateServerPeers', $gserver['url']);
Logger::info('Update directory', ['server' => $gserver['url'], 'id' => $gserver['id']]);
Worker::add(PRIORITY_LOW, 'UpdateServerDirectory', $gserver);
Worker::add(Worker::PRIORITY_LOW, 'UpdateServerDirectory', $gserver);
$fields = ['last_poco_query' => DateTimeFormat::utcNow()];
self::update($fields, ['nurl' => $gserver['nurl']]);
@ -2168,7 +2173,7 @@ class GServer
foreach ($servers['instances'] as $server) {
$url = (is_null($server['https_score']) ? 'http' : 'https') . '://' . $server['name'];
self::add($url);
}
}
}
}
}

View file

@ -102,7 +102,7 @@ class Group
$group = DBA::selectFirst('group', ['deleted'], ['id' => $gid]);
if (DBA::isResult($group) && $group['deleted']) {
DBA::update('group', ['deleted' => 0], ['id' => $gid]);
notice(DI::l10n()->t('A deleted group with this name was revived. Existing item permissions <strong>may</strong> apply to this group and any future members. If this is not what you intended, please create another group with a different name.'));
DI::sysmsg()->addNotice(DI::l10n()->t('A deleted group with this name was revived. Existing item permissions <strong>may</strong> apply to this group and any future members. If this is not what you intended, please create another group with a different name.'));
}
return true;
}
@ -187,8 +187,8 @@ class Group
) AS `count`
FROM `group`
WHERE `group`.`uid` = ?;",
local_user(),
local_user()
DI::userSession()->getLocalUserId(),
DI::userSession()->getLocalUserId()
);
return DBA::toArray($stmt);
@ -526,7 +526,7 @@ class Group
*/
public static function sidebarWidget(string $every = 'contact', string $each = 'group', string $editmode = 'standard', $group_id = '', int $cid = 0)
{
if (!local_user()) {
if (!DI::userSession()->getLocalUserId()) {
return '';
}
@ -544,7 +544,7 @@ class Group
$member_of = self::getIdsByContactId($cid);
}
$stmt = DBA::select('group', [], ['deleted' => false, 'uid' => local_user(), 'cid' => null], ['order' => ['name']]);
$stmt = DBA::select('group', [], ['deleted' => false, 'uid' => DI::userSession()->getLocalUserId(), 'cid' => null], ['order' => ['name']]);
while ($group = DBA::fetch($stmt)) {
$selected = (($group_id == $group['id']) ? ' group-selected' : '');

View file

@ -27,7 +27,6 @@ use Friendica\Core\Hook;
use Friendica\Core\Logger;
use Friendica\Core\Protocol;
use Friendica\Core\Renderer;
use Friendica\Core\Session;
use Friendica\Core\System;
use Friendica\Model\Tag;
use Friendica\Core\Worker;
@ -93,7 +92,7 @@ class Item
'commented', 'created', 'edited', 'received', 'verb', 'object-type', 'postopts', 'plink',
'wall', 'private', 'starred', 'origin', 'parent-origin', 'title', 'body', 'language',
'content-warning', 'location', 'coord', 'app', 'rendered-hash', 'rendered-html', 'object',
'allow_cid', 'allow_gid', 'deny_cid', 'deny_gid', 'mention', 'global',
'quote-uri', 'quote-uri-id', 'allow_cid', 'allow_gid', 'deny_cid', 'deny_gid', 'mention', 'global',
'author-id', 'author-link', 'author-name', 'author-avatar', 'author-network', 'author-updated', 'author-gsid', 'author-addr', 'author-uri-id',
'owner-id', 'owner-link', 'owner-name', 'owner-avatar', 'owner-network', 'owner-contact-type', 'owner-updated',
'causer-id', 'causer-link', 'causer-name', 'causer-avatar', 'causer-contact-type', 'causer-network',
@ -113,9 +112,9 @@ class Item
'private', 'title', 'body', 'raw-body', 'location', 'coord', 'app',
'inform', 'deleted', 'extid', 'post-type', 'post-reason', 'gravity',
'allow_cid', 'allow_gid', 'deny_cid', 'deny_gid',
'author-id', 'author-link', 'author-name', 'author-avatar', 'owner-id', 'owner-link', 'contact-uid',
'author-id', 'author-addr', 'author-link', 'author-name', 'author-avatar', 'owner-id', 'owner-link', 'contact-uid',
'signed_text', 'network', 'wall', 'contact-id', 'plink', 'origin',
'thr-parent-id', 'parent-uri-id', 'postopts', 'pubmail',
'thr-parent-id', 'parent-uri-id', 'quote-uri', 'quote-uri-id', 'postopts', 'pubmail',
'event-created', 'event-edited', 'event-start', 'event-finish',
'event-summary', 'event-desc', 'event-location', 'event-type',
'event-nofinish', 'event-ignore', 'event-id'];
@ -123,7 +122,7 @@ class Item
// All fields in the item table
const ITEM_FIELDLIST = ['id', 'uid', 'parent', 'uri', 'parent-uri', 'thr-parent',
'guid', 'uri-id', 'parent-uri-id', 'thr-parent-id', 'conversation', 'vid',
'contact-id', 'wall', 'gravity', 'extid', 'psid',
'quote-uri', 'quote-uri-id', 'contact-id', 'wall', 'gravity', 'extid', 'psid',
'created', 'edited', 'commented', 'received', 'changed', 'verb',
'postopts', 'plink', 'resource-id', 'event-id', 'inform',
'allow_cid', 'allow_gid', 'deny_cid', 'deny_gid', 'post-type', 'post-reason',
@ -142,10 +141,17 @@ class Item
Activity::FOLLOW,
Activity::ANNOUNCE];
// Privacy levels
const PUBLIC = 0;
const PRIVATE = 1;
const UNLISTED = 2;
// Item weight for query ordering
const GRAVITY_PARENT = 0;
const GRAVITY_ACTIVITY = 3;
const GRAVITY_COMMENT = 6;
const GRAVITY_UNKNOWN = 9;
/**
* Update existing item entries
*
@ -191,12 +197,20 @@ class Item
Logger::info('Updating per single row method', ['fields' => $fields, 'condition' => $condition]);
$items = Post::select(['id', 'origin', 'uri-id', 'uid', 'author-network'], $condition);
$items = Post::select(['id', 'origin', 'uri-id', 'uid', 'author-network', 'quote-uri-id'], $condition);
$notify_items = [];
while ($item = DBA::fetch($items)) {
if (!empty($fields['body'])) {
if (!empty($item['quote-uri-id'])) {
$fields['body'] = BBCode::removeSharedData($fields['body']);
if (!empty($fields['raw-body'])) {
$fields['raw-body'] = BBCode::removeSharedData($fields['raw-body']);
}
}
Post\Media::insertFromAttachmentData($item['uri-id'], $fields['body']);
$content_fields = ['raw-body' => trim($fields['raw-body'] ?? $fields['body'])];
@ -206,10 +220,7 @@ class Item
$content_fields['raw-body'] = Post\Media::insertFromBody($item['uri-id'], $content_fields['raw-body']);
$content_fields['raw-body'] = self::setHashtags($content_fields['raw-body']);
if ($item['author-network'] != Protocol::DFRN) {
Post\Media::insertFromRelevantUrl($item['uri-id'], $content_fields['raw-body']);
}
Post\Media::insertFromRelevantUrl($item['uri-id'], $content_fields['raw-body'], $fields['body'], $item['author-network']);
Post\Content::update($item['uri-id'], $content_fields);
}
@ -232,7 +243,7 @@ class Item
foreach ($notify_items as $notify_item) {
$post = Post::selectFirst(['uri-id', 'uid'], ['id' => $notify_item]);
Worker::add(PRIORITY_HIGH, 'Notifier', Delivery::POST, (int)$post['uri-id'], (int)$post['uid']);
Worker::add(Worker::PRIORITY_HIGH, 'Notifier', Delivery::POST, (int)$post['uri-id'], (int)$post['uid']);
}
return $rows;
@ -246,7 +257,7 @@ class Item
* @return void
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function markForDeletion(array $condition, int $priority = PRIORITY_HIGH)
public static function markForDeletion(array $condition, int $priority = Worker::PRIORITY_HIGH)
{
$items = Post::select(['id'], $condition);
while ($item = Post::fetch($items)) {
@ -277,7 +288,7 @@ class Item
}
if ($item['uid'] == $uid) {
self::markForDeletionById($item['id'], PRIORITY_HIGH);
self::markForDeletionById($item['id'], Worker::PRIORITY_HIGH);
} elseif ($item['uid'] != 0) {
Logger::warning('Wrong ownership. Not deleting item', ['id' => $item['id']]);
}
@ -293,7 +304,7 @@ class Item
* @return boolean success
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function markForDeletionById(int $item_id, int $priority = PRIORITY_HIGH): bool
public static function markForDeletionById(int $item_id, int $priority = Worker::PRIORITY_HIGH): bool
{
Logger::info('Mark item for deletion by id', ['id' => $item_id, 'callstack' => System::callstack()]);
// locate item to be deleted
@ -326,7 +337,7 @@ class Item
* generate a resource-id and therefore aren't intimately linked to the item.
*/
/// @TODO: this should first check if photo is used elsewhere
if (strlen($item['resource-id'])) {
if ($item['resource-id']) {
Photo::delete(['resource-id' => $item['resource-id'], 'uid' => $item['uid']]);
}
@ -356,7 +367,7 @@ class Item
Post\DeliveryData::delete($item['uri-id']);
// If it's the parent of a comment thread, kill all the kids
if ($item['gravity'] == GRAVITY_PARENT) {
if ($item['gravity'] == self::GRAVITY_PARENT) {
self::markForDeletion(['parent' => $item['parent'], 'deleted' => false], $priority);
}
@ -374,6 +385,9 @@ class Item
Post\ThreadUser::update($item['uri-id'], $item['uid'], ['hidden' => true]);
}
DI::notify()->deleteForItem($item['uri-id']);
DI::notification()->deleteForItem($item['uri-id']);
Logger::info('Item has been marked for deletion.', ['id' => $item_id]);
return true;
@ -463,7 +477,7 @@ class Item
}
}
if ($item['gravity'] == GRAVITY_PARENT) {
if ($item['gravity'] == self::GRAVITY_PARENT) {
if (Contact::isSharingByURL($item['owner-link'], $item['uid'], true)) {
$contact_id = Contact::getIdForURL($item['owner-link'], $item['uid']);
} else {
@ -572,7 +586,7 @@ class Item
public static function isValid(array $item): bool
{
// When there is no content then we don't post it
if (($item['body'] . $item['title'] == '') && (empty($item['uri-id']) || !Post\Media::existsByURIId($item['uri-id']))) {
if (($item['body'] . $item['title'] == '') && empty($item['quote-uri-id']) && (empty($item['uri-id']) || !Post\Media::existsByURIId($item['uri-id']))) {
Logger::notice('No body, no title.');
return false;
}
@ -714,7 +728,7 @@ class Item
return 0;
}
if ($thread_parent['gravity'] == GRAVITY_PARENT) {
if ($thread_parent['gravity'] == Item::GRAVITY_PARENT) {
return $uriid;
}
@ -791,17 +805,60 @@ class Item
if (isset($item['gravity'])) {
return intval($item['gravity']);
} elseif ($item['parent-uri-id'] === $item['uri-id']) {
return GRAVITY_PARENT;
return self::GRAVITY_PARENT;
} elseif ($activity->match($item['verb'], Activity::POST)) {
return GRAVITY_COMMENT;
return self::GRAVITY_COMMENT;
} elseif ($activity->match($item['verb'], Activity::FOLLOW)) {
return GRAVITY_ACTIVITY;
return self::GRAVITY_ACTIVITY;
} elseif ($activity->match($item['verb'], Activity::ANNOUNCE)) {
return GRAVITY_ACTIVITY;
return self::GRAVITY_ACTIVITY;
}
Logger::info('Unknown gravity for verb', ['verb' => $item['verb']]);
return GRAVITY_UNKNOWN; // Should not happen
return self::GRAVITY_UNKNOWN; // Should not happen
}
private static function prepareOriginPost(array $item): array
{
$item['wall'] = 1;
$item['origin'] = 1;
$item['network'] = Protocol::DFRN;
$item['protocol'] = Conversation::PARCEL_DIRECT;
$item['direction'] = Conversation::PUSH;
$owner = User::getOwnerDataById($item['uid']);
if (empty($item['contact-id'])) {
$item['contact-id'] = $owner['id'];
}
if (empty($item['author-link']) && empty($item['author-id'])) {
$item['author-link'] = $owner['url'];
$item['author-name'] = $owner['name'];
$item['author-avatar'] = $owner['thumb'];
}
if (empty($item['owner-link']) && empty($item['owner-id'])) {
$item['owner-link'] = $item['author-link'];
$item['owner-name'] = $item['author-name'];
$item['owner-avatar'] = $item['author-avatar'];
}
// Setting the object type if not defined before
if (empty($item['object-type'])) {
$item['object-type'] = Activity\ObjectType::NOTE; // Default value
$objectdata = BBCode::getAttachedData($item['body']);
if ($objectdata['type'] == 'link') {
$item['object-type'] = Activity\ObjectType::BOOKMARK;
} elseif ($objectdata['type'] == 'video') {
$item['object-type'] = Activity\ObjectType::VIDEO;
} elseif ($objectdata['type'] == 'photo') {
$item['object-type'] = Activity\ObjectType::IMAGE;
}
}
return $item;
}
/**
@ -816,17 +873,13 @@ class Item
{
$orig_item = $item;
$priority = PRIORITY_HIGH;
$priority = Worker::PRIORITY_HIGH;
// If it is a posting where users should get notifications, then define it as wall posting
if ($notify) {
$item['wall'] = 1;
$item['origin'] = 1;
$item['network'] = Protocol::DFRN;
$item['protocol'] = Conversation::PARCEL_DIRECT;
$item['direction'] = Conversation::PUSH;
$item = self::prepareOriginPost($item);
if (is_int($notify) && in_array($notify, PRIORITIES)) {
if (is_int($notify) && in_array($notify, Worker::PRIORITIES)) {
$priority = $notify;
}
} else {
@ -965,7 +1018,7 @@ class Item
return 0;
}
if ($item['gravity'] !== GRAVITY_PARENT) {
if ($item['gravity'] !== self::GRAVITY_PARENT) {
$toplevel_parent = self::getTopLevelParent($item);
if (empty($toplevel_parent)) {
return 0;
@ -982,6 +1035,7 @@ class Item
$item['parent-uri'] = $toplevel_parent['uri'];
$item['parent-uri-id'] = $toplevel_parent['uri-id'];
$item['deleted'] = $toplevel_parent['deleted'];
$item['wall'] = $toplevel_parent['wall'];
// Reshares have to keep their permissions to allow forums to work
if (!$item['origin'] || ($item['verb'] != Activity::ANNOUNCE)) {
@ -1060,7 +1114,7 @@ class Item
}
// We have to tell the hooks who we are - this really should be improved
if (!local_user()) {
if (!DI::userSession()->getLocalUserId()) {
$_SESSION['authenticated'] = true;
$_SESSION['uid'] = $uid;
$dummy_session = true;
@ -1113,15 +1167,29 @@ class Item
unset($item['attachments']);
}
if (empty($item['quote-uri-id'])) {
$quote_id = self::getQuoteUriId($item['body']);
if (!empty($quote_id)) {
// This is one of these "should not happen" situations.
// The protocol implementations should already have done this job.
Logger::notice('Quote-uri-id detected in post', ['id' => $quote_id, 'guid' => $item['guid'], 'uri-id' => $item['uri-id'], 'callstack' => System::callstack(20)]);
$item['quote-uri-id'] = $quote_id;
}
}
if (!empty($item['quote-uri-id'])) {
$item['raw-body'] = BBCode::removeSharedData($item['raw-body']);
$item['body'] = BBCode::removeSharedData($item['body']);
}
Post\Media::insertFromAttachmentData($item['uri-id'], $item['body']);
// Remove all media attachments from the body and store them in the post-media table
$item['raw-body'] = Post\Media::insertFromBody($item['uri-id'], $item['raw-body']);
$item['raw-body'] = self::setHashtags($item['raw-body']);
if (!DBA::exists('contact', ['id' => $item['author-id'], 'network' => Protocol::DFRN])) {
Post\Media::insertFromRelevantUrl($item['uri-id'], $item['raw-body']);
}
$author = Contact::getById($item['author-id'], ['network']);
Post\Media::insertFromRelevantUrl($item['uri-id'], $item['raw-body'], $item['body'], $author['network'] ?? '');
// Check for hashtags in the body and repair or add hashtag links
$item['body'] = self::setHashtags($item['body']);
@ -1178,7 +1246,7 @@ class Item
Post::insert($item['uri-id'], $item);
if ($item['gravity'] == GRAVITY_PARENT) {
if ($item['gravity'] == self::GRAVITY_PARENT) {
Post\Thread::insert($item['uri-id'], $item);
}
@ -1187,7 +1255,7 @@ class Item
}
// Create Diaspora signature
if ($item['origin'] && empty($item['diaspora_signed_text']) && ($item['gravity'] != GRAVITY_PARENT)) {
if ($item['origin'] && empty($item['diaspora_signed_text']) && ($item['gravity'] != self::GRAVITY_PARENT)) {
$signed = Diaspora::createCommentSignature($item);
if (!empty($signed)) {
$item['diaspora_signed_text'] = json_encode($signed);
@ -1227,7 +1295,7 @@ class Item
return 0;
}
if ($item['gravity'] == GRAVITY_PARENT) {
if ($item['gravity'] == self::GRAVITY_PARENT) {
$item['post-user-id'] = $post_user_id;
Post\ThreadUser::insert($item['uri-id'], $item['uid'], $item);
}
@ -1245,7 +1313,7 @@ class Item
// update the commented timestamp on the parent
if (DI::config()->get('system', 'like_no_comment')) {
// Update when it is a comment
$update_commented = in_array($posted_item['gravity'], [GRAVITY_PARENT, GRAVITY_COMMENT]);
$update_commented = in_array($posted_item['gravity'], [self::GRAVITY_PARENT, self::GRAVITY_COMMENT]);
} else {
// Update when it isn't a follow or tag verb
$update_commented = !in_array($posted_item['verb'], [Activity::FOLLOW, Activity::TAG]);
@ -1269,7 +1337,7 @@ class Item
}
if ($notify) {
if (!\Friendica\Content\Feature::isEnabled($posted_item['uid'], 'explicit_mentions') && ($posted_item['gravity'] == GRAVITY_COMMENT)) {
if (!\Friendica\Content\Feature::isEnabled($posted_item['uid'], 'explicit_mentions') && ($posted_item['gravity'] == self::GRAVITY_COMMENT)) {
Tag::createImplicitMentions($posted_item['uri-id'], $posted_item['thr-parent-id']);
}
Hook::callAll('post_local_end', $posted_item);
@ -1277,7 +1345,7 @@ class Item
Hook::callAll('post_remote_end', $posted_item);
}
if ($posted_item['gravity'] === GRAVITY_PARENT) {
if ($posted_item['gravity'] === self::GRAVITY_PARENT) {
self::addShadow($post_user_id);
} else {
self::addShadowPost($post_user_id);
@ -1304,15 +1372,16 @@ class Item
}
}
if (!empty($source) && ($transmit || DI::config()->get('debug', 'store_source'))) {
Post\Activity::insert($item['uri-id'], $source);
}
if ($transmit) {
if (!empty($source)) {
Post\Activity::insert($item['uri-id'], $source);
}
Worker::add(['priority' => $priority, 'dont_fork' => true], 'Notifier', $notify_type, (int)$posted_item['uri-id'], (int)$posted_item['uid']);
}
// Fill the cache with the rendered content.
if (in_array($posted_item['gravity'], [GRAVITY_PARENT, GRAVITY_COMMENT]) && ($posted_item['uid'] == 0)) {
if (in_array($posted_item['gravity'], [self::GRAVITY_PARENT, self::GRAVITY_COMMENT]) && ($posted_item['uid'] == 0)) {
self::updateDisplayCache($posted_item['uri-id']);
}
@ -1328,7 +1397,7 @@ class Item
*/
public static function getPostReason(array $item): int
{
$actor = ($item['gravity'] == GRAVITY_PARENT) ? $item['owner-id'] : $item['author-id'];
$actor = ($item['gravity'] == self::GRAVITY_PARENT) ? $item['owner-id'] : $item['author-id'];
if (empty($item['origin']) && ($item['uid'] != 0) && Contact::isSharing($actor, $item['uid'])) {
return self::PR_FOLLOWER;
}
@ -1409,7 +1478,7 @@ class Item
*/
private static function distributeByTags(array $item)
{
if (($item['uid'] != 0) || ($item['gravity'] != GRAVITY_PARENT) || !in_array($item['network'], Protocol::FEDERATED)) {
if (($item['uid'] != 0) || ($item['gravity'] != self::GRAVITY_PARENT) || !in_array($item['network'], Protocol::FEDERATED)) {
return;
}
@ -1535,7 +1604,7 @@ class Item
return 0;
}
if (($uid != 0) && ($item['gravity'] == GRAVITY_PARENT)) {
if (($uid != 0) && ($item['gravity'] == self::GRAVITY_PARENT)) {
$owner = User::getOwnerDataById($uid);
if (($owner['contact-type'] == User::ACCOUNT_TYPE_COMMUNITY) && !Tag::isMentioned($uri_id, $owner['url'])) {
Logger::info('Target user is a forum but is not mentioned here, thread will not be stored', ['uid' => $uid, 'uri-id' => $uri_id]);
@ -1552,13 +1621,13 @@ class Item
$item = array_merge($item, $fields);
if (($uid != 0) && Contact::isSharing(($item['gravity'] == GRAVITY_PARENT) ? $item['owner-id'] : $item['author-id'], $uid)) {
if (($uid != 0) && Contact::isSharing(($item['gravity'] == Item::GRAVITY_PARENT) ? $item['owner-id'] : $item['author-id'], $uid)) {
$item['post-reason'] = self::PR_FOLLOWER;
}
$is_reshare = ($item['gravity'] == GRAVITY_ACTIVITY) && ($item['verb'] == Activity::ANNOUNCE);
$is_reshare = ($item['gravity'] == self::GRAVITY_ACTIVITY) && ($item['verb'] == Activity::ANNOUNCE);
if (($uid != 0) && (($item['gravity'] == GRAVITY_PARENT) || $is_reshare) &&
if (($uid != 0) && (($item['gravity'] == self::GRAVITY_PARENT) || $is_reshare) &&
DI::pConfig()->get($uid, 'system', 'accept_only_sharer') == self::COMPLETION_NONE &&
!in_array($item['post-reason'], [self::PR_FOLLOWER, self::PR_TAG, self::PR_TO, self::PR_CC])) {
Logger::info('Contact is not a follower, thread will not be stored', ['author' => $item['author-link'], 'uid' => $uid, 'uri-id' => $uri_id, 'post-reason' => $item['post-reason']]);
@ -1567,7 +1636,7 @@ class Item
$causer = $item['causer-id'] ?: $item['author-id'];
if (($uri_id != $item['parent-uri-id']) && ($item['gravity'] == GRAVITY_COMMENT) && !Post::exists(['uri-id' => $item['parent-uri-id'], 'uid' => $uid])) {
if (($uri_id != $item['parent-uri-id']) && ($item['gravity'] == self::GRAVITY_COMMENT) && !Post::exists(['uri-id' => $item['parent-uri-id'], 'uid' => $uid])) {
if (!self::fetchParent($item['parent-uri-id'], $uid, $causer)) {
Logger::info('Parent post had not been added', ['uri-id' => $item['parent-uri-id'], 'uid' => $uid, 'causer' => $causer]);
return 0;
@ -1708,7 +1777,7 @@ class Item
$item['contact-id'] = self::contactId($item);
$notify = false;
if ($item['gravity'] == GRAVITY_PARENT) {
if ($item['gravity'] == self::GRAVITY_PARENT) {
$contact = DBA::selectFirst('contact', [], ['id' => $item['contact-id'], 'self' => false]);
if (DBA::isResult($contact)) {
$notify = self::isRemoteSelf($contact, $item);
@ -1738,7 +1807,7 @@ class Item
private static function addShadow(int $itemid)
{
$fields = ['uid', 'private', 'visible', 'deleted', 'network', 'uri-id'];
$condition = ['id' => $itemid, 'gravity' => GRAVITY_PARENT];
$condition = ['id' => $itemid, 'gravity' => self::GRAVITY_PARENT];
$item = Post::selectFirst($fields, $condition);
if (!DBA::isResult($item)) {
@ -1806,7 +1875,7 @@ class Item
}
// Is it a toplevel post?
if ($item['gravity'] == GRAVITY_PARENT) {
if ($item['gravity'] == self::GRAVITY_PARENT) {
self::addShadow($itemid);
return;
}
@ -1868,7 +1937,7 @@ class Item
return $item['language'];
}
if (!in_array($item['gravity'], [GRAVITY_PARENT, GRAVITY_COMMENT]) || empty($item['body'])) {
if (!in_array($item['gravity'], [self::GRAVITY_PARENT, self::GRAVITY_COMMENT]) || empty($item['body'])) {
return '';
}
@ -1965,9 +2034,10 @@ class Item
* Posts that are created on this system are using System::createUUID.
* Received ActivityPub posts are using Processor::getGUIDByURL.
*
* @param string $uri uri of an item entry
* @param string $uri uri of an item entry
* @param string|null $host hostname for the GUID prefix
* @return string Unique guid
* @throws \Exception
*/
public static function guidFromUri(string $uri, string $host = null): string
{
@ -1978,11 +2048,16 @@ class Item
// Remove the scheme to make sure that "https" and "http" doesn't make a difference
unset($parsed['scheme']);
$hostPart = $host ?? $parsed['host'] ?? '';
if (!$hostPart) {
Logger::warning('Empty host GUID part', ['uri' => $uri, 'host' => $host, 'parsed' => $parsed, 'callstack' => System::callstack(10)]);
}
// Glue it together to be able to make a hash from it
$host_id = implode('/', $parsed);
// Use a mixture of several hashes to provide some GUID like experience
return hash('crc32', $host) . '-'. hash('joaat', $host_id) . '-'. hash('fnv164', $host_id);
return hash('crc32', $hostPart) . '-' . hash('joaat', $host_id) . '-' . hash('fnv164', $host_id);
}
/**
@ -2148,13 +2223,13 @@ class Item
return false;
}
$item = Post::selectFirst(self::ITEM_FIELDLIST, ['id' => $item_id, 'gravity' => [GRAVITY_PARENT, GRAVITY_COMMENT], 'origin' => false]);
$item = Post::selectFirst(self::ITEM_FIELDLIST, ['id' => $item_id, 'gravity' => [self::GRAVITY_PARENT, self::GRAVITY_COMMENT], 'origin' => false]);
if (!DBA::isResult($item)) {
Logger::debug('Post is an activity or origin or not found at all, quitting here.', ['id' => $item_id]);
return false;
}
if ($item['gravity'] == GRAVITY_PARENT) {
if ($item['gravity'] == self::GRAVITY_PARENT) {
if (Tag::isMentioned($item['uri-id'], $owner['url'])) {
Logger::info('Mention found in tag.', ['uri' => $item['uri'], 'uid' => $uid, 'id' => $item_id, 'uri-id' => $item['uri-id'], 'guid' => $item['guid']]);
} else {
@ -2199,7 +2274,12 @@ class Item
*/
private static function autoReshare(array $item)
{
if ($item['gravity'] != GRAVITY_PARENT) {
if ($item['gravity'] != self::GRAVITY_PARENT) {
return;
}
$cdata = Contact::getPublicAndUserContactID($item['author-id'], $item['uid']);
if (empty($cdata['user']) || ($cdata['user'] != $item['contact-id'])) {
return;
}
@ -2223,24 +2303,24 @@ class Item
public static function isRemoteSelf(array $contact, array &$datarray): bool
{
if (!$contact['remote_self']) {
if ($contact['remote_self'] != Contact::MIRROR_OWN_POST) {
return false;
}
// Prevent the forwarding of posts that are forwarded
if (!empty($datarray["extid"]) && ($datarray["extid"] == Protocol::DFRN)) {
if (!empty($datarray['extid']) && ($datarray['extid'] == Protocol::DFRN)) {
Logger::info('Already forwarded');
return false;
}
// Prevent to forward already forwarded posts
if ($datarray["app"] == DI::baseUrl()->getHostname()) {
if ($datarray['app'] == DI::baseUrl()->getHostname()) {
Logger::info('Already forwarded (second test)');
return false;
}
// Only forward posts
if ($datarray["verb"] != Activity::POST) {
if ($datarray['verb'] != Activity::POST) {
Logger::info('No post');
return false;
}
@ -2252,53 +2332,48 @@ class Item
$datarray2 = $datarray;
Logger::info('remote-self start', ['contact' => $contact['url'], 'remote_self'=> $contact['remote_self'], 'item' => $datarray]);
if ($contact['remote_self'] == Contact::MIRROR_OWN_POST) {
$self = DBA::selectFirst('contact', ['id', 'name', 'url', 'thumb'],
$self = DBA::selectFirst('contact', ['id', 'name', 'url', 'thumb'],
['uid' => $contact['uid'], 'self' => true]);
if (DBA::isResult($self)) {
$datarray['contact-id'] = $self["id"];
$datarray['owner-name'] = $self["name"];
$datarray['owner-link'] = $self["url"];
$datarray['owner-avatar'] = $self["thumb"];
$datarray['author-name'] = $datarray['owner-name'];
$datarray['author-link'] = $datarray['owner-link'];
$datarray['author-avatar'] = $datarray['owner-avatar'];
unset($datarray['edited']);
unset($datarray['network']);
unset($datarray['owner-id']);
unset($datarray['author-id']);
}
if ($contact['network'] != Protocol::FEED) {
$old_uri_id = $datarray["uri-id"] ?? 0;
$datarray["guid"] = System::createUUID();
unset($datarray["plink"]);
$datarray["uri"] = self::newURI($datarray["guid"]);
$datarray["uri-id"] = ItemURI::getIdByURI($datarray["uri"]);
$datarray["extid"] = Protocol::DFRN;
$urlpart = parse_url($datarray2['author-link']);
$datarray["app"] = $urlpart["host"];
if (!empty($old_uri_id)) {
Post\Media::copy($old_uri_id, $datarray["uri-id"]);
}
unset($datarray["parent-uri"]);
unset($datarray["thr-parent"]);
} else {
$datarray['private'] = self::PUBLIC;
}
if (!DBA::isResult($self)) {
Logger::error('Self contact not found', ['uid' => $contact['uid']]);
return false;
}
$datarray['contact-id'] = $self['id'];
$datarray['author-name'] = $datarray['owner-name'] = $self['name'];
$datarray['author-link'] = $datarray['owner-link'] = $self['url'];
$datarray['author-avatar'] = $datarray['owner-avatar'] = $self['thumb'];
unset($datarray['edited']);
unset($datarray['network']);
unset($datarray['owner-id']);
unset($datarray['author-id']);
if ($contact['network'] != Protocol::FEED) {
$old_uri_id = $datarray['uri-id'] ?? 0;
$datarray['guid'] = System::createUUID();
unset($datarray['plink']);
$datarray['uri'] = self::newURI($datarray['guid']);
$datarray['uri-id'] = ItemURI::getIdByURI($datarray['uri']);
$datarray['extid'] = Protocol::DFRN;
$urlpart = parse_url($datarray2['author-link']);
$datarray['app'] = $urlpart['host'];
if (!empty($old_uri_id)) {
Post\Media::copy($old_uri_id, $datarray['uri-id']);
}
unset($datarray['parent-uri']);
unset($datarray['thr-parent']);
// Store the original post
$result = self::insert($datarray2);
Logger::info('remote-self post original item', ['contact' => $contact['url'], 'result'=> $result, 'item' => $datarray2]);
} else {
$datarray["app"] = "Feed";
$datarray['private'] = self::PUBLIC;
$datarray['app'] = 'Feed';
$result = true;
}
@ -2465,7 +2540,7 @@ class Item
}
$condition = ["`uid` = ? AND NOT `deleted` AND `gravity` = ?",
$uid, GRAVITY_PARENT];
$uid, self::GRAVITY_PARENT];
/*
* $expire_network_only = save your own wall posts
@ -2672,8 +2747,8 @@ class Item
$vids = Verb::getID($activity);
}
$condition = ['vid' => $vids, 'deleted' => false, 'gravity' => GRAVITY_ACTIVITY,
'author-id' => $author_id, 'uid' => $item['uid'], 'thr-parent-id' => $uri_id];
$condition = ['vid' => $vids, 'deleted' => false, 'gravity' => self::GRAVITY_ACTIVITY,
'author-id' => $author_id, 'uid' => $uid, 'thr-parent-id' => $uri_id];
$like_item = Post::selectFirst(['id', 'guid', 'verb'], $condition);
if (DBA::isResult($like_item)) {
@ -2726,7 +2801,7 @@ class Item
'network' => Protocol::DFRN,
'protocol' => Conversation::PARCEL_DIRECT,
'direction' => Conversation::PUSH,
'gravity' => GRAVITY_ACTIVITY,
'gravity' => self::GRAVITY_ACTIVITY,
'parent' => $item['id'],
'thr-parent' => $item['uri'],
'owner-id' => $author_id,
@ -2767,8 +2842,8 @@ class Item
*/
public static function getPermissionsConditionArrayByUserId(int $owner_id): array
{
$local_user = local_user();
$remote_user = Session::getRemoteContactID($owner_id);
$local_user = DI::userSession()->getLocalUserId();
$remote_user = DI::userSession()->getRemoteContactID($owner_id);
// default permissions - anonymous user
$condition = ["`private` != ?", self::PRIVATE];
@ -2799,8 +2874,8 @@ class Item
*/
public static function getPermissionsSQLByUserId(int $owner_id, string $table = ''): string
{
$local_user = local_user();
$remote_user = Session::getRemoteContactID($owner_id);
$local_user = DI::userSession()->getLocalUserId();
$remote_user = DI::userSession()->getRemoteContactID($owner_id);
if (!empty($table)) {
$table = DBA::quoteIdentifier($table) . '.';
@ -2851,9 +2926,9 @@ class Item
return $l10n->t('event');
} elseif (!empty($item['resource-id'])) {
return $l10n->t('photo');
} elseif ($item['gravity'] == GRAVITY_ACTIVITY) {
} elseif ($item['gravity'] == self::GRAVITY_ACTIVITY) {
return $l10n->t('activity');
} elseif ($item['gravity'] == GRAVITY_COMMENT) {
} elseif ($item['gravity'] == self::GRAVITY_COMMENT) {
return $l10n->t('comment');
}
@ -2941,23 +3016,48 @@ class Item
$item['hashtags'] = $tags['hashtags'];
$item['mentions'] = $tags['mentions'];
$body = $item['body'] ?? '';
$shared = BBCode::fetchShareAttributes($body);
if (!empty($shared['guid'])) {
$shared_item = Post::selectFirst(['uri-id', 'plink', 'has-media'], ['guid' => $shared['guid']]);
$shared_uri_id = $shared_item['uri-id'] ?? 0;
$shared_links = [strtolower($shared_item['plink'] ?? '')];
$shared_attachments = Post\Media::splitAttachments($shared_uri_id, $shared['guid'], [], $shared_item['has-media'] ?? false);
$body = $item['body'] = Post\Media::removeFromEndOfBody($item['body'] ?? '');
$fields = ['uri-id', 'uri', 'body', 'title', 'author-name', 'author-link', 'author-avatar', 'guid', 'created', 'plink', 'network', 'has-media', 'quote-uri-id', 'post-type'];
$shared_uri_id = 0;
$shared_links = [];
$shared = DI::contentItem()->getSharedPost($item, $fields);
if (!empty($shared['post'])) {
$shared_item = $shared['post'];
$quote_uri_id = $shared['post']['uri-id'];
$shared_links[] = strtolower($shared['post']['uri']);
$item['body'] = BBCode::removeSharedData($item['body']);
} elseif (empty($shared_item['uri-id']) && empty($item['quote-uri-id']) && ($item['network'] != Protocol::DIASPORA)) {
$media = Post\Media::getByURIId($item['uri-id'], [Post\Media::ACTIVITY]);
if (!empty($media)) {
$shared_item = Post::selectFirst($fields, ['plink' => $media[0]['url'], 'uid' => [$item['uid'], 0]]);
if (empty($shared_item['uri-id'])) {
$shared_item = Post::selectFirst($fields, ['uri' => $media[0]['url'], 'uid' => [$item['uid'], 0]]);
$shared_links[] = strtolower($media[0]['url']);
}
$quote_uri_id = $shared_item['uri-id'] ?? 0;
}
}
if (!empty($quote_uri_id)) {
$item['body'] .= "\n" . DI::contentItem()->createSharedBlockByArray($shared_item);
}
if (!empty($shared_item['uri-id'])) {
$shared_uri_id = $shared_item['uri-id'];
$shared_links[] = strtolower($shared_item['plink']);
$shared_attachments = Post\Media::splitAttachments($shared_uri_id, [], $shared_item['has-media']);
$shared_links = array_merge($shared_links, array_column($shared_attachments['visual'], 'url'));
$shared_links = array_merge($shared_links, array_column($shared_attachments['link'], 'url'));
$shared_links = array_merge($shared_links, array_column($shared_attachments['additional'], 'url'));
$item['body'] = self::replaceVisualAttachments($shared_attachments, $item['body']);
} else {
$shared_uri_id = 0;
$shared_links = [];
}
$attachments = Post\Media::splitAttachments($item['uri-id'], $item['guid'] ?? '', $shared_links, $item['has-media'] ?? false);
$attachments = Post\Media::splitAttachments($item['uri-id'], $shared_links, $item['has-media'] ?? false);
$item['body'] = self::replaceVisualAttachments($attachments, $item['body'] ?? '');
$item['body'] = preg_replace("/\s*\[attachment .*?\].*?\[\/attachment\]\s*/ism", "\n", $item['body']);
@ -2971,8 +3071,8 @@ class Item
// Compile eventual content filter reasons
$filter_reasons = [];
if (!$is_preview && public_contact() != $item['author-id']) {
if (!empty($item['content-warning']) && (!local_user() || !DI::pConfig()->get(local_user(), 'system', 'disable_cw', false))) {
if (!$is_preview && DI::userSession()->getPublicContactId() != $item['author-id']) {
if (!empty($item['content-warning']) && (!DI::userSession()->getLocalUserId() || !DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'system', 'disable_cw', false))) {
$filter_reasons[] = DI::l10n()->t('Content warning: %s', $item['content-warning']);
}
@ -3005,10 +3105,10 @@ class Item
}
if (!empty($shared_attachments)) {
$s = self::addVisualAttachments($shared_attachments, $item, $s, true);
$s = self::addVisualAttachments($shared_attachments, $shared_item, $s, true);
$s = self::addLinkAttachment($shared_uri_id ?: $item['uri-id'], $shared_attachments, $body, $s, true, []);
$s = self::addNonVisualAttachments($shared_attachments, $item, $s, true);
$body = preg_replace("/\s*\[share .*?\].*?\[\/share\]\s*/ism", '', $body);
$body = BBCode::removeSharedData($body);
}
$s = self::addVisualAttachments($attachments, $item, $s, false);
@ -3050,11 +3150,20 @@ class Item
{
// Make sure that for example site parameters aren't used when testing if the link is contained in the body
$urlparts = parse_url($url);
if (!empty($urlparts)) {
unset($urlparts['query']);
unset($urlparts['fragment']);
if (empty($urlparts)) {
return false;
}
unset($urlparts['query']);
unset($urlparts['fragment']);
try {
$url = (string)Uri::fromParts($urlparts);
} else {
} catch (\InvalidArgumentException $e) {
DI::logger()->notice('Invalid URL', ['$url' => $url, '$urlparts' => $urlparts]);
/* See https://github.com/friendica/friendica/issues/12113
* Malformed URLs will result in a Fatal Error
*/
return false;
}
@ -3067,12 +3176,14 @@ class Item
if (strpos($body, $url)) {
return true;
}
foreach ([0, 1, 2] as $size) {
if (preg_match('#/photo/.*-' . $size . '\.#ism', $url) &&
strpos(preg_replace('#(/photo/.*)-[012]\.#ism', '$1-' . $size . '.', $body), $url)) {
return true;
}
}
return false;
}
@ -3117,8 +3228,9 @@ class Item
private static function addVisualAttachments(array $attachments, array $item, string $content, bool $shared): string
{
DI::profiler()->startRecording('rendering');
$leading = '';
$leading = '';
$trailing = '';
$images = [];
// @todo In the future we should make a single for the template engine with all media in it. This allows more flexibilty.
foreach ($attachments['visual'] as $attachment) {
@ -3126,12 +3238,18 @@ class Item
continue;
}
if (!empty($attachment['preview'])) {
if ($attachment['filetype'] == 'image') {
$preview_url = Post\Media::getPreviewUrlForId($attachment['id'], ($attachment['width'] > $attachment['height']) ? Proxy::SIZE_MEDIUM : Proxy::SIZE_LARGE);
} elseif (!empty($attachment['preview'])) {
$preview_url = Post\Media::getPreviewUrlForId($attachment['id'], Proxy::SIZE_LARGE);
} else {
$preview_url = '';
}
if ($preview_url && self::containsLink($item['body'], $preview_url)) {
continue;
}
if (($attachment['filetype'] == 'video')) {
/// @todo Move the template to /content as well
$media = Renderer::replaceMacros(Renderer::getMarkupTemplate('video_top.tpl'), [
@ -3163,19 +3281,23 @@ class Item
$trailing .= $media;
}
} elseif ($attachment['filetype'] == 'image') {
$media = Renderer::replaceMacros(Renderer::getMarkupTemplate('content/image.tpl'), [
'$image' => [
'src' => Post\Media::getUrlForId($attachment['id']),
'preview' => Post\Media::getPreviewUrlForId($attachment['id'], ($attachment['width'] > $attachment['height']) ? Proxy::SIZE_MEDIUM : Proxy::SIZE_LARGE),
'attachment' => $attachment,
],
]);
// On Diaspora posts the attached pictures are leading
if ($item['network'] == Protocol::DIASPORA) {
$leading .= $media;
} else {
$trailing .= $media;
$src_url = Post\Media::getUrlForId($attachment['id']);
if (self::containsLink($item['body'], $src_url)) {
continue;
}
$images[] = ['src' => $src_url, 'preview' => $preview_url, 'attachment' => $attachment];
}
}
foreach ($images as $image) {
$media = Renderer::replaceMacros(Renderer::getMarkupTemplate('content/image.tpl'), [
'$image' => $image,
]);
// On Diaspora posts the attached pictures are leading
if ($item['network'] == Protocol::DIASPORA) {
$leading .= $media;
} else {
$trailing .= $media;
}
}
@ -3276,7 +3398,7 @@ class Item
}
DI::profiler()->stopRecording();
if (isset($data['url']) && !in_array($data['url'], $ignore_links)) {
if (isset($data['url']) && !in_array(strtolower($data['url']), $ignore_links)) {
if (!empty($data['description']) || !empty($data['image']) || !empty($data['preview'])) {
$parts = parse_url($data['url']);
if (!empty($parts['scheme']) && !empty($parts['host'])) {
@ -3293,7 +3415,10 @@ class Item
}
// @todo Use a template
$rendered = BBCode::convertAttachment('', BBCode::INTERNAL, false, $data, $uriid);
$preview_mode = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'system', 'preview_mode', BBCode::PREVIEW_LARGE);
if ($preview_mode != BBCode::PREVIEW_NONE) {
$rendered = BBCode::convertAttachment('', BBCode::INTERNAL, false, $data, $uriid, $preview_mode);
}
} elseif (!self::containsLink($content, $data['url'], Post\Media::HTML)) {
$rendered = Renderer::replaceMacros(Renderer::getMarkupTemplate('content/link.tpl'), [
'$url' => $data['url'],
@ -3369,7 +3494,7 @@ class Item
$percent = $option['replies'] / $question['voters'] * 100;
$options[$key]['vote'] = DI::l10n()->tt('%2$s (%3$d%%, %1$d vote)', '%2$s (%3$d%%, %1$d votes)', $option['replies'], $option['name'], round($percent, 1));
} else {
$options[$key]['vote'] = DI::l10n()->tt('%2$s (%1$d vote)', '%2$s (%1$d votes)', $option['replies'], $option['name'], );
$options[$key]['vote'] = DI::l10n()->tt('%2$s (%1$d vote)', '%2$s (%1$d votes)', $option['replies'], $option['name']);
}
}
@ -3408,7 +3533,7 @@ class Item
$plink = $item['uri'];
}
if (local_user()) {
if (DI::userSession()->getLocalUserId()) {
$ret = [
'href' => "display/" . $item['guid'],
'orig' => "display/" . $item['guid'],
@ -3554,21 +3679,6 @@ class Item
return 0;
}
/**
* Return share data from an item array (if the item is shared item)
* We are providing the complete Item array, because at some time in the future
* we hopefully will define these values not in the body anymore but in some item fields.
* This function is meant to replace all similar functions in the system.
*
* @param array $item
*
* @return array with share information
*/
public static function getShareArray(array $item): array
{
return BBCode::fetchShareAttributes($item['body']);
}
/**
* Check a prospective item array against user-level permissions
*
@ -3598,7 +3708,7 @@ class Item
return false;
}
if (!empty($item['causer-id']) && ($item['gravity'] === GRAVITY_PARENT) && Contact\User::isIgnored($item['causer-id'], $user_id)) {
if (!empty($item['causer-id']) && ($item['gravity'] === self::GRAVITY_PARENT) && Contact\User::isIgnored($item['causer-id'], $user_id)) {
Logger::notice('Causer is ignored by user', ['causer-link' => $item['causer-link'] ?? $item['causer-id'], 'uid' => $user_id, 'item-uri' => $item['uri']]);
return false;
}
@ -3607,31 +3717,61 @@ class Item
}
/**
* Improve the data in shared posts
* Fetch the uri-id of a quote
*
* @param array $item
* @return string body
* @param string $body
* @return integer
*/
public static function improveSharedDataInBody(array $item): string
public static function getQuoteUriId(string $body, int $uid = 0): int
{
$shared = BBCode::fetchShareAttributes($item['body']);
$shared = BBCode::fetchShareAttributes($body);
if (empty($shared['guid']) && empty($shared['message_id'])) {
return $item['body'];
return 0;
}
$link = $shared['link'] ?: $shared['message_id'];
if (empty($shared_content)) {
$shared_content = DI::contentItem()->createSharedPostByUrl($link, $item['uid'] ?? 0);
if (empty($shared['link']) && empty($shared['message_id'])) {
Logger::notice('Invalid share block.', ['share' => $shared]);
return 0;
}
if (empty($shared_content)) {
return $item['body'];
if (!empty($shared['guid'])) {
$shared_item = Post::selectFirst(['uri-id'], ['guid' => $shared['guid'], 'uid' => [0, $uid]]);
if (!empty($shared_item['uri-id'])) {
Logger::debug('Found post by guid', ['guid' => $shared['guid'], 'uid' => $uid]);
return $shared_item['uri-id'];
}
}
$item['body'] = preg_replace("/\[share.*?\](.*)\[\/share\]/ism", $shared_content, $item['body']);
if (!empty($shared['message_id'])) {
$shared_item = Post::selectFirst(['uri-id'], ['uri' => $shared['message_id'], 'uid' => [0, $uid]]);
if (!empty($shared_item['uri-id'])) {
Logger::debug('Found post by message_id', ['message_id' => $shared['message_id'], 'uid' => $uid]);
return $shared_item['uri-id'];
}
}
Logger::debug('New shared data', ['uri-id' => $item['uri-id'], 'link' => $link, 'guid' => $item['guid']]);
return $item['body'];
if (!empty($shared['link'])) {
$shared_item = Post::selectFirst(['uri-id'], ['plink' => $shared['link'], 'uid' => [0, $uid]]);
if (!empty($shared_item['uri-id'])) {
Logger::debug('Found post by link', ['link' => $shared['link'], 'uid' => $uid]);
return $shared_item['uri-id'];
}
}
$url = $shared['message_id'] ?: $shared['link'];
$id = self::fetchByLink($url);
if (!$id) {
Logger::notice('Post could not be fetched.', ['url' => $url, 'uid' => $uid]);
return 0;
}
$shared_item = Post::selectFirst(['uri-id'], ['id' => $id]);
if (!empty($shared_item['uri-id'])) {
Logger::debug('Fetched shared post', ['id' => $id, 'url' => $url, 'uid' => $uid]);
return $shared_item['uri-id'];
}
Logger::warning('Post does not exist although it was supposed to had been fetched.', ['id' => $id, 'url' => $url, 'uid' => $uid]);
return 0;
}
}

View file

@ -160,7 +160,7 @@ class ParsedLogIterator implements \Iterator
* @see Iterator::next()
* @return void
*/
public function next()
public function next(): void
{
$parsed = $this->read();
@ -177,7 +177,7 @@ class ParsedLogIterator implements \Iterator
* @see Iterator::rewind()
* @return void
*/
public function rewind()
public function rewind(): void
{
$this->value = null;
$this->reader->rewind();
@ -200,9 +200,9 @@ class ParsedLogIterator implements \Iterator
* Return current iterator value
*
* @see Iterator::current()
* @return ?ParsedLogLing
* @return ?ParsedLogLine
*/
public function current()
public function current(): ?ParsedLogLine
{
return $this->value;
}

View file

@ -59,8 +59,7 @@ class Mail
}
if (empty($msg['guid'])) {
$host = parse_url($msg['from-url'], PHP_URL_HOST);
$msg['guid'] = Item::guidFromUri($msg['uri'], $host);
$msg['guid'] = Item::guidFromUri($msg['uri'], parse_url($msg['from-url'], PHP_URL_HOST));
}
$msg['created'] = (!empty($msg['created']) ? DateTimeFormat::utc($msg['created']) : DateTimeFormat::utcNow());
@ -137,12 +136,12 @@ class Mail
$subject = DI::l10n()->t('[no subject]');
}
$me = DBA::selectFirst('contact', [], ['uid' => local_user(), 'self' => true]);
$me = DBA::selectFirst('contact', [], ['uid' => DI::userSession()->getLocalUserId(), 'self' => true]);
if (!DBA::isResult($me)) {
return -2;
}
$contacts = ACL::getValidMessageRecipientsForUser(local_user());
$contacts = ACL::getValidMessageRecipientsForUser(DI::userSession()->getLocalUserId());
$contactIndex = array_search($recipient, array_column($contacts, 'id'));
if ($contactIndex === false) {
@ -151,7 +150,7 @@ class Mail
$contact = $contacts[$contactIndex];
Photo::setPermissionFromBody($body, local_user(), $me['id'], '<' . $contact['id'] . '>', '', '', '');
Photo::setPermissionFromBody($body, DI::userSession()->getLocalUserId(), $me['id'], '<' . $contact['id'] . '>', '', '', '');
$guid = System::createUUID();
$uri = Item::newURI($guid);
@ -164,7 +163,7 @@ class Mail
if (strlen($replyto)) {
$reply = true;
$condition = ["`uid` = ? AND (`uri` = ? OR `parent-uri` = ?)",
local_user(), $replyto, $replyto];
DI::userSession()->getLocalUserId(), $replyto, $replyto];
$mail = DBA::selectFirst('mail', ['convid'], $condition);
if (DBA::isResult($mail)) {
$convid = $mail['convid'];
@ -177,7 +176,7 @@ class Mail
$conv_guid = System::createUUID();
$convuri = $contact['addr'] . ':' . $conv_guid;
$fields = ['uid' => local_user(), 'guid' => $conv_guid, 'creator' => $me['addr'],
$fields = ['uid' => DI::userSession()->getLocalUserId(), 'guid' => $conv_guid, 'creator' => $me['addr'],
'created' => DateTimeFormat::utcNow(), 'updated' => DateTimeFormat::utcNow(),
'subject' => $subject, 'recips' => $contact['addr'] . ';' . $me['addr']];
if (DBA::insert('conv', $fields)) {
@ -196,7 +195,7 @@ class Mail
$post_id = self::insert(
[
'uid' => local_user(),
'uid' => DI::userSession()->getLocalUserId(),
'guid' => $guid,
'convid' => $convid,
'from-name' => $me['name'],
@ -232,14 +231,14 @@ class Mail
foreach ($images as $image) {
$image_rid = Photo::ridFromURI($image);
if (!empty($image_rid)) {
Photo::update(['allow-cid' => '<' . $recipient . '>'], ['resource-id' => $image_rid, 'album' => 'Wall Photos', 'uid' => local_user()]);
Photo::update(['allow-cid' => '<' . $recipient . '>'], ['resource-id' => $image_rid, 'album' => 'Wall Photos', 'uid' => DI::userSession()->getLocalUserId()]);
}
}
}
}
if ($post_id) {
Worker::add(PRIORITY_HIGH, "Notifier", Delivery::MAIL, $post_id);
Worker::add(Worker::PRIORITY_HIGH, "Notifier", Delivery::MAIL, $post_id);
return intval($post_id);
} else {
return -3;

View file

@ -25,6 +25,7 @@ use Friendica\Core\Addon;
use Friendica\Core\Config\Capability\IManageConfigValues;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\Item;
use stdClass;
/**
@ -62,8 +63,8 @@ class Nodeinfo
$logger->info('user statistics', $userStats);
$posts = DBA::count('post-thread', ["`uri-id` IN (SELECT `uri-id` FROM `post-user` WHERE NOT `deleted` AND `origin`)"]);
$comments = DBA::count('post', ["NOT `deleted` AND `gravity` = ? AND `uri-id` IN (SELECT `uri-id` FROM `post-user` WHERE `origin`)", GRAVITY_COMMENT]);
$config->set('nodeinfo', 'local_posts', $posts);
$comments = DBA::count('post', ["NOT `deleted` AND `gravity` = ? AND `uri-id` IN (SELECT `uri-id` FROM `post-user` WHERE `origin`)", Item::GRAVITY_COMMENT]);
$config->set('nodeinfo', 'local_posts', $posts);
$config->set('nodeinfo', 'local_comments', $comments);
$logger->info('User actitivy', ['posts' => $posts, 'comments' => $comments]);
@ -79,19 +80,17 @@ class Nodeinfo
$config = DI::config();
$usage = new stdClass();
$usage->users = [];
$usage->users = new \stdClass;
if (!empty($config->get('system', 'nodeinfo'))) {
$usage->users = [
'total' => intval($config->get('nodeinfo', 'total_users')),
'activeHalfyear' => intval($config->get('nodeinfo', 'active_users_halfyear')),
'activeMonth' => intval($config->get('nodeinfo', 'active_users_monthly'))
];
$usage->users->total = intval($config->get('nodeinfo', 'total_users'));
$usage->users->activeHalfyear = intval($config->get('nodeinfo', 'active_users_halfyear'));
$usage->users->activeMonth = intval($config->get('nodeinfo', 'active_users_monthly'));
$usage->localPosts = intval($config->get('nodeinfo', 'local_posts'));
$usage->localComments = intval($config->get('nodeinfo', 'local_comments'));
if ($version2) {
$usage->users['activeWeek'] = intval($config->get('nodeinfo', 'active_users_weekly'));
$usage->users->activeWeek = intval($config->get('nodeinfo', 'active_users_weekly'));
}
}
@ -163,25 +162,16 @@ class Nodeinfo
*
* @param IManageConfigValues $config Configuration instance
* @return array Organization information
* @throws \Exception
*/
public static function getOrganization(IManageConfigValues $config): array
{
$organization = [
'name' => null,
'contact' => null,
'account' => null
$administrator = User::getFirstAdmin(['username', 'email', 'nickname']);
return [
'name' => $administrator['username'] ?? null,
'contact' => $administrator['email'] ?? null,
'account' => $administrator['nickname'] ?? '' ? DI::baseUrl()->get() . '/profile/' . $administrator['nickname'] : null,
];
if (!empty($config->get('config', 'admin_email'))) {
$adminList = explode(',', str_replace(' ', '', $config->get('config', 'admin_email')));
$organization['contact'] = $adminList[0];
$administrator = User::getByEmail($adminList[0], ['username', 'nickname']);
if (!empty($administrator)) {
$organization['name'] = $administrator['username'];
$organization['account'] = DI::baseUrl()->get() . '/profile/' . $administrator['nickname'];
}
}
return $organization;
}
}

View file

@ -25,7 +25,6 @@ use Friendica\Core\Cache\Enum\Duration;
use Friendica\Core\Logger;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\Database\DBStructure;
use Friendica\DI;
use Friendica\Core\Storage\Type\ExternalResource;
use Friendica\Core\Storage\Exception\InvalidClassStorageException;
@ -174,6 +173,64 @@ class Photo
return $photo;
}
/**
* Returns all browsable albums for a given user
*
* @param int $uid The given user
*
* @return array An array of albums
* @throws \Exception
*/
public static function getBrowsableAlbumsForUser(int $uid): array
{
$photos = DBA::toArray(
DBA::p(
"SELECT DISTINCT(`album`) AS `album` FROM `photo` WHERE `uid` = ? AND NOT `photo-type` IN (?, ?)",
$uid,
static::CONTACT_AVATAR,
static::CONTACT_BANNER
)
);
return array_column($photos, 'album');
}
/**
* Returns browsable photos for a given user (optional and a given album)
*
* @param int $uid The given user id
* @param string|null $album (optional) The given album
*
* @return array All photos of the user/album
* @throws \Exception
*/
public static function getBrowsablePhotosForUser(int $uid, string $album = null): array
{
$values = [
$uid,
Photo::CONTACT_AVATAR,
Photo::CONTACT_BANNER
];
if (!empty($album)) {
$sqlExtra = "AND `album` = ? ";
$values[] = $album;
$sqlExtra2 = "";
} else {
$sqlExtra = '';
$sqlExtra2 = ' ORDER BY created DESC LIMIT 0, 10';
}
return DBA::toArray(
DBA::p(
"SELECT `resource-id`, ANY_VALUE(`id`) AS `id`, ANY_VALUE(`filename`) AS `filename`, ANY_VALUE(`type`) AS `type`,
min(`scale`) AS `hiq`, max(`scale`) AS `loq`, ANY_VALUE(`desc`) AS `desc`, ANY_VALUE(`created`) AS `created`
FROM `photo` WHERE `uid` = ? AND NOT `photo-type` IN (?, ?) $sqlExtra
GROUP BY `resource-id` $sqlExtra2",
$values
));
}
/**
* Check if photo with given conditions exists
*
@ -289,11 +346,14 @@ class Photo
* @param string $url Image URL
* @param int $uid User ID of the requesting person
* @param string $mimetype Image mime type. Is guessed by file name when empty.
* @param string $blurhash The blurhash that will be used to generate a picture when the original picture can't be fetched
* @param int $width Image width
* @param int $height Image height
*
* @return array
* @throws \Exception
*/
public static function createPhotoForExternalResource(string $url, int $uid = 0, string $mimetype = ''): array
public static function createPhotoForExternalResource(string $url, int $uid = 0, string $mimetype = '', string $blurhash = null, int $width = null, int $height = null): array
{
if (empty($mimetype)) {
$mimetype = Images::guessTypeByExtension($url);
@ -307,6 +367,9 @@ class Photo
$photo['backend-ref'] = json_encode(['url' => $url, 'uid' => $uid]);
$photo['type'] = $mimetype;
$photo['cacheable'] = true;
$photo['blurhash'] = $blurhash;
$photo['width'] = $width;
$photo['height'] = $height;
return $photo;
}
@ -379,6 +442,7 @@ class Photo
'height' => $image->getHeight(),
'width' => $image->getWidth(),
'datasize' => strlen($image->asString()),
'blurhash' => $image->getBlurHash(),
'data' => $data,
'scale' => $scale,
'photo-type' => $type,
@ -518,8 +582,9 @@ class Photo
$image->scaleToSquare(300);
$filesize = strlen($image->asString());
$maximagesize = DI::config()->get('system', 'maximagesize');
if (!empty($maximagesize) && ($filesize > $maximagesize)) {
$maximagesize = Strings::getBytesFromShorthand(DI::config()->get('system', 'maximagesize'));
if ($maximagesize && ($filesize > $maximagesize)) {
Logger::info('Avatar exceeds image limit', ['uid' => $uid, 'cid' => $cid, 'maximagesize' => $maximagesize, 'size' => $filesize, 'type' => $image->getType()]);
if ($image->getType() == 'image/gif') {
$image->toStatic();
@ -639,10 +704,10 @@ class Photo
{
$sql_extra = Security::getPermissionsSQLByUserId($uid);
$avatar_type = (local_user() && (local_user() == $uid)) ? self::USER_AVATAR : self::DEFAULT;
$banner_type = (local_user() && (local_user() == $uid)) ? self::USER_BANNER : self::DEFAULT;
$avatar_type = (DI::userSession()->getLocalUserId() && (DI::userSession()->getLocalUserId() == $uid)) ? self::USER_AVATAR : self::DEFAULT;
$banner_type = (DI::userSession()->getLocalUserId() && (DI::userSession()->getLocalUserId() == $uid)) ? self::USER_BANNER : self::DEFAULT;
$key = 'photo_albums:' . $uid . ':' . local_user() . ':' . remote_user();
$key = 'photo_albums:' . $uid . ':' . DI::userSession()->getLocalUserId() . ':' . DI::userSession()->getRemoteUserId();
$albums = DI::cache()->get($key);
if (is_null($albums) || $update) {
@ -681,7 +746,7 @@ class Photo
*/
public static function clearAlbumCache(int $uid)
{
$key = 'photo_albums:' . $uid . ':' . local_user() . ':' . remote_user();
$key = 'photo_albums:' . $uid . ':' . DI::userSession()->getLocalUserId() . ':' . DI::userSession()->getRemoteUserId();
DI::cache()->set($key, null, Duration::DAY);
}
@ -909,9 +974,9 @@ class Photo
$width = $image->getWidth();
$height = $image->getHeight();
$maximagesize = DI::config()->get('system', 'maximagesize');
$maximagesize = Strings::getBytesFromShorthand(DI::config()->get('system', 'maximagesize'));
if (!empty($maximagesize) && ($filesize > $maximagesize)) {
if ($maximagesize && ($filesize > $maximagesize)) {
// Scale down to multiples of 640 until the maximum size isn't exceeded anymore
foreach ([5120, 2560, 1280, 640] as $pixels) {
if (($filesize > $maximagesize) && (max($width, $height) > $pixels)) {
@ -1131,8 +1196,8 @@ class Photo
$picture['height'] = $photo['height'];
$picture['type'] = $photo['type'];
$picture['albumpage'] = DI::baseUrl() . '/photos/' . $user['nickname'] . '/image/' . $resource_id;
$picture['picture'] = DI::baseUrl() . '/photo/{$resource_id}-0.' . $image->getExt();
$picture['preview'] = DI::baseUrl() . '/photo/{$resource_id}-{$smallest}.' . $image->getExt();
$picture['picture'] = DI::baseUrl() . '/photo/' . $resource_id . '-0.' . $image->getExt();
$picture['preview'] = DI::baseUrl() . '/photo/' . $resource_id . '-' . $smallest . '.' . $image->getExt();
Logger::info('upload done', ['picture' => $picture]);
return $picture;
@ -1260,7 +1325,7 @@ class Photo
logger::warning('profile banner upload with scale 3 (960) failed');
}
logger::info('new profile banner upload ended');
logger::info('new profile banner upload ended', ['uid' => $uid, 'resource_id' => $resource_id, 'filename' => $filename]);
$condition = ["`photo-type` = ? AND `resource-id` != ? AND `uid` = ?", self::USER_BANNER, $resource_id, $uid];
self::update(['photo-type' => self::DEFAULT], $condition);
@ -1273,4 +1338,3 @@ class Photo
return $resource_id;
}
}

View file

@ -26,7 +26,6 @@ use Friendica\Core\Logger;
use Friendica\Core\System;
use Friendica\Database\Database;
use Friendica\Database\DBA;
use Friendica\Database\DBStructure;
use Friendica\DI;
use Friendica\Protocol\Activity;
@ -103,26 +102,25 @@ class Post
}
/**
* Fills an array with data from an post query
* Fills an array with data from a post query
*
* @param object $stmt statement object
* @param bool $do_close
* @param object|bool $stmt Return value from Database->select
* @return array Data array
* @todo Find proper type-hint for $stmt and maybe avoid boolean
* @throws \Exception
*/
public static function toArray($stmt, bool $do_close = true)
public static function toArray($stmt): array
{
if (is_bool($stmt)) {
return $stmt;
return [];
}
$data = [];
while ($row = self::fetch($stmt)) {
$data[] = $row;
}
if ($do_close) {
DBA::close($stmt);
}
DBA::close($stmt);
return $data;
}
@ -376,6 +374,21 @@ class Post
return self::selectView('post-thread-user-view', $selected, $condition, $params);
}
/**
* Select rows from the post-thread-view view
*
* @param array $selected Array of selected fields, empty for all
* @param array $condition Array of fields for condition
* @param array $params Array of several parameters
*
* @return boolean|object
* @throws \Exception
*/
public static function selectPostThread(array $selected = [], array $condition = [], array $params = [])
{
return self::selectView('post-thread-view', $selected, $condition, $params);
}
/**
* Select rows from the given view for a given user
*
@ -405,12 +418,12 @@ class Post
AND NOT `owner-id` IN (SELECT `cid` FROM `user-contact` WHERE `uid` = ? AND `blocked` AND `cid` = `owner-id`)
AND NOT (`gravity` = ? AND `author-id` IN (SELECT `cid` FROM `user-contact` WHERE `uid` = ? AND `ignored` AND `cid` = `author-id`))
AND NOT (`gravity` = ? AND `owner-id` IN (SELECT `cid` FROM `user-contact` WHERE `uid` = ? AND `ignored` AND `cid` = `owner-id`))",
0, Contact::SHARING, Contact::FRIEND, GRAVITY_PARENT, 0, $uid, $uid, $uid, GRAVITY_PARENT, $uid, GRAVITY_PARENT, $uid]);
0, Contact::SHARING, Contact::FRIEND, Item::GRAVITY_PARENT, 0, $uid, $uid, $uid, Item::GRAVITY_PARENT, $uid, Item::GRAVITY_PARENT, $uid]);
$select_string = implode(', ', array_map([DBA::class, 'quoteIdentifier'], $selected));
$condition_string = DBA::buildCondition($condition);
$param_string = DBA::buildParameter($params);
$param_string = DBA::buildParameter($params);
$sql = "SELECT " . $select_string . " FROM `" . $view . "` " . $condition_string . $param_string;
$sql = DBA::cleanQuery($sql);
@ -507,7 +520,7 @@ class Post
{
$affected = 0;
Logger::info('Start Update', ['fields' => $fields, 'condition' => $condition, 'uid' => local_user(),'callstack' => System::callstack(10)]);
Logger::info('Start Update', ['fields' => $fields, 'condition' => $condition, 'uid' => DI::userSession()->getLocalUserId(),'callstack' => System::callstack(10)]);
// Don't allow changes to fields that are responsible for the relation between the records
unset($fields['id']);
@ -520,7 +533,7 @@ class Post
unset($fields['parent-uri']);
unset($fields['parent-uri-id']);
$thread_condition = DBA::mergeConditions($condition, ['gravity' => GRAVITY_PARENT]);
$thread_condition = DBA::mergeConditions($condition, ['gravity' => Item::GRAVITY_PARENT]);
// To ensure the data integrity we do it in an transaction
DBA::transaction();
@ -528,7 +541,7 @@ class Post
$update_fields = DI::dbaDefinition()->truncateFieldsForTable('post-user', $fields);
if (!empty($update_fields)) {
$affected_count = 0;
$posts = DBA::select('post-user-view', ['post-user-id'], $condition);
$posts = DBA::select('post-user-view', ['post-user-id'], $condition);
while ($rows = DBA::toArray($posts, false, 100)) {
$puids = array_column($rows, 'post-user-id');
if (!DBA::update('post-user', $update_fields, ['id' => $puids])) {
@ -545,7 +558,7 @@ class Post
$update_fields = DI::dbaDefinition()->truncateFieldsForTable('post-content', $fields);
if (!empty($update_fields)) {
$affected_count = 0;
$posts = DBA::select('post-user-view', ['uri-id'], $condition, ['group_by' => ['uri-id']]);
$posts = DBA::select('post-user-view', ['uri-id'], $condition, ['group_by' => ['uri-id']]);
while ($rows = DBA::toArray($posts, false, 100)) {
$uriids = array_column($rows, 'uri-id');
if (!DBA::update('post-content', $update_fields, ['uri-id' => $uriids])) {
@ -562,7 +575,7 @@ class Post
$update_fields = DI::dbaDefinition()->truncateFieldsForTable('post', $fields);
if (!empty($update_fields)) {
$affected_count = 0;
$posts = DBA::select('post-user-view', ['uri-id'], $condition, ['group_by' => ['uri-id']]);
$posts = DBA::select('post-user-view', ['uri-id'], $condition, ['group_by' => ['uri-id']]);
while ($rows = DBA::toArray($posts, false, 100)) {
$uriids = array_column($rows, 'uri-id');
@ -585,7 +598,7 @@ class Post
$update_fields = Post\DeliveryData::extractFields($fields);
if (!empty($update_fields)) {
$affected_count = 0;
$posts = DBA::select('post-user-view', ['uri-id'], $condition, ['group_by' => ['uri-id']]);
$posts = DBA::select('post-user-view', ['uri-id'], $condition, ['group_by' => ['uri-id']]);
while ($rows = DBA::toArray($posts, false, 100)) {
$uriids = array_column($rows, 'uri-id');
if (!DBA::update('post-delivery-data', $update_fields, ['uri-id' => $uriids])) {
@ -602,7 +615,7 @@ class Post
$update_fields = DI::dbaDefinition()->truncateFieldsForTable('post-thread', $fields);
if (!empty($update_fields)) {
$affected_count = 0;
$posts = DBA::select('post-user-view', ['uri-id'], $thread_condition, ['group_by' => ['uri-id']]);
$posts = DBA::select('post-user-view', ['uri-id'], $thread_condition, ['group_by' => ['uri-id']]);
while ($rows = DBA::toArray($posts, false, 100)) {
$uriids = array_column($rows, 'uri-id');
if (!DBA::update('post-thread', $update_fields, ['uri-id' => $uriids])) {
@ -619,7 +632,7 @@ class Post
$update_fields = DI::dbaDefinition()->truncateFieldsForTable('post-thread-user', $fields);
if (!empty($update_fields)) {
$affected_count = 0;
$posts = DBA::select('post-user-view', ['post-user-id'], $thread_condition);
$posts = DBA::select('post-user-view', ['post-user-id'], $thread_condition);
while ($rows = DBA::toArray($posts, false, 100)) {
$thread_puids = array_column($rows, 'post-user-id');
if (!DBA::update('post-thread-user', $update_fields, ['post-user-id' => $thread_puids])) {

View file

@ -38,12 +38,6 @@ class Delayed
* This is used for automated scheduled posts via feeds or from the API.
*/
const PREPARED = 0;
/**
* The content is posted like a manual post. Means some processing of body will be done.
* Also it is posted with default permissions and default connector settings.
* This is used for mirrored connector posts.
*/
const UNPREPARED = 1;
/**
* Like PREPARED, but additionally the connector settings can differ.
* This is used when manually publishing scheduled posts.
@ -80,7 +74,7 @@ class Delayed
Logger::notice('Adding post for delayed publishing', ['uid' => $item['uid'], 'delayed' => $delayed, 'uri' => $uri]);
$wid = Worker::add(['priority' => PRIORITY_HIGH, 'delayed' => $delayed], 'DelayedPublish', $item, $notify, $taglist, $attachments, $preparation_mode, $uri);
$wid = Worker::add(['priority' => Worker::PRIORITY_HIGH, 'delayed' => $delayed], 'DelayedPublish', $item, $notify, $taglist, $attachments, $preparation_mode, $uri);
if (!$wid) {
return 0;
}
@ -199,37 +193,9 @@ class Delayed
$item['attachments'] = $attachments;
}
if ($preparation_mode == self::UNPREPARED) {
$_SESSION['authenticated'] = true;
$_SESSION['uid'] = $item['uid'];
$_REQUEST = $item;
$_REQUEST['api_source'] = true;
$_REQUEST['profile_uid'] = $item['uid'];
$_REQUEST['title'] = $item['title'] ?? '';
if (!empty($item['app'])) {
$_REQUEST['source'] = $item['app'];
}
require_once 'mod/item.php';
$id = item_post(DI::app());
if (empty($uri) && !empty($item['extid'])) {
$uri = $item['extid'];
}
Logger::notice('Unprepared post stored', ['id' => $id, 'uid' => $item['uid'], 'uri' => $uri]);
if (self::exists($uri, $item['uid'])) {
self::delete($uri, $item['uid']);
}
return $id;
}
$id = Item::insert($item, $notify, $preparation_mode == self::PREPARED);
Logger::notice('Post stored', ['id' => $id, 'uid' => $item['uid'], 'cid' => $item['contact-id']]);
Logger::notice('Post stored', ['id' => $id, 'uid' => $item['uid'], 'cid' => $item['contact-id'] ?? 'N/A']);
if (empty($uri) && !empty($item['uri'])) {
$uri = $item['uri'];

View file

@ -23,10 +23,12 @@ namespace Friendica\Model\Post;
use Friendica\Content\Text\BBCode;
use Friendica\Core\Logger;
use Friendica\Core\Protocol;
use Friendica\Core\System;
use Friendica\Database\Database;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\Contact;
use Friendica\Model\Item;
use Friendica\Model\Photo;
use Friendica\Model\Post;
@ -56,12 +58,15 @@ class Media
const HTML = 17;
const XML = 18;
const PLAIN = 19;
const ACTIVITY = 20;
const ACCOUNT = 21;
const DOCUMENT = 128;
/**
* Insert a post-media record
*
* @param array $media
* @param bool $force
* @return void
*/
public static function insert(array $media, bool $force = false)
@ -113,7 +118,7 @@ class Media
*/
private static function unsetEmptyFields(array $media): array
{
$fields = ['mimetype', 'height', 'width', 'size', 'preview', 'preview-height', 'preview-width', 'description'];
$fields = ['mimetype', 'height', 'width', 'size', 'preview', 'preview-height', 'preview-width', 'blurhash', 'description'];
foreach ($fields as $field) {
if (empty($media[$field])) {
unset($media[$field]);
@ -199,6 +204,7 @@ class Media
$media['size'] = $imagedata['size'];
$media['width'] = $imagedata[0];
$media['height'] = $imagedata[1];
$media['blurhash'] = $imagedata['blurhash'] ?? null;
} else {
Logger::notice('No image data', ['media' => $media]);
}
@ -215,20 +221,142 @@ class Media
$media = self::addType($media);
}
if ($media['type'] == self::HTML) {
$data = ParseUrl::getSiteinfoCached($media['url'], false);
$media['preview'] = $data['images'][0]['src'] ?? null;
$media['preview-height'] = $data['images'][0]['height'] ?? null;
$media['preview-width'] = $data['images'][0]['width'] ?? null;
$media['description'] = $data['text'] ?? null;
$media['name'] = $data['title'] ?? null;
$media['author-url'] = $data['author_url'] ?? null;
$media['author-name'] = $data['author_name'] ?? null;
$media['author-image'] = $data['author_img'] ?? null;
$media['publisher-url'] = $data['publisher_url'] ?? null;
$media['publisher-name'] = $data['publisher_name'] ?? null;
$media['publisher-image'] = $data['publisher_img'] ?? null;
if (in_array($media['type'], [self::TEXT, self::APPLICATION, self::HTML, self::XML, self::PLAIN])) {
$media = self::addActivity($media);
}
if (in_array($media['type'], [self::TEXT, self::APPLICATION, self::HTML, self::XML, self::PLAIN])) {
$media = self::addAccount($media);
}
if ($media['type'] == self::HTML) {
$media = self::addPage($media);
}
return $media;
}
/**
* Adds the activity type if the media entry is linked to an activity
*
* @param array $media
* @return array
*/
private static function addActivity(array $media): array
{
$id = Item::fetchByLink($media['url']);
if (empty($id)) {
return $media;
}
$item = Post::selectFirst([], ['id' => $id, 'network' => Protocol::FEDERATED]);
if (empty($item['id'])) {
Logger::debug('Not a federated activity', ['id' => $id, 'uri-id' => $media['uri-id'], 'url' => $media['url']]);
return $media;
}
if (!empty($item['plink']) && Strings::compareLink($item['plink'], $media['url']) &&
parse_url($item['plink'], PHP_URL_HOST) != parse_url($item['uri'], PHP_URL_HOST)) {
Logger::debug('Not a link to an activity', ['uri-id' => $media['uri-id'], 'url' => $media['url'], 'plink' => $item['plink'], 'uri' => $item['uri']]);
return $media;
}
if (in_array($item['network'], [Protocol::ACTIVITYPUB, Protocol::DFRN])) {
$media['mimetype'] = 'application/activity+json';
} elseif ($item['network'] == Protocol::DIASPORA) {
$media['mimetype'] = 'application/xml';
}
$contact = Contact::getById($item['author-id'], ['avatar', 'gsid']);
if (!empty($contact['gsid'])) {
$gserver = DBA::selectFirst('gserver', ['url', 'site_name'], ['id' => $contact['gsid']]);
}
$media['type'] = self::ACTIVITY;
$media['media-uri-id'] = $item['uri-id'];
$media['height'] = null;
$media['width'] = null;
$media['preview'] = null;
$media['preview-height'] = null;
$media['preview-width'] = null;
$media['blurhash'] = null;
$media['description'] = $item['body'];
$media['name'] = $item['title'];
$media['author-url'] = $item['author-link'];
$media['author-name'] = $item['author-name'];
$media['author-image'] = $contact['avatar'] ?? $item['author-avatar'];
$media['publisher-url'] = $gserver['url'] ?? null;
$media['publisher-name'] = $gserver['site_name'] ?? null;
$media['publisher-image'] = null;
Logger::debug('Activity detected', ['uri-id' => $media['uri-id'], 'url' => $media['url'], 'plink' => $item['plink'], 'uri' => $item['uri']]);
return $media;
}
/**
* Adds the account type if the media entry is linked to an account
*
* @param array $media
* @return array
*/
private static function addAccount(array $media): array
{
$contact = Contact::getByURL($media['url'], false);
if (empty($contact) || ($contact['network'] == Protocol::PHANTOM)) {
return $media;
}
if (in_array($contact['network'], [Protocol::ACTIVITYPUB, Protocol::DFRN])) {
$media['mimetype'] = 'application/activity+json';
}
if (!empty($contact['gsid'])) {
$gserver = DBA::selectFirst('gserver', ['url', 'site_name'], ['id' => $contact['gsid']]);
}
$media['type'] = self::ACCOUNT;
$media['media-uri-id'] = $contact['uri-id'];
$media['height'] = null;
$media['width'] = null;
$media['preview'] = null;
$media['preview-height'] = null;
$media['preview-width'] = null;
$media['blurhash'] = null;
$media['description'] = $contact['about'];
$media['name'] = $contact['name'];
$media['author-url'] = $contact['url'];
$media['author-name'] = $contact['name'];
$media['author-image'] = $contact['avatar'];
$media['publisher-url'] = $gserver['url'] ?? null;
$media['publisher-name'] = $gserver['site_name'] ?? null;
$media['publisher-image'] = null;
Logger::debug('Account detected', ['uri-id' => $media['uri-id'], 'url' => $media['url'], 'uri' => $contact['url']]);
return $media;
}
/**
* Add page infos for HTML entries
*
* @param array $media
* @return array
*/
private static function addPage(array $media): array
{
$data = ParseUrl::getSiteinfoCached($media['url'], false);
$media['preview'] = $data['images'][0]['src'] ?? null;
$media['preview-height'] = $data['images'][0]['height'] ?? null;
$media['preview-width'] = $data['images'][0]['width'] ?? null;
$media['blurhash'] = $data['images'][0]['blurhash'] ?? null;
$media['description'] = $data['text'] ?? null;
$media['name'] = $data['title'] ?? null;
$media['author-url'] = $data['author_url'] ?? null;
$media['author-name'] = $data['author_name'] ?? null;
$media['author-image'] = $data['author_img'] ?? null;
$media['publisher-url'] = $data['publisher_url'] ?? null;
$media['publisher-name'] = $data['publisher_name'] ?? null;
$media['publisher-image'] = $data['publisher_img'] ?? null;
return $media;
}
@ -248,6 +376,7 @@ class Media
$media['size'] = $photo['datasize'];
$media['width'] = $photo['width'];
$media['height'] = $photo['height'];
$media['blurhash'] = $photo['blurhash'];
}
if (!preg_match('|.*?/photo/(.*[a-fA-F0-9])\-(.*[0-9])\..*[\w]|', $media['preview'] ?? '', $matches)) {
@ -332,19 +461,14 @@ class Media
* @param string $body
* @return string Body without media links
*/
public static function insertFromBody(int $uriid, string $body): string
public static function insertFromBody(int $uriid, string $body, bool $endmatch = false): string
{
$endmatchpattern = $endmatch ? '\z' : '';
// Simplify image codes
$unshared_body = $body = preg_replace("/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/ism", '[img]$3[/img]', $body);
// Only remove the shared data from "real" reshares
$shared = BBCode::fetchShareAttributes($body);
if (!empty($shared['guid'])) {
$unshared_body = preg_replace("/\s*\[share .*?\].*?\[\/share\]\s*/ism", '', $body);
}
$unshared_body = $body = preg_replace("/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]$endmatchpattern/ism", '[img]$3[/img]', $body);
$attachments = [];
if (preg_match_all("#\[url=([^\]]+?)\]\s*\[img=([^\[\]]*)\]([^\[\]]*)\[\/img\]\s*\[/url\]#ism", $body, $pictures, PREG_SET_ORDER)) {
if (preg_match_all("#\[url=([^\]]+?)\]\s*\[img=([^\[\]]*)\]([^\[\]]*)\[\/img\]\s*\[/url\]$endmatchpattern#ism", $body, $pictures, PREG_SET_ORDER)) {
foreach ($pictures as $picture) {
if (!self::isPictureLink($picture[1], $picture[2])) {
continue;
@ -356,14 +480,14 @@ class Media
}
}
if (preg_match_all("/\[img=([^\[\]]*)\]([^\[\]]*)\[\/img\]/Usi", $body, $pictures, PREG_SET_ORDER)) {
if (preg_match_all("/\[img=([^\[\]]*)\]([^\[\]]*)\[\/img\]$endmatchpattern/Usi", $body, $pictures, PREG_SET_ORDER)) {
foreach ($pictures as $picture) {
$body = str_replace($picture[0], '', $body);
$attachments[$picture[1]] = ['uri-id' => $uriid, 'type' => self::IMAGE, 'url' => $picture[1], 'description' => $picture[2]];
}
}
if (preg_match_all("#\[url=([^\]]+?)\]\s*\[img\]([^\[]+?)\[/img\]\s*\[/url\]#ism", $body, $pictures, PREG_SET_ORDER)) {
if (preg_match_all("#\[url=([^\]]+?)\]\s*\[img\]([^\[]+?)\[/img\]\s*\[/url\]$endmatchpattern#ism", $body, $pictures, PREG_SET_ORDER)) {
foreach ($pictures as $picture) {
if (!self::isPictureLink($picture[1], $picture[2])) {
continue;
@ -375,41 +499,58 @@ class Media
}
}
if (preg_match_all("/\[img\]([^\[\]]*)\[\/img\]/ism", $body, $pictures, PREG_SET_ORDER)) {
if (preg_match_all("/\[img\]([^\[\]]*)\[\/img\]$endmatchpattern/ism", $body, $pictures, PREG_SET_ORDER)) {
foreach ($pictures as $picture) {
$body = str_replace($picture[0], '', $body);
$attachments[$picture[1]] = ['uri-id' => $uriid, 'type' => self::IMAGE, 'url' => $picture[1]];
}
}
if (preg_match_all("/\[audio\]([^\[\]]*)\[\/audio\]/ism", $body, $audios, PREG_SET_ORDER)) {
if (preg_match_all("/\[audio\]([^\[\]]*)\[\/audio\]$endmatchpattern/ism", $body, $audios, PREG_SET_ORDER)) {
foreach ($audios as $audio) {
$body = str_replace($audio[0], '', $body);
$attachments[$audio[1]] = ['uri-id' => $uriid, 'type' => self::AUDIO, 'url' => $audio[1]];
}
}
if (preg_match_all("/\[video\]([^\[\]]*)\[\/video\]/ism", $body, $videos, PREG_SET_ORDER)) {
if (preg_match_all("/\[video\]([^\[\]]*)\[\/video\]$endmatchpattern/ism", $body, $videos, PREG_SET_ORDER)) {
foreach ($videos as $video) {
$body = str_replace($video[0], '', $body);
$attachments[$video[1]] = ['uri-id' => $uriid, 'type' => self::VIDEO, 'url' => $video[1]];
}
}
foreach ($attachments as $attachment) {
if (Post\Link::exists($uriid, $attachment['preview'] ?? $attachment['url'])) {
continue;
}
if ($uriid != 0) {
foreach ($attachments as $attachment) {
if (Post\Link::exists($uriid, $attachment['preview'] ?? $attachment['url'])) {
continue;
}
// Only store attachments that are part of the unshared body
if (Item::containsLink($unshared_body, $attachment['preview'] ?? $attachment['url'], $attachment['type'])) {
self::insert($attachment);
// Only store attachments that are part of the unshared body
if (Item::containsLink($unshared_body, $attachment['preview'] ?? $attachment['url'], $attachment['type'])) {
self::insert($attachment);
}
}
}
return trim($body);
}
/**
* Remove media that is at the end of the body
*
* @param string $body
* @return string
*/
public static function removeFromEndOfBody(string $body): string
{
do {
$prebody = $body;
$body = self::insertFromBody(0, $body, true);
} while ($prebody != $body);
return $body;
}
/**
* Add media links from a relevant url in the body
*
@ -417,15 +558,8 @@ class Media
* @param string $body
* @return void
*/
public static function insertFromRelevantUrl(int $uriid, string $body)
public static function insertFromRelevantUrl(int $uriid, string $body, string $fullbody, string $network)
{
// Only remove the shared data from "real" reshares
$shared = BBCode::fetchShareAttributes($body);
if (!empty($shared['guid'])) {
// Don't look at the shared content
$body = preg_replace("/\s*\[share .*?\].*?\[\/share\]\s*/ism", '', $body);
}
// Remove all hashtags and mentions
$body = preg_replace("/([#@!])\[url\=(.*?)\](.*?)\[\/url\]/ism", '', $body);
@ -433,7 +567,10 @@ class Media
if (preg_match_all("/\[url\](https?:.*?)\[\/url\]/ism", $body, $matches)) {
foreach ($matches[1] as $url) {
Logger::info('Got page url (link without description)', ['uri-id' => $uriid, 'url' => $url]);
self::insert(['uri-id' => $uriid, 'type' => self::UNKNOWN, 'url' => $url]);
self::insert(['uri-id' => $uriid, 'type' => self::UNKNOWN, 'url' => $url], false, $network);
if ($network == Protocol::DFRN) {
self::revertHTMLType($uriid, $url, $fullbody);
}
}
}
@ -441,11 +578,31 @@ class Media
if (preg_match_all("/\[url\=(https?:.*?)\].*?\[\/url\]/ism", $body, $matches)) {
foreach ($matches[1] as $url) {
Logger::info('Got page url (link with description)', ['uri-id' => $uriid, 'url' => $url]);
self::insert(['uri-id' => $uriid, 'type' => self::UNKNOWN, 'url' => $url]);
self::insert(['uri-id' => $uriid, 'type' => self::UNKNOWN, 'url' => $url], false, $network);
if ($network == Protocol::DFRN) {
self::revertHTMLType($uriid, $url, $fullbody);
}
}
}
}
/**
* Revert the media type of links to UNKNOWN for DFRN posts when they aren't attached
*
* @param integer $uriid
* @param string $url
* @param string $body
* @return void
*/
private static function revertHTMLType(int $uriid, string $url, string $body)
{
$attachment = BBCode::getAttachmentData($body);
if (!empty($attachment['url']) && Network::getUrlMatch($attachment['url'], $url)) {
return;
}
DBA::update('post-media', ['type' => self::UNKNOWN], ['uri-id' => $uriid, 'type' => self::HTML, 'url' => $url]);
}
/**
* Add media links from the attachment field
*
@ -455,9 +612,6 @@ class Media
*/
public static function insertFromAttachmentData(int $uriid, string $body)
{
// Don't look at the shared content
$body = preg_replace("/\s*\[share .*?\].*?\[\/share\]\s*/ism", '', $body);
$data = BBCode::getAttachmentData($body);
if (empty($data)) {
return;
@ -517,7 +671,7 @@ class Media
*/
public static function getByURIId(int $uri_id, array $types = [])
{
$condition = ['uri-id' => $uri_id];
$condition = ["`uri-id` = ? AND `type` != ?", $uri_id, self::UNKNOWN];
if (!empty($types)) {
$condition = DBA::mergeConditions($condition, ['type' => $types]);
@ -536,7 +690,7 @@ class Media
*/
public static function existsByURIId(int $uri_id, array $types = []): bool
{
$condition = ['uri-id' => $uri_id];
$condition = ["`uri-id` = ? AND `type` != ?", $uri_id, self::UNKNOWN];
if (!empty($types)) {
$condition = DBA::mergeConditions($condition, ['type' => $types]);
@ -549,12 +703,11 @@ class Media
* Split the attachment media in the three segments "visual", "link" and "additional"
*
* @param int $uri_id URI id
* @param string $guid GUID
* @param array $links list of links that shouldn't be added
* @param bool $has_media
* @return array attachments
*/
public static function splitAttachments(int $uri_id, string $guid = '', array $links = [], bool $has_media = true): array
public static function splitAttachments(int $uri_id, array $links = [], bool $has_media = true): array
{
$attachments = ['visual' => [], 'link' => [], 'additional' => []];
@ -585,11 +738,17 @@ class Media
}
}
// Currently these two types are ignored here.
// Posts are added differently and contacts are not displayed as attachments.
if (in_array($medium['type'], [self::ACCOUNT, self::ACTIVITY])) {
continue;
}
if (!empty($medium['preview'])) {
$previews[] = $medium['preview'];
}
$type = explode('/', current(explode(';', $medium['mimetype'])));
$type = explode('/', explode(';', $medium['mimetype'] ?? '')[0]);
if (count($type) < 2) {
Logger::info('Unknown MimeType', ['type' => $type, 'media' => $medium]);
$filetype = 'unkn';
@ -648,11 +807,13 @@ class Media
/**
* Add media attachments to the body
*
* @param int $uriid
* @param int $uriid
* @param string $body
* @param array $types
*
* @return string body
*/
public static function addAttachmentsToBody(int $uriid, string $body = ''): string
public static function addAttachmentsToBody(int $uriid, string $body = '', array $types = [self::IMAGE, self::AUDIO, self::VIDEO]): string
{
if (empty($body)) {
$item = Post::selectFirst(['body'], ['uri-id' => $uriid]);
@ -665,7 +826,7 @@ class Media
$body = preg_replace("/\s*\[attachment .*?\].*?\[\/attachment\]\s*/ism", '', $body);
foreach (self::getByURIId($uriid, [self::IMAGE, self::AUDIO, self::VIDEO]) as $media) {
foreach (self::getByURIId($uriid, $types) as $media) {
if (Item::containsLink($body, $media['preview'] ?? $media['url'], $media['type'])) {
continue;
}

View file

@ -30,6 +30,7 @@ use Friendica\Database\DBA;
use Friendica\Database\DBStructure;
use Friendica\DI;
use Friendica\Model\Contact;
use Friendica\Model\Item;
use Friendica\Model\Post;
use Friendica\Model\Subscription;
use Friendica\Model\Tag;
@ -177,11 +178,15 @@ class UserNotification
return;
}
$user = User::getById($uid, ['account-type']);
$user = User::getById($uid, ['account-type', 'account_removed', 'account_expired']);
if (in_array($user['account-type'], [User::ACCOUNT_TYPE_COMMUNITY, User::ACCOUNT_TYPE_RELAY])) {
return;
}
if ($user['account_removed'] || $user['account_expired']) {
return;
}
$author = Contact::getById($item['author-id'], ['contact-type']);
if (empty($author)) {
return;
@ -288,7 +293,7 @@ class UserNotification
}
// Only create notifications for posts and comments, not for activities
if (($item['gravity'] == GRAVITY_ACTIVITY) && ($item['verb'] != Activity::ANNOUNCE)) {
if (($item['gravity'] == Item::GRAVITY_ACTIVITY) && ($item['verb'] != Activity::ANNOUNCE)) {
return;
}
@ -310,7 +315,7 @@ class UserNotification
*/
private static function insertNotificationByItem(int $type, int $uid, array $item): void
{
if (($item['verb'] != Activity::ANNOUNCE) && ($item['gravity'] == GRAVITY_ACTIVITY) &&
if (($item['verb'] != Activity::ANNOUNCE) && ($item['gravity'] == Item::GRAVITY_ACTIVITY) &&
!in_array($type, [self::TYPE_DIRECT_COMMENT, self::TYPE_DIRECT_THREAD_COMMENT])) {
// Activities are only stored when performed on the user's post or comment
return;
@ -321,7 +326,7 @@ class UserNotification
$item['vid'],
$type,
$item['author-id'],
$item['gravity'] == GRAVITY_ACTIVITY ? $item['thr-parent-id'] : $item['uri-id'],
$item['gravity'] == Item::GRAVITY_ACTIVITY ? $item['thr-parent-id'] : $item['uri-id'],
$item['parent-uri-id']
);
@ -423,14 +428,14 @@ class UserNotification
private static function checkShared(array $item, int $uid): bool
{
// Only check on original posts and reshare ("announce") activities, otherwise return
if (($item['gravity'] != GRAVITY_PARENT) && ($item['verb'] != Activity::ANNOUNCE)) {
if (($item['gravity'] != Item::GRAVITY_PARENT) && ($item['verb'] != Activity::ANNOUNCE)) {
return false;
}
// Don't notify about reshares by communities of our own posts or each time someone comments
if (($item['verb'] == Activity::ANNOUNCE) && DBA::exists('contact', ['id' => $item['contact-id'], 'contact-type' => Contact::TYPE_COMMUNITY])) {
$post = Post::selectFirst(['origin', 'gravity'], ['uri-id' => $item['thr-parent-id'], 'uid' => $uid]);
if ($post['origin'] || ($post['gravity'] != GRAVITY_PARENT)) {
if (!$post || $post['origin'] || ($post['gravity'] != Item::GRAVITY_PARENT)) {
return false;
}
}
@ -497,7 +502,7 @@ class UserNotification
*/
private static function checkCommentedThread(array $item, array $contacts): bool
{
$condition = ['parent' => $item['parent'], 'author-id' => $contacts, 'deleted' => false, 'gravity' => GRAVITY_PARENT];
$condition = ['parent' => $item['parent'], 'author-id' => $contacts, 'deleted' => false, 'gravity' => Item::GRAVITY_PARENT];
return Post::exists($condition);
}
@ -511,7 +516,7 @@ class UserNotification
*/
private static function checkDirectComment(array $item, array $contacts): bool
{
$condition = ['uri' => $item['thr-parent'], 'uid' => $item['uid'], 'author-id' => $contacts, 'deleted' => false, 'gravity' => GRAVITY_COMMENT];
$condition = ['uri' => $item['thr-parent'], 'uid' => $item['uid'], 'author-id' => $contacts, 'deleted' => false, 'gravity' => Item::GRAVITY_COMMENT];
return Post::exists($condition);
}
@ -525,7 +530,7 @@ class UserNotification
*/
private static function checkDirectCommentedThread(array $item, array $contacts): bool
{
$condition = ['uri' => $item['thr-parent'], 'uid' => $item['uid'], 'author-id' => $contacts, 'deleted' => false, 'gravity' => GRAVITY_PARENT];
$condition = ['uri' => $item['thr-parent'], 'uid' => $item['uid'], 'author-id' => $contacts, 'deleted' => false, 'gravity' => Item::GRAVITY_PARENT];
return Post::exists($condition);
}
@ -539,7 +544,7 @@ class UserNotification
*/
private static function checkCommentedParticipation(array $item, array $contacts): bool
{
$condition = ['parent' => $item['parent'], 'author-id' => $contacts, 'deleted' => false, 'gravity' => GRAVITY_COMMENT];
$condition = ['parent' => $item['parent'], 'author-id' => $contacts, 'deleted' => false, 'gravity' => Item::GRAVITY_COMMENT];
return Post::exists($condition);
}
@ -553,7 +558,7 @@ class UserNotification
*/
private static function checkFollowParticipation(array $item, array $contacts): bool
{
$condition = ['parent' => $item['parent'], 'author-id' => $contacts, 'deleted' => false, 'gravity' => GRAVITY_ACTIVITY, 'verb' => Activity::FOLLOW];
$condition = ['parent' => $item['parent'], 'author-id' => $contacts, 'deleted' => false, 'gravity' => Item::GRAVITY_ACTIVITY, 'verb' => Activity::FOLLOW];
return Post::exists($condition);
}
@ -567,7 +572,7 @@ class UserNotification
*/
private static function checkActivityParticipation(array $item, array $contacts): bool
{
$condition = ['parent' => $item['parent'], 'author-id' => $contacts, 'deleted' => false, 'gravity' => GRAVITY_ACTIVITY];
$condition = ['parent' => $item['parent'], 'author-id' => $contacts, 'deleted' => false, 'gravity' => Item::GRAVITY_ACTIVITY];
return Post::exists($condition);
}
}

View file

@ -30,7 +30,6 @@ use Friendica\Core\Logger;
use Friendica\Core\Protocol;
use Friendica\Core\Renderer;
use Friendica\Core\Search;
use Friendica\Core\Session;
use Friendica\Core\System;
use Friendica\Core\Worker;
use Friendica\Database\DBA;
@ -153,11 +152,11 @@ class Profile
if ($owner['net-publish'] || $force) {
// Update global directory in background
if (Search::getGlobalDirectory()) {
Worker::add(PRIORITY_LOW, 'Directory', $owner['url']);
Worker::add(Worker::PRIORITY_LOW, 'Directory', $owner['url']);
}
}
Worker::add(PRIORITY_LOW, 'ProfileUpdate', $uid);
Worker::add(Worker::PRIORITY_LOW, 'ProfileUpdate', $uid);
}
/**
@ -239,7 +238,7 @@ class Profile
DI::page()['title'] = $profile['name'] . ' @ ' . DI::config()->get('config', 'sitename');
if (!local_user()) {
if (!DI::userSession()->getLocalUserId()) {
$a->setCurrentTheme($profile['theme']);
$a->setCurrentMobileTheme(DI::pConfig()->get($a->getProfileOwner(), 'system', 'mobile_theme') ?? '');
}
@ -255,7 +254,7 @@ class Profile
require_once $theme_info_file;
}
$block = (DI::config()->get('system', 'block_public') && !Session::isAuthenticated());
$block = (DI::config()->get('system', 'block_public') && !DI::userSession()->isAuthenticated());
/**
* @todo
@ -295,8 +294,8 @@ class Profile
$profile_contact = [];
if (local_user() && ($profile['uid'] ?? 0) != local_user()) {
$profile_contact = Contact::getByURL($profile['nurl'], null, [], local_user());
if (DI::userSession()->getLocalUserId() && ($profile['uid'] ?? 0) != DI::userSession()->getLocalUserId()) {
$profile_contact = Contact::getByURL($profile['nurl'], null, [], DI::userSession()->getLocalUserId());
}
if (!empty($profile['cid']) && self::getMyURL()) {
$profile_contact = Contact::selectFirst([], ['id' => $profile['cid']]);
@ -336,13 +335,13 @@ class Profile
if (!$visitor_is_authenticated) {
// Remote follow is only available for local profiles
if (!empty($profile['nickname']) && strpos($profile_url, DI::baseUrl()->get()) === 0) {
$follow_link = 'remote_follow/' . $profile['nickname'];
$follow_link = 'profile/' . $profile['nickname'] . '/remote_follow';
}
} else {
if ($visitor_is_following) {
$unfollow_link = $visitor_base_path . '/unfollow?url=' . urlencode($profile_url) . '&auto=1';
$unfollow_link = $visitor_base_path . '/contact/unfollow?url=' . urlencode($profile_url) . '&auto=1';
} else {
$follow_link = $visitor_base_path .'/follow?url=' . urlencode($profile_url) . '&auto=1';
$follow_link = $visitor_base_path . '/contact/follow?url=' . urlencode($profile_url) . '&auto=1';
}
}
@ -350,7 +349,7 @@ class Profile
if ($visitor_is_followed || $visitor_is_following) {
$wallmessage_link = $visitor_base_path . '/message/new/' . $profile_contact['id'];
} elseif ($visitor_is_authenticated && !empty($profile['unkmail'])) {
$wallmessage_link = 'wallmessage/' . $profile['nickname'];
$wallmessage_link = 'profile/' . $profile['nickname'] . '/unkmail';
}
}
}
@ -379,7 +378,7 @@ class Profile
$xmpp = !empty($profile['xmpp']) ? DI::l10n()->t('XMPP:') : false;
$matrix = !empty($profile['matrix']) ? DI::l10n()->t('Matrix:') : false;
if ((!empty($profile['hidewall']) || $block) && !Session::isAuthenticated()) {
if ((!empty($profile['hidewall']) || $block) && !DI::userSession()->isAuthenticated()) {
$location = $homepage = $about = false;
}
@ -413,7 +412,7 @@ class Profile
}
if (!$block && $show_contacts) {
$contact_block = ContactBlock::getHTML($profile, local_user());
$contact_block = ContactBlock::getHTML($profile, DI::userSession()->getLocalUserId());
if (is_array($profile) && !$profile['hide-friends']) {
$contact_count = DBA::count('contact', [
@ -452,6 +451,10 @@ class Profile
$p['url'] = Contact::magicLinkById($cid, $profile['url']);
if (!isset($profile['hidewall'])) {
Logger::warning('Missing hidewall key in profile array', ['profile' => $profile, 'callstack' => System::callstack(10)]);
}
$tpl = Renderer::getMarkupTemplate('profile/vcard.tpl');
$o .= Renderer::replaceMacros($tpl, [
'$profile' => $p,
@ -462,12 +465,13 @@ class Profile
'$unfollow' => DI::l10n()->t('Unfollow'),
'$unfollow_link' => $unfollow_link,
'$subscribe_feed' => DI::l10n()->t('Atom feed'),
'$subscribe_feed_link' => $profile['poll'],
'$subscribe_feed_link' => $profile['hidewall'] ?? 0 ? '' : $profile['poll'],
'$wallmessage' => DI::l10n()->t('Message'),
'$wallmessage_link' => $wallmessage_link,
'$account_type' => $account_type,
'$location' => $location,
'$homepage' => $homepage,
'$homepage_verified' => DI::l10n()->t('This website has been verified to belong to the same person.'),
'$about' => $about,
'$network' => DI::l10n()->t('Network:'),
'$contacts' => $contact_count,
@ -493,7 +497,7 @@ class Profile
*/
public static function getBirthdays(): string
{
if (!local_user() || DI::mode()->isMobile() || DI::mode()->isMobile()) {
if (!DI::userSession()->getLocalUserId() || DI::mode()->isMobile() || DI::mode()->isMobile()) {
return '';
}
@ -506,7 +510,7 @@ class Profile
$bd_short = DI::l10n()->t('F d');
$cacheKey = 'get_birthdays:' . local_user();
$cacheKey = 'get_birthdays:' . DI::userSession()->getLocalUserId();
$events = DI::cache()->get($cacheKey);
if (is_null($events)) {
$result = DBA::p(
@ -523,7 +527,7 @@ class Profile
ORDER BY `start`",
Contact::SHARING,
Contact::FRIEND,
local_user(),
DI::userSession()->getLocalUserId(),
DateTimeFormat::utc('now + 6 days'),
DateTimeFormat::utcNow()
);
@ -595,7 +599,7 @@ class Profile
$a = DI::app();
$o = '';
if (!local_user() || DI::mode()->isMobile() || DI::mode()->isMobile()) {
if (!DI::userSession()->getLocalUserId() || DI::mode()->isMobile() || DI::mode()->isMobile()) {
return $o;
}
@ -610,7 +614,7 @@ class Profile
$classtoday = '';
$condition = ["`uid` = ? AND `type` != 'birthday' AND `start` < ? AND `start` >= ?",
local_user(), DateTimeFormat::utc('now + 7 days'), DateTimeFormat::utc('now - 1 days')];
DI::userSession()->getLocalUserId(), DateTimeFormat::utc('now + 7 days'), DateTimeFormat::utc('now - 1 days')];
$s = DBA::select('event', [], $condition, ['order' => ['start']]);
$r = [];
@ -620,7 +624,7 @@ class Profile
$total = 0;
while ($rr = DBA::fetch($s)) {
$condition = ['parent-uri' => $rr['uri'], 'uid' => $rr['uid'], 'author-id' => public_contact(),
$condition = ['parent-uri' => $rr['uri'], 'uid' => $rr['uid'], 'author-id' => DI::userSession()->getPublicContactId(),
'vid' => [Verb::getID(Activity::ATTEND), Verb::getID(Activity::ATTENDMAYBE)],
'visible' => true, 'deleted' => false];
if (!Post::exists($condition)) {
@ -680,10 +684,11 @@ class Profile
* Retrieves the my_url session variable
*
* @return string
* @deprecated since version 2022.12, please use UserSession->getMyUrl instead
*/
public static function getMyURL(): string
{
return Session::get('my_url') ?? '';
return DI::userSession()->getMyUrl();
}
/**
@ -712,7 +717,7 @@ class Profile
$my_url = self::getMyURL();
$my_url = Network::isUrlValid($my_url);
if (empty($my_url) || local_user()) {
if (empty($my_url) || DI::userSession()->getLocalUserId()) {
return;
}
@ -730,7 +735,7 @@ class Profile
$contact = DBA::selectFirst('contact',['id', 'url'], ['id' => $cid]);
if (DBA::isResult($contact) && remote_user() && remote_user() == $contact['id']) {
if (DBA::isResult($contact) && DI::userSession()->getRemoteUserId() && DI::userSession()->getRemoteUserId() == $contact['id']) {
Logger::info('The visitor ' . $my_url . ' is already authenticated');
return;
}
@ -797,7 +802,7 @@ class Profile
$_SESSION['my_url'] = $visitor['url'];
$_SESSION['remote_comment'] = $visitor['subscribe'];
Session::setVisitorsContacts();
DI::userSession()->setVisitorsContacts();
$a->setContactId($visitor['id']);
@ -865,7 +870,7 @@ class Profile
$a->setContactId($arr['visitor']['id']);
info(DI::l10n()->t('OpenWebAuth: %1$s welcomes %2$s', DI::baseUrl()->getHostname(), $visitor['name']));
DI::sysmsg()->addInfo(DI::l10n()->t('OpenWebAuth: %1$s welcomes %2$s', DI::baseUrl()->getHostname(), $visitor['name']));
Logger::info('OpenWebAuth: auth success from ' . $visitor['addr']);
}
@ -916,7 +921,7 @@ class Profile
*/
public static function getThemeUid(App $a): int
{
return local_user() ?: $a->getProfileOwner();
return DI::userSession()->getLocalUserId() ?: $a->getProfileOwner();
}
/**

View file

@ -37,7 +37,7 @@ class PushSubscriber
* @return void
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function publishFeed(int $uid, int $default_priority = PRIORITY_HIGH)
public static function publishFeed(int $uid, int $default_priority = Worker::PRIORITY_HIGH)
{
$condition = ['push' => 0, 'uid' => $uid];
DBA::update('push_subscriber', ['push' => 1, 'next_try' => DBA::NULL_DATETIME], $condition);
@ -52,7 +52,7 @@ class PushSubscriber
* @return void
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function requeue(int $default_priority = PRIORITY_HIGH)
public static function requeue(int $default_priority = Worker::PRIORITY_HIGH)
{
// We'll push to each subscriber that has push > 0,
// i.e. there has been an update (set in notifier.php).
@ -61,7 +61,7 @@ class PushSubscriber
while ($subscriber = DBA::fetch($subscribers)) {
// We always handle retries with low priority
if ($subscriber['push'] > 1) {
$priority = PRIORITY_LOW;
$priority = Worker::PRIORITY_LOW;
} else {
$priority = $default_priority;
}

View file

@ -23,6 +23,7 @@ namespace Friendica\Model;
use Friendica\Content\Pager;
use Friendica\Database\DBA;
use Friendica\Network\HTTPException;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\Strings;
@ -113,21 +114,27 @@ class Register
}
/**
* Creates a register record for approval and returns the success of the database insert
* Creates a register record for approval
* Checks for the existence of the provided user id
*
* @param integer $uid The ID of the user needing approval
* @param string $language The registration language
* @param string $note An additional message from the user
* @return boolean
* @throws \Exception
* @param integer $uid The ID of the user needing approval
* @param string $language The registration language
* @param string $note An additional message from the user
* @return void
* @throws \OutOfBoundsException
* @throws HTTPException\InternalServerErrorException
* @throws HTTPException\NotFoundException
*/
public static function createForApproval(int $uid, string $language, string $note = ''): bool
public static function createForApproval(int $uid, string $language, string $note = ''): void
{
$hash = Strings::getRandomHex();
if (!$uid) {
throw new \OutOfBoundsException("User ID can't be empty");
}
if (!User::exists($uid)) {
return false;
throw new HTTPException\NotFoundException("User ID doesn't exist");
}
$fields = [
@ -139,7 +146,9 @@ class Register
'note' => $note
];
return DBA::insert('register', $fields);
if (!DBA::insert('register', $fields)) {
throw new HTTPException\InternalServerErrorException('Unable to insert a `register` record');
}
}
/**

View file

@ -141,7 +141,7 @@ class Subscription
{
$type = NotificationFactory::getType($notification);
if (DI::notify()->NotifyOnDesktop($notification, $type)) {
if (DI::notify()->shouldShowOnDesktop($notification, $type)) {
DI::notify()->createFromNotification($notification);
}
@ -152,7 +152,7 @@ class Subscription
$subscriptions = DBA::select('subscription', [], ['uid' => $notification->uid, $type => true]);
while ($subscription = DBA::fetch($subscriptions)) {
Logger::info('Push notification', ['id' => $subscription['id'], 'uid' => $subscription['uid'], 'type' => $type]);
Worker::add(PRIORITY_HIGH, 'PushSubscription', $subscription['id'], $notification->id);
Worker::add(Worker::PRIORITY_HIGH, 'PushSubscription', $subscription['id'], $notification->id);
}
DBA::close($subscriptions);
}

View file

@ -257,17 +257,16 @@ class Tag
* @param string $hash
* @param string $name
* @param string $url
* @param boolean $probing Whether probing is active
* @return void
*/
public static function storeByHash(int $uriId, string $hash, string $name, string $url = '', bool $probing = true)
public static function storeByHash(int $uriId, string $hash, string $name, string $url = '')
{
$type = self::getTypeForHash($hash);
if ($type == self::UNKNOWN) {
return;
}
self::store($uriId, $type, $name, $url, $probing);
self::store($uriId, $type, $name, $url);
}
/**
@ -297,34 +296,39 @@ class Tag
* @param integer $uriId URI-Id
* @param string $body Body of the post
* @param string $tags Accepted tags
* @param boolean $probing Perform a probing for contacts, adding them if needed
* @return void
*/
public static function storeFromBody(int $uriId, string $body, string $tags = null, bool $probing = true)
public static function storeFromBody(int $uriId, string $body, string $tags = null)
{
Logger::info('Check for tags', ['uri-id' => $uriId, 'hash' => $tags, 'callstack' => System::callstack()]);
$item = ['uri-id' => $uriId, 'body' => $body, 'quote-uri-id' => null];
self::storeFromArray($item, $tags);
}
/**
* Store tags and mentions from the item array
*
* @param array $item Item array
* @param string $tags Accepted tags
* @return void
*/
public static function storeFromArray(array $item, string $tags = null)
{
Logger::info('Check for tags', ['uri-id' => $item['uri-id'], 'hash' => $tags, 'callstack' => System::callstack()]);
if (is_null($tags)) {
$tags = self::TAG_CHARACTER[self::HASHTAG] . self::TAG_CHARACTER[self::MENTION] . self::TAG_CHARACTER[self::EXCLUSIVE_MENTION];
}
// Only remove the shared data from "real" reshares
$shared = BBCode::fetchShareAttributes($body);
if (!empty($shared['guid'])) {
if (preg_match("/\s*\[share .*?\](.*?)\[\/share\]\s*/ism", $body, $matches)) {
$share_body = $matches[1];
}
$body = preg_replace("/\s*\[share .*?\].*?\[\/share\]\s*/ism", '', $body);
foreach (self::getFromBody($item['body'], $tags) as $tag) {
self::storeByHash($item['uri-id'], $tag[1], $tag[3], $tag[2]);
}
foreach (self::getFromBody($body, $tags) as $tag) {
self::storeByHash($uriId, $tag[1], $tag[3], $tag[2], $probing);
}
$shared = DI::contentItem()->getSharedPost($item, ['uri-id']);
// Search for hashtags in the shared body (but only if hashtags are wanted)
if (!empty($share_body) && (strpos($tags, self::TAG_CHARACTER[self::HASHTAG]) !== false)) {
foreach (self::getFromBody($share_body, self::TAG_CHARACTER[self::HASHTAG]) as $tag) {
self::storeByHash($uriId, $tag[1], $tag[3], $tag[2], $probing);
if (!empty($shared) && (strpos($tags, self::TAG_CHARACTER[self::HASHTAG]) !== false)) {
foreach (self::getByURIId($shared['post']['uri-id'], [self::HASHTAG]) as $tag) {
self::store($item['uri-id'], $tag['type'], $tag['name'], $tag['url']);
}
}
}

View file

@ -158,6 +158,7 @@ class User
$system['publish'] = false;
$system['net-publish'] = false;
$system['hide-friends'] = true;
$system['hidewall'] = true;
$system['prv_keywords'] = '';
$system['pub_keywords'] = '';
$system['address'] = '';
@ -265,7 +266,7 @@ class User
// List of possible actor names
$possible_accounts = ['friendica', 'actor', 'system', 'internal'];
foreach ($possible_accounts as $name) {
if (!DBA::exists('user', ['nickname' => $name, 'account_removed' => false, 'expire' => false]) &&
if (!DBA::exists('user', ['nickname' => $name, 'account_removed' => false, 'account_expired' => false]) &&
!DBA::exists('userd', ['username' => $name])) {
DI::config()->set('system', 'actor_name', $name);
return $name;
@ -381,17 +382,15 @@ class User
*
* @param array $fields
* @return array user
* @throws Exception
*/
public static function getFirstAdmin(array $fields = []) : array
{
if (!empty(DI::config()->get('config', 'admin_nickname'))) {
return self::getByNickname(DI::config()->get('config', 'admin_nickname'), $fields);
} elseif (!empty(DI::config()->get('config', 'admin_email'))) {
$adminList = explode(',', str_replace(' ', '', DI::config()->get('config', 'admin_email')));
return self::getByEmail($adminList[0], $fields);
} else {
return [];
}
return self::getAdminList()[0] ?? [];
}
/**
@ -667,6 +666,28 @@ class User
return $user;
}
/**
* Update the day of the last activity of the given user
*
* @param integer $uid
* @return void
*/
public static function updateLastActivity(int $uid)
{
$user = User::getById($uid, ['last-activity']);
if (empty($user)) {
return;
}
$current_day = DateTimeFormat::utcNow('Y-m-d');
if ($user['last-activity'] != $current_day) {
User::update(['last-activity' => $current_day], $uid);
// Set the last actitivy for all identities of the user
DBA::update('user', ['last-activity' => $current_day], ['parent-uid' => $uid, 'account_removed' => false]);
}
}
/**
* Generates a human-readable random password
*
@ -994,7 +1015,7 @@ class User
try {
$authurl = $openid->authUrl();
} catch (Exception $e) {
throw new Exception(DI::l10n()->t('We encountered a problem while logging in with the OpenID you provided. Please check the correct spelling of the ID.') . EOL . EOL . DI::l10n()->t('The error message was:') . $e->getMessage(), 0, $e);
throw new Exception(DI::l10n()->t('We encountered a problem while logging in with the OpenID you provided. Please check the correct spelling of the ID.') . '<br />' . DI::l10n()->t('The error message was:') . $e->getMessage(), 0, $e);
}
System::externalRedirect($authurl);
// NOTREACHED
@ -1054,11 +1075,8 @@ class User
// Disallow somebody creating an account using openid that uses the admin email address,
// since openid bypasses email verification. We'll allow it if there is not yet an admin account.
if (DI::config()->get('config', 'admin_email') && strlen($openid_url)) {
$adminlist = explode(',', str_replace(' ', '', strtolower(DI::config()->get('config', 'admin_email'))));
if (in_array(strtolower($email), $adminlist)) {
throw new Exception(DI::l10n()->t('Cannot use that email.'));
}
if (strlen($openid_url) && in_array(strtolower($email), self::getAdminEmailList())) {
throw new Exception(DI::l10n()->t('Cannot use that email.'));
}
$nickname = $data['nickname'] = strtolower($nickname);
@ -1317,7 +1335,7 @@ class User
if (DBA::isResult($profile) && $profile['net-publish'] && Search::getGlobalDirectory()) {
$url = DI::baseUrl() . '/profile/' . $user['nickname'];
Worker::add(PRIORITY_LOW, "Directory", $url);
Worker::add(Worker::PRIORITY_LOW, "Directory", $url);
}
$l10n = DI::l10n()->withLang($register['language']);
@ -1418,7 +1436,7 @@ class User
If you are new and do not know anybody here, they may help
you to make some new and interesting friends.
If you ever want to delete your account, you can do so at %1$s/removeme
If you ever want to delete your account, you can do so at %1$s/settings/removeme
Thank you and welcome to %4$s.'));
@ -1522,7 +1540,7 @@ class User
If you are new and do not know anybody here, they may help
you to make some new and interesting friends.
If you ever want to delete your account, you can do so at %3$s/removeme
If you ever want to delete your account, you can do so at %3$s/settings/removeme
Thank you and welcome to %2$s.',
$user['nickname'],
@ -1567,14 +1585,14 @@ class User
// The user and related data will be deleted in Friendica\Worker\ExpireAndRemoveUsers
DBA::update('user', ['account_removed' => true, 'account_expires_on' => DateTimeFormat::utc('now + 7 day')], ['uid' => $uid]);
Worker::add(PRIORITY_HIGH, 'Notifier', Delivery::REMOVAL, $uid);
Worker::add(Worker::PRIORITY_HIGH, 'Notifier', Delivery::REMOVAL, $uid);
// Send an update to the directory
$self = DBA::selectFirst('contact', ['url'], ['uid' => $uid, 'self' => true]);
Worker::add(PRIORITY_LOW, 'Directory', $self['url']);
Worker::add(Worker::PRIORITY_LOW, 'Directory', $self['url']);
// Remove the user relevant data
Worker::add(PRIORITY_NEGLIGIBLE, 'RemoveUser', $uid);
Worker::add(Worker::PRIORITY_NEGLIGIBLE, 'RemoveUser', $uid);
return true;
}
@ -1714,8 +1732,8 @@ class User
'active_users_weekly' => 0,
];
$userStmt = DBA::select('owner-view', ['uid', 'login_date', 'last-item'],
["`verified` AND `login_date` > ? AND NOT `blocked`
$userStmt = DBA::select('owner-view', ['uid', 'last-activity', 'last-item'],
["`verified` AND `last-activity` > ? AND NOT `blocked`
AND NOT `account_removed` AND NOT `account_expired`",
DBA::NULL_DATETIME]);
if (!DBA::isResult($userStmt)) {
@ -1729,17 +1747,17 @@ class User
while ($user = DBA::fetch($userStmt)) {
$statistics['total_users']++;
if ((strtotime($user['login_date']) > $halfyear) || (strtotime($user['last-item']) > $halfyear)
if ((strtotime($user['last-activity']) > $halfyear) || (strtotime($user['last-item']) > $halfyear)
) {
$statistics['active_users_halfyear']++;
}
if ((strtotime($user['login_date']) > $month) || (strtotime($user['last-item']) > $month)
if ((strtotime($user['last-activity']) > $month) || (strtotime($user['last-item']) > $month)
) {
$statistics['active_users_monthly']++;
}
if ((strtotime($user['login_date']) > $week) || (strtotime($user['last-item']) > $week)
if ((strtotime($user['last-activity']) > $week) || (strtotime($user['last-item']) > $week)
) {
$statistics['active_users_weekly']++;
}
@ -1783,4 +1801,64 @@ class User
return DBA::selectToArray('owner-view', [], $condition, $param);
}
/**
* Returns a list of lowercase admin email addresses from the comma-separated list in the config
*
* @return array
*/
public static function getAdminEmailList(): array
{
$adminEmails = strtolower(str_replace(' ', '', DI::config()->get('config', 'admin_email')));
if (!$adminEmails) {
return [];
}
return explode(',', $adminEmails);
}
/**
* Returns the complete list of admin user accounts
*
* @param array $fields
* @return array
* @throws Exception
*/
public static function getAdminList(array $fields = []): array
{
$condition = [
'email' => self::getAdminEmailList(),
'parent-uid' => 0,
'blocked' => 0,
'verified' => true,
'account_removed' => false,
'account_expired' => false,
];
return DBA::selectToArray('user', $fields, $condition, ['order' => ['uid']]);
}
/**
* Return a list of admin user accounts where each unique email address appears only once.
*
* This method is meant for admin notifications that do not need to be sent multiple times to the same email address.
*
* @param array $fields
* @return array
* @throws Exception
*/
public static function getAdminListForEmailing(array $fields = []): array
{
return array_filter(self::getAdminList($fields), function ($user) {
static $emails = [];
if (in_array($user['email'], $emails)) {
return false;
}
$emails[] = $user['email'];
return true;
});
}
}

View file

@ -22,7 +22,6 @@
namespace Friendica\Moderation;
use Exception;
use Friendica\App\BaseURL;
use Friendica\Core\Config\Capability\IManageConfigValues;
use Friendica\Core\L10n;
use Friendica\Database\Database;
@ -34,25 +33,9 @@ class DomainPatternBlocklist
/** @var IManageConfigValues */
private $config;
/** @var Database */
private $db;
/** @var Emailer */
private $emailer;
/** @var L10n */
private $l10n;
/** @var BaseURL */
private $baseUrl;
public function __construct(IManageConfigValues $config, Database $db, Emailer $emailer, L10n $l10n, BaseURL $baseUrl)
public function __construct(IManageConfigValues $config)
{
$this->config = $config;
$this->db = $db;
$this->emailer = $emailer;
$this->l10n = $l10n;
$this->baseUrl = $baseUrl;
$this->config = $config;
}
public function get(): array
@ -62,12 +45,7 @@ class DomainPatternBlocklist
public function set(array $blocklist): bool
{
$result = $this->config->set('system', 'blocklist', $blocklist);
if ($result) {
$this->notifyAll();
}
return $result;
return $this->config->set('system', 'blocklist', $blocklist);
}
/**
@ -192,56 +170,10 @@ class DomainPatternBlocklist
'reason' => $data[1] ?? '',
];
if (!in_array($item, $blocklist)) {
$blocklist[] = $data;
$blocklist[] = $item;
}
}
return $blocklist;
}
/**
* Sends a system email to all the node users about a change in the block list. Sends a single email to each unique
* email address among the valid users.
*
* @return int The number of recipients that were sent an email
* @throws HTTPException\InternalServerErrorException
* @throws HTTPException\UnprocessableEntityException
*/
public function notifyAll(): int
{
// Gathering all non-system parent users who verified their email address and aren't blocked or about to be deleted
// We sort on language to minimize the number of actual language switches during the email build loop
$recipients = $this->db->selectToArray(
'user',
['username', 'email', 'language'],
['`uid` > 0 AND `parent-uid` = 0 AND `verified` AND NOT `account_removed` AND NOT `account_expired` AND NOT `blocked`'],
['group_by' => ['email'], 'order' => ['language']]
);
if (!$recipients) {
return 0;
}
foreach ($recipients as $recipient) {
$l10n = $this->l10n->withLang($recipient['language']);
$email = $this->emailer->newSystemMail()
->withMessage(
$l10n->t('[%s] Notice of remote server domain pattern block list update', $this->emailer->getSiteEmailName()),
$l10n->t(
'Dear %s,
You are receiving this email because the Friendica node at %s where you are registered as a user updated their remote server domain pattern block list.
Please review the updated list at %s at your earliest convenience.',
$recipient['username'],
$this->baseUrl->get(),
$this->baseUrl . '/friendica'
)
)
->withRecipient($recipient['email'])
->build();
$this->emailer->send($email);
}
return count($recipients);
}
}

Some files were not shown because too many files have changed in this diff Show more