diff options
Diffstat (limited to 'include/items.php')
-rw-r--r-- | include/items.php | 674 |
1 files changed, 448 insertions, 226 deletions
diff --git a/include/items.php b/include/items.php index a5a23650e..1f3671c83 100644 --- a/include/items.php +++ b/include/items.php @@ -4,6 +4,7 @@ * @brief Items related functions. */ +use Zotlabs\Lib\Config; use Zotlabs\Lib\Crypto; use Zotlabs\Lib\Enotify; use Zotlabs\Lib\MarkdownSoap; @@ -35,10 +36,10 @@ require_once('include/permissions.php'); * @param boolean $include_groups * @return array containing the recipients */ -function collect_recipients($item, &$private_envelope,$include_groups = true) { +function collect_recipients($item, &$private_envelope, $include_groups = true) { $private_envelope = ((intval($item['item_private'])) ? true : false); - $recipients = array(); + $recipients = []; if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid']) { @@ -53,8 +54,15 @@ function collect_recipients($item, &$private_envelope,$include_groups = true) { $allow_groups = []; } - $raw_recipients = array_unique(array_merge($allow_people, $allow_groups)); - $recipients = deliverable_abook_xchans($item['uid'], $raw_recipients); + $recipients = array_unique(array_merge($allow_people, $allow_groups)); + + if ($recipients) { + // deliverable_abook_xchans() will return all deliverable xchans + // if passed an empty array as 2nd argument (no filtering). + // Hence only call it if we do actually have any recipients. + $recipients = deliverable_abook_xchans($item['uid'], $recipients); + } + // if you specifically deny somebody but haven't allowed anybody, we'll allow everybody in your // address book minus the denied connections. The post is still private and can't be seen publicly @@ -250,6 +258,25 @@ function item_normal() { return $sql; } +function item_forwardable($item) { + if (intval($item['item_unpublished']) || + intval($item['item_delayed']) || + intval($item['item_blocked']) || + intval($item['item_hidden']) || + intval($item['item_restrict']) || // this might change in the future + // internal follow/unfollow thread + in_array($item['verb'], ['Follow', 'Ignore', ACTIVITY_FOLLOW, ACTIVITY_UNFOLLOW]) || + str_contains($item['postopts'], 'nodeliver') || + // actor not fetchable + (isset($item['author']['xchan_network']) && in_array($item['author']['xchan_network'], ['rss', 'anon', 'token'])) + + ) { + return false; + } + + return true; +} + function item_normal_search() { return " and item.item_hidden = 0 and item.item_type in (0,3,6,7) and item.item_deleted = 0 and item.item_unpublished = 0 and item.item_delayed = 0 and item.item_pending_remove = 0 @@ -353,7 +380,7 @@ function can_comment_on_post($observer_xchan, $item) { case 'specific': case 'contacts': case '': - if(local_channel() && get_abconfig(local_channel(), (($item['verb'] === ACTIVITY_SHARE) ? $item['source_xchan'] : $item['owner_xchan']), 'their_perms', 'post_comments')) { + if(local_channel() && get_abconfig(local_channel(), $item['owner_xchan'], 'their_perms', 'post_comments')) { return true; } if(intval($item['item_wall']) && perm_is_allowed($item['uid'],$observer_xchan,'post_comments')) { @@ -423,8 +450,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; @@ -438,8 +466,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']); @@ -485,6 +518,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 @@ -501,29 +542,30 @@ function post_activity_item($arr, $allow_code = false, $deliver = true) { return $ret; } - $post = item_store($arr,$allow_code,$deliver); - - if($post['success']) { - $post_id = $post['item_id']; - $ret['success'] = true; - $ret['item_id'] = $post_id; - $ret['activity'] = $post['item']; + $post = item_store($arr, $allow_code, $deliver, $addAndSync); - /** - * @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; } @@ -1078,7 +1120,7 @@ function encode_item($item,$mirror = false,$zap_compat = false) { $x['type'] = 'activity'; $x['encoding'] = 'zot'; - $key = get_config('system','prvkey'); + $key = Config::Get('system','prvkey'); // If we're trying to backup an item so that it's recoverable or for export/imprt, // add all the attributes we need to recover it @@ -1586,7 +1628,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, @@ -1768,16 +1810,6 @@ function item_store($arr, $allow_exec = false, $deliver = true) { if((! array_key_exists('item_nocomment',$arr)) && ($arr['comment_policy'] == 'none')) $arr['item_nocomment'] = 1; - // handle time travelers - // Allow a bit of fudge in case somebody just has a slightly slow/fast clock - - $d1 = new DateTime('now +10 minutes', new DateTimeZone('UTC')); - $d2 = new DateTime($arr['created'] . '+00:00'); - - if($d2 > $d1) { - $arr['item_delayed'] = 1; - } - if(empty($arr['llink'])) { $arr['llink'] = z_root() . '/display/' . $arr['uuid']; } @@ -1819,7 +1851,7 @@ function item_store($arr, $allow_exec = false, $deliver = true) { ); } - if(comments_are_now_closed($r[0])) { + if(comments_are_now_closed($r[0]) && !in_array($arr['verb'], ['Add', 'Remove'])) { logger('item_store: comments closed'); $ret['message'] = 'Comments closed.'; return $ret; @@ -1874,7 +1906,7 @@ function item_store($arr, $allow_exec = false, $deliver = true) { $arr['item_private'] = 0; if(in_array($arr['obj_type'], ['Note','Answer']) && $r[0]['obj_type'] === 'Question' && intval($r[0]['item_wall'])) { - Activity::update_poll($r[0]['id'], $arr); + Activity::update_poll($r[0], $arr); } } @@ -2044,6 +2076,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 @@ -2054,9 +2093,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; } @@ -2069,7 +2105,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, @@ -2203,7 +2239,7 @@ function item_store_update($arr, $allow_exec = false, $deliver = true) { $arr['revision'] = ((x($arr,'revision') && $arr['revision'] > 0) ? intval($arr['revision']) : 0); - if(array_key_exists('comments_closed',$arr) && $arr['comments_closed'] > NULL_DATE) + if(array_key_exists('comments_closed',$arr)) $arr['comments_closed'] = datetime_convert('UTC','UTC',$arr['comments_closed']); else $arr['comments_closed'] = $orig[0]['comments_closed']; @@ -2382,17 +2418,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; } @@ -2696,7 +2734,7 @@ function tag_deliver($uid, $item_id) { logger('Post mentions: ' . print_r($terms,true), LOGGER_DATA); } - $max_forums = get_config('system','max_tagged_forums',2); + $max_forums = Config::Get('system','max_tagged_forums',2); $matched_forums = 0; @@ -2943,35 +2981,32 @@ function tgroup_check($uid, $item) { // post to group via DM if ($is_group) { - if (intval($item['item_private']) === 2 && $item['mid'] === $item['parent_mid']) { + if (intval($item['item_private']) === 2 && $item['mid'] === $item['parent_mid'] && perm_is_allowed($uid, $item['owner_xchan'], 'post_wall')) { return true; } } - // see if we already have this item. Maybe it is being updated. $r = q("select id from item where mid = '%s' and uid = %d limit 1", dbesc($item['mid']), intval($uid) ); - if($r) - return true; - if(! perm_is_allowed($uid,$item['author_xchan'],'tag_deliver')) - return false; + if ($r) { + return true; + } - $u = q("select * from channel left join xchan on channel_hash = xchan_hash where channel_id = %d limit 1", - intval($uid) - ); + $u = channelx_by_n($uid); - if(! $u) + if (!$u) { return false; + } - $max_forums = get_config('system','max_tagged_forums',2); + $max_forums = Config::Get('system','max_tagged_forums',2); $matched_forums = 0; - $link = normalise_link($u[0]['xchan_url']); + $link = normalise_link($u['xchan_url']); $terms = []; @@ -2988,7 +3023,7 @@ function tgroup_check($uid, $item) { } $mention = true; - logger('tgroup_check: mention found for ' . $u[0]['channel_name']); + logger('tgroup_check: mention found for ' . $u['channel_name']); // At this point we've determined that the person receiving this post was mentioned in it. // Now let's check if this mention was inside a reshare so we don't spam a forum @@ -3073,7 +3108,7 @@ function i_am_mentioned($channel, $item, $check_groups = false) { } } } - $unless = intval(get_pconfig($channel['channel_id'], 'system', 'unless_mention_count', get_config('system', 'unless_mention_count', 20))); + $unless = intval(get_pconfig($channel['channel_id'], 'system', 'unless_mention_count', Config::Get('system', 'unless_mention_count', 20))); if ($unless && $terms && count($terms) > $unless) { $tagged = false; } @@ -3096,6 +3131,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) { @@ -3141,17 +3181,113 @@ function start_delivery_chain($channel, $item, $item_id, $parent, $group = false $item['parent_mid'] = $item['mid']; $item['thr_parent'] = $item['mid']; $item['llink'] = z_root() . '/display/' . $item['uuid']; + $item['target'] = json_encode([ + 'id' => str_replace('/item/', '/conversation/', $item['mid']), + 'type' => 'Collection', + 'attributedTo' => z_root() . '/channel/' . $channel['channel_address'] + ]); + $item['tgt_type'] = 'Collection'; } + } - $r = q("UPDATE item SET author_xchan = '%s', mid = '%s', parent_mid = '%s', thr_parent = '%s', llink = '%s' WHERE id = %d", - dbesc($item['author_xchan']), - dbesc($item['mid']), - dbesc($item['parent_mid']), - dbesc($item['thr_parent']), - dbesc($item['llink']), + $private = (($channel['channel_allow_cid'] || $channel['channel_allow_gid'] + || $channel['channel_deny_cid'] || $channel['channel_deny_gid']) ? 1 : 0); + + $new_public_policy = map_scope(PermissionLimits::Get($channel['channel_id'],'view_stream'),true); + + if((! $private) && $new_public_policy) + $private = 1; + + $item_wall = 1; + $item_origin = (($item['item_deleted']) ? 0 : 1); // item_origin for deleted items is set to 0 in delete_imported_item() to prevent looping. In this case we probably should not set it back to 1 here. + $item_uplink = 0; + $item_nocomment = 0; + + $flag_bits = $item['item_flags']; + + // maintain the original source, which will be the original item owner and was stored in source_xchan + // when we created the delivery fork + + if($parent) { + $r = q("update item set source_xchan = '%s' where id = %d", + dbesc($parent['source_xchan']), + intval($item_id) + ); + } + else { + $item_uplink = (($item['item_rss']) ? 0 : 1); // Do not set item_uplink for rss items - we can not send anything to them. + + // if this is an edit, item_store_update() will have already updated the item + // with the correct value for source_xchan (by ignoring it). We cannot set to owner_xchan + // in this case because owner_xchan will point to the parent of this chain + // and not the original sender. + + if(!$edit) { + $r = q("update item set source_xchan = owner_xchan where id = %d", + intval($item_id) + ); + } + } + + // this will not work with item_store_update() + + $r = q("update item set item_uplink = %d, item_nocomment = %d, item_flags = %d, owner_xchan = '%s', allow_cid = '%s', allow_gid = '%s', + deny_cid = '%s', deny_gid = '%s', item_private = %d, public_policy = '%s', comment_policy = '%s', title = '%s', body = '%s', item_wall = %d, item_origin = %d, + author_xchan = '%s', mid = '%s', parent_mid = '%s', thr_parent = '%s', llink = '%s', target = '%s', tgt_type = '%s' where id = %d", + intval($item_uplink), + intval($item_nocomment), + intval($flag_bits), + dbesc($channel['channel_hash']), + dbesc($channel['channel_allow_cid']), + dbesc($channel['channel_allow_gid']), + dbesc($channel['channel_deny_cid']), + dbesc($channel['channel_deny_gid']), + intval($private), + dbesc($new_public_policy), + dbesc(map_scope(PermissionLimits::Get($channel['channel_id'],'post_comments'))), + dbesc($item['title']), + dbesc($item['body']), + intval($item_wall), + intval($item_origin), + dbesc($item['author_xchan']), + dbesc($item['mid']), + dbesc($item['parent_mid']), + dbesc($item['thr_parent']), + dbesc($item['llink']), + dbesc($item['target']), + dbesc($item['tgt_type']), + intval($item_id) + ); + + if($r) { + $rr = q("select * from item where id = %d", intval($item_id) ); + + if ($rr) { + + // this is hackish but since we can not use item_store_update() here, + // we will prepare a similar output to feed to addToCollectionAndSync() + $ret['success'] = 1; + $ret['item_id'] = $rr[0]['id']; + $ret['item'] = $rr[0]; + + $result = addToCollectionAndSync($ret); + + Master::Summon(['Notifier', 'tgroup', $result['item_id']]); + if ($result['approval_id']) { + Master::Summon(['Notifier', 'tgroup', $result['approval_id']]); + } + } } + else { + logger('start_delivery_chain: failed to update item'); + // reset the source xchan to prevent loops + $r = q("update item set source_xchan = '' where id = %d", + intval($item_id) + ); + } + return; } if ($group && (! $parent)) { @@ -3173,8 +3309,8 @@ function start_delivery_chain($channel, $item, $item_id, $parent, $group = false if ($r) { if (intval($item['item_deleted'])) { - drop_item($r[0]['id'], false, DROPITEM_PHASE1); - Master::Summon([ 'Notifier', 'drop', $r[0]['id'] ]); + drop_item($r[0]['id'], DROPITEM_PHASE1, uid: $r[0]['uid']); + Master::Summon(['Notifier', 'drop' ,$r[0]['id']]); return; } $arr['id'] = intval($r[0]['id']); @@ -3191,9 +3327,14 @@ function start_delivery_chain($channel, $item, $item_id, $parent, $group = false } else { - $arr['uuid'] = item_message_id(); + // To prevent duplicates from possible clones of the forum/group, + // we will create a v5 UUID of the source item mid. + // Add some extra entropy to prevent duplicate UUIDs with items where we already + // created an UUID from the mid (activities which do not provide an UUID field). + $arr['uuid'] = uuid_from_url($item['mid'] . '#group_item'); $arr['mid'] = z_root() . '/item/' . $arr['uuid']; $arr['parent_mid'] = $arr['mid']; + $arr['plink'] = $arr['mid']; } $arr['aid'] = $channel['channel_account_id']; @@ -3254,8 +3395,15 @@ function start_delivery_chain($channel, $item, $item_id, $parent, $group = false } $arr['title'] = $item['title']; - $arr['tgt_type'] = $item['tgt_type']; - $arr['target'] = $item['target']; +// $arr['tgt_type'] = $item['tgt_type']; +// $arr['target'] = $item['target']; + + $arr['tgt_type'] = 'Collection'; + $arr['target'] = [ + 'id' => str_replace('/item/', '/conversation/', $arr['parent_mid']), + 'type' => 'Collection', + 'attributedTo' => channel_url($channel['channel_address']) + ]; $arr['term'] = $item['term']; @@ -3278,10 +3426,14 @@ function start_delivery_chain($channel, $item, $item_id, $parent, $group = false $post = item_store($arr); } - $post_id = $post['item_id']; + $post_id = $post['item_id'] ?? 0; + $approval_id = $post['approval_id'] ?? 0; if($post_id) { Master::Summon([ 'Notifier','tgroup',$post_id ]); + if ($approval_id) { + Master::Summon(['Notifier', 'tgroup', $approval_id]); + } } return; } @@ -3306,14 +3458,14 @@ function start_delivery_chain($channel, $item, $item_id, $parent, $group = false if ($edit) { if (intval($item['item_deleted'])) { - drop_item($item['id'],false,DROPITEM_PHASE1); - Master::Summon([ 'Notifier','drop',$item['id'] ]); + drop_item($item['id'], DROPITEM_PHASE1, uid: $item['uid']); + Master::Summon(['Notifier', 'drop', $item['id']]); return; } return; } else { - $arr['uuid'] = item_message_id(); + $arr['uuid'] = uuid_from_url($item['mid']); $arr['mid'] = z_root() . '/activity/' . $arr['uuid']; $arr['parent_mid'] = $item['parent_mid']; //IConfig::Set($arr,'activitypub','context', str_replace('/item/','/conversation/',$item['parent_mid'])); @@ -3357,12 +3509,12 @@ 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_id = $post['item_id']; + $post = item_store($arr, deliver: false, addAndSync: false); + $post_id = $post['item_id'] ?? 0; - if ($post_id) { - Master::Summon([ 'Notifier','tgroup',$post_id ]); - } + if ($post_id) { + Master::Summon(['Notifier', 'tgroup', $post_id]); + } q("update channel set channel_lastpost = '%s' where channel_id = %d", dbesc(datetime_convert()), @@ -3372,81 +3524,6 @@ function start_delivery_chain($channel, $item, $item_id, $parent, $group = false return; } - - // Change this copy of the post to a forum head message and deliver to all the tgroup members - // also reset all the privacy bits to the forum default permissions - - $private = (($channel['channel_allow_cid'] || $channel['channel_allow_gid'] - || $channel['channel_deny_cid'] || $channel['channel_deny_gid']) ? 1 : 0); - - $new_public_policy = map_scope(PermissionLimits::Get($channel['channel_id'],'view_stream'),true); - - if((! $private) && $new_public_policy) - $private = 1; - - $item_wall = 1; - $item_origin = (($item['item_deleted']) ? 0 : 1); // item_origin for deleted items is set to 0 in delete_imported_item() to prevent looping. In this case we probably should not set it back to 1 here. - $item_uplink = 0; - $item_nocomment = 0; - - $flag_bits = $item['item_flags']; - - // maintain the original source, which will be the original item owner and was stored in source_xchan - // when we created the delivery fork - - if($parent) { - $r = q("update item set source_xchan = '%s' where id = %d", - dbesc($parent['source_xchan']), - intval($item_id) - ); - } - else { - $item_uplink = (($item['item_rss']) ? 0 : 1); // Do not set item_uplink for rss items - we can not send anything to them. - - // if this is an edit, item_store_update() will have already updated the item - // with the correct value for source_xchan (by ignoring it). We cannot set to owner_xchan - // in this case because owner_xchan will point to the parent of this chain - // and not the original sender. - - if(! $edit) { - $r = q("update item set source_xchan = owner_xchan where id = %d", - intval($item_id) - ); - } - } - - $title = $item['title']; - $body = $item['body']; - - $r = q("update item set item_uplink = %d, item_nocomment = %d, item_flags = %d, owner_xchan = '%s', allow_cid = '%s', allow_gid = '%s', - deny_cid = '%s', deny_gid = '%s', item_private = %d, public_policy = '%s', comment_policy = '%s', title = '%s', body = '%s', item_wall = %d, item_origin = %d where id = %d", - intval($item_uplink), - intval($item_nocomment), - intval($flag_bits), - dbesc($channel['channel_hash']), - dbesc($channel['channel_allow_cid']), - dbesc($channel['channel_allow_gid']), - dbesc($channel['channel_deny_cid']), - dbesc($channel['channel_deny_gid']), - intval($private), - dbesc($new_public_policy), - dbesc(map_scope(PermissionLimits::Get($channel['channel_id'],'post_comments'))), - dbesc($title), - dbesc($body), - intval($item_wall), - intval($item_origin), - intval($item_id) - ); - - if($r) - Master::Summon([ 'Notifier','tgroup',$item_id ]); - else { - logger('start_delivery_chain: failed to update item'); - // reset the source xchan to prevent loops - $r = q("update item set source_xchan = '' where id = %d", - intval($item_id) - ); - } } /** @@ -3762,7 +3839,7 @@ function item_expire($uid,$days,$comment_days = 7) { $sql_extra = ((intval($expire_network_only)) ? " AND item_wall = 0 " : ""); - $expire_limit = get_config('system','expire_limit', 1000); + $expire_limit = Config::Get('system','expire_limit', 1000); $item_normal = item_normal(); @@ -3785,7 +3862,7 @@ function item_expire($uid,$days,$comment_days = 7) { if ($r) { foreach ($r as $item) { - drop_item($item['id'], false); + drop_item($item['id'], uid: $uid); } } @@ -3798,25 +3875,12 @@ function retain_item($id) { ); } -function drop_items($items,$interactive = false,$stage = DROPITEM_NORMAL) { - $uid = 0; - - if(! local_channel() && ! remote_channel()) - return; - - if(count($items)) { +function drop_items($items, $stage = DROPITEM_NORMAL, $force = false, $expire = false) { + if ($items) { foreach($items as $item) { - $owner = drop_item($item,$interactive,$stage); - if($owner && ! $uid) - $uid = $owner; + drop_item($item, $stage, $force, expire: $expire); } } - - // multiple threads may have been deleted, send an expire notification - - if($uid) { - Master::Summon([ 'Notifier','expire',$uid ]); - } } @@ -3829,7 +3893,7 @@ function drop_items($items,$interactive = false,$stage = DROPITEM_NORMAL) { // $stage = 1 => set deleted flag on the item and perform intial notifications // $stage = 2 => perform low level delete at a later stage -function drop_item($id,$interactive = true,$stage = DROPITEM_NORMAL) { +function drop_item($id, $stage = DROPITEM_NORMAL, $force = false, $uid = 0, $observer_hash = '', $expire = false, $recurse = false) { // locate item to be deleted @@ -3837,33 +3901,48 @@ function drop_item($id,$interactive = true,$stage = DROPITEM_NORMAL) { intval($id) ); - if((! $r) || (intval($r[0]['item_deleted']) && ($stage === DROPITEM_NORMAL))) { - if(! $interactive) - return 0; - notice( t('Item not found.') . EOL); - //goaway(z_root() . '/' . $_SESSION['return_url']); + if(!$r || (intval($r[0]['item_deleted']) && $stage === DROPITEM_NORMAL)) { + return false; } $item = $r[0]; - $ok_to_delete = false; + if(!$recurse) { + drop_related($item, $stage, $force, $uid, $observer_hash, $expire); + } - // system deletion - if(! $interactive) - $ok_to_delete = true; + $ok_to_delete = false; // admin deletion - if(is_site_admin()) + if(is_site_admin()) { $ok_to_delete = true; + } // owner deletion - if(local_channel() && local_channel() == $item['uid']) + if(local_channel() && local_channel() == $item['uid']) { + $ok_to_delete = true; + } + + // remote delete when nobody is authenticated (called from Libzot and Daemons) + if ($uid && intval($uid) === intval($item['uid'])) { $ok_to_delete = true; + } // author deletion - $observer = App::get_observer(); - if($observer && $observer['xchan_hash'] && ($observer['xchan_hash'] === $item['author_xchan'])) + if ($observer_hash) { + $observer = ['xchan_hash' => $observer_hash]; + } + else { + $observer = App::get_observer(); + } + + if (isset($observer['xchan_hash']) && $observer['xchan_hash'] === $item['author_xchan']) { + $ok_to_delete = true; + } + + if (isset($observer['xchan_hash']) && $observer['xchan_hash'] === $item['owner_xchan']) { $ok_to_delete = true; + } if($ok_to_delete) { @@ -3876,9 +3955,9 @@ function drop_item($id,$interactive = true,$stage = DROPITEM_NORMAL) { $arr = [ 'item' => $item, - 'interactive' => $interactive, 'stage' => $stage ]; + /** * @hooks drop_item * Called when an 'item' is removed. @@ -3901,30 +3980,95 @@ function drop_item($id,$interactive = true,$stage = DROPITEM_NORMAL) { delete_item_lowlevel($item, $stage); } - if(! $interactive) - return 1; + return true; + } + else { + return false; + } +} - // send the notification upstream/downstream as the case may be - // only send notifications to others if this is the owner's wall item. - // This isn't optimal. We somehow need to pass to this function whether or not - // to call the notifier, or we need to call the notifier from the calling function. - // We'll rely on the undocumented behaviour that DROPITEM_PHASE1 is (hopefully) only - // set if we know we're going to send delete notifications out to others. +// If somebody deletes a 'Create' activity, find any associated 'Add/Collection' +// activity and delete it. And vice versa. - if((intval($item['item_wall']) && ($stage != DROPITEM_PHASE2)) || ($stage == DROPITEM_PHASE1)) { - Master::Summon([ 'Notifier','drop',$notify_id ]); +function drop_related($item, $stage = DROPITEM_NORMAL, $force = false, $uid = 0, $observer_hash = '', $expire = false, $recurse = false) { + $allRelated = q("select * from item where parent_mid = '%s' and uid = %d", + dbesc($item['parent_mid']), + intval($item['uid']) + ); + if (! $allRelated) { + return; + } + if ($item['verb'] === 'Add' && $item['tgt_type'] === 'Collection') { + if (is_array($item['obj'])) { + $thisItem = $item['obj']; + } + else { + $thisItem = json_decode($item['obj'], true); + } + if (isset($thisItem['object']['id'])) { + $targetMid = $thisItem['object']['id']; + } + if (!$targetMid) { + return; + } + foreach ($allRelated as $related) { + if ($related['mid'] === $targetMid) { + drop_item($related['id'], $stage, $force, $uid, $observer_hash, $expire, recurse: true); + break; + } } - //goaway(z_root() . '/' . $_SESSION['return_url']); } else { - if(! $interactive) - return 0; - notice( t('Permission denied.') . EOL); - //goaway(z_root() . '/' . $_SESSION['return_url']); + foreach ($allRelated as $related) { + if ($related['verb'] === 'Add' && str_contains($related['tgt_type'], 'Collection')) { + $thisItem = json_decode($related['obj'], true); + if (isset($thisItem['id']) && $thisItem['id'] === str_replace('/item/', '/activity/', $item['mid'])) { + drop_item($related['id'], $stage, $force, $uid, $observer_hash, $expire, recurse: true); + break; + } + } + } + } +} + + +function find_related($item) { + $allRelated = q("select * from item where parent_mid = '%s' and uid = %d", + dbesc($item['parent_mid']), + intval($item['uid']) + ); + if (! $allRelated) { + return false; + } + if ($item['verb'] === 'Add' && $item['tgt_type'] === 'Collection') { + $thisItem = json_decode($item['obj'],true); + if (is_array($thisItem)) { + $targetMid = $thisItem['object']['id']; + } + if (!$targetMid) { + return false; + } + foreach ($allRelated as $related) { + if ($related['mid'] === $targetMid) { + return $related; + } + } } + else { + foreach ($allRelated as $related) { + if ($related['verb'] === 'Add' && str_contains($related['tgt_type'], 'Collection')) { + $thisItem = json_decode($related['obj'], true); + if (isset($thisItem['object']['id']) && $thisItem['object']['id'] === $item['mid']) { + return $related; + } + } + } + } + return false; } + /** * @warning This function does not check for permission and does not send * notifications and does not check recursion. @@ -5000,7 +5144,7 @@ function fix_attached_permissions($uid, $body, $str_contact_allow, $str_group_al $attach = array_shift($attach_q); - $new_public = !(($str_contact_allow || $str_group_allow || $str_contact_deny || $str_group_deny)); + //$new_public = !(($str_contact_allow || $str_group_allow || $str_contact_deny || $str_group_deny)); $existing_public = !(($attach['allow_cid'] || $attach['allow_gid'] || $attach['deny_cid'] || $attach['deny_gid'])); if ($existing_public) { @@ -5015,10 +5159,11 @@ function fix_attached_permissions($uid, $body, $str_contact_allow, $str_group_al continue; } - $item_private = 0; - - if ($new_public === false) { - $item_private = (($str_group_allow || ($str_contact_allow && substr_count($str_contact_allow,'<') > 2)) ? 1 : 2); + if ($token) { + $str_contact_allow = $attach['allow_cid']; + $str_group_allow = $attach['allow_gid']; + $str_contact_deny = $attach['deny_cid']; + $str_group_deny = $attach['deny_gid']; // preserve any existing tokens that may have been set for this file $token_matches = null; @@ -5046,7 +5191,7 @@ function fix_attached_permissions($uid, $body, $str_contact_allow, $str_group_al ); if ($attach['is_photo']) { - $r = q("UPDATE photo SET allow_cid = '%s', allow_gid = '%s', deny_cid = '%s', deny_gid = '%s' + q("UPDATE photo SET allow_cid = '%s', allow_gid = '%s', deny_cid = '%s', deny_gid = '%s' WHERE resource_id = '%s' AND uid = %d ", dbesc($str_contact_allow), dbesc($str_group_allow), @@ -5056,7 +5201,16 @@ function fix_attached_permissions($uid, $body, $str_contact_allow, $str_group_al intval($uid) ); - $r = q("UPDATE item SET allow_cid = '%s', allow_gid = '%s', deny_cid = '%s', deny_gid = '%s', item_private = %d + $item_private = 0; + + if ($str_group_allow || $str_contact_deny || $str_group_deny) { + $item_private = 1; + } + elseif ($str_contact_allow) { + $item_private = 2; + } + + q("UPDATE item SET allow_cid = '%s', allow_gid = '%s', deny_cid = '%s', deny_gid = '%s', item_private = %d WHERE resource_id = '%s' AND 'resource_type' = 'photo' AND uid = %d", dbesc($str_contact_allow), dbesc($str_group_allow), @@ -5077,7 +5231,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(); @@ -5095,36 +5249,104 @@ function copy_of_pubitem($channel,$mid) { return $item[0]; } - - $r = q("select * from item where parent_mid = (select parent_mid from item where mid = '%s' and uid = %d ) order by id ", + $r = q("select * from item where parent_mid = (select parent_mid from item where mid = '%s' and uid = %d) and uid = %d order by id ", dbesc($mid), + intval($syschan['channel_id']), intval($syschan['channel_id']) ); if($r) { $items = fetch_post_tags($r,true); foreach($items as $rv) { - $d = q("select id from item where mid = '%s' and uid = %d limit 1", - dbesc($rv['mid']), - intval($channel['channel_id']) - ); - if($d) { - continue; - } unset($rv['id']); unset($rv['parent']); + $rv['aid'] = $channel['channel_account_id']; $rv['uid'] = $channel['channel_id']; $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); + // TODO: fetch_post_tags() will add term and iconfig twice if called twice and it looks like they are already added here + // $items = fetch_post_tags($items); + $sync_items = []; + $sync_items[] = encode_item($items[0], true); + + if (!in_array($ret['item']['verb'], ['Add', 'Remove'])) { + $activity = Activity::encode_activity($items[0]); + + if (!$activity) { + return $ret; + } + + $new_obj = Activity::build_packet($activity, $channel, false); + $approval = Activity::addToCollection($channel, $new_obj, $ret['item']['parent_mid'], $ret['item'], deliver: false); + + if ($approval['success']) { + $ret['approval_id'] = $approval['item_id']; + $ret['approval'] = $approval['activity']; + $add_items = [$approval['activity']]; + xchan_query($add_items); + $add_items = fetch_post_tags($add_items); + $sync_items[] = encode_item($add_items[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; +} + +function reverse_activity_mid($string) { + return str_replace(z_root() . '/activity/', z_root() . '/item/', $string); +} + +function set_activity_mid($string) { + return str_replace(z_root() . '/item/', z_root() . '/activity/', $string); +} + |