2018-10-03 02:15:07 -04:00
< ? php
/**
2021-03-29 02:40:20 -04:00
* @ copyright Copyright ( C ) 2010 - 2021 , the Friendica project
2020-02-09 10:18:46 -05:00
*
* @ license GNU AGPL version 3 or any later version
*
* This program is free software : you can redistribute it and / or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation , either version 3 of the
* License , or ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU Affero General Public License for more details .
*
* You should have received a copy of the GNU Affero General Public License
* along with this program . If not , see < https :// www . gnu . org / licenses />.
*
2018-10-03 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-05-06 22:41:59 -04:00
use Friendica\Content\Text\BBCode ;
2018-10-03 02:15:07 -04:00
use Friendica\Database\DBA ;
2019-11-13 11:22:20 -05:00
use Friendica\Content\Text\HTML ;
use Friendica\Content\Text\Markdown ;
2018-10-29 17:20:46 -04:00
use Friendica\Core\Logger ;
2018-10-03 02:15:07 -04:00
use Friendica\Core\Protocol ;
use Friendica\Model\Contact ;
use Friendica\Model\APContact ;
use Friendica\Model\Item ;
2021-01-15 23:14:58 -05:00
use Friendica\Model\Post ;
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-11-08 11:28:29 -05:00
use Friendica\Util\HTTPSignature ;
use Friendica\Util\JsonLD ;
use Friendica\Util\LDSignature ;
use Friendica\Util\Strings ;
2018-10-03 02:15:07 -04:00
/**
2020-01-19 01:05:23 -05:00
* ActivityPub Receiver Protocol class
2018-10-03 05:53:12 -04:00
*
* To - Do :
2020-02-09 10:18:46 -05:00
* @ todo Undo Announce
2018-10-03 05:53:12 -04:00
*
* Check what this is meant to do :
* - Add
* - Block
* - Flag
* - Remove
* - Undo Block
2018-10-03 02:15:07 -04:00
*/
class Receiver
{
2018-10-07 09:37:05 -04:00
const PUBLIC_COLLECTION = 'as:Public' ;
const ACCOUNT_TYPES = [ 'as:Person' , 'as:Organization' , 'as:Service' , 'as:Group' , 'as:Application' ];
2020-03-23 00:43:06 -04:00
const CONTENT_TYPES = [ 'as:Note' , 'as:Article' , 'as:Video' , 'as:Image' , 'as:Event' , 'as:Audio' ];
2018-10-07 09:37:05 -04:00
const ACTIVITY_TYPES = [ 'as:Like' , 'as:Dislike' , 'as:Accept' , 'as:Reject' , 'as:TentativeAccept' ];
2020-09-12 08:12:55 -04:00
const TARGET_UNKNOWN = 0 ;
const TARGET_TO = 1 ;
const TARGET_CC = 2 ;
2020-09-12 13:45:04 -04:00
const TARGET_BTO = 3 ;
const TARGET_BCC = 4 ;
const TARGET_FOLLOWER = 5 ;
2020-09-13 10:15:28 -04:00
const TARGET_ANSWER = 6 ;
2020-09-14 13:48:57 -04:00
const TARGET_GLOBAL = 7 ;
2020-09-12 08:12:55 -04:00
2018-10-03 02:15:07 -04:00
/**
2018-10-06 00:18:40 -04:00
* Checks if the web request is done for the AP protocol
2018-10-03 02:15:07 -04:00
*
2019-01-06 16:06:53 -05:00
* @ return bool is it AP ?
2018-10-03 02:15:07 -04:00
*/
public static function isRequest ()
{
2019-10-16 08:35:14 -04:00
return stristr ( $_SERVER [ 'HTTP_ACCEPT' ] ? ? '' , 'application/activity+json' ) ||
stristr ( $_SERVER [ 'HTTP_ACCEPT' ] ? ? '' , 'application/ld+json' );
2018-10-03 02:15:07 -04:00
}
/**
2018-10-07 09:37:05 -04:00
* Checks incoming message from the inbox
2018-10-03 02:15:07 -04:00
*
2019-01-06 16:06:53 -05:00
* @ param $body
* @ param $header
2018-10-03 02:15:07 -04:00
* @ param integer $uid User ID
2019-01-06 16:06:53 -05:00
* @ throws \Exception
2018-10-03 02:15:07 -04:00
*/
public static function processInbox ( $body , $header , $uid )
{
$activity = json_decode ( $body , true );
if ( empty ( $activity )) {
2019-02-22 23:00:16 -05:00
Logger :: warning ( 'Invalid body.' );
2018-10-03 02:15:07 -04:00
return ;
}
2018-10-07 09:37:05 -04:00
$ldactivity = JsonLD :: compact ( $activity );
2019-04-26 02:17:37 -04:00
$actor = JsonLD :: fetchElement ( $ldactivity , 'as:actor' , '@id' );
2018-10-07 09:37:05 -04:00
2020-09-14 16:58:41 -04:00
$apcontact = APContact :: getByURL ( $actor );
2020-11-24 17:32:52 -05:00
if ( empty ( $apcontact )) {
2021-03-10 09:40:57 -05:00
Logger :: notice ( 'Unable to retrieve AP contact for actor - message is discarded' , [ 'actor' => $actor ]);
return ;
2020-11-24 17:32:52 -05:00
} elseif ( $apcontact [ 'type' ] == 'Application' && $apcontact [ 'nick' ] == 'relay' ) {
2020-09-21 11:17:33 -04:00
self :: processRelayPost ( $ldactivity , $actor );
2020-09-14 16:58:41 -04:00
return ;
2020-11-24 17:32:52 -05:00
} else {
APContact :: unmarkForArchival ( $apcontact );
2020-09-14 16:58:41 -04:00
}
$http_signer = HTTPSignature :: getSigner ( $body , $header );
2021-05-01 08:32:33 -04:00
if ( $http_signer === false ) {
2020-09-14 16:58:41 -04:00
Logger :: warning ( 'Invalid HTTP signature, message will be discarded.' );
return ;
2021-05-01 08:32:33 -04:00
} elseif ( empty ( $http_signer )) {
Logger :: info ( 'Signer is a tombstone. The message will be discarded, the signer account is deleted.' );
return ;
2020-09-14 16:58:41 -04:00
} else {
Logger :: info ( 'Valid HTTP signature' , [ 'signer' => $http_signer ]);
}
$signer = [ $http_signer ];
2019-02-22 23:00:16 -05:00
Logger :: info ( 'Message for user ' . $uid . ' is from actor ' . $actor );
2018-10-07 09:37:05 -04:00
2018-10-03 02:15:07 -04:00
if ( LDSignature :: isSigned ( $activity )) {
$ld_signer = LDSignature :: getSigner ( $activity );
if ( empty ( $ld_signer )) {
2018-10-30 09:58:45 -04:00
Logger :: log ( 'Invalid JSON-LD signature from ' . $actor , Logger :: DEBUG );
2020-09-12 08:12:55 -04:00
} elseif ( $ld_signer != $http_signer ) {
$signer [] = $ld_signer ;
2018-10-03 02:15:07 -04:00
}
if ( ! empty ( $ld_signer && ( $actor == $http_signer ))) {
2018-10-30 09:58:45 -04:00
Logger :: log ( 'The HTTP and the JSON-LD signature belong to ' . $ld_signer , Logger :: DEBUG );
2018-10-03 02:15:07 -04:00
$trust_source = true ;
} elseif ( ! empty ( $ld_signer )) {
2018-10-30 09:58:45 -04:00
Logger :: log ( 'JSON-LD signature is signed by ' . $ld_signer , Logger :: DEBUG );
2018-10-03 02:15:07 -04:00
$trust_source = true ;
} elseif ( $actor == $http_signer ) {
2018-10-30 09:58:45 -04:00
Logger :: log ( 'Bad JSON-LD signature, but HTTP signer fits the actor.' , Logger :: DEBUG );
2018-10-03 02:15:07 -04:00
$trust_source = true ;
} else {
2018-10-30 09:58:45 -04:00
Logger :: log ( 'Invalid JSON-LD signature and the HTTP signer is different.' , Logger :: DEBUG );
2018-10-03 02:15:07 -04:00
$trust_source = false ;
}
} elseif ( $actor == $http_signer ) {
2018-10-30 09:58:45 -04:00
Logger :: log ( 'Trusting post without JSON-LD signature, The actor fits the HTTP signer.' , Logger :: DEBUG );
2018-10-03 02:15:07 -04:00
$trust_source = true ;
} else {
2018-10-30 09:58:45 -04:00
Logger :: log ( 'No JSON-LD signature, different actor.' , Logger :: DEBUG );
2018-10-03 02:15:07 -04:00
$trust_source = false ;
}
2020-09-12 08:12:55 -04:00
self :: processActivity ( $ldactivity , $body , $uid , $trust_source , true , $signer );
2018-10-03 02:15:07 -04:00
}
2020-09-14 16:58:41 -04:00
/**
* Process incoming posts from relays
*
2020-09-21 11:17:33 -04:00
* @ param array $activity
* @ param string $actor
2020-09-14 16:58:41 -04:00
* @ return void
*/
2020-09-21 11:17:33 -04:00
private static function processRelayPost ( array $activity , string $actor )
2020-09-14 16:58:41 -04:00
{
$type = JsonLD :: fetchElement ( $activity , '@type' );
if ( ! $type ) {
Logger :: info ( 'Empty type' , [ 'activity' => $activity ]);
return ;
}
if ( $type != 'as:Announce' ) {
Logger :: info ( 'Not an announcement' , [ 'activity' => $activity ]);
2020-09-15 13:45:19 -04:00
return ;
2020-09-14 16:58:41 -04:00
}
$object_id = JsonLD :: fetchElement ( $activity , 'as:object' , '@id' );
if ( empty ( $object_id )) {
Logger :: info ( 'No object id found' , [ 'activity' => $activity ]);
2020-09-15 13:45:19 -04:00
return ;
2020-09-14 16:58:41 -04:00
}
2020-09-29 01:06:37 -04:00
$contact = Contact :: getByURL ( $actor );
if ( empty ( $contact )) {
Logger :: info ( 'Relay contact not found' , [ 'actor' => $actor ]);
return ;
}
if ( ! in_array ( $contact [ 'rel' ], [ Contact :: SHARING , Contact :: FRIEND ])) {
Logger :: notice ( 'Relay is no sharer' , [ 'actor' => $actor ]);
return ;
}
2020-09-14 16:58:41 -04:00
Logger :: info ( 'Got relayed message id' , [ 'id' => $object_id ]);
$item_id = Item :: searchByLink ( $object_id );
if ( $item_id ) {
Logger :: info ( 'Relayed message already exists' , [ 'id' => $object_id , 'item' => $item_id ]);
return ;
}
2020-09-22 11:48:44 -04:00
$id = Processor :: fetchMissingActivity ( $object_id , [], $actor );
if ( empty ( $id )) {
Logger :: notice ( 'Relayed message had not been fetched' , [ 'id' => $object_id ]);
return ;
}
2020-09-17 00:31:38 -04:00
$item_id = Item :: searchByLink ( $object_id );
if ( $item_id ) {
Logger :: info ( 'Relayed message had been fetched and stored' , [ 'id' => $object_id , 'item' => $item_id ]);
} else {
Logger :: notice ( 'Relayed message had not been stored' , [ 'id' => $object_id ]);
}
2020-09-14 16:58:41 -04:00
}
2018-10-07 16:36:15 -04:00
/**
* Fetches the object type for a given object id
*
2018-11-03 17:37:08 -04:00
* @ param array $activity
* @ param string $object_id Object ID of the the provided object
2019-01-06 16:06:53 -05:00
* @ param integer $uid User ID
2018-10-07 16:36:15 -04:00
*
* @ return string with object type
2019-01-06 16:06:53 -05:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
* @ throws \ImagickException
2018-10-07 16:36:15 -04:00
*/
2018-11-03 17:37:08 -04:00
private static function fetchObjectType ( $activity , $object_id , $uid = 0 )
2018-10-07 16:36:15 -04:00
{
2018-10-27 02:17:17 -04:00
if ( ! empty ( $activity [ 'as:object' ])) {
$object_type = JsonLD :: fetchElement ( $activity [ 'as:object' ], '@type' );
if ( ! empty ( $object_type )) {
return $object_type ;
}
2018-10-07 16:36:15 -04:00
}
2021-01-15 23:14:58 -05:00
if ( Post :: exists ([ 'uri' => $object_id , 'gravity' => [ GRAVITY_PARENT , GRAVITY_COMMENT ]])) {
2018-10-07 16:36:15 -04:00
// We just assume "note" since it doesn't make a difference for the further processing
return 'as:Note' ;
}
$profile = APContact :: getByURL ( $object_id );
if ( ! empty ( $profile [ 'type' ])) {
2020-11-24 17:32:52 -05:00
APContact :: unmarkForArchival ( $profile );
2018-10-07 16:36:15 -04:00
return 'as:' . $profile [ 'type' ];
}
2018-11-03 17:37:08 -04:00
$data = ActivityPub :: fetchContent ( $object_id , $uid );
2018-10-07 16:36:15 -04:00
if ( ! empty ( $data )) {
$object = JsonLD :: compact ( $data );
$type = JsonLD :: fetchElement ( $object , '@type' );
if ( ! empty ( $type )) {
return $type ;
}
}
return null ;
}
2018-10-03 02:15:07 -04:00
/**
2018-10-07 14:41:45 -04:00
* Prepare the object array
2018-10-03 02:15:07 -04:00
*
2020-03-03 03:01:04 -05:00
* @ param array $activity Array with activity data
* @ param integer $uid User ID
* @ param boolean $push Message had been pushed to our system
* @ param boolean $trust_source Do we trust the source ?
2018-10-03 02:15:07 -04:00
*
2018-10-07 14:41:45 -04:00
* @ return array with object data
2019-01-06 16:06:53 -05:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
* @ throws \ImagickException
2018-10-03 02:15:07 -04:00
*/
2020-07-20 00:38:45 -04:00
public static function prepareObjectData ( $activity , $uid , $push , & $trust_source )
2018-10-03 02:15:07 -04:00
{
2020-09-12 08:12:55 -04:00
$id = JsonLD :: fetchElement ( $activity , '@id' );
if ( ! empty ( $id ) && ! $trust_source ) {
$fetched_activity = ActivityPub :: fetchContent ( $id , $uid ? ? 0 );
if ( ! empty ( $fetched_activity )) {
$object = JsonLD :: compact ( $fetched_activity );
$fetched_id = JsonLD :: fetchElement ( $object , '@id' );
if ( $fetched_id == $id ) {
Logger :: info ( 'Activity had been fetched successfully' , [ 'id' => $id ]);
$trust_source = true ;
$activity = $object ;
} else {
Logger :: info ( 'Activity id is not equal' , [ 'id' => $id , 'fetched' => $fetched_id ]);
}
} else {
Logger :: info ( 'Activity could not been fetched' , [ 'id' => $id ]);
}
}
2019-04-26 02:17:37 -04:00
$actor = JsonLD :: fetchElement ( $activity , 'as:actor' , '@id' );
2018-10-03 02:15:07 -04:00
if ( empty ( $actor )) {
2020-09-12 08:12:55 -04:00
Logger :: info ( 'Empty actor' , [ 'activity' => $activity ]);
2018-10-03 02:15:07 -04:00
return [];
}
2018-10-07 14:41:45 -04:00
$type = JsonLD :: fetchElement ( $activity , '@type' );
2018-10-07 09:37:05 -04:00
2018-10-03 02:15:07 -04:00
// Fetch all receivers from to, cc, bto and bcc
2020-09-12 13:45:04 -04:00
$receiverdata = self :: getReceivers ( $activity , $actor );
$receivers = $reception_types = [];
foreach ( $receiverdata as $key => $data ) {
$receivers [ $key ] = $data [ 'uid' ];
2020-09-25 02:47:07 -04:00
$reception_types [ $data [ 'uid' ]] = $data [ 'type' ] ? ? self :: TARGET_UNKNOWN ;
2020-09-12 13:45:04 -04:00
}
2018-10-03 02:15:07 -04:00
// When it is a delivery to a personal inbox we add that user to the receivers
if ( ! empty ( $uid )) {
2020-09-25 02:47:07 -04:00
$additional = [ $uid => $uid ];
$receivers = array_replace ( $receivers , $additional );
if ( empty ( $activity [ 'thread-completion' ]) && ( empty ( $reception_types [ $uid ]) || in_array ( $reception_types [ $uid ], [ self :: TARGET_UNKNOWN , self :: TARGET_FOLLOWER , self :: TARGET_ANSWER , self :: TARGET_GLOBAL ]))) {
2020-09-12 13:45:04 -04:00
$reception_types [ $uid ] = self :: TARGET_BCC ;
}
2018-11-03 17:37:08 -04:00
} else {
// We possibly need some user to fetch private content,
// so we fetch the first out ot the list.
$uid = self :: getFirstUserFromReceivers ( $receivers );
2018-10-03 02:15:07 -04:00
}
2019-04-26 02:17:37 -04:00
$object_id = JsonLD :: fetchElement ( $activity , 'as:object' , '@id' );
2018-10-03 02:15:07 -04:00
if ( empty ( $object_id )) {
2018-10-30 09:58:45 -04:00
Logger :: log ( 'No object found' , Logger :: DEBUG );
2018-10-03 02:15:07 -04:00
return [];
}
2019-06-13 22:58:40 -04:00
if ( ! is_string ( $object_id )) {
Logger :: info ( 'Invalid object id' , [ 'object' => $object_id ]);
return [];
}
2018-11-03 17:37:08 -04:00
$object_type = self :: fetchObjectType ( $activity , $object_id , $uid );
2018-10-07 16:36:15 -04:00
2018-10-03 02:15:07 -04:00
// Fetch the content only on activities where this matters
2021-07-09 15:30:41 -04:00
// We can receive "#emojiReaction" when fetching content from Hubzilla systems
if ( in_array ( $type , [ 'as:Create' , 'as:Update' , 'as:Announce' ]) || strpos ( $type , '#emojiReaction' )) {
2020-09-12 08:12:55 -04:00
// Always fetch on "Announce"
$object_data = self :: fetchObject ( $object_id , $activity [ 'as:object' ], $trust_source && ( $type != 'as:Announce' ), $uid );
2018-10-03 02:15:07 -04:00
if ( empty ( $object_data )) {
2018-10-30 09:58:45 -04:00
Logger :: log ( " Object data couldn't be processed " , Logger :: DEBUG );
2018-10-03 02:15:07 -04:00
return [];
}
2020-02-27 00:01:43 -05:00
2019-04-02 17:10:49 -04:00
$object_data [ 'object_id' ] = $object_id ;
2020-03-04 01:04:27 -05:00
if ( $type == 'as:Announce' ) {
$object_data [ 'push' ] = false ;
} else {
$object_data [ 'push' ] = $push ;
}
2019-05-18 03:00:57 -04:00
// Test if it is an answer to a mail
if ( DBA :: exists ( 'mail' , [ 'uri' => $object_data [ 'reply-to-id' ]])) {
$object_data [ 'directmessage' ] = true ;
} else {
$object_data [ 'directmessage' ] = JsonLD :: fetchElement ( $activity , 'litepub:directMessage' );
}
2019-06-22 18:38:52 -04:00
} elseif ( in_array ( $type , array_merge ( self :: ACTIVITY_TYPES , [ 'as:Follow' ])) && in_array ( $object_type , self :: CONTENT_TYPES )) {
2018-10-03 02:15:07 -04:00
// Create a mostly empty array out of the activity data (instead of the object).
// This way we later don't have to check for the existence of ech individual array element.
2018-10-07 14:41:45 -04:00
$object_data = self :: processObject ( $activity );
2018-10-07 09:37:05 -04:00
$object_data [ 'name' ] = $type ;
2019-04-26 02:17:37 -04:00
$object_data [ 'author' ] = JsonLD :: fetchElement ( $activity , 'as:actor' , '@id' );
2018-10-07 15:42:04 -04:00
$object_data [ 'object_id' ] = $object_id ;
2018-10-03 02:15:07 -04:00
$object_data [ 'object_type' ] = '' ; // Since we don't fetch the object, we don't know the type
2021-01-09 07:59:30 -05:00
$object_data [ 'push' ] = $push ;
2019-05-26 07:20:03 -04:00
} elseif ( in_array ( $type , [ 'as:Add' ])) {
$object_data = [];
$object_data [ 'id' ] = JsonLD :: fetchElement ( $activity , '@id' );
$object_data [ 'target_id' ] = JsonLD :: fetchElement ( $activity , 'as:target' , '@id' );
$object_data [ 'object_id' ] = JsonLD :: fetchElement ( $activity , 'as:object' , '@id' );
$object_data [ 'object_type' ] = JsonLD :: fetchElement ( $activity [ 'as:object' ], '@type' );
$object_data [ 'object_content' ] = JsonLD :: fetchElement ( $activity [ 'as:object' ], 'as:content' , '@type' );
2021-01-09 07:59:30 -05:00
$object_data [ 'push' ] = $push ;
2018-10-03 02:15:07 -04:00
} else {
$object_data = [];
2018-10-07 14:41:45 -04:00
$object_data [ 'id' ] = JsonLD :: fetchElement ( $activity , '@id' );
2019-04-26 02:17:37 -04:00
$object_data [ 'object_id' ] = JsonLD :: fetchElement ( $activity , 'as:object' , '@id' );
$object_data [ 'object_actor' ] = JsonLD :: fetchElement ( $activity [ 'as:object' ], 'as:actor' , '@id' );
2018-10-07 14:41:45 -04:00
$object_data [ 'object_object' ] = JsonLD :: fetchElement ( $activity [ 'as:object' ], 'as:object' );
$object_data [ 'object_type' ] = JsonLD :: fetchElement ( $activity [ 'as:object' ], '@type' );
2021-01-09 07:59:30 -05:00
$object_data [ 'push' ] = $push ;
2018-10-27 02:17:17 -04:00
// An Undo is done on the object of an object, so we need that type as well
2020-05-14 00:53:56 -04:00
if (( $type == 'as:Undo' ) && ! empty ( $object_data [ 'object_object' ])) {
2018-11-03 17:37:08 -04:00
$object_data [ 'object_object_type' ] = self :: fetchObjectType ([], $object_data [ 'object_object' ], $uid );
2018-10-27 02:17:17 -04:00
}
2018-10-03 02:15:07 -04:00
}
2018-10-07 14:41:45 -04:00
$object_data = self :: addActivityFields ( $object_data , $activity );
2018-10-03 02:15:07 -04:00
2018-10-07 16:36:15 -04:00
if ( empty ( $object_data [ 'object_type' ])) {
$object_data [ 'object_type' ] = $object_type ;
}
2018-10-07 11:34:51 -04:00
$object_data [ 'type' ] = $type ;
2018-10-07 13:35:43 -04:00
$object_data [ 'actor' ] = $actor ;
2019-01-10 17:51:03 -05:00
$object_data [ 'item_receiver' ] = $receivers ;
2020-09-25 02:47:07 -04:00
$object_data [ 'receiver' ] = array_replace ( $object_data [ 'receiver' ] ? ? [], $receivers );
$object_data [ 'reception_type' ] = array_replace ( $object_data [ 'reception_type' ] ? ? [], $reception_types );
2020-09-12 08:12:55 -04:00
$author = $object_data [ 'author' ] ? ? $actor ;
if ( ! empty ( $author ) && ! empty ( $object_data [ 'id' ])) {
$author_host = parse_url ( $author , PHP_URL_HOST );
$id_host = parse_url ( $object_data [ 'id' ], PHP_URL_HOST );
if ( $author_host == $id_host ) {
Logger :: info ( 'Valid hosts' , [ 'type' => $type , 'host' => $id_host ]);
} else {
Logger :: notice ( 'Differing hosts on author and id' , [ 'type' => $type , 'author' => $author_host , 'id' => $id_host ]);
$trust_source = false ;
}
}
2018-10-03 02:15:07 -04:00
2018-10-30 09:58:45 -04:00
Logger :: log ( 'Processing ' . $object_data [ 'type' ] . ' ' . $object_data [ 'object_type' ] . ' ' . $object_data [ 'id' ], Logger :: DEBUG );
2018-10-03 02:15:07 -04:00
return $object_data ;
}
2018-11-03 17:37:08 -04:00
/**
2018-11-04 05:51:01 -05:00
* Fetches the first user id from the receiver array
2018-11-03 17:37:08 -04:00
*
* @ param array $receivers Array with receivers
* @ return integer user id ;
*/
public static function getFirstUserFromReceivers ( $receivers )
{
foreach ( $receivers as $receiver ) {
if ( ! empty ( $receiver )) {
return $receiver ;
}
}
return 0 ;
}
2018-10-03 02:15:07 -04:00
/**
2018-10-07 13:17:06 -04:00
* Processes the activity object
2018-10-03 02:15:07 -04:00
*
2018-10-07 13:17:06 -04:00
* @ param array $activity Array with activity data
* @ param string $body
* @ param integer $uid User ID
* @ param boolean $trust_source Do we trust the source ?
2020-03-03 03:01:04 -05:00
* @ param boolean $push Message had been pushed to our system
2019-01-06 16:06:53 -05:00
* @ throws \Exception
2018-10-03 02:15:07 -04:00
*/
2020-09-12 08:12:55 -04:00
public static function processActivity ( $activity , string $body = '' , int $uid = null , bool $trust_source = false , bool $push = false , array $signer = [])
2018-10-03 02:15:07 -04:00
{
2018-10-07 14:41:45 -04:00
$type = JsonLD :: fetchElement ( $activity , '@type' );
2018-10-07 09:37:05 -04:00
if ( ! $type ) {
2020-09-12 08:12:55 -04:00
Logger :: info ( 'Empty type' , [ 'activity' => $activity ]);
2018-10-03 02:15:07 -04:00
return ;
}
2019-04-26 02:17:37 -04:00
if ( ! JsonLD :: fetchElement ( $activity , 'as:object' , '@id' )) {
2020-09-12 08:12:55 -04:00
Logger :: info ( 'Empty object' , [ 'activity' => $activity ]);
2018-10-03 02:15:07 -04:00
return ;
}
2020-09-12 08:12:55 -04:00
$actor = JsonLD :: fetchElement ( $activity , 'as:actor' , '@id' );
if ( empty ( $actor )) {
Logger :: info ( 'Empty actor' , [ 'activity' => $activity ]);
2018-10-03 02:15:07 -04:00
return ;
}
2020-09-12 08:12:55 -04:00
if ( is_array ( $activity [ 'as:object' ])) {
2019-04-26 02:17:37 -04:00
$attributed_to = JsonLD :: fetchElement ( $activity [ 'as:object' ], 'as:attributedTo' , '@id' );
2020-09-12 08:12:55 -04:00
} else {
$attributed_to = '' ;
}
// Test the provided signatures against the actor and "attributedTo"
if ( $trust_source ) {
if ( ! empty ( $attributed_to ) && ! empty ( $actor )) {
$trust_source = ( in_array ( $actor , $signer ) && in_array ( $attributed_to , $signer ));
} else {
$trust_source = in_array ( $actor , $signer );
2018-11-20 15:40:47 -05:00
}
}
2018-10-03 02:15:07 -04:00
// $trust_source is called by reference and is set to true if the content was retrieved successfully
2020-02-27 00:01:43 -05:00
$object_data = self :: prepareObjectData ( $activity , $uid , $push , $trust_source );
2018-10-03 02:15:07 -04:00
if ( empty ( $object_data )) {
2020-09-12 08:12:55 -04:00
Logger :: info ( 'No object data found' , [ 'activity' => $activity ]);
2018-10-03 02:15:07 -04:00
return ;
}
if ( ! $trust_source ) {
2020-09-12 08:12:55 -04:00
Logger :: info ( 'Activity trust could not be achieved.' , [ 'id' => $object_data [ 'object_id' ], 'type' => $type , 'signer' => $signer , 'actor' => $actor , 'attributedTo' => $attributed_to ]);
2018-10-06 09:16:52 -04:00
return ;
2018-10-03 02:15:07 -04:00
}
2020-03-04 01:04:27 -05:00
if ( ! empty ( $body ) && empty ( $object_data [ 'raw' ])) {
2020-02-28 04:21:40 -05:00
$object_data [ 'raw' ] = $body ;
2019-01-19 11:44:15 -05:00
}
2018-10-11 16:08:04 -04:00
2018-10-09 01:04:24 -04:00
// Internal flag for thread completion. See Processor.php
if ( ! empty ( $activity [ 'thread-completion' ])) {
$object_data [ 'thread-completion' ] = $activity [ 'thread-completion' ];
}
2020-09-21 08:31:20 -04:00
// Internal flag for posts that arrived via relay
if ( ! empty ( $activity [ 'from-relay' ])) {
$object_data [ 'from-relay' ] = $activity [ 'from-relay' ];
}
2021-07-09 15:30:41 -04:00
2018-10-07 09:37:05 -04:00
switch ( $type ) {
case 'as:Create' :
2019-04-02 17:10:49 -04:00
if ( in_array ( $object_data [ 'object_type' ], self :: CONTENT_TYPES )) {
2020-07-20 00:37:43 -04:00
$item = ActivityPub\Processor :: createItem ( $object_data );
ActivityPub\Processor :: postItem ( $object_data , $item );
2019-04-02 17:10:49 -04:00
}
break ;
2019-05-26 07:20:03 -04:00
case 'as:Add' :
if ( $object_data [ 'object_type' ] == 'as:tag' ) {
ActivityPub\Processor :: addTag ( $object_data );
}
break ;
2018-10-07 09:37:05 -04:00
case 'as:Announce' :
2018-10-27 02:17:17 -04:00
if ( in_array ( $object_data [ 'object_type' ], self :: CONTENT_TYPES )) {
2020-09-25 08:16:08 -04:00
$object_data [ 'thread-completion' ] = Contact :: getIdForURL ( $actor );
2019-07-15 16:05:36 -04:00
2020-07-20 00:37:43 -04:00
$item = ActivityPub\Processor :: createItem ( $object_data );
2020-09-20 00:49:48 -04:00
if ( empty ( $item )) {
return ;
}
2021-04-07 02:02:06 -04:00
$item [ 'post-reason' ] = Item :: PR_ANNOUNCEMENT ;
2020-07-20 00:37:43 -04:00
ActivityPub\Processor :: postItem ( $object_data , $item );
2019-04-02 17:10:49 -04:00
2020-08-09 18:46:18 -04:00
$announce_object_data = self :: processObject ( $activity );
$announce_object_data [ 'name' ] = $type ;
$announce_object_data [ 'author' ] = JsonLD :: fetchElement ( $activity , 'as:actor' , '@id' );
$announce_object_data [ 'object_id' ] = $object_data [ 'object_id' ];
$announce_object_data [ 'object_type' ] = $object_data [ 'object_type' ];
$announce_object_data [ 'push' ] = $push ;
2020-03-04 01:04:27 -05:00
2020-08-09 18:46:18 -04:00
if ( ! empty ( $body )) {
$announce_object_data [ 'raw' ] = $body ;
2019-04-02 17:10:49 -04:00
}
2020-08-09 18:46:18 -04:00
ActivityPub\Processor :: createActivity ( $announce_object_data , Activity :: ANNOUNCE );
2018-10-27 02:17:17 -04:00
}
2018-10-03 02:15:07 -04:00
break ;
2018-10-07 09:37:05 -04:00
case 'as:Like' :
2018-10-27 02:17:17 -04:00
if ( in_array ( $object_data [ 'object_type' ], self :: CONTENT_TYPES )) {
2019-10-23 18:25:43 -04:00
ActivityPub\Processor :: createActivity ( $object_data , Activity :: LIKE );
2018-10-27 02:17:17 -04:00
}
2018-10-03 02:15:07 -04:00
break ;
2018-10-07 09:37:05 -04:00
case 'as:Dislike' :
2018-10-27 02:17:17 -04:00
if ( in_array ( $object_data [ 'object_type' ], self :: CONTENT_TYPES )) {
2019-10-23 18:25:43 -04:00
ActivityPub\Processor :: createActivity ( $object_data , Activity :: DISLIKE );
2018-10-27 02:17:17 -04:00
}
break ;
case 'as:TentativeAccept' :
if ( in_array ( $object_data [ 'object_type' ], self :: CONTENT_TYPES )) {
2019-10-23 18:25:43 -04:00
ActivityPub\Processor :: createActivity ( $object_data , Activity :: ATTENDMAYBE );
2018-10-27 02:17:17 -04:00
}
2018-10-03 02:15:07 -04:00
break ;
2018-10-07 09:37:05 -04:00
case 'as:Update' :
if ( in_array ( $object_data [ 'object_type' ], self :: CONTENT_TYPES )) {
2018-10-27 02:17:17 -04:00
ActivityPub\Processor :: updateItem ( $object_data );
2018-10-07 09:37:05 -04:00
} elseif ( in_array ( $object_data [ 'object_type' ], self :: ACCOUNT_TYPES )) {
2019-01-07 12:09:10 -05:00
ActivityPub\Processor :: updatePerson ( $object_data );
2018-10-03 02:15:07 -04:00
}
break ;
2018-10-07 09:37:05 -04:00
case 'as:Delete' :
if ( $object_data [ 'object_type' ] == 'as:Tombstone' ) {
2019-01-07 12:09:10 -05:00
ActivityPub\Processor :: deleteItem ( $object_data );
2018-10-07 09:37:05 -04:00
} elseif ( in_array ( $object_data [ 'object_type' ], self :: ACCOUNT_TYPES )) {
2019-01-07 12:09:10 -05:00
ActivityPub\Processor :: deletePerson ( $object_data );
2018-10-03 02:15:07 -04:00
}
break ;
2018-10-07 09:37:05 -04:00
case 'as:Follow' :
2018-10-27 02:17:17 -04:00
if ( in_array ( $object_data [ 'object_type' ], self :: ACCOUNT_TYPES )) {
ActivityPub\Processor :: followUser ( $object_data );
2019-01-30 11:30:01 -05:00
} elseif ( in_array ( $object_data [ 'object_type' ], self :: CONTENT_TYPES )) {
$object_data [ 'reply-to-id' ] = $object_data [ 'object_id' ];
2019-10-23 18:25:43 -04:00
ActivityPub\Processor :: createActivity ( $object_data , Activity :: FOLLOW );
2018-10-27 02:17:17 -04:00
}
2018-10-03 02:15:07 -04:00
break ;
2018-10-07 09:37:05 -04:00
case 'as:Accept' :
if ( $object_data [ 'object_type' ] == 'as:Follow' ) {
2018-10-03 05:15:38 -04:00
ActivityPub\Processor :: acceptFollowUser ( $object_data );
2018-10-27 02:17:17 -04:00
} elseif ( in_array ( $object_data [ 'object_type' ], self :: CONTENT_TYPES )) {
2019-10-23 18:25:43 -04:00
ActivityPub\Processor :: createActivity ( $object_data , Activity :: ATTEND );
2018-10-03 02:15:07 -04:00
}
break ;
2018-10-07 09:37:05 -04:00
case 'as:Reject' :
if ( $object_data [ 'object_type' ] == 'as:Follow' ) {
2018-10-03 05:15:38 -04:00
ActivityPub\Processor :: rejectFollowUser ( $object_data );
2018-10-27 02:17:17 -04:00
} elseif ( in_array ( $object_data [ 'object_type' ], self :: CONTENT_TYPES )) {
2019-10-23 18:25:43 -04:00
ActivityPub\Processor :: createActivity ( $object_data , Activity :: ATTENDNO );
2018-10-03 02:15:07 -04:00
}
break ;
2018-10-07 09:37:05 -04:00
case 'as:Undo' :
2018-10-27 02:17:17 -04:00
if (( $object_data [ 'object_type' ] == 'as:Follow' ) &&
in_array ( $object_data [ 'object_object_type' ], self :: ACCOUNT_TYPES )) {
2018-10-03 05:15:38 -04:00
ActivityPub\Processor :: undoFollowUser ( $object_data );
2018-10-27 02:17:17 -04:00
} elseif (( $object_data [ 'object_type' ] == 'as:Accept' ) &&
in_array ( $object_data [ 'object_object_type' ], self :: ACCOUNT_TYPES )) {
ActivityPub\Processor :: rejectFollowUser ( $object_data );
} elseif ( in_array ( $object_data [ 'object_type' ], self :: ACTIVITY_TYPES ) &&
in_array ( $object_data [ 'object_object_type' ], self :: CONTENT_TYPES )) {
2018-10-03 05:15:38 -04:00
ActivityPub\Processor :: undoActivity ( $object_data );
2018-10-03 02:15:07 -04:00
}
break ;
default :
2018-10-30 09:58:45 -04:00
Logger :: log ( 'Unknown activity: ' . $type . ' ' . $object_data [ 'object_type' ], Logger :: DEBUG );
2018-10-03 02:15:07 -04:00
break ;
}
}
/**
2018-10-07 09:37:05 -04:00
* Fetch the receiver list from an activity array
2018-10-03 02:15:07 -04:00
*
2020-03-02 02:57:23 -05:00
* @ param array $activity
* @ param string $actor
* @ param array $tags
2021-07-09 15:30:41 -04:00
* @ param boolean $fetch_unlisted
2018-10-03 02:15:07 -04:00
*
2018-10-07 09:37:05 -04:00
* @ return array with receivers ( user id )
2019-01-06 16:06:53 -05:00
* @ throws \Exception
2018-10-03 02:15:07 -04:00
*/
2020-03-02 02:57:23 -05:00
private static function getReceivers ( $activity , $actor , $tags = [], $fetch_unlisted = false )
2018-10-03 02:15:07 -04:00
{
2020-09-13 10:15:28 -04:00
$reply = $receivers = [];
2018-10-03 02:15:07 -04:00
// When it is an answer, we inherite the receivers from the parent
2019-04-26 02:17:37 -04:00
$replyto = JsonLD :: fetchElement ( $activity , 'as:inReplyTo' , '@id' );
2018-10-03 02:15:07 -04:00
if ( ! empty ( $replyto )) {
2020-09-13 10:15:28 -04:00
$reply = [ $replyto ];
2020-02-02 14:59:14 -05:00
// Fix possibly wrong item URI (could be an answer to a plink uri)
$fixedReplyTo = Item :: getURIByLink ( $replyto );
2020-09-13 10:15:28 -04:00
if ( ! empty ( $fixedReplyTo )) {
$reply [] = $fixedReplyTo ;
}
}
// Fetch all posts that refer to the object id
$object_id = JsonLD :: fetchElement ( $activity , 'as:object' , '@id' );
if ( ! empty ( $object_id )) {
$reply [] = $object_id ;
}
2020-02-02 14:59:14 -05:00
2020-09-13 10:15:28 -04:00
if ( ! empty ( $reply )) {
2021-01-15 23:14:58 -05:00
$parents = Post :: select ([ 'uid' ], [ 'uri' => $reply ]);
while ( $parent = Post :: fetch ( $parents )) {
2020-09-25 02:47:07 -04:00
$receivers [ $parent [ 'uid' ]] = [ 'uid' => $parent [ 'uid' ], 'type' => self :: TARGET_ANSWER ];
2018-10-03 02:15:07 -04:00
}
2021-01-15 23:14:58 -05:00
DBA :: close ( $parents );
2018-10-03 02:15:07 -04:00
}
if ( ! empty ( $actor )) {
2021-06-06 15:28:47 -04:00
$profile = APContact :: getByURL ( $actor );
2019-10-16 08:35:14 -04:00
$followers = $profile [ 'followers' ] ? ? '' ;
2021-06-07 06:21:48 -04:00
$is_forum = ( $actor [ 'type' ] ? ? '' ) == 'Group' ;
2021-06-06 15:28:47 -04:00
Logger :: info ( 'Got actor and followers' , [ 'actor' => $actor , 'followers' => $followers ]);
2018-10-03 02:15:07 -04:00
} else {
2020-09-12 08:12:55 -04:00
Logger :: info ( 'Empty actor' , [ 'activity' => $activity ]);
2018-10-03 02:15:07 -04:00
$followers = '' ;
2021-06-06 15:28:47 -04:00
$is_forum = false ;
2018-10-03 02:15:07 -04:00
}
2020-09-25 02:47:07 -04:00
// We have to prevent false follower assumptions upon thread completions
$follower_target = empty ( $activity [ 'thread-completion' ]) ? self :: TARGET_FOLLOWER : self :: TARGET_UNKNOWN ;
2018-10-07 09:37:05 -04:00
foreach ([ 'as:to' , 'as:cc' , 'as:bto' , 'as:bcc' ] as $element ) {
2019-04-26 02:17:37 -04:00
$receiver_list = JsonLD :: fetchElementArray ( $activity , $element , '@id' );
2018-10-07 09:37:05 -04:00
if ( empty ( $receiver_list )) {
2018-10-03 02:15:07 -04:00
continue ;
}
2018-10-07 09:37:05 -04:00
foreach ( $receiver_list as $receiver ) {
if ( $receiver == self :: PUBLIC_COLLECTION ) {
2020-09-25 02:47:07 -04:00
$receivers [ 0 ] = [ 'uid' => 0 , 'type' => self :: TARGET_GLOBAL ];
2018-10-03 02:15:07 -04:00
}
2021-07-09 15:30:41 -04:00
// Add receiver "-1" for unlisted posts
2020-03-02 02:57:23 -05:00
if ( $fetch_unlisted && ( $receiver == self :: PUBLIC_COLLECTION ) && ( $element == 'as:cc' )) {
2020-09-25 02:47:07 -04:00
$receivers [ - 1 ] = [ 'uid' => - 1 , 'type' => self :: TARGET_GLOBAL ];
2020-03-02 02:57:23 -05:00
}
2020-09-09 12:55:14 -04:00
// Fetch the receivers for the public and the followers collection
2021-06-06 15:28:47 -04:00
if ((( $receiver == $followers ) || (( $receiver == self :: PUBLIC_COLLECTION ) && ! $is_forum )) && ! empty ( $actor )) {
2020-09-25 02:47:07 -04:00
$receivers = self :: getReceiverForActor ( $actor , $tags , $receivers , $follower_target );
2018-10-03 02:15:07 -04:00
continue ;
}
2018-11-03 17:37:08 -04:00
// Fetching all directly addressed receivers
2018-11-08 11:28:29 -05:00
$condition = [ 'self' => true , 'nurl' => Strings :: normaliseLink ( $receiver )];
2018-11-03 17:37:08 -04:00
$contact = DBA :: selectFirst ( 'contact' , [ 'uid' , 'contact-type' ], $condition );
2018-10-03 02:15:07 -04:00
if ( ! DBA :: isResult ( $contact )) {
continue ;
}
2018-11-03 17:37:08 -04:00
// Check if the potential receiver is following the actor
// Exception: The receiver is targetted via "to" or this is a comment
2019-01-06 17:08:35 -05:00
if ((( $element != 'as:to' ) && empty ( $replyto )) || ( $contact [ 'contact-type' ] == Contact :: TYPE_COMMUNITY )) {
2019-07-01 14:00:55 -04:00
$networks = Protocol :: FEDERATED ;
2018-11-08 11:28:29 -05:00
$condition = [ 'nurl' => Strings :: normaliseLink ( $actor ), 'rel' => [ Contact :: SHARING , Contact :: FRIEND ],
2018-11-03 17:37:08 -04:00
'network' => $networks , 'archive' => false , 'pending' => false , 'uid' => $contact [ 'uid' ]];
// Forum posts are only accepted from forum contacts
2019-01-06 17:08:35 -05:00
if ( $contact [ 'contact-type' ] == Contact :: TYPE_COMMUNITY ) {
2018-11-03 17:37:08 -04:00
$condition [ 'rel' ] = [ Contact :: SHARING , Contact :: FRIEND , Contact :: FOLLOWER ];
}
if ( ! DBA :: exists ( 'contact' , $condition )) {
continue ;
}
}
2020-09-25 02:47:07 -04:00
$type = $receivers [ $contact [ 'uid' ]][ 'type' ] ? ? self :: TARGET_UNKNOWN ;
2020-09-14 13:48:57 -04:00
if ( in_array ( $type , [ self :: TARGET_UNKNOWN , self :: TARGET_FOLLOWER , self :: TARGET_ANSWER , self :: TARGET_GLOBAL ])) {
2020-09-12 13:45:04 -04:00
switch ( $element ) {
case 'as:to' :
$type = self :: TARGET_TO ;
break ;
case 'as:cc' :
$type = self :: TARGET_CC ;
break ;
case 'as:bto' :
$type = self :: TARGET_BTO ;
break ;
case 'as:bcc' :
$type = self :: TARGET_BCC ;
break ;
}
2020-09-25 02:47:07 -04:00
$receivers [ $contact [ 'uid' ]] = [ 'uid' => $contact [ 'uid' ], 'type' => $type ];
2020-09-12 13:45:04 -04:00
}
2018-10-03 02:15:07 -04:00
}
}
2018-10-03 05:15:38 -04:00
self :: switchContacts ( $receivers , $actor );
2018-10-03 02:15:07 -04:00
return $receivers ;
}
2018-11-03 17:37:08 -04:00
/**
* Fetch the receiver list of a given actor
*
2020-09-25 02:47:07 -04:00
* @ param string $actor
* @ param array $tags
* @ param array $receivers
* @ param integer $target_type
2018-11-03 17:37:08 -04:00
*
* @ return array with receivers ( user id )
2019-01-06 16:06:53 -05:00
* @ throws \Exception
2018-11-03 17:37:08 -04:00
*/
2020-09-25 02:47:07 -04:00
private static function getReceiverForActor ( $actor , $tags , $receivers , $target_type )
2018-11-03 17:37:08 -04:00
{
2020-09-09 12:55:14 -04:00
$basecondition = [ 'rel' => [ Contact :: SHARING , Contact :: FRIEND , Contact :: FOLLOWER ],
'network' => Protocol :: FEDERATED , 'archive' => false , 'pending' => false ];
2020-09-24 06:26:28 -04:00
$condition = DBA :: mergeConditions ( $basecondition , [ " `nurl` = ? AND `uid` != ? " , Strings :: normaliseLink ( $actor ), 0 ]);
2020-09-09 12:55:14 -04:00
$contacts = DBA :: select ( 'contact' , [ 'uid' , 'rel' ], $condition );
while ( $contact = DBA :: fetch ( $contacts )) {
2020-09-25 02:47:07 -04:00
if ( empty ( $receivers [ $contact [ 'uid' ]]) && self :: isValidReceiverForActor ( $contact , $tags )) {
$receivers [ $contact [ 'uid' ]] = [ 'uid' => $contact [ 'uid' ], 'type' => $target_type ];
2020-09-09 12:55:14 -04:00
}
}
DBA :: close ( $contacts );
// The queries are split because of performance issues
2020-09-24 06:26:28 -04:00
$condition = DBA :: mergeConditions ( $basecondition , [ " `alias` IN (?, ?) AND `uid` != ? " , Strings :: normaliseLink ( $actor ), $actor , 0 ]);
2018-11-03 17:37:08 -04:00
$contacts = DBA :: select ( 'contact' , [ 'uid' , 'rel' ], $condition );
while ( $contact = DBA :: fetch ( $contacts )) {
2020-09-25 02:47:07 -04:00
if ( empty ( $receivers [ $contact [ 'uid' ]]) && self :: isValidReceiverForActor ( $contact , $tags )) {
$receivers [ $contact [ 'uid' ]] = [ 'uid' => $contact [ 'uid' ], 'type' => $target_type ];
2018-11-03 17:37:08 -04:00
}
}
DBA :: close ( $contacts );
return $receivers ;
}
/**
* Tests if the contact is a valid receiver for this actor
*
2019-01-06 16:06:53 -05:00
* @ param array $contact
2018-11-03 17:37:08 -04:00
* @ param string $actor
2019-01-06 16:06:53 -05:00
* @ param array $tags
2018-11-03 17:37:08 -04:00
*
2019-01-06 16:06:53 -05:00
* @ return bool with receivers ( user id )
* @ throws \Exception
2018-11-03 17:37:08 -04:00
*/
2020-09-24 06:26:28 -04:00
private static function isValidReceiverForActor ( $contact , $tags )
2018-11-03 17:37:08 -04:00
{
// Are we following the contact? Then this is a valid receiver
if ( in_array ( $contact [ 'rel' ], [ Contact :: SHARING , Contact :: FRIEND ])) {
return true ;
}
// When the possible receiver isn't a community, then it is no valid receiver
$owner = User :: getOwnerDataById ( $contact [ 'uid' ]);
2019-01-06 17:08:35 -05:00
if ( empty ( $owner ) || ( $owner [ 'contact-type' ] != Contact :: TYPE_COMMUNITY )) {
2018-11-03 17:37:08 -04:00
return false ;
}
// Is the community account tagged?
foreach ( $tags as $tag ) {
if ( $tag [ 'type' ] != 'Mention' ) {
continue ;
}
2020-09-24 06:26:28 -04:00
if ( Strings :: compareLink ( $tag [ 'href' ], $owner [ 'url' ])) {
2018-11-03 17:37:08 -04:00
return true ;
}
}
return false ;
}
2018-10-03 02:15:07 -04:00
/**
2018-10-06 00:18:40 -04:00
* Switches existing contacts to ActivityPub
2018-10-03 02:15:07 -04:00
*
2018-10-05 15:48:48 -04:00
* @ param integer $cid Contact ID
2018-10-03 02:15:07 -04:00
* @ param integer $uid User ID
2019-01-06 16:06:53 -05:00
* @ param string $url Profile URL
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
* @ throws \ImagickException
2018-10-03 02:15:07 -04:00
*/
2018-10-13 14:13:01 -04:00
public static function switchContact ( $cid , $uid , $url )
2018-10-03 02:15:07 -04:00
{
2019-09-11 12:54:13 -04:00
if ( DBA :: exists ( 'contact' , [ 'id' => $cid , 'network' => Protocol :: ACTIVITYPUB ])) {
Logger :: info ( 'Contact is already ActivityPub' , [ 'id' => $cid , 'uid' => $uid , 'url' => $url ]);
return ;
}
2018-10-03 02:15:07 -04:00
2020-08-06 14:53:45 -04:00
if ( Contact :: updateFromProbe ( $cid )) {
2019-09-11 12:54:13 -04:00
Logger :: info ( 'Update was successful' , [ 'id' => $cid , 'uid' => $uid , 'url' => $url ]);
}
2018-10-04 08:57:42 -04:00
2018-10-05 15:48:48 -04:00
// Send a new follow request to be sure that the connection still exists
2019-09-11 12:54:13 -04:00
if (( $uid != 0 ) && DBA :: exists ( 'contact' , [ 'id' => $cid , 'rel' => [ Contact :: SHARING , Contact :: FRIEND ], 'network' => Protocol :: ACTIVITYPUB ])) {
Logger :: info ( 'Contact had been switched to ActivityPub. Sending a new follow request.' , [ 'uid' => $uid , 'url' => $url ]);
2019-07-11 16:11:51 -04:00
ActivityPub\Transmitter :: sendActivity ( 'Follow' , $url , $uid );
2018-10-05 15:48:48 -04:00
}
2018-10-03 02:15:07 -04:00
}
/**
2018-10-16 18:29:28 -04:00
*
2018-10-03 02:15:07 -04:00
*
* @ param $receivers
* @ param $actor
2019-01-06 16:06:53 -05:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
* @ throws \ImagickException
2018-10-03 02:15:07 -04:00
*/
private static function switchContacts ( $receivers , $actor )
{
if ( empty ( $actor )) {
return ;
}
foreach ( $receivers as $receiver ) {
2020-09-12 13:45:04 -04:00
$contact = DBA :: selectFirst ( 'contact' , [ 'id' ], [ 'uid' => $receiver [ 'uid' ], 'network' => Protocol :: OSTATUS , 'nurl' => Strings :: normaliseLink ( $actor )]);
2018-10-03 02:15:07 -04:00
if ( DBA :: isResult ( $contact )) {
2020-09-12 13:45:04 -04:00
self :: switchContact ( $contact [ 'id' ], $receiver [ 'uid' ], $actor );
2018-10-03 02:15:07 -04:00
}
2020-09-12 13:45:04 -04:00
$contact = DBA :: selectFirst ( 'contact' , [ 'id' ], [ 'uid' => $receiver [ 'uid' ], 'network' => Protocol :: OSTATUS , 'alias' => [ Strings :: normaliseLink ( $actor ), $actor ]]);
2018-10-03 02:15:07 -04:00
if ( DBA :: isResult ( $contact )) {
2020-09-12 13:45:04 -04:00
self :: switchContact ( $contact [ 'id' ], $receiver [ 'uid' ], $actor );
2018-10-03 02:15:07 -04:00
}
}
}
/**
2018-10-16 18:29:28 -04:00
*
2018-10-03 02:15:07 -04:00
*
2019-01-06 16:06:53 -05:00
* @ param $object_data
2018-10-03 02:15:07 -04:00
* @ param array $activity
*
2019-01-06 16:06:53 -05:00
* @ return mixed
2018-10-03 02:15:07 -04:00
*/
private static function addActivityFields ( $object_data , $activity )
{
if ( ! empty ( $activity [ 'published' ]) && empty ( $object_data [ 'published' ])) {
2018-10-13 17:37:39 -04:00
$object_data [ 'published' ] = JsonLD :: fetchElement ( $activity , 'as:published' , '@value' );
2018-10-03 02:15:07 -04:00
}
2018-10-06 10:02:23 -04:00
if ( ! empty ( $activity [ 'diaspora:guid' ]) && empty ( $object_data [ 'diaspora:guid' ])) {
2019-04-26 02:17:37 -04:00
$object_data [ 'diaspora:guid' ] = JsonLD :: fetchElement ( $activity , 'diaspora:guid' , '@value' );
2018-10-06 10:02:23 -04:00
}
2018-10-13 17:37:39 -04:00
$object_data [ 'service' ] = JsonLD :: fetchElement ( $activity , 'as:instrument' , 'as:name' , '@type' , 'as:Service' );
2019-04-26 02:17:37 -04:00
$object_data [ 'service' ] = JsonLD :: fetchElement ( $object_data , 'service' , '@value' );
2018-10-03 02:15:07 -04:00
2020-02-02 14:59:14 -05:00
if ( ! empty ( $object_data [ 'object_id' ])) {
// Some systems (e.g. GNU Social) don't reply to the "id" field but the "uri" field.
$objectId = Item :: getURIByLink ( $object_data [ 'object_id' ]);
if ( ! empty ( $objectId ) && ( $object_data [ 'object_id' ] != $objectId )) {
Logger :: notice ( 'Fix wrong object-id' , [ 'received' => $object_data [ 'object_id' ], 'correct' => $objectId ]);
$object_data [ 'object_id' ] = $objectId ;
}
}
2018-10-03 02:15:07 -04:00
return $object_data ;
}
/**
2018-10-07 13:17:06 -04:00
* Fetches the object data from external ressources if needed
2018-10-03 02:15:07 -04:00
*
2018-10-07 13:17:06 -04:00
* @ param string $object_id Object ID of the the provided object
* @ param array $object The provided object array
* @ param boolean $trust_source Do we trust the provided object ?
2018-11-03 17:37:08 -04:00
* @ param integer $uid User ID for the signature that we use to fetch data
2018-10-03 02:15:07 -04:00
*
2019-06-13 19:07:39 -04:00
* @ return array | false with trusted and valid object data
2019-01-06 16:06:53 -05:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
* @ throws \ImagickException
2018-10-03 02:15:07 -04:00
*/
2019-06-13 19:07:39 -04:00
private static function fetchObject ( string $object_id , array $object = [], bool $trust_source = false , int $uid = 0 )
2018-10-03 02:15:07 -04:00
{
2018-10-07 13:17:06 -04:00
// By fetching the type we check if the object is complete.
$type = JsonLD :: fetchElement ( $object , '@type' );
if ( ! $trust_source || empty ( $type )) {
2018-11-03 17:37:08 -04:00
$data = ActivityPub :: fetchContent ( $object_id , $uid );
2018-10-07 11:34:51 -04:00
if ( ! empty ( $data )) {
$object = JsonLD :: compact ( $data );
2018-10-30 09:58:45 -04:00
Logger :: log ( 'Fetched content for ' . $object_id , Logger :: DEBUG );
2018-10-07 11:34:51 -04:00
} else {
2018-10-30 09:58:45 -04:00
Logger :: log ( 'Empty content for ' . $object_id . ', check if content is available locally.' , Logger :: DEBUG );
2018-10-07 11:34:51 -04:00
2021-02-13 14:56:03 -05:00
$item = Post :: selectFirst ( Item :: DELIVER_FIELDLIST , [ 'uri' => $object_id ]);
2018-10-07 11:34:51 -04:00
if ( ! DBA :: isResult ( $item )) {
2018-10-30 09:58:45 -04:00
Logger :: log ( 'Object with url ' . $object_id . ' was not found locally.' , Logger :: DEBUG );
2018-10-07 11:34:51 -04:00
return false ;
}
2018-10-30 09:58:45 -04:00
Logger :: log ( 'Using already stored item for url ' . $object_id , Logger :: DEBUG );
2018-10-07 11:34:51 -04:00
$data = ActivityPub\Transmitter :: createNote ( $item );
$object = JsonLD :: compact ( $data );
2018-10-03 02:15:07 -04:00
}
2020-09-12 08:12:55 -04:00
$id = JsonLD :: fetchElement ( $object , '@id' );
if ( empty ( $id )) {
Logger :: info ( 'Empty id' );
return false ;
}
2021-07-09 15:30:41 -04:00
2020-09-12 08:12:55 -04:00
if ( $id != $object_id ) {
Logger :: info ( 'Fetched id differs from provided id' , [ 'provided' => $object_id , 'fetched' => $id ]);
return false ;
}
2018-10-03 02:15:07 -04:00
} else {
2018-10-30 09:58:45 -04:00
Logger :: log ( 'Using original object for url ' . $object_id , Logger :: DEBUG );
2018-10-03 02:15:07 -04:00
}
2018-10-07 11:34:51 -04:00
$type = JsonLD :: fetchElement ( $object , '@type' );
if ( empty ( $type )) {
2020-09-12 08:12:55 -04:00
Logger :: info ( 'Empty type' );
2018-10-03 02:15:07 -04:00
return false ;
}
2020-09-12 08:12:55 -04:00
// We currently don't handle 'pt:CacheFile', but with this step we avoid logging
if ( in_array ( $type , self :: CONTENT_TYPES ) || ( $type == 'pt:CacheFile' )) {
2020-03-03 17:43:19 -05:00
$object_data = self :: processObject ( $object );
if ( ! empty ( $data )) {
$object_data [ 'raw' ] = json_encode ( $data );
}
return $object_data ;
2018-10-03 02:15:07 -04:00
}
2018-10-07 11:34:51 -04:00
if ( $type == 'as:Announce' ) {
2019-04-26 02:17:37 -04:00
$object_id = JsonLD :: fetchElement ( $object , 'object' , '@id' );
2019-06-13 22:58:40 -04:00
if ( empty ( $object_id ) || ! is_string ( $object_id )) {
2018-10-03 02:15:07 -04:00
return false ;
}
2018-11-03 17:37:08 -04:00
return self :: fetchObject ( $object_id , [], false , $uid );
2018-10-03 02:15:07 -04:00
}
2018-10-30 09:58:45 -04:00
Logger :: log ( 'Unhandled object type: ' . $type , Logger :: DEBUG );
2019-06-13 19:07:39 -04:00
return false ;
2018-10-03 02:15:07 -04:00
}
2021-03-06 03:43:25 -05:00
/**
* Converts the language element ( Used by Peertube )
*
* @ param array $languages
* @ return array Languages
*/
public static function processLanguages ( array $languages )
{
if ( empty ( $languages )) {
return [];
}
$language_list = [];
foreach ( $languages as $language ) {
if ( ! empty ( $language [ '_:identifier' ]) && ! empty ( $language [ 'as:name' ])) {
$language_list [ $language [ '_:identifier' ]] = $language [ 'as:name' ];
}
}
return $language_list ;
}
2018-10-03 02:15:07 -04:00
/**
2018-10-07 09:37:05 -04:00
* Convert tags from JSON - LD format into a simplified format
2018-10-03 02:15:07 -04:00
*
2018-10-07 09:37:05 -04:00
* @ param array $tags Tags in JSON - LD format
2018-10-03 02:15:07 -04:00
*
2018-10-07 09:37:05 -04:00
* @ return array with tags in a simplified format
*/
2020-09-22 11:48:44 -04:00
public static function processTags ( array $tags )
2018-10-07 09:37:05 -04:00
{
$taglist = [];
2018-10-07 11:34:51 -04:00
2018-10-07 09:37:05 -04:00
foreach ( $tags as $tag ) {
2018-10-07 11:34:51 -04:00
if ( empty ( $tag )) {
continue ;
}
2018-11-07 15:34:03 -05:00
$element = [ 'type' => str_replace ( 'as:' , '' , JsonLD :: fetchElement ( $tag , '@type' )),
2019-04-26 02:17:37 -04:00
'href' => JsonLD :: fetchElement ( $tag , 'as:href' , '@id' ),
'name' => JsonLD :: fetchElement ( $tag , 'as:name' , '@value' )];
2018-11-07 15:34:03 -05:00
if ( empty ( $element [ 'type' ])) {
continue ;
}
2020-03-24 19:12:53 -04:00
if ( empty ( $element [ 'href' ])) {
$element [ 'href' ] = $element [ 'name' ];
}
2018-11-07 15:34:03 -05:00
$taglist [] = $element ;
2018-10-07 09:37:05 -04:00
}
return $taglist ;
}
2018-11-07 15:34:03 -05:00
/**
* Convert emojis from JSON - LD format into a simplified format
*
2020-06-04 15:51:14 -04:00
* @ param array $emojis
2018-11-07 15:34:03 -05:00
* @ return array with emojis in a simplified format
*/
2020-06-04 15:51:14 -04:00
private static function processEmojis ( array $emojis )
2018-11-07 15:34:03 -05:00
{
$emojilist = [];
foreach ( $emojis as $emoji ) {
if ( empty ( $emoji ) || ( JsonLD :: fetchElement ( $emoji , '@type' ) != 'toot:Emoji' ) || empty ( $emoji [ 'as:icon' ])) {
continue ;
}
2019-04-26 02:17:37 -04:00
$url = JsonLD :: fetchElement ( $emoji [ 'as:icon' ], 'as:url' , '@id' );
$element = [ 'name' => JsonLD :: fetchElement ( $emoji , 'as:name' , '@value' ),
2018-11-07 15:34:03 -05:00
'href' => $url ];
$emojilist [] = $element ;
}
2020-06-04 15:51:14 -04:00
2018-11-07 15:34:03 -05:00
return $emojilist ;
}
2018-10-07 09:37:05 -04:00
/**
* Convert attachments from JSON - LD format into a simplified format
*
* @ param array $attachments Attachments in JSON - LD format
*
2020-07-20 00:26:42 -04:00
* @ return array Attachments in a simplified format
2018-10-07 09:37:05 -04:00
*/
2020-06-04 15:51:14 -04:00
private static function processAttachments ( array $attachments )
2018-10-07 09:37:05 -04:00
{
$attachlist = [];
2018-10-07 11:34:51 -04:00
2020-06-04 15:51:14 -04:00
// Removes empty values
$attachments = array_filter ( $attachments );
2018-10-07 13:17:06 -04:00
2018-10-07 09:37:05 -04:00
foreach ( $attachments as $attachment ) {
2020-06-04 15:51:14 -04:00
switch ( JsonLD :: fetchElement ( $attachment , '@type' )) {
case 'as:Page' :
$pageUrl = null ;
$pageImage = null ;
$urls = JsonLD :: fetchElementArray ( $attachment , 'as:url' );
foreach ( $urls as $url ) {
// Single scalar URL case
if ( is_string ( $url )) {
$pageUrl = $url ;
continue ;
}
$href = JsonLD :: fetchElement ( $url , 'as:href' , '@id' );
$mediaType = JsonLD :: fetchElement ( $url , 'as:mediaType' , '@value' );
if ( Strings :: startsWith ( $mediaType , 'image' )) {
$pageImage = $href ;
} else {
$pageUrl = $href ;
}
}
2018-10-07 11:34:51 -04:00
2020-06-04 15:51:14 -04:00
$attachlist [] = [
'type' => 'link' ,
'title' => JsonLD :: fetchElement ( $attachment , 'as:name' , '@value' ),
'desc' => JsonLD :: fetchElement ( $attachment , 'as:summary' , '@value' ),
'url' => $pageUrl ,
'image' => $pageImage ,
];
break ;
2020-07-20 00:26:42 -04:00
case 'as:Image' :
$mediaType = JsonLD :: fetchElement ( $attachment , 'as:mediaType' , '@value' );
$imageFullUrl = JsonLD :: fetchElement ( $attachment , 'as:url' , '@id' );
$imagePreviewUrl = null ;
// Multiple URLs?
if ( ! $imageFullUrl && ( $urls = JsonLD :: fetchElementArray ( $attachment , 'as:url' ))) {
$imageVariants = [];
$previewVariants = [];
foreach ( $urls as $url ) {
// Scalar URL, no discrimination possible
if ( is_string ( $url )) {
$imageFullUrl = $url ;
continue ;
}
// Not sure what to do with a different Link media type than the base Image, we skip
if ( $mediaType != JsonLD :: fetchElement ( $url , 'as:mediaType' , '@value' )) {
continue ;
}
$href = JsonLD :: fetchElement ( $url , 'as:href' , '@id' );
// Default URL choice if no discriminating width is provided
$imageFullUrl = $href ? ? $imageFullUrl ;
$width = intval ( JsonLD :: fetchElement ( $url , 'as:width' , '@value' ) ? ? 1 );
if ( $href && $width ) {
$imageVariants [ $width ] = $href ;
// 632 is the ideal width for full screen frio posts, we compute the absolute distance to it
$previewVariants [ abs ( 632 - $width )] = $href ;
}
}
if ( $imageVariants ) {
// Taking the maximum size image
ksort ( $imageVariants );
$imageFullUrl = array_pop ( $imageVariants );
// Taking the minimum number distance to the target distance
ksort ( $previewVariants );
$imagePreviewUrl = array_shift ( $previewVariants );
}
unset ( $imageVariants );
unset ( $previewVariants );
}
$attachlist [] = [
'type' => str_replace ( 'as:' , '' , JsonLD :: fetchElement ( $attachment , '@type' )),
'mediaType' => $mediaType ,
'name' => JsonLD :: fetchElement ( $attachment , 'as:name' , '@value' ),
'url' => $imageFullUrl ,
'image' => $imagePreviewUrl !== $imageFullUrl ? $imagePreviewUrl : null ,
];
break ;
2020-06-04 15:51:14 -04:00
default :
$attachlist [] = [
'type' => str_replace ( 'as:' , '' , JsonLD :: fetchElement ( $attachment , '@type' )),
'mediaType' => JsonLD :: fetchElement ( $attachment , 'as:mediaType' , '@value' ),
'name' => JsonLD :: fetchElement ( $attachment , 'as:name' , '@value' ),
2021-07-04 02:30:54 -04:00
'url' => JsonLD :: fetchElement ( $attachment , 'as:url' , '@id' ),
'height' => JsonLD :: fetchElement ( $attachment , 'as:height' , '@value' ),
'width' => JsonLD :: fetchElement ( $attachment , 'as:width' , '@value' ),
'image' => JsonLD :: fetchElement ( $attachment , 'as:image' , '@id' )
2020-06-04 15:51:14 -04:00
];
}
2018-10-07 09:37:05 -04:00
}
2020-06-04 15:51:14 -04:00
2018-10-07 09:37:05 -04:00
return $attachlist ;
}
2019-11-13 11:22:20 -05:00
/**
* Fetch the original source or content with the " language " Markdown or HTML
*
* @ param array $object
* @ param array $object_data
*
* @ return array
* @ throws \Exception
*/
private static function getSource ( $object , $object_data )
{
$object_data [ 'source' ] = JsonLD :: fetchElement ( $object , 'as:source' , 'as:content' , 'as:mediaType' , 'text/bbcode' );
$object_data [ 'source' ] = JsonLD :: fetchElement ( $object_data , 'source' , '@value' );
if ( ! empty ( $object_data [ 'source' ])) {
return $object_data ;
}
$object_data [ 'source' ] = JsonLD :: fetchElement ( $object , 'as:source' , 'as:content' , 'as:mediaType' , 'text/markdown' );
$object_data [ 'source' ] = JsonLD :: fetchElement ( $object_data , 'source' , '@value' );
if ( ! empty ( $object_data [ 'source' ])) {
$object_data [ 'source' ] = Markdown :: toBBCode ( $object_data [ 'source' ]);
return $object_data ;
}
$object_data [ 'source' ] = JsonLD :: fetchElement ( $object , 'as:source' , 'as:content' , 'as:mediaType' , 'text/html' );
$object_data [ 'source' ] = JsonLD :: fetchElement ( $object_data , 'source' , '@value' );
if ( ! empty ( $object_data [ 'source' ])) {
$object_data [ 'source' ] = HTML :: toBBCode ( $object_data [ 'source' ]);
return $object_data ;
}
return $object_data ;
}
2020-03-23 00:43:06 -04:00
/**
2021-08-05 10:51:42 -04:00
* Extracts a potential alternate URL from a list of additional URL elements
2020-03-23 00:43:06 -04:00
*
2021-08-05 10:51:42 -04:00
* @ param array $urls
* @ return string
2020-03-23 00:43:06 -04:00
*/
2021-08-05 10:51:42 -04:00
private static function extractAlternateUrl ( array $urls ) : string
{
$alternateUrl = '' ;
foreach ( $urls as $key => $url ) {
// Not a list but a single URL element
if ( ! is_numeric ( $key )) {
continue ;
}
2021-07-09 15:30:41 -04:00
2021-08-05 10:51:42 -04:00
if ( empty ( $url [ '@type' ]) || ( $url [ '@type' ] != 'as:Link' )) {
continue ;
}
$href = JsonLD :: fetchElement ( $url , 'as:href' , '@id' );
if ( empty ( $href )) {
continue ;
}
$mediatype = JsonLD :: fetchElement ( $url , 'as:mediaType' );
if ( empty ( $mediatype )) {
continue ;
}
if ( $mediatype == 'text/html' ) {
$alternateUrl = $href ;
}
2020-03-23 00:43:06 -04:00
}
2021-08-05 10:51:42 -04:00
return $alternateUrl ;
}
/**
* Check if the " as:url " element is an array with multiple links
* This is the case with audio and video posts .
* Then the links are added as attachments
*
* @ param array $urls The object URL list
* @ return array an array of attachments
*/
private static function processAttachmentUrls ( array $urls ) : array
{
2020-03-23 00:43:06 -04:00
$attachments = [];
2021-08-05 10:51:42 -04:00
foreach ( $urls as $key => $url ) {
// Not a list but a single URL element
if ( ! is_numeric ( $key )) {
continue ;
}
2020-03-23 00:43:06 -04:00
if ( empty ( $url [ '@type' ]) || ( $url [ '@type' ] != 'as:Link' )) {
continue ;
}
$href = JsonLD :: fetchElement ( $url , 'as:href' , '@id' );
if ( empty ( $href )) {
continue ;
}
$mediatype = JsonLD :: fetchElement ( $url , 'as:mediaType' );
if ( empty ( $mediatype )) {
continue ;
}
$filetype = strtolower ( substr ( $mediatype , 0 , strpos ( $mediatype , '/' )));
if ( $filetype == 'audio' ) {
2021-08-06 12:43:47 -04:00
$attachments [] = [ 'type' => $filetype , 'mediaType' => $mediatype , 'url' => $href , 'height' => null , 'size' => null , 'name' => '' ];
2020-03-23 00:43:06 -04:00
} elseif ( $filetype == 'video' ) {
$height = ( int ) JsonLD :: fetchElement ( $url , 'as:height' , '@value' );
2021-08-05 10:51:42 -04:00
// PeerTube audio-only track
if ( $height === 0 ) {
continue ;
}
2020-10-29 01:20:26 -04:00
$size = ( int ) JsonLD :: fetchElement ( $url , 'pt:size' , '@value' );
2021-08-06 12:43:47 -04:00
$attachments [] = [ 'type' => $filetype , 'mediaType' => $mediatype , 'url' => $href , 'height' => $height , 'size' => $size , 'name' => '' ];
2020-10-29 01:20:26 -04:00
} elseif ( in_array ( $mediatype , [ 'application/x-bittorrent' , 'application/x-bittorrent;x-scheme-handler/magnet' ])) {
$height = ( int ) JsonLD :: fetchElement ( $url , 'as:height' , '@value' );
// For Torrent links we always store the highest resolution
if ( ! empty ( $attachments [ $mediatype ][ 'height' ]) && ( $height < $attachments [ $mediatype ][ 'height' ])) {
continue ;
}
2021-08-06 12:43:47 -04:00
$attachments [ $mediatype ] = [ 'type' => $mediatype , 'mediaType' => $mediatype , 'url' => $href , 'height' => $height , 'size' => null , 'name' => '' ];
2021-08-05 10:51:42 -04:00
} elseif ( $mediatype == 'application/x-mpegURL' ) {
// PeerTube exception, actual video link is in the tags of this URL element
$attachments = array_merge ( $attachments , self :: processAttachmentUrls ( $url [ 'as:tag' ]));
2020-03-23 00:43:06 -04:00
}
}
2021-08-06 12:43:47 -04:00
return array_values ( $attachments );
2020-03-23 00:43:06 -04:00
}
2018-10-07 09:37:05 -04:00
/**
* Fetches data from the object part of an activity
*
* @ param array $object
*
* @ return array
2019-01-06 16:06:53 -05:00
* @ throws \Exception
2018-10-03 02:15:07 -04:00
*/
2021-07-09 15:32:06 -04:00
private static function processObject ( $object )
2018-10-03 02:15:07 -04:00
{
2018-10-07 09:37:05 -04:00
if ( ! JsonLD :: fetchElement ( $object , '@id' )) {
2018-10-03 02:15:07 -04:00
return false ;
}
$object_data = [];
2018-10-07 09:37:05 -04:00
$object_data [ 'object_type' ] = JsonLD :: fetchElement ( $object , '@type' );
$object_data [ 'id' ] = JsonLD :: fetchElement ( $object , '@id' );
2019-04-26 02:17:37 -04:00
$object_data [ 'reply-to-id' ] = JsonLD :: fetchElement ( $object , 'as:inReplyTo' , '@id' );
2018-10-07 09:37:05 -04:00
2019-01-13 04:38:01 -05:00
// An empty "id" field is translated to "./" by the compactor, so we have to check for this content
if ( empty ( $object_data [ 'reply-to-id' ]) || ( $object_data [ 'reply-to-id' ] == './' )) {
2018-10-03 02:15:07 -04:00
$object_data [ 'reply-to-id' ] = $object_data [ 'id' ];
2020-02-02 14:59:14 -05:00
} else {
// Some systems (e.g. GNU Social) don't reply to the "id" field but the "uri" field.
$replyToId = Item :: getURIByLink ( $object_data [ 'reply-to-id' ]);
if ( ! empty ( $replyToId ) && ( $object_data [ 'reply-to-id' ] != $replyToId )) {
Logger :: notice ( 'Fix wrong reply-to' , [ 'received' => $object_data [ 'reply-to-id' ], 'correct' => $replyToId ]);
$object_data [ 'reply-to-id' ] = $replyToId ;
}
2018-10-03 02:15:07 -04:00
}
2018-10-07 09:37:05 -04:00
$object_data [ 'published' ] = JsonLD :: fetchElement ( $object , 'as:published' , '@value' );
$object_data [ 'updated' ] = JsonLD :: fetchElement ( $object , 'as:updated' , '@value' );
if ( empty ( $object_data [ 'updated' ])) {
$object_data [ 'updated' ] = $object_data [ 'published' ];
}
2018-10-03 02:15:07 -04:00
if ( empty ( $object_data [ 'published' ]) && ! empty ( $object_data [ 'updated' ])) {
$object_data [ 'published' ] = $object_data [ 'updated' ];
}
2019-04-26 02:17:37 -04:00
$actor = JsonLD :: fetchElement ( $object , 'as:attributedTo' , '@id' );
2018-10-03 02:15:07 -04:00
if ( empty ( $actor )) {
2019-04-26 02:17:37 -04:00
$actor = JsonLD :: fetchElement ( $object , 'as:actor' , '@id' );
2018-10-03 02:15:07 -04:00
}
2020-05-06 22:41:59 -04:00
$location = JsonLD :: fetchElement ( $object , 'as:location' , 'as:name' , '@type' , 'as:Place' );
$location = JsonLD :: fetchElement ( $location , 'location' , '@value' );
2020-05-07 09:17:16 -04:00
if ( $location ) {
// Some AP software allow formatted text in post location, so we run all the text converters we have to boil
// down to HTML and then finally format to plaintext.
$location = Markdown :: convert ( $location );
2021-07-10 08:58:48 -04:00
$location = BBCode :: toPlaintext ( $location );
2020-05-07 09:17:16 -04:00
}
2020-05-06 22:41:59 -04:00
2020-03-05 03:06:19 -05:00
$object_data [ 'sc:identifier' ] = JsonLD :: fetchElement ( $object , 'sc:identifier' , '@value' );
2019-04-26 02:17:37 -04:00
$object_data [ 'diaspora:guid' ] = JsonLD :: fetchElement ( $object , 'diaspora:guid' , '@value' );
$object_data [ 'diaspora:comment' ] = JsonLD :: fetchElement ( $object , 'diaspora:comment' , '@value' );
$object_data [ 'diaspora:like' ] = JsonLD :: fetchElement ( $object , 'diaspora:like' , '@value' );
2018-10-07 13:35:43 -04:00
$object_data [ 'actor' ] = $object_data [ 'author' ] = $actor ;
2019-04-26 02:17:37 -04:00
$object_data [ 'context' ] = JsonLD :: fetchElement ( $object , 'as:context' , '@id' );
$object_data [ 'conversation' ] = JsonLD :: fetchElement ( $object , 'ostatus:conversation' , '@id' );
2018-10-07 09:37:05 -04:00
$object_data [ 'sensitive' ] = JsonLD :: fetchElement ( $object , 'as:sensitive' );
2019-04-26 02:17:37 -04:00
$object_data [ 'name' ] = JsonLD :: fetchElement ( $object , 'as:name' , '@value' );
$object_data [ 'summary' ] = JsonLD :: fetchElement ( $object , 'as:summary' , '@value' );
$object_data [ 'content' ] = JsonLD :: fetchElement ( $object , 'as:content' , '@value' );
2021-03-06 03:43:25 -05:00
$object_data [ 'mediatype' ] = JsonLD :: fetchElement ( $object , 'as:mediaType' , '@value' );
2019-11-13 11:22:20 -05:00
$object_data = self :: getSource ( $object , $object_data );
2018-10-26 00:13:26 -04:00
$object_data [ 'start-time' ] = JsonLD :: fetchElement ( $object , 'as:startTime' , '@value' );
$object_data [ 'end-time' ] = JsonLD :: fetchElement ( $object , 'as:endTime' , '@value' );
2021-04-08 15:38:16 -04:00
$object_data [ 'adjust' ] = JsonLD :: fetchElement ( $object , 'dfrn:adjust' , '@value' );
2020-05-06 22:41:59 -04:00
$object_data [ 'location' ] = $location ;
2018-10-13 17:37:39 -04:00
$object_data [ 'latitude' ] = JsonLD :: fetchElement ( $object , 'as:location' , 'as:latitude' , '@type' , 'as:Place' );
$object_data [ 'latitude' ] = JsonLD :: fetchElement ( $object_data , 'latitude' , '@value' );
$object_data [ 'longitude' ] = JsonLD :: fetchElement ( $object , 'as:location' , 'as:longitude' , '@type' , 'as:Place' );
$object_data [ 'longitude' ] = JsonLD :: fetchElement ( $object_data , 'longitude' , '@value' );
2020-06-04 15:51:14 -04:00
$object_data [ 'attachments' ] = self :: processAttachments ( JsonLD :: fetchElementArray ( $object , 'as:attachment' ) ? ? []);
$object_data [ 'tags' ] = self :: processTags ( JsonLD :: fetchElementArray ( $object , 'as:tag' ) ? ? []);
2020-11-11 11:19:08 -05:00
$object_data [ 'emojis' ] = self :: processEmojis ( JsonLD :: fetchElementArray ( $object , 'as:tag' , null , '@type' , 'toot:Emoji' ) ? ? []);
2021-03-06 03:43:25 -05:00
$object_data [ 'languages' ] = self :: processLanguages ( JsonLD :: fetchElementArray ( $object , 'sc:inLanguage' ) ? ? []);
2018-10-13 17:37:39 -04:00
$object_data [ 'generator' ] = JsonLD :: fetchElement ( $object , 'as:generator' , 'as:name' , '@type' , 'as:Application' );
2019-04-26 02:17:37 -04:00
$object_data [ 'generator' ] = JsonLD :: fetchElement ( $object_data , 'generator' , '@value' );
$object_data [ 'alternate-url' ] = JsonLD :: fetchElement ( $object , 'as:url' , '@id' );
2018-10-07 09:37:05 -04:00
// Special treatment for Hubzilla links
if ( is_array ( $object_data [ 'alternate-url' ])) {
2019-04-26 02:17:37 -04:00
$object_data [ 'alternate-url' ] = JsonLD :: fetchElement ( $object_data [ 'alternate-url' ], 'as:href' , '@id' );
2018-10-14 13:57:44 -04:00
if ( ! is_string ( $object_data [ 'alternate-url' ])) {
2019-04-26 02:17:37 -04:00
$object_data [ 'alternate-url' ] = JsonLD :: fetchElement ( $object [ 'as:url' ], 'as:href' , '@id' );
2018-10-07 09:37:05 -04:00
}
}
2020-03-23 00:43:06 -04:00
if ( in_array ( $object_data [ 'object_type' ], [ 'as:Audio' , 'as:Video' ])) {
2021-08-05 10:51:42 -04:00
$object_data [ 'alternate-url' ] = self :: extractAlternateUrl ( $object [ 'as:url' ] ? ? []) ? : $object_data [ 'alternate-url' ];
2021-08-06 12:43:47 -04:00
$object_data [ 'attachments' ] = array_merge ( $object_data [ 'attachments' ], self :: processAttachmentUrls ( $object [ 'as:url' ] ? ? []));
2020-03-23 00:43:06 -04:00
}
2020-09-12 13:45:04 -04:00
$receiverdata = self :: getReceivers ( $object , $object_data [ 'actor' ], $object_data [ 'tags' ], true );
2020-09-13 10:15:28 -04:00
$receivers = $reception_types = [];
2020-09-12 13:45:04 -04:00
foreach ( $receiverdata as $key => $data ) {
$receivers [ $key ] = $data [ 'uid' ];
2020-09-13 10:15:28 -04:00
$reception_types [ $data [ 'uid' ]] = $data [ 'type' ] ? ? 0 ;
2020-09-12 13:45:04 -04:00
}
$object_data [ 'receiver' ] = $receivers ;
2020-09-13 10:15:28 -04:00
$object_data [ 'reception_type' ] = $reception_types ;
2020-09-12 13:45:04 -04:00
2020-03-02 02:57:23 -05:00
$object_data [ 'unlisted' ] = in_array ( - 1 , $object_data [ 'receiver' ]);
2020-09-25 02:47:07 -04:00
unset ( $object_data [ 'receiver' ][ - 1 ]);
unset ( $object_data [ 'reception_type' ][ - 1 ]);
2018-10-03 02:15:07 -04:00
// Common object data:
// Unhandled
// @context, type, actor, signature, mediaType, duration, replies, icon
// Also missing: (Defined in the standard, but currently unused)
2018-10-13 17:37:39 -04:00
// audience, preview, endTime, startTime, image
2018-10-03 02:15:07 -04:00
// Data in Notes:
// Unhandled
// contentMap, announcement_count, announcements, context_id, likes, like_count
// inReplyToStatusId, shares, quoteUrl, statusnetConversationId
// Data in video:
// To-Do?
// category, licence, language, commentsEnabled
// Unhandled
// views, waitTranscoding, state, support, subtitleLanguage
// likes, dislikes, shares, comments
return $object_data ;
}
}