2018-10-03 02:15:07 -04:00
< ? php
/**
2020-02-09 10:18:46 -05:00
* @ copyright Copyright ( C ) 2020 , Friendica
*
* @ 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 02:15:07 -04:00
*/
2020-02-09 10:18:46 -05:00
2018-10-03 02:15:07 -04:00
namespace Friendica\Protocol\ActivityPub ;
2020-07-17 19:39:12 -04:00
use Friendica\Content\PageInfo ;
2019-05-16 01:44:59 -04:00
use Friendica\Content\Text\BBCode ;
2018-11-08 11:28:29 -05:00
use Friendica\Content\Text\HTML ;
2018-10-29 17:20:46 -04:00
use Friendica\Core\Logger ;
2018-10-03 02:15:07 -04:00
use Friendica\Core\Protocol ;
2019-10-24 18:10:20 -04:00
use Friendica\Database\DBA ;
2020-01-18 10:50:57 -05:00
use Friendica\DI ;
2018-10-03 02:15:07 -04:00
use Friendica\Model\APContact ;
2019-10-24 18:10:20 -04:00
use Friendica\Model\Contact ;
2020-02-28 04:46:53 -05:00
use Friendica\Model\Conversation ;
2018-10-26 00:13:26 -04:00
use Friendica\Model\Event ;
2019-10-24 18:10:20 -04:00
use Friendica\Model\Item ;
2020-04-13 19:54:28 -04:00
use Friendica\Model\ItemURI ;
2019-10-24 18:10:20 -04:00
use Friendica\Model\Mail ;
2020-09-22 11:48:44 -04:00
use Friendica\Model\Search ;
2020-04-17 02:35:20 -04:00
use Friendica\Model\Tag ;
2018-10-03 02:15:07 -04:00
use Friendica\Model\User ;
2019-10-23 18:25:43 -04:00
use Friendica\Protocol\Activity ;
2018-10-03 02:15:07 -04:00
use Friendica\Protocol\ActivityPub ;
2018-10-27 02:17:17 -04:00
use Friendica\Util\DateTimeFormat ;
2018-11-08 11:28:29 -05:00
use Friendica\Util\JsonLD ;
use Friendica\Util\Strings ;
2020-09-23 05:00:09 -04:00
use Text_LanguageDetect ;
2018-10-03 02:15:07 -04:00
/**
2018-10-24 00:51:37 -04:00
* ActivityPub Processor Protocol class
2018-10-03 02:15:07 -04:00
*/
class Processor
{
/**
2018-10-06 00:18:40 -04:00
* Converts mentions from Pleroma into the Friendica format
2018-10-03 02:15:07 -04:00
*
* @ param string $body
*
2019-01-06 16:06:53 -05:00
* @ return string converted body
2018-10-03 02:15:07 -04:00
*/
private static function convertMentions ( $body )
{
$URLSearchString = " ^ \ [ \ ] " ;
$body = preg_replace ( " / \ [url \ =([ $URLSearchString ]*) \ ]([#@!])(.*?) \ [ \ /url \ ]/ism " , '$2[url=$1]$3[/url]' , $body );
return $body ;
}
2018-11-07 15:34:03 -05:00
/**
* Replaces emojis in the body
*
* @ param array $emojis
* @ param string $body
*
* @ return string with replaced emojis
*/
2019-03-17 18:13:17 -04:00
private static function replaceEmojis ( $body , array $emojis )
2018-11-07 15:34:03 -05:00
{
foreach ( $emojis as $emoji ) {
2018-11-08 02:42:19 -05:00
$replace = '[class=emoji mastodon][img=' . $emoji [ 'href' ] . ']' . $emoji [ 'name' ] . '[/img][/class]' ;
$body = str_replace ( $emoji [ 'name' ], $replace , $body );
2018-11-07 15:34:03 -05:00
}
return $body ;
}
2018-10-03 02:15:07 -04:00
/**
2018-10-07 14:41:45 -04:00
* Add attachment data to the item array
2018-10-03 02:15:07 -04:00
*
2019-11-28 01:34:35 -05:00
* @ param array $activity
2019-05-13 15:56:46 -04:00
* @ param array $item
2018-10-03 02:15:07 -04:00
*
2019-01-06 16:06:53 -05:00
* @ return array array
2018-10-03 02:15:07 -04:00
*/
2019-11-28 01:34:35 -05:00
private static function constructAttachList ( $activity , $item )
2018-10-03 02:15:07 -04:00
{
2019-11-28 01:34:35 -05:00
if ( empty ( $activity [ 'attachments' ])) {
2018-10-03 02:15:07 -04:00
return $item ;
}
2019-11-28 01:34:35 -05:00
foreach ( $activity [ 'attachments' ] as $attach ) {
2020-06-04 15:51:14 -04:00
switch ( $attach [ 'type' ]) {
case 'link' :
2020-07-17 19:39:12 -04:00
$data = [
'url' => $attach [ 'url' ],
'type' => $attach [ 'type' ],
'title' => $attach [ 'title' ] ? ? '' ,
'text' => $attach [ 'desc' ] ? ? '' ,
'image' => $attach [ 'image' ] ? ? '' ,
'images' => [],
'keywords' => [],
];
$item [ 'body' ] = PageInfo :: appendDataToBody ( $item [ 'body' ], $data );
2020-06-04 15:51:14 -04:00
break ;
default :
$filetype = strtolower ( substr ( $attach [ 'mediaType' ], 0 , strpos ( $attach [ 'mediaType' ], '/' )));
if ( $filetype == 'image' ) {
if ( ! empty ( $activity [ 'source' ]) && strpos ( $activity [ 'source' ], $attach [ 'url' ])) {
2020-06-09 12:40:36 -04:00
continue 2 ;
2020-06-04 15:51:14 -04:00
}
2019-05-13 15:56:46 -04:00
2020-07-20 00:27:36 -04:00
$item [ 'body' ] .= " \n " ;
// image is the preview/thumbnail URL
if ( ! empty ( $attach [ 'image' ])) {
$item [ 'body' ] .= '[url=' . $attach [ 'url' ] . ']' ;
$attach [ 'url' ] = $attach [ 'image' ];
}
2020-06-04 15:51:14 -04:00
if ( empty ( $attach [ 'name' ])) {
2020-07-20 00:27:36 -04:00
$item [ 'body' ] .= '[img]' . $attach [ 'url' ] . '[/img]' ;
2020-06-04 15:51:14 -04:00
} else {
2020-07-20 00:27:36 -04:00
$item [ 'body' ] .= '[img=' . $attach [ 'url' ] . ']' . $attach [ 'name' ] . '[/img]' ;
}
if ( ! empty ( $attach [ 'image' ])) {
$item [ 'body' ] .= '[/url]' ;
2020-06-04 15:51:14 -04:00
}
} elseif ( $filetype == 'audio' ) {
if ( ! empty ( $activity [ 'source' ]) && strpos ( $activity [ 'source' ], $attach [ 'url' ])) {
2020-06-09 12:40:36 -04:00
continue 2 ;
2020-06-04 15:51:14 -04:00
}
2020-03-23 00:43:06 -04:00
2020-06-04 15:51:14 -04:00
$item [ 'body' ] .= " \n [audio] " . $attach [ 'url' ] . '[/audio]' ;
} elseif ( $filetype == 'video' ) {
if ( ! empty ( $activity [ 'source' ]) && strpos ( $activity [ 'source' ], $attach [ 'url' ])) {
2020-06-09 12:40:36 -04:00
continue 2 ;
2020-06-04 15:51:14 -04:00
}
2020-03-23 00:43:06 -04:00
2020-06-04 15:51:14 -04:00
$item [ 'body' ] .= " \n [video] " . $attach [ 'url' ] . '[/video]' ;
} else {
if ( ! empty ( $item [ " attach " ])) {
$item [ " attach " ] .= ',' ;
} else {
$item [ " attach " ] = '' ;
}
$item [ " attach " ] .= '[attach]href="' . $attach [ 'url' ] . '" length="' . ( $attach [ 'length' ] ? ? '0' ) . '" type="' . $attach [ 'mediaType' ] . '" title="' . ( $attach [ 'name' ] ? ? '' ) . '"[/attach]' ;
}
2018-10-03 02:15:07 -04:00
}
}
return $item ;
}
2018-10-27 02:17:17 -04:00
/**
* Updates a message
*
2019-01-06 16:06:53 -05:00
* @ param array $activity Activity array
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
2018-10-27 02:17:17 -04:00
*/
public static function updateItem ( $activity )
{
2020-04-15 01:10:40 -04:00
$item = Item :: selectFirst ([ 'uri' , 'uri-id' , 'thr-parent' , 'gravity' ], [ 'uri' => $activity [ 'id' ]]);
2019-02-08 22:57:35 -05:00
if ( ! DBA :: isResult ( $item )) {
2020-04-23 15:57:20 -04:00
Logger :: warning ( 'No existing item, item will be created' , [ 'uri' => $activity [ 'id' ]]);
2020-07-20 00:37:43 -04:00
$item = self :: createItem ( $activity );
self :: postItem ( $activity , $item );
2019-02-08 22:57:35 -05:00
return ;
}
2018-10-27 02:17:17 -04:00
$item [ 'changed' ] = DateTimeFormat :: utcNow ();
2019-06-12 21:02:37 -04:00
$item [ 'edited' ] = DateTimeFormat :: utc ( $activity [ 'updated' ]);
2019-02-08 22:57:35 -05:00
2019-03-17 09:50:14 -04:00
$item = self :: processContent ( $activity , $item );
if ( empty ( $item )) {
return ;
2019-02-08 22:57:35 -05:00
}
2018-10-27 02:17:17 -04:00
Item :: update ( $item , [ 'uri' => $activity [ 'id' ]]);
}
2018-10-03 02:15:07 -04:00
/**
2018-10-07 14:41:45 -04:00
* Prepares data for a message
2018-10-03 02:15:07 -04:00
*
2019-01-06 16:06:53 -05:00
* @ param array $activity Activity array
2020-07-20 00:37:43 -04:00
* @ return array Internal item
2019-01-06 16:06:53 -05:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
* @ throws \ImagickException
2018-10-03 02:15:07 -04:00
*/
2018-10-11 16:08:04 -04:00
public static function createItem ( $activity )
2018-10-03 02:15:07 -04:00
{
$item = [];
2019-10-23 18:25:43 -04:00
$item [ 'verb' ] = Activity :: POST ;
2019-02-22 23:42:04 -05:00
$item [ 'thr-parent' ] = $activity [ 'reply-to-id' ];
2018-10-03 02:15:07 -04:00
if ( $activity [ 'reply-to-id' ] == $activity [ 'id' ]) {
$item [ 'gravity' ] = GRAVITY_PARENT ;
2019-10-24 18:10:20 -04:00
$item [ 'object-type' ] = Activity\ObjectType :: NOTE ;
2018-10-03 02:15:07 -04:00
} else {
$item [ 'gravity' ] = GRAVITY_COMMENT ;
2019-10-24 18:10:20 -04:00
$item [ 'object-type' ] = Activity\ObjectType :: COMMENT ;
2018-10-03 02:15:07 -04:00
}
2019-05-18 03:00:57 -04:00
if ( empty ( $activity [ 'directmessage' ]) && ( $activity [ 'id' ] != $activity [ 'reply-to-id' ]) && ! Item :: exists ([ 'uri' => $activity [ 'reply-to-id' ]])) {
2020-02-02 14:59:14 -05:00
Logger :: notice ( 'Parent not found. Try to refetch it.' , [ 'parent' => $activity [ 'reply-to-id' ]]);
2018-10-03 02:15:07 -04:00
self :: fetchMissingActivity ( $activity [ 'reply-to-id' ], $activity );
}
2019-10-16 08:35:14 -04:00
$item [ 'diaspora_signed_text' ] = $activity [ 'diaspora:comment' ] ? ? '' ;
2018-10-27 10:35:22 -04:00
2020-07-20 00:37:43 -04:00
/// @todo What to do with $activity['context']?
if ( empty ( $activity [ 'directmessage' ]) && ( $item [ 'gravity' ] != GRAVITY_PARENT ) && ! Item :: exists ([ 'uri' => $item [ 'thr-parent' ]])) {
Logger :: info ( 'Parent not found, message will be discarded.' , [ 'thr-parent' => $item [ 'thr-parent' ]]);
return [];
}
$item [ 'network' ] = Protocol :: ACTIVITYPUB ;
$item [ 'author-link' ] = $activity [ 'author' ];
2020-08-07 09:49:59 -04:00
$item [ 'author-id' ] = Contact :: getIdForURL ( $activity [ 'author' ]);
2020-07-20 00:37:43 -04:00
$item [ 'owner-link' ] = $activity [ 'actor' ];
2020-08-07 09:49:59 -04:00
$item [ 'owner-id' ] = Contact :: getIdForURL ( $activity [ 'actor' ]);
2020-07-20 00:37:43 -04: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' ];
$item [ 'protocol' ] = Conversation :: PARCEL_ACTIVITYPUB ;
$item [ 'conversation-href' ] = $activity [ 'context' ] ? ? '' ;
$item [ 'conversation-uri' ] = $activity [ 'conversation' ] ? ? '' ;
if ( isset ( $activity [ 'push' ])) {
$item [ 'direction' ] = $activity [ 'push' ] ? Conversation :: PUSH : Conversation :: PULL ;
}
}
$item [ 'isForum' ] = false ;
if ( ! empty ( $activity [ 'thread-completion' ])) {
2020-09-25 08:16:08 -04: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' ];
$item [ 'causer-id' ] = $item [ 'owner-id' ];
Logger :: info ( 'Use actor as causer.' , [ 'id' => $item [ 'owner-id' ], 'actor' => $item [ 'owner-link' ]]);
}
2020-07-20 00:37:43 -04: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' ];
$item [ 'created' ] = DateTimeFormat :: utc ( $activity [ 'published' ]);
$item [ 'edited' ] = DateTimeFormat :: utc ( $activity [ 'updated' ]);
$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' ]]);
$item = self :: processContent ( $activity , $item );
if ( empty ( $item )) {
2020-09-20 03:46:23 -04:00
Logger :: info ( 'Message was not processed' );
2020-07-20 00:37:43 -04:00
return [];
}
$item [ 'plink' ] = $activity [ 'alternate-url' ] ? ? $item [ 'uri' ];
$item = self :: constructAttachList ( $activity , $item );
return $item ;
2018-10-03 02:15:07 -04:00
}
/**
2018-10-06 00:18:40 -04:00
* Delete items
2018-10-03 02:15:07 -04:00
*
* @ param array $activity
2019-01-06 16:06:53 -05:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
* @ throws \ImagickException
2018-10-03 02:15:07 -04:00
*/
2018-10-03 05:15:38 -04:00
public static function deleteItem ( $activity )
2018-10-03 02:15:07 -04:00
{
2018-10-07 13:35:43 -04:00
$owner = Contact :: getIdForURL ( $activity [ 'actor' ]);
2018-10-07 14:41:45 -04:00
2020-06-28 13:50:11 -04:00
Logger :: info ( 'Deleting item' , [ 'object' => $activity [ 'object_id' ], 'owner' => $owner ]);
2020-03-03 01:47:28 -05:00
Item :: markForDeletion ([ 'uri' => $activity [ 'object_id' ], 'owner-id' => $owner ]);
2018-10-03 02:15:07 -04:00
}
2019-05-26 07:20:03 -04:00
/**
* Prepare the item array for an activity
*
* @ param array $activity Activity array
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
* @ throws \ImagickException
*/
public static function addTag ( $activity )
{
if ( empty ( $activity [ 'object_content' ]) || empty ( $activity [ 'object_id' ])) {
return ;
}
foreach ( $activity [ 'receiver' ] as $receiver ) {
2020-04-17 09:34:29 -04:00
$item = Item :: selectFirst ([ 'id' , 'uri-id' , 'tag' , 'origin' , 'author-link' ], [ 'uri' => $activity [ 'target_id' ], 'uid' => $receiver ]);
2019-05-26 07:20:03 -04: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 09:34:29 -04:00
Tag :: store ( $item [ 'uri-id' ], Tag :: HASHTAG , $activity [ 'object_content' ], $activity [ 'object_id' ]);
2020-05-05 01:11:59 -04:00
Logger :: info ( 'Tagged item' , [ 'id' => $item [ 'id' ], 'tag' => $activity [ 'object_content' ], 'uri' => $activity [ 'target_id' ], 'actor' => $activity [ 'actor' ]]);
2019-05-26 07:20:03 -04:00
}
}
2018-10-03 02:15:07 -04:00
/**
2018-10-27 02:17:17 -04:00
* Prepare the item array for an activity
2018-10-03 02:15:07 -04:00
*
2018-10-07 14:41:45 -04:00
* @ param array $activity Activity array
2018-10-27 02:17:17 -04:00
* @ param string $verb Activity verb
2019-01-06 16:06:53 -05:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
* @ throws \ImagickException
2018-10-03 02:15:07 -04:00
*/
2018-10-27 02:17:17 -04:00
public static function createActivity ( $activity , $verb )
2018-10-03 02:15:07 -04:00
{
2020-07-20 00:37:43 -04:00
$item = self :: createItem ( $activity );
2018-10-27 02:17:17 -04:00
$item [ 'verb' ] = $verb ;
2019-02-22 23:42:04 -05:00
$item [ 'thr-parent' ] = $activity [ 'object_id' ];
2018-10-03 02:15:07 -04:00
$item [ 'gravity' ] = GRAVITY_ACTIVITY ;
2019-10-24 18:10:20 -04:00
$item [ 'object-type' ] = Activity\ObjectType :: NOTE ;
2018-10-03 02:15:07 -04:00
2019-10-16 08:35:14 -04:00
$item [ 'diaspora_signed_text' ] = $activity [ 'diaspora:like' ] ? ? '' ;
2018-10-27 10:35:22 -04:00
2018-10-11 16:08:04 -04:00
self :: postItem ( $activity , $item );
2018-10-03 02:15:07 -04:00
}
2018-10-26 00:13:26 -04:00
/**
* Create an event
*
2018-10-27 02:17:17 -04:00
* @ param array $activity Activity array
* @ param array $item
2019-01-06 16:06:53 -05:00
* @ throws \Exception
2018-10-26 00:13:26 -04:00
*/
public static function createEvent ( $activity , $item )
{
2018-12-24 22:52:21 -05:00
$event [ 'summary' ] = HTML :: toBBCode ( $activity [ 'name' ]);
$event [ 'desc' ] = HTML :: toBBCode ( $activity [ 'content' ]);
$event [ 'start' ] = $activity [ 'start-time' ];
$event [ 'finish' ] = $activity [ 'end-time' ];
2018-10-26 00:13:26 -04:00
$event [ 'nofinish' ] = empty ( $event [ 'finish' ]);
$event [ 'location' ] = $activity [ 'location' ];
2018-12-24 22:52:21 -05:00
$event [ 'adjust' ] = true ;
$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' ];
2018-10-26 00:13:26 -04:00
$condition = [ 'uri' => $item [ 'uri' ], 'uid' => $item [ 'uid' ]];
$ev = DBA :: selectFirst ( 'event' , [ 'id' ], $condition );
if ( DBA :: isResult ( $ev )) {
$event [ 'id' ] = $ev [ 'id' ];
}
$event_id = Event :: store ( $event );
2020-06-28 13:50:11 -04:00
Logger :: info ( 'Event was stored' , [ 'id' => $event_id ]);
2018-10-26 00:13:26 -04:00
}
2019-03-17 09:50:14 -04:00
/**
* Process the content
*
* @ param array $activity Activity array
* @ param array $item
2019-03-17 11:49:21 -04:00
* @ return array | bool Returns the item array or false if there was an unexpected occurrence
2019-03-17 09:50:14 -04:00
* @ throws \Exception
*/
private static function processContent ( $activity , $item )
{
$item [ 'title' ] = HTML :: toBBCode ( $activity [ 'name' ]);
if ( ! empty ( $activity [ 'source' ])) {
$item [ 'body' ] = $activity [ 'source' ];
} else {
$content = HTML :: toBBCode ( $activity [ 'content' ]);
2019-03-17 18:13:17 -04:00
if ( ! empty ( $activity [ 'emojis' ])) {
$content = self :: replaceEmojis ( $content , $activity [ 'emojis' ]);
}
2019-03-17 09:50:14 -04:00
$content = self :: convertMentions ( $content );
2019-05-16 01:44:59 -04:00
if ( empty ( $activity [ 'directmessage' ]) && ( $item [ 'thr-parent' ] != $item [ 'uri' ]) && ( $item [ 'gravity' ] == GRAVITY_COMMENT )) {
2019-03-17 09:50:14 -04:00
$item_private = ! in_array ( 0 , $activity [ 'item_receiver' ]);
2020-05-02 01:14:30 -04:00
$parent = Item :: selectFirst ([ 'id' , 'uri-id' , 'private' , 'author-link' , 'alias' ], [ 'uri' => $item [ 'thr-parent' ]]);
2019-03-17 09:50:14 -04:00
if ( ! DBA :: isResult ( $parent )) {
2019-03-17 09:56:47 -04:00
Logger :: warning ( 'Unknown parent item.' , [ 'uri' => $item [ 'thr-parent' ]]);
2019-03-17 09:50:14 -04:00
return false ;
}
2020-06-27 06:35:45 -04:00
if ( $item_private && ( $parent [ 'private' ] != Item :: PRIVATE )) {
2019-03-17 09:50:14 -04: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 04:55:10 -04:00
$content = self :: removeImplicitMentionsFromBody ( $content , $parent );
2019-03-17 09:50:14 -04:00
}
$item [ 'content-warning' ] = HTML :: toBBCode ( $activity [ 'summary' ]);
$item [ 'body' ] = $content ;
}
2020-04-20 05:47:26 -04:00
self :: storeFromBody ( $item );
2020-04-14 13:18:48 -04:00
self :: storeTags ( $item [ 'uri-id' ], $activity [ 'tags' ]);
2020-04-13 19:54:28 -04:00
2019-03-17 09:50:14 -04:00
$item [ 'location' ] = $activity [ 'location' ];
2020-06-28 04:46:27 -04:00
if ( ! empty ( $activity [ 'latitude' ]) && ! empty ( $activity [ 'longitude' ])) {
$item [ 'coord' ] = $activity [ 'latitude' ] . ' ' . $activity [ 'longitude' ];
2019-03-17 09:50:14 -04:00
}
$item [ 'app' ] = $activity [ 'generator' ];
return $item ;
}
2020-04-20 08:19:26 -04:00
/**
* Store hashtags and mentions
*
* @ param array $item
*/
private static function storeFromBody ( array $item )
2020-04-20 05:47:26 -04:00
{
// Make sure to delete all existing tags (can happen when called via the update functionality)
2020-04-20 08:19:26 -04:00
DBA :: delete ( 'post-tag' , [ 'uri-id' => $item [ 'uri-id' ]]);
2020-04-20 05:47:26 -04:00
Tag :: storeFromBody ( $item [ 'uri-id' ], $item [ 'body' ], '@!' );
}
2020-01-19 09:33:16 -05:00
/**
* Generate a GUID out of an URL
*
* @ param string $url message URL
* @ return string with GUID
*/
private static function getGUIDByURL ( string $url )
{
$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 );
}
2018-10-03 02:15:07 -04:00
/**
2018-10-07 14:41:45 -04:00
* Creates an item post
2018-10-03 02:15:07 -04:00
*
2019-01-06 16:06:53 -05:00
* @ param array $activity Activity data
* @ param array $item item array
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
* @ throws \ImagickException
2018-10-03 02:15:07 -04:00
*/
2020-07-20 00:37:43 -04:00
public static function postItem ( array $activity , array $item )
2018-10-03 02:15:07 -04:00
{
2020-07-21 15:55:24 -04:00
if ( empty ( $item )) {
return ;
}
2019-02-10 13:42:51 -05:00
$stored = false ;
2020-09-25 02:47:07 -04:00
ksort ( $activity [ 'receiver' ]);
2019-02-10 13:42:51 -05:00
2018-10-03 02:15:07 -04:00
foreach ( $activity [ 'receiver' ] as $receiver ) {
2020-03-02 02:57:23 -05:00
if ( $receiver == - 1 ) {
continue ;
}
2018-10-03 02:15:07 -04:00
$item [ 'uid' ] = $receiver ;
2019-07-16 01:07:26 -04:00
2020-09-12 13:45:04 -04:00
$type = $activity [ 'reception_type' ][ $receiver ] ? ? Receiver :: TARGET_UNKNOWN ;
switch ( $type ) {
case Receiver :: TARGET_TO :
$item [ 'post-type' ] = Item :: PT_TO ;
break ;
case Receiver :: TARGET_CC :
$item [ 'post-type' ] = Item :: PT_CC ;
break ;
case Receiver :: TARGET_BTO :
$item [ 'post-type' ] = Item :: PT_BTO ;
break ;
case Receiver :: TARGET_BCC :
$item [ 'post-type' ] = Item :: PT_BCC ;
break ;
case Receiver :: TARGET_FOLLOWER :
$item [ 'post-type' ] = Item :: PT_FOLLOWER ;
break ;
2020-09-13 10:15:28 -04:00
case Receiver :: TARGET_ANSWER :
$item [ 'post-type' ] = Item :: PT_COMMENT ;
break ;
2020-09-14 13:48:57 -04:00
case Receiver :: TARGET_GLOBAL :
$item [ 'post-type' ] = Item :: PT_GLOBAL ;
break ;
2020-09-12 13:45:04 -04:00
default :
$item [ 'post-type' ] = Item :: PT_ARTICLE ;
}
2020-09-25 02:47:07 -04:00
if ( ! empty ( $activity [ 'from-relay' ])) {
$item [ 'post-type' ] = Item :: PT_RELAY ;
} elseif ( ! empty ( $activity [ 'thread-completion' ])) {
$item [ 'post-type' ] = Item :: PT_FETCHED ;
2020-09-21 08:31:20 -04:00
}
2020-07-20 00:37:43 -04:00
if ( $item [ 'isForum' ] ? ? false ) {
2020-08-07 09:49:59 -04:00
$item [ 'contact-id' ] = Contact :: getIdForURL ( $activity [ 'actor' ], $receiver );
2019-07-30 09:08:14 -04:00
} else {
2020-08-07 09:49:59 -04:00
$item [ 'contact-id' ] = Contact :: getIdForURL ( $activity [ 'author' ], $receiver );
2019-07-16 01:07:26 -04:00
}
2018-10-03 02:15:07 -04:00
2019-08-01 20:39:42 -04:00
if (( $receiver != 0 ) && empty ( $item [ 'contact-id' ])) {
2020-08-07 09:49:59 -04:00
$item [ 'contact-id' ] = Contact :: getIdForURL ( $activity [ 'author' ]);
2018-10-03 02:15:07 -04:00
}
2019-05-16 01:44:59 -04:00
if ( ! empty ( $activity [ 'directmessage' ])) {
self :: postMail ( $activity , $item );
continue ;
}
2020-01-18 10:50:57 -05:00
if ( DI :: pConfig () -> get ( $receiver , 'system' , 'accept_only_sharer' , false ) && ( $receiver != 0 ) && ( $item [ 'gravity' ] == GRAVITY_PARENT )) {
2019-07-17 17:37:13 -04:00
$skip = ! Contact :: isSharingByURL ( $activity [ 'author' ], $receiver );
2020-07-20 00:37:43 -04:00
if ( $skip && (( $activity [ 'type' ] == 'as:Announce' ) || ( $item [ 'isForum' ] ? ? false ))) {
2019-07-17 17:37:13 -04:00
$skip = ! Contact :: isSharingByURL ( $activity [ 'actor' ], $receiver );
}
if ( $skip ) {
Logger :: info ( 'Skipping post' , [ 'uid' => $receiver , 'url' => $item [ 'uri' ]]);
continue ;
}
Logger :: info ( 'Accepting post' , [ 'uid' => $receiver , 'url' => $item [ 'uri' ]]);
}
2020-03-28 10:02:49 -04:00
if (( $item [ 'gravity' ] != GRAVITY_ACTIVITY ) && ( $activity [ 'object_type' ] == 'as:Event' )) {
2018-10-26 00:13:26 -04:00
self :: createEvent ( $activity , $item );
}
2018-10-03 02:15:07 -04:00
$item_id = Item :: insert ( $item );
2019-02-22 23:00:16 -05:00
if ( $item_id ) {
Logger :: info ( 'Item insertion successful' , [ 'user' => $item [ 'uid' ], 'item_id' => $item_id ]);
} else {
Logger :: notice ( 'Item insertion aborted' , [ 'user' => $item [ 'uid' ]]);
}
2019-02-10 13:42:51 -05:00
2019-02-10 13:59:05 -05:00
if ( $item [ 'uid' ] == 0 ) {
$stored = $item_id ;
2019-02-10 13:42:51 -05:00
}
2018-10-03 02:15:07 -04:00
}
2019-01-30 16:33:23 -05:00
2019-02-10 13:42:51 -05:00
// Store send a follow request for every reshare - but only when the item had been stored
2020-03-02 02:57:23 -05:00
if ( $stored && ( $item [ 'private' ] != Item :: PRIVATE ) && ( $item [ 'gravity' ] == GRAVITY_PARENT ) && ( $item [ 'author-link' ] != $item [ 'owner-link' ])) {
2019-01-30 16:33:23 -05: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 13:50:11 -04:00
Logger :: info ( 'Send follow request' , [ 'uri' => $item [ 'uri' ], 'stored' => $stored , 'to' => $item [ 'author-link' ]]);
2019-01-30 16:33:23 -05:00
ActivityPub\Transmitter :: sendFollowObject ( $item [ 'uri' ], $item [ 'author-link' ]);
}
}
2018-10-03 02:15:07 -04:00
}
2020-04-15 16:59:45 -04:00
/**
* Store tags and mentions into the tag table
*
* @ param integer $uriid
* @ param array $tags
*/
2020-04-14 13:18:48 -04:00
private static function storeTags ( int $uriid , array $tags = null )
2020-04-13 19:54:28 -04:00
{
foreach ( $tags as $tag ) {
if ( empty ( $tag [ 'name' ]) || empty ( $tag [ 'type' ]) || ! in_array ( $tag [ 'type' ], [ 'Mention' , 'Hashtag' ])) {
continue ;
}
2020-04-17 02:35:20 -04:00
$hash = substr ( $tag [ 'name' ], 0 , 1 );
2020-04-13 19:54:28 -04:00
if ( $tag [ 'type' ] == 'Mention' ) {
2020-04-17 02:35:20 -04: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 19:54:28 -04:00
}
2020-04-20 01:43:13 -04:00
$type = Tag :: IMPLICIT_MENTION ;
2020-04-17 02:35:20 -04:00
2020-04-15 16:45:04 -04:00
if ( ! empty ( $tag [ 'href' ])) {
$apcontact = APContact :: getByURL ( $tag [ 'href' ]);
2020-04-15 16:52:30 -04:00
if ( ! empty ( $apcontact [ 'name' ]) || ! empty ( $apcontact [ 'nick' ])) {
2020-04-17 02:35:20 -04:00
$tag [ 'name' ] = $apcontact [ 'name' ] ? : $apcontact [ 'nick' ];
2020-04-15 16:45:04 -04:00
}
}
2020-04-13 19:54:28 -04:00
} elseif ( $tag [ 'type' ] == 'Hashtag' ) {
2020-04-20 01:43:13 -04:00
if ( $hash == Tag :: TAG_CHARACTER [ Tag :: HASHTAG ]) {
2020-04-17 02:35:20 -04:00
$tag [ 'name' ] = substr ( $tag [ 'name' ], 1 );
2020-04-13 19:54:28 -04:00
}
2020-04-20 01:43:13 -04:00
$type = Tag :: HASHTAG ;
2020-04-13 19:54:28 -04:00
}
2020-04-17 02:35:20 -04:00
if ( empty ( $tag [ 'name' ])) {
2020-04-13 19:54:28 -04:00
continue ;
}
2020-04-20 05:47:26 -04:00
2020-04-20 01:43:13 -04:00
Tag :: store ( $uriid , $type , $tag [ 'name' ], $tag [ 'href' ]);
2020-04-13 19:54:28 -04:00
}
}
2019-05-16 01:44:59 -04:00
/**
* Creates an mail post
*
* @ param array $activity Activity data
* @ param array $item item array
2019-06-12 21:01:44 -04:00
* @ return int | bool New mail table row id or false on error
2019-05-16 01:44:59 -04:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
private static function postMail ( $activity , $item )
{
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 ];
}
2020-05-16 12:28:15 -04:00
$title = trim ( HTML :: toPlaintext ( BBCode :: convert ( $title , false , BBCode :: API , true ), 0 ));
2019-05-16 01:44:59 -04:00
if ( strlen ( $title ) > 20 ) {
$title = substr ( $title , 0 , 20 ) . '...' ;
}
$msg [ 'title' ] = $title ;
}
}
$msg [ 'body' ] = $item [ 'body' ];
2019-06-12 21:01:44 -04:00
return Mail :: insert ( $msg );
2019-05-16 01:44:59 -04:00
}
2018-10-03 02:15:07 -04:00
/**
2018-10-07 14:41:45 -04:00
* Fetches missing posts
2018-10-03 02:15:07 -04:00
*
2020-09-22 01:36:01 -04:00
* @ param string $url message URL
* @ param array $child activity array with the child of this message
* @ param string $relay_actor Relay actor
2020-01-20 17:30:34 -05:00
* @ return string fetched message URL
2019-01-06 16:06:53 -05:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
2018-10-03 02:15:07 -04:00
*/
2020-09-22 01:36:01 -04:00
public static function fetchMissingActivity ( string $url , array $child = [], string $relay_actor = '' )
2018-10-03 02:15:07 -04:00
{
2019-07-17 15:36:32 -04:00
if ( ! empty ( $child [ 'receiver' ])) {
$uid = ActivityPub\Receiver :: getFirstUserFromReceivers ( $child [ 'receiver' ]);
} else {
$uid = 0 ;
}
2018-11-03 17:37:08 -04:00
$object = ActivityPub :: fetchContent ( $url , $uid );
2018-10-03 02:15:07 -04:00
if ( empty ( $object )) {
2018-10-29 17:20:46 -04:00
Logger :: log ( 'Activity ' . $url . ' was not fetchable, aborting.' );
2020-01-20 17:30:34 -05:00
return '' ;
2018-10-03 02:15:07 -04:00
}
2018-12-29 04:53:31 -05:00
if ( empty ( $object [ 'id' ])) {
Logger :: log ( 'Activity ' . $url . ' has got not id, aborting. ' . json_encode ( $object ));
2020-01-20 17:30:34 -05:00
return '' ;
2018-12-29 04:53:31 -05:00
}
2020-09-14 16:58:41 -04:00
if ( ! empty ( $object [ 'actor' ])) {
$object_actor = $object [ 'actor' ];
2019-07-17 15:36:32 -04:00
} elseif ( ! empty ( $object [ 'attributedTo' ])) {
2020-09-14 16:58:41 -04:00
$object_actor = $object [ 'attributedTo' ];
2019-07-17 15:36:32 -04:00
} else {
// Shouldn't happen
2020-09-14 16:58:41 -04:00
$object_actor = '' ;
}
$signer = [ $object_actor ];
if ( ! empty ( $child [ 'author' ])) {
$actor = $child [ 'author' ];
$signer [] = $actor ;
} else {
$actor = $object_actor ;
2019-07-17 15:36:32 -04:00
}
if ( ! empty ( $object [ 'published' ])) {
$published = $object [ 'published' ];
} elseif ( ! empty ( $child [ 'published' ])) {
$published = $child [ 'published' ];
} else {
$published = DateTimeFormat :: utcNow ();
}
2018-10-03 02:15:07 -04:00
$activity = [];
$activity [ '@context' ] = $object [ '@context' ];
unset ( $object [ '@context' ]);
$activity [ 'id' ] = $object [ 'id' ];
2019-10-16 08:35:14 -04:00
$activity [ 'to' ] = $object [ 'to' ] ? ? [];
$activity [ 'cc' ] = $object [ 'cc' ] ? ? [];
2019-07-17 15:36:32 -04:00
$activity [ 'actor' ] = $actor ;
2018-10-03 02:15:07 -04:00
$activity [ 'object' ] = $object ;
2019-07-17 15:36:32 -04:00
$activity [ 'published' ] = $published ;
2018-10-03 02:15:07 -04:00
$activity [ 'type' ] = 'Create' ;
2018-10-07 11:34:51 -04:00
$ldactivity = JsonLD :: compact ( $activity );
2018-10-09 01:04:24 -04:00
2020-09-25 08:16:08 -04:00
if ( ! empty ( $relay_actor )) {
$ldactivity [ 'thread-completion' ] = $ldactivity [ 'from-relay' ] = Contact :: getIdForURL ( $relay_actor );
} elseif ( ! empty ( $child [ 'thread-completion' ])) {
$ldactivity [ 'thread-completion' ] = $child [ 'thread-completion' ];
} else {
$ldactivity [ 'thread-completion' ] = Contact :: getIdForURL ( $actor );
}
2018-10-09 01:04:24 -04:00
2020-09-22 11:48:44 -04:00
if ( ! empty ( $relay_actor ) && ! self :: acceptIncomingMessage ( $ldactivity , $object [ 'id' ])) {
return '' ;
}
2020-09-14 16:58:41 -04:00
ActivityPub\Receiver :: processActivity ( $ldactivity , json_encode ( $activity ), $uid , true , false , $signer );
2020-03-03 17:43:19 -05:00
2020-01-20 17:30:34 -05:00
Logger :: notice ( 'Activity had been fetched and processed.' , [ 'url' => $url , 'object' => $activity [ 'id' ]]);
2019-07-21 03:37:50 -04:00
2020-01-20 17:30:34 -05:00
return $activity [ 'id' ];
2018-10-03 02:15:07 -04:00
}
2020-09-22 11:48:44 -04: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
*/
private static function acceptIncomingMessage ( array $activity , string $id )
{
if ( empty ( $activity [ 'as:object' ])) {
Logger :: info ( 'No object field in activity - accepted' , [ 'id' => $id ]);
return true ;
}
$config = DI :: config ();
$subscribe = $config -> get ( 'system' , 'relay_subscribe' , false );
if ( $subscribe ) {
$scope = $config -> get ( 'system' , 'relay_scope' , SR_SCOPE_ALL );
} else {
$scope = SR_SCOPE_NONE ;
}
$replyto = JsonLD :: fetchElement ( $activity [ 'as:object' ], 'as:inReplyTo' , '@id' );
if ( Item :: exists ([ 'uri' => $replyto ])) {
Logger :: info ( 'Post is a reply to an existing post - accepted' , [ 'id' => $id , 'replyto' => $replyto ]);
return true ;
}
if ( $scope == SR_SCOPE_NONE ) {
Logger :: info ( 'Server does not accept relay posts - rejected' , [ 'id' => $id ]);
return false ;
}
$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' ]), '#' );
}
}
$systemTags = [];
$userTags = [];
2020-09-29 15:48:26 -04:00
$denyTags = [];
2020-09-22 11:48:44 -04:00
if ( $scope == SR_SCOPE_TAGS ) {
2020-09-29 15:48:26 -04:00
$server_tags = $config -> get ( 'system' , 'relay_server_tags' );
2020-09-22 11:48:44 -04:00
$tagitems = explode ( ',' , mb_strtolower ( $server_tags ));
foreach ( $tagitems AS $tag ) {
$systemTags [] = trim ( $tag , '# ' );
}
if ( $config -> get ( 'system' , 'relay_user_tags' )) {
$userTags = Search :: getUserTags ();
}
}
$tagList = array_unique ( array_merge ( $systemTags , $userTags ));
2020-09-29 15:48:26 -04:00
$deny_tags = $config -> get ( 'system' , 'relay_deny_tags' );
$tagitems = explode ( ',' , mb_strtolower ( $deny_tags ));
foreach ( $tagitems AS $tag ) {
$tag = trim ( $tag , '# ' );
$denyTags [] = $tag ;
}
if ( ! empty ( $tagList ) || ! empty ( $denyTags )) {
$content = mb_strtolower ( BBCode :: toPlaintext ( HTML :: toBBCode ( JsonLD :: fetchElement ( $activity [ 'as:object' ], 'as:content' , '@value' )), false ));
foreach ( $messageTags as $tag ) {
if ( in_array ( $tag , $denyTags )) {
Logger :: info ( 'Unwanted hashtag found - rejected' , [ 'id' => $id , 'hashtag' => $tag ]);
return false ;
}
if ( in_array ( $tag , $tagList )) {
Logger :: info ( 'Subscribed hashtag found - accepted' , [ 'id' => $id , 'hashtag' => $tag ]);
return true ;
}
// We check with "strpos" for performance issues. Only when this is true, the regular expression check is used
// RegExp is taken from here: https://medium.com/@shiba1014/regex-word-boundaries-with-unicode-207794f6e7ed
if (( strpos ( $content , $tag ) !== false ) && preg_match ( '/(?<=[\s,.:;"\']|^)' . preg_quote ( $tag , '/' ) . '(?=[\s,.:;"\']|$)/' , $content )) {
Logger :: info ( 'Subscribed hashtag found in content - accepted' , [ 'id' => $id , 'hashtag' => $tag ]);
return true ;
}
2020-09-22 11:48:44 -04:00
}
}
2020-09-29 15:48:26 -04:00
if ( $scope == SR_SCOPE_ALL ) {
Logger :: info ( 'Server accept all posts - accepted' , [ 'id' => $id ]);
return true ;
}
2020-09-22 11:48:44 -04:00
Logger :: info ( 'No matching hashtags found - rejected' , [ 'id' => $id ]);
return false ;
}
2018-10-03 02:15:07 -04:00
/**
2018-10-06 00:18:40 -04:00
* perform a " follow " request
2018-10-03 02:15:07 -04:00
*
* @ param array $activity
2019-01-06 16:06:53 -05:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
* @ throws \ImagickException
2018-10-03 02:15:07 -04:00
*/
2018-10-03 05:15:38 -04:00
public static function followUser ( $activity )
2018-10-03 02:15:07 -04:00
{
2018-10-07 15:42:04 -04:00
$uid = User :: getIdForURL ( $activity [ 'object_id' ]);
2018-10-03 02:15:07 -04:00
if ( empty ( $uid )) {
return ;
}
$owner = User :: getOwnerDataById ( $uid );
2020-08-22 12:34:04 -04:00
if ( empty ( $owner )) {
return ;
}
2018-10-03 02:15:07 -04:00
2018-10-07 13:35:43 -04:00
$cid = Contact :: getIdForURL ( $activity [ 'actor' ], $uid );
2018-10-03 02:15:07 -04:00
if ( ! empty ( $cid )) {
2018-10-09 15:58:15 -04:00
self :: switchContact ( $cid );
2019-05-05 07:02:19 -04:00
DBA :: update ( 'contact' , [ 'hub-verify' => $activity [ 'id' ], 'protocol' => Protocol :: ACTIVITYPUB ], [ 'id' => $cid ]);
2018-10-05 02:35:50 -04:00
$contact = DBA :: selectFirst ( 'contact' , [], [ 'id' => $cid , 'network' => Protocol :: NATIVE_SUPPORT ]);
2018-10-03 02:15:07 -04:00
} else {
2019-05-19 18:43:19 -04:00
$contact = [];
2018-10-03 02:15:07 -04:00
}
2018-10-07 13:35:43 -04:00
$item = [ 'author-id' => Contact :: getIdForURL ( $activity [ 'actor' ]),
'author-link' => $activity [ 'actor' ]];
2018-10-03 02:15:07 -04:00
2019-10-16 08:35:14 -04:00
$note = Strings :: escapeTags ( trim ( $activity [ 'content' ] ? ? '' ));
2019-05-05 05:17:45 -04:00
2018-10-13 14:13:01 -04:00
// Ensure that the contact has got the right network type
self :: switchContact ( $item [ 'author-id' ]);
2019-05-19 18:43:19 -04:00
$result = Contact :: addRelationship ( $owner , $contact , $item , false , $note );
2019-05-20 16:33:09 -04:00
if ( $result === true ) {
2020-07-10 01:30:12 -04:00
ActivityPub\Transmitter :: sendContactAccept ( $item [ 'author-link' ], $activity [ 'id' ], $owner [ 'uid' ]);
2019-05-19 18:46:29 -04:00
}
2018-10-07 13:35:43 -04:00
$cid = Contact :: getIdForURL ( $activity [ 'actor' ], $uid );
2018-10-03 02:15:07 -04:00
if ( empty ( $cid )) {
return ;
}
2019-01-09 17:30:26 -05:00
if ( empty ( $contact )) {
2019-05-05 07:02:19 -04:00
DBA :: update ( 'contact' , [ 'hub-verify' => $activity [ 'id' ], 'protocol' => Protocol :: ACTIVITYPUB ], [ 'id' => $cid ]);
2019-01-09 17:30:26 -05:00
}
2018-10-29 17:20:46 -04:00
Logger :: log ( 'Follow user ' . $uid . ' from contact ' . $cid . ' with id ' . $activity [ 'id' ]);
2018-10-03 02:15:07 -04:00
}
/**
2018-10-06 00:18:40 -04:00
* Update the given profile
2018-10-03 02:15:07 -04:00
*
* @ param array $activity
2019-01-06 16:06:53 -05:00
* @ throws \Exception
2018-10-03 02:15:07 -04:00
*/
2018-10-03 05:15:38 -04:00
public static function updatePerson ( $activity )
2018-10-03 02:15:07 -04:00
{
2018-10-07 14:41:45 -04:00
if ( empty ( $activity [ 'object_id' ])) {
2018-10-03 02:15:07 -04:00
return ;
}
2020-06-28 13:50:11 -04:00
Logger :: info ( 'Updating profile' , [ 'object' => $activity [ 'object_id' ]]);
2020-08-06 14:53:45 -04:00
Contact :: updateFromProbeByURL ( $activity [ 'object_id' ]);
2018-10-03 02:15:07 -04:00
}
/**
2018-10-06 00:18:40 -04:00
* Delete the given profile
2018-10-03 02:15:07 -04:00
*
* @ param array $activity
2019-01-06 16:06:53 -05:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
2018-10-03 02:15:07 -04:00
*/
2018-10-03 05:15:38 -04:00
public static function deletePerson ( $activity )
2018-10-03 02:15:07 -04:00
{
2018-10-07 15:42:04 -04:00
if ( empty ( $activity [ 'object_id' ]) || empty ( $activity [ 'actor' ])) {
2020-06-28 13:50:11 -04:00
Logger :: info ( 'Empty object id or actor.' );
2018-10-03 02:15:07 -04:00
return ;
}
2018-10-07 15:42:04 -04:00
if ( $activity [ 'object_id' ] != $activity [ 'actor' ]) {
2020-06-28 13:50:11 -04:00
Logger :: info ( 'Object id does not match actor.' );
2018-10-03 02:15:07 -04:00
return ;
}
2018-11-08 11:28:29 -05:00
$contacts = DBA :: select ( 'contact' , [ 'id' ], [ 'nurl' => Strings :: normaliseLink ( $activity [ 'object_id' ])]);
2018-10-03 02:15:07 -04:00
while ( $contact = DBA :: fetch ( $contacts )) {
2018-10-07 11:34:51 -04:00
Contact :: remove ( $contact [ 'id' ]);
2018-10-03 02:15:07 -04:00
}
DBA :: close ( $contacts );
2020-06-28 13:50:11 -04:00
Logger :: info ( 'Deleted contact' , [ 'object' => $activity [ 'object_id' ]]);
2018-10-03 02:15:07 -04:00
}
/**
2018-10-06 00:18:40 -04:00
* Accept a follow request
2018-10-03 02:15:07 -04:00
*
* @ param array $activity
2019-01-06 16:06:53 -05:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
* @ throws \ImagickException
2018-10-03 02:15:07 -04:00
*/
2018-10-03 05:15:38 -04:00
public static function acceptFollowUser ( $activity )
2018-10-03 02:15:07 -04:00
{
2018-10-07 14:41:45 -04:00
$uid = User :: getIdForURL ( $activity [ 'object_actor' ]);
2018-10-03 02:15:07 -04:00
if ( empty ( $uid )) {
return ;
}
2018-10-07 13:35:43 -04:00
$cid = Contact :: getIdForURL ( $activity [ 'actor' ], $uid );
2018-10-03 02:15:07 -04:00
if ( empty ( $cid )) {
2020-06-28 13:50:11 -04:00
Logger :: info ( 'No contact found' , [ 'actor' => $activity [ 'actor' ]]);
2018-10-03 02:15:07 -04:00
return ;
}
2018-10-09 15:58:15 -04:00
self :: switchContact ( $cid );
2018-10-03 02:15:07 -04:00
$fields = [ 'pending' => false ];
$contact = DBA :: selectFirst ( 'contact' , [ 'rel' ], [ 'id' => $cid ]);
if ( $contact [ 'rel' ] == Contact :: FOLLOWER ) {
$fields [ 'rel' ] = Contact :: FRIEND ;
}
$condition = [ 'id' => $cid ];
DBA :: update ( 'contact' , $fields , $condition );
2020-06-28 13:50:11 -04:00
Logger :: info ( 'Accept contact request' , [ 'contact' => $cid , 'user' => $uid ]);
2018-10-03 02:15:07 -04:00
}
/**
2018-10-06 00:18:40 -04:00
* Reject a follow request
2018-10-03 02:15:07 -04:00
*
* @ param array $activity
2019-01-06 16:06:53 -05:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
* @ throws \ImagickException
2018-10-03 02:15:07 -04:00
*/
2018-10-03 05:15:38 -04:00
public static function rejectFollowUser ( $activity )
2018-10-03 02:15:07 -04:00
{
2018-10-07 14:41:45 -04:00
$uid = User :: getIdForURL ( $activity [ 'object_actor' ]);
2018-10-03 02:15:07 -04:00
if ( empty ( $uid )) {
return ;
}
2018-10-07 13:35:43 -04:00
$cid = Contact :: getIdForURL ( $activity [ 'actor' ], $uid );
2018-10-03 02:15:07 -04:00
if ( empty ( $cid )) {
2020-06-28 13:50:11 -04:00
Logger :: info ( 'No contact found' , [ 'actor' => $activity [ 'actor' ]]);
2018-10-03 02:15:07 -04:00
return ;
}
2018-10-09 15:58:15 -04:00
self :: switchContact ( $cid );
2019-05-02 16:04:15 -04:00
if ( DBA :: exists ( 'contact' , [ 'id' => $cid , 'rel' => Contact :: SHARING ])) {
2018-10-03 02:15:07 -04:00
Contact :: remove ( $cid );
2020-06-28 13:50:11 -04:00
Logger :: info ( 'Rejected contact request - contact removed' , [ 'contact' => $cid , 'user' => $uid ]);
2018-10-03 02:15:07 -04:00
} else {
2020-06-28 13:50:11 -04:00
Logger :: info ( 'Rejected contact request' , [ 'contact' => $cid , 'user' => $uid ]);
2018-10-03 02:15:07 -04:00
}
}
/**
2018-10-06 00:18:40 -04:00
* Undo activity like " like " or " dislike "
2018-10-03 02:15:07 -04:00
*
* @ param array $activity
2019-01-06 16:06:53 -05:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
* @ throws \ImagickException
2018-10-03 02:15:07 -04:00
*/
2018-10-03 05:15:38 -04:00
public static function undoActivity ( $activity )
2018-10-03 02:15:07 -04:00
{
2018-10-07 14:41:45 -04:00
if ( empty ( $activity [ 'object_id' ])) {
2018-10-03 02:15:07 -04:00
return ;
}
2018-10-07 14:41:45 -04:00
if ( empty ( $activity [ 'object_actor' ])) {
2018-10-03 02:15:07 -04:00
return ;
}
2018-10-07 14:41:45 -04:00
$author_id = Contact :: getIdForURL ( $activity [ 'object_actor' ]);
2018-10-03 02:15:07 -04:00
if ( empty ( $author_id )) {
return ;
}
2020-03-03 01:47:28 -05:00
Item :: markForDeletion ([ 'uri' => $activity [ 'object_id' ], 'author-id' => $author_id , 'gravity' => GRAVITY_ACTIVITY ]);
2018-10-03 02:15:07 -04:00
}
/**
2018-10-06 00:18:40 -04:00
* Activity to remove a follower
2018-10-03 02:15:07 -04:00
*
* @ param array $activity
2019-01-06 16:06:53 -05:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
* @ throws \ImagickException
2018-10-03 02:15:07 -04:00
*/
2018-10-03 05:15:38 -04:00
public static function undoFollowUser ( $activity )
2018-10-03 02:15:07 -04:00
{
2018-10-07 14:41:45 -04:00
$uid = User :: getIdForURL ( $activity [ 'object_object' ]);
2018-10-03 02:15:07 -04:00
if ( empty ( $uid )) {
return ;
}
$owner = User :: getOwnerDataById ( $uid );
2020-08-22 12:34:04 -04:00
if ( empty ( $owner )) {
return ;
}
2018-10-03 02:15:07 -04:00
2018-10-07 13:35:43 -04:00
$cid = Contact :: getIdForURL ( $activity [ 'actor' ], $uid );
2018-10-03 02:15:07 -04:00
if ( empty ( $cid )) {
2020-06-28 13:50:11 -04:00
Logger :: info ( 'No contact found' , [ 'actor' => $activity [ 'actor' ]]);
2018-10-03 02:15:07 -04:00
return ;
}
2018-10-09 15:58:15 -04:00
self :: switchContact ( $cid );
2018-10-03 02:15:07 -04:00
$contact = DBA :: selectFirst ( 'contact' , [], [ 'id' => $cid ]);
if ( ! DBA :: isResult ( $contact )) {
return ;
}
Contact :: removeFollower ( $owner , $contact );
2020-06-28 13:50:11 -04:00
Logger :: info ( 'Undo following request' , [ 'contact' => $cid , 'user' => $uid ]);
2018-10-03 02:15:07 -04:00
}
2018-10-09 15:58:15 -04:00
/**
* Switches a contact to AP if needed
*
* @ param integer $cid Contact ID
2019-01-06 16:06:53 -05:00
* @ throws \Exception
2018-10-09 15:58:15 -04:00
*/
private static function switchContact ( $cid )
{
2020-01-16 01:43:21 -05: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 15:58:15 -04:00
return ;
}
2020-01-16 01:43:21 -05:00
Logger :: info ( 'Change existing contact' , [ 'cid' => $cid , 'previous' => $contact [ 'network' ]]);
Contact :: updateFromProbe ( $cid );
2018-10-09 15:58:15 -04:00
}
2019-02-08 22:57:35 -05: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
*/
private static function getImplicitMentionList ( array $parent )
{
2020-05-01 02:01:22 -04:00
$parent_terms = Tag :: getByURIId ( $parent [ 'uri-id' ], [ Tag :: MENTION , Tag :: IMPLICIT_MENTION , Tag :: EXCLUSIVE_MENTION ]);
2019-02-22 23:42:04 -05:00
2020-07-15 13:06:48 -04:00
$parent_author = Contact :: getByURL ( $parent [ 'author-link' ], false , [ 'url' , 'nurl' , 'alias' ]);
2019-02-08 22:57:35 -05:00
2019-02-24 15:48:56 -05:00
$implicit_mentions = [];
2020-06-16 16:41:34 -04:00
if ( empty ( $parent_author [ 'url' ])) {
2019-02-24 15:48:56 -05:00
Logger :: notice ( 'Author public contact unknown.' , [ 'author-link' => $parent [ 'author-link' ], 'item-id' => $parent [ 'id' ]]);
2019-02-25 03:16:18 -05:00
} else {
2019-02-24 15:48:56 -05:00
$implicit_mentions [] = $parent_author [ 'url' ];
$implicit_mentions [] = $parent_author [ 'nurl' ];
$implicit_mentions [] = $parent_author [ 'alias' ];
}
2019-02-08 22:57:35 -05:00
2019-02-24 15:48:56 -05:00
if ( ! empty ( $parent [ 'alias' ])) {
2019-02-08 22:57:35 -05:00
$implicit_mentions [] = $parent [ 'alias' ];
}
foreach ( $parent_terms as $term ) {
2020-07-15 13:06:48 -04:00
$contact = Contact :: getByURL ( $term [ 'url' ], false , [ 'url' , 'nurl' , 'alias' ]);
2020-06-16 16:41:34 -04:00
if ( ! empty ( $contact [ 'url' ])) {
2019-02-10 13:42:51 -05:00
$implicit_mentions [] = $contact [ 'url' ];
$implicit_mentions [] = $contact [ 'nurl' ];
$implicit_mentions [] = $contact [ 'alias' ];
}
2019-02-08 22:57:35 -05:00
}
return $implicit_mentions ;
}
/**
* Strips from the body prepended implicit mentions
*
* @ param string $body
2020-05-09 04:55:10 -04:00
* @ param array $parent
2019-02-08 22:57:35 -05:00
* @ return string
*/
2020-05-09 04:55:10 -04:00
private static function removeImplicitMentionsFromBody ( string $body , array $parent )
2019-02-08 22:57:35 -05:00
{
2020-01-19 15:21:13 -05:00
if ( DI :: config () -> get ( 'system' , 'disable_implicit_mentions' )) {
2019-02-13 12:26:54 -05:00
return $body ;
2019-02-13 12:23:23 -05:00
}
2020-05-09 04:55:10 -04:00
$potential_mentions = self :: getImplicitMentionList ( $parent );
2019-02-08 22:57:35 -05:00
$kept_mentions = [];
// Extract one prepended mention at a time from the body
2019-03-09 09:04:11 -05:00
while ( preg_match ( '#^(@\[url=([^\]]+)].*?\[\/url]\s)(.*)#is' , $body , $matches )) {
2019-10-16 08:58:09 -04:00
if ( ! in_array ( $matches [ 2 ], $potential_mentions )) {
2019-02-08 22:57:35 -05: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 );
}
2018-10-03 02:15:07 -04:00
}