Compare commits
170 commits
develop
...
right-clic
Author | SHA1 | Date | |
---|---|---|---|
|
486b7caa46 | ||
|
013bba50bc | ||
|
5f0657a30c | ||
|
435b30be11 | ||
|
73863561d2 | ||
|
67696d08da | ||
|
1b00b91767 | ||
|
68c2bdb98e | ||
|
54852ecb56 | ||
|
8c4b2107b5 | ||
|
111df607bc | ||
|
b8b76e870d | ||
|
41c89abe68 | ||
|
24e7556f85 | ||
|
8cc7bad1ea | ||
|
2357385162 | ||
|
31b92b16ed | ||
|
3ad4ab2940 | ||
|
5ab81abaa6 | ||
|
1b651519a3 | ||
|
ba07172a65 | ||
|
72e045e744 | ||
|
dc96a72173 | ||
|
38141edbea | ||
|
9b0e243350 | ||
|
52cc8ab73b | ||
|
ea4e66c74c | ||
|
f4826bae52 | ||
|
7471513269 | ||
|
ae37c44cc0 | ||
|
424e219c53 | ||
|
bae7644d6f | ||
|
f2ccce05b8 | ||
|
89ffe6875f | ||
|
7284210bf7 | ||
|
4fcc92e532 | ||
|
2c259c5c6f | ||
|
39d25b9699 | ||
|
5df1ead001 | ||
|
2d4f28dcde | ||
|
dd55ba2d77 | ||
|
c9f7d9baff | ||
|
504a2e91e2 | ||
|
40e882004e | ||
|
e394a6b0fa | ||
|
8cf82a8449 | ||
|
0d922b75af | ||
|
ba0a8069c4 | ||
|
d37699bc08 | ||
|
ac087749e3 | ||
|
ddc9f5f595 | ||
|
35bba685fa | ||
|
e52fa44d3f | ||
|
f74d6f9ebb | ||
|
ae358cae4c | ||
|
b572b8989f | ||
|
5800a973cb | ||
|
44ce5471b3 | ||
|
e05b57cd5d | ||
|
ecdf8f2b47 | ||
|
1c5681c199 | ||
|
20fd25258a | ||
|
00bb538fd0 | ||
|
12bdbaaba8 | ||
|
821a135033 | ||
|
0ff37c0075 | ||
|
0a73050de1 | ||
|
a25dbf839a | ||
|
e16b6ee6e1 | ||
|
5c5d7eb04f | ||
|
fc3898fe64 | ||
|
71384e6f39 | ||
|
d95c9d28a8 | ||
|
bb7d25dfc9 | ||
|
d5c0f086bd | ||
|
892e0a5623 | ||
|
cb294cf411 | ||
|
9ad452a19b | ||
|
623a5be8a6 | ||
|
d1cd9a016e | ||
|
7d5d3b3c29 | ||
|
bcec6c5ab2 | ||
|
6384265cbd | ||
|
f12276eff8 | ||
|
c6160a1c38 | ||
|
07c20da08f | ||
|
4eefd0a205 | ||
|
78bc1359e0 | ||
|
1956c2ecfd | ||
|
ade2369b5d | ||
|
0d2ea97eb1 | ||
|
08fa51d0bb | ||
|
7d10518e94 | ||
|
1069cfb570 | ||
|
14e5b06029 | ||
|
1ea8a4042d | ||
|
fad55e0948 | ||
|
262ca4131d | ||
|
c7e0500529 | ||
|
686d0b6dbb | ||
|
59c27a6cbb | ||
|
e2cbe0983a | ||
|
3b0cc45588 | ||
|
061f43788c | ||
|
fe00a3893d | ||
|
5d4f72698d | ||
|
96ede22abb | ||
|
2cc8fcc4aa | ||
|
98900c33d4 | ||
|
7dc9a812f6 | ||
|
7a14d5f7e4 | ||
|
909d516ed4 | ||
|
52825cb4c4 | ||
|
dbc72adaf9 | ||
|
ba5a288b2d | ||
|
84043abbda | ||
|
f212888e90 | ||
|
50c0fd6738 | ||
|
5b5c9ddc74 | ||
|
cb992f693c | ||
|
ede41166ae | ||
|
caa7b6f326 | ||
|
0a6dff0618 | ||
|
9b4ade4542 | ||
|
0153c2a027 | ||
|
1a0f7c15ad | ||
|
d5bf306884 | ||
|
4cd2fde6f2 | ||
|
259e7876ad | ||
|
760c7deba3 | ||
|
83306949ac | ||
|
c0cd0dc74d | ||
|
f7b0a0bef1 | ||
|
a9d668cc78 | ||
|
15df9990da | ||
|
91ddb406ab | ||
|
d9d42105d6 | ||
|
7924085c94 | ||
|
672186e549 | ||
|
053dfb3e2b | ||
|
f1efb8d277 | ||
|
b6e958fdcf | ||
|
885b3a12b9 | ||
|
fc05daefb5 | ||
|
7faa42882b | ||
|
fc22a3e83f | ||
|
8ddc71188f | ||
|
b77a5c3eb4 | ||
|
6a6e2cd2a2 | ||
|
01c04fe2c2 | ||
|
e60f3e1a99 | ||
|
665316c14d | ||
|
fef14d96c7 | ||
|
c8087a7827 | ||
|
5a59dff817 | ||
|
e9554c32c9 | ||
|
d2f935df1d | ||
|
ed30d888fa | ||
|
606bd0be60 | ||
|
d29d7c40cd | ||
|
1e3cfca58d | ||
|
d6632bb0ea | ||
|
3fe4991fcf | ||
|
0c583574e1 | ||
|
7432e47f7a | ||
|
cda1b91b77 | ||
|
7c43b41f0b | ||
|
36313fe35b | ||
|
820674a7ad | ||
|
9bd8d974b3 |
210 changed files with 28372 additions and 25499 deletions
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
|||
2024.03-dev
|
||||
2024.03-rc
|
||||
|
|
62
database.sql
62
database.sql
|
@ -1,6 +1,6 @@
|
|||
-- ------------------------------------------
|
||||
-- Friendica 2024.03-dev (Yellow Archangel)
|
||||
-- DB_UPDATE_VERSION 1548
|
||||
-- Friendica 2024.03-rc (Yellow Archangel)
|
||||
-- DB_UPDATE_VERSION 1556
|
||||
-- ------------------------------------------
|
||||
|
||||
|
||||
|
@ -502,6 +502,8 @@ CREATE TABLE IF NOT EXISTS `channel` (
|
|||
`access-key` varchar(1) COMMENT 'Access key',
|
||||
`include-tags` varchar(1023) COMMENT 'Comma separated list of tags that will be included in the channel',
|
||||
`exclude-tags` varchar(1023) COMMENT 'Comma separated list of tags that aren\'t allowed in the channel',
|
||||
`min-size` int unsigned COMMENT 'Minimum post size',
|
||||
`max-size` int unsigned COMMENT 'Maximum post size',
|
||||
`full-text-search` varchar(1023) COMMENT 'Full text search pattern, see https://mariadb.com/kb/en/full-text-index-overview/#in-boolean-mode',
|
||||
`media-type` smallint unsigned COMMENT 'Filtered media types',
|
||||
`languages` mediumtext COMMENT 'Desired languages',
|
||||
|
@ -537,6 +539,7 @@ CREATE TABLE IF NOT EXISTS `contact-relation` (
|
|||
`relation-score` smallint unsigned COMMENT 'score for interactions of relation-cid on cid',
|
||||
`thread-score` smallint unsigned COMMENT 'score for interactions of cid on threads of relation-cid',
|
||||
`relation-thread-score` smallint unsigned COMMENT 'score for interactions of relation-cid on threads of cid',
|
||||
`post-score` smallint unsigned COMMENT 'score for the amount of posts from cid that can be seen by relation-cid',
|
||||
PRIMARY KEY(`cid`,`relation-cid`),
|
||||
INDEX `relation-cid` (`relation-cid`),
|
||||
FOREIGN KEY (`cid`) REFERENCES `contact` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE,
|
||||
|
@ -1281,6 +1284,7 @@ CREATE TABLE IF NOT EXISTS `post-content` (
|
|||
`location` varchar(255) NOT NULL DEFAULT '' COMMENT 'text location where this item originated',
|
||||
`coord` varchar(255) NOT NULL DEFAULT '' COMMENT 'longitude/latitude pair representing location where this item originated',
|
||||
`language` text COMMENT 'Language information about this post',
|
||||
`sensitive` boolean COMMENT 'If true, this post contains sensitive content',
|
||||
`app` varchar(255) NOT NULL DEFAULT '' COMMENT 'application which generated this item',
|
||||
`rendered-hash` varchar(32) NOT NULL DEFAULT '' COMMENT '',
|
||||
`rendered-html` mediumtext COMMENT 'item.body converted to html',
|
||||
|
@ -1344,9 +1348,11 @@ CREATE TABLE IF NOT EXISTS `post-engagement` (
|
|||
`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',
|
||||
`language` varchar(128) COMMENT 'Language information about this post',
|
||||
`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',
|
||||
`created` datetime COMMENT '',
|
||||
`network` char(4) COMMENT '',
|
||||
`restricted` boolean NOT NULL DEFAULT '0' COMMENT 'If true, this post is either unlisted or not from a federated network',
|
||||
`comments` mediumint unsigned COMMENT 'Number of comments',
|
||||
`activities` mediumint unsigned COMMENT 'Number of activities (like, dislike, ...)',
|
||||
|
@ -1464,14 +1470,19 @@ 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',
|
||||
`network` char(4) COMMENT '',
|
||||
`private` tinyint unsigned COMMENT '0=public, 1=private, 2=unlisted',
|
||||
`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',
|
||||
`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',
|
||||
`created` datetime COMMENT '',
|
||||
`restricted` boolean NOT NULL DEFAULT '0' COMMENT 'If true, this post is either unlisted or not from a federated network',
|
||||
PRIMARY KEY(`uri-id`),
|
||||
INDEX `owner-id` (`owner-id`),
|
||||
INDEX `created` (`created`),
|
||||
FULLTEXT INDEX `searchtext` (`searchtext`),
|
||||
FOREIGN KEY (`uri-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE
|
||||
FOREIGN KEY (`uri-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE,
|
||||
FOREIGN KEY (`owner-id`) REFERENCES `contact` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE
|
||||
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Content for all posts';
|
||||
|
||||
--
|
||||
|
@ -2006,7 +2017,8 @@ CREATE VIEW `application-view` AS SELECT
|
|||
`application-token`.`follow` AS `follow`,
|
||||
`application-token`.`push` AS `push`
|
||||
FROM `application-token`
|
||||
INNER JOIN `application` ON `application-token`.`application-id` = `application`.`id`;
|
||||
INNER JOIN `application` ON `application-token`.`application-id` = `application`.`id`
|
||||
INNER JOIN `user` ON `user`.`uid` = `application-token`.`uid` AND `user`.`verified` AND NOT `user`.`blocked` AND NOT `user`.`account_removed` AND NOT `user`.`account_expired`;
|
||||
|
||||
--
|
||||
-- VIEW circle-member-view
|
||||
|
@ -2098,6 +2110,38 @@ 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-searchindex-user-view
|
||||
--
|
||||
DROP VIEW IF EXISTS `post-searchindex-user-view`;
|
||||
CREATE VIEW `post-searchindex-user-view` AS SELECT
|
||||
`post-thread-user`.`uid` AS `uid`,
|
||||
`post-searchindex`.`uri-id` AS `uri-id`,
|
||||
`post-searchindex`.`owner-id` AS `owner-id`,
|
||||
`post-searchindex`.`media-type` AS `media-type`,
|
||||
`post-searchindex`.`language` AS `language`,
|
||||
`post-searchindex`.`searchtext` AS `searchtext`,
|
||||
`post-searchindex`.`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-searchindex`.`language` AS `restricted`,
|
||||
0 AS `comments`,
|
||||
0 AS `activities`
|
||||
FROM `post-thread-user`
|
||||
INNER JOIN `post-searchindex` ON `post-searchindex`.`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-user-view
|
||||
--
|
||||
|
@ -2155,6 +2199,7 @@ CREATE VIEW `post-user-view` AS SELECT
|
|||
`post-content`.`plink` AS `plink`,
|
||||
`post-content`.`location` AS `location`,
|
||||
`post-content`.`coord` AS `coord`,
|
||||
`post-content`.`sensitive` AS `sensitive`,
|
||||
`post-content`.`app` AS `app`,
|
||||
`post-content`.`object-type` AS `object-type`,
|
||||
`post-content`.`object` AS `object`,
|
||||
|
@ -2339,6 +2384,7 @@ CREATE VIEW `post-thread-user-view` AS SELECT
|
|||
`post-content`.`plink` AS `plink`,
|
||||
`post-content`.`location` AS `location`,
|
||||
`post-content`.`coord` AS `coord`,
|
||||
`post-content`.`sensitive` AS `sensitive`,
|
||||
`post-content`.`app` AS `app`,
|
||||
`post-content`.`object-type` AS `object-type`,
|
||||
`post-content`.`object` AS `object`,
|
||||
|
@ -2509,6 +2555,7 @@ CREATE VIEW `post-view` AS SELECT
|
|||
`post-content`.`plink` AS `plink`,
|
||||
`post-content`.`location` AS `location`,
|
||||
`post-content`.`coord` AS `coord`,
|
||||
`post-content`.`sensitive` AS `sensitive`,
|
||||
`post-content`.`app` AS `app`,
|
||||
`post-content`.`object-type` AS `object-type`,
|
||||
`post-content`.`object` AS `object`,
|
||||
|
@ -2655,6 +2702,7 @@ CREATE VIEW `post-thread-view` AS SELECT
|
|||
`post-content`.`plink` AS `plink`,
|
||||
`post-content`.`location` AS `location`,
|
||||
`post-content`.`coord` AS `coord`,
|
||||
`post-content`.`sensitive` AS `sensitive`,
|
||||
`post-content`.`app` AS `app`,
|
||||
`post-content`.`object-type` AS `object-type`,
|
||||
`post-content`.`object` AS `object`,
|
||||
|
|
|
@ -34,6 +34,7 @@ General
|
|||
* y - for you
|
||||
* f - followers
|
||||
* r - sharers of sharers
|
||||
* q - quiet sharers
|
||||
* h - what's hot
|
||||
* i - Images
|
||||
* v - Videos
|
||||
|
|
|
@ -376,8 +376,8 @@ code</code></td>
|
|||
[li] Second list element<br>
|
||||
[/ul]<br>
|
||||
[list]<br>
|
||||
[*] First list element<br>
|
||||
[*] Second list element<br>
|
||||
[li] First list element<br>
|
||||
[li] Second list element<br>
|
||||
[/list]</td>
|
||||
<td>
|
||||
<ul class="listbullet" style="list-style-type: circle;">
|
||||
|
@ -388,12 +388,12 @@ code</code></td>
|
|||
</tr>
|
||||
<tr>
|
||||
<td>[ol]<br>
|
||||
[*] First list element<br>
|
||||
[*] Second list element<br>
|
||||
[li] First list element<br>
|
||||
[li] Second list element<br>
|
||||
[/ol]<br>
|
||||
[list=1]<br>
|
||||
[*] First list element<br>
|
||||
[*] Second list element<br>
|
||||
[li] First list element<br>
|
||||
[li] Second list element<br>
|
||||
[/list]</td>
|
||||
<td>
|
||||
<ul class="listdecimal" style="list-style-type: decimal;">
|
||||
|
@ -404,8 +404,8 @@ code</code></td>
|
|||
</tr>
|
||||
<tr>
|
||||
<td>[list=]<br>
|
||||
[*] First list element<br>
|
||||
[*] Second list element<br>
|
||||
[li] First list element<br>
|
||||
[li] Second list element<br>
|
||||
[/list]</td>
|
||||
<td>
|
||||
<ul class="listnone" style="list-style-type: none;">
|
||||
|
@ -416,8 +416,8 @@ code</code></td>
|
|||
</tr>
|
||||
<tr>
|
||||
<td>[list=i]<br>
|
||||
[*] First list element<br>
|
||||
[*] Second list element<br>
|
||||
[li] First list element<br>
|
||||
[li] Second list element<br>
|
||||
[/list]</td>
|
||||
<td>
|
||||
<ul class="listlowerroman" style="list-style-type: lower-roman;">
|
||||
|
@ -428,8 +428,8 @@ code</code></td>
|
|||
</tr>
|
||||
<tr>
|
||||
<td>[list=I]<br>
|
||||
[*] First list element<br>
|
||||
[*] Second list element<br>
|
||||
[li] First list element<br>
|
||||
[li] Second list element<br>
|
||||
[/list]</td>
|
||||
<td>
|
||||
<ul class="listupperroman" style="list-style-type: upper-roman;">
|
||||
|
@ -440,8 +440,8 @@ code</code></td>
|
|||
</tr>
|
||||
<tr>
|
||||
<td>[list=a]<br>
|
||||
[*] First list element<br>
|
||||
[*] Second list element<br>
|
||||
[li] First list element<br>
|
||||
[li] Second list element<br>
|
||||
[/list]</td>
|
||||
<td>
|
||||
<ul class="listloweralpha" style="list-style-type: lower-alpha;">
|
||||
|
@ -452,8 +452,8 @@ code</code></td>
|
|||
</tr>
|
||||
<tr>
|
||||
<td>[list=A]<br>
|
||||
[*] First list element<br>
|
||||
[*] Second list element<br>
|
||||
[li] First list element<br>
|
||||
[li] Second list element<br>
|
||||
[/list]</td>
|
||||
<td>
|
||||
<ul class="listupperalpha" style="list-style-type: upper-alpha;">
|
||||
|
|
|
@ -33,6 +33,7 @@ Predefined Channels
|
|||
* Language: Posts in your language.
|
||||
* Followers: Posts from your followers that you don't follow.
|
||||
* Sharers of sharers: Posts from accounts that are followed by accounts that you follow.
|
||||
* Quiet sharers: Posts from accounts that you follow but who don't post very often.
|
||||
* Images: Posts with images.
|
||||
* Audio: Posts with audio.
|
||||
* Videos: Posts with videos.
|
||||
|
@ -56,35 +57,43 @@ Each channel is defined by these values:
|
|||
Additional keywords for the full text search
|
||||
---
|
||||
|
||||
Additionally to the search for content, there are additional keywords that can be used in the full text search:
|
||||
Additionally to the search for content, there are keywords that can be used in the full text search.
|
||||
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 "from:nickname" or "from:nickname@domain.tld" to search for group post of the given group.
|
||||
* group - Use "group:nickname" or "group:nickname@domain.tld" to search for group post 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).
|
||||
* source:person - The post is created by a regular user account.
|
||||
* source:organization - The post is created by an organisation.
|
||||
* source:group - The post is created by or distributed via a group.
|
||||
* source:service - The posts originates from a service account. This source type is often used to mark bot accounts.
|
||||
* source:application - The post is created by an application. This is most likely unused in the fediverse for post creation.
|
||||
* source:service | source:news - The posts originates from a service account. This source type is often used to mark bot accounts.
|
||||
* source:application | source:relay - The post is created by an application. This is most likely unused in the fediverse for post creation.
|
||||
* tag - Use "tag:tagname" to search for a specific tag.
|
||||
* network - Use this to include or exclude some networks from your channel.
|
||||
* network:apub - ActivityPub (Used by the systems in the Fediverse)
|
||||
* network:dfrn - Legacy Friendica protocol. Nowayday Friendica mostly uses ActivityPub.
|
||||
* network:dspr - The Diaspora protocol is mainly used by Diaspora itself. Some other systems support the protocol as well like Hubzilla, Socialhome or Ganggo.
|
||||
* media - With this keyword you can search for attached media.
|
||||
* media:image | media:photo | media:picture - The post contains an image
|
||||
* media:video - The post contains a video
|
||||
* media:audio - The post contains audio
|
||||
* media:card - The post contains a link preview card
|
||||
* media:post - The post links another post, means it is a quoted post
|
||||
* network | net - Use this to include or exclude some networks from your channel.
|
||||
* network:apub | network:activitypub - ActivityPub (Used by the systems in the Fediverse)
|
||||
* network:dfrn | network:friendica - Legacy Friendica protocol. Nowayday Friendica mostly uses ActivityPub.
|
||||
* network:dspr | network:diaspora - The Diaspora protocol is mainly used by Diaspora itself. Some other systems support the protocol as well like Hubzilla, Socialhome or Ganggo.
|
||||
* network:feed - RSS/Atom feeds
|
||||
* network:mail - Mails that had been imported via IMAP.
|
||||
* network:stat - The OStatus protocol is mainly used by old GNU Social installations.
|
||||
* network:dscs - Posts that are received by the Discourse connector.
|
||||
* network:tmbl - Posts that are received by the Tumblr connector.
|
||||
* network:bsky - Posts that are received by the Bluesky connector.
|
||||
* network:stat | network:ostatus - The OStatus protocol is mainly used by old GNU Social installations.
|
||||
* network:dscs | network:discourse - Posts that are received by the Discourse connector.
|
||||
* network:tmbl | network:tumblr - Posts that are received by the Tumblr connector.
|
||||
* network:bsky | network:bluesky - Posts that are received by the Bluesky connector.
|
||||
* platform - Use this to include or exclude some platforms from your channel, e.g. "+platform:friendica". In the case of group postings, the search text contains both the platform of the group server and the author's platform.
|
||||
* visibility - You have the choice between different visibilities. You can only see unlisted or private posts that you have the access for.
|
||||
* visibility:public
|
||||
* visibility:unlisted
|
||||
* visibility:private
|
||||
* language - 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"
|
|
@ -44,7 +44,7 @@ For alternative server configurations (such as Nginx server and MariaDB database
|
|||
|
||||
### Optional
|
||||
|
||||
* PHP ImageMagick extension (php-imagick) for animated GIF support.
|
||||
* PHP ImageMagick extension (php-imagick) for animated GIF and animated WebP support.
|
||||
|
||||
## Installation procedure
|
||||
|
||||
|
|
|
@ -16,6 +16,8 @@ Fields
|
|||
| access-key | Access key | varchar(1) | YES | | NULL | |
|
||||
| include-tags | Comma separated list of tags that will be included in the channel | varchar(1023) | YES | | NULL | |
|
||||
| exclude-tags | Comma separated list of tags that aren't allowed in the channel | varchar(1023) | YES | | NULL | |
|
||||
| min-size | Minimum post size | int unsigned | YES | | NULL | |
|
||||
| max-size | Maximum post size | int unsigned | YES | | NULL | |
|
||||
| full-text-search | Full text search pattern, see https://mariadb.com/kb/en/full-text-index-overview/#in-boolean-mode | varchar(1023) | YES | | NULL | |
|
||||
| media-type | Filtered media types | smallint unsigned | YES | | NULL | |
|
||||
| languages | Desired languages | mediumtext | YES | | NULL | |
|
||||
|
|
|
@ -6,17 +6,18 @@ Contact relations
|
|||
Fields
|
||||
------
|
||||
|
||||
| Field | Description | Type | Null | Key | Default | Extra |
|
||||
| --------------------- | -------------------------------------------------------- | ----------------- | ---- | --- | ------------------- | ----- |
|
||||
| cid | contact the related contact had interacted with | int unsigned | NO | PRI | 0 | |
|
||||
| relation-cid | related contact who had interacted with the contact | int unsigned | NO | PRI | 0 | |
|
||||
| last-interaction | Date of the last interaction by relation-cid on cid | datetime | NO | | 0001-01-01 00:00:00 | |
|
||||
| follow-updated | Date of the last update of the contact relationship | datetime | NO | | 0001-01-01 00:00:00 | |
|
||||
| follows | if true, relation-cid follows cid | boolean | NO | | 0 | |
|
||||
| score | score for interactions of cid on relation-cid | smallint unsigned | YES | | NULL | |
|
||||
| relation-score | score for interactions of relation-cid on cid | smallint unsigned | YES | | NULL | |
|
||||
| thread-score | score for interactions of cid on threads of relation-cid | smallint unsigned | YES | | NULL | |
|
||||
| relation-thread-score | score for interactions of relation-cid on threads of cid | smallint unsigned | YES | | NULL | |
|
||||
| Field | Description | Type | Null | Key | Default | Extra |
|
||||
| --------------------- | ----------------------------------------------------------------------- | ----------------- | ---- | --- | ------------------- | ----- |
|
||||
| cid | contact the related contact had interacted with | int unsigned | NO | PRI | 0 | |
|
||||
| relation-cid | related contact who had interacted with the contact | int unsigned | NO | PRI | 0 | |
|
||||
| last-interaction | Date of the last interaction by relation-cid on cid | datetime | NO | | 0001-01-01 00:00:00 | |
|
||||
| follow-updated | Date of the last update of the contact relationship | datetime | NO | | 0001-01-01 00:00:00 | |
|
||||
| follows | if true, relation-cid follows cid | boolean | NO | | 0 | |
|
||||
| score | score for interactions of cid on relation-cid | smallint unsigned | YES | | NULL | |
|
||||
| relation-score | score for interactions of relation-cid on cid | smallint unsigned | YES | | NULL | |
|
||||
| thread-score | score for interactions of cid on threads of relation-cid | smallint unsigned | YES | | NULL | |
|
||||
| relation-thread-score | score for interactions of relation-cid on threads of cid | smallint unsigned | YES | | NULL | |
|
||||
| post-score | score for the amount of posts from cid that can be seen by relation-cid | smallint unsigned | YES | | NULL | |
|
||||
|
||||
Indexes
|
||||
------------
|
||||
|
|
|
@ -17,6 +17,7 @@ Fields
|
|||
| location | text location where this item originated | varchar(255) | NO | | | |
|
||||
| coord | longitude/latitude pair representing location where this item originated | varchar(255) | NO | | | |
|
||||
| language | Language information about this post | text | YES | | NULL | |
|
||||
| sensitive | If true, this post contains sensitive content | boolean | YES | | NULL | |
|
||||
| app | application which generated this item | varchar(255) | NO | | | |
|
||||
| rendered-hash | | varchar(32) | NO | | | |
|
||||
| rendered-html | item.body converted to html | mediumtext | YES | | NULL | |
|
||||
|
|
|
@ -12,9 +12,11 @@ Fields
|
|||
| 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 | |
|
||||
| language | Language information about this post | varchar(128) | YES | | NULL | |
|
||||
| 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 | |
|
||||
| created | | datetime | YES | | NULL | |
|
||||
| network | | char(4) | YES | | NULL | |
|
||||
| restricted | If true, this post is either unlisted or not from a federated network | boolean | NO | | 0 | |
|
||||
| comments | Number of comments | mediumint unsigned | YES | | NULL | |
|
||||
| activities | Number of activities (like, dislike, ...) | mediumint unsigned | YES | | NULL | |
|
||||
|
|
|
@ -6,13 +6,16 @@ Content 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 | |
|
||||
| network | | char(4) | YES | | NULL | |
|
||||
| private | 0=public, 1=private, 2=unlisted | tinyint unsigned | YES | | NULL | |
|
||||
| searchtext | Simplified text for the full text search | mediumtext | YES | | NULL | |
|
||||
| created | | datetime | YES | | NULL | |
|
||||
| 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 | |
|
||||
| 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 | |
|
||||
| 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 | |
|
||||
| created | | datetime | YES | | NULL | |
|
||||
| restricted | If true, this post is either unlisted or not from a federated network | boolean | NO | | 0 | |
|
||||
|
||||
Indexes
|
||||
------------
|
||||
|
@ -20,6 +23,7 @@ Indexes
|
|||
| Name | Fields |
|
||||
| ---------- | -------------------- |
|
||||
| PRIMARY | uri-id |
|
||||
| owner-id | owner-id |
|
||||
| created | created |
|
||||
| searchtext | FULLTEXT, searchtext |
|
||||
|
||||
|
@ -29,5 +33,6 @@ Foreign Keys
|
|||
| Field | Target Table | Target Field |
|
||||
|-------|--------------|--------------|
|
||||
| uri-id | [item-uri](help/database/db_item-uri) | id |
|
||||
| owner-id | [contact](help/database/db_contact) | id |
|
||||
|
||||
Return to [database documentation](help/database)
|
||||
|
|
|
@ -356,8 +356,8 @@ Zeilen</code></td>
|
|||
[li] Zweites Listenelement<br>
|
||||
[/ul]<br>
|
||||
[list]<br>
|
||||
[*] Erstes Listenelement<br>
|
||||
[*] Zweites Listenelement<br>
|
||||
[li] Erstes Listenelement<br>
|
||||
[li] Zweites Listenelement<br>
|
||||
[/list]</td>
|
||||
<td>
|
||||
<ul class="listbullet" style="list-style-type: circle;">
|
||||
|
@ -368,12 +368,12 @@ Zeilen</code></td>
|
|||
</tr>
|
||||
<tr>
|
||||
<td>[ol]<br>
|
||||
[*] Erstes Listenelement<br>
|
||||
[*] Zweites Listenelement<br>
|
||||
[li] Erstes Listenelement<br>
|
||||
[li] Zweites Listenelement<br>
|
||||
[/ol]<br>
|
||||
[list=1]<br>
|
||||
[*] Erstes Listenelement<br>
|
||||
[*] Zweites Listenelement<br>
|
||||
[li] Erstes Listenelement<br>
|
||||
[li] Zweites Listenelement<br>
|
||||
[/list]</td>
|
||||
<td>
|
||||
<ul class="listdecimal" style="list-style-type: decimal;">
|
||||
|
@ -384,8 +384,8 @@ Zeilen</code></td>
|
|||
</tr>
|
||||
<tr>
|
||||
<td>[list=]<br>
|
||||
[*] Erstes Listenelement<br>
|
||||
[*] Zweites Listenelement<br>
|
||||
[li] Erstes Listenelement<br>
|
||||
[li] Zweites Listenelement<br>
|
||||
[/list]</td>
|
||||
<td>
|
||||
<ul class="listnone" style="list-style-type: none;">
|
||||
|
@ -396,8 +396,8 @@ Zeilen</code></td>
|
|||
</tr>
|
||||
<tr>
|
||||
<td>[list=i]<br>
|
||||
[*] Erstes Listenelement<br>
|
||||
[*] Zweites Listenelement<br>
|
||||
[li] Erstes Listenelement<br>
|
||||
[li] Zweites Listenelement<br>
|
||||
[/list]</td>
|
||||
<td>
|
||||
<ul class="listlowerroman" style="list-style-type: lower-roman;">
|
||||
|
@ -408,8 +408,8 @@ Zeilen</code></td>
|
|||
</tr>
|
||||
<tr>
|
||||
<td>[list=I]<br>
|
||||
[*] Erstes Listenelement<br>
|
||||
[*] Zweites Listenelement<br>
|
||||
[li] Erstes Listenelement<br>
|
||||
[li] Zweites Listenelement<br>
|
||||
[/list]</td>
|
||||
<td>
|
||||
<ul class="listupperroman" style="list-style-type: upper-roman;">
|
||||
|
@ -420,8 +420,8 @@ Zeilen</code></td>
|
|||
</tr>
|
||||
<tr>
|
||||
<td>[list=a]<br>
|
||||
[*] Erstes Listenelement<br>
|
||||
[*] Zweites Listenelement<br>
|
||||
[li] Erstes Listenelement<br>
|
||||
[li] Zweites Listenelement<br>
|
||||
[/list]</td>
|
||||
<td>
|
||||
<ul class="listloweralpha" style="list-style-type: lower-alpha;">
|
||||
|
@ -432,8 +432,8 @@ Zeilen</code></td>
|
|||
</tr>
|
||||
<tr>
|
||||
<td>[list=A]<br>
|
||||
[*] Erstes Listenelement<br>
|
||||
[*] Zweites Listenelement<br>
|
||||
[li] Erstes Listenelement<br>
|
||||
[li] Zweites Listenelement<br>
|
||||
[/list]</td>
|
||||
<td>
|
||||
<ul class="listupperalpha" style="list-style-type: upper-alpha;">
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 5 KiB After Width: | Height: | Size: 7.8 KiB |
|
@ -278,6 +278,7 @@ function item_process(array $post, array $request, bool $preview, string $return
|
|||
$post['quote-uri-id'] = Item::getQuoteUriId($post['body'], $post['uid']);
|
||||
$post['body'] = BBCode::removeSharedData(Item::setHashtags($post['body']));
|
||||
$post['writable'] = true;
|
||||
$post['sensitive'] = true;
|
||||
|
||||
$o = DI::conversation()->render([$post], Conversation::MODE_SEARCH, false, true);
|
||||
|
||||
|
|
|
@ -132,8 +132,6 @@ function photos_post(App $a)
|
|||
throw new HTTPException\NotFoundException(DI::l10n()->t('User not found.'));
|
||||
}
|
||||
|
||||
$phototypes = Images::supportedTypes();
|
||||
|
||||
$can_post = false;
|
||||
$visitor = 0;
|
||||
|
||||
|
@ -337,7 +335,7 @@ function photos_post(App $a)
|
|||
|
||||
if (DBA::isResult($photos)) {
|
||||
$photo = $photos[0];
|
||||
$ext = $phototypes[$photo['type']];
|
||||
$ext = Images::getExtensionByMimeType($photo['type']);
|
||||
Photo::update(
|
||||
['desc' => $desc, 'album' => $albname, 'allow_cid' => $str_contact_allow, 'allow_gid' => $str_circle_allow, 'deny_cid' => $str_contact_deny, 'deny_gid' => $str_circle_deny],
|
||||
['resource-id' => $resource_id, 'uid' => $page_owner_uid]
|
||||
|
@ -590,8 +588,6 @@ function photos_content(App $a)
|
|||
|
||||
$profile = Profile::getByUID($user['uid']);
|
||||
|
||||
$phototypes = Images::supportedTypes();
|
||||
|
||||
$_SESSION['photo_return'] = DI::args()->getCommand();
|
||||
|
||||
// Parse arguments
|
||||
|
@ -844,7 +840,7 @@ function photos_content(App $a)
|
|||
foreach ($r as $rr) {
|
||||
$twist = !$twist;
|
||||
|
||||
$ext = $phototypes[$rr['type']];
|
||||
$ext = Images::getExtensionByMimeType($rr['type']);
|
||||
|
||||
$imgalt_e = $rr['filename'];
|
||||
$desc_e = $rr['desc'];
|
||||
|
@ -855,7 +851,7 @@ function photos_content(App $a)
|
|||
'link' => 'photos/' . $user['nickname'] . '/image/' . $rr['resource-id']
|
||||
. ($order_field === 'created' ? '?order=created' : ''),
|
||||
'title' => DI::l10n()->t('View Photo'),
|
||||
'src' => 'photo/' . $rr['resource-id'] . '-' . $rr['scale'] . '.' . $ext,
|
||||
'src' => 'photo/' . $rr['resource-id'] . '-' . $rr['scale'] . $ext,
|
||||
'alt' => $imgalt_e,
|
||||
'desc' => $desc_e,
|
||||
'ext' => $ext,
|
||||
|
@ -1013,9 +1009,9 @@ function photos_content(App $a)
|
|||
}
|
||||
|
||||
$photo = [
|
||||
'href' => 'photo/' . $hires['resource-id'] . '-' . $hires['scale'] . '.' . $phototypes[$hires['type']],
|
||||
'href' => 'photo/' . $hires['resource-id'] . '-' . $hires['scale'] . Images::getExtensionByMimeType($hires['type']),
|
||||
'title' => DI::l10n()->t('View Full Size'),
|
||||
'src' => 'photo/' . $lores['resource-id'] . '-' . $lores['scale'] . '.' . $phototypes[$lores['type']] . '?_u=' . DateTimeFormat::utcNow('ymdhis'),
|
||||
'src' => 'photo/' . $lores['resource-id'] . '-' . $lores['scale'] . Images::getExtensionByMimeType($lores['type']) . '?_u=' . DateTimeFormat::utcNow('ymdhis'),
|
||||
'height' => $hires['height'],
|
||||
'width' => $hires['width'],
|
||||
'album' => $hires['album'],
|
||||
|
@ -1043,7 +1039,7 @@ function photos_content(App $a)
|
|||
$pager = new Pager(DI::l10n(), DI::args()->getQueryString());
|
||||
|
||||
$params = ['order' => ['id'], 'limit' => [$pager->getStart(), $pager->getItemsPerPage()]];
|
||||
$items = Post::toArray(Post::selectForUser($link_item['uid'], Item::ITEM_FIELDLIST, $condition, $params));
|
||||
$items = Post::toArray(Post::selectForUser($link_item['uid'], array_merge(Item::ITEM_FIELDLIST, ['author-alias']), $condition, $params));
|
||||
|
||||
if (DI::userSession()->getLocalUserId() == $link_item['uid']) {
|
||||
Item::update(['unseen' => false], ['parent' => $link_item['parent']]);
|
||||
|
|
|
@ -1,8 +0,0 @@
|
|||
fpostit
|
||||
|
||||
original author: Devlon Duthied
|
||||
|
||||
see his blog posting:
|
||||
http://blog.duthied.com/2011/09/13/node-agnostic-friendika-bookmarklet/
|
||||
|
||||
original published at github https://github.com/duthied/Friendika-Bookmarklet
|
|
@ -1,11 +0,0 @@
|
|||
javascript: (function() {
|
||||
the_url = 'http://testbubble.com/fpostit.php?url=' + encodeURIComponent(window.location.href) + '&title=' + encodeURIComponent(document.title) + '&text=' + encodeURIComponent('' (window.getSelection ? window.getSelection() : document.getSelection ? document.getSelection() : document.selection.createRange().text));
|
||||
a_funct = function() {
|
||||
if (!window.open(the_url, 'fpostit', 'location=yes,links=no,scrollbars=no,toolbar=no,width=600,height=300')) location.href = the_url;
|
||||
};
|
||||
if (/Firefox/.test(navigator.userAgent)) {
|
||||
setTimeout(a_funct, 0)
|
||||
} else {
|
||||
a_funct()
|
||||
}
|
||||
})()
|
|
@ -1,148 +0,0 @@
|
|||
<?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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
if (($_POST["friendica_acct_name"] != '') && ($_POST["friendica_password"] != '')) {
|
||||
setcookie("username", $_POST["friendica_acct_name"], time()+60*60*24*300);
|
||||
setcookie("password", $_POST["friendica_password"], time()+60*60*24*300);
|
||||
}
|
||||
|
||||
?>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
body {
|
||||
font-family: arial, Helvetica,sans-serif;
|
||||
margin: 0px;
|
||||
}
|
||||
.wrap1 {
|
||||
padding: 2px 5px;
|
||||
background-color: #729FCF;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.wrap2 {
|
||||
margin-left: 10px;
|
||||
font-size: 12px;
|
||||
}
|
||||
.logo {
|
||||
margin-left: 3px;
|
||||
margin-right: 5px;
|
||||
float: left;
|
||||
}
|
||||
h2 {
|
||||
color: #ffffff;
|
||||
}
|
||||
.error {
|
||||
background-color: #FFFF66;
|
||||
font-size: 12px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<?php
|
||||
|
||||
if (isset($_GET['title'])) {
|
||||
$title = $_GET['title'];
|
||||
}
|
||||
if (isset($_GET['text'])) {
|
||||
$text = $_GET['text'];
|
||||
}
|
||||
if (isset($_GET['url'])) {
|
||||
$url = $_GET['url'];
|
||||
}
|
||||
|
||||
if ((isset($title)) && (isset($text)) && (isset($url))) {
|
||||
$content = "$title\nsource:$url\n\n$text";
|
||||
} else {
|
||||
$content = $_POST['content'];
|
||||
}
|
||||
|
||||
if (isset($_POST['submit'])) {
|
||||
|
||||
if (($_POST["friendica_acct_name"] != '') && ($_POST["friendica_password"] != '')) {
|
||||
$acctname = $_POST["friendica_acct_name"];
|
||||
$tmp_account_array = explode("@", $acctname);
|
||||
if (isset($tmp_account_array[1])) {
|
||||
$username = $tmp_account_array[0];
|
||||
$hostname = $tmp_account_array[1];
|
||||
}
|
||||
$password = $_POST["friendica_password"];
|
||||
$content = $_POST["content"];
|
||||
|
||||
$url = "http://" . $hostname . '/api/statuses/update';
|
||||
$data = ['status' => $content];
|
||||
|
||||
// echo "posting to: $url<br/>";
|
||||
|
||||
$c = curl_init();
|
||||
curl_setopt($c, CURLOPT_URL, $url);
|
||||
curl_setopt($c, CURLOPT_USERPWD, "$username:$password");
|
||||
curl_setopt($c, CURLOPT_POSTFIELDS, $data);
|
||||
curl_setopt($c, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($c, CURLOPT_FOLLOWLOCATION, true);
|
||||
$c_result = curl_exec($c);
|
||||
if(curl_errno($c)){
|
||||
$error = curl_error($c);
|
||||
showForm($error, $content);
|
||||
}
|
||||
|
||||
curl_close($c);
|
||||
if (!isset($error)) {
|
||||
echo '<script language="javascript" type="text/javascript">window.close();</script>';
|
||||
}
|
||||
|
||||
} else {
|
||||
$error = "Missing account name and/or password...try again please";
|
||||
showForm($error, $content);
|
||||
}
|
||||
|
||||
} else {
|
||||
showForm(null, $content);
|
||||
}
|
||||
|
||||
function showForm($error, $content) {
|
||||
$username_cookie = $_COOKIE['username'];
|
||||
$password_cookie = $_COOKIE['password'];
|
||||
|
||||
echo <<<EOF
|
||||
<div class='wrap1'>
|
||||
<h2><img class='logo' width="32" height="32" src='friendica.svg' align='middle';/>
|
||||
Friendica Bookmarklet</h2>
|
||||
</div>
|
||||
|
||||
<div class="wrap2">
|
||||
<form method="post" action="{$_SERVER['PHP_SELF']}">
|
||||
Enter the email address of the Friendica Account that you want to cross-post to:(example: user@friendica.org)<br /><br />
|
||||
Account ID: <input type="text" name="friendica_acct_name" value="{$username_cookie}" size="50"/><br />
|
||||
Password: <input type="password" name="friendica_password" value="{$password_cookie}" size="50"/><br />
|
||||
<textarea name="content" id="content" rows="6" cols="70">{$content}</textarea><br />
|
||||
<input type="submit" value="PostIt!" name="submit" /> <span class='error'>$error</span>
|
||||
</form>
|
||||
<p></p>
|
||||
</div>
|
||||
EOF;
|
||||
|
||||
}
|
||||
?>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -1 +0,0 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" width="192" height="192" viewBox="0 0 1920 1920"><rect width="1800" height="1800" x="60" y="60" fill="#1872a2" rx="333"/><path fill="#febf19" d="M40 371q0-136 98-234 98-97 234-97h1178q136 0 233 97 97 98 97 234v1178q0 136-97 234-97 97-233 97H372q-137 0-234-97-97-98-98-234Zm1510-258h-296v442H666v373l587-4 1 441H666v442h884q107 0 182-75 75-74 74-183V371q0-108-74-182-74-75-182-76z"/></svg>
|
Before Width: | Height: | Size: 428 B |
|
@ -64,7 +64,7 @@ class App
|
|||
{
|
||||
const PLATFORM = 'Friendica';
|
||||
const CODENAME = 'Yellow Archangel';
|
||||
const VERSION = '2024.03-dev';
|
||||
const VERSION = '2024.03-rc';
|
||||
|
||||
// Allow themes to control internal parameters
|
||||
// by changing App values in theme.php
|
||||
|
|
|
@ -150,7 +150,7 @@ HELP;
|
|||
|
||||
if ($valid) {
|
||||
$this->out('3', false);
|
||||
$image = new Image($imgdata, Images::getMimeTypeByData($imgdata));
|
||||
$image = new Image($imgdata);
|
||||
if (!$image->isValid()) {
|
||||
$this->out(' ' . $this->l10n->t('invalid image for id %s', $resourceid) . ' ', false);
|
||||
$valid = false;
|
||||
|
|
|
@ -104,12 +104,17 @@ HELP;
|
|||
$actor = $this->getArgument(1);
|
||||
|
||||
$apcontact = APContact::getByURL($actor);
|
||||
if (empty($apcontact) || !in_array($apcontact['type'], ['Application', 'Service'])) {
|
||||
$this->out($actor . ' is no relay actor');
|
||||
if (empty($apcontact)) {
|
||||
$this->out($actor . ' wasn\'t found');
|
||||
return 1;
|
||||
}
|
||||
|
||||
if ($mode == 'add') {
|
||||
if (!APContact::isRelay($apcontact)) {
|
||||
$this->out($actor . ' is no relay actor');
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (Transmitter::sendRelayFollow($actor)) {
|
||||
$this->out('Successfully added ' . $actor);
|
||||
} else {
|
||||
|
|
|
@ -80,13 +80,18 @@ class Avatar
|
|||
return $fields;
|
||||
}
|
||||
|
||||
if (!$fetchResult->isSuccess()) {
|
||||
Logger::debug('Fetching was unsuccessful', ['avatar' => $avatar]);
|
||||
return $fields;
|
||||
}
|
||||
|
||||
$img_str = $fetchResult->getBodyString();
|
||||
if (empty($img_str)) {
|
||||
Logger::debug('Avatar is invalid', ['avatar' => $avatar]);
|
||||
return $fields;
|
||||
}
|
||||
|
||||
$image = new Image($img_str, Images::getMimeTypeByData($img_str));
|
||||
$image = new Image($img_str, $fetchResult->getContentType(), $avatar);
|
||||
if (!$image->isValid()) {
|
||||
Logger::debug('Avatar picture is invalid', ['avatar' => $avatar]);
|
||||
return $fields;
|
||||
|
@ -145,7 +150,7 @@ class Avatar
|
|||
return '';
|
||||
}
|
||||
|
||||
$path = $filename . $size . '.' . $image->getExt();
|
||||
$path = $filename . $size . $image->getExt();
|
||||
|
||||
$basepath = self::basePath();
|
||||
if (empty($basepath)) {
|
||||
|
|
|
@ -362,6 +362,7 @@ class Conversation
|
|||
$tpl = Renderer::getMarkupTemplate('jot.tpl');
|
||||
|
||||
$o .= Renderer::replaceMacros($tpl, [
|
||||
'$post' => $this->l10n->t($x['content'] ? 'Post to group' : 'Post'),
|
||||
'$new_post' => $this->l10n->t('New Post'),
|
||||
'$return_path' => $this->args->getQueryString(),
|
||||
'$action' => 'item',
|
||||
|
|
|
@ -28,6 +28,7 @@ class Channel extends Timeline
|
|||
const DISCOVER = 'discover';
|
||||
const FOLLOWERS = 'followers';
|
||||
const SHARERSOFSHARERS = 'sharersofsharers';
|
||||
const QUIETSHARERS = 'quietsharers';
|
||||
const IMAGE = 'image';
|
||||
const VIDEO = 'video';
|
||||
const AUDIO = 'audio';
|
||||
|
|
|
@ -30,6 +30,8 @@ namespace Friendica\Content\Conversation\Entity;
|
|||
* @property-read int $uid User of the channel
|
||||
* @property-read string $includeTags The tags to include in the channel
|
||||
* @property-read string $excludeTags The tags to exclude in the channel
|
||||
* @property-read int $minSize Minimum content size
|
||||
* @property-read int $maxSize Maximum content size
|
||||
* @property-read string $fullTextSearch full text search pattern
|
||||
* @property-read int $mediaType Media types that are included in the channel
|
||||
* @property-read array $languages Channel languages
|
||||
|
@ -57,6 +59,10 @@ class Timeline extends \Friendica\BaseEntity
|
|||
protected $includeTags;
|
||||
/** @var string */
|
||||
protected $excludeTags;
|
||||
/** @var int */
|
||||
protected $minSize;
|
||||
/** @var int */
|
||||
protected $maxSize;
|
||||
/** @var string */
|
||||
protected $fullTextSearch;
|
||||
/** @var int */
|
||||
|
@ -68,7 +74,7 @@ class Timeline extends \Friendica\BaseEntity
|
|||
/** @var bool */
|
||||
protected $valid;
|
||||
|
||||
public function __construct(string $code = null, string $label = null, string $description = null, string $accessKey = null, string $path = null, int $uid = null, string $includeTags = null, string $excludeTags = null, string $fullTextSearch = null, int $mediaType = null, int $circle = null, array $languages = null, bool $publish = null, bool $valid = null)
|
||||
public function __construct(string $code = null, string $label = null, string $description = null, string $accessKey = null, string $path = null, int $uid = null, string $includeTags = null, string $excludeTags = null, string $fullTextSearch = null, int $mediaType = null, int $circle = null, array $languages = null, bool $publish = null, bool $valid = null, int $minSize = null, int $maxSize = null)
|
||||
{
|
||||
$this->code = $code;
|
||||
$this->label = $label;
|
||||
|
@ -78,6 +84,8 @@ class Timeline extends \Friendica\BaseEntity
|
|||
$this->uid = $uid;
|
||||
$this->includeTags = $includeTags;
|
||||
$this->excludeTags = $excludeTags;
|
||||
$this->minSize = $minSize;
|
||||
$this->maxSize = $maxSize;
|
||||
$this->fullTextSearch = $fullTextSearch;
|
||||
$this->mediaType = $mediaType;
|
||||
$this->circle = $circle;
|
||||
|
|
|
@ -45,6 +45,7 @@ final class Channel extends Timeline
|
|||
new ChannelEntity(ChannelEntity::LANGUAGE, $native, $this->l10n->t('Posts in %s', $native), 'g'),
|
||||
new ChannelEntity(ChannelEntity::FOLLOWERS, $this->l10n->t('Followers'), $this->l10n->t('Posts from your followers that you don\'t follow'), 'f'),
|
||||
new ChannelEntity(ChannelEntity::SHARERSOFSHARERS, $this->l10n->t('Sharers of sharers'), $this->l10n->t('Posts from accounts that are followed by accounts that you follow'), 'r'),
|
||||
new ChannelEntity(ChannelEntity::QUIETSHARERS, $this->l10n->t('Quiet sharers'), $this->l10n->t('Posts from accounts that you follow but who don\'t post very often'), 'q'),
|
||||
new ChannelEntity(ChannelEntity::IMAGE, $this->l10n->t('Images'), $this->l10n->t('Posts with images'), 'i'),
|
||||
new ChannelEntity(ChannelEntity::AUDIO, $this->l10n->t('Audio'), $this->l10n->t('Posts with audio'), 'd'),
|
||||
new ChannelEntity(ChannelEntity::VIDEO, $this->l10n->t('Videos'), $this->l10n->t('Posts with videos'), 'v'),
|
||||
|
@ -55,6 +56,6 @@ final class Channel extends Timeline
|
|||
|
||||
public function isTimeline(string $selectedTab): bool
|
||||
{
|
||||
return in_array($selectedTab, [ChannelEntity::WHATSHOT, ChannelEntity::FORYOU, ChannelEntity::DISCOVER, ChannelEntity::FOLLOWERS, ChannelEntity::SHARERSOFSHARERS, ChannelEntity::IMAGE, ChannelEntity::VIDEO, ChannelEntity::AUDIO, ChannelEntity::LANGUAGE]);
|
||||
return in_array($selectedTab, [ChannelEntity::WHATSHOT, ChannelEntity::FORYOU, ChannelEntity::DISCOVER, ChannelEntity::FOLLOWERS, ChannelEntity::SHARERSOFSHARERS, ChannelEntity::QUIETSHARERS, ChannelEntity::IMAGE, ChannelEntity::VIDEO, ChannelEntity::AUDIO, ChannelEntity::LANGUAGE]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -52,6 +52,8 @@ final class UserDefinedChannel extends Timeline implements ICanCreateFromTableRo
|
|||
$row['languages'] ?? null,
|
||||
$row['publish'] ?? null,
|
||||
$row['valid'] ?? null,
|
||||
$row['min-size'] ?? null,
|
||||
$row['max-size'] ?? null,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -130,9 +130,11 @@ class UserDefinedChannel extends \Friendica\BaseRepository
|
|||
'circle' => $Channel->circle,
|
||||
'include-tags' => $Channel->includeTags,
|
||||
'exclude-tags' => $Channel->excludeTags,
|
||||
'min-size' => $Channel->minSize,
|
||||
'max-size' => $Channel->maxSize,
|
||||
'full-text-search' => $Channel->fullTextSearch,
|
||||
'media-type' => $Channel->mediaType,
|
||||
'languages' => serialize($Channel->languages),
|
||||
'languages' => !empty($Channel->languages) ? serialize($Channel->languages) : null,
|
||||
'publish' => $Channel->publish,
|
||||
'valid' => $this->isValid($Channel->fullTextSearch),
|
||||
];
|
||||
|
|
|
@ -695,7 +695,7 @@ class Item
|
|||
$item['body'] = Post\Media::addAttachmentsToBody($item['uri-id'], $item['body']);
|
||||
}
|
||||
|
||||
$shared_content = BBCode::getShareOpeningTag($item['author-name'], $item['author-link'], $item['author-avatar'], $item['plink'], $item['created'], $item['guid'], $item['uri']);
|
||||
$shared_content = BBCode::getShareOpeningTag($item['author-name'], $item['author-link'], $item['author-avatar'], $item['plink'] ?? $item['uri'], $item['created'], $item['guid'], $item['uri']);
|
||||
|
||||
if (!empty($item['title'])) {
|
||||
$shared_content .= '[h3]' . $item['title'] . "[/h3]\n";
|
||||
|
@ -1105,4 +1105,35 @@ class Item
|
|||
Tag::store($toUriId, $receiver['type'], $receiver['name'], $receiver['url']);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the item is too old
|
||||
*
|
||||
* @param string $created
|
||||
* @param integer $uid
|
||||
* @return boolean item is too old
|
||||
*/
|
||||
public function isTooOld(string $created, int $uid = 0): bool
|
||||
{
|
||||
// check for create date and expire time
|
||||
$expire_interval = DI::config()->get('system', 'dbclean-expire-days', 0);
|
||||
|
||||
if ($uid) {
|
||||
$user = DBA::selectFirst('user', ['expire'], ['uid' => $uid]);
|
||||
if (DBA::isResult($user) && ($user['expire'] > 0) && (($user['expire'] < $expire_interval) || ($expire_interval == 0))) {
|
||||
$expire_interval = $user['expire'];
|
||||
}
|
||||
}
|
||||
|
||||
if (($expire_interval > 0) && !empty($created)) {
|
||||
$expire_date = time() - ($expire_interval * 86400);
|
||||
$created_date = strtotime($created);
|
||||
if ($created_date < $expire_date) {
|
||||
Logger::notice('Item created before expiration interval.', ['created' => date('c', $created_date), 'expired' => date('c', $expire_date)]);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,10 +22,9 @@
|
|||
namespace Friendica\Content;
|
||||
|
||||
use DOMDocument;
|
||||
use DOMNode;
|
||||
use DOMText;
|
||||
use DOMXPath;
|
||||
use Exception;
|
||||
use Friendica\Content\Text\BBCode;
|
||||
use Friendica\Core\Cache\Enum\Duration;
|
||||
use Friendica\Core\Hook;
|
||||
use Friendica\Core\Renderer;
|
||||
|
@ -49,32 +48,15 @@ use Friendica\Util\Strings;
|
|||
*/
|
||||
class OEmbed
|
||||
{
|
||||
/**
|
||||
* Callback for fetching URL, checking allowance and returning formatted HTML
|
||||
*
|
||||
* @param array $matches
|
||||
* @return string Formatted HTML
|
||||
*/
|
||||
public static function replaceCallback(array $matches): string
|
||||
{
|
||||
$embedurl = $matches[1];
|
||||
$j = self::fetchURL($embedurl, !self::isAllowedURL($embedurl));
|
||||
$s = self::formatObject($j);
|
||||
|
||||
return $s;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get data from an URL to embed its content.
|
||||
*
|
||||
* @param string $embedurl The URL from which the data should be fetched.
|
||||
* @param bool $no_rich_type If set to true rich type content won't be fetched.
|
||||
* @param bool $use_parseurl Use the "ParseUrl" functionality to add additional data
|
||||
* @param string $embedurl The URL from which the data should be fetched.
|
||||
*
|
||||
* @return \Friendica\Object\OEmbed
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
*/
|
||||
public static function fetchURL(string $embedurl, bool $no_rich_type = false, bool $use_parseurl = true): \Friendica\Object\OEmbed
|
||||
private static function fetchURL(string $embedurl): \Friendica\Object\OEmbed
|
||||
{
|
||||
$embedurl = trim($embedurl, '\'"');
|
||||
|
||||
|
@ -119,7 +101,7 @@ class OEmbed
|
|||
$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'));
|
||||
if ($result->getReturnCode() === 200) {
|
||||
if ($result->isSuccess()) {
|
||||
$json_string = $result->getBodyString();
|
||||
break;
|
||||
}
|
||||
|
@ -157,57 +139,55 @@ class OEmbed
|
|||
}
|
||||
|
||||
// Improve the OEmbed data with data from OpenGraph, Twitter cards and other sources
|
||||
if ($use_parseurl) {
|
||||
$data = ParseUrl::getSiteinfoCached($embedurl, false);
|
||||
$data = ParseUrl::getSiteinfoCached($embedurl);
|
||||
|
||||
if (($oembed->type == 'error') && empty($data['title']) && empty($data['text'])) {
|
||||
return $oembed;
|
||||
}
|
||||
if (($oembed->type == 'error') && empty($data['title']) && empty($data['text'])) {
|
||||
return $oembed;
|
||||
}
|
||||
|
||||
if ($no_rich_type || ($oembed->type == 'error')) {
|
||||
$oembed->html = '';
|
||||
$oembed->type = $data['type'];
|
||||
if (!self::isAllowedURL($embedurl) || ($oembed->type == 'error')) {
|
||||
$oembed->html = '';
|
||||
$oembed->type = $data['type'];
|
||||
|
||||
if ($oembed->type == 'photo') {
|
||||
if (!empty($data['images'])) {
|
||||
$oembed->url = $data['images'][0]['src'];
|
||||
$oembed->width = $data['images'][0]['width'];
|
||||
$oembed->height = $data['images'][0]['height'];
|
||||
} else {
|
||||
$oembed->type = 'link';
|
||||
}
|
||||
if ($oembed->type == 'photo') {
|
||||
if (!empty($data['images'])) {
|
||||
$oembed->url = $data['images'][0]['src'];
|
||||
$oembed->width = $data['images'][0]['width'];
|
||||
$oembed->height = $data['images'][0]['height'];
|
||||
} else {
|
||||
$oembed->type = 'link';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($data['title'])) {
|
||||
$oembed->title = $data['title'];
|
||||
}
|
||||
if (!empty($data['title'])) {
|
||||
$oembed->title = $data['title'];
|
||||
}
|
||||
|
||||
if (!empty($data['text'])) {
|
||||
$oembed->description = $data['text'];
|
||||
}
|
||||
if (!empty($data['text'])) {
|
||||
$oembed->description = $data['text'];
|
||||
}
|
||||
|
||||
if (!empty($data['publisher_name'])) {
|
||||
$oembed->provider_name = $data['publisher_name'];
|
||||
}
|
||||
if (!empty($data['publisher_name'])) {
|
||||
$oembed->provider_name = $data['publisher_name'];
|
||||
}
|
||||
|
||||
if (!empty($data['publisher_url'])) {
|
||||
$oembed->provider_url = $data['publisher_url'];
|
||||
}
|
||||
if (!empty($data['publisher_url'])) {
|
||||
$oembed->provider_url = $data['publisher_url'];
|
||||
}
|
||||
|
||||
if (!empty($data['author_name'])) {
|
||||
$oembed->author_name = $data['author_name'];
|
||||
}
|
||||
if (!empty($data['author_name'])) {
|
||||
$oembed->author_name = $data['author_name'];
|
||||
}
|
||||
|
||||
if (!empty($data['author_url'])) {
|
||||
$oembed->author_url = $data['author_url'];
|
||||
}
|
||||
if (!empty($data['author_url'])) {
|
||||
$oembed->author_url = $data['author_url'];
|
||||
}
|
||||
|
||||
if (!empty($data['images']) && ($oembed->type != 'photo')) {
|
||||
$oembed->thumbnail_url = $data['images'][0]['src'];
|
||||
$oembed->thumbnail_width = $data['images'][0]['width'];
|
||||
$oembed->thumbnail_height = $data['images'][0]['height'];
|
||||
}
|
||||
if (!empty($data['images']) && ($oembed->type != 'photo')) {
|
||||
$oembed->thumbnail_url = $data['images'][0]['src'];
|
||||
$oembed->thumbnail_width = $data['images'][0]['width'];
|
||||
$oembed->thumbnail_height = $data['images'][0]['height'];
|
||||
}
|
||||
|
||||
Hook::callAll('oembed_fetch_url', $embedurl, $oembed);
|
||||
|
@ -219,9 +199,10 @@ class OEmbed
|
|||
* Returns a formatted string from OEmbed object
|
||||
*
|
||||
* @param \Friendica\Object\OEmbed $oembed
|
||||
* @param int $uriid
|
||||
* @return string
|
||||
*/
|
||||
private static function formatObject(\Friendica\Object\OEmbed $oembed): string
|
||||
private static function formatObject(\Friendica\Object\OEmbed $oembed, int $uriid): string
|
||||
{
|
||||
$ret = '<div class="oembed ' . $oembed->type . '">';
|
||||
|
||||
|
@ -241,22 +222,22 @@ class OEmbed
|
|||
'$escapedhtml' => base64_encode($oembed->html),
|
||||
'$tw' => $tw,
|
||||
'$th' => $th,
|
||||
'$turl' => $oembed->thumbnail_url,
|
||||
'$turl' => BBCode::proxyUrl($oembed->thumbnail_url, BBCode::INTERNAL, $uriid, Proxy::SIZE_SMALL),
|
||||
]);
|
||||
} else {
|
||||
$ret = $oembed->html;
|
||||
$ret .= Proxy::proxifyHtml($oembed->html, $uriid);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'photo':
|
||||
$ret .= '<img width="' . $oembed->width . '" src="' . Proxy::proxifyUrl($oembed->url) . '">';
|
||||
$ret .= '<img width="' . $oembed->width . '" src="' . BBCode::proxyUrl($oembed->url, BBCode::INTERNAL, $uriid, Proxy::SIZE_MEDIUM) . '">';
|
||||
break;
|
||||
|
||||
case 'link':
|
||||
break;
|
||||
|
||||
case 'rich':
|
||||
$ret .= Proxy::proxifyHtml($oembed->html);
|
||||
$ret .= Proxy::proxifyHtml($oembed->html, $uriid);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -294,12 +275,21 @@ class OEmbed
|
|||
$ret .= '<a href="' . $oembed->embed_url . '" rel="oembed">' . $oembed->embed_url . '</a>';
|
||||
}
|
||||
$ret .= "</h4>";
|
||||
if ($oembed->type == 'link') {
|
||||
if (!empty($oembed->thumbnail_url)) {
|
||||
$ret .= '<img width="' . $oembed->width . '" src="' . BBCode::proxyUrl($oembed->thumbnail_url, BBCode::INTERNAL, $uriid, Proxy::SIZE_MEDIUM) . '">';
|
||||
}
|
||||
if (!empty($oembed->description)) {
|
||||
$ret .= '<p>' . $oembed->description . '</p>';
|
||||
}
|
||||
}
|
||||
} elseif (!strpos($oembed->html, $oembed->embed_url)) {
|
||||
// add <a> for html2bbcode conversion
|
||||
$ret .= '<a href="' . $oembed->embed_url . '" rel="oembed">' . $oembed->title . '</a>';
|
||||
}
|
||||
|
||||
$ret .= '</div>';
|
||||
$test = Proxy::proxifyHtml($ret, $uriid);
|
||||
|
||||
return str_replace("\n", "", $ret);
|
||||
}
|
||||
|
@ -308,51 +298,19 @@ class OEmbed
|
|||
* Converts BBCode to HTML code
|
||||
*
|
||||
* @param string $text
|
||||
* @param int $uriid
|
||||
* @return string
|
||||
*/
|
||||
public static function BBCode2HTML(string $text): string
|
||||
public static function BBCode2HTML(string $text, int $uriid): string
|
||||
{
|
||||
if (DI::config()->get('system', 'no_oembed')) {
|
||||
return preg_replace("/\[embed\](.+?)\[\/embed\]/is", "<!-- oembed $1 --><i>" . DI::l10n()->t('Embedding disabled') . " : $1</i><!-- /oembed $1 -->", $text);
|
||||
}
|
||||
return preg_replace_callback("/\[embed\](.+?)\[\/embed\]/is", [self::class, 'replaceCallback'], $text);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find <span class='oembed'>..<a href='url' rel='oembed'>..</a></span>
|
||||
* and replace it with [embed]url[/embed]
|
||||
*
|
||||
* @param string $text
|
||||
* @return string
|
||||
*/
|
||||
public static function HTML2BBCode(string $text): string
|
||||
{
|
||||
// start parser only if 'oembed' is in text
|
||||
if (strpos($text, 'oembed')) {
|
||||
// convert non ascii chars to html entities
|
||||
$html_text = mb_convert_encoding($text, 'HTML-ENTITIES', mb_detect_encoding($text));
|
||||
|
||||
// If it doesn't parse at all, just return the text.
|
||||
$dom = new DOMDocument();
|
||||
if (!@$dom->loadHTML($html_text)) {
|
||||
return $text;
|
||||
}
|
||||
$xpath = new DOMXPath($dom);
|
||||
|
||||
$xattr = self::buildXPath('class', 'oembed');
|
||||
$entries = $xpath->query("//div[$xattr]");
|
||||
|
||||
$xattr = "@rel='oembed'"; //oe_build_xpath("rel","oembed");
|
||||
foreach ($entries as $e) {
|
||||
$href = $xpath->evaluate("a[$xattr]/@href", $e)->item(0)->nodeValue;
|
||||
if (!is_null($href)) {
|
||||
$e->parentNode->replaceChild(new DOMText('[embed]' . $href . '[/embed]'), $e);
|
||||
}
|
||||
}
|
||||
return self::getInnerHTML($dom->getElementsByTagName('body')->item(0));
|
||||
} else {
|
||||
if (!preg_match_all("/\[embed\](.+?)\[\/embed\]/is", $text, $matches, PREG_SET_ORDER)) {
|
||||
return $text;
|
||||
}
|
||||
foreach ($matches as $match) {
|
||||
$data = self::fetchURL($match[1]);
|
||||
$text = str_replace($match[0], self::formatObject($data, $uriid), $text);
|
||||
}
|
||||
return $text;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -373,26 +331,25 @@ class OEmbed
|
|||
return false;
|
||||
}
|
||||
|
||||
$str_allowed = DI::config()->get('system', 'allowed_oembed', '');
|
||||
if (empty($str_allowed)) {
|
||||
$allowed = DI::config()->get('system', 'allowed_oembed', '');
|
||||
if (empty($allowed)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$allowed = explode(',', $str_allowed);
|
||||
|
||||
return Network::isDomainAllowed($domain, $allowed);
|
||||
return Network::isDomainMatch($domain, explode(',', $allowed));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a formatted HTML code from given URL and sets optional title
|
||||
*
|
||||
* @param string $url URL to fetch
|
||||
* @param string $title Optional title (default: what comes from OEmbed object)
|
||||
* @param string $title title (default: what comes from OEmbed object)
|
||||
* @param int $uriid
|
||||
* @return string Formatted HTML
|
||||
*/
|
||||
public static function getHTML(string $url, string $title = ''): string
|
||||
public static function getHTML(string $url, string $title, int $uriid): string
|
||||
{
|
||||
$o = self::fetchURL($url, !self::isAllowedURL($url));
|
||||
$o = self::fetchURL($url);
|
||||
|
||||
if (!is_object($o) || property_exists($o, 'type') && $o->type == 'error') {
|
||||
throw new Exception('OEmbed failed for URL: ' . $url);
|
||||
|
@ -402,74 +359,8 @@ class OEmbed
|
|||
$o->title = $title;
|
||||
}
|
||||
|
||||
$html = self::formatObject($o);
|
||||
$html = self::formatObject($o, $uriid);
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the iframe HTML for an oembed attachment.
|
||||
*
|
||||
* Width and height are given by the remote, and are regularly too small for
|
||||
* the generated iframe.
|
||||
*
|
||||
* The width is entirely discarded for the actual width of the post, while fixed
|
||||
* height is used as a starting point before the inevitable resizing.
|
||||
*
|
||||
* Since the iframe is automatically resized on load, there are no need for ugly
|
||||
* and impractical scrollbars.
|
||||
*
|
||||
* @todo This function is currently unused until someone™ adds support for a separate OEmbed domain
|
||||
*
|
||||
* @param string $src Original remote URL to embed
|
||||
* @param string $width
|
||||
* @param string $height
|
||||
* @return string Formatted HTML
|
||||
*
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
* @see oembed_format_object()
|
||||
*/
|
||||
private static function iframe(string $src, string $width, string $height): string
|
||||
{
|
||||
if (!$height || strstr($height, '%')) {
|
||||
$height = '200';
|
||||
}
|
||||
$width = '100%';
|
||||
|
||||
$src = DI::baseUrl() . '/oembed/' . Strings::base64UrlEncode($src);
|
||||
return '<iframe onload="resizeIframe(this);" class="embed_rich" height="' . $height . '" width="' . $width . '" src="' . $src . '" allowfullscreen scrolling="no" frameborder="no">' . DI::l10n()->t('Embedded content') . '</iframe>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates attribute search XPath string
|
||||
*
|
||||
* Generates an XPath query to select elements whose provided attribute contains
|
||||
* the provided value in a space-separated list.
|
||||
*
|
||||
* @param string $attr Name of the attribute to search
|
||||
* @param string $value Value to search in a space-separated list
|
||||
* @return string
|
||||
*/
|
||||
private static function buildXPath(string $attr, $value): string
|
||||
{
|
||||
// https://www.westhoffswelt.de/blog/2009/6/9/select-html-elements-with-more-than-one-css-class-using-xpath
|
||||
return "contains(normalize-space(@$attr), ' $value ') or substring(normalize-space(@$attr), 1, string-length('$value') + 1) = '$value ' or substring(normalize-space(@$attr), string-length(@$attr) - string-length('$value')) = ' $value' or @$attr = '$value'";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the inner XML string of a provided DOMNode
|
||||
*
|
||||
* @param DOMNode $node
|
||||
* @return string
|
||||
*/
|
||||
private static function getInnerHTML(DOMNode $node): string
|
||||
{
|
||||
$innerHTML = '';
|
||||
$children = $node->childNodes;
|
||||
foreach ($children as $child) {
|
||||
$innerHTML .= $child->ownerDocument->saveXML($child);
|
||||
}
|
||||
return $innerHTML;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -170,7 +170,7 @@ class PageInfo
|
|||
foreach ($data['keywords'] as $keyword) {
|
||||
/// @TODO make a positive list of allowed characters
|
||||
$hashtag = str_replace([' ', '+', '/', '.', '#', '@', "'", '"', '’', '`', '(', ')', '„', '“'], '', $keyword);
|
||||
$hashtags .= '#[url=' . DI::baseUrl() . '/search?tag=' . $hashtag . ']' . $hashtag . '[/url] ';
|
||||
$hashtags .= '#[url=' . DI::baseUrl() . '/search?tag=' . urlencode($hashtag) . ']' . $hashtag . '[/url] ';
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -177,15 +177,20 @@ class PostMedia extends BaseEntity
|
|||
/**
|
||||
* Get preview path for given media id relative to the base URL
|
||||
*
|
||||
* @param string $size One of the Proxy::SIZE_* constants
|
||||
* @param string $size One of the Proxy::SIZE_* constants
|
||||
* @param bool $vlurred If "true", the preview will be blurred
|
||||
* @return string preview link
|
||||
*/
|
||||
public function getPreviewPath(string $size = ''): string
|
||||
public function getPreviewPath(string $size = '', bool $blurred = false): string
|
||||
{
|
||||
return '/photo/preview/' .
|
||||
$path = '/photo/preview/' .
|
||||
(Proxy::getPixelsFromSize($size) ? Proxy::getPixelsFromSize($size) . '/' : '') .
|
||||
$this->id;
|
||||
|
||||
if ($blurred) {
|
||||
$path .= '?' . http_build_query(['blur' => true]);
|
||||
}
|
||||
return $path;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -40,7 +40,9 @@ use Friendica\Model\Post;
|
|||
use Friendica\Model\Tag;
|
||||
use Friendica\Network\HTTPClient\Client\HttpClientAccept;
|
||||
use Friendica\Network\HTTPClient\Client\HttpClientOptions;
|
||||
use Friendica\Util\Images;
|
||||
use Friendica\Util\Map;
|
||||
use Friendica\Util\Network;
|
||||
use Friendica\Util\ParseUrl;
|
||||
use Friendica\Util\Proxy;
|
||||
use Friendica\Util\Strings;
|
||||
|
@ -124,7 +126,7 @@ class BBCode
|
|||
break;
|
||||
|
||||
case 'publisher_url':
|
||||
$data['provider_url'] = html_entity_decode($value, ENT_QUOTES, 'UTF-8');
|
||||
$data['provider_url'] = Network::sanitizeUrl(html_entity_decode($value, ENT_QUOTES, 'UTF-8'));
|
||||
break;
|
||||
|
||||
case 'author_name':
|
||||
|
@ -135,7 +137,7 @@ class BBCode
|
|||
break;
|
||||
|
||||
case 'author_url':
|
||||
$data['author_url'] = html_entity_decode($value, ENT_QUOTES, 'UTF-8');
|
||||
$data['author_url'] = Network::sanitizeUrl(html_entity_decode($value, ENT_QUOTES, 'UTF-8'));
|
||||
if ($data['provider_url'] == $data['author_url']) {
|
||||
$data['author_url'] = '';
|
||||
}
|
||||
|
@ -308,7 +310,7 @@ class BBCode
|
|||
return trim($text);
|
||||
}
|
||||
|
||||
private static function proxyUrl(string $image, int $simplehtml = self::INTERNAL, int $uriid = 0, string $size = ''): string
|
||||
public static function proxyUrl(string $image, int $simplehtml = self::INTERNAL, int $uriid = 0, string $size = ''): string
|
||||
{
|
||||
// Only send proxied pictures to API and for internal display
|
||||
if (!in_array($simplehtml, [self::INTERNAL, self::MASTODON_API, self::TWITTER_API])) {
|
||||
|
@ -434,6 +436,8 @@ class BBCode
|
|||
return $text;
|
||||
}
|
||||
|
||||
$data['url'] = Network::sanitizeUrl($data['url']);
|
||||
|
||||
if (isset($data['title'])) {
|
||||
$data['title'] = strip_tags($data['title']);
|
||||
$data['title'] = str_replace(['http://', 'https://'], '', $data['title']);
|
||||
|
@ -449,7 +453,7 @@ class BBCode
|
|||
$return = '';
|
||||
try {
|
||||
if ($tryoembed && OEmbed::isAllowedURL($data['url'])) {
|
||||
$return = OEmbed::getHTML($data['url'], $data['title']);
|
||||
$return = OEmbed::getHTML($data['url'], $data['title'], $uriid);
|
||||
} else {
|
||||
throw new Exception('OEmbed is disabled for this attachment.');
|
||||
}
|
||||
|
@ -485,6 +489,7 @@ class BBCode
|
|||
}
|
||||
|
||||
if (!empty($data['provider_url']) && !empty($data['provider_name'])) {
|
||||
$data['provider_url'] = Network::sanitizeUrl($data['provider_url']);
|
||||
if (!empty($data['author_name'])) {
|
||||
$return .= sprintf('<sup><a href="%s" target="_blank" rel="noopener noreferrer">%s (%s)</a></sup>', $data['provider_url'], $data['author_name'], $data['provider_name']);
|
||||
} else {
|
||||
|
@ -1023,12 +1028,12 @@ class BBCode
|
|||
if (is_null($text)) {
|
||||
$curlResult = DI::httpClient()->head($match[1], [HttpClientOptions::TIMEOUT => DI::config()->get('system', 'xrd_timeout')]);
|
||||
if ($curlResult->isSuccess()) {
|
||||
$mimetype = $curlResult->getHeader('Content-Type')[0] ?? '';
|
||||
$mimetype = $curlResult->getContentType() ?? '';
|
||||
} else {
|
||||
$mimetype = '';
|
||||
}
|
||||
|
||||
if (substr($mimetype, 0, 6) == 'image/') {
|
||||
if (Images::isSupportedMimeType($mimetype)) {
|
||||
$text = '[url=' . $match[1] . ']' . $match[1] . '[/url]';
|
||||
} else {
|
||||
$text = '[url=' . $match[2] . ']' . $match[2] . '[/url]';
|
||||
|
@ -1064,6 +1069,21 @@ class BBCode
|
|||
return $text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback: Sanitize links from given $match array
|
||||
*
|
||||
* @param array $match Array with link match
|
||||
* @return string BBCode
|
||||
*/
|
||||
private static function sanitizeLinksCallback(array $match): string
|
||||
{
|
||||
if (count($match) == 3) {
|
||||
return '[' . $match[1] . ']' . Network::sanitizeUrl($match[2]) . '[/' . $match[1] . ']';
|
||||
} else {
|
||||
return '[' . $match[1] . '=' . Network::sanitizeUrl($match[2]) . ']' . $match[3] . '[/' . $match[1] . ']';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback: Expands links from given $match array
|
||||
*
|
||||
|
@ -1106,13 +1126,13 @@ class BBCode
|
|||
|
||||
$curlResult = DI::httpClient()->head($match[1], [HttpClientOptions::TIMEOUT => DI::config()->get('system', 'xrd_timeout')]);
|
||||
if ($curlResult->isSuccess()) {
|
||||
$mimetype = $curlResult->getHeader('Content-Type')[0] ?? '';
|
||||
$mimetype = $curlResult->getContentType() ?? '';
|
||||
} else {
|
||||
$mimetype = '';
|
||||
}
|
||||
|
||||
// if its a link to a picture then embed this picture
|
||||
if (substr($mimetype, 0, 6) == 'image/') {
|
||||
if (Images::isSupportedMimeType($mimetype)) {
|
||||
$text = '[img]' . $match[1] . '[/img]';
|
||||
} else {
|
||||
if (!empty($match[3])) {
|
||||
|
@ -1338,12 +1358,12 @@ class BBCode
|
|||
* $match[1] = $url
|
||||
* $match[2] = $title or absent
|
||||
*/
|
||||
$try_oembed_callback = function (array $match) {
|
||||
$try_oembed_callback = function (array $match) use ($uriid) {
|
||||
$url = $match[1];
|
||||
$title = $match[2] ?? '';
|
||||
|
||||
try {
|
||||
$return = OEmbed::getHTML($url, $title);
|
||||
$return = OEmbed::getHTML($url, $title, $uriid);
|
||||
} catch (Exception $ex) {
|
||||
$return = $match[0];
|
||||
}
|
||||
|
@ -1455,7 +1475,7 @@ class BBCode
|
|||
|
||||
// Replace non graphical smilies for external posts
|
||||
if (!$nosmile) {
|
||||
$text = self::performWithEscapedTags($text, ['img'], function ($text) use ($simple_html, $for_plaintext) {
|
||||
$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);
|
||||
});
|
||||
}
|
||||
|
@ -1549,9 +1569,6 @@ class BBCode
|
|||
// Check for centered text
|
||||
$text = preg_replace("(\[center\](.*?)\[\/center\])ism", '<div style="text-align:center;">$1</div>', $text);
|
||||
|
||||
// Check for list text
|
||||
$text = str_replace("[*]", "<li>", $text);
|
||||
|
||||
// Check for block-level custom CSS
|
||||
$text = preg_replace('#(?<=^|\n)\[style=(.*?)](.*?)\[/style](?:\n|$)#ism', '<div style="$1">$2</div>', $text);
|
||||
|
||||
|
@ -1591,6 +1608,10 @@ class BBCode
|
|||
$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);
|
||||
|
@ -1716,6 +1737,9 @@ class BBCode
|
|||
// 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",
|
||||
|
@ -1758,12 +1782,13 @@ class BBCode
|
|||
}
|
||||
|
||||
// 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) {
|
||||
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(
|
||||
|
@ -1774,7 +1799,7 @@ class BBCode
|
|||
}
|
||||
|
||||
// Vimeo extensions
|
||||
if ($try_oembed) {
|
||||
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(
|
||||
|
@ -1785,7 +1810,7 @@ class BBCode
|
|||
}
|
||||
|
||||
// oembed tag
|
||||
$text = OEmbed::BBCode2HTML($text);
|
||||
$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);
|
||||
|
@ -1810,6 +1835,9 @@ class BBCode
|
|||
$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);
|
||||
|
||||
// Handle mentions and hashtag links
|
||||
if ($simple_html == self::DIASPORA) {
|
||||
// The ! is converted to @ since Diaspora only understands the @
|
||||
|
@ -1912,11 +1940,11 @@ class BBCode
|
|||
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=' . rawurlencode($matches[1])
|
||||
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=' . rawurlencode($matches[1])
|
||||
return '#<a href="' . DI::baseUrl() . '/search?tag=' . urlencode($matches[1])
|
||||
. '" class="tag" rel="tag" title="' . XML::escape($matches[1]) . '">'
|
||||
. XML::escape($matches[1]) . '</a>';
|
||||
}
|
||||
|
@ -1943,6 +1971,7 @@ class BBCode
|
|||
$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);
|
||||
|
||||
|
@ -2028,13 +2057,7 @@ class BBCode
|
|||
);
|
||||
|
||||
// Default iframe allowed domains/path
|
||||
$allowedIframeDomains = [
|
||||
DI::baseUrl()->getHost()
|
||||
. (DI::baseUrl()->getPath() ? '/' . DI::baseUrl()->getPath() : '')
|
||||
. '/oembed/', # The path part has to change with the source in Content\Oembed::iframe
|
||||
'www.youtube.com/embed/',
|
||||
'player.vimeo.com/video/',
|
||||
];
|
||||
$allowedIframeDomains = DI::config()->get('system', 'no_oembed_rich_content') ? [] : ['www.youtube.com/embed/', 'player.vimeo.com/video/'];
|
||||
|
||||
$allowedIframeDomains = array_merge(
|
||||
$allowedIframeDomains,
|
||||
|
@ -2303,7 +2326,7 @@ class BBCode
|
|||
|
||||
case '#':
|
||||
default:
|
||||
return $match[1] . '[url=' . DI::baseUrl() . '/search?tag=' . $match[2] . ']' . $match[2] . '[/url]';
|
||||
return $match[1] . '[url=' . DI::baseUrl() . '/search?tag=' . urlencode($match[2]) . ']' . $match[2] . '[/url]';
|
||||
}
|
||||
},
|
||||
$body
|
||||
|
|
|
@ -103,6 +103,7 @@ class Widget
|
|||
{
|
||||
// Always hide content from these networks
|
||||
$networks = [Protocol::PHANTOM, Protocol::FACEBOOK, Protocol::APPNET, Protocol::TWITTER, Protocol::ZOT];
|
||||
Addon::loadAddons();
|
||||
|
||||
if (!Addon::isEnabled("discourse")) {
|
||||
$networks[] = Protocol::DISCOURSE;
|
||||
|
@ -535,6 +536,7 @@ class Widget
|
|||
['ref' => 'organisation', 'name' => DI::l10n()->t('Organisations')],
|
||||
['ref' => 'news', 'name' => DI::l10n()->t('News')],
|
||||
['ref' => 'community', 'name' => DI::l10n()->t('Groups')],
|
||||
['ref' => 'relay', 'name' => DI::l10n()->t('Relays')],
|
||||
];
|
||||
|
||||
return self::filter(
|
||||
|
|
|
@ -28,7 +28,6 @@ use Friendica\Core\Protocol;
|
|||
use Friendica\Core\Renderer;
|
||||
use Friendica\DI;
|
||||
use Friendica\Model\Contact;
|
||||
use Friendica\Util\Network;
|
||||
use Friendica\Util\Strings;
|
||||
|
||||
/**
|
||||
|
@ -42,9 +41,11 @@ class VCard
|
|||
* Get HTML for vcard block
|
||||
*
|
||||
* @template widget/vcard.tpl
|
||||
* @param array $contact
|
||||
* @param bool $hide_mention
|
||||
* @return string
|
||||
*/
|
||||
public static function getHTML(array $contact): string
|
||||
public static function getHTML(array $contact, bool $hide_mention = false): string
|
||||
{
|
||||
if (!isset($contact['network']) || !isset($contact['id'])) {
|
||||
Logger::warning('Incomplete contact', ['contact' => $contact ?? []]);
|
||||
|
@ -99,10 +100,12 @@ class VCard
|
|||
}
|
||||
|
||||
if ($contact['contact-type'] == Contact::TYPE_COMMUNITY) {
|
||||
$mention_label = DI::l10n()->t('Post to group');
|
||||
$mention_link = 'compose/0?body=!' . $contact['addr'];
|
||||
if (!$hide_mention) {
|
||||
$mention_label = DI::l10n()->t('Post to group');
|
||||
$mention_link = 'compose/0?body=!' . $contact['addr'];
|
||||
}
|
||||
$showgroup_link = 'network/group/' . $id;
|
||||
} else {
|
||||
} elseif (!$hide_mention) {
|
||||
$mention_label = DI::l10n()->t('Mention');
|
||||
$mention_link = 'compose/0?body=@' . $contact['addr'];
|
||||
}
|
||||
|
|
|
@ -632,23 +632,10 @@ class Installer
|
|||
*/
|
||||
public function checkImagick()
|
||||
{
|
||||
$imagick = false;
|
||||
$gif = false;
|
||||
|
||||
if (class_exists('Imagick')) {
|
||||
$imagick = true;
|
||||
$supported = Images::supportedTypes();
|
||||
if (array_key_exists('image/gif', $supported)) {
|
||||
$gif = true;
|
||||
}
|
||||
}
|
||||
if (!$imagick) {
|
||||
$this->addCheck(DI::l10n()->t('ImageMagick PHP extension is not installed'), $imagick, false, "");
|
||||
if (!class_exists('Imagick')) {
|
||||
$this->addCheck(DI::l10n()->t('ImageMagick PHP extension is not installed'), false, false, "");
|
||||
} else {
|
||||
$this->addCheck(DI::l10n()->t('ImageMagick PHP extension is installed'), $imagick, false, "");
|
||||
if ($imagick) {
|
||||
$this->addCheck(DI::l10n()->t('ImageMagick supports GIF'), $gif, false, "");
|
||||
}
|
||||
$this->addCheck(DI::l10n()->t('ImageMagick PHP extension is installed'), true, false, "");
|
||||
}
|
||||
|
||||
// Imagick is not required
|
||||
|
|
|
@ -245,6 +245,7 @@ class Search
|
|||
'Group' => Contact::TYPE_COMMUNITY,
|
||||
'Organization' => Contact::TYPE_ORGANISATION,
|
||||
'News' => Contact::TYPE_NEWS,
|
||||
'Relay' => Contact::TYPE_RELAY,
|
||||
];
|
||||
|
||||
return [
|
||||
|
|
|
@ -473,7 +473,7 @@ class System
|
|||
return false;
|
||||
}
|
||||
|
||||
return max($load_arr[0], $load_arr[1]);
|
||||
return round(max($load_arr[0], $load_arr[1]), 2);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -52,7 +52,7 @@ class PostUpdate
|
|||
// Needed for the helper function to read from the legacy term table
|
||||
const OBJECT_TYPE_POST = 1;
|
||||
|
||||
const VERSION = 1547;
|
||||
const VERSION = 1550;
|
||||
|
||||
/**
|
||||
* Calls the post update functions
|
||||
|
@ -128,7 +128,7 @@ class PostUpdate
|
|||
if (!self::update1544()) {
|
||||
return false;
|
||||
}
|
||||
if (!self::update1547()) {
|
||||
if (!self::update1550()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
@ -1369,14 +1369,24 @@ class PostUpdate
|
|||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
* @throws \ImagickException
|
||||
*/
|
||||
private static function update1547()
|
||||
private static function update1550()
|
||||
{
|
||||
// Was the script completed?
|
||||
if (DI::keyValue()->get('post_update_version') >= 1547) {
|
||||
if (DI::keyValue()->get('post_update_version') >= 1550) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$id = (int)(DI::keyValue()->get('post_update_version_1547_id') ?? 0);
|
||||
$engagements = DBA::select('post-engagement', ['uri-id'], ["`language` IS NULL"], ['order' => ['uri-id' => true], 'limit' => 1000]);
|
||||
while ($engagement = DBA::fetch($engagements)) {
|
||||
$item = Post::selectFirst([], ['uri-id' => $engagement['uri-id']]);
|
||||
if (empty($item)) {
|
||||
continue;
|
||||
}
|
||||
Post\Engagement::storeFromItem($item);
|
||||
}
|
||||
DBA::close($engagements);
|
||||
|
||||
$id = (int)(DI::keyValue()->get('post_update_version_1550_id') ?? 0);
|
||||
if ($id == 0) {
|
||||
$post = Post::selectFirstPost(['uri-id'], [], ['order' => ['uri-id' => true]]);
|
||||
$id = (int)($post['uri-id'] ?? 0);
|
||||
|
@ -1393,7 +1403,7 @@ class PostUpdate
|
|||
DBA::mergeConditions($condition, ["`created` > ?", $limit]);
|
||||
}
|
||||
|
||||
$posts = Post::selectPosts(['uri-id', 'network', 'private', 'created'], $condition, ['order' => ['uri-id' => true], 'limit' => 1000]);
|
||||
$posts = Post::selectPosts(['uri-id', 'created'], $condition, ['order' => ['uri-id' => true], 'limit' => 1000]);
|
||||
|
||||
if (DBA::errorNo() != 0) {
|
||||
Logger::error('Database error', ['no' => DBA::errorNo(), 'message' => DBA::errorMessage()]);
|
||||
|
@ -1402,17 +1412,17 @@ class PostUpdate
|
|||
|
||||
while ($post = Post::fetch($posts)) {
|
||||
$id = $post['uri-id'];
|
||||
Post\SearchIndex::insert($post['uri-id'], $post['network'], $post['private'], $post['created'], true);
|
||||
Post\SearchIndex::insert($post['uri-id'], $post['created'], true);
|
||||
++$rows;
|
||||
}
|
||||
DBA::close($posts);
|
||||
|
||||
DI::keyValue()->set('post_update_version_1547_id', $id);
|
||||
DI::keyValue()->set('post_update_version_1550_id', $id);
|
||||
|
||||
Logger::info('Processed', ['rows' => $rows, 'last' => $id]);
|
||||
|
||||
if ($rows <= 100) {
|
||||
DI::keyValue()->set('post_update_version', 1547);
|
||||
DI::keyValue()->set('post_update_version', 1550);
|
||||
Logger::info('Done');
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ namespace Friendica\Factory\Api\Mastodon;
|
|||
|
||||
use Friendica\BaseFactory;
|
||||
use Friendica\Database\Database;
|
||||
use Friendica\Model\Subscription;
|
||||
use Friendica\Network\HTTPException\UnprocessableEntityException;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
|
@ -56,6 +57,8 @@ class Application extends BaseFactory
|
|||
$application['client_secret'],
|
||||
$application['id'],
|
||||
$application['redirect_uri'],
|
||||
$application['website']);
|
||||
$application['website'],
|
||||
Subscription::getPublicVapidKey(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -84,7 +84,7 @@ class Attachment extends BaseFactory
|
|||
$type = 'audio';
|
||||
} elseif (($filetype == 'video') || ($attachment['type'] == Post\Media::VIDEO)) {
|
||||
$type = 'video';
|
||||
} elseif ($attachment['mimetype'] == 'image/gif') {
|
||||
} elseif ($attachment['mimetype'] == image_type_to_mime_type(IMAGETYPE_GIF)) {
|
||||
$type = 'gifv';
|
||||
} elseif (($filetype == 'image') || ($attachment['type'] == Post\Media::IMAGE)) {
|
||||
$type = 'image';
|
||||
|
@ -95,12 +95,12 @@ class Attachment extends BaseFactory
|
|||
$remote = $attachment['url'];
|
||||
if ($type == 'image') {
|
||||
$url = Post\Media::getPreviewUrlForId($attachment['id']);
|
||||
$preview = Post\Media::getPreviewUrlForId($attachment['id'], Proxy::SIZE_SMALL);
|
||||
$preview = Post\Media::getPreviewUrlForId($attachment['id'], Proxy::SIZE_MEDIUM);
|
||||
} else {
|
||||
$url = $attachment['url'];
|
||||
|
||||
if (!empty($attachment['preview'])) {
|
||||
$preview = Post\Media::getPreviewUrlForId($attachment['id'], Proxy::SIZE_SMALL);
|
||||
$preview = Post\Media::getPreviewUrlForId($attachment['id'], Proxy::SIZE_MEDIUM);
|
||||
} else {
|
||||
$preview = '';
|
||||
}
|
||||
|
@ -130,14 +130,13 @@ class Attachment extends BaseFactory
|
|||
'blurhash' => $photo['blurhash'],
|
||||
];
|
||||
|
||||
$photoTypes = Images::supportedTypes();
|
||||
$ext = $photoTypes[$photo['type']];
|
||||
$ext = Images::getExtensionByMimeType($photo['type']);
|
||||
|
||||
$url = $this->baseUrl . '/photo/' . $photo['resource-id'] . '-0.' . $ext;
|
||||
$url = $this->baseUrl . '/photo/' . $photo['resource-id'] . '-0' . $ext;
|
||||
|
||||
$preview = Photo::selectFirst(['scale'], ["`resource-id` = ? AND `uid` = ? AND `scale` > ?", $photo['resource-id'], $photo['uid'], 0], ['order' => ['scale']]);
|
||||
if (!empty($preview)) {
|
||||
$preview_url = $this->baseUrl . '/photo/' . $photo['resource-id'] . '-' . $preview['scale'] . '.' . $ext;
|
||||
$preview_url = $this->baseUrl . '/photo/' . $photo['resource-id'] . '-' . $preview['scale'] . $ext;
|
||||
} else {
|
||||
$preview_url = '';
|
||||
}
|
||||
|
|
|
@ -67,10 +67,12 @@ class Poll extends BaseFactory
|
|||
|
||||
if (empty($uid)) {
|
||||
$ownvotes = null;
|
||||
$voted = null;
|
||||
} else {
|
||||
$ownvotes = [];
|
||||
$voted = false;
|
||||
}
|
||||
|
||||
return new \Friendica\Object\Api\Mastodon\Poll($question, $options, $expired, $votes, $ownvotes);
|
||||
return new \Friendica\Object\Api\Mastodon\Poll($question, $options, $expired, $votes, $ownvotes, $voted);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,7 +32,6 @@ use Friendica\Database\DBA;
|
|||
use Friendica\DI;
|
||||
use Friendica\Model\Item;
|
||||
use Friendica\Model\Post;
|
||||
use Friendica\Model\Tag as TagModel;
|
||||
use Friendica\Model\Verb;
|
||||
use Friendica\Network\HTTPException;
|
||||
use Friendica\Object\Api\Mastodon\Status\FriendicaDeliveryData;
|
||||
|
@ -60,8 +59,6 @@ class Status extends BaseFactory
|
|||
private $mstdnAttachmentFactory;
|
||||
/** @var Emoji */
|
||||
private $mstdnEmojiFactory;
|
||||
/** @var Error */
|
||||
private $mstdnErrorFactory;
|
||||
/** @var Poll */
|
||||
private $mstdnPollFactory;
|
||||
/** @var ContentItem */
|
||||
|
@ -78,7 +75,6 @@ class Status extends BaseFactory
|
|||
Card $mstdnCardFactory,
|
||||
Attachment $mstdnAttachmentFactory,
|
||||
Emoji $mstdnEmojiFactory,
|
||||
Error $mstdnErrorFactory,
|
||||
Poll $mstdnPollFactory,
|
||||
ContentItem $contentItem,
|
||||
ACLFormatter $aclFormatter
|
||||
|
@ -91,7 +87,6 @@ class Status extends BaseFactory
|
|||
$this->mstdnCardFactory = $mstdnCardFactory;
|
||||
$this->mstdnAttachmentFactory = $mstdnAttachmentFactory;
|
||||
$this->mstdnEmojiFactory = $mstdnEmojiFactory;
|
||||
$this->mstdnErrorFactory = $mstdnErrorFactory;
|
||||
$this->mstdnPollFactory = $mstdnPollFactory;
|
||||
$this->contentItem = $contentItem;
|
||||
$this->aclFormatter = $aclFormatter;
|
||||
|
@ -112,7 +107,7 @@ class Status extends BaseFactory
|
|||
{
|
||||
$fields = ['uri-id', 'uid', 'author-id', 'causer-id', 'author-uri-id', 'author-link', 'causer-uri-id', 'post-reason', 'starred', 'app', 'title', 'body', 'raw-body', 'content-warning', 'question-id',
|
||||
'created', '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'];
|
||||
'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]]);
|
||||
if (!$item) {
|
||||
$mail = DBA::selectFirst('mail', ['id'], ['uri-id' => $uriId, 'uid' => $uid]);
|
||||
|
@ -217,7 +212,7 @@ class Status extends BaseFactory
|
|||
$item['featured']
|
||||
);
|
||||
|
||||
$sensitive = $this->dba->exists('tag-view', ['uri-id' => $uriId, 'name' => 'nsfw', 'type' => TagModel::HASHTAG]);
|
||||
$sensitive = (bool)$item['sensitive'];
|
||||
$application = new \Friendica\Object\Api\Mastodon\Application($item['app'] ?: ContactSelector::networkToName($item['network'], $item['author-link']));
|
||||
|
||||
$mentions = $this->mstdnMentionFactory->createFromUriId($uriId)->getArrayCopy();
|
||||
|
|
|
@ -24,10 +24,9 @@ namespace Friendica\Factory\Api\Twitter;
|
|||
use Friendica\BaseFactory;
|
||||
use Friendica\Model\APContact;
|
||||
use Friendica\Model\Contact;
|
||||
use Friendica\Network\HTTPException;
|
||||
use Friendica\Factory\Api\Twitter\Status;
|
||||
use Friendica\Model\Item;
|
||||
use Friendica\Model\Post;
|
||||
use Friendica\Network\HTTPException;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
class User extends BaseFactory
|
||||
|
@ -85,9 +84,17 @@ class User extends BaseFactory
|
|||
* @param bool $include_user_entities
|
||||
*
|
||||
* @return \Friendica\Object\Api\Twitter\User
|
||||
* @throws HTTPException\InternalServerErrorException
|
||||
* @throws HTTPException\NotFoundException If the $uid doesn't exist
|
||||
* @throws \ImagickException
|
||||
*/
|
||||
public function createFromUserId(int $uid, bool $skip_status = true, bool $include_user_entities = true): \Friendica\Object\Api\Twitter\User
|
||||
{
|
||||
return $this->createFromContactId(Contact::getPublicIdByUserId($uid), $uid, $skip_status, $include_user_entities);
|
||||
$cid = Contact::getPublicIdByUserId($uid);
|
||||
if (!$cid) {
|
||||
throw new HTTPException\NotFoundException();
|
||||
}
|
||||
|
||||
return $this->createFromContactId($cid, $uid, $skip_status, $include_user_entities);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -208,6 +208,9 @@ class APContact
|
|||
|
||||
if (!$failed && ($curlResult->getReturnCode() == 410)) {
|
||||
$data = ['@context' => ActivityPub::CONTEXT, 'id' => $url, 'type' => 'Tombstone'];
|
||||
} elseif (!$failed && !HTTPSignature::isValidContentType($curlResult->getContentType())) {
|
||||
Logger::debug('Unexpected content type', ['content-type' => $curlResult->getContentType(), 'url' => $url]);
|
||||
$failed = true;
|
||||
}
|
||||
} catch (\Exception $exception) {
|
||||
Logger::notice('Error fetching url', ['url' => $url, 'exception' => $exception]);
|
||||
|
|
|
@ -755,7 +755,7 @@ class Contact
|
|||
$user = DBA::selectFirst(
|
||||
'user',
|
||||
['uid', 'username', 'nickname', 'pubkey', 'prvkey'],
|
||||
['uid' => $uid, 'verified' => true, 'blocked' => false, 'account_removed' => false, 'account_expired' => false]
|
||||
['uid' => $uid, 'account_removed' => false, 'account_expired' => false]
|
||||
);
|
||||
if (!DBA::isResult($user)) {
|
||||
return false;
|
||||
|
@ -784,6 +784,7 @@ class Contact
|
|||
'name-date' => DateTimeFormat::utcNow(),
|
||||
'uri-date' => DateTimeFormat::utcNow(),
|
||||
'avatar-date' => DateTimeFormat::utcNow(),
|
||||
'baseurl' => DI::baseUrl(),
|
||||
'closeness' => 0
|
||||
];
|
||||
|
||||
|
@ -819,7 +820,7 @@ class Contact
|
|||
$fields = [
|
||||
'id', 'uri-id', 'name', 'nick', 'location', 'about', 'keywords', 'avatar', 'prvkey', 'pubkey', 'manually-approve',
|
||||
'xmpp', 'matrix', 'contact-type', 'forum', 'prv', 'avatar-date', 'url', 'nurl', 'unsearchable',
|
||||
'photo', 'thumb', 'micro', 'header', 'addr', 'request', 'notify', 'poll', 'confirm', 'poco', 'network'
|
||||
'photo', 'thumb', 'micro', 'header', 'addr', 'request', 'notify', 'poll', 'confirm', 'poco', 'network', 'baseurl', 'gsid'
|
||||
];
|
||||
$self = DBA::selectFirst('contact', $fields, ['uid' => $uid, 'self' => true]);
|
||||
if (!DBA::isResult($self)) {
|
||||
|
@ -841,7 +842,6 @@ class Contact
|
|||
return false;
|
||||
}
|
||||
|
||||
$file_suffix = 'jpg';
|
||||
$url = DI::baseUrl() . '/profile/' . $user['nickname'];
|
||||
|
||||
$fields = [
|
||||
|
@ -874,17 +874,11 @@ class Contact
|
|||
$fields['avatar-date'] = DateTimeFormat::utcNow();
|
||||
}
|
||||
|
||||
// Creating the path to the avatar, beginning with the file suffix
|
||||
$types = Images::supportedTypes();
|
||||
if (isset($types[$avatar['type']])) {
|
||||
$file_suffix = $types[$avatar['type']];
|
||||
}
|
||||
|
||||
// We are adding a timestamp value so that other systems won't use cached content
|
||||
$timestamp = strtotime($fields['avatar-date']);
|
||||
|
||||
$prefix = DI::baseUrl() . '/photo/' . $avatar['resource-id'] . '-';
|
||||
$suffix = '.' . $file_suffix . '?ts=' . $timestamp;
|
||||
$suffix = Images::getExtensionByMimeType($avatar['type']) . '?ts=' . $timestamp;
|
||||
|
||||
$fields['photo'] = $prefix . '4' . $suffix;
|
||||
$fields['thumb'] = $prefix . '5' . $suffix;
|
||||
|
@ -902,6 +896,8 @@ class Contact
|
|||
$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['baseurl'] = DI::baseUrl();
|
||||
$fields['gsid'] = GServer::getID($fields['baseurl'], true);
|
||||
|
||||
$update = false;
|
||||
|
||||
|
@ -1747,6 +1743,10 @@ class Contact
|
|||
$account_type = DI::l10n()->t("Group");
|
||||
break;
|
||||
|
||||
case self::TYPE_RELAY:
|
||||
$account_type = DI::l10n()->t("Relay");
|
||||
break;
|
||||
|
||||
default:
|
||||
$account_type = "";
|
||||
break;
|
||||
|
@ -2306,8 +2306,8 @@ class Contact
|
|||
$fetchResult = HTTPSignature::fetchRaw($avatar, 0, [HttpClientOptions::ACCEPT_CONTENT => [HttpClientAccept::IMAGE]]);
|
||||
|
||||
$img_str = $fetchResult->getBodyString();
|
||||
if (!empty($img_str)) {
|
||||
$image = new Image($img_str, Images::getMimeTypeByData($img_str));
|
||||
if ($fetchResult->isSuccess() && !empty($img_str)) {
|
||||
$image = new Image($img_str, $fetchResult->getContentType(), $avatar);
|
||||
if ($image->isValid()) {
|
||||
$update_fields['blurhash'] = $image->getBlurHash();
|
||||
} else {
|
||||
|
@ -3099,6 +3099,13 @@ class Contact
|
|||
$ret['url'] = str_replace('?absolute=true', '', $ret['url']);
|
||||
}
|
||||
|
||||
if (($protocol == Protocol::ACTIVITYPUB) && ($uid != 0)) {
|
||||
if (APContact::isRelay(APContact::getByURL($ret['url']))) {
|
||||
$result['message'] = DI::l10n()->t('This seems to be a relay account. They can\'t be followed by users.');
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
// do we have enough information?
|
||||
if (empty($protocol) || ($protocol == Protocol::PHANTOM) || (empty($ret['url']) && empty($ret['addr']))) {
|
||||
$result['message'] .= DI::l10n()->t('The profile address specified does not provide adequate information.') . '<br />';
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
namespace Friendica\Model\Contact;
|
||||
|
||||
use Exception;
|
||||
use Friendica\Content\Widget;
|
||||
use Friendica\Core\Logger;
|
||||
use Friendica\Core\Protocol;
|
||||
use Friendica\Database\Database;
|
||||
|
@ -78,14 +79,14 @@ class Relation
|
|||
*/
|
||||
public static function discoverByUser(int $uid)
|
||||
{
|
||||
$contact = Contact::selectFirst(['id', 'url', 'network'], ['uid' => $uid, 'self' => true]);
|
||||
$contact = Contact::selectFirst(['id', 'url', 'network'], ['id' => Contact::getPublicIdByUserId($uid)]);
|
||||
if (empty($contact)) {
|
||||
Logger::warning('Self contact for user not found', ['uid' => $uid]);
|
||||
return;
|
||||
}
|
||||
|
||||
$followers = self::getContacts($uid, [Contact::FOLLOWER, Contact::FRIEND]);
|
||||
$followings = self::getContacts($uid, [Contact::SHARING, Contact::FRIEND]);
|
||||
$followers = self::getContacts($uid, [Contact::FOLLOWER, Contact::FRIEND], false);
|
||||
$followings = self::getContacts($uid, [Contact::SHARING, Contact::FRIEND], false);
|
||||
|
||||
self::updateFollowersFollowings($contact, $followers, $followings);
|
||||
}
|
||||
|
@ -207,10 +208,11 @@ class Relation
|
|||
* Fetch contact url list from the given local user
|
||||
*
|
||||
* @param integer $uid
|
||||
* @param array $rel
|
||||
* @param array $rel
|
||||
* @param bool $only_ap
|
||||
* @return array contact list
|
||||
*/
|
||||
private static function getContacts(int $uid, array $rel): array
|
||||
private static function getContacts(int $uid, array $rel, bool $only_ap = true): array
|
||||
{
|
||||
$list = [];
|
||||
$profile = Profile::getByUID($uid);
|
||||
|
@ -219,15 +221,22 @@ class Relation
|
|||
}
|
||||
|
||||
$condition = [
|
||||
'rel' => $rel,
|
||||
'uid' => $uid,
|
||||
'self' => false,
|
||||
'rel' => $rel,
|
||||
'uid' => $uid,
|
||||
'self' => false,
|
||||
'deleted' => false,
|
||||
'hidden' => false,
|
||||
'hidden' => false,
|
||||
'archive' => false,
|
||||
'pending' => false,
|
||||
'blocked' => false,
|
||||
'failed' => false,
|
||||
];
|
||||
$condition = DBA::mergeConditions($condition, ["`url` IN (SELECT `url` FROM `apcontact`)"]);
|
||||
if ($only_ap) {
|
||||
$condition = DBA::mergeConditions($condition, ["`url` IN (SELECT `url` FROM `apcontact`)"]);
|
||||
} else {
|
||||
$networks = Widget::unavailableNetworks();
|
||||
$condition = DBA::mergeConditions($condition, array_merge(["NOT `network` IN (" . substr(str_repeat("?, ", count($networks)), 0, -2) . ")"], $networks));
|
||||
}
|
||||
$contacts = DBA::select('contact', ['url'], $condition);
|
||||
while ($contact = DBA::fetch($contacts)) {
|
||||
$list[] = $contact['url'];
|
||||
|
@ -870,6 +879,20 @@ class Relation
|
|||
DBA::update('contact-relation', ['thread-score' => $score], ['relation-cid' => $contact_id, 'cid' => $interaction['author-id']]);
|
||||
}
|
||||
DBA::close($interactions);
|
||||
|
||||
$total = DBA::fetchFirst("SELECT count(*) AS `posts` FROM `post-thread-user` WHERE EXISTS(SELECT `cid` FROM `contact-relation` WHERE `cid` = `post-thread-user`.`author-id` AND `relation-cid` = ? AND `follows`) AND `uid` = ? AND `created` > ?",
|
||||
$contact_id, $uid, DateTimeFormat::utc('now - ' . $days . ' day'));
|
||||
|
||||
Logger::debug('Calculate post-score', ['uid' => $uid, 'total' => $total['posts']]);
|
||||
|
||||
$posts = DBA::p("SELECT `author-id`, count(*) AS `posts` FROM `post-thread-user` WHERE EXISTS(SELECT `cid` FROM `contact-relation` WHERE `cid` = `post-thread-user`.`author-id` AND `relation-cid` = ? AND `follows`) AND `uid` = ? AND `created` > ? GROUP BY `author-id`",
|
||||
$contact_id, $uid, DateTimeFormat::utc('now - ' . $days . ' day'));
|
||||
while ($post = DBA::fetch($posts)) {
|
||||
$score = min((int)(($post['posts'] / $total['posts']) * 65535), 65535);
|
||||
DBA::update('contact-relation', ['post-score' => $score], ['relation-cid' => $contact_id, 'cid' => $post['author-id']]);
|
||||
}
|
||||
DBA::close($posts);
|
||||
|
||||
Logger::debug('Calculation - end', ['uid' => $uid]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -925,9 +925,6 @@ class Event
|
|||
$end_short = '';
|
||||
}
|
||||
|
||||
// Format the event location.
|
||||
$location = self::locationToArray($item['event-location']);
|
||||
|
||||
// Construct the profile link (magic-auth).
|
||||
$author = [
|
||||
'uid' => 0,
|
||||
|
@ -964,7 +961,7 @@ class Event
|
|||
'$show_map_label' => DI::l10n()->t('Show map'),
|
||||
'$hide_map_label' => DI::l10n()->t('Hide map'),
|
||||
'$map_btn_label' => DI::l10n()->t('Show map'),
|
||||
'$location' => $location
|
||||
'$location' => self::locationToTemplateVars($item['event-location']),
|
||||
]);
|
||||
|
||||
return $return;
|
||||
|
@ -984,7 +981,7 @@ class Event
|
|||
* 'coordinates' => Latitude and longitude (e.g. '48.864716,2.349014').<br>
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
*/
|
||||
private static function locationToArray(string $s = ''): array
|
||||
private static function locationToTemplateVars(string $s = ''): array
|
||||
{
|
||||
if ($s == '') {
|
||||
return [];
|
||||
|
|
|
@ -34,10 +34,11 @@ use Friendica\Core\Protocol;
|
|||
use Friendica\Core\Renderer;
|
||||
use Friendica\Core\System;
|
||||
use Friendica\Core\Worker;
|
||||
use Friendica\Database\Database;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\DI;
|
||||
use Friendica\Model\Post\Category;
|
||||
use Friendica\Network\HTTPClient\Client\HttpClientAccept;
|
||||
use Friendica\Network\HTTPClient\Client\HttpClientOptions;
|
||||
use Friendica\Network\HTTPException\InternalServerErrorException;
|
||||
use Friendica\Network\HTTPException\ServiceUnavailableException;
|
||||
use Friendica\Protocol\Activity;
|
||||
|
@ -45,6 +46,7 @@ use Friendica\Protocol\ActivityPub;
|
|||
use Friendica\Protocol\Delivery;
|
||||
use Friendica\Protocol\Diaspora;
|
||||
use Friendica\Util\DateTimeFormat;
|
||||
use Friendica\Util\HTTPSignature;
|
||||
use Friendica\Util\Map;
|
||||
use Friendica\Util\Network;
|
||||
use Friendica\Util\Proxy;
|
||||
|
@ -99,7 +101,7 @@ class Item
|
|||
'uid', 'id', 'parent', 'guid', 'network', 'gravity',
|
||||
'uri-id', 'uri', 'thr-parent-id', 'thr-parent', 'parent-uri-id', 'parent-uri', 'conversation',
|
||||
'commented', 'created', 'edited', 'received', 'verb', 'object-type', 'postopts', 'plink',
|
||||
'wall', 'private', 'starred', 'origin', 'parent-origin', 'title', 'body', 'language',
|
||||
'wall', 'private', 'starred', 'origin', 'parent-origin', 'title', 'body', 'language', 'sensitive',
|
||||
'content-warning', 'location', 'coord', 'app', 'rendered-hash', 'rendered-html', 'object',
|
||||
'quote-uri', 'quote-uri-id', 'allow_cid', 'allow_gid', 'deny_cid', 'deny_gid', 'mention', 'global',
|
||||
'author-id', 'author-link', 'author-alias', 'author-name', 'author-avatar', 'author-network', 'author-updated', 'author-gsid', 'author-baseurl', 'author-addr', 'author-uri-id',
|
||||
|
@ -119,7 +121,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',
|
||||
'private', 'title', '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',
|
||||
|
@ -689,38 +691,6 @@ class Item
|
|||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the item array is too old
|
||||
*
|
||||
* @param array $item Item record
|
||||
* @return boolean item is too old
|
||||
*/
|
||||
public static function isTooOld(array $item): bool
|
||||
{
|
||||
// check for create date and expire time
|
||||
$expire_interval = DI::config()->get('system', 'dbclean-expire-days', 0);
|
||||
|
||||
$user = DBA::selectFirst('user', ['expire'], ['uid' => $item['uid']]);
|
||||
if (DBA::isResult($user) && ($user['expire'] > 0) && (($user['expire'] < $expire_interval) || ($expire_interval == 0))) {
|
||||
$expire_interval = $user['expire'];
|
||||
}
|
||||
|
||||
if (($expire_interval > 0) && !empty($item['created'])) {
|
||||
$expire_date = time() - ($expire_interval * 86400);
|
||||
$created_date = strtotime($item['created']);
|
||||
if ($created_date < $expire_date) {
|
||||
Logger::notice('Item created before expiration interval.', [
|
||||
'created' => date('c', $created_date),
|
||||
'expired' => date('c', $expire_date),
|
||||
'$item' => $item
|
||||
]);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the id of the given item array if it has been stored before
|
||||
*
|
||||
|
@ -1032,7 +1002,7 @@ class Item
|
|||
|
||||
if (
|
||||
!empty($item['direction']) && in_array($item['direction'], [Conversation::PUSH, Conversation::RELAY]) &&
|
||||
empty($item['origin']) && self::isTooOld($item)
|
||||
empty($item['origin']) && DI::contentItem()->isTooOld($item['created'], $item['uid'])
|
||||
) {
|
||||
Logger::info('Item is too old', ['item' => $item]);
|
||||
return 0;
|
||||
|
@ -1280,6 +1250,15 @@ class Item
|
|||
}
|
||||
}
|
||||
|
||||
// Store tags from the body if this hadn't been handled previously in the protocol classes
|
||||
if (!Tag::existsForPost($item['uri-id'])) {
|
||||
Tag::storeFromBody($item['uri-id'], $item['body']);
|
||||
}
|
||||
|
||||
if (in_array($item['gravity'], [self::GRAVITY_PARENT, self::GRAVITY_COMMENT]) && (!isset($item['sensitive']) || is_null($item['sensitive']))) {
|
||||
$item['sensitive'] = Tag::existsTagForPost($item['uri-id'], 'nsfw');
|
||||
}
|
||||
|
||||
$item['language'] = self::getLanguage($item);
|
||||
|
||||
$inserted = Post::insert($item['uri-id'], $item);
|
||||
|
@ -1319,11 +1298,6 @@ class Item
|
|||
Post\DeliveryData::insert($item['uri-id'], $delivery_data);
|
||||
}
|
||||
|
||||
// Store tags from the body if this hadn't been handled previously in the protocol classes
|
||||
if (!Tag::existsForPost($item['uri-id'])) {
|
||||
Tag::storeFromBody($item['uri-id'], $item['body']);
|
||||
}
|
||||
|
||||
$condition = ['uri-id' => $item['uri-id'], 'uid' => $item['uid']];
|
||||
if (Post::exists($condition)) {
|
||||
Logger::notice('Item is already inserted - aborting', $condition);
|
||||
|
@ -1448,9 +1422,11 @@ class Item
|
|||
}
|
||||
|
||||
$engagement_uri_id = Post\Engagement::storeFromItem($posted_item);
|
||||
|
||||
|
||||
if (in_array($posted_item['gravity'], [self::GRAVITY_PARENT, self::GRAVITY_COMMENT])) {
|
||||
Post\SearchIndex::insert($posted_item['uri-id'], $posted_item['network'], $posted_item['private'], $posted_item['created']);
|
||||
Post\SearchIndex::insert($posted_item['uri-id'], $posted_item['created']);
|
||||
} elseif ($posted_item['verb'] == Activity::ANNOUNCE) {
|
||||
Post\SearchIndex::update($posted_item['thr-parent-id']);
|
||||
}
|
||||
|
||||
if (($posted_item['gravity'] == self::GRAVITY_ACTIVITY) && ($posted_item['verb'] == Activity::ANNOUNCE) && ($posted_item['parent-uri-id'] == $posted_item['thr-parent-id'])) {
|
||||
|
@ -1499,7 +1475,11 @@ class Item
|
|||
];
|
||||
if (!Post::exists($condition)) {
|
||||
Logger::debug('Reshare post', ['uid' => $uid, 'uri-id' => $uri_id]);
|
||||
self::performActivity($item['id'], 'announce', $uid);
|
||||
$allow_cid = '';
|
||||
$allow_gid = '<' . Circle::FOLLOWERS . '>';
|
||||
$deny_cid = '';
|
||||
$deny_gid = '';
|
||||
self::performActivity($item['id'], 'announce', $uid, $allow_cid, $allow_gid, $deny_cid, $deny_gid);
|
||||
} else {
|
||||
Logger::debug('Reshare already exists', ['uid' => $uid, 'uri-id' => $uri_id]);
|
||||
}
|
||||
|
@ -1616,7 +1596,7 @@ class Item
|
|||
}
|
||||
|
||||
$languages = $item['language'] ? array_keys(json_decode($item['language'], true)) : [];
|
||||
|
||||
|
||||
foreach (Tag::getUIDListByURIId($item['uri-id']) as $uid => $tags) {
|
||||
if (!empty($languages)) {
|
||||
$keep = false;
|
||||
|
@ -2481,7 +2461,7 @@ class Item
|
|||
}
|
||||
|
||||
$basetag = str_replace('_', ' ', substr($tag, 1));
|
||||
$newtag = '#[url=' . DI::baseUrl() . '/search?tag=' . $basetag . ']' . $basetag . '[/url]';
|
||||
$newtag = '#[url=' . DI::baseUrl() . '/search?tag=' . urlencode($basetag) . ']' . $basetag . '[/url]';
|
||||
|
||||
$body = str_replace($tag, $newtag, $body);
|
||||
}
|
||||
|
@ -3320,9 +3300,9 @@ class Item
|
|||
public static function prepareBody(array &$item, bool $attach = false, bool $is_preview = false, bool $only_cache = false): string
|
||||
{
|
||||
$a = DI::app();
|
||||
$uid = DI::userSession()->getLocalUserId();
|
||||
Hook::callAll('prepare_body_init', $item);
|
||||
|
||||
|
||||
// In order to provide theme developers more possibilities, event items
|
||||
// are treated differently.
|
||||
if ($item['object-type'] === Activity\ObjectType::EVENT && isset($item['event-id'])) {
|
||||
|
@ -3335,6 +3315,7 @@ 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']);
|
||||
|
@ -3402,11 +3383,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']);
|
||||
$item['body'] = self::replaceVisualAttachments($sharedSplitAttachments['visual'], $item['body'], $sensitive);
|
||||
}
|
||||
|
||||
$itemSplitAttachments = DI::postMediaRepository()->splitAttachments($item['uri-id'], $shared_links, $item['has-media'] ?? false);
|
||||
$item['body'] = self::replaceVisualAttachments($itemSplitAttachments['visual'], $item['body'] ?? '');
|
||||
$item['body'] = self::replaceVisualAttachments($itemSplitAttachments['visual'], $item['body'] ?? '', $sensitive);
|
||||
|
||||
self::putInCache($item);
|
||||
$item['body'] = $body;
|
||||
|
@ -3427,7 +3408,7 @@ class Item
|
|||
$filter_reasons[] = DI::l10n()->t('Content from %s is collapsed', $item['author-name']);
|
||||
}
|
||||
|
||||
if (!empty($item['content-warning']) && (!DI::userSession()->getLocalUserId() || !DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'system', 'disable_cw', false))) {
|
||||
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']);
|
||||
}
|
||||
|
||||
|
@ -3461,9 +3442,9 @@ class Item
|
|||
}
|
||||
|
||||
if (!empty($sharedSplitAttachments)) {
|
||||
$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::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::addNonVisualAttachments($sharedSplitAttachments['additional'], $item, $s, true);
|
||||
$body = BBCode::removeSharedData($body);
|
||||
}
|
||||
|
@ -3474,9 +3455,9 @@ class Item
|
|||
$s = substr($s, 0, $pos);
|
||||
}
|
||||
|
||||
$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::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::addNonVisualAttachments($itemSplitAttachments['additional'], $item, $s, false);
|
||||
$s = self::addQuestions($item, $s);
|
||||
|
||||
|
@ -3510,9 +3491,10 @@ class Item
|
|||
*
|
||||
* @param string $s
|
||||
* @param PostMedias $PostMedias
|
||||
* @param bool $sensitive
|
||||
* @return string
|
||||
*/
|
||||
private static function addGallery(string $s, PostMedias $PostMedias): string
|
||||
private static function addGallery(string $s, PostMedias $PostMedias, bool $sensitive): string
|
||||
{
|
||||
foreach ($PostMedias as $PostMedia) {
|
||||
if (!$PostMedia->preview || ($PostMedia->type !== Post\Media::IMAGE)) {
|
||||
|
@ -3522,9 +3504,10 @@ class Item
|
|||
if ($PostMedia->hasDimensions()) {
|
||||
$pattern = '#<a href="' . preg_quote($PostMedia->url) . '">(.*?)"></a>#';
|
||||
|
||||
$s = preg_replace_callback($pattern, function () use ($PostMedia) {
|
||||
$s = preg_replace_callback($pattern, function () use ($PostMedia, $sensitive) {
|
||||
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',
|
||||
]);
|
||||
|
@ -3593,9 +3576,10 @@ class Item
|
|||
*
|
||||
* @param PostMedias $PostMedias
|
||||
* @param string $body
|
||||
* @param bool $sensitive
|
||||
* @return string modified body
|
||||
*/
|
||||
private static function replaceVisualAttachments(PostMedias $PostMedias, string $body): string
|
||||
private static function replaceVisualAttachments(PostMedias $PostMedias, string $body, bool $sensitive): string
|
||||
{
|
||||
DI::profiler()->startRecording('rendering');
|
||||
|
||||
|
@ -3604,7 +3588,7 @@ class Item
|
|||
if (DI::baseUrl()->isLocalUri($PostMedia->preview)) {
|
||||
continue;
|
||||
}
|
||||
$proxy = DI::baseUrl() . $PostMedia->getPreviewPath(Proxy::SIZE_LARGE);
|
||||
$proxy = DI::baseUrl() . $PostMedia->getPreviewPath(Proxy::SIZE_LARGE, $sensitive);
|
||||
$search = ['[img=' . $PostMedia->preview . ']', ']' . $PostMedia->preview . '[/img]'];
|
||||
$replace = ['[img=' . $proxy . ']', ']' . $proxy . '[/img]'];
|
||||
|
||||
|
@ -3613,7 +3597,7 @@ class Item
|
|||
if (DI::baseUrl()->isLocalUri($PostMedia->url)) {
|
||||
continue;
|
||||
}
|
||||
$proxy = DI::baseUrl() . $PostMedia->getPreviewPath(Proxy::SIZE_LARGE);
|
||||
$proxy = DI::baseUrl() . $PostMedia->getPreviewPath(Proxy::SIZE_LARGE, $sensitive);
|
||||
$search = ['[img=' . $PostMedia->url . ']', ']' . $PostMedia->url . '[/img]'];
|
||||
$replace = ['[img=' . $proxy . ']', ']' . $proxy . '[/img]'];
|
||||
|
||||
|
@ -3631,10 +3615,11 @@ 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): string
|
||||
private static function addVisualAttachments(PostMedias $PostMedias, array $item, string $content, bool $shared, bool $sensitive): string
|
||||
{
|
||||
DI::profiler()->startRecording('rendering');
|
||||
$leading = '';
|
||||
|
@ -3649,7 +3634,7 @@ class Item
|
|||
|
||||
if ($PostMedia->mimetype->type == 'image' || $PostMedia->preview) {
|
||||
$preview_size = Proxy::SIZE_MEDIUM;
|
||||
$preview_url = DI::baseUrl() . $PostMedia->getPreviewPath($preview_size);
|
||||
$preview_url = DI::baseUrl() . $PostMedia->getPreviewPath($preview_size, $sensitive);
|
||||
} else {
|
||||
$preview_size = 0;
|
||||
$preview_url = '';
|
||||
|
@ -3660,6 +3645,13 @@ class Item
|
|||
}
|
||||
|
||||
if ($PostMedia->mimetype->type == 'video') {
|
||||
if (($PostMedia->height ?? 0) > ($PostMedia->width ?? 0)) {
|
||||
$height = min(DI::config()->get('system', 'max_video_height') ?: '100%', $PostMedia->height);
|
||||
$width = 'auto';
|
||||
} else {
|
||||
$height = 'auto';
|
||||
$width = '100%';
|
||||
}
|
||||
/// @todo Move the template to /content as well
|
||||
$media = Renderer::replaceMacros(Renderer::getMarkupTemplate('video_top.tpl'), [
|
||||
'$video' => [
|
||||
|
@ -3668,6 +3660,8 @@ class Item
|
|||
'name' => $PostMedia->name ?: $PostMedia->url,
|
||||
'preview' => $preview_url,
|
||||
'mime' => (string)$PostMedia->mimetype,
|
||||
'height' => $height,
|
||||
'width' => $width,
|
||||
],
|
||||
]);
|
||||
if (($item['post-type'] ?? null) == Item::PT_VIDEO) {
|
||||
|
@ -3734,11 +3728,12 @@ 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): string
|
||||
private static function addLinkAttachment(int $uriid, array $attachments, string $body, string $content, bool $shared, array $ignore_links, bool $sensitive): string
|
||||
{
|
||||
DI::profiler()->startRecording('rendering');
|
||||
// Don't show a preview when there is a visual attachment (audio or video)
|
||||
|
@ -3781,9 +3776,9 @@ class Item
|
|||
|
||||
if ($preview && $attachment->preview) {
|
||||
if ($attachment->previewWidth >= 500) {
|
||||
$data['image'] = DI::baseUrl() . $attachment->getPreviewPath(Proxy::SIZE_MEDIUM);
|
||||
$data['image'] = DI::baseUrl() . $attachment->getPreviewPath(Proxy::SIZE_MEDIUM, $sensitive);
|
||||
} else {
|
||||
$data['preview'] = DI::baseUrl() . $attachment->getPreviewPath(Proxy::SIZE_MEDIUM);
|
||||
$data['preview'] = DI::baseUrl() . $attachment->getPreviewPath(Proxy::SIZE_MEDIUM, $sensitive);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3807,6 +3802,12 @@ class Item
|
|||
} elseif (preg_match("/.*(\[attachment.*?\].*?\[\/attachment\]).*/ism", $body, $match)) {
|
||||
$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)) {
|
||||
|
@ -4095,9 +4096,12 @@ class Item
|
|||
return is_numeric($hookData['item_id']) ? $hookData['item_id'] : 0;
|
||||
}
|
||||
|
||||
$fetched_uri = ActivityPub\Processor::fetchMissingActivity($uri, [], '', $completion, $uid);
|
||||
$curlResult = DI::httpClient()->head($uri, [HttpClientOptions::ACCEPT_CONTENT => HttpClientAccept::JSON_AS]);
|
||||
if (HTTPSignature::isValidContentType($curlResult->getContentType())) {
|
||||
$fetched_uri = ActivityPub\Processor::fetchMissingActivity($uri, [], '', $completion, $uid);
|
||||
}
|
||||
|
||||
if ($fetched_uri) {
|
||||
if (!empty($fetched_uri)) {
|
||||
$item_id = self::searchByLink($fetched_uri, $uid);
|
||||
} else {
|
||||
$item_id = Diaspora::fetchByURL($uri);
|
||||
|
|
|
@ -363,6 +363,7 @@ class Photo
|
|||
$photo['backend-class'] = SystemResource::NAME;
|
||||
$photo['backend-ref'] = $filename;
|
||||
$photo['type'] = $mimetype;
|
||||
$photo['filename'] = basename($filename);
|
||||
$photo['cacheable'] = false;
|
||||
|
||||
return $photo;
|
||||
|
@ -394,6 +395,7 @@ class Photo
|
|||
$photo['backend-class'] = ExternalResource::NAME;
|
||||
$photo['backend-ref'] = json_encode(['url' => $url, 'uid' => $uid]);
|
||||
$photo['type'] = $mimetype;
|
||||
$photo['filename'] = basename(parse_url($url, PHP_URL_PATH));
|
||||
$photo['cacheable'] = true;
|
||||
$photo['blurhash'] = $blurhash;
|
||||
$photo['width'] = $width;
|
||||
|
@ -608,9 +610,7 @@ class Photo
|
|||
return false;
|
||||
}
|
||||
|
||||
$type = Images::getMimeTypeByData($img_str, $image_url, $type);
|
||||
|
||||
$image = new Image($img_str, $type);
|
||||
$image = new Image($img_str, $type, $image_url);
|
||||
if ($image->isValid()) {
|
||||
$image->scaleToSquare(300);
|
||||
|
||||
|
@ -619,9 +619,9 @@ class Photo
|
|||
|
||||
if ($maximagesize && ($filesize > $maximagesize)) {
|
||||
Logger::info('Avatar exceeds image limit', ['uid' => $uid, 'cid' => $cid, 'maximagesize' => $maximagesize, 'size' => $filesize, 'type' => $image->getType()]);
|
||||
if ($image->getType() == 'image/gif') {
|
||||
if ($image->getImageType() == IMAGETYPE_GIF) {
|
||||
$image->toStatic();
|
||||
$image = new Image($image->asString(), 'image/png');
|
||||
$image = new Image($image->asString(), image_type_to_mime_type(IMAGETYPE_PNG));
|
||||
|
||||
$filesize = strlen($image->asString());
|
||||
Logger::info('Converted gif to a static png', ['uid' => $uid, 'cid' => $cid, 'size' => $filesize, 'type' => $image->getType()]);
|
||||
|
@ -662,9 +662,9 @@ class Photo
|
|||
|
||||
$suffix = '?ts=' . time();
|
||||
|
||||
$image_url = DI::baseUrl() . '/photo/' . $resource_id . '-4.' . $image->getExt() . $suffix;
|
||||
$thumb = DI::baseUrl() . '/photo/' . $resource_id . '-5.' . $image->getExt() . $suffix;
|
||||
$micro = DI::baseUrl() . '/photo/' . $resource_id . '-6.' . $image->getExt() . $suffix;
|
||||
$image_url = DI::baseUrl() . '/photo/' . $resource_id . '-4' . $image->getExt() . $suffix;
|
||||
$thumb = DI::baseUrl() . '/photo/' . $resource_id . '-5' . $image->getExt() . $suffix;
|
||||
$micro = DI::baseUrl() . '/photo/' . $resource_id . '-6' . $image->getExt() . $suffix;
|
||||
} else {
|
||||
$photo_failure = true;
|
||||
}
|
||||
|
@ -1060,9 +1060,7 @@ class Photo
|
|||
return [];
|
||||
}
|
||||
|
||||
$type = Images::getMimeTypeByData($img_str, $image_url, $type);
|
||||
|
||||
$image = new Image($img_str, $type);
|
||||
$image = new Image($img_str, $type, $image_url);
|
||||
|
||||
$image = self::fitImageSize($image);
|
||||
if (empty($image)) {
|
||||
|
@ -1132,12 +1130,10 @@ class Photo
|
|||
return [];
|
||||
}
|
||||
|
||||
$filetype = Images::getMimeTypeBySource($src, $filename, $filetype);
|
||||
|
||||
Logger::info('File upload', ['src' => $src, 'filename' => $filename, 'size' => $filesize, 'type' => $filetype]);
|
||||
|
||||
$imagedata = @file_get_contents($src);
|
||||
$image = new Image($imagedata, $filetype);
|
||||
$image = new Image($imagedata, $filetype, $filename);
|
||||
if (!$image->isValid()) {
|
||||
Logger::notice('Image is unvalid', ['files' => $files]);
|
||||
return [];
|
||||
|
|
|
@ -110,9 +110,9 @@ class Content
|
|||
{
|
||||
$search = Post\Engagement::escapeKeywords($search);
|
||||
if ($uid != 0) {
|
||||
$condition = ["MATCH (`searchtext`) AGAINST (? IN BOOLEAN MODE) and (private = ? OR `uri-id` in (SELECT `uri-id` FROM `post-user` where `uid` = ?))", $search, Item::PUBLIC, $uid];
|
||||
$condition = ["MATCH (`searchtext`) AGAINST (? IN BOOLEAN MODE) AND (NOT `restricted` OR `uri-id` IN (SELECT `uri-id` FROM `post-user` WHERE `uid` = ?))", $search, $uid];
|
||||
} else {
|
||||
$condition = ["MATCH (`searchtext`) AGAINST (? IN BOOLEAN MODE) and private = ?", $search, Item::PUBLIC];
|
||||
$condition = ["MATCH (`searchtext`) AGAINST (? IN BOOLEAN MODE) AND NOT `restricted`", $search];
|
||||
}
|
||||
|
||||
if (!empty($last_uriid)) {
|
||||
|
@ -139,9 +139,9 @@ class Content
|
|||
{
|
||||
$search = Post\Engagement::escapeKeywords($search);
|
||||
if ($uid != 0) {
|
||||
$condition = ["MATCH (`searchtext`) AGAINST (? IN BOOLEAN MODE) and (private = ? OR `uri-id` in (SELECT `uri-id` FROM `post-user` where `uid` = ?))", $search, Item::PUBLIC, $uid];
|
||||
$condition = ["MATCH (`searchtext`) AGAINST (? IN BOOLEAN MODE) AND (NOT `restricted` OR `uri-id` IN (SELECT `uri-id` FROM `post-user` WHERE `uid` = ?))", $search, $uid];
|
||||
} else {
|
||||
$condition = ["MATCH (`searchtext`) AGAINST (? IN BOOLEAN MODE) and private = ?", $search, Item::PUBLIC];
|
||||
$condition = ["MATCH (`searchtext`) AGAINST (? IN BOOLEAN MODE) AND NOT `restricted", $search];
|
||||
}
|
||||
return DBA::count('post-searchindex', $condition);
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@ class Counts
|
|||
Activity::EMOJIREACT, Activity::ANNOUNCE, Activity::VIEW, Activity::READ])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
$condition = ['thr-parent-id' => $uri_id, 'vid' => $vid, 'deleted' => false];
|
||||
|
||||
if ($body == $verb) {
|
||||
|
@ -52,7 +52,7 @@ class Counts
|
|||
} elseif ($verb == Activity::POST) {
|
||||
$condition['gravity'] = Item::GRAVITY_COMMENT;
|
||||
$body = '';
|
||||
} elseif (($verb != Activity::POST) && (mb_strlen($body) == 1) && Smilies::isEmojiPost($body)) {
|
||||
} elseif ($body && mb_strlen($body) == 1 && Smilies::isEmojiPost($body)) {
|
||||
$condition['body'] = $body;
|
||||
} else {
|
||||
$body = '';
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
namespace Friendica\Model\Post;
|
||||
|
||||
use Friendica\Content\Text\BBCode;
|
||||
use Friendica\Core\L10n;
|
||||
use Friendica\Core\Logger;
|
||||
use Friendica\Core\Protocol;
|
||||
use Friendica\Database\DBA;
|
||||
|
@ -38,7 +39,19 @@ use Friendica\Util\DateTimeFormat;
|
|||
|
||||
class Engagement
|
||||
{
|
||||
const KEYWORDS = ['source', 'server', 'from', 'to', 'group', 'tag', 'network', 'platform', 'visibility', 'language'];
|
||||
const KEYWORDS = ['source', 'server', 'from', 'to', 'group', 'application', 'tag', 'network', 'platform', 'visibility', 'language', 'media'];
|
||||
const SHORTCUTS = ['lang' => 'language', 'net' => 'network', 'relay' => 'application'];
|
||||
const ALTERNATIVES = ['source:news' => 'source:service', 'source:relay' => 'source:application',
|
||||
'media:picture' => 'media:image', 'media:photo' => 'media:image',
|
||||
'network:activitypub' => 'network:apub', 'network:friendica' => 'network:dfrn',
|
||||
'network:diaspora' => 'network:dspr', 'network:ostatus' => 'network:stat',
|
||||
'network:discourse' => 'network:dscs', 'network:tumblr' => 'network:tmbl', 'network:bluesky' => 'network:bsky'];
|
||||
const MEDIA_NONE = 0;
|
||||
const MEDIA_IMAGE = 1;
|
||||
const MEDIA_VIDEO = 2;
|
||||
const MEDIA_AUDIO = 4;
|
||||
const MEDIA_CARD = 8;
|
||||
const MEDIA_POST = 16;
|
||||
|
||||
/**
|
||||
* Store engagement data from an item array
|
||||
|
@ -53,8 +66,10 @@ class Engagement
|
|||
return 0;
|
||||
}
|
||||
|
||||
$parent = Post::selectFirst(['uri-id', 'created', 'author-id', 'owner-id', 'uid', 'private', 'contact-contact-type', 'language', 'network',
|
||||
'title', 'content-warning', 'body', 'author-contact-type', 'author-nick', 'author-addr', 'author-gsid', 'owner-contact-type', 'owner-nick', 'owner-addr', 'owner-gsid'],
|
||||
$parent = Post::selectFirst(['uri-id', 'created', 'uid', 'private', 'quote-uri-id',
|
||||
'contact-contact-type', 'network', 'title', 'content-warning', 'body', 'language',
|
||||
'author-id', 'author-contact-type', 'author-nick', 'author-addr', 'author-gsid',
|
||||
'owner-id', 'owner-contact-type', 'owner-nick', 'owner-addr', 'owner-gsid'],
|
||||
['uri-id' => $item['parent-uri-id']]);
|
||||
|
||||
if ($parent['created'] < self::getCreationDateLimit(false)) {
|
||||
|
@ -82,16 +97,16 @@ class Engagement
|
|||
}
|
||||
}
|
||||
|
||||
$mediatype = self::getMediaType($item['parent-uri-id']);
|
||||
$mediatype = self::getMediaType($item['parent-uri-id'], $parent['quote-uri-id']);
|
||||
|
||||
if (!$store) {
|
||||
$store = !empty($mediatype);
|
||||
}
|
||||
|
||||
$searchtext = self::getSearchTextForItem($parent);
|
||||
$searchtext = self::getSearchTextForItem($parent, $mediatype);
|
||||
$language = !empty($parent['language']) ? (array_key_first(json_decode($parent['language'], true)) ?? L10n::UNDETERMINED_LANGUAGE) : L10n::UNDETERMINED_LANGUAGE;
|
||||
if (!$store) {
|
||||
$language = !empty($parent['language']) ? (array_key_first(json_decode($parent['language'], true)) ?? '') : '';
|
||||
$store = DI::userDefinedChannel()->match($searchtext, $language);
|
||||
$store = DI::userDefinedChannel()->match($searchtext, $language);
|
||||
}
|
||||
|
||||
$engagement = [
|
||||
|
@ -99,9 +114,11 @@ class Engagement
|
|||
'owner-id' => $parent['owner-id'],
|
||||
'contact-type' => $parent['contact-contact-type'],
|
||||
'media-type' => $mediatype,
|
||||
'language' => $parent['language'],
|
||||
'language' => $language,
|
||||
'searchtext' => $searchtext,
|
||||
'size' => self::getContentSize($parent),
|
||||
'created' => $parent['created'],
|
||||
'network' => $parent['network'],
|
||||
'restricted' => !in_array($item['network'], Protocol::FEDERATED) || ($parent['private'] != Item::PUBLIC),
|
||||
'comments' => DBA::count('post', ['parent-uri-id' => $item['parent-uri-id'], 'gravity' => Item::GRAVITY_COMMENT]),
|
||||
'activities' => DBA::count('post', [
|
||||
|
@ -125,6 +142,18 @@ class Engagement
|
|||
return ($ret && !$exists) ? $engagement['uri-id'] : 0;
|
||||
}
|
||||
|
||||
public static function getContentSize(array $item): int
|
||||
{
|
||||
$body = ' ' . $item['title'] . ' ' . $item['content-warning'] . ' ' . $item['body'];
|
||||
$body = BBCode::removeAttachment($body);
|
||||
$body = BBCode::removeSharedData($body);
|
||||
$body = preg_replace('/[^@!#]\[url\=.*?\].*?\[\/url\]/ism', '', $body);
|
||||
$body = BBCode::removeLinks($body);
|
||||
$msg = BBCode::toPlaintext($body, false);
|
||||
|
||||
return mb_strlen($msg);
|
||||
}
|
||||
|
||||
public static function getSearchTextForActivity(string $content, int $author_id, array $tags, array $receivers): string
|
||||
{
|
||||
$author = Contact::getById($author_id);
|
||||
|
@ -146,6 +175,7 @@ class Engagement
|
|||
'owner-nick' => $author['nick'],
|
||||
'owner-addr' => $author['addr'],
|
||||
'owner-gsid' => $author['gsid'],
|
||||
'quote-uri-id' => 0,
|
||||
];
|
||||
|
||||
foreach ($receivers as $receiver) {
|
||||
|
@ -154,7 +184,7 @@ class Engagement
|
|||
}
|
||||
}
|
||||
|
||||
return self::getSearchText($item, $receivers, $tags);
|
||||
return self::getSearchText($item, $receivers, $tags, 0);
|
||||
}
|
||||
|
||||
public static function getSearchTextForUriId(int $uri_id, bool $refresh = false): string
|
||||
|
@ -167,28 +197,29 @@ class Engagement
|
|||
}
|
||||
|
||||
$post = Post::selectFirstPost(['uri-id', 'network', 'title', 'content-warning', 'body', 'private',
|
||||
'author-id', 'author-contact-type', 'author-nick', 'author-addr', 'author-gsid',
|
||||
'author-id', 'author-contact-type', 'author-nick', 'author-addr', 'author-gsid', 'quote-uri-id',
|
||||
'owner-id', 'owner-contact-type', 'owner-nick', 'owner-addr', 'owner-gsid'], ['uri-id' => $uri_id]);
|
||||
if (empty($post['uri-id'])) {
|
||||
return '';
|
||||
}
|
||||
return self::getSearchTextForItem($post);
|
||||
$mediatype = self::getMediaType($uri_id, $post['quote-uri-id']);
|
||||
return self::getSearchTextForItem($post, $mediatype);
|
||||
}
|
||||
|
||||
private static function getSearchTextForItem(array $item): string
|
||||
private static function getSearchTextForItem(array $item, int $mediatype): string
|
||||
{
|
||||
$receivers = array_column(Tag::getByURIId($item['uri-id'], [Tag::MENTION, Tag::IMPLICIT_MENTION, Tag::EXCLUSIVE_MENTION, Tag::AUDIENCE]), 'url');
|
||||
$tags = array_column(Tag::getByURIId($item['uri-id'], [Tag::HASHTAG]), 'name');
|
||||
return self::getSearchText($item, $receivers, $tags);
|
||||
return self::getSearchText($item, $receivers, $tags, $mediatype);
|
||||
}
|
||||
|
||||
private static function getSearchText(array $item, array $receivers, array $tags): string
|
||||
private static function getSearchText(array $item, array $receivers, array $tags, int $mediatype): string
|
||||
{
|
||||
$body = '[nosmile]network_' . $item['network'];
|
||||
|
||||
if (!empty($item['author-gsid'])) {
|
||||
$gserver = DBA::selectFirst('gserver', ['platform', 'nurl'], ['id' => $item['author-gsid']]);
|
||||
$platform = preg_replace( '/[\W]/', '', $gserver['platform'] ?? '');
|
||||
$platform = preg_replace('/[\W]/', '', $gserver['platform'] ?? '');
|
||||
if (!empty($platform)) {
|
||||
$body .= ' platform_' . $platform;
|
||||
}
|
||||
|
@ -197,7 +228,7 @@ class Engagement
|
|||
|
||||
if (($item['owner-contact-type'] == Contact::TYPE_COMMUNITY) && !empty($item['owner-gsid']) && ($item['owner-gsid'] != ($item['author-gsid'] ?? 0))) {
|
||||
$gserver = DBA::selectFirst('gserver', ['platform', 'nurl'], ['id' => $item['owner-gsid']]);
|
||||
$platform = preg_replace( '/[\W]/', '', $gserver['platform'] ?? '');
|
||||
$platform = preg_replace('/[\W]/', '', $gserver['platform'] ?? '');
|
||||
if (!empty($platform) && !strpos($body, 'platform_' . $platform)) {
|
||||
$body .= ' platform_' . $platform;
|
||||
}
|
||||
|
@ -230,6 +261,8 @@ class Engagement
|
|||
|
||||
if ($item['author-contact-type'] == Contact::TYPE_COMMUNITY) {
|
||||
$body .= ' group_' . $item['author-nick'] . ' group_' . $item['author-addr'];
|
||||
} elseif ($item['author-contact-type'] == Contact::TYPE_RELAY) {
|
||||
$body .= ' application_' . $item['author-nick'] . ' application_' . $item['author-addr'];
|
||||
} elseif (in_array($item['author-contact-type'], [Contact::TYPE_PERSON, Contact::TYPE_NEWS, Contact::TYPE_ORGANISATION])) {
|
||||
$body .= ' from_' . $item['author-nick'] . ' from_' . $item['author-addr'];
|
||||
}
|
||||
|
@ -242,7 +275,12 @@ class Engagement
|
|||
}
|
||||
}
|
||||
|
||||
$body = self::addResharers($body, $item['uri-id']);
|
||||
|
||||
foreach ($receivers as $receiver) {
|
||||
if (empty($receiver)) {
|
||||
continue;
|
||||
}
|
||||
$contact = Contact::getByURL($receiver, false, ['nick', 'addr', 'contact-type']);
|
||||
if (empty($contact)) {
|
||||
continue;
|
||||
|
@ -264,22 +302,71 @@ class Engagement
|
|||
$body .= ' language_' . array_key_first($languages);
|
||||
}
|
||||
|
||||
if ($mediatype & self::MEDIA_IMAGE) {
|
||||
$body .= ' media_image';
|
||||
}
|
||||
|
||||
if ($mediatype & self::MEDIA_VIDEO) {
|
||||
$body .= ' media_video';
|
||||
}
|
||||
|
||||
if ($mediatype & self::MEDIA_AUDIO) {
|
||||
$body .= ' media_audio';
|
||||
}
|
||||
|
||||
if ($mediatype & self::MEDIA_CARD) {
|
||||
$body .= ' media_card';
|
||||
}
|
||||
|
||||
if ($mediatype & self::MEDIA_POST) {
|
||||
$body .= ' media_post';
|
||||
}
|
||||
|
||||
$body .= ' ' . $item['title'] . ' ' . $item['content-warning'] . ' ' . $item['body'];
|
||||
|
||||
return BBCode::toSearchText($body, $item['uri-id']);
|
||||
}
|
||||
|
||||
private static function getMediaType(int $uri_id): int
|
||||
private static function addResharers(string $text, int $uri_id): string
|
||||
{
|
||||
$result = Post::selectPosts(['author-addr', 'author-nick', 'author-contact-type'],
|
||||
['thr-parent-id' => $uri_id, 'gravity' => Item::GRAVITY_ACTIVITY, 'verb' => Activity::ANNOUNCE, 'author-contact-type' => [Contact::TYPE_RELAY, Contact::TYPE_COMMUNITY]]);
|
||||
while ($reshare = Post::fetch($result)) {
|
||||
switch ($reshare['author-contact-type']) {
|
||||
case Contact::TYPE_RELAY:
|
||||
$prefix = ' application_';
|
||||
break;
|
||||
case Contact::TYPE_COMMUNITY:
|
||||
$prefix = ' group_';
|
||||
break;
|
||||
}
|
||||
$nick = $prefix . $reshare['author-nick'];
|
||||
$addr = $prefix . $reshare['author-addr'];
|
||||
|
||||
if (stripos($text, $addr) === false) {
|
||||
$text .= $nick . $addr;
|
||||
}
|
||||
}
|
||||
DBA::close($result);
|
||||
|
||||
return $text;
|
||||
}
|
||||
|
||||
public static function getMediaType(int $uri_id, int $quote_uri_id = null): int
|
||||
{
|
||||
$media = Post\Media::getByURIId($uri_id);
|
||||
$type = 0;
|
||||
$type = !empty($quote_uri_id) ? self::MEDIA_POST : self::MEDIA_NONE;
|
||||
foreach ($media as $entry) {
|
||||
if ($entry['type'] == Post\Media::IMAGE) {
|
||||
$type = $type | 1;
|
||||
$type = $type | self::MEDIA_IMAGE;
|
||||
} elseif ($entry['type'] == Post\Media::VIDEO) {
|
||||
$type = $type | 2;
|
||||
$type = $type | self::MEDIA_VIDEO;
|
||||
} elseif ($entry['type'] == Post\Media::AUDIO) {
|
||||
$type = $type | 4;
|
||||
$type = $type | self::MEDIA_AUDIO;
|
||||
} elseif ($entry['type'] == Post\Media::HTML) {
|
||||
$type = $type | self::MEDIA_CARD;
|
||||
} elseif ($entry['type'] == Post\Media::ACTIVITY) {
|
||||
$type = $type | self::MEDIA_POST;
|
||||
}
|
||||
}
|
||||
return $type;
|
||||
|
@ -318,7 +405,15 @@ class Engagement
|
|||
|
||||
public static function escapeKeywords(string $fullTextSearch): string
|
||||
{
|
||||
foreach (Engagement::KEYWORDS as $keyword) {
|
||||
foreach (SELF::SHORTCUTS as $search => $replace) {
|
||||
$fullTextSearch = preg_replace('~' . $search . ':(.[\w\*@\.-]+)~', $replace . ':$1', $fullTextSearch);
|
||||
}
|
||||
|
||||
foreach (SELF::ALTERNATIVES as $search => $replace) {
|
||||
$fullTextSearch = str_replace($search, $replace, $fullTextSearch);
|
||||
}
|
||||
|
||||
foreach (self::KEYWORDS as $keyword) {
|
||||
$fullTextSearch = preg_replace('~(' . $keyword . '):(.[\w\*@\.-]+)~', '"$1_$2"', $fullTextSearch);
|
||||
}
|
||||
return $fullTextSearch;
|
||||
|
|
|
@ -31,6 +31,7 @@ use Friendica\Util\HTTPSignature;
|
|||
use Friendica\Util\Images;
|
||||
use Friendica\Util\Proxy;
|
||||
use Friendica\Object\Image;
|
||||
use Friendica\Util\Network;
|
||||
|
||||
/**
|
||||
* Class Link
|
||||
|
@ -77,7 +78,7 @@ class Link
|
|||
} else {
|
||||
$fields = self::fetchMimeType($url);
|
||||
$fields['uri-id'] = $uriId;
|
||||
$fields['url'] = $url;
|
||||
$fields['url'] = Network::sanitizeUrl($url);
|
||||
|
||||
DBA::insert('post-link', $fields, Database::INSERT_IGNORE);
|
||||
$id = DBA::lastInsertId();
|
||||
|
@ -133,15 +134,23 @@ class Link
|
|||
Logger::notice('Error fetching url', ['url' => $url, 'exception' => $exception]);
|
||||
return [];
|
||||
}
|
||||
$fields = ['mimetype' => $curlResult->getHeader('Content-Type')[0]];
|
||||
|
||||
$img_str = $curlResult->getBodyString();
|
||||
$image = new Image($img_str, Images::getMimeTypeByData($img_str));
|
||||
if ($image->isValid()) {
|
||||
$fields['mimetype'] = $image->getType();
|
||||
$fields['width'] = $image->getWidth();
|
||||
$fields['height'] = $image->getHeight();
|
||||
$fields['blurhash'] = $image->getBlurHash();
|
||||
if (!$curlResult->isSuccess()) {
|
||||
Logger::notice('Fetching unsuccessful', ['url' => $url]);
|
||||
return [];
|
||||
}
|
||||
|
||||
$fields = ['mimetype' => $curlResult->getContentType()];
|
||||
|
||||
if (Images::isSupportedMimeType($fields['mimetype'])) {
|
||||
$img_str = $curlResult->getBodyString();
|
||||
$image = new Image($img_str, $fields['mimetype'], $url);
|
||||
if ($image->isValid()) {
|
||||
$fields['mimetype'] = $image->getType();
|
||||
$fields['width'] = $image->getWidth();
|
||||
$fields['height'] = $image->getHeight();
|
||||
$fields['blurhash'] = $image->getBlurHash();
|
||||
}
|
||||
}
|
||||
|
||||
return $fields;
|
||||
|
|
|
@ -96,6 +96,7 @@ class Media
|
|||
return false;
|
||||
}
|
||||
|
||||
$media['url'] = Network::sanitizeUrl($media['url']);
|
||||
$media = self::unsetEmptyFields($media);
|
||||
$media = DI::dbaDefinition()->truncateFieldsForTable('post-media', $media);
|
||||
|
||||
|
@ -195,7 +196,7 @@ class Media
|
|||
|
||||
if ($curlResult->isSuccess()) {
|
||||
if (empty($media['mimetype'])) {
|
||||
$media['mimetype'] = $curlResult->getHeader('Content-Type')[0] ?? '';
|
||||
$media['mimetype'] = $curlResult->getContentType() ?? '';
|
||||
}
|
||||
if (empty($media['size'])) {
|
||||
$media['size'] = (int)($curlResult->getHeader('Content-Length')[0] ?? 0);
|
||||
|
@ -364,7 +365,7 @@ class Media
|
|||
*/
|
||||
private static function addPage(array $media): array
|
||||
{
|
||||
$data = ParseUrl::getSiteinfoCached($media['url'], false);
|
||||
$data = ParseUrl::getSiteinfoCached($media['url']);
|
||||
$media['preview'] = $data['images'][0]['src'] ?? null;
|
||||
$media['preview-height'] = $data['images'][0]['height'] ?? null;
|
||||
$media['preview-width'] = $data['images'][0]['width'] ?? null;
|
||||
|
|
|
@ -21,10 +21,13 @@
|
|||
|
||||
namespace Friendica\Model\Post;
|
||||
|
||||
use Friendica\Core\L10n;
|
||||
use Friendica\Core\Logger;
|
||||
use Friendica\Core\Protocol;
|
||||
use Friendica\Database\Database;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\DI;
|
||||
use Friendica\Model\Item;
|
||||
use Friendica\Model\Post;
|
||||
use Friendica\Util\DateTimeFormat;
|
||||
|
||||
|
@ -34,24 +37,30 @@ class SearchIndex
|
|||
* Insert a post-searchindex entry
|
||||
*
|
||||
* @param int $uri_id
|
||||
* @param string $network
|
||||
* @param int $private
|
||||
* @param string $created
|
||||
* @param bool $refresh
|
||||
*/
|
||||
public static function insert(int $uri_id, string $network, int $private, string $created, bool $refresh = false)
|
||||
public static function insert(int $uri_id, string $created, bool $refresh = false)
|
||||
{
|
||||
$limit = self::searchAgeDateLimit();
|
||||
if (!empty($limit) && (strtotime($created) < strtotime($limit))) {
|
||||
return;
|
||||
}
|
||||
|
||||
$item = Post::selectFirstPost(['created', 'owner-id', 'private', 'language', 'network', 'title', 'content-warning', 'body', 'quote-uri-id'], ['uri-id' => $uri_id]);
|
||||
if (empty($item)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$search = [
|
||||
'uri-id' => $uri_id,
|
||||
'network' => $network,
|
||||
'private' => $private,
|
||||
'created' => $created,
|
||||
'owner-id' => $item['owner-id'],
|
||||
'media-type' => Engagement::getMediaType($uri_id, $item['quote-uri-id']),
|
||||
'language' => substr(!empty($item['language']) ? (array_key_first(json_decode($item['language'], true)) ?? L10n::UNDETERMINED_LANGUAGE) : L10n::UNDETERMINED_LANGUAGE, 0, 2),
|
||||
'searchtext' => Post\Engagement::getSearchTextForUriId($uri_id, $refresh),
|
||||
'size' => Engagement::getContentSize($item),
|
||||
'created' => $item['created'],
|
||||
'restricted' => !in_array($item['network'], Protocol::FEDERATED) || ($item['private'] != Item::PUBLIC),
|
||||
];
|
||||
return DBA::insert('post-searchindex', $search, Database::INSERT_UPDATE);
|
||||
}
|
||||
|
@ -63,7 +72,7 @@ class SearchIndex
|
|||
*/
|
||||
public static function update(int $uri_id)
|
||||
{
|
||||
$searchtext = Post\Engagement::getSearchTextForUriId($uri_id, true);
|
||||
$searchtext = Post\Engagement::getSearchTextForUriId($uri_id);
|
||||
return DBA::update('post-searchindex', ['searchtext' => $searchtext], ['uri-id' => $uri_id]);
|
||||
}
|
||||
|
||||
|
|
|
@ -378,6 +378,18 @@ class Tag
|
|||
return DBA::exists('post-tag', ['uri-id' => $uriId, 'type' => [self::HASHTAG, self::MENTION, self::EXCLUSIVE_MENTION, self::IMPLICIT_MENTION]]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for a given hashtag on a given post
|
||||
*
|
||||
* @param integer $uriId
|
||||
* @param string $tag
|
||||
* @return boolean
|
||||
*/
|
||||
public static function existsTagForPost(int $uriId, string $tag): bool
|
||||
{
|
||||
return DBA::exists('tag-view', ['uri-id' => $uriId, 'type' => self::HASHTAG, 'name' => $tag]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove tag/mention
|
||||
*
|
||||
|
@ -546,7 +558,7 @@ class Tag
|
|||
);
|
||||
while ($tag = DBA::fetch($taglist)) {
|
||||
if ($tag['url'] == '') {
|
||||
$tag['url'] = $searchpath . rawurlencode($tag['name']);
|
||||
$tag['url'] = $searchpath . urlencode($tag['name']);
|
||||
}
|
||||
|
||||
$orig_tag = $tag['url'];
|
||||
|
|
|
@ -127,6 +127,9 @@ class User
|
|||
|
||||
case 'community':
|
||||
return self::ACCOUNT_TYPE_COMMUNITY;
|
||||
|
||||
case 'relay':
|
||||
return self::ACCOUNT_TYPE_RELAY;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
@ -477,7 +480,7 @@ class User
|
|||
|
||||
// Check for correct url and normalised nurl
|
||||
$url = DI::baseUrl() . '/profile/' . $owner['nickname'];
|
||||
$repair = empty($owner['network']) || ($owner['url'] != $url) || ($owner['nurl'] != Strings::normaliseLink($owner['url']));
|
||||
$repair = empty($owner['baseurl']) || empty($owner['network']) || ($owner['url'] != $url) || ($owner['nurl'] != Strings::normaliseLink($owner['url']));
|
||||
|
||||
if (!$repair) {
|
||||
// Check if "addr" is present and correct
|
||||
|
@ -823,26 +826,30 @@ class User
|
|||
/**
|
||||
* Update the day of the last activity of the given user
|
||||
*
|
||||
* @param integer $uid
|
||||
* @param array $user
|
||||
* @param bool $refresh_login
|
||||
* @return void
|
||||
*/
|
||||
public static function updateLastActivity(int $uid)
|
||||
public static function updateLastActivity(array $user, bool $refresh_login)
|
||||
{
|
||||
if (!$uid) {
|
||||
return;
|
||||
}
|
||||
|
||||
$user = self::getById($uid, ['last-activity']);
|
||||
if (empty($user)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$current_day = DateTimeFormat::utcNow('Y-m-d');
|
||||
if (($user['last-activity'] == $current_day) && (!$refresh_login || DateTimeFormat::utc($user['login_date'], 'z-H') == DateTimeFormat::utcNow('z-H'))) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($user['last-activity'] != $current_day) {
|
||||
self::update(['last-activity' => $current_day], $uid);
|
||||
// Set the last activity for all identities of the user
|
||||
DBA::update('user', ['last-activity' => $current_day], ['parent-uid' => $uid, 'verified' => true, 'blocked' => false, 'account_removed' => false, 'account_expired' => false]);
|
||||
$fields = ['last-activity' => $current_day];
|
||||
if ($refresh_login) {
|
||||
$fields['login_date'] = DateTimeFormat::utcNow();
|
||||
}
|
||||
|
||||
Logger::debug('Set last activity for user', ['uid' => $user['uid'], 'fields' => $fields]);
|
||||
self::update($fields, $user['uid']);
|
||||
// Set the last activity for all identities of the user
|
||||
DBA::update('user', $fields, ['parent-uid' => $user['uid'], 'verified' => true, 'blocked' => false, 'account_removed' => false, 'account_expired' => false]);
|
||||
|
||||
if (!empty($user['parent-uid'])) {
|
||||
self::update($fields, $user['parent-uid']);
|
||||
DBA::update('user', $fields, ['parent-uid' => $user['parent-uid'], 'verified' => true, 'blocked' => false, 'account_removed' => false, 'account_expired' => false]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1400,9 +1407,7 @@ class User
|
|||
$type = '';
|
||||
}
|
||||
|
||||
$type = Images::getMimeTypeByData($img_str, $photo, $type);
|
||||
|
||||
$image = new Image($img_str, $type);
|
||||
$image = new Image($img_str, $type, $photo);
|
||||
if ($image->isValid()) {
|
||||
$image->scaleToSquare(300);
|
||||
|
||||
|
|
|
@ -30,6 +30,8 @@ class PhpInfo extends BaseAdmin
|
|||
{
|
||||
self::checkAdminAccess();
|
||||
|
||||
self::checkFormSecurityTokenForbiddenOnError('phpinfo', 't');
|
||||
|
||||
phpinfo();
|
||||
System::exit();
|
||||
}
|
||||
|
|
|
@ -80,6 +80,7 @@ class Site extends BaseAdmin
|
|||
|
||||
$allowed_sites = (!empty($_POST['allowed_sites']) ? trim($_POST['allowed_sites']) : '');
|
||||
$allowed_email = (!empty($_POST['allowed_email']) ? trim($_POST['allowed_email']) : '');
|
||||
$disallowed_email = (!empty($_POST['disallowed_email']) ? trim($_POST['disallowed_email']) : '');
|
||||
$forbidden_nicknames = (!empty($_POST['forbidden_nicknames']) ? strtolower(trim($_POST['forbidden_nicknames'])) : '');
|
||||
$system_actor_name = (!empty($_POST['system_actor_name']) ? trim($_POST['system_actor_name']) : '');
|
||||
$no_oembed_rich_content = !empty($_POST['no_oembed_rich_content']);
|
||||
|
@ -255,6 +256,7 @@ class Site extends BaseAdmin
|
|||
$transactionConfig->set('config', 'register_text' , $register_text);
|
||||
$transactionConfig->set('system', 'allowed_sites' , $allowed_sites);
|
||||
$transactionConfig->set('system', 'allowed_email' , $allowed_email);
|
||||
$transactionConfig->set('system', 'disallowed_email' , $disallowed_email);
|
||||
$transactionConfig->set('system', 'forbidden_nicknames' , $forbidden_nicknames);
|
||||
$transactionConfig->set('system', 'system_actor_name' , $system_actor_name);
|
||||
$transactionConfig->set('system', 'no_oembed_rich_content' , $no_oembed_rich_content);
|
||||
|
@ -505,6 +507,7 @@ class Site extends BaseAdmin
|
|||
'$abandon_days' => ['abandon_days', DI::l10n()->t('Accounts abandoned after x days'), DI::config()->get('system', 'account_abandon_days'), DI::l10n()->t('Will not waste system resources polling external sites for abandonded accounts. Enter 0 for no time limit.')],
|
||||
'$allowed_sites' => ['allowed_sites', DI::l10n()->t('Allowed friend domains'), DI::config()->get('system', 'allowed_sites'), DI::l10n()->t('Comma separated list of domains which are allowed to establish friendships with this site. Wildcards are accepted. Empty to allow any domains')],
|
||||
'$allowed_email' => ['allowed_email', DI::l10n()->t('Allowed email domains'), DI::config()->get('system', 'allowed_email'), DI::l10n()->t('Comma separated list of domains which are allowed in email addresses for registrations to this site. Wildcards are accepted. Empty to allow any domains')],
|
||||
'$disallowed_email' => ['disallowed_email', DI::l10n()->t('Disallowed email domains'), DI::config()->get('system', 'disallowed_email'), DI::l10n()->t('Comma separated list of domains which are rejected as email addresses for registrations to this site. Wildcards are accepted.')],
|
||||
'$no_oembed_rich_content' => ['no_oembed_rich_content', DI::l10n()->t('No OEmbed rich content'), DI::config()->get('system', 'no_oembed_rich_content'), DI::l10n()->t('Don\'t show the rich content (e.g. embedded PDF), except from the domains listed below.')],
|
||||
'$allowed_oembed' => ['allowed_oembed', DI::l10n()->t('Trusted third-party domains'), DI::config()->get('system', 'allowed_oembed'), DI::l10n()->t('Comma separated list of domains from which content is allowed to be embedded in posts like with OEmbed. All sub-domains of the listed domains are allowed as well.')],
|
||||
'$block_public' => ['block_public', DI::l10n()->t('Block public'), DI::config()->get('system', 'block_public'), DI::l10n()->t('Check to block public access to all otherwise public personal pages on this site unless you are currently logged in.')],
|
||||
|
|
56
src/Module/Api/Mastodon/Accounts/Lookup.php
Normal file
56
src/Module/Api/Mastodon/Accounts/Lookup.php
Normal file
|
@ -0,0 +1,56 @@
|
|||
<?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\Api\Mastodon\Accounts;
|
||||
|
||||
use Friendica\Core\Protocol;
|
||||
use Friendica\DI;
|
||||
use Friendica\Model\Contact;
|
||||
use Friendica\Module\BaseApi;
|
||||
|
||||
/**
|
||||
* @see https://docs.joinmastodon.org/methods/accounts/#lookup
|
||||
*/
|
||||
class Lookup extends BaseApi
|
||||
{
|
||||
/**
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
*/
|
||||
protected function rawContent(array $request = [])
|
||||
{
|
||||
$this->checkAllowedScope(self::SCOPE_READ);
|
||||
$uid = self::getCurrentUserID();
|
||||
|
||||
$request = $this->getRequest([
|
||||
'acct' => '', // The username or Webfinger address to lookup.
|
||||
], $request);
|
||||
|
||||
if (empty($request['acct'])) {
|
||||
$this->logAndJsonError(404, $this->errorFactory->RecordNotFound());
|
||||
}
|
||||
$contact = Contact::getByURL($request['acct'], null, ['id', 'network', 'failed', 'blocked']);
|
||||
if (empty($contact) || ($contact['network'] == Protocol::PHANTOM) || $contact['failed'] || $contact['blocked']) {
|
||||
$this->logAndJsonError(404, $this->errorFactory->RecordNotFound());
|
||||
}
|
||||
|
||||
$this->jsonExit(DI::mstdnAccount()->createFromContactId($contact['id'], $uid));
|
||||
}
|
||||
}
|
|
@ -21,7 +21,6 @@
|
|||
|
||||
namespace Friendica\Module\Api\Mastodon\Apps;
|
||||
|
||||
use Friendica\Core\System;
|
||||
use Friendica\DI;
|
||||
use Friendica\Module\BaseApi;
|
||||
|
||||
|
|
|
@ -21,7 +21,6 @@
|
|||
|
||||
namespace Friendica\Module\Api\Mastodon;
|
||||
|
||||
use Friendica\Core\System;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\DI;
|
||||
use Friendica\Module\BaseApi;
|
||||
|
|
|
@ -38,7 +38,7 @@ use Friendica\Util\Strings;
|
|||
use Psr\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
* @see https://docs.joinmastodon.org/api/rest/instances/
|
||||
* @see https://docs.joinmastodon.org/methods/instance/
|
||||
*/
|
||||
class Instance extends BaseApi
|
||||
{
|
||||
|
@ -95,7 +95,7 @@ class Instance extends BaseApi
|
|||
|
||||
return new InstanceV2Entity\Configuration(
|
||||
$statuses_config,
|
||||
new InstanceV2Entity\MediaAttachmentsConfig(array_keys(Images::supportedTypes()), $image_size_limit, $image_matrix_limit),
|
||||
new InstanceV2Entity\MediaAttachmentsConfig(Images::supportedMimeTypes(), $image_size_limit, $image_matrix_limit),
|
||||
new InstanceV2Entity\Polls(),
|
||||
new InstanceV2Entity\Accounts(),
|
||||
);
|
||||
|
|
59
src/Module/Api/Mastodon/Instance/ExtendedDescription.php
Normal file
59
src/Module/Api/Mastodon/Instance/ExtendedDescription.php
Normal file
|
@ -0,0 +1,59 @@
|
|||
<?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\Api\Mastodon\Instance;
|
||||
|
||||
use DateTime;
|
||||
use Friendica\App;
|
||||
use Friendica\Core\Config\Capability\IManageConfigValues;
|
||||
use Friendica\Core\L10n;
|
||||
use Friendica\Model\User;
|
||||
use Friendica\Module\Api\ApiResponse;
|
||||
use Friendica\Module\BaseApi;
|
||||
use Friendica\Network\HTTPException;
|
||||
use Friendica\Object\Api\Mastodon;
|
||||
use Friendica\Util\Profiler;
|
||||
use Psr\Log\LoggerInterface;
|
||||
|
||||
/**
|
||||
* @see https://docs.joinmastodon.org/methods/instance/#extended_description
|
||||
*/
|
||||
class ExtendedDescription extends BaseApi
|
||||
{
|
||||
private IManageConfigValues $config;
|
||||
|
||||
public function __construct(\Friendica\Factory\Api\Mastodon\Error $errorFactory, App $app, L10n $l10n, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, ApiResponse $response, IManageConfigValues $config, 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 = [])
|
||||
{
|
||||
$account = User::getSystemAccount();
|
||||
|
||||
$this->jsonExit(new Mastodon\ExtendedDescription(new DateTime($account['updated']), $this->config->get('config', 'info')));
|
||||
}
|
||||
}
|
|
@ -22,7 +22,6 @@
|
|||
namespace Friendica\Module\Api\Mastodon\Instance;
|
||||
|
||||
use Friendica\Core\Protocol;
|
||||
use Friendica\Core\System;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\Model\GServer;
|
||||
use Friendica\Module\BaseApi;
|
||||
|
|
|
@ -21,10 +21,7 @@
|
|||
|
||||
namespace Friendica\Module\Api\Mastodon\Instance;
|
||||
|
||||
use Friendica\Content\Text\BBCode;
|
||||
use Friendica\Content\Text\HTML;
|
||||
use Friendica\Core\System;
|
||||
use Friendica\DI;
|
||||
use Friendica\Module\BaseApi;
|
||||
use Friendica\Network\HTTPException;
|
||||
|
||||
|
|
|
@ -131,7 +131,7 @@ class InstanceV2 extends BaseApi
|
|||
|
||||
return new InstanceEntity\Configuration(
|
||||
$statuses_config,
|
||||
new InstanceEntity\MediaAttachmentsConfig(array_keys(Images::supportedTypes()), $image_size_limit, $image_matrix_limit),
|
||||
new InstanceEntity\MediaAttachmentsConfig(Images::supportedMimeTypes(), $image_size_limit, $image_matrix_limit),
|
||||
new InstanceEntity\Polls(),
|
||||
new InstanceEntity\Accounts(),
|
||||
);
|
||||
|
|
|
@ -21,7 +21,6 @@
|
|||
|
||||
namespace Friendica\Module\Api\Mastodon;
|
||||
|
||||
use Friendica\Core\System;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\DI;
|
||||
use Friendica\Module\BaseApi;
|
||||
|
@ -39,15 +38,6 @@ class Mutes extends BaseApi
|
|||
$this->checkAllowedScope(self::SCOPE_READ);
|
||||
$uid = self::getCurrentUserID();
|
||||
|
||||
if (empty($this->parameters['id'])) {
|
||||
$this->logAndJsonError(422, $this->errorFactory->UnprocessableEntity());
|
||||
}
|
||||
|
||||
$id = $this->parameters['id'];
|
||||
if (!DBA::exists('contact', ['id' => $id, 'uid' => 0])) {
|
||||
$this->logAndJsonError(404, $this->errorFactory->RecordNotFound());
|
||||
}
|
||||
|
||||
$request = $this->getRequest([
|
||||
'max_id' => 0, // Return results older than this id
|
||||
'since_id' => 0, // Return results newer than this id
|
||||
|
@ -57,7 +47,7 @@ class Mutes extends BaseApi
|
|||
|
||||
$params = ['order' => ['cid' => true], 'limit' => $request['limit']];
|
||||
|
||||
$condition = ['cid' => $id, 'ignored' => true, 'uid' => $uid];
|
||||
$condition = ['ignored' => true, 'uid' => $uid];
|
||||
|
||||
if (!empty($request['max_id'])) {
|
||||
$condition = DBA::mergeConditions($condition, ["`cid` < ?", $request['max_id']]);
|
||||
|
@ -74,6 +64,7 @@ class Mutes extends BaseApi
|
|||
}
|
||||
|
||||
$followers = DBA::select('user-contact', ['cid'], $condition, $params);
|
||||
$accounts = [];
|
||||
while ($follower = DBA::fetch($followers)) {
|
||||
self::setBoundaries($follower['cid']);
|
||||
$accounts[] = DI::mstdnAccount()->createFromContactId($follower['cid'], $uid);
|
||||
|
|
|
@ -21,7 +21,6 @@
|
|||
|
||||
namespace Friendica\Module\Api\Mastodon;
|
||||
|
||||
use Friendica\Core\System;
|
||||
use Friendica\DI;
|
||||
use Friendica\Module\BaseApi;
|
||||
use Friendica\Network\HTTPException;
|
||||
|
|
|
@ -137,6 +137,10 @@ class Search extends BaseApi
|
|||
private static function searchStatuses(int $uid, string $q, string $account_id, int $max_id, int $min_id, int $limit, int $offset)
|
||||
{
|
||||
if (Network::isValidHttpUrl($q)) {
|
||||
// Unique post search, any offset greater than 0 should return empty result
|
||||
if ($offset > 0) {
|
||||
return [];
|
||||
}
|
||||
$q = Network::convertToIdn($q);
|
||||
// If the user-specific search failed, we search and probe a public post
|
||||
$item_id = Item::fetchByLink($q, $uid) ?: Item::fetchByLink($q);
|
||||
|
@ -154,10 +158,14 @@ class Search extends BaseApi
|
|||
$table = 'tag-search-view';
|
||||
} else {
|
||||
$q = Post\Engagement::escapeKeywords($q);
|
||||
$condition = ["MATCH (`searchtext`) AGAINST (? IN BOOLEAN MODE) and (private = ? OR `uri-id` in (SELECT `uri-id` FROM `post-user` where `uid` = ?))", $q, Item::PUBLIC, $uid];
|
||||
$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';
|
||||
}
|
||||
|
||||
if (!empty($account_id)) {
|
||||
$condition = DBA::mergeConditions($condition, ["`author-id` = ?", $account_id]);
|
||||
}
|
||||
|
||||
if (!empty($max_id)) {
|
||||
$condition = DBA::mergeConditions($condition, ["`uri-id` < ?", $max_id]);
|
||||
}
|
||||
|
|
|
@ -55,6 +55,7 @@ class Statuses extends BaseApi
|
|||
'status' => '', // Text content of the status. If media_ids is provided, this becomes optional. Attaching a poll is optional while status is provided.
|
||||
'media_ids' => [], // Array of Attachment ids to be attached as media. If provided, status becomes optional, and poll cannot be used.
|
||||
'in_reply_to_id' => 0, // ID of the status being replied to, if status is a reply
|
||||
'sensitive' => false, // Mark status and attached media as sensitive? Defaults to false.
|
||||
'spoiler_text' => '', // Text to be shown as a warning or subject before the actual content. Statuses are generally collapsed behind this field.
|
||||
'language' => '', // ISO 639 language code for this status.
|
||||
'media_attributes' => [],
|
||||
|
@ -83,6 +84,7 @@ class Statuses extends BaseApi
|
|||
$item['gravity'] = $post['gravity'];
|
||||
$item['verb'] = $post['verb'];
|
||||
$item['app'] = $this->getApp();
|
||||
$item['sensitive'] = $request['sensitive'];
|
||||
|
||||
if (!empty($request['language'])) {
|
||||
$item['language'] = json_encode([$request['language'] => 1]);
|
||||
|
@ -179,7 +181,7 @@ class Statuses extends BaseApi
|
|||
'poll' => [], // Poll data. If provided, media_ids cannot be used, and poll[expires_in] must be provided.
|
||||
'in_reply_to_id' => 0, // ID of the status being replied to, if status is a reply
|
||||
'quote_id' => 0, // ID of the message to quote
|
||||
'sensitive' => false, // Mark status and attached media as sensitive?
|
||||
'sensitive' => false, // Mark status and attached media as sensitive? Defaults to false.
|
||||
'spoiler_text' => '', // Text to be shown as a warning or subject before the actual content. Statuses are generally collapsed behind this field.
|
||||
'visibility' => '', // Visibility of the posted status. One of: "public", "unlisted", "private" or "direct".
|
||||
'scheduled_at' => '', // ISO 8601 Datetime at which to schedule a status. Providing this parameter will cause ScheduledStatus to be returned instead of Status. Must be at least 5 minutes in the future.
|
||||
|
@ -198,6 +200,7 @@ class Statuses extends BaseApi
|
|||
$item['title'] = '';
|
||||
$item['body'] = $this->formatStatus($request['status'], $uid);
|
||||
$item['app'] = $this->getApp();
|
||||
$item['sensitive'] = $request['sensitive'];
|
||||
$item['visibility'] = $request['visibility'];
|
||||
|
||||
switch ($request['visibility']) {
|
||||
|
@ -400,11 +403,10 @@ class Statuses extends BaseApi
|
|||
|
||||
Photo::setPermissionForResource($media[0]['resource-id'], $item['uid'], $item['allow_cid'], $item['allow_gid'], $item['deny_cid'], $item['deny_gid']);
|
||||
|
||||
$phototypes = Images::supportedTypes();
|
||||
$ext = $phototypes[$media[0]['type']];
|
||||
$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,
|
||||
'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'] ?? '',
|
||||
|
@ -412,7 +414,7 @@ class Statuses extends BaseApi
|
|||
'height' => $media[0]['height']];
|
||||
|
||||
if (count($media) > 1) {
|
||||
$attachment['preview'] = DI::baseUrl() . '/photo/' . $media[1]['resource-id'] . '-' . $media[1]['scale'] . '.' . $ext;
|
||||
$attachment['preview'] = DI::baseUrl() . '/photo/' . $media[1]['resource-id'] . '-' . $media[1]['scale'] . $ext;
|
||||
$attachment['preview-width'] = $media[1]['width'];
|
||||
$attachment['preview-height'] = $media[1]['height'];
|
||||
}
|
||||
|
|
|
@ -129,7 +129,11 @@ class Context extends BaseApi
|
|||
$display_quotes = self::appSupportsQuotes();
|
||||
|
||||
foreach (array_slice($ancestors, 0, $request['limit']) as $ancestor) {
|
||||
$statuses['ancestors'][] = DI::mstdnStatus()->createFromUriId($ancestor, $uid, $display_quotes);
|
||||
try {
|
||||
$statuses['ancestors'][] = DI::mstdnStatus()->createFromUriId($ancestor, $uid, $display_quotes);
|
||||
} catch (\Throwable $th) {
|
||||
$this->logger->info('Post not fetchable', ['uri-id' => $ancestor, 'uid' => $uid, 'error' => $th]);
|
||||
}
|
||||
}
|
||||
|
||||
$descendants = array_diff(self::getChildren($id, $children), $deleted);
|
||||
|
@ -137,7 +141,11 @@ class Context extends BaseApi
|
|||
asort($descendants);
|
||||
|
||||
foreach (array_slice($descendants, 0, $request['limit']) as $descendant) {
|
||||
$statuses['descendants'][] = DI::mstdnStatus()->createFromUriId($descendant, $uid, $display_quotes);
|
||||
try {
|
||||
$statuses['descendants'][] = DI::mstdnStatus()->createFromUriId($descendant, $uid, $display_quotes);
|
||||
} catch (\Throwable $th) {
|
||||
$this->logger->info('Post not fetchable', ['uri-id' => $descendant, 'uid' => $uid, 'error' => $th]);
|
||||
}
|
||||
}
|
||||
|
||||
$this->jsonExit($statuses);
|
||||
|
|
|
@ -155,13 +155,12 @@ class Update extends BaseApi
|
|||
|
||||
Photo::setPermissionForResource($media[0]['resource-id'], $uid, $item['allow_cid'], $item['allow_gid'], $item['deny_cid'], $item['deny_gid']);
|
||||
|
||||
$phototypes = Images::supportedTypes();
|
||||
$ext = $phototypes[$media[0]['type']];
|
||||
$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,
|
||||
'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'] ?? '',
|
||||
|
@ -170,7 +169,7 @@ class Update extends BaseApi
|
|||
];
|
||||
|
||||
if (count($media) > 1) {
|
||||
$attachment['preview'] = DI::baseUrl() . '/photo/' . $media[1]['resource-id'] . '-' . $media[1]['scale'] . '.' . $ext;
|
||||
$attachment['preview'] = DI::baseUrl() . '/photo/' . $media[1]['resource-id'] . '-' . $media[1]['scale'] . $ext;
|
||||
$attachment['preview-width'] = $media[1]['width'];
|
||||
$attachment['preview-height'] = $media[1]['height'];
|
||||
}
|
||||
|
|
|
@ -65,11 +65,7 @@ class Attach extends BaseModule
|
|||
// error in Chrome for filenames with commas in them
|
||||
header('Content-type: ' . $item['filetype']);
|
||||
header('Content-length: ' . $item['filesize']);
|
||||
if (isset($_GET['attachment']) && $_GET['attachment'] === '0') {
|
||||
header('Content-disposition: filename="' . $item['filename'] . '"');
|
||||
} else {
|
||||
header('Content-disposition: attachment; filename="' . $item['filename'] . '"');
|
||||
}
|
||||
header('Content-disposition: attachment; filename="' . $item['filename'] . '"');
|
||||
|
||||
echo $data;
|
||||
System::exit();
|
||||
|
|
|
@ -104,7 +104,7 @@ abstract class BaseAdmin extends BaseModule
|
|||
'logsview' => ['admin/logs/view' , DI::l10n()->t('View Logs') , 'viewlogs'],
|
||||
]],
|
||||
'diagnostics' => [DI::l10n()->t('Diagnostics'), [
|
||||
'phpinfo' => ['admin/phpinfo' , DI::l10n()->t('PHP Info') , 'phpinfo'],
|
||||
'phpinfo' => ['admin/phpinfo?t=' . self::getFormSecurityToken('phpinfo'), DI::l10n()->t('PHP Info') , 'phpinfo'],
|
||||
'probe' => ['probe' , DI::l10n()->t('probe address') , 'probe'],
|
||||
'webfinger' => ['webfinger' , DI::l10n()->t('check webfinger') , 'webfinger'],
|
||||
'babel' => ['babel' , DI::l10n()->t('Babel') , 'babel'],
|
||||
|
|
|
@ -142,7 +142,8 @@ class API extends BaseModule
|
|||
{
|
||||
$eventId = !empty($request['event_id']) ? intval($request['event_id']) : 0;
|
||||
$uid = (int)$this->session->getLocalUserId();
|
||||
$cid = !empty($request['cid']) ? intval($request['cid']) : 0;
|
||||
// No overwriting event.cid on edit
|
||||
$cid = !empty($request['cid']) && !$eventId ? intval($request['cid']) : 0;
|
||||
|
||||
$strStartDateTime = Strings::escapeHtml($request['start_text'] ?? '');
|
||||
$strFinishDateTime = Strings::escapeHtml($request['finish_text'] ?? '');
|
||||
|
|
|
@ -27,11 +27,13 @@ use Friendica\Contact\LocalRelationship\Repository\LocalRelationship;
|
|||
use Friendica\Content\Conversation;
|
||||
use Friendica\Content\Nav;
|
||||
use Friendica\Content\Widget;
|
||||
use Friendica\Core\ACL;
|
||||
use Friendica\Core\L10n;
|
||||
use Friendica\Core\Protocol;
|
||||
use Friendica\Core\Session\Capability\IHandleUserSessions;
|
||||
use Friendica\Core\Theme;
|
||||
use Friendica\Model;
|
||||
use Friendica\Model\Contact as ModelContact;
|
||||
use Friendica\Module\Contact;
|
||||
use Friendica\Module\Response;
|
||||
use Friendica\Module\Security\Login;
|
||||
|
@ -105,12 +107,17 @@ class Conversations extends BaseModule
|
|||
$this->page->registerStylesheet(Theme::getPathForFile('js/friendica-tagsinput/friendica-tagsinput.css'));
|
||||
$this->page->registerStylesheet(Theme::getPathForFile('js/friendica-tagsinput/friendica-tagsinput-typeahead.css'));
|
||||
|
||||
$this->page['aside'] .= Widget\VCard::getHTML($contact);
|
||||
$this->page['aside'] .= Widget\VCard::getHTML($contact, true);
|
||||
|
||||
Nav::setSelected('contact');
|
||||
|
||||
// We need the editor here to be able to reshare an item.
|
||||
$o = $this->conversation->statusEditor([], 0, true);
|
||||
$options = [
|
||||
'lockstate' => ACL::getLockstateForUserId($this->userSession->getLocalUserId()) ? 'lock' : 'unlock',
|
||||
'acl' => ACL::getFullSelectorHTML($this->page, $this->userSession->getLocalUserId(), true, []),
|
||||
'bang' => '',
|
||||
'content' => ($contact['contact-type'] == ModelContact::TYPE_COMMUNITY ? '!' : '@') . ($contact['addr'] ?: $contact['url']),
|
||||
];
|
||||
$o = $this->conversation->statusEditor($options);
|
||||
|
||||
$o .= Contact::getTabsHTML($contact, Contact::TAB_CONVERSATIONS);
|
||||
$o .= Model\Contact::getThreadsFromId($contact['id'], $this->userSession->getLocalUserId(), 0, 0, $request['last_created'] ?? '');
|
||||
|
|
|
@ -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();
|
||||
$items = $this->getChannelItems($request);
|
||||
$order = 'created';
|
||||
} else {
|
||||
$items = $this->getCommunityItems();
|
||||
|
|
|
@ -65,8 +65,6 @@ class Network extends Timeline
|
|||
/** @var int */
|
||||
protected $circleId;
|
||||
/** @var string */
|
||||
protected $network;
|
||||
/** @var string */
|
||||
protected $dateFrom;
|
||||
/** @var string */
|
||||
protected $dateTo;
|
||||
|
@ -74,8 +72,6 @@ class Network extends Timeline
|
|||
protected $star;
|
||||
/** @var int */
|
||||
protected $mention;
|
||||
/** @var string */
|
||||
protected $order;
|
||||
|
||||
/** @var App */
|
||||
protected $app;
|
||||
|
@ -162,8 +158,6 @@ class Network extends Timeline
|
|||
|
||||
Nav::setSelected($this->args->get(0));
|
||||
|
||||
$content = '';
|
||||
|
||||
$default_permissions = [];
|
||||
if ($this->circleId) {
|
||||
$default_permissions['allow_gid'] = [$this->circleId];
|
||||
|
@ -195,7 +189,7 @@ class Network extends Timeline
|
|||
'lockstate' => $this->circleId || $this->network || ACL::getLockstateForUserId($this->session->getLocalUserId()) ? 'lock' : 'unlock',
|
||||
'acl' => ACL::getFullSelectorHTML($this->page, $this->session->getLocalUserId(), true, $default_permissions),
|
||||
'bang' => (($this->circleId || $this->network) ? '!' : ''),
|
||||
'content' => $content,
|
||||
'content' => '',
|
||||
];
|
||||
|
||||
$o .= $this->conversation->statusEditor($x);
|
||||
|
@ -217,7 +211,7 @@ class Network extends Timeline
|
|||
|
||||
try {
|
||||
if ($this->channel->isTimeline($this->selectedTab) || $this->userDefinedChannel->isTimeline($this->selectedTab, $this->session->getLocalUserId())) {
|
||||
$items = $this->getChannelItems();
|
||||
$items = $this->getChannelItems($request);
|
||||
} elseif ($this->community->isTimeline($this->selectedTab)) {
|
||||
$items = $this->getCommunityItems();
|
||||
} else {
|
||||
|
@ -362,24 +356,7 @@ class Network extends Timeline
|
|||
$this->dateFrom = $this->parameters['from'] ?? '';
|
||||
$this->dateTo = $this->parameters['to'] ?? '';
|
||||
|
||||
switch ($this->order) {
|
||||
case 'received':
|
||||
$this->maxId = $request['last_received'] ?? $this->maxId;
|
||||
$this->minId = $request['first_received'] ?? $this->minId;
|
||||
break;
|
||||
case 'created':
|
||||
$this->maxId = $request['last_created'] ?? $this->maxId;
|
||||
$this->minId = $request['first_created'] ?? $this->minId;
|
||||
break;
|
||||
case 'uriid':
|
||||
$this->maxId = $request['last_uriid'] ?? $this->maxId;
|
||||
$this->minId = $request['first_uriid'] ?? $this->minId;
|
||||
break;
|
||||
default:
|
||||
$this->order = 'commented';
|
||||
$this->maxId = $request['last_commented'] ?? $this->maxId;
|
||||
$this->minId = $request['first_commented'] ?? $this->minId;
|
||||
}
|
||||
$this->setMaxMinByOrder($request);
|
||||
}
|
||||
|
||||
protected function getItems()
|
||||
|
|
|
@ -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\UserDefinedChannel as UserDefinedChannelEntity;
|
||||
use Friendica\Content\Conversation\Repository\UserDefinedChannel;
|
||||
use Friendica\Core\Cache\Capability\ICanCache;
|
||||
use Friendica\Core\Cache\Enum\Duration;
|
||||
|
@ -41,7 +42,6 @@ use Friendica\Database\DBA;
|
|||
use Friendica\Model\Item;
|
||||
use Friendica\Model\Post;
|
||||
use Friendica\Model\Post\Engagement;
|
||||
use Friendica\Model\Verb;
|
||||
use Friendica\Module\Response;
|
||||
use Friendica\Protocol\Activity;
|
||||
use Friendica\Util\DateTimeFormat;
|
||||
|
@ -72,6 +72,10 @@ class Timeline extends BaseModule
|
|||
protected $update;
|
||||
/** @var bool */
|
||||
protected $raw;
|
||||
/** @var string */
|
||||
protected $order;
|
||||
/** @var string */
|
||||
protected $network;
|
||||
|
||||
/** @var App\Mode $mode */
|
||||
protected $mode;
|
||||
|
@ -138,6 +142,8 @@ class Timeline extends BaseModule
|
|||
$this->itemUriId = 0;
|
||||
}
|
||||
|
||||
$this->order = 'created';
|
||||
|
||||
$this->minId = $request['min_id'] ?? null;
|
||||
$this->maxId = $request['max_id'] ?? null;
|
||||
|
||||
|
@ -147,6 +153,28 @@ class Timeline extends BaseModule
|
|||
$this->raw = !empty($request['mode']) && ($request['mode'] == 'raw');
|
||||
}
|
||||
|
||||
protected function setMaxMinByOrder(array $request)
|
||||
{
|
||||
switch ($this->order) {
|
||||
case 'received':
|
||||
$this->maxId = $request['last_received'] ?? $this->maxId;
|
||||
$this->minId = $request['first_received'] ?? $this->minId;
|
||||
break;
|
||||
case 'created':
|
||||
$this->maxId = $request['last_created'] ?? $this->maxId;
|
||||
$this->minId = $request['first_created'] ?? $this->minId;
|
||||
break;
|
||||
case 'uriid':
|
||||
$this->maxId = $request['last_uriid'] ?? $this->maxId;
|
||||
$this->minId = $request['first_uriid'] ?? $this->minId;
|
||||
break;
|
||||
default:
|
||||
$this->order = 'commented';
|
||||
$this->maxId = $request['last_commented'] ?? $this->maxId;
|
||||
$this->minId = $request['first_commented'] ?? $this->minId;
|
||||
}
|
||||
}
|
||||
|
||||
protected function getNoSharerWidget(string $base): string
|
||||
{
|
||||
$path = $this->selectedTab;
|
||||
|
@ -204,9 +232,9 @@ class Timeline extends BaseModule
|
|||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function getChannelItems()
|
||||
protected function getChannelItems(array $request)
|
||||
{
|
||||
$items = $this->getRawChannelItems();
|
||||
$items = $this->getRawChannelItems($request);
|
||||
|
||||
$contacts = $this->database->selectToArray('user-contact', ['cid'], ['channel-frequency' => Contact\User::FREQUENCY_REDUCED, 'cid' => array_column($items, 'owner-id')]);
|
||||
$reduced = array_column($contacts, 'cid');
|
||||
|
@ -220,8 +248,8 @@ class Timeline extends BaseModule
|
|||
|
||||
while (count($selected_items) < $this->itemsPerPage && ++$count < 50 && count($items) > 0) {
|
||||
$maxposts = round((count($items) / $this->itemsPerPage) * $maxpostperauthor);
|
||||
$minId = $items[array_key_first($items)]['created'];
|
||||
$maxId = $items[array_key_last($items)]['created'];
|
||||
$minId = $items[array_key_first($items)][$this->order];
|
||||
$maxId = $items[array_key_last($items)][$this->order];
|
||||
|
||||
foreach ($items as $item) {
|
||||
if (!in_array($item['owner-id'], $reduced)) {
|
||||
|
@ -252,7 +280,7 @@ class Timeline extends BaseModule
|
|||
}
|
||||
|
||||
if (count($selected_items) < $this->itemsPerPage) {
|
||||
$items = $this->getRawChannelItems();
|
||||
$items = $this->getRawChannelItems($request);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -271,10 +299,12 @@ class Timeline extends BaseModule
|
|||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
private function getRawChannelItems()
|
||||
private function getRawChannelItems(array $request)
|
||||
{
|
||||
$uid = $this->session->getLocalUserId();
|
||||
|
||||
$table = 'post-engagement';
|
||||
|
||||
if ($this->selectedTab == ChannelEntity::WHATSHOT) {
|
||||
if (!is_null($this->accountType)) {
|
||||
$condition = ["(`comments` > ? OR `activities` > ?) AND `contact-type` = ?", $this->getMedianComments($uid, 4), $this->getMedianActivities($uid, 4), $this->accountType];
|
||||
|
@ -317,6 +347,13 @@ class Timeline extends BaseModule
|
|||
AND NOT `cid` IN (SELECT `cid` FROM `contact-relation` WHERE `follows` AND `relation-cid` = ?))",
|
||||
DateTimeFormat::utc('now - ' . $this->config->get('channel', 'sharer_interaction_days') . ' day'), $cid, $this->getMedianRelationThreadScore($cid, 4), $cid
|
||||
];
|
||||
} elseif ($this->selectedTab == ChannelEntity::QUIETSHARERS) {
|
||||
$cid = Contact::getPublicIdByUserId($uid);
|
||||
|
||||
$condition = [
|
||||
"`owner-id` IN (SELECT `cid` FROM `contact-relation` WHERE `follows` AND `relation-cid` = ? AND `post-score` <= ?)",
|
||||
$cid, $this->getMedianPostScore($cid, 2)
|
||||
];
|
||||
} elseif ($this->selectedTab == ChannelEntity::IMAGE) {
|
||||
$condition = ["`media-type` & ?", 1];
|
||||
} elseif ($this->selectedTab == ChannelEntity::VIDEO) {
|
||||
|
@ -324,48 +361,62 @@ class Timeline extends BaseModule
|
|||
} elseif ($this->selectedTab == ChannelEntity::AUDIO) {
|
||||
$condition = ["`media-type` & ?", 4];
|
||||
} elseif ($this->selectedTab == ChannelEntity::LANGUAGE) {
|
||||
$condition = ["JSON_EXTRACT(JSON_KEYS(language), '$[0]') = ?", User::getLanguageCode($uid)];
|
||||
} elseif (is_numeric($this->selectedTab)) {
|
||||
$condition = $this->getUserChannelConditions($this->selectedTab, $uid);
|
||||
$condition = ["`language` = ?", User::getLanguageCode($uid)];
|
||||
} 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';
|
||||
$condition = DBA::mergeConditions($condition, ['uid' => $uid]);
|
||||
$orders = ['-3' => 'created', '-4' => 'received', '-5' => 'commented'];
|
||||
$this->order = $orders[$channel->circle];
|
||||
}
|
||||
}
|
||||
|
||||
$this->setMaxMinByOrder($request);
|
||||
|
||||
if (!empty($this->network)) {
|
||||
$condition = DBA::mergeConditions($condition, ['network' => $this->network]);
|
||||
}
|
||||
|
||||
if (($this->selectedTab != ChannelEntity::LANGUAGE) && !is_numeric($this->selectedTab)) {
|
||||
$condition = $this->addLanguageCondition($uid, $condition);
|
||||
}
|
||||
|
||||
$condition = DBA::mergeConditions($condition, ["(NOT `restricted` OR EXISTS(SELECT `id` FROM `post-user` WHERE `uid` = ? AND `uri-id` = `post-engagement`.`uri-id`))", $uid]);
|
||||
$condition = DBA::mergeConditions($condition, ["(NOT `restricted` OR EXISTS(SELECT `id` FROM `post-user` WHERE `uid` = ? AND `uri-id` = `$table`.`uri-id`))", $uid]);
|
||||
|
||||
$condition = DBA::mergeConditions($condition, ["NOT EXISTS(SELECT `cid` FROM `user-contact` WHERE `uid` = ? AND `cid` = `post-engagement`.`owner-id` AND (`ignored` OR `blocked` OR `collapsed` OR `is-blocked` OR `channel-frequency` = ?))", $uid, Contact\User::FREQUENCY_NEVER]);
|
||||
$condition = DBA::mergeConditions($condition, ["NOT EXISTS(SELECT `cid` FROM `user-contact` WHERE `uid` = ? AND `cid` = `$table`.`owner-id` AND (`ignored` OR `blocked` OR `collapsed` OR `is-blocked` OR `channel-frequency` = ?))", $uid, Contact\User::FREQUENCY_NEVER]);
|
||||
|
||||
if (($this->selectedTab != ChannelEntity::WHATSHOT) && !is_null($this->accountType)) {
|
||||
$condition = DBA::mergeConditions($condition, ['contact-type' => $this->accountType]);
|
||||
}
|
||||
|
||||
$params = ['order' => ['created' => true], 'limit' => $this->itemsPerPage];
|
||||
$params = ['order' => [$this->order => true], 'limit' => $this->itemsPerPage];
|
||||
|
||||
if (!empty($this->itemUriId)) {
|
||||
$condition = DBA::mergeConditions($condition, ['uri-id' => $this->itemUriId]);
|
||||
} else {
|
||||
if ($this->noSharer) {
|
||||
$condition = DBA::mergeConditions($condition, ["NOT `uri-id` IN (SELECT `uri-id` FROM `post-user` WHERE `post-user`.`uid` = ? AND `post-user`.`uri-id` = `post-engagement`.`uri-id`)", $this->session->getLocalUserId()]);
|
||||
$condition = DBA::mergeConditions($condition, ["NOT `uri-id` IN (SELECT `uri-id` FROM `post-user` WHERE `post-user`.`uid` = ? AND `post-user`.`uri-id` = `$table`.`uri-id`)", $this->session->getLocalUserId()]);
|
||||
}
|
||||
|
||||
if (isset($this->maxId)) {
|
||||
$condition = DBA::mergeConditions($condition, ["`created` < ?", $this->maxId]);
|
||||
$condition = DBA::mergeConditions($condition, ["`$this->order` < ?", $this->maxId]);
|
||||
}
|
||||
|
||||
if (isset($this->minId)) {
|
||||
$condition = DBA::mergeConditions($condition, ["`created` > ?", $this->minId]);
|
||||
$condition = DBA::mergeConditions($condition, ["`$this->order` > ?", $this->minId]);
|
||||
|
||||
// Previous page case: we want the items closest to min_id but for that we need to reverse the query order
|
||||
if (!isset($this->maxId)) {
|
||||
$params['order']['created'] = false;
|
||||
$params['order'][$this->order] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$items = [];
|
||||
$result = $this->database->select('post-engagement', ['uri-id', 'created', 'owner-id', 'comments', 'activities'], $condition, $params);
|
||||
$fields = ['uri-id', 'owner-id', 'comments', 'activities'];
|
||||
$fields[] = $this->order;
|
||||
$result = $this->database->select($table, $fields, $condition, $params);
|
||||
if ($this->database->errorNo()) {
|
||||
throw new \Exception($this->database->errorMessage(), $this->database->errorNo());
|
||||
}
|
||||
|
@ -390,13 +441,8 @@ class Timeline extends BaseModule
|
|||
return $items;
|
||||
}
|
||||
|
||||
private function getUserChannelConditions(int $id, int $uid): array
|
||||
private function getUserChannelConditions(UserDefinedChannelEntity $channel, int $uid): array
|
||||
{
|
||||
$channel = $this->channelRepository->selectById($id, $uid);
|
||||
if (empty($channel)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$condition = [];
|
||||
|
||||
if (!empty($channel->circle)) {
|
||||
|
@ -404,47 +450,127 @@ class Timeline extends BaseModule
|
|||
$condition = ["`owner-id` IN (SELECT `pid` FROM `account-user-view` WHERE `uid` = ? AND `rel` IN (?, ?))", $uid, Contact::SHARING, Contact::FRIEND];
|
||||
} elseif ($channel->circle == -2) {
|
||||
$condition = ["`owner-id` IN (SELECT `pid` FROM `account-user-view` WHERE `uid` = ? AND `rel` = ?)", $uid, Contact::FOLLOWER];
|
||||
} elseif ($channel->circle == -3) {
|
||||
$condition = ["EXISTS(SELECT `uri-id` FROM `post-thread-user` WHERE `uid` = ? AND `post-thread-user`.`uri-id` = `post-engagement`.`uri-id`)", $uid];
|
||||
} elseif ($channel->circle > 0) {
|
||||
$condition = DBA::mergeConditions($condition, ["`owner-id` IN (SELECT `pid` FROM `group_member` INNER JOIN `account-user-view` ON `group_member`.`contact-id` = `account-user-view`.`id` WHERE `gid` = ? AND `account-user-view`.`uid` = ?)", $channel->circle, $uid]);
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($channel->fullTextSearch)) {
|
||||
$condition = DBA::mergeConditions($condition, ["MATCH (`searchtext`) AGAINST (? IN BOOLEAN MODE)", Engagement::escapeKeywords($channel->fullTextSearch)]);
|
||||
if (!empty($channel->includeTags)) {
|
||||
$additional = self:: addIncludeTags($channel->includeTags);
|
||||
} else {
|
||||
$additional = '';
|
||||
}
|
||||
|
||||
if (!empty($channel->excludeTags)) {
|
||||
foreach (explode(',', mb_strtolower($channel->excludeTags)) as $tag) {
|
||||
$additional .= ' -tag:' . $tag;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($channel->mediaType)) {
|
||||
$additional .= self::addMediaTerms($channel->mediaType);
|
||||
}
|
||||
|
||||
$additional .= self::addLanguageSearchTerms($uid, $channel->languages);
|
||||
|
||||
if ($additional) {
|
||||
$searchterms = '+(' . trim($channel->fullTextSearch) . ')' . $additional;
|
||||
} else {
|
||||
$searchterms = $channel->fullTextSearch;
|
||||
}
|
||||
|
||||
$condition = DBA::mergeConditions($condition, ["MATCH (`searchtext`) AGAINST (? IN BOOLEAN MODE)", Engagement::escapeKeywords($searchterms)]);
|
||||
} else {
|
||||
if (!empty($channel->includeTags)) {
|
||||
$search = explode(',', mb_strtolower($channel->includeTags));
|
||||
$placeholders = substr(str_repeat("?, ", count($search)), 0, -2);
|
||||
$condition = DBA::mergeConditions($condition, array_merge(["`uri-id` IN (SELECT `uri-id` FROM `post-tag` INNER JOIN `tag` ON `tag`.`id` = `post-tag`.`tid` WHERE `post-tag`.`type` = 1 AND `name` IN (" . $placeholders . "))"], $search));
|
||||
}
|
||||
|
||||
if (!empty($channel->excludeTags)) {
|
||||
$search = explode(',', mb_strtolower($channel->excludeTags));
|
||||
$placeholders = substr(str_repeat("?, ", count($search)), 0, -2);
|
||||
$condition = DBA::mergeConditions($condition, array_merge(["NOT `uri-id` IN (SELECT `uri-id` FROM `post-tag` INNER JOIN `tag` ON `tag`.`id` = `post-tag`.`tid` WHERE `post-tag`.`type` = 1 AND `name` IN (" . $placeholders . "))"], $search));
|
||||
}
|
||||
|
||||
if (!empty($channel->mediaType)) {
|
||||
$condition = DBA::mergeConditions($condition, ["`media-type` & ?", $channel->mediaType]);
|
||||
}
|
||||
|
||||
// For "addLanguageCondition" to work, the condition must not be empty
|
||||
$condition = $this->addLanguageCondition($uid, $condition ?: ["true"], $channel->languages);
|
||||
}
|
||||
|
||||
if (!empty($channel->includeTags)) {
|
||||
$search = explode(',', mb_strtolower($channel->includeTags));
|
||||
$placeholders = substr(str_repeat("?, ", count($search)), 0, -2);
|
||||
$condition = DBA::mergeConditions($condition, array_merge(["`uri-id` IN (SELECT `uri-id` FROM `post-tag` INNER JOIN `tag` ON `tag`.`id` = `post-tag`.`tid` WHERE `post-tag`.`type` = 1 AND `name` IN (" . $placeholders . "))"], $search));
|
||||
if (!is_null($channel->minSize)) {
|
||||
$condition = DBA::mergeConditions($condition, ["`size` >= ?", $channel->minSize]);
|
||||
}
|
||||
|
||||
if (!empty($channel->excludeTags)) {
|
||||
$search = explode(',', mb_strtolower($channel->excludeTags));
|
||||
$placeholders = substr(str_repeat("?, ", count($search)), 0, -2);
|
||||
$condition = DBA::mergeConditions($condition, array_merge(["NOT `uri-id` IN (SELECT `uri-id` FROM `post-tag` INNER JOIN `tag` ON `tag`.`id` = `post-tag`.`tid` WHERE `post-tag`.`type` = 1 AND `name` IN (" . $placeholders . "))"], $search));
|
||||
if (!is_null($channel->maxSize)) {
|
||||
$condition = DBA::mergeConditions($condition, ["`size` <= ?", $channel->maxSize]);
|
||||
}
|
||||
|
||||
if (!empty($channel->mediaType)) {
|
||||
$condition = DBA::mergeConditions($condition, ["`media-type` & ?", $channel->mediaType]);
|
||||
}
|
||||
|
||||
// For "addLanguageCondition" to work, the condition must not be empty
|
||||
$condition = $this->addLanguageCondition($uid, $condition ?: ["true"], $channel->languages);
|
||||
|
||||
return $condition;
|
||||
}
|
||||
|
||||
private function addIncludeTags(string $includeTags): string
|
||||
{
|
||||
$tagterms = '';
|
||||
foreach (explode(',', mb_strtolower($includeTags)) as $tag) {
|
||||
$tagterms .= ' tag:' . $tag;
|
||||
}
|
||||
|
||||
if ($tagterms) {
|
||||
return ' +(' . trim($tagterms) . ')';
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
private function addMediaTerms(int $mediaType): string
|
||||
{
|
||||
$mediaterms = '';
|
||||
if ($mediaType & 1) {
|
||||
$mediaterms .= ' media:image';
|
||||
}
|
||||
|
||||
if ($mediaType & 2) {
|
||||
$mediaterms .= ' media:video';
|
||||
}
|
||||
|
||||
if ($mediaType & 4) {
|
||||
$mediaterms .= ' media:audio';
|
||||
}
|
||||
|
||||
if ($mediaterms) {
|
||||
return ' +(' . trim($mediaterms) . ')';
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
private function addLanguageSearchTerms(int $uid, $languages = null): string
|
||||
{
|
||||
$langterms = '';
|
||||
foreach ($languages ?: User::getWantedLanguages($uid) as $language) {
|
||||
$langterms .= ' language:' . $language;
|
||||
}
|
||||
|
||||
if ($langterms) {
|
||||
return ' +(' . trim($langterms) . ')';
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
private function addLanguageCondition(int $uid, array $condition, $languages = null): array
|
||||
{
|
||||
$conditions = [];
|
||||
$languages = $languages ?: User::getWantedLanguages($uid);
|
||||
foreach ($languages as $language) {
|
||||
$conditions[] = "JSON_EXTRACT(JSON_KEYS(language), '$[0]') = ?";
|
||||
foreach ($languages ?: User::getWantedLanguages($uid) as $language) {
|
||||
$conditions[] = "`language` = ?";
|
||||
$condition[] = $language;
|
||||
}
|
||||
|
||||
if (!empty($conditions)) {
|
||||
$condition[0] .= " AND (" . implode(' OR ', $conditions) . ")";
|
||||
}
|
||||
|
@ -521,6 +647,28 @@ class Timeline extends BaseModule
|
|||
return $score;
|
||||
}
|
||||
|
||||
private function getMedianPostScore(int $cid, int $divider): int
|
||||
{
|
||||
$cache_key = 'Channel:getPostScore:' . $cid . ':' . $divider;
|
||||
$score = $this->cache->get($cache_key);
|
||||
if (!empty($score)) {
|
||||
return $score;
|
||||
}
|
||||
|
||||
$condition = ["`relation-cid` = ? AND `post-score` > ?", $cid, 0];
|
||||
|
||||
$limit = $this->database->count('contact-relation', $condition) / $divider;
|
||||
$relation = $this->database->selectToArray('contact-relation', ['post-score'], $condition, ['order' => ['post-score' => true], 'limit' => [$limit, 1]]);
|
||||
$score = $relation[0]['post-score'] ?? 0;
|
||||
if (empty($score)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$this->cache->set($cache_key, $score, Duration::HALF_HOUR);
|
||||
$this->logger->debug('Calculated median score', ['cid' => $cid, 'divider' => $divider, 'median' => $score]);
|
||||
return $score;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes the displayed items.
|
||||
*
|
||||
|
@ -601,6 +749,8 @@ class Timeline extends BaseModule
|
|||
*/
|
||||
private function selectItems()
|
||||
{
|
||||
$this->order = 'received';
|
||||
|
||||
if ($this->selectedTab == 'local') {
|
||||
$condition = ["`wall` AND `origin` AND `private` = ?", Item::PUBLIC];
|
||||
} elseif ($this->selectedTab == 'global') {
|
||||
|
|
|
@ -43,10 +43,11 @@ class Babel extends BaseModule
|
|||
}
|
||||
|
||||
$results = [];
|
||||
if (!empty($_REQUEST['text'])) {
|
||||
switch (($_REQUEST['type'] ?? '') ?: 'bbcode') {
|
||||
if (!empty($request['text'])) {
|
||||
self::checkFormSecurityTokenForbiddenOnError('babel');
|
||||
switch (($request['type'] ?? '') ?: 'bbcode') {
|
||||
case 'bbcode':
|
||||
$bbcode = $_REQUEST['text'];
|
||||
$bbcode = $request['text'];
|
||||
$results[] = [
|
||||
'title' => DI::l10n()->t('Source input'),
|
||||
'content' => visible_whitespace($bbcode)
|
||||
|
@ -136,7 +137,7 @@ class Babel extends BaseModule
|
|||
];
|
||||
break;
|
||||
case 'diaspora':
|
||||
$diaspora = trim($_REQUEST['text']);
|
||||
$diaspora = trim($request['text']);
|
||||
$results[] = [
|
||||
'title' => DI::l10n()->t('Source input (Diaspora format)'),
|
||||
'content' => visible_whitespace($diaspora),
|
||||
|
@ -144,7 +145,7 @@ class Babel extends BaseModule
|
|||
|
||||
$markdown = XML::unescape($diaspora);
|
||||
case 'markdown':
|
||||
$markdown = $markdown ?? trim($_REQUEST['text']);
|
||||
$markdown = $markdown ?? trim($request['text']);
|
||||
|
||||
$results[] = [
|
||||
'title' => DI::l10n()->t('Source input (Markdown)'),
|
||||
|
@ -169,7 +170,7 @@ class Babel extends BaseModule
|
|||
];
|
||||
break;
|
||||
case 'html' :
|
||||
$html = trim($_REQUEST['text']);
|
||||
$html = trim($request['text']);
|
||||
$results[] = [
|
||||
'title' => DI::l10n()->t('Raw HTML input'),
|
||||
'content' => visible_whitespace($html),
|
||||
|
@ -239,7 +240,7 @@ class Babel extends BaseModule
|
|||
];
|
||||
break;
|
||||
case 'twitter':
|
||||
$json = trim($_REQUEST['text']);
|
||||
$json = trim($request['text']);
|
||||
|
||||
if (file_exists('addon/twitter/twitter.php')) {
|
||||
require_once 'addon/twitter/twitter.php';
|
||||
|
@ -302,13 +303,14 @@ class Babel extends BaseModule
|
|||
$tpl = Renderer::getMarkupTemplate('babel.tpl');
|
||||
$o = Renderer::replaceMacros($tpl, [
|
||||
'$title' => DI::l10n()->t('Babel Diagnostic'),
|
||||
'$text' => ['text', DI::l10n()->t('Source text'), $_REQUEST['text'] ?? '', ''],
|
||||
'$type_bbcode' => ['type', DI::l10n()->t('BBCode'), 'bbcode', '', (($_REQUEST['type'] ?? '') ?: 'bbcode') == 'bbcode'],
|
||||
'$type_diaspora' => ['type', DI::l10n()->t('Diaspora'), 'diaspora', '', (($_REQUEST['type'] ?? '') ?: 'bbcode') == 'diaspora'],
|
||||
'$type_markdown' => ['type', DI::l10n()->t('Markdown'), 'markdown', '', (($_REQUEST['type'] ?? '') ?: 'bbcode') == 'markdown'],
|
||||
'$type_html' => ['type', DI::l10n()->t('HTML'), 'html', '', (($_REQUEST['type'] ?? '') ?: 'bbcode') == 'html'],
|
||||
'$form_security_token' => self::getFormSecurityToken('babel'),
|
||||
'$text' => ['text', DI::l10n()->t('Source text'), $request['text'] ?? '', ''],
|
||||
'$type_bbcode' => ['type', DI::l10n()->t('BBCode'), 'bbcode', '', (($request['type'] ?? '') ?: 'bbcode') == 'bbcode'],
|
||||
'$type_diaspora' => ['type', DI::l10n()->t('Diaspora'), 'diaspora', '', (($request['type'] ?? '') ?: 'bbcode') == 'diaspora'],
|
||||
'$type_markdown' => ['type', DI::l10n()->t('Markdown'), 'markdown', '', (($request['type'] ?? '') ?: 'bbcode') == 'markdown'],
|
||||
'$type_html' => ['type', DI::l10n()->t('HTML'), 'html', '', (($request['type'] ?? '') ?: 'bbcode') == 'html'],
|
||||
'$flag_twitter' => file_exists('addon/twitter/twitter.php'),
|
||||
'$type_twitter' => ['type', DI::l10n()->t('Twitter Source / Tweet URL (requires API key)'), 'twitter', '', (($_REQUEST['type'] ?? '') ?: 'bbcode') == 'twitter'],
|
||||
'$type_twitter' => ['type', DI::l10n()->t('Twitter Source / Tweet URL (requires API key)'), 'twitter', '', (($request['type'] ?? '') ?: 'bbcode') == 'twitter'],
|
||||
'$results' => $results,
|
||||
'$submit' => DI::l10n()->t('Submit'),
|
||||
]);
|
||||
|
|
|
@ -99,8 +99,7 @@ class Browser extends BaseModule
|
|||
|
||||
protected function map_files(array $record): array
|
||||
{
|
||||
$types = Images::supportedTypes();
|
||||
$ext = $types[$record['type']];
|
||||
$ext = Images::getExtensionByMimeType($record['type']);
|
||||
$filename_e = $record['filename'];
|
||||
|
||||
// Take the largest picture that is smaller or equal 640 pixels
|
||||
|
@ -118,7 +117,7 @@ class Browser extends BaseModule
|
|||
return [
|
||||
sprintf('%s/photos/%s/image/%s', $this->baseUrl, $this->app->getLoggedInUserNickname(), $record['resource-id']),
|
||||
$filename_e,
|
||||
sprintf('%s/photo/%s-%s.%s', $this->baseUrl, $record['resource-id'], $scale, $ext),
|
||||
sprintf('%s/photo/%s-%s%s', $this->baseUrl, $record['resource-id'], $scale, $ext),
|
||||
$record['desc'],
|
||||
];
|
||||
}
|
||||
|
|
|
@ -135,8 +135,6 @@ class Upload extends \Friendica\BaseModule
|
|||
$this->return(401, $this->t('Invalid request.'), true);
|
||||
}
|
||||
|
||||
$filetype = Images::getMimeTypeBySource($src, $filename, $filetype);
|
||||
|
||||
$this->logger->info('File upload:', [
|
||||
'src' => $src,
|
||||
'filename' => $filename,
|
||||
|
@ -145,7 +143,7 @@ class Upload extends \Friendica\BaseModule
|
|||
]);
|
||||
|
||||
$imagedata = @file_get_contents($src);
|
||||
$image = new Image($imagedata, $filetype);
|
||||
$image = new Image($imagedata, $filetype, $filename);
|
||||
|
||||
if (!$image->isValid()) {
|
||||
@unlink($src);
|
||||
|
|
|
@ -57,11 +57,11 @@ class Index extends BaseModeration
|
|||
|
||||
// Edit the entries from blocklist
|
||||
$blocklist = [];
|
||||
foreach ($request['domain'] as $id => $domain) {
|
||||
foreach ((array)$request['domain'] as $id => $domain) {
|
||||
// Trimming whitespaces as well as any lingering slashes
|
||||
$domain = trim($domain);
|
||||
$reason = trim($request['reason'][$id]);
|
||||
if (empty($request['delete'][$id])) {
|
||||
if (empty($request['delete'][$id]) && !empty($domain)) {
|
||||
$blocklist[] = [
|
||||
'domain' => $domain,
|
||||
'reason' => $reason
|
||||
|
|
|
@ -21,10 +21,7 @@
|
|||
|
||||
namespace Friendica\Module\OAuth;
|
||||
|
||||
use Friendica\Core\Logger;
|
||||
use Friendica\Core\System;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\DI;
|
||||
use Friendica\Module\BaseApi;
|
||||
use Friendica\Module\Special\HTTPException;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
|
|
@ -22,15 +22,12 @@
|
|||
namespace Friendica\Module\OAuth;
|
||||
|
||||
use Friendica\Core\Logger;
|
||||
use Friendica\Core\System;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\DI;
|
||||
use Friendica\Model\User;
|
||||
use Friendica\Module\BaseApi;
|
||||
use Friendica\Module\Special\HTTPException;
|
||||
use Friendica\Security\OAuth;
|
||||
use Friendica\Util\DateTimeFormat;
|
||||
use GuzzleHttp\Psr7\Uri;
|
||||
use Psr\Http\Message\ResponseInterface;
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,74 +0,0 @@
|
|||
<?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\BaseModule;
|
||||
use Friendica\Content;
|
||||
use Friendica\Core\System;
|
||||
use Friendica\DI;
|
||||
use Friendica\Util\Strings;
|
||||
|
||||
/**
|
||||
* Oembed module
|
||||
*
|
||||
* Displays stored embed content based on a base64 hash of a remote URL
|
||||
*
|
||||
* Example: /oembed/aHR0cHM6Ly9...
|
||||
*
|
||||
* @author Hypolite Petovan <hypolite@mrpetovan.com>
|
||||
*/
|
||||
class Oembed extends BaseModule
|
||||
{
|
||||
protected function content(array $request = []): string
|
||||
{
|
||||
// Unused form: /oembed/b2h?url=...
|
||||
if (DI::args()->getArgv()[1] == 'b2h') {
|
||||
$url = ["", trim(hex2bin($_REQUEST['url']))];
|
||||
echo Content\OEmbed::replaceCallback($url);
|
||||
System::exit();
|
||||
}
|
||||
|
||||
// Unused form: /oembed/h2b?text=...
|
||||
if (DI::args()->getArgv()[1] == 'h2b') {
|
||||
$text = trim(hex2bin($_REQUEST['text']));
|
||||
echo Content\OEmbed::HTML2BBCode($text);
|
||||
System::exit();
|
||||
}
|
||||
|
||||
// @TODO: Replace with parameter from router
|
||||
if (DI::args()->getArgc() == 2) {
|
||||
echo '<html><body>';
|
||||
$url = Strings::base64UrlDecode(DI::args()->getArgv()[1]);
|
||||
$j = Content\OEmbed::fetchURL($url);
|
||||
|
||||
// workaround for media.ccc.de (and any other endpoint that return size 0)
|
||||
if (substr($j->html, 0, 7) == "<iframe" && strstr($j->html, 'width="0"')) {
|
||||
$j->html = '<style>html,body{margin:0;padding:0;} iframe{width:100%;height:100%;}</style>' . $j->html;
|
||||
$j->html = str_replace('width="0"', '', $j->html);
|
||||
$j->html = str_replace('height="0"', '', $j->html);
|
||||
}
|
||||
echo $j->html;
|
||||
echo '</body></html>';
|
||||
}
|
||||
System::exit();
|
||||
}
|
||||
}
|
|
@ -100,7 +100,6 @@ class Photo extends BaseApi
|
|||
$id = $account['id'];
|
||||
}
|
||||
|
||||
// Contact Id Fallback, to remove after version 2021.12
|
||||
if (isset($this->parameters['contact_id'])) {
|
||||
$id = intval($this->parameters['contact_id']);
|
||||
}
|
||||
|
@ -115,12 +114,6 @@ class Photo extends BaseApi
|
|||
$id = $user['uid'];
|
||||
}
|
||||
|
||||
// User Id Fallback, to remove after version 2021.12
|
||||
if (!empty($this->parameters['uid_ext'])) {
|
||||
$id = intval(pathinfo($this->parameters['uid_ext'], PATHINFO_FILENAME));
|
||||
}
|
||||
|
||||
// Please refactor this for the love of everything that's good
|
||||
if (isset($this->parameters['id'])) {
|
||||
$id = $this->parameters['id'];
|
||||
}
|
||||
|
@ -166,13 +159,17 @@ class Photo extends BaseApi
|
|||
|
||||
$stamp = microtime(true);
|
||||
|
||||
$imgdata = MPhoto::getImageDataForPhoto($photo);
|
||||
if (empty($request['blur']) || empty($photo['blurhash'])) {
|
||||
$imgdata = MPhoto::getImageDataForPhoto($photo);
|
||||
$mimetype = $photo['type'];
|
||||
}
|
||||
if (empty($imgdata) && empty($photo['blurhash'])) {
|
||||
throw new HTTPException\NotFoundException();
|
||||
} elseif (empty($imgdata) && !empty($photo['blurhash'])) {
|
||||
$image = New Image('', 'image/png');
|
||||
$image = New Image('', image_type_to_mime_type(IMAGETYPE_WEBP));
|
||||
$image->getFromBlurHash($photo['blurhash'], $photo['width'], $photo['height']);
|
||||
$imgdata = $image->asString();
|
||||
$imgdata = $image->asString();
|
||||
$mimetype = $image->getType();
|
||||
}
|
||||
|
||||
// The mimetype for an external or system resource can only be known reliably after it had been fetched
|
||||
|
@ -197,20 +194,23 @@ class Photo extends BaseApi
|
|||
}
|
||||
|
||||
if (!empty($request['static'])) {
|
||||
$img = new Image($imgdata, $photo['type']);
|
||||
$img = new Image($imgdata, $photo['type'], $photo['filename']);
|
||||
$img->toStatic();
|
||||
$imgdata = $img->asString();
|
||||
$imgdata = $img->asString();
|
||||
$mimetype = $img->getType();
|
||||
}
|
||||
|
||||
// if customsize is set and image is not a gif, resize it
|
||||
if ($photo['type'] !== 'image/gif' && $customsize > 0 && $customsize <= Proxy::PIXEL_THUMB && $square_resize) {
|
||||
$img = new Image($imgdata, $photo['type']);
|
||||
// if customsize is set and image resizing is supported for this image, resize it
|
||||
if (Images::canResize($photo['type']) && $customsize > 0 && $customsize <= Proxy::PIXEL_THUMB && $square_resize) {
|
||||
$img = new Image($imgdata, $photo['type'], $photo['filename']);
|
||||
$img->scaleToSquare($customsize);
|
||||
$imgdata = $img->asString();
|
||||
} elseif ($photo['type'] !== 'image/gif' && $customsize > 0) {
|
||||
$img = new Image($imgdata, $photo['type']);
|
||||
$imgdata = $img->asString();
|
||||
$mimetype = $img->getType();
|
||||
} elseif (Images::canResize($photo['type']) && $customsize > 0) {
|
||||
$img = new Image($imgdata, $photo['type'], $photo['filename']);
|
||||
$img->scaleDown($customsize);
|
||||
$imgdata = $img->asString();
|
||||
$imgdata = $img->asString();
|
||||
$mimetype = $img->getType();
|
||||
}
|
||||
|
||||
if (function_exists('header_remove')) {
|
||||
|
@ -218,7 +218,7 @@ class Photo extends BaseApi
|
|||
header_remove('pragma');
|
||||
}
|
||||
|
||||
header('Content-type: ' . $photo['type']);
|
||||
header('Content-type: ' . $mimetype);
|
||||
|
||||
$stamp = microtime(true);
|
||||
if (!$cacheable) {
|
||||
|
@ -389,7 +389,7 @@ class Photo extends BaseApi
|
|||
}
|
||||
}
|
||||
if (empty($mimetext) && !empty($contact['blurhash'])) {
|
||||
$image = New Image('', 'image/png');
|
||||
$image = New Image('', image_type_to_mime_type(IMAGETYPE_WEBP));
|
||||
$image->getFromBlurHash($contact['blurhash'], $customsize, $customsize);
|
||||
return MPhoto::createPhotoForImageData($image->asString());
|
||||
} elseif (empty($mimetext)) {
|
||||
|
|
|
@ -184,8 +184,6 @@ class Photos extends \Friendica\Module\BaseProfile
|
|||
return;
|
||||
}
|
||||
|
||||
$type = Images::getMimeTypeBySource($src, $filename, $type);
|
||||
|
||||
$this->logger->info('photos: upload: received file: ' . $filename . ' as ' . $src . ' ('. $type . ') ' . $filesize . ' bytes');
|
||||
|
||||
$maximagesize = Strings::getBytesFromShorthand($this->config->get('system', 'maximagesize'));
|
||||
|
@ -210,7 +208,7 @@ class Photos extends \Friendica\Module\BaseProfile
|
|||
|
||||
$imagedata = @file_get_contents($src);
|
||||
|
||||
$image = new Image($imagedata, $type);
|
||||
$image = new Image($imagedata, $type, $filename);
|
||||
|
||||
if (!$image->isValid()) {
|
||||
$this->logger->notice('unable to process image');
|
||||
|
@ -341,14 +339,12 @@ class Photos extends \Friendica\Module\BaseProfile
|
|||
$pager->getItemsPerPage()
|
||||
));
|
||||
|
||||
$phototypes = Images::supportedTypes();
|
||||
|
||||
$photos = array_map(function ($photo) use ($phototypes) {
|
||||
$photos = array_map(function ($photo){
|
||||
return [
|
||||
'id' => $photo['id'],
|
||||
'link' => 'photos/' . $this->owner['nickname'] . '/image/' . $photo['resource-id'],
|
||||
'title' => $this->t('View Photo'),
|
||||
'src' => 'photo/' . $photo['resource-id'] . '-' . ((($photo['scale']) == 6) ? 4 : $photo['scale']) . '.' . $phototypes[$photo['type']],
|
||||
'src' => 'photo/' . $photo['resource-id'] . '-' . ((($photo['scale']) == 6) ? 4 : $photo['scale']) . Images::getExtensionByMimeType($photo['type']),
|
||||
'alt' => $photo['filename'],
|
||||
'album' => [
|
||||
'link' => 'photos/' . $this->owner['nickname'] . '/album/' . bin2hex($photo['album']),
|
||||
|
|
|
@ -226,7 +226,7 @@ class Profile extends BaseProfile
|
|||
// Separator is defined in Module\Settings\Profile\Index::cleanKeywords
|
||||
foreach (explode(', ', $profile['pub_keywords']) as $tag_label) {
|
||||
$tags[] = [
|
||||
'url' => '/search?tag=' . $tag_label,
|
||||
'url' => '/search?tag=' . urlencode($tag_label),
|
||||
'label' => Tag::TAG_CHARACTER[Tag::HASHTAG] . $tag_label,
|
||||
];
|
||||
}
|
||||
|
|
|
@ -99,17 +99,15 @@ class Proxy extends BaseModule
|
|||
|
||||
Logger::debug('Got picture', ['Content-Type' => $fetchResult->getHeader('Content-Type'), 'uid' => DI::userSession()->getLocalUserId(), 'image' => $request['url']]);
|
||||
|
||||
$mime = Images::getMimeTypeByData($img_str);
|
||||
|
||||
$image = new Image($img_str, $mime);
|
||||
$image = new Image($img_str, $fetchResult->getContentType(), $request['url']);
|
||||
if (!$image->isValid()) {
|
||||
Logger::notice('The image is invalid', ['image' => $request['url'], 'mime' => $mime]);
|
||||
Logger::notice('The image is invalid', ['image' => $request['url'], 'mime' => $fetchResult->getContentType()]);
|
||||
self::responseError();
|
||||
// stop.
|
||||
}
|
||||
|
||||
// reduce quality - if it isn't a GIF
|
||||
if ($image->getType() != 'image/gif') {
|
||||
// reduce quality - if it is supported for this image type
|
||||
if (Images::canResize($image->getType())) {
|
||||
$image->scaleDown($request['size']);
|
||||
}
|
||||
|
||||
|
|
|
@ -284,7 +284,19 @@ class Register extends BaseModule
|
|||
$regdata = ['email' => $arr['email'], 'nickname' => $arr['nickname'], 'username' => $arr['username']];
|
||||
DI::baseUrl()->redirect('register?' . http_build_query($regdata));
|
||||
}
|
||||
|
||||
|
||||
//Check if nickname contains only US-ASCII and do not start with a digit
|
||||
if (!preg_match('/^[a-zA-Z][a-zA-Z0-9]*$/', $arr['nickname'])) {
|
||||
if (is_numeric(substr($arr['nickname'], 0, 1))) {
|
||||
DI::sysmsg()->addNotice(DI::l10n()->t("Nickname cannot start with a digit."));
|
||||
} else {
|
||||
DI::sysmsg()->addNotice(DI::l10n()->t("Nickname can only contain US-ASCII characters."));
|
||||
}
|
||||
$regdata = ['email' => $arr['email'], 'nickname' => $arr['nickname'], 'username' => $arr['username']];
|
||||
DI::baseUrl()->redirect('register?' . http_build_query($regdata));
|
||||
return;
|
||||
}
|
||||
|
||||
$arr['blocked'] = $blocked;
|
||||
$arr['verified'] = $verified;
|
||||
$arr['language'] = L10n::detectLanguage($_SERVER, $_GET, DI::config()->get('system', 'language'));
|
||||
|
|
|
@ -83,6 +83,8 @@ class Channels extends BaseSettings
|
|||
'circle' => (int)$request['new_circle'],
|
||||
'include-tags' => Strings::cleanTags($request['new_include_tags']),
|
||||
'exclude-tags' => Strings::cleanTags($request['new_exclude_tags']),
|
||||
'min-size' => $request['new_min_size'] != '' ? (int)$request['new_min_size'] : null,
|
||||
'max-size' => $request['new_max_size'] != '' ? (int)$request['new_max_size'] : null,
|
||||
'full-text-search' => $request['new_text_search'],
|
||||
'media-type' => ($request['new_image'] ? 1 : 0) | ($request['new_video'] ? 2 : 0) | ($request['new_audio'] ? 4 : 0),
|
||||
'languages' => $request['new_languages'],
|
||||
|
@ -99,7 +101,7 @@ class Channels extends BaseSettings
|
|||
continue;
|
||||
}
|
||||
|
||||
if (!array_diff((array)$request['languages'][$id], $channel_languages)) {
|
||||
if (!array_diff((array)$request['languages'][$id], $channel_languages) && (count((array)$request['languages'][$id]) == count($channel_languages))) {
|
||||
$request['languages'][$id] = null;
|
||||
}
|
||||
|
||||
|
@ -112,6 +114,8 @@ class Channels extends BaseSettings
|
|||
'circle' => (int)$request['circle'][$id],
|
||||
'include-tags' => Strings::cleanTags($request['include_tags'][$id]),
|
||||
'exclude-tags' => Strings::cleanTags($request['exclude_tags'][$id]),
|
||||
'min-size' => $request['min_size'][$id] != '' ? (int)$request['min_size'][$id] : null,
|
||||
'max-size' => $request['max_size'][$id] != '' ? (int)$request['max_size'][$id] : null,
|
||||
'full-text-search' => $request['text_search'][$id],
|
||||
'media-type' => ($request['image'][$id] ? 1 : 0) | ($request['video'][$id] ? 2 : 0) | ($request['audio'][$id] ? 4 : 0),
|
||||
'languages' => $request['languages'][$id],
|
||||
|
@ -143,7 +147,9 @@ class Channels extends BaseSettings
|
|||
$intro = $this->t('This page can be used to define your own channels.');
|
||||
$circles = [
|
||||
0 => $this->l10n->t('Global Community'),
|
||||
-3 => $this->l10n->t('Network'),
|
||||
-5 => $this->l10n->t('Latest Activity'),
|
||||
-4 => $this->l10n->t('Latest Posts'),
|
||||
-3 => $this->l10n->t('Latest Creation'),
|
||||
-1 => $this->l10n->t('Following'),
|
||||
-2 => $this->l10n->t('Followers'),
|
||||
];
|
||||
|
@ -181,6 +187,8 @@ class Channels extends BaseSettings
|
|||
'circle' => ["circle[$channel->code]", $this->t('Circle/Channel'), $channel->circle, '', $circles],
|
||||
'include_tags' => ["include_tags[$channel->code]", $this->t("Include Tags"), str_replace(',', ', ', $channel->includeTags)],
|
||||
'exclude_tags' => ["exclude_tags[$channel->code]", $this->t("Exclude Tags"), str_replace(',', ', ', $channel->excludeTags)],
|
||||
'min_size' => ["min_size[$channel->code]", $this->t("Minimum Size"), $channel->minSize],
|
||||
'max_size' => ["max_size[$channel->code]", $this->t("Maximum Size"), $channel->maxSize],
|
||||
'text_search' => ["text_search[$channel->code]", $this->t("Full Text Search"), $channel->fullTextSearch],
|
||||
'image' => ["image[$channel->code]", $this->t("Images"), $channel->mediaType & 1],
|
||||
'video' => ["video[$channel->code]", $this->t("Videos"), $channel->mediaType & 2],
|
||||
|
@ -200,6 +208,8 @@ class Channels extends BaseSettings
|
|||
'circle' => ['new_circle', $this->t('Circle/Channel'), 0, $this->t('Select a circle or channel, that your channel should be based on.'), $circles],
|
||||
'include_tags' => ["new_include_tags", $this->t("Include Tags"), '', $this->t('Comma separated list of tags. A post will be used when it contains any of the listed tags.')],
|
||||
'exclude_tags' => ["new_exclude_tags", $this->t("Exclude Tags"), '', $this->t('Comma separated list of tags. If a post contain any of these tags, then it will not be part of nthis channel.')],
|
||||
'min_size' => ["new_min_size", $this->t("Minimum Size"), '', $this->t('Minimum post size. Leave empty for no minimum size. The size is calculated without links, attached posts, mentions or hashtags.')],
|
||||
'max_size' => ["new_max_size", $this->t("Maximum Size"), '', $this->t('Maximum post size. Leave empty for no maximum size. The size is calculated without links, attached posts, mentions or hashtags.')],
|
||||
'text_search' => ["new_text_search", $this->t("Full Text Search"), '', $this->t('Search terms for the body, supports the "boolean mode" operators from MariaDB. See the help for a complete list of operators and additional keywords: %s', '<a href="help/Channels">help/Channels</a>')],
|
||||
'image' => ['new_image', $this->t("Images"), false, $this->t("Check to display images in the channel.")],
|
||||
'video' => ["new_video", $this->t("Videos"), false, $this->t("Check to display videos in the channel.")],
|
||||
|
|
|
@ -106,6 +106,7 @@ class Display extends BaseSettings
|
|||
$enable_smart_threading = (bool)$request['enable_smart_threading'];
|
||||
$enable_dislike = (bool)$request['enable_dislike'];
|
||||
$display_resharer = (bool)$request['display_resharer'];
|
||||
$display_sensitive = (bool)$request['display_sensitive'];
|
||||
$stay_local = (bool)$request['stay_local'];
|
||||
$show_page_drop = (bool)$request['show_page_drop'];
|
||||
$display_eventlist = (bool)$request['display_eventlist'];
|
||||
|
@ -157,6 +158,7 @@ class Display extends BaseSettings
|
|||
$this->pConfig->set($uid, 'system', 'no_smart_threading' , !$enable_smart_threading);
|
||||
$this->pConfig->set($uid, 'system', 'hide_dislike' , !$enable_dislike);
|
||||
$this->pConfig->set($uid, 'system', 'display_resharer' , $display_resharer);
|
||||
$this->pConfig->set($uid, 'system', 'display_sensitive' , $display_sensitive);
|
||||
$this->pConfig->set($uid, 'system', 'stay_local' , $stay_local);
|
||||
$this->pConfig->set($uid, 'system', 'show_page_drop' , $show_page_drop);
|
||||
$this->pConfig->set($uid, 'system', 'display_eventlist' , $display_eventlist);
|
||||
|
@ -251,6 +253,7 @@ class Display extends BaseSettings
|
|||
$enable_smart_threading = !$this->pConfig->get($uid, 'system', 'no_smart_threading', false);
|
||||
$enable_dislike = !$this->pConfig->get($uid, 'system', 'hide_dislike', false);
|
||||
$display_resharer = $this->pConfig->get($uid, 'system', 'display_resharer', false);
|
||||
$display_sensitive = $this->pConfig->get($uid, 'system', 'display_sensitive', false);
|
||||
$stay_local = $this->pConfig->get($uid, 'system', 'stay_local', false);
|
||||
$show_page_drop = $this->pConfig->get($uid, 'system', 'show_page_drop', true);
|
||||
$display_eventlist = $this->pConfig->get($uid, 'system', 'display_eventlist', true);
|
||||
|
@ -330,6 +333,7 @@ class Display extends BaseSettings
|
|||
'$enable_smart_threading' => ['enable_smart_threading' , $this->t('Enable Smart Threading'), $enable_smart_threading, $this->t('Enable the automatic suppression of extraneous thread indentation.')],
|
||||
'$enable_dislike' => ['enable_dislike' , $this->t('Display the Dislike feature'), $enable_dislike, $this->t('Display the Dislike button and dislike reactions on posts and comments.')],
|
||||
'$display_resharer' => ['display_resharer' , $this->t('Display the resharer'), $display_resharer, $this->t('Display the first resharer as icon and text on a reshared item.')],
|
||||
'$display_sensitive' => ['display_sensitive' , $this->t('Display sensitive content'), $display_sensitive, $this->t('If enabled, pictures in posts marked as "sensitive" will not be blurred.')],
|
||||
'$stay_local' => ['stay_local' , $this->t('Stay local'), $stay_local, $this->t("Don't go to a remote system when following a contact link.")],
|
||||
'$show_page_drop' => ['show_page_drop' , $this->t('Show the post deletion checkbox'), $show_page_drop, $this->t("Display the checkbox for the post deletion on the network page.")],
|
||||
'$display_eventlist' => ['display_eventlist' , $this->t('DIsplay the event list'), $display_eventlist, $this->t("Display the birthday reminder and event list on the network page.")],
|
||||
|
|
|
@ -213,7 +213,7 @@ class Crop extends BaseSettings
|
|||
|
||||
DI::page()['htmlhead'] .= Renderer::replaceMacros(Renderer::getMarkupTemplate('settings/profile/photo/crop_head.tpl'), []);
|
||||
|
||||
$filename = $imagecrop['resource-id'] . '-' . $imagecrop['scale'] . '.' . $imagecrop['ext'];
|
||||
$filename = $imagecrop['resource-id'] . '-' . $imagecrop['scale'] . $imagecrop['ext'];
|
||||
$tpl = Renderer::getMarkupTemplate('settings/profile/photo/crop.tpl');
|
||||
$o = Renderer::replaceMacros($tpl, [
|
||||
'$filename' => $filename,
|
||||
|
|
|
@ -52,8 +52,6 @@ class Index extends BaseSettings
|
|||
$filesize = intval($_FILES['userfile']['size']);
|
||||
$filetype = $_FILES['userfile']['type'];
|
||||
|
||||
$filetype = Images::getMimeTypeBySource($src, $filename, $filetype);
|
||||
|
||||
$maximagesize = Strings::getBytesFromShorthand(DI::config()->get('system', 'maximagesize', 0));
|
||||
|
||||
if ($maximagesize && $filesize > $maximagesize) {
|
||||
|
@ -63,7 +61,7 @@ class Index extends BaseSettings
|
|||
}
|
||||
|
||||
$imagedata = @file_get_contents($src);
|
||||
$Image = new Image($imagedata, $filetype);
|
||||
$Image = new Image($imagedata, $filetype, $filename);
|
||||
|
||||
if (!$Image->isValid()) {
|
||||
DI::sysmsg()->addNotice(DI::l10n()->t('Unable to process image.'));
|
||||
|
|
|
@ -29,7 +29,6 @@ use Friendica\Core\Session\Capability\IHandleUserSessions;
|
|||
use Friendica\Core\System;
|
||||
use Friendica\Database\DBA;
|
||||
use Friendica\Database\Definition\DbaDefinition;
|
||||
use Friendica\DI;
|
||||
use Friendica\Model\Contact;
|
||||
use Friendica\Model\Item;
|
||||
use Friendica\Model\Post;
|
||||
|
@ -47,8 +46,7 @@ use Psr\Log\LoggerInterface;
|
|||
**/
|
||||
class UserExport extends BaseSettings
|
||||
{
|
||||
/** @var DbaDefinition */
|
||||
private $dbaDefinition;
|
||||
private DbaDefinition $dbaDefinition;
|
||||
|
||||
public function __construct(DbaDefinition $dbaDefinition, IHandleUserSessions $session, App\Page $page, L10n $l10n, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, array $server, array $parameters = [])
|
||||
{
|
||||
|
@ -86,10 +84,12 @@ class UserExport extends BaseSettings
|
|||
* options shown on "Export personal data" page
|
||||
* list of array( 'link url', 'link text', 'help text' )
|
||||
*/
|
||||
|
||||
$t = self::getFormSecurityToken('userexport');
|
||||
$options = [
|
||||
['settings/userexport/account', $this->l10n->t('Export account'), $this->l10n->t('Export your account info and contacts. Use this to make a backup of your account and/or to move it to another server.')],
|
||||
['settings/userexport/backup', $this->l10n->t('Export all'), $this->l10n->t('Export your account info, contacts and all your items as json. Could be a very big file, and could take a lot of time. Use this to make a full backup of your account (photos are not exported)')],
|
||||
['settings/userexport/contact', $this->l10n->t('Export Contacts to CSV'), $this->l10n->t('Export the list of the accounts you are following as CSV file. Compatible to e.g. Mastodon.')],
|
||||
['settings/userexport/account?t=' . $t, $this->l10n->t('Export account'), $this->l10n->t('Export your account info and contacts. Use this to make a backup of your account and/or to move it to another server.')],
|
||||
['settings/userexport/backup?t=' . $t, $this->l10n->t('Export all'), $this->l10n->t('Export your account info, contacts and all your items as json. Could be a very big file, and could take a lot of time. Use this to make a full backup of your account (photos are not exported)')],
|
||||
['settings/userexport/contact?t=' . $t, $this->l10n->t('Export Contacts to CSV'), $this->l10n->t('Export the list of the accounts you are following as CSV file. Compatible to e.g. Mastodon.')],
|
||||
];
|
||||
Hook::callAll('uexport_options', $options);
|
||||
|
||||
|
@ -115,20 +115,21 @@ class UserExport extends BaseSettings
|
|||
}
|
||||
|
||||
if (isset($this->parameters['action'])) {
|
||||
self::checkFormSecurityTokenForbiddenOnError('userexport', 't');
|
||||
switch ($this->parameters['action']) {
|
||||
case 'backup':
|
||||
header('Content-type: application/json');
|
||||
header('Content-Disposition: attachment; filename="' . DI::app()->getLoggedInUserNickname() . '.' . $this->parameters['action'] . '"');
|
||||
header('Content-Disposition: attachment; filename="' . $this->session->getLocalUserNickname() . '.' . $this->parameters['action'] . '"');
|
||||
$this->echoAll($this->session->getLocalUserId());
|
||||
break;
|
||||
case 'account':
|
||||
header('Content-type: application/json');
|
||||
header('Content-Disposition: attachment; filename="' . DI::app()->getLoggedInUserNickname() . '.' . $this->parameters['action'] . '"');
|
||||
header('Content-Disposition: attachment; filename="' . $this->session->getLocalUserNickname() . '.' . $this->parameters['action'] . '"');
|
||||
$this->echoAccount($this->session->getLocalUserId());
|
||||
break;
|
||||
case 'contact':
|
||||
header('Content-type: application/csv');
|
||||
header('Content-Disposition: attachment; filename="' . DI::app()->getLoggedInUserNickname() . '-contacts.csv' . '"');
|
||||
header('Content-Disposition: attachment; filename="' . $this->session->getLocalUserNickname() . '-contacts.csv' . '"');
|
||||
$this->echoContactsAsCSV($this->session->getLocalUserId());
|
||||
break;
|
||||
}
|
||||
|
@ -156,11 +157,8 @@ class UserExport extends BaseSettings
|
|||
if (!isset($row[$column])) {
|
||||
continue;
|
||||
}
|
||||
if ($field['type'] == 'datetime') {
|
||||
$p[$column] = $row[$column] ?? DBA::NULL_DATETIME;
|
||||
} else {
|
||||
$p[$column] = $row[$column];
|
||||
}
|
||||
|
||||
$p[$column] = $row[$column];
|
||||
}
|
||||
$result[] = $p;
|
||||
}
|
||||
|
|
|
@ -89,7 +89,7 @@ class Tos extends BaseModule
|
|||
$rules = "[ol]";
|
||||
foreach (explode("\n", $lines) as $line) {
|
||||
if (trim($line)) {
|
||||
$rules .= "\n[*]" . trim($line);
|
||||
$rules .= "\n[li]" . trim($line);
|
||||
}
|
||||
}
|
||||
$rules .= "\n[/ol]\n";
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue