Compare commits

..

No commits in common. "2022.05-rc" and "develop" have entirely different histories.

126 changed files with 19844 additions and 23144 deletions

4
.gitignore vendored
View File

@ -86,7 +86,3 @@ venv/
#Ignore cache file
.php_cs.cache
#ignore avatar picture cache path
/avatar

View File

@ -28,11 +28,9 @@ Andy
Andy Hee
Angristan
Anthronaut
Anton
Antron Samurai
Arian - Cazare Muncitori
Asher Pen
atjn
aweiher
axelt
balderino
@ -57,7 +55,6 @@ Carlos Solís
Carsten Pfeiffer
Casper
Cat Gray
chinnux
Chris Case
Christian González
Christian Kalkhoff
@ -93,7 +90,6 @@ effex7
Elena
emilia.krawczyk
Eric Côté
Erich
erik
Erkan Yilmaz
Eugene Veresk
@ -117,7 +113,6 @@ Gidi Kroon
GLComo
greeneyedred
Gregory Smith
gudzpoz
guzzisti
Haakon Meland Eriksen
Hans Meine
@ -143,7 +138,6 @@ Joe Doe
joe slam
Johannes Schwab
John Brazil
John Mortensen
Jonatan Nyberg
Jonny Tischbein
Josef Moravek
@ -222,7 +216,6 @@ Philipp Holzer
Pierre Bernardeau
Pierre Rudloff
Piotr Blonkowski
Piotr Strębski
pokerazor
R C
Rabuzarus
@ -291,7 +284,6 @@ Tobias Diekershoff
Tobias Hößl
Tom
Tom Aurlund
Tom Hu
tomamplius
tomtom84
Tony Baldwin

View File

@ -1 +1 @@
2022.05-rc
2022.05-dev

View File

