diff options
-rw-r--r-- | Zotlabs/Daemon/Notifier.php | 2 | ||||
-rw-r--r-- | Zotlabs/Lib/Activity.php | 133 | ||||
-rw-r--r-- | Zotlabs/Lib/Libzot.php | 1 | ||||
-rw-r--r-- | Zotlabs/Module/Conversation.php | 4 | ||||
-rw-r--r-- | Zotlabs/Module/Item.php | 38 | ||||
-rw-r--r-- | include/items.php | 150 |
6 files changed, 287 insertions, 41 deletions
diff --git a/Zotlabs/Daemon/Notifier.php b/Zotlabs/Daemon/Notifier.php index 20134b8fe..76e0628bf 100644 --- a/Zotlabs/Daemon/Notifier.php +++ b/Zotlabs/Daemon/Notifier.php @@ -345,7 +345,7 @@ class Notifier { } logger('target_item: ' . print_r($target_item, true), LOGGER_DEBUG); - logger('encoded: ' . print_r(self::$encoded_item, true), LOGGER_DEBUG); + hz_syslog('encoded: ' . print_r(self::$encoded_item, true), LOGGER_DEBUG); // Send comments to the owner to re-deliver to everybody in the conversation // We only do this if the item in question originated on this site. This prevents looping. diff --git a/Zotlabs/Lib/Activity.php b/Zotlabs/Lib/Activity.php index 1ea42acd3..2b9beb3f5 100644 --- a/Zotlabs/Lib/Activity.php +++ b/Zotlabs/Lib/Activity.php @@ -8,6 +8,7 @@ use Zotlabs\Access\PermissionRoles; use Zotlabs\Access\Permissions; use Zotlabs\Daemon\Master; use Zotlabs\Web\HTTPSig; +use Zotlabs\Entity\Item; require_once('include/event.php'); require_once('include/html2plain.php'); @@ -570,6 +571,25 @@ class Activity { if ($i['mid'] !== $i['parent_mid']) { $ret['inReplyTo'] = ((strpos($i['thr_parent'], 'http') === 0) ? $i['thr_parent'] : z_root() . '/item/' . urlencode($i['thr_parent'])); + + $cnv = IConfig::Get($i['parent'], 'activitypub', 'context'); + if (!$cnv) { + $cnv = $i['parent_mid']; + } + } + + if (empty($cnv)) { + $cnv = IConfig::Get($i, 'activitypub', 'context'); + if (!$cnv) { + $cnv = $i['parent_mid']; + } + } + + if (!empty($cnv)) { + if (is_string($cnv) && str_starts_with($cnv, z_root())) { + $cnv = str_replace(['/item/', '/activity/'], ['/conversation/', '/conversation/'], $cnv); + } + $ret['context'] = $cnv; } if ($i['mimetype'] === 'text/bbcode') { @@ -589,6 +609,12 @@ class Activity { $t = self::encode_taxonomy($i); if ($t) { + foreach($t as $tag) { + if (strcasecmp($tag['name'], '#nsfw') === 0 || strcasecmp($tag['name'], '#sensitive') === 0) { + $ret['sensitive'] = true; + } + } + $ret['tag'] = $t; } @@ -596,7 +622,20 @@ class Activity { if ($a) { $ret['attachment'] = $a; } - +/* + if ($i['target']) { + if (is_string($i['target'])) { + $tmp = json_decode($i['target'], true); + if ($tmp !== null) { + $i['target'] = $tmp; + } + } + $tgt = self::encode_object($i['target']); + if ($tgt) { + $ret['target'] = $tgt; + } + } +*/ if (intval($i['item_private']) === 0) { $ret['to'] = [ACTIVITY_PUBLIC_INBOX]; } @@ -938,6 +977,25 @@ class Activity { if (!in_array($ret['type'], ['Create', 'Update', 'Accept', 'Reject', 'TentativeAccept', 'TentativeReject'])) { $ret['inReplyTo'] = ((strpos($i['thr_parent'], 'http') === 0) ? $i['thr_parent'] : z_root() . '/item/' . urlencode($i['thr_parent'])); } + + $cnv = IConfig::Get($i['parent'], 'activitypub', 'context'); + if (!$cnv) { + $cnv = $item['parent_mid']; + } + } + + if (empty($cnv)) { + $cnv = IConfig::Get($i, 'activitypub', 'context'); + if (!$cnv) { + $cnv = $i['parent_mid']; + } + } + + if (!empty($cnv)) { + if (is_string($cnv) && str_starts_with($cnv, z_root())) { + $cnv = str_replace(['/item/', '/activity/'], ['/conversation/', '/conversation/'], $cnv); + } + $ret['context'] = $cnv; } $actor = self::encode_person($i['author'], false); @@ -1009,7 +1067,11 @@ class Activity { call_hooks('encode_activity', $hookinfo); +//hz_syslog(print_r($hookinfo['encoded'], true)); + return $hookinfo['encoded']; + + } // Returns an array of URLS for any mention tags found in the item array $i. @@ -2867,6 +2929,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'])) { + IConfig::Set($item, 'activitypub', 'context', $act->obj['context'], 1); + } + IConfig::Set($item, 'activitypub', 'recips', $act->raw_recips); if (intval($act->sigok)) { @@ -3676,5 +3742,70 @@ class Activity { } } + public static function addToCollection($channel, $object, $target, $sourceItem = null, $deliver = true) { + if (!isset($channel['xchan_hash'])) { + $channel = channelx_by_hash($channel['channel_hash']); + } + + $item = ((new Item()) + ->setUid($channel['channel_id']) + ->setVerb('Add') + ->setAuthorXchan($channel['channel_hash']) + ->setOwnerXchan($channel['channel_hash']) + ->setObj($object) + ->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), + 'type' => 'Collection', + 'attributedTo' => z_root() . '/channel/' . $channel['channel_address'], + ]) + ); + if ($sourceItem) { + $item->setAllowCid($sourceItem['allow_cid']) + ->setAllowGid($sourceItem['allow_gid']) + ->setDenyCid($sourceItem['deny_cid']) + ->setDenyGid($sourceItem['deny_gid']) + ->setPrivate($sourceItem['item_private']) + ->setNocomment($sourceItem['item_nocomment']) + ->setCommentPolicy($sourceItem['comment_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; + } + + public static function removeFromCollection($channel, $object, $target, $deliver = true) { + if (!isset($channel['xchan_hash'])) { + $channel = channelx_by_hash($channel['channel_hash']); + } + + $item = ((new Item()) + ->setUid($channel['channel_id']) + ->setVerb('Remove') + ->setAuthorXchan($channel['channel_hash']) + ->setOwnerXchan($channel['channel_hash']) + ->setObj($object) + ->setObjType($object['type']) + ->setParentMid(str_replace('/conversation/','/item/', $target)) + ->setThrParent(str_replace('/conversation/','/item/', $target)) + ->setReplyto(z_root() . '/channel/' . $channel['channel_address']) + ->setTgtType('Collection') + ->setTarget([ + 'id' => str_replace('/item/','/conversation/', $target), + 'type' => 'Collection', + 'attributedTo' => z_root() . '/channel/' . $channel['channel_address'] + ]) + ); + + $result = post_activity_item($item->toArray(), deliver: $deliver, channel: $channel, observer: $channel, addAndSync: false); + logger('removeFromCollection: ' . print_r($result, true)); + return $result; + } } diff --git a/Zotlabs/Lib/Libzot.php b/Zotlabs/Lib/Libzot.php index bc944c97c..243588d2b 100644 --- a/Zotlabs/Lib/Libzot.php +++ b/Zotlabs/Lib/Libzot.php @@ -1148,6 +1148,7 @@ class Libzot { logger('Activity rejected: ' . print_r($data, true)); return; } + if (is_array($AS->obj)) { $item = Activity::decode_note($AS); if (!$item) { diff --git a/Zotlabs/Module/Conversation.php b/Zotlabs/Module/Conversation.php index aa8349f55..c3e6ae5ec 100644 --- a/Zotlabs/Module/Conversation.php +++ b/Zotlabs/Module/Conversation.php @@ -16,7 +16,7 @@ class Conversation extends Controller { public function init() { - if (ActivityStreams::is_as_request()) { + if (ActivityStreams::is_as_request() || Libzot::is_zot_request()) { $item_id = argv(1); if (!$item_id) { @@ -77,7 +77,7 @@ class Conversation extends Controller { } } } - elseif (Config::get('system', 'require_authenticated_fetch', false)) { + elseif (Config::Get('system', 'require_authenticated_fetch', false)) { http_status_exit(403, 'Permission denied'); } diff --git a/Zotlabs/Module/Item.php b/Zotlabs/Module/Item.php index 5618137a1..c755fadd0 100644 --- a/Zotlabs/Module/Item.php +++ b/Zotlabs/Module/Item.php @@ -1029,18 +1029,44 @@ class Item extends Controller { } - if ($moderated) + if ($moderated) { $item_blocked = ITEM_MODERATED; + } - if (!strlen($verb)) + if (!strlen($verb)) { $verb = 'Create'; + } $notify_type = (($parent) ? 'comment-new' : 'wall-new'); $uuid = $uuid ?? $message_id ?? item_message_id(); $mid = $mid ?? z_root() . '/item/' . $uuid; + + // Set the conversation target. + if (empty($owner_hash)) { + $owner_hash = $owner_xchan['xchan_hash']; + } + + if ($owner_hash === $channel['channel_hash']) { + $attributedTo = z_root() . '/channel/' . $channel['channel_address']; + + $conversation = isset($parent_item) ? $parent_item['mid'] : $mid; + $datarray['target'] = [ + 'id' => str_replace('/item/', '/conversation/', $conversation), + 'type' => 'Collection', + 'attributedTo' => $attributedTo, + ]; + $datarray['tgt_type'] = 'Collection'; + } + elseif (!empty($parent_item['target'])) { + $datarray['target'] = $parent_item['target']; + $datarray['tgt_type'] = $parent_item['tgt_type']; + } + + + if ($is_poll) { $poll = [ 'question' => $body, @@ -1375,8 +1401,12 @@ class Item extends Controller { $nopush = false; } - if (!$nopush) - Master::Summon(['Notifier', $notify_type, $post_id]); + if (!$nopush) { + Master::Summon(['Notifier', $notify_type, $post_id]); + if (intval($post['approval_id'])) { + Master::Summon(['Notifier', $notify_type, $post['approval_id']]); + } + } logger('post_complete'); diff --git a/include/items.php b/include/items.php index 23222892f..fa699148a 100644 --- a/include/items.php +++ b/include/items.php @@ -431,8 +431,9 @@ function add_source_route($iid, $hash) { * * \e boolean \b success true or false * * \e array \b activity the resulting activity if successful */ -function post_activity_item($arr, $allow_code = false, $deliver = true) { + +function post_activity_item($arr, $allow_code = false, $deliver = true, $channel = null, $observer = null, $addAndSync = true) { $ret = array('success' => false); $is_comment = false; @@ -446,8 +447,13 @@ function post_activity_item($arr, $allow_code = false, $deliver = true) { if(! array_key_exists('item_thread_top',$arr) && (! $is_comment)) $arr['item_thread_top'] = 1; - $channel = App::get_channel(); - $observer = App::get_observer(); + if (!$channel) { + $channel = App::get_channel(); + } + + if (!$observer) { + $observer = App::get_observer(); + } $arr['aid'] = ((x($arr,'aid')) ? $arr['aid'] : $channel['channel_account_id']); $arr['uid'] = ((x($arr,'uid')) ? $arr['uid'] : $channel['channel_id']); @@ -493,6 +499,14 @@ function post_activity_item($arr, $allow_code = false, $deliver = true) { $arr['plink'] = $arr['mid']; } + if (!$arr['target']) { + $arr['target'] = [ + 'id' => str_replace('/item/', '/conversation/', $arr['parent_mid']), + 'type' => 'Collection', + 'attributedTo' => z_root() . '/channel/' . $channel['channel_address'], + ]; + $arr['tgt_type'] = 'Collection'; + } // for the benefit of plugins, we will behave as if this is an API call rather than a normal online post @@ -509,29 +523,32 @@ function post_activity_item($arr, $allow_code = false, $deliver = true) { return $ret; } - $post = item_store($arr,$allow_code,$deliver); + $post = item_store($arr, $allow_code, $deliver, $addAndSync); - if($post['success']) { - $post_id = $post['item_id']; - $ret['success'] = true; - $ret['item_id'] = $post_id; - $ret['activity'] = $post['item']; +hz_syslog('xxx: ' . print_r($post, true)); - /** - * @hooks post_local_end - * Called after a local post operation has completed. - * * \e array - the item returned from item_store() - */ - call_hooks('post_local_end', $ret['activity']); - } - else + if (!$post['success']) { return $ret; - - if($post_id && $deliver) { - Master::Summon(['Notifier','activity', $post_id]); } + $post_id = $post['item_id']; $ret['success'] = true; + $ret['item_id'] = $post_id; + $ret['activity'] = $post['item']; + + /** + * @hooks post_local_end + * Called after a local post operation has completed. + * * \e array - the item returned from item_store() + */ + call_hooks('post_local_end', $ret['activity']); + + if($post_id && $deliver) { + Master::Summon(['Notifier', 'activity', $post_id]); + if (!empty($post['approval_id'])) { + Master::Summon(['Notifier', 'activity', $post['approval_id']]); + } + } return $ret; } @@ -1594,7 +1611,7 @@ function item_json_encapsulate($arr, $k) { * * \e boolean \b success * * \e int \b item_id */ -function item_store($arr, $allow_exec = false, $deliver = true) { +function item_store($arr, $allow_exec = false, $deliver = true, $addAndSync = true) { $d = [ 'item' => $arr, @@ -2052,6 +2069,13 @@ function item_store($arr, $allow_exec = false, $deliver = true) { Master::Summon([ 'Cache_embeds', $current_post ]); } + $ret['success'] = true; + $ret['item_id'] = $current_post; + + if ($addAndSync) { + $ret = addToCollectionAndSync($ret); + } + // If _creating_ a deleted item, don't propagate it further or send out notifications. // We need to store the item details just in case the delete came in before the original post, // so that we have an item in the DB that's marked deleted and won't store a fresh post @@ -2062,9 +2086,6 @@ function item_store($arr, $allow_exec = false, $deliver = true) { tag_deliver($arr['uid'],$current_post); } - $ret['success'] = true; - $ret['item_id'] = $current_post; - return $ret; } @@ -2077,7 +2098,7 @@ function item_store($arr, $allow_exec = false, $deliver = true) { * @param boolean $deliver (optional) default true * @return array */ -function item_store_update($arr, $allow_exec = false, $deliver = true) { +function item_store_update($arr, $allow_exec = false, $deliver = true, $addAndSync = true) { $d = [ 'item' => $arr, @@ -2390,17 +2411,19 @@ function item_store_update($arr, $allow_exec = false, $deliver = true) { Master::Summon([ 'Cache_embeds', $orig_post_id ]); } + $ret['success'] = true; + $ret['item_id'] = $orig_post_id; - + if ($addAndSync) { + $ret = addToCollectionAndSync($ret); + } if($deliver) { - send_status_notifications($orig_post_id,$arr); + // don't send notify_comment for edits + // send_status_notifications($orig_post_id,$arr); tag_deliver($uid,$orig_post_id); } - $ret['success'] = true; - $ret['item_id'] = $orig_post_id; - return $ret; } @@ -3101,6 +3124,11 @@ function i_am_mentioned($channel, $item, $check_groups = false) { */ function start_delivery_chain($channel, $item, $item_id, $parent, $group = false, $edit = false) { + if ($item['author_xchan'] === $channel['channel_hash'] && in_array($item['verb'], ['Add', 'Remove'])) { + logger('delivery chain already started'); + return; + } + $sourced = check_item_source($channel['channel_id'],$item); if($sourced) { @@ -3362,7 +3390,7 @@ function start_delivery_chain($channel, $item, $item_id, $parent, $group = false $arr['deny_gid'] = $channel['channel_deny_gid']; $arr['comment_policy'] = map_scope(PermissionLimits::Get($channel['channel_id'],'post_comments')); - $post = item_store($arr); + $post = item_store($arr, deliver: false, addAndSync: false); $post_id = $post['item_id']; if ($post_id) { @@ -5092,7 +5120,7 @@ function fix_attached_permissions($uid, $body, $str_contact_allow, $str_group_al * which will allow you to interact with it. */ -function copy_of_pubitem($channel,$mid) { +function copy_of_pubitem($channel, $mid) { $result = null; $syschan = get_sys_channel(); @@ -5134,12 +5162,68 @@ function copy_of_pubitem($channel,$mid) { $rv['item_wall'] = 0; $rv['item_origin'] = 0; - $x = item_store($rv); + $x = item_store($rv, deliver: false, addAndSync: false); if($x['item_id'] && $x['item']['mid'] === $mid) { $result = $x['item']; + sync_an_item($channel['channel_id'], $x['item_id']); } } } return $result; } + +function addToCollectionAndSync($ret) { + if (!$ret['success']) { + return $ret; + } + + $channel = channelx_by_n($ret['item']['uid']); + if ($channel && $channel['channel_hash'] === $ret['item']['owner_xchan']) { + $items = [$ret['item']]; + if ((int)$items[0]['item_blocked'] === ITEM_MODERATED + || (int)$items[0]['item_unpublished'] || (int)$items[0]['item_delayed']) { + return $ret; + } + xchan_query($items); + $items = fetch_post_tags($items); + $sync_items = []; + $sync_items[] = encode_item($items[0], true); + + if (!in_array($ret['item']['verb'], ['Add', 'Remove'])) { + + $newObj = Activity::build_packet(Activity::encode_activity($items[0]), $channel, false); + $approval = Activity::addToCollection($channel, $newObj, $ret['item']['parent_mid'], $ret['item'], deliver: false); +//hz_syslog(print_r($approval, true)); + if ($approval['success']) { + $ret['approval_id'] = $approval['item_id']; + $ret['approval'] = $approval['activity']; + $addItems = [$approval['activity']]; + xchan_query($addItems); + $addItems = fetch_post_tags($addItems); + $sync_items[] = encode_item($addItems[0], true); + } + } + + $resource_type = $ret['item']['resource_type']; + + if ($resource_type === 'event') { + $z = q("select * from event where event_hash = '%s' and uid = %d limit 1", + dbesc($ret['item']['resource_id']), + intval($channel['channel_id']) + ); + + if ($z) { + Libsync::build_sync_packet($channel['channel_id'], ['event_item' => $sync_items, 'event' => $z]); + } + } + elseif ($resource_type === 'photo') { + // reserved for future use, currently handled in the photo upload workflow + } + else { + Libsync::build_sync_packet($ret['item']['uid'], ['item' => $sync_items]); + } + } + + return $ret; +} |