From 9067a319399abee5372ed684d1b30124e8d6a020 Mon Sep 17 00:00:00 2001 From: Mike Macgirvin Date: Tue, 25 Jun 2024 06:37:06 +1000 Subject: [PATCH] addressing refactor --- include/connections.php | 2 +- include/constants.php | 4 + include/misc.php | 5 + src/Lib/Activity.php | 264 ++++++++++++++++------------------------ 4 files changed, 117 insertions(+), 158 deletions(-) diff --git a/include/connections.php b/include/connections.php index 84b033b5c..5c8bac743 100644 --- a/include/connections.php +++ b/include/connections.php @@ -447,7 +447,7 @@ function contact_remove($channel_id, $abook_id, $atoken_sync = false) Hook::call('connection_remove', $x); - $archive = get_pconfig($channel_id, 'system', 'archive_removed_contacts'); + $archive = get_pconfig($channel_id, 'system', 'archive_removed_contacts', true); if ($archive) { q( "update abook set abook_archived = 1 where abook_id = %d and abook_channel = %d", diff --git a/include/constants.php b/include/constants.php index a243cf23e..0ae6b264d 100644 --- a/include/constants.php +++ b/include/constants.php @@ -145,6 +145,10 @@ define ( 'CLIENT_MODE_UPDATE', 0x0002); define ('AUDIENCE_SENDER', 0x0001); define ('AUDIENCE_CONVERSATION', 0x0002); define ('AUDIENCE_FOLLOWERS', 0x0004); +define ('AUDIENCE_PUBLIC', 0x0008); +define ('AUDIENCE_UNLISTED', 0x0010); +define ('AUDIENCE_MENTIONS', 0x0020); +define ('AUDIENCE_SERVER', 0x0040); /** * diff --git a/include/misc.php b/include/misc.php index e62d8f418..e817979a0 100644 --- a/include/misc.php +++ b/include/misc.php @@ -2636,6 +2636,11 @@ function ids_to_querystr($arr, $idx = 'id', $quote = false) return(implode(',', $t)); } +function array_merge_clean($array1, $array2) +{ + return array_values(array_unique(array_merge($array1, $array2))); +} + /** * @brief array_elm_to_str($arr,$elm,$delim = ',') extract unique individual elements from an array of arrays and return them as a string separated by a delimiter * similar to ids_to_querystr, but allows a different delimiter instead of a db-quote option diff --git a/src/Lib/Activity.php b/src/Lib/Activity.php index 27343a0cb..f4b8504c2 100644 --- a/src/Lib/Activity.php +++ b/src/Lib/Activity.php @@ -951,12 +951,6 @@ class Activity $activity['inReplyTo'] = set_activity_mid($item['thr_parent']); } - // @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']; - // } - $cnv = get_iconfig($item['parent'], 'activitypub', 'context'); if (!$cnv) { $cnv = $item['parent_mid']; @@ -1087,89 +1081,7 @@ class Activity } } - // addressing madness - - $audience = self::getAudienceFromItem($item); - $mentions = self::map_mentions($item); - - 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']))); - } - } elseif ((int)$item['item_private'] === 1) { - if (($audience & AUDIENCE_FOLLOWERS) && $item['item_origin']) { - $activity['to'] = [z_root() . '/followers/' . substr($item['author']['xchan_addr'], 0, strpos($item['author']['xchan_addr'], '@'))]; - } - else { - $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; - } - } - } - } - - if (count($mentions) > 0) { - if (!$activity['to']) { - $activity['to'] = $mentions; - } else { - $activity['cc'] = array_values(array_unique(array_merge($activity['cc'], $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; + $activity = self::encodeAddressing($activity, $item); return $activity; } @@ -1462,91 +1374,113 @@ class Activity } } - // addressing madness + $activity = self::encodeAddressing($activity, $item); + return $activity; + } + + public static function encodeAddressing($activity,$item) + { + /* + * addressing madness + * + * For what it's worth, Mastodon’s visibility scopes are a custom thing and this is generally how they map: + * public = to: [public], cc: [followers, mentions] + * unlisted = cc: [public, followers, mentions] + * followers-only = to: [followers], cc: [mentions] + * direct = to: [mentions] + * + * This software does not normally use the same mappings - and doesn't even require mappings; + * but the Mastodon mappings may need to be considered so that private audiences are not mis-identified + * when communicating with people using that software. + */ + + $channel = Channel::from_id($item['uid']); // needed in future for nomadic follower addressing $audience = self::getAudienceFromItem($item); $mentions = self::map_mentions($item); + $followers = [z_root() . '/followers/' . substr($item['author']['xchan_addr'], 0, strpos($item['author']['xchan_addr'], '@'))]; - if ($activitypub) { - $parent_i = []; - $activity['to'] = []; - $activity['cc'] = []; + $parent_i = []; + $activity['to'] = []; + $activity['cc'] = []; - $public = !$item['item_private']; - $top_level = $item['mid'] === $item['parent_mid']; + $public = !$item['item_private']; + $top_level = $item['mid'] === $item['parent_mid']; - if (!$top_level) { - if (intval($item['parent'])) { - $recips = get_iconfig($item['parent'], 'activitypub', 'recips'); + if (!$top_level) { + if (intval($item['parent'])) { + $recips = get_iconfig($item['parent'], 'activitypub', 'recips'); + } else { + // if we are encoding this item prior to storage there won't be a parent. + $p = q( + "select parent from item where parent_mid = '%s' and uid = %d", + dbesc($item['parent_mid']), + intval($item['uid']) + ); + if ($p) { + $recips = get_iconfig($p[0]['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_merge_clean($activity['to'], $parent_i['to']); + } + if ($item['item_origin']) { + $activity['cc'] = $followers; + } + if (isset($parent_i['cc']) && is_array($parent_i['cc'])) { + $activity['cc'] = array_merge_clean($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_merge_clean($activity['to'], $parent_i['to']); + } + } elseif ((int)$item['item_private'] === 1) { + if (($audience & AUDIENCE_FOLLOWERS) && $item['item_origin']) { + $activity['to'] = $followers; } else { - // if we are encoding this item prior to storage there won't be a parent. - $p = q( - "select parent from item where parent_mid = '%s' and uid = %d", + $activity['cc'] = self::map_acl($item); + if (isset($parent_i['cc']) && is_array($parent_i['cc'])) { + $activity['cc'] = array_merge_clean($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 ($p) { - $recips = get_iconfig($p[0]['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']))); - } - } elseif ((int)$item['item_private'] === 1) { - if (($audience & AUDIENCE_FOLLOWERS) && $item['item_origin']) { - $activity['to'] = [z_root() . '/followers/' . substr($item['author']['xchan_addr'], 0, strpos($item['author']['xchan_addr'], '@'))]; - } - else { - $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; + if ($d) { + if (in_array($d[0]['hubloc_network'], ['activitypub', 'apnomadic'])) { + $addr = $d[0]['hubloc_hash']; + } else { + $addr = $d[0]['hubloc_id_url']; } + $activity['cc'][] = $addr; } } } + } - if (count($mentions) > 0) { + // Delivery is controlled by parent ACL when AUDIENCE_CONVERSATION is in effect - + // and mentions are ignored. + + if ((!$audience) || !($audience & AUDIENCE_CONVERSATION)) { + if (count($mentions)) { if (!$activity['to']) { $activity['to'] = $mentions; - } else { - $activity['cc'] = array_values(array_unique(array_merge($activity['to'], $mentions))); + } elseif ((int)$item['item_private'] !== 2) { + $activity['cc'] = array_merge_clean($activity['cc'], $mentions); } } } @@ -1570,6 +1504,8 @@ class Activity } + + // Returns an array of URLS for any mention tags found in the item array $i. public static function map_mentions($i) @@ -5221,10 +5157,15 @@ class Activity return $item; } + public static function getPostOpts($item) + { + return explode(',', $item['postopts']); + } + public static function getAudienceFromItem($item) { $audience = 0; - $postopts = explode(',', $item['postopts']); + $postopts = self::getPostOpts($item); if (in_array('sender', $postopts)) { $audience += AUDIENCE_SENDER; } @@ -5234,6 +5175,15 @@ class Activity if (in_array('followers', $postopts)) { $audience += AUDIENCE_FOLLOWERS; } + if (in_array('public', $postopts)) { + $audience += AUDIENCE_PUBLIC; + } + if (in_array('unlisted', $postopts)) { + $audience += AUDIENCE_UNLISTED; + } + if (in_array('mentions', $postopts)) { + $audience += AUDIENCE_MENTIONS; + } return $audience; } }