aboutsummaryrefslogtreecommitdiffstats
path: root/Zotlabs/Lib/Activity.php
diff options
context:
space:
mode:
Diffstat (limited to 'Zotlabs/Lib/Activity.php')
-rw-r--r--Zotlabs/Lib/Activity.php292
1 files changed, 151 insertions, 141 deletions
diff --git a/Zotlabs/Lib/Activity.php b/Zotlabs/Lib/Activity.php
index 54a1b8d2a..296129ea2 100644
--- a/Zotlabs/Lib/Activity.php
+++ b/Zotlabs/Lib/Activity.php
@@ -13,6 +13,7 @@ use Zotlabs\Entity\Item;
require_once('include/event.php');
require_once('include/html2plain.php');
require_once('include/items.php');
+require_once('include/markdown.php');
class Activity {
@@ -68,10 +69,10 @@ class Activity {
if ($j) {
xchan_query($j, true);
$items = fetch_post_tags($j);
- }
- if ($items) {
- return self::encode_item(array_shift($items));
+ if ($items) {
+ return self::encode_item(array_shift($items));
+ }
}
return null;
@@ -165,7 +166,7 @@ class Activity {
}
else {
logger('fetch failed: ' . $url);
- logger($x['body']);
+ logger(print_r($x, true), LOGGER_DEBUG);
}
@@ -580,14 +581,12 @@ class Activity {
}
}
- if (intval($i['item_wall'])) {
- $ret['commentPolicy'] = map_scope(PermissionLimits::Get($i['uid'], 'post_comments'));
- }
-
if (intval($i['item_private']) === 2) {
$ret['directMessage'] = true;
}
+ $ret['commentPolicy'] = (($i['item_wall']) ? map_scope(PermissionLimits::Get($i['uid'], 'post_comments')) : '');
+
if (array_key_exists('comments_closed', $i) && $i['comments_closed'] !== EMPTY_STR && $i['comments_closed'] > NULL_DATE) {
if ($ret['commentPolicy']) {
$ret['commentPolicy'] .= ' ';
@@ -616,6 +615,7 @@ class Activity {
if (!empty($cnv)) {
if (is_string($cnv) && str_starts_with($cnv, z_root())) {
$cnv = str_replace(['/item/', '/activity/'], ['/conversation/', '/conversation/'], $cnv);
+ $ret['contextHistory'] = $cnv;
}
$ret['context'] = $cnv;
}
@@ -694,7 +694,7 @@ class Activity {
if (is_array($t) && !array_key_exists('type', $t))
$t['type'] = 'Hashtag';
- if (is_array($t) && (array_key_exists('href', $t) || array_key_exists('id', $t)) && array_key_exists('name', $t)) {
+ if (is_array($t) && (array_key_exists('href', $t) || array_key_exists('id', $t) || isset($t['icon']['url'])) && array_key_exists('name', $t)) {
switch ($t['type']) {
case 'Hashtag':
$ret[] = ['ttype' => TERM_HASHTAG, 'url' => $t['href'], 'term' => escape_tags((substr($t['name'], 0, 1) === '#') ? substr($t['name'], 1) : $t['name'])];
@@ -709,7 +709,7 @@ class Activity {
break;
case 'Emoji':
- $ret[] = ['ttype' => TERM_EMOJI, 'url' => $t['id'], 'term' => escape_tags($t['name']), 'imgurl' => $t['icon']['url']];
+ $ret[] = ['ttype' => TERM_EMOJI, 'url' => $t['id'] ?? $t['icon']['url'], 'term' => escape_tags($t['name']), 'imgurl' => $t['icon']['url']];
break;
default:
@@ -802,7 +802,7 @@ class Activity {
$ret = [];
- if (isset($item['attachment'])) {
+ if (isset($item['attachment']) && is_array($item['attachment'])) {
$ptr = $item['attachment'];
if (!array_key_exists(0, $ptr)) {
$ptr = [$ptr];
@@ -852,6 +852,8 @@ class Activity {
$entry['type'] = $att['mediaType'];
} elseif (array_key_exists('type', $att) && $att['type'] === 'Image') {
$entry['type'] = 'image/jpeg';
+ } elseif (array_key_exists('type', $att) && $att['type'] === 'Link') {
+ $entry['type'] = 'text/uri-list';
}
if (array_key_exists('name', $att) && $att['name']) {
$entry['name'] = html2plain(purify_html($att['name']), 256);
@@ -941,36 +943,8 @@ class Activity {
}
- if ($ret['type'] === 'emojiReaction') {
- // There may not be an object for these items for legacy reasons - it should be the conversation parent.
- $p = q("select * from item where mid = '%s' and uid = %d",
- dbesc($i['parent_mid']),
- intval($i['uid'])
- );
- if ($p) {
- xchan_query($p, true);
- $p = fetch_post_tags($p);
- $i['obj'] = self::encode_item($p[0]);
-
- // convert to zot6 emoji reaction encoding which uses the target object to indicate the
- // specific emoji instead of overloading the verb or type.
-
- $im = explode('#', $i['verb']);
- if ($im && count($im) > 1)
- $emoji = $im[1];
- if (preg_match("/\[img(.*?)\](.*?)\[\/img\]/ism", $i['body'], $match)) {
- $ln = $match[2];
- }
-
- $i['tgt_type'] = 'Image';
-
- $i['target'] = [
- 'type' => 'Image',
- 'name' => $emoji,
- 'url' => (($ln) ? $ln : z_root() . '/images/emoji/' . $emoji . '.png')
- ];
-
- }
+ if ($ret['type'] === 'EmojiReact') {
+ $ret['content'] = $i['body'];
}
if (strpos($i['mid'], z_root() . '/item/') !== false) {
@@ -985,15 +959,15 @@ class Activity {
$ret['diaspora:guid'] = $i['uuid'];
- if (isset($i['title']) && $i['title'])
- $ret['name'] = html2plain(bbcode($i['title'], ['cache' => true]));
+ if (!empty($i['title']))
+ $ret['name'] = html2plain(bbcode($i['title']));
- if (isset($i['summary']) && $i['summary'])
- $ret['summary'] = bbcode($i['summary'], ['cache' => true]);
+ if (!empty($i['summary']))
+ $ret['summary'] = bbcode($i['summary']);
if ($ret['type'] === 'Announce') {
$tmp = preg_replace('/\[share(.*?)\[\/share\]/ism', EMPTY_STR, $i['body']);
- $ret['content'] = bbcode($tmp, ['cache' => true]);
+ $ret['content'] = bbcode($tmp);
$ret['source'] = [
'content' => $i['body'],
'mediaType' => 'text/bbcode'
@@ -1009,7 +983,7 @@ class Activity {
}
}
- if (isset($i['app']) && $i['app']) {
+ if (!empty($i['app'])) {
$ret['generator'] = ['type' => 'Application', 'name' => $i['app']];
}
if (!empty($i['location']) || !empty($i['coord'])) {
@@ -1050,6 +1024,7 @@ class Activity {
if (!empty($cnv)) {
if (is_string($cnv) && str_starts_with($cnv, z_root())) {
$cnv = str_replace(['/item/', '/activity/'], ['/conversation/', '/conversation/'], $cnv);
+ $ret['contextHistory'] = $cnv;
}
$ret['context'] = $cnv;
}
@@ -1060,7 +1035,7 @@ class Activity {
else
return [];
- if (isset($i['obj']) && $i['obj']) {
+ if (!empty($i['obj'])) {
if (!is_array($i['obj'])) {
$i['obj'] = json_decode($i['obj'], true);
}
@@ -1088,7 +1063,7 @@ class Activity {
$ret['type'] = 'Invite';
}
- if (isset($i['target']) && $i['target']) {
+ if (!empty($i['target'])) {
if (!is_array($i['target'])) {
$i['target'] = json_decode($i['target'], true);
}
@@ -1099,12 +1074,10 @@ class Activity {
return [];
}
-/* this should not be needed
$t = self::encode_taxonomy($i);
if ($t) {
$ret['tag'] = $t;
}
-*/
$a = self::encode_attachment($i, true);
if ($a) {
@@ -1115,7 +1088,6 @@ class Activity {
$ret['to'] = [ACTIVITY_PUBLIC_INBOX];
}
-
$hookinfo = [
'item' => $i,
'encoded' => $ret
@@ -1716,9 +1688,9 @@ class Activity {
return;
}
- $name = $person_obj['name'] ?? '';
+ $name = ((isset($person_obj['name'])) ? escape_tags($person_obj['name']) : '');
if (!$name) {
- $name = $person_obj['preferredUsername'] ?? '';
+ $name = ((isset($person_obj['preferredUsername'])) ? escape_tags($person_obj['preferredUsername']) : '');
}
if (!$name) {
$name = t('Unknown');
@@ -1727,13 +1699,11 @@ class Activity {
$webfinger_addr = ((isset($person_obj['webfinger'])) ? str_replace('acct:', '', $person_obj['webfinger']) : '');
$hostname = '';
$baseurl = '';
- $site_url = '';
$m = parse_url($url);
if ($m) {
- $hostname = $m['host'];
- $baseurl = $m['scheme'] . '://' . $m['host'] . ((isset($m['port'])) ? ':' . $m['port'] : '');
- $site_url = $m['scheme'] . '://' . $m['host'];
+ $hostname = unparse_url($m, ['host']);
+ $baseurl = unparse_url($m, ['scheme', 'host', 'port']);
}
if (!$webfinger_addr && !empty($person_obj['preferredUsername']) && $hostname) {
@@ -1835,7 +1805,7 @@ class Activity {
q("UPDATE site SET site_update = '%s', site_dead = 0 WHERE site_url = '%s'",
dbesc(datetime_convert()),
- dbesc($site_url)
+ dbesc($baseurl)
);
// update existing xchan record
@@ -2081,6 +2051,9 @@ class Activity {
$i = fetch_post_tags($i);
$i[0]['obj'] = $o;
+ $edited = datetime_convert();
+ $i[0]['edited'] = $edited;
+
// create the new object
$newObj = self::build_packet(self::encode_activity($i[0]), $channel, true);
@@ -2098,7 +2071,7 @@ class Activity {
dbesc(json_encode($o)),
intval($relatedItem['id']),
dbesc($newObj),
- dbesc(datetime_convert())
+ dbesc($edited)
);
dbq("COMMIT");
@@ -2145,35 +2118,25 @@ class Activity {
$s['owner_xchan'] = $act->actor['id'];
$s['author_xchan'] = $act->actor['id'];
- $content = [];
+ $s['mid'] = self::getMessageID($act);
- if (is_array($act->obj)) {
- $content = self::get_content($act->obj);
+ if (!$s['mid']) {
+ return false;
}
- $s['mid'] = $act->objprop('id');
-
- if (!$s['mid'] && is_string($act->obj)) {
- $s['mid'] = $act->obj;
- }
+ $s['uuid'] = self::getUUID($act);
- // pleroma fetched activities
- if (!$s['mid'] && isset($act->obj['data']['id'])) {
- $s['mid'] = $act->obj['data']['id'];
+ if (!$s['uuid']) {
+ // If we have not found anything useful, create an uuid v5 from the mid
+ $s['uuid'] = uuid_from_url($s['mid']);
}
- if ($act->objprop('type') === 'Profile') {
- $s['mid'] = $act->id;
- }
+ $content = [];
- if (!$s['mid']) {
- return false;
+ if (is_array($act->obj)) {
+ $content = self::get_content($act->obj);
}
- // Friendica sends the diaspora guid in a nonstandard field via AP
- // If no uuid is provided we will create an uuid v5 from the mid
- $s['uuid'] = (($act->objprop('diaspora:guid')) ?: uuid_from_url($s['mid']));
-
$s['parent_mid'] = $act->parent_id;
if (array_key_exists('published', $act->data)) {
@@ -2212,23 +2175,8 @@ class Activity {
$response_activity = true;
- $s['mid'] = $act->id;
- $s['uuid'] = ((!empty($act->data['diaspora:guid'])) ? $act->data['diaspora:guid'] : uuid_from_url($s['mid']));
-
$s['parent_mid'] = $act->objprop('id') ?: $act->obj;
-/*
- if ($act->objprop('inReplyTo')) {
- $s['parent_mid'] = $act->objprop('inReplyTo');
- }
-
- $s['thr_parent'] = $act->objprop('id') ?: $act->obj;
-
- if (empty($s['parent_mid']) || empty($s['thr_parent'])) {
- logger('response activity without parent_mid or thr_parent');
- return;
- }
-*/
// over-ride the object timestamp with the activity
if (isset($act->data['published'])) {
@@ -2239,9 +2187,9 @@ class Activity {
$s['edited'] = datetime_convert('UTC', 'UTC', $act->data['updated']);
}
- $obj_actor = $act->objprop('actor') ?: $act->get_actor('attributedTo', $act->obj);
+ $obj_actor = is_array($act->objprop('actor')) ? $act->objprop('actor') : $act->get_actor('attributedTo', $act->obj);
- if (!isset($obj_actor['id'])) {
+ if (empty($obj_actor['id'])) {
return false;
}
@@ -2277,12 +2225,8 @@ class Activity {
$content['content'] = sprintf(t('🔁 Repeated %1$s\'s %2$s'), $mention, $act->obj['type']);
}
- // TODO: Deprecated
- if ($act->type === 'emojiReaction') {
- $content['content'] = (($act->tgt && $act->tgt['type'] === 'Image') ? '[img=32x32]' . $act->tgt['url'] . '[/img]' : '&#x' . $act->tgt['name'] . ';');
- }
-
if (in_array($act->type, ['EmojiReact'])) {
+
// Pleroma reactions
$t = trim(self::get_textfield($act->data, 'content'));
@@ -2306,6 +2250,8 @@ class Activity {
if ($s['mid'] === $s['parent_mid']) {
$s['item_thread_top'] = 1;
+ $s['item_nocomment'] = 0;
+ $s['comments_closed'] = NULL_DATE;
// it is a parent node - decode the comment policy info if present
if ($act->objprop('commentPolicy')) {
@@ -2313,7 +2259,7 @@ class Activity {
if ($until !== false) {
$s['comments_closed'] = datetime_convert('UTC', 'UTC', substr($act->obj['commentPolicy'], $until + 6));
if ($s['comments_closed'] < datetime_convert()) {
- $s['nocomment'] = true;
+ $s['item_nocomment'] = 1;
}
}
@@ -2330,10 +2276,15 @@ class Activity {
if (!array_key_exists('edited', $s))
$s['edited'] = $s['created'];
- $s['title'] = (($response_activity) ? EMPTY_STR : self::bb_content($content, 'name'));
- $s['summary'] = self::bb_content($content, 'summary');
+ $s['title'] = (($response_activity) ? EMPTY_STR : html2plain($content['name']));
+ $s['summary'] = (($content['summary'] !== $content['content']) ? html2plain($content['summary']) : '');
$s['body'] = ((self::bb_content($content, 'bbcode') && (!$response_activity)) ? self::bb_content($content, 'bbcode') : self::bb_content($content, 'content'));
+ // peertube quirks
+ if ($act->objprop('mediaType') === 'text/markdown') {
+ $s['body'] = markdown_to_bb($act->objprop('content'));
+ }
+
if ($act->objprop('quoteUrl')) {
$quote_bbcode = self::get_quote_bbcode($act->obj['quoteUrl']);
@@ -2465,7 +2416,8 @@ class Activity {
}
}
- $tag = (($poster) ? '[video poster=&quot;' . $poster . '&quot;]' : '[video]' );
+ $tag = (($poster) ? '[video poster=\'' . $poster . '\']' : '[video]' );
+
$ptr = null;
if ($act->objprop('url')) {
@@ -2680,6 +2632,7 @@ class Activity {
}
}
+
if (!$ap_rawmsg && array_key_exists('signed', $raw_arr)) {
// zap
$ap_rawmsg = json_encode($act->data, JSON_UNESCAPED_SLASHES);
@@ -2713,8 +2666,7 @@ class Activity {
return $hookinfo['s'];
}
-
- static function store($channel, $observer_hash, $act, $item, $fetch_parents = true, $force = false) {
+ static function store($channel, $observer_hash, $act, $item, $fetch_parents = true, $force = false, $is_collection_operation = false) {
$is_sys_channel = is_sys_channel($channel['channel_id']);
$is_child_node = false;
$parent = null;
@@ -2745,6 +2697,8 @@ class Activity {
}
$allowed = false;
+ $relay = false;
+
$permit_mentions = intval(PConfig::Get($channel['channel_id'], 'system','permit_all_mentions') && i_am_mentioned($channel, $item));
if ($is_child_node) {
@@ -2771,13 +2725,22 @@ class Activity {
return;
}
+ $relay = $channel['channel_hash'] === $parent[0]['owner_xchan'];
+
+ if (str_contains($parent[0]['tgt_type'], 'Collection') && !$relay && !$is_collection_operation) {
+ logger('not a collection activity');
+ return;
+ }
+
if ($parent[0]['obj_type'] === 'Question') {
if (in_array($item['obj_type'], ['Note', ACTIVITY_OBJ_COMMENT]) && $item['title'] && (!$item['body'])) {
$item['obj_type'] = 'Answer';
+ $item['item_hidden'] = 1;
}
}
if ($parent[0]['item_wall']) {
+
// set the owner to the owner of the parent
$item['owner_xchan'] = $parent[0]['owner_xchan'];
@@ -2958,7 +2921,10 @@ class Activity {
// This isn't perfect but the best we can do for now.
$item['comment_policy'] = ((isset($act->data['commentPolicy'])) ? $act->data['commentPolicy'] : 'authenticated');
- if (!empty($act->obj['context'])) {
+ if (!empty($act->obj['contextHistory'])) {
+ IConfig::Set($item, 'activitypub', 'context', $act->obj['contextHistory'], 1);
+ }
+ elseif (!empty($act->obj['context'])) {
IConfig::Set($item, 'activitypub', 'context', $act->obj['context'], 1);
}
@@ -2993,7 +2959,7 @@ class Activity {
if (intval($parent[0]['item_private']) === 0) {
if (intval($item['item_private'])) {
- $item['item_restrict'] = $item['item_restrict'] | 1;
+ $item['item_restrict'] = ((isset($item['item_restrict'])) ? $item['item_restrict'] | 1 : 1);
$item['allow_cid'] = '<' . $channel['channel_hash'] . '>';
$item['allow_gid'] = $item['deny_cid'] = $item['deny_gid'] = '';
}
@@ -3010,7 +2976,7 @@ class Activity {
}
}
}
-
+/*
if (isset($item['term']) && !PConfig::Get($channel['channel_id'], 'system', 'no_smilies')) {
foreach ($item['term'] as $t) {
if ($t['ttype'] === TERM_EMOJI) {
@@ -3024,6 +2990,7 @@ class Activity {
}
}
}
+*/
// TODO: not implemented
// self::rewrite_mentions($item);
@@ -3032,17 +2999,42 @@ class Activity {
dbesc($item['mid']),
intval($item['uid'])
);
+
if ($r) {
if ($item['edited'] > $r[0]['edited']) {
$item['id'] = $r[0]['id'];
- $x = item_store_update($item);
+ $x = item_store_update($item, deliver: false);
}
else {
return;
}
}
else {
- $x = item_store($item);
+ $x = item_store($item, deliver: false, addAndSync: false);
+ }
+
+ if ($x['success']) {
+ if ($relay && $channel['channel_hash'] === $x['item']['owner_xchan'] && $x['item']['verb'] !== 'Add' && !$is_collection_operation) {
+ $approval = Activity::addToCollection($channel, $act->data, $x['item']['parent_mid'], $x['item'], deliver: false);
+ }
+
+ if (check_item_source($channel['channel_id'], $x['item']) && in_array($x['item']['obj_type'], ['Event', ACTIVITY_OBJ_EVENT])) {
+ event_addtocal($x['item_id'], $channel['channel_id']);
+ }
+
+ tag_deliver($channel['channel_id'], $x['item_id']);
+
+ if ($relay && $is_child_node) {
+ // We are the owner of this conversation, so send all received comments back downstream
+ Master::Summon(['Notifier', 'comment-import', $x['item_id']]);
+ if (!empty($approval['item_id'])) {
+ Master::Summon(['Notifier', 'comment-import', $approval['item_id']]);
+ }
+ }
+
+ send_status_notifications($x['item_id'], $x['item']);
+
+ sync_an_item($channel['channel_id'], $x['item_id']);
}
if ($fetch_parents && $parent && !intval($parent[0]['item_private'])) {
@@ -3069,28 +3061,6 @@ class Activity {
}
}
}
-
- if ($x['success']) {
-
- if (check_item_source($channel['channel_id'], $x['item']) && in_array($x['item']['obj_type'], ['Event', ACTIVITY_OBJ_EVENT])) {
- event_addtocal($x['item_id'], $channel['channel_id']);
- }
-
- 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
- Master::Summon(['Notifier', 'comment-import', $x['item_id']]);
- }
- $r = q("select * from item where id = %d limit 1",
- intval($x['item_id'])
- );
- if ($r) {
- send_status_notifications($x['item_id'], $r[0]);
- }
- }
- sync_an_item($channel['channel_id'], $x['item_id']);
- }
-
}
/**
@@ -3380,10 +3350,10 @@ class Activity {
if (array_key_exists('startTime', $act) && strpos($act['startTime'], -1, 1) === 'Z') {
$adjust = true;
$event['adjust'] = 1;
- $event['dtstart'] = datetime_convert('UTC', 'UTC', $event['startTime'] . (($adjust) ? '' : 'Z'));
+ $event['dtstart'] = datetime_convert('UTC', 'UTC', $act['startTime'] . (($adjust) ? '' : 'Z'));
}
if (array_key_exists('endTime', $act)) {
- $event['dtend'] = datetime_convert('UTC', 'UTC', $event['endTime'] . (($adjust) ? '' : 'Z'));
+ $event['dtend'] = datetime_convert('UTC', 'UTC', $act['endTime'] . (($adjust) ? '' : 'Z'));
}
else {
$event['nofinish'] = true;
@@ -3669,6 +3639,8 @@ class Activity {
return [
'zot' => z_root() . '/apschema#',
+
+ 'contextHistory' => 'https://w3id.org/fep/171b/contextHistory',
'schema' => 'http://schema.org#',
'ostatus' => 'http://ostatus.org#',
'diaspora' => 'https://diasporafoundation.org/ns/',
@@ -3692,7 +3664,6 @@ class Activity {
'manuallyApprovesFollowers' => 'as:manuallyApprovesFollowers',
'Hashtag' => 'as:Hashtag'
-
];
}
@@ -3785,8 +3756,6 @@ class Activity {
->setObjType($object['type'])
->setParentMid(str_replace('/conversation/','/item/', $target))
->setThrParent(str_replace('/conversation/','/item/', $target))
- // ->setApproved($object['object']['id'] ?? '')
- // ->setReplyto(z_root() . '/channel/' . $channel['channel_address'])
->setTgtType('Collection')
->setTarget([
'id' => str_replace('/item/','/conversation/', $target),
@@ -3801,13 +3770,19 @@ class Activity {
->setDenyCid($sourceItem['deny_cid'])
->setDenyGid($sourceItem['deny_gid'])
->setPrivate($sourceItem['item_private'])
- ->setNocomment($sourceItem['item_nocomment'])
+ ->setRestrict($sourceItem['item_restrict'])
+ ->setHidden($sourceItem['item_hidden'])
+ ->setDelayed($sourceItem['item_delayed'])
+ ->setUnpublished($sourceItem['item_unpublished'])
+ ->setBlocked($sourceItem['item_blocked'])
+ ->setType($sourceItem['item_type'])
->setCommentPolicy($sourceItem['comment_policy'])
->setPublicPolicy($sourceItem['public_policy'])
->setPostopts($sourceItem['postopts']);
}
$result = post_activity_item($item->toArray(), deliver: $deliver, channel: $channel, observer: $channel, addAndSync: false);
logger('addToCollection: ' . print_r($result, true));
+
return $result;
}
@@ -3839,4 +3814,39 @@ class Activity {
return $result;
}
+
+ /**
+ * @brief Retrieves message ID from activity object.
+ * @param object $act Activity object
+ * @return string Message ID or empty string if not found
+ */
+ public static function getMessageID($act): string
+ {
+ if (ActivityStreams::is_response_activity($act->type) || $act->objprop('type') === 'Profile') {
+ return $act->id;
+ }
+
+ return $act->objprop('id', null)
+ ?? (is_string($act->obj) ? $act->obj : null)
+ ?? '';
+ }
+
+ /**
+ * @brief Retrieves the UUID from an activity object.
+ * @param object $act Activity object
+ * @return string UUID or empty string if not found
+ */
+ public static function getUUID($act): string
+ {
+ if (ActivityStreams::is_response_activity($act->type)) {
+ return $act->data['uuid']
+ ?? $act->data['diaspora:guid']
+ ?? '';
+ }
+
+ return $act->objprop('uuid', null)
+ ?? $act->objprop('diaspora:guid', null)
+ ?? '';
+ }
+
}