Merge branch '2024.06-rc' into stable

This commit is contained in:
Tobias Diekershoff 2024-08-17 17:22:22 +02:00
commit c8c79ddc71
427 changed files with 34011 additions and 29029 deletions

View file

@ -1,3 +1,87 @@
Version 2024.08 (unreleased)
Friendica Core
Updates to the translations AR, CS, DE, ES, FR, GD, HU, IS, IT, JA, NL, PL, RU, SV
Updates to the documentation [foss-, loma-one, mexon]
Updates to the themes (frio) [haheute]
General code cleanup [annando, haheute, mexon, MrPetovan]
Improved the redirection for contact actions [annando]
Improved the performance while fetching of replies [annando]
Improved the performance when visiting remote profiles [annando]
Improved OWA [annando]
Improved the procession of worker tasks [annando]
Improved performance in the probing process [annando]
Improved INBOX performance [annando]
Improved perfomance when expireing postings [annando]
Improved mirroring settings for RSS contacts [annando]
Improved supported image formats [annando]
Improved handling of CC for comments [annando]
Improved handling of "sensitive" flags for postings [annando]
Improved display of log levels [annando, tobiasd]
Improved handling of permissions for attachments [annando]
Improved addon handling [MrPetovan]
Improved API for channels and circles [annando]
Improved performance while displaying local postings [annando]
Improved federation with pixelfed, threads [annando]
Improved integration with Bluesky [annando]
Improved automatic cleanup of the database [annando]
Fixed access to restricted timeline via API [annando]
Fixed problem fetching from INBOXes [annando]
Fixed display of contacts from unavailable networks [annando]
Fixed profile display [annando]
Fixed a problem with local un-/follows [annando]
Fixed the uimport POST endpoint [annando]
Fixed problem with 0Auth logins [annando]
Fixed problem with @mentions in comments [annando]
Fixed XSS in profile fields [annando, apexrabbit, Devilx86, MrPetovan, ponlayookm]
Fixed bug in deleting unused cached avatar pictures [annando]
Fixed paging bug on the media tab of remote profiles [annando]
Fixed display of attached links [annando]
Fixed a bug in circle only contacts [annando]
Fixed display of moderation reports [MrPetovan, TheTomcat14]
Fixed delivery problems to group postings [annando]
Added monitoring service endpoint [annando]
Added admin option display_link_length to set the length of displayed links [annando]
Added the possibility to upload media files via API [annando]
Added console command to clear avatar cache [annando]
Added platform data to the API [annando]
Added parsing support for Nodeinfo 2.1 and 2.2 [annando]
Added node description to Nodeinfo [annando]
Added owner information of relay accounts [annando]
Added option for users about how to transmit postings with titles [annando]
Added for non HTML content of feeds [annando]
Added reshares for postings from Bluesky and tumbl [annando]
Added public forums with manual request approval [annando]
Added "next try" information for deferred worker jobs listing [annando]
Added support of FEP-e232 [annando]
Added automatic closure of registration if admin becomes inactive [annando]
Added channel only option for contacts [annando]
Friendica Addons
Updates to the translations AR, CS, DE, FR, IT, PL, SV
Blockbot
Added Relatica to good client list [hankg]
Improved agent identifier list [annando]
Bluesky
Added monitoring statistics [annando]
Added support of sensitive postings [annando]
Improved API handling [annando]
Improved fetching of user DID [annando]
Fixed conversion BS/Friendica handles [annando]
jsuploader
Improved detection of supported file types [annando]
mailstream
Improved image handling [mexon]
tumblr
Added monitoring statistics [annando]
Improved quoted postings [annando]
Closed Issues
11963, 13714, 13787, 13812, 13821, 13910, 14012, 14030, 14059,
14077, 14079, 14045, 14052, 14055, 14081, 14084, 14102, 14110,
14118, 14121, 14125, 14132, 14134, 14153, 14160, 14170, 14175,
14186, 14197, 14220, 14228, 14231, 14240, 14249, 14250, 14285,
14295, 14303, 14312, 14324, 14329, 14349, 14364
Version 2024.03 (2024-03-21)
Friendica Core
Updates to the translations AR, BG, CS, DE, EO, ES, FR, GD, HU, IS, IT, JA, PL, RO, RU, SV
@ -285,7 +369,7 @@ Version 2023.04 (2023-04-23)
twitter
Improve remote-self handling [annando]
impressum
Avoide obfuscation on un-set email addresses [MrPestovan]
Avoide obfuscation on un-set email addresses [MrPetovan]
notifyall
Fixed a bug selecting the email addresses [nupplaphil]
tumblr
@ -1734,7 +1818,7 @@ Version 2018.05 (2018-06-01)
Friendica Addons:
Updates to the translations (DE, EN_GB, EN_US, ES, FI, FR, IS, IT, NL, PL, RU, ZH_CN) [translation teams]
advancedcontentfilter: new addon with advanced filter capabilities [MrPetova]
advancedcontentfilter: new addon with advanced filter capabilities [MrPetovan]
catavatar: new addon for profile pictures based on David Revoy's cat-avatar generator [annando, fabrixxm, tobiasd]
languagefilter: better help text [andyhee]
mathjax: fixed the config form and adopted new CDN URL [tobiasd]

View file

@ -1 +1 @@
2024.03
2024.06-rc

View file

@ -138,9 +138,9 @@ function execute_tests() {
if [ -n "${USEDOCKER}" ]; then
echo "Fire up the mysql docker"
DOCKER_CONTAINER_ID=$(docker run \
-e MYSQL_ROOT_PASSWORD=friendica \
-e MYSQL_ROOT_PASSWORD="${DATABASE_PASSWORD}" \
-e MYSQL_USER="${DATABASE_USER}" \
-e MYSQL_PASSWORD=friendica \
-e MYSQL_PASSWORD="${DATABASE_PASSWORD}" \
-e MYSQL_DATABASE="${DATABASE_NAME}" \
-d mysql)
DATABASE_HOST=$(docker inspect --format="{{.NetworkSettings.IPAddress}}" "${DOCKER_CONTAINER_ID}")
@ -152,8 +152,8 @@ function execute_tests() {
echo "To use the docker container set the USEDOCKER environment variable"
exit 3
fi
mysql -u "${DATABASE_USER}" -pfriendica -e "DROP DATABASE IF EXISTS ${DATABASE_NAME}" -h ${DATABASE_HOST} || true
mysql -u "${DATABASE_USER}" -pfriendica -e "CREATE DATABASE ${DATABASE_NAME} DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci" -h ${DATABASE_HOST}
mysql -u "${DATABASE_USER}" -p"${DATABASE_PASSWORD}" -e "DROP DATABASE IF EXISTS ${DATABASE_NAME}" -h ${DATABASE_HOST} || true
mysql -u "${DATABASE_USER}" -p"${DATABASE_PASSWORD}" -e "CREATE DATABASE ${DATABASE_NAME} DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci" -h ${DATABASE_HOST}
else
DATABASE_HOST=mysql
fi
@ -171,9 +171,9 @@ function execute_tests() {
if [ -n "${USEDOCKER}" ]; then
echo "Fire up the mariadb docker"
DOCKER_CONTAINER_ID=$(docker run \
-e MYSQL_ROOT_PASSWORD=friendica \
-e MYSQL_ROOT_PASSWORD="${DATABASE_PASSWORD}" \
-e MYSQL_USER="${DATABASE_USER}" \
-e MYSQL_PASSWORD=friendica \
-e MYSQL_PASSWORD="${DATABASE_PASSWORD}" \
-e MYSQL_DATABASE="${DATABASE_NAME}" \
-d mariadb)
DATABASE_HOST=$(docker inspect --format="{{.NetworkSettings.IPAddress}}" "${DOCKER_CONTAINER_ID}")
@ -185,8 +185,8 @@ function execute_tests() {
echo "To use the docker container set the USEDOCKER environment variable"
exit 3
fi
mysql -u "${DATABASE_USER}" -pfriendica -e "DROP DATABASE IF EXISTS ${DATABASE_NAME}" -h ${DATABASE_HOST} || true
mysql -u "${DATABASE_USER}" -pfriendica -e "CREATE DATABASE ${DATABASE_NAME} DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci" -h ${DATABASE_HOST}
mysql -u "${DATABASE_USER}" -p"${DATABASE_PASSWORD}" -e "DROP DATABASE IF EXISTS ${DATABASE_NAME}" -h ${DATABASE_HOST} || true
mysql -u "${DATABASE_USER}" -p"${DATABASE_PASSWORD}" -e "CREATE DATABASE ${DATABASE_NAME} DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci" -h ${DATABASE_HOST}
else
DATABASE_HOST=mariadb
fi
@ -203,14 +203,14 @@ function execute_tests() {
if [ -n "${USEDOCKER}" ]; then
echo "Initialize database..."
docker exec ${DOCKER_CONTAINER_ID} mysql -u root -pfriendica -e "CREATE DATABASE IF NOT EXISTS ${DATABASE_NAME};"
docker exec ${DOCKER_CONTAINER_ID} mysql -u root -p"${DATABASE_PASSWORD}" -e "CREATE DATABASE IF NOT EXISTS ${DATABASE_NAME};"
fi
export MYSQL_HOST="${DATABASE_HOST}"
#call installer
echo "Installing Friendica..."
"${PHP}" ./bin/console.php autoinstall --dbuser="${DATABASE_USER}" --dbpass=friendica --dbdata="${DATABASE_NAME}" --dbhost="${DATABASE_HOST}" --url=https://friendica.local --admin=admin@friendica.local
"${PHP}" ./bin/console.php autoinstall --dbuser="${DATABASE_USER}" --dbpass="${DATABASE_PASSWORD}" --dbdata="${DATABASE_NAME}" --dbhost="${DATABASE_HOST}" --url=https://friendica.local --admin=admin@friendica.local
fi
#test execution

View file

@ -72,7 +72,7 @@ echo "Extract strings to $OUTFILE.."
[ -f "$OUTFILE" ] && rm "$OUTFILE"; touch "$OUTFILE"
# shellcheck disable=SC2086 # $FINDOPTS is meant to be split
find_result=$(find "$FINDSTARTDIR" $FINDOPTS -name "*.php" -type f | LC_ALL=C sort --stable)
find_result=$(find "$FINDSTARTDIR" $FINDOPTS -name "*.php" -type f | LC_ALL=C sort -s)
total_files=$(wc -l <<< "${find_result}")
@ -86,7 +86,7 @@ do
if [ ! -d "$file" ]
then
# shellcheck disable=SC2086 # $KEYWORDS is meant to be split
xgettext $KEYWORDS -j -o "$OUTFILE" --from-code=UTF-8 "$file" || exit 1
xgettext $KEYWORDS --no-wrap -j -o "$OUTFILE" --from-code=UTF-8 "$file" || exit 1
sed -i.bkp "s/CHARSET/UTF-8/g" "$OUTFILE"
fi
(( count++ ))

View file

@ -1,6 +1,6 @@
-- ------------------------------------------
-- Friendica 2024.03 (Yellow Archangel)
-- DB_UPDATE_VERSION 1557
-- Friendica 2024.06-rc (Yellow Archangel)
-- DB_UPDATE_VERSION 1571
-- ------------------------------------------
@ -11,7 +11,7 @@ CREATE TABLE IF NOT EXISTS `gserver` (
`id` int unsigned NOT NULL auto_increment COMMENT 'sequential ID',
`url` varbinary(383) NOT NULL DEFAULT '' COMMENT '',
`nurl` varbinary(383) NOT NULL DEFAULT '' COMMENT '',
`version` varchar(255) NOT NULL DEFAULT '' COMMENT '',
`version` varchar(255) NOT NULL DEFAULT '' COMMENT 'The version of this server software.',
`site_name` varchar(255) NOT NULL DEFAULT '' COMMENT '',
`info` text COMMENT '',
`register_policy` tinyint NOT NULL DEFAULT 0 COMMENT '',
@ -23,10 +23,14 @@ CREATE TABLE IF NOT EXISTS `gserver` (
`local-comments` int unsigned COMMENT 'Number of local comments',
`directory-type` tinyint DEFAULT 0 COMMENT 'Type of directory service (Poco, Mastodon)',
`poco` varbinary(383) NOT NULL DEFAULT '' COMMENT '',
`openwebauth` varbinary(383) COMMENT 'Path to the OpenWebAuth endpoint',
`authredirect` varbinary(383) COMMENT 'Path to the authRedirect endpoint',
`noscrape` varbinary(383) NOT NULL DEFAULT '' COMMENT '',
`network` char(4) NOT NULL DEFAULT '' COMMENT '',
`protocol` tinyint unsigned COMMENT 'The protocol of the server',
`platform` varchar(255) NOT NULL DEFAULT '' COMMENT '',
`platform` varchar(255) NOT NULL DEFAULT '' COMMENT 'The canonical name of this server software.',
`repository` varbinary(383) COMMENT 'The url of the source code repository of this server software.',
`homepage` varbinary(383) COMMENT 'The url of the homepage of this server software.',
`relay-subscribe` boolean NOT NULL DEFAULT '0' COMMENT 'Has the server subscribed to the relay system',
`relay-scope` varchar(10) NOT NULL DEFAULT '' COMMENT 'The scope of messages that the server wants to get',
`detection-method` tinyint unsigned COMMENT 'Method that had been used to detect that server',
@ -813,6 +817,7 @@ CREATE TABLE IF NOT EXISTS `inbox-entry` (
`activity-id` varbinary(383) COMMENT 'id of the incoming activity',
`object-id` varbinary(383) COMMENT '',
`in-reply-to-id` varbinary(383) COMMENT '',
`context` varbinary(383) COMMENT '',
`conversation` varbinary(383) COMMENT '',
`type` varchar(64) COMMENT 'Type of the activity',
`object-type` varchar(64) COMMENT 'Type of the object activity',
@ -823,6 +828,7 @@ CREATE TABLE IF NOT EXISTS `inbox-entry` (
`push` boolean COMMENT 'Is the entry pushed or have pulled it?',
`trust` boolean COMMENT 'Do we trust this entry?',
`wid` int unsigned COMMENT 'Workerqueue id',
`retrial` tinyint unsigned DEFAULT 0 COMMENT 'Retrial counter',
PRIMARY KEY(`id`),
UNIQUE INDEX `activity-id` (`activity-id`),
INDEX `object-id` (`object-id`),
@ -1181,6 +1187,7 @@ CREATE TABLE IF NOT EXISTS `post` (
`parent-uri-id` int unsigned COMMENT 'Id of the item-uri table that contains the parent uri',
`thr-parent-id` int unsigned COMMENT 'Id of the item-uri table that contains the thread parent uri',
`external-id` int unsigned COMMENT 'Id of the item-uri table entry that contains the external uri',
`replies-id` int unsigned COMMENT 'Id of the item-uri table entry that contains the endpoint for the replies collection',
`created` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT 'Creation timestamp.',
`edited` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT 'Date of last edit (default is created)',
`received` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT 'datetime',
@ -1199,6 +1206,7 @@ CREATE TABLE IF NOT EXISTS `post` (
INDEX `parent-uri-id` (`parent-uri-id`),
INDEX `thr-parent-id` (`thr-parent-id`),
INDEX `external-id` (`external-id`),
INDEX `replies-id` (`replies-id`),
INDEX `owner-id` (`owner-id`),
INDEX `author-id` (`author-id`),
INDEX `causer-id` (`causer-id`),
@ -1207,6 +1215,7 @@ CREATE TABLE IF NOT EXISTS `post` (
FOREIGN KEY (`parent-uri-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE,
FOREIGN KEY (`thr-parent-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE,
FOREIGN KEY (`external-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE,
FOREIGN KEY (`replies-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE,
FOREIGN KEY (`owner-id`) REFERENCES `contact` (`id`) ON UPDATE RESTRICT ON DELETE RESTRICT,
FOREIGN KEY (`author-id`) REFERENCES `contact` (`id`) ON UPDATE RESTRICT ON DELETE RESTRICT,
FOREIGN KEY (`causer-id`) REFERENCES `contact` (`id`) ON UPDATE RESTRICT ON DELETE RESTRICT,
@ -1347,7 +1356,7 @@ CREATE TABLE IF NOT EXISTS `post-engagement` (
`uri-id` int unsigned NOT NULL COMMENT 'Id of the item-uri table entry that contains the item uri',
`owner-id` int unsigned NOT NULL DEFAULT 0 COMMENT 'Item owner',
`contact-type` tinyint NOT NULL DEFAULT 0 COMMENT 'Person, organisation, news, community, relay',
`media-type` tinyint NOT NULL DEFAULT 0 COMMENT 'Type of media in a bit array (1 = image, 2 = video, 4 = audio',
`media-type` tinyint NOT NULL DEFAULT 0 COMMENT 'Type of media in a bit array (1 = image, 2 = video, 4 = audio)',
`language` char(2) COMMENT 'Language information about this post in the ISO 639-1 format',
`searchtext` mediumtext COMMENT 'Simplified text for the full text search',
`size` int unsigned COMMENT 'Body size',
@ -1439,6 +1448,36 @@ CREATE TABLE IF NOT EXISTS `post-media` (
FOREIGN KEY (`media-uri-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Attached media';
--
-- TABLE post-origin
--
CREATE TABLE IF NOT EXISTS `post-origin` (
`id` int unsigned NOT NULL,
`uri-id` int unsigned NOT NULL COMMENT 'Id of the item-uri table entry that contains the item uri',
`uid` mediumint unsigned NOT NULL COMMENT 'Owner id which owns this copy of the item',
`parent-uri-id` int unsigned COMMENT 'Id of the item-uri table that contains the parent uri',
`thr-parent-id` int unsigned COMMENT 'Id of the item-uri table that contains the thread parent uri',
`created` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT 'Creation timestamp.',
`received` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT 'datetime',
`gravity` tinyint unsigned NOT NULL DEFAULT 0 COMMENT '',
`vid` smallint unsigned COMMENT 'Id of the verb table entry that contains the activity verbs',
`private` tinyint unsigned NOT NULL DEFAULT 0 COMMENT '0=public, 1=private, 2=unlisted',
`wall` boolean NOT NULL DEFAULT '0' COMMENT 'This item was posted to the wall of uid',
PRIMARY KEY(`id`),
UNIQUE INDEX `uid_uri-id` (`uid`,`uri-id`),
INDEX `uri-id` (`uri-id`),
INDEX `parent-uri-id` (`parent-uri-id`),
INDEX `thr-parent-id` (`thr-parent-id`),
INDEX `vid` (`vid`),
INDEX `parent-uri-id_uid` (`parent-uri-id`,`uid`),
INDEX `uid_wall_received` (`uid`,`wall`,`received`),
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,
FOREIGN KEY (`parent-uri-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE,
FOREIGN KEY (`thr-parent-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE,
FOREIGN KEY (`vid`) REFERENCES `verb` (`id`) ON UPDATE RESTRICT ON DELETE RESTRICT
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Posts from local users';
--
-- TABLE post-question
--
@ -1471,7 +1510,7 @@ CREATE TABLE IF NOT EXISTS `post-question-option` (
CREATE TABLE IF NOT EXISTS `post-searchindex` (
`uri-id` int unsigned NOT NULL COMMENT 'Id of the item-uri table entry that contains the item uri',
`owner-id` int unsigned NOT NULL DEFAULT 0 COMMENT 'Item owner',
`media-type` tinyint NOT NULL DEFAULT 0 COMMENT 'Type of media in a bit array (1 = image, 2 = video, 4 = audio',
`media-type` tinyint NOT NULL DEFAULT 0 COMMENT 'Type of media in a bit array (1 = image, 2 = video, 4 = audio)',
`language` char(2) COMMENT 'Language information about this post in the ISO 639-1 format',
`searchtext` mediumtext COMMENT 'Simplified text for the full text search',
`size` int unsigned COMMENT 'Body size',
@ -1506,6 +1545,7 @@ CREATE TABLE IF NOT EXISTS `post-tag` (
--
CREATE TABLE IF NOT EXISTS `post-thread` (
`uri-id` int unsigned NOT NULL COMMENT 'Id of the item-uri table entry that contains the item uri',
`context-id` int unsigned COMMENT 'Id of the item-uri table entry that contains the endpoint for the context collection',
`conversation-id` int unsigned COMMENT 'Id of the item-uri table entry that contains the conversation uri',
`owner-id` int unsigned NOT NULL DEFAULT 0 COMMENT 'Item owner',
`author-id` int unsigned NOT NULL DEFAULT 0 COMMENT 'Item author',
@ -1516,6 +1556,7 @@ CREATE TABLE IF NOT EXISTS `post-thread` (
`changed` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT 'Date that something in the conversation changed, indicating clients should fetch the conversation again',
`commented` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT '',
PRIMARY KEY(`uri-id`),
INDEX `context-id` (`context-id`),
INDEX `conversation-id` (`conversation-id`),
INDEX `owner-id` (`owner-id`),
INDEX `author-id` (`author-id`),
@ -1523,6 +1564,7 @@ CREATE TABLE IF NOT EXISTS `post-thread` (
INDEX `received` (`received`),
INDEX `commented` (`commented`),
FOREIGN KEY (`uri-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE,
FOREIGN KEY (`context-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE,
FOREIGN KEY (`conversation-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE,
FOREIGN KEY (`owner-id`) REFERENCES `contact` (`id`) ON UPDATE RESTRICT ON DELETE RESTRICT,
FOREIGN KEY (`author-id`) REFERENCES `contact` (`id`) ON UPDATE RESTRICT ON DELETE RESTRICT,
@ -1538,6 +1580,7 @@ CREATE TABLE IF NOT EXISTS `post-user` (
`parent-uri-id` int unsigned COMMENT 'Id of the item-uri table that contains the parent uri',
`thr-parent-id` int unsigned COMMENT 'Id of the item-uri table that contains the thread parent uri',
`external-id` int unsigned COMMENT 'Id of the item-uri table entry that contains the external uri',
`replies-id` int unsigned COMMENT 'Id of the item-uri table entry that contains the endpoint for the replies collection',
`created` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT 'Creation timestamp.',
`edited` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT 'Date of last edit (default is created)',
`received` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT 'datetime',
@ -1550,6 +1593,7 @@ CREATE TABLE IF NOT EXISTS `post-user` (
`post-reason` tinyint unsigned NOT NULL DEFAULT 0 COMMENT 'Reason why the post arrived at the user',
`vid` smallint unsigned COMMENT 'Id of the verb table entry that contains the activity verbs',
`private` tinyint unsigned NOT NULL DEFAULT 0 COMMENT '0=public, 1=private, 2=unlisted',
`restrictions` tinyint unsigned COMMENT 'Bit array of post restrictions (1 = Reply, 2 = Like, 4 = Announce)',
`global` boolean NOT NULL DEFAULT '0' COMMENT '',
`visible` boolean NOT NULL DEFAULT '0' COMMENT '',
`deleted` boolean NOT NULL DEFAULT '0' COMMENT 'item has been marked for deletion',
@ -1569,6 +1613,7 @@ CREATE TABLE IF NOT EXISTS `post-user` (
INDEX `parent-uri-id` (`parent-uri-id`),
INDEX `thr-parent-id` (`thr-parent-id`),
INDEX `external-id` (`external-id`),
INDEX `replies-id` (`replies-id`),
INDEX `owner-id` (`owner-id`),
INDEX `author-id` (`author-id`),
INDEX `causer-id` (`causer-id`),
@ -1589,6 +1634,7 @@ CREATE TABLE IF NOT EXISTS `post-user` (
FOREIGN KEY (`parent-uri-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE,
FOREIGN KEY (`thr-parent-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE,
FOREIGN KEY (`external-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE,
FOREIGN KEY (`replies-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE,
FOREIGN KEY (`owner-id`) REFERENCES `contact` (`id`) ON UPDATE RESTRICT ON DELETE RESTRICT,
FOREIGN KEY (`author-id`) REFERENCES `contact` (`id`) ON UPDATE RESTRICT ON DELETE RESTRICT,
FOREIGN KEY (`causer-id`) REFERENCES `contact` (`id`) ON UPDATE RESTRICT ON DELETE RESTRICT,
@ -1604,6 +1650,7 @@ CREATE TABLE IF NOT EXISTS `post-user` (
--
CREATE TABLE IF NOT EXISTS `post-thread-user` (
`uri-id` int unsigned NOT NULL COMMENT 'Id of the item-uri table entry that contains the item uri',
`context-id` int unsigned COMMENT 'Id of the item-uri table entry that contains the endpoint for the context collection',
`conversation-id` int unsigned COMMENT 'Id of the item-uri table entry that contains the conversation uri',
`owner-id` int unsigned NOT NULL DEFAULT 0 COMMENT 'Item owner',
`author-id` int unsigned NOT NULL DEFAULT 0 COMMENT 'Item author',
@ -1629,6 +1676,7 @@ CREATE TABLE IF NOT EXISTS `post-thread-user` (
`post-user-id` int unsigned COMMENT 'Id of the post-user table',
PRIMARY KEY(`uid`,`uri-id`),
INDEX `uri-id` (`uri-id`),
INDEX `context-id` (`context-id`),
INDEX `conversation-id` (`conversation-id`),
INDEX `owner-id` (`owner-id`),
INDEX `author-id` (`author-id`),
@ -1651,6 +1699,7 @@ CREATE TABLE IF NOT EXISTS `post-thread-user` (
INDEX `contact-id_received` (`contact-id`,`received`),
INDEX `contact-id_created` (`contact-id`,`created`),
FOREIGN KEY (`uri-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE,
FOREIGN KEY (`context-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE,
FOREIGN KEY (`conversation-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE,
FOREIGN KEY (`owner-id`) REFERENCES `contact` (`id`) ON UPDATE RESTRICT ON DELETE RESTRICT,
FOREIGN KEY (`author-id`) REFERENCES `contact` (`id`) ON UPDATE RESTRICT ON DELETE RESTRICT,
@ -1947,6 +1996,7 @@ CREATE TABLE IF NOT EXISTS `user-contact` (
`ignored` boolean COMMENT 'Posts from this contact are ignored',
`collapsed` boolean COMMENT 'Posts from this contact are collapsed',
`hidden` boolean COMMENT 'This contact is hidden from the others',
`channel-only` boolean COMMENT 'This contact is displayed only in channels, but not in the network stream.',
`is-blocked` boolean COMMENT 'User is blocked by this contact',
`channel-frequency` tinyint unsigned COMMENT 'Controls the frequency of the appearance of this contact in channels',
`pending` boolean COMMENT '',
@ -2065,6 +2115,38 @@ CREATE VIEW `post-counts-view` AS SELECT
FROM `post-counts`
INNER JOIN `verb` ON `verb`.`id` = `post-counts`.`vid`;
--
-- VIEW post-engagement-user-view
--
DROP VIEW IF EXISTS `post-engagement-user-view`;
CREATE VIEW `post-engagement-user-view` AS SELECT
`post-thread-user`.`uid` AS `uid`,
`post-engagement`.`uri-id` AS `uri-id`,
`post-engagement`.`owner-id` AS `owner-id`,
`post-engagement`.`media-type` AS `media-type`,
`post-engagement`.`language` AS `language`,
`post-engagement`.`searchtext` AS `searchtext`,
`post-engagement`.`size` AS `size`,
`post-thread-user`.`commented` AS `commented`,
`post-thread-user`.`received` AS `received`,
`post-thread-user`.`created` AS `created`,
`post-thread-user`.`network` AS `network`,
`post-engagement`.`language` AS `restricted`,
0 AS `comments`,
0 AS `activities`
FROM `post-thread-user`
INNER JOIN `post-engagement` ON `post-engagement`.`uri-id` = `post-thread-user`.`uri-id`
INNER JOIN `post-user` ON `post-user`.`id` = `post-thread-user`.`post-user-id`
STRAIGHT_JOIN `contact` ON `contact`.`id` = `post-thread-user`.`contact-id`
STRAIGHT_JOIN `contact` AS `authorcontact` ON `authorcontact`.`id` = `post-thread-user`.`author-id`
STRAIGHT_JOIN `contact` AS `ownercontact` ON `ownercontact`.`id` = `post-thread-user`.`owner-id`
WHERE `post-user`.`visible` AND NOT `post-user`.`deleted`
AND (NOT `contact`.`readonly` AND NOT `contact`.`blocked` AND NOT `contact`.`pending`)
AND (`post-thread-user`.`hidden` IS NULL OR NOT `post-thread-user`.`hidden`)
AND NOT `authorcontact`.`blocked` AND NOT `ownercontact`.`blocked`
AND NOT EXISTS(SELECT `cid` FROM `user-contact` WHERE `uid` = `post-thread-user`.`uid` AND `cid` IN (`authorcontact`.`id`, `ownercontact`.`id`) AND (`blocked` OR `ignored`))
AND NOT EXISTS(SELECT `gsid` FROM `user-gserver` WHERE `uid` = `post-thread-user`.`uid` AND `gsid` IN (`authorcontact`.`gsid`, `ownercontact`.`gsid`) AND `ignored`);
--
-- VIEW post-timeline-view
--
@ -2110,6 +2192,52 @@ CREATE VIEW `post-timeline-view` AS SELECT
STRAIGHT_JOIN `contact` AS `owner` ON `owner`.`id` = `post-user`.`owner-id`
LEFT JOIN `contact` AS `causer` ON `causer`.`id` = `post-user`.`causer-id`;
--
-- VIEW post-timeline-origin-view
--
DROP VIEW IF EXISTS `post-timeline-origin-view`;
CREATE VIEW `post-timeline-origin-view` AS SELECT
`post-origin`.`uid` AS `uid`,
`post-origin`.`uri-id` AS `uri-id`,
`post-origin`.`gravity` AS `gravity`,
`post-origin`.`created` AS `created`,
`post-user`.`edited` AS `edited`,
`post-thread-user`.`commented` AS `commented`,
`post-origin`.`received` AS `received`,
`post-thread-user`.`changed` AS `changed`,
`post-origin`.`private` AS `private`,
`post-user`.`visible` AS `visible`,
`post-user`.`deleted` AS `deleted`,
true AS `origin`,
`post-user`.`global` AS `global`,
`post-user`.`network` AS `network`,
`post-user`.`protocol` AS `protocol`,
`post-origin`.`vid` AS `vid`,
`post-user`.`contact-id` AS `contact-id`,
`contact`.`blocked` AS `contact-blocked`,
`contact`.`readonly` AS `contact-readonly`,
`contact`.`pending` AS `contact-pending`,
`contact`.`rel` AS `contact-rel`,
`contact`.`uid` AS `contact-uid`,
`contact`.`self` AS `self`,
`post-user`.`author-id` AS `author-id`,
`author`.`blocked` AS `author-blocked`,
`author`.`hidden` AS `author-hidden`,
`author`.`gsid` AS `author-gsid`,
`post-user`.`owner-id` AS `owner-id`,
`owner`.`blocked` AS `owner-blocked`,
`owner`.`gsid` AS `owner-gsid`,
`post-user`.`causer-id` AS `causer-id`,
`causer`.`blocked` AS `causer-blocked`,
`causer`.`gsid` AS `causer-gsid`
FROM `post-origin`
INNER JOIN `post-user` ON `post-user`.`id` = `post-origin`.`id`
LEFT JOIN `post-thread-user` ON `post-thread-user`.`uri-id` = `post-origin`.`parent-uri-id` AND `post-thread-user`.`uid` = `post-origin`.`uid`
STRAIGHT_JOIN `contact` ON `contact`.`id` = `post-user`.`contact-id`
STRAIGHT_JOIN `contact` AS `author` ON `author`.`id` = `post-user`.`author-id`
STRAIGHT_JOIN `contact` AS `owner` ON `owner`.`id` = `post-user`.`owner-id`
LEFT JOIN `contact` AS `causer` ON `causer`.`id` = `post-user`.`causer-id`;
--
-- VIEW post-searchindex-user-view
--
@ -2142,6 +2270,392 @@ CREATE VIEW `post-searchindex-user-view` AS SELECT
AND NOT EXISTS(SELECT `cid` FROM `user-contact` WHERE `uid` = `post-thread-user`.`uid` AND `cid` IN (`authorcontact`.`id`, `ownercontact`.`id`) AND (`blocked` OR `ignored`))
AND NOT EXISTS(SELECT `gsid` FROM `user-gserver` WHERE `uid` = `post-thread-user`.`uid` AND `gsid` IN (`authorcontact`.`gsid`, `ownercontact`.`gsid`) AND `ignored`);
--
-- VIEW post-origin-view
--
DROP VIEW IF EXISTS `post-origin-view`;
CREATE VIEW `post-origin-view` AS SELECT
`post-origin`.`id` AS `id`,
`post-origin`.`id` AS `post-user-id`,
`post-origin`.`uid` AS `uid`,
`post-thread-user`.`post-user-id` AS `parent`,
`item-uri`.`uri` AS `uri`,
`post-origin`.`uri-id` AS `uri-id`,
`parent-item-uri`.`uri` AS `parent-uri`,
`post-origin`.`parent-uri-id` AS `parent-uri-id`,
`thr-parent-item-uri`.`uri` AS `thr-parent`,
`post-origin`.`thr-parent-id` AS `thr-parent-id`,
`conversation-item-uri`.`uri` AS `conversation`,
`post-thread-user`.`conversation-id` AS `conversation-id`,
`context-item-uri`.`uri` AS `context`,
`post-thread-user`.`context-id` AS `context-id`,
`quote-item-uri`.`uri` AS `quote-uri`,
`post-content`.`quote-uri-id` AS `quote-uri-id`,
`item-uri`.`guid` AS `guid`,
`post-origin`.`wall` AS `wall`,
`post-origin`.`gravity` AS `gravity`,
`external-item-uri`.`uri` AS `extid`,
`post-user`.`external-id` AS `external-id`,
`replies-item-uri`.`uri` AS `replies`,
`post-user`.`replies-id` AS `replies-id`,
`post-origin`.`created` AS `created`,
`post-user`.`edited` AS `edited`,
`post-thread-user`.`commented` AS `commented`,
`post-origin`.`received` AS `received`,
`post-thread-user`.`changed` AS `changed`,
`post-user`.`post-type` AS `post-type`,
`post-user`.`post-reason` AS `post-reason`,
`post-origin`.`private` AS `private`,
`post-thread-user`.`pubmail` AS `pubmail`,
`post-user`.`visible` AS `visible`,
`post-thread-user`.`starred` AS `starred`,
`post-user`.`unseen` AS `unseen`,
`post-user`.`deleted` AS `deleted`,
true AS `origin`,
`post-thread-user`.`origin` AS `parent-origin`,
`post-thread-user`.`mention` AS `mention`,
`post-user`.`global` AS `global`,
EXISTS(SELECT `type` FROM `post-collection` WHERE `type` = 0 AND `uri-id` = `post-origin`.`uri-id`) AS `featured`,
`post-user`.`network` AS `network`,
`post-user`.`protocol` AS `protocol`,
`post-origin`.`vid` AS `vid`,
`post-user`.`psid` AS `psid`,
IF (`post-origin`.`vid` IS NULL, '', `verb`.`name`) AS `verb`,
`post-content`.`title` AS `title`,
`post-content`.`content-warning` AS `content-warning`,
`post-content`.`raw-body` AS `raw-body`,
IFNULL (`post-content`.`body`, '') AS `body`,
`post-content`.`rendered-hash` AS `rendered-hash`,
`post-content`.`rendered-html` AS `rendered-html`,
`post-content`.`language` AS `language`,
`post-content`.`plink` AS `plink`,
`post-content`.`location` AS `location`,
`post-content`.`coord` AS `coord`,
`post-content`.`sensitive` AS `sensitive`,
`post-user`.`restrictions` AS `restrictions`,
`post-content`.`app` AS `app`,
`post-content`.`object-type` AS `object-type`,
`post-content`.`object` AS `object`,
`post-content`.`target-type` AS `target-type`,
`post-content`.`target` AS `target`,
`post-content`.`resource-id` AS `resource-id`,
`post-user`.`contact-id` AS `contact-id`,
`contact`.`uri-id` AS `contact-uri-id`,
`contact`.`url` AS `contact-link`,
`contact`.`addr` AS `contact-addr`,
`contact`.`name` AS `contact-name`,
`contact`.`nick` AS `contact-nick`,
`contact`.`thumb` AS `contact-avatar`,
`contact`.`network` AS `contact-network`,
`contact`.`blocked` AS `contact-blocked`,
`contact`.`hidden` AS `contact-hidden`,
`contact`.`readonly` AS `contact-readonly`,
`contact`.`archive` AS `contact-archive`,
`contact`.`pending` AS `contact-pending`,
`contact`.`rel` AS `contact-rel`,
`contact`.`uid` AS `contact-uid`,
`contact`.`contact-type` AS `contact-contact-type`,
IF (`post-user`.`network` IN ('apub', 'dfrn', 'dspr', 'stat'), true, `contact`.`writable`) AS `writable`,
`contact`.`self` AS `self`,
`contact`.`id` AS `cid`,
`contact`.`alias` AS `alias`,
`contact`.`photo` AS `photo`,
`contact`.`name-date` AS `name-date`,
`contact`.`uri-date` AS `uri-date`,
`contact`.`avatar-date` AS `avatar-date`,
`contact`.`thumb` AS `thumb`,
`post-user`.`author-id` AS `author-id`,
`author`.`uri-id` AS `author-uri-id`,
`author`.`url` AS `author-link`,
`author`.`addr` AS `author-addr`,
IF (`contact`.`url` = `author`.`url` AND `contact`.`name` != '', `contact`.`name`, `author`.`name`) AS `author-name`,
`author`.`nick` AS `author-nick`,
`author`.`alias` AS `author-alias`,
IF (`contact`.`url` = `author`.`url` AND `contact`.`thumb` != '', `contact`.`thumb`, `author`.`thumb`) AS `author-avatar`,
`author`.`network` AS `author-network`,
`author`.`blocked` AS `author-blocked`,
`author`.`hidden` AS `author-hidden`,
`author`.`updated` AS `author-updated`,
`author`.`contact-type` AS `author-contact-type`,
`author`.`gsid` AS `author-gsid`,
`author`.`baseurl` AS `author-baseurl`,
`post-user`.`owner-id` AS `owner-id`,
`owner`.`uri-id` AS `owner-uri-id`,
`owner`.`url` AS `owner-link`,
`owner`.`addr` AS `owner-addr`,
IF (`contact`.`url` = `owner`.`url` AND `contact`.`name` != '', `contact`.`name`, `owner`.`name`) AS `owner-name`,
`owner`.`nick` AS `owner-nick`,
`owner`.`alias` AS `owner-alias`,
IF (`contact`.`url` = `owner`.`url` AND `contact`.`thumb` != '', `contact`.`thumb`, `owner`.`thumb`) AS `owner-avatar`,
`owner`.`network` AS `owner-network`,
`owner`.`blocked` AS `owner-blocked`,
`owner`.`hidden` AS `owner-hidden`,
`owner`.`updated` AS `owner-updated`,
`owner`.`gsid` AS `owner-gsid`,
`owner`.`contact-type` AS `owner-contact-type`,
`post-user`.`causer-id` AS `causer-id`,
`causer`.`uri-id` AS `causer-uri-id`,
`causer`.`url` AS `causer-link`,
`causer`.`addr` AS `causer-addr`,
`causer`.`name` AS `causer-name`,
`causer`.`nick` AS `causer-nick`,
`causer`.`alias` AS `causer-alias`,
`causer`.`thumb` AS `causer-avatar`,
`causer`.`network` AS `causer-network`,
`causer`.`blocked` AS `causer-blocked`,
`causer`.`hidden` AS `causer-hidden`,
`causer`.`gsid` AS `causer-gsid`,
`causer`.`contact-type` AS `causer-contact-type`,
`post-delivery-data`.`postopts` AS `postopts`,
`post-delivery-data`.`inform` AS `inform`,
`post-delivery-data`.`queue_count` AS `delivery_queue_count`,
`post-delivery-data`.`queue_done` AS `delivery_queue_done`,
`post-delivery-data`.`queue_failed` AS `delivery_queue_failed`,
IF (`post-user`.`psid` IS NULL, '', `permissionset`.`allow_cid`) AS `allow_cid`,
IF (`post-user`.`psid` IS NULL, '', `permissionset`.`allow_gid`) AS `allow_gid`,
IF (`post-user`.`psid` IS NULL, '', `permissionset`.`deny_cid`) AS `deny_cid`,
IF (`post-user`.`psid` IS NULL, '', `permissionset`.`deny_gid`) AS `deny_gid`,
`post-user`.`event-id` AS `event-id`,
`event`.`created` AS `event-created`,
`event`.`edited` AS `event-edited`,
`event`.`start` AS `event-start`,
`event`.`finish` AS `event-finish`,
`event`.`summary` AS `event-summary`,
`event`.`desc` AS `event-desc`,
`event`.`location` AS `event-location`,
`event`.`type` AS `event-type`,
`event`.`nofinish` AS `event-nofinish`,
`event`.`ignore` AS `event-ignore`,
`post-question`.`id` AS `question-id`,
`post-question`.`multiple` AS `question-multiple`,
`post-question`.`voters` AS `question-voters`,
`post-question`.`end-time` AS `question-end-time`,
EXISTS(SELECT `uri-id` FROM `post-category` WHERE `post-category`.`uri-id` = `post-origin`.`uri-id` AND `post-category`.`uid` = `post-origin`.`uid`) AS `has-categories`,
EXISTS(SELECT `id` FROM `post-media` WHERE `post-media`.`uri-id` = `post-origin`.`uri-id`) AS `has-media`,
`diaspora-interaction`.`interaction` AS `signed_text`,
`parent-item-uri`.`guid` AS `parent-guid`,
`post-thread-user`.`network` AS `parent-network`,
`post-thread-user`.`author-id` AS `parent-author-id`,
`parent-post-author`.`url` AS `parent-author-link`,
`parent-post-author`.`name` AS `parent-author-name`,
`parent-post-author`.`nick` AS `parent-author-nick`,
`parent-post-author`.`network` AS `parent-author-network`
FROM `post-origin`
INNER JOIN `post-user` ON `post-user`.`id` = `post-origin`.`id`
INNER JOIN `post-thread-user` ON `post-thread-user`.`uri-id` = `post-origin`.`parent-uri-id` AND `post-thread-user`.`uid` = `post-origin`.`uid`
STRAIGHT_JOIN `contact` ON `contact`.`id` = `post-user`.`contact-id`
STRAIGHT_JOIN `contact` AS `author` ON `author`.`id` = `post-user`.`author-id`
STRAIGHT_JOIN `contact` AS `owner` ON `owner`.`id` = `post-user`.`owner-id`
LEFT JOIN `contact` AS `causer` ON `causer`.`id` = `post-user`.`causer-id`
LEFT JOIN `item-uri` ON `item-uri`.`id` = `post-origin`.`uri-id`
LEFT JOIN `item-uri` AS `thr-parent-item-uri` ON `thr-parent-item-uri`.`id` = `post-origin`.`thr-parent-id`
LEFT JOIN `item-uri` AS `parent-item-uri` ON `parent-item-uri`.`id` = `post-origin`.`parent-uri-id`
LEFT JOIN `item-uri` AS `conversation-item-uri` ON `conversation-item-uri`.`id` = `post-thread-user`.`conversation-id`
LEFT JOIN `item-uri` AS `context-item-uri` ON `context-item-uri`.`id` = `post-thread-user`.`context-id`
LEFT JOIN `item-uri` AS `external-item-uri` ON `external-item-uri`.`id` = `post-user`.`external-id`
LEFT JOIN `item-uri` AS `replies-item-uri` ON `replies-item-uri`.`id` = `post-user`.`replies-id`
LEFT JOIN `verb` ON `verb`.`id` = `post-origin`.`vid`
LEFT JOIN `event` ON `event`.`id` = `post-user`.`event-id`
LEFT JOIN `diaspora-interaction` ON `diaspora-interaction`.`uri-id` = `post-origin`.`uri-id`
LEFT JOIN `post-content` ON `post-content`.`uri-id` = `post-origin`.`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-origin`.`uri-id`
LEFT JOIN `post-question` ON `post-question`.`uri-id` = `post-origin`.`uri-id`
LEFT JOIN `permissionset` ON `permissionset`.`id` = `post-user`.`psid`
LEFT JOIN `contact` AS `parent-post-author` ON `parent-post-author`.`id` = `post-thread-user`.`author-id`;
--
-- VIEW post-thread-origin-view
--
DROP VIEW IF EXISTS `post-thread-origin-view`;
CREATE VIEW `post-thread-origin-view` AS SELECT
`post-origin`.`id` AS `id`,
`post-origin`.`id` AS `post-user-id`,
`post-origin`.`uid` AS `uid`,
`post-thread-user`.`post-user-id` AS `parent`,
`item-uri`.`uri` AS `uri`,
`post-origin`.`uri-id` AS `uri-id`,
`parent-item-uri`.`uri` AS `parent-uri`,
`post-origin`.`parent-uri-id` AS `parent-uri-id`,
`thr-parent-item-uri`.`uri` AS `thr-parent`,
`post-origin`.`thr-parent-id` AS `thr-parent-id`,
`conversation-item-uri`.`uri` AS `conversation`,
`post-thread-user`.`conversation-id` AS `conversation-id`,
`context-item-uri`.`uri` AS `context`,
`post-thread-user`.`context-id` AS `context-id`,
`quote-item-uri`.`uri` AS `quote-uri`,
`post-content`.`quote-uri-id` AS `quote-uri-id`,
`item-uri`.`guid` AS `guid`,
`post-origin`.`wall` AS `wall`,
`post-origin`.`gravity` AS `gravity`,
`external-item-uri`.`uri` AS `extid`,
`post-user`.`external-id` AS `external-id`,
`replies-item-uri`.`uri` AS `replies`,
`post-user`.`replies-id` AS `replies-id`,
`post-origin`.`created` AS `created`,
`post-user`.`edited` AS `edited`,
`post-thread-user`.`commented` AS `commented`,
`post-origin`.`received` AS `received`,
`post-thread-user`.`changed` AS `changed`,
`post-user`.`post-type` AS `post-type`,
`post-user`.`post-reason` AS `post-reason`,
`post-origin`.`private` AS `private`,
`post-thread-user`.`pubmail` AS `pubmail`,
`post-thread-user`.`ignored` AS `ignored`,
`post-user`.`visible` AS `visible`,
`post-thread-user`.`starred` AS `starred`,
`post-thread-user`.`unseen` AS `unseen`,
`post-user`.`deleted` AS `deleted`,
true AS `origin`,
`post-thread-user`.`mention` AS `mention`,
`post-user`.`global` AS `global`,
EXISTS(SELECT `type` FROM `post-collection` WHERE `type` = 0 AND `uri-id` = `post-thread-user`.`uri-id`) AS `featured`,
`post-thread-user`.`network` AS `network`,
`post-origin`.`vid` AS `vid`,
`post-thread-user`.`psid` AS `psid`,
IF (`post-origin`.`vid` IS NULL, '', `verb`.`name`) AS `verb`,
`post-content`.`title` AS `title`,
`post-content`.`content-warning` AS `content-warning`,
`post-content`.`raw-body` AS `raw-body`,
`post-content`.`body` AS `body`,
`post-content`.`rendered-hash` AS `rendered-hash`,
`post-content`.`rendered-html` AS `rendered-html`,
`post-content`.`language` AS `language`,
`post-content`.`plink` AS `plink`,
`post-content`.`location` AS `location`,
`post-content`.`coord` AS `coord`,
`post-content`.`sensitive` AS `sensitive`,
`post-user`.`restrictions` AS `restrictions`,
`post-content`.`app` AS `app`,
`post-content`.`object-type` AS `object-type`,
`post-content`.`object` AS `object`,
`post-content`.`target-type` AS `target-type`,
`post-content`.`target` AS `target`,
`post-content`.`resource-id` AS `resource-id`,
`post-thread-user`.`contact-id` AS `contact-id`,
`contact`.`uri-id` AS `contact-uri-id`,
`contact`.`url` AS `contact-link`,
`contact`.`addr` AS `contact-addr`,
`contact`.`name` AS `contact-name`,
`contact`.`nick` AS `contact-nick`,
`contact`.`thumb` AS `contact-avatar`,
`contact`.`network` AS `contact-network`,
`contact`.`blocked` AS `contact-blocked`,
`contact`.`hidden` AS `contact-hidden`,
`contact`.`readonly` AS `contact-readonly`,
`contact`.`archive` AS `contact-archive`,
`contact`.`pending` AS `contact-pending`,
`contact`.`rel` AS `contact-rel`,
`contact`.`uid` AS `contact-uid`,
`contact`.`gsid` AS `contact-gsid`,
`contact`.`contact-type` AS `contact-contact-type`,
IF (`post-user`.`network` IN ('apub', 'dfrn', 'dspr', 'stat'), true, `contact`.`writable`) AS `writable`,
`contact`.`self` AS `self`,
`contact`.`id` AS `cid`,
`contact`.`alias` AS `alias`,
`contact`.`photo` AS `photo`,
`contact`.`name-date` AS `name-date`,
`contact`.`uri-date` AS `uri-date`,
`contact`.`avatar-date` AS `avatar-date`,
`contact`.`thumb` AS `thumb`,
`post-thread-user`.`author-id` AS `author-id`,
`author`.`uri-id` AS `author-uri-id`,
`author`.`url` AS `author-link`,
`author`.`addr` AS `author-addr`,
IF (`contact`.`url` = `author`.`url` AND `contact`.`name` != '', `contact`.`name`, `author`.`name`) AS `author-name`,
`author`.`nick` AS `author-nick`,
`author`.`alias` AS `author-alias`,
IF (`contact`.`url` = `author`.`url` AND `contact`.`thumb` != '', `contact`.`thumb`, `author`.`thumb`) AS `author-avatar`,
`author`.`network` AS `author-network`,
`author`.`blocked` AS `author-blocked`,
`author`.`hidden` AS `author-hidden`,
`author`.`updated` AS `author-updated`,
`author`.`contact-type` AS `author-contact-type`,
`author`.`gsid` AS `author-gsid`,
`post-thread-user`.`owner-id` AS `owner-id`,
`owner`.`uri-id` AS `owner-uri-id`,
`owner`.`url` AS `owner-link`,
`owner`.`addr` AS `owner-addr`,
IF (`contact`.`url` = `owner`.`url` AND `contact`.`name` != '', `contact`.`name`, `owner`.`name`) AS `owner-name`,
`owner`.`nick` AS `owner-nick`,
`owner`.`alias` AS `owner-alias`,
IF (`contact`.`url` = `owner`.`url` AND `contact`.`thumb` != '', `contact`.`thumb`, `owner`.`thumb`) AS `owner-avatar`,
`owner`.`network` AS `owner-network`,
`owner`.`blocked` AS `owner-blocked`,
`owner`.`hidden` AS `owner-hidden`,
`owner`.`updated` AS `owner-updated`,
`owner`.`gsid` AS `owner-gsid`,
`owner`.`contact-type` AS `owner-contact-type`,
`post-thread-user`.`causer-id` AS `causer-id`,
`causer`.`uri-id` AS `causer-uri-id`,
`causer`.`url` AS `causer-link`,
`causer`.`addr` AS `causer-addr`,
`causer`.`name` AS `causer-name`,
`causer`.`nick` AS `causer-nick`,
`causer`.`alias` AS `causer-alias`,
`causer`.`thumb` AS `causer-avatar`,
`causer`.`network` AS `causer-network`,
`causer`.`blocked` AS `causer-blocked`,
`causer`.`hidden` AS `causer-hidden`,
`causer`.`gsid` AS `causer-gsid`,
`causer`.`contact-type` AS `causer-contact-type`,
`post-delivery-data`.`postopts` AS `postopts`,
`post-delivery-data`.`inform` AS `inform`,
`post-delivery-data`.`queue_count` AS `delivery_queue_count`,
`post-delivery-data`.`queue_done` AS `delivery_queue_done`,
`post-delivery-data`.`queue_failed` AS `delivery_queue_failed`,
IF (`post-thread-user`.`psid` IS NULL, '', `permissionset`.`allow_cid`) AS `allow_cid`,
IF (`post-thread-user`.`psid` IS NULL, '', `permissionset`.`allow_gid`) AS `allow_gid`,
IF (`post-thread-user`.`psid` IS NULL, '', `permissionset`.`deny_cid`) AS `deny_cid`,
IF (`post-thread-user`.`psid` IS NULL, '', `permissionset`.`deny_gid`) AS `deny_gid`,
`post-user`.`event-id` AS `event-id`,
`event`.`created` AS `event-created`,
`event`.`edited` AS `event-edited`,
`event`.`start` AS `event-start`,
`event`.`finish` AS `event-finish`,
`event`.`summary` AS `event-summary`,
`event`.`desc` AS `event-desc`,
`event`.`location` AS `event-location`,
`event`.`type` AS `event-type`,
`event`.`nofinish` AS `event-nofinish`,
`event`.`ignore` AS `event-ignore`,
`post-question`.`id` AS `question-id`,
`post-question`.`multiple` AS `question-multiple`,
`post-question`.`voters` AS `question-voters`,
`post-question`.`end-time` AS `question-end-time`,
EXISTS(SELECT `uri-id` FROM `post-category` WHERE `post-category`.`uri-id` = `post-thread-user`.`uri-id` AND `post-category`.`uid` = `post-thread-user`.`uid`) AS `has-categories`,
EXISTS(SELECT `id` FROM `post-media` WHERE `post-media`.`uri-id` = `post-thread-user`.`uri-id`) AS `has-media`,
`diaspora-interaction`.`interaction` AS `signed_text`,
`parent-item-uri`.`guid` AS `parent-guid`,
`post-thread-user`.`network` AS `parent-network`,
`post-thread-user`.`author-id` AS `parent-author-id`,
`author`.`url` AS `parent-author-link`,
`author`.`name` AS `parent-author-name`,
`author`.`nick` AS `parent-author-nick`,
`author`.`network` AS `parent-author-network`
FROM `post-origin`
INNER JOIN `post-thread-user` ON `post-thread-user`.`uri-id` = `post-origin`.`uri-id` AND `post-thread-user`.`uid` = `post-origin`.`uid`
INNER JOIN `post-user` ON `post-user`.`id` = `post-origin`.`id`
STRAIGHT_JOIN `contact` ON `contact`.`id` = `post-thread-user`.`contact-id`
STRAIGHT_JOIN `contact` AS `author` ON `author`.`id` = `post-thread-user`.`author-id`
STRAIGHT_JOIN `contact` AS `owner` ON `owner`.`id` = `post-thread-user`.`owner-id`
LEFT JOIN `contact` AS `causer` ON `causer`.`id` = `post-thread-user`.`causer-id`
LEFT JOIN `item-uri` ON `item-uri`.`id` = `post-origin`.`uri-id`
LEFT JOIN `item-uri` AS `thr-parent-item-uri` ON `thr-parent-item-uri`.`id` = `post-origin`.`thr-parent-id`
LEFT JOIN `item-uri` AS `parent-item-uri` ON `parent-item-uri`.`id` = `post-origin`.`parent-uri-id`
LEFT JOIN `item-uri` AS `conversation-item-uri` ON `conversation-item-uri`.`id` = `post-thread-user`.`conversation-id`
LEFT JOIN `item-uri` AS `context-item-uri` ON `context-item-uri`.`id` = `post-thread-user`.`context-id`
LEFT JOIN `item-uri` AS `external-item-uri` ON `external-item-uri`.`id` = `post-user`.`external-id`
LEFT JOIN `item-uri` AS `replies-item-uri` ON `replies-item-uri`.`id` = `post-user`.`replies-id`
LEFT JOIN `verb` ON `verb`.`id` = `post-origin`.`vid`
LEFT JOIN `event` ON `event`.`id` = `post-user`.`event-id`
LEFT JOIN `diaspora-interaction` ON `diaspora-interaction`.`uri-id` = `post-origin`.`uri-id`
LEFT JOIN `post-content` ON `post-content`.`uri-id` = `post-origin`.`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-origin`.`uri-id`
LEFT JOIN `post-question` ON `post-question`.`uri-id` = `post-origin`.`uri-id`
LEFT JOIN `permissionset` ON `permissionset`.`id` = `post-thread-user`.`psid`;
--
-- VIEW post-user-view
--
@ -2159,6 +2673,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`,
`context-item-uri`.`uri` AS `context`,
`post-thread-user`.`context-id` AS `context-id`,
`quote-item-uri`.`uri` AS `quote-uri`,
`post-content`.`quote-uri-id` AS `quote-uri-id`,
`item-uri`.`guid` AS `guid`,
@ -2166,6 +2682,8 @@ CREATE VIEW `post-user-view` AS SELECT
`post-user`.`gravity` AS `gravity`,
`external-item-uri`.`uri` AS `extid`,
`post-user`.`external-id` AS `external-id`,
`replies-item-uri`.`uri` AS `replies`,
`post-user`.`replies-id` AS `replies-id`,
`post-user`.`created` AS `created`,
`post-user`.`edited` AS `edited`,
`post-thread-user`.`commented` AS `commented`,
@ -2200,6 +2718,7 @@ CREATE VIEW `post-user-view` AS SELECT
`post-content`.`location` AS `location`,
`post-content`.`coord` AS `coord`,
`post-content`.`sensitive` AS `sensitive`,
`post-user`.`restrictions` AS `restrictions`,
`post-content`.`app` AS `app`,
`post-content`.`object-type` AS `object-type`,
`post-content`.`object` AS `object`,
@ -2317,7 +2836,9 @@ CREATE VIEW `post-user-view` AS SELECT
LEFT JOIN `item-uri` AS `thr-parent-item-uri` ON `thr-parent-item-uri`.`id` = `post-user`.`thr-parent-id`
LEFT JOIN `item-uri` AS `parent-item-uri` ON `parent-item-uri`.`id` = `post-user`.`parent-uri-id`
LEFT JOIN `item-uri` AS `conversation-item-uri` ON `conversation-item-uri`.`id` = `post-thread-user`.`conversation-id`
LEFT JOIN `item-uri` AS `context-item-uri` ON `context-item-uri`.`id` = `post-thread-user`.`context-id`
LEFT JOIN `item-uri` AS `external-item-uri` ON `external-item-uri`.`id` = `post-user`.`external-id`
LEFT JOIN `item-uri` AS `replies-item-uri` ON `replies-item-uri`.`id` = `post-user`.`replies-id`
LEFT JOIN `verb` ON `verb`.`id` = `post-user`.`vid`
LEFT JOIN `event` ON `event`.`id` = `post-user`.`event-id`
LEFT JOIN `diaspora-interaction` ON `diaspora-interaction`.`uri-id` = `post-user`.`uri-id`
@ -2345,6 +2866,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`,
`context-item-uri`.`uri` AS `context`,
`post-thread-user`.`context-id` AS `context-id`,
`quote-item-uri`.`uri` AS `quote-uri`,
`post-content`.`quote-uri-id` AS `quote-uri-id`,
`item-uri`.`guid` AS `guid`,
@ -2352,6 +2875,8 @@ CREATE VIEW `post-thread-user-view` AS SELECT
`post-user`.`gravity` AS `gravity`,
`external-item-uri`.`uri` AS `extid`,
`post-user`.`external-id` AS `external-id`,
`replies-item-uri`.`uri` AS `replies`,
`post-user`.`replies-id` AS `replies-id`,
`post-thread-user`.`created` AS `created`,
`post-user`.`edited` AS `edited`,
`post-thread-user`.`commented` AS `commented`,
@ -2385,6 +2910,7 @@ CREATE VIEW `post-thread-user-view` AS SELECT
`post-content`.`location` AS `location`,
`post-content`.`coord` AS `coord`,
`post-content`.`sensitive` AS `sensitive`,
`post-user`.`restrictions` AS `restrictions`,
`post-content`.`app` AS `app`,
`post-content`.`object-type` AS `object-type`,
`post-content`.`object` AS `object`,
@ -2502,7 +3028,9 @@ CREATE VIEW `post-thread-user-view` AS SELECT
LEFT JOIN `item-uri` AS `thr-parent-item-uri` ON `thr-parent-item-uri`.`id` = `post-user`.`thr-parent-id`
LEFT JOIN `item-uri` AS `parent-item-uri` ON `parent-item-uri`.`id` = `post-user`.`parent-uri-id`
LEFT JOIN `item-uri` AS `conversation-item-uri` ON `conversation-item-uri`.`id` = `post-thread-user`.`conversation-id`
LEFT JOIN `item-uri` AS `context-item-uri` ON `context-item-uri`.`id` = `post-thread-user`.`context-id`
LEFT JOIN `item-uri` AS `external-item-uri` ON `external-item-uri`.`id` = `post-user`.`external-id`
LEFT JOIN `item-uri` AS `replies-item-uri` ON `replies-item-uri`.`id` = `post-user`.`replies-id`
LEFT JOIN `verb` ON `verb`.`id` = `post-user`.`vid`
LEFT JOIN `event` ON `event`.`id` = `post-user`.`event-id`
LEFT JOIN `diaspora-interaction` ON `diaspora-interaction`.`uri-id` = `post-thread-user`.`uri-id`
@ -2525,12 +3053,16 @@ 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`,
`context-item-uri`.`uri` AS `context`,
`post-thread`.`context-id` AS `context-id`,
`quote-item-uri`.`uri` AS `quote-uri`,
`post-content`.`quote-uri-id` AS `quote-uri-id`,
`item-uri`.`guid` AS `guid`,
`post`.`gravity` AS `gravity`,
`external-item-uri`.`uri` AS `extid`,
`post`.`external-id` AS `external-id`,
`replies-item-uri`.`uri` AS `replies`,
`post`.`replies-id` AS `replies-id`,
`post`.`created` AS `created`,
`post`.`edited` AS `edited`,
`post-thread`.`commented` AS `commented`,
@ -2651,7 +3183,9 @@ CREATE VIEW `post-view` AS SELECT
LEFT JOIN `item-uri` AS `thr-parent-item-uri` ON `thr-parent-item-uri`.`id` = `post`.`thr-parent-id`
LEFT JOIN `item-uri` AS `parent-item-uri` ON `parent-item-uri`.`id` = `post`.`parent-uri-id`
LEFT JOIN `item-uri` AS `conversation-item-uri` ON `conversation-item-uri`.`id` = `post-thread`.`conversation-id`
LEFT JOIN `item-uri` AS `context-item-uri` ON `context-item-uri`.`id` = `post-thread`.`context-id`
LEFT JOIN `item-uri` AS `external-item-uri` ON `external-item-uri`.`id` = `post`.`external-id`
LEFT JOIN `item-uri` AS `replies-item-uri` ON `replies-item-uri`.`id` = `post`.`replies-id`
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`
@ -2672,12 +3206,16 @@ 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`,
`context-item-uri`.`uri` AS `context`,
`post-thread`.`context-id` AS `context-id`,
`quote-item-uri`.`uri` AS `quote-uri`,
`post-content`.`quote-uri-id` AS `quote-uri-id`,
`item-uri`.`guid` AS `guid`,
`post`.`gravity` AS `gravity`,
`external-item-uri`.`uri` AS `extid`,
`post`.`external-id` AS `external-id`,
`replies-item-uri`.`uri` AS `replies`,
`post`.`replies-id` AS `replies-id`,
`post-thread`.`created` AS `created`,
`post`.`edited` AS `edited`,
`post-thread`.`commented` AS `commented`,
@ -2800,7 +3338,9 @@ CREATE VIEW `post-thread-view` AS SELECT
LEFT JOIN `item-uri` AS `thr-parent-item-uri` ON `thr-parent-item-uri`.`id` = `post`.`thr-parent-id`
LEFT JOIN `item-uri` AS `parent-item-uri` ON `parent-item-uri`.`id` = `post`.`parent-uri-id`
LEFT JOIN `item-uri` AS `conversation-item-uri` ON `conversation-item-uri`.`id` = `post-thread`.`conversation-id`
LEFT JOIN `item-uri` AS `context-item-uri` ON `context-item-uri`.`id` = `post-thread`.`context-id`
LEFT JOIN `item-uri` AS `external-item-uri` ON `external-item-uri`.`id` = `post`.`external-id`
LEFT JOIN `item-uri` AS `replies-item-uri` ON `replies-item-uri`.`id` = `post`.`replies-id`
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`
@ -2876,36 +3416,6 @@ CREATE VIEW `tag-view` AS SELECT
LEFT JOIN `tag` ON `post-tag`.`tid` = `tag`.`id`
LEFT JOIN `contact` ON `post-tag`.`cid` = `contact`.`id`;
--
-- VIEW network-item-view
--
DROP VIEW IF EXISTS `network-item-view`;
CREATE VIEW `network-item-view` AS SELECT
`post-user`.`uri-id` AS `uri-id`,
`post-thread-user`.`post-user-id` AS `parent`,
`post-user`.`received` AS `received`,
`post-thread-user`.`commented` AS `commented`,
`post-user`.`created` AS `created`,
`post-user`.`uid` AS `uid`,
`post-thread-user`.`starred` AS `starred`,
`post-thread-user`.`mention` AS `mention`,
`post-user`.`network` AS `network`,
`post-user`.`unseen` AS `unseen`,
`post-user`.`gravity` AS `gravity`,
`post-user`.`contact-id` AS `contact-id`,
`ownercontact`.`contact-type` AS `contact-type`
FROM `post-user`
INNER JOIN `post-thread-user` ON `post-thread-user`.`uri-id` = `post-user`.`parent-uri-id` AND `post-thread-user`.`uid` = `post-user`.`uid`
STRAIGHT_JOIN `contact` ON `contact`.`id` = `post-thread-user`.`contact-id`
STRAIGHT_JOIN `contact` AS `authorcontact` ON `authorcontact`.`id` = `post-thread-user`.`author-id`
STRAIGHT_JOIN `contact` AS `ownercontact` ON `ownercontact`.`id` = `post-thread-user`.`owner-id`
WHERE `post-user`.`visible` AND NOT `post-user`.`deleted`
AND (NOT `contact`.`readonly` AND NOT `contact`.`blocked` AND NOT `contact`.`pending`)
AND (`post-user`.`hidden` IS NULL OR NOT `post-user`.`hidden`)
AND NOT `authorcontact`.`blocked` AND NOT `ownercontact`.`blocked`
AND NOT EXISTS(SELECT `cid` FROM `user-contact` WHERE `uid` = `post-thread-user`.`uid` AND `cid` IN (`authorcontact`.`id`, `ownercontact`.`id`) AND (`blocked` OR `ignored`))
AND NOT EXISTS(SELECT `gsid` FROM `user-gserver` WHERE `uid` = `post-thread-user`.`uid` AND `gsid` IN (`authorcontact`.`gsid`, `ownercontact`.`gsid`) AND `ignored`);
--
-- VIEW network-thread-view
--
@ -2931,8 +3441,36 @@ CREATE VIEW `network-thread-view` AS SELECT
AND (NOT `contact`.`readonly` AND NOT `contact`.`blocked` AND NOT `contact`.`pending`)
AND (`post-thread-user`.`hidden` IS NULL OR NOT `post-thread-user`.`hidden`)
AND NOT `authorcontact`.`blocked` AND NOT `ownercontact`.`blocked`
AND NOT EXISTS(SELECT `cid` FROM `user-contact` WHERE `uid` = `post-thread-user`.`uid` AND `cid` IN (`authorcontact`.`id`, `ownercontact`.`id`) AND (`blocked` OR `ignored`))
AND NOT EXISTS(SELECT `gsid` FROM `user-gserver` WHERE `uid` = `post-thread-user`.`uid` AND `gsid` IN (`authorcontact`.`gsid`, `ownercontact`.`gsid`) AND `ignored`);
AND NOT EXISTS(SELECT `cid` FROM `user-contact` WHERE `uid` = `post-thread-user`.`uid` AND `cid` IN (`post-thread-user`.`author-id`, `post-thread-user`.`owner-id`, `post-thread-user`.`causer-id`) AND (`blocked` OR `ignored` OR `channel-only`))
AND NOT EXISTS(SELECT `gsid` FROM `user-gserver` WHERE `uid` = `post-thread-user`.`uid` AND `gsid` IN (`authorcontact`.`gsid`, `ownercontact`.`gsid`) AND `ignored`);
--
-- VIEW network-thread-circle-view
--
DROP VIEW IF EXISTS `network-thread-circle-view`;
CREATE VIEW `network-thread-circle-view` AS SELECT
`post-thread-user`.`uri-id` AS `uri-id`,
`post-thread-user`.`post-user-id` AS `parent`,
`post-thread-user`.`received` AS `received`,
`post-thread-user`.`commented` AS `commented`,
`post-thread-user`.`created` AS `created`,
`post-thread-user`.`uid` AS `uid`,
`post-thread-user`.`starred` AS `starred`,
`post-thread-user`.`mention` AS `mention`,
`post-thread-user`.`network` AS `network`,
`post-thread-user`.`contact-id` AS `contact-id`,
`ownercontact`.`contact-type` AS `contact-type`
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`
STRAIGHT_JOIN `contact` AS `authorcontact` ON `authorcontact`.`id` = `post-thread-user`.`author-id`
STRAIGHT_JOIN `contact` AS `ownercontact` ON `ownercontact`.`id` = `post-thread-user`.`owner-id`
WHERE `post-user`.`visible` AND NOT `post-user`.`deleted`
AND (NOT `contact`.`readonly` AND NOT `contact`.`blocked` AND NOT `contact`.`pending`)
AND (`post-thread-user`.`hidden` IS NULL OR NOT `post-thread-user`.`hidden`)
AND NOT `authorcontact`.`blocked` AND NOT `ownercontact`.`blocked`
AND NOT EXISTS(SELECT `cid` FROM `user-contact` WHERE `uid` = `post-thread-user`.`uid` AND `cid` IN (`post-thread-user`.`author-id`, `post-thread-user`.`owner-id`, `post-thread-user`.`causer-id`) AND (`blocked` OR `ignored`))
AND NOT EXISTS(SELECT `gsid` FROM `user-gserver` WHERE `uid` = `post-thread-user`.`uid` AND `gsid` IN (`authorcontact`.`gsid`, `ownercontact`.`gsid`) AND `ignored`);
--
-- VIEW owner-view

View file

@ -850,10 +850,6 @@ Here is a complete list of all hook callbacks with file locations (as of 24-Sep-
Hook::callAll('register_account', $uid);
Hook::callAll('remove_user', $user);
### src/Module/Notifications/Ping.php
Hook::callAll('network_ping', $arr);
### src/Module/PermissionTooltip.php
Hook::callAll('lockview_content', $item);

View file

@ -23,7 +23,7 @@ Predefined Channels
* For you: Posts from contacts you interact with and who interact with you. In detail, it consists of:
* Posts from people you interact with on a more than average level.
* Posts from the accounts that you follow with a more than average number of interactions-
* Posts from the accounts that you follow with a more than average number of interactions.
* Posts from accounts where you activated "notify on new posts" or where you have set the channel frequency accordingly.
* Discover: Posts from contacts you don't follow, but that might be of interest for you to follow. In detail, it consists of:
* Posts from people you don't follow but you interact with on a more than average level.
@ -48,11 +48,11 @@ Each channel is defined by these values:
* Label: This value is mandatory and is used for the menu label.
* Description: A short description of the content. This can help to keep the overview, when you have got a lot of channels.
* Access Key: When you want to access this channel via an access key, you can define it here. Pay attention to not use an already used one.
* Circle: This defines the data source for this channel. By default it is set to the public timeline. There are some predefined values, like the accounts that you follow or the accounts that follow you. Also all of your circles can be selected.
* Circle: This defines the data source for this channel. By default it is set to the public timeline. There are some predefined values, like the accounts that you follow or the accounts that follow you. Also all of your circles can be selected.
* Include Tags: Comma separated list of tags. A post will be used when it contains any of the listed tags.
* Exclude Tags: Comma separated list of tags. If a post contain any of these tags, then it will not be part of nthis channel.
* Exclude Tags: Comma separated list of tags. If a post contain any of these tags, then it will not be part of this channel.
* Full Text Search: This can be used to include or exclude content, based on the content and some additional keywords. It uses the "boolean mode" operators from MariaDB: https://mariadb.com/kb/en/full-text-index-overview/#in-boolean-mode
* Images, Videos, Audio: When selected, you will see content with the selected media type. This can be combined. If none of these fields are checked, you will see any content, with or without attacked media.
* Images, Videos, Audio: When selected, you will see content with the selected media type. This can be combined. If none of these fields are checked, you will see any content, with or without attached media.
Additional keywords for the full text search
---
@ -61,8 +61,8 @@ Additionally to the search for content, there are keywords that can be used in t
Alternatives are presented with "|".
* from - Use "from:nickname" or "from:nickname@domain.tld" to search for posts from a specific author.
* to - Use "from:nickname" or "from:nickname@domain.tld" to search for posts with the given contact as receiver.
* group - Use "group:nickname" or "group:nickname@domain.tld" to search for group post of the given group.
* to - Use "to:nickname" or "to:nickname@domain.tld" to search for posts with the given contact as receiver.
* group - Use "group:nickname" or "group:nickname@domain.tld" to search for posts of the given group.
* application | relay - Use "application:nickname" or "application:nickname@domain.tld" to search for posts that had been reshared by the given relay application.
* server - Use "server:hostname" to search for posts from a specific server. In the case of group postings, the search text contains both the hostname of the group server and the author's hostname.
* source - The ActivityPub type of the post source. Use this for example to include or exclude group posts or posts from services (aka bots).
@ -93,7 +93,7 @@ Alternatives are presented with "|".
* visibility:public
* visibility:unlisted
* visibility:private
* language | lang - Use "language:code" to search for posts with the given language in the [ISO 639-1](https://en.wikipedia.org/wiki/ISO_639-1) format.
* language | lang - Use "language:code" to search for posts with the given language in the [ISO 639-1](https://en.wikipedia.org/wiki/ISO_639-1) format.
Remember that you can combine these kerywords.
So for example you can create a channel with all posts that talk about the Fediverse - that aren't posted in the Fediverse with the search terms: "fediverse -network:apub -network:dfrn"
Remember that you can combine these keywords.
So for example you can create a channel with all posts that talk about the Fediverse - that aren't posted in the Fediverse with the search terms: "fediverse -network:apub -network:dfrn"

View file

@ -1,5 +1,5 @@
Friendica Documentation and Resources
=====================================
Help
====
**User Manual**
@ -64,18 +64,18 @@ Friendica Documentation and Resources
* [Database schema documentation](help/database)
* [Class Autoloading](help/autoloader)
**External Resources**
**Links**
* Website: [https://friendi.ca](https://friendi.ca)
* Help Group: [@helpers@forum.friendi.ca](https://forum.friendi.ca/~helpers)
* XMPP: [support@forum.friendi.ca](xmpp:support@forum.friendi.ca?join)
* IRC: [https://web.libera.chat/?channels=#friendica](https://web.libera.chat/?channels=#friendica)
* Matrix: [https://matrix.to/#/#friendi.ca:matrix.org](https://matrix.to/#/#friendi.ca:matrix.org)
* Mailing List: [https://mailman.friendi.ca/mailman/listinfo/support-friendi.ca](http://mailman.friendi.ca/mailman/listinfo/support-friendi.ca)
* [Main Website](https://friendi.ca)
* Ways to get Support
* Friendica Support Group: [@helpers@forum.friendi.ca](https://forum.friendi.ca/~helpers)
* [Mailing List Archive](http://mailman.friendi.ca/mailman/listinfo/support-friendi.ca) you can subscribe to the list by sending an email to ``support-request(at)friendi.ca?subject=subscribe``
* Community chat rooms (the IRC, Matrix and XMPP rooms are bridged) these public chats are logged [from IRC](https://gnusociarg.nsupdate.info/2021/%23friendica/) and [Matrix](https://view.matrix.org/alias/%23friendi.ca:matrix.org/)
* XMPP/Jabber MUC: support(at)forum.friendi.ca
* IRC: #friendica at [libera.chat](https://web.libera.chat/?channels=#friendica)
* Matrix: [#friendi.ca](https://matrix.to/#/#friendi.ca:matrix.org) or [#friendica-en](https://matrix.to/#/#friendica-en:matrix.org) at matrix.org
**About**
* [Site/Version Info](friendica)
* [Friendica Credits](credits)
* [Server Information](friendica)
* [Terms of Service](tos)
* [Credits](credits)

View file

@ -30,7 +30,7 @@ Due to the large variety of operating systems and PHP platforms in existence we
* Apache with mod-rewrite enabled and "Options All" so you can use a local `.htaccess` file
* PHP 7.4+
* PHP *command line* access with register_argc_argv set to true in the php.ini file
* Curl, GD, GMP, PDO, mbstrings, MySQLi, hash, xml, zip, IntlChar and OpenSSL extensions
* Curl, GD, GMP, PDO, mbstrings, MySQLi, hash, xml, zip, IntlChar, IDN and OpenSSL extensions
* The POSIX module of PHP needs to be activated (e.g. [RHEL, CentOS](http://www.bigsoft.co.uk/blog/index.php/2014/12/08/posix-php-commands-not-working-under-centos-7) have disabled it)
* Some form of email server or email gateway such that PHP mail() works.
If you cannot set up your own email server, you can use the [phpmailer](https://github.com/friendica/friendica-addons/tree/develop/phpmailer) addon and use a remote SMTP server.

View file

@ -107,5 +107,7 @@ They think they are a friend.
You can also "block" a person.
This completely blocks communications with that person.
They may still be able to see your public posts, as can anybody in the world, but they cannot communicate with you directly.
Friendica will notify their server that you have blocked them, although normally that server should not notify them individually.
However, there are various simple ways they can deduce that they have been blocked if they investigate.
You can also delete a friend no matter what the friendship status - which completely removes everything relating to that person from your website.

View file

@ -68,6 +68,7 @@ Database Tables
| [post-history](help/database/db_post-history) | Post history |
| [post-link](help/database/db_post-link) | Post related external links |
| [post-media](help/database/db_post-media) | Attached media |
| [post-origin](help/database/db_post-origin) | Posts from local users |
| [post-question](help/database/db_post-question) | Question |
| [post-question-option](help/database/db_post-question-option) | Question option |
| [post-searchindex](help/database/db_post-searchindex) | Content for all posts |

View file

@ -6,37 +6,41 @@ Global servers
Fields
------
| Field | Description | Type | Null | Key | Default | Extra |
| --------------------- | -------------------------------------------------- | ---------------- | ---- | --- | ------------------- | -------------- |
| id | sequential ID | int unsigned | NO | PRI | NULL | auto_increment |
| url | | varbinary(383) | NO | | | |
| nurl | | varbinary(383) | NO | | | |
| version | | varchar(255) | NO | | | |
| site_name | | varchar(255) | NO | | | |
| info | | text | YES | | NULL | |
| register_policy | | tinyint | NO | | 0 | |
| registered-users | Number of registered users | int unsigned | NO | | 0 | |
| active-week-users | Number of active users in the last week | int unsigned | YES | | NULL | |
| active-month-users | Number of active users in the last month | int unsigned | YES | | NULL | |
| active-halfyear-users | Number of active users in the last six month | int unsigned | YES | | NULL | |
| local-posts | Number of local posts | int unsigned | YES | | NULL | |
| local-comments | Number of local comments | int unsigned | YES | | NULL | |
| directory-type | Type of directory service (Poco, Mastodon) | tinyint | YES | | 0 | |
| poco | | varbinary(383) | NO | | | |
| noscrape | | varbinary(383) | NO | | | |
| network | | char(4) | NO | | | |
| protocol | The protocol of the server | tinyint unsigned | YES | | NULL | |
| platform | | varchar(255) | NO | | | |
| relay-subscribe | Has the server subscribed to the relay system | boolean | NO | | 0 | |
| relay-scope | The scope of messages that the server wants to get | varchar(10) | NO | | | |
| detection-method | Method that had been used to detect that server | tinyint unsigned | YES | | NULL | |
| created | | datetime | NO | | 0001-01-01 00:00:00 | |
| last_poco_query | | datetime | YES | | 0001-01-01 00:00:00 | |
| last_contact | Last successful connection request | datetime | YES | | 0001-01-01 00:00:00 | |
| last_failure | Last failed connection request | datetime | YES | | 0001-01-01 00:00:00 | |
| blocked | Server is blocked | boolean | YES | | NULL | |
| failed | Connection failed | boolean | YES | | NULL | |
| next_contact | Next connection request | datetime | YES | | 0001-01-01 00:00:00 | |
| Field | Description | Type | Null | Key | Default | Extra |
| --------------------- | -------------------------------------------------------------- | ---------------- | ---- | --- | ------------------- | -------------- |
| id | sequential ID | int unsigned | NO | PRI | NULL | auto_increment |
| url | | varbinary(383) | NO | | | |
| nurl | | varbinary(383) | NO | | | |
| version | The version of this server software. | varchar(255) | NO | | | |
| site_name | | varchar(255) | NO | | | |
| info | | text | YES | | NULL | |
| register_policy | | tinyint | NO | | 0 | |
| registered-users | Number of registered users | int unsigned | NO | | 0 | |
| active-week-users | Number of active users in the last week | int unsigned | YES | | NULL | |
| active-month-users | Number of active users in the last month | int unsigned | YES | | NULL | |
| active-halfyear-users | Number of active users in the last six month | int unsigned | YES | | NULL | |
| local-posts | Number of local posts | int unsigned | YES | | NULL | |
| local-comments | Number of local comments | int unsigned | YES | | NULL | |
| directory-type | Type of directory service (Poco, Mastodon) | tinyint | YES | | 0 | |
| poco | | varbinary(383) | NO | | | |
| openwebauth | Path to the OpenWebAuth endpoint | varbinary(383) | YES | | NULL | |
| authredirect | Path to the authRedirect endpoint | varbinary(383) | YES | | NULL | |
| noscrape | | varbinary(383) | NO | | | |
| network | | char(4) | NO | | | |
| protocol | The protocol of the server | tinyint unsigned | YES | | NULL | |
| platform | The canonical name of this server software. | varchar(255) | NO | | | |
| repository | The url of the source code repository of this server software. | varbinary(383) | YES | | NULL | |
| homepage | The url of the homepage of this server software. | varbinary(383) | YES | | NULL | |
| relay-subscribe | Has the server subscribed to the relay system | boolean | NO | | 0 | |
| relay-scope | The scope of messages that the server wants to get | varchar(10) | NO | | | |
| detection-method | Method that had been used to detect that server | tinyint unsigned | YES | | NULL | |
| created | | datetime | NO | | 0001-01-01 00:00:00 | |
| last_poco_query | | datetime | YES | | 0001-01-01 00:00:00 | |
| last_contact | Last successful connection request | datetime | YES | | 0001-01-01 00:00:00 | |
| last_failure | Last failed connection request | datetime | YES | | 0001-01-01 00:00:00 | |
| blocked | Server is blocked | boolean | YES | | NULL | |
| failed | Connection failed | boolean | YES | | NULL | |
| next_contact | Next connection request | datetime | YES | | 0001-01-01 00:00:00 | |
Indexes
------------

View file

@ -6,22 +6,24 @@ Incoming activity
Fields
------
| Field | Description | Type | Null | Key | Default | Extra |
| ------------------ | -------------------------------------- | -------------- | ---- | --- | ------- | -------------- |
| id | sequential ID | int unsigned | NO | PRI | NULL | auto_increment |
| activity-id | id of the incoming activity | varbinary(383) | YES | | NULL | |
| object-id | | varbinary(383) | YES | | NULL | |
| in-reply-to-id | | varbinary(383) | YES | | NULL | |
| conversation | | varbinary(383) | YES | | NULL | |
| type | Type of the activity | varchar(64) | YES | | NULL | |
| object-type | Type of the object activity | varchar(64) | YES | | NULL | |
| object-object-type | Type of the object's object activity | varchar(64) | YES | | NULL | |
| received | Receiving date | datetime | YES | | NULL | |
| activity | The JSON activity | mediumtext | YES | | NULL | |
| signer | | varchar(255) | YES | | NULL | |
| push | Is the entry pushed or have pulled it? | boolean | YES | | NULL | |
| trust | Do we trust this entry? | boolean | YES | | NULL | |
| wid | Workerqueue id | int unsigned | YES | | NULL | |
| Field | Description | Type | Null | Key | Default | Extra |
| ------------------ | -------------------------------------- | ---------------- | ---- | --- | ------- | -------------- |
| id | sequential ID | int unsigned | NO | PRI | NULL | auto_increment |
| activity-id | id of the incoming activity | varbinary(383) | YES | | NULL | |
| object-id | | varbinary(383) | YES | | NULL | |
| in-reply-to-id | | varbinary(383) | YES | | NULL | |
| context | | varbinary(383) | YES | | NULL | |
| conversation | | varbinary(383) | YES | | NULL | |
| type | Type of the activity | varchar(64) | YES | | NULL | |
| object-type | Type of the object activity | varchar(64) | YES | | NULL | |
| object-object-type | Type of the object's object activity | varchar(64) | YES | | NULL | |
| received | Receiving date | datetime | YES | | NULL | |
| activity | The JSON activity | mediumtext | YES | | NULL | |
| signer | | varchar(255) | YES | | NULL | |
| push | Is the entry pushed or have pulled it? | boolean | YES | | NULL | |
| trust | Do we trust this entry? | boolean | YES | | NULL | |
| wid | Workerqueue id | int unsigned | YES | | NULL | |
| retrial | Retrial counter | tinyint unsigned | YES | | 0 | |
Indexes
------------

View file

@ -11,7 +11,7 @@ Fields
| uri-id | Id of the item-uri table entry that contains the item uri | int unsigned | NO | PRI | NULL | |
| owner-id | Item owner | int unsigned | NO | | 0 | |
| contact-type | Person, organisation, news, community, relay | tinyint | NO | | 0 | |
| media-type | Type of media in a bit array (1 = image, 2 = video, 4 = audio | tinyint | NO | | 0 | |
| media-type | Type of media in a bit array (1 = image, 2 = video, 4 = audio) | tinyint | NO | | 0 | |
| language | Language information about this post in the ISO 639-1 format | char(2) | YES | | NULL | |
| searchtext | Simplified text for the full text search | mediumtext | YES | | NULL | |
| size | Body size | int unsigned | YES | | NULL | |

View file

@ -0,0 +1,48 @@
Table post-origin
===========
Posts from local users
Fields
------
| Field | Description | Type | Null | Key | Default | Extra |
| ------------- | ------------------------------------------------------------ | ------------------ | ---- | --- | ------------------- | ----- |
| id | | int unsigned | NO | PRI | NULL | |
| uri-id | Id of the item-uri table entry that contains the item uri | int unsigned | NO | | NULL | |
| uid | Owner id which owns this copy of the item | mediumint unsigned | NO | | NULL | |
| parent-uri-id | Id of the item-uri table that contains the parent uri | int unsigned | YES | | NULL | |
| thr-parent-id | Id of the item-uri table that contains the thread parent uri | int unsigned | YES | | NULL | |
| created | Creation timestamp. | datetime | NO | | 0001-01-01 00:00:00 | |
| received | datetime | datetime | NO | | 0001-01-01 00:00:00 | |
| gravity | | tinyint unsigned | NO | | 0 | |
| vid | Id of the verb table entry that contains the activity verbs | smallint unsigned | YES | | NULL | |
| private | 0=public, 1=private, 2=unlisted | tinyint unsigned | NO | | 0 | |
| wall | This item was posted to the wall of uid | boolean | NO | | 0 | |
Indexes
------------
| Name | Fields |
| ----------------- | ------------------- |
| PRIMARY | id |
| uid_uri-id | UNIQUE, uid, uri-id |
| uri-id | uri-id |
| parent-uri-id | parent-uri-id |
| thr-parent-id | thr-parent-id |
| vid | vid |
| parent-uri-id_uid | parent-uri-id, uid |
| uid_wall_received | uid, wall, received |
Foreign Keys
------------
| Field | Target Table | Target Field |
|-------|--------------|--------------|
| uri-id | [item-uri](help/database/db_item-uri) | id |
| uid | [user](help/database/db_user) | uid |
| parent-uri-id | [item-uri](help/database/db_item-uri) | id |
| thr-parent-id | [item-uri](help/database/db_item-uri) | id |
| vid | [verb](help/database/db_verb) | id |
Return to [database documentation](help/database)

View file

@ -10,7 +10,7 @@ Fields
| ---------- | --------------------------------------------------------------------- | ------------ | ---- | --- | ------- | ----- |
| uri-id | Id of the item-uri table entry that contains the item uri | int unsigned | NO | PRI | NULL | |
| owner-id | Item owner | int unsigned | NO | | 0 | |
| media-type | Type of media in a bit array (1 = image, 2 = video, 4 = audio | tinyint | NO | | 0 | |
| media-type | Type of media in a bit array (1 = image, 2 = video, 4 = audio) | tinyint | NO | | 0 | |
| language | Language information about this post in the ISO 639-1 format | char(2) | YES | | NULL | |
| searchtext | Simplified text for the full text search | mediumtext | YES | | NULL | |
| size | Body size | int unsigned | YES | | NULL | |

View file

@ -9,6 +9,7 @@ Fields
| Field | Description | Type | Null | Key | Default | Extra |
| --------------- | ------------------------------------------------------------------------------------------------------- | ------------------ | ---- | --- | ------------------- | ----- |
| uri-id | Id of the item-uri table entry that contains the item uri | int unsigned | NO | PRI | NULL | |
| context-id | Id of the item-uri table entry that contains the endpoint for the context collection | int unsigned | YES | | NULL | |
| conversation-id | Id of the item-uri table entry that contains the conversation uri | int unsigned | YES | | NULL | |
| owner-id | Item owner | int unsigned | NO | | 0 | |
| author-id | Item author | int unsigned | NO | | 0 | |
@ -40,6 +41,7 @@ Indexes
| -------------------- | --------------------- |
| PRIMARY | uid, uri-id |
| uri-id | uri-id |
| context-id | context-id |
| conversation-id | conversation-id |
| owner-id | owner-id |
| author-id | author-id |
@ -68,6 +70,7 @@ Foreign Keys
| Field | Target Table | Target Field |
|-------|--------------|--------------|
| uri-id | [item-uri](help/database/db_item-uri) | id |
| context-id | [item-uri](help/database/db_item-uri) | id |
| conversation-id | [item-uri](help/database/db_item-uri) | id |
| owner-id | [contact](help/database/db_contact) | id |
| author-id | [contact](help/database/db_contact) | id |

View file

@ -9,6 +9,7 @@ Fields
| Field | Description | Type | Null | Key | Default | Extra |
| --------------- | ------------------------------------------------------------------------------------------------------- | ------------ | ---- | --- | ------------------- | ----- |
| uri-id | Id of the item-uri table entry that contains the item uri | int unsigned | NO | PRI | NULL | |
| context-id | Id of the item-uri table entry that contains the endpoint for the context collection | int unsigned | YES | | NULL | |
| conversation-id | Id of the item-uri table entry that contains the conversation uri | int unsigned | YES | | NULL | |
| owner-id | Item owner | int unsigned | NO | | 0 | |
| author-id | Item author | int unsigned | NO | | 0 | |
@ -25,6 +26,7 @@ Indexes
| Name | Fields |
| --------------- | --------------- |
| PRIMARY | uri-id |
| context-id | context-id |
| conversation-id | conversation-id |
| owner-id | owner-id |
| author-id | author-id |
@ -38,6 +40,7 @@ Foreign Keys
| Field | Target Table | Target Field |
|-------|--------------|--------------|
| uri-id | [item-uri](help/database/db_item-uri) | id |
| context-id | [item-uri](help/database/db_item-uri) | id |
| conversation-id | [item-uri](help/database/db_item-uri) | id |
| owner-id | [contact](help/database/db_contact) | id |
| author-id | [contact](help/database/db_contact) | id |

View file

@ -6,38 +6,40 @@ User specific post data
Fields
------
| Field | Description | Type | Null | Key | Default | Extra |
| ----------------- | --------------------------------------------------------------------------------- | ------------------ | ---- | --- | ------------------- | -------------- |
| 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 | |
| parent-uri-id | Id of the item-uri table that contains the parent uri | int unsigned | YES | | NULL | |
| thr-parent-id | Id of the item-uri table that contains the thread parent uri | int unsigned | YES | | NULL | |
| external-id | Id of the item-uri table entry that contains the external uri | int unsigned | YES | | NULL | |
| created | Creation timestamp. | datetime | NO | | 0001-01-01 00:00:00 | |
| edited | Date of last edit (default is created) | datetime | NO | | 0001-01-01 00:00:00 | |
| received | datetime | datetime | NO | | 0001-01-01 00:00:00 | |
| gravity | | tinyint unsigned | NO | | 0 | |
| network | Network from where the item comes from | char(4) | NO | | | |
| owner-id | Link to the contact table with uid=0 of the owner of this item | int unsigned | NO | | 0 | |
| author-id | Link to the contact table with uid=0 of the author of this item | int unsigned | NO | | 0 | |
| causer-id | Link to the contact table with uid=0 of the contact that caused the item creation | int unsigned | YES | | NULL | |
| post-type | Post type (personal note, image, article, ...) | tinyint unsigned | NO | | 0 | |
| post-reason | Reason why the post arrived at the user | tinyint unsigned | NO | | 0 | |
| vid | Id of the verb table entry that contains the activity verbs | smallint unsigned | YES | | NULL | |
| private | 0=public, 1=private, 2=unlisted | tinyint unsigned | NO | | 0 | |
| global | | boolean | NO | | 0 | |
| visible | | boolean | NO | | 0 | |
| deleted | item has been marked for deletion | boolean | NO | | 0 | |
| uid | Owner id which owns this copy of the item | mediumint unsigned | NO | | NULL | |
| protocol | Protocol used to deliver the item for this user | tinyint unsigned | YES | | NULL | |
| contact-id | contact.id | int unsigned | NO | | 0 | |
| event-id | Used to link to the event.id | int unsigned | YES | | NULL | |
| unseen | post has not been seen | boolean | NO | | 1 | |
| hidden | Marker to hide the post from the user | boolean | NO | | 0 | |
| notification-type | | smallint unsigned | NO | | 0 | |
| wall | This item was posted to the wall of uid | boolean | NO | | 0 | |
| origin | item originated at this site | boolean | NO | | 0 | |
| psid | ID of the permission set of this post | int unsigned | YES | | NULL | |
| Field | Description | Type | Null | Key | Default | Extra |
| ----------------- | ------------------------------------------------------------------------------------ | ------------------ | ---- | --- | ------------------- | -------------- |
| 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 | |
| parent-uri-id | Id of the item-uri table that contains the parent uri | int unsigned | YES | | NULL | |
| thr-parent-id | Id of the item-uri table that contains the thread parent uri | int unsigned | YES | | NULL | |
| external-id | Id of the item-uri table entry that contains the external uri | int unsigned | YES | | NULL | |
| replies-id | Id of the item-uri table entry that contains the endpoint for the replies collection | int unsigned | YES | | NULL | |
| created | Creation timestamp. | datetime | NO | | 0001-01-01 00:00:00 | |
| edited | Date of last edit (default is created) | datetime | NO | | 0001-01-01 00:00:00 | |
| received | datetime | datetime | NO | | 0001-01-01 00:00:00 | |
| gravity | | tinyint unsigned | NO | | 0 | |
| network | Network from where the item comes from | char(4) | NO | | | |
| owner-id | Link to the contact table with uid=0 of the owner of this item | int unsigned | NO | | 0 | |
| author-id | Link to the contact table with uid=0 of the author of this item | int unsigned | NO | | 0 | |
| causer-id | Link to the contact table with uid=0 of the contact that caused the item creation | int unsigned | YES | | NULL | |
| post-type | Post type (personal note, image, article, ...) | tinyint unsigned | NO | | 0 | |
| post-reason | Reason why the post arrived at the user | tinyint unsigned | NO | | 0 | |
| vid | Id of the verb table entry that contains the activity verbs | smallint unsigned | YES | | NULL | |
| private | 0=public, 1=private, 2=unlisted | tinyint unsigned | NO | | 0 | |
| restrictions | Bit array of post restrictions (1 = Reply, 2 = Like, 4 = Announce) | tinyint unsigned | YES | | NULL | |
| global | | boolean | NO | | 0 | |
| visible | | boolean | NO | | 0 | |
| deleted | item has been marked for deletion | boolean | NO | | 0 | |
| uid | Owner id which owns this copy of the item | mediumint unsigned | NO | | NULL | |
| protocol | Protocol used to deliver the item for this user | tinyint unsigned | YES | | NULL | |
| contact-id | contact.id | int unsigned | NO | | 0 | |
| event-id | Used to link to the event.id | int unsigned | YES | | NULL | |
| unseen | post has not been seen | boolean | NO | | 1 | |
| hidden | Marker to hide the post from the user | boolean | NO | | 0 | |
| notification-type | | smallint unsigned | NO | | 0 | |
| wall | This item was posted to the wall of uid | boolean | NO | | 0 | |
| origin | item originated at this site | boolean | NO | | 0 | |
| psid | ID of the permission set of this post | int unsigned | YES | | NULL | |
Indexes
------------
@ -50,6 +52,7 @@ Indexes
| parent-uri-id | parent-uri-id |
| thr-parent-id | thr-parent-id |
| external-id | external-id |
| replies-id | replies-id |
| owner-id | owner-id |
| author-id | author-id |
| causer-id | causer-id |
@ -76,6 +79,7 @@ Foreign Keys
| parent-uri-id | [item-uri](help/database/db_item-uri) | id |
| thr-parent-id | [item-uri](help/database/db_item-uri) | id |
| external-id | [item-uri](help/database/db_item-uri) | id |
| replies-id | [item-uri](help/database/db_item-uri) | id |
| owner-id | [contact](help/database/db_contact) | id |
| author-id | [contact](help/database/db_contact) | id |
| causer-id | [contact](help/database/db_contact) | id |

View file

@ -6,26 +6,27 @@ Structure for all posts
Fields
------
| Field | Description | Type | Null | Key | Default | Extra |
| ------------- | --------------------------------------------------------------------------------- | ----------------- | ---- | --- | ------------------- | ----- |
| uri-id | Id of the item-uri table entry that contains the item uri | int unsigned | NO | PRI | NULL | |
| parent-uri-id | Id of the item-uri table that contains the parent uri | int unsigned | YES | | NULL | |
| thr-parent-id | Id of the item-uri table that contains the thread parent uri | int unsigned | YES | | NULL | |
| external-id | Id of the item-uri table entry that contains the external uri | int unsigned | YES | | NULL | |
| created | Creation timestamp. | datetime | NO | | 0001-01-01 00:00:00 | |
| edited | Date of last edit (default is created) | datetime | NO | | 0001-01-01 00:00:00 | |
| received | datetime | datetime | NO | | 0001-01-01 00:00:00 | |
| gravity | | tinyint unsigned | NO | | 0 | |
| network | Network from where the item comes from | char(4) | NO | | | |
| owner-id | Link to the contact table with uid=0 of the owner of this item | int unsigned | NO | | 0 | |
| author-id | Link to the contact table with uid=0 of the author of this item | int unsigned | NO | | 0 | |
| causer-id | Link to the contact table with uid=0 of the contact that caused the item creation | int unsigned | YES | | NULL | |
| post-type | Post type (personal note, image, article, ...) | tinyint unsigned | NO | | 0 | |
| vid | Id of the verb table entry that contains the activity verbs | smallint unsigned | YES | | NULL | |
| private | 0=public, 1=private, 2=unlisted | tinyint unsigned | NO | | 0 | |
| global | | boolean | NO | | 0 | |
| visible | | boolean | NO | | 0 | |
| deleted | item has been marked for deletion | boolean | NO | | 0 | |
| Field | Description | Type | Null | Key | Default | Extra |
| ------------- | ------------------------------------------------------------------------------------ | ----------------- | ---- | --- | ------------------- | ----- |
| uri-id | Id of the item-uri table entry that contains the item uri | int unsigned | NO | PRI | NULL | |
| parent-uri-id | Id of the item-uri table that contains the parent uri | int unsigned | YES | | NULL | |
| thr-parent-id | Id of the item-uri table that contains the thread parent uri | int unsigned | YES | | NULL | |
| external-id | Id of the item-uri table entry that contains the external uri | int unsigned | YES | | NULL | |
| replies-id | Id of the item-uri table entry that contains the endpoint for the replies collection | int unsigned | YES | | NULL | |
| created | Creation timestamp. | datetime | NO | | 0001-01-01 00:00:00 | |
| edited | Date of last edit (default is created) | datetime | NO | | 0001-01-01 00:00:00 | |
| received | datetime | datetime | NO | | 0001-01-01 00:00:00 | |
| gravity | | tinyint unsigned | NO | | 0 | |
| network | Network from where the item comes from | char(4) | NO | | | |
| owner-id | Link to the contact table with uid=0 of the owner of this item | int unsigned | NO | | 0 | |
| author-id | Link to the contact table with uid=0 of the author of this item | int unsigned | NO | | 0 | |
| causer-id | Link to the contact table with uid=0 of the contact that caused the item creation | int unsigned | YES | | NULL | |
| post-type | Post type (personal note, image, article, ...) | tinyint unsigned | NO | | 0 | |
| vid | Id of the verb table entry that contains the activity verbs | smallint unsigned | YES | | NULL | |
| private | 0=public, 1=private, 2=unlisted | tinyint unsigned | NO | | 0 | |
| global | | boolean | NO | | 0 | |
| visible | | boolean | NO | | 0 | |
| deleted | item has been marked for deletion | boolean | NO | | 0 | |
Indexes
------------
@ -36,6 +37,7 @@ Indexes
| parent-uri-id | parent-uri-id |
| thr-parent-id | thr-parent-id |
| external-id | external-id |
| replies-id | replies-id |
| owner-id | owner-id |
| author-id | author-id |
| causer-id | causer-id |
@ -50,6 +52,7 @@ Foreign Keys
| parent-uri-id | [item-uri](help/database/db_item-uri) | id |
| thr-parent-id | [item-uri](help/database/db_item-uri) | id |
| external-id | [item-uri](help/database/db_item-uri) | id |
| replies-id | [item-uri](help/database/db_item-uri) | id |
| owner-id | [contact](help/database/db_contact) | id |
| author-id | [contact](help/database/db_contact) | id |
| causer-id | [contact](help/database/db_contact) | id |

View file

@ -6,29 +6,30 @@ User specific public contact data
Fields
------
| Field | Description | Type | Null | Key | Default | Extra |
| ------------------------- | ----------------------------------------------------------------------- | ------------------ | ---- | --- | ------- | ----- |
| cid | Contact id of the linked public contact | int unsigned | NO | PRI | 0 | |
| uid | User id | mediumint unsigned | NO | PRI | 0 | |
| uri-id | Id of the item-uri table entry that contains the contact url | int unsigned | YES | | NULL | |
| blocked | Contact is completely blocked for this user | boolean | YES | | NULL | |
| ignored | Posts from this contact are ignored | boolean | YES | | NULL | |
| collapsed | Posts from this contact are collapsed | boolean | YES | | NULL | |
| hidden | This contact is hidden from the others | boolean | YES | | NULL | |
| is-blocked | User is blocked by this contact | boolean | YES | | NULL | |
| channel-frequency | Controls the frequency of the appearance of this contact in channels | tinyint unsigned | YES | | NULL | |
| pending | | boolean | YES | | NULL | |
| rel | The kind of the relation between the user and the contact | tinyint unsigned | YES | | NULL | |
| info | | mediumtext | YES | | NULL | |
| notify_new_posts | | boolean | YES | | NULL | |
| remote_self | 0 => No mirroring, 1-2 => Mirror as own post, 3 => Mirror as reshare | tinyint unsigned | YES | | NULL | |
| fetch_further_information | 0 => None, 1 => Fetch information, 3 => Fetch keywords, 2 => Fetch both | tinyint unsigned | YES | | NULL | |
| ffi_keyword_denylist | | text | YES | | NULL | |
| subhub | | boolean | YES | | NULL | |
| hub-verify | | varbinary(383) | YES | | NULL | |
| protocol | Protocol of the contact | char(4) | YES | | NULL | |
| rating | Automatically detected feed poll frequency | tinyint | YES | | NULL | |
| priority | Feed poll priority | tinyint unsigned | YES | | NULL | |
| Field | Description | Type | Null | Key | Default | Extra |
| ------------------------- | -------------------------------------------------------------------------- | ------------------ | ---- | --- | ------- | ----- |
| cid | Contact id of the linked public contact | int unsigned | NO | PRI | 0 | |
| uid | User id | mediumint unsigned | NO | PRI | 0 | |
| uri-id | Id of the item-uri table entry that contains the contact url | int unsigned | YES | | NULL | |
| blocked | Contact is completely blocked for this user | boolean | YES | | NULL | |
| ignored | Posts from this contact are ignored | boolean | YES | | NULL | |
| collapsed | Posts from this contact are collapsed | boolean | YES | | NULL | |
| hidden | This contact is hidden from the others | boolean | YES | | NULL | |
| channel-only | This contact is displayed only in channels, but not in the network stream. | boolean | YES | | NULL | |
| is-blocked | User is blocked by this contact | boolean | YES | | NULL | |
| channel-frequency | Controls the frequency of the appearance of this contact in channels | tinyint unsigned | YES | | NULL | |
| pending | | boolean | YES | | NULL | |
| rel | The kind of the relation between the user and the contact | tinyint unsigned | YES | | NULL | |
| info | | mediumtext | YES | | NULL | |
| notify_new_posts | | boolean | YES | | NULL | |
| remote_self | 0 => No mirroring, 1-2 => Mirror as own post, 3 => Mirror as reshare | tinyint unsigned | YES | | NULL | |
| fetch_further_information | 0 => None, 1 => Fetch information, 3 => Fetch keywords, 2 => Fetch both | tinyint unsigned | YES | | NULL | |
| ffi_keyword_denylist | | text | YES | | NULL | |
| subhub | | boolean | YES | | NULL | |
| hub-verify | | varbinary(383) | YES | | NULL | |
| protocol | Protocol of the contact | char(4) | YES | | NULL | |
| rating | Automatically detected feed poll frequency | tinyint | YES | | NULL | |
| priority | Feed poll priority | tinyint unsigned | YES | | NULL | |
Indexes
------------

View file

@ -418,10 +418,6 @@ Eine komplette Liste aller Hook-Callbacks mit den zugehörigen Dateien (am 01-Ap
Hook::callAll('storage_instance', $data);
Hook::callAll('storage_config', $data);
### src/Module/Notifications/Ping.php
Hook::callAll('network_ping', $arr);
### src/Module/PermissionTooltip.php
Hook::callAll('lockview_content', $item);

99
doc/de/Channels.md Normal file
View file

@ -0,0 +1,99 @@
Kanäle (Channels)
=====
* [Home](help)
Kanäle sind eine Möglichkeit neue Inhalte zu finden, oder Inhalte anzuzeigen, die du sonst möglicherweise verpasst hättest.
Es gibt mehrere vordefinierte Kanäle und zusätzlich kannst du deine eigenen, basierend auf ein paar Regeln, erstellen.
Kanäle zeigen nur Beiträge aus den letzten 24 Stunden an. (Dieser Wert kann vom Administrator geändert werden.)
In den Anzeige-Einstellungen, im Bereich "Timelines", kannst du definieren, welche Kanäle und andere Timelines du im "Kanäle"-Widget auf der "Network"-Seite sehen möchtest und welche Kanäle in der Menüleiste oben auf der Seite erscheinen sollen.
Ebenfalls in den Anzeige-Einstellungen, im Bereich "Kanäle", kannst du alle die Sprachen einstellen, die du in deinen Kanälen sehen möchtest. Hier kannst du mehr als eine Sprache auswählen.
Auf der Profilseite kannst du die Kanal-Frequenz für jeden Kontakt definieren. Die Optionen sind:
* Standardhäufigkeit: Beiträge dieses Kontakts werden im "Für Dich"-Kanal angezeigt, wenn du häufig mit diesem Kontakt interagiert hast oder wenn ein Beitrag ein gewisses Maß an Interaktion erreicht hat.
* Alle Beiträge dieses Kontakts anzeigen: Alle Beiträge dieses Kontakts werden auf dem Kanal "Für Dich" erscheinen
* Zeige nur einige Beiträge an: Wenn ein Kontakt viele Beiträge in einem kurzen Zeitraum erstellt, reduziert diese Einstellung die Anzahl der angezeigten Beiträge in jedem Kanal.
* Zeige keine Beiträge an: Beiträge von diesem Kontakt werden in keinem Kanal angezeigt.
Voreingestellte Kanäle
---
* Für Dich: Beiträge von Kontakten mit denen du interagierst und die mit dir interagieren. Im Detail bestehend aus:
* Beiträge von Leuten, mit denen du überdurchschnittlich viel interagierst.
* Beiträge von Kontakten, denen du folgst und mit denen du überdurchschnittlich viel interagierst.
* Beiträge von Kontakten, bei denen du "Benachrichtigung bei neuen Beiträgen" aktiviert hast oder wo du die Kanalfrequenz entsprechend eingestellt hast.
* Entdecken: Beiträge von Kontakten denen du nicht folgst, aber denen zu folgen für dich interessant sein könnte. Im Detail bestehend aus:
* Beiträge von Leuten denen du nicht folgst, aber mit denen du überdurchschnittlich viel interagierst.
* Beiträge von Leuten denen du nicht folgst, aber die mit dir überdurchschnittlich viel interagieren.
* Beliebte Beiträge von Leuten denen du nicht folgst, aber mit denen du interagiert hast oder die mit dir interagiert haben.
* Angesagt: Beiträge mit überdurchschnittlich hoher Anzahl von Interaktionen.
* Sprache: Beiträge in deiner Sprache.
* Folgende: Beiträge von Leuten die dir folgen, aber denen du nicht folgst.
* Geteilt von teilenden: Beiträge von Kontakten denen die Leute folgen, denen du folgst.
* Ruhige teilende: Beiträge von Konten denen du folgst, aber die nicht sehr oft posten.
* Bilder: Beiträge mit Bildern.
* Audio: Beiträge mit Audio.
* Videos: Beiträge mit Videos.
Vom Benutzer eingestellte Kanäle
---
In den Einstellungen, unter "Kanäle", kannst du deine eigenen Kanäle erstellen.
Jeder Kanal wird durch diese Werte definiert:
* Bezeichnung: Dieses Feld ist notwendig und wird für die Kanalbezeichnung verwendet.
* Beschreibung: Eine kurze Beschreibung des Inhalts. Dies kann helfen den Überblick zu behalten, wenn du viele Kanäle hast.
* Zugriffsschlüssel: Wenn du auf diesen Kanal über einen Zugriffsschlüssel zugreifen willst, kannst du ihn hier festlegen. Achte darauf, dass du nicht einen bereits verwendeten Schlüssel benutzt.
* Circle/Kanal: Dies definiert die Datenquelle für diesen Kanal. Voreingestellt ist die Globale Gemeinschaft. Es gibt ein paar vorgegebene Werte, wie die Konten denen du folgst, oder die Kontakte, die dir folgen. Außerdem können alle deine Circles ausgewählt werden.
* Tags einschließen: Durch Kommata getrennte Liste von Tags. Ein Beitrag wird verwendet, wenn er eines der aufgeführten Tags enthält.
* Tags ausschließen: Durch Kommata getrennte Liste von Tags. Wenn ein Beitrag eines dieser Tags enthält, wird er nicht Teil dieses Kanals sein.
* Volltextsuche: Dies kann genutzt werden um Inhalte, basierend auf dem Inhalt und ein paar zusätzlichen Schlüsselwörtern, ein- oder auszuschließen. Es nutzt die "boolean mode"-Operatoren von MariaDB: https://mariadb.com/kb/en/full-text-index-overview/#in-boolean-mode
* Bilder, Videos, Audio: Wenn ausgewählt, wirst du Inhalte mit dem gewählten Medientyp sehen. Diese Optionen können kombiniert werden. Wenn keines dieser Felder ausgewählt wurde, wirst du alle Inhalte, mit oder ohne angefügten Medien, sehen.
Zusätzliche Schlüsselwörter für die Volltextsuche
---
Zusätzlich zu der Suche nach Inhalten, gibt es Schlüsselwörter, die in der Volltextsuche genutzt werden können.
Alternativen werden durch "|" dargestellt.
* from - Verwende "from:nickname" oder "from:nickname@domain.tld" um nach Beiträgen von einem bestimmten Autor zu suchen.
* to - Verwende "to:nickname" oder "to:nickname@domain.tld" um nach Beiträgen mit dem gegebenen Empfänger zu suchen.
* group - Verwende "group:nickname" oder "group:nickname@domain.tld" um nach Beiträgen aus der gegebenen Gruppe zu suchen.
* application | relay - Nutze "application:nickname" oder "application:nickname@domain.tld" um Beiträge zu finden, die von der gegebenen relay application geteilt wurden.
* server - Verwende "server:hostname" um Beiträge von einem bestimmten Server zu suchen. Im Falle eine Gruppen-Postings enthält der Suchtext beides, den Hostname des Gruppen-Servers und den Hostname des Autors.
* source - Der ActivityPub-Typ der Beitragsquelle. Nutze dies um beispielsweise Gruppenpostings oder Beiträge von Services (aka Bots) ein- oder auszuschließen.
* source:person - Der Beitrag wurde von einem regulären Nutzerkonto erstellt.
* source:organization - Der Beitrag wurde von einer Organisation erstellt.
* source:group - Dieser Beitrag wurde über eine Gruppe erstellt oder verteilt.
* source:service | source:news - Dieser Beitrag stammt aus einem 'service' Account. Dieser Quellen(source)-Typ wird oft genutzt um Bot Accounts zu markieren.
* source:application | source:relay - Dieser Beitrag wurde von einer Anwendung (application) erstellt. Dies wird im Fediverse höchstwahrscheinlich für die Beitragserstellung nicht genutzt.
* tag - Nutze "tag:tagname" um nach einem bestimmten tag (Schlagwort) zu suchen.
* media - Mit diesem Schlüsselwort kannst du nach angefügten Medien suchen.
* media:image | media:photo | media:picture - Dieser Beitrag enthält ein Bild
* media:video - Dieser Beitrag enthält ein Video
* media:audio - Dieser Beitrag enthält Audio
* media:card - Dieser Beitrag enthält eine Linkvorschau-'card'
* media:post - Dieser Beitrag verweist auf einen anderen Beitrag, was bedeutet, es ist ein zitierter Beitrag
* network | net - Verwende dies um Netzwerke in deinen Kanal einzuschließen oder von ihm auszuschließen.
* network:apub | network:activitypub - ActivityPub (verwendet von den Systemen im Fediverse)
* network:dfrn | network:friendica - altes Friendica-Protokoll. Heutzutage nutzt Friendica meist ActivityPub.
* network:dspr | network:diaspora - Das Diaspora-Protokoll wird hauptsächlich von Diaspora selbst genutzt. Ein paar andere Systeme unterstützen dieses Protokoll ebenfalls, wie Hubzilla, Socialhome or Ganggo.
* network:feed - RSS/Atom feeds
* network:mail - Mails die via IMAP importiert worden sind.
* network:stat | network:ostatus - Das OStatus-Protokoll wird hauptsächlich von alten GNU Social-Installationen genutzt.
* network:dscs | network:discourse - Beiträge, die über den Discourse connector empfangen werden.
* network:tmbl | network:tumblr - Beiträge, die über den Tumblr connector empfangen werden.
* network:bsky | network:bluesky - Beiträge, die über den Bluesky connector empfangen werden.
* platform - Benutze dies, um Plattformen in deinen Kanal einzuschließen, oder von ihm auszuschließen, d.h. "+platform:friendica". Im Falle eines Gruppen-Postings enthält der Suchtext beides, die Plattform des Gruppen-Servers und die Plattform des Autors.
* visibility - Du hast die Wahl zwischen verschiedenen Sichtbarkeiten. Du kannst nur die ungelisteten oder privaten Beiträge sehen, zu denen du Zugang hast.
* visibility:public - (öffentlich)
* visibility:unlisted - (ungelistet)
* visibility:private - (privat)
* language | lang - Verwende "language:code" um nach Beiträgen in der gewünschten Sprache (im [ISO 639-1](https://en.wikipedia.org/wiki/ISO_639-1) format) zu suchen.
Denke daran, dass du diese Schlüsselwörter kombinieren kannst.
So kannst du zum Beispiel einen Kanal erstellen, mit allen Beiträgen, die über das Fediverse sprechen, aber nicht im Fediverse veröffentlich wurden, mit diesen Suchbegriffen: "fediverse -network:apub -network:dfrn".

View file

@ -1,7 +1,7 @@
Friendica - Dokumentation und Ressourcen
=====================================
Hilfe
=====
**Inhalte**
**Dokumentation für Benutzer**
* Allgemeine Funktionen - Erste Schritte
* [Account - Basics](help/Account-Basics)
@ -17,7 +17,7 @@ Friendica - Dokumentation und Ressourcen
* [Circles und Privatsphäre](help/Circles-and-Privacy)
* [Tags und Erwähnungen](help/Tags-and-Mentions)
* [Community-Gruppen](help/Groups)
* [Channels](help/Channels)
* [Kanäle (Channels)](help/Channels)
* [Chats](help/Chats)
* Weiterführende Informationen
* [Account umziehen](help/Move-Account)
@ -60,18 +60,17 @@ Friendica - Dokumentation und Ressourcen
* [Translation of Friendica](help/translations) (EN)
* [Run tests](help/Tests) (EN)
**Externe Ressourcen**
**Links**
* [Haupt-Webseite](https://friendi.ca)
* Support Kanäle
* Friendica Support Gruppe: [@helpers@forum.friendi.ca](https://forum.friendi.ca/~helpers)
* [Mailing Listen Archiv](http://mailman.friendi.ca/mailman/listinfo/support-friendi.ca) zum Abonnieren der Liste eine E-Mail an ``support-request(at)friendi.ca?subject=subscribe`` senden
* Chats der Friendica Community (die IRC, Matrix und XMPP Räume sind mit einer Brücke verbunden) Logs dieser öffentlichen Chaträume können [hier aus dem IRC](https://gnusociarg.nsupdate.info/2021/%23frie) und [hier aus der Matrix](https://view.matrix.org/alias/%23friendi.ca:matrix.org/) gefunden werden.
* XMPP/Jabber MUC: support(at)forum.friendi.ca
* IRC: #friendica auf [libera.chat](https://web.libera.chat/?channels=#friendica)
* Matrix: [#friendi.ca](https://matrix.to/#/#friendi.ca:matrix.org) oder [#friendica-en](https://matrix.to/#/#friendica-en:matrix.org) auf matrix.org
* Website: [https://friendi.ca](https://friendi.ca)
* Help Group: [@helpers@forum.friendi.ca](https://forum.friendi.ca/~helpers)
* XMPP: [support@forum.friendi.ca](xmpp:support@forum.friendi.ca?join)
* IRC: [https://web.libera.chat/?channels=#friendica](https://web.libera.chat/?channels=#friendica)
* Matrix: [https://matrix.to/#/#friendi.ca:matrix.org](https://matrix.to/#/#friendi.ca:matrix.org)
* Mailing List: [https://mailman.friendi.ca/mailman/listinfo/support-friendi.ca](http://mailman.friendi.ca/mailman/listinfo/support-friendi.ca)
**Über diese Seite**
**Über**
* [Seite/Friendica-Version](friendica)
* [Mitwirkenden bei Friendica](credits)
* [Server Information](friendica)
* [Nutzungsbedingungen](tos)
* [Mitwirkende](credits)

View file

@ -27,7 +27,7 @@ Requirements
* Apache mit einer aktiverten mod-rewrite-Funktion und dem Eintrag "Options All", so dass du die lokale .htaccess-Datei nutzen kannst
* PHP 7.4+
* PHP *Kommandozeilen*-Zugang mit register_argc_argv auf "true" gesetzt in der php.ini-Datei
* Curl, GD, GMP, PDO, mbstrings, MySQLi, hash, xml, zip, IntlChar and OpenSSL-Erweiterung
* Curl, GD, GMP, PDO, mbstrings, MySQLi, hash, xml, zip, IntlChar, IDN und OpenSSL-Erweiterung
* Das POSIX Modul muss aktiviert sein ([CentOS, RHEL](http://www.bigsoft.co.uk/blog/index.php/2014/12/08/posix-php-commands-not-working-under-centos-7http://www.bigsoft.co.uk/blog/index.php/2014/12/08/posix-php-commands-not-working-under-centos-7) haben dies z.B. deaktiviert)
* Einen E-Mail Server, so dass PHP `mail()` funktioniert.
Wenn kein eigener E-Mail Server zur Verfügung steht, kann alternativ das [phpmailer](https://github.com/friendica/friendica-addons/tree/develop/phpmailer) Addon mit einem externen SMTP Account verwendet werden.

View file

@ -78,3 +78,9 @@ The following will compress */var/log/friendica* (assuming this is the location
daily
rotate 2
}
### Zabbix
To monitor the health status of your Friendica installation, you can use for example a tool like Zabbix. Please define 'stats_key' in your local.config.php in the 'system' section to be able to access the statistics page at /stats?key=your-defined-stats_key
The statistics contain data about the worker performance, the last cron call, number of reports, inbound and outbound packets, posts and comments.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 185 KiB

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 166 KiB

After

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 400 KiB

After

Width:  |  Height:  |  Size: 371 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1,011 KiB

After

Width:  |  Height:  |  Size: 707 KiB

View file

@ -45,7 +45,8 @@ use Friendica\Model\Post;
use Friendica\Network\HTTPException;
use Friendica\Util\DateTimeFormat;
function item_post(App $a) {
function item_post()
{
$uid = DI::userSession()->getLocalUserId();
if (!$uid) {
@ -233,13 +234,15 @@ function item_insert(int $uid, array $request, bool $preview, string $return_pat
function item_process(array $post, array $request, bool $preview, string $return_path): array
{
$post['self'] = true;
$post['api_source'] = false;
$post['attach'] = '';
$post['title'] = trim($request['title'] ?? '');
$post['body'] = $request['body'] ?? '';
$post['location'] = trim($request['location'] ?? '');
$post['coord'] = trim($request['coord'] ?? '');
$post['self'] = true;
$post['api_source'] = false;
$post['attach'] = '';
$post['title'] = trim($request['title'] ?? '');
$post['content-warning'] = trim($request['summary'] ?? '');
$post['sensitive'] = !empty($request['sensitive'] ?? false);
$post['body'] = $request['body'] ?? '';
$post['location'] = trim($request['location'] ?? '');
$post['coord'] = trim($request['coord'] ?? '');
$post = DI::contentItem()->addCategories($post, $request['category'] ?? '');
@ -248,7 +251,7 @@ function item_process(array $post, array $request, bool $preview, string $return
$post['body'] .= DI::contentItem()->storeAttachmentFromRequest($request);
}
$post = DI::contentItem()->finalizePost($post);
$post = DI::contentItem()->finalizePost($post, $preview);
if (!strlen($post['body'])) {
if ($preview) {
@ -279,13 +282,14 @@ function item_process(array $post, array $request, bool $preview, string $return
$post['body'] = BBCode::removeSharedData(Item::setHashtags($post['body']));
$post['writable'] = true;
$post['sensitive'] = false;
$post['post-reason'] = Item::PR_LOCAL;
$o = DI::conversation()->render([$post], Conversation::MODE_SEARCH, false, true);
System::jsonExit(['preview' => $o]);
}
Hook::callAll('post_local',$post);
Hook::callAll('post_local', $post);
unset($post['edit']);
unset($post['self']);

View file

@ -19,7 +19,6 @@
*
*/
use Friendica\App;
use Friendica\Content\Nav;
use Friendica\Content\Pager;
use Friendica\Content\Text\BBCode;
@ -34,7 +33,7 @@ use Friendica\Util\DateTimeFormat;
use Friendica\Util\Strings;
use Friendica\Util\Temporal;
function message_init(App $a)
function message_init()
{
$tabs = '';
@ -61,7 +60,7 @@ function message_init(App $a)
]);
}
function message_post(App $a)
function message_post()
{
if (!DI::userSession()->getLocalUserId()) {
DI::sysmsg()->addNotice(DI::l10n()->t('Permission denied.'));
@ -104,7 +103,7 @@ function message_post(App $a)
}
}
function message_content(App $a)
function message_content()
{
$o = '';
Nav::setSelected('messages');
@ -114,7 +113,7 @@ function message_content(App $a)
return Login::form();
}
$myprofile = DI::baseUrl() . '/profile/' . $a->getLoggedInUserNickname();
$myprofile = DI::baseUrl() . '/profile/' . DI::userSession()->getLocalUserNickname();
$tpl = Renderer::getMarkupTemplate('mail_head.tpl');
if (DI::args()->getArgc() > 1 && DI::args()->getArgv()[1] == 'new') {
@ -177,7 +176,7 @@ function message_content(App $a)
$tpl = Renderer::getMarkupTemplate('msg-header.tpl');
DI::page()['htmlhead'] .= Renderer::replaceMacros($tpl, [
'$nickname' => $a->getLoggedInUserNickname(),
'$nickname' => DI::userSession()->getLocalUserNickname(),
'$linkurl' => DI::l10n()->t('Please enter a link URL:')
]);
@ -282,7 +281,7 @@ function message_content(App $a)
$tpl = Renderer::getMarkupTemplate('msg-header.tpl');
DI::page()['htmlhead'] .= Renderer::replaceMacros($tpl, [
'$nickname' => $a->getLoggedInUserNickname(),
'$nickname' => DI::userSession()->getLocalUserNickname(),
'$linkurl' => DI::l10n()->t('Please enter a link URL:')
]);
@ -415,12 +414,10 @@ function get_messages(int $uid, int $start, int $limit): array
function render_messages(array $msg, string $t): string
{
$a = DI::app();
$tpl = Renderer::getMarkupTemplate($t);
$rslt = '';
$myprofile = DI::baseUrl() . '/profile/' . $a->getLoggedInUserNickname();
$myprofile = DI::baseUrl() . '/profile/' . DI::userSession()->getLocalUserNickname();
foreach ($msg as $rr) {
if ($rr['unknown']) {

View file

@ -46,7 +46,7 @@ function notes_content(App $a, bool $update = false)
return;
}
$o = BaseProfile::getTabsHTML('notes', true, $a->getLoggedInUserNickname(), false);
$o = BaseProfile::getTabsHTML('notes', true, DI::userSession()->getLocalUserNickname(), false);
if (!$update) {
$o .= '<h3>' . DI::l10n()->t('Personal Notes') . '</h3>';

View file

@ -19,11 +19,9 @@
*
*/
use Friendica\App;
use Friendica\Content\Nav;
use Friendica\Content\Pager;
use Friendica\Content\Text\BBCode;
use Friendica\Content\Widget;
use Friendica\Core\ACL;
use Friendica\Core\Addon;
use Friendica\Core\Hook;
@ -44,6 +42,7 @@ use Friendica\Module\BaseProfile;
use Friendica\Network\HTTPException;
use Friendica\Network\Probe;
use Friendica\Protocol\Activity;
use Friendica\Protocol\ActivityNamespace;
use Friendica\Security\Security;
use Friendica\Util\Crypto;
use Friendica\Util\DateTimeFormat;
@ -53,7 +52,7 @@ use Friendica\Util\Strings;
use Friendica\Util\Temporal;
use Friendica\Util\XML;
function photos_init(App $a)
function photos_init()
{
if (DI::config()->get('system', 'block_public') && !DI::userSession()->isAuthenticated()) {
return;
@ -67,8 +66,6 @@ function photos_init(App $a)
throw new HTTPException\NotFoundException(DI::l10n()->t('User not found.'));
}
$is_owner = (DI::userSession()->getLocalUserId() && (DI::userSession()->getLocalUserId() == $owner['uid']));
$albums = Photo::getAlbums($owner['uid']);
$albums_visible = ((intval($owner['hidewall']) && !DI::userSession()->isAuthenticated()) ? false : true);
@ -125,7 +122,7 @@ function photos_init(App $a)
return;
}
function photos_post(App $a)
function photos_post()
{
$user = User::getByNickname(DI::args()->getArgv()[1]);
if (!DBA::isResult($user)) {
@ -136,7 +133,7 @@ function photos_post(App $a)
$visitor = 0;
$page_owner_uid = intval($user['uid']);
$community_page = $user['page-flags'] == User::PAGE_FLAGS_COMMUNITY;
$community_page = in_array($user['page-flags'], [User::PAGE_FLAGS_COMMUNITY, User::PAGE_FLAGS_COMM_MAN]);
if (DI::userSession()->getLocalUserId() && (DI::userSession()->getLocalUserId() == $page_owner_uid)) {
$can_post = true;
@ -200,7 +197,7 @@ function photos_post(App $a)
// Update the photo albums cache
Photo::clearAlbumCache($page_owner_uid);
DI::baseUrl()->redirect('photos/' . $a->getLoggedInUserNickname() . '/album/' . bin2hex($newalbum));
DI::baseUrl()->redirect('photos/' . DI::userSession()->getLocalUserNickname() . '/album/' . bin2hex($newalbum));
return; // NOTREACHED
}
@ -412,7 +409,7 @@ function photos_post(App $a)
if (count($links)) {
foreach ($links as $link) {
if ($link['@attributes']['rel'] === 'http://webfinger.net/rel/profile-page') {
if ($link['@attributes']['rel'] === ActivityNamespace::WEBFINGERPROFILE) {
$profile = $link['@attributes']['href'];
}
@ -559,7 +556,7 @@ function photos_post(App $a)
}
}
function photos_content(App $a)
function photos_content()
{
// URLs:
// photos/name/upload
@ -618,7 +615,7 @@ function photos_content(App $a)
$owner_uid = $user['uid'];
$community_page = (($user['page-flags'] == User::PAGE_FLAGS_COMMUNITY) ? true : false);
$community_page = in_array($user['page-flags'], [User::PAGE_FLAGS_COMMUNITY, User::PAGE_FLAGS_COMM_MAN]);
if (DI::userSession()->getLocalUserId() && (DI::userSession()->getLocalUserId() == $owner_uid)) {
$can_post = true;
@ -672,18 +669,14 @@ function photos_content(App $a)
$selname = (!is_null($datum) && Strings::isHex($datum)) ? hex2bin($datum) : '';
$albumselect = '';
$albumselect = ['' => '<current year>'];
$albumselect .= '<option value="" ' . (!$selname ? ' selected="selected" ' : '') . '>&lt;current year&gt;</option>';
$albums = Photo::getAlbums($owner_uid);
if (!empty($albums)) {
foreach ($albums as $album) {
if ($album['album'] === '') {
continue;
}
$selected = (($selname === $album['album']) ? ' selected="selected" ' : '');
$albumselect .= '<option value="' . $album['album'] . '"' . $selected . '>' . $album['album'] . '</option>';
foreach (Photo::getAlbums($owner_uid) as $album) {
if ($album['album'] === '') {
continue;
}
$albumselect[$album['album']] = $album['album'];
}
$uploader = '';
@ -718,7 +711,7 @@ function photos_content(App $a)
$tpl = Renderer::getMarkupTemplate('photos_upload.tpl');
$aclselect_e = ($visitor ? '' : ACL::getFullSelectorHTML(DI::page(), $a->getLoggedInUserId()));
$aclselect_e = ($visitor ? '' : ACL::getFullSelectorHTML(DI::page(), DI::userSession()->getLocalUserId()));
$o .= Renderer::replaceMacros($tpl, [
'$pagename' => DI::l10n()->t('Upload Photos'),
@ -729,9 +722,10 @@ function photos_content(App $a)
'$existalbumtext' => DI::l10n()->t('or select existing album:'),
'$nosharetext' => DI::l10n()->t('Do not show a status post for this upload'),
'$albumselect' => $albumselect,
'$selname' => $selname,
'$permissions' => DI::l10n()->t('Permissions'),
'$aclselect' => $aclselect_e,
'$lockstate' => ACL::getLockstateForUserId($a->getLoggedInUserId()) ? 'lock' : 'unlock',
'$lockstate' => ACL::getLockstateForUserId(DI::userSession()->getLocalUserId()) ? 'lock' : 'unlock',
'$alt_uploader' => $ret['addon_text'],
'$default_upload_box' => ($ret['default_upload'] ? $default_upload_box : ''),
'$default_upload_submit' => ($ret['default_upload'] ? $default_upload_submit : ''),
@ -1077,7 +1071,7 @@ function photos_content(App $a)
$album_e = $ph[0]['album'];
$caption_e = $ph[0]['desc'];
$aclselect_e = ACL::getFullSelectorHTML(DI::page(), $a->getLoggedInUserId(), false, ACL::getDefaultUserPermissions($ph[0]));
$aclselect_e = ACL::getFullSelectorHTML(DI::page(), DI::userSession()->getLocalUserId(), false, ACL::getDefaultUserPermissions($ph[0]));
$edit = Renderer::replaceMacros($edit_tpl, [
'$id' => $ph[0]['id'],

View file

@ -24,6 +24,7 @@ namespace Friendica;
use Exception;
use Friendica\App\Arguments;
use Friendica\App\BaseURL;
use Friendica\App\Request;
use Friendica\Capabilities\ICanCreateResponses;
use Friendica\Content\Nav;
use Friendica\Core\Config\Factory\Config;
@ -39,10 +40,10 @@ use Friendica\Core\L10n;
use Friendica\Core\System;
use Friendica\Core\Theme;
use Friendica\Database\Database;
use Friendica\Model\Contact;
use Friendica\Model\Profile;
use Friendica\Module\Special\HTTPException as ModuleHTTPException;
use Friendica\Network\HTTPException;
use Friendica\Protocol\ATProtocol\DID;
use Friendica\Security\OpenWebAuth;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\HTTPInputData;
use Friendica\Util\HTTPSignature;
@ -64,7 +65,7 @@ class App
{
const PLATFORM = 'Friendica';
const CODENAME = 'Yellow Archangel';
const VERSION = '2024.03';
const VERSION = '2024.06-rc';
// Allow themes to control internal parameters
// by changing App values in theme.php
@ -93,6 +94,12 @@ class App
/** @var string The name of the current mobile theme */
private $currentMobileTheme;
/** @var string */
private $requestId;
/** @var Authentication */
private $auth;
/**
* @var IManageConfigValues The config
*/
@ -133,42 +140,6 @@ class App
*/
private $session;
/**
* @deprecated 2022.03
* @see IHandleUserSessions::isAuthenticated()
*/
public function isLoggedIn(): bool
{
return $this->session->isAuthenticated();
}
/**
* @deprecated 2022.03
* @see IHandleUserSessions::isSiteAdmin()
*/
public function isSiteAdmin(): bool
{
return $this->session->isSiteAdmin();
}
/**
* @deprecated 2022.03
* @see IHandleUserSessions::getLocalUserId()
*/
public function getLoggedInUserId(): int
{
return $this->session->getLocalUserId();
}
/**
* @deprecated 2022.03
* @see IHandleUserSessions::getLocalUserNickname()
*/
public function getLoggedInUserNickname(): string
{
return $this->session->getLocalUserNickname();
}
/**
* Set the profile owner ID
*
@ -314,8 +285,10 @@ class App
* @param DbaDefinition $dbaDefinition
* @param ViewDefinition $viewDefinition
*/
public function __construct(Database $database, IManageConfigValues $config, App\Mode $mode, BaseURL $baseURL, LoggerInterface $logger, Profiler $profiler, L10n $l10n, Arguments $args, IManagePersonalConfigValues $pConfig, IHandleUserSessions $session, DbaDefinition $dbaDefinition, ViewDefinition $viewDefinition)
public function __construct(Request $request, Authentication $auth, Database $database, IManageConfigValues $config, App\Mode $mode, BaseURL $baseURL, LoggerInterface $logger, Profiler $profiler, L10n $l10n, Arguments $args, IManagePersonalConfigValues $pConfig, IHandleUserSessions $session, DbaDefinition $dbaDefinition, ViewDefinition $viewDefinition)
{
$this->requestId = $request->getRequestId();
$this->auth = $auth;
$this->database = $database;
$this->config = $config;
$this->mode = $mode;
@ -565,8 +538,8 @@ class App
*/
public function runFrontend(App\Router $router, IManagePersonalConfigValues $pconfig, Authentication $auth, App\Page $page, Nav $nav, ModuleHTTPException $httpException, HTTPInputData $httpInput, float $start_time, array $server)
{
$requeststring = ($_SERVER['REQUEST_METHOD'] ?? '') . ' ' . ($_SERVER['REQUEST_URI'] ?? '') . ' ' . ($_SERVER['SERVER_PROTOCOL'] ?? '');
$this->logger->debug('Request received', ['address' => $_SERVER['REMOTE_ADDR'] ?? '', 'request' => $requeststring, 'referer' => $_SERVER['HTTP_REFERER'] ?? '', 'user-agent' => $_SERVER['HTTP_USER_AGENT'] ?? '']);
$requeststring = ($server['REQUEST_METHOD'] ?? '') . ' ' . ($server['REQUEST_URI'] ?? '') . ' ' . ($server['SERVER_PROTOCOL'] ?? '');
$this->logger->debug('Request received', ['address' => $server['REMOTE_ADDR'] ?? '', 'request' => $requeststring, 'referer' => $server['HTTP_REFERER'] ?? '', 'user-agent' => $server['HTTP_USER_AGENT'] ?? '']);
$request_start = microtime(true);
$this->profiler->set($start_time, 'start');
@ -593,10 +566,12 @@ class App
Core\Hook::callAll('init_1');
}
DID::routeRequest($this->args->getCommand(), $server);
if ($this->mode->isNormal() && !$this->mode->isBackend()) {
$requester = HTTPSignature::getSigner('', $_SERVER);
$requester = HTTPSignature::getSigner('', $server);
if (!empty($requester)) {
Profile::addVisitorCookieForHandle($requester);
OpenWebAuth::addVisitorCookieForHandle($requester);
}
}
@ -606,17 +581,8 @@ class App
// Valid profile links contain a path with "/profile/" and no query parameters
if ((parse_url($_GET['zrl'], PHP_URL_QUERY) == '') &&
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'])) {
$_SESSION['remote_comment'] = $remote_contact['subscribe'];
}
}
Model\Profile::zrlInit($this);
$this->auth->setUnauthenticatedVisitor($_GET['zrl']);
OpenWebAuth::zrlInit();
} else {
// Someone came with an invalid parameter, maybe as a DDoS attempt
// We simply stop processing here
@ -627,14 +593,14 @@ class App
if (!empty($_GET['owt']) && $this->mode->isNormal()) {
$token = $_GET['owt'];
Model\Profile::openWebAuthInit($token);
OpenWebAuth::init($token);
}
if (!$this->mode->isBackend()) {
$auth->withSession($this);
}
if (empty($_SESSION['authenticated'])) {
if ($this->session->isUnauthenticated()) {
header('X-Account-Management-Status: none');
}
@ -654,6 +620,11 @@ class App
Core\Hook::loadHooks();
}
// Compatibility with Hubzilla
if ($moduleName == 'rpost') {
$this->baseURL->redirect('compose');
}
// Compatibility with the Android Diaspora client
if ($moduleName == 'stream') {
$this->baseURL->redirect('network?order=post');
@ -713,13 +684,15 @@ class App
// Wrapping HTML responses in the theme template
if ($response->getHeaderLine(ICanCreateResponses::X_HEADER) === ICanCreateResponses::TYPE_HTML) {
$response = $page->run($this, $this->baseURL, $this->args, $this->mode, $response, $this->l10n, $this->profiler, $this->config, $pconfig, $nav, $this->session->getLocalUserId());
$response = $page->run($this, $this->session, $this->baseURL, $this->args, $this->mode, $response, $this->l10n, $this->profiler, $this->config, $pconfig, $nav, $this->session->getLocalUserId());
}
$this->logger->debug('Request processed sucessfully', ['response' => $response->getStatusCode(), 'address' => $_SERVER['REMOTE_ADDR'] ?? '', 'request' => $requeststring, 'referer' => $_SERVER['HTTP_REFERER'] ?? '', 'user-agent' => $_SERVER['HTTP_USER_AGENT'] ?? '', 'duration' => number_format(microtime(true) - $request_start, 3)]);
$this->logger->debug('Request processed sucessfully', ['response' => $response->getStatusCode(), 'address' => $server['REMOTE_ADDR'] ?? '', 'request' => $requeststring, 'referer' => $server['HTTP_REFERER'] ?? '', 'user-agent' => $server['HTTP_USER_AGENT'] ?? '', 'duration' => number_format(microtime(true) - $request_start, 3)]);
$this->logSlowCalls(microtime(true) - $request_start, $response->getStatusCode(), $requeststring, $server['HTTP_USER_AGENT'] ?? '');
System::echoResponse($response);
} catch (HTTPException $e) {
$this->logger->debug('Request processed with exception', ['response' => $e->getCode(), 'address' => $_SERVER['REMOTE_ADDR'] ?? '', 'request' => $requeststring, 'referer' => $_SERVER['HTTP_REFERER'] ?? '', 'user-agent' => $_SERVER['HTTP_USER_AGENT'] ?? '', 'duration' => number_format(microtime(true) - $request_start, 3)]);
$this->logger->debug('Request processed with exception', ['response' => $e->getCode(), 'address' => $server['REMOTE_ADDR'] ?? '', 'request' => $requeststring, 'referer' => $server['HTTP_REFERER'] ?? '', 'user-agent' => $server['HTTP_USER_AGENT'] ?? '', 'duration' => number_format(microtime(true) - $request_start, 3)]);
$this->logSlowCalls(microtime(true) - $request_start, $e->getCode(), $requeststring, $server['HTTP_USER_AGENT'] ?? '');
$httpException->rawContent($e);
}
$page->logRuntime($this->config, 'runFrontend');
@ -741,4 +714,30 @@ class App
$this->baseURL->redirect($toUrl);
}
}
/**
* Log slow page executions
*
* @param float $duration
* @param integer $code
* @param string $request
* @param string $agent
* @return void
*/
private function logSlowCalls(float $duration, int $code, string $request, string $agent)
{
$logfile = $this->config->get('system', 'page_execution_logfile');
$loglimit = $this->config->get('system', 'page_execution_log_limit');
if (empty($logfile) || empty($loglimit) || ($duration < $loglimit)) {
return;
}
@file_put_contents(
$logfile,
DateTimeFormat::utcNow() . "\t" . round($duration, 3) . "\t" .
$this->requestId . "\t" . $code . "\t" .
$request . "\t" . $agent . "\n",
FILE_APPEND
);
}
}

View file

@ -32,6 +32,7 @@ use Friendica\Core\L10n;
use Friendica\Core\Logger;
use Friendica\Core\PConfig\Capability\IManagePersonalConfigValues;
use Friendica\Core\Renderer;
use Friendica\Core\Session\Model\UserSession;
use Friendica\Core\System;
use Friendica\Core\Theme;
use Friendica\Module\Response;
@ -325,13 +326,13 @@ class Page implements ArrayAccess
*
* @throws HTTPException\InternalServerErrorException
*/
private function initFooter(App $app, Mode $mode, L10n $l10n)
private function initFooter(UserSession $session, Mode $mode, L10n $l10n)
{
// If you're just visiting, let javascript take you home
if (!empty($_SESSION['visitor_home'])) {
$homebase = $_SESSION['visitor_home'];
} elseif (!empty($app->getLoggedInUserNickname())) {
$homebase = 'profile/' . $app->getLoggedInUserNickname();
} elseif (!empty($session->getLocalUserNickname())) {
$homebase = 'profile/' . $session->getLocalUserNickname();
}
if (isset($homebase)) {
@ -420,7 +421,7 @@ class Page implements ArrayAccess
* @throws HTTPException\InternalServerErrorException
* @throws HTTPException\ServiceUnavailableException
*/
public function run(App $app, BaseURL $baseURL, Arguments $args, Mode $mode, ResponseInterface $response, L10n $l10n, Profiler $profiler, IManageConfigValues $config, IManagePersonalConfigValues $pconfig, Nav $nav, int $localUID)
public function run(App $app, UserSession $session, BaseURL $baseURL, Arguments $args, Mode $mode, ResponseInterface $response, L10n $l10n, Profiler $profiler, IManageConfigValues $config, IManagePersonalConfigValues $pconfig, Nav $nav, int $localUID)
{
$moduleName = $args->getModuleName();
@ -459,7 +460,7 @@ class Page implements ArrayAccess
/* Build the page ending -- this is stuff that goes right before
* the closing </body> tag
*/
$this->initFooter($app, $mode, $l10n);
$this->initFooter($session, $mode, $l10n);
$profiler->set(microtime(true) - $timestamp, 'aftermath');

View file

@ -224,17 +224,17 @@ abstract class BaseModule implements ICanHandleRequests
switch ($this->args->getMethod()) {
case Router::DELETE:
$this->delete($request);
break;
return $this->response->generate();
case Router::PATCH:
$this->patch($request);
break;
return $this->response->generate();
case Router::POST:
Core\Hook::callAll($this->args->getModuleName() . '_mod_post', $request);
$this->post($request);
break;
return $this->response->generate();
case Router::PUT:
$this->put($request);
break;
return $this->response->generate();
}
$timestamp = microtime(true);
@ -356,7 +356,7 @@ abstract class BaseModule implements ICanHandleRequests
*/
public static function getFormSecurityToken(string $typename = ''): string
{
$user = User::getById(DI::app()->getLoggedInUserId(), ['guid', 'prvkey']);
$user = User::getById(DI::userSession()->getLocalUserId(), ['guid', 'prvkey']);
$timestamp = time();
$sec_hash = hash('whirlpool', ($user['guid'] ?? '') . ($user['prvkey'] ?? '') . session_id() . $timestamp . $typename);
@ -390,7 +390,7 @@ abstract class BaseModule implements ICanHandleRequests
$max_livetime = 10800; // 3 hours
$user = User::getById(DI::app()->getLoggedInUserId(), ['guid', 'prvkey']);
$user = User::getById(DI::userSession()->getLocalUserId(), ['guid', 'prvkey']);
$x = explode('.', $hash);
if (time() > (intval($x[0]) + $max_livetime)) {
@ -410,7 +410,7 @@ abstract class BaseModule implements ICanHandleRequests
public static function checkFormSecurityTokenRedirectOnError(string $err_redirect, string $typename = '', string $formname = 'form_security_token')
{
if (!self::checkFormSecurityToken($typename, $formname)) {
Logger::notice('checkFormSecurityToken failed: user ' . DI::app()->getLoggedInUserNickname() . ' - form element ' . $typename);
Logger::notice('checkFormSecurityToken failed: user ' . DI::userSession()->getLocalUserNickname() . ' - form element ' . $typename);
Logger::debug('checkFormSecurityToken failed', ['request' => $_REQUEST]);
DI::sysmsg()->addNotice(self::getFormSecurityStandardErrorMessage());
DI::baseUrl()->redirect($err_redirect);
@ -420,7 +420,7 @@ abstract class BaseModule implements ICanHandleRequests
public static function checkFormSecurityTokenForbiddenOnError(string $typename = '', string $formname = 'form_security_token')
{
if (!self::checkFormSecurityToken($typename, $formname)) {
Logger::notice('checkFormSecurityToken failed: user ' . DI::app()->getLoggedInUserNickname() . ' - form element ' . $typename);
Logger::notice('checkFormSecurityToken failed: user ' . DI::userSession()->getLocalUserNickname() . ' - form element ' . $typename);
Logger::debug('checkFormSecurityToken failed', ['request' => $_REQUEST]);
throw new \Friendica\Network\HTTPException\ForbiddenException();

View file

@ -0,0 +1,119 @@
<?php
/**
* @copyright Copyright (C) 2010-2024, 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\Console;
use Friendica\App\BaseURL;
use Friendica\Contact\Avatar;
use Friendica\Core\L10n;
use Friendica\Model\Contact;
use Friendica\Core\Config\Capability\IManageConfigValues;
/**
* tool to clear the avatar file cache.
*/
class ClearAvatarCache extends \Asika\SimpleConsole\Console
{
protected $helpOptions = ['h', 'help', '?'];
/**
* @var $dba Friendica\Database\Database
*/
private $dba;
/**
* @var $baseurl Friendica\App\BaseURL
*/
private $baseUrl;
/**
* @var L10n
*/
private $l10n;
/**
* @var IManageConfigValues
*/
private $config;
protected function getHelp()
{
$help = <<<HELP
console clearavatarcache - Clear the file based avatar cache
Synopsis
bin/console clearavatarcache
Description
bin/console clearavatarcache
Clear the file based avatar cache
Options
-h|--help|-? Show help information
HELP;
return $help;
}
public function __construct(\Friendica\Database\Database $dba, BaseURL $baseUrl, L10n $l10n, IManageConfigValues $config, array $argv = null)
{
parent::__construct($argv);
$this->dba = $dba;
$this->baseUrl = $baseUrl;
$this->l10n = $l10n;
$this->config = $config;
}
protected function doExecute(): int
{
if ($this->config->get('system', 'avatar_cache')) {
$this->err($this->l10n->t('The avatar cache needs to be disabled in local.config.php to use this command.'));
return 2;
}
// Contacts (but not self contacts) with cached avatars.
$condition = ["NOT `self` AND (`photo` != ? OR `thumb` != ? OR `micro` != ?)", '', '', ''];
$total = $this->dba->count('contact', $condition);
$count = 0;
$contacts = $this->dba->select('contact', ['id', 'uri-id', 'url', 'uid', 'photo', 'thumb', 'micro'], $condition);
while ($contact = $this->dba->fetch($contacts)) {
if (Avatar::deleteCache($contact) || $this->isAvatarCache($contact)) {
Contact::update(['photo' => '', 'thumb' => '', 'micro' => ''], ['id' => $contact['id']]);
}
$this->out(++$count . '/' . $total . "\t" . $contact['id'] . "\t" . $contact['url'] . "\t" . $contact['photo']);
}
$this->dba->close($contacts);
return 0;
}
private function isAvatarCache(array $contact): bool
{
if (!empty($contact['photo']) && strpos($contact['photo'], Avatar::baseUrl()) === 0) {
return true;
}
if (!empty($contact['thumb']) && strpos($contact['thumb'], Avatar::baseUrl()) === 0) {
return true;
}
if (!empty($contact['micro']) && strpos($contact['micro'], Avatar::baseUrl()) === 0) {
return true;
}
return false;
}
}

View file

@ -25,6 +25,9 @@ use Asika\SimpleConsole\CommandArgsException;
use Friendica\Core\Storage\Repository\StorageManager;
use Friendica\Core\Storage\Exception\ReferenceStorageException;
use Friendica\Core\Storage\Exception\StorageException;
use Friendica\Database\DBA;
use Friendica\Model\Contact;
use Friendica\Model\Photo;
/**
* tool to manage storage backend and stored data from CLI
@ -57,6 +60,9 @@ Synopsis
bin/console storage list
List available storage backends
bin/console storage clear
Remove the contact avatar cache data
bin/console storage set <name>
Set current storage backend
name storage backend to use. see "list".
@ -87,6 +93,9 @@ HELP;
case 'list':
return $this->doList();
break;
case 'clear':
return $this->clear();
break;
case 'set':
return $this->doSet();
break;
@ -126,6 +135,22 @@ HELP;
return 0;
}
protected function clear()
{
$fields = ['photo' => '', 'thumb' => '', 'micro' => ''];
$photos = DBA::select('photo', ['id', 'contact-id'], ['uid' => 0, 'photo-type' => [Photo::CONTACT_AVATAR, Photo::CONTACT_BANNER]]);
while ($photo = DBA::fetch($photos)) {
if (Photo::delete(['id' => $photo['id']])) {
Contact::update($fields, ['id' => $photo['contact-id']]);
$this->out('Cleared photo id ' . $photo['id'] . ' - contact id ' . $photo['contact-id']);
} else {
$this->out('Photo id ' . $photo['id'] . ' was not deleted.');
}
}
DBA::close($photos);
return 0;
}
protected function doSet()
{
if (count($this->args) !== 2 || empty($this->args[1])) {

View file

@ -23,14 +23,11 @@ namespace Friendica\Contact;
use Friendica\Core\Logger;
use Friendica\DI;
use Friendica\Model\Item;
use Friendica\Network\HTTPClient\Client\HttpClientAccept;
use Friendica\Network\HTTPClient\Client\HttpClientOptions;
use Friendica\Object\Image;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\HTTPSignature;
use Friendica\Util\Images;
use Friendica\Util\Network;
use Friendica\Util\Proxy;
/**
@ -57,7 +54,7 @@ class Avatar
return $fields;
}
if (Network::isLocalLink($avatar) || empty($avatar)) {
if (DI::baseUrl()->isLocalUrl($avatar) || empty($avatar)) {
self::deleteCache($contact);
return $fields;
}
@ -97,7 +94,7 @@ class Avatar
return $fields;
}
$filename = self::getFilename($contact['url'], $avatar);
$filename = self::getFilename($contact['url']);
$timestamp = time();
$fields['blurhash'] = $image->getBlurHash();
@ -120,12 +117,12 @@ class Avatar
return $fields;
}
if (Network::isLocalLink($contact['avatar']) || empty($contact['avatar'])) {
if (DI::baseUrl()->isLocalUrl($contact['avatar']) || empty($contact['avatar'])) {
self::deleteCache($contact);
return $fields;
}
$filename = self::getFilename($contact['url'], $contact['avatar']);
$filename = self::getFilename($contact['url']);
$timestamp = time();
$fields['photo'] = self::storeAvatarCache($image, $filename, Proxy::PIXEL_SMALL, $timestamp);
@ -135,12 +132,10 @@ class Avatar
return $fields;
}
private static function getFilename(string $url, string $host): string
private static function getFilename(string $url): string
{
$guid = Item::guidFromUri($url, $host);
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) . '-';
$guid = hash('ripemd128', $url);
return substr($guid, 0, 3) . '/' . substr($guid, 4) . '-';
}
private static function storeAvatarCache(Image $image, string $filename, int $size, int $timestamp): string
@ -279,7 +274,7 @@ class Avatar
$localFile = self::getCacheFile($avatar);
if (!empty($localFile)) {
@unlink($localFile);
Logger::debug('Unlink avatar', ['avatar' => $avatar]);
Logger::debug('Unlink avatar', ['avatar' => $avatar, 'local' => $localFile]);
}
}
@ -316,7 +311,7 @@ class Avatar
*
* @return string
*/
private static function baseUrl(): string
public static function baseUrl(): string
{
$baseurl = DI::config()->get('system', 'avatar_cache_url');
if (!empty($baseurl)) {

View file

@ -23,8 +23,8 @@ namespace Friendica\Content;
use Friendica\Core\L10n;
use Friendica\Core\Renderer;
use Friendica\Util\Network;
use Friendica\Util\Strings;
use GuzzleHttp\Psr7\Uri;
/**
* This pager should be used by lists using the min_id†/max_id† parameters
@ -67,7 +67,7 @@ class BoundariesPager extends Pager
$parsed['query'] = http_build_query($queryParameters);
$url = Network::unparseURL($parsed);
$url = (string)Uri::fromParts((array)$parsed);
$this->setQueryString($url);
}

View file

@ -25,8 +25,8 @@ use Friendica\Core\Hook;
use Friendica\Core\Protocol;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Util\Network;
use Friendica\Util\Strings;
use GuzzleHttp\Psr7\Uri;
/**
* ContactSelector class
@ -102,7 +102,7 @@ class ContactSelector
// Create the server url out of the profile url
$parts = parse_url($profile);
unset($parts['path']);
$server_url = Strings::normaliseLink(Network::unparseURL($parts));
$server_url = Strings::normaliseLink((string)Uri::fromParts((array)$parts));
}
self::$server_url[$profile] = $server_url;

View file

@ -308,7 +308,7 @@ class Conversation
public function statusEditor(array $x = [], int $notes_cid = 0, bool $popup = false): string
{
$user = User::getById($this->app->getLoggedInUserId(), ['uid', 'nickname', 'allow_location', 'default-location']);
$user = User::getById($this->session->getLocalUserId(), ['uid', 'nickname', 'allow_location', 'default-location']);
if (empty($user['uid'])) {
return '';
}
@ -332,7 +332,6 @@ class Conversation
$tpl = Renderer::getMarkupTemplate('jot-header.tpl');
$this->page['htmlhead'] .= Renderer::replaceMacros($tpl, [
'$newpost' => 'true',
'$baseurl' => $this->baseURL,
'$geotag' => $geotag,
'$nickname' => $x['nickname'],
'$ispublic' => $this->l10n->t('Visible to <strong>everybody</strong>'),
@ -389,7 +388,7 @@ class Conversation
'$title' => $x['title'] ?? '',
'$placeholdertitle' => $this->l10n->t('Set title'),
'$category' => $x['category'] ?? '',
'$placeholdercategory' => Feature::isEnabled($this->session->getLocalUserId(), 'categories') ? $this->l10n->t("Categories \x28comma-separated list\x29") : '',
'$placeholdercategory' => Feature::isEnabled($this->session->getLocalUserId(), Feature::CATEGORIES) ? $this->l10n->t("Categories \x28comma-separated list\x29") : '',
'$scheduled_at' => Temporal::getDateTimeField(
new \DateTime(),
new \DateTime('now + 6 months'),
@ -405,7 +404,6 @@ class Conversation
'$posttype' => $notes_cid ? ItemModel::PT_PERSONAL_NOTE : ItemModel::PT_ARTICLE,
'$content' => $x['content'] ?? '',
'$post_id' => $x['post_id'] ?? '',
'$baseurl' => $this->baseURL,
'$defloc' => $x['default_location'],
'$visitor' => $x['visitor'],
'$pvisit' => $notes_cid ? 'none' : $x['visitor'],
@ -591,7 +589,6 @@ class Conversation
}
$o = Renderer::replaceMacros($page_template, [
'$baseurl' => $this->baseURL,
'$return_path' => $this->args->getQueryString(),
'$live_update' => $live_update_div,
'$remove' => $this->l10n->t('remove'),
@ -1517,14 +1514,6 @@ class Conversation
[$categories, $folders] = $this->item->determineCategoriesTerms($item, $this->session->getLocalUserId());
if (!empty($item['title'])) {
$title = $item['title'];
} elseif (!empty($item['content-warning']) && $this->pConfig->get($this->session->getLocalUserId(), 'system', 'disable_cw', false)) {
$title = ucfirst($item['content-warning']);
} else {
$title = '';
}
if (!empty($item['featured'])) {
$pinned = $this->l10n->t('Pinned item');
} else {
@ -1550,7 +1539,8 @@ class Conversation
'sparkle' => $sparkle,
'lock' => false,
'thumb' => $this->baseURL->remove($this->item->getAuthorAvatar($item)),
'title' => $title,
'title' => $item['title'],
'summary' => $item['content-warning'],
'body_html' => $body_html,
'tags' => $tags['tags'],
'hashtags' => $tags['hashtags'],

View file

@ -26,6 +26,25 @@ use Friendica\DI;
class Feature
{
const ACCOUNTS = 'accounts';
const ADD_ABSTRACT = 'add_abstract';
const ARCHIVE = 'archive';
const CATEGORIES = 'categories';
const CHANNELS = 'channels';
const CIRCLES = 'circles';
const COMMUNITY = 'community';
const EXPLICIT_MENTIONS = 'explicit_mentions';
const FOLDERS = 'folders';
const GROUPS = 'forumlist_profile';
const MEMBER_SINCE = 'profile_membersince';
const NETWORKS = 'networks';
const NOSHARER = 'nosharer';
const PHOTO_LOCATION = 'photo_location';
const PUBLIC_CALENDAR = 'public_calendar';
const SEARCHES = 'searches';
const TAGCLOUD = 'tagadelic';
const TRENDING_TAGS = 'trending_tags';
/**
* check if feature is enabled
*
@ -34,25 +53,18 @@ class Feature
* @return boolean
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function isEnabled(int $uid, $feature)
public static function isEnabled(int $uid, $feature): bool
{
$x = DI::config()->get('feature_lock', $feature, false);
if ($x === false) {
$x = DI::pConfig()->get($uid, 'feature', $feature, false);
if (!DI::config()->get('feature_lock', $feature, false)) {
$enabled = DI::config()->get('feature', $feature) ?? self::getDefault($feature);
$enabled = DI::pConfig()->get($uid, 'feature', $feature) ?? $enabled;
} else {
$enabled = true;
}
if ($x === false) {
$x = DI::config()->get('feature', $feature, false);
}
if ($x === false) {
$x = self::getDefault($feature);
}
$arr = ['uid' => $uid, 'feature' => $feature, 'enabled' => $x];
$arr = ['uid' => $uid, 'feature' => $feature, 'enabled' => $enabled];
Hook::callAll('isEnabled', $arr);
return($arr['enabled']);
return (bool)$arr['enabled'];
}
/**
@ -64,8 +76,7 @@ class Feature
*/
private static function getDefault($feature)
{
$f = self::get();
foreach ($f as $cat) {
foreach (self::get() as $cat) {
foreach ($cat as $feat) {
if (is_array($feat) && $feat[0] === $feature) {
return $feat[3];
@ -95,36 +106,49 @@ class Feature
'general' => [
DI::l10n()->t('General Features'),
//array('expire', DI::l10n()->t('Content Expiration'), DI::l10n()->t('Remove old posts/comments after a period of time')),
['photo_location', DI::l10n()->t('Photo Location'), DI::l10n()->t("Photo metadata is normally stripped. This extracts the location \x28if present\x29 prior to stripping metadata and links it to a map."), false, DI::config()->get('feature_lock', 'photo_location', false)],
['trending_tags', DI::l10n()->t('Trending Tags'), DI::l10n()->t('Show a community page widget with a list of the most popular tags in recent public posts.'), false, DI::config()->get('feature_lock', 'trending_tags', false)],
[self::PHOTO_LOCATION, DI::l10n()->t('Photo Location'), DI::l10n()->t("Photo metadata is normally stripped. This extracts the location \x28if present\x29 prior to stripping metadata and links it to a map."), false, DI::config()->get('feature_lock', self::PHOTO_LOCATION, false)],
[self::COMMUNITY, DI::l10n()->t('Display the community in the navigation'), DI::l10n()->t('If enabled, the community can be accessed via the navigation menu. Independent from this setting, the community timelines can always be accessed via the channels.'), true, DI::config()->get('feature_lock', self::COMMUNITY, false)],
],
// Post composition
'composition' => [
DI::l10n()->t('Post Composition Features'),
['aclautomention', DI::l10n()->t('Auto-mention Groups'), DI::l10n()->t('Add/remove mention when a group page is selected/deselected in ACL window.'), false, DI::config()->get('feature_lock', 'aclautomention', false)],
['explicit_mentions', DI::l10n()->t('Explicit Mentions'), DI::l10n()->t('Add explicit mentions to comment box for manual control over who gets mentioned in replies.'), false, DI::config()->get('feature_lock', 'explicit_mentions', false)],
['add_abstract', DI::l10n()->t('Add an abstract from ActivityPub content warnings'), DI::l10n()->t('Add an abstract when commenting on ActivityPub posts with a content warning. Abstracts are displayed as content warning on systems like Mastodon or Pleroma.'), false, DI::config()->get('feature_lock', 'add_abstract', false)],
[self::EXPLICIT_MENTIONS, DI::l10n()->t('Explicit Mentions'), DI::l10n()->t('Add explicit mentions to comment box for manual control over who gets mentioned in replies.'), false, DI::config()->get('feature_lock', Feature::EXPLICIT_MENTIONS, false)],
[self::ADD_ABSTRACT, DI::l10n()->t('Add an abstract from ActivityPub content warnings'), DI::l10n()->t('Add an abstract when commenting on ActivityPub posts with a content warning. Abstracts are displayed as content warning on systems like Mastodon or Pleroma.'), false, DI::config()->get('feature_lock', self::ADD_ABSTRACT, false)],
],
// Item tools
'tools' => [
DI::l10n()->t('Post/Comment Tools'),
['categories', DI::l10n()->t('Post Categories'), DI::l10n()->t('Add categories to your posts'), false, DI::config()->get('feature_lock', 'categories', false)],
[self::CATEGORIES, DI::l10n()->t('Post Categories'), DI::l10n()->t('Add categories to your posts'), false, DI::config()->get('feature_lock', self::CATEGORIES, false)],
],
// Widget visibility on the network stream
'network' => [
DI::l10n()->t('Network Widgets'),
[self::CIRCLES, DI::l10n()->t('Circles'), DI::l10n()->t('Display posts that have been created by accounts of the selected circle.'), true, DI::config()->get('feature_lock', self::CIRCLES, false)],
[self::GROUPS, DI::l10n()->t('Groups'), DI::l10n()->t('Display posts that have been distributed by the selected group.'), true, DI::config()->get('feature_lock', self::GROUPS, false)],
[self::ARCHIVE, DI::l10n()->t('Archives'), DI::l10n()->t('Display an archive where posts can be selected by month and year.'), true, DI::config()->get('feature_lock', self::ARCHIVE, false)],
[self::NETWORKS, DI::l10n()->t('Protocols'), DI::l10n()->t('Display posts with the selected protocols.'), true, DI::config()->get('feature_lock', self::NETWORKS, false)],
[self::ACCOUNTS, DI::l10n()->t('Account Types'), DI::l10n()->t('Display posts done by accounts with the selected account type.'), true, DI::config()->get('feature_lock', self::ACCOUNTS, false)],
[self::CHANNELS, DI::l10n()->t('Channels'), DI::l10n()->t('Display posts in the system channels and user defined channels.'), true, DI::config()->get('feature_lock', self::CHANNELS, false)],
[self::SEARCHES, DI::l10n()->t('Saved Searches'), DI::l10n()->t('Display posts that contain subscribed hashtags.'), true, DI::config()->get('feature_lock', self::SEARCHES, false)],
[self::FOLDERS, DI::l10n()->t('Saved Folders'), DI::l10n()->t('Display a list of folders in which posts are stored.'), true, DI::config()->get('feature_lock', self::FOLDERS, false)],
[self::NOSHARER, DI::l10n()->t('Own Contacts'), DI::l10n()->t('Include or exclude posts from subscribed accounts. This widget is not visible on all channels.'), true, DI::config()->get('feature_lock', self::NOSHARER, false)],
[self::TRENDING_TAGS, DI::l10n()->t('Trending Tags'), DI::l10n()->t('Display a list of the most popular tags in recent public posts.'), false, DI::config()->get('feature_lock', self::TRENDING_TAGS, false)],
],
// Advanced Profile Settings
'advanced_profile' => [
DI::l10n()->t('Advanced Profile Settings'),
['forumlist_profile', DI::l10n()->t('List Groups'), DI::l10n()->t('Show visitors public groups at the Advanced Profile Page'), false, DI::config()->get('feature_lock', 'forumlist_profile', false)],
['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)],
[self::TAGCLOUD, DI::l10n()->t('Tag Cloud'), DI::l10n()->t('Provide a personal tag cloud on your profile page'), false, DI::config()->get('feature_lock', self::TAGCLOUD, false)],
[self::MEMBER_SINCE, DI::l10n()->t('Display Membership Date'), DI::l10n()->t('Display membership date in profile'), false, DI::config()->get('feature_lock', self::MEMBER_SINCE, false)],
],
//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)],
[self::PUBLIC_CALENDAR, DI::l10n()->t('Allow anonymous access to your calendar'), DI::l10n()->t('Allows anonymous visitors to consult your calendar and your public events. Contact birthday events are private to you.'), false, DI::config()->get('feature_lock', self::PUBLIC_CALENDAR, false)],
]
];

View file

@ -66,7 +66,7 @@ class GroupManager
'archive' => false,
];
$condition = DBA::mergeConditions($condition, ["`platform` != ?", 'peertube']);
$condition = DBA::mergeConditions($condition, ["`platform` NOT IN (?, ?)", 'peertube', 'wordpress']);
if (!$showprivate) {
$condition = DBA::mergeConditions($condition, ['manually-approve' => false]);
@ -172,8 +172,7 @@ class GroupManager
*/
public static function profileAdvanced($uid)
{
$profile = intval(Feature::isEnabled($uid, 'forumlist_profile'));
if (!$profile) {
if (!Feature::isEnabled($uid, Feature::GROUPS)) {
return '';
}

View file

@ -48,7 +48,6 @@ use Friendica\Model\User;
use Friendica\Network\HTTPException;
use Friendica\Object\EMail\ItemCCEMail;
use Friendica\Protocol\Activity;
use Friendica\Protocol\ActivityPub;
use Friendica\Util\ACLFormatter;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\Emailer;
@ -440,9 +439,11 @@ class Item
];
if (!empty($item['language'])) {
$menu[$this->l10n->t('Languages')] = 'javascript:alert(\'' . ItemModel::getLanguageMessage($item) . '\');';
$menu[$this->l10n->t('Languages')] = 'javascript:displayLanguage(' . $item['uri-id'] . ');';
}
$menu[$this->l10n->t('Search Text')] = 'javascript:displaySearchText(' . $item['uri-id'] . ');';
if ((($cid == 0) || ($rel == Contact::FOLLOWER)) &&
in_array($item['network'], Protocol::FEDERATED)
) {
@ -546,9 +547,9 @@ class Item
$item['private'] = $private_group ? ItemModel::PRIVATE : ItemModel::UNLISTED;
if ($only_to_group) {
$cdata = Contact::getPublicAndUserContactID($group_contact['id'], $item['uid']);
if (!empty($cdata['user'])) {
$item['owner-id'] = $cdata['user'];
$pcid = Contact::getPublicContactId($group_contact['id'], $item['uid']);
if ($pcid) {
$item['owner-id'] = $pcid;
unset($item['owner-link']);
unset($item['owner-name']);
unset($item['owner-avatar']);
@ -800,14 +801,14 @@ class Item
*/
public function addShareLink(string $body, int $quote_uri_id): string
{
$post = Post::selectFirstPost(['uri', 'plink'], ['uri-id' => $quote_uri_id]);
$post = Post::selectFirstPost(['uri'], ['uri-id' => $quote_uri_id]);
if (empty($post)) {
return $body;
}
$body = BBCode::removeSharedData($body);
$body .= "\n" . ($post['plink'] ?: $post['uri']);
$body .= "\nRE: " . $post['uri'];
return $body;
}
@ -910,40 +911,6 @@ class Item
return $post;
}
public function moveAttachmentsFromBodyToAttach(array $post): array
{
if (!preg_match_all('/(\[attachment\]([0-9]+)\[\/attachment\])/', $post['body'], $match)) {
return $post;
}
foreach ($match[2] as $attachment_id) {
$attachment = Attach::selectFirst(['id', 'uid', 'filename', 'filesize', 'filetype'], ['id' => $attachment_id, 'uid' => $post['uid']]);
if (empty($attachment)) {
continue;
}
if ($post['attach']) {
$post['attach'] .= ',';
}
$post['attach'] .= Post\Media::getAttachElement(
$this->baseURL . '/attach/' . $attachment['id'],
$attachment['filesize'],
$attachment['filetype'],
$attachment['filename'] ?? ''
);
$fields = [
'allow_cid' => $post['allow_cid'], 'allow_gid' => $post['allow_gid'],
'deny_cid' => $post['deny_cid'], 'deny_gid' => $post['deny_gid']
];
$condition = ['id' => $attachment_id];
Attach::update($fields, $condition);
}
$post['body'] = str_replace($match[1], '', $post['body']);
return $post;
}
private function setObjectType(array $post): array
{
if (empty($post['post-type'])) {
@ -1023,8 +990,13 @@ class Item
return $post;
}
public function finalizePost(array $post): array
public function finalizePost(array $post, bool $preview): array
{
if ($preview) {
$post['body'] = Attach::addAttachmentToBody($post['body'], $post['uid']);
} else {
Attach::setPermissionFromBody($post);
}
if (preg_match("/\[attachment\](.*?)\[\/attachment\]/ism", $post['body'], $matches)) {
$post['body'] = preg_replace("/\[attachment].*?\[\/attachment\]/ism", PageInfo::getFooterFromUrl($matches[1]), $post['body']);
}
@ -1045,7 +1017,7 @@ class Item
public function postProcessPost(array $post, array $recipients = [])
{
if (!\Friendica\Content\Feature::isEnabled($post['uid'], 'explicit_mentions') && ($post['gravity'] == ItemModel::GRAVITY_COMMENT)) {
if (!Feature::isEnabled($post['uid'], Feature::EXPLICIT_MENTIONS) && ($post['gravity'] == ItemModel::GRAVITY_COMMENT)) {
Tag::createImplicitMentions($post['uri-id'], $post['thr-parent-id']);
}
@ -1060,7 +1032,7 @@ class Item
}
$this->emailer->send(new ItemCCEMail(
$this->app,
$this->userSession,
$this->l10n,
$this->baseURL,
$post,
@ -1078,7 +1050,7 @@ class Item
$to_author = DBA::selectFirst('account-view', ['ap-followers'], ['id' => $to['author-id']]);
$parent = Post::selectFirstPost(['author-id'], ['uri-id' => $parentUriId]);
$parent_author = DBA::selectFirst('account-view', ['ap-followers'], ['id' => $parent['author-id']]);
$followers = '';
foreach (array_column(Tag::getByURIId($parentUriId, [Tag::TO, Tag::CC, Tag::BCC]), 'url') as $url) {
if ($url == $parent_author['ap-followers']) {

View file

@ -30,12 +30,12 @@ use Friendica\Core\Renderer;
use Friendica\Core\Session\Capability\IHandleUserSessions;
use Friendica\Database\Database;
use Friendica\Model\Contact;
use Friendica\Model\Profile;
use Friendica\Model\User;
use Friendica\Module\Conversation\Community;
use Friendica\Module\Home;
use Friendica\Module\Security\Login;
use Friendica\Network\HTTPException;
use Friendica\Security\OpenWebAuth;
class Nav
{
@ -251,7 +251,7 @@ class Nav
$nav['home'] = [$homelink, $this->l10n->t('Home'), '', $this->l10n->t('Home Page')];
}
if (intval($this->config->get('config', 'register_policy')) === \Friendica\Module\Register::OPEN && !$this->session->isAuthenticated()) {
if (\Friendica\Module\Register::getPolicy() === \Friendica\Module\Register::OPEN && !$this->session->isAuthenticated()) {
$nav['register'] = ['register', $this->l10n->t('Register'), '', $this->l10n->t('Create an account')];
}
@ -281,11 +281,11 @@ class Nav
$gdirpath = 'directory';
if ($this->config->get('system', 'singleuser') && $this->config->get('system', 'directory')) {
$gdirpath = Profile::zrl($this->config->get('system', 'directory'), true);
$gdirpath = OpenWebAuth::getZrlUrl($this->config->get('system', 'directory'), true);
}
if (($this->session->getLocalUserId() || $this->config->get('system', 'community_page_style') != Community::DISABLED_VISITOR) &&
!($this->config->get('system', 'community_page_style') == Community::DISABLED)) {
if (Feature::isEnabled($this->session->getLocalUserId(), Feature::COMMUNITY) && (($this->session->getLocalUserId() || $this->config->get('system', 'community_page_style') != Community::DISABLED_VISITOR) &&
!($this->config->get('system', 'community_page_style') == Community::DISABLED))) {
$nav['community'] = ['community', $this->l10n->t('Community'), '', $this->l10n->t('Conversations on this and other servers')];
}

View file

@ -32,6 +32,7 @@ use Friendica\Database\Database;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Network\HTTPClient\Client\HttpClientAccept;
use Friendica\Network\HTTPClient\Client\HttpClientRequest;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\Network;
use Friendica\Util\ParseUrl;
@ -86,7 +87,7 @@ class OEmbed
if (!in_array($ext, $noexts)) {
// try oembed autodiscovery
$html_text = DI::httpClient()->fetch($embedurl, HttpClientAccept::HTML, 15);
$html_text = DI::httpClient()->fetch($embedurl, HttpClientAccept::HTML, 15, '', HttpClientRequest::SITEINFO);
if (!empty($html_text)) {
$dom = new DOMDocument();
if (@$dom->loadHTML($html_text)) {
@ -100,7 +101,7 @@ class OEmbed
// but their OEmbed endpoint is only accessible by HTTPS ¯\_(ツ)_/¯
$href = str_replace(['http://www.youtube.com/', 'http://player.vimeo.com/'],
['https://www.youtube.com/', 'https://player.vimeo.com/'], $href);
$result = DI::httpClient()->fetchFull($href . '&maxwidth=' . $a->getThemeInfoValue('videowidth'));
$result = DI::httpClient()->fetchFull($href . '&maxwidth=' . $a->getThemeInfoValue('videowidth'), HttpClientAccept::DEFAULT, 0, '', HttpClientRequest::SITEINFO);
if ($result->isSuccess()) {
$json_string = $result->getBodyString();
break;

File diff suppressed because it is too large Load diff

View file

@ -29,9 +29,9 @@ use Friendica\Core\Renderer;
use Friendica\Core\Search;
use Friendica\DI;
use Friendica\Model\Contact;
use Friendica\Util\Network;
use Friendica\Util\Strings;
use Friendica\Util\XML;
use GuzzleHttp\Psr7\Uri;
use League\HTMLToMarkdown\HtmlConverter;
use Psr\Http\Message\UriInterface;
@ -253,13 +253,14 @@ class HTML
self::tagToBBCode($doc, 'span', ['class' => 'type-link'], '[class=type-link]', '[/class]');
self::tagToBBCode($doc, 'span', ['class' => 'type-video'], '[class=type-video]', '[/class]');
self::tagToBBCode($doc, 'strong', [], '[b]', '[/b]');
self::tagToBBCode($doc, 'em', [], '[i]', '[/i]');
self::tagToBBCode($doc, 'b', [], '[b]', '[/b]');
self::tagToBBCode($doc, 'i', [], '[i]', '[/i]');
self::tagToBBCode($doc, 'u', [], '[u]', '[/u]');
self::tagToBBCode($doc, 's', [], '[s]', '[/s]');
self::tagToBBCode($doc, 'del', [], '[s]', '[/s]');
$elements = [
'b', 'del', 'em', 'i', 'ins', 'kbd', 'mark',
's', 'samp', 'strong', 'sub', 'sup', 'u', 'var'
];
foreach ($elements as $element) {
self::tagToBBCode($doc, $element, [], '[' . $element . ']', '[/' . $element . ']');
}
self::tagToBBCode($doc, 'strike', [], '[s]', '[/s]');
self::tagToBBCode($doc, 'big', [], "[size=large]", "[/size]");
@ -406,7 +407,7 @@ class HTML
}
$parts = array_merge($base, parse_url($url));
$url2 = Network::unparseURL($parts);
$url2 = (string)Uri::fromParts((array)$parts);
return str_replace($url, $url2, $link);
}
@ -867,7 +868,7 @@ class HTML
'$save_label' => $save_label,
'$search_hint' => DI::l10n()->t('@name, !group, #tags, content'),
'$mode' => $mode,
'$return_url' => urlencode(Search::getSearchPath($s)),
'$return_url' => bin2hex(Search::getSearchPath($s)),
];
if (!$aside) {
@ -1060,4 +1061,15 @@ class HTML
return null;
}
/**
* Check if a document contains HTML or entities
*
* @param string $text
* @return boolean
*/
public static function isHTML(string $text): bool
{
return ($text != html_entity_decode($text)) || ($text != strip_tags($text));
}
}

View file

@ -32,7 +32,7 @@ use Friendica\Model\Contact;
use Friendica\Model\Circle;
use Friendica\Model\Item;
use Friendica\Model\Post;
use Friendica\Model\Profile;
use Friendica\Security\OpenWebAuth;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\Temporal;
@ -67,7 +67,7 @@ class Widget
if (DI::config()->get('system', 'invitation_only')) {
$x = intval(DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'system', 'invites_remaining'));
if ($x || DI::app()->isSiteAdmin()) {
if ($x || DI::userSession()->isSiteAdmin()) {
DI::page()['aside'] .= '<div class="side-link widget" id="side-invite-remain">'
. DI::l10n()->tt('%d invitation available', '%d invitations available', $x)
. '</div>';
@ -85,7 +85,7 @@ class Widget
$nv['random'] = DI::l10n()->t('Random Profile');
$nv['inv'] = DI::l10n()->t('Invite Friends');
$nv['directory'] = DI::l10n()->t('Global Directory');
$nv['global_dir'] = Profile::zrl($global_dir, true);
$nv['global_dir'] = OpenWebAuth::getZrlUrl($global_dir, true);
$nv['local_directory'] = DI::l10n()->t('Local Directory');
$aside = [];
@ -336,7 +336,7 @@ class Widget
*/
public static function categories(int $uid, string $baseurl, string $selected = ''): string
{
if (!Feature::isEnabled($uid, 'categories')) {
if (!Feature::isEnabled($uid, Feature::CATEGORIES)) {
return '';
}
@ -398,7 +398,7 @@ class Widget
$entries[] = [
'url' => Contact::magicLinkByContact($contact),
'name' => $contact['name'],
'photo' => Contact::getThumb($contact),
'photo' => Contact::getThumb($contact, true),
];
}
@ -428,7 +428,7 @@ class Widget
return '';
}
if (Feature::isEnabled($uid, 'tagadelic')) {
if (Feature::isEnabled($uid, Feature::TAGCLOUD)) {
$owner_id = Contact::getPublicIdByUserId($uid);
if (!$owner_id) {
@ -598,4 +598,4 @@ class Widget
$channelname
);
}
}
}

View file

@ -61,7 +61,7 @@ class SavedSearches
'$add' => '',
'$searchbox' => '',
'$saved' => $saved,
'$return_url' => urlencode($return_url),
'$return_url' => bin2hex($return_url),
]);
}
}

View file

@ -21,6 +21,7 @@
namespace Friendica\Content\Widget;
use Friendica\Content\Conversation\Entity\Community;
use Friendica\Core\Renderer;
use Friendica\DI;
use Friendica\Model\Tag;
@ -39,9 +40,9 @@ class TrendingTags
* @return string Formatted HTML code
* @throws \Exception
*/
public static function getHTML(string $content = 'global', int $period = 24): string
public static function getHTML(string $content = Community::GLOBAL, int $period = 24): string
{
if ($content == 'local') {
if ($content == Community::LOCAL) {
$tags = Tag::getLocalTrendingHashtags($period, 20);
} else {
$tags = Tag::getGlobalTrendingHashtags($period, 20);

View file

@ -43,9 +43,10 @@ class VCard
* @template widget/vcard.tpl
* @param array $contact
* @param bool $hide_mention
* @param bool $hide_follow
* @return string
*/
public static function getHTML(array $contact, bool $hide_mention = false): string
public static function getHTML(array $contact, bool $hide_mention = false, bool $hide_follow = false): string
{
if (!isset($contact['network']) || !isset($contact['id'])) {
Logger::warning('Incomplete contact', ['contact' => $contact ?? []]);
@ -87,7 +88,7 @@ class VCard
}
}
if (empty($contact['self']) && Protocol::supportsFollow($contact['network'])) {
if (!$hide_follow && empty($contact['self']) && Protocol::supportsFollow($contact['network'])) {
if (in_array($rel, [Contact::SHARING, Contact::FRIEND])) {
$unfollow_link = 'contact/unfollow?url=' . urlencode($contact_url) . '&auto=1';
} elseif (!$pending) {

View file

@ -140,7 +140,8 @@ class Addon
$func();
}
Hook::delete(['file' => 'addon/' . $addon . '/' . $addon . '.php']);
// Handles both relative and absolute file paths
Hook::delete(['`file` LIKE ?', "%addon/$addon/$addon.php"]);
unset(self::$addons[array_search($addon, self::$addons)]);
}

View file

@ -47,6 +47,7 @@ Usage: bin/console [--version] [-h|--help|-?] <command> [<args>] [-v]
Commands:
addon Addon management
cache Manage node cache
clearavatarcache Clear the file based avatar cache
config Edit site config
contact Contact management
createdoxygen Generate Doxygen headers
@ -84,6 +85,7 @@ HELP;
'archivecontact' => Friendica\Console\ArchiveContact::class,
'autoinstall' => Friendica\Console\AutomaticInstallation::class,
'cache' => Friendica\Console\Cache::class,
'clearavatarcache' => Friendica\Console\ClearAvatarCache::class,
'config' => Friendica\Console\Config::class,
'contact' => Friendica\Console\Contact::class,
'createdoxygen' => Friendica\Console\CreateDoxygen::class,

View file

@ -495,6 +495,13 @@ class Installer
);
$returnVal = $returnVal ? $status : false;
$status = $this->checkFunction('idn_to_ascii',
DI::l10n()->t('IDN Functions PHP module'),
DI::l10n()->t('Error: IDN Functions PHP module required but not installed.'),
true
);
$returnVal = $returnVal ? $status : false;
return $returnVal;
}

View file

@ -75,8 +75,11 @@ class Renderer
{
DI::profiler()->startRecording('rendering');
// pass $baseurl to all templates if it isn't set
$vars = array_merge(['$baseurl' => DI::baseUrl(), '$APP' => DI::app()], $vars);
// Default template variables
$vars = array_merge([
'$baseurl' => DI::baseUrl(),
'$VERSION' => \Friendica\App::VERSION,
], $vars);
$t = self::getTemplateEngine();
@ -84,7 +87,7 @@ class Renderer
$output = $t->replaceMacros($template, $vars);
} catch (Exception $e) {
DI::logger()->critical($e->getMessage(), ['template' => $template, 'vars' => $vars]);
$message = DI::app()->isSiteAdmin() ?
$message = DI::userSession()->isSiteAdmin() ?
$e->getMessage() :
DI::l10n()->t('Friendica can\'t display this page at the moment, please contact the administrator.');
throw new ServiceUnavailableException($message);
@ -113,7 +116,7 @@ class Renderer
$template = $t->getTemplateFile($file, $subDir);
} catch (Exception $e) {
DI::logger()->critical($e->getMessage(), ['file' => $file, 'subDir' => $subDir]);
$message = DI::app()->isSiteAdmin() ?
$message = DI::userSession()->isSiteAdmin() ?
$e->getMessage() :
DI::l10n()->t('Friendica can\'t display this page at the moment, please contact the administrator.');
throw new ServiceUnavailableException($message);
@ -142,7 +145,7 @@ class Renderer
} else {
$admin_message = DI::l10n()->t('template engine cannot be registered without a name.');
DI::logger()->critical($admin_message, ['class' => $class]);
$message = DI::app()->isSiteAdmin() ?
$message = DI::userSession()->isSiteAdmin() ?
$admin_message :
DI::l10n()->t('Friendica can\'t display this page at the moment, please contact the administrator.');
throw new ServiceUnavailableException($message);
@ -176,7 +179,7 @@ class Renderer
$admin_message = DI::l10n()->t('template engine is not registered!');
DI::logger()->critical($admin_message, ['template_engine' => $template_engine]);
$message = DI::app()->isSiteAdmin() ?
$message = DI::userSession()->isSiteAdmin() ?
$admin_message :
DI::l10n()->t('Friendica can\'t display this page at the moment, please contact the administrator.');
throw new ServiceUnavailableException($message);

View file

@ -24,6 +24,8 @@ namespace Friendica\Core;
use Friendica\DI;
use Friendica\Model\Contact;
use Friendica\Network\HTTPClient\Client\HttpClientAccept;
use Friendica\Network\HTTPClient\Client\HttpClientOptions;
use Friendica\Network\HTTPClient\Client\HttpClientRequest;
use Friendica\Network\HTTPException;
use Friendica\Object\Search\ContactResult;
use Friendica\Object\Search\ResultList;
@ -125,7 +127,7 @@ class Search
$searchUrl .= '&page=' . $page;
}
$resultJson = DI::httpClient()->fetch($searchUrl, HttpClientAccept::JSON);
$resultJson = DI::httpClient()->fetch($searchUrl, HttpClientAccept::JSON, 0, '', HttpClientRequest::CONTACTDISCOVER);
$results = json_decode($resultJson, true);
@ -232,7 +234,7 @@ class Search
$return = Contact::searchByName($search, $mode, true);
} else {
$p = $page > 1 ? 'p=' . $page : '';
$curlResult = DI::httpClient()->get(self::getGlobalDirectory() . '/search/people?' . $p . '&q=' . urlencode($search), HttpClientAccept::JSON);
$curlResult = DI::httpClient()->get(self::getGlobalDirectory() . '/search/people?' . $p . '&q=' . urlencode($search), HttpClientAccept::JSON, [HttpClientOptions::REQUEST => HttpClientRequest::CONTACTDISCOVER]);
if ($curlResult->isSuccess()) {
$searchResult = json_decode($curlResult->getBodyString(), true);
if (!empty($searchResult['profiles'])) {

View file

@ -80,9 +80,9 @@ interface IHandleUserSessions extends IHandleSessions
public function getMyUrl(): string;
/**
* Returns if the current visitor is authenticated
* Returns if the current visitor is a local user
*
* @return bool "true" when visitor is either a local or remote user
* @return bool "true" when visitor is a local user
*/
public function isAuthenticated(): bool;
@ -100,6 +100,20 @@ interface IHandleUserSessions extends IHandleSessions
*/
public function isModerator(): bool;
/**
* Returns if the current visitor is a verified remote user
*
* @return bool "true" when visitor is a verified remote user
*/
public function isVisitor(): bool;
/**
* Returns if the current visitor is an unauthenticated user
*
* @return bool "true" when visitor is an unauthenticated user
*/
public function isUnauthenticated(): bool;
/**
* Returns User ID of the managed user in case it's a different identity
*

View file

@ -130,7 +130,7 @@ class UserSession implements IHandleUserSessions
/** {@inheritDoc} */
public function isAuthenticated(): bool
{
return $this->session->get('authenticated', false);
return $this->session->get('authenticated', false) && $this->getLocalUserId();
}
/** {@inheritDoc} */
@ -145,6 +145,18 @@ class UserSession implements IHandleUserSessions
return User::isModerator($this->getLocalUserId());
}
/** {@inheritDoc} */
public function isVisitor(): bool
{
return $this->session->get('authenticated', false) && $this->session->get('visitor_id') && !$this->session->get('uid');
}
/** {@inheritDoc} */
public function isUnauthenticated(): bool
{
return !$this->session->get('authenticated', false);
}
/** {@inheritDoc} */
public function setVisitorsContacts(string $my_url)
{

View file

@ -21,6 +21,7 @@
namespace Friendica\Core;
use Friendica\Core\Cache\Enum\Duration;
use Friendica\Core\Worker\Entity\Process;
use Friendica\Database\DBA;
use Friendica\DI;
@ -54,7 +55,8 @@ class Worker
const FAST_COMMANDS = ['APDelivery', 'Delivery'];
const LOCK_PROCESS = 'worker_process';
const LOCK_WORKER = 'worker';
const LOCK_WORKER = 'worker';
const LAST_CHECK = 'worker::check';
private static $up_start;
private static $db_duration = 0;
@ -233,7 +235,7 @@ class Worker
* @return integer Number of deferred entries in the worker queue
* @throws \Exception
*/
private static function deferredEntries(): int
public static function deferredEntries(): int
{
$stamp = (float)microtime(true);
$count = DBA::count('workerqueue', ["NOT `done` AND `pid` = 0 AND `retrial` > ?", 0]);
@ -248,7 +250,7 @@ class Worker
* @return integer Number of non executed entries in the worker queue
* @throws \Exception
*/
private static function totalEntries(): int
public static function totalEntries(): int
{
$stamp = (float)microtime(true);
$count = DBA::count('workerqueue', ['done' => false, 'pid' => 0]);
@ -832,6 +834,17 @@ class Worker
} else {
self::spawnWorker();
}
} elseif (($active > $queues) && ($active < $maxqueues) && ($load < $maxsysload)) {
$max_idletime = DI::config()->get('system', 'worker_max_idletime');
$last_check = DI::cache()->get(self::LAST_CHECK);
$last_date = $last_check ? date('c', $last_check) : '';
if (($max_idletime > 0) && (time() > $last_check + $max_idletime) && !DBA::exists('workerqueue', ["`done` AND `executed` > ?", DateTimeFormat::utc('now - ' . $max_idletime . ' second')])) {
DI::cache()->set(self::LAST_CHECK, time(), Duration::HOUR);
Logger::info('The last worker execution had been too long ago.', ['last' => $last_check, 'last-check' => $last_date, 'seconds' => $max_idletime, 'load' => $load, 'max_load' => $maxsysload, 'active_worker' => $active, 'max_worker' => $maxqueues]);
return false;
} elseif ($max_idletime > 0) {
Logger::debug('Maximum idletime not reached.', ['last' => $last_check, 'last-check' => $last_date, 'seconds' => $max_idletime, 'load' => $load, 'max_load' => $maxsysload, 'active_worker' => $active, 'max_worker' => $maxqueues]);
}
}
}
@ -849,7 +862,7 @@ class Worker
* @return integer Number of active worker processes
* @throws \Exception
*/
private static function activeWorkers(): int
public static function activeWorkers(): int
{
$stamp = (float)microtime(true);
$count = DI::process()->countCommand('Worker.php');

View file

@ -66,7 +66,7 @@ abstract class DI
public static function setCompositeRootDependencyByHand()
{
$database = static::dba();
$database->setDependency(static::config(), static::profiler(), static::logger());
$database->setDependency(static::config(), static::profiler(), static::logger(), static::lock());
}
/**

View file

@ -21,6 +21,7 @@
namespace Friendica\Database;
use Friendica\Core\Lock\Exception\LockPersistenceException;
use Friendica\DI;
use mysqli;
use mysqli_result;
@ -823,6 +824,27 @@ class DBA
return DI::dba()->optimizeTable($table);
}
/**
* Acquire a lock to prevent a table optimization
*
* @return bool
* @throws LockPersistenceException
*/
public static function acquireOptimizeLock(): bool
{
return DI::dba()->acquireOptimizeLock();
}
/**
* Release the table optimization lock
* @return bool
* @throws LockPersistenceException
*/
public static function releaseOptimizeLock(): bool
{
return DI::dba()->releaseOptimizeLock();
}
/**
* Kill sleeping database processes
*/

View file

@ -22,6 +22,8 @@
namespace Friendica\Database;
use Friendica\Core\Config\Capability\IManageConfigValues;
use Friendica\Core\Lock\Capability\ICanLock;
use Friendica\Core\Lock\Exception\LockPersistenceException;
use Friendica\Core\System;
use Friendica\Database\Definition\DbaDefinition;
use Friendica\Database\Definition\ViewDefinition;
@ -50,6 +52,8 @@ class Database
const INSERT_UPDATE = 1;
const INSERT_IGNORE = 2;
const LOCK_OPTIMIZE = 'database::optimize_tables';
protected $connected = false;
/**
@ -64,6 +68,11 @@ class Database
* @var LoggerInterface
*/
protected $logger = null;
/**
* @var ICanLock
*/
protected $syslock = null;
protected $server_info = '';
/** @var PDO|mysqli */
protected $connection;
@ -106,11 +115,12 @@ class Database
*
* @todo Make this method obsolete - use a clean pattern instead ...
*/
public function setDependency(IManageConfigValues $config, Profiler $profiler, LoggerInterface $logger)
public function setDependency(IManageConfigValues $config, Profiler $profiler, LoggerInterface $logger, ICanLock $lock)
{
$this->logger = $logger;
$this->profiler = $profiler;
$this->config = $config;
$this->syslock = $lock;
}
/**
@ -1755,7 +1765,42 @@ class Database
*/
public function optimizeTable(string $table): bool
{
return $this->e("OPTIMIZE TABLE " . DBA::buildTableString([$table])) !== false;
if ($this->syslock->isLocked(self::LOCK_OPTIMIZE)) {
$this->logger->info('Optimization is locked');
return false;
}
if (!$this->acquireOptimizeLock()) {
return false;
}
$result = $this->e("OPTIMIZE TABLE " . DBA::buildTableString([$table])) !== false;
$this->releaseOptimizeLock();
return $result;
}
/**
* Acquire a lock to prevent a table optimization
*
* @return bool
* @throws LockPersistenceException
*/
public function acquireOptimizeLock(): bool
{
return $this->syslock->acquire(self::LOCK_OPTIMIZE, 0);
}
/**
* Release the table optimization lock
*
* @return bool
* @throws LockPersistenceException
*/
public function releaseOptimizeLock(): bool
{
return $this->syslock->release(self::LOCK_OPTIMIZE);
}
/**

View file

@ -96,7 +96,6 @@ class Activities extends BaseFactory
break;
default:
$this->logger->warning('Unsupported verb in parent item:', ['parent_item' => $parent_item]);
break;
}
}

View file

@ -23,6 +23,7 @@ namespace Friendica\Factory\Api\Mastodon;
use Friendica\App\BaseURL;
use Friendica\BaseFactory;
use Friendica\Model\Attach;
use Friendica\Model\Photo;
use Friendica\Network\HTTPException;
use Friendica\Model\Post;
@ -144,4 +145,37 @@ class Attachment extends BaseFactory
$object = new \Friendica\Object\Api\Mastodon\Attachment($attachment, 'image', $url, $preview_url, '');
return $object->toArray();
}
/**
* @param int $id id of the attachment
*
* @return array
* @throws HTTPException\InternalServerErrorException
*/
public function createFromAttach(int $id): array
{
$media = Attach::selectFirst(['id', 'filetype'], ['id' => $id]);
if (empty($media)) {
return [];
}
$attachment = [
'id' => 'attach:' . $media['id'],
'description' => null,
'blurhash' => null,
];
$types = [Post\Media::AUDIO => 'audio', Post\Media::VIDEO => 'video', Post\Media::IMAGE => 'image'];
$type = Post\Media::getType($media['filetype']);
$url = $this->baseUrl . '/attach/' . $id;
$object = new \Friendica\Object\Api\Mastodon\Attachment($attachment, $types[$type] ?? 'unknown', $url, '', '');
return $object->toArray();
}
public function isAttach(string $id): bool
{
return substr($id, 0, 7) == 'attach:';
}
}

View file

@ -22,6 +22,7 @@
namespace Friendica\Factory\Api\Mastodon;
use Friendica\BaseFactory;
use Friendica\Content\Conversation\Entity\Timeline;
use Friendica\Database\Database;
use Friendica\Network\HTTPException\InternalServerErrorException;
use Psr\Log\LoggerInterface;
@ -45,4 +46,14 @@ class ListEntity extends BaseFactory
$circle = $this->dba->selectFirst('group', ['name'], ['id' => $id, 'deleted' => false]);
return new \Friendica\Object\Api\Mastodon\ListEntity($id, $circle['name'] ?? '', 'list');
}
public function createFromChannel(Timeline $channel): \Friendica\Object\Api\Mastodon\ListEntity
{
return new \Friendica\Object\Api\Mastodon\ListEntity('channel:' . $channel->code, $channel->label, 'followed');
}
public function createFromGroup(array $group): \Friendica\Object\Api\Mastodon\ListEntity
{
return new \Friendica\Object\Api\Mastodon\ListEntity('group:' . $group['id'], $group['name'], 'followed');
}
}

View file

@ -27,6 +27,7 @@ use Friendica\Content\Item as ContentItem;
use Friendica\Content\Smilies;
use Friendica\Content\Text\BBCode;
use Friendica\Core\Logger;
use Friendica\Core\Protocol;
use Friendica\Database\Database;
use Friendica\Database\DBA;
use Friendica\DI;
@ -105,7 +106,7 @@ class Status extends BaseFactory
*/
public function createFromUriId(int $uriId, int $uid = 0, bool $display_quote = false, bool $reblog = true, bool $in_reply_status = true): \Friendica\Object\Api\Mastodon\Status
{
$fields = ['uri-id', 'uid', 'author-id', 'causer-id', 'author-uri-id', 'author-link', 'causer-uri-id', 'post-reason', 'starred', 'app', 'title', 'body', 'raw-body', 'content-warning', 'question-id',
$fields = ['uri-id', 'uid', 'author-id', 'causer-id', 'author-uri-id', 'author-link', 'author-gsid', 'causer-uri-id', 'post-reason', 'starred', 'app', 'title', 'body', 'raw-body', 'content-warning', 'question-id',
'created', 'edited', 'commented', 'received', 'changed', 'network', 'thr-parent-id', 'parent-author-id', 'language', 'uri', 'plink', 'private', 'vid', 'gravity', 'featured', 'has-media', 'quote-uri-id',
'delivery_queue_count', 'delivery_queue_done','delivery_queue_failed', 'allow_cid', 'deny_cid', 'allow_gid', 'deny_gid', 'sensitive'];
$item = Post::selectFirst($fields, ['uri-id' => $uriId, 'uid' => [0, $uid]], ['order' => ['uid' => true]]);
@ -212,8 +213,27 @@ class Status extends BaseFactory
$item['featured']
);
$sensitive = (bool)$item['sensitive'];
$application = new \Friendica\Object\Api\Mastodon\Application($item['app'] ?: ContactSelector::networkToName($item['network'], $item['author-link']));
$sensitive = (bool)$item['sensitive'];
$network = ContactSelector::networkToName($item['network']);
$sitename = '';
$platform = '';
$version = '';
if (in_array($item['network'], Protocol::FEDERATED)) {
$gserver = $this->dba->selectFirst('gserver', ['site_name', 'platform', 'version'], ['id' => $item['author-gsid']]);
if (!empty($gserver)) {
$platform = ucfirst($gserver['platform']);
$version = $gserver['version'];
$sitename = $gserver['site_name'];
}
}
if ($platform == '') {
$platform = ContactSelector::networkToName($item['network'], $item['author-link'], $item['network'], $item['author-gsid']);
}
$application = new \Friendica\Object\Api\Mastodon\Application($item['app'] ?: $platform);
$mentions = $this->mstdnMentionFactory->createFromUriId($uriId)->getArrayCopy();
$tags = $this->mstdnTagFactory->createFromUriId($uriId);
@ -322,7 +342,7 @@ class Status extends BaseFactory
$delivery_data = $uid != $item['uid'] ? null : new FriendicaDeliveryData($item['delivery_queue_count'], $item['delivery_queue_done'], $item['delivery_queue_failed']);
$visibility_data = $uid != $item['uid'] ? null : new FriendicaVisibility($this->aclFormatter->expand($item['allow_cid']), $this->aclFormatter->expand($item['deny_cid']), $this->aclFormatter->expand($item['allow_gid']), $this->aclFormatter->expand($item['deny_gid']));
$friendica = new FriendicaExtension($item['title'] ?? '', $item['changed'], $item['commented'], $item['received'], $counts->dislikes, $origin_dislike, $delivery_data, $visibility_data);
$friendica = new FriendicaExtension($item['title'] ?? '', $item['changed'], $item['commented'], $item['received'], $counts->dislikes, $origin_dislike, $network, $platform, $version, $sitename, $delivery_data, $visibility_data);
return new \Friendica\Object\Api\Mastodon\Status($item, $account, $counts, $userAttributes, $sensitive, $application, $mentions, $tags, $card, $attachments, $in_reply, $reshare, $friendica, $quote, $poll, $emojis);
}
@ -393,7 +413,7 @@ class Status extends BaseFactory
$attachments = [];
$in_reply = [];
$reshare = [];
$friendica = new FriendicaExtension('', null, null, null, 0, false, null, null);
$friendica = new FriendicaExtension('', null, null, null, 0, false, null, null, null, null, null, null);
return new \Friendica\Object\Api\Mastodon\Status($item, $account, $counts, $userAttributes, $sensitive, $application, $mentions, $tags, $card, $attachments, $in_reply, $reshare, $friendica);
}

View file

@ -84,13 +84,9 @@ class APContact
if (!empty($link['template']) && ($link['rel'] == ActivityNamespace::OSTATUSSUB)) {
$data['subscribe'] = $link['template'];
}
if (!empty($link['href']) && !empty($link['type']) && ($link['rel'] == 'self') && ($link['type'] == 'application/activity+json')) {
} elseif (!empty($link['href']) && !empty($link['type']) && ($link['rel'] == 'self') && ($link['type'] == 'application/activity+json')) {
$data['url'] = $link['href'];
}
if (!empty($link['href']) && !empty($link['type']) && ($link['rel'] == 'http://webfinger.net/rel/profile-page') && ($link['type'] == 'text/html')) {
} elseif (!empty($link['href']) && !empty($link['type']) && ($link['rel'] == ActivityNamespace::WEBFINGERPROFILE) && ($link['type'] == 'text/html')) {
$data['alias'] = $link['href'];
}
}
@ -105,14 +101,12 @@ class APContact
/**
* Fetches a profile from a given url
*
* @param string $url profile url
* @param boolean $update true = always update, false = never update, null = update when not found or outdated
* @param string $url profile url
* @param ?boolean $update true = always update, false = never update, null = update when not found or outdated
* @return array profile array
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
* @throws \ImagickException
* @todo Rewrite parameter $update to avoid true|false|null (boolean is binary, null adds a third case)
*/
public static function getByURL(string $url, $update = null): array
public static function getByURL(string $url, bool $update = null): array
{
if (empty($url) || Network::isUrlBlocked($url)) {
Logger::info('Domain is blocked', ['url' => $url]);
@ -184,7 +178,7 @@ class APContact
DI::cache()->set($cachekey, System::callstack(20), Duration::FIVE_MINUTES);
}
if (Network::isLocalLink($url) && ($local_uid = User::getIdForURL($url))) {
if (DI::baseUrl()->isLocalUrl($url) && ($local_uid = User::getIdForURL($url))) {
try {
$data = Transmitter::getProfile($local_uid);
$local_owner = User::getOwnerDataById($local_uid);
@ -201,7 +195,7 @@ class APContact
$failed = empty($curlResult) || empty($curlResult->getBodyString()) ||
(!$curlResult->isSuccess() && ($curlResult->getReturnCode() != 410));
if (!$failed) {
if (!$failed) {
$data = json_decode($curlResult->getBodyString(), true);
$failed = empty($data) || !is_array($data);
}
@ -293,6 +287,7 @@ class APContact
} elseif ($apcontact['type'] == 'Tombstone') {
// The "inbox" field must have a content
$apcontact['inbox'] = '';
$apcontact['addr'] = '';
}
// Quit if this doesn't seem to be an account at all
@ -300,7 +295,7 @@ class APContact
return $fetched_contact;
}
if (empty($apcontact['addr'])) {
if (empty($apcontact['addr']) && ($apcontact['type'] != 'Tombstone')) {
try {
$apcontact['addr'] = $apcontact['nick'] . '@' . (new Uri($apcontact['url']))->getAuthority();
} catch (\Throwable $e) {

View file

@ -30,6 +30,7 @@ use Friendica\Object\Image;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\Mimetype;
use Friendica\Security\Security;
use Friendica\Util\Network;
/**
* Class to handle attach database table
@ -109,16 +110,17 @@ class Attach
/**
* Retrieve a single record given the ID
*
* @param int $id Row id of the record
* @param int $id Row id of the record
* @param int $uid User-Id
*
* @return bool|array
*
* @throws \Exception
* @see \Friendica\Database\DBA::select
*/
public static function getById(int $id)
public static function getById(int $id, int $uid)
{
return self::selectFirst([], ['id' => $id]);
return self::selectFirst([], ['id' => $id, 'uid' => $uid]);
}
/**
@ -197,7 +199,7 @@ class Attach
* @return boolean|integer Row id on success, False on errors
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function store(string $data, int $uid, string $filename, string $filetype = '' , int $filesize = null, string $allow_cid = '', string $allow_gid = '', string $deny_cid = '', string $deny_gid = '')
public static function store(string $data, int $uid, string $filename, string $filetype = '', int $filesize = null, string $allow_cid = '', string $allow_gid = '', string $deny_cid = '', string $deny_gid = '')
{
if ($filetype === '') {
$filetype = Mimetype::getContentType($filename);
@ -243,6 +245,7 @@ class Attach
* @param string $src Source file name
* @param int $uid User id
* @param string $filename Optional file name
* @param string $filetype Optional file type
* @param string $allow_cid
* @param string $allow_gid
* @param string $deny_cid
@ -250,7 +253,7 @@ class Attach
* @return boolean|int Insert id or false on failure
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function storeFile(string $src, int $uid, string $filename = '', string $allow_cid = '', string $allow_gid = '', string $deny_cid = '', string $deny_gid = '')
public static function storeFile(string $src, int $uid, string $filename = '', string $filetype = '', string $allow_cid = '', string $allow_gid = '', string $deny_cid = '', string $deny_gid = '')
{
if ($filename === '') {
$filename = basename($src);
@ -258,7 +261,7 @@ class Attach
$data = @file_get_contents($src);
return self::store($data, $uid, $filename, '', null, $allow_cid, $allow_gid, $deny_cid, $deny_gid);
return self::store($data, $uid, $filename, $filetype, null, $allow_cid, $allow_gid, $deny_cid, $deny_gid);
}
@ -279,9 +282,9 @@ class Attach
{
if (!is_null($img)) {
// get items to update
$items = self::selectToArray(['backend-class','backend-ref'], $conditions);
$items = self::selectToArray(['backend-class', 'backend-ref'], $conditions);
foreach($items as $item) {
foreach ($items as $item) {
try {
$backend_class = DI::storageManager()->getWritableStorageByName($item['backend-class'] ?? '');
$fields['backend-ref'] = $backend_class->put($img->asString(), $item['backend-ref'] ?? '');
@ -313,9 +316,9 @@ class Attach
public static function delete(array $conditions, array $options = []): bool
{
// get items to delete data info
$items = self::selectToArray(['backend-class','backend-ref'], $conditions);
$items = self::selectToArray(['backend-class', 'backend-ref'], $conditions);
foreach($items as $item) {
foreach ($items as $item) {
try {
$backend_class = DI::storageManager()->getWritableStorageByName($item['backend-class'] ?? '');
$backend_class->delete($item['backend-ref'] ?? '');
@ -328,4 +331,51 @@ class Attach
return DBA::delete('attach', $conditions, $options);
}
public static function setPermissionFromBody(array $post)
{
preg_match_all("/\[attachment\](.*?)\[\/attachment\]/ism", $post['body'], $matches, PREG_SET_ORDER);
foreach ($matches as $attachment) {
if (DI::baseUrl()->isLocalUrl($attachment[1]) && preg_match('|.*?/attach/(\d+)|', $attachment[1], $match)) {
$fields = [
'allow_cid' => $post['allow_cid'], 'allow_gid' => $post['allow_gid'],
'deny_cid' => $post['deny_cid'], 'deny_gid' => $post['deny_gid']
];
self::update($fields, ['id' => $match[1], 'uid' => $post['uid']]);
}
}
}
public static function setPermissionForId(int $id, int $uid, string $str_contact_allow, string $str_circle_allow, string $str_contact_deny, string $str_circle_deny)
{
$fields = [
'allow_cid' => $str_contact_allow, 'allow_gid' => $str_circle_allow,
'deny_cid' => $str_contact_deny, 'deny_gid' => $str_circle_deny,
];
self::update($fields, ['id' => $id, 'uid' => $uid]);
}
public static function addAttachmentToBody(string $body, int $uid): string
{
preg_match_all("/\[attachment\](.*?)\[\/attachment\]/ism", $body, $matches, PREG_SET_ORDER);
foreach ($matches as $attachment) {
if (DI::baseUrl()->isLocalUrl($attachment[1]) && preg_match('|.*?/attach/(\d+)|', $attachment[1], $match)) {
$attach = self::getById($match[1], $uid);
if (empty($attach)) {
return $body;
}
$media = [
'type' => Post\Media::DOCUMENT,
'url' => $attachment[1],
'size' => $attach['filesize'],
'mimetype' => $attach['filetype'],
'description' => $attach['filename']
];
$media = Post\Media::addType($media);
$body = str_replace($attachment[0], Post\Media::addAttachmentToBody($media, ''), $body);
}
}
return $body;
}
}

View file

@ -290,12 +290,12 @@ class Circle
throw new HTTPException\NotFoundException('Circle not found.');
}
$cdata = Contact::getPublicAndUserContactID($cid, $circle['uid']);
if (empty($cdata['user'])) {
$ucid = Contact::getUserContactId($cid, $circle['uid']);
if (!$ucid) {
throw new HTTPException\NotFoundException('Invalid contact.');
}
return DBA::insert('group_member', ['gid' => $gid, 'contact-id' => $cdata['user']], Database::INSERT_IGNORE);
return DBA::insert('group_member', ['gid' => $gid, 'contact-id' => $ucid], Database::INSERT_IGNORE);
}
/**
@ -318,12 +318,12 @@ class Circle
throw new HTTPException\NotFoundException('Circle not found.');
}
$cdata = Contact::getPublicAndUserContactID($cid, $circle['uid']);
if (empty($cdata['user'])) {
$ucid = Contact::getUserContactId($cid, $circle['uid']);
if (!$ucid) {
throw new HTTPException\NotFoundException('Invalid contact.');
}
return DBA::delete('group_member', ['gid' => $gid, 'contact-id' => $cid]);
return DBA::delete('group_member', ['gid' => $gid, 'contact-id' => $ucid]);
}
/**
@ -347,12 +347,12 @@ class Circle
}
foreach ($contacts as $cid) {
$cdata = Contact::getPublicAndUserContactID($cid, $circle['uid']);
if (empty($cdata['user'])) {
$ucid = Contact::getUserContactId($cid, $circle['uid']);
if (!$ucid) {
throw new HTTPException\NotFoundException('Invalid contact.');
}
DBA::insert('group_member', ['gid' => $gid, 'contact-id' => $cdata['user']], Database::INSERT_IGNORE);
DBA::insert('group_member', ['gid' => $gid, 'contact-id' => $ucid], Database::INSERT_IGNORE);
}
}
@ -379,12 +379,12 @@ class Circle
$contactIds = [];
foreach ($contacts as $cid) {
$cdata = Contact::getPublicAndUserContactID($cid, $circle['uid']);
if (empty($cdata['user'])) {
$ucid = Contact::getUserContactId($cid, $circle['uid']);
if (!$ucid) {
throw new HTTPException\NotFoundException('Invalid contact.');
}
$contactIds[] = $cdata['user'];
$contactIds[] = $ucid;
}
// Return status of deletion

View file

@ -113,15 +113,6 @@ class Contact
* @}
*/
/** @deprecated Use Entity\LocalRelationship::MIRROR_DEACTIVATED instead */
const MIRROR_DEACTIVATED = LocalRelationship::MIRROR_DEACTIVATED;
/** @deprecated Now does the same as MIRROR_OWN_POST */
const MIRROR_FORWARDED = 1;
/** @deprecated Use Entity\LocalRelationship::MIRROR_OWN_POST instead */
const MIRROR_OWN_POST = LocalRelationship::MIRROR_OWN_POST;
/** @deprecated Use Entity\LocalRelationship::MIRROR_NATIVE_RESHARE instead */
const MIRROR_NATIVE_RESHARE = LocalRelationship::MIRROR_NATIVE_RESHARE;
/**
* @param array $fields Array of selected fields, empty for all
* @param array $condition Array of fields for condition
@ -453,12 +444,12 @@ class Contact
return false;
}
$cdata = self::getPublicAndUserContactID($cid, $uid);
if (empty($cdata['user'])) {
$ucid = self::getUserContactId($cid, $uid);
if (!$ucid) {
return false;
}
$condition = ['id' => $cdata['user'], 'rel' => [self::FOLLOWER, self::FRIEND]];
$condition = ['id' => $ucid, 'rel' => [self::FOLLOWER, self::FRIEND]];
if ($strict) {
$condition = array_merge($condition, ['pending' => false, 'readonly' => false, 'blocked' => false]);
}
@ -504,12 +495,12 @@ class Contact
return false;
}
$cdata = self::getPublicAndUserContactID($cid, $uid);
if (empty($cdata['user'])) {
$ucid = self::getUserContactId($cid, $uid);
if (!$ucid) {
return false;
}
$condition = ['id' => $cdata['user'], 'rel' => [self::SHARING, self::FRIEND]];
$condition = ['id' => $ucid, 'rel' => [self::SHARING, self::FRIEND]];
if ($strict) {
$condition = array_merge($condition, ['pending' => false, 'readonly' => false, 'blocked' => false]);
}
@ -635,11 +626,8 @@ class Contact
*/
public static function getPublicIdByUserId(int $uid)
{
$self = DBA::selectFirst('contact', ['url'], ['self' => true, 'uid' => $uid]);
if (!DBA::isResult($self)) {
return false;
}
return self::getIdForURL($self['url']);
$self = self::selectFirstAccountUser(['pid'], ['self' => true, 'uid' => $uid]);
return $self['pid'] ?? false;
}
/**
@ -683,6 +671,32 @@ class Contact
return ['public' => $pcid, 'user' => $ucid];
}
/**
* Returns the public contact id of a provided contact id
*
* @param integer $cid
* @param integer $uid
* @return integer
*/
public static function getPublicContactId(int $cid, int $uid): int
{
$contact = DBA::selectFirst('account-user-view', ['pid'], ['id' => $cid, 'uid' => [0, $uid]]);
return $contact['pid'] ?? 0;
}
/**
* Returns the user contact id of a provided contact id
*
* @param integer $cid
* @param integer $uid
* @return integer
*/
public static function getUserContactId(int $cid, int $uid): int
{
$data = self::getPublicAndUserContactID($cid, $uid);
return $data['user'] ?? 0;
}
/**
* Helper function for "getPublicAndUserContactID"
*
@ -892,10 +906,10 @@ class Contact
$fields['avatar'] = User::getAvatarUrl($user);
$fields['header'] = User::getBannerUrl($user);
$fields['forum'] = $user['page-flags'] == User::PAGE_FLAGS_COMMUNITY;
$fields['forum'] = in_array($user['page-flags'], [User::PAGE_FLAGS_COMMUNITY, User::PAGE_FLAGS_COMM_MAN]);
$fields['prv'] = $user['page-flags'] == User::PAGE_FLAGS_PRVGROUP;
$fields['unsearchable'] = !$profile['net-publish'];
$fields['manually-approve'] = in_array($user['page-flags'], [User::PAGE_FLAGS_NORMAL, User::PAGE_FLAGS_PRVGROUP]);
$fields['manually-approve'] = in_array($user['page-flags'], [User::PAGE_FLAGS_NORMAL, User::PAGE_FLAGS_PRVGROUP, User::PAGE_FLAGS_COMM_MAN]);
$fields['baseurl'] = DI::baseUrl();
$fields['gsid'] = GServer::getID($fields['baseurl'], true);
@ -980,13 +994,13 @@ class Contact
}
if (in_array($contact['rel'], [self::SHARING, self::FRIEND])) {
$cdata = self::getPublicAndUserContactID($contact['id'], $contact['uid']);
if (!empty($cdata['public'])) {
Worker::add(Worker::PRIORITY_HIGH, 'Contact\Unfollow', $cdata['public'], $contact['uid']);
$pcid = self::getPublicContactId($contact['id'], $contact['uid']);
if ($pcid) {
Worker::add(Worker::PRIORITY_HIGH, 'Contact\Unfollow', $pcid, $contact['uid']);
}
}
self::removeSharer($contact);
self::removeSharer($contact, false);
}
/**
@ -1010,13 +1024,13 @@ class Contact
}
if (in_array($contact['rel'], [self::FOLLOWER, self::FRIEND])) {
$cdata = self::getPublicAndUserContactID($contact['id'], $contact['uid']);
if (!empty($cdata['public'])) {
Worker::add(Worker::PRIORITY_HIGH, 'Contact\RevokeFollow', $cdata['public'], $contact['uid']);
$pcid = self::getPublicContactId($contact['id'], $contact['uid']);
if ($pcid) {
Worker::add(Worker::PRIORITY_HIGH, 'Contact\RevokeFollow', $pcid, $contact['uid']);
}
}
self::removeFollower($contact);
self::removeFollower($contact, false);
}
/**
@ -1037,14 +1051,14 @@ class Contact
throw new \InvalidArgumentException('Unexpected public contact record');
}
$cdata = self::getPublicAndUserContactID($contact['id'], $contact['uid']);
$pcid = self::getPublicContactId($contact['id'], $contact['uid']);
if (in_array($contact['rel'], [self::SHARING, self::FRIEND]) && !empty($cdata['public'])) {
Worker::add(Worker::PRIORITY_HIGH, 'Contact\Unfollow', $cdata['public'], $contact['uid']);
if (in_array($contact['rel'], [self::SHARING, self::FRIEND]) && $pcid) {
Worker::add(Worker::PRIORITY_HIGH, 'Contact\Unfollow', $pcid, $contact['uid']);
}
if (in_array($contact['rel'], [self::FOLLOWER, self::FRIEND]) && !empty($cdata['public'])) {
Worker::add(Worker::PRIORITY_HIGH, 'Contact\RevokeFollow', $cdata['public'], $contact['uid']);
if (in_array($contact['rel'], [self::FOLLOWER, self::FRIEND]) && $pcid) {
Worker::add(Worker::PRIORITY_HIGH, 'Contact\RevokeFollow', $pcid, $contact['uid']);
}
self::remove($contact['id']);
@ -1455,6 +1469,7 @@ class Contact
}
}
GServer::updateFromProbeArray($data);
self::updateFromProbeArray($contact_id, $data);
// Don't return a number for a deleted account
@ -1558,24 +1573,25 @@ class Contact
/**
* Returns posts from a given contact url
*
* @param string $contact_url Contact URL
* @param bool $thread_mode
* @param int $update Update mode
* @param int $parent Item parent ID for the update mode
* @param bool $only_media Only display media content
* @param string $contact_url Contact URL
* @param int $uid User ID
* @param bool $only_media Only display media content
* @param string $last_created Newest creation date, used for paging
* @return string posts in HTML
* @throws \Exception
*/
public static function getPostsFromUrl(string $contact_url, int $uid, bool $only_media = false): string
public static function getPostsFromUrl(string $contact_url, int $uid, bool $only_media = false, string $last_created = null): string
{
return self::getPostsFromId(self::getIdForURL($contact_url), $uid, $only_media);
return self::getPostsFromId(self::getIdForURL($contact_url), $uid, $only_media, $last_created);
}
/**
* Returns posts from a given contact id
*
* @param int $cid Contact ID
* @param bool $only_media Only display media content
* @param int $cid Contact ID
* @param int $uid User ID
* @param bool $only_media Only display media content
* @param string $last_created Newest creation date, used for paging
* @return string posts in HTML
* @throws \Exception
*/
@ -1793,7 +1809,7 @@ class Contact
return;
}
if (Network::isLocalLink($contact['url'])) {
if (DI::baseUrl()->isLocalUrl($contact['url'])) {
return;
}
@ -1910,7 +1926,7 @@ class Contact
return $contact;
}
$local = !empty($contact['url']) && Network::isLocalLink($contact['url']);
$local = !empty($contact['url']) && DI::baseUrl()->isLocalUrl($contact['url']);
if (!$local && !empty($contact['id']) && !empty($contact['avatar'])) {
self::updateAvatar($contact['id'], $contact['avatar'], true);
@ -2300,7 +2316,7 @@ class Contact
if (($uid == 0) && !$force && empty($contact['thumb']) && empty($contact['micro']) && !$create_cache) {
if (($contact['avatar'] != $avatar) || empty($contact['blurhash'])) {
$update_fields = ['avatar' => $avatar];
if (!Network::isLocalLink($avatar)) {
if (!DI::baseUrl()->isLocalUrl($avatar)) {
try {
$fetchResult = HTTPSignature::fetchRaw($avatar, 0, [HttpClientOptions::ACCEPT_CONTENT => [HttpClientAccept::IMAGE]]);
@ -2348,7 +2364,7 @@ class Contact
$cache_avatar = DI::config()->get('system', 'cache_contact_avatar');
// Local contact avatars don't need to be cached
if ($cache_avatar && Network::isLocalLink($contact['url'])) {
if ($cache_avatar && DI::baseUrl()->isLocalUrl($contact['url'])) {
$cache_avatar = !DBA::exists('contact', ['nurl' => $contact['nurl'], 'self' => true]);
}
@ -2664,6 +2680,14 @@ class Contact
$data = Probe::uri($contact['url'], $network, $contact['uid']);
if (in_array($data['network'], Protocol::FEDERATED) && (parse_url($data['url'], PHP_URL_SCHEME) == 'http')) {
$ssl_url = str_replace('http://', 'https://', $contact['url']);
$ssl_data = Probe::uri($ssl_url, $network, $contact['uid']);
if (($ssl_data['network'] == $data['network']) && (parse_url($ssl_data['url'], PHP_URL_SCHEME) != 'http')) {
$data = $ssl_data;
}
}
if ($data['network'] == Protocol::DIASPORA) {
try {
DI::dsprContact()->updateFromProbeArray($data);
@ -2682,6 +2706,7 @@ class Contact
}
}
GServer::updateFromProbeArray($data);
return self::updateFromProbeArray($id, $data);
}
@ -2821,7 +2846,7 @@ class Contact
// We must not try to update relay contacts via probe. They are no real contacts.
// See Relay::updateContact() for more details.
// We check after the probing to be able to correct falsely detected contact types.
if (($contact['contact-type'] == self::TYPE_RELAY) && Strings::compareLink($contact['url'], $contact['baseurl']) &&
if (($contact['contact-type'] == self::TYPE_RELAY) && Strings::compareLink($contact['url'], $contact['baseurl'] ?? '') &&
(!Strings::compareLink($ret['url'], $contact['url']) || in_array($ret['network'], [Protocol::FEED, Protocol::PHANTOM]))
) {
if (GServer::reachable($contact)) {
@ -3010,6 +3035,10 @@ class Contact
*/
public static function getProtocol(string $url, string $network): string
{
if (self::isLocal($url)) {
return Protocol::ACTIVITYPUB;
}
if ($network != Protocol::DFRN) {
return $network;
}
@ -3224,6 +3253,7 @@ class Contact
}
if ($probed) {
GServer::updateFromProbeArray($ret);
self::updateFromProbeArray($contact_id, $ret);
} else {
try {
@ -3400,16 +3430,21 @@ class Contact
* Update the local relationship when a local user loses a follower
*
* @param array $contact User-specific contact (uid != 0) array
* @param bool $delete Delete if set, otherwise set relation to "nothing" when contact had been a follower
* @return void
* @throws HTTPException\InternalServerErrorException
* @throws \ImagickException
*/
public static function removeFollower(array $contact)
public static function removeFollower(array $contact, bool $delete = true)
{
if (in_array($contact['rel'] ?? [], [self::FRIEND, self::SHARING])) {
self::update(['rel' => self::SHARING], ['id' => $contact['id']]);
} elseif (!empty($contact['id'])) {
self::remove($contact['id']);
if ($delete) {
self::remove($contact['id']);
} else {
self::update(['rel' => self::NOTHING, 'pending' => false], ['id' => $contact['id']]);
}
} else {
DI::logger()->info('Couldn\'t remove follower because of invalid contact array', ['contact' => $contact]);
return;
@ -3419,9 +3454,9 @@ class Contact
self::clearFollowerFollowingEndpointCache($contact['uid']);
$cdata = self::getPublicAndUserContactID($contact['id'], $contact['uid']);
if (!empty($cdata['public'])) {
DI::notification()->deleteForUserByVerb($contact['uid'], Activity::FOLLOW, ['actor-id' => $cdata['public']]);
$pcid = self::getPublicContactId($contact['id'], $contact['uid']);
if ($pcid) {
DI::notification()->deleteForUserByVerb($contact['uid'], Activity::FOLLOW, ['actor-id' => $pcid]);
}
}
@ -3430,14 +3465,19 @@ class Contact
* Removes the contact for sharing-only protocols (feed and mail).
*
* @param array $contact User-specific contact (uid != 0) array
* @param bool $delete Delete if set, otherwise set relation to "nothing" when contact had been a sharer
* @throws HTTPException\InternalServerErrorException
*/
public static function removeSharer(array $contact)
public static function removeSharer(array $contact, bool $delete = true)
{
self::clearFollowerFollowingEndpointCache($contact['uid']);
if ($contact['rel'] == self::SHARING || in_array($contact['network'], [Protocol::FEED, Protocol::MAIL])) {
self::remove($contact['id']);
if (in_array($contact['rel'], [self::SHARING, self::NOTHING]) || in_array($contact['network'], [Protocol::FEED, Protocol::MAIL])) {
if ($delete) {
self::remove($contact['id']);
} else {
self::update(['rel' => self::NOTHING, 'pending' => false], ['id' => $contact['id']]);
}
} else {
self::update(['rel' => self::FOLLOWER, 'pending' => false], ['id' => $contact['id']]);
}
@ -3563,7 +3603,14 @@ class Contact
*/
public static function magicLinkById(int $cid, string $url = ''): string
{
if (($url == '') && DI::userSession()->isAuthenticated() && DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'system', 'stay_local')) {
return 'contact/' . $cid . '/conversations';
}
$contact = DBA::selectFirst('contact', ['id', 'network', 'url', 'alias', 'uid'], ['id' => $cid]);
if (empty($contact)) {
return $url;
}
return self::magicLinkByContact($contact, $url);
}
@ -3586,15 +3633,19 @@ class Contact
return $destination;
}
if (DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'system', 'stay_local') && ($url == '')) {
return 'contact/' . $contact['id'] . '/conversations';
}
if (Strings::compareLink($contact['url'], $url) || Strings::compareLink($contact['alias'] ?? '', $url)) {
$url = '';
}
// Only redirections to the same host do make sense
if (($url != '') && (parse_url($url, PHP_URL_HOST) != parse_url($contact['url'], PHP_URL_HOST))) {
return $url;
}
if (DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'system', 'stay_local') && ($url == '')) {
return 'contact/' . $contact['id'] . '/conversations';
}
if (!empty($contact['network']) && $contact['network'] != Protocol::DFRN) {
return $destination;
}
@ -3605,7 +3656,7 @@ class Contact
$redirect = 'contact/redir/' . $contact['id'];
if (($url != '') && !Strings::compareLink($contact['url'], $url)) {
if ($url != '') {
$redirect .= '?url=' . $url;
}

View file

@ -281,12 +281,12 @@ class User
*/
public static function setCollapsed(int $cid, int $uid, bool $collapsed)
{
$cdata = Contact::getPublicAndUserContactID($cid, $uid);
if (empty($cdata)) {
$pcid = Contact::getPublicContactId($cid, $uid);
if (!$pcid) {
return;
}
DBA::update('user-contact', ['collapsed' => $collapsed], ['cid' => $cdata['public'], 'uid' => $uid], true);
DBA::update('user-contact', ['collapsed' => $collapsed], ['cid' => $pcid, 'uid' => $uid], true);
}
/**
@ -300,21 +300,13 @@ class User
*/
public static function isCollapsed(int $cid, int $uid): bool
{
$cdata = Contact::getPublicAndUserContactID($cid, $uid);
if (empty($cdata)) {
$pcid = Contact::getPublicContactId($cid, $uid);
if (!$pcid) {
return false;
}
$collapsed = false;
if (!empty($cdata['public'])) {
$public_contact = DBA::selectFirst('user-contact', ['collapsed'], ['cid' => $cdata['public'], 'uid' => $uid]);
if (DBA::isResult($public_contact)) {
$collapsed = (bool) $public_contact['collapsed'];
}
}
return $collapsed;
$public_contact = DBA::selectFirst('user-contact', ['collapsed'], ['cid' => $pcid, 'uid' => $uid]);
return $public_contact['collapsed'] ?? false;
}
/**
@ -328,12 +320,12 @@ class User
*/
public static function setChannelFrequency(int $cid, int $uid, int $frequency)
{
$cdata = Contact::getPublicAndUserContactID($cid, $uid);
if (empty($cdata)) {
$pcid = Contact::getPublicContactId($cid, $uid);
if (!$pcid) {
return;
}
DBA::update('user-contact', ['channel-frequency' => $frequency], ['cid' => $cdata['public'], 'uid' => $uid], true);
DBA::update('user-contact', ['channel-frequency' => $frequency], ['cid' => $pcid, 'uid' => $uid], true);
}
/**
@ -347,21 +339,52 @@ class User
*/
public static function getChannelFrequency(int $cid, int $uid): int
{
$cdata = Contact::getPublicAndUserContactID($cid, $uid);
if (empty($cdata)) {
$pcid = Contact::getPublicContactId($cid, $uid);
if (!$pcid) {
return false;
}
$frequency = self::FREQUENCY_DEFAULT;
$public_contact = DBA::selectFirst('user-contact', ['channel-frequency'], ['cid' => $pcid, 'uid' => $uid]);
return $public_contact['channel-frequency'] ?? self::FREQUENCY_DEFAULT;
}
if (!empty($cdata['public'])) {
$public_contact = DBA::selectFirst('user-contact', ['channel-frequency'], ['cid' => $cdata['public'], 'uid' => $uid]);
if (DBA::isResult($public_contact)) {
$frequency = $public_contact['channel-frequency'] ?? self::FREQUENCY_DEFAULT;
}
/**
* Set the channel only value for contact id and user id
*
* @param int $cid Either public contact id or user's contact id
* @param int $uid User ID
* @param int $isChannelOnly Is channel only
* @return void
* @throws \Exception
*/
public static function setChannelOnly(int $cid, int $uid, bool $isChannelOnly)
{
$pcid = Contact::getPublicContactId($cid, $uid);
if (!$pcid) {
return;
}
return $frequency;
DBA::update('user-contact', ['channel-only' => $isChannelOnly], ['cid' => $pcid, 'uid' => $uid], true);
}
/**
* Returns if the contact is channel only for contact id and user id
*
* @param int $cid Either public contact id or user's contact id
* @param int $uid User ID
* @return bool Contact is channel only
* @throws HTTPException\InternalServerErrorException
* @throws \ImagickException
*/
public static function getChannelOnly(int $cid, int $uid): bool
{
$pcid = Contact::getPublicContactId($cid, $uid);
if (!$pcid) {
return false;
}
$public_contact = DBA::selectFirst('user-contact', ['channel-only'], ['cid' => $pcid, 'uid' => $uid]);
return $public_contact['channel-only'] ?? false;
}
/**
@ -375,12 +398,12 @@ class User
*/
public static function setIsBlocked(int $cid, int $uid, bool $blocked)
{
$cdata = Contact::getPublicAndUserContactID($cid, $uid);
if (empty($cdata)) {
$pcid = Contact::getPublicContactId($cid, $uid);
if (!$pcid) {
return;
}
DBA::update('user-contact', ['is-blocked' => $blocked], ['cid' => $cdata['public'], 'uid' => $uid], true);
DBA::update('user-contact', ['is-blocked' => $blocked], ['cid' => $pcid, 'uid' => $uid], true);
}
/**
@ -393,18 +416,12 @@ class User
*/
public static function isIsBlocked(int $cid, int $uid): bool
{
$cdata = Contact::getPublicAndUserContactID($cid, $uid);
if (empty($cdata)) {
$pcid = Contact::getPublicContactId($cid, $uid);
if (!$pcid) {
return false;
}
if (!empty($cdata['public'])) {
$public_contact = DBA::selectFirst('user-contact', ['is-blocked'], ['cid' => $cdata['public'], 'uid' => $uid]);
if (DBA::isResult($public_contact)) {
return $public_contact['is-blocked'];
}
}
return false;
$public_contact = DBA::selectFirst('user-contact', ['is-blocked'], ['cid' => $pcid, 'uid' => $uid]);
return $public_contact['is-blocked'] ?? false;
}
}

View file

@ -516,7 +516,7 @@ class Event
throw new HTTPException\UnauthorizedException(DI::l10n()->t('Access to this profile has been restricted.'));
}
if (!DI::userSession()->isAuthenticated() && !Feature::isEnabled($owner['uid'], 'public_calendar')) {
if (!DI::userSession()->isAuthenticated() && !Feature::isEnabled($owner['uid'], Feature::PUBLIC_CALENDAR)) {
throw new HTTPException\UnauthorizedException(DI::l10n()->t('Permission denied.'));
}

View file

@ -34,6 +34,7 @@ use Friendica\Module\Register;
use Friendica\Network\HTTPClient\Client\HttpClientAccept;
use Friendica\Network\HTTPClient\Client\HttpClientOptions;
use Friendica\Network\HTTPClient\Capability\ICanHandleHttpResponses;
use Friendica\Network\HTTPClient\Client\HttpClientRequest;
use Friendica\Network\Probe;
use Friendica\Protocol\ActivityPub;
use Friendica\Protocol\Relay;
@ -86,9 +87,11 @@ class GServer
// Standardized endpoints
const DETECT_STATISTICS_JSON = 100;
const DETECT_NODEINFO_1 = 101;
const DETECT_NODEINFO_2 = 102;
const DETECT_NODEINFO_210 = 103;
const DETECT_NODEINFO_10 = 101; // Nodeinfo Version 1.0
const DETECT_NODEINFO_20 = 102; // Nodeinfo Version 2.0
const DETECT_NODEINFO2_10 = 103; // Nodeinfo2 Version 1.0
const DETECT_NODEINFO_21 = 104; // Nodeinfo Version 2.1
const DETECT_NODEINFO_22 = 105; // Nodeinfo Version 2.2
/**
* Check for the existence of a server and adds it in the background if not existant
@ -577,7 +580,7 @@ class GServer
// We only follow redirects when the path stays the same or the target url has no path.
// Some systems have got redirects on their landing page to a single account page. This check handles it.
if (((parse_url($url, PHP_URL_HOST) != parse_url($valid_url, PHP_URL_HOST)) && (parse_url($url, PHP_URL_PATH) == parse_url($valid_url, PHP_URL_PATH))) ||
(((parse_url($url, PHP_URL_HOST) != parse_url($valid_url, PHP_URL_HOST)) || (parse_url($url, PHP_URL_PATH) != parse_url($valid_url, PHP_URL_PATH))) && empty(parse_url($valid_url, PHP_URL_PATH)))) {
(((parse_url($url, PHP_URL_HOST) != parse_url($valid_url, PHP_URL_HOST)) || (parse_url($url, PHP_URL_PATH) != parse_url($valid_url, PHP_URL_PATH))) && empty(parse_url($valid_url, PHP_URL_PATH)))) {
Logger::debug('Found redirect. Mark old entry as failure', ['old' => $url, 'new' => $valid_url]);
self::setFailureByUrl($url);
if (!self::getID($valid_url, true) && !Network::isUrlBlocked($valid_url)) {
@ -587,7 +590,7 @@ class GServer
}
if ((parse_url($url, PHP_URL_HOST) != parse_url($valid_url, PHP_URL_HOST)) && (parse_url($url, PHP_URL_PATH) != parse_url($valid_url, PHP_URL_PATH)) &&
(parse_url($url, PHP_URL_PATH) == '')) {
(parse_url($url, PHP_URL_PATH) == '')) {
Logger::debug('Found redirect. Mark old entry as failure and redirect to the basepath.', ['old' => $url, 'new' => $valid_url]);
$parts = (array)parse_url($valid_url);
unset($parts['path']);
@ -605,13 +608,13 @@ class GServer
if ((parse_url($url, PHP_URL_HOST) == parse_url($valid_url, PHP_URL_HOST)) &&
(parse_url($url, PHP_URL_PATH) == parse_url($valid_url, PHP_URL_PATH)) &&
(parse_url($url, PHP_URL_SCHEME) != parse_url($valid_url, PHP_URL_SCHEME))) {
$url = $valid_url;
$url = $valid_url;
}
$in_webroot = empty(parse_url($url, PHP_URL_PATH));
// When a nodeinfo is present, we don't need to dig further
$curlResult = DI::httpClient()->get($url . '/.well-known/x-nodeinfo2', HttpClientAccept::JSON);
$curlResult = DI::httpClient()->get($url . '/.well-known/nodeinfo', HttpClientAccept::JSON, [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]);
if ($curlResult->isTimeout()) {
self::setFailureByUrl($url);
return false;
@ -620,10 +623,11 @@ class GServer
if (!empty($network) && !in_array($network, Protocol::NATIVE_SUPPORT)) {
$serverdata = ['detection-method' => self::DETECT_MANUAL, 'network' => $network, 'platform' => '', 'version' => '', 'site_name' => '', 'info' => ''];
} else {
$serverdata = self::parseNodeinfo210($curlResult);
if (empty($serverdata)) {
$curlResult = DI::httpClient()->get($url . '/.well-known/nodeinfo', HttpClientAccept::JSON);
$serverdata = self::fetchNodeinfo($url, $curlResult);
$serverdata = self::parseNodeinfo($url, $curlResult);
if (empty($serverdata) || !in_array($serverdata['detection-method'], [self::DETECT_NODEINFO_20, self::DETECT_NODEINFO_21, self::DETECT_NODEINFO_22])) {
$curlResult = DI::httpClient()->get($url . '/.well-known/x-nodeinfo2', HttpClientAccept::JSON, [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]);
$serverdata = self::parseNodeinfo2($curlResult) ?: $serverdata;
}
}
@ -640,9 +644,9 @@ class GServer
if ($in_webroot) {
// Fetch the landing page, possibly it reveals some data
$accept = 'application/activity+json,application/ld+json,application/json,*/*;q=0.9';
$curlResult = DI::httpClient()->get($url, $accept);
$curlResult = DI::httpClient()->get($url, $accept, [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]);
if (!$curlResult->isSuccess() && $curlResult->getReturnCode() == '406') {
$curlResult = DI::httpClient()->get($url, HttpClientAccept::HTML);
$curlResult = DI::httpClient()->get($url, HttpClientAccept::HTML, [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]);
$html_fetched = true;
} else {
$html_fetched = false;
@ -655,10 +659,10 @@ class GServer
$serverdata = $data['server'];
$systemactor = $data['actor'];
if (!$html_fetched && !in_array($serverdata['detection-method'], [self::DETECT_SYSTEM_ACTOR, self::DETECT_AP_COLLECTION])) {
$curlResult = DI::httpClient()->get($url, HttpClientAccept::HTML);
$curlResult = DI::httpClient()->get($url, HttpClientAccept::HTML, [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]);
}
} elseif (!$html_fetched && (strlen($curlResult->getBodyString()) < 1000)) {
$curlResult = DI::httpClient()->get($url, HttpClientAccept::HTML);
$curlResult = DI::httpClient()->get($url, HttpClientAccept::HTML, [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]);
}
if ($serverdata['detection-method'] != self::DETECT_SYSTEM_ACTOR) {
@ -736,6 +740,10 @@ class GServer
}
}
if (in_array($serverdata['platform'] ?? '', ['friendica', 'hubzilla', 'streams', 'osada', 'mistpark', 'roadhouse', 'zap'])) {
$serverdata = self::getZotData($url, $serverdata);
}
// When we hadn't been able to detect the network type, we use the hint from the parameter
if (($serverdata['network'] == Protocol::PHANTOM) && !empty($network)) {
$serverdata['network'] = $network;
@ -867,7 +875,7 @@ class GServer
{
Logger::info('Discover relay data', ['server' => $server_url]);
$curlResult = DI::httpClient()->get($server_url . '/.well-known/x-social-relay', HttpClientAccept::JSON);
$curlResult = DI::httpClient()->get($server_url . '/.well-known/x-social-relay', HttpClientAccept::JSON, [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]);
if (!$curlResult->isSuccess()) {
return;
}
@ -962,7 +970,7 @@ class GServer
*/
private static function fetchStatistics(string $url, array $serverdata): array
{
$curlResult = DI::httpClient()->get($url . '/statistics.json', HttpClientAccept::JSON);
$curlResult = DI::httpClient()->get($url . '/statistics.json', HttpClientAccept::JSON, [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]);
if (!$curlResult->isSuccess()) {
return $serverdata;
}
@ -1044,7 +1052,9 @@ class GServer
}
/**
* Detect server type by using the nodeinfo data
* Parses Nodeinfo
*
* @see https://github.com/jhass/nodeinfo
*
* @param string $url address of the server
* @param ICanHandleHttpResponses $httpResult
@ -1053,7 +1063,7 @@ class GServer
*
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
private static function fetchNodeinfo(string $url, ICanHandleHttpResponses $httpResult): array
private static function parseNodeinfo(string $url, ICanHandleHttpResponses $httpResult): array
{
if (!$httpResult->isSuccess()) {
return [];
@ -1067,6 +1077,7 @@ class GServer
$nodeinfo1_url = '';
$nodeinfo2_url = '';
$detection_method = self::DETECT_MANUAL;
foreach ($nodeinfo['links'] as $link) {
if (!is_array($link) || empty($link['rel']) || empty($link['href'])) {
@ -1076,8 +1087,15 @@ class GServer
if ($link['rel'] == 'http://nodeinfo.diaspora.software/ns/schema/1.0') {
$nodeinfo1_url = Network::addBasePath($link['href'], $httpResult->getUrl());
} elseif ($link['rel'] == 'http://nodeinfo.diaspora.software/ns/schema/2.0') {
} elseif (($detection_method < self::DETECT_NODEINFO_20) && ($link['rel'] == 'http://nodeinfo.diaspora.software/ns/schema/2.0')) {
$nodeinfo2_url = Network::addBasePath($link['href'], $httpResult->getUrl());
$detection_method = self::DETECT_NODEINFO_20;
} elseif (($detection_method < self::DETECT_NODEINFO_21) && ($link['rel'] == 'http://nodeinfo.diaspora.software/ns/schema/2.1')) {
$nodeinfo2_url = Network::addBasePath($link['href'], $httpResult->getUrl());
$detection_method = self::DETECT_NODEINFO_21;
} elseif (($detection_method < self::DETECT_NODEINFO_22) && ($link['rel'] == 'http://nodeinfo.diaspora.software/ns/schema/2.2')) {
$nodeinfo2_url = Network::addBasePath($link['href'], $httpResult->getUrl());
$detection_method = self::DETECT_NODEINFO_22;
}
}
@ -1088,18 +1106,20 @@ class GServer
$server = [];
if (!empty($nodeinfo2_url)) {
$server = self::parseNodeinfo2($nodeinfo2_url);
$server = self::parseNodeinfo_2($nodeinfo2_url, $detection_method);
}
if (empty($server) && !empty($nodeinfo1_url)) {
$server = self::parseNodeinfo1($nodeinfo1_url);
$server = self::parseNodeinfo_1($nodeinfo1_url);
}
return $server;
}
/**
* Parses Nodeinfo 1
* Parses Nodeinfo with the version 1.0
*
* @see https://github.com/jhass/nodeinfo/tree/main/schemas/1.0
*
* @param string $nodeinfo_url address of the nodeinfo path
*
@ -1107,9 +1127,9 @@ class GServer
*
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
private static function parseNodeinfo1(string $nodeinfo_url): array
private static function parseNodeinfo_1(string $nodeinfo_url): array
{
$curlResult = DI::httpClient()->get($nodeinfo_url, HttpClientAccept::JSON);
$curlResult = DI::httpClient()->get($nodeinfo_url, HttpClientAccept::JSON, [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]);
if (!$curlResult->isSuccess()) {
return [];
}
@ -1120,8 +1140,10 @@ class GServer
return [];
}
$server = ['detection-method' => self::DETECT_NODEINFO_1,
'register_policy' => Register::CLOSED];
$server = [
'detection-method' => self::DETECT_NODEINFO_10,
'register_policy' => Register::CLOSED
];
if (!empty($nodeinfo['openRegistrations'])) {
$server['register_policy'] = Register::OPEN;
@ -1197,19 +1219,22 @@ class GServer
}
/**
* Parses Nodeinfo 2
* Parses Nodeinfo with the versions 2.0, 2.1 and 2.2
*
* @see https://git.feneas.org/jaywink/nodeinfo2
* @see https://github.com/jhass/nodeinfo/tree/main/schemas/2.0
* @see https://github.com/jhass/nodeinfo/tree/main/schemas/2.1
* @see https://github.com/jhass/nodeinfo/tree/main/schemas/2.2
*
* @param string $nodeinfo_url address of the nodeinfo path
* @param string $nodeinfo_url address of the nodeinfo path
* @param int $detection_method nodeinfo version
*
* @return array Server data
*
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
private static function parseNodeinfo2(string $nodeinfo_url): array
private static function parseNodeinfo_2(string $nodeinfo_url, int $detection_method): array
{
$curlResult = DI::httpClient()->get($nodeinfo_url, HttpClientAccept::JSON);
$curlResult = DI::httpClient()->get($nodeinfo_url, HttpClientAccept::JSON, [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]);
if (!$curlResult->isSuccess()) {
return [];
}
@ -1220,7 +1245,7 @@ class GServer
}
$server = [
'detection-method' => self::DETECT_NODEINFO_2,
'detection-method' => $detection_method,
'register_policy' => Register::CLOSED,
'platform' => 'unknown',
];
@ -1229,6 +1254,15 @@ class GServer
$server['register_policy'] = Register::OPEN;
}
if (!empty($nodeinfo['instance'])) {
if (!empty($nodeinfo['instance']['name'])) {
$server['site_name'] = $nodeinfo['instance']['name'];
}
if (!empty($nodeinfo['instance']['description'])) {
$server['info'] = $nodeinfo['instance']['description'];
}
}
if (!empty($nodeinfo['software'])) {
if (isset($nodeinfo['software']['name'])) {
$server['platform'] = strtolower($nodeinfo['software']['name']);
@ -1244,6 +1278,13 @@ class GServer
if (($server['platform'] == 'mastodon') && substr($nodeinfo['software']['version'], -5) == '-qoto') {
$server['platform'] = 'qoto';
}
if (isset($nodeinfo['software']['repository'])) {
$server['repository'] = strtolower($nodeinfo['software']['repository']);
}
if (isset($nodeinfo['software']['homepage'])) {
$server['homepage'] = strtolower($nodeinfo['software']['homepage']);
}
}
}
@ -1255,6 +1296,9 @@ class GServer
if (!empty($nodeinfo['metadata']['nodeName'])) {
$server['site_name'] = $nodeinfo['metadata']['nodeName'];
}
if (!empty($nodeinfo['metadata']['nodeDescription'])) {
$server['info'] = $nodeinfo['metadata']['nodeDescription'];
}
if (!empty($nodeinfo['usage']['users']['total'])) {
$server['registered-users'] = max($nodeinfo['usage']['users']['total'], 1);
@ -1315,9 +1359,9 @@ class GServer
}
/**
* Parses NodeInfo2 protocol 1.0
* Parses NodeInfo2
*
* @see https://github.com/jaywink/nodeinfo2/blob/master/PROTOCOL.md
* @see https://github.com/jaywink/nodeinfo2
*
* @param string $nodeinfo_url address of the nodeinfo path
*
@ -1325,7 +1369,7 @@ class GServer
*
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
private static function parseNodeinfo210(ICanHandleHttpResponses $httpResult): array
private static function parseNodeinfo2(ICanHandleHttpResponses $httpResult): array
{
if (!$httpResult->isSuccess()) {
return [];
@ -1337,8 +1381,10 @@ class GServer
return [];
}
$server = ['detection-method' => self::DETECT_NODEINFO_210,
'register_policy' => Register::CLOSED];
$server = [
'detection-method' => self::DETECT_NODEINFO2_10,
'register_policy' => Register::CLOSED
];
if (!empty($nodeinfo['openRegistrations'])) {
$server['register_policy'] = Register::OPEN;
@ -1429,7 +1475,7 @@ class GServer
*/
private static function fetchSiteinfo(string $url, array $serverdata): array
{
$curlResult = DI::httpClient()->get($url . '/siteinfo.json', HttpClientAccept::JSON);
$curlResult = DI::httpClient()->get($url . '/siteinfo.json', HttpClientAccept::JSON, [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]);
if (!$curlResult->isSuccess()) {
return $serverdata;
}
@ -1586,8 +1632,11 @@ class GServer
private static function getNomadName(string $url): string
{
$name = 'nomad';
$curlResult = DI::httpClient()->get($url . '/manifest', 'application/manifest+json');
$curlResult = DI::httpClient()->get($url . '/manifest', 'application/manifest+json', [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]);
if (!$curlResult->isSuccess() || ($curlResult->getBodyString() == '')) {
if ($curlResult->getReturnCode() == 418) {
$name = 'streams';
}
return $name;
}
@ -1607,7 +1656,7 @@ class GServer
*/
private static function getNomadVersion(string $url): string
{
$curlResult = DI::httpClient()->get($url . '/api/z/1.0/version', HttpClientAccept::JSON);
$curlResult = DI::httpClient()->get($url . '/api/z/1.0/version', HttpClientAccept::JSON, [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]);
if (!$curlResult->isSuccess() || ($curlResult->getBodyString() == '')) {
return '';
}
@ -1619,6 +1668,77 @@ class GServer
return $data ?? '';
}
private static function getZotData(string $url, array $serverdata): array
{
$curlResult = DI::httpClient()->get($url, 'application/x-zot+json');
if (!$curlResult->isSuccess()) {
return $serverdata;
}
$json = $curlResult->getBodyString();
$data = json_decode($json, true);
if (empty($data)) {
return $serverdata;
}
if (!empty($data['site'])) {
$serverdata = self::getFromZotData($data['site'], $serverdata);
} else {
$serverdata = self::getFromZotData($data, $serverdata);
}
return $serverdata;
}
private static function getFromZotData(array $data, array $serverdata): array
{
if (!empty($data['version'])) {
$serverdata['version'] = $data['version'];
}
if (!empty($data['openWebAuth'])) {
$serverdata['openwebauth'] = $data['openWebAuth'];
}
if (!empty($data['authRedirect'])) {
$serverdata['authredirect'] = $data['authRedirect'];
}
if (!empty($data['sitename'])) {
$serverdata['site_name'] = $data['sitename'];
}
if (!empty($data['about'])) {
$serverdata['info'] = $data['about'];
}
if (empty($serverdata['info']) && !empty($data['location'])) {
$serverdata['info'] = $data['location'];
}
if (!empty($data['project']) && in_array($data['project'], ['friendica', 'hubzilla', 'streams', 'osada', 'mistpark', 'roadhouse', 'zap'])) {
$serverdata['platform'] = $data['project'];
}
if (!empty($data['accounts'])) {
$serverdata['registered-users'] = $data['accounts'];
}
if (!empty($data['register_policy'])) {
switch ($data['register_policy']) {
case 'open':
$serverdata['register_policy'] = Register::OPEN;
break;
case 'closed':
$serverdata['register_policy'] = Register::CLOSED;
break;
case 'approve':
$serverdata['register_policy'] = Register::APPROVE;
break;
}
}
return $serverdata;
}
/**
* Checks if the server contains a valid host meta file
*
@ -1629,7 +1749,7 @@ class GServer
private static function validHostMeta(string $url): bool
{
$xrd_timeout = DI::config()->get('system', 'xrd_timeout');
$curlResult = DI::httpClient()->get($url . Probe::HOST_META, HttpClientAccept::XRD_XML, [HttpClientOptions::TIMEOUT => $xrd_timeout]);
$curlResult = DI::httpClient()->get($url . Probe::HOST_META, HttpClientAccept::XRD_XML, [HttpClientOptions::TIMEOUT => $xrd_timeout, HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]);
if (!$curlResult->isSuccess()) {
return false;
}
@ -1728,7 +1848,7 @@ class GServer
{
$serverdata['poco'] = '';
$curlResult = DI::httpClient()->get($url . '/poco', HttpClientAccept::JSON);
$curlResult = DI::httpClient()->get($url . '/poco', HttpClientAccept::JSON, [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]);
if (!$curlResult->isSuccess()) {
return $serverdata;
}
@ -1758,7 +1878,7 @@ class GServer
*/
public static function checkMastodonDirectory(string $url, array $serverdata): array
{
$curlResult = DI::httpClient()->get($url . '/api/v1/directory?limit=1', HttpClientAccept::JSON);
$curlResult = DI::httpClient()->get($url . '/api/v1/directory?limit=1', HttpClientAccept::JSON, [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]);
if (!$curlResult->isSuccess()) {
return $serverdata;
}
@ -1785,7 +1905,7 @@ class GServer
*/
private static function detectPeertube(string $url, array $serverdata): array
{
$curlResult = DI::httpClient()->get($url . '/api/v1/config', HttpClientAccept::JSON);
$curlResult = DI::httpClient()->get($url . '/api/v1/config', HttpClientAccept::JSON, [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]);
if (!$curlResult->isSuccess() || ($curlResult->getBodyString() == '')) {
return $serverdata;
}
@ -1833,7 +1953,7 @@ class GServer
*/
private static function detectNextcloud(string $url, array $serverdata, bool $validHostMeta): array
{
$curlResult = DI::httpClient()->get($url . '/status.php', HttpClientAccept::JSON);
$curlResult = DI::httpClient()->get($url . '/status.php', HttpClientAccept::JSON, [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]);
if (!$curlResult->isSuccess() || ($curlResult->getBodyString() == '')) {
return $serverdata;
}
@ -1869,7 +1989,7 @@ class GServer
*/
private static function fetchWeeklyUsage(string $url, array $serverdata): array
{
$curlResult = DI::httpClient()->get($url . '/api/v1/instance/activity', HttpClientAccept::JSON);
$curlResult = DI::httpClient()->get($url . '/api/v1/instance/activity', HttpClientAccept::JSON, [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]);
if (!$curlResult->isSuccess() || ($curlResult->getBodyString() == '')) {
return $serverdata;
}
@ -1909,7 +2029,7 @@ class GServer
*/
private static function detectMastodonAlikes(string $url, array $serverdata): array
{
$curlResult = DI::httpClient()->get($url . '/api/v1/instance', HttpClientAccept::JSON);
$curlResult = DI::httpClient()->get($url . '/api/v1/instance', HttpClientAccept::JSON, [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]);
if (!$curlResult->isSuccess() || ($curlResult->getBodyString() == '')) {
return $serverdata;
}
@ -1981,7 +2101,7 @@ class GServer
*/
private static function detectHubzilla(string $url, array $serverdata): array
{
$curlResult = DI::httpClient()->get($url . '/api/statusnet/config.json', HttpClientAccept::JSON);
$curlResult = DI::httpClient()->get($url . '/api/statusnet/config.json', HttpClientAccept::JSON, [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]);
if (!$curlResult->isSuccess() || ($curlResult->getBodyString() == '')) {
return $serverdata;
}
@ -2078,7 +2198,7 @@ class GServer
private static function detectGNUSocial(string $url, array $serverdata): array
{
// Test for GNU Social
$curlResult = DI::httpClient()->get($url . '/api/gnusocial/version.json', HttpClientAccept::JSON);
$curlResult = DI::httpClient()->get($url . '/api/gnusocial/version.json', HttpClientAccept::JSON, [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]);
if ($curlResult->isSuccess() && ($curlResult->getBodyString() != '{"error":"not implemented"}') &&
($curlResult->getBodyString() != '') && (strlen($curlResult->getBodyString()) < 30)) {
$serverdata['platform'] = 'gnusocial';
@ -2096,12 +2216,12 @@ class GServer
}
// Test for Statusnet
$curlResult = DI::httpClient()->get($url . '/api/statusnet/version.json', HttpClientAccept::JSON);
$curlResult = DI::httpClient()->get($url . '/api/statusnet/version.json', HttpClientAccept::JSON, [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]);
if ($curlResult->isSuccess() && ($curlResult->getBodyString() != '{"error":"not implemented"}') &&
($curlResult->getBodyString() != '') && (strlen($curlResult->getBodyString()) < 30)) {
// Remove junk that some GNU Social servers return
$serverdata['version'] = str_replace(chr(239).chr(187).chr(191), '', $curlResult->getBodyString());
$serverdata['version'] = str_replace(chr(239) . chr(187) . chr(191), '', $curlResult->getBodyString());
$serverdata['version'] = str_replace(["\r", "\n", "\t"], '', $serverdata['version']);
$serverdata['version'] = trim($serverdata['version'], '"');
@ -2134,9 +2254,9 @@ class GServer
{
// There is a bug in some versions of Friendica that will return an ActivityStream actor when the content type "application/json" is requested.
// Because of this me must not use ACCEPT_JSON here.
$curlResult = DI::httpClient()->get($url . '/friendica/json');
$curlResult = DI::httpClient()->get($url . '/friendica/json', HttpClientAccept::DEFAULT, [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]);
if (!$curlResult->isSuccess()) {
$curlResult = DI::httpClient()->get($url . '/friendika/json');
$curlResult = DI::httpClient()->get($url . '/friendika/json', HttpClientAccept::DEFAULT, [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]);
$friendika = true;
$platform = 'Friendika';
} else {
@ -2188,7 +2308,7 @@ class GServer
break;
default:
Logger::info('Register policy is invalid', ['policy' => $register_policy, 'server' => $url]);
$serverdata['register_policy'] = Register::CLOSED;
$serverdata['register_policy'] = Register::CLOSED;
break;
}
@ -2447,7 +2567,7 @@ class GServer
$protocols = ['activitypub', 'diaspora', 'dfrn', 'ostatus'];
foreach ($protocols as $protocol) {
$query = '{nodes(protocol:"' . $protocol . '"){host}}';
$curlResult = DI::httpClient()->fetch('https://the-federation.info/graphql?query=' . urlencode($query), HttpClientAccept::JSON);
$curlResult = DI::httpClient()->fetch('https://the-federation.info/graphql?query=' . urlencode($query), HttpClientAccept::JSON, 0, '', HttpClientRequest::SERVERDISCOVER);
if (!empty($curlResult)) {
$data = json_decode($curlResult, true);
if (!empty($data['data']['nodes'])) {
@ -2464,7 +2584,7 @@ class GServer
if (!empty($accesstoken)) {
$api = 'https://instances.social/api/1.0/instances/list?count=0';
$curlResult = DI::httpClient()->get($api, HttpClientAccept::JSON, [HttpClientOptions::HEADERS => ['Authorization' => ['Bearer ' . $accesstoken]]]);
$curlResult = DI::httpClient()->get($api, HttpClientAccept::JSON, [HttpClientOptions::HEADERS => ['Authorization' => ['Bearer ' . $accesstoken], HttpClientOptions::REQUEST => HttpClientRequest::SERVERDISCOVER]]);
if ($curlResult->isSuccess()) {
$servers = json_decode($curlResult->getBodyString(), true);
@ -2480,6 +2600,29 @@ class GServer
DI::keyValue()->set('poco_last_federation_discovery', time());
}
public static function updateFromProbeArray(array $data)
{
if (empty($data['gsid']) || empty($data['openwebauth'])) {
return;
}
$gserver = DBA::selectFirst('gserver', ['url', 'openwebauth'], ['id' => $data['gsid']]);
if (!DBA::isResult($gserver)) {
return;
}
if ($data['openwebauth'] == $gserver['openwebauth']) {
return;
}
$serverdata = self::getZotData($gserver['url'], []);
if (empty($serverdata)) {
$serverdata = ['openwebauth' => $data['openwebauth']];
}
self::update($serverdata, ['id' => $data['gsid']]);
}
/**
* Set the protocol for the given server
*

View file

@ -39,6 +39,7 @@ use Friendica\DI;
use Friendica\Model\Post\Category;
use Friendica\Network\HTTPClient\Client\HttpClientAccept;
use Friendica\Network\HTTPClient\Client\HttpClientOptions;
use Friendica\Network\HTTPClient\Client\HttpClientRequest;
use Friendica\Network\HTTPException\InternalServerErrorException;
use Friendica\Network\HTTPException\ServiceUnavailableException;
use Friendica\Protocol\Activity;
@ -108,7 +109,7 @@ class Item
'owner-id', 'owner-link', 'owner-alias', 'owner-name', 'owner-avatar', 'owner-network', 'owner-contact-type', 'owner-updated', 'owner-gsid',
'causer-id', 'causer-link', 'causer-alias', 'causer-name', 'causer-avatar', 'causer-contact-type', 'causer-network', 'causer-gsid',
'contact-id', 'contact-uid', 'contact-link', 'contact-name', 'contact-avatar',
'writable', 'self', 'cid', 'alias',
'writable', 'restrictions', 'self', 'cid', 'alias', 'post-reason',
'event-created', 'event-edited', 'event-start', 'event-finish',
'event-summary', 'event-desc', 'event-location', 'event-type',
'event-nofinish', 'event-ignore', 'event-id',
@ -121,7 +122,7 @@ class Item
const DELIVER_FIELDLIST = [
'uid', 'id', 'parent', 'uri-id', 'uri', 'thr-parent', 'parent-uri', 'guid',
'parent-guid', 'conversation', 'received', 'created', 'edited', 'verb', 'object-type', 'object', 'target',
'private', 'title', 'body', 'raw-body', 'language', 'location', 'coord', 'app', 'sensitive',
'private', 'title', 'content-warning', 'body', 'raw-body', 'language', 'location', 'coord', 'app', 'sensitive',
'inform', 'deleted', 'extid', 'post-type', 'post-reason', 'gravity',
'allow_cid', 'allow_gid', 'deny_cid', 'deny_gid',
'author-id', 'author-addr', 'author-link', 'author-name', 'author-avatar', 'owner-id', 'owner-link', 'contact-uid',
@ -168,6 +169,11 @@ class Item
const GRAVITY_COMMENT = 6;
const GRAVITY_UNKNOWN = 9;
// Restrictions
const CANT_REPLY = 1;
const CANT_LIKE = 2;
const CANT_ANNOUNCE = 4;
/**
* Update existing item entries
*
@ -192,6 +198,10 @@ class Item
$fields['external-id'] = ItemURI::getIdByURI($fields['extid']);
}
if (!empty($fields['replies'])) {
$fields['replies-id'] = ItemURI::getIdByURI($fields['replies']);
}
if (!empty($fields['verb'])) {
$fields['vid'] = Verb::getID($fields['verb']);
}
@ -409,8 +419,24 @@ class Item
self::markForDeletion(['parent' => $item['parent'], 'deleted' => false], $priority);
}
if ($item['uid'] == 0 && $item['gravity'] == self::GRAVITY_PARENT) {
$posts = DI::keyValue()->get('nodeinfo_total_posts') ?? 0;
DI::keyValue()->set('nodeinfo_total_posts', $posts - 1);
} elseif ($item['uid'] == 0 && $item['gravity'] == self::GRAVITY_COMMENT) {
$comments = DI::keyValue()->get('nodeinfo_total_comments') ?? 0;
DI::keyValue()->set('nodeinfo_total_comments', $comments - 1);
}
// Is it our comment and/or our thread?
if (($item['origin'] || $parent['origin']) && ($item['uid'] != 0)) {
if ($item['origin'] && $item['gravity'] == self::GRAVITY_PARENT) {
$posts = DI::keyValue()->get('nodeinfo_local_posts') ?? 0;
DI::keyValue()->set('nodeinfo_local_posts', $posts - 1);
} elseif ($item['origin'] && $item['gravity'] == self::GRAVITY_COMMENT) {
$comments = DI::keyValue()->get('nodeinfo_local_comments') ?? 0;
DI::keyValue()->set('nodeinfo_local_comments', $comments - 1);
}
// When we delete the original post we will delete all existing copies on the server as well
self::markForDeletion(['uri-id' => $item['uri-id'], 'deleted' => false], $priority);
@ -530,9 +556,9 @@ class Item
}
if (!empty($item['causer-id']) && Contact::isSharing($item['causer-id'], $item['uid'], true)) {
$cdata = Contact::getPublicAndUserContactID($item['causer-id'], $item['uid']);
if (!empty($cdata['user'])) {
return $cdata['user'];
$ucid = Contact::getUserContactId($item['causer-id'], $item['uid']);
if ($ucid) {
return $ucid;
}
}
@ -759,7 +785,7 @@ class Item
{
$fields = [
'uid', 'uri', 'parent-uri', 'id', 'deleted',
'uri-id', 'parent-uri-id',
'uri-id', 'parent-uri-id', 'restrictions', 'verb',
'allow_cid', 'allow_gid', 'deny_cid', 'deny_gid',
'wall', 'private', 'origin', 'author-id'
];
@ -783,6 +809,11 @@ class Item
return [];
}
if (self::hasRestrictions($item, $parent['author-id'], $parent['restrictions'])) {
Logger::notice('Restrictions apply - ignoring item', ['restrictions' => $parent['restrictions'], 'verb' => $parent['verb'], 'uri-id' => $item['uri-id'], 'thr-parent-id' => $item['thr-parent-id'], 'uid' => $item['uid']]);
return [];
}
if ($parent['uri-id'] == $parent['parent-uri-id']) {
return $parent;
}
@ -838,7 +869,7 @@ class Item
private static function prepareOriginPost(array $item): array
{
$item = DI::contentItem()->initializePost($item);
$item = DI::contentItem()->finalizePost($item);
$item = DI::contentItem()->finalizePost($item, false);
return $item;
}
@ -1034,7 +1065,7 @@ class Item
$item['deny_gid'] = $store_permissions ? $toplevel_parent['deny_gid'] : '';
}
$parent_origin = $toplevel_parent['origin'];
$parent_origin = $toplevel_parent['origin'];
// Don't federate received participation messages
if ($item['verb'] != Activity::FOLLOW) {
@ -1066,6 +1097,10 @@ class Item
$parent_id = 0;
$parent_origin = $item['origin'];
if ($item['wall'] && empty($item['context'])) {
$item['context'] = $item['parent-uri'] . '#context';
}
if ($item['wall'] && empty($item['conversation'])) {
$item['conversation'] = $item['parent-uri'] . '#context';
}
@ -1078,8 +1113,6 @@ class Item
) {
$item['object-type'] = Activity\ObjectType::IMAGE;
}
$item = DI::contentItem()->moveAttachmentsFromBodyToAttach($item);
}
$item['parent-uri-id'] = ItemURI::getIdByURI($item['parent-uri']);
@ -1089,6 +1122,10 @@ class Item
$item['conversation-id'] = ItemURI::getIdByURI($item['conversation']);
}
if (!empty($item['context']) && empty($item['context-id'])) {
$item['context-id'] = ItemURI::getIdByURI($item['context']);
}
// Is this item available in the global items (with uid=0)?
if ($item['uid'] == 0) {
$item['global'] = true;
@ -1156,6 +1193,10 @@ class Item
$item['external-id'] = ItemURI::getIdByURI($item['extid']);
}
if (!empty($item['replies'])) {
$item['replies-id'] = ItemURI::getIdByURI($item['replies']);
}
if ($item['verb'] == Activity::ANNOUNCE) {
self::setOwnerforResharedItem($item);
}
@ -1325,6 +1366,16 @@ class Item
return 0;
}
if ($posted_item['origin'] && $posted_item['gravity'] == self::GRAVITY_PARENT) {
$posts = (int)(DI::keyValue()->get('nodeinfo_local_posts') ?? 0);
DI::keyValue()->set('nodeinfo_local_posts', $posts + 1);
} elseif ($posted_item['origin'] && $posted_item['gravity'] == self::GRAVITY_COMMENT) {
$comments = (int)(DI::keyValue()->get('nodeinfo_local_comments') ?? 0);
DI::keyValue()->set('nodeinfo_local_comments', $comments + 1);
}
Post\Origin::insert($posted_item);
// update the commented timestamp on the parent
if (DI::config()->get('system', 'like_no_comment')) {
// Update when it is a comment
@ -1412,6 +1463,14 @@ class Item
}
if ($inserted) {
if ($posted_item['gravity'] == self::GRAVITY_PARENT) {
$posts = (int)(DI::keyValue()->get('nodeinfo_total_posts') ?? 0);
DI::keyValue()->set('nodeinfo_total_posts', $posts + 1);
} elseif ($posted_item['gravity'] == self::GRAVITY_COMMENT) {
$comments = (int)(DI::keyValue()->get('nodeinfo_total_comments') ?? 0);
DI::keyValue()->set('nodeinfo_total_comments', $comments + 1);
}
// Fill the cache with the rendered content.
if (in_array($posted_item['gravity'], [self::GRAVITY_PARENT, self::GRAVITY_COMMENT])) {
self::updateDisplayCache($posted_item['uri-id']);
@ -1439,6 +1498,27 @@ class Item
return $post_user_id;
}
private static function hasRestrictions(array $item, int $author_id, int $restrictions = null): bool
{
if (empty($restrictions) || ($author_id == $item['author-id'])) {
return false;
}
if (($restrictions & self::CANT_REPLY) && ($item['verb'] == Activity::POST)) {
return true;
}
if (($restrictions & self::CANT_ANNOUNCE) && ($item['verb'] == Activity::ANNOUNCE)) {
return true;
}
if (($restrictions & self::CANT_LIKE) && in_array($item['verb'], [Activity::LIKE, Activity::DISLIKE, Activity::ATTEND, Activity::ATTENDMAYBE, Activity::ATTENDNO])) {
return true;
}
return false;
}
private static function reshareChannelPost(int $uri_id, int $reshare_id = 0)
{
if (!DI::config()->get('system', 'allow_relay_channels')) {
@ -2552,12 +2632,12 @@ class Item
return;
}
$cdata = Contact::getPublicAndUserContactID($item['author-id'], $item['uid']);
if (empty($cdata['user']) || ($cdata['user'] != $item['contact-id'])) {
$ucid = Contact::getUserContactId($item['author-id'], $item['uid']);
if (!$ucid || ($ucid != $item['contact-id'])) {
return;
}
if (!DBA::exists('contact', ['id' => $cdata['user'], 'remote_self' => LocalRelationship::MIRROR_NATIVE_RESHARE])) {
if (!DBA::exists('contact', ['id' => $ucid, 'remote_self' => LocalRelationship::MIRROR_NATIVE_RESHARE])) {
return;
}
@ -3315,7 +3395,6 @@ class Item
$item['tags'] = $tags['tags'];
$item['hashtags'] = $tags['hashtags'];
$item['mentions'] = $tags['mentions'];
$sensitive = $item['sensitive'] && !DI::pConfig()->get($uid, 'system', 'display_sensitive', false);
if (!$is_preview) {
$item['body'] = preg_replace("#\s*\[attachment .*?].*?\[/attachment]\s*#ism", "\n", $item['body']);
@ -3383,11 +3462,11 @@ class Item
$shared_links = array_merge($shared_links, $sharedSplitAttachments['visual']->column('url'));
$shared_links = array_merge($shared_links, $sharedSplitAttachments['link']->column('url'));
$shared_links = array_merge($shared_links, $sharedSplitAttachments['additional']->column('url'));
$item['body'] = self::replaceVisualAttachments($sharedSplitAttachments['visual'], $item['body'], $sensitive);
$item['body'] = self::replaceVisualAttachments($sharedSplitAttachments['visual'], $item['body']);
}
$itemSplitAttachments = DI::postMediaRepository()->splitAttachments($item['uri-id'], $shared_links, $item['has-media'] ?? false);
$item['body'] = self::replaceVisualAttachments($itemSplitAttachments['visual'], $item['body'] ?? '', $sensitive);
$item['body'] = self::replaceVisualAttachments($itemSplitAttachments['visual'], $item['body'] ?? '');
self::putInCache($item);
$item['body'] = $body;
@ -3408,8 +3487,8 @@ class Item
$filter_reasons[] = DI::l10n()->t('Content from %s is collapsed', $item['author-name']);
}
if (!empty($item['content-warning']) && (!$uid || !DI::pConfig()->get($uid, 'system', 'disable_cw', false))) {
$filter_reasons[] = DI::l10n()->t('Content warning: %s', $item['content-warning']);
if ($item['sensitive'] && (!$uid || !DI::pConfig()->get($uid, 'system', 'disable_cw', false))) {
$filter_reasons[] = DI::l10n()->t('Sensitive content');
}
$item['attachments'] = $itemSplitAttachments;
@ -3442,9 +3521,9 @@ class Item
}
if (!empty($sharedSplitAttachments)) {
$s = self::addGallery($s, $sharedSplitAttachments['visual'], $sensitive);
$s = self::addVisualAttachments($sharedSplitAttachments['visual'], $shared_item, $s, true, $sensitive);
$s = self::addLinkAttachment($shared_uri_id ?: $item['uri-id'], $sharedSplitAttachments, $body, $s, true, $quote_shared_links, $sensitive);
$s = self::addGallery($s, $sharedSplitAttachments['visual']);
$s = self::addVisualAttachments($sharedSplitAttachments['visual'], $shared_item, $s, true);
$s = self::addLinkAttachment($shared_uri_id ?: $item['uri-id'], $sharedSplitAttachments, $body, $s, true, $quote_shared_links);
$s = self::addNonVisualAttachments($sharedSplitAttachments['additional'], $item, $s, true);
$body = BBCode::removeSharedData($body);
}
@ -3455,9 +3534,9 @@ class Item
$s = substr($s, 0, $pos);
}
$s = self::addGallery($s, $itemSplitAttachments['visual'], $sensitive);
$s = self::addVisualAttachments($itemSplitAttachments['visual'], $item, $s, false, $sensitive);
$s = self::addLinkAttachment($item['uri-id'], $itemSplitAttachments, $body, $s, false, $shared_links, $sensitive);
$s = self::addGallery($s, $itemSplitAttachments['visual']);
$s = self::addVisualAttachments($itemSplitAttachments['visual'], $item, $s, false);
$s = self::addLinkAttachment($item['uri-id'], $itemSplitAttachments, $body, $s, false, $shared_links);
$s = self::addNonVisualAttachments($itemSplitAttachments['additional'], $item, $s, false);
$s = self::addQuestions($item, $s);
@ -3491,10 +3570,9 @@ class Item
*
* @param string $s
* @param PostMedias $PostMedias
* @param bool $sensitive
* @return string
*/
private static function addGallery(string $s, PostMedias $PostMedias, bool $sensitive): string
private static function addGallery(string $s, PostMedias $PostMedias): string
{
foreach ($PostMedias as $PostMedia) {
if (!$PostMedia->preview || ($PostMedia->type !== Post\Media::IMAGE)) {
@ -3504,10 +3582,9 @@ class Item
if ($PostMedia->hasDimensions()) {
$pattern = '#<a href="' . preg_quote($PostMedia->url) . '">(.*?)"></a>#';
$s = preg_replace_callback($pattern, function () use ($PostMedia, $sensitive) {
$s = preg_replace_callback($pattern, function () use ($PostMedia) {
return Renderer::replaceMacros(Renderer::getMarkupTemplate('content/image/single_with_height_allocation.tpl'), [
'$image' => $PostMedia,
'$sensitive' => $sensitive,
'$allocated_height' => $PostMedia->getAllocatedHeight(),
'$allocated_max_width' => ($PostMedia->previewWidth ?? $PostMedia->width) . 'px',
]);
@ -3576,10 +3653,9 @@ class Item
*
* @param PostMedias $PostMedias
* @param string $body
* @param bool $sensitive
* @return string modified body
*/
private static function replaceVisualAttachments(PostMedias $PostMedias, string $body, bool $sensitive): string
private static function replaceVisualAttachments(PostMedias $PostMedias, string $body): string
{
DI::profiler()->startRecording('rendering');
@ -3588,7 +3664,7 @@ class Item
if (DI::baseUrl()->isLocalUri($PostMedia->preview)) {
continue;
}
$proxy = DI::baseUrl() . $PostMedia->getPreviewPath(Proxy::SIZE_LARGE, $sensitive);
$proxy = DI::baseUrl() . $PostMedia->getPreviewPath(Proxy::SIZE_LARGE);
$search = ['[img=' . $PostMedia->preview . ']', ']' . $PostMedia->preview . '[/img]'];
$replace = ['[img=' . $proxy . ']', ']' . $proxy . '[/img]'];
@ -3597,7 +3673,7 @@ class Item
if (DI::baseUrl()->isLocalUri($PostMedia->url)) {
continue;
}
$proxy = DI::baseUrl() . $PostMedia->getPreviewPath(Proxy::SIZE_LARGE, $sensitive);
$proxy = DI::baseUrl() . $PostMedia->getPreviewPath(Proxy::SIZE_LARGE);
$search = ['[img=' . $PostMedia->url . ']', ']' . $PostMedia->url . '[/img]'];
$replace = ['[img=' . $proxy . ']', ']' . $proxy . '[/img]'];
@ -3615,11 +3691,10 @@ class Item
* @param array $item
* @param string $content
* @param bool $shared
* @param bool $sensitive
* @return string modified content
* @throws ServiceUnavailableException
*/
private static function addVisualAttachments(PostMedias $PostMedias, array $item, string $content, bool $shared, bool $sensitive): string
private static function addVisualAttachments(PostMedias $PostMedias, array $item, string $content, bool $shared): string
{
DI::profiler()->startRecording('rendering');
$leading = '';
@ -3634,7 +3709,7 @@ class Item
if ($PostMedia->mimetype->type == 'image' || $PostMedia->preview) {
$preview_size = Proxy::SIZE_MEDIUM;
$preview_url = DI::baseUrl() . $PostMedia->getPreviewPath($preview_size, $sensitive);
$preview_url = DI::baseUrl() . $PostMedia->getPreviewPath($preview_size);
} else {
$preview_size = 0;
$preview_url = '';
@ -3655,13 +3730,14 @@ class Item
/// @todo Move the template to /content as well
$media = Renderer::replaceMacros(Renderer::getMarkupTemplate('video_top.tpl'), [
'$video' => [
'id' => $PostMedia->id,
'src' => (string)$PostMedia->url,
'name' => $PostMedia->name ?: $PostMedia->url,
'preview' => $preview_url,
'mime' => (string)$PostMedia->mimetype,
'height' => $height,
'width' => $width,
'id' => $PostMedia->id,
'src' => (string)$PostMedia->url,
'name' => $PostMedia->name ?: $PostMedia->url,
'preview' => $preview_url,
'mime' => (string)$PostMedia->mimetype,
'height' => $height,
'width' => $width,
'description' => $PostMedia->description,
],
]);
if (($item['post-type'] ?? null) == Item::PT_VIDEO) {
@ -3728,12 +3804,11 @@ class Item
* @param string $content
* @param bool $shared
* @param array $ignore_links A list of URLs to ignore
* @param bool $sensitive
* @return string modified content
* @throws InternalServerErrorException
* @throws ServiceUnavailableException
*/
private static function addLinkAttachment(int $uriid, array $attachments, string $body, string $content, bool $shared, array $ignore_links, bool $sensitive): string
private static function addLinkAttachment(int $uriid, array $attachments, string $body, string $content, bool $shared, array $ignore_links): string
{
DI::profiler()->startRecording('rendering');
// Don't show a preview when there is a visual attachment (audio or video)
@ -3776,9 +3851,9 @@ class Item
if ($preview && $attachment->preview) {
if ($attachment->previewWidth >= 500) {
$data['image'] = DI::baseUrl() . $attachment->getPreviewPath(Proxy::SIZE_MEDIUM, $sensitive);
$data['image'] = DI::baseUrl() . $attachment->getPreviewPath(Proxy::SIZE_MEDIUM);
} else {
$data['preview'] = DI::baseUrl() . $attachment->getPreviewPath(Proxy::SIZE_MEDIUM, $sensitive);
$data['preview'] = DI::baseUrl() . $attachment->getPreviewPath(Proxy::SIZE_MEDIUM);
}
}
@ -3803,11 +3878,6 @@ class Item
$data = BBCode::getAttachmentData($match[1]);
}
if ($sensitive) {
$data['image'] = '';
$data['preview'] = '';
}
DI::profiler()->stopRecording();
if (isset($data['url']) && !in_array(strtolower($data['url']), $ignore_links)) {
@ -3830,6 +3900,11 @@ class Item
$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'],
'$title' => $data['title'],
]);
} else {
$rendered = '';
}
@ -3950,10 +4025,17 @@ class Item
{
if (!empty($item['plink']) && Network::isValidHttpUrl($item['plink'])) {
$plink = $item['plink'];
} elseif (!empty($item['uri']) && Network::isValidHttpUrl($item['uri']) && !Network::isLocalLink($item['uri'])) {
} elseif (!empty($item['uri']) && Network::isValidHttpUrl($item['uri']) && !DI::baseUrl()->isLocalUrl($item['uri'])) {
$plink = $item['uri'];
}
if (($item['post-reason'] == self::PR_ANNOUNCEMENT) && ($item['owner-contact-type'] == Contact::TYPE_COMMUNITY) && ($item['owner-network'] == Protocol::DFRN)) {
$contact = Contact::getById($item['owner-id'], ['baseurl']);
if (!empty($contact['baseurl'])) {
$plink = $contact['baseurl'] . '/display/' . $item['guid'];
}
}
if (DI::userSession()->getLocalUserId()) {
$ret = [
'href' => "display/" . $item['guid'],
@ -4084,6 +4166,10 @@ class Item
return $item_id;
}
if (ActivityPub\Processor::alreadyKnown($uri, '')) {
return 0;
}
$hookData = [
'uri' => $uri,
'uid' => $uid,
@ -4096,9 +4182,14 @@ class Item
return is_numeric($hookData['item_id']) ? $hookData['item_id'] : 0;
}
$curlResult = DI::httpClient()->head($uri, [HttpClientOptions::ACCEPT_CONTENT => HttpClientAccept::JSON_AS]);
if (HTTPSignature::isValidContentType($curlResult->getContentType(), $uri)) {
$fetched_uri = ActivityPub\Processor::fetchMissingActivity($uri, [], '', $completion, $uid);
try {
$curlResult = DI::httpClient()->head($uri, [HttpClientOptions::ACCEPT_CONTENT => HttpClientAccept::JSON_AS, HttpClientOptions::REQUEST => HttpClientRequest::ACTIVITYPUB]);
if (HTTPSignature::isValidContentType($curlResult->getContentType(), $uri)) {
$fetched_uri = ActivityPub\Processor::fetchMissingActivity($uri, [], '', $completion, $uid);
}
} catch (\Throwable $th) {
Logger::info('Invalid link', ['uid' => $uid, 'uri' => $uri, 'code' => $th->getCode(), 'message' => $th->getMessage()]);
return 0;
}
if (!empty($fetched_uri)) {
@ -4174,4 +4265,22 @@ class Item
Logger::warning('Post does not exist although it was supposed to had been fetched.', ['id' => $id, 'url' => $url, 'uid' => $uid]);
return 0;
}
public static function incrementInbound(string $network)
{
$packets = (int)(DI::keyValue()->get('stats_packets_inbound_' . $network) ?? 0);
if ($packets >= PHP_INT_MAX) {
$packets = 0;
}
DI::keyValue()->set('stats_packets_inbound_' . $network, $packets + 1);
}
public static function incrementOutbound(string $network)
{
$packets = (int)(DI::keyValue()->get('stats_packets_outbound_' . $network) ?? 0);
if ($packets >= PHP_INT_MAX) {
$packets = 0;
}
DI::keyValue()->set('stats_packets_outbound_' . $network, $packets + 1);
}
}

View file

@ -53,6 +53,8 @@ class Nodeinfo
return;
}
$logger->info('User statistics - start');
$userStats = User::getStatistics();
DI::keyValue()->set('nodeinfo_total_users', $userStats['total_users']);
@ -60,21 +62,26 @@ class Nodeinfo
DI::keyValue()->set('nodeinfo_active_users_monthly', $userStats['active_users_monthly']);
DI::keyValue()->set('nodeinfo_active_users_weekly', $userStats['active_users_weekly']);
$logger->info('user statistics', $userStats);
$logger->info('user statistics - done', $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`)", Item::GRAVITY_COMMENT]);
DI::keyValue()->set('nodeinfo_local_posts', $posts);
DI::keyValue()->set('nodeinfo_local_comments', $comments);
$logger->info('User activity', ['posts' => $posts, 'comments' => $comments]);
$posts = DBA::count('post', ['deleted' => false, 'gravity' => Item::GRAVITY_COMMENT]);
$comments = DBA::count('post', ['deleted' => false, 'gravity' => Item::GRAVITY_COMMENT]);
DI::keyValue()->set('nodeinfo_total_posts', $posts);
DI::keyValue()->set('nodeinfo_total_comments', $comments);
$logger->info('Post statistics - done', ['posts' => $posts, 'comments' => $comments]);
}
/**
* Return the supported services
*
* @return Object with supported services
*/
*/
public static function getUsage(bool $version2 = false)
{
$config = DI::config();
@ -101,7 +108,7 @@ class Nodeinfo
* Return the supported services
*
* @return array with supported services
*/
*/
public static function getServices(): array
{
$services = [

View file

@ -32,6 +32,8 @@ use Friendica\Core\Storage\Exception\ReferenceStorageException;
use Friendica\Core\Storage\Exception\StorageException;
use Friendica\Core\Storage\Type\SystemResource;
use Friendica\Network\HTTPClient\Client\HttpClientAccept;
use Friendica\Network\HTTPClient\Client\HttpClientOptions;
use Friendica\Network\HTTPClient\Client\HttpClientRequest;
use Friendica\Object\Image;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\Images;
@ -234,7 +236,8 @@ class Photo
FROM `photo` WHERE `uid` = ? AND NOT `photo-type` IN (?, ?) $sqlExtra
GROUP BY `resource-id` $sqlExtra2",
$values
));
)
);
}
/**
@ -549,7 +552,7 @@ class Photo
// get photo to update
$photos = self::selectToArray(['backend-class', 'backend-ref'], $conditions);
foreach($photos as $photo) {
foreach ($photos as $photo) {
try {
$backend_class = DI::storageManager()->getWritableStorageByName($photo['backend-class'] ?? '');
$fields['backend-ref'] = $backend_class->put($image->asString(), $photo['backend-ref']);
@ -580,7 +583,9 @@ class Photo
$micro = '';
$photo = DBA::selectFirst(
'photo', ['resource-id'], ['uid' => $uid, 'contact-id' => $cid, 'scale' => 4, 'photo-type' => self::CONTACT_AVATAR]
'photo',
['resource-id'],
['uid' => $uid, 'contact-id' => $cid, 'scale' => 4, 'photo-type' => self::CONTACT_AVATAR]
);
if (!empty($photo['resource-id'])) {
$resource_id = $photo['resource-id'];
@ -597,7 +602,7 @@ class Photo
$filename = basename($image_url);
if (!empty($image_url)) {
$ret = DI::httpClient()->get($image_url, HttpClientAccept::IMAGE);
$ret = DI::httpClient()->get($image_url, HttpClientAccept::IMAGE, [HttpClientOptions::REQUEST => HttpClientRequest::MEDIAPROXY]);
Logger::debug('Got picture', ['Content-Type' => $ret->getHeader('Content-Type'), 'url' => $image_url]);
$img_str = $ret->getBodyString();
$type = $ret->getContentType();
@ -681,7 +686,9 @@ class Photo
}
$photo = DBA::selectFirst(
'photo', ['blurhash'], ['uid' => $uid, 'contact-id' => $cid, 'scale' => 4, 'photo-type' => self::CONTACT_AVATAR]
'photo',
['blurhash'],
['uid' => $uid, 'contact-id' => $cid, 'scale' => 4, 'photo-type' => self::CONTACT_AVATAR]
);
return [$image_url, $thumb, $micro, $photo['blurhash']];
@ -751,7 +758,8 @@ class Photo
if (!DI::config()->get('system', 'no_count', false)) {
/// @todo This query needs to be renewed. It is really slow
// At this time we just store the data in the cache
$albums = DBA::toArray(DBA::p("SELECT COUNT(DISTINCT `resource-id`) AS `total`, `album`, MIN(`created`) AS `created`
$albums = DBA::toArray(DBA::p(
"SELECT COUNT(DISTINCT `resource-id`) AS `total`, `album`, MIN(`created`) AS `created`
FROM `photo`
WHERE `uid` = ? AND `photo-type` IN (?, ?, ?) $sql_extra
GROUP BY `album` ORDER BY `created` DESC",
@ -762,7 +770,8 @@ class Photo
));
} else {
// This query doesn't do the count and is much faster
$albums = DBA::toArray(DBA::p("SELECT '' AS `total`, `album`, MIN(`created`) AS `created`
$albums = DBA::toArray(DBA::p(
"SELECT '' AS `total`, `album`, MIN(`created`) AS `created`
FROM `photo` USE INDEX (`uid_album_scale_created`)
WHERE `uid` = ? AND `photo-type` IN (?, ?, ?) $sql_extra
GROUP BY `album` ORDER BY `created` DESC",
@ -902,9 +911,11 @@ class Photo
*/
public static function setPermissionForResource(string $image_rid, int $uid, string $str_contact_allow, string $str_circle_allow, string $str_contact_deny, string $str_circle_deny)
{
$fields = ['allow_cid' => $str_contact_allow, 'allow_gid' => $str_circle_allow,
'deny_cid' => $str_contact_deny, 'deny_gid' => $str_circle_deny,
'accessible' => DI::pConfig()->get($uid, 'system', 'accessible-photos', false)];
$fields = [
'allow_cid' => $str_contact_allow, 'allow_gid' => $str_circle_allow,
'deny_cid' => $str_contact_deny, 'deny_gid' => $str_circle_deny,
'accessible' => DI::pConfig()->get($uid, 'system', 'accessible-photos', false)
];
$condition = ['resource-id' => $image_rid, 'uid' => $uid];
Logger::info('Set permissions', ['condition' => $condition, 'permissions' => $fields]);
@ -1046,7 +1057,7 @@ class Photo
{
$filename = basename($image_url);
if (!empty($image_url)) {
$ret = DI::httpClient()->get($image_url, HttpClientAccept::IMAGE);
$ret = DI::httpClient()->get($image_url, HttpClientAccept::IMAGE, [HttpClientOptions::REQUEST => HttpClientRequest::MEDIAPROXY]);
Logger::debug('Got picture', ['Content-Type' => $ret->getHeader('Content-Type'), 'url' => $image_url]);
$img_str = $ret->getBodyString();
$type = $ret->getContentType();

View file

@ -379,6 +379,21 @@ class Post
return self::selectView('post-user-view', $selected, $condition, $params);
}
/**
* Select rows from the post-origin-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 selectOrigin(array $selected = [], array $condition = [], array $params = [])
{
return self::selectView('post-origin-view', $selected, $condition, $params);
}
/**
* Select rows from the post-view view
*
@ -424,6 +439,21 @@ class Post
return self::selectView('post-thread-view', $selected, $condition, $params);
}
/**
* Select rows from the post-thread-origin-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 selectOriginThread(array $selected = [], array $condition = [], array $params = [])
{
return self::selectView('post-thread-origin-view', $selected, $condition, $params);
}
/**
* Select rows from the given view for a given user
*
@ -513,6 +543,11 @@ class Post
return self::selectViewForUser('post-timeline-view', $uid, $selected, $condition, $params);
}
public static function selectLocalTimelineForUser(int $uid, array $selected = [], array $condition = [], array $params = [])
{
return self::selectViewForUser('post-timeline-origin-view', $uid, $selected, $condition, $params);
}
/**
* Select rows from the post-thread-user-view view for a given user
*

View file

@ -124,7 +124,7 @@ class Content
'limit' => [$start, $limit]
];
$tags = DBA::select('post-searchindex', ['uri-id'], $condition, $params);
$tags = DBA::select(SearchIndex::getSearchTable(), ['uri-id'], $condition, $params);
$uriids = [];
while ($tag = DBA::fetch($tags)) {
@ -141,8 +141,8 @@ class Content
if ($uid != 0) {
$condition = ["MATCH (`searchtext`) AGAINST (? IN BOOLEAN MODE) AND (NOT `restricted` OR `uri-id` IN (SELECT `uri-id` FROM `post-user` WHERE `uid` = ?))", $search, $uid];
} else {
$condition = ["MATCH (`searchtext`) AGAINST (? IN BOOLEAN MODE) AND NOT `restricted", $search];
$condition = ["MATCH (`searchtext`) AGAINST (? IN BOOLEAN MODE) AND NOT `restricted`", $search];
}
return DBA::count('post-searchindex', $condition);
return DBA::count(SearchIndex::getSearchTable(), $condition);
}
}

View file

@ -418,4 +418,12 @@ class Engagement
}
return $fullTextSearch;
}
public static function unescapeKeywords(string $fullTextSearch): string
{
foreach (self::KEYWORDS as $keyword) {
$fullTextSearch = preg_replace('~(' . $keyword . ')_(.[\w\*@\.-]+)~', '$1:$2', $fullTextSearch);
}
return $fullTextSearch;
}
}

View file

@ -28,6 +28,7 @@ use Friendica\Core\Protocol;
use Friendica\Database\Database;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\Attach;
use Friendica\Model\Contact;
use Friendica\Model\Item;
use Friendica\Model\ItemURI;
@ -35,6 +36,7 @@ use Friendica\Model\Photo;
use Friendica\Model\Post;
use Friendica\Network\HTTPClient\Client\HttpClientAccept;
use Friendica\Network\HTTPClient\Client\HttpClientOptions;
use Friendica\Network\HTTPClient\Client\HttpClientRequest;
use Friendica\Protocol\ActivityPub;
use Friendica\Util\Images;
use Friendica\Util\Network;
@ -86,8 +88,8 @@ class Media
// "document" has got the lowest priority. So when the same file is both attached as document
// and embedded as picture then we only store the picture or replace the document
$found = DBA::selectFirst('post-media', ['type'], ['uri-id' => $media['uri-id'], 'url' => $media['url']]);
if (!$force && !empty($found) && (($found['type'] != self::DOCUMENT) || ($media['type'] == self::DOCUMENT))) {
Logger::info('Media already exists', ['uri-id' => $media['uri-id'], 'url' => $media['url']]);
if (!$force && !empty($found) && (!in_array($found['type'], [self::UNKNOWN, self::DOCUMENT]) || ($media['type'] == self::DOCUMENT))) {
Logger::info('Media already exists', ['uri-id' => $media['uri-id'], 'url' => $media['url'], 'found' => $found['type'], 'new' => $media['type']]);
return false;
}
@ -180,14 +182,20 @@ class Media
*/
public static function fetchAdditionalData(array $media): array
{
if (Network::isLocalLink($media['url'])) {
if (DI::baseUrl()->isLocalUrl($media['url'])) {
$media = self::fetchLocalData($media);
if (preg_match('|.*?/search\?(.+)|', $media['url'], $matches)) {
return $media;
}
if (empty($media['mimetype']) || empty($media['size'])) {
Logger::debug('Unknown local link', ['url' => $media['url']]);
}
}
// Fetch the mimetype or size if missing.
if (Network::isValidHttpUrl($media['url']) && (empty($media['mimetype']) || empty($media['size']))) {
$timeout = DI::config()->get('system', 'xrd_timeout');
$curlResult = DI::httpClient()->head($media['url'], [HttpClientOptions::TIMEOUT => $timeout]);
$curlResult = DI::httpClient()->head($media['url'], [HttpClientOptions::TIMEOUT => $timeout, HttpClientOptions::REQUEST => HttpClientRequest::CONTENTTYPE]);
// Workaround for systems that can't handle a HEAD request
if (!$curlResult->isSuccess() && ($curlResult->getReturnCode() == 405)) {
@ -389,7 +397,17 @@ class Media
*/
private static function fetchLocalData(array $media): array
{
if (!preg_match('|.*?/photo/(.*[a-fA-F0-9])\-(.*[0-9])\..*[\w]|', $media['url'] ?? '', $matches)) {
if (preg_match('|.*?/attach/(\d+)|', $media['url'], $matches)) {
$attachment = Attach::selectFirst(['filename', 'filetype', 'filesize'], ['id' => $matches[1]]);
if (!empty($attachment)) {
$media['name'] = $attachment['filename'];
$media['mimetype'] = $attachment['filetype'];
$media['size'] = $attachment['filesize'];
}
return $media;
}
if (!preg_match('|.*?/photo/(.*[a-fA-F0-9])\-(.*[0-9])\..*[\w]|', $media['url'], $matches)) {
return $media;
}
$photo = Photo::selectFirst([], ['resource-id' => $matches[1], 'scale' => $matches[2]]);
@ -426,42 +444,46 @@ class Media
return $data;
}
$type = explode('/', current(explode(';', $data['mimetype'])));
$data['type'] = self::getType($data['mimetype']);
return $data;
}
public static function getType(string $mimeType): int
{
$type = explode('/', current(explode(';', $mimeType)));
if (count($type) < 2) {
Logger::info('Unknown MimeType', ['type' => $type, 'media' => $data]);
$data['type'] = self::UNKNOWN;
return $data;
Logger::info('Unknown MimeType', ['type' => $type, 'media' => $mimeType]);
return self::UNKNOWN;
}
$filetype = strtolower($type[0]);
$subtype = strtolower($type[1]);
if ($filetype == 'image') {
$data['type'] = self::IMAGE;
$type = self::IMAGE;
} elseif ($filetype == 'video') {
$data['type'] = self::VIDEO;
$type = self::VIDEO;
} elseif ($filetype == 'audio') {
$data['type'] = self::AUDIO;
$type = self::AUDIO;
} elseif (($filetype == 'text') && ($subtype == 'html')) {
$data['type'] = self::HTML;
$type = self::HTML;
} elseif (($filetype == 'text') && ($subtype == 'xml')) {
$data['type'] = self::XML;
$type = self::XML;
} elseif (($filetype == 'text') && ($subtype == 'plain')) {
$data['type'] = self::PLAIN;
$type = self::PLAIN;
} elseif ($filetype == 'text') {
$data['type'] = self::TEXT;
$type = self::TEXT;
} elseif (($filetype == 'application') && ($subtype == 'x-bittorrent')) {
$data['type'] = self::TORRENT;
$type = self::TORRENT;
} elseif ($filetype == 'application') {
$data['type'] = self::APPLICATION;
$type = self::APPLICATION;
} else {
$data['type'] = self::UNKNOWN;
Logger::info('Unknown type', ['filetype' => $filetype, 'subtype' => $subtype, 'media' => $data]);
return $data;
$type = self::UNKNOWN;
Logger::info('Unknown type', ['filetype' => $filetype, 'subtype' => $subtype, 'media' => $mimeType]);
}
Logger::debug('Detected type', ['filetype' => $filetype, 'subtype' => $subtype, 'media' => $data]);
return $data;
Logger::debug('Detected type', ['filetype' => $filetype, 'subtype' => $subtype, 'media' => $mimeType]);
return $type;
}
/**
@ -902,19 +924,7 @@ class Media
$body = BBCode::removeAttachment($body);
foreach (self::getByURIId($uriid, $types) as $media) {
if (Item::containsLink($body, $media['preview'] ?? $media['url'], $media['type'])) {
continue;
}
if ($media['type'] == self::IMAGE) {
$body .= "\n" . Images::getBBCodeByUrl($media['url'], $media['preview'], $media['description'] ?? '');
} elseif ($media['type'] == self::AUDIO) {
$body .= "\n[audio]" . $media['url'] . "[/audio]\n";
} elseif ($media['type'] == self::VIDEO) {
$body .= "\n[video]" . $media['url'] . "[/video]\n";
} else {
$body .= "\n[url]" . $media['url'] . "[/url]\n";
}
$body = self::addAttachmentToBody($media, $body);
}
if (preg_match("/.*(\[attachment.*?\].*?\[\/attachment\]).*/ism", $original_body, $match)) {
@ -924,6 +934,24 @@ class Media
return $body;
}
public static function addAttachmentToBody(array $media, string $body): string
{
if (Item::containsLink($body, $media['preview'] ?? $media['url'], $media['type'])) {
return $body;
}
if ($media['type'] == self::IMAGE) {
$body .= "\n" . Images::getBBCodeByUrl($media['url'], $media['preview'], $media['description'] ?? '');
} elseif ($media['type'] == self::AUDIO) {
$body .= "\n[audio]" . $media['url'] . "[/audio]\n";
} elseif ($media['type'] == self::VIDEO) {
$body .= "\n[video]" . $media['url'] . "[/video]\n";
} else {
$body .= "\n[url]" . $media['url'] . "[/url]\n";
}
return $body;
}
/**
* Add an [attachment] element to the body for a given uri-id with a HTML media element
*

93
src/Model/Post/Origin.php Normal file
View file

@ -0,0 +1,93 @@
<?php
/**
* @copyright Copyright (C) 2010-2024, 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\Post;
use Friendica\Database\DBA;
use \BadMethodCallException;
use Friendica\Database\Database;
use Friendica\DI;
class Origin
{
/**
* Insert a new post origin entry
*
* @param array $fields
* @return boolean was the insert successful?
* @throws \Exception
*/
public static function insert(array $data = []): bool
{
if (!$data['origin'] || ($data['uid'] == 0)) {
return false;
}
$fields = DI::dbaDefinition()->truncateFieldsForTable('post-origin', $data);
return DBA::insert('post-origin', $fields, Database::INSERT_IGNORE);
}
/**
* Update a post origin entry
*
* @param integer $uri_id
* @param integer $uid
* @param array $data
* @param bool $insert_if_missing
* @return bool
* @throws \Exception
*/
public static function update(int $uri_id, int $uid, array $data = [], bool $insert_if_missing = false)
{
if (empty($uri_id)) {
throw new BadMethodCallException('Empty URI_id');
}
$fields = DI::dbaDefinition()->truncateFieldsForTable('post-origin', $data);
// Remove the key fields
unset($fields['uri-id']);
unset($fields['uid']);
if (empty($fields)) {
return true;
}
return DBA::update('post-origin', $fields, ['uri-id' => $uri_id, 'uid' => $uid], $insert_if_missing ? true : []);
}
/**
* Delete a row from the post-origin table
*
* @param array $conditions Field condition(s)
* @param array $options
* - cascade: If true we delete records in other tables that depend on the one we're deleting through
* relations (default: true)
*
* @return boolean was the delete successful?
* @throws \Exception
*/
public static function delete(array $conditions, array $options = [])
{
return DBA::delete('post-origin', $conditions, $options);
}
}

View file

@ -99,4 +99,14 @@ class SearchIndex
}
return DateTimeFormat::utc('now - ' . $days . ' day');
}
public static function getSearchTable(): string
{
return DI::config()->get('system', 'limited_search_scope') ? 'post-engagement' : 'post-searchindex';
}
public static function getSearchView(): string
{
return DI::config()->get('system', 'limited_search_scope') ? 'post-engagement-user-view' : 'post-searchindex-user-view';
}
}

View file

@ -31,20 +31,14 @@ use Friendica\Core\Logger;
use Friendica\Core\Protocol;
use Friendica\Core\Renderer;
use Friendica\Core\Search;
use Friendica\Core\System;
use Friendica\Core\Worker;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Network\HTTPClient\Client\HttpClientAccept;
use Friendica\Network\HTTPClient\Client\HttpClientOptions;
use Friendica\Network\HTTPException;
use Friendica\Network\HTTPException\InternalServerErrorException;
use Friendica\Protocol\Activity;
use Friendica\Protocol\Diaspora;
use Friendica\Security\PermissionSet\Entity\PermissionSet;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\HTTPSignature;
use Friendica\Util\Network;
use Friendica\Util\Proxy;
use Friendica\Util\Strings;
@ -296,7 +290,7 @@ class Profile
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()) {
if (!empty($profile['cid']) && DI::userSession()->getMyUrl()) {
$profile_contact = Contact::selectFirst([], ['id' => $profile['cid']]);
}
@ -321,19 +315,19 @@ class Profile
// Who is the logged-in user to this profile?
$visitor_contact = [];
if (!empty($profile['uid']) && self::getMyURL()) {
$visitor_contact = Contact::selectFirst(['rel'], ['uid' => $profile['uid'], 'nurl' => Strings::normaliseLink(self::getMyURL())]);
if (!empty($profile['uid']) && DI::userSession()->getMyUrl()) {
$visitor_contact = Contact::selectFirst(['rel'], ['uid' => $profile['uid'], 'nurl' => Strings::normaliseLink(DI::userSession()->getMyUrl())]);
}
$local_user_is_self = self::getMyURL() && ($profile['url'] == self::getMyURL());
$visitor_is_authenticated = (bool)self::getMyURL();
$local_user_is_self = DI::userSession()->getMyUrl() && ($profile['url'] == DI::userSession()->getMyUrl());
$visitor_is_authenticated = (bool)DI::userSession()->getMyUrl();
$visitor_is_following =
in_array($visitor_contact['rel'] ?? 0, [Contact::FOLLOWER, Contact::FRIEND])
|| in_array($profile_contact['rel'] ?? 0, [Contact::SHARING, Contact::FRIEND]);
$visitor_is_followed =
in_array($visitor_contact['rel'] ?? 0, [Contact::SHARING, Contact::FRIEND])
|| in_array($profile_contact['rel'] ?? 0, [Contact::FOLLOWER, Contact::FRIEND]);
$visitor_base_path = self::getMyURL() ? preg_replace('=/profile/(.*)=ism', '', self::getMyURL()) : '';
$visitor_base_path = DI::userSession()->getMyUrl() ? preg_replace('=/profile/(.*)=ism', '', DI::userSession()->getMyUrl()) : '';
if (!$local_user_is_self) {
if (!$visitor_is_authenticated) {
@ -628,8 +622,10 @@ class Profile
$bd_format = DI::l10n()->t('g A l F d'); // 8 AM Friday January 18
$classtoday = '';
$condition = ["`uid` = ? AND `type` != 'birthday' AND `start` < ? AND `start` >= ?",
$uid, DateTimeFormat::utc('now + 7 days'), DateTimeFormat::utc('now - 1 days')];
$condition = [
"`uid` = ? AND `type` != 'birthday' AND `start` < ? AND `start` >= ?",
$uid, DateTimeFormat::utc('now + 7 days'), DateTimeFormat::utc('now - 1 days')
];
$s = DBA::select('event', [], $condition, ['order' => ['start']]);
$r = [];
@ -639,9 +635,11 @@ class Profile
$total = 0;
while ($rr = DBA::fetch($s)) {
$condition = ['parent-uri' => $rr['uri'], 'uid' => $rr['uid'], 'author-id' => $pcid,
$condition = [
'parent-uri' => $rr['uri'], 'uid' => $rr['uid'], 'author-id' => $pcid,
'vid' => [Verb::getID(Activity::ATTEND), Verb::getID(Activity::ATTENDMAYBE)],
'visible' => true, 'deleted' => false];
'visible' => true, 'deleted' => false
];
if (!Post::exists($condition)) {
continue;
}
@ -695,235 +693,6 @@ 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 DI::userSession()->getMyUrl();
}
/**
* Process the 'zrl' parameter and initiate the remote authentication.
*
* This method checks if the visitor has a public contact entry and
* redirects the visitor to his/her instance to start the magic auth (Authentication)
* process.
*
* Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/channel.php
*
* The implementation for Friendica sadly differs in some points from the one for Hubzilla:
* - Hubzilla uses the "zid" parameter, while for Friendica it had been replaced with "zrl"
* - There seem to be some reverse authentication (rmagic) that isn't implemented in Friendica at all
*
* It would be favourable to harmonize the two implementations.
*
* @param App $a Application instance.
*
* @return void
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
* @throws \ImagickException
*/
public static function zrlInit(App $a)
{
$my_url = self::getMyURL();
$my_url = Network::isUrlValid($my_url);
if (empty($my_url) || DI::userSession()->getLocalUserId()) {
return;
}
$addr = $_GET['addr'] ?? $my_url;
$arr = ['zrl' => $my_url, 'url' => DI::args()->getCommand()];
Hook::callAll('zrl_init', $arr);
// Try to find the public contact entry of the visitor.
$cid = Contact::getIdForURL($my_url);
if (!$cid) {
Logger::info('No contact record found for ' . $my_url);
return;
}
$contact = DBA::selectFirst('contact',['id', 'url'], ['id' => $cid]);
if (DBA::isResult($contact) && DI::userSession()->getRemoteUserId() && DI::userSession()->getRemoteUserId() == $contact['id']) {
Logger::info('The visitor ' . $my_url . ' is already authenticated');
return;
}
// Avoid endless loops
$cachekey = 'zrlInit:' . $my_url;
if (DI::cache()->get($cachekey)) {
Logger::info('URL ' . $my_url . ' already tried to authenticate.');
return;
} else {
DI::cache()->set($cachekey, true, Duration::MINUTE);
}
Logger::info('Not authenticated. Invoking reverse magic-auth for ' . $my_url);
// Remove the "addr" parameter from the destination. It is later added as separate parameter again.
$addr_request = 'addr=' . urlencode($addr);
$query = rtrim(str_replace($addr_request, '', DI::args()->getQueryString()), '?&');
// The other instance needs to know where to redirect.
$dest = urlencode(DI::baseUrl() . '/' . $query);
// We need to extract the basebath from the profile url
// to redirect the visitors '/magic' module.
$basepath = Contact::getBasepath($contact['url']);
if ($basepath != DI::baseUrl() && !strstr($dest, '/magic')) {
$magic_path = $basepath . '/magic' . '?owa=1&dest=' . $dest . '&' . $addr_request;
// We have to check if the remote server does understand /magic without invoking something
$serverret = DI::httpClient()->head($basepath . '/magic', [HttpClientOptions::ACCEPT_CONTENT => HttpClientAccept::HTML]);
if ($serverret->isSuccess()) {
Logger::info('Doing magic auth for visitor ' . $my_url . ' to ' . $magic_path);
System::externalRedirect($magic_path);
}
}
}
/**
* Set the visitor cookies (see remote_user()) for the given handle
*
* @param string $handle Visitor handle
*
* @return array Visitor contact array
*/
public static function addVisitorCookieForHandle(string $handle): array
{
$a = DI::app();
// Try to find the public contact entry of the visitor.
$cid = Contact::getIdForURL($handle);
if (!$cid) {
Logger::info('Handle not found', ['handle' => $handle]);
return [];
}
$visitor = Contact::getById($cid);
// Authenticate the visitor.
DI::userSession()->setMultiple([
'authenticated' => 1,
'visitor_id' => $visitor['id'],
'visitor_handle' => $visitor['addr'],
'visitor_home' => $visitor['url'],
'my_url' => $visitor['url'],
'remote_comment' => $visitor['subscribe'],
]);
DI::userSession()->setVisitorsContacts($visitor['url']);
$a->setContactId($visitor['id']);
Logger::info('Authenticated visitor', ['url' => $visitor['url']]);
return $visitor;
}
/**
* Set the visitor cookies (see remote_user()) for signed HTTP requests
*
* @param array $server The content of the $_SERVER superglobal
* @return array Visitor contact array
* @throws InternalServerErrorException
*/
public static function addVisitorCookieForHTTPSigner(array $server): array
{
$requester = HTTPSignature::getSigner('', $server);
if (empty($requester)) {
return [];
}
return Profile::addVisitorCookieForHandle($requester);
}
/**
* OpenWebAuth authentication.
*
* Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/zid.php
*
* @param string $token
*
* @return void
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
* @throws \ImagickException
*/
public static function openWebAuthInit(string $token)
{
$a = DI::app();
// Clean old OpenWebAuthToken entries.
OpenWebAuthToken::purge('owt', '3 MINUTE');
// Check if the token we got is the same one
// we have stored in the database.
$visitor_handle = OpenWebAuthToken::getMeta('owt', 0, $token);
if ($visitor_handle === false) {
return;
}
$visitor = self::addVisitorCookieForHandle($visitor_handle);
if (empty($visitor)) {
return;
}
$arr = [
'visitor' => $visitor,
'url' => DI::args()->getQueryString()
];
/**
* @hooks magic_auth_success
* Called when a magic-auth was successful.
* * \e array \b visitor
* * \e string \b url
*/
Hook::callAll('magic_auth_success', $arr);
$a->setContactId($arr['visitor']['id']);
DI::sysmsg()->addInfo(DI::l10n()->t('OpenWebAuth: %1$s welcomes %2$s', DI::baseUrl()->getHost(), $visitor['name']));
Logger::info('OpenWebAuth: auth success from ' . $visitor['addr']);
}
/**
* Returns URL with URL-encoded zrl parameter
*
* @param string $url URL to enhance
* @param bool $force Either to force adding zrl parameter
*
* @return string URL with 'zrl' parameter or original URL in case of no Friendica profile URL
*/
public static function zrl(string $url, bool $force = false): string
{
if (!strlen($url)) {
return $url;
}
if (!strpos($url, '/profile/') && !$force) {
return $url;
}
if ($force && substr($url, -1, 1) !== '/') {
$url = $url . '/';
}
$achar = strpos($url, '?') ? '&' : '?';
$mine = self::getMyURL();
if ($mine && !Strings::compareLink($mine, $url)) {
return $url . $achar . 'zrl=' . urlencode($mine);
}
return $url;
}
/**
* Get the user ID of the page owner.
*
@ -959,7 +728,8 @@ class Profile
if (!empty($search)) {
$publish = (DI::config()->get('system', 'publish_all') ? '' : "AND `publish` ");
$searchTerm = '%' . $search . '%';
$condition = ["`verified` AND NOT `blocked` AND NOT `account_removed` AND NOT `account_expired`
$condition = [
"`verified` AND NOT `blocked` AND NOT `account_removed` AND NOT `account_expired`
$publish
AND ((`name` LIKE ?) OR
(`nickname` LIKE ?) OR
@ -970,7 +740,8 @@ class Profile
(`pub_keywords` LIKE ?) OR
(`prv_keywords` LIKE ?))",
$searchTerm, $searchTerm, $searchTerm, $searchTerm,
$searchTerm, $searchTerm, $searchTerm, $searchTerm];
$searchTerm, $searchTerm, $searchTerm, $searchTerm
];
} else {
$condition = ['verified' => true, 'blocked' => false, 'account_removed' => false, 'account_expired' => false];
if (!DI::config()->get('system', 'publish_all')) {
@ -1073,4 +844,44 @@ class Profile
DBA::delete('profile', ['id' => $profile['id']]);
}
}
/**
* Get "about" field with the added responsible relay contact if appropriate.
*
* @param string $about
* @param integer|null $parent_uid
* @param integer $account_type
* @param string $language
* @return string
*/
public static function addResponsibleRelayContact(string $about = null, int $parent_uid = null, int $account_type, string $language): ?string
{
if (($account_type != User::ACCOUNT_TYPE_RELAY) || empty($parent_uid)) {
return $about;
}
$parent = User::getOwnerDataById($parent_uid);
if (strpos($about, $parent['addr']) || strpos($about, $parent['url'])) {
return $about;
}
$l10n = DI::l10n()->withLang($language);
return $about .= "\n" . $l10n->t('Responsible account: %s', $parent['addr']);
}
/**
* Set "about" field with the added responsible relay contact if appropriate.
*
* @param integer $uid
* @return void
*/
public static function setResponsibleRelayContact(int $uid)
{
$owner = User::getOwnerDataById($uid);
$about = self::addResponsibleRelayContact($owner['about'], $owner['parent-uid'], $owner['account-type'], $owner['language']);
if ($about != $owner['about']) {
self::update(['about' => $about], $uid);
}
}
}

View file

@ -25,7 +25,7 @@ use Friendica\Core\Logger;
use Friendica\Core\Worker;
use Friendica\Database\DBA;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\Network;
use GuzzleHttp\Psr7\Uri;
class PushSubscriber
{
@ -179,7 +179,7 @@ class PushSubscriber
$parts = parse_url($subscriber['callback_url']);
unset($parts['path']);
$server_url = Network::unparseURL($parts);
$server_url = (string)Uri::fromParts((array)$parts);
$gsid = GServer::getID($server_url, true);
if (!empty($gsid)) {
GServer::setProtocol($gsid, Post\DeliveryData::OSTATUS);

View file

@ -25,6 +25,7 @@ use DivineOmega\DOFileCachePSR6\CacheItemPool;
use DivineOmega\PasswordExposed;
use ErrorException;
use Exception;
use Friendica\App;
use Friendica\Content\Pager;
use Friendica\Core\Hook;
use Friendica\Core\L10n;
@ -37,6 +38,8 @@ use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Module;
use Friendica\Network\HTTPClient\Client\HttpClientAccept;
use Friendica\Network\HTTPClient\Client\HttpClientOptions;
use Friendica\Network\HTTPClient\Client\HttpClientRequest;
use Friendica\Network\HTTPException;
use Friendica\Object\Image;
use Friendica\Protocol\Delivery;
@ -72,6 +75,7 @@ class User
const PAGE_FLAGS_FREELOVE = 3;
const PAGE_FLAGS_BLOG = 4;
const PAGE_FLAGS_PRVGROUP = 5;
const PAGE_FLAGS_COMM_MAN = 6;
/**
* @}
*/
@ -161,6 +165,7 @@ class User
}
}
$system['name'] = App::PLATFORM . " '" . App::CODENAME . "' " . App::VERSION . '-' . DB_UPDATE_VERSION;
$system['sprvkey'] = $system['uprvkey'] = $system['prvkey'];
$system['spubkey'] = $system['upubkey'] = $system['pubkey'];
$system['nickname'] = $system['nick'];
@ -407,6 +412,29 @@ class User
return 0;
}
/**
* Returns the user id of a given contact id
*
* @param int $cid
*
* @return integer user id
* @throws Exception
*/
public static function getIdForContactId(int $cid): int
{
$account = Contact::selectFirstAccountUser(['pid', 'self', 'uid'], ['id' => $cid]);
if (empty($account['pid'])) {
return 0;
}
if ($account['self']) {
return $account['uid'];
}
$self = Contact::selectFirstAccountUser(['uid'], ['pid' => $cid, 'self' => true]);
return $self['uid'] ?? 0;
}
/**
* Get a user based on its email
*
@ -1397,7 +1425,7 @@ class User
$photo_failure = false;
$filename = basename($photo);
$curlResult = DI::httpClient()->get($photo, HttpClientAccept::IMAGE);
$curlResult = DI::httpClient()->get($photo, HttpClientAccept::IMAGE, [HttpClientOptions::REQUEST => HttpClientRequest::CONTENTTYPE]);
if ($curlResult->isSuccess()) {
Logger::debug('Got picture', ['Content-Type' => $curlResult->getHeader('Content-Type'), 'url' => $photo]);
$img_str = $curlResult->getBodyString();
@ -1487,6 +1515,12 @@ class User
*/
public static function block(int $uid, bool $block = true): bool
{
$self = Contact::getPublicIdByUserId($uid);
if ($block) {
Contact::block($self);
} else {
Contact::unblock($self);
}
return DBA::update('user', ['blocked' => $block], ['uid' => $uid]);
}

View file

@ -22,9 +22,11 @@
namespace Friendica\Module\ActivityPub;
use Friendica\Core\Logger;
use Friendica\Core\Protocol;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\Item;
use Friendica\Model\User;
use Friendica\Module\BaseApi;
use Friendica\Module\Special\HTTPException;
@ -77,6 +79,11 @@ class Inbox extends BaseApi
throw new \Friendica\Network\HTTPException\BadRequestException();
}
if (!HTTPSignature::isValidContentType($this->server['CONTENT_TYPE'] ?? '')) {
Logger::notice('Unexpected content type', ['content-type' => $this->server['CONTENT_TYPE'] ?? '', 'agent' => $this->server['HTTP_USER_AGENT'] ?? '']);
throw new \Friendica\Network\HTTPException\UnsupportedMediaTypeException();
}
if (DI::config()->get('debug', 'ap_inbox_log')) {
if (HTTPSignature::getSigner($postdata, $_SERVER)) {
$filename = 'signed-activitypub';
@ -98,6 +105,7 @@ class Inbox extends BaseApi
$uid = 0;
}
Item::incrementInbound(Protocol::ACTIVITYPUB);
ActivityPub\Receiver::processInbox($postdata, $_SERVER, $uid);
throw new \Friendica\Network\HTTPException\AcceptedException();

View file

@ -51,7 +51,7 @@ class Whoami extends BaseApi
$data['name'] = $owner['name'];
$data['preferredUsername'] = $owner['nick'];
$data['alsoKnownAs'] = [];
$data['manuallyApprovesFollowers'] = in_array($owner['page-flags'], [User::PAGE_FLAGS_NORMAL, User::PAGE_FLAGS_PRVGROUP]);
$data['manuallyApprovesFollowers'] = in_array($owner['page-flags'], [User::PAGE_FLAGS_NORMAL, User::PAGE_FLAGS_PRVGROUP, User::PAGE_FLAGS_COMM_MAN]);
$data['discoverable'] = (bool)$owner['net-publish'];
$data['tag'] = [];

View file

@ -28,6 +28,12 @@ use Friendica\Module\BaseAdmin;
class Index extends BaseAdmin
{
protected function post(array $request = [])
{
// @todo check if POST is really used here
$this->content($request);
}
protected function content(array $request = []): string
{
parent::content();

View file

@ -34,25 +34,24 @@ class Features extends BaseAdmin
self::checkFormSecurityTokenRedirectOnError('/admin/features', 'admin_manage_features');
$features = Feature::get(false);
foreach ($features as $fname => $fdata) {
foreach (Feature::get(false) as $fdata) {
foreach (array_slice($fdata, 1) as $f) {
$feature = $f[0];
$feature_state = 'feature_' . $feature;
$featurelock = 'featurelock_' . $feature;
switch ($_POST['featureselect_' . $feature]) {
case 0:
DI::config()->set('feature', $feature, false);
DI::config()->delete('feature_lock', $feature);
break;
if (!empty($_POST[$feature_state])) {
$val = intval($_POST[$feature_state]);
} else {
$val = 0;
}
DI::config()->set('feature', $feature, $val);
case 1:
DI::config()->set('feature', $feature, true);
DI::config()->delete('feature_lock', $feature);
break;
if (!empty($_POST[$featurelock])) {
DI::config()->set('feature_lock', $feature, 1);
} else {
DI::config()->delete('feature_lock', $feature);
case 2:
DI::config()->delete('feature', $feature);
DI::config()->set('feature_lock', $feature, true);
break;
}
}
}
@ -64,17 +63,15 @@ class Features extends BaseAdmin
{
parent::content();
$features = [];
$features = [];
$selection = [DI::l10n()->t('No'), DI::l10n()->t('Yes'), DI::l10n()->t('Locked')];
foreach (Feature::get(false) as $fname => $fdata) {
$features[$fname] = [];
$features[$fname][0] = $fdata[0];
foreach (array_slice($fdata, 1) as $f) {
$set = DI::config()->get('feature', $f[0], $f[3]);
$features[$fname][1][] = [
['feature_' . $f[0], $f[1], $set, $f[2]],
['featurelock_' . $f[0], DI::l10n()->t('Lock feature %s', $f[1]), $f[4], '']
];
$selected = $f[4] ? 2 : (int)$set;
$features[$fname][1][] = ['featureselect_' . $f[0], $f[1], $selected, $f[2], $selection];
}
}

View file

@ -221,7 +221,6 @@ class Federation extends BaseAdmin
'$page' => DI::l10n()->t('Federation Statistics'),
'$intro' => $intro,
'$counts' => $counts,
'$version' => App::VERSION,
'$legendtext' => DI::l10n()->tt('Currently this node is aware of %2$s node (%3$s active users last month, %4$s active users last six months, %5$s registered users in total) from the following platforms:', 'Currently this node is aware of %2$s nodes (%3$s active users last month, %4$s active users last six months, %5$s registered users in total) from the following platforms:', $total, number_format($total), number_format($month), number_format($halfyear), number_format($users)),
]);
}

View file

@ -66,11 +66,14 @@ class Settings extends BaseAdmin
parent::content();
$log_choices = [
LogLevel::ERROR => 'Error',
LogLevel::WARNING => 'Warning',
LogLevel::NOTICE => 'Notice',
LogLevel::INFO => 'Info',
LogLevel::DEBUG => 'Debug',
LogLevel::EMERGENCY => 'Emergency',
LogLevel::ALERT => 'Alert',
LogLevel::CRITICAL => 'Critical',
LogLevel::ERROR => 'Error',
LogLevel::WARNING => 'Warning',
LogLevel::NOTICE => 'Notice',
LogLevel::INFO => 'Info',
LogLevel::DEBUG => 'Debug',
];
if (ini_get('log_errors')) {

View file

@ -47,6 +47,8 @@ class View extends BaseAdmin
$filters_valid_values = [
'level' => [
'',
LogLevel::EMERGENCY,
LogLevel::ALERT,
LogLevel::CRITICAL,
LogLevel::ERROR,
LogLevel::WARNING,
@ -54,7 +56,7 @@ class View extends BaseAdmin
LogLevel::INFO,
LogLevel::DEBUG,
],
'context' => ['', 'index', 'worker'],
'context' => ['', 'index', 'worker', 'daemon'],
];
$filters = [
'level' => $_GET['level'] ?? '',
@ -71,10 +73,10 @@ class View extends BaseAdmin
} else {
try {
$data = DI::parsedLogIterator()
->open($f)
->withLimit(self::LIMIT)
->withFilters($filters)
->withSearch($search);
->open($f)
->withLimit(self::LIMIT)
->withFilters($filters)
->withSearch($search);
} catch (\Exception $e) {
$error = DI::l10n()->t('Couldn\'t open <strong>%1$s</strong> log file.<br/>Check to see if file %1$s is readable.', $f);
}

View file

@ -56,13 +56,14 @@ class Queue extends BaseAdmin
}
// @TODO Move to Model\WorkerQueue::getEntries()
$entries = DBA::select('workerqueue', ['id', 'parameter', 'created', 'priority', 'command'], $condition, ['limit' => 999, 'order' => ['created']]);
$entries = DBA::select('workerqueue', ['id', 'parameter', 'created', 'next_try', 'priority', 'command'], $condition, ['limit' => 999, 'order' => ['created']]);
$r = [];
while ($entry = DBA::fetch($entries)) {
// fix GH-5469. ref: src/Core/Worker.php:217
$entry['parameter'] = Arrays::recursiveImplode(json_decode($entry['parameter'], true), ': ');
$entry['created'] = DateTimeFormat::local($entry['created']);
$entry['next_try'] = DateTimeFormat::local($entry['next_try']);
$r[] = $entry;
}
DBA::close($entries);
@ -76,8 +77,10 @@ class Queue extends BaseAdmin
'$command_header' => DI::l10n()->t('Command'),
'$param_header' => DI::l10n()->t('Job Parameters'),
'$created_header' => DI::l10n()->t('Created'),
'$next_try_header' => DI::l10n()->t('Next Try'),
'$prio_header' => DI::l10n()->t('Priority'),
'$info' => $info,
'$status' => $status,
'$entries' => $r,
]);
}

View file

@ -97,7 +97,6 @@ class Site extends BaseAdmin
$adjust_poll_frequency = !empty($_POST['adjust_poll_frequency']);
$min_poll_interval = (!empty($_POST['min_poll_interval']) ? intval(trim($_POST['min_poll_interval'])) : 0);
$explicit_content = !empty($_POST['explicit_content']);
$proxify_content = !empty($_POST['proxify_content']);
$local_search = !empty($_POST['local_search']);
$blocked_tags = (!empty($_POST['blocked_tags']) ? trim($_POST['blocked_tags']) : '');
$cache_contact_avatar = !empty($_POST['cache_contact_avatar']);
@ -141,6 +140,7 @@ class Site extends BaseAdmin
$temppath = (!empty($_POST['temppath']) ? trim($_POST['temppath']) : '');
$singleuser = (!empty($_POST['singleuser']) ? trim($_POST['singleuser']) : '');
$only_tag_search = !empty($_POST['only_tag_search']);
$limited_search_scope = !empty($_POST['limited_search_scope']);
$search_age_days = (!empty($_POST['search_age_days']) ? intval($_POST['search_age_days']) : 0);
$compute_circle_counts = !empty($_POST['compute_circle_counts']);
$process_view = !empty($_POST['process_view']);
@ -271,7 +271,6 @@ class Site extends BaseAdmin
$transactionConfig->set('system', 'adjust_poll_frequency' , $adjust_poll_frequency);
$transactionConfig->set('system', 'min_poll_interval' , $min_poll_interval);
$transactionConfig->set('system', 'explicit_content' , $explicit_content);
$transactionConfig->set('system', 'proxify_content' , $proxify_content);
$transactionConfig->set('system', 'local_search' , $local_search);
$transactionConfig->set('system', 'blocked_tags' , Strings::cleanTags($blocked_tags));
$transactionConfig->set('system', 'cache_contact_avatar' , $cache_contact_avatar);
@ -319,6 +318,7 @@ class Site extends BaseAdmin
$transactionConfig->set('system', 'temppath', $temppath);
$transactionConfig->set('system', 'only_tag_search', $only_tag_search);
$transactionConfig->set('system', 'limited_search_scope', $limited_search_scope);
$transactionConfig->set('system', 'search_age_days', $search_age_days);
$transactionConfig->set('system', 'compute_circle_counts', $compute_circle_counts);
$transactionConfig->set('system', 'process_view', $process_view);
@ -518,7 +518,6 @@ class Site extends BaseAdmin
'$private_addons' => ['private_addons', DI::l10n()->t('Disallow public access to addons listed in the apps menu.'), DI::config()->get('config', 'private_addons'), DI::l10n()->t('Checking this box will restrict addons listed in the apps menu to members only.')],
'$disable_embedded' => ['disable_embedded', DI::l10n()->t('Don\'t embed private images in posts'), DI::config()->get('system', 'disable_embedded'), DI::l10n()->t('Don\'t replace locally-hosted private photos in posts with an embedded copy of the image. This means that contacts who receive posts containing private photos will have to authenticate and load each image, which may take a while.')],
'$explicit_content' => ['explicit_content', DI::l10n()->t('Explicit Content'), DI::config()->get('system', 'explicit_content'), DI::l10n()->t('Set this to announce that your node is used mostly for explicit content that might not be suited for minors. This information will be published in the node information and might be used, e.g. by the global directory, to filter your node from listings of nodes to join. Additionally a note about this will be shown at the user registration page.')],
'$proxify_content' => ['proxify_content', DI::l10n()->t('Proxify external content'), DI::config()->get('system', 'proxify_content'), DI::l10n()->t('Route external content via the proxy functionality. This is used for example for some OEmbed accesses and in some other rare cases.')],
'$local_search' => ['local_search', DI::l10n()->t('Only local search'), DI::config()->get('system', 'local_search'), DI::l10n()->t('Blocks search for users who are not logged in to prevent crawlers from blocking your system.')],
'$blocked_tags' => ['blocked_tags', DI::l10n()->t('Blocked tags for trending tags'), DI::config()->get('system', 'blocked_tags'), DI::l10n()->t("Comma separated list of hashtags that shouldn't be displayed in the trending tags.")],
'$cache_contact_avatar' => ['cache_contact_avatar', DI::l10n()->t('Cache contact avatars'), DI::config()->get('system', 'cache_contact_avatar'), DI::l10n()->t('Locally store the avatar pictures of the contacts. This uses a lot of storage space but it increases the performance.')],
@ -574,6 +573,7 @@ class Site extends BaseAdmin
'$itemspage_network_mobile' => ['itemspage_network_mobile', DI::l10n()->t('Items per page for mobile devices'), DI::config()->get('system', 'itemspage_network_mobile'), DI::l10n()->t('Number of items per page in stream pages (network, community, profile/contact statuses, search) for mobile devices.')],
'$temppath' => ['temppath', DI::l10n()->t('Temp path'), DI::config()->get('system', 'temppath'), DI::l10n()->t('If you have a restricted system where the webserver can\'t access the system temp path, enter another path here.')],
'$only_tag_search' => ['only_tag_search', DI::l10n()->t('Only search in tags'), DI::config()->get('system', 'only_tag_search'), DI::l10n()->t('On large systems the text search can slow down the system extremely.')],
'$limited_search_scope' => ['limited_search_scope', DI::l10n()->t('Limited search scope'), DI::config()->get('system', 'limited_search_scope'), DI::l10n()->t('If enabled, searches will only be performed in the data used for the channels and not in all posts.')],
'$search_age_days' => ['search_age_days', DI::l10n()->t('Maximum age of items in the search table'), DI::config()->get('system', 'search_age_days'), DI::l10n()->t('Maximum age of items in the search table in days. Lower values will increase the performance and reduce disk usage. 0 means no age restriction.')],
'$compute_circle_counts' => ['compute_circle_counts', DI::l10n()->t('Generate counts per contact circle when calculating network count'), DI::config()->get('system', 'compute_circle_counts'), DI::l10n()->t('On systems with users that heavily use contact circles the query can be very expensive.')],
'$process_view' => ['process_view', DI::l10n()->t('Process "view" activities'), DI::config()->get('system', 'process_view'), DI::l10n()->t('"view" activities are mostly geberated by Peertube systems. Per default they are not processed for performance reasons. Only activate this option on performant system.')],

View file

@ -196,7 +196,7 @@ class Summary extends BaseAdmin
'$title' => DI::l10n()->t('Administration'),
'$page' => DI::l10n()->t('Summary'),
'$queues' => $queues,
'$version' => [DI::l10n()->t('Version'), App::VERSION],
'$version_label' => DI::l10n()->t('Version'),
'$platform' => App::PLATFORM,
'$codename' => App::CODENAME,
'$build' => DI::config()->get('system', 'build'),

View file

@ -30,6 +30,12 @@ use Friendica\Util\Strings;
class Details extends BaseAdmin
{
protected function post(array $request = [])
{
// @todo check if POST is really used here
$this->content($request);
}
protected function content(array $request = []): string
{
parent::content();

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