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

View file

@ -1 +1 @@
2024.06-dev
2024.08

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -24,6 +24,7 @@ namespace Friendica;
use Exception;
use Friendica\App\Arguments;
use Friendica\App\BaseURL;
use Friendica\App\Request;
use Friendica\Capabilities\ICanCreateResponses;
use Friendica\Content\Nav;
use Friendica\Core\Config\Factory\Config;
@ -64,7 +65,7 @@ class App
{
const PLATFORM = 'Friendica';
const CODENAME = 'Yellow Archangel';
const VERSION = '2024.06-dev';
const VERSION = '2024.08';
// Allow themes to control internal parameters
// by changing App values in theme.php
@ -93,6 +94,9 @@ class App
/** @var string The name of the current mobile theme */
private $currentMobileTheme;
/** @var string */
private $requestId;
/** @var Authentication */
private $auth;
@ -281,8 +285,9 @@ class App
* @param DbaDefinition $dbaDefinition
* @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->database = $database;
$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->logSlowCalls(microtime(true) - $request_start, $response->getStatusCode(), $requeststring, $server['HTTP_USER_AGENT'] ?? '');
System::echoResponse($response);
} catch (HTTPException $e) {
$this->logger->debug('Request processed with exception', ['response' => $e->getCode(), 'address' => $server['REMOTE_ADDR'] ?? '', 'request' => $requeststring, 'referer' => $server['HTTP_REFERER'] ?? '', 'user-agent' => $server['HTTP_USER_AGENT'] ?? '', 'duration' => number_format(microtime(true) - $request_start, 3)]);
$this->logSlowCalls(microtime(true) - $request_start, $e->getCode(), $requeststring, $server['HTTP_USER_AGENT'] ?? '');
$httpException->rawContent($e);
}
$page->logRuntime($this->config, 'runFrontend');
@ -707,4 +714,30 @@ class App
$this->baseURL->redirect($toUrl);
}
}
/**
* Log slow page executions
*
* @param float $duration
* @param integer $code
* @param string $request
* @param string $agent
* @return void
*/
private function logSlowCalls(float $duration, int $code, string $request, string $agent)
{
$logfile = $this->config->get('system', 'page_execution_logfile');
$loglimit = $this->config->get('system', 'page_execution_log_limit');
if (empty($logfile) || empty($loglimit) || ($duration < $loglimit)) {
return;
}
@file_put_contents(
$logfile,
DateTimeFormat::utcNow() . "\t" . round($duration, 3) . "\t" .
$this->requestId . "\t" . $code . "\t" .
$request . "\t" . $agent . "\n",
FILE_APPEND
);
}
}

View file

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

View file

@ -0,0 +1,119 @@
<?php
/**
* @copyright Copyright (C) 2010-2024, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
namespace Friendica\Console;
use Friendica\App\BaseURL;
use Friendica\Contact\Avatar;
use Friendica\Core\L10n;
use Friendica\Model\Contact;
use Friendica\Core\Config\Capability\IManageConfigValues;
/**
* tool to clear the avatar file cache.
*/
class ClearAvatarCache extends \Asika\SimpleConsole\Console
{
protected $helpOptions = ['h', 'help', '?'];
/**
* @var $dba Friendica\Database\Database
*/
private $dba;
/**
* @var $baseurl Friendica\App\BaseURL
*/
private $baseUrl;
/**
* @var L10n
*/
private $l10n;
/**
* @var IManageConfigValues
*/
private $config;
protected function getHelp()
{
$help = <<<HELP
console clearavatarcache - Clear the file based avatar cache
Synopsis
bin/console clearavatarcache
Description
bin/console clearavatarcache
Clear the file based avatar cache
Options
-h|--help|-? Show help information
HELP;
return $help;
}
public function __construct(\Friendica\Database\Database $dba, BaseURL $baseUrl, L10n $l10n, IManageConfigValues $config, array $argv = null)
{
parent::__construct($argv);
$this->dba = $dba;
$this->baseUrl = $baseUrl;
$this->l10n = $l10n;
$this->config = $config;
}
protected function doExecute(): int
{
if ($this->config->get('system', 'avatar_cache')) {
$this->err($this->l10n->t('The avatar cache needs to be disabled in local.config.php to use this command.'));
return 2;
}
// Contacts (but not self contacts) with cached avatars.
$condition = ["NOT `self` AND (`photo` != ? OR `thumb` != ? OR `micro` != ?)", '', '', ''];
$total = $this->dba->count('contact', $condition);
$count = 0;
$contacts = $this->dba->select('contact', ['id', 'uri-id', 'url', 'uid', 'photo', 'thumb', 'micro'], $condition);
while ($contact = $this->dba->fetch($contacts)) {
if (Avatar::deleteCache($contact) || $this->isAvatarCache($contact)) {
Contact::update(['photo' => '', 'thumb' => '', 'micro' => ''], ['id' => $contact['id']]);
}
$this->out(++$count . '/' . $total . "\t" . $contact['id'] . "\t" . $contact['url'] . "\t" . $contact['photo']);
}
$this->dba->close($contacts);
return 0;
}
private function isAvatarCache(array $contact): bool
{
if (!empty($contact['photo']) && strpos($contact['photo'], Avatar::baseUrl()) === 0) {
return true;
}
if (!empty($contact['thumb']) && strpos($contact['thumb'], Avatar::baseUrl()) === 0) {
return true;
}
if (!empty($contact['micro']) && strpos($contact['micro'], Avatar::baseUrl()) === 0) {
return true;
}
return false;
}
}