@ -18,13 +18,11 @@
* 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/>.
*
*/
/**
* Run the worker from a daemon.
*
* This script was taken from http://php.net/manual/en/function.pcntl-fork.php
*/
if (php_sapi_name() !== 'cli') {
header($_SERVER["SERVER_PROTOCOL"] . ' 403 Forbidden');
exit();
@ -232,7 +230,7 @@ while (true) {
}
$timeout = ($seconds >= $wait_interval);
} while (!$timeout && !Worker\IPC::JobsExists());
} while (!$timeout && !Worker::IPCJobsExists());
if ($timeout) {
$do_cron = true;

View File

@ -58,7 +58,7 @@ case "$MODE" in
OUTFILE="$FULLPATH/../view/lang/C/messages.po"
FINDSTARTDIR="."
# skip addon folder
FINDOPTS="( -path ./addon -or -path ./addons -or -path ./addons-extra -or -path ./tests -or -path ./view/lang -or -path ./view/smarty3 -or -path ./vendor -or -path ./local -or -path ./avatar -or -path ./proxy ) -prune -or"
FINDOPTS="( -path ./addon -or -path ./addons -or -path ./addons-extra -or -path ./tests -or -path ./view/lang -or -path ./view/smarty3 -or -path ./vendor ) -prune -or"
F9KVERSION=$(cat ./VERSION);
echo "Friendica version $F9KVERSION"

View File

@ -31,7 +31,7 @@ use Friendica\Model\Contact;
define('FRIENDICA_PLATFORM', 'Friendica');
define('FRIENDICA_CODENAME', 'Siberian Iris');
define('FRIENDICA_VERSION', '2022.05-rc');
define('FRIENDICA_VERSION', '2022.05-dev');
define('DFRN_PROTOCOL_VERSION', '2.23');
define('NEW_TABLE_STRUCTURE_VERSION', 1288);

View File

@ -1,6 +1,6 @@
-- ------------------------------------------
-- Friendica 2022.05-rc (Siberian Iris)
-- DB_UPDATE_VERSION 1464
-- Friendica 2022.05-dev (Siberian Iris)
-- DB_UPDATE_VERSION 1460
-- ------------------------------------------
@ -712,16 +712,13 @@ CREATE TABLE IF NOT EXISTS `hook` (
--
CREATE TABLE IF NOT EXISTS `inbox-status` (
`url` varbinary(255) NOT NULL COMMENT 'URL of the inbox',
`uri-id` int unsigned COMMENT 'Item-uri id of inbox url',
`created` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT 'Creation date of this entry',
`success` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT 'Date of the last successful delivery',
`failure` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT 'Date of the last failed delivery',
`previous` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT 'Previous delivery date',
`archive` boolean NOT NULL DEFAULT '0' COMMENT 'Is the inbox archived?',
`shared` boolean NOT NULL DEFAULT '0' COMMENT 'Is it a shared inbox?',
PRIMARY KEY(`url`),
INDEX `uri-id` (`uri-id`),
FOREIGN KEY (`uri-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE
PRIMARY KEY(`url`)
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Status of ActivityPub inboxes';
--
@ -1071,8 +1068,8 @@ CREATE TABLE IF NOT EXISTS `post-category` (
`type` tinyint unsigned NOT NULL DEFAULT 0 COMMENT '',
`tid` int unsigned NOT NULL DEFAULT 0 COMMENT '',
PRIMARY KEY(`uri-id`,`uid`,`type`,`tid`),
INDEX `tid` (`tid`),
INDEX `uid_uri-id` (`uid`,`uri-id`),
INDEX `uri-id` (`tid`),
INDEX `uid` (`uid`),
FOREIGN KEY (`uri-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE,
FOREIGN KEY (`uid`) REFERENCES `user` (`uid`) ON UPDATE RESTRICT ON DELETE CASCADE,
FOREIGN KEY (`tid`) REFERENCES `tag` (`id`) ON UPDATE RESTRICT ON DELETE RESTRICT
@ -1117,25 +1114,6 @@ CREATE TABLE IF NOT EXISTS `post-content` (
FOREIGN KEY (`uri-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Content for all posts';
--
-- TABLE post-delivery
--
CREATE TABLE IF NOT EXISTS `post-delivery` (
`uri-id` int unsigned NOT NULL COMMENT 'Id of the item-uri table entry that contains the item uri',
`inbox-id` int unsigned NOT NULL COMMENT 'Item-uri id of inbox url',
`uid` mediumint unsigned COMMENT 'Delivering user',
`created` datetime DEFAULT '0001-01-01 00:00:00' COMMENT '',
`command` varbinary(32) COMMENT '',
`failed` tinyint DEFAULT 0 COMMENT 'Number of times the delivery has failed',
`receivers` mediumtext COMMENT 'JSON encoded array with the receiving contacts',
PRIMARY KEY(`uri-id`,`inbox-id`),
INDEX `inbox-id_created` (`inbox-id`,`created`),
INDEX `uid` (`uid`),
FOREIGN KEY (`uri-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE,
FOREIGN KEY (`inbox-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE,
FOREIGN KEY (`uid`) REFERENCES `user` (`uid`) ON UPDATE RESTRICT ON DELETE CASCADE
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Delivery data for posts for the batch processing';
--
-- TABLE post-delivery-data
--
@ -1155,32 +1133,6 @@ CREATE TABLE IF NOT EXISTS `post-delivery-data` (
FOREIGN KEY (`uri-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Delivery data for items';
--
-- TABLE post-history
--
CREATE TABLE IF NOT EXISTS `post-history` (
`uri-id` int unsigned NOT NULL COMMENT 'Id of the item-uri table entry that contains the item uri',
`edited` datetime NOT NULL DEFAULT '0001-01-01 00:00:00' COMMENT 'Date of edit',
`title` varchar(255) NOT NULL DEFAULT '' COMMENT 'item title',
`content-warning` varchar(255) NOT NULL DEFAULT '' COMMENT '',
`body` mediumtext COMMENT 'item body content',
`raw-body` mediumtext COMMENT 'Body without embedded media links',
`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',
`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',
`object-type` varchar(100) NOT NULL DEFAULT '' COMMENT 'ActivityStreams object type',
`object` text COMMENT 'JSON encoded object structure unless it is an implied object (normal post)',
`target-type` varchar(100) NOT NULL DEFAULT '' COMMENT 'ActivityStreams target type if applicable (URI)',
`target` text COMMENT 'JSON encoded target structure if used',
`resource-id` varchar(32) NOT NULL DEFAULT '' COMMENT 'Used to link other tables to items, it identifies the linked resource (e.g. photo) and if set must also set resource_type',
`plink` varchar(255) NOT NULL DEFAULT '' COMMENT 'permalink or URL to a displayable copy of the message at its source',
PRIMARY KEY(`uri-id`,`edited`),
FOREIGN KEY (`uri-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Post history';
--
-- TABLE post-link
--
@ -1219,7 +1171,6 @@ CREATE TABLE IF NOT EXISTS `post-media` (
`publisher-image` varbinary(255) COMMENT 'Image of the publisher of the media',
PRIMARY KEY(`id`),
UNIQUE INDEX `uri-id-url` (`uri-id`,`url`),
INDEX `uri-id-id` (`uri-id`,`id`),
FOREIGN KEY (`uri-id`) REFERENCES `item-uri` (`id`) ON UPDATE RESTRICT ON DELETE CASCADE
) DEFAULT COLLATE utf8mb4_general_ci COMMENT='Attached media';
@ -1768,9 +1719,6 @@ CREATE VIEW `post-user-view` AS SELECT
`author`.`network` AS `author-network`,
`author`.`blocked` AS `author-blocked`,
`author`.`hidden` AS `author-hidden`,
`author`.`updated` AS `author-updated`,
`author`.`gsid` AS `author-gsid`,
`author`.`uri-id` AS `author-uri-id`,
`post-user`.`owner-id` AS `owner-id`,
`owner`.`url` AS `owner-link`,
`owner`.`addr` AS `owner-addr`,
@ -1780,7 +1728,6 @@ CREATE VIEW `post-user-view` AS SELECT
`owner`.`network` AS `owner-network`,
`owner`.`blocked` AS `owner-blocked`,
`owner`.`hidden` AS `owner-hidden`,
`owner`.`updated` AS `owner-updated`,
`owner`.`contact-type` AS `owner-contact-type`,
`post-user`.`causer-id` AS `causer-id`,
`causer`.`url` AS `causer-link`,
@ -1816,8 +1763,6 @@ CREATE VIEW `post-user-view` AS SELECT
`post-question`.`multiple` AS `question-multiple`,
`post-question`.`voters` AS `question-voters`,
`post-question`.`end-time` AS `question-end-time`,
EXISTS(SELECT `uri-id` FROM `post-category` WHERE `post-category`.`uri-id` = `post-user`.`uri-id` AND `post-category`.`uid` = `post-user`.`uid`) AS `has-categories`,
EXISTS(SELECT `id` FROM `post-media` WHERE `post-media`.`uri-id` = `post-user`.`uri-id`) AS `has-media`,
`diaspora-interaction`.`interaction` AS `signed_text`,
`parent-item-uri`.`guid` AS `parent-guid`,
`parent-post`.`network` AS `parent-network`,
@ -1939,9 +1884,6 @@ CREATE VIEW `post-thread-user-view` AS SELECT
`author`.`network` AS `author-network`,
`author`.`blocked` AS `author-blocked`,
`author`.`hidden` AS `author-hidden`,
`author`.`updated` AS `author-updated`,
`author`.`gsid` AS `author-gsid`,
`author`.`uri-id` AS `author-uri-id`,
`post-thread-user`.`owner-id` AS `owner-id`,
`owner`.`url` AS `owner-link`,
`owner`.`addr` AS `owner-addr`,
@ -1951,7 +1893,6 @@ CREATE VIEW `post-thread-user-view` AS SELECT
`owner`.`network` AS `owner-network`,
`owner`.`blocked` AS `owner-blocked`,
`owner`.`hidden` AS `owner-hidden`,
`owner`.`updated` AS `owner-updated`,
`owner`.`contact-type` AS `owner-contact-type`,
`post-thread-user`.`causer-id` AS `causer-id`,
`causer`.`url` AS `causer-link`,
@ -1987,8 +1928,6 @@ CREATE VIEW `post-thread-user-view` AS SELECT
`post-question`.`multiple` AS `question-multiple`,
`post-question`.`voters` AS `question-voters`,
`post-question`.`end-time` AS `question-end-time`,
EXISTS(SELECT `uri-id` FROM `post-category` WHERE `post-category`.`uri-id` = `post-thread-user`.`uri-id` AND `post-category`.`uid` = `post-thread-user`.`uid`) AS `has-categories`,
EXISTS(SELECT `id` FROM `post-media` WHERE `post-media`.`uri-id` = `post-thread-user`.`uri-id`) AS `has-media`,
`diaspora-interaction`.`interaction` AS `signed_text`,
`parent-item-uri`.`guid` AS `parent-guid`,
`parent-post`.`network` AS `parent-network`,
@ -2096,9 +2035,6 @@ CREATE VIEW `post-view` AS SELECT
`author`.`network` AS `author-network`,
`author`.`blocked` AS `author-blocked`,
`author`.`hidden` AS `author-hidden`,
`author`.`updated` AS `author-updated`,
`author`.`gsid` AS `author-gsid`,
`author`.`uri-id` AS `author-uri-id`,
`post`.`owner-id` AS `owner-id`,
`owner`.`url` AS `owner-link`,
`owner`.`addr` AS `owner-addr`,
@ -2108,7 +2044,6 @@ CREATE VIEW `post-view` AS SELECT
`owner`.`network` AS `owner-network`,
`owner`.`blocked` AS `owner-blocked`,
`owner`.`hidden` AS `owner-hidden`,
`owner`.`updated` AS `owner-updated`,
`owner`.`contact-type` AS `owner-contact-type`,
`post`.`causer-id` AS `causer-id`,
`causer`.`url` AS `causer-link`,
@ -2124,8 +2059,6 @@ CREATE VIEW `post-view` AS SELECT
`post-question`.`multiple` AS `question-multiple`,
`post-question`.`voters` AS `question-voters`,
`post-question`.`end-time` AS `question-end-time`,
0 AS `has-categories`,
EXISTS(SELECT `id` FROM `post-media` WHERE `post-media`.`uri-id` = `post`.`uri-id`) AS `has-media`,
`diaspora-interaction`.`interaction` AS `signed_text`,
`parent-item-uri`.`guid` AS `parent-guid`,
`parent-post`.`network` AS `parent-network`,
@ -2229,9 +2162,6 @@ CREATE VIEW `post-thread-view` AS SELECT
`author`.`network` AS `author-network`,
`author`.`blocked` AS `author-blocked`,
`author`.`hidden` AS `author-hidden`,
`author`.`updated` AS `author-updated`,
`author`.`gsid` AS `author-gsid`,
`author`.`uri-id` AS `author-uri-id`,
`post-thread`.`owner-id` AS `owner-id`,
`owner`.`url` AS `owner-link`,
`owner`.`addr` AS `owner-addr`,
@ -2241,7 +2171,6 @@ CREATE VIEW `post-thread-view` AS SELECT
`owner`.`network` AS `owner-network`,
`owner`.`blocked` AS `owner-blocked`,
`owner`.`hidden` AS `owner-hidden`,
`owner`.`updated` AS `owner-updated`,
`owner`.`contact-type` AS `owner-contact-type`,
`post-thread`.`causer-id` AS `causer-id`,
`causer`.`url` AS `causer-link`,
@ -2257,8 +2186,6 @@ CREATE VIEW `post-thread-view` AS SELECT
`post-question`.`multiple` AS `question-multiple`,
`post-question`.`voters` AS `question-voters`,
`post-question`.`end-time` AS `question-end-time`,
0 AS `has-categories`,
EXISTS(SELECT `id` FROM `post-media` WHERE `post-media`.`uri-id` = `post-thread`.`uri-id`) AS `has-media`,
`diaspora-interaction`.`interaction` AS `signed_text`,
`parent-item-uri`.`guid` AS `parent-guid`,
`parent-post`.`network` AS `parent-network`,
@ -2307,14 +2234,9 @@ CREATE VIEW `collection-view` AS SELECT
`post-collection`.`type` AS `type`,
`post`.`author-id` AS `cid`,
`post`.`received` AS `received`,
`post`.`created` AS `created`,
`post-thread`.`commented` AS `commented`,
`post`.`thr-parent-id` AS `thr-parent-id`,
`post`.`author-id` AS `author-id`,
`post`.`gravity` AS `gravity`
`post`.`created` AS `created`
FROM `post-collection`
INNER JOIN `post` ON `post-collection`.`uri-id` = `post`.`uri-id`
INNER JOIN `post-thread` ON `post-thread`.`uri-id` = `post`.`parent-uri-id`;
INNER JOIN `post` ON `post-collection`.`uri-id` = `post`.`uri-id`;
--
-- VIEW tag-view

View File

@ -50,9 +50,7 @@ Database Tables
| [post-category](help/database/db_post-category) | post relation to categories |
| [post-collection](help/database/db_post-collection) | Collection of posts |
| [post-content](help/database/db_post-content) | Content for all posts |
| [post-delivery](help/database/db_post-delivery) | Delivery data for posts for the batch processing |
| [post-delivery-data](help/database/db_post-delivery-data) | Delivery data for items |
| [post-history](help/database/db_post-history) | Post history |
| [post-link](help/database/db_post-link) | Post related external links |
| [post-media](help/database/db_post-media) | Attached media |
| [post-question](help/database/db_post-question) | Question |

View File

@ -9,7 +9,6 @@ Fields
| Field | Description | Type | Null | Key | Default | Extra |
| -------- | ------------------------------------ | -------------- | ---- | --- | ------------------- | ----- |
| url | URL of the inbox | varbinary(255) | NO | PRI | NULL | |
| uri-id | Item-uri id of inbox url | int unsigned | YES | | NULL | |
| created | Creation date of this entry | datetime | NO | | 0001-01-01 00:00:00 | |
| success | Date of the last successful delivery | datetime | NO | | 0001-01-01 00:00:00 | |
| failure | Date of the last failed delivery | datetime | NO | | 0001-01-01 00:00:00 | |
@ -23,13 +22,6 @@ Indexes
| Name | Fields |
| ------- | ------ |
| PRIMARY | url |
| uri-id | uri-id |
Foreign Keys
------------
| Field | Target Table | Target Field |
|-------|--------------|--------------|
| uri-id | [item-uri](help/database/db_item-uri) | id |
Return to [database documentation](help/database)

View File

@ -16,11 +16,11 @@ Fields
Indexes
------------
| Name | Fields |
| ---------- | ---------------------- |
| PRIMARY | uri-id, uid, type, tid |
| tid | tid |
| uid_uri-id | uid, uri-id |
| Name | Fields |
| ------- | ---------------------- |
| PRIMARY | uri-id, uid, type, tid |
| uri-id | tid |
| uid | uid |
Foreign Keys
------------

View File

@ -1,37 +0,0 @@
Table post-delivery
===========
Delivery data for posts for the batch processing
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 | |
| inbox-id | Item-uri id of inbox url | int unsigned | NO | PRI | NULL | |
| uid | Delivering user | mediumint unsigned | YES | | NULL | |
| created | | datetime | YES | | 0001-01-01 00:00:00 | |
| command | | varbinary(32) | YES | | NULL | |
| failed | Number of times the delivery has failed | tinyint | YES | | 0 | |
| receivers | JSON encoded array with the receiving contacts | mediumtext | YES | | NULL | |
Indexes
------------
| Name | Fields |
| ---------------- | ----------------- |
| PRIMARY | uri-id, inbox-id |
| inbox-id_created | inbox-id, created |
| uid | uid |
Foreign Keys
------------
| Field | Target Table | Target Field |
|-------|--------------|--------------|
| uri-id | [item-uri](help/database/db_item-uri) | id |
| inbox-id | [item-uri](help/database/db_item-uri) | id |
| uid | [user](help/database/db_user) | uid |
Return to [database documentation](help/database)

View File

@ -1,44 +0,0 @@
Table post-history
===========
Post history
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 | |
| edited | Date of edit | datetime | NO | PRI | 0001-01-01 00:00:00 | |
| title | item title | varchar(255) | NO | | | |
| content-warning | | varchar(255) | NO | | | |
| body | item body content | mediumtext | YES | | NULL | |
| raw-body | Body without embedded media links | mediumtext | YES | | NULL | |
| 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 | |
| app | application which generated this item | varchar(255) | NO | | | |
| rendered-hash | | varchar(32) | NO | | | |
| rendered-html | item.body converted to html | mediumtext | YES | | NULL | |
| object-type | ActivityStreams object type | varchar(100) | NO | | | |
| object | JSON encoded object structure unless it is an implied object (normal post) | text | YES | | NULL | |
| target-type | ActivityStreams target type if applicable (URI) | varchar(100) | NO | | | |
| target | JSON encoded target structure if used | text | YES | | NULL | |
| resource-id | Used to link other tables to items, it identifies the linked resource (e.g. photo) and if set must also set resource_type | varchar(32) | NO | | | |
| plink | permalink or URL to a displayable copy of the message at its source | varchar(255) | NO | | | |
Indexes
------------
| Name | Fields |
| ------- | -------------- |
| PRIMARY | uri-id, edited |
Foreign Keys
------------
| Field | Target Table | Target Field |
|-------|--------------|--------------|
| uri-id | [item-uri](help/database/db_item-uri) | id |
Return to [database documentation](help/database)

View File

@ -35,7 +35,6 @@ Indexes
| ---------- | ------------------- |
| PRIMARY | id |
| uri-id-url | UNIQUE, uri-id, url |
| uri-id-id | uri-id, id |
Foreign Keys
------------

View File

@ -110,13 +110,7 @@ function display_init(App $a)
$item = $parent ?: $item;
}
$author = display_fetchauthor($item);
if (\Friendica\Util\Network::isLocalLink($author['url'])) {
\Friendica\Model\Profile::load(DI::app(), $author['nick'], false);
} else {
DI::page()['aside'] = Widget\VCard::getHTML($author);
}
DI::page()['aside'] = Widget\VCard::getHTML(display_fetchauthor($item));
}
function display_fetchauthor($item)

View File

@ -39,11 +39,11 @@ use Friendica\Util\Strings;
function fbrowser_content(App $a)
{
if (!local_user()) {
System::exit();
exit();
}
if (DI::args()->getArgc() == 1) {
System::exit();
exit();
}
// Needed to match the correct template in a module that uses a different theme than the user/site/default

View File

@ -621,11 +621,6 @@ function item_post(App $a) {
$datarray["id"] = -1;
$datarray["uri-id"] = -1;
$datarray["author-network"] = Protocol::DFRN;
$datarray["author-updated"] = '';
$datarray["author-gsid"] = 0;
$datarray["author-uri-id"] = ItemURI::getIdByURI($datarray["author-link"]);
$datarray["owner-updated"] = '';
$datarray["has-media"] = false;
$o = DI::conversation()->create([array_merge($contact_record, $datarray)], 'search', false, true);
@ -668,17 +663,21 @@ function item_post(App $a) {
$datarray['uri-id'] = ItemURI::getIdByURI($datarray['uri']);
if ($orig_post) {
// Fill the cache field
// This could be done in Item::update as well - but we have to check for the existance of some fields.
Item::putInCache($datarray);
$fields = [
'title' => $datarray['title'],
'body' => $datarray['body'],
'attach' => $datarray['attach'],
'file' => $datarray['file'],
'rendered-html' => $datarray['rendered-html'],
'rendered-hash' => $datarray['rendered-hash'],
'edited' => DateTimeFormat::utcNow(),
'changed' => DateTimeFormat::utcNow()
];
'changed' => DateTimeFormat::utcNow()];
Item::update($fields, ['id' => $post_id]);
Item::updateDisplayCache($datarray['uri-id']);
if ($return_path) {
DI::baseUrl()->redirect($return_path);

View File

@ -65,7 +65,7 @@ function photos_init(App $a) {
if (DI::args()->getArgc() > 1) {
$owner = User::getOwnerDataByNick(DI::args()->getArgv()[1]);
if (empty($owner) || $owner['account_removed']) {
if (!$owner) {
throw new HTTPException\NotFoundException(DI::l10n()->t('User not found.'));
}
@ -158,7 +158,7 @@ function photos_post(App $a)
if (!$can_post) {
notice(DI::l10n()->t('Permission denied.'));
System::exit();
exit();
}
$owner_record = User::getOwnerDataById($page_owner_uid);
@ -166,7 +166,7 @@ function photos_post(App $a)
if (!$owner_record) {
notice(DI::l10n()->t('Contact information unavailable'));
DI::logger()->info('photos_post: unable to locate contact record for page owner. uid=' . $page_owner_uid);
System::exit();
exit();
}
$aclFormatter = DI::aclFormatter();
@ -1257,12 +1257,14 @@ function photos_content(App $a)
$tags = null;
if (!empty($link_item['id'])) {
$tag_text = Tag::getCSVByURIId($link_item['uri-id']);
$arr = explode(',', $tag_text);
// parse tags and add links
$tag_arr = [];
foreach (Tag::getByURIId($link_item['uri-id']) as $tag) {
foreach ($arr as $tag) {
$tag_arr[] = [
'name' => $tag['name'],
'removeurl' => '/tagrm/' . $link_item['id'] . '/' . bin2hex($tag['name'])
'name' => BBCode::convert($tag),
'removeurl' => '/tagrm/' . $link_item['id'] . '/' . bin2hex($tag)
];
}
$tags = ['title' => DI::l10n()->t('Tags: '), 'tags' => $tag_arr];

View File

@ -22,7 +22,6 @@
use Friendica\App;
use Friendica\Core\Logger;
use Friendica\Core\Protocol;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\Contact;
@ -39,7 +38,7 @@ function hub_return($valid, $body)
} else {
throw new \Friendica\Network\HTTPException\NotFoundException();
}
System::exit();
exit();
}
// when receiving an XML feed, always return OK

View File

@ -21,7 +21,6 @@
use Friendica\App;
use Friendica\Core\Logger;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\PushSubscriber;
@ -143,5 +142,5 @@ function pubsubhubbub_init(App $a) {
throw new \Friendica\Network\HTTPException\AcceptedException();
}
System::exit();
exit();
}

View File

@ -23,16 +23,25 @@ use Friendica\App;
use Friendica\BaseModule;
use Friendica\Content\Feature;
use Friendica\Content\Nav;
use Friendica\Core\ACL;
use Friendica\Core\Hook;
use Friendica\Core\Logger;
use Friendica\Core\Renderer;
use Friendica\Core\Worker;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\Group;
use Friendica\Model\Item;
use Friendica\Model\Notification;
use Friendica\Model\Profile;
use Friendica\Model\User;
use Friendica\Model\Verb;
use Friendica\Module\BaseSettings;
use Friendica\Module\Security\Login;
use Friendica\Protocol\Activity;
use Friendica\Protocol\Email;
use Friendica\Util\Temporal;
use Friendica\Worker\Delivery;
function settings_init(App $a)
{

View File

@ -21,7 +21,6 @@
use Friendica\App;
use Friendica\Content\Text\BBCode;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\Item;
@ -31,7 +30,7 @@ function share_init(App $a) {
$post_id = ((DI::args()->getArgc() > 1) ? intval(DI::args()->getArgv()[1]) : 0);
if (!$post_id || !local_user()) {
System::exit();
exit();
}
$fields = ['private', 'body', 'author-name', 'author-link', 'author-avatar',
@ -39,7 +38,7 @@ function share_init(App $a) {
$item = Post::selectFirst($fields, ['id' => $post_id]);
if (!DBA::isResult($item) || $item['private'] == Item::PRIVATE) {
System::exit();
exit();
}
if (strpos($item['body'], "[/share]") !== false) {
@ -57,5 +56,5 @@ function share_init(App $a) {
}
echo $o;
System::exit();
exit();
}

View File

@ -167,5 +167,6 @@ EOT;
$post = Post::selectFirst(['uri-id', 'uid'], ['id' => $post_id]);
Worker::add(PRIORITY_HIGH, "Notifier", Delivery::POST, $post['uri-id'], $post['uid']);
System::exit();
exit();
}

View File

@ -67,14 +67,14 @@ function wall_attach_post(App $a) {
System::jsonExit(['error' => DI::l10n()->t('Permission denied.')]);
}
notice(DI::l10n()->t('Permission denied.') . EOL );
System::exit();
exit();
}
if (empty($_FILES['userfile'])) {
if ($r_json) {
System::jsonExit(['error' => DI::l10n()->t('Invalid request.')]);
}
System::exit();
exit();
}
$src = $_FILES['userfile']['tmp_name'];
@ -97,7 +97,7 @@ function wall_attach_post(App $a) {
} else {
notice($msg);
}
System::exit();
exit();
}
if ($maxfilesize && $filesize > $maxfilesize) {
@ -108,7 +108,7 @@ function wall_attach_post(App $a) {
} else {
echo $msg . EOL;
}
System::exit();
exit();
}
$newid = Attach::storeFile($src, $page_owner_uid, $filename, '<' . $page_owner_cid . '>');
@ -122,7 +122,7 @@ function wall_attach_post(App $a) {
} else {
echo $msg . EOL;
}
System::exit();
exit();
}
if ($r_json) {
@ -132,6 +132,7 @@ function wall_attach_post(App $a) {
$lf = "\n";
echo $lf . $lf . '[attachment]' . $newid . '[/attachment]' . $lf;
System::exit();
exit();
// NOTREACHED
}

View File

@ -89,14 +89,14 @@ function wall_upload_post(App $a, $desktopmode = true)
System::jsonExit(['error' => DI::l10n()->t('Permission denied.')]);
}
notice(DI::l10n()->t('Permission denied.'));
System::exit();
exit();
}
if (empty($_FILES['userfile']) && empty($_FILES['media'])) {
if ($r_json) {
System::jsonExit(['error' => DI::l10n()->t('Invalid request.')]);
}
System::exit();
exit();
}
$src = '';
@ -148,7 +148,7 @@ function wall_upload_post(App $a, $desktopmode = true)
System::jsonExit(['error' => DI::l10n()->t('Invalid request.')]);
}
notice(DI::l10n()->t('Invalid request.'));
System::exit();
exit();
}
$filetype = Images::getMimeTypeBySource($src, $filename, $filetype);
@ -167,7 +167,7 @@ function wall_upload_post(App $a, $desktopmode = true)
} else {
echo $msg. EOL;
}
System::exit();
exit();
}
$Image->orient($src);
@ -205,7 +205,7 @@ function wall_upload_post(App $a, $desktopmode = true)
} else {
echo $msg. EOL;
}
System::exit();
exit();
}
}
@ -229,7 +229,7 @@ function wall_upload_post(App $a, $desktopmode = true)
} else {
echo $msg. EOL;
}
System::exit();
exit();
}
if ($width > 640 || $height > 640) {
@ -281,6 +281,6 @@ function wall_upload_post(App $a, $desktopmode = true)
}
echo "\n\n" . '[url=' . DI::baseUrl() . '/photos/' . $page_owner_nick . '/image/' . $resource_id . '][img]' . DI::baseUrl() . "/photo/{$resource_id}-{$smallest}.".$Image->getExt()."[/img][/url]\n\n";
System::exit();
exit();
// NOTREACHED
}

View File

@ -472,7 +472,7 @@ class App
// Allow folks to override user themes and always use their own on their own site.
// This works only if the user is on the same server
$user = $this->database->selectFirst('user', ['theme'], ['uid' => $this->profile_owner]);
if ($this->database->isResult($user) && !local_user()) {
if ($this->database->isResult($user) && !$this->pConfig->get(local_user(), 'system', 'always_my_theme')) {
$page_theme = $user['theme'];
}
}
@ -504,7 +504,7 @@ class App
if (!empty($this->profile_owner) && ($this->profile_owner != local_user())) {
// Allow folks to override user themes and always use their own on their own site.
// This works only if the user is on the same server
if (!local_user()) {
if (!$this->pConfig->get(local_user(), 'system', 'always_my_theme')) {
$page_mobile_theme = $this->pConfig->get($this->profile_owner, 'system', 'mobile-theme');
}
}
@ -576,7 +576,6 @@ class App
$this->profiler->set(microtime(true), 'classinit');
$moduleName = $this->args->getModuleName();
$page->setLogging($this->args->getCommand(), $this->args->getMethod());
try {
// Missing DB connection: ERROR
@ -719,7 +718,6 @@ class App
} catch (HTTPException $e) {
(new ModuleHTTPException())->rawContent($e);
}
$page->logRuntime($this->config);
}
/**

View File

@ -30,7 +30,6 @@ use Friendica\Core\Config\Capability\IManageConfigValues;
use Friendica\Core\PConfig\Capability\IManagePersonalConfigValues;
use Friendica\Core\Hook;
use Friendica\Core\L10n;
use Friendica\Core\Logger;
use Friendica\Core\Renderer;
use Friendica\Core\System;
use Friendica\Core\Theme;
@ -79,37 +78,14 @@ class Page implements ArrayAccess
*/
private $basePath;
private $timestamp = 0;
private $command = '';
private $method = '';
/**
* @param string $basepath The Page basepath
*/
public function __construct(string $basepath)
{
$this->timestamp = microtime(true);
$this->basePath = $basepath;
}
public function setLogging(string $command, string $method)
{
$this->command = $command;
$this->method = $method;
}
public function logRuntime(IManageConfigValues $config)
{
if (in_array($this->command, $config->get('system', 'runtime_ignore'))) {
return;
}
$runtime = number_format(microtime(true) - $this->timestamp, 3);
if ($runtime > $config->get('system', 'runtime_loglimit')) {
Logger::debug('Runtime', ['method' => $this->method, 'command' => $this->command, 'runtime' => $runtime]);
}
}
/**
* Whether a offset exists
*
@ -445,9 +421,6 @@ class Page implements ArrayAccess
{
$moduleName = $args->getModuleName();
$this->command = $moduleName;
$this->method = $args->getMethod();
/* Create the page content.
* Calls all hooks which are including content operations
*

View File

@ -1,179 +0,0 @@
<?php
/**
* @copyright Copyright (C) 2010-2022, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
namespace Friendica\Console;
use Friendica\App\BaseURL;
use Friendica\Contact\Avatar;
use Friendica\Core\L10n;
use Friendica\Model\Contact;
use Friendica\Model\Photo;
use Friendica\Util\Images;
use Friendica\Object\Image;
use Friendica\Core\Config\Capability\IManageConfigValues;
use Friendica\Core\Protocol;
/**
* tool to move cached avatars to the avatar file cache.
*/
class MoveToAvatarCache extends \Asika\SimpleConsole\Console
{
protected $helpOptions = ['h', 'help', '?'];
/**
* @var $dba Friendica\Database\Database
*/
private $dba;
/**
* @var $baseurl Friendica\App\BaseURL
*/
private $baseurl;
/**
* @var L10n
*/
private $l10n;
/**
* @var IManageConfigValues
*/
private $config;
protected function getHelp()
{
$help = <<<HELP
console movetoavatarcache - Move all cached avatars to the file based avatar cache
Synopsis
bin/console movetoavatarcache
Description
bin/console movetoavatarcache
Move all cached avatars to the file based avatar cache
Options
-h|--help|-? Show help information
HELP;
return $help;
}
public function __construct(\Friendica\Database\Database $dba, BaseURL $baseurl, L10n $l10n, IManageConfigValues $config, array $argv = null)
{
parent::__construct($argv);
$this->dba = $dba;
$this->baseurl = $baseurl;
$this->l10n = $l10n;
$this->config = $config;
}
protected function doExecute()
{
if (!$this->config->get('system', 'avatar_cache')) {
$this->err($this->l10n->t('The avatar cache needs to be enabled to use this command.'));
return 2;
}
$fields = ['id', 'avatar', 'photo', 'thumb', 'micro', 'uri-id', 'url', 'avatar', 'network'];
$condition = ["NOT `self` AND `avatar` != ? AND `photo` LIKE ? AND `uid` = ? AND `uri-id` != ? AND NOT `uri-id` IS NULL AND NOT `network` IN (?, ?)",
'', $this->baseurl->get() . '/photo/%', 0, 0, Protocol::MAIL, Protocol::FEED];
$count = 0;
$total = $this->dba->count('contact', $condition);
$contacts = $this->dba->select('contact', $fields, $condition, ['order' => ['id']]);
while ($contact = $this->dba->fetch($contacts)) {
if (Contact::isLocal($contact['url'])) {
continue;
}
$this->out(++$count . '/' . $total . "\t" . $contact['id'] . "\t" . $contact['url'] . "\t", false);
$resourceid = Photo::ridFromURI($contact['photo']);
if (empty($resourceid)) {
$this->out($this->l10n->t('no resource in photo %s', $contact['photo']) . ' ', false);
}
$this->storeAvatar($resourceid, $contact, false);
}
$count = 0;
$totals = $this->dba->p("SELECT COUNT(DISTINCT(`resource-id`)) AS `total` FROM `photo` WHERE `contact-id` != ? AND `photo-type` = ?;", 0, Photo::CONTACT_AVATAR);
$total = $this->dba->fetch($totals)['total'] ?? 0;
$photos = $this->dba->p("SELECT `resource-id`, MAX(`contact-id`) AS `contact-id` FROM `photo` WHERE `contact-id` != ? AND `photo-type` = ? GROUP BY `resource-id`;", 0, Photo::CONTACT_AVATAR);
while ($photo = $this->dba->fetch($photos)) {
$contact = Contact::getById($photo['contact-id'], $fields);
if (empty($contact) || in_array($contact['network'], [Protocol::MAIL, Protocol::FEED]) || Contact::isLocal($contact['url'])) {
continue;
}
$this->out(++$count . '/' . $total . "\t" . $contact['id'] . "\t" . $contact['url'] . "\t", false);
$this->storeAvatar($photo['resource-id'], $contact, true);
}
return 0;
}
private function storeAvatar(string $resourceid, array $contact, bool $quit_on_invalid)
{
$valid = !empty($resourceid);
if ($valid) {
$this->out('1', false);
$photo = Photo::selectFirst([], ['resource-id' => $resourceid], ['order' => ['scale']]);
if (empty($photo)) {
$this->out(' ' . $this->l10n->t('no photo with id %s', $resourceid) . ' ', false);
$valid = false;
}
}
if ($valid) {
$this->out('2', false);
$imgdata = Photo::getImageDataForPhoto($photo);
if (empty($imgdata)) {
$this->out(' ' . $this->l10n->t('no image data for photo with id %s', $resourceid) . ' ', false);
$valid = false;
}
}
if ($valid) {
$this->out('3', false);
$image = new Image($imgdata, Images::getMimeTypeByData($imgdata));
if (!$image->isValid()) {
$this->out(' ' . $this->l10n->t('invalid image for id %s', $resourceid) . ' ', false);
$valid = false;
}
}
if ($valid) {
$this->out('4', false);
$fields = Avatar::storeAvatarByImage($contact, $image);
} else {
$fields = ['photo' => '', 'thumb' => '', 'micro' => ''];
}
if ($quit_on_invalid && $fields['photo'] == '') {
$this->out(' ' . $this->l10n->t('Quit on invalid photo %s', $contact['avatar']));
Photo::delete(['resource-id' => $resourceid]);
return;
}
$this->out('5', false);
Contact::update($fields, ['uri-id' => $contact['uri-id']]);
$this->out('6', false);
Photo::delete(['resource-id' => $resourceid]);
$this->out(' ' . $fields['photo']);
}
}

View File

@ -1,205 +0,0 @@
<?php
/**
* @copyright Copyright (C) 2010-2022, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
namespace Friendica\Console;
use Asika\SimpleConsole\Console;
use Friendica\Core\Config\Capability\IManageConfigValues;
use Friendica\Core\Worker;
use Friendica\Util\Strings;
use Friendica\Worker\Delivery;
class Relocate extends Console
{
protected $helpOptions = ['h', 'help', '?'];
/**
* @var IManageConfigValues
*/
private $config;
/**
* @var \Friendica\App\BaseURL
*/
private $baseUrl;
/**
* @var \Friendica\Database\Database
*/
private $database;
protected function getHelp()
{
$help = <<<HELP
console relocate - Update the node base URL
Usage
bin/console relocate <new base URL> [-h|--help|-?] [-v]
Description
Warning! Advanced function. Could make this server unreachable.
Change the base URL for this server. Sends relocation message to all the Friendica and Diaspora* contacts of all local users.
This process updates all the database fields that may contain a URL pointing at the current domain, as a result it takes
a while and the node will be in maintenance mode for the whole duration.
Options
-h|--help|-? Show help information
-v Show more debug information.
HELP;
return $help;
}
public function __construct(\Friendica\App\BaseURL $baseUrl, \Friendica\Database\Database $database, IManageConfigValues $config, $argv = null)
{
parent::__construct($argv);
$this->baseUrl = $baseUrl;
$this->database = $database;
$this->config = $config;
}
protected function doExecute()
{
if (count($this->args) == 0) {
$this->out($this->getHelp());
return 0;
}
if (count($this->args) > 1) {
throw new \Asika\SimpleConsole\CommandArgsException('Too many arguments');
}
$new_url = rtrim($this->getArgument(0), '/');
$parsed = @parse_url($new_url);
if (!is_array($parsed) || empty($parsed['host']) || empty($parsed['scheme'])) {
throw new \InvalidArgumentException('Can not parse new base URL. Must have at least <scheme>://<domain>');
}
$this->out(sprintf('Relocation started from %s to %s. Could take a while to complete.', $this->baseUrl->get(true), $this->getArgument(0)));
$old_url = $this->baseUrl->get(true);
// Generate host names for relocation the addresses in the format user@address.tld
$new_host = str_replace('http://', '@', Strings::normaliseLink($new_url));
$old_host = str_replace('http://', '@', Strings::normaliseLink($old_url));
$this->out('Entering maintenance mode');
$this->config->set('system', 'maintenance', true);
$this->config->set('system', 'maintenance_reason', 'Relocating node to ' . $new_url);
try {
if (!$this->database->transaction()) {
throw new \Exception('Unable to start a transaction, please retry later.');
}
// update tables
$this->out('Updating apcontact table fields');
$this->database->replaceInTableFields('apcontact', ['url', 'inbox', 'outbox', 'sharedinbox', 'photo', 'header', 'alias', 'subscribe', 'baseurl'], $old_url, $new_url);
$this->database->replaceInTableFields('apcontact', ['addr'], $old_host, $new_host);
$this->out('Updating contact table fields');
$this->database->replaceInTableFields('contact', ['photo', 'thumb', 'micro', 'url', 'alias', 'request', 'batch', 'notify', 'poll', 'subscribe', 'baseurl', 'confirm', 'poco', 'avatar', 'header'], $old_url, $new_url);
$this->database->replaceInTableFields('contact', ['nurl'], Strings::normaliseLink($old_url), Strings::normaliseLink($new_url));
$this->database->replaceInTableFields('contact', ['addr'], $old_host, $new_host);
$this->out('Updating conv table fields');
$this->database->replaceInTableFields('conv', ['creator', 'recips'], $old_host, $new_host);
$this->out('Updating delayed-post table fields');
$this->database->replaceInTableFields('delayed-post', ['uri'], $old_url, $new_url);
$this->out('Updating endpoint table fields');
$this->database->replaceInTableFields('endpoint', ['url'], $old_url, $new_url);
$this->out('Updating event table fields');
$this->database->replaceInTableFields('event', ['uri'], $old_url, $new_url);
$this->out('Updating fcontact table fields');
$this->database->replaceInTableFields('fcontact', ['url', 'photo', 'request', 'batch', 'poll', 'confirm', 'alias'], $old_url, $new_url);
$this->database->replaceInTableFields('fcontact', ['addr'], $old_host, $new_host);
$this->out('Updating fsuggest table fields');
$this->database->replaceInTableFields('fsuggest', ['url', 'request', 'photo'], $old_url, $new_url);
$this->out('Updating gserver table fields');
$this->database->replaceInTableFields('gserver', ['url'], $old_url, $new_url);
$this->database->replaceInTableFields('gserver', ['nurl'], Strings::normaliseLink($old_url), Strings::normaliseLink($new_url));
$this->out('Updating inbox-status table fields');
$this->database->replaceInTableFields('inbox-status', ['url'], $old_url, $new_url);
$this->out('Updating item-uri table fields');
$this->database->replaceInTableFields('item-uri', ['uri'], $old_url, $new_url);
$this->out('Updating mail table fields');
$this->database->replaceInTableFields('mail', ['from-photo', 'from-url', 'uri', 'thr-parent'], $old_url, $new_url);
$this->database->replaceInTableFields('mail', ['parent-uri'], $old_host, $new_host);
$this->out('Updating notify table fields');
$this->database->replaceInTableFields('notify', ['url', 'photo', 'link', 'msg', 'name_cache', 'msg_cache'], $old_url, $new_url);
$this->out('Updating profile table fields');
$this->database->replaceInTableFields('profile', ['photo', 'thumb'], $old_url, $new_url);
$this->out('Updating post-content table fields');
$this->database->replaceInTableFields('post-content', ['body', 'raw-body', 'rendered-html', 'target', 'plink'], $old_url, $new_url);
$this->database->replaceInTableFields('post-content', ['body', 'raw-body', 'rendered-html', 'target'], $old_host, $new_host);
$this->out('Updating post-history table fields');
$this->database->replaceInTableFields('post-history', ['body', 'raw-body', 'rendered-html', 'target', 'plink'], $old_url, $new_url);
$this->database->replaceInTableFields('post-history', ['body', 'raw-body', 'rendered-html', 'target'], $old_host, $new_host);
$this->out('Updating post-link table fields');
$this->database->replaceInTableFields('post-link', ['url'], $old_url, $new_url);
$this->out('Updating post-media table fields');
$this->database->replaceInTableFields('post-media', ['url', 'preview', 'author-url', 'author-image', 'publisher-url', 'publisher-image'], $old_url, $new_url);
$this->out('Updating tag table fields');
$this->database->replaceInTableFields('tag', ['url'], $old_url, $new_url);
// update config
$this->out('Updating config values');
$this->config->set('system', 'url', $new_url);
$this->baseUrl->saveByURL($new_url);
$this->database->commit();
} catch (\Throwable $e) {
$this->database->rollback();
$this->out('Process aborted with message: ' . $e->getMessage() . ' thrown in ' . $e->getFile() . ':' . $e->getLine());
return 1;
} finally {
$this->out('Leaving maintenance mode');
$this->config->set('system', 'maintenance', false);
$this->config->set('system', 'maintenance_reason', '');
}
// send relocate
$this->out('Schedule relocation messages to remote Friendica and Diaspora hosts');
$users = $this->database->selectToArray('user', ['uid'], ['account_removed' => false, 'account_expired' => false]);
foreach ($users as $user) {
Worker::add(PRIORITY_HIGH, 'Notifier', Delivery::RELOCATION, $user['uid']);
}
return 0;
}
}

View File

@ -1,265 +0,0 @@
<?php
/**
* @copyright Copyright (C) 2010-2022, 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\Contact;
use Friendica\Core\Logger;
use Friendica\DI;
use Friendica\Model\Item;
use Friendica\Network\HTTPClient\Client\HttpClientAccept;
use Friendica\Network\HTTPClient\Client\HttpClientOptions;
use Friendica\Object\Image;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\HTTPSignature;
use Friendica\Util\Images;
use Friendica\Util\Network;
use Friendica\Util\Proxy;
use Friendica\Util\Strings;
/**
* functions for handling contact avatar caching
*/
class Avatar
{
const BASE_PATH = '/avatar/';
/**
* Returns a field array with locally cached avatar pictures
*
* @param array $contact Contact array
* @param string $avatar Link to avatar picture
* @param bool $force force picture update
* @return array
*/
public static function fetchAvatarContact(array $contact, string $avatar, bool $force = false): array
{
$fields = ['avatar' => $avatar, 'avatar-date' => DateTimeFormat::utcNow(), 'photo' => '', 'thumb' => '', 'micro' => ''];
if (!DI::config()->get('system', 'avatar_cache')) {
self::deleteCache($contact);
return $fields;
}
if (Network::isLocalLink($avatar) || empty($avatar)) {
self::deleteCache($contact);
return $fields;
}
if (($avatar != $contact['avatar']) || $force) {
self::deleteCache($contact);
Logger::debug('Avatar file name changed', ['new' => $avatar, 'old' => $contact['avatar']]);
} elseif (self::isCacheFile($contact['photo']) && self::isCacheFile($contact['thumb']) && self::isCacheFile($contact['micro'])) {
$fields['photo'] = $contact['photo'];
$fields['thumb'] = $contact['thumb'];
$fields['micro'] = $contact['micro'];
Logger::debug('Using existing cache files', ['uri-id' => $contact['uri-id'], 'fields' => $fields]);
return $fields;
}
$fetchResult = HTTPSignature::fetchRaw($avatar, 0, [HttpClientOptions::ACCEPT_CONTENT => [HttpClientAccept::IMAGE]]);
$img_str = $fetchResult->getBody();
if (empty($img_str)) {
Logger::debug('Avatar is invalid', ['avatar' => $avatar]);
return $fields;
}
$image = new Image($img_str, Images::getMimeTypeByData($img_str));
if (!$image->isValid()) {
Logger::debug('Avatar picture is invalid', ['avatar' => $avatar]);
return $fields;
}
$filename = self::getFilename($contact['url']);
$timestamp = time();
$fields['photo'] = self::storeAvatarCache($image, $filename, Proxy::PIXEL_SMALL, $timestamp);
$fields['thumb'] = self::storeAvatarCache($image, $filename, Proxy::PIXEL_THUMB, $timestamp);
$fields['micro'] = self::storeAvatarCache($image, $filename, Proxy::PIXEL_MICRO, $timestamp);
Logger::debug('Storing new avatar cache', ['uri-id' => $contact['uri-id'], 'fields' => $fields]);
return $fields;
}
public static function storeAvatarByImage(array $contact, Image $image): array
{
$fields = ['photo' => '', 'thumb' => '', 'micro' => ''];
if (!DI::config()->get('system', 'avatar_cache')) {
self::deleteCache($contact);
return $fields;
}
if (Network::isLocalLink($contact['avatar']) || empty($contact['avatar'])) {
self::deleteCache($contact);
return $fields;
}
$filename = self::getFilename($contact['url']);
$timestamp = time();
$fields['photo'] = self::storeAvatarCache($image, $filename, Proxy::PIXEL_SMALL, $timestamp);
$fields['thumb'] = self::storeAvatarCache($image, $filename, Proxy::PIXEL_THUMB, $timestamp);
$fields['micro'] = self::storeAvatarCache($image, $filename, Proxy::PIXEL_MICRO, $timestamp);
return $fields;
}
private static function getFilename(string $url)
{
$guid = Item::guidFromUri($url, parse_url($url, PHP_URL_HOST));
return substr($guid, 0, 2) . '/' . substr($guid, 3, 2) . '/' . substr($guid, 5, 3) . '/' .
substr($guid, 9, 2) .'/' . substr($guid, 11, 2) . '/' . substr($guid, 13, 4). '/' . substr($guid, 18) . '-';
}
private static function storeAvatarCache(Image $image, string $filename, int $size, int $timestamp): string
{
$image->scaleDown($size);
if (is_null($image) || !$image->isValid()) {
return '';
}
$path = self::BASE_PATH . $filename . $size . '.' . $image->getExt();
$filepath = DI::basePath() . $path;
$dirpath = DI::basePath() . self::BASE_PATH;
DI::profiler()->startRecording('file');
// Fetch the permission and group ownership of the "avatar" path and apply to all files
$dir_perm = fileperms($dirpath) & 0777;
$file_perm = fileperms($dirpath) & 0666;
$group = filegroup($dirpath);
// Check directory permissions of all parts of the path
foreach (explode('/', dirname($filename)) as $part) {
$dirpath .= $part . '/';
if (!file_exists($dirpath)) {
if (!mkdir($dirpath, $dir_perm)) {
Logger::warning('Directory could not be created', ['directory' => $dirpath]);
}
} elseif ((($old_perm = fileperms($dirpath) & 0777) != $dir_perm) && !chmod($dirpath, $dir_perm)) {
Logger::notice('Directory permissions could not be changed', ['directory' => $dirpath, 'old' => $old_perm, 'new' => $dir_perm]);
}
if ((($old_group = filegroup($dirpath)) != $group) && !chgrp($dirpath, $group)) {
Logger::notice('Directory group could not be changed', ['directory' => $dirpath, 'old' => $old_group, 'new' => $group]);
}
}
if (!file_put_contents($filepath, $image->asString())) {
Logger::warning('File could not be created', ['file' => $filepath]);
}
$old_perm = fileperms($filepath) & 0666;
$old_group = filegroup($filepath);
if (($old_perm != $file_perm) && !chmod($filepath, $file_perm)) {
Logger::notice('File permissions could not be changed', ['file' => $filepath, 'old' => $old_perm, 'new' => $file_perm]);
}
if (($old_group != $group) && !chgrp($filepath, $group)) {
Logger::notice('File group could not be changed', ['file' => $filepath, 'old' => $old_group, 'new' => $group]);
}
DI::profiler()->stopRecording();
if (!file_exists($filepath)) {
Logger::warning('Avatar cache file could not be stored', ['file' => $filepath]);
return '';
}
return DI::baseUrl() . $path . '?ts=' . $timestamp;
}
/**
* Check if the avatar cache file is locally stored
*
* @param string $avatar
* @return boolean
*/
private static function isCacheFile(string $avatar): bool
{
return !empty(self::getCacheFile($avatar));
}
/**
* Fetch the name of locally cached avatar pictures
*
* @param string $avatar
* @return string
*/
private static function getCacheFile(string $avatar): string
{
$parts = parse_url($avatar);
if (empty($parts['host']) || ($parts['host'] != DI::baseUrl()->getHostname())) {
return '';
}
$pos = strpos($parts['path'], DI::baseUrl()->getUrlPath() . self::BASE_PATH);
if ($pos !== 0) {
return '';
}
$filename = DI::basePath() . $parts['path'];
DI::profiler()->startRecording('file');
$exists = file_exists($filename);
DI::profiler()->stopRecording();
if (!$exists) {
return '';
}
return $filename;
}
/**
* Delete locally cached avatar pictures of a contact
*
* @param string $avatar
* @return void
*/
public static function deleteCache(array $contact)
{
self::deleteCacheFile($contact['photo']);
self::deleteCacheFile($contact['thumb']);
self::deleteCacheFile($contact['micro']);
}
/**
* Delete a locally cached avatar picture
*
* @param string $avatar
* @return void
*/
private static function deleteCacheFile(string $avatar)
{
$localFile = self::getCacheFile($avatar);
if (!empty($localFile)) {
unlink($localFile);
Logger::debug('Unlink avatar', ['avatar' => $avatar]);
}
}
}

View File

@ -184,7 +184,7 @@ class ContactSelector
* @return string
* @throws \Exception
*/
public static function networkToIcon($network, $profile = "", $gsid = 0)
public static function networkToIcon($network, $profile = "")
{
$nets = [
Protocol::DFRN => 'friendica',
@ -218,14 +218,7 @@ class ContactSelector
$network_icon = str_replace($search, $replace, $network);
if ((in_array($network, Protocol::FEDERATED)) && ($profile != "")) {
if (!empty($gsid) && !empty(self::$serverdata[$gsid])) {
$gserver = self::$serverdata[$gsid];
} elseif (!empty($gsid)) {
$gserver = DBA::selectFirst('gserver', ['platform', 'network'], ['id' => $gsid]);
self::$serverdata[$gsid] = $gserver;
} else {
$gserver = self::getServerForProfile($profile);
}
$gserver = self::getServerForProfile($profile);
if (!empty($gserver['platform'])) {
$network_icon = $platform_icons[strtolower($gserver['platform'])] ?? $network_icon;
}

View File

@ -47,6 +47,7 @@ use Friendica\Protocol\Activity;
use Friendica\Util\Crypto;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\Profiler;
use Friendica\Util\Proxy;
use Friendica\Util\Strings;
use Friendica\Util\Temporal;
use Psr\Log\LoggerInterface;
@ -107,8 +108,6 @@ class Conversation
*/
public function builtinActivityPuller(array $activity, array &$conv_responses)
{
$thread_parent = $activity['thr-parent-row'] ?? [];
foreach ($conv_responses as $mode => $v) {
$sparkle = '';
@ -153,8 +152,9 @@ class Conversation
$activity['thr-parent-id'] = $activity['parent-uri-id'];
}
// Skip when the causer of the parent is the same as the author of the announce
if (($verb == Activity::ANNOUNCE) && !empty($thread_parent['causer-id'] && ($thread_parent['causer-id'] == $activity['author-id']))) {
// Skip when the causer of the parent is the same than the author of the announce
if (($verb == Activity::ANNOUNCE) && Post::exists(['uri-id' => $activity['thr-parent-id'],
'uid' => $activity['uid'], 'causer-id' => $activity['author-id'], 'gravity' => [GRAVITY_PARENT, GRAVITY_COMMENT]])) {
continue;
}
@ -441,7 +441,7 @@ class Conversation
$previewing = (($preview) ? ' preview ' : '');
if ($mode === 'network') {
$items = $this->addChildren($items, false, $order, $uid, $mode);
$items = $this->addChildren($items, false, $order, $uid);
if (!$update) {
/*
* The special div is needed for liveUpdate to kick in for this page.
@ -467,7 +467,7 @@ class Conversation
. "'; </script>\r\n";
}
} elseif ($mode === 'profile') {
$items = $this->addChildren($items, false, $order, $uid, $mode);
$items = $this->addChildren($items, false, $order, $uid);
if (!$update) {
$tab = !empty($_GET['tab']) ? trim($_GET['tab']) : 'posts';
@ -484,7 +484,7 @@ class Conversation
}
}
} elseif ($mode === 'notes') {
$items = $this->addChildren($items, false, $order, local_user(), $mode);
$items = $this->addChildren($items, false, $order, local_user());
if (!$update) {
$live_update_div = '<div id="live-notes"></div>' . "\r\n"
@ -492,7 +492,7 @@ class Conversation
. "; var netargs = '/?f='; </script>\r\n";
}
} elseif ($mode === 'display') {
$items = $this->addChildren($items, false, $order, $uid, $mode);
$items = $this->addChildren($items, false, $order, $uid);
if (!$update) {
$live_update_div = '<div id="live-display"></div>' . "\r\n"
@ -500,7 +500,7 @@ class Conversation
. "</script>";
}
} elseif ($mode === 'community') {
$items = $this->addChildren($items, true, $order, $uid, $mode);
$items = $this->addChildren($items, true, $order, $uid);
if (!$update) {
$live_update_div = '<div id="live-community"></div>' . "\r\n"
@ -510,7 +510,7 @@ class Conversation
. "'; </script>\r\n";
}
} elseif ($mode === 'contacts') {
$items = $this->addChildren($items, false, $order, $uid, $mode);
$items = $this->addChildren($items, false, $order, $uid);
if (!$update) {
$live_update_div = '<div id="live-contact"></div>' . "\r\n"
@ -667,15 +667,15 @@ class Conversation
'created_date' => $item['created'],
'uriid' => $item['uri-id'],
'network' => $item['network'],
'network_name' => ContactSelector::networkToName($item['author-network'], $item['author-link'], $item['network'], $item['author-gsid']),
'network_icon' => ContactSelector::networkToIcon($item['network'], $item['author-link'], $item['author-gsid']),
'network_name' => ContactSelector::networkToName($item['author-network'], $item['author-link'], $item['network']),
'network_icon' => ContactSelector::networkToIcon($item['network'], $item['author-link']),
'linktitle' => $this->l10n->t('View %s\'s profile @ %s', $profile_name, $item['author-link']),
'profile_url' => $profile_link,
'item_photo_menu_html' => $this->item->photoMenu($item, $formSecurityToken),
'name' => $profile_name,
'sparkle' => $sparkle,
'lock' => false,
'thumb' => $this->baseURL->remove($this->item->getAuthorAvatar($item)),
'thumb' => $this->baseURL->remove(Contact::getAvatarUrlForUrl($item['author-link'], $item['uid'], Proxy::SIZE_THUMB)),
'title' => $title,
'body_html' => $body_html,
'tags' => $tags['tags'],
@ -696,7 +696,7 @@ class Conversation
'indent' => '',
'owner_name' => '',
'owner_url' => '',
'owner_photo' => $this->baseURL->remove($this->item->getOwnerAvatar($item)),
'owner_photo' => $this->baseURL->remove(Contact::getAvatarUrlForUrl($item['owner-link'], $item['uid'], Proxy::SIZE_THUMB)),
'plink' => ItemModel::getPlink($item),
'edpost' => false,
'pinned' => $pinned,
@ -809,13 +809,12 @@ class Conversation
/**
* Adds some information (Causer, post reason, direction) to the fetched post row.
*
* @param array $row Post row
* @param array $activity Contact data of the resharer
* @param array $thr_parent Thread parent row
* @param array $row Post row
* @param array $activity Contact data of the resharer
*
* @return array items with parents and comments
*/
private function addRowInformation(array $row, array $activity, array $thr_parent)
private function addRowInformation(array $row, array $activity)
{
$this->profiler->startRecording('rendering');
@ -890,8 +889,6 @@ class Conversation
break;
}
$row['thr-parent-row'] = $thr_parent;
$this->profiler->stopRecording();
return $row;
}
@ -902,15 +899,15 @@ class Conversation
* The system will fetch the comments for the local user whenever possible.
* This behaviour is currently needed to allow commenting on Friendica posts.
*
* @param array $parents Parent items
* @param bool $block_authors
* @param bool $order
* @param int $uid
* @param string $mode
* @param array $parents Parent items
*
* @param $block_authors
* @param $order
* @param $uid
* @return array items with parents and comments
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
private function addChildren(array $parents, bool $block_authors, string $order, int $uid, string $mode)
private function addChildren(array $parents, $block_authors, $order, $uid)
{
$this->profiler->startRecording('rendering');
if (count($parents) > 1) {
@ -919,6 +916,8 @@ class Conversation
$max_comments = $this->config->get('system', 'max_display_comments', 1000);
}
$params = ['order' => ['uri-id' => true, 'uid' => true]];
$activities = [];
$uriids = [];
$commentcounter = [];
@ -952,17 +951,6 @@ class Conversation
$condition = DBA::mergeConditions($condition,
["`uid` IN (0, ?) AND (`vid` != ? OR `vid` IS NULL)", $uid, Verb::getID(Activity::FOLLOW)]);
$thread_parents = Post::select(['uri-id', 'causer-id'], $condition, ['order' => ['uri-id' => false, 'uid']]);
$thr_parent = [];
while ($row = Post::fetch($thread_parents)) {
$thr_parent[$row['uri-id']] = $row;
}
DBA::close($thread_parents);
$params = ['order' => ['uri-id' => true, 'uid' => true]];
$thread_items = Post::selectForUser($uid, array_merge(ItemModel::DISPLAY_FIELDLIST, ['featured', 'contact-uid', 'gravity', 'post-type', 'post-reason']), $condition, $params);
$items = [];
@ -972,10 +960,6 @@ class Conversation
continue;
}
if (($mode != 'contacts') && !$row['origin']) {
$row['featured'] = false;
}
if ($max_comments > 0) {
if (($row['gravity'] == GRAVITY_COMMENT) && (++$commentcounter[$row['parent-uri-id']] > $max_comments)) {
continue;
@ -984,8 +968,7 @@ class Conversation
continue;
}
}
$items[$row['uri-id']] = $this->addRowInformation($row, $activities[$row['uri-id']] ?? [], $thr_parent[$row['thr-parent-id']] ?? []);
$items[$row['uri-id']] = $this->addRowInformation($row, $activities[$row['uri-id']] ?? []);
}
DBA::close($thread_items);

View File

@ -26,16 +26,16 @@ use Friendica\Core\Hook;
use Friendica\Core\L10n;
use Friendica\Core\Logger;
use Friendica\Core\Protocol;
use Friendica\Core\Session;
use Friendica\Database\DBA;
use Friendica\Model\Contact;
use Friendica\Model\Group;
use Friendica\Model\Item as ModelItem;
use Friendica\Model\Photo;
use Friendica\Model\Tag;
use Friendica\Model\Post;
use Friendica\Protocol\Activity;
use Friendica\Util\Profiler;
use Friendica\Util\Proxy;
use Friendica\Util\Strings;
use Friendica\Util\XML;
/**
@ -93,10 +93,6 @@ class Item
$uid = $item['uid'] ?: $uid;
if (empty($item['has-categories'])) {
return [$categories, $folders];
}
foreach (Post\Category::getArrayByURIId($item['uri-id'], $uid, Post\Category::CATEGORY) as $savedFolderName) {
if (!empty($item['author-link'])) {
$url = $item['author-link'] . "?category=" . rawurlencode($savedFolderName);
@ -357,6 +353,22 @@ class Item
}
}
$matches = null;
if (preg_match_all('/@\[url=(.*?)\]/is', $item['body'], $matches, PREG_SET_ORDER)) {
foreach ($matches as $mtch) {
if (!strpos($mtch[1], 'zrl=')) {
$item['body'] = str_replace($mtch[0], '@[url=' . Contact::magicLink($mtch[1]) . ']', $item['body']);
}
}
}
// add sparkle links to appropriate permalinks
// Only create a redirection to a magic link when logged in
if (!empty($item['plink']) && Session::isAuthenticated() && $item['private'] == ModelItem::PRIVATE) {
$author = ['uid' => 0, 'id' => $item['author-id'],
'network' => $item['author-network'], 'url' => $item['author-link']];
$item['plink'] = Contact::magicLinkByContact($author, $item['plink']);
}
$this->profiler->stopRecording();
}
@ -386,7 +398,7 @@ class Item
$pcid = $item['author-id'];
$network = '';
$rel = 0;
$condition = ['uid' => local_user(), 'uri-id' => $item['author-uri-id']];
$condition = ['uid' => local_user(), 'nurl' => Strings::normaliseLink($item['author-link'])];
$contact = DBA::selectFirst('contact', ['id', 'network', 'rel'], $condition);
if (DBA::isResult($contact)) {
$cid = $contact['id'];
@ -565,43 +577,4 @@ class Item
}
return $item;
}
public function getAuthorAvatar(array $item): string
{
if (in_array($item['network'], [Protocol::FEED, Protocol::MAIL])) {
$author_avatar = $item['contact-id'];
$author_updated = '';
$author_thumb = $item['contact-avatar'];
} else {
$author_avatar = $item['author-id'];
$author_updated = $item['author-updated'];
$author_thumb = $item['author-avatar'];
}
if (empty($author_thumb) || Photo::isPhotoURI($author_thumb)) {
$author_thumb = Contact::getAvatarUrlForId($author_avatar, Proxy::SIZE_THUMB, $author_updated);
}
return $author_thumb;
}
public function getOwnerAvatar(array $item): string
{
if (in_array($item['network'], [Protocol::FEED, Protocol::MAIL])) {
$owner_avatar = $item['contact-id'];
$owner_updated = '';
$owner_thumb = $item['contact-avatar'];
} else {
$owner_avatar = $item['owner-id'];
$owner_updated = $item['owner-updated'];
$owner_thumb = $item['owner-avatar'];
}
if (empty($owner_thumb) || Photo::isPhotoURI($owner_thumb)) {
$owner_thumb = Contact::getAvatarUrlForId($owner_avatar, Proxy::SIZE_THUMB, $owner_updated);
}
return $owner_thumb;
}
}

View File

@ -1952,20 +1952,17 @@ class BBCode
* - #[url=<anything>]<term>[/url]
* - [url=<anything>]#<term>[/url]
*/
self::performWithEscapedTags($text, ['url', 'share'], function ($text) use ($simple_html) {
$text = preg_replace_callback("/(?:#\[url\=[^\[\]]*\]|\[url\=[^\[\]]*\]#)(.*?)\[\/url\]/ism", function($matches) use ($simple_html) {
if ($simple_html == self::ACTIVITYPUB) {
return '<a href="' . DI::baseUrl() . '/search?tag=' . rawurlencode($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])
. '" class="tag" rel="tag" title="' . XML::escape($matches[1]) . '">'
. XML::escape($matches[1]) . '</a>';
}
}, $text);
return $text;
});
$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])
. '" 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])
. '" class="tag" rel="tag" title="' . XML::escape($matches[1]) . '">'
. XML::escape($matches[1]) . '</a>';
}
}, $text);
// We need no target="_blank" rel="noopener noreferrer" for local links
// convert links start with DI::baseUrl() as local link without the target="_blank" rel="noopener noreferrer" attribute
@ -2089,15 +2086,11 @@ class BBCode
* @param string $text The text with BBCode
* @return string The same text - but without "abstract" element
*/
public static function stripAbstract(string $text): string
public static function stripAbstract($text)
{
DI::profiler()->startRecording('rendering');
$text = BBCode::performWithEscapedTags($text, ['code', 'noparse', 'nobb', 'pre'], function ($text) {
$text = preg_replace("/[\s|\n]*\[abstract\].*?\[\/abstract\][\s|\n]*/ism", ' ', $text);
$text = preg_replace("/[\s|\n]*\[abstract=.*?\].*?\[\/abstract][\s|\n]*/ism", ' ', $text);
return $text;
});
$text = preg_replace("/[\s|\n]*\[abstract\].*?\[\/abstract\][\s|\n]*/ism", ' ', $text);
$text = preg_replace("/[\s|\n]*\[abstract=.*?\].*?\[\/abstract][\s|\n]*/ism", ' ', $text);
DI::profiler()->stopRecording();
return $text;
@ -2106,26 +2099,30 @@ class BBCode
/**
* Returns the value of the "abstract" element
*
* @param string $text The text that maybe contains the element
* @param string $text The text that maybe contains the element
* @param string $addon The addon for which the abstract is meant for
* @return string The abstract
*/
public static function getAbstract(string $text, string $addon = ''): string
public static function getAbstract($text, $addon = '')
{
DI::profiler()->startRecording('rendering');
$abstract = '';
$abstracts = [];
$addon = strtolower($addon);
$abstract = BBCode::performWithEscapedTags($text, ['code', 'noparse', 'nobb', 'pre'], function ($text) use ($addon) {
if ($addon && preg_match('#\[abstract=' . preg_quote($addon, '#') . '](.*?)\[/abstract]#ism', $text, $matches)) {
return $matches[1];
if (preg_match_all("/\[abstract=(.*?)\](.*?)\[\/abstract\]/ism", $text, $results, PREG_SET_ORDER)) {
foreach ($results as $result) {
$abstracts[strtolower($result[1])] = $result[2];
}
}
if (preg_match("#\[abstract](.*?)\[/abstract]#ism", $text, $matches)) {
return $matches[1];
}
if (isset($abstracts[$addon])) {
$abstract = $abstracts[$addon];
}
return '';
});
if ($abstract == '' && preg_match("/\[abstract\](.*?)\[\/abstract\]/ism", $text, $result)) {
$abstract = $result[1];
}
DI::profiler()->stopRecording();
return $abstract;
@ -2340,9 +2337,11 @@ class BBCode
* @param array $tagList A list of tag names, e.g ['noparse', 'nobb', 'pre']
* @param callable $callback
* @return string
* @see Strings::performWithEscapedBlocks
* @throws Exception
*@see Strings::performWithEscapedBlocks
*
*/
public static function performWithEscapedTags(string $text, array $tagList, callable $callback): string
public static function performWithEscapedTags(string $text, array $tagList, callable $callback)
{
$tagList = array_map('preg_quote', $tagList);

View File

@ -59,13 +59,11 @@ Commands:
autoinstall Starts automatic installation of friendica based on values from htconfig.php
lock Edit site locks
maintenance Set maintenance mode for this node
movetoavatarcache Move cached avatars to the file based avatar cache
user User management
php2po Generate a messages.po file from a strings.php file
po2php Generate a strings.php file from a messages.po file
typo Checks for parse errors in Friendica files
postupdate Execute pending post update scripts (can last days)
relocate Update node base URL
serverblock Manage blocked servers
storage Manage storage backend
relay Manage ActivityPub relay servers
@ -93,12 +91,10 @@ HELP;
'globalcommunitysilence' => Friendica\Console\GlobalCommunitySilence::class,
'lock' => Friendica\Console\Lock::class,
'maintenance' => Friendica\Console\Maintenance::class,
'movetoavatarcache' => Friendica\Console\MoveToAvatarCache::class,
'php2po' => Friendica\Console\PhpToPo::class,
'postupdate' => Friendica\Console\PostUpdate::class,
'po2php' => Friendica\Console\PoToPhp::class,
'relay' => Friendica\Console\Relay::class,
'relocate' => Friendica\Console\Relocate::class,
'serverblock' => Friendica\Console\ServerBlock::class,
'storage' => Friendica\Console\Storage::class,
'test' => Friendica\Console\Test::class,

View File

@ -40,7 +40,6 @@ class L10n
'bg' => 'Български',
'ca' => 'Català',
'cs' => 'Česky',
'da-dk' => 'Dansk (Danmark)',
'de' => 'Deutsch',
'en-gb' => 'English (United Kingdom)',
'en-us' => 'English (United States)',

View File

@ -295,7 +295,7 @@ class System
DI::apiResponse()->addContent(XML::fromArray(["result" => $result], $xml));
DI::page()->exit(DI::apiResponse()->generate());
self::exit();
exit();
}
/**
@ -315,7 +315,7 @@ class System
DI::apiResponse()->addContent($content);
DI::page()->exit(DI::apiResponse()->generate());
self::exit();
exit();
}
/**
@ -331,8 +331,7 @@ class System
DI::apiResponse()->setType($responce, $content_type);
DI::apiResponse()->addContent($content);
DI::page()->exit(DI::apiResponse()->generate());
self::exit();
exit();
}
public static function jsonError($httpCode, $content, $content_type = 'application/json')
@ -360,16 +359,6 @@ class System
DI::apiResponse()->setType(Response::TYPE_JSON, $content_type);
DI::apiResponse()->addContent(json_encode($content, $options));
DI::page()->exit(DI::apiResponse()->generate());
self::exit();
}
/**
* Exit the program execution.
*/
public static function exit()
{
DI::page()->logRuntime(DI::config());
exit();
}
@ -459,7 +448,8 @@ class System
case 307:
throw new TemporaryRedirectException();
}
self::exit();
exit();
}
/**
@ -532,7 +522,7 @@ class System
echo str_replace("\t", " ", $o);
echo "</section>";
echo "</body></html>\r\n";
self::exit();
exit();
}
/**

View File

@ -21,6 +21,8 @@
namespace Friendica\Core;
use Friendica\App\Mode;
use Friendica\Core;
use Friendica\Core\Worker\Entity\Process;
use Friendica\Database\DBA;
use Friendica\DI;
@ -49,6 +51,7 @@ class Worker
private static $lock_duration = 0;
private static $last_update;
private static $state;
private static $daemon_mode = null;
/** @var Process */
private static $process;
@ -77,7 +80,7 @@ class Worker
$last_cleanup = DI::config()->get('system', 'worker_last_cleaned', 0);
if (time() > ($last_cleanup + 300)) {
DI::config()->set('system', 'worker_last_cleaned', time());
Worker\Cron::killStaleWorkers();
self::killStaleWorkers();
}
// Check if the system is ready
@ -87,7 +90,7 @@ class Worker
// Now we start additional cron processes if we should do so
if ($run_cron) {
Worker\Cron::run();
self::runCron();
}
$last_check = $starttime = time();
@ -95,13 +98,16 @@ class Worker
// We fetch the next queue entry that is about to be executed
while ($r = self::workerProcess()) {
if (Worker\IPC::JobsExists(getmypid())) {
Worker\IPC::DeleteJobState(getmypid());
if (self::IPCJobsExists(getmypid())) {
self::IPCDeleteJobState(getmypid());
}
// Don't refetch when a worker fetches tasks for multiple workers
$refetched = DI::config()->get('system', 'worker_multiple_fetch');
foreach ($r as $entry) {
// Assure that the priority is an integer value
$entry['priority'] = (int)$entry['priority'];
// The work will be done
if (!self::execute($entry)) {
Logger::notice('Process execution failed, quitting.');
@ -146,8 +152,8 @@ class Worker
if (time() > ($starttime + (DI::config()->get('system', 'cron_interval') * 60))) {
Logger::info('Process lifetime reached, respawning.');
self::unclaimProcess($process);
if (Worker\Daemon::isMode()) {
Worker\IPC::SetJobState(true);
if (self::isDaemonMode()) {
self::IPCSetJobState(true);
} else {
self::spawnWorker();
}
@ -156,8 +162,8 @@ class Worker
}
// Cleaning up. Possibly not needed, but it doesn't harm anything.
if (Worker\Daemon::isMode()) {
Worker\IPC::SetJobState(false);
if (self::isDaemonMode()) {
self::IPCSetJobState(false);
}
Logger::info("Couldn't select a workerqueue entry, quitting process", ['pid' => getmypid()]);
}
@ -255,7 +261,7 @@ class Worker
$workerqueue = DBA::selectFirst('workerqueue', ['priority'], $condition, ['order' => ['priority']]);
self::$db_duration += (microtime(true) - $stamp);
if (DBA::isResult($workerqueue)) {
return $workerqueue['priority'];
return $workerqueue["priority"];
} else {
return 0;
}
@ -460,13 +466,13 @@ class Worker
$cooldown = DI::config()->get("system", "worker_cooldown", 0);
if ($cooldown > 0) {
Logger::info('Pre execution cooldown.', ['priority' => $queue['priority'], 'id' => $queue["id"], 'cooldown' => $cooldown]);
Logger::info('Pre execution cooldown.', ['priority' => $queue["priority"], 'id' => $queue["id"], 'cooldown' => $cooldown]);
sleep($cooldown);
}
Logger::enableWorker($funcname);
Logger::info("Process start.", ['priority' => $queue['priority'], 'id' => $queue["id"]]);
Logger::info("Process start.", ['priority' => $queue["priority"], 'id' => $queue["id"]]);
$stamp = (float)microtime(true);
@ -474,6 +480,11 @@ class Worker
// For this reason the variables have to be initialized.
DI::profiler()->reset();
if (!in_array($queue['priority'], PRIORITIES)) {
Logger::warning('Invalid priority', ['queue' => $queue, 'callstack' => System::callstack(20)]);
$queue['priority'] = PRIORITY_MEDIUM;
}
$a->setQueue($queue);
$up_duration = microtime(true) - self::$up_start;
@ -518,21 +529,21 @@ class Worker
self::$lock_duration = 0;
if ($duration > 3600) {
Logger::info('Longer than 1 hour.', ['priority' => $queue['priority'], 'id' => $queue["id"], 'duration' => round($duration/60, 3)]);
Logger::info('Longer than 1 hour.', ['priority' => $queue["priority"], 'id' => $queue["id"], 'duration' => round($duration/60, 3)]);
} elseif ($duration > 600) {
Logger::info('Longer than 10 minutes.', ['priority' => $queue['priority'], 'id' => $queue["id"], 'duration' => round($duration/60, 3)]);
Logger::info('Longer than 10 minutes.', ['priority' => $queue["priority"], 'id' => $queue["id"], 'duration' => round($duration/60, 3)]);
} elseif ($duration > 300) {
Logger::info('Longer than 5 minutes.', ['priority' => $queue['priority'], 'id' => $queue["id"], 'duration' => round($duration/60, 3)]);
Logger::info('Longer than 5 minutes.', ['priority' => $queue["priority"], 'id' => $queue["id"], 'duration' => round($duration/60, 3)]);
} elseif ($duration > 120) {
Logger::info('Longer than 2 minutes.', ['priority' => $queue['priority'], 'id' => $queue["id"], 'duration' => round($duration/60, 3)]);
Logger::info('Longer than 2 minutes.', ['priority' => $queue["priority"], 'id' => $queue["id"], 'duration' => round($duration/60, 3)]);
}
Logger::info('Process done.', ['priority' => $queue['priority'], 'id' => $queue["id"], 'duration' => round($duration, 3)]);
Logger::info('Process done.', ['priority' => $queue["priority"], 'id' => $queue["id"], 'duration' => round($duration, 3)]);
DI::profiler()->saveLog(DI::logger(), "ID " . $queue["id"] . ": " . $funcname);
if ($cooldown > 0) {
Logger::info('Post execution cooldown.', ['priority' => $queue['priority'], 'id' => $queue["id"], 'cooldown' => $cooldown]);
Logger::info('Post execution cooldown.', ['priority' => $queue["priority"], 'id' => $queue["id"], 'cooldown' => $cooldown]);
sleep($cooldown);
}
}
@ -620,6 +631,87 @@ class Worker
return true;
}
/**
* fix the queue entry if the worker process died
*
* @return void
* @throws \Exception
*/
private static function killStaleWorkers()
{
$stamp = (float)microtime(true);
$entries = DBA::select(
'workerqueue',
['id', 'pid', 'executed', 'priority', 'command', 'parameter'],
['NOT `done` AND `pid` != 0'],
['order' => ['priority', 'retrial', 'created']]
);
self::$db_duration += (microtime(true) - $stamp);
while ($entry = DBA::fetch($entries)) {
if (!posix_kill($entry["pid"], 0)) {
$stamp = (float)microtime(true);
DBA::update(
'workerqueue',
['executed' => DBA::NULL_DATETIME, 'pid' => 0],
['id' => $entry["id"]]
);
self::$db_duration += (microtime(true) - $stamp);
self::$db_duration_write += (microtime(true) - $stamp);
} else {
// Kill long running processes
// Check if the priority is in a valid range
if (!in_array($entry["priority"], [PRIORITY_CRITICAL, PRIORITY_HIGH, PRIORITY_MEDIUM, PRIORITY_LOW, PRIORITY_NEGLIGIBLE])) {
$entry["priority"] = PRIORITY_MEDIUM;
}
// Define the maximum durations
$max_duration_defaults = [PRIORITY_CRITICAL => 720, PRIORITY_HIGH => 10, PRIORITY_MEDIUM => 60, PRIORITY_LOW => 180, PRIORITY_NEGLIGIBLE => 720];
$max_duration = $max_duration_defaults[$entry["priority"]];
$argv = json_decode($entry['parameter'], true);
if (!empty($entry['command'])) {
$command = $entry['command'];
} elseif (!empty($argv)) {
$command = array_shift($argv);
} else {
return;
}
$command = basename($command);
// How long is the process already running?
$duration = (time() - strtotime($entry["executed"])) / 60;
if ($duration > $max_duration) {
Logger::notice('Worker process took too much time - killed', ['duration' => number_format($duration, 3), 'max' => $max_duration, 'id' => $entry["id"], 'pid' => $entry["pid"], 'command' => $command]);
posix_kill($entry["pid"], SIGTERM);
// We killed the stale process.
// To avoid a blocking situation we reschedule the process at the beginning of the queue.
// Additionally we are lowering the priority. (But not PRIORITY_CRITICAL)
$new_priority = $entry["priority"];
if ($entry["priority"] == PRIORITY_HIGH) {
$new_priority = PRIORITY_MEDIUM;
} elseif ($entry["priority"] == PRIORITY_MEDIUM) {
$new_priority = PRIORITY_LOW;
} elseif ($entry["priority"] != PRIORITY_CRITICAL) {
$new_priority = PRIORITY_NEGLIGIBLE;
}
$stamp = (float)microtime(true);
DBA::update(
'workerqueue',
['executed' => DBA::NULL_DATETIME, 'created' => DateTimeFormat::utcNow(), 'priority' => $new_priority, 'pid' => 0],
['id' => $entry["id"]]
);
self::$db_duration += (microtime(true) - $stamp);
self::$db_duration_write += (microtime(true) - $stamp);
} else {
Logger::info('Process runtime is okay', ['duration' => number_format($duration, 3), 'max' => $max_duration, 'id' => $entry["id"], 'pid' => $entry["pid"], 'command' => $command]);
}
}
}
DBA::close($entries);
}
/**
* Checks if the number of active workers exceeds the given limits
@ -686,12 +778,12 @@ class Worker
self::$db_duration_stat += (microtime(true) - $stamp);
while ($entry = DBA::fetch($jobs)) {
$stamp = (float)microtime(true);
$running = DBA::count('workerqueue-view', ['priority' => $entry['priority']]);
$running = DBA::count('workerqueue-view', ['priority' => $entry["priority"]]);
self::$db_duration += (microtime(true) - $stamp);
self::$db_duration_stat += (microtime(true) - $stamp);
$idle_workers -= $running;
$waiting_processes += $entry["entries"];
$listitem[$entry['priority']] = $entry['priority'] . ":" . $running . "/" . $entry["entries"];
$listitem[$entry["priority"]] = $entry["priority"] . ":" . $running . "/" . $entry["entries"];
}
DBA::close($jobs);
} else {
@ -703,7 +795,7 @@ class Worker
while ($entry = DBA::fetch($jobs)) {
$idle_workers -= $entry["running"];
$listitem[$entry['priority']] = $entry['priority'].":".$entry["running"];
$listitem[$entry["priority"]] = $entry["priority"].":".$entry["running"];
}
DBA::close($jobs);
}
@ -729,8 +821,8 @@ class Worker
// Are there fewer workers running as possible? Then fork a new one.
if (!DI::config()->get("system", "worker_dont_fork", false) && ($queues > ($active + 1)) && self::entriesExists()) {
Logger::info("There are fewer workers as possible, fork a new worker.", ['active' => $active, 'queues' => $queues]);
if (Worker\Daemon::isMode()) {
Worker\IPC::SetJobState(true);
if (self::isDaemonMode()) {
self::IPCSetJobState(true);
} else {
self::spawnWorker();
}
@ -738,8 +830,8 @@ class Worker
}
// if there are too much worker, we don't spawn a new one.
if (Worker\Daemon::isMode() && ($active > $queues)) {
Worker\IPC::SetJobState(false);
if (self::isDaemonMode() && ($active > $queues)) {
self::IPCSetJobState(false);
}
return $active > $queues;
@ -1030,6 +1122,26 @@ class Worker
self::$db_duration_write += (microtime(true) - $stamp);
}
/**
* Runs the cron processes
*
* @return void
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
private static function runCron()
{
Logger::info('Add cron entries');
// Check for spooled items
self::add(['priority' => PRIORITY_HIGH, 'force_priority' => true], 'SpoolPost');
// Run the cron job that calls all other jobs
self::add(['priority' => PRIORITY_MEDIUM, 'force_priority' => true], 'Cron');
// Cleaning dead processes
self::killStaleWorkers();
}
/**
* Fork a child process
*
@ -1055,11 +1167,11 @@ class Worker
// The parent process continues here
DBA::connect();
Worker\IPC::SetJobState(true, $pid);
self::IPCSetJobState(true, $pid);
Logger::info('Spawned new worker', ['pid' => $pid]);
$cycles = 0;
while (Worker\IPC::JobsExists($pid) && (++$cycles < 100)) {
while (self::IPCJobsExists($pid) && (++$cycles < 100)) {
usleep(10000);
}
@ -1074,7 +1186,7 @@ class Worker
$process = DI::process()->create(getmypid(), basename(__FILE__));
$cycles = 0;
while (!Worker\IPC::JobsExists($process->pid) && (++$cycles < 100)) {
while (!self::IPCJobsExists($process->pid) && (++$cycles < 100)) {
usleep(10000);
}
@ -1084,7 +1196,7 @@ class Worker
self::unclaimProcess($process);
Worker\IPC::SetJobState(false, $process->pid);
self::IPCSetJobState(false, $process->pid);
DI::process()->delete($process);
Logger::info('Worker ended', ['pid' => $process->pid]);
exit();
@ -1099,13 +1211,13 @@ class Worker
*/
public static function spawnWorker($do_cron = false)
{
if (Worker\Daemon::isMode() && DI::config()->get('system', 'worker_fork')) {
if (self::isDaemonMode() && DI::config()->get('system', 'worker_fork')) {
self::forkProcess($do_cron);
} else {
DI::system()->run('bin/worker.php', ['no_cron' => !$do_cron]);
}
if (Worker\Daemon::isMode()) {
Worker\IPC::SetJobState(false);
if (self::isDaemonMode()) {
self::IPCSetJobState(false);
}
}
@ -1175,7 +1287,7 @@ class Worker
$found = DBA::exists('workerqueue', ['command' => $command, 'parameter' => $parameters, 'done' => false]);
$added = 0;
if (!is_int($priority) || !in_array($priority, PRIORITIES)) {
if (!in_array($priority, PRIORITIES)) {
Logger::warning('Invalid priority', ['priority' => $priority, 'command' => $command, 'callstack' => System::callstack(20)]);
$priority = PRIORITY_MEDIUM;
}
@ -1196,11 +1308,11 @@ class Worker
}
// Set the IPC flag to ensure an immediate process execution via daemon
if (Worker\Daemon::isMode()) {
Worker\IPC::SetJobState(true);
if (self::isDaemonMode()) {
self::IPCSetJobState(true);
}
Worker\Daemon::checkState();
self::checkDaemonState();
// Should we quit and wait for the worker to be called as a cronjob?
if ($dont_fork) {
@ -1221,7 +1333,7 @@ class Worker
}
// Quit on daemon mode
if (Worker\Daemon::isMode()) {
if (self::isDaemonMode()) {
return $added;
}
@ -1311,6 +1423,159 @@ class Worker
return true;
}
/**
* Set the flag if some job is waiting
*
* @param boolean $jobs Is there a waiting job?
* @param int $key Key number
* @throws \Exception
*/
public static function IPCSetJobState(bool $jobs, int $key = 0)
{
$stamp = (float)microtime(true);
DBA::replace('worker-ipc', ['jobs' => $jobs, 'key' => $key]);
self::$db_duration += (microtime(true) - $stamp);
self::$db_duration_write += (microtime(true) - $stamp);
}
/**
* Delete a key entry
*
* @param int $key Key number
* @throws \Exception
*/
public static function IPCDeleteJobState(int $key)
{
$stamp = (float)microtime(true);
DBA::delete('worker-ipc', ['key' => $key]);
self::$db_duration += (microtime(true) - $stamp);
self::$db_duration_write += (microtime(true) - $stamp);
}
/**
* Checks if some worker job waits to be executed
*
* @param int $key Key number
* @return bool
* @throws \Exception
*/
public static function IPCJobsExists(int $key = 0)
{
$stamp = (float)microtime(true);
$row = DBA::selectFirst('worker-ipc', ['jobs'], ['key' => $key]);
self::$db_duration += (microtime(true) - $stamp);
// When we don't have a row, no job is running
if (!DBA::isResult($row)) {
return false;
}
return (bool)$row['jobs'];
}
/**
* Checks if the worker is running in the daemon mode.
*
* @return boolean
*/
public static function isDaemonMode()
{
if (!is_null(self::$daemon_mode)) {
return self::$daemon_mode;
}
if (DI::mode()->getExecutor() == Mode::DAEMON) {
return true;
}
$daemon_mode = DI::config()->get('system', 'worker_daemon_mode', false, true);
if ($daemon_mode) {
return $daemon_mode;
}
if (!function_exists('pcntl_fork')) {
self::$daemon_mode = false;
return false;
}
$pidfile = DI::config()->get('system', 'pidfile');
if (empty($pidfile)) {
// No pid file, no daemon
self::$daemon_mode = false;
return false;
}
if (!is_readable($pidfile)) {
// No pid file. We assume that the daemon had been intentionally stopped.
self::$daemon_mode = false;
return false;
}
$pid = intval(file_get_contents($pidfile));
$running = posix_kill($pid, 0);
self::$daemon_mode = $running;
return $running;
}
/**
* Test if the daemon is running. If not, it will be started
*
* @return void
*/
private static function checkDaemonState()
{
if (!DI::config()->get('system', 'daemon_watchdog', false)) {
return;
}
if (!DI::mode()->isNormal()) {
return;
}
// Check every minute if the daemon is running
if (DI::config()->get('system', 'last_daemon_check', 0) + 60 > time()) {
return;
}
DI::config()->set('system', 'last_daemon_check', time());
$pidfile = DI::config()->get('system', 'pidfile');
if (empty($pidfile)) {
// No pid file, no daemon
return;
}
if (!is_readable($pidfile)) {
// No pid file. We assume that the daemon had been intentionally stopped.
return;
}
$pid = intval(file_get_contents($pidfile));
if (posix_kill($pid, 0)) {
Logger::info('Daemon process is running', ['pid' => $pid]);
return;
}
Logger::warning('Daemon process is not running', ['pid' => $pid]);
self::spawnDaemon();
}
/**
* Spawn a new daemon process
*
* @return void
*/
private static function spawnDaemon()
{
Logger::notice('Starting new daemon process');
$command = 'bin/daemon.php';
$a = DI::app();
DI::system()->run($command, ['start']);
Logger::notice('New daemon process started');
}
/**
* Check if the system is inside the defined maintenance window
*

View File

@ -1,182 +0,0 @@
<?php
/**
* @copyright Copyright (C) 2010-2022, 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\Core\Worker;
use Friendica\Core\Logger;
use Friendica\Core\Worker;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\Post;
use Friendica\Protocol\ActivityPub;
use Friendica\Util\DateTimeFormat;
/**
* Contains the class for jobs that are executed in an interval
*/
class Cron
{
/**
* Runs the cron processes
*
* @return void
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function run()
{
Logger::info('Add cron entries');
// Check for spooled items
Worker::add(['priority' => PRIORITY_HIGH, 'force_priority' => true], 'SpoolPost');
// Run the cron job that calls all other jobs
Worker::add(['priority' => PRIORITY_MEDIUM, 'force_priority' => true], 'Cron');
// Cleaning dead processes
self::killStaleWorkers();
// Remove old entries from the workerqueue
self::cleanWorkerQueue();
// Directly deliver or requeue posts
self::deliverPosts();
}
/**
* fix the queue entry if the worker process died
*
* @return void
* @throws \Exception
*/
public static function killStaleWorkers()
{
$entries = DBA::select(
'workerqueue',
['id', 'pid', 'executed', 'priority', 'command', 'parameter'],
['NOT `done` AND `pid` != 0'],
['order' => ['priority', 'retrial', 'created']]
);
while ($entry = DBA::fetch($entries)) {
if (!posix_kill($entry["pid"], 0)) {
DBA::update('workerqueue', ['executed' => DBA::NULL_DATETIME, 'pid' => 0], ['id' => $entry["id"]]);
} else {
// Kill long running processes
// Define the maximum durations
$max_duration_defaults = [PRIORITY_CRITICAL => 720, PRIORITY_HIGH => 10, PRIORITY_MEDIUM => 60, PRIORITY_LOW => 180, PRIORITY_NEGLIGIBLE => 720];
$max_duration = $max_duration_defaults[$entry['priority']];
$argv = json_decode($entry['parameter'], true);
if (!empty($entry['command'])) {
$command = $entry['command'];
} elseif (!empty($argv)) {
$command = array_shift($argv);
} else {
return;
}
$command = basename($command);
// How long is the process already running?
$duration = (time() - strtotime($entry["executed"])) / 60;
if ($duration > $max_duration) {
Logger::notice('Worker process took too much time - killed', ['duration' => number_format($duration, 3), 'max' => $max_duration, 'id' => $entry["id"], 'pid' => $entry["pid"], 'command' => $command]);
posix_kill($entry["pid"], SIGTERM);
// We killed the stale process.
// To avoid a blocking situation we reschedule the process at the beginning of the queue.
// Additionally we are lowering the priority. (But not PRIORITY_CRITICAL)
$new_priority = $entry['priority'];
if ($entry['priority'] == PRIORITY_HIGH) {
$new_priority = PRIORITY_MEDIUM;
} elseif ($entry['priority'] == PRIORITY_MEDIUM) {
$new_priority = PRIORITY_LOW;
} elseif ($entry['priority'] != PRIORITY_CRITICAL) {
$new_priority = PRIORITY_NEGLIGIBLE;
}
DBA::update('workerqueue', ['executed' => DBA::NULL_DATETIME, 'created' => DateTimeFormat::utcNow(), 'priority' => $new_priority, 'pid' => 0], ['id' => $entry["id"]]
);
} else {
Logger::info('Process runtime is okay', ['duration' => number_format($duration, 3), 'max' => $max_duration, 'id' => $entry["id"], 'pid' => $entry["pid"], 'command' => $command]);
}
}
}
DBA::close($entries);
}
/**
* Remove old entries from the workerqueue
*
* @return void
*/
private static function cleanWorkerQueue()
{
DBA::delete('workerqueue', ["`done` AND `executed` < ?", DateTimeFormat::utc('now - 1 hour')]);
// Optimizing this table only last seconds
if (DI::config()->get('system', 'optimize_tables')) {
// We are acquiring the two locks from the worker to avoid locking problems
if (DI::lock()->acquire(Worker::LOCK_PROCESS, 10)) {
if (DI::lock()->acquire(Worker::LOCK_WORKER, 10)) {
DBA::e("OPTIMIZE TABLE `workerqueue`");
DBA::e("OPTIMIZE TABLE `process`");
DI::lock()->release(Worker::LOCK_WORKER);
}
DI::lock()->release(Worker::LOCK_PROCESS);
}
}
}
/**
* Directly deliver AP messages or requeue them.
*
* This function is placed here as a safeguard. Even when the worker queue is completely blocked, messages will be delivered.
*/
private static function deliverPosts()
{
$deliveries = DBA::p("SELECT `item-uri`.`uri` AS `inbox`, MAX(`failed`) AS `failed` FROM `post-delivery` INNER JOIN `item-uri` ON `item-uri`.`id` = `post-delivery`.`inbox-id` GROUP BY `inbox`");
while ($delivery = DBA::fetch($deliveries)) {
if ($delivery['failed'] == 0) {
$result = ActivityPub\Delivery::deliver($delivery['inbox']);
Logger::info('Directly deliver inbox', ['inbox' => $delivery['inbox'], 'result' => $result['success']]);
continue;
} elseif ($delivery['failed'] < 3) {
$priority = PRIORITY_HIGH;
} elseif ($delivery['failed'] < 6) {
$priority = PRIORITY_MEDIUM;
} elseif ($delivery['failed'] < 8) {
$priority = PRIORITY_LOW;
} else {
$priority = PRIORITY_NEGLIGIBLE;
}
if ($delivery['failed'] >= DI::config()->get('system', 'worker_defer_limit')) {
Logger::info('Removing failed deliveries', ['inbox' => $delivery['inbox'], 'failed' => $delivery['failed']]);
Post\Delivery::removeFailed($delivery['inbox']);
}
if (Worker::add($priority, 'APDelivery', '', 0, $delivery['inbox'], 0)) {
Logger::info('Missing APDelivery worker added for inbox', ['inbox' => $delivery['inbox'], 'failed' => $delivery['failed'], 'priority' => $priority]);
}
}
}
}

View File

@ -1,135 +0,0 @@
<?php
/**
* @copyright Copyright (C) 2010-2022, 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\Core\Worker;
use Friendica\App\Mode;
use Friendica\Core\Logger;
use Friendica\DI;
/**
* Contains the class for the worker background job processing
*/
class Daemon
{
private static $mode = null;
/**
* Checks if the worker is running in the daemon mode.
*
* @return boolean
*/
public static function isMode()
{
if (!is_null(self::$mode)) {
return self::$mode;
}
if (DI::mode()->getExecutor() == Mode::DAEMON) {
return true;
}
$daemon_mode = DI::config()->get('system', 'worker_daemon_mode', false, true);
if ($daemon_mode) {
return $daemon_mode;
}
if (!function_exists('pcntl_fork')) {
self::$mode = false;
return false;
}
$pidfile = DI::config()->get('system', 'pidfile');
if (empty($pidfile)) {
// No pid file, no daemon
self::$mode = false;
return false;
}
if (!is_readable($pidfile)) {
// No pid file. We assume that the daemon had been intentionally stopped.
self::$mode = false;
return false;
}
$pid = intval(file_get_contents($pidfile));
$running = posix_kill($pid, 0);
self::$mode = $running;
return $running;
}
/**
* Test if the daemon is running. If not, it will be started
*
* @return void
*/
public static function checkState()
{
if (!DI::config()->get('system', 'daemon_watchdog', false)) {
return;
}
if (!DI::mode()->isNormal()) {
return;
}
// Check every minute if the daemon is running
if (DI::config()->get('system', 'last_daemon_check', 0) + 60 > time()) {
return;
}
DI::config()->set('system', 'last_daemon_check', time());
$pidfile = DI::config()->get('system', 'pidfile');
if (empty($pidfile)) {
// No pid file, no daemon
return;
}
if (!is_readable($pidfile)) {
// No pid file. We assume that the daemon had been intentionally stopped.
return;
}
$pid = intval(file_get_contents($pidfile));
if (posix_kill($pid, 0)) {
Logger::info('Daemon process is running', ['pid' => $pid]);
return;
}
Logger::warning('Daemon process is not running', ['pid' => $pid]);
self::spawn();
}
/**
* Spawn a new daemon process
*
* @return void
*/
private static function spawn()
{
Logger::notice('Starting new daemon process');
DI::system()->run('bin/daemon.php', ['start']);
Logger::notice('New daemon process started');
}
}

View File

@ -1,74 +0,0 @@
<?php
/**
* @copyright Copyright (C) 2010-2022, 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\Core\Worker;
use Friendica\Database\DBA;
/**
* Contains the class for the inter process communication
*/
class IPC
{
/**
* Set the flag if some job is waiting
*
* @param boolean $jobs Is there a waiting job?
* @param int $key Key number
* @throws \Exception
*/
public static function SetJobState(bool $jobs, int $key = 0)
{
$stamp = (float)microtime(true);
DBA::replace('worker-ipc', ['jobs' => $jobs, 'key' => $key]);
}
/**
* Delete a key entry
*
* @param int $key Key number
* @throws \Exception
*/
public static function DeleteJobState(int $key)
{
$stamp = (float)microtime(true);
DBA::delete('worker-ipc', ['key' => $key]);
}
/**
* Checks if some worker job waits to be executed
*
* @param int $key Key number
* @return bool
* @throws \Exception
*/
public static function JobsExists(int $key = 0)
{
$row = DBA::selectFirst('worker-ipc', ['jobs'], ['key' => $key]);
// When we don't have a row, no job is running
if (!DBA::isResult($row)) {
return false;
}
return (bool)$row['jobs'];
}
}

View File

@ -1153,7 +1153,7 @@ class Database
*
* @return boolean Was the command executed successfully?
*/
public function transaction(): bool
public function transaction()
{
if (!$this->performCommit()) {
return false;
@ -1790,32 +1790,4 @@ class Database
{
array_walk($arr, [$this, 'escapeArrayCallback'], $add_quotation);
}
/**
* Replaces a string in the provided fields of the provided table
*
* @param string $table_name
* @param array $fields List of field names in the provided table
* @param string $search
* @param string $replace
* @throws \Exception
*/
public function replaceInTableFields(string $table_name, array $fields, string $search, string $replace)
{
$search = $this->escape($search);
$replace = $this->escape($replace);
$upd = [];
foreach ($fields as $field) {
$field = DBA::quoteIdentifier($field);
$upd[] = "$field = REPLACE($field, '$search', '$replace')";
}
$upds = implode(', ', $upd);
$r = $this->e(sprintf("UPDATE %s SET %s;", $table_name, $upds));
if (!$this->isResult($r)) {
throw new \RuntimeException("Failed updating `$table_name`: " . $this->errorMessage());
}
}
}

View File

@ -21,7 +21,7 @@
namespace Friendica\Model;
use Friendica\Contact\Avatar;
use Friendica\App\BaseURL;
use Friendica\Contact\Introduction\Exception\IntroductionNotFoundException;
use Friendica\Content\Pager;
use Friendica\Content\Text\HTML;
@ -35,7 +35,6 @@ use Friendica\Core\Worker;
use Friendica\Database\Database;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Module\NoScrape;
use Friendica\Network\HTTPException;
use Friendica\Network\Probe;
use Friendica\Protocol\Activity;
@ -683,7 +682,7 @@ class Contact
*/
public static function updateSelfFromUserID($uid, $update_avatar = false)
{
$fields = ['id', 'uri-id', 'name', 'nick', 'location', 'about', 'keywords', 'avatar', 'prvkey', 'pubkey', 'manually-approve',
$fields = ['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'];
$self = DBA::selectFirst('contact', $fields, ['uid' => $uid, 'self' => true]);
@ -715,7 +714,6 @@ class Contact
// it seems as if ported accounts can have wrong values, so we make sure that now everything is fine.
$fields['url'] = DI::baseUrl() . '/profile/' . $user['nickname'];
$fields['nurl'] = Strings::normaliseLink($fields['url']);
$fields['uri-id'] = ItemURI::getIdByURI($fields['url']);
$fields['addr'] = $user['nickname'] . '@' . substr(DI::baseUrl(), strpos(DI::baseUrl(), '://') + 3);
$fields['request'] = DI::baseUrl() . '/dfrn_request/' . $user['nickname'];
$fields['notify'] = DI::baseUrl() . '/dfrn_notify/' . $user['nickname'];
@ -773,10 +771,10 @@ class Contact
$fields['updated'] = DateTimeFormat::utcNow();
self::update($fields, ['id' => $self['id']]);
// Update the other contacts as well
unset($fields['prvkey']);
$fields['self'] = false;
self::update($fields, ['uri-id' => $self['uri-id'], 'self' => false]);
// Update the public contact as well
$fields['prvkey'] = null;
$fields['self'] = false;
self::update($fields, ['uid' => 0, 'nurl' => $self['nurl']]);
// Update the profile
$fields = [
@ -799,20 +797,14 @@ class Contact
public static function remove($id)
{
// We want just to make sure that we don't delete our "self" contact
$contact = DBA::selectFirst('contact', ['uri-id', 'photo', 'thumb', 'micro', 'uid'], ['id' => $id, 'self' => false]);
$contact = DBA::selectFirst('contact', ['uid'], ['id' => $id, 'self' => false]);
if (!DBA::isResult($contact)) {
return;
}
self::clearFollowerFollowingEndpointCache($contact['uid']);
// Archive the contact
self::update(['archive' => true, 'network' => Protocol::PHANTOM, 'deleted' => true], ['id' => $id]);
if (!DBA::exists('contact', ['uri-id' => $contact['uri-id'], 'deleted' => false])) {
Avatar::deleteCache($contact);
}
// Delete it in the background
Worker::add(PRIORITY_MEDIUM, 'Contact\Remove', $id);
}
@ -835,10 +827,8 @@ class Contact
}
if (in_array($contact['rel'], [self::SHARING, self::FRIEND])) {
$cdata = self::getPublicAndUserContactID($contact['id'], $contact['uid']);
if (!empty($cdata['public'])) {
Worker::add(PRIORITY_HIGH, 'Contact\Unfollow', $cdata['public'], $contact['uid']);
}
$cdata = Contact::getPublicAndUserContactID($contact['id'], $contact['uid']);
Worker::add(PRIORITY_HIGH, 'Contact\Unfollow', $cdata['public'], $contact['uid']);
}
self::removeSharer($contact);
@ -864,10 +854,8 @@ class Contact
}
if (in_array($contact['rel'], [self::FOLLOWER, self::FRIEND])) {
$cdata = self::getPublicAndUserContactID($contact['id'], $contact['uid']);
if (!empty($cdata['public'])) {
Worker::add(PRIORITY_HIGH, 'Contact\RevokeFollow', $cdata['public'], $contact['uid']);
}
$cdata = Contact::getPublicAndUserContactID($contact['id'], $contact['uid']);
Worker::add(PRIORITY_HIGH, 'Contact\RevokeFollow', $cdata['public'], $contact['uid']);
}
self::removeFollower($contact);
@ -890,29 +878,19 @@ class Contact
throw new \InvalidArgumentException('Unexpected public contact record');
}
$cdata = self::getPublicAndUserContactID($contact['id'], $contact['uid']);
$cdata = Contact::getPublicAndUserContactID($contact['id'], $contact['uid']);
if (in_array($contact['rel'], [self::SHARING, self::FRIEND]) && !empty($cdata['public'])) {
if (in_array($contact['rel'], [self::SHARING, self::FRIEND])) {
Worker::add(PRIORITY_HIGH, 'Contact\Unfollow', $cdata['public'], $contact['uid']);
}
if (in_array($contact['rel'], [self::FOLLOWER, self::FRIEND]) && !empty($cdata['public'])) {
if (in_array($contact['rel'], [self::FOLLOWER, self::FRIEND])) {
Worker::add(PRIORITY_HIGH, 'Contact\RevokeFollow', $cdata['public'], $contact['uid']);
}
self::remove($contact['id']);
}
private static function clearFollowerFollowingEndpointCache(int $uid)
{
if (empty($uid)) {
return;
}
DI::cache()->delete(ActivityPub\Transmitter::CACHEKEY_CONTACTS . 'followers:' . $uid);
DI::cache()->delete(ActivityPub\Transmitter::CACHEKEY_CONTACTS . 'following:' . $uid);
DI::cache()->delete(NoScrape::CACHEKEY . $uid);
}
/**
* Marks a contact for archival after a communication issue delay
@ -1482,7 +1460,7 @@ class Contact
$items = Post::toArray(Post::selectForUser(local_user(), $fields, $condition, $params));
if ($pager->getStart() == 0) {
$cdata = self::getPublicAndUserContactID($cid, local_user());
$cdata = Contact::getPublicAndUserContactID($cid, local_user());
if (!empty($cdata['public'])) {
$pinned = Post\Collection::selectToArrayForContact($cdata['public'], Post\Collection::FEATURED, $fields);
$items = array_merge($items, $pinned);
@ -1495,7 +1473,7 @@ class Contact
$items = Post::toArray(Post::selectForUser(local_user(), $fields, $condition, $params));
if ($pager->getStart() == 0) {
$cdata = self::getPublicAndUserContactID($cid, local_user());
$cdata = Contact::getPublicAndUserContactID($cid, local_user());
if (!empty($cdata['public'])) {
$condition = ["`uri-id` IN (SELECT `uri-id` FROM `collection-view` WHERE `cid` = ? AND `type` = ?)",
$cdata['public'], Post\Collection::FEATURED];
@ -1589,24 +1567,16 @@ class Contact
return;
}
if (Network::isLocalLink($contact['url'])) {
return;
}
if (in_array($contact['network'], [Protocol::FEED, Protocol::MAIL]) || DI::config()->get('system', 'cache_contact_avatar')) {
if (!empty($contact['avatar']) && (empty($contact['photo']) || empty($contact['thumb']) || empty($contact['micro']))) {
Logger::info('Adding avatar cache', ['id' => $cid, 'contact' => $contact]);
self::updateAvatar($cid, $contact['avatar'], true);
return;
}
} elseif (Photo::isPhotoURI($contact['photo']) || Photo::isPhotoURI($contact['thumb']) || Photo::isPhotoURI($contact['micro'])) {
Logger::info('Replacing legacy avatar cache', ['id' => $cid, 'contact' => $contact]);
} elseif (!empty($contact['photo']) || !empty($contact['thumb']) || !empty($contact['micro'])) {
Logger::info('Removing avatar cache', ['id' => $cid, 'contact' => $contact]);
self::updateAvatar($cid, $contact['avatar'], true);
return;
} elseif (DI::config()->get('system', 'avatar_cache') && (empty($contact['photo']) || empty($contact['thumb']) || empty($contact['micro']))) {
Logger::info('Adding avatar cache file', ['id' => $cid, 'contact' => $contact]);
self::updateAvatar($cid, $contact['avatar'], true);
return;
}
}
@ -1623,27 +1593,6 @@ class Contact
private static function getAvatarPath(array $contact, string $size, $no_update = false)
{
$contact = self::checkAvatarCacheByArray($contact, $no_update);
if (DI::config()->get('system', 'avatar_cache')) {
switch ($size) {
case Proxy::SIZE_MICRO:
if (!empty($contact['micro']) && !Photo::isPhotoURI($contact['micro'])) {
return $contact['micro'];
}
break;
case Proxy::SIZE_THUMB:
if (!empty($contact['thumb']) && !Photo::isPhotoURI($contact['thumb'])) {
return $contact['thumb'];
}
break;
case Proxy::SIZE_SMALL:
if (!empty($contact['photo']) && !Photo::isPhotoURI($contact['photo'])) {
return $contact['photo'];
}
break;
}
}
return self::getAvatarUrlForId($contact['id'], $size, $contact['updated'] ?? '');
}
@ -1708,9 +1657,7 @@ class Contact
return $contact;
}
$local = !empty($contact['url']) && Network::isLocalLink($contact['url']);
if (!$local && !empty($contact['id']) && !empty($contact['avatar'])) {
if (!empty($contact['id']) && !empty($contact['avatar'])) {
self::updateAvatar($contact['id'], $contact['avatar'], true);
$new_contact = self::getById($contact['id'], $contact_fields);
@ -1718,8 +1665,6 @@ class Contact
// We only update the cache fields
$contact = array_merge($contact, $new_contact);
}
} elseif ($local && !empty($contact['avatar'])) {
return $contact;
}
/// add the default avatars if the fields aren't filled
@ -1854,7 +1799,7 @@ class Contact
{
// We have to fetch the "updated" variable when it wasn't provided
// The parameter can be provided to improve performance
if (empty($updated)) {
if (empty($updated) || empty($guid)) {
$account = DBA::selectFirst('account-user-view', ['updated', 'guid'], ['id' => $cid]);
$updated = $account['updated'] ?? '';
$guid = $account['guid'] ?? '';
@ -1956,7 +1901,7 @@ class Contact
*/
public static function updateAvatar(int $cid, string $avatar, bool $force = false, bool $create_cache = false)
{
$contact = DBA::selectFirst('contact', ['uid', 'avatar', 'photo', 'thumb', 'micro', 'xmpp', 'addr', 'nurl', 'url', 'network', 'uri-id'],
$contact = DBA::selectFirst('contact', ['uid', 'avatar', 'photo', 'thumb', 'micro', 'xmpp', 'addr', 'nurl', 'url', 'network'],
['id' => $cid, 'self' => false]);
if (!DBA::isResult($contact)) {
return;
@ -1997,8 +1942,6 @@ class Contact
}
if (in_array($contact['network'], [Protocol::FEED, Protocol::MAIL]) || $cache_avatar) {
Avatar::deleteCache($contact);
if ($default_avatar && Proxy::isLocalImage($avatar)) {
$fields = ['avatar' => $avatar, 'avatar-date' => DateTimeFormat::utcNow(),
'photo' => $avatar,
@ -2050,8 +1993,9 @@ class Contact
}
} else {
Photo::delete(['uid' => $uid, 'contact-id' => $cid, 'photo-type' => Photo::CONTACT_AVATAR]);
$fields = Avatar::fetchAvatarContact($contact, $avatar, $force);
$update = ($avatar . $fields['photo'] . $fields['thumb'] . $fields['micro'] != $contact['avatar'] . $contact['photo'] . $contact['thumb'] . $contact['micro']) || $force;
$fields = ['avatar' => $avatar, 'avatar-date' => DateTimeFormat::utcNow(),
'photo' => '', 'thumb' => '', 'micro' => ''];
$update = ($avatar != $contact['avatar'] . $contact['photo'] . $contact['thumb'] . $contact['micro']) || $force;
}
if (!$update) {
@ -2330,10 +2274,7 @@ class Contact
if ($uid == 0) {
if ($ret['network'] == Protocol::ACTIVITYPUB) {
$apcontact = APContact::getByURL($ret['url'], false);
if (!empty($apcontact['featured'])) {
Worker::add(PRIORITY_LOW, 'FetchFeaturedPosts', $ret['url']);
}
ActivityPub\Processor::fetchFeaturedPosts($ret['url']);
}
$ret['last-item'] = Probe::getLastUpdate($ret);
@ -2543,11 +2484,6 @@ class Contact
} else {
$probed = true;
$ret = Probe::uri($url, $network, $uid);
// Ensure that the public contact exists
if ($ret['network'] != Protocol::PHANTOM) {
self::getIdForURL($url);
}
}
if (($network != '') && ($ret['network'] != $network)) {
@ -2726,8 +2662,6 @@ class Contact
$contact = DBA::selectFirst('contact', [], ['id' => $cid]);
}
self::clearFollowerFollowingEndpointCache($importer['uid']);
if (!empty($contact)) {
if (!empty($contact['pending'])) {
Logger::info('Pending contact request already exists.', ['url' => $url, 'uid' => $importer['uid']]);
@ -2851,9 +2785,7 @@ class Contact
return;
}
self::clearFollowerFollowingEndpointCache($contact['uid']);
$cdata = self::getPublicAndUserContactID($contact['id'], $contact['uid']);
$cdata = Contact::getPublicAndUserContactID($contact['id'], $contact['uid']);
DI::notification()->deleteForUserByVerb($contact['uid'], Activity::FOLLOW, ['actor-id' => $cdata['public']]);
}
@ -2867,8 +2799,6 @@ class Contact
*/
public static function removeSharer(array $contact)
{
self::clearFollowerFollowingEndpointCache($contact['uid']);
if ($contact['rel'] == self::SHARING || in_array($contact['network'], [Protocol::FEED, Protocol::MAIL])) {
self::remove($contact['id']);
} else {

View File

@ -42,7 +42,6 @@ use Friendica\Util\Map;
use Friendica\Util\Network;
use Friendica\Util\Proxy;
use Friendica\Util\Strings;
use Friendica\Util\Temporal;
use Friendica\Worker\Delivery;
use LanguageDetection\Language;
@ -88,16 +87,14 @@ class Item
'wall', 'private', 'starred', 'origin', 'parent-origin', 'title', 'body', 'language',
'content-warning', 'location', 'coord', 'app', 'rendered-hash', 'rendered-html', 'object',
'allow_cid', 'allow_gid', 'deny_cid', 'deny_gid', 'mention', 'global',
'author-id', 'author-link', 'author-name', 'author-avatar', 'author-network', 'author-updated', 'author-gsid', 'author-addr', 'author-uri-id',
'owner-id', 'owner-link', 'owner-name', 'owner-avatar', 'owner-network', 'owner-contact-type', 'owner-updated',
'author-id', 'author-link', 'author-name', 'author-avatar', 'author-network',
'owner-id', 'owner-link', 'owner-name', 'owner-avatar', 'owner-network', 'owner-contact-type',
'causer-id', 'causer-link', 'causer-name', 'causer-avatar', 'causer-contact-type', 'causer-network',
'contact-id', 'contact-uid', 'contact-link', 'contact-name', 'contact-avatar',
'writable', 'self', 'cid', 'alias',
'event-created', 'event-edited', 'event-start', 'event-finish',
'event-summary', 'event-desc', 'event-location', 'event-type',
'event-nofinish', 'event-ignore', 'event-id',
"question-id", "question-multiple", "question-voters", "question-end-time",
"has-categories", "has-media",
'delivery_queue_count', 'delivery_queue_done', 'delivery_queue_failed'
];
@ -728,7 +725,7 @@ class Item
return GRAVITY_UNKNOWN; // Should not happen
}
public static function insert(array $item, int $notify = 0, bool $post_local = true)
public static function insert(array $item, bool $notify = false, bool $post_local = true)
{
$orig_item = $item;
@ -742,7 +739,7 @@ class Item
$item['protocol'] = Conversation::PARCEL_DIRECT;
$item['direction'] = Conversation::PUSH;
if (is_int($notify) && in_array($notify, PRIORITIES)) {
if (in_array($notify, PRIORITIES)) {
$priority = $notify;
}
} else {
@ -1082,7 +1079,7 @@ class Item
if (in_array($item['network'], [Protocol::ACTIVITYPUB, Protocol::DFRN])) {
$content_warning = BBCode::getAbstract($item['body'], Protocol::ACTIVITYPUB);
if (!empty($content_warning) && empty($item['content-warning'])) {
$item['content-warning'] = BBCode::toPlaintext($content_warning);
$item['content-warning'] = $content_warning;
}
}
@ -1218,30 +1215,9 @@ class Item
Worker::add(['priority' => $priority, 'dont_fork' => true], 'Notifier', $notify_type, (int)$posted_item['uri-id'], (int)$posted_item['uid']);
}
// Fill the cache with the rendered content.
if (in_array($posted_item['gravity'], [GRAVITY_PARENT, GRAVITY_COMMENT]) && ($posted_item['uid'] == 0)) {
self::updateDisplayCache($posted_item['uri-id']);
}
if ($posted_item['origin'] && ($posted_item['uid'] != 0) && in_array($posted_item['gravity'], [GRAVITY_PARENT, GRAVITY_COMMENT])) {
DI::cache()->delete(ActivityPub\Transmitter::CACHEKEY_OUTBOX . $posted_item['uid']);
}
return $post_user_id;
}
/**
* Update the display cache
*
* @param integer $uri_id
* @return void
*/
public static function updateDisplayCache(int $uri_id)
{
$item = Post::selectFirst(self::DISPLAY_FIELDLIST, ['uri-id' => $uri_id]);
self::prepareBody($item, false, false, true);
}
/**
* Change the owner of a parent item if it had been shared by a forum
*
@ -1449,7 +1425,7 @@ class Item
return 0;
}
if (($uri_id != $item['thr-parent-id']) && (($item['gravity'] == GRAVITY_COMMENT) || $is_reshare) && !Post::exists(['uri-id' => $item['thr-parent-id'], 'uid' => $uid])) {
if ((($item['gravity'] == GRAVITY_COMMENT) || $is_reshare) && !Post::exists(['uri-id' => $item['thr-parent-id'], 'uid' => $uid])) {
// Fetch the origin user for the post
$origin_uid = self::GetOriginUidForUriId($item['thr-parent-id'], $uid);
if (is_null($origin_uid)) {
@ -2716,7 +2692,7 @@ class Item
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
* @todo Remove reference, simply return "rendered-html" and "rendered-hash"
*/
private static function putInCache(&$item)
public static function putInCache(&$item)
{
// Save original body to prevent addons to modify it
$body = $item['body'];
@ -2729,6 +2705,8 @@ class Item
|| $rendered_hash != hash('md5', BBCode::VERSION . '::' . $body)
|| DI::config()->get('system', 'ignore_cache')
) {
self::addRedirToImageTags($item);
$item['rendered-html'] = BBCode::convertForUriId($item['uri-id'], $item['body']);
$item['rendered-hash'] = hash('md5', BBCode::VERSION . '::' . $body);
@ -2753,6 +2731,31 @@ class Item
$item['body'] = $body;
}
/**
* Find any non-embedded images in private items and add redir links to them
*
* @param array &$item The field array of an item row
*/
private static function addRedirToImageTags(array &$item)
{
$app = DI::app();
$matches = [];
$cnt = preg_match_all('|\[img\](http[^\[]*?/photo/[a-fA-F0-9]+?(-[0-9]\.[\w]+?)?)\[\/img\]|', $item['body'], $matches, PREG_SET_ORDER);
if ($cnt) {
foreach ($matches as $mtch) {
if (strpos($mtch[1], '/redir') !== false) {
continue;
}
if ((local_user() == $item['uid']) && ($item['private'] == self::PRIVATE) && ($item['contact-id'] != $app->getContactId()) && ($item['network'] == Protocol::DFRN)) {
$img_url = 'redir/' . $item['contact-id'] . '?url=' . urlencode($mtch[1]);
$item['body'] = str_replace($mtch[0], '[img]' . $img_url . '[/img]', $item['body']);
}
}
}
}
/**
* Given an item array, convert the body element from bbcode to html and add smilie icons.
* If attach is true, also add icons for item attachments.
@ -2760,7 +2763,6 @@ class Item
* @param array $item
* @param boolean $attach
* @param boolean $is_preview
* @param boolean $only_cache
* @return string item body html
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
* @throws \ImagickException
@ -2769,7 +2771,7 @@ class Item
* @hook prepare_body ('item'=>item array, 'html'=>body string, 'is_preview'=>boolean, 'filter_reasons'=>string array) after first bbcode to html
* @hook prepare_body_final ('item'=>item array, 'html'=>body string) after attach icons and blockquote special case handling (spoiler, author)
*/
public static function prepareBody(array &$item, $attach = false, $is_preview = false, $only_cache = false)
public static function prepareBody(array &$item, $attach = false, $is_preview = false)
{
$a = DI::app();
Hook::callAll('prepare_body_init', $item);
@ -2790,10 +2792,10 @@ class Item
$body = $item['body'] ?? '';
$shared = BBCode::fetchShareAttributes($body);
if (!empty($shared['guid'])) {
$shared_item = Post::selectFirst(['uri-id', 'plink', 'has-media'], ['guid' => $shared['guid']]);
$shared_item = Post::selectFirst(['uri-id', 'plink'], ['guid' => $shared['guid']]);
$shared_uri_id = $shared_item['uri-id'] ?? 0;
$shared_links = [strtolower($shared_item['plink'] ?? '')];
$shared_attachments = Post\Media::splitAttachments($shared_uri_id, $shared['guid'], [], $shared_item['has-media'] ?? false);
$shared_attachments = Post\Media::splitAttachments($shared_uri_id, $shared['guid']);
$shared_links = array_merge($shared_links, array_column($shared_attachments['visual'], 'url'));
$shared_links = array_merge($shared_links, array_column($shared_attachments['link'], 'url'));
$shared_links = array_merge($shared_links, array_column($shared_attachments['additional'], 'url'));
@ -2802,7 +2804,7 @@ class Item
$shared_uri_id = 0;
$shared_links = [];
}
$attachments = Post\Media::splitAttachments($item['uri-id'], $item['guid'] ?? '', $shared_links, $item['has-media']);
$attachments = Post\Media::splitAttachments($item['uri-id'], $item['guid'] ?? '', $shared_links);
$item['body'] = self::replaceVisualAttachments($attachments, $item['body'] ?? '');
$item['body'] = preg_replace("/\s*\[attachment .*?\].*?\[\/attachment\]\s*/ism", "\n", $item['body']);
@ -2810,10 +2812,6 @@ class Item
$item['body'] = $body;
$s = $item["rendered-html"];
if ($only_cache) {
return;
}
// Compile eventual content filter reasons
$filter_reasons = [];
if (!$is_preview && public_contact() != $item['author-id']) {
@ -2859,7 +2857,6 @@ class Item
$s = self::addVisualAttachments($attachments, $item, $s, false);
$s = self::addLinkAttachment($item['uri-id'], $attachments, $body, $s, false, $shared_links);
$s = self::addNonVisualAttachments($attachments, $item, $s, false);
$s = self::addQuestions($item, $s);
// Map.
if (strpos($s, '<div class="map">') !== false && !empty($item['coord'])) {
@ -2930,17 +2927,9 @@ class Item
foreach ($attachments['visual'] as $attachment) {
if (!empty($attachment['preview'])) {
$proxy = Post\Media::getPreviewUrlForId($attachment['id'], Proxy::SIZE_LARGE);
$search = ['[img=' . $attachment['preview'] . ']', ']' . $attachment['preview'] . '[/img]'];
$replace = ['[img=' . $proxy . ']', ']' . $proxy . '[/img]'];
$body = str_replace($search, $replace, $body);
$body = str_replace($attachment['preview'], Post\Media::getPreviewUrlForId($attachment['id'], Proxy::SIZE_LARGE), $body);
} elseif ($attachment['filetype'] == 'image') {
$proxy = Post\Media::getUrlForId($attachment['id']);
$search = ['[img=' . $attachment['url'] . ']', ']' . $attachment['url'] . '[/img]'];
$replace = ['[img=' . $proxy . ']', ']' . $proxy . '[/img]'];
$body = str_replace($search, $replace, $body);
$body = str_replace($attachment['url'], Post\Media::getUrlForId($attachment['id']), $body);
}
}
DI::profiler()->stopRecording();
@ -3193,50 +3182,6 @@ class Item
return $content;
}
private static function addQuestions(array $item, string $content)
{
DI::profiler()->startRecording('rendering');
if (!empty($item['question-id'])) {
$question = [
'id' => $item['question-id'],
'multiple' => $item['question-multiple'],
'voters' => $item['question-voters'],
'endtime' => $item['question-end-time']
];
$options = Post\QuestionOption::getByURIId($item['uri-id']);
foreach ($options as $key => $option) {
$percent = $question['voters'] ? ($option['replies'] / $question['voters'] * 100) : 0;
$options[$key]['percent'] = $percent;
if ($question['voters'] > 0) {
$options[$key]['vote'] = DI::l10n()->t('%s (%d%s, %d votes)', $option['name'], round($percent, 1), '%', $option['replies']);
} else {
$options[$key]['vote'] = DI::l10n()->t('%s (%d votes)', $option['name'], $option['replies']);
}
}
if (!empty($question['voters']) && !empty($question['endtime'])) {
$summary = DI::l10n()->t('%d voters. Poll end: %s', $question['voters'], Temporal::getRelativeDate($question['endtime']));
} elseif (!empty($question['voters'])) {
$summary = DI::l10n()->t('%d voters.', $question['voters']);
} elseif (!empty($question['endtime'])) {
$summary = DI::l10n()->t('Poll end: %s', Temporal::getRelativeDate($question['endtime']));
} else {
$summary = '';
}
$content .= Renderer::replaceMacros(Renderer::getMarkupTemplate('content/question.tpl'), [
'$question' => $question,
'$options' => $options,
'$summary' => $summary,
]);
}
DI::profiler()->stopRecording();
return $content;
}
/**
* get private link for item
*
@ -3260,12 +3205,6 @@ class Item
'orig_title' => DI::l10n()->t('View on separate page'),
];
if (!empty($plink) && ($item['private'] == self::PRIVATE)) {
$author = ['uid' => 0, 'id' => $item['author-id'],
'network' => $item['author-network'], 'url' => $item['author-link']];
$plink = Contact::magicLinkByContact($author, $plink);
}
if (!empty($plink)) {
$ret['href'] = DI::baseUrl()->remove($plink);
$ret['title'] = DI::l10n()->t('Link to source');

View File

@ -704,19 +704,10 @@ class Photo
}
$image_uri = substr($image_uri, strrpos($image_uri, '/') + 1);
$image_uri = substr($image_uri, 0, strpos($image_uri, '-'));
return trim($image_uri);
}
/**
* Checks if the given URL is a local photo.
* Since it is meant for time critical occasions, the check is done without any database requests.
*
* @param string $url
* @return boolean
*/
public static function isPhotoURI(string $url): bool
{
return !empty(self::ridFromURI($url));
if (!strlen($image_uri)) {
return '';
}
return $image_uri;
}
/**

View File

@ -111,11 +111,6 @@ class Category
return array_column($tags, 'name');
}
public static function existsForURIId(int $uri_id, int $uid)
{
return DBA::exists('post-category', ['uri-id' => $uri_id, 'uid' => $uid]);
}
/**
* Generates an array of files or categories of a given uri-id
*

View File

@ -24,8 +24,6 @@ namespace Friendica\Model\Post;
use Friendica\Database\DBA;
use BadMethodCallException;
use Friendica\Database\Database;
use Friendica\DI;
use Friendica\Protocol\ActivityPub;
class Collection
{
@ -36,19 +34,14 @@ class Collection
*
* @param integer $uri_id
* @param integer $type
* @param integer $cache_uid If set to a non zero value, the featured cache is cleared
*/
public static function add(int $uri_id, int $type, int $cache_uid = 0)
public static function add(int $uri_id, int $type)
{
if (empty($uri_id)) {
throw new BadMethodCallException('Empty URI_id');
}
DBA::insert('post-collection', ['uri-id' => $uri_id, 'type' => $type], Database::INSERT_IGNORE);
if (!empty($cache_uid) && ($type == self::FEATURED)) {
DI::cache()->delete(ActivityPub\Transmitter::CACHEKEY_FEATURED . $cache_uid);
}
}
/**
@ -56,19 +49,14 @@ class Collection
*
* @param integer $uri_id
* @param integer $type
* @param integer $cache_uid If set to a non zero value, the featured cache is cleared
*/
public static function remove(int $uri_id, int $type, int $cache_uid = 0)
public static function remove(int $uri_id, int $type)
{
if (empty($uri_id)) {
throw new BadMethodCallException('Empty URI_id');
}
DBA::delete('post-collection', ['uri-id' => $uri_id, 'type' => $type]);
if (!empty($cache_uid) && ($type == self::FEATURED)) {
DI::cache()->delete(ActivityPub\Transmitter::CACHEKEY_FEATURED . $cache_uid);
}
}
/**

View File

@ -1,100 +0,0 @@
<?php
/**
* @copyright Copyright (C) 2010-2022, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
namespace Friendica\Model\Post;
use Friendica\Database\DBA;
use BadMethodCallException;
use Friendica\Database\Database;
use Friendica\DI;
use Friendica\Model\ItemURI;
class Delivery
{
/**
* Add a post to an inbox
*
* @param integer $uri_id
* @param string $inbox
* @param string $created
* @param array %receivers
*/
public static function add(int $uri_id, int $uid, string $inbox, string $created, string $command, array $receivers)
{
if (empty($uri_id)) {
throw new BadMethodCallException('Empty URI_id');
}
$fields = ['uri-id' => $uri_id, 'uid' => $uid, 'inbox-id' => ItemURI::getIdByURI($inbox),
'created' => $created, 'command' => $command, 'receivers' => json_encode($receivers)];
DBA::insert('post-delivery', $fields, Database::INSERT_IGNORE);
}
/**
* Remove post from an inbox after delivery
*
* @param integer $uri_id
* @param string $inbox
*/
public static function remove(int $uri_id, string $inbox)
{
DBA::delete('post-delivery', ['uri-id' => $uri_id, 'inbox-id' => ItemURI::getIdByURI($inbox)]);
}
/**
* Remove failed posts for an inbox
*
* @param string $inbox
*/
public static function removeFailed(string $inbox)
{
DBA::delete('post-delivery', ["`inbox-id` = ? AND `failed` >= ?", ItemURI::getIdByURI($inbox), DI::config()->get('system', 'worker_defer_limit')]);
}
/**
* Increment "failed" counter for the given inbox and post
*
* @param integer $uri_id
* @param string $inbox
*/
public static function incrementFailed(int $uri_id, string $inbox)
{
return DBA::e('UPDATE `post-delivery` SET `failed` = `failed` + 1 WHERE `uri-id` = ? AND `inbox-id` = ?', $uri_id, ItemURI::getIdByURI($inbox));
}
public static function selectForInbox(string $inbox)
{
$rows = DBA::select('post-delivery', [], ["`inbox-id` = ? AND `failed` < ?", ItemURI::getIdByURI($inbox), DI::config()->get('system', 'worker_defer_limit')], ['order' => ['created']]);
$deliveries = [];
while ($row = DBA::fetch($rows)) {
if (!empty($row['receivers'])) {
$row['receivers'] = json_decode($row['receivers'], true);
} else {
$row['receivers'] = [];
}
$deliveries[] = $row;
}
DBA::close($rows);
return $deliveries;
}
}

View File

@ -1,71 +0,0 @@
<?php
/**
* @copyright Copyright (C) 2010-2022, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
namespace Friendica\Model\Post;
use Friendica\Core\Logger;
use Friendica\Database\DBA;
use Friendica\Database\Database;
use Friendica\Database\DBStructure;
use Friendica\Model\Post;
class History
{
/**
* Add a post to the history before it is changed
*
* @param integer $uri_id
* @param array $item
*/
public static function add(int $uri_id, array $item)
{
$allfields = DBStructure::definition('', false);
$fields = array_keys($allfields['post-history']['fields']);
$post = Post::selectFirstPost($fields, ['uri-id' => $uri_id]);
if (empty($post)) {
Logger::warning('Post not found', ['uri-id' => $uri_id]);
return;
}
if ($item['edited'] <= $post['edited']) {
Logger::info('New edit date is not newer than the old one', ['uri-id' => $uri_id, 'old' => $post['edited'], 'new' => $item['edited']]);
return;
}
$update = false;
$changed = DBStructure::getFieldsForTable('post-history', $item);
unset($changed['uri-id']);
unset($changed['edited']);
foreach ($changed as $field => $content) {
if ($content != $post[$field]) {
$update = true;
}
}
if ($update) {
DBA::insert('post-history', $post, Database::INSERT_IGNORE);
Logger::info('Added history', ['uri-id' => $uri_id, 'edited' => $post['edited']]);
} else {
Logger::info('No content fields had been changed', ['uri-id' => $uri_id, 'edited' => $post['edited']]);
}
}
}

View File

@ -547,17 +547,12 @@ class Media
* @param int $uri_id
* @param string $guid
* @param array $links list of links that shouldn't be added
* @param bool $has_media
* @return array attachments
*/
public static function splitAttachments(int $uri_id, string $guid = '', array $links = [], bool $has_media = true)
public static function splitAttachments(int $uri_id, string $guid = '', array $links = [])
{
$attachments = ['visual' => [], 'link' => [], 'additional' => []];
if (!$has_media) {
return $attachments;
}
$media = self::getByURIId($uri_id);
if (empty($media)) {
return $attachments;

View File

@ -22,9 +22,11 @@
namespace Friendica\Model\Post;
use \BadMethodCallException;
use Friendica\Core\Protocol;
use Friendica\Database\Database;
use Friendica\Database\DBA;
use Friendica\Database\DBStructure;
use Friendica\Model\Post;
class Thread
{

View File

@ -308,7 +308,7 @@ class UserNotification
return;
}
$notification = DI::notificationFactory()->createForUser(
$notification = (new Notifications\Factory\Notification(DI::baseUrl(), DI::l10n(), DI::localRelationship(), DI::logger()))->createForUser(
$uid,
$item['vid'],
$type,
@ -336,7 +336,7 @@ class UserNotification
*/
public static function insertNotification(int $actor, string $verb, int $uid): bool
{
$notification = DI::notificationFactory()->createForRelationship(
$notification = (new Notifications\Factory\Notification(DI::baseUrl(), DI::l10n(), DI::localRelationship(), DI::logger()))->createForRelationship(
$uid,
$actor,
$verb

View File

@ -220,7 +220,7 @@ class Profile
public static function load(App $a, string $nickname, bool $show_contacts = true)
{
$profile = User::getOwnerDataByNick($nickname);
if (empty($profile) || $profile['account_removed']) {
if (empty($profile)) {
Logger::info('profile error: ' . DI::args()->getQueryString());
return [];
}
@ -235,7 +235,7 @@ class Profile
DI::page()['title'] = $profile['name'] . ' @ ' . DI::config()->get('config', 'sitename');
if (!local_user()) {
if (!DI::pConfig()->get(local_user(), 'system', 'always_my_theme')) {
$a->setCurrentTheme($profile['theme']);
$a->setCurrentMobileTheme(DI::pConfig()->get($a->getProfileOwner(), 'system', 'mobile_theme'));
}
@ -880,17 +880,23 @@ class Profile
*
* Used from within PCSS themes to set theme parameters. If there's a
* profile_uid variable set in App, that is the "page owner" and normally their theme
* settings take precedence; unless a local user is logged in which means they don't
* want to see anybody else's theme settings except their own while on this site.
* settings take precedence; unless a local user sets the "always_my_theme"
* system pconfig, which means they don't want to see anybody else's theme
* settings except their own while on this site.
*
* @param App $a
* @return int user ID
*
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
* @note Returns local_user instead of user ID if "always_my_theme" is set to true
*/
public static function getThemeUid(App $a): int
public static function getThemeUid(App $a)
{
return local_user() ?: $a->getProfileOwner();
$uid = !empty($a->getProfileOwner()) ? intval($a->getProfileOwner()) : 0;
if (local_user() && (DI::pConfig()->get(local_user(), 'system', 'always_my_theme') || !$uid)) {
return local_user();
}
return $uid;
}
/**

View File

@ -143,7 +143,17 @@ class Subscription
{
$type = \Friendica\Factory\Api\Mastodon\Notification::getType($Notification);
if (DI::notify()->NotifyOnDesktop($Notification, $type)) {
$desktop_notification = !in_array($type, [Notification::TYPE_RESHARE, Notification::TYPE_LIKE]);
if (DI::pConfig()->get($Notification->uid, 'system', 'notify_like') && ($type == Notification::TYPE_LIKE)) {
$desktop_notification = true;
}
if (DI::pConfig()->get($Notification->uid, 'system', 'notify_announce') && ($type == Notification::TYPE_RESHARE)) {
$desktop_notification = true;
}
if ($desktop_notification) {
DI::notify()->createFromNotification($Notification);
}

View File

@ -224,18 +224,15 @@ class Tag
{
$fields = ['name' => substr($name, 0, 96), 'url' => $url];
$tag = DBA::selectFirst('tag', ['id', 'type'], $fields);
if (DBA::isResult($tag)) {
if (empty($tag['type']) && !empty($type)) {
DBA::update('tag', ['type' => $type], $fields);
}
return $tag['id'];
}
if (!empty($type)) {
$fields['type'] = $type;
}
$tag = DBA::selectFirst('tag', ['id'], $fields);
if (DBA::isResult($tag)) {
return $tag['id'];
}
DBA::insert('tag', $fields, Database::INSERT_IGNORE);
$tid = DBA::lastInsertId();
if (!empty($tid)) {
@ -276,7 +273,7 @@ class Tag
public static function getFromBody(string $body, string $tags = null)
{
if (is_null($tags)) {
$tags = self::TAG_CHARACTER[self::HASHTAG] . self::TAG_CHARACTER[self::MENTION] . self::TAG_CHARACTER[self::EXCLUSIVE_MENTION];
$tags = self::TAG_CHARACTER[self::HASHTAG] . self::TAG_CHARACTER[self::MENTION] . self::TAG_CHARACTER[self::EXCLUSIVE_MENTION];
}
if (!preg_match_all("/([" . $tags . "])\[url\=([^\[\]]*)\]([^\[\]]*)\[\/url\]/ism", $body, $result, PREG_SET_ORDER)) {
@ -298,29 +295,16 @@ class Tag
{
Logger::info('Check for tags', ['uri-id' => $uriid, 'hash' => $tags, 'callstack' => System::callstack()]);
if (is_null($tags)) {
$tags = self::TAG_CHARACTER[self::HASHTAG] . self::TAG_CHARACTER[self::MENTION] . self::TAG_CHARACTER[self::EXCLUSIVE_MENTION];
$result = self::getFromBody($body, $tags);
if (empty($result)) {
return;
}
// Only remove the shared data from "real" reshares
$shared = BBCode::fetchShareAttributes($body);
if (!empty($shared['guid'])) {
if (preg_match("/\s*\[share .*?\](.*?)\[\/share\]\s*/ism", $body, $matches)) {
$share_body = $matches[1];
}
$body = preg_replace("/\s*\[share .*?\].*?\[\/share\]\s*/ism", '', $body);
}
Logger::info('Found tags', ['uri-id' => $uriid, 'hash' => $tags, 'result' => $result]);
foreach (self::getFromBody($body, $tags) as $tag) {
foreach ($result as $tag) {
self::storeByHash($uriid, $tag[1], $tag[3], $tag[2], $probing);
}
// Search for hashtags in the shared body (but only if hashtags are wanted)
if (!empty($share_body) && (strpos($tags, self::TAG_CHARACTER[self::HASHTAG]) !== false)) {
foreach (self::getFromBody($share_body, self::TAG_CHARACTER[self::HASHTAG]) as $tag) {
self::storeByHash($uriid, $tag[1], $tag[3], $tag[2], $probing);
}
}
}
/**

View File

@ -43,7 +43,6 @@ class Federation extends BaseAdmin
'diaspora' => ['name' => 'Diaspora', 'color' => '#a1a1a1'], // logo is black and white, makes a gray
'funkwhale' => ['name' => 'Funkwhale', 'color' => '#4082B4'], // From the homepage
'gnusocial' => ['name' => 'GNU Social/Statusnet', 'color' => '#a22430'], // dark red from the logo
'gotosocial' => ['name' => 'GoToSocial', 'color' => '#df8958'], // Some color from their mascot
'hometown' => ['name' => 'Hometown', 'color' => '#1f70c1'], // Color from the Patreon page
'hubzilla' => ['name' => 'Hubzilla/Red Matrix', 'color' => '#43488a'], // blue from the logo
'lemmy' => ['name' => 'Lemmy', 'color' => '#00c853'], // Green from the page
@ -155,7 +154,7 @@ class Federation extends BaseAdmin
$versionCounts = self::reformaDiasporaVersions($versionCounts);
} elseif ($platform == 'relay') {
$versionCounts = self::reformatRelayVersions($versionCounts);
} elseif (in_array($platform, ['funkwhale', 'mastodon', 'mobilizon', 'misskey', 'gotosocial'])) {
} elseif (in_array($platform, ['funkwhale', 'mastodon', 'mobilizon', 'misskey'])) {
$versionCounts = self::removeVersionSuffixes($versionCounts);
}

View File

@ -21,7 +21,6 @@
namespace Friendica\Module\Admin;
use Friendica\Core\System;
use Friendica\Module\BaseAdmin;
class PhpInfo extends BaseAdmin
@ -31,6 +30,6 @@ class PhpInfo extends BaseAdmin
self::checkAdminAccess();
phpinfo();
System::exit();
exit();
}
}

View File

@ -22,7 +22,6 @@
namespace Friendica\Module\Admin;
use Friendica\App;
use Friendica\Core\Relocate;
use Friendica\Core\Renderer;
use Friendica\Core\Search;
use Friendica\Core\System;
@ -61,6 +60,74 @@ class Site extends BaseAdmin
return;
}
// relocate
// @TODO This file could benefit from moving this feature away in a Module\Admin\Relocate class for example
if (!empty($_POST['relocate']) && !empty($_POST['relocate_url']) && $_POST['relocate_url'] != "") {
$new_url = $_POST['relocate_url'];
$new_url = rtrim($new_url, "/");
$parsed = @parse_url($new_url);
if (!is_array($parsed) || empty($parsed['host']) || empty($parsed['scheme'])) {
notice(DI::l10n()->t("Can not parse base url. Must have at least <scheme>://<domain>"));
DI::baseUrl()->redirect('admin/site');
}
/* steps:
* replace all "baseurl" to "new_url" in config, profile, term, items and contacts
* send relocate for every local user
* */
$old_url = DI::baseUrl()->get(true);
// Generate host names for relocation the addresses in the format user@address.tld
$new_host = str_replace("http://", "@", Strings::normaliseLink($new_url));
$old_host = str_replace("http://", "@", Strings::normaliseLink($old_url));
function update_table(App $a, $table_name, $fields, $old_url, $new_url)
{
$dbold = DBA::escape($old_url);
$dbnew = DBA::escape($new_url);
$upd = [];
foreach ($fields as $f) {
$upd[] = "`$f` = REPLACE(`$f`, '$dbold', '$dbnew')";
}
$upds = implode(", ", $upd);
$r = DBA::e(sprintf("UPDATE %s SET %s;", $table_name, $upds));
if (!DBA::isResult($r)) {
notice("Failed updating '$table_name': " . DBA::errorMessage());
DI::baseUrl()->redirect('admin/site');
}
}
// update tables
// update profile links in the format "http://server.tld"
update_table($a, "profile", ['photo', 'thumb'], $old_url, $new_url);
update_table($a, "contact", ['photo', 'thumb', 'micro', 'url', 'nurl', 'alias', 'request', 'notify', 'poll', 'confirm', 'poco', 'avatar'], $old_url, $new_url);
update_table($a, "post-content", ['body'], $old_url, $new_url);
// update profile addresses in the format "user@server.tld"
update_table($a, "contact", ['addr'], $old_host, $new_host);
// update config
DI::config()->set('system', 'url', $new_url);
DI::baseUrl()->saveByURL($new_url);
// send relocate
$usersStmt = DBA::select('user', ['uid'], ['account_removed' => false, 'account_expired' => false]);
while ($user = DBA::fetch($usersStmt)) {
Worker::add(PRIORITY_HIGH, 'Notifier', Delivery::RELOCATION, $user['uid']);
}
DBA::close($usersStmt);
info(DI::l10n()->t("Relocation started. Could take a while to complete."));
DI::baseUrl()->redirect('admin/site');
}
// end relocate
$sitename = (!empty($_POST['sitename']) ? trim($_POST['sitename']) : '');
$sender_email = (!empty($_POST['sender_email']) ? trim($_POST['sender_email']) : '');
$banner = (!empty($_POST['banner']) ? trim($_POST['banner']) : false);
@ -445,9 +512,8 @@ class Site extends BaseAdmin
'$no_relay_list' => DI::l10n()->t('The system is not subscribed to any relays at the moment.'),
'$relay_list_title' => DI::l10n()->t('The system is currently subscribed to the following relays:'),
'$relay_list' => Relay::getList(['url']),
'$relocate' => DI::l10n()->t('Relocate Node'),
'$relocate_msg' => DI::l10n()->t('Relocating your node enables you to change the DNS domain of this node and keep all the existing users and posts. This process takes a while and can only be started from the relocate console command like this:'),
'$relocate_cmd' => DI::l10n()->t('(Friendica directory)# bin/console relocate https://newdomain.com'),
'$relocate' => DI::l10n()->t('Relocate Instance'),
'$relocate_warning' => DI::l10n()->t('<strong>Warning!</strong> Advanced function. Could make this server unreachable.'),
'$baseurl' => DI::baseUrl()->get(true),
// name, label, value, help string, extra data...
@ -535,6 +601,8 @@ class Site extends BaseAdmin
'$temppath' => ['temppath', DI::l10n()->t('Temp path'), DI::config()->get('system', 'temppath'), DI::l10n()->t('If you have a restricted system where the webserver can\'t access the system temp path, enter another path here.')],
'$only_tag_search' => ['only_tag_search', DI::l10n()->t('Only search in tags'), DI::config()->get('system', 'only_tag_search'), DI::l10n()->t('On large systems the text search can slow down the system extremely.')],
'$relocate_url' => ['relocate_url', DI::l10n()->t('New base url'), DI::baseUrl()->get(), DI::l10n()->t('Change base url for this server. Sends relocate message to all Friendica and Diaspora* contacts of all users.')],
'$worker_queues' => ['worker_queues', DI::l10n()->t('Maximum number of parallel workers'), DI::config()->get('system', 'worker_queues'), DI::l10n()->t('On shared hosters set this to %d. On larger systems, values of %d are great. Default value is %d.', 5, 20, 10)],
'$worker_fastlane' => ['worker_fastlane', DI::l10n()->t('Enable fastlane'), DI::config()->get('system', 'worker_fastlane'), DI::l10n()->t('When enabed, the fastlane mechanism starts an additional worker if processes with higher priority are blocked by processes of lower priority.')],

View File

@ -46,7 +46,7 @@ class Pin extends BaseApi
DI::mstdnError()->RecordNotFound();
}
Post\Collection::add($this->parameters['id'], Post\Collection::FEATURED, $uid);
Post\Collection::add($this->parameters['id'], Post\Collection::FEATURED);
System::jsonExit(DI::mstdnStatus()->createFromUriId($this->parameters['id'], $uid)->toArray());
}

View File

@ -46,7 +46,7 @@ class Unpin extends BaseApi
DI::mstdnError()->RecordNotFound();
}
Post\Collection::remove($this->parameters['id'], Post\Collection::FEATURED, $uid);
Post\Collection::remove($this->parameters['id'], Post\Collection::FEATURED);
System::jsonExit(DI::mstdnStatus()->createFromUriId($this->parameters['id'], $uid)->toArray());
}

View File

@ -23,7 +23,6 @@ namespace Friendica\Module;
use Friendica\BaseModule;
use Friendica\Core\Logger;
use Friendica\Core\System;
use Friendica\DI;
use Friendica\Model\Attach as MAttach;
@ -73,7 +72,7 @@ class Attach extends BaseModule
}
echo $data;
System::exit();
exit();
// NOTREACHED
}
}

View File

@ -30,7 +30,6 @@ use Friendica\Model;
use Friendica\Network\HTTPException;
use Friendica\Object\Search\ContactResult;
use Friendica\Object\Search\ResultList;
use Friendica\Util\Network;
/**
* Base class for search modules
@ -69,7 +68,7 @@ class BaseSearch extends BaseModule
$header = DI::l10n()->t('People Search - %s', $search);
if (strrpos($search, '@') > 0) {
$results = Search::getContactsFromProbe(Network::convertToIdn($search));
$results = Search::getContactsFromProbe($search);
}
}
@ -79,8 +78,6 @@ class BaseSearch extends BaseModule
$header = DI::l10n()->t('Forum Search - %s', $search);
}
$search = Network::convertToIdn($search);
if (DI::mode()->isMobile()) {
$itemsPerPage = DI::pConfig()->get(local_user(), 'system', 'itemspage_mobile_network',
DI::config()->get('system', 'itemspage_network_mobile'));

View File

@ -24,7 +24,6 @@ namespace Friendica\Module\Contact;
use Friendica\BaseModule;
use Friendica\Core\Renderer;
use Friendica\Core\Session;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\Contact;
@ -108,6 +107,6 @@ class Hovercard extends BaseModule
]);
echo $o;
System::exit();
exit();
}
}

View File

@ -22,7 +22,6 @@
namespace Friendica\Module\Debug;
use Friendica\BaseModule;
use Friendica\Core\System;
use Friendica\DI;
use Friendica\Model\Post;
use Friendica\Network\HTTPException;
@ -49,7 +48,7 @@ class ItemBody extends BaseModule
if (!empty($item)) {
if (DI::mode()->isAjax()) {
echo str_replace("\n", '<br />', $item['body']);
System::exit();
exit();
} else {
return str_replace("\n", '<br />', $item['body']);
}

View File

@ -25,7 +25,6 @@ use Friendica\BaseModule;
use Friendica\Core\System;
use Friendica\DI;
use Friendica\Protocol\Feed as ProtocolFeed;
use Friendica\Network\HTTPException;
/**
* Provides public Atom feeds
@ -43,7 +42,7 @@ use Friendica\Network\HTTPException;
*/
class Feed extends BaseModule
{
protected function rawContent(array $request = [])
protected function content(array $request = []): string
{
$last_update = $request['last_update'] ?? '';
$nocache = !empty($request['nocache']) && local_user();
@ -67,11 +66,6 @@ class Feed extends BaseModule
$type = 'posts';
}
$feed = ProtocolFeed::atom($this->parameters['nickname'], $last_update, 10, $type, $nocache, true);
if (empty($feed)) {
throw new HTTPException\NotFoundException(DI::l10n()->t('User not found.'));
}
System::httpExit($feed, Response::TYPE_ATOM);
System::httpExit(ProtocolFeed::atom($this->parameters['nickname'], $last_update, 10, $type, $nocache, true), Response::TYPE_ATOM);
}
}

View File

@ -360,7 +360,7 @@ class Group extends BaseModule
if ($change) {
$tpl = Renderer::getMarkupTemplate('groupeditor.tpl');
echo Renderer::replaceMacros($tpl, $context);
System::exit();
exit();
}
return Renderer::replaceMacros($tpl, $context);

View File

@ -22,7 +22,6 @@
namespace Friendica\Module\HTTPException;
use Friendica\BaseModule;
use Friendica\Core\System;
use Friendica\DI;
use Friendica\Network\HTTPException;
use Psr\Http\Message\ResponseInterface;
@ -48,7 +47,7 @@ class PageNotFound extends BaseModule
$queryString = $this->server['QUERY_STRING'];
// Stupid browser tried to pre-fetch our Javascript img template. Don't log the event or return anything - just quietly exit.
if (!empty($queryString) && preg_match('/{[0-9]}/', $queryString) !== 0) {
System::exit();
exit();
}
if (!empty($queryString) && ($queryString === 'q=internal_error.html') && isset($dreamhost_error_hack)) {

View File

@ -60,9 +60,9 @@ class Pin extends BaseModule
$pinned = !$item['featured'];
if ($pinned) {
Post\Collection::add($item['uri-id'], Post\Collection::FEATURED, local_user());
Post\Collection::add($item['uri-id'], Post\Collection::FEATURED);
} else {
Post\Collection::remove($item['uri-id'], Post\Collection::FEATURED, local_user());
Post\Collection::remove($item['uri-id'], Post\Collection::FEATURED);
}
// See if we've been passed a return path to redirect to

View File

@ -22,7 +22,6 @@
namespace Friendica\Module;
use Friendica\BaseModule;
use Friendica\Core\Cache\Enum\Duration;
use Friendica\Core\Protocol;
use Friendica\Core\System;
use Friendica\Database\DBA;
@ -36,8 +35,6 @@ use Friendica\Model\User;
*/
class NoScrape extends BaseModule
{
const CACHEKEY = 'noscrape:';
protected function rawContent(array $request = [])
{
$a = DI::app();
@ -58,12 +55,6 @@ class NoScrape extends BaseModule
System::jsonError(404, 'Profile not found');
}
$cachekey = self::CACHEKEY . $owner['uid'];
$result = DI::cache()->get($cachekey);
if (!is_null($result)) {
System::jsonExit($result);
}
$json_info = [
'addr' => $owner['addr'],
'nick' => $which,
@ -135,8 +126,6 @@ class NoScrape extends BaseModule
}
}
DI::cache()->set($cachekey, $json_info, Duration::DAY);
System::jsonExit($json_info);
}
}

View File

@ -187,9 +187,6 @@ class Ping extends BaseModule
$owner = User::getOwnerDataById(local_user());
$navNotifications = array_map(function (Entity\Notification $notification) use ($owner) {
if (!DI::notify()->NotifyOnDesktop($notification)) {
return null;
}
if (($notification->type == Post\UserNotification::TYPE_NONE) && in_array($owner['page-flags'], [User::PAGE_FLAGS_NORMAL, User::PAGE_FLAGS_PRVGROUP])) {
return null;
}

View File

@ -23,7 +23,6 @@ namespace Friendica\Module;
use Friendica\BaseModule;
use Friendica\Content;
use Friendica\Core\System;
use Friendica\DI;
use Friendica\Util\Strings;
@ -44,14 +43,14 @@ class Oembed extends BaseModule
if (DI::args()->getArgv()[1] == 'b2h') {
$url = ["", trim(hex2bin($_REQUEST['url']))];
echo Content\OEmbed::replaceCallback($url);
System::exit();
exit();
}
// Unused form: /oembed/h2b?text=...
if (DI::args()->getArgv()[1] == 'h2b') {
$text = trim(hex2bin($_REQUEST['text']));
echo Content\OEmbed::HTML2BBCode($text);
System::exit();
exit();
}
// @TODO: Replace with parameter from router
@ -69,6 +68,6 @@ class Oembed extends BaseModule
echo $j->html;
echo '</body></html>';
}
System::exit();
exit();
}
}

View File

@ -22,7 +22,6 @@
namespace Friendica\Module;
use Friendica\Core\Hook;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\APContact;
@ -165,7 +164,8 @@ class PermissionTooltip extends \Friendica\BaseModule
} else {
echo $o . $receivers;
}
System::exit();
exit();
}
/**

View File

@ -32,7 +32,6 @@ use Friendica\Model\Post;
use Friendica\Model\Profile;
use Friendica\Core\Storage\Type\ExternalResource;
use Friendica\Core\Storage\Type\SystemResource;
use Friendica\Core\System;
use Friendica\Core\Worker;
use Friendica\Model\User;
use Friendica\Network\HTTPClient\Client\HttpClientAccept;
@ -225,7 +224,7 @@ class Photo extends BaseModule
'output' => number_format($output, 3), 'rest' => number_format($rest, 3)]);
}
System::exit();
exit();
}
private static function getPhotoByid(int $id, $type, $customsize)
@ -305,12 +304,10 @@ class Photo extends BaseModule
$photo = MPhoto::selectFirst([], ['resource-id' => $resourceid], ['order' => ['scale']]);
if (!empty($photo)) {
return $photo;
} else {
$url = $contact['avatar'];
}
} else {
$url = $contact['photo'];
}
// We continue with the avatar link when the photo link is invalid
$url = $contact['avatar'];
} elseif (!empty($contact['avatar'])) {
$url = $contact['avatar'];
}

View File

@ -49,7 +49,7 @@ class Profile extends BaseProfile
protected function rawContent(array $request = [])
{
if (ActivityPub::isRequest()) {
$user = DBA::selectFirst('user', ['uid'], ['nickname' => $this->parameters['nickname'], 'account_removed' => false]);
$user = DBA::selectFirst('user', ['uid'], ['nickname' => $this->parameters['nickname']]);
if (DBA::isResult($user)) {
try {
$data = ActivityPub\Transmitter::getProfile($user['uid']);

View File

@ -202,6 +202,6 @@ class Proxy extends BaseModule
header('Expires: ' . gmdate('D, d M Y H:i:s', time() + (31536000)) . ' GMT');
header('Cache-Control: max-age=31536000');
echo $img->asString();
System::exit();
exit();
}
}

View File

@ -22,7 +22,6 @@
namespace Friendica\Module;
use Friendica\BaseModule;
use Friendica\Core\System;
/**
* Return the default robots.txt
@ -45,6 +44,6 @@ class RobotsTxt extends BaseModule
foreach ($allDisalloweds as $disallowed) {
echo 'Disallow: ' . $disallowed . PHP_EOL;
}
System::exit();
exit();
}
}

View File

@ -38,7 +38,6 @@ use Friendica\Model\Post;
use Friendica\Model\Tag;
use Friendica\Module\BaseSearch;
use Friendica\Network\HTTPException;
use Friendica\Util\Network;
class Index extends BaseSearch
{
@ -227,8 +226,7 @@ class Index extends BaseSearch
*/
private static function tryRedirectToProfile(string $search)
{
$search = Network::convertToIdn($search);
$isUrl = !empty(parse_url($search, PHP_URL_SCHEME));
$isUrl = !empty(parse_url($search, PHP_URL_SCHEME));
$isAddr = (bool)preg_match('/^@?([a-z0-9.-_]+@[a-z0-9.-_:]+)$/i', trim($search), $matches);
if (!$isUrl && !$isAddr) {
@ -276,8 +274,6 @@ class Index extends BaseSearch
return;
}
$search = Network::convertToIdn($search);
if (local_user()) {
// Post URL search
$item_id = Item::fetchByLink($search, local_user());

View File

@ -349,7 +349,7 @@ class Account extends BaseSettings
// "http" or "@" to be present in the string.
// All other fields from the row will be ignored
if ((strpos($csvRow[0], '@') !== false) || in_array(parse_url($csvRow[0], PHP_URL_SCHEME), ['http', 'https'])) {
Worker::add(PRIORITY_MEDIUM, 'AddContact', local_user(), $csvRow[0]);
Worker::add(PRIORITY_LOW, 'AddContact', $_SESSION['uid'], $csvRow[0]);
} else {
Logger::notice('Invalid account', ['url' => $csvRow[0]]);
}

View File

@ -23,7 +23,6 @@ namespace Friendica\Module\Settings;
use Friendica\Core\Hook;
use Friendica\Core\Renderer;
use Friendica\Core\System;
use Friendica\Database\DBA;
use Friendica\Database\DBStructure;
use Friendica\DI;
@ -113,7 +112,8 @@ class UserExport extends BaseSettings
self::exportContactsAsCSV(local_user());
break;
}
System::exit();
exit();
}
}

View File

@ -22,7 +22,7 @@
namespace Friendica\Module;
use Friendica\BaseModule;
use Friendica\Core\System;
use Friendica\DI;
use Friendica\Util\Strings;
/**
@ -45,6 +45,7 @@ class Theme extends BaseModule
if (file_exists("view/theme/$theme/style.php")) {
require_once "view/theme/$theme/style.php";
}
System::exit();
exit();
}
}

View File

@ -48,6 +48,6 @@ class ThemeDetails extends BaseModule
'credits' => $credits,
]);
}
System::exit();
exit();
}
}

View File

@ -31,7 +31,7 @@ class Network extends NetworkModule
protected function rawContent(array $request = [])
{
if (!isset($_GET['p']) || !isset($_GET['item'])) {
System::exit();
exit();
}
$this->parseRequest($_GET);

View File

@ -25,10 +25,7 @@ use Friendica\App\BaseURL;
use Friendica\BaseFactory;
use Friendica\Capabilities\ICanCreateFromTableRow;
use Friendica\Contact\LocalRelationship\Repository\LocalRelationship;
use Friendica\Content\Text\BBCode;
use Friendica\Content\Text\Plaintext;
use Friendica\Core\Cache\Enum\Duration;
use Friendica\Core\Cache\Capability\ICanCache;
use Friendica\Core\L10n;
use Friendica\Model\Contact;
use Friendica\Model\Post;
@ -46,17 +43,14 @@ class Notification extends BaseFactory implements ICanCreateFromTableRow
private $l10n;
/** @var LocalRelationship */
private $localRelationshipRepo;
/** @var ICanCache */
private $cache;
public function __construct(\Friendica\App\BaseURL $baseUrl, \Friendica\Core\L10n $l10n, \Friendica\Contact\LocalRelationship\Repository\LocalRelationship $localRelationshipRepo, LoggerInterface $logger, ICanCache $cache)
public function __construct(\Friendica\App\BaseURL $baseUrl, \Friendica\Core\L10n $l10n, \Friendica\Contact\LocalRelationship\Repository\LocalRelationship $localRelationshipRepo, LoggerInterface $logger)
{
parent::__construct($logger);
$this->baseUrl = $baseUrl;
$this->l10n = $l10n;
$this->localRelationshipRepo = $localRelationshipRepo;
$this->cache = $cache;
}
public function createFromTableRow(array $row): Entity\Notification
@ -113,12 +107,6 @@ class Notification extends BaseFactory implements ICanCreateFromTableRow
{
$message = [];
$cachekey = 'Notification:' . $Notification->id;
$result = $this->cache->get($cachekey);
if (!is_null($result)) {
return $result;
}
$causer = $author = Contact::getById($Notification->actorId, ['id', 'name', 'url', 'contact-type', 'pending']);
if (empty($causer)) {
$this->logger->info('Causer not found', ['contact' => $Notification->actorId]);
@ -182,10 +170,11 @@ class Notification extends BaseFactory implements ICanCreateFromTableRow
$link = $this->baseUrl . '/display/' . urlencode($link_item['guid']);
$body = BBCode::toPlaintext($item['body'], false);
$title = Plaintext::shorten($body, 70);
if (!empty($title)) {
$title = '"' . trim(str_replace("\n", " ", $title)) . '"';
$content = Plaintext::getPost($item, 70);
if (!empty($content['text'])) {
$title = '"' . trim(str_replace("\n", " ", $content['text'])) . '"';
} else {
$title = '';
}
$this->logger->debug('Got verb and type', ['verb' => $Notification->verb, 'type' => $Notification->type, 'causer' => $causer['id'], 'author' => $author['id'], 'item' => $item['id'], 'uid' => $Notification->uid]);
@ -317,7 +306,6 @@ class Notification extends BaseFactory implements ICanCreateFromTableRow
'[url=' . $link . ']' . $title . '[/url]',
'[url=' . $author['url'] . ']' . $author['name'] . '[/url]');
$message['link'] = $link;
$this->cache->set($cachekey, $message, Duration::HOUR);
} else {
$this->logger->debug('Unhandled notification', ['notification' => $Notification]);
}

View File

@ -23,10 +23,8 @@ namespace Friendica\Navigation\Notifications\Repository;
use Friendica\App\BaseURL;
use Friendica\BaseRepository;
use Friendica\Content\Text\BBCode;
use Friendica\Content\Text\Plaintext;
use Friendica\Core\Config\Capability\IManageConfigValues;
use Friendica\Core\PConfig\Capability\IManagePersonalConfigValues;
use Friendica\Core\Hook;
use Friendica\Core\L10n;
use Friendica\Core\System;
@ -38,7 +36,6 @@ use Friendica\Navigation\Notifications\Entity;
use Friendica\Navigation\Notifications\Exception;
use Friendica\Navigation\Notifications\Factory;
use Friendica\Network\HTTPException;
use Friendica\Object\Api\Mastodon\Notification;
use Friendica\Protocol\Activity;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\Emailer;
@ -61,9 +58,6 @@ class Notify extends BaseRepository
/** @var IManageConfigValues */
protected $config;
/** @var IManagePersonalConfigValues */
private $pConfig;
/** @var Emailer */
protected $emailer;
@ -72,12 +66,11 @@ class Notify extends BaseRepository
protected static $table_name = 'notify';
public function __construct(Database $database, LoggerInterface $logger, L10n $l10n, BaseURL $baseUrl, IManageConfigValues $config, IManagePersonalConfigValues $pConfig, Emailer $emailer, Factory\Notification $notification, Factory\Notify $factory = null)
public function __construct(Database $database, LoggerInterface $logger, L10n $l10n, BaseURL $baseUrl, IManageConfigValues $config, Emailer $emailer, Factory\Notification $notification, Factory\Notify $factory = null)
{
$this->l10n = $l10n;
$this->baseUrl = $baseUrl;
$this->config = $config;
$this->pConfig = $pConfig;
$this->emailer = $emailer;
$this->notification = $notification;
@ -308,10 +301,11 @@ class Notify extends BaseRepository
$item_post_type = Model\Item::postType($item, $l10n);
$body = BBCode::toPlaintext($item['body'], false);
$title = Plaintext::shorten($body, 70);
if (!empty($title)) {
$title = '"' . trim(str_replace("\n", " ", $title)) . '"';
$content = Plaintext::getPost($item, 70);
if (!empty($content['text'])) {
$title = '"' . trim(str_replace("\n", " ", $content['text'])) . '"';
} else {
$title = '';
}
// First go for the general message
@ -657,27 +651,6 @@ class Notify extends BaseRepository
return false;
}
public function NotifyOnDesktop(Entity\Notification $Notification, string $type = null): bool
{
if (is_null($type)) {
$type = \Friendica\Factory\Api\Mastodon\Notification::getType($Notification);
}
if (!in_array($type, [Notification::TYPE_RESHARE, Notification::TYPE_LIKE])) {
return true;
}
if ($this->pConfig->get($Notification->uid, 'system', 'notify_like') && ($type == Notification::TYPE_LIKE)) {
return true;
}
if ($this->pConfig->get($Notification->uid, 'system', 'notify_announce') && ($type == Notification::TYPE_RESHARE)) {
return true;
}
return false;
}
public function createFromNotification(Entity\Notification $Notification)
{
$this->logger->info('Start', ['uid' => $Notification->uid, 'id' => $Notification->id, 'type' => $Notification->type]);
@ -737,10 +710,11 @@ class Notify extends BaseRepository
return false;
}
$body = BBCode::toPlaintext($item['body'], false);
$title = Plaintext::shorten($body, 70);
if (!empty($title)) {
$title = '"' . trim(str_replace("\n", " ", $title)) . '"';
$content = Plaintext::getPost($item, 70);
if (!empty($content['text'])) {
$title = '"' . trim(str_replace("\n", " ", $content['text'])) . '"';
} else {
$title = $item['title'];
}
// Some mail software relies on subject field for threading.

View File

@ -140,7 +140,7 @@ class HttpClient implements ICanSendHttpRequests
}
};
if (empty($conf[HttpClientOptions::HEADERS]['Accept']) && in_array($method, ['get', 'head'])) {
if (empty($conf[HttpClientOptions::HEADERS]['Accept'])) {
$this->logger->info('Accept header was missing, using default.', ['url' => $url, 'callstack' => System::callstack()]);
$conf[HttpClientOptions::HEADERS]['Accept'] = HttpClientAccept::DEFAULT;
}

View File

@ -68,8 +68,6 @@ class Probe
// At first remove leading and trailing junk
$rawUri = trim($rawUri, "@#?:/ \t\n\r\0\x0B");
$rawUri = Network::convertToIdn($rawUri);
$uri = new Uri($rawUri);
if (!$uri->getScheme()) {
return $uri->__toString();
@ -245,6 +243,49 @@ class Probe
return $lrdd;
}
/**
* Perform Webfinger lookup and return DFRN data
*
* Given an email style address, perform webfinger lookup and
* return the resulting DFRN profile URL, or if no DFRN profile URL
* is located, returns an OStatus subscription template (prefixed
* with the string 'stat:' to identify it as on OStatus template).
* If this isn't an email style address just return $webbie.
* Return an empty string if email-style addresses but webfinger fails,
* or if the resultant personal XRD doesn't contain a supported
* subscription/friend-request attribute.
*
* amended 7/9/2011 to return an hcard which could save potentially loading
* a lengthy content page to scrape dfrn attributes
*
* @param string $webbie Address that should be probed
* @param string $hcard_url Link to the hcard - is returned by reference
*
* @return string profile link
* @throws HTTPException\InternalServerErrorException
*/
public static function webfingerDfrn(string $webbie, string &$hcard_url)
{
$profile_link = '';
$links = self::lrdd($webbie);
Logger::debug('Result', ['url' => $webbie, 'links' => $links]);
if (!empty($links) && is_array($links)) {
foreach ($links as $link) {
if ($link['@attributes']['rel'] === ActivityNamespace::DFRN) {
$profile_link = $link['@attributes']['href'];
}
if (($link['@attributes']['rel'] === ActivityNamespace::OSTATUSSUB) && ($profile_link == "")) {
$profile_link = 'stat:'.$link['@attributes']['template'];
}
if ($link['@attributes']['rel'] === 'http://microformats.org/profile/hcard') {
$hcard_url = $link['@attributes']['href'];
}
}
}
return $profile_link;
}
/**
* Check an URI for LRDD data
*

View File

@ -159,24 +159,15 @@ class Image
}
$this->valid = false;
try {
$this->image = @imagecreatefromstring($data);
if ($this->image !== false) {
$this->width = imagesx($this->image);
$this->height = imagesy($this->image);
$this->valid = true;
imagealphablending($this->image, false);
imagesavealpha($this->image, true);
$this->image = @imagecreatefromstring($data);
if ($this->image !== false) {
$this->width = imagesx($this->image);
$this->height = imagesy($this->image);
$this->valid = true;
imagealphablending($this->image, false);
imagesavealpha($this->image, true);
return true;
}
} catch (\Throwable $error) {
/** @see https://github.com/php/doc-en/commit/d09a881a8e9059d11e756ee59d75bf404d6941ed */
if (strstr($error->getMessage(), "gd-webp cannot allocate temporary buffer")) {
DI::logger()->notice('Image is probably animated and therefore unsupported', ['error' => $error]);
} else {
DI::logger()->warning('Unexpected throwable.', ['error' => $error]);
}
return true;
}
return false;

View File

@ -29,10 +29,10 @@ use Friendica\Core\Logger;
use Friendica\Core\Protocol;
use Friendica\Core\Renderer;
use Friendica\Core\Session;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\Contact;
use Friendica\Model\Item;
use Friendica\Model\Photo;
use Friendica\Model\Post as PostModel;
use Friendica\Model\Tag;
use Friendica\Model\User;
@ -482,7 +482,7 @@ class Post
'profile_url' => $profile_link,
'name' => $profile_name,
'item_photo_menu_html' => DI::contentItem()->photoMenu($item, $formSecurityToken),
'thumb' => DI::baseUrl()->remove(DI::contentItem()->getAuthorAvatar($item)),
'thumb' => DI::baseUrl()->remove(Contact::getAvatarUrlForUrl($item['author-link'], $item['uid'], Proxy::SIZE_THUMB)),
'osparkle' => $osparkle,
'sparkle' => $sparkle,
'title' => $title,
@ -499,7 +499,7 @@ class Post
'shiny' => $shiny,
'owner_self' => $item['author-link'] == Session::get('my_url'),
'owner_url' => $this->getOwnerUrl(),
'owner_photo' => DI::baseUrl()->remove(DI::contentItem()->getOwnerAvatar($item)),
'owner_photo' => DI::baseUrl()->remove(Contact::getAvatarUrlForUrl($item['owner-link'], $item['uid'], Proxy::SIZE_THUMB)),
'owner_name' => $this->getOwnerName(),
'plink' => Item::getPlink($item),
'browsershare' => $browsershare,
@ -529,8 +529,8 @@ class Post
'thread_level' => $thread_level,
'edited' => $edited,
'network' => $item["network"],
'network_name' => ContactSelector::networkToName($item['author-network'], $item['author-link'], $item['network'], $item['author-gsid']),
'network_icon' => ContactSelector::networkToIcon($item['network'], $item['author-link'], $item['author-gsid']),
'network_name' => ContactSelector::networkToName($item['author-network'], $item['author-link'], $item['network']),
'network_icon' => ContactSelector::networkToIcon($item['network'], $item['author-link']),
'received' => $item['received'],
'commented' => $item['commented'],
'created_date' => $item['created'],
@ -898,7 +898,12 @@ class Post
}
$owner = User::getOwnerDataById($a->getLoggedInUserId());
$item = $this->getData();
$item = PostModel::selectFirst(['author-addr', 'uri-id', 'network', 'gravity', 'content-warning'], ['id' => $this->getId()]);
if (!DBA::isResult($item) || empty($item['author-addr'])) {
// Should not happen
return '';
}
if (!empty($item['content-warning']) && Feature::isEnabled(local_user(), 'add_abstract')) {
$text = '[abstract=' . Protocol::ACTIVITYPUB . ']' . $item['content-warning'] . "[/abstract]\n";
@ -962,7 +967,7 @@ class Post
$uid = $conv->getProfileOwner();
$parent_uid = $this->getDataValue('uid');
$owner = User::getOwnerDataById($a->getLoggedInUserId());
$contact = Contact::getById($a->getContactId());
$default_text = $this->getDefaultText();
@ -981,9 +986,9 @@ class Post
'$qcomment' => $qcomment,
'$default' => $default_text,
'$profile_uid' => $uid,
'$mylink' => DI::baseUrl()->remove($owner['url'] ?? ''),
'$mylink' => DI::baseUrl()->remove($contact['url'] ?? ''),
'$mytitle' => DI::l10n()->t('This is you'),
'$myphoto' => DI::baseUrl()->remove($owner['thumb'] ?? ''),
'$myphoto' => DI::baseUrl()->remove($contact['thumb'] ?? ''),
'$comment' => DI::l10n()->t('Comment'),
'$submit' => DI::l10n()->t('Submit'),
'$loading' => DI::l10n()->t('Loading...'),

View File

@ -1,222 +0,0 @@
<?php
/**
* @copyright Copyright (C) 2010-2022, 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\Protocol\ActivityPub;
use Friendica\Core\Logger;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\Contact;
use Friendica\Model\GServer;
use Friendica\Model\Post;
use Friendica\Protocol\ActivityPub;
use Friendica\Util\HTTPSignature;
use Friendica\Worker\Delivery as WorkerDelivery;
class Delivery
{
/**
* Deliver posts to the given inbox
*
* @param string $inbox
* @return array with the elements "success" and "uri_ids" of the failed posts
*/
public static function deliver(string $inbox): array
{
$uri_ids = [];
$posts = Post\Delivery::selectForInbox($inbox);
$serverfail = false;
foreach ($posts as $post) {
if (!$serverfail) {
$result = self::deliverToInbox($post['command'], 0, $inbox, $post['uid'], $post['receivers'], $post['uri-id']);
if ($result['serverfailure']) {
// In a timeout situation we assume that every delivery to that inbox will time out.
// So we set the flag and try all deliveries at a later time.
Logger::info('Inbox delivery has a server failure', ['inbox' => $inbox]);
$serverfail = true;
}
}
if ($serverfail || (!$result['success'] && !$result['drop'])) {
$uri_ids[] = $post['uri-id'];
}
}
Logger::debug('Inbox delivery done', ['inbox' => $inbox, 'posts' => count($posts), 'failed' => count($uri_ids), 'serverfailure' => $serverfail]);
return ['success' => empty($uri_ids), 'uri_ids' => $uri_ids];
}
/**
* Deliver the given post to the given inbox
*
* @param string $cmd
* @param integer $item_id
* @param string $inbox
* @param integer $uid
* @param array $receivers
* @param integer $uri_id
* @return array
*/
public static function deliverToInbox(string $cmd, int $item_id, string $inbox, int $uid, array $receivers, int $uri_id): array
{
if (empty($item_id) && !empty($uri_id) && !empty($uid)) {
$item = Post::selectFirst(['id', 'parent', 'origin'], ['uri-id' => $uri_id, 'uid' => [$uid, 0]], ['order' => ['uid' => true]]);
if (empty($item['id'])) {
Logger::notice('Item not found, removing delivery', ['uri-id' => $uri_id, 'uid' => $uid, 'cmd' => $cmd, 'inbox' => $inbox]);
Post\Delivery::remove($uri_id, $inbox);
return true;
} else {
$item_id = $item['id'];
}
}
$success = true;
$serverfail = false;
$drop = false;
if ($cmd == WorkerDelivery::MAIL) {
$data = ActivityPub\Transmitter::createActivityFromMail($item_id);
if (!empty($data)) {
$success = HTTPSignature::transmit($data, $inbox, $uid);
}
} elseif ($cmd == WorkerDelivery::SUGGESTION) {
$success = ActivityPub\Transmitter::sendContactSuggestion($uid, $inbox, $item_id);
} elseif ($cmd == WorkerDelivery::RELOCATION) {
// @todo Implementation pending
} elseif ($cmd == WorkerDelivery::POKE) {
// Implementation not planned
} elseif ($cmd == WorkerDelivery::REMOVAL) {
$success = ActivityPub\Transmitter::sendProfileDeletion($uid, $inbox);
} elseif ($cmd == WorkerDelivery::PROFILEUPDATE) {
$success = ActivityPub\Transmitter::sendProfileUpdate($uid, $inbox);
} else {
$data = ActivityPub\Transmitter::createCachedActivityFromItem($item_id);
if (!empty($data)) {
$timestamp = microtime(true);
$response = HTTPSignature::post($data, $inbox, $uid);
$runtime = microtime(true) - $timestamp;
$success = $response->isSuccess();
$serverfail = $response->isTimeout();
if (!$success) {
// 5xx errors are problems on the server. We don't need to continue delivery then.
if (!$serverfail && ($response->getReturnCode() >= 500) && ($response->getReturnCode() <= 599)) {
$serverfail = true;
}
// A 404 means that the inbox doesn't exist. We can stop the delivery here.
if (!$serverfail && ($response->getReturnCode() == 404)) {
$serverfail = true;
}
$xrd_timeout = DI::config()->get('system', 'xrd_timeout');
if (!$serverfail && $xrd_timeout && ($runtime > $xrd_timeout)) {
$serverfail = true;
}
$curl_timeout = DI::config()->get('system', 'curl_timeout');
if (!$serverfail && $curl_timeout && ($runtime > $curl_timeout)) {
$serverfail = true;
}
// Resubscribe to relay server upon client error
if (!$serverfail && ($response->getReturnCode() >= 400) && ($response->getReturnCode() <= 499)) {
$actor = self:: fetchActorForRelayInbox($inbox);
if (!empty($actor)) {
$drop = !ActivityPub\Transmitter::sendRelayFollow($actor);
Logger::notice('Resubscribed to relay', ['url' => $actor, 'success' => !$drop]);
} elseif ($cmd = WorkerDelivery::DELETION) {
// Remote systems not always accept our deletion requests, so we drop them if rejected.
// Situation is: In Friendica we allow the thread owner to delete foreign comments to their thread.
// Most AP systems don't allow this, so they will reject the deletion request.
$drop = true;
}
}
Logger::info('Delivery failed', ['retcode' => $response->getReturnCode(), 'serverfailure' => $serverfail, 'drop' => $drop, 'runtime' => round($runtime, 3), 'uri-id' => $uri_id, 'uid' => $uid, 'item_id' => $item_id, 'cmd' => $cmd, 'inbox' => $inbox]);
}
if ($uri_id) {
if ($success) {
Post\Delivery::remove($uri_id, $inbox);
} else {
Post\Delivery::incrementFailed($uri_id, $inbox);
}
}
}
}
self::setSuccess($receivers, $success);
Logger::debug('Delivered', ['uri-id' => $uri_id, 'uid' => $uid, 'item_id' => $item_id, 'cmd' => $cmd, 'inbox' => $inbox, 'success' => $success, 'serverfailure' => $serverfail, 'drop' => $drop]);
if (($success || $drop) && in_array($cmd, [WorkerDelivery::POST])) {
Post\DeliveryData::incrementQueueDone($uri_id, Post\DeliveryData::ACTIVITYPUB);
}
return ['success' => $success, 'serverfailure' => $serverfail, 'drop' => $drop];
}
/**
* Fetch the actor of the given inbox of an relay server
*
* @param string $inbox
* @return string
*/
private static function fetchActorForRelayInbox(string $inbox): string
{
$apcontact = DBA::selectFirst('apcontact', ['url'], ["`sharedinbox` = ? AND `type` = ? AND `url` IN (SELECT `url` FROM `contact` WHERE `uid` = ? AND `rel` = ?)",
$inbox, 'Application', 0, Contact::FRIEND]);
return $apcontact['url'] ?? '';
}
/**
* mark or unmark the given receivers for archival upon succoess
*
* @param array $receivers
* @param boolean $success
* @return void
*/
private static function setSuccess(array $receivers, bool $success)
{
$gsid = null;
foreach ($receivers as $receiver) {
$contact = Contact::getById($receiver);
if (empty($contact)) {
continue;
}
$gsid = $gsid ?: $contact['gsid'];
if ($success) {
Contact::unmarkForArchival($contact);
} else {
Contact::markForArchival($contact);
}
}
if (!empty($gsid)) {
GServer::setProtocol($gsid, Post\DeliveryData::ACTIVITYPUB);
}
}
}

View File

@ -170,7 +170,7 @@ class Processor
}
if (!empty($activity['question']['end-time'])) {
$question['end-time'] = DateTimeFormat::utc($activity['question']['end-time']);
$question['end-time'] = $activity['question']['end-time'];
}
Post\Question::update($item['uri-id'], $question);
@ -215,7 +215,6 @@ class Processor
return;
}
Post\History::add($item['uri-id'], $item);
Item::update($item, ['uri' => $activity['id']]);
if ($activity['object_type'] == 'as:Event') {
@ -239,12 +238,8 @@ class Processor
$event['edited'] = DateTimeFormat::utc($activity['updated']);
$event['summary'] = HTML::toBBCode($activity['name']);
$event['desc'] = HTML::toBBCode($activity['content']);
if (!empty($activity['start-time'])) {
$event['start'] = DateTimeFormat::utc($activity['start-time']);
}
if (!empty($activity['end-time'])) {
$event['finish'] = DateTimeFormat::utc($activity['end-time']);
}
$event['start'] = $activity['start-time'];
$event['finish'] = $activity['end-time'];
$event['nofinish'] = empty($event['finish']);
$event['location'] = $activity['location'];
@ -563,12 +558,8 @@ class Processor
{
$event['summary'] = HTML::toBBCode($activity['name'] ?: $activity['summary']);
$event['desc'] = HTML::toBBCode($activity['content']);
if (!empty($activity['start-time'])) {
$event['start'] = DateTimeFormat::utc($activity['start-time']);
}
if (!empty($activity['end-time'])) {
$event['finish'] = DateTimeFormat::utc($activity['end-time']);
}
$event['start'] = $activity['start-time'];
$event['finish'] = $activity['end-time'];
$event['nofinish'] = empty($event['finish']);
$event['location'] = $activity['location'];
$event['cid'] = $item['contact-id'];

View File

@ -938,7 +938,7 @@ class Receiver
// Fetch the receivers for the public and the followers collection
if ((($receiver == $followers) || (($receiver == self::PUBLIC_COLLECTION) && !$is_forum)) && !empty($actor)) {
$receivers = self::getReceiverForActor($actor, $tags, $receivers, $follower_target, $profile);
$receivers = self::getReceiverForActor($actor, $tags, $receivers, $follower_target);
continue;
}
@ -1000,46 +1000,33 @@ class Receiver
* @param array $tags
* @param array $receivers
* @param integer $target_type
* @param array $profile
*
* @return array with receivers (user id)
* @throws \Exception
*/
private static function getReceiverForActor($actor, $tags, $receivers, $target_type, $profile)
private static function getReceiverForActor($actor, $tags, $receivers, $target_type)
{
$basecondition = ['rel' => [Contact::SHARING, Contact::FRIEND, Contact::FOLLOWER],
'network' => Protocol::FEDERATED, 'archive' => false, 'pending' => false];
if (!empty($profile['uri-id'])) {
$condition = DBA::mergeConditions($basecondition, ["`uri-id` = ? AND `uid` != ?", $profile['uri-id'], 0]);
$contacts = DBA::select('contact', ['uid', 'rel'], $condition);
while ($contact = DBA::fetch($contacts)) {
if (empty($receivers[$contact['uid']]) && self::isValidReceiverForActor($contact, $tags)) {
$receivers[$contact['uid']] = ['uid' => $contact['uid'], 'type' => $target_type];
}
$condition = DBA::mergeConditions($basecondition, ["`nurl` = ? AND `uid` != ?", Strings::normaliseLink($actor), 0]);
$contacts = DBA::select('contact', ['uid', 'rel'], $condition);
while ($contact = DBA::fetch($contacts)) {
if (empty($receivers[$contact['uid']]) && self::isValidReceiverForActor($contact, $tags)) {
$receivers[$contact['uid']] = ['uid' => $contact['uid'], 'type' => $target_type];
}
DBA::close($contacts);
} else {
// This part will only be called while post update 1426 wasn't finished
$condition = DBA::mergeConditions($basecondition, ["`nurl` = ? AND `uid` != ?", Strings::normaliseLink($actor), 0]);
$contacts = DBA::select('contact', ['uid', 'rel'], $condition);
while ($contact = DBA::fetch($contacts)) {
if (empty($receivers[$contact['uid']]) && self::isValidReceiverForActor($contact, $tags)) {
$receivers[$contact['uid']] = ['uid' => $contact['uid'], 'type' => $target_type];
}
}
DBA::close($contacts);
// The queries are split because of performance issues
$condition = DBA::mergeConditions($basecondition, ["`alias` IN (?, ?) AND `uid` != ?", Strings::normaliseLink($actor), $actor, 0]);
$contacts = DBA::select('contact', ['uid', 'rel'], $condition);
while ($contact = DBA::fetch($contacts)) {
if (empty($receivers[$contact['uid']]) && self::isValidReceiverForActor($contact, $tags)) {
$receivers[$contact['uid']] = ['uid' => $contact['uid'], 'type' => $target_type];
}
}
DBA::close($contacts);
}
DBA::close($contacts);
// The queries are split because of performance issues
$condition = DBA::mergeConditions($basecondition, ["`alias` IN (?, ?) AND `uid` != ?", Strings::normaliseLink($actor), $actor, 0]);
$contacts = DBA::select('contact', ['uid', 'rel'], $condition);
while ($contact = DBA::fetch($contacts)) {
if (empty($receivers[$contact['uid']]) && self::isValidReceiverForActor($contact, $tags)) {
$receivers[$contact['uid']] = ['uid' => $contact['uid'], 'type' => $target_type];
}
}
DBA::close($contacts);
return $receivers;
}

View File

@ -44,6 +44,7 @@ use Friendica\Protocol\ActivityPub;
use Friendica\Protocol\Relay;
use Friendica\Util\DateTimeFormat;
use Friendica\Util\HTTPSignature;
use Friendica\Util\JsonLD;
use Friendica\Util\LDSignature;
use Friendica\Util\Map;
use Friendica\Util\Network;
@ -58,10 +59,6 @@ use Friendica\Util\XML;
*/
class Transmitter
{
const CACHEKEY_FEATURED = 'transmitter:getFeatured:';
const CACHEKEY_CONTACTS = 'transmitter:getContacts:';
const CACHEKEY_OUTBOX = 'transmitter:getOutbox:';
/**
* Add relay servers to the list of inboxes
*
@ -154,21 +151,12 @@ class Transmitter
* @param string $module The name of the relevant AP endpoint module (followers|following)
* @param integer $page Page number
* @param string $requester URL of the requester
* @param boolean $nocache Wether to bypass caching
*
* @return array of owners
* @throws \Exception
*/
public static function getContacts(array $owner, array $rel, string $module, int $page = null, string $requester = null, $nocache = false)
public static function getContacts(array $owner, array $rel, string $module, int $page = null, string $requester = null)
{
if (empty($page)) {
$cachekey = self::CACHEKEY_CONTACTS . $module . ':'. $owner['uid'];
$result = DI::cache()->get($cachekey);
if (!$nocache && !is_null($result)) {
return $result;
}
}
$parameters = [
'rel' => $rel,
'uid' => $owner['uid'],
@ -204,10 +192,6 @@ class Transmitter
}
if (!$show_contacts) {
if (!empty($cachekey)) {
DI::cache()->set($cachekey, $data, Duration::DAY);
}
return $data;
}
@ -232,10 +216,6 @@ class Transmitter
$data['orderedItems'] = $list;
}
if (!empty($cachekey)) {
DI::cache()->set($cachekey, $data, Duration::DAY);
}
return $data;
}
@ -245,22 +225,13 @@ class Transmitter
* @param array $owner Owner array
* @param integer $page Page number
* @param string $requester URL of requesting account
* @param boolean $nocache Wether to bypass caching
*
* @return array of posts
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
* @throws \ImagickException
*/
public static function getOutbox(array $owner, int $page = null, string $requester = '', $nocache = false)
public static function getOutbox(array $owner, int $page = null, string $requester = '')
{
if (empty($page)) {
$cachekey = self::CACHEKEY_OUTBOX . $owner['uid'];
$result = DI::cache()->get($cachekey);
if (!$nocache && !is_null($result)) {
return $result;
}
}
$condition = ['private' => [Item::PUBLIC, Item::UNLISTED]];
if (!empty($requester)) {
@ -322,42 +293,27 @@ class Transmitter
$data['orderedItems'] = $list;
}
if (!empty($cachekey)) {
DI::cache()->set($cachekey, $data, Duration::DAY);
}
return $data;
}
/**
* Public posts for the given owner
*
* @param array $owner Owner array
* @param integer $page Page number
* @param boolean $nocache Wether to bypass caching
* @param array $owner Owner array
* @param integer $page Page number
*
* @return array of posts
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
* @throws \ImagickException
*/
public static function getFeatured(array $owner, int $page = null, $nocache = false)
public static function getFeatured(array $owner, int $page = null)
{
if (empty($page)) {
$cachekey = self::CACHEKEY_FEATURED . $owner['uid'];
$result = DI::cache()->get($cachekey);
if (!$nocache && !is_null($result)) {
return $result;
}
}
$owner_cid = Contact::getIdForURL($owner['url'], 0, false);
$condition = ["`uri-id` IN (SELECT `uri-id` FROM `collection-view` WHERE `cid` = ? AND `type` = ?)",
$owner_cid, Post\Collection::FEATURED];
Contact::getIdForURL($owner['url'], 0, false), Post\Collection::FEATURED];
$condition = DBA::mergeConditions($condition,
['uid' => $owner['uid'],
'author-id' => $owner_cid,
'author-id' => Contact::getIdForURL($owner['url'], 0, false),
'private' => [Item::PUBLIC, Item::UNLISTED],
'gravity' => [GRAVITY_PARENT, GRAVITY_COMMENT],
'network' => Protocol::FEDERATED,
@ -378,36 +334,30 @@ class Transmitter
}
if (empty($page)) {
$items = Post::select(['id'], $condition, ['limit' => 20, 'order' => ['created' => true]]);
$data['first'] = DI::baseUrl() . '/featured/' . $owner['nickname'] . '?page=1';
} else {
$data['type'] = 'OrderedCollectionPage';
$list = [];
$items = Post::select(['id'], $condition, ['limit' => [($page - 1) * 20, 20], 'order' => ['created' => true]]);
}
$list = [];
while ($item = Post::fetch($items)) {
$activity = self::createActivityFromItem($item['id'], true);
$activity['type'] = $activity['type'] == 'Update' ? 'Create' : $activity['type'];
while ($item = Post::fetch($items)) {
$activity = self::createActivityFromItem($item['id'], true);
$activity['type'] = $activity['type'] == 'Update' ? 'Create' : $activity['type'];
// Only list "Create" activity objects here, no reshares
if (!empty($activity['object']) && ($activity['type'] == 'Create')) {
$list[] = $activity['object'];
// Only list "Create" activity objects here, no reshares
if (!empty($activity['object']) && ($activity['type'] == 'Create')) {
$list[] = $activity['object'];
}
}
}
DBA::close($items);
DBA::close($items);
if (count($list) == 20) {
$data['next'] = DI::baseUrl() . '/featured/' . $owner['nickname'] . '?page=' . ($page + 1);
}
if (count($list) == 20) {
$data['next'] = DI::baseUrl() . '/featured/' . $owner['nickname'] . '?page=' . ($page + 1);
}
if (!empty($page)) {
$data['partOf'] = DI::baseUrl() . '/featured/' . $owner['nickname'];
}
$data['orderedItems'] = $list;
if (!empty($cachekey)) {
DI::cache()->set($cachekey, $data, Duration::DAY);
$data['orderedItems'] = $list;
}
return $data;

View File

@ -341,15 +341,4 @@ class Relay
// It should never happen that we arrive here
return [];
}
/**
* Resubscribe to all relay servers
*/
public static function reSubscribe()
{
foreach (self::getList() as $server) {
$success = ActivityPub\Transmitter::sendRelayFollow($server['url']);
Logger::debug('Resubscribed', ['profile' => $server['url'], 'success' => $success]);
}
}
}

View File

@ -27,9 +27,7 @@ use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Model\APContact;
use Friendica\Model\Contact;
use Friendica\Model\ItemURI;
use Friendica\Model\User;
use Friendica\Network\HTTPClient\Capability\ICanHandleHttpResponses;
use Friendica\Network\HTTPClient\Client\HttpClientAccept;
use Friendica\Network\HTTPClient\Client\HttpClientOptions;
@ -265,21 +263,21 @@ class HTTPSignature
*/
/**
* Post given data to a target for a user, returns the result class
* Transmit given data to a target for a user
*
* @param array $data Data that is about to be send
* @param string $target The URL of the inbox
* @param integer $uid User id of the sender
*
* @return ICanHandleHttpResponses
* @return boolean Was the transmission successful?
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function post(array $data, string $target, int $uid): ICanHandleHttpResponses
public static function transmit($data, $target, $uid)
{
$owner = User::getOwnerDataById($uid);
if (!$owner) {
return null;
return;
}
$content = json_encode($data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
@ -306,32 +304,16 @@ class HTTPSignature
$headers['Content-Type'] = 'application/activity+json';
$postResult = DI::httpClient()->post($target, $content, $headers, DI::config()->get('system', 'curl_timeout'));
$postResult = DI::httpClient()->post($target, $content, $headers);
$return_code = $postResult->getReturnCode();
Logger::info('Transmit to ' . $target . ' returned ' . $return_code);
self::setInboxStatus($target, ($return_code >= 200) && ($return_code <= 299));
$success = ($return_code >= 200) && ($return_code <= 299);
return $postResult;
}
self::setInboxStatus($target, $success);
/**
* Transmit given data to a target for a user
*
* @param array $data Data that is about to be send
* @param string $target The URL of the inbox
* @param integer $uid User id of the sender
*
* @return boolean Was the transmission successful?
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
public static function transmit(array $data, string $target, int $uid): bool
{
$postResult = self::post($data, $target, $uid);
$return_code = $postResult->getReturnCode();
return ($return_code >= 200) && ($return_code <= 299);
return $success;
}
/**
@ -347,7 +329,7 @@ class HTTPSignature
$status = DBA::selectFirst('inbox-status', [], ['url' => $url]);
if (!DBA::isResult($status)) {
DBA::insert('inbox-status', ['url' => $url, 'uri-id' => ItemURI::getIdByURI($url), 'created' => $now, 'shared' => $shared], Database::INSERT_IGNORE);
DBA::insert('inbox-status', ['url' => $url, 'created' => $now, 'shared' => $shared], Database::INSERT_IGNORE);
$status = DBA::selectFirst('inbox-status', [], ['url' => $url]);
}
@ -387,10 +369,6 @@ class HTTPSignature
$fields['archive'] = false;
}
if (empty($status['uri-id'])) {
$fields['uri-id'] = ItemURI::getIdByURI($url);
}
DBA::update('inbox-status', $fields, ['url' => $url]);
}

View File

@ -24,7 +24,6 @@ namespace Friendica\Util;
use Friendica\Core\Cache\Enum\Duration;
use Friendica\Core\Logger;
use Exception;
use Friendica\Core\System;
use Friendica\DI;
/**
@ -70,7 +69,7 @@ class JsonLD
if ($recursion > 5) {
Logger::error('jsonld bomb detected at: ' . $url);
System::exit();
exit();
}
$result = DI::cache()->get('documentLoader:' . $url);

View File

@ -26,7 +26,6 @@ use Friendica\Core\Logger;
use Friendica\DI;
use Friendica\Model\Contact;
use Friendica\Network\HTTPException\NotModifiedException;
use GuzzleHttp\Psr7\Uri;
class Network
{
@ -437,7 +436,7 @@ class Network
* @param array $parsed URL parts
*
* @return string The glued URL.
* @deprecated since version 2021.12, use GuzzleHttp\Psr7\Uri::fromParts($parts) instead
* @deprecated since version 2021.12, use a UriInterface object like GuzzleHttp\Psr7\Uri instead
*/
public static function unparseURL(array $parsed)
{
@ -463,29 +462,6 @@ class Network
(strlen($fragment) ? "#".$fragment : '');
}
/**
* Convert an URI to an IDN compatible URI
*
* @param string $uri
* @return string
*/
public static function convertToIdn(string $uri): string
{
$parts = parse_url($uri);
if (!empty($parts['scheme']) && !empty($parts['host'])) {
$parts['host'] = idn_to_ascii($parts['host']);
$uri = Uri::fromParts($parts);
} else {
$parts = explode('@', $uri);
if (count($parts) == 2) {
$uri = $parts[0] . '@' . idn_to_ascii($parts[1]);
} else {
$uri = idn_to_ascii($uri);
}
}
return $uri;
}
/**
* Switch the scheme of an url between http and https

View File

@ -485,8 +485,9 @@ class Strings
* @param string $regex
* @param callable $callback
* @return string
* @throws \Exception
*/
public static function performWithEscapedBlocks(string $text, string $regex, callable $callback): string
public static function performWithEscapedBlocks(string $text, string $regex, callable $callback)
{
// Enables nested use
$executionId = random_int(PHP_INT_MAX / 10, PHP_INT_MAX);

View File

@ -460,7 +460,7 @@ class XML
public static function getFirstNodeValue(DOMXPath $xpath, $element, $context = null)
{
$result = @$xpath->evaluate($element, $context);
$result = $xpath->evaluate($element, $context);
if (!is_object($result)) {
return '';
}

View File

@ -23,8 +23,12 @@ namespace Friendica\Worker;
use Friendica\Core\Logger;
use Friendica\Core\Worker;
use Friendica\Model\Contact;
use Friendica\Model\GServer;
use Friendica\Model\Post;
use Friendica\Protocol\ActivityPub;
use Friendica\Util\HTTPSignature;
class APDelivery
{
/**
@ -42,44 +46,65 @@ class APDelivery
public static function execute(string $cmd, int $item_id, string $inbox, int $uid, array $receivers = [], int $uri_id = 0)
{
if (ActivityPub\Transmitter::archivedInbox($inbox)) {
Logger::info('Inbox is archived', ['cmd' => $cmd, 'inbox' => $inbox, 'id' => $item_id, 'uri-id' => $uri_id, 'uid' => $uid]);
if (empty($uri_id) && !empty($item_id)) {
Logger::info('Inbox is archived', ['cmd' => $cmd, 'inbox' => $inbox, 'id' => $item_id, 'uid' => $uid]);
if (in_array($cmd, [Delivery::POST])) {
$item = Post::selectFirst(['uri-id'], ['id' => $item_id]);
$uri_id = $item['uri-id'] ?? 0;
}
if (empty($uri_id)) {
$posts = Post\Delivery::selectForInbox($inbox);
$uri_ids = array_column($posts, 'uri-id');
} else {
$uri_ids = [$uri_id];
}
foreach ($uri_ids as $uri_id) {
Post\Delivery::remove($uri_id, $inbox);
Post\DeliveryData::incrementQueueFailed($uri_id);
Post\DeliveryData::incrementQueueFailed($item['uri-id'] ?? 0);
}
return;
}
Logger::debug('Invoked', ['cmd' => $cmd, 'inbox' => $inbox, 'id' => $item_id, 'uri-id' => $uri_id, 'uid' => $uid]);
Logger::info('Invoked', ['cmd' => $cmd, 'inbox' => $inbox, 'id' => $item_id, 'uri-id' => $uri_id, 'uid' => $uid]);
if (empty($uri_id)) {
$result = ActivityPub\Delivery::deliver($inbox);
$success = $result['success'];
$drop = false;
$uri_ids = $result['uri_ids'];
$success = true;
if ($cmd == Delivery::MAIL) {
$data = ActivityPub\Transmitter::createActivityFromMail($item_id);
if (!empty($data)) {
$success = HTTPSignature::transmit($data, $inbox, $uid);
}
} elseif ($cmd == Delivery::SUGGESTION) {
$success = ActivityPub\Transmitter::sendContactSuggestion($uid, $inbox, $item_id);
} elseif ($cmd == Delivery::RELOCATION) {
// @todo Implementation pending
} elseif ($cmd == Delivery::POKE) {
// Implementation not planned
} elseif ($cmd == Delivery::REMOVAL) {
$success = ActivityPub\Transmitter::sendProfileDeletion($uid, $inbox);
} elseif ($cmd == Delivery::PROFILEUPDATE) {
$success = ActivityPub\Transmitter::sendProfileUpdate($uid, $inbox);
} else {
$result = ActivityPub\Delivery::deliverToInbox($cmd, $item_id, $inbox, $uid, $receivers, $uri_id);
$success = $result['success'];
$drop = $result['drop'];
$uri_ids = [$uri_id];
$data = ActivityPub\Transmitter::createCachedActivityFromItem($item_id);
if (!empty($data)) {
$success = HTTPSignature::transmit($data, $inbox, $uid);
}
}
if (!$drop && !$success && !Worker::defer() && !empty($uri_ids)) {
foreach ($uri_ids as $uri_id) {
Post\Delivery::remove($uri_id, $inbox);
Post\DeliveryData::incrementQueueFailed($uri_id);
$gsid = null;
foreach ($receivers as $receiver) {
$contact = Contact::getById($receiver);
if (empty($contact)) {
continue;
}
$gsid = $gsid ?: $contact['gsid'];
if ($success) {
Contact::unmarkForArchival($contact);
} else {
Contact::markForArchival($contact);
}
}
if (!empty($gsid)) {
GServer::setProtocol($gsid, Post\DeliveryData::ACTIVITYPUB);
}
if (!$success && !Worker::defer() && in_array($cmd, [Delivery::POST])) {
Post\DeliveryData::incrementQueueFailed($uri_id);
} elseif ($success && in_array($cmd, [Delivery::POST])) {
Post\DeliveryData::incrementQueueDone($uri_id, Post\DeliveryData::ACTIVITYPUB);
}
}
}

View File

@ -21,10 +21,8 @@
namespace Friendica\Worker;
use Friendica\DI;
use Friendica\Core\Logger;
use Friendica\Model\Contact;
use Friendica\Network\HTTPException\InternalServerErrorException;
use Friendica\Network\HTTPException\NotFoundException;
class AddContact
{
@ -35,22 +33,14 @@ class AddContact
*/
public static function execute(int $uid, string $url)
{
try {
if ($uid == 0) {
// Adding public contact
$result = Contact::getIdForURL($url);
DI::logger()->info('Added public contact', ['url' => $url, 'result' => $result]);
return;
}
$result = Contact::createFromProbeForUser($uid, $url);
DI::logger()->info('Added contact for user', ['uid' => $uid, 'url' => $url, 'result' => $result]);
} catch (InternalServerErrorException $e) {
DI::logger()->warning('Internal server error.', ['exception' => $e, 'uid' => $uid, 'url' => $url]);
} catch (NotFoundException $e) {
DI::logger()->notice('uid not found.', ['exception' => $e, 'uid' => $uid, 'url' => $url]);
} catch (\ImagickException $e) {
DI::logger()->notice('Imagick not found.', ['exception' => $e, 'uid' => $uid, 'url' => $url]);
if ($uid == 0) {
// Adding public contact
$result = Contact::getIdForURL($url);
Logger::info('Added public contact', ['url' => $url, 'result' => $result]);
return;
}
$result = Contact::createFromProbeForUser($uid, $url);
Logger::info('Added contact', ['uid' => $uid, 'url' => $url, 'result' => $result]);
}
}

View File

@ -21,19 +21,31 @@
namespace Friendica\Worker;
use Friendica\Core\Logger;
use Friendica\Protocol\ActivityPub;
use Friendica\Core\Worker;
use Friendica\Database\DBA;
use Friendica\DI;
use Friendica\Util\DateTimeFormat;
class FetchFeaturedPosts
/**
* Delete all done workerqueue entries
*/
class CleanWorkerQueue
{
/**
* Fetch featured posts from a contact with the given URL
* @param string $url Contact URL
*/
public static function execute(string $url)
public static function execute()
{
Logger::info('Start fetching featured posts', ['url' => $url]);
ActivityPub\Processor::fetchFeaturedPosts($url);
Logger::info('Finished fetching featured posts', ['url' => $url]);
DBA::delete('workerqueue', ["`done` AND `executed` < ?", DateTimeFormat::utc('now - 1 hour')]);
// Optimizing this table only last seconds
if (DI::config()->get('system', 'optimize_tables')) {
// We are acquiring the two locks from the worker to avoid locking problems
if (DI::lock()->acquire(Worker::LOCK_PROCESS, 10)) {
if (DI::lock()->acquire(Worker::LOCK_WORKER, 10)) {
DBA::e("OPTIMIZE TABLE `workerqueue`");
DBA::e("OPTIMIZE TABLE `process`");
DI::lock()->release(Worker::LOCK_WORKER);
}
DI::lock()->release(Worker::LOCK_PROCESS);
}
}
}
}

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