aboutsummaryrefslogtreecommitdiffstats
path: root/include/items.php
diff options
context:
space:
mode:
Diffstat (limited to 'include/items.php')
-rw-r--r--include/items.php1283
1 files changed, 960 insertions, 323 deletions
diff --git a/include/items.php b/include/items.php
index dc9e9a7d9..b80c5672b 100644
--- a/include/items.php
+++ b/include/items.php
@@ -240,24 +240,46 @@ function comments_are_now_closed($item) {
return false;
}
-function item_normal() {
- $profile_uid = App::$profile['profile_uid'] ?? App::$profile_uid ?? null;
+function item_normal($profile_uid = null, $prefix = 'item') {
+ if ($profile_uid === null) {
+ $profile_uid = App::$profile['profile_uid'] ?? App::$profile_uid ?? null;
+ }
+
$uid = local_channel();
$is_owner = ($uid && intval($profile_uid) === $uid);
- $sql = " and item.item_hidden = 0 and item.item_type = 0 and item.item_deleted = 0
- and item.item_unpublished = 0 and item.item_pending_remove = 0";
+ $sql = " and $prefix.item_hidden = 0 and $prefix.item_type = 0 and $prefix.item_deleted = 0
+ and $prefix.item_unpublished = 0 and $prefix.item_pending_remove = 0";
if ($is_owner) {
- $sql .= " and item.item_blocked IN (0, " . intval(ITEM_MODERATED) . ") and item.item_delayed IN (0, 1) ";
+ $sql .= " and $prefix.item_blocked IN (0, " . intval(ITEM_MODERATED) . ") and $prefix.item_delayed IN (0, 1) ";
}
else {
- $sql .= " and item.item_blocked = 0 and item.item_delayed = 0 ";
+ $sql .= " and $prefix.item_blocked = 0 and $prefix.item_delayed = 0 ";
}
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
@@ -361,7 +383,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')) {
@@ -431,12 +453,13 @@ 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;
- if((($arr['parent']) && $arr['parent'] != $arr['id']) || (($arr['parent_mid']) && $arr['parent_mid'] != $arr['mid']))
+ if((isset($arr['parent'], $arr['id']) && intval($arr['parent']) !== intval($arr['id'])) || (isset($arr['parent_mid'], $arr['mid']) && $arr['parent_mid'] !== $arr['mid']))
$is_comment = true;
if(! array_key_exists('item_origin',$arr))
@@ -446,11 +469,16 @@ 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']);
+ $arr['aid'] = ((!empty($arr['aid'])) ? $arr['aid'] : $channel['channel_account_id']);
+ $arr['uid'] = ((!empty($arr['uid'])) ? $arr['uid'] : $channel['channel_id']);
if(! perm_is_allowed($arr['uid'],$observer['xchan_hash'],(($is_comment) ? 'post_comments' : 'post_wall'))) {
$ret['message'] = t('Permission denied');
@@ -466,18 +494,18 @@ function post_activity_item($arr, $allow_code = false, $deliver = true) {
$arr['mimetype'] = 'text/bbcode';
- if(! $arr['mid']) {
- $arr['uuid'] = ((x($arr,'uuid')) ? $arr['uuid'] : new_uuid());
+ if(empty($arr['mid'])) {
+ $arr['uuid'] = ((!empty($arr['uuid'])) ? $arr['uuid'] : new_uuid());
}
- $arr['mid'] = ((x($arr,'mid')) ? $arr['mid'] : z_root() . '/item/' . $arr['uuid']);
- $arr['parent_mid'] = ((x($arr,'parent_mid')) ? $arr['parent_mid'] : $arr['mid']);
- $arr['thr_parent'] = ((x($arr,'thr_parent')) ? $arr['thr_parent'] : $arr['mid']);
+ $arr['mid'] = ((!empty($arr['mid'])) ? $arr['mid'] : z_root() . '/item/' . $arr['uuid']);
+ $arr['parent_mid'] = ((!empty($arr['parent_mid'])) ? $arr['parent_mid'] : $arr['mid']);
+ $arr['thr_parent'] = ((!empty($arr['thr_parent'])) ? $arr['thr_parent'] : $arr['mid']);
- $arr['owner_xchan'] = ((x($arr,'owner_xchan')) ? $arr['owner_xchan'] : $channel['channel_hash']);
- $arr['author_xchan'] = ((x($arr,'author_xchan')) ? $arr['author_xchan'] : $observer['xchan_hash']);
+ $arr['owner_xchan'] = ((!empty($arr['owner_xchan'])) ? $arr['owner_xchan'] : $channel['channel_hash']);
+ $arr['author_xchan'] = ((!empty($arr['author_xchan'])) ? $arr['author_xchan'] : $observer['xchan_hash']);
- $arr['verb'] = ((x($arr,'verb')) ? $arr['verb'] : 'Create');
- $arr['obj_type'] = ((x($arr,'obj_type')) ? $arr['obj_type'] : 'Note');
+ $arr['verb'] = ((!empty($arr['verb'])) ? $arr['verb'] : 'Create');
+ $arr['obj_type'] = ((!empty($arr['obj_type'])) ? $arr['obj_type'] : 'Note');
if(! ( array_key_exists('allow_cid',$arr) || array_key_exists('allow_gid',$arr)
|| array_key_exists('deny_cid',$arr) || array_key_exists('deny_gid',$arr))) {
@@ -489,10 +517,18 @@ function post_activity_item($arr, $allow_code = false, $deliver = true) {
$arr['comment_policy'] = map_scope(PermissionLimits::Get($channel['channel_id'],'post_comments'));
- if ((! $arr['plink']) && (intval($arr['item_thread_top']))) {
+ if (empty($arr['plink']) && (intval($arr['item_thread_top']))) {
$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
@@ -504,34 +540,35 @@ function post_activity_item($arr, $allow_code = false, $deliver = true) {
*/
call_hooks('post_local', $arr);
- if(x($arr, 'cancel')) {
+ if (!empty($arr['cancel'])) {
logger('Post cancelled by plugin.');
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;
}
@@ -693,14 +730,14 @@ function get_item_elements($x,$allow_code = false) {
if($arr['edited'] > datetime_convert())
$arr['edited'] = datetime_convert();
- $arr['expires'] = ((x($x,'expires') && $x['expires'])
+ $arr['expires'] = ((!empty($x['expires']) && $x['expires'])
? datetime_convert('UTC','UTC',$x['expires'])
: NULL_DATE);
- $arr['commented'] = ((x($x,'commented') && $x['commented'])
+ $arr['commented'] = ((!empty($x['commented']) && $x['commented'])
? datetime_convert('UTC','UTC',$x['commented'])
: $arr['created']);
- $arr['comments_closed'] = ((x($x,'comments_closed') && $x['comments_closed'])
+ $arr['comments_closed'] = ((!empty($x['comments_closed']) && $x['comments_closed'])
? datetime_convert('UTC','UTC',$x['comments_closed'])
: NULL_DATE);
@@ -1594,7 +1631,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,
@@ -1645,7 +1682,7 @@ function item_store($arr, $allow_exec = false, $deliver = true) {
if(array_key_exists('parent',$arr))
unset($arr['parent']);
- $arr['mimetype'] = ((x($arr,'mimetype')) ? notags(trim($arr['mimetype'])) : 'text/bbcode');
+ $arr['mimetype'] = ((!empty($arr['mimetype'])) ? notags(trim($arr['mimetype'])) : 'text/bbcode');
if(($arr['mimetype'] == 'application/x-php') && (! $allow_exec)) {
logger('item_store: php mimetype but allow_exec is denied.');
@@ -1657,19 +1694,19 @@ function item_store($arr, $allow_exec = false, $deliver = true) {
$arr['summary'] = ((array_key_exists('summary',$arr) && $arr['summary']) ? trim($arr['summary']) : '');
$arr['body'] = ((array_key_exists('body',$arr) && $arr['body']) ? trim($arr['body']) : '');
- $arr['allow_cid'] = ((x($arr,'allow_cid')) ? trim($arr['allow_cid']) : '');
- $arr['allow_gid'] = ((x($arr,'allow_gid')) ? trim($arr['allow_gid']) : '');
- $arr['deny_cid'] = ((x($arr,'deny_cid')) ? trim($arr['deny_cid']) : '');
- $arr['deny_gid'] = ((x($arr,'deny_gid')) ? trim($arr['deny_gid']) : '');
- $arr['postopts'] = ((x($arr,'postopts')) ? trim($arr['postopts']) : '');
- $arr['route'] = ((x($arr,'route')) ? trim($arr['route']) : '');
- $arr['uuid'] = ((x($arr,'uuid')) ? trim($arr['uuid']) : '');
- $arr['item_private'] = ((x($arr,'item_private')) ? intval($arr['item_private']) : 0 );
- $arr['item_wall'] = ((x($arr,'item_wall')) ? intval($arr['item_wall']) : 0 );
- $arr['item_type'] = ((x($arr,'item_type')) ? intval($arr['item_type']) : 0 );
+ $arr['allow_cid'] = ((!empty($arr['allow_cid'])) ? trim($arr['allow_cid']) : '');
+ $arr['allow_gid'] = ((!empty($arr['allow_gid'])) ? trim($arr['allow_gid']) : '');
+ $arr['deny_cid'] = ((!empty($arr['deny_cid'])) ? trim($arr['deny_cid']) : '');
+ $arr['deny_gid'] = ((!empty($arr['deny_gid'])) ? trim($arr['deny_gid']) : '');
+ $arr['postopts'] = ((!empty($arr['postopts'])) ? trim($arr['postopts']) : '');
+ $arr['route'] = ((!empty($arr['route'])) ? trim($arr['route']) : '');
+ $arr['uuid'] = ((!empty($arr['uuid'])) ? trim($arr['uuid']) : '');
+ $arr['item_private'] = ((!empty($arr['item_private'])) ? intval($arr['item_private']) : 0 );
+ $arr['item_wall'] = ((!empty($arr['item_wall'])) ? intval($arr['item_wall']) : 0 );
+ $arr['item_type'] = ((!empty($arr['item_type'])) ? intval($arr['item_type']) : 0 );
// obsolete, but needed so as not to throw not-null constraints on some database driveres
- $arr['item_flags'] = ((x($arr,'item_flags')) ? intval($arr['item_flags']) : 0 );
+ $arr['item_flags'] = ((!empty($arr['item_flags'])) ? intval($arr['item_flags']) : 0 );
$arr['lang'] = detect_language($arr['body']);
@@ -1710,32 +1747,32 @@ function item_store($arr, $allow_exec = false, $deliver = true) {
$arr = $translate['item'];
}
- if((x($arr,'obj')) && is_array($arr['obj'])) {
+ if((!empty($arr['obj'])) && is_array($arr['obj'])) {
activity_sanitise($arr['obj']);
$arr['obj'] = json_encode($arr['obj']);
}
- if((x($arr,'target')) && is_array($arr['target'])) {
+ if((!empty($arr['target'])) && is_array($arr['target'])) {
activity_sanitise($arr['target']);
$arr['target'] = json_encode($arr['target']);
}
- if((x($arr,'attach')) && is_array($arr['attach'])) {
+ if((!empty($arr['attach'])) && is_array($arr['attach'])) {
activity_sanitise($arr['attach']);
$arr['attach'] = json_encode($arr['attach']);
}
- $arr['aid'] = ((x($arr,'aid')) ? intval($arr['aid']) : 0);
- $arr['mid'] = ((x($arr,'mid')) ? notags(trim($arr['mid'])) : random_string());
- $arr['revision'] = ((x($arr,'revision') && intval($arr['revision']) > 0) ? intval($arr['revision']) : 0);
+ $arr['aid'] = ((!empty($arr['aid'])) ? intval($arr['aid']) : 0);
+ $arr['mid'] = ((!empty($arr['mid'])) ? notags(trim($arr['mid'])) : random_string());
+ $arr['revision'] = ((!empty($arr['revision']) && intval($arr['revision']) > 0) ? intval($arr['revision']) : 0);
- $arr['author_xchan'] = ((x($arr,'author_xchan')) ? notags(trim($arr['author_xchan'])) : '');
- $arr['owner_xchan'] = ((x($arr,'owner_xchan')) ? notags(trim($arr['owner_xchan'])) : '');
- $arr['created'] = ((x($arr,'created') !== false) ? datetime_convert('UTC','UTC',$arr['created']) : datetime_convert());
- $arr['edited'] = ((x($arr,'edited') !== false) ? datetime_convert('UTC','UTC',$arr['edited']) : datetime_convert());
- $arr['expires'] = ((x($arr,'expires') !== false) ? datetime_convert('UTC','UTC',$arr['expires']) : NULL_DATE);
- $arr['commented'] = ((x($arr,'commented') !== false) ? datetime_convert('UTC','UTC',$arr['commented']) : datetime_convert());
- $arr['comments_closed'] = ((x($arr,'comments_closed') !== false) ? datetime_convert('UTC','UTC',$arr['comments_closed']) : NULL_DATE);
+ $arr['author_xchan'] = ((!empty($arr['author_xchan'])) ? notags(trim($arr['author_xchan'])) : '');
+ $arr['owner_xchan'] = ((!empty($arr['owner_xchan'])) ? notags(trim($arr['owner_xchan'])) : '');
+ $arr['created'] = ((!empty($arr['created']) !== false) ? datetime_convert('UTC','UTC',$arr['created']) : datetime_convert());
+ $arr['edited'] = ((!empty($arr['edited']) !== false) ? datetime_convert('UTC','UTC',$arr['edited']) : datetime_convert());
+ $arr['expires'] = ((!empty($arr['expires']) !== false) ? datetime_convert('UTC','UTC',$arr['expires']) : NULL_DATE);
+ $arr['commented'] = ((!empty($arr['commented']) !== false) ? datetime_convert('UTC','UTC',$arr['commented']) : datetime_convert());
+ $arr['comments_closed'] = ((!empty($arr['comments_closed']) !== false) ? datetime_convert('UTC','UTC',$arr['comments_closed']) : NULL_DATE);
$arr['html'] = ((array_key_exists('html',$arr)) ? $arr['html'] : '');
if($deliver) {
@@ -1749,26 +1786,26 @@ function item_store($arr, $allow_exec = false, $deliver = true) {
// will still take place through backdoor methods. Since these fields are rarely used
// otherwise, just preserve the original timestamp.
- $arr['received'] = ((x($arr,'received') !== false) ? datetime_convert('UTC','UTC',$arr['received']) : datetime_convert());
- $arr['changed'] = ((x($arr,'changed') !== false) ? datetime_convert('UTC','UTC',$arr['changed']) : datetime_convert());
+ $arr['received'] = ((!empty($arr['received']) !== false) ? datetime_convert('UTC','UTC',$arr['received']) : datetime_convert());
+ $arr['changed'] = ((!empty($arr['changed']) !== false) ? datetime_convert('UTC','UTC',$arr['changed']) : datetime_convert());
}
- $arr['location'] = ((x($arr,'location')) ? notags(trim($arr['location'])) : '');
- $arr['coord'] = ((x($arr,'coord')) ? notags(trim($arr['coord'])) : '');
- $arr['parent_mid'] = ((x($arr,'parent_mid')) ? notags(trim($arr['parent_mid'])) : '');
- $arr['thr_parent'] = ((x($arr,'thr_parent')) ? notags(trim($arr['thr_parent'])) : $arr['parent_mid']);
- $arr['verb'] = ((x($arr,'verb')) ? notags(trim($arr['verb'])) : 'Create');
- $arr['obj_type'] = ((x($arr,'obj_type')) ? notags(trim($arr['obj_type'])) : 'Note');
- $arr['obj'] = ((x($arr,'obj')) ? trim($arr['obj']) : '');
- $arr['tgt_type'] = ((x($arr,'tgt_type')) ? notags(trim($arr['tgt_type'])) : '');
- $arr['target'] = ((x($arr,'target')) ? trim($arr['target']) : '');
- $arr['plink'] = ((x($arr,'plink')) ? notags(trim($arr['plink'])) : '');
- $arr['attach'] = ((x($arr,'attach')) ? notags(trim($arr['attach'])) : '');
- $arr['app'] = ((x($arr,'app')) ? notags(trim($arr['app'])) : '');
+ $arr['location'] = ((!empty($arr['location'])) ? notags(trim($arr['location'])) : '');
+ $arr['coord'] = ((!empty($arr['coord'])) ? notags(trim($arr['coord'])) : '');
+ $arr['parent_mid'] = ((!empty($arr['parent_mid'])) ? notags(trim($arr['parent_mid'])) : '');
+ $arr['thr_parent'] = ((!empty($arr['thr_parent'])) ? notags(trim($arr['thr_parent'])) : $arr['parent_mid']);
+ $arr['verb'] = ((!empty($arr['verb'])) ? notags(trim($arr['verb'])) : 'Create');
+ $arr['obj_type'] = ((!empty($arr['obj_type'])) ? notags(trim($arr['obj_type'])) : 'Note');
+ $arr['obj'] = ((!empty($arr['obj'])) ? trim($arr['obj']) : '');
+ $arr['tgt_type'] = ((!empty($arr['tgt_type'])) ? notags(trim($arr['tgt_type'])) : '');
+ $arr['target'] = ((!empty($arr['target'])) ? trim($arr['target']) : '');
+ $arr['plink'] = ((!empty($arr['plink'])) ? notags(trim($arr['plink'])) : '');
+ $arr['attach'] = ((!empty($arr['attach'])) ? notags(trim($arr['attach'])) : '');
+ $arr['app'] = ((!empty($arr['app'])) ? notags(trim($arr['app'])) : '');
- $arr['public_policy'] = ((x($arr,'public_policy')) ? notags(trim($arr['public_policy'])) : '' );
+ $arr['public_policy'] = ((!empty($arr['public_policy'])) ? notags(trim($arr['public_policy'])) : '' );
- $arr['comment_policy'] = ((x($arr,'comment_policy')) ? notags(trim($arr['comment_policy'])) : 'contacts' );
+ $arr['comment_policy'] = ((!empty($arr['comment_policy'])) ? notags(trim($arr['comment_policy'])) : 'contacts' );
if(! array_key_exists('item_unseen',$arr))
$arr['item_unseen'] = 1;
@@ -1776,16 +1813,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'];
}
@@ -1827,7 +1854,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;
@@ -1882,7 +1909,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);
}
}
@@ -1922,7 +1949,7 @@ function item_store($arr, $allow_exec = false, $deliver = true) {
*/
call_hooks('post_remote', $arr);
- if(x($arr, 'cancel')) {
+ if(!empty($arr['cancel'])) {
logger('Post cancelled by plugin.');
$ret['message'] = 'cancelled.';
return $ret;
@@ -1965,7 +1992,9 @@ function item_store($arr, $allow_exec = false, $deliver = true) {
// find the item we just created
- $r = q("SELECT * FROM item WHERE mid = '%s' AND uid = %d and revision = %d ORDER BY id ASC ",
+ $r = q("SELECT item.*, tp.uuid AS thr_parent_uuid FROM item
+ LEFT JOIN item tp ON item.thr_parent = tp.mid AND item.uid = tp.uid
+ WHERE item.mid = '%s' AND item.uid = %d and item.revision = %d ORDER BY item.id ASC ",
dbesc($arr['mid']),
intval($arr['uid']),
intval($arr['revision'])
@@ -2047,9 +2076,15 @@ function item_store($arr, $allow_exec = false, $deliver = true) {
item_update_parent_commented($arr);
+ if (str_contains($arr['body'], '[/embed]') || str_contains($arr['body'], '[/img]') || str_contains($arr['body'], '[/zmg]')) {
+ Master::Summon(['Cache_embeds', $arr['uuid']]);
+ }
- if(strpos($arr['body'],'[embed]') !== false) {
- 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.
@@ -2062,9 +2097,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 +2109,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,
@@ -2135,7 +2167,7 @@ function item_store_update($arr, $allow_exec = false, $deliver = true) {
if(array_key_exists('edit',$arr))
unset($arr['edit']);
- $arr['mimetype'] = ((x($arr,'mimetype')) ? notags(trim($arr['mimetype'])) : 'text/bbcode');
+ $arr['mimetype'] = ((!empty($arr['mimetype'])) ? notags(trim($arr['mimetype'])) : 'text/bbcode');
if(($arr['mimetype'] == 'application/x-php') && (! $allow_exec)) {
logger('item_store: php mimetype but allow_exec is denied.');
@@ -2206,12 +2238,12 @@ function item_store_update($arr, $allow_exec = false, $deliver = true) {
unset($arr['thr_parent']);
unset($arr['llink']);
- $arr['edited'] = ((x($arr,'edited') !== false) ? datetime_convert('UTC','UTC',$arr['edited']) : datetime_convert());
- $arr['expires'] = ((x($arr,'expires') !== false) ? datetime_convert('UTC','UTC',$arr['expires']) : $orig[0]['expires']);
+ $arr['edited'] = ((!empty($arr['edited'])) ? datetime_convert('UTC','UTC',$arr['edited']) : datetime_convert());
+ $arr['expires'] = ((!empty($arr['expires'])) ? datetime_convert('UTC','UTC',$arr['expires']) : $orig[0]['expires']);
- $arr['revision'] = ((x($arr,'revision') && $arr['revision'] > 0) ? intval($arr['revision']) : 0);
+ $arr['revision'] = ((!empty($arr['revision'])) ? 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'];
@@ -2223,15 +2255,15 @@ function item_store_update($arr, $allow_exec = false, $deliver = true) {
$arr['route'] = ((array_key_exists('route',$arr)) ? trim($arr['route']) : $orig[0]['route']);
- $arr['location'] = ((x($arr,'location')) ? notags(trim($arr['location'])) : $orig[0]['location']);
- $arr['uuid'] = ((x($arr,'uuid')) ? notags(trim($arr['uuid'])) : $orig[0]['uuid']);
- $arr['coord'] = ((x($arr,'coord')) ? notags(trim($arr['coord'])) : $orig[0]['coord']);
- $arr['verb'] = ((x($arr,'verb')) ? notags(trim($arr['verb'])) : $orig[0]['verb']);
- $arr['obj_type'] = ((x($arr,'obj_type')) ? notags(trim($arr['obj_type'])) : $orig[0]['obj_type']);
- $arr['obj'] = ((x($arr,'obj')) ? trim($arr['obj']) : $orig[0]['obj']);
- $arr['tgt_type'] = ((x($arr,'tgt_type')) ? notags(trim($arr['tgt_type'])) : $orig[0]['tgt_type']);
- $arr['target'] = ((x($arr,'target')) ? trim($arr['target']) : $orig[0]['target']);
- $arr['plink'] = ((x($arr,'plink')) ? notags(trim($arr['plink'])) : $orig[0]['plink']);
+ $arr['location'] = ((!empty($arr['location'])) ? notags(trim($arr['location'])) : $orig[0]['location']);
+ $arr['uuid'] = ((!empty($arr['uuid'])) ? notags(trim($arr['uuid'])) : $orig[0]['uuid']);
+ $arr['coord'] = ((!empty($arr['coord'])) ? notags(trim($arr['coord'])) : $orig[0]['coord']);
+ $arr['verb'] = ((!empty($arr['verb'])) ? notags(trim($arr['verb'])) : $orig[0]['verb']);
+ $arr['obj_type'] = ((!empty($arr['obj_type'])) ? notags(trim($arr['obj_type'])) : $orig[0]['obj_type']);
+ $arr['obj'] = ((!empty($arr['obj'])) ? trim($arr['obj']) : $orig[0]['obj']);
+ $arr['tgt_type'] = ((!empty($arr['tgt_type'])) ? notags(trim($arr['tgt_type'])) : $orig[0]['tgt_type']);
+ $arr['target'] = ((!empty($arr['target'])) ? trim($arr['target']) : $orig[0]['target']);
+ $arr['plink'] = ((!empty($arr['plink'])) ? notags(trim($arr['plink'])) : $orig[0]['plink']);
$arr['allow_cid'] = ((array_key_exists('allow_cid',$arr)) ? trim($arr['allow_cid']) : $orig[0]['allow_cid']);
$arr['allow_gid'] = ((array_key_exists('allow_gid',$arr)) ? trim($arr['allow_gid']) : $orig[0]['allow_gid']);
@@ -2270,11 +2302,11 @@ function item_store_update($arr, $allow_exec = false, $deliver = true) {
$arr['item_pending_remove'] = ((array_key_exists('item_pending_remove',$arr)) ? intval($arr['item_pending_remove']) : $orig[0]['item_pending_remove'] );
$arr['item_blocked'] = ((array_key_exists('item_blocked',$arr)) ? intval($arr['item_blocked']) : $orig[0]['item_blocked'] );
- $arr['sig'] = ((x($arr,'sig')) ? $arr['sig'] : '');
+ $arr['sig'] = ((!empty($arr['sig'])) ? $arr['sig'] : '');
$arr['layout_mid'] = ((array_key_exists('layout_mid',$arr)) ? dbesc($arr['layout_mid']) : $orig[0]['layout_mid'] );
- $arr['public_policy'] = ((x($arr,'public_policy')) ? notags(trim($arr['public_policy'])) : $orig[0]['public_policy'] );
- $arr['comment_policy'] = ((x($arr,'comment_policy')) ? notags(trim($arr['comment_policy'])) : $orig[0]['comment_policy'] );
+ $arr['public_policy'] = ((!empty($arr['public_policy'])) ? notags(trim($arr['public_policy'])) : $orig[0]['public_policy'] );
+ $arr['comment_policy'] = ((!empty($arr['comment_policy'])) ? notags(trim($arr['comment_policy'])) : $orig[0]['comment_policy'] );
/**
* @hooks post_remote_update
@@ -2282,7 +2314,7 @@ function item_store_update($arr, $allow_exec = false, $deliver = true) {
*/
call_hooks('post_remote_update', $arr);
- if(x($arr, 'cancel')) {
+ if(!empty($arr['cancel'])) {
logger('Post cancelled by plugin.');
$ret['message'] = 'cancelled.';
return $ret;
@@ -2334,7 +2366,9 @@ function item_store_update($arr, $allow_exec = false, $deliver = true) {
// fetch an unescaped complete copy of the stored item
- $r = q("select * from item where id = %d",
+ $r = q("SELECT item.*, tp.uuid AS thr_parent_uuid FROM item
+ LEFT JOIN item tp ON item.thr_parent = tp.mid AND item.uid = tp.uid
+ WHERE item.id = %d",
intval($orig_post_id)
);
if($r)
@@ -2348,14 +2382,15 @@ function item_store_update($arr, $allow_exec = false, $deliver = true) {
if(is_array($terms)) {
foreach($terms as $t) {
- q("insert into term (uid,oid,otype,ttype,term,url)
- values(%d,%d,%d,%d,'%s','%s') ",
+ q("insert into term (uid, oid, otype, ttype, term, url, imgurl)
+ values (%d, %d, %d, %d, '%s', '%s', '%s')",
intval($uid),
intval($orig_post_id),
intval(TERM_OBJ_POST),
intval($t['ttype']),
dbesc($t['term']),
- dbesc($t['url'])
+ dbesc($t['url']),
+ dbesc($t['imgurl'] ?? ''),
);
}
$arr['term'] = $terms;
@@ -2386,21 +2421,23 @@ function item_store_update($arr, $allow_exec = false, $deliver = true) {
*/
call_hooks('item_stored_update',$arr);
- if(strpos($arr['body'],'[embed]') !== false) {
- Master::Summon([ 'Cache_embeds', $orig_post_id ]);
+ if (str_contains($arr['body'], '[/embed]') || str_contains($arr['body'], '[/img]') || str_contains($arr['body'], '[/zmg]')) {
+ Master::Summon(['Cache_embeds', $arr['uuid']]);
}
+ $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;
}
@@ -2448,21 +2485,23 @@ function send_status_notifications($post_id,$item) {
$parent = 0;
$is_reaction = false;
- $thr_parent_id = 0;
+ $thr_parent_id = null;
+ $thr_parent_uuid = null;
$type = ((intval($item['item_private']) === 2) ? NOTIFY_MAIL : NOTIFY_COMMENT);
- if(array_key_exists('verb',$item) && activity_match($item['verb'], ['Like', 'Dislike', ACTIVITY_LIKE, ACTIVITY_DISLIKE])) {
+ if(array_key_exists('verb',$item) && activity_match($item['verb'], ['Like', 'Dislike', ACTIVITY_LIKE, ACTIVITY_DISLIKE, 'Announce'])) {
$type = NOTIFY_LIKE;
- $r = q("select id from item where mid = '%s' and uid = %d limit 1",
+ $r = q("select id, uuid from item where mid = '%s' and uid = %d limit 1",
dbesc($item['thr_parent']),
intval($item['uid'])
);
if ($r) {
$thr_parent_id = $r[0]['id'];
+ $thr_parent_uuid = $r[0]['uuid'];
}
}
@@ -2487,6 +2526,7 @@ function send_status_notifications($post_id,$item) {
dbesc($item['parent_mid']),
intval($item['uid'])
);
+
if($x) {
foreach($x as $xx) {
if($xx['author_xchan'] === $r[0]['channel_hash']) {
@@ -2531,7 +2571,7 @@ function send_status_notifications($post_id,$item) {
'link' => $link,
'verb' => $item['verb'],
'otype' => 'item',
- 'parent' => $thr_parent_id ? $thr_parent_id : $parent,
+ 'parent' => $thr_parent_id ?? $parent,
'parent_mid' => $thr_parent_id ? $item['thr_parent'] : $item['parent_mid']
));
}
@@ -2808,8 +2848,8 @@ function tag_deliver($uid, $item_id) {
'from_xchan' => $item['author_xchan'],
'type' => NOTIFY_TAGSELF,
'item' => $item,
- 'link' => $i[0]['llink'],
- 'verb' => ACTIVITY_TAG,
+ 'link' => $item['llink'],
+ 'verb' => $item['verb'],
'otype' => 'item'
));
@@ -2951,35 +2991,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)
+
+ if ($r) {
return true;
+ }
- if(! perm_is_allowed($uid,$item['author_xchan'],'tag_deliver'))
- return false;
+ $u = channelx_by_n($uid);
- $u = q("select * from channel left join xchan on channel_hash = xchan_hash where channel_id = %d limit 1",
- intval($uid)
- );
-
- if(! $u)
+ if (!$u) {
return false;
+ }
$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 = [];
@@ -2996,7 +3033,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
@@ -3104,6 +3141,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) {
@@ -3149,17 +3191,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)) {
@@ -3181,8 +3319,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']);
@@ -3199,9 +3337,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'];
@@ -3262,8 +3405,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'];
@@ -3286,10 +3436,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;
}
@@ -3314,14 +3468,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']));
@@ -3365,12 +3519,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()),
@@ -3380,81 +3534,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)
- );
- }
}
/**
@@ -3793,7 +3872,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);
}
}
@@ -3806,25 +3885,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 ]);
- }
}
@@ -3837,7 +3903,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
@@ -3845,33 +3911,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) {
@@ -3884,9 +3965,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.
@@ -3909,30 +3990,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.
@@ -4656,19 +4802,19 @@ function items_fetch($arr,$channel = null,$observer_hash = null,$client_mode = C
return $items;
}
-function webpage_to_namespace($webpage) {
+function item_type_to_namespace($item_type) {
- if($webpage == ITEM_TYPE_WEBPAGE)
+ if($item_type == ITEM_TYPE_WEBPAGE)
$page_type = 'WEBPAGE';
- elseif($webpage == ITEM_TYPE_BLOCK)
+ elseif($item_type == ITEM_TYPE_BLOCK)
$page_type = 'BUILDBLOCK';
- elseif($webpage == ITEM_TYPE_PDL)
+ elseif($item_type == ITEM_TYPE_PDL)
$page_type = 'PDL';
- elseif($webpage == ITEM_TYPE_CARD)
+ elseif($item_type == ITEM_TYPE_CARD)
$page_type = 'CARD';
- elseif($webpage == ITEM_TYPE_ARTICLE)
+ elseif($item_type == ITEM_TYPE_ARTICLE)
$page_type = 'ARTICLE';
- elseif($webpage == ITEM_TYPE_DOC)
+ elseif($item_type == ITEM_TYPE_DOC)
$page_type = 'docfile';
else
$page_type = 'unknown';
@@ -4677,12 +4823,12 @@ function webpage_to_namespace($webpage) {
}
-function update_remote_id($channel,$post_id,$webpage,$pagetitle,$namespace,$remote_id,$mid) {
+function update_remote_id($channel,$post_id,$item_type,$pagetitle,$namespace,$remote_id,$mid) {
if(! intval($post_id))
return;
- $page_type = webpage_to_namespace($webpage);
+ $page_type = item_type_to_namespace($item_type);
if($page_type == 'unknown' && $namespace && $remote_id) {
$page_type = $namespace;
@@ -5008,7 +5154,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) {
@@ -5023,10 +5169,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;
@@ -5054,7 +5201,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),
@@ -5064,7 +5211,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),
@@ -5085,7 +5241,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();
@@ -5103,36 +5259,517 @@ 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);
+}
+
+/**
+ * @brief returns an item by id and parent belonging to local_channel()
+ * including activity counts.
+ * @param int $id
+ * @param int $parent
+ */
+
+function item_by_item_id(int $id, int $parent): array
+{
+ if (!$id && !$parent && !local_channel()) {
+ return [];
+ }
+
+ $item_normal_sql = item_normal();
+
+ $reaction = item_reaction_sql($parent);
+ $reaction_cte_sql = $reaction['cte'];
+ $reaction_select_sql = $reaction['select'];
+ $reaction_join_sql = $reaction['join'];
+
+ return q("WITH
+ $reaction_cte_sql
+ SELECT
+ *,
+ $reaction_select_sql
+ FROM item
+ $reaction_join_sql
+ WHERE
+ item.id = %d
+ AND item.uid = %d
+ AND item.verb IN ('Create', 'Update', 'EmojiReact')
+ $item_normal_sql",
+ intval($id),
+ intval(local_channel())
+ );
+}
+
+
+/**
+ * @brief returns an array of items by ids
+ * ATTENTION: no permissions for the parents are checked here!!!
+ * Permissions MUST be checked by the module which calls this function.
+ * @param array $parents
+ * @param null|array $thr_parents (optional) - thr_parent mids which will be included
+ * @param string $permission_sql (optional) - SQL as provided by item_permission_sql() from the calling module
+ * @param bool $blog_mode (optional) - if set to yes only the parent items will be returned
+ */
+
+function items_by_parent_ids(array $parents, null|array $thr_parents = null, string $permission_sql = '', bool $blog_mode = false): array
+{
+ if (!$parents) {
+ return [];
+ }
+
+ $ids = ids_to_querystr($parents, 'item_id');
+ $thread_allow = ((local_channel()) ? PConfig::Get(local_channel(), 'system', 'thread_allow', true) : Config::Get('system', 'thread_allow', true));
+ $item_normal_sql = item_normal();
+ $limit = $thread_allow ? 3 : 1000;
+
+ $thr_parent_sql = (($thread_allow) ? " AND item.thr_parent = item.parent_mid " : '');
+ if ($thr_parents && $thread_allow) {
+ $limit = 300;
+ $thr_parent_str = stringify_array($thr_parents, true);
+ $thr_parent_sql = " AND item.thr_parent IN (" . protect_sprintf($thr_parent_str) . ") ";
+ }
+
+ $reaction = item_reaction_sql($ids, $permission_sql, 'final_selection');
+ $reaction_cte_sql = $reaction['cte'];
+ $reaction_select_sql = $reaction['select'];
+ $reaction_join_sql = $reaction['join'];
+
+ if ($blog_mode) {
+ $q = <<<SQL
+ WITH
+ final_selection AS (
+ SELECT
+ item.*
+ FROM
+ item
+ WHERE
+ item.id IN ($ids)
+ ),
+
+ $reaction_cte_sql
+
+ SELECT
+ final_selection.*,
+ $reaction_select_sql
+ FROM final_selection
+ $reaction_join_sql
+ SQL;
+
+ return dbq(trim($q));
+ }
+
+ $q = <<<SQL
+ WITH
+ parent_items AS (
+ SELECT
+ item.*,
+ 0 AS rn
+ FROM item
+ WHERE
+ item.id IN ($ids)
+ ),
+
+ $reaction_cte_sql,
+
+ all_comments AS (
+ SELECT
+ item.*,
+ ROW_NUMBER() OVER (PARTITION BY item.parent ORDER BY item.created DESC) AS rn
+ FROM item
+ WHERE item.parent IN ($ids)
+ AND item.verb IN ('Create', 'Update', 'EmojiReact')
+ AND item.item_thread_top = 0
+ $thr_parent_sql
+ $permission_sql
+ $item_normal_sql
+ ),
+
+ final_selection AS (
+ SELECT * FROM parent_items
+ UNION ALL
+ SELECT * FROM all_comments WHERE all_comments.rn <= $limit
+ )
+
+ SELECT
+ final_selection.*,
+ $reaction_select_sql
+ FROM final_selection
+ $reaction_join_sql
+ SQL;
+
+ return dbq(trim($q));
+}
+
+/**
+ * @brief prepare reaction sql for items_by_parent_ids()
+ * ATTENTION: no permissions for the pa are checked here!!!
+ * Permissions MUST be checked by the function which returns the ids.
+ * @param string $ids
+ * @param string $permission_sql (optional) - SQL provided by item_permission_sql()
+ * @param string $join_prefix (optional) - prefix for the join part defaults to 'item'
+ */
+
+function item_reaction_sql(string $ids, string $permission_sql = '', string $join_prefix = 'item'): array
+{
+ $item_normal_sql = item_normal();
+ $observer = get_observer_hash();
+
+ $verbs = [
+ 'like' => ['Like'],
+ 'dislike' => ['Dislike'],
+ 'announce' => ['Announce'],
+ 'accept' => ['Accept'],
+ 'reject' => ['Reject'],
+ 'tentativeaccept' => ['TentativeAccept']
+ ];
+
+ $thread_allow = ((local_channel()) ? PConfig::Get(local_channel(), 'system', 'thread_allow', true) : Config::Get('system', 'thread_allow', true));
+
+ if ($thread_allow) {
+ $verbs['comment'] = ['Create', 'Update', 'EmojiReact'];
+ }
+
+ $cte = '';
+ $select = '';
+ $join = '';
+
+ foreach($verbs as $k => $v) {
+
+ $observer_sql = "0 AS observer_{$k}_count";
+ if ($observer) {
+ $observer_sql = "COUNT(CASE WHEN item.author_xchan = '$observer' THEN 1 END) AS observer_{$k}_count";
+ }
+
+ $verbs_str = stringify_array($v);
+
+ if ($cte) {
+ $cte .= ",\n";
+ }
+
+ $cte .= <<<SQL
+ reaction_{$k} AS (
+ SELECT
+ item.thr_parent,
+ -- COUNT(DISTINCT item.author_xchan) AS {$k}_count, (should we prevent multiple reactions by the same author?)
+ COUNT(*) AS {$k}_count,
+ $observer_sql
+ FROM item
+ WHERE item.verb IN ($verbs_str)
+ AND item.item_thread_top = 0
+ AND item.parent IN ($ids)
+ $item_normal_sql
+ $permission_sql
+ GROUP BY item.thr_parent
+ )
+ SQL;
+
+ if ($select) {
+ $select .= ",\n";
+ }
+
+ $select .= <<<SQL
+ COALESCE(reaction_{$k}.{$k}_count, 0) AS {$k}_count,
+ COALESCE(reaction_{$k}.observer_{$k}_count, 0) AS observer_{$k}_count
+ SQL;
+
+ $join .= <<<SQL
+ LEFT JOIN reaction_{$k} ON reaction_{$k}.thr_parent = $join_prefix.mid
+ SQL;
+
+ }
+
+ $ret['cte'] = $cte;
+ $ret['select'] = $select;
+ $ret['join'] = $join;
+
+ return $ret;
+}
+
+
+
+/**
+ * @brief returns an array of items by thr_parent mid of a parent
+
+ * @param string $mid
+ * @param int $parent
+ * @param int|null $offset
+ */
+
+function items_by_thr_parent(string $mid, int $parent, int|null $offset = null): array
+{
+ if (!$mid && !$parent) {
+ return [];
+ }
+
+ $parent_item = q("SELECT uid FROM item WHERE id = %d",
+ intval($parent)
+ );
+
+ $order_sql = "ORDER BY item.created";
+ if (isset($offset)) {
+ $order_sql = "ORDER BY item.created DESC, item.received DESC LIMIT 3 OFFSET $offset";
+ }
+
+ $owner_uid = intval($parent_item[0]['uid']);
+ $item_normal_sql = item_normal($owner_uid);
+
+ if (local_channel() === $owner_uid) {
+ $reaction = item_reaction_sql($parent);
+ $reaction_cte_sql = $reaction['cte'];
+ $reaction_select_sql = $reaction['select'];
+ $reaction_join_sql = $reaction['join'];
+
+ $ret = q("WITH
+ $reaction_cte_sql
+ SELECT
+ item.*,
+ $reaction_select_sql
+ FROM item
+ $reaction_join_sql
+ WHERE
+ item.thr_parent = '%s'
+ AND item.uid = %d
+ AND item.verb IN ('Create', 'Update', 'EmojiReact')
+ AND item.item_thread_top = 0
+ $item_normal_sql
+ $order_sql",
+ dbesc($mid),
+ intval($owner_uid)
+ );
+ }
+ else {
+ $observer_hash = get_observer_hash();
+ $permission_sql = item_permissions_sql($owner_uid, $observer_hash);
+
+ $reaction = item_reaction_sql($parent, $permission_sql);
+ $reaction_cte_sql = $reaction['cte'];
+ $reaction_select_sql = $reaction['select'];
+ $reaction_join_sql = $reaction['join'];
+
+ $ret = q("WITH
+ $reaction_cte_sql
+ SELECT
+ item.*,
+ $reaction_select_sql
+ FROM item
+ $reaction_join_sql
+ WHERE
+ item.thr_parent = '%s'
+ AND item.uid = %d
+ AND item.verb IN ('Create', 'Update', 'EmojiReact')
+ AND item.item_thread_top = 0
+ $permission_sql
+ $item_normal_sql
+ $order_sql",
+ dbesc($mid),
+ intval($owner_uid)
+ );
+ }
+
+ if (isset($offset)) {
+ $ret = array_reverse($ret);
+ }
+
+ return $ret;
+}
+
+
+/**
+ * @brief returns an array of xchan entries (partly) for activities of an item by mid of a parent.
+ * Also checks if observer is allowed to add activities to the item.
+ * @param string $mid
+ * @param int $parent
+ * @param string $verb
+ */
+
+function item_activity_xchans(string $mid, int $parent, string $verb): array
+{
+ if (!$mid && !$parent && !$verb) {
+ return [];
+ }
+
+ $observer_hash = get_observer_hash();
+ $parent_item = q("SELECT * FROM item WHERE id = %d",
+ intval($parent)
+ );
+
+ $owner_uid = intval($parent_item[0]['uid']);
+ $item_normal = item_normal($owner_uid);
+
+ if (local_channel() === $owner_uid) {
+ $ret = q("SELECT item.id, item.item_blocked, xchan.xchan_hash, xchan.xchan_name as name, xchan.xchan_url as url, xchan.xchan_photo_s as photo FROM item
+ LEFT JOIN xchan ON item.author_xchan = xchan.xchan_hash
+ WHERE item.uid = %d
+ AND item.parent = %d
+ AND item.thr_parent = '%s'
+ AND item.verb = '%s'
+ AND item.item_thread_top = 0
+ $item_normal
+ -- GROUP BY item.author_xchan (should we prevent multiple reactions by the same author?)
+ ORDER BY item.created",
+ intval(local_channel()),
+ intval($parent),
+ dbesc($mid),
+ dbesc($verb)
+ );
+ }
+ else {
+ $sql_extra = item_permissions_sql($owner_uid, $observer_hash);
+
+ $ret = q("SELECT item.id, item.item_blocked, xchan.xchan_hash, xchan.xchan_name as name, xchan.xchan_url as url, xchan.xchan_photo_s as photo FROM item
+ LEFT JOIN xchan ON item.author_xchan = xchan.xchan_hash
+ WHERE item.uid = %d
+ AND item.thr_parent = '%s'
+ AND item.verb = '%s'
+ AND item.item_thread_top = 0
+ $sql_extra
+ $item_normal
+ -- GROUP BY item.author_xchan (should we prevent multiple reactions by the same author?)
+ ORDER BY item.created",
+ intval($owner_uid),
+ dbesc($mid),
+ dbesc($verb)
+ );
+ }
+
+ $ret['is_commentable'] = can_comment_on_post($observer_hash, $parent_item[0]);
+
+ return $ret;
+}
+
+
+/**
+ * @brief find and return thr_parents we need to show when displaying a nested comment.
+ * TODO: can this be improved or maybe implemented differently in the UI?
+ * @param array $item
+ */
+
+function get_recursive_thr_parents(array $item): array|null
+{
+ if ($item['id'] === $item['parent']) {
+ // This is a toplevel post, return null.
+ return null;
+ }
+
+ $thr_parents[] = $item['thr_parent'];
+
+ $mid = $item['thr_parent'];
+ $parent_mid = $item['parent_mid'];
+ $uid = $item['uid'];
+ $i = 0;
+
+ while ($mid !== $item['parent_mid'] && $i < 100) {
+ $x = q("SELECT thr_parent, mid FROM item WHERE uid = %d AND mid = '%s'",
+ intval($uid),
+ dbesc($mid)
+ );
+
+ if (!$x) {
+ break;
+ }
+
+ $mid = $x[0]['thr_parent'];
+ $thr_parents[] = $x[0]['thr_parent'];
+
+ $i++;
+ }
+
+ return $thr_parents;
+}