View file

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

View file

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

View file

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

View file

@ -547,9 +547,9 @@ class Item
$item['private'] = $private_group ? ItemModel::PRIVATE : ItemModel::UNLISTED;
if ($only_to_group) {
$cdata = Contact::getPublicAndUserContactID($group_contact['id'], $item['uid']);
if (!empty($cdata['user'])) {
$item['owner-id'] = $cdata['user'];
$pcid = Contact::getPublicContactId($group_contact['id'], $item['uid']);
if ($pcid) {
$item['owner-id'] = $pcid;
unset($item['owner-link']);
unset($item['owner-name']);
unset($item['owner-avatar']);
@ -1032,7 +1032,7 @@ class Item
}
$this->emailer->send(new ItemCCEMail(
$this->app,
$this->userSession,
$this->l10n,
$this->baseURL,
$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.
$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) {
$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);
});
@ -2090,7 +2090,7 @@ class BBCode
$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])) {
$text = self::shortenLinkDescription($text);
$text = self::shortenLinkDescription($text, $simple_html);
} else {
$text = self::unifyLinks($text);
}
@ -2124,7 +2124,12 @@ class BBCode
}
$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
@ -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(
"/\[url\](.*?)\[\/url\]/ism",
function ($match) {
return "[url=" . self::escapeUrl($match[1]) . "]" . Strings::getStyledURL($match[1]) . "[/url]";
function ($match) use ($max_length) {
return "[url=" . self::escapeUrl($match[1]) . "]" . Strings::getStyledURL($match[1], $max_length) . "[/url]";
},
$text
);
$text = preg_replace_callback(
"/\[url\=(.*?)\](.*?)\[\/url\]/ism",
function ($match) {
function ($match) use ($max_length) {
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 {
return "[url=" . self::escapeUrl($match[1]) . "]" . $match[2] . "[/url]";
}
@ -2641,7 +2652,7 @@ class BBCode
$bbcode = "\n" . '[audio]' . $url . '[/audio]' . "\n";
break;
default:
$bbcode = "\n" . '[img]' . $url . '[/img]' . "\n";
$bbcode = "\n" . '[img=' . $url . '][/img]' . "\n";
break;
}

View file

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

View file

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

View file

@ -61,7 +61,7 @@ class SavedSearches
'$add' => '',
'$searchbox' => '',
'$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:
addon Addon management
cache Manage node cache
clearavatarcache Clear the file based avatar cache
config Edit site config
contact Contact management
createdoxygen Generate Doxygen headers
@ -84,6 +85,7 @@ HELP;
'archivecontact' => Friendica\Console\ArchiveContact::class,
'autoinstall' => Friendica\Console\AutomaticInstallation::class,
'cache' => Friendica\Console\Cache::class,
'clearavatarcache' => Friendica\Console\ClearAvatarCache::class,
'config' => Friendica\Console\Config::class,
'contact' => Friendica\Console\Contact::class,
'createdoxygen' => Friendica\Console\CreateDoxygen::class,

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -245,6 +245,7 @@ class Attach
* @param string $src Source file name
* @param int $uid User id
* @param string $filename Optional file name
* @param string $filetype Optional file type
* @param string $allow_cid
* @param string $allow_gid
* @param string $deny_cid
@ -252,7 +253,7 @@ class Attach
* @return boolean|int Insert id or false on failure
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function storeFile(string $src, int $uid, string $filename = '', string $allow_cid = '', string $allow_gid = '', string $deny_cid = '', string $deny_gid = '')
public static function storeFile(string $src, int $uid, string $filename = '', string $filetype = '', string $allow_cid = '', string $allow_gid = '', string $deny_cid = '', string $deny_gid = '')
{
if ($filename === '') {
$filename = basename($src);
@ -260,7 +261,7 @@ class Attach
$data = @file_get_contents($src);
return self::store($data, $uid, $filename, '', null, $allow_cid, $allow_gid, $deny_cid, $deny_gid);
return self::store($data, $uid, $filename, $filetype, null, $allow_cid, $allow_gid, $deny_cid, $deny_gid);
}
@ -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
{
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.');
}
$cdata = Contact::getPublicAndUserContactID($cid, $circle['uid']);
if (empty($cdata['user'])) {
$ucid = Contact::getUserContactId($cid, $circle['uid']);
if (!$ucid) {
throw new HTTPException\NotFoundException('Invalid contact.');
}
return DBA::insert('group_member', ['gid' => $gid, 'contact-id' => $cdata['user']], Database::INSERT_IGNORE);
return DBA::insert('group_member', ['gid' => $gid, 'contact-id' => $ucid], Database::INSERT_IGNORE);
}
/**
@ -318,12 +318,12 @@ class Circle
throw new HTTPException\NotFoundException('Circle not found.');
}
$cdata = Contact::getPublicAndUserContactID($cid, $circle['uid']);
if (empty($cdata['user'])) {
$ucid = Contact::getUserContactId($cid, $circle['uid']);
if (!$ucid) {
throw new HTTPException\NotFoundException('Invalid contact.');
}
return DBA::delete('group_member', ['gid' => $gid, 'contact-id' => $cid]);
return DBA::delete('group_member', ['gid' => $gid, 'contact-id' => $ucid]);
}
/**
@ -347,12 +347,12 @@ class Circle
}
foreach ($contacts as $cid) {
$cdata = Contact::getPublicAndUserContactID($cid, $circle['uid']);
if (empty($cdata['user'])) {
$ucid = Contact::getUserContactId($cid, $circle['uid']);
if (!$ucid) {
throw new HTTPException\NotFoundException('Invalid contact.');
}
DBA::insert('group_member', ['gid' => $gid, 'contact-id' => $cdata['user']], Database::INSERT_IGNORE);
DBA::insert('group_member', ['gid' => $gid, 'contact-id' => $ucid], Database::INSERT_IGNORE);
}
}
@ -379,12 +379,12 @@ class Circle
$contactIds = [];
foreach ($contacts as $cid) {
$cdata = Contact::getPublicAndUserContactID($cid, $circle['uid']);
if (empty($cdata['user'])) {
$ucid = Contact::getUserContactId($cid, $circle['uid']);
if (!$ucid) {
throw new HTTPException\NotFoundException('Invalid contact.');
}
$contactIds[] = $cdata['user'];
$contactIds[] = $ucid;
}
// Return status of deletion

View file

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

View file

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

View file

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

View file

@ -198,6 +198,10 @@ class Item
$fields['external-id'] = ItemURI::getIdByURI($fields['extid']);
}
if (!empty($fields['replies'])) {
$fields['replies-id'] = ItemURI::getIdByURI($fields['replies']);
}
if (!empty($fields['verb'])) {
$fields['vid'] = Verb::getID($fields['verb']);
}
@ -415,8 +419,24 @@ class Item
self::markForDeletion(['parent' => $item['parent'], 'deleted' => false], $priority);
}
if ($item['uid'] == 0 && $item['gravity'] == self::GRAVITY_PARENT) {
$posts = DI::keyValue()->get('nodeinfo_total_posts') ?? 0;
DI::keyValue()->set('nodeinfo_total_posts', $posts - 1);
} elseif ($item['uid'] == 0 && $item['gravity'] == self::GRAVITY_COMMENT) {
$comments = DI::keyValue()->get('nodeinfo_total_comments') ?? 0;
DI::keyValue()->set('nodeinfo_total_comments', $comments - 1);
}
// Is it our comment and/or our thread?
if (($item['origin'] || $parent['origin']) && ($item['uid'] != 0)) {
if ($item['origin'] && $item['gravity'] == self::GRAVITY_PARENT) {
$posts = DI::keyValue()->get('nodeinfo_local_posts') ?? 0;
DI::keyValue()->set('nodeinfo_local_posts', $posts - 1);
} elseif ($item['origin'] && $item['gravity'] == self::GRAVITY_COMMENT) {
$comments = DI::keyValue()->get('nodeinfo_local_comments') ?? 0;
DI::keyValue()->set('nodeinfo_local_comments', $comments - 1);
}
// When we delete the original post we will delete all existing copies on the server as well
self::markForDeletion(['uri-id' => $item['uri-id'], 'deleted' => false], $priority);
@ -536,9 +556,9 @@ class Item
}
if (!empty($item['causer-id']) && Contact::isSharing($item['causer-id'], $item['uid'], true)) {
$cdata = Contact::getPublicAndUserContactID($item['causer-id'], $item['uid']);
if (!empty($cdata['user'])) {
return $cdata['user'];
$ucid = Contact::getUserContactId($item['causer-id'], $item['uid']);
if ($ucid) {
return $ucid;
}
}
@ -1077,6 +1097,10 @@ class Item
$parent_id = 0;
$parent_origin = $item['origin'];
if ($item['wall'] && empty($item['context'])) {
$item['context'] = $item['parent-uri'] . '#context';
}
if ($item['wall'] && empty($item['conversation'])) {
$item['conversation'] = $item['parent-uri'] . '#context';
}
@ -1098,6 +1122,10 @@ class Item
$item['conversation-id'] = ItemURI::getIdByURI($item['conversation']);
}
if (!empty($item['context']) && empty($item['context-id'])) {
$item['context-id'] = ItemURI::getIdByURI($item['context']);
}
// Is this item available in the global items (with uid=0)?
if ($item['uid'] == 0) {
$item['global'] = true;
@ -1165,6 +1193,10 @@ class Item
$item['external-id'] = ItemURI::getIdByURI($item['extid']);
}
if (!empty($item['replies'])) {
$item['replies-id'] = ItemURI::getIdByURI($item['replies']);
}
if ($item['verb'] == Activity::ANNOUNCE) {
self::setOwnerforResharedItem($item);
}
@ -1334,6 +1366,14 @@ class Item
return 0;
}
if ($posted_item['origin'] && $posted_item['gravity'] == self::GRAVITY_PARENT) {
$posts = (int)(DI::keyValue()->get('nodeinfo_local_posts') ?? 0);
DI::keyValue()->set('nodeinfo_local_posts', $posts + 1);
} elseif ($posted_item['origin'] && $posted_item['gravity'] == self::GRAVITY_COMMENT) {
$comments = (int)(DI::keyValue()->get('nodeinfo_local_comments') ?? 0);
DI::keyValue()->set('nodeinfo_local_comments', $comments + 1);
}
Post\Origin::insert($posted_item);
// update the commented timestamp on the parent
@ -1423,6 +1463,14 @@ class Item
}
if ($inserted) {
if ($posted_item['gravity'] == self::GRAVITY_PARENT) {
$posts = (int)(DI::keyValue()->get('nodeinfo_total_posts') ?? 0);
DI::keyValue()->set('nodeinfo_total_posts', $posts + 1);
} elseif ($posted_item['gravity'] == self::GRAVITY_COMMENT) {
$comments = (int)(DI::keyValue()->get('nodeinfo_total_comments') ?? 0);
DI::keyValue()->set('nodeinfo_total_comments', $comments + 1);
}
// Fill the cache with the rendered content.
if (in_array($posted_item['gravity'], [self::GRAVITY_PARENT, self::GRAVITY_COMMENT])) {
self::updateDisplayCache($posted_item['uri-id']);
@ -2584,12 +2632,12 @@ class Item
return;
}
$cdata = Contact::getPublicAndUserContactID($item['author-id'], $item['uid']);
if (empty($cdata['user']) || ($cdata['user'] != $item['contact-id'])) {
$ucid = Contact::getUserContactId($item['author-id'], $item['uid']);
if (!$ucid || ($ucid != $item['contact-id'])) {
return;
}
if (!DBA::exists('contact', ['id' => $cdata['user'], 'remote_self' => LocalRelationship::MIRROR_NATIVE_RESHARE])) {
if (!DBA::exists('contact', ['id' => $ucid, 'remote_self' => LocalRelationship::MIRROR_NATIVE_RESHARE])) {
return;
}
@ -4118,6 +4166,10 @@ class Item
return $item_id;
}
if (ActivityPub\Processor::alreadyKnown($uri, '')) {
return 0;
}
$hookData = [
'uri' => $uri,
'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]);
return 0;
}
public static function incrementInbound(string $network)
{
$packets = (int)(DI::keyValue()->get('stats_packets_inbound_' . $network) ?? 0);
if ($packets >= PHP_INT_MAX) {
$packets = 0;
}
DI::keyValue()->set('stats_packets_inbound_' . $network, $packets + 1);
}
public static function incrementOutbound(string $network)
{
$packets = (int)(DI::keyValue()->get('stats_packets_outbound_' . $network) ?? 0);
if ($packets >= PHP_INT_MAX) {
$packets = 0;
}
DI::keyValue()->set('stats_packets_outbound_' . $network, $packets + 1);
}
}

View file

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

View file

@ -141,7 +141,7 @@ class Content
if ($uid != 0) {
$condition = ["MATCH (`searchtext`) AGAINST (? IN BOOLEAN MODE) AND (NOT `restricted` OR `uri-id` IN (SELECT `uri-id` FROM `post-user` WHERE `uid` = ?))", $search, $uid];
} else {
$condition = ["MATCH (`searchtext`) AGAINST (? IN BOOLEAN MODE) AND NOT `restricted", $search];
$condition = ["MATCH (`searchtext`) AGAINST (? IN BOOLEAN MODE) AND NOT `restricted`", $search];
}
return DBA::count(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
// and embedded as picture then we only store the picture or replace the document
$found = DBA::selectFirst('post-media', ['type'], ['uri-id' => $media['uri-id'], 'url' => $media['url']]);
if (!$force && !empty($found) && (($found['type'] != self::DOCUMENT) || ($media['type'] == self::DOCUMENT))) {
Logger::info('Media already exists', ['uri-id' => $media['uri-id'], 'url' => $media['url']]);
if (!$force && !empty($found) && (!in_array($found['type'], [self::UNKNOWN, self::DOCUMENT]) || ($media['type'] == self::DOCUMENT))) {
Logger::info('Media already exists', ['uri-id' => $media['uri-id'], 'url' => $media['url'], 'found' => $found['type'], 'new' => $media['type']]);
return false;
}
@ -444,42 +444,46 @@ class Media
return $data;
}
$type = explode('/', current(explode(';', $data['mimetype'])));
$data['type'] = self::getType($data['mimetype']);
return $data;
}
public static function getType(string $mimeType): int
{
$type = explode('/', current(explode(';', $mimeType)));
if (count($type) < 2) {
Logger::info('Unknown MimeType', ['type' => $type, 'media' => $data]);
$data['type'] = self::UNKNOWN;
return $data;
Logger::info('Unknown MimeType', ['type' => $type, 'media' => $mimeType]);
return self::UNKNOWN;
}
$filetype = strtolower($type[0]);
$subtype = strtolower($type[1]);
if ($filetype == 'image') {
$data['type'] = self::IMAGE;
$type = self::IMAGE;
} elseif ($filetype == 'video') {
$data['type'] = self::VIDEO;
$type = self::VIDEO;
} elseif ($filetype == 'audio') {
$data['type'] = self::AUDIO;
$type = self::AUDIO;
} elseif (($filetype == 'text') && ($subtype == 'html')) {
$data['type'] = self::HTML;
$type = self::HTML;
} elseif (($filetype == 'text') && ($subtype == 'xml')) {
$data['type'] = self::XML;
$type = self::XML;
} elseif (($filetype == 'text') && ($subtype == 'plain')) {
$data['type'] = self::PLAIN;
$type = self::PLAIN;
} elseif ($filetype == 'text') {
$data['type'] = self::TEXT;
$type = self::TEXT;
} elseif (($filetype == 'application') && ($subtype == 'x-bittorrent')) {
$data['type'] = self::TORRENT;
$type = self::TORRENT;
} elseif ($filetype == 'application') {
$data['type'] = self::APPLICATION;
$type = self::APPLICATION;
} else {
$data['type'] = self::UNKNOWN;
Logger::info('Unknown type', ['filetype' => $filetype, 'subtype' => $subtype, 'media' => $data]);
return $data;
$type = self::UNKNOWN;
Logger::info('Unknown type', ['filetype' => $filetype, 'subtype' => $subtype, 'media' => $mimeType]);
}
Logger::debug('Detected type', ['filetype' => $filetype, 'subtype' => $subtype, 'media' => $data]);
return $data;
Logger::debug('Detected type', ['filetype' => $filetype, 'subtype' => $subtype, 'media' => $mimeType]);
return $type;
}
/**

View file

@ -622,8 +622,10 @@ class Profile
$bd_format = DI::l10n()->t('g A l F d'); // 8 AM Friday January 18
$classtoday = '';
$condition = ["`uid` = ? AND `type` != 'birthday' AND `start` < ? AND `start` >= ?",
$uid, DateTimeFormat::utc('now + 7 days'), DateTimeFormat::utc('now - 1 days')];
$condition = [
"`uid` = ? AND `type` != 'birthday' AND `start` < ? AND `start` >= ?",
$uid, DateTimeFormat::utc('now + 7 days'), DateTimeFormat::utc('now - 1 days')
];
$s = DBA::select('event', [], $condition, ['order' => ['start']]);
$r = [];
@ -633,9 +635,11 @@ class Profile
$total = 0;
while ($rr = DBA::fetch($s)) {
$condition = ['parent-uri' => $rr['uri'], 'uid' => $rr['uid'], 'author-id' => $pcid,
$condition = [
'parent-uri' => $rr['uri'], 'uid' => $rr['uid'], 'author-id' => $pcid,
'vid' => [Verb::getID(Activity::ATTEND), Verb::getID(Activity::ATTENDMAYBE)],
'visible' => true, 'deleted' => false];
'visible' => true, 'deleted' => false
];
if (!Post::exists($condition)) {
continue;
}
@ -724,7 +728,8 @@ class Profile
if (!empty($search)) {
$publish = (DI::config()->get('system', 'publish_all') ? '' : "AND `publish` ");
$searchTerm = '%' . $search . '%';
$condition = ["`verified` AND NOT `blocked` AND NOT `account_removed` AND NOT `account_expired`
$condition = [
"`verified` AND NOT `blocked` AND NOT `account_removed` AND NOT `account_expired`
$publish
AND ((`name` LIKE ?) OR
(`nickname` LIKE ?) OR
@ -735,7 +740,8 @@ class Profile
(`pub_keywords` LIKE ?) OR
(`prv_keywords` LIKE ?))",
$searchTerm, $searchTerm, $searchTerm, $searchTerm,
$searchTerm, $searchTerm, $searchTerm, $searchTerm];
$searchTerm, $searchTerm, $searchTerm, $searchTerm
];
} else {
$condition = ['verified' => true, 'blocked' => false, 'account_removed' => false, 'account_expired' => false];
if (!DI::config()->get('system', 'publish_all')) {
@ -838,4 +844,44 @@ class Profile
DBA::delete('profile', ['id' => $profile['id']]);
}
}
/**
* Get "about" field with the added responsible relay contact if appropriate.
*
* @param string $about
* @param integer|null $parent_uid
* @param integer $account_type
* @param string $language
* @return string
*/
public static function addResponsibleRelayContact(string $about = null, int $parent_uid = null, int $account_type, string $language): ?string
{
if (($account_type != User::ACCOUNT_TYPE_RELAY) || empty($parent_uid)) {
return $about;
}
$parent = User::getOwnerDataById($parent_uid);
if (strpos($about, $parent['addr']) || strpos($about, $parent['url'])) {
return $about;
}
$l10n = DI::l10n()->withLang($language);
return $about .= "\n" . $l10n->t('Responsible account: %s', $parent['addr']);
}
/**
* Set "about" field with the added responsible relay contact if appropriate.
*
* @param integer $uid
* @return void
*/
public static function setResponsibleRelayContact(int $uid)
{
$owner = User::getOwnerDataById($uid);
$about = self::addResponsibleRelayContact($owner['about'], $owner['parent-uid'], $owner['account-type'], $owner['language']);
if ($about != $owner['about']) {
self::update(['about' => $about], $uid);
}
}
}

View file

@ -412,6 +412,29 @@ class User
return 0;
}
/**
* Returns the user id of a given contact id
*
* @param int $cid
*
* @return integer user id
* @throws Exception
*/
public static function getIdForContactId(int $cid): int
{
$account = Contact::selectFirstAccountUser(['pid', 'self', 'uid'], ['id' => $cid]);
if (empty($account['pid'])) {
return 0;
}
if ($account['self']) {
return $account['uid'];
}
$self = Contact::selectFirstAccountUser(['uid'], ['pid' => $cid, 'self' => true]);
return $self['uid'] ?? 0;
}
/**
* Get a user based on its email
*

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -45,12 +45,12 @@ class Note extends BaseApi
'comment' => '',
], $request);
$cdata = Contact::getPublicAndUserContactID($this->parameters['id'], $uid);
if (empty($cdata['user'])) {
$ucid = Contact::getUserContactId($this->parameters['id'], $uid);
if (!$ucid) {
$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());
}

View file

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

View file

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

View file

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

View file

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

View file

@ -131,12 +131,19 @@ class InstanceV2 extends BaseApi
return new InstanceEntity\Configuration(
$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\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
{
$email = implode(',', User::getAdminEmailList());

View file

@ -22,8 +22,9 @@
namespace Friendica\Module\Api\Mastodon;
use Friendica\Core\Logger;
use Friendica\Core\System;
use Friendica\DI;
use Friendica\Model\Attach;
use Friendica\Model\Contact;
use Friendica\Model\Photo;
use Friendica\Model\Post;
use Friendica\Module\BaseApi;
@ -51,14 +52,38 @@ class Media extends BaseApi
$this->logAndJsonError(422, $this->errorFactory->UnprocessableEntity());
}
$media = Photo::upload($uid, $_FILES['file'], '', null, null, '', '', $request['description']);
if (empty($media)) {
$this->logAndJsonError(422, $this->errorFactory->UnprocessableEntity());
$type = Post\Media::getType($_FILES['file']['type']);
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 = [])
@ -77,6 +102,10 @@ class Media extends BaseApi
$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]);
if (empty($photo['resource-id'])) {
$media = Post\Media::getById($this->parameters['id']);
@ -108,10 +137,15 @@ class Media extends BaseApi
}
$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 = [
Notification::TYPE_FOLLOW => $request['data']['alerts'][Notification::TYPE_FOLLOW] ?? false,
Notification::TYPE_LIKE => $request['data']['alerts'][Notification::TYPE_LIKE] ?? false,
Notification::TYPE_RESHARE => $request['data']['alerts'][Notification::TYPE_RESHARE] ?? false,
Notification::TYPE_MENTION => $request['data']['alerts'][Notification::TYPE_MENTION] ?? false,
Notification::TYPE_POLL => $request['data']['alerts'][Notification::TYPE_POLL] ?? false,
Notification::TYPE_INTRODUCTION => $request['data']['alerts'][Notification::TYPE_INTRODUCTION] ?? false,
Notification::TYPE_POST => $request['data']['alerts'][Notification::TYPE_POST] ?? false,
Notification::TYPE_FOLLOW => $this->setBoolean($request['data']['alerts'][Notification::TYPE_FOLLOW] ?? false),
Notification::TYPE_LIKE => $this->setBoolean($request['data']['alerts'][Notification::TYPE_LIKE] ?? false),
Notification::TYPE_RESHARE => $this->setBoolean($request['data']['alerts'][Notification::TYPE_RESHARE] ?? false),
Notification::TYPE_MENTION => $this->setBoolean($request['data']['alerts'][Notification::TYPE_MENTION] ?? false),
Notification::TYPE_POLL => $this->setBoolean($request['data']['alerts'][Notification::TYPE_POLL] ?? false),
Notification::TYPE_INTRODUCTION => $this->setBoolean($request['data']['alerts'][Notification::TYPE_INTRODUCTION] ?? false),
Notification::TYPE_POST => $this->setBoolean($request['data']['alerts'][Notification::TYPE_POST] ?? false),
];
$ret = Subscription::update($application['id'], $uid, $fields);
@ -120,6 +120,14 @@ class PushSubscription extends BaseApi
$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
{
$this->checkAllowedScope(self::SCOPE_PUSH);

View file

@ -28,6 +28,7 @@ use Friendica\Core\Protocol;
use Friendica\Core\Worker;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\Attach;
use Friendica\Model\Contact;
use Friendica\Model\Circle;
use Friendica\Model\Item;
@ -397,6 +398,20 @@ class Statuses extends BaseApi
$item['attachments'] = [];
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`
WHERE `resource-id` IN (SELECT `resource-id` FROM `photo` WHERE `id` = ?) AND `photo`.`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']);
$attachment = ['type' => Post\Media::IMAGE, 'mimetype' => $media[0]['type'],
'url' => DI::baseUrl() . '/photo/' . $media[0]['resource-id'] . '-' . $media[0]['scale'] . $ext,
'size' => $media[0]['datasize'],
'name' => $media[0]['filename'] ?: $media[0]['resource-id'],
$attachment = [
'type' => Post\Media::IMAGE,
'mimetype' => $media[0]['type'],
'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'] ?? '',
'width' => $media[0]['width'],
'height' => $media[0]['height']];
'width' => $media[0]['width'],
'height' => $media[0]['height']
];
if (count($media) > 1) {
$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
{
$cdata = Contact::getPublicAndUserContactID((int)substr($this->parameters['id'], 6), $uid);
$cid = $cdata['public'];
$cid = Contact::getPublicContactId((int)substr($this->parameters['id'], 6), $uid);
$condition = ["(`uid` = ? OR (`uid` = ? AND NOT `global`))", 0, $uid];

View file

@ -21,26 +21,47 @@
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\Protocol;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\Item;
use Friendica\Model\Post;
use Friendica\Module\Api\ApiResponse;
use Friendica\Module\BaseApi;
use Friendica\Module\Conversation\Community;
use Friendica\Network\HTTPException;
use Friendica\Object\Api\Mastodon\TimelineOrderByTypes;
use Friendica\Util\Profiler;
use Psr\Log\LoggerInterface;
/**
* @see https://docs.joinmastodon.org/methods/timelines/
*/
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
*/
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();
$request = $this->getRequest([
@ -56,6 +77,10 @@ class PublicTimeline extends BaseApi
'friendica_order' => TimelineOrderByTypes::ID, // Sort order options (defaults to ID)
], $request);
if (!$this->localAllowed() && !$this->globalAllowed()) {
$this->jsonExit([]);
}
$condition = [
'gravity' => [Item::GRAVITY_PARENT, Item::GRAVITY_COMMENT], 'private' => Item::PUBLIC,
'network' => Protocol::FEDERATED, 'author-blocked' => false, 'author-hidden' => false
@ -64,13 +89,13 @@ class PublicTimeline extends BaseApi
$condition = $this->addPagingConditions($request, $condition);
$params = $this->buildOrderAndLimitParams($request);
if ($request['local']) {
if ($request['local'] && $this->localAllowed()) {
$condition = DBA::mergeConditions($condition, ['origin' => true]);
} else {
$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`)"]);
}
@ -113,4 +138,14 @@ class PublicTimeline extends BaseApi
self::setLinkHeader($request['friendica_order'] != TimelineOrderByTypes::ID);
$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;
use Friendica\App;
use Friendica\Core\Config\Capability\IManageConfigValues;
use Friendica\Core\L10n;
use Friendica\Core\Logger;
use Friendica\Core\Protocol;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\Post;
use Friendica\Module\Api\ApiResponse;
use Friendica\Module\BaseApi;
use Friendica\Module\Conversation\Community;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\Profiler;
use Psr\Log\LoggerInterface;
/**
* @see https://docs.joinmastodon.org/methods/trends/#statuses
*/
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
*/
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();
$request = $this->getRequest([

View file

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

View file

@ -54,7 +54,7 @@ class NewDM extends BaseApi
$this->directMessage = $directMessage;
}
protected function rawContent(array $request = [])
protected function post(array $request = [])
{
$this->checkAllowedScope(BaseApi::SCOPE_WRITE);
$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) {
$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);
if (!empty($cid)) {
$cdata = Contact::getPublicAndUserContactID($cid, $uid);
if (!empty($cdata['user'])) {
$condition = DBA::mergeConditions($condition, ["`contact-id` = ?", $cdata['user']]);
$ucid = Contact::getUserContactId($cid, $uid);
if ($ucid) {
$condition = DBA::mergeConditions($condition, ["`contact-id` = ?", $ucid]);
}
}

View file

@ -71,13 +71,13 @@ class Destroy extends ContactEndpoint
}
// Get Contact by given id
$cdata = Contact::getPublicAndUserContactID($contact_id, $uid);
if (!empty($cdata['user'])) {
$ucid = Contact::getUserContactId($contact_id, $uid);
if (!$ucid) {
Logger::notice(BaseApi::LOG_PREFIX . 'Not following contact', ['module' => 'api', 'action' => 'friendships_destroy']);
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();
try {

View file

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

View file

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

View file

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

View file

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

View file

@ -58,7 +58,13 @@ class Contact extends BaseModule
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');
@ -253,7 +259,7 @@ class Contact extends BaseModule
$sql_extra = " AND `archive` AND NOT `blocked` AND NOT `pending`";
break;
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`))";
$sql_values[] = Model\Contact::SHARING;
$sql_values[] = DI::userSession()->getLocalUserId();
@ -459,6 +465,7 @@ class Contact extends BaseModule
'$finding' => $searching ? DI::l10n()->t('Results for: %s', $search) : '',
'$submit' => DI::l10n()->t('Find'),
'$cmd' => DI::args()->getCommand(),
'$parameter' => http_build_query($request),
'$contacts' => $contacts,
'$form_security_token' => BaseModule::getFormSecurityToken('contact_batch_actions'),
'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
// Remove by version 2022.03
$data = Model\Contact::getPublicAndUserContactID(intval($this->parameters['id']), $this->userSession->getLocalUserId());
if (empty($data)) {
$pcid = Model\Contact::getPublicContactId(intval($this->parameters['id']), $this->userSession->getLocalUserId());
if (!$pcid) {
throw new NotFoundException($this->t('Contact not found.'));
}
$contact = Model\Contact::getById($data['public']);
$contact = Model\Contact::getById($pcid);
if (empty($contact)) {
throw new NotFoundException($this->t('Contact not found.'));
}
// 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.'));
}

View file

@ -215,7 +215,7 @@ class Follow extends BaseModule
$this->baseUrl->redirect($returnPath);
} 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.'));

View file

@ -65,7 +65,7 @@ class Media extends BaseModule
$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;
}

View file

@ -73,18 +73,18 @@ class Posts extends BaseModule
// Backward compatibility: Ensure to use the public contact when the user contact is provided
// Remove by version 2022.03
$data = Model\Contact::getPublicAndUserContactID(intval($this->parameters['id']), $this->userSession->getLocalUserId());
if (empty($data)) {
$pcid = Model\Contact::getPublicContactId(intval($this->parameters['id']), $this->userSession->getLocalUserId());
if (!$pcid) {
throw new NotFoundException($this->t('Contact not found.'));
}
$contact = Model\Contact::getById($data['public']);
$contact = Model\Contact::getById($pcid);
if (!DBA::isResult($contact)) {
throw new NotFoundException($this->t('Contact not found.'));
}
// 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.'));
}

View file

@ -91,8 +91,8 @@ class Profile extends BaseModule
// Backward compatibility: The update still needs a user-specific contact ID
// Change to user-contact table check by version 2022.03
$cdata = Contact::getPublicAndUserContactID($contact_id, $this->session->getLocalUserId());
if (empty($cdata['user']) || !$this->db->exists('contact', ['id' => $cdata['user'], 'deleted' => false])) {
$ucid = Contact::getUserContactId($contact_id, $this->session->getLocalUserId());
if (!$ucid || !$this->db->exists('contact', ['id' => $ucid, 'deleted' => false])) {
return;
}
@ -134,14 +134,14 @@ class Profile extends BaseModule
}
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'])) {
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.'));
}
}
@ -164,8 +164,22 @@ class Profile extends BaseModule
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
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.'));
}

