diff options
Diffstat (limited to 'Zotlabs')
77 files changed, 1916 insertions, 1512 deletions
diff --git a/Zotlabs/Daemon/Cache_embeds.php b/Zotlabs/Daemon/Cache_embeds.php index d5adfcc59..99b1c7ac0 100644 --- a/Zotlabs/Daemon/Cache_embeds.php +++ b/Zotlabs/Daemon/Cache_embeds.php @@ -6,23 +6,28 @@ class Cache_embeds { static public function run($argc,$argv) { - if(! $argc == 2) + if(!$argc == 2) { return; + } - $c = q("select body from item where id = %d ", - dbesc(intval($argv[1])) + $c = q("select uid, aid, body, item_private from item where uuid = '%s'", + dbesc($argv[1]) ); - if(! $c) + if(!$c) { return; + } $item = $c[0]; // bbcode conversion by default processes embeds that aren't already cached. // Ignore the returned html output. - bbcode($item['body']); + // photocache addon hook to prefetch one copy of public item images for the sys channel + call_hooks('cache_prefetch_hook', $item); + return; } + } diff --git a/Zotlabs/Daemon/Channel_purge.php b/Zotlabs/Daemon/Channel_purge.php index d25edf8ba..7286a791a 100644 --- a/Zotlabs/Daemon/Channel_purge.php +++ b/Zotlabs/Daemon/Channel_purge.php @@ -24,7 +24,7 @@ class Channel_purge { ); if ($r) { foreach ($r as $rv) { - drop_item($rv['id']); + drop_item($rv['id'], uid: $channel_id); } } } while ($r); diff --git a/Zotlabs/Daemon/Cron.php b/Zotlabs/Daemon/Cron.php index 186f3efcf..131abe2e1 100644 --- a/Zotlabs/Daemon/Cron.php +++ b/Zotlabs/Daemon/Cron.php @@ -69,19 +69,18 @@ class Cron { // expire any expired items - $r = q("select id,item_wall from item where expires > '2001-01-01 00:00:00' and expires < %s + $r = q("select id, uid, item_wall from item where expires > '2001-01-01 00:00:00' and expires < %s and item_deleted = 0 ", db_utcnow() ); + if ($r) { - require_once('include/items.php'); foreach ($r as $rr) { + // pass uid of the message for permission check as we are running as a daemon process with no session. drop_item($rr['id'], (($rr['item_wall']) ? DROPITEM_PHASE1 : DROPITEM_NORMAL), uid: intval($rr['uid'])); - if ($rr['item_wall']) { // The notifier isn't normally invoked unless item_drop is interactive. Master::Summon(['Notifier', 'drop', $rr['id']]); - if ($interval) { usleep($interval); } @@ -126,14 +125,15 @@ class Cron { $r = q("SELECT DISTINCT xchan, content FROM photo WHERE photo_usage = %d AND expires < %s - INTERVAL %s", intval(PHOTO_CACHE), db_utcnow(), - db_quoteinterval(Config::Get('system', 'cache_expire_days', 7) . ' DAY') + db_quoteinterval(Config::Get('system', 'default_expire_days', 30) . ' DAY') ); if ($r) { q("DELETE FROM photo WHERE photo_usage = %d AND expires < %s - INTERVAL %s", intval(PHOTO_CACHE), db_utcnow(), - db_quoteinterval(Config::Get('system', 'cache_expire_days', 7) . ' DAY') + db_quoteinterval(Config::Get('system', 'default_expire_days', 30) . ' DAY') ); + foreach ($r as $rr) { $file = dbunescbin($rr['content']); if (is_file($file)) { @@ -148,37 +148,29 @@ class Cron { // (time travel posts). Restrict to items that have come of age in the last // couple of days to limit the query to something reasonable. - $r = q("select id from item where item_delayed = 1 and created <= %s and created > '%s' ", + $r = q("select * from item where item_delayed = 1 and created <= %s and created > '%s' ", db_utcnow(), dbesc(datetime_convert('UTC', 'UTC', 'now - 2 days')) ); - if ($r) { - foreach ($r as $rr) { - $x = q("update item set item_delayed = 0 where id = %d", - intval($rr['id']) - ); - if ($x) { - $z = q("select * from item where id = %d", - intval($rr['id']) - ); - if ($z) { - xchan_query($z); - $sync_item = fetch_post_tags($z); - Libsync::build_sync_packet($sync_item[0]['uid'], - [ - 'item' => [encode_item($sync_item[0], true)] - ] - ); - } - Master::Summon(array('Notifier', 'wall-new', $rr['id'])); - if ($interval) { - usleep($interval); + if ($r) { + xchan_query($r); + $items = fetch_post_tags($r); + foreach ($items as $item) { + $item['item_delayed'] = 0; + $post = item_store_update($item); + + if($post['success']) { + Master::Summon(['Notifier', 'wall-new', $post['item_id']]); + if (!empty($post['approval_id'])) { + Master::Summon(['Notifier', 'wall-new', $post['approval_id']]); } } - } - } - + if ($interval) { + usleep($interval); + } + } + } // once daily run birthday_updates and then expire in background // FIXME: add birthday updates, both locally and for xprof for use diff --git a/Zotlabs/Daemon/Expire.php b/Zotlabs/Daemon/Expire.php index 3ac3dee3a..dae585578 100644 --- a/Zotlabs/Daemon/Expire.php +++ b/Zotlabs/Daemon/Expire.php @@ -23,13 +23,13 @@ class Expire { // perform final cleanup on previously delete items - $r = q("select id from item where item_deleted = 1 and item_pending_remove = 0 and changed < %s - INTERVAL %s", + $r = q("select id, uid from item where item_deleted = 1 and item_pending_remove = 0 and changed < %s - INTERVAL %s", db_utcnow(), db_quoteinterval('10 DAY') ); if ($r) { foreach ($r as $rr) { - drop_item($rr['id'], DROPITEM_PHASE2); + drop_item($rr['id'], DROPITEM_PHASE2, uid: $rr['uid']); } } diff --git a/Zotlabs/Daemon/Importdoc.php b/Zotlabs/Daemon/Importdoc.php index 8f04e05f8..de571848e 100644 --- a/Zotlabs/Daemon/Importdoc.php +++ b/Zotlabs/Daemon/Importdoc.php @@ -11,6 +11,21 @@ class Importdoc { self::update_docs_dir('doc/*'); + $sys = get_sys_channel(); + + // remove old files that weren't updated (indicates they were most likely deleted). + $i = q("select id from item where uid = %d and item_type = 5 and edited < %s - INTERVAL %s", + intval($sys['channel_id']), + db_utcnow(), + db_quoteinterval('14 DAY') + ); + + if ($i) { + foreach ($i as $iv) { + drop_item($iv['id'], uid: $sys['channel_id']); + } + } + return; } @@ -41,18 +56,6 @@ class Importdoc { } } } - - // remove old files that weren't updated (indicates they were most likely deleted). - $i = q("select * from item where item_type = 5 and edited < %s - %s", - db_utcnow(), - db_quoteinterval('14 DAY', true) - ); - - if ($i) { - foreach ($i as $iv) { - drop_item($iv['id'], DROPITEM_NORMAL, true); - } - } } } diff --git a/Zotlabs/Daemon/Notifier.php b/Zotlabs/Daemon/Notifier.php index 1693f9c9f..9fdb1defb 100644 --- a/Zotlabs/Daemon/Notifier.php +++ b/Zotlabs/Daemon/Notifier.php @@ -241,11 +241,6 @@ class Notifier { $target_item = $r[0]; - if (in_array($target_item['author']['xchan_network'], ['rss', 'anon', 'token'])) { - logger('notifier: target item author is not a fetchable actor', LOGGER_DEBUG); - return; - } - if (intval($target_item['item_deleted'])) { logger('notifier: target item ITEM_DELETED', LOGGER_DEBUG); } @@ -268,22 +263,9 @@ class Notifier { } - // Check for non published items, but allow an exclusion for transmitting hidden file activities - - if (intval($target_item['item_unpublished']) || intval($target_item['item_delayed']) || - intval($target_item['item_blocked']) || intval($target_item['item_hidden'])) { - logger('notifier: target item not published, so not forwardable', LOGGER_DEBUG); - return; - } - - // follow/unfollow is for internal use only - if (in_array($target_item['verb'], ['Follow', 'Ignore', ACTIVITY_FOLLOW, ACTIVITY_UNFOLLOW])) { - logger('not fowarding follow/unfollow note activity'); - return; - } - - if (strpos($target_item['postopts'], 'nodeliver') !== false) { - logger('notifier: target item is undeliverable', LOGGER_DEBUG); + if (!item_forwardable($target_item)) { + //hz_syslog(print_r($target_item,true)); + logger('notifier: target item not forwardable', LOGGER_DEBUG); return; } @@ -341,7 +323,13 @@ class Notifier { self::$encoded_item = json_decode($m, true); } else { - self::$encoded_item = Activity::build_packet(Activity::encode_activity($target_item), self::$channel, false); + $activity = Activity::encode_activity($target_item); + + if (!$activity) { + return; + } + + self::$encoded_item = Activity::build_packet($activity, self::$channel, false); } logger('target_item: ' . print_r($target_item, true), LOGGER_DEBUG); @@ -358,6 +346,10 @@ class Notifier { $relay_to_owner = (!$top_level_post && intval($target_item['item_origin']) && comment_local_origin($target_item)); + if (self::$channel['channel_hash'] === $target_item['owner_xchan']) { + $relay_to_owner = false; + } + // $cmd === 'relay' indicates the owner is sending it to the original recipients // don't allow the item in the relay command to relay to owner under any circumstances, it will loop @@ -388,7 +380,7 @@ class Notifier { logger('normal (downstream) distribution', LOGGER_DEBUG); } - if ($parent_item && $parent_item['item_private'] !== $target_item['item_private']) { + if (($parent_item && $parent_item['item_private'] !== $target_item['item_private']) || (intval($target_item['item_restrict']) & 1)) { logger('conversation privacy mismatch - downstream delivery prevented'); return; } diff --git a/Zotlabs/Lib/Activity.php b/Zotlabs/Lib/Activity.php index 54a1b8d2a..296129ea2 100644 --- a/Zotlabs/Lib/Activity.php +++ b/Zotlabs/Lib/Activity.php @@ -13,6 +13,7 @@ use Zotlabs\Entity\Item; require_once('include/event.php'); require_once('include/html2plain.php'); require_once('include/items.php'); +require_once('include/markdown.php'); class Activity { @@ -68,10 +69,10 @@ class Activity { if ($j) { xchan_query($j, true); $items = fetch_post_tags($j); - } - if ($items) { - return self::encode_item(array_shift($items)); + if ($items) { + return self::encode_item(array_shift($items)); + } } return null; @@ -165,7 +166,7 @@ class Activity { } else { logger('fetch failed: ' . $url); - logger($x['body']); + logger(print_r($x, true), LOGGER_DEBUG); } @@ -580,14 +581,12 @@ class Activity { } } - if (intval($i['item_wall'])) { - $ret['commentPolicy'] = map_scope(PermissionLimits::Get($i['uid'], 'post_comments')); - } - if (intval($i['item_private']) === 2) { $ret['directMessage'] = true; } + $ret['commentPolicy'] = (($i['item_wall']) ? map_scope(PermissionLimits::Get($i['uid'], 'post_comments')) : ''); + if (array_key_exists('comments_closed', $i) && $i['comments_closed'] !== EMPTY_STR && $i['comments_closed'] > NULL_DATE) { if ($ret['commentPolicy']) { $ret['commentPolicy'] .= ' '; @@ -616,6 +615,7 @@ class Activity { if (!empty($cnv)) { if (is_string($cnv) && str_starts_with($cnv, z_root())) { $cnv = str_replace(['/item/', '/activity/'], ['/conversation/', '/conversation/'], $cnv); + $ret['contextHistory'] = $cnv; } $ret['context'] = $cnv; } @@ -694,7 +694,7 @@ class Activity { if (is_array($t) && !array_key_exists('type', $t)) $t['type'] = 'Hashtag'; - if (is_array($t) && (array_key_exists('href', $t) || array_key_exists('id', $t)) && array_key_exists('name', $t)) { + if (is_array($t) && (array_key_exists('href', $t) || array_key_exists('id', $t) || isset($t['icon']['url'])) && array_key_exists('name', $t)) { switch ($t['type']) { case 'Hashtag': $ret[] = ['ttype' => TERM_HASHTAG, 'url' => $t['href'], 'term' => escape_tags((substr($t['name'], 0, 1) === '#') ? substr($t['name'], 1) : $t['name'])]; @@ -709,7 +709,7 @@ class Activity { break; case 'Emoji': - $ret[] = ['ttype' => TERM_EMOJI, 'url' => $t['id'], 'term' => escape_tags($t['name']), 'imgurl' => $t['icon']['url']]; + $ret[] = ['ttype' => TERM_EMOJI, 'url' => $t['id'] ?? $t['icon']['url'], 'term' => escape_tags($t['name']), 'imgurl' => $t['icon']['url']]; break; default: @@ -802,7 +802,7 @@ class Activity { $ret = []; - if (isset($item['attachment'])) { + if (isset($item['attachment']) && is_array($item['attachment'])) { $ptr = $item['attachment']; if (!array_key_exists(0, $ptr)) { $ptr = [$ptr]; @@ -852,6 +852,8 @@ class Activity { $entry['type'] = $att['mediaType']; } elseif (array_key_exists('type', $att) && $att['type'] === 'Image') { $entry['type'] = 'image/jpeg'; + } elseif (array_key_exists('type', $att) && $att['type'] === 'Link') { + $entry['type'] = 'text/uri-list'; } if (array_key_exists('name', $att) && $att['name']) { $entry['name'] = html2plain(purify_html($att['name']), 256); @@ -941,36 +943,8 @@ class Activity { } - if ($ret['type'] === 'emojiReaction') { - // There may not be an object for these items for legacy reasons - it should be the conversation parent. - $p = q("select * from item where mid = '%s' and uid = %d", - dbesc($i['parent_mid']), - intval($i['uid']) - ); - if ($p) { - xchan_query($p, true); - $p = fetch_post_tags($p); - $i['obj'] = self::encode_item($p[0]); - - // convert to zot6 emoji reaction encoding which uses the target object to indicate the - // specific emoji instead of overloading the verb or type. - - $im = explode('#', $i['verb']); - if ($im && count($im) > 1) - $emoji = $im[1]; - if (preg_match("/\[img(.*?)\](.*?)\[\/img\]/ism", $i['body'], $match)) { - $ln = $match[2]; - } - - $i['tgt_type'] = 'Image'; - - $i['target'] = [ - 'type' => 'Image', - 'name' => $emoji, - 'url' => (($ln) ? $ln : z_root() . '/images/emoji/' . $emoji . '.png') - ]; - - } + if ($ret['type'] === 'EmojiReact') { + $ret['content'] = $i['body']; } if (strpos($i['mid'], z_root() . '/item/') !== false) { @@ -985,15 +959,15 @@ class Activity { $ret['diaspora:guid'] = $i['uuid']; - if (isset($i['title']) && $i['title']) - $ret['name'] = html2plain(bbcode($i['title'], ['cache' => true])); + if (!empty($i['title'])) + $ret['name'] = html2plain(bbcode($i['title'])); - if (isset($i['summary']) && $i['summary']) - $ret['summary'] = bbcode($i['summary'], ['cache' => true]); + if (!empty($i['summary'])) + $ret['summary'] = bbcode($i['summary']); if ($ret['type'] === 'Announce') { $tmp = preg_replace('/\[share(.*?)\[\/share\]/ism', EMPTY_STR, $i['body']); - $ret['content'] = bbcode($tmp, ['cache' => true]); + $ret['content'] = bbcode($tmp); $ret['source'] = [ 'content' => $i['body'], 'mediaType' => 'text/bbcode' @@ -1009,7 +983,7 @@ class Activity { } } - if (isset($i['app']) && $i['app']) { + if (!empty($i['app'])) { $ret['generator'] = ['type' => 'Application', 'name' => $i['app']]; } if (!empty($i['location']) || !empty($i['coord'])) { @@ -1050,6 +1024,7 @@ class Activity { if (!empty($cnv)) { if (is_string($cnv) && str_starts_with($cnv, z_root())) { $cnv = str_replace(['/item/', '/activity/'], ['/conversation/', '/conversation/'], $cnv); + $ret['contextHistory'] = $cnv; } $ret['context'] = $cnv; } @@ -1060,7 +1035,7 @@ class Activity { else return []; - if (isset($i['obj']) && $i['obj']) { + if (!empty($i['obj'])) { if (!is_array($i['obj'])) { $i['obj'] = json_decode($i['obj'], true); } @@ -1088,7 +1063,7 @@ class Activity { $ret['type'] = 'Invite'; } - if (isset($i['target']) && $i['target']) { + if (!empty($i['target'])) { if (!is_array($i['target'])) { $i['target'] = json_decode($i['target'], true); } @@ -1099,12 +1074,10 @@ class Activity { return []; } -/* this should not be needed $t = self::encode_taxonomy($i); if ($t) { $ret['tag'] = $t; } -*/ $a = self::encode_attachment($i, true); if ($a) { @@ -1115,7 +1088,6 @@ class Activity { $ret['to'] = [ACTIVITY_PUBLIC_INBOX]; } - $hookinfo = [ 'item' => $i, 'encoded' => $ret @@ -1716,9 +1688,9 @@ class Activity { return; } - $name = $person_obj['name'] ?? ''; + $name = ((isset($person_obj['name'])) ? escape_tags($person_obj['name']) : ''); if (!$name) { - $name = $person_obj['preferredUsername'] ?? ''; + $name = ((isset($person_obj['preferredUsername'])) ? escape_tags($person_obj['preferredUsername']) : ''); } if (!$name) { $name = t('Unknown'); @@ -1727,13 +1699,11 @@ class Activity { $webfinger_addr = ((isset($person_obj['webfinger'])) ? str_replace('acct:', '', $person_obj['webfinger']) : ''); $hostname = ''; $baseurl = ''; - $site_url = ''; $m = parse_url($url); if ($m) { - $hostname = $m['host']; - $baseurl = $m['scheme'] . '://' . $m['host'] . ((isset($m['port'])) ? ':' . $m['port'] : ''); - $site_url = $m['scheme'] . '://' . $m['host']; + $hostname = unparse_url($m, ['host']); + $baseurl = unparse_url($m, ['scheme', 'host', 'port']); } if (!$webfinger_addr && !empty($person_obj['preferredUsername']) && $hostname) { @@ -1835,7 +1805,7 @@ class Activity { q("UPDATE site SET site_update = '%s', site_dead = 0 WHERE site_url = '%s'", dbesc(datetime_convert()), - dbesc($site_url) + dbesc($baseurl) ); // update existing xchan record @@ -2081,6 +2051,9 @@ class Activity { $i = fetch_post_tags($i); $i[0]['obj'] = $o; + $edited = datetime_convert(); + $i[0]['edited'] = $edited; + // create the new object $newObj = self::build_packet(self::encode_activity($i[0]), $channel, true); @@ -2098,7 +2071,7 @@ class Activity { dbesc(json_encode($o)), intval($relatedItem['id']), dbesc($newObj), - dbesc(datetime_convert()) + dbesc($edited) ); dbq("COMMIT"); @@ -2145,35 +2118,25 @@ class Activity { $s['owner_xchan'] = $act->actor['id']; $s['author_xchan'] = $act->actor['id']; - $content = []; + $s['mid'] = self::getMessageID($act); - if (is_array($act->obj)) { - $content = self::get_content($act->obj); + if (!$s['mid']) { + return false; } - $s['mid'] = $act->objprop('id'); - - if (!$s['mid'] && is_string($act->obj)) { - $s['mid'] = $act->obj; - } + $s['uuid'] = self::getUUID($act); - // pleroma fetched activities - if (!$s['mid'] && isset($act->obj['data']['id'])) { - $s['mid'] = $act->obj['data']['id']; + if (!$s['uuid']) { + // If we have not found anything useful, create an uuid v5 from the mid + $s['uuid'] = uuid_from_url($s['mid']); } - if ($act->objprop('type') === 'Profile') { - $s['mid'] = $act->id; - } + $content = []; - if (!$s['mid']) { - return false; + if (is_array($act->obj)) { + $content = self::get_content($act->obj); } - // Friendica sends the diaspora guid in a nonstandard field via AP - // If no uuid is provided we will create an uuid v5 from the mid - $s['uuid'] = (($act->objprop('diaspora:guid')) ?: uuid_from_url($s['mid'])); - $s['parent_mid'] = $act->parent_id; if (array_key_exists('published', $act->data)) { @@ -2212,23 +2175,8 @@ class Activity { $response_activity = true; - $s['mid'] = $act->id; - $s['uuid'] = ((!empty($act->data['diaspora:guid'])) ? $act->data['diaspora:guid'] : uuid_from_url($s['mid'])); - $s['parent_mid'] = $act->objprop('id') ?: $act->obj; -/* - if ($act->objprop('inReplyTo')) { - $s['parent_mid'] = $act->objprop('inReplyTo'); - } - - $s['thr_parent'] = $act->objprop('id') ?: $act->obj; - - if (empty($s['parent_mid']) || empty($s['thr_parent'])) { - logger('response activity without parent_mid or thr_parent'); - return; - } -*/ // over-ride the object timestamp with the activity if (isset($act->data['published'])) { @@ -2239,9 +2187,9 @@ class Activity { $s['edited'] = datetime_convert('UTC', 'UTC', $act->data['updated']); } - $obj_actor = $act->objprop('actor') ?: $act->get_actor('attributedTo', $act->obj); + $obj_actor = is_array($act->objprop('actor')) ? $act->objprop('actor') : $act->get_actor('attributedTo', $act->obj); - if (!isset($obj_actor['id'])) { + if (empty($obj_actor['id'])) { return false; } @@ -2277,12 +2225,8 @@ class Activity { $content['content'] = sprintf(t('🔁 Repeated %1$s\'s %2$s'), $mention, $act->obj['type']); } - // TODO: Deprecated - if ($act->type === 'emojiReaction') { - $content['content'] = (($act->tgt && $act->tgt['type'] === 'Image') ? '[img=32x32]' . $act->tgt['url'] . '[/img]' : '&#x' . $act->tgt['name'] . ';'); - } - if (in_array($act->type, ['EmojiReact'])) { + // Pleroma reactions $t = trim(self::get_textfield($act->data, 'content')); @@ -2306,6 +2250,8 @@ class Activity { if ($s['mid'] === $s['parent_mid']) { $s['item_thread_top'] = 1; + $s['item_nocomment'] = 0; + $s['comments_closed'] = NULL_DATE; // it is a parent node - decode the comment policy info if present if ($act->objprop('commentPolicy')) { @@ -2313,7 +2259,7 @@ class Activity { if ($until !== false) { $s['comments_closed'] = datetime_convert('UTC', 'UTC', substr($act->obj['commentPolicy'], $until + 6)); if ($s['comments_closed'] < datetime_convert()) { - $s['nocomment'] = true; + $s['item_nocomment'] = 1; } } @@ -2330,10 +2276,15 @@ class Activity { if (!array_key_exists('edited', $s)) $s['edited'] = $s['created']; - $s['title'] = (($response_activity) ? EMPTY_STR : self::bb_content($content, 'name')); - $s['summary'] = self::bb_content($content, 'summary'); + $s['title'] = (($response_activity) ? EMPTY_STR : html2plain($content['name'])); + $s['summary'] = (($content['summary'] !== $content['content']) ? html2plain($content['summary']) : ''); $s['body'] = ((self::bb_content($content, 'bbcode') && (!$response_activity)) ? self::bb_content($content, 'bbcode') : self::bb_content($content, 'content')); + // peertube quirks + if ($act->objprop('mediaType') === 'text/markdown') { + $s['body'] = markdown_to_bb($act->objprop('content')); + } + if ($act->objprop('quoteUrl')) { $quote_bbcode = self::get_quote_bbcode($act->obj['quoteUrl']); @@ -2465,7 +2416,8 @@ class Activity { } } - $tag = (($poster) ? '[video poster="' . $poster . '"]' : '[video]' ); + $tag = (($poster) ? '[video poster=\'' . $poster . '\']' : '[video]' ); + $ptr = null; if ($act->objprop('url')) { @@ -2680,6 +2632,7 @@ class Activity { } } + if (!$ap_rawmsg && array_key_exists('signed', $raw_arr)) { // zap $ap_rawmsg = json_encode($act->data, JSON_UNESCAPED_SLASHES); @@ -2713,8 +2666,7 @@ class Activity { return $hookinfo['s']; } - - static function store($channel, $observer_hash, $act, $item, $fetch_parents = true, $force = false) { + static function store($channel, $observer_hash, $act, $item, $fetch_parents = true, $force = false, $is_collection_operation = false) { $is_sys_channel = is_sys_channel($channel['channel_id']); $is_child_node = false; $parent = null; @@ -2745,6 +2697,8 @@ class Activity { } $allowed = false; + $relay = false; + $permit_mentions = intval(PConfig::Get($channel['channel_id'], 'system','permit_all_mentions') && i_am_mentioned($channel, $item)); if ($is_child_node) { @@ -2771,13 +2725,22 @@ class Activity { return; } + $relay = $channel['channel_hash'] === $parent[0]['owner_xchan']; + + if (str_contains($parent[0]['tgt_type'], 'Collection') && !$relay && !$is_collection_operation) { + logger('not a collection activity'); + return; + } + if ($parent[0]['obj_type'] === 'Question') { if (in_array($item['obj_type'], ['Note', ACTIVITY_OBJ_COMMENT]) && $item['title'] && (!$item['body'])) { $item['obj_type'] = 'Answer'; + $item['item_hidden'] = 1; } } if ($parent[0]['item_wall']) { + // set the owner to the owner of the parent $item['owner_xchan'] = $parent[0]['owner_xchan']; @@ -2958,7 +2921,10 @@ class Activity { // This isn't perfect but the best we can do for now. $item['comment_policy'] = ((isset($act->data['commentPolicy'])) ? $act->data['commentPolicy'] : 'authenticated'); - if (!empty($act->obj['context'])) { + if (!empty($act->obj['contextHistory'])) { + IConfig::Set($item, 'activitypub', 'context', $act->obj['contextHistory'], 1); + } + elseif (!empty($act->obj['context'])) { IConfig::Set($item, 'activitypub', 'context', $act->obj['context'], 1); } @@ -2993,7 +2959,7 @@ class Activity { if (intval($parent[0]['item_private']) === 0) { if (intval($item['item_private'])) { - $item['item_restrict'] = $item['item_restrict'] | 1; + $item['item_restrict'] = ((isset($item['item_restrict'])) ? $item['item_restrict'] | 1 : 1); $item['allow_cid'] = '<' . $channel['channel_hash'] . '>'; $item['allow_gid'] = $item['deny_cid'] = $item['deny_gid'] = ''; } @@ -3010,7 +2976,7 @@ class Activity { } } } - +/* if (isset($item['term']) && !PConfig::Get($channel['channel_id'], 'system', 'no_smilies')) { foreach ($item['term'] as $t) { if ($t['ttype'] === TERM_EMOJI) { @@ -3024,6 +2990,7 @@ class Activity { } } } +*/ // TODO: not implemented // self::rewrite_mentions($item); @@ -3032,17 +2999,42 @@ class Activity { dbesc($item['mid']), intval($item['uid']) ); + if ($r) { if ($item['edited'] > $r[0]['edited']) { $item['id'] = $r[0]['id']; - $x = item_store_update($item); + $x = item_store_update($item, deliver: false); } else { return; } } else { - $x = item_store($item); + $x = item_store($item, deliver: false, addAndSync: false); + } + + if ($x['success']) { + if ($relay && $channel['channel_hash'] === $x['item']['owner_xchan'] && $x['item']['verb'] !== 'Add' && !$is_collection_operation) { + $approval = Activity::addToCollection($channel, $act->data, $x['item']['parent_mid'], $x['item'], deliver: false); + } + + if (check_item_source($channel['channel_id'], $x['item']) && in_array($x['item']['obj_type'], ['Event', ACTIVITY_OBJ_EVENT])) { + event_addtocal($x['item_id'], $channel['channel_id']); + } + + tag_deliver($channel['channel_id'], $x['item_id']); + + if ($relay && $is_child_node) { + // We are the owner of this conversation, so send all received comments back downstream + Master::Summon(['Notifier', 'comment-import', $x['item_id']]); + if (!empty($approval['item_id'])) { + Master::Summon(['Notifier', 'comment-import', $approval['item_id']]); + } + } + + send_status_notifications($x['item_id'], $x['item']); + + sync_an_item($channel['channel_id'], $x['item_id']); } if ($fetch_parents && $parent && !intval($parent[0]['item_private'])) { @@ -3069,28 +3061,6 @@ class Activity { } } } - - if ($x['success']) { - - if (check_item_source($channel['channel_id'], $x['item']) && in_array($x['item']['obj_type'], ['Event', ACTIVITY_OBJ_EVENT])) { - event_addtocal($x['item_id'], $channel['channel_id']); - } - - if ($is_child_node) { - if ($item['owner_xchan'] === $channel['channel_hash']) { - // We are the owner of this conversation, so send all received comments back downstream - Master::Summon(['Notifier', 'comment-import', $x['item_id']]); - } - $r = q("select * from item where id = %d limit 1", - intval($x['item_id']) - ); - if ($r) { - send_status_notifications($x['item_id'], $r[0]); - } - } - sync_an_item($channel['channel_id'], $x['item_id']); - } - } /** @@ -3380,10 +3350,10 @@ class Activity { if (array_key_exists('startTime', $act) && strpos($act['startTime'], -1, 1) === 'Z') { $adjust = true; $event['adjust'] = 1; - $event['dtstart'] = datetime_convert('UTC', 'UTC', $event['startTime'] . (($adjust) ? '' : 'Z')); + $event['dtstart'] = datetime_convert('UTC', 'UTC', $act['startTime'] . (($adjust) ? '' : 'Z')); } if (array_key_exists('endTime', $act)) { - $event['dtend'] = datetime_convert('UTC', 'UTC', $event['endTime'] . (($adjust) ? '' : 'Z')); + $event['dtend'] = datetime_convert('UTC', 'UTC', $act['endTime'] . (($adjust) ? '' : 'Z')); } else { $event['nofinish'] = true; @@ -3669,6 +3639,8 @@ class Activity { return [ 'zot' => z_root() . '/apschema#', + + 'contextHistory' => 'https://w3id.org/fep/171b/contextHistory', 'schema' => 'http://schema.org#', 'ostatus' => 'http://ostatus.org#', 'diaspora' => 'https://diasporafoundation.org/ns/', @@ -3692,7 +3664,6 @@ class Activity { 'manuallyApprovesFollowers' => 'as:manuallyApprovesFollowers', 'Hashtag' => 'as:Hashtag' - ]; } @@ -3785,8 +3756,6 @@ class Activity { ->setObjType($object['type']) ->setParentMid(str_replace('/conversation/','/item/', $target)) ->setThrParent(str_replace('/conversation/','/item/', $target)) - // ->setApproved($object['object']['id'] ?? '') - // ->setReplyto(z_root() . '/channel/' . $channel['channel_address']) ->setTgtType('Collection') ->setTarget([ 'id' => str_replace('/item/','/conversation/', $target), @@ -3801,13 +3770,19 @@ class Activity { ->setDenyCid($sourceItem['deny_cid']) ->setDenyGid($sourceItem['deny_gid']) ->setPrivate($sourceItem['item_private']) - ->setNocomment($sourceItem['item_nocomment']) + ->setRestrict($sourceItem['item_restrict']) + ->setHidden($sourceItem['item_hidden']) + ->setDelayed($sourceItem['item_delayed']) + ->setUnpublished($sourceItem['item_unpublished']) + ->setBlocked($sourceItem['item_blocked']) + ->setType($sourceItem['item_type']) ->setCommentPolicy($sourceItem['comment_policy']) ->setPublicPolicy($sourceItem['public_policy']) ->setPostopts($sourceItem['postopts']); } $result = post_activity_item($item->toArray(), deliver: $deliver, channel: $channel, observer: $channel, addAndSync: false); logger('addToCollection: ' . print_r($result, true)); + return $result; } @@ -3839,4 +3814,39 @@ class Activity { return $result; } + + /** + * @brief Retrieves message ID from activity object. + * @param object $act Activity object + * @return string Message ID or empty string if not found + */ + public static function getMessageID($act): string + { + if (ActivityStreams::is_response_activity($act->type) || $act->objprop('type') === 'Profile') { + return $act->id; + } + + return $act->objprop('id', null) + ?? (is_string($act->obj) ? $act->obj : null) + ?? ''; + } + + /** + * @brief Retrieves the UUID from an activity object. + * @param object $act Activity object + * @return string UUID or empty string if not found + */ + public static function getUUID($act): string + { + if (ActivityStreams::is_response_activity($act->type)) { + return $act->data['uuid'] + ?? $act->data['diaspora:guid'] + ?? ''; + } + + return $act->objprop('uuid', null) + ?? $act->objprop('diaspora:guid', null) + ?? ''; + } + } diff --git a/Zotlabs/Lib/ActivityStreams.php b/Zotlabs/Lib/ActivityStreams.php index 55a1de5dd..f2b9050e3 100644 --- a/Zotlabs/Lib/ActivityStreams.php +++ b/Zotlabs/Lib/ActivityStreams.php @@ -529,8 +529,8 @@ class ActivityStreams { public function checkEddsaSignature() { $signer = $this->get_property_obj('verificationMethod', $this->sig); - $parseUrl = parse_url($signer); + $publicKey = null; if (isset($parseUrl['fragment'])) { if (str_starts_with($parseUrl['fragment'], 'z6Mk')) { diff --git a/Zotlabs/Lib/Apps.php b/Zotlabs/Lib/Apps.php index 0dc405ea9..337344645 100644 --- a/Zotlabs/Lib/Apps.php +++ b/Zotlabs/Lib/Apps.php @@ -341,7 +341,7 @@ class Apps { 'Suggest Channels' => t('Suggest Channels'), 'Login' => t('Login'), 'Channel Manager' => t('Channel Manager'), - 'Network' => t('Stream'), + 'Network' => t('Network'), 'Settings' => t('Settings'), 'Files' => t('Files'), 'Webpages' => t('Webpages'), diff --git a/Zotlabs/Lib/Connect.php b/Zotlabs/Lib/Connect.php index b8e7a5c4e..9f6d077b4 100644 --- a/Zotlabs/Lib/Connect.php +++ b/Zotlabs/Lib/Connect.php @@ -24,10 +24,16 @@ class Connect { $uid = $channel['channel_id']; - if (strpos($url,'@') === false && strpos($url,'/') === false) { + // If we get just a channel name and it is not an URL turn it into a local webbie + if (!str_contains($url, '@') && strpos($url,'/') === false) { $url = $url . '@' . App::get_hostname(); } + // Remove a possible leading @ + if (str_starts_with($url, '@')) { + $url = ltrim($url, '@'); + } + $result = [ 'success' => false, 'message' => '' ]; $my_perms = false; diff --git a/Zotlabs/Lib/DReport.php b/Zotlabs/Lib/DReport.php index ac8e0d377..99bb05293 100644 --- a/Zotlabs/Lib/DReport.php +++ b/Zotlabs/Lib/DReport.php @@ -35,7 +35,7 @@ class DReport { } function addto_update($status) { - $this->status = $this->status . ' ' . $status; + $this->status = $this->status . ', ' . $status; } @@ -89,8 +89,14 @@ class DReport { if(array_key_exists('reject',$dr) && intval($dr['reject'])) return false; - if(! ($dr['sender'])) + if (!$dr['sender']) { return false; + } + + // do not store dismissed create activities + if ($dr['status'] === 'not a collection activity') { + return false; + } // Is the sender one of our channels? diff --git a/Zotlabs/Lib/Enotify.php b/Zotlabs/Lib/Enotify.php index 9bffc53a0..6d5e249ef 100644 --- a/Zotlabs/Lib/Enotify.php +++ b/Zotlabs/Lib/Enotify.php @@ -95,8 +95,8 @@ class Enotify { if (array_key_exists('verb', $params['item'])) { // localize_item() alters the original item so make a copy first $i = $params['item']; - logger('calling localize'); - localize_item($i); + // logger('calling localize'); + // localize_item($i); $title = $i['title']; $body = $i['body']; $private = (($i['item_private']) || intval($i['item_obscured'])); @@ -131,9 +131,9 @@ class Enotify { logger('notification: mail'); $subject = sprintf( t('[$Projectname:Notify] New direct message received at %s'), $sitename); - $preamble = sprintf( t('%1$s sent you a new direct message at %2$s'), $sender['xchan_name'], $sitename); + $preamble = sprintf( t('%1$s sent you a new private message at %2$s'), $sender['xchan_name'], $sitename); $epreamble = sprintf( t('%1$s sent you %2$s.'), '[zrl=' . $sender['xchan_url'] . ']' . $sender['xchan_name'] . '[/zrl]', '[zrl=$itemlink]' . t('a direct message') . '[/zrl]'); - $sitelink = t('Please visit %s to view and/or reply to your direct messages.'); + $sitelink = t('Please visit %s to view and/or reply to your private messages.'); $tsitelink = sprintf( $sitelink, $siteurl . '/hq/' . gen_link_id($params['item']['mid'])); $hsitelink = sprintf( $sitelink, '<a href="' . $siteurl . '/hq/' . gen_link_id($params['item']['mid']) . '">' . $sitename . '</a>'); $itemlink = $siteurl . '/hq/' . gen_link_id($params['item']['mid']); @@ -146,7 +146,7 @@ class Enotify { $itemlink = $params['link']; - $action = (($moderated) ? t('requested to comment on') : t('commented on')); + $action = (($moderated) ? t('requested to post in') : t('posted in')); if(array_key_exists('item',$params)) { @@ -164,8 +164,8 @@ class Enotify { if(activity_match($params['verb'], ['Dislike', ACTIVITY_DISLIKE])) $action = (($moderated) ? t('requested to dislike') : t('disliked')); - if(activity_match($params['verb'], ACTIVITY_SHARE)) - $action = t('repeated'); + if(activity_match($params['verb'], [ACTIVITY_SHARE])) + $action = (($moderated) ? t('requested to repeat') : t('repeated')); } @@ -213,28 +213,36 @@ class Enotify { //$possess_desc = str_replace('<!item_type!>',$possess_desc); // "a post" - $dest_str = sprintf(t('%1$s %2$s [zrl=%3$s]a %4$s[/zrl]'), + $dest_str = sprintf( + t('%1$s %2$s [zrl=%3$s]a %4$s[/zrl]'), '[zrl=' . $sender['xchan_url'] . ']' . $sender['xchan_name'] . '[/zrl]', $action, $itemlink, - $item_post_type); + $item_post_type + ); // "George Bull's post" - if($p) - $dest_str = sprintf(t('%1$s %2$s [zrl=%3$s]%4$s\'s %5$s[/zrl]'), + if($p) { + $dest_str = sprintf( + t('%1$s %2$s [zrl=%3$s]%4$s\'s %5$s[/zrl]'), '[zrl=' . $sender['xchan_url'] . ']' . $sender['xchan_name'] . '[/zrl]', $action, $itemlink, - $p[0]['author']['xchan_name'], - $item_post_type); + $parent_item['author']['xchan_name'], + $item_post_type + ); + } // "your post" - if($p[0]['owner']['xchan_name'] == $p[0]['author']['xchan_name'] && intval($p[0]['item_wall'])) - $dest_str = sprintf(t('%1$s %2$s [zrl=%3$s]your %4$s[/zrl]'), + if ($parent_item['owner']['xchan_hash'] === $recip['channel_hash'] && intval($parent_item['item_wall'])) { + $dest_str = sprintf( + t('%1$s %2$s [zrl=%3$s]your %4$s[/zrl]'), '[zrl=' . $sender['xchan_url'] . ']' . $sender['xchan_name'] . '[/zrl]', $action, $itemlink, - $item_post_type); + $item_post_type + ); + } // Some mail softwares relies on subject field for threading. // So, we cannot have different subjects for notifications of the same thread. @@ -263,7 +271,7 @@ class Enotify { $itemlink = $params['link']; - if (array_key_exists('item',$params) && (activity_match($params['item']['verb'], ['Like', 'Dislike', ACTIVITY_LIKE, ACTIVITY_DISLIKE]))) { + if (array_key_exists('item',$params) && (activity_match($params['item']['verb'], ['Like', 'Dislike', ACTIVITY_LIKE, ACTIVITY_DISLIKE, 'Announce']))) { if(! $always_show_in_notices || !($vnotify & VNOTIFY_LIKE) || !feature_enabled($recip['channel_id'], 'dislike')) { logger('notification: not a visible activity. Ignoring.'); pop_lang(); @@ -308,7 +316,6 @@ class Enotify { $item_post_type = item_post_type($p[0]); // $private = $p[0]['item_private']; $parent_id = $p[0]['id']; - $parent_item = $p[0]; //$verb = ((activity_match($params['item']['verb'], ACTIVITY_DISLIKE)) ? t('disliked') : t('liked')); @@ -320,14 +327,18 @@ class Enotify { if(activity_match($params['item']['verb'], ['Dislike', ACTIVITY_DISLIKE])) $verb = (($moderated) ? t('requested to dislike') : t('disliked')); + if(activity_match($params['item']['verb'], [ACTIVITY_SHARE])) + $verb = (($moderated) ? t('requested to repeat') : t('repeated')); + // "your post" - if($p[0]['owner']['xchan_name'] === $p[0]['author']['xchan_name'] && intval($p[0]['item_wall'])) + if ($parent_item['author']['xchan_hash'] === $recip['channel_hash']) { $dest_str = sprintf(t('%1$s %2$s [zrl=%3$s]your %4$s[/zrl]'), '[zrl=' . $sender['xchan_url'] . ']' . $sender['xchan_name'] . '[/zrl]', $verb, $itemlink, $item_post_type ); + } else { pop_lang(); return; @@ -500,9 +511,14 @@ class Enotify { */ + $hash = ((in_array($params['verb'], ['Create', 'Update'])) ? $params['item']['uuid'] : $params['item']['thr_parent_uuid']); + + if (!$hash) { + $hash = new_uuid(); + } $datarray = []; - $datarray['hash'] = $params['item']['uuid'] ?? new_uuid(); + $datarray['hash'] = $hash; $datarray['sender_hash'] = $sender['xchan_hash']; $datarray['xname'] = $sender['xchan_name']; $datarray['url'] = $sender['xchan_url']; @@ -561,8 +577,9 @@ class Enotify { dbesc($datarray['otype']) ); - $r = q("select id from notify where hash = '%s' and ntype = %d and uid = %d limit 1", + $r = q("select id from notify where hash = '%s' and link = '%s' and ntype = %d and uid = %d limit 1", dbesc($datarray['hash']), + dbesc($itemlink), intval($datarray['ntype']), intval($recip['channel_id']) ); @@ -840,8 +857,8 @@ class Enotify { } else { $itemem_text = (($item['item_thread_top']) - ? (($item['obj_type'] === 'Question') ? t('created a new poll') : t('created a new post')) - : (($item['obj_type'] === 'Answer') ? sprintf( t('voted on %s\'s poll'), '[bdi]' . $item['owner']['xchan_name'] . '[/bdi]') : sprintf( t('commented on %s\'s post'), '[bdi]' . $item['owner']['xchan_name'] . '[/bdi]')) + ? (($item['obj_type'] === 'Question') ? t('started a poll') : t('started a conversation')) + : (($item['obj_type'] === 'Answer') ? sprintf( t('voted on %s\'s poll'), '[bdi]' . $item['owner']['xchan_name'] . '[/bdi]') : sprintf( t('posted in %s\'s conversation'), '[bdi]' . $item['owner']['xchan_name'] . '[/bdi]')) ); if(in_array($item['obj_type'], ['Document', 'Video', 'Audio', 'Image'])) { @@ -853,12 +870,7 @@ class Enotify { if($item['edited'] > $item['created']) { $edit = true; - if($item['item_thread_top']) { - $itemem_text = sprintf( t('edited a post dated %s'), relative_date($item['created'])); - } - else { - $itemem_text = sprintf( t('edited a comment dated %s'), relative_date($item['created'])); - } + $itemem_text = sprintf( t('edited a message dated %s'), relative_date($item['created'])); } @@ -878,7 +890,7 @@ class Enotify { 'when' => (($edit) ? datetime_convert('UTC', date_default_timezone_get(), $item['edited']) : datetime_convert('UTC', date_default_timezone_get(), $item['created'])), 'class' => (intval($item['item_unseen']) ? 'notify-unseen' : 'notify-seen'), // 'b64mid' => (($item['mid']) ? gen_link_id($item['mid']) : ''), - 'b64mid' => (($item['uuid']) ? $item['uuid'] : ''), + 'b64mid' => ((in_array($item['verb'] , ['Like', 'Dislike', 'Announce']) && !empty($item['thr_parent_uuid'])) ? $item['thr_parent_uuid'] : $item['uuid'] ?? ''), //'b64mid' => ((in_array($item['verb'], [ACTIVITY_LIKE, ACTIVITY_DISLIKE])) ? gen_link_id($item['thr_parent']) : gen_link_id($item['mid'])), 'thread_top' => (($item['item_thread_top']) ? true : false), 'message' => bbcode(escape_tags($itemem_text)), @@ -898,14 +910,13 @@ class Enotify { } static public function format_notify($tt) { - $message = trim(strip_tags(bbcode($tt['msg']))); if(strpos($message, $tt['xname']) === 0) $message = substr($message, strlen($tt['xname']) + 1); $x = [ - 'notify_link' => (($tt['ntype'] === NOTIFY_MAIL) ? $tt['link'] : z_root() . '/notify/view/' . $tt['id']), + 'notify_link' => (($tt['ntype'] === NOTIFY_INTRO) ? z_root() . '/notify/view/' . $tt['id'] : $tt['link']), 'name' => $tt['xname'], 'url' => $tt['url'], 'photo' => $tt['photo'], @@ -917,11 +928,9 @@ class Enotify { ]; return $x; - } static public function format_intros($rr) { - return [ 'notify_link' => z_root() . '/connections#' . $rr['abook_id'], 'name' => $rr['xchan_name'], diff --git a/Zotlabs/Lib/IConfig.php b/Zotlabs/Lib/IConfig.php index 74c1107f0..3540c2b24 100644 --- a/Zotlabs/Lib/IConfig.php +++ b/Zotlabs/Lib/IConfig.php @@ -13,6 +13,7 @@ class IConfig { static public function Get(&$item, $family, $key, $default = false) { $is_item = false; + $iid = null; if(is_array($item)) { $is_item = true; @@ -27,12 +28,13 @@ class IConfig { elseif(intval($item)) $iid = $item; - if(! $iid) + if (!$iid) return $default; + if(is_array($item) && array_key_exists('iconfig',$item) && is_array($item['iconfig'])) { foreach($item['iconfig'] as $c) { - if($c['iid'] == $iid && $c['cat'] == $family && $c['k'] == $key) + if (isset($c['iid']) && $c['iid'] == $iid && isset($c['cat']) && $c['cat'] == $family && isset($c['k']) && $c['k'] == $key) return $c['v']; } } diff --git a/Zotlabs/Lib/JcsEddsa2022.php b/Zotlabs/Lib/JcsEddsa2022.php index 14f16c94b..c56f093af 100644 --- a/Zotlabs/Lib/JcsEddsa2022.php +++ b/Zotlabs/Lib/JcsEddsa2022.php @@ -7,11 +7,28 @@ use StephenHill\Base58; class JcsEddsa2022 { - public function __construct() { - return $this; - } - + /** + * Sign arbitrary data with the keys of the provided channel. + * + * @param $data The data to be signed. + * @param array $channel A channel as an array of key/value pairs. + * + * @return An array with the following fields: + * - `type`: The type of signature, always `DataIntegrityProof`. + * - `cryptosuite`: The cryptographic algorithm used, always `eddsa-jcs-2022`. + * - `created`: The UTC date and timestamp when the signature was created. + * - `verificationMethod`: The channel URL and the public key separated by a `#`. + * - `proofPurpose`: The purpose of the signature, always `assertionMethod`. + * - `proofValue`: The signature itself. + * + * @throws JcsEddsa2022SignatureException if the channel is missing, or + * don't have valid keys. + */ public function sign($data, $channel): array { + if (!is_array($channel) || !isset($channel['channel_epubkey'], $channel['channel_eprvkey'])) { + throw new JcsEddsa2022SignException('Invalid or missing channel provided.'); + } + $base58 = new Base58(); $pubkey = (new Multibase())->publicKey($channel['channel_epubkey']); $options = [ diff --git a/Zotlabs/Lib/JcsEddsa2022SignException.php b/Zotlabs/Lib/JcsEddsa2022SignException.php new file mode 100644 index 000000000..81d02d631 --- /dev/null +++ b/Zotlabs/Lib/JcsEddsa2022SignException.php @@ -0,0 +1,15 @@ +<?php +/* + * SPDX-FileCopyrightText: 2025 The Hubzilla Community + * SPDX-FileContributor: Harald Eilertsen <haraldei@anduin.net> + * + * SPDX-License-Identifier: MIT + */ + +namespace Zotlabs\Lib; + +use Exception; + +class JcsEddsa2022SignException extends Exception +{ +} diff --git a/Zotlabs/Lib/Libzot.php b/Zotlabs/Lib/Libzot.php index 3d499bd08..d2d696356 100644 --- a/Zotlabs/Lib/Libzot.php +++ b/Zotlabs/Lib/Libzot.php @@ -655,6 +655,11 @@ class Libzot { return $ret; } + if (empty($arr['primary_location']['address'])) { + logger('Empty primary location address: ' . print_r($arr, true), LOGGER_DEBUG); + return $ret; + } + /** * @hooks import_xchan * Called when processing the result of zot_finger() to store the result @@ -1148,7 +1153,7 @@ class Libzot { $AS = new ActivityStreams($data); // process add/remove from collection separately, as it requires a target. - // use the raw object, as it will not include actor expansion + // use the data object, as it will not include actor expansion if (in_array($AS->type, ['Add', 'Remove']) && is_array($AS->obj) && array_key_exists('object', $AS->obj) @@ -1164,10 +1169,6 @@ class Libzot { $raw_activity = $AS->data; $AS = new ActivityStreams($raw_activity['object'], portable_id: $env['sender']); - - // Store the original activity id and type for later usage - $AS->meta['original_id'] = $original_id; - $AS->meta['original_type'] = $original_type; } if (is_array($AS->obj)) { @@ -1227,7 +1228,6 @@ class Libzot { logger('public post'); - // Public post. look for any site members who are or may be accepting posts from this sender // and who are allowed to see them based on the sender's permissions // @fixme; @@ -1294,25 +1294,6 @@ class Libzot { $item['item_private'] = 1; } - if ($item['mid'] === $item['parent_mid']) { - if (is_array($AS->obj) && array_key_exists('commentPolicy', $AS->obj)) { - $p = strstr($AS->obj['commentPolicy'], 'until='); - if ($p !== false) { - $comments_closed_at = datetime_convert('UTC', 'UTC', substr($p, 6)); - if ($comments_closed_at === $item['created']) { - $item['item_nocomment'] = 1; - } - else { - $item['comments_closed'] = $comments_closed_at; - $aritemr['comment_policy'] = trim(str_replace($p, '', $AS->obj['commentPolicy'])); - } - } - else { - $item['comment_policy'] = $AS->obj['commentPolicy']; - } - } - } - if (!empty($AS->meta['hubloc']) || $AS->sigok) { $item['item_verified'] = true; } @@ -1561,6 +1542,7 @@ class Libzot { $local_public = $public; $item_result = null; + $parent = null; $DR = new DReport(z_root(), $sender, $d, $arr['mid'], $arr['uuid']); @@ -1576,7 +1558,7 @@ class Libzot { $conversation_operation = $is_collection_operation && isset($arr['target']['attributedTo']); - if (str_contains($arr['tgt_type'], 'Collection') && !$relay && !$conversation_operation) { + if (isset($arr['tgt_type']) && str_contains($arr['tgt_type'], 'Collection') && !$relay && !$conversation_operation) { $DR->update('not a collection activity'); $result[] = $DR->get(); continue; @@ -1663,22 +1645,24 @@ class Libzot { if (intval($channel['channel_system']) && (!$arr['item_private']) && (!$relay)) { $local_public = true; - $r = q("select xchan_selfcensored from xchan where xchan_hash = '%s' limit 1", - dbesc($sender) - ); - // don't import sys channel posts from selfcensored authors - if ($r && (intval($r[0]['xchan_selfcensored']))) { + $incl = Config::Get('system','pubstream_incl'); + $excl = Config::Get('system','pubstream_excl'); + + if(($incl || $excl) && !MessageFilter::evaluate($arr, $incl, $excl)) { $local_public = false; continue; } - $incl = Config::Get('system','pubstream_incl'); - $excl = Config::Get('system','pubstream_excl'); + $r = q("select xchan_selfcensored, xchan_censored from xchan where xchan_hash = '%s'", + dbesc($sender) + ); - if(($incl || $excl) && !MessageFilter::evaluate($arr, $incl, $excl)) { + // don't import sys channel posts from selfcensored or censored authors + if ($r && ($r[0]['xchan_selfcensored'] || $r[0]['xchan_censored'])) { $local_public = false; continue; } + } $tag_delivery = tgroup_check($channel['channel_id'], $arr); @@ -1740,6 +1724,7 @@ class Libzot { // If this is a poll response, convert the obj_type to our (internal-only) "Answer" type if (in_array($arr['obj_type'], ['Note', ACTIVITY_OBJ_COMMENT]) && $arr['title'] && (!$arr['body'])) { $arr['obj_type'] = 'Answer'; + $arr['item_hidden'] = 1; } } @@ -1865,19 +1850,12 @@ class Libzot { dbesc($arr['author_xchan']) ); - // If we import an add/remove activity ($is_collection_operation) we strip off the - // add/remove part and only process the object. - // When looking up the item to pass it to the notifier for relay, we need to look up - // the original (stripped off) message id which we stored in $act->meta. - - $sql_mid = (($is_collection_operation && $relay && $channel['channel_hash'] === $arr['owner_xchan']) ? $act->meta['original_id'] : $arr['mid']); - // Reactions such as like and dislike could have an mid with /activity/ in it. // Check for both forms in order to prevent duplicates. $r = q("select * from item where mid in ('%s', '%s') and uid = %d limit 1", - dbesc($sql_mid), - dbesc(reverse_activity_mid($sql_mid)), + dbesc($arr['mid']), + dbesc(reverse_activity_mid($arr['mid'])), intval($channel['channel_id']) ); @@ -2008,10 +1986,10 @@ class Libzot { if ((is_array($stored)) && ($stored['id'] != $stored['parent']) && ($stored['author_xchan'] === $channel['channel_hash'])) { - retain_item($stored['item']['parent']); + retain_item($stored['parent']); } - if ($relay && $item_id && $stored['item_blocked'] !== ITEM_MODERATED) { + if ($relay && $item_id && item_forwardable($stored)) { logger('Invoking relay'); Master::Summon(['Notifier', 'relay', intval($item_id)]); if (!empty($approval) && $approval['item_id']) { @@ -2019,7 +1997,7 @@ class Libzot { } $DR->addto_update('relayed'); - $result[] = $DR->get(); + $result = [$DR->get()]; } } diff --git a/Zotlabs/Lib/Mailer.php b/Zotlabs/Lib/Mailer.php new file mode 100644 index 000000000..ca2d84d0d --- /dev/null +++ b/Zotlabs/Lib/Mailer.php @@ -0,0 +1,86 @@ +<?php +/** + * Mailer class for sending emails from Hubzilla. + * + * SPDX-FileCopyrightText: 2024 Hubzilla Community + * SPDX-FileContributor: Harald Eilertsen + * + * SPDX-License-Identifier: MIT + */ + +namespace Zotlabs\Lib; + +use App; + +/** + * A class for sending emails. + * + * Based on the previous `z_mail` function, but adaped and made more + * robust and usable as a class. + */ +class Mailer { + + public function __construct(private array $params = []) { + } + + public function deliver(): bool { + + if(empty($this->params['fromEmail'])) { + $this->params['fromEmail'] = Config::Get('system','from_email'); + if(empty($this->params['fromEmail'])) { + $this->params['fromEmail'] = 'Administrator@' . App::get_hostname(); + } + } + + if(empty($this->params['fromName'])) { + $this->params['fromName'] = Config::Get('system','from_email_name'); + if(empty($this->params['fromName'])) { + $this->params['fromName'] = System::get_site_name(); + } + } + + if(empty($this->params['replyTo'])) { + $this->params['replyTo'] = Config::Get('system','reply_address'); + if(empty($this->params['replyTo'])) { + $this->params['replyTo'] = 'noreply@' . App::get_hostname(); + } + } + + if (!isset($this->params['additionalMailHeader'])) { + $this->params['additionalMailHeader'] = ''; + } + + $this->params['sent'] = false; + $this->params['result'] = false; + + /** + * @hooks email_send + * * \e params @see z_mail() + */ + call_hooks('email_send', $this->params); + + if($this->params['sent']) { + logger('notification: z_mail returns ' . (($this->params['result']) ? 'success' : 'failure'), LOGGER_DEBUG); + return $this->params['result']; + } + + $fromName = email_header_encode(html_entity_decode($this->params['fromName'],ENT_QUOTES,'UTF-8'),'UTF-8'); + $messageSubject = email_header_encode(html_entity_decode($this->params['messageSubject'],ENT_QUOTES,'UTF-8'),'UTF-8'); + + $messageHeader = + $this->params['additionalMailHeader'] . + "From: $fromName <{$this->params['fromEmail']}>" . PHP_EOL . + "Reply-To: $fromName <{$this->params['replyTo']}>" . PHP_EOL . + "Content-Type: text/plain; charset=UTF-8"; + + // send the message + $res = mail( + $this->params['toEmail'], // send to address + $messageSubject, // subject + $this->params['textVersion'], + $messageHeader // message headers + ); + logger('notification: z_mail returns ' . (($res) ? 'success' : 'failure'), LOGGER_DEBUG); + return $res; + } +} diff --git a/Zotlabs/Lib/MessageFilter.php b/Zotlabs/Lib/MessageFilter.php index e7382c0d5..3f2db88c3 100644 --- a/Zotlabs/Lib/MessageFilter.php +++ b/Zotlabs/Lib/MessageFilter.php @@ -8,17 +8,18 @@ class MessageFilter { public static function evaluate($item, $incl, $excl) { - $text = prepare_text($item['body'],((isset($item['mimetype'])) ? $item['mimetype'] : 'text/bbcode')); - $text = html2plain(($item['title']) ? $item['title'] . ' ' . $text : $text); + $text = prepare_text($item['body'], ((isset($item['mimetype'])) ? $item['mimetype'] : 'text/bbcode')); + $text = html2plain((!empty($item['title'])) ? $item['title'] . ' ' . $text : $text); $lang = null; - if ((strpos($incl, 'lang=') !== false) || (strpos($excl, 'lang=') !== false) || (strpos($incl, 'lang!=') !== false) || (strpos($excl, 'lang!=') !== false)) { $lang = detect_language($text); } $tags = ((isset($item['term']) && is_array($item['term']) && count($item['term'])) ? $item['term'] : false); + $until = null; + // exclude always has priority $exclude = (($excl) ? explode("\n", $excl) : null); @@ -41,7 +42,13 @@ class MessageFilter { return false; } } - elseif (substr($word, 0, 1) === '#' && $tags) { + elseif (str_starts_with($word, 'until=')) { + $until = strtotime(trim(substr($word, 6))); + if ($until > strtotime($item['created'] . ' UTC')) { + return false; + } + } + elseif (substr($word, 0, 1) === '#' && $tags) { foreach ($tags as $t) { if ((($t['ttype'] == TERM_HASHTAG) || ($t['ttype'] == TERM_COMMUNITYTAG)) && (($t['term'] === substr($word, 1)) || (substr($word, 1) === '*'))) { return false; @@ -89,7 +96,13 @@ class MessageFilter { return true; } } - elseif (substr($word, 0, 1) === '#' && $tags) { + elseif (str_starts_with($word, 'until=')) { + $until = strtotime(trim(substr($word, 6))); + if ($until > strtotime($item['created'] . ' UTC')) { + return true; + } + } + elseif (substr($word, 0, 1) === '#' && $tags) { foreach ($tags as $t) { if ((($t['ttype'] == TERM_HASHTAG) || ($t['ttype'] == TERM_COMMUNITYTAG)) && (($t['term'] === substr($word, 1)) || (substr($word, 1) === '*'))) { return true; @@ -124,9 +137,7 @@ class MessageFilter { /** - * @brief Test for Conditional Execution conditions. Shamelessly ripped off from Code/Render/Comanche - * - * This is extensible. The first version of variable testing supports tests of the forms: + * Evaluate a conditional expression with support for AND (&&) and OR (||) operators. * * - ?foo ~= baz which will check if item.foo contains the string 'baz'; * - ?foo == baz which will check if item.foo is the string 'baz'; @@ -143,103 +154,110 @@ class MessageFilter { * * The values 0, '', an empty array, and an unset value will all evaluate to false. * - * @param string $s - * @param array $item - * @return bool + * @param string $s The condition string to evaluate. + * @param array $item The associative array providing variable values. + * @return bool True if the condition is met, false otherwise. */ - public static function test_condition($s,$item) { + public static function test_condition($s, $item) { + $s = trim($s); - if (preg_match('/(.*?)\s\~\=\s(.*?)$/', $s, $matches)) { - $x = ((array_key_exists(trim($matches[1]),$item)) ? $item[trim($matches[1])] : EMPTY_STR); - if (stripos($x, trim($matches[2])) !== false) { - return true; + // Handle OR (||) + // Split on '||' not inside quotes + $or_parts = preg_split('/\s*\|\|\s*/', $s); + if (count($or_parts) > 1) { + foreach ($or_parts as $part) { + if (self::test_condition(ltrim($part, '?+'), $item)) { + return true; + } } return false; } - if (preg_match('/(.*?)\s\=\=\s(.*?)$/', $s, $matches)) { - $x = ((array_key_exists(trim($matches[1]),$item)) ? $item[trim($matches[1])] : EMPTY_STR); - if ($x == trim($matches[2])) { - return true; + // Handle AND (&&) + // Split on '&&' not inside quotes + $and_parts = preg_split('/\s*\&\&\s*/', $s); + if (count($and_parts) > 1) { + foreach ($and_parts as $part) { + if (!self::test_condition(ltrim($part, '?+'), $item)) { + return false; + } } - return false; + return true; + } + + // Basic checks + + // Contains substring (case-insensitive) + if (preg_match('/(.*?)\s\~\=\s(.*?)$/', $s, $matches)) { + $x = ((array_key_exists(trim($matches[1]), $item)) ? $item[trim($matches[1])] : EMPTY_STR); + return (stripos($x, trim($matches[2])) !== false); + } + + // Equality + if (preg_match('/(.*?)\s\=\=\s(.*?)$/', $s, $matches)) { + $x = ((array_key_exists(trim($matches[1]), $item)) ? $item[trim($matches[1])] : EMPTY_STR); + return ($x == trim($matches[2])); } + // Inequality if (preg_match('/(.*?)\s\!\=\s(.*?)$/', $s, $matches)) { - $x = ((array_key_exists(trim($matches[1]),$item)) ? $item[trim($matches[1])] : EMPTY_STR); - if ($x != trim($matches[2])) { - return true; - } - return false; + $x = ((array_key_exists(trim($matches[1]), $item)) ? $item[trim($matches[1])] : EMPTY_STR); + return ($x != trim($matches[2])); } + // Greater than or equal if (preg_match('/(.*?)\s\>\=\s(.*?)$/', $s, $matches)) { - $x = ((array_key_exists(trim($matches[1]),$item)) ? $item[trim($matches[1])] : EMPTY_STR); - if ($x >= trim($matches[2])) { - return true; - } - return false; + $x = ((array_key_exists(trim($matches[1]), $item)) ? $item[trim($matches[1])] : EMPTY_STR); + return ($x >= trim($matches[2])); } + // Less than or equal if (preg_match('/(.*?)\s\<\=\s(.*?)$/', $s, $matches)) { - $x = ((array_key_exists(trim($matches[1]),$item)) ? $item[trim($matches[1])] : EMPTY_STR); - if ($x <= trim($matches[2])) { - return true; - } - return false; + $x = ((array_key_exists(trim($matches[1]), $item)) ? $item[trim($matches[1])] : EMPTY_STR); + return ($x <= trim($matches[2])); } + // Greater than if (preg_match('/(.*?)\s\>\s(.*?)$/', $s, $matches)) { - $x = ((array_key_exists(trim($matches[1]),$item)) ? $item[trim($matches[1])] : EMPTY_STR); - if ($x > trim($matches[2])) { - return true; - } - return false; + $x = ((array_key_exists(trim($matches[1]), $item)) ? $item[trim($matches[1])] : EMPTY_STR); + return ($x > trim($matches[2])); } - if (preg_match('/(.*?)\s\>\s(.*?)$/', $s, $matches)) { - $x = ((array_key_exists(trim($matches[1]),$item)) ? $item[trim($matches[1])] : EMPTY_STR); - if ($x < trim($matches[2])) { - return true; - } - return false; + // Less than + if (preg_match('/(.*?)\s\<\s(.*?)$/', $s, $matches)) { + $x = ((array_key_exists(trim($matches[1]), $item)) ? $item[trim($matches[1])] : EMPTY_STR); + return ($x < trim($matches[2])); } - if (preg_match('/[\$](.*?)\s\{\}\s(.*?)$/', $s, $matches)) { - $x = ((array_key_exists(trim($matches[1]),$item)) ? $item[trim($matches[1])] : EMPTY_STR); - if (is_array($x) && in_array(trim($matches[2]), $x)) { - return true; - } - return false; + // Array contains value + if (preg_match('/(.*?)\s\{\}\s(.*?)$/', $s, $matches)) { + $x = ((array_key_exists(trim($matches[1]), $item)) ? $item[trim($matches[1])] : EMPTY_STR); + return (is_array($x) && in_array(trim($matches[2]), $x)); } + // Array contains key if (preg_match('/(.*?)\s\{\*\}\s(.*?)$/', $s, $matches)) { - $x = ((array_key_exists(trim($matches[1]),$item)) ? $item[trim($matches[1])] : EMPTY_STR); - if (is_array($x) && array_key_exists(trim($matches[2]), $x)) { - return true; - } - return false; + $x = ((array_key_exists(trim($matches[1]), $item)) ? $item[trim($matches[1])] : EMPTY_STR); + return (is_array($x) && array_key_exists(trim($matches[2]), $x)); } // Ordering of this check (for falsiness) with relation to the following one (check for truthiness) is important. + // Falsy check if (preg_match('/\!(.*?)$/', $s, $matches)) { - $x = ((array_key_exists(trim($matches[1]),$item)) ? $item[trim($matches[1])] : EMPTY_STR); - if (!$x) { - return true; - } - return false; + $x = ((array_key_exists(trim($matches[1]), $item)) ? $item[trim($matches[1])] : EMPTY_STR); + return !$x; } + // Truthy check (default) if (preg_match('/(.*?)$/', $s, $matches)) { - $x = ((array_key_exists(trim($matches[1]),$item)) ? $item[trim($matches[1])] : EMPTY_STR); - if ($x) { - return true; - } - return false; + $x = ((array_key_exists(trim($matches[1]), $item)) ? $item[trim($matches[1])] : EMPTY_STR); + return (bool)$x; } + // If no conditions matched, return false return false; } + } diff --git a/Zotlabs/Lib/Text.php b/Zotlabs/Lib/Text.php index f593f9dd6..4a962670a 100644 --- a/Zotlabs/Lib/Text.php +++ b/Zotlabs/Lib/Text.php @@ -21,4 +21,13 @@ class Text { return htmlspecialchars($string, ENT_COMPAT, 'UTF-8', false); } + public static function rawurlencode_parts(string $string): string { + if (!$string) { + return EMPTY_STR; + } + + return implode('/', array_map('rawurlencode', explode('/', $string))); + } + + } diff --git a/Zotlabs/Lib/ThreadItem.php b/Zotlabs/Lib/ThreadItem.php index 90a3d3fc8..46fe6d815 100644 --- a/Zotlabs/Lib/ThreadItem.php +++ b/Zotlabs/Lib/ThreadItem.php @@ -4,8 +4,6 @@ namespace Zotlabs\Lib; use App; use Zotlabs\Access\AccessList; -use Zotlabs\Lib\Apps; -use Zotlabs\Lib\Config; require_once('include/text.php'); @@ -26,6 +24,7 @@ class ThreadItem { private $parent = null; private $conversation = null; private $redirect_url = null; + private $owner_addr = ''; private $owner_url = ''; private $owner_photo = ''; private $owner_name = ''; @@ -35,14 +34,12 @@ class ThreadItem { private $channel = null; private $display_mode = 'normal'; private $reload = ''; - private $mid_uuid_map = []; - public function __construct($data) { $this->data = $data; $this->toplevel = ($this->get_id() == $this->get_data_value('parent')); - $this->threaded = Config::Get('system','thread_allow'); + $this->threaded = ((local_channel()) ? PConfig::Get(local_channel(), 'system', 'thread_allow', true) : Config::Get('system', 'thread_allow', true)); // Prepare the children if(isset($data['children'])) { @@ -65,8 +62,6 @@ class ThreadItem { unset($this->data['children']); } - - // allow a site to configure the order and content of the reaction emoji list if($this->toplevel) { $x = Config::Get('system','reactions'); @@ -84,7 +79,7 @@ class ThreadItem { * _ false on failure */ - public function get_template_data($conv_responses, $mid_uuid_map, $thread_level=1, $conv_flags = []) { + public function get_template_data($thread_level=1, $conv_flags = []) { $result = []; $item = $this->get_data(); @@ -103,6 +98,8 @@ class ThreadItem { $conv = $this->get_conversation(); $observer = $conv->get_observer(); + $conv->mid_uuid_map[$item['mid']] = $item['uuid']; + $acl = new AccessList([]); $acl->set($item); @@ -114,7 +111,7 @@ class ThreadItem { $locktype = intval($item['item_private']); if ($locktype === 2) { - $lock = t('Direct message'); + $lock = t('Private message'); } // 0 = limited based on public policy @@ -209,9 +206,9 @@ class ThreadItem { } if (in_array($item['obj_type'], ['Event', ACTIVITY_OBJ_EVENT])) { - $response_verbs[] = 'attendyes'; - $response_verbs[] = 'attendno'; - $response_verbs[] = 'attendmaybe'; + $response_verbs[] = 'accept'; + $response_verbs[] = 'reject'; + $response_verbs[] = 'tentativeaccept'; if($this->is_commentable() && $observer) { $isevent = true; $attend = array( t('I will attend'), t('I will not attend'), t('I might attend')); @@ -222,56 +219,8 @@ class ThreadItem { $response_verbs[] = 'answer'; } - if (!feature_enabled($conv->get_profile_owner(),'dislike')) { - unset($conv_responses['dislike']); - } - - $responses = get_responses($conv_responses,$response_verbs,$this,$item); - - $my_responses = []; - foreach($response_verbs as $v) { - $my_responses[$v] = ((isset($conv_responses[$v][$item['mid'] . '-m'])) ? 1 : 0); - } - -/* - - $like_count = ((x($conv_responses['like'],$item['mid'])) ? $conv_responses['like'][$item['mid']] : ''); - $like_list = ((x($conv_responses['like'],$item['mid'])) ? $conv_responses['like'][$item['mid'] . '-l'] : ''); - if (($like_list) && (count($like_list) > MAX_LIKERS)) { - $like_list_part = array_slice($like_list, 0, MAX_LIKERS); - array_push($like_list_part, '<a class="dropdown-item" href="#" data-toggle="modal" data-target="#likeModal-' . $this->get_id() . '"><b>' . t('View all') . '</b></a>'); - } else { - $like_list_part = ''; - } - $like_button_label = tt('Like','Likes',$like_count,'noun'); - - $repeat_count = ((x($conv_responses['announce'],$item['mid'])) ? $conv_responses['announce'][$item['mid']] : ''); - $repeat_list = ((x($conv_responses['announce'],$item['mid'])) ? $conv_responses['announce'][$item['mid'] . '-l'] : ''); - if (($repeat_list) && (count($repeat_list) > MAX_LIKERS)) { - $repeat_list_part = array_slice($repeat_list, 0, MAX_LIKERS); - array_push($repeat_list_part, '<a class="dropdown-item" href="#" data-toggle="modal" data-target="#repeatModal-' . $this->get_id() . '"><b>' . t('View all') . '</b></a>'); - } else { - $repeat_list_part = ''; - } - $repeat_button_label = tt('Repeat','Repeats',$repeat_count,'noun'); - - $showdislike = ''; - if (feature_enabled($conv->get_profile_owner(),'dislike')) { - $dislike_count = ((x($conv_responses['dislike'],$item['mid'])) ? $conv_responses['dislike'][$item['mid']] : ''); - $dislike_list = ((x($conv_responses['dislike'],$item['mid'])) ? $conv_responses['dislike'][$item['mid'] . '-l'] : ''); - $dislike_button_label = tt('Dislike','Dislikes',$dislike_count,'noun'); - if (($dislike_list) && (count($dislike_list) > MAX_LIKERS)) { - $dislike_list_part = array_slice($dislike_list, 0, MAX_LIKERS); - array_push($dislike_list_part, '<a class="dropdown-item" href="#" data-toggle="modal" data-target="#dislikeModal-' . $this->get_id() . '"><b>' . t('View all') . '</b></a>'); - } else { - $dislike_list_part = ''; - } - - $showdislike = ((x($conv_responses['dislike'],$item['mid'])) ? format_like($conv_responses['dislike'][$item['mid']],$conv_responses['dislike'][$item['mid'] . '-l'],'dislike',$item['mid']) : ''); - } - - $showlike = ((x($conv_responses['like'],$item['mid'])) ? format_like($conv_responses['like'][$item['mid']],$conv_responses['like'][$item['mid'] . '-l'],'like',$item['mid']) : ''); -*/ + $response_verbs[] = 'comment'; + $responses = get_responses($response_verbs, $item); /* * We should avoid doing this all the time, but it depends on the conversation mode @@ -281,7 +230,13 @@ class ThreadItem { $this->check_wall_to_wall(); + $children = $this->get_children(); + $children_count = count($children); + if($this->is_toplevel()) { + $conv->comments_total = $responses['comment']['count'] ?? 0; + $conv->comments_loaded = $children_count; + if((local_channel() && $conv->get_profile_owner() === local_channel()) || (local_channel() && App::$module === 'pubstream')) { $star = [ 'toggle' => t("Toggle Star Status"), @@ -293,7 +248,6 @@ class ThreadItem { $is_comment = true; } - $verified = (intval($item['item_verified']) ? t('Message signature validated') : ''); $forged = ((($item['sig']) && (! intval($item['item_verified']))) ? t('Message signature incorrect') : ''); $unverified = '' ; // (($this->is_wall_to_wall() && (! intval($item['item_verified']))) ? t('Message cannot be verified') : ''); @@ -326,15 +280,11 @@ class ThreadItem { if((in_array($item['obj_type'], ['Event', ACTIVITY_OBJ_EVENT])) && $conv->get_profile_owner() == local_channel()) $has_event = true; - $like = []; - $dislike = []; $reply_to = []; $reactions_allowed = false; if($this->is_commentable() && $observer) { - $like = array( t("I like this \x28toggle\x29"), t("like")); - $dislike = array( t("I don't like this \x28toggle\x29"), t("dislike")); - $reply_to = array( t("Reply to this comment"), t("reply"), t("Reply to")); + $reply_to = array( t("Reply to this message"), t("reply"), t("Reply to")); $reactions_allowed = true; } @@ -378,9 +328,8 @@ class ThreadItem { $viewthread = z_root() . '/channel/' . $owner_address . '?f=&mid=' . urlencode(gen_link_id($item['mid'])); $comment_count_txt = ['label' => sprintf(tt('%d comment', '%d comments', $total_children), $total_children), 'count' => $total_children]; - $list_unseen_txt = $unseen_comments ? ['label' => sprintf(t('%d unseen'), $unseen_comments), 'count' => $unseen_comments] : []; - $children = $this->get_children(); + $list_unseen_txt = $unseen_comments ? ['label' => sprintf(t('%d unseen'), $unseen_comments), 'count' => $unseen_comments] : []; $has_tags = (($body['tags'] || $body['categories'] || $body['mentions'] || $body['attachments'] || $body['folders']) ? true : false); @@ -390,14 +339,7 @@ class ThreadItem { $midb64 = $item['uuid']; $mids = [ $item['uuid'] ]; - $response_mids = []; - foreach($response_verbs as $v) { - if(isset($conv_responses[$v]['mids'][$item['mid']])) { - $response_mids = array_merge($response_mids, $conv_responses[$v]['mids'][$item['mid']]); - } - } - $mids = array_merge($mids, $response_mids); $json_mids = json_encode($mids); // Pinned item processing @@ -411,11 +353,26 @@ class ThreadItem { $contact = App::$contacts[$item['author_xchan']]; } + $blog_mode = $this->get_display_mode() === 'list'; + $load_more = false; + $load_more_title = ''; + $comments_total_percent = 0; + if (($conv->comments_total > $conv->comments_loaded) || ($blog_mode && $conv->comments_total > 3)) { + // provide a load more comments button + $load_more = true; + $load_more_title = sprintf(t('Load the next few of total %d comments'), $conv->comments_total); + $comments_total_percent = round(100 * 3 / $conv->comments_total); + } + + $expand = ''; + if ($this->threaded && !empty($item['comment_count'] && !$this->is_toplevel())) { + $expand = t('Expand Replies'); + } + $tmp_item = array( 'template' => $this->get_template(), 'mode' => $mode, 'item_type' => intval($item['item_type']), - //'type' => implode("",array_slice(explode("/",$item['verb']),-1)), 'body' => $body['html'], 'tags' => $body['tags'], 'categories' => $body['categories'], @@ -424,9 +381,9 @@ class ThreadItem { 'folders' => $body['folders'], 'text' => strip_tags($body['html']), 'id' => $this->get_id(), + 'parent' => $item['parent'], 'mid' => $midb64, 'mids' => $json_mids, - 'parent' => $item['parent'], 'author_id' => (($item['author']['xchan_addr']) ? $item['author']['xchan_addr'] : $item['author']['xchan_url']), 'author_is_group_actor' => (($item['author']['xchan_pubforum']) ? t('Forum') : ''), 'isevent' => $isevent, @@ -450,16 +407,15 @@ class ThreadItem { 'sparkle' => $sparkle, 'title' => $item['title'], 'title_tosource' => get_pconfig($conv->get_profile_owner(),'system','title_tosource'), - //'ago' => relative_date($item['created']), 'app' => $item['app'], 'str_app' => sprintf( t('from %s'), $item['app']), 'isotime' => datetime_convert('UTC', date_default_timezone_get(), $item['created'], 'c'), - 'localtime' => datetime_convert('UTC', date_default_timezone_get(), $item['created'], 'r'), - 'editedtime' => (($item['edited'] != $item['created']) ? sprintf( t('last edited: %s'), datetime_convert('UTC', date_default_timezone_get(), $item['edited'], 'r')) : ''), - 'expiretime' => (($item['expires'] > NULL_DATE) ? sprintf( t('Expires: %s'), datetime_convert('UTC', date_default_timezone_get(), $item['expires'], 'r')):''), + 'localtime' => datetime_convert('UTC', date_default_timezone_get(), $item['created']), + 'editedtime' => (($item['edited'] != $item['created']) ? sprintf(t('Last edited %s'), relative_time($item['edited'])) : ''), + 'expiretime' => (($item['expires'] > NULL_DATE) ? sprintf(t('Expires %s'), relative_time($item['expires'])) : ''), 'lock' => $lock, 'locktype' => $locktype, - 'delayed' => $item['item_delayed'], + 'delayed' => (($item['item_delayed']) ? sprintf(t('Published %s'), relative_time($item['created'])) : ''), 'privacy_warning' => $privacy_warning, 'verified' => $verified, 'unverified' => $unverified, @@ -472,6 +428,7 @@ class ThreadItem { 'vote_title' => t('Voting Options'), 'is_comment' => $is_comment, 'is_new' => $is_new, + 'owner_addr' => $this->get_owner_addr(), 'owner_url' => $this->get_owner_url(), 'owner_photo' => $this->get_owner_photo(), 'owner_name' => $this->get_owner_name(), @@ -479,17 +436,16 @@ class ThreadItem { 'event' => $body['event'], 'has_tags' => $has_tags, 'reactions' => $this->reactions, -// Item toolbar buttons + // Item toolbar buttons 'emojis' => (($this->is_toplevel() && $this->is_commentable() && $observer && feature_enabled($conv->get_profile_owner(),'emojis')) ? '1' : ''), - 'like' => $like, - 'dislike' => ((feature_enabled($conv->get_profile_owner(),'dislike')) ? $dislike : ''), - 'reply_to' => (((! $this->is_toplevel()) && feature_enabled($conv->get_profile_owner(),'reply_to')) ? $reply_to : ''), + 'reply_to' => ((feature_enabled($conv->get_profile_owner(),'reply_to')) ? $reply_to : ''), 'top_hint' => t("Go to previous comment"), 'share' => $share, 'embed' => $embed, 'rawmid' => $item['mid'], + 'parent_mid' => $item['parent_mid'], 'plink' => get_plink($item), - 'edpost' => $edpost, // ((feature_enabled($conv->get_profile_owner(),'edit_posts')) ? $edpost : ''), + 'edpost' => $edpost, 'star' => ((feature_enabled($conv->get_profile_owner(),'star_posts') && ($item['item_type'] == ITEM_TYPE_POST)) ? $star : ''), 'tagger' => ((feature_enabled($conv->get_profile_owner(),'commtag')) ? $tagger : ''), 'filer' => ((feature_enabled($conv->get_profile_owner(),'filing') && ($item['item_type'] == ITEM_TYPE_POST)) ? $filer : ''), @@ -500,44 +456,24 @@ class ThreadItem { 'addtocal' => (($has_event) ? t('Add to Calendar') : ''), 'drop' => $drop, 'dropdown_extras' => $dropdown_extras, -// end toolbar buttons + // end toolbar buttons 'unseen_comments' => $unseen_comments, 'comment_count' => $total_children, 'comment_count_txt' => $comment_count_txt, 'list_unseen_txt' => $list_unseen_txt, 'markseen' => t('Mark all comments seen'), 'responses' => $responses, - 'my_responses' => $my_responses, - /* - 'like_count' => $like_count, - 'like_list' => $like_list, - 'like_list_part' => $like_list_part, - 'like_button_label' => $like_button_label, - 'like_modal_title' => t('Likes','noun'), - - 'repeat_count' => $repeat_count, - 'repeat_list' => $repeat_list, - 'repeat_list_part' => $repeat_list_part, - 'repeat_button_label' => $repeat_button_label, - 'repeat_modal_title' => t('Repeats','noun'), - - - 'dislike_modal_title' => t('Dislikes','noun'), - 'dislike_count' => ((feature_enabled($conv->get_profile_owner(),'dislike')) ? $dislike_count : ''), - 'dislike_list' => ((feature_enabled($conv->get_profile_owner(),'dislike')) ? $dislike_list : ''), - 'dislike_list_part' => ((feature_enabled($conv->get_profile_owner(),'dislike')) ? $dislike_list_part : ''), - 'dislike_button_label' => ((feature_enabled($conv->get_profile_owner(),'dislike')) ? $dislike_button_label : ''), -*/ + // 'my_responses' => $my_responses, 'modal_dismiss' => t('Close'), - // 'showlike' => $showlike, - // 'showdislike' => $showdislike, 'comment' => ($item['item_delayed'] ? '' : $this->get_comment_box()), + 'comment_hidden' => feature_enabled($conv->get_profile_owner(),'reply_to'), + 'no_comment' => (($item['item_thread_top'] && $item['item_nocomment'])? t('Comments disabled') : ''), 'previewing' => ($conv->is_preview() ? true : false ), 'preview_lbl' => t('This is an unsaved preview'), 'wait' => t('Please wait'), 'thread_level' => $thread_level, 'settings' => $settings, - 'thr_parent_uuid' => (($item['parent_mid'] != $item['thr_parent']) ? $mid_uuid_map[$item['thr_parent']] : ''), + 'thr_parent_uuid' => (($item['parent_mid'] !== $item['thr_parent'] && isset($conv->mid_uuid_map[$item['thr_parent']])) ? $conv->mid_uuid_map[$item['thr_parent']] : ''), 'contact_id' => (($contact) ? $contact['abook_id'] : ''), 'moderate' => ($item['item_blocked'] == ITEM_MODERATED), 'moderate_approve' => t('Approve'), @@ -545,7 +481,25 @@ class ThreadItem { 'rtl' => in_array($item['lang'], rtl_languages()), 'reactions_allowed' => $reactions_allowed, 'reaction_str' => [t('Add yours'), t('Remove yours')], - 'is_contained' => $this->is_toplevel() && str_contains($item['tgt_type'], 'Collection') + 'is_contained' => $this->is_toplevel() && str_contains($item['tgt_type'], 'Collection'), + 'observer_activity' => [ + 'like' => intval($item['observer_like_count'] ?? 0), + 'dislike' => intval($item['observer_dislike_count'] ?? 0), + 'announce' => intval($item['observer_announce_count'] ?? 0), + 'comment' => intval($item['observer_comment_count'] ?? 0), + 'accept' => intval($item['observer_accept_count'] ?? 0), + 'reject' => intval($item['observer_reject_count'] ?? 0), + 'tentativeaccept' => intval($item['observer_tentativeaccept_count'] ?? 0) + ], + 'threaded' => $this->threaded, + 'blog_mode' => $blog_mode, + 'collapse_comments' => t('show less'), + 'expand_comments' => $this->threaded ? t('show more') : t('show all'), + 'load_more' => $load_more, + 'load_more_title' => $load_more_title, + 'comments_total' => $conv->comments_total, + 'comments_total_percent' => $comments_total_percent, + 'expand' => $expand ); $arr = array('item' => $item, 'output' => $tmp_item); @@ -554,33 +508,19 @@ class ThreadItem { $result = $arr['output']; $result['children'] = array(); - $nb_children = count($children); - $visible_comments = Config::Get('system','expanded_comments'); - if($visible_comments === false) - $visible_comments = 3; + $visible_comments = 3; // Config::Get('system', 'expanded_comments', 3); -// needed for scroll to comment from notification but needs more work -// as we do not want to open all comments unless there is actually an #item_xx anchor -// and the url fragment is not sent to the server. -// if(in_array(\App::$module,['display','update_display'])) -// $visible_comments = 99999; - - if(($this->get_display_mode() === 'normal') && ($nb_children > 0)) { + if(($this->get_display_mode() === 'normal') && ($children_count > 0)) { foreach($children as $child) { - $result['children'][] = $child->get_template_data($conv_responses, $mid_uuid_map, $thread_level + 1,$conv_flags); + $result['children'][] = $child->get_template_data($thread_level + 1, $conv_flags); } + // Collapse - if(($nb_children > $visible_comments) || ($thread_level > 1)) { + if($thread_level === 1 && $children_count > $visible_comments) { $result['children'][0]['comment_firstcollapsed'] = true; $result['children'][0]['num_comments'] = $comment_count_txt['label']; - $result['children'][0]['hide_text'] = t('show all'); - if($thread_level > 1) { - $result['children'][$nb_children - 1]['comment_lastcollapsed'] = true; - } - else { - $result['children'][$nb_children - ($visible_comments + 1)]['comment_lastcollapsed'] = true; - } + $result['children'][$children_count - ($visible_comments + 1)]['comment_lastcollapsed'] = true; } } @@ -833,7 +773,7 @@ class ThreadItem { */ private function get_comment_box() { - if(!$this->is_toplevel() && !Config::Get('system','thread_allow')) { + if(!$this->is_toplevel()) { return ''; } @@ -869,14 +809,15 @@ class ThreadItem { '$submit' => t('Submit'), '$edbold' => t('Bold'), '$editalic' => t('Italic'), + '$edhighlighter' => t('Highlight selected text'), '$eduline' => t('Underline'), '$edquote' => t('Quote'), '$edcode' => t('Code'), - '$edimg' => t('Image'), + '$edimg' => t('Embed (existing) photo from your photo albums'), '$edatt' => t('Attach/Upload file'), '$edurl' => t('Insert Link'), '$edvideo' => t('Video'), - '$preview' => t('Preview'), // ((feature_enabled($conv->get_profile_owner(),'preview')) ? t('Preview') : ''), + '$preview' => t('Preview'), '$can_upload' => (perm_is_allowed($conv->get_profile_owner(),get_observer_hash(),'write_storage') && $conv->is_uploadable()), '$feature_encrypt' => ((feature_enabled($conv->get_profile_owner(),'content_encrypt')) ? true : false), '$encrypt' => t('Encrypt text'), @@ -897,12 +838,13 @@ class ThreadItem { } /** - * Check if we are a wall to wall item and set the relevant properties + * Check if we are a wall to wall or announce item and set the relevant properties */ protected function check_wall_to_wall() { $conv = $this->get_conversation(); $this->wall_to_wall = false; $this->owner_url = ''; + $this->owner_addr = ''; $this->owner_photo = ''; $this->owner_name = ''; @@ -911,12 +853,14 @@ class ThreadItem { if($this->is_toplevel() && ($this->get_data_value('author_xchan') != $this->get_data_value('owner_xchan'))) { $this->owner_url = chanlink_hash($this->data['owner']['xchan_hash']); + $this->owner_addr = $this->data['owner']['xchan_addr']; $this->owner_photo = $this->data['owner']['xchan_photo_s']; $this->owner_name = $this->data['owner']['xchan_name']; $this->wall_to_wall = true; } elseif($this->is_toplevel() && $this->get_data_value('verb') === 'Announce' && isset($this->data['source'])) { $this->owner_url = chanlink_hash($this->data['source']['xchan_hash']); + $this->owner_addr = $this->data['source']['xchan_addr']; $this->owner_photo = $this->data['source']['xchan_photo_s']; $this->owner_name = $this->data['source']['xchan_name']; $this->wall_to_wall = true; @@ -931,6 +875,10 @@ class ThreadItem { return $this->owner_url; } + private function get_owner_addr() { + return $this->owner_addr; + } + private function get_owner_photo() { return $this->owner_photo; } diff --git a/Zotlabs/Lib/ThreadStream.php b/Zotlabs/Lib/ThreadStream.php index fb3b6dd9b..1d968fa1a 100644 --- a/Zotlabs/Lib/ThreadStream.php +++ b/Zotlabs/Lib/ThreadStream.php @@ -24,6 +24,10 @@ class ThreadStream { private $prepared_item = ''; public $reload = ''; private $cipher = 'AES-128-CCM'; + public $mid_uuid_map = []; + public $comments_total = 0; + public $comments_loaded = 0; + // $prepared_item is for use by alternate conversation structures such as photos // wherein we've already prepared a top level item which doesn't look anything like @@ -211,16 +215,15 @@ class ThreadStream { * _ The data requested on success * _ false on failure */ - public function get_template_data($conv_responses, $mid_uuid_map) { + public function get_template_data() { $result = array(); foreach($this->threads as $item) { - if(($item->get_data_value('id') == $item->get_data_value('parent')) && $this->prepared_item) { $item_data = $this->prepared_item; } else { - $item_data = $item->get_template_data($conv_responses, $mid_uuid_map); + $item_data = $item->get_template_data(); } if(!$item_data) { logger('Failed to get item template data ('. $item->get_id() .').', LOGGER_DEBUG, LOG_ERR); diff --git a/Zotlabs/Lib/Traits/HelpHelperTrait.php b/Zotlabs/Lib/Traits/HelpHelperTrait.php index 63b0eb22e..69b3cc21b 100644 --- a/Zotlabs/Lib/Traits/HelpHelperTrait.php +++ b/Zotlabs/Lib/Traits/HelpHelperTrait.php @@ -89,7 +89,7 @@ trait HelpHelperTrait { ); return bbcode( - t("This page is not yet available in {$prefered_language_name}. See [observer.baseurl]/help/developer/developer_guide#Translations for information about how to help.") + t("This page is not yet available in {$prefered_language_name}. See [observer.baseurl]/help/developer/developers_guide#Translations for information about how to help.") ); } } diff --git a/Zotlabs/Module/Acl.php b/Zotlabs/Module/Acl.php index 1de7a3d02..cf9278f5e 100644 --- a/Zotlabs/Module/Acl.php +++ b/Zotlabs/Module/Acl.php @@ -29,11 +29,11 @@ class Acl extends \Zotlabs\Web\Controller { // logger('mod_acl: ' . print_r($_GET,true),LOGGER_DATA); - $start = (x($_REQUEST,'start') ? $_REQUEST['start'] : 0); - $count = (x($_REQUEST,'count') ? $_REQUEST['count'] : 500); - $search = (x($_REQUEST,'search') ? $_REQUEST['search'] : ''); - $type = (x($_REQUEST,'type') ? $_REQUEST['type'] : ''); - $noforums = (x($_REQUEST,'n') ? $_REQUEST['n'] : false); + $start = (!empty($_REQUEST['start']) ? $_REQUEST['start'] : 0); + $count = (!empty($_REQUEST['count']) ? $_REQUEST['count'] : 500); + $search = (!empty($_REQUEST['search']) ? $_REQUEST['search'] : ''); + $type = (!empty($_REQUEST['type']) ? $_REQUEST['type'] : ''); + $noforums = (!empty($_REQUEST['n']) ? $_REQUEST['n'] : false); // $type = @@ -53,7 +53,7 @@ class Acl extends \Zotlabs\Web\Controller { // List of channels whose connections to also suggest, // e.g. currently viewed channel or channels mentioned in a post - $extra_channels = (x($_REQUEST,'extra_channels') ? $_REQUEST['extra_channels'] : array()); + $extra_channels = (!empty($_REQUEST['extra_channels']) ? $_REQUEST['extra_channels'] : []); // The different autocomplete libraries use different names for the search text // parameter. Internally we'll use $search to represent the search text no matter @@ -416,7 +416,7 @@ class Acl extends \Zotlabs\Web\Controller { } $dirmode = intval(Config::Get('system','directory_mode')); - $search = ((x($_REQUEST,'search')) ? htmlentities($_REQUEST['search'],ENT_COMPAT,'UTF-8',false) : ''); + $search = ((!empty($_REQUEST['search'])) ? htmlentities($_REQUEST['search'], ENT_COMPAT, 'UTF-8', false) : ''); if(! $search || mb_strlen($search) < 2) return array(); @@ -446,7 +446,7 @@ class Acl extends \Zotlabs\Web\Controller { $token = Config::Get('system','realm_token'); - $count = (x($_REQUEST,'count') ? $_REQUEST['count'] : 100); + $count = (!empty($_REQUEST['count']) ? $_REQUEST['count'] : 100); if($url) { $query = $url . '?f=' . (($token) ? '&t=' . urlencode($token) : ''); $query .= '&name=' . urlencode($search) . "&limit=$count" . (($address) ? '&address=' . urlencode(punify($search)) : ''); diff --git a/Zotlabs/Module/Activity.php b/Zotlabs/Module/Activity.php index 133312e28..64da2586b 100644 --- a/Zotlabs/Module/Activity.php +++ b/Zotlabs/Module/Activity.php @@ -23,14 +23,14 @@ class Activity extends Controller { if (! $item_id) http_status_exit(404, 'Not found'); - $portable_id = EMPTY_STR; + $portable_id = null; $item_normal_extra = sprintf(" and not verb in ('Follow', 'Ignore', '%s', '%s') ", dbesc(ACTIVITY_FOLLOW), dbesc(ACTIVITY_UNFOLLOW) ); - $item_normal = " and item.item_hidden = 0 and item.item_type = 0 and item.item_unpublished = 0 and item.item_delayed = 0 and item.item_blocked = 0 $item_normal_extra "; + $item_normal = " and item.item_hidden = 0 and item.item_type = 0 and item.item_unpublished = 0 and item.item_delayed = 0 and item.item_blocked = 0 and item.item_uplink = 0 $item_normal_extra "; $i = null; @@ -166,6 +166,7 @@ class Activity extends Controller { return; } + $portable_id = null; $ob_authorize = false; $item_uid = 0; @@ -191,7 +192,7 @@ class Activity extends Controller { dbesc(ACTIVITY_UNFOLLOW) ); - $item_normal = " and item.item_hidden = 0 and item.item_type = 0 and item.item_unpublished = 0 and item.item_delayed = 0 and item.item_blocked = 0 $item_normal_extra "; + $item_normal = " and item.item_hidden = 0 and item.item_type = 0 and item.item_unpublished = 0 and item.item_delayed = 0 and item.item_blocked = 0 and item.item_uplink = 0 $item_normal_extra "; $sigdata = HTTPSig::verify(EMPTY_STR); if ($sigdata['portable_id'] && $sigdata['header_valid']) { diff --git a/Zotlabs/Module/Admin/Accounts.php b/Zotlabs/Module/Admin/Accounts.php index 6f7cb0311..108231d7d 100644 --- a/Zotlabs/Module/Admin/Accounts.php +++ b/Zotlabs/Module/Admin/Accounts.php @@ -6,133 +6,33 @@ use Zotlabs\Lib\Config; class Accounts { - /** - * @brief Handle POST actions on accounts admin page. - * - * This function is called when on the admin user/account page the form was - * submitted to handle multiple operations at once. If one of the icons next - * to an entry are pressed the function admin_page_accounts() will handle this. - * - */ const MYP = 'ZAR'; // ZAR2x const VERSION = '2.0.0'; - function post() { + /** + * Handle POST actions on accounts admin page. + */ + public function post() { - $pending = ( x($_POST, 'pending') ? $_POST['pending'] : array() ); - $users = ( x($_POST, 'user') ? $_POST['user'] : array() ); - $blocked = ( x($_POST, 'blocked') ? $_POST['blocked'] : array() ); + $pending = x($_POST, 'pending') ? $_POST['pending'] : array(); check_form_security_token_redirectOnErr('/admin/accounts', 'admin_accounts'); - $isajax = is_ajax(); - $rc = 0; - - If (!is_site_admin()) { - if ($isajax) { - killme(); - exit; - } - goaway(z_root() . '/'); - } - - if ($isajax) { - //$debug = print_r($_SESSION[self::MYP],true); - $zarop = (x($_POST['zardo']) && preg_match('/^[ad]{1,1}$/', $_POST['zardo']) ) - ? $_POST['zardo'] : ''; - // zarat arrives with leading underscore _n - $zarat = (x($_POST['zarat']) && preg_match('/^_{1,1}[0-9]{1,6}$/', $_POST['zarat']) ) - ? substr($_POST['zarat'],1) : ''; - $zarse = (x($_POST['zarse']) && preg_match('/^[0-9a-f]{8,8}$/', $_POST['zarse']) ) - ? hex2bin($_POST['zarse']) : ''; - - if ($zarop && $zarat >= 0 && $zarse && $zarse == $_SESSION[self::MYP]['h'][$zarat]) { - - // - if ($zarop == 'd') { - $rd = q("UPDATE register SET reg_vital = 0 WHERE reg_id = %d AND SUBSTR(reg_hash,1,4) = '%s' ", - intval($_SESSION[self::MYP]['i'][$zarat]), - dbesc($_SESSION[self::MYP]['h'][$zarat]) - ); - $rc = '×'; - } - elseif ($zarop == 'a') { - // approval, REGISTER_DENIED by user 0x0040, REGISTER_AGREED by user 0x0020 @Regate - $rd = q("UPDATE register SET reg_flags = (reg_flags & ~ 16), " - . " reg_vital = (CASE (reg_flags & ~ 48) WHEN 0 THEN 0 ELSE 1 END) " - . " WHERE reg_vital = 1 AND reg_id = %d AND SUBSTR(reg_hash,1,4) = '%s' ", - intval($_SESSION[self::MYP]['i'][$zarat]), - dbesc($_SESSION[self::MYP]['h'][$zarat]) - ); - $rc = 0; - $rs = q("SELECT * from register WHERE reg_id = %d ", - intval($_SESSION[self::MYP]['i'][$zarat]) - ); - if ($rs && ($rs[0]['reg_flags'] & ~ 48) == 0) { - // create account - $rc = 'ok'.$rs[0]['reg_id']; - $ac = create_account_from_register($rs[0]); - if ( $ac['success'] ) { - $rc .= '✔'; - - $auto_create = Config::Get('system','auto_channel_create',1); - - if($auto_create) { - $reonar = json_decode($rs[0]['reg_stuff'], true); - // prepare channel creation - if($reonar['chan.name']) - set_aconfig($ac['account']['account_id'], 'register', 'channel_name', $reonar['chan.name']); - - if($reonar['chan.did1']) - set_aconfig($ac['account']['account_id'], 'register', 'channel_address', $reonar['chan.did1']); - - $permissions_role = Config::Get('system','default_permissions_role'); - if($permissions_role) - set_aconfig($ac['account']['account_id'], 'register', 'permissions_role', $permissions_role); - - // create channel - $new_channel = auto_channel_create($ac['account']['account_id']); - - if($new_channel['success']) { - $rc .= ' c,ok' . $new_channel['channel']['channel_id'] . '✔'; - } - else { - $rc .= ' c ×'; - } - } - - - } - } else { - $rc='oh ×'; - } - } - echo json_encode(array('re' => $zarop, 'at' => '_' . $zarat, 'rc' => $rc)); - } + if (is_ajax()) { + $this->handle_ajax_request(); killme(); - exit; } // change to switch structure? // account block/unblock button was submitted if (x($_POST, 'page_accounts_block')) { - for ($i = 0; $i < count($users); $i++) { - // if account is blocked remove blocked bit-flag, otherwise add blocked bit-flag - $op = ($blocked[$i]) ? '& ~' : '| '; - q("UPDATE account SET account_flags = (account_flags $op%d) WHERE account_id = %d", - intval(ACCOUNT_BLOCKED), - intval($users[$i]) - ); - } - notice( sprintf( tt("%s account blocked/unblocked", "%s account blocked/unblocked", count($users)), count($users)) ); + $this->block_unblock_accounts(); } + // account delete button was submitted if (x($_POST, 'page_accounts_delete')) { - foreach ($users as $uid){ - account_remove($uid, true, false); - } - notice( sprintf( tt("%s account deleted", "%s accounts deleted", count($users)), count($users)) ); + $this->delete_accounts(); } // registration approved button was submitted if (x($_POST, 'page_accounts_approve')) { @@ -351,5 +251,143 @@ class Accounts { return $o; } + private function handle_ajax_request(): void { + //$debug = print_r($_SESSION[self::MYP],true); + $zarop = (x($_POST['zardo']) && preg_match('/^[ad]{1,1}$/', $_POST['zardo']) ) + ? $_POST['zardo'] : ''; + // zarat arrives with leading underscore _n + $zarat = (x($_POST['zarat']) && preg_match('/^_{1,1}[0-9]{1,6}$/', $_POST['zarat']) ) + ? substr($_POST['zarat'],1) : ''; + $zarse = (x($_POST['zarse']) && preg_match('/^[0-9a-f]{8,8}$/', $_POST['zarse']) ) + ? hex2bin($_POST['zarse']) : ''; + + if ($zarop && $zarat >= 0 && $zarse && $zarse == $_SESSION[self::MYP]['h'][$zarat]) { + + // + if ($zarop == 'd') { + $rd = q("UPDATE register SET reg_vital = 0 WHERE reg_id = %d AND SUBSTR(reg_hash,1,4) = '%s' ", + intval($_SESSION[self::MYP]['i'][$zarat]), + dbesc($_SESSION[self::MYP]['h'][$zarat]) + ); + $rc = '×'; + } + elseif ($zarop == 'a') { + // approval, REGISTER_DENIED by user 0x0040, REGISTER_AGREED by user 0x0020 @Regate + $rd = q("UPDATE register SET reg_flags = (reg_flags & ~ 16), " + . " reg_vital = (CASE (reg_flags & ~ 48) WHEN 0 THEN 0 ELSE 1 END) " + . " WHERE reg_vital = 1 AND reg_id = %d AND SUBSTR(reg_hash,1,4) = '%s' ", + intval($_SESSION[self::MYP]['i'][$zarat]), + dbesc($_SESSION[self::MYP]['h'][$zarat]) + ); + $rc = 0; + $rs = q("SELECT * from register WHERE reg_id = %d ", + intval($_SESSION[self::MYP]['i'][$zarat]) + ); + if ($rs && ($rs[0]['reg_flags'] & ~ 48) == 0) { + // create account + $rc = 'ok'.$rs[0]['reg_id']; + $ac = create_account_from_register($rs[0]); + if ( $ac['success'] ) { + $rc .= '✔'; + + $auto_create = Config::Get('system','auto_channel_create',1); + + if($auto_create) { + $reonar = json_decode($rs[0]['reg_stuff'], true); + // prepare channel creation + if($reonar['chan.name']) + set_aconfig($ac['account']['account_id'], 'register', 'channel_name', $reonar['chan.name']); + + if($reonar['chan.did1']) + set_aconfig($ac['account']['account_id'], 'register', 'channel_address', $reonar['chan.did1']); + + $permissions_role = Config::Get('system','default_permissions_role'); + if($permissions_role) + set_aconfig($ac['account']['account_id'], 'register', 'permissions_role', $permissions_role); + + // create channel + $new_channel = auto_channel_create($ac['account']['account_id']); + + if($new_channel['success']) { + $rc .= ' c,ok' . $new_channel['channel']['channel_id'] . '✔'; + } + else { + $rc .= ' c ×'; + } + } + + + } + } else { + $rc='oh ×'; + } + } + echo json_encode(array('re' => $zarop, 'at' => '_' . $zarat, 'rc' => $rc)); + } + } + + /** + * Block or unblock accounts given by the `user` and `blocked` POST params. + * + * The post params `user` and `blocked` must be present and arrays of equal + * lengths. The `user` array should contain account id's or the accounts to + * process, and the `blocked` array holds a corresponding boolean value to + * indicate that the account at the same offset in the `user` array is or is + * not blocked. + * + * An account that is _not_ blocked will be blocked, and accounts that _are_ + * blocked will be unblocked. + * + * @SuppressWarnings(PHPMD.ShortVariable) + */ + private function block_unblock_accounts(): void { + if (!isset($_POST['user']) || !isset($_POST['blocked'])) { + return; + } + + $users = $_POST['user']; + $blocked = $_POST['blocked']; + + if (!is_array($users) || !is_array($blocked)) { + return; + } + + foreach($users as $i => $id) { + // if account is blocked remove blocked bit-flag, otherwise add blocked bit-flag + $op = $blocked[$i] ? '& ~' : '| '; + + q("UPDATE account SET account_flags = (account_flags $op%d) WHERE account_id = %d", + intval(ACCOUNT_BLOCKED), + intval($id) + ); + } + + $count = count($users); + $fmt = tt("%s account blocked/unblocked", "%s account blocked/unblocked", $count); + notice(sprintf($fmt, $count)); + } + + /** + * Delete multiple accounts given by the `user` POST param. + */ + private function delete_accounts(): void { + if (!isset($_POST['user'])) { + return; + } + + $users = $_POST['user']; + + if (!is_array($users)) { + return; + } + + foreach ($users as $uid){ + account_remove($uid, true, false); + } + + $count = count($users); + $fmt = tt("%s account deleted", "%s accounts deleted", $count); + notice(sprintf($fmt, $count)); + } } diff --git a/Zotlabs/Module/Attach_edit.php b/Zotlabs/Module/Attach_edit.php index 5880d8f13..4cde1c168 100644 --- a/Zotlabs/Module/Attach_edit.php +++ b/Zotlabs/Module/Attach_edit.php @@ -133,6 +133,11 @@ class Attach_edit extends Controller { } $x = attach_move($channel_id, $resource, $newfolder, (($single) ? $newfilename : '')); + if (!$x['success']) { + notice($x['message'] . EOL); + goaway($return_path); + } + $actions_done .= 'move,'; } diff --git a/Zotlabs/Module/Channel.php b/Zotlabs/Module/Channel.php index f3855b7e8..e35a611d0 100644 --- a/Zotlabs/Module/Channel.php +++ b/Zotlabs/Module/Channel.php @@ -85,7 +85,7 @@ class Channel extends Controller { $headers = [ 'Content-Type' => 'application/x-zot+json', 'Digest' => HTTPSig::generate_digest_header($data), - '(request-target)' => strtolower($_SERVER['REQUEST_METHOD']) . ' ' . $_SERVER['REQUEST_URI'] + 'Date' => datetime_convert('UTC','UTC', 'now', 'D, d M Y H:i:s \\G\\M\\T') ]; $h = HTTPSig::create_sig($headers, $channel['channel_prvkey'], channel_url($channel)); @@ -298,12 +298,15 @@ class Channel extends Controller { $item_normal = item_normal(); $item_normal_update = item_normal_update(); - $sql_extra = item_permissions_sql(App::$profile['profile_uid']); + $sql_extra = ''; + $permission_sql = item_permissions_sql(App::$profile['profile_uid']); - if (feature_enabled(App::$profile['profile_uid'], 'channel_list_mode') && (!$mid)) + $page_mode = 'client'; + + $blog_mode = feature_enabled(App::$profile['profile_uid'], 'channel_list_mode') && !$mid; + if ($blog_mode) { $page_mode = 'list'; - else - $page_mode = 'client'; + } $abook_uids = " and abook.abook_channel = " . intval(App::$profile['profile_uid']) . " "; @@ -334,8 +337,8 @@ class Channel extends Controller { if (($update) && (!$load)) { if ($mid) { - $r = q("SELECT parent AS item_id, uuid from item where $identifier = '%s' and uid = %d $item_normal_update - AND item_wall = 1 $simple_update $sql_extra limit 1", + $r = q("SELECT *, parent AS item_id from item where $identifier = '%s' and uid = %d $item_normal_update + AND item_wall = 1 $simple_update $permission_sql $sql_extra limit 1", dbesc($mid), intval(App::$profile['profile_uid']) ); @@ -346,6 +349,7 @@ class Channel extends Controller { WHERE uid = %d $item_normal_update AND item_wall = 1 $simple_update AND (abook.abook_blocked = 0 or abook.abook_flags is null) + $permission_sql $sql_extra ORDER BY created DESC", intval(App::$profile['profile_uid']) @@ -382,8 +386,8 @@ class Channel extends Controller { if ($noscript_content || $load) { if ($mid) { - $r = q("SELECT parent AS item_id, uuid from item where $identifier = '%s' and uid = %d $item_normal - AND item_wall = 1 $sql_extra limit 1", + $r = q("SELECT *, parent AS item_id from item where $identifier = '%s' and uid = %d $item_normal + AND item_wall = 1 $permission_sql $sql_extra limit 1", dbesc($mid), intval(App::$profile['profile_uid']) ); @@ -392,13 +396,18 @@ class Channel extends Controller { } } else { - $r = q("SELECT DISTINCT item.parent AS item_id, $ordering FROM item - left join abook on ( item.author_xchan = abook.abook_xchan $abook_uids ) - WHERE true and item.uid = %d $item_normal + $r = q("SELECT parent AS item_id, $ordering FROM item + LEFT JOIN abook ON (item.author_xchan = abook.abook_xchan $abook_uids) + WHERE item.uid = %d + AND item.id = item.parent AND (abook.abook_blocked = 0 or abook.abook_flags is null) - AND item.item_wall = 1 AND item.item_thread_top = 1 - $sql_extra $sql_extra2 - ORDER BY $ordering DESC, item_id $pager_sql ", + AND item.item_wall = 1 + $item_normal + $permission_sql + $sql_extra + $sql_extra2 + ORDER BY $ordering DESC, item_id + $pager_sql", intval(App::$profile['profile_uid']) ); } @@ -408,19 +417,15 @@ class Channel extends Controller { } } if ($r) { - $parents_str = ids_to_querystr($r, 'item_id'); - - $r = q("SELECT item.*, item.id AS item_id - FROM item - WHERE item.uid = %d $item_normal - AND item.parent IN ( %s ) - $sql_extra ", - intval(App::$profile['profile_uid']), - dbesc($parents_str) - ); + $thr_parents = null; + if ($mid) { + $thr_parents = get_recursive_thr_parents($r[0]); + } - xchan_query($r); - $items = fetch_post_tags($r, true); + $items = items_by_parent_ids($r, $thr_parents, $permission_sql, $blog_mode); + + xchan_query($items); + $items = fetch_post_tags($items, true); $items = conv_sort($items, $ordering); if ($load && $mid && (!count($items))) { @@ -434,11 +439,8 @@ class Channel extends Controller { $items = []; } - - $mode = (($search) ? 'search' : 'channel'); - if ((!$update) && (!$load)) { // This is ugly, but we can't pass the profile_uid through the session to the ajax updater, diff --git a/Zotlabs/Module/Cloud.php b/Zotlabs/Module/Cloud.php index 510f91c1e..f9abd767a 100644 --- a/Zotlabs/Module/Cloud.php +++ b/Zotlabs/Module/Cloud.php @@ -7,6 +7,7 @@ namespace Zotlabs\Module; * Module for accessing the DAV storage area. */ +use App; use Sabre\DAV as SDAV; use Zotlabs\Web\Controller; use Zotlabs\Storage\BasicAuth; @@ -32,6 +33,15 @@ class Cloud extends Controller { */ function init() { + // TODO: why is this required? + // if we arrived at this path with any query parameters in the url, build a clean url without + // them and redirect. + + $parsed = parse_url(App::$query_string); + if (!empty($parsed['query'])) { + goaway(z_root() . '/' . $parsed['path']); + } + if (! is_dir('store')) os_mkdir('store', STORAGE_DEFAULT_PERMISSIONS, false); @@ -44,15 +54,13 @@ class Cloud extends Controller { if ($which) profile_load( $which, $profile); - - $auth = new BasicAuth(); $ob_hash = get_observer_hash(); if ($ob_hash) { if (local_channel()) { - $channel = \App::get_channel(); + $channel = App::get_channel(); $auth->setCurrentUser($channel['channel_address']); $auth->channel_account_id = $channel['channel_account_id']; $auth->channel_id = $channel['channel_id']; @@ -63,19 +71,12 @@ class Cloud extends Controller { $auth->observer = $ob_hash; } - // if we arrived at this path with any query parameters in the url, build a clean url without - // them and redirect. - if(! array_key_exists('cloud_sort',$_SESSION)) { $_SESSION['cloud_sort'] = 'name'; } $_SESSION['cloud_sort'] = ((isset($_REQUEST['sort']) && $_REQUEST['sort']) ? trim(notags($_REQUEST['sort'])) : $_SESSION['cloud_sort']); - $x = clean_query_string(); - if($x !== \App::$query_string) - goaway(z_root() . '/' . $x); - $rootDirectory = new Directory('/', [], $auth); // A SabreDAV server-object @@ -116,16 +117,16 @@ class Cloud extends Controller { function DAVException($err) { if($err instanceof \Sabre\DAV\Exception\NotFound) { - \App::$page['content'] = '<h2>404 Not found</h2>'; + App::$page['content'] = '<h2>404 Not found</h2>'; } elseif($err instanceof \Sabre\DAV\Exception\Forbidden) { - \App::$page['content'] = '<h2>403 Forbidden</h2>'; + App::$page['content'] = '<h2>403 Forbidden</h2>'; } elseif($err instanceof \Sabre\DAV\Exception\NotImplemented) { - goaway(z_root() . '/' . \App::$query_string); + goaway(z_root() . '/' . App::$query_string); } else { - \App::$page['content'] = '<h2>Unknown error</h2>'; + App::$page['content'] = '<h2>Unknown error</h2>'; } construct_page(); diff --git a/Zotlabs/Module/Conversation.php b/Zotlabs/Module/Conversation.php index c3e6ae5ec..0a1ba87d1 100644 --- a/Zotlabs/Module/Conversation.php +++ b/Zotlabs/Module/Conversation.php @@ -30,14 +30,13 @@ class Conversation extends Controller { dbesc(ACTIVITY_UNFOLLOW) ); - $item_normal = " and item.item_hidden = 0 and item.item_type = 0 and item.item_unpublished = 0 and item.item_delayed = 0 and item.item_blocked = 0 $item_normal_extra "; + $item_normal = " and item.item_hidden = 0 and item.item_type = 0 and item.item_unpublished = 0 and item.item_delayed = 0 and item.item_blocked = 0 and item.item_uplink = 0 $item_normal_extra "; $i = null; // do we have the item (at all)? - $r = q("select parent_mid from item where mid = '%s' or uuid = '%s' $item_normal order by item_wall desc limit 1", - dbesc(z_root() . '/item/' . $item_id), + $r = q("select parent_mid from item where uuid = '%s' $item_normal order by item_wall desc limit 1", dbesc($item_id) ); @@ -84,9 +83,9 @@ class Conversation extends Controller { // if we don't have a parent id belonging to the signer see if we can obtain one as a visitor that we have permission to access // with a bias towards those items owned by channels on this site (item_wall = 1) - $sql_extra = item_permissions_sql(0); - if (!$i) { + $sql_extra = item_permissions_sql(0); + $i = q("select id as item_id from item where mid = '%s' $item_normal $sql_extra order by item_wall desc limit 1", dbesc($r[0]['parent_mid']) ); diff --git a/Zotlabs/Module/Display.php b/Zotlabs/Module/Display.php index 090e0c92e..094466665 100644 --- a/Zotlabs/Module/Display.php +++ b/Zotlabs/Module/Display.php @@ -212,7 +212,7 @@ class Display extends Controller { $observer_hash = get_observer_hash(); $item_normal = item_normal(); $item_normal_update = item_normal_update(); - $sql_extra = ''; + $permission_sql = ''; $r = []; if($noscript_content || $load) { @@ -231,7 +231,7 @@ class Display extends Controller { } if(!$r) { - $sql_extra = item_permissions_sql(0, $observer_hash); + $permission_sql = item_permissions_sql(0, $observer_hash); $r = q("SELECT item.id AS item_id FROM item WHERE ((mid = '%s' @@ -239,7 +239,7 @@ class Display extends Controller { AND item.deny_gid = '' AND item_private = 0 ) AND uid IN ( " . stream_perms_api_uids(($observer_hash) ? (PERMS_NETWORK|PERMS_PUBLIC) : PERMS_PUBLIC) . " )) OR uid = %d ))) OR - (mid = '%s' $sql_extra )) + (mid = '%s' $permission_sql )) $item_normal limit 1", dbesc($target_item['parent_mid']), @@ -269,7 +269,7 @@ class Display extends Controller { } if(!$r) { - $sql_extra = item_permissions_sql(0, $observer_hash); + $permission_sql = item_permissions_sql(0, $observer_hash); $r = q("SELECT item.id as item_id from item WHERE ((parent_mid = '%s' @@ -277,7 +277,7 @@ class Display extends Controller { AND item.deny_gid = '' AND item_private = 0 ) and uid in ( " . stream_perms_api_uids(($observer_hash) ? (PERMS_NETWORK|PERMS_PUBLIC) : PERMS_PUBLIC) . " )) OR uid = %d ))) OR - (parent_mid = '%s' $sql_extra )) + (parent_mid = '%s' $permission_sql )) $item_normal limit 1", dbesc($target_item['parent_mid']), @@ -288,17 +288,12 @@ class Display extends Controller { } if($r) { - $parents_str = ids_to_querystr($r,'item_id'); - if($parents_str) { - $items = q("SELECT item.*, item.id AS item_id - FROM item - WHERE parent in ( %s ) $sql_extra $item_normal ", - dbesc($parents_str) - ); - xchan_query($items); - $items = fetch_post_tags($items,true); - $items = conv_sort($items,'created'); - } + $thr_parents = get_recursive_thr_parents($target_item); + $items = items_by_parent_ids($r, $thr_parents, $permission_sql); + + xchan_query($items); + $items = fetch_post_tags($items,true); + $items = conv_sort($items,'created'); } else { $items = array(); diff --git a/Zotlabs/Module/Dreport.php b/Zotlabs/Module/Dreport.php index 5db607545..d1ffb8027 100644 --- a/Zotlabs/Module/Dreport.php +++ b/Zotlabs/Module/Dreport.php @@ -19,7 +19,7 @@ class Dreport extends \Zotlabs\Web\Controller { $table = 'push'; if($mid) { - $i = q("select id from item where mid = '%s' and uid = %d and ( author_xchan = '%s' or ( owner_xchan = '%s' and item_wall = 1 )) ", + $i = q("select * from item where mid = '%s' and uid = %d and ( author_xchan = '%s' or ( owner_xchan = '%s' and item_wall = 1 )) ", dbesc($mid), intval($channel['channel_id']), dbesc($channel['channel_hash']), @@ -27,6 +27,12 @@ class Dreport extends \Zotlabs\Web\Controller { ); if($i) { \Zotlabs\Daemon\Master::Summon([ 'Notifier', 'edit_post', $i[0]['id'] ]); + + $relatedItem = find_related($i[0]); + if (isset($relatedItem['id'])) { + \Zotlabs\Daemon\Master::Summon([ 'Notifier', 'edit_post', $relatedItem['id'] ]); + } + } } sleep(3); diff --git a/Zotlabs/Module/Editpost.php b/Zotlabs/Module/Editpost.php index 6f524cdb8..678ceb207 100644 --- a/Zotlabs/Module/Editpost.php +++ b/Zotlabs/Module/Editpost.php @@ -86,6 +86,7 @@ class Editpost extends \Zotlabs\Web\Controller { 'bbco_autocomplete'=> 'bbcode', 'return_path' => 'hq', 'button' => t('Submit'), + 'disable_comments' => (($itm[0]['item_thread_top']) ? false : true), 'hide_voting' => true, 'hide_future' => true, 'hide_location' => true, diff --git a/Zotlabs/Module/Embedphotos.php b/Zotlabs/Module/Embedphotos.php index ed5b24724..edf2d162a 100644 --- a/Zotlabs/Module/Embedphotos.php +++ b/Zotlabs/Module/Embedphotos.php @@ -43,7 +43,7 @@ class Embedphotos extends \Zotlabs\Web\Controller { $arr = explode('/', $href); $resource_id = array_pop($arr); $x = self::photolink($resource_id); - if($x) + if($x) json_return_and_die(array('status' => true, 'photolink' => $x, 'resource_id' => $resource_id)); json_return_and_die(array('errormsg' => 'Error retrieving resource ' . $resource_id, 'status' => false)); } @@ -55,7 +55,7 @@ class Embedphotos extends \Zotlabs\Web\Controller { $output = EMPTY_STR; if($channel) { $resolution = ((feature_enabled($channel['channel_id'],'large_photos')) ? 1 : 2); - $r = q("select mimetype, height, width from photo where resource_id = '%s' and $resolution = %d and uid = %d limit 1", + $r = q("select mimetype, filename from photo where resource_id = '%s' and $resolution = %d and uid = %d limit 1", dbesc($resource), intval($resolution), intval($channel['channel_id']) @@ -63,6 +63,8 @@ class Embedphotos extends \Zotlabs\Web\Controller { if(! $r) return $output; + $filename = $r[0]['filename']; + if($r[0]['mimetype'] === 'image/jpeg') $ext = '.jpg'; elseif($r[0]['mimetype'] === 'image/png') @@ -75,7 +77,7 @@ class Embedphotos extends \Zotlabs\Web\Controller { $ext = EMPTY_STR; $output = '[zrl=' . z_root() . '/photos/' . $channel['channel_address'] . '/image/' . $resource . ']' . - '[zmg=' . $r[0]['width'] . 'x' . $r[0]['height'] . ']' . z_root() . '/photo/' . $resource . '-' . $resolution . $ext . '[/zmg][/zrl]'; + '[zmg=' . z_root() . '/photo/' . $resource . '-' . $resolution . $ext . ']' . $filename . '[/zmg][/zrl]' . "\r\n"; return $output; } diff --git a/Zotlabs/Module/File_upload.php b/Zotlabs/Module/File_upload.php index 8956ce16f..2586265f8 100644 --- a/Zotlabs/Module/File_upload.php +++ b/Zotlabs/Module/File_upload.php @@ -11,39 +11,42 @@ require_once('include/photos.php'); class File_upload extends \Zotlabs\Web\Controller { function post() { - logger('file upload: ' . print_r($_REQUEST,true)); + logger('file upload: ' . print_r($_POST,true)); logger('file upload: ' . print_r($_FILES,true)); - $channel = (($_REQUEST['channick']) ? channelx_by_nick($_REQUEST['channick']) : null); + $channel = (($_POST['channick']) ? channelx_by_nick($_POST['channick']) : null); - if(! $channel) { + if (!$channel) { logger('channel not found'); - killme(); + is_ajax() ? killme() : goaway(z_root() . '/' . $_POST['return_url']); } - $_REQUEST['source'] = 'file_upload'; + $_POST['source'] = 'file_upload'; if($channel['channel_id'] != local_channel()) { - $_REQUEST['contact_allow'] = expand_acl($channel['channel_allow_cid']); - $_REQUEST['group_allow'] = expand_acl($channel['channel_allow_gid']); - $_REQUEST['contact_deny'] = expand_acl($channel['channel_deny_cid']); - $_REQUEST['group_deny'] = expand_acl($channel['channel_deny_gid']); + $_POST['contact_allow'] = expand_acl($channel['channel_allow_cid']); + $_POST['group_allow'] = expand_acl($channel['channel_allow_gid']); + $_POST['contact_deny'] = expand_acl($channel['channel_deny_cid']); + $_POST['group_deny'] = expand_acl($channel['channel_deny_gid']); } - $_REQUEST['allow_cid'] = ((isset($_REQUEST['contact_allow'])) ? perms2str($_REQUEST['contact_allow']) : ''); - $_REQUEST['allow_gid'] = ((isset($_REQUEST['group_allow'])) ? perms2str($_REQUEST['group_allow']) : ''); - $_REQUEST['deny_cid'] = ((isset($_REQUEST['contact_deny'])) ? perms2str($_REQUEST['contact_deny']) : ''); - $_REQUEST['deny_gid'] = ((isset($_REQUEST['group_deny'])) ? perms2str($_REQUEST['group_deny']) : ''); - - if(isset($_REQUEST['filename']) && strlen($_REQUEST['filename'])) { - $r = attach_mkdir($channel, get_observer_hash(), $_REQUEST); - if($r['success']) { - $hash = $r['data']['hash']; - $sync = attach_export_data($channel,$hash); - if($sync) { - Libsync::build_sync_packet($channel['channel_id'],array('file' => array($sync))); - } - goaway(z_root() . '/' . $_REQUEST['return_url']); + $_POST['allow_cid'] = ((isset($_POST['contact_allow'])) ? perms2str($_POST['contact_allow']) : ''); + $_POST['allow_gid'] = ((isset($_POST['group_allow'])) ? perms2str($_POST['group_allow']) : ''); + $_POST['deny_cid'] = ((isset($_POST['contact_deny'])) ? perms2str($_POST['contact_deny']) : ''); + $_POST['deny_gid'] = ((isset($_POST['group_deny'])) ? perms2str($_POST['group_deny']) : ''); + + if(isset($_POST['filename']) && strlen($_POST['filename'])) { + $r = attach_mkdir($channel, get_observer_hash(), $_POST); + + if (!$r['success']) { + notice($r['message'] . EOL); + is_ajax() ? killme() : goaway(z_root() . '/' . $_POST['return_url']); + } + + $hash = $r['data']['hash']; + $sync = attach_export_data($channel,$hash); + if ($sync) { + Libsync::build_sync_packet($channel['channel_id'], ['file' => [$sync]]); } } else { @@ -90,19 +93,19 @@ class File_upload extends \Zotlabs\Web\Controller { } } - $r = attach_store($channel, get_observer_hash(), '', $_REQUEST); - if($r['success']) { - $sync = attach_export_data($channel,$r['data']['hash']); - if($sync) - Libsync::build_sync_packet($channel['channel_id'],array('file' => array($sync))); + $r = attach_store($channel, get_observer_hash(), '', $_POST); + if (!$r['success']) { + notice($r['message'] . EOL); + is_ajax() ? killme() : goaway(z_root() . '/' . $_POST['return_url']); + } + $sync = attach_export_data($channel,$r['data']['hash']); + if ($sync) { + Libsync::build_sync_packet($channel['channel_id'], ['file' => [$sync]]); } } - if(is_ajax()) - killme(); - - goaway(z_root() . '/' . $_REQUEST['return_url']); + is_ajax() ? killme() : goaway(z_root() . '/' . $_POST['return_url']); } diff --git a/Zotlabs/Module/Help.php b/Zotlabs/Module/Help.php index fc0ef2708..52e23e4e0 100644 --- a/Zotlabs/Module/Help.php +++ b/Zotlabs/Module/Help.php @@ -30,7 +30,7 @@ class Help extends \Zotlabs\Web\Controller { $this->determine_help_language(); if (empty($_REQUEST['search']) && argc() === 1) { - goaway("/help/about/about"); + goaway("/help/about"); killme(); } } @@ -85,7 +85,7 @@ class Help extends \Zotlabs\Web\Controller { } - if(argc() > 2 && argv(argc()-2) === 'assets') { + if(argc() > 2 && argv(argc()-2) === 'pic') { $path = ''; for($x = 1; $x < argc(); $x ++) { if(strlen($path)) diff --git a/Zotlabs/Module/Home.php b/Zotlabs/Module/Home.php index 691ca1af9..0dec432d0 100644 --- a/Zotlabs/Module/Home.php +++ b/Zotlabs/Module/Home.php @@ -24,9 +24,13 @@ class Home extends Controller { $key = Config::Get('system', 'prvkey'); $ret = json_encode(Libzot::site_info()); - $headers = ['Content-Type' => 'application/x-zot+json', 'Digest' => HTTPSig::generate_digest_header($ret)]; - $headers['(request-target)'] = strtolower($_SERVER['REQUEST_METHOD']) . ' ' . $_SERVER['REQUEST_URI']; - $h = HTTPSig::create_sig($headers, $key, z_root()); + $headers = [ + 'Content-Type' => 'application/x-zot+json', + 'Digest' => HTTPSig::generate_digest_header($ret), + 'Date' => datetime_convert('UTC','UTC', 'now', 'D, d M Y H:i:s \\G\\M\\T') + ]; + + $h = HTTPSig::create_sig($headers, $key, z_root()); HTTPSig::set_headers($h); echo $ret; @@ -68,9 +72,9 @@ class Home extends Controller { $o = ''; - if (x($_SESSION, 'theme')) + if (isset($_SESSION['theme'])) unset($_SESSION['theme']); - if (x($_SESSION, 'mobile_theme')) + if (isset($_SESSION['mobile_theme'])) unset($_SESSION['mobile_theme']); $splash = ((argc() > 1 && argv(1) === 'splash') ? true : false); @@ -100,14 +104,25 @@ class Home extends Controller { goaway($frontpage); } + $o .= '<div class="generic-content-wrapper">'; + $sitename = Config::Get('system', 'sitename'); - if ($sitename) - $o .= '<h1 class="home-welcome">' . sprintf(t('Welcome to %s'), $sitename) . '</h1>'; + if ($sitename) { + $o .= '<div class="section-title-wrapper">'; + $o .= '<h2 class="">' . sprintf(t('Welcome to %s'), $sitename) . '</h2>'; + $o .= '</div>'; + + } + + $o .= '<div class="section-content-wrapper">'; $loginbox = Config::Get('system', 'login_on_homepage'); if (intval($loginbox) || $loginbox === false) $o .= login(true); + $o .= '</div>'; + $o .= '</div>'; + return $o; } diff --git a/Zotlabs/Module/Hq.php b/Zotlabs/Module/Hq.php index 51caa179c..241a5101a 100644 --- a/Zotlabs/Module/Hq.php +++ b/Zotlabs/Module/Hq.php @@ -3,6 +3,7 @@ namespace Zotlabs\Module; use App; use Zotlabs\Widget\Messages; +use Zotlabs\Lib\Config; class Hq extends \Zotlabs\Web\Controller { @@ -50,12 +51,13 @@ class Hq extends \Zotlabs\Web\Controller { // select the target item with a bias to our own item $sql_order = ((local_channel() > $sys['channel_id']) ? 'DESC' : 'ASC'); - $r = q("select id, uid, mid, parent_mid, thr_parent, verb, item_type, item_deleted, item_blocked from item where uid in (%d, %d) and $identifier = '%s' order by uid $sql_order limit 2", + $r = q("select id, uid, mid, parent, parent_mid, thr_parent, verb, item_type, item_deleted, item_blocked from item where uid in (%d, %d) and $identifier = '%s' order by uid $sql_order limit 2", intval(local_channel()), intval($sys['channel_id']), dbesc($item_hash) ); + if($r) { $target_item = $r[0]; if (intval($target_item['uid']) === intval($sys['channel_id'])) { @@ -129,7 +131,7 @@ class Hq extends \Zotlabs\Web\Controller { '$nouveau' => '0', '$wall' => '0', '$page' => '1', - '$list' => ((x($_REQUEST,'list')) ? intval($_REQUEST['list']) : 0), + '$list' => ((!empty($_REQUEST['list'])) ? intval($_REQUEST['list']) : 0), '$search' => '', '$xchan' => '', '$order' => '', @@ -145,7 +147,6 @@ class Hq extends \Zotlabs\Web\Controller { } if($load && $target_item) { - if (!$sys_item) { $r = q("SELECT item.id AS item_id FROM item WHERE uid = %d @@ -199,11 +200,8 @@ class Hq extends \Zotlabs\Web\Controller { } if($r) { - $items = q("SELECT item.*, item.id AS item_id - FROM item - WHERE parent = '%s' $item_normal $sql_extra", - dbesc($r[0]['item_id']) - ); + $thr_parents = get_recursive_thr_parents($target_item); + $items = items_by_parent_ids($r, $thr_parents); xchan_query($items,true,(($sys_item) ? local_channel() : 0)); $items = fetch_post_tags($items,true); diff --git a/Zotlabs/Module/Id.php b/Zotlabs/Module/Id.php index e08568d00..004cad6e7 100644 --- a/Zotlabs/Module/Id.php +++ b/Zotlabs/Module/Id.php @@ -6,8 +6,8 @@ namespace Zotlabs\Module; * * Controller for responding to x-zot: protocol requests * x-zot:_jkfRG85nJ-714zn-LW_VbTFW8jSjGAhAydOcJzHxqHkvEHWG2E0RbA_pbch-h4R63RG1YJZifaNzgccoLa3MQ/453c1678-1a79-4af7-ab65-6b012f6cab77 - * - */ + * + */ use Zotlabs\Lib\Activity; use Zotlabs\Lib\ActivityStreams; @@ -104,7 +104,7 @@ class Id extends Controller { $headers['Content-Type'] = 'application/x-zot+json' ; $ret = json_encode($x, JSON_UNESCAPED_SLASHES); $headers['Digest'] = HTTPSig::generate_digest_header($ret); - $headers['(request-target)'] = strtolower($_SERVER['REQUEST_METHOD']) . ' ' . $_SERVER['REQUEST_URI']; + $headers['Date'] = datetime_convert('UTC','UTC', 'now', 'D, d M Y H:i:s \\G\\M\\T'); $h = HTTPSig::create_sig($headers,$chan['channel_prvkey'],channel_url($chan)); HTTPSig::set_headers($h); echo $ret; diff --git a/Zotlabs/Module/Invite.php b/Zotlabs/Module/Invite.php index 3e1e98f89..aff0e9340 100644 --- a/Zotlabs/Module/Invite.php +++ b/Zotlabs/Module/Invite.php @@ -310,9 +310,9 @@ class Invite extends Controller { function get() { - // zai1 + $channel_id = local_channel(); - if(! local_channel()) { + if ($channel_id === false || $channel_id < 1) { notice( 'ZAI0101E,' . t('Permission denied.') . EOL); return; } @@ -330,15 +330,15 @@ class Invite extends Controller { return $o; } - // invitation_by_user may still not configured, the default 'na' will tell this - // if configured, 0 disables invitations by users, other numbers are how many invites a user may propagate - $invuser = Config::Get('system','invitation_by_user', 'na'); + $ihave = $this->count_invites_by_user($channel_id); - // if the mortal user drives the invitation - If (! is_site_admin()) { - - // when not configured, 4 is the default - $invuser = ($invuser === 'na') ? 4 : $invuser; + if (is_site_admin()) { + // Admins have unlimited invites + $invuser = '∞'; + } else { + // invitation_by_user may still not configured, the default 'na' will tell this + // if configured, 0 disables invitations by users, other numbers are how many invites a user may propagate + $invuser = Config::Get('system','invitation_by_user', 4); // a config value 0 disables invitation by users if (!$invuser) { @@ -350,12 +350,6 @@ class Invite extends Controller { notice( 'ZAI0105W,' . t('You have no more invitations available') . EOL); return ''; } - - } else { - // general deity admin invite limit infinite (theoretical) - if ($invuser === 'na') Config::Set('system','invitation_by_user', 4); - // for display only - $invuser = '∞'; } // xchan record of the page observer @@ -394,17 +388,6 @@ class Invite extends Controller { } } - if ($wehave > $invmaxau) { - if (! is_site_admin()) { - $feedbk .= 'ZAI0200E,' . t('All users invitation limit exceeded.') . $eol; - } - } - - // let see how many invites currently used by the user - $r = q("SELECT count(reg_id) AS n FROM register WHERE reg_vital = 1 AND reg_byc = %d", - intval(local_channel())); - $ihave = $r ? $r[0]['n'] : 0; - $tpl = get_markup_template('invite.tpl'); $inv_rabots = array( @@ -420,11 +403,11 @@ class Invite extends Controller { 'field' => array( 'name' => 'expire', 'title' => t('duration up from now'), - 'value' => ($invexpire_n ? $invexpire_n : 2), + 'value' => 2, 'min' => '1', 'max' => '99', 'size' => '2', - 'default' => ($invexpire_u ? $invexpire_u : 'd') + 'default' => 'd', ), 'rabot' => $inv_rabots ) @@ -583,5 +566,18 @@ class Invite extends Controller { } return false; } + + /** + * Find how many invites the given channel is currently using. + * + * @param int $channel_id The id of the channel + * + * @return int Number of invites this channel is currently using. + */ + private function count_invites_by_user(int $channel): int { + $r = q("SELECT count(reg_id) AS n FROM register WHERE reg_vital = 1 AND reg_byc = %d", $channel); + + return $r ? $r[0]['n'] : 0; + } } diff --git a/Zotlabs/Module/Item.php b/Zotlabs/Module/Item.php index 8ded7c1d7..83e8d609e 100644 --- a/Zotlabs/Module/Item.php +++ b/Zotlabs/Module/Item.php @@ -53,22 +53,21 @@ class Item extends Controller { if (argc() > 1 && argv(1) !== 'drop') { - $x = q("select uid, item_wall, llink, mid, uuid from item where mid = '%s' or mid = '%s' or uuid = '%s'", - dbesc(z_root() . '/item/' . argv(1)), - dbesc(z_root() . '/activity/' . argv(1)), + $x = q("select uid, item_wall, llink, uuid from item where uuid = '%s' order by item_wall desc", dbesc(argv(1)) ); + if ($x) { - foreach ($x as $xv) { - if (intval($xv['item_wall'])) { - $c = channelx_by_n($xv['uid']); - if ($c) { - goaway(z_root() . '/channel/' . $c['channel_address'] . '?mid=' . $xv['uuid']); - } + if ($x[0]['item_wall']) { + $c = channelx_by_n($x[0]['uid']); + if ($c) { + goaway(z_root() . '/channel/' . $c['channel_address'] . '?mid=' . $x[0]['uuid']); } } + goaway($x[0]['llink']); } + http_status_exit(404, 'Not found'); } @@ -79,7 +78,7 @@ class Item extends Controller { // This will change. Figure out who the observer is and whether or not // they have permission to post here. Else ignore the post. - if ((!local_channel()) && (!remote_channel()) && (!x($_REQUEST, 'anonname'))) + if ((!local_channel()) && (!remote_channel()) && (empty($_POST['anonname']))) return; $uid = local_channel(); @@ -101,7 +100,6 @@ class Item extends Controller { $item_deleted = false; $item_hidden = false; $item_unpublished = false; - $item_delayed = false; $item_pending_remove = false; $item_blocked = false; @@ -109,12 +107,13 @@ class Item extends Controller { * Is this a reply to something? */ - $parent = ((x($_REQUEST, 'parent')) ? intval($_REQUEST['parent']) : 0); - $parent_mid = ((x($_REQUEST, 'parent_mid')) ? trim($_REQUEST['parent_mid']) : ''); - $mode = ((isset($_REQUEST['conv_mode']) && $_REQUEST['conv_mode'] === 'channel') ? 'channel' : 'network'); + $parent = ((!empty($_POST['parent'])) ? intval($_POST['parent']) : 0); + $thr_parent_id = $parent; + $parent_mid = ((!empty($_POST['parent_mid'])) ? trim($_POST['parent_mid']) : ''); + $mode = ((isset($_POST['conv_mode']) && $_POST['conv_mode'] === 'channel') ? 'channel' : 'network'); - $remote_xchan = ((x($_REQUEST, 'remote_xchan')) ? trim($_REQUEST['remote_xchan']) : false); - $r = q("select * from xchan where xchan_hash = '%s' limit 1", + $remote_xchan = ((!empty($_POST['remote_xchan'])) ? trim($_POST['remote_xchan']) : false); + $r = q("select * from xchan where xchan_hash = '%s' limit 1", dbesc($remote_xchan) ); if ($r) @@ -122,7 +121,7 @@ class Item extends Controller { else $remote_xchan = $remote_observer = false; - $profile_uid = ((x($_REQUEST, 'profile_uid')) ? intval($_REQUEST['profile_uid']) : 0); + $profile_uid = ((!empty($_POST['profile_uid'])) ? intval($_POST['profile_uid']) : 0); require_once('include/channel.php'); $sys = get_sys_channel(); @@ -132,25 +131,25 @@ class Item extends Controller { $observer = $sys; } - if (x($_REQUEST, 'dropitems')) { + if (!empty($_POST['dropitems'])) { require_once('include/items.php'); - $arr_drop = explode(',', $_REQUEST['dropitems']); + $arr_drop = explode(',', $_POST['dropitems']); drop_items($arr_drop); $json = ['success' => 1]; echo json_encode($json); killme(); } - call_hooks('post_local_start', $_REQUEST); + call_hooks('post_local_start', $_POST); - // logger('postvars ' . print_r($_REQUEST,true), LOGGER_DATA); + // logger('postvars ' . print_r($_POST,true), LOGGER_DATA); - $api_source = ((x($_REQUEST, 'api_source') && $_REQUEST['api_source']) ? true : false); + $api_source = ((!empty($_POST['api_source'])) ? true : false); - $consensus = $_REQUEST['consensus'] ?? 0; - $nocomment = $_REQUEST['nocomment'] ?? 0; + $consensus = $_POST['consensus'] ?? 0; + $nocomment = $_POST['nocomment'] ?? 0; - $is_poll = ((isset($_REQUEST['poll_answers'][0]) && $_REQUEST['poll_answers'][0]) && (isset($_REQUEST['poll_answers'][1]) && $_REQUEST['poll_answers'][1])); + $is_poll = ((isset($_POST['poll_answers'][0]) && $_POST['poll_answers'][0]) && (isset($_POST['poll_answers'][1]) && $_POST['poll_answers'][1])); // 'origin' (if non-zero) indicates that this network is where the message originated, // for the purpose of relaying comments to other conversation members. @@ -161,42 +160,43 @@ class Item extends Controller { // If you are unsure, it is prudent (and important) to leave it unset. - $origin = (($api_source && array_key_exists('origin', $_REQUEST)) ? intval($_REQUEST['origin']) : 1); + $origin = (($api_source && array_key_exists('origin', $_POST)) ? intval($_REQU_POSTEST['origin']) : 1); // To represent message-ids on other networks - this will create an iconfig record - $namespace = (($api_source && array_key_exists('namespace', $_REQUEST)) ? strip_tags($_REQUEST['namespace']) : ''); - $remote_id = (($api_source && array_key_exists('remote_id', $_REQUEST)) ? strip_tags($_REQUEST['remote_id']) : ''); + $namespace = (($api_source && array_key_exists('namespace', $_POST)) ? strip_tags($_POST['namespace']) : ''); + $remote_id = (($api_source && array_key_exists('remote_id', $_POST)) ? strip_tags($_POST['remote_id']) : ''); $owner_hash = null; - $message_id = ((x($_REQUEST, 'message_id') && $api_source) ? strip_tags($_REQUEST['message_id']) : null); - $created = ((x($_REQUEST, 'created')) ? datetime_convert(date_default_timezone_get(), 'UTC', $_REQUEST['created']) : datetime_convert()); - $post_id = ((x($_REQUEST, 'post_id')) ? intval($_REQUEST['post_id']) : 0); - $app = ((x($_REQUEST, 'source')) ? strip_tags($_REQUEST['source']) : ''); - $return_path = ((x($_REQUEST, 'return')) ? $_REQUEST['return'] : ''); - $preview = ((x($_REQUEST, 'preview')) ? intval($_REQUEST['preview']) : 0); - $categories = ((x($_REQUEST, 'category')) ? escape_tags($_REQUEST['category']) : ''); - $webpage = ((x($_REQUEST, 'webpage')) ? intval($_REQUEST['webpage']) : 0); - $item_obscured = ((x($_REQUEST, 'obscured')) ? intval($_REQUEST['obscured']) : 0); - $pagetitle = ((x($_REQUEST, 'pagetitle')) ? escape_tags($_REQUEST['pagetitle']) : ''); - $layout_mid = ((x($_REQUEST, 'layout_mid')) ? escape_tags($_REQUEST['layout_mid']) : ''); - $plink = ((x($_REQUEST, 'permalink')) ? escape_tags($_REQUEST['permalink']) : ''); - $obj_type = ((x($_REQUEST, 'obj_type')) ? escape_tags($_REQUEST['obj_type']) : 'Note'); + $message_id = ((!empty($_POST['message_id']) && $api_source) ? strip_tags($_POST['message_id']) : null); + $created = ((!empty($_POST['created'])) ? datetime_convert(date_default_timezone_get(), 'UTC', $_POST['created']) : datetime_convert()); + $post_id = ((!empty($_POST['post_id'])) ? intval($_POST['post_id']) : 0); + $app = ((!empty($_POST['source'])) ? strip_tags($_POST['source']) : ''); + $return_path = ((!empty($_POST['return'])) ? $_POST['return'] : ''); + $preview = ((!empty($_POST['preview'])) ? intval($_POST['preview']) : 0); + $categories = ((!empty($_POST['category'])) ? escape_tags($_POST['category']) : ''); + $item_type = ((!empty($_POST['webpage'])) ? intval($_POST['webpage']) : ITEM_TYPE_POST); + $item_obscured = ((!empty($_POST['obscured'])) ? intval($_POST['obscured']) : 0); + $item_delayed = ((!empty($_POST['delayed'])) ? intval($_POST['delayed']) : 0); + $pagetitle = ((!empty($_POST['pagetitle'])) ? escape_tags($_POST['pagetitle']) : ''); + $layout_mid = ((!empty($_POST['layout_mid'])) ? escape_tags($_POST['layout_mid']) : ''); + $plink = ((!empty($_POST['permalink'])) ? escape_tags($_POST['permalink']) : null); + $obj_type = ((!empty($_POST['obj_type'])) ? escape_tags($_POST['obj_type']) : 'Note'); // allow API to bulk load a bunch of imported items with sending out a bunch of posts. - $nopush = ((x($_REQUEST, 'nopush')) ? intval($_REQUEST['nopush']) : 0); + $nopush = ((!empty($_POST['nopush'])) ? intval($_POST['nopush']) : 0); /* * Check service class limits */ - if ($uid && !(x($_REQUEST, 'parent')) && !(x($_REQUEST, 'post_id'))) { - $ret = $this->item_check_service_class($uid, (($_REQUEST['webpage'] == ITEM_TYPE_WEBPAGE) ? true : false)); + if ($uid && empty($_POST['parent']) && empty($_POST['post_id'])) { + $ret = $this->item_check_service_class($uid, (($_POST['webpage'] == ITEM_TYPE_WEBPAGE) ? true : false)); if (!$ret['success']) { notice(t($ret['message']) . EOL); if ($api_source) return (['success' => false, 'message' => 'service class exception']); - if (x($_REQUEST, 'return')) + if (!empty($_POST['return'])) goaway(z_root() . "/" . $return_path); killme(); } @@ -208,7 +208,6 @@ class Item extends Controller { $expires = NULL_DATE; - $comments_closed = NULL_DATE; $route = ''; $parent_item = null; @@ -218,8 +217,8 @@ class Item extends Controller { if ($parent || $parent_mid) { - if (!x($_REQUEST, 'type')) - $_REQUEST['type'] = 'net-comment'; + if (empty($_POST['type'])) + $_POST['type'] = 'net-comment'; if ($parent) { $r = q("SELECT * FROM item WHERE id = %d LIMIT 1", @@ -255,7 +254,7 @@ class Item extends Controller { notice(t('Unable to locate original post.') . EOL); if ($api_source) return (['success' => false, 'message' => 'invalid post id']); - if (x($_REQUEST, 'return')) + if (!empty($_POST['return'])) goaway(z_root() . "/" . $return_path); killme(); } @@ -278,7 +277,7 @@ class Item extends Controller { if (!$observer) { $observer = App::get_observer(); if (!$observer) { - $observer = anon_identity_init($_REQUEST); + $observer = anon_identity_init($_POST); if ($observer) { $moderated = true; $remote_xchan = $remote_observer = $observer; @@ -290,7 +289,7 @@ class Item extends Controller { notice(t('Permission denied.') . EOL); if ($api_source) return (['success' => false, 'message' => 'permission denied']); - if (x($_REQUEST, 'return')) + if (!empty($_POST['return'])) goaway(z_root() . "/" . $return_path); killme(); } @@ -309,17 +308,17 @@ class Item extends Controller { notice(t('Permission denied.') . EOL); if ($api_source) return (['success' => false, 'message' => 'permission denied']); - if (x($_REQUEST, 'return')) + if (!empty($_POST['return'])) goaway(z_root() . "/" . $return_path); killme(); } } else { - if (!perm_is_allowed($profile_uid, $observer['xchan_hash'], ($webpage) ? 'write_pages' : 'post_wall')) { + if (!perm_is_allowed($profile_uid, $observer['xchan_hash'], (intval($item_type) === ITEM_TYPE_POST) ? 'post_wall' : 'write_pages')) { notice(t('Permission denied.') . EOL); if ($api_source) return (['success' => false, 'message' => 'permission denied']); - if (x($_REQUEST, 'return')) + if (!empty($_POST['return'])) goaway(z_root() . "/" . $return_path); killme(); } @@ -375,7 +374,7 @@ class Item extends Controller { logger("mod_item: no channel."); if ($api_source) return (['success' => false, 'message' => 'no channel']); - if (x($_REQUEST, 'return')) + if (!empty($_POST['return'])) goaway(z_root() . "/" . $return_path); killme(); } @@ -385,6 +384,7 @@ class Item extends Controller { $r = q("select * from xchan where xchan_hash = '%s' limit 1", dbesc($channel['channel_hash']) ); + if ($r && count($r)) { $owner_xchan = $r[0]; } @@ -392,7 +392,7 @@ class Item extends Controller { logger("mod_item: no owner."); if ($api_source) return (['success' => false, 'message' => 'no owner']); - if (x($_REQUEST, 'return')) + if (!empty($_POST['return'])) goaway(z_root() . "/" . $return_path); killme(); } @@ -426,17 +426,21 @@ class Item extends Controller { $view_policy = \Zotlabs\Access\PermissionLimits::Get($channel['channel_id'], 'view_stream'); $comment_policy = \Zotlabs\Access\PermissionLimits::Get($channel['channel_id'], 'post_comments'); - $public_policy = ((x($_REQUEST, 'public_policy')) ? escape_tags($_REQUEST['public_policy']) : map_scope($view_policy, true)); - if ($webpage) - $public_policy = ''; - if ($public_policy) + $public_policy = ''; + + if (intval($item_type) === ITEM_TYPE_POST) { + $public_policy = ((!empty($_POST['public_policy'])) ? escape_tags($_POST['public_policy']) : map_scope($view_policy, true)); + } + + if ($public_policy) { $private = 1; + } if ($orig_post) { $private = 0; - // webpages are allowed to change ACLs after the fact. Normal conversation items aren't. - if ($webpage) { - $acl->set_from_array($_REQUEST); + // Normal conversation items are not allowed to change ACL. + if (intval($item_type) !== ITEM_TYPE_POST) { + $acl->set_from_array($_POST); } else { $acl->set($orig_post); @@ -452,10 +456,10 @@ class Item extends Controller { $coord = $orig_post['coord']; $verb = $orig_post['verb']; $app = $orig_post['app']; - $title = escape_tags(trim($_REQUEST['title'])); - $summary = escape_tags(trim($_REQUEST['summary'])); - $body = trim($_REQUEST['body']); - $item_flags = $orig_post['item_flags']; + $title = escape_tags(trim($_POST['title'])); + $summary = escape_tags(trim($_POST['summary'])); + $body = trim($_POST['body']); + $item_flags = $orig_post['item_flags']; $item_origin = $orig_post['item_origin']; $item_unseen = $orig_post['item_unseen']; $item_starred = $orig_post['item_starred']; @@ -469,7 +473,7 @@ class Item extends Controller { $item_mentionsme = $orig_post['item_mentionsme']; $item_nocomment = $orig_post['item_nocomment']; $item_obscured = $orig_post['item_obscured']; - $item_verified = $orig_post['item_verified']; + $item_verified = $orig_post['item_verified']; $item_retained = $orig_post['item_retained']; $item_rss = $orig_post['item_rss']; $item_deleted = $orig_post['item_deleted']; @@ -488,14 +492,15 @@ class Item extends Controller { $thr_parent = $orig_post['thr_parent']; $parent_mid = $orig_post['parent_mid']; $plink = $orig_post['plink']; + $owner_hash = $orig_post['owner_xchan']; } else { if (!$walltowall) { - if ((array_key_exists('contact_allow', $_REQUEST)) - || (array_key_exists('group_allow', $_REQUEST)) - || (array_key_exists('contact_deny', $_REQUEST)) - || (array_key_exists('group_deny', $_REQUEST))) { - $acl->set_from_array($_REQUEST); + if ((array_key_exists('contact_allow', $_POST)) + || (array_key_exists('group_allow', $_POST)) + || (array_key_exists('contact_deny', $_POST)) + || (array_key_exists('group_deny', $_POST))) { + $acl->set_from_array($_POST); } elseif (!$api_source) { @@ -510,16 +515,16 @@ class Item extends Controller { } - $location = ((isset($_REQUEST['location'])) ? notags(trim($_REQUEST['location'])) : ''); - $coord = ((isset($_REQUEST['coord'])) ? notags(trim($_REQUEST['coord'])) : ''); - $verb = ((isset($_REQUEST['verb'])) ? notags(trim($_REQUEST['verb'])) : ''); - $title = ((isset($_REQUEST['title'])) ? escape_tags(trim($_REQUEST['title'])) : ''); - $summary = ((isset($_REQUEST['summary'])) ? escape_tags(trim($_REQUEST['summary'])) : ''); - $body = ((isset($_REQUEST['body'])) ? trim($_REQUEST['body']) : ''); - $body .= ((isset($_REQUEST['attachment'])) ? trim($_REQUEST['attachment']) : ''); + $location = ((isset($_POST['location'])) ? notags(trim($_POST['location'])) : ''); + $coord = ((isset($_POST['coord'])) ? notags(trim($_POST['coord'])) : ''); + $verb = ((isset($_POST['verb'])) ? notags(trim($_POST['verb'])) : ''); + $title = ((isset($_POST['title'])) ? escape_tags(trim($_POST['title'])) : ''); + $summary = ((isset($_POST['summary'])) ? escape_tags(trim($_POST['summary'])) : ''); + $body = ((isset($_POST['body'])) ? trim($_POST['body']) : ''); + $body .= ((isset($_POST['attachment'])) ? trim($_POST['attachment']) : ''); $postopts = ''; - $allow_empty = ((array_key_exists('allow_empty', $_REQUEST)) ? intval($_REQUEST['allow_empty']) : 0); + $allow_empty = ((array_key_exists('allow_empty', $_POST)) ? intval($_POST['allow_empty']) : 0); $private = ((isset($private) && $private) ? $private : intval($acl->is_private() || ($public_policy))); @@ -530,7 +535,7 @@ class Item extends Controller { $private = intval($parent_item['item_private']); $public_policy = $parent_item['public_policy']; $owner_hash = $parent_item['owner_xchan']; - $webpage = $parent_item['item_type']; + $item_type = $parent_item['item_type']; } @@ -541,7 +546,7 @@ class Item extends Controller { info(t('Empty post discarded.') . EOL); if ($api_source) return (['success' => false, 'message' => 'no content']); - if (x($_REQUEST, 'return')) + if (!empty($_POST['return'])) goaway(z_root() . "/" . $return_path); killme(); } @@ -549,15 +554,15 @@ class Item extends Controller { if (feature_enabled($profile_uid, 'content_expire')) { - if (x($_REQUEST, 'expire')) { - $expires = datetime_convert(date_default_timezone_get(), 'UTC', $_REQUEST['expire']); + if (!empty($_POST['expire'])) { + $expires = datetime_convert(date_default_timezone_get(), 'UTC', $_POST['expire']); if ($expires <= datetime_convert()) $expires = NULL_DATE; } } - $mimetype = ((isset($_REQUEST['mimetype'])) ? notags(trim($_REQUEST['mimetype'])) : ''); + $mimetype = ((isset($_POST['mimetype'])) ? notags(trim($_POST['mimetype'])) : ''); if (!$mimetype) $mimetype = 'text/bbcode'; @@ -591,7 +596,7 @@ class Item extends Controller { $is_group = get_pconfig($profile_uid, 'system', 'group_actor'); - if ($is_group && $walltowall && !$walltowall_comment && !$webpage) { + if ($is_group && $walltowall && !$walltowall_comment && (intval($item_type) === ITEM_TYPE_POST)) { $groupww = true; $str_contact_allow = $owner_xchan['xchan_hash']; $str_group_allow = ''; @@ -790,22 +795,21 @@ class Item extends Controller { } $item_unseen = ((local_channel() != $profile_uid) ? 1 : 0); - $item_wall = ((isset($_REQUEST['type']) && ($_REQUEST['type'] === 'wall' || $_REQUEST['type'] === 'wall-comment')) ? 1 : 0); + $item_wall = ((isset($_POST['type']) && ($_POST['type'] === 'wall' || $_POST['type'] === 'wall-comment')) ? 1 : 0); $item_origin = (($origin) ? 1 : 0); $item_consensus = (($consensus) ? 1 : 0); $item_nocomment = (($nocomment) ? 1 : 0); + $comments_closed = (($nocomment) ? $comments_closed : NULL_DATE); // determine if this is a wall post + if (in_array($item_type, [ITEM_TYPE_POST, ITEM_TYPE_CARD, ITEM_TYPE_ARTICLE])) { + $item_wall = 1; + } + if ($parent) { $item_wall = $parent_item['item_wall']; } - else { - if (!$webpage) { - $item_wall = 1; - } - } - if ($moderated) { $item_blocked = ITEM_MODERATED; @@ -822,36 +826,34 @@ class Item extends Controller { $mid = $mid ?? z_root() . '/item/' . $uuid; - // Set the conversation target. - if (empty($owner_hash)) { - $owner_hash = $owner_xchan['xchan_hash']; - } - - if ($owner_hash === $channel['channel_hash']) { - $attributedTo = z_root() . '/channel/' . $channel['channel_address']; - - $conversation = isset($parent_item) ? $parent_item['mid'] : $mid; - $datarray['target'] = [ - 'id' => str_replace('/item/', '/conversation/', $conversation), - 'type' => 'Collection', - 'attributedTo' => $attributedTo, - ]; - $datarray['tgt_type'] = 'Collection'; - } - elseif (!empty($parent_item['target'])) { - $datarray['target'] = $parent_item['target']; - $datarray['tgt_type'] = $parent_item['tgt_type']; - } + if (empty($owner_hash)) { + $owner_hash = $owner_xchan['xchan_hash']; + } + // Set the conversation target. + if ($owner_hash === $channel['channel_hash']) { + $attributedTo = z_root() . '/channel/' . $channel['channel_address']; + $conversation = isset($parent_item) ? $parent_item['mid'] : $mid; + $datarray['target'] = [ + 'id' => str_replace('/item/', '/conversation/', $conversation), + 'type' => 'Collection', + 'attributedTo' => $attributedTo, + ]; + $datarray['tgt_type'] = 'Collection'; + } + elseif (!empty($parent_item['target'])) { + $datarray['target'] = $parent_item['target']; + $datarray['tgt_type'] = $parent_item['tgt_type']; + } if ($is_poll) { $poll = [ 'question' => $body, - 'answers' => $_REQUEST['poll_answers'], - 'multiple_answers' => $_REQUEST['poll_multiple_answers'], - 'expire_value' => $_REQUEST['poll_expire_value'], - 'expire_unit' => $_REQUEST['poll_expire_unit'] + 'answers' => $_POST['poll_answers'], + 'multiple_answers' => $_POST['poll_multiple_answers'], + 'expire_value' => $_POST['poll_expire_value'], + 'expire_unit' => $_POST['poll_expire_unit'] ]; $obj = $this->extract_poll_data($poll, ['item_private' => $private, 'allow_cid' => $str_contact_allow, 'allow_gid' => $str_contact_deny]); } @@ -886,19 +888,13 @@ class Item extends Controller { if ($parent_item) $parent_mid = $parent_item['mid']; - // Fallback so that we always have a thr_parent if (!$thr_parent) $thr_parent = $mid; - $item_thread_top = ((!$parent) ? 1 : 0); - if ((!$plink) && ($item_thread_top)) { - $plink = $mid; - } - if (isset($datarray['obj']) && $datarray['obj']) { $datarray['obj']['id'] = $mid; } @@ -936,7 +932,7 @@ class Item extends Controller { $datarray['item_unseen'] = intval($item_unseen); $datarray['item_wall'] = intval($item_wall); $datarray['item_origin'] = intval($item_origin); - $datarray['item_type'] = $webpage; + $datarray['item_type'] = $item_type; $datarray['item_private'] = intval($private); $datarray['item_thread_top'] = intval($item_thread_top); $datarray['item_starred'] = intval($item_starred); @@ -961,7 +957,7 @@ class Item extends Controller { $datarray['public_policy'] = $public_policy; $datarray['comment_policy'] = map_scope($comment_policy); $datarray['term'] = array_unique($post_tags, SORT_REGULAR); - $datarray['plink'] = $plink; + $datarray['plink'] = $plink ?? $mid; $datarray['route'] = $route; // A specific ACL over-rides public_policy completely @@ -1015,14 +1011,14 @@ class Item extends Controller { call_hooks('post_local', $datarray); - if (x($datarray, 'cancel')) { + if (!empty($datarray['cancel'])) { logger('mod_item: post cancelled by plugin or duplicate suppressed.'); if ($return_path) goaway(z_root() . "/" . $return_path); if ($api_source) return (['success' => false, 'message' => 'operation cancelled']); $json = ['cancel' => 1]; - $json['reload'] = z_root() . '/' . $_REQUEST['jsreload']; + $json['reload'] = z_root() . '/' . $_POST['jsreload']; echo json_encode($json); killme(); } @@ -1031,8 +1027,8 @@ class Item extends Controller { if (mb_strlen($datarray['title']) > 191) $datarray['title'] = mb_substr($datarray['title'], 0, 191); - if ($webpage) { - IConfig::Set($datarray, 'system', webpage_to_namespace($webpage), + if (intval($item_type) !== ITEM_TYPE_POST) { + IConfig::Set($datarray, 'system', item_type_to_namespace($item_type), (($pagetitle) ? $pagetitle : basename($datarray['mid'])), true); } elseif ($namespace) { @@ -1072,7 +1068,7 @@ class Item extends Controller { if ($api_source) return ($x); - if ((x($_REQUEST, 'return')) && strlen($return_path)) { + if ((!empty($_POST['return'])) && strlen($return_path)) { logger('return: ' . $return_path); if ($return_path === 'hq') { @@ -1225,11 +1221,12 @@ class Item extends Controller { $json = [ 'success' => 1, 'id' => $post_id, + 'thr_parent_id' => $thr_parent_id, 'html' => conversation($item, $mode, true, 'r_preview'), ]; - if (x($_REQUEST, 'jsreload') && strlen($_REQUEST['jsreload'])) - $json['reload'] = z_root() . '/' . $_REQUEST['jsreload']; + if (!empty($_POST['jsreload'])) + $json['reload'] = z_root() . '/' . $_POST['jsreload']; logger('post_json: ' . print_r($json, true), LOGGER_DEBUG); @@ -1246,8 +1243,6 @@ class Item extends Controller { if ((argc() == 3) && (argv(1) === 'drop') && intval(argv(2))) { - require_once('include/items.php'); - $i = q("select * from item where id = %d limit 1", intval(argv(2)) ); @@ -1506,7 +1501,7 @@ class Item extends Controller { dbesc(ACTIVITY_UNFOLLOW) ); - $item_normal = " and item.item_hidden = 0 and item.item_type = 0 and item.item_unpublished = 0 and item.item_delayed = 0 and item.item_blocked = 0 $item_normal_extra "; + $item_normal = " and item.item_hidden = 0 and item.item_type = 0 and item.item_unpublished = 0 and item.item_delayed = 0 and item.item_blocked = 0 and item.item_uplink = 0 $item_normal_extra "; $i = null; @@ -1622,7 +1617,7 @@ class Item extends Controller { dbesc(ACTIVITY_UNFOLLOW) ); - $item_normal = " and item.item_hidden = 0 and item.item_type = 0 and item.item_unpublished = 0 and item.item_delayed = 0 and item.item_blocked = 0 $item_normal_extra "; + $item_normal = " and item.item_hidden = 0 and item.item_type = 0 and item.item_unpublished = 0 and item.item_delayed = 0 and item.item_blocked = 0 and item.item_uplink = 0 $item_normal_extra "; $i = null; diff --git a/Zotlabs/Module/Lang.php b/Zotlabs/Module/Lang.php index fe185ebea..1eeb29363 100644 --- a/Zotlabs/Module/Lang.php +++ b/Zotlabs/Module/Lang.php @@ -65,8 +65,22 @@ class Lang extends Controller { } nav_set_selected('Language'); - return lang_selector(); + return $this->lang_selector(); } + private function lang_selector(): string + { + $lang_options = language_list(); + array_unshift($lang_options, t('default')); + + $tpl = get_markup_template('lang_selector.tpl'); + + return replace_macros($tpl, [ + '$title' => t('Select an alternate language'), + '$langs' => array($lang_options, App::$language), + + ]); + } + } diff --git a/Zotlabs/Module/Like.php b/Zotlabs/Module/Like.php index 2fb3fab83..52c559a17 100644 --- a/Zotlabs/Module/Like.php +++ b/Zotlabs/Module/Like.php @@ -22,9 +22,9 @@ class Like extends Controller { 'like' => 'Like', 'dislike' => 'Dislike', 'announce' => ACTIVITY_SHARE, - 'attendyes' => 'Accept', - 'attendno' => 'Reject', - 'attendmaybe' => 'TentativeAccept' + 'accept' => 'Accept', + 'reject' => 'Reject', + 'tentativeaccept' => 'TentativeAccept' ]; // unlike (etc.) reactions are an undo of positive reactions, rather than a negative action. @@ -52,43 +52,31 @@ class Like extends Controller { profile_load($parts[0]); } - $item_normal = item_normal(); - if ($page_mode === 'list') { + $item_normal = item_normal(); + $items = q("SELECT item.*, item.id AS item_id FROM item WHERE uid = %d $item_normal AND parent = %d", intval($arr['item']['uid']), intval($arr['item']['parent']) ); + xchan_query($items, true); $items = fetch_post_tags($items, true); $items = conv_sort($items, 'commented'); } else { - $activities = q("SELECT item.*, item.id AS item_id FROM item - WHERE uid = %d $item_normal - AND thr_parent = '%s' - AND verb IN ('%s', '%s', '%s', '%s', '%s', '%s', 'Accept', 'Reject', 'TentativeAccept')", - intval($arr['item']['uid']), - dbesc($arr['item']['mid']), - dbesc('Like'), - dbesc('Dislike'), - dbesc(ACTIVITY_SHARE), - dbesc(ACTIVITY_ATTEND), - dbesc(ACTIVITY_ATTENDNO), - dbesc(ACTIVITY_ATTENDMAYBE) - ); - xchan_query($activities, true); - $items = array_merge([$arr['item']], $activities); - $items = fetch_post_tags($items, true); + $item = item_by_item_id($arr['item']['id'], $arr['item']['parent']); + xchan_query($item, true); + $item = fetch_post_tags($item, true); } $ret = [ 'success' => 1, 'orig_id' => $arr['orig_item_id'], //this is required for pubstream items where $item_id != $item['id'] 'id' => $arr['item']['id'], - 'html' => conversation($items, $conv_mode, true, $page_mode), + 'html' => conversation($item, $conv_mode, true, $page_mode), ]; // mod photos @@ -486,11 +474,11 @@ class Like extends Controller { $bodyverb = t('%1$s likes %2$s\'s %3$s'); if ($verb === 'dislike') $bodyverb = t('%1$s doesn\'t like %2$s\'s %3$s'); - if ($verb === 'attendyes') + if ($verb === 'accept') $bodyverb = t('%1$s is attending %2$s\'s %3$s'); - if ($verb === 'attendno') + if ($verb === 'reject') $bodyverb = t('%1$s is not attending %2$s\'s %3$s'); - if ($verb === 'attendmaybe') + if ($verb === 'tentativeaccept') $bodyverb = t('%1$s may attend %2$s\'s %3$s'); if (!isset($bodyverb)) @@ -573,7 +561,7 @@ class Like extends Controller { call_hooks('post_local_end', $arr); - if ($is_rsvp && in_array($verb, ['attendyes', 'attendmaybe'])) { + if ($is_rsvp && in_array($verb, ['accept', 'tentativeaccept'])) { event_addtocal($item_id, local_channel()); } diff --git a/Zotlabs/Module/Login.php b/Zotlabs/Module/Login.php index 721ac15ac..f5a83a91a 100644 --- a/Zotlabs/Module/Login.php +++ b/Zotlabs/Module/Login.php @@ -5,12 +5,28 @@ namespace Zotlabs\Module; class Login extends \Zotlabs\Web\Controller { function get() { - if(local_channel()) + if (local_channel()) { goaway(z_root()); - if(remote_channel() && $_SESSION['atoken']) + } + + if (remote_channel() && $_SESSION['atoken']) { goaway(z_root()); + } + + if (!empty($_GET['retry'])) { + notice( t('Login failed.') . EOL ); + } + + $o = '<div class="generic-content-wrapper">'; + $o .= '<div class="section-title-wrapper">'; + $o .= '<h2 class="">' . t('Login') . '</h2>'; + $o .= '</div>'; + $o .= '<div class="section-content-wrapper">'; + $o .= login(true); + $o .= '</div>'; + $o .= '</div>'; - return login(true); + return $o; } } diff --git a/Zotlabs/Module/Magic.php b/Zotlabs/Module/Magic.php index e722a7161..122d90b1b 100644 --- a/Zotlabs/Module/Magic.php +++ b/Zotlabs/Module/Magic.php @@ -41,11 +41,7 @@ class Magic extends Controller { http_status_exit(400, 'Bad Request'); } - $basepath = unparse_url(array_filter( - $parsed, - fn (string $key) => in_array($key, ['scheme', 'host', 'port']), - ARRAY_FILTER_USE_KEY - )); + $basepath = unparse_url($parsed, ['scheme', 'host', 'port']); $owapath = SConfig::get($basepath, 'system', 'openwebauth', $basepath . '/owa'); @@ -134,11 +130,22 @@ class Magic extends Controller { $args = (($x) ? '&owt=' . $token : '?owt=' . $token) . (($delegate) ? '&delegate=1' : ''); goaway($dest . $args); } + else { + $o = '<h1>OWA ERROR</h1>'; + if (!empty($j['message'])) { + $o .= '<h2>' . $j['message'] . '</h2>'; + } + $o .= '<a href=' . $dest . '>' . $dest . '</a>'; + + echo $o; + killme(); + + } } } } - killme(); + goaway($dest); } diff --git a/Zotlabs/Module/Moderate.php b/Zotlabs/Module/Moderate.php index 2103684ab..1d8f65348 100644 --- a/Zotlabs/Module/Moderate.php +++ b/Zotlabs/Module/Moderate.php @@ -67,7 +67,7 @@ class Moderate extends \Zotlabs\Web\Controller { $item['item_blocked'] = 0; item_update_parent_commented($item); - notice( t('Item approved') . EOL); + info(t('Item approved') . EOL); } elseif($action === 'drop') { // TODO: not implemented @@ -75,7 +75,7 @@ class Moderate extends \Zotlabs\Web\Controller { // Activity::send_rejection_activity(App::get_channel(), $item['author_xchan'], $item); drop_item($post_id); - notice( t('Item deleted') . EOL); + info(t('Item deleted') . EOL); } // refetch the item after changes have been made diff --git a/Zotlabs/Module/Network.php b/Zotlabs/Module/Network.php index 3ea813547..f95d92fe2 100644 --- a/Zotlabs/Module/Network.php +++ b/Zotlabs/Module/Network.php @@ -70,17 +70,19 @@ class Network extends \Zotlabs\Web\Controller { $dm = ((x($_REQUEST,'dm')) ? $_REQUEST['dm'] : 0); - $order = get_pconfig(local_channel(), 'mod_network', 'order', 0); + $order = get_pconfig(local_channel(), 'mod_network', 'order', 'created'); switch($order) { - case 0: - $order = 'comment'; + case 'commented': + $ordering = 'commented'; break; - case 1: - $order = 'post'; + case 'created': + $ordering = 'created'; break; - case 2: + case 'unthreaded': $nouveau = true; break; + default: + $ordering = 'created'; } $search = $_GET['search'] ?? ''; @@ -92,7 +94,7 @@ class Network extends \Zotlabs\Web\Controller { } if($datequery) - $order = 'post'; + $order = 'created'; // filter by collection (e.g. group) @@ -274,36 +276,18 @@ class Network extends \Zotlabs\Web\Controller { elseif($pf && $unseen && $nouveau) { $vnotify = get_pconfig(local_channel(), 'system', 'vnotify'); - if(! ($vnotify & VNOTIFY_LIKE)) + $likes_sql = ''; + if (!($vnotify & VNOTIFY_LIKE)) { $likes_sql = " AND verb NOT IN ('Like', 'Dislike', '" . dbesc(ACTIVITY_LIKE) . "', '" . dbesc(ACTIVITY_DISLIKE) . "') "; + } // This is for nouveau view public forum cid queries (if a forum notification is clicked) - //$p = q("SELECT oid AS parent FROM term WHERE uid = %d AND ttype = %d AND term = '%s'", - //intval(local_channel()), - //intval(TERM_FORUM), - //dbesc($cid_r[0]['xchan_name']) - //); - - //$p_str = ids_to_querystr($p, 'parent'); + $sql_extra = " AND item.parent IN (SELECT DISTINCT parent FROM item WHERE uid = " . intval(local_channel()) . " AND ( author_xchan = '" . dbesc($cid_r[0]['abook_xchan']) . "' OR owner_xchan = '" . dbesc($cid_r[0]['abook_xchan']) . "' ) $item_normal) AND item_unseen = 1 AND verb != 'Announce' $likes_sql "; - $p_sql = ''; - //if($p_str) - //$p_sql = " OR item.parent IN ( $p_str ) "; - - $sql_extra = " AND ( owner_xchan = '" . protect_sprintf(dbesc($cid_r[0]['abook_xchan'])) . "' OR owner_xchan = '" . protect_sprintf(dbesc($cid_r[0]['abook_xchan'])) . "' $p_sql ) AND item_unseen = 1 $likes_sql "; } else { // This is for threaded view cid queries (e.g. if a forum is selected from the forum filter) - $ttype = (($pf) ? TERM_FORUM : TERM_MENTION); - - $p1 = dbq("SELECT DISTINCT parent FROM item WHERE uid = " . intval(local_channel()) . " AND ( author_xchan = '" . dbesc($cid_r[0]['abook_xchan']) . "' OR owner_xchan = '" . dbesc($cid_r[0]['abook_xchan']) . "' ) $item_normal "); - $p2 = dbq("SELECT oid AS parent FROM term WHERE uid = " . intval(local_channel()) . " AND ttype = $ttype AND term = '" . dbesc($cid_r[0]['xchan_name']) . "'"); - - $p_str = ids_to_querystr(array_merge($p1, $p2), 'parent'); - if(! $p_str) - killme(); - - $sql_extra = " AND item.parent IN ( $p_str ) "; + $sql_extra = " AND item.parent IN (SELECT DISTINCT parent FROM item WHERE uid = " . intval(local_channel()) . " AND ( author_xchan = '" . dbesc($cid_r[0]['abook_xchan']) . "' OR owner_xchan = '" . dbesc($cid_r[0]['abook_xchan']) . "' ) $item_normal) "; } } @@ -393,10 +377,10 @@ class Network extends \Zotlabs\Web\Controller { } if ($dm) { - $sql_extra .= ' AND item_private = 2 '; + $sql_extra .= ' AND item.item_private = 2 '; } else { - $sql_extra .= ' AND item_private IN (0, 1) '; + $sql_extra .= ' AND item.item_private IN (0, 1) '; } @@ -445,10 +429,12 @@ class Network extends \Zotlabs\Web\Controller { $abook_uids = ' and abook.abook_channel = ' . local_channel() . ' '; $uids = ' and item.uid = ' . local_channel() . ' '; - if(feature_enabled(local_channel(), 'network_list_mode')) + $page_mode = 'client'; + + $blog_mode = feature_enabled(local_channel(), 'network_list_mode'); + if ($blog_mode) { $page_mode = 'list'; - else - $page_mode = 'client'; + } $parents_str = ''; @@ -472,11 +458,12 @@ class Network extends \Zotlabs\Web\Controller { if($nouveau && $load) { // "New Item View" - show all items unthreaded in reverse created date order - $items = dbq("SELECT item.*, item.id AS item_id, created FROM item + $items = dbq("SELECT item.*, item.id AS item_id FROM item left join abook on ( item.owner_xchan = abook.abook_xchan $abook_uids ) $net_query WHERE true $uids $item_normal and (abook.abook_blocked = 0 or abook.abook_flags is null) + AND item.verb NOT IN ('Add', 'Remove') $sql_extra $sql_options $sql_nets $net_query2 ORDER BY item.created DESC $pager_sql " @@ -492,13 +479,6 @@ class Network extends \Zotlabs\Web\Controller { } elseif($update) { - // Normal conversation view - - if($order === 'post') - $ordering = 'created'; - else - $ordering = 'commented'; - if($load) { // Fetch a page full of parent items for this page $r = dbq("SELECT item.parent AS item_id FROM item @@ -527,12 +507,7 @@ class Network extends \Zotlabs\Web\Controller { // Then fetch all the children of the parents that are on this page if($r) { - $parents_str = ids_to_querystr($r, 'item_id'); - $items = dbq("SELECT item.*, item.id AS item_id FROM item - WHERE true $uids $item_normal - AND item.parent IN ( $parents_str ) - $sql_extra " - ); + $items = items_by_parent_ids($r, blog_mode: $blog_mode); xchan_query($items, true); $items = fetch_post_tags($items, true); diff --git a/Zotlabs/Module/Oep.php b/Zotlabs/Module/Oep.php index 37a46a23e..201e5a06f 100644 --- a/Zotlabs/Module/Oep.php +++ b/Zotlabs/Module/Oep.php @@ -23,6 +23,7 @@ class Oep extends \Zotlabs\Web\Controller { if(! $url) http_status_exit(404, 'Not found'); + $arr = []; $maxwidth = $_REQUEST['maxwidth'] ?? 0; $maxheight = $_REQUEST['maxheight'] ?? 0; $format = $_REQUEST['format'] ?? ''; diff --git a/Zotlabs/Module/Owa.php b/Zotlabs/Module/Owa.php index 85467d4f4..254b1c7d1 100644 --- a/Zotlabs/Module/Owa.php +++ b/Zotlabs/Module/Owa.php @@ -18,96 +18,97 @@ use Zotlabs\Web\Controller; class Owa extends Controller { - function init() { + public function init(): void + { $ret = [ 'success' => false ]; - if (array_key_exists('REDIRECT_REMOTE_USER',$_SERVER) && (! array_key_exists('HTTP_AUTHORIZATION',$_SERVER))) { - $_SERVER['HTTP_AUTHORIZATION'] = $_SERVER['REDIRECT_REMOTE_USER']; + if (!$this->validateAuthorizationHeader()) { + $this->error('Missing or invalid authorization header.'); } - if (array_key_exists('HTTP_AUTHORIZATION',$_SERVER) && substr(trim($_SERVER['HTTP_AUTHORIZATION']),0,9) === 'Signature') { - $sigblock = HTTPSig::parse_sigheader($_SERVER['HTTP_AUTHORIZATION']); - if ($sigblock) { - $keyId = $sigblock['keyId']; - $parsed = parse_url($keyId); - if (str_starts_with($parsed['scheme'],'http')) { - unset($parsed['fragment']); - unset($parsed['query']); - $keyId = unparse_url($parsed); - } - else { - $keyId = str_replace('acct:', '', $keyId); + $_SERVER['HTTP_AUTHORIZATION'] = $_SERVER['HTTP_AUTHORIZATION'] ?? $_SERVER['REDIRECT_REMOTE_USER']; + + $sigblock = HTTPSig::parse_sigheader($_SERVER['HTTP_AUTHORIZATION']); + if ($sigblock) { + $keyId = $sigblock['keyId']; + $parsed = parse_url($keyId); + if (str_starts_with($parsed['scheme'],'http')) { + unset($parsed['fragment']); + unset($parsed['query']); + $keyId = unparse_url($parsed); + } + else { + $keyId = str_replace('acct:', '', $keyId); + } + if ($keyId) { + $r = q("SELECT * FROM hubloc LEFT JOIN xchan ON hubloc_hash = xchan_hash + WHERE (hubloc_addr = '%s' OR hubloc_id_url = '%s' OR xchan_hash = '%s') + AND hubloc_deleted = 0 AND xchan_pubkey != '' + ORDER BY hubloc_id DESC", + dbesc($keyId), + dbesc($keyId), + dbesc($keyId) + ); + if (! $r) { + $found = discover_by_webbie($keyId); + logger('found = ' . print_r($found, true)); + if ($found) { + $r = q("SELECT * FROM hubloc LEFT JOIN xchan ON hubloc_hash = xchan_hash + WHERE (hubloc_addr = '%s' OR hubloc_id_url = '%s' OR xchan_hash = '%s') AND hubloc_deleted = 0 AND xchan_pubkey != '' ORDER BY hubloc_id DESC ", + dbesc($keyId), + dbesc($keyId), + dbesc($keyId) + ); + } } - if ($keyId) { - $r = q("SELECT * FROM hubloc LEFT JOIN xchan ON hubloc_hash = xchan_hash - WHERE (hubloc_addr = '%s' OR hubloc_id_url = '%s' OR xchan_hash = '%s') - AND hubloc_deleted = 0 AND xchan_pubkey != '' - ORDER BY hubloc_id DESC", - dbesc($keyId), - dbesc($keyId), - dbesc($keyId) - ); - if (! $r) { + + if ($r) { + foreach ($r as $hubloc) { + $verified = HTTPSig::verify(file_get_contents('php://input'), $hubloc['xchan_pubkey']); + if ($verified && $verified['header_signed'] && $verified['header_valid'] && ($verified['content_valid'] || (! $verified['content_signed']))) { + logger('OWA header: ' . print_r($verified,true),LOGGER_DATA); + logger('OWA success: ' . $hubloc['hubloc_id_url'],LOGGER_DATA); + $ret['success'] = true; + $token = random_string(32); + Verify::create('owt',0,$token,$hubloc['hubloc_id_url']); + $result = ''; + openssl_public_encrypt($token,$result,$hubloc['xchan_pubkey']); + $ret['encrypted_token'] = base64url_encode($result); + break; + } else { + logger('OWA fail: ' . $hubloc['hubloc_id'] . ' ' . $hubloc['hubloc_id_url']); + } + } + + if (!$ret['success']) { + + // Possible a reinstall? + // In this case we probably already have an old hubloc + // but not the new one yet. + $found = discover_by_webbie($keyId); - logger('found = ' . print_r($found, true)); + if ($found) { $r = q("SELECT * FROM hubloc LEFT JOIN xchan ON hubloc_hash = xchan_hash - WHERE (hubloc_addr = '%s' OR hubloc_id_url = '%s' OR xchan_hash = '%s') AND hubloc_deleted = 0 AND xchan_pubkey != '' ORDER BY hubloc_id DESC ", - dbesc($keyId), - dbesc($keyId), + WHERE (hubloc_addr = '%s' OR hubloc_id_url = '%s') AND hubloc_deleted = 0 ORDER BY hubloc_id DESC LIMIT 1", + dbesc(str_replace('acct:', '', $keyId)), dbesc($keyId) ); - } - } - - if ($r) { - foreach ($r as $hubloc) { - $verified = HTTPSig::verify(file_get_contents('php://input'), $hubloc['xchan_pubkey']); - if ($verified && $verified['header_signed'] && $verified['header_valid'] && ($verified['content_valid'] || (! $verified['content_signed']))) { - logger('OWA header: ' . print_r($verified,true),LOGGER_DATA); - logger('OWA success: ' . $hubloc['hubloc_id_url'],LOGGER_DATA); - $ret['success'] = true; - $token = random_string(32); - Verify::create('owt',0,$token,$hubloc['hubloc_id_url']); - $result = ''; - openssl_public_encrypt($token,$result,$hubloc['xchan_pubkey']); - $ret['encrypted_token'] = base64url_encode($result); - break; - } else { - logger('OWA fail: ' . $hubloc['hubloc_id'] . ' ' . $hubloc['hubloc_id_url']); - } - } - if (!$ret['success']) { - - // Possible a reinstall? - // In this case we probably already have an old hubloc - // but not the new one yet. - - $found = discover_by_webbie($keyId); - - if ($found) { - $r = q("SELECT * FROM hubloc LEFT JOIN xchan ON hubloc_hash = xchan_hash - WHERE (hubloc_addr = '%s' OR hubloc_id_url = '%s') AND hubloc_deleted = 0 ORDER BY hubloc_id DESC LIMIT 1", - dbesc(str_replace('acct:', '', $keyId)), - dbesc($keyId) - ); - - if ($r) { - $verified = HTTPSig::verify(file_get_contents('php://input'), $r[0]['xchan_pubkey']); - if ($verified && $verified['header_signed'] && $verified['header_valid'] && ($verified['content_valid'] || (! $verified['content_signed']))) { - logger('OWA header: ' . print_r($verified,true), LOGGER_DATA); - logger('OWA success: ' . $r[0]['hubloc_id_url'], LOGGER_DATA); - $ret['success'] = true; - $token = random_string(32); - Verify::create('owt', 0, $token, $r[0]['hubloc_id_url']); - $result = ''; - openssl_public_encrypt($token, $result, $r[0]['xchan_pubkey']); - $ret['encrypted_token'] = base64url_encode($result); - } else { - logger('OWA fail: ' . $hubloc['hubloc_id'] . ' ' . $hubloc['hubloc_id_url']); - } + if ($r) { + $verified = HTTPSig::verify(file_get_contents('php://input'), $r[0]['xchan_pubkey']); + if ($verified && $verified['header_signed'] && $verified['header_valid'] && ($verified['content_valid'] || (! $verified['content_signed']))) { + logger('OWA header: ' . print_r($verified,true), LOGGER_DATA); + logger('OWA success: ' . $r[0]['hubloc_id_url'], LOGGER_DATA); + $ret['success'] = true; + $token = random_string(32); + Verify::create('owt', 0, $token, $r[0]['hubloc_id_url']); + $result = ''; + openssl_public_encrypt($token, $result, $r[0]['xchan_pubkey']); + $ret['encrypted_token'] = base64url_encode($result); + } else { + logger('OWA fail: ' . $hubloc['hubloc_id'] . ' ' . $hubloc['hubloc_id_url']); } } } @@ -118,4 +119,33 @@ class Owa extends Controller { json_return_and_die($ret,'application/x-zot+json'); } + + private function validateAuthorizationHeader(): bool + { + if (!empty($_SERVER['HTTP_AUTHORIZATION'])) { + $auth = trim($_SERVER['HTTP_AUTHORIZATION']); + } else if (!empty($_SERVER['REDIRECT_REMOTE_USER'])) { + $auth = trim($_SERVER['REDIRECT_REMOTE_USER']); + } else { + return false; + } + + return strncmp($auth, 'Signature', 9) === 0; + } + + /** + * Terminates the request, and return a json error response. + * + * @Note This function does not return! + * + * @param string $msg The error message for the response. + */ + private function error(string $msg): void { + $ret = [ + 'success' => false, + 'message' => $msg + ]; + + json_return_and_die($ret,'application/x-zot+json'); + } } diff --git a/Zotlabs/Module/Photos.php b/Zotlabs/Module/Photos.php index e31aa9dc1..5f6162ba7 100644 --- a/Zotlabs/Module/Photos.php +++ b/Zotlabs/Module/Photos.php @@ -557,7 +557,9 @@ class Photos extends \Zotlabs\Web\Controller { $can_post = false; $visitor = 0; - + $link_item = null; + $like = null; + $dislike = null; $owner_uid = \App::$data['channel']['channel_id']; $owner_aid = \App::$data['channel']['channel_account_id']; @@ -965,7 +967,6 @@ class Photos extends \Zotlabs\Web\Controller { $map = null; if($linked_items) { - xchan_query($linked_items); $linked_items = fetch_post_tags($linked_items,true); @@ -1103,20 +1104,8 @@ class Photos extends \Zotlabs\Web\Controller { $alike = array(); $dlike = array(); - $like = ''; - $dislike = ''; - - $conv_responses = array( - 'like' => array('title' => t('Likes','title')),'dislike' => array('title' => t('Dislikes','title')), - 'attendyes' => array('title' => t('Attending','title')), 'attendno' => array('title' => t('Not attending','title')), 'attendmaybe' => array('title' => t('Might attend','title')) - ); - if($r) { - foreach($r as $item) { - builtin_activity_puller($item, $conv_responses); - } - $like_count = ((x($alike,$link_item['mid'])) ? $alike[$link_item['mid']] : ''); $like_list = ((x($alike,$link_item['mid'])) ? $alike[$link_item['mid'] . '-l'] : ''); @@ -1217,12 +1206,17 @@ class Photos extends \Zotlabs\Web\Controller { $like_e = $like; $dislike_e = $dislike; $paginate = paginate(); + $responses = []; - $response_verbs = array('like'); - if(feature_enabled($owner_uid,'dislike')) - $response_verbs[] = 'dislike'; + if ($link_item) { + $response_verbs = ['like']; - $responses = get_responses($conv_responses,$response_verbs,'',$link_item); + if(feature_enabled($owner_uid,'dislike')) { + $response_verbs[] = 'dislike'; + } + + $responses = get_responses($response_verbs, $link_item); + } $hookdata = [ 'onclick' => '$.colorbox({href: \'' . $photo['href'] . '\'}); return false;', diff --git a/Zotlabs/Module/Pin.php b/Zotlabs/Module/Pin.php index de3c75622..14a45c10d 100644 --- a/Zotlabs/Module/Pin.php +++ b/Zotlabs/Module/Pin.php @@ -29,8 +29,9 @@ class Pin extends \Zotlabs\Web\Controller { if(! $observer) http_status_exit(403, 'Forbidden'); - $r = q("SELECT * FROM item WHERE id = %d AND id = parent AND item_private = 0 LIMIT 1", - $item_id + $r = q("SELECT * FROM item WHERE id = %d AND uid = %d AND id = parent AND item_private = 0 LIMIT 1", + intval($item_id), + intval(local_channel()) ); if(! $r) { notice(t('Unable to locate original post.')); diff --git a/Zotlabs/Module/Pubstream.php b/Zotlabs/Module/Pubstream.php index 6d8edf4d8..99b8ab587 100644 --- a/Zotlabs/Module/Pubstream.php +++ b/Zotlabs/Module/Pubstream.php @@ -183,6 +183,7 @@ class Pubstream extends \Zotlabs\Web\Controller { $sql_extra .= protect_sprintf(term_query('item', $hashtags, TERM_HASHTAG, TERM_COMMUNITYTAG)); $sql_extra_order = " ORDER BY item.created DESC "; $thread_top = ''; + } $net_query2 = (($net) ? " and xchan_network = '" . protect_sprintf(dbesc($net)) . "' " : ''); @@ -196,17 +197,15 @@ class Pubstream extends \Zotlabs\Web\Controller { if($update) { - $ordering = Config::Get('system', 'pubstream_ordering', 'commented'); + $ordering = Config::Get('system', 'pubstream_ordering', 'created'); if($load) { if($mid) { $r = q("SELECT parent AS item_id FROM item left join abook on item.author_xchan = abook.abook_xchan - left join xchan on item.author_xchan = xchan.xchan_hash WHERE item.$identifier = '%s' and item.item_private = 0 $uids $site_firehose_sql $item_normal - and xchan.xchan_censored = 0 and (abook.abook_blocked = 0 or abook.abook_flags is null) $sql_extra $net_query2", dbesc($mid) @@ -216,11 +215,9 @@ class Pubstream extends \Zotlabs\Web\Controller { // Fetch a page full of parent items for this page $r = dbq("SELECT parent AS item_id FROM item left join abook on ( item.author_xchan = abook.abook_xchan $abook_uids ) - left join xchan on item.author_xchan = xchan.xchan_hash WHERE item.item_private = 0 $thread_top $uids $site_firehose_sql $item_normal - and xchan.xchan_censored = 0 and (abook.abook_blocked = 0 or abook.abook_flags is null) $sql_extra $net_query2 ORDER BY $ordering DESC $pager_sql " @@ -231,10 +228,8 @@ class Pubstream extends \Zotlabs\Web\Controller { if($mid) { $r = q("SELECT parent AS item_id FROM item left join abook on item.author_xchan = abook.abook_xchan - left join xchan on item.author_xchan = xchan.xchan_hash WHERE item.$identifier = '%s' and item.item_private = 0 $uids $site_firehose_sql $item_normal_update $simple_update - and xchan.xchan_censored = 0 and (abook.abook_blocked = 0 or abook.abook_flags is null) $sql_extra $net_query2", dbesc($mid) @@ -243,11 +238,9 @@ class Pubstream extends \Zotlabs\Web\Controller { else { $r = dbq("SELECT parent AS item_id FROM item left join abook on item.author_xchan = abook.abook_xchan - left join xchan on item.author_xchan = xchan.xchan_hash WHERE item.item_private = 0 $thread_top $uids $site_firehose_sql $item_normal_update $simple_update - and xchan.xchan_censored = 0 and (abook.abook_blocked = 0 or abook.abook_flags is null) $sql_extra $net_query2" ); @@ -258,15 +251,7 @@ class Pubstream extends \Zotlabs\Web\Controller { $parents_str = ''; if($r) { - - $parents_str = ids_to_querystr($r,'item_id'); - - $items = dbq("SELECT item.*, item.id AS item_id FROM item - WHERE true $uids $item_normal - AND item.parent IN ( $parents_str ) - $sql_extra $sql_extra_order" - ); - + $items = items_by_parent_ids($r); // use effective_uid param of xchan_query to help sort out comment permission // for sys_channel owned items. diff --git a/Zotlabs/Module/Regate.php b/Zotlabs/Module/Regate.php index c67f45a88..956c5e2ea 100644 --- a/Zotlabs/Module/Regate.php +++ b/Zotlabs/Module/Regate.php @@ -375,7 +375,7 @@ class Regate extends \Zotlabs\Web\Controller { ]); $reonar = json_decode( $r['reg_stuff'], true); - $reonar['deny'] = $now . ',' . $ip . ' ' . $did2 . ' ' . $msg; + $reonar['deny'] = $now . ',' . $ip . ' ' . $did2; $flags = ( $r['reg_flags'] &= ( $r['reg_flags'] ^ ACCOUNT_UNVERIFIED) ) | ( $r['reg_flags'] |= REGISTER_DENIED); $rd = q("UPDATE register SET reg_stuff='%s', reg_vital=0, reg_flags=%d WHERE reg_id = %d ", @@ -456,7 +456,7 @@ class Regate extends \Zotlabs\Web\Controller { // $log = ' from § ' . $ip . ' §' . ' (' . dbesc($did2) . ')'; zar_log($msg); $o = replace_macros(get_markup_template('plain.tpl'), [ - '$title' => $title, + '$title' => $msg, '$now' => $nowfmt, '$infos' => $msg ]); diff --git a/Zotlabs/Module/Request.php b/Zotlabs/Module/Request.php new file mode 100644 index 000000000..439f56282 --- /dev/null +++ b/Zotlabs/Module/Request.php @@ -0,0 +1,89 @@ +<?php +namespace Zotlabs\Module; + +use Zotlabs\Web\Controller; + +class Request extends Controller +{ + + private function mapVerb(string $verb) : string + { + $verbs = [ + 'like' => 'Like', + 'dislike' => 'Dislike', + 'announce' => 'Announce', + 'accept' => 'Accept', + 'reject' => 'Reject', + 'tentativeaccept' => 'TentativeAccept' + ]; + + if (array_key_exists($verb, $verbs)) { + return $verbs[$verb]; + } + + return EMPTY_STR; + } + + + private function processSubthreadRequest() : string + { + $mid = $_GET['mid']; + $parent = intval($_GET['parent']); + + $offset = null; + if ($_GET['verb'] === 'load') { + $offset = intval($_GET['offset']); + } + + $module = strip_tags($_GET['module']); + + $items = items_by_thr_parent($mid, $parent, $offset); + xchan_query($items); + + $items = fetch_post_tags($items,true); + + if ($module === 'channel') { + $parts = explode('@', $items[0]['owner']['xchan_addr']); + profile_load($parts[0]); + } + + $ret['html'] = conversation($items, $module, true, 'r_preview'); + + json_return_and_die($ret); + } + + public function get() : string + { + + if (in_array($_GET['verb'], ['comment', 'load'])) { + return self::processSubthreadRequest(); + } + + $verb = self::mapVerb($_GET['verb']); + + if (!$verb) { + killme(); + } + + $text = get_response_button_text($_GET['verb']); + $mid = strip_tags($_GET['mid']); + $parent = intval($_GET['parent']); + $observer_hash = get_observer_hash(); + + $ret['result'] = item_activity_xchans($mid, $parent, $verb); + + $commentable = $ret['result']['is_commentable']; + unset($ret['result']['is_commentable']); + + if ($commentable) { + $ret['action'] = (($verb === 'Announce') ? 'jotShare' : 'dolike'); + $ret['action_label'] = ((find_xchan_in_array($observer_hash, $ret['result'])) ? (($verb === 'Announce') ? t('+ Repeat again') : t('- Remove yours')) : t('+ Add yours')); + } + + $ret['title'] = $text['label']; + + json_return_and_die($ret); + + } + +} diff --git a/Zotlabs/Module/Settings/Display.php b/Zotlabs/Module/Settings/Display.php index a7fccea47..98c3d7543 100644 --- a/Zotlabs/Module/Settings/Display.php +++ b/Zotlabs/Module/Settings/Display.php @@ -24,7 +24,7 @@ class Display { $theme = 'redbasic'; - $preload_images = ((x($_POST,'preload_images')) ? intval($_POST['preload_images']) : 0); + $thread_allow = ((!empty($_POST['thread_allow'])) ? intval($_POST['thread_allow']) : 0); $user_scalable = ((x($_POST,'user_scalable')) ? intval($_POST['user_scalable']) : 0); $nosmile = ((x($_POST,'nosmile')) ? intval($_POST['nosmile']) : 0); $title_tosource = ((x($_POST,'title_tosource')) ? intval($_POST['title_tosource']) : 0); @@ -40,7 +40,7 @@ class Display { $itemspage = 30; - set_pconfig(local_channel(),'system','preload_images',$preload_images); + set_pconfig(local_channel(), 'system', 'thread_allow', $thread_allow); set_pconfig(local_channel(),'system','user_scalable',$user_scalable); set_pconfig(local_channel(),'system','update_interval', $browser_update); set_pconfig(local_channel(),'system','itemspage', $itemspage); @@ -146,8 +146,7 @@ class Display { $start_menu = get_pconfig(local_channel(), 'system', 'start_menu', 0); } - $preload_images = get_pconfig(local_channel(),'system','preload_images'); - $preload_images = (($preload_images===false)? '0': $preload_images); // default if not set: 0 + $thread_allow = get_pconfig(local_channel(), 'system', 'thread_allow', true); $user_scalable = get_pconfig(local_channel(),'system','user_scalable'); $user_scalable = (($user_scalable===false)? '0': $user_scalable); // default if not set: 0 @@ -192,7 +191,7 @@ class Display { '$theme' => (($themes) ? array('theme', t('Display Theme:'), $theme_selected, '', $themes, 'preview') : false), '$schema' => (($schemas) ? array('schema', t('Select scheme'), $existing_schema, '' , $schemas) : false), - '$preload_images' => array('preload_images', t("Preload images before rendering the page"), $preload_images, t("The subjective page load time will be longer but the page will be ready when displayed"), $yes_no), + '$thread_allow' => ['thread_allow', t('Threaded conversation view'), $thread_allow, t('Display replies below their parent message (default yes)'), $yes_no], '$user_scalable' => array('user_scalable', t("Enable user zoom on mobile devices"), $user_scalable, '', $yes_no), '$ajaxint' => array('browser_update', t("Update browser every xx seconds"), $browser_update, t('Minimum of 10 seconds, no maximum')), '$itemspage' => array('itemspage', t("Maximum number of conversations to load at any time:"), $itemspage, t('Maximum of 30 items')), diff --git a/Zotlabs/Module/Sse.php b/Zotlabs/Module/Sse.php index fda2f2be4..673457db1 100644 --- a/Zotlabs/Module/Sse.php +++ b/Zotlabs/Module/Sse.php @@ -207,10 +207,9 @@ class Sse extends Controller { if ($result) { XConfig::Set(self::$ob_hash, 'sse', 'notifications', []); - json_return_and_die($result); } - killme(); + json_return_and_die($result); } diff --git a/Zotlabs/Module/Sse_bs.php b/Zotlabs/Module/Sse_bs.php index 09c4ed614..c457363c0 100644 --- a/Zotlabs/Module/Sse_bs.php +++ b/Zotlabs/Module/Sse_bs.php @@ -22,7 +22,6 @@ class Sse_bs extends Controller { public static $xchans; function init() { - self::$uid = local_channel(); self::$ob_hash = get_observer_hash(); self::$sse_id = false; @@ -43,8 +42,9 @@ class Sse_bs extends Controller { self::$offset = 0; self::$xchans = ''; - if(isset($_REQUEST['sse_rmids'])) - self::mark_read($_REQUEST['sse_rmids']); + if (!empty($_REQUEST['sse_rmids'])) { + self::mark_read(explode(',', $_REQUEST['sse_rmids'])); + } if(!empty($_REQUEST['nquery']) && $_REQUEST['nquery'] !== '%') { $nquery = $_REQUEST['nquery']; @@ -118,7 +118,6 @@ class Sse_bs extends Controller { } function mark_read($arr) { - $mids = []; $str = ''; $slice = 0; @@ -142,24 +141,51 @@ class Sse_bs extends Controller { } } - $_SESSION['sse_mids_all'] = serialise($mids_all); + $str = implode(',', $mids); + + $sys = get_sys_channel(); + $sql_order = ((self::$uid > $sys['channel_id']) ? 'DESC' : 'ASC'); + + $r = q("SELECT uid, uuid FROM item + WHERE uid in (%d, %d) + AND verb IN ('Like', 'Dislike', 'Announce', 'Accept', 'Reject', 'TentativeAccept') + AND thr_parent IN ( + SELECT mid FROM item WHERE uid IN (%d, %d) AND uuid IN (%s) ORDER BY uid $sql_order + ) + GROUP BY uid, uuid + ORDER BY uid $sql_order", + intval(self::$uid), + intval($sys['channel_id']), + intval(self::$uid), + intval($sys['channel_id']), + $str // this is dbesc() in the above foreach loop + ); + + if ($r) { + $activities_str = ids_to_querystr($r, 'uuid', true); + $str .= ',' . $activities_str; + $activities_arr = explode(',', $activities_str); + $mids_all = array_merge($mids_all, $activities_arr); + } + + $_SESSION['sse_mids_all'] = serialise(array_unique($mids_all)); if(! self::$uid) { return; } - $str = implode(',', $mids); - $x = [ 'channel_id' => self::$uid, 'update' => 'unset' ]; call_hooks('update_unseen',$x); - if($x['update'] === 'unset' || intval($x['update'])) { - q("UPDATE item SET item_unseen = 0 WHERE uid = %d AND uuid in (%s) AND item_unseen = 1", + if ($x['update'] === 'unset' || intval($x['update'])) { + q("UPDATE item SET item_unseen = 0 + WHERE uid = %d + AND uuid in (%s) + AND item_unseen = 1", intval(self::$uid), $str // this is dbesc() in the above foreach loop ); } - } function bs_network($notifications) { @@ -182,32 +208,34 @@ class Sse_bs extends Controller { $sql_extra = ''; if (!(self::$vnotify & VNOTIFY_LIKE)) { - $sql_extra = " AND verb NOT IN ('Like', 'Dislike', '" . dbesc(ACTIVITY_LIKE) . "', '" . dbesc(ACTIVITY_DISLIKE) . "') "; + $sql_extra = " AND item.verb NOT IN ('Like', 'Dislike', '" . dbesc(ACTIVITY_LIKE) . "', '" . dbesc(ACTIVITY_DISLIKE) . "') "; } elseif (!feature_enabled(self::$uid, 'dislike')) { - $sql_extra = " AND verb NOT IN ('Dislike', '" . dbesc(ACTIVITY_DISLIKE) . "') "; + $sql_extra = " AND item.verb NOT IN ('Dislike', '" . dbesc(ACTIVITY_DISLIKE) . "') "; } $sql_extra2 = ''; if(self::$xchans) - $sql_extra2 = " AND CASE WHEN verb = '" . dbesc(ACTIVITY_SHARE) . "' THEN owner_xchan ELSE author_xchan END IN (" . self::$xchans . ") "; + $sql_extra2 = " AND CASE WHEN item.verb = '" . dbesc(ACTIVITY_SHARE) . "' THEN item.owner_xchan ELSE item.author_xchan END IN (" . self::$xchans . ") "; $item_normal = item_normal(); // Filter internal follow activities and strerams add/remove activities - $item_normal .= " AND verb NOT IN ('Add', 'Remove', 'Follow', 'Ignore', '" . dbesc(ACTIVITY_FOLLOW) . "') "; + $item_normal .= " AND item.verb NOT IN ('Add', 'Remove', 'Follow', 'Ignore', '" . dbesc(ACTIVITY_FOLLOW) . "') "; if ($notifications) { - $items = q("SELECT * FROM item - WHERE uid = %d - AND created <= '%s' - AND item_unseen = 1 AND item_wall = 0 AND item_private IN (0, 1) - AND obj_type NOT IN ('Document', 'Video', 'Audio', 'Image') - AND author_xchan != '%s' + $items = 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.uid = %d + AND item.created <= '%s' + AND item.item_unseen = 1 AND item.item_wall = 0 AND item.item_private IN (0, 1) + AND item.obj_type NOT IN ('Document', 'Video', 'Audio', 'Image') + AND NOT (item.verb = 'Announce' AND item.item_thread_top = 1) -- only show the announce activity and not the resulting item + AND NOT item.author_xchan = '%s' $item_normal $sql_extra $sql_extra2 - ORDER BY created DESC LIMIT $limit OFFSET $offset", + ORDER BY item.created DESC LIMIT $limit OFFSET $offset", intval(self::$uid), dbescdate($_SESSION['sse_loadtime']), dbesc(self::$ob_hash) @@ -265,28 +293,30 @@ class Sse_bs extends Controller { $sql_extra = ''; if (!(self::$vnotify & VNOTIFY_LIKE)) { - $sql_extra = " AND verb NOT IN ('Like', 'Dislike', '" . dbesc(ACTIVITY_LIKE) . "', '" . dbesc(ACTIVITY_DISLIKE) . "') "; + $sql_extra = " AND item.verb NOT IN ('Like', 'Dislike', '" . dbesc(ACTIVITY_LIKE) . "', '" . dbesc(ACTIVITY_DISLIKE) . "') "; } elseif (!feature_enabled(self::$uid, 'dislike')) { - $sql_extra = " AND verb NOT IN ('Dislike', '" . dbesc(ACTIVITY_DISLIKE) . "') "; + $sql_extra = " AND item.verb NOT IN ('Dislike', '" . dbesc(ACTIVITY_DISLIKE) . "') "; } $sql_extra2 = ''; if(self::$xchans) - $sql_extra2 = " AND CASE WHEN verb = '" . ACTIVITY_SHARE . "' THEN owner_xchan ELSE author_xchan END IN (" . self::$xchans . ") "; + $sql_extra2 = " AND CASE WHEN item.verb = '" . ACTIVITY_SHARE . "' THEN item.owner_xchan ELSE item.author_xchan END IN (" . self::$xchans . ") "; $item_normal = item_normal(); // Filter internal follow activities and strerams add/remove activities - $item_normal .= " AND verb NOT IN ('Add', 'Remove', 'Follow', 'Ignore', '" . dbesc(ACTIVITY_FOLLOW) . "') "; + $item_normal .= " AND item.verb NOT IN ('Add', 'Remove', 'Follow', 'Ignore', '" . dbesc(ACTIVITY_FOLLOW) . "') "; if ($notifications) { - $items = q("SELECT * FROM item - WHERE uid = %d - AND created <= '%s' - AND item_unseen = 1 AND item_private = 2 - AND obj_type NOT IN ('Document', 'Video', 'Audio', 'Image') - AND author_xchan != '%s' + $items = 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.uid = %d + AND item.created <= '%s' + AND item.item_unseen = 1 AND item.item_private = 2 + AND item.obj_type NOT IN ('Document', 'Video', 'Audio', 'Image') + AND NOT (item.verb = 'Announce' AND item.item_thread_top = 1) -- only show the announce activity and not the resulting item + AND NOT item.author_xchan = '%s' $item_normal $sql_extra $sql_extra2 @@ -347,33 +377,35 @@ class Sse_bs extends Controller { $sql_extra = ''; if (!(self::$vnotify & VNOTIFY_LIKE)) { - $sql_extra = " AND verb NOT IN ('Like', 'Dislike', '" . dbesc(ACTIVITY_LIKE) . "', '" . dbesc(ACTIVITY_DISLIKE) . "') "; + $sql_extra = " AND item.verb NOT IN ('Like', 'Dislike', '" . dbesc(ACTIVITY_LIKE) . "', '" . dbesc(ACTIVITY_DISLIKE) . "') "; } elseif (!feature_enabled(self::$uid, 'dislike')) { - $sql_extra = " AND verb NOT IN ('Dislike', '" . dbesc(ACTIVITY_DISLIKE) . "') "; + $sql_extra = " AND item.verb NOT IN ('Dislike', '" . dbesc(ACTIVITY_DISLIKE) . "') "; } $sql_extra2 = ''; if(self::$xchans) - $sql_extra2 = " AND CASE WHEN verb = '" . ACTIVITY_SHARE . "' THEN owner_xchan ELSE author_xchan END IN (" . self::$xchans . ") "; + $sql_extra2 = " AND CASE WHEN item.verb = '" . ACTIVITY_SHARE . "' THEN item.owner_xchan ELSE item.author_xchan END IN (" . self::$xchans . ") "; $item_normal = item_normal(); // Filter internal follow activities and strerams add/remove activities - $item_normal .= " AND verb NOT IN ('Add', 'Remove', 'Follow', 'Ignore', '" . dbesc(ACTIVITY_FOLLOW) . "') "; + $item_normal .= " AND item.verb NOT IN ('Add', 'Remove', 'Follow', 'Ignore', '" . dbesc(ACTIVITY_FOLLOW) . "') "; if ($notifications) { - $items = q("SELECT * FROM item - WHERE uid = %d - AND created <= '%s' - AND item_unseen = 1 AND item_wall = 1 AND item_private IN (0, 1) - AND obj_type NOT IN ('Document', 'Video', 'Audio', 'Image') - AND author_xchan != '%s' + $items = 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.uid = %d + AND item.created <= '%s' + AND item.item_unseen = 1 AND item.item_wall = 1 AND item.item_private IN (0, 1) + AND item.obj_type NOT IN ('Document', 'Video', 'Audio', 'Image') + AND NOT (item.verb = 'Announce' AND item.item_thread_top = 1) -- only show the announce activity and not the resulting item + AND NOT item.author_xchan = '%s' $item_normal $sql_extra $sql_extra2 - ORDER BY created DESC LIMIT $limit OFFSET $offset", + ORDER BY item.created DESC LIMIT $limit OFFSET $offset", intval(self::$uid), dbescdate($_SESSION['sse_loadtime']), dbesc(self::$ob_hash) @@ -442,49 +474,51 @@ class Sse_bs extends Controller { $sys = get_sys_channel(); $sql_extra = ''; if (!(self::$vnotify & VNOTIFY_LIKE)) { - $sql_extra = " AND verb NOT IN ('Like', 'Dislike', '" . dbesc(ACTIVITY_LIKE) . "', '" . dbesc(ACTIVITY_DISLIKE) . "') "; + $sql_extra = " AND item.verb NOT IN ('Like', 'Dislike', '" . dbesc(ACTIVITY_LIKE) . "', '" . dbesc(ACTIVITY_DISLIKE) . "') "; } elseif (!feature_enabled(self::$uid, 'dislike')) { - $sql_extra = " AND verb NOT IN ('Dislike', '" . dbesc(ACTIVITY_DISLIKE) . "') "; + $sql_extra = " AND item.verb NOT IN ('Dislike', '" . dbesc(ACTIVITY_DISLIKE) . "') "; } $sql_extra2 = ''; if(self::$xchans) - $sql_extra2 = " AND CASE WHEN verb = '" . ACTIVITY_SHARE . "' THEN owner_xchan ELSE author_xchan END IN (" . self::$xchans . ") "; + $sql_extra2 = " AND CASE WHEN item.verb = '" . ACTIVITY_SHARE . "' THEN item.owner_xchan ELSE item.author_xchan END IN (" . self::$xchans . ") "; $sql_extra3 = ''; - $sse_mids_all = unserialise($_SESSION['sse_mids_all']) ?? []; + $sse_mids_all = isset($_SESSION['sse_mids_all']) ? unserialise($_SESSION['sse_mids_all']) : []; if ($sse_mids_all) { - $sql_extra3 = " AND uuid NOT IN (" . protect_sprintf(implode(',', $sse_mids_all)) . ") "; + $sql_extra3 = " AND item.uuid NOT IN (" . protect_sprintf(implode(',', $sse_mids_all)) . ") "; } - $uids = " AND uid IN ( " . $sys['channel_id'] . " ) "; + $uids = " AND item.uid IN ( " . $sys['channel_id'] . " ) "; $site_firehose = Config::Get('system', 'site_firehose', 0); if($site_firehose) { - $uids = " AND uid IN ( " . stream_perms_api_uids(PERMS_PUBLIC) . " ) AND item_private = 0 AND item_wall = 1 "; + $uids = " AND item.uid IN ( " . stream_perms_api_uids(PERMS_PUBLIC) . " ) AND item.item_private = 0 AND item.item_wall = 1 "; } $item_normal = item_normal(); // Filter internal follow activities and strerams add/remove activities - $item_normal .= " AND verb NOT IN ('Add', 'Remove', 'Follow', 'Ignore', '" . dbesc(ACTIVITY_FOLLOW) . "') "; + $item_normal .= " AND item.verb NOT IN ('Add', 'Remove', 'Follow', 'Ignore', '" . dbesc(ACTIVITY_FOLLOW) . "') "; if ($notifications) { - $items = q("SELECT * FROM item + $items = 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 true $uids - AND created <= '%s' - AND obj_type NOT IN ('Document', 'Video', 'Audio', 'Image') - AND author_xchan != '%s' - AND created > '%s' + AND item.created <= '%s' + AND item.created > '%s' + AND item.obj_type NOT IN ('Document', 'Video', 'Audio', 'Image') + AND NOT (item.verb = 'Announce' AND item.item_thread_top = 1) -- only show the announce activity not the resulting item + AND NOT item.author_xchan = '%s' $item_normal $sql_extra $sql_extra2 $sql_extra3 - ORDER BY created DESC LIMIT $limit OFFSET $offset", + ORDER BY item.created DESC LIMIT $limit OFFSET $offset", dbescdate($_SESSION['sse_loadtime']), - dbesc(self::$ob_hash), - dbescdate($_SESSION['last_login_date'] ?? $_SESSION['static_loadtime']) + dbescdate($_SESSION['last_login_date'] ?? $_SESSION['static_loadtime']), + dbesc(self::$ob_hash) ); if($items) { @@ -502,13 +536,15 @@ class Sse_bs extends Controller { } } - $r = q("SELECT id FROM item + $r = q("SELECT id, body FROM item WHERE true $uids + AND created <= '%s' AND created > '%s' $item_normal $sql_extra $sql_extra3 AND author_xchan != '%s' LIMIT 100", + dbescdate($_SESSION['sse_loadtime']), dbescdate($_SESSION['last_login_date'] ?? $_SESSION['static_loadtime']), dbesc(self::$ob_hash) ); @@ -600,25 +636,15 @@ class Sse_bs extends Controller { $i = 0; for($x = 0; $x < $fcount; $x ++) { - /* - $p = q("SELECT oid AS parent FROM term WHERE uid = %d AND ttype = %d AND term = '%s'", - intval(self::$uid), - intval(TERM_FORUM), - dbesc($forums[$x]['xchan_name']) - ); - - $p_str = ids_to_querystr($p, 'parent'); - $p_sql = (($p_str) ? "OR parent IN ( $p_str )" : ''); - */ $r = q("select count(*) as total from item - where uid = %d and ( owner_xchan = '%s' OR author_xchan = '%s' $p_sql ) and verb != 'Announce' and item_unseen = 1 $sql_extra $item_normal", + where uid = %d and (owner_xchan = '%s' or author_xchan = '%s') and author_xchan != '%s' and verb != 'Announce' and item_unseen = 1 $sql_extra $item_normal", intval(self::$uid), dbesc($forums[$x]['xchan_hash']), - dbesc($forums[$x]['xchan_hash']) + dbesc($forums[$x]['xchan_hash']), + dbesc(self::$ob_hash) ); - if($r[0]['total']) { $forums[$x]['notify_link'] = z_root() . '/network/?f=&pf=1&unseen=1&cid=' . $forums[$x]['abook_id']; diff --git a/Zotlabs/Module/Viewsrc.php b/Zotlabs/Module/Viewsrc.php index 3e49b9db4..cfc184a9d 100644 --- a/Zotlabs/Module/Viewsrc.php +++ b/Zotlabs/Module/Viewsrc.php @@ -6,34 +6,34 @@ namespace Zotlabs\Module; class Viewsrc extends \Zotlabs\Web\Controller { function get() { - + $o = ''; - + $sys = get_sys_channel(); - + $item_id = ((argc() > 1) ? intval(argv(1)) : 0); $json = ((argc() > 2 && argv(2) === 'json') ? true : false); $dload = ((argc() > 2 && argv(2) === 'download') ? true : false); - + if(! local_channel()) { notice( t('Permission denied.') . EOL); } - - + + if(! $item_id) { \App::$error = 404; notice( t('Item not found.') . EOL); } - + $item_normal = item_normal_search(); - + if(local_channel() && $item_id) { - $r = q("select id, mid, item_flags, mimetype, item_obscured, body, llink, plink from item where uid in (%d , %d) and id = %d $item_normal limit 1", + $r = q("select id, mid, uuid, item_flags, mimetype, item_obscured, body, llink, plink from item where uid in (%d , %d) and id = %d $item_normal limit 1", intval(local_channel()), intval($sys['channel_id']), intval($item_id) ); - + if($r) { if(intval($r[0]['item_obscured'])) $dload = true; @@ -50,18 +50,18 @@ class Viewsrc extends \Zotlabs\Web\Controller { $o = (($json) ? json_encode($content) : $content); } } - + if(is_ajax()) { echo '<div class="p-1">'; - echo '<div>id: ' . $r[0]['id'] . ' | <a href="' . $r[0]['plink'] . '" target="_blank">plink</a> | <a href="' . $r[0]['llink'] . '" target="_blank">llink</a><br>mid: ' . $r[0]['mid'] . '</div>'; + echo '<div>id: ' . $r[0]['id'] . ' | <a href="' . $r[0]['plink'] . '" target="_blank">plink</a> | <a href="' . $r[0]['llink'] . '" target="_blank">llink</a><br>mid: ' . $r[0]['mid'] . '<br>uuid: ' . $r[0]['uuid'] . '</div>'; echo '<hr>'; echo '<pre class="p-1">' . $o . '</pre>'; echo '</div>'; killme(); - } - + } + return $o; } - - + + } diff --git a/Zotlabs/Module/Wall_attach.php b/Zotlabs/Module/Wall_attach.php index e354f58f1..84c76f8dd 100644 --- a/Zotlabs/Module/Wall_attach.php +++ b/Zotlabs/Module/Wall_attach.php @@ -10,7 +10,7 @@ class Wall_attach extends \Zotlabs\Web\Controller { function init() { logger('request_method: ' . $_SERVER['REQUEST_METHOD'],LOGGER_DATA,LOG_INFO); - logger('wall_attach: ' . print_r($_REQUEST,true),LOGGER_DEBUG,LOG_INFO); + logger('wall_attach: ' . print_r($_POST,true),LOGGER_DEBUG,LOG_INFO); logger('wall_attach files: ' . print_r($_FILES,true),LOGGER_DEBUG,LOG_INFO); // for testing without actually storing anything // http_status_exit(200,'OK'); @@ -18,12 +18,11 @@ class Wall_attach extends \Zotlabs\Web\Controller { function post() { - $using_api = false; $result = []; - if($_REQUEST['api_source'] && array_key_exists('media',$_FILES)) { + if($_POST['api_source'] && array_key_exists('media',$_FILES)) { $using_api = true; } @@ -98,8 +97,8 @@ class Wall_attach extends \Zotlabs\Web\Controller { $r = attach_store($channel, get_observer_hash(), '', $data); - if(! $r['success']) { - notice( $r['message'] . EOL); + if (!$r['success']) { + notice($r['message'] . EOL); killme(); } diff --git a/Zotlabs/Module/Xref.php b/Zotlabs/Module/Xref.php deleted file mode 100644 index e9d494da4..000000000 --- a/Zotlabs/Module/Xref.php +++ /dev/null @@ -1,26 +0,0 @@ -<?php -namespace Zotlabs\Module; - - -class Xref extends \Zotlabs\Web\Controller { - - function init() { - // Sets a referral URL using an xchan directly - // Link format: example.com/xref/[xchan]/[TargetURL] - // Target URL is optional. - // Cookie lasts 24 hours to survive a browser restart. Contains no personal - // information at all - just somebody else's xchan. - $referrer = argv(1); - $expire=time()+60*60*2; - $path = 'xref'; - setcookie($path, $referrer, $expire, "/"); - $url = ''; - - if (argc() > 2) - $url = argv(2); - - goaway (z_root() . '/' . $url); - - } - -} diff --git a/Zotlabs/Module/Zot_probe.php b/Zotlabs/Module/Zot_probe.php index 3eaabdd92..feb51f071 100644 --- a/Zotlabs/Module/Zot_probe.php +++ b/Zotlabs/Module/Zot_probe.php @@ -9,19 +9,29 @@ class Zot_probe extends \Zotlabs\Web\Controller { function get() { + if (!local_channel()) { + return; + } + $addr = $_GET['addr'] ?? ''; $o = '<h3>Zot6 Probe Diagnostic</h3>'; $o .= '<form action="zot_probe" method="get">'; $o .= 'Lookup URI: <input type="text" style="width: 250px;" name="addr" value="' . $addr .'" /><br>'; + $o .= '<input type="checkbox" name="sign" /> Sign request <br>'; $o .= '<input type="submit" name="submit" value="Submit" /></form>'; $o .= '<br /><br />'; if($addr) { - $x = Zotfinger::exec($addr); + $channel = null; + if ($_GET['sign']) { + $channel = get_sys_channel(); + } + + $x = Zotfinger::exec($addr, $channel); $o .= '<pre>' . htmlspecialchars(print_array($x)) . '</pre>'; diff --git a/Zotlabs/Photo/PhotoGd.php b/Zotlabs/Photo/PhotoGd.php index a81d9cae2..8d46ae4d5 100644 --- a/Zotlabs/Photo/PhotoGd.php +++ b/Zotlabs/Photo/PhotoGd.php @@ -25,6 +25,8 @@ class PhotoGd extends PhotoDriver { $t['image/gif'] = 'gif'; if(\imagetypes() & IMG_WEBP) $t['image/webp'] = 'webp'; + if(\imagetypes() & IMG_AVIF) + $t['image/avif'] = 'avif'; return $t; } @@ -184,6 +186,19 @@ class PhotoGd extends PhotoDriver { break; + case 'image/avif': + $quality = Config::Get('system', 'avif_quality'); + + if((! $quality) || ($quality > 100)) { + $quality = AVIF_QUALITY; + } + + if (function_exists('imageavif')) { + \imageavif($this->image, NULL, $quality); + } + + break; + case 'image/jpeg': // gd can lack imagejpeg(), but we verify during installation it is available diff --git a/Zotlabs/Render/SmartyInterface.php b/Zotlabs/Render/SmartyInterface.php index 64c6aa377..003262939 100644 --- a/Zotlabs/Render/SmartyInterface.php +++ b/Zotlabs/Render/SmartyInterface.php @@ -2,7 +2,7 @@ namespace Zotlabs\Render; -use Smarty; +use Smarty\Smarty; use App; class SmartyInterface extends Smarty { @@ -19,20 +19,20 @@ class SmartyInterface extends Smarty { // The order is thus very important here $template_dirs = array('theme' => "view/theme/$thname/tpl/"); - if ( x(App::$theme_info,"extends") ) { + if (!empty(App::$theme_info['extends'])) { $template_dirs = $template_dirs + array('extends' => "view/theme/" . App::$theme_info["extends"] . "/tpl/"); } $template_dirs = $template_dirs + array('base' => 'view/tpl/'); $this->setTemplateDir($template_dirs); $basecompiledir = App::$config['system']['smarty3_folder']; - + $this->setCompileDir($basecompiledir.'/compiled/'); $this->setConfigDir($basecompiledir.'/config/'); $this->setCacheDir($basecompiledir.'/cache/'); - $this->left_delimiter = App::get_template_ldelim('smarty3'); - $this->right_delimiter = App::get_template_rdelim('smarty3'); + $this->setLeftDelimiter(App::get_template_ldelim('smarty3')); + $this->setRightDelimiter(App::get_template_rdelim('smarty3')); // Don't report errors so verbosely $this->error_reporting = E_ALL & ~E_WARNING & ~E_NOTICE; diff --git a/Zotlabs/Render/Theme.php b/Zotlabs/Render/Theme.php index a42b65e03..2faf3631a 100644 --- a/Zotlabs/Render/Theme.php +++ b/Zotlabs/Render/Theme.php @@ -26,9 +26,9 @@ class Theme { */ static public function current() { - self::$system_theme = ((isset(App::$config['system']['theme'])) + self::$system_theme = ((!empty(App::$config['system']['theme'])) ? App::$config['system']['theme'] : ''); - self::$session_theme = ((isset($_SESSION) && x($_SESSION, 'theme')) + self::$session_theme = ((!empty($_SESSION['theme'])) ? $_SESSION['theme'] : self::$system_theme); $page_theme = null; @@ -87,8 +87,13 @@ class Theme { // Find any theme at all and use it. $fallback = array_merge(glob('view/theme/*/css/style.css'), glob('view/theme/*/php/style.php')); - if(count($fallback)) - return(array(str_replace('view/theme/', '', substr($fallback[0], 0, -14)))); + + if (empty($fallback)) { + logger('Unable to find a theme'); + http_status_exit(500, 'internal server error'); + } + + return(array(str_replace('view/theme/', '', substr($fallback[0], 0, -14)))); } @@ -111,7 +116,7 @@ class Theme { $opts = ''; $opts = (($uid) ? '?puid=' . $uid : ''); - $schema_str = ((x(App::$layout,'schema')) ? '&schema=' . App::$layout['schema'] : ''); + $schema_str = ((!empty(App::$layout['schema'])) ? '&schema=' . App::$layout['schema'] : ''); if(($s) && (! $schema_str)) $schema_str = '&schema=' . $s; diff --git a/Zotlabs/Storage/Browser.php b/Zotlabs/Storage/Browser.php index 6a9cd61ef..6ea6031d3 100644 --- a/Zotlabs/Storage/Browser.php +++ b/Zotlabs/Storage/Browser.php @@ -5,6 +5,7 @@ namespace Zotlabs\Storage; use Sabre\DAV; use App; use Zotlabs\Lib\Config; +use Zotlabs\Lib\Text; /** * @brief Provides a DAV frontend for the webbrowser. @@ -260,13 +261,16 @@ class Browser extends DAV\Browser\Plugin { } } + $display_path_encoded = Text::rawurlencode_parts($data['display_path']); + $href_encoded = Text::rawurlencode_parts($href); + // put the array for this file together $ft['attach_id'] = $id; // $ft['icon'] = $icon; $ft['photo_icon'] = $photo_icon; $ft['is_creator'] = $is_creator; - $ft['rel_path'] = (($data) ? '/cloud/' . $nick .'/' . $data['display_path'] : $href); - $ft['full_path'] = z_root() . (($data) ? '/cloud/' . $nick .'/' . $data['display_path'] : $href); + $ft['rel_path'] = (($data) ? '/cloud/' . $nick .'/' . $display_path_encoded : $href_encoded); + $ft['full_path'] = z_root() . (($data) ? '/cloud/' . $nick .'/' . $display_path_encoded : $href_encoded); $ft['name'] = $name; $ft['type'] = $type; $ft['size'] = $size; diff --git a/Zotlabs/Thumbs/Epubthumb.php b/Zotlabs/Thumbs/Epubthumb.php index b50583e30..af372e85c 100644 --- a/Zotlabs/Thumbs/Epubthumb.php +++ b/Zotlabs/Thumbs/Epubthumb.php @@ -2,8 +2,11 @@ namespace Zotlabs\Thumbs; -use SebLucas\EPubMeta\EPub; +use DOMDocument; +use DOMElement; +use DOMXPath; use GdImage; +use ZipArchive; /** * Thumbnail creation for epub files. @@ -24,20 +27,21 @@ class Epubthumb { * Create the thumbnail if the Epub has a cover. * * @param array $attach - * @param number $preview_style unused - * @param number $height (optional) default 300 - * @param number $width (optional) default 300 + * @param int $preview_style unused + * @param int $height (optional) default 300 + * @param int $width (optional) default 300 * * @SuppressWarnings(PHPMD.UnusedFormalParameter) + * phpcs:disable Generic.CodeAnalysis.UnusedFunctionParameter.FoundBeforeLastUsed */ - function Thumb($attach, $preview_style, $height = 300, $width = 300) { + function Thumb($attach, $preview_style, $height = 300, $width = 300): void { $file = dbunescbin($attach['content']); if (!$file) { return; } - $image = $this->getCover($file); + $image = $this->getCoverFromEpub($file); if ($image) { $srcwidth = imagesx($image); @@ -56,15 +60,139 @@ class Epubthumb { } } - private function getCover(string $filename): GdImage|false { - $epub = new EPub($filename); - $cover = $epub->getCover(); + /** + * Fetch the cover from the epub archive, if it's present. + * + * There's a few limitations here: This will only work if the cover + * is a raster image of a supported format. SVG does not work, neither + * will other schemes sometimes used for cover/front page. + * + * @param string $filename The local filename of the epub archive. + * + * @return GdImage|false If a cover is found, it is returned as a + * GdImage object. Otherwise return false. + */ + private function getCoverFromEpub(string $filename): GdImage|false { + $epub = new ZipArchive(); + $rc = $epub->open($filename, ZipArchive::RDONLY); + + if ($rc !== true) { + logger("Error opening file '{$filename}': rc = ${rc}.", LOGGER_DEBUG, LOG_DEBUG); + return false; + } + + $cover = false; + $cover_name = $this->parseEpub($epub); + if ($cover_name !== false) { + $cover = $epub->getFromName($cover_name); + if ($cover === false) { + logger("File '{$cover_name}' not found in EPUB.", LOGGER_DEBUG, LOG_DEBUG); + } + } + + $epub->close(); - if (! empty($cover)) { + if ($cover !== false && !empty($cover)) { return imagecreatefromstring($cover); } else { return false; } } + + /** + * Parse the epub to find the path of the cover image. + * + * @param ZipArchive $epub An opened epub ZipArchive. + * + * @return string|false The path to the cover image or false. + */ + private function parseEpub(ZipArchive $epub): string|false { + $packagePath = $this->getEpubPackagePath($epub); + if ($packagePath !== false) { + $package = $epub->getFromName($packagePath); + if ($package === false || empty($package)) { + logger("Package file '${packagePath}' not found in EPUB", LOGGER_DEBUG, LOG_DEBUG); + return false; + } + + $domdoc = new DOMDocument(); + $domdoc->loadXML($package); + $xpath = new DOMXPath($domdoc); + $xpath->registerNamespace("n", "http://www.idpf.org/2007/opf"); + $nodes = $xpath->query('/n:package/n:manifest/n:item[@properties="cover-image"]'); + + if ($nodes->count() === 0) { + logger('No cover found in EPUB manifest.', LOGGER_DEBUG, LOG_DEBUG); + return false; + } + + $node = $nodes->item(0); + if ($node === null) { + logger('No nodes in non-empty node list?', LOGGER_DEBUG, LOG_DEBUG); + return false; + } + + if (is_a($node, DOMElement::class)) { + // The URL's in the package file is relative to the subdirectory + // within the epub archive where it is located. See + // https://www.w3.org/TR/epub-33/#sec-parsing-urls-metainf + return dirname($packagePath) . '/' . $node->getAttribute('href'); + } + } + + return false; + } + + /** + * Locate the package file within the epub. + * + * The package file in an epub archive contains the manifest + * that again may contain a reference to the cover for the + * epub. + * + * @param ZipArchive $epub An opened epub archive. + * + * @return string|false The full pathname of the package file or false. + */ + private function getEpubPackagePath(ZipArchive $epub): string|false { + // + // The only mandatory known file within the archive is the + // container file, so we fetch it to find the reference to + // the package file. + // + // See: https://www.w3.org/TR/epub-33/#sec-container-metainf + // + $container = $epub->getFromName('META-INF/container.xml'); + + if ($container === false || empty($container)) { + logger('No container in archive, probably not an EPUB.', LOGGER_DEBUG, LOG_DEBUG); + return false; + } + + $domdoc = new DOMDocument(); + $domdoc->loadXML($container); + $nodes = $domdoc->getElementsByTagName('rootfile'); + + if ($nodes->count() == 0) { + logger('EPUB rootfile not found, is this an epub?', LOGGER_DEBUG, LOG_DEBUG); + return false; + } + + $packageNode = $nodes->item(0); + if ($packageNode === null || !is_a($packageNode, DOMElement::class)) { + logger('EPUB rootfile element missing or invalid.', LOGGER_DEBUG, LOG_DEBUG); + return false; + } + + $packagePath = $packageNode->getAttribute('full-path'); + $packageMediaType = $packageNode->getAttribute('media-type'); + + if (empty($packagePath) || $packageMediaType !== 'application/oebps-package+xml') { + logger('EPUB package path missing or incorrect media type.', LOGGER_DEBUG, LOG_DEBUG); + return false; + } + + return $packagePath; + } } diff --git a/Zotlabs/Web/HTTPSig.php b/Zotlabs/Web/HTTPSig.php index 7c289ff5f..ce56ae46b 100644 --- a/Zotlabs/Web/HTTPSig.php +++ b/Zotlabs/Web/HTTPSig.php @@ -2,6 +2,7 @@ namespace Zotlabs\Web; +use App; use DateTime; use DateTimeZone; use Zotlabs\Lib\Activity; @@ -11,6 +12,7 @@ use Zotlabs\Lib\Keyutils; use Zotlabs\Lib\Webfinger; use Zotlabs\Lib\Zotfinger; use Zotlabs\Lib\Libzot; +use HttpSignature\HttpMessageSigner; /** * @brief Implements HTTP Signatures per draft-cavage-http-signatures-10. @@ -88,7 +90,7 @@ class HTTPSig { // See draft-cavage-http-signatures-10 - static function verify($data, $key = '', $keytype = '') { + public static function verify($data, $key = '', $keytype = '') { $body = $data; $headers = null; @@ -102,11 +104,49 @@ class HTTPSig { 'content_valid' => false ]; - $headers = self::find_headers($data, $body); - if (!$headers) + if (!$headers) { return $result; + } + + if (array_key_exists('signature-input', $headers) && array_key_exists('signature', $headers)) { + $found = preg_match('/keyid="(.*?)"/', $headers['signature-input'], $matches); + $keyId = ($found) ? $matches[1] : ''; + + if (!$keyId) { + return $result; + } + + $found = preg_match('/alg="(.*?)"/', $headers['signature-input'], $matches); + $alg = ($found) ? $matches[1] : null; + + $keyInfo = self::get_key($key, $keytype, $keyId); + $publicKey = $keyInfo['public_key']; + + $messageSigner = new HttpMessageSigner(); + + $messageSigner->setPublicKey($publicKey); + $messageSigner->setAlgorithm($alg); + $messageSigner->setKeyId($keyId); + + $messageSigner->setNonce(preg_match('/nonce="(.*?)"/', $headers['signature-input'], $matches) ? $matches[1] : ''); + $messageSigner->setTag(preg_match('/tag="(.*?)"/', $headers['signature-input'], $matches) ? $matches[1] : ''); + $messageSigner->setCreated(preg_match('/created=([0-9]+)/', $headers['signature-input'], $matches) ? $matches[1] : ''); + $messageSigner->setExpires(preg_match('/expires=([0-9]+)/', $headers['signature-input'], $matches) ? $matches[1] : ''); + + $verified = $messageSigner->verifyRequest(App::$request); + logger('verified (RFC9421): ' . (($verified) ? 'true' : 'false'), LOGGER_DEBUG); + + return [ + 'signer' => $keyId, + 'portable_id' => $keyInfo['portable_id'] ?? '', + 'header_signed' => true, + 'header_valid' => $verified, + 'content_signed' => array_key_exists('content-digest', $headers), + 'content_valid' => $verified + ]; + } if (is_array($body)) { btlogger('body is array:' . print_r($body, true)); diff --git a/Zotlabs/Web/Router.php b/Zotlabs/Web/Router.php index 122e7ff73..9f4cb4b4a 100644 --- a/Zotlabs/Web/Router.php +++ b/Zotlabs/Web/Router.php @@ -4,6 +4,7 @@ namespace Zotlabs\Web; use App; use Zotlabs\Extend\Route; +use Zotlabs\Render\Theme; use Zotlabs\Lib\Config; use Exception; @@ -33,6 +34,7 @@ use Exception; * so within the module init and/or post functions and then invoke killme() to terminate * further processing. */ + class Router { private $modname = ''; @@ -49,7 +51,7 @@ class Router { $module = App::$module; $modname = "Zotlabs\\Module\\" . ucfirst($module); - if(strlen($module)) { + if(!empty($module)) { /* * We will always have a module name. @@ -57,11 +59,11 @@ class Router { */ $routes = Route::get(); - if($routes) { + if ($routes) { foreach($routes as $route) { - if(is_array($route) && file_exists($route[0]) && strtolower($route[1]) === $module) { + if (is_array($route) && file_exists($route[0]) && strtolower($route[1]) === $module) { include_once($route[0]); - if(class_exists($modname)) { + if (class_exists($modname)) { $this->controller = new $modname; $this->module_loaded = true; } @@ -71,14 +73,14 @@ class Router { // legacy plugins - this can be removed when they have all been converted - if(! ($this->module_loaded)) { - if(is_array(App::$plugins) && in_array($module, App::$plugins) && file_exists("addon/{$module}/{$module}.php")) { + if (!$this->module_loaded) { + if (is_array(App::$plugins) && in_array($module, App::$plugins) && file_exists("addon/{$module}/{$module}.php")) { include_once("addon/{$module}/{$module}.php"); - if(class_exists($modname)) { + if (class_exists($modname)) { $this->controller = new $modname; $this->module_loaded = true; } - elseif(function_exists($module . '_module')) { + elseif (function_exists($module . '_module')) { $this->module_loaded = true; } } @@ -89,7 +91,7 @@ class Router { * Otherwise, look for the standard program module */ - if(! ($this->module_loaded)) { + if (!$this->module_loaded) { try { $filename = 'Zotlabs/SiteModule/'. ucfirst($module). '.php'; if(file_exists($filename)) { @@ -105,15 +107,16 @@ class Router { $this->module_loaded = true; } } - if(! $this->module_loaded) + if (!$this->module_loaded) { throw new Exception('Module not found'); + } } - catch(Exception $e) { - if(file_exists("mod/site/{$module}.php")) { + catch (Exception $e) { + if (file_exists("mod/site/{$module}.php")) { include_once("mod/site/{$module}.php"); $this->module_loaded = true; } - elseif(file_exists("mod/{$module}.php")) { + elseif (file_exists("mod/{$module}.php")) { include_once("mod/{$module}.php"); $this->module_loaded = true; } @@ -125,6 +128,7 @@ class Router { 'installed' => $this->module_loaded, 'controller' => $this->controller ]; + /** * @hooks module_loaded * Called when a module has been successfully locate to server a URL request. @@ -138,7 +142,8 @@ class Router { * * \e mixed \b controller - The initialized module object */ call_hooks('module_loaded', $x); - if($x['installed']) { + + if ($x['installed']) { $this->module_loaded = true; $this->controller = $x['controller']; } @@ -147,7 +152,7 @@ class Router { * The URL provided does not resolve to a valid module. */ - if(! ($this->module_loaded)) { + if (!$this->module_loaded) { // undo the setting of a letsencrypt acme-challenge rewrite rule // which blocks access to our .well-known routes. @@ -155,8 +160,8 @@ class Router { // for a custom .htaccess in the .well-known directory; but they should // make the file read-only so letsencrypt doesn't modify it - if(strpos($_SERVER['REQUEST_URI'],'/.well-known/') === 0) { - if(file_exists('.well-known/.htaccess') && Config::Get('system','fix_apache_acme',true)) { + if (strpos($_SERVER['REQUEST_URI'],'/.well-known/') === 0) { + if (file_exists('.well-known/.htaccess') && Config::Get('system','fix_apache_acme',true)) { rename('.well-known/.htaccess','.well-known/.htaccess.old'); } } @@ -166,16 +171,17 @@ class Router { 'installed' => $this->module_loaded, 'controller' => $this->controller ]; + call_hooks('page_not_found',$x); // Stupid browser tried to pre-fetch our Javascript img template. // Don't log the event or return anything - just quietly exit. - if((x($_SERVER, 'QUERY_STRING')) && preg_match('/{[0-9]}/', $_SERVER['QUERY_STRING']) !== 0) { + if (!empty($_SERVER['QUERY_STRING']) && preg_match('/{[0-9]}/', $_SERVER['QUERY_STRING']) !== 0) { killme(); } - if(Config::Get('system','log_404',true)) { + if (Config::Get('system','log_404',true)) { logger("Module {$module} not found.", LOGGER_DEBUG, LOG_WARNING); logger('index.php: page not found: ' . $_SERVER['REQUEST_URI'] . ' ADDRESS: ' . $_SERVER['REMOTE_ADDR'] . ' QUERY: ' @@ -206,7 +212,7 @@ class Router { * Call module functions */ - if($this->module_loaded) { + if ($this->module_loaded) { App::$page['page_title'] = App::$module; $placeholder = ''; @@ -218,13 +224,18 @@ class Router { * to over-ride them. */ - $arr = array('init' => true, 'replace' => false); + $arr = [ + 'init' => true, + 'replace' => false + ]; + call_hooks(App::$module . '_mod_init', $arr); - if(! $arr['replace']) { - if($this->controller && method_exists($this->controller,'init')) { + + if (!$arr['replace']) { + if ($this->controller && method_exists($this->controller,'init')) { $this->controller->init(); } - elseif(function_exists(App::$module . '_init')) { + elseif (function_exists(App::$module . '_init')) { $func = App::$module . '_init'; $func(); } @@ -250,52 +261,58 @@ class Router { * load current theme info */ - $current_theme = \Zotlabs\Render\Theme::current(); + $current_theme = Theme::current(); $theme_info_file = 'view/theme/' . $current_theme[0] . '/php/theme.php'; if (file_exists($theme_info_file)){ require_once($theme_info_file); } - if(function_exists(str_replace('-', '_', $current_theme[0]) . '_init')) { + if (function_exists(str_replace('-', '_', $current_theme[0]) . '_init')) { $func = str_replace('-', '_', $current_theme[0]) . '_init'; $func(); } - elseif (x(App::$theme_info, 'extends') && file_exists('view/theme/' . App::$theme_info['extends'] . '/php/theme.php')) { + elseif (!empty(App::$theme_info['extends']) && file_exists('view/theme/' . App::$theme_info['extends'] . '/php/theme.php')) { require_once('view/theme/' . App::$theme_info['extends'] . '/php/theme.php'); - if(function_exists(str_replace('-', '_', App::$theme_info['extends']) . '_init')) { + if (function_exists(str_replace('-', '_', App::$theme_info['extends']) . '_init')) { $func = str_replace('-', '_', App::$theme_info['extends']) . '_init'; $func(); } } - if(($_SERVER['REQUEST_METHOD'] === 'POST') && (! App::$error) && (! x($_POST, 'auth-params'))) { + if ($_SERVER['REQUEST_METHOD'] === 'POST' && !App::$error && empty($_POST['auth-params'])) { call_hooks(App::$module . '_mod_post', $_POST); - if($this->controller && method_exists($this->controller,'post')) { + if ($this->controller && method_exists($this->controller, 'post')) { $this->controller->post(); } - elseif(function_exists(App::$module . '_post')) { + elseif (function_exists(App::$module . '_post')) { $func = App::$module . '_post'; $func(); } } - if(! App::$error) { - $arr = array('content' => App::$page['content'], 'replace' => false); + if (!App::$error) { + $arr = [ + 'content' => App::$page['content'], + 'replace' => false + ]; + call_hooks(App::$module . '_mod_content', $arr); - if(! $arr['replace']) { - if($this->controller && method_exists($this->controller,'get')) { + if (!$arr['replace']) { + if ($this->controller && method_exists($this->controller, 'get')) { $arr = array('content' => $this->controller->get()); } - elseif(function_exists(App::$module . '_content')) { + elseif (function_exists(App::$module . '_content')) { $func = App::$module . '_content'; $arr = array('content' => $func()); } } + call_hooks(App::$module . '_mod_aftercontent', $arr); - App::$page['content'] = ((isset($arr['replace'])) ? $arr['content'] : App::$page['content'] . $arr['content']); + + App::$page['content'] = ((empty($arr['replace'])) ? App::$page['content'] . $arr['content'] : $arr['content']); } } } diff --git a/Zotlabs/Web/Session.php b/Zotlabs/Web/Session.php index ec26e6b0d..1762d3832 100644 --- a/Zotlabs/Web/Session.php +++ b/Zotlabs/Web/Session.php @@ -132,7 +132,7 @@ class Session { else logger('no session handler'); - if (x($_COOKIE, 'jsdisabled')) { + if (!empty($_COOKIE['jsdisabled'])) { setcookie( 'jsdisabled', $_COOKIE['jsdisabled'], diff --git a/Zotlabs/Web/WebServer.php b/Zotlabs/Web/WebServer.php index 19f14ee8a..89ef755d9 100644 --- a/Zotlabs/Web/WebServer.php +++ b/Zotlabs/Web/WebServer.php @@ -2,7 +2,9 @@ namespace Zotlabs\Web; +use App; use Zotlabs\Lib\Text; +use GuzzleHttp\Psr7\ServerRequest; class WebServer { @@ -17,9 +19,10 @@ class WebServer { $installed = sys_boot(); + App::$request = ServerRequest::fromGlobals(); - \App::$language = get_best_language(); - load_translation_table(\App::$language, !$installed); + App::$language = get_best_language(); + load_translation_table(App::$language, !$installed); /** @@ -33,8 +36,8 @@ class WebServer { * */ - if(\App::$session) { - \App::$session->start(); + if(App::$session) { + App::$session->start(); } else { session_start(); @@ -53,15 +56,15 @@ class WebServer { unset($_SESSION['language']); } - if ((x($_SESSION, 'language')) && ($_SESSION['language'] !== \App::$language)) { - \App::$language = $_SESSION['language']; + if ((!empty($_SESSION['language'])) && ($_SESSION['language'] !== App::$language)) { + App::$language = $_SESSION['language']; load_translation_table(\App::$language); } - if (x($_GET,'zid') && $installed) { - \App::$query_string = strip_zids(\App::$query_string); + if (!empty($_GET['zid']) && $installed) { + App::$query_string = strip_zids(App::$query_string); if(! local_channel()) { - if (!isset($_SESSION['my_address']) || $_SESSION['my_address'] != $_GET['zid']) { + if (!isset($_SESSION['my_address'])) { $_SESSION['my_address'] = Text::escape_tags($_GET['zid']); $_SESSION['authenticated'] = 0; } @@ -71,26 +74,28 @@ class WebServer { } } - if (x($_GET,'zat') && $installed) { - \App::$query_string = strip_zats(\App::$query_string); + if (!empty($_GET['zat']) && $installed) { + App::$query_string = strip_zats(App::$query_string); if(! local_channel()) { zat_init(); } } - if (x($_REQUEST,'owt') && $installed) { + if (!empty($_REQUEST['owt']) && $installed) { $token = $_REQUEST['owt']; - \App::$query_string = strip_query_param(\App::$query_string,'owt'); + App::$query_string = strip_query_param(App::$query_string,'owt'); owt_init($token); } - if((x($_SESSION, 'authenticated')) || (x($_POST, 'auth-params')) || (\App::$module === 'login')) + if(!empty($_SESSION['authenticated']) || !empty($_POST['auth-params']) || App::$module === 'login') { require('include/auth.php'); + } if (!$installed) { /* Allow an exception for the view module so that pcss will be interpreted during installation */ - if(\App::$module != 'view') - \App::$module = 'setup'; + if(App::$module != 'view') { + App::$module = 'setup'; + } } else { @@ -111,17 +116,7 @@ class WebServer { $Router->Dispatch(); - // TODO: this is not used for anything atm and messes up comanche templates by adding some javascript - //$this->set_homebase(); - - // now that we've been through the module content, see if the page reported - // a permission problem and if so, a 403 response would seem to be in order. - - if(isset($_SESSION['sysmsg']) && is_array($_SESSION['sysmsg']) && stristr(implode("", $_SESSION['sysmsg']), t('Permission denied'))) { - header($_SERVER['SERVER_PROTOCOL'] . ' 403 ' . t('Permission denied.')); - } - - call_hooks('page_end', \App::$page['content']); + call_hooks('page_end', App::$page['content']); construct_page(); @@ -133,10 +128,11 @@ class WebServer { /* initialise content region */ - if(! x(\App::$page, 'content')) - \App::$page['content'] = ''; + if(empty(App::$page['content'])) { + App::$page['content'] = ''; + } - call_hooks('page_content_top', \App::$page['content']); + call_hooks('page_content_top', App::$page['content']); } @@ -148,44 +144,24 @@ class WebServer { * to all protocol drivers; thus doing it here avoids duplication. */ - if (( \App::$module === 'channel' ) && argc() > 1) { - \App::$channel_links = [ + if (App::$module === 'channel' && argc() > 1) { + App::$channel_links = [ [ 'rel' => 'lrdd', 'type' => 'application/xrd+xml', - 'url' => z_root() . '/xrd?f=&uri=acct%3A' . argv(1) . '%40' . \App::get_hostname() + 'url' => z_root() . '/xrd?f=&uri=acct%3A' . argv(1) . '%40' . App::get_hostname() ], [ 'rel' => 'jrd', 'type' => 'application/jrd+json', - 'url' => z_root() . '/.well-known/webfinger?f=&resource=acct%3A' . argv(1) . '%40' . \App::get_hostname() + 'url' => z_root() . '/.well-known/webfinger?f=&resource=acct%3A' . argv(1) . '%40' . App::get_hostname() ], ]; - $x = [ 'channel_address' => argv(1), 'channel_links' => \App::$channel_links ]; + $x = [ 'channel_address' => argv(1), 'channel_links' => App::$channel_links ]; call_hooks('channel_links', $x ); - \App::$channel_links = $x['channel_links']; + App::$channel_links = $x['channel_links']; header('Link: ' . \App::get_channel_links()); } } - - private function set_homebase() { - - // If you're just visiting, let javascript take you home - - if(x($_SESSION, 'visitor_home')) { - $homebase = $_SESSION['visitor_home']; - } - elseif(local_channel()) { - $homebase = z_root() . '/channel/' . \App::$channel['channel_address']; - } - - if(isset($homebase)) { - \App::$page['content'] .= '<script>var homebase = "' . $homebase . '";</script>'; - } - - } - - - } diff --git a/Zotlabs/Widget/Activity_order.php b/Zotlabs/Widget/Activity_order.php index e8ee11508..8d47370db 100644 --- a/Zotlabs/Widget/Activity_order.php +++ b/Zotlabs/Widget/Activity_order.php @@ -16,7 +16,7 @@ class Activity_order { return ''; if(! feature_enabled(local_channel(),'order_tab')) { - set_pconfig(local_channel(), 'mod_network', 'order', 0); + set_pconfig(local_channel(), 'mod_network', 'order', 'created'); return ''; } @@ -26,17 +26,17 @@ class Activity_order { if(x($_GET, 'order')) { switch($_GET['order']){ - case 'post': + case 'created': $postord_active = 'active'; - set_pconfig(local_channel(), 'mod_network', 'order', 1); + set_pconfig(local_channel(), 'mod_network', 'order', 'created'); break; - case 'comment': + case 'commented': $commentord_active = 'active'; - set_pconfig(local_channel(), 'mod_network', 'order', 0); + set_pconfig(local_channel(), 'mod_network', 'order', 'commented'); break; case 'unthreaded': $unthreaded_active = 'active'; - set_pconfig(local_channel(), 'mod_network', 'order', 2); + set_pconfig(local_channel(), 'mod_network', 'order', 'unthreaded'); break; default: $commentord_active = 'active'; @@ -44,19 +44,19 @@ class Activity_order { } } else { - $order = get_pconfig(local_channel(), 'mod_network', 'order', 0); + $order = get_pconfig(local_channel(), 'mod_network', 'order', 'created'); switch($order) { - case 0: + case 'commented': $commentord_active = 'active'; break; - case 1: + case 'created': $postord_active = 'active'; break; - case 2: + case 'unthreaded': $unthreaded_active = 'active'; break; default: - $commentord_active = 'active'; + $postord_active = 'active'; } } @@ -90,26 +90,26 @@ class Activity_order { // tabs - $tabs = []; + $tabs[] = [ + 'label' => t('Posted Date'), + 'icon' => '', + 'url'=>z_root() . '/' . $cmd . '?order=created' . $filter, + 'sel'=> $postord_active, + 'title' => t('Order by last posted date'), + ]; $tabs[] = [ 'label' => t('Commented Date'), 'icon' => '', - 'url'=>z_root() . '/' . $cmd . '?f=&order=comment' . $filter, + 'url'=>z_root() . '/' . $cmd . '?order=commented' . $filter, 'sel'=> $commentord_active, 'title' => t('Order by last commented date'), ]; - $tabs[] = [ - 'label' => t('Posted Date'), - 'icon' => '', - 'url'=>z_root() . '/' . $cmd . '?f=&order=post' . $filter, - 'sel'=> $postord_active, - 'title' => t('Order by last posted date'), - ]; + $tabs[] = array( 'label' => t('Date Unthreaded'), 'icon' => '', - 'url' => z_root() . '/' . $cmd . '?f=&order=unthreaded' . $filter, + 'url' => z_root() . '/' . $cmd . '?order=unthreaded' . $filter, 'sel' => $unthreaded_active, 'title' => t('Order unthreaded by date'), ); diff --git a/Zotlabs/Widget/Album.php b/Zotlabs/Widget/Album.php index f1fa69182..667952360 100644 --- a/Zotlabs/Widget/Album.php +++ b/Zotlabs/Widget/Album.php @@ -59,6 +59,9 @@ class Album { //edit album name $album_edit = null; + $ph = photo_factory(''); + $phototypes = $ph->supportedTypes(); + $photos = array(); if($r) { $twist = 'rotright'; diff --git a/Zotlabs/Widget/Channel_activities.php b/Zotlabs/Widget/Channel_activities.php index debaf20d4..2677f82c3 100644 --- a/Zotlabs/Widget/Channel_activities.php +++ b/Zotlabs/Widget/Channel_activities.php @@ -223,7 +223,7 @@ class Channel_activities { $i[] = [ 'url' => z_root() . '/manage/' . $rr['channel_id'], 'title' => '', - 'summary' => '<div class="text-truncate lh-sm"><img src="' . $rr['xchan_photo_s'] . '" class="menu-img-2">' . '<strong>' . $rr['channel_name'] . '</strong><br><small class="opacity-75">' . $rr['xchan_addr'] . '</small></div>', + 'summary' => '<div class="text-truncate lh-sm"><img src="' . $rr['xchan_photo_s'] . '" class="menu-img-2">' . '<strong>' . $rr['channel_name'] . '</strong><br><small class="text-body-secondary">' . $rr['xchan_addr'] . '</small></div>', 'footer' => $footer ]; diff --git a/Zotlabs/Widget/Messages.php b/Zotlabs/Widget/Messages.php index f90b4f99e..cc1811ffc 100644 --- a/Zotlabs/Widget/Messages.php +++ b/Zotlabs/Widget/Messages.php @@ -28,6 +28,8 @@ class Messages { intval(TERM_FILE) ); + $file_tags = []; + if ($r) { foreach($r as $rr) { $file_tags[] = $rr['term']; @@ -42,14 +44,14 @@ class Messages { '$feature_file' => feature_enabled(local_channel(), 'filing'), '$file_tags' => $file_tags, '$strings' => [ - 'messages_title' => t('Public and restricted messages'), - 'direct_messages_title' => t('Direct messages'), - 'starred_messages_title' => t('Starred messages'), + 'messages_title' => t('Public and restricted conversations'), + 'direct_messages_title' => t('Private conversations'), + 'starred_messages_title' => t('Starred conversations'), 'filed_messages_title' => t('Filed messages'), - 'notice_messages_title' => t('Notices'), + 'notice_messages_title' => t('Notifications'), 'loading' => t('Loading'), - 'empty' => t('No messages'), - 'unseen_count' => t('Unseen'), + 'empty' => t('No conversations'), + 'unseen_count' => t('Unseen reactions'), 'filter' => t('Filter by name or address'), 'file_filter' => t('Filter by file name') ] @@ -84,8 +86,6 @@ class Messages { $entries = []; $limit = 30; $order_sql = 'i.created DESC'; - $dummy_order_sql = ''; - $filter_sql = ''; $loadtime = (($offset) ? $_SESSION['messages_loadtime'] : datetime_convert()); $vnotify = get_pconfig(local_channel(), 'system', 'vnotify', -1); @@ -101,14 +101,18 @@ class Messages { $vnotify_sql_i = " AND i.verb NOT IN ('Dislike', '" . dbesc(ACTIVITY_DISLIKE) . "') "; } + $filter_sql = ''; if($type !== 'filed' && $author) { $filter_sql = " AND (i.owner_xchan = '" . protect_sprintf(dbesc($author)) . "') "; } + $filed_filter_sql = ''; if($type === 'filed' && $file) { $filed_filter_sql = " AND (term.term = '" . protect_sprintf(dbesc($file)) . "') "; } + $dummy_order_sql = ''; + switch($type) { case 'direct': $type_sql = ' AND i.item_private = 2 AND i.item_thread_top = 1 '; diff --git a/Zotlabs/Widget/Notifications.php b/Zotlabs/Widget/Notifications.php index 225403535..f9cee6e71 100644 --- a/Zotlabs/Widget/Notifications.php +++ b/Zotlabs/Widget/Notifications.php @@ -21,16 +21,16 @@ class Notifications { 'icon' => 'grid-3x3', 'severity' => 'secondary', 'label' => t('Network'), - 'title' => t('New network activity notifications'), + 'title' => t('Unseen network activity'), 'viewall' => [ 'url' => 'network', 'label' => t('Network stream') ], 'markall' => [ - 'label' => t('Mark all notifications read') + 'label' => t('Mark all read') ], 'filter' => [ - 'posts_label' => t('Show new posts only'), + 'posts_label' => t('Conversation starters'), 'name_label' => t('Filter by name or address') ] ]; @@ -40,17 +40,17 @@ class Notifications { 'type' => 'home', 'icon' => 'house', 'severity' => 'danger', - 'label' => t('Home'), - 'title' => t('New home activity notifications'), + 'label' => t('Channel'), + 'title' => t('Unseen channel activity'), 'viewall' => [ 'url' => 'channel/' . $channel['channel_address'], - 'label' => t('Home stream') + 'label' => t('Channel stream') ], 'markall' => [ - 'label' => t('Mark all notifications seen') + 'label' => t('Mark all seen') ], 'filter' => [ - 'posts_label' => t('Show new posts only'), + 'posts_label' => t('Conversation starters'), 'name_label' => t('Filter by name or address') ] ]; @@ -59,17 +59,17 @@ class Notifications { 'type' => 'dm', 'icon' => 'envelope', 'severity' => 'danger', - 'label' => t('Direct Messages'), - 'title' => t('New direct messages notifications'), + 'label' => t('Private'), + 'title' => t('Unseen private activity'), 'viewall' => [ 'url' => 'network/?dm=1', - 'label' => t('Direct messages stream') + 'label' => t('Private stream') ], 'markall' => [ - 'label' => t('Mark all notifications read') + 'label' => t('Mark all read') ], 'filter' => [ - 'posts_label' => t('Show new posts only'), + 'posts_label' => t('Conversation starters'), 'name_label' => t('Filter by name or address') ] ]; @@ -79,13 +79,13 @@ class Notifications { 'icon' => 'calendar-date', 'severity' => 'secondary', 'label' => t('Events'), - 'title' => t('New events notifications'), + 'title' => t('Unseen events activity'), 'viewall' => [ 'url' => 'cdav/calendar', 'label' => t('View events') ], 'markall' => [ - 'label' => t('Mark all events seen') + 'label' => t('Mark all seen') ] ]; @@ -94,10 +94,10 @@ class Notifications { 'icon' => 'people', 'severity' => 'danger', 'label' => t('New Connections'), - 'title' => t('New connections notifications'), + 'title' => t('New connections'), 'viewall' => [ 'url' => 'connections', - 'label' => t('View all connections') + 'label' => t('View all') ] ]; @@ -106,21 +106,21 @@ class Notifications { 'icon' => 'folder', 'severity' => 'danger', 'label' => t('Files'), - 'title' => t('New files notifications'), + 'title' => t('Useen files activity'), ]; $notifications[] = [ 'type' => 'notify', 'icon' => 'exclamation-circle', 'severity' => 'danger', - 'label' => t('Notices'), - 'title' => t('Notices'), + 'label' => t('Notifications'), + 'title' => t('Unseen notifications'), 'viewall' => [ 'url' => 'notifications/system', - 'label' => t('View all notices') + 'label' => t('View all') ], 'markall' => [ - 'label' => t('Mark all notices seen') + 'label' => t('Mark all seen') ] ]; @@ -129,7 +129,7 @@ class Notifications { 'icon' => 'chat-quote', 'severity' => 'secondary', 'label' => t('Forums'), - 'title' => t('Forums'), + 'title' => t('Unseen forums activity'), 'filter' => [ 'name_label' => t('Filter by name or address') ] @@ -142,7 +142,7 @@ class Notifications { 'icon' => 'person-exclamation', 'severity' => 'danger', 'label' => t('Registrations'), - 'title' => t('New registrations notifications'), + 'title' => t('Unseen registration activity'), ]; } @@ -152,7 +152,7 @@ class Notifications { 'icon' => 'globe', 'severity' => 'secondary', 'label' => t('Public Stream'), - 'title' => t('New public stream notifications'), + 'title' => t('Unseen public stream activity'), 'viewall' => [ 'url' => 'pubstream', 'label' => t('Public stream') @@ -163,7 +163,7 @@ class Notifications { ], */ 'filter' => [ - 'posts_label' => t('Show new posts only'), + 'posts_label' => t('Conversation starters'), 'name_label' => t('Filter by name or address') ] ]; diff --git a/Zotlabs/Widget/Pinned.php b/Zotlabs/Widget/Pinned.php index be6b98434..7b95d3bc6 100644 --- a/Zotlabs/Widget/Pinned.php +++ b/Zotlabs/Widget/Pinned.php @@ -67,19 +67,19 @@ class Pinned { $attend = null; $canvote = false; - $conv_responses = []; - - if(in_array($item['obj_type'], ['Event', ACTIVITY_OBJ_EVENT])) { - $conv_responses['attendyes'] = [ 'title' => t('Attending','title') ]; - $conv_responses['attendno'] = [ 'title' => t('Not attending','title') ]; - $conv_responses['attendmaybe'] = [ 'title' => t('Might attend','title') ]; - if($commentable && $observer) { - $attend = [ t('I will attend'), t('I will not attend'), t('I might attend') ]; - $isevent = true; - } + $response_verbs[] = 'like'; + + if(feature_enabled(\App::$profile['profile_uid'],'dislike')) { + $response_verbs[] = 'dislike'; + } + + $response_verbs[] = 'announce'; + + if ($item['obj_type'] === 'Question') { + $response_verbs[] = 'answer'; } - $this->activity($item, $conv_responses); + $responses = get_responses($response_verbs, $item); $verified = (intval($item['item_verified']) ? t('Message signature validated') : ''); $forged = ((! intval($item['item_verified']) && $item['sig']) ? t('Message signature incorrect') : ''); @@ -110,6 +110,9 @@ class Pinned { 'text' => strip_tags($body['html']), 'id' => $item['id'], 'mids' => json_encode([ $midb64 ]), + 'mid' => $item['uuid'], + 'rawmid' => $item['mid'], + 'parent' => $item['parent'], 'isevent' => $isevent, 'attend' => $attend, 'conlabels' => [], @@ -151,7 +154,7 @@ class Pinned { 'hide' => (! $is_new && isset($observer['xchan_hash']) && $observer['xchan_hash'] != $owner['xchan_hash'] ? t("Don't show") : ''), // end toolbar buttons 'modal_dismiss' => t('Close'), - 'responses' => $conv_responses, + 'responses' => $responses, 'author_id' => (($author['xchan_addr']) ? $author['xchan_addr'] : $author['xchan_url']) ]; @@ -193,74 +196,14 @@ class Pinned { if(empty($mids_list)) return []; - $r = q("SELECT * FROM item WHERE uuid IN ( '%s' ) AND uid = %d AND id = parent AND item_private = 0 ORDER BY created DESC", + + $r = q("SELECT parent AS item_id FROM item WHERE uuid IN ( '%s' ) AND uid = %d AND id = parent AND item_private = 0", dbesc(implode(",", $mids_list)), intval($this->uid) ); - if($r) - return $r; - - return []; - } + return items_by_parent_ids($r, blog_mode: true); - /* - * @brief List activities on item - * - * @param array $item - * @param array $conv_responses - * @return array - * - */ - private function activity($item, &$conv_responses) { - - foreach(array_keys($conv_responses) as $verb) { - $verb_sql = ''; - - switch($verb) { - case 'like': - $verb_sql = " AND verb IN ('Like', '" . ACTIVITY_LIKE . "') "; - break; - case 'dislike': - $verb_sql = " AND verb IN ('Dislike', '" . ACTIVITY_DISLIKE . "') "; - break; - case 'attendyes': - $verb_sql = " AND verb IN ('Accept', '" . ACTIVITY_ATTEND . "') "; - break; - case 'attendno': - $verb_sql = " AND verb IN ('Reject', '" . ACTIVITY_ATTENDNO . "') "; - break; - case 'attendmaybe': - $verb_sql = " AND verb IN ('TentativeAccept', '" . ACTIVITY_ATTENDMAYBE . "') "; - break; - default: - break; - } - - $r = q("SELECT * FROM item WHERE parent = %d AND id <> parent $verb_sql AND item_deleted = 0", - intval($item['id']) - ); - if(! $r) { - unset($conv_responses[$verb]); - continue; - } - - $conv_responses[$verb]['count'] = count($r); - $conv_responses[$verb]['button'] = get_response_button_text($verb, $conv_responses[$verb]['count']); - - foreach($r as $rr) { - - $author = q("SELECT * FROM xchan WHERE xchan_hash = '%s' LIMIT 1", - dbesc($rr['author_xchan']) - ); - $name = (($author && $author[0]['xchan_name']) ? $author[0]['xchan_name'] : t('Unknown')); - $conv_responses[$verb]['list'][] = (($rr['author_xchan'] && $author && $author[0]['xchan_photo_s']) ? - '<a class="dropdown-item" href="' . chanlink_hash($rr['author_xchan']) . '">' . '<img class="menu-img-1" src="' . zid($author[0]['xchan_photo_s']) . '" alt="' . urlencode($name) . '" /> ' . $name . '</a>' : - '<a class="dropdown-item" href="#" class="disabled">' . $name . '</a>' - ); - } - } - - $conv_responses['count'] = count($conv_responses); } + } diff --git a/Zotlabs/Widget/Portfolio.php b/Zotlabs/Widget/Portfolio.php index bde1c7d6a..1c9dc162a 100644 --- a/Zotlabs/Widget/Portfolio.php +++ b/Zotlabs/Widget/Portfolio.php @@ -66,6 +66,10 @@ class Portfolio { //edit album name $album_edit = null; + + $ph = photo_factory(''); + $phototypes = $ph->supportedTypes(); + $photos = array(); if($r) { $twist = 'rotright'; diff --git a/Zotlabs/Zot6/Zot6Handler.php b/Zotlabs/Zot6/Zot6Handler.php index 053901205..1716ff84b 100644 --- a/Zotlabs/Zot6/Zot6Handler.php +++ b/Zotlabs/Zot6/Zot6Handler.php @@ -12,7 +12,7 @@ class Zot6Handler implements IHandler { } function Rekey($sender, $data, $hub) { - return self::reply_rekey_request($sender, $data, $hub); + return self::rekey_request($sender, $data, $hub); } function Refresh($sender, $recipients, $hub, $force) { @@ -34,11 +34,13 @@ class Zot6Handler implements IHandler { logger('notify received from ' . $hub['hubloc_url']); - $x = Libzot::fetch($data, $hub); - $ret['delivery_report'] = $x; + $x = Libzot::fetch($data); + if ($x) { + $ret['delivery_report'] = $x; + $ret['success'] = true; + } - $ret['success'] = true; return $ret; } |