2018-05-30 04:08:52 +00:00
< ? php
2022-02-16 04:08:28 +00:00
namespace Code\Lib ;
2018-05-30 04:08:52 +00:00
2020-08-07 03:19:03 +00:00
use App ;
2023-01-25 20:32:42 +00:00
use Code\Access\PermissionLimits ;
2023-07-18 07:04:13 +00:00
use Code\ActivityStreams\Actor ;
use Code\ActivityStreams\ASObject ;
2024-01-13 23:13:18 +00:00
use Code\ActivityStreams\Collection ;
2023-07-18 07:04:13 +00:00
use Code\ActivityStreams\Link ;
2023-08-03 10:59:25 +00:00
use Code\ActivityStreams\Place ;
2024-01-12 05:31:10 +00:00
use Code\ActivityStreams\AssertionMethod ;
2023-09-02 22:17:39 +00:00
use Code\ActivityStreams\UnhandledElementException ;
2024-01-13 23:13:18 +00:00
use Code\Entity\Item ;
2023-09-02 22:17:39 +00:00
use Code\Nomad\Location ;
2023-07-22 23:01:55 +00:00
use Code\Nomad\Profile ;
2023-07-29 01:07:38 +00:00
use Code\Web\HTTPHeaders ;
2022-02-16 04:08:28 +00:00
use Code\Web\HTTPSig ;
use Code\Access\Permissions ;
use Code\Access\PermissionRoles ;
use Code\Daemon\Run ;
2022-06-21 23:36:56 +00:00
use Code\Lib as Zlib ;
2022-02-16 04:08:28 +00:00
use Code\Extend\Hook ;
2020-02-21 02:17:16 +00:00
use Emoji ;
2018-05-30 04:08:52 +00:00
2019-09-18 04:25:26 +00:00
require_once ( 'include/html2bbcode.php' );
require_once ( 'include/html2plain.php' );
require_once ( 'include/event.php' );
2021-12-02 23:02:31 +00:00
class Activity
{
2023-09-17 22:08:30 +00:00
public const ACTOR_CACHE_DAYS = 3 ;
2021-12-02 23:02:31 +00:00
2022-07-18 22:03:22 +00:00
// Turn $element into an array if it isn't already.
2022-07-18 12:27:03 +00:00
public static function force_array ( $element ) {
2022-07-18 22:03:22 +00:00
if ( empty ( $element )) {
return [];
}
2022-07-18 12:27:03 +00:00
return ( is_array ( $element )) ? $element : [ $element ];
}
2022-07-20 05:27:23 +00:00
2021-12-02 23:02:31 +00:00
// $x (string|array)
// if json string, decode it
// returns activitystreams object as an array except if it is a URL
// which returns the URL as string
public static function encode_object ( $x )
{
if ( $x ) {
if ( is_string ( $x )) {
$tmp = json_decode ( $x , true );
2021-12-03 03:01:39 +00:00
if ( $tmp !== null ) {
2021-12-02 23:02:31 +00:00
$x = $tmp ;
}
}
}
if ( is_string ( $x )) {
return ( $x );
}
if ( $x [ 'type' ] === ACTIVITY_OBJ_PERSON ) {
return self :: fetch_person ( $x );
}
if ( $x [ 'type' ] === ACTIVITY_OBJ_PROFILE ) {
return self :: fetch_profile ( $x );
}
if ( in_array ( $x [ 'type' ], [ ACTIVITY_OBJ_NOTE , ACTIVITY_OBJ_ARTICLE ])) {
// Use Mastodon-specific note and media hacks if nomadic. Else HTML.
// Eventually this needs to be passed in much further up the stack
2022-10-11 07:59:26 +00:00
// and base the decision on whether we are encoding for
2022-07-20 05:27:23 +00:00
// ActivityPub or Zot6 or Nomad
2019-09-10 04:03:33 +00:00
2022-08-23 10:15:05 +00:00
return self :: fetch_item ( $x , ( bool ) get_config ( 'system' , 'activitypub' , ACTIVITYPUB_ENABLED ));
2021-12-02 23:02:31 +00:00
}
if ( $x [ 'type' ] === ACTIVITY_OBJ_THING ) {
return self :: fetch_thing ( $x );
}
2022-02-12 08:50:48 +00:00
Hook :: call ( 'encode_object' , $x );
2021-12-02 23:02:31 +00:00
return $x ;
}
2023-05-04 11:37:49 +00:00
public static function fetch_local ( $url , $portable_id ) {
$sql_extra = item_permissions_sql ( 0 , $portable_id );
2023-05-23 21:37:57 +00:00
$item_normal = item_normal ();
2023-05-04 11:37:49 +00:00
// Find the original object
$j = q (
2023-05-04 20:55:11 +00:00
" select *, id as item_id from item where mid = '%s' and item_wall = 1 $item_normal $sql_extra " ,
2023-05-04 11:37:49 +00:00
dbesc ( $url )
);
if ( $j ) {
2023-05-04 20:55:11 +00:00
xchan_query ( $j , true );
$items = fetch_post_tags ( $j );
2023-05-04 11:37:49 +00:00
}
2023-05-04 20:55:11 +00:00
if ( $items ) {
return self :: encode_item ( array_shift ( $items ), true );
}
return false ;
2023-05-04 11:37:49 +00:00
}
2021-12-02 23:02:31 +00:00
2022-12-11 19:16:17 +00:00
public static function fetch ( $url , $channel = null , $must_verify = false , $debug = false )
2021-12-02 23:02:31 +00:00
{
2022-04-21 21:46:21 +00:00
if ( ! $url ) {
return null ;
}
2021-12-02 23:02:31 +00:00
if ( ! check_siteallowed ( $url )) {
logger ( 'denied: ' . $url );
return null ;
}
if ( ! $channel ) {
2022-01-25 01:26:12 +00:00
$channel = Channel :: get_system ();
2021-12-02 23:02:31 +00:00
}
$parsed = parse_url ( $url );
// perform IDN substitution
2022-04-21 21:46:21 +00:00
if ( isset ( $parsed [ 'host' ]) && $parsed [ 'host' ] !== punify ( $parsed [ 'host' ])) {
2021-12-02 23:02:31 +00:00
$url = str_replace ( $parsed [ 'host' ], punify ( $parsed [ 'host' ]), $url );
}
logger ( 'fetch: ' . $url , LOGGER_DEBUG );
2022-06-29 05:19:12 +00:00
// handle bearcaps
if ( isset ( $parsed [ 'scheme' ]) && isset ( $parsed [ 'query' ]) && $parsed [ 'scheme' ] === 'bear' && $parsed [ 'query' ] !== EMPTY_STR ) {
$params = explode ( '&' , $parsed [ 'query' ]);
if ( $params ) {
foreach ( $params as $p ) {
2022-10-09 01:47:49 +00:00
if ( str_starts_with ( $p , 'u=' )) {
2022-06-29 05:19:12 +00:00
$url = substr ( $p , 2 );
}
2022-10-09 01:47:49 +00:00
if ( str_starts_with ( $p , 't=' )) {
2022-06-29 05:19:12 +00:00
$token = substr ( $p , 2 );
2021-12-02 23:02:31 +00:00
}
}
2022-06-29 05:19:12 +00:00
// the entire URL just changed so parse it again
$parsed = parse_url ( $url );
2021-12-02 23:02:31 +00:00
}
2022-06-29 05:19:12 +00:00
}
2021-12-02 23:02:31 +00:00
2022-06-29 05:19:12 +00:00
// Ignore fragments; as we are not in a browser.
unset ( $parsed [ 'fragment' ]);
2021-12-02 23:02:31 +00:00
2022-06-29 05:19:12 +00:00
// rebuild the url
$url = unparse_url ( $parsed );
2021-12-02 23:02:31 +00:00
2022-06-29 05:19:12 +00:00
logger ( 'fetch_actual: ' . $url , LOGGER_DEBUG );
2021-12-02 23:02:31 +00:00
2023-03-19 08:36:36 +00:00
$default_accept_header = 'application/activity+json, application/x-zot-activity+json, application/ld+json; profile="https://www.w3.org/ns/activitystreams"' ;
if ( $channel ) {
$accept_header = PConfig :: Get ( $channel [ 'channel_id' ], 'system' , 'accept_header' );
}
if ( ! $accept_header ) {
$accept_header = Config :: Get ( 'system' , 'accept_header' , $default_accept_header );
}
2022-06-29 05:19:12 +00:00
$headers = [
2023-03-19 08:36:36 +00:00
'Accept' => $accept_header ,
2022-06-29 05:19:12 +00:00
'Host' => $parsed [ 'host' ],
'Date' => datetime_convert ( 'UTC' , 'UTC' , 'now' , 'D, d M Y H:i:s \\G\\M\\T' ),
'(request-target)' => 'get ' . get_request_string ( $url )
];
2021-12-02 23:02:31 +00:00
2022-06-29 05:19:12 +00:00
if ( isset ( $token )) {
$headers [ 'Authorization' ] = 'Bearer ' . $token ;
2021-12-02 23:02:31 +00:00
}
2023-04-12 22:17:42 +00:00
$h = HTTPSig :: create_sig ( $headers , $channel [ 'channel_prvkey' ], Channel :: keyId ( $channel ));
2022-06-29 05:19:12 +00:00
$x = Url :: get ( $url , [ 'headers' => $h ]);
2021-12-02 23:02:31 +00:00
if ( $x [ 'success' ]) {
$y = json_decode ( $x [ 'body' ], true );
2022-05-30 20:54:08 +00:00
logger ( 'returned: ' . json_encode ( $y , JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES ), LOGGER_DEBUG );
2021-12-02 23:02:31 +00:00
$site_url = unparse_url ([ 'scheme' => $parsed [ 'scheme' ], 'host' => $parsed [ 'host' ], 'port' => (( array_key_exists ( 'port' , $parsed ) && intval ( $parsed [ 'port' ])) ? $parsed [ 'port' ] : 0 )]);
2021-12-03 03:01:39 +00:00
q (
" update site set site_update = '%s' where site_url = '%s' and site_update < %s - INTERVAL %s " ,
2021-12-02 23:02:31 +00:00
dbesc ( datetime_convert ()),
dbesc ( $site_url ),
2021-12-03 03:01:39 +00:00
db_utcnow (),
db_quoteinterval ( '1 DAY' )
2021-12-02 23:02:31 +00:00
);
// check for a valid signature, but only if this is not an actor object. If it is signed, it must be valid.
// Ignore actors because of the potential for infinite recursion if we perform this step while
// fetching an actor key to validate a signature elsewhere. This should validate relayed activities
// over litepub which arrived at our inbox that do not use LD signatures
if (( $y [ 'type' ]) && ( ! ActivityStreams :: is_an_actor ( $y [ 'type' ]))) {
$sigblock = HTTPSig :: verify ( $x );
2022-12-11 19:16:17 +00:00
if ( $must_verify && ! $sigblock [ 'header_signed' ]) {
return null ;
}
2021-12-02 23:02:31 +00:00
if (( $sigblock [ 'header_signed' ]) && ( ! $sigblock [ 'header_valid' ])) {
2022-08-10 21:24:55 +00:00
if ( $debug ) {
return array_merge ( $x , $sigblock );
}
2021-12-02 23:02:31 +00:00
return null ;
}
}
return json_decode ( $x [ 'body' ], true );
2022-10-21 07:12:11 +00:00
}
else {
2021-12-02 23:02:31 +00:00
logger ( 'fetch failed: ' . $url );
if ( $debug ) {
return $x ;
}
}
return null ;
}
public static function fetch_person ( $x )
{
return self :: fetch_profile ( $x );
}
public static function fetch_profile ( $x )
{
2021-12-03 03:01:39 +00:00
$r = q (
" select * from xchan left join hubloc on xchan_hash = hubloc_hash where hubloc_id_url = '%s' limit 1 " ,
2021-12-02 23:02:31 +00:00
dbesc ( $x [ 'id' ])
);
if ( ! $r ) {
2021-12-03 03:01:39 +00:00
$r = q (
" select * from xchan where xchan_hash = '%s' limit 1 " ,
2021-12-02 23:02:31 +00:00
dbesc ( $x [ 'id' ])
);
}
if ( ! $r ) {
return [];
}
2024-01-11 18:34:22 +00:00
return self :: actorEncode ( $r [ 0 ], false );
2021-12-02 23:02:31 +00:00
}
2022-08-23 10:15:05 +00:00
public static function fetch_thing ( $x ) : array
2021-12-02 23:02:31 +00:00
{
2021-12-03 03:01:39 +00:00
$r = q (
" select * from obj where obj_type = %d and obj_obj = '%s' limit 1 " ,
2021-12-02 23:02:31 +00:00
intval ( TERM_OBJ_THING ),
dbesc ( $x [ 'id' ])
);
if ( ! $r ) {
return [];
}
$x = [
'type' => 'Object' ,
'id' => z_root () . '/thing/' . $r [ 0 ][ 'obj_obj' ],
'name' => $r [ 0 ][ 'obj_term' ]
];
if ( $r [ 0 ][ 'obj_image' ]) {
$x [ 'image' ] = $r [ 0 ][ 'obj_image' ];
}
return $x ;
}
public static function fetch_item ( $x , $activitypub = false )
{
if ( array_key_exists ( 'source' , $x )) {
// This item is already processed and encoded
return $x ;
}
2021-12-03 03:01:39 +00:00
$r = q (
" select * from item where mid = '%s' limit 1 " ,
2021-12-02 23:02:31 +00:00
dbesc ( $x [ 'id' ])
);
if ( $r ) {
2022-08-23 10:15:05 +00:00
xchan_query ( $r );
2022-10-23 00:50:31 +00:00
$r = fetch_post_tags ( $r );
2023-09-29 22:31:07 +00:00
if ( in_array ( $r [ 0 ][ 'verb' ], [ 'Invite' , 'Undo' ])) {
2023-12-19 10:08:08 +00:00
return self :: encode_activity ( $r [ 0 ], $activitypub );
2021-12-02 23:02:31 +00:00
}
return self :: encode_item ( $r [ 0 ], $activitypub );
}
2022-08-23 10:15:05 +00:00
return false ;
2021-12-02 23:02:31 +00:00
}
2023-12-27 07:38:36 +00:00
public static function paged_collection_init ( $total , $id , $type = 'OrderedCollection' , $attributedTo = '' ) : array
2021-12-02 23:02:31 +00:00
{
$ret = [
'id' => z_root () . '/' . $id ,
'type' => $type ,
'totalItems' => $total ,
];
$numpages = $total / App :: $pager [ 'itemspage' ];
$lastpage = (( $numpages > intval ( $numpages )) ? intval ( $numpages ) + 1 : $numpages );
$ret [ 'first' ] = z_root () . '/' . App :: $query_string . '?page=1' ;
$ret [ 'last' ] = z_root () . '/' . App :: $query_string . '?page=' . $lastpage ;
return $ret ;
}
2023-12-14 11:36:44 +00:00
public static function encode_item_collection ( $items , $id , $type , $activitypub = false , $attributedTo = '' , $total = 0 ) : array
2021-12-02 23:02:31 +00:00
{
2022-08-05 21:12:53 +00:00
if ( $total > App :: $pager [ 'itemspage' ]) {
2021-12-02 23:02:31 +00:00
$ret = [
'id' => z_root () . '/' . $id ,
'type' => $type . 'Page' ,
];
2023-12-14 11:36:44 +00:00
if ( $attributedTo ) {
$ret [ 'attributedTo' ] = $attributedTo ;
}
2021-12-02 23:02:31 +00:00
$numpages = $total / App :: $pager [ 'itemspage' ];
$lastpage = (( $numpages > intval ( $numpages )) ? intval ( $numpages ) + 1 : $numpages );
$url_parts = parse_url ( $id );
$ret [ 'partOf' ] = z_root () . '/' . $url_parts [ 'path' ];
$extra_query_args = '' ;
2022-08-23 10:15:05 +00:00
$query_args = [];
2021-12-02 23:02:31 +00:00
if ( isset ( $url_parts [ 'query' ])) {
parse_str ( $url_parts [ 'query' ], $query_args );
}
if ( is_array ( $query_args )) {
unset ( $query_args [ 'page' ]);
foreach ( $query_args as $k => $v ) {
$extra_query_args .= '&' . urlencode ( $k ) . '=' . urlencode ( $v );
}
}
if ( App :: $pager [ 'page' ] < $lastpage ) {
$ret [ 'next' ] = z_root () . '/' . $url_parts [ 'path' ] . '?page=' . ( intval ( App :: $pager [ 'page' ]) + 1 ) . $extra_query_args ;
}
if ( App :: $pager [ 'page' ] > 1 ) {
$ret [ 'prev' ] = z_root () . '/' . $url_parts [ 'path' ] . '?page=' . ( intval ( App :: $pager [ 'page' ]) - 1 ) . $extra_query_args ;
}
} else {
$ret = [
'id' => z_root () . '/' . $id ,
'type' => $type ,
'totalItems' => $total ,
];
2023-12-14 11:36:44 +00:00
if ( $attributedTo ) {
$ret [ 'attributedTo' ] = $attributedTo ;
}
2021-12-02 23:02:31 +00:00
}
if ( $items ) {
$x = [];
foreach ( $items as $i ) {
2022-11-06 21:56:10 +00:00
$m = ObjCache :: Get ( $i [ 'mid' ]);
2021-12-02 23:02:31 +00:00
if ( $m ) {
$t = json_decode ( $m , true );
} else {
2023-12-19 10:08:08 +00:00
$t = self :: encode_activity ( $i , $activitypub );
2024-01-25 22:09:35 +00:00
if ( ! $t [ 'proof' ]) {
$channel = Channel :: from_hash ( $i [ 'author_xchan' ]);
$t [ 'proof' ] = ( new JcsEddsa2022 ()) -> sign ( $t , $channel );
}
2021-12-02 23:02:31 +00:00
}
if ( $t ) {
$x [] = $t ;
}
}
if ( $type === 'OrderedCollection' ) {
$ret [ 'orderedItems' ] = $x ;
} else {
$ret [ 'items' ] = $x ;
}
}
return $ret ;
}
2022-08-23 10:15:05 +00:00
public static function encode_follow_collection ( $items , $id , $type , $total = 0 , $extra = null ) : array
2021-12-02 23:02:31 +00:00
{
if ( $total > 100 ) {
$ret = [
'id' => z_root () . '/' . $id ,
'type' => $type . 'Page' ,
];
$numpages = $total / App :: $pager [ 'itemspage' ];
$lastpage = (( $numpages > intval ( $numpages )) ? intval ( $numpages ) + 1 : $numpages );
2022-09-16 10:57:04 +00:00
$stripped = preg_replace ( '/([&|?]page=[0-9]*)/' , '' , $id );
2021-12-02 23:02:31 +00:00
$stripped = rtrim ( $stripped , '/' );
$ret [ 'partOf' ] = z_root () . '/' . $stripped ;
if ( App :: $pager [ 'page' ] < $lastpage ) {
$ret [ 'next' ] = z_root () . '/' . $stripped . '?page=' . ( intval ( App :: $pager [ 'page' ]) + 1 );
}
if ( App :: $pager [ 'page' ] > 1 ) {
$ret [ 'prev' ] = z_root () . '/' . $stripped . '?page=' . ( intval ( App :: $pager [ 'page' ]) - 1 );
}
} else {
$ret = [
'id' => z_root () . '/' . $id ,
'type' => $type ,
'totalItems' => $total ,
];
}
if ( $extra ) {
$ret = array_merge ( $ret , $extra );
}
if ( $items ) {
$x = [];
foreach ( $items as $i ) {
if ( $i [ 'xchan_network' ] === 'activitypub' ) {
$x [] = $i [ 'xchan_hash' ];
} else {
$x [] = $i [ 'xchan_url' ];
}
}
if ( $type === 'OrderedCollection' ) {
$ret [ 'orderedItems' ] = $x ;
} else {
$ret [ 'items' ] = $x ;
}
}
return $ret ;
}
2022-08-23 10:15:05 +00:00
public static function encode_simple_collection ( $items , $id , $type , $total = 0 , $extra = null ) : array
2021-12-02 23:02:31 +00:00
{
$ret = [
'id' => z_root () . '/' . $id ,
'type' => $type ,
'totalItems' => $total ,
];
if ( $extra ) {
$ret = array_merge ( $ret , $extra );
}
if ( $items ) {
if ( $type === 'OrderedCollection' ) {
$ret [ 'orderedItems' ] = $items ;
} else {
$ret [ 'items' ] = $items ;
}
}
return $ret ;
}
public static function decode_taxonomy ( $item )
{
$ret = [];
if ( array_key_exists ( 'tag' , $item ) && is_array ( $item [ 'tag' ])) {
$ptr = $item [ 'tag' ];
if ( ! array_key_exists ( 0 , $ptr )) {
$ptr = [ $ptr ];
}
2023-07-09 08:28:02 +00:00
2021-12-02 23:02:31 +00:00
foreach ( $ptr as $t ) {
if ( ! is_array ( $t )) {
continue ;
}
if ( ! array_key_exists ( 'type' , $t )) {
$t [ 'type' ] = 'Hashtag' ;
}
2022-06-28 03:10:51 +00:00
2022-10-09 01:47:49 +00:00
if ( $t [ 'type' ] === 'Link' && isset ( $t [ 'href' ]) && isset ( $t [ 'mediaType' ]) && str_contains ( $t [ 'mediaType' ], 'activity' )) {
2022-08-03 21:23:36 +00:00
$entry = [ 'ttype' => TERM_QUOTED , 'url' => $t [ 'href' ]];
$entry [ 'term' ] = (( ! empty ( $t [ 'name' ])) ? escape_tags ( $t [ 'name' ]) : t ( 'Quoted post' ));
$ret [] = $entry ;
2022-06-28 03:10:51 +00:00
continue ;
}
2022-07-20 05:27:23 +00:00
2021-12-02 23:02:31 +00:00
if ( ! ( array_key_exists ( 'name' , $t ))) {
continue ;
}
switch ( $t [ 'type' ]) {
case 'Hashtag' :
2023-11-04 22:15:53 +00:00
$ret [] = [ 'ttype' => TERM_HASHTAG , 'url' => isset ( $t [ 'id' ]) ? $t [ 'id' ] : $t [ 'href' ], 'term' => escape_tags (( str_starts_with ( $t [ 'name' ], '#' )) ? substr ( $t [ 'name' ], 1 ) : $t [ 'name' ])];
2021-12-02 23:02:31 +00:00
break ;
case 'Category' :
$ret [] = [ 'ttype' => TERM_CATEGORY , 'url' => $t [ 'href' ], 'term' => escape_tags ( $t [ 'name' ])];
break ;
case 'Mention' :
$mention_type = substr ( $t [ 'name' ], 0 , 1 );
if ( $mention_type === '!' ) {
$ret [] = [ 'ttype' => TERM_FORUM , 'url' => $t [ 'href' ], 'term' => escape_tags ( substr ( $t [ 'name' ], 1 ))];
} else {
2022-10-09 01:47:49 +00:00
$ret [] = [ 'ttype' => TERM_MENTION , 'url' => $t [ 'href' ], 'term' => escape_tags (( str_starts_with ( $t [ 'name' ], '@' )) ? substr ( $t [ 'name' ], 1 ) : $t [ 'name' ])];
2021-12-02 23:02:31 +00:00
}
break ;
case 'Emoji' :
$ret [] = [ 'ttype' => TERM_EMOJI , 'url' => $t [ 'icon' ][ 'url' ], 'term' => escape_tags ( $t [ 'name' ])];
break ;
default :
break ;
}
}
}
return $ret ;
}
public static function encode_taxonomy ( $item )
{
$ret = [];
2023-07-09 08:28:02 +00:00
logger ( 'terms: ' . print_r ( $item [ 'term' ], true ));
2021-12-02 23:02:31 +00:00
if ( isset ( $item [ 'term' ]) && is_array ( $item [ 'term' ]) && $item [ 'term' ]) {
foreach ( $item [ 'term' ] as $t ) {
switch ( $t [ 'ttype' ]) {
2022-09-06 03:38:57 +00:00
case TERM_QUOTED :
if ( $t [ 'url' ] && $t [ 'term' ]) {
$ret [] = [ 'type' => 'Link' , 'href' => $t [ 'url' ], 'name' => $t [ 'term' ],
'mediaType' => 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"' ];
}
break ;
2021-12-02 23:02:31 +00:00
case TERM_HASHTAG :
2022-10-20 09:23:02 +00:00
// An id is required so if there is no url in the taxonomy, ignore it and keep going.
2023-07-09 22:34:55 +00:00
if ( $t [ 'url' ] && $t [ 'term' ]) {
$ret [] = [ 'type' => 'Hashtag' , 'id' => $t [ 'url' ], 'href' => $t [ 'url' ], 'name' => '#' . $t [ 'term' ]];
2021-12-02 23:02:31 +00:00
}
break ;
case TERM_CATEGORY :
if ( $t [ 'url' ] && $t [ 'term' ]) {
$ret [] = [ 'type' => 'Category' , 'href' => $t [ 'url' ], 'name' => $t [ 'term' ]];
}
break ;
case TERM_FORUM :
$term = self :: lookup_term_addr ( $t [ 'url' ], $t [ 'term' ]);
2022-10-11 07:59:26 +00:00
$ret [] = [ 'type' => 'Mention' , 'href' => $t [ 'url' ], 'name' => '!' . (( $term ) ? : $t [ 'term' ])];
2021-12-02 23:02:31 +00:00
break ;
case TERM_MENTION :
$term = self :: lookup_term_addr ( $t [ 'url' ], $t [ 'term' ]);
2022-10-11 07:59:26 +00:00
$ret [] = [ 'type' => 'Mention' , 'href' => $t [ 'url' ], 'name' => '@' . (( $term ) ? : $t [ 'term' ])];
2021-12-02 23:02:31 +00:00
break ;
default :
break ;
}
}
}
return $ret ;
}
public static function lookup_term_addr ( $url , $name )
{
// The visible mention in our activities is always the full name.
// In the object taxonomy change this to the webfinger handle in case
// platforms expect the Mastodon form in order to generate notifications
// Try a couple of different things in case the url provided isn't the canonical id.
// If all else fails, try to match the name.
if ( $url ) {
2021-12-03 03:01:39 +00:00
$r = q (
" select xchan_addr from xchan where ( xchan_url = '%s' OR xchan_hash = '%s' ) limit 1 " ,
2021-12-02 23:02:31 +00:00
dbesc ( $url ),
dbesc ( $url )
);
if ( $r ) {
return $r [ 0 ][ 'xchan_addr' ];
}
}
if ( $name ) {
2021-12-03 03:01:39 +00:00
$r = q (
" select xchan_addr from xchan where xchan_name = '%s' limit 1 " ,
2021-12-02 23:02:31 +00:00
dbesc ( $name )
);
if ( $r ) {
return $r [ 0 ][ 'xchan_addr' ];
}
}
return EMPTY_STR ;
}
public static function lookup_term_url ( $url )
{
// The xchan_url for mastodon is a text/html rendering. This is called from map_mentions where we need
// to convert the mention url to an ActivityPub id. If this fails for any reason, return the url we have
2022-06-17 02:46:54 +00:00
$r = hubloc_id_query ( $url , 1 );
2021-12-02 23:02:31 +00:00
if ( $r ) {
if ( $r [ 0 ][ 'hubloc_network' ] === 'activitypub' ) {
return $r [ 0 ][ 'hubloc_hash' ];
}
return $r [ 0 ][ 'hubloc_id_url' ];
}
return $url ;
}
2022-06-23 23:37:11 +00:00
/**
* Convert an item attach list to an ActivityStreams attachment array
*/
2021-12-02 23:02:31 +00:00
public static function encode_attachment ( $item )
{
$ret = [];
if ( array_key_exists ( 'attach' , $item )) {
$atts = (( is_array ( $item [ 'attach' ])) ? $item [ 'attach' ] : json_decode ( $item [ 'attach' ], true ));
if ( $atts ) {
foreach ( $atts as $att ) {
2023-02-05 00:55:19 +00:00
$name = '' ;
2022-10-09 01:47:49 +00:00
if ( isset ( $att [ 'type' ]) && str_contains ( $att [ 'type' ], 'image' )) {
2023-01-30 18:30:05 +00:00
if ( ! empty ( $att [ 'href' ])) {
2023-02-05 00:55:19 +00:00
if ( $att [ 'name' ]) {
$name = $att [ 'name' ];
}
2023-01-30 18:30:05 +00:00
if ( ! $name ) {
2023-02-05 00:55:19 +00:00
$name = $att [ 'title' ];
2023-01-30 18:30:05 +00:00
}
2022-08-09 21:39:09 +00:00
$ret [] = [
'type' => 'Image' ,
2023-01-30 18:30:05 +00:00
'url' => $att [ 'href' ],
'name' => $name ,
2022-08-09 21:39:09 +00:00
];
}
2021-12-02 23:02:31 +00:00
} else {
2022-06-23 23:37:11 +00:00
$ret [] = [
'type' => 'Link' ,
2023-11-04 22:15:53 +00:00
'mediaType' => isset ( $att [ 'type' ]) ? $att [ 'type' ] : 'application/octet-stream' ,
'href' => isset ( $att [ 'href' ]) ? $att [ 'href' ] : ''
2022-06-23 23:37:11 +00:00
];
2021-12-02 23:02:31 +00:00
}
}
}
}
if ( array_key_exists ( 'iconfig' , $item ) && is_array ( $item [ 'iconfig' ])) {
foreach ( $item [ 'iconfig' ] as $att ) {
if ( $att [ 'sharing' ]) {
2023-05-16 11:06:18 +00:00
$ret [] = [ 'type' => 'Note' , 'name' => 'nomad.' . $att [ 'cat' ] . '.' . $att [ 'k' ], 'content' => unserialise ( $att [ 'v' ])];
2021-12-02 23:02:31 +00:00
}
}
}
return $ret ;
}
public static function decode_iconfig ( $item )
{
$ret = [];
2022-03-12 05:17:09 +00:00
if ( isset ( $item [ 'attachment' ]) && is_array ( $item [ 'attachment' ]) && $item [ 'attachment' ]) {
2021-12-02 23:02:31 +00:00
$ptr = $item [ 'attachment' ];
if ( ! array_key_exists ( 0 , $ptr )) {
$ptr = [ $ptr ];
}
foreach ( $ptr as $att ) {
$entry = [];
2023-05-16 11:06:18 +00:00
if ( $att [ 'type' ] === 'Note' ) {
2021-12-02 23:02:31 +00:00
if ( array_key_exists ( 'name' , $att ) && $att [ 'name' ]) {
$key = explode ( '.' , $att [ 'name' ]);
2022-06-23 23:39:49 +00:00
if ( count ( $key ) === 3 && in_array ( $key [ 0 ], [ 'nomad' , 'zot' ])) {
2021-12-02 23:02:31 +00:00
$entry [ 'cat' ] = $key [ 1 ];
$entry [ 'k' ] = $key [ 2 ];
2023-05-16 11:06:18 +00:00
$entry [ 'v' ] = $att [ 'content' ];
2021-12-02 23:02:31 +00:00
$entry [ 'sharing' ] = '1' ;
$ret [] = $entry ;
}
}
}
}
}
return $ret ;
}
2022-06-23 23:37:11 +00:00
/**
* Decodes and Activity object attachment structure to store in an item .
*/
2022-07-20 05:27:23 +00:00
2022-06-23 23:37:11 +00:00
public static function decode_attachment ( $obj )
2021-12-02 23:02:31 +00:00
{
$ret = [];
2022-06-23 23:37:11 +00:00
if ( array_key_exists ( 'attachment' , $obj ) && is_array ( $obj [ 'attachment' ])) {
$ptr = $obj [ 'attachment' ];
2021-12-02 23:02:31 +00:00
if ( ! array_key_exists ( 0 , $ptr )) {
$ptr = [ $ptr ];
}
foreach ( $ptr as $att ) {
$entry = [];
2021-12-03 03:01:39 +00:00
if ( array_key_exists ( 'href' , $att ) && $att [ 'href' ]) {
2021-12-02 23:02:31 +00:00
$entry [ 'href' ] = $att [ 'href' ];
2021-12-03 03:01:39 +00:00
} elseif ( array_key_exists ( 'url' , $att ) && $att [ 'url' ]) {
2021-12-02 23:02:31 +00:00
$entry [ 'href' ] = $att [ 'url' ];
2021-12-03 03:01:39 +00:00
}
if ( array_key_exists ( 'mediaType' , $att ) && $att [ 'mediaType' ]) {
2021-12-02 23:02:31 +00:00
$entry [ 'type' ] = $att [ 'mediaType' ];
2021-12-03 03:01:39 +00:00
} elseif ( array_key_exists ( 'type' , $att ) && $att [ 'type' ] === 'Image' ) {
2021-12-02 23:02:31 +00:00
$entry [ 'type' ] = 'image/jpeg' ;
2021-12-03 03:01:39 +00:00
}
2021-12-02 23:02:31 +00:00
if ( array_key_exists ( 'name' , $att ) && $att [ 'name' ]) {
$entry [ 'name' ] = html2plain ( purify_html ( $att [ 'name' ]), 256 );
2023-08-27 21:54:30 +00:00
$entry [ 'name' ] = str_replace ( '"' , '"' , $entry [ 'name' ]);
2021-12-02 23:02:31 +00:00
}
2022-07-07 22:31:16 +00:00
// Friendica attachments don't match the URL in the body.
// This makes it more difficult to detect image duplication in bb_attach()
// which adds images to plaintext microblog software. For these we need to examine both the
// url and image properties.
if ( isset ( $att [ 'image' ]) && is_string ( $att [ 'image' ]) && isset ( $att [ 'url' ]) && $att [ 'image' ] !== $att [ 'url' ]) {
$entry [ 'image' ] = $att [ 'image' ];
}
2021-12-03 03:01:39 +00:00
if ( $entry ) {
2021-12-02 23:02:31 +00:00
$ret [] = $entry ;
2021-12-03 03:01:39 +00:00
}
2021-12-02 23:02:31 +00:00
}
}
return $ret ;
}
2023-12-13 06:08:37 +00:00
public static function isContainer ( $item )
{
$query = q ( " select * from channel where channel_hash = '%s' and channel_id = %d " ,
dbesc ( $item [ 'owner_xchan' ]),
intval ( $item [ 'uid' ])
);
2023-12-19 10:08:08 +00:00
return ( bool ) $query ;
2023-12-13 06:08:37 +00:00
}
2021-12-02 23:02:31 +00:00
// the $recurse flag encodes the original non-deleted object of a deleted activity
2023-12-19 10:08:08 +00:00
public static function encode_activity ( $item , $activitypub = false , $recurse = false )
2021-12-02 23:02:31 +00:00
{
2022-12-04 18:15:43 +00:00
$activity = [];
2021-12-02 23:02:31 +00:00
2022-12-04 18:15:43 +00:00
if ( intval ( $item [ 'item_deleted' ]) && ( ! $recurse )) {
$is_response = ActivityStreams :: is_response_activity ( $item [ 'verb' ]);
2021-12-02 23:02:31 +00:00
if ( $is_response ) {
2022-12-04 18:15:43 +00:00
$activity [ 'type' ] = 'Undo' ;
2021-12-02 23:02:31 +00:00
$fragment = '#undo' ;
} else {
2022-12-04 18:15:43 +00:00
$activity [ 'type' ] = 'Delete' ;
2021-12-02 23:02:31 +00:00
$fragment = '#delete' ;
}
2022-12-04 18:15:43 +00:00
$activity [ 'id' ] = str_replace ( '/item/' , '/activity/' , $item [ 'mid' ]) . $fragment ;
2024-01-11 18:34:22 +00:00
$actor = self :: actorEncode ( $item [ 'author' ], false );
2021-12-03 03:01:39 +00:00
if ( $actor ) {
2022-12-04 18:15:43 +00:00
$activity [ 'actor' ] = $actor ;
2021-12-03 03:01:39 +00:00
} else {
2020-10-06 02:41:47 +00:00
return [];
2021-12-03 03:01:39 +00:00
}
2018-05-30 04:08:52 +00:00
2023-12-19 10:08:08 +00:00
$obj = (( $is_response ) ? self :: encode_activity ( $item , $activitypub , true ) : self :: encode_item ( $item , $activitypub ));
2021-12-02 23:02:31 +00:00
if ( $obj ) {
if ( array_path_exists ( 'object/id' , $obj )) {
$obj [ 'object' ] = $obj [ 'object' ][ 'id' ];
}
if ( $obj ) {
2022-12-04 18:15:43 +00:00
$activity [ 'object' ] = $obj ;
2021-12-02 23:02:31 +00:00
}
} else {
2020-10-06 02:41:47 +00:00
return [];
2021-12-02 23:02:31 +00:00
}
2018-05-30 04:08:52 +00:00
2022-12-04 18:15:43 +00:00
$activity [ 'to' ] = [ ACTIVITY_PUBLIC_INBOX ];
return $activity ;
2021-12-02 23:02:31 +00:00
}
2023-12-19 10:08:08 +00:00
$activity [ 'type' ] = self :: activity_mapper ( $item [ 'verb' ]);
2021-12-02 23:02:31 +00:00
2022-12-04 18:15:43 +00:00
if ( str_contains ( $item [ 'mid' ], z_root () . '/item/' )) {
$activity [ 'id' ] = str_replace ( '/item/' , '/activity/' , $item [ 'mid' ]);
} elseif ( str_contains ( $item [ 'mid' ], z_root () . '/event/' )) {
$activity [ 'id' ] = str_replace ( '/event/' , '/activity/' , $item [ 'mid' ]);
2021-12-02 23:02:31 +00:00
} else {
2022-12-04 18:15:43 +00:00
$activity [ 'id' ] = $item [ 'mid' ];
2021-12-02 23:02:31 +00:00
}
2022-12-04 18:15:43 +00:00
if ( $item [ 'title' ]) {
$activity [ 'name' ] = $item [ 'title' ];
2021-12-02 23:02:31 +00:00
}
2022-12-04 18:15:43 +00:00
if ( $item [ 'summary' ]) {
$activity [ 'summary' ] = bbcode ( $item [ 'summary' ], [ 'export' => true ]);
2021-12-02 23:02:31 +00:00
}
2022-12-04 18:15:43 +00:00
if ( $activity [ 'type' ] === 'Announce' ) {
$tmp = $item [ 'body' ];
$activity [ 'content' ] = bbcode ( $tmp , [ 'export' => true ]);
$activity [ 'source' ] = [
'content' => $item [ 'body' ],
2021-12-06 00:49:55 +00:00
'mediaType' => 'text/x-multicode'
2021-12-02 23:02:31 +00:00
];
2022-12-04 18:15:43 +00:00
if ( $item [ 'summary' ]) {
$activity [ 'source' ][ 'summary' ] = $item [ 'summary' ];
2021-12-02 23:02:31 +00:00
}
}
2018-05-30 04:08:52 +00:00
2022-12-04 18:15:43 +00:00
$activity [ 'published' ] = datetime_convert ( 'UTC' , 'UTC' , $item [ 'created' ], ATOM_TIME );
if ( $item [ 'created' ] !== $item [ 'edited' ]) {
$activity [ 'updated' ] = datetime_convert ( 'UTC' , 'UTC' , $item [ 'edited' ], ATOM_TIME );
if ( $activity [ 'type' ] === 'Create' ) {
$activity [ 'type' ] = 'Update' ;
2021-12-02 23:02:31 +00:00
}
}
2022-12-04 18:15:43 +00:00
if ( $item [ 'app' ]) {
$activity [ 'generator' ] = [ 'type' => 'Application' , 'name' => $item [ 'app' ]];
2021-12-02 23:02:31 +00:00
}
2022-12-04 18:15:43 +00:00
if ( $item [ 'location' ] || $item [ 'lat' ] || $item [ 'lon' ]) {
2023-08-03 10:59:25 +00:00
$place = ( new Place ()) -> setType ( 'Place' )
2023-11-04 22:15:53 +00:00
-> setName ( isset ( $item [ 'location' ]) ? $item [ 'location' ] : null );
2022-12-04 18:15:43 +00:00
if ( $item [ 'lat' ] || $item [ 'lon' ]) {
2023-11-04 22:15:53 +00:00
$place -> setLatitude ( isset ( $item [ 'lat' ]) ? $item [ 'lat' ] : 0 )
-> setLongitude ( isset ( $item [ 'lon' ]) ? $item [ 'lon' ] : 0 );
2021-12-02 23:02:31 +00:00
}
2023-12-19 10:08:08 +00:00
$activity [ 'location' ] = $place -> toArray ();
2021-12-02 23:02:31 +00:00
}
2024-01-13 11:04:08 +00:00
if ( $item [ 'mid' ] !== $item [ 'parent_mid' ]) {
2021-12-02 23:02:31 +00:00
// inReplyTo needs to be set in the activity for followup actions (Like, Dislike, Announce, etc.),
// but *not* for comments and RSVPs, where it should only be present in the object
2023-12-14 12:05:27 +00:00
if ( ! in_array ( $activity [ 'type' ], [ 'Create' , 'Update' , 'Add' , 'Remove' ])) {
2022-12-04 18:15:43 +00:00
$activity [ 'inReplyTo' ] = $item [ 'thr_parent' ];
2022-03-15 22:15:39 +00:00
}
2022-07-20 05:27:23 +00:00
2023-09-29 22:31:07 +00:00
if ( in_array ( $activity [ 'type' ], [ 'Accept' , 'Reject' , 'TentativeAccept' , 'TentativeReject' ])) {
$activity [ 'inReplyTo' ] = set_activity_mid ( $item [ 'thr_parent' ]);
2022-12-11 19:16:17 +00:00
}
2023-09-29 22:31:07 +00:00
// @FIXME FEP-5624 set for comment approvals but not event approvals
// For comment approvals and rejections
// if (in_array($activity['type'], ['Accept','Reject']) && is_string($item['obj']) && strlen($item['obj'])) {
// $activity['inReplyTo'] = $item['thr_parent'];
// }
2022-12-04 18:15:43 +00:00
$cnv = get_iconfig ( $item [ 'parent' ], 'activitypub' , 'context' );
2022-03-15 22:15:39 +00:00
if ( ! $cnv ) {
2023-11-08 21:36:24 +00:00
$cnv = $item [ 'parent_mid' ];
2021-12-02 23:02:31 +00:00
}
}
2018-05-30 04:08:52 +00:00
2021-12-02 23:02:31 +00:00
if ( ! ( isset ( $cnv ) && $cnv )) {
2022-12-04 18:15:43 +00:00
$cnv = get_iconfig ( $item , 'activitypub' , 'context' );
2021-12-02 23:02:31 +00:00
if ( ! $cnv ) {
2022-12-04 18:15:43 +00:00
$cnv = $item [ 'parent_mid' ];
2021-12-02 23:02:31 +00:00
}
}
2023-01-20 21:22:35 +00:00
if ( ! empty ( $cnv )) {
2022-10-09 01:47:49 +00:00
if ( is_string ( $cnv ) && str_starts_with ( $cnv , z_root ())) {
2021-12-02 23:02:31 +00:00
$cnv = str_replace ([ '/item/' , '/activity/' ], [ '/conversation/' , '/conversation/' ], $cnv );
}
2022-12-04 18:15:43 +00:00
$activity [ 'context' ] = $cnv ;
2021-12-02 23:02:31 +00:00
}
2022-12-04 18:15:43 +00:00
if ( intval ( $item [ 'item_private' ]) === 2 ) {
$activity [ 'directMessage' ] = true ;
2021-12-02 23:02:31 +00:00
}
2024-01-11 18:34:22 +00:00
$actor = self :: actorEncode ( $item [ 'author' ], false );
2021-12-03 03:01:39 +00:00
if ( $actor ) {
2022-12-04 18:15:43 +00:00
$activity [ 'actor' ] = $actor ;
2021-12-03 03:01:39 +00:00
} else {
2021-12-02 23:02:31 +00:00
return [];
2021-12-03 03:01:39 +00:00
}
2021-12-02 23:02:31 +00:00
2022-12-04 18:15:43 +00:00
if ( ! isset ( $activity [ 'url' ])) {
2021-12-02 23:02:31 +00:00
$urls = [];
2022-12-04 18:15:43 +00:00
if ( intval ( $item [ 'item_wall' ])) {
$locs = self :: nomadic_locations ( $item );
2021-12-02 23:02:31 +00:00
if ( $locs ) {
foreach ( $locs as $l ) {
2022-12-04 18:15:43 +00:00
if ( str_contains ( $activity [ 'id' ], $l [ 'hubloc_url' ])) {
2021-12-02 23:02:31 +00:00
continue ;
}
$urls [] = [
'type' => 'Link' ,
2022-12-04 18:15:43 +00:00
'href' => str_replace ( z_root (), $l [ 'hubloc_url' ], $activity [ 'id' ]),
2021-12-02 23:02:31 +00:00
'rel' => 'alternate' ,
'mediaType' => 'text/html'
];
$urls [] = [
'type' => 'Link' ,
2022-12-04 18:15:43 +00:00
'href' => str_replace ( z_root (), $l [ 'hubloc_url' ], $activity [ 'id' ]),
2021-12-02 23:02:31 +00:00
'rel' => 'alternate' ,
'mediaType' => 'application/activity+json'
];
$urls [] = [
'type' => 'Link' ,
2022-12-04 18:15:43 +00:00
'href' => str_replace ( z_root (), $l [ 'hubloc_url' ], $activity [ 'id' ]),
2021-12-02 23:02:31 +00:00
'rel' => 'alternate' ,
'mediaType' => 'application/x-zot+json'
];
2021-12-06 00:49:55 +00:00
$urls [] = [
'type' => 'Link' ,
2022-12-04 18:15:43 +00:00
'href' => str_replace ( z_root (), $l [ 'hubloc_url' ], $activity [ 'id' ]),
2021-12-06 00:49:55 +00:00
'rel' => 'alternate' ,
'mediaType' => 'application/x-nomad+json'
];
2021-12-02 23:02:31 +00:00
}
}
}
if ( $urls ) {
$curr [] = [
'type' => 'Link' ,
2022-12-04 18:15:43 +00:00
'href' => $activity [ 'id' ],
2021-12-02 23:02:31 +00:00
'rel' => 'alternate' ,
'mediaType' => 'text/html'
];
2022-12-04 18:15:43 +00:00
$activity [ 'url' ] = array_merge ( $curr , $urls );
2021-12-02 23:02:31 +00:00
} else {
2022-12-04 18:15:43 +00:00
$activity [ 'url' ] = $activity [ 'id' ];
2021-12-02 23:02:31 +00:00
}
}
2018-05-30 04:08:52 +00:00
2022-12-04 18:15:43 +00:00
if ( $item [ 'obj' ]) {
if ( is_string ( $item [ 'obj' ])) {
$tmp = json_decode ( $item [ 'obj' ], true );
2021-12-03 03:01:39 +00:00
if ( $tmp !== null ) {
2022-12-04 18:15:43 +00:00
$item [ 'obj' ] = $tmp ;
2021-12-02 23:02:31 +00:00
}
}
2022-12-04 18:15:43 +00:00
$obj = self :: encode_object ( $item [ 'obj' ]);
2022-10-20 09:23:02 +00:00
}
else {
2022-12-04 18:15:43 +00:00
$obj = self :: encode_item ( $item , $activitypub );
2022-10-20 09:23:02 +00:00
}
if ( $obj ) {
2022-12-04 18:15:43 +00:00
$activity [ 'object' ] = $obj ;
2022-10-20 09:23:02 +00:00
} else {
return [];
2021-12-02 23:02:31 +00:00
}
2022-12-04 18:15:43 +00:00
if ( $item [ 'target' ]) {
if ( is_string ( $item [ 'target' ])) {
$tmp = json_decode ( $item [ 'target' ], true );
2021-12-03 03:01:39 +00:00
if ( $tmp !== null ) {
2022-12-04 18:15:43 +00:00
$item [ 'target' ] = $tmp ;
2021-12-02 23:02:31 +00:00
}
}
2022-12-04 18:15:43 +00:00
$tgt = self :: encode_object ( $item [ 'target' ]);
2021-12-02 23:02:31 +00:00
if ( $tgt ) {
2022-12-04 18:15:43 +00:00
$activity [ 'target' ] = $tgt ;
2021-12-02 23:02:31 +00:00
}
}
2022-12-04 18:15:43 +00:00
$t = self :: encode_taxonomy ( $item );
2021-12-02 23:02:31 +00:00
if ( $t ) {
2023-01-02 20:54:03 +00:00
foreach ( $t as $tag ) {
2023-10-01 08:20:52 +00:00
if ( strcasecmp ( $tag [ 'name' ], '#nsfw' ) === 0
|| strcasecmp ( $tag [ 'name' ], '#sensitive' ) === 0 ) {
2023-01-02 20:54:03 +00:00
$activity [ 'sensitive' ] = true ;
}
2023-12-09 09:57:55 +00:00
}
$activity [ 'tag' ] = $t ;
}
if ( $obj && $obj [ 'attachment' ]) {
$activity [ 'attachment' ] = $obj [ 'attachment' ];
}
else {
$a = self :: encode_attachment ( $item );
if ( $a ) {
$activity [ 'attachment' ] = $a ;
}
}
// addressing madness
if ( $activitypub ) {
$parent_i = [];
$public = ! $item [ 'item_private' ];
$top_level = ( $item [ 'mid' ] === $item [ 'parent_mid' ]);
$activity [ 'to' ] = [];
$activity [ 'cc' ] = [];
$recips = get_iconfig ( $item [ 'parent' ], 'activitypub' , 'recips' );
if ( $recips ) {
$parent_i [ 'to' ] = $recips [ 'to' ];
$parent_i [ 'cc' ] = $recips [ 'cc' ];
}
if ( $public ) {
$activity [ 'to' ] = [ ACTIVITY_PUBLIC_INBOX ];
if ( isset ( $parent_i [ 'to' ]) && is_array ( $parent_i [ 'to' ])) {
$activity [ 'to' ] = array_values ( array_unique ( array_merge ( $activity [ 'to' ], $parent_i [ 'to' ])));
}
if ( $item [ 'item_origin' ]) {
$activity [ 'cc' ] = [ z_root () . '/followers/' . substr ( $item [ 'author' ][ 'xchan_addr' ], 0 , strpos ( $item [ 'author' ][ 'xchan_addr' ], '@' ))];
}
if ( isset ( $parent_i [ 'cc' ]) && is_array ( $parent_i [ 'cc' ])) {
$activity [ 'cc' ] = array_values ( array_unique ( array_merge ( $activity [ 'cc' ], $parent_i [ 'cc' ])));
}
} else {
// private activity
if ( $top_level ) {
$activity [ 'to' ] = self :: map_acl ( $item );
if ( isset ( $parent_i [ 'to' ]) && is_array ( $parent_i [ 'to' ])) {
$activity [ 'to' ] = array_values ( array_unique ( array_merge ( $activity [ 'to' ], $parent_i [ 'to' ])));
}
2024-01-02 01:25:37 +00:00
} elseif (( int ) $item [ 'item_private' ] === 1 ) {
2023-12-09 09:57:55 +00:00
$activity [ 'cc' ] = self :: map_acl ( $item );
if ( isset ( $parent_i [ 'cc' ]) && is_array ( $parent_i [ 'cc' ])) {
$activity [ 'cc' ] = array_values ( array_unique ( array_merge ( $activity [ 'cc' ], $parent_i [ 'cc' ])));
}
$d = q (
" select hubloc.* from hubloc left join item on hubloc_hash = owner_xchan where item.parent_mid = '%s' and item.uid = %d and hubloc_deleted = 0 order by hubloc_id desc limit 1 " ,
dbesc ( $item [ 'parent_mid' ]),
intval ( $item [ 'uid' ])
);
if ( $d ) {
if ( $d [ 0 ][ 'hubloc_network' ] === 'activitypub' ) {
$addr = $d [ 0 ][ 'hubloc_hash' ];
} else {
$addr = $d [ 0 ][ 'hubloc_id_url' ];
}
$activity [ 'cc' ][] = $addr ;
}
}
}
$mentions = self :: map_mentions ( $item );
if ( count ( $mentions ) > 0 ) {
if ( ! $activity [ 'to' ]) {
$activity [ 'to' ] = $mentions ;
} else {
$activity [ 'to' ] = array_values ( array_unique ( array_merge ( $activity [ 'to' ], $mentions )));
}
}
}
$cc = [];
if ( $activity [ 'cc' ] && is_array ( $activity [ 'cc' ])) {
foreach ( $activity [ 'cc' ] as $e ) {
if ( ! is_array ( $activity [ 'to' ])) {
$cc [] = $e ;
} elseif ( ! in_array ( $e , $activity [ 'to' ])) {
$cc [] = $e ;
}
}
}
$activity [ 'cc' ] = $cc ;
return $activity ;
}
2021-12-02 23:02:31 +00:00
public static function nomadic_locations ( $item )
{
$synchubs = [];
2021-12-03 03:01:39 +00:00
$h = q (
2022-07-20 05:27:23 +00:00
" select hubloc.*, site.site_crypto from hubloc left join site on site_url = hubloc_url
where hubloc_hash = '%s' and hubloc_network in ( 'zot6' , 'nomad' ) and hubloc_deleted = 0 " ,
2021-12-02 23:02:31 +00:00
dbesc ( $item [ 'author_xchan' ])
);
2018-05-30 04:08:52 +00:00
2021-12-02 23:02:31 +00:00
if ( ! $h ) {
return [];
}
2018-05-30 04:08:52 +00:00
2021-12-02 23:02:31 +00:00
foreach ( $h as $x ) {
2021-12-03 03:01:39 +00:00
$y = q (
" select site_dead from site where site_url = '%s' limit 1 " ,
2021-12-02 23:02:31 +00:00
dbesc ( $x [ 'hubloc_url' ])
);
2018-05-30 04:08:52 +00:00
2021-12-02 23:02:31 +00:00
if (( ! $y ) || intval ( $y [ 0 ][ 'site_dead' ]) === 0 ) {
$synchubs [] = $x ;
}
}
2018-05-30 04:08:52 +00:00
2021-12-02 23:02:31 +00:00
return $synchubs ;
}
2018-05-30 04:08:52 +00:00
2022-12-04 18:15:43 +00:00
public static function encode_item ( $item , $activitypub = false )
2021-12-02 23:02:31 +00:00
{
2022-12-04 18:15:43 +00:00
$activity = [];
2021-12-02 23:02:31 +00:00
$bbopts = (( $activitypub ) ? 'activitypub' : 'export' );
2018-05-30 04:08:52 +00:00
2022-12-04 18:15:43 +00:00
$objtype = self :: activity_obj_mapper ( $item [ 'obj_type' ]);
2018-05-30 04:08:52 +00:00
2022-12-04 18:15:43 +00:00
if ( intval ( $item [ 'item_deleted' ])) {
$activity [ 'type' ] = 'Tombstone' ;
$activity [ 'formerType' ] = $objtype ;
$activity [ 'id' ] = $item [ 'mid' ];
$activity [ 'to' ] = [ ACTIVITY_PUBLIC_INBOX ];
return $activity ;
2021-12-02 23:02:31 +00:00
}
2018-05-30 04:08:52 +00:00
2022-12-04 18:15:43 +00:00
if ( isset ( $item [ 'obj' ]) && $item [ 'obj' ]) {
if ( is_string ( $item [ 'obj' ])) {
$tmp = json_decode ( $item [ 'obj' ], true );
2021-12-03 03:01:39 +00:00
if ( $tmp !== null ) {
2022-12-04 18:15:43 +00:00
$item [ 'obj' ] = $tmp ;
2021-12-02 23:02:31 +00:00
}
}
2022-12-04 18:15:43 +00:00
$activity = $item [ 'obj' ];
if ( is_string ( $activity )) {
return $activity ;
2021-12-02 23:02:31 +00:00
}
}
2019-09-22 16:16:07 +00:00
2022-12-04 18:15:43 +00:00
$activity [ 'type' ] = $objtype ;
2020-01-21 21:07:41 +00:00
2021-12-02 23:02:31 +00:00
if ( $objtype === 'Question' ) {
2022-12-04 18:15:43 +00:00
if ( $item [ 'obj' ]) {
if ( is_array ( $item [ 'obj' ])) {
$activity = $item [ 'obj' ];
2021-12-02 23:02:31 +00:00
} else {
2022-12-04 18:15:43 +00:00
$activity = json_decode ( $item [ 'obj' ], true );
2021-12-02 23:02:31 +00:00
}
2018-05-30 04:08:52 +00:00
2022-12-04 18:15:43 +00:00
if ( array_path_exists ( 'actor' , $activity )) {
2024-01-11 18:34:22 +00:00
$activity [ 'actor' ] = self :: actorEncode ( $activity [ 'actor' ], false );
2021-12-02 23:02:31 +00:00
}
}
}
$images = false ;
2023-07-22 02:24:18 +00:00
// protect code blocks before searching for images
$item [ 'body' ] = preg_replace_callback ( '#(^|\n)([`~]{3,})(?: *\.?([a-zA-Z0-9\-.]+))?\n+([\s\S]+?)\n+\2(\n|$)#' , function ( $match ) {
return $match [ 1 ] . $match [ 2 ] . " \n " . bb_code_protect ( $match [ 4 ]) . " \n " . $match [ 2 ] . (( $match [ 5 ]) ? : " \n " );
}, $item [ 'body' ]);
2023-09-17 22:08:30 +00:00
$item [ 'body' ] = preg_replace_callback ( '/\[code(.*?)\[\/(code)]/ism' , '\red_escape_codeblock' , $item [ 'body' ]);
2023-07-22 02:24:18 +00:00
// find all inline images (outside protected code blocks) to add back as attachments
2022-12-04 18:15:43 +00:00
$has_images = preg_match_all ( '/\[[zi]mg(.*?)](.*?)\[/ism' , $item [ 'body' ], $images , PREG_SET_ORDER );
2021-12-02 23:02:31 +00:00
2023-09-17 22:08:30 +00:00
$item [ 'body' ] = preg_replace_callback ( '/\[\$b64code(.*?)\[\/(code)]/ism' , '\red_unescape_codeblock' , $item [ 'body' ]);
2023-07-22 02:24:18 +00:00
$item [ 'body' ] = bb_code_unprotect ( $item [ 'body' ]);
2022-12-04 18:15:43 +00:00
$activity [ 'id' ] = $item [ 'mid' ];
2021-12-02 23:02:31 +00:00
2022-12-04 18:15:43 +00:00
$activity [ 'published' ] = datetime_convert ( 'UTC' , 'UTC' , $item [ 'created' ], ATOM_TIME );
if ( $item [ 'created' ] !== $item [ 'edited' ]) {
$activity [ 'updated' ] = datetime_convert ( 'UTC' , 'UTC' , $item [ 'edited' ], ATOM_TIME );
2021-12-02 23:02:31 +00:00
}
2022-12-04 18:15:43 +00:00
if ( $item [ 'expires' ] > NULL_DATE ) {
$activity [ 'expires' ] = datetime_convert ( 'UTC' , 'UTC' , $item [ 'expires' ], ATOM_TIME );
2021-12-02 23:02:31 +00:00
}
2022-12-04 18:15:43 +00:00
if ( $item [ 'app' ]) {
$activity [ 'generator' ] = [ 'type' => 'Application' , 'name' => $item [ 'app' ]];
2021-12-02 23:02:31 +00:00
}
2022-12-04 18:15:43 +00:00
if ( $item [ 'location' ] || $item [ 'lat' ] || $item [ 'lon' ]) {
2023-11-04 22:15:53 +00:00
$place = ( new Place ()) -> setType ( 'Place' ) -> setName ( isset ( $item [ 'location' ]) ? $item [ 'location' ] : null );
2022-12-04 18:15:43 +00:00
if ( $item [ 'lat' ] || $item [ 'lon' ]) {
2023-11-04 22:15:53 +00:00
$place -> setLatitude ( isset ( $item [ 'lat' ]) ? $item [ 'lat' ] : 0 ) -> setLongitude ( isset ( $item [ 'lon' ]) ? $item [ 'lon' ] : 0 );
2021-12-02 23:02:31 +00:00
}
2023-08-03 10:59:25 +00:00
$activity [ 'location' ] = $place -> toArray ();
2021-12-02 23:02:31 +00:00
}
2022-12-04 18:15:43 +00:00
if ( intval ( $item [ 'item_wall' ]) && $item [ 'mid' ] === $item [ 'parent_mid' ]) {
$activity [ 'commentPolicy' ] = $item [ 'comment_policy' ];
2021-12-02 23:02:31 +00:00
}
2018-05-30 04:08:52 +00:00
2023-12-14 11:36:44 +00:00
2022-12-04 18:15:43 +00:00
if ( intval ( $item [ 'item_private' ]) === 2 ) {
$activity [ 'directMessage' ] = true ;
2021-12-02 23:02:31 +00:00
}
2018-05-30 04:08:52 +00:00
2022-11-18 22:11:19 +00:00
// avoid double addition of the until= clause
2022-12-04 18:15:43 +00:00
if ( ! str_contains ( $activity [ 'commentPolicy' ], 'until=' )) {
if ( intval ( $item [ 'item_nocomment' ])) {
if ( $activity [ 'commentPolicy' ]) {
$activity [ 'commentPolicy' ] .= ' ' ;
2022-11-18 22:11:19 +00:00
}
2022-12-04 18:15:43 +00:00
$activity [ 'commentPolicy' ] .= 'until=' . datetime_convert ( 'UTC' , 'UTC' , $item [ 'created' ], ATOM_TIME );
} elseif ( array_key_exists ( 'comments_closed' , $item ) && $item [ 'comments_closed' ] !== EMPTY_STR && $item [ 'comments_closed' ] > NULL_DATE ) {
if ( $activity [ 'commentPolicy' ]) {
$activity [ 'commentPolicy' ] .= ' ' ;
2022-11-18 22:11:19 +00:00
}
2022-12-04 18:15:43 +00:00
$activity [ 'commentPolicy' ] .= 'until=' . datetime_convert ( 'UTC' , 'UTC' , $item [ 'comments_closed' ], ATOM_TIME );
2021-12-02 23:02:31 +00:00
}
}
2018-05-30 04:08:52 +00:00
2024-01-11 18:34:22 +00:00
$activity [ 'attributedTo' ] = self :: actorEncode ( $item [ 'author' ], false );
2022-11-28 20:53:03 +00:00
2022-12-04 18:15:43 +00:00
if ( $item [ 'mid' ] === $item [ 'parent_mid' ]) {
if ( in_array ( $activity [ 'commentPolicy' ], [ 'public' , 'authenticated' ])) {
$activity [ 'canReply' ] = ACTIVITY_PUBLIC_INBOX ;
} elseif ( in_array ( $activity [ 'commentPolicy' ], [ 'contacts' , 'specific' ])) {
$activity [ 'canReply' ] = z_root () . '/followers/' . substr ( $item [ 'author' ][ 'xchan_addr' ], 0 , strpos ( $item [ 'author' ][ 'xchan_addr' ], '@' ));
2023-01-22 09:14:02 +00:00
} elseif ( in_array ( $activity [ 'commentPolicy' ], [ 'self' , 'none' ]) || $item [ 'item_nocomment' ] || datetime_convert ( 'UTC' , 'UTC' , $item [ 'comments_closed' ]) <= datetime_convert ()) {
2022-12-04 18:15:43 +00:00
$activity [ 'canReply' ] = [];
}
}
2018-05-30 04:08:52 +00:00
2022-12-04 18:15:43 +00:00
if ( $item [ 'mid' ] !== $item [ 'parent_mid' ]) {
if ( $item [ 'approved' ]) {
2022-12-20 22:12:07 +00:00
$activity [ 'approval' ] = $item [ 'approved' ];
2022-12-04 18:15:43 +00:00
}
$activity [ 'inReplyTo' ] = $item [ 'thr_parent' ];
$cnv = get_iconfig ( $item [ 'parent' ], 'activitypub' , 'context' );
2021-12-02 23:02:31 +00:00
if ( ! $cnv ) {
2023-11-30 09:22:11 +00:00
$cnv = $item [ 'parent_mid' ];
2021-12-02 23:02:31 +00:00
}
}
if ( ! isset ( $cnv )) {
2022-12-04 18:15:43 +00:00
$cnv = get_iconfig ( $item , 'activitypub' , 'context' );
2021-12-02 23:02:31 +00:00
if ( ! $cnv ) {
2022-12-04 18:15:43 +00:00
$cnv = $item [ 'parent_mid' ];
2021-12-02 23:02:31 +00:00
}
}
2023-01-20 21:22:35 +00:00
if ( ! empty ( $cnv )) {
2022-10-09 01:47:49 +00:00
if ( is_string ( $cnv ) && str_starts_with ( $cnv , z_root ())) {
2021-12-02 23:02:31 +00:00
$cnv = str_replace ([ '/item/' , '/activity/' ], [ '/conversation/' , '/conversation/' ], $cnv );
}
2022-12-04 18:15:43 +00:00
$activity [ 'context' ] = $cnv ;
2021-12-02 23:02:31 +00:00
}
// provide ocap access token for private media.
// set this for descendants even if the current item is not private
// because it may have been relayed from a private item.
2022-12-04 18:15:43 +00:00
$token = get_iconfig ( $item , 'ocap' , 'relay' );
2021-12-02 23:02:31 +00:00
if ( $token && $has_images ) {
for ( $n = 0 ; $n < count ( $images ); $n ++ ) {
$match = $images [ $n ];
2022-10-20 09:23:02 +00:00
if ( str_starts_with ( $match [ 1 ], '=http' ) && str_contains ( $match [ 1 ], '/photo/' )) {
2022-12-04 18:15:43 +00:00
$item [ 'body' ] = str_replace ( $match [ 1 ], $match [ 1 ] . '?token=' . $token , $item [ 'body' ]);
2021-12-02 23:02:31 +00:00
$images [ $n ][ 2 ] = substr ( $match [ 1 ], 1 ) . '?token=' . $token ;
2022-10-09 01:47:49 +00:00
} elseif ( str_contains ( $match [ 2 ], z_root () . '/photo/' )) {
2022-12-04 18:15:43 +00:00
$item [ 'body' ] = str_replace ( $match [ 2 ], $match [ 2 ] . '?token=' . $token , $item [ 'body' ]);
2021-12-02 23:02:31 +00:00
$images [ $n ][ 2 ] = $match [ 2 ] . '?token=' . $token ;
}
}
}
2020-08-12 01:03:57 +00:00
2022-12-04 18:15:43 +00:00
if ( $item [ 'title' ]) {
$activity [ 'name' ] = $item [ 'title' ];
2021-12-02 23:02:31 +00:00
}
2018-05-30 04:08:52 +00:00
2022-12-04 18:15:43 +00:00
if ( in_array ( $item [ 'mimetype' ], [ 'text/bbcode' , 'text/x-multicode' ])) {
if ( $item [ 'summary' ]) {
$activity [ 'summary' ] = bbcode ( $item [ 'summary' ], [ $bbopts => true ]);
2021-12-02 23:02:31 +00:00
}
$opts = [ $bbopts => true ];
2022-12-04 18:15:43 +00:00
$activity [ 'content' ] = bbcode ( $item [ 'body' ], $opts );
$activity [ 'source' ] = [ 'content' => $item [ 'body' ], 'mediaType' => 'text/x-multicode' ];
if ( isset ( $activity [ 'summary' ])) {
$activity [ 'source' ][ 'summary' ] = $item [ 'summary' ];
2021-12-02 23:02:31 +00:00
}
} else {
2022-12-04 18:15:43 +00:00
$activity [ 'mediaType' ] = $item [ 'mimetype' ];
$activity [ 'content' ] = $item [ 'body' ];
2021-12-02 23:02:31 +00:00
}
2022-12-04 18:15:43 +00:00
if ( ! ( isset ( $activity [ 'actor' ]) || isset ( $activity [ 'attributedTo' ]))) {
2024-01-11 18:34:22 +00:00
$actor = self :: actorEncode ( $item [ 'author' ], false );
2021-12-02 23:02:31 +00:00
if ( $actor ) {
2022-12-04 18:15:43 +00:00
$activity [ 'actor' ] = $actor ;
2021-12-02 23:02:31 +00:00
} else {
return [];
}
}
2022-12-04 18:15:43 +00:00
if ( ! isset ( $activity [ 'url' ])) {
2021-12-02 23:02:31 +00:00
$urls = [];
2022-12-04 18:15:43 +00:00
if ( intval ( $item [ 'item_wall' ])) {
$locs = self :: nomadic_locations ( $item );
2021-12-02 23:02:31 +00:00
if ( $locs ) {
foreach ( $locs as $l ) {
2022-12-04 18:15:43 +00:00
if ( str_contains ( $item [ 'mid' ], $l [ 'hubloc_url' ])) {
2021-12-02 23:02:31 +00:00
continue ;
}
$urls [] = [
'type' => 'Link' ,
2022-12-04 18:15:43 +00:00
'href' => str_replace ( z_root (), $l [ 'hubloc_url' ], $activity [ 'id' ]),
2021-12-02 23:02:31 +00:00
'rel' => 'alternate' ,
'mediaType' => 'text/html'
];
$urls [] = [
'type' => 'Link' ,
2022-12-04 18:15:43 +00:00
'href' => str_replace ( z_root (), $l [ 'hubloc_url' ], $activity [ 'id' ]),
2021-12-02 23:02:31 +00:00
'rel' => 'alternate' ,
'mediaType' => 'application/activity+json'
];
$urls [] = [
'type' => 'Link' ,
2022-12-04 18:15:43 +00:00
'href' => str_replace ( z_root (), $l [ 'hubloc_url' ], $activity [ 'id' ]),
2021-12-02 23:02:31 +00:00
'rel' => 'alternate' ,
2021-12-06 21:19:19 +00:00
'mediaType' => 'application/x-nomad+json'
2021-12-02 23:02:31 +00:00
];
}
}
}
if ( $urls ) {
$curr [] = [
'type' => 'Link' ,
2022-12-04 18:15:43 +00:00
'href' => $activity [ 'id' ],
2021-12-02 23:02:31 +00:00
'rel' => 'alternate' ,
'mediaType' => 'text/html'
];
2022-12-04 18:15:43 +00:00
$activity [ 'url' ] = array_merge ( $curr , $urls );
2021-12-02 23:02:31 +00:00
} else {
2022-12-04 18:15:43 +00:00
$activity [ 'url' ] = $activity [ 'id' ];
2021-12-02 23:02:31 +00:00
}
}
2022-12-04 18:15:43 +00:00
$t = self :: encode_taxonomy ( $item );
2021-12-02 23:02:31 +00:00
if ( $t ) {
2023-01-02 20:54:03 +00:00
foreach ( $t as $tag ) {
2023-10-01 08:20:52 +00:00
if ( strcasecmp ( $tag [ 'name' ], '#nsfw' ) === 0
|| strcasecmp ( $tag [ 'name' ], '#sensitive' ) === 0 ) {
2023-01-02 20:54:03 +00:00
$activity [ 'sensitive' ] = true ;
}
}
2022-12-04 18:15:43 +00:00
$activity [ 'tag' ] = $t ;
2021-12-02 23:02:31 +00:00
}
2022-12-04 18:15:43 +00:00
$a = self :: encode_attachment ( $item );
2021-12-02 23:02:31 +00:00
if ( $a ) {
2022-12-04 18:15:43 +00:00
$activity [ 'attachment' ] = $a ;
2021-12-02 23:02:31 +00:00
}
2023-09-10 19:38:13 +00:00
if ( $activitypub && $has_images && in_array ( $activity [ 'type' ], [ 'Note' , 'Story' ])) {
2021-12-02 23:02:31 +00:00
foreach ( $images as $match ) {
$img = [];
// handle Friendica/Hubzilla style img links with [img=$url]$alttext[/img]
2022-10-09 01:47:49 +00:00
if ( str_starts_with ( $match [ 1 ], '=http' )) {
2021-12-02 23:02:31 +00:00
$img [] = [ 'type' => 'Image' , 'url' => substr ( $match [ 1 ], 1 ), 'name' => $match [ 2 ]];
} // preferred mechanism for adding alt text
2022-10-09 01:47:49 +00:00
elseif ( str_contains ( $match [ 1 ], 'alt=' )) {
2021-12-02 23:02:31 +00:00
$txt = str_replace ( '"' , '"' , $match [ 1 ]);
2022-08-23 10:15:05 +00:00
$txt = substr ( $txt , strpos ( $txt , 'alt="' ) + 5 , - 1 );
2021-12-02 23:02:31 +00:00
$img [] = [ 'type' => 'Image' , 'url' => $match [ 2 ], 'name' => $txt ];
} else {
$img [] = [ 'type' => 'Image' , 'url' => $match [ 2 ]];
}
2022-12-04 18:15:43 +00:00
if ( ! $activity [ 'attachment' ]) {
$activity [ 'attachment' ] = [];
2021-12-02 23:02:31 +00:00
}
$already_added = false ;
if ( $img ) {
2022-12-04 18:15:43 +00:00
for ( $pc = 0 ; $pc < count ( $activity [ 'attachment' ]); $pc ++ ) {
2021-12-02 23:02:31 +00:00
// caution: image attachments use url and links use href, and our own links will be 'attach' links based on the image href
// We could alternatively supply the correct attachment info when item is saved, but by replacing here we will pick up
// any "per-post" or manual changes to the image alt-text before sending.
2023-05-14 20:13:06 +00:00
if (( isset ( $activity [ 'attachment' ][ $pc ][ 'href' ])
&& str_contains ( $img [ 0 ][ 'url' ], str_replace ( '/attach/' , '/photo/' , $activity [ 'attachment' ][ $pc ][ 'href' ])))
|| ( isset ( $activity [ 'attachment' ][ $pc ][ 'url' ]) && $activity [ 'attachment' ][ $pc ][ 'url' ] === $img [ 0 ][ 'url' ])) {
2021-12-02 23:02:31 +00:00
// if it's already there, replace it with our alt-text aware version
2022-12-04 18:15:43 +00:00
$activity [ 'attachment' ][ $pc ] = $img [ 0 ];
2021-12-02 23:02:31 +00:00
$already_added = true ;
}
}
if ( ! $already_added ) {
// add it
2022-12-04 18:15:43 +00:00
$activity [ 'attachment' ] = array_merge ( $img , $activity [ 'attachment' ]);
2021-12-02 23:02:31 +00:00
}
}
}
}
// addressing madness
if ( $activitypub ) {
$parent_i = [];
2022-12-04 18:15:43 +00:00
$activity [ 'to' ] = [];
$activity [ 'cc' ] = [];
2021-12-02 23:02:31 +00:00
2022-12-04 18:15:43 +00:00
$public = ! $item [ 'item_private' ];
$top_level = $item [ 'mid' ] === $item [ 'parent_mid' ];
2021-12-02 23:02:31 +00:00
if ( ! $top_level ) {
2022-12-04 18:15:43 +00:00
if ( intval ( $item [ 'parent' ])) {
$recips = get_iconfig ( $item [ 'parent' ], 'activitypub' , 'recips' );
2021-12-02 23:02:31 +00:00
} else {
2022-07-18 22:03:22 +00:00
// if we are encoding this item prior to storage there won't be a parent.
2021-12-03 03:01:39 +00:00
$p = q (
" select parent from item where parent_mid = '%s' and uid = %d " ,
2022-12-04 18:15:43 +00:00
dbesc ( $item [ 'parent_mid' ]),
intval ( $item [ 'uid' ])
2021-12-02 23:02:31 +00:00
);
if ( $p ) {
$recips = get_iconfig ( $p [ 0 ][ 'parent' ], 'activitypub' , 'recips' );
}
}
if ( $recips ) {
$parent_i [ 'to' ] = $recips [ 'to' ];
$parent_i [ 'cc' ] = $recips [ 'cc' ];
}
}
2018-05-30 04:08:52 +00:00
2021-12-02 23:02:31 +00:00
if ( $public ) {
2022-12-04 18:15:43 +00:00
$activity [ 'to' ] = [ ACTIVITY_PUBLIC_INBOX ];
2021-12-02 23:02:31 +00:00
if ( isset ( $parent_i [ 'to' ]) && is_array ( $parent_i [ 'to' ])) {
2022-12-04 18:15:43 +00:00
$activity [ 'to' ] = array_values ( array_unique ( array_merge ( $activity [ 'to' ], $parent_i [ 'to' ])));
2021-12-02 23:02:31 +00:00
}
2022-12-04 18:15:43 +00:00
if ( $item [ 'item_origin' ]) {
$activity [ 'cc' ] = [ z_root () . '/followers/' . substr ( $item [ 'author' ][ 'xchan_addr' ], 0 , strpos ( $item [ 'author' ][ 'xchan_addr' ], '@' ))];
2021-12-02 23:02:31 +00:00
}
if ( isset ( $parent_i [ 'cc' ]) && is_array ( $parent_i [ 'cc' ])) {
2022-12-04 18:15:43 +00:00
$activity [ 'cc' ] = array_values ( array_unique ( array_merge ( $activity [ 'cc' ], $parent_i [ 'cc' ])));
2021-12-02 23:02:31 +00:00
}
} else {
// private activity
if ( $top_level ) {
2022-12-04 18:15:43 +00:00
$activity [ 'to' ] = self :: map_acl ( $item );
2021-12-02 23:02:31 +00:00
if ( isset ( $parent_i [ 'to' ]) && is_array ( $parent_i [ 'to' ])) {
2022-12-04 18:15:43 +00:00
$activity [ 'to' ] = array_values ( array_unique ( array_merge ( $activity [ 'to' ], $parent_i [ 'to' ])));
2021-12-02 23:02:31 +00:00
}
2024-01-02 01:25:37 +00:00
} elseif (( int ) $item [ 'item_private' ] === 1 ) {
2022-12-04 18:15:43 +00:00
$activity [ 'cc' ] = self :: map_acl ( $item );
2021-12-02 23:02:31 +00:00
if ( isset ( $parent_i [ 'cc' ]) && is_array ( $parent_i [ 'cc' ])) {
2022-12-04 18:15:43 +00:00
$activity [ 'cc' ] = array_values ( array_unique ( array_merge ( $activity [ 'cc' ], $parent_i [ 'cc' ])));
2021-12-02 23:02:31 +00:00
}
2021-12-03 03:01:39 +00:00
$d = q (
2022-04-06 23:12:46 +00:00
" select hubloc.* from hubloc left join item on hubloc_hash = owner_xchan where item.parent_mid = '%s' and item.uid = %d and hubloc_deleted = 0 order by hubloc_id desc limit 1 " ,
2022-12-04 18:15:43 +00:00
dbesc ( $item [ 'parent_mid' ]),
intval ( $item [ 'uid' ])
2021-12-02 23:02:31 +00:00
);
if ( $d ) {
if ( $d [ 0 ][ 'hubloc_network' ] === 'activitypub' ) {
$addr = $d [ 0 ][ 'hubloc_hash' ];
} else {
$addr = $d [ 0 ][ 'hubloc_id_url' ];
}
2022-12-04 18:15:43 +00:00
$activity [ 'cc' ][] = $addr ;
2021-12-02 23:02:31 +00:00
}
}
}
2022-12-04 18:15:43 +00:00
$mentions = self :: map_mentions ( $item );
2021-12-02 23:02:31 +00:00
if ( count ( $mentions ) > 0 ) {
2022-12-04 18:15:43 +00:00
if ( ! $activity [ 'to' ]) {
$activity [ 'to' ] = $mentions ;
2021-12-02 23:02:31 +00:00
} else {
2022-12-04 18:15:43 +00:00
$activity [ 'to' ] = array_values ( array_unique ( array_merge ( $activity [ 'to' ], $mentions )));
2021-12-02 23:02:31 +00:00
}
}
}
// remove any duplicates from 'cc' that are present in 'to'
// as this may indicate that mentions changed the audience from secondary to primary
$cc = [];
2022-12-04 18:15:43 +00:00
if ( $activity [ 'cc' ] && is_array ( $activity [ 'cc' ])) {
foreach ( $activity [ 'cc' ] as $e ) {
if ( ! is_array ( $activity [ 'to' ])) {
2021-12-02 23:02:31 +00:00
$cc [] = $e ;
2022-12-04 18:15:43 +00:00
} elseif ( ! in_array ( $e , $activity [ 'to' ])) {
2021-12-02 23:02:31 +00:00
$cc [] = $e ;
}
}
}
2022-12-04 18:15:43 +00:00
$activity [ 'cc' ] = $cc ;
2018-05-30 04:08:52 +00:00
2022-12-04 18:15:43 +00:00
return $activity ;
2021-12-02 23:02:31 +00:00
}
2018-05-30 04:08:52 +00:00
2021-12-02 23:02:31 +00:00
// Returns an array of URLS for any mention tags found in the item array $i.
2018-05-30 04:08:52 +00:00
2021-12-02 23:02:31 +00:00
public static function map_mentions ( $i )
{
if ( ! ( array_key_exists ( 'term' , $i ) && is_array ( $i [ 'term' ]))) {
return [];
}
2018-05-30 04:08:52 +00:00
2021-12-02 23:02:31 +00:00
$list = [];
2018-05-30 04:08:52 +00:00
2021-12-02 23:02:31 +00:00
foreach ( $i [ 'term' ] as $t ) {
if ( ! ( array_key_exists ( 'url' , $t ) && $t [ 'url' ])) {
continue ;
}
if ( array_key_exists ( 'ttype' , $t ) && $t [ 'ttype' ] == TERM_MENTION ) {
$url = self :: lookup_term_url ( $t [ 'url' ]);
2022-08-23 10:15:05 +00:00
$list [] = (( $url ) ? : $t [ 'url' ]);
2021-12-02 23:02:31 +00:00
}
}
2018-05-30 04:08:52 +00:00
2021-12-02 23:02:31 +00:00
return $list ;
}
2018-10-25 05:43:00 +00:00
2021-12-02 23:02:31 +00:00
// Returns an array of all recipients targeted by private item array $i.
2018-05-30 04:08:52 +00:00
2021-12-02 23:02:31 +00:00
public static function map_acl ( $i )
{
$ret = [];
2018-05-30 04:08:52 +00:00
2021-12-02 23:02:31 +00:00
if ( ! $i [ 'item_private' ]) {
return $ret ;
}
if ( $i [ 'mid' ] !== $i [ 'parent_mid' ]) {
2021-12-03 03:01:39 +00:00
$i = q (
" select * from item where parent_mid = '%s' and uid = %d " ,
2021-12-02 23:02:31 +00:00
dbesc ( $i [ 'parent_mid' ]),
intval ( $i [ 'uid' ])
);
if ( $i ) {
$i = array_shift ( $i );
}
}
if ( $i [ 'allow_gid' ]) {
$tmp = expand_acl ( $i [ 'allow_gid' ]);
if ( $tmp ) {
foreach ( $tmp as $t ) {
2023-08-25 22:50:01 +00:00
if ( str_starts_with ( $t , 'connections:' )) {
$split = explode ( ':' , $t , 2 );
$listChannel = Channel :: from_hash ( $split [ 1 ]);
if ( $listChannel ) {
$ret [] = z_root () . '/followers/' . $listChannel [ 'channel_address' ];
}
else {
$ret [] = z_root () . '/lists/' . $t ;
}
}
else {
$ret [] = z_root () . '/lists/' . $t ;
}
2021-12-02 23:02:31 +00:00
}
}
}
if ( $i [ 'allow_cid' ]) {
$tmp = expand_acl ( $i [ 'allow_cid' ]);
$list = stringify_array ( $tmp , true );
if ( $list ) {
2022-06-17 02:46:54 +00:00
$details = q ( " select hubloc_id_url, hubloc_hash, hubloc_network from hubloc where hubloc_hash in ( " . $list . " ) and hubloc_deleted = 0 " );
2021-12-02 23:02:31 +00:00
if ( $details ) {
foreach ( $details as $d ) {
if ( $d [ 'hubloc_network' ] === 'activitypub' ) {
$ret [] = $d [ 'hubloc_hash' ];
} else {
$ret [] = $d [ 'hubloc_id_url' ];
}
}
}
}
}
$x = get_iconfig ( $i [ 'id' ], 'activitypub' , 'recips' );
if ( $x ) {
foreach ([ 'to' , 'cc' ] as $k ) {
if ( isset ( $x [ $k ])) {
if ( is_string ( $x [ $k ])) {
$ret [] = $x [ $k ];
} else {
$ret = array_merge ( $ret , $x [ $k ]);
}
}
}
}
return array_values ( array_unique ( $ret ));
}
2023-09-02 22:17:39 +00:00
/**
* @ throws UnhandledElementException
*/
2024-01-11 18:34:22 +00:00
public static function actorEncode ( $p , $extended = true , $activitypub = false )
2021-12-02 23:02:31 +00:00
{
2024-01-11 18:34:22 +00:00
$actor = new Actor ();
2022-04-25 00:22:18 +00:00
$currhub = false ;
2022-07-20 05:27:23 +00:00
2021-12-03 03:01:39 +00:00
if ( ! $p [ 'xchan_url' ]) {
2024-01-11 18:34:22 +00:00
return $actor -> toArray ();
2021-12-03 03:01:39 +00:00
}
2021-12-02 23:02:31 +00:00
2022-06-17 02:46:54 +00:00
$h = q ( " select * from hubloc where hubloc_hash = '%s' and hubloc_deleted = 0 " ,
2022-04-25 00:22:18 +00:00
dbesc ( $p [ 'xchan_hash' ])
);
if ( $h ) {
$currhub = $h [ 0 ];
foreach ( $h as $hub ) {
if ( $hub [ 'hubloc_url' ] === z_root ()) {
$currhub = $hub ;
}
}
}
2023-10-07 01:54:24 +00:00
2023-10-06 21:58:50 +00:00
$fallback = $p [ 'xchan_network' ] === 'activitypub' ? $p [ 'xchan_hash' ] : $p [ 'xchan_url' ];
$current_url = $currhub ? $currhub [ 'hubloc_id_url' ] : $fallback ;
2022-04-25 00:22:18 +00:00
2021-12-02 23:02:31 +00:00
if ( ! $extended ) {
2022-04-25 00:22:18 +00:00
return $current_url ;
2021-12-02 23:02:31 +00:00
}
2022-01-25 01:26:12 +00:00
$c = (( array_key_exists ( 'channel_id' , $p )) ? $p : Channel :: from_hash ( $p [ 'xchan_hash' ]));
2021-12-02 23:02:31 +00:00
2024-01-11 18:34:22 +00:00
$actor -> setType ( 'Person' );
2021-12-02 23:02:31 +00:00
$auto_follow = false ;
if ( $c ) {
$role = PConfig :: Get ( $c [ 'channel_id' ], 'system' , 'permissions_role' );
2022-10-09 01:47:49 +00:00
if ( str_contains ( $role , 'forum' )) {
2024-01-11 18:34:22 +00:00
$actor -> setType ( 'Group' );
2021-12-02 23:02:31 +00:00
}
2022-01-28 18:39:46 +00:00
$auto_follow = intval ( PConfig :: Get ( $c [ 'channel_id' ], 'system' , 'autoperms' ));
2021-12-02 23:02:31 +00:00
}
if ( $c ) {
2024-01-11 18:34:22 +00:00
$actor -> setId ( Channel :: url ( $c ));
2021-12-02 23:02:31 +00:00
} else {
2024-01-11 18:34:22 +00:00
$actor -> setId (( str_starts_with ( $p [ 'xchan_hash' ], 'http' )) ? $p [ 'xchan_hash' ] : $current_url );
2021-12-02 23:02:31 +00:00
}
2021-12-03 03:01:39 +00:00
if ( $p [ 'xchan_addr' ] && strpos ( $p [ 'xchan_addr' ], '@' )) {
2024-01-11 18:34:22 +00:00
$actor -> setPreferredUsername ( substr ( $p [ 'xchan_addr' ], 0 , strpos ( $p [ 'xchan_addr' ], '@' )));
2021-12-03 03:01:39 +00:00
}
2024-01-11 18:34:22 +00:00
$actor -> setName ( $p [ 'xchan_name' ]);
$actor -> setPublished ( datetime_convert ( 'UTC' , 'UTC' , $p [ 'xchan_created' ], ATOM_TIME ));
$actor -> setUpdated ( datetime_convert ( 'UTC' , 'UTC' , $p [ 'xchan_name_date' ], ATOM_TIME ));
$actor -> setIcon ([
2021-12-02 23:02:31 +00:00
'type' => 'Image' ,
2022-08-23 10:15:05 +00:00
'mediaType' => (( $p [ 'xchan_photo_mimetype' ]) ? : 'image/png' ),
2021-12-02 23:02:31 +00:00
'updated' => datetime_convert ( 'UTC' , 'UTC' , $p [ 'xchan_photo_date' ], ATOM_TIME ),
'url' => $p [ 'xchan_photo_l' ],
'height' => 300 ,
'width' => 300 ,
2024-01-11 18:34:22 +00:00
]);
$actor -> setUrl ( $current_url );
2021-12-02 23:02:31 +00:00
if ( isset ( $p [ 'channel_location' ]) && $p [ 'channel_location' ]) {
2024-01-11 18:34:22 +00:00
$actor -> setLocation (( new Place ([ 'type' => 'Place' , 'name' => $p [ 'channel_location' ]])));
2021-12-02 23:02:31 +00:00
}
2024-01-11 18:34:22 +00:00
$tag = [
[ 'type' => 'Note' , 'name' => 'Protocol' , 'content' => 'zot6' ],
[ 'type' => 'Note' , 'name' => 'Protocol' , 'content' => 'nomad' ]
];
2021-12-02 23:02:31 +00:00
if ( $activitypub && get_config ( 'system' , 'activitypub' , ACTIVITYPUB_ENABLED )) {
if ( $c ) {
if ( get_pconfig ( $c [ 'channel_id' ], 'system' , 'activitypub' , ACTIVITYPUB_ENABLED )) {
2024-01-11 18:34:22 +00:00
$actor -> setInbox ( z_root () . '/inbox/' . $c [ 'channel_address' ]);
$tag [] = [ 'type' => 'Note' , 'name' => 'Protocol' , 'content' => 'activitypub' ];
2021-12-02 23:02:31 +00:00
}
2024-01-11 18:34:22 +00:00
$actor -> setOutbox ( z_root () . '/outbox/' . $c [ 'channel_address' ]);
$actor -> setFollowers ( z_root () . '/followers/' . $c [ 'channel_address' ]);
$actor -> setFollowing ( z_root () . '/following/' . $c [ 'channel_address' ]);
$actor -> setWebfinger ( 'acct:' . $c [ 'channel_address' ] . '@' . App :: get_hostname ());
2021-12-02 23:02:31 +00:00
2024-01-11 18:34:22 +00:00
$actor -> setEndpoints ([
2021-12-02 23:02:31 +00:00
'sharedInbox' => z_root () . '/inbox' ,
'oauthRegistrationEndpoint' => z_root () . '/api/client/register' ,
'oauthAuthorizationEndpoint' => z_root () . '/authorize' ,
2023-01-25 20:32:42 +00:00
'oauthTokenEndpoint' => z_root () . '/token' ,
2023-09-05 22:19:07 +00:00
'searchContent' => z_root () . '/search/' . $c [ 'channel_address' ] . '?search={}' ,
'searchTags' => z_root () . '/search/' . $c [ 'channel_address' ] . '?tag={}' ,
2024-01-11 18:34:22 +00:00
]);
$actor -> setDiscoverable (( bool )(( 1 - intval ( $p [ 'xchan_hidden' ]))));
2023-01-22 01:28:38 +00:00
2023-01-25 20:32:42 +00:00
$searchPerm = PermissionLimits :: Get ( $c [ 'channel_id' ], 'search_stream' );
if ( $searchPerm === PERMS_PUBLIC ) {
2024-01-11 18:34:22 +00:00
$actor -> setCanSearch ([ ACTIVITY_PUBLIC_INBOX ]);
$actor -> setIndexable ( true );
2023-01-25 20:32:42 +00:00
}
elseif ( in_array ( $searchPerm , [ PERMS_SPECIFIC , PERMS_CONTACTS ])) {
2024-01-11 18:34:22 +00:00
$actor -> setCanSearch ([ z_root () . '/followers/' . $c [ 'channel_address' ]]);
$actor -> setIndexable ( false );
2023-01-25 20:32:42 +00:00
}
else {
2024-01-11 18:34:22 +00:00
$actor -> setCanSearch ([]);
$actor -> setIndexable ( false );
2023-01-22 01:28:38 +00:00
}
2023-07-27 00:15:43 +00:00
// force over-ride
if ( Config :: Get ( 'system' , 'block_public_search' )) {
2024-01-11 18:34:22 +00:00
$actor -> setCanSearch ([]);
$actor -> setIndexable ( false );
2023-07-27 00:15:43 +00:00
}
2023-01-22 01:28:38 +00:00
2023-01-25 20:32:42 +00:00
2024-01-11 18:34:22 +00:00
$actor -> setPublicKey ([
2023-12-07 21:46:09 +00:00
'id' => $current_url . '?operation=rsakey' ,
2022-04-25 00:22:18 +00:00
'owner' => $current_url ,
2021-12-02 23:02:31 +00:00
'signatureAlgorithm' => 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256' ,
'publicKeyPem' => $p [ 'xchan_pubkey' ]
2024-01-11 18:34:22 +00:00
]);
2021-12-02 23:02:31 +00:00
2024-01-02 01:25:37 +00:00
$ed25519publicKey = ( new Multibase ()) -> publicKey ( $c [ 'channel_epubkey' ]);
2024-01-12 05:31:10 +00:00
$actor -> setAssertionMethod ( new AssertionMethod ([
2023-12-09 19:52:07 +00:00
[
2024-01-02 01:25:37 +00:00
'id' => $current_url . '#' . $ed25519publicKey ,
2023-12-09 19:52:07 +00:00
'type' => 'Multikey' ,
'controller' => $current_url ,
2024-01-02 01:25:37 +00:00
'publicKeyMultibase' => $ed25519publicKey ,
2023-12-09 19:52:07 +00:00
]
2024-01-12 05:31:10 +00:00
]));
2024-01-11 18:34:22 +00:00
$actor -> setManuallyApprovesFollowers ( ! $auto_follow );
2023-12-01 00:34:16 +00:00
2021-12-02 23:02:31 +00:00
// map other nomadic identities linked with this channel
$locations = [];
2023-09-02 22:17:39 +00:00
$nomadicLocations = [];
2021-12-02 23:02:31 +00:00
$locs = Libzot :: encode_locations ( $c );
if ( $locs ) {
foreach ( $locs as $loc ) {
if ( $loc [ 'url' ] !== z_root ()) {
$locations [] = $loc [ 'id_url' ];
}
}
2023-09-02 22:17:39 +00:00
foreach ( $locs as $loc ) {
$loc = new Location ( $loc );
$sig = explode ( '.' , $loc -> getUrlSig (), 2 );
$entry = [
'id' => $loc -> getIdUrl (),
'url' => $loc -> getIdUrl (),
'signature' => [
2023-12-07 21:46:09 +00:00
'id' => $loc -> getIdUrl () . '?operation=rsakey' ,
2023-09-02 22:17:39 +00:00
'nonce' => random_string (),
'creator' => $loc -> getIdUrl (),
'signature' => base64_encode ( Crypto :: sign ( $loc -> getIdUrl (), $c [ 'channel_prvkey' ])),
],
];
$nomadicLocations [] = $entry ;
}
}
// $ret['nomadicLocations'] = $nomadicLocations;
2021-12-02 23:02:31 +00:00
if ( $locations ) {
if ( count ( $locations ) === 1 ) {
$locations = array_shift ( $locations );
}
2024-01-11 18:34:22 +00:00
$actor -> setCopiedTo ( $locations );
$actor -> setAlsoKnownAs ( $locations );
2021-12-02 23:02:31 +00:00
}
2023-09-02 22:17:39 +00:00
2022-09-11 10:19:32 +00:00
// To move your followers from a Mastodon account,
// visit https://$yoursite/pconfig/system/movefrom
// And set the value to the URL of your Mastodon profile.
// Then go back to Mastodon and move your account.
$move_id = PConfig :: Get ( $c [ 'channel_id' ], 'system' , 'movefrom' );
if ( $move_id ) {
2024-01-11 18:34:22 +00:00
$actor -> setMovedTo ( z_root () . '/channel/' . $c [ 'channel_address' ]);
$actor -> setAlsoKnownAs ( $move_id );
2022-09-10 22:20:18 +00:00
}
2022-01-25 01:26:12 +00:00
$cp = Channel :: get_cover_photo ( $c [ 'channel_id' ], 'array' );
2021-12-02 23:02:31 +00:00
if ( $cp ) {
2024-01-11 18:34:22 +00:00
$actor -> setImage ([
2021-12-02 23:02:31 +00:00
'type' => 'Image' ,
'mediaType' => $cp [ 'type' ],
'url' => $cp [ 'url' ]
2024-01-11 18:34:22 +00:00
]);
2021-12-02 23:02:31 +00:00
}
// only fill in profile information if the profile is publicly visible
if ( perm_is_allowed ( $c [ 'channel_id' ], EMPTY_STR , 'view_profile' )) {
2021-12-03 03:01:39 +00:00
$dp = q (
" select * from profile where uid = %d and is_default = 1 " ,
2021-12-02 23:02:31 +00:00
intval ( $c [ 'channel_id' ])
);
if ( $dp ) {
if ( $dp [ 0 ][ 'about' ]) {
2024-01-11 18:34:22 +00:00
$actor -> setSummary ( bbcode ( $dp [ 0 ][ 'about' ], [ 'export' => true ]));
2021-12-02 23:02:31 +00:00
}
2024-01-11 18:34:22 +00:00
$attachment = [];
2021-12-03 03:01:39 +00:00
foreach (
[ 'pdesc' , 'address' , 'locality' , 'region' , 'postal_code' , 'country_name' ,
2021-12-02 23:02:31 +00:00
'hometown' , 'gender' , 'marital' , 'sexual' , 'politic' , 'religion' , 'pronouns' ,
2021-12-03 03:01:39 +00:00
'homepage' , 'contact' , 'dob' ] as $k
) {
2021-12-02 23:02:31 +00:00
if ( $dp [ 0 ][ $k ]) {
$key = $k ;
if ( $key === 'pdesc' ) {
$key = 'description' ;
}
if ( $key == 'politic' ) {
$key = 'political' ;
}
if ( $key === 'dob' ) {
$key = 'birthday' ;
}
2024-01-11 18:34:22 +00:00
$attachment [] = [ 'type' => 'Note' , 'name' => $key , 'content' => $dp [ 0 ][ $k ]];
2021-12-02 23:02:31 +00:00
}
}
2024-01-11 18:34:22 +00:00
$actor -> setAttachment ( $attachment );
2021-12-02 23:02:31 +00:00
if ( $dp [ 0 ][ 'keywords' ]) {
$kw = explode ( ' ' , $dp [ 0 ][ 'keywords' ]);
if ( $kw ) {
foreach ( $kw as $k ) {
$k = trim ( $k );
$k = trim ( $k , '#,' );
2024-01-11 18:34:22 +00:00
$tag = $actor -> getTag ();
$tag [] = [ 'type' => 'Hashtag' , 'id' => z_root () . '/search?tag=' . urlencode ( $k ), 'name' => '#' . urlencode ( $k )];
$actor -> setTag ( $tag );
2021-12-02 23:02:31 +00:00
}
}
}
}
}
} else {
$collections = get_xconfig ( $p [ 'xchan_hash' ], 'activitypub' , 'collections' , []);
if ( $collections ) {
2024-01-11 18:34:22 +00:00
$actor = array_merge ( $actor , $collections );
2021-12-02 23:02:31 +00:00
}
}
} else {
2024-01-11 18:34:22 +00:00
$actor -> setPublicKey ([
2022-04-25 00:22:18 +00:00
'id' => $current_url ,
'owner' => $current_url ,
2021-12-02 23:02:31 +00:00
'publicKeyPem' => $p [ 'xchan_pubkey' ]
2024-01-11 18:34:22 +00:00
]);
2021-12-02 23:02:31 +00:00
}
2024-01-11 18:34:22 +00:00
$actor -> setTag ( $tag );
2018-05-30 04:08:52 +00:00
2024-01-11 18:34:22 +00:00
$arr = [ 'xchan' => $p , 'encoded' => $actor -> toArray (), 'activitypub' => $activitypub ];
2022-02-12 08:50:48 +00:00
Hook :: call ( 'encode_person' , $arr );
2022-10-20 09:23:02 +00:00
return $arr [ 'encoded' ];
2021-12-02 23:02:31 +00:00
}
2018-05-30 04:08:52 +00:00
2021-12-02 23:02:31 +00:00
public static function encode_site ()
{
2018-05-30 04:08:52 +00:00
2022-01-25 01:26:12 +00:00
$sys = Channel :: get_system ();
2024-01-11 18:34:22 +00:00
$actor = new Actor ( self :: actorEncode ( $sys , true , true ));
2018-05-30 04:08:52 +00:00
2023-07-18 07:04:13 +00:00
$actor -> setType ( self :: xchan_type_to_type ( intval ( $sys [ 'xchan_type' ])));
$actor -> setId ( z_root ());
$actor -> setAlsoKnownAs ( z_root () . '/channel/sys' );
$actor -> setPreferredUsername ( 'sys' );
$actor -> setName ( System :: get_site_name ());
2018-05-30 04:08:52 +00:00
2023-07-18 07:04:13 +00:00
$actor -> setIcon (( new ASObject ([
2022-01-28 22:52:24 +00:00
'type' => 'Image' ,
'url' => System :: get_site_icon (),
2023-07-18 07:04:13 +00:00
])) -> toArray ());
2019-09-22 16:16:07 +00:00
2023-01-22 01:28:38 +00:00
2023-07-18 07:04:13 +00:00
$actor -> setCanSearch ( Config :: Get ( 'system' , 'block_public_search' , 1 )
? []
: ACTIVITY_PUBLIC_INBOX
);
2020-01-21 21:07:41 +00:00
2023-07-18 07:04:13 +00:00
$actor -> setGenerator (( new ASObject ([
'type' => 'Application' ,
'name' => System :: get_project_name ()
])) -> toArray ());
2020-01-21 21:07:41 +00:00
2023-07-18 07:04:13 +00:00
$actor -> setUrl ( z_root ());
$actor -> setManuallyApprovesFollowers (( bool ) get_config ( 'system' , 'allowed_sites' ));
2018-05-30 04:08:52 +00:00
2022-01-25 01:26:12 +00:00
$cp = Channel :: get_cover_photo ( $sys [ 'channel_id' ], 'array' );
2021-12-02 23:02:31 +00:00
if ( $cp ) {
2023-07-18 07:04:13 +00:00
$actor -> setImage ([
2021-12-02 23:02:31 +00:00
'type' => 'Image' ,
2023-07-18 07:04:13 +00:00
'url' => (( new Link ()) -> setMediaType ( $cp [ 'type' ]) -> setHref ( $cp [ 'url' ]) -> toArray ())
]);
2021-12-02 23:02:31 +00:00
}
2018-05-30 04:08:52 +00:00
2023-07-18 07:04:13 +00:00
$actor -> setSummary ( bbcode ( get_config ( 'system' , 'siteinfo' , '' )), [ 'export' => true ]);
$actor -> setSource ([
2021-12-06 02:01:10 +00:00
'mediaType' => 'text/x-multicode' ,
2021-12-02 23:02:31 +00:00
'summary' => get_config ( 'system' , 'siteinfo' , '' )
2023-07-18 07:04:13 +00:00
]);
2018-05-30 04:08:52 +00:00
2023-07-18 07:04:13 +00:00
$actor -> setPublicKey ([
2023-12-07 21:46:09 +00:00
'id' => z_root () . '?operation=rsakey' ,
2021-12-02 23:02:31 +00:00
'owner' => z_root (),
'publicKeyPem' => get_config ( 'system' , 'pubkey' )
2023-07-18 07:04:13 +00:00
]);
2018-05-30 04:08:52 +00:00
2023-07-18 07:04:13 +00:00
return $actor -> toArray ();
2021-12-02 23:02:31 +00:00
}
2018-05-30 04:08:52 +00:00
2021-12-02 23:02:31 +00:00
public static function activity_mapper ( $verb )
{
2018-05-30 04:08:52 +00:00
2022-10-09 01:47:49 +00:00
if ( ! str_contains ( $verb , '/' )) {
2021-12-02 23:02:31 +00:00
return $verb ;
}
2020-08-12 01:03:57 +00:00
2021-12-02 23:02:31 +00:00
$acts = [
'http://activitystrea.ms/schema/1.0/post' => 'Create' ,
'http://activitystrea.ms/schema/1.0/share' => 'Announce' ,
'http://activitystrea.ms/schema/1.0/update' => 'Update' ,
'http://activitystrea.ms/schema/1.0/like' => 'Like' ,
'http://activitystrea.ms/schema/1.0/favorite' => 'Like' ,
'http://purl.org/zot/activity/dislike' => 'Dislike' ,
'http://activitystrea.ms/schema/1.0/tag' => 'Add' ,
'http://activitystrea.ms/schema/1.0/follow' => 'Follow' ,
'http://activitystrea.ms/schema/1.0/unfollow' => 'Ignore' ,
];
2018-05-30 04:08:52 +00:00
2022-02-12 08:50:48 +00:00
Hook :: call ( 'activity_mapper' , $acts );
2018-05-30 04:08:52 +00:00
2021-12-02 23:02:31 +00:00
if ( array_key_exists ( $verb , $acts ) && $acts [ $verb ]) {
return $acts [ $verb ];
}
2018-05-30 04:08:52 +00:00
2021-12-02 23:02:31 +00:00
// Reactions will just map to normal activities
2018-05-30 04:08:52 +00:00
2022-10-09 01:47:49 +00:00
if ( str_contains ( $verb , ACTIVITY_REACT )) {
2021-12-02 23:02:31 +00:00
return 'Create' ;
2021-12-03 03:01:39 +00:00
}
2022-10-09 01:47:49 +00:00
if ( str_contains ( $verb , ACTIVITY_MOOD )) {
2021-12-02 23:02:31 +00:00
return 'Create' ;
2021-12-03 03:01:39 +00:00
}
2018-05-30 04:08:52 +00:00
2022-10-09 01:47:49 +00:00
if ( str_contains ( $verb , ACTIVITY_POKE )) {
2021-12-02 23:02:31 +00:00
return 'Activity' ;
2021-12-03 03:01:39 +00:00
}
2018-05-30 04:08:52 +00:00
2021-12-02 23:02:31 +00:00
// We should return false, however this will trigger an uncaught exception and crash
// the delivery system if encountered by the JSON-LDSignature library
2018-05-30 04:08:52 +00:00
2021-12-02 23:02:31 +00:00
logger ( 'Unmapped activity: ' . $verb );
return 'Create' ;
2021-12-03 03:01:39 +00:00
// return false;
2021-12-02 23:02:31 +00:00
}
2018-10-25 05:43:00 +00:00
2022-05-01 05:57:46 +00:00
public static function activity_obj_mapper ( $obj , $sync = false )
2021-12-02 23:02:31 +00:00
{
2018-05-30 04:08:52 +00:00
2018-10-25 05:43:00 +00:00
2021-12-02 23:02:31 +00:00
$objs = [
'http://activitystrea.ms/schema/1.0/note' => 'Note' ,
'http://activitystrea.ms/schema/1.0/comment' => 'Note' ,
'http://activitystrea.ms/schema/1.0/person' => 'Person' ,
'http://purl.org/zot/activity/profile' => 'Profile' ,
'http://activitystrea.ms/schema/1.0/photo' => 'Image' ,
'http://activitystrea.ms/schema/1.0/profile-photo' => 'Icon' ,
'http://activitystrea.ms/schema/1.0/event' => 'Event' ,
'http://activitystrea.ms/schema/1.0/wiki' => 'Document' ,
'http://purl.org/zot/activity/location' => 'Place' ,
'http://purl.org/zot/activity/chessgame' => 'Game' ,
'http://purl.org/zot/activity/tagterm' => 'zot:Tag' ,
'http://purl.org/zot/activity/thing' => 'Object' ,
'http://purl.org/zot/activity/file' => 'zot:File' ,
'http://purl.org/zot/activity/mood' => 'zot:Mood' ,
];
2022-02-12 08:50:48 +00:00
Hook :: call ( 'activity_obj_mapper' , $objs );
2021-12-02 23:02:31 +00:00
if ( $obj === 'Answer' ) {
2022-05-01 05:57:46 +00:00
if ( $sync ) {
return $obj ;
}
2021-12-02 23:02:31 +00:00
return 'Note' ;
}
2022-10-09 01:47:49 +00:00
if ( ! str_contains ( $obj , '/' )) {
2021-12-02 23:02:31 +00:00
return $obj ;
}
if ( array_key_exists ( $obj , $objs )) {
return $objs [ $obj ];
}
logger ( 'Unmapped activity object: ' . $obj );
return 'Note' ;
2021-12-03 03:01:39 +00:00
// return false;
2021-12-02 23:02:31 +00:00
}
2018-05-30 04:08:52 +00:00
2021-12-02 23:02:31 +00:00
public static function follow ( $channel , $act )
{
2018-05-30 04:08:52 +00:00
2021-12-02 23:02:31 +00:00
$contact = null ;
$their_follow_id = null ;
2023-05-16 11:34:01 +00:00
2021-12-02 23:02:31 +00:00
/*
2021-12-03 03:01:39 +00:00
*
* if $act -> type === 'Follow' , actor is now following $channel
* if $act -> type === 'Accept' , actor has approved a follow request from $channel
*
*/
2021-12-02 23:02:31 +00:00
$person_obj = $act -> actor ;
if ( in_array ( $act -> type , [ 'Follow' , 'Invite' , 'Join' ])) {
$their_follow_id = $act -> id ;
}
2018-05-30 04:08:52 +00:00
2021-12-02 23:02:31 +00:00
if ( is_array ( $person_obj )) {
// store their xchan and hubloc
2018-05-30 04:08:52 +00:00
2021-12-02 23:02:31 +00:00
self :: actor_store ( $person_obj [ 'id' ], $person_obj );
2018-05-30 04:08:52 +00:00
2021-12-02 23:02:31 +00:00
// Find any existing abook record
2018-05-30 04:08:52 +00:00
2021-12-03 03:01:39 +00:00
$r = q (
" select * from abook left join xchan on abook_xchan = xchan_hash where abook_xchan = '%s' and abook_channel = %d limit 1 " ,
2021-12-02 23:02:31 +00:00
dbesc ( $person_obj [ 'id' ]),
intval ( $channel [ 'channel_id' ])
);
if ( $r ) {
$contact = $r [ 0 ];
}
}
$x = PermissionRoles :: role_perms ( 'social' );
$p = Permissions :: FilledPerms ( $x [ 'perms_connect' ]);
// add tag_deliver permissions to remote groups
if ( is_array ( $person_obj ) && $person_obj [ 'type' ] === 'Group' ) {
$p [ 'tag_deliver' ] = 1 ;
}
$their_perms = Permissions :: serialise ( $p );
if ( $contact && $contact [ 'abook_id' ]) {
// A relationship of some form already exists on this site.
switch ( $act -> type ) {
case 'Follow' :
case 'Invite' :
case 'Join' :
// A second Follow request, but we haven't approved the first one
2018-05-30 04:08:52 +00:00
2021-12-02 23:02:31 +00:00
if ( $contact [ 'abook_pending' ]) {
return ;
}
2018-05-30 04:08:52 +00:00
2021-12-02 23:02:31 +00:00
// We've already approved them or followed them first
// Send an Accept back to them
2018-05-30 04:08:52 +00:00
2021-12-02 23:02:31 +00:00
set_abconfig ( $channel [ 'channel_id' ], $person_obj [ 'id' ], 'activitypub' , 'their_follow_id' , $their_follow_id );
set_abconfig ( $channel [ 'channel_id' ], $person_obj [ 'id' ], 'activitypub' , 'their_follow_type' , $act -> type );
2023-03-23 05:21:07 +00:00
// In case they unfollowed us and followed again, reset their permissions to show that we're connected again.
if ( $their_perms ) {
AbConfig :: Set ( $channel [ 'channel_id' ], $person_obj [ 'id' ], 'system' , 'their_perms' , $their_perms );
}
2021-12-02 23:02:31 +00:00
Run :: Summon ([ 'Notifier' , 'permissions_accept' , $contact [ 'abook_id' ]]);
return ;
case 'Accept' :
// They accepted our Follow request - set default permissions
set_abconfig ( $channel [ 'channel_id' ], $contact [ 'abook_xchan' ], 'system' , 'their_perms' , $their_perms );
$abook_instance = $contact [ 'abook_instance' ];
2022-10-09 01:47:49 +00:00
if ( ! str_contains ( $abook_instance , z_root ())) {
2021-12-03 03:01:39 +00:00
if ( $abook_instance ) {
2021-12-02 23:02:31 +00:00
$abook_instance .= ',' ;
2021-12-03 03:01:39 +00:00
}
2021-12-02 23:02:31 +00:00
$abook_instance .= z_root ();
2022-10-20 09:23:02 +00:00
q (
2022-07-20 05:27:23 +00:00
" update abook set abook_instance = '%s', abook_not_here = 0
where abook_id = % d and abook_channel = % d " ,
2021-12-02 23:02:31 +00:00
dbesc ( $abook_instance ),
intval ( $contact [ 'abook_id' ]),
intval ( $channel [ 'channel_id' ])
);
}
return ;
default :
return ;
}
}
// No previous relationship exists.
if ( $act -> type === 'Accept' ) {
// This should not happen unless we deleted the connection before it was accepted.
return ;
}
2019-10-03 01:07:13 +00:00
2021-12-02 23:02:31 +00:00
// From here on out we assume a Follow activity to somebody we have no existing relationship with
2021-06-19 05:12:23 +00:00
2021-12-02 23:02:31 +00:00
set_abconfig ( $channel [ 'channel_id' ], $person_obj [ 'id' ], 'activitypub' , 'their_follow_id' , $their_follow_id );
set_abconfig ( $channel [ 'channel_id' ], $person_obj [ 'id' ], 'activitypub' , 'their_follow_type' , $act -> type );
2018-05-30 04:08:52 +00:00
2021-12-02 23:02:31 +00:00
// The xchan should have been created by actor_store() above
2018-05-30 04:08:52 +00:00
2021-12-03 03:01:39 +00:00
$r = q (
" select * from xchan where xchan_hash = '%s' and xchan_network = 'activitypub' limit 1 " ,
2021-12-02 23:02:31 +00:00
dbesc ( $person_obj [ 'id' ])
);
2018-05-30 04:08:52 +00:00
2021-12-02 23:02:31 +00:00
if ( ! $r ) {
logger ( 'xchan not found for ' . $person_obj [ 'id' ]);
return ;
}
$ret = $r [ 0 ];
2018-05-30 04:08:52 +00:00
2021-12-02 23:02:31 +00:00
$blocked = LibBlock :: fetch ( $channel [ 'channel_id' ], BLOCKTYPE_SERVER );
if ( $blocked ) {
foreach ( $blocked as $b ) {
2022-10-09 01:47:49 +00:00
if ( str_contains ( $ret [ 'xchan_url' ], $b [ 'block_entity' ])) {
2021-12-02 23:02:31 +00:00
logger ( 'siteblock - follower denied' );
return ;
}
}
}
if ( LibBlock :: fetch_by_entity ( $channel [ 'channel_id' ], $ret [ 'xchan_hash' ])) {
logger ( 'actorblock - follower denied' );
return ;
}
$p = Permissions :: connect_perms ( $channel [ 'channel_id' ]);
$my_perms = Permissions :: serialise ( $p [ 'perms' ]);
$automatic = $p [ 'automatic' ];
$closeness = PConfig :: Get ( $channel [ 'channel_id' ], 'system' , 'new_abook_closeness' , 80 );
2023-05-27 06:45:53 +00:00
$abook_stored = abook_store_lowlevel (
2021-12-02 23:02:31 +00:00
[
'abook_account' => intval ( $channel [ 'channel_account_id' ]),
'abook_channel' => intval ( $channel [ 'channel_id' ]),
'abook_xchan' => $ret [ 'xchan_hash' ],
'abook_closeness' => intval ( $closeness ),
'abook_created' => datetime_convert (),
'abook_updated' => datetime_convert (),
'abook_connected' => datetime_convert (),
'abook_dob' => NULL_DATE ,
'abook_pending' => intval (( $automatic ) ? 0 : 1 ),
'abook_instance' => z_root ()
]
);
2021-12-03 03:01:39 +00:00
if ( $my_perms ) {
2021-12-02 23:02:31 +00:00
AbConfig :: Set ( $channel [ 'channel_id' ], $ret [ 'xchan_hash' ], 'system' , 'my_perms' , $my_perms );
2021-12-03 03:01:39 +00:00
}
2021-12-02 23:02:31 +00:00
2021-12-03 03:01:39 +00:00
if ( $their_perms ) {
2021-12-02 23:02:31 +00:00
AbConfig :: Set ( $channel [ 'channel_id' ], $ret [ 'xchan_hash' ], 'system' , 'their_perms' , $their_perms );
2021-12-03 03:01:39 +00:00
}
2021-12-02 23:02:31 +00:00
2023-05-27 06:45:53 +00:00
// not widely used: save an intro message if it's here.
2023-06-03 22:42:26 +00:00
$content = self :: get_content ( $act , false );
2023-05-27 06:45:53 +00:00
if ( $content [ 'content' ]) {
XConfig :: Set ( $ret [ 'xchan_hash' ], 'system' , 'intro_text' , $content [ 'content' ]);
}
2021-12-02 23:02:31 +00:00
2023-05-27 06:45:53 +00:00
if ( $abook_stored ) {
2021-12-02 23:02:31 +00:00
logger ( " New ActivityPub follower for { $channel [ 'channel_name' ] } " );
2021-12-03 03:01:39 +00:00
$new_connection = q (
2022-06-17 02:46:54 +00:00
" select * from abook left join xchan on abook_xchan = xchan_hash left join hubloc on hubloc_hash = xchan_hash where abook_channel = %d and abook_xchan = '%s' and hubloc_deleted = 0 order by abook_created desc limit 1 " ,
2021-12-02 23:02:31 +00:00
intval ( $channel [ 'channel_id' ]),
dbesc ( $ret [ 'xchan_hash' ])
);
if ( $new_connection ) {
Enotify :: submit (
[
'type' => NOTIFY_INTRO ,
'from_xchan' => $ret [ 'xchan_hash' ],
'to_xchan' => $channel [ 'channel_hash' ],
'link' => z_root () . '/connedit/' . $new_connection [ 0 ][ 'abook_id' ],
]
);
if ( $my_perms && $automatic ) {
// send an Accept for this Follow activity
Run :: Summon ([ 'Notifier' , 'permissions_accept' , $new_connection [ 0 ][ 'abook_id' ]]);
// Send back a Follow notification to them
Run :: Summon ([ 'Notifier' , 'permissions_create' , $new_connection [ 0 ][ 'abook_id' ]]);
}
2022-08-09 21:39:09 +00:00
if ( $automatic || PConfig :: Get ( $channel [ 'channel_id' ], 'system' , 'preview_outbox' )) {
Run :: Summon ([ 'Onepoll' , $new_connection [ 0 ][ 'abook_id' ]]);
}
2021-12-02 23:02:31 +00:00
$clone = [];
foreach ( $new_connection [ 0 ] as $k => $v ) {
2022-10-09 01:47:49 +00:00
if ( str_starts_with ( $k , 'abook_' )) {
2021-12-02 23:02:31 +00:00
$clone [ $k ] = $v ;
}
}
unset ( $clone [ 'abook_id' ]);
unset ( $clone [ 'abook_account' ]);
unset ( $clone [ 'abook_channel' ]);
$abconfig = load_abconfig ( $channel [ 'channel_id' ], $clone [ 'abook_xchan' ]);
if ( $abconfig ) {
$clone [ 'abconfig' ] = $abconfig ;
}
Libsync :: build_sync_packet ( $channel [ 'channel_id' ], [ 'abook' => [ $clone ]]);
}
}
2018-05-30 04:08:52 +00:00
2021-12-02 23:02:31 +00:00
/* If there is a default group for this channel and permissions are automatic, add this member to it */
2018-05-30 04:08:52 +00:00
2021-12-02 23:02:31 +00:00
if ( $channel [ 'channel_default_group' ] && $automatic ) {
$g = AccessList :: rec_byhash ( $channel [ 'channel_id' ], $channel [ 'channel_default_group' ]);
if ( $g ) {
AccessList :: member_add ( $channel [ 'channel_id' ], '' , $ret [ 'xchan_hash' ], $g [ 'id' ]);
}
}
}
2018-05-30 04:08:52 +00:00
2023-12-31 21:02:52 +00:00
public static function unfollowActor ( $channel , $actor )
2021-12-02 23:02:31 +00:00
{
2023-12-31 21:02:52 +00:00
if ( ! $actor ) {
return ;
}
$actorId = is_string ( $actor ) ? $actor : $actor [ 'id' ];
2018-05-30 04:08:52 +00:00
2023-12-31 21:02:52 +00:00
if ( $actorId ) {
2021-12-03 03:01:39 +00:00
$r = q (
" select * from abook left join xchan on abook_xchan = xchan_hash where abook_xchan = '%s' and abook_channel = %d limit 1 " ,
2023-12-31 21:02:52 +00:00
dbesc ( $actorId ),
2021-12-02 23:02:31 +00:00
intval ( $channel [ 'channel_id' ])
);
if ( $r ) {
// remove all permissions they provided
2022-10-20 09:23:02 +00:00
del_abconfig ( $channel [ 'channel_id' ], $r [ 0 ][ 'xchan_hash' ], 'system' , 'their_perms' );
2021-12-02 23:02:31 +00:00
}
}
}
2018-05-30 04:08:52 +00:00
2023-10-25 19:31:37 +00:00
public static function actor_store ( $url , $person_obj , $webfinger = null , $force = false )
2021-12-02 23:02:31 +00:00
{
if ( ! is_array ( $person_obj )) {
return ;
}
2018-05-30 04:08:52 +00:00
2021-12-03 03:01:39 +00:00
// logger('person_obj: ' . print_r($person_obj,true));
2018-05-30 04:08:52 +00:00
2021-12-02 23:02:31 +00:00
if ( array_key_exists ( 'movedTo' , $person_obj ) && $person_obj [ 'movedTo' ] && ! is_array ( $person_obj [ 'movedTo' ])) {
$tgt = self :: fetch ( $person_obj [ 'movedTo' ]);
if ( is_array ( $tgt )) {
self :: actor_store ( $person_obj [ 'movedTo' ], $tgt );
ActivityPub :: move ( $person_obj [ 'id' ], $tgt );
}
return ;
}
2018-05-30 04:08:52 +00:00
2018-10-25 05:43:00 +00:00
2021-12-02 23:02:31 +00:00
$ap_hubloc = null ;
2018-10-25 05:43:00 +00:00
2021-12-02 23:02:31 +00:00
$hublocs = self :: get_actor_hublocs ( $url );
if ( $hublocs ) {
foreach ( $hublocs as $hub ) {
if ( $hub [ 'hubloc_network' ] === 'activitypub' ) {
$ap_hubloc = $hub ;
}
2021-12-06 21:19:19 +00:00
if ( in_array ( $hub [ 'hubloc_network' ],[ 'zot6' , 'nomad' ])) {
2021-12-02 23:02:31 +00:00
Libzot :: update_cached_hubloc ( $hub );
}
}
}
if ( $ap_hubloc ) {
// we already have a stored record. Determine if it needs updating.
2023-09-17 22:08:30 +00:00
if ( $ap_hubloc [ 'hubloc_updated' ] < datetime_convert ( 'UTC' , 'UTC' , ' now - ' . self :: ACTOR_CACHE_DAYS . ' days' ) || $force ) {
2021-12-02 23:02:31 +00:00
$person_obj = self :: fetch ( $url );
// ensure we received something
if ( ! is_array ( $person_obj )) {
return ;
}
} else {
return ;
}
}
if ( isset ( $person_obj [ 'id' ])) {
$url = $person_obj [ 'id' ];
}
if ( ! $url ) {
return ;
}
// store the actor record in XConfig
XConfig :: Set ( $url , 'system' , 'actor_record' , $person_obj );
2022-06-17 23:22:17 +00:00
$name = unicode_trim ( escape_tags ( $person_obj [ 'name' ]));
2021-12-03 03:01:39 +00:00
if ( ! $name ) {
2021-12-02 23:02:31 +00:00
$name = escape_tags ( $person_obj [ 'preferredUsername' ]);
2021-12-03 03:01:39 +00:00
}
if ( ! $name ) {
2021-12-02 23:02:31 +00:00
$name = escape_tags ( t ( 'Unknown' ));
2021-12-03 03:01:39 +00:00
}
2021-12-02 23:02:31 +00:00
2023-10-28 10:20:06 +00:00
$webfingerAddress = EMPTY_STR ;
2021-12-02 23:02:31 +00:00
$username = escape_tags ( $person_obj [ 'preferredUsername' ]);
$h = parse_url ( $url );
if ( $h && $h [ 'host' ]) {
2023-10-28 10:20:06 +00:00
$webfingerAddress = $username . '@' . $h [ 'host' ];
2021-12-02 23:02:31 +00:00
}
2022-07-20 05:27:23 +00:00
2023-07-29 10:14:37 +00:00
$icon = self :: getIcon ( $person_obj [ 'icon' ]);
2018-05-30 04:08:52 +00:00
2021-12-02 23:02:31 +00:00
$cover_photo = false ;
2018-10-25 05:43:00 +00:00
2021-12-02 23:02:31 +00:00
if ( isset ( $person_obj [ 'image' ])) {
if ( is_string ( $person_obj [ 'image' ])) {
$cover_photo = $person_obj [ 'image' ];
}
if ( isset ( $person_obj [ 'image' ][ 'url' ])) {
2023-07-18 07:04:13 +00:00
$ptr = $person_obj [ 'image' ][ 'url' ];
if ( is_string ( $ptr )) {
$cover_photo = $ptr ;
}
else {
if ( ! array_key_exists ( 0 , $ptr )) {
$ptr = [ $ptr ];
}
foreach ( $ptr as $p ) {
if ( isset ( $p [ 'type' ]) && $p [ 'type' ] === 'Link' && isset ( $p [ 'href' ])) {
$cover_photo = $ptr [ 'href' ];
if ( ! isset ( $p [ 'mediaType' ]) || str_starts_with ( $p [ 'mediaType' ], 'image' )) {
break ;
}
}
}
}
2021-12-02 23:02:31 +00:00
}
}
2018-05-30 04:08:52 +00:00
2021-12-02 23:02:31 +00:00
$hidden = false ;
2022-03-07 21:54:11 +00:00
// Mastodon style hidden flag
2021-12-02 23:02:31 +00:00
if ( array_key_exists ( 'discoverable' , $person_obj ) && ( ! intval ( $person_obj [ 'discoverable' ]))) {
$hidden = true ;
}
2022-03-07 21:54:11 +00:00
// Pleroma style hidden flag
if ( array_key_exists ( 'invisible' , $person_obj ) && ( ! intval ( $person_obj [ 'invisible' ]))) {
$hidden = true ;
}
2018-05-30 04:08:52 +00:00
2021-12-02 23:02:31 +00:00
$links = false ;
$profile = false ;
2018-05-30 04:08:52 +00:00
2021-12-02 23:02:31 +00:00
if ( is_array ( $person_obj [ 'url' ])) {
if ( ! array_key_exists ( 0 , $person_obj [ 'url' ])) {
$links = [ $person_obj [ 'url' ]];
} else {
$links = $person_obj [ 'url' ];
}
}
2018-05-30 04:08:52 +00:00
2021-12-02 23:02:31 +00:00
if ( is_array ( $links ) && $links ) {
foreach ( $links as $link ) {
if ( is_array ( $link ) && array_key_exists ( 'mediaType' , $link ) && $link [ 'mediaType' ] === 'text/html' ) {
$profile = $link [ 'href' ];
2022-06-17 22:41:57 +00:00
} elseif ( is_string ( $link )) {
$profile = $link ;
break ;
2021-12-02 23:02:31 +00:00
}
}
if ( ! $profile ) {
$profile = $links [ 0 ][ 'href' ];
}
} elseif ( isset ( $person_obj [ 'url' ]) && is_string ( $person_obj [ 'url' ])) {
$profile = $person_obj [ 'url' ];
}
2018-05-30 04:08:52 +00:00
2021-12-02 23:02:31 +00:00
if ( ! $profile ) {
$profile = $url ;
}
2018-05-30 04:08:52 +00:00
2021-12-02 23:02:31 +00:00
$inbox = (( array_key_exists ( 'inbox' , $person_obj )) ? $person_obj [ 'inbox' ] : null );
2018-05-30 04:08:52 +00:00
2021-12-02 23:02:31 +00:00
// either an invalid identity or a cached entry of some kind which didn't get caught above
2018-05-30 04:08:52 +00:00
2022-10-09 01:47:49 +00:00
if (( ! $inbox ) || str_contains ( $inbox , z_root ())) {
2021-12-02 23:02:31 +00:00
return ;
}
2018-05-30 04:08:52 +00:00
2021-12-02 23:02:31 +00:00
$collections = [];
2018-05-30 04:08:52 +00:00
2022-10-20 09:23:02 +00:00
$collections [ 'inbox' ] = $inbox ;
if ( array_key_exists ( 'outbox' , $person_obj ) && is_string ( $person_obj [ 'outbox' ])) {
$collections [ 'outbox' ] = $person_obj [ 'outbox' ];
}
if ( array_key_exists ( 'followers' , $person_obj ) && is_string ( $person_obj [ 'followers' ])) {
$collections [ 'followers' ] = $person_obj [ 'followers' ];
}
if ( array_key_exists ( 'following' , $person_obj ) && is_string ( $person_obj [ 'following' ])) {
$collections [ 'following' ] = $person_obj [ 'following' ];
}
if ( array_key_exists ( 'wall' , $person_obj ) && is_string ( $person_obj [ 'wall' ])) {
$collections [ 'wall' ] = $person_obj [ 'wall' ];
}
if ( array_path_exists ( 'endpoints/sharedInbox' , $person_obj ) && is_string ( $person_obj [ 'endpoints' ][ 'sharedInbox' ])) {
$collections [ 'sharedInbox' ] = $person_obj [ 'endpoints' ][ 'sharedInbox' ];
2021-12-02 23:02:31 +00:00
}
2023-01-22 09:14:02 +00:00
if ( array_path_exists ( 'endpoints/searchContent' , $person_obj ) && is_string ( $person_obj [ 'endpoints' ][ 'searchContent' ])) {
$collections [ 'searchContent' ] = $person_obj [ 'endpoints' ][ 'searchContent' ];
}
if ( array_path_exists ( 'endpoints/searchTags' , $person_obj ) && is_string ( $person_obj [ 'endpoints' ][ 'searchTags' ])) {
$collections [ 'searchTags' ] = $person_obj [ 'endpoints' ][ 'searchTags' ];
}
2021-12-02 23:02:31 +00:00
if ( isset ( $person_obj [ 'publicKey' ][ 'publicKeyPem' ])) {
if ( $person_obj [ 'id' ] === $person_obj [ 'publicKey' ][ 'owner' ]) {
$pubkey = $person_obj [ 'publicKey' ][ 'publicKeyPem' ];
2022-10-09 01:47:49 +00:00
if ( str_contains ( $pubkey , 'RSA ' )) {
2022-10-20 09:23:02 +00:00
$pubkey = Keyutils :: rsaToPem ( $pubkey );
2021-12-02 23:02:31 +00:00
}
}
}
2024-01-02 04:34:19 +00:00
$epubkey = '' ;
if ( isset ( $person_obj [ 'assertionMethod' ][ 'publicKeyMultibase' ])) {
if ( $person_obj [ 'id' ] === $person_obj [ 'assertionMethod' ][ 'controller' ]) {
$epubkey = $person_obj [ 'assertionMethod' ][ 'publicKeyMultibase' ];
if ( $person_obj [ 'assertionMethod' ][ 'type' ] === 'Multikey' ) {
$epubkey = $person_obj [ 'assertionMethod' ][ 'publicKeyMultibase' ];
}
}
}
2021-12-02 23:02:31 +00:00
$keywords = [];
if ( isset ( $person_obj [ 'tag' ]) && is_array ( $person_obj [ 'tag' ])) {
foreach ( $person_obj [ 'tag' ] as $t ) {
if ( is_array ( $t ) && isset ( $t [ 'type' ]) && $t [ 'type' ] === 'Hashtag' ) {
if ( isset ( $t [ 'name' ])) {
2022-10-09 01:47:49 +00:00
$tag = escape_tags (( str_starts_with ( $t [ 'name' ], '#' )) ? substr ( $t [ 'name' ], 1 ) : $t [ 'name' ]);
2021-12-02 23:02:31 +00:00
if ( $tag ) {
$keywords [] = $tag ;
}
}
}
2023-05-16 11:06:18 +00:00
if ( is_array ( $t ) && isset ( $t [ 'type' ]) && $t [ 'type' ] === 'Note' ) {
if ( isset ( $t [ 'name' ]) && isset ( $t [ 'content' ]) && $t [ 'name' ] === 'Protocol' ) {
self :: update_protocols ( $url , trim ( $t [ 'content' ]));
2021-12-02 23:02:31 +00:00
}
}
}
}
$xchan_type = self :: get_xchan_type ( $person_obj [ 'type' ]);
$about = (( isset ( $person_obj [ 'summary' ])) ? html2bbcode ( purify_html ( $person_obj [ 'summary' ])) : EMPTY_STR );
2021-12-03 03:01:39 +00:00
$p = q (
2021-12-06 21:19:19 +00:00
" select * from xchan where xchan_url = '%s' and xchan_network in ('zot6','nomad') limit 1 " ,
2021-12-02 23:02:31 +00:00
dbesc ( $url )
);
if ( $p ) {
2021-12-06 21:19:19 +00:00
set_xconfig ( $url , 'system' , 'protocols' , 'nomad,zot6,activitypub' );
2021-12-02 23:02:31 +00:00
}
// there is no standard way to represent an 'instance actor' but this will at least subdue the multiple
// pages of Mastodon and Pleroma instance actors in the directory.
// @TODO - (2021-08-27) remove this if they provide a non-person xchan_type
// once extended xchan_type directory filtering is implemented.
2023-11-24 06:07:37 +00:00
2021-12-02 23:02:31 +00:00
$censored = (( strpos ( $profile , 'instance_actor' ) || strpos ( $profile , '/internal/fetch' )) ? 1 : 0 );
2021-12-03 03:01:39 +00:00
$r = q (
" select * from xchan where xchan_hash = '%s' limit 1 " ,
2021-12-02 23:02:31 +00:00
dbesc ( $url )
);
if ( ! $r ) {
// create a new record
2022-10-23 05:00:42 +00:00
xchan_store_lowlevel ( [
'xchan_hash' => $url ,
'xchan_guid' => $url ,
'xchan_pubkey' => $pubkey ,
2024-01-02 04:34:19 +00:00
'xchan_epubkey' => $epubkey ,
2023-10-28 10:20:06 +00:00
'xchan_addr' => $webfingerAddress ,
2022-10-23 05:00:42 +00:00
'xchan_url' => $profile ,
'xchan_name' => $name ,
'xchan_hidden' => intval ( $hidden ),
'xchan_updated' => datetime_convert (),
'xchan_name_date' => datetime_convert (),
'xchan_network' => 'activitypub' ,
'xchan_type' => $xchan_type ,
'xchan_photo_date' => datetime_convert ( 'UTC' , 'UTC' , '1968-01-01' ),
'xchan_photo_l' => z_root () . '/' . Channel :: get_default_profile_photo (),
'xchan_photo_m' => z_root () . '/' . Channel :: get_default_profile_photo ( 80 ),
'xchan_photo_s' => z_root () . '/' . Channel :: get_default_profile_photo ( 48 ),
'xchan_photo_mimetype' => 'image/png' ,
'xchan_censored' => $censored
]);
2022-10-20 09:23:02 +00:00
}
else {
2021-12-02 23:02:31 +00:00
// Record exists. Cache existing records for a set number of days
// then refetch to catch updated profile photos, names, etc.
2023-09-17 22:08:30 +00:00
if ( $r [ 0 ][ 'xchan_name_date' ] >= datetime_convert ( 'UTC' , 'UTC' , 'now - ' . self :: ACTOR_CACHE_DAYS . ' days' ) && ( ! $force )) {
2021-12-02 23:02:31 +00:00
return ;
}
// update existing record
2022-10-20 09:23:02 +00:00
q (
2024-01-02 04:34:19 +00:00
" update xchan set xchan_updated = '%s', xchan_name = '%s', xchan_pubkey = '%s', xchan_epubkey = '%s', xchan_network = '%s', xchan_name_date = '%s', xchan_hidden = %d, xchan_type = %d, xchan_censored = %d where xchan_hash = '%s' " ,
2021-12-02 23:02:31 +00:00
dbesc ( datetime_convert ()),
dbesc ( $name ),
dbesc ( $pubkey ),
2024-01-02 04:34:19 +00:00
dbesc ( $epubkey ),
2021-12-02 23:02:31 +00:00
dbesc ( 'activitypub' ),
dbesc ( datetime_convert ()),
intval ( $hidden ),
intval ( $xchan_type ),
2023-11-24 10:18:40 +00:00
intval ( $r [ 0 ][ 'xchan_censored' ] ? : $censored ),
2021-12-02 23:02:31 +00:00
dbesc ( $url )
);
2023-10-28 10:20:06 +00:00
if ( $webfingerAddress !== $r [ 0 ][ 'xchan_addr' ]) {
2022-10-20 09:23:02 +00:00
q (
2021-12-03 03:01:39 +00:00
" update xchan set xchan_addr = '%s' where xchan_hash = '%s' " ,
2023-10-28 10:20:06 +00:00
dbesc ( $webfingerAddress ),
2021-12-02 23:02:31 +00:00
dbesc ( $url )
);
}
}
if ( $cover_photo ) {
set_xconfig ( $url , 'system' , 'cover_photo' , $cover_photo );
2022-08-23 10:15:05 +00:00
if ( is_string ( $cover_photo )) {
import_remote_cover_photo ( $cover_photo , $url );
}
2021-12-02 23:02:31 +00:00
}
$m = parse_url ( $url );
if ( $m [ 'scheme' ] && $m [ 'host' ]) {
2023-10-28 10:20:06 +00:00
$site_url = $m [ 'scheme' ] . '://' . $m [ 'host' ] . (( $m [ 'port' ]) ? ':' . $m [ 'port' ] : '' );
2023-11-07 20:35:40 +00:00
if ( ! SConfig :: Get ( $site_url , 'system' , 'owa' )) {
if ( $webfinger === null ) {
$webfinger = Webfinger :: exec ( $webfingerAddress );
}
if ( $webfinger && ! empty ( $webfinger [ 'links' ])) {
$authlinks = linksByRel ( $webfinger [ 'links' ], 'http://purl.org/openwebauth/v1' );
if ( $authlinks ) {
$owa = array_shift ( $authlinks );
if ( isset ( $owa [ 'href' ])) {
SConfig :: Set ( $site_url , 'system' , 'owa' , $owa [ 'href' ]);
}
2023-10-28 10:20:06 +00:00
}
}
}
2021-12-02 23:02:31 +00:00
$ni = Nodeinfo :: fetch ( $site_url );
if ( $ni && is_array ( $ni )) {
$software = (( array_path_exists ( 'software/name' , $ni )) ? $ni [ 'software' ][ 'name' ] : '' );
$version = (( array_path_exists ( 'software/version' , $ni )) ? $ni [ 'software' ][ 'version' ] : '' );
$register = $ni [ 'openRegistrations' ];
2021-12-03 03:01:39 +00:00
$site = q (
" select * from site where site_url = '%s' " ,
2021-12-02 23:02:31 +00:00
dbesc ( $site_url )
);
if ( $site ) {
2021-12-03 03:01:39 +00:00
q (
" update site set site_project = '%s', site_update = '%s', site_version = '%s' where site_url = '%s' " ,
2021-12-02 23:02:31 +00:00
dbesc ( $software ),
dbesc ( datetime_convert ()),
dbesc ( $version ),
dbesc ( $site_url )
);
// it may have been saved originally as an unknown type, but we now know what it is
if ( intval ( $site [ 0 ][ 'site_type' ]) === SITE_TYPE_UNKNOWN ) {
2021-12-03 03:01:39 +00:00
q (
" update site set site_type = %d where site_url = '%s' " ,
2021-12-02 23:02:31 +00:00
intval ( SITE_TYPE_NOTZOT ),
dbesc ( $site_url )
);
}
} else {
site_store_lowlevel (
[
'site_url' => $site_url ,
'site_update' => datetime_convert (),
'site_dead' => 0 ,
'site_type' => SITE_TYPE_NOTZOT ,
'site_project' => $software ,
'site_version' => $version ,
'site_access' => (( $register ) ? ACCESS_FREE : ACCESS_PRIVATE ),
'site_register' => (( $register ) ? REGISTER_OPEN : REGISTER_CLOSED )
]
);
}
}
}
2023-07-22 23:01:55 +00:00
$nomadProfile = new Profile ([ 'about' => $about , 'keywords' => $keywords , 'dob' => '0000-00-00' ]);
Libzotdir :: import_directory_profile ( $url , $nomadProfile , null , 0 , true );
2021-12-02 23:02:31 +00:00
if ( $collections ) {
set_xconfig ( $url , 'activitypub' , 'collections' , $collections );
}
2021-12-03 03:01:39 +00:00
$h = q (
2022-06-17 02:46:54 +00:00
" select * from hubloc where hubloc_hash = '%s' and hubloc_deleted = 0 limit 1 " ,
2021-12-02 23:02:31 +00:00
dbesc ( $url )
);
$m = parse_url ( $url );
if ( $m ) {
$hostname = $m [ 'host' ];
$baseurl = $m [ 'scheme' ] . '://' . $m [ 'host' ] . (( isset ( $m [ 'port' ]) && intval ( $m [ 'port' ])) ? ':' . $m [ 'port' ] : '' );
}
if ( ! $h ) {
2022-10-23 05:00:42 +00:00
hubloc_store_lowlevel ([
'hubloc_guid' => $url ,
'hubloc_hash' => $url ,
'hubloc_id_url' => $profile ,
2023-10-28 10:20:06 +00:00
'hubloc_addr' => $webfingerAddress ,
2022-10-23 05:00:42 +00:00
'hubloc_network' => 'activitypub' ,
'hubloc_url' => $baseurl ,
'hubloc_host' => $hostname ,
'hubloc_callback' => $inbox ,
'hubloc_updated' => datetime_convert (),
'hubloc_primary' => 1
]);
}
else {
2023-10-28 10:20:06 +00:00
if ( $webfingerAddress !== $h [ 0 ][ 'hubloc_addr' ]) {
2022-10-23 05:00:42 +00:00
q (
2021-12-03 03:01:39 +00:00
" update hubloc set hubloc_addr = '%s' where hubloc_hash = '%s' " ,
2023-10-28 10:20:06 +00:00
dbesc ( $webfingerAddress ),
2021-12-02 23:02:31 +00:00
dbesc ( $url )
);
}
if ( $inbox !== $h [ 0 ][ 'hubloc_callback' ]) {
2022-10-23 05:00:42 +00:00
q (
2021-12-03 03:01:39 +00:00
" update hubloc set hubloc_callback = '%s' where hubloc_hash = '%s' " ,
2021-12-02 23:02:31 +00:00
dbesc ( $inbox ),
dbesc ( $url )
);
}
if ( $profile !== $h [ 0 ][ 'hubloc_id_url' ]) {
2022-10-23 05:00:42 +00:00
q (
2021-12-03 03:01:39 +00:00
" update hubloc set hubloc_id_url = '%s' where hubloc_hash = '%s' " ,
2021-12-02 23:02:31 +00:00
dbesc ( $profile ),
dbesc ( $url )
);
}
2022-10-23 05:00:42 +00:00
q (
2021-12-03 03:01:39 +00:00
" update hubloc set hubloc_updated = '%s' where hubloc_hash = '%s' " ,
2021-12-02 23:02:31 +00:00
dbesc ( datetime_convert ()),
dbesc ( $url )
);
}
if ( ! $icon ) {
2022-10-20 09:23:02 +00:00
$icon = z_root () . '/' . Channel :: get_default_profile_photo ();
2021-12-02 23:02:31 +00:00
}
// We store all ActivityPub actors we can resolve. Some of them may be able to communicate over Zot6. Find them.
// Only probe if it looks like it looks something like a zot6 URL as there isn't anything in the actor record which we can reliably use for this purpose
// and adding zot discovery urls to the actor record will cause federation to fail with the 20-30 projects which don't accept arrays in the url field.
2022-10-09 01:47:49 +00:00
if ( str_contains ( $url , '/channel/' )) {
2021-12-03 03:01:39 +00:00
$zx = q (
2022-06-17 02:46:54 +00:00
" select * from hubloc where hubloc_id_url = '%s' and hubloc_network in ('zot6','nomad') and hubloc_deleted = 0 " ,
2021-12-02 23:02:31 +00:00
dbesc ( $url )
);
2023-10-28 10:20:06 +00:00
if ( $webfingerAddress && ( ! $zx )) {
Run :: Summon ([ 'Gprobe' , $webfingerAddress ]);
2021-12-02 23:02:31 +00:00
}
}
Run :: Summon ([ 'Xchan_photo' , bin2hex ( $icon ), bin2hex ( $url )]);
}
public static function update_protocols ( $xchan , $str )
{
$existing = explode ( ',' , get_xconfig ( $xchan , 'system' , 'protocols' , EMPTY_STR ));
if ( ! in_array ( $str , $existing )) {
$existing [] = $str ;
set_xconfig ( $xchan , 'system' , 'protocols' , implode ( ',' , $existing ));
}
}
2023-07-29 10:14:37 +00:00
public static function getIcon ( $element )
{
$icon = null ;
if ( isset ( $element )) {
if ( is_string ( $element )) {
$icon = $element ;
}
if ( is_array ( $element )) {
if ( ! array_key_exists ( 0 , $element )) {
$element = [ $element ];
}
foreach ( $element as $asobject ) {
if ( is_string ( $asobject )) {
$icon = $asobject ;
break ;
}
if ( ! empty ( $asobject [ 'url' ]) && is_string ( $asobject [ 'url' ])) {
$icon = $asobject [ 'url' ];
break ;
}
if ( ! empty ( $asobject [ 'href' ]) && is_string ( $asobject [ 'href' ])) {
$icon = $asobject [ 'href' ];
break ;
}
}
}
}
return $icon ;
}
2021-12-02 23:02:31 +00:00
2024-01-13 23:13:18 +00:00
public static function drop ( $channel , $observer , $act )
2021-12-02 23:02:31 +00:00
{
2021-12-03 03:01:39 +00:00
$r = q (
" select * from item where mid = '%s' and uid = %d limit 1 " ,
2021-12-02 23:02:31 +00:00
dbesc (( is_array ( $act -> obj )) ? $act -> obj [ 'id' ] : $act -> obj ),
intval ( $channel [ 'channel_id' ])
);
2023-03-27 04:56:05 +00:00
2021-12-02 23:02:31 +00:00
if ( ! $r ) {
return ;
}
if ( in_array ( $observer , [ $r [ 0 ][ 'author_xchan' ], $r [ 0 ][ 'owner_xchan' ]])) {
2023-03-27 04:56:05 +00:00
drop_item ( $r [ 0 ][ 'id' ], observer_hash : $observer );
}
elseif ( in_array ( $act -> actor [ 'id' ], [ $r [ 0 ][ 'author_xchan' ], $r [ 0 ][ 'owner_xchan' ]])) {
2022-06-26 08:36:02 +00:00
drop_item ( $r [ 0 ][ 'id' ]);
2021-12-02 23:02:31 +00:00
}
}
// sort function width decreasing
public static function vid_sort ( $a , $b )
{
2021-12-03 03:01:39 +00:00
if ( $a [ 'width' ] === $b [ 'width' ]) {
2021-12-02 23:02:31 +00:00
return 0 ;
2021-12-03 03:01:39 +00:00
}
2021-12-02 23:02:31 +00:00
return (( $a [ 'width' ] > $b [ 'width' ]) ? - 1 : 1 );
}
public static function get_actor_bbmention ( $id )
{
2022-06-17 02:46:54 +00:00
$x = hublocx_id_query ( $id , 1 );
2021-12-02 23:02:31 +00:00
if ( $x ) {
2022-10-20 09:23:02 +00:00
// a name starting with a left paren can trick the Markdown parser into creating a link so insert a zero-width space
2022-10-09 01:47:49 +00:00
if ( str_starts_with ( $x [ 0 ][ 'xchan_name' ], '(' )) {
2021-12-02 23:02:31 +00:00
$x [ 0 ][ 'xchan_name' ] = htmlspecialchars_decode ( '​' , ENT_QUOTES ) . $x [ 0 ][ 'xchan_name' ];
}
return sprintf ( '@[zrl=%s]%s[/zrl]' , $x [ 0 ][ 'xchan_url' ], $x [ 0 ][ 'xchan_name' ]);
}
return '@{' . $id . '}' ;
}
2023-08-11 19:43:19 +00:00
public static function update_poll ( $item , $post )
2021-12-02 23:02:31 +00:00
{
logger ( 'updating poll' );
$multi = false ;
$mid = $post [ 'mid' ];
2022-12-20 20:33:11 +00:00
$content = trim ( $post [ 'title' ]);
2021-12-02 23:02:31 +00:00
if ( ! $item ) {
2023-08-11 05:44:53 +00:00
logger ( 'no item' );
2021-12-02 23:02:31 +00:00
return false ;
}
$o = json_decode ( $item [ 'obj' ], true );
if ( $o && array_key_exists ( 'anyOf' , $o )) {
$multi = true ;
}
2021-12-03 03:01:39 +00:00
$r = q (
" select mid, title from item where parent_mid = '%s' and author_xchan = '%s' and mid != parent_mid " ,
2021-12-02 23:02:31 +00:00
dbesc ( $item [ 'mid' ]),
dbesc ( $post [ 'author_xchan' ])
);
// prevent any duplicate votes by same author for oneOf and duplicate votes with same author and same answer for anyOf
if ( $r ) {
if ( $multi ) {
foreach ( $r as $rv ) {
2022-12-20 20:33:11 +00:00
if ( trim ( $rv [ 'title' ]) === $content && $rv [ 'mid' ] !== $mid ) {
2023-08-11 05:44:53 +00:00
logger ( 'already voted multi' );
2021-12-02 23:02:31 +00:00
return false ;
}
}
} else {
foreach ( $r as $rv ) {
2023-08-11 09:15:53 +00:00
if ( $rv [ 'mid' ] !== $mid && $content ) {
2023-08-11 05:44:53 +00:00
logger ( 'already voted' );
2021-12-02 23:02:31 +00:00
return false ;
}
}
}
}
$answer_found = false ;
2023-07-20 22:31:04 +00:00
$foundPrevious = false ;
2021-12-02 23:02:31 +00:00
if ( $multi ) {
for ( $c = 0 ; $c < count ( $o [ 'anyOf' ]); $c ++ ) {
2022-12-20 20:33:11 +00:00
if ( trim ( $o [ 'anyOf' ][ $c ][ 'name' ]) === $content ) {
2021-12-02 23:02:31 +00:00
$answer_found = true ;
if ( is_array ( $o [ 'anyOf' ][ $c ][ 'replies' ])) {
foreach ( $o [ 'anyOf' ][ $c ][ 'replies' ] as $reply ) {
if ( is_array ( $reply ) && array_key_exists ( 'id' , $reply ) && $reply [ 'id' ] === $mid ) {
2023-07-20 22:31:04 +00:00
$foundPrevious = true ;
2021-12-02 23:02:31 +00:00
}
}
}
2023-07-20 22:31:04 +00:00
if ( ! $foundPrevious ) {
2021-12-02 23:02:31 +00:00
$o [ 'anyOf' ][ $c ][ 'replies' ][ 'totalItems' ] ++ ;
$o [ 'anyOf' ][ $c ][ 'replies' ][ 'items' ][] = [ 'id' => $mid , 'type' => 'Note' ];
}
}
}
} else {
for ( $c = 0 ; $c < count ( $o [ 'oneOf' ]); $c ++ ) {
2022-12-20 20:33:11 +00:00
if ( trim ( $o [ 'oneOf' ][ $c ][ 'name' ]) === $content ) {
2021-12-02 23:02:31 +00:00
$answer_found = true ;
if ( is_array ( $o [ 'oneOf' ][ $c ][ 'replies' ])) {
foreach ( $o [ 'oneOf' ][ $c ][ 'replies' ] as $reply ) {
if ( is_array ( $reply ) && array_key_exists ( 'id' , $reply ) && $reply [ 'id' ] === $mid ) {
2023-07-20 22:31:04 +00:00
$foundPrevious = true ;
2021-12-02 23:02:31 +00:00
}
}
}
2023-07-20 22:31:04 +00:00
if ( ! $foundPrevious ) {
2021-12-02 23:02:31 +00:00
$o [ 'oneOf' ][ $c ][ 'replies' ][ 'totalItems' ] ++ ;
$o [ 'oneOf' ][ $c ][ 'replies' ][ 'items' ][] = [ 'id' => $mid , 'type' => 'Note' ];
}
}
}
}
2018-05-30 04:08:52 +00:00
2021-12-02 23:02:31 +00:00
if ( $item [ 'comments_closed' ] > NULL_DATE ) {
if ( $item [ 'comments_closed' ] > datetime_convert ()) {
$o [ 'closed' ] = datetime_convert ( 'UTC' , 'UTC' , $item [ 'comments_closed' ], ATOM_TIME );
// set this to force an update
$answer_found = true ;
}
}
logger ( 'updated_poll: ' . print_r ( $o , true ), LOGGER_DATA );
2023-08-11 19:43:19 +00:00
if ( $answer_found && ! $foundPrevious ) {
2023-07-20 22:31:04 +00:00
// undo moderation if it was applied.
2022-10-20 09:23:02 +00:00
q (
2023-07-20 22:31:04 +00:00
" update item set obj = '%s', edited = '%s', item_blocked = 0 where id = %d " ,
2021-12-02 23:02:31 +00:00
dbesc ( json_encode ( $o )),
dbesc ( datetime_convert ()),
intval ( $item [ 'id' ])
);
2023-07-09 22:34:55 +00:00
Run :: Summon ([ 'Notifier' , 'edit_post' , $item [ 'id' ]]);
2021-12-02 23:02:31 +00:00
return true ;
}
2023-08-11 05:44:53 +00:00
logger ( 'update poll was not stored' );
2021-12-02 23:02:31 +00:00
return false ;
}
public static function decode_note ( $act , $cacheable = false )
{
$response_activity = false ;
2023-08-05 07:56:01 +00:00
$item = [];
2021-12-02 23:02:31 +00:00
2023-09-11 11:34:38 +00:00
// Intransitives. Treat the target as the object in order to pick out any
// important fields and represent those as an item.
2023-09-15 09:10:58 +00:00
2023-09-11 11:34:38 +00:00
if ( in_array ( $act -> type ,[ 'Arrive' , 'Leave' ]) && $act -> tgt && ! $act -> obj ) {
$act -> obj = $act -> tgt ;
}
2021-12-02 23:02:31 +00:00
if ( is_array ( $act -> obj )) {
$binary = false ;
$markdown = false ;
2022-04-29 21:32:41 +00:00
$mediatype = $act -> objprop ( 'mediaType' , '' );
if ( $mediatype && $mediatype !== 'text/html' ) {
if ( $mediatype === 'text/markdown' ) {
2021-12-02 23:02:31 +00:00
$markdown = true ;
} else {
2023-08-05 07:56:01 +00:00
$item [ 'mimetype' ] = escape_tags ( $mediatype );
2021-12-02 23:02:31 +00:00
$binary = true ;
}
}
2018-05-30 04:08:52 +00:00
2021-12-02 23:02:31 +00:00
$content = self :: get_content ( $act -> obj , $binary );
2018-05-30 04:08:52 +00:00
2021-12-02 23:02:31 +00:00
if ( $cacheable ) {
// Zot6 activities will all be rendered from bbcode source in order to generate dynamic content.
// If the activity came from ActivityPub (hence $cacheable is set), use the HTML rendering
// and discard the bbcode source since it is unlikely that it is compatible with our implementation.
2022-08-13 10:57:14 +00:00
//
2021-12-02 23:02:31 +00:00
// Friendica for example.
unset ( $content [ 'bbcode' ]);
}
// handle markdown conversion inline (peertube)
if ( $markdown ) {
foreach ([ 'summary' , 'content' ] as $t ) {
$content [ $t ] = Markdown :: to_bbcode ( $content [ $t ], true , [ 'preserve_lf' => true ]);
}
}
}
// These activities should have been handled separately in the Inbox module and should not be turned into posts
2021-12-03 03:01:39 +00:00
if (
2022-04-29 21:32:41 +00:00
in_array ( $act -> type , [ 'Follow' , 'Accept' , 'Reject' , 'Create' , 'Update' ])
&& ( $act -> objprop ( 'type' ) === 'Follow' || ActivityStreams :: is_an_actor ( $act -> objprop ( 'type' )))
2021-12-03 03:01:39 +00:00
) {
2021-12-02 23:02:31 +00:00
return false ;
}
// Within our family of projects, Follow/Unfollow of a thread is an internal activity which should not be transmitted,
// hence if we receive it - ignore or reject it.
// This may have to be revisited if AP projects start using Follow for objects other than actors.
if ( in_array ( $act -> type , [ ACTIVITY_FOLLOW , ACTIVITY_IGNORE ])) {
return false ;
}
// Do not proceed further if there is no actor.
if ( ! isset ( $act -> actor [ 'id' ])) {
logger ( 'No actor!' );
return false ;
}
2023-08-05 07:56:01 +00:00
$item [ 'owner_xchan' ] = $act -> actor [ 'id' ];
$item [ 'author_xchan' ] = $act -> actor [ 'id' ];
2021-12-02 23:02:31 +00:00
// ensure we store the original actor
self :: actor_store ( $act -> actor [ 'id' ], $act -> actor );
2023-08-05 07:56:01 +00:00
$item [ 'mid' ] = ( $act -> objprop ( 'id' )) ? $act -> objprop ( 'id' ) : $act -> obj ;
2021-12-02 23:02:31 +00:00
2023-08-05 07:56:01 +00:00
if ( ! $item [ 'mid' ]) {
2021-12-02 23:02:31 +00:00
return false ;
}
2023-09-29 22:31:07 +00:00
if ( str_starts_with ( $item [ 'mid' ], z_root () . '/event/' )) {
$item [ 'mid' ] = str_replace ( '/event/' , '/item/' , $item [ 'mid' ]);
}
2023-08-05 07:56:01 +00:00
$item [ 'parent_mid' ] = $act -> parent_id ;
2021-12-02 23:02:31 +00:00
2023-09-29 22:31:07 +00:00
if ( isset ( $item [ 'parent_mid' ]) && str_starts_with ( $item [ 'parent_mid' ], z_root () . '/event/' )) {
$item [ 'parent_mid' ] = str_replace ( '/event/' , '/item/' , $item [ 'parent_mid' ]);
}
2021-12-02 23:02:31 +00:00
if ( array_key_exists ( 'published' , $act -> data ) && $act -> data [ 'published' ]) {
2023-08-05 07:56:01 +00:00
$item [ 'created' ] = datetime_convert ( 'UTC' , 'UTC' , $act -> data [ 'published' ]);
2022-04-29 21:32:41 +00:00
} elseif ( $act -> objprop ( 'published' )) {
2023-08-05 07:56:01 +00:00
$item [ 'created' ] = datetime_convert ( 'UTC' , 'UTC' , $act -> obj [ 'published' ]);
2021-12-02 23:02:31 +00:00
}
if ( array_key_exists ( 'updated' , $act -> data ) && $act -> data [ 'updated' ]) {
2023-08-05 07:56:01 +00:00
$item [ 'edited' ] = datetime_convert ( 'UTC' , 'UTC' , $act -> data [ 'updated' ]);
2022-04-29 21:32:41 +00:00
} elseif ( $act -> objprop ( 'updated' )) {
2023-08-05 07:56:01 +00:00
$item [ 'edited' ] = datetime_convert ( 'UTC' , 'UTC' , $act -> obj [ 'updated' ]);
2021-12-02 23:02:31 +00:00
}
2023-10-25 10:02:15 +00:00
2021-12-02 23:02:31 +00:00
if ( array_key_exists ( 'expires' , $act -> data ) && $act -> data [ 'expires' ]) {
2023-08-05 07:56:01 +00:00
$item [ 'expires' ] = datetime_convert ( 'UTC' , 'UTC' , $act -> data [ 'expires' ]);
2022-04-29 21:32:41 +00:00
} elseif ( $act -> objprop ( 'expires' )) {
2023-08-05 07:56:01 +00:00
$item [ 'expires' ] = datetime_convert ( 'UTC' , 'UTC' , $act -> obj [ 'expires' ]);
2021-12-02 23:02:31 +00:00
}
2023-09-09 23:04:58 +00:00
// pixelfed stories
if ( array_key_exists ( 'expiresAt' , $act -> data ) && $act -> data [ 'expiresAt' ]) {
$item [ 'expires' ] = datetime_convert ( 'UTC' , 'UTC' , $act -> data [ 'expiresAt' ]);
} elseif ( $act -> objprop ( 'expiresAt' )) {
$item [ 'expires' ] = datetime_convert ( 'UTC' , 'UTC' , $act -> obj [ 'expiresAt' ]);
}
2023-10-25 10:02:15 +00:00
// this will prevent peertube View activities which only exist for 2 minutes, but are like
// scrobblers and seem to only put noise into your stream and then vanish abruptly.
if ( $item [ 'expires' ] > NULL_DATE && $item [ 'expires' ] < datetime_convert ( datetime : 'now + 15 minutes' )) {
2023-08-02 21:40:41 +00:00
// We shouldn't even be seeing this activity.
return false ;
}
2021-12-02 23:02:31 +00:00
if ( isset ( $act -> replyto ) && ! empty ( $act -> replyto )) {
if ( is_array ( $act -> replyto ) && isset ( $act -> replyto [ 'id' ])) {
2023-08-05 07:56:01 +00:00
$item [ 'replyto' ] = $act -> replyto [ 'id' ];
2021-12-02 23:02:31 +00:00
} else {
2023-08-05 07:56:01 +00:00
$item [ 'replyto' ] = $act -> replyto ;
2021-12-02 23:02:31 +00:00
}
}
if ( ActivityStreams :: is_response_activity ( $act -> type )) {
$response_activity = true ;
2023-08-05 07:56:01 +00:00
$item [ 'mid' ] = $act -> id ;
2022-07-05 02:48:49 +00:00
2023-09-29 22:31:07 +00:00
$item [ 'mid' ] = reverse_activity_mid ( $item [ 'mid' ]);
2023-08-05 07:56:01 +00:00
$item [ 'parent_mid' ] = ( $act -> objprop ( 'id' )) ? $act -> objprop ( 'id' ) : $act -> obj ;
2021-12-02 23:02:31 +00:00
2022-07-05 02:48:49 +00:00
// Something went horribly wrong. The activity object isn't a string but doesn't have an id.
// Seen in the wild with a post from jasonrobinson.me being liked by a Friendica account.
2022-07-20 05:27:23 +00:00
2023-08-05 07:56:01 +00:00
if ( ! is_string ( $item [ 'parent_mid' ])) {
2022-07-05 02:48:49 +00:00
return false ;
}
2021-12-02 23:02:31 +00:00
2023-09-29 22:31:07 +00:00
$item [ 'parent_mid' ] = reverse_activity_mid ( $item [ 'parent_mid' ]);
2024-01-10 20:47:24 +00:00
if ( isset ( $item [ 'parent_mid' ]) && str_starts_with ( $item [ 'parent_mid' ], z_root () . '/event/' )) {
$item [ 'parent_mid' ] = str_replace ( '/event/' , '/item/' , $item [ 'parent_mid' ]);
}
2021-12-02 23:02:31 +00:00
// over-ride the object timestamp with the activity
if ( isset ( $act -> data [ 'published' ]) && $act -> data [ 'published' ]) {
2023-08-05 07:56:01 +00:00
$item [ 'created' ] = datetime_convert ( 'UTC' , 'UTC' , $act -> data [ 'published' ]);
2023-10-07 10:05:02 +00:00
unset ( $item [ 'edited' ]);
2021-12-02 23:02:31 +00:00
}
if ( isset ( $act -> data [ 'updated' ]) && $act -> data [ 'updated' ]) {
2023-08-05 07:56:01 +00:00
$item [ 'edited' ] = datetime_convert ( 'UTC' , 'UTC' , $act -> data [ 'updated' ]);
2021-12-02 23:02:31 +00:00
}
2022-04-29 21:32:41 +00:00
$obj_actor = ( $act -> objprop ( 'actor' )) ? $act -> obj [ 'actor' ] : $act -> get_actor ( 'attributedTo' , $act -> obj );
2022-07-20 05:27:23 +00:00
2021-12-02 23:02:31 +00:00
// Actor records themselves do not have an actor or attributedTo
2022-10-09 01:47:49 +00:00
if (( ! $obj_actor ) && $act -> objprop ( 'type' ) && ActivityStreams :: is_an_actor ( $act -> obj [ 'type' ])) {
2021-12-02 23:02:31 +00:00
$obj_actor = $act -> obj ;
}
2022-07-20 05:27:23 +00:00
// ensure that the object actor record has been fetched and is an array.
2022-07-14 00:32:34 +00:00
if ( is_string ( $obj_actor )) {
$obj_actor = Activity :: fetch ( $obj_actor );
}
if ( ! is_array ( $obj_actor )) {
return false ;
}
2022-07-20 05:27:23 +00:00
2021-12-02 23:02:31 +00:00
// We already check for admin blocks of third-party objects when fetching them explicitly.
// Repeat here just in case the entire object was supplied inline and did not require fetching
2023-08-05 07:56:01 +00:00
if ( isset ( $obj_actor [ 'id' ])) {
$parsed = parse_url ( $obj_actor [ 'id' ]);
if ( $parsed && $parsed [ 'scheme' ] && $parsed [ 'host' ]) {
if ( ! check_siteallowed ( $parsed [ 'scheme' ] . '://' . $parsed [ 'host' ])) {
2022-10-20 09:23:02 +00:00
return false ;
2021-12-02 23:02:31 +00:00
}
}
if ( ! check_channelallowed ( $obj_actor [ 'id' ])) {
2022-10-20 09:23:02 +00:00
return false ;
2021-12-02 23:02:31 +00:00
}
}
// if the object is an actor, it is not really a response activity, so reset it to a top level post
2022-04-29 21:32:41 +00:00
if ( $act -> objprop ( 'type' ) && ActivityStreams :: is_an_actor ( $act -> obj [ 'type' ])) {
2023-08-05 07:56:01 +00:00
$item [ 'parent_mid' ] = $item [ 'mid' ];
2021-12-02 23:02:31 +00:00
}
// ensure we store the original actor of the associated (parent) object
self :: actor_store ( $obj_actor [ 'id' ], $obj_actor );
$mention = self :: get_actor_bbmention ( $obj_actor [ 'id' ]);
$quoted_content = '[quote]' . $content [ 'content' ] . '[/quote]' ;
2022-04-29 21:32:41 +00:00
$object_type = $act -> objprop ( 'type' , t ( 'Activity' ));
if ( ActivityStreams :: is_an_actor ( $object_type )) {
$object_type = t ( 'Profile' );
}
2022-07-20 05:27:23 +00:00
2021-12-02 23:02:31 +00:00
if ( $act -> type === 'Like' ) {
2022-04-29 21:32:41 +00:00
$content [ 'content' ] = sprintf ( t ( 'Likes %1$s\'s %2$s' ), $mention , $object_type ) . EOL . EOL . $quoted_content ;
2021-12-02 23:02:31 +00:00
}
if ( $act -> type === 'Dislike' ) {
2022-04-29 21:32:41 +00:00
$content [ 'content' ] = sprintf ( t ( 'Doesn\'t like %1$s\'s %2$s' ), $mention , $object_type ) . EOL . EOL . $quoted_content ;
2021-12-02 23:02:31 +00:00
}
2023-09-20 08:29:17 +00:00
if ( $act -> type === 'Flag' ) {
$content [ 'content' ] = sprintf ( t ( 'Flagged %1$s\'s %2$s' ), $mention , $object_type ) . EOL . EOL . $quoted_content ;
}
if ( $act -> type === 'Block' ) {
$content [ 'content' ] = sprintf ( t ( 'Blocked %1$s\'s %2$s' ), $mention , $object_type ) . EOL . EOL . $quoted_content ;
}
2021-12-02 23:02:31 +00:00
// handle event RSVPs
2022-04-29 21:32:41 +00:00
if (( $object_type === 'Event' ) || ( $object_type === 'Invite' && array_path_exists ( 'object/type' , $act -> obj ) && $act -> obj [ 'object' ][ 'type' ] === 'Event' )) {
2021-12-02 23:02:31 +00:00
if ( $act -> type === 'Accept' ) {
$content [ 'content' ] = sprintf ( t ( 'Will attend %s\'s event' ), $mention ) . EOL . EOL . $quoted_content ;
}
if ( $act -> type === 'Reject' ) {
$content [ 'content' ] = sprintf ( t ( 'Will not attend %s\'s event' ), $mention ) . EOL . EOL . $quoted_content ;
}
if ( $act -> type === 'TentativeAccept' ) {
$content [ 'content' ] = sprintf ( t ( 'May attend %s\'s event' ), $mention ) . EOL . EOL . $quoted_content ;
}
if ( $act -> type === 'TentativeReject' ) {
$content [ 'content' ] = sprintf ( t ( 'May not attend %s\'s event' ), $mention ) . EOL . EOL . $quoted_content ;
}
}
if ( $act -> type === 'Announce' ) {
2023-10-14 21:51:22 +00:00
$content [ 'content' ] = sprintf ( t ( '📢 Repeated %1$s\'s %2$s' ), $mention , $object_type ) . EOL . EOL . $item [ 'parent_mid' ] . EOL ;
2021-12-02 23:02:31 +00:00
}
if ( $act -> type === 'emojiReaction' ) {
// Hubzilla reactions
$content [ 'content' ] = (( $act -> tgt && $act -> tgt [ 'type' ] === 'Image' ) ? '[img=32x32]' . $act -> tgt [ 'url' ] . '[/img]' : '&#x' . $act -> tgt [ 'name' ] . ';' );
}
if ( in_array ( $act -> type , [ 'EmojiReaction' , 'EmojiReact' ])) {
// Pleroma reactions
$t = trim ( self :: get_textfield ( $act -> data , 'content' ));
$e = Emoji\is_single_emoji ( $t ) || mb_strlen ( $t ) === 1 ;
if ( $e ) {
$content [ 'content' ] = $t ;
}
}
2022-02-01 08:41:59 +00:00
$a = self :: decode_taxonomy ( $act -> data );
if ( $a ) {
2023-08-05 07:56:01 +00:00
$item [ 'term' ] = $a ;
2022-02-01 08:41:59 +00:00
foreach ( $a as $b ) {
if ( $b [ 'ttype' ] === TERM_EMOJI ) {
2023-08-05 07:56:01 +00:00
$item [ 'summary' ] = str_replace ( $b [ 'term' ], '[img=16x16]' . $b [ 'url' ] . '[/img]' , $item [ 'summary' ]);
2022-02-01 08:41:59 +00:00
// @todo - @bug
// The emoji reference in the body might be inside a code block. In that case we shouldn't replace it.
// Currently we do.
2023-08-05 07:56:01 +00:00
$item [ 'body' ] = str_replace ( $b [ 'term' ], '[img=16x16]' . $b [ 'url' ] . '[/img]' , $item [ 'body' ]);
2022-02-01 08:41:59 +00:00
}
}
}
$a = self :: decode_attachment ( $act -> data );
if ( $a ) {
2023-08-05 07:56:01 +00:00
$item [ 'attach' ] = $a ;
2022-02-01 08:41:59 +00:00
}
$a = self :: decode_iconfig ( $act -> data );
if ( $a ) {
2023-08-05 07:56:01 +00:00
$item [ 'iconfig' ] = $a ;
2022-02-01 08:41:59 +00:00
}
2021-12-02 23:02:31 +00:00
}
2023-08-05 07:56:01 +00:00
$item [ 'comment_policy' ] = 'authenticated' ;
2021-12-02 23:02:31 +00:00
2023-08-05 07:56:01 +00:00
if ( $item [ 'mid' ] === $item [ 'parent_mid' ]) {
2021-12-02 23:02:31 +00:00
// it is a parent node - decode the comment policy info if present
2022-04-29 21:32:41 +00:00
if ( $act -> objprop ( 'commentPolicy' )) {
2021-12-02 23:02:31 +00:00
$until = strpos ( $act -> obj [ 'commentPolicy' ], 'until=' );
if ( $until !== false ) {
2023-08-05 07:56:01 +00:00
$item [ 'comments_closed' ] = datetime_convert ( 'UTC' , 'UTC' , substr ( $act -> obj [ 'commentPolicy' ], $until + 6 ));
if ( $item [ 'comments_closed' ] < datetime_convert ()) {
$item [ 'item_nocomment' ] = true ;
2021-12-02 23:02:31 +00:00
}
}
2022-10-20 09:23:02 +00:00
$remainder = substr ( $act -> obj [ 'commentPolicy' ], 0 , (( $until ) ? : strlen ( $act -> obj [ 'commentPolicy' ])));
if ( ! empty ( $remainder )) {
2023-08-05 07:56:01 +00:00
$item [ 'comment_policy' ] = $remainder ;
2021-12-02 23:02:31 +00:00
}
}
2023-08-05 07:56:01 +00:00
if ( ! $item [ 'comment_policy' ] && isset ( $act -> objprop [ 'canReply' ])) {
2022-11-28 20:53:03 +00:00
if ( empty ( $act -> objprop [ 'canReply' ])) {
2023-08-05 07:56:01 +00:00
$item [ 'item_nocomment' ] = true ;
2022-11-28 20:53:03 +00:00
}
elseif ( ! is_array ( $act -> objprop [ 'canReply' ])) {
$act -> objprop [ 'canReply' ] = [ $act -> objprop [ 'canReply' ]];
}
if ( is_array ( $act -> objprop [ 'canReply' ])) {
foreach ( $act -> objprop [ 'canReply' ] as $canReply ) {
if ( in_array ( $canReply , [ ACTIVITY_PUBLIC_INBOX , 'Public' , 'as:Public' ])) {
2023-08-05 07:56:01 +00:00
$item [ 'comment_policy' ] = 'authenticated' ;
2022-11-28 20:53:03 +00:00
break ;
}
}
2023-08-05 07:56:01 +00:00
if ( ! $item [ 'comment_policy' ]) {
2022-11-28 20:53:03 +00:00
foreach ( $act -> objprop [ 'canReply' ] as $canReply ) {
if ( strpos ( $canReply , 'follow' )) {
2023-08-05 07:56:01 +00:00
$item [ 'comment_policy' ] = 'contacts' ;
2022-11-28 20:53:03 +00:00
}
}
}
}
}
2021-12-02 23:02:31 +00:00
}
2022-12-11 19:16:17 +00:00
2023-08-05 07:56:01 +00:00
if ( ! ( array_key_exists ( 'created' , $item ) && $item [ 'created' ])) {
$item [ 'created' ] = datetime_convert ();
2021-12-02 23:02:31 +00:00
}
2023-08-05 07:56:01 +00:00
if ( ! ( array_key_exists ( 'edited' , $item ) && $item [ 'edited' ])) {
$item [ 'edited' ] = $item [ 'created' ];
2021-12-02 23:02:31 +00:00
}
2023-08-05 07:56:01 +00:00
$item [ 'title' ] = (( $response_activity ) ? EMPTY_STR : self :: bb_content ( $content , 'name' ));
$item [ 'summary' ] = self :: bb_content ( $content , 'summary' );
2021-12-02 23:02:31 +00:00
2023-08-04 22:08:33 +00:00
2023-08-05 07:56:01 +00:00
if ( array_key_exists ( 'mimetype' , $item ) && ( ! in_array ( $item [ 'mimetype' ], [ 'text/bbcode' , 'text/x-multicode' ]))) {
$item [ 'body' ] = $content [ 'content' ];
2021-12-02 23:02:31 +00:00
} else {
2023-08-05 07:56:01 +00:00
$item [ 'body' ] = (( self :: bb_content ( $content , 'bbcode' ) && ( ! $response_activity )) ? self :: bb_content ( $content , 'bbcode' ) : self :: bb_content ( $content , 'content' ));
2021-12-02 23:02:31 +00:00
}
2023-08-04 22:08:33 +00:00
$flohmarkt = $act -> objprop ( 'flohmarkt:data' );
if ( ! empty ( $flohmarkt )) {
if ( ! empty ( $flohmarkt [ 'price' ])) {
2023-08-05 07:56:01 +00:00
$item [ 'body' ] = $flohmarkt [ 'price' ] . " \n \n " . $item [ 'body' ];
2023-08-04 22:08:33 +00:00
}
if ( ! empty ( $flohmarkt [ 'coordinates' ][ 'lat' ]) && ! empty ( $flohmarkt [ 'coordinates' ][ 'lon' ])) {
2023-08-05 07:56:01 +00:00
$item [ 'lat' ] = floatval ( $flohmarkt [ 'coordinates' ][ 'lat' ]);
$item [ 'lon' ] = floatval ( $flohmarkt [ 'coordinates' ][ 'lon' ]);
2023-08-04 22:08:33 +00:00
}
}
2022-03-31 19:21:52 +00:00
// For the special snowflakes who can't figure out how to use attachments.
2023-05-11 20:38:33 +00:00
$misskeyquotefound = [];
2022-06-28 03:10:51 +00:00
foreach ( [ 'quoteUrl' , 'quoteUri' , '_misskey_quote' ] as $quote ) {
$quote_url = $act -> get_property_obj ( $quote );
if ( $quote_url ) {
2023-05-11 20:38:33 +00:00
if ( in_array ( $quote_url , $misskeyquotefound )) {
continue ;
}
2023-08-05 07:56:01 +00:00
$item = self :: get_quote ( $quote_url , $item );
2023-05-11 20:38:33 +00:00
$misskeyquotefound [] = $quote_url ;
2022-06-28 03:10:51 +00:00
}
elseif ( $act -> objprop ( $quote )) {
2023-05-11 20:38:33 +00:00
if ( in_array ( $act -> obj [ $quote ], $misskeyquotefound )) {
continue ;
}
2023-08-05 07:56:01 +00:00
$item = self :: get_quote ( $act -> obj [ $quote ], $item );
2023-05-11 20:38:33 +00:00
$misskeyquotefound [] = $act -> obj [ $quote ];
2022-06-28 03:10:51 +00:00
}
2022-03-31 19:21:52 +00:00
}
2021-12-02 23:02:31 +00:00
// handle some of the more widely used of the numerous and varied ways of deleting something
if ( in_array ( $act -> type , [ 'Delete' , 'Undo' , 'Tombstone' ])) {
2023-08-05 07:56:01 +00:00
$item [ 'item_deleted' ] = 1 ;
2021-12-02 23:02:31 +00:00
}
if ( $act -> type === 'Create' && $act -> obj [ 'type' ] === 'Tombstone' ) {
2023-08-05 07:56:01 +00:00
$item [ 'item_deleted' ] = 1 ;
2021-12-02 23:02:31 +00:00
}
2022-04-29 21:32:41 +00:00
if ( $act -> objprop ( 'sensitive' )) {
2023-08-05 07:56:01 +00:00
$item [ 'item_nsfw' ] = 1 ;
2021-12-02 23:02:31 +00:00
}
2023-08-05 07:56:01 +00:00
$item [ 'verb' ] = self :: activity_mapper ( $act -> type );
2021-12-02 23:02:31 +00:00
// Mastodon does not provide update timestamps when updating poll tallies which means race conditions may occur here.
2023-08-05 07:56:01 +00:00
if ( in_array ( $act -> type ,[ 'Create' , 'Update' ]) && $act -> objprop ( 'type' ) === 'Question' && $item [ 'edited' ] === $item [ 'created' ]) {
2022-07-20 05:27:23 +00:00
if ( intval ( $act -> objprop ( 'votersCount' ))) {
2023-08-05 07:56:01 +00:00
$item [ 'edited' ] = datetime_convert ();
2022-07-20 05:27:23 +00:00
}
2021-12-02 23:02:31 +00:00
}
2022-04-29 21:32:41 +00:00
if ( $act -> objprop ( 'type' )) {
2023-08-05 07:56:01 +00:00
$item [ 'obj_type' ] = self :: activity_obj_mapper ( $act -> obj [ 'type' ]);
2022-04-29 09:48:06 +00:00
}
2023-08-05 07:56:01 +00:00
$item [ 'obj' ] = $act -> obj ;
2022-07-20 05:27:23 +00:00
2023-08-05 07:56:01 +00:00
if ( array_path_exists ( 'actor/id' , $item [ 'obj' ])) {
$item [ 'obj' ][ 'actor' ] = $item [ 'obj' ][ 'actor' ][ 'id' ];
2021-12-02 23:02:31 +00:00
}
if ( is_array ( $act -> tgt ) && $act -> tgt ) {
if ( array_key_exists ( 'type' , $act -> tgt )) {
2023-08-05 07:56:01 +00:00
$item [ 'tgt_type' ] = self :: activity_obj_mapper ( $act -> tgt [ 'type' ]);
2021-12-02 23:02:31 +00:00
}
// We shouldn't need to store collection contents which could be large. We will often only require the meta-data
2023-08-05 07:56:01 +00:00
if ( isset ( $item [ 'tgt_type' ]) && str_contains ( $item [ 'tgt_type' ], 'Collection' )) {
2024-01-23 11:04:08 +00:00
$item [ 'target' ] = [ 'id' => $act -> tgt [ 'id' ], 'type' => $item [ 'tgt_type' ], 'attributedTo' => $act -> tgt [ 'attributedTo' ] ? ? $act -> tgt [ 'actor' ]];
2021-12-02 23:02:31 +00:00
}
}
$generator = $act -> get_property_obj ( 'generator' );
if (( ! $generator ) && ( ! $response_activity )) {
$generator = $act -> get_property_obj ( 'generator' , $act -> obj );
}
2021-12-03 03:01:39 +00:00
if (
$generator && array_key_exists ( 'type' , $generator )
2022-02-17 05:00:35 +00:00
&& in_array ( $generator [ 'type' ], [ 'Application' , 'Service' , 'Organization' ]) && array_key_exists ( 'name' , $generator )
2021-12-03 03:01:39 +00:00
) {
2023-08-05 07:56:01 +00:00
$item [ 'app' ] = escape_tags ( $generator [ 'name' ]);
2021-12-02 23:02:31 +00:00
}
2023-09-11 04:53:04 +00:00
if ( is_array ( $act -> tgt ) && $act -> tgt [ 'type' ] === 'Place' ) {
$location = new Place ( $act -> tgt );
}
2023-09-11 05:03:18 +00:00
elseif ( is_array ( $act -> obj )) {
if ( $act -> obj [ 'type' ] === 'Place' ) {
$location = new Place ( $act -> obj );
}
2023-09-25 06:25:48 +00:00
elseif ( is_array ( $act -> obj [ 'location' ]) && ! $response_activity ) {
2023-09-11 05:03:18 +00:00
$location = new Place ( $act -> obj [ 'location' ]);
}
2023-09-11 04:53:04 +00:00
}
else {
$location = new Place ( $act -> get_property_obj ( 'location' ));
}
2023-09-11 05:03:18 +00:00
if ( $location && $location -> getType () === 'Place' ) {
2023-08-05 07:56:01 +00:00
$item [ 'location' ] = $location -> getName () ? escape_tags ( $location -> getName ()) : '' ;
2023-08-03 10:59:25 +00:00
// Look for something resembling latitude/longitude coordinates in the place name and set the
// coordinates appropriately. This technically isn't supported but is provided as a convenience
// to reduce support requests.
2023-08-05 07:56:01 +00:00
if ( $item [ 'location' ]) {
2023-02-08 19:27:08 +00:00
$latlon = '/(?<!\d)([-+]?(?:[1-8]?\d(?:\.\d+)?|90(?:\.0+)?)),\s*([-+]?(?:180(?:\.0+)?|(?:(?:1[0-7]\d)|(?:[1-9]?\d))(?:\.\d+)?))(?!\d)/' ;
2023-08-05 07:56:01 +00:00
if ( preg_match ( $latlon , $item [ 'location' ], $matches )) {
$item [ 'lat' ] = floatval ( $matches [ 1 ]);
$item [ 'lon' ] = floatval ( $matches [ 2 ]);
2023-02-08 19:27:08 +00:00
}
2021-12-02 23:02:31 +00:00
}
2023-08-03 10:59:25 +00:00
if ( $location -> getContent ()) {
2023-08-05 07:56:01 +00:00
$item [ 'location' ] = html2plain ( purify_html ( $location -> getContent (), 256 ));
2021-12-02 23:02:31 +00:00
}
2023-08-03 10:59:25 +00:00
if ( $location -> getLatitude () && $location -> getLongitude ()) {
2023-08-05 07:56:01 +00:00
$item [ 'lat' ] = floatval ( $location -> getLatitude ());
$item [ 'lon' ] = floatval ( $location -> getLongitude ());
2021-12-02 23:02:31 +00:00
}
}
2022-04-29 21:32:41 +00:00
if ( is_array ( $act -> obj ) && ! $response_activity ) {
2021-12-02 23:02:31 +00:00
$a = self :: decode_taxonomy ( $act -> obj );
if ( $a ) {
2023-08-05 07:56:01 +00:00
$item [ 'term' ] = $a ;
2021-12-02 23:02:31 +00:00
foreach ( $a as $b ) {
if ( $b [ 'ttype' ] === TERM_EMOJI ) {
2023-08-05 07:56:01 +00:00
$item [ 'summary' ] = str_replace ( $b [ 'term' ], '[img=16x16]' . $b [ 'url' ] . '[/img]' , $item [ 'summary' ]);
2021-12-02 23:02:31 +00:00
// @todo - @bug
// The emoji reference in the body might be inside a code block. In that case we shouldn't replace it.
// Currently we do.
2023-08-05 07:56:01 +00:00
$item [ 'body' ] = str_replace ( $b [ 'term' ], '[img=16x16]' . $b [ 'url' ] . '[/img]' , $item [ 'body' ]);
2021-12-02 23:02:31 +00:00
}
}
}
$a = self :: decode_attachment ( $act -> obj );
if ( $a ) {
2023-08-05 07:56:01 +00:00
$item [ 'attach' ] = $a ;
2021-12-02 23:02:31 +00:00
}
$a = self :: decode_iconfig ( $act -> obj );
if ( $a ) {
2023-08-05 07:56:01 +00:00
$item [ 'iconfig' ] = $a ;
2021-12-02 23:02:31 +00:00
}
}
// Objects that might have media attachments which aren't already provided in the content element.
// We'll check specific media objects separately.
2023-10-16 18:44:08 +00:00
if (( ! $response_activity ) && in_array ( $act -> objprop ( 'type' , '' ), [ 'Article' , 'Document' , 'Event' , 'Note' , 'Story' , 'Page' , 'Place' , 'Question' ])
2023-08-05 07:56:01 +00:00
&& isset ( $item [ 'attach' ]) && $item [ 'attach' ]) {
$item = self :: bb_attach ( $item );
2021-12-02 23:02:31 +00:00
}
2022-06-28 01:48:48 +00:00
2022-04-29 21:32:41 +00:00
if ( $act -> objprop ( 'type' ) === 'Question' && in_array ( $act -> type , [ 'Create' , 'Update' ])) {
if ( $act -> objprop [ 'endTime' ]) {
2023-08-05 07:56:01 +00:00
$item [ 'comments_closed' ] = datetime_convert ( 'UTC' , 'UTC' , $act -> obj [ 'endTime' ]);
2021-12-02 23:02:31 +00:00
}
}
2022-04-29 21:32:41 +00:00
if ( $act -> objprop ( 'closed' )) {
2023-08-05 07:56:01 +00:00
$item [ 'comments_closed' ] = datetime_convert ( 'UTC' , 'UTC' , $act -> obj [ 'closed' ]);
2021-12-02 23:02:31 +00:00
}
// we will need a hook here to extract magnet links e.g. peertube
// right now just link to the largest mp4 we find that will fit in our
// standard content region
if ( ! $response_activity ) {
2022-04-29 21:32:41 +00:00
if ( $act -> objprop ( 'type' ) === 'Video' ) {
2021-12-02 23:02:31 +00:00
$vtypes = [
'video/mp4' ,
'video/ogg' ,
'video/webm'
];
$mps = [];
$poster = null ;
$ptr = null ;
// try to find a poster to display on the video element
2022-04-29 21:32:41 +00:00
if ( $act -> objprop ( 'icon' )) {
2021-12-02 23:02:31 +00:00
if ( is_array ( $act -> obj [ 'icon' ])) {
if ( array_key_exists ( 0 , $act -> obj [ 'icon' ])) {
$ptr = $act -> obj [ 'icon' ];
} else {
$ptr = [ $act -> obj [ 'icon' ]];
}
}
if ( $ptr ) {
foreach ( $ptr as $foo ) {
if ( is_array ( $foo ) && array_key_exists ( 'type' , $foo ) && $foo [ 'type' ] === 'Image' && is_string ( $foo [ 'url' ])) {
$poster = $foo [ 'url' ];
}
}
}
}
$tag = (( $poster ) ? '[video poster="' . $poster . '"]' : '[video]' );
$ptr = null ;
2022-04-29 21:32:41 +00:00
if ( $act -> objprop ( 'url' )) {
2021-12-02 23:02:31 +00:00
if ( is_array ( $act -> obj [ 'url' ])) {
if ( array_key_exists ( 0 , $act -> obj [ 'url' ])) {
$ptr = $act -> obj [ 'url' ];
} else {
$ptr = [ $act -> obj [ 'url' ]];
}
// handle peertube's weird url link tree if we find it here
// 0 => html link, 1 => application/x-mpegURL with 'tag' set to an array of actual media links
foreach ( $ptr as $idex ) {
if ( is_array ( $idex ) && array_key_exists ( 'mediaType' , $idex )) {
if ( $idex [ 'mediaType' ] === 'application/x-mpegURL' && isset ( $idex [ 'tag' ]) && is_array ( $idex [ 'tag' ])) {
$ptr = $idex [ 'tag' ];
break ;
}
}
}
foreach ( $ptr as $vurl ) {
if ( array_key_exists ( 'mediaType' , $vurl )) {
if ( in_array ( $vurl [ 'mediaType' ], $vtypes )) {
if ( ! array_key_exists ( 'width' , $vurl )) {
$vurl [ 'width' ] = 0 ;
}
$mps [] = $vurl ;
}
}
}
}
if ( $mps ) {
usort ( $mps , [ __CLASS__ , 'vid_sort' ]);
2023-08-05 07:56:01 +00:00
foreach ( $mps as $parsed ) {
if ( intval ( $parsed [ 'width' ]) < 500 && self :: media_not_in_body ( $parsed [ 'href' ], $item [ 'body' ])) {
$item [ 'body' ] .= " \n \n " . $tag . $parsed [ 'href' ] . '[/video]' ;
2021-12-02 23:02:31 +00:00
break ;
}
}
2023-08-05 07:56:01 +00:00
} elseif ( is_string ( $act -> obj [ 'url' ]) && self :: media_not_in_body ( $act -> obj [ 'url' ], $item [ 'body' ])) {
$item [ 'body' ] .= " \n \n " . $tag . $act -> obj [ 'url' ] . '[/video]' ;
2021-12-02 23:02:31 +00:00
}
}
}
2022-04-29 21:32:41 +00:00
if ( $act -> objprop ( 'type' ) === 'Audio' ) {
2021-12-02 23:02:31 +00:00
$atypes = [
'audio/mpeg' ,
'audio/ogg' ,
'audio/wav'
];
$ptr = null ;
if ( array_key_exists ( 'url' , $act -> obj )) {
if ( is_array ( $act -> obj [ 'url' ])) {
if ( array_key_exists ( 0 , $act -> obj [ 'url' ])) {
$ptr = $act -> obj [ 'url' ];
} else {
$ptr = [ $act -> obj [ 'url' ]];
}
foreach ( $ptr as $vurl ) {
2023-08-05 07:56:01 +00:00
if ( isset ( $vurl [ 'mediaType' ]) && in_array ( $vurl [ 'mediaType' ], $atypes ) && self :: media_not_in_body ( $vurl [ 'href' ], $item [ 'body' ])) {
$item [ 'body' ] .= " \n \n " . '[audio]' . $vurl [ 'href' ] . '[/audio]' ;
2021-12-02 23:02:31 +00:00
break ;
}
}
2023-08-05 07:56:01 +00:00
} elseif ( is_string ( $act -> obj [ 'url' ]) && self :: media_not_in_body ( $act -> obj [ 'url' ], $item [ 'body' ])) {
$item [ 'body' ] .= " \n \n " . '[audio]' . $act -> obj [ 'url' ] . '[/audio]' ;
2021-12-02 23:02:31 +00:00
}
} // Pleroma audio scrobbler
2023-08-05 07:56:01 +00:00
elseif ( $act -> type === 'Listen' && array_key_exists ( 'artist' , $act -> obj ) && array_key_exists ( 'title' , $act -> obj ) && $item [ 'body' ] === EMPTY_STR ) {
$item [ 'body' ] .= " \n \n " . sprintf ( 'Listening to \"%1$s\" by %2$s' , escape_tags ( $act -> obj [ 'title' ]), escape_tags ( $act -> obj [ 'artist' ]));
2021-12-02 23:02:31 +00:00
if ( isset ( $act -> obj [ 'album' ])) {
2023-08-05 07:56:01 +00:00
$item [ 'body' ] .= " \n " . sprintf ( '(%s)' , escape_tags ( $act -> obj [ 'album' ]));
2021-12-02 23:02:31 +00:00
}
}
}
2023-08-05 07:56:01 +00:00
if ( $act -> objprop ( 'type' ) === 'Image' && ! str_contains ( $item [ 'body' ], 'zrl=' )) {
2021-12-02 23:02:31 +00:00
$ptr = null ;
if ( array_key_exists ( 'url' , $act -> obj )) {
if ( is_array ( $act -> obj [ 'url' ])) {
if ( array_key_exists ( 0 , $act -> obj [ 'url' ])) {
$ptr = $act -> obj [ 'url' ];
} else {
$ptr = [ $act -> obj [ 'url' ]];
}
foreach ( $ptr as $vurl ) {
2023-08-05 07:56:01 +00:00
if ( is_array ( $vurl ) && isset ( $vurl [ 'href' ]) && ! str_contains ( $item [ 'body' ], $vurl [ 'href' ])) {
$item [ 'body' ] .= " \n \n " . '[zmg]' . $vurl [ 'href' ] . '[/zmg]' ;
2021-12-02 23:02:31 +00:00
break ;
}
}
} elseif ( is_string ( $act -> obj [ 'url' ])) {
2023-08-05 07:56:01 +00:00
if ( ! str_contains ( $item [ 'body' ], $act -> obj [ 'url' ])) {
$item [ 'body' ] .= " \n \n " . '[zmg]' . $act -> obj [ 'url' ] . '[/zmg]' ;
2021-12-02 23:02:31 +00:00
}
}
}
}
2023-08-05 07:56:01 +00:00
if ( $act -> objprop ( 'type' ) === 'Page' && ! $item [ 'body' ]) {
2021-12-02 23:02:31 +00:00
$ptr = null ;
2023-10-13 01:23:54 +00:00
$vurl = [];
2021-12-02 23:02:31 +00:00
$purl = EMPTY_STR ;
if ( array_key_exists ( 'url' , $act -> obj )) {
if ( is_array ( $act -> obj [ 'url' ])) {
if ( array_key_exists ( 0 , $act -> obj [ 'url' ])) {
$ptr = $act -> obj [ 'url' ];
} else {
$ptr = [ $act -> obj [ 'url' ]];
}
}
2023-11-06 20:41:22 +00:00
}
elseif ( array_key_exists ( 'attachment' , $act -> obj )) {
if ( is_array ( $act -> obj [ 'attachment' ])) {
if ( array_key_exists ( 0 , $act -> obj [ 'attachment' ])) {
2023-11-06 21:50:16 +00:00
$ptr = $act -> obj [ 'attachment' ];
2021-12-02 23:02:31 +00:00
} else {
2023-11-06 21:50:16 +00:00
$ptr = [ $act -> obj [ 'attachment' ]];
2021-12-02 23:02:31 +00:00
}
}
}
2023-11-06 20:41:22 +00:00
if ( $ptr ) {
foreach ( $ptr as $vurl ) {
if ( ! is_array ( $vurl )) {
continue ;
}
if ( array_key_exists ( 'mediaType' , $vurl ) && $vurl [ 'mediaType' ] === 'text/html' ) {
$purl = $vurl [ 'href' ];
break ;
} elseif ( array_key_exists ( 'mimeType' , $vurl ) && $vurl [ 'mimeType' ] === 'text/html' ) {
$purl = $vurl [ 'href' ];
break ;
} elseif ( $item [ 'mimetype' ] === 'text/html' ) {
// lemmy makes everything difficult to parse; this time by putting the mediaType on the object but not the link
// we can target this specifically because there's a mediaType set on the object but no content could be found.
$purl = $vurl [ 'href' ];
break ;
}
}
} elseif ( is_string ( $act -> obj [ 'url' ])) {
$purl = $act -> obj [ 'url' ];
} elseif ( is_string ( $act -> obj [ 'attachment' ])) {
$purl = $act -> obj [ 'attachment' ];
}
if ( $purl ) {
$li = Url :: get ( z_root () . '/linkinfo?binurl=' . bin2hex ( $purl ));
if ( $li [ 'success' ] && $li [ 'body' ]) {
$item [ 'body' ] .= " \n " . $li [ 'body' ];
} else {
$item [ 'body' ] .= " \n \n " . $purl ;
}
}
2021-12-02 23:02:31 +00:00
}
}
2023-09-09 23:04:58 +00:00
if ( in_array ( $act -> objprop ( 'type' ), [ 'Note' , 'Story' , 'Article' , 'Page' ])) {
2021-12-02 23:02:31 +00:00
$ptr = null ;
if ( array_key_exists ( 'url' , $act -> obj )) {
if ( is_array ( $act -> obj [ 'url' ])) {
if ( array_key_exists ( 0 , $act -> obj [ 'url' ])) {
$ptr = $act -> obj [ 'url' ];
} else {
$ptr = [ $act -> obj [ 'url' ]];
}
foreach ( $ptr as $vurl ) {
if ( is_array ( $vurl ) && array_key_exists ( 'mediaType' , $vurl ) && $vurl [ 'mediaType' ] === 'text/html' ) {
2023-08-05 07:56:01 +00:00
$item [ 'plink' ] = $vurl [ 'href' ];
2021-12-02 23:02:31 +00:00
break ;
}
}
} elseif ( is_string ( $act -> obj [ 'url' ])) {
2023-08-05 07:56:01 +00:00
$item [ 'plink' ] = $act -> obj [ 'url' ];
2021-12-02 23:02:31 +00:00
}
}
}
2023-08-05 07:56:01 +00:00
if ( ! ( isset ( $item [ 'plink' ]) && $item [ 'plink' ])) {
$item [ 'plink' ] = $item [ 'mid' ];
2021-12-02 23:02:31 +00:00
}
// assume this is private unless specifically told otherwise.
2023-08-05 07:56:01 +00:00
$item [ 'item_private' ] = 1 ;
2021-12-02 23:02:31 +00:00
if ( $act -> recips && ( in_array ( ACTIVITY_PUBLIC_INBOX , $act -> recips ) || in_array ( 'Public' , $act -> recips ) || in_array ( 'as:Public' , $act -> recips ))) {
2023-08-05 07:56:01 +00:00
$item [ 'item_private' ] = 0 ;
2021-12-02 23:02:31 +00:00
}
2022-04-29 21:32:41 +00:00
if ( $act -> objprop ( 'directMessage' )) {
2023-08-05 07:56:01 +00:00
$item [ 'item_private' ] = 2 ;
2021-12-02 23:02:31 +00:00
}
2023-08-05 07:56:01 +00:00
set_iconfig ( $item , 'activitypub' , 'recips' , $act -> raw_recips );
2021-12-02 23:02:31 +00:00
if ( array_key_exists ( 'directMessage' , $act -> data ) && intval ( $act -> data [ 'directMessage' ])) {
2023-08-05 07:56:01 +00:00
$item [ 'item_private' ] = 2 ;
2021-12-02 23:02:31 +00:00
}
2023-08-05 07:56:01 +00:00
if ( in_array ( $item [ 'verb' ], [ 'Arrive' , 'Leave' ])) {
if ( $item [ 'lat' ] || $item [ 'lon' ]) {
if ( ! str_contains ( $item [ 'body' ], '[map=' )) {
$item [ 'body' ] .= " \n \n " . '[map=' . $item [ 'lat' ] . ',' . $item [ 'lon' ] . ']' . " \n " ;
2022-11-22 00:10:04 +00:00
}
2022-11-10 08:56:42 +00:00
}
2023-08-05 07:56:01 +00:00
elseif ( $item [ 'location' ]) {
if ( ! str_contains ( $item [ 'body' ], '[map]' )) {
$item [ 'body' ] .= " \n \n " . '[map]' . $item [ 'location' ] . '[/map]' . " \n " ;
2022-11-22 00:10:04 +00:00
}
2022-11-10 08:56:42 +00:00
}
}
2022-11-11 21:25:58 +00:00
2021-12-02 23:02:31 +00:00
// Restrict html caching to ActivityPub senders.
// Zot has dynamic content and this library is used by both.
if ( $cacheable ) {
2023-08-05 07:56:01 +00:00
if (( ! array_key_exists ( 'mimetype' , $item )) || ( in_array ( $item [ 'mimetype' ], [ 'text/bbcode' , 'text/x-multicode' ]))) {
2021-12-02 23:02:31 +00:00
// preserve the original purified HTML content *unless* we've modified $s['body']
// within this function (to add attachments or reaction descriptions or mention rewrites).
// This avoids/bypasses some markdown rendering issues which can occur when
// converting to our markdown-enhanced bbcode and then back to HTML again.
2022-10-20 09:23:02 +00:00
// Also, if we do need bbcode, use the 'bbonly' flag to ignore Markdown and only
2021-12-02 23:02:31 +00:00
// interpret bbcode; which is much less susceptible to false positives in the
// conversion regexes.
2023-08-05 07:56:01 +00:00
if ( $item [ 'body' ] === self :: bb_content ( $content , 'content' )) {
$item [ 'html' ] = $content [ 'content' ];
2021-12-02 23:02:31 +00:00
} else {
2023-08-05 07:56:01 +00:00
$item [ 'html' ] = bbcode ( $item [ 'body' ], [ 'bbonly' => true ]);
2021-12-02 23:02:31 +00:00
}
}
}
2023-08-05 07:56:01 +00:00
if ( $item [ 'term' ]) {
foreach ( $item [ 'term' ] as $t ) {
if ( $t [ 'ttype' ] === TERM_QUOTED && self :: share_not_in_body ( $item [ 'body' ])) {
$item = self :: get_quote ( $t [ 'url' ], $item );
2022-06-28 03:10:51 +00:00
}
}
}
2021-12-02 23:02:31 +00:00
$hookinfo = [
'act' => $act ,
2023-08-05 07:56:01 +00:00
's' => $item
2021-12-02 23:02:31 +00:00
];
2022-02-12 08:50:48 +00:00
Hook :: call ( 'decode_note' , $hookinfo );
2023-07-09 08:28:02 +00:00
logger ( 'decode_note: ' . print_r ( $hookinfo [ 's' ], true ), LOGGER_DATA );
2022-10-20 09:23:02 +00:00
return $hookinfo [ 's' ];
2021-12-02 23:02:31 +00:00
}
public static function rewrite_mentions_sub ( & $s , $pref , & $obj = null )
{
if ( isset ( $s [ 'term' ]) && is_array ( $s [ 'term' ])) {
foreach ( $s [ 'term' ] as $tag ) {
$txt = EMPTY_STR ;
if ( intval ( $tag [ 'ttype' ]) === TERM_MENTION ) {
// some platforms put the identity url into href rather than the profile url. Accept either form.
2021-12-03 03:01:39 +00:00
$x = q (
" select * from xchan where xchan_url = '%s' or xchan_hash = '%s' limit 1 " ,
2021-12-02 23:02:31 +00:00
dbesc ( $tag [ 'url' ]),
dbesc ( $tag [ 'url' ])
);
2022-06-10 09:18:37 +00:00
if ( ! $x ) {
// This tagged identity has never before been seen on this site. Perform discovery and retry.
2022-10-20 09:23:02 +00:00
/** @noinspection PhpUnusedLocalVariableInspection */
2022-09-03 21:30:13 +00:00
$hash = discover_resource ( $tag [ 'url' ]);
2022-06-10 09:18:37 +00:00
$x = q (
" select * from xchan where xchan_url = '%s' or xchan_hash = '%s' limit 1 " ,
dbesc ( $tag [ 'url' ]),
dbesc ( $tag [ 'url' ])
2022-07-20 05:27:23 +00:00
);
2022-06-10 09:18:37 +00:00
}
2021-12-02 23:02:31 +00:00
if ( $x ) {
switch ( $pref ) {
case 0 :
$txt = $x [ 0 ][ 'xchan_name' ];
break ;
case 1 :
2022-10-20 09:23:02 +00:00
$txt = (( $x [ 0 ][ 'xchan_addr' ]) ? : $x [ 0 ][ 'xchan_name' ]);
2021-12-02 23:02:31 +00:00
break ;
case 2 :
default ;
if ( $x [ 0 ][ 'xchan_addr' ]) {
$txt = sprintf ( t ( '%1$s (%2$s)' ), $x [ 0 ][ 'xchan_name' ], $x [ 0 ][ 'xchan_addr' ]);
} else {
$txt = $x [ 0 ][ 'xchan_name' ];
}
break ;
}
}
}
if ( $txt ) {
2022-10-20 09:23:02 +00:00
// the Markdown filter will get tripped up and think this is a Markdown link
// if $txt begins with parens, so put it behind a zero-width space
2022-10-09 01:47:49 +00:00
if ( str_starts_with ( $txt , '(' )) {
2021-12-02 23:02:31 +00:00
$txt = htmlspecialchars_decode ( '​' , ENT_QUOTES ) . $txt ;
}
2021-12-03 03:01:39 +00:00
$s [ 'body' ] = preg_replace (
2022-10-09 01:47:49 +00:00
'/@\[zrl=' . preg_quote ( $x [ 0 ][ 'xchan_url' ], '/' ) . '](.*?)\[\/zrl]/ism' ,
2021-12-03 03:01:39 +00:00
'@[zrl=' . $x [ 0 ][ 'xchan_url' ] . ']' . $txt . '[/zrl]' ,
$s [ 'body' ]
);
$s [ 'body' ] = preg_replace (
2022-10-09 01:47:49 +00:00
'/@\[url=' . preg_quote ( $x [ 0 ][ 'xchan_url' ], '/' ) . '](.*?)\[\/url]/ism' ,
2021-12-03 03:01:39 +00:00
'@[url=' . $x [ 0 ][ 'xchan_url' ] . ']' . $txt . '[/url]' ,
$s [ 'body' ]
);
$s [ 'body' ] = preg_replace (
2022-10-09 01:47:49 +00:00
'/\[zrl=' . preg_quote ( $x [ 0 ][ 'xchan_url' ], '/' ) . ']@(.*?)\[\/zrl]/ism' ,
2021-12-03 03:01:39 +00:00
'@[zrl=' . $x [ 0 ][ 'xchan_url' ] . ']' . $txt . '[/zrl]' ,
$s [ 'body' ]
);
$s [ 'body' ] = preg_replace (
2022-10-09 01:47:49 +00:00
'/\[url=' . preg_quote ( $x [ 0 ][ 'xchan_url' ], '/' ) . ']@(.*?)\[\/url]/ism' ,
2021-12-03 03:01:39 +00:00
'@[url=' . $x [ 0 ][ 'xchan_url' ] . ']' . $txt . '[/url]' ,
$s [ 'body' ]
);
2021-12-02 23:02:31 +00:00
// replace these just in case the sender (in this case Friendica) got it wrong
2021-12-03 03:01:39 +00:00
$s [ 'body' ] = preg_replace (
2022-10-09 01:47:49 +00:00
'/@\[zrl=' . preg_quote ( $x [ 0 ][ 'xchan_hash' ], '/' ) . '](.*?)\[\/zrl]/ism' ,
2021-12-03 03:01:39 +00:00
'@[zrl=' . $x [ 0 ][ 'xchan_url' ] . ']' . $txt . '[/zrl]' ,
$s [ 'body' ]
);
$s [ 'body' ] = preg_replace (
2022-10-09 01:47:49 +00:00
'/@\[url=' . preg_quote ( $x [ 0 ][ 'xchan_hash' ], '/' ) . '](.*?)\[\/url]/ism' ,
2021-12-03 03:01:39 +00:00
'@[url=' . $x [ 0 ][ 'xchan_url' ] . ']' . $txt . '[/url]' ,
$s [ 'body' ]
);
$s [ 'body' ] = preg_replace (
2022-10-09 01:47:49 +00:00
'/\[zrl=' . preg_quote ( $x [ 0 ][ 'xchan_hash' ], '/' ) . ']@(.*?)\[\/zrl]/ism' ,
2021-12-03 03:01:39 +00:00
'@[zrl=' . $x [ 0 ][ 'xchan_url' ] . ']' . $txt . '[/zrl]' ,
$s [ 'body' ]
);
$s [ 'body' ] = preg_replace (
2022-10-09 01:47:49 +00:00
'/\[url=' . preg_quote ( $x [ 0 ][ 'xchan_hash' ], '/' ) . ']@(.*?)\[\/url]/ism' ,
2021-12-03 03:01:39 +00:00
'@[url=' . $x [ 0 ][ 'xchan_url' ] . ']' . $txt . '[/url]' ,
$s [ 'body' ]
);
2021-12-02 23:02:31 +00:00
if ( $obj && $txt ) {
if ( ! is_array ( $obj )) {
$obj = json_decode ( $obj , true );
}
if ( array_path_exists ( 'source/content' , $obj )) {
2021-12-03 03:01:39 +00:00
$obj [ 'source' ][ 'content' ] = preg_replace (
2022-10-09 01:47:49 +00:00
'/@\[zrl=' . preg_quote ( $x [ 0 ][ 'xchan_url' ], '/' ) . '](.*?)\[\/zrl]/ism' ,
2021-12-03 03:01:39 +00:00
'@[zrl=' . $x [ 0 ][ 'xchan_url' ] . ']' . $txt . '[/zrl]' ,
$obj [ 'source' ][ 'content' ]
);
$obj [ 'source' ][ 'content' ] = preg_replace (
2022-10-09 01:47:49 +00:00
'/@\[url=' . preg_quote ( $x [ 0 ][ 'xchan_url' ], '/' ) . '](.*?)\[\/url]/ism' ,
2021-12-03 03:01:39 +00:00
'@[url=' . $x [ 0 ][ 'xchan_url' ] . ']' . $txt . '[/url]' ,
$obj [ 'source' ][ 'content' ]
);
2021-12-02 23:02:31 +00:00
}
2022-10-09 01:47:49 +00:00
/** @noinspection HtmlUnknownAttribute */
$obj [ 'content' ] = preg_replace ( '/@(.*?)<a (.*?)href=\"' . preg_quote ( $x [ 0 ][ 'xchan_url' ], '/' ) . '\"(.*?)>(.*?)<\/a>/ism' ,
2021-12-03 03:01:39 +00:00
'@$1<a $2 href="' . $x [ 0 ][ 'xchan_url' ] . '"$3>' . $txt . '</a>' ,
$obj [ 'content' ]
);
2021-12-02 23:02:31 +00:00
}
}
}
}
// $s['html'] will be populated if caching was enabled.
// This is usually the case for ActivityPub sourced content, while Zot6 content is not cached.
if ( isset ( $s [ 'html' ]) && $s [ 'html' ]) {
$s [ 'html' ] = bbcode ( $s [ 'body' ], [ 'bbonly' => true ]);
}
}
public static function rewrite_mentions ( & $s )
{
// rewrite incoming mentions in accordance with system.tag_username setting
// 0 - displayname
// 1 - username
// 2 - displayname (username)
// 127 - default
2022-10-20 09:23:02 +00:00
$pref = intval ( PConfig :: Get ( $s [ 'uid' ], 'system' , 'tag_username' , Config :: Get ( 'system' , 'tag_username' )));
2021-12-02 23:02:31 +00:00
if ( $pref === 127 ) {
return ;
}
self :: rewrite_mentions_sub ( $s , $pref );
}
// $force is used when manually fetching a remote item - it assumes you are granting one-time
// permission for the selected item/conversation regardless of your relationship with the author and
// assumes that you are in fact the sender. Please do not use it for anything else. The only permission
// checking that is performed is that the author isn't blocked by the site admin.
2024-01-22 09:43:22 +00:00
public static function store ( $channel , $observer_hash , $act , $item , $fetch_parents = true , $force = false , $isCollectionOperation = false )
2021-12-02 23:02:31 +00:00
{
if ( $act && $act -> implied_create && ! $force ) {
// This is originally a S2S object with no associated activity
logger ( 'Not storing implied create activity!' );
return ;
}
2024-01-24 20:01:42 +00:00
2024-01-22 09:43:22 +00:00
2022-01-25 01:26:12 +00:00
$is_system = Channel :: is_system ( $channel [ 'channel_id' ]);
2021-12-02 23:02:31 +00:00
$is_child_node = false ;
2022-12-11 19:16:17 +00:00
$commentApproval = null ;
2021-12-02 23:02:31 +00:00
// Pleroma scrobbles can be really noisy and contain lots of duplicate activities. Disable them by default.
2022-10-20 09:23:02 +00:00
if (( $act -> type === 'Listen' ) && ( $is_system || get_pconfig ( $channel [ 'channel_id' ], 'system' , 'allow_scrobbles' ))) {
2021-12-02 23:02:31 +00:00
return ;
}
// Mastodon only allows visibility in public timelines if the public inbox is listed in the 'to' field.
// They are hidden in the public timeline if the public inbox is listed in the 'cc' field.
// This is not part of the activitypub protocol - we might change this to show all public posts in pubstream at some point.
2022-10-20 09:23:02 +00:00
$pubstream = is_array ( $act -> obj )
&& array_key_exists ( 'to' , $act -> obj )
&& is_array ( $act -> obj [ 'to' ])
&& (
in_array ( ACTIVITY_PUBLIC_INBOX , $act -> obj [ 'to' ])
|| in_array ( 'Public' , $act -> obj [ 'to' ])
|| in_array ( 'as:Public' , $act -> obj [ 'to' ])
);
2021-12-02 23:02:31 +00:00
// very unpleasant and imperfect way of determining a Mastodon DM
2022-10-20 09:23:02 +00:00
if ( $act -> raw_recips
&& array_key_exists ( 'to' , $act -> raw_recips )
&& is_array ( $act -> raw_recips [ 'to' ])
&& count ( $act -> raw_recips [ 'to' ]) === 1
&& $act -> raw_recips [ 'to' ][ 0 ] === Channel :: url ( $channel )
&& ! $act -> raw_recips [ 'cc' ]) {
2021-12-02 23:02:31 +00:00
$item [ 'item_private' ] = 2 ;
}
2023-04-30 19:48:44 +00:00
if ( Tombstone :: check ( $item [ 'mid' ], $channel [ 'channel_id' ])
|| Tombstone :: check ( $item [ 'parent_mid' ], $channel [ 'channel_id' ])) {
2023-04-28 05:35:28 +00:00
logger ( 'tombstone: post was deleted. Ignoring update.' );
return ;
}
2021-12-02 23:02:31 +00:00
if ( $item [ 'parent_mid' ] && $item [ 'parent_mid' ] !== $item [ 'mid' ]) {
$is_child_node = true ;
}
$allowed = false ;
$reason = [ 'init' ];
2023-04-15 06:54:15 +00:00
$isMail = ( bool ) ( intval ( $item [ 'item_private' ]) === 2 );
2021-12-02 23:02:31 +00:00
if ( $is_child_node ) {
2022-10-09 01:47:49 +00:00
$parent_item = q (
2022-10-20 00:00:48 +00:00
" select * from item where mid = '%s' and uid = %d " ,
2021-12-02 23:02:31 +00:00
dbesc ( $item [ 'parent_mid' ]),
intval ( $channel [ 'channel_id' ])
);
2022-10-09 01:47:49 +00:00
if ( $parent_item ) {
$parent_item = array_shift ( $parent_item );
2023-06-29 20:21:20 +00:00
// We've found the inReplyTo. However, if this is not
// the top of the conversation, look again.
if ( $parent_item [ 'parent_mid' ] !== $item [ 'parent_mid' ]) {
$parent_top_item = q (
" select * from item where mid = '%s' and uid = %d " ,
dbesc ( $parent_item [ 'parent_mid' ]),
intval ( $channel [ 'channel_id' ])
);
if ( $parent_top_item ) {
$parent_item = array_shift ( $parent_top_item );
}
}
2022-10-20 00:00:48 +00:00
}
2023-06-29 20:21:20 +00:00
if ( $parent_item && $parent_item [ 'item_wall' ]) {
2021-12-02 23:02:31 +00:00
// set the owner to the owner of the parent
2022-10-09 01:47:49 +00:00
$item [ 'owner_xchan' ] = $parent_item [ 'owner_xchan' ];
if ( $parent_item [ 'obj_type' ] === 'Question' ) {
if ( $item [ 'obj_type' ] === 'Note' && $item [ 'title' ] && ( ! $item [ 'content' ])) {
$item [ 'obj_type' ] = 'Answer' ;
2023-08-11 20:02:14 +00:00
// if poll responses are sent as DMs, reset them to the conversation default
if (( int ) $item [ 'item_private' ] === 2 && ( int ) $parent_item [ 'item_private' ] !== 2 ) {
$item [ 'item_private' ] = ( int ) $parent_item [ 'item_private' ];
}
2022-10-09 01:47:49 +00:00
}
}
2022-12-11 19:16:17 +00:00
if ( $item [ 'approved' ]) {
$valid = CommentApproval :: verify ( $item , $channel );
if ( ! $valid ) {
logger ( 'commentApproval failed' );
return ;
}
}
2023-09-29 22:31:07 +00:00
$objtype = $act -> objprop ( 'type' , '' );
if ( in_array ( $item [ 'verb' ], [ 'Accept' , 'Reject' ]) && ! in_array ( $objtype , [ 'Invite' , 'Event' ])) {
2022-12-16 22:32:59 +00:00
if ( CommentApproval :: doVerify ( $item , $channel , $act )) {
return ;
2022-12-11 19:16:17 +00:00
}
}
2021-12-02 23:02:31 +00:00
2022-12-11 19:16:17 +00:00
if ( ! $item [ 'approved' ] && $parent_item [ 'owner_xchan' ] === $channel [ 'channel_hash' ] && $item [ 'author_xchan' ] !== $channel [ 'channel_hash' ]) {
$commentApproval = new CommentApproval ( $channel , $item );
}
2021-12-02 23:02:31 +00:00
// quietly reject group comment boosts by group owner
// (usually only sent via ActivityPub so groups will work on microblog platforms)
// This catches those activities if they slipped in via a conversation fetch
2022-10-09 01:47:49 +00:00
if ( $parent_item [ 'parent_mid' ] !== $item [ 'parent_mid' ]) {
2021-12-02 23:02:31 +00:00
if ( $item [ 'verb' ] === 'Announce' && $item [ 'author_xchan' ] === $item [ 'owner_xchan' ]) {
2022-10-09 01:47:49 +00:00
logger ( 'group boost activity by group owner suppressed' );
2021-12-02 23:02:31 +00:00
return ;
}
}
2022-10-09 01:47:49 +00:00
$allowed = self :: comment_allowed ( $channel , $item , $parent_item );
2021-12-02 23:02:31 +00:00
2022-11-30 06:23:24 +00:00
if ( $allowed ) {
// At this point we know it is allowed, but check if it requires moderation.
if ( perm_is_allowed ( $channel [ 'channel_id' ], $item [ 'author_xchan' ], 'moderated' )
|| $allowed === 'moderated' ) {
$item [ 'item_blocked' ] = ITEM_MODERATED ;
}
2022-12-11 19:16:17 +00:00
if ( $item [ 'item_blocked' ] !== ITEM_MODERATED && $commentApproval ) {
$commentApproval -> Accept ();
2022-11-30 06:23:24 +00:00
}
}
else {
2023-06-23 21:39:05 +00:00
logger ( 'rejected comment from ' . $item [ 'author_xchan' ] . ' for ' . $channel [ 'channel_address' ]);
logger ( 'rejected: ' . print_r ( $item , true ), LOGGER_DATA );
// let the sender know we received their comment, but we don't permit spam here.
$commentApproval ? -> Reject ();
return ;
2021-12-02 23:02:31 +00:00
}
2023-05-06 05:55:09 +00:00
}
else {
2022-10-09 01:47:49 +00:00
// By default, if we allow you to send_stream and comments and this is a comment, it is allowed.
2021-12-02 23:02:31 +00:00
// A side effect of this action is that if you take away send_stream permission, comments to those
// posts you previously allowed will still be accepted. It is possible but might be difficult to fix this.
2022-12-17 21:43:34 +00:00
if ( $item [ 'approved' ]) {
$allowed = CommentApproval :: verify ( $item , $channel );
if ( ! $allowed ) {
logger ( 'commentApproval failed' );
return ;
}
}
else {
2023-05-03 12:02:51 +00:00
$allowed = ! ( bool ) Config :: Get ( 'system' , 'use_fep5624' );
2022-12-17 21:43:34 +00:00
}
2021-12-02 23:02:31 +00:00
// reject public stream comments that weren't sent by the conversation owner
// but only on remote message deliveries to our site ($fetch_parents === true)
2022-01-25 01:26:12 +00:00
if ( $is_system && $pubstream && $item [ 'owner_xchan' ] !== $observer_hash && ! $fetch_parents ) {
2021-12-02 23:02:31 +00:00
$allowed = false ;
$reason [] = 'sender ' . $observer_hash . ' not owner ' . $item [ 'owner_xchan' ];
}
}
2022-02-27 20:15:20 +00:00
}
else {
2023-04-15 06:54:15 +00:00
if (( ! $isMail ) && ( perm_is_allowed ( $channel [ 'channel_id' ], $observer_hash , 'send_stream' ) || ( $is_system && $pubstream ))) {
2021-12-02 23:02:31 +00:00
logger ( 'allowed: permission allowed' , LOGGER_DATA );
$allowed = true ;
}
2022-10-09 04:39:03 +00:00
if ( intval ( PConfig :: Get ( $channel [ 'channel_id' ], 'system' , 'permit_all_mentions' )
&& i_am_mentioned ( $channel , $item ))) {
2021-12-02 23:02:31 +00:00
logger ( 'allowed: permitted mention' , LOGGER_DATA );
$allowed = true ;
}
2022-10-09 04:39:03 +00:00
if ( tgroup_check ( $channel [ 'channel_id' ], $item )) {
logger ( 'allowed: tgroup' );
$allowed = true ;
}
2021-12-02 23:02:31 +00:00
}
2024-01-24 20:01:42 +00:00
$relay = $channel [ 'channel_hash' ] === $item [ 'owner_xchan' ];
if ( str_contains ( $item [ 'tgt_type' ], 'Collection' ) && ! $relay && ! $isCollectionOperation ) {
logger ( 'not a collection activity' );
return ;
}
2022-10-20 09:23:02 +00:00
if ( get_abconfig ( $channel [ 'channel_id' ], $observer_hash , 'system' , 'block_announce' )) {
2021-12-02 23:02:31 +00:00
if ( $item [ 'verb' ] === 'Announce' || strpos ( $item [ 'body' ], '[/share]' )) {
$allowed = false ;
}
}
2023-04-15 06:54:15 +00:00
if ( $isMail ) {
2023-05-03 12:02:51 +00:00
$allowed = perm_is_allowed ( $channel [ 'channel_id' ], $observer_hash , 'post_mail' );
if ( ! $allowed ) {
logger ( 'mail permission denied for "' . $observer_hash . '" ' );
$reason [] = 'mail permission' ;
2021-12-02 23:02:31 +00:00
}
}
2022-01-25 01:26:12 +00:00
if ( $is_system ) {
2023-10-07 06:33:45 +00:00
$public_stream_mode = ( int ) Config :: Get ( 'system' , 'public_stream_mode' , PUBLIC_STREAM_NONE );
if ( $public_stream_mode === PUBLIC_STREAM_NONE ) {
$allowed = false ;
$reason [] = 'public stream disabled' ;
}
2023-10-07 10:05:02 +00:00
2021-12-02 23:02:31 +00:00
if ( ! check_pubstream_channelallowed ( $observer_hash )) {
$allowed = false ;
$reason [] = 'pubstream channel blocked' ;
}
// don't allow pubstream posts if the sender even has a clone on a pubstream denied site
2021-12-03 03:01:39 +00:00
$h = q (
2022-06-17 02:46:54 +00:00
" select hubloc_url from hubloc where hubloc_hash = '%s' and hubloc_deleted = 0 " ,
2021-12-02 23:02:31 +00:00
dbesc ( $observer_hash )
);
if ( $h ) {
foreach ( $h as $hub ) {
if ( ! check_pubstream_siteallowed ( $hub [ 'hubloc_url' ])) {
$allowed = false ;
$reason = 'pubstream site blocked' ;
break ;
}
}
}
if ( intval ( $item [ 'item_private' ])) {
$allowed = false ;
$reason [] = 'private item' ;
}
}
$blocked = LibBlock :: fetch ( $channel [ 'channel_id' ], BLOCKTYPE_SERVER );
if ( $blocked ) {
foreach ( $blocked as $b ) {
2022-10-09 01:47:49 +00:00
if ( str_contains ( $observer_hash , $b [ 'block_entity' ])) {
2021-12-02 23:02:31 +00:00
$allowed = false ;
$reason [] = 'blocked' ;
}
}
}
if ( ! $allowed && ! $force ) {
2023-06-02 22:40:38 +00:00
logger ( 'no permission: channel ' . $channel [ 'channel_address' ] . ', id = ' . $item [ 'mid' ]);
logger ( 'no permission: reason ' . print_r ( $reason , true ));
return ;
2021-12-02 23:02:31 +00:00
}
$item [ 'aid' ] = $channel [ 'channel_account_id' ];
$item [ 'uid' ] = $channel [ 'channel_id' ];
// Some authors may be zot6 authors in which case we want to store their nomadic identity
// instead of their ActivityPub identity
2018-10-09 04:27:35 +00:00
2021-12-02 23:02:31 +00:00
$item [ 'author_xchan' ] = self :: find_best_identity ( $item [ 'author_xchan' ]);
$item [ 'owner_xchan' ] = self :: find_best_identity ( $item [ 'owner_xchan' ]);
if ( ! ( $item [ 'author_xchan' ] && $item [ 'owner_xchan' ])) {
logger ( 'owner or author missing.' );
return ;
}
2022-07-05 01:34:07 +00:00
$plaintext = prepare_text ( $item [ 'body' ],(( isset ( $item [ 'mimetype' ])) ? $item [ 'mimetype' ] : 'text/x-multicode' ));
$plaintext = html2plain (( isset ( $item [ 'title' ]) && $item [ 'title' ]) ? $item [ 'title' ] . ' ' . $plaintext : $plaintext );
2022-07-20 05:27:23 +00:00
2021-12-02 23:02:31 +00:00
if ( $channel [ 'channel_system' ]) {
2022-07-05 01:34:07 +00:00
if ( ! MessageFilter :: evaluate ( $item , get_config ( 'system' , 'pubstream_incl' ), get_config ( 'system' , 'pubstream_excl' ), [ 'plaintext' => $plaintext ])) {
2021-12-02 23:02:31 +00:00
logger ( 'post is filtered' );
return ;
}
}
// fetch allow/deny lists for the sender, author, or both
// if you have them. post_is_importable() assumes true
// and only fails if there was intentional rejection
// due to this channel's filtering rules for content
// provided by either of these entities.
2021-12-03 03:01:39 +00:00
$abook = q (
2022-06-24 20:59:30 +00:00
" select * from abook where ( abook_xchan = '%s' OR abook_xchan = '%s' OR abook_xchan = '%s') and abook_channel = %d " ,
2021-12-02 23:02:31 +00:00
dbesc ( $item [ 'author_xchan' ]),
dbesc ( $item [ 'owner_xchan' ]),
2022-06-24 20:59:30 +00:00
dbesc ( $observer_hash ),
2021-12-02 23:02:31 +00:00
intval ( $channel [ 'channel_id' ])
);
2023-06-02 22:40:38 +00:00
$isFilteredByChannel = ! post_is_importable ( $channel [ 'channel_id' ], $item , $abook );
2021-12-02 23:02:31 +00:00
2023-06-02 22:40:38 +00:00
if ( $isFilteredByChannel ) {
if ( PConfig :: Get ( $channel [ 'channel_id' ], 'system' , 'filter_moderate' )) {
$item [ 'item_blocked' ] = ITEM_MODERATED ;
}
else {
logger ( 'post is filtered' );
return ;
}
2021-12-02 23:02:31 +00:00
}
$maxlen = get_max_import_size ();
if ( $maxlen && mb_strlen ( $item [ 'body' ]) > $maxlen ) {
$item [ 'body' ] = mb_substr ( $item [ 'body' ], 0 , $maxlen , 'UTF-8' );
logger ( 'message length exceeds max_import_size: truncated' );
}
if ( $maxlen && mb_strlen ( $item [ 'summary' ]) > $maxlen ) {
$item [ 'summary' ] = mb_substr ( $item [ 'summary' ], 0 , $maxlen , 'UTF-8' );
logger ( 'message summary length exceeds max_import_size: truncated' );
}
if ( $act -> obj [ 'context' ]) {
set_iconfig ( $item , 'activitypub' , 'context' , $act -> obj [ 'context' ], 1 );
}
set_iconfig ( $item , 'activitypub' , 'recips' , $act -> raw_recips );
if ( intval ( $act -> sigok )) {
$item [ 'item_verified' ] = 1 ;
}
if ( $is_child_node ) {
2022-10-09 04:39:03 +00:00
if ( ! $parent_item ) {
2021-12-02 23:02:31 +00:00
if ( ! get_config ( 'system' , 'activitypub' , ACTIVITYPUB_ENABLED )) {
return ;
} else {
$fetch = false ;
2023-12-20 03:13:40 +00:00
if ( intval ( $channel [ 'channel_system' ]) || ( perm_is_allowed ( $channel [ 'channel_id' ], $observer_hash , 'send_stream' ) && perm_is_allowed ( $channel [ 'channel_id' ], $observer_hash , 'hyperdrive' ) && ( PConfig :: Get ( $channel [ 'channel_id' ], 'system' , 'hyperdrive' , true ) || $act -> type === 'Announce' ))) {
2022-10-20 09:23:02 +00:00
$fetch = ( $fetch_parents && self :: fetch_and_store_parents ( $channel , $observer_hash , $item ));
2021-12-02 23:02:31 +00:00
}
if ( $fetch ) {
2022-10-09 04:39:03 +00:00
$parent_item = q (
2021-12-03 03:01:39 +00:00
" select * from item where mid = '%s' and uid = %d limit 1 " ,
2021-12-02 23:02:31 +00:00
dbesc ( $item [ 'parent_mid' ]),
intval ( $item [ 'uid' ])
);
2022-10-09 04:39:03 +00:00
if ( $parent_item ) {
$parent_item = array_shift ( $parent_item );
}
2021-12-02 23:02:31 +00:00
} else {
2023-03-06 21:48:55 +00:00
logger ( 'no parent: ' . $item [ 'mid' ]);
2021-12-02 23:02:31 +00:00
return ;
}
}
}
2022-10-09 04:39:03 +00:00
$item [ 'comment_policy' ] = $parent_item [ 'comment_policy' ];
$item [ 'item_nocomment' ] = $parent_item [ 'item_nocomment' ];
$item [ 'comments_closed' ] = $parent_item [ 'comments_closed' ];
2021-12-02 23:02:31 +00:00
2022-10-20 00:00:48 +00:00
// If this is a nested conversation with more than one level of comments,
// set thr_parent to the immediate parent and set parent_mid to the conversation root.
2022-10-09 04:39:03 +00:00
if ( $parent_item [ 'parent_mid' ] !== $item [ 'parent_mid' ]) {
2021-12-02 23:02:31 +00:00
$item [ 'thr_parent' ] = $item [ 'parent_mid' ];
} else {
2022-10-09 04:39:03 +00:00
$item [ 'thr_parent' ] = $parent_item [ 'parent_mid' ];
2021-12-02 23:02:31 +00:00
}
2022-10-09 04:39:03 +00:00
$item [ 'parent_mid' ] = $parent_item [ 'parent_mid' ];
2021-12-02 23:02:31 +00:00
/*
2021-12-03 03:01:39 +00:00
*
* Check for conversation privacy mismatches
2022-10-20 09:23:02 +00:00
* We can only do this if we have a channel , and we have fetched the parent
2021-12-03 03:01:39 +00:00
*
*/
2021-12-02 23:02:31 +00:00
// public conversation, but this comment went rogue and was published privately
// hide it from everybody except the channel owner
2022-10-09 04:39:03 +00:00
if ( intval ( $parent_item [ 'item_private' ]) === 0 ) {
2021-12-02 23:02:31 +00:00
if ( intval ( $item [ 'item_private' ])) {
$item [ 'item_restrict' ] = $item [ 'item_restrict' ] | 1 ;
$item [ 'allow_cid' ] = '<' . $channel [ 'channel_hash' ] . '>' ;
$item [ 'allow_gid' ] = $item [ 'deny_cid' ] = $item [ 'deny_gid' ] = '' ;
}
}
2023-05-25 04:54:05 +00:00
// private conversation, but this comment went rogue and was published publicly
// hide it from everybody except the channel owner
if ( intval ( $parent_item [ 'item_private' ])) {
if ( ! intval ( $item [ 'item_private' ])) {
$item [ 'item_private' ] = intval ( $parent_item [ 'item_private' ]);
$item [ 'allow_cid' ] = '<' . $channel [ 'channel_hash' ] . '>' ;
$item [ 'allow_gid' ] = $item [ 'deny_cid' ] = $item [ 'deny_gid' ] = '' ;
}
}
2022-03-16 00:06:52 +00:00
}
2021-12-02 23:02:31 +00:00
2022-03-16 00:06:52 +00:00
self :: rewrite_mentions ( $item );
2021-12-02 23:02:31 +00:00
2022-03-16 00:06:52 +00:00
if ( ! isset ( $item [ 'replyto' ])) {
2022-10-09 01:47:49 +00:00
if ( str_starts_with ( $item [ 'owner_xchan' ], 'http' )) {
2022-03-16 00:06:52 +00:00
$item [ 'replyto' ] = $item [ 'owner_xchan' ];
}
else {
2022-06-17 02:46:54 +00:00
$r = q ( " select hubloc_id_url from hubloc where hubloc_hash = '%s' and hubloc_primary = 1 and hubloc_deleted = 0 " ,
2022-03-16 00:06:52 +00:00
dbesc ( $item [ 'owner_xchan' ])
);
if ( $r ) {
$item [ 'replyto' ] = $r [ 0 ][ 'hubloc_id_url' ];
}
2021-12-02 23:02:31 +00:00
}
}
2021-12-03 03:01:39 +00:00
$r = q (
2022-12-11 19:16:17 +00:00
" select id, created, edited, approved from item where mid = '%s' and uid = %d limit 1 " ,
2021-12-02 23:02:31 +00:00
dbesc ( $item [ 'mid' ]),
intval ( $item [ 'uid' ])
);
if ( $r ) {
2022-12-11 19:16:17 +00:00
if ( $item [ 'edited' ] > $r [ 0 ][ 'edited' ] || $item [ 'approved' ] !== $r [ 0 ][ 'approved' ]) {
2021-12-02 23:02:31 +00:00
$item [ 'id' ] = $r [ 0 ][ 'id' ];
2022-11-11 21:23:18 +00:00
ObjCache :: Set ( $item [ 'mid' ], $act -> raw );
2022-07-18 10:47:05 +00:00
$x = item_store_update ( $item , deliver : false );
2021-12-02 23:02:31 +00:00
} else {
return ;
}
} else {
2022-11-11 21:23:18 +00:00
ObjCache :: Set ( $item [ 'mid' ], $act -> raw );
2022-07-18 10:47:05 +00:00
$x = item_store ( $item , deliver : false );
2021-12-02 23:02:31 +00:00
}
2018-08-20 03:39:23 +00:00
2024-01-24 20:01:42 +00:00
if ( $relay && $channel [ 'channel_hash' ] === $x [ 'item' ][ 'owner_xchan' ] && $x [ 'item' ][ 'verb' ] !== 'Add' ) {
$x = Activity :: addToCollection ( $channel , $act -> raw , $x [ 'item' ][ 'parent_mid' ], $x [ 'item' ], deliver : false );
}
2018-08-20 03:39:23 +00:00
2021-06-17 23:17:31 +00:00
// experimental code that needs more work. What this did was once we fetched a conversation to find the root node,
2022-10-20 09:23:02 +00:00
// start at that root node and fetch children, so you get all the branches and not just the branch related to the current node.
2021-06-17 23:18:50 +00:00
// Unfortunately there is no standard method for achieving this. Mastodon provides a 'replies' collection and Nomad projects
2022-10-20 09:23:02 +00:00
// can fetch the 'context'. For other platforms it's a wild guess. Additionally, when we tested this, it started an infinite
2021-12-02 23:02:31 +00:00
// recursion and has been disabled until the recursive behaviour is tracked down and fixed.
2021-06-17 23:17:31 +00:00
2022-10-09 04:39:03 +00:00
// if ($fetch_parents && $parent && ! intval($parent_item['item_private'])) {
2021-12-03 03:01:39 +00:00
// logger('topfetch', LOGGER_DEBUG);
// // if the thread owner is a connnection, we will already receive any additional comments to their posts
// // but if they are not we can try to fetch others in the background
// $x = q("SELECT abook.*, xchan.* FROM abook left join xchan on abook_xchan = xchan_hash
// WHERE abook_channel = %d and abook_xchan = '%s' LIMIT 1",
// intval($channel['channel_id']),
2022-10-09 04:39:03 +00:00
// dbesc($parent_item['owner_xchan'])
2021-12-03 03:01:39 +00:00
// );
// if (! $x) {
// // determine if the top-level post provides a replies collection
2022-10-09 04:39:03 +00:00
// if ($parent_item['obj']) {
// $parent_item['obj'] = json_decode($parent_item['obj'],true);
2021-12-03 03:01:39 +00:00
// }
2022-10-09 04:39:03 +00:00
// logger('topfetch: ' . print_r($parent_item,true), LOGGER_ALL);
// $id = ((array_path_exists('obj/replies/id',$parent_item)) ? $parent_item['obj']['replies']['id'] : false);
2021-12-03 03:01:39 +00:00
// if (! $id) {
2022-10-09 04:39:03 +00:00
// $id = ((array_path_exists('obj/replies',$parent_item) && is_string($parent_item['obj']['replies'])) ? $parent_item['obj']['replies'] : false);
2021-12-03 03:01:39 +00:00
// }
// if ($id) {
// Run::Summon( [ 'Convo', $id, $channel['channel_id'], $observer_hash ] );
// }
// }
// }
2018-08-20 03:39:23 +00:00
2021-12-02 23:02:31 +00:00
if ( is_array ( $x ) && $x [ 'item_id' ]) {
2023-05-03 21:13:45 +00:00
tag_deliver ( $channel [ 'channel_id' ], $x [ 'item_id' ]);
2021-12-02 23:02:31 +00:00
if ( $is_child_node ) {
if ( $item [ 'owner_xchan' ] === $channel [ 'channel_hash' ]) {
// We are the owner of this conversation, so send all received comments back downstream
Run :: Summon ([ 'Notifier' , 'comment-import' , $x [ 'item_id' ]]);
}
2022-03-14 10:09:03 +00:00
}
2022-07-18 10:47:05 +00:00
elseif ( $act -> client && $channel [ 'channel_hash' ] === $observer_hash && ! $force ) {
2022-03-14 10:09:03 +00:00
Run :: Summon ([ 'Notifier' , 'wall-new' , $x [ 'item_id' ]]);
}
$r = q (
" select * from item where id = %d limit 1 " ,
intval ( $x [ 'item_id' ])
);
if ( $r ) {
2022-08-14 09:20:43 +00:00
send_status_notifications ( $r [ 0 ]);
2021-12-02 23:02:31 +00:00
}
sync_an_item ( $channel [ 'channel_id' ], $x [ 'item_id' ]);
}
}
2019-09-21 22:26:55 +00:00
2022-10-12 11:00:23 +00:00
public static function comment_allowed ( $channel , $item , $parent_item ) : bool | string
2022-10-09 01:47:49 +00:00
{
// First check if comment permissions have been granted to this author.
2023-11-04 00:51:57 +00:00
$allowed = perm_is_allowed ( $channel [ 'channel_id' ], $item [ 'author_xchan' ],
((( int ) $item [ 'item_private' ] === 2 ) ? 'post_mail' : 'post_comments' ));
2022-10-09 01:47:49 +00:00
// Allow likes from strangers if permitted to do so. These are difficult (but not impossible) to spam.
2023-03-13 02:41:15 +00:00
if ( $item [ 'verb' ] === 'Like' && PConfig :: Get ( $channel [ 'channel_id' ], 'system' , 'permit_all_likes' ) && $item [ 'obj_type' ] === 'Note' ) {
2022-10-09 01:47:49 +00:00
$allowed = true ;
}
// Allow mentions from strangers if permitted to do so.
if (( ! $allowed ) && intval ( PConfig :: Get ( $channel [ 'channel_id' ], 'system' , 'permit_all_mentions' )
&& i_am_mentioned ( $channel , $item ))) {
// This deserves explanation. Most comments in my own conversation will mention me because
// this is normal Mastodon behaviour - as that platform requires mentions to trigger comment
// notifications. We have separate comment notifications for that and do not require mentions
// in every comment. We may not want to allow anybody on the planet to comment on our posts.
// There is a separate permission setting for that. That's like not having comment permissions
// at all and basically creates a spam gateway. But if we were mentioned in somebody else's
// conversation, that *might* be interesting. We have an "unless" setting which is evaluated
// inside i_am_mentioned() that puts a limit on your tolerance to mention/tag spam. So if the
// post mentions 87000 people, it will still be ignored.
$allowed = ( $parent_item [ 'owner_xchan' ] !== $channel [ 'channel_hash' ]);
}
2022-10-12 11:00:23 +00:00
if (( ! $allowed ) && intval ( PConfig :: Get ( $channel [ 'channel_id' ], 'system' , 'permit_moderated_comments' ))) {
$allowed = 'moderated' ;
}
2022-10-09 01:47:49 +00:00
// If the item comment control forbids any comments, this over-rides everything.
if ( absolutely_no_comments ( $parent_item )) {
$allowed = false ;
}
return $allowed ;
}
2021-12-02 23:02:31 +00:00
public static function find_best_identity ( $xchan )
{
2021-08-27 05:15:24 +00:00
2021-12-03 03:01:39 +00:00
$r = q (
2022-07-14 00:16:02 +00:00
" select hubloc_hash, hubloc_network from hubloc where hubloc_id_url = '%s' and hubloc_deleted = 0 order by hubloc_id desc " ,
2021-12-02 23:02:31 +00:00
dbesc ( $xchan )
);
if ( $r ) {
2022-07-14 00:16:02 +00:00
$r = Libzot :: zot_record_preferred ( $r );
return $r [ 'hubloc_hash' ];
2021-12-02 23:02:31 +00:00
}
return $xchan ;
}
2021-08-27 05:15:24 +00:00
2022-10-09 04:39:03 +00:00
public static function fetch_and_store_parents ( $channel , $observer_hash , $item )
2021-12-02 23:02:31 +00:00
{
logger ( 'fetching parents' );
2022-10-09 04:39:03 +00:00
$conversation = [];
2022-11-11 21:23:18 +00:00
$seen_mids = [];
2021-12-02 23:02:31 +00:00
$current_item = $item ;
while ( $current_item [ 'parent_mid' ] !== $current_item [ 'mid' ]) {
2022-11-11 21:23:18 +00:00
// recursion breaker
if ( in_array ( $current_item [ 'parent_mid' ], $seen_mids )) {
break ;
}
2022-10-09 04:39:03 +00:00
$json = self :: fetch ( $current_item [ 'parent_mid' ]);
2022-11-11 21:23:18 +00:00
$seen_mids [] = $current_item [ 'parent_mid' ];
2022-10-09 04:39:03 +00:00
if ( ! $json ) {
2021-12-02 23:02:31 +00:00
break ;
}
// set client flag to convert objects to implied activities
2022-10-09 04:39:03 +00:00
$activity = new ActivityStreams ( $json , null , true );
2021-12-03 03:01:39 +00:00
if (
2022-10-09 04:39:03 +00:00
$activity -> type === 'Announce' && is_array ( $activity -> obj )
&& array_key_exists ( 'object' , $activity -> obj ) && array_key_exists ( 'actor' , $activity -> obj )
2021-12-03 03:01:39 +00:00
) {
2021-12-02 23:02:31 +00:00
// This is a relayed/forwarded Activity (as opposed to a shared/boosted object)
// Reparse the encapsulated Activity and use that instead
logger ( 'relayed activity' , LOGGER_DEBUG );
2022-10-09 04:39:03 +00:00
$activity = new ActivityStreams ( $activity -> obj , null , true );
2021-12-02 23:02:31 +00:00
}
2022-10-09 04:39:03 +00:00
logger ( $activity -> debug (), LOGGER_DATA );
2019-09-21 22:26:55 +00:00
2022-10-09 04:39:03 +00:00
if ( ! $activity -> is_valid ()) {
2021-12-02 23:02:31 +00:00
logger ( 'not a valid activity' );
break ;
}
2022-10-09 04:39:03 +00:00
if ( is_array ( $activity -> actor ) && array_key_exists ( 'id' , $activity -> actor )) {
self :: actor_store ( $activity -> actor [ 'id' ], $activity -> actor );
2021-12-02 23:02:31 +00:00
}
// ActivityPub sourced items are cacheable
2022-10-09 04:39:03 +00:00
$item = self :: decode_note ( $activity , true );
2021-12-02 23:02:31 +00:00
if ( ! $item ) {
break ;
}
2023-03-06 20:48:28 +00:00
logger ( 'decoded_note: ' . print_r ( $item , true ), LOGGER_DATA );
2021-12-02 23:02:31 +00:00
$hookinfo = [
2022-10-09 04:39:03 +00:00
'activity' => $activity ,
2021-12-02 23:02:31 +00:00
'item' => $item
];
2022-02-12 08:50:48 +00:00
Hook :: call ( 'fetch_and_store' , $hookinfo );
2021-12-02 23:02:31 +00:00
$item = $hookinfo [ 'item' ];
if ( $item ) {
// don't leak any private conversations to the public stream
// even if they contain publicly addressed comments/reactions
if ( intval ( $channel [ 'channel_system' ]) && intval ( $item [ 'item_private' ])) {
logger ( 'private conversation ignored' );
2022-10-09 04:39:03 +00:00
$conversation = [];
2021-12-02 23:02:31 +00:00
break ;
}
2022-10-09 04:39:03 +00:00
// We're fetching upstream starting with the initial post,
// so push each fetched activity to the head of the conversation.
array_unshift ( $conversation , [ 'activity' => $activity , 'item' => $item ]);
2021-12-02 23:02:31 +00:00
if ( $item [ 'parent_mid' ] === $item [ 'mid' ]) {
break ;
}
}
$current_item = $item ;
}
2023-03-06 21:48:55 +00:00
if ( $conversation && $conversation [ 0 ][ 'item' ][ 'mid' ] === $conversation [ 0 ][ 'item' ][ 'parent_mid' ]) {
2022-10-09 04:39:03 +00:00
foreach ( $conversation as $post ) {
if ( $post [ 'activity' ] -> is_valid ()) {
self :: store ( $channel , $observer_hash , $post [ 'activity' ], $post [ 'item' ], false );
2021-12-02 23:02:31 +00:00
}
}
return true ;
}
return false ;
}
2022-06-08 23:47:17 +00:00
// This function is designed to work with Nomad attachments and item body
2021-12-02 23:02:31 +00:00
2022-04-21 21:46:21 +00:00
public static function bb_attach ( $item )
2021-12-02 23:02:31 +00:00
{
2022-07-29 21:46:26 +00:00
if ( ! is_array ( $item [ 'attach' ])) {
2022-04-21 21:46:21 +00:00
return $item ;
2021-12-02 23:02:31 +00:00
}
2022-04-21 21:46:21 +00:00
foreach ( $item [ 'attach' ] as $a ) {
2021-12-02 23:02:31 +00:00
if ( array_key_exists ( 'type' , $a ) && stripos ( $a [ 'type' ], 'image' ) !== false ) {
2022-10-20 09:23:02 +00:00
// don't add inline image if it's type svg, and we already have an inline svg
2022-04-21 21:46:21 +00:00
if ( $a [ 'type' ] === 'image/svg+xml' && strpos ( $item [ 'body' ], '[/svg]' )) {
2021-12-02 23:02:31 +00:00
continue ;
}
2022-07-07 22:31:16 +00:00
// Friendica attachment weirdness
// Check both the attachment image and href since they can be different and the one in the href is a different link with different resolution.
2022-07-29 21:46:26 +00:00
// Otheriwse you'll get duplicated images
if ( isset ( $a [ 'image' ])) {
if ( self :: media_not_in_body ( $a [ 'image' ], $item [ 'body' ]) && self :: media_not_in_body ( $a [ 'href' ], $item [ 'body' ])) {
if ( isset ( $a [ 'name' ]) && $a [ 'name' ]) {
$alt = htmlspecialchars ( $a [ 'name' ], ENT_QUOTES );
// Escape brackets by converting to unicode full-width bracket since regular brackets will confuse multicode/bbcode parsing.
// The full width bracket isn't quite as alien looking as most other unicode bracket replacements.
$alt = str_replace ([ '[' , ']' ], [ '[' , ']' ], $alt );
$item [ 'body' ] .= " \n \n " . '[img alt="' . $alt . '"]' . $a [ 'href' ] . '[/img]' ;
} else {
$item [ 'body' ] .= " \n \n " . '[img]' . $a [ 'href' ] . '[/img]' ;
}
2022-07-07 22:31:16 +00:00
}
continue ;
2022-07-20 05:27:23 +00:00
}
2022-07-29 21:46:26 +00:00
elseif ( self :: media_not_in_body ( $a [ 'href' ], $item [ 'body' ])) {
2021-12-02 23:02:31 +00:00
if ( isset ( $a [ 'name' ]) && $a [ 'name' ]) {
$alt = htmlspecialchars ( $a [ 'name' ], ENT_QUOTES );
2022-06-08 23:47:17 +00:00
// Escape brackets by converting to unicode full-width bracket since regular brackets will confuse multicode/bbcode parsing.
2022-07-20 05:27:23 +00:00
// The full width bracket isn't quite as alien looking as most other unicode bracket replacements.
2022-06-07 07:45:57 +00:00
$alt = str_replace ([ '[' , ']' ], [ '[' , ']' ], $alt );
2022-04-21 21:46:21 +00:00
$item [ 'body' ] .= " \n \n " . '[img alt="' . $alt . '"]' . $a [ 'href' ] . '[/img]' ;
2021-12-02 23:02:31 +00:00
} else {
2022-04-21 21:46:21 +00:00
$item [ 'body' ] .= " \n \n " . '[img]' . $a [ 'href' ] . '[/img]' ;
2021-12-02 23:02:31 +00:00
}
}
}
if ( array_key_exists ( 'type' , $a ) && stripos ( $a [ 'type' ], 'video' ) !== false ) {
2022-04-21 21:46:21 +00:00
if ( self :: media_not_in_body ( $a [ 'href' ], $item [ 'body' ])) {
$item [ 'body' ] .= " \n \n " . '[video]' . $a [ 'href' ] . '[/video]' ;
2021-12-02 23:02:31 +00:00
}
}
if ( array_key_exists ( 'type' , $a ) && stripos ( $a [ 'type' ], 'audio' ) !== false ) {
2022-04-21 21:46:21 +00:00
if ( self :: media_not_in_body ( $a [ 'href' ], $item [ 'body' ])) {
$item [ 'body' ] .= " \n \n " . '[audio]' . $a [ 'href' ] . '[/audio]' ;
2021-12-02 23:02:31 +00:00
}
}
2022-03-31 19:21:52 +00:00
if ( array_key_exists ( 'type' , $a ) && stripos ( $a [ 'type' ], 'activity' ) !== false ) {
2022-06-23 20:44:21 +00:00
if ( self :: share_not_in_body ( $item [ 'body' ])) {
2022-04-21 21:46:21 +00:00
$item = self :: get_quote ( $a [ 'href' ], $item );
2022-03-31 19:21:52 +00:00
}
}
2022-06-28 03:10:51 +00:00
2021-12-02 23:02:31 +00:00
}
2022-04-21 21:46:21 +00:00
return $item ;
2021-12-02 23:02:31 +00:00
}
// check for the existence of existing media link in body
public static function media_not_in_body ( $s , $body )
{
$s_alt = htmlspecialchars ( $s , ENT_QUOTES , 'UTF-8' );
2021-12-03 03:01:39 +00:00
if (
2022-10-09 01:47:49 +00:00
( ! str_contains ( $body , ']' . $s . '[/img]' )) &&
( ! str_contains ( $body , ']' . $s . '[/zmg]' )) &&
( ! str_contains ( $body , '[img=' . $s . ']' )) &&
( ! str_contains ( $body , '[zmg=' . $s . ']' )) &&
( ! str_contains ( $body , ']' . $s . '[/video]' )) &&
( ! str_contains ( $body , ']' . $s . '[/zvideo]' )) &&
( ! str_contains ( $body , ']' . $s . '[/audio]' )) &&
( ! str_contains ( $body , ']' . $s . '[/zaudio]' )) &&
( ! str_contains ( $body , ']' . $s_alt . '[/img]' )) &&
( ! str_contains ( $body , ']' . $s_alt . '[/zmg]' )) &&
( ! str_contains ( $body , '[img=' . $s_alt . ']' )) &&
( ! str_contains ( $body , '[zmg=' . $s_alt . ']' )) &&
( ! str_contains ( $body , ']' . $s_alt . '[/video]' )) &&
( ! str_contains ( $body , ']' . $s_alt . '[/zvideo]' )) &&
( ! str_contains ( $body , ']' . $s_alt . '[/audio]' )) &&
( ! str_contains ( $body , ']' . $s_alt . '[/zaudio]' ))
2021-12-03 03:01:39 +00:00
) {
2021-12-02 23:02:31 +00:00
return true ;
}
return false ;
}
2023-04-17 19:25:16 +00:00
// check for the existence of existing share in body
2022-06-23 20:44:21 +00:00
public static function share_not_in_body ( $body )
{
2022-10-09 01:47:49 +00:00
if ( ! str_contains ( $body , '[/share]' )) {
2022-06-23 20:44:21 +00:00
return true ;
}
return false ;
}
2021-12-02 23:02:31 +00:00
public static function bb_content ( $content , $field )
{
$ret = false ;
if ( ! is_array ( $content )) {
btlogger ( 'content not initialised' );
2022-10-20 09:23:02 +00:00
return false ;
2021-12-02 23:02:31 +00:00
}
2021-08-27 05:15:24 +00:00
2021-12-02 23:02:31 +00:00
if ( array_key_exists ( $field , $content ) && is_array ( $content [ $field ])) {
2022-10-20 09:23:02 +00:00
/** @noinspection PhpUnusedLocalVariableInspection */
2021-12-02 23:02:31 +00:00
foreach ( $content [ $field ] as $k => $v ) {
$ret .= html2bbcode ( $v );
// save this for auto-translate or dynamic filtering
// $ret .= '[language=' . $k . ']' . html2bbcode($v) . '[/language]';
}
} elseif ( isset ( $content [ $field ])) {
if ( $field === 'bbcode' && array_key_exists ( 'bbcode' , $content )) {
$ret = $content [ $field ];
} else {
$ret = html2bbcode ( $content [ $field ]);
}
} else {
$ret = EMPTY_STR ;
}
if ( $field === 'content' && isset ( $content [ 'event' ]) && ( ! strpos ( $ret , '[event' ))) {
$ret .= format_event_bbcode ( $content [ 'event' ]);
}
return $ret ;
}
public static function get_content ( $act , $binary = false )
{
$content = [];
$event = null ;
if (( ! $act ) || ( ! is_array ( $act ))) {
return $content ;
}
if ( $act [ 'type' ] === 'Event' ) {
$adjust = false ;
$event = [];
$event [ 'event_hash' ] = $act [ 'id' ];
2022-10-20 09:23:02 +00:00
if ( array_key_exists ( 'startTime' , $act ) && str_ends_with ( $act [ 'startTime' ], 'Z' )) {
2021-12-02 23:02:31 +00:00
$adjust = true ;
$event [ 'adjust' ] = 1 ;
2022-10-20 09:23:02 +00:00
$event [ 'dtstart' ] = datetime_convert ( 'UTC' , 'UTC' , $event [ 'startTime' ]);
2021-12-02 23:02:31 +00:00
}
if ( array_key_exists ( 'endTime' , $act )) {
$event [ 'dtend' ] = datetime_convert ( 'UTC' , 'UTC' , $event [ 'endTime' ] . (( $adjust ) ? '' : 'Z' ));
} else {
$event [ 'nofinish' ] = true ;
}
if ( array_key_exists ( 'eventRepeat' , $act )) {
$event [ 'event_repeat' ] = $act [ 'eventRepeat' ];
}
}
foreach ([ 'name' , 'summary' , 'content' ] as $a ) {
if (( $x = self :: get_textfield ( $act , $a , $binary )) !== false ) {
$content [ $a ] = $x ;
}
if ( isset ( $content [ 'name' ])) {
$content [ 'name' ] = html2plain ( purify_html ( $content [ 'name' ]), 256 );
}
}
if ( $event && ! $binary ) {
$event [ 'summary' ] = html2plain ( purify_html ( $content [ 'summary' ]), 256 );
if ( ! $event [ 'summary' ]) {
if ( $content [ 'name' ]) {
$event [ 'summary' ] = html2plain ( purify_html ( $content [ 'name' ]), 256 );
}
}
if ( ! $event [ 'summary' ]) {
if ( $content [ 'content' ]) {
$event [ 'summary' ] = html2plain ( purify_html ( $content [ 'content' ]), 256 );
}
}
if ( $event [ 'summary' ]) {
$event [ 'summary' ] = substr ( $event [ 'summary' ], 0 , 256 );
}
$event [ 'description' ] = html2bbcode ( $content [ 'content' ]);
if ( $event [ 'summary' ] && $event [ 'dtstart' ]) {
$content [ 'event' ] = $event ;
}
}
if ( array_path_exists ( 'source/mediaType' , $act ) && array_path_exists ( 'source/content' , $act )) {
if ( in_array ( $act [ 'source' ][ 'mediaType' ], [ 'text/bbcode' , 'text/x-multicode' ])) {
2022-10-09 01:47:49 +00:00
if ( is_string ( $act [ 'source' ][ 'content' ]) && str_contains ( $act [ 'source' ][ 'content' ], '<' )) {
2021-12-02 23:02:31 +00:00
$content [ 'bbcode' ] = multicode_purify ( $act [ 'source' ][ 'content' ]);
} else {
$content [ 'bbcode' ] = purify_html ( $act [ 'source' ][ 'content' ], [ 'escape' ]);
}
}
}
return $content ;
}
public static function get_textfield ( $act , $field , $binary = false )
{
$content = false ;
2021-12-03 03:01:39 +00:00
if ( array_key_exists ( $field , $act ) && $act [ $field ]) {
2021-12-02 23:02:31 +00:00
$content = (( $binary ) ? $act [ $field ] : purify_html ( $act [ $field ]));
2021-12-03 03:01:39 +00:00
} elseif ( array_key_exists ( $field . 'Map' , $act ) && $act [ $field . 'Map' ]) {
2021-12-02 23:02:31 +00:00
foreach ( $act [ $field . 'Map' ] as $k => $v ) {
$content [ escape_tags ( $k )] = (( $binary ) ? $v : purify_html ( $v ));
}
}
return $content ;
}
2022-11-30 06:23:24 +00:00
public static function send_accept_activity ( $channel , $observer_hash , $item , $inReplyTo = '' )
2021-12-02 23:02:31 +00:00
{
2021-12-03 03:01:39 +00:00
$recip = q (
2022-06-17 02:46:54 +00:00
" select * from hubloc where hubloc_hash = '%s' and hubloc_deleted = 0 limit 1 " ,
2021-12-02 23:02:31 +00:00
dbesc ( $observer_hash )
);
if ( ! $recip ) {
return ;
}
$arr = [
2022-11-30 06:23:24 +00:00
'id' => z_root () . '/approvals/' . new_uuid (),
'to' => [ $recip [ 0 ][ 'hubloc_id_url' ]],
'type' => 'Accept' ,
'actor' => Channel :: url ( $channel ),
2022-12-04 18:15:43 +00:00
'name' => 'comment accepted' ,
2022-11-30 06:23:24 +00:00
'object' => $item [ 'mid' ]
];
if ( $inReplyTo ) {
$arr [ 'inReplyTo' ] = $inReplyTo ;
}
2022-12-15 21:30:16 +00:00
$msg = array_merge ( self :: ap_context (), $arr );
2022-11-30 06:23:24 +00:00
2022-12-04 18:15:43 +00:00
logger ( 'sending accept: ' . json_encode ( $msg , JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES ), LOGGER_DEBUG );
2022-11-30 06:23:24 +00:00
$queue_id = ActivityPub :: queue_message ( json_encode ( $msg , JSON_UNESCAPED_SLASHES ), $channel , $recip [ 0 ]);
do_delivery ([ $queue_id ]);
}
public static function send_reject_activity ( $channel , $observer_hash , $item , $inReplyTo )
{
$recip = q (
" select * from hubloc where hubloc_hash = '%s' and hubloc_deleted = 0 limit 1 " ,
dbesc ( $observer_hash )
);
if ( ! $recip ) {
return ;
}
$arr = [
'id' => z_root () . '/approvals/' . new_uuid (),
'to' => [ $recip [ 0 ][ 'hubloc_id_url' ]],
2021-12-02 23:02:31 +00:00
'type' => 'Reject' ,
2022-01-25 01:26:12 +00:00
'actor' => Channel :: url ( $channel ),
2021-12-02 23:02:31 +00:00
'name' => 'Permission denied' ,
2022-06-16 20:40:47 +00:00
'object' => $item [ 'mid' ]
2021-12-02 23:02:31 +00:00
];
2022-11-30 06:23:24 +00:00
if ( $inReplyTo ) {
$arr [ 'inReplyTo' ] = $inReplyTo ;
}
2022-12-15 21:24:47 +00:00
$msg = array_merge ( self :: ap_context (), $arr );
2021-12-02 23:02:31 +00:00
2022-12-04 18:15:43 +00:00
logger ( 'sending reject: ' . json_encode ( $msg , JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES ), LOGGER_DEBUG );
2021-12-02 23:02:31 +00:00
$queue_id = ActivityPub :: queue_message ( json_encode ( $msg , JSON_UNESCAPED_SLASHES ), $channel , $recip [ 0 ]);
do_delivery ([ $queue_id ]);
}
// Find either an Authorization: Bearer token or 'token' request variable
// in the current web request and return it
public static function token_from_request ()
{
2023-07-29 01:07:38 +00:00
$authHeader = ( new HTTPHeaders ()) -> getAuthHeader ();
$auth = ( $authHeader && str_starts_with ( $authHeader , 'Bearer' ));
2021-12-02 23:02:31 +00:00
if ( ! $auth ) {
if ( array_key_exists ( 'token' , $_REQUEST ) && $_REQUEST [ 'token' ]) {
$auth = $_REQUEST [ 'token' ];
}
}
return $auth ;
}
public static function get_xchan_type ( $type )
{
2022-10-20 09:23:02 +00:00
return match ( $type ) {
'Person' => XCHAN_TYPE_PERSON ,
'Group' => XCHAN_TYPE_GROUP ,
'Service' => XCHAN_TYPE_SERVICE ,
'Organization' => XCHAN_TYPE_ORGANIZATION ,
'Application' => XCHAN_TYPE_APPLICATION ,
default => XCHAN_TYPE_UNKNOWN ,
};
2021-12-02 23:02:31 +00:00
}
2022-02-17 05:00:35 +00:00
public static function xchan_type_to_type ( $type )
{
2022-10-20 09:23:02 +00:00
return match ( $type ) {
XCHAN_TYPE_GROUP => 'Group' ,
XCHAN_TYPE_SERVICE => 'Service' ,
XCHAN_TYPE_ORGANIZATION => 'Organization' ,
XCHAN_TYPE_APPLICATION => 'Application' ,
default => 'Person' ,
};
2022-02-17 05:00:35 +00:00
}
2021-12-02 23:02:31 +00:00
public static function get_cached_actor ( $id )
{
return ( XConfig :: Get ( $id , 'system' , 'actor_record' ));
}
public static function get_actor_hublocs ( $url , $options = 'all,not_deleted' )
{
$sql_options = EMPTY_STR ;
$options_arr = explode ( ',' , $options );
if ( count ( $options_arr ) > 1 ) {
for ( $x = 1 ; $x < count ( $options_arr ); $x ++ ) {
switch ( trim ( $options_arr [ $x ])) {
case 'not_deleted' :
$sql_options .= ' and hubloc_deleted = 0 ' ;
break ;
default :
break ;
}
}
}
2022-10-20 09:23:02 +00:00
return match ( trim ( $options_arr [ 0 ])) {
'activitypub' => q (
2023-09-15 06:39:10 +00:00
" select * from hubloc left join xchan on hubloc_hash = xchan_hash where hubloc_hash = '%s' $sql_options order by hubloc_id DESC " ,
2022-10-20 09:23:02 +00:00
dbesc ( $url )
),
'zot6' , 'nomad' => q (
2023-09-15 06:39:10 +00:00
" select * from hubloc left join xchan on hubloc_hash = xchan_hash where hubloc_id_url = '%s' $sql_options order by hubloc_id DESC " ,
2022-10-20 09:23:02 +00:00
dbesc ( $url )
),
default => q (
2023-09-15 06:39:10 +00:00
" select * from hubloc left join xchan on hubloc_hash = xchan_hash where ( hubloc_id_url = '%s' OR hubloc_hash = '%s' ) $sql_options order by hubloc_id DESC " ,
2022-10-20 09:23:02 +00:00
dbesc ( $url ),
dbesc ( $url )
),
};
2021-12-02 23:02:31 +00:00
}
public static function get_actor_collections ( $url )
{
$ret = [];
$actor_record = XConfig :: Get ( $url , 'system' , 'actor_record' );
if ( ! $actor_record ) {
return $ret ;
}
foreach ([ 'inbox' , 'outbox' , 'followers' , 'following' ] as $collection ) {
if ( isset ( $actor_record [ $collection ]) && $actor_record [ $collection ]) {
$ret [ $collection ] = $actor_record [ $collection ];
}
}
if ( array_path_exists ( 'endpoints/sharedInbox' , $actor_record ) && $actor_record [ 'endpoints' ][ 'sharedInbox' ]) {
$ret [ 'sharedInbox' ] = $actor_record [ 'endpoints' ][ 'sharedInbox' ];
}
return $ret ;
}
2024-01-23 09:12:39 +00:00
public static function addToCollection ( $channel , $object , $target , $sourceItem = null , $deliver = true )
2024-01-13 23:13:18 +00:00
{
$item = (( new Item ())
2024-01-23 09:02:09 +00:00
-> setUid ( $channel [ 'channel_id' ])
2024-01-13 23:13:18 +00:00
-> setVerb ( 'Add' )
2024-01-22 21:48:38 +00:00
-> setAuthorXchan ( $channel [ 'channel_hash' ])
-> setOwnerXchan ( $channel [ 'channel_hash' ])
2024-01-13 23:13:18 +00:00
-> setObj ( $object )
-> setObjType ( $object [ 'type' ])
-> setParentMid ( $target )
2024-01-16 20:18:55 +00:00
-> setTarget ([
'id' => $target ,
'type' => 'Collection' ,
'attributedTo' => z_root () . '/channel/' . $channel [ 'channel_address' ]
]
2024-01-13 23:13:18 +00:00
)
);
2024-01-16 20:18:55 +00:00
if ( $sourceItem ) {
$item -> setAllowCid ( $sourceItem [ 'allow_cid' ])
-> setAllowGid ( $sourceItem [ 'allow_gid' ])
-> setDenyCid ( $sourceItem [ 'deny_cid' ])
-> setDenyGid ( $sourceItem [ 'deny_gid' ])
-> setPrivate ( $sourceItem [ 'item_private' ]);
}
2024-01-23 09:12:39 +00:00
$result = post_activity_item ( $item -> toArray (), deliver : $deliver , channel : $channel );
2024-01-22 21:48:38 +00:00
logger ( 'addToCollection: ' . print_r ( $result , true ));
return $result ;
2024-01-13 23:13:18 +00:00
}
2024-01-23 09:12:39 +00:00
public static function removeFromCollection ( $channel , $object , $target , $deliver = true )
2024-01-13 23:13:18 +00:00
{
$item = (( new Item ())
2024-01-23 09:02:09 +00:00
-> setUid ( $channel [ 'channel_id' ])
2024-01-13 23:13:18 +00:00
-> setVerb ( 'Remove' )
2024-01-22 21:48:38 +00:00
-> setAuthorXchan ( $channel [ 'channel_hash' ])
-> setOwnerXchan ( $channel [ 'channel_hash' ])
2024-01-13 23:13:18 +00:00
-> setObj ( $object )
-> setObjType ( $object [ 'type' ])
-> setParentMid ( $target )
2024-01-16 20:18:55 +00:00
-> setTarget ([
'id' => $target ,
'type' => 'Collection' ,
'attributedTo' => z_root () . '/channel/' . $channel [ 'channel_address' ]
]
2024-01-13 23:13:18 +00:00
)
);
2024-01-23 09:12:39 +00:00
$result = post_activity_item ( $item -> toArray (), deliver : $deliver , channel : $channel );
2024-01-22 21:48:38 +00:00
logger ( 'removeFromCollection: ' . print_r ( $result , true ));
return $result ;
2024-01-13 23:13:18 +00:00
}
2023-10-28 19:59:56 +00:00
public static function ap_context ( $contextType = null ) : array
2022-12-15 21:24:47 +00:00
{
return [ '@context' => [
ACTIVITYSTREAMS_JSONLD_REV ,
2023-12-01 00:34:16 +00:00
'https://w3id.org/security/v1' ,
'https://www.w3.org/ns/did/v1' ,
'https://w3id.org/security/multikey/v1' ,
2024-01-04 20:51:37 +00:00
'https://w3id.org/security/data-integrity/v1' ,
'https://purl.archive.org/socialweb/webfinger' ,
2023-10-28 19:59:56 +00:00
self :: ap_schema ( $contextType )
2022-12-15 21:24:47 +00:00
]];
}
2023-10-28 19:59:56 +00:00
public static function ap_schema ( $contextType = null )
2021-12-02 23:02:31 +00:00
{
2023-10-28 19:59:56 +00:00
// $contextType is reserved for future use so that the caller can specify
// a limited subset of the entire schema definition for particular activities.
2021-12-02 23:02:31 +00:00
return [
2022-06-29 05:19:12 +00:00
'nomad' => z_root () . '/apschema#' ,
2021-12-02 23:02:31 +00:00
'toot' => 'http://joinmastodon.org/ns#' ,
'manuallyApprovesFollowers' => 'as:manuallyApprovesFollowers' ,
'oauthRegistrationEndpoint' => 'litepub:oauthRegistrationEndpoint' ,
'sensitive' => 'as:sensitive' ,
'movedTo' => 'as:movedTo' ,
'discoverable' => 'toot:discoverable' ,
2023-09-05 22:19:07 +00:00
'indexable' => 'toot:indexable' ,
2022-10-09 04:43:35 +00:00
'Hashtag' => 'as:Hashtag' ,
2022-11-28 20:53:03 +00:00
'canReply' => 'toot:canReply' ,
2023-12-14 11:36:44 +00:00
'canSearch' => 'nomad:canSearch' ,
2022-12-20 22:12:07 +00:00
'approval' => 'toot:approval' ,
2023-01-20 21:22:35 +00:00
'expires' => 'nomad:expires' ,
'directMessage' => 'nomad:directMessage' ,
'Category' => 'nomad:Category' ,
2023-01-22 01:28:38 +00:00
'copiedTo' => 'nomad:copiedTo' ,
'searchContent' => 'nomad:searchContent' ,
'searchTags' => 'nomad:searchTags' ,
2021-12-02 23:02:31 +00:00
];
2023-12-19 10:08:08 +00:00
2021-12-02 23:02:31 +00:00
}
2022-03-31 19:21:52 +00:00
2022-07-20 05:27:23 +00:00
public static function get_quote ( $url , $item ) {
2022-03-31 19:21:52 +00:00
2022-07-20 05:27:23 +00:00
$a = self :: fetch ( $url );
if ( $a ) {
$act = new ActivityStreams ( $a );
2022-03-31 19:21:52 +00:00
2022-10-20 09:23:02 +00:00
if ( $act -> is_valid ()) {
2022-04-21 21:46:21 +00:00
$z = Activity :: decode_note ( $act );
2022-06-17 02:46:54 +00:00
$r = hubloc_id_query (( is_array ( $act -> actor )) ? $act -> actor [ 'id' ] : $act -> actor );
2022-07-20 05:27:23 +00:00
2022-04-21 21:46:21 +00:00
if ( $r ) {
$r = Libzot :: zot_record_preferred ( $r );
if ( $z ) {
$z [ 'author_xchan' ] = $r [ 'hubloc_hash' ];
}
}
if ( $z ) {
2023-01-22 02:28:37 +00:00
// do not allow somebody to embed a post that was blocked by the site admin
// We *will* let them over-rule any blocks they created themselves
2022-04-21 21:46:21 +00:00
if ( check_siteallowed ( $r [ 'hubloc_id_url' ]) && check_channelallowed ( $z [ 'author_xchan' ])) {
$s = new Zlib\Share ( $z );
$item [ 'body' ] .= " \n \n " . $s -> bbcode ();
2022-06-28 01:48:48 +00:00
$item [ 'attach' ] = $s -> get_attach ();
2022-04-21 21:46:21 +00:00
}
}
2022-03-31 19:21:52 +00:00
}
2022-07-20 05:27:23 +00:00
}
return $item ;
}
2018-11-03 14:20:18 +00:00
}