View file

@ -30,6 +30,7 @@ use Friendica\Core\Renderer;
use Friendica\Database\Database;
use Friendica\DI;
use Friendica\Model;
use Friendica\Model\Contact as ModelContact;
use Friendica\Module\Contact;
use Friendica\Module\Response;
use Friendica\Module\Security\Login;
@ -58,16 +59,12 @@ class Revoke extends BaseModule
return;
}
$data = Model\Contact::getPublicAndUserContactID($this->parameters['id'], DI::userSession()->getLocalUserId());
if (!$this->dba->isResult($data)) {
throw new HTTPException\NotFoundException($this->t('Unknown contact.'));
}
if (empty($data['user'])) {
$ucid = Model\Contact::getUserContactId($this->parameters['id'], DI::userSession()->getLocalUserId());
if (!$ucid) {
throw new HTTPException\ForbiddenException();
}
$this->contact = Model\Contact::getById($data['user']);
$this->contact = Model\Contact::getById($ucid);
if ($this->contact['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.'));
$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

View file

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

View file

@ -47,10 +47,12 @@ use Friendica\Core\L10n;
use Friendica\Core\PConfig\Capability\IManagePersonalConfigValues;
use Friendica\Core\Renderer;
use Friendica\Core\Session\Capability\IHandleUserSessions;
use Friendica\Core\Worker;
use Friendica\Database\DBA;
use Friendica\Database\Database;
use Friendica\Model\Contact;
use Friendica\Model\Circle;
use Friendica\Model\Post;
use Friendica\Model\Profile;
use Friendica\Module\Response;
use Friendica\Module\Security\Login;
@ -470,23 +472,20 @@ class Network extends Timeline
$items = array_reverse($items);
}
if ($this->database->isResult($items)) {
$parents = array_column($items, 'uri-id');
} else {
$parents = [];
if ($this->ping || !$this->database->isResult($items)) {
return $items;
}
// We aren't going to try and figure out at the item, circle, and page
// 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.
if (!$this->circleId && !$this->star && !$this->mention) {
$condition = ['unseen' => true, 'uid' => $this->session->getLocalUserId()];
$this->setItemsSeenByCondition($condition);
} elseif (!empty($parents)) {
$condition = ['unseen' => true, 'uid' => $this->session->getLocalUserId(), 'parent-uri-id' => $parents];
$this->setItemsSeenByCondition($condition);
$this->setItemsSeenByCondition(['unseen' => true, 'uid' => $this->session->getLocalUserId(), 'parent-uri-id' => array_column($items, 'uri-id')]);
$posts = Post::selectToArray(['uri-id'], ['unseen' => true, 'uid' => $this->session->getLocalUserId()], ['limit' => 100]);
if (!empty($posts)) {
$this->setItemsSeenByCondition(['unseen' => true, 'uid' => $this->session->getLocalUserId(), 'uri-id' => array_column($posts, 'uri-id')]);
}
if (count($posts) == 100) {
Worker::add(Worker::PRIORITY_MEDIUM, 'SetSeen', $this->session->getLocalUserId());
}
return $items;
}

View file

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

View file

@ -29,6 +29,12 @@ use Friendica\Util\JsonLD;
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
{
function visible_whitespace($s)

View file

@ -35,6 +35,12 @@ use Friendica\Util\XML;
*/
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
{
function visible_whitespace($s)

View file

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

View file

@ -22,6 +22,8 @@
namespace Friendica\Module;
use Friendica\BaseModule;
use Friendica\Core\Protocol;
use Friendica\Model\Item;
use Friendica\Model\User;
use Friendica\Network\HTTPException;
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.'));
}
Item::incrementOutbound(Protocol::FEED);
$feed = ProtocolFeed::atom($owner, $last_update, 10, $type);
$this->httpExit($feed, Response::TYPE_ATOM);

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -106,7 +106,7 @@ class Upload extends \Friendica\BaseModule
$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);

View file

@ -45,6 +45,12 @@ class Source extends BaseModeration
$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
{
parent::content();

View file

@ -48,6 +48,12 @@ class Reports extends BaseModeration
$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
{
parent::content();

View file

@ -56,10 +56,11 @@ class NodeInfo120 extends BaseModule
],
'protocols' => ['dfrn', 'activitypub'],
'services' => Nodeinfo::getServices(),
'usage' => Nodeinfo::getUsage(),
'openRegistrations' => Register::getPolicy() !== Register::CLOSED,
'usage' => Nodeinfo::getUsage(),
'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.
* @see https://github.com/jhass/nodeinfo/blob/master/PROTOCOL.md
* @see https://github.com/jaywink/nodeinfo2/blob/master/PROTOCOL.md
*/
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
{
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
{
Nav::setSelected('notifications');

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