Merge branch 'stable' into develop

This commit is contained in:
Tobias Diekershoff 2024-08-17 17:29:18 +02:00
commit 95229140f8
194 changed files with 11224 additions and 9691 deletions

View file

@ -1,9 +1,86 @@
Version 2024.06 (unreleased) Version 2024.08 (2024-08-17)
Friendica Core 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 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 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) Version 2024.03 (2024-03-21)
Friendica Core Friendica Core
@ -292,7 +369,7 @@ Version 2023.04 (2023-04-23)
twitter twitter
Improve remote-self handling [annando] Improve remote-self handling [annando]
impressum impressum
Avoide obfuscation on un-set email addresses [MrPestovan] Avoide obfuscation on un-set email addresses [MrPetovan]
notifyall notifyall
Fixed a bug selecting the email addresses [nupplaphil] Fixed a bug selecting the email addresses [nupplaphil]
tumblr tumblr
@ -1741,7 +1818,7 @@ Version 2018.05 (2018-06-01)
Friendica Addons: Friendica Addons:
Updates to the translations (DE, EN_GB, EN_US, ES, FI, FR, IS, IT, NL, PL, RU, ZH_CN) [translation teams] 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] catavatar: new addon for profile pictures based on David Revoy's cat-avatar generator [annando, fabrixxm, tobiasd]
languagefilter: better help text [andyhee] languagefilter: better help text [andyhee]
mathjax: fixed the config form and adopted new CDN URL [tobiasd] mathjax: fixed the config form and adopted new CDN URL [tobiasd]

View file

@ -1 +1 @@
2024.06-dev 2024.08

View file

@ -72,7 +72,7 @@ echo "Extract strings to $OUTFILE.."
[ -f "$OUTFILE" ] && rm "$OUTFILE"; touch "$OUTFILE" [ -f "$OUTFILE" ] && rm "$OUTFILE"; touch "$OUTFILE"
# shellcheck disable=SC2086 # $FINDOPTS is meant to be split # 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}") total_files=$(wc -l <<< "${find_result}")
@ -86,7 +86,7 @@ do
if [ ! -d "$file" ] if [ ! -d "$file" ]
then then
# shellcheck disable=SC2086 # $KEYWORDS is meant to be split # 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" sed -i.bkp "s/CHARSET/UTF-8/g" "$OUTFILE"
fi fi
(( count++ )) (( count++ ))

View file

