2018-10-03 06:15:07 +00:00
< ? php
/**
2020-02-09 15:18:46 +00: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 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 ;
2020-07-17 23:39:12 +00:00
use Friendica\Content\PageInfo ;
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 ;
2018-10-29 21:20:46 +00:00
use Friendica\Core\Logger ;
2018-10-03 06:15:07 +00:00
use Friendica\Core\Protocol ;
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 ;
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 ;
2018-11-08 16:28:29 +00:00
use Friendica\Util\JsonLD ;
use Friendica\Util\Strings ;
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
{
/**
2018-10-06 04:18:40 +00:00
* Converts mentions from Pleroma into the Friendica format
2018-10-03 06:15:07 +00:00
*
* @ param string $body
*
2019-01-06 21:06:53 +00:00
* @ return string converted body
2018-10-03 06:15:07 +00: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 20:34:03 +00:00
/**
* Replaces emojis in the body
*
* @ param array $emojis
* @ param string $body
*
* @ return string with replaced emojis
*/
2019-03-17 22:13:17 +00:00
private static function replaceEmojis ( $body , array $emojis )
2018-11-07 20:34:03 +00:00
{
foreach ( $emojis as $emoji ) {
2018-11-08 07:42:19 +00:00
$replace = '[class=emoji mastodon][img=' . $emoji [ 'href' ] . ']' . $emoji [ 'name' ] . '[/img][/class]' ;
$body = str_replace ( $emoji [ 'name' ], $replace , $body );
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 ];
$filetype = strtolower ( substr ( $attachment [ 'mediaType' ], 0 , strpos ( $attachment [ 'mediaType' ], '/' )));
if ( $filetype == 'image' ) {
$data [ 'type' ] = Post\Media :: IMAGE ;
} elseif ( $filetype == 'video' ) {
$data [ 'type' ] = Post\Media :: VIDEO ;
} elseif ( $filetype == 'audio' ) {
$data [ 'type' ] = Post\Media :: AUDIO ;
} elseif ( in_array ( $attachment [ 'mediaType' ], [ 'application/x-bittorrent' , 'application/x-bittorrent;x-scheme-handler/magnet' ])) {
$data [ 'type' ] = Post\Media :: TORRENT ;
} else {
Logger :: info ( 'Unknown type' , [ 'attachment' => $attachment ]);
return ;
}
$data [ 'url' ] = $attachment [ 'url' ];
$data [ 'mimetype' ] = $attachment [ 'mediaType' ];
$data [ 'height' ] = $attachment [ 'height' ] ? ? null ;
$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
/**
2018-10-07 18:41:45 +00:00
* Add attachment data to the item array
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
*
2019-01-06 21:06:53 +00:00
* @ return array array
2018-10-03 06:15:07 +00:00
*/
2019-11-28 06:34:35 +00:00
private static function constructAttachList ( $activity , $item )
2018-10-03 06:15:07 +00:00
{
2019-11-28 06:34:35 +00:00
if ( empty ( $activity [ 'attachments' ])) {
2018-10-03 06:15:07 +00:00
return $item ;
}
2020-10-29 05:20:26 +00:00
$item [ 'attach' ] = '' ;
2019-11-28 06:34:35 +00:00
foreach ( $activity [ 'attachments' ] as $attach ) {
2020-06-04 19:51:14 +00:00
switch ( $attach [ 'type' ]) {
case 'link' :
2020-07-17 23:39:12 +00: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 19:51:14 +00:00
break ;
default :
2020-10-29 09:03:06 +00:00
self :: storeAttachmentAsMedia ( $item [ 'uri-id' ], $attach );
2020-10-29 05:20:26 +00:00
2020-06-04 19:51:14 +00:00
$filetype = strtolower ( substr ( $attach [ 'mediaType' ], 0 , strpos ( $attach [ 'mediaType' ], '/' )));
if ( $filetype == 'image' ) {
if ( ! empty ( $activity [ 'source' ]) && strpos ( $activity [ 'source' ], $attach [ 'url' ])) {
2020-06-09 16:40:36 +00:00
continue 2 ;
2020-06-04 19:51:14 +00:00
}
2019-05-13 19:56:46 +00:00
2020-07-20 04:27:36 +00: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 19:51:14 +00:00
if ( empty ( $attach [ 'name' ])) {
2020-07-20 04:27:36 +00:00
$item [ 'body' ] .= '[img]' . $attach [ 'url' ] . '[/img]' ;
2020-06-04 19:51:14 +00:00
} else {
2020-07-20 04:27:36 +00:00
$item [ 'body' ] .= '[img=' . $attach [ 'url' ] . ']' . $attach [ 'name' ] . '[/img]' ;
}
if ( ! empty ( $attach [ 'image' ])) {
$item [ 'body' ] .= '[/url]' ;
2020-06-04 19:51:14 +00:00
}
} elseif ( $filetype == 'audio' ) {
if ( ! empty ( $activity [ 'source' ]) && strpos ( $activity [ 'source' ], $attach [ 'url' ])) {
2020-06-09 16:40:36 +00:00
continue 2 ;
2020-06-04 19:51:14 +00:00
}
2020-03-23 04:43:06 +00:00
2020-06-04 19:51:14 +00:00
$item [ 'body' ] .= " \n [audio] " . $attach [ 'url' ] . '[/audio]' ;
} elseif ( $filetype == 'video' ) {
if ( ! empty ( $activity [ 'source' ]) && strpos ( $activity [ 'source' ], $attach [ 'url' ])) {
2020-06-09 16:40:36 +00:00
continue 2 ;
2020-06-04 19:51:14 +00:00
}
2020-03-23 04:43:06 +00:00
2020-06-04 19:51:14 +00:00
$item [ 'body' ] .= " \n [video] " . $attach [ 'url' ] . '[/video]' ;
} else {
2020-10-29 05:20:26 +00:00
if ( ! empty ( $item [ 'attach' ])) {
$item [ 'attach' ] .= ',' ;
2020-06-04 19:51:14 +00:00
} else {
2020-10-29 05:20:26 +00:00
$item [ 'attach' ] = '' ;
2020-06-04 19:51:14 +00:00
}
2020-11-02 05:23:08 +00:00
$item [ 'attach' ] .= Post\Media :: getAttachElement (
$attach [ 'url' ],
$attach [ 'length' ] ? ? 0 ,
$attach [ 'mediaType' ] ? ? '' ,
$attach [ 'name' ] ? ? ''
);
2020-06-04 19:51:14 +00:00
}
2018-10-03 06:15:07 +00:00
}
}
return $item ;
}
2018-10-27 06:17:17 +00:00
/**
* Updates a message
*
2019-01-06 21:06:53 +00:00
* @ param array $activity Activity array
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
2018-10-27 06:17:17 +00:00
*/
public static function updateItem ( $activity )
{
2020-04-15 05:10:40 +00:00
$item = Item :: selectFirst ([ 'uri' , 'uri-id' , 'thr-parent' , 'gravity' ], [ 'uri' => $activity [ 'id' ]]);
2019-02-09 03:57:35 +00:00
if ( ! DBA :: isResult ( $item )) {
2020-04-23 19:57:20 +00:00
Logger :: warning ( 'No existing item, item will be created' , [ 'uri' => $activity [ 'id' ]]);
2020-07-20 04:37:43 +00:00
$item = self :: createItem ( $activity );
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 );
2020-10-29 05:20:26 +00:00
$item = self :: constructAttachList ( $activity , $item );
2019-03-17 13:50:14 +00:00
if ( empty ( $item )) {
return ;
2019-02-09 03:57:35 +00:00
}
2018-10-27 06:17:17 +00:00
Item :: update ( $item , [ 'uri' => $activity [ 'id' ]]);
}
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
*
2019-01-06 21:06:53 +00:00
* @ param array $activity Activity array
2020-07-20 04:37:43 +00:00
* @ return array Internal item
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
* @ throws \ImagickException
2018-10-03 06:15:07 +00:00
*/
2018-10-11 20:08:04 +00:00
public static function createItem ( $activity )
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
}
2019-05-18 07:00:57 +00:00
if ( empty ( $activity [ 'directmessage' ]) && ( $activity [ 'id' ] != $activity [ 'reply-to-id' ]) && ! Item :: exists ([ 'uri' => $activity [ 'reply-to-id' ]])) {
2020-02-02 19:59:14 +00:00
Logger :: notice ( 'Parent not found. Try to refetch it.' , [ 'parent' => $activity [ 'reply-to-id' ]]);
2018-10-03 06:15:07 +00:00
self :: fetchMissingActivity ( $activity [ 'reply-to-id' ], $activity );
}
2019-10-16 12:35:14 +00:00
$item [ 'diaspora_signed_text' ] = $activity [ 'diaspora:comment' ] ? ? '' ;
2018-10-27 14:35:22 +00:00
2020-07-20 04:37:43 +00: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 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' ];
$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 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' ];
$item [ 'causer-id' ] = $item [ 'owner-id' ];
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' ];
$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 07:46:23 +00:00
Logger :: info ( 'Message was not processed' );
2020-07-20 04:37:43 +00:00
return [];
}
$item [ 'plink' ] = $activity [ 'alternate-url' ] ? ? $item [ 'uri' ];
$item = self :: constructAttachList ( $activity , $item );
return $item ;
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
*/
2018-10-03 09:15:38 +00:00
public static function deleteItem ( $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 ]);
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
*/
public static function addTag ( $activity )
{
if ( empty ( $activity [ 'object_content' ]) || empty ( $activity [ 'object_id' ])) {
return ;
}
foreach ( $activity [ 'receiver' ] as $receiver ) {
2020-04-17 13:34:29 +00:00
$item = Item :: selectFirst ([ 'id' , 'uri-id' , 'tag' , '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
*
2018-10-07 18:41:45 +00:00
* @ param array $activity Activity array
2018-10-27 06:17:17 +00:00
* @ 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
*/
2018-10-27 06:17:17 +00:00
public static function createActivity ( $activity , $verb )
2018-10-03 06:15:07 +00:00
{
2020-07-20 04:37:43 +00:00
$item = self :: createItem ( $activity );
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 ;
2019-10-24 22:10:20 +00:00
$item [ 'object-type' ] = Activity\ObjectType :: NOTE ;
2018-10-03 06:15:07 +00:00
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
}
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
2019-01-06 21:06:53 +00:00
* @ throws \Exception
2018-10-26 04:13:26 +00:00
*/
public static function createEvent ( $activity , $item )
{
2018-12-25 03:52:21 +00: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 04:13:26 +00:00
$event [ 'nofinish' ] = empty ( $event [ 'finish' ]);
$event [ 'location' ] = $activity [ 'location' ];
2018-12-25 03:52:21 +00: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 04:13:26 +00: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 17:50:11 +00:00
Logger :: info ( 'Event was stored' , [ 'id' => $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
*/
private static function processContent ( $activity , $item )
{
$item [ 'title' ] = HTML :: toBBCode ( $activity [ 'name' ]);
2020-10-29 05:20:26 +00:00
$content = HTML :: toBBCode ( $activity [ 'content' ]);
2019-03-17 22:13:17 +00:00
2020-10-29 05:20:26 +00:00
if ( ! empty ( $activity [ 'emojis' ])) {
$content = self :: replaceEmojis ( $content , $activity [ 'emojis' ]);
}
2019-03-17 22:13:17 +00:00
2020-10-29 05:20:26 +00:00
$content = self :: convertMentions ( $content );
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 ;
} else {
2019-05-16 05:44:59 +00:00
if ( empty ( $activity [ 'directmessage' ]) && ( $item [ 'thr-parent' ] != $item [ 'uri' ]) && ( $item [ 'gravity' ] == GRAVITY_COMMENT )) {
2019-03-17 13:50:14 +00:00
$item_private = ! in_array ( 0 , $activity [ 'item_receiver' ]);
2020-05-02 05:14:30 +00:00
$parent = Item :: selectFirst ([ 'id' , 'uri-id' , 'private' , 'author-link' , 'alias' ], [ 'uri' => $item [ 'thr-parent' ]]);
2019-03-17 13:50:14 +00:00
if ( ! DBA :: isResult ( $parent )) {
2019-03-17 13:56:47 +00:00
Logger :: warning ( 'Unknown parent item.' , [ 'uri' => $item [ 'thr-parent' ]]);
2019-03-17 13:50:14 +00:00
return false ;
}
2020-06-27 10:35:45 +00:00
if ( $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
}
$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
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
/**
* 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 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 ;
2020-09-25 06:47:07 +00:00
ksort ( $activity [ 'receiver' ]);
2019-02-10 18:42:51 +00:00
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 :
$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 14:15:28 +00:00
case Receiver :: TARGET_ANSWER :
$item [ 'post-type' ] = Item :: PT_COMMENT ;
break ;
2020-09-14 17:48:57 +00:00
case Receiver :: TARGET_GLOBAL :
$item [ 'post-type' ] = Item :: PT_GLOBAL ;
break ;
2020-09-12 17:45:04 +00:00
default :
$item [ 'post-type' ] = Item :: PT_ARTICLE ;
}
2020-09-25 06:47:07 +00: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 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 ;
}
2020-01-18 15:50:57 +00:00
if ( DI :: pConfig () -> get ( $receiver , 'system' , 'accept_only_sharer' , false ) && ( $receiver != 0 ) && ( $item [ 'gravity' ] == GRAVITY_PARENT )) {
2019-07-17 21:37:13 +00:00
$skip = ! Contact :: isSharingByURL ( $activity [ 'author' ], $receiver );
2020-07-20 04:37:43 +00:00
if ( $skip && (( $activity [ 'type' ] == 'as:Announce' ) || ( $item [ 'isForum' ] ? ? false ))) {
2019-07-17 21:37:13 +00: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 14:02:49 +00:00
if (( $item [ 'gravity' ] != GRAVITY_ACTIVITY ) && ( $activity [ 'object_type' ] == 'as:Event' )) {
2018-10-26 04:13:26 +00:00
self :: createEvent ( $activity , $item );
}
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 ]);
} else {
Logger :: notice ( 'Item insertion aborted' , [ 'user' => $item [ 'uid' ]]);
}
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
2019-02-10 18:42:51 +00:00
// Store send a follow request for every reshare - but only when the item had been stored
2020-03-02 07:57:23 +00:00
if ( $stored && ( $item [ 'private' ] != Item :: PRIVATE ) && ( $item [ 'gravity' ] == GRAVITY_PARENT ) && ( $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
}
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
}
}
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
*/
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 16:28:15 +00:00
$title = trim ( HTML :: toPlaintext ( BBCode :: convert ( $title , false , BBCode :: API , true ), 0 ));
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
}
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
*
2020-09-22 05:36:01 +00: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 22:30:34 +00:00
* @ return string fetched message URL
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
2018-10-03 06:15:07 +00:00
*/
2020-09-22 05:36:01 +00:00
public static function fetchMissingActivity ( string $url , array $child = [], string $relay_actor = '' )
2018-10-03 06:15:07 +00:00
{
2019-07-17 19:36:32 +00:00
if ( ! empty ( $child [ 'receiver' ])) {
$uid = ActivityPub\Receiver :: getFirstUserFromReceivers ( $child [ 'receiver' ]);
} else {
$uid = 0 ;
}
2018-11-03 21:37:08 +00:00
$object = ActivityPub :: fetchContent ( $url , $uid );
2018-10-03 06:15:07 +00:00
if ( empty ( $object )) {
2018-10-29 21:20:46 +00:00
Logger :: log ( 'Activity ' . $url . ' was not fetchable, aborting.' );
2020-01-20 22:30:34 +00:00
return '' ;
2018-10-03 06:15:07 +00:00
}
2018-12-29 09:53:31 +00:00
if ( empty ( $object [ 'id' ])) {
Logger :: log ( 'Activity ' . $url . ' has got not id, aborting. ' . json_encode ( $object ));
2020-01-20 22:30:34 +00:00
return '' ;
2018-12-29 09:53:31 +00:00
}
2020-09-14 20:58:41 +00:00
if ( ! empty ( $object [ 'actor' ])) {
$object_actor = $object [ 'actor' ];
2019-07-17 19:36:32 +00:00
} elseif ( ! empty ( $object [ 'attributedTo' ])) {
2020-09-14 20:58:41 +00:00
$object_actor = $object [ 'attributedTo' ];
2019-07-17 19:36:32 +00:00
} else {
// Shouldn't happen
2020-09-14 20:58:41 +00:00
$object_actor = '' ;
}
$signer = [ $object_actor ];
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 = [];
$activity [ '@context' ] = $object [ '@context' ];
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
2020-09-25 12:16:08 +00: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 05:04:24 +00:00
2020-09-22 15:48:44 +00:00
if ( ! empty ( $relay_actor ) && ! self :: acceptIncomingMessage ( $ldactivity , $object [ 'id' ])) {
return '' ;
}
2020-09-14 20:58:41 +00:00
ActivityPub\Receiver :: processActivity ( $ldactivity , json_encode ( $activity ), $uid , true , false , $signer );
2020-03-03 22:43:19 +00:00
2020-01-20 22:30:34 +00:00
Logger :: notice ( 'Activity had been fetched and processed.' , [ 'url' => $url , '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
*/
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 ;
}
$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 ;
}
2020-10-02 03:35:22 +00:00
$attributed_to = JsonLD :: fetchElement ( $activity [ 'as:object' ], 'as:attributedTo' , '@id' );
$authorid = Contact :: getIdForURL ( $attributed_to );
2020-09-30 17:37:46 +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
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
* @ throws \ImagickException
2018-10-03 06:15:07 +00:00
*/
2018-10-03 09:15:38 +00:00
public static function followUser ( $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 )) {
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 );
2019-05-05 11:02:19 +00:00
DBA :: update ( 'contact' , [ 'hub-verify' => $activity [ 'id' ], 'protocol' => Protocol :: ACTIVITYPUB ], [ 'id' => $cid ]);
2018-10-05 06:35:50 +00:00
$contact = DBA :: selectFirst ( 'contact' , [], [ 'id' => $cid , 'network' => Protocol :: NATIVE_SUPPORT ]);
2018-10-03 06:15:07 +00:00
} else {
2019-05-19 22:43:19 +00:00
$contact = [];
2018-10-03 06:15:07 +00:00
}
2018-10-07 17:35:43 +00:00
$item = [ 'author-id' => Contact :: getIdForURL ( $activity [ 'actor' ]),
'author-link' => $activity [ 'actor' ]];
2018-10-03 06:15:07 +00:00
2019-10-16 12:35:14 +00:00
$note = Strings :: escapeTags ( trim ( $activity [ 'content' ] ? ? '' ));
2019-05-05 09:17:45 +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' ]);
2019-05-19 22:43:19 +00:00
$result = Contact :: addRelationship ( $owner , $contact , $item , false , $note );
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 ;
}
2019-01-09 22:30:26 +00:00
if ( empty ( $contact )) {
2019-05-05 11:02:19 +00:00
DBA :: update ( 'contact' , [ 'hub-verify' => $activity [ 'id' ], 'protocol' => Protocol :: ACTIVITYPUB ], [ 'id' => $cid ]);
2019-01-09 22:30:26 +00:00
}
2018-10-29 21:20:46 +00:00
Logger :: log ( 'Follow user ' . $uid . ' from contact ' . $cid . ' with id ' . $activity [ '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
*/
2018-10-03 09:15:38 +00:00
public static function updatePerson ( $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' ]);
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
2019-01-06 21:06:53 +00:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
2018-10-03 06:15:07 +00:00
*/
2018-10-03 09:15:38 +00:00
public static function deletePerson ( $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' ]]);
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
*/
2018-10-03 09:15:38 +00:00
public static function acceptFollowUser ( $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 ];
DBA :: update ( 'contact' , $fields , $condition );
2020-06-28 17:50:11 +00:00
Logger :: info ( 'Accept contact request' , [ 'contact' => $cid , 'user' => $uid ]);
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
*/
2018-10-03 09:15:38 +00:00
public static function rejectFollowUser ( $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 );
2019-05-02 20:04:15 +00:00
if ( DBA :: exists ( 'contact' , [ 'id' => $cid , '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 ]);
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
}
}
/**
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
*/
2018-10-03 09:15:38 +00:00
public static function undoActivity ( $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 ]);
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
*/
2018-10-03 09:15:38 +00:00
public static function undoFollowUser ( $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 ;
}
Contact :: removeFollower ( $owner , $contact );
2020-06-28 17:50:11 +00:00
Logger :: info ( 'Undo following request' , [ 'contact' => $cid , 'user' => $uid ]);
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
2019-01-06 21:06:53 +00:00
* @ throws \Exception
2018-10-09 19:58:15 +00:00
*/
private static function switchContact ( $cid )
{
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
*/
private static function getImplicitMentionList ( array $parent )
{
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' ])) {
2019-02-24 20:48:56 +00:00
Logger :: notice ( 'Author public contact unknown.' , [ 'author-link' => $parent [ 'author-link' ], 'item-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
*/
2020-05-09 08:55:10 +00:00
private static function removeImplicitMentionsFromBody ( string $body , array $parent )
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 );
}
2018-10-03 06:15:07 +00:00
}