2018-10-03 08:15:07 +02:00
< ? php
/**
2020-02-09 16:18:46 +01: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 08:15:07 +02:00
*/
2020-02-09 16:18:46 +01:00
2018-10-03 08:15:07 +02:00
namespace Friendica\Protocol\ActivityPub ;
2020-05-07 04:41:59 +02:00
use Friendica\Content\Text\BBCode ;
2018-10-03 08:15:07 +02:00
use Friendica\Database\DBA ;
2019-11-13 17:22:20 +01:00
use Friendica\Content\Text\HTML ;
use Friendica\Content\Text\Markdown ;
2018-10-29 22:20:46 +01:00
use Friendica\Core\Logger ;
2018-10-03 08:15:07 +02:00
use Friendica\Core\Protocol ;
use Friendica\Model\Contact ;
use Friendica\Model\APContact ;
use Friendica\Model\Item ;
use Friendica\Model\User ;
2019-10-24 00:25:43 +02:00
use Friendica\Protocol\Activity ;
2018-10-03 08:15:07 +02:00
use Friendica\Protocol\ActivityPub ;
2018-11-08 17:28:29 +01:00
use Friendica\Util\HTTPSignature ;
use Friendica\Util\JsonLD ;
use Friendica\Util\LDSignature ;
use Friendica\Util\Strings ;
2018-10-03 08:15:07 +02:00
/**
2020-01-19 07:05:23 +01:00
* ActivityPub Receiver Protocol class
2018-10-03 11:53:12 +02:00
*
* To - Do :
2020-02-09 16:18:46 +01:00
* @ todo Undo Announce
2018-10-03 11:53:12 +02:00
*
* Check what this is meant to do :
* - Add
* - Block
* - Flag
* - Remove
* - Undo Block
2018-10-03 08:15:07 +02:00
*/
class Receiver
{
2018-10-07 15:37:05 +02:00
const PUBLIC_COLLECTION = 'as:Public' ;
const ACCOUNT_TYPES = [ 'as:Person' , 'as:Organization' , 'as:Service' , 'as:Group' , 'as:Application' ];
2020-03-23 05:43:06 +01:00
const CONTENT_TYPES = [ 'as:Note' , 'as:Article' , 'as:Video' , 'as:Image' , 'as:Event' , 'as:Audio' ];
2018-10-07 15:37:05 +02:00
const ACTIVITY_TYPES = [ 'as:Like' , 'as:Dislike' , 'as:Accept' , 'as:Reject' , 'as:TentativeAccept' ];
2020-09-12 14:12:55 +02:00
const TARGET_UNKNOWN = 0 ;
const TARGET_TO = 1 ;
const TARGET_CC = 2 ;
2020-09-12 19:45:04 +02:00
const TARGET_BTO = 3 ;
const TARGET_BCC = 4 ;
const TARGET_FOLLOWER = 5 ;
2020-09-13 16:15:28 +02:00
const TARGET_ANSWER = 6 ;
2020-09-14 19:48:57 +02:00
const TARGET_GLOBAL = 7 ;
2020-09-12 14:12:55 +02:00
2018-10-03 08:15:07 +02:00
/**
2018-10-06 06:18:40 +02:00
* Checks if the web request is done for the AP protocol
2018-10-03 08:15:07 +02:00
*
2019-01-06 22:06:53 +01:00
* @ return bool is it AP ?
2018-10-03 08:15:07 +02:00
*/
public static function isRequest ()
{
2019-10-16 14:35:14 +02:00
return stristr ( $_SERVER [ 'HTTP_ACCEPT' ] ? ? '' , 'application/activity+json' ) ||
stristr ( $_SERVER [ 'HTTP_ACCEPT' ] ? ? '' , 'application/ld+json' );
2018-10-03 08:15:07 +02:00
}
/**
2018-10-07 15:37:05 +02:00
* Checks incoming message from the inbox
2018-10-03 08:15:07 +02:00
*
2019-01-06 22:06:53 +01:00
* @ param $body
* @ param $header
2018-10-03 08:15:07 +02:00
* @ param integer $uid User ID
2019-01-06 22:06:53 +01:00
* @ throws \Exception
2018-10-03 08:15:07 +02:00
*/
public static function processInbox ( $body , $header , $uid )
{
$activity = json_decode ( $body , true );
if ( empty ( $activity )) {
2019-02-23 05:00:16 +01:00
Logger :: warning ( 'Invalid body.' );
2018-10-03 08:15:07 +02:00
return ;
}
2018-10-07 15:37:05 +02:00
$ldactivity = JsonLD :: compact ( $activity );
2019-04-26 08:17:37 +02:00
$actor = JsonLD :: fetchElement ( $ldactivity , 'as:actor' , '@id' );
2018-10-07 15:37:05 +02:00
2020-09-14 22:58:41 +02:00
$apcontact = APContact :: getByURL ( $actor );
2020-11-24 23:32:52 +01:00
if ( empty ( $apcontact )) {
Logger :: notice ( 'Unable to retrieve AP contact for actor' , [ 'actor' => $actor ]);
} elseif ( $apcontact [ 'type' ] == 'Application' && $apcontact [ 'nick' ] == 'relay' ) {
2020-09-21 17:17:33 +02:00
self :: processRelayPost ( $ldactivity , $actor );
2020-09-14 22:58:41 +02:00
return ;
2020-11-24 23:32:52 +01:00
} else {
APContact :: unmarkForArchival ( $apcontact );
2020-09-14 22:58:41 +02:00
}
$http_signer = HTTPSignature :: getSigner ( $body , $header );
if ( empty ( $http_signer )) {
Logger :: warning ( 'Invalid HTTP signature, message will be discarded.' );
return ;
} else {
Logger :: info ( 'Valid HTTP signature' , [ 'signer' => $http_signer ]);
}
$signer = [ $http_signer ];
2019-02-23 05:00:16 +01:00
Logger :: info ( 'Message for user ' . $uid . ' is from actor ' . $actor );
2018-10-07 15:37:05 +02:00
2018-10-03 08:15:07 +02:00
if ( LDSignature :: isSigned ( $activity )) {
$ld_signer = LDSignature :: getSigner ( $activity );
if ( empty ( $ld_signer )) {
2018-10-30 14:58:45 +01:00
Logger :: log ( 'Invalid JSON-LD signature from ' . $actor , Logger :: DEBUG );
2020-09-12 14:12:55 +02:00
} elseif ( $ld_signer != $http_signer ) {
$signer [] = $ld_signer ;
2018-10-03 08:15:07 +02:00
}
if ( ! empty ( $ld_signer && ( $actor == $http_signer ))) {
2018-10-30 14:58:45 +01:00
Logger :: log ( 'The HTTP and the JSON-LD signature belong to ' . $ld_signer , Logger :: DEBUG );
2018-10-03 08:15:07 +02:00
$trust_source = true ;
} elseif ( ! empty ( $ld_signer )) {
2018-10-30 14:58:45 +01:00
Logger :: log ( 'JSON-LD signature is signed by ' . $ld_signer , Logger :: DEBUG );
2018-10-03 08:15:07 +02:00
$trust_source = true ;
} elseif ( $actor == $http_signer ) {
2018-10-30 14:58:45 +01:00
Logger :: log ( 'Bad JSON-LD signature, but HTTP signer fits the actor.' , Logger :: DEBUG );
2018-10-03 08:15:07 +02:00
$trust_source = true ;
} else {
2018-10-30 14:58:45 +01:00
Logger :: log ( 'Invalid JSON-LD signature and the HTTP signer is different.' , Logger :: DEBUG );
2018-10-03 08:15:07 +02:00
$trust_source = false ;
}
} elseif ( $actor == $http_signer ) {
2018-10-30 14:58:45 +01:00
Logger :: log ( 'Trusting post without JSON-LD signature, The actor fits the HTTP signer.' , Logger :: DEBUG );
2018-10-03 08:15:07 +02:00
$trust_source = true ;
} else {
2018-10-30 14:58:45 +01:00
Logger :: log ( 'No JSON-LD signature, different actor.' , Logger :: DEBUG );
2018-10-03 08:15:07 +02:00
$trust_source = false ;
}
2020-09-12 14:12:55 +02:00
self :: processActivity ( $ldactivity , $body , $uid , $trust_source , true , $signer );
2018-10-03 08:15:07 +02:00
}
2020-09-14 22:58:41 +02:00
/**
* Process incoming posts from relays
*
2020-09-21 17:17:33 +02:00
* @ param array $activity
* @ param string $actor
2020-09-14 22:58:41 +02:00
* @ return void
*/
2020-09-21 17:17:33 +02:00
private static function processRelayPost ( array $activity , string $actor )
2020-09-14 22:58:41 +02: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 19:45:19 +02:00
return ;
2020-09-14 22:58:41 +02:00
}
$object_id = JsonLD :: fetchElement ( $activity , 'as:object' , '@id' );
if ( empty ( $object_id )) {
Logger :: info ( 'No object id found' , [ 'activity' => $activity ]);
2020-09-15 19:45:19 +02:00
return ;
2020-09-14 22:58:41 +02:00
}
2020-09-29 07:06:37 +02: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 22:58:41 +02: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 17:48:44 +02: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 06:31:38 +02: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 22:58:41 +02:00
}
2018-10-07 22:36:15 +02:00
/**
* Fetches the object type for a given object id
*
2018-11-03 22:37:08 +01:00
* @ param array $activity
* @ param string $object_id Object ID of the the provided object
2019-01-06 22:06:53 +01:00
* @ param integer $uid User ID
2018-10-07 22:36:15 +02:00
*
* @ return string with object type
2019-01-06 22:06:53 +01:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
* @ throws \ImagickException
2018-10-07 22:36:15 +02:00
*/
2018-11-03 22:37:08 +01:00
private static function fetchObjectType ( $activity , $object_id , $uid = 0 )
2018-10-07 22:36:15 +02:00
{
2018-10-27 08:17:17 +02:00
if ( ! empty ( $activity [ 'as:object' ])) {
$object_type = JsonLD :: fetchElement ( $activity [ 'as:object' ], '@type' );
if ( ! empty ( $object_type )) {
return $object_type ;
}
2018-10-07 22:36:15 +02:00
}
if ( Item :: exists ([ 'uri' => $object_id , 'gravity' => [ GRAVITY_PARENT , GRAVITY_COMMENT ]])) {
// 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 23:32:52 +01:00
APContact :: unmarkForArchival ( $profile );
2018-10-07 22:36:15 +02:00
return 'as:' . $profile [ 'type' ];
}
2018-11-03 22:37:08 +01:00
$data = ActivityPub :: fetchContent ( $object_id , $uid );
2018-10-07 22:36:15 +02:00
if ( ! empty ( $data )) {
$object = JsonLD :: compact ( $data );
$type = JsonLD :: fetchElement ( $object , '@type' );
if ( ! empty ( $type )) {
return $type ;
}
}
return null ;
}
2018-10-03 08:15:07 +02:00
/**
2018-10-07 20:41:45 +02:00
* Prepare the object array
2018-10-03 08:15:07 +02:00
*
2020-03-03 09:01:04 +01: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 08:15:07 +02:00
*
2018-10-07 20:41:45 +02:00
* @ return array with object data
2019-01-06 22:06:53 +01:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
* @ throws \ImagickException
2018-10-03 08:15:07 +02:00
*/
2020-07-20 06:38:45 +02:00
public static function prepareObjectData ( $activity , $uid , $push , & $trust_source )
2018-10-03 08:15:07 +02:00
{
2020-09-12 14:12:55 +02: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 08:17:37 +02:00
$actor = JsonLD :: fetchElement ( $activity , 'as:actor' , '@id' );
2018-10-03 08:15:07 +02:00
if ( empty ( $actor )) {
2020-09-12 14:12:55 +02:00
Logger :: info ( 'Empty actor' , [ 'activity' => $activity ]);
2018-10-03 08:15:07 +02:00
return [];
}
2018-10-07 20:41:45 +02:00
$type = JsonLD :: fetchElement ( $activity , '@type' );
2018-10-07 15:37:05 +02:00
2018-10-03 08:15:07 +02:00
// Fetch all receivers from to, cc, bto and bcc
2020-09-12 19:45:04 +02:00
$receiverdata = self :: getReceivers ( $activity , $actor );
$receivers = $reception_types = [];
foreach ( $receiverdata as $key => $data ) {
$receivers [ $key ] = $data [ 'uid' ];
2020-09-25 08:47:07 +02:00
$reception_types [ $data [ 'uid' ]] = $data [ 'type' ] ? ? self :: TARGET_UNKNOWN ;
2020-09-12 19:45:04 +02:00
}
2018-10-03 08:15:07 +02:00
// When it is a delivery to a personal inbox we add that user to the receivers
if ( ! empty ( $uid )) {
2020-09-25 08:47:07 +02: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 19:45:04 +02:00
$reception_types [ $uid ] = self :: TARGET_BCC ;
}
2018-11-03 22:37:08 +01: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 08:15:07 +02:00
}
2019-04-26 08:17:37 +02:00
$object_id = JsonLD :: fetchElement ( $activity , 'as:object' , '@id' );
2018-10-03 08:15:07 +02:00
if ( empty ( $object_id )) {
2018-10-30 14:58:45 +01:00
Logger :: log ( 'No object found' , Logger :: DEBUG );
2018-10-03 08:15:07 +02:00
return [];
}
2019-06-14 04:58:40 +02:00
if ( ! is_string ( $object_id )) {
Logger :: info ( 'Invalid object id' , [ 'object' => $object_id ]);
return [];
}
2018-11-03 22:37:08 +01:00
$object_type = self :: fetchObjectType ( $activity , $object_id , $uid );
2018-10-07 22:36:15 +02:00
2018-10-03 08:15:07 +02:00
// Fetch the content only on activities where this matters
2018-10-27 08:17:17 +02:00
if ( in_array ( $type , [ 'as:Create' , 'as:Update' , 'as:Announce' ])) {
2020-09-12 14:12:55 +02:00
// Always fetch on "Announce"
$object_data = self :: fetchObject ( $object_id , $activity [ 'as:object' ], $trust_source && ( $type != 'as:Announce' ), $uid );
2018-10-03 08:15:07 +02:00
if ( empty ( $object_data )) {
2018-10-30 14:58:45 +01:00
Logger :: log ( " Object data couldn't be processed " , Logger :: DEBUG );
2018-10-03 08:15:07 +02:00
return [];
}
2020-02-27 06:01:43 +01:00
2019-04-02 23:10:49 +02:00
$object_data [ 'object_id' ] = $object_id ;
2020-03-04 07:04:27 +01:00
if ( $type == 'as:Announce' ) {
$object_data [ 'push' ] = false ;
} else {
$object_data [ 'push' ] = $push ;
}
2019-05-18 09:00:57 +02: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-23 00:38:52 +02:00
} elseif ( in_array ( $type , array_merge ( self :: ACTIVITY_TYPES , [ 'as:Follow' ])) && in_array ( $object_type , self :: CONTENT_TYPES )) {
2018-10-03 08:15:07 +02: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 20:41:45 +02:00
$object_data = self :: processObject ( $activity );
2018-10-07 15:37:05 +02:00
$object_data [ 'name' ] = $type ;
2019-04-26 08:17:37 +02:00
$object_data [ 'author' ] = JsonLD :: fetchElement ( $activity , 'as:actor' , '@id' );
2018-10-07 21:42:04 +02:00
$object_data [ 'object_id' ] = $object_id ;
2018-10-03 08:15:07 +02:00
$object_data [ 'object_type' ] = '' ; // Since we don't fetch the object, we don't know the type
2021-01-09 13:59:30 +01:00
$object_data [ 'push' ] = $push ;
2019-05-26 13:20:03 +02: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 13:59:30 +01:00
$object_data [ 'push' ] = $push ;
2018-10-03 08:15:07 +02:00
} else {
$object_data = [];
2018-10-07 20:41:45 +02:00
$object_data [ 'id' ] = JsonLD :: fetchElement ( $activity , '@id' );
2019-04-26 08:17:37 +02: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 20:41:45 +02: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 13:59:30 +01:00
$object_data [ 'push' ] = $push ;
2018-10-27 08:17:17 +02:00
// An Undo is done on the object of an object, so we need that type as well
2020-05-14 06:53:56 +02:00
if (( $type == 'as:Undo' ) && ! empty ( $object_data [ 'object_object' ])) {
2018-11-03 22:37:08 +01:00
$object_data [ 'object_object_type' ] = self :: fetchObjectType ([], $object_data [ 'object_object' ], $uid );
2018-10-27 08:17:17 +02:00
}
2018-10-03 08:15:07 +02:00
}
2018-10-07 20:41:45 +02:00
$object_data = self :: addActivityFields ( $object_data , $activity );
2018-10-03 08:15:07 +02:00
2018-10-07 22:36:15 +02:00
if ( empty ( $object_data [ 'object_type' ])) {
$object_data [ 'object_type' ] = $object_type ;
}
2018-10-07 17:34:51 +02:00
$object_data [ 'type' ] = $type ;
2018-10-07 19:35:43 +02:00
$object_data [ 'actor' ] = $actor ;
2019-01-10 23:51:03 +01:00
$object_data [ 'item_receiver' ] = $receivers ;
2020-09-25 08:47:07 +02: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 14:12:55 +02: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 08:15:07 +02:00
2018-10-30 14:58:45 +01:00
Logger :: log ( 'Processing ' . $object_data [ 'type' ] . ' ' . $object_data [ 'object_type' ] . ' ' . $object_data [ 'id' ], Logger :: DEBUG );
2018-10-03 08:15:07 +02:00
return $object_data ;
}
2018-11-03 22:37:08 +01:00
/**
2018-11-04 11:51:01 +01:00
* Fetches the first user id from the receiver array
2018-11-03 22:37:08 +01: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 08:15:07 +02:00
/**
2018-10-07 19:17:06 +02:00
* Processes the activity object
2018-10-03 08:15:07 +02:00
*
2018-10-07 19:17:06 +02: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 09:01:04 +01:00
* @ param boolean $push Message had been pushed to our system
2019-01-06 22:06:53 +01:00
* @ throws \Exception
2018-10-03 08:15:07 +02:00
*/
2020-09-12 14:12:55 +02:00
public static function processActivity ( $activity , string $body = '' , int $uid = null , bool $trust_source = false , bool $push = false , array $signer = [])
2018-10-03 08:15:07 +02:00
{
2018-10-07 20:41:45 +02:00
$type = JsonLD :: fetchElement ( $activity , '@type' );
2018-10-07 15:37:05 +02:00
if ( ! $type ) {
2020-09-12 14:12:55 +02:00
Logger :: info ( 'Empty type' , [ 'activity' => $activity ]);
2018-10-03 08:15:07 +02:00
return ;
}
2019-04-26 08:17:37 +02:00
if ( ! JsonLD :: fetchElement ( $activity , 'as:object' , '@id' )) {
2020-09-12 14:12:55 +02:00
Logger :: info ( 'Empty object' , [ 'activity' => $activity ]);
2018-10-03 08:15:07 +02:00
return ;
}
2020-09-12 14:12:55 +02:00
$actor = JsonLD :: fetchElement ( $activity , 'as:actor' , '@id' );
if ( empty ( $actor )) {
Logger :: info ( 'Empty actor' , [ 'activity' => $activity ]);
2018-10-03 08:15:07 +02:00
return ;
}
2020-09-12 14:12:55 +02:00
if ( is_array ( $activity [ 'as:object' ])) {
2019-04-26 08:17:37 +02:00
$attributed_to = JsonLD :: fetchElement ( $activity [ 'as:object' ], 'as:attributedTo' , '@id' );
2020-09-12 14:12:55 +02: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 21:40:47 +01:00
}
}
2018-10-03 08:15:07 +02:00
// $trust_source is called by reference and is set to true if the content was retrieved successfully
2020-02-27 06:01:43 +01:00
$object_data = self :: prepareObjectData ( $activity , $uid , $push , $trust_source );
2018-10-03 08:15:07 +02:00
if ( empty ( $object_data )) {
2020-09-12 14:12:55 +02:00
Logger :: info ( 'No object data found' , [ 'activity' => $activity ]);
2018-10-03 08:15:07 +02:00
return ;
}
if ( ! $trust_source ) {
2020-09-12 14:12:55 +02: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 15:16:52 +02:00
return ;
2018-10-03 08:15:07 +02:00
}
2020-03-04 07:04:27 +01:00
if ( ! empty ( $body ) && empty ( $object_data [ 'raw' ])) {
2020-02-28 10:21:40 +01:00
$object_data [ 'raw' ] = $body ;
2019-01-19 17:44:15 +01:00
}
2018-10-11 22:08:04 +02:00
2018-10-09 07:04:24 +02:00
// Internal flag for thread completion. See Processor.php
if ( ! empty ( $activity [ 'thread-completion' ])) {
$object_data [ 'thread-completion' ] = $activity [ 'thread-completion' ];
}
2020-09-21 14:31:20 +02:00
// Internal flag for posts that arrived via relay
if ( ! empty ( $activity [ 'from-relay' ])) {
$object_data [ 'from-relay' ] = $activity [ 'from-relay' ];
}
2018-10-07 15:37:05 +02:00
switch ( $type ) {
case 'as:Create' :
2019-04-02 23:10:49 +02:00
if ( in_array ( $object_data [ 'object_type' ], self :: CONTENT_TYPES )) {
2020-07-20 06:37:43 +02:00
$item = ActivityPub\Processor :: createItem ( $object_data );
ActivityPub\Processor :: postItem ( $object_data , $item );
2019-04-02 23:10:49 +02:00
}
break ;
2019-05-26 13:20:03 +02:00
case 'as:Add' :
if ( $object_data [ 'object_type' ] == 'as:tag' ) {
ActivityPub\Processor :: addTag ( $object_data );
}
break ;
2018-10-07 15:37:05 +02:00
case 'as:Announce' :
2018-10-27 08:17:17 +02:00
if ( in_array ( $object_data [ 'object_type' ], self :: CONTENT_TYPES )) {
2020-09-25 14:16:08 +02:00
$object_data [ 'thread-completion' ] = Contact :: getIdForURL ( $actor );
2019-07-15 22:05:36 +02:00
2020-07-20 06:37:43 +02:00
$item = ActivityPub\Processor :: createItem ( $object_data );
2020-09-20 06:49:48 +02:00
if ( empty ( $item )) {
return ;
}
2020-09-14 22:58:41 +02:00
$item [ 'post-type' ] = Item :: PT_ANNOUNCEMENT ;
2020-07-20 06:37:43 +02:00
ActivityPub\Processor :: postItem ( $object_data , $item );
2019-04-02 23:10:49 +02:00
2020-08-10 00:46:18 +02: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 07:04:27 +01:00
2020-08-10 00:46:18 +02:00
if ( ! empty ( $body )) {
$announce_object_data [ 'raw' ] = $body ;
2019-04-02 23:10:49 +02:00
}
2020-08-10 00:46:18 +02:00
ActivityPub\Processor :: createActivity ( $announce_object_data , Activity :: ANNOUNCE );
2018-10-27 08:17:17 +02:00
}
2018-10-03 08:15:07 +02:00
break ;
2018-10-07 15:37:05 +02:00
case 'as:Like' :
2018-10-27 08:17:17 +02:00
if ( in_array ( $object_data [ 'object_type' ], self :: CONTENT_TYPES )) {
2019-10-24 00:25:43 +02:00
ActivityPub\Processor :: createActivity ( $object_data , Activity :: LIKE );
2018-10-27 08:17:17 +02:00
}
2018-10-03 08:15:07 +02:00
break ;
2018-10-07 15:37:05 +02:00
case 'as:Dislike' :
2018-10-27 08:17:17 +02:00
if ( in_array ( $object_data [ 'object_type' ], self :: CONTENT_TYPES )) {
2019-10-24 00:25:43 +02:00
ActivityPub\Processor :: createActivity ( $object_data , Activity :: DISLIKE );
2018-10-27 08:17:17 +02:00
}
break ;
case 'as:TentativeAccept' :
if ( in_array ( $object_data [ 'object_type' ], self :: CONTENT_TYPES )) {
2019-10-24 00:25:43 +02:00
ActivityPub\Processor :: createActivity ( $object_data , Activity :: ATTENDMAYBE );
2018-10-27 08:17:17 +02:00
}
2018-10-03 08:15:07 +02:00
break ;
2018-10-07 15:37:05 +02:00
case 'as:Update' :
if ( in_array ( $object_data [ 'object_type' ], self :: CONTENT_TYPES )) {
2018-10-27 08:17:17 +02:00
ActivityPub\Processor :: updateItem ( $object_data );
2018-10-07 15:37:05 +02:00
} elseif ( in_array ( $object_data [ 'object_type' ], self :: ACCOUNT_TYPES )) {
2019-01-07 18:09:10 +01:00
ActivityPub\Processor :: updatePerson ( $object_data );
2018-10-03 08:15:07 +02:00
}
break ;
2018-10-07 15:37:05 +02:00
case 'as:Delete' :
if ( $object_data [ 'object_type' ] == 'as:Tombstone' ) {
2019-01-07 18:09:10 +01:00
ActivityPub\Processor :: deleteItem ( $object_data );
2018-10-07 15:37:05 +02:00
} elseif ( in_array ( $object_data [ 'object_type' ], self :: ACCOUNT_TYPES )) {
2019-01-07 18:09:10 +01:00
ActivityPub\Processor :: deletePerson ( $object_data );
2018-10-03 08:15:07 +02:00
}
break ;
2018-10-07 15:37:05 +02:00
case 'as:Follow' :
2018-10-27 08:17:17 +02:00
if ( in_array ( $object_data [ 'object_type' ], self :: ACCOUNT_TYPES )) {
ActivityPub\Processor :: followUser ( $object_data );
2019-01-30 17:30:01 +01:00
} elseif ( in_array ( $object_data [ 'object_type' ], self :: CONTENT_TYPES )) {
$object_data [ 'reply-to-id' ] = $object_data [ 'object_id' ];
2019-10-24 00:25:43 +02:00
ActivityPub\Processor :: createActivity ( $object_data , Activity :: FOLLOW );
2018-10-27 08:17:17 +02:00
}
2018-10-03 08:15:07 +02:00
break ;
2018-10-07 15:37:05 +02:00
case 'as:Accept' :
if ( $object_data [ 'object_type' ] == 'as:Follow' ) {
2018-10-03 11:15:38 +02:00
ActivityPub\Processor :: acceptFollowUser ( $object_data );
2018-10-27 08:17:17 +02:00
} elseif ( in_array ( $object_data [ 'object_type' ], self :: CONTENT_TYPES )) {
2019-10-24 00:25:43 +02:00
ActivityPub\Processor :: createActivity ( $object_data , Activity :: ATTEND );
2018-10-03 08:15:07 +02:00
}
break ;
2018-10-07 15:37:05 +02:00
case 'as:Reject' :
if ( $object_data [ 'object_type' ] == 'as:Follow' ) {
2018-10-03 11:15:38 +02:00
ActivityPub\Processor :: rejectFollowUser ( $object_data );
2018-10-27 08:17:17 +02:00
} elseif ( in_array ( $object_data [ 'object_type' ], self :: CONTENT_TYPES )) {
2019-10-24 00:25:43 +02:00
ActivityPub\Processor :: createActivity ( $object_data , Activity :: ATTENDNO );
2018-10-03 08:15:07 +02:00
}
break ;
2018-10-07 15:37:05 +02:00
case 'as:Undo' :
2018-10-27 08:17:17 +02:00
if (( $object_data [ 'object_type' ] == 'as:Follow' ) &&
in_array ( $object_data [ 'object_object_type' ], self :: ACCOUNT_TYPES )) {
2018-10-03 11:15:38 +02:00
ActivityPub\Processor :: undoFollowUser ( $object_data );
2018-10-27 08:17:17 +02: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 11:15:38 +02:00
ActivityPub\Processor :: undoActivity ( $object_data );
2018-10-03 08:15:07 +02:00
}
break ;
default :
2018-10-30 14:58:45 +01:00
Logger :: log ( 'Unknown activity: ' . $type . ' ' . $object_data [ 'object_type' ], Logger :: DEBUG );
2018-10-03 08:15:07 +02:00
break ;
}
}
/**
2018-10-07 15:37:05 +02:00
* Fetch the receiver list from an activity array
2018-10-03 08:15:07 +02:00
*
2020-03-02 08:57:23 +01:00
* @ param array $activity
* @ param string $actor
* @ param array $tags
* @ param boolean $fetch_unlisted
2018-10-03 08:15:07 +02:00
*
2018-10-07 15:37:05 +02:00
* @ return array with receivers ( user id )
2019-01-06 22:06:53 +01:00
* @ throws \Exception
2018-10-03 08:15:07 +02:00
*/
2020-03-02 08:57:23 +01:00
private static function getReceivers ( $activity , $actor , $tags = [], $fetch_unlisted = false )
2018-10-03 08:15:07 +02:00
{
2020-09-13 16:15:28 +02:00
$reply = $receivers = [];
2018-10-03 08:15:07 +02:00
// When it is an answer, we inherite the receivers from the parent
2019-04-26 08:17:37 +02:00
$replyto = JsonLD :: fetchElement ( $activity , 'as:inReplyTo' , '@id' );
2018-10-03 08:15:07 +02:00
if ( ! empty ( $replyto )) {
2020-09-13 16:15:28 +02:00
$reply = [ $replyto ];
2020-02-02 20:59:14 +01:00
// Fix possibly wrong item URI (could be an answer to a plink uri)
$fixedReplyTo = Item :: getURIByLink ( $replyto );
2020-09-13 16:15:28 +02: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 20:59:14 +01:00
2020-09-13 16:15:28 +02:00
if ( ! empty ( $reply )) {
$parents = Item :: select ([ 'uid' ], [ 'uri' => $reply ]);
2018-10-03 08:15:07 +02:00
while ( $parent = Item :: fetch ( $parents )) {
2020-09-25 08:47:07 +02:00
$receivers [ $parent [ 'uid' ]] = [ 'uid' => $parent [ 'uid' ], 'type' => self :: TARGET_ANSWER ];
2018-10-03 08:15:07 +02:00
}
}
if ( ! empty ( $actor )) {
$profile = APContact :: getByURL ( $actor );
2019-10-16 14:35:14 +02:00
$followers = $profile [ 'followers' ] ? ? '' ;
2018-10-03 08:15:07 +02:00
2018-10-30 14:58:45 +01:00
Logger :: log ( 'Actor: ' . $actor . ' - Followers: ' . $followers , Logger :: DEBUG );
2018-10-03 08:15:07 +02:00
} else {
2020-09-12 14:12:55 +02:00
Logger :: info ( 'Empty actor' , [ 'activity' => $activity ]);
2018-10-03 08:15:07 +02:00
$followers = '' ;
}
2020-09-25 08:47:07 +02: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 15:37:05 +02:00
foreach ([ 'as:to' , 'as:cc' , 'as:bto' , 'as:bcc' ] as $element ) {
2019-04-26 08:17:37 +02:00
$receiver_list = JsonLD :: fetchElementArray ( $activity , $element , '@id' );
2018-10-07 15:37:05 +02:00
if ( empty ( $receiver_list )) {
2018-10-03 08:15:07 +02:00
continue ;
}
2018-10-07 15:37:05 +02:00
foreach ( $receiver_list as $receiver ) {
if ( $receiver == self :: PUBLIC_COLLECTION ) {
2020-09-25 08:47:07 +02:00
$receivers [ 0 ] = [ 'uid' => 0 , 'type' => self :: TARGET_GLOBAL ];
2018-10-03 08:15:07 +02:00
}
2020-03-02 08:57:23 +01:00
// Add receiver "-1" for unlisted posts
if ( $fetch_unlisted && ( $receiver == self :: PUBLIC_COLLECTION ) && ( $element == 'as:cc' )) {
2020-09-25 08:47:07 +02:00
$receivers [ - 1 ] = [ 'uid' => - 1 , 'type' => self :: TARGET_GLOBAL ];
2020-03-02 08:57:23 +01:00
}
2020-09-09 18:55:14 +02:00
// Fetch the receivers for the public and the followers collection
2018-10-07 15:37:05 +02:00
if ( in_array ( $receiver , [ $followers , self :: PUBLIC_COLLECTION ]) && ! empty ( $actor )) {
2020-09-25 08:47:07 +02:00
$receivers = self :: getReceiverForActor ( $actor , $tags , $receivers , $follower_target );
2018-10-03 08:15:07 +02:00
continue ;
}
2018-11-03 22:37:08 +01:00
// Fetching all directly addressed receivers
2018-11-08 17:28:29 +01:00
$condition = [ 'self' => true , 'nurl' => Strings :: normaliseLink ( $receiver )];
2018-11-03 22:37:08 +01:00
$contact = DBA :: selectFirst ( 'contact' , [ 'uid' , 'contact-type' ], $condition );
2018-10-03 08:15:07 +02:00
if ( ! DBA :: isResult ( $contact )) {
continue ;
}
2018-11-03 22:37:08 +01: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 23:08:35 +01:00
if ((( $element != 'as:to' ) && empty ( $replyto )) || ( $contact [ 'contact-type' ] == Contact :: TYPE_COMMUNITY )) {
2019-07-01 20:00:55 +02:00
$networks = Protocol :: FEDERATED ;
2018-11-08 17:28:29 +01:00
$condition = [ 'nurl' => Strings :: normaliseLink ( $actor ), 'rel' => [ Contact :: SHARING , Contact :: FRIEND ],
2018-11-03 22:37:08 +01:00
'network' => $networks , 'archive' => false , 'pending' => false , 'uid' => $contact [ 'uid' ]];
// Forum posts are only accepted from forum contacts
2019-01-06 23:08:35 +01:00
if ( $contact [ 'contact-type' ] == Contact :: TYPE_COMMUNITY ) {
2018-11-03 22:37:08 +01:00
$condition [ 'rel' ] = [ Contact :: SHARING , Contact :: FRIEND , Contact :: FOLLOWER ];
}
if ( ! DBA :: exists ( 'contact' , $condition )) {
continue ;
}
}
2020-09-25 08:47:07 +02:00
$type = $receivers [ $contact [ 'uid' ]][ 'type' ] ? ? self :: TARGET_UNKNOWN ;
2020-09-14 19:48:57 +02:00
if ( in_array ( $type , [ self :: TARGET_UNKNOWN , self :: TARGET_FOLLOWER , self :: TARGET_ANSWER , self :: TARGET_GLOBAL ])) {
2020-09-12 19:45:04 +02: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 08:47:07 +02:00
$receivers [ $contact [ 'uid' ]] = [ 'uid' => $contact [ 'uid' ], 'type' => $type ];
2020-09-12 19:45:04 +02:00
}
2018-10-03 08:15:07 +02:00
}
}
2018-10-03 11:15:38 +02:00
self :: switchContacts ( $receivers , $actor );
2018-10-03 08:15:07 +02:00
return $receivers ;
}
2018-11-03 22:37:08 +01:00
/**
* Fetch the receiver list of a given actor
*
2020-09-25 08:47:07 +02:00
* @ param string $actor
* @ param array $tags
* @ param array $receivers
* @ param integer $target_type
2018-11-03 22:37:08 +01:00
*
* @ return array with receivers ( user id )
2019-01-06 22:06:53 +01:00
* @ throws \Exception
2018-11-03 22:37:08 +01:00
*/
2020-09-25 08:47:07 +02:00
private static function getReceiverForActor ( $actor , $tags , $receivers , $target_type )
2018-11-03 22:37:08 +01:00
{
2020-09-09 18:55:14 +02:00
$basecondition = [ 'rel' => [ Contact :: SHARING , Contact :: FRIEND , Contact :: FOLLOWER ],
'network' => Protocol :: FEDERATED , 'archive' => false , 'pending' => false ];
2020-09-24 12:26:28 +02:00
$condition = DBA :: mergeConditions ( $basecondition , [ " `nurl` = ? AND `uid` != ? " , Strings :: normaliseLink ( $actor ), 0 ]);
2020-09-09 18:55:14 +02:00
$contacts = DBA :: select ( 'contact' , [ 'uid' , 'rel' ], $condition );
while ( $contact = DBA :: fetch ( $contacts )) {
2020-09-25 08:47:07 +02:00
if ( empty ( $receivers [ $contact [ 'uid' ]]) && self :: isValidReceiverForActor ( $contact , $tags )) {
$receivers [ $contact [ 'uid' ]] = [ 'uid' => $contact [ 'uid' ], 'type' => $target_type ];
2020-09-09 18:55:14 +02:00
}
}
DBA :: close ( $contacts );
// The queries are split because of performance issues
2020-09-24 12:26:28 +02:00
$condition = DBA :: mergeConditions ( $basecondition , [ " `alias` IN (?, ?) AND `uid` != ? " , Strings :: normaliseLink ( $actor ), $actor , 0 ]);
2018-11-03 22:37:08 +01:00
$contacts = DBA :: select ( 'contact' , [ 'uid' , 'rel' ], $condition );
while ( $contact = DBA :: fetch ( $contacts )) {
2020-09-25 08:47:07 +02:00
if ( empty ( $receivers [ $contact [ 'uid' ]]) && self :: isValidReceiverForActor ( $contact , $tags )) {
$receivers [ $contact [ 'uid' ]] = [ 'uid' => $contact [ 'uid' ], 'type' => $target_type ];
2018-11-03 22:37:08 +01:00
}
}
DBA :: close ( $contacts );
return $receivers ;
}
/**
* Tests if the contact is a valid receiver for this actor
*
2019-01-06 22:06:53 +01:00
* @ param array $contact
2018-11-03 22:37:08 +01:00
* @ param string $actor
2019-01-06 22:06:53 +01:00
* @ param array $tags
2018-11-03 22:37:08 +01:00
*
2019-01-06 22:06:53 +01:00
* @ return bool with receivers ( user id )
* @ throws \Exception
2018-11-03 22:37:08 +01:00
*/
2020-09-24 12:26:28 +02:00
private static function isValidReceiverForActor ( $contact , $tags )
2018-11-03 22:37:08 +01: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 23:08:35 +01:00
if ( empty ( $owner ) || ( $owner [ 'contact-type' ] != Contact :: TYPE_COMMUNITY )) {
2018-11-03 22:37:08 +01:00
return false ;
}
// Is the community account tagged?
foreach ( $tags as $tag ) {
if ( $tag [ 'type' ] != 'Mention' ) {
continue ;
}
2020-09-24 12:26:28 +02:00
if ( Strings :: compareLink ( $tag [ 'href' ], $owner [ 'url' ])) {
2018-11-03 22:37:08 +01:00
return true ;
}
}
return false ;
}
2018-10-03 08:15:07 +02:00
/**
2018-10-06 06:18:40 +02:00
* Switches existing contacts to ActivityPub
2018-10-03 08:15:07 +02:00
*
2018-10-05 21:48:48 +02:00
* @ param integer $cid Contact ID
2018-10-03 08:15:07 +02:00
* @ param integer $uid User ID
2019-01-06 22:06:53 +01:00
* @ param string $url Profile URL
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
* @ throws \ImagickException
2018-10-03 08:15:07 +02:00
*/
2018-10-13 20:13:01 +02:00
public static function switchContact ( $cid , $uid , $url )
2018-10-03 08:15:07 +02:00
{
2019-09-11 18:54:13 +02: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 08:15:07 +02:00
2020-08-06 20:53:45 +02:00
if ( Contact :: updateFromProbe ( $cid )) {
2019-09-11 18:54:13 +02:00
Logger :: info ( 'Update was successful' , [ 'id' => $cid , 'uid' => $uid , 'url' => $url ]);
}
2018-10-04 14:57:42 +02:00
2018-10-05 21:48:48 +02:00
// Send a new follow request to be sure that the connection still exists
2019-09-11 18:54:13 +02: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 22:11:51 +02:00
ActivityPub\Transmitter :: sendActivity ( 'Follow' , $url , $uid );
2018-10-05 21:48:48 +02:00
}
2018-10-03 08:15:07 +02:00
}
/**
2018-10-17 00:29:28 +02:00
*
2018-10-03 08:15:07 +02:00
*
* @ param $receivers
* @ param $actor
2019-01-06 22:06:53 +01:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
* @ throws \ImagickException
2018-10-03 08:15:07 +02:00
*/
private static function switchContacts ( $receivers , $actor )
{
if ( empty ( $actor )) {
return ;
}
foreach ( $receivers as $receiver ) {
2020-09-12 19:45:04 +02:00
$contact = DBA :: selectFirst ( 'contact' , [ 'id' ], [ 'uid' => $receiver [ 'uid' ], 'network' => Protocol :: OSTATUS , 'nurl' => Strings :: normaliseLink ( $actor )]);
2018-10-03 08:15:07 +02:00
if ( DBA :: isResult ( $contact )) {
2020-09-12 19:45:04 +02:00
self :: switchContact ( $contact [ 'id' ], $receiver [ 'uid' ], $actor );
2018-10-03 08:15:07 +02:00
}
2020-09-12 19:45:04 +02:00
$contact = DBA :: selectFirst ( 'contact' , [ 'id' ], [ 'uid' => $receiver [ 'uid' ], 'network' => Protocol :: OSTATUS , 'alias' => [ Strings :: normaliseLink ( $actor ), $actor ]]);
2018-10-03 08:15:07 +02:00
if ( DBA :: isResult ( $contact )) {
2020-09-12 19:45:04 +02:00
self :: switchContact ( $contact [ 'id' ], $receiver [ 'uid' ], $actor );
2018-10-03 08:15:07 +02:00
}
}
}
/**
2018-10-17 00:29:28 +02:00
*
2018-10-03 08:15:07 +02:00
*
2019-01-06 22:06:53 +01:00
* @ param $object_data
2018-10-03 08:15:07 +02:00
* @ param array $activity
*
2019-01-06 22:06:53 +01:00
* @ return mixed
2018-10-03 08:15:07 +02:00
*/
private static function addActivityFields ( $object_data , $activity )
{
if ( ! empty ( $activity [ 'published' ]) && empty ( $object_data [ 'published' ])) {
2018-10-13 23:37:39 +02:00
$object_data [ 'published' ] = JsonLD :: fetchElement ( $activity , 'as:published' , '@value' );
2018-10-03 08:15:07 +02:00
}
2018-10-06 16:02:23 +02:00
if ( ! empty ( $activity [ 'diaspora:guid' ]) && empty ( $object_data [ 'diaspora:guid' ])) {
2019-04-26 08:17:37 +02:00
$object_data [ 'diaspora:guid' ] = JsonLD :: fetchElement ( $activity , 'diaspora:guid' , '@value' );
2018-10-06 16:02:23 +02:00
}
2018-10-13 23:37:39 +02:00
$object_data [ 'service' ] = JsonLD :: fetchElement ( $activity , 'as:instrument' , 'as:name' , '@type' , 'as:Service' );
2019-04-26 08:17:37 +02:00
$object_data [ 'service' ] = JsonLD :: fetchElement ( $object_data , 'service' , '@value' );
2018-10-03 08:15:07 +02:00
2020-02-02 20:59:14 +01: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 08:15:07 +02:00
return $object_data ;
}
/**
2018-10-07 19:17:06 +02:00
* Fetches the object data from external ressources if needed
2018-10-03 08:15:07 +02:00
*
2018-10-07 19:17:06 +02: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 22:37:08 +01:00
* @ param integer $uid User ID for the signature that we use to fetch data
2018-10-03 08:15:07 +02:00
*
2019-06-14 01:07:39 +02:00
* @ return array | false with trusted and valid object data
2019-01-06 22:06:53 +01:00
* @ throws \Friendica\Network\HTTPException\InternalServerErrorException
* @ throws \ImagickException
2018-10-03 08:15:07 +02:00
*/
2019-06-14 01:07:39 +02:00
private static function fetchObject ( string $object_id , array $object = [], bool $trust_source = false , int $uid = 0 )
2018-10-03 08:15:07 +02:00
{
2018-10-07 19:17:06 +02: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 22:37:08 +01:00
$data = ActivityPub :: fetchContent ( $object_id , $uid );
2018-10-07 17:34:51 +02:00
if ( ! empty ( $data )) {
$object = JsonLD :: compact ( $data );
2018-10-30 14:58:45 +01:00
Logger :: log ( 'Fetched content for ' . $object_id , Logger :: DEBUG );
2018-10-07 17:34:51 +02:00
} else {
2018-10-30 14:58:45 +01:00
Logger :: log ( 'Empty content for ' . $object_id . ', check if content is available locally.' , Logger :: DEBUG );
2018-10-07 17:34:51 +02:00
$item = Item :: selectFirst ([], [ 'uri' => $object_id ]);
if ( ! DBA :: isResult ( $item )) {
2018-10-30 14:58:45 +01:00
Logger :: log ( 'Object with url ' . $object_id . ' was not found locally.' , Logger :: DEBUG );
2018-10-07 17:34:51 +02:00
return false ;
}
2018-10-30 14:58:45 +01:00
Logger :: log ( 'Using already stored item for url ' . $object_id , Logger :: DEBUG );
2018-10-07 17:34:51 +02:00
$data = ActivityPub\Transmitter :: createNote ( $item );
$object = JsonLD :: compact ( $data );
2018-10-03 08:15:07 +02:00
}
2020-09-12 14:12:55 +02:00
$id = JsonLD :: fetchElement ( $object , '@id' );
if ( empty ( $id )) {
Logger :: info ( 'Empty id' );
return false ;
}
if ( $id != $object_id ) {
Logger :: info ( 'Fetched id differs from provided id' , [ 'provided' => $object_id , 'fetched' => $id ]);
return false ;
}
2018-10-03 08:15:07 +02:00
} else {
2018-10-30 14:58:45 +01:00
Logger :: log ( 'Using original object for url ' . $object_id , Logger :: DEBUG );
2018-10-03 08:15:07 +02:00
}
2018-10-07 17:34:51 +02:00
$type = JsonLD :: fetchElement ( $object , '@type' );
if ( empty ( $type )) {
2020-09-12 14:12:55 +02:00
Logger :: info ( 'Empty type' );
2018-10-03 08:15:07 +02:00
return false ;
}
2020-09-12 14:12:55 +02: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 23:43:19 +01:00
$object_data = self :: processObject ( $object );
if ( ! empty ( $data )) {
$object_data [ 'raw' ] = json_encode ( $data );
}
return $object_data ;
2018-10-03 08:15:07 +02:00
}
2018-10-07 17:34:51 +02:00
if ( $type == 'as:Announce' ) {
2019-04-26 08:17:37 +02:00
$object_id = JsonLD :: fetchElement ( $object , 'object' , '@id' );
2019-06-14 04:58:40 +02:00
if ( empty ( $object_id ) || ! is_string ( $object_id )) {
2018-10-03 08:15:07 +02:00
return false ;
}
2018-11-03 22:37:08 +01:00
return self :: fetchObject ( $object_id , [], false , $uid );
2018-10-03 08:15:07 +02:00
}
2018-10-30 14:58:45 +01:00
Logger :: log ( 'Unhandled object type: ' . $type , Logger :: DEBUG );
2019-06-14 01:07:39 +02:00
return false ;
2018-10-03 08:15:07 +02:00
}
/**
2018-10-07 15:37:05 +02:00
* Convert tags from JSON - LD format into a simplified format
2018-10-03 08:15:07 +02:00
*
2018-10-07 15:37:05 +02:00
* @ param array $tags Tags in JSON - LD format
2018-10-03 08:15:07 +02:00
*
2018-10-07 15:37:05 +02:00
* @ return array with tags in a simplified format
*/
2020-09-22 17:48:44 +02:00
public static function processTags ( array $tags )
2018-10-07 15:37:05 +02:00
{
$taglist = [];
2018-10-07 17:34:51 +02:00
2018-10-07 15:37:05 +02:00
foreach ( $tags as $tag ) {
2018-10-07 17:34:51 +02:00
if ( empty ( $tag )) {
continue ;
}
2018-11-07 21:34:03 +01:00
$element = [ 'type' => str_replace ( 'as:' , '' , JsonLD :: fetchElement ( $tag , '@type' )),
2019-04-26 08:17:37 +02:00
'href' => JsonLD :: fetchElement ( $tag , 'as:href' , '@id' ),
'name' => JsonLD :: fetchElement ( $tag , 'as:name' , '@value' )];
2018-11-07 21:34:03 +01:00
if ( empty ( $element [ 'type' ])) {
continue ;
}
2020-03-25 00:12:53 +01:00
if ( empty ( $element [ 'href' ])) {
$element [ 'href' ] = $element [ 'name' ];
}
2018-11-07 21:34:03 +01:00
$taglist [] = $element ;
2018-10-07 15:37:05 +02:00
}
return $taglist ;
}
2018-11-07 21:34:03 +01:00
/**
* Convert emojis from JSON - LD format into a simplified format
*
2020-06-04 21:51:14 +02:00
* @ param array $emojis
2018-11-07 21:34:03 +01:00
* @ return array with emojis in a simplified format
*/
2020-06-04 21:51:14 +02:00
private static function processEmojis ( array $emojis )
2018-11-07 21:34:03 +01:00
{
$emojilist = [];
foreach ( $emojis as $emoji ) {
if ( empty ( $emoji ) || ( JsonLD :: fetchElement ( $emoji , '@type' ) != 'toot:Emoji' ) || empty ( $emoji [ 'as:icon' ])) {
continue ;
}
2019-04-26 08:17:37 +02:00
$url = JsonLD :: fetchElement ( $emoji [ 'as:icon' ], 'as:url' , '@id' );
$element = [ 'name' => JsonLD :: fetchElement ( $emoji , 'as:name' , '@value' ),
2018-11-07 21:34:03 +01:00
'href' => $url ];
$emojilist [] = $element ;
}
2020-06-04 21:51:14 +02:00
2018-11-07 21:34:03 +01:00
return $emojilist ;
}
2018-10-07 15:37:05 +02:00
/**
* Convert attachments from JSON - LD format into a simplified format
*
* @ param array $attachments Attachments in JSON - LD format
*
2020-07-20 06:26:42 +02:00
* @ return array Attachments in a simplified format
2018-10-07 15:37:05 +02:00
*/
2020-06-04 21:51:14 +02:00
private static function processAttachments ( array $attachments )
2018-10-07 15:37:05 +02:00
{
$attachlist = [];
2018-10-07 17:34:51 +02:00
2020-06-04 21:51:14 +02:00
// Removes empty values
$attachments = array_filter ( $attachments );
2018-10-07 19:17:06 +02:00
2018-10-07 15:37:05 +02:00
foreach ( $attachments as $attachment ) {
2020-06-04 21:51:14 +02: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 17:34:51 +02:00
2020-06-04 21:51:14 +02:00
$attachlist [] = [
'type' => 'link' ,
'title' => JsonLD :: fetchElement ( $attachment , 'as:name' , '@value' ),
'desc' => JsonLD :: fetchElement ( $attachment , 'as:summary' , '@value' ),
'url' => $pageUrl ,
'image' => $pageImage ,
];
break ;
case 'as:Link' :
$attachlist [] = [
'type' => str_replace ( 'as:' , '' , JsonLD :: fetchElement ( $attachment , '@type' )),
'mediaType' => JsonLD :: fetchElement ( $attachment , 'as:mediaType' , '@value' ),
'name' => JsonLD :: fetchElement ( $attachment , 'as:name' , '@value' ),
'url' => JsonLD :: fetchElement ( $attachment , 'as:href' , '@id' )
];
break ;
2020-07-20 06:26:42 +02: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 21:51:14 +02: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' ),
'url' => JsonLD :: fetchElement ( $attachment , 'as:url' , '@id' )
];
}
2018-10-07 15:37:05 +02:00
}
2020-06-04 21:51:14 +02:00
2018-10-07 15:37:05 +02:00
return $attachlist ;
}
2019-11-13 17:22:20 +01: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 05:43:06 +01:00
/**
* 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 $object The raw object
* @ param array $object_data The parsed object data for later processing
* @ return array the object data
*/
private static function processAttachmentUrls ( array $object , array $object_data ) {
// Check if this is some url with multiple links
if ( empty ( $object [ 'as:url' ])) {
return $object_data ;
}
$urls = $object [ 'as:url' ];
$keys = array_keys ( $urls );
if ( ! is_numeric ( array_pop ( $keys ))) {
return $object_data ;
}
$attachments = [];
foreach ( $urls as $url ) {
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' ) {
$object_data [ 'alternate-url' ] = $href ;
}
$filetype = strtolower ( substr ( $mediatype , 0 , strpos ( $mediatype , '/' )));
if ( $filetype == 'audio' ) {
2020-10-29 06:20:26 +01:00
$attachments [ $filetype ] = [ 'type' => $mediatype , 'url' => $href , 'height' => null , 'size' => null ];
2020-03-23 05:43:06 +01:00
} elseif ( $filetype == 'video' ) {
$height = ( int ) JsonLD :: fetchElement ( $url , 'as:height' , '@value' );
2020-10-29 06:20:26 +01:00
$size = ( int ) JsonLD :: fetchElement ( $url , 'pt:size' , '@value' );
2020-03-23 05:43:06 +01:00
2020-10-29 06:20:26 +01:00
// We save bandwidth by using a moderate height (alt least 480 pixel height)
2020-03-23 05:43:06 +01:00
// Peertube normally uses these heights: 240, 360, 480, 720, 1080
if ( ! empty ( $attachments [ $filetype ][ 'height' ]) &&
2020-10-29 06:20:26 +01:00
( $height > $attachments [ $filetype ][ 'height' ]) && ( $attachments [ $filetype ][ 'height' ] >= 480 )) {
2020-03-23 05:43:06 +01:00
continue ;
}
2020-10-29 06:20:26 +01:00
$attachments [ $filetype ] = [ 'type' => $mediatype , 'url' => $href , 'height' => $height , 'size' => $size ];
} 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 ;
}
$attachments [ $mediatype ] = [ 'type' => $mediatype , 'url' => $href , 'height' => $height , 'size' => null ];
2020-03-23 05:43:06 +01:00
}
}
foreach ( $attachments as $type => $attachment ) {
$object_data [ 'attachments' ][] = [ 'type' => $type ,
'mediaType' => $attachment [ 'type' ],
2020-10-29 06:20:26 +01:00
'height' => $attachment [ 'height' ],
'size' => $attachment [ 'size' ],
2020-03-23 05:43:06 +01:00
'name' => '' ,
'url' => $attachment [ 'url' ]];
}
return $object_data ;
}
2018-10-07 15:37:05 +02:00
/**
* Fetches data from the object part of an activity
*
* @ param array $object
*
* @ return array
2019-01-06 22:06:53 +01:00
* @ throws \Exception
2018-10-03 08:15:07 +02:00
*/
private static function processObject ( $object )
{
2018-10-07 15:37:05 +02:00
if ( ! JsonLD :: fetchElement ( $object , '@id' )) {
2018-10-03 08:15:07 +02:00
return false ;
}
$object_data = [];
2018-10-07 15:37:05 +02:00
$object_data [ 'object_type' ] = JsonLD :: fetchElement ( $object , '@type' );
$object_data [ 'id' ] = JsonLD :: fetchElement ( $object , '@id' );
2019-04-26 08:17:37 +02:00
$object_data [ 'reply-to-id' ] = JsonLD :: fetchElement ( $object , 'as:inReplyTo' , '@id' );
2018-10-07 15:37:05 +02:00
2019-01-13 10:38:01 +01: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 08:15:07 +02:00
$object_data [ 'reply-to-id' ] = $object_data [ 'id' ];
2020-02-02 20:59:14 +01: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 08:15:07 +02:00
}
2018-10-07 15:37:05 +02: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 08:15:07 +02:00
if ( empty ( $object_data [ 'published' ]) && ! empty ( $object_data [ 'updated' ])) {
$object_data [ 'published' ] = $object_data [ 'updated' ];
}
2019-04-26 08:17:37 +02:00
$actor = JsonLD :: fetchElement ( $object , 'as:attributedTo' , '@id' );
2018-10-03 08:15:07 +02:00
if ( empty ( $actor )) {
2019-04-26 08:17:37 +02:00
$actor = JsonLD :: fetchElement ( $object , 'as:actor' , '@id' );
2018-10-03 08:15:07 +02:00
}
2020-05-07 04:41:59 +02:00
$location = JsonLD :: fetchElement ( $object , 'as:location' , 'as:name' , '@type' , 'as:Place' );
$location = JsonLD :: fetchElement ( $location , 'location' , '@value' );
2020-05-07 15:17:16 +02: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 );
$location = BBCode :: convert ( $location );
$location = HTML :: toPlaintext ( $location );
}
2020-05-07 04:41:59 +02:00
2020-03-05 09:06:19 +01:00
$object_data [ 'sc:identifier' ] = JsonLD :: fetchElement ( $object , 'sc:identifier' , '@value' );
2019-04-26 08:17:37 +02: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 19:35:43 +02:00
$object_data [ 'actor' ] = $object_data [ 'author' ] = $actor ;
2019-04-26 08:17:37 +02:00
$object_data [ 'context' ] = JsonLD :: fetchElement ( $object , 'as:context' , '@id' );
$object_data [ 'conversation' ] = JsonLD :: fetchElement ( $object , 'ostatus:conversation' , '@id' );
2018-10-07 15:37:05 +02:00
$object_data [ 'sensitive' ] = JsonLD :: fetchElement ( $object , 'as:sensitive' );
2019-04-26 08:17:37 +02: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' );
2019-11-13 17:22:20 +01:00
$object_data = self :: getSource ( $object , $object_data );
2018-10-26 06:13:26 +02:00
$object_data [ 'start-time' ] = JsonLD :: fetchElement ( $object , 'as:startTime' , '@value' );
$object_data [ 'end-time' ] = JsonLD :: fetchElement ( $object , 'as:endTime' , '@value' );
2020-05-07 04:41:59 +02:00
$object_data [ 'location' ] = $location ;
2018-10-13 23:37:39 +02: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 21:51:14 +02:00
$object_data [ 'attachments' ] = self :: processAttachments ( JsonLD :: fetchElementArray ( $object , 'as:attachment' ) ? ? []);
$object_data [ 'tags' ] = self :: processTags ( JsonLD :: fetchElementArray ( $object , 'as:tag' ) ? ? []);
2020-11-11 17:19:08 +01:00
$object_data [ 'emojis' ] = self :: processEmojis ( JsonLD :: fetchElementArray ( $object , 'as:tag' , null , '@type' , 'toot:Emoji' ) ? ? []);
2018-10-13 23:37:39 +02:00
$object_data [ 'generator' ] = JsonLD :: fetchElement ( $object , 'as:generator' , 'as:name' , '@type' , 'as:Application' );
2019-04-26 08:17:37 +02:00
$object_data [ 'generator' ] = JsonLD :: fetchElement ( $object_data , 'generator' , '@value' );
$object_data [ 'alternate-url' ] = JsonLD :: fetchElement ( $object , 'as:url' , '@id' );
2018-10-07 15:37:05 +02:00
// Special treatment for Hubzilla links
if ( is_array ( $object_data [ 'alternate-url' ])) {
2019-04-26 08:17:37 +02:00
$object_data [ 'alternate-url' ] = JsonLD :: fetchElement ( $object_data [ 'alternate-url' ], 'as:href' , '@id' );
2018-10-14 19:57:44 +02:00
if ( ! is_string ( $object_data [ 'alternate-url' ])) {
2019-04-26 08:17:37 +02:00
$object_data [ 'alternate-url' ] = JsonLD :: fetchElement ( $object [ 'as:url' ], 'as:href' , '@id' );
2018-10-07 15:37:05 +02:00
}
}
2020-03-23 05:43:06 +01:00
if ( in_array ( $object_data [ 'object_type' ], [ 'as:Audio' , 'as:Video' ])) {
$object_data = self :: processAttachmentUrls ( $object , $object_data );
}
2020-09-12 19:45:04 +02:00
$receiverdata = self :: getReceivers ( $object , $object_data [ 'actor' ], $object_data [ 'tags' ], true );
2020-09-13 16:15:28 +02:00
$receivers = $reception_types = [];
2020-09-12 19:45:04 +02:00
foreach ( $receiverdata as $key => $data ) {
$receivers [ $key ] = $data [ 'uid' ];
2020-09-13 16:15:28 +02:00
$reception_types [ $data [ 'uid' ]] = $data [ 'type' ] ? ? 0 ;
2020-09-12 19:45:04 +02:00
}
$object_data [ 'receiver' ] = $receivers ;
2020-09-13 16:15:28 +02:00
$object_data [ 'reception_type' ] = $reception_types ;
2020-09-12 19:45:04 +02:00
2020-03-02 08:57:23 +01:00
$object_data [ 'unlisted' ] = in_array ( - 1 , $object_data [ 'receiver' ]);
2020-09-25 08:47:07 +02:00
unset ( $object_data [ 'receiver' ][ - 1 ]);
unset ( $object_data [ 'reception_type' ][ - 1 ]);
2018-10-03 08:15:07 +02: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 23:37:39 +02:00
// audience, preview, endTime, startTime, image
2018-10-03 08:15:07 +02: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 ;
}
}