2018-10-03 06:15:07 +00:00
< ? php
/**
2022-01-02 07:27:47 +00:00
* @ copyright Copyright ( C ) 2010 - 2022 , the Friendica project
2020-02-09 15:18:46 +00:00
*
* @ 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 />.
*
2018-10-03 06:15:07 +00:00
*/
2020-02-09 15:18:46 +00:00
2018-10-03 06:15:07 +00:00
namespace Friendica\Protocol\ActivityPub ;
2019-05-16 05:44:59 +00:00
use Friendica\Content\Text\BBCode ;
2018-11-08 16:28:29 +00:00
use Friendica\Content\Text\HTML ;
2021-03-06 08:43:25 +00:00
use Friendica\Content\Text\Markdown ;
2022-07-28 05:29:47 +00:00
use Friendica\Core\Cache\Enum\Duration ;
2018-10-29 21:20:46 +00:00
use Friendica\Core\Logger ;
2018-10-03 06:15:07 +00:00
use Friendica\Core\Protocol ;
2021-12-11 15:17:34 +00:00
use Friendica\Core\System ;
2022-05-30 20:52:43 +00:00
use Friendica\Core\Worker ;
2019-10-24 22:10:20 +00:00
use Friendica\Database\DBA ;
2020-01-18 15:50:57 +00:00
use Friendica\DI ;
2018-10-03 06:15:07 +00:00
use Friendica\Model\APContact ;
2019-10-24 22:10:20 +00:00
use Friendica\Model\Contact ;
2020-02-28 09:46:53 +00:00
use Friendica\Model\Conversation ;
2018-10-26 04:13:26 +00:00
use Friendica\Model\Event ;
2021-03-10 22:31:33 +00:00
use Friendica\Model\GServer ;
2019-10-24 22:10:20 +00:00
use Friendica\Model\Item ;
2020-04-13 23:54:28 +00:00
use Friendica\Model\ItemURI ;
2019-10-24 22:10:20 +00:00
use Friendica\Model\Mail ;
2020-04-17 06:35:20 +00:00
use Friendica\Model\Tag ;
2018-10-03 06:15:07 +00:00
use Friendica\Model\User ;
2020-10-29 05:20:26 +00:00
use Friendica\Model\Post ;
2019-10-23 22:25:43 +00:00
use Friendica\Protocol\Activity ;
2018-10-03 06:15:07 +00:00
use Friendica\Protocol\ActivityPub ;
2020-09-30 17:37:46 +00:00
use Friendica\Protocol\Relay ;
2018-10-27 06:17:17 +00:00
use Friendica\Util\DateTimeFormat ;
2022-07-24 13:09:35 +00:00
use Friendica\Util\HTTPSignature ;
2018-11-08 16:28:29 +00:00
use Friendica\Util\JsonLD ;
2022-07-24 13:09:35 +00:00
use Friendica\Util\Network ;
2018-11-08 16:28:29 +00:00
use Friendica\Util\Strings ;
2022-05-30 20:52:43 +00:00
use Friendica\Worker\Delivery ;
2018-10-03 06:15:07 +00:00
/**
2018-10-24 04:51:37 +00:00
* ActivityPub Processor Protocol class
2018-10-03 06:15:07 +00:00
*/
class Processor
{
2022-07-28 05:29:47 +00:00
const CACHEKEY_FETCH_ACTIVITY = 'processor:fetchMissingActivity:' ;
2022-08-01 05:56:55 +00:00
const CACHEKEY_JUST_FETCHED = 'processor:isJustFetched:' ;
2022-08-04 21:52:10 +00:00
2022-08-05 04:45:31 +00:00
/**
2022-08-06 17:06:55 +00:00
* Add an object id to the list of processed ids
2022-08-05 04:45:31 +00:00
*
* @ param string $id
*
* @ return void
*/
2022-08-06 17:06:55 +00:00
private static function addActivityId ( string $id )
2022-08-04 21:52:10 +00:00
{
2022-08-10 09:28:18 +00:00
DBA :: delete ( 'fetched-activity' , [ " `received` < ? " , DateTimeFormat :: utc ( 'now - 5 minutes' )]);
DBA :: insert ( 'fetched-activity' , [ 'object-id' => $id , 'received' => DateTimeFormat :: utcNow ()]);
2022-08-04 21:52:10 +00:00
}
2022-08-05 04:45:31 +00:00
/**
2022-08-10 09:28:18 +00:00
* Checks if the given object id has just been fetched
2022-08-05 04:45:31 +00:00
*
* @ param string $id
*
* @ return boolean
*/
2022-08-10 09:28:18 +00:00
private static function isFetched ( string $id ) : bool
2022-08-04 21:52:10 +00:00
{
2022-08-10 09:28:18 +00:00
return DBA :: exists ( 'fetched-activity' , [ 'object-id' => $id ]);
2022-08-04 21:52:10 +00:00
}
2018-10-03 06:15:07 +00:00
/**
2021-08-16 02:53:42 +00:00
* Extracts the tag character ( #, @, !) from mention links
2018-10-03 06:15:07 +00:00
*
* @ param string $body
2021-08-16 02:53:42 +00:00
* @ return string
2018-10-03 06:15:07 +00:00
*/
2021-08-16 02:53:42 +00:00
protected static function normalizeMentionLinks ( string $body ) : string
2018-10-03 06:15:07 +00:00
{
2021-08-16 02:53:42 +00:00
return preg_replace ( '%\[url=([^\[\]]*)]([#@!])(.*?)\[/url]%ism' , '$2[url=$1]$3[/url]' , $body );
2018-10-03 06:15:07 +00:00
}
2021-03-06 08:43:25 +00:00
/**
* Convert the language array into a language JSON
*
* @ param array $languages
* @ return string language JSON
*/
2022-06-16 11:14:00 +00:00
private static function processLanguages ( array $languages ) : string
2021-03-06 08:43:25 +00:00
{
$codes = array_keys ( $languages );
$lang = [];
foreach ( $codes as $code ) {
$lang [ $code ] = 1 ;
}
if ( empty ( $lang )) {
return '' ;
}
return json_encode ( $lang );
}
2018-11-07 20:34:03 +00:00
/**
* Replaces emojis in the body
*
2022-06-16 11:14:00 +00:00
* @ param int $uri_id
2018-11-07 20:34:03 +00:00
* @ param string $body
2022-06-16 11:14:00 +00:00
* @ param array $emojis
2018-11-07 20:34:03 +00:00
*
* @ return string with replaced emojis
*/
2022-06-16 12:59:29 +00:00
private static function replaceEmojis ( int $uri_id , string $body , array $emojis ) : string
2018-11-07 20:34:03 +00:00
{
2020-11-11 23:28:26 +00:00
$body = strtr ( $body ,
array_combine (
array_column ( $emojis , 'name' ),
array_map ( function ( $emoji ) {
2021-10-18 01:29:54 +00:00
return '[emoji=' . $emoji [ 'href' ] . ']' . $emoji [ 'name' ] . '[/emoji]' ;
2020-11-11 23:28:26 +00:00
}, $emojis )
)
);
2021-10-03 09:42:14 +00:00
// We store the emoji here to be able to avoid storing it in the media
foreach ( $emojis as $emoji ) {
Post\Link :: getByLink ( $uri_id , $emoji [ 'href' ]);
}
2018-11-07 20:34:03 +00:00
return $body ;
}
2020-10-29 05:20:26 +00:00
/**
* Store attached media files in the post - media table
*
* @ param int $uriid
* @ param array $attachment
* @ return void
*/
2020-10-29 09:03:06 +00:00
private static function storeAttachmentAsMedia ( int $uriid , array $attachment )
2020-10-29 05:20:26 +00:00
{
if ( empty ( $attachment [ 'url' ])) {
return ;
}
$data = [ 'uri-id' => $uriid ];
2021-04-14 19:12:01 +00:00
$data [ 'type' ] = Post\Media :: UNKNOWN ;
2020-10-29 05:20:26 +00:00
$data [ 'url' ] = $attachment [ 'url' ];
2022-01-27 17:51:23 +00:00
$data [ 'mimetype' ] = $attachment [ 'mediaType' ] ? ? null ;
2020-10-29 05:20:26 +00:00
$data [ 'height' ] = $attachment [ 'height' ] ? ? null ;
2021-07-04 06:30:54 +00:00
$data [ 'width' ] = $attachment [ 'width' ] ? ? null ;
2020-10-29 05:20:26 +00:00
$data [ 'size' ] = $attachment [ 'size' ] ? ? null ;
$data [ 'preview' ] = $attachment [ 'image' ] ? ? null ;
$data [ 'description' ] = $attachment [ 'name' ] ? ? null ;
Post\Media :: insert ( $data );
}
2018-10-03 06:15:07 +00:00
/**
2021-04-26 06:50:12 +00:00
* Stire attachment data
2018-10-03 06:15:07 +00:00
*
2019-11-28 06:34:35 +00:00
* @ param array $activity
2019-05-13 19:56:46 +00:00
* @ param array $item
2018-10-03 06:15:07 +00:00
*/
2022-06-16 11:14:00 +00:00
private static function storeAttachments ( array $activity , array $item )
2018-10-03 06:15:07 +00:00
{
2019-11-28 06:34:35 +00:00
if ( empty ( $activity [ 'attachments' ])) {
2021-04-26 06:50:12 +00:00
return ;
2018-10-03 06:15:07 +00:00
}
2019-11-28 06:34:35 +00:00
foreach ( $activity [ 'attachments' ] as $attach ) {
2021-04-26 06:50:12 +00:00
self :: storeAttachmentAsMedia ( $item [ 'uri-id' ], $attach );
2018-10-03 06:15:07 +00:00
}
}
2022-04-20 06:28:02 +00:00
/**
* Store attachment data
*
* @ param array $activity
* @ param array $item
*/
2022-06-16 11:14:00 +00:00
private static function storeQuestion ( array $activity , array $item )
2022-04-20 06:28:02 +00:00
{
if ( empty ( $activity [ 'question' ])) {
return ;
}
$question = [ 'multiple' => $activity [ 'question' ][ 'multiple' ]];
if ( ! empty ( $activity [ 'question' ][ 'voters' ])) {
$question [ 'voters' ] = $activity [ 'question' ][ 'voters' ];
}
if ( ! empty ( $activity [ 'question' ][ 'end-time' ])) {
2022-05-15 18:00:19 +00:00
$question [ 'end-time' ] = DateTimeFormat :: utc ( $activity [ 'question' ][ 'end-time' ]);
2022-04-20 06:28:02 +00:00
}
Post\Question :: update ( $item [ 'uri-id' ], $question );
foreach ( $activity [ 'question' ][ 'options' ] as $key => $option ) {
$option = [ 'name' => $option [ 'name' ], 'replies' => $option [ 'replies' ]];
Post\QuestionOption :: update ( $item [ 'uri-id' ], $key , $option );
}
Logger :: debug ( 'Storing incoming question' , [ 'type' => $activity [ 'type' ], 'uri-id' => $item [ 'uri-id' ], 'question' => $activity [ 'question' ]]);
}
2018-10-27 06:17:17 +00:00
/**
* Updates a message
*
2022-06-25 03:48:49 +00:00
* @ param array $activity Activity array
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
2022-06-25 03:48:49 +00:00
* @ throws \ImagickException
2018-10-27 06:17:17 +00:00
*/
2022-07-21 05:16:14 +00:00
public static function updateItem ( array $activity )
2018-10-27 06:17:17 +00:00
{
2022-08-04 08:49:39 +00:00
$item = Post :: selectFirst ([ 'uri' , 'uri-id' , 'thr-parent' , 'gravity' , 'post-type' , 'private' ], [ 'uri' => $activity [ 'id' ]]);
2019-02-09 03:57:35 +00:00
if ( ! DBA :: isResult ( $item )) {
2022-08-27 21:22:49 +00:00
Logger :: notice ( 'No existing item, item will be created' , [ 'uri' => $activity [ 'id' ]]);
2022-08-06 17:06:55 +00:00
$item = self :: createItem ( $activity , false );
2022-03-22 12:28:47 +00:00
if ( empty ( $item )) {
2022-08-07 19:24:50 +00:00
Queue :: remove ( $activity );
2022-03-22 12:28:47 +00:00
return ;
}
2020-07-20 04:37:43 +00:00
self :: postItem ( $activity , $item );
2019-02-09 03:57:35 +00:00
return ;
}
2018-10-27 06:17:17 +00:00
$item [ 'changed' ] = DateTimeFormat :: utcNow ();
2019-06-13 01:02:37 +00:00
$item [ 'edited' ] = DateTimeFormat :: utc ( $activity [ 'updated' ]);
2019-02-09 03:57:35 +00:00
2019-03-17 13:50:14 +00:00
$item = self :: processContent ( $activity , $item );
if ( empty ( $item )) {
2022-08-07 19:24:50 +00:00
Queue :: remove ( $activity );
2019-03-17 13:50:14 +00:00
return ;
2019-02-09 03:57:35 +00:00
}
2022-07-30 04:43:18 +00:00
self :: storeAttachments ( $activity , $item );
self :: storeQuestion ( $activity , $item );
2022-05-15 20:21:56 +00:00
Post\History :: add ( $item [ 'uri-id' ], $item );
2018-10-27 06:17:17 +00:00
Item :: update ( $item , [ 'uri' => $activity [ 'id' ]]);
2022-02-05 11:16:50 +00:00
2022-07-21 05:16:14 +00:00
Queue :: remove ( $activity );
2022-07-18 03:31:00 +00:00
2022-02-05 11:16:50 +00:00
if ( $activity [ 'object_type' ] == 'as:Event' ) {
2022-02-05 16:22:21 +00:00
$posts = Post :: select ([ 'event-id' , 'uid' ], [ " `uri` = ? AND `event-id` > ? " , $activity [ 'id' ], 0 ]);
2022-02-05 11:16:50 +00:00
while ( $post = DBA :: fetch ( $posts )) {
self :: updateEvent ( $post [ 'event-id' ], $activity );
}
}
}
/**
* Update an existing event
*
2022-03-12 07:34:30 +00:00
* @ param int $event_id
* @ param array $activity
2022-02-05 11:16:50 +00:00
*/
private static function updateEvent ( int $event_id , array $activity )
{
$event = DBA :: selectFirst ( 'event' , [], [ 'id' => $event_id ]);
$event [ 'edited' ] = DateTimeFormat :: utc ( $activity [ 'updated' ]);
$event [ 'summary' ] = HTML :: toBBCode ( $activity [ 'name' ]);
$event [ 'desc' ] = HTML :: toBBCode ( $activity [ 'content' ]);
2022-05-15 18:00:19 +00:00
if ( ! empty ( $activity [ 'start-time' ])) {
$event [ 'start' ] = DateTimeFormat :: utc ( $activity [ 'start-time' ]);
}
if ( ! empty ( $activity [ 'end-time' ])) {
$event [ 'finish' ] = DateTimeFormat :: utc ( $activity [ 'end-time' ]);
}
2022-02-05 11:16:50 +00:00
$event [ 'nofinish' ] = empty ( $event [ 'finish' ]);
$event [ 'location' ] = $activity [ 'location' ];
Logger :: info ( 'Updating event' , [ 'uri' => $activity [ 'id' ], 'id' => $event_id ]);
Event :: store ( $event );
2018-10-27 06:17:17 +00:00
}
2018-10-03 06:15:07 +00:00
/**
2018-10-07 18:41:45 +00:00
* Prepares data for a message
2018-10-03 06:15:07 +00:00
*
2022-08-03 04:51:57 +00:00
* @ param array $activity Activity array
* @ param bool $fetch_parents
*
2020-07-20 04:37:43 +00:00
* @ return array Internal item
2022-08-03 04:51:57 +00:00
*
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
* @ throws \ImagickException
2018-10-03 06:15:07 +00:00
*/
2022-08-06 17:06:55 +00:00
public static function createItem ( array $activity , bool $fetch_parents ) : array
2018-10-03 06:15:07 +00:00
{
$item = [];
2019-10-23 22:25:43 +00:00
$item [ 'verb' ] = Activity :: POST ;
2019-02-23 04:42:04 +00:00
$item [ 'thr-parent' ] = $activity [ 'reply-to-id' ];
2018-10-03 06:15:07 +00:00
if ( $activity [ 'reply-to-id' ] == $activity [ 'id' ]) {
$item [ 'gravity' ] = GRAVITY_PARENT ;
2019-10-24 22:10:20 +00:00
$item [ 'object-type' ] = Activity\ObjectType :: NOTE ;
2018-10-03 06:15:07 +00:00
} else {
$item [ 'gravity' ] = GRAVITY_COMMENT ;
2019-10-24 22:10:20 +00:00
$item [ 'object-type' ] = Activity\ObjectType :: COMMENT ;
2018-10-03 06:15:07 +00:00
}
2022-08-06 17:06:55 +00:00
if ( ! empty ( $activity [ 'conversation' ])) {
2022-07-23 06:52:43 +00:00
$item [ 'conversation' ] = $activity [ 'conversation' ];
2022-08-06 17:06:55 +00:00
} elseif ( ! empty ( $activity [ 'context' ])) {
$item [ 'conversation' ] = $activity [ 'context' ];
2022-07-23 06:52:43 +00:00
}
if ( ! empty ( $item [ 'conversation' ])) {
$conversation = Post :: selectFirstThread ([ 'uri' ], [ 'conversation' => $item [ 'conversation' ]]);
if ( ! empty ( $conversation )) {
Logger :: debug ( 'Got conversation' , [ 'conversation' => $item [ 'conversation' ], 'parent' => $conversation ]);
$item [ 'parent-uri' ] = $conversation [ 'uri' ];
2022-08-10 09:28:18 +00:00
$item [ 'parent-uri-id' ] = ItemURI :: getIdByURI ( $item [ 'parent-uri' ]);
2022-07-21 08:55:45 +00:00
}
2022-07-23 06:52:43 +00:00
} else {
$conversation = [];
}
2022-07-21 08:55:45 +00:00
2022-08-06 17:06:55 +00:00
Logger :: debug ( 'Create Item' , [ 'id' => $activity [ 'id' ], 'conversation' => $item [ 'conversation' ] ? ? '' ]);
2022-07-30 05:30:08 +00:00
if ( empty ( $activity [ 'author' ]) && empty ( $activity [ 'actor' ])) {
Logger :: notice ( 'Missing author and actor. We quit here.' , [ 'activity' => $activity ]);
2022-08-07 19:24:50 +00:00
Queue :: remove ( $activity );
2022-07-30 05:30:08 +00:00
return [];
}
2022-08-08 20:00:21 +00:00
if ( ! in_array ( 0 , $activity [ 'receiver' ]) || ! DI :: config () -> get ( 'system' , 'fetch_parents' )) {
2022-08-04 19:32:36 +00:00
$fetch_parents = false ;
}
2022-08-03 04:51:57 +00:00
if ( $fetch_parents && empty ( $activity [ 'directmessage' ]) && ( $activity [ 'id' ] != $activity [ 'reply-to-id' ]) && ! Post :: exists ([ 'uri' => $activity [ 'reply-to-id' ]])) {
2022-08-10 09:28:18 +00:00
$result = self :: fetchParent ( $activity , ! empty ( $conversation ));
2022-08-03 03:38:03 +00:00
if ( ! empty ( $result )) {
2022-07-23 06:52:43 +00:00
if (( $item [ 'thr-parent' ] != $result ) && Post :: exists ([ 'uri' => $result ])) {
$item [ 'thr-parent' ] = $result ;
}
2022-08-03 03:38:03 +00:00
} elseif ( empty ( $conversation )) {
return [];
2022-07-21 05:16:14 +00:00
}
2018-10-03 06:15:07 +00:00
}
2019-10-16 12:35:14 +00:00
$item [ 'diaspora_signed_text' ] = $activity [ 'diaspora:comment' ] ? ? '' ;
2018-10-27 14:35:22 +00:00
2022-07-23 06:52:43 +00:00
if ( empty ( $conversation ) && empty ( $activity [ 'directmessage' ]) && ( $item [ 'gravity' ] != GRAVITY_PARENT ) && ! Post :: exists ([ 'uri' => $item [ 'thr-parent' ]])) {
2020-07-20 04:37:43 +00:00
Logger :: info ( 'Parent not found, message will be discarded.' , [ 'thr-parent' => $item [ 'thr-parent' ]]);
2022-08-07 19:24:50 +00:00
if ( ! $fetch_parents ) {
Queue :: remove ( $activity );
}
2020-07-20 04:37:43 +00:00
return [];
}
$item [ 'network' ] = Protocol :: ACTIVITYPUB ;
$item [ 'author-link' ] = $activity [ 'author' ];
2020-08-07 13:49:59 +00:00
$item [ 'author-id' ] = Contact :: getIdForURL ( $activity [ 'author' ]);
2020-07-20 04:37:43 +00:00
$item [ 'owner-link' ] = $activity [ 'actor' ];
2020-08-07 13:49:59 +00:00
$item [ 'owner-id' ] = Contact :: getIdForURL ( $activity [ 'actor' ]);
2020-07-20 04:37:43 +00:00
if ( in_array ( 0 , $activity [ 'receiver' ]) && ! empty ( $activity [ 'unlisted' ])) {
$item [ 'private' ] = Item :: UNLISTED ;
} elseif ( in_array ( 0 , $activity [ 'receiver' ])) {
$item [ 'private' ] = Item :: PUBLIC ;
} else {
$item [ 'private' ] = Item :: PRIVATE ;
}
if ( ! empty ( $activity [ 'raw' ])) {
$item [ 'source' ] = $activity [ 'raw' ];
2022-07-29 21:28:22 +00:00
}
2020-07-20 04:37:43 +00:00
2022-07-31 08:10:47 +00:00
$item [ 'protocol' ] = Conversation :: PARCEL_ACTIVITYPUB ;
2022-07-29 21:28:22 +00:00
if ( isset ( $activity [ 'push' ])) {
$item [ 'direction' ] = $activity [ 'push' ] ? Conversation :: PUSH : Conversation :: PULL ;
2020-07-20 04:37:43 +00:00
}
2021-01-09 16:56:42 +00:00
if ( ! empty ( $activity [ 'from-relay' ])) {
$item [ 'direction' ] = Conversation :: RELAY ;
}
2021-04-07 06:02:06 +00:00
if ( $activity [ 'object_type' ] == 'as:Article' ) {
$item [ 'post-type' ] = Item :: PT_ARTICLE ;
} elseif ( $activity [ 'object_type' ] == 'as:Audio' ) {
$item [ 'post-type' ] = Item :: PT_AUDIO ;
} elseif ( $activity [ 'object_type' ] == 'as:Document' ) {
$item [ 'post-type' ] = Item :: PT_DOCUMENT ;
} elseif ( $activity [ 'object_type' ] == 'as:Event' ) {
$item [ 'post-type' ] = Item :: PT_EVENT ;
} elseif ( $activity [ 'object_type' ] == 'as:Image' ) {
$item [ 'post-type' ] = Item :: PT_IMAGE ;
} elseif ( $activity [ 'object_type' ] == 'as:Page' ) {
$item [ 'post-type' ] = Item :: PT_PAGE ;
2022-01-23 04:40:45 +00:00
} elseif ( $activity [ 'object_type' ] == 'as:Question' ) {
$item [ 'post-type' ] = Item :: PT_POLL ;
2021-04-07 06:02:06 +00:00
} elseif ( $activity [ 'object_type' ] == 'as:Video' ) {
$item [ 'post-type' ] = Item :: PT_VIDEO ;
} else {
$item [ 'post-type' ] = Item :: PT_NOTE ;
}
2020-07-20 04:37:43 +00:00
$item [ 'isForum' ] = false ;
if ( ! empty ( $activity [ 'thread-completion' ])) {
2020-09-25 12:16:08 +00:00
if ( $activity [ 'thread-completion' ] != $item [ 'owner-id' ]) {
$actor = Contact :: getById ( $activity [ 'thread-completion' ], [ 'url' ]);
$item [ 'causer-link' ] = $actor [ 'url' ];
$item [ 'causer-id' ] = $activity [ 'thread-completion' ];
Logger :: info ( 'Use inherited actor as causer.' , [ 'id' => $item [ 'owner-id' ], 'activity' => $activity [ 'thread-completion' ], 'owner' => $item [ 'owner-link' ], 'actor' => $actor [ 'url' ]]);
} else {
// Store the original actor in the "causer" fields to enable the check for ignored or blocked contacts
$item [ 'causer-link' ] = $item [ 'owner-link' ];
2022-02-12 13:05:56 +00:00
$item [ 'causer-id' ] = $item [ 'owner-id' ];
2020-09-25 12:16:08 +00:00
Logger :: info ( 'Use actor as causer.' , [ 'id' => $item [ 'owner-id' ], 'actor' => $item [ 'owner-link' ]]);
}
2020-07-20 04:37:43 +00:00
$item [ 'owner-link' ] = $item [ 'author-link' ];
$item [ 'owner-id' ] = $item [ 'author-id' ];
} else {
$actor = APContact :: getByURL ( $item [ 'owner-link' ], false );
$item [ 'isForum' ] = ( $actor [ 'type' ] == 'Group' );
}
$item [ 'uri' ] = $activity [ 'id' ];
2021-12-11 15:17:34 +00:00
if ( empty ( $activity [ 'published' ]) || empty ( $activity [ 'updated' ])) {
DI :: logger () -> notice ( 'published or updated keys are empty for activity' , [ 'activity' => $activity , 'callstack' => System :: callstack ( 10 )]);
}
2021-12-11 15:16:57 +00:00
$item [ 'created' ] = DateTimeFormat :: utc ( $activity [ 'published' ] ? ? 'now' );
$item [ 'edited' ] = DateTimeFormat :: utc ( $activity [ 'updated' ] ? ? 'now' );
2020-07-20 04:37:43 +00:00
$guid = $activity [ 'sc:identifier' ] ? : self :: getGUIDByURL ( $item [ 'uri' ]);
$item [ 'guid' ] = $activity [ 'diaspora:guid' ] ? : $guid ;
$item [ 'uri-id' ] = ItemURI :: insert ([ 'uri' => $item [ 'uri' ], 'guid' => $item [ 'guid' ]]);
2020-12-13 18:42:08 +00:00
if ( empty ( $item [ 'uri-id' ])) {
Logger :: warning ( 'Unable to get a uri-id for an item uri' , [ 'uri' => $item [ 'uri' ], 'guid' => $item [ 'guid' ]]);
return [];
}
2020-07-20 04:37:43 +00:00
2022-08-10 09:28:18 +00:00
$item [ 'thr-parent-id' ] = ItemURI :: getIdByURI ( $item [ 'thr-parent' ]);
2020-07-20 04:37:43 +00:00
$item = self :: processContent ( $activity , $item );
if ( empty ( $item )) {
2020-09-20 07:46:23 +00:00
Logger :: info ( 'Message was not processed' );
2022-08-07 19:24:50 +00:00
Queue :: remove ( $activity );
2020-07-20 04:37:43 +00:00
return [];
}
$item [ 'plink' ] = $activity [ 'alternate-url' ] ? ? $item [ 'uri' ];
2021-04-26 06:50:12 +00:00
self :: storeAttachments ( $activity , $item );
2022-04-20 06:28:02 +00:00
self :: storeQuestion ( $activity , $item );
2020-07-20 04:37:43 +00:00
2021-03-10 22:31:33 +00:00
// We received the post via AP, so we set the protocol of the server to AP
$contact = Contact :: getById ( $item [ 'author-id' ], [ 'gsid' ]);
if ( ! empty ( $contact [ 'gsid' ])) {
GServer :: setProtocol ( $contact [ 'gsid' ], Post\DeliveryData :: ACTIVITYPUB );
}
if ( $item [ 'author-id' ] != $item [ 'owner-id' ]) {
$contact = Contact :: getById ( $item [ 'owner-id' ], [ 'gsid' ]);
if ( ! empty ( $contact [ 'gsid' ])) {
GServer :: setProtocol ( $contact [ 'gsid' ], Post\DeliveryData :: ACTIVITYPUB );
}
}
2020-07-20 04:37:43 +00:00
return $item ;
2018-10-03 06:15:07 +00:00
}
2022-08-03 03:38:03 +00:00
/**
* Fetch and process parent posts for the given activity
*
* @ param array $activity
2022-08-10 09:28:18 +00:00
* @ param bool $in_background
2022-08-03 03:38:03 +00:00
*
* @ return string
*/
2022-08-10 09:28:18 +00:00
private static function fetchParent ( array $activity , bool $in_background = false ) : string
2022-08-03 03:38:03 +00:00
{
2022-08-10 09:28:18 +00:00
if ( self :: isFetched ( $activity [ 'reply-to-id' ])) {
Logger :: info ( 'Id is already fetched' , [ 'id' => $activity [ 'reply-to-id' ]]);
return '' ;
}
self :: addActivityId ( $activity [ 'reply-to-id' ]);
if ( ! DI :: config () -> get ( 'system' , 'fetch_by_worker' )) {
$in_background = false ;
}
2022-08-03 03:38:03 +00:00
$recursion_depth = $activity [ 'recursion-depth' ] ? ? 0 ;
2022-08-10 09:28:18 +00:00
if ( ! $in_background && ( $recursion_depth < DI :: config () -> get ( 'system' , 'max_recursion_depth' ))) {
2022-08-03 03:38:03 +00:00
Logger :: notice ( 'Parent not found. Try to refetch it.' , [ 'parent' => $activity [ 'reply-to-id' ], 'recursion-depth' => $recursion_depth ]);
$result = self :: fetchMissingActivity ( $activity [ 'reply-to-id' ], $activity , '' , Receiver :: COMPLETION_AUTO );
if ( empty ( $result ) && self :: isActivityGone ( $activity [ 'reply-to-id' ])) {
Logger :: notice ( 'The activity is gone, the queue entry will be deleted' , [ 'parent' => $activity [ 'reply-to-id' ]]);
if ( ! empty ( $activity [ 'entry-id' ])) {
Queue :: deleteById ( $activity [ 'entry-id' ]);
}
return '' ;
} elseif ( ! empty ( $result )) {
$exists = Post :: exists ([ 'uri' => [ $result , $activity [ 'reply-to-id' ]]]);
if ( $exists ) {
Logger :: notice ( 'The activity has been fetched and created.' , [ 'parent' => $result ]);
return $result ;
} elseif ( DI :: config () -> get ( 'system' , 'fetch_by_worker' ) || DI :: config () -> get ( 'system' , 'decoupled_receiver' )) {
Logger :: notice ( 'The activity has been fetched and will hopefully be created later.' , [ 'parent' => $result ]);
} else {
Logger :: notice ( 'The activity exists but has not been created, the queue entry will be deleted.' , [ 'parent' => $result ]);
if ( ! empty ( $activity [ 'entry-id' ])) {
Queue :: deleteById ( $activity [ 'entry-id' ]);
}
}
return '' ;
}
if ( empty ( $result ) && ! DI :: config () -> get ( 'system' , 'fetch_by_worker' )) {
return '' ;
}
} elseif ( self :: isActivityGone ( $activity [ 'reply-to-id' ])) {
Logger :: notice ( 'The activity is gone. We will not spawn a worker. The queue entry will be deleted' , [ 'parent' => $activity [ 'reply-to-id' ]]);
2022-08-10 09:28:18 +00:00
if ( $in_background ) {
// fetching in background is done for all activities where we have got the conversation
2022-08-10 13:43:00 +00:00
// There we only delete the single activity and not the whole thread since we can store the
2022-08-10 09:28:18 +00:00
// other posts in the thread even with missing posts.
Queue :: remove ( $activity );
} elseif ( ! empty ( $activity [ 'entry-id' ])) {
2022-08-03 03:38:03 +00:00
Queue :: deleteById ( $activity [ 'entry-id' ]);
}
return '' ;
2022-08-10 09:28:18 +00:00
} elseif ( $in_background ) {
Logger :: notice ( 'Fetching is done in the background.' , [ 'parent' => $activity [ 'reply-to-id' ]]);
2022-08-10 13:43:00 +00:00
} else {
Logger :: notice ( 'Recursion level is too high.' , [ 'parent' => $activity [ 'reply-to-id' ], 'recursion-depth' => $recursion_depth ]);
2022-08-03 03:38:03 +00:00
}
if ( ! Fetch :: hasWorker ( $activity [ 'reply-to-id' ])) {
Logger :: notice ( 'Fetching is done by worker.' , [ 'parent' => $activity [ 'reply-to-id' ], 'recursion-depth' => $recursion_depth ]);
Fetch :: add ( $activity [ 'reply-to-id' ]);
$activity [ 'recursion-depth' ] = 0 ;
$wid = Worker :: add ( PRIORITY_HIGH , 'FetchMissingActivity' , $activity [ 'reply-to-id' ], $activity , '' , Receiver :: COMPLETION_AUTO );
Fetch :: setWorkerId ( $activity [ 'reply-to-id' ], $wid );
} else {
Logger :: debug ( 'Activity will already be fetched via a worker.' , [ 'url' => $activity [ 'reply-to-id' ]]);
}
return '' ;
}
2022-07-24 13:09:35 +00:00
/**
* Check if a given activity is no longer available
*
* @ param string $url
*
* @ return boolean
*/
2022-07-24 21:58:09 +00:00
public static function isActivityGone ( string $url ) : bool
2022-07-24 13:09:35 +00:00
{
$curlResult = HTTPSignature :: fetchRaw ( $url , 0 );
if ( Network :: isUrlBlocked ( $url )) {
return true ;
}
// @todo To ensure that the remote system is working correctly, we can check if the "Content-Type" contains JSON
2022-08-07 19:24:50 +00:00
if ( in_array ( $curlResult -> getReturnCode (), [ 401 , 404 ])) {
2022-07-27 17:39:00 +00:00
return true ;
}
2022-08-07 19:24:50 +00:00
if ( $curlResult -> isSuccess ()) {
$object = json_decode ( $curlResult -> getBody (), true );
if ( ! empty ( $object )) {
$activity = JsonLD :: compact ( $object );
if ( JsonLD :: fetchElement ( $activity , '@type' ) == 'as:Tombstone' ) {
return true ;
}
}
} elseif ( $curlResult -> getReturnCode () == 0 ) {
$host = parse_url ( $url , PHP_URL_HOST );
if ( ! ( filter_var ( $host , FILTER_VALIDATE_IP ) || @ dns_get_record ( $host . '.' , DNS_A + DNS_AAAA ))) {
2022-07-27 17:39:00 +00:00
return true ;
2022-08-07 19:24:50 +00:00
}
2022-07-27 17:39:00 +00:00
}
return false ;
2022-07-24 13:09:35 +00:00
}
2018-10-03 06:15:07 +00:00
/**
2018-10-06 04:18:40 +00:00
* Delete items
2018-10-03 06:15:07 +00:00
*
* @ param array $activity
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
* @ throws \ImagickException
2018-10-03 06:15:07 +00:00
*/
2022-06-16 11:14:00 +00:00
public static function deleteItem ( array $activity )
2018-10-03 06:15:07 +00:00
{
2018-10-07 17:35:43 +00:00
$owner = Contact :: getIdForURL ( $activity [ 'actor' ]);
2018-10-07 18:41:45 +00:00
2020-06-28 17:50:11 +00:00
Logger :: info ( 'Deleting item' , [ 'object' => $activity [ 'object_id' ], 'owner' => $owner ]);
2020-03-03 06:47:28 +00:00
Item :: markForDeletion ([ 'uri' => $activity [ 'object_id' ], 'owner-id' => $owner ]);
2022-07-21 05:16:14 +00:00
Queue :: remove ( $activity );
2018-10-03 06:15:07 +00:00
}
2019-05-26 11:20:03 +00:00
/**
* Prepare the item array for an activity
*
* @ param array $activity Activity array
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
* @ throws \ImagickException
*/
2022-06-16 11:14:00 +00:00
public static function addTag ( array $activity )
2019-05-26 11:20:03 +00:00
{
if ( empty ( $activity [ 'object_content' ]) || empty ( $activity [ 'object_id' ])) {
return ;
}
foreach ( $activity [ 'receiver' ] as $receiver ) {
2021-01-19 10:30:45 +00:00
$item = Post :: selectFirst ([ 'id' , 'uri-id' , 'origin' , 'author-link' ], [ 'uri' => $activity [ 'target_id' ], 'uid' => $receiver ]);
2019-05-26 11:20:03 +00:00
if ( ! DBA :: isResult ( $item )) {
// We don't fetch missing content for this purpose
continue ;
}
if (( $item [ 'author-link' ] != $activity [ 'actor' ]) && ! $item [ 'origin' ]) {
Logger :: info ( 'Not origin, not from the author, skipping update' , [ 'id' => $item [ 'id' ], 'author' => $item [ 'author-link' ], 'actor' => $activity [ 'actor' ]]);
continue ;
}
2020-04-17 13:34:29 +00:00
Tag :: store ( $item [ 'uri-id' ], Tag :: HASHTAG , $activity [ 'object_content' ], $activity [ 'object_id' ]);
2020-05-05 05:11:59 +00:00
Logger :: info ( 'Tagged item' , [ 'id' => $item [ 'id' ], 'tag' => $activity [ 'object_content' ], 'uri' => $activity [ 'target_id' ], 'actor' => $activity [ 'actor' ]]);
2019-05-26 11:20:03 +00:00
}
}
2018-10-03 06:15:07 +00:00
/**
2018-10-27 06:17:17 +00:00
* Prepare the item array for an activity
2018-10-03 06:15:07 +00:00
*
2022-06-25 03:48:49 +00:00
* @ param array $activity Activity array
* @ param string $verb Activity verb
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
* @ throws \ImagickException
2018-10-03 06:15:07 +00:00
*/
2022-07-21 05:16:14 +00:00
public static function createActivity ( array $activity , string $verb )
2018-10-03 06:15:07 +00:00
{
2022-07-23 06:52:43 +00:00
$activity [ 'reply-to-id' ] = $activity [ 'object_id' ];
2022-08-06 17:06:55 +00:00
$item = self :: createItem ( $activity , false );
2022-03-22 12:28:47 +00:00
if ( empty ( $item )) {
return ;
}
2018-10-27 06:17:17 +00:00
$item [ 'verb' ] = $verb ;
2019-02-23 04:42:04 +00:00
$item [ 'thr-parent' ] = $activity [ 'object_id' ];
2018-10-03 06:15:07 +00:00
$item [ 'gravity' ] = GRAVITY_ACTIVITY ;
2021-04-07 06:02:06 +00:00
unset ( $item [ 'post-type' ]);
2019-10-24 22:10:20 +00:00
$item [ 'object-type' ] = Activity\ObjectType :: NOTE ;
2018-10-03 06:15:07 +00:00
2022-04-04 16:03:53 +00:00
if ( ! empty ( $activity [ 'content' ])) {
$item [ 'body' ] = HTML :: toBBCode ( $activity [ 'content' ]);
}
2019-10-16 12:35:14 +00:00
$item [ 'diaspora_signed_text' ] = $activity [ 'diaspora:like' ] ? ? '' ;
2018-10-27 14:35:22 +00:00
2018-10-11 20:08:04 +00:00
self :: postItem ( $activity , $item );
2018-10-03 06:15:07 +00:00
}
2022-04-04 23:07:44 +00:00
/**
* Fetch the Uri - Id of a post for the " featured " collection
*
2022-05-30 20:52:43 +00:00
* @ param array $activity
2022-08-15 13:23:01 +00:00
* @ return null | array
2022-04-04 23:07:44 +00:00
*/
private static function getUriIdForFeaturedCollection ( array $activity )
{
$actor = APContact :: getByURL ( $activity [ 'actor' ]);
if ( empty ( $actor )) {
return null ;
}
// Refetch the account when the "featured" collection is missing.
// This can be removed in a future version (end of 2022 should be good).
if ( empty ( $actor [ 'featured' ])) {
$actor = APContact :: getByURL ( $activity [ 'actor' ], true );
if ( empty ( $actor )) {
return null ;
}
}
2022-08-15 13:23:01 +00:00
$parent = Post :: selectFirst ([ 'uri-id' , 'author-id' ], [ 'uri' => $activity [ 'object_id' ]]);
2022-07-24 09:26:52 +00:00
if ( empty ( $parent [ 'uri-id' ])) {
if ( self :: fetchMissingActivity ( $activity [ 'object_id' ], $activity , '' , Receiver :: COMPLETION_AUTO )) {
$parent = Post :: selectFirst ([ 'uri-id' ], [ 'uri' => $activity [ 'object_id' ]]);
}
2022-04-04 23:07:44 +00:00
}
if ( ! empty ( $parent [ 'uri-id' ])) {
2022-08-15 13:23:01 +00:00
$parent ;
2022-04-04 23:07:44 +00:00
}
return null ;
}
/**
* Add a post to the " Featured " collection
*
2022-05-30 20:52:43 +00:00
* @ param array $activity
2022-04-04 23:07:44 +00:00
*/
public static function addToFeaturedCollection ( array $activity )
{
2022-08-15 13:23:01 +00:00
$post = self :: getUriIdForFeaturedCollection ( $activity );
if ( empty ( $post )) {
2022-04-04 23:07:44 +00:00
return ;
}
2022-08-15 13:23:01 +00:00
Logger :: debug ( 'Add post to featured collection' , [ 'post' => $post ]);
2022-04-04 23:07:44 +00:00
2022-08-15 13:23:01 +00:00
Post\Collection :: add ( $post [ 'uri-id' ], Post\Collection :: FEATURED , $post [ 'author-id' ]);
2022-07-21 05:16:14 +00:00
Queue :: remove ( $activity );
2022-04-04 23:07:44 +00:00
}
/**
* Remove a post to the " Featured " collection
*
2022-05-30 20:52:43 +00:00
* @ param array $activity
2022-04-04 23:07:44 +00:00
*/
public static function removeFromFeaturedCollection ( array $activity )
{
2022-08-15 13:23:01 +00:00
$post = self :: getUriIdForFeaturedCollection ( $activity );
if ( empty ( $post )) {
2022-04-04 23:07:44 +00:00
return ;
}
2022-08-15 13:23:01 +00:00
Logger :: debug ( 'Remove post from featured collection' , [ 'post' => $post ]);
2022-04-04 23:07:44 +00:00
2022-08-15 13:23:01 +00:00
Post\Collection :: remove ( $post [ 'uri-id' ], Post\Collection :: FEATURED );
2022-07-21 05:16:14 +00:00
Queue :: remove ( $activity );
2022-04-04 23:07:44 +00:00
}
2018-10-26 04:13:26 +00:00
/**
* Create an event
*
2018-10-27 06:17:17 +00:00
* @ param array $activity Activity array
* @ param array $item
2021-10-03 09:42:14 +00:00
*
2021-07-18 15:05:46 +00:00
* @ return int event id
2019-01-06 21:06:53 +00:00
* @ throws \Exception
2018-10-26 04:13:26 +00:00
*/
2022-06-16 11:14:00 +00:00
public static function createEvent ( array $activity , array $item ) : int
2018-10-26 04:13:26 +00:00
{
2022-04-01 11:20:17 +00:00
$event [ 'summary' ] = HTML :: toBBCode ( $activity [ 'name' ] ? : $activity [ 'summary' ]);
2022-08-16 04:18:29 +00:00
$event [ 'desc' ] = HTML :: toBBCode ( $activity [ 'content' ] ? ? '' );
2022-05-15 18:00:19 +00:00
if ( ! empty ( $activity [ 'start-time' ])) {
$event [ 'start' ] = DateTimeFormat :: utc ( $activity [ 'start-time' ]);
}
if ( ! empty ( $activity [ 'end-time' ])) {
$event [ 'finish' ] = DateTimeFormat :: utc ( $activity [ 'end-time' ]);
}
2021-01-09 12:59:30 +00:00
$event [ 'nofinish' ] = empty ( $event [ 'finish' ]);
$event [ 'location' ] = $activity [ 'location' ];
$event [ 'cid' ] = $item [ 'contact-id' ];
$event [ 'uid' ] = $item [ 'uid' ];
$event [ 'uri' ] = $item [ 'uri' ];
$event [ 'edited' ] = $item [ 'edited' ];
$event [ 'private' ] = $item [ 'private' ];
$event [ 'guid' ] = $item [ 'guid' ];
$event [ 'plink' ] = $item [ 'plink' ];
$event [ 'network' ] = $item [ 'network' ];
$event [ 'protocol' ] = $item [ 'protocol' ];
$event [ 'direction' ] = $item [ 'direction' ];
$event [ 'source' ] = $item [ 'source' ];
2018-10-26 04:13:26 +00:00
2021-07-18 15:05:46 +00:00
$ev = DBA :: selectFirst ( 'event' , [ 'id' ], [ 'uri' => $item [ 'uri' ], 'uid' => $item [ 'uid' ]]);
2018-10-26 04:13:26 +00:00
if ( DBA :: isResult ( $ev )) {
$event [ 'id' ] = $ev [ 'id' ];
}
$event_id = Event :: store ( $event );
2021-07-18 15:05:46 +00:00
2020-06-28 17:50:11 +00:00
Logger :: info ( 'Event was stored' , [ 'id' => $event_id ]);
2021-07-18 15:05:46 +00:00
return $event_id ;
2018-10-26 04:13:26 +00:00
}
2019-03-17 13:50:14 +00:00
/**
* Process the content
*
* @ param array $activity Activity array
* @ param array $item
2019-03-17 15:49:21 +00:00
* @ return array | bool Returns the item array or false if there was an unexpected occurrence
2019-03-17 13:50:14 +00:00
* @ throws \Exception
*/
2022-06-16 11:14:00 +00:00
private static function processContent ( array $activity , array $item )
2019-03-17 13:50:14 +00:00
{
2021-03-06 08:43:25 +00:00
if ( ! empty ( $activity [ 'mediatype' ]) && ( $activity [ 'mediatype' ] == 'text/markdown' )) {
2022-06-11 06:50:59 +00:00
$item [ 'title' ] = strip_tags ( $activity [ 'name' ]);
2021-03-06 08:43:25 +00:00
$content = Markdown :: toBBCode ( $activity [ 'content' ]);
} elseif ( ! empty ( $activity [ 'mediatype' ]) && ( $activity [ 'mediatype' ] == 'text/bbcode' )) {
$item [ 'title' ] = $activity [ 'name' ];
$content = $activity [ 'content' ];
} else {
// By default assume "text/html"
2022-06-16 15:03:25 +00:00
$item [ 'title' ] = HTML :: toBBCode ( $activity [ 'name' ] ? ? '' );
$content = HTML :: toBBCode ( $activity [ 'content' ] ? ? '' );
2021-03-06 08:43:25 +00:00
}
2019-03-17 13:50:14 +00:00
2022-06-11 06:50:59 +00:00
$item [ 'title' ] = trim ( BBCode :: toPlaintext ( $item [ 'title' ]));
2021-03-06 08:43:25 +00:00
if ( ! empty ( $activity [ 'languages' ])) {
$item [ 'language' ] = self :: processLanguages ( $activity [ 'languages' ]);
}
2019-03-17 22:13:17 +00:00
2020-10-29 05:20:26 +00:00
if ( ! empty ( $activity [ 'emojis' ])) {
2021-10-03 09:42:14 +00:00
$content = self :: replaceEmojis ( $item [ 'uri-id' ], $content , $activity [ 'emojis' ]);
2020-10-29 05:20:26 +00:00
}
2019-03-17 22:13:17 +00:00
2021-08-16 02:53:42 +00:00
$content = self :: addMentionLinks ( $content , $activity [ 'tags' ]);
2019-03-17 13:50:14 +00:00
2020-10-29 05:20:26 +00:00
if ( ! empty ( $activity [ 'source' ])) {
$item [ 'body' ] = $activity [ 'source' ];
$item [ 'raw-body' ] = $content ;
2021-05-07 06:26:41 +00:00
$item [ 'body' ] = Item :: improveSharedDataInBody ( $item );
2020-10-29 05:20:26 +00:00
} else {
2022-07-23 06:52:43 +00:00
$parent_uri = $item [ 'parent-uri' ] ? ? $item [ 'thr-parent' ];
if ( empty ( $activity [ 'directmessage' ]) && ( $parent_uri != $item [ 'uri' ]) && ( $item [ 'gravity' ] == GRAVITY_COMMENT )) {
$parent = Post :: selectFirst ([ 'id' , 'uri-id' , 'private' , 'author-link' , 'alias' ], [ 'uri' => $parent_uri ]);
2019-03-17 13:50:14 +00:00
if ( ! DBA :: isResult ( $parent )) {
2022-07-23 06:52:43 +00:00
Logger :: warning ( 'Unknown parent item.' , [ 'uri' => $parent_uri ]);
2019-03-17 13:50:14 +00:00
return false ;
}
2022-08-07 19:24:50 +00:00
if ( ! empty ( $activity [ 'type' ]) && in_array ( $activity [ 'type' ], Receiver :: CONTENT_TYPES ) && ( $item [ 'private' ] == Item :: PRIVATE ) && ( $parent [ 'private' ] != Item :: PRIVATE )) {
2019-03-17 13:50:14 +00:00
Logger :: warning ( 'Item is private but the parent is not. Dropping.' , [ 'item-uri' => $item [ 'uri' ], 'thr-parent' => $item [ 'thr-parent' ]]);
return false ;
}
2020-05-09 08:55:10 +00:00
$content = self :: removeImplicitMentionsFromBody ( $content , $parent );
2019-03-17 13:50:14 +00:00
}
2022-06-16 14:59:54 +00:00
$item [ 'content-warning' ] = HTML :: toBBCode ( $activity [ 'summary' ] ? ? '' );
2020-10-29 05:20:26 +00:00
$item [ 'raw-body' ] = $item [ 'body' ] = $content ;
2019-03-17 13:50:14 +00:00
}
2020-04-20 09:47:26 +00:00
self :: storeFromBody ( $item );
2020-04-14 17:18:48 +00:00
self :: storeTags ( $item [ 'uri-id' ], $activity [ 'tags' ]);
2020-04-13 23:54:28 +00:00
2022-02-19 13:31:49 +00:00
self :: storeReceivers ( $item [ 'uri-id' ], $activity [ 'receiver_urls' ] ? ? []);
2019-03-17 13:50:14 +00:00
$item [ 'location' ] = $activity [ 'location' ];
2020-06-28 08:46:27 +00:00
if ( ! empty ( $activity [ 'latitude' ]) && ! empty ( $activity [ 'longitude' ])) {
$item [ 'coord' ] = $activity [ 'latitude' ] . ' ' . $activity [ 'longitude' ];
2019-03-17 13:50:14 +00:00
}
$item [ 'app' ] = $activity [ 'generator' ];
return $item ;
}
2020-04-20 12:19:26 +00:00
/**
* Store hashtags and mentions
*
* @ param array $item
*/
private static function storeFromBody ( array $item )
2020-04-20 09:47:26 +00:00
{
// Make sure to delete all existing tags (can happen when called via the update functionality)
2020-04-20 12:19:26 +00:00
DBA :: delete ( 'post-tag' , [ 'uri-id' => $item [ 'uri-id' ]]);
2020-04-20 09:47:26 +00:00
Tag :: storeFromBody ( $item [ 'uri-id' ], $item [ 'body' ], '@!' );
}
2020-01-19 14:33:16 +00:00
/**
2022-03-17 14:05:06 +00:00
* Generate a GUID out of an URL of an ActivityPub post .
2020-01-19 14:33:16 +00:00
*
* @ param string $url message URL
* @ return string with GUID
*/
2022-06-16 12:59:29 +00:00
private static function getGUIDByURL ( string $url ) : string
2020-01-19 14:33:16 +00:00
{
$parsed = parse_url ( $url );
$host_hash = hash ( 'crc32' , $parsed [ 'host' ]);
unset ( $parsed [ " scheme " ]);
unset ( $parsed [ " host " ]);
$path = implode ( " / " , $parsed );
return $host_hash . '-' . hash ( 'fnv164' , $path ) . '-' . hash ( 'joaat' , $path );
}
2022-03-11 14:00:05 +00:00
/**
* Checks if an incoming message is wanted
*
* @ param array $activity
* @ param array $item
* @ return boolean Is the message wanted ?
*/
2022-06-16 12:59:29 +00:00
private static function isSolicitedMessage ( array $activity , array $item ) : bool
2022-03-11 14:00:05 +00:00
{
// The checks are split to improve the support when searching why a message was accepted.
if ( count ( $activity [ 'receiver' ]) != 1 ) {
// The message has more than one receiver, so it is wanted.
Logger :: debug ( 'Message has got several receivers - accepted' , [ 'uri-id' => $item [ 'uri-id' ], 'guid' => $item [ 'guid' ], 'url' => $item [ 'uri' ]]);
return true ;
}
if ( $item [ 'private' ] == Item :: PRIVATE ) {
// We only look at public posts here. Private posts are expected to be intentionally posted to the single receiver.
Logger :: debug ( 'Message is private - accepted' , [ 'uri-id' => $item [ 'uri-id' ], 'guid' => $item [ 'guid' ], 'url' => $item [ 'uri' ]]);
return true ;
}
2022-03-12 07:34:30 +00:00
2022-03-11 14:00:05 +00:00
if ( ! empty ( $activity [ 'from-relay' ])) {
// We check relay posts at another place. When it arrived here, the message is already checked.
Logger :: debug ( 'Message is a relay post that is already checked - accepted' , [ 'uri-id' => $item [ 'uri-id' ], 'guid' => $item [ 'guid' ], 'url' => $item [ 'uri' ]]);
return true ;
}
2022-03-12 11:17:33 +00:00
if ( in_array ( $activity [ 'completion-mode' ] ? ? Receiver :: COMPLETION_NONE , [ Receiver :: COMPLETION_MANUAL , Receiver :: COMPLETION_ANNOUCE ])) {
// Manual completions and completions caused by reshares are allowed without any further checks.
Logger :: debug ( 'Message is in completion mode - accepted' , [ 'mode' => $activity [ 'completion-mode' ], 'uri-id' => $item [ 'uri-id' ], 'guid' => $item [ 'guid' ], 'url' => $item [ 'uri' ]]);
2022-03-11 14:00:05 +00:00
return true ;
}
if ( $item [ 'gravity' ] != GRAVITY_PARENT ) {
// We cannot reliably check at this point if a comment or activity belongs to an accepted post or needs to be fetched
// This can possibly be improved in the future.
Logger :: debug ( 'Message is no parent - accepted' , [ 'uri-id' => $item [ 'uri-id' ], 'guid' => $item [ 'guid' ], 'url' => $item [ 'uri' ]]);
return true ;
}
$tags = array_column ( Tag :: getByURIId ( $item [ 'uri-id' ], [ Tag :: HASHTAG ]), 'name' );
2022-03-12 07:34:30 +00:00
if ( Relay :: isSolicitedPost ( $tags , $item [ 'body' ], $item [ 'author-id' ], $item [ 'uri' ], Protocol :: ACTIVITYPUB )) {
Logger :: debug ( 'Post is accepted because of the relay settings' , [ 'uri-id' => $item [ 'uri-id' ], 'guid' => $item [ 'guid' ], 'url' => $item [ 'uri' ]]);
return true ;
} else {
return false ;
}
2022-03-11 14:00:05 +00:00
}
2018-10-03 06:15:07 +00:00
/**
2018-10-07 18:41:45 +00:00
* Creates an item post
2018-10-03 06:15:07 +00:00
*
2019-01-06 21:06:53 +00:00
* @ param array $activity Activity data
* @ param array $item item array
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
* @ throws \ImagickException
2018-10-03 06:15:07 +00:00
*/
2020-07-20 04:37:43 +00:00
public static function postItem ( array $activity , array $item )
2018-10-03 06:15:07 +00:00
{
2020-07-21 19:55:24 +00:00
if ( empty ( $item )) {
return ;
}
2019-02-10 18:42:51 +00:00
$stored = false ;
2022-07-21 05:16:14 +00:00
$success = false ;
2020-09-25 06:47:07 +00:00
ksort ( $activity [ 'receiver' ]);
2019-02-10 18:42:51 +00:00
2022-03-11 14:00:05 +00:00
if ( ! self :: isSolicitedMessage ( $activity , $item )) {
DBA :: delete ( 'item-uri' , [ 'id' => $item [ 'uri-id' ]]);
2022-08-07 19:24:50 +00:00
if ( ! empty ( $activity [ 'entry-id' ])) {
Queue :: deleteById ( $activity [ 'entry-id' ]);
}
2022-03-11 14:00:05 +00:00
return ;
}
2018-10-03 06:15:07 +00:00
foreach ( $activity [ 'receiver' ] as $receiver ) {
2020-03-02 07:57:23 +00:00
if ( $receiver == - 1 ) {
continue ;
}
2018-10-03 06:15:07 +00:00
$item [ 'uid' ] = $receiver ;
2019-07-16 05:07:26 +00:00
2020-09-12 17:45:04 +00:00
$type = $activity [ 'reception_type' ][ $receiver ] ? ? Receiver :: TARGET_UNKNOWN ;
switch ( $type ) {
case Receiver :: TARGET_TO :
2021-04-07 06:02:06 +00:00
$item [ 'post-reason' ] = Item :: PR_TO ;
2020-09-12 17:45:04 +00:00
break ;
case Receiver :: TARGET_CC :
2021-04-07 06:02:06 +00:00
$item [ 'post-reason' ] = Item :: PR_CC ;
2020-09-12 17:45:04 +00:00
break ;
case Receiver :: TARGET_BTO :
2021-04-07 06:02:06 +00:00
$item [ 'post-reason' ] = Item :: PR_BTO ;
2020-09-12 17:45:04 +00:00
break ;
case Receiver :: TARGET_BCC :
2021-04-07 06:02:06 +00:00
$item [ 'post-reason' ] = Item :: PR_BCC ;
2020-09-12 17:45:04 +00:00
break ;
case Receiver :: TARGET_FOLLOWER :
2021-04-07 06:02:06 +00:00
$item [ 'post-reason' ] = Item :: PR_FOLLOWER ;
2020-09-12 17:45:04 +00:00
break ;
2020-09-13 14:15:28 +00:00
case Receiver :: TARGET_ANSWER :
2021-04-07 06:02:06 +00:00
$item [ 'post-reason' ] = Item :: PR_COMMENT ;
2020-09-13 14:15:28 +00:00
break ;
2020-09-14 17:48:57 +00:00
case Receiver :: TARGET_GLOBAL :
2021-04-07 06:02:06 +00:00
$item [ 'post-reason' ] = Item :: PR_GLOBAL ;
2020-09-14 17:48:57 +00:00
break ;
2020-09-12 17:45:04 +00:00
default :
2021-04-07 06:02:06 +00:00
$item [ 'post-reason' ] = Item :: PR_NONE ;
2020-09-12 17:45:04 +00:00
}
2022-08-07 19:24:50 +00:00
$item [ 'post-reason' ] = Item :: getPostReason ( $item );
if ( in_array ( $item [ 'post-reason' ], [ Item :: PR_GLOBAL , Item :: PR_NONE ])) {
if ( ! empty ( $activity [ 'from-relay' ])) {
$item [ 'post-reason' ] = Item :: PR_RELAY ;
} elseif ( ! empty ( $activity [ 'thread-completion' ])) {
$item [ 'post-reason' ] = Item :: PR_FETCHED ;
} elseif ( ! empty ( $activity [ 'push' ])) {
$item [ 'post-reason' ] = Item :: PR_PUSHED ;
}
2020-09-21 12:31:20 +00:00
}
2020-07-20 04:37:43 +00:00
if ( $item [ 'isForum' ] ? ? false ) {
2020-08-07 13:49:59 +00:00
$item [ 'contact-id' ] = Contact :: getIdForURL ( $activity [ 'actor' ], $receiver );
2019-07-30 13:08:14 +00:00
} else {
2020-08-07 13:49:59 +00:00
$item [ 'contact-id' ] = Contact :: getIdForURL ( $activity [ 'author' ], $receiver );
2019-07-16 05:07:26 +00:00
}
2018-10-03 06:15:07 +00:00
2019-08-02 00:39:42 +00:00
if (( $receiver != 0 ) && empty ( $item [ 'contact-id' ])) {
2020-08-07 13:49:59 +00:00
$item [ 'contact-id' ] = Contact :: getIdForURL ( $activity [ 'author' ]);
2018-10-03 06:15:07 +00:00
}
2019-05-16 05:44:59 +00:00
if ( ! empty ( $activity [ 'directmessage' ])) {
self :: postMail ( $activity , $item );
continue ;
}
2022-08-07 19:24:50 +00:00
if (( $receiver != 0 ) && ( $item [ 'gravity' ] == GRAVITY_PARENT ) && ! in_array ( $item [ 'post-reason' ], [ Item :: PR_FOLLOWER , Item :: PR_TAG , item :: PR_TO , Item :: PR_CC ])) {
if ( ! ( $item [ 'isForum' ] ? ? false )) {
if ( $item [ 'post-reason' ] == Item :: PR_BCC ) {
Logger :: info ( 'Top level post via BCC from a non sharer, ignoring' , [ 'uid' => $receiver , 'contact' => $item [ 'contact-id' ], 'url' => $item [ 'uri' ]]);
continue ;
}
2022-02-15 20:40:18 +00:00
2022-08-07 19:24:50 +00:00
if (( DI :: pConfig () -> get ( $receiver , 'system' , 'accept_only_sharer' ) != Item :: COMPLETION_LIKE )
&& in_array ( $activity [ 'thread-children-type' ] ? ? '' , Receiver :: ACTIVITY_TYPES )) {
Logger :: info ( 'Top level post from thread completion from a non sharer had been initiated via an activity, ignoring' ,
[ 'type' => $activity [ 'thread-children-type' ], 'user' => $item [ 'uid' ], 'causer' => $item [ 'causer-link' ], 'author' => $activity [ 'author' ], 'url' => $item [ 'uri' ]]);
continue ;
}
2022-02-12 13:05:56 +00:00
}
2021-05-26 20:52:39 +00:00
2022-08-07 19:24:50 +00:00
$is_forum = false ;
2022-02-04 06:02:21 +00:00
$user = User :: getById ( $receiver , [ 'account-type' ]);
if ( ! empty ( $user [ 'account-type' ])) {
$is_forum = ( $user [ 'account-type' ] == User :: ACCOUNT_TYPE_COMMUNITY );
}
2019-07-17 21:37:13 +00:00
2022-08-07 19:24:50 +00:00
if (( DI :: pConfig () -> get ( $receiver , 'system' , 'accept_only_sharer' ) == Item :: COMPLETION_NONE )
&& (( ! $is_forum && ! ( $item [ 'isForum' ] ? ? false ) && ( $activity [ 'type' ] != 'as:Announce' ))
|| ! Contact :: isSharingByURL ( $activity [ 'actor' ], $receiver ))) {
Logger :: info ( 'Actor is a non sharer, is no forum or it is no announce' , [ 'uid' => $receiver , 'actor' => $activity [ 'actor' ], 'url' => $item [ 'uri' ], 'type' => $activity [ 'type' ]]);
2019-07-17 21:37:13 +00:00
continue ;
}
Logger :: info ( 'Accepting post' , [ 'uid' => $receiver , 'url' => $item [ 'uri' ]]);
}
2022-08-10 09:28:18 +00:00
if ( ! self :: hasParents ( $item , $receiver )) {
continue ;
}
2020-03-28 14:02:49 +00:00
if (( $item [ 'gravity' ] != GRAVITY_ACTIVITY ) && ( $activity [ 'object_type' ] == 'as:Event' )) {
2021-07-18 15:05:46 +00:00
$event_id = self :: createEvent ( $activity , $item );
2021-08-10 10:24:14 +00:00
$item = Event :: getItemArrayForImportedId ( $event_id , $item );
2018-10-26 04:13:26 +00:00
}
2018-10-03 06:15:07 +00:00
$item_id = Item :: insert ( $item );
2019-02-23 04:00:16 +00:00
if ( $item_id ) {
Logger :: info ( 'Item insertion successful' , [ 'user' => $item [ 'uid' ], 'item_id' => $item_id ]);
2022-07-21 05:16:14 +00:00
$success = true ;
2019-02-23 04:00:16 +00:00
} else {
2022-07-23 23:10:47 +00:00
Logger :: notice ( 'Item insertion aborted' , [ 'uri' => $item [ 'uri' ], 'uid' => $item [ 'uid' ]]);
2022-08-05 05:48:20 +00:00
if (( $item [ 'uid' ] == 0 ) && ( count ( $activity [ 'receiver' ]) > 1 )) {
Logger :: info ( 'Public item was aborted. We skip for all users.' , [ 'uri' => $item [ 'uri' ]]);
break ;
}
2019-02-23 04:00:16 +00:00
}
2019-02-10 18:42:51 +00:00
2019-02-10 18:59:05 +00:00
if ( $item [ 'uid' ] == 0 ) {
$stored = $item_id ;
2019-02-10 18:42:51 +00:00
}
2018-10-03 06:15:07 +00:00
}
2019-01-30 21:33:23 +00:00
2022-07-30 04:43:18 +00:00
Queue :: remove ( $activity );
2022-07-27 20:03:28 +00:00
2022-08-03 05:14:07 +00:00
if ( $success && Queue :: hasChildren ( $item [ 'uri' ]) && Post :: exists ([ 'uri' => $item [ 'uri' ]])) {
2022-08-03 03:38:03 +00:00
Queue :: processReplyByUri ( $item [ 'uri' ]);
2022-07-21 05:16:14 +00:00
}
2019-02-10 18:42:51 +00:00
// Store send a follow request for every reshare - but only when the item had been stored
2022-07-17 11:47:12 +00:00
if ( $stored && ( $item [ 'private' ] != Item :: PRIVATE ) && ( $item [ 'gravity' ] == GRAVITY_PARENT ) && ! empty ( $item [ 'author-link' ]) && ( $item [ 'author-link' ] != $item [ 'owner-link' ])) {
2019-01-30 21:33:23 +00:00
$author = APContact :: getByURL ( $item [ 'owner-link' ], false );
// We send automatic follow requests for reshared messages. (We don't need though for forum posts)
if ( $author [ 'type' ] != 'Group' ) {
2020-06-28 17:50:11 +00:00
Logger :: info ( 'Send follow request' , [ 'uri' => $item [ 'uri' ], 'stored' => $stored , 'to' => $item [ 'author-link' ]]);
2019-01-30 21:33:23 +00:00
ActivityPub\Transmitter :: sendFollowObject ( $item [ 'uri' ], $item [ 'author-link' ]);
}
}
2018-10-03 06:15:07 +00:00
}
2022-08-10 09:28:18 +00:00
/**
* Checks if there are parent posts for the given receiver .
* If not , then the system will try to add them .
*
* @ param array $item
* @ param integer $receiver
* @ return boolean
*/
private static function hasParents ( array $item , int $receiver )
{
if (( $receiver == 0 ) || ( $item [ 'gravity' ] == GRAVITY_PARENT )) {
return true ;
}
$fields = [ 'causer-id' => $item [ 'causer-id' ] ? ? $item [ 'author-id' ], 'post-reason' => Item :: PR_FETCHED ];
$has_parents = false ;
if ( ! empty ( $item [ 'parent-uri-id' ])) {
if ( Post :: exists ([ 'uri-id' => $item [ 'parent-uri-id' ], 'uid' => $receiver ])) {
$has_parents = true ;
} elseif ( Post :: exists ([ 'uri-id' => $item [ 'parent-uri' ], 'uid' => 0 ])) {
$stored = Item :: storeForUserByUriId ( $item [ 'parent-uri-id' ], $receiver , $fields );
$has_parents = ( bool ) $stored ;
if ( $stored ) {
Logger :: notice ( 'Inserted missing parent post' , [ 'stored' => $stored , 'uid' => $receiver , 'parent' => $item [ 'parent-uri' ]]);
} else {
Logger :: notice ( 'Parent could not be added.' , [ 'uid' => $receiver , 'uri' => $item [ 'uri' ], 'parent' => $item [ 'parent-uri' ]]);
return false ;
}
}
}
if ( empty ( $item [ 'parent-uri-id' ]) || ( $item [ 'thr-parent-id' ] != $item [ 'parent-uri-id' ])) {
if ( Post :: exists ([ 'uri-id' => $item [ 'thr-parent-id' ], 'uid' => $receiver ])) {
$has_parents = true ;
} elseif ( Post :: exists ([ 'uri-id' => $item [ 'thr-parent-id' ], 'uid' => 0 ])) {
$stored = Item :: storeForUserByUriId ( $item [ 'thr-parent-id' ], $receiver , $fields );
$has_parents = $has_parents || ( bool ) $stored ;
if ( $stored ) {
Logger :: notice ( 'Inserted missing thread parent post' , [ 'stored' => $stored , 'uid' => $receiver , 'thread-parent' => $item [ 'thr-parent' ]]);
} else {
Logger :: notice ( 'Thread parent could not be added.' , [ 'uid' => $receiver , 'uri' => $item [ 'uri' ], 'thread-parent' => $item [ 'thr-parent' ]]);
}
}
}
return $has_parents ;
}
2020-04-15 20:59:45 +00:00
/**
* Store tags and mentions into the tag table
*
* @ param integer $uriid
* @ param array $tags
*/
2020-04-14 17:18:48 +00:00
private static function storeTags ( int $uriid , array $tags = null )
2020-04-13 23:54:28 +00:00
{
foreach ( $tags as $tag ) {
if ( empty ( $tag [ 'name' ]) || empty ( $tag [ 'type' ]) || ! in_array ( $tag [ 'type' ], [ 'Mention' , 'Hashtag' ])) {
continue ;
}
2020-04-17 06:35:20 +00:00
$hash = substr ( $tag [ 'name' ], 0 , 1 );
2020-04-13 23:54:28 +00:00
if ( $tag [ 'type' ] == 'Mention' ) {
2020-04-17 06:35:20 +00:00
if ( in_array ( $hash , [ Tag :: TAG_CHARACTER [ Tag :: MENTION ],
Tag :: TAG_CHARACTER [ Tag :: EXCLUSIVE_MENTION ],
Tag :: TAG_CHARACTER [ Tag :: IMPLICIT_MENTION ]])) {
$tag [ 'name' ] = substr ( $tag [ 'name' ], 1 );
2020-04-13 23:54:28 +00:00
}
2020-04-20 05:43:13 +00:00
$type = Tag :: IMPLICIT_MENTION ;
2020-04-17 06:35:20 +00:00
2020-04-15 20:45:04 +00:00
if ( ! empty ( $tag [ 'href' ])) {
$apcontact = APContact :: getByURL ( $tag [ 'href' ]);
2020-04-15 20:52:30 +00:00
if ( ! empty ( $apcontact [ 'name' ]) || ! empty ( $apcontact [ 'nick' ])) {
2020-04-17 06:35:20 +00:00
$tag [ 'name' ] = $apcontact [ 'name' ] ? : $apcontact [ 'nick' ];
2020-04-15 20:45:04 +00:00
}
}
2020-04-13 23:54:28 +00:00
} elseif ( $tag [ 'type' ] == 'Hashtag' ) {
2020-04-20 05:43:13 +00:00
if ( $hash == Tag :: TAG_CHARACTER [ Tag :: HASHTAG ]) {
2020-04-17 06:35:20 +00:00
$tag [ 'name' ] = substr ( $tag [ 'name' ], 1 );
2020-04-13 23:54:28 +00:00
}
2020-04-20 05:43:13 +00:00
$type = Tag :: HASHTAG ;
2020-04-13 23:54:28 +00:00
}
2020-04-17 06:35:20 +00:00
if ( empty ( $tag [ 'name' ])) {
2020-04-13 23:54:28 +00:00
continue ;
}
2020-04-20 09:47:26 +00:00
2020-04-20 05:43:13 +00:00
Tag :: store ( $uriid , $type , $tag [ 'name' ], $tag [ 'href' ]);
2020-04-13 23:54:28 +00:00
}
}
2022-02-19 13:31:49 +00:00
public static function storeReceivers ( int $uriid , array $receivers )
{
foreach ([ 'as:to' => Tag :: TO , 'as:cc' => Tag :: CC , 'as:bto' => Tag :: BTO , 'as:bcc' => Tag :: BCC ] as $element => $type ) {
if ( ! empty ( $receivers [ $element ])) {
foreach ( $receivers [ $element ] as $receiver ) {
if ( $receiver == ActivityPub :: PUBLIC_COLLECTION ) {
$name = Receiver :: PUBLIC_COLLECTION ;
} else {
$name = trim ( parse_url ( $receiver , PHP_URL_PATH ), '/' );
}
2022-04-23 11:39:19 +00:00
$target = Tag :: getTargetType ( $receiver );
Logger :: debug ( 'Got target type' , [ 'type' => $target , 'url' => $receiver ]);
Tag :: store ( $uriid , $type , $name , $receiver , $target );
2022-02-19 13:31:49 +00:00
}
}
}
}
2019-05-16 05:44:59 +00:00
/**
* Creates an mail post
*
* @ param array $activity Activity data
* @ param array $item item array
2019-06-13 01:01:44 +00:00
* @ return int | bool New mail table row id or false on error
2019-05-16 05:44:59 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
2022-06-16 12:59:29 +00:00
private static function postMail ( array $activity , array $item )
2019-05-16 05:44:59 +00:00
{
if (( $item [ 'gravity' ] != GRAVITY_PARENT ) && ! DBA :: exists ( 'mail' , [ 'uri' => $item [ 'thr-parent' ], 'uid' => $item [ 'uid' ]])) {
Logger :: info ( 'Parent not found, mail will be discarded.' , [ 'uid' => $item [ 'uid' ], 'uri' => $item [ 'thr-parent' ]]);
return false ;
}
Logger :: info ( 'Direct Message' , $item );
$msg = [];
$msg [ 'uid' ] = $item [ 'uid' ];
$msg [ 'contact-id' ] = $item [ 'contact-id' ];
$contact = Contact :: getById ( $item [ 'contact-id' ], [ 'name' , 'url' , 'photo' ]);
$msg [ 'from-name' ] = $contact [ 'name' ];
$msg [ 'from-url' ] = $contact [ 'url' ];
$msg [ 'from-photo' ] = $contact [ 'photo' ];
$msg [ 'uri' ] = $item [ 'uri' ];
$msg [ 'created' ] = $item [ 'created' ];
$parent = DBA :: selectFirst ( 'mail' , [ 'parent-uri' , 'title' ], [ 'uri' => $item [ 'thr-parent' ]]);
if ( DBA :: isResult ( $parent )) {
$msg [ 'parent-uri' ] = $parent [ 'parent-uri' ];
$msg [ 'title' ] = $parent [ 'title' ];
} else {
$msg [ 'parent-uri' ] = $item [ 'thr-parent' ];
if ( ! empty ( $item [ 'title' ])) {
$msg [ 'title' ] = $item [ 'title' ];
} elseif ( ! empty ( $item [ 'content-warning' ])) {
$msg [ 'title' ] = $item [ 'content-warning' ];
} else {
// Trying to generate a title out of the body
$title = $item [ 'body' ];
while ( preg_match ( '#^(@\[url=([^\]]+)].*?\[\/url]\s)(.*)#is' , $title , $matches )) {
$title = $matches [ 3 ];
}
2021-07-05 07:00:35 +00:00
$title = trim ( BBCode :: toPlaintext ( $title ));
2019-05-16 05:44:59 +00:00
if ( strlen ( $title ) > 20 ) {
$title = substr ( $title , 0 , 20 ) . '...' ;
}
$msg [ 'title' ] = $title ;
}
}
$msg [ 'body' ] = $item [ 'body' ];
2019-06-13 01:01:44 +00:00
return Mail :: insert ( $msg );
2019-05-16 05:44:59 +00:00
}
2022-04-07 21:52:25 +00:00
/**
* Fetch featured posts from a contact with the given url
*
2022-05-30 20:52:43 +00:00
* @ param string $url
* @ return void
2022-04-07 21:52:25 +00:00
*/
public static function fetchFeaturedPosts ( string $url )
{
Logger :: info ( 'Fetch featured posts' , [ 'contact' => $url ]);
$apcontact = APContact :: getByURL ( $url );
if ( empty ( $apcontact [ 'featured' ])) {
Logger :: info ( 'Contact does not have a featured collection' , [ 'contact' => $url ]);
return ;
}
2022-04-08 04:49:23 +00:00
$pcid = Contact :: getIdForURL ( $url , 0 , false );
if ( empty ( $pcid )) {
Logger :: info ( 'Contact not found' , [ 'contact' => $url ]);
return ;
}
2022-04-08 06:04:50 +00:00
$posts = Post\Collection :: selectToArrayForContact ( $pcid , Post\Collection :: FEATURED );
2022-04-08 04:49:23 +00:00
if ( ! empty ( $posts )) {
$old_featured = array_column ( $posts , 'uri-id' );
2022-04-11 18:57:30 +00:00
} else {
$old_featured = [];
2022-04-08 04:49:23 +00:00
}
2022-04-07 21:52:25 +00:00
$featured = ActivityPub :: fetchItems ( $apcontact [ 'featured' ]);
if ( empty ( $featured )) {
Logger :: info ( 'Contact does not have featured posts' , [ 'contact' => $url ]);
2022-04-08 04:49:23 +00:00
foreach ( $old_featured as $uri_id ) {
Post\Collection :: remove ( $uri_id , Post\Collection :: FEATURED );
Logger :: debug ( 'Removed no longer featured post' , [ 'uri-id' => $uri_id , 'contact' => $url ]);
}
2022-04-07 21:52:25 +00:00
return ;
}
$new = 0 ;
$old = 0 ;
foreach ( $featured as $post ) {
if ( empty ( $post [ 'id' ])) {
continue ;
}
$id = Item :: fetchByLink ( $post [ 'id' ]);
if ( ! empty ( $id )) {
2022-08-15 13:23:01 +00:00
$item = Post :: selectFirst ([ 'uri-id' , 'featured' , 'author-id' ], [ 'id' => $id ]);
2022-04-07 21:52:25 +00:00
if ( ! empty ( $item [ 'uri-id' ])) {
if ( ! $item [ 'featured' ]) {
2022-08-15 13:23:01 +00:00
Post\Collection :: add ( $item [ 'uri-id' ], Post\Collection :: FEATURED , $item [ 'author-id' ]);
2022-04-07 21:52:25 +00:00
Logger :: debug ( 'Added featured post' , [ 'uri-id' => $item [ 'uri-id' ], 'contact' => $url ]);
$new ++ ;
} else {
Logger :: debug ( 'Post already had been featured' , [ 'uri-id' => $item [ 'uri-id' ], 'contact' => $url ]);
$old ++ ;
}
2022-04-08 04:49:23 +00:00
$index = array_search ( $item [ 'uri-id' ], $old_featured );
if ( ! ( $index === false )) {
unset ( $old_featured [ $index ]);
}
2022-04-07 21:52:25 +00:00
}
}
}
2022-04-08 04:49:23 +00:00
foreach ( $old_featured as $uri_id ) {
Post\Collection :: remove ( $uri_id , Post\Collection :: FEATURED );
Logger :: debug ( 'Removed no longer featured post' , [ 'uri-id' => $uri_id , 'contact' => $url ]);
}
2022-04-07 21:52:25 +00:00
Logger :: info ( 'Fetched featured posts' , [ 'new' => $new , 'old' => $old , 'contact' => $url ]);
}
2022-07-28 19:05:04 +00:00
public static function fetchCachedActivity ( string $url , int $uid ) : array
{
$cachekey = self :: CACHEKEY_FETCH_ACTIVITY . $uid . ':' . $url ;
$object = DI :: cache () -> get ( $cachekey );
if ( ! is_null ( $object )) {
2022-07-28 20:25:38 +00:00
if ( ! empty ( $object )) {
Logger :: debug ( 'Fetch from cache' , [ 'url' => $url , 'uid' => $uid ]);
} else {
Logger :: debug ( 'Fetch from negative cache' , [ 'url' => $url , 'uid' => $uid ]);
}
2022-07-28 19:05:04 +00:00
return $object ;
}
$object = ActivityPub :: fetchContent ( $url , $uid );
if ( empty ( $object )) {
Logger :: notice ( 'Activity was not fetchable, aborting.' , [ 'url' => $url , 'uid' => $uid ]);
2022-07-28 20:25:38 +00:00
// We perform negative caching.
DI :: cache () -> set ( $cachekey , [], Duration :: FIVE_MINUTES );
2022-07-28 19:05:04 +00:00
return [];
}
if ( empty ( $object [ 'id' ])) {
Logger :: notice ( 'Activity has got not id, aborting. ' , [ 'url' => $url , 'object' => $object ]);
return [];
}
DI :: cache () -> set ( $cachekey , $object , Duration :: FIVE_MINUTES );
Logger :: debug ( 'Activity was fetched successfully' , [ 'url' => $url , 'uid' => $uid ]);
return $object ;
}
2018-10-03 06:15:07 +00:00
/**
2018-10-07 18:41:45 +00:00
* Fetches missing posts
2018-10-03 06:15:07 +00:00
*
2022-06-25 03:48:49 +00:00
* @ param string $url message URL
* @ param array $child activity array with the child of this message
* @ param string $relay_actor Relay actor
* @ param int $completion Completion mode , see Receiver :: COMPLETION_ *
2020-01-20 22:30:34 +00:00
* @ return string fetched message URL
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
2022-06-25 03:48:49 +00:00
* @ throws \ImagickException
2018-10-03 06:15:07 +00:00
*/
2022-07-21 05:16:14 +00:00
public static function fetchMissingActivity ( string $url , array $child = [], string $relay_actor = '' , int $completion = Receiver :: COMPLETION_MANUAL ) : string
2018-10-03 06:15:07 +00:00
{
2022-08-07 19:24:50 +00:00
$object = self :: fetchCachedActivity ( $url , 0 );
2022-07-28 19:05:04 +00:00
if ( empty ( $object )) {
return '' ;
2018-12-29 09:53:31 +00:00
}
2022-07-24 09:26:52 +00:00
$signer = [];
if ( ! empty ( $object [ 'attributedTo' ])) {
$attributed_to = $object [ 'attributedTo' ];
if ( is_array ( $attributed_to )) {
2020-11-07 08:22:59 +00:00
$compacted = JsonLD :: compact ( $object );
2022-07-24 09:26:52 +00:00
$attributed_to = JsonLD :: fetchElement ( $compacted , 'as:attributedTo' , '@id' );
2020-11-07 08:22:59 +00:00
}
2022-08-07 19:24:50 +00:00
$signer [] = $attributed_to ;
2022-07-24 09:26:52 +00:00
}
if ( ! empty ( $object [ 'actor' ])) {
$object_actor = $object [ 'actor' ];
} elseif ( ! empty ( $attributed_to )) {
$object_actor = $attributed_to ;
2019-07-17 19:36:32 +00:00
} else {
// Shouldn't happen
2020-09-14 20:58:41 +00:00
$object_actor = '' ;
}
2022-07-24 09:26:52 +00:00
$signer [] = $object_actor ;
2020-09-14 20:58:41 +00:00
if ( ! empty ( $child [ 'author' ])) {
$actor = $child [ 'author' ];
$signer [] = $actor ;
} else {
$actor = $object_actor ;
2019-07-17 19:36:32 +00:00
}
if ( ! empty ( $object [ 'published' ])) {
$published = $object [ 'published' ];
} elseif ( ! empty ( $child [ 'published' ])) {
$published = $child [ 'published' ];
} else {
$published = DateTimeFormat :: utcNow ();
}
2018-10-03 06:15:07 +00:00
$activity = [];
2020-12-17 18:08:07 +00:00
$activity [ '@context' ] = $object [ '@context' ] ? ? ActivityPub :: CONTEXT ;
2018-10-03 06:15:07 +00:00
unset ( $object [ '@context' ]);
$activity [ 'id' ] = $object [ 'id' ];
2019-10-16 12:35:14 +00:00
$activity [ 'to' ] = $object [ 'to' ] ? ? [];
$activity [ 'cc' ] = $object [ 'cc' ] ? ? [];
2019-07-17 19:36:32 +00:00
$activity [ 'actor' ] = $actor ;
2018-10-03 06:15:07 +00:00
$activity [ 'object' ] = $object ;
2019-07-17 19:36:32 +00:00
$activity [ 'published' ] = $published ;
2018-10-03 06:15:07 +00:00
$activity [ 'type' ] = 'Create' ;
2018-10-07 15:34:51 +00:00
$ldactivity = JsonLD :: compact ( $activity );
2018-10-09 05:04:24 +00:00
2022-08-06 17:06:55 +00:00
$ldactivity [ 'recursion-depth' ] = ! empty ( $child [ 'recursion-depth' ]) ? $child [ 'recursion-depth' ] + 1 : 0 ;
2022-07-21 05:16:14 +00:00
2020-09-25 12:16:08 +00:00
if ( ! empty ( $relay_actor )) {
$ldactivity [ 'thread-completion' ] = $ldactivity [ 'from-relay' ] = Contact :: getIdForURL ( $relay_actor );
2022-03-12 11:17:33 +00:00
$ldactivity [ 'completion-mode' ] = Receiver :: COMPLETION_RELAY ;
2020-09-25 12:16:08 +00:00
} elseif ( ! empty ( $child [ 'thread-completion' ])) {
$ldactivity [ 'thread-completion' ] = $child [ 'thread-completion' ];
2022-03-12 11:17:33 +00:00
$ldactivity [ 'completion-mode' ] = $child [ 'completion-mode' ] ? ? Receiver :: COMPLETION_NONE ;
2020-09-25 12:16:08 +00:00
} else {
$ldactivity [ 'thread-completion' ] = Contact :: getIdForURL ( $actor );
2022-03-12 11:17:33 +00:00
$ldactivity [ 'completion-mode' ] = $completion ;
2020-09-25 12:16:08 +00:00
}
2018-10-09 05:04:24 +00:00
2022-08-07 19:24:50 +00:00
if ( ! empty ( $child [ 'thread-children-type' ])) {
$ldactivity [ 'thread-children-type' ] = $child [ 'thread-children-type' ];
} elseif ( ! empty ( $child [ 'type' ])) {
2022-02-12 13:05:56 +00:00
$ldactivity [ 'thread-children-type' ] = $child [ 'type' ];
2022-08-07 19:24:50 +00:00
} else {
$ldactivity [ 'thread-children-type' ] = 'as:Create' ;
2022-02-12 13:05:56 +00:00
}
2020-09-22 15:48:44 +00:00
if ( ! empty ( $relay_actor ) && ! self :: acceptIncomingMessage ( $ldactivity , $object [ 'id' ])) {
return '' ;
}
2022-08-03 03:38:03 +00:00
if (( $completion == Receiver :: COMPLETION_RELAY ) && Queue :: exists ( $url , 'as:Create' )) {
Logger :: notice ( 'Activity has already been queued.' , [ 'url' => $url , 'object' => $activity [ 'id' ]]);
2022-08-07 19:24:50 +00:00
} elseif ( ActivityPub\Receiver :: processActivity ( $ldactivity , json_encode ( $activity ), 0 , true , false , $signer , '' , $completion )) {
2022-08-03 03:38:03 +00:00
Logger :: notice ( 'Activity had been fetched and processed.' , [ 'url' => $url , 'entry' => $child [ 'entry-id' ] ? ? 0 , 'completion' => $completion , 'object' => $activity [ 'id' ]]);
} else {
Logger :: notice ( 'Activity had been fetched and will be processed later.' , [ 'url' => $url , 'entry' => $child [ 'entry-id' ] ? ? 0 , 'completion' => $completion , 'object' => $activity [ 'id' ]]);
}
2019-07-21 07:37:50 +00:00
2020-01-20 22:30:34 +00:00
return $activity [ 'id' ];
2018-10-03 06:15:07 +00:00
}
2020-09-22 15:48:44 +00:00
/**
* Test if incoming relay messages should be accepted
*
* @ param array $activity activity array
* @ param string $id object ID
* @ return boolean true if message is accepted
*/
2022-06-16 12:59:29 +00:00
private static function acceptIncomingMessage ( array $activity , string $id ) : bool
2020-09-22 15:48:44 +00:00
{
if ( empty ( $activity [ 'as:object' ])) {
Logger :: info ( 'No object field in activity - accepted' , [ 'id' => $id ]);
return true ;
}
$replyto = JsonLD :: fetchElement ( $activity [ 'as:object' ], 'as:inReplyTo' , '@id' );
2022-06-18 16:30:03 +00:00
$uriid = ItemURI :: getIdByURI ( $replyto ? ? '' );
2021-02-13 19:56:03 +00:00
if ( Post :: exists ([ 'uri-id' => $uriid ])) {
Logger :: info ( 'Post is a reply to an existing post - accepted' , [ 'id' => $id , 'uri-id' => $uriid , 'replyto' => $replyto ]);
2020-09-22 15:48:44 +00:00
return true ;
}
2020-10-02 03:35:22 +00:00
$attributed_to = JsonLD :: fetchElement ( $activity [ 'as:object' ], 'as:attributedTo' , '@id' );
$authorid = Contact :: getIdForURL ( $attributed_to );
2022-06-16 15:36:32 +00:00
$body = HTML :: toBBCode ( JsonLD :: fetchElement ( $activity [ 'as:object' ], 'as:content' , '@value' ) ? ? '' );
2020-09-22 15:48:44 +00:00
$messageTags = [];
$tags = Receiver :: processTags ( JsonLD :: fetchElementArray ( $activity [ 'as:object' ], 'as:tag' ) ? ? []);
if ( ! empty ( $tags )) {
foreach ( $tags as $tag ) {
if ( $tag [ 'type' ] != 'Hashtag' ) {
continue ;
}
$messageTags [] = ltrim ( mb_strtolower ( $tag [ 'name' ]), '#' );
}
}
2020-10-02 03:35:22 +00:00
return Relay :: isSolicitedPost ( $messageTags , $body , $authorid , $id , Protocol :: ACTIVITYPUB );
2020-09-22 15:48:44 +00:00
}
2018-10-03 06:15:07 +00:00
/**
2018-10-06 04:18:40 +00:00
* perform a " follow " request
2018-10-03 06:15:07 +00:00
*
* @ param array $activity
2022-06-20 19:02:18 +00:00
* @ return void
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
* @ throws \ImagickException
2018-10-03 06:15:07 +00:00
*/
2022-06-16 12:59:29 +00:00
public static function followUser ( array $activity )
2018-10-03 06:15:07 +00:00
{
2018-10-07 19:42:04 +00:00
$uid = User :: getIdForURL ( $activity [ 'object_id' ]);
2018-10-03 06:15:07 +00:00
if ( empty ( $uid )) {
2022-07-24 21:58:09 +00:00
Queue :: remove ( $activity );
2018-10-03 06:15:07 +00:00
return ;
}
$owner = User :: getOwnerDataById ( $uid );
2020-08-22 16:34:04 +00:00
if ( empty ( $owner )) {
return ;
}
2018-10-03 06:15:07 +00:00
2018-10-07 17:35:43 +00:00
$cid = Contact :: getIdForURL ( $activity [ 'actor' ], $uid );
2018-10-03 06:15:07 +00:00
if ( ! empty ( $cid )) {
2018-10-09 19:58:15 +00:00
self :: switchContact ( $cid );
2021-09-10 18:21:19 +00:00
Contact :: update ([ 'hub-verify' => $activity [ 'id' ], 'protocol' => Protocol :: ACTIVITYPUB ], [ 'id' => $cid ]);
2018-10-03 06:15:07 +00:00
}
2022-06-23 14:46:44 +00:00
$item = [
'author-id' => Contact :: getIdForURL ( $activity [ 'actor' ]),
'author-link' => $activity [ 'actor' ],
];
2018-10-03 06:15:07 +00:00
2018-10-13 18:13:01 +00:00
// Ensure that the contact has got the right network type
self :: switchContact ( $item [ 'author-id' ]);
2020-12-13 17:16:04 +00:00
$result = Contact :: addRelationship ( $owner , [], $item , false , $activity [ 'content' ] ? ? '' );
2019-05-20 20:33:09 +00:00
if ( $result === true ) {
2020-07-10 05:30:12 +00:00
ActivityPub\Transmitter :: sendContactAccept ( $item [ 'author-link' ], $activity [ 'id' ], $owner [ 'uid' ]);
2019-05-19 22:46:29 +00:00
}
2018-10-07 17:35:43 +00:00
$cid = Contact :: getIdForURL ( $activity [ 'actor' ], $uid );
2018-10-03 06:15:07 +00:00
if ( empty ( $cid )) {
return ;
}
2022-05-30 20:52:43 +00:00
if ( $result && DI :: config () -> get ( 'system' , 'transmit_pending_events' ) && ( $owner [ 'contact-type' ] == Contact :: TYPE_COMMUNITY )) {
self :: transmitPendingEvents ( $cid , $owner [ 'uid' ]);
}
2019-01-09 22:30:26 +00:00
if ( empty ( $contact )) {
2021-09-10 18:21:19 +00:00
Contact :: update ([ 'hub-verify' => $activity [ 'id' ], 'protocol' => Protocol :: ACTIVITYPUB ], [ 'id' => $cid ]);
2019-01-09 22:30:26 +00:00
}
2021-04-26 06:50:12 +00:00
Logger :: notice ( 'Follow user ' . $uid . ' from contact ' . $cid . ' with id ' . $activity [ 'id' ]);
2022-07-23 06:52:43 +00:00
Queue :: remove ( $activity );
2018-10-03 06:15:07 +00:00
}
2022-05-30 20:52:43 +00:00
/**
* Transmit pending events to the new follower
*
2022-06-20 19:21:32 +00:00
* @ param integer $cid Contact id
* @ param integer $uid User id
2022-05-30 20:52:43 +00:00
* @ return void
*/
private static function transmitPendingEvents ( int $cid , int $uid )
{
$account = DBA :: selectFirst ( 'account-user-view' , [ 'ap-inbox' , 'ap-sharedinbox' ], [ 'id' => $cid ]);
$inbox = $account [ 'ap-sharedinbox' ] ? : $account [ 'ap-inbox' ];
$events = DBA :: select ( 'event' , [ 'id' ], [ " `uid` = ? AND `start` > ? AND `type` != ? " , $uid , DateTimeFormat :: utcNow (), 'birthday' ]);
while ( $event = DBA :: fetch ( $events )) {
$post = Post :: selectFirst ([ 'id' , 'uri-id' , 'created' ], [ 'event-id' => $event [ 'id' ]]);
if ( empty ( $post )) {
continue ;
}
if ( DI :: config () -> get ( 'system' , 'bulk_delivery' )) {
Post\Delivery :: add ( $post [ 'uri-id' ], $uid , $inbox , $post [ 'created' ], Delivery :: POST , [ $cid ]);
Worker :: add ( PRIORITY_HIGH , 'APDelivery' , '' , 0 , $inbox , 0 );
} else {
Worker :: add ( PRIORITY_HIGH , 'APDelivery' , Delivery :: POST , $post [ 'id' ], $inbox , $uid , [ $cid ], $post [ 'uri-id' ]);
}
}
}
2018-10-03 06:15:07 +00:00
/**
2018-10-06 04:18:40 +00:00
* Update the given profile
2018-10-03 06:15:07 +00:00
*
* @ param array $activity
2019-01-06 21:06:53 +00:00
* @ throws \Exception
2018-10-03 06:15:07 +00:00
*/
2022-06-16 12:59:29 +00:00
public static function updatePerson ( array $activity )
2018-10-03 06:15:07 +00:00
{
2018-10-07 18:41:45 +00:00
if ( empty ( $activity [ 'object_id' ])) {
2018-10-03 06:15:07 +00:00
return ;
}
2020-06-28 17:50:11 +00:00
Logger :: info ( 'Updating profile' , [ 'object' => $activity [ 'object_id' ]]);
2020-08-06 18:53:45 +00:00
Contact :: updateFromProbeByURL ( $activity [ 'object_id' ]);
2022-07-21 05:16:14 +00:00
Queue :: remove ( $activity );
2018-10-03 06:15:07 +00:00
}
/**
2018-10-06 04:18:40 +00:00
* Delete the given profile
2018-10-03 06:15:07 +00:00
*
* @ param array $activity
2022-06-20 19:21:32 +00:00
* @ return void
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
2018-10-03 06:15:07 +00:00
*/
2022-06-16 12:59:29 +00:00
public static function deletePerson ( array $activity )
2018-10-03 06:15:07 +00:00
{
2018-10-07 19:42:04 +00:00
if ( empty ( $activity [ 'object_id' ]) || empty ( $activity [ 'actor' ])) {
2020-06-28 17:50:11 +00:00
Logger :: info ( 'Empty object id or actor.' );
2018-10-03 06:15:07 +00:00
return ;
}
2018-10-07 19:42:04 +00:00
if ( $activity [ 'object_id' ] != $activity [ 'actor' ]) {
2020-06-28 17:50:11 +00:00
Logger :: info ( 'Object id does not match actor.' );
2018-10-03 06:15:07 +00:00
return ;
}
2018-11-08 16:28:29 +00:00
$contacts = DBA :: select ( 'contact' , [ 'id' ], [ 'nurl' => Strings :: normaliseLink ( $activity [ 'object_id' ])]);
2018-10-03 06:15:07 +00:00
while ( $contact = DBA :: fetch ( $contacts )) {
2018-10-07 15:34:51 +00:00
Contact :: remove ( $contact [ 'id' ]);
2018-10-03 06:15:07 +00:00
}
DBA :: close ( $contacts );
2020-06-28 17:50:11 +00:00
Logger :: info ( 'Deleted contact' , [ 'object' => $activity [ 'object_id' ]]);
2022-07-21 05:16:14 +00:00
Queue :: remove ( $activity );
2018-10-03 06:15:07 +00:00
}
2022-04-05 19:14:29 +00:00
/**
* Blocks the user by the contact
*
* @ param array $activity
2022-06-20 19:21:32 +00:00
* @ return void
2022-04-05 19:14:29 +00:00
* @ throws \Exception
*/
2022-06-16 12:59:29 +00:00
public static function blockAccount ( array $activity )
2022-04-05 19:14:29 +00:00
{
$cid = Contact :: getIdForURL ( $activity [ 'actor' ]);
if ( empty ( $cid )) {
return ;
}
$uid = User :: getIdForURL ( $activity [ 'object_id' ]);
if ( empty ( $uid )) {
2022-05-30 20:52:43 +00:00
return ;
2022-04-05 19:14:29 +00:00
}
Contact\User :: setIsBlocked ( $cid , $uid , true );
Logger :: info ( 'Contact blocked user' , [ 'contact' => $cid , 'user' => $uid ]);
2022-07-23 06:52:43 +00:00
Queue :: remove ( $activity );
2022-04-05 19:14:29 +00:00
}
/**
* Unblocks the user by the contact
*
* @ param array $activity
2022-06-20 19:21:32 +00:00
* @ return void
2022-04-05 19:14:29 +00:00
* @ throws \Exception
*/
2022-06-16 12:59:29 +00:00
public static function unblockAccount ( array $activity )
2022-04-05 19:14:29 +00:00
{
$cid = Contact :: getIdForURL ( $activity [ 'actor' ]);
if ( empty ( $cid )) {
return ;
}
$uid = User :: getIdForURL ( $activity [ 'object_object' ]);
if ( empty ( $uid )) {
2022-05-30 20:52:43 +00:00
return ;
2022-04-05 19:14:29 +00:00
}
Contact\User :: setIsBlocked ( $cid , $uid , false );
Logger :: info ( 'Contact unblocked user' , [ 'contact' => $cid , 'user' => $uid ]);
2022-07-23 06:52:43 +00:00
Queue :: remove ( $activity );
2022-04-05 19:14:29 +00:00
}
2018-10-03 06:15:07 +00:00
/**
2018-10-06 04:18:40 +00:00
* Accept a follow request
2018-10-03 06:15:07 +00:00
*
* @ param array $activity
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
* @ throws \ImagickException
2018-10-03 06:15:07 +00:00
*/
2022-06-16 12:59:29 +00:00
public static function acceptFollowUser ( array $activity )
2018-10-03 06:15:07 +00:00
{
2018-10-07 18:41:45 +00:00
$uid = User :: getIdForURL ( $activity [ 'object_actor' ]);
2018-10-03 06:15:07 +00:00
if ( empty ( $uid )) {
return ;
}
2018-10-07 17:35:43 +00:00
$cid = Contact :: getIdForURL ( $activity [ 'actor' ], $uid );
2018-10-03 06:15:07 +00:00
if ( empty ( $cid )) {
2020-06-28 17:50:11 +00:00
Logger :: info ( 'No contact found' , [ 'actor' => $activity [ 'actor' ]]);
2018-10-03 06:15:07 +00:00
return ;
}
2018-10-09 19:58:15 +00:00
self :: switchContact ( $cid );
2018-10-03 06:15:07 +00:00
$fields = [ 'pending' => false ];
$contact = DBA :: selectFirst ( 'contact' , [ 'rel' ], [ 'id' => $cid ]);
if ( $contact [ 'rel' ] == Contact :: FOLLOWER ) {
$fields [ 'rel' ] = Contact :: FRIEND ;
}
$condition = [ 'id' => $cid ];
2021-09-10 18:21:19 +00:00
Contact :: update ( $fields , $condition );
2020-06-28 17:50:11 +00:00
Logger :: info ( 'Accept contact request' , [ 'contact' => $cid , 'user' => $uid ]);
2022-07-21 05:16:14 +00:00
Queue :: remove ( $activity );
2018-10-03 06:15:07 +00:00
}
/**
2018-10-06 04:18:40 +00:00
* Reject a follow request
2018-10-03 06:15:07 +00:00
*
* @ param array $activity
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
* @ throws \ImagickException
2018-10-03 06:15:07 +00:00
*/
2022-06-16 12:59:29 +00:00
public static function rejectFollowUser ( array $activity )
2018-10-03 06:15:07 +00:00
{
2018-10-07 18:41:45 +00:00
$uid = User :: getIdForURL ( $activity [ 'object_actor' ]);
2018-10-03 06:15:07 +00:00
if ( empty ( $uid )) {
return ;
}
2018-10-07 17:35:43 +00:00
$cid = Contact :: getIdForURL ( $activity [ 'actor' ], $uid );
2018-10-03 06:15:07 +00:00
if ( empty ( $cid )) {
2020-06-28 17:50:11 +00:00
Logger :: info ( 'No contact found' , [ 'actor' => $activity [ 'actor' ]]);
2018-10-03 06:15:07 +00:00
return ;
}
2018-10-09 19:58:15 +00:00
self :: switchContact ( $cid );
2021-10-02 15:19:41 +00:00
$contact = Contact :: getById ( $cid , [ 'rel' ]);
if ( $contact [ 'rel' ] == Contact :: SHARING ) {
2018-10-03 06:15:07 +00:00
Contact :: remove ( $cid );
2020-06-28 17:50:11 +00:00
Logger :: info ( 'Rejected contact request - contact removed' , [ 'contact' => $cid , 'user' => $uid ]);
2021-10-02 15:19:41 +00:00
} elseif ( $contact [ 'rel' ] == Contact :: FRIEND ) {
Contact :: update ([ 'rel' => Contact :: FOLLOWER ], [ 'id' => $cid ]);
2018-10-03 06:15:07 +00:00
} else {
2020-06-28 17:50:11 +00:00
Logger :: info ( 'Rejected contact request' , [ 'contact' => $cid , 'user' => $uid ]);
2018-10-03 06:15:07 +00:00
}
2022-07-21 05:16:14 +00:00
Queue :: remove ( $activity );
2018-10-03 06:15:07 +00:00
}
/**
2018-10-06 04:18:40 +00:00
* Undo activity like " like " or " dislike "
2018-10-03 06:15:07 +00:00
*
* @ param array $activity
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
* @ throws \ImagickException
2018-10-03 06:15:07 +00:00
*/
2022-06-16 12:59:29 +00:00
public static function undoActivity ( array $activity )
2018-10-03 06:15:07 +00:00
{
2018-10-07 18:41:45 +00:00
if ( empty ( $activity [ 'object_id' ])) {
2018-10-03 06:15:07 +00:00
return ;
}
2018-10-07 18:41:45 +00:00
if ( empty ( $activity [ 'object_actor' ])) {
2018-10-03 06:15:07 +00:00
return ;
}
2018-10-07 18:41:45 +00:00
$author_id = Contact :: getIdForURL ( $activity [ 'object_actor' ]);
2018-10-03 06:15:07 +00:00
if ( empty ( $author_id )) {
return ;
}
2020-03-03 06:47:28 +00:00
Item :: markForDeletion ([ 'uri' => $activity [ 'object_id' ], 'author-id' => $author_id , 'gravity' => GRAVITY_ACTIVITY ]);
2022-07-21 05:16:14 +00:00
Queue :: remove ( $activity );
2018-10-03 06:15:07 +00:00
}
/**
2018-10-06 04:18:40 +00:00
* Activity to remove a follower
2018-10-03 06:15:07 +00:00
*
* @ param array $activity
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
* @ throws \ImagickException
2018-10-03 06:15:07 +00:00
*/
2022-06-16 12:59:29 +00:00
public static function undoFollowUser ( array $activity )
2018-10-03 06:15:07 +00:00
{
2018-10-07 18:41:45 +00:00
$uid = User :: getIdForURL ( $activity [ 'object_object' ]);
2018-10-03 06:15:07 +00:00
if ( empty ( $uid )) {
return ;
}
$owner = User :: getOwnerDataById ( $uid );
2020-08-22 16:34:04 +00:00
if ( empty ( $owner )) {
return ;
}
2018-10-03 06:15:07 +00:00
2018-10-07 17:35:43 +00:00
$cid = Contact :: getIdForURL ( $activity [ 'actor' ], $uid );
2018-10-03 06:15:07 +00:00
if ( empty ( $cid )) {
2020-06-28 17:50:11 +00:00
Logger :: info ( 'No contact found' , [ 'actor' => $activity [ 'actor' ]]);
2018-10-03 06:15:07 +00:00
return ;
}
2018-10-09 19:58:15 +00:00
self :: switchContact ( $cid );
2018-10-03 06:15:07 +00:00
$contact = DBA :: selectFirst ( 'contact' , [], [ 'id' => $cid ]);
if ( ! DBA :: isResult ( $contact )) {
return ;
}
2021-09-13 18:22:55 +00:00
Contact :: removeFollower ( $contact );
2020-06-28 17:50:11 +00:00
Logger :: info ( 'Undo following request' , [ 'contact' => $cid , 'user' => $uid ]);
2022-07-21 05:16:14 +00:00
Queue :: remove ( $activity );
2018-10-03 06:15:07 +00:00
}
2018-10-09 19:58:15 +00:00
/**
* Switches a contact to AP if needed
*
* @ param integer $cid Contact ID
2022-06-24 02:42:35 +00:00
* @ return void
2019-01-06 21:06:53 +00:00
* @ throws \Exception
2018-10-09 19:58:15 +00:00
*/
2022-06-16 12:59:29 +00:00
private static function switchContact ( int $cid )
2018-10-09 19:58:15 +00:00
{
2020-01-16 06:43:21 +00:00
$contact = DBA :: selectFirst ( 'contact' , [ 'network' , 'url' ], [ 'id' => $cid ]);
if ( ! DBA :: isResult ( $contact ) || in_array ( $contact [ 'network' ], [ Protocol :: ACTIVITYPUB , Protocol :: DFRN ]) || Contact :: isLocal ( $contact [ 'url' ])) {
2018-10-09 19:58:15 +00:00
return ;
}
2020-01-16 06:43:21 +00:00
Logger :: info ( 'Change existing contact' , [ 'cid' => $cid , 'previous' => $contact [ 'network' ]]);
Contact :: updateFromProbe ( $cid );
2018-10-09 19:58:15 +00:00
}
2019-02-09 03:57:35 +00:00
/**
* Collects implicit mentions like :
* - the author of the parent item
* - all the mentioned conversants in the parent item
*
* @ param array $parent Item array with at least [ 'id' , 'author-link' , 'alias' ]
* @ return array
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
2022-06-16 12:59:29 +00:00
private static function getImplicitMentionList ( array $parent ) : array
2019-02-09 03:57:35 +00:00
{
2020-05-01 06:01:22 +00:00
$parent_terms = Tag :: getByURIId ( $parent [ 'uri-id' ], [ Tag :: MENTION , Tag :: IMPLICIT_MENTION , Tag :: EXCLUSIVE_MENTION ]);
2019-02-23 04:42:04 +00:00
2020-07-15 17:06:48 +00:00
$parent_author = Contact :: getByURL ( $parent [ 'author-link' ], false , [ 'url' , 'nurl' , 'alias' ]);
2019-02-09 03:57:35 +00:00
2019-02-24 20:48:56 +00:00
$implicit_mentions = [];
2020-06-16 20:41:34 +00:00
if ( empty ( $parent_author [ 'url' ])) {
2021-02-19 06:30:38 +00:00
Logger :: notice ( 'Author public contact unknown.' , [ 'author-link' => $parent [ 'author-link' ], 'parent-id' => $parent [ 'id' ]]);
2019-02-25 08:16:18 +00:00
} else {
2019-02-24 20:48:56 +00:00
$implicit_mentions [] = $parent_author [ 'url' ];
$implicit_mentions [] = $parent_author [ 'nurl' ];
$implicit_mentions [] = $parent_author [ 'alias' ];
}
2019-02-09 03:57:35 +00:00
2019-02-24 20:48:56 +00:00
if ( ! empty ( $parent [ 'alias' ])) {
2019-02-09 03:57:35 +00:00
$implicit_mentions [] = $parent [ 'alias' ];
}
foreach ( $parent_terms as $term ) {
2020-07-15 17:06:48 +00:00
$contact = Contact :: getByURL ( $term [ 'url' ], false , [ 'url' , 'nurl' , 'alias' ]);
2020-06-16 20:41:34 +00:00
if ( ! empty ( $contact [ 'url' ])) {
2019-02-10 18:42:51 +00:00
$implicit_mentions [] = $contact [ 'url' ];
$implicit_mentions [] = $contact [ 'nurl' ];
$implicit_mentions [] = $contact [ 'alias' ];
}
2019-02-09 03:57:35 +00:00
}
return $implicit_mentions ;
}
/**
* Strips from the body prepended implicit mentions
*
* @ param string $body
2020-05-09 08:55:10 +00:00
* @ param array $parent
2019-02-09 03:57:35 +00:00
* @ return string
*/
2022-06-16 12:59:29 +00:00
private static function removeImplicitMentionsFromBody ( string $body , array $parent ) : string
2019-02-09 03:57:35 +00:00
{
2020-01-19 20:21:13 +00:00
if ( DI :: config () -> get ( 'system' , 'disable_implicit_mentions' )) {
2019-02-13 17:26:54 +00:00
return $body ;
2019-02-13 17:23:23 +00:00
}
2020-05-09 08:55:10 +00:00
$potential_mentions = self :: getImplicitMentionList ( $parent );
2019-02-09 03:57:35 +00:00
$kept_mentions = [];
// Extract one prepended mention at a time from the body
2019-03-09 14:04:11 +00:00
while ( preg_match ( '#^(@\[url=([^\]]+)].*?\[\/url]\s)(.*)#is' , $body , $matches )) {
2019-10-16 12:58:09 +00:00
if ( ! in_array ( $matches [ 2 ], $potential_mentions )) {
2019-02-09 03:57:35 +00:00
$kept_mentions [] = $matches [ 1 ];
}
$body = $matches [ 3 ];
}
// Re-appending the kept mentions to the body after extraction
$kept_mentions [] = $body ;
return implode ( '' , $kept_mentions );
}
2021-08-16 02:53:42 +00:00
/**
* Adds links to string mentions
*
* @ param string $body
* @ param array $tags
* @ return string
*/
protected static function addMentionLinks ( string $body , array $tags ) : string
{
// This prevents links to be added again to Pleroma-style mention links
$body = self :: normalizeMentionLinks ( $body );
2021-08-20 07:40:23 +00:00
$body = BBCode :: performWithEscapedTags ( $body , [ 'url' ], function ( $body ) use ( $tags ) {
foreach ( $tags as $tag ) {
if ( empty ( $tag [ 'name' ]) || empty ( $tag [ 'type' ]) || empty ( $tag [ 'href' ]) || ! in_array ( $tag [ 'type' ], [ 'Mention' , 'Hashtag' ])) {
continue ;
}
2021-08-16 02:53:42 +00:00
2021-08-20 07:40:23 +00:00
$hash = substr ( $tag [ 'name' ], 0 , 1 );
$name = substr ( $tag [ 'name' ], 1 );
if ( ! in_array ( $hash , Tag :: TAG_CHARACTER )) {
$hash = '' ;
$name = $tag [ 'name' ];
}
$body = str_replace ( $tag [ 'name' ], $hash . '[url=' . $tag [ 'href' ] . ']' . $name . '[/url]' , $body );
2021-08-16 02:53:42 +00:00
}
2021-08-20 07:40:23 +00:00
return $body ;
});
2021-08-16 02:53:42 +00:00
return $body ;
}
2018-10-03 06:15:07 +00:00
}