Merge branch '2024.06-rc' into stable
88
CHANGELOG
|
|
@ -1,3 +1,87 @@
|
|||
Version 2024.08 (unreleased)
|
||||
Friendica Core
|
||||
Updates to the translations AR, CS, DE, ES, FR, GD, HU, IS, IT, JA, NL, PL, RU, SV
|
||||
Updates to the documentation [foss-, loma-one, mexon]
|
||||
Updates to the themes (frio) [haheute]
|
||||
General code cleanup [annando, haheute, mexon, MrPetovan]
|
||||
Improved the redirection for contact actions [annando]
|
||||
Improved the performance while fetching of replies [annando]
|
||||
Improved the performance when visiting remote profiles [annando]
|
||||
Improved OWA [annando]
|
||||
Improved the procession of worker tasks [annando]
|
||||
Improved performance in the probing process [annando]
|
||||
Improved INBOX performance [annando]
|
||||
Improved perfomance when expireing postings [annando]
|
||||
Improved mirroring settings for RSS contacts [annando]
|
||||
Improved supported image formats [annando]
|
||||
Improved handling of CC for comments [annando]
|
||||
Improved handling of "sensitive" flags for postings [annando]
|
||||
Improved display of log levels [annando, tobiasd]
|
||||
Improved handling of permissions for attachments [annando]
|
||||
Improved addon handling [MrPetovan]
|
||||
Improved API for channels and circles [annando]
|
||||
Improved performance while displaying local postings [annando]
|
||||
Improved federation with pixelfed, threads [annando]
|
||||
Improved integration with Bluesky [annando]
|
||||
Improved automatic cleanup of the database [annando]
|
||||
Fixed access to restricted timeline via API [annando]
|
||||
Fixed problem fetching from INBOXes [annando]
|
||||
Fixed display of contacts from unavailable networks [annando]
|
||||
Fixed profile display [annando]
|
||||
Fixed a problem with local un-/follows [annando]
|
||||
Fixed the uimport POST endpoint [annando]
|
||||
Fixed problem with 0Auth logins [annando]
|
||||
Fixed problem with @mentions in comments [annando]
|
||||
Fixed XSS in profile fields [annando, apexrabbit, Devilx86, MrPetovan, ponlayookm]
|
||||
Fixed bug in deleting unused cached avatar pictures [annando]
|
||||
Fixed paging bug on the media tab of remote profiles [annando]
|
||||
Fixed display of attached links [annando]
|
||||
Fixed a bug in circle only contacts [annando]
|
||||
Fixed display of moderation reports [MrPetovan, TheTomcat14]
|
||||
Fixed delivery problems to group postings [annando]
|
||||
Added monitoring service endpoint [annando]
|
||||
Added admin option display_link_length to set the length of displayed links [annando]
|
||||
Added the possibility to upload media files via API [annando]
|
||||
Added console command to clear avatar cache [annando]
|
||||
Added platform data to the API [annando]
|
||||
Added parsing support for Nodeinfo 2.1 and 2.2 [annando]
|
||||
Added node description to Nodeinfo [annando]
|
||||
Added owner information of relay accounts [annando]
|
||||
Added option for users about how to transmit postings with titles [annando]
|
||||
Added for non HTML content of feeds [annando]
|
||||
Added reshares for postings from Bluesky and tumbl [annando]
|
||||
Added public forums with manual request approval [annando]
|
||||
Added "next try" information for deferred worker jobs listing [annando]
|
||||
Added support of FEP-e232 [annando]
|
||||
Added automatic closure of registration if admin becomes inactive [annando]
|
||||
Added channel only option for contacts [annando]
|
||||
|
||||
Friendica Addons
|
||||
Updates to the translations AR, CS, DE, FR, IT, PL, SV
|
||||
Blockbot
|
||||
Added Relatica to good client list [hankg]
|
||||
Improved agent identifier list [annando]
|
||||
Bluesky
|
||||
Added monitoring statistics [annando]
|
||||
Added support of sensitive postings [annando]
|
||||
Improved API handling [annando]
|
||||
Improved fetching of user DID [annando]
|
||||
Fixed conversion BS/Friendica handles [annando]
|
||||
jsuploader
|
||||
Improved detection of supported file types [annando]
|
||||
mailstream
|
||||
Improved image handling [mexon]
|
||||
tumblr
|
||||
Added monitoring statistics [annando]
|
||||
Improved quoted postings [annando]
|
||||
|
||||
Closed Issues
|
||||
11963, 13714, 13787, 13812, 13821, 13910, 14012, 14030, 14059,
|
||||
14077, 14079, 14045, 14052, 14055, 14081, 14084, 14102, 14110,
|
||||
14118, 14121, 14125, 14132, 14134, 14153, 14160, 14170, 14175,
|
||||
14186, 14197, 14220, 14228, 14231, 14240, 14249, 14250, 14285,
|
||||
14295, 14303, 14312, 14324, 14329, 14349, 14364
|
||||
|
||||
Version 2024.03 (2024-03-21)
|
||||
Friendica Core
|
||||
Updates to the translations AR, BG, CS, DE, EO, ES, FR, GD, HU, IS, IT, JA, PL, RO, RU, SV
|
||||
|
|
@ -285,7 +369,7 @@ Version 2023.04 (2023-04-23)
|
|||
twitter
|
||||
Improve remote-self handling [annando]
|
||||
impressum
|
||||
Avoide obfuscation on un-set email addresses [MrPestovan]
|
||||
Avoide obfuscation on un-set email addresses [MrPetovan]
|
||||
notifyall
|
||||
Fixed a bug selecting the email addresses [nupplaphil]
|
||||
tumblr
|
||||
|
|
@ -1734,7 +1818,7 @@ Version 2018.05 (2018-06-01)
|
|||
|
||||
Friendica Addons:
|
||||
Updates to the translations (DE, EN_GB, EN_US, ES, FI, FR, IS, IT, NL, PL, RU, ZH_CN) [translation teams]
|
||||
advancedcontentfilter: new addon with advanced filter capabilities [MrPetova]
|
||||
advancedcontentfilter: new addon with advanced filter capabilities [MrPetovan]
|
||||
catavatar: new addon for profile pictures based on David Revoy's cat-avatar generator [annando, fabrixxm, tobiasd]
|
||||
languagefilter: better help text [andyhee]
|
||||
mathjax: fixed the config form and adopted new CDN URL [tobiasd]
|
||||
|
|
|
|||
2
VERSION
|
|
@ -1 +1 @@
|
|||
2024.03
|
||||
2024.06-rc
|
||||
|
|
|
|||
|
|
@ -138,9 +138,9 @@ function execute_tests() {
|
|||
if [ -n "${USEDOCKER}" ]; then
|
||||
echo "Fire up the mysql docker"
|
||||
DOCKER_CONTAINER_ID=$(docker run \
|
||||
-e MYSQL_ROOT_PASSWORD=friendica \
|
||||
-e MYSQL_ROOT_PASSWORD="${DATABASE_PASSWORD}" \
|
||||
-e MYSQL_USER="${DATABASE_USER}" \
|
||||
-e MYSQL_PASSWORD=friendica \
|
||||
-e MYSQL_PASSWORD="${DATABASE_PASSWORD}" \
|
||||
-e MYSQL_DATABASE="${DATABASE_NAME}" \
|
||||
-d mysql)
|
||||
DATABASE_HOST=$(docker inspect --format="{{.NetworkSettings.IPAddress}}" "${DOCKER_CONTAINER_ID}")
|
||||
|
|
@ -152,8 +152,8 @@ function execute_tests() {
|
|||
echo "To use the docker container set the USEDOCKER environment variable"
|
||||
exit 3
|
||||
fi
|
||||
mysql -u "${DATABASE_USER}" -pfriendica -e "DROP DATABASE IF EXISTS ${DATABASE_NAME}" -h ${DATABASE_HOST} || true
|
||||
mysql -u "${DATABASE_USER}" -pfriendica -e "CREATE DATABASE ${DATABASE_NAME} DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci" -h ${DATABASE_HOST}
|
||||
mysql -u "${DATABASE_USER}" -p"${DATABASE_PASSWORD}" -e "DROP DATABASE IF EXISTS ${DATABASE_NAME}" -h ${DATABASE_HOST} || true
|
||||
mysql -u "${DATABASE_USER}" -p"${DATABASE_PASSWORD}" -e "CREATE DATABASE ${DATABASE_NAME} DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci" -h ${DATABASE_HOST}
|
||||
else
|
||||
DATABASE_HOST=mysql
|
||||
fi
|
||||
|
|
@ -171,9 +171,9 @@ function execute_tests() {
|
|||
if [ -n "${USEDOCKER}" ]; then
|
||||
echo "Fire up the mariadb docker"
|
||||
DOCKER_CONTAINER_ID=$(docker run \
|
||||
-e MYSQL_ROOT_PASSWORD=friendica \
|
||||
-e MYSQL_ROOT_PASSWORD="${DATABASE_PASSWORD}" \
|
||||
-e MYSQL_USER="${DATABASE_USER}" \
|
||||
-e MYSQL_PASSWORD=friendica \
|
||||
-e MYSQL_PASSWORD="${DATABASE_PASSWORD}" \
|
||||
-e MYSQL_DATABASE="${DATABASE_NAME}" \
|
||||
-d mariadb)
|
||||
DATABASE_HOST=$(docker inspect --format="{{.NetworkSettings.IPAddress}}" "${DOCKER_CONTAINER_ID}")
|
||||
|
|
@ -185,8 +185,8 @@ function execute_tests() {
|
|||
echo "To use the docker container set the USEDOCKER environment variable"
|
||||
exit 3
|
||||
fi
|
||||
mysql -u "${DATABASE_USER}" -pfriendica -e "DROP DATABASE IF EXISTS ${DATABASE_NAME}" -h ${DATABASE_HOST} || true
|
||||
mysql -u "${DATABASE_USER}" -pfriendica -e "CREATE DATABASE ${DATABASE_NAME} DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci" -h ${DATABASE_HOST}
|
||||
mysql -u "${DATABASE_USER}" -p"${DATABASE_PASSWORD}" -e "DROP DATABASE IF EXISTS ${DATABASE_NAME}" -h ${DATABASE_HOST} || true
|
||||
mysql -u "${DATABASE_USER}" -p"${DATABASE_PASSWORD}" -e "CREATE DATABASE ${DATABASE_NAME} DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci" -h ${DATABASE_HOST}
|
||||
else
|
||||
DATABASE_HOST=mariadb
|
||||
fi
|
||||
|
|
@ -203,14 +203,14 @@ function execute_tests() {
|
|||
|
||||
if [ -n "${USEDOCKER}" ]; then
|
||||
echo "Initialize database..."
|
||||
docker exec ${DOCKER_CONTAINER_ID} mysql -u root -pfriendica -e "CREATE DATABASE IF NOT EXISTS ${DATABASE_NAME};"
|
||||
docker exec ${DOCKER_CONTAINER_ID} mysql -u root -p"${DATABASE_PASSWORD}" -e "CREATE DATABASE IF NOT EXISTS ${DATABASE_NAME};"
|
||||
fi
|
||||
|
||||
export MYSQL_HOST="${DATABASE_HOST}"
|
||||
|
||||
#call installer
|
||||
echo "Installing Friendica..."
|
||||
"${PHP}" ./bin/console.php autoinstall --dbuser="${DATABASE_USER}" --dbpass=friendica --dbdata="${DATABASE_NAME}" --dbhost="${DATABASE_HOST}" --url=https://friendica.local --admin=admin@friendica.local
|
||||
"${PHP}" ./bin/console.php autoinstall --dbuser="${DATABASE_USER}" --dbpass="${DATABASE_PASSWORD}" --dbdata="${DATABASE_NAME}" --dbhost="${DATABASE_HOST}" --url=https://friendica.local --admin=admin@friendica.local
|
||||
fi
|
||||
|
||||
#test execution
|
||||
|
|
|
|||
|
|
@ -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++ ))
|
||||
|
|
|
|||
614
database.sql
|
|
@ -1,6 +1,6 @@
|
|||
-- ------------------------------------------
|
||||
-- Friendica 2024.03 (Yellow Archangel)
|
||||
-- DB_UPDATE_VERSION 1557
|
||||
-- Friendica 2024.06-rc (Yellow Archangel)
|
||||
-- DB_UPDATE_VERSION 1571
|
||||
-- ------------------------------------------
|
||||
|
||||
|
||||
|
|
@ -11,7 +11,7 @@ CREATE TABLE IF NOT EXISTS `gserver` (
|
|||
`id` int unsigned NOT NULL auto_increment COMMENT 'sequential ID',
|
||||
`url` varbinary(383) NOT NULL DEFAULT '' COMMENT '',
|
||||
`nurl` varbinary(383) NOT NULL DEFAULT '' COMMENT '',
|
||||
`version` varchar(255) NOT NULL DEFAULT '' COMMENT '',
|
||||
`version` varchar(255) NOT NULL DEFAULT '' COMMENT 'The version of this server software.',
|
||||
`site_name` varchar(255) NOT NULL DEFAULT '' COMMENT '',
|
||||
`info` text COMMENT '',
|
||||
`register_policy` tinyint NOT NULL DEFAULT 0 COMMENT '',
|
||||
|
|
@ -23,10 +23,14 @@ CREATE TABLE IF NOT EXISTS `gserver` (
|
|||
`local-comments` int unsigned COMMENT 'Number of local comments',
|
||||
`directory-type` tinyint DEFAULT 0 COMMENT 'Type of directory service (Poco, Mastodon)',
|
||||
`poco` varbinary(383) NOT NULL DEFAULT '' COMMENT '',
|
||||
`openwebauth` varbinary(383) COMMENT 'Path to the OpenWebAuth endpoint',
|
||||
`authredirect` varbinary(383) COMMENT 'Path to the authRedirect endpoint',
|
||||
`noscrape` varbinary(383) NOT NULL DEFAULT '' COMMENT '',
|
||||
`network` char(4) NOT NULL DEFAULT '' COMMENT '',
|
||||
`protocol` tinyint unsigned COMMENT 'The protocol of the server',
|
||||
`platform` varchar(255) NOT NULL DEFAULT '' COMMENT '',
|
||||
`platform` varchar(255) NOT NULL DEFAULT '' COMMENT 'The canonical name of this server software.',
|
||||
`repository` varbinary(383) COMMENT 'The url of the source code repository of this server software.',
|
||||
`homepage` varbinary(383) COMMENT 'The url of the homepage of this server software.',
|
||||
`relay-subscribe` boolean NOT NULL DEFAULT '0' COMMENT 'Has the server subscribed to the relay system',
|
||||
`relay-scope` varchar(10) NOT NULL DEFAULT '' COMMENT 'The scope of messages that the server wants to get',
|
||||
`detection-method` tinyint unsigned COMMENT 'Method that had been used to detect that server',
|
||||
|
|
@ -813,6 +817,7 @@ CREATE TABLE IF NOT EXISTS `inbox-entry` (
|
|||
`activity-id` varbinary(383) COMMENT 'id of the incoming activity',
|
||||
`object-id` varbinary(383) COMMENT '',
|
||||
`in-reply-to-id` varbinary(383) COMMENT '',
|
||||
`context` varbinary(383) COMMENT '',
|
||||
`conversation` varbinary(383) COMMENT '',
|
||||
`type` varchar(64) COMMENT 'Type of the activity',
|
||||
`object-type` varchar(64) COMMENT 'Type of the object activity',
|
||||
|
|
@ -823,6 +828,7 @@ CREATE TABLE IF NOT EXISTS `inbox-entry` (
|
|||
`push` boolean COMMENT 'Is the entry pushed or have pulled it?',
|
||||
`trust` boolean COMMENT 'Do we trust this entry?',
|
||||
`wid` int unsigned COMMENT 'Workerqueue id',
|
||||
`retrial` tinyint unsigned DEFAULT 0 COMMENT 'Retrial counter',
|
||||
PRIMARY KEY(`id`),
|
||||
UNIQUE INDEX `activity-id` (`activity-id`),
|
||||
INDEX `object-id` (`object-id`),
|
||||
|
|
@ -1181,6 +1187,7 @@ CREATE TABLE IF NOT EXISTS `post` (
|
|||
`parent-uri-id` int unsigned COMMENT 'Id of the item-uri table that contains the parent uri',
|
||||
`thr-parent-id` int unsigned COMMENT 'Id of the item-uri table that contains the thread parent uri',
|
||||
`external-id` int unsigned COMMENT 'Id of the item-uri table entry that contains the external uri',
|
||||
`replies-id` int unsigned COMMENT 'Id of the item-uri table entry that contains the endpoint for the replies collection',
|
||||
`created` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT 'Creation timestamp.',
|
||||
`edited` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT 'Date of last edit (default is created)',
|
||||
`received` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT 'datetime',
|
||||
|
|
@ -1199,6 +1206,7 @@ CREATE TABLE IF NOT EXISTS `post` (
|
|||
INDEX `parent-uri-id` (`parent-uri-id`),
|
||||
INDEX `thr-parent-id` (`thr-parent-id`),
|
||||
INDEX `external-id` (`external-id`),
|
||||
INDEX `replies-id` (`replies-id`),
|
||||
INDEX `owner-id` (`owner-id`),
|
||||
INDEX `author-id` (`author-id`),
|
||||
INDEX `causer-id` (`causer-id`),
|
||||
|
|
@ -1207,6 +1215,7 @@ CREATE TABLE IF NOT EXISTS `post` (
|
|||
FOREIGN KEY (`parent-uri-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE,
|
||||
FOREIGN KEY (`thr-parent-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE,
|
||||
FOREIGN KEY (`external-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE,
|
||||
FOREIGN KEY (`replies-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE,
|
||||
FOREIGN KEY (`owner-id`) REFERENCES `contact` (`id`) ON UPDATE RESTRICT ON DELETE RESTRICT,
|
||||
FOREIGN KEY (`author-id`) REFERENCES `contact` (`id`) ON UPDATE RESTRICT ON DELETE RESTRICT,
|
||||
FOREIGN KEY (`causer-id`) REFERENCES `contact` (`id`) ON UPDATE RESTRICT ON DELETE RESTRICT,
|
||||
|
|
@ -1347,7 +1356,7 @@ CREATE TABLE IF NOT EXISTS `post-engagement` (
|
|||
`uri-id` int unsigned NOT NULL COMMENT 'Id of the item-uri table entry that contains the item uri',
|
||||
`owner-id` int unsigned NOT NULL DEFAULT 0 COMMENT 'Item owner',
|
||||
`contact-type` tinyint NOT NULL DEFAULT 0 COMMENT 'Person, organisation, news, community, relay',
|
||||
`media-type` tinyint NOT NULL DEFAULT 0 COMMENT 'Type of media in a bit array (1 = image, 2 = video, 4 = audio',
|
||||
`media-type` tinyint NOT NULL DEFAULT 0 COMMENT 'Type of media in a bit array (1 = image, 2 = video, 4 = audio)',
|
||||
`language` char(2) COMMENT 'Language information about this post in the ISO 639-1 format',
|
||||
`searchtext` mediumtext COMMENT 'Simplified text for the full text search',
|
||||
`size` int unsigned COMMENT 'Body size',
|
||||
|
|
@ -1439,6 +1448,36 @@ CREATE TABLE IF NOT EXISTS `post-media` (
|
|||
FOREIGN KEY (`media-uri-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE
|
||||
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Attached media';
|
||||
|
||||
--
|
||||
-- TABLE post-origin
|
||||
--
|
||||
CREATE TABLE IF NOT EXISTS `post-origin` (
|
||||
`id` int unsigned NOT NULL,
|
||||
`uri-id` int unsigned NOT NULL COMMENT 'Id of the item-uri table entry that contains the item uri',
|
||||
`uid` mediumint unsigned NOT NULL COMMENT 'Owner id which owns this copy of the item',
|
||||
`parent-uri-id` int unsigned COMMENT 'Id of the item-uri table that contains the parent uri',
|
||||
`thr-parent-id` int unsigned COMMENT 'Id of the item-uri table that contains the thread parent uri',
|
||||
`created` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT 'Creation timestamp.',
|
||||
`received` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT 'datetime',
|
||||
`gravity` tinyint unsigned NOT NULL DEFAULT 0 COMMENT '',
|
||||
`vid` smallint unsigned COMMENT 'Id of the verb table entry that contains the activity verbs',
|
||||
`private` tinyint unsigned NOT NULL DEFAULT 0 COMMENT '0=public, 1=private, 2=unlisted',
|
||||
`wall` boolean NOT NULL DEFAULT '0' COMMENT 'This item was posted to the wall of uid',
|
||||
PRIMARY KEY(`id`),
|
||||
UNIQUE INDEX `uid_uri-id` (`uid`,`uri-id`),
|
||||
INDEX `uri-id` (`uri-id`),
|
||||
INDEX `parent-uri-id` (`parent-uri-id`),
|
||||
INDEX `thr-parent-id` (`thr-parent-id`),
|
||||
INDEX `vid` (`vid`),
|
||||
INDEX `parent-uri-id_uid` (`parent-uri-id`,`uid`),
|
||||
INDEX `uid_wall_received` (`uid`,`wall`,`received`),
|
||||
FOREIGN KEY (`uri-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE,
|
||||
FOREIGN KEY (`uid`) REFERENCES `user` (`uid`) ON UPDATE RESTRICT ON DELETE CASCADE,
|
||||
FOREIGN KEY (`parent-uri-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE,
|
||||
FOREIGN KEY (`thr-parent-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE,
|
||||
FOREIGN KEY (`vid`) REFERENCES `verb` (`id`) ON UPDATE RESTRICT ON DELETE RESTRICT
|
||||
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Posts from local users';
|
||||
|
||||
--
|
||||
-- TABLE post-question
|
||||
--
|
||||
|
|
@ -1471,7 +1510,7 @@ CREATE TABLE IF NOT EXISTS `post-question-option` (
|
|||
CREATE TABLE IF NOT EXISTS `post-searchindex` (
|
||||
`uri-id` int unsigned NOT NULL COMMENT 'Id of the item-uri table entry that contains the item uri',
|
||||
`owner-id` int unsigned NOT NULL DEFAULT 0 COMMENT 'Item owner',
|
||||
`media-type` tinyint NOT NULL DEFAULT 0 COMMENT 'Type of media in a bit array (1 = image, 2 = video, 4 = audio',
|
||||
`media-type` tinyint NOT NULL DEFAULT 0 COMMENT 'Type of media in a bit array (1 = image, 2 = video, 4 = audio)',
|
||||
`language` char(2) COMMENT 'Language information about this post in the ISO 639-1 format',
|
||||
`searchtext` mediumtext COMMENT 'Simplified text for the full text search',
|
||||
`size` int unsigned COMMENT 'Body size',
|
||||
|
|
@ -1506,6 +1545,7 @@ CREATE TABLE IF NOT EXISTS `post-tag` (
|
|||
--
|
||||
CREATE TABLE IF NOT EXISTS `post-thread` (
|
||||
`uri-id` int unsigned NOT NULL COMMENT 'Id of the item-uri table entry that contains the item uri',
|
||||
`context-id` int unsigned COMMENT 'Id of the item-uri table entry that contains the endpoint for the context collection',
|
||||
`conversation-id` int unsigned COMMENT 'Id of the item-uri table entry that contains the conversation uri',
|
||||
`owner-id` int unsigned NOT NULL DEFAULT 0 COMMENT 'Item owner',
|
||||
`author-id` int unsigned NOT NULL DEFAULT 0 COMMENT 'Item author',
|
||||
|
|
@ -1516,6 +1556,7 @@ CREATE TABLE IF NOT EXISTS `post-thread` (
|
|||
`changed` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT 'Date that something in the conversation changed, indicating clients should fetch the conversation again',
|
||||
`commented` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT '',
|
||||
PRIMARY KEY(`uri-id`),
|
||||
INDEX `context-id` (`context-id`),
|
||||
INDEX `conversation-id` (`conversation-id`),
|
||||
INDEX `owner-id` (`owner-id`),
|
||||
INDEX `author-id` (`author-id`),
|
||||
|
|
@ -1523,6 +1564,7 @@ CREATE TABLE IF NOT EXISTS `post-thread` (
|
|||
INDEX `received` (`received`),
|
||||
INDEX `commented` (`commented`),
|
||||
FOREIGN KEY (`uri-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE,
|
||||
FOREIGN KEY (`context-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE,
|
||||
FOREIGN KEY (`conversation-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE,
|
||||
FOREIGN KEY (`owner-id`) REFERENCES `contact` (`id`) ON UPDATE RESTRICT ON DELETE RESTRICT,
|
||||
FOREIGN KEY (`author-id`) REFERENCES `contact` (`id`) ON UPDATE RESTRICT ON DELETE RESTRICT,
|
||||
|
|
@ -1538,6 +1580,7 @@ CREATE TABLE IF NOT EXISTS `post-user` (
|
|||
`parent-uri-id` int unsigned COMMENT 'Id of the item-uri table that contains the parent uri',
|
||||
`thr-parent-id` int unsigned COMMENT 'Id of the item-uri table that contains the thread parent uri',
|
||||
`external-id` int unsigned COMMENT 'Id of the item-uri table entry that contains the external uri',
|
||||
`replies-id` int unsigned COMMENT 'Id of the item-uri table entry that contains the endpoint for the replies collection',
|
||||
`created` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT 'Creation timestamp.',
|
||||
`edited` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT 'Date of last edit (default is created)',
|
||||
`received` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT 'datetime',
|
||||
|
|
@ -1550,6 +1593,7 @@ CREATE TABLE IF NOT EXISTS `post-user` (
|
|||
`post-reason` tinyint unsigned NOT NULL DEFAULT 0 COMMENT 'Reason why the post arrived at the user',
|
||||
`vid` smallint unsigned COMMENT 'Id of the verb table entry that contains the activity verbs',
|
||||
`private` tinyint unsigned NOT NULL DEFAULT 0 COMMENT '0=public, 1=private, 2=unlisted',
|
||||
`restrictions` tinyint unsigned COMMENT 'Bit array of post restrictions (1 = Reply, 2 = Like, 4 = Announce)',
|
||||
`global` boolean NOT NULL DEFAULT '0' COMMENT '',
|
||||
`visible` boolean NOT NULL DEFAULT '0' COMMENT '',
|
||||
`deleted` boolean NOT NULL DEFAULT '0' COMMENT 'item has been marked for deletion',
|
||||
|
|
@ -1569,6 +1613,7 @@ CREATE TABLE IF NOT EXISTS `post-user` (
|
|||
INDEX `parent-uri-id` (`parent-uri-id`),
|
||||
INDEX `thr-parent-id` (`thr-parent-id`),
|
||||
INDEX `external-id` (`external-id`),
|
||||
INDEX `replies-id` (`replies-id`),
|
||||
INDEX `owner-id` (`owner-id`),
|
||||
INDEX `author-id` (`author-id`),
|
||||
INDEX `causer-id` (`causer-id`),
|
||||
|
|
@ -1589,6 +1634,7 @@ CREATE TABLE IF NOT EXISTS `post-user` (
|
|||
FOREIGN KEY (`parent-uri-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE,
|
||||
FOREIGN KEY (`thr-parent-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE,
|
||||
FOREIGN KEY (`external-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE,
|
||||
FOREIGN KEY (`replies-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE,
|
||||
FOREIGN KEY (`owner-id`) REFERENCES `contact` (`id`) ON UPDATE RESTRICT ON DELETE RESTRICT,
|
||||
FOREIGN KEY (`author-id`) REFERENCES `contact` (`id`) ON UPDATE RESTRICT ON DELETE RESTRICT,
|
||||
FOREIGN KEY (`causer-id`) REFERENCES `contact` (`id`) ON UPDATE RESTRICT ON DELETE RESTRICT,
|
||||
|
|
@ -1604,6 +1650,7 @@ CREATE TABLE IF NOT EXISTS `post-user` (
|
|||
--
|
||||
CREATE TABLE IF NOT EXISTS `post-thread-user` (
|
||||
`uri-id` int unsigned NOT NULL COMMENT 'Id of the item-uri table entry that contains the item uri',
|
||||
`context-id` int unsigned COMMENT 'Id of the item-uri table entry that contains the endpoint for the context collection',
|
||||
`conversation-id` int unsigned COMMENT 'Id of the item-uri table entry that contains the conversation uri',
|
||||
`owner-id` int unsigned NOT NULL DEFAULT 0 COMMENT 'Item owner',
|
||||
`author-id` int unsigned NOT NULL DEFAULT 0 COMMENT 'Item author',
|
||||
|
|
@ -1629,6 +1676,7 @@ CREATE TABLE IF NOT EXISTS `post-thread-user` (
|
|||
`post-user-id` int unsigned COMMENT 'Id of the post-user table',
|
||||
PRIMARY KEY(`uid`,`uri-id`),
|
||||
INDEX `uri-id` (`uri-id`),
|
||||
INDEX `context-id` (`context-id`),
|
||||
INDEX `conversation-id` (`conversation-id`),
|
||||
INDEX `owner-id` (`owner-id`),
|
||||
INDEX `author-id` (`author-id`),
|
||||
|
|
@ -1651,6 +1699,7 @@ CREATE TABLE IF NOT EXISTS `post-thread-user` (
|
|||
INDEX `contact-id_received` (`contact-id`,`received`),
|
||||
INDEX `contact-id_created` (`contact-id`,`created`),
|
||||
FOREIGN KEY (`uri-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE,
|
||||
FOREIGN KEY (`context-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE,
|
||||
FOREIGN KEY (`conversation-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE,
|
||||
FOREIGN KEY (`owner-id`) REFERENCES `contact` (`id`) ON UPDATE RESTRICT ON DELETE RESTRICT,
|
||||
FOREIGN KEY (`author-id`) REFERENCES `contact` (`id`) ON UPDATE RESTRICT ON DELETE RESTRICT,
|
||||
|
|
@ -1947,6 +1996,7 @@ CREATE TABLE IF NOT EXISTS `user-contact` (
|
|||
`ignored` boolean COMMENT 'Posts from this contact are ignored',
|
||||
`collapsed` boolean COMMENT 'Posts from this contact are collapsed',
|
||||
`hidden` boolean COMMENT 'This contact is hidden from the others',
|
||||
`channel-only` boolean COMMENT 'This contact is displayed only in channels, but not in the network stream.',
|
||||
`is-blocked` boolean COMMENT 'User is blocked by this contact',
|
||||
`channel-frequency` tinyint unsigned COMMENT 'Controls the frequency of the appearance of this contact in channels',
|
||||
`pending` boolean COMMENT '',
|
||||
|
|
@ -2065,6 +2115,38 @@ CREATE VIEW `post-counts-view` AS SELECT
|
|||
FROM `post-counts`
|
||||
INNER JOIN `verb` ON `verb`.`id` = `post-counts`.`vid`;
|
||||
|
||||
--
|
||||
-- VIEW post-engagement-user-view
|
||||
--
|
||||
DROP VIEW IF EXISTS `post-engagement-user-view`;
|
||||
CREATE VIEW `post-engagement-user-view` AS SELECT
|
||||
`post-thread-user`.`uid` AS `uid`,
|
||||
`post-engagement`.`uri-id` AS `uri-id`,
|
||||
`post-engagement`.`owner-id` AS `owner-id`,
|
||||
`post-engagement`.`media-type` AS `media-type`,
|
||||
`post-engagement`.`language` AS `language`,
|
||||
`post-engagement`.`searchtext` AS `searchtext`,
|
||||
`post-engagement`.`size` AS `size`,
|
||||
`post-thread-user`.`commented` AS `commented`,
|
||||
`post-thread-user`.`received` AS `received`,
|
||||
`post-thread-user`.`created` AS `created`,
|
||||
`post-thread-user`.`network` AS `network`,
|
||||
`post-engagement`.`language` AS `restricted`,
|
||||
0 AS `comments`,
|
||||
0 AS `activities`
|
||||
FROM `post-thread-user`
|
||||
INNER JOIN `post-engagement` ON `post-engagement`.`uri-id` = `post-thread-user`.`uri-id`
|
||||
INNER JOIN `post-user` ON `post-user`.`id` = `post-thread-user`.`post-user-id`
|
||||
STRAIGHT_JOIN `contact` ON `contact`.`id` = `post-thread-user`.`contact-id`
|
||||
STRAIGHT_JOIN `contact` AS `authorcontact` ON `authorcontact`.`id` = `post-thread-user`.`author-id`
|
||||
STRAIGHT_JOIN `contact` AS `ownercontact` ON `ownercontact`.`id` = `post-thread-user`.`owner-id`
|
||||
WHERE `post-user`.`visible` AND NOT `post-user`.`deleted`
|
||||
AND (NOT `contact`.`readonly` AND NOT `contact`.`blocked` AND NOT `contact`.`pending`)
|
||||
AND (`post-thread-user`.`hidden` IS NULL OR NOT `post-thread-user`.`hidden`)
|
||||
AND NOT `authorcontact`.`blocked` AND NOT `ownercontact`.`blocked`
|
||||
AND NOT EXISTS(SELECT `cid` FROM `user-contact` WHERE `uid` = `post-thread-user`.`uid` AND `cid` IN (`authorcontact`.`id`, `ownercontact`.`id`) AND (`blocked` OR `ignored`))
|
||||
AND NOT EXISTS(SELECT `gsid` FROM `user-gserver` WHERE `uid` = `post-thread-user`.`uid` AND `gsid` IN (`authorcontact`.`gsid`, `ownercontact`.`gsid`) AND `ignored`);
|
||||
|
||||
--
|
||||
-- VIEW post-timeline-view
|
||||
--
|
||||
|
|
@ -2110,6 +2192,52 @@ CREATE VIEW `post-timeline-view` AS SELECT
|
|||
STRAIGHT_JOIN `contact` AS `owner` ON `owner`.`id` = `post-user`.`owner-id`
|
||||
LEFT JOIN `contact` AS `causer` ON `causer`.`id` = `post-user`.`causer-id`;
|
||||
|
||||
--
|
||||
-- VIEW post-timeline-origin-view
|
||||
--
|
||||
DROP VIEW IF EXISTS `post-timeline-origin-view`;
|
||||
CREATE VIEW `post-timeline-origin-view` AS SELECT
|
||||
`post-origin`.`uid` AS `uid`,
|
||||
`post-origin`.`uri-id` AS `uri-id`,
|
||||
`post-origin`.`gravity` AS `gravity`,
|
||||
`post-origin`.`created` AS `created`,
|
||||
`post-user`.`edited` AS `edited`,
|
||||
`post-thread-user`.`commented` AS `commented`,
|
||||
`post-origin`.`received` AS `received`,
|
||||
`post-thread-user`.`changed` AS `changed`,
|
||||
`post-origin`.`private` AS `private`,
|
||||
`post-user`.`visible` AS `visible`,
|
||||
`post-user`.`deleted` AS `deleted`,
|
||||
true AS `origin`,
|
||||
`post-user`.`global` AS `global`,
|
||||
`post-user`.`network` AS `network`,
|
||||
`post-user`.`protocol` AS `protocol`,
|
||||
`post-origin`.`vid` AS `vid`,
|
||||
`post-user`.`contact-id` AS `contact-id`,
|
||||
`contact`.`blocked` AS `contact-blocked`,
|
||||
`contact`.`readonly` AS `contact-readonly`,
|
||||
`contact`.`pending` AS `contact-pending`,
|
||||
`contact`.`rel` AS `contact-rel`,
|
||||
`contact`.`uid` AS `contact-uid`,
|
||||
`contact`.`self` AS `self`,
|
||||
`post-user`.`author-id` AS `author-id`,
|
||||
`author`.`blocked` AS `author-blocked`,
|
||||
`author`.`hidden` AS `author-hidden`,
|
||||
`author`.`gsid` AS `author-gsid`,
|
||||
`post-user`.`owner-id` AS `owner-id`,
|
||||
`owner`.`blocked` AS `owner-blocked`,
|
||||
`owner`.`gsid` AS `owner-gsid`,
|
||||
`post-user`.`causer-id` AS `causer-id`,
|
||||
`causer`.`blocked` AS `causer-blocked`,
|
||||
`causer`.`gsid` AS `causer-gsid`
|
||||
FROM `post-origin`
|
||||
INNER JOIN `post-user` ON `post-user`.`id` = `post-origin`.`id`
|
||||
LEFT JOIN `post-thread-user` ON `post-thread-user`.`uri-id` = `post-origin`.`parent-uri-id` AND `post-thread-user`.`uid` = `post-origin`.`uid`
|
||||
STRAIGHT_JOIN `contact` ON `contact`.`id` = `post-user`.`contact-id`
|
||||
STRAIGHT_JOIN `contact` AS `author` ON `author`.`id` = `post-user`.`author-id`
|
||||
STRAIGHT_JOIN `contact` AS `owner` ON `owner`.`id` = `post-user`.`owner-id`
|
||||
LEFT JOIN `contact` AS `causer` ON `causer`.`id` = `post-user`.`causer-id`;
|
||||
|
||||
--
|
||||
-- VIEW post-searchindex-user-view
|
||||
--
|
||||
|
|
@ -2142,6 +2270,392 @@ CREATE VIEW `post-searchindex-user-view` AS SELECT
|
|||
AND NOT EXISTS(SELECT `cid` FROM `user-contact` WHERE `uid` = `post-thread-user`.`uid` AND `cid` IN (`authorcontact`.`id`, `ownercontact`.`id`) AND (`blocked` OR `ignored`))
|
||||
AND NOT EXISTS(SELECT `gsid` FROM `user-gserver` WHERE `uid` = `post-thread-user`.`uid` AND `gsid` IN (`authorcontact`.`gsid`, `ownercontact`.`gsid`) AND `ignored`);
|
||||
|
||||
--
|
||||
-- VIEW post-origin-view
|
||||
--
|
||||
DROP VIEW IF EXISTS `post-origin-view`;
|
||||
CREATE VIEW `post-origin-view` AS SELECT
|
||||
`post-origin`.`id` AS `id`,
|
||||
`post-origin`.`id` AS `post-user-id`,
|
||||
`post-origin`.`uid` AS `uid`,
|
||||
`post-thread-user`.`post-user-id` AS `parent`,
|
||||
`item-uri`.`uri` AS `uri`,
|
||||
`post-origin`.`uri-id` AS `uri-id`,
|
||||
`parent-item-uri`.`uri` AS `parent-uri`,
|
||||
`post-origin`.`parent-uri-id` AS `parent-uri-id`,
|
||||
`thr-parent-item-uri`.`uri` AS `thr-parent`,
|
||||
`post-origin`.`thr-parent-id` AS `thr-parent-id`,
|
||||
`conversation-item-uri`.`uri` AS `conversation`,
|
||||
`post-thread-user`.`conversation-id` AS `conversation-id`,
|
||||
`context-item-uri`.`uri` AS `context`,
|
||||
`post-thread-user`.`context-id` AS `context-id`,
|
||||
`quote-item-uri`.`uri` AS `quote-uri`,
|
||||
`post-content`.`quote-uri-id` AS `quote-uri-id`,
|
||||
`item-uri`.`guid` AS `guid`,
|
||||
`post-origin`.`wall` AS `wall`,
|
||||
`post-origin`.`gravity` AS `gravity`,
|
||||
`external-item-uri`.`uri` AS `extid`,
|
||||
`post-user`.`external-id` AS `external-id`,
|
||||
`replies-item-uri`.`uri` AS `replies`,
|
||||
`post-user`.`replies-id` AS `replies-id`,
|
||||
`post-origin`.`created` AS `created`,
|
||||
`post-user`.`edited` AS `edited`,
|
||||
`post-thread-user`.`commented` AS `commented`,
|
||||
`post-origin`.`received` AS `received`,
|
||||
`post-thread-user`.`changed` AS `changed`,
|
||||
`post-user`.`post-type` AS `post-type`,
|
||||
`post-user`.`post-reason` AS `post-reason`,
|
||||
`post-origin`.`private` AS `private`,
|
||||
`post-thread-user`.`pubmail` AS `pubmail`,
|
||||
`post-user`.`visible` AS `visible`,
|
||||
`post-thread-user`.`starred` AS `starred`,
|
||||
`post-user`.`unseen` AS `unseen`,
|
||||
`post-user`.`deleted` AS `deleted`,
|
||||
true AS `origin`,
|
||||
`post-thread-user`.`origin` AS `parent-origin`,
|
||||
`post-thread-user`.`mention` AS `mention`,
|
||||
`post-user`.`global` AS `global`,
|
||||
EXISTS(SELECT `type` FROM `post-collection` WHERE `type` = 0 AND `uri-id` = `post-origin`.`uri-id`) AS `featured`,
|
||||
`post-user`.`network` AS `network`,
|
||||
`post-user`.`protocol` AS `protocol`,
|
||||
`post-origin`.`vid` AS `vid`,
|
||||
`post-user`.`psid` AS `psid`,
|
||||
IF (`post-origin`.`vid` IS NULL, '', `verb`.`name`) AS `verb`,
|
||||
`post-content`.`title` AS `title`,
|
||||
`post-content`.`content-warning` AS `content-warning`,
|
||||
`post-content`.`raw-body` AS `raw-body`,
|
||||
IFNULL (`post-content`.`body`, '') AS `body`,
|
||||
`post-content`.`rendered-hash` AS `rendered-hash`,
|
||||
`post-content`.`rendered-html` AS `rendered-html`,
|
||||
`post-content`.`language` AS `language`,
|
||||
`post-content`.`plink` AS `plink`,
|
||||
`post-content`.`location` AS `location`,
|
||||
`post-content`.`coord` AS `coord`,
|
||||
`post-content`.`sensitive` AS `sensitive`,
|
||||
`post-user`.`restrictions` AS `restrictions`,
|
||||
`post-content`.`app` AS `app`,
|
||||
`post-content`.`object-type` AS `object-type`,
|
||||
`post-content`.`object` AS `object`,
|
||||
`post-content`.`target-type` AS `target-type`,
|
||||
`post-content`.`target` AS `target`,
|
||||
`post-content`.`resource-id` AS `resource-id`,
|
||||
`post-user`.`contact-id` AS `contact-id`,
|
||||
`contact`.`uri-id` AS `contact-uri-id`,
|
||||
`contact`.`url` AS `contact-link`,
|
||||
`contact`.`addr` AS `contact-addr`,
|
||||
`contact`.`name` AS `contact-name`,
|
||||
`contact`.`nick` AS `contact-nick`,
|
||||
`contact`.`thumb` AS `contact-avatar`,
|
||||
`contact`.`network` AS `contact-network`,
|
||||
`contact`.`blocked` AS `contact-blocked`,
|
||||
`contact`.`hidden` AS `contact-hidden`,
|
||||
`contact`.`readonly` AS `contact-readonly`,
|
||||
`contact`.`archive` AS `contact-archive`,
|
||||
`contact`.`pending` AS `contact-pending`,
|
||||
`contact`.`rel` AS `contact-rel`,
|
||||
`contact`.`uid` AS `contact-uid`,
|
||||
`contact`.`contact-type` AS `contact-contact-type`,
|
||||
IF (`post-user`.`network` IN ('apub', 'dfrn', 'dspr', 'stat'), true, `contact`.`writable`) AS `writable`,
|
||||
`contact`.`self` AS `self`,
|
||||
`contact`.`id` AS `cid`,
|
||||
`contact`.`alias` AS `alias`,
|
||||
`contact`.`photo` AS `photo`,
|
||||
`contact`.`name-date` AS `name-date`,
|
||||
`contact`.`uri-date` AS `uri-date`,
|
||||
`contact`.`avatar-date` AS `avatar-date`,
|
||||
`contact`.`thumb` AS `thumb`,
|
||||
`post-user`.`author-id` AS `author-id`,
|
||||
`author`.`uri-id` AS `author-uri-id`,
|
||||
`author`.`url` AS `author-link`,
|
||||
`author`.`addr` AS `author-addr`,
|
||||
IF (`contact`.`url` = `author`.`url` AND `contact`.`name` != '', `contact`.`name`, `author`.`name`) AS `author-name`,
|
||||
`author`.`nick` AS `author-nick`,
|
||||
`author`.`alias` AS `author-alias`,
|
||||
IF (`contact`.`url` = `author`.`url` AND `contact`.`thumb` != '', `contact`.`thumb`, `author`.`thumb`) AS `author-avatar`,
|
||||
`author`.`network` AS `author-network`,
|
||||
`author`.`blocked` AS `author-blocked`,
|
||||
`author`.`hidden` AS `author-hidden`,
|
||||
`author`.`updated` AS `author-updated`,
|
||||
`author`.`contact-type` AS `author-contact-type`,
|
||||
`author`.`gsid` AS `author-gsid`,
|
||||
`author`.`baseurl` AS `author-baseurl`,
|
||||
`post-user`.`owner-id` AS `owner-id`,
|
||||
`owner`.`uri-id` AS `owner-uri-id`,
|
||||
`owner`.`url` AS `owner-link`,
|
||||
`owner`.`addr` AS `owner-addr`,
|
||||
IF (`contact`.`url` = `owner`.`url` AND `contact`.`name` != '', `contact`.`name`, `owner`.`name`) AS `owner-name`,
|
||||
`owner`.`nick` AS `owner-nick`,
|
||||
`owner`.`alias` AS `owner-alias`,
|
||||
IF (`contact`.`url` = `owner`.`url` AND `contact`.`thumb` != '', `contact`.`thumb`, `owner`.`thumb`) AS `owner-avatar`,
|
||||
`owner`.`network` AS `owner-network`,
|
||||
`owner`.`blocked` AS `owner-blocked`,
|
||||
`owner`.`hidden` AS `owner-hidden`,
|
||||
`owner`.`updated` AS `owner-updated`,
|
||||
`owner`.`gsid` AS `owner-gsid`,
|
||||
`owner`.`contact-type` AS `owner-contact-type`,
|
||||
`post-user`.`causer-id` AS `causer-id`,
|
||||
`causer`.`uri-id` AS `causer-uri-id`,
|
||||
`causer`.`url` AS `causer-link`,
|
||||
`causer`.`addr` AS `causer-addr`,
|
||||
`causer`.`name` AS `causer-name`,
|
||||
`causer`.`nick` AS `causer-nick`,
|
||||
`causer`.`alias` AS `causer-alias`,
|
||||
`causer`.`thumb` AS `causer-avatar`,
|
||||
`causer`.`network` AS `causer-network`,
|
||||
`causer`.`blocked` AS `causer-blocked`,
|
||||
`causer`.`hidden` AS `causer-hidden`,
|
||||
`causer`.`gsid` AS `causer-gsid`,
|
||||
`causer`.`contact-type` AS `causer-contact-type`,
|
||||
`post-delivery-data`.`postopts` AS `postopts`,
|
||||
`post-delivery-data`.`inform` AS `inform`,
|
||||
`post-delivery-data`.`queue_count` AS `delivery_queue_count`,
|
||||
`post-delivery-data`.`queue_done` AS `delivery_queue_done`,
|
||||
`post-delivery-data`.`queue_failed` AS `delivery_queue_failed`,
|
||||
IF (`post-user`.`psid` IS NULL, '', `permissionset`.`allow_cid`) AS `allow_cid`,
|
||||
IF (`post-user`.`psid` IS NULL, '', `permissionset`.`allow_gid`) AS `allow_gid`,
|
||||
IF (`post-user`.`psid` IS NULL, '', `permissionset`.`deny_cid`) AS `deny_cid`,
|
||||
IF (`post-user`.`psid` IS NULL, '', `permissionset`.`deny_gid`) AS `deny_gid`,
|
||||
`post-user`.`event-id` AS `event-id`,
|
||||
`event`.`created` AS `event-created`,
|
||||
`event`.`edited` AS `event-edited`,
|
||||
`event`.`start` AS `event-start`,
|
||||
`event`.`finish` AS `event-finish`,
|
||||
`event`.`summary` AS `event-summary`,
|
||||
`event`.`desc` AS `event-desc`,
|
||||
`event`.`location` AS `event-location`,
|
||||
`event`.`type` AS `event-type`,
|
||||
`event`.`nofinish` AS `event-nofinish`,
|
||||
`event`.`ignore` AS `event-ignore`,
|
||||
`post-question`.`id` AS `question-id`,
|
||||
`post-question`.`multiple` AS `question-multiple`,
|
||||
`post-question`.`voters` AS `question-voters`,
|
||||
`post-question`.`end-time` AS `question-end-time`,
|
||||
EXISTS(SELECT `uri-id` FROM `post-category` WHERE `post-category`.`uri-id` = `post-origin`.`uri-id` AND `post-category`.`uid` = `post-origin`.`uid`) AS `has-categories`,
|
||||
EXISTS(SELECT `id` FROM `post-media` WHERE `post-media`.`uri-id` = `post-origin`.`uri-id`) AS `has-media`,
|
||||
`diaspora-interaction`.`interaction` AS `signed_text`,
|
||||
`parent-item-uri`.`guid` AS `parent-guid`,
|
||||
`post-thread-user`.`network` AS `parent-network`,
|
||||
`post-thread-user`.`author-id` AS `parent-author-id`,
|
||||
`parent-post-author`.`url` AS `parent-author-link`,
|
||||
`parent-post-author`.`name` AS `parent-author-name`,
|
||||
`parent-post-author`.`nick` AS `parent-author-nick`,
|
||||
`parent-post-author`.`network` AS `parent-author-network`
|
||||
FROM `post-origin`
|
||||
INNER JOIN `post-user` ON `post-user`.`id` = `post-origin`.`id`
|
||||
INNER JOIN `post-thread-user` ON `post-thread-user`.`uri-id` = `post-origin`.`parent-uri-id` AND `post-thread-user`.`uid` = `post-origin`.`uid`
|
||||
STRAIGHT_JOIN `contact` ON `contact`.`id` = `post-user`.`contact-id`
|
||||
STRAIGHT_JOIN `contact` AS `author` ON `author`.`id` = `post-user`.`author-id`
|
||||
STRAIGHT_JOIN `contact` AS `owner` ON `owner`.`id` = `post-user`.`owner-id`
|
||||
LEFT JOIN `contact` AS `causer` ON `causer`.`id` = `post-user`.`causer-id`
|
||||
LEFT JOIN `item-uri` ON `item-uri`.`id` = `post-origin`.`uri-id`
|
||||
LEFT JOIN `item-uri` AS `thr-parent-item-uri` ON `thr-parent-item-uri`.`id` = `post-origin`.`thr-parent-id`
|
||||
LEFT JOIN `item-uri` AS `parent-item-uri` ON `parent-item-uri`.`id` = `post-origin`.`parent-uri-id`
|
||||
LEFT JOIN `item-uri` AS `conversation-item-uri` ON `conversation-item-uri`.`id` = `post-thread-user`.`conversation-id`
|
||||
LEFT JOIN `item-uri` AS `context-item-uri` ON `context-item-uri`.`id` = `post-thread-user`.`context-id`
|
||||
LEFT JOIN `item-uri` AS `external-item-uri` ON `external-item-uri`.`id` = `post-user`.`external-id`
|
||||
LEFT JOIN `item-uri` AS `replies-item-uri` ON `replies-item-uri`.`id` = `post-user`.`replies-id`
|
||||
LEFT JOIN `verb` ON `verb`.`id` = `post-origin`.`vid`
|
||||
LEFT JOIN `event` ON `event`.`id` = `post-user`.`event-id`
|
||||
LEFT JOIN `diaspora-interaction` ON `diaspora-interaction`.`uri-id` = `post-origin`.`uri-id`
|
||||
LEFT JOIN `post-content` ON `post-content`.`uri-id` = `post-origin`.`uri-id`
|
||||
LEFT JOIN `item-uri` AS `quote-item-uri` ON `quote-item-uri`.`id` = `post-content`.`quote-uri-id`
|
||||
LEFT JOIN `post-delivery-data` ON `post-delivery-data`.`uri-id` = `post-origin`.`uri-id`
|
||||
LEFT JOIN `post-question` ON `post-question`.`uri-id` = `post-origin`.`uri-id`
|
||||
LEFT JOIN `permissionset` ON `permissionset`.`id` = `post-user`.`psid`
|
||||
LEFT JOIN `contact` AS `parent-post-author` ON `parent-post-author`.`id` = `post-thread-user`.`author-id`;
|
||||
|
||||
--
|
||||
-- VIEW post-thread-origin-view
|
||||
--
|
||||
DROP VIEW IF EXISTS `post-thread-origin-view`;
|
||||
CREATE VIEW `post-thread-origin-view` AS SELECT
|
||||
`post-origin`.`id` AS `id`,
|
||||
`post-origin`.`id` AS `post-user-id`,
|
||||
`post-origin`.`uid` AS `uid`,
|
||||
`post-thread-user`.`post-user-id` AS `parent`,
|
||||
`item-uri`.`uri` AS `uri`,
|
||||
`post-origin`.`uri-id` AS `uri-id`,
|
||||
`parent-item-uri`.`uri` AS `parent-uri`,
|
||||
`post-origin`.`parent-uri-id` AS `parent-uri-id`,
|
||||
`thr-parent-item-uri`.`uri` AS `thr-parent`,
|
||||
`post-origin`.`thr-parent-id` AS `thr-parent-id`,
|
||||
`conversation-item-uri`.`uri` AS `conversation`,
|
||||
`post-thread-user`.`conversation-id` AS `conversation-id`,
|
||||
`context-item-uri`.`uri` AS `context`,
|
||||
`post-thread-user`.`context-id` AS `context-id`,
|
||||
`quote-item-uri`.`uri` AS `quote-uri`,
|
||||
`post-content`.`quote-uri-id` AS `quote-uri-id`,
|
||||
`item-uri`.`guid` AS `guid`,
|
||||
`post-origin`.`wall` AS `wall`,
|
||||
`post-origin`.`gravity` AS `gravity`,
|
||||
`external-item-uri`.`uri` AS `extid`,
|
||||
`post-user`.`external-id` AS `external-id`,
|
||||
`replies-item-uri`.`uri` AS `replies`,
|
||||
`post-user`.`replies-id` AS `replies-id`,
|
||||
`post-origin`.`created` AS `created`,
|
||||
`post-user`.`edited` AS `edited`,
|
||||
`post-thread-user`.`commented` AS `commented`,
|
||||
`post-origin`.`received` AS `received`,
|
||||
`post-thread-user`.`changed` AS `changed`,
|
||||
`post-user`.`post-type` AS `post-type`,
|
||||
`post-user`.`post-reason` AS `post-reason`,
|
||||
`post-origin`.`private` AS `private`,
|
||||
`post-thread-user`.`pubmail` AS `pubmail`,
|
||||
`post-thread-user`.`ignored` AS `ignored`,
|
||||
`post-user`.`visible` AS `visible`,
|
||||
`post-thread-user`.`starred` AS `starred`,
|
||||
`post-thread-user`.`unseen` AS `unseen`,
|
||||
`post-user`.`deleted` AS `deleted`,
|
||||
true AS `origin`,
|
||||
`post-thread-user`.`mention` AS `mention`,
|
||||
`post-user`.`global` AS `global`,
|
||||
EXISTS(SELECT `type` FROM `post-collection` WHERE `type` = 0 AND `uri-id` = `post-thread-user`.`uri-id`) AS `featured`,
|
||||
`post-thread-user`.`network` AS `network`,
|
||||
`post-origin`.`vid` AS `vid`,
|
||||
`post-thread-user`.`psid` AS `psid`,
|
||||
IF (`post-origin`.`vid` IS NULL, '', `verb`.`name`) AS `verb`,
|
||||
`post-content`.`title` AS `title`,
|
||||
`post-content`.`content-warning` AS `content-warning`,
|
||||
`post-content`.`raw-body` AS `raw-body`,
|
||||
`post-content`.`body` AS `body`,
|
||||
`post-content`.`rendered-hash` AS `rendered-hash`,
|
||||
`post-content`.`rendered-html` AS `rendered-html`,
|
||||
`post-content`.`language` AS `language`,
|
||||
`post-content`.`plink` AS `plink`,
|
||||
`post-content`.`location` AS `location`,
|
||||
`post-content`.`coord` AS `coord`,
|
||||
`post-content`.`sensitive` AS `sensitive`,
|
||||
`post-user`.`restrictions` AS `restrictions`,
|
||||
`post-content`.`app` AS `app`,
|
||||
`post-content`.`object-type` AS `object-type`,
|
||||
`post-content`.`object` AS `object`,
|
||||
`post-content`.`target-type` AS `target-type`,
|
||||
`post-content`.`target` AS `target`,
|
||||
`post-content`.`resource-id` AS `resource-id`,
|
||||
`post-thread-user`.`contact-id` AS `contact-id`,
|
||||
`contact`.`uri-id` AS `contact-uri-id`,
|
||||
`contact`.`url` AS `contact-link`,
|
||||
`contact`.`addr` AS `contact-addr`,
|
||||
`contact`.`name` AS `contact-name`,
|
||||
`contact`.`nick` AS `contact-nick`,
|
||||
`contact`.`thumb` AS `contact-avatar`,
|
||||
`contact`.`network` AS `contact-network`,
|
||||
`contact`.`blocked` AS `contact-blocked`,
|
||||
`contact`.`hidden` AS `contact-hidden`,
|
||||
`contact`.`readonly` AS `contact-readonly`,
|
||||
`contact`.`archive` AS `contact-archive`,
|
||||
`contact`.`pending` AS `contact-pending`,
|
||||
`contact`.`rel` AS `contact-rel`,
|
||||
`contact`.`uid` AS `contact-uid`,
|
||||
`contact`.`gsid` AS `contact-gsid`,
|
||||
`contact`.`contact-type` AS `contact-contact-type`,
|
||||
IF (`post-user`.`network` IN ('apub', 'dfrn', 'dspr', 'stat'), true, `contact`.`writable`) AS `writable`,
|
||||
`contact`.`self` AS `self`,
|
||||
`contact`.`id` AS `cid`,
|
||||
`contact`.`alias` AS `alias`,
|
||||
`contact`.`photo` AS `photo`,
|
||||
`contact`.`name-date` AS `name-date`,
|
||||
`contact`.`uri-date` AS `uri-date`,
|
||||
`contact`.`avatar-date` AS `avatar-date`,
|
||||
`contact`.`thumb` AS `thumb`,
|
||||
`post-thread-user`.`author-id` AS `author-id`,
|
||||
`author`.`uri-id` AS `author-uri-id`,
|
||||
`author`.`url` AS `author-link`,
|
||||
`author`.`addr` AS `author-addr`,
|
||||
IF (`contact`.`url` = `author`.`url` AND `contact`.`name` != '', `contact`.`name`, `author`.`name`) AS `author-name`,
|
||||
`author`.`nick` AS `author-nick`,
|
||||
`author`.`alias` AS `author-alias`,
|
||||
IF (`contact`.`url` = `author`.`url` AND `contact`.`thumb` != '', `contact`.`thumb`, `author`.`thumb`) AS `author-avatar`,
|
||||
`author`.`network` AS `author-network`,
|
||||
`author`.`blocked` AS `author-blocked`,
|
||||
`author`.`hidden` AS `author-hidden`,
|
||||
`author`.`updated` AS `author-updated`,
|
||||
`author`.`contact-type` AS `author-contact-type`,
|
||||
`author`.`gsid` AS `author-gsid`,
|
||||
`post-thread-user`.`owner-id` AS `owner-id`,
|
||||
`owner`.`uri-id` AS `owner-uri-id`,
|
||||
`owner`.`url` AS `owner-link`,
|
||||
`owner`.`addr` AS `owner-addr`,
|
||||
IF (`contact`.`url` = `owner`.`url` AND `contact`.`name` != '', `contact`.`name`, `owner`.`name`) AS `owner-name`,
|
||||
`owner`.`nick` AS `owner-nick`,
|
||||
`owner`.`alias` AS `owner-alias`,
|
||||
IF (`contact`.`url` = `owner`.`url` AND `contact`.`thumb` != '', `contact`.`thumb`, `owner`.`thumb`) AS `owner-avatar`,
|
||||
`owner`.`network` AS `owner-network`,
|
||||
`owner`.`blocked` AS `owner-blocked`,
|
||||
`owner`.`hidden` AS `owner-hidden`,
|
||||
`owner`.`updated` AS `owner-updated`,
|
||||
`owner`.`gsid` AS `owner-gsid`,
|
||||
`owner`.`contact-type` AS `owner-contact-type`,
|
||||
`post-thread-user`.`causer-id` AS `causer-id`,
|
||||
`causer`.`uri-id` AS `causer-uri-id`,
|
||||
`causer`.`url` AS `causer-link`,
|
||||
`causer`.`addr` AS `causer-addr`,
|
||||
`causer`.`name` AS `causer-name`,
|
||||
`causer`.`nick` AS `causer-nick`,
|
||||
`causer`.`alias` AS `causer-alias`,
|
||||
`causer`.`thumb` AS `causer-avatar`,
|
||||
`causer`.`network` AS `causer-network`,
|
||||
`causer`.`blocked` AS `causer-blocked`,
|
||||
`causer`.`hidden` AS `causer-hidden`,
|
||||
`causer`.`gsid` AS `causer-gsid`,
|
||||
`causer`.`contact-type` AS `causer-contact-type`,
|
||||
`post-delivery-data`.`postopts` AS `postopts`,
|
||||
`post-delivery-data`.`inform` AS `inform`,
|
||||
`post-delivery-data`.`queue_count` AS `delivery_queue_count`,
|
||||
`post-delivery-data`.`queue_done` AS `delivery_queue_done`,
|
||||
`post-delivery-data`.`queue_failed` AS `delivery_queue_failed`,
|
||||
IF (`post-thread-user`.`psid` IS NULL, '', `permissionset`.`allow_cid`) AS `allow_cid`,
|
||||
IF (`post-thread-user`.`psid` IS NULL, '', `permissionset`.`allow_gid`) AS `allow_gid`,
|
||||
IF (`post-thread-user`.`psid` IS NULL, '', `permissionset`.`deny_cid`) AS `deny_cid`,
|
||||
IF (`post-thread-user`.`psid` IS NULL, '', `permissionset`.`deny_gid`) AS `deny_gid`,
|
||||
`post-user`.`event-id` AS `event-id`,
|
||||
`event`.`created` AS `event-created`,
|
||||
`event`.`edited` AS `event-edited`,
|
||||
`event`.`start` AS `event-start`,
|
||||
`event`.`finish` AS `event-finish`,
|
||||
`event`.`summary` AS `event-summary`,
|
||||
`event`.`desc` AS `event-desc`,
|
||||
`event`.`location` AS `event-location`,
|
||||
`event`.`type` AS `event-type`,
|
||||
`event`.`nofinish` AS `event-nofinish`,
|
||||
`event`.`ignore` AS `event-ignore`,
|
||||
`post-question`.`id` AS `question-id`,
|
||||
`post-question`.`multiple` AS `question-multiple`,
|
||||
`post-question`.`voters` AS `question-voters`,
|
||||
`post-question`.`end-time` AS `question-end-time`,
|
||||
EXISTS(SELECT `uri-id` FROM `post-category` WHERE `post-category`.`uri-id` = `post-thread-user`.`uri-id` AND `post-category`.`uid` = `post-thread-user`.`uid`) AS `has-categories`,
|
||||
EXISTS(SELECT `id` FROM `post-media` WHERE `post-media`.`uri-id` = `post-thread-user`.`uri-id`) AS `has-media`,
|
||||
`diaspora-interaction`.`interaction` AS `signed_text`,
|
||||
`parent-item-uri`.`guid` AS `parent-guid`,
|
||||
`post-thread-user`.`network` AS `parent-network`,
|
||||
`post-thread-user`.`author-id` AS `parent-author-id`,
|
||||
`author`.`url` AS `parent-author-link`,
|
||||
`author`.`name` AS `parent-author-name`,
|
||||
`author`.`nick` AS `parent-author-nick`,
|
||||
`author`.`network` AS `parent-author-network`
|
||||
FROM `post-origin`
|
||||
INNER JOIN `post-thread-user` ON `post-thread-user`.`uri-id` = `post-origin`.`uri-id` AND `post-thread-user`.`uid` = `post-origin`.`uid`
|
||||
INNER JOIN `post-user` ON `post-user`.`id` = `post-origin`.`id`
|
||||
STRAIGHT_JOIN `contact` ON `contact`.`id` = `post-thread-user`.`contact-id`
|
||||
STRAIGHT_JOIN `contact` AS `author` ON `author`.`id` = `post-thread-user`.`author-id`
|
||||
STRAIGHT_JOIN `contact` AS `owner` ON `owner`.`id` = `post-thread-user`.`owner-id`
|
||||
LEFT JOIN `contact` AS `causer` ON `causer`.`id` = `post-thread-user`.`causer-id`
|
||||
LEFT JOIN `item-uri` ON `item-uri`.`id` = `post-origin`.`uri-id`
|
||||
LEFT JOIN `item-uri` AS `thr-parent-item-uri` ON `thr-parent-item-uri`.`id` = `post-origin`.`thr-parent-id`
|
||||
LEFT JOIN `item-uri` AS `parent-item-uri` ON `parent-item-uri`.`id` = `post-origin`.`parent-uri-id`
|
||||
LEFT JOIN `item-uri` AS `conversation-item-uri` ON `conversation-item-uri`.`id` = `post-thread-user`.`conversation-id`
|
||||
LEFT JOIN `item-uri` AS `context-item-uri` ON `context-item-uri`.`id` = `post-thread-user`.`context-id`
|
||||
LEFT JOIN `item-uri` AS `external-item-uri` ON `external-item-uri`.`id` = `post-user`.`external-id`
|
||||
LEFT JOIN `item-uri` AS `replies-item-uri` ON `replies-item-uri`.`id` = `post-user`.`replies-id`
|
||||
LEFT JOIN `verb` ON `verb`.`id` = `post-origin`.`vid`
|
||||
LEFT JOIN `event` ON `event`.`id` = `post-user`.`event-id`
|
||||
LEFT JOIN `diaspora-interaction` ON `diaspora-interaction`.`uri-id` = `post-origin`.`uri-id`
|
||||
LEFT JOIN `post-content` ON `post-content`.`uri-id` = `post-origin`.`uri-id`
|
||||
LEFT JOIN `item-uri` AS `quote-item-uri` ON `quote-item-uri`.`id` = `post-content`.`quote-uri-id`
|
||||
LEFT JOIN `post-delivery-data` ON `post-delivery-data`.`uri-id` = `post-origin`.`uri-id`
|
||||
LEFT JOIN `post-question` ON `post-question`.`uri-id` = `post-origin`.`uri-id`
|
||||
LEFT JOIN `permissionset` ON `permissionset`.`id` = `post-thread-user`.`psid`;
|
||||
|
||||
--
|
||||
-- VIEW post-user-view
|
||||
--
|
||||
|
|
@ -2159,6 +2673,8 @@ CREATE VIEW `post-user-view` AS SELECT
|
|||
`post-user`.`thr-parent-id` AS `thr-parent-id`,
|
||||
`conversation-item-uri`.`uri` AS `conversation`,
|
||||
`post-thread-user`.`conversation-id` AS `conversation-id`,
|
||||
`context-item-uri`.`uri` AS `context`,
|
||||
`post-thread-user`.`context-id` AS `context-id`,
|
||||
`quote-item-uri`.`uri` AS `quote-uri`,
|
||||
`post-content`.`quote-uri-id` AS `quote-uri-id`,
|
||||
`item-uri`.`guid` AS `guid`,
|
||||
|
|
@ -2166,6 +2682,8 @@ CREATE VIEW `post-user-view` AS SELECT
|
|||
`post-user`.`gravity` AS `gravity`,
|
||||
`external-item-uri`.`uri` AS `extid`,
|
||||
`post-user`.`external-id` AS `external-id`,
|
||||
`replies-item-uri`.`uri` AS `replies`,
|
||||
`post-user`.`replies-id` AS `replies-id`,
|
||||
`post-user`.`created` AS `created`,
|
||||
`post-user`.`edited` AS `edited`,
|
||||
`post-thread-user`.`commented` AS `commented`,
|
||||
|
|
@ -2200,6 +2718,7 @@ CREATE VIEW `post-user-view` AS SELECT
|
|||
`post-content`.`location` AS `location`,
|
||||
`post-content`.`coord` AS `coord`,
|
||||
`post-content`.`sensitive` AS `sensitive`,
|
||||
`post-user`.`restrictions` AS `restrictions`,
|
||||
`post-content`.`app` AS `app`,
|
||||
`post-content`.`object-type` AS `object-type`,
|
||||
`post-content`.`object` AS `object`,
|
||||
|
|
@ -2317,7 +2836,9 @@ CREATE VIEW `post-user-view` AS SELECT
|
|||
LEFT JOIN `item-uri` AS `thr-parent-item-uri` ON `thr-parent-item-uri`.`id` = `post-user`.`thr-parent-id`
|
||||
LEFT JOIN `item-uri` AS `parent-item-uri` ON `parent-item-uri`.`id` = `post-user`.`parent-uri-id`
|
||||
LEFT JOIN `item-uri` AS `conversation-item-uri` ON `conversation-item-uri`.`id` = `post-thread-user`.`conversation-id`
|
||||
LEFT JOIN `item-uri` AS `context-item-uri` ON `context-item-uri`.`id` = `post-thread-user`.`context-id`
|
||||
LEFT JOIN `item-uri` AS `external-item-uri` ON `external-item-uri`.`id` = `post-user`.`external-id`
|
||||
LEFT JOIN `item-uri` AS `replies-item-uri` ON `replies-item-uri`.`id` = `post-user`.`replies-id`
|
||||
LEFT JOIN `verb` ON `verb`.`id` = `post-user`.`vid`
|
||||
LEFT JOIN `event` ON `event`.`id` = `post-user`.`event-id`
|
||||
LEFT JOIN `diaspora-interaction` ON `diaspora-interaction`.`uri-id` = `post-user`.`uri-id`
|
||||
|
|
@ -2345,6 +2866,8 @@ CREATE VIEW `post-thread-user-view` AS SELECT
|
|||
`post-user`.`thr-parent-id` AS `thr-parent-id`,
|
||||
`conversation-item-uri`.`uri` AS `conversation`,
|
||||
`post-thread-user`.`conversation-id` AS `conversation-id`,
|
||||
`context-item-uri`.`uri` AS `context`,
|
||||
`post-thread-user`.`context-id` AS `context-id`,
|
||||
`quote-item-uri`.`uri` AS `quote-uri`,
|
||||
`post-content`.`quote-uri-id` AS `quote-uri-id`,
|
||||
`item-uri`.`guid` AS `guid`,
|
||||
|
|
@ -2352,6 +2875,8 @@ CREATE VIEW `post-thread-user-view` AS SELECT
|
|||
`post-user`.`gravity` AS `gravity`,
|
||||
`external-item-uri`.`uri` AS `extid`,
|
||||
`post-user`.`external-id` AS `external-id`,
|
||||
`replies-item-uri`.`uri` AS `replies`,
|
||||
`post-user`.`replies-id` AS `replies-id`,
|
||||
`post-thread-user`.`created` AS `created`,
|
||||
`post-user`.`edited` AS `edited`,
|
||||
`post-thread-user`.`commented` AS `commented`,
|
||||
|
|
@ -2385,6 +2910,7 @@ CREATE VIEW `post-thread-user-view` AS SELECT
|
|||
`post-content`.`location` AS `location`,
|
||||
`post-content`.`coord` AS `coord`,
|
||||
`post-content`.`sensitive` AS `sensitive`,
|
||||
`post-user`.`restrictions` AS `restrictions`,
|
||||
`post-content`.`app` AS `app`,
|
||||
`post-content`.`object-type` AS `object-type`,
|
||||
`post-content`.`object` AS `object`,
|
||||
|
|
@ -2502,7 +3028,9 @@ CREATE VIEW `post-thread-user-view` AS SELECT
|
|||
LEFT JOIN `item-uri` AS `thr-parent-item-uri` ON `thr-parent-item-uri`.`id` = `post-user`.`thr-parent-id`
|
||||
LEFT JOIN `item-uri` AS `parent-item-uri` ON `parent-item-uri`.`id` = `post-user`.`parent-uri-id`
|
||||
LEFT JOIN `item-uri` AS `conversation-item-uri` ON `conversation-item-uri`.`id` = `post-thread-user`.`conversation-id`
|
||||
LEFT JOIN `item-uri` AS `context-item-uri` ON `context-item-uri`.`id` = `post-thread-user`.`context-id`
|
||||
LEFT JOIN `item-uri` AS `external-item-uri` ON `external-item-uri`.`id` = `post-user`.`external-id`
|
||||
LEFT JOIN `item-uri` AS `replies-item-uri` ON `replies-item-uri`.`id` = `post-user`.`replies-id`
|
||||
LEFT JOIN `verb` ON `verb`.`id` = `post-user`.`vid`
|
||||
LEFT JOIN `event` ON `event`.`id` = `post-user`.`event-id`
|
||||
LEFT JOIN `diaspora-interaction` ON `diaspora-interaction`.`uri-id` = `post-thread-user`.`uri-id`
|
||||
|
|
@ -2525,12 +3053,16 @@ CREATE VIEW `post-view` AS SELECT
|
|||
`post`.`thr-parent-id` AS `thr-parent-id`,
|
||||
`conversation-item-uri`.`uri` AS `conversation`,
|
||||
`post-thread`.`conversation-id` AS `conversation-id`,
|
||||
`context-item-uri`.`uri` AS `context`,
|
||||
`post-thread`.`context-id` AS `context-id`,
|
||||
`quote-item-uri`.`uri` AS `quote-uri`,
|
||||
`post-content`.`quote-uri-id` AS `quote-uri-id`,
|
||||
`item-uri`.`guid` AS `guid`,
|
||||
`post`.`gravity` AS `gravity`,
|
||||
`external-item-uri`.`uri` AS `extid`,
|
||||
`post`.`external-id` AS `external-id`,
|
||||
`replies-item-uri`.`uri` AS `replies`,
|
||||
`post`.`replies-id` AS `replies-id`,
|
||||
`post`.`created` AS `created`,
|
||||
`post`.`edited` AS `edited`,
|
||||
`post-thread`.`commented` AS `commented`,
|
||||
|
|
@ -2651,7 +3183,9 @@ CREATE VIEW `post-view` AS SELECT
|
|||
LEFT JOIN `item-uri` AS `thr-parent-item-uri` ON `thr-parent-item-uri`.`id` = `post`.`thr-parent-id`
|
||||
LEFT JOIN `item-uri` AS `parent-item-uri` ON `parent-item-uri`.`id` = `post`.`parent-uri-id`
|
||||
LEFT JOIN `item-uri` AS `conversation-item-uri` ON `conversation-item-uri`.`id` = `post-thread`.`conversation-id`
|
||||
LEFT JOIN `item-uri` AS `context-item-uri` ON `context-item-uri`.`id` = `post-thread`.`context-id`
|
||||
LEFT JOIN `item-uri` AS `external-item-uri` ON `external-item-uri`.`id` = `post`.`external-id`
|
||||
LEFT JOIN `item-uri` AS `replies-item-uri` ON `replies-item-uri`.`id` = `post`.`replies-id`
|
||||
LEFT JOIN `verb` ON `verb`.`id` = `post`.`vid`
|
||||
LEFT JOIN `diaspora-interaction` ON `diaspora-interaction`.`uri-id` = `post`.`uri-id`
|
||||
LEFT JOIN `post-content` ON `post-content`.`uri-id` = `post`.`uri-id`
|
||||
|
|
@ -2672,12 +3206,16 @@ CREATE VIEW `post-thread-view` AS SELECT
|
|||
`post`.`thr-parent-id` AS `thr-parent-id`,
|
||||
`conversation-item-uri`.`uri` AS `conversation`,
|
||||
`post-thread`.`conversation-id` AS `conversation-id`,
|
||||
`context-item-uri`.`uri` AS `context`,
|
||||
`post-thread`.`context-id` AS `context-id`,
|
||||
`quote-item-uri`.`uri` AS `quote-uri`,
|
||||
`post-content`.`quote-uri-id` AS `quote-uri-id`,
|
||||
`item-uri`.`guid` AS `guid`,
|
||||
`post`.`gravity` AS `gravity`,
|
||||
`external-item-uri`.`uri` AS `extid`,
|
||||
`post`.`external-id` AS `external-id`,
|
||||
`replies-item-uri`.`uri` AS `replies`,
|
||||
`post`.`replies-id` AS `replies-id`,
|
||||
`post-thread`.`created` AS `created`,
|
||||
`post`.`edited` AS `edited`,
|
||||
`post-thread`.`commented` AS `commented`,
|
||||
|
|
@ -2800,7 +3338,9 @@ CREATE VIEW `post-thread-view` AS SELECT
|
|||
LEFT JOIN `item-uri` AS `thr-parent-item-uri` ON `thr-parent-item-uri`.`id` = `post`.`thr-parent-id`
|
||||
LEFT JOIN `item-uri` AS `parent-item-uri` ON `parent-item-uri`.`id` = `post`.`parent-uri-id`
|
||||
LEFT JOIN `item-uri` AS `conversation-item-uri` ON `conversation-item-uri`.`id` = `post-thread`.`conversation-id`
|
||||
LEFT JOIN `item-uri` AS `context-item-uri` ON `context-item-uri`.`id` = `post-thread`.`context-id`
|
||||
LEFT JOIN `item-uri` AS `external-item-uri` ON `external-item-uri`.`id` = `post`.`external-id`
|
||||
LEFT JOIN `item-uri` AS `replies-item-uri` ON `replies-item-uri`.`id` = `post`.`replies-id`
|
||||
LEFT JOIN `verb` ON `verb`.`id` = `post`.`vid`
|
||||
LEFT JOIN `diaspora-interaction` ON `diaspora-interaction`.`uri-id` = `post-thread`.`uri-id`
|
||||
LEFT JOIN `post-content` ON `post-content`.`uri-id` = `post-thread`.`uri-id`
|
||||
|
|
@ -2876,36 +3416,6 @@ CREATE VIEW `tag-view` AS SELECT
|
|||
LEFT JOIN `tag` ON `post-tag`.`tid` = `tag`.`id`
|
||||
LEFT JOIN `contact` ON `post-tag`.`cid` = `contact`.`id`;
|
||||
|
||||
--
|
||||
-- VIEW network-item-view
|
||||
--
|
||||
DROP VIEW IF EXISTS `network-item-view`;
|
||||
CREATE VIEW `network-item-view` AS SELECT
|
||||
`post-user`.`uri-id` AS `uri-id`,
|
||||
`post-thread-user`.`post-user-id` AS `parent`,
|
||||
`post-user`.`received` AS `received`,
|
||||
`post-thread-user`.`commented` AS `commented`,
|
||||
`post-user`.`created` AS `created`,
|
||||
`post-user`.`uid` AS `uid`,
|
||||
`post-thread-user`.`starred` AS `starred`,
|
||||
`post-thread-user`.`mention` AS `mention`,
|
||||
`post-user`.`network` AS `network`,
|
||||
`post-user`.`unseen` AS `unseen`,
|
||||
`post-user`.`gravity` AS `gravity`,
|
||||
`post-user`.`contact-id` AS `contact-id`,
|
||||
`ownercontact`.`contact-type` AS `contact-type`
|
||||
FROM `post-user`
|
||||
INNER JOIN `post-thread-user` ON `post-thread-user`.`uri-id` = `post-user`.`parent-uri-id` AND `post-thread-user`.`uid` = `post-user`.`uid`
|
||||
STRAIGHT_JOIN `contact` ON `contact`.`id` = `post-thread-user`.`contact-id`
|
||||
STRAIGHT_JOIN `contact` AS `authorcontact` ON `authorcontact`.`id` = `post-thread-user`.`author-id`
|
||||
STRAIGHT_JOIN `contact` AS `ownercontact` ON `ownercontact`.`id` = `post-thread-user`.`owner-id`
|
||||
WHERE `post-user`.`visible` AND NOT `post-user`.`deleted`
|
||||
AND (NOT `contact`.`readonly` AND NOT `contact`.`blocked` AND NOT `contact`.`pending`)
|
||||
AND (`post-user`.`hidden` IS NULL OR NOT `post-user`.`hidden`)
|
||||
AND NOT `authorcontact`.`blocked` AND NOT `ownercontact`.`blocked`
|
||||
AND NOT EXISTS(SELECT `cid` FROM `user-contact` WHERE `uid` = `post-thread-user`.`uid` AND `cid` IN (`authorcontact`.`id`, `ownercontact`.`id`) AND (`blocked` OR `ignored`))
|
||||
AND NOT EXISTS(SELECT `gsid` FROM `user-gserver` WHERE `uid` = `post-thread-user`.`uid` AND `gsid` IN (`authorcontact`.`gsid`, `ownercontact`.`gsid`) AND `ignored`);
|
||||
|
||||
--
|
||||
-- VIEW network-thread-view
|
||||
--
|
||||
|
|
@ -2931,8 +3441,36 @@ CREATE VIEW `network-thread-view` AS SELECT
|
|||
AND (NOT `contact`.`readonly` AND NOT `contact`.`blocked` AND NOT `contact`.`pending`)
|
||||
AND (`post-thread-user`.`hidden` IS NULL OR NOT `post-thread-user`.`hidden`)
|
||||
AND NOT `authorcontact`.`blocked` AND NOT `ownercontact`.`blocked`
|
||||
AND NOT EXISTS(SELECT `cid` FROM `user-contact` WHERE `uid` = `post-thread-user`.`uid` AND `cid` IN (`authorcontact`.`id`, `ownercontact`.`id`) AND (`blocked` OR `ignored`))
|
||||
AND NOT EXISTS(SELECT `gsid` FROM `user-gserver` WHERE `uid` = `post-thread-user`.`uid` AND `gsid` IN (`authorcontact`.`gsid`, `ownercontact`.`gsid`) AND `ignored`);
|
||||
AND NOT EXISTS(SELECT `cid` FROM `user-contact` WHERE `uid` = `post-thread-user`.`uid` AND `cid` IN (`post-thread-user`.`author-id`, `post-thread-user`.`owner-id`, `post-thread-user`.`causer-id`) AND (`blocked` OR `ignored` OR `channel-only`))
|
||||
AND NOT EXISTS(SELECT `gsid` FROM `user-gserver` WHERE `uid` = `post-thread-user`.`uid` AND `gsid` IN (`authorcontact`.`gsid`, `ownercontact`.`gsid`) AND `ignored`);
|
||||
|
||||
--
|
||||
-- VIEW network-thread-circle-view
|
||||
--
|
||||
DROP VIEW IF EXISTS `network-thread-circle-view`;
|
||||
CREATE VIEW `network-thread-circle-view` AS SELECT
|
||||
`post-thread-user`.`uri-id` AS `uri-id`,
|
||||
`post-thread-user`.`post-user-id` AS `parent`,
|
||||
`post-thread-user`.`received` AS `received`,
|
||||
`post-thread-user`.`commented` AS `commented`,
|
||||
`post-thread-user`.`created` AS `created`,
|
||||
`post-thread-user`.`uid` AS `uid`,
|
||||
`post-thread-user`.`starred` AS `starred`,
|
||||
`post-thread-user`.`mention` AS `mention`,
|
||||
`post-thread-user`.`network` AS `network`,
|
||||
`post-thread-user`.`contact-id` AS `contact-id`,
|
||||
`ownercontact`.`contact-type` AS `contact-type`
|
||||
FROM `post-thread-user`
|
||||
INNER JOIN `post-user` ON `post-user`.`id` = `post-thread-user`.`post-user-id`
|
||||
STRAIGHT_JOIN `contact` ON `contact`.`id` = `post-thread-user`.`contact-id`
|
||||
STRAIGHT_JOIN `contact` AS `authorcontact` ON `authorcontact`.`id` = `post-thread-user`.`author-id`
|
||||
STRAIGHT_JOIN `contact` AS `ownercontact` ON `ownercontact`.`id` = `post-thread-user`.`owner-id`
|
||||
WHERE `post-user`.`visible` AND NOT `post-user`.`deleted`
|
||||
AND (NOT `contact`.`readonly` AND NOT `contact`.`blocked` AND NOT `contact`.`pending`)
|
||||
AND (`post-thread-user`.`hidden` IS NULL OR NOT `post-thread-user`.`hidden`)
|
||||
AND NOT `authorcontact`.`blocked` AND NOT `ownercontact`.`blocked`
|
||||
AND NOT EXISTS(SELECT `cid` FROM `user-contact` WHERE `uid` = `post-thread-user`.`uid` AND `cid` IN (`post-thread-user`.`author-id`, `post-thread-user`.`owner-id`, `post-thread-user`.`causer-id`) AND (`blocked` OR `ignored`))
|
||||
AND NOT EXISTS(SELECT `gsid` FROM `user-gserver` WHERE `uid` = `post-thread-user`.`uid` AND `gsid` IN (`authorcontact`.`gsid`, `ownercontact`.`gsid`) AND `ignored`);
|
||||
|
||||
--
|
||||
-- VIEW owner-view
|
||||
|
|
|
|||
|
|
@ -850,10 +850,6 @@ Here is a complete list of all hook callbacks with file locations (as of 24-Sep-
|
|||
Hook::callAll('register_account', $uid);
|
||||
Hook::callAll('remove_user', $user);
|
||||
|
||||
### src/Module/Notifications/Ping.php
|
||||
|
||||
Hook::callAll('network_ping', $arr);
|
||||
|
||||
### src/Module/PermissionTooltip.php
|
||||
|
||||
Hook::callAll('lockview_content', $item);
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ Predefined Channels
|
|||
|
||||
* For you: Posts from contacts you interact with and who interact with you. In detail, it consists of:
|
||||
* Posts from people you interact with on a more than average level.
|
||||
* Posts from the accounts that you follow with a more than average number of interactions-
|
||||
* Posts from the accounts that you follow with a more than average number of interactions.
|
||||
* Posts from accounts where you activated "notify on new posts" or where you have set the channel frequency accordingly.
|
||||
* Discover: Posts from contacts you don't follow, but that might be of interest for you to follow. In detail, it consists of:
|
||||
* Posts from people you don't follow but you interact with on a more than average level.
|
||||
|
|
@ -48,11 +48,11 @@ Each channel is defined by these values:
|
|||
* Label: This value is mandatory and is used for the menu label.
|
||||
* Description: A short description of the content. This can help to keep the overview, when you have got a lot of channels.
|
||||
* Access Key: When you want to access this channel via an access key, you can define it here. Pay attention to not use an already used one.
|
||||
* Circle: This defines the data source for this channel. By default it is set to the public timeline. There are some predefined values, like the accounts that you follow or the accounts that follow you. Also all of your circles can be selected.
|
||||
* Circle: This defines the data source for this channel. By default it is set to the public timeline. There are some predefined values, like the accounts that you follow or the accounts that follow you. Also all of your circles can be selected.
|
||||
* Include Tags: Comma separated list of tags. A post will be used when it contains any of the listed tags.
|
||||
* Exclude Tags: Comma separated list of tags. If a post contain any of these tags, then it will not be part of nthis channel.
|
||||
* Exclude Tags: Comma separated list of tags. If a post contain any of these tags, then it will not be part of this channel.
|
||||
* Full Text Search: This can be used to include or exclude content, based on the content and some additional keywords. It uses the "boolean mode" operators from MariaDB: https://mariadb.com/kb/en/full-text-index-overview/#in-boolean-mode
|
||||
* Images, Videos, Audio: When selected, you will see content with the selected media type. This can be combined. If none of these fields are checked, you will see any content, with or without attacked media.
|
||||
* Images, Videos, Audio: When selected, you will see content with the selected media type. This can be combined. If none of these fields are checked, you will see any content, with or without attached media.
|
||||
|
||||
Additional keywords for the full text search
|
||||
---
|
||||
|
|
@ -61,8 +61,8 @@ Additionally to the search for content, there are keywords that can be used in t
|
|||
Alternatives are presented with "|".
|
||||
|
||||
* from - Use "from:nickname" or "from:nickname@domain.tld" to search for posts from a specific author.
|
||||
* to - Use "from:nickname" or "from:nickname@domain.tld" to search for posts with the given contact as receiver.
|
||||
* group - Use "group:nickname" or "group:nickname@domain.tld" to search for group post of the given group.
|
||||
* to - Use "to:nickname" or "to:nickname@domain.tld" to search for posts with the given contact as receiver.
|
||||
* group - Use "group:nickname" or "group:nickname@domain.tld" to search for posts of the given group.
|
||||
* application | relay - Use "application:nickname" or "application:nickname@domain.tld" to search for posts that had been reshared by the given relay application.
|
||||
* server - Use "server:hostname" to search for posts from a specific server. In the case of group postings, the search text contains both the hostname of the group server and the author's hostname.
|
||||
* source - The ActivityPub type of the post source. Use this for example to include or exclude group posts or posts from services (aka bots).
|
||||
|
|
@ -93,7 +93,7 @@ Alternatives are presented with "|".
|
|||
* visibility:public
|
||||
* visibility:unlisted
|
||||
* visibility:private
|
||||
* language | lang - Use "language:code" to search for posts with the given language in the [ISO 639-1](https://en.wikipedia.org/wiki/ISO_639-1) format.
|
||||
* language | lang - Use "language:code" to search for posts with the given language in the [ISO 639-1](https://en.wikipedia.org/wiki/ISO_639-1) format.
|
||||
|
||||
Remember that you can combine these kerywords.
|
||||
So for example you can create a channel with all posts that talk about the Fediverse - that aren't posted in the Fediverse with the search terms: "fediverse -network:apub -network:dfrn"
|
||||
Remember that you can combine these keywords.
|
||||
So for example you can create a channel with all posts that talk about the Fediverse - that aren't posted in the Fediverse with the search terms: "fediverse -network:apub -network:dfrn"
|
||||
|
|
|
|||
26
doc/Home.md
|
|
@ -1,5 +1,5 @@
|
|||
Friendica Documentation and Resources
|
||||
=====================================
|
||||
Help
|
||||
====
|
||||
|
||||
**User Manual**
|
||||
|
||||
|
|
@ -64,18 +64,18 @@ Friendica Documentation and Resources
|
|||
* [Database schema documentation](help/database)
|
||||
* [Class Autoloading](help/autoloader)
|
||||
|
||||
**External Resources**
|
||||
**Links**
|
||||
|
||||
* Website: [https://friendi.ca](https://friendi.ca)
|
||||
* Help Group: [@helpers@forum.friendi.ca](https://forum.friendi.ca/~helpers)
|
||||
* XMPP: [support@forum.friendi.ca](xmpp:support@forum.friendi.ca?join)
|
||||
* IRC: [https://web.libera.chat/?channels=#friendica](https://web.libera.chat/?channels=#friendica)
|
||||
* Matrix: [https://matrix.to/#/#friendi.ca:matrix.org](https://matrix.to/#/#friendi.ca:matrix.org)
|
||||
* Mailing List: [https://mailman.friendi.ca/mailman/listinfo/support-friendi.ca](http://mailman.friendi.ca/mailman/listinfo/support-friendi.ca)
|
||||
|
||||
* [Main Website](https://friendi.ca)
|
||||
* Ways to get Support
|
||||
* Friendica Support Group: [@helpers@forum.friendi.ca](https://forum.friendi.ca/~helpers)
|
||||
* [Mailing List Archive](http://mailman.friendi.ca/mailman/listinfo/support-friendi.ca) you can subscribe to the list by sending an email to ``support-request(at)friendi.ca?subject=subscribe``
|
||||
* Community chat rooms (the IRC, Matrix and XMPP rooms are bridged) these public chats are logged [from IRC](https://gnusociarg.nsupdate.info/2021/%23friendica/) and [Matrix](https://view.matrix.org/alias/%23friendi.ca:matrix.org/)
|
||||
* XMPP/Jabber MUC: support(at)forum.friendi.ca
|
||||
* IRC: #friendica at [libera.chat](https://web.libera.chat/?channels=#friendica)
|
||||
* Matrix: [#friendi.ca](https://matrix.to/#/#friendi.ca:matrix.org) or [#friendica-en](https://matrix.to/#/#friendica-en:matrix.org) at matrix.org
|
||||
|
||||
**About**
|
||||
|
||||
* [Site/Version Info](friendica)
|
||||
* [Friendica Credits](credits)
|
||||
* [Server Information](friendica)
|
||||
* [Terms of Service](tos)
|
||||
* [Credits](credits)
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ Due to the large variety of operating systems and PHP platforms in existence we
|
|||
* Apache with mod-rewrite enabled and "Options All" so you can use a local `.htaccess` file
|
||||
* PHP 7.4+
|
||||
* PHP *command line* access with register_argc_argv set to true in the php.ini file
|
||||
* Curl, GD, GMP, PDO, mbstrings, MySQLi, hash, xml, zip, IntlChar and OpenSSL extensions
|
||||
* Curl, GD, GMP, PDO, mbstrings, MySQLi, hash, xml, zip, IntlChar, IDN and OpenSSL extensions
|
||||
* The POSIX module of PHP needs to be activated (e.g. [RHEL, CentOS](http://www.bigsoft.co.uk/blog/index.php/2014/12/08/posix-php-commands-not-working-under-centos-7) have disabled it)
|
||||
* Some form of email server or email gateway such that PHP mail() works.
|
||||
If you cannot set up your own email server, you can use the [phpmailer](https://github.com/friendica/friendica-addons/tree/develop/phpmailer) addon and use a remote SMTP server.
|
||||
|
|
|
|||
|
|
@ -107,5 +107,7 @@ They think they are a friend.
|
|||
You can also "block" a person.
|
||||
This completely blocks communications with that person.
|
||||
They may still be able to see your public posts, as can anybody in the world, but they cannot communicate with you directly.
|
||||
Friendica will notify their server that you have blocked them, although normally that server should not notify them individually.
|
||||
However, there are various simple ways they can deduce that they have been blocked if they investigate.
|
||||
|
||||
You can also delete a friend no matter what the friendship status - which completely removes everything relating to that person from your website.
|
||||
|
|
|
|||
|
|
@ -68,6 +68,7 @@ Database Tables
|
|||
| [post-history](help/database/db_post-history) | Post history |
|
||||
| [post-link](help/database/db_post-link) | Post related external links |
|
||||
| [post-media](help/database/db_post-media) | Attached media |
|
||||
| [post-origin](help/database/db_post-origin) | Posts from local users |
|
||||
| [post-question](help/database/db_post-question) | Question |
|
||||
| [post-question-option](help/database/db_post-question-option) | Question option |
|
||||
| [post-searchindex](help/database/db_post-searchindex) | Content for all posts |
|
||||
|
|
|
|||
|
|
@ -6,37 +6,41 @@ Global servers
|
|||
Fields
|
||||
------
|
||||
|
||||
| Field | Description | Type | Null | Key | Default | Extra |
|
||||
| --------------------- | -------------------------------------------------- | ---------------- | ---- | --- | ------------------- | -------------- |
|
||||
| id | sequential ID | int unsigned | NO | PRI | NULL | auto_increment |
|
||||
| url | | varbinary(383) | NO | | | |
|
||||
| nurl | | varbinary(383) | NO | | | |
|
||||
| version | | varchar(255) | NO | | | |
|
||||
| site_name | | varchar(255) | NO | | | |
|
||||
| info | | text | YES | | NULL | |
|
||||
| register_policy | | tinyint | NO | | 0 | |
|
||||
| registered-users | Number of registered users | int unsigned | NO | | 0 | |
|
||||
| active-week-users | Number of active users in the last week | int unsigned | YES | | NULL | |
|
||||
| active-month-users | Number of active users in the last month | int unsigned | YES | | NULL | |
|
||||
| active-halfyear-users | Number of active users in the last six month | int unsigned | YES | | NULL | |
|
||||
| local-posts | Number of local posts | int unsigned | YES | | NULL | |
|
||||
| local-comments | Number of local comments | int unsigned | YES | | NULL | |
|
||||
| directory-type | Type of directory service (Poco, Mastodon) | tinyint | YES | | 0 | |
|
||||
| poco | | varbinary(383) | NO | | | |
|
||||
| noscrape | | varbinary(383) | NO | | | |
|
||||
| network | | char(4) | NO | | | |
|
||||
| protocol | The protocol of the server | tinyint unsigned | YES | | NULL | |
|
||||
| platform | | varchar(255) | NO | | | |
|
||||
| relay-subscribe | Has the server subscribed to the relay system | boolean | NO | | 0 | |
|
||||
| relay-scope | The scope of messages that the server wants to get | varchar(10) | NO | | | |
|
||||
| detection-method | Method that had been used to detect that server | tinyint unsigned | YES | | NULL | |
|
||||
| created | | datetime | NO | | 0001-01-01 00:00:00 | |
|
||||
| last_poco_query | | datetime | YES | | 0001-01-01 00:00:00 | |
|
||||
| last_contact | Last successful connection request | datetime | YES | | 0001-01-01 00:00:00 | |
|
||||
| last_failure | Last failed connection request | datetime | YES | | 0001-01-01 00:00:00 | |
|
||||
| blocked | Server is blocked | boolean | YES | | NULL | |
|
||||
| failed | Connection failed | boolean | YES | | NULL | |
|
||||
| next_contact | Next connection request | datetime | YES | | 0001-01-01 00:00:00 | |
|
||||
| Field | Description | Type | Null | Key | Default | Extra |
|
||||
| --------------------- | -------------------------------------------------------------- | ---------------- | ---- | --- | ------------------- | -------------- |
|
||||
| id | sequential ID | int unsigned | NO | PRI | NULL | auto_increment |
|
||||
| url | | varbinary(383) | NO | | | |
|
||||
| nurl | | varbinary(383) | NO | | | |
|
||||
| version | The version of this server software. | varchar(255) | NO | | | |
|
||||
| site_name | | varchar(255) | NO | | | |
|
||||
| info | | text | YES | | NULL | |
|
||||
| register_policy | | tinyint | NO | | 0 | |
|
||||
| registered-users | Number of registered users | int unsigned | NO | | 0 | |
|
||||
| active-week-users | Number of active users in the last week | int unsigned | YES | | NULL | |
|
||||
| active-month-users | Number of active users in the last month | int unsigned | YES | | NULL | |
|
||||
| active-halfyear-users | Number of active users in the last six month | int unsigned | YES | | NULL | |
|
||||
| local-posts | Number of local posts | int unsigned | YES | | NULL | |
|
||||
| local-comments | Number of local comments | int unsigned | YES | | NULL | |
|
||||
| directory-type | Type of directory service (Poco, Mastodon) | tinyint | YES | | 0 | |
|
||||
| poco | | varbinary(383) | NO | | | |
|
||||
| openwebauth | Path to the OpenWebAuth endpoint | varbinary(383) | YES | | NULL | |
|
||||
| authredirect | Path to the authRedirect endpoint | varbinary(383) | YES | | NULL | |
|
||||
| noscrape | | varbinary(383) | NO | | | |
|
||||
| network | | char(4) | NO | | | |
|
||||
| protocol | The protocol of the server | tinyint unsigned | YES | | NULL | |
|
||||
| platform | The canonical name of this server software. | varchar(255) | NO | | | |
|
||||
| repository | The url of the source code repository of this server software. | varbinary(383) | YES | | NULL | |
|
||||
| homepage | The url of the homepage of this server software. | varbinary(383) | YES | | NULL | |
|
||||
| relay-subscribe | Has the server subscribed to the relay system | boolean | NO | | 0 | |
|
||||
| relay-scope | The scope of messages that the server wants to get | varchar(10) | NO | | | |
|
||||
| detection-method | Method that had been used to detect that server | tinyint unsigned | YES | | NULL | |
|
||||
| created | | datetime | NO | | 0001-01-01 00:00:00 | |
|
||||
| last_poco_query | | datetime | YES | | 0001-01-01 00:00:00 | |
|
||||
| last_contact | Last successful connection request | datetime | YES | | 0001-01-01 00:00:00 | |
|
||||
| last_failure | Last failed connection request | datetime | YES | | 0001-01-01 00:00:00 | |
|
||||
| blocked | Server is blocked | boolean | YES | | NULL | |
|
||||
| failed | Connection failed | boolean | YES | | NULL | |
|
||||
| next_contact | Next connection request | datetime | YES | | 0001-01-01 00:00:00 | |
|
||||
|
||||
Indexes
|
||||
------------
|
||||
|
|
|
|||
|
|
@ -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
|
||||
------------
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ Fields
|
|||
| uri-id | Id of the item-uri table entry that contains the item uri | int unsigned | NO | PRI | NULL | |
|
||||
| owner-id | Item owner | int unsigned | NO | | 0 | |
|
||||
| contact-type | Person, organisation, news, community, relay | tinyint | NO | | 0 | |
|
||||
| media-type | Type of media in a bit array (1 = image, 2 = video, 4 = audio | tinyint | NO | | 0 | |
|
||||
| media-type | Type of media in a bit array (1 = image, 2 = video, 4 = audio) | tinyint | NO | | 0 | |
|
||||
| language | Language information about this post in the ISO 639-1 format | char(2) | YES | | NULL | |
|
||||
| searchtext | Simplified text for the full text search | mediumtext | YES | | NULL | |
|
||||
| size | Body size | int unsigned | YES | | NULL | |
|
||||
|
|
|
|||
48
doc/database/db_post-origin.md
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
Table post-origin
|
||||
===========
|
||||
|
||||
Posts from local users
|
||||
|
||||
Fields
|
||||
------
|
||||
|
||||
| Field | Description | Type | Null | Key | Default | Extra |
|
||||
| ------------- | ------------------------------------------------------------ | ------------------ | ---- | --- | ------------------- | ----- |
|
||||
| id | | int unsigned | NO | PRI | NULL | |
|
||||
| uri-id | Id of the item-uri table entry that contains the item uri | int unsigned | NO | | NULL | |
|
||||
| uid | Owner id which owns this copy of the item | mediumint unsigned | NO | | NULL | |
|
||||
| parent-uri-id | Id of the item-uri table that contains the parent uri | int unsigned | YES | | NULL | |
|
||||
| thr-parent-id | Id of the item-uri table that contains the thread parent uri | int unsigned | YES | | NULL | |
|
||||
| created | Creation timestamp. | datetime | NO | | 0001-01-01 00:00:00 | |
|
||||
| received | datetime | datetime | NO | | 0001-01-01 00:00:00 | |
|
||||
| gravity | | tinyint unsigned | NO | | 0 | |
|
||||
| vid | Id of the verb table entry that contains the activity verbs | smallint unsigned | YES | | NULL | |
|
||||
| private | 0=public, 1=private, 2=unlisted | tinyint unsigned | NO | | 0 | |
|
||||
| wall | This item was posted to the wall of uid | boolean | NO | | 0 | |
|
||||
|
||||
Indexes
|
||||
------------
|
||||
|
||||
| Name | Fields |
|
||||
| ----------------- | ------------------- |
|
||||
| PRIMARY | id |
|
||||
| uid_uri-id | UNIQUE, uid, uri-id |
|
||||
| uri-id | uri-id |
|
||||
| parent-uri-id | parent-uri-id |
|
||||
| thr-parent-id | thr-parent-id |
|
||||
| vid | vid |
|
||||
| parent-uri-id_uid | parent-uri-id, uid |
|
||||
| uid_wall_received | uid, wall, received |
|
||||
|
||||
Foreign Keys
|
||||
------------
|
||||
|
||||
| Field | Target Table | Target Field |
|
||||
|-------|--------------|--------------|
|
||||
| uri-id | [item-uri](help/database/db_item-uri) | id |
|
||||
| uid | [user](help/database/db_user) | uid |
|
||||
| parent-uri-id | [item-uri](help/database/db_item-uri) | id |
|
||||
| thr-parent-id | [item-uri](help/database/db_item-uri) | id |
|
||||
| vid | [verb](help/database/db_verb) | id |
|
||||
|
||||
Return to [database documentation](help/database)
|
||||
|
|
@ -10,7 +10,7 @@ Fields
|
|||
| ---------- | --------------------------------------------------------------------- | ------------ | ---- | --- | ------- | ----- |
|
||||
| uri-id | Id of the item-uri table entry that contains the item uri | int unsigned | NO | PRI | NULL | |
|
||||
| owner-id | Item owner | int unsigned | NO | | 0 | |
|
||||
| media-type | Type of media in a bit array (1 = image, 2 = video, 4 = audio | tinyint | NO | | 0 | |
|
||||
| media-type | Type of media in a bit array (1 = image, 2 = video, 4 = audio) | tinyint | NO | | 0 | |
|
||||
| language | Language information about this post in the ISO 639-1 format | char(2) | YES | | NULL | |
|
||||
| searchtext | Simplified text for the full text search | mediumtext | YES | | NULL | |
|
||||
| size | Body size | int unsigned | YES | | NULL | |
|
||||
|
|
|
|||
|
|
@ -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 |
|
||||
|
|
|
|||
|
|
@ -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 |
|
||||
|
|
|
|||
|
|
@ -6,38 +6,40 @@ User specific post data
|
|||
Fields
|
||||
------
|
||||
|
||||
| Field | Description | Type | Null | Key | Default | Extra |
|
||||
| ----------------- | --------------------------------------------------------------------------------- | ------------------ | ---- | --- | ------------------- | -------------- |
|
||||
| id | | int unsigned | NO | PRI | NULL | auto_increment |
|
||||
| uri-id | Id of the item-uri table entry that contains the item uri | int unsigned | NO | | NULL | |
|
||||
| parent-uri-id | Id of the item-uri table that contains the parent uri | int unsigned | YES | | NULL | |
|
||||
| thr-parent-id | Id of the item-uri table that contains the thread parent uri | int unsigned | YES | | NULL | |
|
||||
| external-id | Id of the item-uri table entry that contains the external uri | int unsigned | YES | | NULL | |
|
||||
| created | Creation timestamp. | datetime | NO | | 0001-01-01 00:00:00 | |
|
||||
| edited | Date of last edit (default is created) | datetime | NO | | 0001-01-01 00:00:00 | |
|
||||
| received | datetime | datetime | NO | | 0001-01-01 00:00:00 | |
|
||||
| gravity | | tinyint unsigned | NO | | 0 | |
|
||||
| network | Network from where the item comes from | char(4) | NO | | | |
|
||||
| owner-id | Link to the contact table with uid=0 of the owner of this item | int unsigned | NO | | 0 | |
|
||||
| author-id | Link to the contact table with uid=0 of the author of this item | int unsigned | NO | | 0 | |
|
||||
| causer-id | Link to the contact table with uid=0 of the contact that caused the item creation | int unsigned | YES | | NULL | |
|
||||
| post-type | Post type (personal note, image, article, ...) | tinyint unsigned | NO | | 0 | |
|
||||
| post-reason | Reason why the post arrived at the user | tinyint unsigned | NO | | 0 | |
|
||||
| vid | Id of the verb table entry that contains the activity verbs | smallint unsigned | YES | | NULL | |
|
||||
| private | 0=public, 1=private, 2=unlisted | tinyint unsigned | NO | | 0 | |
|
||||
| global | | boolean | NO | | 0 | |
|
||||
| visible | | boolean | NO | | 0 | |
|
||||
| deleted | item has been marked for deletion | boolean | NO | | 0 | |
|
||||
| uid | Owner id which owns this copy of the item | mediumint unsigned | NO | | NULL | |
|
||||
| protocol | Protocol used to deliver the item for this user | tinyint unsigned | YES | | NULL | |
|
||||
| contact-id | contact.id | int unsigned | NO | | 0 | |
|
||||
| event-id | Used to link to the event.id | int unsigned | YES | | NULL | |
|
||||
| unseen | post has not been seen | boolean | NO | | 1 | |
|
||||
| hidden | Marker to hide the post from the user | boolean | NO | | 0 | |
|
||||
| notification-type | | smallint unsigned | NO | | 0 | |
|
||||
| wall | This item was posted to the wall of uid | boolean | NO | | 0 | |
|
||||
| origin | item originated at this site | boolean | NO | | 0 | |
|
||||
| psid | ID of the permission set of this post | int unsigned | YES | | NULL | |
|
||||
| Field | Description | Type | Null | Key | Default | Extra |
|
||||
| ----------------- | ------------------------------------------------------------------------------------ | ------------------ | ---- | --- | ------------------- | -------------- |
|
||||
| id | | int unsigned | NO | PRI | NULL | auto_increment |
|
||||
| uri-id | Id of the item-uri table entry that contains the item uri | int unsigned | NO | | NULL | |
|
||||
| parent-uri-id | Id of the item-uri table that contains the parent uri | int unsigned | YES | | NULL | |
|
||||
| thr-parent-id | Id of the item-uri table that contains the thread parent uri | int unsigned | YES | | NULL | |
|
||||
| external-id | Id of the item-uri table entry that contains the external uri | int unsigned | YES | | NULL | |
|
||||
| replies-id | Id of the item-uri table entry that contains the endpoint for the replies collection | int unsigned | YES | | NULL | |
|
||||
| created | Creation timestamp. | datetime | NO | | 0001-01-01 00:00:00 | |
|
||||
| edited | Date of last edit (default is created) | datetime | NO | | 0001-01-01 00:00:00 | |
|
||||
| received | datetime | datetime | NO | | 0001-01-01 00:00:00 | |
|
||||
| gravity | | tinyint unsigned | NO | | 0 | |
|
||||
| network | Network from where the item comes from | char(4) | NO | | | |
|
||||
| owner-id | Link to the contact table with uid=0 of the owner of this item | int unsigned | NO | | 0 | |
|
||||
| author-id | Link to the contact table with uid=0 of the author of this item | int unsigned | NO | | 0 | |
|
||||
| causer-id | Link to the contact table with uid=0 of the contact that caused the item creation | int unsigned | YES | | NULL | |
|
||||
| post-type | Post type (personal note, image, article, ...) | tinyint unsigned | NO | | 0 | |
|
||||
| post-reason | Reason why the post arrived at the user | tinyint unsigned | NO | | 0 | |
|
||||
| vid | Id of the verb table entry that contains the activity verbs | smallint unsigned | YES | | NULL | |
|
||||
| private | 0=public, 1=private, 2=unlisted | tinyint unsigned | NO | | 0 | |
|
||||
| restrictions | Bit array of post restrictions (1 = Reply, 2 = Like, 4 = Announce) | tinyint unsigned | YES | | NULL | |
|
||||
| global | | boolean | NO | | 0 | |
|
||||
| visible | | boolean | NO | | 0 | |
|
||||
| deleted | item has been marked for deletion | boolean | NO | | 0 | |
|
||||
| uid | Owner id which owns this copy of the item | mediumint unsigned | NO | | NULL | |
|
||||
| protocol | Protocol used to deliver the item for this user | tinyint unsigned | YES | | NULL | |
|
||||
| contact-id | contact.id | int unsigned | NO | | 0 | |
|
||||
| event-id | Used to link to the event.id | int unsigned | YES | | NULL | |
|
||||
| unseen | post has not been seen | boolean | NO | | 1 | |
|
||||
| hidden | Marker to hide the post from the user | boolean | NO | | 0 | |
|
||||
| notification-type | | smallint unsigned | NO | | 0 | |
|
||||
| wall | This item was posted to the wall of uid | boolean | NO | | 0 | |
|
||||
| origin | item originated at this site | boolean | NO | | 0 | |
|
||||
| psid | ID of the permission set of this post | int unsigned | YES | | NULL | |
|
||||
|
||||
Indexes
|
||||
------------
|
||||
|
|
@ -50,6 +52,7 @@ Indexes
|
|||
| parent-uri-id | parent-uri-id |
|
||||
| thr-parent-id | thr-parent-id |
|
||||
| external-id | external-id |
|
||||
| replies-id | replies-id |
|
||||
| owner-id | owner-id |
|
||||
| author-id | author-id |
|
||||
| causer-id | causer-id |
|
||||
|
|
@ -76,6 +79,7 @@ Foreign Keys
|
|||
| parent-uri-id | [item-uri](help/database/db_item-uri) | id |
|
||||
| thr-parent-id | [item-uri](help/database/db_item-uri) | id |
|
||||
| external-id | [item-uri](help/database/db_item-uri) | id |
|
||||
| replies-id | [item-uri](help/database/db_item-uri) | id |
|
||||
| owner-id | [contact](help/database/db_contact) | id |
|
||||
| author-id | [contact](help/database/db_contact) | id |
|
||||
| causer-id | [contact](help/database/db_contact) | id |
|
||||
|
|
|
|||
|
|
@ -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 |
|
||||
|
|
|
|||
|
|
@ -6,29 +6,30 @@ User specific public contact data
|
|||
Fields
|
||||
------
|
||||
|
||||
| Field | Description | Type | Null | Key | Default | Extra |
|
||||
| ------------------------- | ----------------------------------------------------------------------- | ------------------ | ---- | --- | ------- | ----- |
|
||||
| cid | Contact id of the linked public contact | int unsigned | NO | PRI | 0 | |
|
||||
| uid | User id | mediumint unsigned | NO | PRI | 0 | |
|
||||
| uri-id | Id of the item-uri table entry that contains the contact url | int unsigned | YES | | NULL | |
|
||||
| blocked | Contact is completely blocked for this user | boolean | YES | | NULL | |
|
||||
| ignored | Posts from this contact are ignored | boolean | YES | | NULL | |
|
||||
| collapsed | Posts from this contact are collapsed | boolean | YES | | NULL | |
|
||||
| hidden | This contact is hidden from the others | boolean | YES | | NULL | |
|
||||
| is-blocked | User is blocked by this contact | boolean | YES | | NULL | |
|
||||
| channel-frequency | Controls the frequency of the appearance of this contact in channels | tinyint unsigned | YES | | NULL | |
|
||||
| pending | | boolean | YES | | NULL | |
|
||||
| rel | The kind of the relation between the user and the contact | tinyint unsigned | YES | | NULL | |
|
||||
| info | | mediumtext | YES | | NULL | |
|
||||
| notify_new_posts | | boolean | YES | | NULL | |
|
||||
| remote_self | 0 => No mirroring, 1-2 => Mirror as own post, 3 => Mirror as reshare | tinyint unsigned | YES | | NULL | |
|
||||
| fetch_further_information | 0 => None, 1 => Fetch information, 3 => Fetch keywords, 2 => Fetch both | tinyint unsigned | YES | | NULL | |
|
||||
| ffi_keyword_denylist | | text | YES | | NULL | |
|
||||
| subhub | | boolean | YES | | NULL | |
|
||||
| hub-verify | | varbinary(383) | YES | | NULL | |
|
||||
| protocol | Protocol of the contact | char(4) | YES | | NULL | |
|
||||
| rating | Automatically detected feed poll frequency | tinyint | YES | | NULL | |
|
||||
| priority | Feed poll priority | tinyint unsigned | YES | | NULL | |
|
||||
| Field | Description | Type | Null | Key | Default | Extra |
|
||||
| ------------------------- | -------------------------------------------------------------------------- | ------------------ | ---- | --- | ------- | ----- |
|
||||
| cid | Contact id of the linked public contact | int unsigned | NO | PRI | 0 | |
|
||||
| uid | User id | mediumint unsigned | NO | PRI | 0 | |
|
||||
| uri-id | Id of the item-uri table entry that contains the contact url | int unsigned | YES | | NULL | |
|
||||
| blocked | Contact is completely blocked for this user | boolean | YES | | NULL | |
|
||||
| ignored | Posts from this contact are ignored | boolean | YES | | NULL | |
|
||||
| collapsed | Posts from this contact are collapsed | boolean | YES | | NULL | |
|
||||
| hidden | This contact is hidden from the others | boolean | YES | | NULL | |
|
||||
| channel-only | This contact is displayed only in channels, but not in the network stream. | boolean | YES | | NULL | |
|
||||
| is-blocked | User is blocked by this contact | boolean | YES | | NULL | |
|
||||
| channel-frequency | Controls the frequency of the appearance of this contact in channels | tinyint unsigned | YES | | NULL | |
|
||||
| pending | | boolean | YES | | NULL | |
|
||||
| rel | The kind of the relation between the user and the contact | tinyint unsigned | YES | | NULL | |
|
||||
| info | | mediumtext | YES | | NULL | |
|
||||
| notify_new_posts | | boolean | YES | | NULL | |
|
||||
| remote_self | 0 => No mirroring, 1-2 => Mirror as own post, 3 => Mirror as reshare | tinyint unsigned | YES | | NULL | |
|
||||
| fetch_further_information | 0 => None, 1 => Fetch information, 3 => Fetch keywords, 2 => Fetch both | tinyint unsigned | YES | | NULL | |
|
||||
| ffi_keyword_denylist | | text | YES | | NULL | |
|
||||
| subhub | | boolean | YES | | NULL | |
|
||||
| hub-verify | | varbinary(383) | YES | | NULL | |
|
||||
| protocol | Protocol of the contact | char(4) | YES | | NULL | |
|
||||
| rating | Automatically detected feed poll frequency | tinyint | YES | | NULL | |
|
||||
| priority | Feed poll priority | tinyint unsigned | YES | | NULL | |
|
||||
|
||||
Indexes
|
||||
------------
|
||||
|
|
|
|||
|
|
@ -418,10 +418,6 @@ Eine komplette Liste aller Hook-Callbacks mit den zugehörigen Dateien (am 01-Ap
|
|||
Hook::callAll('storage_instance', $data);
|
||||
Hook::callAll('storage_config', $data);
|
||||
|
||||
### src/Module/Notifications/Ping.php
|
||||
|
||||
Hook::callAll('network_ping', $arr);
|
||||
|
||||
### src/Module/PermissionTooltip.php
|
||||
|
||||
Hook::callAll('lockview_content', $item);
|
||||
|
|
|
|||
99
doc/de/Channels.md
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
Kanäle (Channels)
|
||||
=====
|
||||
|
||||
* [Home](help)
|
||||
|
||||
Kanäle sind eine Möglichkeit neue Inhalte zu finden, oder Inhalte anzuzeigen, die du sonst möglicherweise verpasst hättest.
|
||||
Es gibt mehrere vordefinierte Kanäle und zusätzlich kannst du deine eigenen, basierend auf ein paar Regeln, erstellen.
|
||||
Kanäle zeigen nur Beiträge aus den letzten 24 Stunden an. (Dieser Wert kann vom Administrator geändert werden.)
|
||||
|
||||
In den Anzeige-Einstellungen, im Bereich "Timelines", kannst du definieren, welche Kanäle und andere Timelines du im "Kanäle"-Widget auf der "Network"-Seite sehen möchtest und welche Kanäle in der Menüleiste oben auf der Seite erscheinen sollen.
|
||||
|
||||
Ebenfalls in den Anzeige-Einstellungen, im Bereich "Kanäle", kannst du alle die Sprachen einstellen, die du in deinen Kanälen sehen möchtest. Hier kannst du mehr als eine Sprache auswählen.
|
||||
|
||||
Auf der Profilseite kannst du die Kanal-Frequenz für jeden Kontakt definieren. Die Optionen sind:
|
||||
|
||||
* Standardhäufigkeit: Beiträge dieses Kontakts werden im "Für Dich"-Kanal angezeigt, wenn du häufig mit diesem Kontakt interagiert hast oder wenn ein Beitrag ein gewisses Maß an Interaktion erreicht hat.
|
||||
* Alle Beiträge dieses Kontakts anzeigen: Alle Beiträge dieses Kontakts werden auf dem Kanal "Für Dich" erscheinen
|
||||
* Zeige nur einige Beiträge an: Wenn ein Kontakt viele Beiträge in einem kurzen Zeitraum erstellt, reduziert diese Einstellung die Anzahl der angezeigten Beiträge in jedem Kanal.
|
||||
* Zeige keine Beiträge an: Beiträge von diesem Kontakt werden in keinem Kanal angezeigt.
|
||||
|
||||
Voreingestellte Kanäle
|
||||
---
|
||||
|
||||
* Für Dich: Beiträge von Kontakten mit denen du interagierst und die mit dir interagieren. Im Detail bestehend aus:
|
||||
* Beiträge von Leuten, mit denen du überdurchschnittlich viel interagierst.
|
||||
* Beiträge von Kontakten, denen du folgst und mit denen du überdurchschnittlich viel interagierst.
|
||||
* Beiträge von Kontakten, bei denen du "Benachrichtigung bei neuen Beiträgen" aktiviert hast oder wo du die Kanalfrequenz entsprechend eingestellt hast.
|
||||
* Entdecken: Beiträge von Kontakten denen du nicht folgst, aber denen zu folgen für dich interessant sein könnte. Im Detail bestehend aus:
|
||||
* Beiträge von Leuten denen du nicht folgst, aber mit denen du überdurchschnittlich viel interagierst.
|
||||
* Beiträge von Leuten denen du nicht folgst, aber die mit dir überdurchschnittlich viel interagieren.
|
||||
* Beliebte Beiträge von Leuten denen du nicht folgst, aber mit denen du interagiert hast oder die mit dir interagiert haben.
|
||||
* Angesagt: Beiträge mit überdurchschnittlich hoher Anzahl von Interaktionen.
|
||||
* Sprache: Beiträge in deiner Sprache.
|
||||
* Folgende: Beiträge von Leuten die dir folgen, aber denen du nicht folgst.
|
||||
* Geteilt von teilenden: Beiträge von Kontakten denen die Leute folgen, denen du folgst.
|
||||
* Ruhige teilende: Beiträge von Konten denen du folgst, aber die nicht sehr oft posten.
|
||||
* Bilder: Beiträge mit Bildern.
|
||||
* Audio: Beiträge mit Audio.
|
||||
* Videos: Beiträge mit Videos.
|
||||
|
||||
Vom Benutzer eingestellte Kanäle
|
||||
---
|
||||
|
||||
In den Einstellungen, unter "Kanäle", kannst du deine eigenen Kanäle erstellen.
|
||||
|
||||
Jeder Kanal wird durch diese Werte definiert:
|
||||
|
||||
* Bezeichnung: Dieses Feld ist notwendig und wird für die Kanalbezeichnung verwendet.
|
||||
* Beschreibung: Eine kurze Beschreibung des Inhalts. Dies kann helfen den Überblick zu behalten, wenn du viele Kanäle hast.
|
||||
* Zugriffsschlüssel: Wenn du auf diesen Kanal über einen Zugriffsschlüssel zugreifen willst, kannst du ihn hier festlegen. Achte darauf, dass du nicht einen bereits verwendeten Schlüssel benutzt.
|
||||
* Circle/Kanal: Dies definiert die Datenquelle für diesen Kanal. Voreingestellt ist die Globale Gemeinschaft. Es gibt ein paar vorgegebene Werte, wie die Konten denen du folgst, oder die Kontakte, die dir folgen. Außerdem können alle deine Circles ausgewählt werden.
|
||||
* Tags einschließen: Durch Kommata getrennte Liste von Tags. Ein Beitrag wird verwendet, wenn er eines der aufgeführten Tags enthält.
|
||||
* Tags ausschließen: Durch Kommata getrennte Liste von Tags. Wenn ein Beitrag eines dieser Tags enthält, wird er nicht Teil dieses Kanals sein.
|
||||
* Volltextsuche: Dies kann genutzt werden um Inhalte, basierend auf dem Inhalt und ein paar zusätzlichen Schlüsselwörtern, ein- oder auszuschließen. Es nutzt die "boolean mode"-Operatoren von MariaDB: https://mariadb.com/kb/en/full-text-index-overview/#in-boolean-mode
|
||||
* Bilder, Videos, Audio: Wenn ausgewählt, wirst du Inhalte mit dem gewählten Medientyp sehen. Diese Optionen können kombiniert werden. Wenn keines dieser Felder ausgewählt wurde, wirst du alle Inhalte, mit oder ohne angefügten Medien, sehen.
|
||||
|
||||
Zusätzliche Schlüsselwörter für die Volltextsuche
|
||||
---
|
||||
|
||||
Zusätzlich zu der Suche nach Inhalten, gibt es Schlüsselwörter, die in der Volltextsuche genutzt werden können.
|
||||
Alternativen werden durch "|" dargestellt.
|
||||
|
||||
* from - Verwende "from:nickname" oder "from:nickname@domain.tld" um nach Beiträgen von einem bestimmten Autor zu suchen.
|
||||
* to - Verwende "to:nickname" oder "to:nickname@domain.tld" um nach Beiträgen mit dem gegebenen Empfänger zu suchen.
|
||||
* group - Verwende "group:nickname" oder "group:nickname@domain.tld" um nach Beiträgen aus der gegebenen Gruppe zu suchen.
|
||||
* application | relay - Nutze "application:nickname" oder "application:nickname@domain.tld" um Beiträge zu finden, die von der gegebenen relay application geteilt wurden.
|
||||
* server - Verwende "server:hostname" um Beiträge von einem bestimmten Server zu suchen. Im Falle eine Gruppen-Postings enthält der Suchtext beides, den Hostname des Gruppen-Servers und den Hostname des Autors.
|
||||
* source - Der ActivityPub-Typ der Beitragsquelle. Nutze dies um beispielsweise Gruppenpostings oder Beiträge von Services (aka Bots) ein- oder auszuschließen.
|
||||
* source:person - Der Beitrag wurde von einem regulären Nutzerkonto erstellt.
|
||||
* source:organization - Der Beitrag wurde von einer Organisation erstellt.
|
||||
* source:group - Dieser Beitrag wurde über eine Gruppe erstellt oder verteilt.
|
||||
* source:service | source:news - Dieser Beitrag stammt aus einem 'service' Account. Dieser Quellen(source)-Typ wird oft genutzt um Bot Accounts zu markieren.
|
||||
* source:application | source:relay - Dieser Beitrag wurde von einer Anwendung (application) erstellt. Dies wird im Fediverse höchstwahrscheinlich für die Beitragserstellung nicht genutzt.
|
||||
* tag - Nutze "tag:tagname" um nach einem bestimmten tag (Schlagwort) zu suchen.
|
||||
* media - Mit diesem Schlüsselwort kannst du nach angefügten Medien suchen.
|
||||
* media:image | media:photo | media:picture - Dieser Beitrag enthält ein Bild
|
||||
* media:video - Dieser Beitrag enthält ein Video
|
||||
* media:audio - Dieser Beitrag enthält Audio
|
||||
* media:card - Dieser Beitrag enthält eine Linkvorschau-'card'
|
||||
* media:post - Dieser Beitrag verweist auf einen anderen Beitrag, was bedeutet, es ist ein zitierter Beitrag
|
||||
* network | net - Verwende dies um Netzwerke in deinen Kanal einzuschließen oder von ihm auszuschließen.
|
||||
* network:apub | network:activitypub - ActivityPub (verwendet von den Systemen im Fediverse)
|
||||
* network:dfrn | network:friendica - altes Friendica-Protokoll. Heutzutage nutzt Friendica meist ActivityPub.
|
||||
* network:dspr | network:diaspora - Das Diaspora-Protokoll wird hauptsächlich von Diaspora selbst genutzt. Ein paar andere Systeme unterstützen dieses Protokoll ebenfalls, wie Hubzilla, Socialhome or Ganggo.
|
||||
* network:feed - RSS/Atom feeds
|
||||
* network:mail - Mails die via IMAP importiert worden sind.
|
||||
* network:stat | network:ostatus - Das OStatus-Protokoll wird hauptsächlich von alten GNU Social-Installationen genutzt.
|
||||
* network:dscs | network:discourse - Beiträge, die über den Discourse connector empfangen werden.
|
||||
* network:tmbl | network:tumblr - Beiträge, die über den Tumblr connector empfangen werden.
|
||||
* network:bsky | network:bluesky - Beiträge, die über den Bluesky connector empfangen werden.
|
||||
* platform - Benutze dies, um Plattformen in deinen Kanal einzuschließen, oder von ihm auszuschließen, d.h. "+platform:friendica". Im Falle eines Gruppen-Postings enthält der Suchtext beides, die Plattform des Gruppen-Servers und die Plattform des Autors.
|
||||
* visibility - Du hast die Wahl zwischen verschiedenen Sichtbarkeiten. Du kannst nur die ungelisteten oder privaten Beiträge sehen, zu denen du Zugang hast.
|
||||
* visibility:public - (öffentlich)
|
||||
* visibility:unlisted - (ungelistet)
|
||||
* visibility:private - (privat)
|
||||
* language | lang - Verwende "language:code" um nach Beiträgen in der gewünschten Sprache (im [ISO 639-1](https://en.wikipedia.org/wiki/ISO_639-1) format) zu suchen.
|
||||
|
||||
Denke daran, dass du diese Schlüsselwörter kombinieren kannst.
|
||||
So kannst du zum Beispiel einen Kanal erstellen, mit allen Beiträgen, die über das Fediverse sprechen, aber nicht im Fediverse veröffentlich wurden, mit diesen Suchbegriffen: "fediverse -network:apub -network:dfrn".
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
Friendica - Dokumentation und Ressourcen
|
||||
=====================================
|
||||
Hilfe
|
||||
=====
|
||||
|
||||
**Inhalte**
|
||||
**Dokumentation für Benutzer**
|
||||
|
||||
* Allgemeine Funktionen - Erste Schritte
|
||||
* [Account - Basics](help/Account-Basics)
|
||||
|
|
@ -17,7 +17,7 @@ Friendica - Dokumentation und Ressourcen
|
|||
* [Circles und Privatsphäre](help/Circles-and-Privacy)
|
||||
* [Tags und Erwähnungen](help/Tags-and-Mentions)
|
||||
* [Community-Gruppen](help/Groups)
|
||||
* [Channels](help/Channels)
|
||||
* [Kanäle (Channels)](help/Channels)
|
||||
* [Chats](help/Chats)
|
||||
* Weiterführende Informationen
|
||||
* [Account umziehen](help/Move-Account)
|
||||
|
|
@ -60,18 +60,17 @@ Friendica - Dokumentation und Ressourcen
|
|||
* [Translation of Friendica](help/translations) (EN)
|
||||
* [Run tests](help/Tests) (EN)
|
||||
|
||||
**Externe Ressourcen**
|
||||
**Links**
|
||||
|
||||
* [Haupt-Webseite](https://friendi.ca)
|
||||
* Support Kanäle
|
||||
* Friendica Support Gruppe: [@helpers@forum.friendi.ca](https://forum.friendi.ca/~helpers)
|
||||
* [Mailing Listen Archiv](http://mailman.friendi.ca/mailman/listinfo/support-friendi.ca) zum Abonnieren der Liste eine E-Mail an ``support-request(at)friendi.ca?subject=subscribe`` senden
|
||||
* Chats der Friendica Community (die IRC, Matrix und XMPP Räume sind mit einer Brücke verbunden) Logs dieser öffentlichen Chaträume können [hier aus dem IRC](https://gnusociarg.nsupdate.info/2021/%23frie) und [hier aus der Matrix](https://view.matrix.org/alias/%23friendi.ca:matrix.org/) gefunden werden.
|
||||
* XMPP/Jabber MUC: support(at)forum.friendi.ca
|
||||
* IRC: #friendica auf [libera.chat](https://web.libera.chat/?channels=#friendica)
|
||||
* Matrix: [#friendi.ca](https://matrix.to/#/#friendi.ca:matrix.org) oder [#friendica-en](https://matrix.to/#/#friendica-en:matrix.org) auf matrix.org
|
||||
* Website: [https://friendi.ca](https://friendi.ca)
|
||||
* Help Group: [@helpers@forum.friendi.ca](https://forum.friendi.ca/~helpers)
|
||||
* XMPP: [support@forum.friendi.ca](xmpp:support@forum.friendi.ca?join)
|
||||
* IRC: [https://web.libera.chat/?channels=#friendica](https://web.libera.chat/?channels=#friendica)
|
||||
* Matrix: [https://matrix.to/#/#friendi.ca:matrix.org](https://matrix.to/#/#friendi.ca:matrix.org)
|
||||
* Mailing List: [https://mailman.friendi.ca/mailman/listinfo/support-friendi.ca](http://mailman.friendi.ca/mailman/listinfo/support-friendi.ca)
|
||||
|
||||
**Über diese Seite**
|
||||
**Über**
|
||||
|
||||
* [Seite/Friendica-Version](friendica)
|
||||
* [Mitwirkenden bei Friendica](credits)
|
||||
* [Server Information](friendica)
|
||||
* [Nutzungsbedingungen](tos)
|
||||
* [Mitwirkende](credits)
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ Requirements
|
|||
* Apache mit einer aktiverten mod-rewrite-Funktion und dem Eintrag "Options All", so dass du die lokale .htaccess-Datei nutzen kannst
|
||||
* PHP 7.4+
|
||||
* PHP *Kommandozeilen*-Zugang mit register_argc_argv auf "true" gesetzt in der php.ini-Datei
|
||||
* Curl, GD, GMP, PDO, mbstrings, MySQLi, hash, xml, zip, IntlChar and OpenSSL-Erweiterung
|
||||
* Curl, GD, GMP, PDO, mbstrings, MySQLi, hash, xml, zip, IntlChar, IDN und OpenSSL-Erweiterung
|
||||
* Das POSIX Modul muss aktiviert sein ([CentOS, RHEL](http://www.bigsoft.co.uk/blog/index.php/2014/12/08/posix-php-commands-not-working-under-centos-7http://www.bigsoft.co.uk/blog/index.php/2014/12/08/posix-php-commands-not-working-under-centos-7) haben dies z.B. deaktiviert)
|
||||
* Einen E-Mail Server, so dass PHP `mail()` funktioniert.
|
||||
Wenn kein eigener E-Mail Server zur Verfügung steht, kann alternativ das [phpmailer](https://github.com/friendica/friendica-addons/tree/develop/phpmailer) Addon mit einem externen SMTP Account verwendet werden.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 7.8 KiB After Width: | Height: | Size: 7.6 KiB |
|
Before Width: | Height: | Size: 185 KiB After Width: | Height: | Size: 150 KiB |
|
Before Width: | Height: | Size: 166 KiB After Width: | Height: | Size: 138 KiB |
|
Before Width: | Height: | Size: 400 KiB After Width: | Height: | Size: 371 KiB |
|
Before Width: | Height: | Size: 1,011 KiB After Width: | Height: | Size: 707 KiB |
24
mod/item.php
|
|
@ -45,7 +45,8 @@ use Friendica\Model\Post;
|
|||
use Friendica\Network\HTTPException;
|
||||
use Friendica\Util\DateTimeFormat;
|
||||
|
||||
function item_post(App $a) {
|
||||
function item_post()
|
||||
{
|
||||
$uid = DI::userSession()->getLocalUserId();
|
||||
|
||||
if (!$uid) {
|
||||
|
|
@ -233,13 +234,15 @@ function item_insert(int $uid, array $request, bool $preview, string $return_pat
|
|||
|
||||
function item_process(array $post, array $request, bool $preview, string $return_path): array
|
||||
{
|
||||
$post['self'] = true;
|
||||
$post['api_source'] = false;
|
||||
$post['attach'] = '';
|
||||
$post['title'] = trim($request['title'] ?? '');
|
||||
$post['body'] = $request['body'] ?? '';
|
||||
$post['location'] = trim($request['location'] ?? '');
|
||||
$post['coord'] = trim($request['coord'] ?? '');
|
||||
$post['self'] = true;
|
||||
$post['api_source'] = false;
|
||||
$post['attach'] = '';
|
||||
$post['title'] = trim($request['title'] ?? '');
|
||||
$post['content-warning'] = trim($request['summary'] ?? '');
|
||||
$post['sensitive'] = !empty($request['sensitive'] ?? false);
|
||||
$post['body'] = $request['body'] ?? '';
|
||||
$post['location'] = trim($request['location'] ?? '');
|
||||
$post['coord'] = trim($request['coord'] ?? '');
|
||||
|
||||
$post = DI::contentItem()->addCategories($post, $request['category'] ?? '');
|
||||
|
||||
|
|
@ -248,7 +251,7 @@ function item_process(array $post, array $request, bool $preview, string $return
|
|||
$post['body'] .= DI::contentItem()->storeAttachmentFromRequest($request);
|
||||
}
|
||||
|
||||
$post = DI::contentItem()->finalizePost($post);
|
||||
$post = DI::contentItem()->finalizePost($post, $preview);
|
||||
|
||||
if (!strlen($post['body'])) {
|
||||
if ($preview) {
|
||||
|
|
@ -279,13 +282,14 @@ function item_process(array $post, array $request, bool $preview, string $return
|
|||
$post['body'] = BBCode::removeSharedData(Item::setHashtags($post['body']));
|
||||
$post['writable'] = true;
|
||||
$post['sensitive'] = false;
|
||||
$post['post-reason'] = Item::PR_LOCAL;
|
||||
|
||||
$o = DI::conversation()->render([$post], Conversation::MODE_SEARCH, false, true);
|
||||
|
||||
System::jsonExit(['preview' => $o]);
|
||||
}
|
||||
|
||||
Hook::callAll('post_local',$post);
|
||||
Hook::callAll('post_local', $post);
|
||||
|
||||
unset($post['edit']);
|
||||
unset($post['self']);
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@
|
|||
*
|
||||
*/
|
||||
|
||||
use Friendica\App;
|
||||
use Friendica\Content\Nav;
|
||||
use Friendica\Content\Pager;
|
||||
use Friendica\Content\Text\BBCode;
|
||||
|
|
@ -34,7 +33,7 @@ use Friendica\Util\DateTimeFormat;
|
|||
use Friendica\Util\Strings;
|
||||
use Friendica\Util\Temporal;
|
||||
|
||||
function message_init(App $a)
|
||||
function message_init()
|
||||
{
|
||||
$tabs = '';
|
||||
|
||||
|
|
@ -61,7 +60,7 @@ function message_init(App $a)
|
|||
]);
|
||||
}
|
||||
|
||||
function message_post(App $a)
|
||||
function message_post()
|
||||
{
|
||||
if (!DI::userSession()->getLocalUserId()) {
|
||||
DI::sysmsg()->addNotice(DI::l10n()->t('Permission denied.'));
|
||||
|
|
@ -104,7 +103,7 @@ function message_post(App $a)
|
|||
}
|
||||
}
|
||||
|
||||
function message_content(App $a)
|
||||
function message_content()
|
||||
{
|
||||
$o = '';
|
||||
Nav::setSelected('messages');
|
||||
|
|
@ -114,7 +113,7 @@ function message_content(App $a)
|
|||
return Login::form();
|
||||
}
|
||||
|
||||
$myprofile = DI::baseUrl() . '/profile/' . $a->getLoggedInUserNickname();
|
||||
$myprofile = DI::baseUrl() . '/profile/' . DI::userSession()->getLocalUserNickname();
|
||||
|
||||
$tpl = Renderer::getMarkupTemplate('mail_head.tpl');
|
||||
if (DI::args()->getArgc() > 1 && DI::args()->getArgv()[1] == 'new') {
|
||||
|
|
@ -177,7 +176,7 @@ function message_content(App $a)
|
|||
|
||||
$tpl = Renderer::getMarkupTemplate('msg-header.tpl');
|
||||
DI::page()['htmlhead'] .= Renderer::replaceMacros($tpl, [
|
||||
'$nickname' => $a->getLoggedInUserNickname(),
|
||||
'$nickname' => DI::userSession()->getLocalUserNickname(),
|
||||
'$linkurl' => DI::l10n()->t('Please enter a link URL:')
|
||||
]);
|
||||
|
||||
|
|
@ -282,7 +281,7 @@ function message_content(App $a)
|
|||
|
||||
$tpl = Renderer::getMarkupTemplate('msg-header.tpl');
|
||||
DI::page()['htmlhead'] .= Renderer::replaceMacros($tpl, [
|
||||
'$nickname' => $a->getLoggedInUserNickname(),
|
||||
'$nickname' => DI::userSession()->getLocalUserNickname(),
|
||||
'$linkurl' => DI::l10n()->t('Please enter a link URL:')
|
||||
]);
|
||||
|
||||
|
|
@ -415,12 +414,10 @@ function get_messages(int $uid, int $start, int $limit): array
|
|||
|
||||
function render_messages(array $msg, string $t): string
|
||||
{
|
||||
$a = DI::app();
|
||||
|
||||
$tpl = Renderer::getMarkupTemplate($t);
|
||||
$rslt = '';
|
||||
|
||||
$myprofile = DI::baseUrl() . '/profile/' . $a->getLoggedInUserNickname();
|
||||
$myprofile = DI::baseUrl() . '/profile/' . DI::userSession()->getLocalUserNickname();
|
||||
|
||||
foreach ($msg as $rr) {
|
||||
if ($rr['unknown']) {
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ function notes_content(App $a, bool $update = false)
|
|||
return;
|
||||
}
|
||||
|
||||
$o = BaseProfile::getTabsHTML('notes', true, $a->getLoggedInUserNickname(), false);
|
||||
$o = BaseProfile::getTabsHTML('notes', true, DI::userSession()->getLocalUserNickname(), false);
|
||||
|
||||
if (!$update) {
|
||||
$o .= '<h3>' . DI::l10n()->t('Personal Notes') . '</h3>';
|
||||
|
|
|
|||
|
|
@ -19,11 +19,9 @@
|
|||
*
|
||||
*/
|
||||
|
||||
use Friendica\App;
|
||||
use Friendica\Content\Nav;
|
||||
use Friendica\Content\Pager;
|
||||
use Friendica\Content\Text\BBCode;
|
||||
use Friendica\Content\Widget;
|
||||
use Friendica\Core\ACL;
|
||||
use Friendica\Core\Addon;
|
||||
use Friendica\Core\Hook;
|
||||
|
|
@ -44,6 +42,7 @@ use Friendica\Module\BaseProfile;
|
|||
use Friendica\Network\HTTPException;
|
||||
use Friendica\Network\Probe;
|
||||
use Friendica\Protocol\Activity;
|
||||
use Friendica\Protocol\ActivityNamespace;
|
||||
use Friendica\Security\Security;
|
||||
use Friendica\Util\Crypto;
|
||||
use Friendica\Util\DateTimeFormat;
|
||||
|
|
@ -53,7 +52,7 @@ use Friendica\Util\Strings;
|
|||
use Friendica\Util\Temporal;
|
||||
use Friendica\Util\XML;
|
||||
|
||||
function photos_init(App $a)
|
||||
function photos_init()
|
||||
{
|
||||
if (DI::config()->get('system', 'block_public') && !DI::userSession()->isAuthenticated()) {
|
||||
return;
|
||||
|
|
@ -67,8 +66,6 @@ function photos_init(App $a)
|
|||
throw new HTTPException\NotFoundException(DI::l10n()->t('User not found.'));
|
||||
}
|
||||
|
||||
$is_owner = (DI::userSession()->getLocalUserId() && (DI::userSession()->getLocalUserId() == $owner['uid']));
|
||||
|
||||
$albums = Photo::getAlbums($owner['uid']);
|
||||
|
||||
$albums_visible = ((intval($owner['hidewall']) && !DI::userSession()->isAuthenticated()) ? false : true);
|
||||
|
|
@ -125,7 +122,7 @@ function photos_init(App $a)
|
|||
return;
|
||||
}
|
||||
|
||||
function photos_post(App $a)
|
||||
function photos_post()
|
||||
{
|
||||
$user = User::getByNickname(DI::args()->getArgv()[1]);
|
||||
if (!DBA::isResult($user)) {
|
||||
|
|
@ -136,7 +133,7 @@ function photos_post(App $a)
|
|||
$visitor = 0;
|
||||
|
||||
$page_owner_uid = intval($user['uid']);
|
||||
$community_page = $user['page-flags'] == User::PAGE_FLAGS_COMMUNITY;
|
||||
$community_page = in_array($user['page-flags'], [User::PAGE_FLAGS_COMMUNITY, User::PAGE_FLAGS_COMM_MAN]);
|
||||
|
||||
if (DI::userSession()->getLocalUserId() && (DI::userSession()->getLocalUserId() == $page_owner_uid)) {
|
||||
$can_post = true;
|
||||
|
|
@ -200,7 +197,7 @@ function photos_post(App $a)
|
|||
// Update the photo albums cache
|
||||
Photo::clearAlbumCache($page_owner_uid);
|
||||
|
||||
DI::baseUrl()->redirect('photos/' . $a->getLoggedInUserNickname() . '/album/' . bin2hex($newalbum));
|
||||
DI::baseUrl()->redirect('photos/' . DI::userSession()->getLocalUserNickname() . '/album/' . bin2hex($newalbum));
|
||||
return; // NOTREACHED
|
||||
}
|
||||
|
||||
|
|
@ -412,7 +409,7 @@ function photos_post(App $a)
|
|||
|
||||
if (count($links)) {
|
||||
foreach ($links as $link) {
|
||||
if ($link['@attributes']['rel'] === 'http://webfinger.net/rel/profile-page') {
|
||||
if ($link['@attributes']['rel'] === ActivityNamespace::WEBFINGERPROFILE) {
|
||||
$profile = $link['@attributes']['href'];
|
||||
}
|
||||
|
||||
|
|
@ -559,7 +556,7 @@ function photos_post(App $a)
|
|||
}
|
||||
}
|
||||
|
||||
function photos_content(App $a)
|
||||
function photos_content()
|
||||
{
|
||||
// URLs:
|
||||
// photos/name/upload
|
||||
|
|
@ -618,7 +615,7 @@ function photos_content(App $a)
|
|||
|
||||
$owner_uid = $user['uid'];
|
||||
|
||||
$community_page = (($user['page-flags'] == User::PAGE_FLAGS_COMMUNITY) ? true : false);
|
||||
$community_page = in_array($user['page-flags'], [User::PAGE_FLAGS_COMMUNITY, User::PAGE_FLAGS_COMM_MAN]);
|
||||
|
||||
if (DI::userSession()->getLocalUserId() && (DI::userSession()->getLocalUserId() == $owner_uid)) {
|
||||
$can_post = true;
|
||||
|
|
@ -672,18 +669,14 @@ function photos_content(App $a)
|
|||
|
||||
$selname = (!is_null($datum) && Strings::isHex($datum)) ? hex2bin($datum) : '';
|
||||
|
||||
$albumselect = '';
|
||||
$albumselect = ['' => '<current year>'];
|
||||
|
||||
$albumselect .= '<option value="" ' . (!$selname ? ' selected="selected" ' : '') . '><current year></option>';
|
||||
$albums = Photo::getAlbums($owner_uid);
|
||||
if (!empty($albums)) {
|
||||
foreach ($albums as $album) {
|
||||
if ($album['album'] === '') {
|
||||
continue;
|
||||
}
|
||||
$selected = (($selname === $album['album']) ? ' selected="selected" ' : '');
|
||||
$albumselect .= '<option value="' . $album['album'] . '"' . $selected . '>' . $album['album'] . '</option>';
|
||||
foreach (Photo::getAlbums($owner_uid) as $album) {
|
||||
if ($album['album'] === '') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$albumselect[$album['album']] = $album['album'];
|
||||
}
|
||||
|
||||
$uploader = '';
|
||||
|
|
@ -718,7 +711,7 @@ function photos_content(App $a)
|
|||
|
||||
$tpl = Renderer::getMarkupTemplate('photos_upload.tpl');
|
||||
|
||||
$aclselect_e = ($visitor ? '' : ACL::getFullSelectorHTML(DI::page(), $a->getLoggedInUserId()));
|
||||
$aclselect_e = ($visitor ? '' : ACL::getFullSelectorHTML(DI::page(), DI::userSession()->getLocalUserId()));
|
||||
|
||||
$o .= Renderer::replaceMacros($tpl, [
|
||||
'$pagename' => DI::l10n()->t('Upload Photos'),
|
||||
|
|
@ -729,9 +722,10 @@ function photos_content(App $a)
|
|||
'$existalbumtext' => DI::l10n()->t('or select existing album:'),
|
||||
'$nosharetext' => DI::l10n()->t('Do not show a status post for this upload'),
|
||||
'$albumselect' => $albumselect,
|
||||
'$selname' => $selname,
|
||||
'$permissions' => DI::l10n()->t('Permissions'),
|
||||
'$aclselect' => $aclselect_e,
|
||||
'$lockstate' => ACL::getLockstateForUserId($a->getLoggedInUserId()) ? 'lock' : 'unlock',
|
||||
'$lockstate' => ACL::getLockstateForUserId(DI::userSession()->getLocalUserId()) ? 'lock' : 'unlock',
|
||||
'$alt_uploader' => $ret['addon_text'],
|
||||
'$default_upload_box' => ($ret['default_upload'] ? $default_upload_box : ''),
|
||||
'$default_upload_submit' => ($ret['default_upload'] ? $default_upload_submit : ''),
|
||||
|
|
@ -1077,7 +1071,7 @@ function photos_content(App $a)
|
|||
|
||||
$album_e = $ph[0]['album'];
|
||||
$caption_e = $ph[0]['desc'];
|
||||
$aclselect_e = ACL::getFullSelectorHTML(DI::page(), $a->getLoggedInUserId(), false, ACL::getDefaultUserPermissions($ph[0]));
|
||||
$aclselect_e = ACL::getFullSelectorHTML(DI::page(), DI::userSession()->getLocalUserId(), false, ACL::getDefaultUserPermissions($ph[0]));
|
||||
|
||||
$edit = Renderer::replaceMacros($edit_tpl, [
|
||||
'$id' => $ph[0]['id'],
|
||||
|
|
|
|||
119
src/App.php
|
|
@ -24,6 +24,7 @@ namespace Friendica;
|
|||
use Exception;
|
||||
use Friendica\App\Arguments;
|
||||
use Friendica\App\BaseURL;
|
||||
use Friendica\App\Request;
|
||||
use Friendica\Capabilities\ICanCreateResponses;
|
||||
use Friendica\Content\Nav;
|
||||
use Friendica\Core\Config\Factory\Config;
|
||||
|
|
@ -39,10 +40,10 @@ use Friendica\Core\L10n;
|
|||
use Friendica\Core\System;
|
||||
use Friendica\Core\Theme;
|
||||
use Friendica\Database\Database;
|
||||
use Friendica\Model\Contact;
|
||||
use Friendica\Model\Profile;
|
||||
use Friendica\Module\Special\HTTPException as ModuleHTTPException;
|
||||
use Friendica\Network\HTTPException;
|
||||
use Friendica\Protocol\ATProtocol\DID;
|
||||
use Friendica\Security\OpenWebAuth;
|
||||
use Friendica\Util\DateTimeFormat;
|
||||
use Friendica\Util\HTTPInputData;
|
||||
use Friendica\Util\HTTPSignature;
|
||||
|
|
@ -64,7 +65,7 @@ class App
|
|||
{
|
||||
const PLATFORM = 'Friendica';
|
||||
const CODENAME = 'Yellow Archangel';
|
||||
const VERSION = '2024.03';
|
||||
const VERSION = '2024.06-rc';
|
||||
|
||||
// Allow themes to control internal parameters
|
||||
// by changing App values in theme.php
|
||||
|
|
@ -93,6 +94,12 @@ class App
|
|||
/** @var string The name of the current mobile theme */
|
||||
private $currentMobileTheme;
|
||||
|
||||
/** @var string */
|
||||
private $requestId;
|
||||
|
||||
/** @var Authentication */
|
||||
private $auth;
|
||||
|
||||
/**
|
||||
* @var IManageConfigValues The config
|
||||
*/
|
||||
|
|
@ -133,42 +140,6 @@ class App
|
|||
*/
|
||||
private $session;
|
||||
|
||||
/**
|
||||
* @deprecated 2022.03
|
||||
* @see IHandleUserSessions::isAuthenticated()
|
||||
*/
|
||||
public function isLoggedIn(): bool
|
||||
{
|
||||
return $this->session->isAuthenticated();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated 2022.03
|
||||
* @see IHandleUserSessions::isSiteAdmin()
|
||||
*/
|
||||
public function isSiteAdmin(): bool
|
||||
{
|
||||
return $this->session->isSiteAdmin();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated 2022.03
|
||||
* @see IHandleUserSessions::getLocalUserId()
|
||||
*/
|
||||
public function getLoggedInUserId(): int
|
||||
{
|
||||
return $this->session->getLocalUserId();
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated 2022.03
|
||||
* @see IHandleUserSessions::getLocalUserNickname()
|
||||
*/
|
||||
public function getLoggedInUserNickname(): string
|
||||
{
|
||||
return $this->session->getLocalUserNickname();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the profile owner ID
|
||||
*
|
||||
|
|
@ -314,8 +285,10 @@ class App
|
|||
* @param DbaDefinition $dbaDefinition
|
||||
* @param ViewDefinition $viewDefinition
|
||||
*/
|
||||
public function __construct(Database $database, IManageConfigValues $config, App\Mode $mode, BaseURL $baseURL, LoggerInterface $logger, Profiler $profiler, L10n $l10n, Arguments $args, IManagePersonalConfigValues $pConfig, IHandleUserSessions $session, DbaDefinition $dbaDefinition, ViewDefinition $viewDefinition)
|
||||
public function __construct(Request $request, Authentication $auth, Database $database, IManageConfigValues $config, App\Mode $mode, BaseURL $baseURL, LoggerInterface $logger, Profiler $profiler, L10n $l10n, Arguments $args, IManagePersonalConfigValues $pConfig, IHandleUserSessions $session, DbaDefinition $dbaDefinition, ViewDefinition $viewDefinition)
|
||||
{
|
||||
$this->requestId = $request->getRequestId();
|
||||
$this->auth = $auth;
|
||||
$this->database = $database;
|
||||
$this->config = $config;
|
||||
$this->mode = $mode;
|
||||
|
|
@ -565,8 +538,8 @@ class App
|
|||
*/
|
||||
public function runFrontend(App\Router $router, IManagePersonalConfigValues $pconfig, Authentication $auth, App\Page $page, Nav $nav, ModuleHTTPException $httpException, HTTPInputData $httpInput, float $start_time, array $server)
|
||||
{
|
||||
$requeststring = ($_SERVER['REQUEST_METHOD'] ?? '') . ' ' . ($_SERVER['REQUEST_URI'] ?? '') . ' ' . ($_SERVER['SERVER_PROTOCOL'] ?? '');
|
||||
$this->logger->debug('Request received', ['address' => $_SERVER['REMOTE_ADDR'] ?? '', 'request' => $requeststring, 'referer' => $_SERVER['HTTP_REFERER'] ?? '', 'user-agent' => $_SERVER['HTTP_USER_AGENT'] ?? '']);
|
||||
$requeststring = ($server['REQUEST_METHOD'] ?? '') . ' ' . ($server['REQUEST_URI'] ?? '') . ' ' . ($server['SERVER_PROTOCOL'] ?? '');
|
||||
$this->logger->debug('Request received', ['address' => $server['REMOTE_ADDR'] ?? '', 'request' => $requeststring, 'referer' => $server['HTTP_REFERER'] ?? '', 'user-agent' => $server['HTTP_USER_AGENT'] ?? '']);
|
||||
$request_start = microtime(true);
|
||||
|
||||
$this->profiler->set($start_time, 'start');
|
||||
|
|
@ -593,10 +566,12 @@ class App
|
|||
Core\Hook::callAll('init_1');
|
||||
}
|
||||
|
||||
DID::routeRequest($this->args->getCommand(), $server);
|
||||
|
||||
if ($this->mode->isNormal() && !$this->mode->isBackend()) {
|
||||
$requester = HTTPSignature::getSigner('', $_SERVER);
|
||||
$requester = HTTPSignature::getSigner('', $server);
|
||||
if (!empty($requester)) {
|
||||
Profile::addVisitorCookieForHandle($requester);
|
||||
OpenWebAuth::addVisitorCookieForHandle($requester);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -606,17 +581,8 @@ class App
|
|||
// Valid profile links contain a path with "/profile/" and no query parameters
|
||||
if ((parse_url($_GET['zrl'], PHP_URL_QUERY) == '') &&
|
||||
strpos(parse_url($_GET['zrl'], PHP_URL_PATH) ?? '', '/profile/') !== false) {
|
||||
if ($this->session->get('visitor_home') != $_GET['zrl']) {
|
||||
$this->session->set('my_url', $_GET['zrl']);
|
||||
$this->session->set('authenticated', 0);
|
||||
|
||||
$remote_contact = Contact::getByURL($_GET['zrl'], false, ['subscribe']);
|
||||
if (!empty($remote_contact['subscribe'])) {
|
||||
$_SESSION['remote_comment'] = $remote_contact['subscribe'];
|
||||
}
|
||||
}
|
||||
|
||||
Model\Profile::zrlInit($this);
|
||||
$this->auth->setUnauthenticatedVisitor($_GET['zrl']);
|
||||
OpenWebAuth::zrlInit();
|
||||
} else {
|
||||
// Someone came with an invalid parameter, maybe as a DDoS attempt
|
||||
// We simply stop processing here
|
||||
|
|
@ -627,14 +593,14 @@ class App
|
|||
|
||||
if (!empty($_GET['owt']) && $this->mode->isNormal()) {
|
||||
$token = $_GET['owt'];
|
||||
Model\Profile::openWebAuthInit($token);
|
||||
OpenWebAuth::init($token);
|
||||
}
|
||||
|
||||
if (!$this->mode->isBackend()) {
|
||||
$auth->withSession($this);
|
||||
}
|
||||
|
||||
if (empty($_SESSION['authenticated'])) {
|
||||
if ($this->session->isUnauthenticated()) {
|
||||
header('X-Account-Management-Status: none');
|
||||
}
|
||||
|
||||
|
|
@ -654,6 +620,11 @@ class App
|
|||
Core\Hook::loadHooks();
|
||||
}
|
||||
|
||||
// Compatibility with Hubzilla
|
||||
if ($moduleName == 'rpost') {
|
||||
$this->baseURL->redirect('compose');
|
||||
}
|
||||
|
||||
// Compatibility with the Android Diaspora client
|
||||
if ($moduleName == 'stream') {
|
||||
$this->baseURL->redirect('network?order=post');
|
||||
|
|
@ -713,13 +684,15 @@ class App
|
|||
|
||||
// Wrapping HTML responses in the theme template
|
||||
if ($response->getHeaderLine(ICanCreateResponses::X_HEADER) === ICanCreateResponses::TYPE_HTML) {
|
||||
$response = $page->run($this, $this->baseURL, $this->args, $this->mode, $response, $this->l10n, $this->profiler, $this->config, $pconfig, $nav, $this->session->getLocalUserId());
|
||||
$response = $page->run($this, $this->session, $this->baseURL, $this->args, $this->mode, $response, $this->l10n, $this->profiler, $this->config, $pconfig, $nav, $this->session->getLocalUserId());
|
||||
}
|
||||
|
||||
$this->logger->debug('Request processed sucessfully', ['response' => $response->getStatusCode(), 'address' => $_SERVER['REMOTE_ADDR'] ?? '', 'request' => $requeststring, 'referer' => $_SERVER['HTTP_REFERER'] ?? '', 'user-agent' => $_SERVER['HTTP_USER_AGENT'] ?? '', 'duration' => number_format(microtime(true) - $request_start, 3)]);
|
||||
$this->logger->debug('Request processed sucessfully', ['response' => $response->getStatusCode(), 'address' => $server['REMOTE_ADDR'] ?? '', 'request' => $requeststring, 'referer' => $server['HTTP_REFERER'] ?? '', 'user-agent' => $server['HTTP_USER_AGENT'] ?? '', 'duration' => number_format(microtime(true) - $request_start, 3)]);
|
||||
$this->logSlowCalls(microtime(true) - $request_start, $response->getStatusCode(), $requeststring, $server['HTTP_USER_AGENT'] ?? '');
|
||||
System::echoResponse($response);
|
||||
} catch (HTTPException $e) {
|
||||
$this->logger->debug('Request processed with exception', ['response' => $e->getCode(), 'address' => $_SERVER['REMOTE_ADDR'] ?? '', 'request' => $requeststring, 'referer' => $_SERVER['HTTP_REFERER'] ?? '', 'user-agent' => $_SERVER['HTTP_USER_AGENT'] ?? '', 'duration' => number_format(microtime(true) - $request_start, 3)]);
|
||||
$this->logger->debug('Request processed with exception', ['response' => $e->getCode(), 'address' => $server['REMOTE_ADDR'] ?? '', 'request' => $requeststring, 'referer' => $server['HTTP_REFERER'] ?? '', 'user-agent' => $server['HTTP_USER_AGENT'] ?? '', 'duration' => number_format(microtime(true) - $request_start, 3)]);
|
||||
$this->logSlowCalls(microtime(true) - $request_start, $e->getCode(), $requeststring, $server['HTTP_USER_AGENT'] ?? '');
|
||||
$httpException->rawContent($e);
|
||||
}
|
||||
$page->logRuntime($this->config, 'runFrontend');
|
||||
|
|
@ -741,4 +714,30 @@ class App
|
|||
$this->baseURL->redirect($toUrl);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log slow page executions
|
||||
*
|
||||
* @param float $duration
|
||||
* @param integer $code
|
||||
* @param string $request
|
||||
* @param string $agent
|
||||
* @return void
|
||||
*/
|
||||
private function logSlowCalls(float $duration, int $code, string $request, string $agent)
|
||||
{
|
||||
$logfile = $this->config->get('system', 'page_execution_logfile');
|
||||
$loglimit = $this->config->get('system', 'page_execution_log_limit');
|
||||
if (empty($logfile) || empty($loglimit) || ($duration < $loglimit)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@file_put_contents(
|
||||
$logfile,
|
||||
DateTimeFormat::utcNow() . "\t" . round($duration, 3) . "\t" .
|
||||
$this->requestId . "\t" . $code . "\t" .
|
||||
$request . "\t" . $agent . "\n",
|
||||
FILE_APPEND
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ use Friendica\Core\L10n;
|
|||
use Friendica\Core\Logger;
|
||||
use Friendica\Core\PConfig\Capability\IManagePersonalConfigValues;
|
||||
use Friendica\Core\Renderer;
|
||||
use Friendica\Core\Session\Model\UserSession;
|
||||
use Friendica\Core\System;
|
||||
use Friendica\Core\Theme;
|
||||
use Friendica\Module\Response;
|
||||
|
|
@ -325,13 +326,13 @@ class Page implements ArrayAccess
|
|||
*
|
||||
* @throws HTTPException\InternalServerErrorException
|
||||
*/
|
||||
private function initFooter(App $app, Mode $mode, L10n $l10n)
|
||||
private function initFooter(UserSession $session, Mode $mode, L10n $l10n)
|
||||
{
|
||||
// If you're just visiting, let javascript take you home
|
||||
if (!empty($_SESSION['visitor_home'])) {
|
||||
$homebase = $_SESSION['visitor_home'];
|
||||
} elseif (!empty($app->getLoggedInUserNickname())) {
|
||||
$homebase = 'profile/' . $app->getLoggedInUserNickname();
|
||||
} elseif (!empty($session->getLocalUserNickname())) {
|
||||
$homebase = 'profile/' . $session->getLocalUserNickname();
|
||||
}
|
||||
|
||||
if (isset($homebase)) {
|
||||
|
|
@ -420,7 +421,7 @@ class Page implements ArrayAccess
|
|||
* @throws HTTPException\InternalServerErrorException
|
||||
* @throws HTTPException\ServiceUnavailableException
|
||||
*/
|
||||
public function run(App $app, BaseURL $baseURL, Arguments $args, Mode $mode, ResponseInterface $response, L10n $l10n, Profiler $profiler, IManageConfigValues $config, IManagePersonalConfigValues $pconfig, Nav $nav, int $localUID)
|
||||
public function run(App $app, UserSession $session, BaseURL $baseURL, Arguments $args, Mode $mode, ResponseInterface $response, L10n $l10n, Profiler $profiler, IManageConfigValues $config, IManagePersonalConfigValues $pconfig, Nav $nav, int $localUID)
|
||||
{
|
||||
$moduleName = $args->getModuleName();
|
||||
|
||||
|
|
@ -459,7 +460,7 @@ class Page implements ArrayAccess
|
|||
/* Build the page ending -- this is stuff that goes right before
|
||||
* the closing </body> tag
|
||||
*/
|
||||
$this->initFooter($app, $mode, $l10n);
|
||||
$this->initFooter($session, $mode, $l10n);
|
||||
|
||||
$profiler->set(microtime(true) - $timestamp, 'aftermath');
|
||||
|
||||
|
|
|
|||
|
|
@ -224,17 +224,17 @@ abstract class BaseModule implements ICanHandleRequests
|
|||
switch ($this->args->getMethod()) {
|
||||
case Router::DELETE:
|
||||
$this->delete($request);
|
||||
break;
|
||||
return $this->response->generate();
|
||||
case Router::PATCH:
|
||||
$this->patch($request);
|
||||
break;
|
||||
return $this->response->generate();
|
||||
case Router::POST:
|
||||
Core\Hook::callAll($this->args->getModuleName() . '_mod_post', $request);
|
||||
$this->post($request);
|
||||
break;
|
||||
return $this->response->generate();
|
||||
case Router::PUT:
|
||||
$this->put($request);
|
||||
break;
|
||||
return $this->response->generate();
|
||||
}
|
||||
|
||||
$timestamp = microtime(true);
|
||||
|
|
@ -356,7 +356,7 @@ abstract class BaseModule implements ICanHandleRequests
|
|||
*/
|
||||
public static function getFormSecurityToken(string $typename = ''): string
|
||||
{
|
||||
$user = User::getById(DI::app()->getLoggedInUserId(), ['guid', 'prvkey']);
|
||||
$user = User::getById(DI::userSession()->getLocalUserId(), ['guid', 'prvkey']);
|
||||
$timestamp = time();
|
||||
$sec_hash = hash('whirlpool', ($user['guid'] ?? '') . ($user['prvkey'] ?? '') . session_id() . $timestamp . $typename);
|
||||
|
||||
|
|
@ -390,7 +390,7 @@ abstract class BaseModule implements ICanHandleRequests
|
|||
|
||||
$max_livetime = 10800; // 3 hours
|
||||
|
||||
$user = User::getById(DI::app()->getLoggedInUserId(), ['guid', 'prvkey']);
|
||||
$user = User::getById(DI::userSession()->getLocalUserId(), ['guid', 'prvkey']);
|
||||
|
||||
$x = explode('.', $hash);
|
||||
if (time() > (intval($x[0]) + $max_livetime)) {
|
||||
|
|
@ -410,7 +410,7 @@ abstract class BaseModule implements ICanHandleRequests
|
|||
public static function checkFormSecurityTokenRedirectOnError(string $err_redirect, string $typename = '', string $formname = 'form_security_token')
|
||||
{
|
||||
if (!self::checkFormSecurityToken($typename, $formname)) {
|
||||
Logger::notice('checkFormSecurityToken failed: user ' . DI::app()->getLoggedInUserNickname() . ' - form element ' . $typename);
|
||||
Logger::notice('checkFormSecurityToken failed: user ' . DI::userSession()->getLocalUserNickname() . ' - form element ' . $typename);
|
||||
Logger::debug('checkFormSecurityToken failed', ['request' => $_REQUEST]);
|
||||
DI::sysmsg()->addNotice(self::getFormSecurityStandardErrorMessage());
|
||||
DI::baseUrl()->redirect($err_redirect);
|
||||
|
|
@ -420,7 +420,7 @@ abstract class BaseModule implements ICanHandleRequests
|
|||
public static function checkFormSecurityTokenForbiddenOnError(string $typename = '', string $formname = 'form_security_token')
|
||||
{
|
||||
if (!self::checkFormSecurityToken($typename, $formname)) {
|
||||
Logger::notice('checkFormSecurityToken failed: user ' . DI::app()->getLoggedInUserNickname() . ' - form element ' . $typename);
|
||||
Logger::notice('checkFormSecurityToken failed: user ' . DI::userSession()->getLocalUserNickname() . ' - form element ' . $typename);
|
||||
Logger::debug('checkFormSecurityToken failed', ['request' => $_REQUEST]);
|
||||
|
||||
throw new \Friendica\Network\HTTPException\ForbiddenException();
|
||||
|
|
|
|||
119
src/Console/ClearAvatarCache.php
Normal 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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])) {
|
||||
|
|
|
|||
|
|
@ -23,14 +23,11 @@ namespace Friendica\Contact;
|
|||
|
||||
use Friendica\Core\Logger;
|
||||
use Friendica\DI;
|
||||
use Friendica\Model\Item;
|
||||
use Friendica\Network\HTTPClient\Client\HttpClientAccept;
|
||||
use Friendica\Network\HTTPClient\Client\HttpClientOptions;
|
||||
use Friendica\Object\Image;
|
||||
use Friendica\Util\DateTimeFormat;
|
||||
use Friendica\Util\HTTPSignature;
|
||||
use Friendica\Util\Images;
|
||||
use Friendica\Util\Network;
|
||||
use Friendica\Util\Proxy;
|
||||
|
||||
/**
|
||||
|
|
@ -57,7 +54,7 @@ class Avatar
|
|||
return $fields;
|
||||
}
|
||||
|
||||
if (Network::isLocalLink($avatar) || empty($avatar)) {
|
||||
if (DI::baseUrl()->isLocalUrl($avatar) || empty($avatar)) {
|
||||
self::deleteCache($contact);
|
||||
return $fields;
|
||||
}
|
||||
|
|
@ -97,7 +94,7 @@ class Avatar
|
|||
return $fields;
|
||||
}
|
||||
|
||||
$filename = self::getFilename($contact['url'], $avatar);
|
||||
$filename = self::getFilename($contact['url']);
|
||||
$timestamp = time();
|
||||
|
||||
$fields['blurhash'] = $image->getBlurHash();
|
||||
|
|
@ -120,12 +117,12 @@ class Avatar
|
|||
return $fields;
|
||||
}
|
||||
|
||||
if (Network::isLocalLink($contact['avatar']) || empty($contact['avatar'])) {
|
||||
if (DI::baseUrl()->isLocalUrl($contact['avatar']) || empty($contact['avatar'])) {
|
||||
self::deleteCache($contact);
|
||||
return $fields;
|
||||
}
|
||||
|
||||
$filename = self::getFilename($contact['url'], $contact['avatar']);
|
||||
$filename = self::getFilename($contact['url']);
|
||||
$timestamp = time();
|
||||
|
||||
$fields['photo'] = self::storeAvatarCache($image, $filename, Proxy::PIXEL_SMALL, $timestamp);
|
||||
|
|
@ -135,12 +132,10 @@ class Avatar
|
|||
return $fields;
|
||||
}
|
||||
|
||||
private static function getFilename(string $url, string $host): string
|
||||
private static function getFilename(string $url): string
|
||||
{
|
||||
$guid = Item::guidFromUri($url, $host);
|
||||
|
||||
return substr($guid, 0, 2) . '/' . substr($guid, 3, 2) . '/' . substr($guid, 5, 3) . '/' .
|
||||
substr($guid, 9, 2) .'/' . substr($guid, 11, 2) . '/' . substr($guid, 13, 4). '/' . substr($guid, 18) . '-';
|
||||
$guid = hash('ripemd128', $url);
|
||||
return substr($guid, 0, 3) . '/' . substr($guid, 4) . '-';
|
||||
}
|
||||
|
||||
private static function storeAvatarCache(Image $image, string $filename, int $size, int $timestamp): string
|
||||
|
|
@ -279,7 +274,7 @@ class Avatar
|
|||
$localFile = self::getCacheFile($avatar);
|
||||
if (!empty($localFile)) {
|
||||
@unlink($localFile);
|
||||
Logger::debug('Unlink avatar', ['avatar' => $avatar]);
|
||||
Logger::debug('Unlink avatar', ['avatar' => $avatar, 'local' => $localFile]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -316,7 +311,7 @@ class Avatar
|
|||
*
|
||||
* @return string
|
||||
*/
|
||||
private static function baseUrl(): string
|
||||
public static function baseUrl(): string
|
||||
{
|
||||
$baseurl = DI::config()->get('system', 'avatar_cache_url');
|
||||
if (!empty($baseurl)) {
|
||||
|
|
|
|||
|
|
@ -23,8 +23,8 @@ namespace Friendica\Content;
|
|||
|
||||
use Friendica\Core\L10n;
|
||||
use Friendica\Core\Renderer;
|
||||
use Friendica\Util\Network;
|
||||
use Friendica\Util\Strings;
|
||||
use GuzzleHttp\Psr7\Uri;
|
||||
|
||||
/**
|
||||
* This pager should be used by lists using the min_id†/max_id† parameters
|
||||
|
|
@ -67,7 +67,7 @@ class BoundariesPager extends Pager
|
|||
|
||||
$parsed['query'] = http_build_query($queryParameters);
|
||||
|
||||
$url = Network::unparseURL($parsed);
|
||||
$url = (string)Uri::fromParts((array)$parsed);
|
||||
|
||||
$this->setQueryString($url);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,8 +25,8 @@ use Friendica\Core\Hook;
|
|||
use Friendica\Core\Protocol;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\DI;
|
||||
use Friendica\Util\Network;
|
||||
use Friendica\Util\Strings;
|
||||
use GuzzleHttp\Psr7\Uri;
|
||||
|
||||
/**
|
||||
* ContactSelector class
|
||||
|
|
@ -102,7 +102,7 @@ class ContactSelector
|
|||
// Create the server url out of the profile url
|
||||
$parts = parse_url($profile);
|
||||
unset($parts['path']);
|
||||
$server_url = Strings::normaliseLink(Network::unparseURL($parts));
|
||||
$server_url = Strings::normaliseLink((string)Uri::fromParts((array)$parts));
|
||||
}
|
||||
|
||||
self::$server_url[$profile] = $server_url;
|
||||
|
|
|
|||
|
|
@ -308,7 +308,7 @@ class Conversation
|
|||
|
||||
public function statusEditor(array $x = [], int $notes_cid = 0, bool $popup = false): string
|
||||
{
|
||||
$user = User::getById($this->app->getLoggedInUserId(), ['uid', 'nickname', 'allow_location', 'default-location']);
|
||||
$user = User::getById($this->session->getLocalUserId(), ['uid', 'nickname', 'allow_location', 'default-location']);
|
||||
if (empty($user['uid'])) {
|
||||
return '';
|
||||
}
|
||||
|
|
@ -332,7 +332,6 @@ class Conversation
|
|||
$tpl = Renderer::getMarkupTemplate('jot-header.tpl');
|
||||
$this->page['htmlhead'] .= Renderer::replaceMacros($tpl, [
|
||||
'$newpost' => 'true',
|
||||
'$baseurl' => $this->baseURL,
|
||||
'$geotag' => $geotag,
|
||||
'$nickname' => $x['nickname'],
|
||||
'$ispublic' => $this->l10n->t('Visible to <strong>everybody</strong>'),
|
||||
|
|
@ -389,7 +388,7 @@ class Conversation
|
|||
'$title' => $x['title'] ?? '',
|
||||
'$placeholdertitle' => $this->l10n->t('Set title'),
|
||||
'$category' => $x['category'] ?? '',
|
||||
'$placeholdercategory' => Feature::isEnabled($this->session->getLocalUserId(), 'categories') ? $this->l10n->t("Categories \x28comma-separated list\x29") : '',
|
||||
'$placeholdercategory' => Feature::isEnabled($this->session->getLocalUserId(), Feature::CATEGORIES) ? $this->l10n->t("Categories \x28comma-separated list\x29") : '',
|
||||
'$scheduled_at' => Temporal::getDateTimeField(
|
||||
new \DateTime(),
|
||||
new \DateTime('now + 6 months'),
|
||||
|
|
@ -405,7 +404,6 @@ class Conversation
|
|||
'$posttype' => $notes_cid ? ItemModel::PT_PERSONAL_NOTE : ItemModel::PT_ARTICLE,
|
||||
'$content' => $x['content'] ?? '',
|
||||
'$post_id' => $x['post_id'] ?? '',
|
||||
'$baseurl' => $this->baseURL,
|
||||
'$defloc' => $x['default_location'],
|
||||
'$visitor' => $x['visitor'],
|
||||
'$pvisit' => $notes_cid ? 'none' : $x['visitor'],
|
||||
|
|
@ -591,7 +589,6 @@ class Conversation
|
|||
}
|
||||
|
||||
$o = Renderer::replaceMacros($page_template, [
|
||||
'$baseurl' => $this->baseURL,
|
||||
'$return_path' => $this->args->getQueryString(),
|
||||
'$live_update' => $live_update_div,
|
||||
'$remove' => $this->l10n->t('remove'),
|
||||
|
|
@ -1517,14 +1514,6 @@ class Conversation
|
|||
|
||||
[$categories, $folders] = $this->item->determineCategoriesTerms($item, $this->session->getLocalUserId());
|
||||
|
||||
if (!empty($item['title'])) {
|
||||
$title = $item['title'];
|
||||
} elseif (!empty($item['content-warning']) && $this->pConfig->get($this->session->getLocalUserId(), 'system', 'disable_cw', false)) {
|
||||
$title = ucfirst($item['content-warning']);
|
||||
} else {
|
||||
$title = '';
|
||||
}
|
||||
|
||||
if (!empty($item['featured'])) {
|
||||
$pinned = $this->l10n->t('Pinned item');
|
||||
} else {
|
||||
|
|
@ -1550,7 +1539,8 @@ class Conversation
|
|||
'sparkle' => $sparkle,
|
||||
'lock' => false,
|
||||
'thumb' => $this->baseURL->remove($this->item->getAuthorAvatar($item)),
|
||||
'title' => $title,
|
||||
'title' => $item['title'],
|
||||
'summary' => $item['content-warning'],
|
||||
'body_html' => $body_html,
|
||||
'tags' => $tags['tags'],
|
||||
'hashtags' => $tags['hashtags'],
|
||||
|
|
|
|||
|
|
@ -26,6 +26,25 @@ use Friendica\DI;
|
|||
|
||||
class Feature
|
||||
{
|
||||
const ACCOUNTS = 'accounts';
|
||||
const ADD_ABSTRACT = 'add_abstract';
|
||||
const ARCHIVE = 'archive';
|
||||
const CATEGORIES = 'categories';
|
||||
const CHANNELS = 'channels';
|
||||
const CIRCLES = 'circles';
|
||||
const COMMUNITY = 'community';
|
||||
const EXPLICIT_MENTIONS = 'explicit_mentions';
|
||||
const FOLDERS = 'folders';
|
||||
const GROUPS = 'forumlist_profile';
|
||||
const MEMBER_SINCE = 'profile_membersince';
|
||||
const NETWORKS = 'networks';
|
||||
const NOSHARER = 'nosharer';
|
||||
const PHOTO_LOCATION = 'photo_location';
|
||||
const PUBLIC_CALENDAR = 'public_calendar';
|
||||
const SEARCHES = 'searches';
|
||||
const TAGCLOUD = 'tagadelic';
|
||||
const TRENDING_TAGS = 'trending_tags';
|
||||
|
||||
/**
|
||||
* check if feature is enabled
|
||||
*
|
||||
|
|
@ -34,25 +53,18 @@ class Feature
|
|||
* @return boolean
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
*/
|
||||
public static function isEnabled(int $uid, $feature)
|
||||
public static function isEnabled(int $uid, $feature): bool
|
||||
{
|
||||
$x = DI::config()->get('feature_lock', $feature, false);
|
||||
|
||||
if ($x === false) {
|
||||
$x = DI::pConfig()->get($uid, 'feature', $feature, false);
|
||||
if (!DI::config()->get('feature_lock', $feature, false)) {
|
||||
$enabled = DI::config()->get('feature', $feature) ?? self::getDefault($feature);
|
||||
$enabled = DI::pConfig()->get($uid, 'feature', $feature) ?? $enabled;
|
||||
} else {
|
||||
$enabled = true;
|
||||
}
|
||||
|
||||
if ($x === false) {
|
||||
$x = DI::config()->get('feature', $feature, false);
|
||||
}
|
||||
|
||||
if ($x === false) {
|
||||
$x = self::getDefault($feature);
|
||||
}
|
||||
|
||||
$arr = ['uid' => $uid, 'feature' => $feature, 'enabled' => $x];
|
||||
$arr = ['uid' => $uid, 'feature' => $feature, 'enabled' => $enabled];
|
||||
Hook::callAll('isEnabled', $arr);
|
||||
return($arr['enabled']);
|
||||
return (bool)$arr['enabled'];
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -64,8 +76,7 @@ class Feature
|
|||
*/
|
||||
private static function getDefault($feature)
|
||||
{
|
||||
$f = self::get();
|
||||
foreach ($f as $cat) {
|
||||
foreach (self::get() as $cat) {
|
||||
foreach ($cat as $feat) {
|
||||
if (is_array($feat) && $feat[0] === $feature) {
|
||||
return $feat[3];
|
||||
|
|
@ -95,36 +106,49 @@ class Feature
|
|||
'general' => [
|
||||
DI::l10n()->t('General Features'),
|
||||
//array('expire', DI::l10n()->t('Content Expiration'), DI::l10n()->t('Remove old posts/comments after a period of time')),
|
||||
['photo_location', DI::l10n()->t('Photo Location'), DI::l10n()->t("Photo metadata is normally stripped. This extracts the location \x28if present\x29 prior to stripping metadata and links it to a map."), false, DI::config()->get('feature_lock', 'photo_location', false)],
|
||||
['trending_tags', DI::l10n()->t('Trending Tags'), DI::l10n()->t('Show a community page widget with a list of the most popular tags in recent public posts.'), false, DI::config()->get('feature_lock', 'trending_tags', false)],
|
||||
[self::PHOTO_LOCATION, DI::l10n()->t('Photo Location'), DI::l10n()->t("Photo metadata is normally stripped. This extracts the location \x28if present\x29 prior to stripping metadata and links it to a map."), false, DI::config()->get('feature_lock', self::PHOTO_LOCATION, false)],
|
||||
[self::COMMUNITY, DI::l10n()->t('Display the community in the navigation'), DI::l10n()->t('If enabled, the community can be accessed via the navigation menu. Independent from this setting, the community timelines can always be accessed via the channels.'), true, DI::config()->get('feature_lock', self::COMMUNITY, false)],
|
||||
],
|
||||
|
||||
// Post composition
|
||||
'composition' => [
|
||||
DI::l10n()->t('Post Composition Features'),
|
||||
['aclautomention', DI::l10n()->t('Auto-mention Groups'), DI::l10n()->t('Add/remove mention when a group page is selected/deselected in ACL window.'), false, DI::config()->get('feature_lock', 'aclautomention', false)],
|
||||
['explicit_mentions', DI::l10n()->t('Explicit Mentions'), DI::l10n()->t('Add explicit mentions to comment box for manual control over who gets mentioned in replies.'), false, DI::config()->get('feature_lock', 'explicit_mentions', false)],
|
||||
['add_abstract', DI::l10n()->t('Add an abstract from ActivityPub content warnings'), DI::l10n()->t('Add an abstract when commenting on ActivityPub posts with a content warning. Abstracts are displayed as content warning on systems like Mastodon or Pleroma.'), false, DI::config()->get('feature_lock', 'add_abstract', false)],
|
||||
[self::EXPLICIT_MENTIONS, DI::l10n()->t('Explicit Mentions'), DI::l10n()->t('Add explicit mentions to comment box for manual control over who gets mentioned in replies.'), false, DI::config()->get('feature_lock', Feature::EXPLICIT_MENTIONS, false)],
|
||||
[self::ADD_ABSTRACT, DI::l10n()->t('Add an abstract from ActivityPub content warnings'), DI::l10n()->t('Add an abstract when commenting on ActivityPub posts with a content warning. Abstracts are displayed as content warning on systems like Mastodon or Pleroma.'), false, DI::config()->get('feature_lock', self::ADD_ABSTRACT, false)],
|
||||
],
|
||||
|
||||
// Item tools
|
||||
'tools' => [
|
||||
DI::l10n()->t('Post/Comment Tools'),
|
||||
['categories', DI::l10n()->t('Post Categories'), DI::l10n()->t('Add categories to your posts'), false, DI::config()->get('feature_lock', 'categories', false)],
|
||||
[self::CATEGORIES, DI::l10n()->t('Post Categories'), DI::l10n()->t('Add categories to your posts'), false, DI::config()->get('feature_lock', self::CATEGORIES, false)],
|
||||
],
|
||||
|
||||
// Widget visibility on the network stream
|
||||
'network' => [
|
||||
DI::l10n()->t('Network Widgets'),
|
||||
[self::CIRCLES, DI::l10n()->t('Circles'), DI::l10n()->t('Display posts that have been created by accounts of the selected circle.'), true, DI::config()->get('feature_lock', self::CIRCLES, false)],
|
||||
[self::GROUPS, DI::l10n()->t('Groups'), DI::l10n()->t('Display posts that have been distributed by the selected group.'), true, DI::config()->get('feature_lock', self::GROUPS, false)],
|
||||
[self::ARCHIVE, DI::l10n()->t('Archives'), DI::l10n()->t('Display an archive where posts can be selected by month and year.'), true, DI::config()->get('feature_lock', self::ARCHIVE, false)],
|
||||
[self::NETWORKS, DI::l10n()->t('Protocols'), DI::l10n()->t('Display posts with the selected protocols.'), true, DI::config()->get('feature_lock', self::NETWORKS, false)],
|
||||
[self::ACCOUNTS, DI::l10n()->t('Account Types'), DI::l10n()->t('Display posts done by accounts with the selected account type.'), true, DI::config()->get('feature_lock', self::ACCOUNTS, false)],
|
||||
[self::CHANNELS, DI::l10n()->t('Channels'), DI::l10n()->t('Display posts in the system channels and user defined channels.'), true, DI::config()->get('feature_lock', self::CHANNELS, false)],
|
||||
[self::SEARCHES, DI::l10n()->t('Saved Searches'), DI::l10n()->t('Display posts that contain subscribed hashtags.'), true, DI::config()->get('feature_lock', self::SEARCHES, false)],
|
||||
[self::FOLDERS, DI::l10n()->t('Saved Folders'), DI::l10n()->t('Display a list of folders in which posts are stored.'), true, DI::config()->get('feature_lock', self::FOLDERS, false)],
|
||||
[self::NOSHARER, DI::l10n()->t('Own Contacts'), DI::l10n()->t('Include or exclude posts from subscribed accounts. This widget is not visible on all channels.'), true, DI::config()->get('feature_lock', self::NOSHARER, false)],
|
||||
[self::TRENDING_TAGS, DI::l10n()->t('Trending Tags'), DI::l10n()->t('Display a list of the most popular tags in recent public posts.'), false, DI::config()->get('feature_lock', self::TRENDING_TAGS, false)],
|
||||
],
|
||||
|
||||
// Advanced Profile Settings
|
||||
'advanced_profile' => [
|
||||
DI::l10n()->t('Advanced Profile Settings'),
|
||||
['forumlist_profile', DI::l10n()->t('List Groups'), DI::l10n()->t('Show visitors public groups at the Advanced Profile Page'), false, DI::config()->get('feature_lock', 'forumlist_profile', false)],
|
||||
['tagadelic', DI::l10n()->t('Tag Cloud'), DI::l10n()->t('Provide a personal tag cloud on your profile page'), false, DI::config()->get('feature_lock', 'tagadelic', false)],
|
||||
['profile_membersince', DI::l10n()->t('Display Membership Date'), DI::l10n()->t('Display membership date in profile'), false, DI::config()->get('feature_lock', 'profile_membersince', false)],
|
||||
[self::TAGCLOUD, DI::l10n()->t('Tag Cloud'), DI::l10n()->t('Provide a personal tag cloud on your profile page'), false, DI::config()->get('feature_lock', self::TAGCLOUD, false)],
|
||||
[self::MEMBER_SINCE, DI::l10n()->t('Display Membership Date'), DI::l10n()->t('Display membership date in profile'), false, DI::config()->get('feature_lock', self::MEMBER_SINCE, false)],
|
||||
],
|
||||
|
||||
//Advanced Calendar Settings
|
||||
'advanced_calendar' => [
|
||||
DI::l10n()->t('Advanced Calendar Settings'),
|
||||
['public_calendar', DI::l10n()->t('Allow anonymous access to your calendar'), DI::l10n()->t('Allows anonymous visitors to consult your calendar and your public events. Contact birthday events are private to you.'), false, DI::config()->get('feature_lock', 'public_calendar', false)],
|
||||
[self::PUBLIC_CALENDAR, DI::l10n()->t('Allow anonymous access to your calendar'), DI::l10n()->t('Allows anonymous visitors to consult your calendar and your public events. Contact birthday events are private to you.'), false, DI::config()->get('feature_lock', self::PUBLIC_CALENDAR, false)],
|
||||
]
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ class GroupManager
|
|||
'archive' => false,
|
||||
];
|
||||
|
||||
$condition = DBA::mergeConditions($condition, ["`platform` != ?", 'peertube']);
|
||||
$condition = DBA::mergeConditions($condition, ["`platform` NOT IN (?, ?)", 'peertube', 'wordpress']);
|
||||
|
||||
if (!$showprivate) {
|
||||
$condition = DBA::mergeConditions($condition, ['manually-approve' => false]);
|
||||
|
|
@ -172,8 +172,7 @@ class GroupManager
|
|||
*/
|
||||
public static function profileAdvanced($uid)
|
||||
{
|
||||
$profile = intval(Feature::isEnabled($uid, 'forumlist_profile'));
|
||||
if (!$profile) {
|
||||
if (!Feature::isEnabled($uid, Feature::GROUPS)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -48,7 +48,6 @@ use Friendica\Model\User;
|
|||
use Friendica\Network\HTTPException;
|
||||
use Friendica\Object\EMail\ItemCCEMail;
|
||||
use Friendica\Protocol\Activity;
|
||||
use Friendica\Protocol\ActivityPub;
|
||||
use Friendica\Util\ACLFormatter;
|
||||
use Friendica\Util\DateTimeFormat;
|
||||
use Friendica\Util\Emailer;
|
||||
|
|
@ -440,9 +439,11 @@ class Item
|
|||
];
|
||||
|
||||
if (!empty($item['language'])) {
|
||||
$menu[$this->l10n->t('Languages')] = 'javascript:alert(\'' . ItemModel::getLanguageMessage($item) . '\');';
|
||||
$menu[$this->l10n->t('Languages')] = 'javascript:displayLanguage(' . $item['uri-id'] . ');';
|
||||
}
|
||||
|
||||
$menu[$this->l10n->t('Search Text')] = 'javascript:displaySearchText(' . $item['uri-id'] . ');';
|
||||
|
||||
if ((($cid == 0) || ($rel == Contact::FOLLOWER)) &&
|
||||
in_array($item['network'], Protocol::FEDERATED)
|
||||
) {
|
||||
|
|
@ -546,9 +547,9 @@ class Item
|
|||
$item['private'] = $private_group ? ItemModel::PRIVATE : ItemModel::UNLISTED;
|
||||
|
||||
if ($only_to_group) {
|
||||
$cdata = Contact::getPublicAndUserContactID($group_contact['id'], $item['uid']);
|
||||
if (!empty($cdata['user'])) {
|
||||
$item['owner-id'] = $cdata['user'];
|
||||
$pcid = Contact::getPublicContactId($group_contact['id'], $item['uid']);
|
||||
if ($pcid) {
|
||||
$item['owner-id'] = $pcid;
|
||||
unset($item['owner-link']);
|
||||
unset($item['owner-name']);
|
||||
unset($item['owner-avatar']);
|
||||
|
|
@ -800,14 +801,14 @@ class Item
|
|||
*/
|
||||
public function addShareLink(string $body, int $quote_uri_id): string
|
||||
{
|
||||
$post = Post::selectFirstPost(['uri', 'plink'], ['uri-id' => $quote_uri_id]);
|
||||
$post = Post::selectFirstPost(['uri'], ['uri-id' => $quote_uri_id]);
|
||||
if (empty($post)) {
|
||||
return $body;
|
||||
}
|
||||
|
||||
$body = BBCode::removeSharedData($body);
|
||||
|
||||
$body .= "\n♲ " . ($post['plink'] ?: $post['uri']);
|
||||
$body .= "\nRE: " . $post['uri'];
|
||||
|
||||
return $body;
|
||||
}
|
||||
|
|
@ -910,40 +911,6 @@ class Item
|
|||
return $post;
|
||||
}
|
||||
|
||||
public function moveAttachmentsFromBodyToAttach(array $post): array
|
||||
{
|
||||
if (!preg_match_all('/(\[attachment\]([0-9]+)\[\/attachment\])/', $post['body'], $match)) {
|
||||
return $post;
|
||||
}
|
||||
|
||||
foreach ($match[2] as $attachment_id) {
|
||||
$attachment = Attach::selectFirst(['id', 'uid', 'filename', 'filesize', 'filetype'], ['id' => $attachment_id, 'uid' => $post['uid']]);
|
||||
if (empty($attachment)) {
|
||||
continue;
|
||||
}
|
||||
if ($post['attach']) {
|
||||
$post['attach'] .= ',';
|
||||
}
|
||||
$post['attach'] .= Post\Media::getAttachElement(
|
||||
$this->baseURL . '/attach/' . $attachment['id'],
|
||||
$attachment['filesize'],
|
||||
$attachment['filetype'],
|
||||
$attachment['filename'] ?? ''
|
||||
);
|
||||
|
||||
$fields = [
|
||||
'allow_cid' => $post['allow_cid'], 'allow_gid' => $post['allow_gid'],
|
||||
'deny_cid' => $post['deny_cid'], 'deny_gid' => $post['deny_gid']
|
||||
];
|
||||
$condition = ['id' => $attachment_id];
|
||||
Attach::update($fields, $condition);
|
||||
}
|
||||
|
||||
$post['body'] = str_replace($match[1], '', $post['body']);
|
||||
|
||||
return $post;
|
||||
}
|
||||
|
||||
private function setObjectType(array $post): array
|
||||
{
|
||||
if (empty($post['post-type'])) {
|
||||
|
|
@ -1023,8 +990,13 @@ class Item
|
|||
return $post;
|
||||
}
|
||||
|
||||
public function finalizePost(array $post): array
|
||||
public function finalizePost(array $post, bool $preview): array
|
||||
{
|
||||
if ($preview) {
|
||||
$post['body'] = Attach::addAttachmentToBody($post['body'], $post['uid']);
|
||||
} else {
|
||||
Attach::setPermissionFromBody($post);
|
||||
}
|
||||
if (preg_match("/\[attachment\](.*?)\[\/attachment\]/ism", $post['body'], $matches)) {
|
||||
$post['body'] = preg_replace("/\[attachment].*?\[\/attachment\]/ism", PageInfo::getFooterFromUrl($matches[1]), $post['body']);
|
||||
}
|
||||
|
|
@ -1045,7 +1017,7 @@ class Item
|
|||
|
||||
public function postProcessPost(array $post, array $recipients = [])
|
||||
{
|
||||
if (!\Friendica\Content\Feature::isEnabled($post['uid'], 'explicit_mentions') && ($post['gravity'] == ItemModel::GRAVITY_COMMENT)) {
|
||||
if (!Feature::isEnabled($post['uid'], Feature::EXPLICIT_MENTIONS) && ($post['gravity'] == ItemModel::GRAVITY_COMMENT)) {
|
||||
Tag::createImplicitMentions($post['uri-id'], $post['thr-parent-id']);
|
||||
}
|
||||
|
||||
|
|
@ -1060,7 +1032,7 @@ class Item
|
|||
}
|
||||
|
||||
$this->emailer->send(new ItemCCEMail(
|
||||
$this->app,
|
||||
$this->userSession,
|
||||
$this->l10n,
|
||||
$this->baseURL,
|
||||
$post,
|
||||
|
|
@ -1078,7 +1050,7 @@ class Item
|
|||
$to_author = DBA::selectFirst('account-view', ['ap-followers'], ['id' => $to['author-id']]);
|
||||
$parent = Post::selectFirstPost(['author-id'], ['uri-id' => $parentUriId]);
|
||||
$parent_author = DBA::selectFirst('account-view', ['ap-followers'], ['id' => $parent['author-id']]);
|
||||
|
||||
|
||||
$followers = '';
|
||||
foreach (array_column(Tag::getByURIId($parentUriId, [Tag::TO, Tag::CC, Tag::BCC]), 'url') as $url) {
|
||||
if ($url == $parent_author['ap-followers']) {
|
||||
|
|
|
|||
|
|
@ -30,12 +30,12 @@ use Friendica\Core\Renderer;
|
|||
use Friendica\Core\Session\Capability\IHandleUserSessions;
|
||||
use Friendica\Database\Database;
|
||||
use Friendica\Model\Contact;
|
||||
use Friendica\Model\Profile;
|
||||
use Friendica\Model\User;
|
||||
use Friendica\Module\Conversation\Community;
|
||||
use Friendica\Module\Home;
|
||||
use Friendica\Module\Security\Login;
|
||||
use Friendica\Network\HTTPException;
|
||||
use Friendica\Security\OpenWebAuth;
|
||||
|
||||
class Nav
|
||||
{
|
||||
|
|
@ -251,7 +251,7 @@ class Nav
|
|||
$nav['home'] = [$homelink, $this->l10n->t('Home'), '', $this->l10n->t('Home Page')];
|
||||
}
|
||||
|
||||
if (intval($this->config->get('config', 'register_policy')) === \Friendica\Module\Register::OPEN && !$this->session->isAuthenticated()) {
|
||||
if (\Friendica\Module\Register::getPolicy() === \Friendica\Module\Register::OPEN && !$this->session->isAuthenticated()) {
|
||||
$nav['register'] = ['register', $this->l10n->t('Register'), '', $this->l10n->t('Create an account')];
|
||||
}
|
||||
|
||||
|
|
@ -281,11 +281,11 @@ class Nav
|
|||
|
||||
$gdirpath = 'directory';
|
||||
if ($this->config->get('system', 'singleuser') && $this->config->get('system', 'directory')) {
|
||||
$gdirpath = Profile::zrl($this->config->get('system', 'directory'), true);
|
||||
$gdirpath = OpenWebAuth::getZrlUrl($this->config->get('system', 'directory'), true);
|
||||
}
|
||||
|
||||
if (($this->session->getLocalUserId() || $this->config->get('system', 'community_page_style') != Community::DISABLED_VISITOR) &&
|
||||
!($this->config->get('system', 'community_page_style') == Community::DISABLED)) {
|
||||
if (Feature::isEnabled($this->session->getLocalUserId(), Feature::COMMUNITY) && (($this->session->getLocalUserId() || $this->config->get('system', 'community_page_style') != Community::DISABLED_VISITOR) &&
|
||||
!($this->config->get('system', 'community_page_style') == Community::DISABLED))) {
|
||||
$nav['community'] = ['community', $this->l10n->t('Community'), '', $this->l10n->t('Conversations on this and other servers')];
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ use Friendica\Database\Database;
|
|||
use Friendica\Database\DBA;
|
||||
use Friendica\DI;
|
||||
use Friendica\Network\HTTPClient\Client\HttpClientAccept;
|
||||
use Friendica\Network\HTTPClient\Client\HttpClientRequest;
|
||||
use Friendica\Util\DateTimeFormat;
|
||||
use Friendica\Util\Network;
|
||||
use Friendica\Util\ParseUrl;
|
||||
|
|
@ -86,7 +87,7 @@ class OEmbed
|
|||
|
||||
if (!in_array($ext, $noexts)) {
|
||||
// try oembed autodiscovery
|
||||
$html_text = DI::httpClient()->fetch($embedurl, HttpClientAccept::HTML, 15);
|
||||
$html_text = DI::httpClient()->fetch($embedurl, HttpClientAccept::HTML, 15, '', HttpClientRequest::SITEINFO);
|
||||
if (!empty($html_text)) {
|
||||
$dom = new DOMDocument();
|
||||
if (@$dom->loadHTML($html_text)) {
|
||||
|
|
@ -100,7 +101,7 @@ class OEmbed
|
|||
// but their OEmbed endpoint is only accessible by HTTPS ¯\_(ツ)_/¯
|
||||
$href = str_replace(['http://www.youtube.com/', 'http://player.vimeo.com/'],
|
||||
['https://www.youtube.com/', 'https://player.vimeo.com/'], $href);
|
||||
$result = DI::httpClient()->fetchFull($href . '&maxwidth=' . $a->getThemeInfoValue('videowidth'));
|
||||
$result = DI::httpClient()->fetchFull($href . '&maxwidth=' . $a->getThemeInfoValue('videowidth'), HttpClientAccept::DEFAULT, 0, '', HttpClientRequest::SITEINFO);
|
||||
if ($result->isSuccess()) {
|
||||
$json_string = $result->getBodyString();
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ use Friendica\Model\Post;
|
|||
use Friendica\Model\Tag;
|
||||
use Friendica\Network\HTTPClient\Client\HttpClientAccept;
|
||||
use Friendica\Network\HTTPClient\Client\HttpClientOptions;
|
||||
use Friendica\Network\HTTPClient\Client\HttpClientRequest;
|
||||
use Friendica\Util\Images;
|
||||
use Friendica\Util\Map;
|
||||
use Friendica\Util\Network;
|
||||
|
|
@ -47,11 +48,12 @@ use Friendica\Util\ParseUrl;
|
|||
use Friendica\Util\Proxy;
|
||||
use Friendica\Util\Strings;
|
||||
use Friendica\Util\XML;
|
||||
use GuzzleHttp\Psr7\Uri;
|
||||
|
||||
class BBCode
|
||||
{
|
||||
// Update this value to the current date whenever changes are made to BBCode::convert
|
||||
const VERSION = '2021-07-28';
|
||||
const VERSION = '2024-04-07';
|
||||
|
||||
const INTERNAL = 0;
|
||||
const EXTERNAL = 1;
|
||||
|
|
@ -146,8 +148,7 @@ class BBCode
|
|||
case 'title':
|
||||
$value = self::toPlaintext(html_entity_decode($value, ENT_QUOTES, 'UTF-8'));
|
||||
$value = html_entity_decode($value, ENT_QUOTES, 'UTF-8');
|
||||
$value = str_replace(['[', ']'], ['[', ']'], $value);
|
||||
$data['title'] = $value;
|
||||
$data['title'] = self::escapeContent($value);
|
||||
|
||||
default:
|
||||
$data[$field] = html_entity_decode($value, ENT_QUOTES, 'UTF-8');
|
||||
|
|
@ -312,13 +313,14 @@ class BBCode
|
|||
|
||||
public static function proxyUrl(string $image, int $simplehtml = self::INTERNAL, int $uriid = 0, string $size = ''): string
|
||||
{
|
||||
$image = self::idnUrl($image);
|
||||
// Only send proxied pictures to API and for internal display
|
||||
if (!in_array($simplehtml, [self::INTERNAL, self::MASTODON_API, self::TWITTER_API])) {
|
||||
return $image;
|
||||
} elseif ($uriid > 0) {
|
||||
return Post\Link::getByLink($uriid, $image, $size);
|
||||
} else {
|
||||
return Proxy::proxifyUrl($image, $size);
|
||||
return $image;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -551,71 +553,6 @@ class BBCode
|
|||
return $text . "\n" . $data['after'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts [url] BBCodes in a format that looks fine on Mastodon. (callback function)
|
||||
*
|
||||
* @param array $match Array with the matching values
|
||||
* @return string reformatted link including HTML codes
|
||||
*/
|
||||
private static function convertUrlForActivityPubCallback(array $match): string
|
||||
{
|
||||
$url = $match[1];
|
||||
|
||||
if (isset($match[2]) && ($match[1] != $match[2])) {
|
||||
return $match[0];
|
||||
}
|
||||
|
||||
$parts = parse_url($url);
|
||||
if (!isset($parts['scheme'])) {
|
||||
return $match[0];
|
||||
}
|
||||
|
||||
return self::convertUrlForActivityPub($url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts [url] BBCodes in a format that looks fine on ActivityPub systems.
|
||||
*
|
||||
* @param string $url URL that is about to be reformatted
|
||||
* @return string reformatted link including HTML codes
|
||||
*/
|
||||
private static function convertUrlForActivityPub(string $url): string
|
||||
{
|
||||
return sprintf('<a href="%s" target="_blank" rel="noopener noreferrer">%s</a>', $url, Strings::getStyledURL($url));
|
||||
}
|
||||
|
||||
/*
|
||||
* [noparse][i]italic[/i][/noparse] turns into
|
||||
* [noparse][ i ]italic[ /i ][/noparse],
|
||||
* to hide them from parser.
|
||||
*
|
||||
* @param array $match
|
||||
* @return string
|
||||
*/
|
||||
private static function escapeNoparseCallback(array $match): string
|
||||
{
|
||||
$whole_match = $match[0];
|
||||
$captured = $match[1];
|
||||
$spacefied = preg_replace("/\[(.*?)\]/", "[ $1 ]", $captured);
|
||||
$new_str = str_replace($captured, $spacefied, $whole_match);
|
||||
return $new_str;
|
||||
}
|
||||
|
||||
/*
|
||||
* The previously spacefied [noparse][ i ]italic[ /i ][/noparse],
|
||||
* now turns back and the [noparse] tags are trimmed
|
||||
* returning [i]italic[/i]
|
||||
*
|
||||
* @param array $match
|
||||
* @return string
|
||||
*/
|
||||
private static function unescapeNoparseCallback(array $match): string
|
||||
{
|
||||
$captured = $match[1];
|
||||
$unspacefied = preg_replace("/\[ (.*?)\ ]/", "[$1]", $captured);
|
||||
return $unspacefied;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the bracket character positions of a set of opening and closing BBCode tags, optionally skipping first
|
||||
* occurrences
|
||||
|
|
@ -1026,7 +963,7 @@ class BBCode
|
|||
$text = DI::cache()->get($cache_key);
|
||||
|
||||
if (is_null($text)) {
|
||||
$curlResult = DI::httpClient()->head($match[1], [HttpClientOptions::TIMEOUT => DI::config()->get('system', 'xrd_timeout')]);
|
||||
$curlResult = DI::httpClient()->head($match[1], [HttpClientOptions::TIMEOUT => DI::config()->get('system', 'xrd_timeout'), HttpClientOptions::REQUEST => HttpClientRequest::CONTENTTYPE]);
|
||||
if ($curlResult->isSuccess()) {
|
||||
$mimetype = $curlResult->getContentType() ?? '';
|
||||
} else {
|
||||
|
|
@ -1039,7 +976,7 @@ class BBCode
|
|||
$text = '[url=' . $match[2] . ']' . $match[2] . '[/url]';
|
||||
|
||||
// if its not a picture then look if its a page that contains a picture link
|
||||
$body = DI::httpClient()->fetch($match[1], HttpClientAccept::HTML, 0);
|
||||
$body = DI::httpClient()->fetch($match[1], HttpClientAccept::HTML, 0, '', HttpClientRequest::SITEINFO);
|
||||
if (empty($body)) {
|
||||
DI::cache()->set($cache_key, $text);
|
||||
return $text;
|
||||
|
|
@ -1124,7 +1061,7 @@ class BBCode
|
|||
return $text;
|
||||
}
|
||||
|
||||
$curlResult = DI::httpClient()->head($match[1], [HttpClientOptions::TIMEOUT => DI::config()->get('system', 'xrd_timeout')]);
|
||||
$curlResult = DI::httpClient()->head($match[1], [HttpClientOptions::TIMEOUT => DI::config()->get('system', 'xrd_timeout'), HttpClientOptions::REQUEST => HttpClientRequest::CONTENTTYPE]);
|
||||
if ($curlResult->isSuccess()) {
|
||||
$mimetype = $curlResult->getContentType() ?? '';
|
||||
} else {
|
||||
|
|
@ -1142,7 +1079,7 @@ class BBCode
|
|||
}
|
||||
|
||||
// if its not a picture then look if its a page that contains a picture link
|
||||
$body = DI::httpClient()->fetch($match[1], HttpClientAccept::HTML, 0);
|
||||
$body = DI::httpClient()->fetch($match[1], HttpClientAccept::HTML, 0, '', HttpClientRequest::SITEINFO);
|
||||
if (empty($body)) {
|
||||
DI::cache()->set($cache_key, $text);
|
||||
return $text;
|
||||
|
|
@ -1383,10 +1320,8 @@ class BBCode
|
|||
|
||||
Hook::callAll('bbcode', $text);
|
||||
|
||||
$a = DI::app();
|
||||
|
||||
$text = self::performWithEscapedTags($text, ['code'], function ($text) use ($try_oembed, $simple_html, $for_plaintext, $a, $uriid) {
|
||||
$text = self::performWithEscapedTags($text, ['noparse', 'nobb', 'pre'], function ($text) use ($try_oembed, $simple_html, $for_plaintext, $a, $uriid) {
|
||||
$text = self::performWithEscapedTags($text, ['code'], function ($text) use ($try_oembed, $simple_html, $for_plaintext, $uriid) {
|
||||
$text = self::performWithEscapedTags($text, ['noparse', 'nobb', 'pre'], function ($text) use ($try_oembed, $simple_html, $for_plaintext, $uriid) {
|
||||
/*
|
||||
* preg_match_callback function to replace potential Oembed tags with Oembed content
|
||||
*
|
||||
|
|
@ -1407,662 +1342,66 @@ class BBCode
|
|||
return $return;
|
||||
};
|
||||
|
||||
// Remove the abstract element. It is a non visible element.
|
||||
$text = self::stripAbstract($text);
|
||||
|
||||
// Line ending normalisation
|
||||
$text = str_replace("\r\n", "\n", $text);
|
||||
|
||||
// Move new lines outside of tags
|
||||
$text = preg_replace("#\[(\w*)](\n*)#ism", '$2[$1]', $text);
|
||||
$text = preg_replace("#(\n*)\[/(\w*)]#ism", '[/$2]$1', $text);
|
||||
|
||||
// Extract the private images which use data urls since preg has issues with
|
||||
// large data sizes. Stash them away while we do bbcode conversion, and then put them back
|
||||
// in after we've done all the regex matching. We cannot use any preg functions to do this.
|
||||
|
||||
$extracted = self::extractImagesFromItemBody($text);
|
||||
$text = $extracted['body'];
|
||||
$saved_image = $extracted['images'];
|
||||
|
||||
// If we find any event code, turn it into an event.
|
||||
// After we're finished processing the bbcode we'll
|
||||
// replace all of the event code with a reformatted version.
|
||||
// General clean up of the content, for example unneeded blanks and new lines
|
||||
$text = self::normaliseInput($extracted['body']);
|
||||
|
||||
$ev = Event::fromBBCode($text);
|
||||
// Now the structural elements are converted
|
||||
$text = self::convertHeaderToHtml($text, $simple_html);
|
||||
$text = self::convertStylesToHtml($text, $simple_html);
|
||||
$text = self::convertListsToHtml($text);
|
||||
$text = self::convertTablesToHtml($text);
|
||||
$text = self::convertSpoilersToHtml($text);
|
||||
$text = self::convertStructuresToHtml($text);
|
||||
|
||||
// Replace any html brackets with HTML Entities to prevent executing HTML or script
|
||||
// Don't use strip_tags here because it breaks [url] search by replacing & with amp
|
||||
|
||||
$text = str_replace("<", "<", $text);
|
||||
$text = str_replace(">", ">", $text);
|
||||
|
||||
// remove some newlines before the general conversion
|
||||
$text = preg_replace("/\s?\[share(.*?)\]\s?(.*?)\s?\[\/share\]\s?/ism", "\n[share$1]$2[/share]\n", $text);
|
||||
$text = preg_replace("/\s?\[quote(.*?)\]\s?(.*?)\s?\[\/quote\]\s?/ism", "\n[quote$1]$2[/quote]\n", $text);
|
||||
|
||||
// when the content is meant exporting to other systems then remove the avatar picture since this doesn't really look good on these systems
|
||||
if (!$try_oembed) {
|
||||
$text = preg_replace("/\[share(.*?)avatar\s?=\s?'.*?'\s?(.*?)\]\s?(.*?)\s?\[\/share\]\s?/ism", "\n[share$1$2]$3[/share]", $text);
|
||||
}
|
||||
|
||||
// Remove linefeeds inside of the table elements. See issue #6799
|
||||
$search = [
|
||||
"\n[th]", "[th]\n", " [th]", "\n[/th]", "[/th]\n", "[/th] ",
|
||||
"\n[td]", "[td]\n", " [td]", "\n[/td]", "[/td]\n", "[/td] ",
|
||||
"\n[tr]", "[tr]\n", " [tr]", "[tr] ", "\n[/tr]", "[/tr]\n", " [/tr]", "[/tr] ",
|
||||
"\n[hr]", "[hr]\n", " [hr]", "[hr] ",
|
||||
"\n[attachment ", " [attachment ", "\n[/attachment]", "[/attachment]\n", " [/attachment]", "[/attachment] ",
|
||||
"[table]\n", "[table] ", " [table]", "\n[/table]", " [/table]", "[/table] ",
|
||||
" \n", "\t\n", "[/li]\n", "\n[li]", "\n[*]",
|
||||
];
|
||||
$replace = [
|
||||
"[th]", "[th]", "[th]", "[/th]", "[/th]", "[/th]",
|
||||
"[td]", "[td]", "[td]", "[/td]", "[/td]", "[/td]",
|
||||
"[tr]", "[tr]", "[tr]", "[tr]", "[/tr]", "[/tr]", "[/tr]", "[/tr]",
|
||||
"[hr]", "[hr]", "[hr]", "[hr]",
|
||||
"[attachment ", "[attachment ", "[/attachment]", "[/attachment]", "[/attachment]", "[/attachment]",
|
||||
"[table]", "[table]", "[table]", "[/table]", "[/table]", "[/table]",
|
||||
"\n", "\n", "[/li]", "[li]", "[*]",
|
||||
];
|
||||
do {
|
||||
$oldtext = $text;
|
||||
$text = str_replace($search, $replace, $text);
|
||||
} while ($oldtext != $text);
|
||||
|
||||
// Replace these here only once
|
||||
$search = ["\n[table]", "[/table]\n"];
|
||||
$replace = ["[table]", "[/table]"];
|
||||
$text = str_replace($search, $replace, $text);
|
||||
|
||||
// Trim new lines regardless of the system.remove_multiplicated_lines config value
|
||||
$text = trim($text, "\n");
|
||||
|
||||
// removing multiplicated newlines
|
||||
if (DI::config()->get('system', 'remove_multiplicated_lines')) {
|
||||
$search = [
|
||||
"\n\n\n", "[/quote]\n\n", "\n[/quote]", "\n[ul]", "[/ul]\n", "\n[ol]", "[/ol]\n", "\n\n[share ", "[/attachment]\n",
|
||||
"\n[h1]", "[/h1]\n", "\n[h2]", "[/h2]\n", "\n[h3]", "[/h3]\n", "\n[h4]", "[/h4]\n", "\n[h5]", "[/h5]\n", "\n[h6]", "[/h6]\n"
|
||||
];
|
||||
$replace = [
|
||||
"\n\n", "[/quote]\n", "[/quote]", "[ul]", "[/ul]", "[ol]", "[/ol]", "\n[share ", "[/attachment]",
|
||||
"[h1]", "[/h1]", "[h2]", "[/h2]", "[h3]", "[/h3]", "[h4]", "[/h4]", "[h5]", "[/h5]", "[h6]", "[/h6]"
|
||||
];
|
||||
do {
|
||||
$oldtext = $text;
|
||||
$text = str_replace($search, $replace, $text);
|
||||
} while ($oldtext != $text);
|
||||
}
|
||||
|
||||
/// @todo Have a closer look at the different html modes
|
||||
// Handle attached links or videos
|
||||
if ($simple_html == self::NPF) {
|
||||
$text = self::removeAttachment($text);
|
||||
} elseif (in_array($simple_html, [self::MASTODON_API, self::TWITTER_API, self::ACTIVITYPUB])) {
|
||||
$text = self::replaceAttachment($text);
|
||||
} elseif (!in_array($simple_html, [self::INTERNAL, self::EXTERNAL, self::CONNECTORS])) {
|
||||
$text = self::replaceAttachment($text, true);
|
||||
} else {
|
||||
$text = self::convertAttachment($text, $simple_html, $try_oembed, [], $uriid);
|
||||
}
|
||||
|
||||
$nosmile = strpos($text, '[nosmile]') !== false;
|
||||
$text = str_replace('[nosmile]', '', $text);
|
||||
|
||||
// Replace non graphical smilies for external posts
|
||||
if (!$nosmile) {
|
||||
$text = self::performWithEscapedTags($text, ['url', 'img', 'audio', 'video', 'youtube', 'vimeo', 'share', 'attachment', 'iframe', 'bookmark'], function ($text) use ($simple_html, $for_plaintext) {
|
||||
return Smilies::replace($text, ($simple_html != self::INTERNAL) || $for_plaintext);
|
||||
});
|
||||
}
|
||||
|
||||
// leave open the possibility of [map=something]
|
||||
// this is replaced in Item::prepareBody() which has knowledge of the item location
|
||||
if (strpos($text, '[/map]') !== false) {
|
||||
$text = preg_replace_callback(
|
||||
"/\[map\](.*?)\[\/map\]/ism",
|
||||
function ($match) use ($simple_html) {
|
||||
return str_replace($match[0], '<p class="map">' . Map::byLocation($match[1], $simple_html) . '</p>', $match[0]);
|
||||
},
|
||||
$text
|
||||
);
|
||||
}
|
||||
|
||||
if (strpos($text, '[map=') !== false) {
|
||||
$text = preg_replace_callback(
|
||||
"/\[map=(.*?)\]/ism",
|
||||
function ($match) use ($simple_html) {
|
||||
return str_replace($match[0], '<p class="map">' . Map::byCoordinates(str_replace('/', ' ', $match[1]), $simple_html) . '</p>', $match[0]);
|
||||
},
|
||||
$text
|
||||
);
|
||||
}
|
||||
|
||||
if (strpos($text, '[map]') !== false) {
|
||||
$text = preg_replace("/\[map\]/", '<p class="map"></p>', $text);
|
||||
}
|
||||
|
||||
// Check for headers
|
||||
|
||||
if ($simple_html == self::INTERNAL) {
|
||||
//Ensure to always start with <h4> if possible
|
||||
$heading_count = 0;
|
||||
for ($level = 6; $level > 0; $level--) {
|
||||
if (preg_match("(\[h$level\].*?\[\/h$level\])ism", $text)) {
|
||||
$heading_count++;
|
||||
}
|
||||
// We add URL without a surrounding URL at this time, since at a earlier stage it would had been too early,
|
||||
// since the used regular expression won't touch URL inside of BBCode elements, but with the structural ones it should.
|
||||
// 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) ?? '';
|
||||
}
|
||||
if ($heading_count > 0) {
|
||||
$heading = min($heading_count + 3, 6);
|
||||
for ($level = 6; $level > 0; $level--) {
|
||||
if (preg_match("(\[h$level\].*?\[\/h$level\])ism", $text)) {
|
||||
$text = preg_replace("(\[h$level\](.*?)\[\/h$level\])ism", "</p><h$heading>$1</h$heading><p>", $text);
|
||||
$heading--;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$text = preg_replace("(\[h1\](.*?)\[\/h1\])ism", '</p><h1>$1</h1><p>', $text);
|
||||
$text = preg_replace("(\[h2\](.*?)\[\/h2\])ism", '</p><h2>$1</h2><p>', $text);
|
||||
$text = preg_replace("(\[h3\](.*?)\[\/h3\])ism", '</p><h3>$1</h3><p>', $text);
|
||||
$text = preg_replace("(\[h4\](.*?)\[\/h4\])ism", '</p><h4>$1</h4><p>', $text);
|
||||
$text = preg_replace("(\[h5\](.*?)\[\/h5\])ism", '</p><h5>$1</h5><p>', $text);
|
||||
$text = preg_replace("(\[h6\](.*?)\[\/h6\])ism", '</p><h6>$1</h6><p>', $text);
|
||||
}
|
||||
return self::convertSmileysToHtml($text, $simple_html, $for_plaintext);
|
||||
});
|
||||
|
||||
// Check for paragraph
|
||||
$text = preg_replace("(\[p\](.*?)\[\/p\])ism", '<p>$1</p>', $text);
|
||||
// Now for some more complex BBCode elements (mostly non standard ones)
|
||||
$text = self::convertAttachmentsToHtml($text, $simple_html, $try_oembed, $uriid);
|
||||
$text = self::convertMapsToHtml($text, $simple_html);
|
||||
$text = self::convertQuotesToHtml($text);
|
||||
$text = self::convertVideoPlatformsToHtml($text, $try_oembed);
|
||||
$text = self::convertOEmbedToHtml($text, $uriid);
|
||||
$text = self::convertEventsToHtml($text, $simple_html, $uriid);
|
||||
|
||||
// Check for bold text
|
||||
$text = preg_replace("(\[b\](.*?)\[\/b\])ism", '<strong>$1</strong>', $text);
|
||||
// Some simpler non standard elements
|
||||
$text = self::convertEmojisToHtml($text, $simple_html);
|
||||
$text = self::convertCryptToHtml($text);
|
||||
$text = self::convertIFramesToHtml($text);
|
||||
$text = self::convertMailToHtml($text);
|
||||
$text = self::convertAudioVideoToHtml($text, $simple_html, $try_oembed, $try_oembed_callback);
|
||||
|
||||
// Check for Italics text
|
||||
$text = preg_replace("(\[i\](.*?)\[\/i\])ism", '<em>$1</em>', $text);
|
||||
|
||||
// Check for Underline text
|
||||
$text = preg_replace("(\[u\](.*?)\[\/u\])ism", '<u>$1</u>', $text);
|
||||
|
||||
// Check for strike-through text
|
||||
$text = preg_replace("(\[s\](.*?)\[\/s\])ism", '<s>$1</s>', $text);
|
||||
|
||||
// Check for over-line text
|
||||
$text = preg_replace("(\[o\](.*?)\[\/o\])ism", '<span class="overline">$1</span>', $text);
|
||||
|
||||
// Check for colored text
|
||||
$text = preg_replace("(\[color=(.*?)\](.*?)\[\/color\])ism", "<span style=\"color: $1;\">$2</span>", $text);
|
||||
|
||||
// Check for sized text
|
||||
// [size=50] --> font-size: 50px (with the unit).
|
||||
if ($simple_html != self::DIASPORA) {
|
||||
$text = preg_replace("(\[size=(\d*?)\](.*?)\[\/size\])ism", '<span style="font-size:$1px;line-height:normal;">$2</span>', $text);
|
||||
$text = preg_replace("(\[size=(.*?)\](.*?)\[\/size\])ism", '<span style="font-size:$1;line-height:normal;">$2</span>', $text);
|
||||
} else {
|
||||
// Issue 2199: Diaspora doesn't interpret the construct above, nor the <small> or <big> element
|
||||
$text = preg_replace("(\[size=(.*?)\](.*?)\[\/size\])ism", "$2", $text);
|
||||
}
|
||||
|
||||
|
||||
// Check for centered text
|
||||
$text = preg_replace("(\[center\](.*?)\[\/center\])ism", '<div style="text-align:center;">$1</div>', $text);
|
||||
|
||||
// Check for block-level custom CSS
|
||||
$text = preg_replace('#(?<=^|\n)\[style=(.*?)](.*?)\[/style](?:\n|$)#ism', '<div style="$1">$2</div>', $text);
|
||||
|
||||
// Check for inline custom CSS
|
||||
$text = preg_replace("(\[style=(.*?)\](.*?)\[\/style\])ism", '<span style="$1">$2</span>', $text);
|
||||
|
||||
// Mastodon Emoji (internal tag, do not document for users)
|
||||
if ($simple_html == self::MASTODON_API) {
|
||||
$text = preg_replace("(\[emoji=(.*?)](.*?)\[/emoji])ism", '$2', $text);
|
||||
} else {
|
||||
$text = preg_replace("(\[emoji=(.*?)](.*?)\[/emoji])ism", '<span class="mastodon emoji"><img src="$1" alt="$2" title="$2"/></span>', $text);
|
||||
}
|
||||
|
||||
// Check for CSS classes
|
||||
// @deprecated since 2021.12, left for backward-compatibility reasons
|
||||
$text = preg_replace("(\[class=(.*?)\](.*?)\[\/class\])ism", '<span class="$1">$2</span>', $text);
|
||||
// Add HTML new lines
|
||||
$text = str_replace("\n\n", '</p><p>', $text);
|
||||
$text = str_replace("\n", '<br>', $text);
|
||||
|
||||
// handle nested lists
|
||||
$endlessloop = 0;
|
||||
|
||||
while ((((strpos($text, "[/list]") !== false) && (strpos($text, "[list") !== false)) ||
|
||||
((strpos($text, "[/ol]") !== false) && (strpos($text, "[ol]") !== false)) ||
|
||||
((strpos($text, "[/ul]") !== false) && (strpos($text, "[ul]") !== false)) ||
|
||||
((strpos($text, "[/li]") !== false) && (strpos($text, "[li]") !== false))) && (++$endlessloop < 20)) {
|
||||
$text = preg_replace("/\[list\](.*?)\[\/list\]/ism", '</p><ul class="listbullet" style="list-style-type: circle;">$1</ul><p>', $text);
|
||||
$text = preg_replace("/\[list=\](.*?)\[\/list\]/ism", '</p><ul class="listnone" style="list-style-type: none;">$1</ul><p>', $text);
|
||||
$text = preg_replace("/\[list=1\](.*?)\[\/list\]/ism", '</p><ul class="listdecimal" style="list-style-type: decimal;">$1</ul><p>', $text);
|
||||
$text = preg_replace("/\[list=((?-i)i)\](.*?)\[\/list\]/ism", '</p><ul class="listlowerroman" style="list-style-type: lower-roman;">$2</ul><p>', $text);
|
||||
$text = preg_replace("/\[list=((?-i)I)\](.*?)\[\/list\]/ism", '</p><ul class="listupperroman" style="list-style-type: upper-roman;">$2</ul><p>', $text);
|
||||
$text = preg_replace("/\[list=((?-i)a)\](.*?)\[\/list\]/ism", '</p><ul class="listloweralpha" style="list-style-type: lower-alpha;">$2</ul><p>', $text);
|
||||
$text = preg_replace("/\[list=((?-i)A)\](.*?)\[\/list\]/ism", '</p><ul class="listupperalpha" style="list-style-type: upper-alpha;">$2</ul><p>', $text);
|
||||
$text = preg_replace("/\[ul\](.*?)\[\/ul\]/ism", '</p><ul>$1</ul><p>', $text);
|
||||
$text = preg_replace("/\[ol\](.*?)\[\/ol\]/ism", '</p><ol>$1</ol><p>', $text);
|
||||
$text = preg_replace("/\[li\](.*?)\[\/li\]/ism", '<li>$1</li>', $text);
|
||||
}
|
||||
|
||||
// Check for list text
|
||||
$text = str_replace("[*]", "<li>", $text);
|
||||
$text = str_replace("[li]", "<li>", $text);
|
||||
|
||||
$text = preg_replace("/\[th\](.*?)\[\/th\]/sm", '<th>$1</th>', $text);
|
||||
$text = preg_replace("/\[td\](.*?)\[\/td\]/sm", '<td>$1</td>', $text);
|
||||
$text = preg_replace("/\[tr\](.*?)\[\/tr\]/sm", '<tr>$1</tr>', $text);
|
||||
$text = preg_replace("/\[table\](.*?)\[\/table\]/sm", '</p><table>$1</table><p>', $text);
|
||||
|
||||
$text = preg_replace("/\[table border=1\](.*?)\[\/table\]/sm", '</p><table border="1" >$1</table><p>', $text);
|
||||
$text = preg_replace("/\[table border=0\](.*?)\[\/table\]/sm", '</p><table border="0" >$1</table><p>', $text);
|
||||
|
||||
$text = str_replace('[hr]', '</p><hr /><p>', $text);
|
||||
|
||||
if (!$for_plaintext) {
|
||||
$text = self::performWithEscapedTags($text, ['url', 'img', 'audio', 'video', 'youtube', 'vimeo', 'share', 'attachment', 'iframe', 'bookmark'], function ($text) {
|
||||
return preg_replace(Strings::autoLinkRegEx(), '[url]$1[/url]', $text);
|
||||
});
|
||||
}
|
||||
|
||||
// Check for font change text
|
||||
$text = preg_replace("/\[font=(.*?)\](.*?)\[\/font\]/sm", "<span style=\"font-family: $1;\">$2</span>", $text);
|
||||
|
||||
// Declare the format for [spoiler] layout
|
||||
$SpoilerLayout = '<details class="spoiler"><summary>' . DI::l10n()->t('Click to open/close') . '</summary>$1</details>';
|
||||
|
||||
// Check for [spoiler] text
|
||||
// handle nested quotes
|
||||
$endlessloop = 0;
|
||||
while ((strpos($text, "[/spoiler]") !== false) && (strpos($text, "[spoiler]") !== false) && (++$endlessloop < 20)) {
|
||||
$text = preg_replace("/\[spoiler\](.*?)\[\/spoiler\]/ism", $SpoilerLayout, $text);
|
||||
}
|
||||
|
||||
// Check for [spoiler=Title] text
|
||||
|
||||
// handle nested quotes
|
||||
$endlessloop = 0;
|
||||
while ((strpos($text, "[/spoiler]") !== false) && (strpos($text, "[spoiler=") !== false) && (++$endlessloop < 20)) {
|
||||
$text = preg_replace(
|
||||
"/\[spoiler=[\"\']*(.*?)[\"\']*\](.*?)\[\/spoiler\]/ism",
|
||||
'<details class="spoiler"><summary>$1</summary>$2</details>',
|
||||
$text
|
||||
);
|
||||
}
|
||||
|
||||
// Declare the format for [quote] layout
|
||||
$QuoteLayout = '</p><blockquote>$1</blockquote><p>';
|
||||
|
||||
// Check for [quote] text
|
||||
// handle nested quotes
|
||||
$endlessloop = 0;
|
||||
while ((strpos($text, "[/quote]") !== false) && (strpos($text, "[quote]") !== false) && (++$endlessloop < 20)) {
|
||||
$text = preg_replace("/\[quote\](.*?)\[\/quote\]/ism", "$QuoteLayout", $text);
|
||||
}
|
||||
|
||||
// Check for [quote=Author] text
|
||||
|
||||
$t_wrote = DI::l10n()->t('$1 wrote:');
|
||||
|
||||
// handle nested quotes
|
||||
$endlessloop = 0;
|
||||
while ((strpos($text, "[/quote]") !== false) && (strpos($text, "[quote=") !== false) && (++$endlessloop < 20)) {
|
||||
$text = preg_replace(
|
||||
"/\[quote=[\"\']*(.*?)[\"\']*\](.*?)\[\/quote\]/ism",
|
||||
"<p><strong class=" . '"author"' . ">" . $t_wrote . "</strong></p><blockquote>$2</blockquote>",
|
||||
$text
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// [img=widthxheight]image source[/img]
|
||||
$text = preg_replace_callback(
|
||||
"/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/ism",
|
||||
function ($matches) use ($simple_html, $uriid) {
|
||||
if (strpos($matches[3], "data:image/") === 0) {
|
||||
return $matches[0];
|
||||
}
|
||||
|
||||
$matches[3] = self::proxyUrl($matches[3], $simple_html, $uriid);
|
||||
return "[img=" . $matches[1] . "x" . $matches[2] . "]" . $matches[3] . "[/img]";
|
||||
},
|
||||
$text
|
||||
);
|
||||
|
||||
$text = preg_replace("/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/ism", '<img src="$3" style="width: $1px;" >', $text);
|
||||
$text = preg_replace("/\[zmg\=([0-9]*)x([0-9]*)\](.*?)\[\/zmg\]/ism", '<img class="zrl" src="$3" style="width: $1px;" >', $text);
|
||||
|
||||
$text = preg_replace_callback(
|
||||
"/\[[iz]mg\=(.*?)\](.*?)\[\/[iz]mg\]/ism",
|
||||
function ($matches) use ($simple_html, $uriid) {
|
||||
$matches[1] = self::proxyUrl($matches[1], $simple_html, $uriid);
|
||||
$alt = htmlspecialchars($matches[2], ENT_COMPAT);
|
||||
// Fix for Markdown problems with Diaspora, see issue #12701
|
||||
if (($simple_html != self::DIASPORA) || strpos($matches[2], '"') === false) {
|
||||
return '<img src="' . $matches[1] . '" alt="' . $alt . '" title="' . $alt . '">';
|
||||
} else {
|
||||
return '<img src="' . $matches[1] . '" alt="' . $alt . '">';
|
||||
}
|
||||
},
|
||||
$text
|
||||
);
|
||||
|
||||
// Images
|
||||
// [img]pathtoimage[/img]
|
||||
$text = preg_replace_callback(
|
||||
"/\[[iz]mg\](.*?)\[\/[iz]mg\]/ism",
|
||||
function ($matches) use ($simple_html, $uriid) {
|
||||
if (strpos($matches[1], "data:image/") === 0) {
|
||||
return $matches[0];
|
||||
}
|
||||
|
||||
$matches[1] = self::proxyUrl($matches[1], $simple_html, $uriid);
|
||||
return "[img]" . $matches[1] . "[/img]";
|
||||
},
|
||||
$text
|
||||
);
|
||||
|
||||
$text = preg_replace("/\[img\](.*?)\[\/img\]/ism", '<img src="$1" alt="' . DI::l10n()->t('Image/photo') . '" />', $text);
|
||||
$text = preg_replace("/\[zmg\](.*?)\[\/zmg\]/ism", '<img src="$1" alt="' . DI::l10n()->t('Image/photo') . '" />', $text);
|
||||
|
||||
$text = self::convertImages($text, $simple_html, $uriid);
|
||||
|
||||
$text = preg_replace("/\[crypt\](.*?)\[\/crypt\]/ism", '<br><img src="' . DI::baseUrl() . '/images/lock_icon.gif" alt="' . DI::l10n()->t('Encrypted content') . '" title="' . DI::l10n()->t('Encrypted content') . '" /><br>', $text);
|
||||
$text = preg_replace("/\[crypt(.*?)\](.*?)\[\/crypt\]/ism", '<br><img src="' . DI::baseUrl() . '/images/lock_icon.gif" alt="' . DI::l10n()->t('Encrypted content') . '" title="' . '$1' . ' ' . DI::l10n()->t('Encrypted content') . '" /><br>', $text);
|
||||
//$text = preg_replace("/\[crypt=(.*?)\](.*?)\[\/crypt\]/ism", '<br><img src="' .DI::baseUrl() . '/images/lock_icon.gif" alt="' . DI::l10n()->t('Encrypted content') . '" title="' . '$1' . ' ' . DI::l10n()->t('Encrypted content') . '" /><br>', $text);
|
||||
|
||||
// Simplify "video" element
|
||||
$text = preg_replace('(\[video[^\]]*?\ssrc\s?=\s?([^\s\]]+)[^\]]*?\].*?\[/video\])ism', '[video]$1[/video]', $text);
|
||||
|
||||
$text = preg_replace_callback("/\[(video)\](.*?)\[\/video\]/ism", [self::class, 'sanitizeLinksCallback'], $text);
|
||||
$text = preg_replace_callback("/\[(audio)\](.*?)\[\/audio\]/ism", [self::class, 'sanitizeLinksCallback'], $text);
|
||||
|
||||
if ($simple_html == self::NPF) {
|
||||
$text = preg_replace(
|
||||
"/\[video\](.*?)\[\/video\]/ism",
|
||||
'</p><video src="$1" controls width="100%" height="auto">$1</video><p>',
|
||||
$text
|
||||
);
|
||||
$text = preg_replace(
|
||||
"/\[audio\](.*?)\[\/audio\]/ism",
|
||||
'</p><audio src="$1" controls>$1">$1</audio><p>',
|
||||
$text
|
||||
);
|
||||
} elseif ($try_oembed) {
|
||||
// html5 video and audio
|
||||
$text = preg_replace(
|
||||
"/\[video\](.*?\.(ogg|ogv|oga|ogm|webm|mp4).*?)\[\/video\]/ism",
|
||||
'<video src="$1" controls width="100%" height="auto"><a href="$1">$1</a></video>',
|
||||
$text
|
||||
);
|
||||
|
||||
$text = preg_replace_callback("/\[video\](.*?)\[\/video\]/ism", $try_oembed_callback, $text);
|
||||
$text = preg_replace_callback("/\[audio\](.*?)\[\/audio\]/ism", $try_oembed_callback, $text);
|
||||
|
||||
$text = preg_replace(
|
||||
"/\[video\](.*?)\[\/video\]/ism",
|
||||
'<a href="$1" target="_blank" rel="noopener noreferrer">$1</a>',
|
||||
$text
|
||||
);
|
||||
$text = preg_replace("/\[audio\](.*?)\[\/audio\]/ism", '<audio src="$1" controls><a href="$1">$1</a></audio>', $text);
|
||||
} else {
|
||||
$text = preg_replace(
|
||||
"/\[video\](.*?)\[\/video\]/ism",
|
||||
'<a href="$1" target="_blank" rel="noopener noreferrer">$1</a>',
|
||||
$text
|
||||
);
|
||||
$text = preg_replace(
|
||||
"/\[audio\](.*?)\[\/audio\]/ism",
|
||||
'<a href="$1" target="_blank" rel="noopener noreferrer">$1</a>',
|
||||
$text
|
||||
);
|
||||
}
|
||||
|
||||
// Backward compatibility, [iframe] support has been removed in version 2020.12
|
||||
$text = preg_replace_callback("/\[(iframe)\](.*?)\[\/iframe\]/ism", [self::class, 'sanitizeLinksCallback'], $text);
|
||||
$text = preg_replace("/\[iframe\](.*?)\[\/iframe\]/ism", '<a href="$1">$1</a>', $text);
|
||||
|
||||
$text = self::normalizeVideoLinks($text);
|
||||
|
||||
// Youtube extensions
|
||||
if ($try_oembed && OEmbed::isAllowedURL('https://www.youtube.com/embed/')) {
|
||||
$text = preg_replace("/\[youtube\]([A-Za-z0-9\-_=]+)(.*?)\[\/youtube\]/ism", '<iframe width="' . $a->getThemeInfoValue('videowidth') . '" height="' . $a->getThemeInfoValue('videoheight') . '" src="https://www.youtube.com/embed/$1" frameborder="0" ></iframe>', $text);
|
||||
} else {
|
||||
$text = preg_replace(
|
||||
"/\[youtube\]([A-Za-z0-9\-_=]+)(.*?)\[\/youtube\]/ism",
|
||||
'<a href="https://www.youtube.com/watch?v=$1" target="_blank" rel="noopener noreferrer">https://www.youtube.com/watch?v=$1</a>',
|
||||
$text
|
||||
);
|
||||
}
|
||||
|
||||
// Vimeo extensions
|
||||
if ($try_oembed && OEmbed::isAllowedURL('https://player.vimeo.com/video')) {
|
||||
$text = preg_replace("/\[vimeo\]([0-9]+)(.*?)\[\/vimeo\]/ism", '<iframe width="' . $a->getThemeInfoValue('videowidth') . '" height="' . $a->getThemeInfoValue('videoheight') . '" src="https://player.vimeo.com/video/$1" frameborder="0" ></iframe>', $text);
|
||||
} else {
|
||||
$text = preg_replace(
|
||||
"/\[vimeo\]([0-9]+)(.*?)\[\/vimeo\]/ism",
|
||||
'<a href="https://vimeo.com/$1" target="_blank" rel="noopener noreferrer">https://vimeo.com/$1</a>',
|
||||
$text
|
||||
);
|
||||
}
|
||||
|
||||
// oembed tag
|
||||
$text = OEmbed::BBCode2HTML($text, $uriid);
|
||||
|
||||
// Avoid triple linefeeds through oembed
|
||||
$text = str_replace("<br style='clear:left'></span><br><br>", "<br style='clear:left'></span><br>", $text);
|
||||
|
||||
// If we found an event earlier, strip out all the event code and replace with a reformatted version.
|
||||
// Replace the event-start section with the entire formatted event. The other bbcode is stripped.
|
||||
// Summary (e.g. title) is required, earlier revisions only required description (in addition to
|
||||
// start which is always required). Allow desc with a missing summary for compatibility.
|
||||
|
||||
if ((!empty($ev['desc']) || !empty($ev['summary'])) && !empty($ev['start'])) {
|
||||
$sub = Event::getHTML($ev, $simple_html, $uriid);
|
||||
|
||||
$text = preg_replace("/\[event\-summary\](.*?)\[\/event\-summary\]/ism", '', $text);
|
||||
$text = preg_replace("/\[event\-description\](.*?)\[\/event\-description\]/ism", '', $text);
|
||||
$text = preg_replace("/\[event\-start\](.*?)\[\/event\-start\]/ism", $sub, $text);
|
||||
$text = preg_replace("/\[event\-finish\](.*?)\[\/event\-finish\]/ism", '', $text);
|
||||
$text = preg_replace("/\[event\-location\](.*?)\[\/event\-location\]/ism", '', $text);
|
||||
$text = preg_replace("/\[event\-id\](.*?)\[\/event\-id\]/ism", '', $text);
|
||||
}
|
||||
// At last, some standard elements. URL has to go last,
|
||||
// since some previous conversions use URL elements.
|
||||
$text = self::convertImagesToHtml($text, $simple_html, $uriid);
|
||||
$text = self::convertUrlToHtml($text, $simple_html, $for_plaintext, $try_oembed, $try_oembed_callback);
|
||||
|
||||
// If the post only consists of an emoji, we display it larger than normal.
|
||||
if (!$for_plaintext && DI::config()->get('system', 'big_emojis') && ($simple_html != self::DIASPORA) && Smilies::isEmojiPost($text)) {
|
||||
$text = '<span style="font-size: xx-large; line-height: normal;">' . $text . '</span>';
|
||||
}
|
||||
|
||||
$text = preg_replace_callback("/\[(url)\](.*?)\[\/url\]/ism", [self::class, 'sanitizeLinksCallback'], $text);
|
||||
$text = preg_replace_callback("/\[(url)\=(.*?)\](.*?)\[\/url\]/ism", [self::class, 'sanitizeLinksCallback'], $text);
|
||||
// Sanitize the created HTML.
|
||||
$text = self::cleanupHtml($text);
|
||||
|
||||
// Handle mentions and hashtag links
|
||||
if ($simple_html == self::DIASPORA) {
|
||||
// The ! is converted to @ since Diaspora only understands the @
|
||||
$text = preg_replace(
|
||||
"/([@!])\[url\=(.*?)\](.*?)\[\/url\]/ism",
|
||||
'@<a href="$2">$3</a>',
|
||||
$text
|
||||
);
|
||||
} elseif (in_array($simple_html, [self::OSTATUS, self::ACTIVITYPUB])) {
|
||||
$text = preg_replace(
|
||||
"/([@!])\[url\=(.*?)\](.*?)\[\/url\]/ism",
|
||||
'<span class="h-card"><a href="$2" class="u-url mention">$1<span>$3</span></a></span>',
|
||||
$text
|
||||
);
|
||||
$text = preg_replace(
|
||||
"/([#])\[url\=(.*?)\](.*?)\[\/url\]/ism",
|
||||
'<a href="$2" class="mention hashtag" rel="tag">$1<span>$3</span></a>',
|
||||
$text
|
||||
);
|
||||
} elseif (in_array($simple_html, [self::INTERNAL, self::EXTERNAL, self::TWITTER_API])) {
|
||||
$text = preg_replace(
|
||||
"/([@!])\[url\=(.*?)\](.*?)\[\/url\]/ism",
|
||||
'<bdi>$1<a href="$2" class="userinfo mention" title="$3">$3</a></bdi>',
|
||||
$text
|
||||
);
|
||||
} elseif ($simple_html == self::MASTODON_API) {
|
||||
$text = preg_replace(
|
||||
"/([@!])\[url\=(.*?)\](.*?)\[\/url\]/ism",
|
||||
'<a class="u-url mention status-link" href="$2" rel="nofollow noopener noreferrer" target="_blank" title="$3">$1<span>$3</span></a>',
|
||||
$text
|
||||
);
|
||||
$text = preg_replace(
|
||||
"/([#])\[url\=(.*?)\](.*?)\[\/url\]/ism",
|
||||
'<a class="mention hashtag status-link" href="$2" rel="tag">$1<span>$3</span></a>',
|
||||
$text
|
||||
);
|
||||
} else {
|
||||
$text = preg_replace("/([#@!])\[url\=(.*?)\](.*?)\[\/url\]/ism", '$1$3', $text);
|
||||
}
|
||||
// This needs to be called after the cleanup, since otherwise some links are invalidated
|
||||
$text = self::convertSharesToHtml($text, $simple_html, $try_oembed, $uriid);
|
||||
|
||||
if (!$for_plaintext) {
|
||||
if (in_array($simple_html, [self::OSTATUS, self::MASTODON_API, self::TWITTER_API, self::ACTIVITYPUB])) {
|
||||
$text = preg_replace_callback("/\[url\](.*?)\[\/url\]/ism", [self::class, 'convertUrlForActivityPubCallback'], $text);
|
||||
$text = preg_replace_callback("/\[url\=(.*?)\](.*?)\[\/url\]/ism", [self::class, 'convertUrlForActivityPubCallback'], $text);
|
||||
}
|
||||
} else {
|
||||
$text = preg_replace("(\[url\](.*?)\[\/url\])ism", " $1 ", $text);
|
||||
$text = preg_replace_callback("&\[url=([^\[\]]*)\]\[img\](.*)\[\/img\]\[\/url\]&Usi", [self::class, 'removePictureLinksCallback'], $text);
|
||||
}
|
||||
|
||||
// Bookmarks in red - will be converted to bookmarks in friendica
|
||||
$text = preg_replace("/#\^\[url\](.*?)\[\/url\]/ism", '[bookmark=$1]$1[/bookmark]', $text);
|
||||
$text = preg_replace("/#\^\[url\=(.*?)\](.*?)\[\/url\]/ism", '[bookmark=$1]$2[/bookmark]', $text);
|
||||
$text = preg_replace(
|
||||
"/#\[url\=.*?\]\^\[\/url\]\[url\=(.*?)\](.*?)\[\/url\]/i",
|
||||
"[bookmark=$1]$2[/bookmark]",
|
||||
$text
|
||||
);
|
||||
|
||||
if (in_array($simple_html, [self::OSTATUS, self::TWITTER, self::BLUESKY])) {
|
||||
$text = preg_replace_callback("/([^#@!])\[url\=([^\]]*)\](.*?)\[\/url\]/ism", [self::class, 'expandLinksCallback'], $text);
|
||||
//$text = preg_replace("/[^#@!]\[url\=([^\]]*)\](.*?)\[\/url\]/ism", ' $2 [url]$1[/url]', $text);
|
||||
$text = preg_replace("/\[bookmark\=([^\]]*)\](.*?)\[\/bookmark\]/ism", ' $2 [url]$1[/url]', $text);
|
||||
}
|
||||
|
||||
// Perform URL Search
|
||||
if ($try_oembed) {
|
||||
$text = preg_replace_callback("/\[bookmark\=([^\]]*)\](.*?)\[\/bookmark\]/ism", $try_oembed_callback, $text);
|
||||
}
|
||||
|
||||
$text = preg_replace("/\[bookmark\=([^\]]*)\](.*?)\[\/bookmark\]/ism", '[url=$1]$2[/url]', $text);
|
||||
|
||||
// Handle Diaspora posts
|
||||
$text = preg_replace_callback(
|
||||
"&\[url=/?posts/([^\[\]]*)\](.*)\[\/url\]&Usi",
|
||||
function ($match) {
|
||||
return "[url=" . DI::baseUrl() . "/display/" . $match[1] . "]" . $match[2] . "[/url]";
|
||||
},
|
||||
$text
|
||||
);
|
||||
|
||||
$text = preg_replace_callback(
|
||||
"&\[url=/people\?q\=(.*)\](.*)\[\/url\]&Usi",
|
||||
function ($match) {
|
||||
return "[url=" . DI::baseUrl() . "/search?search=%40" . $match[1] . "]" . $match[2] . "[/url]";
|
||||
},
|
||||
$text
|
||||
);
|
||||
|
||||
// Server independent link to posts and comments
|
||||
// See issue: https://github.com/diaspora/diaspora_federation/issues/75
|
||||
$expression = "=diaspora://.*?/post/([0-9A-Za-z\-_@.:]{15,254}[0-9A-Za-z])=ism";
|
||||
$text = preg_replace($expression, DI::baseUrl() . "/display/$1", $text);
|
||||
|
||||
/* Tag conversion
|
||||
* Supports:
|
||||
* - #[url=<anything>]<term>[/url]
|
||||
* - [url=<anything>]#<term>[/url]
|
||||
*/
|
||||
self::performWithEscapedTags($text, ['url', 'share'], function ($text) use ($simple_html) {
|
||||
$text = preg_replace_callback("/(?:#\[url\=[^\[\]]*\]|\[url\=[^\[\]]*\]#)(.*?)\[\/url\]/ism", function ($matches) use ($simple_html) {
|
||||
if ($simple_html == self::ACTIVITYPUB) {
|
||||
return '<a href="' . DI::baseUrl() . '/search?tag=' . urlencode($matches[1])
|
||||
. '" data-tag="' . XML::escape($matches[1]) . '" rel="tag ugc">#'
|
||||
. XML::escape($matches[1]) . '</a>';
|
||||
} else {
|
||||
return '#<a href="' . DI::baseUrl() . '/search?tag=' . urlencode($matches[1])
|
||||
. '" class="tag" rel="tag" title="' . XML::escape($matches[1]) . '">'
|
||||
. XML::escape($matches[1]) . '</a>';
|
||||
}
|
||||
}, $text);
|
||||
return $text;
|
||||
});
|
||||
|
||||
// We need no target="_blank" rel="noopener noreferrer" for local links
|
||||
// convert links start with DI::baseUrl() as local link without the target="_blank" rel="noopener noreferrer" attribute
|
||||
$escapedBaseUrl = preg_quote(DI::baseUrl(), '/');
|
||||
$text = preg_replace("/\[url\](" . $escapedBaseUrl . ".*?)\[\/url\]/ism", '<a href="$1">$1</a>', $text);
|
||||
$text = preg_replace("/\[url\=(" . $escapedBaseUrl . ".*?)\](.*?)\[\/url\]/ism", '<a href="$1">$2</a>', $text);
|
||||
|
||||
$text = preg_replace("/\[url\](.*?)\[\/url\]/ism", '<a href="$1" target="_blank" rel="noopener noreferrer">$1</a>', $text);
|
||||
$text = preg_replace("/\[url\=(.*?)\](.*?)\[\/url\]/ism", '<a href="$1" target="_blank" rel="noopener noreferrer">$2</a>', $text);
|
||||
|
||||
// Red compatibility, though the link can't be authenticated on Friendica
|
||||
$text = preg_replace("/\[zrl\=(.*?)\](.*?)\[\/zrl\]/ism", '<a href="$1" target="_blank" rel="noopener noreferrer">$2</a>', $text);
|
||||
|
||||
|
||||
// we may need to restrict this further if it picks up too many strays
|
||||
// link acct:user@host to a webfinger profile redirector
|
||||
|
||||
$text = preg_replace('/acct:([^@]+)@((?!\-)(?:[a-zA-Z\d\-]{0,62}[a-zA-Z\d]\.){1,126}(?!\d+)[a-zA-Z\d]{1,63})/', '<a href="' . DI::baseUrl() . '/acctlink?addr=$1@$2" target="extlink">acct:$1@$2</a>', $text);
|
||||
|
||||
// Perform MAIL Search
|
||||
$text = preg_replace_callback("/\[(mail)\](.*?)\[\/mail\]/ism", [self::class, 'sanitizeLinksCallback'], $text);
|
||||
$text = preg_replace("/\[mail\](.*?)\[\/mail\]/", '<a href="mailto:$1">$1</a>', $text);
|
||||
$text = preg_replace("/\[mail\=(.*?)\](.*?)\[\/mail\]/", '<a href="mailto:$1">$2</a>', $text);
|
||||
|
||||
/// @todo What is the meaning of these lines?
|
||||
$text = preg_replace('/\[\&\;([#a-z0-9]+)\;\]/', '&$1;', $text);
|
||||
$text = preg_replace('/\&\#039\;/', '\'', $text);
|
||||
|
||||
// Currently deactivated, it made problems with " inside of alt texts.
|
||||
//$text = preg_replace('/\"\;/', '"', $text);
|
||||
|
||||
// fix any escaped ampersands that may have been converted into links
|
||||
$text = preg_replace('/\<([^>]*?)(src|href)=(.*?)\&\;(.*?)\>/ism', '<$1$2=$3&$4>', $text);
|
||||
|
||||
// sanitizes src attributes (http and redir URLs for displaying in a web page, cid used for inline images in emails)
|
||||
$allowed_src_protocols = ['//', 'http://', 'https://', 'contact/redir/', 'cid:'];
|
||||
|
||||
array_walk($allowed_src_protocols, function (&$value) {
|
||||
$value = preg_quote($value, '#');
|
||||
});
|
||||
|
||||
$text = preg_replace(
|
||||
'#<([^>]*?)(src)="(?!' . implode('|', $allowed_src_protocols) . ')(.*?)"(.*?)>#ism',
|
||||
'<$1$2=""$4 data-original-src="$3" class="invalid-src" title="' . DI::l10n()->t('Invalid source protocol') . '">',
|
||||
$text
|
||||
);
|
||||
|
||||
// sanitize href attributes (only allowlisted protocols URLs)
|
||||
// default value for backward compatibility
|
||||
$allowed_link_protocols = DI::config()->get('system', 'allowed_link_protocols', []);
|
||||
|
||||
// Always allowed protocol even if config isn't set or not including it
|
||||
$allowed_link_protocols[] = '//';
|
||||
$allowed_link_protocols[] = 'http://';
|
||||
$allowed_link_protocols[] = 'https://';
|
||||
$allowed_link_protocols[] = 'contact/redir/';
|
||||
|
||||
array_walk($allowed_link_protocols, function (&$value) {
|
||||
$value = preg_quote($value, '#');
|
||||
});
|
||||
|
||||
$regex = '#<([^>]*?)(href)="(?!' . implode('|', $allowed_link_protocols) . ')(.*?)"(.*?)>#ism';
|
||||
$text = preg_replace($regex, '<$1$2="javascript:void(0)"$4 data-original-href="$3" class="invalid-href" title="' . DI::l10n()->t('Invalid link protocol') . '">', $text);
|
||||
|
||||
// Shared content
|
||||
$text = self::convertShare(
|
||||
$text,
|
||||
function (array $attributes, array $author_contact, $content, $is_quote_share) use ($simple_html) {
|
||||
return self::convertShareCallback($attributes, $author_contact, $content, $is_quote_share, $simple_html);
|
||||
},
|
||||
$uriid
|
||||
);
|
||||
|
||||
$text = self::interpolateSavedImagesIntoItemBody($uriid, $text, $saved_image);
|
||||
|
||||
return $text;
|
||||
// Insert the previously extracted embedded image again.
|
||||
return self::interpolateSavedImagesIntoItemBody($uriid, $text, $saved_image);
|
||||
}); // Escaped noparse, nobb, pre
|
||||
|
||||
// Remove escaping tags and replace new lines that remain
|
||||
|
|
@ -2112,6 +1451,799 @@ class BBCode
|
|||
return trim($text);
|
||||
}
|
||||
|
||||
private static function normaliseInput(string $text): string
|
||||
{
|
||||
// Remove the abstract element. It is a non visible element.
|
||||
$text = self::stripAbstract($text);
|
||||
|
||||
// Line ending normalisation
|
||||
$text = str_replace("\r\n", "\n", $text);
|
||||
|
||||
// Move new lines outside of tags
|
||||
$text = preg_replace("#\[(\w*)](\n*)#ism", '$2[$1]', $text);
|
||||
$text = preg_replace("#(\n*)\[/(\w*)]#ism", '[/$2]$1', $text);
|
||||
|
||||
// Replace any html brackets with HTML Entities to prevent executing HTML or script
|
||||
// Don't use strip_tags here because it breaks [url] search by replacing & with amp
|
||||
|
||||
$text = str_replace("<", "<", $text);
|
||||
$text = str_replace(">", ">", $text);
|
||||
|
||||
// remove some newlines before the general conversion
|
||||
$text = preg_replace("/\s?\[share(.*?)\]\s?(.*?)\s?\[\/share\]\s?/ism", "\n[share$1]$2[/share]\n", $text);
|
||||
$text = preg_replace("/\s?\[quote(.*?)\]\s?(.*?)\s?\[\/quote\]\s?/ism", "\n[quote$1]$2[/quote]\n", $text);
|
||||
|
||||
// Remove linefeeds inside of the table elements. See issue #6799
|
||||
$search = [
|
||||
"\n[th]", "[th]\n", " [th]", "\n[/th]", "[/th]\n", "[/th] ",
|
||||
"\n[td]", "[td]\n", " [td]", "\n[/td]", "[/td]\n", "[/td] ",
|
||||
"\n[tr]", "[tr]\n", " [tr]", "[tr] ", "\n[/tr]", "[/tr]\n", " [/tr]", "[/tr] ",
|
||||
"\n[hr]", "[hr]\n", " [hr]", "[hr] ",
|
||||
"\n[attachment ", " [attachment ", "\n[/attachment]", "[/attachment]\n", " [/attachment]", "[/attachment] ",
|
||||
"[table]\n", "[table] ", " [table]", "\n[/table]", " [/table]", "[/table] ",
|
||||
" \n", "\t\n", "[/li]\n", "\n[li]", "\n[*]",
|
||||
];
|
||||
$replace = [
|
||||
"[th]", "[th]", "[th]", "[/th]", "[/th]", "[/th]",
|
||||
"[td]", "[td]", "[td]", "[/td]", "[/td]", "[/td]",
|
||||
"[tr]", "[tr]", "[tr]", "[tr]", "[/tr]", "[/tr]", "[/tr]", "[/tr]",
|
||||
"[hr]", "[hr]", "[hr]", "[hr]",
|
||||
"[attachment ", "[attachment ", "[/attachment]", "[/attachment]", "[/attachment]", "[/attachment]",
|
||||
"[table]", "[table]", "[table]", "[/table]", "[/table]", "[/table]",
|
||||
"\n", "\n", "[/li]", "[li]", "[*]",
|
||||
];
|
||||
do {
|
||||
$oldtext = $text;
|
||||
$text = str_replace($search, $replace, $text);
|
||||
} while ($oldtext != $text);
|
||||
|
||||
// Replace these here only once
|
||||
$search = ["\n[table]", "[/table]\n"];
|
||||
$replace = ["[table]", "[/table]"];
|
||||
$text = str_replace($search, $replace, $text);
|
||||
|
||||
// Trim new lines regardless of the system.remove_multiplicated_lines config value
|
||||
$text = trim($text, "\n");
|
||||
|
||||
// removing multiplicated newlines
|
||||
if (DI::config()->get('system', 'remove_multiplicated_lines')) {
|
||||
$search = [
|
||||
"\n\n\n", "[/quote]\n\n", "\n[/quote]", "\n[ul]", "[/ul]\n", "\n[ol]", "[/ol]\n", "\n\n[share ", "[/attachment]\n",
|
||||
"\n[h1]", "[/h1]\n", "\n[h2]", "[/h2]\n", "\n[h3]", "[/h3]\n", "\n[h4]", "[/h4]\n", "\n[h5]", "[/h5]\n", "\n[h6]", "[/h6]\n"
|
||||
];
|
||||
$replace = [
|
||||
"\n\n", "[/quote]\n", "[/quote]", "[ul]", "[/ul]", "[ol]", "[/ol]", "\n[share ", "[/attachment]",
|
||||
"[h1]", "[/h1]", "[h2]", "[/h2]", "[h3]", "[/h3]", "[h4]", "[/h4]", "[h5]", "[/h5]", "[h6]", "[/h6]"
|
||||
];
|
||||
do {
|
||||
$oldtext = $text;
|
||||
$text = str_replace($search, $replace, $text);
|
||||
} while ($oldtext != $text);
|
||||
}
|
||||
|
||||
return $text;
|
||||
}
|
||||
|
||||
private static function convertEventsToHtml(string $text, int $simple_html, int $uriid): string
|
||||
{
|
||||
// If we find any event code, turn it into an event.
|
||||
// After we're finished processing the bbcode we'll
|
||||
// replace all of the event code with a reformatted version.
|
||||
|
||||
$ev = Event::fromBBCode($text);
|
||||
|
||||
// If we found an event earlier, strip out all the event code and replace with a reformatted version.
|
||||
// Replace the event-start section with the entire formatted event. The other bbcode is stripped.
|
||||
// Summary (e.g. title) is required, earlier revisions only required description (in addition to
|
||||
// start which is always required). Allow desc with a missing summary for compatibility.
|
||||
|
||||
if ((!empty($ev['desc']) || !empty($ev['summary'])) && !empty($ev['start'])) {
|
||||
$sub = Event::getHTML($ev, $simple_html, $uriid);
|
||||
|
||||
$text = preg_replace("/\[event\-summary\](.*?)\[\/event\-summary\]/ism", '', $text);
|
||||
$text = preg_replace("/\[event\-description\](.*?)\[\/event\-description\]/ism", '', $text);
|
||||
$text = preg_replace("/\[event\-start\](.*?)\[\/event\-start\]/ism", $sub, $text);
|
||||
$text = preg_replace("/\[event\-finish\](.*?)\[\/event\-finish\]/ism", '', $text);
|
||||
$text = preg_replace("/\[event\-location\](.*?)\[\/event\-location\]/ism", '', $text);
|
||||
$text = preg_replace("/\[event\-id\](.*?)\[\/event\-id\]/ism", '', $text);
|
||||
}
|
||||
|
||||
return $text;
|
||||
}
|
||||
|
||||
private static function convertAttachmentsToHtml(string $text, int $simple_html, bool $try_oembed, int $uriid): string
|
||||
{
|
||||
/// @todo Have a closer look at the different html modes
|
||||
// Handle attached links or videos
|
||||
if ($simple_html == self::NPF) {
|
||||
$text = self::removeAttachment($text);
|
||||
} elseif (in_array($simple_html, [self::MASTODON_API, self::TWITTER_API, self::ACTIVITYPUB])) {
|
||||
$text = self::replaceAttachment($text);
|
||||
} elseif (!in_array($simple_html, [self::INTERNAL, self::EXTERNAL, self::CONNECTORS])) {
|
||||
$text = self::replaceAttachment($text, true);
|
||||
} else {
|
||||
$text = self::convertAttachment($text, $simple_html, $try_oembed, [], $uriid);
|
||||
}
|
||||
|
||||
return $text;
|
||||
}
|
||||
|
||||
private static function convertMapsToHtml(string $text, int $simple_html): string
|
||||
{
|
||||
// leave open the possibility of [map=something]
|
||||
// this is replaced in Item::prepareBody() which has knowledge of the item location
|
||||
if (strpos($text, '[/map]') !== false) {
|
||||
$text = preg_replace_callback(
|
||||
"/\[map\](.*?)\[\/map\]/ism",
|
||||
function ($match) use ($simple_html) {
|
||||
return str_replace($match[0], '<p class="map">' . Map::byLocation($match[1], $simple_html) . '</p>', $match[0]);
|
||||
},
|
||||
$text
|
||||
);
|
||||
}
|
||||
|
||||
if (strpos($text, '[map=') !== false) {
|
||||
$text = preg_replace_callback(
|
||||
"/\[map=(.*?)\]/ism",
|
||||
function ($match) use ($simple_html) {
|
||||
return str_replace($match[0], '<p class="map">' . Map::byCoordinates(str_replace('/', ' ', $match[1]), $simple_html) . '</p>', $match[0]);
|
||||
},
|
||||
$text
|
||||
);
|
||||
}
|
||||
|
||||
if (strpos($text, '[map]') !== false) {
|
||||
$text = preg_replace("/\[map\]/", '<p class="map"></p>', $text);
|
||||
}
|
||||
|
||||
return $text;
|
||||
}
|
||||
|
||||
private static function convertHeaderToHtml(string $text, int $simple_html): string
|
||||
{
|
||||
// Check for headers
|
||||
|
||||
if ($simple_html == self::INTERNAL) {
|
||||
//Ensure to always start with <h4> if possible
|
||||
$heading_count = 0;
|
||||
for ($level = 6; $level > 0; $level--) {
|
||||
if (preg_match("(\[h$level\].*?\[\/h$level\])ism", $text)) {
|
||||
$heading_count++;
|
||||
}
|
||||
}
|
||||
if ($heading_count > 0) {
|
||||
$heading = min($heading_count + 3, 6);
|
||||
for ($level = 6; $level > 0; $level--) {
|
||||
if (preg_match("(\[h$level\].*?\[\/h$level\])ism", $text)) {
|
||||
$text = preg_replace("(\[h$level\](.*?)\[\/h$level\])ism", "</p><h$heading>$1</h$heading><p>", $text);
|
||||
$heading--;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$text = preg_replace("(\[h1\](.*?)\[\/h1\])ism", '</p><h1>$1</h1><p>', $text);
|
||||
$text = preg_replace("(\[h2\](.*?)\[\/h2\])ism", '</p><h2>$1</h2><p>', $text);
|
||||
$text = preg_replace("(\[h3\](.*?)\[\/h3\])ism", '</p><h3>$1</h3><p>', $text);
|
||||
$text = preg_replace("(\[h4\](.*?)\[\/h4\])ism", '</p><h4>$1</h4><p>', $text);
|
||||
$text = preg_replace("(\[h5\](.*?)\[\/h5\])ism", '</p><h5>$1</h5><p>', $text);
|
||||
$text = preg_replace("(\[h6\](.*?)\[\/h6\])ism", '</p><h6>$1</h6><p>', $text);
|
||||
}
|
||||
|
||||
return $text;
|
||||
}
|
||||
|
||||
private static function convertEmojisToHtml(string $text, int $simple_html): string
|
||||
{
|
||||
// Mastodon Emoji (internal tag, do not document for users)
|
||||
if ($simple_html == self::MASTODON_API) {
|
||||
$text = preg_replace("(\[emoji=(.*?)](.*?)\[/emoji])ism", '$2', $text);
|
||||
} else {
|
||||
$text = preg_replace("(\[emoji=(.*?)](.*?)\[/emoji])ism", '<span class="mastodon emoji"><img src="$1" alt="$2" title="$2"/></span>', $text);
|
||||
}
|
||||
return $text;
|
||||
}
|
||||
|
||||
private static function convertStylesToHtml(string $text, int $simple_html): string
|
||||
{
|
||||
// Markdown is designed to pass through HTML elements that it can't handle itself,
|
||||
// so that the other system would parse the original HTML element.
|
||||
// But Diaspora has chosen not to do this and doesn't parse HTML elements.
|
||||
// So we need to make some changes here.
|
||||
if ($simple_html == BBCode::DIASPORA) {
|
||||
$elements = ['big', 'small'];
|
||||
foreach ($elements as $bbcode) {
|
||||
$text = preg_replace("(\[" . $bbcode . "\](.*?)\[\/" . $bbcode . "\])ism", '$1', $text);
|
||||
}
|
||||
|
||||
$elements = [
|
||||
'del' => 's', 'ins' => 'em', 'kbd' => 'code', 'mark' => 'strong',
|
||||
'samp' => 'code', 'u' => 'em', 'var' => 'em'
|
||||
];
|
||||
foreach ($elements as $bbcode => $html) {
|
||||
$text = preg_replace("(\[" . $bbcode . "\](.*?)\[\/" . $bbcode . "\])ism", '<' . $html . '>$1</' . $html . '>', $text);
|
||||
}
|
||||
}
|
||||
|
||||
// Several easy to replace HTML elements
|
||||
// @todo add the new elements to the documentation by the end of 2024 so that most systems will support them.
|
||||
$elements = [
|
||||
'b', 'del', 'em', 'i', 'ins', 'kbd', 'mark',
|
||||
's', 'samp', 'small', 'strong', 'sub', 'sup', 'u', 'var'
|
||||
];
|
||||
foreach ($elements as $element) {
|
||||
$text = preg_replace("(\[" . $element . "\](.*?)\[\/" . $element . "\])ism", '<' . $element . '>$1</' . $element . '>', $text);
|
||||
}
|
||||
|
||||
$text = preg_replace("(\[big\](.*?)\[\/big\])ism", "<span style=\"font-size: larger;\">$1</span>", $text);
|
||||
|
||||
// Check for over-line text
|
||||
$text = preg_replace("(\[o\](.*?)\[\/o\])ism", '<span class="overline">$1</span>', $text);
|
||||
|
||||
// Check for colored text
|
||||
$text = preg_replace("(\[color=(.*?)\](.*?)\[\/color\])ism", "<span style=\"color: $1;\">$2</span>", $text);
|
||||
|
||||
// Check for sized text
|
||||
// [size=50] --> font-size: 50px (with the unit).
|
||||
if ($simple_html != self::DIASPORA) {
|
||||
$text = preg_replace("(\[size=(\d*?)\](.*?)\[\/size\])ism", '<span style="font-size:$1px;line-height:normal;">$2</span>', $text);
|
||||
$text = preg_replace("(\[size=(.*?)\](.*?)\[\/size\])ism", '<span style="font-size:$1;line-height:normal;">$2</span>', $text);
|
||||
} else {
|
||||
// Issue 2199: Diaspora doesn't interpret the construct above, nor the <small> or <big> element
|
||||
$text = preg_replace("(\[size=(.*?)\](.*?)\[\/size\])ism", "$2", $text);
|
||||
}
|
||||
|
||||
// Check for centered text
|
||||
$text = preg_replace("(\[center\](.*?)\[\/center\])ism", '<div style="text-align:center;">$1</div>', $text);
|
||||
|
||||
// Check for block-level custom CSS
|
||||
$text = preg_replace('#(?<=^|\n)\[style=(.*?)](.*?)\[/style](?:\n|$)#ism', '<div style="$1">$2</div>', $text);
|
||||
|
||||
// Check for inline custom CSS
|
||||
$text = preg_replace("(\[style=(.*?)\](.*?)\[\/style\])ism", '<span style="$1">$2</span>', $text);
|
||||
|
||||
// Check for CSS classes
|
||||
// @deprecated since 2021.12, left for backward-compatibility reasons
|
||||
$text = preg_replace("(\[class=(.*?)\](.*?)\[\/class\])ism", '<span class="$1">$2</span>', $text);
|
||||
// Add HTML new lines
|
||||
$text = str_replace("\n\n", '</p><p>', $text);
|
||||
$text = str_replace("\n", '<br>', $text);
|
||||
|
||||
// Check for font change text
|
||||
$text = preg_replace("/\[font=(.*?)\](.*?)\[\/font\]/sm", "<span style=\"font-family: $1;\">$2</span>", $text);
|
||||
|
||||
return $text;
|
||||
}
|
||||
|
||||
private static function convertTablesToHtml(string $text): string
|
||||
{
|
||||
$text = preg_replace("/\[th\](.*?)\[\/th\]/sm", '<th>$1</th>', $text);
|
||||
$text = preg_replace("/\[td\](.*?)\[\/td\]/sm", '<td>$1</td>', $text);
|
||||
$text = preg_replace("/\[tr\](.*?)\[\/tr\]/sm", '<tr>$1</tr>', $text);
|
||||
$text = preg_replace("/\[table\](.*?)\[\/table\]/sm", '</p><table>$1</table><p>', $text);
|
||||
|
||||
$text = preg_replace("/\[table border=1\](.*?)\[\/table\]/sm", '</p><table border="1" >$1</table><p>', $text);
|
||||
$text = preg_replace("/\[table border=0\](.*?)\[\/table\]/sm", '</p><table border="0" >$1</table><p>', $text);
|
||||
|
||||
return $text;
|
||||
}
|
||||
|
||||
private static function convertListsToHtml(string $text): string
|
||||
{
|
||||
// handle nested lists
|
||||
$endlessloop = 0;
|
||||
|
||||
while ((((strpos($text, "[/list]") !== false) && (strpos($text, "[list") !== false)) ||
|
||||
((strpos($text, "[/ol]") !== false) && (strpos($text, "[ol]") !== false)) ||
|
||||
((strpos($text, "[/ul]") !== false) && (strpos($text, "[ul]") !== false)) ||
|
||||
((strpos($text, "[/li]") !== false) && (strpos($text, "[li]") !== false))) && (++$endlessloop < 20)) {
|
||||
$text = preg_replace("/\[list\](.*?)\[\/list\]/ism", '</p><ul class="listbullet" style="list-style-type: circle;">$1</ul><p>', $text);
|
||||
$text = preg_replace("/\[list=\](.*?)\[\/list\]/ism", '</p><ul class="listnone" style="list-style-type: none;">$1</ul><p>', $text);
|
||||
$text = preg_replace("/\[list=1\](.*?)\[\/list\]/ism", '</p><ul class="listdecimal" style="list-style-type: decimal;">$1</ul><p>', $text);
|
||||
$text = preg_replace("/\[list=((?-i)i)\](.*?)\[\/list\]/ism", '</p><ul class="listlowerroman" style="list-style-type: lower-roman;">$2</ul><p>', $text);
|
||||
$text = preg_replace("/\[list=((?-i)I)\](.*?)\[\/list\]/ism", '</p><ul class="listupperroman" style="list-style-type: upper-roman;">$2</ul><p>', $text);
|
||||
$text = preg_replace("/\[list=((?-i)a)\](.*?)\[\/list\]/ism", '</p><ul class="listloweralpha" style="list-style-type: lower-alpha;">$2</ul><p>', $text);
|
||||
$text = preg_replace("/\[list=((?-i)A)\](.*?)\[\/list\]/ism", '</p><ul class="listupperalpha" style="list-style-type: upper-alpha;">$2</ul><p>', $text);
|
||||
$text = preg_replace("/\[ul\](.*?)\[\/ul\]/ism", '</p><ul>$1</ul><p>', $text);
|
||||
$text = preg_replace("/\[ol\](.*?)\[\/ol\]/ism", '</p><ol>$1</ol><p>', $text);
|
||||
$text = preg_replace("/\[li\](.*?)\[\/li\]/ism", '<li>$1</li>', $text);
|
||||
}
|
||||
|
||||
// Check for list text
|
||||
$text = str_replace("[*]", "<li>", $text);
|
||||
$text = str_replace("[li]", "<li>", $text);
|
||||
|
||||
return $text;
|
||||
}
|
||||
|
||||
private static function convertSpoilersToHtml(string $text): string
|
||||
{
|
||||
// Declare the format for [spoiler] layout
|
||||
$SpoilerLayout = '<details class="spoiler"><summary>' . DI::l10n()->t('Click to open/close') . '</summary>$1</details>';
|
||||
|
||||
// Check for [spoiler] text
|
||||
// handle nested quotes
|
||||
$endlessloop = 0;
|
||||
while ((strpos($text, "[/spoiler]") !== false) && (strpos($text, "[spoiler]") !== false) && (++$endlessloop < 20)) {
|
||||
$text = preg_replace("/\[spoiler\](.*?)\[\/spoiler\]/ism", $SpoilerLayout, $text);
|
||||
}
|
||||
|
||||
// Check for [spoiler=Title] text
|
||||
|
||||
// handle nested quotes
|
||||
$endlessloop = 0;
|
||||
while ((strpos($text, "[/spoiler]") !== false) && (strpos($text, "[spoiler=") !== false) && (++$endlessloop < 20)) {
|
||||
$text = preg_replace(
|
||||
"/\[spoiler=[\"\']*(.*?)[\"\']*\](.*?)\[\/spoiler\]/ism",
|
||||
'<details class="spoiler"><summary>$1</summary>$2</details>',
|
||||
$text
|
||||
);
|
||||
}
|
||||
|
||||
return $text;
|
||||
}
|
||||
|
||||
private static function convertStructuresToHtml(string $text): string
|
||||
{
|
||||
$text = preg_replace("(\[p\](.*?)\[\/p\])ism", '<p>$1</p>', $text);
|
||||
// Check for paragraph
|
||||
return str_replace('[hr]', '</p><hr /><p>', $text);
|
||||
}
|
||||
|
||||
private static function convertSmileysToHtml(string $text, int $simple_html, bool $for_plaintext): string
|
||||
{
|
||||
if (strpos($text, '[nosmile]') !== false) {
|
||||
$text = str_replace('[nosmile]', '', $text);
|
||||
return $text;
|
||||
}
|
||||
|
||||
return Smilies::replace($text, ($simple_html != self::INTERNAL) || $for_plaintext);
|
||||
}
|
||||
|
||||
private static function convertQuotesToHtml(string $text): string
|
||||
{
|
||||
// Declare the format for [quote] layout
|
||||
$QuoteLayout = '</p><blockquote>$1</blockquote><p>';
|
||||
|
||||
// Check for [quote] text
|
||||
// handle nested quotes
|
||||
$endlessloop = 0;
|
||||
while ((strpos($text, "[/quote]") !== false) && (strpos($text, "[quote]") !== false) && (++$endlessloop < 20)) {
|
||||
$text = preg_replace("/\[quote\](.*?)\[\/quote\]/ism", "$QuoteLayout", $text);
|
||||
}
|
||||
|
||||
// Check for [quote=Author] text
|
||||
|
||||
$t_wrote = DI::l10n()->t('$1 wrote:');
|
||||
|
||||
// handle nested quotes
|
||||
$endlessloop = 0;
|
||||
while ((strpos($text, "[/quote]") !== false) && (strpos($text, "[quote=") !== false) && (++$endlessloop < 20)) {
|
||||
$text = preg_replace(
|
||||
"/\[quote=[\"\']*(.*?)[\"\']*\](.*?)\[\/quote\]/ism",
|
||||
"<p><strong class=" . '"author"' . ">" . $t_wrote . "</strong></p><blockquote>$2</blockquote>",
|
||||
$text
|
||||
);
|
||||
}
|
||||
|
||||
return $text;
|
||||
}
|
||||
|
||||
private static function convertImagesToHtml(string $text, int $simple_html, int $uriid): string
|
||||
{
|
||||
// [img=widthxheight]image source[/img]
|
||||
$text = preg_replace_callback(
|
||||
"/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/ism",
|
||||
function ($matches) use ($simple_html, $uriid) {
|
||||
if (strpos($matches[3], "data:image/") === 0) {
|
||||
return $matches[0];
|
||||
}
|
||||
|
||||
$matches[3] = self::proxyUrl($matches[3], $simple_html, $uriid);
|
||||
return "[img=" . $matches[1] . "x" . $matches[2] . "]" . $matches[3] . "[/img]";
|
||||
},
|
||||
$text
|
||||
);
|
||||
|
||||
$text = preg_replace("/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/ism", '<img src="$3" style="width: $1px;" >', $text);
|
||||
$text = preg_replace("/\[zmg\=([0-9]*)x([0-9]*)\](.*?)\[\/zmg\]/ism", '<img class="zrl" src="$3" style="width: $1px;" >', $text);
|
||||
|
||||
$text = preg_replace_callback(
|
||||
"/\[[iz]mg\=(.*?)\](.*?)\[\/[iz]mg\]/ism",
|
||||
function ($matches) use ($simple_html, $uriid) {
|
||||
$matches[1] = self::proxyUrl($matches[1], $simple_html, $uriid);
|
||||
$alt = htmlspecialchars($matches[2], ENT_COMPAT);
|
||||
// Fix for Markdown problems with Diaspora, see issue #12701
|
||||
if (($simple_html != self::DIASPORA) || strpos($matches[2], '"') === false) {
|
||||
return '<img src="' . $matches[1] . '" alt="' . $alt . '" title="' . $alt . '">';
|
||||
} else {
|
||||
return '<img src="' . $matches[1] . '" alt="' . $alt . '">';
|
||||
}
|
||||
},
|
||||
$text
|
||||
);
|
||||
|
||||
// Images
|
||||
// [img]pathtoimage[/img]
|
||||
$text = preg_replace_callback(
|
||||
"/\[[iz]mg\](.*?)\[\/[iz]mg\]/ism",
|
||||
function ($matches) use ($simple_html, $uriid) {
|
||||
if (strpos($matches[1], "data:image/") === 0) {
|
||||
return $matches[0];
|
||||
}
|
||||
|
||||
$matches[1] = self::proxyUrl($matches[1], $simple_html, $uriid);
|
||||
return "[img]" . $matches[1] . "[/img]";
|
||||
},
|
||||
$text
|
||||
);
|
||||
|
||||
$text = preg_replace("/\[img\](.*?)\[\/img\]/ism", '<img src="$1" alt="' . DI::l10n()->t('Image/photo') . '" />', $text);
|
||||
$text = preg_replace("/\[zmg\](.*?)\[\/zmg\]/ism", '<img src="$1" alt="' . DI::l10n()->t('Image/photo') . '" />', $text);
|
||||
|
||||
$text = self::convertImages($text, $simple_html, $uriid);
|
||||
|
||||
return $text;
|
||||
}
|
||||
|
||||
private static function convertCryptToHtml(string $text): string
|
||||
{
|
||||
$text = preg_replace("/\[crypt\](.*?)\[\/crypt\]/ism", '<br><img src="' . DI::baseUrl() . '/images/lock_icon.gif" alt="' . DI::l10n()->t('Encrypted content') . '" title="' . DI::l10n()->t('Encrypted content') . '" /><br>', $text);
|
||||
$text = preg_replace("/\[crypt(.*?)\](.*?)\[\/crypt\]/ism", '<br><img src="' . DI::baseUrl() . '/images/lock_icon.gif" alt="' . DI::l10n()->t('Encrypted content') . '" title="' . '$1' . ' ' . DI::l10n()->t('Encrypted content') . '" /><br>', $text);
|
||||
return $text;
|
||||
}
|
||||
|
||||
private static function convertAudioVideoToHtml(string $text, int $simple_html, bool $try_oembed, \Closure $try_oembed_callback): string
|
||||
{
|
||||
// Simplify "video" element
|
||||
$text = preg_replace('(\[video[^\]]*?\ssrc\s?=\s?([^\s\]]+)[^\]]*?\].*?\[/video\])ism', '[video]$1[/video]', $text);
|
||||
|
||||
$text = preg_replace_callback("/\[(video)\](.*?)\[\/video\]/ism", [self::class, 'sanitizeLinksCallback'], $text);
|
||||
$text = preg_replace_callback("/\[(audio)\](.*?)\[\/audio\]/ism", [self::class, 'sanitizeLinksCallback'], $text);
|
||||
|
||||
if ($simple_html == self::NPF) {
|
||||
$text = preg_replace(
|
||||
"/\[video\](.*?)\[\/video\]/ism",
|
||||
'</p><video src="$1" controls width="100%" height="auto">$1</video><p>',
|
||||
$text
|
||||
);
|
||||
$text = preg_replace(
|
||||
"/\[audio\](.*?)\[\/audio\]/ism",
|
||||
'</p><audio src="$1" controls>$1">$1</audio><p>',
|
||||
$text
|
||||
);
|
||||
} elseif ($try_oembed) {
|
||||
// html5 video and audio
|
||||
$text = preg_replace(
|
||||
"/\[video\](.*?\.(ogg|ogv|oga|ogm|webm|mp4).*?)\[\/video\]/ism",
|
||||
'<video src="$1" controls width="100%" height="auto"><a href="$1">$1</a></video>',
|
||||
$text
|
||||
);
|
||||
|
||||
$text = preg_replace_callback("/\[video\](.*?)\[\/video\]/ism", $try_oembed_callback, $text);
|
||||
$text = preg_replace_callback("/\[audio\](.*?)\[\/audio\]/ism", $try_oembed_callback, $text);
|
||||
|
||||
$text = preg_replace("/\[video\](.*?)\[\/video\]/ism", '[url]$1[/url]', $text);
|
||||
$text = preg_replace("/\[audio\](.*?)\[\/audio\]/ism", '<audio src="$1" controls><a href="$1">$1</a></audio>', $text);
|
||||
} else {
|
||||
$text = preg_replace("/\[video\](.*?)\[\/video\]/ism", '[url]$1[/url]', $text);
|
||||
$text = preg_replace("/\[audio\](.*?)\[\/audio\]/ism", '[url]$1[/url]', $text);
|
||||
}
|
||||
return $text;
|
||||
}
|
||||
|
||||
private static function convertIFramesToHtml(string $text): string
|
||||
{
|
||||
// Backward compatibility, [iframe] support has been removed in version 2020.12
|
||||
$text = preg_replace_callback("/\[(iframe)\](.*?)\[\/iframe\]/ism", [self::class, 'sanitizeLinksCallback'], $text);
|
||||
$text = preg_replace("/\[iframe\](.*?)\[\/iframe\]/ism", '[url]$1[/url]', $text);
|
||||
|
||||
return $text;
|
||||
}
|
||||
|
||||
private static function convertVideoPlatformsToHtml(string $text, bool $try_oembed): string
|
||||
{
|
||||
$a = DI::app();
|
||||
$text = self::normalizeVideoLinks($text);
|
||||
|
||||
// Youtube extensions
|
||||
if ($try_oembed && OEmbed::isAllowedURL('https://www.youtube.com/embed/')) {
|
||||
$text = preg_replace("/\[youtube\]([A-Za-z0-9\-_=]+)(.*?)\[\/youtube\]/ism", '<iframe width="' . $a->getThemeInfoValue('videowidth') . '" height="' . $a->getThemeInfoValue('videoheight') . '" src="https://www.youtube.com/embed/$1" frameborder="0" ></iframe>', $text);
|
||||
} else {
|
||||
$text = preg_replace("/\[youtube\]([A-Za-z0-9\-_=]+)(.*?)\[\/youtube\]/ism", '[url]https://www.youtube.com/watch?v=$1[/url]', $text);
|
||||
}
|
||||
|
||||
// Vimeo extensions
|
||||
if ($try_oembed && OEmbed::isAllowedURL('https://player.vimeo.com/video')) {
|
||||
$text = preg_replace("/\[vimeo\]([0-9]+)(.*?)\[\/vimeo\]/ism", '<iframe width="' . $a->getThemeInfoValue('videowidth') . '" height="' . $a->getThemeInfoValue('videoheight') . '" src="https://player.vimeo.com/video/$1" frameborder="0" ></iframe>', $text);
|
||||
} else {
|
||||
$text = preg_replace("/\[vimeo\]([0-9]+)(.*?)\[\/vimeo\]/ism", '[url]https://vimeo.com/$1[/url]', $text);
|
||||
}
|
||||
return $text;
|
||||
}
|
||||
|
||||
private static function convertOEmbedToHtml(string $text, int $uriid): string
|
||||
{
|
||||
// oembed tag
|
||||
$text = OEmbed::BBCode2HTML($text, $uriid);
|
||||
|
||||
// Avoid triple linefeeds through oembed
|
||||
$text = str_replace("<br style='clear:left'></span><br><br>", "<br style='clear:left'></span><br>", $text);
|
||||
|
||||
return $text;
|
||||
}
|
||||
|
||||
private static function convertUrlToHtml(string $text, int $simple_html, bool $for_plaintext, bool $try_oembed, \Closure $try_oembed_callback): string
|
||||
{
|
||||
$text = preg_replace_callback("/\[(url)\](.*?)\[\/url\]/ism", [self::class, 'sanitizeLinksCallback'], $text);
|
||||
$text = preg_replace_callback("/\[(url)\=(.*?)\](.*?)\[\/url\]/ism", [self::class, 'sanitizeLinksCallback'], $text);
|
||||
|
||||
// Handle mentions and hashtag links
|
||||
if ($simple_html == self::DIASPORA) {
|
||||
// The ! is converted to @ since Diaspora only understands the @
|
||||
$text = preg_replace(
|
||||
"/([@!])\[url\=(.*?)\](.*?)\[\/url\]/ism",
|
||||
'@<a href="$2">$3</a>',
|
||||
$text
|
||||
);
|
||||
} elseif (in_array($simple_html, [self::OSTATUS, self::ACTIVITYPUB])) {
|
||||
$text = preg_replace(
|
||||
"/([@!])\[url\=(.*?)\](.*?)\[\/url\]/ism",
|
||||
'<span class="h-card"><a href="$2" class="u-url mention">$1<span>$3</span></a></span>',
|
||||
$text
|
||||
);
|
||||
$text = preg_replace(
|
||||
"/([#])\[url\=(.*?)\](.*?)\[\/url\]/ism",
|
||||
'<a href="$2" class="mention hashtag" rel="tag">$1<span>$3</span></a>',
|
||||
$text
|
||||
);
|
||||
} elseif (in_array($simple_html, [self::INTERNAL, self::EXTERNAL, self::TWITTER_API])) {
|
||||
$text = preg_replace(
|
||||
"/([@!])\[url\=(.*?)\](.*?)\[\/url\]/ism",
|
||||
'<bdi>$1<a href="$2" class="userinfo mention" title="$3">$3</a></bdi>',
|
||||
$text
|
||||
);
|
||||
} elseif ($simple_html == self::MASTODON_API) {
|
||||
$text = preg_replace(
|
||||
"/([@!])\[url\=(.*?)\](.*?)\[\/url\]/ism",
|
||||
'<a class="u-url mention status-link" href="$2" rel="nofollow noopener noreferrer" target="_blank" title="$3">$1<span>$3</span></a>',
|
||||
$text
|
||||
);
|
||||
$text = preg_replace(
|
||||
"/([#])\[url\=(.*?)\](.*?)\[\/url\]/ism",
|
||||
'<a class="mention hashtag status-link" href="$2" rel="tag">$1<span>$3</span></a>',
|
||||
$text
|
||||
);
|
||||
} else {
|
||||
$text = preg_replace("/([#@!])\[url\=(.*?)\](.*?)\[\/url\]/ism", '$1$3', $text);
|
||||
}
|
||||
|
||||
if ($for_plaintext) {
|
||||
$text = preg_replace("(\[url\](.*?)\[\/url\])ism", " $1 ", $text);
|
||||
$text = preg_replace_callback("&\[url=([^\[\]]*)\]\[img\](.*)\[\/img\]\[\/url\]&Usi", [self::class, 'removePictureLinksCallback'], $text);
|
||||
}
|
||||
|
||||
// Bookmarks in red - will be converted to bookmarks in friendica
|
||||
$text = preg_replace("/#\^\[url\](.*?)\[\/url\]/ism", '[bookmark=$1]$1[/bookmark]', $text);
|
||||
$text = preg_replace("/#\^\[url\=(.*?)\](.*?)\[\/url\]/ism", '[bookmark=$1]$2[/bookmark]', $text);
|
||||
$text = preg_replace(
|
||||
"/#\[url\=.*?\]\^\[\/url\]\[url\=(.*?)\](.*?)\[\/url\]/i",
|
||||
"[bookmark=$1]$2[/bookmark]",
|
||||
$text
|
||||
);
|
||||
|
||||
if (in_array($simple_html, [self::OSTATUS, self::TWITTER, self::BLUESKY])) {
|
||||
$text = preg_replace_callback("/([^#@!])\[url\=([^\]]*)\](.*?)\[\/url\]/ism", [self::class, 'expandLinksCallback'], $text);
|
||||
//$text = preg_replace("/[^#@!]\[url\=([^\]]*)\](.*?)\[\/url\]/ism", ' $2 [url]$1[/url]', $text);
|
||||
$text = preg_replace("/\[bookmark\=([^\]]*)\](.*?)\[\/bookmark\]/ism", ' $2 [url]$1[/url]', $text);
|
||||
}
|
||||
|
||||
// Perform URL Search
|
||||
if ($try_oembed) {
|
||||
$text = preg_replace_callback("/\[bookmark\=([^\]]*)\](.*?)\[\/bookmark\]/ism", $try_oembed_callback, $text);
|
||||
}
|
||||
|
||||
$text = preg_replace("/\[bookmark\=([^\]]*)\](.*?)\[\/bookmark\]/ism", '[url=$1]$2[/url]', $text);
|
||||
|
||||
// Handle Diaspora posts
|
||||
$text = preg_replace_callback(
|
||||
"&\[url=/?posts/([^\[\]]*)\](.*)\[\/url\]&Usi",
|
||||
function ($match) {
|
||||
return "[url=" . DI::baseUrl() . "/display/" . $match[1] . "]" . $match[2] . "[/url]";
|
||||
},
|
||||
$text
|
||||
);
|
||||
|
||||
$text = preg_replace_callback(
|
||||
"&\[url=/people\?q\=(.*)\](.*)\[\/url\]&Usi",
|
||||
function ($match) {
|
||||
return "[url=" . DI::baseUrl() . "/search?search=%40" . $match[1] . "]" . $match[2] . "[/url]";
|
||||
},
|
||||
$text
|
||||
);
|
||||
|
||||
// Server independent link to posts and comments
|
||||
// See issue: https://github.com/diaspora/diaspora_federation/issues/75
|
||||
$expression = "=diaspora://.*?/post/([0-9A-Za-z\-_@.:]{15,254}[0-9A-Za-z])=ism";
|
||||
$text = preg_replace($expression, DI::baseUrl() . "/display/$1", $text);
|
||||
|
||||
/* Tag conversion
|
||||
* Supports:
|
||||
* - #[url=<anything>]<term>[/url]
|
||||
* - [url=<anything>]#<term>[/url]
|
||||
*/
|
||||
self::performWithEscapedTags($text, ['url', 'share'], function ($text) use ($simple_html) {
|
||||
$text = preg_replace_callback("/(?:#\[url\=[^\[\]]*\]|\[url\=[^\[\]]*\]#)(.*?)\[\/url\]/ism", function ($matches) use ($simple_html) {
|
||||
if ($simple_html == self::ACTIVITYPUB) {
|
||||
return '<a href="' . DI::baseUrl() . '/search?tag=' . urlencode($matches[1])
|
||||
. '" data-tag="' . XML::escape($matches[1]) . '" rel="tag ugc">#'
|
||||
. XML::escape($matches[1]) . '</a>';
|
||||
} else {
|
||||
return '#<a href="' . DI::baseUrl() . '/search?tag=' . urlencode($matches[1])
|
||||
. '" class="tag" rel="tag" title="' . XML::escape($matches[1]) . '">'
|
||||
. XML::escape($matches[1]) . '</a>';
|
||||
}
|
||||
}, $text);
|
||||
return $text;
|
||||
});
|
||||
|
||||
// Red compatibility, though the link can't be authenticated on Friendica
|
||||
$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, $simple_html);
|
||||
} else {
|
||||
$text = self::unifyLinks($text);
|
||||
}
|
||||
|
||||
// We need no target="_blank" rel="noopener noreferrer" for local links
|
||||
// convert links start with DI::baseUrl() as local link without the target="_blank" rel="noopener noreferrer" attribute
|
||||
$text = preg_replace("/\[url\=(" . preg_quote(DI::baseUrl(), '/') . ".*?)\](.*?)\[\/url\]/ism", '<a href="$1">$2</a>', $text);
|
||||
|
||||
$text = preg_replace("/\[url\=(.*?)\](.*?)\[\/url\]/ism", '<a href="$1" target="_blank" rel="noopener noreferrer">$2</a>', $text);
|
||||
|
||||
// we may need to restrict this further if it picks up too many strays
|
||||
// link acct:user@host to a webfinger profile redirector
|
||||
return preg_replace('/acct:([^@]+)@((?!\-)(?:[a-zA-Z\d\-]{0,62}[a-zA-Z\d]\.){1,126}(?!\d+)[a-zA-Z\d]{1,63})/', '<a href="' . DI::baseUrl() . '/acctlink?addr=$1@$2" target="extlink">acct:$1@$2</a>', $text);
|
||||
}
|
||||
|
||||
private static function escapeUrl(string $url): string
|
||||
{
|
||||
return self::escapeContent(self::idnUrl($url));
|
||||
}
|
||||
|
||||
private static function escapeContent(string $url): string
|
||||
{
|
||||
return str_replace(['[', ']'], ['[', ']'], $url);
|
||||
}
|
||||
|
||||
private static function idnUrl(string $url): string
|
||||
{
|
||||
$parts = parse_url($url);
|
||||
if (empty($parts['host'])) {
|
||||
return $url;
|
||||
}
|
||||
|
||||
$parts['host'] = idn_to_ascii(urldecode($parts['host']));
|
||||
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
|
||||
{
|
||||
return preg_replace_callback(
|
||||
"/\[url\](.*?)\[\/url\]/ism",
|
||||
function ($match) {
|
||||
return "[url=" . self::escapeUrl($match[1]) . "]" . $match[1] . "[/url]";
|
||||
},
|
||||
$text
|
||||
);
|
||||
}
|
||||
|
||||
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) 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) use ($max_length) {
|
||||
if ($match[1] == $match[2]) {
|
||||
return "[url=" . self::escapeUrl($match[1]) . "]" . Strings::getStyledURL($match[2], $max_length) . "[/url]";
|
||||
} else {
|
||||
return "[url=" . self::escapeUrl($match[1]) . "]" . $match[2] . "[/url]";
|
||||
}
|
||||
},
|
||||
$text
|
||||
);
|
||||
return $text;
|
||||
}
|
||||
|
||||
private static function convertMailToHtml(string $text): string
|
||||
{
|
||||
$text = preg_replace_callback("/\[(mail)\](.*?)\[\/mail\]/ism", [self::class, 'sanitizeLinksCallback'], $text);
|
||||
$text = preg_replace("/\[mail\](.*?)\[\/mail\]/", '<a href="mailto:$1">$1</a>', $text);
|
||||
$text = preg_replace("/\[mail\=(.*?)\](.*?)\[\/mail\]/", '<a href="mailto:$1">$2</a>', $text);
|
||||
return $text;
|
||||
}
|
||||
|
||||
private static function convertSharesToHtml(string $text, int $simple_html, bool $try_oembed, int $uriid): string
|
||||
{
|
||||
// Shared content
|
||||
// when the content is meant exporting to other systems then remove the avatar picture since this doesn't really look good on these systems
|
||||
if (!$try_oembed) {
|
||||
$text = preg_replace("/\[share(.*?)avatar\s?=\s?'.*?'\s?(.*?)\]\s?(.*?)\s?\[\/share\]\s?/ism", "\n[share$1$2]$3[/share]", $text);
|
||||
}
|
||||
|
||||
$text = self::convertShare(
|
||||
$text,
|
||||
function (array $attributes, array $author_contact, $content, $is_quote_share) use ($simple_html) {
|
||||
return self::convertShareCallback($attributes, $author_contact, $content, $is_quote_share, $simple_html);
|
||||
},
|
||||
$uriid
|
||||
);
|
||||
|
||||
return $text;
|
||||
}
|
||||
|
||||
private static function cleanupHtml(string $text): string
|
||||
{
|
||||
/// @todo What is the meaning of these lines?
|
||||
$text = preg_replace('/\[\&\;([#a-z0-9]+)\;\]/', '&$1;', $text);
|
||||
$text = preg_replace('/\&\#039\;/', '\'', $text);
|
||||
|
||||
// Currently deactivated, it made problems with " inside of alt texts.
|
||||
//$text = preg_replace('/\"\;/', '"', $text);
|
||||
|
||||
// fix any escaped ampersands that may have been converted into links
|
||||
$text = preg_replace('/\<([^>]*?)(src|href)=(.*?)\&\;(.*?)\>/ism', '<$1$2=$3&$4>', $text);
|
||||
|
||||
// sanitizes src attributes (http and redir URLs for displaying in a web page, cid used for inline images in emails)
|
||||
$allowed_src_protocols = ['//', 'http://', 'https://', 'contact/redir/', 'cid:'];
|
||||
|
||||
array_walk($allowed_src_protocols, function (&$value) {
|
||||
$value = preg_quote($value, '#');
|
||||
});
|
||||
|
||||
$text = preg_replace(
|
||||
'#<([^>]*?)(src)="(?!' . implode('|', $allowed_src_protocols) . ')(.*?)"(.*?)>#ism',
|
||||
'<$1$2=""$4 data-original-src="$3" class="invalid-src" title="' . DI::l10n()->t('Invalid source protocol') . '">',
|
||||
$text
|
||||
);
|
||||
|
||||
// sanitize href attributes (only allowlisted protocols URLs)
|
||||
// default value for backward compatibility
|
||||
$allowed_link_protocols = DI::config()->get('system', 'allowed_link_protocols', []);
|
||||
|
||||
// Always allowed protocol even if config isn't set or not including it
|
||||
$allowed_link_protocols[] = '//';
|
||||
$allowed_link_protocols[] = 'http://';
|
||||
$allowed_link_protocols[] = 'https://';
|
||||
$allowed_link_protocols[] = 'contact/redir/';
|
||||
|
||||
array_walk($allowed_link_protocols, function (&$value) {
|
||||
$value = preg_quote($value, '#');
|
||||
});
|
||||
|
||||
$regex = '#<([^>]*?)(href)="(?!' . implode('|', $allowed_link_protocols) . ')(.*?)"(.*?)>#ism';
|
||||
$text = preg_replace($regex, '<$1$2="javascript:void(0)"$4 data-original-href="$3" class="invalid-href" title="' . DI::l10n()->t('Invalid link protocol') . '">', $text);
|
||||
|
||||
return $text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Strips the "abstract" tag from the provided text
|
||||
*
|
||||
|
|
@ -2520,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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -29,9 +29,9 @@ use Friendica\Core\Renderer;
|
|||
use Friendica\Core\Search;
|
||||
use Friendica\DI;
|
||||
use Friendica\Model\Contact;
|
||||
use Friendica\Util\Network;
|
||||
use Friendica\Util\Strings;
|
||||
use Friendica\Util\XML;
|
||||
use GuzzleHttp\Psr7\Uri;
|
||||
use League\HTMLToMarkdown\HtmlConverter;
|
||||
use Psr\Http\Message\UriInterface;
|
||||
|
||||
|
|
@ -253,13 +253,14 @@ class HTML
|
|||
self::tagToBBCode($doc, 'span', ['class' => 'type-link'], '[class=type-link]', '[/class]');
|
||||
self::tagToBBCode($doc, 'span', ['class' => 'type-video'], '[class=type-video]', '[/class]');
|
||||
|
||||
self::tagToBBCode($doc, 'strong', [], '[b]', '[/b]');
|
||||
self::tagToBBCode($doc, 'em', [], '[i]', '[/i]');
|
||||
self::tagToBBCode($doc, 'b', [], '[b]', '[/b]');
|
||||
self::tagToBBCode($doc, 'i', [], '[i]', '[/i]');
|
||||
self::tagToBBCode($doc, 'u', [], '[u]', '[/u]');
|
||||
self::tagToBBCode($doc, 's', [], '[s]', '[/s]');
|
||||
self::tagToBBCode($doc, 'del', [], '[s]', '[/s]');
|
||||
$elements = [
|
||||
'b', 'del', 'em', 'i', 'ins', 'kbd', 'mark',
|
||||
's', 'samp', 'strong', 'sub', 'sup', 'u', 'var'
|
||||
];
|
||||
foreach ($elements as $element) {
|
||||
self::tagToBBCode($doc, $element, [], '[' . $element . ']', '[/' . $element . ']');
|
||||
}
|
||||
|
||||
self::tagToBBCode($doc, 'strike', [], '[s]', '[/s]');
|
||||
|
||||
self::tagToBBCode($doc, 'big', [], "[size=large]", "[/size]");
|
||||
|
|
@ -406,7 +407,7 @@ class HTML
|
|||
}
|
||||
|
||||
$parts = array_merge($base, parse_url($url));
|
||||
$url2 = Network::unparseURL($parts);
|
||||
$url2 = (string)Uri::fromParts((array)$parts);
|
||||
|
||||
return str_replace($url, $url2, $link);
|
||||
}
|
||||
|
|
@ -867,7 +868,7 @@ class HTML
|
|||
'$save_label' => $save_label,
|
||||
'$search_hint' => DI::l10n()->t('@name, !group, #tags, content'),
|
||||
'$mode' => $mode,
|
||||
'$return_url' => urlencode(Search::getSearchPath($s)),
|
||||
'$return_url' => bin2hex(Search::getSearchPath($s)),
|
||||
];
|
||||
|
||||
if (!$aside) {
|
||||
|
|
@ -1060,4 +1061,15 @@ class HTML
|
|||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a document contains HTML or entities
|
||||
*
|
||||
* @param string $text
|
||||
* @return boolean
|
||||
*/
|
||||
public static function isHTML(string $text): bool
|
||||
{
|
||||
return ($text != html_entity_decode($text)) || ($text != strip_tags($text));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ use Friendica\Model\Contact;
|
|||
use Friendica\Model\Circle;
|
||||
use Friendica\Model\Item;
|
||||
use Friendica\Model\Post;
|
||||
use Friendica\Model\Profile;
|
||||
use Friendica\Security\OpenWebAuth;
|
||||
use Friendica\Util\DateTimeFormat;
|
||||
use Friendica\Util\Temporal;
|
||||
|
||||
|
|
@ -67,7 +67,7 @@ class Widget
|
|||
|
||||
if (DI::config()->get('system', 'invitation_only')) {
|
||||
$x = intval(DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'system', 'invites_remaining'));
|
||||
if ($x || DI::app()->isSiteAdmin()) {
|
||||
if ($x || DI::userSession()->isSiteAdmin()) {
|
||||
DI::page()['aside'] .= '<div class="side-link widget" id="side-invite-remain">'
|
||||
. DI::l10n()->tt('%d invitation available', '%d invitations available', $x)
|
||||
. '</div>';
|
||||
|
|
@ -85,7 +85,7 @@ class Widget
|
|||
$nv['random'] = DI::l10n()->t('Random Profile');
|
||||
$nv['inv'] = DI::l10n()->t('Invite Friends');
|
||||
$nv['directory'] = DI::l10n()->t('Global Directory');
|
||||
$nv['global_dir'] = Profile::zrl($global_dir, true);
|
||||
$nv['global_dir'] = OpenWebAuth::getZrlUrl($global_dir, true);
|
||||
$nv['local_directory'] = DI::l10n()->t('Local Directory');
|
||||
|
||||
$aside = [];
|
||||
|
|
@ -336,7 +336,7 @@ class Widget
|
|||
*/
|
||||
public static function categories(int $uid, string $baseurl, string $selected = ''): string
|
||||
{
|
||||
if (!Feature::isEnabled($uid, 'categories')) {
|
||||
if (!Feature::isEnabled($uid, Feature::CATEGORIES)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
|
|
@ -398,7 +398,7 @@ class Widget
|
|||
$entries[] = [
|
||||
'url' => Contact::magicLinkByContact($contact),
|
||||
'name' => $contact['name'],
|
||||
'photo' => Contact::getThumb($contact),
|
||||
'photo' => Contact::getThumb($contact, true),
|
||||
];
|
||||
}
|
||||
|
||||
|
|
@ -428,7 +428,7 @@ class Widget
|
|||
return '';
|
||||
}
|
||||
|
||||
if (Feature::isEnabled($uid, 'tagadelic')) {
|
||||
if (Feature::isEnabled($uid, Feature::TAGCLOUD)) {
|
||||
$owner_id = Contact::getPublicIdByUserId($uid);
|
||||
|
||||
if (!$owner_id) {
|
||||
|
|
@ -598,4 +598,4 @@ class Widget
|
|||
$channelname
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -61,7 +61,7 @@ class SavedSearches
|
|||
'$add' => '',
|
||||
'$searchbox' => '',
|
||||
'$saved' => $saved,
|
||||
'$return_url' => urlencode($return_url),
|
||||
'$return_url' => bin2hex($return_url),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@
|
|||
|
||||
namespace Friendica\Content\Widget;
|
||||
|
||||
use Friendica\Content\Conversation\Entity\Community;
|
||||
use Friendica\Core\Renderer;
|
||||
use Friendica\DI;
|
||||
use Friendica\Model\Tag;
|
||||
|
|
@ -39,9 +40,9 @@ class TrendingTags
|
|||
* @return string Formatted HTML code
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function getHTML(string $content = 'global', int $period = 24): string
|
||||
public static function getHTML(string $content = Community::GLOBAL, int $period = 24): string
|
||||
{
|
||||
if ($content == 'local') {
|
||||
if ($content == Community::LOCAL) {
|
||||
$tags = Tag::getLocalTrendingHashtags($period, 20);
|
||||
} else {
|
||||
$tags = Tag::getGlobalTrendingHashtags($period, 20);
|
||||
|
|
|
|||
|
|
@ -43,9 +43,10 @@ class VCard
|
|||
* @template widget/vcard.tpl
|
||||
* @param array $contact
|
||||
* @param bool $hide_mention
|
||||
* @param bool $hide_follow
|
||||
* @return string
|
||||
*/
|
||||
public static function getHTML(array $contact, bool $hide_mention = false): string
|
||||
public static function getHTML(array $contact, bool $hide_mention = false, bool $hide_follow = false): string
|
||||
{
|
||||
if (!isset($contact['network']) || !isset($contact['id'])) {
|
||||
Logger::warning('Incomplete contact', ['contact' => $contact ?? []]);
|
||||
|
|
@ -87,7 +88,7 @@ class VCard
|
|||
}
|
||||
}
|
||||
|
||||
if (empty($contact['self']) && Protocol::supportsFollow($contact['network'])) {
|
||||
if (!$hide_follow && empty($contact['self']) && Protocol::supportsFollow($contact['network'])) {
|
||||
if (in_array($rel, [Contact::SHARING, Contact::FRIEND])) {
|
||||
$unfollow_link = 'contact/unfollow?url=' . urlencode($contact_url) . '&auto=1';
|
||||
} elseif (!$pending) {
|
||||
|
|
|
|||
|
|
@ -140,7 +140,8 @@ class Addon
|
|||
$func();
|
||||
}
|
||||
|
||||
Hook::delete(['file' => 'addon/' . $addon . '/' . $addon . '.php']);
|
||||
// Handles both relative and absolute file paths
|
||||
Hook::delete(['`file` LIKE ?', "%addon/$addon/$addon.php"]);
|
||||
|
||||
unset(self::$addons[array_search($addon, self::$addons)]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -495,6 +495,13 @@ class Installer
|
|||
);
|
||||
$returnVal = $returnVal ? $status : false;
|
||||
|
||||
$status = $this->checkFunction('idn_to_ascii',
|
||||
DI::l10n()->t('IDN Functions PHP module'),
|
||||
DI::l10n()->t('Error: IDN Functions PHP module required but not installed.'),
|
||||
true
|
||||
);
|
||||
$returnVal = $returnVal ? $status : false;
|
||||
|
||||
return $returnVal;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -75,8 +75,11 @@ class Renderer
|
|||
{
|
||||
DI::profiler()->startRecording('rendering');
|
||||
|
||||
// pass $baseurl to all templates if it isn't set
|
||||
$vars = array_merge(['$baseurl' => DI::baseUrl(), '$APP' => DI::app()], $vars);
|
||||
// Default template variables
|
||||
$vars = array_merge([
|
||||
'$baseurl' => DI::baseUrl(),
|
||||
'$VERSION' => \Friendica\App::VERSION,
|
||||
], $vars);
|
||||
|
||||
$t = self::getTemplateEngine();
|
||||
|
||||
|
|
@ -84,7 +87,7 @@ class Renderer
|
|||
$output = $t->replaceMacros($template, $vars);
|
||||
} catch (Exception $e) {
|
||||
DI::logger()->critical($e->getMessage(), ['template' => $template, 'vars' => $vars]);
|
||||
$message = DI::app()->isSiteAdmin() ?
|
||||
$message = DI::userSession()->isSiteAdmin() ?
|
||||
$e->getMessage() :
|
||||
DI::l10n()->t('Friendica can\'t display this page at the moment, please contact the administrator.');
|
||||
throw new ServiceUnavailableException($message);
|
||||
|
|
@ -113,7 +116,7 @@ class Renderer
|
|||
$template = $t->getTemplateFile($file, $subDir);
|
||||
} catch (Exception $e) {
|
||||
DI::logger()->critical($e->getMessage(), ['file' => $file, 'subDir' => $subDir]);
|
||||
$message = DI::app()->isSiteAdmin() ?
|
||||
$message = DI::userSession()->isSiteAdmin() ?
|
||||
$e->getMessage() :
|
||||
DI::l10n()->t('Friendica can\'t display this page at the moment, please contact the administrator.');
|
||||
throw new ServiceUnavailableException($message);
|
||||
|
|
@ -142,7 +145,7 @@ class Renderer
|
|||
} else {
|
||||
$admin_message = DI::l10n()->t('template engine cannot be registered without a name.');
|
||||
DI::logger()->critical($admin_message, ['class' => $class]);
|
||||
$message = DI::app()->isSiteAdmin() ?
|
||||
$message = DI::userSession()->isSiteAdmin() ?
|
||||
$admin_message :
|
||||
DI::l10n()->t('Friendica can\'t display this page at the moment, please contact the administrator.');
|
||||
throw new ServiceUnavailableException($message);
|
||||
|
|
@ -176,7 +179,7 @@ class Renderer
|
|||
|
||||
$admin_message = DI::l10n()->t('template engine is not registered!');
|
||||
DI::logger()->critical($admin_message, ['template_engine' => $template_engine]);
|
||||
$message = DI::app()->isSiteAdmin() ?
|
||||
$message = DI::userSession()->isSiteAdmin() ?
|
||||
$admin_message :
|
||||
DI::l10n()->t('Friendica can\'t display this page at the moment, please contact the administrator.');
|
||||
throw new ServiceUnavailableException($message);
|
||||
|
|
|
|||
|
|
@ -24,6 +24,8 @@ namespace Friendica\Core;
|
|||
use Friendica\DI;
|
||||
use Friendica\Model\Contact;
|
||||
use Friendica\Network\HTTPClient\Client\HttpClientAccept;
|
||||
use Friendica\Network\HTTPClient\Client\HttpClientOptions;
|
||||
use Friendica\Network\HTTPClient\Client\HttpClientRequest;
|
||||
use Friendica\Network\HTTPException;
|
||||
use Friendica\Object\Search\ContactResult;
|
||||
use Friendica\Object\Search\ResultList;
|
||||
|
|
@ -125,7 +127,7 @@ class Search
|
|||
$searchUrl .= '&page=' . $page;
|
||||
}
|
||||
|
||||
$resultJson = DI::httpClient()->fetch($searchUrl, HttpClientAccept::JSON);
|
||||
$resultJson = DI::httpClient()->fetch($searchUrl, HttpClientAccept::JSON, 0, '', HttpClientRequest::CONTACTDISCOVER);
|
||||
|
||||
$results = json_decode($resultJson, true);
|
||||
|
||||
|
|
@ -232,7 +234,7 @@ class Search
|
|||
$return = Contact::searchByName($search, $mode, true);
|
||||
} else {
|
||||
$p = $page > 1 ? 'p=' . $page : '';
|
||||
$curlResult = DI::httpClient()->get(self::getGlobalDirectory() . '/search/people?' . $p . '&q=' . urlencode($search), HttpClientAccept::JSON);
|
||||
$curlResult = DI::httpClient()->get(self::getGlobalDirectory() . '/search/people?' . $p . '&q=' . urlencode($search), HttpClientAccept::JSON, [HttpClientOptions::REQUEST => HttpClientRequest::CONTACTDISCOVER]);
|
||||
if ($curlResult->isSuccess()) {
|
||||
$searchResult = json_decode($curlResult->getBodyString(), true);
|
||||
if (!empty($searchResult['profiles'])) {
|
||||
|
|
|
|||
|
|
@ -80,9 +80,9 @@ interface IHandleUserSessions extends IHandleSessions
|
|||
public function getMyUrl(): string;
|
||||
|
||||
/**
|
||||
* Returns if the current visitor is authenticated
|
||||
* Returns if the current visitor is a local user
|
||||
*
|
||||
* @return bool "true" when visitor is either a local or remote user
|
||||
* @return bool "true" when visitor is a local user
|
||||
*/
|
||||
public function isAuthenticated(): bool;
|
||||
|
||||
|
|
@ -100,6 +100,20 @@ interface IHandleUserSessions extends IHandleSessions
|
|||
*/
|
||||
public function isModerator(): bool;
|
||||
|
||||
/**
|
||||
* Returns if the current visitor is a verified remote user
|
||||
*
|
||||
* @return bool "true" when visitor is a verified remote user
|
||||
*/
|
||||
public function isVisitor(): bool;
|
||||
|
||||
/**
|
||||
* Returns if the current visitor is an unauthenticated user
|
||||
*
|
||||
* @return bool "true" when visitor is an unauthenticated user
|
||||
*/
|
||||
public function isUnauthenticated(): bool;
|
||||
|
||||
/**
|
||||
* Returns User ID of the managed user in case it's a different identity
|
||||
*
|
||||
|
|
|
|||
|
|
@ -130,7 +130,7 @@ class UserSession implements IHandleUserSessions
|
|||
/** {@inheritDoc} */
|
||||
public function isAuthenticated(): bool
|
||||
{
|
||||
return $this->session->get('authenticated', false);
|
||||
return $this->session->get('authenticated', false) && $this->getLocalUserId();
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
|
|
@ -145,6 +145,18 @@ class UserSession implements IHandleUserSessions
|
|||
return User::isModerator($this->getLocalUserId());
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
public function isVisitor(): bool
|
||||
{
|
||||
return $this->session->get('authenticated', false) && $this->session->get('visitor_id') && !$this->session->get('uid');
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
public function isUnauthenticated(): bool
|
||||
{
|
||||
return !$this->session->get('authenticated', false);
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
public function setVisitorsContacts(string $my_url)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@
|
|||
|
||||
namespace Friendica\Core;
|
||||
|
||||
use Friendica\Core\Cache\Enum\Duration;
|
||||
use Friendica\Core\Worker\Entity\Process;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\DI;
|
||||
|
|
@ -54,7 +55,8 @@ class Worker
|
|||
const FAST_COMMANDS = ['APDelivery', 'Delivery'];
|
||||
|
||||
const LOCK_PROCESS = 'worker_process';
|
||||
const LOCK_WORKER = 'worker';
|
||||
const LOCK_WORKER = 'worker';
|
||||
const LAST_CHECK = 'worker::check';
|
||||
|
||||
private static $up_start;
|
||||
private static $db_duration = 0;
|
||||
|
|
@ -233,7 +235,7 @@ class Worker
|
|||
* @return integer Number of deferred entries in the worker queue
|
||||
* @throws \Exception
|
||||
*/
|
||||
private static function deferredEntries(): int
|
||||
public static function deferredEntries(): int
|
||||
{
|
||||
$stamp = (float)microtime(true);
|
||||
$count = DBA::count('workerqueue', ["NOT `done` AND `pid` = 0 AND `retrial` > ?", 0]);
|
||||
|
|
@ -248,7 +250,7 @@ class Worker
|
|||
* @return integer Number of non executed entries in the worker queue
|
||||
* @throws \Exception
|
||||
*/
|
||||
private static function totalEntries(): int
|
||||
public static function totalEntries(): int
|
||||
{
|
||||
$stamp = (float)microtime(true);
|
||||
$count = DBA::count('workerqueue', ['done' => false, 'pid' => 0]);
|
||||
|
|
@ -832,6 +834,17 @@ class Worker
|
|||
} else {
|
||||
self::spawnWorker();
|
||||
}
|
||||
} elseif (($active > $queues) && ($active < $maxqueues) && ($load < $maxsysload)) {
|
||||
$max_idletime = DI::config()->get('system', 'worker_max_idletime');
|
||||
$last_check = DI::cache()->get(self::LAST_CHECK);
|
||||
$last_date = $last_check ? date('c', $last_check) : '';
|
||||
if (($max_idletime > 0) && (time() > $last_check + $max_idletime) && !DBA::exists('workerqueue', ["`done` AND `executed` > ?", DateTimeFormat::utc('now - ' . $max_idletime . ' second')])) {
|
||||
DI::cache()->set(self::LAST_CHECK, time(), Duration::HOUR);
|
||||
Logger::info('The last worker execution had been too long ago.', ['last' => $last_check, 'last-check' => $last_date, 'seconds' => $max_idletime, 'load' => $load, 'max_load' => $maxsysload, 'active_worker' => $active, 'max_worker' => $maxqueues]);
|
||||
return false;
|
||||
} elseif ($max_idletime > 0) {
|
||||
Logger::debug('Maximum idletime not reached.', ['last' => $last_check, 'last-check' => $last_date, 'seconds' => $max_idletime, 'load' => $load, 'max_load' => $maxsysload, 'active_worker' => $active, 'max_worker' => $maxqueues]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -849,7 +862,7 @@ class Worker
|
|||
* @return integer Number of active worker processes
|
||||
* @throws \Exception
|
||||
*/
|
||||
private static function activeWorkers(): int
|
||||
public static function activeWorkers(): int
|
||||
{
|
||||
$stamp = (float)microtime(true);
|
||||
$count = DI::process()->countCommand('Worker.php');
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -96,7 +96,6 @@ class Activities extends BaseFactory
|
|||
break;
|
||||
|
||||
default:
|
||||
$this->logger->warning('Unsupported verb in parent item:', ['parent_item' => $parent_item]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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:';
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@
|
|||
namespace Friendica\Factory\Api\Mastodon;
|
||||
|
||||
use Friendica\BaseFactory;
|
||||
use Friendica\Content\Conversation\Entity\Timeline;
|
||||
use Friendica\Database\Database;
|
||||
use Friendica\Network\HTTPException\InternalServerErrorException;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
|
@ -45,4 +46,14 @@ class ListEntity extends BaseFactory
|
|||
$circle = $this->dba->selectFirst('group', ['name'], ['id' => $id, 'deleted' => false]);
|
||||
return new \Friendica\Object\Api\Mastodon\ListEntity($id, $circle['name'] ?? '', 'list');
|
||||
}
|
||||
|
||||
public function createFromChannel(Timeline $channel): \Friendica\Object\Api\Mastodon\ListEntity
|
||||
{
|
||||
return new \Friendica\Object\Api\Mastodon\ListEntity('channel:' . $channel->code, $channel->label, 'followed');
|
||||
}
|
||||
|
||||
public function createFromGroup(array $group): \Friendica\Object\Api\Mastodon\ListEntity
|
||||
{
|
||||
return new \Friendica\Object\Api\Mastodon\ListEntity('group:' . $group['id'], $group['name'], 'followed');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -84,13 +84,9 @@ class APContact
|
|||
|
||||
if (!empty($link['template']) && ($link['rel'] == ActivityNamespace::OSTATUSSUB)) {
|
||||
$data['subscribe'] = $link['template'];
|
||||
}
|
||||
|
||||
if (!empty($link['href']) && !empty($link['type']) && ($link['rel'] == 'self') && ($link['type'] == 'application/activity+json')) {
|
||||
} elseif (!empty($link['href']) && !empty($link['type']) && ($link['rel'] == 'self') && ($link['type'] == 'application/activity+json')) {
|
||||
$data['url'] = $link['href'];
|
||||
}
|
||||
|
||||
if (!empty($link['href']) && !empty($link['type']) && ($link['rel'] == 'http://webfinger.net/rel/profile-page') && ($link['type'] == 'text/html')) {
|
||||
} elseif (!empty($link['href']) && !empty($link['type']) && ($link['rel'] == ActivityNamespace::WEBFINGERPROFILE) && ($link['type'] == 'text/html')) {
|
||||
$data['alias'] = $link['href'];
|
||||
}
|
||||
}
|
||||
|
|
@ -105,14 +101,12 @@ class APContact
|
|||
/**
|
||||
* Fetches a profile from a given url
|
||||
*
|
||||
* @param string $url profile url
|
||||
* @param boolean $update true = always update, false = never update, null = update when not found or outdated
|
||||
* @param string $url profile url
|
||||
* @param ?boolean $update true = always update, false = never update, null = update when not found or outdated
|
||||
* @return array profile array
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
* @throws \ImagickException
|
||||
* @todo Rewrite parameter $update to avoid true|false|null (boolean is binary, null adds a third case)
|
||||
*/
|
||||
public static function getByURL(string $url, $update = null): array
|
||||
public static function getByURL(string $url, bool $update = null): array
|
||||
{
|
||||
if (empty($url) || Network::isUrlBlocked($url)) {
|
||||
Logger::info('Domain is blocked', ['url' => $url]);
|
||||
|
|
@ -184,7 +178,7 @@ class APContact
|
|||
DI::cache()->set($cachekey, System::callstack(20), Duration::FIVE_MINUTES);
|
||||
}
|
||||
|
||||
if (Network::isLocalLink($url) && ($local_uid = User::getIdForURL($url))) {
|
||||
if (DI::baseUrl()->isLocalUrl($url) && ($local_uid = User::getIdForURL($url))) {
|
||||
try {
|
||||
$data = Transmitter::getProfile($local_uid);
|
||||
$local_owner = User::getOwnerDataById($local_uid);
|
||||
|
|
@ -201,7 +195,7 @@ class APContact
|
|||
$failed = empty($curlResult) || empty($curlResult->getBodyString()) ||
|
||||
(!$curlResult->isSuccess() && ($curlResult->getReturnCode() != 410));
|
||||
|
||||
if (!$failed) {
|
||||
if (!$failed) {
|
||||
$data = json_decode($curlResult->getBodyString(), true);
|
||||
$failed = empty($data) || !is_array($data);
|
||||
}
|
||||
|
|
@ -293,6 +287,7 @@ class APContact
|
|||
} elseif ($apcontact['type'] == 'Tombstone') {
|
||||
// The "inbox" field must have a content
|
||||
$apcontact['inbox'] = '';
|
||||
$apcontact['addr'] = '';
|
||||
}
|
||||
|
||||
// Quit if this doesn't seem to be an account at all
|
||||
|
|
@ -300,7 +295,7 @@ class APContact
|
|||
return $fetched_contact;
|
||||
}
|
||||
|
||||
if (empty($apcontact['addr'])) {
|
||||
if (empty($apcontact['addr']) && ($apcontact['type'] != 'Tombstone')) {
|
||||
try {
|
||||
$apcontact['addr'] = $apcontact['nick'] . '@' . (new Uri($apcontact['url']))->getAuthority();
|
||||
} catch (\Throwable $e) {
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ use Friendica\Object\Image;
|
|||
use Friendica\Util\DateTimeFormat;
|
||||
use Friendica\Util\Mimetype;
|
||||
use Friendica\Security\Security;
|
||||
use Friendica\Util\Network;
|
||||
|
||||
/**
|
||||
* Class to handle attach database table
|
||||
|
|
@ -109,16 +110,17 @@ class Attach
|
|||
/**
|
||||
* Retrieve a single record given the ID
|
||||
*
|
||||
* @param int $id Row id of the record
|
||||
* @param int $id Row id of the record
|
||||
* @param int $uid User-Id
|
||||
*
|
||||
* @return bool|array
|
||||
*
|
||||
* @throws \Exception
|
||||
* @see \Friendica\Database\DBA::select
|
||||
*/
|
||||
public static function getById(int $id)
|
||||
public static function getById(int $id, int $uid)
|
||||
{
|
||||
return self::selectFirst([], ['id' => $id]);
|
||||
return self::selectFirst([], ['id' => $id, 'uid' => $uid]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -197,7 +199,7 @@ class Attach
|
|||
* @return boolean|integer Row id on success, False on errors
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
*/
|
||||
public static function store(string $data, int $uid, string $filename, string $filetype = '' , int $filesize = null, string $allow_cid = '', string $allow_gid = '', string $deny_cid = '', string $deny_gid = '')
|
||||
public static function store(string $data, int $uid, string $filename, string $filetype = '', int $filesize = null, string $allow_cid = '', string $allow_gid = '', string $deny_cid = '', string $deny_gid = '')
|
||||
{
|
||||
if ($filetype === '') {
|
||||
$filetype = Mimetype::getContentType($filename);
|
||||
|
|
@ -243,6 +245,7 @@ class Attach
|
|||
* @param string $src Source file name
|
||||
* @param int $uid User id
|
||||
* @param string $filename Optional file name
|
||||
* @param string $filetype Optional file type
|
||||
* @param string $allow_cid
|
||||
* @param string $allow_gid
|
||||
* @param string $deny_cid
|
||||
|
|
@ -250,7 +253,7 @@ class Attach
|
|||
* @return boolean|int Insert id or false on failure
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
*/
|
||||
public static function storeFile(string $src, int $uid, string $filename = '', string $allow_cid = '', string $allow_gid = '', string $deny_cid = '', string $deny_gid = '')
|
||||
public static function storeFile(string $src, int $uid, string $filename = '', string $filetype = '', string $allow_cid = '', string $allow_gid = '', string $deny_cid = '', string $deny_gid = '')
|
||||
{
|
||||
if ($filename === '') {
|
||||
$filename = basename($src);
|
||||
|
|
@ -258,7 +261,7 @@ class Attach
|
|||
|
||||
$data = @file_get_contents($src);
|
||||
|
||||
return self::store($data, $uid, $filename, '', null, $allow_cid, $allow_gid, $deny_cid, $deny_gid);
|
||||
return self::store($data, $uid, $filename, $filetype, null, $allow_cid, $allow_gid, $deny_cid, $deny_gid);
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -279,9 +282,9 @@ class Attach
|
|||
{
|
||||
if (!is_null($img)) {
|
||||
// get items to update
|
||||
$items = self::selectToArray(['backend-class','backend-ref'], $conditions);
|
||||
$items = self::selectToArray(['backend-class', 'backend-ref'], $conditions);
|
||||
|
||||
foreach($items as $item) {
|
||||
foreach ($items as $item) {
|
||||
try {
|
||||
$backend_class = DI::storageManager()->getWritableStorageByName($item['backend-class'] ?? '');
|
||||
$fields['backend-ref'] = $backend_class->put($img->asString(), $item['backend-ref'] ?? '');
|
||||
|
|
@ -313,9 +316,9 @@ class Attach
|
|||
public static function delete(array $conditions, array $options = []): bool
|
||||
{
|
||||
// get items to delete data info
|
||||
$items = self::selectToArray(['backend-class','backend-ref'], $conditions);
|
||||
$items = self::selectToArray(['backend-class', 'backend-ref'], $conditions);
|
||||
|
||||
foreach($items as $item) {
|
||||
foreach ($items as $item) {
|
||||
try {
|
||||
$backend_class = DI::storageManager()->getWritableStorageByName($item['backend-class'] ?? '');
|
||||
$backend_class->delete($item['backend-ref'] ?? '');
|
||||
|
|
@ -328,4 +331,51 @@ class Attach
|
|||
|
||||
return DBA::delete('attach', $conditions, $options);
|
||||
}
|
||||
|
||||
public static function setPermissionFromBody(array $post)
|
||||
{
|
||||
preg_match_all("/\[attachment\](.*?)\[\/attachment\]/ism", $post['body'], $matches, PREG_SET_ORDER);
|
||||
foreach ($matches as $attachment) {
|
||||
if (DI::baseUrl()->isLocalUrl($attachment[1]) && preg_match('|.*?/attach/(\d+)|', $attachment[1], $match)) {
|
||||
$fields = [
|
||||
'allow_cid' => $post['allow_cid'], 'allow_gid' => $post['allow_gid'],
|
||||
'deny_cid' => $post['deny_cid'], 'deny_gid' => $post['deny_gid']
|
||||
];
|
||||
self::update($fields, ['id' => $match[1], 'uid' => $post['uid']]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function setPermissionForId(int $id, int $uid, string $str_contact_allow, string $str_circle_allow, string $str_contact_deny, string $str_circle_deny)
|
||||
{
|
||||
$fields = [
|
||||
'allow_cid' => $str_contact_allow, 'allow_gid' => $str_circle_allow,
|
||||
'deny_cid' => $str_contact_deny, 'deny_gid' => $str_circle_deny,
|
||||
];
|
||||
|
||||
self::update($fields, ['id' => $id, 'uid' => $uid]);
|
||||
}
|
||||
|
||||
public static function addAttachmentToBody(string $body, int $uid): string
|
||||
{
|
||||
preg_match_all("/\[attachment\](.*?)\[\/attachment\]/ism", $body, $matches, PREG_SET_ORDER);
|
||||
foreach ($matches as $attachment) {
|
||||
if (DI::baseUrl()->isLocalUrl($attachment[1]) && preg_match('|.*?/attach/(\d+)|', $attachment[1], $match)) {
|
||||
$attach = self::getById($match[1], $uid);
|
||||
if (empty($attach)) {
|
||||
return $body;
|
||||
}
|
||||
$media = [
|
||||
'type' => Post\Media::DOCUMENT,
|
||||
'url' => $attachment[1],
|
||||
'size' => $attach['filesize'],
|
||||
'mimetype' => $attach['filetype'],
|
||||
'description' => $attach['filename']
|
||||
];
|
||||
$media = Post\Media::addType($media);
|
||||
$body = str_replace($attachment[0], Post\Media::addAttachmentToBody($media, ''), $body);
|
||||
}
|
||||
}
|
||||
return $body;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -113,15 +113,6 @@ class Contact
|
|||
* @}
|
||||
*/
|
||||
|
||||
/** @deprecated Use Entity\LocalRelationship::MIRROR_DEACTIVATED instead */
|
||||
const MIRROR_DEACTIVATED = LocalRelationship::MIRROR_DEACTIVATED;
|
||||
/** @deprecated Now does the same as MIRROR_OWN_POST */
|
||||
const MIRROR_FORWARDED = 1;
|
||||
/** @deprecated Use Entity\LocalRelationship::MIRROR_OWN_POST instead */
|
||||
const MIRROR_OWN_POST = LocalRelationship::MIRROR_OWN_POST;
|
||||
/** @deprecated Use Entity\LocalRelationship::MIRROR_NATIVE_RESHARE instead */
|
||||
const MIRROR_NATIVE_RESHARE = LocalRelationship::MIRROR_NATIVE_RESHARE;
|
||||
|
||||
/**
|
||||
* @param array $fields Array of selected fields, empty for all
|
||||
* @param array $condition Array of fields for condition
|
||||
|
|
@ -453,12 +444,12 @@ class Contact
|
|||
return false;
|
||||
}
|
||||
|
||||
$cdata = self::getPublicAndUserContactID($cid, $uid);
|
||||
if (empty($cdata['user'])) {
|
||||
$ucid = self::getUserContactId($cid, $uid);
|
||||
if (!$ucid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$condition = ['id' => $cdata['user'], 'rel' => [self::FOLLOWER, self::FRIEND]];
|
||||
$condition = ['id' => $ucid, 'rel' => [self::FOLLOWER, self::FRIEND]];
|
||||
if ($strict) {
|
||||
$condition = array_merge($condition, ['pending' => false, 'readonly' => false, 'blocked' => false]);
|
||||
}
|
||||
|
|
@ -504,12 +495,12 @@ class Contact
|
|||
return false;
|
||||
}
|
||||
|
||||
$cdata = self::getPublicAndUserContactID($cid, $uid);
|
||||
if (empty($cdata['user'])) {
|
||||
$ucid = self::getUserContactId($cid, $uid);
|
||||
if (!$ucid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$condition = ['id' => $cdata['user'], 'rel' => [self::SHARING, self::FRIEND]];
|
||||
$condition = ['id' => $ucid, 'rel' => [self::SHARING, self::FRIEND]];
|
||||
if ($strict) {
|
||||
$condition = array_merge($condition, ['pending' => false, 'readonly' => false, 'blocked' => false]);
|
||||
}
|
||||
|
|
@ -635,11 +626,8 @@ class Contact
|
|||
*/
|
||||
public static function getPublicIdByUserId(int $uid)
|
||||
{
|
||||
$self = DBA::selectFirst('contact', ['url'], ['self' => true, 'uid' => $uid]);
|
||||
if (!DBA::isResult($self)) {
|
||||
return false;
|
||||
}
|
||||
return self::getIdForURL($self['url']);
|
||||
$self = self::selectFirstAccountUser(['pid'], ['self' => true, 'uid' => $uid]);
|
||||
return $self['pid'] ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -683,6 +671,32 @@ class Contact
|
|||
return ['public' => $pcid, 'user' => $ucid];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the public contact id of a provided contact id
|
||||
*
|
||||
* @param integer $cid
|
||||
* @param integer $uid
|
||||
* @return integer
|
||||
*/
|
||||
public static function getPublicContactId(int $cid, int $uid): int
|
||||
{
|
||||
$contact = DBA::selectFirst('account-user-view', ['pid'], ['id' => $cid, 'uid' => [0, $uid]]);
|
||||
return $contact['pid'] ?? 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the user contact id of a provided contact id
|
||||
*
|
||||
* @param integer $cid
|
||||
* @param integer $uid
|
||||
* @return integer
|
||||
*/
|
||||
public static function getUserContactId(int $cid, int $uid): int
|
||||
{
|
||||
$data = self::getPublicAndUserContactID($cid, $uid);
|
||||
return $data['user'] ?? 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function for "getPublicAndUserContactID"
|
||||
*
|
||||
|
|
@ -892,10 +906,10 @@ class Contact
|
|||
|
||||
$fields['avatar'] = User::getAvatarUrl($user);
|
||||
$fields['header'] = User::getBannerUrl($user);
|
||||
$fields['forum'] = $user['page-flags'] == User::PAGE_FLAGS_COMMUNITY;
|
||||
$fields['forum'] = in_array($user['page-flags'], [User::PAGE_FLAGS_COMMUNITY, User::PAGE_FLAGS_COMM_MAN]);
|
||||
$fields['prv'] = $user['page-flags'] == User::PAGE_FLAGS_PRVGROUP;
|
||||
$fields['unsearchable'] = !$profile['net-publish'];
|
||||
$fields['manually-approve'] = in_array($user['page-flags'], [User::PAGE_FLAGS_NORMAL, User::PAGE_FLAGS_PRVGROUP]);
|
||||
$fields['manually-approve'] = in_array($user['page-flags'], [User::PAGE_FLAGS_NORMAL, User::PAGE_FLAGS_PRVGROUP, User::PAGE_FLAGS_COMM_MAN]);
|
||||
$fields['baseurl'] = DI::baseUrl();
|
||||
$fields['gsid'] = GServer::getID($fields['baseurl'], true);
|
||||
|
||||
|
|
@ -980,13 +994,13 @@ class Contact
|
|||
}
|
||||
|
||||
if (in_array($contact['rel'], [self::SHARING, self::FRIEND])) {
|
||||
$cdata = self::getPublicAndUserContactID($contact['id'], $contact['uid']);
|
||||
if (!empty($cdata['public'])) {
|
||||
Worker::add(Worker::PRIORITY_HIGH, 'Contact\Unfollow', $cdata['public'], $contact['uid']);
|
||||
$pcid = self::getPublicContactId($contact['id'], $contact['uid']);
|
||||
if ($pcid) {
|
||||
Worker::add(Worker::PRIORITY_HIGH, 'Contact\Unfollow', $pcid, $contact['uid']);
|
||||
}
|
||||
}
|
||||
|
||||
self::removeSharer($contact);
|
||||
self::removeSharer($contact, false);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1010,13 +1024,13 @@ class Contact
|
|||
}
|
||||
|
||||
if (in_array($contact['rel'], [self::FOLLOWER, self::FRIEND])) {
|
||||
$cdata = self::getPublicAndUserContactID($contact['id'], $contact['uid']);
|
||||
if (!empty($cdata['public'])) {
|
||||
Worker::add(Worker::PRIORITY_HIGH, 'Contact\RevokeFollow', $cdata['public'], $contact['uid']);
|
||||
$pcid = self::getPublicContactId($contact['id'], $contact['uid']);
|
||||
if ($pcid) {
|
||||
Worker::add(Worker::PRIORITY_HIGH, 'Contact\RevokeFollow', $pcid, $contact['uid']);
|
||||
}
|
||||
}
|
||||
|
||||
self::removeFollower($contact);
|
||||
self::removeFollower($contact, false);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1037,14 +1051,14 @@ class Contact
|
|||
throw new \InvalidArgumentException('Unexpected public contact record');
|
||||
}
|
||||
|
||||
$cdata = self::getPublicAndUserContactID($contact['id'], $contact['uid']);
|
||||
$pcid = self::getPublicContactId($contact['id'], $contact['uid']);
|
||||
|
||||
if (in_array($contact['rel'], [self::SHARING, self::FRIEND]) && !empty($cdata['public'])) {
|
||||
Worker::add(Worker::PRIORITY_HIGH, 'Contact\Unfollow', $cdata['public'], $contact['uid']);
|
||||
if (in_array($contact['rel'], [self::SHARING, self::FRIEND]) && $pcid) {
|
||||
Worker::add(Worker::PRIORITY_HIGH, 'Contact\Unfollow', $pcid, $contact['uid']);
|
||||
}
|
||||
|
||||
if (in_array($contact['rel'], [self::FOLLOWER, self::FRIEND]) && !empty($cdata['public'])) {
|
||||
Worker::add(Worker::PRIORITY_HIGH, 'Contact\RevokeFollow', $cdata['public'], $contact['uid']);
|
||||
if (in_array($contact['rel'], [self::FOLLOWER, self::FRIEND]) && $pcid) {
|
||||
Worker::add(Worker::PRIORITY_HIGH, 'Contact\RevokeFollow', $pcid, $contact['uid']);
|
||||
}
|
||||
|
||||
self::remove($contact['id']);
|
||||
|
|
@ -1455,6 +1469,7 @@ class Contact
|
|||
}
|
||||
}
|
||||
|
||||
GServer::updateFromProbeArray($data);
|
||||
self::updateFromProbeArray($contact_id, $data);
|
||||
|
||||
// Don't return a number for a deleted account
|
||||
|
|
@ -1558,24 +1573,25 @@ class Contact
|
|||
/**
|
||||
* Returns posts from a given contact url
|
||||
*
|
||||
* @param string $contact_url Contact URL
|
||||
* @param bool $thread_mode
|
||||
* @param int $update Update mode
|
||||
* @param int $parent Item parent ID for the update mode
|
||||
* @param bool $only_media Only display media content
|
||||
* @param string $contact_url Contact URL
|
||||
* @param int $uid User ID
|
||||
* @param bool $only_media Only display media content
|
||||
* @param string $last_created Newest creation date, used for paging
|
||||
* @return string posts in HTML
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function getPostsFromUrl(string $contact_url, int $uid, bool $only_media = false): string
|
||||
public static function getPostsFromUrl(string $contact_url, int $uid, bool $only_media = false, string $last_created = null): string
|
||||
{
|
||||
return self::getPostsFromId(self::getIdForURL($contact_url), $uid, $only_media);
|
||||
return self::getPostsFromId(self::getIdForURL($contact_url), $uid, $only_media, $last_created);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns posts from a given contact id
|
||||
*
|
||||
* @param int $cid Contact ID
|
||||
* @param bool $only_media Only display media content
|
||||
* @param int $cid Contact ID
|
||||
* @param int $uid User ID
|
||||
* @param bool $only_media Only display media content
|
||||
* @param string $last_created Newest creation date, used for paging
|
||||
* @return string posts in HTML
|
||||
* @throws \Exception
|
||||
*/
|
||||
|
|
@ -1793,7 +1809,7 @@ class Contact
|
|||
return;
|
||||
}
|
||||
|
||||
if (Network::isLocalLink($contact['url'])) {
|
||||
if (DI::baseUrl()->isLocalUrl($contact['url'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -1910,7 +1926,7 @@ class Contact
|
|||
return $contact;
|
||||
}
|
||||
|
||||
$local = !empty($contact['url']) && Network::isLocalLink($contact['url']);
|
||||
$local = !empty($contact['url']) && DI::baseUrl()->isLocalUrl($contact['url']);
|
||||
|
||||
if (!$local && !empty($contact['id']) && !empty($contact['avatar'])) {
|
||||
self::updateAvatar($contact['id'], $contact['avatar'], true);
|
||||
|
|
@ -2300,7 +2316,7 @@ class Contact
|
|||
if (($uid == 0) && !$force && empty($contact['thumb']) && empty($contact['micro']) && !$create_cache) {
|
||||
if (($contact['avatar'] != $avatar) || empty($contact['blurhash'])) {
|
||||
$update_fields = ['avatar' => $avatar];
|
||||
if (!Network::isLocalLink($avatar)) {
|
||||
if (!DI::baseUrl()->isLocalUrl($avatar)) {
|
||||
try {
|
||||
$fetchResult = HTTPSignature::fetchRaw($avatar, 0, [HttpClientOptions::ACCEPT_CONTENT => [HttpClientAccept::IMAGE]]);
|
||||
|
||||
|
|
@ -2348,7 +2364,7 @@ class Contact
|
|||
$cache_avatar = DI::config()->get('system', 'cache_contact_avatar');
|
||||
|
||||
// Local contact avatars don't need to be cached
|
||||
if ($cache_avatar && Network::isLocalLink($contact['url'])) {
|
||||
if ($cache_avatar && DI::baseUrl()->isLocalUrl($contact['url'])) {
|
||||
$cache_avatar = !DBA::exists('contact', ['nurl' => $contact['nurl'], 'self' => true]);
|
||||
}
|
||||
|
||||
|
|
@ -2664,6 +2680,14 @@ class Contact
|
|||
|
||||
$data = Probe::uri($contact['url'], $network, $contact['uid']);
|
||||
|
||||
if (in_array($data['network'], Protocol::FEDERATED) && (parse_url($data['url'], PHP_URL_SCHEME) == 'http')) {
|
||||
$ssl_url = str_replace('http://', 'https://', $contact['url']);
|
||||
$ssl_data = Probe::uri($ssl_url, $network, $contact['uid']);
|
||||
if (($ssl_data['network'] == $data['network']) && (parse_url($ssl_data['url'], PHP_URL_SCHEME) != 'http')) {
|
||||
$data = $ssl_data;
|
||||
}
|
||||
}
|
||||
|
||||
if ($data['network'] == Protocol::DIASPORA) {
|
||||
try {
|
||||
DI::dsprContact()->updateFromProbeArray($data);
|
||||
|
|
@ -2682,6 +2706,7 @@ class Contact
|
|||
}
|
||||
}
|
||||
|
||||
GServer::updateFromProbeArray($data);
|
||||
return self::updateFromProbeArray($id, $data);
|
||||
}
|
||||
|
||||
|
|
@ -2821,7 +2846,7 @@ class Contact
|
|||
// We must not try to update relay contacts via probe. They are no real contacts.
|
||||
// See Relay::updateContact() for more details.
|
||||
// We check after the probing to be able to correct falsely detected contact types.
|
||||
if (($contact['contact-type'] == self::TYPE_RELAY) && Strings::compareLink($contact['url'], $contact['baseurl']) &&
|
||||
if (($contact['contact-type'] == self::TYPE_RELAY) && Strings::compareLink($contact['url'], $contact['baseurl'] ?? '') &&
|
||||
(!Strings::compareLink($ret['url'], $contact['url']) || in_array($ret['network'], [Protocol::FEED, Protocol::PHANTOM]))
|
||||
) {
|
||||
if (GServer::reachable($contact)) {
|
||||
|
|
@ -3010,6 +3035,10 @@ class Contact
|
|||
*/
|
||||
public static function getProtocol(string $url, string $network): string
|
||||
{
|
||||
if (self::isLocal($url)) {
|
||||
return Protocol::ACTIVITYPUB;
|
||||
}
|
||||
|
||||
if ($network != Protocol::DFRN) {
|
||||
return $network;
|
||||
}
|
||||
|
|
@ -3224,6 +3253,7 @@ class Contact
|
|||
}
|
||||
|
||||
if ($probed) {
|
||||
GServer::updateFromProbeArray($ret);
|
||||
self::updateFromProbeArray($contact_id, $ret);
|
||||
} else {
|
||||
try {
|
||||
|
|
@ -3400,16 +3430,21 @@ class Contact
|
|||
* Update the local relationship when a local user loses a follower
|
||||
*
|
||||
* @param array $contact User-specific contact (uid != 0) array
|
||||
* @param bool $delete Delete if set, otherwise set relation to "nothing" when contact had been a follower
|
||||
* @return void
|
||||
* @throws HTTPException\InternalServerErrorException
|
||||
* @throws \ImagickException
|
||||
*/
|
||||
public static function removeFollower(array $contact)
|
||||
public static function removeFollower(array $contact, bool $delete = true)
|
||||
{
|
||||
if (in_array($contact['rel'] ?? [], [self::FRIEND, self::SHARING])) {
|
||||
self::update(['rel' => self::SHARING], ['id' => $contact['id']]);
|
||||
} elseif (!empty($contact['id'])) {
|
||||
self::remove($contact['id']);
|
||||
if ($delete) {
|
||||
self::remove($contact['id']);
|
||||
} else {
|
||||
self::update(['rel' => self::NOTHING, 'pending' => false], ['id' => $contact['id']]);
|
||||
}
|
||||
} else {
|
||||
DI::logger()->info('Couldn\'t remove follower because of invalid contact array', ['contact' => $contact]);
|
||||
return;
|
||||
|
|
@ -3419,9 +3454,9 @@ class Contact
|
|||
|
||||
self::clearFollowerFollowingEndpointCache($contact['uid']);
|
||||
|
||||
$cdata = self::getPublicAndUserContactID($contact['id'], $contact['uid']);
|
||||
if (!empty($cdata['public'])) {
|
||||
DI::notification()->deleteForUserByVerb($contact['uid'], Activity::FOLLOW, ['actor-id' => $cdata['public']]);
|
||||
$pcid = self::getPublicContactId($contact['id'], $contact['uid']);
|
||||
if ($pcid) {
|
||||
DI::notification()->deleteForUserByVerb($contact['uid'], Activity::FOLLOW, ['actor-id' => $pcid]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -3430,14 +3465,19 @@ class Contact
|
|||
* Removes the contact for sharing-only protocols (feed and mail).
|
||||
*
|
||||
* @param array $contact User-specific contact (uid != 0) array
|
||||
* @param bool $delete Delete if set, otherwise set relation to "nothing" when contact had been a sharer
|
||||
* @throws HTTPException\InternalServerErrorException
|
||||
*/
|
||||
public static function removeSharer(array $contact)
|
||||
public static function removeSharer(array $contact, bool $delete = true)
|
||||
{
|
||||
self::clearFollowerFollowingEndpointCache($contact['uid']);
|
||||
|
||||
if ($contact['rel'] == self::SHARING || in_array($contact['network'], [Protocol::FEED, Protocol::MAIL])) {
|
||||
self::remove($contact['id']);
|
||||
if (in_array($contact['rel'], [self::SHARING, self::NOTHING]) || in_array($contact['network'], [Protocol::FEED, Protocol::MAIL])) {
|
||||
if ($delete) {
|
||||
self::remove($contact['id']);
|
||||
} else {
|
||||
self::update(['rel' => self::NOTHING, 'pending' => false], ['id' => $contact['id']]);
|
||||
}
|
||||
} else {
|
||||
self::update(['rel' => self::FOLLOWER, 'pending' => false], ['id' => $contact['id']]);
|
||||
}
|
||||
|
|
@ -3563,7 +3603,14 @@ class Contact
|
|||
*/
|
||||
public static function magicLinkById(int $cid, string $url = ''): string
|
||||
{
|
||||
if (($url == '') && DI::userSession()->isAuthenticated() && DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'system', 'stay_local')) {
|
||||
return 'contact/' . $cid . '/conversations';
|
||||
}
|
||||
|
||||
$contact = DBA::selectFirst('contact', ['id', 'network', 'url', 'alias', 'uid'], ['id' => $cid]);
|
||||
if (empty($contact)) {
|
||||
return $url;
|
||||
}
|
||||
|
||||
return self::magicLinkByContact($contact, $url);
|
||||
}
|
||||
|
|
@ -3586,15 +3633,19 @@ class Contact
|
|||
return $destination;
|
||||
}
|
||||
|
||||
if (DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'system', 'stay_local') && ($url == '')) {
|
||||
return 'contact/' . $contact['id'] . '/conversations';
|
||||
}
|
||||
|
||||
if (Strings::compareLink($contact['url'], $url) || Strings::compareLink($contact['alias'] ?? '', $url)) {
|
||||
$url = '';
|
||||
}
|
||||
|
||||
// Only redirections to the same host do make sense
|
||||
if (($url != '') && (parse_url($url, PHP_URL_HOST) != parse_url($contact['url'], PHP_URL_HOST))) {
|
||||
return $url;
|
||||
}
|
||||
|
||||
if (DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'system', 'stay_local') && ($url == '')) {
|
||||
return 'contact/' . $contact['id'] . '/conversations';
|
||||
}
|
||||
|
||||
if (!empty($contact['network']) && $contact['network'] != Protocol::DFRN) {
|
||||
return $destination;
|
||||
}
|
||||
|
|
@ -3605,7 +3656,7 @@ class Contact
|
|||
|
||||
$redirect = 'contact/redir/' . $contact['id'];
|
||||
|
||||
if (($url != '') && !Strings::compareLink($contact['url'], $url)) {
|
||||
if ($url != '') {
|
||||
$redirect .= '?url=' . $url;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -281,12 +281,12 @@ class User
|
|||
*/
|
||||
public static function setCollapsed(int $cid, int $uid, bool $collapsed)
|
||||
{
|
||||
$cdata = Contact::getPublicAndUserContactID($cid, $uid);
|
||||
if (empty($cdata)) {
|
||||
$pcid = Contact::getPublicContactId($cid, $uid);
|
||||
if (!$pcid) {
|
||||
return;
|
||||
}
|
||||
|
||||
DBA::update('user-contact', ['collapsed' => $collapsed], ['cid' => $cdata['public'], 'uid' => $uid], true);
|
||||
DBA::update('user-contact', ['collapsed' => $collapsed], ['cid' => $pcid, 'uid' => $uid], true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -300,21 +300,13 @@ class User
|
|||
*/
|
||||
public static function isCollapsed(int $cid, int $uid): bool
|
||||
{
|
||||
$cdata = Contact::getPublicAndUserContactID($cid, $uid);
|
||||
if (empty($cdata)) {
|
||||
$pcid = Contact::getPublicContactId($cid, $uid);
|
||||
if (!$pcid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$collapsed = false;
|
||||
|
||||
if (!empty($cdata['public'])) {
|
||||
$public_contact = DBA::selectFirst('user-contact', ['collapsed'], ['cid' => $cdata['public'], 'uid' => $uid]);
|
||||
if (DBA::isResult($public_contact)) {
|
||||
$collapsed = (bool) $public_contact['collapsed'];
|
||||
}
|
||||
}
|
||||
|
||||
return $collapsed;
|
||||
$public_contact = DBA::selectFirst('user-contact', ['collapsed'], ['cid' => $pcid, 'uid' => $uid]);
|
||||
return $public_contact['collapsed'] ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -328,12 +320,12 @@ class User
|
|||
*/
|
||||
public static function setChannelFrequency(int $cid, int $uid, int $frequency)
|
||||
{
|
||||
$cdata = Contact::getPublicAndUserContactID($cid, $uid);
|
||||
if (empty($cdata)) {
|
||||
$pcid = Contact::getPublicContactId($cid, $uid);
|
||||
if (!$pcid) {
|
||||
return;
|
||||
}
|
||||
|
||||
DBA::update('user-contact', ['channel-frequency' => $frequency], ['cid' => $cdata['public'], 'uid' => $uid], true);
|
||||
DBA::update('user-contact', ['channel-frequency' => $frequency], ['cid' => $pcid, 'uid' => $uid], true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -347,21 +339,52 @@ class User
|
|||
*/
|
||||
public static function getChannelFrequency(int $cid, int $uid): int
|
||||
{
|
||||
$cdata = Contact::getPublicAndUserContactID($cid, $uid);
|
||||
if (empty($cdata)) {
|
||||
$pcid = Contact::getPublicContactId($cid, $uid);
|
||||
if (!$pcid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$frequency = self::FREQUENCY_DEFAULT;
|
||||
$public_contact = DBA::selectFirst('user-contact', ['channel-frequency'], ['cid' => $pcid, 'uid' => $uid]);
|
||||
return $public_contact['channel-frequency'] ?? self::FREQUENCY_DEFAULT;
|
||||
}
|
||||
|
||||
if (!empty($cdata['public'])) {
|
||||
$public_contact = DBA::selectFirst('user-contact', ['channel-frequency'], ['cid' => $cdata['public'], 'uid' => $uid]);
|
||||
if (DBA::isResult($public_contact)) {
|
||||
$frequency = $public_contact['channel-frequency'] ?? self::FREQUENCY_DEFAULT;
|
||||
}
|
||||
/**
|
||||
* Set the channel only value for contact id and user id
|
||||
*
|
||||
* @param int $cid Either public contact id or user's contact id
|
||||
* @param int $uid User ID
|
||||
* @param int $isChannelOnly Is channel only
|
||||
* @return void
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function setChannelOnly(int $cid, int $uid, bool $isChannelOnly)
|
||||
{
|
||||
$pcid = Contact::getPublicContactId($cid, $uid);
|
||||
if (!$pcid) {
|
||||
return;
|
||||
}
|
||||
|
||||
return $frequency;
|
||||
DBA::update('user-contact', ['channel-only' => $isChannelOnly], ['cid' => $pcid, 'uid' => $uid], true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if the contact is channel only for contact id and user id
|
||||
*
|
||||
* @param int $cid Either public contact id or user's contact id
|
||||
* @param int $uid User ID
|
||||
* @return bool Contact is channel only
|
||||
* @throws HTTPException\InternalServerErrorException
|
||||
* @throws \ImagickException
|
||||
*/
|
||||
public static function getChannelOnly(int $cid, int $uid): bool
|
||||
{
|
||||
$pcid = Contact::getPublicContactId($cid, $uid);
|
||||
if (!$pcid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$public_contact = DBA::selectFirst('user-contact', ['channel-only'], ['cid' => $pcid, 'uid' => $uid]);
|
||||
return $public_contact['channel-only'] ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -375,12 +398,12 @@ class User
|
|||
*/
|
||||
public static function setIsBlocked(int $cid, int $uid, bool $blocked)
|
||||
{
|
||||
$cdata = Contact::getPublicAndUserContactID($cid, $uid);
|
||||
if (empty($cdata)) {
|
||||
$pcid = Contact::getPublicContactId($cid, $uid);
|
||||
if (!$pcid) {
|
||||
return;
|
||||
}
|
||||
|
||||
DBA::update('user-contact', ['is-blocked' => $blocked], ['cid' => $cdata['public'], 'uid' => $uid], true);
|
||||
DBA::update('user-contact', ['is-blocked' => $blocked], ['cid' => $pcid, 'uid' => $uid], true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -393,18 +416,12 @@ class User
|
|||
*/
|
||||
public static function isIsBlocked(int $cid, int $uid): bool
|
||||
{
|
||||
$cdata = Contact::getPublicAndUserContactID($cid, $uid);
|
||||
if (empty($cdata)) {
|
||||
$pcid = Contact::getPublicContactId($cid, $uid);
|
||||
if (!$pcid) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!empty($cdata['public'])) {
|
||||
$public_contact = DBA::selectFirst('user-contact', ['is-blocked'], ['cid' => $cdata['public'], 'uid' => $uid]);
|
||||
if (DBA::isResult($public_contact)) {
|
||||
return $public_contact['is-blocked'];
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
$public_contact = DBA::selectFirst('user-contact', ['is-blocked'], ['cid' => $pcid, 'uid' => $uid]);
|
||||
return $public_contact['is-blocked'] ?? false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -516,7 +516,7 @@ class Event
|
|||
throw new HTTPException\UnauthorizedException(DI::l10n()->t('Access to this profile has been restricted.'));
|
||||
}
|
||||
|
||||
if (!DI::userSession()->isAuthenticated() && !Feature::isEnabled($owner['uid'], 'public_calendar')) {
|
||||
if (!DI::userSession()->isAuthenticated() && !Feature::isEnabled($owner['uid'], Feature::PUBLIC_CALENDAR)) {
|
||||
throw new HTTPException\UnauthorizedException(DI::l10n()->t('Permission denied.'));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ use Friendica\Module\Register;
|
|||
use Friendica\Network\HTTPClient\Client\HttpClientAccept;
|
||||
use Friendica\Network\HTTPClient\Client\HttpClientOptions;
|
||||
use Friendica\Network\HTTPClient\Capability\ICanHandleHttpResponses;
|
||||
use Friendica\Network\HTTPClient\Client\HttpClientRequest;
|
||||
use Friendica\Network\Probe;
|
||||
use Friendica\Protocol\ActivityPub;
|
||||
use Friendica\Protocol\Relay;
|
||||
|
|
@ -86,9 +87,11 @@ class GServer
|
|||
|
||||
// Standardized endpoints
|
||||
const DETECT_STATISTICS_JSON = 100;
|
||||
const DETECT_NODEINFO_1 = 101;
|
||||
const DETECT_NODEINFO_2 = 102;
|
||||
const DETECT_NODEINFO_210 = 103;
|
||||
const DETECT_NODEINFO_10 = 101; // Nodeinfo Version 1.0
|
||||
const DETECT_NODEINFO_20 = 102; // Nodeinfo Version 2.0
|
||||
const DETECT_NODEINFO2_10 = 103; // Nodeinfo2 Version 1.0
|
||||
const DETECT_NODEINFO_21 = 104; // Nodeinfo Version 2.1
|
||||
const DETECT_NODEINFO_22 = 105; // Nodeinfo Version 2.2
|
||||
|
||||
/**
|
||||
* Check for the existence of a server and adds it in the background if not existant
|
||||
|
|
@ -577,7 +580,7 @@ class GServer
|
|||
// We only follow redirects when the path stays the same or the target url has no path.
|
||||
// Some systems have got redirects on their landing page to a single account page. This check handles it.
|
||||
if (((parse_url($url, PHP_URL_HOST) != parse_url($valid_url, PHP_URL_HOST)) && (parse_url($url, PHP_URL_PATH) == parse_url($valid_url, PHP_URL_PATH))) ||
|
||||
(((parse_url($url, PHP_URL_HOST) != parse_url($valid_url, PHP_URL_HOST)) || (parse_url($url, PHP_URL_PATH) != parse_url($valid_url, PHP_URL_PATH))) && empty(parse_url($valid_url, PHP_URL_PATH)))) {
|
||||
(((parse_url($url, PHP_URL_HOST) != parse_url($valid_url, PHP_URL_HOST)) || (parse_url($url, PHP_URL_PATH) != parse_url($valid_url, PHP_URL_PATH))) && empty(parse_url($valid_url, PHP_URL_PATH)))) {
|
||||
Logger::debug('Found redirect. Mark old entry as failure', ['old' => $url, 'new' => $valid_url]);
|
||||
self::setFailureByUrl($url);
|
||||
if (!self::getID($valid_url, true) && !Network::isUrlBlocked($valid_url)) {
|
||||
|
|
@ -587,7 +590,7 @@ class GServer
|
|||
}
|
||||
|
||||
if ((parse_url($url, PHP_URL_HOST) != parse_url($valid_url, PHP_URL_HOST)) && (parse_url($url, PHP_URL_PATH) != parse_url($valid_url, PHP_URL_PATH)) &&
|
||||
(parse_url($url, PHP_URL_PATH) == '')) {
|
||||
(parse_url($url, PHP_URL_PATH) == '')) {
|
||||
Logger::debug('Found redirect. Mark old entry as failure and redirect to the basepath.', ['old' => $url, 'new' => $valid_url]);
|
||||
$parts = (array)parse_url($valid_url);
|
||||
unset($parts['path']);
|
||||
|
|
@ -605,13 +608,13 @@ class GServer
|
|||
if ((parse_url($url, PHP_URL_HOST) == parse_url($valid_url, PHP_URL_HOST)) &&
|
||||
(parse_url($url, PHP_URL_PATH) == parse_url($valid_url, PHP_URL_PATH)) &&
|
||||
(parse_url($url, PHP_URL_SCHEME) != parse_url($valid_url, PHP_URL_SCHEME))) {
|
||||
$url = $valid_url;
|
||||
$url = $valid_url;
|
||||
}
|
||||
|
||||
$in_webroot = empty(parse_url($url, PHP_URL_PATH));
|
||||
|
||||
// When a nodeinfo is present, we don't need to dig further
|
||||
$curlResult = DI::httpClient()->get($url . '/.well-known/x-nodeinfo2', HttpClientAccept::JSON);
|
||||
$curlResult = DI::httpClient()->get($url . '/.well-known/nodeinfo', HttpClientAccept::JSON, [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]);
|
||||
if ($curlResult->isTimeout()) {
|
||||
self::setFailureByUrl($url);
|
||||
return false;
|
||||
|
|
@ -620,10 +623,11 @@ class GServer
|
|||
if (!empty($network) && !in_array($network, Protocol::NATIVE_SUPPORT)) {
|
||||
$serverdata = ['detection-method' => self::DETECT_MANUAL, 'network' => $network, 'platform' => '', 'version' => '', 'site_name' => '', 'info' => ''];
|
||||
} else {
|
||||
$serverdata = self::parseNodeinfo210($curlResult);
|
||||
if (empty($serverdata)) {
|
||||
$curlResult = DI::httpClient()->get($url . '/.well-known/nodeinfo', HttpClientAccept::JSON);
|
||||
$serverdata = self::fetchNodeinfo($url, $curlResult);
|
||||
$serverdata = self::parseNodeinfo($url, $curlResult);
|
||||
|
||||
if (empty($serverdata) || !in_array($serverdata['detection-method'], [self::DETECT_NODEINFO_20, self::DETECT_NODEINFO_21, self::DETECT_NODEINFO_22])) {
|
||||
$curlResult = DI::httpClient()->get($url . '/.well-known/x-nodeinfo2', HttpClientAccept::JSON, [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]);
|
||||
$serverdata = self::parseNodeinfo2($curlResult) ?: $serverdata;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -640,9 +644,9 @@ class GServer
|
|||
if ($in_webroot) {
|
||||
// Fetch the landing page, possibly it reveals some data
|
||||
$accept = 'application/activity+json,application/ld+json,application/json,*/*;q=0.9';
|
||||
$curlResult = DI::httpClient()->get($url, $accept);
|
||||
$curlResult = DI::httpClient()->get($url, $accept, [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]);
|
||||
if (!$curlResult->isSuccess() && $curlResult->getReturnCode() == '406') {
|
||||
$curlResult = DI::httpClient()->get($url, HttpClientAccept::HTML);
|
||||
$curlResult = DI::httpClient()->get($url, HttpClientAccept::HTML, [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]);
|
||||
$html_fetched = true;
|
||||
} else {
|
||||
$html_fetched = false;
|
||||
|
|
@ -655,10 +659,10 @@ class GServer
|
|||
$serverdata = $data['server'];
|
||||
$systemactor = $data['actor'];
|
||||
if (!$html_fetched && !in_array($serverdata['detection-method'], [self::DETECT_SYSTEM_ACTOR, self::DETECT_AP_COLLECTION])) {
|
||||
$curlResult = DI::httpClient()->get($url, HttpClientAccept::HTML);
|
||||
$curlResult = DI::httpClient()->get($url, HttpClientAccept::HTML, [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]);
|
||||
}
|
||||
} elseif (!$html_fetched && (strlen($curlResult->getBodyString()) < 1000)) {
|
||||
$curlResult = DI::httpClient()->get($url, HttpClientAccept::HTML);
|
||||
$curlResult = DI::httpClient()->get($url, HttpClientAccept::HTML, [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]);
|
||||
}
|
||||
|
||||
if ($serverdata['detection-method'] != self::DETECT_SYSTEM_ACTOR) {
|
||||
|
|
@ -736,6 +740,10 @@ class GServer
|
|||
}
|
||||
}
|
||||
|
||||
if (in_array($serverdata['platform'] ?? '', ['friendica', 'hubzilla', 'streams', 'osada', 'mistpark', 'roadhouse', 'zap'])) {
|
||||
$serverdata = self::getZotData($url, $serverdata);
|
||||
}
|
||||
|
||||
// When we hadn't been able to detect the network type, we use the hint from the parameter
|
||||
if (($serverdata['network'] == Protocol::PHANTOM) && !empty($network)) {
|
||||
$serverdata['network'] = $network;
|
||||
|
|
@ -867,7 +875,7 @@ class GServer
|
|||
{
|
||||
Logger::info('Discover relay data', ['server' => $server_url]);
|
||||
|
||||
$curlResult = DI::httpClient()->get($server_url . '/.well-known/x-social-relay', HttpClientAccept::JSON);
|
||||
$curlResult = DI::httpClient()->get($server_url . '/.well-known/x-social-relay', HttpClientAccept::JSON, [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]);
|
||||
if (!$curlResult->isSuccess()) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -962,7 +970,7 @@ class GServer
|
|||
*/
|
||||
private static function fetchStatistics(string $url, array $serverdata): array
|
||||
{
|
||||
$curlResult = DI::httpClient()->get($url . '/statistics.json', HttpClientAccept::JSON);
|
||||
$curlResult = DI::httpClient()->get($url . '/statistics.json', HttpClientAccept::JSON, [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]);
|
||||
if (!$curlResult->isSuccess()) {
|
||||
return $serverdata;
|
||||
}
|
||||
|
|
@ -1044,7 +1052,9 @@ class GServer
|
|||
}
|
||||
|
||||
/**
|
||||
* Detect server type by using the nodeinfo data
|
||||
* Parses Nodeinfo
|
||||
*
|
||||
* @see https://github.com/jhass/nodeinfo
|
||||
*
|
||||
* @param string $url address of the server
|
||||
* @param ICanHandleHttpResponses $httpResult
|
||||
|
|
@ -1053,7 +1063,7 @@ class GServer
|
|||
*
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
*/
|
||||
private static function fetchNodeinfo(string $url, ICanHandleHttpResponses $httpResult): array
|
||||
private static function parseNodeinfo(string $url, ICanHandleHttpResponses $httpResult): array
|
||||
{
|
||||
if (!$httpResult->isSuccess()) {
|
||||
return [];
|
||||
|
|
@ -1067,6 +1077,7 @@ class GServer
|
|||
|
||||
$nodeinfo1_url = '';
|
||||
$nodeinfo2_url = '';
|
||||
$detection_method = self::DETECT_MANUAL;
|
||||
|
||||
foreach ($nodeinfo['links'] as $link) {
|
||||
if (!is_array($link) || empty($link['rel']) || empty($link['href'])) {
|
||||
|
|
@ -1076,8 +1087,15 @@ class GServer
|
|||
|
||||
if ($link['rel'] == 'http://nodeinfo.diaspora.software/ns/schema/1.0') {
|
||||
$nodeinfo1_url = Network::addBasePath($link['href'], $httpResult->getUrl());
|
||||
} elseif ($link['rel'] == 'http://nodeinfo.diaspora.software/ns/schema/2.0') {
|
||||
} elseif (($detection_method < self::DETECT_NODEINFO_20) && ($link['rel'] == 'http://nodeinfo.diaspora.software/ns/schema/2.0')) {
|
||||
$nodeinfo2_url = Network::addBasePath($link['href'], $httpResult->getUrl());
|
||||
$detection_method = self::DETECT_NODEINFO_20;
|
||||
} elseif (($detection_method < self::DETECT_NODEINFO_21) && ($link['rel'] == 'http://nodeinfo.diaspora.software/ns/schema/2.1')) {
|
||||
$nodeinfo2_url = Network::addBasePath($link['href'], $httpResult->getUrl());
|
||||
$detection_method = self::DETECT_NODEINFO_21;
|
||||
} elseif (($detection_method < self::DETECT_NODEINFO_22) && ($link['rel'] == 'http://nodeinfo.diaspora.software/ns/schema/2.2')) {
|
||||
$nodeinfo2_url = Network::addBasePath($link['href'], $httpResult->getUrl());
|
||||
$detection_method = self::DETECT_NODEINFO_22;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1088,18 +1106,20 @@ class GServer
|
|||
$server = [];
|
||||
|
||||
if (!empty($nodeinfo2_url)) {
|
||||
$server = self::parseNodeinfo2($nodeinfo2_url);
|
||||
$server = self::parseNodeinfo_2($nodeinfo2_url, $detection_method);
|
||||
}
|
||||
|
||||
if (empty($server) && !empty($nodeinfo1_url)) {
|
||||
$server = self::parseNodeinfo1($nodeinfo1_url);
|
||||
$server = self::parseNodeinfo_1($nodeinfo1_url);
|
||||
}
|
||||
|
||||
return $server;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses Nodeinfo 1
|
||||
* Parses Nodeinfo with the version 1.0
|
||||
*
|
||||
* @see https://github.com/jhass/nodeinfo/tree/main/schemas/1.0
|
||||
*
|
||||
* @param string $nodeinfo_url address of the nodeinfo path
|
||||
*
|
||||
|
|
@ -1107,9 +1127,9 @@ class GServer
|
|||
*
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
*/
|
||||
private static function parseNodeinfo1(string $nodeinfo_url): array
|
||||
private static function parseNodeinfo_1(string $nodeinfo_url): array
|
||||
{
|
||||
$curlResult = DI::httpClient()->get($nodeinfo_url, HttpClientAccept::JSON);
|
||||
$curlResult = DI::httpClient()->get($nodeinfo_url, HttpClientAccept::JSON, [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]);
|
||||
if (!$curlResult->isSuccess()) {
|
||||
return [];
|
||||
}
|
||||
|
|
@ -1120,8 +1140,10 @@ class GServer
|
|||
return [];
|
||||
}
|
||||
|
||||
$server = ['detection-method' => self::DETECT_NODEINFO_1,
|
||||
'register_policy' => Register::CLOSED];
|
||||
$server = [
|
||||
'detection-method' => self::DETECT_NODEINFO_10,
|
||||
'register_policy' => Register::CLOSED
|
||||
];
|
||||
|
||||
if (!empty($nodeinfo['openRegistrations'])) {
|
||||
$server['register_policy'] = Register::OPEN;
|
||||
|
|
@ -1197,19 +1219,22 @@ class GServer
|
|||
}
|
||||
|
||||
/**
|
||||
* Parses Nodeinfo 2
|
||||
* Parses Nodeinfo with the versions 2.0, 2.1 and 2.2
|
||||
*
|
||||
* @see https://git.feneas.org/jaywink/nodeinfo2
|
||||
* @see https://github.com/jhass/nodeinfo/tree/main/schemas/2.0
|
||||
* @see https://github.com/jhass/nodeinfo/tree/main/schemas/2.1
|
||||
* @see https://github.com/jhass/nodeinfo/tree/main/schemas/2.2
|
||||
*
|
||||
* @param string $nodeinfo_url address of the nodeinfo path
|
||||
* @param string $nodeinfo_url address of the nodeinfo path
|
||||
* @param int $detection_method nodeinfo version
|
||||
*
|
||||
* @return array Server data
|
||||
*
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
*/
|
||||
private static function parseNodeinfo2(string $nodeinfo_url): array
|
||||
private static function parseNodeinfo_2(string $nodeinfo_url, int $detection_method): array
|
||||
{
|
||||
$curlResult = DI::httpClient()->get($nodeinfo_url, HttpClientAccept::JSON);
|
||||
$curlResult = DI::httpClient()->get($nodeinfo_url, HttpClientAccept::JSON, [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]);
|
||||
if (!$curlResult->isSuccess()) {
|
||||
return [];
|
||||
}
|
||||
|
|
@ -1220,7 +1245,7 @@ class GServer
|
|||
}
|
||||
|
||||
$server = [
|
||||
'detection-method' => self::DETECT_NODEINFO_2,
|
||||
'detection-method' => $detection_method,
|
||||
'register_policy' => Register::CLOSED,
|
||||
'platform' => 'unknown',
|
||||
];
|
||||
|
|
@ -1229,6 +1254,15 @@ class GServer
|
|||
$server['register_policy'] = Register::OPEN;
|
||||
}
|
||||
|
||||
if (!empty($nodeinfo['instance'])) {
|
||||
if (!empty($nodeinfo['instance']['name'])) {
|
||||
$server['site_name'] = $nodeinfo['instance']['name'];
|
||||
}
|
||||
if (!empty($nodeinfo['instance']['description'])) {
|
||||
$server['info'] = $nodeinfo['instance']['description'];
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($nodeinfo['software'])) {
|
||||
if (isset($nodeinfo['software']['name'])) {
|
||||
$server['platform'] = strtolower($nodeinfo['software']['name']);
|
||||
|
|
@ -1244,6 +1278,13 @@ class GServer
|
|||
if (($server['platform'] == 'mastodon') && substr($nodeinfo['software']['version'], -5) == '-qoto') {
|
||||
$server['platform'] = 'qoto';
|
||||
}
|
||||
|
||||
if (isset($nodeinfo['software']['repository'])) {
|
||||
$server['repository'] = strtolower($nodeinfo['software']['repository']);
|
||||
}
|
||||
if (isset($nodeinfo['software']['homepage'])) {
|
||||
$server['homepage'] = strtolower($nodeinfo['software']['homepage']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1255,6 +1296,9 @@ class GServer
|
|||
if (!empty($nodeinfo['metadata']['nodeName'])) {
|
||||
$server['site_name'] = $nodeinfo['metadata']['nodeName'];
|
||||
}
|
||||
if (!empty($nodeinfo['metadata']['nodeDescription'])) {
|
||||
$server['info'] = $nodeinfo['metadata']['nodeDescription'];
|
||||
}
|
||||
|
||||
if (!empty($nodeinfo['usage']['users']['total'])) {
|
||||
$server['registered-users'] = max($nodeinfo['usage']['users']['total'], 1);
|
||||
|
|
@ -1315,9 +1359,9 @@ class GServer
|
|||
}
|
||||
|
||||
/**
|
||||
* Parses NodeInfo2 protocol 1.0
|
||||
* Parses NodeInfo2
|
||||
*
|
||||
* @see https://github.com/jaywink/nodeinfo2/blob/master/PROTOCOL.md
|
||||
* @see https://github.com/jaywink/nodeinfo2
|
||||
*
|
||||
* @param string $nodeinfo_url address of the nodeinfo path
|
||||
*
|
||||
|
|
@ -1325,7 +1369,7 @@ class GServer
|
|||
*
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
*/
|
||||
private static function parseNodeinfo210(ICanHandleHttpResponses $httpResult): array
|
||||
private static function parseNodeinfo2(ICanHandleHttpResponses $httpResult): array
|
||||
{
|
||||
if (!$httpResult->isSuccess()) {
|
||||
return [];
|
||||
|
|
@ -1337,8 +1381,10 @@ class GServer
|
|||
return [];
|
||||
}
|
||||
|
||||
$server = ['detection-method' => self::DETECT_NODEINFO_210,
|
||||
'register_policy' => Register::CLOSED];
|
||||
$server = [
|
||||
'detection-method' => self::DETECT_NODEINFO2_10,
|
||||
'register_policy' => Register::CLOSED
|
||||
];
|
||||
|
||||
if (!empty($nodeinfo['openRegistrations'])) {
|
||||
$server['register_policy'] = Register::OPEN;
|
||||
|
|
@ -1429,7 +1475,7 @@ class GServer
|
|||
*/
|
||||
private static function fetchSiteinfo(string $url, array $serverdata): array
|
||||
{
|
||||
$curlResult = DI::httpClient()->get($url . '/siteinfo.json', HttpClientAccept::JSON);
|
||||
$curlResult = DI::httpClient()->get($url . '/siteinfo.json', HttpClientAccept::JSON, [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]);
|
||||
if (!$curlResult->isSuccess()) {
|
||||
return $serverdata;
|
||||
}
|
||||
|
|
@ -1586,8 +1632,11 @@ class GServer
|
|||
private static function getNomadName(string $url): string
|
||||
{
|
||||
$name = 'nomad';
|
||||
$curlResult = DI::httpClient()->get($url . '/manifest', 'application/manifest+json');
|
||||
$curlResult = DI::httpClient()->get($url . '/manifest', 'application/manifest+json', [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]);
|
||||
if (!$curlResult->isSuccess() || ($curlResult->getBodyString() == '')) {
|
||||
if ($curlResult->getReturnCode() == 418) {
|
||||
$name = 'streams';
|
||||
}
|
||||
return $name;
|
||||
}
|
||||
|
||||
|
|
@ -1607,7 +1656,7 @@ class GServer
|
|||
*/
|
||||
private static function getNomadVersion(string $url): string
|
||||
{
|
||||
$curlResult = DI::httpClient()->get($url . '/api/z/1.0/version', HttpClientAccept::JSON);
|
||||
$curlResult = DI::httpClient()->get($url . '/api/z/1.0/version', HttpClientAccept::JSON, [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]);
|
||||
if (!$curlResult->isSuccess() || ($curlResult->getBodyString() == '')) {
|
||||
return '';
|
||||
}
|
||||
|
|
@ -1619,6 +1668,77 @@ class GServer
|
|||
return $data ?? '';
|
||||
}
|
||||
|
||||
private static function getZotData(string $url, array $serverdata): array
|
||||
{
|
||||
$curlResult = DI::httpClient()->get($url, 'application/x-zot+json');
|
||||
if (!$curlResult->isSuccess()) {
|
||||
return $serverdata;
|
||||
}
|
||||
$json = $curlResult->getBodyString();
|
||||
$data = json_decode($json, true);
|
||||
if (empty($data)) {
|
||||
return $serverdata;
|
||||
}
|
||||
|
||||
if (!empty($data['site'])) {
|
||||
$serverdata = self::getFromZotData($data['site'], $serverdata);
|
||||
} else {
|
||||
$serverdata = self::getFromZotData($data, $serverdata);
|
||||
}
|
||||
return $serverdata;
|
||||
}
|
||||
|
||||
private static function getFromZotData(array $data, array $serverdata): array
|
||||
{
|
||||
if (!empty($data['version'])) {
|
||||
$serverdata['version'] = $data['version'];
|
||||
}
|
||||
|
||||
if (!empty($data['openWebAuth'])) {
|
||||
$serverdata['openwebauth'] = $data['openWebAuth'];
|
||||
}
|
||||
|
||||
if (!empty($data['authRedirect'])) {
|
||||
$serverdata['authredirect'] = $data['authRedirect'];
|
||||
}
|
||||
|
||||
if (!empty($data['sitename'])) {
|
||||
$serverdata['site_name'] = $data['sitename'];
|
||||
}
|
||||
|
||||
if (!empty($data['about'])) {
|
||||
$serverdata['info'] = $data['about'];
|
||||
}
|
||||
|
||||
if (empty($serverdata['info']) && !empty($data['location'])) {
|
||||
$serverdata['info'] = $data['location'];
|
||||
}
|
||||
|
||||
if (!empty($data['project']) && in_array($data['project'], ['friendica', 'hubzilla', 'streams', 'osada', 'mistpark', 'roadhouse', 'zap'])) {
|
||||
$serverdata['platform'] = $data['project'];
|
||||
}
|
||||
|
||||
if (!empty($data['accounts'])) {
|
||||
$serverdata['registered-users'] = $data['accounts'];
|
||||
}
|
||||
|
||||
if (!empty($data['register_policy'])) {
|
||||
switch ($data['register_policy']) {
|
||||
case 'open':
|
||||
$serverdata['register_policy'] = Register::OPEN;
|
||||
break;
|
||||
case 'closed':
|
||||
$serverdata['register_policy'] = Register::CLOSED;
|
||||
break;
|
||||
case 'approve':
|
||||
$serverdata['register_policy'] = Register::APPROVE;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $serverdata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the server contains a valid host meta file
|
||||
*
|
||||
|
|
@ -1629,7 +1749,7 @@ class GServer
|
|||
private static function validHostMeta(string $url): bool
|
||||
{
|
||||
$xrd_timeout = DI::config()->get('system', 'xrd_timeout');
|
||||
$curlResult = DI::httpClient()->get($url . Probe::HOST_META, HttpClientAccept::XRD_XML, [HttpClientOptions::TIMEOUT => $xrd_timeout]);
|
||||
$curlResult = DI::httpClient()->get($url . Probe::HOST_META, HttpClientAccept::XRD_XML, [HttpClientOptions::TIMEOUT => $xrd_timeout, HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]);
|
||||
if (!$curlResult->isSuccess()) {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -1728,7 +1848,7 @@ class GServer
|
|||
{
|
||||
$serverdata['poco'] = '';
|
||||
|
||||
$curlResult = DI::httpClient()->get($url . '/poco', HttpClientAccept::JSON);
|
||||
$curlResult = DI::httpClient()->get($url . '/poco', HttpClientAccept::JSON, [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]);
|
||||
if (!$curlResult->isSuccess()) {
|
||||
return $serverdata;
|
||||
}
|
||||
|
|
@ -1758,7 +1878,7 @@ class GServer
|
|||
*/
|
||||
public static function checkMastodonDirectory(string $url, array $serverdata): array
|
||||
{
|
||||
$curlResult = DI::httpClient()->get($url . '/api/v1/directory?limit=1', HttpClientAccept::JSON);
|
||||
$curlResult = DI::httpClient()->get($url . '/api/v1/directory?limit=1', HttpClientAccept::JSON, [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]);
|
||||
if (!$curlResult->isSuccess()) {
|
||||
return $serverdata;
|
||||
}
|
||||
|
|
@ -1785,7 +1905,7 @@ class GServer
|
|||
*/
|
||||
private static function detectPeertube(string $url, array $serverdata): array
|
||||
{
|
||||
$curlResult = DI::httpClient()->get($url . '/api/v1/config', HttpClientAccept::JSON);
|
||||
$curlResult = DI::httpClient()->get($url . '/api/v1/config', HttpClientAccept::JSON, [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]);
|
||||
if (!$curlResult->isSuccess() || ($curlResult->getBodyString() == '')) {
|
||||
return $serverdata;
|
||||
}
|
||||
|
|
@ -1833,7 +1953,7 @@ class GServer
|
|||
*/
|
||||
private static function detectNextcloud(string $url, array $serverdata, bool $validHostMeta): array
|
||||
{
|
||||
$curlResult = DI::httpClient()->get($url . '/status.php', HttpClientAccept::JSON);
|
||||
$curlResult = DI::httpClient()->get($url . '/status.php', HttpClientAccept::JSON, [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]);
|
||||
if (!$curlResult->isSuccess() || ($curlResult->getBodyString() == '')) {
|
||||
return $serverdata;
|
||||
}
|
||||
|
|
@ -1869,7 +1989,7 @@ class GServer
|
|||
*/
|
||||
private static function fetchWeeklyUsage(string $url, array $serverdata): array
|
||||
{
|
||||
$curlResult = DI::httpClient()->get($url . '/api/v1/instance/activity', HttpClientAccept::JSON);
|
||||
$curlResult = DI::httpClient()->get($url . '/api/v1/instance/activity', HttpClientAccept::JSON, [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]);
|
||||
if (!$curlResult->isSuccess() || ($curlResult->getBodyString() == '')) {
|
||||
return $serverdata;
|
||||
}
|
||||
|
|
@ -1909,7 +2029,7 @@ class GServer
|
|||
*/
|
||||
private static function detectMastodonAlikes(string $url, array $serverdata): array
|
||||
{
|
||||
$curlResult = DI::httpClient()->get($url . '/api/v1/instance', HttpClientAccept::JSON);
|
||||
$curlResult = DI::httpClient()->get($url . '/api/v1/instance', HttpClientAccept::JSON, [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]);
|
||||
if (!$curlResult->isSuccess() || ($curlResult->getBodyString() == '')) {
|
||||
return $serverdata;
|
||||
}
|
||||
|
|
@ -1981,7 +2101,7 @@ class GServer
|
|||
*/
|
||||
private static function detectHubzilla(string $url, array $serverdata): array
|
||||
{
|
||||
$curlResult = DI::httpClient()->get($url . '/api/statusnet/config.json', HttpClientAccept::JSON);
|
||||
$curlResult = DI::httpClient()->get($url . '/api/statusnet/config.json', HttpClientAccept::JSON, [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]);
|
||||
if (!$curlResult->isSuccess() || ($curlResult->getBodyString() == '')) {
|
||||
return $serverdata;
|
||||
}
|
||||
|
|
@ -2078,7 +2198,7 @@ class GServer
|
|||
private static function detectGNUSocial(string $url, array $serverdata): array
|
||||
{
|
||||
// Test for GNU Social
|
||||
$curlResult = DI::httpClient()->get($url . '/api/gnusocial/version.json', HttpClientAccept::JSON);
|
||||
$curlResult = DI::httpClient()->get($url . '/api/gnusocial/version.json', HttpClientAccept::JSON, [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]);
|
||||
if ($curlResult->isSuccess() && ($curlResult->getBodyString() != '{"error":"not implemented"}') &&
|
||||
($curlResult->getBodyString() != '') && (strlen($curlResult->getBodyString()) < 30)) {
|
||||
$serverdata['platform'] = 'gnusocial';
|
||||
|
|
@ -2096,12 +2216,12 @@ class GServer
|
|||
}
|
||||
|
||||
// Test for Statusnet
|
||||
$curlResult = DI::httpClient()->get($url . '/api/statusnet/version.json', HttpClientAccept::JSON);
|
||||
$curlResult = DI::httpClient()->get($url . '/api/statusnet/version.json', HttpClientAccept::JSON, [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]);
|
||||
if ($curlResult->isSuccess() && ($curlResult->getBodyString() != '{"error":"not implemented"}') &&
|
||||
($curlResult->getBodyString() != '') && (strlen($curlResult->getBodyString()) < 30)) {
|
||||
|
||||
// Remove junk that some GNU Social servers return
|
||||
$serverdata['version'] = str_replace(chr(239).chr(187).chr(191), '', $curlResult->getBodyString());
|
||||
$serverdata['version'] = str_replace(chr(239) . chr(187) . chr(191), '', $curlResult->getBodyString());
|
||||
$serverdata['version'] = str_replace(["\r", "\n", "\t"], '', $serverdata['version']);
|
||||
$serverdata['version'] = trim($serverdata['version'], '"');
|
||||
|
||||
|
|
@ -2134,9 +2254,9 @@ class GServer
|
|||
{
|
||||
// There is a bug in some versions of Friendica that will return an ActivityStream actor when the content type "application/json" is requested.
|
||||
// Because of this me must not use ACCEPT_JSON here.
|
||||
$curlResult = DI::httpClient()->get($url . '/friendica/json');
|
||||
$curlResult = DI::httpClient()->get($url . '/friendica/json', HttpClientAccept::DEFAULT, [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]);
|
||||
if (!$curlResult->isSuccess()) {
|
||||
$curlResult = DI::httpClient()->get($url . '/friendika/json');
|
||||
$curlResult = DI::httpClient()->get($url . '/friendika/json', HttpClientAccept::DEFAULT, [HttpClientOptions::REQUEST => HttpClientRequest::SERVERINFO]);
|
||||
$friendika = true;
|
||||
$platform = 'Friendika';
|
||||
} else {
|
||||
|
|
@ -2188,7 +2308,7 @@ class GServer
|
|||
break;
|
||||
default:
|
||||
Logger::info('Register policy is invalid', ['policy' => $register_policy, 'server' => $url]);
|
||||
$serverdata['register_policy'] = Register::CLOSED;
|
||||
$serverdata['register_policy'] = Register::CLOSED;
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -2447,7 +2567,7 @@ class GServer
|
|||
$protocols = ['activitypub', 'diaspora', 'dfrn', 'ostatus'];
|
||||
foreach ($protocols as $protocol) {
|
||||
$query = '{nodes(protocol:"' . $protocol . '"){host}}';
|
||||
$curlResult = DI::httpClient()->fetch('https://the-federation.info/graphql?query=' . urlencode($query), HttpClientAccept::JSON);
|
||||
$curlResult = DI::httpClient()->fetch('https://the-federation.info/graphql?query=' . urlencode($query), HttpClientAccept::JSON, 0, '', HttpClientRequest::SERVERDISCOVER);
|
||||
if (!empty($curlResult)) {
|
||||
$data = json_decode($curlResult, true);
|
||||
if (!empty($data['data']['nodes'])) {
|
||||
|
|
@ -2464,7 +2584,7 @@ class GServer
|
|||
|
||||
if (!empty($accesstoken)) {
|
||||
$api = 'https://instances.social/api/1.0/instances/list?count=0';
|
||||
$curlResult = DI::httpClient()->get($api, HttpClientAccept::JSON, [HttpClientOptions::HEADERS => ['Authorization' => ['Bearer ' . $accesstoken]]]);
|
||||
$curlResult = DI::httpClient()->get($api, HttpClientAccept::JSON, [HttpClientOptions::HEADERS => ['Authorization' => ['Bearer ' . $accesstoken], HttpClientOptions::REQUEST => HttpClientRequest::SERVERDISCOVER]]);
|
||||
if ($curlResult->isSuccess()) {
|
||||
$servers = json_decode($curlResult->getBodyString(), true);
|
||||
|
||||
|
|
@ -2480,6 +2600,29 @@ class GServer
|
|||
DI::keyValue()->set('poco_last_federation_discovery', time());
|
||||
}
|
||||
|
||||
public static function updateFromProbeArray(array $data)
|
||||
{
|
||||
if (empty($data['gsid']) || empty($data['openwebauth'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$gserver = DBA::selectFirst('gserver', ['url', 'openwebauth'], ['id' => $data['gsid']]);
|
||||
if (!DBA::isResult($gserver)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($data['openwebauth'] == $gserver['openwebauth']) {
|
||||
return;
|
||||
}
|
||||
|
||||
$serverdata = self::getZotData($gserver['url'], []);
|
||||
if (empty($serverdata)) {
|
||||
$serverdata = ['openwebauth' => $data['openwebauth']];
|
||||
}
|
||||
|
||||
self::update($serverdata, ['id' => $data['gsid']]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the protocol for the given server
|
||||
*
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ use Friendica\DI;
|
|||
use Friendica\Model\Post\Category;
|
||||
use Friendica\Network\HTTPClient\Client\HttpClientAccept;
|
||||
use Friendica\Network\HTTPClient\Client\HttpClientOptions;
|
||||
use Friendica\Network\HTTPClient\Client\HttpClientRequest;
|
||||
use Friendica\Network\HTTPException\InternalServerErrorException;
|
||||
use Friendica\Network\HTTPException\ServiceUnavailableException;
|
||||
use Friendica\Protocol\Activity;
|
||||
|
|
@ -108,7 +109,7 @@ class Item
|
|||
'owner-id', 'owner-link', 'owner-alias', 'owner-name', 'owner-avatar', 'owner-network', 'owner-contact-type', 'owner-updated', 'owner-gsid',
|
||||
'causer-id', 'causer-link', 'causer-alias', 'causer-name', 'causer-avatar', 'causer-contact-type', 'causer-network', 'causer-gsid',
|
||||
'contact-id', 'contact-uid', 'contact-link', 'contact-name', 'contact-avatar',
|
||||
'writable', 'self', 'cid', 'alias',
|
||||
'writable', 'restrictions', 'self', 'cid', 'alias', 'post-reason',
|
||||
'event-created', 'event-edited', 'event-start', 'event-finish',
|
||||
'event-summary', 'event-desc', 'event-location', 'event-type',
|
||||
'event-nofinish', 'event-ignore', 'event-id',
|
||||
|
|
@ -121,7 +122,7 @@ class Item
|
|||
const DELIVER_FIELDLIST = [
|
||||
'uid', 'id', 'parent', 'uri-id', 'uri', 'thr-parent', 'parent-uri', 'guid',
|
||||
'parent-guid', 'conversation', 'received', 'created', 'edited', 'verb', 'object-type', 'object', 'target',
|
||||
'private', 'title', 'body', 'raw-body', 'language', 'location', 'coord', 'app', 'sensitive',
|
||||
'private', 'title', 'content-warning', 'body', 'raw-body', 'language', 'location', 'coord', 'app', 'sensitive',
|
||||
'inform', 'deleted', 'extid', 'post-type', 'post-reason', 'gravity',
|
||||
'allow_cid', 'allow_gid', 'deny_cid', 'deny_gid',
|
||||
'author-id', 'author-addr', 'author-link', 'author-name', 'author-avatar', 'owner-id', 'owner-link', 'contact-uid',
|
||||
|
|
@ -168,6 +169,11 @@ class Item
|
|||
const GRAVITY_COMMENT = 6;
|
||||
const GRAVITY_UNKNOWN = 9;
|
||||
|
||||
// Restrictions
|
||||
const CANT_REPLY = 1;
|
||||
const CANT_LIKE = 2;
|
||||
const CANT_ANNOUNCE = 4;
|
||||
|
||||
/**
|
||||
* Update existing item entries
|
||||
*
|
||||
|
|
@ -192,6 +198,10 @@ class Item
|
|||
$fields['external-id'] = ItemURI::getIdByURI($fields['extid']);
|
||||
}
|
||||
|
||||
if (!empty($fields['replies'])) {
|
||||
$fields['replies-id'] = ItemURI::getIdByURI($fields['replies']);
|
||||
}
|
||||
|
||||
if (!empty($fields['verb'])) {
|
||||
$fields['vid'] = Verb::getID($fields['verb']);
|
||||
}
|
||||
|
|
@ -409,8 +419,24 @@ class Item
|
|||
self::markForDeletion(['parent' => $item['parent'], 'deleted' => false], $priority);
|
||||
}
|
||||
|
||||
if ($item['uid'] == 0 && $item['gravity'] == self::GRAVITY_PARENT) {
|
||||
$posts = DI::keyValue()->get('nodeinfo_total_posts') ?? 0;
|
||||
DI::keyValue()->set('nodeinfo_total_posts', $posts - 1);
|
||||
} elseif ($item['uid'] == 0 && $item['gravity'] == self::GRAVITY_COMMENT) {
|
||||
$comments = DI::keyValue()->get('nodeinfo_total_comments') ?? 0;
|
||||
DI::keyValue()->set('nodeinfo_total_comments', $comments - 1);
|
||||
}
|
||||
|
||||
// Is it our comment and/or our thread?
|
||||
if (($item['origin'] || $parent['origin']) && ($item['uid'] != 0)) {
|
||||
if ($item['origin'] && $item['gravity'] == self::GRAVITY_PARENT) {
|
||||
$posts = DI::keyValue()->get('nodeinfo_local_posts') ?? 0;
|
||||
DI::keyValue()->set('nodeinfo_local_posts', $posts - 1);
|
||||
} elseif ($item['origin'] && $item['gravity'] == self::GRAVITY_COMMENT) {
|
||||
$comments = DI::keyValue()->get('nodeinfo_local_comments') ?? 0;
|
||||
DI::keyValue()->set('nodeinfo_local_comments', $comments - 1);
|
||||
}
|
||||
|
||||
// When we delete the original post we will delete all existing copies on the server as well
|
||||
self::markForDeletion(['uri-id' => $item['uri-id'], 'deleted' => false], $priority);
|
||||
|
||||
|
|
@ -530,9 +556,9 @@ class Item
|
|||
}
|
||||
|
||||
if (!empty($item['causer-id']) && Contact::isSharing($item['causer-id'], $item['uid'], true)) {
|
||||
$cdata = Contact::getPublicAndUserContactID($item['causer-id'], $item['uid']);
|
||||
if (!empty($cdata['user'])) {
|
||||
return $cdata['user'];
|
||||
$ucid = Contact::getUserContactId($item['causer-id'], $item['uid']);
|
||||
if ($ucid) {
|
||||
return $ucid;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -759,7 +785,7 @@ class Item
|
|||
{
|
||||
$fields = [
|
||||
'uid', 'uri', 'parent-uri', 'id', 'deleted',
|
||||
'uri-id', 'parent-uri-id',
|
||||
'uri-id', 'parent-uri-id', 'restrictions', 'verb',
|
||||
'allow_cid', 'allow_gid', 'deny_cid', 'deny_gid',
|
||||
'wall', 'private', 'origin', 'author-id'
|
||||
];
|
||||
|
|
@ -783,6 +809,11 @@ class Item
|
|||
return [];
|
||||
}
|
||||
|
||||
if (self::hasRestrictions($item, $parent['author-id'], $parent['restrictions'])) {
|
||||
Logger::notice('Restrictions apply - ignoring item', ['restrictions' => $parent['restrictions'], 'verb' => $parent['verb'], 'uri-id' => $item['uri-id'], 'thr-parent-id' => $item['thr-parent-id'], 'uid' => $item['uid']]);
|
||||
return [];
|
||||
}
|
||||
|
||||
if ($parent['uri-id'] == $parent['parent-uri-id']) {
|
||||
return $parent;
|
||||
}
|
||||
|
|
@ -838,7 +869,7 @@ class Item
|
|||
private static function prepareOriginPost(array $item): array
|
||||
{
|
||||
$item = DI::contentItem()->initializePost($item);
|
||||
$item = DI::contentItem()->finalizePost($item);
|
||||
$item = DI::contentItem()->finalizePost($item, false);
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
|
@ -1034,7 +1065,7 @@ class Item
|
|||
$item['deny_gid'] = $store_permissions ? $toplevel_parent['deny_gid'] : '';
|
||||
}
|
||||
|
||||
$parent_origin = $toplevel_parent['origin'];
|
||||
$parent_origin = $toplevel_parent['origin'];
|
||||
|
||||
// Don't federate received participation messages
|
||||
if ($item['verb'] != Activity::FOLLOW) {
|
||||
|
|
@ -1066,6 +1097,10 @@ class Item
|
|||
$parent_id = 0;
|
||||
$parent_origin = $item['origin'];
|
||||
|
||||
if ($item['wall'] && empty($item['context'])) {
|
||||
$item['context'] = $item['parent-uri'] . '#context';
|
||||
}
|
||||
|
||||
if ($item['wall'] && empty($item['conversation'])) {
|
||||
$item['conversation'] = $item['parent-uri'] . '#context';
|
||||
}
|
||||
|
|
@ -1078,8 +1113,6 @@ class Item
|
|||
) {
|
||||
$item['object-type'] = Activity\ObjectType::IMAGE;
|
||||
}
|
||||
|
||||
$item = DI::contentItem()->moveAttachmentsFromBodyToAttach($item);
|
||||
}
|
||||
|
||||
$item['parent-uri-id'] = ItemURI::getIdByURI($item['parent-uri']);
|
||||
|
|
@ -1089,6 +1122,10 @@ class Item
|
|||
$item['conversation-id'] = ItemURI::getIdByURI($item['conversation']);
|
||||
}
|
||||
|
||||
if (!empty($item['context']) && empty($item['context-id'])) {
|
||||
$item['context-id'] = ItemURI::getIdByURI($item['context']);
|
||||
}
|
||||
|
||||
// Is this item available in the global items (with uid=0)?
|
||||
if ($item['uid'] == 0) {
|
||||
$item['global'] = true;
|
||||
|
|
@ -1156,6 +1193,10 @@ class Item
|
|||
$item['external-id'] = ItemURI::getIdByURI($item['extid']);
|
||||
}
|
||||
|
||||
if (!empty($item['replies'])) {
|
||||
$item['replies-id'] = ItemURI::getIdByURI($item['replies']);
|
||||
}
|
||||
|
||||
if ($item['verb'] == Activity::ANNOUNCE) {
|
||||
self::setOwnerforResharedItem($item);
|
||||
}
|
||||
|
|
@ -1325,6 +1366,16 @@ class Item
|
|||
return 0;
|
||||
}
|
||||
|
||||
if ($posted_item['origin'] && $posted_item['gravity'] == self::GRAVITY_PARENT) {
|
||||
$posts = (int)(DI::keyValue()->get('nodeinfo_local_posts') ?? 0);
|
||||
DI::keyValue()->set('nodeinfo_local_posts', $posts + 1);
|
||||
} elseif ($posted_item['origin'] && $posted_item['gravity'] == self::GRAVITY_COMMENT) {
|
||||
$comments = (int)(DI::keyValue()->get('nodeinfo_local_comments') ?? 0);
|
||||
DI::keyValue()->set('nodeinfo_local_comments', $comments + 1);
|
||||
}
|
||||
|
||||
Post\Origin::insert($posted_item);
|
||||
|
||||
// update the commented timestamp on the parent
|
||||
if (DI::config()->get('system', 'like_no_comment')) {
|
||||
// Update when it is a comment
|
||||
|
|
@ -1412,6 +1463,14 @@ class Item
|
|||
}
|
||||
|
||||
if ($inserted) {
|
||||
if ($posted_item['gravity'] == self::GRAVITY_PARENT) {
|
||||
$posts = (int)(DI::keyValue()->get('nodeinfo_total_posts') ?? 0);
|
||||
DI::keyValue()->set('nodeinfo_total_posts', $posts + 1);
|
||||
} elseif ($posted_item['gravity'] == self::GRAVITY_COMMENT) {
|
||||
$comments = (int)(DI::keyValue()->get('nodeinfo_total_comments') ?? 0);
|
||||
DI::keyValue()->set('nodeinfo_total_comments', $comments + 1);
|
||||
}
|
||||
|
||||
// Fill the cache with the rendered content.
|
||||
if (in_array($posted_item['gravity'], [self::GRAVITY_PARENT, self::GRAVITY_COMMENT])) {
|
||||
self::updateDisplayCache($posted_item['uri-id']);
|
||||
|
|
@ -1439,6 +1498,27 @@ class Item
|
|||
return $post_user_id;
|
||||
}
|
||||
|
||||
private static function hasRestrictions(array $item, int $author_id, int $restrictions = null): bool
|
||||
{
|
||||
if (empty($restrictions) || ($author_id == $item['author-id'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (($restrictions & self::CANT_REPLY) && ($item['verb'] == Activity::POST)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (($restrictions & self::CANT_ANNOUNCE) && ($item['verb'] == Activity::ANNOUNCE)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (($restrictions & self::CANT_LIKE) && in_array($item['verb'], [Activity::LIKE, Activity::DISLIKE, Activity::ATTEND, Activity::ATTENDMAYBE, Activity::ATTENDNO])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static function reshareChannelPost(int $uri_id, int $reshare_id = 0)
|
||||
{
|
||||
if (!DI::config()->get('system', 'allow_relay_channels')) {
|
||||
|
|
@ -2552,12 +2632,12 @@ class Item
|
|||
return;
|
||||
}
|
||||
|
||||
$cdata = Contact::getPublicAndUserContactID($item['author-id'], $item['uid']);
|
||||
if (empty($cdata['user']) || ($cdata['user'] != $item['contact-id'])) {
|
||||
$ucid = Contact::getUserContactId($item['author-id'], $item['uid']);
|
||||
if (!$ucid || ($ucid != $item['contact-id'])) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!DBA::exists('contact', ['id' => $cdata['user'], 'remote_self' => LocalRelationship::MIRROR_NATIVE_RESHARE])) {
|
||||
if (!DBA::exists('contact', ['id' => $ucid, 'remote_self' => LocalRelationship::MIRROR_NATIVE_RESHARE])) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -3315,7 +3395,6 @@ class Item
|
|||
$item['tags'] = $tags['tags'];
|
||||
$item['hashtags'] = $tags['hashtags'];
|
||||
$item['mentions'] = $tags['mentions'];
|
||||
$sensitive = $item['sensitive'] && !DI::pConfig()->get($uid, 'system', 'display_sensitive', false);
|
||||
|
||||
if (!$is_preview) {
|
||||
$item['body'] = preg_replace("#\s*\[attachment .*?].*?\[/attachment]\s*#ism", "\n", $item['body']);
|
||||
|
|
@ -3383,11 +3462,11 @@ class Item
|
|||
$shared_links = array_merge($shared_links, $sharedSplitAttachments['visual']->column('url'));
|
||||
$shared_links = array_merge($shared_links, $sharedSplitAttachments['link']->column('url'));
|
||||
$shared_links = array_merge($shared_links, $sharedSplitAttachments['additional']->column('url'));
|
||||
$item['body'] = self::replaceVisualAttachments($sharedSplitAttachments['visual'], $item['body'], $sensitive);
|
||||
$item['body'] = self::replaceVisualAttachments($sharedSplitAttachments['visual'], $item['body']);
|
||||
}
|
||||
|
||||
$itemSplitAttachments = DI::postMediaRepository()->splitAttachments($item['uri-id'], $shared_links, $item['has-media'] ?? false);
|
||||
$item['body'] = self::replaceVisualAttachments($itemSplitAttachments['visual'], $item['body'] ?? '', $sensitive);
|
||||
$item['body'] = self::replaceVisualAttachments($itemSplitAttachments['visual'], $item['body'] ?? '');
|
||||
|
||||
self::putInCache($item);
|
||||
$item['body'] = $body;
|
||||
|
|
@ -3408,8 +3487,8 @@ class Item
|
|||
$filter_reasons[] = DI::l10n()->t('Content from %s is collapsed', $item['author-name']);
|
||||
}
|
||||
|
||||
if (!empty($item['content-warning']) && (!$uid || !DI::pConfig()->get($uid, 'system', 'disable_cw', false))) {
|
||||
$filter_reasons[] = DI::l10n()->t('Content warning: %s', $item['content-warning']);
|
||||
if ($item['sensitive'] && (!$uid || !DI::pConfig()->get($uid, 'system', 'disable_cw', false))) {
|
||||
$filter_reasons[] = DI::l10n()->t('Sensitive content');
|
||||
}
|
||||
|
||||
$item['attachments'] = $itemSplitAttachments;
|
||||
|
|
@ -3442,9 +3521,9 @@ class Item
|
|||
}
|
||||
|
||||
if (!empty($sharedSplitAttachments)) {
|
||||
$s = self::addGallery($s, $sharedSplitAttachments['visual'], $sensitive);
|
||||
$s = self::addVisualAttachments($sharedSplitAttachments['visual'], $shared_item, $s, true, $sensitive);
|
||||
$s = self::addLinkAttachment($shared_uri_id ?: $item['uri-id'], $sharedSplitAttachments, $body, $s, true, $quote_shared_links, $sensitive);
|
||||
$s = self::addGallery($s, $sharedSplitAttachments['visual']);
|
||||
$s = self::addVisualAttachments($sharedSplitAttachments['visual'], $shared_item, $s, true);
|
||||
$s = self::addLinkAttachment($shared_uri_id ?: $item['uri-id'], $sharedSplitAttachments, $body, $s, true, $quote_shared_links);
|
||||
$s = self::addNonVisualAttachments($sharedSplitAttachments['additional'], $item, $s, true);
|
||||
$body = BBCode::removeSharedData($body);
|
||||
}
|
||||
|
|
@ -3455,9 +3534,9 @@ class Item
|
|||
$s = substr($s, 0, $pos);
|
||||
}
|
||||
|
||||
$s = self::addGallery($s, $itemSplitAttachments['visual'], $sensitive);
|
||||
$s = self::addVisualAttachments($itemSplitAttachments['visual'], $item, $s, false, $sensitive);
|
||||
$s = self::addLinkAttachment($item['uri-id'], $itemSplitAttachments, $body, $s, false, $shared_links, $sensitive);
|
||||
$s = self::addGallery($s, $itemSplitAttachments['visual']);
|
||||
$s = self::addVisualAttachments($itemSplitAttachments['visual'], $item, $s, false);
|
||||
$s = self::addLinkAttachment($item['uri-id'], $itemSplitAttachments, $body, $s, false, $shared_links);
|
||||
$s = self::addNonVisualAttachments($itemSplitAttachments['additional'], $item, $s, false);
|
||||
$s = self::addQuestions($item, $s);
|
||||
|
||||
|
|
@ -3491,10 +3570,9 @@ class Item
|
|||
*
|
||||
* @param string $s
|
||||
* @param PostMedias $PostMedias
|
||||
* @param bool $sensitive
|
||||
* @return string
|
||||
*/
|
||||
private static function addGallery(string $s, PostMedias $PostMedias, bool $sensitive): string
|
||||
private static function addGallery(string $s, PostMedias $PostMedias): string
|
||||
{
|
||||
foreach ($PostMedias as $PostMedia) {
|
||||
if (!$PostMedia->preview || ($PostMedia->type !== Post\Media::IMAGE)) {
|
||||
|
|
@ -3504,10 +3582,9 @@ class Item
|
|||
if ($PostMedia->hasDimensions()) {
|
||||
$pattern = '#<a href="' . preg_quote($PostMedia->url) . '">(.*?)"></a>#';
|
||||
|
||||
$s = preg_replace_callback($pattern, function () use ($PostMedia, $sensitive) {
|
||||
$s = preg_replace_callback($pattern, function () use ($PostMedia) {
|
||||
return Renderer::replaceMacros(Renderer::getMarkupTemplate('content/image/single_with_height_allocation.tpl'), [
|
||||
'$image' => $PostMedia,
|
||||
'$sensitive' => $sensitive,
|
||||
'$allocated_height' => $PostMedia->getAllocatedHeight(),
|
||||
'$allocated_max_width' => ($PostMedia->previewWidth ?? $PostMedia->width) . 'px',
|
||||
]);
|
||||
|
|
@ -3576,10 +3653,9 @@ class Item
|
|||
*
|
||||
* @param PostMedias $PostMedias
|
||||
* @param string $body
|
||||
* @param bool $sensitive
|
||||
* @return string modified body
|
||||
*/
|
||||
private static function replaceVisualAttachments(PostMedias $PostMedias, string $body, bool $sensitive): string
|
||||
private static function replaceVisualAttachments(PostMedias $PostMedias, string $body): string
|
||||
{
|
||||
DI::profiler()->startRecording('rendering');
|
||||
|
||||
|
|
@ -3588,7 +3664,7 @@ class Item
|
|||
if (DI::baseUrl()->isLocalUri($PostMedia->preview)) {
|
||||
continue;
|
||||
}
|
||||
$proxy = DI::baseUrl() . $PostMedia->getPreviewPath(Proxy::SIZE_LARGE, $sensitive);
|
||||
$proxy = DI::baseUrl() . $PostMedia->getPreviewPath(Proxy::SIZE_LARGE);
|
||||
$search = ['[img=' . $PostMedia->preview . ']', ']' . $PostMedia->preview . '[/img]'];
|
||||
$replace = ['[img=' . $proxy . ']', ']' . $proxy . '[/img]'];
|
||||
|
||||
|
|
@ -3597,7 +3673,7 @@ class Item
|
|||
if (DI::baseUrl()->isLocalUri($PostMedia->url)) {
|
||||
continue;
|
||||
}
|
||||
$proxy = DI::baseUrl() . $PostMedia->getPreviewPath(Proxy::SIZE_LARGE, $sensitive);
|
||||
$proxy = DI::baseUrl() . $PostMedia->getPreviewPath(Proxy::SIZE_LARGE);
|
||||
$search = ['[img=' . $PostMedia->url . ']', ']' . $PostMedia->url . '[/img]'];
|
||||
$replace = ['[img=' . $proxy . ']', ']' . $proxy . '[/img]'];
|
||||
|
||||
|
|
@ -3615,11 +3691,10 @@ class Item
|
|||
* @param array $item
|
||||
* @param string $content
|
||||
* @param bool $shared
|
||||
* @param bool $sensitive
|
||||
* @return string modified content
|
||||
* @throws ServiceUnavailableException
|
||||
*/
|
||||
private static function addVisualAttachments(PostMedias $PostMedias, array $item, string $content, bool $shared, bool $sensitive): string
|
||||
private static function addVisualAttachments(PostMedias $PostMedias, array $item, string $content, bool $shared): string
|
||||
{
|
||||
DI::profiler()->startRecording('rendering');
|
||||
$leading = '';
|
||||
|
|
@ -3634,7 +3709,7 @@ class Item
|
|||
|
||||
if ($PostMedia->mimetype->type == 'image' || $PostMedia->preview) {
|
||||
$preview_size = Proxy::SIZE_MEDIUM;
|
||||
$preview_url = DI::baseUrl() . $PostMedia->getPreviewPath($preview_size, $sensitive);
|
||||
$preview_url = DI::baseUrl() . $PostMedia->getPreviewPath($preview_size);
|
||||
} else {
|
||||
$preview_size = 0;
|
||||
$preview_url = '';
|
||||
|
|
@ -3655,13 +3730,14 @@ class Item
|
|||
/// @todo Move the template to /content as well
|
||||
$media = Renderer::replaceMacros(Renderer::getMarkupTemplate('video_top.tpl'), [
|
||||
'$video' => [
|
||||
'id' => $PostMedia->id,
|
||||
'src' => (string)$PostMedia->url,
|
||||
'name' => $PostMedia->name ?: $PostMedia->url,
|
||||
'preview' => $preview_url,
|
||||
'mime' => (string)$PostMedia->mimetype,
|
||||
'height' => $height,
|
||||
'width' => $width,
|
||||
'id' => $PostMedia->id,
|
||||
'src' => (string)$PostMedia->url,
|
||||
'name' => $PostMedia->name ?: $PostMedia->url,
|
||||
'preview' => $preview_url,
|
||||
'mime' => (string)$PostMedia->mimetype,
|
||||
'height' => $height,
|
||||
'width' => $width,
|
||||
'description' => $PostMedia->description,
|
||||
],
|
||||
]);
|
||||
if (($item['post-type'] ?? null) == Item::PT_VIDEO) {
|
||||
|
|
@ -3728,12 +3804,11 @@ class Item
|
|||
* @param string $content
|
||||
* @param bool $shared
|
||||
* @param array $ignore_links A list of URLs to ignore
|
||||
* @param bool $sensitive
|
||||
* @return string modified content
|
||||
* @throws InternalServerErrorException
|
||||
* @throws ServiceUnavailableException
|
||||
*/
|
||||
private static function addLinkAttachment(int $uriid, array $attachments, string $body, string $content, bool $shared, array $ignore_links, bool $sensitive): string
|
||||
private static function addLinkAttachment(int $uriid, array $attachments, string $body, string $content, bool $shared, array $ignore_links): string
|
||||
{
|
||||
DI::profiler()->startRecording('rendering');
|
||||
// Don't show a preview when there is a visual attachment (audio or video)
|
||||
|
|
@ -3776,9 +3851,9 @@ class Item
|
|||
|
||||
if ($preview && $attachment->preview) {
|
||||
if ($attachment->previewWidth >= 500) {
|
||||
$data['image'] = DI::baseUrl() . $attachment->getPreviewPath(Proxy::SIZE_MEDIUM, $sensitive);
|
||||
$data['image'] = DI::baseUrl() . $attachment->getPreviewPath(Proxy::SIZE_MEDIUM);
|
||||
} else {
|
||||
$data['preview'] = DI::baseUrl() . $attachment->getPreviewPath(Proxy::SIZE_MEDIUM, $sensitive);
|
||||
$data['preview'] = DI::baseUrl() . $attachment->getPreviewPath(Proxy::SIZE_MEDIUM);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -3803,11 +3878,6 @@ class Item
|
|||
$data = BBCode::getAttachmentData($match[1]);
|
||||
}
|
||||
|
||||
if ($sensitive) {
|
||||
$data['image'] = '';
|
||||
$data['preview'] = '';
|
||||
}
|
||||
|
||||
DI::profiler()->stopRecording();
|
||||
|
||||
if (isset($data['url']) && !in_array(strtolower($data['url']), $ignore_links)) {
|
||||
|
|
@ -3830,6 +3900,11 @@ class Item
|
|||
$preview_mode = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'system', 'preview_mode', BBCode::PREVIEW_LARGE);
|
||||
if ($preview_mode != BBCode::PREVIEW_NONE) {
|
||||
$rendered = BBCode::convertAttachment('', BBCode::INTERNAL, false, $data, $uriid, $preview_mode);
|
||||
} elseif (!self::containsLink($content, $data['url'], Post\Media::HTML)) {
|
||||
$rendered = Renderer::replaceMacros(Renderer::getMarkupTemplate('content/link.tpl'), [
|
||||
'$url' => $data['url'],
|
||||
'$title' => $data['title'],
|
||||
]);
|
||||
} else {
|
||||
$rendered = '';
|
||||
}
|
||||
|
|
@ -3950,10 +4025,17 @@ class Item
|
|||
{
|
||||
if (!empty($item['plink']) && Network::isValidHttpUrl($item['plink'])) {
|
||||
$plink = $item['plink'];
|
||||
} elseif (!empty($item['uri']) && Network::isValidHttpUrl($item['uri']) && !Network::isLocalLink($item['uri'])) {
|
||||
} elseif (!empty($item['uri']) && Network::isValidHttpUrl($item['uri']) && !DI::baseUrl()->isLocalUrl($item['uri'])) {
|
||||
$plink = $item['uri'];
|
||||
}
|
||||
|
||||
if (($item['post-reason'] == self::PR_ANNOUNCEMENT) && ($item['owner-contact-type'] == Contact::TYPE_COMMUNITY) && ($item['owner-network'] == Protocol::DFRN)) {
|
||||
$contact = Contact::getById($item['owner-id'], ['baseurl']);
|
||||
if (!empty($contact['baseurl'])) {
|
||||
$plink = $contact['baseurl'] . '/display/' . $item['guid'];
|
||||
}
|
||||
}
|
||||
|
||||
if (DI::userSession()->getLocalUserId()) {
|
||||
$ret = [
|
||||
'href' => "display/" . $item['guid'],
|
||||
|
|
@ -4084,6 +4166,10 @@ class Item
|
|||
return $item_id;
|
||||
}
|
||||
|
||||
if (ActivityPub\Processor::alreadyKnown($uri, '')) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$hookData = [
|
||||
'uri' => $uri,
|
||||
'uid' => $uid,
|
||||
|
|
@ -4096,9 +4182,14 @@ class Item
|
|||
return is_numeric($hookData['item_id']) ? $hookData['item_id'] : 0;
|
||||
}
|
||||
|
||||
$curlResult = DI::httpClient()->head($uri, [HttpClientOptions::ACCEPT_CONTENT => HttpClientAccept::JSON_AS]);
|
||||
if (HTTPSignature::isValidContentType($curlResult->getContentType(), $uri)) {
|
||||
$fetched_uri = ActivityPub\Processor::fetchMissingActivity($uri, [], '', $completion, $uid);
|
||||
try {
|
||||
$curlResult = DI::httpClient()->head($uri, [HttpClientOptions::ACCEPT_CONTENT => HttpClientAccept::JSON_AS, HttpClientOptions::REQUEST => HttpClientRequest::ACTIVITYPUB]);
|
||||
if (HTTPSignature::isValidContentType($curlResult->getContentType(), $uri)) {
|
||||
$fetched_uri = ActivityPub\Processor::fetchMissingActivity($uri, [], '', $completion, $uid);
|
||||
}
|
||||
} catch (\Throwable $th) {
|
||||
Logger::info('Invalid link', ['uid' => $uid, 'uri' => $uri, 'code' => $th->getCode(), 'message' => $th->getMessage()]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!empty($fetched_uri)) {
|
||||
|
|
@ -4174,4 +4265,22 @@ class Item
|
|||
Logger::warning('Post does not exist although it was supposed to had been fetched.', ['id' => $id, 'url' => $url, 'uid' => $uid]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static function incrementInbound(string $network)
|
||||
{
|
||||
$packets = (int)(DI::keyValue()->get('stats_packets_inbound_' . $network) ?? 0);
|
||||
if ($packets >= PHP_INT_MAX) {
|
||||
$packets = 0;
|
||||
}
|
||||
DI::keyValue()->set('stats_packets_inbound_' . $network, $packets + 1);
|
||||
}
|
||||
|
||||
public static function incrementOutbound(string $network)
|
||||
{
|
||||
$packets = (int)(DI::keyValue()->get('stats_packets_outbound_' . $network) ?? 0);
|
||||
if ($packets >= PHP_INT_MAX) {
|
||||
$packets = 0;
|
||||
}
|
||||
DI::keyValue()->set('stats_packets_outbound_' . $network, $packets + 1);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 = [
|
||||
|
|
|
|||
|
|
@ -32,6 +32,8 @@ use Friendica\Core\Storage\Exception\ReferenceStorageException;
|
|||
use Friendica\Core\Storage\Exception\StorageException;
|
||||
use Friendica\Core\Storage\Type\SystemResource;
|
||||
use Friendica\Network\HTTPClient\Client\HttpClientAccept;
|
||||
use Friendica\Network\HTTPClient\Client\HttpClientOptions;
|
||||
use Friendica\Network\HTTPClient\Client\HttpClientRequest;
|
||||
use Friendica\Object\Image;
|
||||
use Friendica\Util\DateTimeFormat;
|
||||
use Friendica\Util\Images;
|
||||
|
|
@ -234,7 +236,8 @@ class Photo
|
|||
FROM `photo` WHERE `uid` = ? AND NOT `photo-type` IN (?, ?) $sqlExtra
|
||||
GROUP BY `resource-id` $sqlExtra2",
|
||||
$values
|
||||
));
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -549,7 +552,7 @@ class Photo
|
|||
// get photo to update
|
||||
$photos = self::selectToArray(['backend-class', 'backend-ref'], $conditions);
|
||||
|
||||
foreach($photos as $photo) {
|
||||
foreach ($photos as $photo) {
|
||||
try {
|
||||
$backend_class = DI::storageManager()->getWritableStorageByName($photo['backend-class'] ?? '');
|
||||
$fields['backend-ref'] = $backend_class->put($image->asString(), $photo['backend-ref']);
|
||||
|
|
@ -580,7 +583,9 @@ class Photo
|
|||
$micro = '';
|
||||
|
||||
$photo = DBA::selectFirst(
|
||||
'photo', ['resource-id'], ['uid' => $uid, 'contact-id' => $cid, 'scale' => 4, 'photo-type' => self::CONTACT_AVATAR]
|
||||
'photo',
|
||||
['resource-id'],
|
||||
['uid' => $uid, 'contact-id' => $cid, 'scale' => 4, 'photo-type' => self::CONTACT_AVATAR]
|
||||
);
|
||||
if (!empty($photo['resource-id'])) {
|
||||
$resource_id = $photo['resource-id'];
|
||||
|
|
@ -597,7 +602,7 @@ class Photo
|
|||
|
||||
$filename = basename($image_url);
|
||||
if (!empty($image_url)) {
|
||||
$ret = DI::httpClient()->get($image_url, HttpClientAccept::IMAGE);
|
||||
$ret = DI::httpClient()->get($image_url, HttpClientAccept::IMAGE, [HttpClientOptions::REQUEST => HttpClientRequest::MEDIAPROXY]);
|
||||
Logger::debug('Got picture', ['Content-Type' => $ret->getHeader('Content-Type'), 'url' => $image_url]);
|
||||
$img_str = $ret->getBodyString();
|
||||
$type = $ret->getContentType();
|
||||
|
|
@ -681,7 +686,9 @@ class Photo
|
|||
}
|
||||
|
||||
$photo = DBA::selectFirst(
|
||||
'photo', ['blurhash'], ['uid' => $uid, 'contact-id' => $cid, 'scale' => 4, 'photo-type' => self::CONTACT_AVATAR]
|
||||
'photo',
|
||||
['blurhash'],
|
||||
['uid' => $uid, 'contact-id' => $cid, 'scale' => 4, 'photo-type' => self::CONTACT_AVATAR]
|
||||
);
|
||||
|
||||
return [$image_url, $thumb, $micro, $photo['blurhash']];
|
||||
|
|
@ -751,7 +758,8 @@ class Photo
|
|||
if (!DI::config()->get('system', 'no_count', false)) {
|
||||
/// @todo This query needs to be renewed. It is really slow
|
||||
// At this time we just store the data in the cache
|
||||
$albums = DBA::toArray(DBA::p("SELECT COUNT(DISTINCT `resource-id`) AS `total`, `album`, MIN(`created`) AS `created`
|
||||
$albums = DBA::toArray(DBA::p(
|
||||
"SELECT COUNT(DISTINCT `resource-id`) AS `total`, `album`, MIN(`created`) AS `created`
|
||||
FROM `photo`
|
||||
WHERE `uid` = ? AND `photo-type` IN (?, ?, ?) $sql_extra
|
||||
GROUP BY `album` ORDER BY `created` DESC",
|
||||
|
|
@ -762,7 +770,8 @@ class Photo
|
|||
));
|
||||
} else {
|
||||
// This query doesn't do the count and is much faster
|
||||
$albums = DBA::toArray(DBA::p("SELECT '' AS `total`, `album`, MIN(`created`) AS `created`
|
||||
$albums = DBA::toArray(DBA::p(
|
||||
"SELECT '' AS `total`, `album`, MIN(`created`) AS `created`
|
||||
FROM `photo` USE INDEX (`uid_album_scale_created`)
|
||||
WHERE `uid` = ? AND `photo-type` IN (?, ?, ?) $sql_extra
|
||||
GROUP BY `album` ORDER BY `created` DESC",
|
||||
|
|
@ -902,9 +911,11 @@ class Photo
|
|||
*/
|
||||
public static function setPermissionForResource(string $image_rid, int $uid, string $str_contact_allow, string $str_circle_allow, string $str_contact_deny, string $str_circle_deny)
|
||||
{
|
||||
$fields = ['allow_cid' => $str_contact_allow, 'allow_gid' => $str_circle_allow,
|
||||
'deny_cid' => $str_contact_deny, 'deny_gid' => $str_circle_deny,
|
||||
'accessible' => DI::pConfig()->get($uid, 'system', 'accessible-photos', false)];
|
||||
$fields = [
|
||||
'allow_cid' => $str_contact_allow, 'allow_gid' => $str_circle_allow,
|
||||
'deny_cid' => $str_contact_deny, 'deny_gid' => $str_circle_deny,
|
||||
'accessible' => DI::pConfig()->get($uid, 'system', 'accessible-photos', false)
|
||||
];
|
||||
|
||||
$condition = ['resource-id' => $image_rid, 'uid' => $uid];
|
||||
Logger::info('Set permissions', ['condition' => $condition, 'permissions' => $fields]);
|
||||
|
|
@ -1046,7 +1057,7 @@ class Photo
|
|||
{
|
||||
$filename = basename($image_url);
|
||||
if (!empty($image_url)) {
|
||||
$ret = DI::httpClient()->get($image_url, HttpClientAccept::IMAGE);
|
||||
$ret = DI::httpClient()->get($image_url, HttpClientAccept::IMAGE, [HttpClientOptions::REQUEST => HttpClientRequest::MEDIAPROXY]);
|
||||
Logger::debug('Got picture', ['Content-Type' => $ret->getHeader('Content-Type'), 'url' => $image_url]);
|
||||
$img_str = $ret->getBodyString();
|
||||
$type = $ret->getContentType();
|
||||
|
|
|
|||
|
|
@ -379,6 +379,21 @@ class Post
|
|||
return self::selectView('post-user-view', $selected, $condition, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Select rows from the post-origin-view view
|
||||
*
|
||||
* @param array $selected Array of selected fields, empty for all
|
||||
* @param array $condition Array of fields for condition
|
||||
* @param array $params Array of several parameters
|
||||
*
|
||||
* @return boolean|object
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function selectOrigin(array $selected = [], array $condition = [], array $params = [])
|
||||
{
|
||||
return self::selectView('post-origin-view', $selected, $condition, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Select rows from the post-view view
|
||||
*
|
||||
|
|
@ -424,6 +439,21 @@ class Post
|
|||
return self::selectView('post-thread-view', $selected, $condition, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Select rows from the post-thread-origin-view view
|
||||
*
|
||||
* @param array $selected Array of selected fields, empty for all
|
||||
* @param array $condition Array of fields for condition
|
||||
* @param array $params Array of several parameters
|
||||
*
|
||||
* @return boolean|object
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function selectOriginThread(array $selected = [], array $condition = [], array $params = [])
|
||||
{
|
||||
return self::selectView('post-thread-origin-view', $selected, $condition, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Select rows from the given view for a given user
|
||||
*
|
||||
|
|
@ -513,6 +543,11 @@ class Post
|
|||
return self::selectViewForUser('post-timeline-view', $uid, $selected, $condition, $params);
|
||||
}
|
||||
|
||||
public static function selectLocalTimelineForUser(int $uid, array $selected = [], array $condition = [], array $params = [])
|
||||
{
|
||||
return self::selectViewForUser('post-timeline-origin-view', $uid, $selected, $condition, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Select rows from the post-thread-user-view view for a given user
|
||||
*
|
||||
|
|
|
|||
|
|
@ -124,7 +124,7 @@ class Content
|
|||
'limit' => [$start, $limit]
|
||||
];
|
||||
|
||||
$tags = DBA::select('post-searchindex', ['uri-id'], $condition, $params);
|
||||
$tags = DBA::select(SearchIndex::getSearchTable(), ['uri-id'], $condition, $params);
|
||||
|
||||
$uriids = [];
|
||||
while ($tag = DBA::fetch($tags)) {
|
||||
|
|
@ -141,8 +141,8 @@ class Content
|
|||
if ($uid != 0) {
|
||||
$condition = ["MATCH (`searchtext`) AGAINST (? IN BOOLEAN MODE) AND (NOT `restricted` OR `uri-id` IN (SELECT `uri-id` FROM `post-user` WHERE `uid` = ?))", $search, $uid];
|
||||
} else {
|
||||
$condition = ["MATCH (`searchtext`) AGAINST (? IN BOOLEAN MODE) AND NOT `restricted", $search];
|
||||
$condition = ["MATCH (`searchtext`) AGAINST (? IN BOOLEAN MODE) AND NOT `restricted`", $search];
|
||||
}
|
||||
return DBA::count('post-searchindex', $condition);
|
||||
return DBA::count(SearchIndex::getSearchTable(), $condition);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -418,4 +418,12 @@ class Engagement
|
|||
}
|
||||
return $fullTextSearch;
|
||||
}
|
||||
|
||||
public static function unescapeKeywords(string $fullTextSearch): string
|
||||
{
|
||||
foreach (self::KEYWORDS as $keyword) {
|
||||
$fullTextSearch = preg_replace('~(' . $keyword . ')_(.[\w\*@\.-]+)~', '$1:$2', $fullTextSearch);
|
||||
}
|
||||
return $fullTextSearch;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ use Friendica\Core\Protocol;
|
|||
use Friendica\Database\Database;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\DI;
|
||||
use Friendica\Model\Attach;
|
||||
use Friendica\Model\Contact;
|
||||
use Friendica\Model\Item;
|
||||
use Friendica\Model\ItemURI;
|
||||
|
|
@ -35,6 +36,7 @@ use Friendica\Model\Photo;
|
|||
use Friendica\Model\Post;
|
||||
use Friendica\Network\HTTPClient\Client\HttpClientAccept;
|
||||
use Friendica\Network\HTTPClient\Client\HttpClientOptions;
|
||||
use Friendica\Network\HTTPClient\Client\HttpClientRequest;
|
||||
use Friendica\Protocol\ActivityPub;
|
||||
use Friendica\Util\Images;
|
||||
use Friendica\Util\Network;
|
||||
|
|
@ -86,8 +88,8 @@ class Media
|
|||
// "document" has got the lowest priority. So when the same file is both attached as document
|
||||
// and embedded as picture then we only store the picture or replace the document
|
||||
$found = DBA::selectFirst('post-media', ['type'], ['uri-id' => $media['uri-id'], 'url' => $media['url']]);
|
||||
if (!$force && !empty($found) && (($found['type'] != self::DOCUMENT) || ($media['type'] == self::DOCUMENT))) {
|
||||
Logger::info('Media already exists', ['uri-id' => $media['uri-id'], 'url' => $media['url']]);
|
||||
if (!$force && !empty($found) && (!in_array($found['type'], [self::UNKNOWN, self::DOCUMENT]) || ($media['type'] == self::DOCUMENT))) {
|
||||
Logger::info('Media already exists', ['uri-id' => $media['uri-id'], 'url' => $media['url'], 'found' => $found['type'], 'new' => $media['type']]);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
@ -180,14 +182,20 @@ class Media
|
|||
*/
|
||||
public static function fetchAdditionalData(array $media): array
|
||||
{
|
||||
if (Network::isLocalLink($media['url'])) {
|
||||
if (DI::baseUrl()->isLocalUrl($media['url'])) {
|
||||
$media = self::fetchLocalData($media);
|
||||
if (preg_match('|.*?/search\?(.+)|', $media['url'], $matches)) {
|
||||
return $media;
|
||||
}
|
||||
if (empty($media['mimetype']) || empty($media['size'])) {
|
||||
Logger::debug('Unknown local link', ['url' => $media['url']]);
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch the mimetype or size if missing.
|
||||
if (Network::isValidHttpUrl($media['url']) && (empty($media['mimetype']) || empty($media['size']))) {
|
||||
$timeout = DI::config()->get('system', 'xrd_timeout');
|
||||
$curlResult = DI::httpClient()->head($media['url'], [HttpClientOptions::TIMEOUT => $timeout]);
|
||||
$curlResult = DI::httpClient()->head($media['url'], [HttpClientOptions::TIMEOUT => $timeout, HttpClientOptions::REQUEST => HttpClientRequest::CONTENTTYPE]);
|
||||
|
||||
// Workaround for systems that can't handle a HEAD request
|
||||
if (!$curlResult->isSuccess() && ($curlResult->getReturnCode() == 405)) {
|
||||
|
|
@ -389,7 +397,17 @@ class Media
|
|||
*/
|
||||
private static function fetchLocalData(array $media): array
|
||||
{
|
||||
if (!preg_match('|.*?/photo/(.*[a-fA-F0-9])\-(.*[0-9])\..*[\w]|', $media['url'] ?? '', $matches)) {
|
||||
if (preg_match('|.*?/attach/(\d+)|', $media['url'], $matches)) {
|
||||
$attachment = Attach::selectFirst(['filename', 'filetype', 'filesize'], ['id' => $matches[1]]);
|
||||
if (!empty($attachment)) {
|
||||
$media['name'] = $attachment['filename'];
|
||||
$media['mimetype'] = $attachment['filetype'];
|
||||
$media['size'] = $attachment['filesize'];
|
||||
}
|
||||
return $media;
|
||||
}
|
||||
|
||||
if (!preg_match('|.*?/photo/(.*[a-fA-F0-9])\-(.*[0-9])\..*[\w]|', $media['url'], $matches)) {
|
||||
return $media;
|
||||
}
|
||||
$photo = Photo::selectFirst([], ['resource-id' => $matches[1], 'scale' => $matches[2]]);
|
||||
|
|
@ -426,42 +444,46 @@ class Media
|
|||
return $data;
|
||||
}
|
||||
|
||||
$type = explode('/', current(explode(';', $data['mimetype'])));
|
||||
$data['type'] = self::getType($data['mimetype']);
|
||||
return $data;
|
||||
}
|
||||
|
||||
public static function getType(string $mimeType): int
|
||||
{
|
||||
$type = explode('/', current(explode(';', $mimeType)));
|
||||
if (count($type) < 2) {
|
||||
Logger::info('Unknown MimeType', ['type' => $type, 'media' => $data]);
|
||||
$data['type'] = self::UNKNOWN;
|
||||
return $data;
|
||||
Logger::info('Unknown MimeType', ['type' => $type, 'media' => $mimeType]);
|
||||
return self::UNKNOWN;
|
||||
}
|
||||
|
||||
$filetype = strtolower($type[0]);
|
||||
$subtype = strtolower($type[1]);
|
||||
|
||||
if ($filetype == 'image') {
|
||||
$data['type'] = self::IMAGE;
|
||||
$type = self::IMAGE;
|
||||
} elseif ($filetype == 'video') {
|
||||
$data['type'] = self::VIDEO;
|
||||
$type = self::VIDEO;
|
||||
} elseif ($filetype == 'audio') {
|
||||
$data['type'] = self::AUDIO;
|
||||
$type = self::AUDIO;
|
||||
} elseif (($filetype == 'text') && ($subtype == 'html')) {
|
||||
$data['type'] = self::HTML;
|
||||
$type = self::HTML;
|
||||
} elseif (($filetype == 'text') && ($subtype == 'xml')) {
|
||||
$data['type'] = self::XML;
|
||||
$type = self::XML;
|
||||
} elseif (($filetype == 'text') && ($subtype == 'plain')) {
|
||||
$data['type'] = self::PLAIN;
|
||||
$type = self::PLAIN;
|
||||
} elseif ($filetype == 'text') {
|
||||
$data['type'] = self::TEXT;
|
||||
$type = self::TEXT;
|
||||
} elseif (($filetype == 'application') && ($subtype == 'x-bittorrent')) {
|
||||
$data['type'] = self::TORRENT;
|
||||
$type = self::TORRENT;
|
||||
} elseif ($filetype == 'application') {
|
||||
$data['type'] = self::APPLICATION;
|
||||
$type = self::APPLICATION;
|
||||
} else {
|
||||
$data['type'] = self::UNKNOWN;
|
||||
Logger::info('Unknown type', ['filetype' => $filetype, 'subtype' => $subtype, 'media' => $data]);
|
||||
return $data;
|
||||
$type = self::UNKNOWN;
|
||||
Logger::info('Unknown type', ['filetype' => $filetype, 'subtype' => $subtype, 'media' => $mimeType]);
|
||||
}
|
||||
|
||||
Logger::debug('Detected type', ['filetype' => $filetype, 'subtype' => $subtype, 'media' => $data]);
|
||||
return $data;
|
||||
Logger::debug('Detected type', ['filetype' => $filetype, 'subtype' => $subtype, 'media' => $mimeType]);
|
||||
return $type;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -902,19 +924,7 @@ class Media
|
|||
$body = BBCode::removeAttachment($body);
|
||||
|
||||
foreach (self::getByURIId($uriid, $types) as $media) {
|
||||
if (Item::containsLink($body, $media['preview'] ?? $media['url'], $media['type'])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($media['type'] == self::IMAGE) {
|
||||
$body .= "\n" . Images::getBBCodeByUrl($media['url'], $media['preview'], $media['description'] ?? '');
|
||||
} elseif ($media['type'] == self::AUDIO) {
|
||||
$body .= "\n[audio]" . $media['url'] . "[/audio]\n";
|
||||
} elseif ($media['type'] == self::VIDEO) {
|
||||
$body .= "\n[video]" . $media['url'] . "[/video]\n";
|
||||
} else {
|
||||
$body .= "\n[url]" . $media['url'] . "[/url]\n";
|
||||
}
|
||||
$body = self::addAttachmentToBody($media, $body);
|
||||
}
|
||||
|
||||
if (preg_match("/.*(\[attachment.*?\].*?\[\/attachment\]).*/ism", $original_body, $match)) {
|
||||
|
|
@ -924,6 +934,24 @@ class Media
|
|||
return $body;
|
||||
}
|
||||
|
||||
public static function addAttachmentToBody(array $media, string $body): string
|
||||
{
|
||||
if (Item::containsLink($body, $media['preview'] ?? $media['url'], $media['type'])) {
|
||||
return $body;
|
||||
}
|
||||
|
||||
if ($media['type'] == self::IMAGE) {
|
||||
$body .= "\n" . Images::getBBCodeByUrl($media['url'], $media['preview'], $media['description'] ?? '');
|
||||
} elseif ($media['type'] == self::AUDIO) {
|
||||
$body .= "\n[audio]" . $media['url'] . "[/audio]\n";
|
||||
} elseif ($media['type'] == self::VIDEO) {
|
||||
$body .= "\n[video]" . $media['url'] . "[/video]\n";
|
||||
} else {
|
||||
$body .= "\n[url]" . $media['url'] . "[/url]\n";
|
||||
}
|
||||
return $body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an [attachment] element to the body for a given uri-id with a HTML media element
|
||||
*
|
||||
|
|
|
|||
93
src/Model/Post/Origin.php
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
<?php
|
||||
/**
|
||||
* @copyright Copyright (C) 2010-2024, the Friendica project
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace Friendica\Model\Post;
|
||||
|
||||
use Friendica\Database\DBA;
|
||||
use \BadMethodCallException;
|
||||
use Friendica\Database\Database;
|
||||
use Friendica\DI;
|
||||
|
||||
class Origin
|
||||
{
|
||||
/**
|
||||
* Insert a new post origin entry
|
||||
*
|
||||
* @param array $fields
|
||||
* @return boolean was the insert successful?
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function insert(array $data = []): bool
|
||||
{
|
||||
if (!$data['origin'] || ($data['uid'] == 0)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$fields = DI::dbaDefinition()->truncateFieldsForTable('post-origin', $data);
|
||||
|
||||
return DBA::insert('post-origin', $fields, Database::INSERT_IGNORE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a post origin entry
|
||||
*
|
||||
* @param integer $uri_id
|
||||
* @param integer $uid
|
||||
* @param array $data
|
||||
* @param bool $insert_if_missing
|
||||
* @return bool
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function update(int $uri_id, int $uid, array $data = [], bool $insert_if_missing = false)
|
||||
{
|
||||
if (empty($uri_id)) {
|
||||
throw new BadMethodCallException('Empty URI_id');
|
||||
}
|
||||
|
||||
$fields = DI::dbaDefinition()->truncateFieldsForTable('post-origin', $data);
|
||||
|
||||
// Remove the key fields
|
||||
unset($fields['uri-id']);
|
||||
unset($fields['uid']);
|
||||
|
||||
if (empty($fields)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return DBA::update('post-origin', $fields, ['uri-id' => $uri_id, 'uid' => $uid], $insert_if_missing ? true : []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a row from the post-origin table
|
||||
*
|
||||
* @param array $conditions Field condition(s)
|
||||
* @param array $options
|
||||
* - cascade: If true we delete records in other tables that depend on the one we're deleting through
|
||||
* relations (default: true)
|
||||
*
|
||||
* @return boolean was the delete successful?
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function delete(array $conditions, array $options = [])
|
||||
{
|
||||
return DBA::delete('post-origin', $conditions, $options);
|
||||
}
|
||||
}
|
||||
|
|
@ -99,4 +99,14 @@ class SearchIndex
|
|||
}
|
||||
return DateTimeFormat::utc('now - ' . $days . ' day');
|
||||
}
|
||||
|
||||
public static function getSearchTable(): string
|
||||
{
|
||||
return DI::config()->get('system', 'limited_search_scope') ? 'post-engagement' : 'post-searchindex';
|
||||
}
|
||||
|
||||
public static function getSearchView(): string
|
||||
{
|
||||
return DI::config()->get('system', 'limited_search_scope') ? 'post-engagement-user-view' : 'post-searchindex-user-view';
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,20 +31,14 @@ use Friendica\Core\Logger;
|
|||
use Friendica\Core\Protocol;
|
||||
use Friendica\Core\Renderer;
|
||||
use Friendica\Core\Search;
|
||||
use Friendica\Core\System;
|
||||
use Friendica\Core\Worker;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\DI;
|
||||
use Friendica\Network\HTTPClient\Client\HttpClientAccept;
|
||||
use Friendica\Network\HTTPClient\Client\HttpClientOptions;
|
||||
use Friendica\Network\HTTPException;
|
||||
use Friendica\Network\HTTPException\InternalServerErrorException;
|
||||
use Friendica\Protocol\Activity;
|
||||
use Friendica\Protocol\Diaspora;
|
||||
use Friendica\Security\PermissionSet\Entity\PermissionSet;
|
||||
use Friendica\Util\DateTimeFormat;
|
||||
use Friendica\Util\HTTPSignature;
|
||||
use Friendica\Util\Network;
|
||||
use Friendica\Util\Proxy;
|
||||
use Friendica\Util\Strings;
|
||||
|
||||
|
|
@ -296,7 +290,7 @@ class Profile
|
|||
if (DI::userSession()->getLocalUserId() && ($profile['uid'] ?? 0) != DI::userSession()->getLocalUserId()) {
|
||||
$profile_contact = Contact::getByURL($profile['nurl'], null, [], DI::userSession()->getLocalUserId());
|
||||
}
|
||||
if (!empty($profile['cid']) && self::getMyURL()) {
|
||||
if (!empty($profile['cid']) && DI::userSession()->getMyUrl()) {
|
||||
$profile_contact = Contact::selectFirst([], ['id' => $profile['cid']]);
|
||||
}
|
||||
|
||||
|
|
@ -321,19 +315,19 @@ class Profile
|
|||
|
||||
// Who is the logged-in user to this profile?
|
||||
$visitor_contact = [];
|
||||
if (!empty($profile['uid']) && self::getMyURL()) {
|
||||
$visitor_contact = Contact::selectFirst(['rel'], ['uid' => $profile['uid'], 'nurl' => Strings::normaliseLink(self::getMyURL())]);
|
||||
if (!empty($profile['uid']) && DI::userSession()->getMyUrl()) {
|
||||
$visitor_contact = Contact::selectFirst(['rel'], ['uid' => $profile['uid'], 'nurl' => Strings::normaliseLink(DI::userSession()->getMyUrl())]);
|
||||
}
|
||||
|
||||
$local_user_is_self = self::getMyURL() && ($profile['url'] == self::getMyURL());
|
||||
$visitor_is_authenticated = (bool)self::getMyURL();
|
||||
$local_user_is_self = DI::userSession()->getMyUrl() && ($profile['url'] == DI::userSession()->getMyUrl());
|
||||
$visitor_is_authenticated = (bool)DI::userSession()->getMyUrl();
|
||||
$visitor_is_following =
|
||||
in_array($visitor_contact['rel'] ?? 0, [Contact::FOLLOWER, Contact::FRIEND])
|
||||
|| in_array($profile_contact['rel'] ?? 0, [Contact::SHARING, Contact::FRIEND]);
|
||||
$visitor_is_followed =
|
||||
in_array($visitor_contact['rel'] ?? 0, [Contact::SHARING, Contact::FRIEND])
|
||||
|| in_array($profile_contact['rel'] ?? 0, [Contact::FOLLOWER, Contact::FRIEND]);
|
||||
$visitor_base_path = self::getMyURL() ? preg_replace('=/profile/(.*)=ism', '', self::getMyURL()) : '';
|
||||
$visitor_base_path = DI::userSession()->getMyUrl() ? preg_replace('=/profile/(.*)=ism', '', DI::userSession()->getMyUrl()) : '';
|
||||
|
||||
if (!$local_user_is_self) {
|
||||
if (!$visitor_is_authenticated) {
|
||||
|
|
@ -628,8 +622,10 @@ class Profile
|
|||
$bd_format = DI::l10n()->t('g A l F d'); // 8 AM Friday January 18
|
||||
$classtoday = '';
|
||||
|
||||
$condition = ["`uid` = ? AND `type` != 'birthday' AND `start` < ? AND `start` >= ?",
|
||||
$uid, DateTimeFormat::utc('now + 7 days'), DateTimeFormat::utc('now - 1 days')];
|
||||
$condition = [
|
||||
"`uid` = ? AND `type` != 'birthday' AND `start` < ? AND `start` >= ?",
|
||||
$uid, DateTimeFormat::utc('now + 7 days'), DateTimeFormat::utc('now - 1 days')
|
||||
];
|
||||
$s = DBA::select('event', [], $condition, ['order' => ['start']]);
|
||||
|
||||
$r = [];
|
||||
|
|
@ -639,9 +635,11 @@ class Profile
|
|||
$total = 0;
|
||||
|
||||
while ($rr = DBA::fetch($s)) {
|
||||
$condition = ['parent-uri' => $rr['uri'], 'uid' => $rr['uid'], 'author-id' => $pcid,
|
||||
$condition = [
|
||||
'parent-uri' => $rr['uri'], 'uid' => $rr['uid'], 'author-id' => $pcid,
|
||||
'vid' => [Verb::getID(Activity::ATTEND), Verb::getID(Activity::ATTENDMAYBE)],
|
||||
'visible' => true, 'deleted' => false];
|
||||
'visible' => true, 'deleted' => false
|
||||
];
|
||||
if (!Post::exists($condition)) {
|
||||
continue;
|
||||
}
|
||||
|
|
@ -695,235 +693,6 @@ class Profile
|
|||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the my_url session variable
|
||||
*
|
||||
* @return string
|
||||
* @deprecated since version 2022.12, please use UserSession->getMyUrl instead
|
||||
*/
|
||||
public static function getMyURL(): string
|
||||
{
|
||||
return DI::userSession()->getMyUrl();
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the 'zrl' parameter and initiate the remote authentication.
|
||||
*
|
||||
* This method checks if the visitor has a public contact entry and
|
||||
* redirects the visitor to his/her instance to start the magic auth (Authentication)
|
||||
* process.
|
||||
*
|
||||
* Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/channel.php
|
||||
*
|
||||
* The implementation for Friendica sadly differs in some points from the one for Hubzilla:
|
||||
* - Hubzilla uses the "zid" parameter, while for Friendica it had been replaced with "zrl"
|
||||
* - There seem to be some reverse authentication (rmagic) that isn't implemented in Friendica at all
|
||||
*
|
||||
* It would be favourable to harmonize the two implementations.
|
||||
*
|
||||
* @param App $a Application instance.
|
||||
*
|
||||
* @return void
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
* @throws \ImagickException
|
||||
*/
|
||||
public static function zrlInit(App $a)
|
||||
{
|
||||
$my_url = self::getMyURL();
|
||||
$my_url = Network::isUrlValid($my_url);
|
||||
|
||||
if (empty($my_url) || DI::userSession()->getLocalUserId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$addr = $_GET['addr'] ?? $my_url;
|
||||
|
||||
$arr = ['zrl' => $my_url, 'url' => DI::args()->getCommand()];
|
||||
Hook::callAll('zrl_init', $arr);
|
||||
|
||||
// Try to find the public contact entry of the visitor.
|
||||
$cid = Contact::getIdForURL($my_url);
|
||||
if (!$cid) {
|
||||
Logger::info('No contact record found for ' . $my_url);
|
||||
return;
|
||||
}
|
||||
|
||||
$contact = DBA::selectFirst('contact',['id', 'url'], ['id' => $cid]);
|
||||
|
||||
if (DBA::isResult($contact) && DI::userSession()->getRemoteUserId() && DI::userSession()->getRemoteUserId() == $contact['id']) {
|
||||
Logger::info('The visitor ' . $my_url . ' is already authenticated');
|
||||
return;
|
||||
}
|
||||
|
||||
// Avoid endless loops
|
||||
$cachekey = 'zrlInit:' . $my_url;
|
||||
if (DI::cache()->get($cachekey)) {
|
||||
Logger::info('URL ' . $my_url . ' already tried to authenticate.');
|
||||
return;
|
||||
} else {
|
||||
DI::cache()->set($cachekey, true, Duration::MINUTE);
|
||||
}
|
||||
|
||||
Logger::info('Not authenticated. Invoking reverse magic-auth for ' . $my_url);
|
||||
|
||||
// Remove the "addr" parameter from the destination. It is later added as separate parameter again.
|
||||
$addr_request = 'addr=' . urlencode($addr);
|
||||
$query = rtrim(str_replace($addr_request, '', DI::args()->getQueryString()), '?&');
|
||||
|
||||
// The other instance needs to know where to redirect.
|
||||
$dest = urlencode(DI::baseUrl() . '/' . $query);
|
||||
|
||||
// We need to extract the basebath from the profile url
|
||||
// to redirect the visitors '/magic' module.
|
||||
$basepath = Contact::getBasepath($contact['url']);
|
||||
|
||||
if ($basepath != DI::baseUrl() && !strstr($dest, '/magic')) {
|
||||
$magic_path = $basepath . '/magic' . '?owa=1&dest=' . $dest . '&' . $addr_request;
|
||||
|
||||
// We have to check if the remote server does understand /magic without invoking something
|
||||
$serverret = DI::httpClient()->head($basepath . '/magic', [HttpClientOptions::ACCEPT_CONTENT => HttpClientAccept::HTML]);
|
||||
if ($serverret->isSuccess()) {
|
||||
Logger::info('Doing magic auth for visitor ' . $my_url . ' to ' . $magic_path);
|
||||
System::externalRedirect($magic_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the visitor cookies (see remote_user()) for the given handle
|
||||
*
|
||||
* @param string $handle Visitor handle
|
||||
*
|
||||
* @return array Visitor contact array
|
||||
*/
|
||||
public static function addVisitorCookieForHandle(string $handle): array
|
||||
{
|
||||
$a = DI::app();
|
||||
|
||||
// Try to find the public contact entry of the visitor.
|
||||
$cid = Contact::getIdForURL($handle);
|
||||
if (!$cid) {
|
||||
Logger::info('Handle not found', ['handle' => $handle]);
|
||||
return [];
|
||||
}
|
||||
|
||||
$visitor = Contact::getById($cid);
|
||||
|
||||
// Authenticate the visitor.
|
||||
DI::userSession()->setMultiple([
|
||||
'authenticated' => 1,
|
||||
'visitor_id' => $visitor['id'],
|
||||
'visitor_handle' => $visitor['addr'],
|
||||
'visitor_home' => $visitor['url'],
|
||||
'my_url' => $visitor['url'],
|
||||
'remote_comment' => $visitor['subscribe'],
|
||||
]);
|
||||
|
||||
DI::userSession()->setVisitorsContacts($visitor['url']);
|
||||
|
||||
$a->setContactId($visitor['id']);
|
||||
|
||||
Logger::info('Authenticated visitor', ['url' => $visitor['url']]);
|
||||
|
||||
return $visitor;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the visitor cookies (see remote_user()) for signed HTTP requests
|
||||
*
|
||||
* @param array $server The content of the $_SERVER superglobal
|
||||
* @return array Visitor contact array
|
||||
* @throws InternalServerErrorException
|
||||
*/
|
||||
public static function addVisitorCookieForHTTPSigner(array $server): array
|
||||
{
|
||||
$requester = HTTPSignature::getSigner('', $server);
|
||||
if (empty($requester)) {
|
||||
return [];
|
||||
}
|
||||
return Profile::addVisitorCookieForHandle($requester);
|
||||
}
|
||||
|
||||
/**
|
||||
* OpenWebAuth authentication.
|
||||
*
|
||||
* Ported from Hubzilla: https://framagit.org/hubzilla/core/blob/master/include/zid.php
|
||||
*
|
||||
* @param string $token
|
||||
*
|
||||
* @return void
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
* @throws \ImagickException
|
||||
*/
|
||||
public static function openWebAuthInit(string $token)
|
||||
{
|
||||
$a = DI::app();
|
||||
|
||||
// Clean old OpenWebAuthToken entries.
|
||||
OpenWebAuthToken::purge('owt', '3 MINUTE');
|
||||
|
||||
// Check if the token we got is the same one
|
||||
// we have stored in the database.
|
||||
$visitor_handle = OpenWebAuthToken::getMeta('owt', 0, $token);
|
||||
|
||||
if ($visitor_handle === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
$visitor = self::addVisitorCookieForHandle($visitor_handle);
|
||||
if (empty($visitor)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$arr = [
|
||||
'visitor' => $visitor,
|
||||
'url' => DI::args()->getQueryString()
|
||||
];
|
||||
/**
|
||||
* @hooks magic_auth_success
|
||||
* Called when a magic-auth was successful.
|
||||
* * \e array \b visitor
|
||||
* * \e string \b url
|
||||
*/
|
||||
Hook::callAll('magic_auth_success', $arr);
|
||||
|
||||
$a->setContactId($arr['visitor']['id']);
|
||||
|
||||
DI::sysmsg()->addInfo(DI::l10n()->t('OpenWebAuth: %1$s welcomes %2$s', DI::baseUrl()->getHost(), $visitor['name']));
|
||||
|
||||
Logger::info('OpenWebAuth: auth success from ' . $visitor['addr']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns URL with URL-encoded zrl parameter
|
||||
*
|
||||
* @param string $url URL to enhance
|
||||
* @param bool $force Either to force adding zrl parameter
|
||||
*
|
||||
* @return string URL with 'zrl' parameter or original URL in case of no Friendica profile URL
|
||||
*/
|
||||
public static function zrl(string $url, bool $force = false): string
|
||||
{
|
||||
if (!strlen($url)) {
|
||||
return $url;
|
||||
}
|
||||
if (!strpos($url, '/profile/') && !$force) {
|
||||
return $url;
|
||||
}
|
||||
if ($force && substr($url, -1, 1) !== '/') {
|
||||
$url = $url . '/';
|
||||
}
|
||||
|
||||
$achar = strpos($url, '?') ? '&' : '?';
|
||||
$mine = self::getMyURL();
|
||||
|
||||
if ($mine && !Strings::compareLink($mine, $url)) {
|
||||
return $url . $achar . 'zrl=' . urlencode($mine);
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the user ID of the page owner.
|
||||
*
|
||||
|
|
@ -959,7 +728,8 @@ class Profile
|
|||
if (!empty($search)) {
|
||||
$publish = (DI::config()->get('system', 'publish_all') ? '' : "AND `publish` ");
|
||||
$searchTerm = '%' . $search . '%';
|
||||
$condition = ["`verified` AND NOT `blocked` AND NOT `account_removed` AND NOT `account_expired`
|
||||
$condition = [
|
||||
"`verified` AND NOT `blocked` AND NOT `account_removed` AND NOT `account_expired`
|
||||
$publish
|
||||
AND ((`name` LIKE ?) OR
|
||||
(`nickname` LIKE ?) OR
|
||||
|
|
@ -970,7 +740,8 @@ class Profile
|
|||
(`pub_keywords` LIKE ?) OR
|
||||
(`prv_keywords` LIKE ?))",
|
||||
$searchTerm, $searchTerm, $searchTerm, $searchTerm,
|
||||
$searchTerm, $searchTerm, $searchTerm, $searchTerm];
|
||||
$searchTerm, $searchTerm, $searchTerm, $searchTerm
|
||||
];
|
||||
} else {
|
||||
$condition = ['verified' => true, 'blocked' => false, 'account_removed' => false, 'account_expired' => false];
|
||||
if (!DI::config()->get('system', 'publish_all')) {
|
||||
|
|
@ -1073,4 +844,44 @@ class Profile
|
|||
DBA::delete('profile', ['id' => $profile['id']]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get "about" field with the added responsible relay contact if appropriate.
|
||||
*
|
||||
* @param string $about
|
||||
* @param integer|null $parent_uid
|
||||
* @param integer $account_type
|
||||
* @param string $language
|
||||
* @return string
|
||||
*/
|
||||
public static function addResponsibleRelayContact(string $about = null, int $parent_uid = null, int $account_type, string $language): ?string
|
||||
{
|
||||
if (($account_type != User::ACCOUNT_TYPE_RELAY) || empty($parent_uid)) {
|
||||
return $about;
|
||||
}
|
||||
|
||||
$parent = User::getOwnerDataById($parent_uid);
|
||||
if (strpos($about, $parent['addr']) || strpos($about, $parent['url'])) {
|
||||
return $about;
|
||||
}
|
||||
|
||||
$l10n = DI::l10n()->withLang($language);
|
||||
|
||||
return $about .= "\n" . $l10n->t('Responsible account: %s', $parent['addr']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set "about" field with the added responsible relay contact if appropriate.
|
||||
*
|
||||
* @param integer $uid
|
||||
* @return void
|
||||
*/
|
||||
public static function setResponsibleRelayContact(int $uid)
|
||||
{
|
||||
$owner = User::getOwnerDataById($uid);
|
||||
$about = self::addResponsibleRelayContact($owner['about'], $owner['parent-uid'], $owner['account-type'], $owner['language']);
|
||||
if ($about != $owner['about']) {
|
||||
self::update(['about' => $about], $uid);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ use Friendica\Core\Logger;
|
|||
use Friendica\Core\Worker;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\Util\DateTimeFormat;
|
||||
use Friendica\Util\Network;
|
||||
use GuzzleHttp\Psr7\Uri;
|
||||
|
||||
class PushSubscriber
|
||||
{
|
||||
|
|
@ -179,7 +179,7 @@ class PushSubscriber
|
|||
|
||||
$parts = parse_url($subscriber['callback_url']);
|
||||
unset($parts['path']);
|
||||
$server_url = Network::unparseURL($parts);
|
||||
$server_url = (string)Uri::fromParts((array)$parts);
|
||||
$gsid = GServer::getID($server_url, true);
|
||||
if (!empty($gsid)) {
|
||||
GServer::setProtocol($gsid, Post\DeliveryData::OSTATUS);
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ use DivineOmega\DOFileCachePSR6\CacheItemPool;
|
|||
use DivineOmega\PasswordExposed;
|
||||
use ErrorException;
|
||||
use Exception;
|
||||
use Friendica\App;
|
||||
use Friendica\Content\Pager;
|
||||
use Friendica\Core\Hook;
|
||||
use Friendica\Core\L10n;
|
||||
|
|
@ -37,6 +38,8 @@ use Friendica\Database\DBA;
|
|||
use Friendica\DI;
|
||||
use Friendica\Module;
|
||||
use Friendica\Network\HTTPClient\Client\HttpClientAccept;
|
||||
use Friendica\Network\HTTPClient\Client\HttpClientOptions;
|
||||
use Friendica\Network\HTTPClient\Client\HttpClientRequest;
|
||||
use Friendica\Network\HTTPException;
|
||||
use Friendica\Object\Image;
|
||||
use Friendica\Protocol\Delivery;
|
||||
|
|
@ -72,6 +75,7 @@ class User
|
|||
const PAGE_FLAGS_FREELOVE = 3;
|
||||
const PAGE_FLAGS_BLOG = 4;
|
||||
const PAGE_FLAGS_PRVGROUP = 5;
|
||||
const PAGE_FLAGS_COMM_MAN = 6;
|
||||
/**
|
||||
* @}
|
||||
*/
|
||||
|
|
@ -161,6 +165,7 @@ class User
|
|||
}
|
||||
}
|
||||
|
||||
$system['name'] = App::PLATFORM . " '" . App::CODENAME . "' " . App::VERSION . '-' . DB_UPDATE_VERSION;
|
||||
$system['sprvkey'] = $system['uprvkey'] = $system['prvkey'];
|
||||
$system['spubkey'] = $system['upubkey'] = $system['pubkey'];
|
||||
$system['nickname'] = $system['nick'];
|
||||
|
|
@ -407,6 +412,29 @@ class User
|
|||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the user id of a given contact id
|
||||
*
|
||||
* @param int $cid
|
||||
*
|
||||
* @return integer user id
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function getIdForContactId(int $cid): int
|
||||
{
|
||||
$account = Contact::selectFirstAccountUser(['pid', 'self', 'uid'], ['id' => $cid]);
|
||||
if (empty($account['pid'])) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ($account['self']) {
|
||||
return $account['uid'];
|
||||
}
|
||||
|
||||
$self = Contact::selectFirstAccountUser(['uid'], ['pid' => $cid, 'self' => true]);
|
||||
return $self['uid'] ?? 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a user based on its email
|
||||
*
|
||||
|
|
@ -1397,7 +1425,7 @@ class User
|
|||
$photo_failure = false;
|
||||
|
||||
$filename = basename($photo);
|
||||
$curlResult = DI::httpClient()->get($photo, HttpClientAccept::IMAGE);
|
||||
$curlResult = DI::httpClient()->get($photo, HttpClientAccept::IMAGE, [HttpClientOptions::REQUEST => HttpClientRequest::CONTENTTYPE]);
|
||||
if ($curlResult->isSuccess()) {
|
||||
Logger::debug('Got picture', ['Content-Type' => $curlResult->getHeader('Content-Type'), 'url' => $photo]);
|
||||
$img_str = $curlResult->getBodyString();
|
||||
|
|
@ -1487,6 +1515,12 @@ class User
|
|||
*/
|
||||
public static function block(int $uid, bool $block = true): bool
|
||||
{
|
||||
$self = Contact::getPublicIdByUserId($uid);
|
||||
if ($block) {
|
||||
Contact::block($self);
|
||||
} else {
|
||||
Contact::unblock($self);
|
||||
}
|
||||
return DBA::update('user', ['blocked' => $block], ['uid' => $uid]);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,9 +22,11 @@
|
|||
namespace Friendica\Module\ActivityPub;
|
||||
|
||||
use Friendica\Core\Logger;
|
||||
use Friendica\Core\Protocol;
|
||||
use Friendica\Core\System;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\DI;
|
||||
use Friendica\Model\Item;
|
||||
use Friendica\Model\User;
|
||||
use Friendica\Module\BaseApi;
|
||||
use Friendica\Module\Special\HTTPException;
|
||||
|
|
@ -77,6 +79,11 @@ class Inbox extends BaseApi
|
|||
throw new \Friendica\Network\HTTPException\BadRequestException();
|
||||
}
|
||||
|
||||
if (!HTTPSignature::isValidContentType($this->server['CONTENT_TYPE'] ?? '')) {
|
||||
Logger::notice('Unexpected content type', ['content-type' => $this->server['CONTENT_TYPE'] ?? '', 'agent' => $this->server['HTTP_USER_AGENT'] ?? '']);
|
||||
throw new \Friendica\Network\HTTPException\UnsupportedMediaTypeException();
|
||||
}
|
||||
|
||||
if (DI::config()->get('debug', 'ap_inbox_log')) {
|
||||
if (HTTPSignature::getSigner($postdata, $_SERVER)) {
|
||||
$filename = 'signed-activitypub';
|
||||
|
|
@ -98,6 +105,7 @@ class Inbox extends BaseApi
|
|||
$uid = 0;
|
||||
}
|
||||
|
||||
Item::incrementInbound(Protocol::ACTIVITYPUB);
|
||||
ActivityPub\Receiver::processInbox($postdata, $_SERVER, $uid);
|
||||
|
||||
throw new \Friendica\Network\HTTPException\AcceptedException();
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ class Whoami extends BaseApi
|
|||
$data['name'] = $owner['name'];
|
||||
$data['preferredUsername'] = $owner['nick'];
|
||||
$data['alsoKnownAs'] = [];
|
||||
$data['manuallyApprovesFollowers'] = in_array($owner['page-flags'], [User::PAGE_FLAGS_NORMAL, User::PAGE_FLAGS_PRVGROUP]);
|
||||
$data['manuallyApprovesFollowers'] = in_array($owner['page-flags'], [User::PAGE_FLAGS_NORMAL, User::PAGE_FLAGS_PRVGROUP, User::PAGE_FLAGS_COMM_MAN]);
|
||||
$data['discoverable'] = (bool)$owner['net-publish'];
|
||||
$data['tag'] = [];
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -34,25 +34,24 @@ class Features extends BaseAdmin
|
|||
|
||||
self::checkFormSecurityTokenRedirectOnError('/admin/features', 'admin_manage_features');
|
||||
|
||||
$features = Feature::get(false);
|
||||
|
||||
foreach ($features as $fname => $fdata) {
|
||||
foreach (Feature::get(false) as $fdata) {
|
||||
foreach (array_slice($fdata, 1) as $f) {
|
||||
$feature = $f[0];
|
||||
$feature_state = 'feature_' . $feature;
|
||||
$featurelock = 'featurelock_' . $feature;
|
||||
switch ($_POST['featureselect_' . $feature]) {
|
||||
case 0:
|
||||
DI::config()->set('feature', $feature, false);
|
||||
DI::config()->delete('feature_lock', $feature);
|
||||
break;
|
||||
|
||||
if (!empty($_POST[$feature_state])) {
|
||||
$val = intval($_POST[$feature_state]);
|
||||
} else {
|
||||
$val = 0;
|
||||
}
|
||||
DI::config()->set('feature', $feature, $val);
|
||||
case 1:
|
||||
DI::config()->set('feature', $feature, true);
|
||||
DI::config()->delete('feature_lock', $feature);
|
||||
break;
|
||||
|
||||
if (!empty($_POST[$featurelock])) {
|
||||
DI::config()->set('feature_lock', $feature, 1);
|
||||
} else {
|
||||
DI::config()->delete('feature_lock', $feature);
|
||||
case 2:
|
||||
DI::config()->delete('feature', $feature);
|
||||
DI::config()->set('feature_lock', $feature, true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -64,17 +63,15 @@ class Features extends BaseAdmin
|
|||
{
|
||||
parent::content();
|
||||
|
||||
$features = [];
|
||||
|
||||
$features = [];
|
||||
$selection = [DI::l10n()->t('No'), DI::l10n()->t('Yes'), DI::l10n()->t('Locked')];
|
||||
foreach (Feature::get(false) as $fname => $fdata) {
|
||||
$features[$fname] = [];
|
||||
$features[$fname][0] = $fdata[0];
|
||||
foreach (array_slice($fdata, 1) as $f) {
|
||||
$set = DI::config()->get('feature', $f[0], $f[3]);
|
||||
$features[$fname][1][] = [
|
||||
['feature_' . $f[0], $f[1], $set, $f[2]],
|
||||
['featurelock_' . $f[0], DI::l10n()->t('Lock feature %s', $f[1]), $f[4], '']
|
||||
];
|
||||
$selected = $f[4] ? 2 : (int)$set;
|
||||
$features[$fname][1][] = ['featureselect_' . $f[0], $f[1], $selected, $f[2], $selection];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -221,7 +221,6 @@ class Federation extends BaseAdmin
|
|||
'$page' => DI::l10n()->t('Federation Statistics'),
|
||||
'$intro' => $intro,
|
||||
'$counts' => $counts,
|
||||
'$version' => App::VERSION,
|
||||
'$legendtext' => DI::l10n()->tt('Currently this node is aware of %2$s node (%3$s active users last month, %4$s active users last six months, %5$s registered users in total) from the following platforms:', 'Currently this node is aware of %2$s nodes (%3$s active users last month, %4$s active users last six months, %5$s registered users in total) from the following platforms:', $total, number_format($total), number_format($month), number_format($halfyear), number_format($users)),
|
||||
]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,11 +66,14 @@ class Settings extends BaseAdmin
|
|||
parent::content();
|
||||
|
||||
$log_choices = [
|
||||
LogLevel::ERROR => 'Error',
|
||||
LogLevel::WARNING => 'Warning',
|
||||
LogLevel::NOTICE => 'Notice',
|
||||
LogLevel::INFO => 'Info',
|
||||
LogLevel::DEBUG => 'Debug',
|
||||
LogLevel::EMERGENCY => 'Emergency',
|
||||
LogLevel::ALERT => 'Alert',
|
||||
LogLevel::CRITICAL => 'Critical',
|
||||
LogLevel::ERROR => 'Error',
|
||||
LogLevel::WARNING => 'Warning',
|
||||
LogLevel::NOTICE => 'Notice',
|
||||
LogLevel::INFO => 'Info',
|
||||
LogLevel::DEBUG => 'Debug',
|
||||
];
|
||||
|
||||
if (ini_get('log_errors')) {
|
||||
|
|
|
|||
|
|
@ -47,6 +47,8 @@ class View extends BaseAdmin
|
|||
$filters_valid_values = [
|
||||
'level' => [
|
||||
'',
|
||||
LogLevel::EMERGENCY,
|
||||
LogLevel::ALERT,
|
||||
LogLevel::CRITICAL,
|
||||
LogLevel::ERROR,
|
||||
LogLevel::WARNING,
|
||||
|
|
@ -54,7 +56,7 @@ class View extends BaseAdmin
|
|||
LogLevel::INFO,
|
||||
LogLevel::DEBUG,
|
||||
],
|
||||
'context' => ['', 'index', 'worker'],
|
||||
'context' => ['', 'index', 'worker', 'daemon'],
|
||||
];
|
||||
$filters = [
|
||||
'level' => $_GET['level'] ?? '',
|
||||
|
|
@ -71,10 +73,10 @@ class View extends BaseAdmin
|
|||
} else {
|
||||
try {
|
||||
$data = DI::parsedLogIterator()
|
||||
->open($f)
|
||||
->withLimit(self::LIMIT)
|
||||
->withFilters($filters)
|
||||
->withSearch($search);
|
||||
->open($f)
|
||||
->withLimit(self::LIMIT)
|
||||
->withFilters($filters)
|
||||
->withSearch($search);
|
||||
} catch (\Exception $e) {
|
||||
$error = DI::l10n()->t('Couldn\'t open <strong>%1$s</strong> log file.<br/>Check to see if file %1$s is readable.', $f);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,13 +56,14 @@ class Queue extends BaseAdmin
|
|||
}
|
||||
|
||||
// @TODO Move to Model\WorkerQueue::getEntries()
|
||||
$entries = DBA::select('workerqueue', ['id', 'parameter', 'created', 'priority', 'command'], $condition, ['limit' => 999, 'order' => ['created']]);
|
||||
$entries = DBA::select('workerqueue', ['id', 'parameter', 'created', 'next_try', 'priority', 'command'], $condition, ['limit' => 999, 'order' => ['created']]);
|
||||
|
||||
$r = [];
|
||||
while ($entry = DBA::fetch($entries)) {
|
||||
// fix GH-5469. ref: src/Core/Worker.php:217
|
||||
$entry['parameter'] = Arrays::recursiveImplode(json_decode($entry['parameter'], true), ': ');
|
||||
$entry['created'] = DateTimeFormat::local($entry['created']);
|
||||
$entry['next_try'] = DateTimeFormat::local($entry['next_try']);
|
||||
$r[] = $entry;
|
||||
}
|
||||
DBA::close($entries);
|
||||
|
|
@ -76,8 +77,10 @@ class Queue extends BaseAdmin
|
|||
'$command_header' => DI::l10n()->t('Command'),
|
||||
'$param_header' => DI::l10n()->t('Job Parameters'),
|
||||
'$created_header' => DI::l10n()->t('Created'),
|
||||
'$next_try_header' => DI::l10n()->t('Next Try'),
|
||||
'$prio_header' => DI::l10n()->t('Priority'),
|
||||
'$info' => $info,
|
||||
'$status' => $status,
|
||||
'$entries' => $r,
|
||||
]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -97,7 +97,6 @@ class Site extends BaseAdmin
|
|||
$adjust_poll_frequency = !empty($_POST['adjust_poll_frequency']);
|
||||
$min_poll_interval = (!empty($_POST['min_poll_interval']) ? intval(trim($_POST['min_poll_interval'])) : 0);
|
||||
$explicit_content = !empty($_POST['explicit_content']);
|
||||
$proxify_content = !empty($_POST['proxify_content']);
|
||||
$local_search = !empty($_POST['local_search']);
|
||||
$blocked_tags = (!empty($_POST['blocked_tags']) ? trim($_POST['blocked_tags']) : '');
|
||||
$cache_contact_avatar = !empty($_POST['cache_contact_avatar']);
|
||||
|
|
@ -141,6 +140,7 @@ class Site extends BaseAdmin
|
|||
$temppath = (!empty($_POST['temppath']) ? trim($_POST['temppath']) : '');
|
||||
$singleuser = (!empty($_POST['singleuser']) ? trim($_POST['singleuser']) : '');
|
||||
$only_tag_search = !empty($_POST['only_tag_search']);
|
||||
$limited_search_scope = !empty($_POST['limited_search_scope']);
|
||||
$search_age_days = (!empty($_POST['search_age_days']) ? intval($_POST['search_age_days']) : 0);
|
||||
$compute_circle_counts = !empty($_POST['compute_circle_counts']);
|
||||
$process_view = !empty($_POST['process_view']);
|
||||
|
|
@ -271,7 +271,6 @@ class Site extends BaseAdmin
|
|||
$transactionConfig->set('system', 'adjust_poll_frequency' , $adjust_poll_frequency);
|
||||
$transactionConfig->set('system', 'min_poll_interval' , $min_poll_interval);
|
||||
$transactionConfig->set('system', 'explicit_content' , $explicit_content);
|
||||
$transactionConfig->set('system', 'proxify_content' , $proxify_content);
|
||||
$transactionConfig->set('system', 'local_search' , $local_search);
|
||||
$transactionConfig->set('system', 'blocked_tags' , Strings::cleanTags($blocked_tags));
|
||||
$transactionConfig->set('system', 'cache_contact_avatar' , $cache_contact_avatar);
|
||||
|
|
@ -319,6 +318,7 @@ class Site extends BaseAdmin
|
|||
$transactionConfig->set('system', 'temppath', $temppath);
|
||||
|
||||
$transactionConfig->set('system', 'only_tag_search', $only_tag_search);
|
||||
$transactionConfig->set('system', 'limited_search_scope', $limited_search_scope);
|
||||
$transactionConfig->set('system', 'search_age_days', $search_age_days);
|
||||
$transactionConfig->set('system', 'compute_circle_counts', $compute_circle_counts);
|
||||
$transactionConfig->set('system', 'process_view', $process_view);
|
||||
|
|
@ -518,7 +518,6 @@ class Site extends BaseAdmin
|
|||
'$private_addons' => ['private_addons', DI::l10n()->t('Disallow public access to addons listed in the apps menu.'), DI::config()->get('config', 'private_addons'), DI::l10n()->t('Checking this box will restrict addons listed in the apps menu to members only.')],
|
||||
'$disable_embedded' => ['disable_embedded', DI::l10n()->t('Don\'t embed private images in posts'), DI::config()->get('system', 'disable_embedded'), DI::l10n()->t('Don\'t replace locally-hosted private photos in posts with an embedded copy of the image. This means that contacts who receive posts containing private photos will have to authenticate and load each image, which may take a while.')],
|
||||
'$explicit_content' => ['explicit_content', DI::l10n()->t('Explicit Content'), DI::config()->get('system', 'explicit_content'), DI::l10n()->t('Set this to announce that your node is used mostly for explicit content that might not be suited for minors. This information will be published in the node information and might be used, e.g. by the global directory, to filter your node from listings of nodes to join. Additionally a note about this will be shown at the user registration page.')],
|
||||
'$proxify_content' => ['proxify_content', DI::l10n()->t('Proxify external content'), DI::config()->get('system', 'proxify_content'), DI::l10n()->t('Route external content via the proxy functionality. This is used for example for some OEmbed accesses and in some other rare cases.')],
|
||||
'$local_search' => ['local_search', DI::l10n()->t('Only local search'), DI::config()->get('system', 'local_search'), DI::l10n()->t('Blocks search for users who are not logged in to prevent crawlers from blocking your system.')],
|
||||
'$blocked_tags' => ['blocked_tags', DI::l10n()->t('Blocked tags for trending tags'), DI::config()->get('system', 'blocked_tags'), DI::l10n()->t("Comma separated list of hashtags that shouldn't be displayed in the trending tags.")],
|
||||
'$cache_contact_avatar' => ['cache_contact_avatar', DI::l10n()->t('Cache contact avatars'), DI::config()->get('system', 'cache_contact_avatar'), DI::l10n()->t('Locally store the avatar pictures of the contacts. This uses a lot of storage space but it increases the performance.')],
|
||||
|
|
@ -574,6 +573,7 @@ class Site extends BaseAdmin
|
|||
'$itemspage_network_mobile' => ['itemspage_network_mobile', DI::l10n()->t('Items per page for mobile devices'), DI::config()->get('system', 'itemspage_network_mobile'), DI::l10n()->t('Number of items per page in stream pages (network, community, profile/contact statuses, search) for mobile devices.')],
|
||||
'$temppath' => ['temppath', DI::l10n()->t('Temp path'), DI::config()->get('system', 'temppath'), DI::l10n()->t('If you have a restricted system where the webserver can\'t access the system temp path, enter another path here.')],
|
||||
'$only_tag_search' => ['only_tag_search', DI::l10n()->t('Only search in tags'), DI::config()->get('system', 'only_tag_search'), DI::l10n()->t('On large systems the text search can slow down the system extremely.')],
|
||||
'$limited_search_scope' => ['limited_search_scope', DI::l10n()->t('Limited search scope'), DI::config()->get('system', 'limited_search_scope'), DI::l10n()->t('If enabled, searches will only be performed in the data used for the channels and not in all posts.')],
|
||||
'$search_age_days' => ['search_age_days', DI::l10n()->t('Maximum age of items in the search table'), DI::config()->get('system', 'search_age_days'), DI::l10n()->t('Maximum age of items in the search table in days. Lower values will increase the performance and reduce disk usage. 0 means no age restriction.')],
|
||||
'$compute_circle_counts' => ['compute_circle_counts', DI::l10n()->t('Generate counts per contact circle when calculating network count'), DI::config()->get('system', 'compute_circle_counts'), DI::l10n()->t('On systems with users that heavily use contact circles the query can be very expensive.')],
|
||||
'$process_view' => ['process_view', DI::l10n()->t('Process "view" activities'), DI::config()->get('system', 'process_view'), DI::l10n()->t('"view" activities are mostly geberated by Peertube systems. Per default they are not processed for performance reasons. Only activate this option on performant system.')],
|
||||
|
|
|
|||
|
|
@ -196,7 +196,7 @@ class Summary extends BaseAdmin
|
|||
'$title' => DI::l10n()->t('Administration'),
|
||||
'$page' => DI::l10n()->t('Summary'),
|
||||
'$queues' => $queues,
|
||||
'$version' => [DI::l10n()->t('Version'), App::VERSION],
|
||||
'$version_label' => DI::l10n()->t('Version'),
|
||||
'$platform' => App::PLATFORM,
|
||||
'$codename' => App::CODENAME,
|
||||
'$build' => DI::config()->get('system', 'build'),
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ class Config extends BaseApi
|
|||
'broughtby' => '',
|
||||
'broughtbyurl' => '',
|
||||
'timezone' => DI::config()->get('system', 'default_timezone'),
|
||||
'closed' => (DI::config()->get('config', 'register_policy') == Register::CLOSED),
|
||||
'closed' => Register::getPolicy() === Register::CLOSED,
|
||||
'inviteonly' => (bool)DI::config()->get('system', 'invitation_only'),
|
||||
'private' => (bool)DI::config()->get('system', 'block_public'),
|
||||
'textlimit' => (string) DI::config()->get('config', 'api_import_size', DI::config()->get('config', 'max_import_size')),
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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']);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -71,6 +71,7 @@ class UpdateCredentials extends BaseApi
|
|||
}
|
||||
|
||||
if ($user['account-type'] == Contact::TYPE_COMMUNITY) {
|
||||
// @todo Support for PAGE_FLAGS_COMM_MAN
|
||||
$user['page-flags'] = $request['locked'] ? User::PAGE_FLAGS_PRVGROUP : User::PAGE_FLAGS_COMMUNITY;
|
||||
} elseif ($user['account-type'] == Contact::TYPE_PERSON) {
|
||||
if ($request['locked']) {
|
||||
|
|
@ -99,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());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ use Friendica\Database\DBA;
|
|||
use Friendica\Model\GServer;
|
||||
use Friendica\Module\BaseApi;
|
||||
use Friendica\Network\HTTPException;
|
||||
use Friendica\Util\Network;
|
||||
use GuzzleHttp\Psr7\Uri;
|
||||
|
||||
/**
|
||||
* Undocumented API endpoint that is implemented by both Mastodon and Pleroma
|
||||
|
|
@ -47,7 +47,7 @@ class Peers extends BaseApi
|
|||
while ($instance = DBA::fetch($instances)) {
|
||||
$urldata = parse_url($instance['url']);
|
||||
unset($urldata['scheme']);
|
||||
$return[] = ltrim(Network::unparseURL($urldata), '/');
|
||||
$return[] = ltrim((string)Uri::fromParts((array)$urldata), '/');
|
||||
}
|
||||
DBA::close($instances);
|
||||
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
@ -166,9 +173,9 @@ class InstanceV2 extends BaseApi
|
|||
|
||||
private function buildRegistrationsInfo(): InstanceEntity\Registrations
|
||||
{
|
||||
$register_policy = intval($this->config->get('config', 'register_policy'));
|
||||
$enabled = ($register_policy != Register::CLOSED);
|
||||
$approval_required = ($register_policy == Register::APPROVE);
|
||||
$register_policy = Register::getPolicy();
|
||||
$enabled = $register_policy !== Register::CLOSED;
|
||||
$approval_required = $register_policy === Register::APPROVE;
|
||||
|
||||
return new InstanceEntity\Registrations($enabled, $approval_required);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,16 +21,36 @@
|
|||
|
||||
namespace Friendica\Module\Api\Mastodon;
|
||||
|
||||
use Friendica\Core\System;
|
||||
use Friendica\App;
|
||||
use Friendica\Core\L10n;
|
||||
use Friendica\DI;
|
||||
use Friendica\Content\Conversation\Factory\Channel as ChannelFactory;
|
||||
use Friendica\Content\Conversation\Repository;
|
||||
use Friendica\Content\GroupManager;
|
||||
use Friendica\Module\BaseApi;
|
||||
use Friendica\Model\Circle;
|
||||
use Friendica\Module\Api\ApiResponse;
|
||||
use Friendica\Util\Profiler;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
* @see https://docs.joinmastodon.org/methods/timelines/lists/
|
||||
*/
|
||||
class Lists extends BaseApi
|
||||
{
|
||||
/** @var ChannelFactory */
|
||||
protected $channel;
|
||||
/** @var Repository\UserDefinedChannel */
|
||||
protected $userDefinedChannel;
|
||||
|
||||
public function __construct(Repository\UserDefinedChannel $userDefinedChannel, ChannelFactory $channel, \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->channel = $channel;
|
||||
$this->userDefinedChannel = $userDefinedChannel;
|
||||
}
|
||||
|
||||
protected function delete(array $request = [])
|
||||
{
|
||||
$this->checkAllowedScope(self::SCOPE_WRITE);
|
||||
|
|
@ -102,6 +122,18 @@ class Lists extends BaseApi
|
|||
foreach (Circle::getByUserId($uid) as $circle) {
|
||||
$lists[] = DI::mstdnList()->createFromCircleId($circle['id']);
|
||||
}
|
||||
|
||||
foreach ($this->channel->getTimelines($uid) as $channel) {
|
||||
$lists[] = DI::mstdnList()->createFromChannel($channel);
|
||||
}
|
||||
|
||||
foreach ($this->userDefinedChannel->selectByUid($uid) as $channel) {
|
||||
$lists[] = DI::mstdnList()->createFromChannel($channel);
|
||||
}
|
||||
|
||||
foreach (GroupManager::getList($uid, true, true, true) as $group) {
|
||||
$lists[] = DI::mstdnList()->createFromGroup($group);
|
||||
}
|
||||
} else {
|
||||
$id = $this->parameters['id'];
|
||||
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ use Friendica\DI;
|
|||
use Friendica\Model\Contact;
|
||||
use Friendica\Model\Item;
|
||||
use Friendica\Model\Post;
|
||||
use Friendica\Model\Post\SearchIndex;
|
||||
use Friendica\Model\Tag;
|
||||
use Friendica\Module\BaseApi;
|
||||
use Friendica\Util\Network;
|
||||
|
|
@ -159,7 +160,7 @@ class Search extends BaseApi
|
|||
} else {
|
||||
$q = Post\Engagement::escapeKeywords($q);
|
||||
$condition = ["MATCH (`searchtext`) AGAINST (? IN BOOLEAN MODE) AND (NOT `restricted` OR `uri-id` IN (SELECT `uri-id` FROM `post-user` WHERE `uid` = ?))", $q, $uid];
|
||||
$table = 'post-searchindex';
|
||||
$table = SearchIndex::getSearchTable();
|
||||
}
|
||||
|
||||
if (!empty($account_id)) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -83,6 +84,10 @@ class Statuses extends BaseApi
|
|||
$item['network'] = $post['network'];
|
||||
$item['gravity'] = $post['gravity'];
|
||||
$item['verb'] = $post['verb'];
|
||||
$item['allow_cid'] = $post['allow_cid'];
|
||||
$item['allow_gid'] = $post['allow_gid'];
|
||||
$item['deny_cid'] = $post['deny_cid'];
|
||||
$item['deny_gid'] = $post['deny_gid'];
|
||||
$item['app'] = $this->getApp();
|
||||
$item['sensitive'] = $request['sensitive'];
|
||||
|
||||
|
|
@ -393,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']));
|
||||
|
|
@ -405,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;
|
||||
|
|
|
|||
|
|
@ -21,7 +21,6 @@
|
|||
|
||||
namespace Friendica\Module\Api\Mastodon\Statuses;
|
||||
|
||||
use Friendica\Core\System;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\DI;
|
||||
use Friendica\Model\Item;
|
||||
|
|
@ -54,7 +53,7 @@ class Bookmark extends BaseApi
|
|||
if ($item['uid'] == 0) {
|
||||
$stored = Item::storeForUserByUriId($item['uri-id'], $uid, ['post-reason' => Item::PR_ACTIVITY]);
|
||||
if (!empty($stored)) {
|
||||
$item = Post::selectFirst(['id', 'gravity'], ['id' => $stored]);
|
||||
$item = Post::selectFirst(['id', 'uri-id', 'gravity'], ['id' => $stored]);
|
||||
if (!DBA::isResult($item)) {
|
||||
$this->logAndJsonError(404, $this->errorFactory->RecordNotFound());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ class Reblog extends BaseApi
|
|||
|
||||
if ($item['network'] == Protocol::DIASPORA) {
|
||||
Diaspora::performReshare($this->parameters['id'], $uid);
|
||||
} elseif (!in_array($item['network'], [Protocol::DFRN, Protocol::ACTIVITYPUB, Protocol::TWITTER])) {
|
||||
} elseif (!in_array($item['network'], [Protocol::DFRN, Protocol::ACTIVITYPUB, Protocol::BLUESKY, Protocol::TUMBLR, Protocol::TWITTER])) {
|
||||
$this->logAndJsonError(
|
||||
422,
|
||||
$this->errorFactory->UnprocessableEntity($this->t("Posts from %s can't be shared", ContactSelector::networkToName($item['network'])))
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ class Unreblog extends BaseApi
|
|||
if (!Item::markForDeletionById($item['id'])) {
|
||||
$this->logAndJsonError(404, $this->errorFactory->RecordNotFound());
|
||||
}
|
||||
} elseif (!in_array($item['network'], [Protocol::DFRN, Protocol::ACTIVITYPUB, Protocol::TWITTER])) {
|
||||
} elseif (!in_array($item['network'], [Protocol::DFRN, Protocol::ACTIVITYPUB, Protocol::BLUESKY, Protocol::TUMBLR, Protocol::TWITTER])) {
|
||||
$this->logAndJsonError(
|
||||
422,
|
||||
$this->errorFactory->UnprocessableEntity($this->t("Posts from %s can't be unshared", ContactSelector::networkToName($item['network'])))
|
||||
|
|
|
|||
|
|
@ -21,21 +21,39 @@
|
|||
|
||||
namespace Friendica\Module\Api\Mastodon\Timelines;
|
||||
|
||||
use Friendica\App;
|
||||
use Friendica\Core\L10n;
|
||||
use Friendica\Core\Logger;
|
||||
use Friendica\Core\System;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\DI;
|
||||
use Friendica\Model\Contact;
|
||||
use Friendica\Model\Conversation;
|
||||
use Friendica\Model\Item;
|
||||
use Friendica\Model\Post;
|
||||
use Friendica\Model\Verb;
|
||||
use Friendica\Module\Api\ApiResponse;
|
||||
use Friendica\Module\BaseApi;
|
||||
use Friendica\Module\Conversation\Timeline;
|
||||
use Friendica\Network\HTTPException;
|
||||
use Friendica\Object\Api\Mastodon\TimelineOrderByTypes;
|
||||
use Friendica\Protocol\Activity;
|
||||
use Friendica\Util\Profiler;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
* @see https://docs.joinmastodon.org/methods/timelines/
|
||||
*/
|
||||
class ListTimeline extends BaseApi
|
||||
{
|
||||
/** @var Timeline */
|
||||
protected $timeline;
|
||||
|
||||
public function __construct(Timeline $timeline, \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->timeline = $timeline;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws HTTPException\InternalServerErrorException
|
||||
*/
|
||||
|
|
@ -61,6 +79,69 @@ class ListTimeline extends BaseApi
|
|||
'friendica_order' => TimelineOrderByTypes::ID, // Sort order options (defaults to ID)
|
||||
], $request);
|
||||
|
||||
$display_quotes = self::appSupportsQuotes();
|
||||
|
||||
if (substr($this->parameters['id'], 0, 6) == 'group:') {
|
||||
$items = $this->getStatusesForGroup($uid, $request);
|
||||
} elseif (substr($this->parameters['id'], 0, 8) == 'channel:') {
|
||||
$items = $this->getStatusesForChannel($uid, $request);
|
||||
} else{
|
||||
$items = $this->getStatusesForCircle($uid, $request);
|
||||
}
|
||||
|
||||
$statuses = [];
|
||||
foreach ($items as $item) {
|
||||
try {
|
||||
$status = DI::mstdnStatus()->createFromUriId($item['uri-id'], $uid, $display_quotes);
|
||||
$this->updateBoundaries($status, $item, $request['friendica_order']);
|
||||
$statuses[] = $status;
|
||||
} catch (\Throwable $th) {
|
||||
Logger::info('Post not fetchable', ['uri-id' => $item['uri-id'], 'uid' => $uid, 'error' => $th]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($request['min_id'])) {
|
||||
$statuses = array_reverse($statuses);
|
||||
}
|
||||
|
||||
self::setLinkHeader($request['friendica_order'] != TimelineOrderByTypes::ID);
|
||||
$this->jsonExit($statuses);
|
||||
}
|
||||
|
||||
private function getStatusesForGroup(int $uid, array $request): array
|
||||
{
|
||||
$cid = Contact::getPublicContactId((int)substr($this->parameters['id'], 6), $uid);
|
||||
|
||||
$condition = ["(`uid` = ? OR (`uid` = ? AND NOT `global`))", 0, $uid];
|
||||
|
||||
$condition1 = DBA::mergeConditions($condition, ["`owner-id` = ? AND `gravity` = ?", $cid, Item::GRAVITY_PARENT]);
|
||||
|
||||
$condition2 = DBA::mergeConditions($condition, [
|
||||
"`author-id` = ? AND `gravity` = ? AND `vid` = ? AND `protocol` != ? AND `thr-parent-id` = `parent-uri-id`",
|
||||
$cid, Item::GRAVITY_ACTIVITY, Verb::getID(Activity::ANNOUNCE), Conversation::PARCEL_DIASPORA
|
||||
]);
|
||||
|
||||
$condition1 = $this->addPagingConditions($request, $condition1);
|
||||
$condition2 = $this->addPagingConditions($request, $condition2);
|
||||
|
||||
$sql1 = "SELECT `uri-id` FROM `post-thread-user-view` WHERE " . array_shift($condition1);
|
||||
$sql2 = "SELECT `thr-parent-id` AS `uri-id` FROM `post-user-view` WHERE " . array_shift($condition2);
|
||||
|
||||
$condition = array_merge($condition1, $condition2);
|
||||
$sql = $sql1 . " UNION " . $sql2 . " GROUP BY `uri-id` " . DBA::buildParameter($this->buildOrderAndLimitParams($request));
|
||||
|
||||
return Post::toArray(DBA::p($sql, $condition));
|
||||
}
|
||||
|
||||
private function getStatusesForChannel(int $uid, array $request): array
|
||||
{
|
||||
$request['friendica_order'] = TimelineOrderByTypes::ID;
|
||||
|
||||
return $this->timeline->getChannelItemsForAPI(substr($this->parameters['id'], 8), $uid, $request['limit'], $request['min_id'], $request['max_id']);
|
||||
}
|
||||
|
||||
private function getStatusesForCircle(int $uid, array $request): array
|
||||
{
|
||||
$condition = [
|
||||
"`uid` = ? AND `gravity` IN (?, ?) AND `contact-id` IN (SELECT `contact-id` FROM `group_member` WHERE `gid` = ?)",
|
||||
$uid, Item::GRAVITY_PARENT, Item::GRAVITY_COMMENT, $this->parameters['id']
|
||||
|
|
@ -89,26 +170,6 @@ class ListTimeline extends BaseApi
|
|||
}
|
||||
|
||||
$items = Post::selectTimelineForUser($uid, ['uri-id'], $condition, $params);
|
||||
|
||||
$display_quotes = self::appSupportsQuotes();
|
||||
|
||||
$statuses = [];
|
||||
while ($item = Post::fetch($items)) {
|
||||
try {
|
||||
$status = DI::mstdnStatus()->createFromUriId($item['uri-id'], $uid, $display_quotes);
|
||||
$this->updateBoundaries($status, $item, $request['friendica_order']);
|
||||
$statuses[] = $status;
|
||||
} catch (\Throwable $th) {
|
||||
Logger::info('Post not fetchable', ['uri-id' => $item['uri-id'], 'uid' => $uid, 'error' => $th]);
|
||||
}
|
||||
}
|
||||
DBA::close($items);
|
||||
|
||||
if (!empty($request['min_id'])) {
|
||||
$statuses = array_reverse($statuses);
|
||||
}
|
||||
|
||||
self::setLinkHeader($request['friendica_order'] != TimelineOrderByTypes::ID);
|
||||
$this->jsonExit($statuses);
|
||||
return Post::toArray($items);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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`)"]);
|
||||
}
|
||||
|
||||
|
|
@ -85,7 +110,11 @@ class PublicTimeline extends BaseApi
|
|||
$condition = DBA::mergeConditions($condition, ['gravity' => Item::GRAVITY_PARENT]);
|
||||
}
|
||||
|
||||
$items = Post::selectTimelineForUser($uid, ['uri-id'], $condition, $params);
|
||||
if ($request['local']) {
|
||||
$items = Post::selectLocalTimelineForUser($uid, ['uri-id'], $condition, $params);
|
||||
} else {
|
||||
$items = Post::selectTimelineForUser($uid, ['uri-id'], $condition, $params);
|
||||
}
|
||||
|
||||
$display_quotes = self::appSupportsQuotes();
|
||||
|
||||
|
|
@ -109,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]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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([
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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', ''));
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ class Retweet extends BaseApi
|
|||
$item = Post::selectFirst($fields, ['uri-id' => $id, 'uid' => [0, $uid], 'private' => [Item::PUBLIC, Item::UNLISTED]], ['order' => ['uid' => true]]);
|
||||
|
||||
if (DBA::isResult($item) && !empty($item['body'])) {
|
||||
if (in_array($item['network'], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::TWITTER])) {
|
||||
if (in_array($item['network'], [Protocol::ACTIVITYPUB, Protocol::DFRN, Protocol::BLUESKY, Protocol::TUMBLR, Protocol::TWITTER])) {
|
||||
if (!Item::performActivity($id, 'announce', $uid)) {
|
||||
throw new InternalServerErrorException();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ abstract class BaseAdmin extends BaseModule
|
|||
}
|
||||
}
|
||||
|
||||
if (!DI::app()->isSiteAdmin()) {
|
||||
if (!DI::userSession()->isSiteAdmin()) {
|
||||
throw new HTTPException\ForbiddenException(DI::l10n()->t('You don\'t have access to administration pages.'));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ class BaseProfile extends BaseModule
|
|||
];
|
||||
} else {
|
||||
$owner = User::getByNickname($nickname, ['uid']);
|
||||
if(DI::userSession()->isAuthenticated() || $owner && Feature::isEnabled($owner['uid'], 'public_calendar')) {
|
||||
if(DI::userSession()->isAuthenticated() || $owner && Feature::isEnabled($owner['uid'], Feature::PUBLIC_CALENDAR)) {
|
||||
$tabs[] = [
|
||||
'label' => DI::l10n()->t('Calendar'),
|
||||
'url' => DI::baseUrl() . '/calendar/show/' . $nickname,
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ class Bookmarklet extends BaseModule
|
|||
|
||||
if (!DI::userSession()->getLocalUserId()) {
|
||||
$output = '<h2>' . DI::l10n()->t('Login') . '</h2>';
|
||||
$output .= Login::form(DI::args()->getQueryString(), intval($config->get('config', 'register_policy')) === Register::CLOSED ? false : true);
|
||||
$output .= Login::form(DI::args()->getQueryString(), Register::getPolicy() !== Register::CLOSED);
|
||||
return $output;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,14 +22,11 @@
|
|||
namespace Friendica\Module\Calendar\Event;
|
||||
|
||||
use Friendica\App;
|
||||
use Friendica\Content\Feature;
|
||||
use Friendica\Core\L10n;
|
||||
use Friendica\Core\Session\Capability\IHandleUserSessions;
|
||||
use Friendica\Core\System;
|
||||
use Friendica\Model\Event;
|
||||
use Friendica\Model\Item;
|
||||
use Friendica\Model\Post;
|
||||
use Friendica\Model\User;
|
||||
use Friendica\Module\Response;
|
||||
use Friendica\Network\HTTPException;
|
||||
use Friendica\Util\DateTimeFormat;
|
||||
|
|
@ -46,17 +43,16 @@ class Get extends \Friendica\BaseModule
|
|||
/** @var IHandleUserSessions */
|
||||
protected $session;
|
||||
|
||||
public function __construct(App $app, L10n $l10n, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, IHandleUserSessions $session, array $server, array $parameters = [])
|
||||
public function __construct(L10n $l10n, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, IHandleUserSessions $session, array $server, array $parameters = [])
|
||||
{
|
||||
parent::__construct($l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters);
|
||||
|
||||
$this->session = $session;
|
||||
$this->app = $app;
|
||||
}
|
||||
|
||||
protected function rawContent(array $request = [])
|
||||
{
|
||||
$nickname = $this->parameters['nickname'] ?? $this->app->getLoggedInUserNickname();
|
||||
$nickname = $this->parameters['nickname'] ?? $this->session->getLocalUserNickname();
|
||||
if (!$nickname) {
|
||||
throw new HTTPException\UnauthorizedException();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ class Show extends BaseModule
|
|||
|
||||
protected function rawContent(array $request = [])
|
||||
{
|
||||
$nickname = $this->parameters['nickname'] ?? $this->app->getLoggedInUserNickname();
|
||||
$nickname = $this->parameters['nickname'] ?? $this->session->getLocalUserNickname();
|
||||
if (!$nickname) {
|
||||
throw new HTTPException\UnauthorizedException();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ class Export extends BaseModule
|
|||
$this->baseUrl->redirect('profile/' . $nickname . '/restricted');
|
||||
}
|
||||
|
||||
if (!$this->session->isAuthenticated() && !Feature::isEnabled($owner['uid'], 'public_calendar')) {
|
||||
if (!$this->session->isAuthenticated() && !Feature::isEnabled($owner['uid'], Feature::PUBLIC_CALENDAR)) {
|
||||
$this->sysMessages->addNotice($this->t('Permission denied.'));
|
||||
$this->baseUrl->redirect('profile/' . $nickname);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ class Show extends BaseModule
|
|||
$this->baseUrl->redirect('profile/' . $nickname . '/restricted');
|
||||
}
|
||||
|
||||
if (!$this->session->isAuthenticated() && !Feature::isEnabled($owner['uid'], 'public_calendar')) {
|
||||
if (!$this->session->isAuthenticated() && !Feature::isEnabled($owner['uid'], Feature::PUBLIC_CALENDAR)) {
|
||||
$this->sysMessages->addNotice($this->t('Permission denied.'));
|
||||
return Login::form();
|
||||
}
|
||||
|
|
@ -91,7 +91,7 @@ class Show extends BaseModule
|
|||
$this->page->registerFooterScript('view/asset/moment/min/moment-with-locales.min.js');
|
||||
$this->page->registerFooterScript('view/asset/fullcalendar/dist/fullcalendar.min.js');
|
||||
|
||||
$is_owner = $nickname == $this->app->getLoggedInUserNickname();
|
||||
$is_owner = $nickname == $this->session->getLocalUserNickname();
|
||||
|
||||
$htpl = Renderer::getMarkupTemplate('calendar/calendar_head.tpl');
|
||||
$this->page['htmlhead'] .= Renderer::replaceMacros($htpl, [
|
||||
|
|
|
|||
|
|
@ -141,13 +141,15 @@ class Circle extends BaseModule
|
|||
|
||||
protected function content(array $request = []): string
|
||||
{
|
||||
$change = false;
|
||||
$change = false;
|
||||
$relation = $request['rel'] ?? '';
|
||||
|
||||
if (!DI::userSession()->getLocalUserId()) {
|
||||
throw new \Friendica\Network\HTTPException\ForbiddenException();
|
||||
}
|
||||
|
||||
DI::page()['aside'] = Model\Circle::sidebarWidget('contact', 'circle', 'extended', ((DI::args()->getArgc() > 1) ? DI::args()->getArgv()[1] : 'everyone'));
|
||||
DI::page()['aside'] .= Widget::contactRels($this->server['REQUEST_URI'], $relation);
|
||||
|
||||
// With no circle number provided we jump to the unassigned contacts as a starting point
|
||||
// @TODO: Replace with parameter from router
|
||||
|
|
@ -298,6 +300,9 @@ class Circle extends BaseModule
|
|||
|
||||
// Format the data of the circle members
|
||||
foreach ($members as $member) {
|
||||
if (!self::matchRelation($relation, $member['rel'])) {
|
||||
continue;
|
||||
}
|
||||
if ($member['url']) {
|
||||
$entry = Contact::getContactTemplateVars($member);
|
||||
$entry['label'] = 'members';
|
||||
|
|
@ -332,6 +337,9 @@ class Circle extends BaseModule
|
|||
if (DBA::isResult($contacts)) {
|
||||
// Format the data of the contacts who aren't in the contact circle
|
||||
foreach ($contacts as $member) {
|
||||
if (!self::matchRelation($relation, $member['rel'])) {
|
||||
continue;
|
||||
}
|
||||
if (!in_array($member['id'], $preselected)) {
|
||||
$entry = Contact::getContactTemplateVars($member);
|
||||
$entry['label'] = 'contacts';
|
||||
|
|
@ -366,4 +374,19 @@ class Circle extends BaseModule
|
|||
|
||||
return Renderer::replaceMacros($tpl, $context);
|
||||
}
|
||||
|
||||
private static function matchRelation(string $relation, int $rel)
|
||||
{
|
||||
switch ($relation) {
|
||||
case 'followers':
|
||||
return($rel == Model\Contact::FOLLOWER);
|
||||
case 'following':
|
||||
return($rel == Model\Contact::SHARING);
|
||||
case 'mutuals':
|
||||
return($rel == Model\Contact::FRIEND);
|
||||
case 'nothing':
|
||||
return($rel == Model\Contact::NOTHING);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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.'));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -32,12 +32,12 @@ use Friendica\Core\Session\Capability\IHandleUserSessions;
|
|||
use Friendica\Model\Contact;
|
||||
use Friendica\Model\Item;
|
||||
use Friendica\Model\Post;
|
||||
use Friendica\Model\Profile;
|
||||
use Friendica\Model\User;
|
||||
use Friendica\Module\Response;
|
||||
use Friendica\Navigation\SystemMessages;
|
||||
use Friendica\Network\HTTPException\ForbiddenException;
|
||||
use Friendica\Network\Probe;
|
||||
use Friendica\Security\OpenWebAuth;
|
||||
use Friendica\Util\Profiler;
|
||||
use Friendica\Util\Strings;
|
||||
use GuzzleHttp\Psr7\Uri;
|
||||
|
|
@ -175,7 +175,7 @@ class Follow extends BaseModule
|
|||
'$action' => $requestUrl,
|
||||
'$name' => $contact['name'],
|
||||
'$url' => $contact['url'],
|
||||
'$zrl' => Profile::zrl($contact['url']),
|
||||
'$zrl' => OpenWebAuth::getZrlUrl($contact['url']),
|
||||
'$myaddr' => $myaddr,
|
||||
'$keywords' => $contact['keywords'],
|
||||
|
||||
|
|
@ -186,7 +186,7 @@ class Follow extends BaseModule
|
|||
$this->page['aside'] = '';
|
||||
|
||||
if (!in_array($protocol, [Protocol::PHANTOM, Protocol::MAIL])) {
|
||||
$this->page['aside'] = VCard::getHTML($contact);
|
||||
$this->page['aside'] = VCard::getHTML($contact, false, true);
|
||||
|
||||
$output .= Renderer::replaceMacros(Renderer::getMarkupTemplate('section_title.tpl'),
|
||||
['$title' => $this->t('Posts and Replies')]
|
||||
|
|
@ -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.'));
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ use Friendica\Module\Contact as ModuleContact;
|
|||
use Friendica\Module\Response;
|
||||
use Friendica\Navigation\SystemMessages;
|
||||
use Friendica\Network\HTTPClient\Capability\ICanSendHttpRequests;
|
||||
use Friendica\Network\HTTPClient\Client\HttpClientRequest;
|
||||
use Friendica\Network\HTTPException\InternalServerErrorException;
|
||||
use Friendica\Util\Profiler;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
|
@ -122,10 +123,10 @@ class MatchInterests extends BaseModule
|
|||
continue;
|
||||
}
|
||||
|
||||
$result = $this->httpClient->post($server . '/search/user/tags', $searchParameters);
|
||||
$result = $this->httpClient->post($server . '/search/user/tags', $searchParameters, [], 0, HttpClientRequest::CONTACTDISCOVER);
|
||||
if (!$result->isSuccess()) {
|
||||
// try legacy endpoint
|
||||
$result = $this->httpClient->post($server . '/msearch', $searchParameters);
|
||||
$result = $this->httpClient->post($server . '/msearch', $searchParameters, [], 0, HttpClientRequest::CONTACTDISCOVER);
|
||||
if (!$result->isSuccess()) {
|
||||
$this->logger->notice('Search-Endpoint not available for server.', ['server' => $server]);
|
||||
continue;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.'));
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ namespace Friendica\Module\Contact;
|
|||
use Friendica\App;
|
||||
use Friendica\BaseModule;
|
||||
use Friendica\Contact\LocalRelationship;
|
||||
use Friendica\Contact\LocalRelationship\Entity\LocalRelationship as LocalRelationshipEntity;
|
||||
use Friendica\Content\ContactSelector;
|
||||
use Friendica\Content\Nav;
|
||||
use Friendica\Content\Text\BBCode;
|
||||
|
|
@ -90,37 +91,37 @@ 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;
|
||||
}
|
||||
|
||||
Hook::callAll('contact_edit_post', $_POST);
|
||||
Hook::callAll('contact_edit_post', $request);
|
||||
|
||||
$fields = [];
|
||||
|
||||
if (isset($_POST['hidden'])) {
|
||||
$fields['hidden'] = !empty($_POST['hidden']);
|
||||
if (isset($request['hidden'])) {
|
||||
$fields['hidden'] = !empty($request['hidden']);
|
||||
}
|
||||
|
||||
if (isset($_POST['notify_new_posts'])) {
|
||||
$fields['notify_new_posts'] = !empty($_POST['notify_new_posts']);
|
||||
if (isset($request['notify_new_posts'])) {
|
||||
$fields['notify_new_posts'] = !empty($request['notify_new_posts']);
|
||||
}
|
||||
|
||||
if (isset($_POST['fetch_further_information'])) {
|
||||
$fields['fetch_further_information'] = intval($_POST['fetch_further_information']);
|
||||
if (isset($request['fetch_further_information'])) {
|
||||
$fields['fetch_further_information'] = intval($request['fetch_further_information']);
|
||||
}
|
||||
|
||||
if (isset($_POST['remote_self'])) {
|
||||
$fields['remote_self'] = intval($_POST['remote_self']);
|
||||
if (isset($request['remote_self'])) {
|
||||
$fields['remote_self'] = intval($request['remote_self']);
|
||||
}
|
||||
|
||||
if (isset($_POST['ffi_keyword_denylist'])) {
|
||||
$fields['ffi_keyword_denylist'] = $_POST['ffi_keyword_denylist'];
|
||||
if (isset($request['ffi_keyword_denylist'])) {
|
||||
$fields['ffi_keyword_denylist'] = $request['ffi_keyword_denylist'];
|
||||
}
|
||||
|
||||
if (isset($_POST['poll'])) {
|
||||
$priority = intval($_POST['poll']);
|
||||
if (isset($request['poll'])) {
|
||||
$priority = intval($request['poll']);
|
||||
if ($priority > 5 || $priority < 0) {
|
||||
$priority = 0;
|
||||
}
|
||||
|
|
@ -128,15 +129,19 @@ class Profile extends BaseModule
|
|||
$fields['priority'] = $priority;
|
||||
}
|
||||
|
||||
if (isset($_POST['info'])) {
|
||||
$fields['info'] = $_POST['info'];
|
||||
if (isset($request['info'])) {
|
||||
$fields['info'] = $request['info'];
|
||||
}
|
||||
|
||||
if (isset($_POST['channel_frequency'])) {
|
||||
Contact\User::setChannelFrequency($cdata['user'], $this->session->getLocalUserId(), $_POST['channel_frequency']);
|
||||
if (isset($request['channel_frequency'])) {
|
||||
Contact\User::setChannelFrequency($ucid, $this->session->getLocalUserId(), $request['channel_frequency']);
|
||||
}
|
||||
|
||||
if (!Contact::update($fields, ['id' => $cdata['user'], 'uid' => $this->session->getLocalUserId()])) {
|
||||
if (isset($request['channel_only'])) {
|
||||
Contact\User::setChannelOnly($ucid, $this->session->getLocalUserId(), $request['channel_only']);
|
||||
}
|
||||
|
||||
if (!Contact::update($fields, ['id' => $ucid, 'uid' => $this->session->getLocalUserId()])) {
|
||||
$this->systemMessages->addNotice($this->t('Failed to update contact record.'));
|
||||
}
|
||||
}
|
||||
|
|
@ -159,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.'));
|
||||
}
|
||||
|
||||
|
|
@ -319,28 +338,29 @@ class Profile extends BaseModule
|
|||
|
||||
if ($contact['network'] == Protocol::FEED) {
|
||||
$remote_self_options = [
|
||||
Contact::MIRROR_DEACTIVATED => $this->t('No mirroring'),
|
||||
Contact::MIRROR_OWN_POST => $this->t('Mirror as my own posting')
|
||||
LocalRelationshipEntity::MIRROR_DEACTIVATED => $this->t('No mirroring'),
|
||||
LocalRelationshipEntity::MIRROR_OWN_POST => $this->t('Mirror as my own posting')
|
||||
];
|
||||
} elseif ($contact['network'] == Protocol::ACTIVITYPUB) {
|
||||
$remote_self_options = [
|
||||
Contact::MIRROR_DEACTIVATED => $this->t('No mirroring'),
|
||||
Contact::MIRROR_NATIVE_RESHARE => $this->t('Native reshare')
|
||||
LocalRelationshipEntity::MIRROR_DEACTIVATED => $this->t('No mirroring'),
|
||||
LocalRelationshipEntity::MIRROR_NATIVE_RESHARE => $this->t('Native reshare')
|
||||
];
|
||||
} elseif ($contact['network'] == Protocol::DFRN) {
|
||||
$remote_self_options = [
|
||||
Contact::MIRROR_DEACTIVATED => $this->t('No mirroring'),
|
||||
Contact::MIRROR_OWN_POST => $this->t('Mirror as my own posting'),
|
||||
Contact::MIRROR_NATIVE_RESHARE => $this->t('Native reshare')
|
||||
LocalRelationshipEntity::MIRROR_DEACTIVATED => $this->t('No mirroring'),
|
||||
LocalRelationshipEntity::MIRROR_OWN_POST => $this->t('Mirror as my own posting'),
|
||||
LocalRelationshipEntity::MIRROR_NATIVE_RESHARE => $this->t('Native reshare')
|
||||
];
|
||||
} else {
|
||||
$remote_self_options = [
|
||||
Contact::MIRROR_DEACTIVATED => $this->t('No mirroring'),
|
||||
Contact::MIRROR_OWN_POST => $this->t('Mirror as my own posting')
|
||||
LocalRelationshipEntity::MIRROR_DEACTIVATED => $this->t('No mirroring'),
|
||||
LocalRelationshipEntity::MIRROR_OWN_POST => $this->t('Mirror as my own posting')
|
||||
];
|
||||
}
|
||||
|
||||
$channel_frequency = Contact\User::getChannelFrequency($contact['id'], $this->session->getLocalUserId());
|
||||
$channel_frequency = Contact\User::getChannelFrequency($contact['id'], $this->session->getLocalUserId());
|
||||
$channel_only = Contact\User::getChannelOnly($contact['id'], $this->session->getLocalUserId());
|
||||
|
||||
$poll_interval = null;
|
||||
if ((($contact['network'] == Protocol::FEED) && !$this->config->get('system', 'adjust_poll_frequency')) || ($contact['network'] == Protocol::MAIL)) {
|
||||
|
|
@ -432,6 +452,7 @@ class Profile extends BaseModule
|
|||
'$frequency_always' => ['channel_frequency', $this->t('Display all posts of this contact'), Contact\User::FREQUENCY_ALWAYS, $this->t('All posts from this contact will appear on the "for you" channel'), $channel_frequency == Contact\User::FREQUENCY_ALWAYS],
|
||||
'$frequency_reduced' => ['channel_frequency', $this->t('Display only few posts'), Contact\User::FREQUENCY_REDUCED, $this->t('When a contact creates a lot of posts in a short period, this setting reduces the number of displayed posts in every channel.'), $channel_frequency == Contact\User::FREQUENCY_REDUCED],
|
||||
'$frequency_never' => ['channel_frequency', $this->t('Never display posts'), Contact\User::FREQUENCY_NEVER, $this->t('Posts from this contact will never be displayed in any channel'), $channel_frequency == Contact\User::FREQUENCY_NEVER],
|
||||
'$channel_only' => ['channel_only', $this->t('Channel Only'), $channel_only, $this->t('If enabled, posts from this contact will only appear in channels and network streams in circles, but not in the general network stream.')],
|
||||
]);
|
||||
|
||||
$arr = ['contact' => $contact, 'output' => $o];
|
||||
|
|
|
|||
|
|
@ -23,16 +23,16 @@ namespace Friendica\Module\Contact;
|
|||
|
||||
use Friendica\Core\L10n;
|
||||
use Friendica\App;
|
||||
use Friendica\Core\Protocol;
|
||||
use Friendica\Core\Session\Capability\IHandleUserSessions;
|
||||
use Friendica\Core\Worker;
|
||||
use Friendica\Database\Database;
|
||||
use Friendica\Model\Contact;
|
||||
use Friendica\Module\Response;
|
||||
use Friendica\Network\HTTPClient\Capability\ICanSendHttpRequests;
|
||||
use Friendica\Network\HTTPClient\Client\HttpClientAccept;
|
||||
use Friendica\Network\HTTPClient\Client\HttpClientOptions;
|
||||
use Friendica\Network\HTTPException;
|
||||
use Friendica\Util\Profiler;
|
||||
use Friendica\Util\Strings;
|
||||
use Friendica\Worker\UpdateContact;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class Redir extends \Friendica\BaseModule
|
||||
|
|
@ -43,17 +43,14 @@ class Redir extends \Friendica\BaseModule
|
|||
private $database;
|
||||
/** @var App */
|
||||
private $app;
|
||||
/** @var ICanSendHttpRequests */
|
||||
private $httpClient;
|
||||
|
||||
public function __construct(ICanSendHttpRequests $httpClient, App $app, Database $database, IHandleUserSessions $session, L10n $l10n, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, array $server, array $parameters = [])
|
||||
public function __construct(App $app, Database $database, IHandleUserSessions $session, L10n $l10n, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, array $server, array $parameters = [])
|
||||
{
|
||||
parent::__construct($l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters);
|
||||
|
||||
$this->session = $session;
|
||||
$this->database = $database;
|
||||
$this->app = $app;
|
||||
$this->httpClient = $httpClient;
|
||||
}
|
||||
|
||||
protected function rawContent(array $request = [])
|
||||
|
|
@ -88,17 +85,31 @@ class Redir extends \Friendica\BaseModule
|
|||
$this->logger->info('Got my url', ['visitor' => $visitor]);
|
||||
}
|
||||
|
||||
$contact = $this->database->selectFirst('contact', ['url'], ['id' => $cid]);
|
||||
$contact = Contact::selectFirst(['id', 'url', 'gsid', 'alias', 'network'], ['id' => $cid]);
|
||||
if (!$contact) {
|
||||
$this->logger->info('Contact not found', ['id' => $cid]);
|
||||
throw new HTTPException\NotFoundException($this->t('Contact not found.'));
|
||||
} else {
|
||||
$contact_url = $contact['url'];
|
||||
$this->checkUrl($contact_url, $url);
|
||||
$target_url = $url ?: $contact_url;
|
||||
} elseif (empty($contact['gsid'])) {
|
||||
$this->logger->info('Contact has got no server', ['id' => $cid]);
|
||||
return;
|
||||
}
|
||||
|
||||
$basepath = Contact::getBasepath($contact_url);
|
||||
$contact_url = Contact::getProfileLink($contact);
|
||||
$this->checkUrl($contact_url, $url);
|
||||
$target_url = $url ?: $contact_url;
|
||||
|
||||
$gserver = $this->database->selectFirst('gserver', ['url', 'network', 'openwebauth'], ['id' => $contact['gsid']]);
|
||||
$basepath = $gserver['url'];
|
||||
|
||||
// This part can be removed, when all server entries had been updated. So removing it in 2025 should be safe.
|
||||
if (empty($gserver['openwebauth']) && ($gserver['network'] == Protocol::DFRN)) {
|
||||
$this->logger->notice('Magic path not provided. Contact update initiated.', ['gsid' => $contact['gsid']]);
|
||||
// Update contact to assign the path to the server
|
||||
UpdateContact::add(Worker::PRIORITY_MEDIUM, $contact['id']);
|
||||
} elseif (empty($gserver['openwebauth'])) {
|
||||
$this->logger->debug('Server does not support open web auth.', ['server' => $gserver]);
|
||||
return;
|
||||
}
|
||||
|
||||
// We don't use magic auth when there is no visitor, we are on the same system, or we visit our own stuff
|
||||
if (empty($visitor) || Strings::compareLink($basepath, $this->baseUrl) || Strings::compareLink($contact_url, $visitor)) {
|
||||
|
|
@ -106,17 +117,11 @@ class Redir extends \Friendica\BaseModule
|
|||
$this->app->redirect($target_url);
|
||||
}
|
||||
|
||||
// Test for magic auth on the target system
|
||||
$response = $this->httpClient->head($basepath . '/magic', [HttpClientOptions::ACCEPT_CONTENT => HttpClientAccept::HTML]);
|
||||
if ($response->isSuccess()) {
|
||||
$separator = strpos($target_url, '?') ? '&' : '?';
|
||||
$target_url .= $separator . 'zrl=' . urlencode($visitor) . '&addr=' . urlencode($contact_url);
|
||||
$separator = strpos($target_url, '?') ? '&' : '?';
|
||||
$target_url .= $separator . 'zrl=' . urlencode($visitor) . '&addr=' . urlencode($contact_url);
|
||||
|
||||
$this->logger->info('Redirecting with magic', ['target' => $target_url, 'visitor' => $visitor, 'contact' => $contact_url]);
|
||||
$this->app->redirect($target_url);
|
||||
} else {
|
||||
$this->logger->info('No magic for contact', ['contact' => $contact_url]);
|
||||
}
|
||||
$this->logger->info('Redirecting with magic', ['target' => $target_url, 'visitor' => $visitor, 'contact' => $contact_url]);
|
||||
$this->app->redirect($target_url);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -134,33 +139,32 @@ class Redir extends \Friendica\BaseModule
|
|||
throw new HTTPException\BadRequestException($this->t('Bad Request.'));
|
||||
}
|
||||
|
||||
$fields = ['id', 'uid', 'nurl', 'url', 'addr', 'name'];
|
||||
$contact = $this->database->selectFirst('contact', $fields, ['id' => $cid, 'uid' => [0, $this->session->getLocalUserId()]]);
|
||||
$fields = ['id', 'uid', 'nurl', 'url', 'addr', 'name', 'alias', 'network'];
|
||||
$contact = Contact::selectFirst($fields, ['id' => $cid, 'uid' => [0, $this->session->getLocalUserId()]]);
|
||||
if (!$contact) {
|
||||
throw new HTTPException\NotFoundException($this->t('Contact not found.'));
|
||||
}
|
||||
|
||||
$contact_url = $contact['url'];
|
||||
$contact_url = Contact::getProfileLink($contact);
|
||||
$this->checkUrl($contact_url, $url);
|
||||
$target_url = $url ?: $contact_url;
|
||||
|
||||
if (!empty($this->app->getContactId()) && $this->app->getContactId() == $cid) {
|
||||
// Local user is already authenticated.
|
||||
$this->checkUrl($contact_url, $url);
|
||||
$this->app->redirect($url ?: $contact_url);
|
||||
$this->app->redirect($target_url);
|
||||
}
|
||||
|
||||
if ($contact['uid'] == 0 && $this->session->getLocalUserId()) {
|
||||
// Let's have a look if there is an established connection
|
||||
// between the public contact we have found and the local user.
|
||||
$contact = $this->database->selectFirst('contact', $fields, ['nurl' => $contact['nurl'], 'uid' => $this->session->getLocalUserId()]);
|
||||
$contact = Contact::selectFirst($fields, ['nurl' => $contact['nurl'], 'uid' => $this->session->getLocalUserId()]);
|
||||
if ($contact) {
|
||||
$cid = $contact['id'];
|
||||
}
|
||||
|
||||
if (!empty($this->app->getContactId()) && $this->app->getContactId() == $cid) {
|
||||
// Local user is already authenticated.
|
||||
$this->checkUrl($contact_url, $url);
|
||||
$target_url = $url ?: $contact_url;
|
||||
$this->logger->info($contact['name'] . " is already authenticated. Redirecting to " . $target_url);
|
||||
$this->logger->info('Contact already authenticated. Redirecting to target url', ['url' => $contact['url'], 'name' => $contact['name'], 'target_url' => $target_url]);
|
||||
$this->app->redirect($target_url);
|
||||
}
|
||||
}
|
||||
|
|
@ -175,29 +179,23 @@ class Redir extends \Friendica\BaseModule
|
|||
// contact.
|
||||
if (($host == $remotehost) && ($this->session->getRemoteContactID($this->session->get('visitor_visiting')) == $this->session->get('visitor_id'))) {
|
||||
// Remote user is already authenticated.
|
||||
$this->checkUrl($contact_url, $url);
|
||||
$target_url = $url ?: $contact_url;
|
||||
$this->logger->info($contact['name'] . " is already authenticated. Redirecting to " . $target_url);
|
||||
$this->logger->info('Contact already authenticated. Redirecting to target url', ['url' => $contact['url'], 'name' => $contact['name'], 'target_url' => $target_url]);
|
||||
$this->app->redirect($target_url);
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($url)) {
|
||||
throw new HTTPException\BadRequestException($this->t('Bad Request.'));
|
||||
}
|
||||
|
||||
// If we don't have a connected contact, redirect with
|
||||
// the 'zrl' parameter.
|
||||
$my_profile = $this->session->getMyUrl();
|
||||
|
||||
if (!empty($my_profile) && !Strings::compareLink($my_profile, $url)) {
|
||||
$separator = strpos($url, '?') ? '&' : '?';
|
||||
if (!empty($my_profile) && !Strings::compareLink($my_profile, $target_url)) {
|
||||
$separator = strpos($target_url, '?') ? '&' : '?';
|
||||
|
||||
$url .= $separator . 'zrl=' . urlencode($my_profile);
|
||||
$target_url .= $separator . 'zrl=' . urlencode($my_profile);
|
||||
}
|
||||
|
||||
$this->logger->info('redirecting to ' . $url);
|
||||
$this->app->redirect($url);
|
||||
$this->logger->info('redirecting to ' . $target_url);
|
||||
$this->app->redirect($target_url);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@ class Unfollow extends \Friendica\BaseModule
|
|||
'$keywords_label' => ''
|
||||
]);
|
||||
|
||||
$this->page['aside'] = Widget\VCard::getHTML(Contact::getByURL($contact['url'], false));
|
||||
$this->page['aside'] = Widget\VCard::getHTML(Contact::getByURL($contact['url'], false), false, true);
|
||||
|
||||
$o .= Renderer::replaceMacros(Renderer::getMarkupTemplate('section_title.tpl'), ['$title' => $this->t('Posts and Replies')]);
|
||||
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ class Channel extends Timeline
|
|||
$this->page['aside'] .= $this->getNoSharerWidget('channel');
|
||||
}
|
||||
|
||||
if (Feature::isEnabled($this->session->getLocalUserId(), 'trending_tags')) {
|
||||
if (Feature::isEnabled($this->session->getLocalUserId(), Feature::TRENDING_TAGS)) {
|
||||
$this->page['aside'] .= TrendingTags::getHTML($this->selectedTab);
|
||||
}
|
||||
|
||||
|
|
@ -128,7 +128,7 @@ class Channel extends Timeline
|
|||
}
|
||||
|
||||
if ($this->channel->isTimeline($this->selectedTab) || $this->userDefinedChannel->isTimeline($this->selectedTab, $this->session->getLocalUserId())) {
|
||||
$items = $this->getChannelItems($request);
|
||||
$items = $this->getChannelItems($request, $this->session->getLocalUserId());
|
||||
$order = 'created';
|
||||
} else {
|
||||
$items = $this->getCommunityItems();
|
||||
|
|
|
|||
|
|
@ -110,7 +110,7 @@ class Community extends Timeline
|
|||
$this->page['aside'] .= $this->getNoSharerWidget('community');
|
||||
}
|
||||
|
||||
if (Feature::isEnabled($this->session->getLocalUserId(), 'trending_tags')) {
|
||||
if (Feature::isEnabled($this->session->getLocalUserId(), Feature::TRENDING_TAGS)) {
|
||||
$this->page['aside'] .= TrendingTags::getHTML($this->selectedTab);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
@ -130,21 +132,35 @@ class Network extends Timeline
|
|||
|
||||
$o = '';
|
||||
|
||||
$this->page['aside'] .= Circle::sidebarWidget($module, $module . '/circle', 'standard', $this->circleId);
|
||||
$this->page['aside'] .= GroupManager::widget($this->session->getLocalUserId());
|
||||
$this->page['aside'] .= Widget::postedByYear($module . '/archive', $this->session->getLocalUserId(), false);
|
||||
$this->page['aside'] .= Widget::networks($module, $this->network);
|
||||
$this->page['aside'] .= Widget::accountTypes($module, $this->accountTypeString);
|
||||
$this->page['aside'] .= Widget::channels($module, $this->selectedTab, $this->session->getLocalUserId());
|
||||
$this->page['aside'] .= Widget\SavedSearches::getHTML($this->args->getQueryString());
|
||||
$this->page['aside'] .= Widget::fileAs('filed', '');
|
||||
|
||||
if (Feature::isEnabled($this->session->getLocalUserId(), Feature::CIRCLES)) {
|
||||
$this->page['aside'] .= Circle::sidebarWidget($module, $module . '/circle', 'standard', $this->circleId);
|
||||
}
|
||||
if (Feature::isEnabled($this->session->getLocalUserId(), Feature::GROUPS)) {
|
||||
$this->page['aside'] .= GroupManager::widget($this->session->getLocalUserId());
|
||||
}
|
||||
if (Feature::isEnabled($this->session->getLocalUserId(), Feature::ARCHIVE)) {
|
||||
$this->page['aside'] .= Widget::postedByYear($module . '/archive', $this->session->getLocalUserId(), false);
|
||||
}
|
||||
if (Feature::isEnabled($this->session->getLocalUserId(), Feature::NETWORKS)) {
|
||||
$this->page['aside'] .= Widget::networks($module, $this->network);
|
||||
}
|
||||
if (Feature::isEnabled($this->session->getLocalUserId(), Feature::ACCOUNTS)) {
|
||||
$this->page['aside'] .= Widget::accountTypes($module, $this->accountTypeString);
|
||||
}
|
||||
if (Feature::isEnabled($this->session->getLocalUserId(), Feature::CHANNELS)) {
|
||||
$this->page['aside'] .= Widget::channels($module, $this->selectedTab, $this->session->getLocalUserId());
|
||||
}
|
||||
if (Feature::isEnabled($this->session->getLocalUserId(), Feature::SEARCHES)) {
|
||||
$this->page['aside'] .= Widget\SavedSearches::getHTML($this->args->getQueryString());
|
||||
}
|
||||
if (Feature::isEnabled($this->session->getLocalUserId(), Feature::FOLDERS)) {
|
||||
$this->page['aside'] .= Widget::fileAs('filed', '');
|
||||
}
|
||||
if (($this->channel->isTimeline($this->selectedTab) || $this->userDefinedChannel->isTimeline($this->selectedTab, $this->session->getLocalUserId())) &&
|
||||
!in_array($this->selectedTab, [Channel::FOLLOWERS, Channel::FORYOU, Channel::DISCOVER])) {
|
||||
!in_array($this->selectedTab, [Channel::FOLLOWERS, Channel::FORYOU, Channel::DISCOVER]) && Feature::isEnabled($this->session->getLocalUserId(), Feature::NOSHARER)) {
|
||||
$this->page['aside'] .= $this->getNoSharerWidget('network');
|
||||
}
|
||||
|
||||
if (Feature::isEnabled($this->session->getLocalUserId(), 'trending_tags')) {
|
||||
if (Feature::isEnabled($this->session->getLocalUserId(), Feature::TRENDING_TAGS)) {
|
||||
$this->page['aside'] .= TrendingTags::getHTML($this->selectedTab);
|
||||
}
|
||||
|
||||
|
|
@ -211,13 +227,13 @@ class Network extends Timeline
|
|||
|
||||
try {
|
||||
if ($this->channel->isTimeline($this->selectedTab) || $this->userDefinedChannel->isTimeline($this->selectedTab, $this->session->getLocalUserId())) {
|
||||
$items = $this->getChannelItems($request);
|
||||
$items = $this->getChannelItems($request, $this->session->getLocalUserId());
|
||||
} elseif ($this->community->isTimeline($this->selectedTab)) {
|
||||
$items = $this->getCommunityItems();
|
||||
} else {
|
||||
$items = $this->getItems();
|
||||
}
|
||||
|
||||
|
||||
$o .= $this->conversation->render($items, Conversation::MODE_NETWORK, false, false, $this->getOrder(), $this->session->getLocalUserId());
|
||||
} catch (\Exception $e) {
|
||||
$o .= $this->l10n->t('Error %d (%s) while fetching the timeline.', $e->getCode(), $e->getMessage());
|
||||
|
|
@ -295,7 +311,7 @@ class Network extends Timeline
|
|||
$this->circleId = (int)($this->parameters['circle_id'] ?? 0);
|
||||
|
||||
if (!$this->selectedTab) {
|
||||
$this->selectedTab = self::getTimelineOrderBySession($this->session, $this->pConfig);
|
||||
$this->selectedTab = $this->getTimelineOrderBySession();
|
||||
} elseif (!$this->networkFactory->isTimeline($this->selectedTab) && !$this->channel->isTimeline($this->selectedTab) && !$this->userDefinedChannel->isTimeline($this->selectedTab, $this->session->getLocalUserId()) && !$this->community->isTimeline($this->selectedTab)) {
|
||||
throw new HTTPException\BadRequestException($this->l10n->t('Network feed not available.'));
|
||||
}
|
||||
|
|
@ -357,6 +373,16 @@ class Network extends Timeline
|
|||
$this->dateTo = $this->parameters['to'] ?? '';
|
||||
|
||||
$this->setMaxMinByOrder($request);
|
||||
|
||||
if (is_null($this->maxId) && !is_null($this->minId)) {
|
||||
$this->session->set('network-request', $request);
|
||||
$this->pConfig->set($this->session->getLocalUserId(), 'network.view', 'request', $request);
|
||||
}
|
||||
}
|
||||
|
||||
protected function setPing(bool $ping)
|
||||
{
|
||||
$this->ping = $ping;
|
||||
}
|
||||
|
||||
protected function getItems()
|
||||
|
|
@ -439,44 +465,51 @@ class Network extends Timeline
|
|||
$params['order'] = [$this->order => true];
|
||||
}
|
||||
|
||||
$items = $this->database->selectToArray('network-thread-view', [], DBA::mergeConditions($conditionFields, $conditionStrings), $params);
|
||||
$items = $this->database->selectToArray($this->circleId ? 'network-thread-circle-view' : 'network-thread-view', [], DBA::mergeConditions($conditionFields, $conditionStrings), $params);
|
||||
|
||||
// min_id quirk, continued
|
||||
if (isset($this->minId) && !isset($this->maxId)) {
|
||||
$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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the selected network tab of the currently logged-in user
|
||||
*
|
||||
* @param IHandleUserSessions $session
|
||||
* @param IManagePersonalConfigValues $pconfig
|
||||
* @return string
|
||||
*/
|
||||
public static function getTimelineOrderBySession(IHandleUserSessions $session, IManagePersonalConfigValues $pconfig): string
|
||||
private function getTimelineOrderBySession(): string
|
||||
{
|
||||
return $session->get('network-tab')
|
||||
?? $pconfig->get($session->getLocalUserId(), 'network.view', 'selected_tab')
|
||||
return $this->session->get('network-tab')
|
||||
?? $this->pConfig->get($this->session->getLocalUserId(), 'network.view', 'selected_tab')
|
||||
?? '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the lst request parameters of the currently logged-in user
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getTimelineRequestBySession(): array
|
||||
{
|
||||
return $this->session->get('network-request')
|
||||
?? $this->pConfig->get($this->session->getLocalUserId(), 'network.view', 'request')
|
||||
?? [];
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ use Friendica\App\Mode;
|
|||
use Friendica\BaseModule;
|
||||
use Friendica\Content\Conversation\Collection\Timelines;
|
||||
use Friendica\Content\Conversation\Entity\Channel as ChannelEntity;
|
||||
use Friendica\Content\Conversation\Entity\Community;
|
||||
use Friendica\Content\Conversation\Entity\UserDefinedChannel as UserDefinedChannelEntity;
|
||||
use Friendica\Content\Conversation\Repository\UserDefinedChannel;
|
||||
use Friendica\Core\Cache\Capability\ICanCache;
|
||||
|
|
@ -42,6 +43,7 @@ use Friendica\Database\DBA;
|
|||
use Friendica\Model\Item;
|
||||
use Friendica\Model\Post;
|
||||
use Friendica\Model\Post\Engagement;
|
||||
use Friendica\Model\Post\SearchIndex;
|
||||
use Friendica\Module\Response;
|
||||
use Friendica\Protocol\Activity;
|
||||
use Friendica\Util\DateTimeFormat;
|
||||
|
|
@ -71,6 +73,8 @@ class Timeline extends BaseModule
|
|||
/** @var bool */
|
||||
protected $update;
|
||||
/** @var bool */
|
||||
protected $ping;
|
||||
/** @var bool */
|
||||
protected $raw;
|
||||
/** @var string */
|
||||
protected $order;
|
||||
|
|
@ -92,7 +96,7 @@ class Timeline extends BaseModule
|
|||
/** @var UserDefinedChannel */
|
||||
protected $channelRepository;
|
||||
|
||||
public function __construct(UserDefinedChannel $channel, Mode $mode, IHandleUserSessions $session, Database $database, IManagePersonalConfigValues $pConfig, IManageConfigValues $config, ICanCache $cache, L10n $l10n, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, array $server, array $parameters = [])
|
||||
public function __construct(UserDefinedChannel $channel, Mode $mode, IHandleUserSessions $session, Database $database, IManagePersonalConfigValues $pConfig, IManageConfigValues $config, ICanCache $cache, L10n $l10n, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, array $server = [], array $parameters = [])
|
||||
{
|
||||
parent::__construct($l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters);
|
||||
|
||||
|
|
@ -164,7 +168,7 @@ class Timeline extends BaseModule
|
|||
$this->maxId = $request['last_created'] ?? $this->maxId;
|
||||
$this->minId = $request['first_created'] ?? $this->minId;
|
||||
break;
|
||||
case 'uriid':
|
||||
case 'uri-id':
|
||||
$this->maxId = $request['last_uriid'] ?? $this->maxId;
|
||||
$this->minId = $request['first_uriid'] ?? $this->minId;
|
||||
break;
|
||||
|
|
@ -226,15 +230,30 @@ class Timeline extends BaseModule
|
|||
return $tabs;
|
||||
}
|
||||
|
||||
public function getChannelItemsForAPI(string $channel, int $uid, int $limit, int $min = null, int $max = null): array
|
||||
{
|
||||
$this->itemsPerPage = $limit;
|
||||
$this->itemUriId = 0;
|
||||
$this->maxId = $max;
|
||||
$this->minId = $min;
|
||||
$this->noSharer = false;
|
||||
$this->order = 'uri-id';
|
||||
$this->ping = false;
|
||||
$this->selectedTab = $channel;
|
||||
|
||||
return $this->getChannelItems([], $uid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Database query for the channel page
|
||||
*
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function getChannelItems(array $request)
|
||||
protected function getChannelItems(array $request, int $uid): array
|
||||
{
|
||||
$items = $this->getRawChannelItems($request);
|
||||
$items = $this->getRawChannelItems($request, $uid);
|
||||
$total = min(count($items), $this->itemsPerPage);
|
||||
|
||||
$contacts = $this->database->selectToArray('user-contact', ['cid'], ['channel-frequency' => Contact\User::FREQUENCY_REDUCED, 'cid' => array_column($items, 'owner-id')]);
|
||||
$reduced = array_column($contacts, 'cid');
|
||||
|
|
@ -246,8 +265,8 @@ class Timeline extends BaseModule
|
|||
$owner_posts = [];
|
||||
$selected_items = [];
|
||||
|
||||
while (count($selected_items) < $this->itemsPerPage && ++$count < 50 && count($items) > 0) {
|
||||
$maxposts = round((count($items) / $this->itemsPerPage) * $maxpostperauthor);
|
||||
while (count($selected_items) < $total && ++$count < 50 && count($items) > 0) {
|
||||
$maxposts = round((count($items) / $total) * $maxpostperauthor);
|
||||
$minId = $items[array_key_first($items)][$this->order];
|
||||
$maxId = $items[array_key_last($items)][$this->order];
|
||||
|
||||
|
|
@ -279,15 +298,15 @@ class Timeline extends BaseModule
|
|||
$this->maxId = $maxId;
|
||||
}
|
||||
|
||||
if (count($selected_items) < $this->itemsPerPage) {
|
||||
$items = $this->getRawChannelItems($request);
|
||||
if (count($selected_items) < $total) {
|
||||
$items = $this->getRawChannelItems($request, $uid);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$selected_items = $items;
|
||||
}
|
||||
|
||||
$condition = ['unseen' => true, 'uid' => $this->session->getLocalUserId(), 'parent-uri-id' => array_column($selected_items, 'uri-id')];
|
||||
$condition = ['unseen' => true, 'uid' => $uid, 'parent-uri-id' => array_column($selected_items, 'uri-id')];
|
||||
$this->setItemsSeenByCondition($condition);
|
||||
|
||||
return $selected_items;
|
||||
|
|
@ -299,10 +318,8 @@ class Timeline extends BaseModule
|
|||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
private function getRawChannelItems(array $request)
|
||||
private function getRawChannelItems(array $request, int $uid): array
|
||||
{
|
||||
$uid = $this->session->getLocalUserId();
|
||||
|
||||
$table = 'post-engagement';
|
||||
|
||||
if ($this->selectedTab == ChannelEntity::WHATSHOT) {
|
||||
|
|
@ -365,7 +382,7 @@ class Timeline extends BaseModule
|
|||
} elseif (is_numeric($this->selectedTab) && !empty($channel = $this->channelRepository->selectById($this->selectedTab, $uid))) {
|
||||
$condition = $this->getUserChannelConditions($channel, $uid);
|
||||
if (in_array($channel->circle, [-3, -4, -5])) {
|
||||
$table = 'post-searchindex-user-view';
|
||||
$table = SearchIndex::getSearchView();
|
||||
$condition = DBA::mergeConditions($condition, ['uid' => $uid]);
|
||||
$orders = ['-3' => 'created', '-4' => 'received', '-5' => 'commented'];
|
||||
$this->order = $orders[$channel->circle];
|
||||
|
|
@ -457,7 +474,7 @@ class Timeline extends BaseModule
|
|||
|
||||
if (!empty($channel->fullTextSearch)) {
|
||||
if (!empty($channel->includeTags)) {
|
||||
$additional = self:: addIncludeTags($channel->includeTags);
|
||||
$additional = $this->addIncludeTags($channel->includeTags);
|
||||
} else {
|
||||
$additional = '';
|
||||
}
|
||||
|
|
@ -469,10 +486,10 @@ class Timeline extends BaseModule
|
|||
}
|
||||
|
||||
if (!empty($channel->mediaType)) {
|
||||
$additional .= self::addMediaTerms($channel->mediaType);
|
||||
$additional .= $this->addMediaTerms($channel->mediaType);
|
||||
}
|
||||
|
||||
$additional .= self::addLanguageSearchTerms($uid, $channel->languages);
|
||||
$additional .= $this->addLanguageSearchTerms($uid, $channel->languages);
|
||||
|
||||
if ($additional) {
|
||||
$searchterms = '+(' . trim($channel->fullTextSearch) . ')' . $additional;
|
||||
|
|
@ -682,10 +699,10 @@ class Timeline extends BaseModule
|
|||
{
|
||||
$items = $this->selectItems();
|
||||
|
||||
if ($this->selectedTab == 'local') {
|
||||
if ($this->selectedTab == Community::LOCAL) {
|
||||
$maxpostperauthor = (int)$this->config->get('system', 'max_author_posts_community_page');
|
||||
$key = 'author-id';
|
||||
} elseif ($this->selectedTab == 'global') {
|
||||
} elseif ($this->selectedTab == Community::GLOBAL) {
|
||||
$maxpostperauthor = (int)$this->config->get('system', 'max_server_posts_community_page');
|
||||
$key = 'author-gsid';
|
||||
} else {
|
||||
|
|
@ -751,7 +768,7 @@ class Timeline extends BaseModule
|
|||
{
|
||||
$this->order = 'received';
|
||||
|
||||
if ($this->selectedTab == 'local') {
|
||||
if ($this->selectedTab == Community::LOCAL) {
|
||||
$condition = ["`wall` AND `origin` AND `private` = ?", Item::PUBLIC];
|
||||
} elseif ($this->selectedTab == 'global') {
|
||||
$condition = ["`uid` = ? AND `private` = ?", 0, Item::PUBLIC];
|
||||
|
|
@ -787,7 +804,11 @@ class Timeline extends BaseModule
|
|||
}
|
||||
|
||||
$items = [];
|
||||
$result = Post::selectThreadForUser($this->session->getLocalUserId() ?: 0, ['uri-id', 'received', 'author-id', 'author-gsid'], $condition, $params);
|
||||
if ($this->selectedTab == Community::LOCAL) {
|
||||
$result = Post::selectOriginThread(['uri-id', 'received', 'author-id', 'author-gsid'], $condition, $params);
|
||||
} else {
|
||||
$result = Post::selectThreadForUser($this->session->getLocalUserId() ?: 0, ['uri-id', 'received', 'author-id', 'author-gsid'], $condition, $params);
|
||||
}
|
||||
|
||||
while ($item = $this->database->fetch($result)) {
|
||||
$item['comments'] = 0;
|
||||
|
|
@ -822,7 +843,7 @@ class Timeline extends BaseModule
|
|||
*/
|
||||
protected function setItemsSeenByCondition(array $condition)
|
||||
{
|
||||
if (empty($condition)) {
|
||||
if (empty($condition) || $this->ping) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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']);
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ use Friendica\Model;
|
|||
use Friendica\Module\Response;
|
||||
use Friendica\Network\HTTPClient\Capability\ICanSendHttpRequests;
|
||||
use Friendica\Network\HTTPClient\Client\HttpClientAccept;
|
||||
use Friendica\Network\HTTPClient\Client\HttpClientRequest;
|
||||
use Friendica\Protocol;
|
||||
use Friendica\Util\Profiler;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
|
@ -62,7 +63,7 @@ class Feed extends BaseModule
|
|||
|
||||
$contact = Model\Contact::getByURLForUser($url, DI::userSession()->getLocalUserId(), null);
|
||||
|
||||
$xml = $this->httpClient->fetch($contact['poll'], HttpClientAccept::FEED_XML);
|
||||
$xml = $this->httpClient->fetch($contact['poll'], HttpClientAccept::FEED_XML, 0, '', HttpClientRequest::FEEDFETCHER);
|
||||
|
||||
$import_result = Protocol\Feed::import($xml);
|
||||
|
||||
|
|
|
|||
|
|
@ -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') {
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ use Friendica\DI;
|
|||
use Friendica\Model;
|
||||
use Friendica\Model\Profile;
|
||||
use Friendica\Network\HTTPException;
|
||||
use Friendica\Security\OpenWebAuth;
|
||||
|
||||
/**
|
||||
* Shows the local directory of this node
|
||||
|
|
@ -63,7 +64,7 @@ class Directory extends BaseModule
|
|||
$gDirPath = '';
|
||||
$dirURL = Search::getGlobalDirectory();
|
||||
if (strlen($dirURL)) {
|
||||
$gDirPath = Profile::zrl($dirURL, true);
|
||||
$gDirPath = OpenWebAuth::getZrlUrl($dirURL, true);
|
||||
}
|
||||
|
||||
$pager = new Pager(DI::l10n(), DI::args()->getQueryString(), 60);
|
||||
|
|
|
|||
|
|
@ -22,7 +22,8 @@
|
|||
namespace Friendica\Module;
|
||||
|
||||
use Friendica\BaseModule;
|
||||
use Friendica\Core\System;
|
||||
use Friendica\Core\Protocol;
|
||||
use Friendica\Model\Item;
|
||||
use Friendica\Model\User;
|
||||
use Friendica\Network\HTTPException;
|
||||
use Friendica\Protocol\Feed as ProtocolFeed;
|
||||
|
|
@ -69,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);
|
||||
|
|
|
|||
|
|
@ -30,7 +30,6 @@ use Friendica\Core\KeyValueStorage\Capability\IManageKeyValuePairs;
|
|||
use Friendica\Core\L10n;
|
||||
use Friendica\Core\Renderer;
|
||||
use Friendica\Core\Session\Capability\IHandleUserSessions;
|
||||
use Friendica\Core\System;
|
||||
use Friendica\Database\PostUpdate;
|
||||
use Friendica\Model\User;
|
||||
use Friendica\Network\HTTPException;
|
||||
|
|
@ -154,7 +153,7 @@ class Friendica extends BaseModule
|
|||
Register::OPEN => 'REGISTER_OPEN'
|
||||
];
|
||||
|
||||
$register_policy_int = $this->config->get('config', 'register_policy');
|
||||
$register_policy_int = Register::getPolicy();
|
||||
if ($register_policy_int !== Register::CLOSED && $this->config->get('config', 'invitation_only')) {
|
||||
$register_policy = 'REGISTER_INVITATION';
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ class HCard extends BaseModule
|
|||
{
|
||||
if (DI::userSession()->getLocalUserId() && ($this->parameters['action'] ?? '') === 'view') {
|
||||
// A logged in user views a profile of a user
|
||||
$nickname = DI::app()->getLoggedInUserNickname();
|
||||
$nickname = DI::userSession()->getLocalUserNickname();
|
||||
} elseif (empty($this->parameters['action'])) {
|
||||
// Show the profile hCard
|
||||
$nickname = $this->parameters['profile'];
|
||||
|
|
@ -53,7 +53,7 @@ class HCard extends BaseModule
|
|||
|
||||
$page = DI::page();
|
||||
|
||||
if (!empty($profile['page-flags']) && ($profile['page-flags'] == User::PAGE_FLAGS_COMMUNITY)) {
|
||||
if (!empty($profile['page-flags']) && in_array($profile['page-flags'], [User::PAGE_FLAGS_COMMUNITY, User::PAGE_FLAGS_COMM_MAN])) {
|
||||
$page['htmlhead'] .= '<meta name="friendica.community" content="true" />';
|
||||
}
|
||||
if (!empty($profile['openidserver'])) {
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ use Friendica\DI;
|
|||
use Friendica\Model\User;
|
||||
use Friendica\Module\Security\Login;
|
||||
use Friendica\Protocol\ActivityPub;
|
||||
use Friendica\Protocol\ZOT;
|
||||
|
||||
/**
|
||||
* Home module - Landing page of the current node
|
||||
|
|
@ -38,6 +39,8 @@ class Home extends BaseModule
|
|||
{
|
||||
if (ActivityPub::isRequest()) {
|
||||
DI::baseUrl()->redirect(User::getActorName());
|
||||
} elseif (ZOT::isRequest()) {
|
||||
$this->jsonExit(ZOT::getSiteInfo(), 'application/x-zot+json');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -51,7 +54,7 @@ class Home extends BaseModule
|
|||
|
||||
Hook::callAll('home_init', $ret);
|
||||
|
||||
if (DI::userSession()->getLocalUserId() && ($app->getLoggedInUserNickname())) {
|
||||
if (DI::userSession()->getLocalUserId() && (DI::userSession()->getLocalUserNickname())) {
|
||||
DI::baseUrl()->redirect('network');
|
||||
}
|
||||
|
||||
|
|
@ -73,7 +76,7 @@ class Home extends BaseModule
|
|||
}
|
||||
}
|
||||
|
||||
$login = Login::form(DI::args()->getQueryString(), $config->get('config', 'register_policy') === Register::CLOSED ? 0 : 1);
|
||||
$login = Login::form(DI::args()->getQueryString(), Register::getPolicy() !== Register::CLOSED);
|
||||
|
||||
$content = '';
|
||||
Hook::callAll('home_content', $content);
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ class Invite extends BaseModule
|
|||
if ($config->get('system', 'invitation_only')) {
|
||||
$invitation_only = true;
|
||||
$invites_remaining = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'system', 'invites_remaining');
|
||||
if ((!$invites_remaining) && (!$app->isSiteAdmin())) {
|
||||
if ((!$invites_remaining) && (!DI::userSession()->isSiteAdmin())) {
|
||||
throw new HTTPException\ForbiddenException();
|
||||
}
|
||||
}
|
||||
|
|
@ -83,11 +83,11 @@ class Invite extends BaseModule
|
|||
continue;
|
||||
}
|
||||
|
||||
if ($invitation_only && ($invites_remaining || $app->isSiteAdmin())) {
|
||||
if ($invitation_only && ($invites_remaining || DI::userSession()->isSiteAdmin())) {
|
||||
$code = Model\Register::createForInvitation();
|
||||
$nmessage = str_replace('$invite_code', $code, $message);
|
||||
|
||||
if (!$app->isSiteAdmin()) {
|
||||
if (!DI::userSession()->isSiteAdmin()) {
|
||||
$invites_remaining--;
|
||||
if ($invites_remaining >= 0) {
|
||||
DI::pConfig()->set(DI::userSession()->getLocalUserId(), 'system', 'invites_remaining', $invites_remaining);
|
||||
|
|
@ -139,21 +139,21 @@ class Invite extends BaseModule
|
|||
if ($config->get('system', 'invitation_only')) {
|
||||
$inviteOnly = true;
|
||||
$x = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'system', 'invites_remaining');
|
||||
if ((!$x) && (!$app->isSiteAdmin())) {
|
||||
if ((!$x) && (!DI::userSession()->isSiteAdmin())) {
|
||||
throw new HTTPException\ForbiddenException(DI::l10n()->t('You have no more invitations available'));
|
||||
}
|
||||
}
|
||||
|
||||
$dirLocation = Search::getGlobalDirectory();
|
||||
if (strlen($dirLocation)) {
|
||||
if ($config->get('config', 'register_policy') === Register::CLOSED) {
|
||||
if (Register::getPolicy() === Register::CLOSED) {
|
||||
$linkTxt = DI::l10n()->t('Visit %s for a list of public sites that you can join. Friendica members on other sites can all connect with each other, as well as with members of many other social networks.', $dirLocation . '/servers');
|
||||
} else {
|
||||
$linkTxt = DI::l10n()->t('To accept this invitation, please visit and register at %s or any other public Friendica website.', DI::baseUrl() . '/register')
|
||||
. "\r\n" . "\r\n" . DI::l10n()->t('Friendica sites all inter-connect to create a huge privacy-enhanced social web that is owned and controlled by its members. They can also connect with many traditional social networks. See %s for a list of alternate Friendica sites you can join.', $dirLocation . '/servers');
|
||||
}
|
||||
} else { // there is no global directory URL defined
|
||||
if ($config->get('config', 'register_policy') === Register::CLOSED) {
|
||||
if (Register::getPolicy() === Register::CLOSED) {
|
||||
return DI::l10n()->t('Our apologies. This system is not currently configured to connect with other public sites or invite members.');
|
||||
} else {
|
||||
$linkTxt = DI::l10n()->t('To accept this invitation, please visit and register at %s.', DI::baseUrl() . '/register'
|
||||
|
|
@ -172,7 +172,7 @@ class Invite extends BaseModule
|
|||
DI::l10n()->t('You are cordially invited to join me and other close friends on Friendica - and help us to create a better social web.') . "\r\n" . "\r\n"
|
||||
. $linkTxt
|
||||
. "\r\n" . "\r\n" . (($inviteOnly) ? DI::l10n()->t('You will need to supply this invitation code: $invite_code') . "\r\n" . "\r\n" : '') . DI::l10n()->t('Once you have registered, please connect with me via my profile page at:')
|
||||
. "\r\n" . "\r\n" . DI::baseUrl() . '/profile/' . $app->getLoggedInUserNickname()
|
||||
. "\r\n" . "\r\n" . DI::baseUrl() . '/profile/' . DI::userSession()->getLocalUserNickname()
|
||||
. "\r\n" . "\r\n" . DI::l10n()->t('For more information about the Friendica project and why we feel it is important, please visit http://friendi.ca') . "\r\n" . "\r\n",
|
||||
],
|
||||
'$submit' => DI::l10n()->t('Submit')
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -31,9 +31,9 @@ use Friendica\Core\Hook;
|
|||
use Friendica\Core\L10n;
|
||||
use Friendica\Core\PConfig\Capability\IManagePersonalConfigValues;
|
||||
use Friendica\Core\Renderer;
|
||||
use Friendica\Core\Session\Model\UserSession;
|
||||
use Friendica\Core\Theme;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\DI;
|
||||
use Friendica\Model\Contact;
|
||||
use Friendica\Model\Item;
|
||||
use Friendica\Model\User;
|
||||
|
|
@ -64,7 +64,14 @@ class Compose extends BaseModule
|
|||
/** @var IManageConfigValues */
|
||||
private $config;
|
||||
|
||||
public function __construct(IManageConfigValues $config, IManagePersonalConfigValues $pConfig, App\Page $page, ACLFormatter $ACLFormatter, SystemMessages $systemMessages, L10n $l10n, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, array $server, array $parameters = [])
|
||||
/** @var UserSession */
|
||||
private $session;
|
||||
|
||||
/** @var App */
|
||||
private $app;
|
||||
|
||||
|
||||
public function __construct(App $app, UserSession $session, IManageConfigValues $config, IManagePersonalConfigValues $pConfig, App\Page $page, ACLFormatter $ACLFormatter, SystemMessages $systemMessages, L10n $l10n, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, array $server, array $parameters = [])
|
||||
{
|
||||
parent::__construct($l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters);
|
||||
|
||||
|
|
@ -73,6 +80,8 @@ class Compose extends BaseModule
|
|||
$this->page = $page;
|
||||
$this->pConfig = $pConfig;
|
||||
$this->config = $config;
|
||||
$this->session = $session;
|
||||
$this->app = $app;
|
||||
}
|
||||
|
||||
protected function post(array $request = [])
|
||||
|
|
@ -80,7 +89,7 @@ class Compose extends BaseModule
|
|||
if (!empty($_REQUEST['body'])) {
|
||||
$_REQUEST['return'] = 'network';
|
||||
require_once 'mod/item.php';
|
||||
item_post(DI::app());
|
||||
item_post();
|
||||
} else {
|
||||
$this->systemMessages->addNotice($this->l10n->t('Please enter a post body.'));
|
||||
}
|
||||
|
|
@ -88,13 +97,11 @@ class Compose extends BaseModule
|
|||
|
||||
protected function content(array $request = []): string
|
||||
{
|
||||
if (!DI::userSession()->getLocalUserId()) {
|
||||
if (!$this->session->getLocalUserId()) {
|
||||
return Login::form('compose');
|
||||
}
|
||||
|
||||
$a = DI::app();
|
||||
|
||||
if ($a->getCurrentTheme() !== 'frio') {
|
||||
if ($this->app->getCurrentTheme() !== 'frio') {
|
||||
throw new NotImplementedException($this->l10n->t('This feature is only available with the frio theme.'));
|
||||
}
|
||||
|
||||
|
|
@ -110,7 +117,7 @@ class Compose extends BaseModule
|
|||
}
|
||||
}
|
||||
|
||||
$user = User::getById(DI::userSession()->getLocalUserId(), ['allow_cid', 'allow_gid', 'deny_cid', 'deny_gid', 'default-location']);
|
||||
$user = User::getById($this->session->getLocalUserId(), ['allow_cid', 'allow_gid', 'deny_cid', 'deny_gid', 'default-location']);
|
||||
|
||||
$contact_allow_list = $this->ACLFormatter->expand($user['allow_cid']);
|
||||
$circle_allow_list = $this->ACLFormatter->expand($user['allow_gid']);
|
||||
|
|
@ -122,7 +129,7 @@ class Compose extends BaseModule
|
|||
$compose_title = $this->l10n->t('Compose new personal note');
|
||||
$type = 'note';
|
||||
$doesFederate = false;
|
||||
$contact_allow_list = [$a->getContactId()];
|
||||
$contact_allow_list = [$this->app->getContactId()];
|
||||
$circle_allow_list = [];
|
||||
$contact_deny_list = [];
|
||||
$circle_deny_list = [];
|
||||
|
|
@ -165,9 +172,9 @@ class Compose extends BaseModule
|
|||
$this->page->registerFooterScript(Theme::getPathForFile('js/linkPreview.js'));
|
||||
$this->page->registerFooterScript(Theme::getPathForFile('js/compose.js'));
|
||||
|
||||
$contact = Contact::getById($a->getContactId());
|
||||
$contact = Contact::getById($this->app->getContactId());
|
||||
|
||||
if ($this->pConfig->get(DI::userSession()->getLocalUserId(), 'system', 'set_creation_date')) {
|
||||
if ($this->pConfig->get($this->session->getLocalUserId(), 'system', 'set_creation_date')) {
|
||||
$created_at = Temporal::getDateTimeField(
|
||||
new \DateTime(DBA::NULL_DATETIME),
|
||||
new \DateTime('now'),
|
||||
|
|
@ -205,8 +212,8 @@ class Compose extends BaseModule
|
|||
'location_disabled' => $this->l10n->t('Location services are disabled. Please check the website\'s permissions on your device'),
|
||||
'wait' => $this->l10n->t('Please wait'),
|
||||
'placeholdertitle' => $this->l10n->t('Set title'),
|
||||
'placeholdercategory' => Feature::isEnabled(DI::userSession()->getLocalUserId(),'categories') ? $this->l10n->t('Categories (comma-separated list)') : '',
|
||||
'always_open_compose' => $this->pConfig->get(DI::userSession()->getLocalUserId(), 'frio', 'always_open_compose',
|
||||
'placeholdercategory' => Feature::isEnabled($this->session->getLocalUserId(), Feature::CATEGORIES) ? $this->l10n->t('Categories (comma-separated list)') : '',
|
||||
'always_open_compose' => $this->pConfig->get($this->session->getLocalUserId(), 'frio', 'always_open_compose',
|
||||
$this->config->get('frio', 'always_open_compose', false)) ? '' :
|
||||
$this->l10n->t('You can make this page always open when you use the New Post button in the <a href="/settings/display">Theme Customization settings</a>.'),
|
||||
],
|
||||
|
|
@ -237,7 +244,7 @@ class Compose extends BaseModule
|
|||
|
||||
'$jotplugins' => $jotplugins,
|
||||
'$rand_num' => Crypto::randomDigits(12),
|
||||
'$acl_selector' => ACL::getFullSelectorHTML($this->page, $a->getLoggedInUserId(), $doesFederate, [
|
||||
'$acl_selector' => ACL::getFullSelectorHTML($this->page, $this->session->getLocalUserId(), $doesFederate, [
|
||||
'allow_cid' => $contact_allow_list,
|
||||
'allow_gid' => $circle_allow_list,
|
||||
'deny_cid' => $contact_deny_list,
|
||||
|
|
|
|||
|
|
@ -188,7 +188,7 @@ class Display extends BaseModule
|
|||
$author = Contact::getById($item['author-id']);
|
||||
}
|
||||
|
||||
if (Network::isLocalLink($author['url'])) {
|
||||
if ($this->baseUrl->isLocalUrl($author['url'])) {
|
||||
Profile::load($this->app, $author['nick'], false);
|
||||
} else {
|
||||
$this->page['aside'] = Widget\VCard::getHTML($author);
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
67
src/Module/Item/Language.php
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
<?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\Item;
|
||||
|
||||
use Friendica\App;
|
||||
use Friendica\BaseModule;
|
||||
use Friendica\Core\L10n;
|
||||
use Friendica\Core\Session\Capability\IHandleUserSessions;
|
||||
use Friendica\Model\Item;
|
||||
use Friendica\Model\Post;
|
||||
use Friendica\Module\Api\ApiResponse;
|
||||
use Friendica\Network\HTTPException;
|
||||
use Friendica\Util\Profiler;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
* Return the language of a given item uri-id
|
||||
*/
|
||||
class Language extends BaseModule
|
||||
{
|
||||
/** @var IHandleUserSessions */
|
||||
private $session;
|
||||
|
||||
public function __construct(IHandleUserSessions $session, L10n $l10n, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, ApiResponse $response, array $server, array $parameters = [])
|
||||
{
|
||||
parent::__construct($l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters);
|
||||
|
||||
$this->session = $session;
|
||||
}
|
||||
|
||||
protected function rawContent(array $request = [])
|
||||
{
|
||||
if (!$this->session->isAuthenticated()) {
|
||||
throw new HttpException\ForbiddenException($this->l10n->t('Access denied.'));
|
||||
}
|
||||
|
||||
if (empty($this->parameters['id'])) {
|
||||
throw new HTTPException\BadRequestException();
|
||||
}
|
||||
|
||||
$item = Post::selectFirstForUser($this->session->getLocalUserId(), ['language'], ['uid' => [0, $this->session->getLocalUserId()], 'uri-id' => $this->parameters['id']]);
|
||||
if (empty($item)) {
|
||||
throw new HTTPException\NotFoundException();
|
||||
}
|
||||
|
||||
$this->httpExit(Item::getLanguageMessage($item));
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
72
src/Module/Item/Searchtext.php
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
<?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\Item;
|
||||
|
||||
use Friendica\App;
|
||||
use Friendica\BaseModule;
|
||||
use Friendica\Core\L10n;
|
||||
use Friendica\Core\Session\Capability\IHandleUserSessions;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\Model\Post;
|
||||
use Friendica\Module\Api\ApiResponse;
|
||||
use Friendica\Network\HTTPException;
|
||||
use Friendica\Util\Profiler;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
* Return the search text of a given item id
|
||||
*/
|
||||
class Searchtext extends BaseModule
|
||||
{
|
||||
/** @var IHandleUserSessions */
|
||||
private $session;
|
||||
|
||||
public function __construct(IHandleUserSessions $session, L10n $l10n, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, ApiResponse $response, array $server, array $parameters = [])
|
||||
{
|
||||
parent::__construct($l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters);
|
||||
|
||||
$this->session = $session;
|
||||
}
|
||||
|
||||
protected function rawContent(array $request = [])
|
||||
{
|
||||
if (!$this->session->isAuthenticated()) {
|
||||
throw new HttpException\ForbiddenException($this->l10n->t('Access denied.'));
|
||||
}
|
||||
|
||||
if (empty($this->parameters['id'])) {
|
||||
throw new HTTPException\BadRequestException();
|
||||
}
|
||||
|
||||
$item = Post::selectFirstForUser($this->session->getLocalUserId(), ['uri-id'], ['uid' => [0, $this->session->getLocalUserId()], 'uri-id' => $this->parameters['id']]);
|
||||
if (empty($item)) {
|
||||
throw new HTTPException\NotFoundException();
|
||||
}
|
||||
|
||||
$search = DBA::selectFirst('post-searchindex', [], ['uri-id' => $item['uri-id']]);
|
||||
if (empty($search)) {
|
||||
throw new HTTPException\NotFoundException();
|
||||
}
|
||||
|
||||
$this->httpExit(Post\Engagement::unescapeKeywords($search['searchtext']));
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
|
||||
|
|
|
|||
|
|
@ -25,8 +25,10 @@ use Exception;
|
|||
use Friendica\App;
|
||||
use Friendica\BaseModule;
|
||||
use Friendica\Core\L10n;
|
||||
use Friendica\Core\Protocol;
|
||||
use Friendica\Core\Session\Capability\IHandleUserSessions;
|
||||
use Friendica\Core\System;
|
||||
use Friendica\Core\Worker;
|
||||
use Friendica\Database\Database;
|
||||
use Friendica\Model\Contact;
|
||||
use Friendica\Model\GServer;
|
||||
|
|
@ -34,9 +36,10 @@ use Friendica\Model\User;
|
|||
use Friendica\Network\HTTPClient\Capability\ICanSendHttpRequests;
|
||||
use Friendica\Network\HTTPClient\Client\HttpClientOptions;
|
||||
use Friendica\Util\HTTPSignature;
|
||||
use Friendica\Util\Network;
|
||||
use Friendica\Util\Profiler;
|
||||
use Friendica\Util\Strings;
|
||||
use GuzzleHttp\Psr7\Uri;
|
||||
use Friendica\Worker\UpdateContact;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
|
|
@ -74,10 +77,12 @@ class Magic extends BaseModule
|
|||
|
||||
$this->logger->debug('Invoked', ['request' => $request]);
|
||||
|
||||
$addr = $request['addr'] ?? '';
|
||||
$dest = $request['dest'] ?? '';
|
||||
$bdest = $request['bdest'] ?? '';
|
||||
$owa = intval($request['owa'] ?? 0);
|
||||
$addr = $request['addr'] ?? '';
|
||||
$bdest = $request['bdest'] ?? '';
|
||||
$dest = $request['dest'] ?? '';
|
||||
$rev = intval($request['rev'] ?? 0);
|
||||
$owa = intval($request['owa'] ?? 0);
|
||||
$delegate = $request['delegate'] ?? '';
|
||||
|
||||
// bdest is preferred as it is hex-encoded and can survive url rewrite and argument parsing
|
||||
if (!empty($bdest)) {
|
||||
|
|
@ -111,27 +116,47 @@ class Magic extends BaseModule
|
|||
$this->app->redirect($dest);
|
||||
}
|
||||
|
||||
$dest = Network::removeUrlParameter($dest, 'zid');
|
||||
$dest = Network::removeUrlParameter($dest, 'f');
|
||||
|
||||
// OpenWebAuth
|
||||
$owner = User::getOwnerDataById($this->userSession->getLocalUserId());
|
||||
|
||||
if (!empty($contact['gsid'])) {
|
||||
$gserver = $this->dba->selectFirst('gserver', ['url'], ['id' => $contact['gsid']]);
|
||||
if (empty($gserver)) {
|
||||
$this->logger->notice('Server not found, redirecting to destination.', ['gsid' => $contact['gsid'], 'dest' => $dest]);
|
||||
System::externalRedirect($dest);
|
||||
}
|
||||
|
||||
$basepath = $gserver['url'];
|
||||
$gsid = $contact['gsid'];
|
||||
} elseif (GServer::check($target)) {
|
||||
$basepath = (string)GServer::cleanUri(new Uri($target));
|
||||
} else {
|
||||
$gsid = GServer::getID($target);
|
||||
}
|
||||
|
||||
if (empty($gsid)) {
|
||||
$this->logger->notice('The target is not a server path, redirecting to destination.', ['target' => $target]);
|
||||
System::externalRedirect($dest);
|
||||
}
|
||||
|
||||
$gserver = $this->dba->selectFirst('gserver', ['url', 'network', 'openwebauth'], ['id' => $gsid]);
|
||||
if (empty($gserver)) {
|
||||
$this->logger->notice('Server not found, redirecting to destination.', ['gsid' => $gsid, 'dest' => $dest]);
|
||||
System::externalRedirect($dest);
|
||||
}
|
||||
|
||||
$openwebauth = $gserver['openwebauth'];
|
||||
|
||||
// This part can be removed, when all server entries had been updated. So removing it in 2025 should be safe.
|
||||
if (empty($openwebauth) && ($gserver['network'] == Protocol::DFRN)) {
|
||||
$this->logger->notice('Open Web Auth path not provided. Assume default path', ['gsid' => $gsid, 'dest' => $dest]);
|
||||
$openwebauth = $gserver['url'] . '/owa';
|
||||
// Update contact to assign the path to the server
|
||||
UpdateContact::add(Worker::PRIORITY_MEDIUM, $contact['id']);
|
||||
}
|
||||
|
||||
if (empty($openwebauth)) {
|
||||
$this->logger->debug('Server does not support open web auth, redirecting to destination.', ['gsid' => $gsid, 'dest' => $dest]);
|
||||
System::externalRedirect($dest);
|
||||
}
|
||||
|
||||
$header = [
|
||||
'Accept' => 'application/x-dfrn+json, application/x-zot+json',
|
||||
'X-Open-Web-Auth' => Strings::getRandomHex()
|
||||
'Accept' => 'application/x-zot+json',
|
||||
'X-Open-Web-Auth' => Strings::getRandomHex(),
|
||||
];
|
||||
|
||||
// Create a header that is signed with the local users private key.
|
||||
|
|
@ -141,13 +166,13 @@ class Magic extends BaseModule
|
|||
'acct:' . $owner['addr']
|
||||
);
|
||||
|
||||
$this->logger->info('Fetch from remote system', ['basepath' => $basepath, 'headers' => $header]);
|
||||
$this->logger->info('Fetch from remote system', ['openwebauth' => $openwebauth, 'headers' => $header]);
|
||||
|
||||
// Try to get an authentication token from the other instance.
|
||||
try {
|
||||
$curlResult = $this->httpClient->request('get', $basepath . '/owa', [HttpClientOptions::HEADERS => $header]);
|
||||
$curlResult = $this->httpClient->request('get', $openwebauth, [HttpClientOptions::HEADERS => $header]);
|
||||
} catch (Exception $exception) {
|
||||
$this->logger->notice('URL is invalid, redirecting to destination.', ['url' => $basepath, 'error' => $exception, 'dest' => $dest]);
|
||||
$this->logger->notice('URL is invalid, redirecting to destination.', ['url' => $openwebauth, 'error' => $exception, 'dest' => $dest]);
|
||||
System::externalRedirect($dest);
|
||||
}
|
||||
if (!$curlResult->isSuccess()) {
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ class Browser extends BaseModule
|
|||
'$folders' => false,
|
||||
'$files' => $fileArray,
|
||||
'$cancel' => $this->t('Cancel'),
|
||||
'$nickname' => $this->app->getLoggedInUserNickname(),
|
||||
'$nickname' => $this->session->getLocalUserNickname(),
|
||||
'$upload' => $this->t('Upload'),
|
||||
]);
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ class Browser extends BaseModule
|
|||
'$folders' => $albums,
|
||||
'$files' => $photosArray,
|
||||
'$cancel' => $this->t('Cancel'),
|
||||
'$nickname' => $this->app->getLoggedInUserNickname(),
|
||||
'$nickname' => $this->session->getLocalUserNickname(),
|
||||
'$upload' => $this->t('Upload'),
|
||||
]);
|
||||
|
||||
|
|
@ -115,7 +115,7 @@ class Browser extends BaseModule
|
|||
$scale = $photo['scale'] ?? $record['loq'];
|
||||
|
||||
return [
|
||||
sprintf('%s/photos/%s/image/%s', $this->baseUrl, $this->app->getLoggedInUserNickname(), $record['resource-id']),
|
||||
sprintf('%s/photos/%s/image/%s', $this->baseUrl, $this->session->getLocalUserNickname(), $record['resource-id']),
|
||||
$filename_e,
|
||||
sprintf('%s/photo/%s-%s%s', $this->baseUrl, $record['resource-id'], $scale, $ext),
|
||||
$record['desc'],
|
||||
|
|
|
|||
|
|
@ -119,6 +119,7 @@ abstract class BaseUsers extends BaseModeration
|
|||
User::PAGE_FLAGS_NORMAL => $this->t('Normal Account Page'),
|
||||
User::PAGE_FLAGS_SOAPBOX => $this->t('Soapbox Page'),
|
||||
User::PAGE_FLAGS_COMMUNITY => $this->t('Public Group'),
|
||||
User::PAGE_FLAGS_COMM_MAN => $this->t('Public Group - Restricted'),
|
||||
User::PAGE_FLAGS_FREELOVE => $this->t('Automatic Friend Page'),
|
||||
User::PAGE_FLAGS_PRVGROUP => $this->t('Private Group')
|
||||
];
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ class Contact extends BaseModeration
|
|||
$this->baseUrl->redirect('moderation/blocklist/contact');
|
||||
}
|
||||
|
||||
if (Network::isLocalLink($contact['nurl'])) {
|
||||
if ($this->baseUrl->isLocalUrl($contact['nurl'])) {
|
||||
$this->systemMessages->addNotice($this->t('You can\'t block a local contact, please block the user instead'));
|
||||
$this->baseUrl->redirect('moderation/blocklist/contact');
|
||||
}
|
||||
|
|
@ -124,8 +124,6 @@ class Contact extends BaseModeration
|
|||
'$form_security_token' => self::getFormSecurityToken('moderation_contactblock'),
|
||||
|
||||
// values //
|
||||
'$baseurl' => $this->baseUrl,
|
||||
|
||||
'$contacts' => $contacts,
|
||||
'$total_contacts' => $this->tt('%s total blocked contact', '%s total blocked contacts', $total),
|
||||
'$paginate' => $pager->renderFull($total),
|
||||
|
|
|
|||
|
|
@ -138,7 +138,6 @@ class Add extends BaseModeration
|
|||
'$newreason' => ['reason', $this->t('Block reason'), $request['reason'] ?? '', $this->t('The reason why you blocked this server domain pattern. This reason will be shown publicly in the server information page.'), $this->t('Required'), '', ''],
|
||||
'$pattern' => $pattern,
|
||||
'$gservers' => $gservers,
|
||||
'$baseurl' => $this->baseUrl,
|
||||
'$form_security_token' => self::getFormSecurityToken('moderation_blocklist_add')
|
||||
]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -130,7 +130,6 @@ class Import extends \Friendica\Module\BaseModeration
|
|||
'$mode_append' => ['mode', $this->t('Append'), 'append', $this->t('Imports patterns from the file that weren\'t already existing in the current blocklist.'), 'checked="checked"'],
|
||||
'$mode_replace' => ['mode', $this->t('Replace'), 'replace', $this->t('Replaces the current blocklist by the imported patterns.')],
|
||||
'$blocklist' => $this->blocklist,
|
||||
'$baseurl' => $this->baseUrl,
|
||||
'$form_security_token' => self::getFormSecurityToken('moderation_blocklist_import')
|
||||
]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -115,7 +115,6 @@ class Index extends BaseModeration
|
|||
'$listfile' => ['listfile', $this->t('Server domain pattern blocklist CSV file'), '', '', $this->t('Required'), '', 'file'],
|
||||
'$newdomain' => ['pattern', $this->t('Server Domain Pattern'), '', $this->t('The domain pattern of the new server to add to the blocklist. Do not include the protocol.'), $this->t('Required'), '', ''],
|
||||
'$entries' => $blocklistform,
|
||||
'$baseurl' => $this->baseUrl,
|
||||
|
||||
'$form_security_token' => self::getFormSecurityToken('moderation_blocklist'),
|
||||
'$form_security_token_import' => self::getFormSecurityToken('moderation_blocklist_import'),
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -293,7 +293,7 @@ class Create extends BaseModule
|
|||
'$ignore' => ['contact_action', $this->t('Ignore contact'), self::CONTACT_ACTION_IGNORE, $this->t("Their posts won't appear in your Network page anymore, but their replies can appear in forum threads. They still can follow you.")],
|
||||
'$block' => ['contact_action', $this->t('Block contact'), self::CONTACT_ACTION_BLOCK, $this->t("Their posts won't appear in your Network page anymore, but their replies can appear in forum threads, with their content collapsed by default. They cannot follow you but still can have access to your public posts by other means.")],
|
||||
|
||||
'$display_forward' => !Network::isLocalLink($contact['url']),
|
||||
'$display_forward' => !$this->baseUrl->isLocalUrl($contact['url']),
|
||||
'$forward' => ['report_forward', $this->t('Forward report'), self::CONTACT_ACTION_BLOCK, $this->t('Would you ike to forward this report to the remote server?')],
|
||||
|
||||
'$summary' => $this->getAside($request),
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
@ -56,9 +62,24 @@ class Reports extends BaseModeration
|
|||
|
||||
$pager = new Pager($this->l10n, $this->args->getQueryString(), 10);
|
||||
|
||||
$query = $this->database->p("SELECT `report`.`id`, `report`.`cid`, `report`.`comment`, `report`.`forward`, `report`.`created`, `report`.`reporter-id`,
|
||||
`report`.`category`, `report`.`rules`, `contact`.`micro`, `contact`.`name`, `contact`.`nick`, `contact`.`url`, `contact`.`addr` FROM report
|
||||
INNER JOIN `contact` ON `contact`.`id` = `report`.`cid` ORDER BY `report`.`created` DESC LIMIT ?, ?", $pager->getStart(), $pager->getItemsPerPage());
|
||||
$query = $this->database->p(
|
||||
"SELECT
|
||||
`report`.`id`, `report`.`cid`, `report`.`comment`, `report`.`forward`, `report`.`created`, `report`.`reporter-id`,
|
||||
`report`.`category-id`,
|
||||
(
|
||||
SELECT GROUP_CONCAT(`report-rule`.`text` ORDER BY `report-rule`.`line-id` SEPARATOR \"\n\")
|
||||
FROM `report-rule`
|
||||
WHERE `report-rule`.`rid` = `report`.`id`
|
||||
GROUP BY `report-rule`.`rid`
|
||||
) AS `rules`,
|
||||
`contact`.`micro`, `contact`.`name`, `contact`.`nick`, `contact`.`url`, `contact`.`addr`
|
||||
FROM report
|
||||
INNER JOIN `contact` ON `contact`.`id` = `report`.`cid`
|
||||
ORDER BY `report`.`created` DESC
|
||||
LIMIT ?, ?",
|
||||
$pager->getStart(),
|
||||
$pager->getItemsPerPage(),
|
||||
);
|
||||
|
||||
$reports = [];
|
||||
while ($report = $this->database->fetch($query)) {
|
||||
|
|
@ -95,8 +116,6 @@ class Reports extends BaseModeration
|
|||
'$th_reports' => [$this->t('Created'), $this->t('Photo'), $this->t('Name'), $this->t('Comment'), $this->t('Category')],
|
||||
|
||||
// values //
|
||||
'$baseurl' => $this->baseUrl,
|
||||
|
||||
'$reports' => $reports,
|
||||
'$total_reports' => $this->tt('%s total report', '%s total reports', $total),
|
||||
'$paginate' => $pager->renderFull($total),
|
||||
|
|
|
|||
|
|
@ -150,7 +150,6 @@ class Active extends BaseUsers
|
|||
'$form_security_token' => self::getFormSecurityToken('moderation_users_active'),
|
||||
|
||||
// values //
|
||||
'$baseurl' => $this->baseUrl,
|
||||
'$query_string' => $this->args->getQueryString(),
|
||||
|
||||
'$users' => $users,
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ class NoScrape extends BaseModule
|
|||
$which = $this->parameters['nick'];
|
||||
} elseif (DI::userSession()->getLocalUserId() && isset($this->parameters['profile']) && DI::args()->get(2) == 'view') {
|
||||
// view infos about a known profile (needs a login)
|
||||
$which = $a->getLoggedInUserNickname();
|
||||
$which = DI::userSession()->getLocalUserNickname();
|
||||
} else {
|
||||
$this->jsonError(403, 'Authentication required');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ namespace Friendica\Module;
|
|||
use Friendica\App;
|
||||
use Friendica\BaseModule;
|
||||
use Friendica\Capabilities\ICanCreateResponses;
|
||||
use Friendica\Core\Addon;
|
||||
use Friendica\Core\Config\Capability\IManageConfigValues;
|
||||
use Friendica\Core\L10n;
|
||||
use Friendica\Model\Nodeinfo;
|
||||
|
|
@ -65,7 +64,7 @@ class NodeInfo110 extends BaseModule
|
|||
],
|
||||
'services' => Nodeinfo::getServices(),
|
||||
'usage' => Nodeinfo::getUsage(),
|
||||
'openRegistrations' => intval($this->config->get('config', 'register_policy')) !== Register::CLOSED,
|
||||
'openRegistrations' => Register::getPolicy() !== Register::CLOSED,
|
||||
'metadata' => [
|
||||
'nodeName' => $this->config->get('config', 'sitename'),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ namespace Friendica\Module;
|
|||
use Friendica\App;
|
||||
use Friendica\BaseModule;
|
||||
use Friendica\Capabilities\ICanCreateResponses;
|
||||
use Friendica\Core\Addon;
|
||||
use Friendica\Core\Config\Capability\IManageConfigValues;
|
||||
use Friendica\Core\L10n;
|
||||
use Friendica\Model\Nodeinfo;
|
||||
|
|
@ -57,10 +56,11 @@ class NodeInfo120 extends BaseModule
|
|||
],
|
||||
'protocols' => ['dfrn', 'activitypub'],
|
||||
'services' => Nodeinfo::getServices(),
|
||||
'openRegistrations' => Register::getPolicy() !== Register::CLOSED,
|
||||
'usage' => Nodeinfo::getUsage(),
|
||||
'openRegistrations' => intval($this->config->get('config', 'register_policy')) !== Register::CLOSED,
|
||||
'metadata' => [
|
||||
'nodeName' => $this->config->get('config', 'sitename'),
|
||||
'nodeName' => $this->config->get('config', 'sitename'),
|
||||
'nodeDescription' => $this->config->get('config', 'info'),
|
||||
],
|
||||
];
|
||||
|
||||
|
|
|
|||
90
src/Module/NodeInfo121.php
Normal 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));
|
||||
}
|
||||
}
|
||||
91
src/Module/NodeInfo122.php
Normal 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));
|
||||
}
|
||||
}
|
||||
|
|
@ -24,7 +24,6 @@ namespace Friendica\Module;
|
|||
use Friendica\App;
|
||||
use Friendica\BaseModule;
|
||||
use Friendica\Capabilities\ICanCreateResponses;
|
||||
use Friendica\Core\Addon;
|
||||
use Friendica\Core\Config\Capability\IManageConfigValues;
|
||||
use Friendica\Core\L10n;
|
||||
use Friendica\Model\Nodeinfo;
|
||||
|
|
@ -33,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
|
||||
{
|
||||
|
|
@ -59,7 +58,7 @@ class NodeInfo210 extends BaseModule
|
|||
'organization' => Nodeinfo::getOrganization($this->config),
|
||||
'protocols' => ['dfrn', 'activitypub'],
|
||||
'services' => Nodeinfo::getServices(),
|
||||
'openRegistrations' => intval($this->config->get('config', 'register_policy')) !== Register::CLOSED,
|
||||
'openRegistrations' => Register::getPolicy() !== Register::CLOSED,
|
||||
'usage' => Nodeinfo::getUsage(true),
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
|
|
|
|||
|
|
@ -28,18 +28,14 @@ use Friendica\Content\GroupManager;
|
|||
use Friendica\Core\Cache\Capability\ICanCache;
|
||||
use Friendica\Core\Cache\Enum\Duration;
|
||||
use Friendica\Core\Config\Capability\IManageConfigValues;
|
||||
use Friendica\Core\Hook;
|
||||
use Friendica\Core\L10n;
|
||||
use Friendica\Core\PConfig\Capability\IManagePersonalConfigValues;
|
||||
use Friendica\Core\Session\Capability\IHandleUserSessions;
|
||||
use Friendica\Core\System;
|
||||
use Friendica\Database\Database;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\Model\Circle;
|
||||
use Friendica\Model\Post;
|
||||
use Friendica\Model\User;
|
||||
use Friendica\Model\Verb;
|
||||
use Friendica\Module\Conversation\Network;
|
||||
use Friendica\Module\Register;
|
||||
use Friendica\Module\Response;
|
||||
use Friendica\Navigation\Notifications\Entity;
|
||||
|
|
@ -106,7 +102,6 @@ class Ping extends BaseModule
|
|||
$intro_count = 0;
|
||||
$mail_count = 0;
|
||||
$home_count = 0;
|
||||
$network_count = 0;
|
||||
$register_count = 0;
|
||||
$sysnotify_count = 0;
|
||||
$circles_unseen = [];
|
||||
|
|
@ -126,34 +121,13 @@ class Ping extends BaseModule
|
|||
}
|
||||
|
||||
$condition = [
|
||||
"`unseen` AND `uid` = ? AND NOT `origin` AND (`vid` != ? OR `vid` IS NULL)",
|
||||
"`unseen` AND `uid` = ? AND NOT `origin` AND `wall` AND (`vid` != ? OR `vid` IS NULL)",
|
||||
$this->session->getLocalUserId(), Verb::getID(Activity::FOLLOW)
|
||||
];
|
||||
|
||||
// No point showing counts for non-top-level posts when the network page is ordered by received field
|
||||
if (Network::getTimelineOrderBySession($this->session, $this->pconfig) == 'received') {
|
||||
$condition = DBA::mergeConditions($condition, ["`parent` = `id`"]);
|
||||
}
|
||||
$home_count = Post::count($condition);
|
||||
|
||||
$items_unseen = $this->database->toArray(Post::selectForUser(
|
||||
$this->session->getLocalUserId(),
|
||||
['wall', 'uid', 'uri-id'],
|
||||
$condition,
|
||||
['limit' => 1000],
|
||||
));
|
||||
$arr = ['items' => $items_unseen];
|
||||
Hook::callAll('network_ping', $arr);
|
||||
|
||||
foreach ($items_unseen as $item) {
|
||||
if ($item['wall']) {
|
||||
$home_count++;
|
||||
} else {
|
||||
$network_count++;
|
||||
}
|
||||
}
|
||||
|
||||
$compute_circle_counts = $this->config->get('system','compute_circle_counts');
|
||||
if ($network_count && $compute_circle_counts) {
|
||||
if ($this->config->get('system','compute_circle_counts')) {
|
||||
// Find out how unseen network posts are spread across circles
|
||||
foreach (Circle::countUnseen($this->session->getLocalUserId()) as $circle_count) {
|
||||
if ($circle_count['count'] > 0) {
|
||||
|
|
@ -175,7 +149,7 @@ class Ping extends BaseModule
|
|||
$myurl = $this->session->getMyUrl();
|
||||
$mail_count = $this->database->count('mail', ["`uid` = ? AND NOT `seen` AND `from-url` != ?", $this->session->getLocalUserId(), $myurl]);
|
||||
|
||||
if (intval($this->config->get('config', 'register_policy')) === Register::APPROVE && $this->session->isSiteAdmin()) {
|
||||
if (Register::getPolicy() === Register::APPROVE && $this->session->isSiteAdmin()) {
|
||||
$registrations = \Friendica\Model\Register::getPending();
|
||||
$register_count = count($registrations);
|
||||
}
|
||||
|
|
@ -214,7 +188,7 @@ class Ping extends BaseModule
|
|||
if (!$this->notify->shouldShowOnDesktop($notification)) {
|
||||
return null;
|
||||
}
|
||||
if (($notification->type == Post\UserNotification::TYPE_NONE) && in_array($owner['page-flags'], [User::PAGE_FLAGS_NORMAL, User::PAGE_FLAGS_PRVGROUP])) {
|
||||
if (($notification->type == Post\UserNotification::TYPE_NONE) && in_array($owner['page-flags'], [User::PAGE_FLAGS_NORMAL, User::PAGE_FLAGS_PRVGROUP, User::PAGE_FLAGS_COMM_MAN])) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
|
|
@ -282,7 +256,6 @@ class Ping extends BaseModule
|
|||
$data = [];
|
||||
$data['intro'] = $intro_count;
|
||||
$data['mail'] = $mail_count;
|
||||
$data['net'] = ($network_count < 1000) ? $network_count : '999+';
|
||||
$data['home'] = ($home_count < 1000) ? $home_count : '999+';
|
||||
$data['register'] = $register_count;
|
||||
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ class Acknowledge extends BaseApi
|
|||
|
||||
protected function content(array $request = []): string
|
||||
{
|
||||
DI::session()->set('return_path', $_REQUEST['return_path'] ?? '');
|
||||
DI::session()->set('return_path', 'oauth/authorize?' . $request['return_authorize']);
|
||||
|
||||
$o = Renderer::replaceMacros(Renderer::getMarkupTemplate('oauth_authorize.tpl'), [
|
||||
'$title' => DI::l10n()->t('Authorize application connection'),
|
||||
|
|
|
|||
|
|
@ -68,19 +68,19 @@ class Authorize extends BaseApi
|
|||
|
||||
$redirect_request = $_REQUEST;
|
||||
unset($redirect_request['pagename']);
|
||||
$redirect = 'oauth/authorize?' . http_build_query($redirect_request);
|
||||
$redirect = http_build_query($redirect_request);
|
||||
|
||||
$uid = DI::userSession()->getLocalUserId();
|
||||
if (empty($uid)) {
|
||||
Logger::info('Redirect to login');
|
||||
DI::app()->redirect('login?return_path=' . urlencode($redirect));
|
||||
DI::app()->redirect('login?' . http_build_query(['return_authorize' => $redirect]));
|
||||
} else {
|
||||
Logger::info('Already logged in user', ['uid' => $uid]);
|
||||
}
|
||||
|
||||
if (!OAuth::existsTokenForUser($application, $uid) && !DI::session()->get('oauth_acknowledge')) {
|
||||
Logger::info('Redirect to acknowledge');
|
||||
DI::app()->redirect('oauth/acknowledge?' . http_build_query(['return_path' => $redirect, 'application' => $application['name']]));
|
||||
DI::app()->redirect('oauth/acknowledge?' . http_build_query(['return_authorize' => $redirect, 'application' => $application['name']]));
|
||||
}
|
||||
|
||||
DI::session()->remove('oauth_acknowledge');
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ use Friendica\Core\System;
|
|||
use Friendica\Database\Database;
|
||||
use Friendica\Model\Contact;
|
||||
use Friendica\Model\GServer;
|
||||
use Friendica\Model\Item;
|
||||
use Friendica\Model\Post;
|
||||
use Friendica\Module\Response;
|
||||
use Friendica\Network\HTTPException;
|
||||
|
|
@ -101,6 +102,7 @@ class PubSub extends \Friendica\BaseModule
|
|||
|
||||
$this->logger->info('Import item from Contact.', ['nickname' => $nickname, 'contact-nickname' => $contact['nick'], 'contact-id' => $contact['id']]);
|
||||
$feedhub = '';
|
||||
Item::incrementOutbound(Protocol::OSTATUS);
|
||||
OStatus::import($xml, $importer, $contact, $feedhub);
|
||||
|
||||
throw new HTTPException\OKException();
|
||||
|
|
|
|||