diff options
author | Mario <mario@mariovavti.com> | 2024-03-22 08:37:29 +0000 |
---|---|---|
committer | Mario <mario@mariovavti.com> | 2024-03-22 08:37:29 +0000 |
commit | 1aeb05628b6a2a069c46980efbe628362c9e3e74 (patch) | |
tree | e9aed15d0cd74e0c23dcb05c7be8fe9541efdf36 /Zotlabs | |
parent | 5b7387459cf4de8f7354d81cb0392c4225714d94 (diff) | |
parent | b464fae3bf22585888c5f3def8eded76fd48ed16 (diff) | |
download | volse-hubzilla-9.0.tar.gz volse-hubzilla-9.0.tar.bz2 volse-hubzilla-9.0.zip |
Merge branch '9.0RC'9.0
Diffstat (limited to 'Zotlabs')
71 files changed, 1900 insertions, 2329 deletions
diff --git a/Zotlabs/Daemon/Cron.php b/Zotlabs/Daemon/Cron.php index caae0ce53..e0fa2d629 100644 --- a/Zotlabs/Daemon/Cron.php +++ b/Zotlabs/Daemon/Cron.php @@ -124,13 +124,13 @@ 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(get_config('system', 'active_expire_days', '30') . ' DAY') + db_quoteinterval(get_config('system', 'cache_expire_days', 7) . ' DAY') ); if ($r) { q("DELETE FROM photo WHERE photo_usage = %d AND expires < %s - INTERVAL %s", intval(PHOTO_CACHE), db_utcnow(), - db_quoteinterval(get_config('system', 'active_expire_days', '30') . ' DAY') + db_quoteinterval(get_config('system', 'cache_expire_days', 7) . ' DAY') ); foreach ($r as $rr) { $file = dbunescbin($rr['content']); diff --git a/Zotlabs/Daemon/Cron_daily.php b/Zotlabs/Daemon/Cron_daily.php index 850d38229..98379be1b 100644 --- a/Zotlabs/Daemon/Cron_daily.php +++ b/Zotlabs/Daemon/Cron_daily.php @@ -65,10 +65,10 @@ class Cron_daily { } } - // Clean up emdedded content cache + // Clean up cache q("DELETE FROM cache WHERE updated < %s - INTERVAL %s", db_utcnow(), - db_quoteinterval(get_config('system', 'active_expire_days', '30') . ' DAY') + db_quoteinterval(get_config('system', 'cache_expire_days', 7) . ' DAY') ); //update statistics in config diff --git a/Zotlabs/Daemon/Expire.php b/Zotlabs/Daemon/Expire.php index 84a606dc2..6ab67150f 100644 --- a/Zotlabs/Daemon/Expire.php +++ b/Zotlabs/Daemon/Expire.php @@ -43,8 +43,8 @@ class Expire { logger('expire: start with pid ' . $pid, LOGGER_DEBUG); - $site_expire = intval(get_config('system', 'default_expire_days')); - $commented_days = intval(get_config('system', 'active_expire_days')); + $site_expire = intval(get_config('system', 'default_expire_days', 30)); + $commented_days = intval(get_config('system', 'active_expire_days', 7)); logger('site_expire: ' . $site_expire); diff --git a/Zotlabs/Daemon/Fetchparents.php b/Zotlabs/Daemon/Fetchparents.php new file mode 100644 index 000000000..b00acdfbf --- /dev/null +++ b/Zotlabs/Daemon/Fetchparents.php @@ -0,0 +1,42 @@ +<?php + +namespace Zotlabs\Daemon; + +use Zotlabs\Lib\Activity; + +class Fetchparents { + + static public function run($argc, $argv) { + + logger('Fetchparents invoked: ' . print_r($argv, true)); + + if ($argc < 4) { + return; + } + + $channels = explode(',', $argv[1]); + if (!$channels) { + return; + } + + $observer_hash = $argv[2]; + if (!$observer_hash) { + return; + } + + $mid = $argv[3]; + if (!$mid) { + return; + } + + $force = $argv[4] ?? false; + + foreach ($channels as $channel_id) { + $channel = channelx_by_n($channel_id); + Activity::fetch_and_store_parents($channel, $observer_hash, $mid, null, $force); + } + + return; + + } +} diff --git a/Zotlabs/Daemon/Notifier.php b/Zotlabs/Daemon/Notifier.php index 4b74a7ba9..4e7ca3911 100644 --- a/Zotlabs/Daemon/Notifier.php +++ b/Zotlabs/Daemon/Notifier.php @@ -5,7 +5,6 @@ namespace Zotlabs\Daemon; use Zotlabs\Lib\Libzot; use Zotlabs\Lib\Activity; use Zotlabs\Lib\Queue; -use Zotlabs\Lib\LDSignatures; require_once('include/html2plain.php'); require_once('include/conversation.php'); @@ -271,14 +270,13 @@ 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']) && ($target_item['obj_type'] !== ACTIVITY_OBJ_FILE))) { + 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'], [ACTIVITY_FOLLOW, ACTIVITY_UNFOLLOW])) { + if (in_array($target_item['verb'], ['Follow', 'Ignore', ACTIVITY_FOLLOW, ACTIVITY_UNFOLLOW])) { logger('not fowarding follow/unfollow note activity'); return; } @@ -342,14 +340,7 @@ class Notifier { self::$encoded_item = json_decode($m, true); } else { - - self::$encoded_item = array_merge(['@context' => [ - ACTIVITYSTREAMS_JSONLD_REV, - 'https://w3id.org/security/v1', - z_root() . ZOT_APSCHEMA_REV - ]], Activity::encode_activity($target_item) - ); - self::$encoded_item['signature'] = LDSignatures::sign(self::$encoded_item, self::$channel); + self::$encoded_item = Activity::build_packet(Activity::encode_activity($target_item), self::$channel, false); } logger('target_item: ' . print_r($target_item, true), LOGGER_DEBUG); @@ -382,7 +373,8 @@ class Notifier { if (($relay_to_owner || $uplink) && ($cmd !== 'relay')) { logger('notifier: followup relay', LOGGER_DEBUG); - $sendto = (($uplink) ? $parent_item['source_xchan'] : (($parent_item['verb'] === ACTIVITY_SHARE) ? $parent_item['author_xchan'] : $parent_item['owner_xchan'])); + // If the Parent item is an Announce the real owner is the parent author + $sendto = (($uplink) ? $parent_item['source_xchan'] : $parent_item['owner_xchan']); self::$recipients = [$sendto]; self::$private = true; $upstream = true; diff --git a/Zotlabs/Daemon/Zotconvo.php b/Zotlabs/Daemon/Zotconvo.php index 16e7f113f..188956d26 100644 --- a/Zotlabs/Daemon/Zotconvo.php +++ b/Zotlabs/Daemon/Zotconvo.php @@ -10,21 +10,26 @@ class Zotconvo { logger('Zotconvo invoked: ' . print_r($argv, true)); - if ($argc != 3) { + if ($argc < 3) { return; } - $mid = $argv[2]; - if (!$mid) { + $channels = explode(',', $argv[1]); + if (!$channels) { return; } - $channel = channelx_by_n(intval($argv[1])); - if (!$channel) { + $mid = $argv[2]; + if (!$mid) { return; } - Libzot::fetch_conversation($channel, $mid); + $force = $argv[3] ?? false; + + foreach ($channels as $channel_id) { + $channel = channelx_by_n($channel_id); + Libzot::fetch_conversation($channel, $mid, $force); + } return; diff --git a/Zotlabs/Lib/ASCache.php b/Zotlabs/Lib/ASCache.php new file mode 100644 index 000000000..4904a1d8a --- /dev/null +++ b/Zotlabs/Lib/ASCache.php @@ -0,0 +1,33 @@ +<?php /** @file */ + +namespace Zotlabs\Lib; + + /** + * A wrapper for the cache api + */ + +class ASCache { + public static function isEnabled() { + return Config::Get('system', 'as_object_cache_enabled', true); + } + + public static function getAge() { + return Config::Get('system', 'as_object_cache_time', '10 MINUTE'); + } + + public static function Get($key) { + if (!self::isEnabled()) { + return; + } + + return Cache::get($key, self::getAge()); + } + + public static function Set($key, $value) { + if (!self::isEnabled()) { + return; + } + + Cache::set($key, $value); + } +} diff --git a/Zotlabs/Lib/Activity.php b/Zotlabs/Lib/Activity.php index 4fbc051bf..75d2ffbe9 100644 --- a/Zotlabs/Lib/Activity.php +++ b/Zotlabs/Lib/Activity.php @@ -27,19 +27,23 @@ class Activity { return $x['asld']; } - if ($x['type'] === ACTIVITY_OBJ_PERSON) { + if (in_array($x['type'], ['Person', ACTIVITY_OBJ_PERSON])) { return self::fetch_person($x); } - if ($x['type'] === ACTIVITY_OBJ_PROFILE) { + + if (in_array($x['type'], ['Profile', ACTIVITY_OBJ_PROFILE])) { return self::fetch_profile($x); } - if (in_array($x['type'], [ACTIVITY_OBJ_NOTE, ACTIVITY_OBJ_ARTICLE])) { + + if (in_array($x['type'], ['Note', 'Article', ACTIVITY_OBJ_NOTE, ACTIVITY_OBJ_ARTICLE])) { return self::fetch_item($x); } + if ($x['type'] === ACTIVITY_OBJ_THING) { return self::fetch_thing($x); } - if ($x['type'] === ACTIVITY_OBJ_EVENT) { + + if (in_array($x['type'], ['Event', ACTIVITY_OBJ_EVENT])) { return self::fetch_event($x); } @@ -59,13 +63,16 @@ class Activity { "select *, id as item_id from item where mid = '%s' and item_wall = 1 $item_normal $sql_extra", dbesc($url) ); + if ($j) { xchan_query($j, true); $items = fetch_post_tags($j); } + if ($items) { return self::encode_item(array_shift($items), true); } + return null; } @@ -150,6 +157,7 @@ class Activity { } else { logger('logger_stats_data cmd:Activity_fetch' . ' start:' . $start_timestamp . ' ' . 'end:' . microtime(true) . ' meta:' . $url . '#' . random_string(16)); + btlogger('activity fetch'); } return json_decode($x['body'], true); @@ -164,10 +172,6 @@ class Activity { } static function fetch_person($x) { - return self::fetch_profile($x); - } - - static function fetch_profile($x) { $r = q("select * from xchan where xchan_url = '%s' limit 1", dbesc($x['id']) ); @@ -181,7 +185,14 @@ class Activity { return []; return self::encode_person($r[0]); + } + + static function fetch_profile($x) { + if (isset($x['describes'])) { + return $x; + } + return []; } static function fetch_thing($x) { @@ -194,14 +205,23 @@ class Activity { if (!$r) return []; + $channel = channelx_by_n($r[0]['obj_channel']); + $x = [ - 'type' => 'Object', + 'type' => 'Page', 'id' => z_root() . '/thing/' . $r[0]['obj_obj'], - 'name' => $r[0]['obj_term'] + 'name' => $channel['channel_name'] . ' ' . $r[0]['obj_verb'] . ' ' . $r[0]['obj_term'], + 'content' => $r[0]['obj_url'], + 'url' => $r[0]['obj_url'] ]; - if ($r[0]['obj_image']) - $x['image'] = $r[0]['obj_image']; + if ($r[0]['obj_imgurl']) { + $x['content'] = '<a href="' . $r[0]['obj_url'] . '"><img src="' . $r[0]['obj_imgurl'] . '" alt="' . $r[0]['obj_term'] . '"></a>'; + $x['icon'] = [ + 'type' => 'Image', + 'url' => $r[0]['obj_imgurl'] + ]; + } return $x; @@ -220,7 +240,7 @@ class Activity { if ($r) { xchan_query($r, true); $r = fetch_post_tags($r); - if (in_array($r[0]['verb'], ['Create', 'Invite']) && $r[0]['obj_type'] === ACTIVITY_OBJ_EVENT) { + if (in_array($r[0]['verb'], ['Create', 'Invite']) && in_array($r[0]['obj_type'], ['Event', ACTIVITY_OBJ_EVENT])) { $r[0]['verb'] = 'Invite'; return self::encode_activity($r[0]); } @@ -360,7 +380,7 @@ class Activity { if ($items) { $x = []; foreach ($items as $i) { - $m = get_iconfig($i['id'], 'activitypub', 'rawmsg'); + $m = IConfig::Get($i['id'], 'activitypub', 'rawmsg'); if ($m) { if (is_string($m)) $t = json_decode($m, true); @@ -440,13 +460,7 @@ class Activity { $ret = []; - if ($i['verb'] === ACTIVITY_FRIEND) { - // Hubzilla 'make-friend' activity, no direct mapping from AS1 to AS2 - make it a note - $objtype = 'Note'; - } - else { - $objtype = self::activity_obj_mapper($i['obj_type']); - } + $objtype = self::activity_obj_mapper($i['obj_type']); if (intval($i['item_deleted'])) { $ret['type'] = 'Tombstone'; @@ -495,7 +509,7 @@ class Activity { // set this for descendants even if the current item is not private // because it may have been relayed from a private item. - $token = get_iconfig($i, 'ocap', 'relay'); + $token = IConfig::Get($i, 'ocap', 'relay'); if ($token && $has_images) { $matches_processed = []; for ($n = 0; $n < count($images); $n++) { @@ -513,7 +527,7 @@ class Activity { } if ($i['title']) - $ret['name'] = $i['title']; + $ret['name'] = unescape_tags($i['title']); $ret['published'] = datetime_convert('UTC', 'UTC', $i['created'], ATOM_TIME); if ($i['created'] !== $i['edited']) @@ -537,7 +551,7 @@ class Activity { } } - if (intval($i['item_wall']) && $i['mid'] === $i['parent_mid']) { + if (intval($i['item_wall'])) { $ret['commentPolicy'] = map_scope(PermissionLimits::Get($i['uid'], 'post_comments')); } @@ -560,11 +574,11 @@ class Activity { if ($i['mimetype'] === 'text/bbcode') { if ($i['title']) - $ret['name'] = bbcode($i['title'], ['cache' => true]); + $ret['name'] = unescape_tags($i['title']); if ($i['summary']) - $ret['summary'] = bbcode($i['summary'], ['cache' => true]); - $ret['content'] = bbcode($i['body'], ['cache' => true]); - $ret['source'] = ['content' => $i['body'], 'mediaType' => 'text/bbcode']; + $ret['summary'] = unescape_tags($i['summary']); + $ret['content'] = bbcode(unescape_tags($i['body']), ['cache' => true]); + $ret['source'] = ['content' => unescape_tags($i['body']), 'mediaType' => 'text/bbcode']; } $actor = self::encode_person($i['author'], false); @@ -594,9 +608,9 @@ class Activity { call_hooks('encode_item', $hookinfo); - return $hookinfo['encoded']; + } static function decode_taxonomy($item) { @@ -608,11 +622,12 @@ class Activity { if (!array_key_exists(0, $ptr)) { $ptr = [$ptr]; } + foreach ($ptr as $t) { if (is_array($t) && !array_key_exists('type', $t)) $t['type'] = 'Hashtag'; - if (is_array($t) && array_key_exists('href', $t) && array_key_exists('name', $t)) { + if (is_array($t) && (array_key_exists('href', $t) || array_key_exists('id', $t)) && 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'])]; @@ -626,6 +641,10 @@ class Activity { $ret[] = ['ttype' => TERM_BOOKMARK, 'url' => $t['href'], 'term' => escape_tags($t['name'])]; break; + case 'Emoji': + $ret[] = ['ttype' => TERM_EMOJI, 'url' => $t['id'], 'term' => escape_tags($t['name']), 'imgurl' => $t['icon']['url']]; + break; + default: break; } @@ -658,6 +677,10 @@ class Activity { $ret[] = ['type' => 'Bookmark', 'href' => $t['url'], 'name' => $t['term']]; break; + case TERM_EMOJI: + $ret[] = ['type' => 'Emoji', 'id' => $t['url'], 'name' => $t['term'], 'icon' => ['type' => 'Image', 'url' => $t['imgurl']]]; + break; + default: break; } @@ -761,7 +784,7 @@ class Activity { $entry['image'] = $att['image']; } if ($entry) { - $ret[] = $entry; + array_unshift($ret, $entry); } } } elseif (isset($item['attachment']) && is_string($item['attachment'])) { @@ -776,11 +799,6 @@ class Activity { $ret = []; $reply = false; - if ($i['verb'] === ACTIVITY_FRIEND) { - // Hubzilla 'make-friend' activity, no direct mapping from AS1 to AS2 - make it a note - $ret['obj'] = []; - } - $ret['type'] = self::activity_mapper($i['verb']); if ((isset($i['item_deleted']) && intval($i['item_deleted'])) && !$recurse) { @@ -927,7 +945,7 @@ class Activity { if (!is_array($i['obj'])) { $i['obj'] = json_decode($i['obj'], true); } - if ($i['obj']['type'] === ACTIVITY_OBJ_PHOTO) { + if (in_array($i['obj']['type'], ['Image', ACTIVITY_OBJ_PHOTO])) { $i['obj']['id'] = $i['mid']; } @@ -962,10 +980,12 @@ 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) { @@ -976,12 +996,14 @@ class Activity { $ret['to'] = [ACTIVITY_PUBLIC_INBOX]; } + $hookinfo = [ 'item' => $i, 'encoded' => $ret ]; call_hooks('encode_activity', $hookinfo); + return $hookinfo['encoded']; } @@ -1080,11 +1102,11 @@ class Activity { $ret['type'] = 'Person'; if ($c) { - if (get_pconfig($c['channel_id'], 'system', 'group_actor')) { + if (PConfig::Get($c['channel_id'], 'system', 'group_actor')) { $ret['type'] = 'Group'; } - $ret['manuallyApprovesFollowers'] = ((get_pconfig($c['channel_id'], 'system', 'autoperms')) ? false : true); + $ret['manuallyApprovesFollowers'] = ((PConfig::Get($c['channel_id'], 'system', 'autoperms')) ? false : true); } $ret['id'] = $id; @@ -1165,7 +1187,7 @@ class Activity { if (!is_array($item[$elm])) { $item[$elm] = json_decode($item[$elm], true); } - if ($item[$elm]['type'] === ACTIVITY_OBJ_PHOTO) { + if (in_array($item[$elm]['type'], ['Image', ACTIVITY_OBJ_PHOTO])) { $item[$elm]['id'] = $item['mid']; } @@ -1194,21 +1216,18 @@ class Activity { $acts = [ 'http://activitystrea.ms/schema/1.0/post' => 'Create', - 'http://activitystrea.ms/schema/1.0/share' => 'Announce', + //'http://activitystrea.ms/schema/1.0/share' => 'Announce', 'http://activitystrea.ms/schema/1.0/update' => 'Update', 'http://activitystrea.ms/schema/1.0/like' => 'Like', 'http://activitystrea.ms/schema/1.0/favorite' => 'Like', 'http://purl.org/zot/activity/dislike' => 'Dislike', - 'http://activitystrea.ms/schema/1.0/tag' => 'Add', + //'http://activitystrea.ms/schema/1.0/tag' => 'Add', 'http://activitystrea.ms/schema/1.0/follow' => 'Follow', 'http://activitystrea.ms/schema/1.0/unfollow' => 'Unfollow', 'http://activitystrea.ms/schema/1.0/stop-following' => 'Unfollow', 'http://purl.org/zot/activity/attendyes' => 'Accept', 'http://purl.org/zot/activity/attendno' => 'Reject', 'http://purl.org/zot/activity/attendmaybe' => 'TentativeAccept', - 'Invite' => 'Invite', - 'Delete' => 'Delete', - 'Undo' => 'Undo' ]; call_hooks('activity_mapper', $acts); @@ -1217,19 +1236,6 @@ class Activity { return $acts[$verb]; } - // Reactions will just map to normal activities - - if (strpos($verb, ACTIVITY_REACT) !== false) - return 'emojiReaction'; - if (strpos($verb, ACTIVITY_MOOD) !== false) - return 'Create'; - - if (strpos($verb, ACTIVITY_FRIEND) !== false) - return 'Create'; - - if (strpos($verb, ACTIVITY_POKE) !== false) - return 'Activity'; - // We should return false, however this will trigger an uncaught execption and crash // the delivery system if encountered by the JSON-LDSignature library @@ -1242,21 +1248,24 @@ class Activity { $acts = [ 'http://activitystrea.ms/schema/1.0/post' => 'Create', - 'http://activitystrea.ms/schema/1.0/share' => 'Announce', + // 'http://activitystrea.ms/schema/1.0/share' => 'Announce', 'http://activitystrea.ms/schema/1.0/update' => 'Update', 'http://activitystrea.ms/schema/1.0/like' => 'Like', 'http://activitystrea.ms/schema/1.0/favorite' => 'Like', 'http://purl.org/zot/activity/dislike' => 'Dislike', - 'http://activitystrea.ms/schema/1.0/tag' => 'Add', + // 'http://activitystrea.ms/schema/1.0/tag' => 'Add', 'http://activitystrea.ms/schema/1.0/follow' => 'Follow', 'http://activitystrea.ms/schema/1.0/unfollow' => 'Unfollow', 'http://activitystrea.ms/schema/1.0/stop-following' => 'Unfollow', 'http://purl.org/zot/activity/attendyes' => 'Accept', 'http://purl.org/zot/activity/attendno' => 'Reject', 'http://purl.org/zot/activity/attendmaybe' => 'TentativeAccept', + 'Announce' => 'Announce', 'Invite' => 'Invite', 'Delete' => 'Delete', - 'Undo' => 'Undo' + 'Undo' => 'Undo', + 'Add' => 'Add', + 'Remove' => 'Remove' ]; call_hooks('activity_decode_mapper', $acts); @@ -1325,13 +1334,7 @@ class Activity { 'http://purl.org/zot/activity/tagterm' => 'zot:Tag', 'http://purl.org/zot/activity/thing' => 'Object', 'http://purl.org/zot/activity/file' => 'zot:File', - 'http://purl.org/zot/activity/mood' => 'zot:Mood', - 'Invite' => 'Invite', - 'Question' => 'Question', - 'Audio' => 'Audio', - 'Video' => 'Video', - 'Delete' => 'Delete', - 'Undo' => 'Undo' + 'http://purl.org/zot/activity/mood' => 'zot:Mood' ]; call_hooks('activity_obj_mapper', $objs); @@ -1391,7 +1394,7 @@ class Activity { } } - $role = get_pconfig($channel['channel_id'], 'system', 'permissions_role', 'personal'); + $role = PConfig::Get($channel['channel_id'], 'system', 'permissions_role', 'personal'); $x = PermissionRoles::role_perms($role); $their_perms = Permissions::FilledPerms($x['perms_connect']); @@ -1412,7 +1415,7 @@ class Activity { // We've already approved them or followed them first // Send an Accept back to them - set_abconfig($channel['channel_id'], $person_obj['id'], 'pubcrawl', 'their_follow_id', $their_follow_id); + AbConfig::Set($channel['channel_id'], $person_obj['id'], 'pubcrawl', 'their_follow_id', $their_follow_id); Master::Summon(['Notifier', 'permission_accept', $contact['abook_id']]); return; @@ -1424,7 +1427,7 @@ class Activity { if(in_array($k, ['send_stream', 'post_wall'])) { continue; // Those will be set once we accept their follow request } - set_abconfig($channel['channel_id'], $contact['abook_xchan'], 'their_perms', $k, $v); + AbConfig::Set($channel['channel_id'], $contact['abook_xchan'], 'their_perms', $k, $v); } $abook_instance = $contact['abook_instance']; @@ -1458,7 +1461,7 @@ class Activity { // From here on out we assume a Follow activity to somebody we have no existing relationship with - set_abconfig($channel['channel_id'], $person_obj['id'], 'pubcrawl', 'their_follow_id', $their_follow_id); + AbConfig::Set($channel['channel_id'], $person_obj['id'], 'pubcrawl', 'their_follow_id', $their_follow_id); // The xchan should have been created by actor_store() above @@ -1476,7 +1479,7 @@ class Activity { $my_perms = $p['perms']; $automatic = $p['automatic']; - $closeness = get_pconfig($channel['channel_id'], 'system', 'new_abook_closeness', 80); + $closeness = PConfig::Get($channel['channel_id'], 'system', 'new_abook_closeness', 80); $r = abook_store_lowlevel( [ @@ -1495,11 +1498,11 @@ class Activity { if ($my_perms) foreach ($my_perms as $k => $v) - set_abconfig($channel['channel_id'], $ret['xchan_hash'], 'my_perms', $k, $v); + AbConfig::Set($channel['channel_id'], $ret['xchan_hash'], 'my_perms', $k, $v); if ($their_perms) foreach ($their_perms as $k => $v) - set_abconfig($channel['channel_id'], $ret['xchan_hash'], 'their_perms', $k, $v); + AbConfig::Set($channel['channel_id'], $ret['xchan_hash'], 'their_perms', $k, $v); if ($r) { logger("New ActivityPub follower for {$channel['channel_name']}"); @@ -1535,7 +1538,7 @@ class Activity { unset($clone['abook_account']); unset($clone['abook_channel']); - $abconfig = load_abconfig($channel['channel_id'], $clone['abook_xchan']); + $abconfig = AbConfig::Load($channel['channel_id'], $clone['abook_xchan']); if ($abconfig) $clone['abconfig'] = $abconfig; @@ -1576,7 +1579,7 @@ class Activity { ); if ($r) { // remove all permissions they provided - del_abconfig($channel['channel_id'], $r[0]['xchan_hash'], 'system', 'their_perms'); + AbConfig::Delete($channel['channel_id'], $r[0]['xchan_hash'], 'system', 'their_perms'); } } @@ -1584,8 +1587,7 @@ class Activity { } public static function drop($channel, $observer, $act) { - $r = q( - "select * from item where mid = '%s' and uid = %d limit 1", + $r = q("select * from item where mid = '%s' and uid = %d limit 1", dbesc((is_array($act->obj)) ? $act->obj['id'] : $act->obj), intval($channel['channel_id']) ); @@ -1605,7 +1607,6 @@ class Activity { if ($r[0]['item_wall']) { Master::Summon(['Notifier', 'drop', $r[0]['id']]); } - } @@ -1651,7 +1652,7 @@ class Activity { if ($ap_hubloc) { // we already have a stored record. Determine if it needs updating. if ($ap_hubloc['hubloc_updated'] < datetime_convert('UTC', 'UTC', ' now - 3 days') || $force) { - $person_obj = self::get_cached_actor($url); + $person_obj = self::get_actor($url, $force); } else { return; @@ -1674,7 +1675,7 @@ class Activity { $name = t('Unknown'); } - $webfinger_addr = ''; + $webfinger_addr = ((isset($person_obj['webfinger'])) ? str_replace('acct:', '', $person_obj['webfinger']) : ''); $hostname = ''; $baseurl = ''; $site_url = ''; @@ -1686,7 +1687,7 @@ class Activity { $site_url = $m['scheme'] . '://' . $m['host']; } - if (!empty($person_obj['preferredUsername']) && $hostname) { + if (!$webfinger_addr && !empty($person_obj['preferredUsername']) && $hostname) { $webfinger_addr = escape_tags($person_obj['preferredUsername']) . '@' . $hostname; } @@ -1740,6 +1741,7 @@ class Activity { $profile = $url; } + $pubkey = ''; if (array_key_exists('publicKey', $person_obj) && array_key_exists('publicKeyPem', $person_obj['publicKey'])) { if ($person_obj['id'] === $person_obj['publicKey']['owner']) { $pubkey = $person_obj['publicKey']['publicKeyPem']; @@ -1749,6 +1751,22 @@ class Activity { } } + $epubkey = ''; + if (isset($person_obj['assertionMethod'])) { + if (!isset($person_obj['assertionMethod'][0])) { + $person_obj['assertionMethod'] = [$person_obj['assertionMethod']]; + } + + foreach($person_obj['assertionMethod'] as $am) { + if ($person_obj['id'] === $am['controller'] && + $am['type'] === 'Multikey' && + str_starts_with($am['publicKeyMultibase'], 'z6Mk') + ) { + $epubkey = $am['publicKeyMultibase']; + } + } + } + $group_actor = ($person_obj['type'] === 'Group'); $r = q("select * from xchan join hubloc on xchan_hash = hubloc_hash where xchan_hash = '%s'", @@ -1769,10 +1787,11 @@ class Activity { ); // update existing xchan record - q("update xchan set xchan_name = '%s', xchan_pubkey = '%s', xchan_addr = '%s', xchan_network = 'activitypub', xchan_name_date = '%s', xchan_pubforum = %d where xchan_hash = '%s'", - dbesc(escape_tags($name)), - dbesc(escape_tags($pubkey)), - dbesc(escape_tags($webfinger_addr)), + q("update xchan set xchan_name = '%s', xchan_pubkey = '%s', xchan_epubkey = '%s', xchan_addr = '%s', xchan_network = 'activitypub', xchan_name_date = '%s', xchan_pubforum = %d where xchan_hash = '%s'", + dbesc($name), + dbesc($pubkey), + dbesc($epubkey), + dbesc($webfinger_addr), dbescdate(datetime_convert()), intval($group_actor), dbesc($url) @@ -1780,7 +1799,7 @@ class Activity { // update existing hubloc record q("update hubloc set hubloc_addr = '%s', hubloc_network = 'activitypub', hubloc_url = '%s', hubloc_host = '%s', hubloc_callback = '%s', hubloc_updated = '%s', hubloc_id_url = '%s' where hubloc_hash = '%s'", - dbesc(escape_tags($webfinger_addr)), + dbesc($webfinger_addr), dbesc($baseurl), dbesc($hostname), dbesc($inbox), @@ -1796,13 +1815,14 @@ class Activity { [ 'xchan_hash' => $url, 'xchan_guid' => $url, - 'xchan_pubkey' => escape_tags($pubkey), + 'xchan_pubkey' => $pubkey, + 'xchan_epubkey' => $epubkey, 'xchan_addr' => $webfinger_addr, 'xchan_url' => $profile, - 'xchan_name' => escape_tags($name), - 'xchan_photo_l' => z_root() . '/' . get_default_profile_photo(), - 'xchan_photo_m' => z_root() . '/' . get_default_profile_photo(80), - 'xchan_photo_s' => z_root() . '/' . get_default_profile_photo(48), + 'xchan_name' => $name, + 'xchan_photo_l' => z_root() . '/' . get_default_profile_photo(), + 'xchan_photo_m' => z_root() . '/' . get_default_profile_photo(80), + 'xchan_photo_s' => z_root() . '/' . get_default_profile_photo(48), 'xchan_name_date' => datetime_convert(), 'xchan_network' => 'activitypub', 'xchan_pubforum' => intval($group_actor) @@ -1847,31 +1867,6 @@ class Activity { } } - static function create_action($channel, $observer_hash, $act) { - - if (in_array($act->obj['type'], ['Note', 'Article', 'Video'])) { - self::create_note($channel, $observer_hash, $act); - } - - - } - - static function announce_action($channel, $observer_hash, $act) { - - if (in_array($act->type, ['Announce'])) { - self::announce_note($channel, $observer_hash, $act); - } - - } - - static function like_action($channel, $observer_hash, $act) { - - if (in_array($act->obj['type'], ['Note', 'Article', 'Video'])) { - self::like_note($channel, $observer_hash, $act); - } - - - } // sort function width decreasing static function vid_sort($a, $b) { @@ -1884,251 +1879,6 @@ class Activity { return (($a_width > $b_width) ? -1 : 1); } - static function create_note($channel, $observer_hash, $act) { - - $s = []; - $is_sys_channel = is_sys_channel($channel['channel_id']); - $parent = ((array_key_exists('inReplyTo', $act->obj)) ? urldecode($act->obj['inReplyTo']) : ''); - - if ($parent) { - - $r = q("select * from item where uid = %d and ( mid = '%s' or mid = '%s' ) limit 1", - intval($channel['channel_id']), - dbesc($parent), - dbesc(basename($parent)) - ); - - if (!$r) { - logger('parent not found.'); - return; - } - - if ($r[0]['owner_xchan'] === $channel['channel_hash']) { - if (!perm_is_allowed($channel['channel_id'], $observer_hash, 'send_stream') && !$is_sys_channel) { - logger('no comment permission.'); - return; - } - } - - $s['parent_mid'] = $r[0]['mid']; - $s['owner_xchan'] = $r[0]['owner_xchan']; - $s['author_xchan'] = $observer_hash; - - } - else { - if (!perm_is_allowed($channel['channel_id'], $observer_hash, 'send_stream') && !$is_sys_channel) { - logger('no send_stream permission'); - return; - } - $s['owner_xchan'] = $s['author_xchan'] = $observer_hash; - } - - if ($act->recips && (!in_array(ACTIVITY_PUBLIC_INBOX, $act->recips))) - $s['item_private'] = 1; - - - if (array_key_exists('directMessage', $act->obj) && intval($act->obj['directMessage'])) { - $s['item_private'] = 2; - } - - if (intval($s['item_private']) === 2) { - if (!perm_is_allowed($channel['channel_id'], $observer_hash, 'post_mail')) { - logger('no post_mail permission'); - return; - } - } - - $content = self::get_content($act->obj); - - if (!$content) { - logger('no content'); - return; - } - - $s['aid'] = $channel['channel_account_id']; - $s['uid'] = $channel['channel_id']; - - // Make sure we use the zot6 identity where applicable - - $s['author_xchan'] = self::find_best_identity($s['author_xchan']); - $s['owner_xchan'] = self::find_best_identity($s['owner_xchan']); - - if (!$s['author_xchan']) { - logger('No author: ' . print_r($act, true)); - } - - if (!$s['owner_xchan']) { - logger('No owner: ' . print_r($act, true)); - } - - if (!$s['author_xchan'] || !$s['owner_xchan']) - return; - - $s['mid'] = urldecode($act->obj['id']); - $s['uuid'] = $act->obj['diaspora:guid']; - $s['plink'] = urldecode($act->obj['id']); - - - if ($act->data['published']) { - $s['created'] = datetime_convert('UTC', 'UTC', $act->data['published']); - } - elseif ($act->obj['published']) { - $s['created'] = datetime_convert('UTC', 'UTC', $act->obj['published']); - } - if ($act->data['updated']) { - $s['edited'] = datetime_convert('UTC', 'UTC', $act->data['updated']); - } - elseif ($act->obj['updated']) { - $s['edited'] = datetime_convert('UTC', 'UTC', $act->obj['updated']); - } - if ($act->data['expires']) { - $s['expires'] = datetime_convert('UTC', 'UTC', $act->data['expires']); - } - elseif ($act->obj['expires']) { - $s['expires'] = datetime_convert('UTC', 'UTC', $act->obj['expires']); - } - - if (!$s['created']) - $s['created'] = datetime_convert(); - - if (!$s['edited']) - $s['edited'] = $s['created']; - - - if (!$s['parent_mid']) - $s['parent_mid'] = $s['mid']; - - - $s['title'] = self::bb_content($content, 'name'); - $s['summary'] = self::bb_content($content, 'summary'); - $s['body'] = self::bb_content($content, 'content'); - $s['verb'] = ACTIVITY_POST; - $s['obj_type'] = ACTIVITY_OBJ_NOTE; - - $generator = $act->get_property_obj('generator'); - if (!$generator) - $generator = $act->get_property_obj('generator', $act->obj); - - if ($generator && array_key_exists('type', $generator) - && in_array($generator['type'], ['Application', 'Service']) && array_key_exists('name', $generator)) { - $s['app'] = escape_tags($generator['name']); - } - - if ($channel['channel_system']) { - $incl = get_config('system','pubstream_incl'); - $excl = get_config('system','pubstream_excl'); - - if(($incl || $excl) && !MessageFilter::evaluate($s, $incl, $excl)) { - logger('post is filtered'); - return; - } - } - - $abook = q("select * from abook where (abook_xchan = '%s' OR abook_xchan = '%s') and abook_channel = %d ", - dbesc($s['author_xchan']), - dbesc($s['owner_xchan']), - intval($channel['channel_id']) - ); - - if ($abook) { - if (!post_is_importable($channel['channel_id'], $s, $abook)) { - logger('post is filtered'); - return; - } - } - - if ($act->obj['conversation']) { - set_iconfig($s, 'ostatus', 'conversation', $act->obj['conversation'], 1); - } - - $a = self::decode_taxonomy($act->obj); - if ($a) { - $s['term'] = $a; - } - - $a = self::decode_attachment($act->obj); - if ($a) { - $s['attach'] = $a; - } - - if ($act->obj['type'] === 'Note' && $s['attach']) { - $s['body'] .= self::bb_attach($s['attach'], $s['body']); - } - - // we will need a hook here to extract magnet links e.g. peertube - // right now just link to the largest mp4 we find that will fit in our - // standard content region - - if ($act->obj['type'] === 'Video') { - - $vtypes = [ - 'video/mp4', - 'video/ogg', - 'video/webm' - ]; - - $mps = []; - if (array_key_exists('url', $act->obj) && is_array($act->obj['url'])) { - foreach ($act->obj['url'] as $vurl) { - if (in_array($vurl['mimeType'], $vtypes)) { - if (!array_key_exists('width', $vurl)) { - $vurl['width'] = 0; - } - $mps[] = $vurl; - } - } - } - if ($mps) { - usort($mps, [__CLASS__, 'vid_sort']); - foreach ($mps as $m) { - if (intval($m['width']) < 500) { - $s['body'] .= "\n\n" . '[video]' . $m['href'] . '[/video]'; - break; - } - } - } - } - - set_iconfig($s, 'activitypub', 'recips', $act->raw_recips); - if ($parent) { - set_iconfig($s, 'activitypub', 'rawmsg', $act->raw, 1); - } - - $x = null; - - $r = q("select created, edited from item where mid = '%s' and uid = %d limit 1", - dbesc($s['mid']), - intval($s['uid']) - ); - if ($r) { - if ($s['edited'] > $r[0]['edited']) { - $x = item_store_update($s); - } - else { - return; - } - } - else { - $x = item_store($s); - } - - if (is_array($x) && $x['item_id']) { - if ($parent) { - if ($s['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']); - } - - } static function get_actor_bbmention($id) { @@ -2308,7 +2058,20 @@ class Activity { $content = self::get_content($act->obj); } - $s['mid'] = $act->objprop('id') ?: $act->obj; + $s['mid'] = $act->objprop('id'); + + if (!$s['mid'] && is_string($act->obj)) { + $s['mid'] = $act->obj; + } + + // pleroma fetched activities + if (!$s['mid'] && isset($act->obj['data']['id'])) { + $s['mid'] = $act->obj['data']['id']; + } + + if ($act->objprop('type') === 'Profile') { + $s['mid'] = $act->id; + } if (!$s['mid']) { return false; @@ -2410,15 +2173,30 @@ class Activity { } } + if ($act->type === 'Announce') { + $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, ['EmojiReaction', 'EmojiReact'])) { + if (in_array($act->type, ['EmojiReact'])) { // Pleroma reactions $t = trim(self::get_textfield($act->data, 'content')); - if (mb_strlen($t) === 1) { - $content['content'] = $t; + + $content['content'] = $t; + + // Unicode emojis + if (grapheme_strlen($t) === 1) { + $content['content'] = '<h1>' . $t . '</h1>'; + } + + $a = self::decode_taxonomy($act->data); + + if ($a) { + $s['term'] = $a; } } } @@ -2466,7 +2244,7 @@ class Activity { $s['body'] .= $quote_bbcode; } - $s['verb'] = self::activity_decode_mapper($act->type); + $s['verb'] = self::activity_mapper($act->type); // Mastodon does not provide update timestamps when updating poll tallies which means race conditions may occur here. if ($act->type === 'Update' && $act->objprop('type') === 'Question' && $s['edited'] === $s['created']) { @@ -2477,63 +2255,16 @@ class Activity { $s['item_deleted'] = 1; } - if (isset($act->obj['type'])) { - $s['obj_type'] = self::activity_obj_decode_mapper($act->obj['type']); - } - - if ($s['obj_type'] === ACTIVITY_OBJ_NOTE && $s['mid'] !== $s['parent_mid']) { - $s['obj_type'] = ACTIVITY_OBJ_COMMENT; + if ($act->objprop('type')) { + $s['obj_type'] = self::activity_obj_mapper($act->obj['type']); } $s['obj'] = $act->obj; + if (array_path_exists('actor/id', $s['obj'])) { $s['obj']['actor'] = $s['obj']['actor']['id']; } -/* - $eventptr = null; - - if ($act->obj['type'] === 'Invite' && array_path_exists('object/type', $act->obj) && $act->obj['object']['type'] === 'Event') { - $eventptr = $act->obj['object']; - $s['mid'] = $s['parent_mid'] = $act->obj['id']; - } - - if ($act->obj['type'] === 'Event') { - if ($act->type === 'Invite') { - $s['mid'] = $s['parent_mid'] = $act->id; - } - $eventptr = $act->obj; - } - - if ($eventptr) { - - $s['obj'] = []; - $s['obj']['asld'] = $eventptr; - $s['obj']['type'] = ACTIVITY_OBJ_EVENT; - $s['obj']['id'] = $eventptr['id']; - $s['obj']['title'] = html2plain($eventptr['name']); - - if (strpos($act->obj['startTime'], 'Z')) - $s['obj']['adjust'] = true; - else - $s['obj']['adjust'] = true; - - $s['obj']['dtstart'] = datetime_convert('UTC', 'UTC', $eventptr['startTime']); - if ($act->obj['endTime']) - $s['obj']['dtend'] = datetime_convert('UTC', 'UTC', $eventptr['endTime']); - else - $s['obj']['nofinish'] = true; - $s['obj']['description'] = html2bbcode($eventptr['content']); - - if (array_path_exists('location/content', $eventptr)) - $s['obj']['location'] = $eventptr['location']['content']; - - } - else { - $s['obj'] = $act->obj; - } -*/ - $generator = $act->get_property_obj('generator'); if ((!$generator) && (!$response_activity)) { $generator = $act->get_property_obj('generator', $act->obj); @@ -2546,15 +2277,9 @@ class Activity { if (is_array($act->obj) && !$response_activity) { $a = self::decode_taxonomy($act->obj); + if ($a) { $s['term'] = $a; - foreach ($a as $b) { - if ($b['ttype'] === TERM_EMOJI) { - $s['title'] = str_replace($b['term'], '[img=16x16]' . $b['url'] . '[/img]', $s['title']); - $s['summary'] = str_replace($b['term'], '[img=16x16]' . $b['url'] . '[/img]', $s['summary']); - $s['body'] = str_replace($b['term'], '[img=16x16]' . $b['url'] . '[/img]', $s['body']); - } - } } $a = self::decode_attachment($act->obj); @@ -2587,16 +2312,13 @@ class Activity { } if (!$response_activity) { - if ($act->type === 'Announce') { - $s['author_xchan'] = self::get_attributed_to_actor_url($act); - $s['mid'] = $act->objprop('id') ?: $act->obj; - // Do not force new thread if the announce is from a group actor - if ($act->actor['type'] !== 'Group') { - $s['parent_mid'] = $act->objprop('id') ?: $act->obj; - } + if ($act->objprop('type') === 'Profile') { + $s['parent_mid'] = $s['mid']; + $s['item_thread_top'] = 1; } + // we will need a hook here to extract magnet links e.g. peertube // right now just link to the largest mp4 we find that will fit in our // standard content region @@ -2669,7 +2391,7 @@ class Activity { if ($mps) { usort($mps,[ '\Zotlabs\Lib\Activity', 'vid_sort' ]); foreach ($mps as $m) { - if (intval($m['height']) < 500 && self::media_not_in_body($m['href'],$s['body'])) { + if (intval($m['height']) <= 720 && self::media_not_in_body($m['href'],$s['body'])) { $s['body'] = $tag . $m['href'] . '[/video]' . "\r\n" . $s['body']; break; } @@ -2742,7 +2464,6 @@ class Activity { } } - if ($act->objprop('type') === 'Page' && !$s['body']) { $ptr = null; @@ -2783,7 +2504,7 @@ class Activity { } } - if (in_array($act->objprop('type', ''), ['Note', 'Article', 'Page'])) { + if (in_array($act->objprop('type'), ['Note', 'Article', 'Page'])) { $ptr = null; if (array_key_exists('url', $act->obj)) { @@ -2849,55 +2570,28 @@ class Activity { } } - // old style: can be removed after most hubs are on 7.0.2 - elseif (array_key_exists('signed', $raw_arr) && is_array($act->obj) && isset($act->data['attachment']) && is_array($act->obj['attachment'])) { - foreach($act->obj['attachment'] as $a) { - if ( - isset($a['type']) && $a['type'] === 'PropertyValue' && - isset($a['name']) && $a['name'] === 'zot.activitypub.rawmsg' && - isset($a['value']) - ) { - $ap_rawmsg = $a['value']; - } - - if ( - isset($a['type']) && $a['type'] === 'PropertyValue' && - isset($a['name']) && $a['name'] === 'zot.diaspora.fields' && - isset($a['value']) - ) { - $diaspora_rawmsg = $a['value']; - } - } - } - - // catch the likes - if (!$ap_rawmsg && $response_activity) { - $ap_rawmsg = json_encode($act->data, JSON_UNESCAPED_SLASHES); - } - // end old style - if (!$ap_rawmsg && array_key_exists('signed', $raw_arr)) { // zap $ap_rawmsg = json_encode($act->data, JSON_UNESCAPED_SLASHES); } if ($ap_rawmsg) { - set_iconfig($s, 'activitypub', 'rawmsg', $ap_rawmsg, 1); + IConfig::Set($s, 'activitypub', 'rawmsg', $ap_rawmsg, 1); } elseif (!array_key_exists('signed', $raw_arr)) { - set_iconfig($s, 'activitypub', 'rawmsg', $act->raw, 1); + IConfig::Set($s, 'activitypub', 'rawmsg', $act->raw, 1); } if ($diaspora_rawmsg) { - set_iconfig($s, 'diaspora', 'fields', $diaspora_rawmsg, 1); + IConfig::Set($s, 'diaspora', 'fields', $diaspora_rawmsg, 1); } if ($act->raw_recips) { - set_iconfig($s, 'activitypub', 'recips', $act->raw_recips); + IConfig::Set($s, 'activitypub', 'recips', $act->raw_recips); } if ($act->objprop('type') === 'Event' && $act->objprop('timezone')) { - set_iconfig($s, 'event', 'timezone', $act->objprop('timezone'), true); + IConfig::Set($s, 'event', 'timezone', $act->objprop('timezone'), true); } $hookinfo = [ @@ -2929,6 +2623,17 @@ class Activity { $item['owner_xchan'] = $observer_hash; } + // An ugly and imperfect way to recognise a mastodon or friendica direct message + if ( + $item['item_private'] === 1 && + !isset($act->raw_recips['cc']) && + is_array($act->raw_recips['to']) && + in_array(channel_url($channel), $act->raw_recips['to']) && + !in_array($act->actor['followers'], $act->raw_recips['to']) + ) { + $item['item_private'] = 2; + } + $allowed = false; $permit_mentions = intval(PConfig::Get($channel['channel_id'], 'system','permit_all_mentions') && i_am_mentioned($channel, $item)); @@ -2939,15 +2644,30 @@ class Activity { intval($channel['channel_id']) ); - // TODO: if we do not have a parent stop here and move the fetch to background? + if (!$parent) { + if (perm_is_allowed($channel['channel_id'], $observer_hash, 'send_stream') || $is_sys_channel) { + if ($item['verb'] === 'Announce') { + $force = true; + } + + if ($fetch_parents) { + App::$cache['as_fetch_objects'][$item['mid']]['channels'][] = $channel['channel_id']; + App::$cache['as_fetch_objects'][$item['mid']]['force'] = intval($force); + return; + } + } + + logger('no parent'); + return; + } - if ($parent && $parent[0]['obj_type'] === 'Question') { - if ($item['obj_type'] === ACTIVITY_OBJ_COMMENT && $item['title'] && (!$item['body'])) { + if ($parent[0]['obj_type'] === 'Question') { + if (in_array($item['obj_type'], ['Note', ACTIVITY_OBJ_COMMENT]) && $item['title'] && (!$item['body'])) { $item['obj_type'] = 'Answer'; } } - if ($parent && $parent[0]['item_wall']) { + if ($parent[0]['item_wall']) { // set the owner to the owner of the parent $item['owner_xchan'] = $parent[0]['owner_xchan']; @@ -2979,7 +2699,7 @@ class Activity { }*/ if (!$allowed) { - if (get_pconfig($channel['channel_id'], 'system', 'moderate_unsolicited_comments') && $item['obj_type'] !== 'Answer') { + if (PConfig::Get($channel['channel_id'], 'system', 'moderate_unsolicited_comments') && $item['obj_type'] !== 'Answer') { $item['item_blocked'] = ITEM_MODERATED; $allowed = true; } @@ -3013,7 +2733,7 @@ class Activity { // The $item['item_fetched'] flag is set in fetch_and_store_parents(). // In this case we should check against author permissions because sender is not owner. - if (perm_is_allowed($channel['channel_id'], ((!empty($item['item_fetched'])) ? $item['author_xchan'] : $observer_hash), 'send_stream') || $is_sys_channel) { + if (perm_is_allowed($channel['channel_id'], ((empty($item['item_fetched'])) ? $observer_hash : $item['author_xchan']), 'send_stream') || $is_sys_channel) { $allowed = true; } @@ -3024,13 +2744,13 @@ class Activity { if (tgroup_check($channel['channel_id'], $item) && (!$is_child_node)) { // for forum deliveries, make sure we keep a copy of the signed original - set_iconfig($item, 'activitypub', 'rawmsg', $act->raw, 1); + IConfig::Set($item, 'activitypub', 'rawmsg', $act->raw, 1); $allowed = true; } if (intval($item['item_private']) === 2) { - if (!perm_is_allowed($channel['channel_id'], $observer_hash, 'post_mail')) { - $allowed = false; + if (perm_is_allowed($channel['channel_id'], $observer_hash, 'post_mail')) { + $allowed = true; } } @@ -3084,6 +2804,7 @@ class Activity { $item['author_xchan'] = self::find_best_identity($item['author_xchan']); $item['owner_xchan'] = self::find_best_identity($item['owner_xchan']); + $item['source_xchan'] = ((!empty($item['source_xchan'])) ? self::find_best_identity($item['source_xchan']) : ''); if (!$item['author_xchan']) { logger('No author: ' . print_r($act, true)); @@ -3097,8 +2818,8 @@ class Activity { return; if ($channel['channel_system']) { - $incl = get_config('system','pubstream_incl'); - $excl = get_config('system','pubstream_excl'); + $incl = Config::Get('system','pubstream_incl'); + $excl = Config::Get('system','pubstream_excl'); if(($incl || $excl) && !MessageFilter::evaluate($item, $incl, $excl)) { logger('post is filtered'); @@ -3106,9 +2827,10 @@ class Activity { } } - $abook = q("select * from abook where ( abook_xchan = '%s' OR abook_xchan = '%s') and abook_channel = %d ", + $abook = q("select * from abook where ( abook_xchan = '%s' OR abook_xchan = '%s' OR abook_xchan = '%s') and abook_channel = %d ", dbesc($item['author_xchan']), dbesc($item['owner_xchan']), + dbesc($item['source_xchan']), intval($channel['channel_id']) ); @@ -3120,43 +2842,21 @@ class Activity { } if (array_key_exists('conversation', $act->obj)) { - set_iconfig($item, 'ostatus', 'conversation', $act->obj['conversation'], 1); + IConfig::Set($item, 'ostatus', 'conversation', $act->obj['conversation'], 1); } // This isn't perfect but the best we can do for now. $item['comment_policy'] = ((isset($act->data['commentPolicy'])) ? $act->data['commentPolicy'] : 'authenticated'); - set_iconfig($item, 'activitypub', 'recips', $act->raw_recips); + IConfig::Set($item, 'activitypub', 'recips', $act->raw_recips); if (intval($act->sigok)) { $item['item_verified'] = 1; } if ($is_child_node) { - if (!$parent) { - if (!plugin_is_installed('pubcrawl')) { - return; - } - - $fetch = false; - - if (perm_is_allowed($channel['channel_id'], $observer_hash, 'send_stream') || $is_sys_channel) { - $fetch = (($fetch_parents) ? self::fetch_and_store_parents($channel, $observer_hash, $item, $force) : false); - } - - if ($fetch) { - $parent = q("select * from item where mid = '%s' and uid = %d", - dbesc($item['parent_mid']), - intval($item['uid']) - ); - } - } - - if (!$parent) { - logger('no parent'); - return; - } + $item['owner_xchan'] = $parent[0]['owner_xchan']; if ($parent[0]['parent_mid'] !== $item['parent_mid']) { $item['thr_parent'] = $item['parent_mid']; @@ -3197,30 +2897,28 @@ class Activity { } } - // An ugly and imperfect way to recognise a mastodon direct message - if ( - $item['item_private'] === 1 && - !isset($act->raw_recips['cc']) && - is_array($act->raw_recips['to']) && - in_array(channel_url($channel), $act->raw_recips['to']) && - !in_array($act->actor['followers'], $act->raw_recips['to']) - ) { - $item['item_private'] = 2; + if (isset($item['term']) && !PConfig::Get($channel['channel_id'], 'system', 'no_smilies')) { + foreach ($item['term'] as $t) { + if ($t['ttype'] === TERM_EMOJI) { + $class = 'emoji'; + $shortname = ':' . trim($t['term'], ':') . ':'; + if (is_solo_string($shortname, $item['body'])) { + $class .= ' single-emoji'; + } + + $item['body'] = str_replace($shortname, '[img class="' . $class . '" alt="' . $t['term'] . '" title="' . $t['term'] . '"]' . ($t['imgurl'] ?: $t['url']) . '[/img]', $item['body']); + } + } } // TODO: not implemented // self::rewrite_mentions($item); + $r = q("select id, created, edited from item where mid = '%s' and uid = %d limit 1", dbesc($item['mid']), intval($item['uid']) ); if ($r) { - - // If we already have the item, dismiss its announce - if ($act->type === 'Announce') { - return; - } - if ($item['edited'] > $r[0]['edited']) { $item['id'] = $r[0]['id']; $x = item_store_update($item); @@ -3281,18 +2979,59 @@ class Activity { } - static public function fetch_and_store_parents($channel, $observer_hash, $item, $force = false) { + /** + * @brief fetch a thread upwards by either providing a message id or an item/activity pair + * + * @param array $channel + * @param array $observer_hash + * @param array $item string|array + * @param object $act activitystreams object (optional) default null + * @param bool $force disregard permissions and force storage (optional) default false + * @return bool + */ + + static public function fetch_and_store_parents($channel, $observer_hash, $item, $act = null, $force = false) { logger('fetching parents'); + if (!$item) { + return false; + } + $p = []; + $announce_init = false; + $group_announce_init = false; + + if (is_object($act) && is_array($item)) { + $p[] = [$act, $item]; + $announce_init = ($item['verb'] === 'Announce'); + $group_announce_init = ($announce_init && $act->actor['type'] === 'Group'); + } + + if (is_string($item)) { + $mid = $item; + $item = [ + 'parent_mid' => $mid, + 'mid' => '' + ]; + } $current_item = $item; - while ($current_item['parent_mid'] !== $current_item['mid']) { - $n = self::fetch($current_item['parent_mid'], $channel); + $i = 0; - if (!$n) { - break; + while ($current_item['parent_mid'] !== $current_item['mid']) { + $cached = ASCache::Get($current_item['parent_mid']); + if ($cached) { + // logger('cached: ' . $current_item['parent_mid']); + $n = unserialise($cached); + } + else { + // logger('fetching: ' . $current_item['parent_mid']); + $n = self::fetch($current_item['parent_mid'], $channel); + if (!$n) { + break; + } + ASCache::Set($current_item['parent_mid'], serialise($n)); } $a = new ActivityStreams($n); @@ -3327,8 +3066,32 @@ class Activity { $item = $hookinfo['item']; if ($item) { + $item['item_fetched'] = true; + if ($announce_init) { + // Store the sender of the initial announce + $item['source_xchan'] = $observer_hash; + // WARNING: the presence of both source_xchan and non-zero item_uplink here will cause a delivery loop + $item['item_uplink'] = 0; + + if ($item['item_thread_top']) { + $item['verb'] = 'Announce'; + } + + if (!$group_announce_init) { + // Force a new thread if the announce init actor is not a group + $item['verb'] = 'Announce'; + $item['parent_mid'] = $item['thr_parent'] = $item['mid']; + $item['item_thread_top'] = 1; + } + + } + else { + $announce_init = ($i === 0 && $item['verb'] === 'Announce'); + $group_announce_init = ($announce_init && $a->actor['type'] === 'Group'); + } + if (intval($channel['channel_system']) && intval($item['item_private'])) { $p = []; break; @@ -3344,9 +3107,11 @@ class Activity { if ($item['parent_mid'] === $item['mid']) { break; } + } $current_item = $item; + $i++; } if ($p) { @@ -3357,407 +3122,9 @@ class Activity { } return true; } - return false; } - /* - static public function fetch_and_store_parents($channel, $item) { - - logger('fetching parents'); - - $p = []; - - $current_item = $item; - - while ($current_item['parent_mid'] !== $current_item['mid']) { - $n = self::fetch($current_item['parent_mid'], $channel); - if (!$n) { - break; - } - $a = new ActivityStreams($n); - - //logger($a->debug()); - - if (!$a->is_valid()) { - break; - } - - if (is_array($a->actor) && array_key_exists('id', $a->actor)) { - self::actor_store($a->actor); - } - - $replies = null; - if (isset($a->obj['replies']['first']['items'])) { - $replies = $a->obj['replies']['first']['items']; - // we already have this one - array_diff($replies, [$current_item['mid']]); - } - - $item = null; - - switch ($a->type) { - case 'Create': - case 'Update': - //case 'Like': - //case 'Dislike': - case 'Announce': - $item = self::decode_note($a); - break; - default: - break; - - } - - $hookinfo = [ - 'a' => $a, - 'item' => $item - ]; - - call_hooks('fetch_and_store', $hookinfo); - - $item = $hookinfo['item']; - - if ($item) { - - array_unshift($p, [$a, $item, $replies]); - - if ($item['parent_mid'] === $item['mid'] || count($p) > 20) { - break; - } - - } - $current_item = $item; - } - - if ($p) { - foreach ($p as $pv) { - self::store($channel, $pv[0]->actor['id'], $pv[0], $pv[1], false); - if ($pv[2]) - self::fetch_and_store_replies($channel, $pv[2]); - } - return true; - } - - return false; - } - - static public function fetch_and_store_replies($channel, $arr) { - - logger('fetching replies'); - logger(print_r($arr, true)); - - $p = []; - - foreach ($arr as $url) { - - $n = self::fetch($url, $channel); - if (!$n) { - break; - } - - $a = new ActivityStreams($n); - - if (!$a->is_valid()) { - break; - } - - $item = null; - - switch ($a->type) { - case 'Create': - case 'Update': - case 'Like': - case 'Dislike': - case 'Announce': - $item = self::decode_note($a); - break; - default: - break; - } - - $hookinfo = [ - 'a' => $a, - 'item' => $item - ]; - - call_hooks('fetch_and_store', $hookinfo); - - $item = $hookinfo['item']; - - if ($item) { - array_unshift($p, [$a, $item]); - } - - } - - if ($p) { - foreach ($p as $pv) { - self::store($channel, $pv[0]->actor['id'], $pv[0], $pv[1], false); - } - } - - } -*/ - -/* this is deprecated and not used anymore - static function announce_note($channel, $observer_hash, $act) { - - $s = []; - $is_sys_channel = is_sys_channel($channel['channel_id']); - - if (!perm_is_allowed($channel['channel_id'], $observer_hash, 'send_stream') && !$is_sys_channel) { - logger('no permission'); - return; - } - - $content = self::get_content($act->obj); - - if (!$content) { - logger('no content'); - return; - } - - $s['owner_xchan'] = $s['author_xchan'] = $observer_hash; - - $s['aid'] = $channel['channel_account_id']; - $s['uid'] = $channel['channel_id']; - $s['mid'] = urldecode($act->obj['id']); - $s['plink'] = urldecode($act->obj['id']); - - if (!$s['created']) - $s['created'] = datetime_convert(); - - if (!$s['edited']) - $s['edited'] = $s['created']; - - - $s['parent_mid'] = $s['mid']; - - $s['verb'] = ACTIVITY_POST; - $s['obj_type'] = ACTIVITY_OBJ_NOTE; - $s['app'] = t('ActivityPub'); - - if ($channel['channel_system']) { - if (!MessageFilter::evaluate($s, get_config('system', 'pubstream_incl'), get_config('system', 'pubstream_excl'))) { - logger('post is filtered'); - return; - } - } - - $abook = q("select * from abook where abook_xchan = '%s' and abook_channel = %d limit 1", - dbesc($observer_hash), - intval($channel['channel_id']) - ); - - if ($abook) { - if (!post_is_importable($s, $abook[0])) { - logger('post is filtered'); - return; - } - } - - if ($act->obj['conversation']) { - set_iconfig($s, 'ostatus', 'conversation', $act->obj['conversation'], 1); - } - - $a = self::decode_taxonomy($act->obj); - if ($a) { - $s['term'] = $a; - } - - $a = self::decode_attachment($act->obj); - if ($a) { - $s['attach'] = $a; - } - - $body = "[share author='" . urlencode($act->sharee['name']) . - "' profile='" . $act->sharee['url'] . - "' avatar='" . $act->sharee['photo_s'] . - "' link='" . ((is_array($act->obj['url'])) ? $act->obj['url']['href'] : $act->obj['url']) . - "' auth='" . ((is_matrix_url($act->obj['url'])) ? 'true' : 'false') . - "' posted='" . $act->obj['published'] . - "' message_id='" . $act->obj['id'] . - "']"; - - if ($content['name']) - $body .= self::bb_content($content, 'name') . "\r\n"; - - $body .= self::bb_content($content, 'content'); - - if ($act->obj['type'] === 'Note' && $s['attach']) { - $body .= self::bb_attach($s['attach'], $body); - } - - $body .= "[/share]"; - - $s['title'] = self::bb_content($content, 'name'); - $s['body'] = $body; - - if ($act->recips && (!in_array(ACTIVITY_PUBLIC_INBOX, $act->recips))) - $s['item_private'] = 1; - - set_iconfig($s, 'activitypub', 'recips', $act->raw_recips); - - $r = q("select created, edited from item where mid = '%s' and uid = %d limit 1", - dbesc($s['mid']), - intval($s['uid']) - ); - if ($r) { - if ($s['edited'] > $r[0]['edited']) { - $x = item_store_update($s); - } - else { - return; - } - } - else { - $x = item_store($s); - } - - if (is_array($x) && $x['item_id']) { - if ($s['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']); - } - - } -*/ - - static function like_note($channel, $observer_hash, $act) { - - $s = []; - - $parent = $act->obj['id']; - - if ($act->type === 'Like') - $s['verb'] = ACTIVITY_LIKE; - if ($act->type === 'Dislike') - $s['verb'] = ACTIVITY_DISLIKE; - - if (!$parent) - return; - - $r = q("select * from item where uid = %d and ( mid = '%s' or mid = '%s' ) limit 1", - intval($channel['channel_id']), - dbesc($parent), - dbesc(urldecode(basename($parent))) - ); - - if (!$r) { - logger('parent not found.'); - return; - } - - xchan_query($r); - $parent_item = $r[0]; - - if ($parent_item['owner_xchan'] === $channel['channel_hash']) { - if (!perm_is_allowed($channel['channel_id'], $observer_hash, 'post_comments')) { - logger('no comment permission.'); - return; - } - } - - if ($parent_item['mid'] === $parent_item['parent_mid']) { - $s['parent_mid'] = $parent_item['mid']; - } - else { - $s['thr_parent'] = $parent_item['mid']; - $s['parent_mid'] = $parent_item['parent_mid']; - } - - $s['owner_xchan'] = $parent_item['owner_xchan']; - $s['author_xchan'] = $observer_hash; - - $s['aid'] = $channel['channel_account_id']; - $s['uid'] = $channel['channel_id']; - $s['mid'] = $act->id; - - if (!$s['parent_mid']) - $s['parent_mid'] = $s['mid']; - - - $post_type = (($parent_item['resource_type'] === 'photo') ? t('photo') : t('post')); - - $links = [['rel' => 'alternate', 'type' => 'text/html', 'href' => $parent_item['plink']]]; - $objtype = (($parent_item['resource_type'] === 'photo') ? ACTIVITY_OBJ_PHOTO : ACTIVITY_OBJ_NOTE); - - $z = q("select * from xchan where xchan_hash = '%s' limit 1", - dbesc($parent_item['author_xchan']) - ); - if ($z) - $item_author = $z[0]; - - $object = json_encode([ - 'type' => $post_type, - 'id' => $parent_item['mid'], - 'parent' => (($parent_item['thr_parent']) ? $parent_item['thr_parent'] : $parent_item['parent_mid']), - 'link' => $links, - 'title' => $parent_item['title'], - 'content' => $parent_item['body'], - 'created' => $parent_item['created'], - 'edited' => $parent_item['edited'], - 'author' => [ - 'name' => $item_author['xchan_name'], - 'address' => $item_author['xchan_addr'], - 'guid' => $item_author['xchan_guid'], - 'guid_sig' => $item_author['xchan_guid_sig'], - 'link' => [ - ['rel' => 'alternate', 'type' => 'text/html', 'href' => $item_author['xchan_url']], - ['rel' => 'photo', 'type' => $item_author['xchan_photo_mimetype'], 'href' => $item_author['xchan_photo_m']]], - ], - ], JSON_UNESCAPED_SLASHES - ); - - if ($act->type === 'Like') - $bodyverb = t('%1$s likes %2$s\'s %3$s'); - if ($act->type === 'Dislike') - $bodyverb = t('%1$s doesn\'t like %2$s\'s %3$s'); - - $ulink = '[url=' . $item_author['xchan_url'] . ']' . $item_author['xchan_name'] . '[/url]'; - $alink = '[url=' . $parent_item['author']['xchan_url'] . ']' . $parent_item['author']['xchan_name'] . '[/url]'; - $plink = '[url=' . z_root() . '/display/' . urlencode($act->id) . ']' . $post_type . '[/url]'; - $s['body'] = sprintf($bodyverb, $ulink, $alink, $plink); - - $s['app'] = t('ActivityPub'); - - // set the route to that of the parent so downstream hubs won't reject it. - - $s['route'] = $parent_item['route']; - $s['item_private'] = $parent_item['item_private']; - $s['obj_type'] = $objtype; - $s['obj'] = $object; - - if ($act->obj['conversation']) { - set_iconfig($s, 'ostatus', 'conversation', $act->obj['conversation'], 1); - } - - if ($act->recips && (!in_array(ACTIVITY_PUBLIC_INBOX, $act->recips))) - $s['item_private'] = 1; - - set_iconfig($s, 'activitypub', 'recips', $act->raw_recips); - - $result = item_store($s); - - if ($result['success']) { - // if the message isn't already being relayed, notify others - if (intval($parent_item['item_origin'])) - Master::Summon(['Notifier', 'comment-import', $result['item_id']]); - sync_an_item($channel['channel_id'], $result['item_id']); - } - - return; - } public static function bb_attach($item) { @@ -3823,6 +3190,10 @@ class Activity { public static function media_not_in_body($s, $body) { + if (empty($body)) { + return true; + } + $s_alt = htmlspecialchars($s, ENT_QUOTES, 'UTF-8'); if ( @@ -3904,49 +3275,7 @@ class Activity { $event['nofinish'] = true; } } -/* - $eventptr = null; - - if ($act->obj['type'] === 'Invite' && array_path_exists('object/type', $act->obj) && $act->obj['object']['type'] === 'Event') { - $eventptr = $act->obj['object']; - $s['mid'] = $s['parent_mid'] = $act->obj['id']; - } - - if ($act->obj['type'] === 'Event') { - if ($act->type === 'Invite') { - $s['mid'] = $s['parent_mid'] = $act->id; - } - $eventptr = $act->obj; - } - - if ($eventptr) { - $s['obj'] = []; - $s['obj']['asld'] = $eventptr; - $s['obj']['type'] = ACTIVITY_OBJ_EVENT; - $s['obj']['id'] = $eventptr['id']; - $s['obj']['title'] = html2plain($eventptr['name']); - - if (strpos($act->obj['startTime'], 'Z')) - $s['obj']['adjust'] = true; - else - $s['obj']['adjust'] = true; - - $s['obj']['dtstart'] = datetime_convert('UTC', 'UTC', $eventptr['startTime']); - if ($act->obj['endTime']) - $s['obj']['dtend'] = datetime_convert('UTC', 'UTC', $eventptr['endTime']); - else - $s['obj']['nofinish'] = true; - $s['obj']['description'] = html2bbcode($eventptr['content']); - - if (array_path_exists('location/content', $eventptr)) - $s['obj']['location'] = $eventptr['location']['content']; - - } - else { - $s['obj'] = $act->obj; - } -*/ foreach (['name', 'summary', 'content'] as $a) { if (($x = self::get_textfield($act, $a)) !== false) { $content[$a] = $x; @@ -3975,17 +3304,18 @@ class Activity { return $content; } - static function get_textfield($act, $field) { + static function get_textfield($act, $field): null|string|array { + $content = null; - $content = false; - - if (array_key_exists($field, $act) && $act[$field]) + if (array_key_exists($field, $act) && $act[$field]) { $content = purify_html($act[$field]); + } elseif (array_key_exists($field . 'Map', $act) && $act[$field . 'Map']) { foreach ($act[$field . 'Map'] as $k => $v) { $content[escape_tags($k)] = purify_html($v); } } + return $content; } @@ -4051,11 +3381,11 @@ class Activity { return $hookdata['actor']; } - static function get_actor($actor_id) { + static function get_actor($actor_id, $force = false) { // remove fragment $actor_id = ((strpos($actor_id, '#')) ? substr($actor_id, 0, strpos($actor_id, '#')) : $actor_id); - $actor = self::get_cached_actor($actor_id); + $actor = ((!$force) ? self::get_cached_actor($actor_id) : null); if ($actor) { return $actor; @@ -4085,6 +3415,8 @@ class Activity { static function get_actor_hublocs($url, $options = 'all') { + $url = ((strpos($url, '#')) ? substr($url, 0, strpos($url, '#')) : $url); + switch ($options) { case 'activitypub': $hublocs = q("select * from hubloc left join xchan on hubloc_hash = xchan_hash where hubloc_hash = '%s' and hubloc_deleted = 0 order by hubloc_id desc", @@ -4205,4 +3537,125 @@ class Activity { } + public static function ap_context($contextType = null): array { + return ['@context' => [ + ACTIVITYSTREAMS_JSONLD_REV, + 'https://w3id.org/security/v1', + // 'https://www.w3.org/ns/did/v1', + // 'https://w3id.org/security/multikey/v1', + // 'https://w3id.org/security/data-integrity/v1', + 'https://purl.archive.org/socialweb/webfinger', + self::ap_schema($contextType) + ]]; + } + + public static function ap_schema($contextType = null): array { + // $contextType is reserved for future use so that the caller can specify + // a limited subset of the entire schema definition for particular activities. + + return [ + 'zot' => z_root() . '/apschema#', + 'schema' => 'http://schema.org#', + 'ostatus' => 'http://ostatus.org#', + 'diaspora' => 'https://diasporafoundation.org/ns/', + 'litepub' => 'http://litepub.social/ns#', + 'toot' => 'http://joinmastodon.org/ns#', + + 'commentPolicy' => 'zot:commentPolicy', + 'Bookmark' => 'zot:Bookmark', + 'Category' => 'zot:Category', + 'Emoji' => 'toot:Emoji', + + 'directMessage' => 'litepub:directMessage', + + 'PropertyValue' => 'schema:PropertyValue', + 'value' => 'schema:value', + 'uuid' => 'schema:identifier', + + 'conversation' => 'ostatus:conversation', + + 'guid' => 'diaspora:guid', + + 'manuallyApprovesFollowers' => 'as:manuallyApprovesFollowers', + 'Hashtag' => 'as:Hashtag' + + ]; + + } + + /** + * @brief Builds the activity packet and signs it if $channel is provided. + * + * @param array $obj + * @param array $channel (optional) default [] + * @param bool $json_encode (optional) default true + * @return string|array + */ + + public static function build_packet(array $obj, array $channel = [], bool $json_encode = true): string|array { + $arr = array_merge(Activity::ap_context(), $obj); + + if ($channel) { + $proof = (new JcsEddsa2022)->sign($arr, $channel); + $arr['proof'] = $proof; + + $signature = LDSignatures::sign($arr, $channel); + $arr['signature'] = $signature; + } + + if ($json_encode) { + return json_encode($arr, JSON_UNESCAPED_SLASHES); + } + + return $arr; + } + + /** + * @brief Prepares the arguments and inititates the Fetchparents or Zotconvo daemon. + * @param string $observer + * + */ + + public static function init_background_fetch(string $observer_hash = '') { + if (isset(App::$cache['zot_fetch_objects'])) { + $channels_str = ''; + + foreach (App::$cache['zot_fetch_objects'] as $mid => $info) { + $force = $info['force']; + + foreach ($info['channels'] as $c) { + if ($channels_str) { + $channels_str .= ','; + } + $channels_str .= $c; + } + + Master::Summon(['Zotconvo', $channels_str, $mid, $force]); + } + } + + if (isset(App::$cache['as_fetch_objects'])) { + if (!$observer_hash) { + logger('Attempt to initiate Fetchparents daemon without observer'); + return; + } + + $channels_str = ''; + + foreach (App::$cache['as_fetch_objects'] as $mid => $info) { + $force = $info['force']; + + foreach ($info['channels'] as $c) { + if ($channels_str) { + $channels_str .= ','; + } + $channels_str .= $c; + } + + Master::Summon(['Fetchparents', $channels_str, $observer_hash, $mid, $force]); + } + } + } + + } diff --git a/Zotlabs/Lib/ActivityStreams.php b/Zotlabs/Lib/ActivityStreams.php index 4c3e3d8f8..9f028bb46 100644 --- a/Zotlabs/Lib/ActivityStreams.php +++ b/Zotlabs/Lib/ActivityStreams.php @@ -24,10 +24,11 @@ class ActivityStreams { public $origin = null; public $owner = null; public $signer = null; - public $ldsig = null; + public $sig = null; public $sigok = false; public $recips = null; public $raw_recips = null; + public $saved_recips = null; /** * @brief Constructor for ActivityStreams. @@ -88,7 +89,16 @@ class ActivityStreams { // Attempt to assemble an Activity from what we were given. if ($this->is_valid()) { - $this->id = $this->get_property_obj('id'); + $this->id = $this->get_property_obj('id'); + + if (!$this->id) { + logger('Data with mmissing id: ' . print_r($this->data, true)); + return; + } + + // cache for future use + ASCache::Set($this->id, 'json:' . $this->raw); + $this->type = $this->get_primary_type(); $this->actor = $this->get_actor('actor', '', ''); $this->obj = $this->get_compound_property('object'); @@ -96,11 +106,19 @@ class ActivityStreams { $this->origin = $this->get_compound_property('origin'); $this->recips = $this->collect_recips(); - $this->ldsig = $this->get_compound_property('signature'); - if ($this->ldsig) { - $this->signer = $this->get_actor('creator', $this->ldsig); - if ($this->signer && is_array($this->signer) && array_key_exists('publicKey', $this->signer) && is_array($this->signer['publicKey']) && $this->signer['publicKey']['publicKeyPem']) { - $this->sigok = LDSignatures::verify($this->data, $this->signer['publicKey']['publicKeyPem']); + $this->sig = $this->get_compound_property('proof'); + if ($this->sig) { + $this->checkEddsaSignature(); // will set signer and sigok if everything works out + } + + // Try LDSignatures if edsig failed + if (!$this->sigok) { + $this->sig = $this->get_compound_property('signature'); + if ($this->sig) { + $this->signer = $this->get_actor('creator', $this->sig); + if ($this->signer && is_array($this->signer) && array_key_exists('publicKey', $this->signer) && is_array($this->signer['publicKey']) && $this->signer['publicKey']['publicKeyPem']) { + $this->sigok = LDSignatures::verify($this->data, $this->signer['publicKey']['publicKeyPem']); + } } } @@ -112,24 +130,31 @@ class ActivityStreams { } } - // fetch recursive or embedded activities + // Fetch recursive or embedded activities if ($this->obj && is_array($this->obj) && array_key_exists('object', $this->obj)) { $this->obj['object'] = $this->get_compound_property('object', $this->obj); } - if ($this->obj && is_array($this->obj) && isset($this->obj['actor'])) + // Enumerate and store actors in referenced objects + + if ($this->obj && is_array($this->obj) && isset($this->obj['actor'])) { $this->obj['actor'] = $this->get_actor('actor', $this->obj); - if ($this->tgt && is_array($this->tgt) && isset($this->tgt['actor'])) + } + + if ($this->tgt && is_array($this->tgt) && isset($this->tgt['actor'])) { $this->tgt['actor'] = $this->get_actor('actor', $this->tgt); + } + + // Determine if this is a followup or response activity $this->parent_id = $this->get_property_obj('inReplyTo'); - if (!$this->parent_id && is_array($this->obj) && isset($this->obj['inReplyTo'])) { - $this->parent_id = $this->obj['inReplyTo']; + if (!$this->parent_id && isset($this->obj['inReplyTo'])) { + $this->parent_id = ((is_array($this->obj['inReplyTo'])) ? $this->obj['inReplyTo']['id'] : $this->obj['inReplyTo']); } - if (!$this->parent_id && is_array($this->obj) && isset($this->obj['id'])) { + if (!$this->parent_id && isset($this->obj['id'])) { $this->parent_id = $this->obj['id']; } @@ -318,9 +343,10 @@ class ActivityStreams { if ($x === null && strpos($url, '/channel/')) { // look for other nomadic channels which might be alive $zf = Zotfinger::exec($url, $channel); - - $url = $zf['signature']['signer']; - $x = Activity::fetch($url, $channel); + if ($zf) { + $url = $zf['signature']['signer']; + $x = Activity::fetch($url, $channel); + } } } @@ -335,7 +361,7 @@ class ActivityStreams { if (!$s) { return false; } - return (in_array($s, ['Like', 'Dislike', 'Flag', 'Block', 'Accept', 'Reject', 'TentativeAccept', 'TentativeReject', 'emojiReaction', 'EmojiReaction', 'EmojiReact'])); + return (in_array($s, ['Announce', 'Like', 'Dislike', 'Flag', 'Block', 'Accept', 'Reject', 'TentativeAccept', 'TentativeReject', 'emojiReaction', 'EmojiReaction', 'EmojiReact'])); } /** @@ -384,12 +410,24 @@ class ActivityStreams { $x = $this->get_property_obj($property, $base, $namespace); if ($this->is_url($x)) { - $y = $this->fetch_property($x); + $cached = ASCache::Get($x); + if ($cached) { + // logger('AS cached: ' . $x); + $y = unserialise($cached); + } + else { + // logger('AS fetching: ' . $x); + $y = $this->fetch_property($x); + if ($y) { + ASCache::Set($x, serialise($y)); + } + } if (is_array($y)) { $x = $y; } } + // verify and unpack JSalmon signature if present if (is_array($x) && array_key_exists('signed', $x)) { @@ -489,4 +527,58 @@ class ActivityStreams { } + public function checkEddsaSignature() { + $signer = $this->get_property_obj('verificationMethod', $this->sig); + + $parseUrl = parse_url($signer); + + if (isset($parseUrl['fragment'])) { + if (str_starts_with($parseUrl['fragment'], 'z6Mk')) { + $publicKey = $parseUrl['fragment']; + } + unset($parseUrl['fragment']); + } + + if (isset($parseUrl['query'])) { + unset($parseUrl['query']); + } + + $url = unparse_url($parseUrl); + + $hublocs = Activity::get_actor_hublocs($url); + + $hasStoredKey = false; + if ($hublocs) { + foreach ($hublocs as $hubloc) { + if ($publicKey && $hubloc['xchan_epubkey'] === $publicKey) { + $hasStoredKey = true; + break; + } + } + } + + if (!$hasStoredKey) { + $this->signer = Activity::get_actor($url); + + if (isset($this->signer['assertionMethod'])) { + if (!isset($this->signer['assertionMethod'][0])) { + $this->signer['assertionMethod'] = [$this->signer['assertionMethod']]; + } + + foreach($this->signer['assertionMethod'] as $am) { + if ($url === $am['controller'] && + $am['type'] === 'Multikey' && + str_starts_with($am['publicKeyMultibase'], 'z6Mk') + ) { + $publicKey = $am['publicKeyMultibase']; + } + } + } + } + + if ($publicKey) { + $this->sigok = (new JcsEddsa2022)->verify($this->data, $publicKey); + } + } + } diff --git a/Zotlabs/Lib/Apps.php b/Zotlabs/Lib/Apps.php index 00e65479e..1c05d69b1 100644 --- a/Zotlabs/Lib/Apps.php +++ b/Zotlabs/Lib/Apps.php @@ -352,8 +352,6 @@ class Apps { 'Directory' => t('Directory'), 'Help' => t('Help'), 'Mail' => t('Mail'), - 'Mood' => t('Mood'), - 'Poke' => t('Poke'), 'Chat' => t('Chat'), 'Search' => t('Search'), 'Probe' => t('Probe'), diff --git a/Zotlabs/Lib/Cache.php b/Zotlabs/Lib/Cache.php index 60bf64611..f3f520496 100644 --- a/Zotlabs/Lib/Cache.php +++ b/Zotlabs/Lib/Cache.php @@ -17,8 +17,8 @@ class Cache { */ public static function get($key, $age = '') { - - $hash = hash('whirlpool',$key); +// $hash = hash('whirlpool',$key); + $hash = uuid_from_url($key); $r = q("SELECT v FROM cache WHERE k = '%s' AND updated > %s - INTERVAL %s LIMIT 1", dbesc($hash), @@ -32,23 +32,25 @@ class Cache { } public static function set($key,$value) { +// $hash = hash('whirlpool',$key); + $hash = uuid_from_url($key); - $hash = hash('whirlpool',$key); - - $r = q("SELECT * FROM cache WHERE k = '%s' limit 1", + $r = q("SELECT * FROM cache WHERE k = '%s' LIMIT 1", dbesc($hash) ); if($r) { q("UPDATE cache SET v = '%s', updated = '%s' WHERE k = '%s'", dbesc($value), dbesc(datetime_convert()), - dbesc($hash)); + dbesc($hash) + ); } else { q("INSERT INTO cache (k, v, updated) VALUES ('%s', '%s', '%s')", dbesc($hash), dbesc($value), - dbesc(datetime_convert())); + dbesc(datetime_convert()) + ); } } } diff --git a/Zotlabs/Lib/Config.php b/Zotlabs/Lib/Config.php index 933f4bff3..95df8ed6f 100644 --- a/Zotlabs/Lib/Config.php +++ b/Zotlabs/Lib/Config.php @@ -143,6 +143,9 @@ class Config { return $value; } } + else { + return $value; + } } return $default; diff --git a/Zotlabs/Lib/DReport.php b/Zotlabs/Lib/DReport.php index e22ed65be..71e39a9d7 100644 --- a/Zotlabs/Lib/DReport.php +++ b/Zotlabs/Lib/DReport.php @@ -6,23 +6,26 @@ class DReport { private $location; private $sender; private $recipient; + private $name; private $message_id; + private $message_uuid; private $status; private $date; - function __construct($location,$sender,$recipient,$message_id,$status = 'deliver') { - $this->location = $location; - $this->sender = $sender; - $this->recipient = $recipient; - $this->name = EMPTY_STR; - $this->message_id = $message_id; - $this->status = $status; - $this->date = datetime_convert(); + function __construct($location, $sender, $recipient, $message_id, $message_uuid = '', $status = 'deliver') { + $this->location = $location; + $this->sender = $sender; + $this->recipient = $recipient; + $this->name = EMPTY_STR; + $this->message_id = $message_id; + $this->message_uuid = $message_uuid; + $this->status = $status; + $this->date = datetime_convert(); } function update($status) { - $this->status = $status; - $this->date = datetime_convert(); + $this->status = $status; + $this->date = datetime_convert(); } function set_name($name) { @@ -35,24 +38,26 @@ class DReport { function set($arr) { - $this->location = $arr['location']; - $this->sender = $arr['sender']; - $this->recipient = $arr['recipient']; - $this->name = $arr['name']; - $this->message_id = $arr['message_id']; - $this->status = $arr['status']; - $this->date = $arr['date']; + $this->location = $arr['location']; + $this->sender = $arr['sender']; + $this->recipient = $arr['recipient']; + $this->name = $arr['name']; + $this->message_id = $arr['message_id']; + $this->message_uuid = $arr['message_uuid'] ?? ''; + $this->status = $arr['status']; + $this->date = $arr['date']; } function get() { return array( - 'location' => $this->location, - 'sender' => $this->sender, - 'recipient' => $this->recipient, - 'name' => $this->name, - 'message_id' => $this->message_id, - 'status' => $this->status, - 'date' => $this->date + 'location' => $this->location, + 'sender' => $this->sender, + 'recipient' => $this->recipient, + 'name' => $this->name, + 'message_id' => $this->message_id, + 'message_uuid' => $this->message_uuid, + 'status' => $this->status, + 'date' => $this->date ); } diff --git a/Zotlabs/Lib/Enotify.php b/Zotlabs/Lib/Enotify.php index c3f96e103..48a255e95 100644 --- a/Zotlabs/Lib/Enotify.php +++ b/Zotlabs/Lib/Enotify.php @@ -149,7 +149,7 @@ class Enotify { if(array_key_exists('item',$params)) { - if(in_array($params['item']['verb'], [ACTIVITY_LIKE, ACTIVITY_DISLIKE])) { + if(in_array($params['item']['verb'], ['Like', 'Dislike', ACTIVITY_LIKE, ACTIVITY_DISLIKE, ACTIVITY_SHARE])) { if(! $always_show_in_notices || !($vnotify & VNOTIFY_LIKE)) { logger('notification: not a visible activity. Ignoring.'); @@ -157,12 +157,15 @@ class Enotify { return; } - if(activity_match($params['verb'], ACTIVITY_LIKE)) + if(activity_match($params['verb'], ['Like', ACTIVITY_LIKE])) $action = (($moderated) ? t('requested to like') : t('liked')); - if(activity_match($params['verb'], ACTIVITY_DISLIKE)) + 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($params['item']['obj_type'] === 'Answer') @@ -259,7 +262,7 @@ class Enotify { $itemlink = $params['link']; - if (array_key_exists('item',$params) && (activity_match($params['item']['verb'], ACTIVITY_LIKE) || activity_match($params['item']['verb'], ACTIVITY_DISLIKE))) { + if (array_key_exists('item',$params) && (activity_match($params['item']['verb'], ['Like', 'Dislike', ACTIVITY_LIKE, ACTIVITY_DISLIKE]))) { if(! $always_show_in_notices || !($vnotify & VNOTIFY_LIKE) || !feature_enabled($recip['channel_id'], 'dislike')) { logger('notification: not a visible activity. Ignoring.'); pop_lang(); @@ -310,10 +313,10 @@ class Enotify { //$verb = ((activity_match($params['item']['verb'], ACTIVITY_DISLIKE)) ? t('disliked') : t('liked')); $moderated = (($params['item']['item_blocked'] == ITEM_MODERATED) ? true : false); - if(activity_match($params['item']['verb'], ACTIVITY_LIKE)) + if(activity_match($params['item']['verb'], ['Like', ACTIVITY_LIKE])) $verb = (($moderated) ? t('requested to like') : t('liked')); - if(activity_match($params['item']['verb'], ACTIVITY_DISLIKE)) + if(activity_match($params['item']['verb'], ['Dislike', ACTIVITY_DISLIKE])) $verb = (($moderated) ? t('requested to dislike') : t('disliked')); // "your post" @@ -483,6 +486,8 @@ class Enotify { require_once('include/html2bbcode.php'); +/* + do { $dups = false; $hash = random_string(); @@ -491,10 +496,12 @@ class Enotify { if ($r) $dups = true; } while ($dups === true); +*/ + $datarray = []; - $datarray['hash'] = $hash; + $datarray['hash'] = $params['item']['uuid'] ?? new_uuid(); $datarray['sender_hash'] = $sender['xchan_hash']; $datarray['xname'] = $sender['xchan_name']; $datarray['url'] = $sender['xchan_url']; @@ -505,7 +512,7 @@ class Enotify { $datarray['link'] = $itemlink; $datarray['parent'] = $parent_mid; $datarray['parent_item'] = $parent_item; - $datarray['ntype'] = $params['type'] ?? ''; + $datarray['ntype'] = $params['type'] ?? 0; $datarray['verb'] = $params['verb'] ?? ''; $datarray['otype'] = $params['otype'] ?? ''; $datarray['abort'] = false; @@ -553,8 +560,9 @@ class Enotify { dbesc($datarray['otype']) ); - $r = q("select id from notify where hash = '%s' and uid = %d limit 1", - dbesc($hash), + $r = q("select id from notify where hash = '%s' and ntype = %d and uid = %d limit 1", + dbesc($datarray['hash']), + intval($datarray['ntype']), intval($recip['channel_id']) ); if ($r) { @@ -835,18 +843,6 @@ class Enotify { : (($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]')) ); - if($item['verb'] === ACTIVITY_SHARE && empty($item['owner']['xchan_pubforum'])) { - $itemem_text = sprintf( t('repeated %s\'s post'), '[bdi]' . $item['author']['xchan_name'] . '[/bdi]'); - } - - if($item['verb'] === ACTIVITY_LIKE) { - $itemem_text = sprintf( t('liked %s\'s post'), '[bdi]' . $item['author']['xchan_name'] . '[/bdi]'); - } - - if($item['verb'] === ACTIVITY_DISLIKE) { - $itemem_text = sprintf( t('disliked %s\'s post'), '[bdi]' . $item['author']['xchan_name'] . '[/bdi]'); - } - if(in_array($item['obj_type'], ['Document', 'Video', 'Audio', 'Image'])) { $itemem_text = t('shared a file with you'); } @@ -867,7 +863,6 @@ class Enotify { // convert this logic into a json array just like the system notifications - $who = (($item['verb'] === ACTIVITY_SHARE && empty($item['owner']['xchan_pubforum'])) ? 'owner' : 'author'); $body = html2plain(bbcode($item['body'], ['drop_media' => true, 'tryoembed' => false]), 75, true); if ($body) { $body = htmlentities($body, ENT_QUOTES, 'UTF-8', false); @@ -875,19 +870,20 @@ class Enotify { $x = array( 'notify_link' => $item['llink'], - 'name' => $item[$who]['xchan_name'], - 'addr' => $item[$who]['xchan_addr'] ? $item[$who]['xchan_addr'] : $item[$who]['xchan_url'], - 'url' => $item[$who]['xchan_url'], - 'photo' => $item[$who]['xchan_photo_s'], + 'name' => $item['author']['xchan_name'], + 'addr' => $item['author']['xchan_addr'] ? $item['author']['xchan_addr'] : $item['author']['xchan_url'], + 'url' => $item['author']['xchan_url'], + 'photo' => $item['author']['xchan_photo_s'], '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['mid']) ? gen_link_id($item['mid']) : ''), + 'b64mid' => (($item['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)), 'body' => $body, // these are for the superblock addon - 'hash' => $item[$who]['xchan_hash'], + 'hash' => $item['author']['xchan_hash'], 'uid' => $item['uid'], 'display' => true ); @@ -907,9 +903,6 @@ class Enotify { if(strpos($message, $tt['xname']) === 0) $message = substr($message, strlen($tt['xname']) + 1); - $mid = basename($tt['link']); - - $b64mid = gen_link_id($mid); $x = [ 'notify_link' => (($tt['ntype'] === NOTIFY_MAIL) ? $tt['link'] : z_root() . '/notify/view/' . $tt['id']), 'name' => $tt['xname'], @@ -917,7 +910,7 @@ class Enotify { 'photo' => $tt['photo'], 'when' => datetime_convert('UTC', date_default_timezone_get(), $tt['created']), 'hclass' => (($tt['seen']) ? 'notify-seen' : 'notify-unseen'), - 'b64mid' => (($tt['otype'] == 'item') ? $b64mid : ''), + 'b64mid' => (($tt['otype'] == 'item') ? $tt['hash'] : ''), 'notify_id' => (($tt['otype'] == 'item') ? $tt['id'] : ''), 'message' => $message ]; diff --git a/Zotlabs/Lib/JcsEddsa2022.php b/Zotlabs/Lib/JcsEddsa2022.php new file mode 100644 index 000000000..14f16c94b --- /dev/null +++ b/Zotlabs/Lib/JcsEddsa2022.php @@ -0,0 +1,92 @@ +<?php + +namespace Zotlabs\Lib; + +use Mmccook\JsonCanonicalizator\JsonCanonicalizatorFactory; +use StephenHill\Base58; + +class JcsEddsa2022 { + + public function __construct() { + return $this; + } + + public function sign($data, $channel): array { + $base58 = new Base58(); + $pubkey = (new Multibase())->publicKey($channel['channel_epubkey']); + $options = [ + 'type' => 'DataIntegrityProof', + 'cryptosuite' => 'eddsa-jcs-2022', + 'created' => datetime_convert('UTC', 'UTC', 'now', ATOM_TIME), + 'verificationMethod' => channel_url($channel) . '#' . $pubkey, + 'proofPurpose' => 'assertionMethod', + ]; + + $optionsHash = $this->hash($this->signableOptions($options), true); + $dataHash = $this->hash($this->signableData($data), true); + + $options['proofValue'] = 'z' . $base58->encode(sodium_crypto_sign_detached($optionsHash . $dataHash, + sodium_base642bin($channel['channel_eprvkey'], SODIUM_BASE64_VARIANT_ORIGINAL_NO_PADDING))); + + return $options; + } + + public function verify($data, $publicKey) { + $base58 = new Base58(); + $encodedSignature = $data['proof']['proofValue'] ?? ''; + if (!str_starts_with($encodedSignature,'z')) { + return false; + } + + $encodedSignature = substr($encodedSignature, 1); + $optionsHash = $this->hash($this->signableOptions($data['proof']), true); + $dataHash = $this->hash($this->signableData($data),true); + + try { + $result = sodium_crypto_sign_verify_detached($base58->decode($encodedSignature), $optionsHash . $dataHash, + (new Multibase())->decode($publicKey, true)); + } + catch (\Exception $e) { + logger('verify exception:' . $e->getMessage()); + } + + logger('SignatureVerify (eddsa-jcs-2022) ' . (($result) ? 'true' : 'false')); + + return $result; + } + + public function signableData($data) { + $signableData = []; + if ($data) { + foreach ($data as $k => $v) { + if (!in_array($k, ['proof', 'signature'])) { + $signableData[$k] = $v; + } + } + } + return $signableData; + } + + public function signableOptions($options) { + $signableOptions = []; + + if ($options) { + foreach ($options as $k => $v) { + if ($k !== 'proofValue') { + $signableOptions[$k] = $v; + } + } + } + return $signableOptions; + } + + public function hash($obj, $binary = false) { + return hash('sha256', $this->canonicalize($obj), $binary); + } + + public function canonicalize($data) { + $canonicalization = JsonCanonicalizatorFactory::getInstance(); + return $canonicalization->canonicalize($data); + } + +} diff --git a/Zotlabs/Lib/Libsync.php b/Zotlabs/Lib/Libsync.php index 80c447672..3130290f7 100644 --- a/Zotlabs/Lib/Libsync.php +++ b/Zotlabs/Lib/Libsync.php @@ -325,9 +325,6 @@ class Libsync { if (array_key_exists('channel', $arr) && is_array($arr['channel']) && count($arr['channel'])) { - $remote_channel = $arr['channel']; - $remote_channel['channel_id'] = $channel['channel_id']; - if (array_key_exists('channel_pageflags', $arr['channel'])) { // Several pageflags are site-specific and cannot be sync'd. @@ -351,16 +348,21 @@ class Libsync { 'channel_a_delegate' ]; + if (empty($channel['channel_epubkey']) && empty($channel['channel_eprvkey'])) { + $eckey = sodium_crypto_sign_keypair(); + $channel['channel_epubkey'] = sodium_bin2base64(sodium_crypto_sign_publickey($eckey), SODIUM_BASE64_VARIANT_ORIGINAL_NO_PADDING); + $channel['channel_eprvkey'] = sodium_bin2base64(sodium_crypto_sign_secretkey($eckey), SODIUM_BASE64_VARIANT_ORIGINAL_NO_PADDING); + } + foreach ($arr['channel'] as $k => $v) { if (in_array($k, $disallowed)) { continue; } - if (!in_array($k, $columns)) { continue; } - - dbq("UPDATE channel set " . dbesc($k) . " = '" . dbesc($v) . "' where channel_id = " . intval($channel['channel_id'])); + $r = dbq("UPDATE channel set " . dbesc($k) . " = '" . dbesc($v) + . "' where channel_id = " . intval($channel['channel_id'])); } } diff --git a/Zotlabs/Lib/Libzot.php b/Zotlabs/Lib/Libzot.php index 3f8685397..3495ede06 100644 --- a/Zotlabs/Lib/Libzot.php +++ b/Zotlabs/Lib/Libzot.php @@ -2,6 +2,7 @@ namespace Zotlabs\Lib; +use App; use Zotlabs\Web\HTTPSig; use Zotlabs\Access\Permissions; use Zotlabs\Access\PermissionLimits; @@ -759,12 +760,13 @@ class Libzot { || ($r[0]['xchan_connurl'] != $arr['primary_location']['connections_url']) || ($r[0]['xchan_addr'] != $arr['primary_location']['address']) || ($r[0]['xchan_follow'] != $arr['primary_location']['follow_url']) + || (isset($arr['ed25519_key']) && $r[0]['xchan_epubkey'] != $arr['ed25519_key']) || ($r[0]['xchan_connpage'] != $arr['connect_url']) || ($r[0]['xchan_url'] != $arr['primary_location']['url']) || $hidden_changed || $adult_changed || $deleted_changed || $pubforum_changed) { $rup = q("update xchan set xchan_name = '%s', xchan_name_date = '%s', xchan_connurl = '%s', xchan_follow = '%s', xchan_connpage = '%s', xchan_hidden = %d, xchan_selfcensored = %d, xchan_deleted = %d, xchan_pubforum = %d, - xchan_addr = '%s', xchan_url = '%s' where xchan_hash = '%s'", + xchan_addr = '%s', xchan_url = '%s', xchan_epubkey = '%s' where xchan_hash = '%s'", dbesc(($arr['name']) ? escape_tags($arr['name']) : '-'), dbesc($arr['name_updated']), dbesc($arr['primary_location']['connections_url']), @@ -776,6 +778,7 @@ class Libzot { intval($arr['public_forum']), dbesc(escape_tags($arr['primary_location']['address'])), dbesc(escape_tags($arr['primary_location']['url'])), + dbesc($arr['ed25519_key'] ?? ''), dbesc($xchan_hash) ); @@ -799,6 +802,7 @@ class Libzot { 'xchan_guid' => $arr['id'], 'xchan_guid_sig' => $arr['id_sig'], 'xchan_pubkey' => $arr['public_key'], + 'xchan_epubkey' => $arr['xchan_epubkey'] ?? '', 'xchan_photo_mimetype' => $arr['photo']['type'], 'xchan_photo_l' => $arr['photo']['url'], 'xchan_addr' => escape_tags($arr['primary_location']['address']), @@ -1139,7 +1143,6 @@ class Libzot { if ($env['encoding'] === 'activitystreams') { $AS = new ActivityStreams($data); - if (!$AS->is_valid()) { logger('Activity rejected: ' . print_r($data, true)); return; @@ -1154,7 +1157,6 @@ class Libzot { else { $item = []; } - logger($AS->debug(), LOGGER_DATA); } @@ -1201,7 +1203,6 @@ class Libzot { // @fixme; $deliveries = self::public_recips($env, $AS); - } $deliveries = array_unique($deliveries); @@ -1222,10 +1223,6 @@ class Libzot { $author_url = $AS->actor['id']; - if ($AS->type === 'Announce') { - $author_url = Activity::get_attributed_to_actor_url($AS); - } - $r = Activity::get_actor_hublocs($author_url); if (!$r) { @@ -1286,7 +1283,7 @@ class Libzot { } } - if (isset($AS->meta['hubloc']) && $AS->meta['hubloc']) { + if (!empty($AS->meta['hubloc']) || $AS->sigok) { $item['item_verified'] = true; } @@ -1304,6 +1301,8 @@ class Libzot { $relay = (($env['type'] === 'response') ? true : false); $result = self::process_delivery($env['sender'], $AS, $item, $deliveries, $relay, false, $message_request); + + Activity::init_background_fetch($env['sender']); } elseif ($env['type'] === 'sync') { // $item = get_channelsync_elements($data); @@ -1324,6 +1323,7 @@ class Libzot { if ($result) { $return = array_merge($return, $result); } + return $return; } @@ -1366,11 +1366,13 @@ class Libzot { static function find_parent_owner_hashes($env, $act) { $r = []; - $thread_parent = self::find_parent($env, $act); - if ($thread_parent) { - $uids = q("SELECT uid FROM item WHERE thr_parent = '%s' OR parent_mid = '%s'", - dbesc($thread_parent), - dbesc($thread_parent) + $parent = self::find_parent($env, $act); + + if ($parent) { + $uids = q("SELECT uid FROM item WHERE thr_parent = '%s' OR parent_mid = '%s' OR mid = '%s'", + dbesc($parent), + dbesc($parent), + dbesc($parent) ); if ($uids) { @@ -1530,7 +1532,7 @@ class Libzot { $local_public = $public; $item_result = null; - $DR = new DReport(z_root(), $sender, $d, $arr['mid']); + $DR = new DReport(z_root(), $sender, $d, $arr['mid'], $arr['uuid']); $channel = channelx_by_hash($d); @@ -1581,6 +1583,39 @@ class Libzot { continue; } + $arr['item_wall'] = 0; + + // This is our own post, possibly coming from a channel clone + if ($arr['owner_xchan'] === $d) { + $arr['item_wall'] = 1; + } + + if (isset($arr['item_deleted']) && $arr['item_deleted']) { + + // remove_community_tag is a no-op if this isn't a community tag activity + // self::remove_community_tag($sender, $arr, $channel['channel_id']); + + // set these just in case we need to store a fresh copy of the deleted post. + // This could happen if the delete got here before the original post did. + + $arr['aid'] = $channel['channel_account_id']; + $arr['uid'] = $channel['channel_id']; + + $item_id = self::delete_imported_item($sender, $act, $arr, $channel['channel_id'], $relay); + $DR->update(($item_id) ? 'deleted' : 'delete_failed'); + $result[] = $DR->get(); + + if ($relay && $item_id) { + logger('process_delivery: invoking relay'); + Master::Summon(['Notifier', 'relay', intval($item_id), 'delete']); + $DR->update('relayed'); + $result[] = $DR->get(); + } + + continue; + } + + // allow public postings to the sys channel regardless of permissions, but not // for comments travelling upstream. Wait and catch them on the way down. // They may have been blocked by the owner. @@ -1607,116 +1642,23 @@ class Libzot { } $tag_delivery = tgroup_check($channel['channel_id'], $arr); - $perm = 'send_stream'; - if (($arr['mid'] !== $arr['parent_mid']) && ($relay)) - $perm = 'post_comments'; - - // This is our own post, possibly coming from a channel clone - if ($arr['owner_xchan'] == $d) { - $arr['item_wall'] = 1; - } - else { - $arr['item_wall'] = 0; - } - - $friendofriend = false; - - if ((!$tag_delivery) && (!$local_public)) { - $allowed = (perm_is_allowed($channel['channel_id'], $sender, $perm)); - - $permit_mentions = intval(PConfig::Get($channel['channel_id'], 'system', 'permit_all_mentions') && i_am_mentioned($channel, $arr)); - - if (!$allowed) { - if ($perm === 'post_comments') { - $parent = q("select * from item where mid = '%s' and uid = %d limit 1", - dbesc($arr['parent_mid']), - intval($channel['channel_id']) - ); - if ($parent) { - $allowed = can_comment_on_post($sender, $parent[0]); - if (!$allowed && $permit_mentions) { - $allowed = true; - } - - if (!$allowed) { - if (PConfig::Get($channel['channel_id'], 'system', 'moderate_unsolicited_comments') && $arr['obj_type'] !== 'Answer') { - $arr['item_blocked'] = ITEM_MODERATED; - $allowed = true; - } - } - } - - } elseif ($permit_mentions) { - $allowed = true; - } - } - - if ($request) { - // Conversation fetches (e.g. $request == true) take place for - // a) new comments on expired posts - // b) hyperdrive (friend-of-friend) conversations - // c) Repeats of posts by others - - - // over-ride normal connection permissions for hyperdrive (friend-of-friend) conversations - // (if hyperdrive is enabled) and repeated posts by a friend. - // If $allowed is already true, this is probably the conversation of a direct friend or a - // conversation fetch for a new comment on an expired post - // Comments of all these activities are allowed and will only be rejected (later) if the parent - // doesn't exist. - - if ($perm === 'send_stream') { - if ($force || get_pconfig($channel['channel_id'], 'system', 'hyperdrive', false)) { - $allowed = true; - } - } - else { - $allowed = true; - } - - $friendofriend = true; - } - - if (intval($arr['item_private']) === 2) { - if (!perm_is_allowed($channel['channel_id'], $sender, 'post_mail')) { - $allowed = false; - } - } - - if (!$allowed) { - logger("permission denied for delivery to channel {$channel['channel_id']} {$channel['channel_address']}"); - $DR->update('permission denied'); - $result[] = $DR->get(); - continue; - } - } - - // logger('item: ' . print_r($arr,true), LOGGER_DATA); + $perm = 'send_stream'; if ($arr['mid'] !== $arr['parent_mid']) { - logger('checking source: "' . $arr['mid'] . '" != "' . $arr['parent_mid'] . '"'); - - // check source route. - // We are only going to accept comments from this sender if the comment has the same route as the top-level-post, - // this is so that permissions mismatches between senders apply to the entire conversation - // As a side effect we will also do a preliminary check that we have the top-level-post, otherwise - // processing it is pointless. + if ($relay) + $perm = 'post_comments'; - $r = q("select route, id, parent_mid, mid, owner_xchan, item_private, obj_type from item where mid = '%s' and uid = %d limit 1", + $parent = q("select * from item where mid = '%s' and uid = %d limit 1", dbesc($arr['parent_mid']), intval($channel['channel_id']) ); - if (!$r) { + if (!$parent) { $DR->update('comment parent not found'); $result[] = $DR->get(); - if ($relay || $request || $local_public) { - continue; - } - // We don't seem to have a copy of this conversation or at least the parent // - so request a copy of the entire conversation to date. // Don't do this if it's a relay post as we're the ones who are supposed to @@ -1728,24 +1670,40 @@ class Libzot { // the top level post is unlikely to be imported and // this is just an exercise in futility. - if (perm_is_allowed($channel['channel_id'], $sender, 'send_stream')) { - Master::Summon(['Zotconvo', $channel['channel_id'], $arr['parent_mid']]); + if ($relay || $request || (!$local_public && !perm_is_allowed($channel['channel_id'], $sender, 'send_stream'))) { + continue; + } + + if ($arr['verb'] === 'Announce') { + App::$cache['as_fetch_objects'][$arr['mid']]['channels'][] = $channel['channel_id']; + App::$cache['as_fetch_objects'][$arr['mid']]['force'] = true; + } + else { + App::$cache['zot_fetch_objects'][$arr['mid']]['channels'][] = $channel['channel_id']; + App::$cache['zot_fetch_objects'][$arr['mid']]['force'] = false; } continue; } - if ($r[0]['obj_type'] === 'Question') { + logger('checking source: "' . $arr['mid'] . '" != "' . $arr['parent_mid'] . '"'); + + // check source route. + // We are only going to accept comments from this sender if the comment has the same route as the top-level-post, + // this is so that permissions mismatches between senders apply to the entire conversation + // As a side effect we will also do a preliminary check that we have the top-level-post, otherwise + // processing it is pointless. + + if ($parent[0]['obj_type'] === 'Question') { // route checking doesn't work correctly here because we've changed the privacy - $r[0]['route'] = EMPTY_STR; + $parent[0]['route'] = EMPTY_STR; // If this is a poll response, convert the obj_type to our (internal-only) "Answer" type - if ($arr['obj_type'] === ACTIVITY_OBJ_COMMENT && $arr['title'] && (!$arr['body'])) { + if (in_array($arr['obj_type'], ['Note', ACTIVITY_OBJ_COMMENT]) && $arr['title'] && (!$arr['body'])) { $arr['obj_type'] = 'Answer'; } } - - if ($relay || $friendofriend || (intval($r[0]['item_private']) === 0 && intval($arr['item_private']) === 0)) { + if ($relay || (intval($parent[0]['item_private']) === 0 && intval($arr['item_private']) === 0)) { // reset the route in case it travelled a great distance upstream // use our parent's route so when we go back downstream we'll match // with whatever route our parent has. @@ -1753,8 +1711,8 @@ class Libzot { // but we are now getting comments via listener delivery // and if there is no privacy on this or the parent, we don't care about the route, // so just set the owner and route accordingly. - $arr['route'] = $r[0]['route']; - $arr['owner_xchan'] = $r[0]['owner_xchan']; + $arr['route'] = $parent[0]['route']; + $arr['owner_xchan'] = $parent[0]['owner_xchan']; } else { @@ -1764,7 +1722,7 @@ class Libzot { // only compare the last hop since it could have arrived at the last location any number of ways. // Always accept empty routes and firehose items (route contains 'undefined') . - $existing_route = explode(',', $r[0]['route']); + $existing_route = explode(',', $parent[0]['route']); $routes = count($existing_route); if ($routes) { $last_hop = array_pop($existing_route); @@ -1781,8 +1739,8 @@ class Libzot { $current_route = ((isset($arr['route']) && $arr['route']) ? $arr['route'] . ',' : '') . $sender; if ($last_hop && $last_hop != $sender) { - logger('comment route mismatch: parent route = ' . $r[0]['route'] . ' expected = ' . $current_route, LOGGER_DEBUG); - logger('comment route mismatch: parent msg = ' . $r[0]['id'], LOGGER_DEBUG); + logger('comment route mismatch: parent route = ' . $parent[0]['route'] . ' expected = ' . $current_route, LOGGER_DEBUG); + logger('comment route mismatch: parent msg = ' . $parent[0]['id'], LOGGER_DEBUG); $DR->update('comment route mismatch'); $result[] = $DR->get(); continue; @@ -1795,42 +1753,80 @@ class Libzot { } } - // This is used to fetch allow/deny rules if either the sender - // or owner is a connection. post_is_importable() evaluates all of them - $abook = q("select * from abook where abook_channel = %d and ( abook_xchan = '%s' OR abook_xchan = '%s' )", - intval($channel['channel_id']), - dbesc($arr['owner_xchan']), - dbesc($arr['author_xchan']) - ); + if (!$tag_delivery && !$local_public) { + $allowed = perm_is_allowed($channel['channel_id'], $sender, $perm); - if (isset($arr['item_deleted']) && $arr['item_deleted']) { + $permit_mentions = intval(PConfig::Get($channel['channel_id'], 'system', 'permit_all_mentions') && i_am_mentioned($channel, $arr)); - // remove_community_tag is a no-op if this isn't a community tag activity - self::remove_community_tag($sender, $arr, $channel['channel_id']); + if (!$allowed) { + if ($parent && $perm === 'send_stream') { + // if we own the parent we will accept its comments + $allowed = true; + } - // set these just in case we need to store a fresh copy of the deleted post. - // This could happen if the delete got here before the original post did. + elseif ($parent && $perm === 'post_comments') { + $allowed = can_comment_on_post($sender, $parent[0]); - $arr['aid'] = $channel['channel_account_id']; - $arr['uid'] = $channel['channel_id']; + if (!$allowed && $permit_mentions) { + $allowed = true; + } - $item_id = self::delete_imported_item($sender, $act, $arr, $channel['channel_id'], $relay); - $DR->update(($item_id) ? 'deleted' : 'delete_failed'); - $result[] = $DR->get(); + if (!$allowed) { + if (PConfig::Get($channel['channel_id'], 'system', 'moderate_unsolicited_comments') && $arr['obj_type'] !== 'Answer') { + $arr['item_blocked'] = ITEM_MODERATED; + $allowed = true; + } + } - if ($relay && $item_id) { - logger('process_delivery: invoking relay'); - Master::Summon(['Notifier', 'relay', intval($item_id)]); - $DR->update('relayed'); - $result[] = $DR->get(); + } + elseif ($permit_mentions) { + $allowed = true; + } } - continue; + if ($request) { + // Conversation fetches (e.g. $request == true) take place for + // a) new comments on expired posts + // b) manual import of posts via search (in this case force will be true) + // c) import of conversations from friends of friends (they can currently arriuve from streams if a channel is configured to do so) + + // Comments of all these activities are allowed and will only be rejected (later) if the parent + // doesn't exist. + + if ($perm === 'send_stream') { + if ($force) { + $allowed = true; + } + } + else { + $allowed = true; + } + } + + if (intval($arr['item_private']) === 2) { + if (!perm_is_allowed($channel['channel_id'], $sender, 'post_mail')) { + $allowed = false; + } + } + + if (!$allowed) { + logger("permission denied for delivery to channel {$channel['channel_id']} {$channel['channel_address']}"); + $DR->update('permission denied'); + $result[] = $DR->get(); + continue; + } } + // This is used to fetch allow/deny rules if either the sender + // or owner is a connection. post_is_importable() evaluates all of them + $abook = q("select * from abook where abook_channel = %d and ( abook_xchan = '%s' OR abook_xchan = '%s' )", + intval($channel['channel_id']), + dbesc($arr['owner_xchan']), + dbesc($arr['author_xchan']) + ); + // 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($arr['mid']), dbesc(str_replace(z_root() . '/activity/', z_root() . '/item/', $arr['mid'])), @@ -1838,14 +1834,6 @@ class Libzot { ); if ($r) { - // We already have this post. - // Dismiss its announce - if ($act->type === 'Announce') { - $DR->update('update ignored'); - $result[] = $DR->get(); - continue; - } - $item_id = $r[0]['id']; if (intval($r[0]['item_deleted'])) { @@ -1902,12 +1890,12 @@ class Libzot { $maxlen = get_max_import_size(); - if ($maxlen && mb_strlen($arr['body']) > $maxlen) { + if ($maxlen && isset($arr['body']) && mb_strlen($arr['body']) > $maxlen) { $arr['body'] = mb_substr($arr['body'], 0, $maxlen, 'UTF-8'); logger('message length exceeds max_import_size: truncated'); } - if ($maxlen && mb_strlen($arr['summary']) > $maxlen) { + if ($maxlen && isset($arr['summary']) && mb_strlen($arr['summary']) > $maxlen) { $arr['summary'] = mb_substr($arr['summary'], 0, $maxlen, 'UTF-8'); logger('message summary length exceeds max_import_size: truncated'); } @@ -1915,7 +1903,6 @@ class Libzot { if (post_is_importable($arr['uid'], $arr, $abook)) { $item_result = item_store($arr); if ($item_result['success']) { - $item_id = $item_result['item_id']; if ($item_source && in_array($item_result['item']['obj_type'], ['Event', ACTIVITY_OBJ_EVENT])) { @@ -1957,6 +1944,7 @@ class Libzot { // preserve conversations with which you are involved from expiration $stored = ((isset($item_result['item'])) ? $item_result['item'] : false); + if ((is_array($stored)) && ($stored['id'] != $stored['parent']) && ($stored['author_xchan'] === $channel['channel_hash'])) { retain_item($stored['item']['parent']); @@ -2005,11 +1993,14 @@ class Libzot { $ret = []; - $signer = q("select hubloc_hash, hubloc_url from hubloc where hubloc_id_url = '%s' and hubloc_network = 'zot6' order by hubloc_id desc limit 1", dbesc($a['signature']['signer']) ); + $signer_hash = $signer[0]['hubloc_hash'] ?? $a['signature']['signer']; + $conv_owner = $signer_hash; + + $i = 0; foreach ($items as $activity) { @@ -2023,14 +2014,14 @@ class Libzot { } if (!$AS->is_valid()) { - logger('FOF Activity rejected: ' . print_r($activity, true)); + logger('Fetched activity rejected: ' . print_r($activity, true)); continue; } // logger($AS->debug()); if(empty($AS->actor['id'])) { - logger('No actor id: ' . print_r($AS, true)); + logger('Fetched activity no actor id: ' . print_r($AS, true)); continue; } @@ -2043,7 +2034,7 @@ class Libzot { $r = self::zot_record_preferred($r); } if (!$r) { - logger('FOF Activity: no actor'); + logger('Fetched activity: no actor'); continue; } } @@ -2058,7 +2049,7 @@ class Libzot { $ro = self::zot_record_preferred($ro); } if (!$ro) { - logger('FOF Activity: no obj actor'); + logger('Fetched activity: no obj actor'); continue; } } @@ -2073,14 +2064,18 @@ class Libzot { $arr['author_xchan'] = $r['hubloc_hash']; - if ($signer) { - $arr['owner_xchan'] = $signer[0]['hubloc_hash']; - } - else { - $arr['owner_xchan'] = $a['signature']['signer']; + if ($i === 0) { + // Set the author of the toplevel post as conv_owner + $conv_owner = $r['hubloc_hash']; } - if (isset($AS->meta['hubloc']) || $arr['author_xchan'] === $arr['owner_xchan']) { + $arr['owner_xchan'] = $conv_owner; + $arr['source_xchan'] = $signer_hash; + + // WARNING: the presence of both source_xchan and non-zero item_uplink here will cause a delivery loop + $arr['item_uplink'] = 0; + + if (!empty($AS->meta['hubloc']) || $arr['author_xchan'] === $arr['owner_xchan'] || $AS->sigok) { $arr['item_verified'] = true; } @@ -2092,13 +2087,15 @@ class Libzot { } } - logger('FOF Activity received: ' . print_r($arr, true), LOGGER_DATA, LOG_DEBUG); - logger('FOF Activity recipient: ' . $channel['channel_hash'], LOGGER_DATA, LOG_DEBUG); + logger('Fetched activity received: ' . print_r($arr, true), LOGGER_DATA, LOG_DEBUG); + logger('Fetched activity recipient: ' . $channel['channel_hash'], LOGGER_DATA, LOG_DEBUG); $result = self::process_delivery($arr['owner_xchan'], $AS, $arr, [$channel['channel_hash']], false, false, true, $force); if ($result) { $ret = array_merge($ret, $result); } + + $i++; } return $ret; @@ -2320,12 +2317,20 @@ class Libzot { // this information from the metadata should have no other discernible impact. if (($stored['id'] != $stored['parent']) && intval($stored['item_origin'])) { - q("update item set item_origin = 0 where id = %d and uid = %d", - intval($stored['id']), - intval($stored['uid']) + q("update item set item_origin = 0 where id = %d", + intval($stored['id']) ); } - } + } else { + if ($stored['id'] !== $stored['parent']) { + q( + "update item set commented = '%s', changed = '%s' where id = %d", + dbesc(datetime_convert()), + dbesc(datetime_convert()), + intval($stored['parent']) + ); + } + } // Use phased deletion to set the deleted flag, call both tag_deliver and the notifier to notify downstream channels @@ -2814,6 +2819,7 @@ class Libzot { ]; $ret['public_key'] = $e['channel_pubkey']; + $ret['ed25519_key'] = $e['xchan_epubkey']; $ret['signing_algorithm'] = 'rsa-sha256'; $ret['username'] = $e['channel_address']; $ret['name'] = $e['channel_name']; @@ -2979,7 +2985,7 @@ class Libzot { $ret['site']['admin'] = get_config('system', 'admin_email'); $visible_plugins = []; - if (is_array(\App::$plugins) && count(\App::$plugins)) { + if (is_array(App::$plugins) && count(App::$plugins)) { $r = q("select * from addon where hidden = 0"); if ($r) foreach ($r as $rr) diff --git a/Zotlabs/Lib/Multibase.php b/Zotlabs/Lib/Multibase.php new file mode 100644 index 000000000..099723630 --- /dev/null +++ b/Zotlabs/Lib/Multibase.php @@ -0,0 +1,34 @@ +<?php +namespace Zotlabs\Lib; + +use StephenHill\Base58; + +class Multibase { + + protected $key = null; + + public function __construct() { + return $this; + } + + public function publicKey($key) { + $base58 = new Base58(); + $raw = hex2bin('ed01') . sodium_base642bin($key, SODIUM_BASE64_VARIANT_ORIGINAL_NO_PADDING); + return 'z' . $base58->encode($raw); + } + + public function secretKey($key) { + $base58 = new Base58(); + $raw = hex2bin('8026') . sodium_base642bin($key, SODIUM_BASE64_VARIANT_ORIGINAL_NO_PADDING); + return 'z' . $base58->encode($raw); + } + + public function decode($key, $binary = false) { + $base58 = new Base58(); + $key = substr($key,1); + $raw = $base58->decode($key); + $binaryKey = substr($raw, 2); + return $binary ? $binaryKey : sodium_bin2base64($binaryKey, SODIUM_BASE64_VARIANT_ORIGINAL_NO_PADDING); + } + +} diff --git a/Zotlabs/Lib/Share.php b/Zotlabs/Lib/Share.php index 81f378d0d..8abbfda80 100644 --- a/Zotlabs/Lib/Share.php +++ b/Zotlabs/Lib/Share.php @@ -112,7 +112,7 @@ class Share { if(! $this->item) return $bb; - $is_photo = (($this->item['obj_type'] === ACTIVITY_OBJ_PHOTO) ? true : false); + $is_photo = ((in_array($this->item['obj_type'], ['Image', ACTIVITY_OBJ_PHOTO])) ? true : false); if($is_photo) { $object = json_decode($this->item['obj'],true); $photo_bb = $object['body']; diff --git a/Zotlabs/Lib/Text.php b/Zotlabs/Lib/Text.php new file mode 100644 index 000000000..f593f9dd6 --- /dev/null +++ b/Zotlabs/Lib/Text.php @@ -0,0 +1,24 @@ +<?php + +namespace Zotlabs\Lib; + +class Text { + + /** + * use this on "body" or "content" input where angle chars shouldn't be removed, + * and allow them to be safely displayed. + * + * @param string $string + * + * @return string + */ + + public static function escape_tags(string $string): string { + if (!$string) { + return EMPTY_STR; + } + + return htmlspecialchars($string, ENT_COMPAT, 'UTF-8', false); + } + +} diff --git a/Zotlabs/Lib/ThreadItem.php b/Zotlabs/Lib/ThreadItem.php index 037ddb19e..8f364e945 100644 --- a/Zotlabs/Lib/ThreadItem.php +++ b/Zotlabs/Lib/ThreadItem.php @@ -19,7 +19,7 @@ class ThreadItem { private $comment_box_template = 'comment_item.tpl'; private $commentable = false; // list of supported reaction emojis - a site can over-ride this via config system.reactions - private $reactions = ['1f60a','1f44f','1f37e','1f48b','1f61e','2665','1f606','1f62e','1f634','1f61c','1f607','1f608']; + private $reactions = ['slightly_smiling_face','clapping_hands','bottle_with_popping_cork','kiss_mark','disappointed_face','red_heart','grinning_face','astonished_face','sleeping_face','winking_face_with_tongue','smiling_face_with_halo','smiling_face_with_horns']; private $toplevel = false; private $children = array(); private $parent = null; @@ -34,6 +34,7 @@ class ThreadItem { private $channel = null; private $display_mode = 'normal'; private $reload = ''; + private $mid_uuid_map = []; public function __construct($data) { @@ -46,6 +47,7 @@ class ThreadItem { // Prepare the children if(isset($data['children'])) { + foreach($data['children'] as $item) { /* @@ -56,7 +58,6 @@ class ThreadItem { continue; } - $child = new ThreadItem($item); $this->add_child($child); } @@ -65,6 +66,8 @@ class ThreadItem { unset($this->data['children']); } + + // allow a site to configure the order and content of the reaction emoji list if($this->toplevel) { $x = get_config('system','reactions'); @@ -82,7 +85,7 @@ class ThreadItem { * _ false on failure */ - public function get_template_data($conv_responses, $thread_level=1, $conv_flags = []) { + public function get_template_data($conv_responses, $mid_uuid_map, $thread_level=1, $conv_flags = []) { $result = []; $item = $this->get_data(); @@ -121,12 +124,14 @@ class ThreadItem { $locktype = 0; } - $shareable = ((($conv->get_profile_owner() == local_channel() && local_channel()) && ($item['item_private'] != 1)) ? true : false); + $shareable = ((($conv->get_profile_owner() == local_channel() && local_channel()) && (intval($item['item_private']) === 0)) ? true : false); // allow an exemption for sharing stuff from your private feeds if($item['author']['xchan_network'] === 'rss') $shareable = true; + $repeatable = ((($conv->get_profile_owner() == local_channel() && local_channel()) && (intval($item['item_private']) === 0) && (in_array($item['author']['xchan_network'], ['zot6', 'activitypub']))) ? true : false); + // @fixme // Have recently added code to properly handle polls in group reshares by redirecting all of the poll responses to the group. // Sharing a poll using a regular embedded share is harder because the poll will need to fork. This is due to comment permissions. @@ -194,9 +199,14 @@ class ThreadItem { $attend = null; // process action responses - e.g. like/dislike/attend/agree/whatever - $response_verbs = array('like'); - if(feature_enabled($conv->get_profile_owner(),'dislike')) + $response_verbs[] = 'like'; + + if(feature_enabled($conv->get_profile_owner(),'dislike')) { $response_verbs[] = 'dislike'; + } + + $response_verbs[] = 'announce'; + if(in_array($item['obj_type'], ['Event', ACTIVITY_OBJ_EVENT])) { $response_verbs[] = 'attendyes'; $response_verbs[] = 'attendno'; @@ -222,6 +232,8 @@ class ThreadItem { $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)) { @@ -232,6 +244,16 @@ class ThreadItem { } $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']] : ''); @@ -248,6 +270,7 @@ class ThreadItem { } $showlike = ((x($conv_responses['like'],$item['mid'])) ? format_like($conv_responses['like'][$item['mid']],$conv_responses['like'][$item['mid'] . '-l'],'like',$item['mid']) : ''); +*/ /* * We should avoid doing this all the time, but it depends on the conversation mode @@ -315,13 +338,11 @@ class ThreadItem { $share = []; $embed = []; if ($shareable) { - // This actually turns out not to be possible in some protocol stacks without opening up hundreds of new issues. - // Will allow it only for uri resolvable sources. - if(strpos($item['mid'],'http') === 0) { - //Not yet ready for primetime - //$share = array( t('Repeat This'), t('repeat')); - } - $embed = [t('Share This'), t('share')]; + $embed = [t('Share'), t('share')]; + } + + if ($repeatable) { + $share = [t('Repeat'), t('repeat')]; } $dreport = ''; @@ -333,7 +354,7 @@ class ThreadItem { $dreport_link = ''; if((intval($item['item_type']) == ITEM_TYPE_POST) && (! get_config('system','disable_dreport')) && strcmp(datetime_convert('UTC','UTC',$item['created']),datetime_convert('UTC','UTC',"now - $keep_reports days")) > 0) { $dreport = t('Delivery Report'); - $dreport_link = gen_link_id($item['mid']); + $dreport_link = '?mid=' . $item['mid']; } $is_new = false; @@ -352,8 +373,8 @@ class ThreadItem { if($conv->get_mode() === 'channel') $viewthread = z_root() . '/channel/' . $owner_address . '?f=&mid=' . urlencode(gen_link_id($item['mid'])); - $comment_count_txt = sprintf(tt('%d Comment', '%d Comments', $total_children), $total_children); - $list_unseen_txt = (($unseen_comments) ? sprintf(t('%d unseen'), $unseen_comments) : ''); + $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(); @@ -363,8 +384,8 @@ class ThreadItem { call_hooks('dropdown_extras',$dropdown_extras_arr); $dropdown_extras = $dropdown_extras_arr['dropdown_extras']; - $midb64 = gen_link_id($item['mid']); - $mids = [ $midb64 ]; + $midb64 = $item['uuid']; + $mids = [ $item['uuid'] ]; $response_mids = []; foreach($response_verbs as $v) { if(isset($conv_responses[$v]['mids'][$item['mid']])) { @@ -480,35 +501,44 @@ class ThreadItem { 'comment_count' => $total_children, 'comment_count_txt' => $comment_count_txt, 'list_unseen_txt' => $list_unseen_txt, - 'markseen' => t('Mark all seen'), + '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 : ''), +*/ 'modal_dismiss' => t('Close'), - 'showlike' => $showlike, - 'showdislike' => $showdislike, + // 'showlike' => $showlike, + // 'showdislike' => $showdislike, 'comment' => ($item['item_delayed'] ? '' : $this->get_comment_box()), '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' => (($item['parent_mid'] != $item['thr_parent']) ? gen_link_id($item['thr_parent']) : ''), + 'thr_parent_uuid' => (($item['parent_mid'] != $item['thr_parent']) ? $mid_uuid_map[$item['thr_parent']] : ''), 'contact_id' => (($contact) ? $contact['abook_id'] : ''), 'moderate' => ($item['item_blocked'] == ITEM_MODERATED), 'moderate_approve' => t('Approve'), 'moderate_delete' => t('Delete'), - 'rtl' => in_array($item['lang'], rtl_languages()) - + 'rtl' => in_array($item['lang'], rtl_languages()), ); $arr = array('item' => $item, 'output' => $tmp_item); @@ -531,12 +561,12 @@ class ThreadItem { if(($this->get_display_mode() === 'normal') && ($nb_children > 0)) { foreach($children as $child) { - $result['children'][] = $child->get_template_data($conv_responses, $thread_level + 1,$conv_flags); + $result['children'][] = $child->get_template_data($conv_responses, $mid_uuid_map, $thread_level + 1,$conv_flags); } // Collapse if(($nb_children > $visible_comments) || ($thread_level > 1)) { $result['children'][0]['comment_firstcollapsed'] = true; - $result['children'][0]['num_comments'] = $comment_count_txt; + $result['children'][0]['num_comments'] = $comment_count_txt['label']; $result['children'][0]['hide_text'] = sprintf( t('%s show all'), '<i class="fa fa-chevron-down"></i>'); if($thread_level > 1) { $result['children'][$nb_children - 1]['comment_lastcollapsed'] = true; @@ -613,7 +643,7 @@ class ThreadItem { * Only add what will be displayed */ - if(activity_match($item->get_data_value('verb'),ACTIVITY_LIKE) || activity_match($item->get_data_value('verb'),ACTIVITY_DISLIKE)) { + if(activity_match($item->get_data_value('verb'), ['Like', 'Dislike', ACTIVITY_LIKE, ACTIVITY_DISLIKE])) { return false; } @@ -878,6 +908,12 @@ class ThreadItem { $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_photo = $this->data['source']['xchan_photo_s']; + $this->owner_name = $this->data['source']['xchan_name']; + $this->wall_to_wall = true; + } } private function is_wall_to_wall() { diff --git a/Zotlabs/Lib/ThreadStream.php b/Zotlabs/Lib/ThreadStream.php index 2ad24a690..fb3b6dd9b 100644 --- a/Zotlabs/Lib/ThreadStream.php +++ b/Zotlabs/Lib/ThreadStream.php @@ -171,7 +171,7 @@ class ThreadStream { */ - if(($item->get_data_value('id') != $item->get_data_value('parent')) && (activity_match($item->get_data_value('verb'),ACTIVITY_LIKE) || activity_match($item->get_data_value('verb'),ACTIVITY_DISLIKE))) { + if($item->get_data_value('id') != $item->get_data_value('parent') && activity_match($item->get_data_value('verb'), ['Like', 'Dislike', ACTIVITY_LIKE, ACTIVITY_DISLIKE])) { return false; } @@ -211,7 +211,7 @@ class ThreadStream { * _ The data requested on success * _ false on failure */ - public function get_template_data($conv_responses) { + public function get_template_data($conv_responses, $mid_uuid_map) { $result = array(); foreach($this->threads as $item) { @@ -220,7 +220,7 @@ class ThreadStream { $item_data = $this->prepared_item; } else { - $item_data = $item->get_template_data($conv_responses); + $item_data = $item->get_template_data($conv_responses, $mid_uuid_map); } if(!$item_data) { logger('Failed to get item template data ('. $item->get_id() .').', LOGGER_DEBUG, LOG_ERR); diff --git a/Zotlabs/Module/Activity.php b/Zotlabs/Module/Activity.php index 4ddfe602d..133312e28 100644 --- a/Zotlabs/Module/Activity.php +++ b/Zotlabs/Module/Activity.php @@ -7,7 +7,6 @@ use Zotlabs\Web\Controller; use Zotlabs\Daemon\Master; use Zotlabs\Lib\Activity as ZlibActivity; use Zotlabs\Lib\ActivityStreams; -use Zotlabs\Lib\LDSignatures; use Zotlabs\Web\HTTPSig; use Zotlabs\Lib\Libzot; use Zotlabs\Lib\ThreadListener; @@ -26,7 +25,7 @@ class Activity extends Controller { $portable_id = EMPTY_STR; - $item_normal_extra = sprintf(" and not verb in ('%s', '%s') ", + $item_normal_extra = sprintf(" and not verb in ('Follow', 'Ignore', '%s', '%s') ", dbesc(ACTIVITY_FOLLOW), dbesc(ACTIVITY_UNFOLLOW) ); @@ -155,22 +154,7 @@ class Activity extends Controller { if(! $i) http_status_exit(404, 'Not found'); - $x = array_merge(['@context' => [ - ACTIVITYSTREAMS_JSONLD_REV, - 'https://w3id.org/security/v1', - z_root() . ZOT_APSCHEMA_REV - ]], $i); - - $headers = []; - $headers['Content-Type'] = 'application/x-zot+json' ; - $x['signature'] = LDSignatures::sign($x,$chan); - $ret = json_encode($x, JSON_UNESCAPED_SLASHES); - $headers['Digest'] = HTTPSig::generate_digest_header($ret); - $headers['(request-target)'] = strtolower($_SERVER['REQUEST_METHOD']) . ' ' . $_SERVER['REQUEST_URI']; - $h = HTTPSig::create_sig($headers,$chan['channel_prvkey'],channel_url($chan)); - HTTPSig::set_headers($h); - echo $ret; - killme(); + as_return_and_die($i, $chan); } @@ -202,7 +186,7 @@ class Activity extends Controller { } } - $item_normal_extra = sprintf(" and not verb in ('%s', '%s') ", + $item_normal_extra = sprintf(" and not verb in ('Follow', 'Ignore', '%s', '%s') ", dbesc(ACTIVITY_FOLLOW), dbesc(ACTIVITY_UNFOLLOW) ); @@ -260,25 +244,7 @@ class Activity extends Controller { $channel = channelx_by_n($items[0]['uid']); - $x = array_merge( ['@context' => [ - ACTIVITYSTREAMS_JSONLD_REV, - 'https://w3id.org/security/v1', - z_root() . ZOT_APSCHEMA_REV - ]], ZlibActivity::encode_activity($items[0],true)); - - $headers = []; - $headers['Content-Type'] = 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"' ; - $x['signature'] = LDSignatures::sign($x,$channel); - $ret = json_encode($x, JSON_UNESCAPED_SLASHES); - $headers['Date'] = datetime_convert('UTC','UTC', 'now', 'D, d M Y H:i:s \\G\\M\\T'); - $headers['Digest'] = HTTPSig::generate_digest_header($ret); - $headers['(request-target)'] = strtolower($_SERVER['REQUEST_METHOD']) . ' ' . $_SERVER['REQUEST_URI']; - - $h = HTTPSig::create_sig($headers,$channel['channel_prvkey'],channel_url($channel)); - HTTPSig::set_headers($h); - echo $ret; - killme(); - + as_return_and_die(ZlibActivity::encode_activity($items[0]), $channel); } goaway(z_root() . '/item/' . argv(1)); diff --git a/Zotlabs/Module/Admin/Site.php b/Zotlabs/Module/Admin/Site.php index b24821b28..dae5e7c77 100644 --- a/Zotlabs/Module/Admin/Site.php +++ b/Zotlabs/Module/Admin/Site.php @@ -68,7 +68,7 @@ class Site { $login_on_homepage = ((x($_POST,'login_on_homepage')) ? True : False); $enable_context_help = ((x($_POST,'enable_context_help')) ? True : False); $no_community_page = !((x($_POST,'no_community_page')) ? True : False); - $default_expire_days = ((array_key_exists('default_expire_days',$_POST)) ? intval($_POST['default_expire_days']) : 0); + $default_expire_days = ((array_key_exists('default_expire_days',$_POST)) ? intval($_POST['default_expire_days']) : 30); $active_expire_days = ((array_key_exists('active_expire_days',$_POST)) ? intval($_POST['active_expire_days']) : 7); $reply_address = ((array_key_exists('reply_address',$_POST) && trim($_POST['reply_address'])) ? trim($_POST['reply_address']) : 'noreply@' . \App::get_hostname()); @@ -227,20 +227,6 @@ class Site { */ function get() { - /* Installed langs */ - $lang_choices = array(); - $langs = glob('view/*/hstrings.php'); - - if(is_array($langs) && count($langs)) { - if(! in_array('view/en/hstrings.php',$langs)) - $langs[] = 'view/en/'; - asort($langs); - foreach($langs as $l) { - $t = explode("/",$l); - $lang_choices[$t[1]] = $t[1]; - } - } - /* Installed themes */ $theme_choices_mobile["---"] = t("Default"); $theme_choices = array(); @@ -425,7 +411,6 @@ class Site { '$banner' => array('banner', t("Banner/Logo"), $banner, t('Unfiltered HTML/CSS/JS is allowed')), '$admininfo' => array('admininfo', t("Administrator Information"), $admininfo, t("Contact information for site administrators. Displayed on siteinfo page. BBCode can be used here")), '$siteinfo' => array('siteinfo', t('Site Information'), get_config('system','siteinfo'), t("Publicly visible description of this site. Displayed on siteinfo page. BBCode can be used here")), - //'$language' => array('language', t("System language"), get_config('system','language'), "", $lang_choices), '$theme' => array('theme', t("System theme"), get_config('system','theme'), t("Default system theme - may be over-ridden by user profiles - <a href='#' id='cnftheme'>change theme settings</a>"), $theme_choices), // '$theme_mobile' => array('theme_mobile', t("Mobile system theme"), get_config('system','mobile_theme'), t("Theme for mobile devices"), $theme_choices_mobile), // '$site_channel' => array('site_channel', t("Channel to use for this website's static pages"), get_config('system','site_channel'), t("Site Channel")), @@ -536,7 +521,7 @@ class Site { '$poll_interval' => array('poll_interval', t("Poll interval"), (x(get_config('system','poll_interval'))?get_config('system','poll_interval'):2), t("Delay background polling processes by this many seconds to reduce system load. If 0, use delivery interval.")), '$imagick_path' => array('imagick_path', t("Path to ImageMagick convert program"), get_config('system','imagick_convert_path'), t("If set, use this program to generate photo thumbnails for huge images ( > 4000 pixels in either dimension), otherwise memory exhaustion may occur. Example: /usr/bin/convert")), '$maxloadavg' => array('maxloadavg', t("Maximum Load Average"), ((intval(get_config('system','maxloadavg')) > 0)?get_config('system','maxloadavg'):50), t("Maximum system load before delivery and poll processes are deferred - default 50.")), - '$default_expire_days' => array('default_expire_days', t('Expiration period in days for imported (grid/network) content'), intval(get_config('system','default_expire_days')), t('0 for no expiration of imported content')), + '$default_expire_days' => array('default_expire_days', t('Expiration period in days for imported (grid/network) content'), intval(get_config('system','default_expire_days', 30)), t('0 for no expiration of imported content')), '$active_expire_days' => array('active_expire_days', t('Do not expire any posts which have comments less than this many days ago'), intval(get_config('system','active_expire_days',7)), ''), '$sellpage' => array('site_sellpage', t('Public servers: Optional landing (marketing) webpage for new registrants'), get_config('system','sellpage',''), sprintf( t('Create this page first. Default is %s/register'),z_root())), '$first_page' => array('first_page', t('Page to display after creating a new channel'), get_config('system','workflow_channel_next','profiles'), t('Default: profiles')), diff --git a/Zotlabs/Module/Api.php b/Zotlabs/Module/Api.php index aa0fca54d..5b835ac51 100644 --- a/Zotlabs/Module/Api.php +++ b/Zotlabs/Module/Api.php @@ -24,42 +24,42 @@ class Api extends \Zotlabs\Web\Controller { notice( t('Permission denied.') . EOL); return; } - + } - + function get() { if(\App::$cmd === 'api/oauth/authorize'){ - - /* + + /* * api/oauth/authorize interact with the user. return a standard page */ - + \App::$page['template'] = 'minimal'; - + // get consumer/client from request token try { $request = \OAuth1Request::from_request(); } catch(\Exception $e) { logger('OAuth exception: ' . print_r($e,true)); - // echo "<pre>"; var_dump($e); + // echo "<pre>"; var_dump($e); killme(); } - - + + if(x($_POST,'oauth_yes')){ - + $app = $this->oauth_get_client($request); - if (is_null($app)) + if (is_null($app)) return "Invalid request. Unknown token."; $consumer = new \OAuth1Consumer($app['client_id'], $app['pw'], $app['redirect_uri']); - + $verifier = md5($app['secret'] . local_channel()); set_config('oauth', $verifier, local_channel()); - - + + if($consumer->callback_url != null) { $params = $request->get_parameters(); $glue = '?'; @@ -68,28 +68,28 @@ class Api extends \Zotlabs\Web\Controller { goaway($consumer->callback_url . $glue . "oauth_token=" . \OAuth1Util::urlencode_rfc3986($params['oauth_token']) . "&oauth_verifier=" . \OAuth1Util::urlencode_rfc3986($verifier)); killme(); } - + $tpl = get_markup_template("oauth_authorize_done.tpl"); $o = replace_macros($tpl, array( '$title' => t('Authorize application connection'), '$info' => t('Return to your app and insert this Security Code:'), '$code' => $verifier, )); - + return $o; } - - + + if(! local_channel()) { //TODO: we need login form to redirect to this page notice( t('Please login to continue.') . EOL ); return login(false,'api-login',$request->get_parameters()); } - + $app = $this->oauth_get_client($request); if (is_null($app)) return "Invalid request. Unknown token."; - + $tpl = get_markup_template('oauth_authorize.tpl'); $o = replace_macros($tpl, array( '$title' => t('Authorize application connection'), @@ -98,12 +98,12 @@ class Api extends \Zotlabs\Web\Controller { '$yes' => t('Yes'), '$no' => t('No'), )); - + //echo "<pre>"; var_dump($app); killme(); - + return $o; } - + echo api_call(); killme(); } @@ -112,8 +112,8 @@ class Api extends \Zotlabs\Web\Controller { $params = $request->get_parameters(); $token = $params['oauth_token']; - - $r = q("SELECT clients.* FROM clients, tokens WHERE clients.client_id = tokens.client_id + + $r = q("SELECT clients.* FROM clients, tokens WHERE clients.client_id = tokens.client_id AND tokens.id = '%s' AND tokens.auth_scope = 'request' ", dbesc($token) ); @@ -121,7 +121,7 @@ class Api extends \Zotlabs\Web\Controller { return $r[0]; return null; - + } - + } diff --git a/Zotlabs/Module/Apschema.php b/Zotlabs/Module/Apschema.php index e8d45c522..2ec11086a 100644 --- a/Zotlabs/Module/Apschema.php +++ b/Zotlabs/Module/Apschema.php @@ -2,68 +2,13 @@ namespace Zotlabs\Module; +use Zotlabs\Web\Controller; +use Zotlabs\Lib\Activity; -class Apschema extends \Zotlabs\Web\Controller { - +class Apschema extends Controller { function init() { - - $base = z_root(); - - $arr = [ - '@context' => [ - 'zot' => z_root() . '/apschema#', - 'id' => '@id', - 'type' => '@type', - 'commentPolicy' => 'zot:commentPolicy', - 'meData' => 'zot:meData', - 'meDataType' => 'zot:meDataType', - 'meEncoding' => 'zot:meEncoding', - 'meAlgorithm' => 'zot:meAlgorithm', - 'meCreator' => 'zot:meCreator', - 'meSignatureValue' => 'zot:meSignatureValue', - 'locationAddress' => 'zot:locationAddress', - 'locationPrimary' => 'zot:locationPrimary', - 'locationDeleted' => 'zot:locationDeleted', - 'nomadicLocation' => 'zot:nomadicLocation', - 'nomadicHubs' => 'zot:nomadicHubs', - 'emojiReaction' => 'zot:emojiReaction', - 'expires' => 'zot:expires', - 'directMessage' => 'zot:directMessage', - 'schema' => 'http://schema.org#', - 'PropertyValue' => 'schema:PropertyValue', - 'value' => 'schema:value', - - 'manuallyApprovesFollowers' => 'as:manuallyApprovesFollowers', - - - 'magicEnv' => [ - '@id' => 'zot:magicEnv', - '@type' => '@id' - ], - - 'nomadicLocations' => [ - '@id' => 'zot:nomadicLocations', - '@type' => '@id' - ], - - 'ostatus' => 'http://ostatus.org#', - 'conversation' => 'ostatus:conversation', - - 'diaspora' => 'https://diasporafoundation.org/ns/', - 'guid' => 'diaspora:guid', - - 'Hashtag' => 'as:Hashtag' - - ] - ]; - header('Content-Type: application/ld+json'); - echo json_encode($arr,JSON_UNESCAPED_SLASHES); + echo json_encode(Activity::ap_context(), JSON_UNESCAPED_SLASHES); killme(); - } - - - - } diff --git a/Zotlabs/Module/Channel.php b/Zotlabs/Module/Channel.php index e8c3316e9..4e6811a10 100644 --- a/Zotlabs/Module/Channel.php +++ b/Zotlabs/Module/Channel.php @@ -101,16 +101,23 @@ class Channel extends Controller { App::$meta->set('robots', 'noindex, noarchive'); } + $identifier = 'uuid'; + $mid = $_REQUEST['mid'] ?? ''; + + if (str_starts_with($mid, 'b64.')) { + $mid = unpack_link_id($mid); + $identifier = 'mid'; + } + + if ($mid === false) { + http_status_exit(404, 'Not found'); + } + if (ActivityStreams::is_as_request($channel)) { // Somebody may attempt an ActivityStreams fetch on one of our message permalinks // Make it do the right thing. - $mid = ((x($_REQUEST, 'mid')) ? unpack_link_id($_REQUEST['mid']) : ''); - if ($mid === false) { - http_status_exit(404, 'Not found'); - } - if ($mid) { $obj = null; if (strpos($mid, z_root() . '/item/') === 0) { @@ -127,6 +134,7 @@ class Channel extends Controller { $obj->init(); } } + as_return_and_die(Activity::encode_person($channel, true), $channel); } @@ -155,15 +163,9 @@ class Channel extends Controller { profile_load($which, $profile); // Add Opengraph markup - $mid = ((x($_REQUEST, 'mid')) ? unpack_link_id($_REQUEST['mid']) : ''); - - if ($mid === false) { - notice(t('Malformed message id.') . EOL); - return; - } if ($mid) { - $r = q("SELECT * FROM item WHERE mid = '%s' AND uid = %d AND item_private = 0 LIMIT 1", + $r = q("SELECT * FROM item WHERE $identifier = '%s' AND uid = %d AND item_private = 0 LIMIT 1", dbesc($mid), intval($channel['channel_id']) ); @@ -178,7 +180,16 @@ class Channel extends Controller { $category = $datequery = $datequery2 = ''; - $mid = ((x($_REQUEST, 'mid')) ? unpack_link_id($_REQUEST['mid']) : ''); + $mid = $_REQUEST['mid'] ?? ''; + $identifier = 'uuid'; + $encoded_mid = null; + + if (str_starts_with($mid, 'b64.')) { + $encoded_mid = $mid; + $mid = unpack_link_id($mid); + $identifier = 'mid'; + } + if ($mid === false) { notice(t('Malformed message id.') . EOL); return; @@ -322,7 +333,7 @@ class Channel extends Controller { if (($update) && (!$load)) { if ($mid) { - $r = q("SELECT parent AS item_id from item where mid = '%s' and uid = %d $item_normal_update + $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", dbesc($mid), intval(App::$profile['profile_uid']) @@ -370,7 +381,7 @@ class Channel extends Controller { if ($noscript_content || $load) { if ($mid) { - $r = q("SELECT parent AS item_id from item where mid = '%s' and uid = %d $item_normal + $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", dbesc($mid), intval(App::$profile['profile_uid']) @@ -396,7 +407,6 @@ class Channel extends Controller { } } if ($r) { - $parents_str = ids_to_querystr($r, 'item_id'); $r = q("SELECT item.*, item.id AS item_id @@ -427,12 +437,8 @@ class Channel extends Controller { $mode = (($search) ? 'search' : 'channel'); - if ((!$update) && (!$load)) { - - - //if we got a decoded hash we must encode it again before handing to javascript - $mid = gen_link_id($mid); + if ((!$update) && (!$load)) { // This is ugly, but we can't pass the profile_uid through the session to the ajax updater, // because browser prefetching might change it on us. We have to deliver it with the page. @@ -470,7 +476,7 @@ class Channel extends Controller { '$file' => '', '$cats' => (($category) ? urlencode($category) : ''), '$tags' => (($hashtags) ? urlencode($hashtags) : ''), - '$mid' => (($mid) ? urlencode($mid) : ''), + '$mid' => $encoded_mid ?? $mid, '$verb' => '', '$net' => '', '$dend' => $datequery, diff --git a/Zotlabs/Module/Cloud.php b/Zotlabs/Module/Cloud.php index 05109247a..527b06b3a 100644 --- a/Zotlabs/Module/Cloud.php +++ b/Zotlabs/Module/Cloud.php @@ -54,9 +54,9 @@ class Cloud extends Controller { if (local_channel()) { $channel = \App::get_channel(); $auth->setCurrentUser($channel['channel_address']); + $auth->channel_account_id = $channel['channel_account_id']; $auth->channel_id = $channel['channel_id']; $auth->channel_hash = $channel['channel_hash']; - $auth->channel_account_id = $channel['channel_account_id']; if($channel['channel_timezone']) $auth->setTimezone($channel['channel_timezone']); } diff --git a/Zotlabs/Module/Contactedit.php b/Zotlabs/Module/Contactedit.php index 58c3380a1..3527e9380 100644 --- a/Zotlabs/Module/Contactedit.php +++ b/Zotlabs/Module/Contactedit.php @@ -177,22 +177,8 @@ class Contactedit extends Controller { intval($channel['channel_id']) ); if (($pr) && (!intval($contact['abook_hidden'])) && (intval(get_pconfig($channel['channel_id'], 'system', 'post_newfriend')))) { - $xarr = []; - - $xarr['item_wall'] = 1; - $xarr['item_origin'] = 1; - $xarr['item_thread_top'] = 1; - $xarr['owner_xchan'] = $xarr['author_xchan'] = $channel['channel_hash']; - $xarr['allow_cid'] = $channel['channel_allow_cid']; - $xarr['allow_gid'] = $channel['channel_allow_gid']; - $xarr['deny_cid'] = $channel['channel_deny_cid']; - $xarr['deny_gid'] = $channel['channel_deny_gid']; - $xarr['item_private'] = (($xarr['allow_cid'] || $xarr['allow_gid'] || $xarr['deny_cid'] || $xarr['deny_gid']) ? 1 : 0); - $xarr['body'] = '[zrl=' . $channel['xchan_url'] . ']' . $channel['xchan_name'] . '[/zrl]' . ' ' . t('is now connected to') . ' ' . '[zrl=' . $contact['xchan_url'] . ']' . $contact['xchan_name'] . '[/zrl]'; - - $xarr['body'] .= "\n\n\n" . '[zrl=' . $contact['xchan_url'] . '][zmg=80x80]' . $contact['xchan_photo_m'] . '[/zmg][/zrl]'; - + $xarr['body'] .= "\n\n\n" . '[zrl=' . $contact['xchan_url'] . '][zmg=' . $contact['xchan_photo_m'] . ']' . $contact['xchan_name'] . '[/zmg][/zrl]'; post_activity_item($xarr); } @@ -494,28 +480,32 @@ class Contactedit extends Controller { 'message' => '' ]; - if ($cmd === 'resetphoto') { - q("update xchan set xchan_photo_date = '2001-01-01 00:00:00' where xchan_hash = '%s'", + if ($cmd === 'refresh') { + q("update xchan set xchan_photo_date = '0001-01-01 00:00:00', xchan_name_date = '0001-01-01 00:00:00' where xchan_hash = '%s'", dbesc($contact['xchan_hash']) ); - $cmd = 'refresh'; - } - if ($cmd === 'refresh') { if ($contact['xchan_network'] === 'zot6') { if (Libzot::refresh($contact, App::get_channel())) { $ret['success'] = true; $ret['message'] = t('Refresh succeeded'); } else { - $ret['message'] = t('Refresh failed - channel is currently unavailable'); + $ret['message'] = t('Refresh failed'); } } else { // if you are on a different network we'll force a refresh of the connection basic info - Master::Summon(['Notifier', 'permission_update', $contact['abook_id']]); - $ret['success'] = true; - $ret['message'] = t('Refresh succeeded'); + $hookinfo = [ + 'contact' => $contact, + 'success' => false, + 'message' => '' + ]; + + call_hooks('actor_refetch', $hookinfo); + + $ret['success'] = $hookinfo['success']; + $ret['message'] = $hookinfo['message']; } return $ret; @@ -625,16 +615,10 @@ class Contactedit extends Controller { return [ 'refresh' => [ - 'label' => t('Refresh Permissions'), - 'title' => t('Fetch updated permissions'), + 'label' => t('Refresh'), + 'title' => t('Refetch contact info'), ], - 'rephoto' => [ - 'label' => t('Refresh Photo'), - 'title' => t('Fetch updated photo'), - ], - - 'block' => [ 'label' => (intval($contact['abook_blocked']) ? t('Unblock') : t('Block')), 'sel' => (intval($contact['abook_blocked']) ? 'active' : ''), diff --git a/Zotlabs/Module/Conversation.php b/Zotlabs/Module/Conversation.php index 86ce66caa..aa8349f55 100644 --- a/Zotlabs/Module/Conversation.php +++ b/Zotlabs/Module/Conversation.php @@ -25,7 +25,7 @@ class Conversation extends Controller { $portable_id = EMPTY_STR; - $item_normal_extra = sprintf(" and not verb in ('%s', '%s') ", + $item_normal_extra = sprintf(" and not verb in ('Follow', 'Ignore', '%s', '%s') ", dbesc(ACTIVITY_FOLLOW), dbesc(ACTIVITY_UNFOLLOW) ); diff --git a/Zotlabs/Module/Cover_photo.php b/Zotlabs/Module/Cover_photo.php index 1ecbfce3e..1c1240e29 100644 --- a/Zotlabs/Module/Cover_photo.php +++ b/Zotlabs/Module/Cover_photo.php @@ -93,8 +93,6 @@ class Cover_photo extends \Zotlabs\Web\Controller { $image_id = substr($image_id,0,-2); } - - $srcX = intval($_POST['xstart']); $srcY = intval($_POST['ystart']); $srcW = intval($_POST['xfinal']) - $srcX; @@ -228,7 +226,7 @@ class Cover_photo extends \Zotlabs\Web\Controller { return; } - $this->send_cover_photo_activity($channel,$base_image,$profile); + profile_activity([t('Cover Photo')], $base_image['resource_id']); $sync = attach_export_data($channel,$base_image['resource_id']); if($sync) @@ -245,13 +243,12 @@ class Cover_photo extends \Zotlabs\Web\Controller { } - $hash = photo_new_resource(); $smallest = 0; require_once('include/attach.php'); - $res = attach_store(\App::get_channel(), get_observer_hash(), '', array('album' => t('Cover Photos'), 'hash' => $hash, 'nosync' => true)); + $res = attach_store(\App::get_channel(), get_observer_hash(), '', ['album' => t('Cover Photos'), 'hash' => $hash, 'nosync' => true, 'source' => 'photos']); logger('attach_store: ' . print_r($res,true)); @@ -287,45 +284,6 @@ class Cover_photo extends \Zotlabs\Web\Controller { } - function send_cover_photo_activity($channel,$photo,$profile) { - - $arr = array(); - $arr['item_thread_top'] = 1; - $arr['item_origin'] = 1; - $arr['item_wall'] = 1; - - if($profile && stripos($profile['gender'],t('female')) !== false) - $t = t('%1$s updated her %2$s'); - elseif($profile && stripos($profile['gender'],t('male')) !== false) - $t = t('%1$s updated his %2$s'); - else - $t = t('%1$s updated their %2$s'); - - $ptext = '[zrl=' . z_root() . '/photos/' . $channel['channel_address'] . '/image/' . $photo['resource_id'] . ']' . t('cover photo') . '[/zrl]'; - - $ltext = '[zrl=' . z_root() . '/profile/' . $channel['channel_address'] . ']' . '[zmg]' . z_root() . '/photo/' . $photo['resource_id'] . '-8[/zmg][/zrl]'; - - $arr['body'] = sprintf($t,$channel['channel_name'],$ptext) . "\n\n" . $ltext; - - $acl = new \Zotlabs\Access\AccessList($channel); - $x = $acl->get(); - $arr['allow_cid'] = $x['allow_cid']; - - $arr['allow_gid'] = $x['allow_gid']; - $arr['deny_cid'] = $x['deny_cid']; - $arr['deny_gid'] = $x['deny_gid']; - - $arr['uid'] = $channel['channel_id']; - $arr['aid'] = $channel['channel_account_id']; - - $arr['owner_xchan'] = $channel['channel_hash']; - $arr['author_xchan'] = $channel['channel_hash']; - - post_activity_item($arr); - - - } - /** * @brief Generate content of profile-photo view @@ -334,7 +292,6 @@ class Cover_photo extends \Zotlabs\Web\Controller { * */ - function get() { if(! local_channel()) { diff --git a/Zotlabs/Module/Display.php b/Zotlabs/Module/Display.php index 9e46d7620..f08aedc95 100644 --- a/Zotlabs/Module/Display.php +++ b/Zotlabs/Module/Display.php @@ -38,7 +38,14 @@ class Display extends Controller { $item_hash = $_REQUEST['mid']; } - $item_hash = unpack_link_id($item_hash); + $identifier = 'uuid'; + $encoded_item_hash = null; + + if (str_starts_with($item_hash, 'b64.')) { + $encoded_item_hash = $item_hash; + $item_hash = unpack_link_id($item_hash); + $identifier = 'mid'; + } if ($item_hash === false) { App::$error = 400; @@ -104,7 +111,7 @@ class Display extends Controller { $target_item = null; - $r = q("select id, uid, mid, parent, parent_mid, thr_parent, verb, item_type, item_deleted, author_xchan, item_blocked from item where mid = '%s' limit 1", + $r = q("select id, uid, mid, parent, parent_mid, thr_parent, verb, item_type, item_deleted, author_xchan, item_blocked from item where $identifier = '%s' limit 1", dbesc($item_hash) ); @@ -152,18 +159,11 @@ class Display extends Controller { call_hooks('item_custom_display', $target_item); $simple_update = ''; - if($update && $_SESSION['loadtime']) + if($update && isset($_SESSION['loadtime'])) $simple_update = " AND (( item_unseen = 1 AND item.changed > '" . datetime_convert('UTC','UTC',$_SESSION['loadtime']) . "' ) OR item.changed > '" . datetime_convert('UTC','UTC',$_SESSION['loadtime']) . "' ) "; if((! $update) && (! $load)) { - // if the target item is not a post (eg a like) we want to address its thread parent - - //$mid = ((($target_item['verb'] == ACTIVITY_LIKE) || ($target_item['verb'] == ACTIVITY_DISLIKE)) ? $target_item['thr_parent'] : $target_item['mid']); - - // if we got a decoded hash we must encode it again before handing to javascript - $mid = gen_link_id($target_item['mid']); - $o .= '<div id="live-display"></div>' . "\r\n"; $o .= "<script> var profile_uid = " . ((intval(local_channel())) ? local_channel() : (-1)) . "; var netargs = '?f='; var profile_page = " . App::$pager['page'] . "; </script>\r\n"; @@ -196,7 +196,7 @@ class Display extends Controller { '$dbegin' => '', '$verb' => '', '$net' => '', - '$mid' => (($mid) ? urlencode($mid) : '') + '$mid' => $encoded_item_hash ?? $item_hash )); head_add_link([ diff --git a/Zotlabs/Module/Dreport.php b/Zotlabs/Module/Dreport.php index f5ad80eef..5db607545 100644 --- a/Zotlabs/Module/Dreport.php +++ b/Zotlabs/Module/Dreport.php @@ -13,11 +13,10 @@ class Dreport extends \Zotlabs\Web\Controller { $table = 'item'; $channel = \App::get_channel(); - $mid = ((argc() > 1) ? unpack_link_id(argv(1)) : ''); + $mid = $_REQUEST['mid'] ?? ''; - if($mid === 'push') { + if(argv(1) === 'push') { $table = 'push'; - $mid = ((argc() > 2) ? unpack_link_id(argv(2)) : ''); 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 )) ", @@ -31,7 +30,7 @@ class Dreport extends \Zotlabs\Web\Controller { } } sleep(3); - goaway(z_root() . '/dreport/' . gen_link_id($mid)); + goaway(z_root() . '/dreport?mid=' . $mid); } if(! $mid) { @@ -114,7 +113,7 @@ class Dreport extends \Zotlabs\Web\Controller { } } - usort($r,'self::dreport_gravity_sort'); + usort($r, [self::class, 'dreport_gravity_sort']); $entries = array(); foreach($r as $rr) { diff --git a/Zotlabs/Module/Emoji.php b/Zotlabs/Module/Emoji.php new file mode 100644 index 000000000..a1459d179 --- /dev/null +++ b/Zotlabs/Module/Emoji.php @@ -0,0 +1,57 @@ +<?php +namespace Zotlabs\Module; + +use Zotlabs\Web\Controller; +use Zotlabs\Daemon\Master; +use Zotlabs\Lib\ActivityStreams; +use App; + + +class Emoji extends Controller { + + function init() { + + $shortname = argv(1); + + if (!$shortname) { + killme(); + } + + $emojis = get_emojis(); + + if (!isset($emojis[$shortname])) { + killme(); + } + + $emoji = $emojis[$shortname]; + + if (!file_exists($emoji['filepath'])) { + killme(); + } + + $image = getimagesize($emoji['filepath']); + + if(ActivityStreams::is_as_request()) { + $last_modified = date(ATOM_TIME, filemtime($emoji['filepath'])); + + $obj = [ + 'id' => z_root() . '/emoji/' . $shortname, + 'type' => 'Emoji', + 'name' => $emoji['shortname'], + 'updated' => $last_modified, + 'icon' => [ + 'type' => 'Image', + 'mediaType' => $image['mime'], + 'url' => z_root() . '/' . $emoji['filepath'] + ] + ]; + + as_return_and_die($obj); + } + + header('Content-Type: ' . $image['mime']); + echo file_get_contents($emoji['filepath']); + killme(); + } + +} diff --git a/Zotlabs/Module/Event.php b/Zotlabs/Module/Event.php index 22a1341cc..767a8f494 100644 --- a/Zotlabs/Module/Event.php +++ b/Zotlabs/Module/Event.php @@ -4,7 +4,6 @@ namespace Zotlabs\Module; use Zotlabs\Web\Controller; use Zotlabs\Lib\ActivityStreams; use Zotlabs\Lib\Activity; -use Zotlabs\Lib\LDSignatures; use Zotlabs\Web\HTTPSig; class Event extends Controller { @@ -17,7 +16,7 @@ class Event extends Controller { if(! $item_id) return; - $item_normal = " and item.item_hidden = 0 and item.item_type = 0 and item.item_unpublished = 0 + $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 "; $sql_extra = item_permissions_sql(0); @@ -49,28 +48,9 @@ class Event extends Controller { $obj = $items[0]['obj']; } - $x = array_merge(['@context' => [ - ACTIVITYSTREAMS_JSONLD_REV, - 'https://w3id.org/security/v1', - z_root() . ZOT_APSCHEMA_REV - ]], $obj ); - - $headers = []; - $headers['Content-Type'] = 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"' ; - $x['signature'] = LDSignatures::sign($x,$channel); - $ret = json_encode($x, JSON_UNESCAPED_SLASHES); - $headers['Date'] = datetime_convert('UTC','UTC', 'now', 'D, d M Y H:i:s \\G\\M\\T'); - $headers['Digest'] = HTTPSig::generate_digest_header($ret); - $headers['(request-target)'] = strtolower($_SERVER['REQUEST_METHOD']) . ' ' . $_SERVER['REQUEST_URI']; - - $h = HTTPSig::create_sig($headers,$channel['channel_prvkey'],channel_url($channel)); - HTTPSig::set_headers($h); - - echo $ret; - killme(); - + as_return_and_die($obj, $channel); } } -}
\ No newline at end of file +} diff --git a/Zotlabs/Module/Follow.php b/Zotlabs/Module/Follow.php index f8bfc11f3..55ff507c8 100644 --- a/Zotlabs/Module/Follow.php +++ b/Zotlabs/Module/Follow.php @@ -7,7 +7,6 @@ use Zotlabs\Lib\Libsync; use Zotlabs\Lib\ActivityStreams; use Zotlabs\Lib\Activity; use Zotlabs\Web\HTTPSig; -use Zotlabs\Lib\LDSignatures; use Zotlabs\Lib\Connect; use Zotlabs\Daemon\Master; @@ -39,30 +38,14 @@ class Follow extends Controller { http_status_exit(404, 'Not found'); } - $x = array_merge(['@context' => [ - ACTIVITYSTREAMS_JSONLD_REV, - 'https://w3id.org/security/v1', - z_root() . ZOT_APSCHEMA_REV - ]], - [ + $obj = [ 'id' => z_root() . '/follow/' . $r[0]['abook_id'], 'type' => 'Follow', 'actor' => $actor, 'object' => $r[0]['xchan_url'] - ]); - - $headers = []; - $headers['Content-Type'] = 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"' ; - $x['signature'] = LDSignatures::sign($x,$chan); - $ret = json_encode($x, JSON_UNESCAPED_SLASHES); - $headers['Date'] = datetime_convert('UTC','UTC', 'now', 'D, d M Y H:i:s \\G\\M\\T'); - $headers['Digest'] = HTTPSig::generate_digest_header($ret); - $headers['(request-target)'] = strtolower($_SERVER['REQUEST_METHOD']) . ' ' . $_SERVER['REQUEST_URI']; - $h = HTTPSig::create_sig($headers,$chan['channel_prvkey'],channel_url($chan)); - HTTPSig::set_headers($h); - echo $ret; - killme(); + ]; + as_return_and_die($obj, $chan); } if (! local_channel()) { diff --git a/Zotlabs/Module/Hq.php b/Zotlabs/Module/Hq.php index 5c3ae9273..ae6a016fc 100644 --- a/Zotlabs/Module/Hq.php +++ b/Zotlabs/Module/Hq.php @@ -30,16 +30,20 @@ class Hq extends \Zotlabs\Web\Controller { $item_hash = ''; if(argc() > 1 && argv(1) !== 'load') { - $item_hash = unpack_link_id(argv(1)); + $item_hash = argv(1); } if(isset($_REQUEST['mid'])) { - $item_hash = unpack_link_id($_REQUEST['mid']); + $item_hash = $_REQUEST['mid']; } - if($item_hash === false) { - notice(t('Malformed message id.') . EOL); - return; + $identifier = 'uuid'; + $encoded_item_hash = null; + + if (str_starts_with($item_hash, 'b64.')) { + $encoded_item_hash = $item_hash; + $item_hash = unpack_link_id($item_hash); + $identifier = 'mid'; } $item_normal = item_normal(); @@ -54,7 +58,7 @@ 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 mid = '%s' order by uid $sql_order limit 2", + $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", intval(local_channel()), intval($sys['channel_id']), dbesc($item_hash) @@ -118,7 +122,7 @@ class Hq extends \Zotlabs\Web\Controller { //$mid = ((($target_item['verb'] == ACTIVITY_LIKE) || ($target_item['verb'] == ACTIVITY_DISLIKE)) ? $target_item['thr_parent'] : $target_item['mid']); // if we got a decoded hash we must encode it again before handing to javascript - $mid = gen_link_id($target_item['mid']); + // $mid = gen_link_id($target_item['mid']); } else { $mid = ''; @@ -156,7 +160,7 @@ class Hq extends \Zotlabs\Web\Controller { '$dbegin' => '', '$verb' => '', '$net' => '', - '$mid' => (($mid) ? urlencode($mid) : '') + '$mid' => $encoded_item_hash ?? $item_hash ]); } diff --git a/Zotlabs/Module/Item.php b/Zotlabs/Module/Item.php index 25ccb0cbf..b158ed4e0 100644 --- a/Zotlabs/Module/Item.php +++ b/Zotlabs/Module/Item.php @@ -52,7 +52,7 @@ class Item extends Controller { $portable_id = EMPTY_STR; - $item_normal_extra = sprintf(" and not verb in ('%s', '%s') ", + $item_normal_extra = sprintf(" and not verb in ('Follow', 'Ignore', '%s', '%s') ", dbesc(ACTIVITY_FOLLOW), dbesc(ACTIVITY_UNFOLLOW) ); @@ -168,7 +168,7 @@ class Item extends Controller { $portable_id = EMPTY_STR; - $item_normal_extra = sprintf(" and not verb in ('%s', '%s') ", + $item_normal_extra = sprintf(" and not verb in ('Follow', 'Ignore', '%s', '%s') ", dbesc(ACTIVITY_FOLLOW), dbesc(ACTIVITY_UNFOLLOW) ); @@ -275,7 +275,7 @@ class Item extends Controller { if (argc() > 1 && argv(1) !== 'drop') { - $x = q("select uid, item_wall, llink, mid from item where mid = '%s' or mid = '%s' or uuid = '%s'", + $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)), dbesc(argv(1)) @@ -285,7 +285,7 @@ class Item extends Controller { if (intval($xv['item_wall'])) { $c = channelx_by_n($xv['uid']); if ($c) { - goaway(z_root() . '/channel/' . $c['channel_address'] . '?mid=' . gen_link_id($xv['mid'])); + goaway(z_root() . '/channel/' . $c['channel_address'] . '?mid=' . $xv['uuid']); } } } @@ -298,7 +298,6 @@ class Item extends Controller { function post() { - // This will change. Figure out who the observer is and whether or not // they have permission to post here. Else ignore the post. @@ -405,7 +404,7 @@ class Item extends Controller { $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']) : ACTIVITY_OBJ_NOTE); + $obj_type = ((x($_REQUEST, 'obj_type')) ? escape_tags($_REQUEST['obj_type']) : 'Article'); // 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); @@ -444,9 +443,6 @@ class Item extends Controller { if (!x($_REQUEST, 'type')) $_REQUEST['type'] = 'net-comment'; - if ($obj_type == ACTIVITY_OBJ_NOTE) - $obj_type = ACTIVITY_OBJ_COMMENT; - if ($parent) { $r = q("SELECT * FROM item WHERE id = %d LIMIT 1", intval($parent) @@ -679,7 +675,7 @@ class Item extends Controller { $verb = $orig_post['verb']; $app = $orig_post['app']; $title = escape_tags(trim($_REQUEST['title'])); - $summary = trim($_REQUEST['summary']); + $summary = escape_tags(trim($_REQUEST['summary'])); $body = trim($_REQUEST['body']); $item_flags = $orig_post['item_flags']; $item_origin = $orig_post['item_origin']; @@ -740,7 +736,7 @@ class Item extends Controller { $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'])) ? trim($_REQUEST['summary']) : ''); + $summary = ((isset($_REQUEST['summary'])) ? escape_tags(trim($_REQUEST['summary'])) : ''); $body = ((isset($_REQUEST['body'])) ? trim($_REQUEST['body']) : ''); $body .= ((isset($_REQUEST['attachment'])) ? trim($_REQUEST['attachment']) : ''); $postopts = ''; @@ -793,7 +789,6 @@ class Item extends Controller { && ($channel['channel_pageflags'] & PAGE_ALLOWCODE)) ? true : false); if ($preview) { - $summary = z_input_filter($summary, $mimetype, $execflag); $body = z_input_filter($body, $mimetype, $execflag); } @@ -943,6 +938,30 @@ class Item extends Controller { } } + if (preg_match_all('/(\:(\w|\+|\-)+\:)(?=|[\!\.\?]|$)/', $body, $match)) { + // emoji shortcodes + $emojis = get_emojis(); + foreach ($match[0] as $mtch) { + $shortname = trim($mtch, ':'); + + if (!isset($emojis[$shortname])) { + continue; + } + + $emoji = $emojis[$shortname]; + + $post_tags[] = [ + 'uid' => $profile_uid, + 'ttype' => TERM_EMOJI, + 'otype' => TERM_OBJ_POST, + 'term' => trim($mtch), + 'url' => z_root() . '/emoji/' . $shortname, + 'imgurl' => z_root() . '/' . $emoji['filepath'] + ]; + } + } + + // BBCODE end alert } @@ -963,6 +982,10 @@ class Item extends Controller { } } + + + + if ($orig_post) { // preserve original tags $t = q("select * from term where oid = %d and otype = %d and uid = %d and ttype in ( %d, %d, %d )", @@ -1009,7 +1032,7 @@ class Item extends Controller { if (!strlen($verb)) - $verb = ACTIVITY_POST; + $verb = 'Create'; $notify_type = (($parent) ? 'comment-new' : 'wall-new'); @@ -1220,18 +1243,6 @@ class Item extends Controller { $this->add_listeners($datarray); } - // We only need edit activities for other federated protocols - // which do not support edits natively. While this does federate - // edits, it presents a number of issues locally - such as #757 and #758. - // The SQL check for an edit activity would not perform that well so to fix these issues - // requires an additional item flag (perhaps 'item_edit_activity') that we can add to the - // query for searches and notifications. - - // For now we'll just forget about trying to make edits work on network protocols that - // don't support them. - - // item_create_edit_activity($x); - if (!$parent) { $r = q("select * from item where id = %d", intval($post_id) @@ -1285,8 +1296,8 @@ class Item extends Controller { 'from_xchan' => $datarray['author_xchan'], 'to_xchan' => $datarray['owner_xchan'], 'item' => $datarray, - 'link' => z_root() . '/display/' . gen_link_id($datarray['mid']), - 'verb' => ACTIVITY_POST, + 'link' => z_root() . '/display/' . $datarray['uuid'], + 'verb' => 'Create', 'otype' => 'item', 'parent' => $parent, 'parent_mid' => $parent_item['mid'] @@ -1303,8 +1314,8 @@ class Item extends Controller { 'from_xchan' => $datarray['author_xchan'], 'to_xchan' => $datarray['owner_xchan'], 'item' => $datarray, - 'link' => z_root() . '/display/' . gen_link_id($datarray['mid']), - 'verb' => ACTIVITY_POST, + 'link' => z_root() . '/display/' . $datarray['uuid'], + 'verb' => 'Create', 'otype' => 'item' ]); } @@ -1349,7 +1360,7 @@ class Item extends Controller { } $datarray['id'] = $post_id; - $datarray['llink'] = z_root() . '/display/' . gen_link_id($datarray['mid']); + $datarray['llink'] = z_root() . '/display/' . $datarray['uuid']; call_hooks('post_local_end', $datarray); @@ -1373,7 +1384,7 @@ class Item extends Controller { if ($return_path) { if ($return_path === 'hq') { - goaway(z_root() . '/hq/' . gen_link_id($datarray['mid'])); + goaway(z_root() . '/hq/' . $datarray['uuid']); } goaway(z_root() . "/" . $return_path); diff --git a/Zotlabs/Module/Like.php b/Zotlabs/Module/Like.php index 54daf6471..4460900a8 100644 --- a/Zotlabs/Module/Like.php +++ b/Zotlabs/Module/Like.php @@ -19,14 +19,12 @@ class Like extends Controller { private function reaction_to_activity($reaction) { $acts = [ - 'like' => ACTIVITY_LIKE, - 'dislike' => ACTIVITY_DISLIKE, - 'agree' => ACTIVITY_AGREE, - 'disagree' => ACTIVITY_DISAGREE, - 'abstain' => ACTIVITY_ABSTAIN, - 'attendyes' => ACTIVITY_ATTEND, - 'attendno' => ACTIVITY_ATTENDNO, - 'attendmaybe' => ACTIVITY_ATTENDMAYBE + 'like' => 'Like', + 'dislike' => 'Dislike', + 'announce' => ACTIVITY_SHARE, + 'attendyes' => 'Accept', + 'attendno' => 'Reject', + 'attendmaybe' => 'TentativeAccept' ]; // unlike (etc.) reactions are an undo of positive reactions, rather than a negative action. @@ -71,11 +69,12 @@ class Like extends Controller { $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')", + AND verb IN ('%s', '%s', '%s', '%s', '%s', '%s', 'Accept', 'Reject', 'TentativeAccept')", intval($arr['item']['uid']), dbesc($arr['item']['mid']), - dbesc(ACTIVITY_LIKE), - dbesc(ACTIVITY_DISLIKE), + dbesc('Like'), + dbesc('Dislike'), + dbesc(ACTIVITY_SHARE), dbesc(ACTIVITY_ATTEND), dbesc(ACTIVITY_ATTENDNO), dbesc(ACTIVITY_ATTENDMAYBE) @@ -133,7 +132,7 @@ class Like extends Controller { } $is_rsvp = false; - if (in_array($activity, [ACTIVITY_ATTEND, ACTIVITY_ATTENDNO, ACTIVITY_ATTENDMAYBE])) { + if (in_array($activity, ['Accept', 'Reject', 'TentativeAccept', ACTIVITY_ATTEND, ACTIVITY_ATTENDNO, ACTIVITY_ATTENDMAYBE])) { $is_rsvp = true; } @@ -182,7 +181,7 @@ class Like extends Controller { } } $post_type = t('channel'); - $obj_type = ACTIVITY_OBJ_PROFILE; + $obj_type = 'Profile'; $profile = $r[0]; } @@ -211,8 +210,8 @@ class Like extends Controller { $public = false; $post_type = t('thing'); - $obj_type = ACTIVITY_OBJ_PROFILE; - $tgttype = ACTIVITY_OBJ_THING; + $obj_type = 'Profile'; + $tgttype = 'Page'; $links = array(); $links[] = array('rel' => 'alternate', 'type' => 'text/html', @@ -220,12 +219,7 @@ class Like extends Controller { if ($r[0]['imgurl']) $links[] = array('rel' => 'photo', 'href' => $r[0]['obj_imgurl']); - $target = json_encode(array( - 'type' => $tgttype, - 'title' => $r[0]['obj_term'], - 'id' => z_root() . '/thing/' . $r[0]['obj_obj'], - 'link' => $links - )); + $target = Activity::fetch_thing(['id' => $r[0]['obj_obj']]); $plink = '[zrl=' . z_root() . '/thing/' . $r[0]['obj_obj'] . ']' . $r[0]['obj_term'] . '[/zrl]'; @@ -323,6 +317,8 @@ class Like extends Controller { // parent, copy that as well. if ($r) { + $obj_type = $r[0]['obj_type']; + if ($r[0]['uid'] === $sys_channel['channel_id'] && local_channel()) { $r = [copy_of_pubitem(App::get_channel(), $r[0]['mid'])]; } @@ -370,15 +366,11 @@ class Like extends Controller { $multi_undo = false; - // event participation and consensus items are essentially radio toggles. If you make a subsequent choice, + // event participation items are essentially radio toggles. If you make a subsequent choice, // we need to eradicate your first choice. - if ($activity === ACTIVITY_ATTEND || $activity === ACTIVITY_ATTENDNO || $activity === ACTIVITY_ATTENDMAYBE) { - $verbs = " '" . dbesc(ACTIVITY_ATTEND) . "','" . dbesc(ACTIVITY_ATTENDNO) . "','" . dbesc(ACTIVITY_ATTENDMAYBE) . "' "; - $multi_undo = 1; - } - if ($activity === ACTIVITY_AGREE || $activity === ACTIVITY_DISAGREE || $activity === ACTIVITY_ABSTAIN) { - $verbs = " '" . dbesc(ACTIVITY_AGREE) . "','" . dbesc(ACTIVITY_DISAGREE) . "','" . dbesc(ACTIVITY_ABSTAIN) . "' "; + if (in_array($activity, ['Accept', 'Reject', 'TentativeAccept', ACTIVITY_ATTEND, ACTIVITY_ATTENDNO, ACTIVITY_ATTENDMAYBE])) { + $verbs = "'Accept','Reject','TentativeAccept','" . dbesc(ACTIVITY_ATTEND) . "','" . dbesc(ACTIVITY_ATTENDNO) . "','" . dbesc(ACTIVITY_ATTENDMAYBE) . "' "; $multi_undo = true; } @@ -437,7 +429,7 @@ class Like extends Controller { } } - $uuid = item_message_id(); + $uuid = new_uuid(); $arr = array(); @@ -450,14 +442,20 @@ class Like extends Controller { $arr['item_wall'] = 1; } else { - $post_type = (($item['resource_type'] === 'photo') ? t('photo') : t('status')); - if (in_array($item['obj_type'], ['Event', ACTIVITY_OBJ_EVENT])) - $post_type = t('event'); - - $obj_type = (($item['resource_type'] === 'photo') ? ACTIVITY_OBJ_PHOTO : ACTIVITY_OBJ_NOTE); - - if ($obj_type === ACTIVITY_OBJ_NOTE && (!intval($item['item_thread_top']))) - $obj_type = ACTIVITY_OBJ_COMMENT; + switch ($item['object_type']) { + case 'Image': + $post_type = t('image'); + break; + case 'Invite': + $post_type = t('event'); + break; + case 'Profile': + $post_type = t('profile'); + break; + default: + $post_type = t('status'); + break; + } $object = json_encode(Activity::fetch_item(['id' => $item['mid']])); @@ -485,12 +483,6 @@ 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 === 'agree') - $bodyverb = t('%1$s agrees with %2$s\'s %3$s'); - if ($verb === 'disagree') - $bodyverb = t('%1$s doesn\'t agree with %2$s\'s %3$s'); - if ($verb === 'abstain') - $bodyverb = t('%1$s abstains from a decision on %2$s\'s %3$s'); if ($verb === 'attendyes') $bodyverb = t('%1$s is attending %2$s\'s %3$s'); if ($verb === 'attendno') @@ -511,7 +503,7 @@ class Like extends Controller { $arr['thr_parent'] = $item['mid']; $ulink = '[zrl=' . $item_author['xchan_url'] . '][bdi]' . $item_author['xchan_name'] . '[/bdi][/zrl]'; $alink = '[zrl=' . $observer['xchan_url'] . '][bdi]' . $observer['xchan_name'] . '[/bdi][/zrl]'; - $plink = '[zrl=' . z_root() . '/display/' . gen_link_id($item['mid']) . ']' . $post_type . '[/zrl]'; + $plink = '[zrl=' . z_root() . '/display/' . $item['uuid'] . ']' . $post_type . '[/zrl]'; $allow_cid = $item['allow_cid']; $allow_gid = $item['allow_gid']; $deny_cid = $item['deny_cid']; @@ -532,7 +524,7 @@ class Like extends Controller { if ($obj_type === 'thing' && $r[0]['imgurl']) { $arr['body'] .= "\n\n[zmg=80x80]" . $r[0]['imgurl'] . '[/zmg]'; } - if ($obj_type === 'profile') { + if ($obj_type === 'Profile') { if ($public) { $arr['body'] .= "\n\n" . '[embed]' . z_root() . '/profile/' . $ch[0]['channel_address'] . '[/embed]'; } @@ -586,6 +578,7 @@ class Like extends Controller { Libsync::build_sync_packet($profile_uid, ['item' => [encode_item($sync_item[0], true)]]); } + if ($extended_like) { $r = q("insert into likes (channel_id,liker,likee,iid,i_mid,verb,target_type,target_id,target) values (%d,'%s','%s',%d,'%s','%s','%s','%s','%s')", intval($ch[0]['channel_id']), diff --git a/Zotlabs/Module/Linkinfo.php b/Zotlabs/Module/Linkinfo.php index 038c739d5..081966dba 100644 --- a/Zotlabs/Module/Linkinfo.php +++ b/Zotlabs/Module/Linkinfo.php @@ -291,11 +291,15 @@ class Linkinfo extends \Zotlabs\Web\Controller { // Check codepage in HTTP headers or HTML if not exist $cp = (preg_match('/Content-Type: text\/html; charset=(.+)\r\n/i', $header, $o) ? $o[1] : ''); - if(empty($cp)) - $cp = (preg_match('/meta.+content=["\']text\/html; charset=([^"\']+)/i', $body, $o) ? $o[1] : 'AUTO'); + if(empty($cp)) { + $cp = (preg_match('/meta.+content=["\']text\/html; charset=([^"\']+)/i', $body, $o) ? $o[1] : 'AUTO'); + } + + $body = mb_convert_encoding($body, 'UTF-8', $cp); - $body = mb_convert_encoding($body, 'UTF-8', $cp); - $body = mb_convert_encoding($body, 'HTML-ENTITIES', "UTF-8"); + // Handling HTML entities via mbstring is deprecated + //$body = mb_convert_encoding($body, 'HTML-ENTITIES', "UTF-8"); + $body = mb_encode_numericentity($body, [0x80, 0x10FFFF, 0, ~0], 'UTF-8'); $doc = new \DOMDocument(); @$doc->loadHTML($body); diff --git a/Zotlabs/Module/Mood.php b/Zotlabs/Module/Mood.php deleted file mode 100644 index edd3f0e1a..000000000 --- a/Zotlabs/Module/Mood.php +++ /dev/null @@ -1,163 +0,0 @@ -<?php -namespace Zotlabs\Module; - -use App; -use Zotlabs\Lib\Apps; -use Zotlabs\Web\Controller; - -require_once('include/security.php'); -require_once('include/bbcode.php'); -require_once('include/items.php'); - - - -class Mood extends Controller { - - function init() { - - if(! local_channel()) - return; - - if(! Apps::system_app_installed(local_channel(), 'Mood')) { - return; - } - - $uid = local_channel(); - $channel = App::get_channel(); - $verb = ((isset($_GET['verb'])) ? notags(trim($_GET['verb'])) : ''); - - if(! $verb) - return; - - $verbs = get_mood_verbs(); - - if(! array_key_exists($verb,$verbs)) - return; - - $activity = ACTIVITY_MOOD . '#' . urlencode($verb); - - $parent = ((x($_GET,'parent')) ? intval($_GET['parent']) : 0); - - - logger('mood: verb ' . $verb, LOGGER_DEBUG); - - - if($parent) { - $r = q("select mid, owner_xchan, private, allow_cid, allow_gid, deny_cid, deny_gid - from item where id = %d and parent = %d and uid = %d limit 1", - intval($parent), - intval($parent), - intval($uid) - ); - if(count($r)) { - $parent_mid = $r[0]['mid']; - $private = $r[0]['item_private']; - $allow_cid = $r[0]['allow_cid']; - $allow_gid = $r[0]['allow_gid']; - $deny_cid = $r[0]['deny_cid']; - $deny_gid = $r[0]['deny_gid']; - } - } - else { - - $private = 0; - - $allow_cid = $channel['channel_allow_cid']; - $allow_gid = $channel['channel_allow_gid']; - $deny_cid = $channel['channel_deny_cid']; - $deny_gid = $channel['channel_deny_gid']; - } - - $poster = App::get_observer(); - - $uuid = item_message_id(); - $mid = z_root() . '/item/' . $uuid; - - $action = sprintf( t('%1$s is %2$s','mood'), '[zrl=' . $poster['xchan_url'] . ']' . $poster['xchan_name'] . '[/zrl]' , $verbs[$verb]); - - $arr = array(); - - $arr['aid'] = get_account_id(); - $arr['uid'] = $uid; - $arr['uuid'] = $uuid; - $arr['mid'] = $mid; - $arr['parent_mid'] = (($parent_mid) ? $parent_mid : $mid); - $arr['author_xchan'] = $poster['xchan_hash']; - $arr['owner_xchan'] = (($parent_mid) ? $r[0]['owner_xchan'] : $poster['xchan_hash']); - $arr['title'] = ''; - $arr['allow_cid'] = $allow_cid; - $arr['allow_gid'] = $allow_gid; - $arr['deny_cid'] = $deny_cid; - $arr['deny_gid'] = $deny_gid; - $arr['item_private'] = $private; - $arr['verb'] = $activity; - $arr['body'] = $action; - $arr['item_origin'] = 1; - $arr['item_wall'] = 1; - $arr['item_unseen'] = 1; - if(! $parent_mid) - $item['item_thread_top'] = 1; - - if ((! $arr['plink']) && intval($arr['item_thread_top'])) { - $arr['plink'] = z_root() . '/channel/' . $channel['channel_address'] . '/?f=&mid=' . urlencode($arr['mid']); - } - - - $post = item_store($arr); - $item_id = $post['item_id']; - - if($item_id) { - \Zotlabs\Daemon\Master::Summon(array('Notifier','activity', $item_id)); - } - - call_hooks('post_local_end', $arr); - - if($_SESSION['return_url']) - goaway(z_root() . '/' . $_SESSION['return_url']); - - return; - } - - - - function get() { - - if(! local_channel()) { - notice( t('Permission denied.') . EOL); - return; - } - - if(! Apps::system_app_installed(local_channel(), 'Mood')) { - //Do not display any associated widgets at this point - App::$pdl = ''; - $papp = Apps::get_papp('Mood'); - return Apps::app_render($papp, 'module'); - } - - nav_set_selected('Mood'); - - $parent = ((x($_GET,'parent')) ? intval($_GET['parent']) : '0'); - - $verbs = get_mood_verbs(); - - $shortlist = array(); - foreach($verbs as $k => $v) - if($v !== 'NOTRANSLATION') - $shortlist[] = array($k,$v); - - - $tpl = get_markup_template('mood_content.tpl'); - - $o = replace_macros($tpl,array( - '$title' => t('Mood'), - '$desc' => t('Set your current mood and tell your friends'), - '$verbs' => $shortlist, - '$parent' => $parent, - '$submit' => t('Submit'), - )); - - return $o; - - } - -} diff --git a/Zotlabs/Module/Network.php b/Zotlabs/Module/Network.php index 4f8e2f4e4..3ea813547 100644 --- a/Zotlabs/Module/Network.php +++ b/Zotlabs/Module/Network.php @@ -275,7 +275,7 @@ class Network extends \Zotlabs\Web\Controller { $vnotify = get_pconfig(local_channel(), 'system', 'vnotify'); if(! ($vnotify & VNOTIFY_LIKE)) - $likes_sql = " AND verb NOT IN ('" . dbesc(ACTIVITY_LIKE) . "', '" . dbesc(ACTIVITY_DISLIKE) . "') "; + $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'", diff --git a/Zotlabs/Module/Owa.php b/Zotlabs/Module/Owa.php index e41435ecd..85467d4f4 100644 --- a/Zotlabs/Module/Owa.php +++ b/Zotlabs/Module/Owa.php @@ -63,10 +63,6 @@ class Owa extends Controller { if ($r) { foreach ($r as $hubloc) { - // fix friendica accept header for nginx - if (str_starts_with($keyId, 'acct:') && $_SERVER['HTTP_ACCEPT'] === 'application/x-zot+json') - $_SERVER['HTTP_ACCEPT'] = 'application/x-dfrn+json, application/x-zot+json'; - $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); diff --git a/Zotlabs/Module/Photos.php b/Zotlabs/Module/Photos.php index 6c73c411e..870a2cb79 100644 --- a/Zotlabs/Module/Photos.php +++ b/Zotlabs/Module/Photos.php @@ -1108,7 +1108,6 @@ class Photos extends \Zotlabs\Web\Controller { $conv_responses = array( 'like' => array('title' => t('Likes','title')),'dislike' => array('title' => t('Dislikes','title')), - 'agree' => array('title' => t('Agree','title')),'disagree' => array('title' => t('Disagree','title')), 'abstain' => array('title' => t('Abstain','title')), 'attendyes' => array('title' => t('Attending','title')), 'attendno' => array('title' => t('Not attending','title')), 'attendmaybe' => array('title' => t('Might attend','title')) ); @@ -1152,8 +1151,9 @@ class Photos extends \Zotlabs\Web\Controller { $template = $tpl; $sparkle = ''; - if(((activity_match($item['verb'],ACTIVITY_LIKE)) || (activity_match($item['verb'],ACTIVITY_DISLIKE))) && ($item['id'] != $item['parent'])) + if(activity_match($item['verb'], ['Like', 'Dislike', ACTIVITY_LIKE, ACTIVITY_DISLIKE]) && $item['id'] != $item['parent']) { continue; + } $redirect_url = z_root() . '/redir/' . $item['cid'] ; diff --git a/Zotlabs/Module/Pin.php b/Zotlabs/Module/Pin.php index f82327ce6..de3c75622 100644 --- a/Zotlabs/Module/Pin.php +++ b/Zotlabs/Module/Pin.php @@ -37,7 +37,7 @@ class Pin extends \Zotlabs\Web\Controller { http_status_exit(404, 'Not found'); } - $midb64 = gen_link_id($r[0]['mid']); + $midb64 = $r[0]['uuid']; $pinned = (in_array($midb64, get_pconfig($r[0]['uid'], 'pinned', $r[0]['item_type'], [])) ? true : false); switch(argv(1)) { diff --git a/Zotlabs/Module/Poke.php b/Zotlabs/Module/Poke.php deleted file mode 100644 index 30585bf3d..000000000 --- a/Zotlabs/Module/Poke.php +++ /dev/null @@ -1,207 +0,0 @@ -<?php -namespace Zotlabs\Module; /** @file */ - -use App; -use Zotlabs\Lib\Apps; -use Zotlabs\Lib\Activity; -use Zotlabs\Web\Controller; - -/** - * - * Poke, prod, finger, or otherwise do unspeakable things to somebody - who must be a connection in your address book - * This function can be invoked with the required arguments (verb and cid and private and possibly parent) silently via ajax or - * other web request. You must be logged in and connected to a channel. - * If the required arguments aren't present, we'll display a simple form to choose a recipient and a verb. - * parent is a special argument which let's you attach this activity as a comment to an existing conversation, which - * may have started with somebody else poking (etc.) somebody, but this isn't necessary. This can be used in the adult - * plugin version to have entire conversations where Alice poked Bob, Bob fingered Alice, Alice hugged Bob, etc. - * - * private creates a private conversation with the recipient. Otherwise your channel's default post privacy is used. - * - */ - -require_once('include/items.php'); - - -class Poke extends Controller { - - function init() { - - if(! local_channel()) - return; - - if(! Apps::system_app_installed(local_channel(), 'Poke')) { - return; - } - - $uid = local_channel(); - $channel = App::get_channel(); - - $verb = ((isset($_GET['verb'])) ? notags(trim($_GET['verb'])) : ''); - - if(! $verb) - return; - - $verbs = get_poke_verbs(); - - if(! array_key_exists($verb,$verbs)) - return; - - $activity = ACTIVITY_POKE . '#' . urlencode($verbs[$verb][0]); - - $contact_id = intval($_REQUEST['cid']); - - $xchan = trim($_REQUEST['xchan']); - - if(! ($contact_id || $xchan)) - return; - - $parent = ((x($_REQUEST,'parent')) ? intval($_REQUEST['parent']) : 0); - - logger('poke: verb ' . $verb . ' contact ' . $contact_id, LOGGER_DEBUG); - - - if($contact_id) { - $r = q("SELECT * FROM abook left join xchan on xchan_hash = abook_xchan where abook_id = %d and abook_channel = %d LIMIT 1", - intval($contact_id), - intval($uid) - ); - } - if($xchan) { - $r = q("SELECT * FROM xchan where xchan_hash like ( '%s' ) LIMIT 1", - dbesc($xchan . '%') - ); - } - - if(! $r) { - logger('poke: no target.'); - return; - } - - $target = $r[0]; - $parent_item = null; - - if($parent) { - $r = q("select mid, item_private, owner_xchan, allow_cid, allow_gid, deny_cid, deny_gid - from item where id = %d and parent = %d and uid = %d limit 1", - intval($parent), - intval($parent), - intval($uid) - ); - if($r) { - $parent_item = $r[0]; - $parent_mid = $r[0]['mid']; - $item_private = $r[0]['item_private']; - $allow_cid = $r[0]['allow_cid']; - $allow_gid = $r[0]['allow_gid']; - $deny_cid = $r[0]['deny_cid']; - $deny_gid = $r[0]['deny_gid']; - } - } - elseif($contact_id) { - - $item_private = ((x($_GET,'private')) ? intval($_GET['private']) : 0); - - $allow_cid = (($item_private) ? '<' . $target['abook_xchan']. '>' : $channel['channel_allow_cid']); - $allow_gid = (($item_private) ? '' : $channel['channel_allow_gid']); - $deny_cid = (($item_private) ? '' : $channel['channel_deny_cid']); - $deny_gid = (($item_private) ? '' : $channel['channel_deny_gid']); - } - - $arr['item_wall'] = 1; - $arr['owner_xchan'] = (($parent_item) ? $parent_item['owner_xchan'] : $channel['channel_hash']); - $arr['parent_mid'] = (($parent_mid) ? $parent_mid : ''); - $arr['title'] = ''; - $arr['allow_cid'] = $allow_cid; - $arr['allow_gid'] = $allow_gid; - $arr['deny_cid'] = $deny_cid; - $arr['deny_gid'] = $deny_gid; - $arr['verb'] = $activity; - $arr['item_private'] = $item_private; - $arr['obj_type'] = ACTIVITY_OBJ_NOTE; - $arr['body'] = '[zrl=' . $channel['xchan_url'] . ']' . $channel['xchan_name'] . '[/zrl]' . ' ' . t($verbs[$verb][0]) . ' ' . '[zrl=' . $target['xchan_url'] . ']' . $target['xchan_name'] . '[/zrl]'; - $arr['item_origin'] = 1; - $arr['item_unseen'] = 1; - if(! $parent_item) - $arr['item_thread_top'] = 1; - - $arr['obj'] = Activity::encode_item($arr); - - - post_activity_item($arr); - - return; - } - - - - function get() { - - if(! local_channel()) { - notice( t('Permission denied.') . EOL); - return; - } - - if(! Apps::system_app_installed(local_channel(), 'Poke')) { - //Do not display any associated widgets at this point - App::$pdl = ''; - $papp = Apps::get_papp('Poke'); - return Apps::app_render($papp, 'module'); - } - - nav_set_selected('Poke'); - - $name = ''; - $id = ''; - - if(isset($_REQUEST['c']) && intval($_REQUEST['c'])) { - $r = q("select abook_id, xchan_name from abook left join xchan on abook_xchan = xchan_hash - where abook_id = %d and abook_channel = %d limit 1", - intval($_REQUEST['c']), - intval(local_channel()) - ); - if($r) { - $name = $r[0]['xchan_name']; - $id = $r[0]['abook_id']; - } - } - - $parent = ((x($_REQUEST,'parent')) ? intval($_REQUEST['parent']) : '0'); - - $verbs = get_poke_verbs(); - - $shortlist = array(); - foreach($verbs as $k => $v) - if($v[1] !== 'NOTRANSLATION') - $shortlist[] = array($k,$v[1]); - - - $poke_basic = get_config('system','poke_basic'); - if($poke_basic) { - $title = t('Poke'); - $desc = t('Poke somebody'); - } - else { - $title = t('Poke'); - $desc = t('Poke or ping somebody'); - } - - $o = replace_macros(get_markup_template('poke_content.tpl'),array( - '$title' => $title, - '$poke_basic' => $poke_basic, - '$desc' => $desc, - '$clabel' => t('Recipient'), - '$choice' => t('Choose action'), - '$verbs' => $shortlist, - '$parent' => $parent, - '$prv_desc' => t('Make this post private'), - '$private' => array('private', t('Make this post private'), false, ''), - '$submit' => t('Submit'), - '$name' => $name, - '$id' => $id - )); - - return $o; - - } -} diff --git a/Zotlabs/Module/Profile_photo.php b/Zotlabs/Module/Profile_photo.php index d7e2bbce1..dc47d213b 100644 --- a/Zotlabs/Module/Profile_photo.php +++ b/Zotlabs/Module/Profile_photo.php @@ -223,7 +223,7 @@ class Profile_photo extends Controller { intval(local_channel()) ); - send_profile_photo_activity($channel, $base_image, $profile); + profile_activity([t('Profile Photo')], $base_image['resource_id']); } else { q("update profile set photo = '%s', thumb = '%s' where id = %d and uid = %d", @@ -269,7 +269,6 @@ class Profile_photo extends Controller { // Update directory in background Master::Summon(['Directory', $channel['channel_id']]); - } else notice(t('Unable to process image') . EOL); diff --git a/Zotlabs/Module/Profiles.php b/Zotlabs/Module/Profiles.php index ce496252b..15252d6e6 100644 --- a/Zotlabs/Module/Profiles.php +++ b/Zotlabs/Module/Profiles.php @@ -3,10 +3,6 @@ namespace Zotlabs\Module; use Zotlabs\Lib\Libsync; -require_once('include/channel.php'); -require_once('include/selectors.php'); - - class Profiles extends \Zotlabs\Web\Controller { function init() { @@ -492,7 +488,7 @@ class Profiles extends \Zotlabs\Web\Controller { $publish = ((x($_POST, 'profile_in_directory') && (intval($_POST['profile_in_directory']) == 1)) ? 1 : 0); - profile_activity($changes,$value); + profile_activity($changes, $value); } diff --git a/Zotlabs/Module/Pubstream.php b/Zotlabs/Module/Pubstream.php index 08de168cb..c40751fdc 100644 --- a/Zotlabs/Module/Pubstream.php +++ b/Zotlabs/Module/Pubstream.php @@ -40,7 +40,15 @@ class Pubstream extends \Zotlabs\Web\Controller { $site_firehose = ((intval(get_config('system','site_firehose',0))) ? true : false); - $mid = ((isset($_REQUEST['mid'])) ? unpack_link_id($_REQUEST['mid']) : ''); + $mid = $_REQUEST['mid'] ?? ''; + $identifier = 'uuid'; + $encoded_mid = null; + + if (str_starts_with($mid, 'b64.')) { + $encoded_mid = $mid; + $mid = unpack_link_id($mid); + $identifier = 'mid'; + } if ($mid === false) { notice(t('Malformed message id.') . EOL); @@ -108,9 +116,6 @@ class Pubstream extends \Zotlabs\Web\Controller { . "; var profile_page = " . \App::$pager['page'] . "; divmore_height = " . intval($maxheight) . "; </script>\r\n"; - //if we got a decoded hash we must encode it again before handing to javascript - $mid = gen_link_id($mid); - \App::$page['htmlhead'] .= replace_macros(get_markup_template("build_query.tpl"),array( '$baseurl' => z_root(), '$pgtype' => 'pubstream', @@ -136,7 +141,7 @@ class Pubstream extends \Zotlabs\Web\Controller { '$cats' => '', '$tags' => (($hashtags) ? urlencode($hashtags) : ''), '$dend' => '', - '$mid' => (($mid) ? urlencode($mid) : ''), + '$mid' => $encoded_mid ?? $mid, '$verb' => '', '$net' => (($net) ? urlencode($net) : ''), '$dbegin' => '' @@ -198,7 +203,7 @@ class Pubstream extends \Zotlabs\Web\Controller { $r = q("SELECT parent AS item_id FROM item left join abook on item.author_xchan = abook.abook_xchan $net_query - WHERE item.mid = '%s' and item.item_private = 0 + WHERE item.$identifier = '%s' and item.item_private = 0 $uids $site_firehose_sql $item_normal and (abook.abook_blocked = 0 or abook.abook_flags is null) @@ -225,7 +230,7 @@ class Pubstream extends \Zotlabs\Web\Controller { $r = q("SELECT parent AS item_id FROM item left join abook on item.author_xchan = abook.abook_xchan $net_query - WHERE item.mid = '%s' and item.item_private = 0 + WHERE item.$identifier = '%s' and item.item_private = 0 $uids $site_firehose_sql $item_normal_update $simple_update and (abook.abook_blocked = 0 or abook.abook_flags is null) $sql_extra $net_query2", diff --git a/Zotlabs/Module/React.php b/Zotlabs/Module/React.php index f80b04a3f..e04b9b257 100644 --- a/Zotlabs/Module/React.php +++ b/Zotlabs/Module/React.php @@ -2,82 +2,96 @@ namespace Zotlabs\Module; +use App; +use Zotlabs\Web\Controller; +use Zotlabs\Lib\Activity; +use Zotlabs\Daemon\Master; -class React extends \Zotlabs\Web\Controller { +class React extends Controller { function get() { - if(! local_channel()) + if (!local_channel()) { return; + } $sys = get_sys_channel(); - $channel = \App::get_channel(); + $channel = App::get_channel(); $postid = $_REQUEST['postid']; - if(! $postid) + if (!$postid) { return; + } - $emoji = $_REQUEST['emoji']; - - - if($_REQUEST['emoji']) { - - $i = q("select * from item where id = %d and uid = %d", - intval($postid), - intval(local_channel()) - ); - - if(! $i) { - $i = q("select * from item where id = %d and uid = %d", - intval($postid), - intval($sys['channel_id']) - ); - - if($i) { - $i = [ copy_of_pubitem($channel, $i[0]['mid']) ]; - $postid = (($i) ? $i[0]['id'] : 0); - } - } + $shortname = $_REQUEST['emoji']; - if(! $i) { - return; - } + $emojis = get_emojis(); - $uuid = item_message_id(); - - $n = array(); - $n['aid'] = $channel['channel_account_id']; - $n['uid'] = $channel['channel_id']; - $n['item_origin'] = true; - $n['item_type'] = $i[0]['item_type']; - $n['parent'] = $postid; - $n['parent_mid'] = $i[0]['mid']; - $n['uuid'] = $uuid; - $n['mid'] = z_root() . '/item/' . $uuid; - $n['verb'] = ACTIVITY_REACT . '#' . $emoji; - $n['body'] = "\n\n[zmg=32x32]" . z_root() . '/images/emoji/' . $emoji . '.png[/zmg]' . "\n\n"; - $n['author_xchan'] = $channel['channel_hash']; + if (!isset($emojis[$shortname])) { + return; + } - $n['tgt_type'] = 'Image'; - $n['target'] = [ - 'type' => 'Image', - 'name' => $emoji, - 'url' => z_root() . '/images/emoji/' . $emoji . '.png' - ]; + $emoji = $emojis[$shortname]; + if (!$emoji) { + return; + } - $x = item_store($n); + $i = q("select * from item where id = %d and uid = %d", + intval($postid), + intval(local_channel()) + ); - retain_item($postid); + if (!$i) { + $i = q("select * from item where id = %d and uid = %d", + intval($postid), + intval($sys['channel_id']) + ); - if($x['success']) { - $nid = $x['item_id']; - \Zotlabs\Daemon\Master::Summon(array('Notifier','like',$nid)); + if ($i) { + $i = [ copy_of_pubitem($channel, $i[0]['mid']) ]; + $postid = (($i) ? $i[0]['id'] : 0); } + } + if (!$i) { + return; } + $uuid = item_message_id(); + + $n['aid'] = $channel['channel_account_id']; + $n['uid'] = $channel['channel_id']; + $n['item_origin'] = true; + $n['item_type'] = $i[0]['item_type']; + $n['parent'] = $postid; + $n['parent_mid'] = $i[0]['mid']; + $n['uuid'] = $uuid; + $n['mid'] = z_root() . '/item/' . $uuid; + $n['verb'] = 'Create'; + $n['body'] = $emoji['shortname']; //'[img class="emoji single-emoji"]' . z_root() . '/' . $emoji['filepath'] . '[/img]'; + $n['author_xchan'] = $channel['channel_hash']; + // $n['obj'] = Activity::fetch_item(['id' => $i[0]['mid']]); + // $n['obj_type'] = ((array_path_exists('obj/type', $n)) ? $n['obj']['type'] : EMPTY_STR); + + $n['term'][] = [ + 'uid' => $channel['channel_id'], + 'ttype' => TERM_EMOJI, + 'otype' => TERM_OBJ_POST, + 'term' => $emoji['shortname'], + 'url' => z_root() . '/emoji/' . $shortname, + 'imgurl' => z_root() . '/' . $emoji['filepath'] + ]; + + $x = item_store($n); + + retain_item($postid); + + if ($x['success']) { + $nid = $x['item_id']; + Master::Summon(['Notifier', 'like', $nid]); + } } -}
\ No newline at end of file +} diff --git a/Zotlabs/Module/Search.php b/Zotlabs/Module/Search.php index 6b1060570..9511c0389 100644 --- a/Zotlabs/Module/Search.php +++ b/Zotlabs/Module/Search.php @@ -73,15 +73,15 @@ class Search extends Controller { $f = Libzot::fetch_conversation(App::get_channel(), punify($url), true); if ($f) { - $mid = $f[0]['message_id']; + $uuid = $f[0]['message_uuid']; foreach ($f as $m) { - if (str_starts_with($url, $m['message_id'])) { - $mid = $m['message_id']; + if ($url === $m['message_id']) { + $uuid = $m['message_uuid']; break; } } - goaway(z_root() . '/hq/' . gen_link_id($mid)); + goaway(z_root() . '/hq/' . $uuid); } else { // try other fetch providers (e.g. diaspora, pubcrawl) diff --git a/Zotlabs/Module/Setup.php b/Zotlabs/Module/Setup.php index 3a188d9ce..83faf85dc 100644 --- a/Zotlabs/Module/Setup.php +++ b/Zotlabs/Module/Setup.php @@ -491,6 +491,19 @@ class Setup extends \Zotlabs\Web\Controller { } $this->check_add($checks, t('Generate encryption keys'), $res, true, $help); + + $res = function_exists('sodium_crypto_sign_keypair'); + if (!$res) { + $help = t('Error: the sodium encryption library is not installed.') . EOL; + } + $this->check_add($checks, t('Generate ed25519 encryption keys'), $res, true, $help); + + $res1 = extension_loaded('bcmath'); + $res2 = extension_loaded('gmp'); + if (! ($res1 || $res2)) { + $help = t('Error: one of "bcmath" or "gmp" (bigmath library) extensions are required.') . EOL; + } + $this->check_add($checks, t('Bigmath library (either bcmath or gmp)'), $res1||$res2, $help); } /** @@ -514,6 +527,7 @@ class Setup extends \Zotlabs\Web\Controller { $this->check_add($ck_funcs, t('mb_string PHP module'), true, true); $this->check_add($ck_funcs, t('xml PHP module'), true, true); $this->check_add($ck_funcs, t('zip PHP module'), true, true); + $this->check_add($ck_funcs, t('intl PHP module'), true, true); if(function_exists('apache_get_modules')){ if(! in_array('mod_rewrite', apache_get_modules())) { @@ -570,6 +584,10 @@ class Setup extends \Zotlabs\Web\Controller { $ck_funcs[6]['status'] = false; $ck_funcs[6]['help'] = t('Error: zip PHP module required but not installed.'); } + if(! extension_loaded('intl')) { + $ck_funcs[6]['status'] = false; + $ck_funcs[6]['help'] = t('Error: intl PHP module required but not installed.'); + } $checks = array_merge($checks, $ck_funcs); } diff --git a/Zotlabs/Module/Share.php b/Zotlabs/Module/Share.php index 716f7229b..ea9313fa8 100644 --- a/Zotlabs/Module/Share.php +++ b/Zotlabs/Module/Share.php @@ -65,9 +65,7 @@ class Share extends \Zotlabs\Web\Controller { $item = $r[0]; - $owner_uid = $r[0]['uid']; - $owner_aid = $r[0]['aid']; - +/* $can_comment = false; if((array_key_exists('owner',$item)) && intval($item['owner']['abook_self'])) $can_comment = perm_is_allowed($item['uid'],$observer['xchan_hash'],'post_comments'); @@ -78,7 +76,7 @@ class Share extends \Zotlabs\Web\Controller { notice( t('Permission denied') . EOL); killme(); } - +*/ $r = q("select * from xchan where xchan_hash = '%s' limit 1", dbesc($item['owner_xchan']) ); @@ -96,25 +94,39 @@ class Share extends \Zotlabs\Web\Controller { else killme(); - - $arr['aid'] = $owner_aid; - $arr['uid'] = $owner_uid; + $arr['aid'] = $item['aid']; + $arr['uid'] = $item['uid']; $arr['item_origin'] = 1; $arr['item_wall'] = $item['item_wall']; + $arr['item_private'] = $item['item_private']; $arr['uuid'] = item_message_id(); $arr['mid'] = z_root() . '/activity/' . $arr['uuid']; - $arr['parent_mid'] = $item['mid']; + $arr['parent_mid'] = $item['parent_mid']; + $arr['thr_parent'] = $item['mid']; + + $created = datetime_convert(); + + $arr['created'] = $created; + $arr['edited'] = $created; + $arr['commented'] = $created; + $arr['received'] = $created; + $arr['changed'] = $created; + $arr['item_type'] = ITEM_TYPE_POST; $mention = '@[zrl=' . $item['author']['xchan_url'] . ']' . $item['author']['xchan_name'] . '[/zrl]'; $arr['body'] = sprintf( t('🔁 Repeated %1$s\'s %2$s'), $mention, Activity::activity_obj_mapper($item['obj_type'])); $arr['author_xchan'] = $channel['channel_hash']; - $arr['owner_xchan'] = $item['author_xchan']; - $arr['obj'] = Activity::encode_item($item); + $arr['owner_xchan'] = $item['author_xchan']; + $arr['source_xchan'] = ''; + + $arr['obj'] = $item['obj']; $arr['obj_type'] = $item['obj_type']; $arr['verb'] = ACTIVITY_SHARE; + call_hooks('post_local', $arr); + $post = item_store($arr); $post_id = $post['item_id']; @@ -123,7 +135,7 @@ class Share extends \Zotlabs\Web\Controller { call_hooks('post_local_end', $arr); - info( t('Post repeated') . EOL); + // info( t('Post repeated') . EOL); $r = q("select * from item where id = %d", intval($post_id) diff --git a/Zotlabs/Module/Sharedwithme.php b/Zotlabs/Module/Sharedwithme.php index c294079d4..6489959f0 100644 --- a/Zotlabs/Module/Sharedwithme.php +++ b/Zotlabs/Module/Sharedwithme.php @@ -40,7 +40,7 @@ class Sharedwithme extends Controller { //drop all files - localuser if((argc() > 1) && (argv(1) === 'dropall')) { - $r = q("SELECT id FROM item WHERE verb = '%s' AND obj_type IN ('Document', 'Video', 'Audio', 'Image') AND uid = %d AND owner_xchan != '%s' $item_normal", + $r = q("SELECT id FROM item WHERE (verb = 'Create' OR verb = '%s') AND obj_type IN ('Document', 'Video', 'Audio', 'Image') AND uid = %d AND owner_xchan != '%s' $item_normal", dbesc(ACTIVITY_POST), intval(local_channel()), dbesc($channel['channel_hash']) @@ -56,7 +56,7 @@ class Sharedwithme extends Controller { } //list files - $r = q("SELECT id, uid, obj, item_unseen FROM item WHERE verb = '%s' AND obj_type IN ('Document', 'Video', 'Audio', 'Image') AND uid = %d AND owner_xchan != '%s' $item_normal", + $r = q("SELECT id, uid, obj, item_unseen FROM item WHERE (verb = 'Create' OR verb = '%s') AND obj_type IN ('Document', 'Video', 'Audio', 'Image') AND uid = %d AND owner_xchan != '%s' $item_normal", dbesc(ACTIVITY_POST), intval(local_channel()), dbesc($channel['channel_hash']) diff --git a/Zotlabs/Module/Smilies.php b/Zotlabs/Module/Smilies.php index efac07f84..7dde8c834 100644 --- a/Zotlabs/Module/Smilies.php +++ b/Zotlabs/Module/Smilies.php @@ -4,18 +4,8 @@ namespace Zotlabs\Module; class Smilies extends \Zotlabs\Web\Controller { - function get() { - if (\App::$argv[1]==="json"){ - $tmp = list_smilies(); - $results = array(); - for($i = 0; $i < count($tmp['texts']); $i++) { - $results[] = array('text' => $tmp['texts'][$i], 'icon' => $tmp['icons'][$i]); - } - json_return_and_die($results); - } - else { - return smilies('',true); - } + function init() { + json_return_and_die(get_emojis()); } - + } diff --git a/Zotlabs/Module/Sse_bs.php b/Zotlabs/Module/Sse_bs.php index e4cb1c822..9fb295c4b 100644 --- a/Zotlabs/Module/Sse_bs.php +++ b/Zotlabs/Module/Sse_bs.php @@ -120,13 +120,17 @@ class Sse_bs extends Controller { $mids = []; $str = ''; + $slice = 0; - $mids_all_json = Cache::get('sse_mids_all_' . session_id()); + $mids_all = isset($_SESSION['sse_mids_all']) ? unserialise($_SESSION['sse_mids_all']) : []; - if (!$mids_all_json) - $mids_all_json = '[]'; + if (count($mids_all) > 3000) { + $slice = count($mids_all) - 3000; + } - $mids_all = json_decode($mids_all_json, true); + if ($slice) { + $mids_all = array_slice($mids_all, $slice); + } foreach($arr as $a) { $mid_str = '\'' . dbesc(unpack_link_id($a)) . '\''; @@ -137,7 +141,7 @@ class Sse_bs extends Controller { } } - Cache::set('sse_mids_all_' . session_id(), json_encode($mids_all)); + $_SESSION['sse_mids_all'] = serialise($mids_all); if(! self::$uid) { return; @@ -149,7 +153,7 @@ class Sse_bs extends Controller { call_hooks('update_unseen',$x); if($x['update'] === 'unset' || intval($x['update'])) { - q("UPDATE item SET item_unseen = 0 WHERE uid = %d AND mid in (%s) AND item_unseen = 1", + 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 ); @@ -177,10 +181,10 @@ class Sse_bs extends Controller { $sql_extra = ''; if (!(self::$vnotify & VNOTIFY_LIKE)) { - $sql_extra = " AND verb NOT IN ('" . dbesc(ACTIVITY_LIKE) . "', '" . dbesc(ACTIVITY_DISLIKE) . "') "; + $sql_extra = " AND verb NOT IN ('Like', 'Dislike', '" . dbesc(ACTIVITY_LIKE) . "', '" . dbesc(ACTIVITY_DISLIKE) . "') "; } elseif (!feature_enabled(self::$uid, 'dislike')) { - $sql_extra = " AND verb NOT IN ('" . dbesc(ACTIVITY_DISLIKE) . "') "; + $sql_extra = " AND verb NOT IN ('Dislike', '" . dbesc(ACTIVITY_DISLIKE) . "') "; } $sql_extra2 = ''; @@ -189,8 +193,8 @@ class Sse_bs extends Controller { $item_normal = item_normal(); - // Filter FEP-5624 approvals for comments and internal follow activities - $item_normal .= " AND verb NOT IN ('Add', 'Remove', '" . dbesc(ACTIVITY_TAG) . "', '" . dbesc(ACTIVITY_ATTEND) . "', 'Accept', '" . dbesc(ACTIVITY_ATTENDNO) . "', 'Reject', '" . dbesc(ACTIVITY_FOLLOW) . "') "; + // Filter internal follow activities and strerams add/remove activities + $item_normal .= " AND verb NOT IN ('Add', 'Remove', 'Follow', 'Ignore', '" . dbesc(ACTIVITY_FOLLOW) . "') "; if ($notifications) { $items = q("SELECT * FROM item @@ -260,10 +264,10 @@ class Sse_bs extends Controller { $sql_extra = ''; if (!(self::$vnotify & VNOTIFY_LIKE)) { - $sql_extra = " AND verb NOT IN ('" . dbesc(ACTIVITY_LIKE) . "', '" . dbesc(ACTIVITY_DISLIKE) . "') "; + $sql_extra = " AND verb NOT IN ('Like', 'Dislike', '" . dbesc(ACTIVITY_LIKE) . "', '" . dbesc(ACTIVITY_DISLIKE) . "') "; } elseif (!feature_enabled(self::$uid, 'dislike')) { - $sql_extra = " AND verb NOT IN ('" . dbesc(ACTIVITY_DISLIKE) . "') "; + $sql_extra = " AND verb NOT IN ('Dislike', '" . dbesc(ACTIVITY_DISLIKE) . "') "; } $sql_extra2 = ''; @@ -272,8 +276,8 @@ class Sse_bs extends Controller { $item_normal = item_normal(); - // Filter FEP-5624 approvals for comments and internal follow activities - $item_normal .= " AND verb NOT IN ('Add', 'Remove', '" . dbesc(ACTIVITY_TAG) . "', '" . dbesc(ACTIVITY_ATTEND) . "', 'Accept', '" . dbesc(ACTIVITY_ATTENDNO) . "', 'Reject', '" . dbesc(ACTIVITY_FOLLOW) . "') "; + // Filter internal follow activities and strerams add/remove activities + $item_normal .= " AND verb NOT IN ('Add', 'Remove', 'Follow', 'Ignore', '" . dbesc(ACTIVITY_FOLLOW) . "') "; if ($notifications) { $items = q("SELECT * FROM item @@ -342,10 +346,10 @@ class Sse_bs extends Controller { $sql_extra = ''; if (!(self::$vnotify & VNOTIFY_LIKE)) { - $sql_extra = " AND verb NOT IN ('" . dbesc(ACTIVITY_LIKE) . "', '" . dbesc(ACTIVITY_DISLIKE) . "') "; + $sql_extra = " AND verb NOT IN ('Like', 'Dislike', '" . dbesc(ACTIVITY_LIKE) . "', '" . dbesc(ACTIVITY_DISLIKE) . "') "; } elseif (!feature_enabled(self::$uid, 'dislike')) { - $sql_extra = " AND verb NOT IN ('" . dbesc(ACTIVITY_DISLIKE) . "') "; + $sql_extra = " AND verb NOT IN ('Dislike', '" . dbesc(ACTIVITY_DISLIKE) . "') "; } $sql_extra2 = ''; @@ -355,8 +359,8 @@ class Sse_bs extends Controller { $item_normal = item_normal(); - // Filter FEP-5624 approvals for comments and internal follow activities - $item_normal .= " AND verb NOT IN ('Add', 'Remove', '" . dbesc(ACTIVITY_TAG) . "', '" . dbesc(ACTIVITY_ATTEND) . "', 'Accept', '" . dbesc(ACTIVITY_ATTENDNO) . "', 'Reject', '" . dbesc(ACTIVITY_FOLLOW) . "') "; + // Filter internal follow activities and strerams add/remove activities + $item_normal .= " AND verb NOT IN ('Add', 'Remove', 'Follow', 'Ignore', '" . dbesc(ACTIVITY_FOLLOW) . "') "; if ($notifications) { $items = q("SELECT * FROM item @@ -437,10 +441,10 @@ class Sse_bs extends Controller { $sys = get_sys_channel(); $sql_extra = ''; if (!(self::$vnotify & VNOTIFY_LIKE)) { - $sql_extra = " AND verb NOT IN ('" . dbesc(ACTIVITY_LIKE) . "', '" . dbesc(ACTIVITY_DISLIKE) . "') "; + $sql_extra = " AND verb NOT IN ('Like', 'Dislike', '" . dbesc(ACTIVITY_LIKE) . "', '" . dbesc(ACTIVITY_DISLIKE) . "') "; } elseif (!feature_enabled(self::$uid, 'dislike')) { - $sql_extra = " AND verb NOT IN ('" . dbesc(ACTIVITY_DISLIKE) . "') "; + $sql_extra = " AND verb NOT IN ('Dislike', '" . dbesc(ACTIVITY_DISLIKE) . "') "; } $sql_extra2 = ''; @@ -448,10 +452,9 @@ class Sse_bs extends Controller { $sql_extra2 = " AND CASE WHEN verb = '" . ACTIVITY_SHARE . "' THEN owner_xchan ELSE author_xchan END IN (" . self::$xchans . ") "; $sql_extra3 = ''; - $sse_mids_all_json = Cache::get('sse_mids_all_' . session_id()); - if ($sse_mids_all_json) { - $sse_mids_all = json_decode($sse_mids_all_json, true); - $sql_extra3 = " AND mid NOT IN (" . protect_sprintf(implode(',', $sse_mids_all)) . ") "; + $sse_mids_all = unserialise($_SESSION['sse_mids_all']) ?? []; + if ($sse_mids_all) { + $sql_extra3 = " AND uuid NOT IN (" . protect_sprintf(implode(',', $sse_mids_all)) . ") "; } $uids = " AND uid IN ( " . $sys['channel_id'] . " ) "; @@ -463,8 +466,8 @@ class Sse_bs extends Controller { $item_normal = item_normal(); - // Filter FEP-5624 approvals for comments and internal follow activities - $item_normal .= " AND verb NOT IN ('Add', 'Remove', '" . dbesc(ACTIVITY_TAG) . "', '" . dbesc(ACTIVITY_ATTEND) . "', 'Accept', '" . dbesc(ACTIVITY_ATTENDNO) . "', 'Reject', '" . dbesc(ACTIVITY_FOLLOW) . "') "; + // Filter internal follow activities and strerams add/remove activities + $item_normal .= " AND verb NOT IN ('Add', 'Remove', 'Follow', 'Ignore', '" . dbesc(ACTIVITY_FOLLOW) . "') "; if ($notifications) { $items = q("SELECT * FROM item @@ -589,7 +592,7 @@ class Sse_bs extends Controller { $sql_extra = ''; if(! (self::$vnotify & VNOTIFY_LIKE)) - $sql_extra = " AND verb NOT IN ('" . dbesc(ACTIVITY_LIKE) . "', '" . dbesc(ACTIVITY_DISLIKE) . "') "; + $sql_extra = " AND verb NOT IN ('Like', 'Dislike', '" . dbesc(ACTIVITY_LIKE) . "', '" . dbesc(ACTIVITY_DISLIKE) . "') "; $fcount = count($forums); $i = 0; @@ -659,11 +662,11 @@ class Sse_bs extends Controller { $item_normal = item_normal(); - // Filter FEP-5624 approvals for comments and internal follow activities - $item_normal .= " AND verb NOT IN ('Add', 'Remove', '" . dbesc(ACTIVITY_TAG) . "', '" . dbesc(ACTIVITY_ATTEND) . "', 'Accept', '" . dbesc(ACTIVITY_ATTENDNO) . "', 'Reject', '" . dbesc(ACTIVITY_FOLLOW) . "') "; + // Filter internal follow activities and strerams add/remove activities + $item_normal .= " AND verb NOT IN ('Add', 'Remove', 'Follow', 'Ignore', '" . dbesc(ACTIVITY_FOLLOW) . "') "; $r = q("SELECT * FROM item - WHERE verb = '%s' + WHERE (verb = 'Create' OR verb = '%s') AND obj_type IN ('Document', 'Video', 'Audio', 'Image') AND uid = %d AND author_xchan != '%s' diff --git a/Zotlabs/Module/Subthread.php b/Zotlabs/Module/Subthread.php index a796d85cb..b927ee480 100644 --- a/Zotlabs/Module/Subthread.php +++ b/Zotlabs/Module/Subthread.php @@ -24,9 +24,9 @@ class Subthread extends \Zotlabs\Web\Controller { $item_id = ((argc() > 2) ? notags(trim(argv(2))) : 0); if(argv(1) === 'sub') - $activity = ACTIVITY_FOLLOW; + $activity = 'Follow'; elseif(argv(1) === 'unsub') - $activity = ACTIVITY_UNFOLLOW; + $activity = 'Ignore'; $i = q("select * from item where id = %d and uid = %d", @@ -106,16 +106,13 @@ class Subthread extends \Zotlabs\Web\Controller { else killme(); - - - $uuid = item_message_id(); $mid = z_root() . '/item/' . $uuid; $post_type = (($item['resource_type'] === 'photo') ? t('photo') : t('status')); $links = array(array('rel' => 'alternate','type' => 'text/html', 'href' => $item['plink'])); - $objtype = (($item['resource_type'] === 'photo') ? ACTIVITY_OBJ_PHOTO : ACTIVITY_OBJ_NOTE ); + $objtype = (($item['resource_type'] === 'photo') ? 'Image' : 'Note'); $body = $item['body']; @@ -124,9 +121,9 @@ class Subthread extends \Zotlabs\Web\Controller { if(! intval($item['item_thread_top'])) $post_type = 'comment'; - if($activity === ACTIVITY_FOLLOW) + if($activity === 'Follow') $bodyverb = t('%1$s is following %2$s\'s %3$s'); - if($activity === ACTIVITY_UNFOLLOW) + if($activity === 'Ignore') $bodyverb = t('%1$s stopped following %2$s\'s %3$s'); $arr = array(); @@ -149,7 +146,7 @@ class Subthread extends \Zotlabs\Web\Controller { $ulink = '[zrl=' . $item_author['xchan_url'] . ']' . $item_author['xchan_name'] . '[/zrl]'; $alink = '[zrl=' . $observer['xchan_url'] . ']' . $observer['xchan_name'] . '[/zrl]'; - $plink = '[zrl=' . z_root() . '/display/' . gen_link_id($item['mid']) . ']' . $post_type . '[/zrl]'; + $plink = '[zrl=' . z_root() . '/display/' . $item['uuid'] . ']' . $post_type . '[/zrl]'; $arr['body'] = sprintf( $bodyverb, $alink, $ulink, $plink ); diff --git a/Zotlabs/Module/Tagger.php b/Zotlabs/Module/Tagger.php index 4aaae5885..b6067be5e 100644 --- a/Zotlabs/Module/Tagger.php +++ b/Zotlabs/Module/Tagger.php @@ -67,15 +67,15 @@ class Tagger extends \Zotlabs\Web\Controller { switch($item['resource_type']) { case 'photo': - $targettype = ACTIVITY_OBJ_PHOTO; + $targettype = 'Image'; $post_type = t('photo'); break; case 'event': - $targettype = ACTIVITY_OBJ_EVENT; + $targettype = 'Event'; $post_type = t('event'); break; default: - $targettype = ACTIVITY_OBJ_NOTE; + $targettype = 'Note'; $post_type = t('post'); if($item['mid'] != $item['parent_mid']) $post_type = t('comment'); @@ -86,7 +86,7 @@ class Tagger extends \Zotlabs\Web\Controller { $clean_term = trim($term,'"\' '); $links = array(array('rel' => 'alternate','type' => 'text/html', - 'href' => z_root() . '/display/' . gen_link_id($item['mid']))); + 'href' => z_root() . '/display/' . $item['uuid'])); $target = json_encode(array( 'type' => $targettype, diff --git a/Zotlabs/Module/Thing.php b/Zotlabs/Module/Thing.php index b065b0022..2038db8c0 100644 --- a/Zotlabs/Module/Thing.php +++ b/Zotlabs/Module/Thing.php @@ -5,27 +5,50 @@ namespace Zotlabs\Module; +use App; use Zotlabs\Lib\Libsync; - -require_once('include/items.php'); -require_once('include/security.php'); -require_once('include/selectors.php'); -require_once('include/acl_selectors.php'); +use Zotlabs\Lib\Activity; +use Zotlabs\Lib\ActivityStreams; +use Zotlabs\Lib\Libzot; class Thing extends \Zotlabs\Web\Controller { function init() { + if (argv(1) && ActivityStreams::is_as_request()) { + $r = q("select obj_channel from obj where obj_type = %d and obj_obj = '%s' limit 1", + intval(TERM_OBJ_THING), + dbesc(argv(1)) + ); - if(! local_channel()) - return; + if (!$r) { + http_status_exit(404, 'Not found'); + } - $channel = \App::get_channel(); + $sql_extra = permissions_sql($r[0]['obj_channel']); + + $r = q("select * from obj where obj_type = %d and obj_obj = '%s' $sql_extra limit 1", + intval(TERM_OBJ_THING), + dbesc(argv(1)) + ); + + if ($r) { + $channel = channelx_by_n($r[0]['obj_channel']); + as_return_and_die(Activity::fetch_thing(['id' => $r[0]['obj_obj']]), $channel); + } + + http_status_exit(404, 'Not found'); - if($_SERVER['REQUEST_METHOD'] === 'GET' && argc() < 2) { - profile_load($channel['channel_address']); } + } + + + function post() { + + if(! local_channel()) + return; + $channel = \App::get_channel(); $term_hash = (($_REQUEST['term_hash']) ? $_REQUEST['term_hash'] : ''); @@ -36,7 +59,7 @@ class Thing extends \Zotlabs\Web\Controller { $url = $_REQUEST['url']; $photo = $_REQUEST['img']; - $hash = random_string(); + $hash = new_uuid(); $verbs = obj_verbs(); @@ -77,13 +100,7 @@ class Thing extends \Zotlabs\Web\Controller { return; $acl = new \Zotlabs\Access\AccessList($channel); - - 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); - } + $acl->set_from_array($_REQUEST); $x = $acl->get(); @@ -181,28 +198,20 @@ class Thing extends \Zotlabs\Web\Controller { intval(local_channel()), dbesc($hash) ); + if($r) { Libsync::build_sync_packet(0, array('obj' => $r)); } if($activity) { - $arr = array(); - $links = array(array('rel' => 'alternate','type' => 'text/html', 'href' => $url)); - if($local_photo) - $links[] = array('rel' => 'photo', 'type' => $local_photo_type, 'href' => $local_photo); - $objtype = ACTIVITY_OBJ_THING; - - $obj = json_encode(array( - 'type' => $objtype, - 'id' => $url, - 'link' => $links, - 'title' => $name, - 'content' => $name - )); + $obj = Activity::fetch_thing(['id' => $r[0]['obj_obj']]); $bodyverb = str_replace('OBJ: ', '',t('OBJ: %1$s %2$s %3$s')); + $arr['uuid'] = $r[0]['obj_obj']; + $arr['mid'] = z_root() . '/thing/' . $arr['uuid']; + $arr['owner_xchan'] = $channel['channel_hash']; $arr['author_xchan'] = $channel['channel_hash']; @@ -213,29 +222,38 @@ class Thing extends \Zotlabs\Web\Controller { $ulink = '[zrl=' . $channel['xchan_url'] . ']' . $channel['channel_name'] . '[/zrl]'; $plink = '[zrl=' . $url . ']' . $name . '[/zrl]'; - $arr['body'] = sprintf( $bodyverb, $ulink, $translated_verb, $plink ); + $arr['title'] = $channel['channel_name'] . ' ' . $translated_verb . ' ' . $name; + $arr['body'] = $url; if($local_photo) - $arr['body'] .= "\n\n[zmg]" . $local_photo . "[/zmg]"; + $arr['body'] = '[zrl=' . $url . '][zmg=' . $local_photo . ']' . $name . '[/zmg][/zrl]'; - $arr['verb'] = $verb; - $arr['obj_type'] = $objtype; + $arr['verb'] = 'Create'; + $arr['obj_type'] = 'Page'; $arr['obj'] = $obj; - if(! $profile['is_default']) { + $arr['allow_cid'] = $x['allow_cid']; + $arr['allow_gid'] = $x['allow_gid']; + $arr['deny_cid'] = $x['deny_cid']; + $arr['deny_gid'] = $x['deny_gid']; + + if (!$profile['is_default']) { $arr['item_private'] = true; - $str = ''; + $r = q("select abook_xchan from abook where abook_channel = %d and abook_profile = '%s'", intval(local_channel()), dbesc($profile_guid) ); + if($r) { $arr['allow_cid'] = ''; - foreach($r as $rr) + foreach($r as $rr) { $arr['allow_cid'] .= '<' . $rr['abook_xchan'] . '>'; + } } - else + else { $arr['allow_cid'] = '<' . get_observer_hash() . '>'; + } } $ret = post_activity_item($arr); @@ -254,21 +272,30 @@ class Thing extends \Zotlabs\Web\Controller { intval(TERM_OBJ_THING), dbesc(argv(1)) ); - if($r) - $sql_extra = permissions_sql($r[0]['obj_channel']); + + if (!$r) { + notice( t('item not found.') . EOL); + return; + } + + $sql_extra = permissions_sql($r[0]['obj_channel']); $r = q("select * from obj where obj_type = %d and obj_obj = '%s' $sql_extra limit 1", intval(TERM_OBJ_THING), dbesc(argv(1)) ); - if($r) { + if ($r) { + $channel = channelx_by_n($r[0]['obj_channel']); + profile_load($channel['channel_address']); + return replace_macros(get_markup_template('show_thing.tpl'), array( - '$header' => t('Show Thing'), + '$header' => $channel['xchan_name'] . ' ' . $r[0]['obj_verb'] . ' ' . $r[0]['obj_term'], '$edit' => t('Edit'), '$delete' => t('Delete'), '$canedit' => ((local_channel() && local_channel() == $r[0]['obj_channel']) ? true : false), - '$thing' => $r[0] )); + '$thing' => $r[0] + )); } else { notice( t('item not found.') . EOL); @@ -283,6 +310,8 @@ class Thing extends \Zotlabs\Web\Controller { return; } + profile_load($channel['channel_address']); + $acl = new \Zotlabs\Access\AccessList($channel); $channel_acl = $acl->get(); @@ -319,7 +348,7 @@ class Thing extends \Zotlabs\Web\Controller { '$img_lbl' => t('URL for photo of thing (optional)'), '$imgurl' => $r[0]['obj_imgurl'], '$permissions' => t('Permissions'), - '$aclselect' => populate_acl($channel_acl,false), + '$aclselect' => populate_acl($channel_acl, false, \Zotlabs\Lib\PermissionDescription::fromGlobalPermission('view_pages')), '$allow_cid' => acl2json($channel_acl['allow_cid']), '$allow_gid' => acl2json($channel_acl['allow_gid']), '$deny_cid' => acl2json($channel_acl['deny_cid']), @@ -344,7 +373,6 @@ class Thing extends \Zotlabs\Web\Controller { return ''; } - delete_thing_photo($r[0]['obj_imgurl'],get_observer_hash()); $x = q("delete from obj where obj_obj = '%s' and obj_type = %d and obj_channel = %d", @@ -372,7 +400,7 @@ class Thing extends \Zotlabs\Web\Controller { '$url_lbl' => t('URL of thing (optional)'), '$img_lbl' => t('URL for photo of thing (optional)'), '$permissions' => t('Permissions'), - '$aclselect' => populate_acl($channel_acl,false), + '$aclselect' => populate_acl($channel_acl, false, \Zotlabs\Lib\PermissionDescription::fromGlobalPermission('view_pages')), '$allow_cid' => acl2json($channel_acl['allow_cid']), '$allow_gid' => acl2json($channel_acl['allow_gid']), '$deny_cid' => acl2json($channel_acl['deny_cid']), diff --git a/Zotlabs/Module/Vote.php b/Zotlabs/Module/Vote.php index 870fd760c..06c47f91f 100644 --- a/Zotlabs/Module/Vote.php +++ b/Zotlabs/Module/Vote.php @@ -98,7 +98,7 @@ class Vote extends Controller { // now reset the placeholders - $item['verb'] = ACTIVITY_POST; + $item['verb'] = 'Create'; $item['obj_type'] = 'Answer'; unset($item['author']); diff --git a/Zotlabs/Render/Theme.php b/Zotlabs/Render/Theme.php index 2e6ca0c32..543bf7a3f 100644 --- a/Zotlabs/Render/Theme.php +++ b/Zotlabs/Render/Theme.php @@ -3,7 +3,7 @@ namespace Zotlabs\Render; use App; - +use Zotlabs\Lib\PConfig; class Theme { @@ -26,8 +26,8 @@ class Theme { */ static public function current(){ - self::$system_theme = ((isset(\App::$config['system']['theme'])) - ? \App::$config['system']['theme'] : ''); + self::$system_theme = ((isset(App::$config['system']['theme'])) + ? App::$config['system']['theme'] : ''); self::$session_theme = ((isset($_SESSION) && x($_SESSION, 'theme')) ? $_SESSION['theme'] : self::$system_theme); @@ -35,7 +35,7 @@ class Theme { // Find the theme that belongs to the channel whose stuff we are looking at - if(\App::$profile_uid) { + if(App::$profile_uid) { $r = q("select channel_theme from channel where channel_id = %d limit 1", intval(\App::$profile_uid) ); @@ -46,8 +46,9 @@ class Theme { // Themes from Comanche layouts over-ride the channel theme - if(array_key_exists('theme', \App::$layout) && \App::$layout['theme']) - $page_theme = \App::$layout['theme']; + if(array_key_exists('theme', \App::$layout) && \App::$layout['theme']) { + $page_theme = App::$layout['theme']; + } $chosen_theme = self::$session_theme; @@ -96,14 +97,11 @@ class Theme { * * Provide a sane default if nothing is chosen or the specified theme does not exist. * - * @param bool $installing (optional) default false, if true return the name of the first base theme - * * @return string */ - static public function url($installing = false) { + static public function url() { - if($installing) - return self::$base_themes[0]; + $uid = App::$profile_uid ?: local_channel(); $theme = self::current(); @@ -111,16 +109,23 @@ class Theme { $s = ((count($theme) > 1) ? $theme[1] : ''); $opts = ''; - $opts = ((\App::$profile_uid) ? '?f=&puid=' . \App::$profile_uid : ''); + $opts = (($uid) ? '?puid=' . $uid : ''); - $schema_str = ((x(\App::$layout,'schema')) ? '&schema=' . App::$layout['schema'] : ''); + $schema_str = ((x(App::$layout,'schema')) ? '&schema=' . App::$layout['schema'] : ''); if(($s) && (! $schema_str)) $schema_str = '&schema=' . $s; $opts .= $schema_str; + if ($uid) { + $timestamp = PConfig::Get($uid, 'system', 'style_update', false); + if ($timestamp) { + $opts .= '&updt=' . $timestamp; + } + } + if(file_exists('view/theme/' . $t . '/php/style.php')) - return('/view/theme/' . $t . '/php/style.pcss' . $opts); + return('/view/theme/' . $t . '/php/style.css' . $opts); return('/view/theme/' . $t . '/css/style.css'); } diff --git a/Zotlabs/Storage/BasicAuth.php b/Zotlabs/Storage/BasicAuth.php index d23f3d848..3fd1bcc1f 100644 --- a/Zotlabs/Storage/BasicAuth.php +++ b/Zotlabs/Storage/BasicAuth.php @@ -34,6 +34,14 @@ class BasicAuth extends DAV\Auth\Backend\AbstractBasic { * @var int $channel_id */ public $channel_id = 0; + + /** + * @brief channel_account_id of the current channel of the logged-in account. + * + * @var int $channel_account_id + */ + public $channel_account_id = 0; + /** * @brief channel_hash of the current channel of the logged-in account. * @@ -127,6 +135,7 @@ class BasicAuth extends DAV\Auth\Backend\AbstractBasic { */ protected function setAuthenticated($channel) { $this->channel_name = $channel['channel_address']; + $this->channel_account_id = $channel['channel_account_id']; $this->channel_id = $channel['channel_id']; $this->channel_hash = $this->observer = $channel['channel_hash']; @@ -139,8 +148,8 @@ class BasicAuth extends DAV\Auth\Backend\AbstractBasic { } } - $_SESSION['uid'] = $channel['channel_id']; - $_SESSION['account_id'] = $channel['channel_account_id']; + $_SESSION['uid'] = $this->channel_id; + $_SESSION['account_id'] = $this->channel_account_id; $_SESSION['authenticated'] = true; return true; } diff --git a/Zotlabs/Storage/Directory.php b/Zotlabs/Storage/Directory.php index 683887b31..333251f69 100644 --- a/Zotlabs/Storage/Directory.php +++ b/Zotlabs/Storage/Directory.php @@ -173,7 +173,7 @@ class Directory extends DAV\Node implements DAV\ICollection, DAV\IQuota, DAV\IMo intval($this->auth->owner_id) ); - $x = attach_syspaths($this->auth->owner_id,$this->folder_hash); + $x = attach_syspaths($this->auth->owner_id, $this->folder_hash); $y = q("update attach set display_path = '%s' where hash = '%s' and uid = %d", dbesc($x['path']), @@ -181,6 +181,20 @@ class Directory extends DAV\Node implements DAV\ICollection, DAV\IQuota, DAV\IMo intval($this->auth->owner_id) ); + $z = q("select hash from attach where folder = '%s' and uid = %d", + dbesc($this->folder_hash), + intval($this->auth->owner_id) + ); + + if($z) { + foreach ($z as $zz) { + $rs = attach_move($this->auth->owner_id, $zz['hash'], $this->folder_hash, '', false); + if(!$rs['success']) { + break; + } + } + } + $ch = channelx_by_n($this->auth->owner_id); if ($ch) { $sync = attach_export_data($ch, $this->folder_hash); @@ -481,7 +495,7 @@ class Directory extends DAV\Node implements DAV\ICollection, DAV\IQuota, DAV\IMo } - public function moveInto($targetName,$sourcePath, DAV\INode $sourceNode) { + public function moveInto($targetName, $sourcePath, DAV\INode $sourceNode) { $channel_id = $this->auth->owner_id; // Files have $sourceNode->data['hash'] set. For directories rely on $sourceNode->folder_hash. diff --git a/Zotlabs/Storage/File.php b/Zotlabs/Storage/File.php index dc60a72ae..ff379e2e9 100644 --- a/Zotlabs/Storage/File.php +++ b/Zotlabs/Storage/File.php @@ -38,6 +38,8 @@ class File extends DAV\Node implements DAV\IFile { */ private $name; + public $os_path; + public $folder_hash; /** * Sets up the node, expects a full path name. diff --git a/Zotlabs/Update/_1260.php b/Zotlabs/Update/_1260.php new file mode 100644 index 000000000..7f91418f6 --- /dev/null +++ b/Zotlabs/Update/_1260.php @@ -0,0 +1,58 @@ +<?php +namespace Zotlabs\Update; + +class _1260 { + public function run() { + + $has_sodium = function_exists('sodium_crypto_sign_keypair'); + $has_bcmath = function_exists('bcadd'); + $has_gmp = function_exists('gmp_add'); + + if (!$has_sodium) { + return UPDATE_FAILED; + } + + if (!($has_gmp || $has_bcmath)) { + return UPDATE_FAILED; + } + + dbq("START TRANSACTION"); + + if(ACTIVE_DBTYPE == DBTYPE_POSTGRES) { + $r1 = dbq("ALTER TABLE channel ADD channel_epubkey text NOT NULL DEFAULT ''"); + $r2 = dbq("ALTER TABLE channel ADD channel_eprvkey text NOT NULL DEFAULT ''"); + } + else { + $r1 = dbq("ALTER TABLE channel ADD channel_epubkey text NOT NULL"); + $r2 = dbq("ALTER TABLE channel ADD channel_eprvkey text NOT NULL"); + } + + $channels = dbq("select channel_id from channel where true"); + if ($channels) { + foreach ($channels as $channel) { + $keys = sodium_crypto_sign_keypair(); + $pubkey = sodium_bin2base64(sodium_crypto_sign_publickey($keys), SODIUM_BASE64_VARIANT_ORIGINAL_NO_PADDING); + $prvkey = sodium_bin2base64(sodium_crypto_sign_secretkey($keys), SODIUM_BASE64_VARIANT_ORIGINAL_NO_PADDING); + q("update channel set channel_epubkey = '%s', channel_eprvkey = '%s' where channel_id = %d", + dbesc($pubkey), + dbesc($prvkey), + intval($channel['channel_id']) + ); + } + } + + if ($r1 && $r2) { + dbq("COMMIT"); + return UPDATE_SUCCESS; + } + + dbq("ROLLBACK"); + return UPDATE_FAILED; + } + + public function verify() { + $columns = db_columns('channel'); + return in_array('channel_epubkey', $columns) && in_array('channel_eprvkey', $columns); + } +} + diff --git a/Zotlabs/Update/_1261.php b/Zotlabs/Update/_1261.php new file mode 100644 index 000000000..43fd0b098 --- /dev/null +++ b/Zotlabs/Update/_1261.php @@ -0,0 +1,58 @@ +<?php +namespace Zotlabs\Update; + +use Zotlabs\Lib\Multibase; + +class _1261 { + public function run() { + + $has_sodium = function_exists('sodium_crypto_sign_keypair'); + $has_bcmath = function_exists('bcadd'); + $has_gmp = function_exists('gmp_add'); + + if (!$has_sodium) { + return UPDATE_FAILED; + } + + if (!($has_gmp || $has_bcmath)) { + return UPDATE_FAILED; + } + + dbq("START TRANSACTION"); + + if(ACTIVE_DBTYPE == DBTYPE_POSTGRES) { + $r1 = dbq("ALTER TABLE xchan ADD xchan_epubkey text NOT NULL DEFAULT ''"); + $r2 = dbq("ALTER TABLE xchan ADD xchan_updated timestamp NOT NULL DEFAULT '0001-01-01 00:00:00'"); + } + else { + $r1 = dbq("ALTER TABLE xchan ADD xchan_epubkey text NOT NULL"); + $r2 = dbq("ALTER TABLE xchan ADD xchan_updated datetime NOT NULL DEFAULT '0001-01-01 00:00:00'"); + } + + $channels = dbq("select * from channel where true"); + if ($channels) { + foreach ($channels as $channel) { + $epubkey = (new Multibase())->publicKey($channel['channel_epubkey']); + q("update xchan set xchan_epubkey = '%s' where xchan_url = '%s'", + dbesc($epubkey), + dbesc(channel_url($channel)) + ); + } + } + + if ($r1 && $r2) { + dbq("COMMIT"); + return UPDATE_SUCCESS; + } + + dbq("ROLLBACK"); + return UPDATE_FAILED; + + } + + public function verify() { + $columns = db_columns('xchan'); + return in_array('xchan_epubkey', $columns) && in_array('xchan_updated', $columns); + } +} + diff --git a/Zotlabs/Update/_1262.php b/Zotlabs/Update/_1262.php new file mode 100644 index 000000000..f457a0116 --- /dev/null +++ b/Zotlabs/Update/_1262.php @@ -0,0 +1,30 @@ +<?php + +namespace Zotlabs\Update; + +class _1262 { + + function run() { + + dbq("START TRANSACTION"); + + + if(ACTIVE_DBTYPE == DBTYPE_POSTGRES) { + $r = true; + } + + if(ACTIVE_DBTYPE == DBTYPE_MYSQL) { + $r = dbq("ALTER TABLE session MODIFY COLUMN sess_data MEDIUMTEXT NOT NULL"); + } + + if($r) { + dbq("COMMIT"); + return UPDATE_SUCCESS; + } + + q("ROLLBACK"); + return UPDATE_FAILED; + + } + +} diff --git a/Zotlabs/Update/_1263.php b/Zotlabs/Update/_1263.php new file mode 100644 index 000000000..bd12e7321 --- /dev/null +++ b/Zotlabs/Update/_1263.php @@ -0,0 +1,26 @@ +<?php + +namespace Zotlabs\Update; + +class _1263 { + + function run() { + + dbq("START TRANSACTION"); + + $poke = hash('whirlpool', 'Poke'); + $mood = hash('whirlpool', 'Mood'); + + $r = dbq("DELETE FROM app WHERE (app_id = '$poke' OR app_id = '$mood') AND app_system = 1"); + + if($r) { + dbq("COMMIT"); + return UPDATE_SUCCESS; + } + + dbq("ROLLBACK"); + return UPDATE_FAILED; + + } + +} diff --git a/Zotlabs/Web/HTTPSig.php b/Zotlabs/Web/HTTPSig.php index 36a00528e..793b8cb45 100644 --- a/Zotlabs/Web/HTTPSig.php +++ b/Zotlabs/Web/HTTPSig.php @@ -27,9 +27,12 @@ class HTTPSig { * @param string $alg hash algorithm (one of 'sha256','sha512') * @return string The generated digest header string for $body */ - static function generate_digest_header($body, $alg = 'sha256') { + if ($body === null) { + $body = ''; + } + $digest = base64_encode(hash($alg, $body, true)); switch ($alg) { case 'sha512': @@ -41,37 +44,42 @@ class HTTPSig { } } - static function find_headers($data, &$body) { + public static function find_headers($data, &$body) { // decide if $data arrived via controller submission or curl + // changes $body for the caller - if (is_array($data) && $data['header']) { - if (!$data['success']) + if (is_array($data) && array_key_exists('header', $data)) { + if (!$data['success']) { + $body = EMPTY_STR; return []; + } - $h = new HTTPHeaders($data['header']); - $headers = $h->fetcharr(); - $body = $data['body']; - $headers['(request-target)'] = $data['request_target']; - } + if (!$data['header']) { + $body = EMPTY_STR; + return []; + } - else { - $headers = []; + $h = new HTTPHeaders($data['header']); + $headers = $h->fetcharr(); + $body = $data['body']; + $headers['(request-target)'] = $data['request_target']; + } else { + $headers = []; $headers['(request-target)'] = strtolower($_SERVER['REQUEST_METHOD']) . ' ' . $_SERVER['REQUEST_URI']; - $headers['content-type'] = $_SERVER['CONTENT_TYPE']; - $headers['content-length'] = $_SERVER['CONTENT_LENGTH']; + $headers['content-type'] = $_SERVER['CONTENT_TYPE']; + $headers['content-length'] = $_SERVER['CONTENT_LENGTH']; foreach ($_SERVER as $k => $v) { if (strpos($k, 'HTTP_') === 0) { - $field = str_replace('_', '-', strtolower(substr($k, 5))); + $field = str_replace('_', '-', strtolower(substr($k, 5))); $headers[$field] = $v; } } } //logger('SERVER: ' . print_r($_SERVER,true), LOGGER_ALL); - - //logger('headers: ' . print_r($headers,true), LOGGER_ALL); + //logger('found_headers: ' . print_r($headers,true), LOGGER_ALL); return $headers; } @@ -99,6 +107,10 @@ class HTTPSig { if (!$headers) return $result; + if (is_array($body)) { + btlogger('body is array:' . print_r($body, true)); + } + $sig_block = null; if (array_key_exists('signature', $headers)) { @@ -214,8 +226,10 @@ class HTTPSig { $result['content_signed'] = true; $digest = explode('=', $headers['digest'], 2); $digest[0] = strtoupper($digest[0]); + if ($digest[0] === 'SHA-256') $hashalg = 'sha256'; + if ($digest[0] === 'SHA-512') $hashalg = 'sha512'; diff --git a/Zotlabs/Widget/Messages.php b/Zotlabs/Widget/Messages.php index 2a612c2ce..8654d8e8f 100644 --- a/Zotlabs/Widget/Messages.php +++ b/Zotlabs/Widget/Messages.php @@ -61,8 +61,8 @@ class Messages { $channel = App::get_channel(); $item_normal = item_normal(); - $item_normal .= " and item.verb not in ('Add', 'Remove', '" . ACTIVITY_FOLLOW . "', '" . ACTIVITY_TAG . "') "; - + // Filter internal follow activities and strerams add/remove activities + $item_normal .= " and item.verb not in ('Add', 'Remove', 'Follow', 'Ignore', '" . ACTIVITY_FOLLOW . "') "; $item_normal_i = str_replace('item.', 'i.', $item_normal); $item_normal_c = str_replace('item.', 'c.', $item_normal); $entries = []; @@ -76,16 +76,16 @@ class Messages { $vnotify_sql_i = ''; if (!($vnotify & VNOTIFY_LIKE)) { - $vnotify_sql_c = " AND c.verb NOT IN ('" . dbesc(ACTIVITY_LIKE) . "', '" . dbesc(ACTIVITY_DISLIKE) . "') "; - $vnotify_sql_i = " AND i.verb NOT IN ('" . dbesc(ACTIVITY_LIKE) . "', '" . dbesc(ACTIVITY_DISLIKE) . "') "; + $vnotify_sql_c = " AND c.verb NOT IN ('Like', 'Dislike', '" . dbesc(ACTIVITY_LIKE) . "', '" . dbesc(ACTIVITY_DISLIKE) . "') "; + $vnotify_sql_i = " AND i.verb NOT IN ('Like', 'Dislike', '" . dbesc(ACTIVITY_LIKE) . "', '" . dbesc(ACTIVITY_DISLIKE) . "') "; } elseif (!feature_enabled(local_channel(), 'dislike')) { - $vnotify_sql_c = " AND c.verb NOT IN ('" . dbesc(ACTIVITY_DISLIKE) . "') "; - $vnotify_sql_i = " AND i.verb NOT IN ('" . dbesc(ACTIVITY_DISLIKE) . "') "; + $vnotify_sql_c = " AND c.verb NOT IN ('Dislike', '" . dbesc(ACTIVITY_DISLIKE) . "') "; + $vnotify_sql_i = " AND i.verb NOT IN ('Dislike', '" . dbesc(ACTIVITY_DISLIKE) . "') "; } if($author) { - $author_sql = " AND i.owner_xchan = '" . protect_sprintf(dbesc($author)) . "' "; + $author_sql = " AND (i.owner_xchan = '" . protect_sprintf(dbesc($author)) . "') "; } switch($type) { @@ -102,11 +102,8 @@ class Messages { $type_sql = ' AND i.item_private IN (0, 1) '; } - // FEP-5624 filter approvals for comments - $approvals_c = " AND c.verb NOT IN ('" . dbesc(ACTIVITY_ATTEND) . "', 'Accept', '" . dbesc(ACTIVITY_ATTENDNO) . "', 'Reject') "; - $items = q("SELECT *, - (SELECT count(*) FROM item c WHERE c.uid = %d AND c.parent = i.parent AND c.item_unseen = 1 AND c.item_thread_top = 0 $item_normal_c $approvals_c $vnotify_sql_c) AS unseen_count + (SELECT count(*) FROM item c WHERE c.uid = %d AND c.parent = i.parent AND c.item_unseen = 1 AND c.item_thread_top = 0 $item_normal_c $vnotify_sql_c) AS unseen_count FROM item i WHERE i.uid = %d AND i.created <= '%s' $type_sql @@ -148,6 +145,9 @@ class Messages { if($item['owner_xchan'] !== $item['author_xchan']) { $info .= t('via') . ' ' . $item['owner']['xchan_name']; } + elseif($item['verb'] === 'Announce' && isset($item['source'])) { + $info .= t('via') . ' ' . $item['source']['xchan_name']; + } $summary = $item['title']; if (!$summary) { @@ -185,8 +185,9 @@ class Messages { $entries[$i]['info'] = $info; $entries[$i]['created'] = datetime_convert('UTC', date_default_timezone_get(), $item['created']); $entries[$i]['summary'] = $summary; - $entries[$i]['b64mid'] = gen_link_id($item['mid']); - $entries[$i]['href'] = z_root() . '/hq/' . gen_link_id($item['mid']); + //$entries[$i]['b64mid'] = gen_link_id($item['mid']); + $entries[$i]['b64mid'] = $item['uuid']; + $entries[$i]['href'] = z_root() . '/hq/' . $item['uuid']; $entries[$i]['icon'] = $icon; $entries[$i]['unseen_count'] = (($item['unseen_count']) ? $item['unseen_count'] : (($item['item_unseen']) ? ' ' : '')); $entries[$i]['unseen_class'] = (($item['item_unseen']) ? 'primary' : 'secondary'); @@ -290,8 +291,8 @@ class Messages { $entries[$i]['info'] = ''; $entries[$i]['created'] = datetime_convert('UTC', date_default_timezone_get(), $notice['created']); $entries[$i]['summary'] = $summary; - $entries[$i]['b64mid'] = (($notice['ntype'] & NOTIFY_INTRO) ? '' : basename($notice['link'])); - $entries[$i]['href'] = (($notice['ntype'] & NOTIFY_INTRO) ? $notice['link'] : z_root() . '/hq/' . basename($notice['link'])); + $entries[$i]['b64mid'] = (($notice['ntype'] & NOTIFY_INTRO) ? '' : ((str_contains($notice['hash'], '-')) ? $notice['hash'] : basename($notice['link']))); + $entries[$i]['href'] = (($notice['ntype'] & NOTIFY_INTRO) ? $notice['link'] : z_root() . '/hq/' . ((str_contains($notice['hash'], '-')) ? $notice['hash'] : basename($notice['link']))); $entries[$i]['icon'] = (($notice['ntype'] & NOTIFY_INTRO) ? '<i class="fa fa-user-plus"></i>' : ''); $i++; diff --git a/Zotlabs/Widget/Pinned.php b/Zotlabs/Widget/Pinned.php index bd1c0e462..1380c156b 100644 --- a/Zotlabs/Widget/Pinned.php +++ b/Zotlabs/Widget/Pinned.php @@ -46,7 +46,7 @@ class Pinned { foreach($items as $item) { - $midb64 = gen_link_id($item['mid']); + $midb64 = $item['uuid']; if(isset($observer['xchan_hash']) && in_array($observer['xchan_hash'], get_pconfig($item['uid'], 'pinned_hide', $midb64, []))) continue; @@ -77,17 +77,6 @@ class Pinned { } } - $consensus = (intval($item['item_consensus']) ? true : false); - if($consensus) { - $conv_responses['agree'] = [ 'title' => t('Agree','title') ]; - $conv_responses['disagree'] = [ 'title' => t('Disagree','title') ]; - $conv_responses['abstain'] = [ 'title' => t('Abstain','title') ]; - if($commentable && $observer) { - $conlabels = [ t('I agree'), t('I disagree'), t('I abstain') ]; - $canvote = true; - } - } - $this->activity($item, $conv_responses); $verified = (intval($item['item_verified']) ? t('Message signature validated') : ''); @@ -203,7 +192,7 @@ class Pinned { if(empty($mids_list)) return []; - $r = q("SELECT * FROM item WHERE mid IN ( '%s' ) AND uid = %d AND id = parent AND item_private = 0 ORDER BY created DESC", + $r = q("SELECT * FROM item WHERE uuid IN ( '%s' ) AND uid = %d AND id = parent AND item_private = 0 ORDER BY created DESC", dbesc(implode(",", $mids_list)), intval($this->uid) ); @@ -225,39 +214,30 @@ class Pinned { private function activity($item, &$conv_responses) { foreach(array_keys($conv_responses) as $verb) { + $verb_sql = ''; switch($verb) { case 'like': - $v = ACTIVITY_LIKE; + $verb_sql = " AND verb IN ('Like', '" . ACTIVITY_LIKE . "') "; break; case 'dislike': - $v = ACTIVITY_DISLIKE; - break; - case 'agree': - $v = ACTIVITY_AGREE; - break; - case 'disagree': - $v = ACTIVITY_DISAGREE; - break; - case 'abstain': - $v = ACTIVITY_ABSTAIN; + $verb_sql = " AND verb IN ('Dislike', '" . ACTIVITY_DISLIKE . "') "; break; case 'attendyes': - $v = ACTIVITY_ATTEND; + $verb_sql = " AND verb IN ('Accept', '" . ACTIVITY_ATTEND . "') "; break; case 'attendno': - $v = ACTIVITY_ATTENDNO; + $verb_sql = " AND verb IN ('Reject', '" . ACTIVITY_ATTENDNO . "') "; break; case 'attendmaybe': - $v = ACTIVITY_ATTENDMAYBE; + $verb_sql = " AND verb IN ('TentativeAccept', '" . ACTIVITY_ATTENDMAYBE . "') "; break; default: break; } - $r = q("SELECT * FROM item WHERE parent = %d AND id <> parent AND verb = '%s' AND item_deleted = 0", - intval($item['id']), - dbesc($v) + $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]); |