@ -1,6 +1,6 @@
-- ------------------------------------------ -- ------------------------------------------
-- Friendica 2024.06-dev (Yellow Archangel) -- Friendica 2024.08 (Yellow Archangel)
-- DB_UPDATE_VERSION 1565 -- DB_UPDATE_VERSION 1571
-- ------------------------------------------ -- ------------------------------------------
@ -11,7 +11,7 @@ CREATE TABLE IF NOT EXISTS `gserver` (
`id` int unsigned NOT NULL auto_increment COMMENT 'sequential ID', `id` int unsigned NOT NULL auto_increment COMMENT 'sequential ID',
`url` varbinary(383) NOT NULL DEFAULT '' COMMENT '', `url` varbinary(383) NOT NULL DEFAULT '' COMMENT '',
`nurl` 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 '', `site_name` varchar(255) NOT NULL DEFAULT '' COMMENT '',
`info` text COMMENT '', `info` text COMMENT '',
`register_policy` tinyint NOT NULL DEFAULT 0 COMMENT '', `register_policy` tinyint NOT NULL DEFAULT 0 COMMENT '',
@ -28,7 +28,9 @@ CREATE TABLE IF NOT EXISTS `gserver` (
`noscrape` varbinary(383) NOT NULL DEFAULT '' COMMENT '', `noscrape` varbinary(383) NOT NULL DEFAULT '' COMMENT '',
`network` char(4) NOT NULL DEFAULT '' COMMENT '', `network` char(4) NOT NULL DEFAULT '' COMMENT '',
`protocol` tinyint unsigned COMMENT 'The protocol of the server', `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-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', `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', `detection-method` tinyint unsigned COMMENT 'Method that had been used to detect that server',
@ -815,6 +817,7 @@ CREATE TABLE IF NOT EXISTS `inbox-entry` (
`activity-id` varbinary(383) COMMENT 'id of the incoming activity', `activity-id` varbinary(383) COMMENT 'id of the incoming activity',
`object-id` varbinary(383) COMMENT '', `object-id` varbinary(383) COMMENT '',
`in-reply-to-id` varbinary(383) COMMENT '', `in-reply-to-id` varbinary(383) COMMENT '',
`context` varbinary(383) COMMENT '',
`conversation` varbinary(383) COMMENT '', `conversation` varbinary(383) COMMENT '',
`type` varchar(64) COMMENT 'Type of the activity', `type` varchar(64) COMMENT 'Type of the activity',
`object-type` varchar(64) COMMENT 'Type of the object activity', `object-type` varchar(64) COMMENT 'Type of the object activity',
@ -825,6 +828,7 @@ CREATE TABLE IF NOT EXISTS `inbox-entry` (
`push` boolean COMMENT 'Is the entry pushed or have pulled it?', `push` boolean COMMENT 'Is the entry pushed or have pulled it?',
`trust` boolean COMMENT 'Do we trust this entry?', `trust` boolean COMMENT 'Do we trust this entry?',
`wid` int unsigned COMMENT 'Workerqueue id', `wid` int unsigned COMMENT 'Workerqueue id',
`retrial` tinyint unsigned DEFAULT 0 COMMENT 'Retrial counter',
PRIMARY KEY(`id`), PRIMARY KEY(`id`),
UNIQUE INDEX `activity-id` (`activity-id`), UNIQUE INDEX `activity-id` (`activity-id`),
INDEX `object-id` (`object-id`), INDEX `object-id` (`object-id`),
@ -1183,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', `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', `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', `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.', `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)', `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', `received` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT 'datetime',
@ -1201,6 +1206,7 @@ CREATE TABLE IF NOT EXISTS `post` (
INDEX `parent-uri-id` (`parent-uri-id`), INDEX `parent-uri-id` (`parent-uri-id`),
INDEX `thr-parent-id` (`thr-parent-id`), INDEX `thr-parent-id` (`thr-parent-id`),
INDEX `external-id` (`external-id`), INDEX `external-id` (`external-id`),
INDEX `replies-id` (`replies-id`),
INDEX `owner-id` (`owner-id`), INDEX `owner-id` (`owner-id`),
INDEX `author-id` (`author-id`), INDEX `author-id` (`author-id`),
INDEX `causer-id` (`causer-id`), INDEX `causer-id` (`causer-id`),
@ -1209,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 (`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 (`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 (`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 (`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 (`author-id`) REFERENCES `contact` (`id`) ON UPDATE RESTRICT ON DELETE RESTRICT,
FOREIGN KEY (`causer-id`) REFERENCES `contact` (`id`) ON UPDATE RESTRICT ON DELETE RESTRICT, FOREIGN KEY (`causer-id`) REFERENCES `contact` (`id`) ON UPDATE RESTRICT ON DELETE RESTRICT,
@ -1538,6 +1545,7 @@ CREATE TABLE IF NOT EXISTS `post-tag` (
-- --
CREATE TABLE IF NOT EXISTS `post-thread` ( 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', `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', `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', `owner-id` int unsigned NOT NULL DEFAULT 0 COMMENT 'Item owner',
`author-id` int unsigned NOT NULL DEFAULT 0 COMMENT 'Item author', `author-id` int unsigned NOT NULL DEFAULT 0 COMMENT 'Item author',
@ -1548,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', `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 '', `commented` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT '',
PRIMARY KEY(`uri-id`), PRIMARY KEY(`uri-id`),
INDEX `context-id` (`context-id`),
INDEX `conversation-id` (`conversation-id`), INDEX `conversation-id` (`conversation-id`),
INDEX `owner-id` (`owner-id`), INDEX `owner-id` (`owner-id`),
INDEX `author-id` (`author-id`), INDEX `author-id` (`author-id`),
@ -1555,6 +1564,7 @@ CREATE TABLE IF NOT EXISTS `post-thread` (
INDEX `received` (`received`), INDEX `received` (`received`),
INDEX `commented` (`commented`), INDEX `commented` (`commented`),
FOREIGN KEY (`uri-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE, 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 (`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 (`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 (`author-id`) REFERENCES `contact` (`id`) ON UPDATE RESTRICT ON DELETE RESTRICT,
@ -1570,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', `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', `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', `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.', `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)', `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', `received` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT 'datetime',
@ -1602,6 +1613,7 @@ CREATE TABLE IF NOT EXISTS `post-user` (
INDEX `parent-uri-id` (`parent-uri-id`), INDEX `parent-uri-id` (`parent-uri-id`),
INDEX `thr-parent-id` (`thr-parent-id`), INDEX `thr-parent-id` (`thr-parent-id`),
INDEX `external-id` (`external-id`), INDEX `external-id` (`external-id`),
INDEX `replies-id` (`replies-id`),
INDEX `owner-id` (`owner-id`), INDEX `owner-id` (`owner-id`),
INDEX `author-id` (`author-id`), INDEX `author-id` (`author-id`),
INDEX `causer-id` (`causer-id`), INDEX `causer-id` (`causer-id`),
@ -1622,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 (`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 (`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 (`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 (`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 (`author-id`) REFERENCES `contact` (`id`) ON UPDATE RESTRICT ON DELETE RESTRICT,
FOREIGN KEY (`causer-id`) REFERENCES `contact` (`id`) ON UPDATE RESTRICT ON DELETE RESTRICT, FOREIGN KEY (`causer-id`) REFERENCES `contact` (`id`) ON UPDATE RESTRICT ON DELETE RESTRICT,
@ -1637,6 +1650,7 @@ CREATE TABLE IF NOT EXISTS `post-user` (
-- --
CREATE TABLE IF NOT EXISTS `post-thread-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', `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', `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', `owner-id` int unsigned NOT NULL DEFAULT 0 COMMENT 'Item owner',
`author-id` int unsigned NOT NULL DEFAULT 0 COMMENT 'Item author', `author-id` int unsigned NOT NULL DEFAULT 0 COMMENT 'Item author',
@ -1662,6 +1676,7 @@ CREATE TABLE IF NOT EXISTS `post-thread-user` (
`post-user-id` int unsigned COMMENT 'Id of the post-user table', `post-user-id` int unsigned COMMENT 'Id of the post-user table',
PRIMARY KEY(`uid`,`uri-id`), PRIMARY KEY(`uid`,`uri-id`),
INDEX `uri-id` (`uri-id`), INDEX `uri-id` (`uri-id`),
INDEX `context-id` (`context-id`),
INDEX `conversation-id` (`conversation-id`), INDEX `conversation-id` (`conversation-id`),
INDEX `owner-id` (`owner-id`), INDEX `owner-id` (`owner-id`),
INDEX `author-id` (`author-id`), INDEX `author-id` (`author-id`),
@ -1684,6 +1699,7 @@ CREATE TABLE IF NOT EXISTS `post-thread-user` (
INDEX `contact-id_received` (`contact-id`,`received`), INDEX `contact-id_received` (`contact-id`,`received`),
INDEX `contact-id_created` (`contact-id`,`created`), INDEX `contact-id_created` (`contact-id`,`created`),
FOREIGN KEY (`uri-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE, 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 (`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 (`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 (`author-id`) REFERENCES `contact` (`id`) ON UPDATE RESTRICT ON DELETE RESTRICT,
@ -2271,6 +2287,8 @@ CREATE VIEW `post-origin-view` AS SELECT
`post-origin`.`thr-parent-id` AS `thr-parent-id`, `post-origin`.`thr-parent-id` AS `thr-parent-id`,
`conversation-item-uri`.`uri` AS `conversation`, `conversation-item-uri`.`uri` AS `conversation`,
`post-thread-user`.`conversation-id` AS `conversation-id`, `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`, `quote-item-uri`.`uri` AS `quote-uri`,
`post-content`.`quote-uri-id` AS `quote-uri-id`, `post-content`.`quote-uri-id` AS `quote-uri-id`,
`item-uri`.`guid` AS `guid`, `item-uri`.`guid` AS `guid`,
@ -2278,6 +2296,8 @@ CREATE VIEW `post-origin-view` AS SELECT
`post-origin`.`gravity` AS `gravity`, `post-origin`.`gravity` AS `gravity`,
`external-item-uri`.`uri` AS `extid`, `external-item-uri`.`uri` AS `extid`,
`post-user`.`external-id` AS `external-id`, `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-origin`.`created` AS `created`,
`post-user`.`edited` AS `edited`, `post-user`.`edited` AS `edited`,
`post-thread-user`.`commented` AS `commented`, `post-thread-user`.`commented` AS `commented`,
@ -2431,7 +2451,9 @@ CREATE VIEW `post-origin-view` AS SELECT
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 `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 `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 `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 `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 `verb` ON `verb`.`id` = `post-origin`.`vid`
LEFT JOIN `event` ON `event`.`id` = `post-user`.`event-id` 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 `diaspora-interaction` ON `diaspora-interaction`.`uri-id` = `post-origin`.`uri-id`
@ -2459,6 +2481,8 @@ CREATE VIEW `post-thread-origin-view` AS SELECT
`post-origin`.`thr-parent-id` AS `thr-parent-id`, `post-origin`.`thr-parent-id` AS `thr-parent-id`,
`conversation-item-uri`.`uri` AS `conversation`, `conversation-item-uri`.`uri` AS `conversation`,
`post-thread-user`.`conversation-id` AS `conversation-id`, `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`, `quote-item-uri`.`uri` AS `quote-uri`,
`post-content`.`quote-uri-id` AS `quote-uri-id`, `post-content`.`quote-uri-id` AS `quote-uri-id`,
`item-uri`.`guid` AS `guid`, `item-uri`.`guid` AS `guid`,
@ -2466,6 +2490,8 @@ CREATE VIEW `post-thread-origin-view` AS SELECT
`post-origin`.`gravity` AS `gravity`, `post-origin`.`gravity` AS `gravity`,
`external-item-uri`.`uri` AS `extid`, `external-item-uri`.`uri` AS `extid`,
`post-user`.`external-id` AS `external-id`, `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-origin`.`created` AS `created`,
`post-user`.`edited` AS `edited`, `post-user`.`edited` AS `edited`,
`post-thread-user`.`commented` AS `commented`, `post-thread-user`.`commented` AS `commented`,
@ -2618,7 +2644,9 @@ CREATE VIEW `post-thread-origin-view` AS SELECT
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 `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 `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 `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 `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 `verb` ON `verb`.`id` = `post-origin`.`vid`
LEFT JOIN `event` ON `event`.`id` = `post-user`.`event-id` 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 `diaspora-interaction` ON `diaspora-interaction`.`uri-id` = `post-origin`.`uri-id`
@ -2645,6 +2673,8 @@ CREATE VIEW `post-user-view` AS SELECT
`post-user`.`thr-parent-id` AS `thr-parent-id`, `post-user`.`thr-parent-id` AS `thr-parent-id`,
`conversation-item-uri`.`uri` AS `conversation`, `conversation-item-uri`.`uri` AS `conversation`,
`post-thread-user`.`conversation-id` AS `conversation-id`, `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`, `quote-item-uri`.`uri` AS `quote-uri`,
`post-content`.`quote-uri-id` AS `quote-uri-id`, `post-content`.`quote-uri-id` AS `quote-uri-id`,
`item-uri`.`guid` AS `guid`, `item-uri`.`guid` AS `guid`,
@ -2652,6 +2682,8 @@ CREATE VIEW `post-user-view` AS SELECT
`post-user`.`gravity` AS `gravity`, `post-user`.`gravity` AS `gravity`,
`external-item-uri`.`uri` AS `extid`, `external-item-uri`.`uri` AS `extid`,
`post-user`.`external-id` AS `external-id`, `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`.`created` AS `created`,
`post-user`.`edited` AS `edited`, `post-user`.`edited` AS `edited`,
`post-thread-user`.`commented` AS `commented`, `post-thread-user`.`commented` AS `commented`,
@ -2804,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 `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 `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 `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 `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 `verb` ON `verb`.`id` = `post-user`.`vid`
LEFT JOIN `event` ON `event`.`id` = `post-user`.`event-id` LEFT JOIN `event` ON `event`.`id` = `post-user`.`event-id`
LEFT JOIN `diaspora-interaction` ON `diaspora-interaction`.`uri-id` = `post-user`.`uri-id` LEFT JOIN `diaspora-interaction` ON `diaspora-interaction`.`uri-id` = `post-user`.`uri-id`
@ -2832,6 +2866,8 @@ CREATE VIEW `post-thread-user-view` AS SELECT
`post-user`.`thr-parent-id` AS `thr-parent-id`, `post-user`.`thr-parent-id` AS `thr-parent-id`,
`conversation-item-uri`.`uri` AS `conversation`, `conversation-item-uri`.`uri` AS `conversation`,
`post-thread-user`.`conversation-id` AS `conversation-id`, `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`, `quote-item-uri`.`uri` AS `quote-uri`,
`post-content`.`quote-uri-id` AS `quote-uri-id`, `post-content`.`quote-uri-id` AS `quote-uri-id`,
`item-uri`.`guid` AS `guid`, `item-uri`.`guid` AS `guid`,
@ -2839,6 +2875,8 @@ CREATE VIEW `post-thread-user-view` AS SELECT
`post-user`.`gravity` AS `gravity`, `post-user`.`gravity` AS `gravity`,
`external-item-uri`.`uri` AS `extid`, `external-item-uri`.`uri` AS `extid`,
`post-user`.`external-id` AS `external-id`, `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-thread-user`.`created` AS `created`,
`post-user`.`edited` AS `edited`, `post-user`.`edited` AS `edited`,
`post-thread-user`.`commented` AS `commented`, `post-thread-user`.`commented` AS `commented`,
@ -2990,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 `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 `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 `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 `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 `verb` ON `verb`.`id` = `post-user`.`vid`
LEFT JOIN `event` ON `event`.`id` = `post-user`.`event-id` LEFT JOIN `event` ON `event`.`id` = `post-user`.`event-id`
LEFT JOIN `diaspora-interaction` ON `diaspora-interaction`.`uri-id` = `post-thread-user`.`uri-id` LEFT JOIN `diaspora-interaction` ON `diaspora-interaction`.`uri-id` = `post-thread-user`.`uri-id`
@ -3013,12 +3053,16 @@ CREATE VIEW `post-view` AS SELECT
`post`.`thr-parent-id` AS `thr-parent-id`, `post`.`thr-parent-id` AS `thr-parent-id`,
`conversation-item-uri`.`uri` AS `conversation`, `conversation-item-uri`.`uri` AS `conversation`,
`post-thread`.`conversation-id` AS `conversation-id`, `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`, `quote-item-uri`.`uri` AS `quote-uri`,
`post-content`.`quote-uri-id` AS `quote-uri-id`, `post-content`.`quote-uri-id` AS `quote-uri-id`,
`item-uri`.`guid` AS `guid`, `item-uri`.`guid` AS `guid`,
`post`.`gravity` AS `gravity`, `post`.`gravity` AS `gravity`,
`external-item-uri`.`uri` AS `extid`, `external-item-uri`.`uri` AS `extid`,
`post`.`external-id` AS `external-id`, `post`.`external-id` AS `external-id`,
`replies-item-uri`.`uri` AS `replies`,
`post`.`replies-id` AS `replies-id`,
`post`.`created` AS `created`, `post`.`created` AS `created`,
`post`.`edited` AS `edited`, `post`.`edited` AS `edited`,
`post-thread`.`commented` AS `commented`, `post-thread`.`commented` AS `commented`,
@ -3139,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 `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 `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 `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 `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 `verb` ON `verb`.`id` = `post`.`vid`
LEFT JOIN `diaspora-interaction` ON `diaspora-interaction`.`uri-id` = `post`.`uri-id` LEFT JOIN `diaspora-interaction` ON `diaspora-interaction`.`uri-id` = `post`.`uri-id`
LEFT JOIN `post-content` ON `post-content`.`uri-id` = `post`.`uri-id` LEFT JOIN `post-content` ON `post-content`.`uri-id` = `post`.`uri-id`
@ -3160,12 +3206,16 @@ CREATE VIEW `post-thread-view` AS SELECT
`post`.`thr-parent-id` AS `thr-parent-id`, `post`.`thr-parent-id` AS `thr-parent-id`,
`conversation-item-uri`.`uri` AS `conversation`, `conversation-item-uri`.`uri` AS `conversation`,
`post-thread`.`conversation-id` AS `conversation-id`, `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`, `quote-item-uri`.`uri` AS `quote-uri`,
`post-content`.`quote-uri-id` AS `quote-uri-id`, `post-content`.`quote-uri-id` AS `quote-uri-id`,
`item-uri`.`guid` AS `guid`, `item-uri`.`guid` AS `guid`,
`post`.`gravity` AS `gravity`, `post`.`gravity` AS `gravity`,
`external-item-uri`.`uri` AS `extid`, `external-item-uri`.`uri` AS `extid`,
`post`.`external-id` AS `external-id`, `post`.`external-id` AS `external-id`,
`replies-item-uri`.`uri` AS `replies`,
`post`.`replies-id` AS `replies-id`,
`post-thread`.`created` AS `created`, `post-thread`.`created` AS `created`,
`post`.`edited` AS `edited`, `post`.`edited` AS `edited`,
`post-thread`.`commented` AS `commented`, `post-thread`.`commented` AS `commented`,
@ -3288,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 `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 `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 `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 `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 `verb` ON `verb`.`id` = `post`.`vid`
LEFT JOIN `diaspora-interaction` ON `diaspora-interaction`.`uri-id` = `post-thread`.`uri-id` LEFT JOIN `diaspora-interaction` ON `diaspora-interaction`.`uri-id` = `post-thread`.`uri-id`
LEFT JOIN `post-content` ON `post-content`.`uri-id` = `post-thread`.`uri-id` LEFT JOIN `post-content` ON `post-content`.`uri-id` = `post-thread`.`uri-id`

View file

@ -78,4 +78,5 @@ Help
**About** **About**
* [Server Information](friendica) * [Server Information](friendica)
* [Terms of Service](tos)
* [Credits](credits) * [Credits](credits)

View file

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

View file

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

View file

@ -9,6 +9,7 @@ Fields
| Field | Description | Type | Null | Key | Default | Extra | | 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 | | | 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 | | | 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 | | | owner-id | Item owner | int unsigned | NO | | 0 | |
| author-id | Item author | int unsigned | NO | | 0 | | | author-id | Item author | int unsigned | NO | | 0 | |
@ -40,6 +41,7 @@ Indexes
| -------------------- | --------------------- | | -------------------- | --------------------- |
| PRIMARY | uid, uri-id | | PRIMARY | uid, uri-id |
| uri-id | uri-id | | uri-id | uri-id |
| context-id | context-id |
| conversation-id | conversation-id | | conversation-id | conversation-id |
| owner-id | owner-id | | owner-id | owner-id |
| author-id | author-id | | author-id | author-id |
@ -68,6 +70,7 @@ Foreign Keys
| Field | Target Table | Target Field | | Field | Target Table | Target Field |
|-------|--------------|--------------| |-------|--------------|--------------|
| uri-id | [item-uri](help/database/db_item-uri) | id | | 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 | | conversation-id | [item-uri](help/database/db_item-uri) | id |
| owner-id | [contact](help/database/db_contact) | id | | owner-id | [contact](help/database/db_contact) | id |
| author-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 | | 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 | | | 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 | | | 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 | | | owner-id | Item owner | int unsigned | NO | | 0 | |
| author-id | Item author | int unsigned | NO | | 0 | | | author-id | Item author | int unsigned | NO | | 0 | |
@ -25,6 +26,7 @@ Indexes
| Name | Fields | | Name | Fields |
| --------------- | --------------- | | --------------- | --------------- |
| PRIMARY | uri-id | | PRIMARY | uri-id |
| context-id | context-id |
| conversation-id | conversation-id | | conversation-id | conversation-id |
| owner-id | owner-id | | owner-id | owner-id |
| author-id | author-id | | author-id | author-id |
@ -38,6 +40,7 @@ Foreign Keys
| Field | Target Table | Target Field | | Field | Target Table | Target Field |
|-------|--------------|--------------| |-------|--------------|--------------|
| uri-id | [item-uri](help/database/db_item-uri) | id | | 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 | | conversation-id | [item-uri](help/database/db_item-uri) | id |
| owner-id | [contact](help/database/db_contact) | id | | owner-id | [contact](help/database/db_contact) | id |
| author-id | [contact](help/database/db_contact) | id | | author-id | [contact](help/database/db_contact) | id |

View file

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

View file

@ -72,4 +72,5 @@ Hilfe
**Über** **Über**
* [Server Information](friendica) * [Server Information](friendica)
* [Nutzungsbedingungen](tos)
* [Mitwirkende](credits) * [Mitwirkende](credits)

View file

@ -78,3 +78,9 @@ The following will compress */var/log/friendica* (assuming this is the location
daily daily
rotate 2 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.

View file

@ -1,6 +1,6 @@
Contact: mailto:info@friendi.ca Contact: mailto:info@friendi.ca
Expires: 2025-01-30T23:59:59Z Expires: 2025-06-30T23:59:59Z
Preferred-Languages: en Preferred-Languages: en

View file

@ -24,6 +24,7 @@ namespace Friendica;
use Exception; use Exception;
use Friendica\App\Arguments; use Friendica\App\Arguments;
use Friendica\App\BaseURL; use Friendica\App\BaseURL;
use Friendica\App\Request;
use Friendica\Capabilities\ICanCreateResponses; use Friendica\Capabilities\ICanCreateResponses;
use Friendica\Content\Nav; use Friendica\Content\Nav;
use Friendica\Core\Config\Factory\Config; use Friendica\Core\Config\Factory\Config;
@ -64,7 +65,7 @@ class App
{ {
const PLATFORM = 'Friendica'; const PLATFORM = 'Friendica';
const CODENAME = 'Yellow Archangel'; const CODENAME = 'Yellow Archangel';
const VERSION = '2024.06-dev'; const VERSION = '2024.08';
// Allow themes to control internal parameters // Allow themes to control internal parameters
// by changing App values in theme.php // by changing App values in theme.php
@ -93,6 +94,9 @@ class App
/** @var string The name of the current mobile theme */ /** @var string The name of the current mobile theme */
private $currentMobileTheme; private $currentMobileTheme;
/** @var string */
private $requestId;
/** @var Authentication */ /** @var Authentication */
private $auth; private $auth;
@ -281,8 +285,9 @@ class App
* @param DbaDefinition $dbaDefinition * @param DbaDefinition $dbaDefinition
* @param ViewDefinition $viewDefinition * @param ViewDefinition $viewDefinition
*/ */
public function __construct(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) 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->auth = $auth;
$this->database = $database; $this->database = $database;
$this->config = $config; $this->config = $config;
@ -683,9 +688,11 @@ class App
} }
$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); System::echoResponse($response);
} catch (HTTPException $e) { } 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); $httpException->rawContent($e);
} }
$page->logRuntime($this->config, 'runFrontend'); $page->logRuntime($this->config, 'runFrontend');
@ -707,4 +714,30 @@ class App
$this->baseURL->redirect($toUrl); $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

@ -224,17 +224,17 @@ abstract class BaseModule implements ICanHandleRequests
switch ($this->args->getMethod()) { switch ($this->args->getMethod()) {
case Router::DELETE: case Router::DELETE:
$this->delete($request); $this->delete($request);
break; return $this->response->generate();
case Router::PATCH: case Router::PATCH:
$this->patch($request); $this->patch($request);
break; return $this->response->generate();
case Router::POST: case Router::POST:
Core\Hook::callAll($this->args->getModuleName() . '_mod_post', $request); Core\Hook::callAll($this->args->getModuleName() . '_mod_post', $request);
$this->post($request); $this->post($request);
break; return $this->response->generate();
case Router::PUT: case Router::PUT:
$this->put($request); $this->put($request);
break; return $this->response->generate();
} }
$timestamp = microtime(true); $timestamp = microtime(true);

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\Repository\StorageManager;
use Friendica\Core\Storage\Exception\ReferenceStorageException; use Friendica\Core\Storage\Exception\ReferenceStorageException;
use Friendica\Core\Storage\Exception\StorageException; 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 * tool to manage storage backend and stored data from CLI
@ -57,6 +60,9 @@ Synopsis
bin/console storage list bin/console storage list
List available storage backends List available storage backends
bin/console storage clear
Remove the contact avatar cache data
bin/console storage set <name> bin/console storage set <name>
Set current storage backend Set current storage backend
name storage backend to use. see "list". name storage backend to use. see "list".
@ -87,6 +93,9 @@ HELP;
case 'list': case 'list':
return $this->doList(); return $this->doList();
break; break;
case 'clear':
return $this->clear();
break;
case 'set': case 'set':
return $this->doSet(); return $this->doSet();
break; break;
@ -126,6 +135,22 @@ HELP;
return 0; 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() protected function doSet()
{ {
if (count($this->args) !== 2 || empty($this->args[1])) { if (count($this->args) !== 2 || empty($this->args[1])) {

View file

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

View file

@ -66,7 +66,7 @@ class GroupManager
'archive' => false, 'archive' => false,
]; ];
$condition = DBA::mergeConditions($condition, ["`platform` != ?", 'peertube']); $condition = DBA::mergeConditions($condition, ["`platform` NOT IN (?, ?)", 'peertube', 'wordpress']);
if (!$showprivate) { if (!$showprivate) {
$condition = DBA::mergeConditions($condition, ['manually-approve' => false]); $condition = DBA::mergeConditions($condition, ['manually-approve' => false]);

View file

@ -547,9 +547,9 @@ class Item
$item['private'] = $private_group ? ItemModel::PRIVATE : ItemModel::UNLISTED; $item['private'] = $private_group ? ItemModel::PRIVATE : ItemModel::UNLISTED;
if ($only_to_group) { if ($only_to_group) {
$cdata = Contact::getPublicAndUserContactID($group_contact['id'], $item['uid']); $pcid = Contact::getPublicContactId($group_contact['id'], $item['uid']);
if (!empty($cdata['user'])) { if ($pcid) {
$item['owner-id'] = $cdata['user']; $item['owner-id'] = $pcid;
unset($item['owner-link']); unset($item['owner-link']);
unset($item['owner-name']); unset($item['owner-name']);
unset($item['owner-avatar']); unset($item['owner-avatar']);
@ -1032,7 +1032,7 @@ class Item
} }
$this->emailer->send(new ItemCCEMail( $this->emailer->send(new ItemCCEMail(
$this->app, $this->userSession,
$this->l10n, $this->l10n,
$this->baseURL, $this->baseURL,
$post, $post,

View file

@ -1364,7 +1364,7 @@ class BBCode
// At a later stage we won't be able to exclude certain parts of the code. // At a later stage we won't be able to exclude certain parts of the code.
$text = self::performWithEscapedTags($text, ['url', 'img', 'audio', 'video', 'youtube', 'vimeo', 'share', 'attachment', 'iframe', 'bookmark', 'map', 'oembed'], function ($text) use ($simple_html, $for_plaintext) { $text = self::performWithEscapedTags($text, ['url', 'img', 'audio', 'video', 'youtube', 'vimeo', 'share', 'attachment', 'iframe', 'bookmark', 'map', 'oembed'], function ($text) use ($simple_html, $for_plaintext) {
if (!$for_plaintext) { if (!$for_plaintext) {
$text = preg_replace(Strings::autoLinkRegEx(), '[url]$1[/url]', $text); $text = preg_replace(Strings::autoLinkRegEx(), '[url]$1[/url]', $text) ?? '';
} }
return self::convertSmileysToHtml($text, $simple_html, $for_plaintext); return self::convertSmileysToHtml($text, $simple_html, $for_plaintext);
}); });
@ -2090,7 +2090,7 @@ class BBCode
$text = preg_replace("/\[zrl\=(.*?)\](.*?)\[\/zrl\]/ism", '[url=$1]$2[/url]', $text); $text = preg_replace("/\[zrl\=(.*?)\](.*?)\[\/zrl\]/ism", '[url=$1]$2[/url]', $text);
if (in_array($simple_html, [self::INTERNAL, self::EXTERNAL, self::DIASPORA, self::OSTATUS, self::MASTODON_API, self::TWITTER_API, self::ACTIVITYPUB])) { if (in_array($simple_html, [self::INTERNAL, self::EXTERNAL, self::DIASPORA, self::OSTATUS, self::MASTODON_API, self::TWITTER_API, self::ACTIVITYPUB])) {
$text = self::shortenLinkDescription($text); $text = self::shortenLinkDescription($text, $simple_html);
} else { } else {
$text = self::unifyLinks($text); $text = self::unifyLinks($text);
} }
@ -2124,7 +2124,12 @@ class BBCode
} }
$parts['host'] = idn_to_ascii(urldecode($parts['host'])); $parts['host'] = idn_to_ascii(urldecode($parts['host']));
return (string)Uri::fromParts($parts); try {
return (string)Uri::fromParts($parts);
} catch (\Throwable $th) {
Logger::notice('Exception on unparsing url', ['url' => $url, 'parts' => $parts, 'code' => $th->getCode(), 'message' => $th->getMessage()]);
return $url;
}
} }
private static function unifyLinks(string $text): string private static function unifyLinks(string $text): string
@ -2138,20 +2143,26 @@ class BBCode
); );
} }
private static function shortenLinkDescription(string $text): string private static function shortenLinkDescription(string $text, int $simple_html): string
{ {
if ($simple_html == self::INTERNAL) {
$max_length = DI::config()->get('system', 'display_link_length');
} else {
$max_length = 30;
}
$text = preg_replace_callback( $text = preg_replace_callback(
"/\[url\](.*?)\[\/url\]/ism", "/\[url\](.*?)\[\/url\]/ism",
function ($match) { function ($match) use ($max_length) {
return "[url=" . self::escapeUrl($match[1]) . "]" . Strings::getStyledURL($match[1]) . "[/url]"; return "[url=" . self::escapeUrl($match[1]) . "]" . Strings::getStyledURL($match[1], $max_length) . "[/url]";
}, },
$text $text
); );
$text = preg_replace_callback( $text = preg_replace_callback(
"/\[url\=(.*?)\](.*?)\[\/url\]/ism", "/\[url\=(.*?)\](.*?)\[\/url\]/ism",
function ($match) { function ($match) use ($max_length) {
if ($match[1] == $match[2]) { if ($match[1] == $match[2]) {
return "[url=" . self::escapeUrl($match[1]) . "]" . Strings::getStyledURL($match[2]) . "[/url]"; return "[url=" . self::escapeUrl($match[1]) . "]" . Strings::getStyledURL($match[2], $max_length) . "[/url]";
} else { } else {
return "[url=" . self::escapeUrl($match[1]) . "]" . $match[2] . "[/url]"; return "[url=" . self::escapeUrl($match[1]) . "]" . $match[2] . "[/url]";
} }
@ -2641,7 +2652,7 @@ class BBCode
$bbcode = "\n" . '[audio]' . $url . '[/audio]' . "\n"; $bbcode = "\n" . '[audio]' . $url . '[/audio]' . "\n";
break; break;
default: default:
$bbcode = "\n" . '[img]' . $url . '[/img]' . "\n"; $bbcode = "\n" . '[img=' . $url . '][/img]' . "\n";
break; break;
} }

View file

@ -868,7 +868,7 @@ class HTML
'$save_label' => $save_label, '$save_label' => $save_label,
'$search_hint' => DI::l10n()->t('@name, !group, #tags, content'), '$search_hint' => DI::l10n()->t('@name, !group, #tags, content'),
'$mode' => $mode, '$mode' => $mode,
'$return_url' => urlencode(Search::getSearchPath($s)), '$return_url' => bin2hex(Search::getSearchPath($s)),
]; ];
if (!$aside) { if (!$aside) {

View file

@ -398,7 +398,7 @@ class Widget
$entries[] = [ $entries[] = [
'url' => Contact::magicLinkByContact($contact), 'url' => Contact::magicLinkByContact($contact),
'name' => $contact['name'], 'name' => $contact['name'],
'photo' => Contact::getThumb($contact), 'photo' => Contact::getThumb($contact, true),
]; ];
} }

View file

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

View file

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

View file

@ -235,7 +235,7 @@ class Worker
* @return integer Number of deferred entries in the worker queue * @return integer Number of deferred entries in the worker queue
* @throws \Exception * @throws \Exception
*/ */
private static function deferredEntries(): int public static function deferredEntries(): int
{ {
$stamp = (float)microtime(true); $stamp = (float)microtime(true);
$count = DBA::count('workerqueue', ["NOT `done` AND `pid` = 0 AND `retrial` > ?", 0]); $count = DBA::count('workerqueue', ["NOT `done` AND `pid` = 0 AND `retrial` > ?", 0]);
@ -250,7 +250,7 @@ class Worker
* @return integer Number of non executed entries in the worker queue * @return integer Number of non executed entries in the worker queue
* @throws \Exception * @throws \Exception
*/ */
private static function totalEntries(): int public static function totalEntries(): int
{ {
$stamp = (float)microtime(true); $stamp = (float)microtime(true);
$count = DBA::count('workerqueue', ['done' => false, 'pid' => 0]); $count = DBA::count('workerqueue', ['done' => false, 'pid' => 0]);
@ -862,7 +862,7 @@ class Worker
* @return integer Number of active worker processes * @return integer Number of active worker processes
* @throws \Exception * @throws \Exception
*/ */
private static function activeWorkers(): int public static function activeWorkers(): int
{ {
$stamp = (float)microtime(true); $stamp = (float)microtime(true);
$count = DI::process()->countCommand('Worker.php'); $count = DI::process()->countCommand('Worker.php');

View file

@ -66,7 +66,7 @@ abstract class DI
public static function setCompositeRootDependencyByHand() public static function setCompositeRootDependencyByHand()
{ {
$database = static::dba(); $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; namespace Friendica\Database;
use Friendica\Core\Lock\Exception\LockPersistenceException;
use Friendica\DI; use Friendica\DI;
use mysqli; use mysqli;
use mysqli_result; use mysqli_result;
@ -823,6 +824,27 @@ class DBA
return DI::dba()->optimizeTable($table); 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 * Kill sleeping database processes
*/ */

View file

@ -22,6 +22,8 @@
namespace Friendica\Database; namespace Friendica\Database;
use Friendica\Core\Config\Capability\IManageConfigValues; use Friendica\Core\Config\Capability\IManageConfigValues;
use Friendica\Core\Lock\Capability\ICanLock;
use Friendica\Core\Lock\Exception\LockPersistenceException;
use Friendica\Core\System; use Friendica\Core\System;
use Friendica\Database\Definition\DbaDefinition; use Friendica\Database\Definition\DbaDefinition;
use Friendica\Database\Definition\ViewDefinition; use Friendica\Database\Definition\ViewDefinition;
@ -50,6 +52,8 @@ class Database
const INSERT_UPDATE = 1; const INSERT_UPDATE = 1;
const INSERT_IGNORE = 2; const INSERT_IGNORE = 2;
const LOCK_OPTIMIZE = 'database::optimize_tables';
protected $connected = false; protected $connected = false;
/** /**
@ -64,6 +68,11 @@ class Database
* @var LoggerInterface * @var LoggerInterface
*/ */
protected $logger = null; protected $logger = null;
/**
* @var ICanLock
*/
protected $syslock = null;
protected $server_info = ''; protected $server_info = '';
/** @var PDO|mysqli */ /** @var PDO|mysqli */
protected $connection; protected $connection;
@ -106,11 +115,12 @@ class Database
* *
* @todo Make this method obsolete - use a clean pattern instead ... * @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->logger = $logger;
$this->profiler = $profiler; $this->profiler = $profiler;
$this->config = $config; $this->config = $config;
$this->syslock = $lock;
} }
/** /**
@ -1755,7 +1765,42 @@ class Database
*/ */
public function optimizeTable(string $table): bool 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

@ -23,6 +23,7 @@ namespace Friendica\Factory\Api\Mastodon;
use Friendica\App\BaseURL; use Friendica\App\BaseURL;
use Friendica\BaseFactory; use Friendica\BaseFactory;
use Friendica\Model\Attach;
use Friendica\Model\Photo; use Friendica\Model\Photo;
use Friendica\Network\HTTPException; use Friendica\Network\HTTPException;
use Friendica\Model\Post; use Friendica\Model\Post;
@ -144,4 +145,37 @@ class Attachment extends BaseFactory
$object = new \Friendica\Object\Api\Mastodon\Attachment($attachment, 'image', $url, $preview_url, ''); $object = new \Friendica\Object\Api\Mastodon\Attachment($attachment, 'image', $url, $preview_url, '');
return $object->toArray(); 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

@ -27,6 +27,7 @@ use Friendica\Content\Item as ContentItem;
use Friendica\Content\Smilies; use Friendica\Content\Smilies;
use Friendica\Content\Text\BBCode; use Friendica\Content\Text\BBCode;
use Friendica\Core\Logger; use Friendica\Core\Logger;
use Friendica\Core\Protocol;
use Friendica\Database\Database; use Friendica\Database\Database;
use Friendica\Database\DBA; use Friendica\Database\DBA;
use Friendica\DI; 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 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', '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']; '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]]); $item = Post::selectFirst($fields, ['uri-id' => $uriId, 'uid' => [0, $uid]], ['order' => ['uid' => true]]);
@ -212,8 +213,27 @@ class Status extends BaseFactory
$item['featured'] $item['featured']
); );
$sensitive = (bool)$item['sensitive']; $sensitive = (bool)$item['sensitive'];
$application = new \Friendica\Object\Api\Mastodon\Application($item['app'] ?: ContactSelector::networkToName($item['network'], $item['author-link']));
$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(); $mentions = $this->mstdnMentionFactory->createFromUriId($uriId)->getArrayCopy();
$tags = $this->mstdnTagFactory->createFromUriId($uriId); $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']); $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'])); $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); 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 = []; $attachments = [];
$in_reply = []; $in_reply = [];
$reshare = []; $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); return new \Friendica\Object\Api\Mastodon\Status($item, $account, $counts, $userAttributes, $sensitive, $application, $mentions, $tags, $card, $attachments, $in_reply, $reshare, $friendica);
} }

View file

@ -287,6 +287,7 @@ class APContact
} elseif ($apcontact['type'] == 'Tombstone') { } elseif ($apcontact['type'] == 'Tombstone') {
// The "inbox" field must have a content // The "inbox" field must have a content
$apcontact['inbox'] = ''; $apcontact['inbox'] = '';
$apcontact['addr'] = '';
} }
// Quit if this doesn't seem to be an account at all // Quit if this doesn't seem to be an account at all
@ -294,7 +295,7 @@ class APContact
return $fetched_contact; return $fetched_contact;
} }
if (empty($apcontact['addr'])) { if (empty($apcontact['addr']) && ($apcontact['type'] != 'Tombstone')) {
try { try {
$apcontact['addr'] = $apcontact['nick'] . '@' . (new Uri($apcontact['url']))->getAuthority(); $apcontact['addr'] = $apcontact['nick'] . '@' . (new Uri($apcontact['url']))->getAuthority();
} catch (\Throwable $e) { } catch (\Throwable $e) {

View file

@ -245,6 +245,7 @@ class Attach
* @param string $src Source file name * @param string $src Source file name
* @param int $uid User id * @param int $uid User id
* @param string $filename Optional file name * @param string $filename Optional file name
* @param string $filetype Optional file type
* @param string $allow_cid * @param string $allow_cid
* @param string $allow_gid * @param string $allow_gid
* @param string $deny_cid * @param string $deny_cid
@ -252,7 +253,7 @@ class Attach
* @return boolean|int Insert id or false on failure * @return boolean|int Insert id or false on failure
* @throws \Friendica\Network\HTTPException\InternalServerErrorException * @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 === '') { if ($filename === '') {
$filename = basename($src); $filename = basename($src);
@ -260,7 +261,7 @@ class Attach
$data = @file_get_contents($src); $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);
} }
@ -345,6 +346,16 @@ class Attach
} }
} }
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 public static function addAttachmentToBody(string $body, int $uid): string
{ {
preg_match_all("/\[attachment\](.*?)\[\/attachment\]/ism", $body, $matches, PREG_SET_ORDER); preg_match_all("/\[attachment\](.*?)\[\/attachment\]/ism", $body, $matches, PREG_SET_ORDER);

View file

@ -290,12 +290,12 @@ class Circle
throw new HTTPException\NotFoundException('Circle not found.'); throw new HTTPException\NotFoundException('Circle not found.');
} }
$cdata = Contact::getPublicAndUserContactID($cid, $circle['uid']); $ucid = Contact::getUserContactId($cid, $circle['uid']);
if (empty($cdata['user'])) { if (!$ucid) {
throw new HTTPException\NotFoundException('Invalid contact.'); 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.'); throw new HTTPException\NotFoundException('Circle not found.');
} }
$cdata = Contact::getPublicAndUserContactID($cid, $circle['uid']); $ucid = Contact::getUserContactId($cid, $circle['uid']);
if (empty($cdata['user'])) { if (!$ucid) {
throw new HTTPException\NotFoundException('Invalid contact.'); 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) { foreach ($contacts as $cid) {
$cdata = Contact::getPublicAndUserContactID($cid, $circle['uid']); $ucid = Contact::getUserContactId($cid, $circle['uid']);
if (empty($cdata['user'])) { if (!$ucid) {
throw new HTTPException\NotFoundException('Invalid contact.'); 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 = []; $contactIds = [];
foreach ($contacts as $cid) { foreach ($contacts as $cid) {
$cdata = Contact::getPublicAndUserContactID($cid, $circle['uid']); $ucid = Contact::getUserContactId($cid, $circle['uid']);
if (empty($cdata['user'])) { if (!$ucid) {
throw new HTTPException\NotFoundException('Invalid contact.'); throw new HTTPException\NotFoundException('Invalid contact.');
} }
$contactIds[] = $cdata['user']; $contactIds[] = $ucid;
} }
// Return status of deletion // Return status of deletion

View file

@ -444,12 +444,12 @@ class Contact
return false; return false;
} }
$cdata = self::getPublicAndUserContactID($cid, $uid); $ucid = self::getUserContactId($cid, $uid);
if (empty($cdata['user'])) { if (!$ucid) {
return false; return false;
} }
$condition = ['id' => $cdata['user'], 'rel' => [self::FOLLOWER, self::FRIEND]]; $condition = ['id' => $ucid, 'rel' => [self::FOLLOWER, self::FRIEND]];
if ($strict) { if ($strict) {
$condition = array_merge($condition, ['pending' => false, 'readonly' => false, 'blocked' => false]); $condition = array_merge($condition, ['pending' => false, 'readonly' => false, 'blocked' => false]);
} }
@ -495,12 +495,12 @@ class Contact
return false; return false;
} }
$cdata = self::getPublicAndUserContactID($cid, $uid); $ucid = self::getUserContactId($cid, $uid);
if (empty($cdata['user'])) { if (!$ucid) {
return false; return false;
} }
$condition = ['id' => $cdata['user'], 'rel' => [self::SHARING, self::FRIEND]]; $condition = ['id' => $ucid, 'rel' => [self::SHARING, self::FRIEND]];
if ($strict) { if ($strict) {
$condition = array_merge($condition, ['pending' => false, 'readonly' => false, 'blocked' => false]); $condition = array_merge($condition, ['pending' => false, 'readonly' => false, 'blocked' => false]);
} }
@ -671,6 +671,32 @@ class Contact
return ['public' => $pcid, 'user' => $ucid]; 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" * Helper function for "getPublicAndUserContactID"
* *
@ -968,13 +994,13 @@ class Contact
} }
if (in_array($contact['rel'], [self::SHARING, self::FRIEND])) { if (in_array($contact['rel'], [self::SHARING, self::FRIEND])) {
$cdata = self::getPublicAndUserContactID($contact['id'], $contact['uid']); $pcid = self::getPublicContactId($contact['id'], $contact['uid']);
if (!empty($cdata['public'])) { if ($pcid) {
Worker::add(Worker::PRIORITY_HIGH, 'Contact\Unfollow', $cdata['public'], $contact['uid']); Worker::add(Worker::PRIORITY_HIGH, 'Contact\Unfollow', $pcid, $contact['uid']);
} }
} }
self::removeSharer($contact); self::removeSharer($contact, false);
} }
/** /**
@ -998,13 +1024,13 @@ class Contact
} }
if (in_array($contact['rel'], [self::FOLLOWER, self::FRIEND])) { if (in_array($contact['rel'], [self::FOLLOWER, self::FRIEND])) {
$cdata = self::getPublicAndUserContactID($contact['id'], $contact['uid']); $pcid = self::getPublicContactId($contact['id'], $contact['uid']);
if (!empty($cdata['public'])) { if ($pcid) {
Worker::add(Worker::PRIORITY_HIGH, 'Contact\RevokeFollow', $cdata['public'], $contact['uid']); Worker::add(Worker::PRIORITY_HIGH, 'Contact\RevokeFollow', $pcid, $contact['uid']);
} }
} }
self::removeFollower($contact); self::removeFollower($contact, false);
} }
/** /**
@ -1025,14 +1051,14 @@ class Contact
throw new \InvalidArgumentException('Unexpected public contact record'); 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'])) { if (in_array($contact['rel'], [self::SHARING, self::FRIEND]) && $pcid) {
Worker::add(Worker::PRIORITY_HIGH, 'Contact\Unfollow', $cdata['public'], $contact['uid']); Worker::add(Worker::PRIORITY_HIGH, 'Contact\Unfollow', $pcid, $contact['uid']);
} }
if (in_array($contact['rel'], [self::FOLLOWER, self::FRIEND]) && !empty($cdata['public'])) { if (in_array($contact['rel'], [self::FOLLOWER, self::FRIEND]) && $pcid) {
Worker::add(Worker::PRIORITY_HIGH, 'Contact\RevokeFollow', $cdata['public'], $contact['uid']); Worker::add(Worker::PRIORITY_HIGH, 'Contact\RevokeFollow', $pcid, $contact['uid']);
} }
self::remove($contact['id']); self::remove($contact['id']);
@ -1547,24 +1573,25 @@ class Contact
/** /**
* Returns posts from a given contact url * Returns posts from a given contact url
* *
* @param string $contact_url Contact URL * @param string $contact_url Contact URL
* @param bool $thread_mode * @param int $uid User ID
* @param int $update Update mode * @param bool $only_media Only display media content
* @param int $parent Item parent ID for the update mode * @param string $last_created Newest creation date, used for paging
* @param bool $only_media Only display media content
* @return string posts in HTML * @return string posts in HTML
* @throws \Exception * @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 * Returns posts from a given contact id
* *
* @param int $cid Contact ID * @param int $cid Contact ID
* @param bool $only_media Only display media content * @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 * @return string posts in HTML
* @throws \Exception * @throws \Exception
*/ */
@ -2666,6 +2693,14 @@ class Contact
$data = Probe::uri($contact['url'], $network, $contact['uid']); $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) { if ($data['network'] == Protocol::DIASPORA) {
try { try {
DI::dsprContact()->updateFromProbeArray($data); DI::dsprContact()->updateFromProbeArray($data);
@ -2824,7 +2859,7 @@ class Contact
// We must not try to update relay contacts via probe. They are no real contacts. // We must not try to update relay contacts via probe. They are no real contacts.
// See Relay::updateContact() for more details. // See Relay::updateContact() for more details.
// We check after the probing to be able to correct falsely detected contact types. // 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])) (!Strings::compareLink($ret['url'], $contact['url']) || in_array($ret['network'], [Protocol::FEED, Protocol::PHANTOM]))
) { ) {
if (GServer::reachable($contact)) { if (GServer::reachable($contact)) {
@ -3013,6 +3048,10 @@ class Contact
*/ */
public static function getProtocol(string $url, string $network): string public static function getProtocol(string $url, string $network): string
{ {
if (self::isLocal($url)) {
return Protocol::ACTIVITYPUB;
}
if ($network != Protocol::DFRN) { if ($network != Protocol::DFRN) {
return $network; return $network;
} }
@ -3404,16 +3443,21 @@ class Contact
* Update the local relationship when a local user loses a follower * Update the local relationship when a local user loses a follower
* *
* @param array $contact User-specific contact (uid != 0) array * @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 * @return void
* @throws HTTPException\InternalServerErrorException * @throws HTTPException\InternalServerErrorException
* @throws \ImagickException * @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])) { if (in_array($contact['rel'] ?? [], [self::FRIEND, self::SHARING])) {
self::update(['rel' => self::SHARING], ['id' => $contact['id']]); self::update(['rel' => self::SHARING], ['id' => $contact['id']]);
} elseif (!empty($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 { } else {
DI::logger()->info('Couldn\'t remove follower because of invalid contact array', ['contact' => $contact]); DI::logger()->info('Couldn\'t remove follower because of invalid contact array', ['contact' => $contact]);
return; return;
@ -3423,9 +3467,9 @@ class Contact
self::clearFollowerFollowingEndpointCache($contact['uid']); self::clearFollowerFollowingEndpointCache($contact['uid']);
$cdata = self::getPublicAndUserContactID($contact['id'], $contact['uid']); $pcid = self::getPublicContactId($contact['id'], $contact['uid']);
if (!empty($cdata['public'])) { if ($pcid) {
DI::notification()->deleteForUserByVerb($contact['uid'], Activity::FOLLOW, ['actor-id' => $cdata['public']]); DI::notification()->deleteForUserByVerb($contact['uid'], Activity::FOLLOW, ['actor-id' => $pcid]);
} }
} }
@ -3434,14 +3478,19 @@ class Contact
* Removes the contact for sharing-only protocols (feed and mail). * Removes the contact for sharing-only protocols (feed and mail).
* *
* @param array $contact User-specific contact (uid != 0) array * @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 * @throws HTTPException\InternalServerErrorException
*/ */
public static function removeSharer(array $contact) public static function removeSharer(array $contact, bool $delete = true)
{ {
self::clearFollowerFollowingEndpointCache($contact['uid']); self::clearFollowerFollowingEndpointCache($contact['uid']);
if ($contact['rel'] == self::SHARING || in_array($contact['network'], [Protocol::FEED, Protocol::MAIL])) { if (in_array($contact['rel'], [self::SHARING, self::NOTHING]) || in_array($contact['network'], [Protocol::FEED, Protocol::MAIL])) {
self::remove($contact['id']); if ($delete) {
self::remove($contact['id']);
} else {
self::update(['rel' => self::NOTHING, 'pending' => false], ['id' => $contact['id']]);
}
} else { } else {
self::update(['rel' => self::FOLLOWER, 'pending' => false], ['id' => $contact['id']]); self::update(['rel' => self::FOLLOWER, 'pending' => false], ['id' => $contact['id']]);
} }
@ -3572,6 +3621,9 @@ class Contact
} }
$contact = DBA::selectFirst('contact', ['id', 'network', 'url', 'alias', 'uid'], ['id' => $cid]); $contact = DBA::selectFirst('contact', ['id', 'network', 'url', 'alias', 'uid'], ['id' => $cid]);
if (empty($contact)) {
return $url;
}
return self::magicLinkByContact($contact, $url); return self::magicLinkByContact($contact, $url);
} }

View file

@ -281,12 +281,12 @@ class User
*/ */
public static function setCollapsed(int $cid, int $uid, bool $collapsed) public static function setCollapsed(int $cid, int $uid, bool $collapsed)
{ {
$cdata = Contact::getPublicAndUserContactID($cid, $uid); $pcid = Contact::getPublicContactId($cid, $uid);
if (empty($cdata)) { if (!$pcid) {
return; 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 public static function isCollapsed(int $cid, int $uid): bool
{ {
$cdata = Contact::getPublicAndUserContactID($cid, $uid); $pcid = Contact::getPublicContactId($cid, $uid);
if (empty($cdata)) { if (!$pcid) {
return false; return false;
} }
$collapsed = false; $public_contact = DBA::selectFirst('user-contact', ['collapsed'], ['cid' => $pcid, 'uid' => $uid]);
return $public_contact['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;
} }
/** /**
@ -328,12 +320,12 @@ class User
*/ */
public static function setChannelFrequency(int $cid, int $uid, int $frequency) public static function setChannelFrequency(int $cid, int $uid, int $frequency)
{ {
$cdata = Contact::getPublicAndUserContactID($cid, $uid); $pcid = Contact::getPublicContactId($cid, $uid);
if (empty($cdata)) { if (!$pcid) {
return; 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,13 @@ class User
*/ */
public static function getChannelFrequency(int $cid, int $uid): int public static function getChannelFrequency(int $cid, int $uid): int
{ {
$cdata = Contact::getPublicAndUserContactID($cid, $uid); $pcid = Contact::getPublicContactId($cid, $uid);
if (empty($cdata)) { if (!$pcid) {
return false; 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;
}
}
return $frequency;
} }
/** /**
@ -375,12 +359,12 @@ class User
*/ */
public static function setChannelOnly(int $cid, int $uid, bool $isChannelOnly) public static function setChannelOnly(int $cid, int $uid, bool $isChannelOnly)
{ {
$cdata = Contact::getPublicAndUserContactID($cid, $uid); $pcid = Contact::getPublicContactId($cid, $uid);
if (empty($cdata)) { if (!$pcid) {
return; return;
} }
DBA::update('user-contact', ['channel-only' => $isChannelOnly], ['cid' => $cdata['public'], 'uid' => $uid], true); DBA::update('user-contact', ['channel-only' => $isChannelOnly], ['cid' => $pcid, 'uid' => $uid], true);
} }
/** /**
@ -394,21 +378,13 @@ class User
*/ */
public static function getChannelOnly(int $cid, int $uid): bool public static function getChannelOnly(int $cid, int $uid): bool
{ {
$cdata = Contact::getPublicAndUserContactID($cid, $uid); $pcid = Contact::getPublicContactId($cid, $uid);
if (empty($cdata)) { if (!$pcid) {
return false; return false;
} }
$isChannelOnly = false; $public_contact = DBA::selectFirst('user-contact', ['channel-only'], ['cid' => $pcid, 'uid' => $uid]);
return $public_contact['channel-only'] ?? false;
if (!empty($cdata['public'])) {
$public_contact = DBA::selectFirst('user-contact', ['channel-only'], ['cid' => $cdata['public'], 'uid' => $uid]);
if (DBA::isResult($public_contact)) {
$isChannelOnly = $public_contact['channel-only'] ?? false;
}
}
return $isChannelOnly;
} }
/** /**
@ -422,12 +398,12 @@ class User
*/ */
public static function setIsBlocked(int $cid, int $uid, bool $blocked) public static function setIsBlocked(int $cid, int $uid, bool $blocked)
{ {
$cdata = Contact::getPublicAndUserContactID($cid, $uid); $pcid = Contact::getPublicContactId($cid, $uid);
if (empty($cdata)) { if (!$pcid) {
return; 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);
} }
/** /**
@ -440,18 +416,12 @@ class User
*/ */
public static function isIsBlocked(int $cid, int $uid): bool public static function isIsBlocked(int $cid, int $uid): bool
{ {
$cdata = Contact::getPublicAndUserContactID($cid, $uid); $pcid = Contact::getPublicContactId($cid, $uid);
if (empty($cdata)) { if (!$pcid) {
return false; return false;
} }
if (!empty($cdata['public'])) { $public_contact = DBA::selectFirst('user-contact', ['is-blocked'], ['cid' => $pcid, 'uid' => $uid]);
$public_contact = DBA::selectFirst('user-contact', ['is-blocked'], ['cid' => $cdata['public'], 'uid' => $uid]); return $public_contact['is-blocked'] ?? false;
if (DBA::isResult($public_contact)) {
return $public_contact['is-blocked'];
}
}
return false;
} }
} }

View file

@ -87,9 +87,11 @@ class GServer
// Standardized endpoints // Standardized endpoints
const DETECT_STATISTICS_JSON = 100; const DETECT_STATISTICS_JSON = 100;
const DETECT_NODEINFO_1 = 101; const DETECT_NODEINFO_10 = 101; // Nodeinfo Version 1.0
const DETECT_NODEINFO_2 = 102; const DETECT_NODEINFO_20 = 102; // Nodeinfo Version 2.0
const DETECT_NODEINFO_210 = 103; 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 * Check for the existence of a server and adds it in the background if not existant
@ -612,7 +614,7 @@ class GServer
$in_webroot = empty(parse_url($url, PHP_URL_PATH)); $in_webroot = empty(parse_url($url, PHP_URL_PATH));
// When a nodeinfo is present, we don't need to dig further // When a nodeinfo is present, we don't need to dig further
$curlResult = DI::httpClient()->get($url . '/.well-known/x-nodeinfo2', HttpClientAccept::JSON, [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]); $curlResult = DI::httpClient()->get($url . '/.well-known/nodeinfo', HttpClientAccept::JSON, [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]);
if ($curlResult->isTimeout()) { if ($curlResult->isTimeout()) {
self::setFailureByUrl($url); self::setFailureByUrl($url);
return false; return false;
@ -621,10 +623,11 @@ class GServer
if (!empty($network) && !in_array($network, Protocol::NATIVE_SUPPORT)) { if (!empty($network) && !in_array($network, Protocol::NATIVE_SUPPORT)) {
$serverdata = ['detection-method' => self::DETECT_MANUAL, 'network' => $network, 'platform' => '', 'version' => '', 'site_name' => '', 'info' => '']; $serverdata = ['detection-method' => self::DETECT_MANUAL, 'network' => $network, 'platform' => '', 'version' => '', 'site_name' => '', 'info' => ''];
} else { } else {
$serverdata = self::parseNodeinfo210($curlResult); $serverdata = self::parseNodeinfo($url, $curlResult);
if (empty($serverdata)) {
$curlResult = DI::httpClient()->get($url . '/.well-known/nodeinfo', HttpClientAccept::JSON, [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]); if (empty($serverdata) || !in_array($serverdata['detection-method'], [self::DETECT_NODEINFO_20, self::DETECT_NODEINFO_21, self::DETECT_NODEINFO_22])) {
$serverdata = self::fetchNodeinfo($url, $curlResult); $curlResult = DI::httpClient()->get($url . '/.well-known/x-nodeinfo2', HttpClientAccept::JSON, [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]);
$serverdata = self::parseNodeinfo2($curlResult) ?: $serverdata;
} }
} }
@ -1049,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 string $url address of the server
* @param ICanHandleHttpResponses $httpResult * @param ICanHandleHttpResponses $httpResult
@ -1058,7 +1063,7 @@ class GServer
* *
* @throws \Friendica\Network\HTTPException\InternalServerErrorException * @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()) { if (!$httpResult->isSuccess()) {
return []; return [];
@ -1072,6 +1077,7 @@ class GServer
$nodeinfo1_url = ''; $nodeinfo1_url = '';
$nodeinfo2_url = ''; $nodeinfo2_url = '';
$detection_method = self::DETECT_MANUAL;
foreach ($nodeinfo['links'] as $link) { foreach ($nodeinfo['links'] as $link) {
if (!is_array($link) || empty($link['rel']) || empty($link['href'])) { if (!is_array($link) || empty($link['rel']) || empty($link['href'])) {
@ -1081,8 +1087,15 @@ class GServer
if ($link['rel'] == 'http://nodeinfo.diaspora.software/ns/schema/1.0') { if ($link['rel'] == 'http://nodeinfo.diaspora.software/ns/schema/1.0') {
$nodeinfo1_url = Network::addBasePath($link['href'], $httpResult->getUrl()); $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()); $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;
} }
} }
@ -1093,18 +1106,20 @@ class GServer
$server = []; $server = [];
if (!empty($nodeinfo2_url)) { if (!empty($nodeinfo2_url)) {
$server = self::parseNodeinfo2($nodeinfo2_url); $server = self::parseNodeinfo_2($nodeinfo2_url, $detection_method);
} }
if (empty($server) && !empty($nodeinfo1_url)) { if (empty($server) && !empty($nodeinfo1_url)) {
$server = self::parseNodeinfo1($nodeinfo1_url); $server = self::parseNodeinfo_1($nodeinfo1_url);
} }
return $server; 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 * @param string $nodeinfo_url address of the nodeinfo path
* *
@ -1112,7 +1127,7 @@ class GServer
* *
* @throws \Friendica\Network\HTTPException\InternalServerErrorException * @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, [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]); $curlResult = DI::httpClient()->get($nodeinfo_url, HttpClientAccept::JSON, [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]);
if (!$curlResult->isSuccess()) { if (!$curlResult->isSuccess()) {
@ -1125,8 +1140,10 @@ class GServer
return []; return [];
} }
$server = ['detection-method' => self::DETECT_NODEINFO_1, $server = [
'register_policy' => Register::CLOSED]; 'detection-method' => self::DETECT_NODEINFO_10,
'register_policy' => Register::CLOSED
];
if (!empty($nodeinfo['openRegistrations'])) { if (!empty($nodeinfo['openRegistrations'])) {
$server['register_policy'] = Register::OPEN; $server['register_policy'] = Register::OPEN;
@ -1202,17 +1219,20 @@ 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 * @return array Server data
* *
* @throws \Friendica\Network\HTTPException\InternalServerErrorException * @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, [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]); $curlResult = DI::httpClient()->get($nodeinfo_url, HttpClientAccept::JSON, [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]);
if (!$curlResult->isSuccess()) { if (!$curlResult->isSuccess()) {
@ -1225,7 +1245,7 @@ class GServer
} }
$server = [ $server = [
'detection-method' => self::DETECT_NODEINFO_2, 'detection-method' => $detection_method,
'register_policy' => Register::CLOSED, 'register_policy' => Register::CLOSED,
'platform' => 'unknown', 'platform' => 'unknown',
]; ];
@ -1234,6 +1254,15 @@ class GServer
$server['register_policy'] = Register::OPEN; $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 (!empty($nodeinfo['software'])) {
if (isset($nodeinfo['software']['name'])) { if (isset($nodeinfo['software']['name'])) {
$server['platform'] = strtolower($nodeinfo['software']['name']); $server['platform'] = strtolower($nodeinfo['software']['name']);
@ -1249,6 +1278,13 @@ class GServer
if (($server['platform'] == 'mastodon') && substr($nodeinfo['software']['version'], -5) == '-qoto') { if (($server['platform'] == 'mastodon') && substr($nodeinfo['software']['version'], -5) == '-qoto') {
$server['platform'] = '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']);
}
} }
} }
@ -1260,6 +1296,9 @@ class GServer
if (!empty($nodeinfo['metadata']['nodeName'])) { if (!empty($nodeinfo['metadata']['nodeName'])) {
$server['site_name'] = $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'])) { if (!empty($nodeinfo['usage']['users']['total'])) {
$server['registered-users'] = max($nodeinfo['usage']['users']['total'], 1); $server['registered-users'] = max($nodeinfo['usage']['users']['total'], 1);
@ -1320,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 * @param string $nodeinfo_url address of the nodeinfo path
* *
@ -1330,7 +1369,7 @@ class GServer
* *
* @throws \Friendica\Network\HTTPException\InternalServerErrorException * @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/ */
private static function parseNodeinfo210(ICanHandleHttpResponses $httpResult): array private static function parseNodeinfo2(ICanHandleHttpResponses $httpResult): array
{ {
if (!$httpResult->isSuccess()) { if (!$httpResult->isSuccess()) {
return []; return [];
@ -1342,8 +1381,10 @@ class GServer
return []; return [];
} }
$server = ['detection-method' => self::DETECT_NODEINFO_210, $server = [
'register_policy' => Register::CLOSED]; 'detection-method' => self::DETECT_NODEINFO2_10,
'register_policy' => Register::CLOSED
];
if (!empty($nodeinfo['openRegistrations'])) { if (!empty($nodeinfo['openRegistrations'])) {
$server['register_policy'] = Register::OPEN; $server['register_policy'] = Register::OPEN;
@ -2570,6 +2611,10 @@ class GServer
return; return;
} }
if ($data['openwebauth'] == $gserver['openwebauth']) {
return;
}
$serverdata = self::getZotData($gserver['url'], []); $serverdata = self::getZotData($gserver['url'], []);
if (empty($serverdata)) { if (empty($serverdata)) {
$serverdata = ['openwebauth' => $data['openwebauth']]; $serverdata = ['openwebauth' => $data['openwebauth']];

View file

@ -198,6 +198,10 @@ class Item
$fields['external-id'] = ItemURI::getIdByURI($fields['extid']); $fields['external-id'] = ItemURI::getIdByURI($fields['extid']);
} }
if (!empty($fields['replies'])) {
$fields['replies-id'] = ItemURI::getIdByURI($fields['replies']);
}
if (!empty($fields['verb'])) { if (!empty($fields['verb'])) {
$fields['vid'] = Verb::getID($fields['verb']); $fields['vid'] = Verb::getID($fields['verb']);
} }
@ -415,8 +419,24 @@ class Item
self::markForDeletion(['parent' => $item['parent'], 'deleted' => false], $priority); 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? // Is it our comment and/or our thread?
if (($item['origin'] || $parent['origin']) && ($item['uid'] != 0)) { 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 // 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); self::markForDeletion(['uri-id' => $item['uri-id'], 'deleted' => false], $priority);
@ -536,9 +556,9 @@ class Item
} }
if (!empty($item['causer-id']) && Contact::isSharing($item['causer-id'], $item['uid'], true)) { if (!empty($item['causer-id']) && Contact::isSharing($item['causer-id'], $item['uid'], true)) {
$cdata = Contact::getPublicAndUserContactID($item['causer-id'], $item['uid']); $ucid = Contact::getUserContactId($item['causer-id'], $item['uid']);
if (!empty($cdata['user'])) { if ($ucid) {
return $cdata['user']; return $ucid;
} }
} }
@ -1077,6 +1097,10 @@ class Item
$parent_id = 0; $parent_id = 0;
$parent_origin = $item['origin']; $parent_origin = $item['origin'];
if ($item['wall'] && empty($item['context'])) {
$item['context'] = $item['parent-uri'] . '#context';
}
if ($item['wall'] && empty($item['conversation'])) { if ($item['wall'] && empty($item['conversation'])) {
$item['conversation'] = $item['parent-uri'] . '#context'; $item['conversation'] = $item['parent-uri'] . '#context';
} }
@ -1098,6 +1122,10 @@ class Item
$item['conversation-id'] = ItemURI::getIdByURI($item['conversation']); $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)? // Is this item available in the global items (with uid=0)?
if ($item['uid'] == 0) { if ($item['uid'] == 0) {
$item['global'] = true; $item['global'] = true;
@ -1165,6 +1193,10 @@ class Item
$item['external-id'] = ItemURI::getIdByURI($item['extid']); $item['external-id'] = ItemURI::getIdByURI($item['extid']);
} }
if (!empty($item['replies'])) {
$item['replies-id'] = ItemURI::getIdByURI($item['replies']);
}
if ($item['verb'] == Activity::ANNOUNCE) { if ($item['verb'] == Activity::ANNOUNCE) {
self::setOwnerforResharedItem($item); self::setOwnerforResharedItem($item);
} }
@ -1334,6 +1366,14 @@ class Item
return 0; 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); Post\Origin::insert($posted_item);
// update the commented timestamp on the parent // update the commented timestamp on the parent
@ -1423,6 +1463,14 @@ class Item
} }
if ($inserted) { 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. // Fill the cache with the rendered content.
if (in_array($posted_item['gravity'], [self::GRAVITY_PARENT, self::GRAVITY_COMMENT])) { if (in_array($posted_item['gravity'], [self::GRAVITY_PARENT, self::GRAVITY_COMMENT])) {
self::updateDisplayCache($posted_item['uri-id']); self::updateDisplayCache($posted_item['uri-id']);
@ -2584,12 +2632,12 @@ class Item
return; return;
} }
$cdata = Contact::getPublicAndUserContactID($item['author-id'], $item['uid']); $ucid = Contact::getUserContactId($item['author-id'], $item['uid']);
if (empty($cdata['user']) || ($cdata['user'] != $item['contact-id'])) { if (!$ucid || ($ucid != $item['contact-id'])) {
return; 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; return;
} }
@ -4118,6 +4166,10 @@ class Item
return $item_id; return $item_id;
} }
if (ActivityPub\Processor::alreadyKnown($uri, '')) {
return 0;
}
$hookData = [ $hookData = [
'uri' => $uri, 'uri' => $uri,
'uid' => $uid, 'uid' => $uid,
@ -4213,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]); Logger::warning('Post does not exist although it was supposed to had been fetched.', ['id' => $id, 'url' => $url, 'uid' => $uid]);
return 0; 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; return;
} }
$logger->info('User statistics - start');
$userStats = User::getStatistics(); $userStats = User::getStatistics();
DI::keyValue()->set('nodeinfo_total_users', $userStats['total_users']); 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_monthly', $userStats['active_users_monthly']);
DI::keyValue()->set('nodeinfo_active_users_weekly', $userStats['active_users_weekly']); 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`)"]); $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]); $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_posts', $posts);
DI::keyValue()->set('nodeinfo_local_comments', $comments); 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 the supported services
* *
* @return Object with supported services * @return Object with supported services
*/ */
public static function getUsage(bool $version2 = false) public static function getUsage(bool $version2 = false)
{ {
$config = DI::config(); $config = DI::config();
@ -101,7 +108,7 @@ class Nodeinfo
* Return the supported services * Return the supported services
* *
* @return array with supported services * @return array with supported services
*/ */
public static function getServices(): array public static function getServices(): array
{ {
$services = [ $services = [

View file

@ -141,7 +141,7 @@ class Content
if ($uid != 0) { 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]; $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 { } 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(SearchIndex::getSearchTable(), $condition); return DBA::count(SearchIndex::getSearchTable(), $condition);
} }

View file

@ -88,8 +88,8 @@ class Media
// "document" has got the lowest priority. So when the same file is both attached as document // "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 // 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']]); $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))) { 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']]); Logger::info('Media already exists', ['uri-id' => $media['uri-id'], 'url' => $media['url'], 'found' => $found['type'], 'new' => $media['type']]);
return false; return false;
} }
@ -444,42 +444,46 @@ class Media
return $data; 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) { if (count($type) < 2) {
Logger::info('Unknown MimeType', ['type' => $type, 'media' => $data]); Logger::info('Unknown MimeType', ['type' => $type, 'media' => $mimeType]);
$data['type'] = self::UNKNOWN; return self::UNKNOWN;
return $data;
} }
$filetype = strtolower($type[0]); $filetype = strtolower($type[0]);
$subtype = strtolower($type[1]); $subtype = strtolower($type[1]);
if ($filetype == 'image') { if ($filetype == 'image') {
$data['type'] = self::IMAGE; $type = self::IMAGE;
} elseif ($filetype == 'video') { } elseif ($filetype == 'video') {
$data['type'] = self::VIDEO; $type = self::VIDEO;
} elseif ($filetype == 'audio') { } elseif ($filetype == 'audio') {
$data['type'] = self::AUDIO; $type = self::AUDIO;
} elseif (($filetype == 'text') && ($subtype == 'html')) { } elseif (($filetype == 'text') && ($subtype == 'html')) {
$data['type'] = self::HTML; $type = self::HTML;
} elseif (($filetype == 'text') && ($subtype == 'xml')) { } elseif (($filetype == 'text') && ($subtype == 'xml')) {
$data['type'] = self::XML; $type = self::XML;
} elseif (($filetype == 'text') && ($subtype == 'plain')) { } elseif (($filetype == 'text') && ($subtype == 'plain')) {
$data['type'] = self::PLAIN; $type = self::PLAIN;
} elseif ($filetype == 'text') { } elseif ($filetype == 'text') {
$data['type'] = self::TEXT; $type = self::TEXT;
} elseif (($filetype == 'application') && ($subtype == 'x-bittorrent')) { } elseif (($filetype == 'application') && ($subtype == 'x-bittorrent')) {
$data['type'] = self::TORRENT; $type = self::TORRENT;
} elseif ($filetype == 'application') { } elseif ($filetype == 'application') {
$data['type'] = self::APPLICATION; $type = self::APPLICATION;
} else { } else {
$data['type'] = self::UNKNOWN; $type = self::UNKNOWN;
Logger::info('Unknown type', ['filetype' => $filetype, 'subtype' => $subtype, 'media' => $data]); Logger::info('Unknown type', ['filetype' => $filetype, 'subtype' => $subtype, 'media' => $mimeType]);
return $data;
} }
Logger::debug('Detected type', ['filetype' => $filetype, 'subtype' => $subtype, 'media' => $data]); Logger::debug('Detected type', ['filetype' => $filetype, 'subtype' => $subtype, 'media' => $mimeType]);
return $data; return $type;
} }
/** /**

View file

@ -622,8 +622,10 @@ class Profile
$bd_format = DI::l10n()->t('g A l F d'); // 8 AM Friday January 18 $bd_format = DI::l10n()->t('g A l F d'); // 8 AM Friday January 18
$classtoday = ''; $classtoday = '';
$condition = ["`uid` = ? AND `type` != 'birthday' AND `start` < ? AND `start` >= ?", $condition = [
$uid, DateTimeFormat::utc('now + 7 days'), DateTimeFormat::utc('now - 1 days')]; "`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']]); $s = DBA::select('event', [], $condition, ['order' => ['start']]);
$r = []; $r = [];
@ -633,9 +635,11 @@ class Profile
$total = 0; $total = 0;
while ($rr = DBA::fetch($s)) { 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)], 'vid' => [Verb::getID(Activity::ATTEND), Verb::getID(Activity::ATTENDMAYBE)],
'visible' => true, 'deleted' => false]; 'visible' => true, 'deleted' => false
];
if (!Post::exists($condition)) { if (!Post::exists($condition)) {
continue; continue;
} }
@ -724,7 +728,8 @@ class Profile
if (!empty($search)) { if (!empty($search)) {
$publish = (DI::config()->get('system', 'publish_all') ? '' : "AND `publish` "); $publish = (DI::config()->get('system', 'publish_all') ? '' : "AND `publish` ");
$searchTerm = '%' . $search . '%'; $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 $publish
AND ((`name` LIKE ?) OR AND ((`name` LIKE ?) OR
(`nickname` LIKE ?) OR (`nickname` LIKE ?) OR
@ -735,7 +740,8 @@ class Profile
(`pub_keywords` LIKE ?) OR (`pub_keywords` LIKE ?) OR
(`prv_keywords` LIKE ?))", (`prv_keywords` LIKE ?))",
$searchTerm, $searchTerm, $searchTerm, $searchTerm, $searchTerm, $searchTerm, $searchTerm, $searchTerm,
$searchTerm, $searchTerm, $searchTerm, $searchTerm]; $searchTerm, $searchTerm, $searchTerm, $searchTerm
];
} else { } else {
$condition = ['verified' => true, 'blocked' => false, 'account_removed' => false, 'account_expired' => false]; $condition = ['verified' => true, 'blocked' => false, 'account_removed' => false, 'account_expired' => false];
if (!DI::config()->get('system', 'publish_all')) { if (!DI::config()->get('system', 'publish_all')) {
@ -838,4 +844,44 @@ class Profile
DBA::delete('profile', ['id' => $profile['id']]); 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

@ -412,6 +412,29 @@ class User
return 0; 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 * Get a user based on its email
* *

View file

@ -22,9 +22,11 @@
namespace Friendica\Module\ActivityPub; namespace Friendica\Module\ActivityPub;
use Friendica\Core\Logger; use Friendica\Core\Logger;
use Friendica\Core\Protocol;
use Friendica\Core\System; use Friendica\Core\System;
use Friendica\Database\DBA; use Friendica\Database\DBA;
use Friendica\DI; use Friendica\DI;
use Friendica\Model\Item;
use Friendica\Model\User; use Friendica\Model\User;
use Friendica\Module\BaseApi; use Friendica\Module\BaseApi;
use Friendica\Module\Special\HTTPException; use Friendica\Module\Special\HTTPException;
@ -103,6 +105,7 @@ class Inbox extends BaseApi
$uid = 0; $uid = 0;
} }
Item::incrementInbound(Protocol::ACTIVITYPUB);
ActivityPub\Receiver::processInbox($postdata, $_SERVER, $uid); ActivityPub\Receiver::processInbox($postdata, $_SERVER, $uid);
throw new \Friendica\Network\HTTPException\AcceptedException(); throw new \Friendica\Network\HTTPException\AcceptedException();

View file

@ -28,6 +28,12 @@ use Friendica\Module\BaseAdmin;
class Index extends 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 protected function content(array $request = []): string
{ {
parent::content(); parent::content();

View file

@ -30,6 +30,12 @@ use Friendica\Util\Strings;
class Details extends BaseAdmin 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 protected function content(array $request = []): string
{ {
parent::content(); parent::content();

View file

@ -29,6 +29,12 @@ use Friendica\Util\Strings;
class Index extends 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 protected function content(array $request = []): string
{ {
parent::content(); parent::content();

View file

@ -43,9 +43,9 @@ class Block extends BaseApi
Contact\User::setBlocked($this->parameters['id'], $uid, true); Contact\User::setBlocked($this->parameters['id'], $uid, true);
$cdata = Contact::getPublicAndUserContactID($this->parameters['id'], $uid); $ucid = Contact::getUserContactId($this->parameters['id'], $uid);
if (!empty($cdata['user'])) { if ($ucid) {
$contact = Contact::getById($cdata['user']); $contact = Contact::getById($ucid);
if (!empty($contact)) { if (!empty($contact)) {
// Mastodon-expected behavior: relationship is severed on block // Mastodon-expected behavior: relationship is severed on block
Contact::terminateFriendship($contact); Contact::terminateFriendship($contact);

View file

@ -21,7 +21,7 @@
namespace Friendica\Module\Api\Mastodon\Accounts; namespace Friendica\Module\Api\Mastodon\Accounts;
use Friendica\Core\System; use Friendica\Content\Widget;
use Friendica\Database\DBA; use Friendica\Database\DBA;
use Friendica\DI; use Friendica\DI;
use Friendica\Model\Contact; use Friendica\Model\Contact;
@ -75,6 +75,9 @@ class Followers extends BaseApi
$params['order'] = ['pid']; $params['order'] = ['pid'];
} }
$networks = Widget::unavailableNetworks();
$condition = DBA::mergeConditions($condition, array_merge(["NOT `network` IN (" . substr(str_repeat("?, ", count($networks)), 0, -2) . ")"], $networks));
$accounts = []; $accounts = [];
foreach (Contact::selectAccountToArray(['pid'], $condition, $params) as $follower) { foreach (Contact::selectAccountToArray(['pid'], $condition, $params) as $follower) {

View file

@ -21,7 +21,7 @@
namespace Friendica\Module\Api\Mastodon\Accounts; namespace Friendica\Module\Api\Mastodon\Accounts;
use Friendica\Core\System; use Friendica\Content\Widget;
use Friendica\Database\DBA; use Friendica\Database\DBA;
use Friendica\DI; use Friendica\DI;
use Friendica\Model\Contact; use Friendica\Model\Contact;
@ -75,6 +75,9 @@ class Following extends BaseApi
$params['order'] = ['pid']; $params['order'] = ['pid'];
} }
$networks = Widget::unavailableNetworks();
$condition = DBA::mergeConditions($condition, array_merge(["NOT `network` IN (" . substr(str_repeat("?, ", count($networks)), 0, -2) . ")"], $networks));
$accounts = []; $accounts = [];
foreach (Contact::selectAccountToArray(['pid'], $condition, $params) as $follower) { foreach (Contact::selectAccountToArray(['pid'], $condition, $params) as $follower) {

View file

@ -51,9 +51,9 @@ class Lists extends BaseApi
$lists = []; $lists = [];
$cdata = Contact::getPublicAndUserContactID($id, $uid); $ucid = Contact::getUserContactId($id, $uid);
if (!empty($cdata['user'])) { if ($ucid) {
$circles = DBA::select('group_member', ['gid'], ['contact-id' => $cdata['user']]); $circles = DBA::select('group_member', ['gid'], ['contact-id' => $ucid]);
while ($circle = DBA::fetch($circles)) { while ($circle = DBA::fetch($circles)) {
$lists[] = DI::mstdnList()->createFromCircleId($circle['gid']); $lists[] = DI::mstdnList()->createFromCircleId($circle['gid']);
} }

View file

@ -45,12 +45,12 @@ class Note extends BaseApi
'comment' => '', 'comment' => '',
], $request); ], $request);
$cdata = Contact::getPublicAndUserContactID($this->parameters['id'], $uid); $ucid = Contact::getUserContactId($this->parameters['id'], $uid);
if (empty($cdata['user'])) { if (!$ucid) {
$this->logAndJsonError(404, $this->errorFactory->RecordNotFound()); $this->logAndJsonError(404, $this->errorFactory->RecordNotFound());
} }
Contact::update(['info' => $request['comment']], ['id' => $cdata['user']]); Contact::update(['info' => $request['comment']], ['id' => $ucid]);
$this->jsonExit(DI::mstdnRelationship()->createFromContactId($this->parameters['id'], $uid)->toArray()); $this->jsonExit(DI::mstdnRelationship()->createFromContactId($this->parameters['id'], $uid)->toArray());
} }

View file

@ -40,12 +40,12 @@ class Unfollow extends BaseApi
$this->logAndJsonError(422, $this->errorFactory->UnprocessableEntity()); $this->logAndJsonError(422, $this->errorFactory->UnprocessableEntity());
} }
$cdata = Contact::getPublicAndUserContactID($this->parameters['id'], $uid); $ucid = Contact::getUserContactId($this->parameters['id'], $uid);
if (empty($cdata['user'])) { if (!$ucid) {
$this->logAndJsonError(404, $this->errorFactory->RecordNotFound()); $this->logAndJsonError(404, $this->errorFactory->RecordNotFound());
} }
$contact = Contact::getById($cdata['user']); $contact = Contact::getById($ucid);
Contact::unfollow($contact); Contact::unfollow($contact);

View file

@ -100,12 +100,12 @@ class UpdateCredentials extends BaseApi
User::update($user, $uid); User::update($user, $uid);
Profile::update($profile, $uid); Profile::update($profile, $uid);
$cdata = Contact::getPublicAndUserContactID($owner['id'], $uid); $ucid = Contact::getUserContactId($owner['id'], $uid);
if (empty($cdata)) { if (!$ucid) {
DI::mstdnError()->InternalError(); DI::mstdnError()->InternalError();
} }
$account = DI::mstdnAccount()->createFromContactId($cdata['user'], $uid); $account = DI::mstdnAccount()->createFromContactId($ucid, $uid);
$this->response->addJsonContent($account->toArray()); $this->response->addJsonContent($account->toArray());
} }
} }

View file

@ -45,13 +45,13 @@ class VerifyCredentials extends BaseApi
DI::mstdnError()->InternalError(); DI::mstdnError()->InternalError();
} }
$cdata = Contact::getPublicAndUserContactID($self['id'], $uid); $ucid = Contact::getUserContactId($self['id'], $uid);
if (empty($cdata)) { if (!$ucid) {
DI::mstdnError()->InternalError(); DI::mstdnError()->InternalError();
} }
// @todo Support the source property, // @todo Support the source property,
$account = DI::mstdnAccount()->createFromContactId($cdata['user'], $uid); $account = DI::mstdnAccount()->createFromContactId($ucid, $uid);
$this->response->addJsonContent($account->toArray()); $this->response->addJsonContent($account->toArray());
} }
} }

View file

@ -21,7 +21,6 @@
namespace Friendica\Module\Api\Mastodon; namespace Friendica\Module\Api\Mastodon;
use Friendica\Core\System;
use Friendica\DI; use Friendica\DI;
use Friendica\Model\Contact; use Friendica\Model\Contact;
use Friendica\Module\BaseApi; use Friendica\Module\BaseApi;
@ -47,12 +46,12 @@ class FollowRequests extends BaseApi
$this->checkAllowedScope(self::SCOPE_FOLLOW); $this->checkAllowedScope(self::SCOPE_FOLLOW);
$uid = self::getCurrentUserID(); $uid = self::getCurrentUserID();
$cdata = Contact::getPublicAndUserContactID($this->parameters['id'], $uid); $ucid = Contact::getUserContactId($this->parameters['id'], $uid);
if (empty($cdata['user'])) { if (!$ucid) {
throw new HTTPException\NotFoundException('Contact not found'); throw new HTTPException\NotFoundException('Contact not found');
} }
$introduction = DI::intro()->selectForContact($cdata['user']); $introduction = DI::intro()->selectForContact($ucid);
$contactId = $introduction->cid; $contactId = $introduction->cid;

View file

@ -131,12 +131,19 @@ class InstanceV2 extends BaseApi
return new InstanceEntity\Configuration( return new InstanceEntity\Configuration(
$statuses_config, $statuses_config,
new InstanceEntity\MediaAttachmentsConfig(Images::supportedMimeTypes(), $image_size_limit, $image_matrix_limit), new InstanceEntity\MediaAttachmentsConfig($this->supportedMimeTypes(), $image_size_limit, $image_matrix_limit),
new InstanceEntity\Polls(), new InstanceEntity\Polls(),
new InstanceEntity\Accounts(), new InstanceEntity\Accounts(),
); );
} }
private function supportedMimeTypes(): array
{
$mimetypes = ['audio/aac', 'audio/flac', 'audio/mpeg', 'audio/mp4', 'audio/ogg', 'audio/wav',
'audio/webm', 'video/mp4', 'video/ogg', 'video/webm'];
return array_merge(Images::supportedMimeTypes(), $mimetypes);
}
private function buildContactInfo(): InstanceEntity\Contact private function buildContactInfo(): InstanceEntity\Contact
{ {
$email = implode(',', User::getAdminEmailList()); $email = implode(',', User::getAdminEmailList());

View file

@ -22,8 +22,9 @@
namespace Friendica\Module\Api\Mastodon; namespace Friendica\Module\Api\Mastodon;
use Friendica\Core\Logger; use Friendica\Core\Logger;
use Friendica\Core\System;
use Friendica\DI; use Friendica\DI;
use Friendica\Model\Attach;
use Friendica\Model\Contact;
use Friendica\Model\Photo; use Friendica\Model\Photo;
use Friendica\Model\Post; use Friendica\Model\Post;
use Friendica\Module\BaseApi; use Friendica\Module\BaseApi;
@ -51,14 +52,38 @@ class Media extends BaseApi
$this->logAndJsonError(422, $this->errorFactory->UnprocessableEntity()); $this->logAndJsonError(422, $this->errorFactory->UnprocessableEntity());
} }
$media = Photo::upload($uid, $_FILES['file'], '', null, null, '', '', $request['description']); $type = Post\Media::getType($_FILES['file']['type']);
if (empty($media)) {
$this->logAndJsonError(422, $this->errorFactory->UnprocessableEntity()); if (in_array($type, [Post\Media::IMAGE, Post\Media::UNKNOWN])) {
$media = Photo::upload($uid, $_FILES['file'], '', null, null, '', '', $request['description']);
if (empty($media)) {
$this->logAndJsonError(422, $this->errorFactory->UnprocessableEntity());
}
Logger::info('Uploaded photo', ['media' => $media]);
$this->jsonExit(DI::mstdnAttachment()->createFromPhoto($media['id']));
} else {
$tempFileName = $_FILES['file']['tmp_name'];
$fileName = basename($_FILES['file']['name']);
$fileSize = intval($_FILES['file']['size']);
$maxFileSize = DI::config()->get('system', 'maxfilesize');
if ($fileSize <= 0) {
@unlink($tempFileName);
$this->logAndJsonError(422, $this->errorFactory->UnprocessableEntity());
}
if ($maxFileSize && $fileSize > $maxFileSize) {
@unlink($tempFileName);
$this->logAndJsonError(422, $this->errorFactory->UnprocessableEntity());
}
$id = Attach::storeFile($tempFileName, self::getCurrentUserID(), $fileName, $_FILES['file']['type'], '<' . Contact::getPublicIdByUserId(self::getCurrentUserID()) . '>');
@unlink($tempFileName);
Logger::info('Uploaded media', ['id' => $id]);
$this->jsonExit(DI::mstdnAttachment()->createFromAttach($id));
} }
Logger::info('Uploaded photo', ['media' => $media]);
$this->jsonExit(DI::mstdnAttachment()->createFromPhoto($media['id']));
} }
public function put(array $request = []) public function put(array $request = [])
@ -77,6 +102,10 @@ class Media extends BaseApi
$this->logAndJsonError(422, $this->errorFactory->UnprocessableEntity()); $this->logAndJsonError(422, $this->errorFactory->UnprocessableEntity());
} }
if (DI::mstdnAttachment()->isAttach($this->parameters['id']) && Attach::exists(['id' => substr($this->parameters['id'], 7)])) {
$this->jsonExit(DI::mstdnAttachment()->createFromAttach(substr($this->parameters['id'], 7)));
}
$photo = Photo::selectFirst(['resource-id'], ['id' => $this->parameters['id'], 'uid' => $uid]); $photo = Photo::selectFirst(['resource-id'], ['id' => $this->parameters['id'], 'uid' => $uid]);
if (empty($photo['resource-id'])) { if (empty($photo['resource-id'])) {
$media = Post\Media::getById($this->parameters['id']); $media = Post\Media::getById($this->parameters['id']);
@ -108,10 +137,15 @@ class Media extends BaseApi
} }
$id = $this->parameters['id']; $id = $this->parameters['id'];
if (!Photo::exists(['id' => $id, 'uid' => $uid])) {
$this->logAndJsonError(404, $this->errorFactory->RecordNotFound()); if (Photo::exists(['id' => $id, 'uid' => $uid])) {
$this->jsonExit(DI::mstdnAttachment()->createFromPhoto($id));
} }
$this->jsonExit(DI::mstdnAttachment()->createFromPhoto($id)); if (DI::mstdnAttachment()->isAttach($id) && Attach::exists(['id' => substr($id, 7)])) {
$this->jsonExit(DI::mstdnAttachment()->createFromAttach(substr($id, 7)));
}
$this->logAndJsonError(404, $this->errorFactory->RecordNotFound());
} }
} }

View file

@ -98,13 +98,13 @@ class PushSubscription extends BaseApi
} }
$fields = [ $fields = [
Notification::TYPE_FOLLOW => $request['data']['alerts'][Notification::TYPE_FOLLOW] ?? false, Notification::TYPE_FOLLOW => $this->setBoolean($request['data']['alerts'][Notification::TYPE_FOLLOW] ?? false),
Notification::TYPE_LIKE => $request['data']['alerts'][Notification::TYPE_LIKE] ?? false, Notification::TYPE_LIKE => $this->setBoolean($request['data']['alerts'][Notification::TYPE_LIKE] ?? false),
Notification::TYPE_RESHARE => $request['data']['alerts'][Notification::TYPE_RESHARE] ?? false, Notification::TYPE_RESHARE => $this->setBoolean($request['data']['alerts'][Notification::TYPE_RESHARE] ?? false),
Notification::TYPE_MENTION => $request['data']['alerts'][Notification::TYPE_MENTION] ?? false, Notification::TYPE_MENTION => $this->setBoolean($request['data']['alerts'][Notification::TYPE_MENTION] ?? false),
Notification::TYPE_POLL => $request['data']['alerts'][Notification::TYPE_POLL] ?? false, Notification::TYPE_POLL => $this->setBoolean($request['data']['alerts'][Notification::TYPE_POLL] ?? false),
Notification::TYPE_INTRODUCTION => $request['data']['alerts'][Notification::TYPE_INTRODUCTION] ?? false, Notification::TYPE_INTRODUCTION => $this->setBoolean($request['data']['alerts'][Notification::TYPE_INTRODUCTION] ?? false),
Notification::TYPE_POST => $request['data']['alerts'][Notification::TYPE_POST] ?? false, Notification::TYPE_POST => $this->setBoolean($request['data']['alerts'][Notification::TYPE_POST] ?? false),
]; ];
$ret = Subscription::update($application['id'], $uid, $fields); $ret = Subscription::update($application['id'], $uid, $fields);
@ -120,6 +120,14 @@ class PushSubscription extends BaseApi
$this->response->addJsonContent($subscriptionObj->toArray()); $this->response->addJsonContent($subscriptionObj->toArray());
} }
private function setBoolean($input): bool
{
if (is_bool($input)) {
return $input;
}
return strtolower($input) == 'true';
}
protected function delete(array $request = []): void protected function delete(array $request = []): void
{ {
$this->checkAllowedScope(self::SCOPE_PUSH); $this->checkAllowedScope(self::SCOPE_PUSH);

View file

@ -28,6 +28,7 @@ use Friendica\Core\Protocol;
use Friendica\Core\Worker; use Friendica\Core\Worker;
use Friendica\Database\DBA; use Friendica\Database\DBA;
use Friendica\DI; use Friendica\DI;
use Friendica\Model\Attach;
use Friendica\Model\Contact; use Friendica\Model\Contact;
use Friendica\Model\Circle; use Friendica\Model\Circle;
use Friendica\Model\Item; use Friendica\Model\Item;
@ -397,6 +398,20 @@ class Statuses extends BaseApi
$item['attachments'] = []; $item['attachments'] = [];
foreach ($media_ids as $id) { foreach ($media_ids as $id) {
if (DI::mstdnAttachment()->isAttach($id) && Attach::exists(['id' => substr($id, 7)])) {
$attach = Attach::selectFirst([], ['id' => substr($id, 7)]);
$attachment = [
'type' => Post\Media::getType($attach['filetype']),
'mimetype' => $attach['filetype'],
'url' => DI::baseUrl() . '/attach/' . substr($id, 7),
'size' => $attach['filetype'],
'name' => $attach['filename']
];
$item['attachments'][] = $attachment;
Attach::setPermissionForId(substr($id, 7), $item['uid'], $item['allow_cid'], $item['allow_gid'], $item['deny_cid'], $item['deny_gid']);
continue;
}
$media = DBA::toArray(DBA::p("SELECT `resource-id`, `scale`, `type`, `desc`, `filename`, `datasize`, `width`, `height` FROM `photo` $media = DBA::toArray(DBA::p("SELECT `resource-id`, `scale`, `type`, `desc`, `filename`, `datasize`, `width`, `height` FROM `photo`
WHERE `resource-id` IN (SELECT `resource-id` FROM `photo` WHERE `id` = ?) AND `photo`.`uid` = ? WHERE `resource-id` IN (SELECT `resource-id` FROM `photo` WHERE `id` = ?) AND `photo`.`uid` = ?
ORDER BY `photo`.`width` DESC LIMIT 2", $id, $item['uid'])); ORDER BY `photo`.`width` DESC LIMIT 2", $id, $item['uid']));
@ -409,13 +424,16 @@ class Statuses extends BaseApi
$ext = Images::getExtensionByMimeType($media[0]['type']); $ext = Images::getExtensionByMimeType($media[0]['type']);
$attachment = ['type' => Post\Media::IMAGE, 'mimetype' => $media[0]['type'], $attachment = [
'url' => DI::baseUrl() . '/photo/' . $media[0]['resource-id'] . '-' . $media[0]['scale'] . $ext, 'type' => Post\Media::IMAGE,
'size' => $media[0]['datasize'], 'mimetype' => $media[0]['type'],
'name' => $media[0]['filename'] ?: $media[0]['resource-id'], 'url' => DI::baseUrl() . '/photo/' . $media[0]['resource-id'] . '-' . $media[0]['scale'] . $ext,
'size' => $media[0]['datasize'],
'name' => $media[0]['filename'] ?: $media[0]['resource-id'],
'description' => $media[0]['desc'] ?? '', 'description' => $media[0]['desc'] ?? '',
'width' => $media[0]['width'], 'width' => $media[0]['width'],
'height' => $media[0]['height']]; 'height' => $media[0]['height']
];
if (count($media) > 1) { if (count($media) > 1) {
$attachment['preview'] = DI::baseUrl() . '/photo/' . $media[1]['resource-id'] . '-' . $media[1]['scale'] . $ext; $attachment['preview'] = DI::baseUrl() . '/photo/' . $media[1]['resource-id'] . '-' . $media[1]['scale'] . $ext;

View file

@ -110,8 +110,7 @@ class ListTimeline extends BaseApi
private function getStatusesForGroup(int $uid, array $request): array private function getStatusesForGroup(int $uid, array $request): array
{ {
$cdata = Contact::getPublicAndUserContactID((int)substr($this->parameters['id'], 6), $uid); $cid = Contact::getPublicContactId((int)substr($this->parameters['id'], 6), $uid);
$cid = $cdata['public'];
$condition = ["(`uid` = ? OR (`uid` = ? AND NOT `global`))", 0, $uid]; $condition = ["(`uid` = ? OR (`uid` = ? AND NOT `global`))", 0, $uid];

View file

@ -21,26 +21,47 @@
namespace Friendica\Module\Api\Mastodon\Timelines; namespace Friendica\Module\Api\Mastodon\Timelines;
use Friendica\App;
use Friendica\Core\Config\Capability\IManageConfigValues;
use Friendica\Core\L10n;
use Friendica\Core\Logger; use Friendica\Core\Logger;
use Friendica\Core\Protocol; use Friendica\Core\Protocol;
use Friendica\Database\DBA; use Friendica\Database\DBA;
use Friendica\DI; use Friendica\DI;
use Friendica\Model\Item; use Friendica\Model\Item;
use Friendica\Model\Post; use Friendica\Model\Post;
use Friendica\Module\Api\ApiResponse;
use Friendica\Module\BaseApi; use Friendica\Module\BaseApi;
use Friendica\Module\Conversation\Community;
use Friendica\Network\HTTPException; use Friendica\Network\HTTPException;
use Friendica\Object\Api\Mastodon\TimelineOrderByTypes; use Friendica\Object\Api\Mastodon\TimelineOrderByTypes;
use Friendica\Util\Profiler;
use Psr\Log\LoggerInterface;
/** /**
* @see https://docs.joinmastodon.org/methods/timelines/ * @see https://docs.joinmastodon.org/methods/timelines/
*/ */
class PublicTimeline extends BaseApi class PublicTimeline extends BaseApi
{ {
/**
* @var IManageConfigValues
*/
private $config;
public function __construct(IManageConfigValues $config, \Friendica\Factory\Api\Mastodon\Error $errorFactory, App $app, L10n $l10n, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, ApiResponse $response, array $server, array $parameters = [])
{
parent::__construct($errorFactory, $app, $l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters);
$this->config = $config;
}
/** /**
* @throws HTTPException\InternalServerErrorException * @throws HTTPException\InternalServerErrorException
*/ */
protected function rawContent(array $request = []) protected function rawContent(array $request = [])
{ {
if ($this->config->get('system', 'block_public') || $this->config->get('system', 'community_page_style') == Community::DISABLED_VISITOR) {
$this->checkAllowedScope(BaseApi::SCOPE_READ);
}
$uid = self::getCurrentUserID(); $uid = self::getCurrentUserID();
$request = $this->getRequest([ $request = $this->getRequest([
@ -56,6 +77,10 @@ class PublicTimeline extends BaseApi
'friendica_order' => TimelineOrderByTypes::ID, // Sort order options (defaults to ID) 'friendica_order' => TimelineOrderByTypes::ID, // Sort order options (defaults to ID)
], $request); ], $request);
if (!$this->localAllowed() && !$this->globalAllowed()) {
$this->jsonExit([]);
}
$condition = [ $condition = [
'gravity' => [Item::GRAVITY_PARENT, Item::GRAVITY_COMMENT], 'private' => Item::PUBLIC, 'gravity' => [Item::GRAVITY_PARENT, Item::GRAVITY_COMMENT], 'private' => Item::PUBLIC,
'network' => Protocol::FEDERATED, 'author-blocked' => false, 'author-hidden' => false 'network' => Protocol::FEDERATED, 'author-blocked' => false, 'author-hidden' => false
@ -64,13 +89,13 @@ class PublicTimeline extends BaseApi
$condition = $this->addPagingConditions($request, $condition); $condition = $this->addPagingConditions($request, $condition);
$params = $this->buildOrderAndLimitParams($request); $params = $this->buildOrderAndLimitParams($request);
if ($request['local']) { if ($request['local'] && $this->localAllowed()) {
$condition = DBA::mergeConditions($condition, ['origin' => true]); $condition = DBA::mergeConditions($condition, ['origin' => true]);
} else { } else {
$condition = DBA::mergeConditions($condition, ['uid' => 0]); $condition = DBA::mergeConditions($condition, ['uid' => 0]);
} }
if ($request['remote']) { if ($request['remote'] && $this->globalAllowed()) {
$condition = DBA::mergeConditions($condition, ["NOT `uri-id` IN (SELECT `uri-id` FROM `post-user` WHERE `origin` AND `post-user`.`uri-id` = `post-timeline-view`.`uri-id`)"]); $condition = DBA::mergeConditions($condition, ["NOT `uri-id` IN (SELECT `uri-id` FROM `post-user` WHERE `origin` AND `post-user`.`uri-id` = `post-timeline-view`.`uri-id`)"]);
} }
@ -113,4 +138,14 @@ class PublicTimeline extends BaseApi
self::setLinkHeader($request['friendica_order'] != TimelineOrderByTypes::ID); self::setLinkHeader($request['friendica_order'] != TimelineOrderByTypes::ID);
$this->jsonExit($statuses); $this->jsonExit($statuses);
} }
private function localAllowed(): bool
{
return in_array($this->config->get('system', 'community_page_style'), [Community::LOCAL, Community::LOCAL_AND_GLOBAL, Community::DISABLED_VISITOR]);
}
private function globalAllowed(): bool
{
return in_array($this->config->get('system', 'community_page_style'), [Community::GLOBAL, Community::LOCAL_AND_GLOBAL, Community::DISABLED_VISITOR]);
}
} }

View file

@ -21,25 +21,46 @@
namespace Friendica\Module\Api\Mastodon\Trends; namespace Friendica\Module\Api\Mastodon\Trends;
use Friendica\App;
use Friendica\Core\Config\Capability\IManageConfigValues;
use Friendica\Core\L10n;
use Friendica\Core\Logger; use Friendica\Core\Logger;
use Friendica\Core\Protocol; use Friendica\Core\Protocol;
use Friendica\Core\System;
use Friendica\Database\DBA; use Friendica\Database\DBA;
use Friendica\DI; use Friendica\DI;
use Friendica\Model\Post; use Friendica\Model\Post;
use Friendica\Module\Api\ApiResponse;
use Friendica\Module\BaseApi; use Friendica\Module\BaseApi;
use Friendica\Module\Conversation\Community;
use Friendica\Util\DateTimeFormat; use Friendica\Util\DateTimeFormat;
use Friendica\Util\Profiler;
use Psr\Log\LoggerInterface;
/** /**
* @see https://docs.joinmastodon.org/methods/trends/#statuses * @see https://docs.joinmastodon.org/methods/trends/#statuses
*/ */
class Statuses extends BaseApi class Statuses extends BaseApi
{ {
/**
* @var IManageConfigValues
*/
private $config;
public function __construct(IManageConfigValues $config, \Friendica\Factory\Api\Mastodon\Error $errorFactory, App $app, L10n $l10n, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, ApiResponse $response, array $server, array $parameters = [])
{
parent::__construct($errorFactory, $app, $l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters);
$this->config = $config;
}
/** /**
* @throws \Friendica\Network\HTTPException\InternalServerErrorException * @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/ */
protected function rawContent(array $request = []) protected function rawContent(array $request = [])
{ {
if ($this->config->get('system', 'block_public') || $this->config->get('system', 'community_page_style') == Community::DISABLED_VISITOR) {
$this->checkAllowedScope(BaseApi::SCOPE_READ);
}
$uid = self::getCurrentUserID(); $uid = self::getCurrentUserID();
$request = $this->getRequest([ $request = $this->getRequest([

View file

@ -47,7 +47,8 @@ class Destroy extends BaseApi
$this->dba = $dba; $this->dba = $dba;
} }
protected function rawContent(array $request = [])
protected function post(array $request = [])
{ {
$this->checkAllowedScope(BaseApi::SCOPE_WRITE); $this->checkAllowedScope(BaseApi::SCOPE_WRITE);
$uid = BaseApi::getCurrentUserID(); $uid = BaseApi::getCurrentUserID();

View file

@ -54,7 +54,7 @@ class NewDM extends BaseApi
$this->directMessage = $directMessage; $this->directMessage = $directMessage;
} }
protected function rawContent(array $request = []) protected function post(array $request = [])
{ {
$this->checkAllowedScope(BaseApi::SCOPE_WRITE); $this->checkAllowedScope(BaseApi::SCOPE_WRITE);
$uid = BaseApi::getCurrentUserID(); $uid = BaseApi::getCurrentUserID();
@ -81,9 +81,9 @@ class NewDM extends BaseApi
} }
} }
$cdata = Contact::getPublicAndUserContactID($cid, $uid); $ucid = Contact::getUserContactId($cid, $uid);
$id = Mail::send($uid, $cdata['user'], $request['text'], $sub, $replyto); $id = Mail::send($uid, $ucid, $request['text'], $sub, $replyto);
if ($id > -1) { if ($id > -1) {
$ret = $this->directMessage->createFromMailId($id, $uid, $this->getRequestValue($request, 'getText', '')); $ret = $this->directMessage->createFromMailId($id, $uid, $this->getRequestValue($request, 'getText', ''));

View file

@ -88,9 +88,9 @@ abstract class DirectMessagesEndpoint extends BaseApi
$cid = BaseApi::getContactIDForSearchterm($this->getRequestValue($request, 'screen_name', ''), $this->getRequestValue($request, 'profileurl', ''), $this->getRequestValue($request, 'user_id', 0), 0); $cid = BaseApi::getContactIDForSearchterm($this->getRequestValue($request, 'screen_name', ''), $this->getRequestValue($request, 'profileurl', ''), $this->getRequestValue($request, 'user_id', 0), 0);
if (!empty($cid)) { if (!empty($cid)) {
$cdata = Contact::getPublicAndUserContactID($cid, $uid); $ucid = Contact::getUserContactId($cid, $uid);
if (!empty($cdata['user'])) { if ($ucid) {
$condition = DBA::mergeConditions($condition, ["`contact-id` = ?", $cdata['user']]); $condition = DBA::mergeConditions($condition, ["`contact-id` = ?", $ucid]);
} }
} }

View file

@ -71,13 +71,13 @@ class Destroy extends ContactEndpoint
} }
// Get Contact by given id // Get Contact by given id
$cdata = Contact::getPublicAndUserContactID($contact_id, $uid); $ucid = Contact::getUserContactId($contact_id, $uid);
if (!empty($cdata['user'])) { if (!$ucid) {
Logger::notice(BaseApi::LOG_PREFIX . 'Not following contact', ['module' => 'api', 'action' => 'friendships_destroy']); Logger::notice(BaseApi::LOG_PREFIX . 'Not following contact', ['module' => 'api', 'action' => 'friendships_destroy']);
throw new HTTPException\NotFoundException('Not following Contact'); throw new HTTPException\NotFoundException('Not following Contact');
} }
$contact = Contact::getById($cdata['user']); $contact = Contact::getById($ucid);
$user = $this->twitterUser->createFromContactId($contact_id, $uid, true)->toArray(); $user = $this->twitterUser->createFromContactId($contact_id, $uid, true)->toArray();
try { try {

View file

@ -55,9 +55,9 @@ class Show extends ContactEndpoint
$following = false; $following = false;
if ($source_cid == Contact::getPublicIdByUserId($uid)) { if ($source_cid == Contact::getPublicIdByUserId($uid)) {
$cdata = Contact::getPublicAndUserContactID($target_cid, $uid); $ucid = Contact::getUserContactId($target_cid, $uid);
if (!empty($cdata['user'])) { if ($ucid) {
$usercontact = Contact::getById($cdata['user'], ['rel']); $usercontact = Contact::getById($ucid, ['rel']);
switch ($usercontact['rel'] ?? Contact::NOTHING) { switch ($usercontact['rel'] ?? Contact::NOTHING) {
case Contact::FOLLOWER: case Contact::FOLLOWER:
$follower = true; $follower = true;

View file

@ -54,7 +54,7 @@ class Create extends BaseApi
$this->friendicaCircle = $friendicaCircle; $this->friendicaCircle = $friendicaCircle;
} }
protected function rawContent(array $request = []) protected function post(array $request = [])
{ {
$this->checkAllowedScope(BaseApi::SCOPE_WRITE); $this->checkAllowedScope(BaseApi::SCOPE_WRITE);
$uid = BaseApi::getCurrentUserID(); $uid = BaseApi::getCurrentUserID();

View file

@ -54,7 +54,7 @@ class Destroy extends BaseApi
$this->friendicaCircle = $friendicaCircle; $this->friendicaCircle = $friendicaCircle;
} }
protected function rawContent(array $request = []) protected function post(array $request = [])
{ {
$this->checkAllowedScope(BaseApi::SCOPE_WRITE); $this->checkAllowedScope(BaseApi::SCOPE_WRITE);
$uid = BaseApi::getCurrentUserID(); $uid = BaseApi::getCurrentUserID();

View file

@ -54,7 +54,7 @@ class Update extends BaseApi
$this->friendicaCircle = $friendicaCircle; $this->friendicaCircle = $friendicaCircle;
} }
protected function rawContent(array $request = []) protected function post(array $request = [])
{ {
$this->checkAllowedScope(BaseApi::SCOPE_WRITE); $this->checkAllowedScope(BaseApi::SCOPE_WRITE);
$uid = BaseApi::getCurrentUserID(); $uid = BaseApi::getCurrentUserID();

View file

@ -58,7 +58,13 @@ class Contact extends BaseModule
return; return;
} }
$redirectUrl = $_POST['redirect_url'] ?? 'contact'; $redirectUrl = $_POST['command'] ?? '';
if (substr($redirectUrl, 0, 7) != 'contact') {
$redirectUrl = 'contact';
}
if (!empty($_POST['parameter'])) {
$redirectUrl .= '?' . $_POST['parameter'];
}
self::checkFormSecurityTokenRedirectOnError($redirectUrl, 'contact_batch_actions'); self::checkFormSecurityTokenRedirectOnError($redirectUrl, 'contact_batch_actions');
@ -253,7 +259,7 @@ class Contact extends BaseModule
$sql_extra = " AND `archive` AND NOT `blocked` AND NOT `pending`"; $sql_extra = " AND `archive` AND NOT `blocked` AND NOT `pending`";
break; break;
case 'pending': case 'pending':
$sql_extra = " AND `pending` AND NOT `archive` AND NOT `failed` AND ((`rel` = ?) $sql_extra = " AND `pending` AND NOT `archive` AND ((`rel` = ?)
OR `id` IN (SELECT `contact-id` FROM `intro` WHERE `intro`.`uid` = ? AND NOT `ignore`))"; OR `id` IN (SELECT `contact-id` FROM `intro` WHERE `intro`.`uid` = ? AND NOT `ignore`))";
$sql_values[] = Model\Contact::SHARING; $sql_values[] = Model\Contact::SHARING;
$sql_values[] = DI::userSession()->getLocalUserId(); $sql_values[] = DI::userSession()->getLocalUserId();
@ -459,6 +465,7 @@ class Contact extends BaseModule
'$finding' => $searching ? DI::l10n()->t('Results for: %s', $search) : '', '$finding' => $searching ? DI::l10n()->t('Results for: %s', $search) : '',
'$submit' => DI::l10n()->t('Find'), '$submit' => DI::l10n()->t('Find'),
'$cmd' => DI::args()->getCommand(), '$cmd' => DI::args()->getCommand(),
'$parameter' => http_build_query($request),
'$contacts' => $contacts, '$contacts' => $contacts,
'$form_security_token' => BaseModule::getFormSecurityToken('contact_batch_actions'), '$form_security_token' => BaseModule::getFormSecurityToken('contact_batch_actions'),
'multiselect' => 1, 'multiselect' => 1,

View file

@ -81,18 +81,18 @@ class Conversations extends BaseModule
// Backward compatibility: Ensure to use the public contact when the user contact is provided // Backward compatibility: Ensure to use the public contact when the user contact is provided
// Remove by version 2022.03 // Remove by version 2022.03
$data = Model\Contact::getPublicAndUserContactID(intval($this->parameters['id']), $this->userSession->getLocalUserId()); $pcid = Model\Contact::getPublicContactId(intval($this->parameters['id']), $this->userSession->getLocalUserId());
if (empty($data)) { if (!$pcid) {
throw new NotFoundException($this->t('Contact not found.')); throw new NotFoundException($this->t('Contact not found.'));
} }
$contact = Model\Contact::getById($data['public']); $contact = Model\Contact::getById($pcid);
if (empty($contact)) { if (empty($contact)) {
throw new NotFoundException($this->t('Contact not found.')); throw new NotFoundException($this->t('Contact not found.'));
} }
// Don't display contacts that are about to be deleted // Don't display contacts that are about to be deleted
if (!empty($contact['deleted']) || !empty($contact['network']) && $contact['network'] == Protocol::PHANTOM) { if ($contact['deleted'] || $contact['network'] == Protocol::PHANTOM) {
throw new NotFoundException($this->t('Contact not found.')); throw new NotFoundException($this->t('Contact not found.'));
} }

View file

@ -215,7 +215,7 @@ class Follow extends BaseModule
$this->baseUrl->redirect($returnPath); $this->baseUrl->redirect($returnPath);
} elseif (!empty($result['cid'])) { } elseif (!empty($result['cid'])) {
$this->baseUrl->redirect('contact/' . $result['cid']); $this->baseUrl->redirect('contact/' . Contact::getPublicContactId($result['cid'], $this->session->getLocalUserId()));
} }
$this->sysMessages->addNotice($this->t('The contact could not be added.')); $this->sysMessages->addNotice($this->t('The contact could not be added.'));

View file

@ -65,7 +65,7 @@ class Media extends BaseModule
$o = Contact::getTabsHTML($contact, Contact::TAB_MEDIA); $o = Contact::getTabsHTML($contact, Contact::TAB_MEDIA);
$o .= ModelContact::getPostsFromUrl($contact['url'], $this->userSession->getLocalUserId(), true); $o .= ModelContact::getPostsFromUrl($contact['url'], $this->userSession->getLocalUserId(), true, $request['last_created'] ?? '');
return $o; return $o;
} }

View file

@ -73,18 +73,18 @@ class Posts extends BaseModule
// Backward compatibility: Ensure to use the public contact when the user contact is provided // Backward compatibility: Ensure to use the public contact when the user contact is provided
// Remove by version 2022.03 // Remove by version 2022.03
$data = Model\Contact::getPublicAndUserContactID(intval($this->parameters['id']), $this->userSession->getLocalUserId()); $pcid = Model\Contact::getPublicContactId(intval($this->parameters['id']), $this->userSession->getLocalUserId());
if (empty($data)) { if (!$pcid) {
throw new NotFoundException($this->t('Contact not found.')); throw new NotFoundException($this->t('Contact not found.'));
} }
$contact = Model\Contact::getById($data['public']); $contact = Model\Contact::getById($pcid);
if (!DBA::isResult($contact)) { if (!DBA::isResult($contact)) {
throw new NotFoundException($this->t('Contact not found.')); throw new NotFoundException($this->t('Contact not found.'));
} }
// Don't display contacts that are about to be deleted // Don't display contacts that are about to be deleted
if (DBA::isResult($contact) && (!empty($contact['deleted']) || !empty($contact['network']) && $contact['network'] == Protocol::PHANTOM)) { if ($contact['deleted'] || $contact['network'] == Protocol::PHANTOM) {
throw new NotFoundException($this->t('Contact not found.')); throw new NotFoundException($this->t('Contact not found.'));
} }

View file

@ -91,8 +91,8 @@ class Profile extends BaseModule
// Backward compatibility: The update still needs a user-specific contact ID // Backward compatibility: The update still needs a user-specific contact ID
// Change to user-contact table check by version 2022.03 // Change to user-contact table check by version 2022.03
$cdata = Contact::getPublicAndUserContactID($contact_id, $this->session->getLocalUserId()); $ucid = Contact::getUserContactId($contact_id, $this->session->getLocalUserId());
if (empty($cdata['user']) || !$this->db->exists('contact', ['id' => $cdata['user'], 'deleted' => false])) { if (!$ucid || !$this->db->exists('contact', ['id' => $ucid, 'deleted' => false])) {
return; return;
} }
@ -134,14 +134,14 @@ class Profile extends BaseModule
} }
if (isset($request['channel_frequency'])) { if (isset($request['channel_frequency'])) {
Contact\User::setChannelFrequency($cdata['user'], $this->session->getLocalUserId(), $request['channel_frequency']); Contact\User::setChannelFrequency($ucid, $this->session->getLocalUserId(), $request['channel_frequency']);
} }
if (isset($request['channel_only'])) { if (isset($request['channel_only'])) {
Contact\User::setChannelOnly($cdata['user'], $this->session->getLocalUserId(), $request['channel_only']); Contact\User::setChannelOnly($ucid, $this->session->getLocalUserId(), $request['channel_only']);
} }
if (!Contact::update($fields, ['id' => $cdata['user'], 'uid' => $this->session->getLocalUserId()])) { if (!Contact::update($fields, ['id' => $ucid, 'uid' => $this->session->getLocalUserId()])) {
$this->systemMessages->addNotice($this->t('Failed to update contact record.')); $this->systemMessages->addNotice($this->t('Failed to update contact record.'));
} }
} }
@ -164,8 +164,22 @@ class Profile extends BaseModule
throw new HTTPException\NotFoundException($this->t('Contact not found.')); throw new HTTPException\NotFoundException($this->t('Contact not found.'));
} }
// Fetch the protocol from the user's contact.
if ($data['user']) {
$usercontact = Contact::getById($data['user'], ['network', 'protocol']);
if ($this->db->isResult($usercontact)) {
$contact['network'] = $usercontact['network'];
$contact['protocol'] = $usercontact['protocol'];
}
}
if (empty($contact['network']) && Contact::isLocal($contact['url']) ) {
$contact['network'] = Protocol::DFRN;
$contact['protocol'] = Protocol::ACTIVITYPUB;
}
// Don't display contacts that are about to be deleted // Don't display contacts that are about to be deleted
if ($this->db->isResult($contact) && (!empty($contact['deleted']) || !empty($contact['network']) && $contact['network'] == Protocol::PHANTOM)) { if ($contact['deleted'] || $contact['network'] == Protocol::PHANTOM) {
throw new HTTPException\NotFoundException($this->t('Contact not found.')); throw new HTTPException\NotFoundException($this->t('Contact not found.'));
} }

View file

@ -30,6 +30,7 @@ use Friendica\Core\Renderer;
use Friendica\Database\Database; use Friendica\Database\Database;
use Friendica\DI; use Friendica\DI;
use Friendica\Model; use Friendica\Model;
use Friendica\Model\Contact as ModelContact;
use Friendica\Module\Contact; use Friendica\Module\Contact;
use Friendica\Module\Response; use Friendica\Module\Response;
use Friendica\Module\Security\Login; use Friendica\Module\Security\Login;
@ -58,16 +59,12 @@ class Revoke extends BaseModule
return; return;
} }
$data = Model\Contact::getPublicAndUserContactID($this->parameters['id'], DI::userSession()->getLocalUserId()); $ucid = Model\Contact::getUserContactId($this->parameters['id'], DI::userSession()->getLocalUserId());
if (!$this->dba->isResult($data)) { if (!$ucid) {
throw new HTTPException\NotFoundException($this->t('Unknown contact.'));
}
if (empty($data['user'])) {
throw new HTTPException\ForbiddenException(); throw new HTTPException\ForbiddenException();
} }
$this->contact = Model\Contact::getById($data['user']); $this->contact = Model\Contact::getById($ucid);
if ($this->contact['deleted']) { if ($this->contact['deleted']) {
throw new HTTPException\NotFoundException($this->t('Contact is deleted.')); throw new HTTPException\NotFoundException($this->t('Contact is deleted.'));
@ -90,7 +87,7 @@ class Revoke extends BaseModule
DI::sysmsg()->addNotice($this->t('Follow was successfully revoked.')); DI::sysmsg()->addNotice($this->t('Follow was successfully revoked.'));
$this->baseUrl->redirect('contact/' . $this->parameters['id']); $this->baseUrl->redirect('contact/' . ModelContact::getPublicContactId($this->parameters['id'], DI::userSession()->getLocalUserId()));
} }
protected function content(array $request = []): string protected function content(array $request = []): string

View file

@ -168,7 +168,7 @@ class Unfollow extends \Friendica\BaseModule
$this->baseUrl->redirect($base_return_path); $this->baseUrl->redirect($base_return_path);
} }
$return_path = $base_return_path . '/' . $contact['id']; $return_path = $base_return_path . '/' . Contact::getPublicContactId($contact['id'], $uid);
try { try {
Contact::unfollow($contact); Contact::unfollow($contact);

View file

@ -47,10 +47,12 @@ use Friendica\Core\L10n;
use Friendica\Core\PConfig\Capability\IManagePersonalConfigValues; use Friendica\Core\PConfig\Capability\IManagePersonalConfigValues;
use Friendica\Core\Renderer; use Friendica\Core\Renderer;
use Friendica\Core\Session\Capability\IHandleUserSessions; use Friendica\Core\Session\Capability\IHandleUserSessions;
use Friendica\Core\Worker;
use Friendica\Database\DBA; use Friendica\Database\DBA;
use Friendica\Database\Database; use Friendica\Database\Database;
use Friendica\Model\Contact; use Friendica\Model\Contact;
use Friendica\Model\Circle; use Friendica\Model\Circle;
use Friendica\Model\Post;
use Friendica\Model\Profile; use Friendica\Model\Profile;
use Friendica\Module\Response; use Friendica\Module\Response;
use Friendica\Module\Security\Login; use Friendica\Module\Security\Login;
@ -231,7 +233,7 @@ class Network extends Timeline
} else { } else {
$items = $this->getItems(); $items = $this->getItems();
} }
$o .= $this->conversation->render($items, Conversation::MODE_NETWORK, false, false, $this->getOrder(), $this->session->getLocalUserId()); $o .= $this->conversation->render($items, Conversation::MODE_NETWORK, false, false, $this->getOrder(), $this->session->getLocalUserId());
} catch (\Exception $e) { } catch (\Exception $e) {
$o .= $this->l10n->t('Error %d (%s) while fetching the timeline.', $e->getCode(), $e->getMessage()); $o .= $this->l10n->t('Error %d (%s) while fetching the timeline.', $e->getCode(), $e->getMessage());
@ -470,23 +472,20 @@ class Network extends Timeline
$items = array_reverse($items); $items = array_reverse($items);
} }
if ($this->database->isResult($items)) { if ($this->ping || !$this->database->isResult($items)) {
$parents = array_column($items, 'uri-id'); return $items;
} else {
$parents = [];
} }
// We aren't going to try and figure out at the item, circle, and page $this->setItemsSeenByCondition(['unseen' => true, 'uid' => $this->session->getLocalUserId(), 'parent-uri-id' => array_column($items, 'uri-id')]);
// level which items you've seen and which you haven't. If you're looking
// at the top level network page just mark everything seen. $posts = Post::selectToArray(['uri-id'], ['unseen' => true, 'uid' => $this->session->getLocalUserId()], ['limit' => 100]);
if (!$this->circleId && !$this->star && !$this->mention) { if (!empty($posts)) {
$condition = ['unseen' => true, 'uid' => $this->session->getLocalUserId()]; $this->setItemsSeenByCondition(['unseen' => true, 'uid' => $this->session->getLocalUserId(), 'uri-id' => array_column($posts, 'uri-id')]);
$this->setItemsSeenByCondition($condition);
} elseif (!empty($parents)) {
$condition = ['unseen' => true, 'uid' => $this->session->getLocalUserId(), 'parent-uri-id' => $parents];
$this->setItemsSeenByCondition($condition);
} }
if (count($posts) == 100) {
Worker::add(Worker::PRIORITY_MEDIUM, 'SetSeen', $this->session->getLocalUserId());
}
return $items; return $items;
} }

View file

@ -22,8 +22,10 @@
namespace Friendica\Module\DFRN; namespace Friendica\Module\DFRN;
use Friendica\BaseModule; use Friendica\BaseModule;
use Friendica\Core\Protocol;
use Friendica\Model\Contact; use Friendica\Model\Contact;
use Friendica\Model\Conversation; use Friendica\Model\Conversation;
use Friendica\Model\Item;
use Friendica\Model\User; use Friendica\Model\User;
use Friendica\Module\Response; use Friendica\Module\Response;
use Friendica\Network\HTTPException; use Friendica\Network\HTTPException;
@ -45,6 +47,8 @@ class Notify extends BaseModule
throw new HTTPException\BadRequestException(); throw new HTTPException\BadRequestException();
} }
Item::incrementInbound(Protocol::DFRN);
$data = json_decode($postdata); $data = json_decode($postdata);
if (is_object($data) && !empty($this->parameters['nickname'])) { if (is_object($data) && !empty($this->parameters['nickname'])) {
$user = User::getByNickname($this->parameters['nickname']); $user = User::getByNickname($this->parameters['nickname']);

View file

@ -29,6 +29,12 @@ use Friendica\Util\JsonLD;
class ActivityPubConversion extends BaseModule class ActivityPubConversion extends BaseModule
{ {
protected function post(array $request = [])
{
// @todo check if POST is really used here
$this->content($request);
}
protected function content(array $request = []): string protected function content(array $request = []): string
{ {
function visible_whitespace($s) function visible_whitespace($s)

View file

@ -35,6 +35,12 @@ use Friendica\Util\XML;
*/ */
class Babel extends BaseModule class Babel extends BaseModule
{ {
protected function post(array $request = [])
{
// @todo check if POST is really used here
$this->content($request);
}
protected function content(array $request = []): string protected function content(array $request = []): string
{ {
function visible_whitespace($s) function visible_whitespace($s)

View file

@ -25,6 +25,8 @@ use Friendica\App;
use Friendica\BaseModule; use Friendica\BaseModule;
use Friendica\Core\Config\Capability\IManageConfigValues; use Friendica\Core\Config\Capability\IManageConfigValues;
use Friendica\Core\L10n; use Friendica\Core\L10n;
use Friendica\Core\Protocol;
use Friendica\Model\Item;
use Friendica\Model\User; use Friendica\Model\User;
use Friendica\Module\Response; use Friendica\Module\Response;
use Friendica\Network\HTTPException; use Friendica\Network\HTTPException;
@ -57,6 +59,8 @@ class Receive extends BaseModule
throw new HTTPException\ForbiddenException($this->t('Access denied.')); throw new HTTPException\ForbiddenException($this->t('Access denied.'));
} }
Item::incrementInbound(Protocol::DIASPORA);
if ($this->parameters['type'] === 'public') { if ($this->parameters['type'] === 'public') {
$this->receivePublic(); $this->receivePublic();
} else if ($this->parameters['type'] === 'users') { } else if ($this->parameters['type'] === 'users') {

View file

@ -22,6 +22,8 @@
namespace Friendica\Module; namespace Friendica\Module;
use Friendica\BaseModule; use Friendica\BaseModule;
use Friendica\Core\Protocol;
use Friendica\Model\Item;
use Friendica\Model\User; use Friendica\Model\User;
use Friendica\Network\HTTPException; use Friendica\Network\HTTPException;
use Friendica\Protocol\Feed as ProtocolFeed; use Friendica\Protocol\Feed as ProtocolFeed;
@ -68,6 +70,8 @@ class Feed extends BaseModule
throw new HTTPException\UnauthorizedException($this->t('Access to this profile has been restricted.')); throw new HTTPException\UnauthorizedException($this->t('Access to this profile has been restricted.'));
} }
Item::incrementOutbound(Protocol::FEED);
$feed = ProtocolFeed::atom($owner, $last_update, 10, $type); $feed = ProtocolFeed::atom($owner, $last_update, 10, $type);
$this->httpExit($feed, Response::TYPE_ATOM); $this->httpExit($feed, Response::TYPE_ATOM);

View file

@ -36,7 +36,7 @@ use Friendica\Protocol\Diaspora;
*/ */
class Activity extends BaseModule class Activity extends BaseModule
{ {
protected function rawContent(array $request = []) protected function post(array $request = [])
{ {
if (!DI::userSession()->isAuthenticated()) { if (!DI::userSession()->isAuthenticated()) {
throw new HTTPException\ForbiddenException(); throw new HTTPException\ForbiddenException();

View file

@ -33,7 +33,7 @@ use Friendica\Network\HTTPException;
*/ */
class Follow extends BaseModule class Follow extends BaseModule
{ {
protected function rawContent(array $request = []) protected function post(array $request = [])
{ {
$l10n = DI::l10n(); $l10n = DI::l10n();

View file

@ -33,7 +33,7 @@ use Friendica\Network\HTTPException;
*/ */
class Ignore extends BaseModule class Ignore extends BaseModule
{ {
protected function rawContent(array $request = []) protected function post(array $request = [])
{ {
$l10n = DI::l10n(); $l10n = DI::l10n();

View file

@ -33,7 +33,7 @@ use Friendica\Network\HTTPException;
*/ */
class Pin extends BaseModule class Pin extends BaseModule
{ {
protected function rawContent(array $request = []) protected function post(array $request = [])
{ {
$l10n = DI::l10n(); $l10n = DI::l10n();

View file

@ -34,7 +34,7 @@ use Friendica\Network\HTTPException;
*/ */
class Star extends BaseModule class Star extends BaseModule
{ {
protected function rawContent(array $request = []) protected function post(array $request = [])
{ {
$l10n = DI::l10n(); $l10n = DI::l10n();

View file

@ -106,7 +106,7 @@ class Upload extends \Friendica\BaseModule
$this->return(401, $msg); $this->return(401, $msg);
} }
$newid = Attach::storeFile($tempFileName, $owner['uid'], $fileName, '<' . $owner['id'] . '>'); $newid = Attach::storeFile($tempFileName, $owner['uid'], $fileName, $_FILES['userfile']['type'] ?? '', '<' . $owner['id'] . '>');
@unlink($tempFileName); @unlink($tempFileName);

View file

@ -45,6 +45,12 @@ class Source extends BaseModeration
$this->config = $config; $this->config = $config;
} }
protected function post(array $request = [])
{
// @todo check if POST is really used here
$this->content($request);
}
protected function content(array $request = []): string protected function content(array $request = []): string
{ {
parent::content(); parent::content();

View file

@ -48,6 +48,12 @@ class Reports extends BaseModeration
$this->database = $database; $this->database = $database;
} }
protected function post(array $request = [])
{
// @todo check if POST is really used here
$this->content($request);
}
protected function content(array $request = []): string protected function content(array $request = []): string
{ {
parent::content(); parent::content();

View file

@ -56,10 +56,11 @@ class NodeInfo120 extends BaseModule
], ],
'protocols' => ['dfrn', 'activitypub'], 'protocols' => ['dfrn', 'activitypub'],
'services' => Nodeinfo::getServices(), 'services' => Nodeinfo::getServices(),
'usage' => Nodeinfo::getUsage(),
'openRegistrations' => Register::getPolicy() !== Register::CLOSED, 'openRegistrations' => Register::getPolicy() !== Register::CLOSED,
'usage' => Nodeinfo::getUsage(),
'metadata' => [ 'metadata' => [
'nodeName' => $this->config->get('config', 'sitename'), 'nodeName' => $this->config->get('config', 'sitename'),
'nodeDescription' => $this->config->get('config', 'info'),
], ],
]; ];

View file

@ -0,0 +1,90 @@
<?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\Module;
use Friendica\App;
use Friendica\BaseModule;
use Friendica\Capabilities\ICanCreateResponses;
use Friendica\Core\Config\Capability\IManageConfigValues;
use Friendica\Core\L10n;
use Friendica\Model\Nodeinfo;
use Friendica\Util\Profiler;
use Psr\Log\LoggerInterface;
/**
* Version 2.1 of Nodeinfo, a standardized way of exposing metadata about a server running one of the distributed social networks.
* @see https://github.com/jhass/nodeinfo/blob/master/PROTOCOL.md
*/
class NodeInfo121 extends BaseModule
{
/** @var IManageConfigValues */
protected $config;
public function __construct(L10n $l10n, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, IManageConfigValues $config, array $server, array $parameters = [])
{
parent::__construct($l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters);
$this->config = $config;
}
protected function rawContent(array $request = [])
{
$nodeinfo = [
'version' => '2.1',
'software' => [
'name' => 'friendica',
'version' => App::VERSION . '-' . DB_UPDATE_VERSION,
'repository' => 'https://github.com/friendica/friendica',
'homepage' => 'https://friendi.ca',
],
'protocols' => ['dfrn', 'activitypub'],
'services' => Nodeinfo::getServices(),
'openRegistrations' => Register::getPolicy() !== Register::CLOSED,
'usage' => Nodeinfo::getUsage(),
'metadata' => [
'nodeName' => $this->config->get('config', 'sitename'),
'nodeDescription' => $this->config->get('config', 'info'),
],
];
if (!empty($this->config->get('system', 'diaspora_enabled'))) {
$nodeinfo['protocols'][] = 'diaspora';
}
if (empty($this->config->get('system', 'ostatus_disabled'))) {
$nodeinfo['protocols'][] = 'ostatus';
}
$nodeinfo['services']['inbound'][] = 'atom1.0';
$nodeinfo['services']['inbound'][] = 'rss2.0';
$nodeinfo['services']['outbound'][] = 'atom1.0';
if (function_exists('imap_open') && !$this->config->get('system', 'imap_disabled')) {
$nodeinfo['services']['inbound'][] = 'imap';
}
$nodeinfo['metadata']['explicitContent'] = $this->config->get('system', 'explicit_content', false) == true;
$this->response->setType(ICanCreateResponses::TYPE_JSON, 'application/json; charset=utf-8');
$this->response->addContent(json_encode($nodeinfo, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
}
}

View file

@ -0,0 +1,91 @@
<?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\Module;
use Friendica\App;
use Friendica\BaseModule;
use Friendica\Capabilities\ICanCreateResponses;
use Friendica\Core\Config\Capability\IManageConfigValues;
use Friendica\Core\L10n;
use Friendica\Model\Nodeinfo;
use Friendica\Util\Profiler;
use Psr\Log\LoggerInterface;
/**
* Version 2.2 of Nodeinfo, a standardized way of exposing metadata about a server running one of the distributed social networks.
* @see https://github.com/jhass/nodeinfo/blob/master/PROTOCOL.md
*/
class NodeInfo122 extends BaseModule
{
/** @var IManageConfigValues */
protected $config;
public function __construct(L10n $l10n, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, IManageConfigValues $config, array $server, array $parameters = [])
{
parent::__construct($l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters);
$this->config = $config;
}
protected function rawContent(array $request = [])
{
$nodeinfo = [
'version' => '2.2',
'instance' => [
'name' => $this->config->get('config', 'sitename'),
'description' => $this->config->get('config', 'info'),
],
'software' => [
'name' => 'friendica',
'version' => App::VERSION . '-' . DB_UPDATE_VERSION,
'repository' => 'https://github.com/friendica/friendica',
'homepage' => 'https://friendi.ca',
],
'protocols' => ['dfrn', 'activitypub'],
'services' => Nodeinfo::getServices(),
'openRegistrations' => Register::getPolicy() !== Register::CLOSED,
'usage' => Nodeinfo::getUsage(),
'metadata' => [],
];
if (!empty($this->config->get('system', 'diaspora_enabled'))) {
$nodeinfo['protocols'][] = 'diaspora';
}
if (empty($this->config->get('system', 'ostatus_disabled'))) {
$nodeinfo['protocols'][] = 'ostatus';
}
$nodeinfo['services']['inbound'][] = 'atom1.0';
$nodeinfo['services']['inbound'][] = 'rss2.0';
$nodeinfo['services']['outbound'][] = 'atom1.0';
if (function_exists('imap_open') && !$this->config->get('system', 'imap_disabled')) {
$nodeinfo['services']['inbound'][] = 'imap';
}
$nodeinfo['metadata']['explicitContent'] = $this->config->get('system', 'explicit_content', false) == true;
$this->response->setType(ICanCreateResponses::TYPE_JSON, 'application/json; charset=utf-8');
$this->response->addContent(json_encode($nodeinfo, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
}
}

View file

@ -32,7 +32,7 @@ use Psr\Log\LoggerInterface;
/** /**
* Version 1.0 of Nodeinfo 2, a sStandardized way of exposing metadata about a server running one of the distributed social networks. * Version 1.0 of Nodeinfo 2, a sStandardized way of exposing metadata about a server running one of the distributed social networks.
* @see https://github.com/jhass/nodeinfo/blob/master/PROTOCOL.md * @see https://github.com/jaywink/nodeinfo2/blob/master/PROTOCOL.md
*/ */
class NodeInfo210 extends BaseModule class NodeInfo210 extends BaseModule
{ {

View file

@ -77,6 +77,12 @@ class Introductions extends BaseNotifications
]; ];
} }
protected function post(array $request = [])
{
// @todo check if POST is really used here
$this->content($request);
}
protected function content(array $request = []): string protected function content(array $request = []): string
{ {
Nav::setSelected('introductions'); Nav::setSelected('introductions');

View file

@ -96,6 +96,12 @@ class Notifications extends BaseNotifications
]; ];
} }
protected function post(array $request = [])
{
// @todo check if POST is really used here
$this->content($request);
}
protected function content(array $request = []): string protected function content(array $request = []): string
{ {
Nav::setSelected('notifications'); Nav::setSelected('notifications');

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