diff options
Diffstat (limited to 'Zotlabs/Lib')
-rw-r--r-- | Zotlabs/Lib/AccessList.php | 411 | ||||
-rw-r--r-- | Zotlabs/Lib/Activity.php | 756 | ||||
-rw-r--r-- | Zotlabs/Lib/ActivityStreams.php | 34 | ||||
-rw-r--r-- | Zotlabs/Lib/Apps.php | 14 | ||||
-rw-r--r-- | Zotlabs/Lib/Cache.php | 14 | ||||
-rw-r--r-- | Zotlabs/Lib/Chatroom.php | 4 | ||||
-rw-r--r-- | Zotlabs/Lib/Connect.php | 312 | ||||
-rw-r--r-- | Zotlabs/Lib/Enotify.php | 167 | ||||
-rw-r--r-- | Zotlabs/Lib/JSalmon.php | 2 | ||||
-rw-r--r-- | Zotlabs/Lib/Libsync.php | 31 | ||||
-rw-r--r-- | Zotlabs/Lib/Libzot.php | 139 | ||||
-rw-r--r-- | Zotlabs/Lib/Libzotdir.php | 26 | ||||
-rw-r--r-- | Zotlabs/Lib/NativeWiki.php | 6 | ||||
-rw-r--r-- | Zotlabs/Lib/NativeWikiPage.php | 7 | ||||
-rw-r--r-- | Zotlabs/Lib/Queue.php | 2 | ||||
-rw-r--r-- | Zotlabs/Lib/Share.php | 5 | ||||
-rw-r--r-- | Zotlabs/Lib/System.php | 11 | ||||
-rw-r--r-- | Zotlabs/Lib/ThreadItem.php | 62 | ||||
-rw-r--r-- | Zotlabs/Lib/ThreadStream.php | 2 | ||||
-rw-r--r-- | Zotlabs/Lib/Webfinger.php | 2 | ||||
-rw-r--r-- | Zotlabs/Lib/Zotfinger.php | 2 |
21 files changed, 1758 insertions, 251 deletions
diff --git a/Zotlabs/Lib/AccessList.php b/Zotlabs/Lib/AccessList.php new file mode 100644 index 000000000..3c008f8c7 --- /dev/null +++ b/Zotlabs/Lib/AccessList.php @@ -0,0 +1,411 @@ +<?php + +namespace Zotlabs\Lib; + +use Zotlabs\Lib\Libsync; + + +class AccessList { + + static function add($uid,$name,$public = 0) { + + $ret = false; + if ($uid && $name) { + $r = self::byname($uid,$name); // check for dups + if ($r !== false) { + + // This could be a problem. + // Let's assume we've just created a list which we once deleted + // all the old members are gone, but the list remains so we don't break any security + // access lists. What we're doing here is reviving the dead list, but old content which + // was restricted to this list may now be seen by the new list members. + + $z = q("SELECT * FROM pgrp WHERE id = %d LIMIT 1", + intval($r) + ); + if(($z) && $z[0]['deleted']) { + q('UPDATE pgrp SET deleted = 0 WHERE id = %d', intval($z[0]['id'])); + notice( t('A deleted list with this name was revived. Existing item permissions <strong>may</strong> apply to this list and any future members. If this is not what you intended, please create another list with a different name.') . EOL); + } + return true; + } + + $hash = new_uuid(); + + $r = q("INSERT INTO pgrp ( hash, uid, visible, gname ) + VALUES( '%s', %d, %d, '%s' ) ", + dbesc($hash), + intval($uid), + intval($public), + dbesc($name) + ); + $ret = $r; + } + + Libsync::build_sync_packet($uid,null,true); + return $ret; + } + + + static function remove($uid,$name) { + $ret = false; + if ($uid && $name) { + $r = q("SELECT id, hash FROM pgrp WHERE uid = %d AND gname = '%s' LIMIT 1", + intval($uid), + dbesc($name) + ); + if ($r) { + $group_id = $r[0]['id']; + $group_hash = $r[0]['hash']; + } + else { + return false; + } + + // remove group from default posting lists + $r = q("SELECT channel_default_group, channel_allow_gid, channel_deny_gid FROM channel WHERE channel_id = %d LIMIT 1", + intval($uid) + ); + if ($r) { + $user_info = array_shift($r); + $change = false; + + if ($user_info['channel_default_group'] == $group_hash) { + $user_info['channel_default_group'] = ''; + $change = true; + } + if (strpos($user_info['channel_allow_gid'], '<' . $group_hash . '>') !== false) { + $user_info['channel_allow_gid'] = str_replace('<' . $group_hash . '>', '', $user_info['channel_allow_gid']); + $change = true; + } + if (strpos($user_info['channel_deny_gid'], '<' . $group_hash . '>') !== false) { + $user_info['channel_deny_gid'] = str_replace('<' . $group_hash . '>', '', $user_info['channel_deny_gid']); + $change = true; + } + + if ($change) { + q("UPDATE channel SET channel_default_group = '%s', channel_allow_gid = '%s', channel_deny_gid = '%s' + WHERE channel_id = %d", + intval($user_info['channel_default_group']), + dbesc($user_info['channel_allow_gid']), + dbesc($user_info['channel_deny_gid']), + intval($uid) + ); + } + } + + // remove all members + $r = q("DELETE FROM pgrp_member WHERE uid = %d AND gid = %d ", + intval($uid), + intval($group_id) + ); + + // remove group + $r = q("UPDATE pgrp SET deleted = 1 WHERE uid = %d AND gname = '%s'", + intval($uid), + dbesc($name) + ); + + $ret = $r; + + } + + Libsync::build_sync_packet($uid,null,true); + + return $ret; + } + + // returns the integer id of an access group owned by $uid and named $name + // or false. + + static function byname($uid,$name) { + if (! ($uid && $name)) { + return false; + } + $r = q("SELECT id FROM pgrp WHERE uid = %d AND gname = '%s' LIMIT 1", + intval($uid), + dbesc($name) + ); + if ($r) { + return $r[0]['id']; + } + return false; + } + + static function by_id($uid,$id) { + if (! ($uid && $id)) { + return false; + } + + $r = q("SELECT * FROM pgrp WHERE uid = %d AND id = %d and deleted = 0", + intval($uid), + intval($id) + ); + if ($r) { + return array_shift($r); + } + return false; + } + + + + static function rec_byhash($uid,$hash) { + if (! ( $uid && $hash)) { + return false; + } + $r = q("SELECT * FROM pgrp WHERE uid = %d AND hash = '%s' LIMIT 1", + intval($uid), + dbesc($hash) + ); + if ($r) { + return array_shift($r); + } + return false; + } + + + static function member_remove($uid,$name,$member) { + $gid = self::byname($uid,$name); + if (! $gid) { + return false; + } + if (! ($uid && $gid && $member)) { + return false; + } + $r = q("DELETE FROM pgrp_member WHERE uid = %d AND gid = %d AND xchan = '%s' ", + intval($uid), + intval($gid), + dbesc($member) + ); + + Libsync::build_sync_packet($uid,null,true); + + return $r; + } + + + static function member_add($uid,$name,$member,$gid = 0) { + if (! $gid) { + $gid = self::byname($uid,$name); + } + if (! ($gid && $uid && $member)) { + return false; + } + + $r = q("SELECT * FROM pgrp_member WHERE uid = %d AND gid = %d AND xchan = '%s' LIMIT 1", + intval($uid), + intval($gid), + dbesc($member) + ); + if ($r) { + return true; // You might question this, but + // we indicate success because the group member was in fact created + // -- It was just created at another time + } + else { + $r = q("INSERT INTO pgrp_member (uid, gid, xchan) + VALUES( %d, %d, '%s' ) ", + intval($uid), + intval($gid), + dbesc($member) + ); + } + Libsync::build_sync_packet($uid,null,true); + return $r; + } + + + static function members($uid, $gid) { + $ret = []; + if (intval($gid)) { + $r = q("SELECT * FROM pgrp_member + LEFT JOIN abook ON abook_xchan = pgrp_member.xchan left join xchan on xchan_hash = abook_xchan + WHERE gid = %d AND abook_channel = %d and pgrp_member.uid = %d and xchan_deleted = 0 and abook_self = 0 and abook_blocked = 0 and abook_pending = 0 ORDER BY xchan_name ASC ", + intval($gid), + intval($uid), + intval($uid) + ); + if ($r) { + $ret = $r; + } + } + return $ret; + } + + static function members_xchan($uid,$gid) { + $ret = []; + if (intval($gid)) { + $r = q("SELECT xchan FROM pgrp_member WHERE gid = %d AND uid = %d", + intval($gid), + intval($uid) + ); + if ($r) { + foreach ($r as $rv) { + $ret[] = $rv['xchan']; + } + } + } + return $ret; + } + + static function members_profile_xchan($uid,$gid) { + $ret = []; + if (intval($gid)) { + $r = q("SELECT abook_xchan as xchan from abook left join profile on abook_profile = profile_guid where profile.id = %d and profile.uid = %d", + intval($gid), + intval($uid) + ); + if ($r) { + foreach($r as $rv) { + $ret[] = $rv['xchan']; + } + } + } + return $ret; + } + + + + + static function select($uid,$group = '') { + + $grps = []; + + $r = q("SELECT * FROM pgrp WHERE deleted = 0 AND uid = %d ORDER BY gname ASC", + intval($uid) + ); + $grps[] = [ 'name' => '', 'hash' => '0', 'selected' => '' ]; + if ($r) { + foreach ($r as $rr) { + $grps[] = [ 'name' => $rr['gname'], 'id' => $rr['hash'], 'selected' => (($group == $rr['hash']) ? 'true' : '') ]; + } + + } + + return replace_macros(get_markup_template('group_selection.tpl'), [ + '$label' => t('Add new connections to this access list'), + '$groups' => $grps + ]); + } + + + static function widget($every="connections",$each="lists",$edit = false, $group_id = 0, $cid = '',$mode = 1) { + + $o = ''; + + $groups = []; + + $r = q("SELECT * FROM pgrp WHERE deleted = 0 AND uid = %d ORDER BY gname ASC", + intval($_SESSION['uid']) + ); + $member_of = []; + if ($cid) { + $member_of = self::containing(local_channel(),$cid); + } + + if ($r) { + foreach ($r as $rr) { + $selected = (($group_id == $rr['id']) ? ' group-selected' : ''); + + if ($edit) { + $groupedit = [ 'href' => "lists/".$rr['id'], 'title' => t('edit') ]; + } + else { + $groupedit = null; + } + + $groups[] = [ + 'id' => $rr['id'], + 'enc_cid' => base64url_encode($cid), + 'cid' => $cid, + 'text' => $rr['gname'], + 'selected' => $selected, + 'href' => (($mode == 0) ? $each.'?f=&gid='.$rr['id'] : $each."/".$rr['id']) . ((x($_GET,'new')) ? '&new=' . $_GET['new'] : '') . ((x($_GET,'order')) ? '&order=' . $_GET['order'] : ''), + 'edit' => $groupedit, + 'ismember' => in_array($rr['id'],$member_of), + ]; + } + } + + return replace_macros(get_markup_template('group_side.tpl'), [ + '$title' => t('Lists'), + '$edittext' => t('Edit list'), + '$createtext' => t('Create new list'), + '$ungrouped' => (($every === 'contacts') ? t('Channels not in any access list') : ''), + '$groups' => $groups, + '$add' => t('add'), + ]); + + } + + + static function expand($g) { + if (! (is_array($g) && count($g))) { + return []; + } + + $ret = []; + $x = []; + + // private profile linked virtual groups + + foreach ($g as $gv) { + if (substr($gv,0,3) === 'vp.') { + $profile_hash = substr($gv,3); + if ($profile_hash) { + $r = q("select abook_xchan from abook where abook_profile = '%s'", + dbesc($profile_hash) + ); + if ($r) { + foreach ($r as $rv) { + $ret[] = $rv['abook_xchan']; + } + } + } + } + else { + $x[] = $gv; + } + } + + if ($x) { + stringify_array_elms($x,true); + $groups = implode(',', $x); + if ($groups) { + $r = q("SELECT xchan FROM pgrp_member WHERE gid IN ( select id from pgrp where hash in ( $groups ))"); + if ($r) { + foreach ($r as $rv) { + $ret[] = $rv['xchan']; + } + } + } + } + return $ret; + } + + + static function member_of($c) { + $r = q("SELECT pgrp.gname, pgrp.id FROM pgrp LEFT JOIN pgrp_member ON pgrp_member.gid = pgrp.id + WHERE pgrp_member.xchan = '%s' AND pgrp.deleted = 0 ORDER BY pgrp.gname ASC ", + dbesc($c) + ); + + return $r; + } + + static function containing($uid,$c) { + + $r = q("SELECT gid FROM pgrp_member WHERE uid = %d AND pgrp_member.xchan = '%s' ", + intval($uid), + dbesc($c) + ); + + $ret = []; + if ($r) { + foreach ($r as $rv) + $ret[] = $rv['gid']; + } + + return $ret; + } +}
\ No newline at end of file diff --git a/Zotlabs/Lib/Activity.php b/Zotlabs/Lib/Activity.php index 08a8b8d03..e7fa6352a 100644 --- a/Zotlabs/Lib/Activity.php +++ b/Zotlabs/Lib/Activity.php @@ -168,13 +168,16 @@ class Activity { if($r) { xchan_query($r,true); $r = fetch_post_tags($r,true); + if (in_array($r[0]['verb'], ['Create', 'Invite']) && $r[0]['obj_type'] === ACTIVITY_OBJ_EVENT) { + $r[0]['verb'] = 'Invite'; + return self::encode_activity($r[0]); + } return self::encode_item($r[0]); } } static function fetch_image($x) { - $ret = [ 'type' => 'Image', 'id' => $x['id'], @@ -220,7 +223,7 @@ class Activity { 'startTime' => (($ev['adjust']) ? datetime_convert($ev['timezone'],'UTC',$ev['dtstart'], ATOM_TIME) : datetime_convert('UTC','UTC',$ev['dtstart'],'Y-m-d\\TH:i:s-00:00')), 'content' => bbcode($ev['description'], [ 'cache' => true ]), 'location' => [ 'type' => 'Place', 'content' => bbcode($ev['location'], [ 'cache' => true ]) ], - 'source' => [ 'content' => format_event_bbcode($ev), 'mediaType' => 'text/bbcode' ], + 'source' => [ 'content' => format_event_bbcode($ev,true), 'mediaType' => 'text/bbcode' ], 'actor' => $actor, ]; if(! $ev['nofinish']) { @@ -311,17 +314,51 @@ class Activity { else { $objtype = self::activity_obj_mapper($i['obj_type']); } - - if(intval($i['item_deleted'])) { + + if ($i['obj']) { + $ret = Activity::encode_object($i['obj']); + } + + if (intval($i['item_deleted'])) { $ret['type'] = 'Tombstone'; $ret['formerType'] = $objtype; - $ret['id'] = ((strpos($i['mid'],'http') === 0) ? $i['mid'] : z_root() . '/item/' . urlencode($i['mid'])); + $ret['id'] = $i['mid']; + if($i['id'] != $i['parent']) + $ret['inReplyTo'] = $i['thr_parent']; + + $ret['to'] = [ ACTIVITY_PUBLIC_INBOX ]; return $ret; } + if ($i['obj']) { + if (is_array($i['obj'])) { + $ret = $i['obj']; + } + else { + $ret = json_decode($i['obj'],true); + } + } + $ret['type'] = $objtype; + if ($objtype === 'Question') { + if ($i['obj']) { + if (is_array($i['obj'])) { + $ret = $i['obj']; + } + else { + $ret = json_decode($i['obj'],true); + } + + if(array_path_exists('actor/id',$ret)) { + $ret['actor'] = $ret['actor']['id']; + } + } + } + + $ret['id'] = ((strpos($i['mid'],'http') === 0) ? $i['mid'] : z_root() . '/item/' . urlencode($i['mid'])); + $ret['diaspora:guid'] = $i['uuid']; if($i['title']) $ret['name'] = $i['title']; @@ -394,7 +431,71 @@ class Activity { $ret['attachment'] = $a; } + $public = (($i['item_private']) ? false : true); + $top_level = (($i['mid'] === $i['parent_mid']) ? true : false); + + if ($public) { + $ret['to'] = [ ACTIVITY_PUBLIC_INBOX ]; + $ret['cc'] = [ z_root() . '/followers/' . substr($i['author']['xchan_addr'],0,strpos($i['author']['xchan_addr'],'@')) ]; + } + else { + + // private activity + + if ($top_level) { + $ret['to'] = self::map_acl($i); + } + else { + $ret['to'] = []; + if ($ret['tag']) { + foreach ($ret['tag'] as $mention) { + if (is_array($mention) && array_key_exists('href',$mention) && $mention['href']) { + $h = q("select * from hubloc where hubloc_id_url = '%s' limit 1", + dbesc($mention['href']) + ); + if ($h) { + if ($h[0]['hubloc_network'] === 'activitypub') { + $addr = $h[0]['hubloc_hash']; + } + else { + $addr = $h[0]['hubloc_id_url']; + } + if (! in_array($addr,$ret['to'])) { + $ret['to'][] = $addr; + } + } + } + } + } + $d = q("select hubloc.* from hubloc left join item on hubloc_hash = owner_xchan where item.id = %d limit 1", + intval($i['parent']) + ); + if ($d) { + if ($d[0]['hubloc_network'] === 'activitypub') { + $addr = $d[0]['hubloc_hash']; + } + else { + $addr = $d[0]['hubloc_id_url']; + } + if (! in_array($addr,$ret['to'])) { + $ret['cc'][] = $addr; + } + } + } + } + + $mentions = self::map_mentions($i); + if (count($mentions) > 0) { + if (! $ret['to']) { + $ret['to'] = $mentions; + } + else { + $ret['to'] = array_values(array_unique(array_merge($ret['to'], $mentions))); + } + } + return $ret; + } static function decode_taxonomy($item) { @@ -484,10 +585,47 @@ class Activity { } } } + if ($item['iconfig']) { + foreach ($item['iconfig'] as $att) { + if ($att['sharing']) { + $value = ((is_string($att['v']) && preg_match('|^a:[0-9]+:{.*}$|s', $att['v'])) ? unserialize($att['v']) : $att['v']); + $ret[] = [ 'type' => 'PropertyValue', 'name' => 'zot.' . $att['cat'] . '.' . $att['k'], 'value' => $value ]; + } + } + } return $ret; } + static function decode_iconfig($item) { + + $ret = []; + + if (is_array($item['attachment']) && $item['attachment']) { + $ptr = $item['attachment']; + if (! array_key_exists(0,$ptr)) { + $ptr = [ $ptr ]; + } + foreach ($ptr as $att) { + $entry = []; + if ($att['type'] === 'PropertyValue') { + if (array_key_exists('name',$att) && $att['name']) { + $key = explode('.',$att['name']); + if (count($key) === 3 && $key[0] === 'zot') { + $entry['cat'] = $key[1]; + $entry['k'] = $key[2]; + $entry['v'] = $att['value']; + $entry['sharing'] = '1'; + $ret[] = $entry; + } + } + } + } + } + return $ret; + } + + static function decode_attachment($item) { @@ -514,7 +652,7 @@ class Activity { - static function encode_activity($i) { + static function encode_activity($i, $recurse = false) { $ret = []; $reply = false; @@ -525,20 +663,46 @@ class Activity { $ret['obj'] = []; } - if(intval($i['item_deleted'])) { - $ret['type'] = 'Tombstone'; - $ret['formerType'] = self::activity_obj_mapper($i['obj_type']); - $ret['id'] = ((strpos($i['mid'],'http') === 0) ? $i['mid'] : z_root() . '/item/' . urlencode($i['mid'])); + $ret['type'] = self::activity_mapper($i['verb']); + $fragment = ''; + + if (intval($i['item_deleted']) && !$recurse) { + $is_response = false; + + if (ActivityStreams::is_response_activity($ret['type'])) { + $ret['type'] = 'Undo'; + $fragment = 'undo'; + $is_response = true; + } + else { + $ret['type'] = 'Delete'; + $fragment = 'delete'; + } + + $ret['id'] = str_replace('/item/','/activity/',$i['mid']) . '#' . $fragment; $actor = self::encode_person($i['author'],false); - if($actor) + if ($actor) $ret['actor'] = $actor; else return []; - return $ret; - } + $obj = (($is_response) ? self::encode_activity($i,true) : self::encode_item($i,true)); + if ($obj) { + if (array_path_exists('object/id',$obj)) { + $obj['object'] = $obj['object']['id']; + } + unset($obj['cc']); + $obj['to'] = [ ACTIVITY_PUBLIC_INBOX ]; + $ret['object'] = $obj; + } + else + return []; + + $ret['to'] = [ ACTIVITY_PUBLIC_INBOX ]; - $ret['type'] = self::activity_mapper($i['verb']); + return $ret; + + } if($ret['type'] === 'emojiReaction') { // There may not be an object for these items for legacy reasons - it should be the conversation parent. @@ -572,8 +736,17 @@ class Activity { } } + if (strpos($i['mid'],z_root() . '/item/') !== false) { + $ret['id'] = str_replace('/item/','/activity/',$i['mid']); + } + elseif (strpos($i['mid'],z_root() . '/event/') !== false) { + $ret['id'] = str_replace('/event/','/activity/',$i['mid']); + } + else { + $ret['id'] = ((strpos($i['mid'],'http') === 0) ? $i['mid'] : z_root() . '/activity/' . urlencode($i['mid'])); + } - $ret['id'] = ((strpos($i['mid'],'http') === 0) ? $i['mid'] : z_root() . '/activity/' . urlencode($i['mid'])); + $ret['diaspora:guid'] = $i['uuid']; if($i['title']) $ret['name'] = html2plain(bbcode($i['title'], [ 'cache' => true ])); @@ -611,10 +784,10 @@ class Activity { if($i['id'] != $i['parent']) { $reply = true; - // inReplyTo needs to be set in the activity for followup actiions (Like, Dislike, Attend, Announce, etc.), - // but *not* for comments, where it should only be present in the object - - if (! in_array($ret['type'],[ 'Create','Update' ])) { + // inReplyTo needs to be set in the activity for followup actions (Like, Dislike, Announce, etc.), + // but *not* for comments and RSVPs, where it should only be present in the object + + if (! in_array($ret['type'],[ 'Create','Update','Accept','Reject','TentativeAccept','TentativeReject' ])) { $ret['inReplyTo'] = ((strpos($i['thr_parent'],'http') === 0) ? $i['thr_parent'] : z_root() . '/item/' . urlencode($i['thr_parent'])); } @@ -672,6 +845,9 @@ class Activity { return []; } + if(array_path_exists('object/type',$ret) && $ret['object']['type'] === 'Event' && $ret['type'] === 'Create') { + $ret['type'] = 'Invite'; + } if($i['target']) { if(! is_array($i['target'])) { @@ -684,57 +860,155 @@ class Activity { return []; } + $t = self::encode_taxonomy($i); + if ($t) { + $ret['tag'] = $t; + } + + // addressing madness + + $public = (($i['item_private']) ? false : true); + $top_level = (($reply) ? false : true); + + if ($public) { + $ret['to'] = [ ACTIVITY_PUBLIC_INBOX ]; + $ret['cc'] = [ z_root() . '/followers/' . substr($i['author']['xchan_addr'],0,strpos($i['author']['xchan_addr'],'@')) ]; + } + else { + + // private activity + + if ($top_level) { + $ret['to'] = self::map_acl($i); + } + else { + $ret['to'] = []; + if ($ret['tag']) { + foreach ($ret['tag'] as $mention) { + if (is_array($mention) && array_key_exists('href',$mention) && $mention['href']) { + $h = q("select * from hubloc where hubloc_id_url = '%s' limit 1", + dbesc($mention['href']) + ); + if ($h) { + if ($h[0]['hubloc_network'] === 'activitypub') { + $addr = $h[0]['hubloc_hash']; + } + else { + $addr = $h[0]['hubloc_id_url']; + } + if (! in_array($addr,$ret['to'])) { + $ret['to'][] = $addr; + } + } + } + } + } + + $d = q("select hubloc.* from hubloc left join item on hubloc_hash = owner_xchan where item.id = %d limit 1", + intval($i['parent']) + ); + if ($d) { + if ($d[0]['hubloc_network'] === 'activitypub') { + $addr = $d[0]['hubloc_hash']; + } + else { + $addr = $d[0]['hubloc_id_url']; + } + if (! in_array($addr,$ret['to'])) { + $ret['cc'][] = $addr; + } + } + } + } + + $mentions = self::map_mentions($i); + if (count($mentions) > 0) { + if (! $ret['to']) { + $ret['to'] = $mentions; + } + else { + $ret['to'] = array_values(array_unique(array_merge($ret['to'], $mentions))); + } + } + return $ret; } + // Returns an array of URLS for any mention tags found in the item array $i. + static function map_mentions($i) { - if(! $i['term']) { + + if (! $i['term']) { return []; } $list = []; foreach ($i['term'] as $t) { - if($t['ttype'] == TERM_MENTION) { - $list[] = $t['url']; + if (! $t['url']) { + continue; + } + if ($t['ttype'] == TERM_MENTION) { + $url = self::lookup_term_url($t['url']); + $list[] = (($url) ? $url : $t['url']); } } return $list; } - static function map_acl($i,$mentions = false) { + // Returns an array of all recipients targeted by private item array $i. - $private = false; - $list = []; - $x = collect_recipients($i,$private); - if($x) { - stringify_array_elms($x); - if(! $x) - return; - - $strict = (($mentions) ? true : get_config('activitypub','compliance')); + static function map_acl($i) { + $ret = []; - $sql_extra = (($strict) ? " and xchan_network = 'activitypub' " : ''); + if (! $i['item_private']) { + return $ret; + } - $details = q("select xchan_url, xchan_addr, xchan_name from xchan where xchan_hash in (" . implode(',',$x) . ") $sql_extra"); + if ($i['allow_gid']) { + $tmp = expand_acl($i['allow_gid']); + if ($tmp) { + foreach ($tmp as $t) { + $ret[] = z_root() . '/lists/' . $t; + } + } + } - if($details) { - foreach($details as $d) { - if($mentions) { - $list[] = [ 'type' => 'Mention', 'href' => $d['xchan_url'], 'name' => '@' . (($d['xchan_addr']) ? $d['xchan_addr'] : $d['xchan_name']) ]; - } - else { - $list[] = $d['xchan_url']; + if ($i['allow_cid']) { + $tmp = expand_acl($i['allow_cid']); + $list = stringify_array($tmp,true); + if ($list) { + $details = q("select hubloc_id_url from hubloc where hubloc_hash in (" . $list . ") and hubloc_id_url != ''"); + if ($details) { + foreach ($details as $d) { + $ret[] = $d['hubloc_id_url']; } } } } - return $list; - + return $ret; } + static function lookup_term_url($url) { + + // The xchan_url for mastodon is a text/html rendering. This is called from map_mentions where we need + // to convert the mention url to an ActivityPub id. If this fails for any reason, return the url we have + + $r = q("select hubloc_network, hubloc_hash, hubloc_id_url from hubloc where hubloc_id_url = '%s' limit 1", + dbesc($url) + ); + + if ($r) { + if ($r[0]['hubloc_network'] === 'activitypub') { + return $r[0]['hubloc_hash']; + } + return $r[0]['hubloc_id_url']; + } + + return $url; + } static function encode_person($p, $extended = true) { @@ -744,6 +1018,7 @@ class Activity { if(! $extended) { return $p['xchan_url']; } + $ret = []; $c = ((array_key_exists('channel_id',$p)) ? $p : channelx_by_hash($p['xchan_hash'])); @@ -789,10 +1064,16 @@ class Activity { ] ]; + $ret['publicKey'] = [ + 'id' => $p['xchan_url'], + 'owner' => $p['xchan_url'], + 'publicKeyPem' => $p['xchan_pubkey'] + ]; + $arr = [ 'xchan' => $p, 'encoded' => $ret ]; call_hooks('encode_person', $arr); - $ret = $arr['encoded']; + $ret = $arr['encoded']; return $ret; } @@ -822,7 +1103,10 @@ class Activity { 'http://activitystrea.ms/schema/1.0/unfollow' => 'Unfollow', 'http://purl.org/zot/activity/attendyes' => 'Accept', 'http://purl.org/zot/activity/attendno' => 'Reject', - 'http://purl.org/zot/activity/attendmaybe' => 'TentativeAccept' + 'http://purl.org/zot/activity/attendmaybe' => 'TentativeAccept', + 'Invite' => 'Invite', + 'Delete' => 'Delete', + 'Undo' => 'Undo' ]; call_hooks('activity_mapper',$acts); @@ -868,7 +1152,10 @@ class Activity { 'http://activitystrea.ms/schema/1.0/unfollow' => 'Unfollow', 'http://purl.org/zot/activity/attendyes' => 'Accept', 'http://purl.org/zot/activity/attendno' => 'Reject', - 'http://purl.org/zot/activity/attendmaybe' => 'TentativeAccept' + 'http://purl.org/zot/activity/attendmaybe' => 'TentativeAccept', + 'Invite' => 'Invite', + 'Delete' => 'Delete', + 'Undo' => 'Undo' ]; call_hooks('activity_decode_mapper',$acts); @@ -895,14 +1182,19 @@ class Activity { 'http://activitystrea.ms/schema/1.0/photo' => 'Image', 'http://activitystrea.ms/schema/1.0/profile-photo' => 'Icon', 'http://activitystrea.ms/schema/1.0/event' => 'Event', - 'http://activitystrea.ms/schema/1.0/wiki' => 'Document', 'http://purl.org/zot/activity/location' => 'Place', 'http://purl.org/zot/activity/chessgame' => 'Game', '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', + 'Document' => 'Document', + 'Audio' => 'Audio', + 'Video' => 'Video', + 'Delete' => 'Delete', + 'Undo' => 'Undo' ]; call_hooks('activity_obj_decode_mapper',$objs); @@ -922,10 +1214,6 @@ class Activity { static function activity_obj_mapper($obj) { - if(strpos($obj,'/') === false) { - return $obj; - } - $objs = [ 'http://activitystrea.ms/schema/1.0/note' => 'Note', 'http://activitystrea.ms/schema/1.0/comment' => 'Note', @@ -934,18 +1222,31 @@ class Activity { 'http://activitystrea.ms/schema/1.0/photo' => 'Image', 'http://activitystrea.ms/schema/1.0/profile-photo' => 'Icon', 'http://activitystrea.ms/schema/1.0/event' => 'Event', - 'http://activitystrea.ms/schema/1.0/wiki' => 'Document', 'http://purl.org/zot/activity/location' => 'Place', 'http://purl.org/zot/activity/chessgame' => 'Game', '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' ]; call_hooks('activity_obj_mapper',$objs); + if ($obj === 'Answer') { + return 'Note'; + } + + if (strpos($obj,'/') === false) { + return $obj; + } + + if(array_key_exists($obj,$objs)) { return $objs[$obj]; } @@ -1208,11 +1509,35 @@ class Activity { $icon = $person_obj['icon']; } - if(is_array($person_obj['url']) && array_key_exists('href', $person_obj['url'])) - $profile = $person_obj['url']['href']; - else - $profile = $url; + $links = false; + $profile = false; + + if (is_array($person_obj['url'])) { + if (! array_key_exists(0,$person_obj['url'])) { + $links = [ $person_obj['url'] ]; + } + else { + $links = $person_obj['url']; + } + } + + if ($links) { + foreach ($links as $link) { + if (array_key_exists('mediaType',$link) && $link['mediaType'] === 'text/html') { + $profile = $link['href']; + } + } + if (! $profile) { + $profile = $links[0]['href']; + } + } + elseif (isset($person_obj['url']) && is_string($person_obj['url'])) { + $profile = $person_obj['url']; + } + if (! $profile) { + $profile = $url; + } $inbox = $person_obj['inbox']; @@ -1244,6 +1569,7 @@ class Activity { ); if(! $r) { // create a new record + $r = xchan_store_lowlevel( [ 'xchan_hash' => $url, @@ -1302,7 +1628,8 @@ class Activity { 'hubloc_host' => $hostname, 'hubloc_callback' => $inbox, 'hubloc_updated' => datetime_convert(), - 'hubloc_primary' => 1 + 'hubloc_primary' => 1, + 'hubloc_id_url' => $profile ] ); } @@ -1352,7 +1679,7 @@ class Activity { // sort function width decreasing - static function as_vid_sort($a,$b) { + static function vid_sort($a,$b) { if($a['width'] === $b['width']) return 0; return (($a['width'] > $b['width']) ? -1 : 1); @@ -1416,7 +1743,25 @@ class Activity { $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']); @@ -1522,7 +1867,7 @@ class Activity { } } if($mps) { - usort($mps,'as_vid_sort'); + usort($mps,[ __CLASS__, 'vid_sort' ]); foreach($mps as $m) { if(intval($m['width']) < 500) { $s['body'] .= "\n\n" . '[video]' . $m['href'] . '[/video]'; @@ -1596,6 +1941,101 @@ class Activity { } + + static function update_poll($item,$post) { + $multi = false; + $mid = $post['mid']; + $content = $post['title']; + + if (! $item) { + return false; + } + + $o = json_decode($item['obj'],true); + if ($o && array_key_exists('anyOf',$o)) { + $multi = true; + } + + $r = q("select mid, title from item where parent_mid = '%s' and author_xchan = '%s'", + dbesc($item['mid']), + dbesc($post['author_xchan']) + ); + + // prevent any duplicate votes by same author for oneOf and duplicate votes with same author and same answer for anyOf + + if ($r) { + if ($multi) { + foreach ($r as $rv) { + if ($rv['title'] === $content && $rv['mid'] !== $mid) { + return false; + } + } + } + else { + foreach ($r as $rv) { + if ($rv['mid'] !== $mid) { + return false; + } + } + } + } + + $answer_found = false; + $found = false; + if ($multi) { + for ($c = 0; $c < count($o['anyOf']); $c ++) { + if ($o['anyOf'][$c]['name'] === $content) { + $answer_found = true; + if (is_array($o['anyOf'][$c]['replies'])) { + foreach($o['anyOf'][$c]['replies'] as $reply) { + if(is_array($reply) && array_key_exists('id',$reply) && $reply['id'] === $mid) { + $found = true; + } + } + } + + if (! $found) { + $o['anyOf'][$c]['replies']['totalItems'] ++; + $o['anyOf'][$c]['replies']['items'][] = [ 'id' => $mid, 'type' => 'Note' ]; + } + } + } + } + else { + for ($c = 0; $c < count($o['oneOf']); $c ++) { + if ($o['oneOf'][$c]['name'] === $content) { + $answer_found = true; + if (is_array($o['oneOf'][$c]['replies'])) { + foreach($o['oneOf'][$c]['replies'] as $reply) { + if(is_array($reply) && array_key_exists('id',$reply) && $reply['id'] === $mid) { + $found = true; + } + } + } + + if (! $found) { + $o['oneOf'][$c]['replies']['totalItems'] ++; + $o['oneOf'][$c]['replies']['items'][] = [ 'id' => $mid, 'type' => 'Note' ]; + } + } + } + } + logger('updated_poll: ' . print_r($o,true),LOGGER_DATA); + if ($answer_found && ! $found) { + $x = q("update item set obj = '%s', edited = '%s' where id = %d", + dbesc(json_encode($o)), + dbesc(datetime_convert()), + intval($item['id']) + ); + Master::Summon( [ 'Notifier', 'wall-new', $item['id'] ] ); + return true; + } + + return false; + } + + + static function decode_note($act) { $response_activity = false; @@ -1613,6 +2053,7 @@ class Activity { self::actor_store($act->actor['id'],$act->actor); $s['mid'] = $act->obj['id']; + $s['uuid'] = $act->obj['diaspora:guid']; $s['parent_mid'] = $act->parent_id; if($act->data['published']) { @@ -1634,13 +2075,13 @@ class Activity { $s['expires'] = datetime_convert('UTC','UTC',$act->obj['expires']); } - - if(in_array($act->type, [ 'Like', 'Dislike', 'Flag', 'Block', 'Announce', 'Accept', 'Reject', 'TentativeAccept', 'emojiReaction' ])) { + if(ActivityStreams::is_response_activity($act->type)) { $response_activity = true; $s['mid'] = $act->id; - $s['parent_mid'] = $act->obj['id']; + // $s['parent_mid'] = $act->obj['id']; + $s['uuid'] = $act->data['diaspora:guid']; // over-ride the object timestamp with the activity @@ -1664,15 +2105,23 @@ class Activity { if($act->type === 'Dislike') { $content['content'] = sprintf( t('Doesn\'t like %1$s\'s %2$s'),$mention,$act->obj['type']) . "\n\n" . $content['content']; } - if($act->type === 'Accept' && $act->obj['type'] === 'Event' ) { - $content['content'] = sprintf( t('Will attend %1$s\'s %2$s'),$mention,$act->obj['type']) . "\n\n" . $content['content']; - } - if($act->type === 'Reject' && $act->obj['type'] === 'Event' ) { - $content['content'] = sprintf( t('Will not attend %1$s\'s %2$s'),$mention,$act->obj['type']) . "\n\n" . $content['content']; - } - if($act->type === 'TentativeAccept' && $act->obj['type'] === 'Event' ) { - $content['content'] = sprintf( t('May attend %1$s\'s %2$s'),$mention,$act->obj['type']) . "\n\n" . $content['content']; + + // handle event RSVPs + if (($act->obj['type'] === 'Event') || ($act->obj['type'] === 'Invite' && array_path_exists('object/type',$act->obj) && $act->obj['object']['type'] === 'Event')) { + if ($act->type === 'Accept') { + $content['content'] = sprintf( t('Will attend %s\'s event'),$mention) . EOL . EOL . $content['content']; + } + if ($act->type === 'Reject') { + $content['content'] = sprintf( t('Will not attend %s\'s event'),$mention) . EOL . EOL . $content['content']; + } + if ($act->type === 'TentativeAccept') { + $content['content'] = sprintf( t('May attend %s\'s event'),$mention) . EOL . EOL . $content['content']; + } + if ($act->type === 'TentativeReject') { + $content['content'] = sprintf( t('May not attend %s\'s event'),$mention) . EOL . EOL . $content['content']; + } } + if($act->type === 'Announce') { $content['content'] = sprintf( t('🔁 Repeated %1$s\'s %2$s'), $mention, $act->obj['type']); } @@ -1693,8 +2142,12 @@ class Activity { $s['verb'] = self::activity_decode_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->obj['type'] === 'Question' && $s['edited'] === $s['created']) { + $s['edited'] = datetime_convert(); + } - if($act->type === 'Tombstone' || $act->type === 'Delete' || ($act->type === 'Create' && $act->obj['type'] === 'Tombstone')) { + if(in_array($act->type, [ 'Delete', 'Undo', 'Tombstone' ]) || ($act->type === 'Create' && $act->obj['type'] === 'Tombstone')) { $s['item_deleted'] = 1; } @@ -1703,28 +2156,42 @@ class Activity { $s['obj_type'] = ACTIVITY_OBJ_COMMENT; } + $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'] = $act->obj; + $s['obj']['asld'] = $eventptr; $s['obj']['type'] = ACTIVITY_OBJ_EVENT; - $s['obj']['id'] = $act->obj['id']; - $s['obj']['title'] = $act->obj['name']; + $s['obj']['id'] = $eventptr['id']; + $s['obj']['title'] = $eventptr['name']; if(strpos($act->obj['startTime'],'Z')) $s['obj']['adjust'] = true; else $s['obj']['adjust'] = false; - $s['obj']['dtstart'] = datetime_convert('UTC','UTC',$act->obj['startTime']); + $s['obj']['dtstart'] = datetime_convert('UTC','UTC',$eventptr['startTime']); if($act->obj['endTime']) - $s['obj']['dtend'] = datetime_convert('UTC','UTC',$act->obj['endTime']); + $s['obj']['dtend'] = datetime_convert('UTC','UTC',$eventptr['endTime']); else $s['obj']['nofinish'] = true; - $s['obj']['description'] = $act->obj['content']; + $s['obj']['description'] = $eventptr['content']; - if(array_path_exists('location/content',$act->obj)) - $s['obj']['location'] = $act->obj['location']['content']; + if(array_path_exists('location/content',$eventptr)) + $s['obj']['location'] = $eventptr['location']['content']; } else { @@ -1755,16 +2222,33 @@ class Activity { } } - $a = self::decode_attachment($act->obj); - if($a) { - $s['attach'] = $a; - } + } + + $a = self::decode_attachment($act->obj); + if ($a) { + $s['attach'] = $a; + } + + $a = self::decode_iconfig($act->obj); + if ($a) { + $s['iconfig'] = $a; } if($act->obj['type'] === 'Note' && $s['attach']) { $s['body'] .= self::bb_attach($s['attach'],$s['body']); } + if ($act->obj['type'] === 'Question' && in_array($act->type,['Create','Update'])) { + if ($act->obj['endTime']) { + $s['comments_closed'] = datetime_convert('UTC','UTC', $act->obj['endTime']); + } + } + + if ($act->obj['closed']) { + $s['comments_closed'] = datetime_convert('UTC','UTC', $act->obj['closed']); + } + + // 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 @@ -1857,9 +2341,7 @@ class Activity { } - // avoid double images from hubzilla to zap/osada - - if($act->obj['type'] === 'Image' && strpos($s['body'],'zrl=') === false) { + if($act->obj['type'] === 'Image') { $ptr = null; @@ -1873,10 +2355,11 @@ class Activity { } foreach($ptr as $vurl) { if(strpos($s['body'],$vurl['href']) === false) { - $s['body'] .= '[zmg]' . $vurl['href'] . '[/zmg]' . "\n\n" . $s['body']; + $bb_imgs .= '[zmg]' . $vurl['href'] . '[/zmg]' . "\n\n"; break; } } + $s['body'] = $bb_imgs . $s['body']; } elseif(is_string($act->obj['url'])) { if(strpos($s['body'],$act->obj['url']) === false) { @@ -1957,8 +2440,19 @@ class Activity { $s['plink'] = $s['mid']; } - if($act->recips && (! in_array(ACTIVITY_PUBLIC_INBOX,$act->recips))) - $s['item_private'] = 1; + // assume this is private unless specifically told otherwise. + + $s['item_private'] = 1; + + if ($act->recips && in_array(ACTIVITY_PUBLIC_INBOX, $act->recips)) { + $s['item_private'] = 0; + } + + if (is_array($act->obj)) { + if (array_key_exists('directMessage',$act->obj) && intval($act->obj['directMessage'])) { + $s['item_private'] = 2; + } + } set_iconfig($s,'activitypub','recips',$act->raw_recips); @@ -2006,17 +2500,23 @@ class Activity { $item['aid'] = $channel['channel_account_id']; $item['uid'] = $channel['channel_id']; - $s['uuid'] = ''; - // Friendica sends the diaspora guid in a nonstandard field via AP - if($act->obj['diaspora:guid']) - $s['uuid'] = $act->obj['diaspora:guid']; + // Make sure we use the zot6 identity where applicable - if(! ( $item['author_xchan'] && $item['owner_xchan'])) { - logger('owner or author missing.'); - return; + $item['author_xchan'] = self::find_best_identity($item['author_xchan']); + $item['owner_xchan'] = self::find_best_identity($item['owner_xchan']); + + if(!$item['author_xchan']) { + logger('No author: ' . print_r($act, true)); + } + + if(!$item['owner_xchan']) { + logger('No owner: ' . print_r($act, true)); } + if(!$item['author_xchan'] || !$item['owner_xchan']) + return; + if($channel['channel_system']) { if(! MessageFilter::evaluate($item,get_config('system','pubstream_incl'),get_config('system','pubstream_excl'))) { logger('post is filtered'); @@ -2048,7 +2548,7 @@ class Activity { set_iconfig($item,'activitypub','recips',$act->raw_recips); if(! $is_parent) { - $p = q("select parent_mid from item where mid = '%s' and uid = %d limit 1", + $p = q("select parent_mid, id, obj_type from item where mid = '%s' and uid = %d limit 1", dbesc($item['parent_mid']), intval($item['uid']) ); @@ -2078,6 +2578,15 @@ class Activity { // $s['thr_parent'] = $s['mid']; } } + + + if ($p[0]['obj_type'] === 'Question') { + if ($item['obj_type'] === ACTIVITY_OBJ_NOTE && $item['title'] && (! $item['content'])) { + $item['obj_type'] = 'Answer'; + } + } + + if($p[0]['parent_mid'] !== $item['parent_mid']) { $item['thr_parent'] = $item['parent_mid']; } @@ -2156,8 +2665,8 @@ class Activity { switch($a->type) { case 'Create': case 'Update': - case 'Like': - case 'Dislike': + //case 'Like': + //case 'Dislike': case 'Announce': $item = self::decode_note($a); break; @@ -2203,6 +2712,7 @@ class Activity { static public function fetch_and_store_replies($channel, $arr) { logger('fetching replies'); + logger(print_r($arr,true)); $p = []; @@ -2447,7 +2957,7 @@ class Activity { $s['parent_mid'] = $s['mid']; - $post_type = (($parent_item['resource_type'] === 'photo') ? t('photo') : t('status')); + $post_type = (($parent_item['resource_type'] === 'photo') ? t('photo') : t('post')); $links = array(array('rel' => 'alternate','type' => 'text/html', 'href' => $parent_item['plink'])); $objtype = (($parent_item['resource_type'] === 'photo') ? ACTIVITY_OBJ_PHOTO : ACTIVITY_OBJ_NOTE ); @@ -2637,7 +3147,7 @@ class Activity { } } - if (array_key_exists('source',$act) && array_key_exists('mediaType',$act['source'])) { + if (array_path_exists('source/mediaType',$act) && array_path_exists('source/content',$act)) { if ($act['source']['mediaType'] === 'text/bbcode') { $content['bbcode'] = purify_html($act['source']['content']); } @@ -2664,5 +3174,45 @@ class Activity { return $content; } + // Find either an Authorization: Bearer token or 'token' request variable + // in the current web request and return it + + static function token_from_request() { + + foreach ( [ 'REDIRECT_REMOTE_USER', 'HTTP_AUTHORIZATION' ] as $s ) { + $auth = ((array_key_exists($s,$_SERVER) && strpos($_SERVER[$s],'Bearer ') === 0) + ? str_replace('Bearer ', EMPTY_STR, $_SERVER[$s]) + : EMPTY_STR + ); + if ($auth) { + break; + } + } + + if (! $auth) { + if (array_key_exists('token',$_REQUEST) && $_REQUEST['token']) { + $auth = $_REQUEST['token']; + } + } + + return $auth; + } + + static function find_best_identity($xchan) { + + if(filter_var($xchan, FILTER_VALIDATE_URL)) { + $r = q("select hubloc_hash, hubloc_network from hubloc where hubloc_id_url = '%s' and hubloc_network in ('zot6', 'zot') and hubloc_deleted = 0", + dbesc($xchan) + ); + if ($r) { + $r = Libzot::zot_record_preferred($r); + logger('find_best_identity: ' . $xchan . ' > ' . $r['hubloc_hash']); + return $r['hubloc_hash']; + } + } + + return $xchan; + + } } diff --git a/Zotlabs/Lib/ActivityStreams.php b/Zotlabs/Lib/ActivityStreams.php index 006744aff..a0ba52aa6 100644 --- a/Zotlabs/Lib/ActivityStreams.php +++ b/Zotlabs/Lib/ActivityStreams.php @@ -101,7 +101,13 @@ class ActivityStreams { $this->actor = $this->get_actor('attributedTo',$this->obj); } } + + // 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($this->obj['object']); + } + if($this->obj && is_array($this->obj) && $this->obj['actor']) $this->obj['actor'] = $this->get_actor('actor',$this->obj); if($this->tgt && is_array($this->tgt) && $this->tgt['actor']) @@ -140,15 +146,20 @@ class ActivityStreams { */ function collect_recips($base = '', $namespace = '') { $x = []; + $fields = [ 'to', 'cc', 'bto', 'bcc', 'audience']; foreach($fields as $f) { $y = $this->get_compound_property($f, $base, $namespace); if($y) { - $x = array_merge($x, $y); - if(! is_array($this->raw_recips)) + if (! is_array($this->raw_recips)) { $this->raw_recips = []; + } - $this->raw_recips[$f] = $x; + if (! is_array($y)) { + $y = [ $y ]; + } + $this->raw_recips[$f] = $y; + $x = array_merge($x, $y); } } // not yet ready for prime time @@ -263,12 +274,19 @@ class ActivityStreams { return self::fetch($url); } - static function fetch($url,$channel = null) { - return Activity::fetch($url,$channel); + static function fetch($url, $channel = null) { + return Activity::fetch($url, $channel); } static function is_an_actor($s) { - return(in_array($s,[ 'Application','Group','Organization','Person','Service' ])); + return (in_array($s, [ 'Application','Group','Organization','Person','Service' ])); + } + + static function is_response_activity($s) { + if (! $s) { + return false; + } + return (in_array($s, [ 'Like', 'Dislike', 'Flag', 'Block', 'Announce', 'Accept', 'Reject', 'TentativeAccept', 'TentativeReject', 'emojiReaction', 'EmojiReaction', 'EmojiReact' ])); } /** @@ -391,7 +409,6 @@ class ActivityStreams { return $x; } - static function is_as_request() { $x = getBestSupportedMimeType([ @@ -404,5 +421,4 @@ class ActivityStreams { } - -}
\ No newline at end of file +} diff --git a/Zotlabs/Lib/Apps.php b/Zotlabs/Lib/Apps.php index 69996b49d..7b980b8d3 100644 --- a/Zotlabs/Lib/Apps.php +++ b/Zotlabs/Lib/Apps.php @@ -2,6 +2,8 @@ namespace Zotlabs\Lib; +use Zotlabs\Lib\Libsync; + require_once('include/plugin.php'); require_once('include/channel.php'); @@ -74,7 +76,6 @@ class Apps { 'Directory', 'Search', 'Help', - 'Mail', 'Profile Photo' ]); @@ -371,7 +372,6 @@ class Apps { 'OAuth2 Apps Manager' => t('OAuth2 Apps Manager'), 'PDL Editor' => t('PDL Editor'), 'Permission Categories' => t('Permission Categories'), - 'Premium Channel' => t('Premium Channel'), 'Public Stream' => t('Public Stream'), 'My Chatrooms' => t('My Chatrooms'), 'Channel Export' => t('Channel Export') @@ -564,7 +564,8 @@ class Apps { '$featured' => ((strpos($papp['categories'], 'nav_featured_app') === false) ? false : true), '$pinned' => ((strpos($papp['categories'], 'nav_pinned_app') === false) ? false : true), '$navapps' => (($mode == 'nav') ? true : false), - '$order' => (($mode == 'nav-order') ? true : false), + '$order' => (($mode === 'nav-order' || $mode === 'nav-order-pinned') ? true : false), + '$mode' => $mode, '$add' => t('Add to app-tray'), '$remove' => t('Remove from app-tray'), '$add_nav' => t('Pin to navbar'), @@ -604,7 +605,7 @@ class Apps { intval(TERM_OBJ_APP), intval($r[0]['id']) ); - build_sync_packet($uid,array('app' => $r[0])); + Libsync::build_sync_packet($uid,array('app' => $r[0])); } } } @@ -670,7 +671,7 @@ class Apps { ); } if(! intval($x[0]['app_system'])) { - build_sync_packet($uid,array('app' => $x)); + Libsync::build_sync_packet($uid,array('app' => $x)); } } else { @@ -959,9 +960,6 @@ class Apps { if($list) { foreach($list as $li) { $papp = self::app_encode($li); - if($menu !== 'nav_pinned_app' && strpos($papp['categories'],'nav_pinned_app') !== false) - continue; - $syslist[] = $papp; } } diff --git a/Zotlabs/Lib/Cache.php b/Zotlabs/Lib/Cache.php index 878201a42..a5052a183 100644 --- a/Zotlabs/Lib/Cache.php +++ b/Zotlabs/Lib/Cache.php @@ -7,14 +7,23 @@ namespace Zotlabs\Lib; */ class Cache { - public static function get($key) { + + /** + * @brief Returns cached content + * + * @param string $key + * @param string $age in SQL format, default is '30 DAY' + * @return string + */ + + public static function get($key, $age = '') { $hash = hash('whirlpool',$key); $r = q("SELECT v FROM cache WHERE k = '%s' AND updated > %s - INTERVAL %s LIMIT 1", dbesc($hash), db_utcnow(), - db_quoteinterval(get_config('system','object_cache_days', '30') . ' DAY') + db_quoteinterval(($age ? $age : get_config('system','object_cache_days', '30') . ' DAY')) ); if ($r) @@ -43,4 +52,3 @@ class Cache { } } } - diff --git a/Zotlabs/Lib/Chatroom.php b/Zotlabs/Lib/Chatroom.php index 882c846cd..34853b6ab 100644 --- a/Zotlabs/Lib/Chatroom.php +++ b/Zotlabs/Lib/Chatroom.php @@ -1,6 +1,8 @@ <?php namespace Zotlabs\Lib; +use Zotlabs\Lib\Libsync; + /** * @brief A class with chatroom related static methods. */ @@ -91,7 +93,7 @@ class Chatroom { return $ret; } - build_sync_packet($channel['channel_id'],array('chatroom' => $r)); + Libsync::build_sync_packet($channel['channel_id'],array('chatroom' => $r)); q("delete from chatroom where cr_id = %d", intval($r[0]['cr_id']) diff --git a/Zotlabs/Lib/Connect.php b/Zotlabs/Lib/Connect.php new file mode 100644 index 000000000..481b02ce2 --- /dev/null +++ b/Zotlabs/Lib/Connect.php @@ -0,0 +1,312 @@ +<?php /** @file */ + +namespace Zotlabs\Lib; + +use App; +use Zotlabs\Access\Permissions; +use Zotlabs\Daemon\Master; + + + +class Connect { + + /** + * Takes a $channel and a $url/handle and adds a new connection + * + * Returns array + * $return['success'] boolean true if successful + * $return['abook'] Address book entry joined with xchan if successful + * $return['message'] error text if success is false. + * + * This function does NOT send sync packets to clones. The caller is responsible for doing this + */ + + static function connect($channel, $url, $sub_channel = false) { + + $uid = $channel['channel_id']; + + if (strpos($url,'@') === false && strpos($url,'/') === false) { + $url = $url . '@' . App::get_hostname(); + } + + $result = [ 'success' => false, 'message' => '' ]; + + $my_perms = false; + $protocol = ''; + + if (substr($url,0,1) === '[') { + $x = strpos($url,']'); + if ($x) { + $protocol = substr($url,1,$x-1); + $url = substr($url,$x+1); + } + } + + if (! check_siteallowed($url)) { + $result['message'] = t('Channel is blocked on this site.'); + return $result; + } + + if (! $url) { + $result['message'] = t('Channel location missing.'); + return $result; + } + + // check service class limits + + $r = q("select count(*) as total from abook where abook_channel = %d and abook_self = 0 ", + intval($uid) + ); + if ($r) { + $total_channels = $r[0]['total']; + } + + if (! service_class_allows($uid,'total_channels',$total_channels)) { + $result['message'] = upgrade_message(); + return $result; + } + + $xchan_hash = ''; + $sql_options = (($protocol) ? " and xchan_network = '" . dbesc($protocol) . "' " : ''); + + $r = q("select * from xchan where ( xchan_hash = '%s' or xchan_url = '%s' or xchan_addr = '%s') $sql_options ", + dbesc($url), + dbesc($url), + dbesc($url) + ); + + if ($r) { + + // reset results to the best record or the first if we don't have the best + // note: this is a single record and not an array of results + + $r = Libzot::zot_record_preferred($r,'xchan_network'); + + } + + $singleton = false; + $d = false; + + if (! $r) { + + // not in cache - try discovery + + $wf = discover_by_webbie($url,$protocol); + + if (! $wf) { + $feeds = get_config('system','feed_contacts'); + + if (($feeds) && (in_array($protocol, [ '', 'feed', 'rss' ]))) { + $d = discover_by_url($url); + } + else { + $result['message'] = t('Remote channel or protocol unavailable.'); + return $result; + } + } + } + + if ($wf || $d) { + + // something was discovered - find the record which was just created. + + $r = q("select * from xchan where ( xchan_hash = '%s' or xchan_url = '%s' or xchan_addr = '%s' ) $sql_options", + dbesc(($wf) ? $wf : $url), + dbesc($url), + dbesc($url) + ); + + // convert to a single record (once again preferring a zot solution in the case of multiples) + + if ($r) { + $r = Libzot::zot_record_preferred($r,'xchan_network'); + } + } + + // if discovery was a success or the channel was already cached we should have an xchan record in $r + + if ($r) { + $xchan = $r; + $xchan_hash = $r['xchan_hash']; + $their_perms = EMPTY_STR; + } + + // failure case + + if (! $xchan_hash) { + $result['message'] = t('Channel discovery failed.'); + logger('follow: ' . $result['message']); + return $result; + } + + if (! check_channelallowed($xchan_hash)) { + $result['message'] = t('Channel is blocked on this site.'); + logger('follow: ' . $result['message']); + return $result; + + } + + $allowed = ((in_array($xchan['xchan_network'],['rss','zot','zot6'])) ? 1 : 0); + + $hookdata = ['channel_id' => $uid, 'follow_address' => $url, 'xchan' => $xchan, 'allowed' => $allowed, 'singleton' => 0]; + call_hooks('follow_allow',$hookdata); + + if(! $hookdata['allowed']) { + $result['message'] = t('Protocol disabled.'); + return $result; + } + + $singleton = intval($hookdata['singleton']); + + // Now start processing the new connection + + $aid = $channel['channel_account_id']; + $default_group = $channel['channel_default_group']; + + if (in_array($xchan_hash, [$channel['channel_hash'], $channel['channel_portable_id']])) { + $result['message'] = t('Cannot connect to yourself.'); + return $result; + } + + if ($xchan['xchan_network'] === 'rss') { + + // check service class feed limits + + $t = q("select count(*) as total from abook where abook_account = %d and abook_feed = 1 ", + intval($aid) + ); + if ($t) { + $total_feeds = $t[0]['total']; + } + + if (! service_class_allows($uid,'total_feeds',$total_feeds)) { + $result['message'] = upgrade_message(); + return $result; + } + + // Always set these "remote" permissions for feeds since we cannot interact with them + // to negotiate a suitable permission response + + set_abconfig($uid,$xchan_hash,'their_perms','view_stream',1); + set_abconfig($uid,$xchan_hash,'their_perms','republish',1); + + } + + + $p = Permissions::connect_perms($uid); + + // parent channels have unencumbered write permission + + if ($sub_channel) { + $p['perms']['post_wall'] = 1; + $p['perms']['post_comments'] = 1; + $p['perms']['write_storage'] = 1; + $p['perms']['post_like'] = 1; + $p['perms']['delegate'] = 0; + $p['perms']['moderated'] = 0; + } + + $my_perms = $p['perms']; + + $profile_assign = get_pconfig($uid,'system','profile_assign',''); + + + // See if we are already connected by virtue of having an abook record + + $r = q("select abook_id, abook_xchan, abook_pending, abook_instance from abook + where abook_xchan = '%s' and abook_channel = %d limit 1", + dbesc($xchan_hash), + intval($uid) + ); + + if ($r) { + + $abook_instance = $r[0]['abook_instance']; + + // If they are on a non-nomadic network, add them to this location + + if (($singleton) && strpos($abook_instance,z_root()) === false) { + if ($abook_instance) { + $abook_instance .= ','; + } + $abook_instance .= z_root(); + + $x = q("update abook set abook_instance = '%s', abook_not_here = 0 where abook_id = %d", + dbesc($abook_instance), + intval($r[0]['abook_id']) + ); + } + + // if they have a pending connection, we just followed them so approve the connection request + + if (intval($r[0]['abook_pending'])) { + $x = q("update abook set abook_pending = 0 where abook_id = %d", + intval($r[0]['abook_id']) + ); + } + } + else { + + // create a new abook record + + $closeness = get_pconfig($uid,'system','new_abook_closeness',80); + + $r = abook_store_lowlevel( + [ + 'abook_account' => intval($aid), + 'abook_channel' => intval($uid), + 'abook_closeness' => intval($closeness), + 'abook_xchan' => $xchan_hash, + 'abook_profile' => $profile_assign, + 'abook_feed' => intval(($xchan['xchan_network'] === 'rss') ? 1 : 0), + 'abook_created' => datetime_convert(), + 'abook_updated' => datetime_convert(), + 'abook_instance' => (($singleton) ? z_root() : '') + ] + ); + } + + if (! $r) { + logger('abook creation failed'); + $result['message'] = t('error saving data'); + return $result; + } + + // Set suitable permissions to the connection + + if($my_perms) { + foreach($my_perms as $k => $v) { + set_abconfig($uid,$xchan_hash,'my_perms',$k,$v); + } + } + + // fetch the entire record + + $r = q("select abook.*, xchan.* from abook left join xchan on abook_xchan = xchan_hash + where abook_xchan = '%s' and abook_channel = %d limit 1", + dbesc($xchan_hash), + intval($uid) + ); + + if ($r) { + $result['abook'] = array_shift($r); + Master::Summon([ 'Notifier', 'permission_create', $result['abook']['abook_id'] ]); + } + + $arr = [ 'channel_id' => $uid, 'channel' => $channel, 'abook' => $result['abook'] ]; + + call_hooks('follow', $arr); + + /** If there is a default group for this channel, add this connection to it */ + + if ($default_group) { + $g = AccessList::rec_byhash($uid,$default_group); + if ($g) { + AccessList::member_add($uid,'',$xchan_hash,$g['id']); + } + } + + $result['success'] = true; + return $result; + } +} diff --git a/Zotlabs/Lib/Enotify.php b/Zotlabs/Lib/Enotify.php index 92a488f67..c78325ee3 100644 --- a/Zotlabs/Lib/Enotify.php +++ b/Zotlabs/Lib/Enotify.php @@ -143,19 +143,26 @@ class Enotify { $action = t('commented on'); - if(array_key_exists('item',$params) && in_array($params['item']['verb'], [ACTIVITY_LIKE, ACTIVITY_DISLIKE])) { + if(array_key_exists('item',$params)) { - if(! $always_show_in_notices || !($vnotify & VNOTIFY_LIKE)) { - logger('notification: not a visible activity. Ignoring.'); - pop_lang(); - return; - } + if(in_array($params['item']['verb'], [ACTIVITY_LIKE, ACTIVITY_DISLIKE])) { - if(activity_match($params['verb'], ACTIVITY_LIKE)) - $action = t('liked'); + if(! $always_show_in_notices || !($vnotify & VNOTIFY_LIKE)) { + logger('notification: not a visible activity. Ignoring.'); + pop_lang(); + return; + } - if(activity_match($params['verb'], ACTIVITY_DISLIKE)) - $action = t('disliked'); + if(activity_match($params['verb'], ACTIVITY_LIKE)) + $action = t('liked'); + + if(activity_match($params['verb'], ACTIVITY_DISLIKE)) + $action = t('disliked'); + + } + + if($params['item']['obj_type'] === 'Answer') + $action = t('voted on'); } @@ -550,6 +557,11 @@ class Enotify { if ((\App::$language === 'en' || (! \App::$language)) && strpos($msg,', ')) $msg = substr($msg,strpos($msg,', ')+1); + $datarray['id'] = $notify_id; + $datarray['msg'] = $msg; + + call_hooks('enotify_store_end', $datarray); + $r = q("update notify set msg = '%s' where id = %d and uid = %d", dbesc($msg), intval($notify_id), @@ -805,13 +817,17 @@ class Enotify { } else { $itemem_text = (($item['item_thread_top']) - ? t('created a new post') - : sprintf( t('commented on %s\'s post'), $item['owner']['xchan_name'])); + ? (($item['obj_type'] === 'Question') ? t('created a new poll') : t('created a new post')) + : (($item['obj_type'] === 'Answer') ? sprintf( t('voted on %s\'s poll'), '[bdi]' . $item['owner']['xchan_name'] . '[/bdi]') : sprintf( t('commented on %s\'s post'), '[bdi]' . $item['owner']['xchan_name'] . '[/bdi]')) + ); if($item['verb'] === ACTIVITY_SHARE) { - $itemem_text = sprintf( t('repeated %s\'s post'), $item['author']['xchan_name']); + $itemem_text = sprintf( t('repeated %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'); + } } $edit = false; @@ -838,15 +854,16 @@ class Enotify { 'addr' => (($item[$who]['xchan_addr']) ? $item[$who]['xchan_addr'] : $item[$who]['xchan_url']), 'url' => $item[$who]['xchan_url'], 'photo' => $item[$who]['xchan_photo_s'], - 'when' => relative_date(($edit)? $item['edited'] : $item['created']), + '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' => ((in_array($item['verb'], [ACTIVITY_LIKE, ACTIVITY_DISLIKE])) ? 'b64.' . base64url_encode($item['thr_parent']) : 'b64.' . base64url_encode($item['mid'])), - 'notify_id' => 'undefined', + 'b64mid' => (($item['mid']) ? 'b64.' . base64url_encode($item['mid']) : ''), + //'b64mid' => ((in_array($item['verb'], [ACTIVITY_LIKE, ACTIVITY_DISLIKE])) ? 'b64.' . base64url_encode($item['thr_parent']) : 'b64.' . base64url_encode($item['mid'])), 'thread_top' => (($item['item_thread_top']) ? true : false), - 'message' => strip_tags(bbcode($itemem_text)), + 'message' => bbcode(escape_tags($itemem_text)), + 'body' => htmlentities(html2plain(bbcode($item['body']), 75, true), ENT_COMPAT, 'UTF-8', false), // these are for the superblock addon 'hash' => $item[$who]['xchan_hash'], - 'uid' => local_channel(), + 'uid' => $item['uid'], 'display' => true ); @@ -858,4 +875,118 @@ class Enotify { return $x; } + static public function format_notify($tt) { + + $message = trim(strip_tags(bbcode($tt['msg']))); + + if(strpos($message, $tt['xname']) === 0) + $message = substr($message, strlen($tt['xname']) + 1); + + $mid = basename($tt['link']); + + $b64mid = ((strpos($mid, 'b64.') === 0) ? $mid : 'b64.' . base64url_encode($mid)); + $x = [ + 'notify_link' => z_root() . '/notify/view/' . $tt['id'], + 'name' => $tt['xname'], + 'url' => $tt['url'], + '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 : ''), + 'notify_id' => (($tt['otype'] == 'item') ? $tt['id'] : ''), + 'message' => $message + ]; + + return $x; + + } + + static public function format_intros($rr) { + + $x = [ + 'notify_link' => z_root() . '/connections/ifpending', + 'name' => $rr['xchan_name'], + 'addr' => $rr['xchan_addr'], + 'url' => $rr['xchan_url'], + 'photo' => $rr['xchan_photo_s'], + 'when' => datetime_convert('UTC', date_default_timezone_get(), $rr['abook_created']), + 'hclass' => ('notify-unseen'), + 'message' => t('added your channel') + ]; + + return $x; + + } + + static public function format_files($rr) { + + $x = [ + 'notify_link' => z_root() . '/sharedwithme', + 'name' => $rr['author']['xchan_name'], + 'addr' => $rr['author']['xchan_addr'], + 'url' => $rr['author']['xchan_url'], + 'photo' => $rr['author']['xchan_photo_s'], + 'when' => datetime_convert('UTC', date_default_timezone_get(), $rr['created']), + 'hclass' => ('notify-unseen'), + 'message' => t('shared a file with you') + ]; + + return $x; + + } + + static public function format_mail($rr) { + + $x = [ + 'notify_link' => z_root() . '/mail/' . $rr['id'], + 'name' => $rr['xchan_name'], + 'addr' => $rr['xchan_addr'], + 'url' => $rr['xchan_url'], + 'photo' => $rr['xchan_photo_s'], + 'when' => datetime_convert('UTC', date_default_timezone_get(), $rr['created']), + 'hclass' => (intval($rr['mail_seen']) ? 'notify-seen' : 'notify-unseen'), + 'message' => t('sent you a private message'), + ]; + + return $x; + + } + + static public function format_all_events($rr) { + + $bd_format = t('g A l F d') ; // 8 AM Friday January 18 + $strt = datetime_convert('UTC', (($rr['adjust']) ? date_default_timezone_get() : 'UTC'), $rr['dtstart']); + $today = ((substr($strt, 0, 10) === datetime_convert('UTC', date_default_timezone_get(), 'now', 'Y-m-d')) ? true : false); + $when = day_translate(datetime_convert('UTC', (($rr['adjust']) ? date_default_timezone_get() : 'UTC'), $rr['dtstart'], $bd_format)) . (($today) ? ' ' . t('[today]') : ''); + + $x = [ + 'notify_link' => z_root() . '/cdav/calendar/' . $rr['event_hash'], + 'name' => $rr['xchan_name'], + 'addr' => $rr['xchan_addr'], + 'url' => $rr['xchan_url'], + 'photo' => $rr['xchan_photo_s'], + 'when' => $when, + 'hclass' => (($today) ? 'notify-unseen bg-warning' : 'notify-unseen'), + 'message' => t('created an event') + ]; + + return $x; + + } + + static public function format_register($rr) { + + $x = [ + 'notify_link' => z_root() . '/admin/accounts', + 'name' => $rr['account_email'], + //'addr' => $rr['account_email'], + 'photo' => z_root() . '/' . get_default_profile_photo(48), + 'when' => datetime_convert('UTC', date_default_timezone_get(),$rr['account_created']), + 'hclass' => ('notify-unseen'), + 'message' => t('requires approval') + ]; + + return $x; + + } } diff --git a/Zotlabs/Lib/JSalmon.php b/Zotlabs/Lib/JSalmon.php index bed748432..67512046f 100644 --- a/Zotlabs/Lib/JSalmon.php +++ b/Zotlabs/Lib/JSalmon.php @@ -69,4 +69,4 @@ class JSalmon { } -}
\ No newline at end of file +} diff --git a/Zotlabs/Lib/Libsync.php b/Zotlabs/Lib/Libsync.php index d93270bc5..cff320e11 100644 --- a/Zotlabs/Lib/Libsync.php +++ b/Zotlabs/Lib/Libsync.php @@ -83,7 +83,7 @@ class Libsync { $info = (($packet) ? $packet : array()); $info['type'] = 'sync'; - $info['encoding'] = 'red'; // note: not zot, this packet is very platform specific + $info['encoding'] = 'hz'; // note: not zot, this packet is very platform specific $info['relocate'] = ['channel_address' => $channel['channel_address'], 'url' => z_root() ]; if(array_key_exists($uid,\App::$config) && array_key_exists('transient',\App::$config[$uid])) { @@ -144,12 +144,13 @@ class Libsync { foreach($synchubs as $hub) { $hash = random_string(); - $n = Libzot::build_packet($channel,'sync',$env_recips,json_encode($info),'red',$hub['hubloc_sitekey'],$hub['site_crypto']); + $n = Libzot::build_packet($channel,'sync',$env_recips,json_encode($info),'hz',$hub['hubloc_sitekey'],$hub['site_crypto']); Queue::insert(array( 'hash' => $hash, 'account_id' => $channel['channel_account_id'], 'channel_id' => $channel['channel_id'], 'posturl' => $hub['hubloc_callback'], + 'driver' => $hub['hubloc_network'], 'notify' => $n, 'msg' => EMPTY_STR )); @@ -244,7 +245,13 @@ class Libsync { if(array_key_exists('app',$arr) && $arr['app']) sync_apps($channel,$arr['app']); - + + if(array_key_exists('addressbook',$arr) && $arr['addressbook']) + sync_addressbook($channel,$arr['addressbook']); + + if(array_key_exists('calendar',$arr) && $arr['calendar']) + sync_calendar($channel,$arr['calendar']); + if(array_key_exists('chatroom',$arr) && $arr['chatroom']) sync_chatrooms($channel,$arr['chatroom']); @@ -812,9 +819,9 @@ class Libsync { } if(intval($r[0]['hubloc_primary']) && (! $location['primary'])) { - $m = q("update hubloc set hubloc_primary = 0, hubloc_updated = '%s' where hubloc_id = %d", + $m = q("update hubloc set hubloc_primary = 0, hubloc_updated = '%s' where hubloc_id_url = '%s'", dbesc(datetime_convert()), - intval($r[0]['hubloc_id']) + dbesc($r[0]['hubloc_id_url']) ); $r[0]['hubloc_primary'] = intval($location['primary']); hubloc_change_primary($r[0]); @@ -841,18 +848,18 @@ class Libsync { } } if(intval($r[0]['hubloc_deleted']) && (! intval($location['deleted']))) { - $n = q("update hubloc set hubloc_deleted = 0, hubloc_updated = '%s' where hubloc_id = %d", + $n = q("update hubloc set hubloc_deleted = 0, hubloc_updated = '%s' where hubloc_id_url = '%s'", dbesc(datetime_convert()), - intval($r[0]['hubloc_id']) + dbesc($r[0]['hubloc_id_url']) ); $what .= 'undelete_hub '; $changed = true; } elseif((! intval($r[0]['hubloc_deleted'])) && (intval($location['deleted']))) { logger('deleting hubloc: ' . $r[0]['hubloc_addr']); - $n = q("update hubloc set hubloc_deleted = 1, hubloc_updated = '%s' where hubloc_id = %d", + $n = q("update hubloc set hubloc_deleted = 1, hubloc_updated = '%s' where hubloc_id_url = '%s'", dbesc(datetime_convert()), - intval($r[0]['hubloc_id']) + dbesc($r[0]['hubloc_id_url']) ); $what .= 'delete_hub '; $changed = true; @@ -911,9 +918,9 @@ class Libsync { foreach($xisting as $x) { if(! array_key_exists('updated',$x)) { logger('Deleting unreferenced hub location ' . $x['hubloc_addr']); - $r = q("update hubloc set hubloc_deleted = 1, hubloc_updated = '%s' where hubloc_id = %d", + $r = q("update hubloc set hubloc_deleted = 1, hubloc_updated = '%s' where hubloc_id_url = '%s'", dbesc(datetime_convert()), - intval($x['hubloc_id']) + dbesc($x['hubloc_id_url']) ); $what .= 'removed_hub '; $changed = true; @@ -1016,4 +1023,4 @@ class Libsync { } -}
\ No newline at end of file +} diff --git a/Zotlabs/Lib/Libzot.php b/Zotlabs/Lib/Libzot.php index 100d45c05..f0fe3ab24 100644 --- a/Zotlabs/Lib/Libzot.php +++ b/Zotlabs/Lib/Libzot.php @@ -105,7 +105,7 @@ class Libzot { $data = [ 'type' => $type, 'encoding' => $encoding, - 'sender' => $channel['channel_portable_id'], + 'sender' => $channel['channel_hash'], 'site_id' => self::make_xchan_hash(z_root(), get_config('system','pubkey')), 'version' => System::get_zot_revision(), ]; @@ -316,16 +316,20 @@ class Libzot { $x = self::import_xchan($record['data'], (($force) ? UPDATE_FLAGS_FORCED : UPDATE_FLAGS_UPDATED)); + if(! $x['success']) return false; if($channel && $record['data']['permissions']) { $permissions = explode(',',$record['data']['permissions']); + if($permissions && is_array($permissions)) { $old_read_stream_perm = get_abconfig($channel['channel_id'],$x['hash'],'their_perms','view_stream'); - foreach($permissions as $p) { - set_abconfig($channel['channel_id'],$x['hash'],'their_perms',$p,'1'); + $permissions = Permissions::FilledPerms($permissions); + + foreach($permissions as $k => $v) { + set_abconfig($channel['channel_id'],$x['hash'],'their_perms',$k,$v); } } @@ -422,7 +426,7 @@ class Libzot { [ 'type' => NOTIFY_INTRO, 'from_xchan' => $x['hash'], - 'to_xchan' => $channel['channel_portable_id'], + 'to_xchan' => $channel['channel_hash'], 'link' => z_root() . '/connedit/' . $new_connection[0]['abook_id'] ] ); @@ -788,7 +792,7 @@ class Libzot { // see if this is a channel clone that's hosted locally - which we treat different from other xchans/connections - $local = q("select channel_account_id, channel_id from channel where channel_portable_id = '%s' limit 1", + $local = q("select channel_account_id, channel_id from channel where channel_hash = '%s' limit 1", dbesc($xchan_hash) ); if($local) { @@ -1133,6 +1137,7 @@ class Libzot { } logger($AS->debug(),LOGGER_DATA); + } @@ -1151,7 +1156,7 @@ class Libzot { if($recip_arr) { stringify_array_elms($recip_arr,true); $recips = implode(',',$recip_arr); - $r = q("select channel_portable_id as hash from channel where channel_portable_id in ( " . $recips . " ) and channel_removed = 0 "); + $r = q("select channel_hash as hash from channel where channel_hash in ( " . $recips . " ) and channel_removed = 0 "); } if(! $r) { @@ -1193,10 +1198,6 @@ class Libzot { if(in_array($env['type'],['activity','response'])) { - $arr = Activity::decode_note($AS); - - //logger($AS->debug()); - $r = q("select hubloc_hash, hubloc_network from hubloc where hubloc_id_url = '%s' ", dbesc($AS->actor['id']) ); @@ -1207,6 +1208,10 @@ class Libzot { $arr['author_xchan'] = $r['hubloc_hash']; } + if (! $arr['author_xchan']) { + logger('No author!'); + return; + } $s = q("select hubloc_hash from hubloc where hubloc_id_url = '%s' and hubloc_network = 'zot6' limit 1", dbesc($env['sender']) @@ -1220,8 +1225,8 @@ class Libzot { $arr['owner_xchan'] = $env['sender']; } - if($private) { - $arr['item_private'] = true; + if ($private && (! intval($arr['item_private']))) { + $arr['item_private'] = 1; } if ($arr['mid'] === $arr['parent_mid']) { @@ -1277,7 +1282,12 @@ class Libzot { logger('Channel sync received: ' . print_r($arr,true), LOGGER_DATA, LOG_DEBUG); logger('Channel sync recipients: ' . print_r($deliveries,true), LOGGER_DATA, LOG_DEBUG); - $result = Libsync::process_channel_sync_delivery($env['sender'],$arr,$deliveries); + if ($env['encoding'] === 'hz') { + $result = Libsync::process_channel_sync_delivery($env['sender'],$arr,$deliveries); + } + else { + logger('sync packet type not supported.'); + } } } if ($result) { @@ -1363,12 +1373,12 @@ class Libzot { $r = []; - $c = q("select channel_id, channel_portable_id from channel where channel_removed = 0"); + $c = q("select channel_id, channel_hash from channel where channel_removed = 0"); if($c) { foreach($c as $cc) { if(perm_is_allowed($cc['channel_id'],$msg['sender'],$perm)) { - $r[] = $cc['channel_portable_id']; + $r[] = $cc['channel_hash']; } } } @@ -1376,7 +1386,7 @@ class Libzot { if($include_sys) { $sys = get_sys_channel(); if($sys) - $r[] = $sys['channel_portable_id']; + $r[] = $sys['channel_hash']; } @@ -1392,7 +1402,7 @@ class Libzot { if($tag['type'] === 'Mention' && (strpos($tag['href'],z_root()) !== false)) { $address = basename($tag['href']); if($address) { - $z = q("select channel_portable_id as hash from channel where channel_address = '%s' + $z = q("select channel_hash as hash from channel where channel_address = '%s' and channel_removed = 0 limit 1", dbesc($address) ); @@ -1413,7 +1423,7 @@ class Libzot { $thread_parent = self::find_parent($msg,$act); if($thread_parent) { - $z = q("select channel_portable_id as hash from channel left join item on channel.channel_id = item.uid where ( item.thr_parent = '%s' OR item.parent_mid = '%s' ) ", + $z = q("select channel_hash as hash from channel left join item on channel.channel_id = item.uid where ( item.thr_parent = '%s' OR item.parent_mid = '%s' ) ", dbesc($thread_parent), dbesc($thread_parent) ); @@ -1468,7 +1478,7 @@ class Libzot { $DR = new DReport(z_root(),$sender,$d,$arr['mid']); - $channel = channelx_by_portid($d); + $channel = channelx_by_hash($d); if (! $channel) { $DR->update('recipient not found'); @@ -1483,13 +1493,14 @@ class Libzot { // Try again using the delivery channel credentials. // We will also need to re-parse the $item array, // but preserve any values that were set during anonymous parsing. - + $o = Activity::fetch($act->obj,$channel); if($o) { $act->obj = $o; $arr = array_merge(Activity::decode_note($act),$arr); } else { + $DR->update('Incomplete or corrupt activity'); $result[] = $DR->get(); continue; @@ -1505,7 +1516,7 @@ class Libzot { * access checks. */ - if($sender === $channel['channel_portable_id'] && $arr['author_xchan'] === $channel['channel_portable_id'] && $arr['mid'] === $arr['parent_mid']) { + if($sender === $channel['channel_hash'] && $arr['author_xchan'] === $channel['channel_hash'] && $arr['mid'] === $arr['parent_mid']) { $DR->update('self delivery ignored'); $result[] = $DR->get(); continue; @@ -1608,10 +1619,11 @@ class Libzot { // As a side effect we will also do a preliminary check that we have the top-level-post, otherwise // processing it is pointless. - $r = q("select route, id, owner_xchan, item_private from item where mid = '%s' and uid = %d limit 1", + $r = q("select route, id, parent_mid, mid, owner_xchan, item_private, obj_type from item where mid = '%s' and uid = %d limit 1", dbesc($arr['parent_mid']), intval($channel['channel_id']) ); + if(! $r) { $DR->update('comment parent not found'); $result[] = $DR->get(); @@ -1634,6 +1646,16 @@ class Libzot { continue; } + if ($r[0]['obj_type'] === 'Question') { + // route checking doesn't work correctly here because we've changed the privacy + $r[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'])) { + $arr['obj_type'] = 'Answer'; + } + } + + if($relay || $friendofriend || (intval($r[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 @@ -1701,7 +1723,7 @@ class Libzot { $arr['aid'] = $channel['channel_account_id']; $arr['uid'] = $channel['channel_id']; - $item_id = self::delete_imported_item($sender,$arr,$channel['channel_id'],$relay); + $item_id = self::delete_imported_item($sender,$act,$arr,$channel['channel_id'],$relay); $DR->update(($item_id) ? 'deleted' : 'delete_failed'); $result[] = $DR->get(); @@ -1715,11 +1737,15 @@ class Libzot { continue; } + // 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 = '%s' and uid = %d limit 1", + $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'])), intval($channel['channel_id']) ); + if($r) { // We already have this post. $item_id = $r[0]['id']; @@ -1811,7 +1837,7 @@ class Libzot { $stored = (($item_result && $item_result['item']) ? $item_result['item'] : false); if((is_array($stored)) && ($stored['id'] != $stored['parent']) - && ($stored['author_xchan'] === $channel['channel_hash'] || $stored['author_xchan'] === $channel['channel_portable_id'])) { + && ($stored['author_xchan'] === $channel['channel_hash'] || $stored['author_xchan'] === $channel['channel_hash'])) { retain_item($stored['item']['parent']); } @@ -1933,9 +1959,9 @@ class Libzot { } logger('FOF Activity received: ' . print_r($arr,true), LOGGER_DATA, LOG_DEBUG); - logger('FOF Activity recipient: ' . $channel['channel_portable_id'], LOGGER_DATA, LOG_DEBUG); + logger('FOF Activity recipient: ' . $channel['channel_hash'], LOGGER_DATA, LOG_DEBUG); - $result = self::process_delivery($arr['owner_xchan'],$AS, $arr, [ $channel['channel_portable_id'] ],false,false,true); + $result = self::process_delivery($arr['owner_xchan'],$AS, $arr, [ $channel['channel_hash'] ],false,false,true); if ($result) { $ret = array_merge($ret, $result); } @@ -2080,7 +2106,7 @@ class Libzot { * @return boolean|int post_id */ - static function delete_imported_item($sender, $item, $uid, $relay) { + static function delete_imported_item($sender, $act, $item, $uid, $relay) { logger('invoked', LOGGER_DEBUG); @@ -2088,38 +2114,39 @@ class Libzot { $item_found = false; $post_id = 0; + if ($item['verb'] === 'Tombstone') { + // The id of the deleted thing is the item mid (activity id) + $mid = $item['mid']; + } + else { + // The id is the object id if the type is Undo or Delete + $mid = ((is_array($act->obj)) ? $act->obj['id'] : $act->obj); + } + + // we may have stored either the object id or the activity id if it was a response activity (like, dislike, etc.) + $r = q("select * from item where ( author_xchan = '%s' or owner_xchan = '%s' or source_xchan = '%s' ) - and mid = '%s' and uid = %d limit 1", + and mid IN ('%s', '%s') and uid = %d limit 1", dbesc($sender), dbesc($sender), dbesc($sender), - dbesc($item['mid']), + dbesc($mid), + dbesc(str_replace('/activity/','/item/',$mid)), intval($uid) ); if($r) { $stored = $r[0]; - if($stored['author_xchan'] === $sender || $stored['owner_xchan'] === $sender || $stored['source_xchan'] === $sender) - $ownership_valid = true; + // we proved ownership in the sql query + $ownership_valid = true; $post_id = $stored['id']; $item_found = true; } else { - - // perhaps the item is still in transit and the delete notification got here before the actual item did. Store it with the deleted flag set. - // item_store() won't try to deliver any notifications or start delivery chains if this flag is set. - // This means we won't end up with potentially even more delivery threads trying to push this delete notification. - // But this will ensure that if the (undeleted) original post comes in at a later date, we'll reject it because it will have an older timestamp. - - logger('delete received for non-existent item - storing item data.'); - - if($item['author_xchan'] === $sender || $item['owner_xchan'] === $sender || $item['source_xchan'] === $sender) { - $ownership_valid = true; - $item_result = item_store($item); - $post_id = $item_result['item_id']; - } + // this will fail with an ownership issue, so explain the real reason + logger('delete received for non-existent item or not owned by sender - ignoring.'); } if($ownership_valid === false) { @@ -2191,7 +2218,7 @@ class Libzot { $DR = new DReport(z_root(),$sender,$d,$arr['mid']); - $r = q("select * from channel where channel_portable_id = '%s' limit 1", + $r = q("select * from channel where channel_hash = '%s' limit 1", dbesc($d['hash']) ); @@ -2346,7 +2373,7 @@ class Libzot { $loc = $locations[0]; - $r = q("select * from channel where channel_portable_id = '%s' limit 1", + $r = q("select * from channel where channel_hash = '%s' limit 1", dbesc($sender_hash) ); @@ -2354,7 +2381,7 @@ class Libzot { return; if($loc['url'] !== z_root()) { - $x = q("update channel set channel_moved = '%s' where channel_portable_id = '%s' limit 1", + $x = q("update channel set channel_moved = '%s' where channel_hash = '%s' limit 1", dbesc($loc['url']), dbesc($sender_hash) ); @@ -2388,7 +2415,7 @@ class Libzot { static function encode_locations($channel) { $ret = []; - $x = self::get_hublocs($channel['channel_portable_id']); + $x = self::get_hublocs($channel['channel_hash']); if($x && count($x)) { foreach($x as $hub) { @@ -2736,13 +2763,13 @@ class Libzot { $r = null; if(strlen($zhash)) { - $r = q("select channel.*, xchan.* from channel left join xchan on channel_portable_id = xchan_hash - where channel_portable_id = '%s' limit 1", + $r = q("select channel.*, xchan.* from channel left join xchan on channel_hash = xchan_hash + where channel_hash = '%s' limit 1", dbesc($zhash) ); } elseif(strlen($zguid) && strlen($zguid_sig)) { - $r = q("select channel.*, xchan.* from channel left join xchan on channel_portable_id = xchan_hash + $r = q("select channel.*, xchan.* from channel left join xchan on channel_hash = xchan_hash where channel_guid = '%s' and channel_guid_sig = '%s' limit 1", dbesc($zguid), dbesc($zguid_sig) @@ -2750,7 +2777,7 @@ class Libzot { } elseif(strlen($zaddr)) { if(strpos($zaddr,'[system]') === false) { /* normal address lookup */ - $r = q("select channel.*, xchan.* from channel left join xchan on channel_portable_id = xchan_hash + $r = q("select channel.*, xchan.* from channel left join xchan on channel_hash = xchan_hash where ( channel_address = '%s' or xchan_addr = '%s' ) limit 1", dbesc($zaddr), dbesc($zaddr) @@ -2770,10 +2797,10 @@ class Libzot { * */ - $r = q("select channel.*, xchan.* from channel left join xchan on channel_portable_id = xchan_hash + $r = q("select channel.*, xchan.* from channel left join xchan on channel_hash = xchan_hash where channel_system = 1 order by channel_id limit 1"); if(! $r) { - $r = q("select channel.*, xchan.* from channel left join xchan on channel_portable_id = xchan_hash + $r = q("select channel.*, xchan.* from channel left join xchan on channel_hash = xchan_hash where channel_removed = 0 order by channel_id limit 1"); } } @@ -2892,7 +2919,7 @@ class Libzot { ]; $ret['channel_role'] = get_pconfig($e['channel_id'],'system','permissions_role','custom'); - $ret['protocols'] = [ 'zot', 'zot6' ]; + $ret['protocols'] = [ 'zot6', 'zot' ]; $ret['searchable'] = $searchable; $ret['adult_content'] = $adult_channel; $ret['public_forum'] = $public_forum; diff --git a/Zotlabs/Lib/Libzotdir.php b/Zotlabs/Lib/Libzotdir.php index 1cb52275c..b02516a98 100644 --- a/Zotlabs/Lib/Libzotdir.php +++ b/Zotlabs/Lib/Libzotdir.php @@ -3,6 +3,8 @@ namespace Zotlabs\Lib; use Zotlabs\Lib\Libzot; +use Zotlabs\Lib\Zotfinger; +use Zotlabs\Lib\Webfinger; require_once('include/permissions.php'); @@ -307,9 +309,9 @@ class Libzotdir { if ($ud['ud_addr'] && (! ($ud['ud_flags'] & UPDATE_FLAGS_DELETED))) { $success = false; - $href = \Zotlabs\Lib\Webfinger::zot_url(punify($ud['ud_addr'])); + $href = Webfinger::zot_url(punify($ud['ud_addr'])); if($href) { - $zf = \Zotlabs\Lib\Zotfinger::exec($href); + $zf = Zotfinger::exec($href); } if(is_array($zf) && array_path_exists('signature/signer',$zf) && $zf['signature']['signer'] === $href && intval($zf['signature']['header_valid'])) { $xc = Libzot::import_xchan($zf['data'], 0, $ud); @@ -339,7 +341,7 @@ class Libzotdir { logger('local_dir_update: uid: ' . $uid, LOGGER_DEBUG); - $p = q("select channel.channel_hash, channel_address, channel_timezone, profile.* from profile left join channel on channel_id = uid where uid = %d and is_default = 1", + $p = q("select channel.channel_hash, channel_address, channel_timezone, channel_portable_id, profile.* from profile left join channel on channel_id = uid where uid = %d and is_default = 1", intval($uid) ); @@ -348,6 +350,7 @@ class Libzotdir { if ($p) { $hash = $p[0]['channel_hash']; + $legacy_hash = $p[0]['channel_portable_id']; $profile['description'] = $p[0]['pdesc']; $profile['birthday'] = $p[0]['dob']; @@ -381,14 +384,15 @@ class Libzotdir { logger('hidden: ' . $hidden); - $r = q("select xchan_hidden from xchan where xchan_hash = '%s' limit 1", + $r = q("select xchan_hidden from xchan where xchan_hash = '%s'", dbesc($p[0]['channel_hash']) ); if(intval($r[0]['xchan_hidden']) != $hidden) { - $r = q("update xchan set xchan_hidden = %d where xchan_hash = '%s'", + $r = q("update xchan set xchan_hidden = %d where xchan_hash in ('%s', '%s')", intval($hidden), - dbesc($p[0]['channel_hash']) + dbesc($hash), + dbesc($legacy_hash) ); } @@ -402,11 +406,13 @@ class Libzotdir { } else { // they may have made it private - $r = q("delete from xprof where xprof_hash = '%s'", - dbesc($hash) + q("delete from xprof where xprof_hash in ('%s', '%s')", + dbesc($hash), + dbesc($legacy_hash) ); - $r = q("delete from xtag where xtag_hash = '%s'", - dbesc($hash) + q("delete from xtag where xtag_hash in ('%s', '%s')", + dbesc($hash), + dbesc($legacy_hash) ); } diff --git a/Zotlabs/Lib/NativeWiki.php b/Zotlabs/Lib/NativeWiki.php index 662fddad0..3ec032075 100644 --- a/Zotlabs/Lib/NativeWiki.php +++ b/Zotlabs/Lib/NativeWiki.php @@ -2,6 +2,8 @@ namespace Zotlabs\Lib; +use Zotlabs\Lib\Libsync; + define ( 'NWIKI_ITEM_RESOURCE_TYPE', 'nwiki' ); class NativeWiki { @@ -71,7 +73,7 @@ class NativeWiki { $arr['item_thread_top'] = 1; $arr['item_private'] = intval($acl->is_private()); $arr['verb'] = ACTIVITY_CREATE; - $arr['obj_type'] = ACTIVITY_OBJ_WIKI; + $arr['obj_type'] = 'Document'; $arr['body'] = '[table][tr][td][h1]New Wiki[/h1][/td][/tr][tr][td][zrl=' . $wiki_url . ']' . $wiki['htmlName'] . '[/zrl][/td][/tr][/table]'; $arr['public_policy'] = map_scope(\Zotlabs\Access\PermissionLimits::Get($channel['channel_id'],'view_wiki'),true); @@ -178,7 +180,7 @@ class NativeWiki { foreach($sync_item as $w) { $pkt[] = encode_item($w,true); } - build_sync_packet($uid,array('wiki' => $pkt)); + Libsync::build_sync_packet($uid,array('wiki' => $pkt)); } } } diff --git a/Zotlabs/Lib/NativeWikiPage.php b/Zotlabs/Lib/NativeWikiPage.php index dddd26af3..d84cc50a8 100644 --- a/Zotlabs/Lib/NativeWikiPage.php +++ b/Zotlabs/Lib/NativeWikiPage.php @@ -530,8 +530,11 @@ class NativeWikiPage { foreach ($match[1] as $m) { // TODO: Why do we need to double urlencode for this to work? //$pageURLs[] = urlencode(urlencode(escape_tags($m))); - $pageURLs[] = Zlib\NativeWiki::name_encode(escape_tags($m)); - $pages[] = $m; + $titleUri = explode('|',$m); + $page = $titleUri[0] ?? ''; + $title = $titleUri[1] ?? $page; + $pageURLs[] = Zlib\NativeWiki::name_encode(escape_tags($page)); + $pages[] = $title; } $idx = 0; while(strpos($s,'[[') !== false) { diff --git a/Zotlabs/Lib/Queue.php b/Zotlabs/Lib/Queue.php index 49891a55b..6acc58bc5 100644 --- a/Zotlabs/Lib/Queue.php +++ b/Zotlabs/Lib/Queue.php @@ -116,7 +116,7 @@ class Queue { dbesc($arr['hash']), intval($arr['account_id']), intval($arr['channel_id']), - dbesc(($arr['driver']) ? $arr['driver'] : 'zot'), + dbesc(($arr['driver']) ? $arr['driver'] : 'zot6'), dbesc($arr['posturl']), intval(1), intval(($arr['priority']) ? $arr['priority'] : 0), diff --git a/Zotlabs/Lib/Share.php b/Zotlabs/Lib/Share.php index 3a2ab1783..419e6ed5f 100644 --- a/Zotlabs/Lib/Share.php +++ b/Zotlabs/Lib/Share.php @@ -2,6 +2,7 @@ namespace Zotlabs\Lib; +use Zotlabs\Lib\Activity; class Share { @@ -54,7 +55,7 @@ class Share { if(! $this->item) return $obj; - $obj['asld'] = $this->item['mid']; + $obj['asld'] = Activity::fetch_item( [ 'id' => $this->item['mid'] ] ); $obj['type'] = $this->item['obj_type']; $obj['id'] = $this->item['mid']; $obj['content'] = $this->item['body']; @@ -127,7 +128,7 @@ class Share { "' profile='" . $this->item['author']['xchan_url'] . "' avatar='" . $this->item['author']['xchan_photo_s'] . "' link='" . $this->item['plink'] . - "' auth='" . (($this->item['author']['network'] === 'zot') ? 'true' : 'false') . + "' auth='" . ((in_array($this->item['author']['xchan_network'], ['zot6', 'zot'])) ? 'true' : 'false') . "' posted='" . $this->item['created'] . "' message_id='" . $this->item['mid'] . "']"; diff --git a/Zotlabs/Lib/System.php b/Zotlabs/Lib/System.php index 7bf1343bb..3cc46fbda 100644 --- a/Zotlabs/Lib/System.php +++ b/Zotlabs/Lib/System.php @@ -5,9 +5,14 @@ namespace Zotlabs\Lib; class System { static public function get_platform_name() { - if(is_array(\App::$config) && is_array(\App::$config['system']) && array_key_exists('platform_name',\App::$config['system'])) - return \App::$config['system']['platform_name']; - return PLATFORM_NAME; + static $platform_name = ''; + if(empty($platform_name)) { + if(is_array(\App::$config) && is_array(\App::$config['system']) && array_key_exists('platform_name',\App::$config['system'])) + $platform_name = \App::$config['system']['platform_name']; + else + $platform_name = PLATFORM_NAME; + } + return $platform_name; } static public function get_site_name() { diff --git a/Zotlabs/Lib/ThreadItem.php b/Zotlabs/Lib/ThreadItem.php index 667ea269a..024502d2a 100644 --- a/Zotlabs/Lib/ThreadItem.php +++ b/Zotlabs/Lib/ThreadItem.php @@ -78,7 +78,7 @@ class ThreadItem { */ public function get_template_data($conv_responses, $thread_level=1, $conv_flags = []) { - + $result = array(); $item = $this->get_data(); @@ -95,7 +95,7 @@ class ThreadItem { $total_children = $this->count_descendants(); $unseen_comments = (($item['real_uid']) ? 0 : $this->count_unseen_descendants()); - $conv = $this->get_conversation(); + $conv = $this->get_conversation(); $observer = $conv->get_observer(); $lock = (((intval($item['item_private'])) || (($item['uid'] == local_channel()) && (strlen($item['allow_cid']) || strlen($item['allow_gid']) @@ -113,7 +113,7 @@ class ThreadItem { if(intval($item['item_private']) && ($item['owner']['xchan_network'] === 'activitypub')) { $recips = get_iconfig($item['parent'], 'activitypub', 'recips'); - if(! in_array($observer['xchan_url'], $recips['to'])) + if(! is_array($recips['to']) || ! in_array($observer['xchan_url'], $recips['to'])) $privacy_warning = true; } @@ -150,9 +150,11 @@ class ThreadItem { $edpost = false; - if($observer['xchan_hash'] == $this->get_data_value('author_xchan') + if($observer && $observer['xchan_hash'] + && ($observer['xchan_hash'] == $this->get_data_value('author_xchan') || $observer['xchan_hash'] == $this->get_data_value('owner_xchan') - || $this->get_data_value('uid') == local_channel()) + || $observer['xchan_hash'] == $this->get_data_value('source_xchan') + || $this->get_data_value('uid') == local_channel())) $dropping = true; @@ -204,6 +206,10 @@ class ThreadItem { } } + if($item['obj_type'] === 'Question') { + $response_verbs[] = 'answer'; + } + $consensus = (intval($item['item_consensus']) ? true : false); if($consensus) { $response_verbs[] = 'agree'; @@ -281,12 +287,16 @@ class ThreadItem { $settings = ''; + $tagger = []; + // FIXME - check this permission if($conv->get_profile_owner() == local_channel()) { + /* disable until we agree on how to implemnt this in zot6/activitypub $tagger = array( 'tagit' => t("Add Tag"), 'classtagger' => "", ); + */ $settings = t('Conversation Tools'); } @@ -346,7 +356,7 @@ class ThreadItem { $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('%d unseen',$unseen_comments) : ''); + $list_unseen_txt = (($unseen_comments) ? sprintf( t('%d unseen'),$unseen_comments) : ''); $children = $this->get_children(); @@ -356,11 +366,28 @@ class ThreadItem { call_hooks('dropdown_extras',$dropdown_extras_arr); $dropdown_extras = $dropdown_extras_arr['dropdown_extras']; + $midb64 = 'b64.' . base64url_encode($item['mid']); + $mids = [ $midb64 ]; + $response_mids = []; + foreach($response_verbs as $v) { + if(isset($conv_responses[$v]['mids'][$item['mid']])) { + $response_mids = array_merge($response_mids, $conv_responses[$v]['mids'][$item['mid']]); + } + } + + $mids = array_merge($mids, $response_mids); + $json_mids = json_encode($mids); + + // Pinned item processing + $allowed_type = (in_array($item['item_type'], get_config('system', 'pin_types', [ ITEM_TYPE_POST ])) ? true : false); + $pinned_items = ($allowed_type ? get_pconfig($item['uid'], 'pinned', $item['item_type'], []) : []); + $pinned = ((!empty($pinned_items) && in_array($midb64, $pinned_items)) ? true : false); + $tmp_item = array( 'template' => $this->get_template(), 'mode' => $mode, 'item_type' => intval($item['item_type']), - 'type' => implode("",array_slice(explode("/",$item['verb']),-1)), + //'type' => implode("",array_slice(explode("/",$item['verb']),-1)), 'body' => $body['html'], 'tags' => $body['tags'], 'categories' => $body['categories'], @@ -369,7 +396,8 @@ class ThreadItem { 'folders' => $body['folders'], 'text' => strip_tags($body['html']), 'id' => $this->get_id(), - 'mid' => $item['mid'], + 'mid' => $midb64, + 'mids' => $json_mids, 'parent' => $item['parent'], 'author_id' => (($item['author']['xchan_addr']) ? $item['author']['xchan_addr'] : $item['author']['xchan_url']), 'isevent' => $isevent, @@ -377,8 +405,8 @@ class ThreadItem { 'consensus' => $consensus, 'conlabels' => $conlabels, 'canvote' => $canvote, - 'linktitle' => sprintf( t('View %s\'s profile - %s'), $profile_name, (($item['author']['xchan_addr']) ? $item['author']['xchan_addr'] : $item['author']['xchan_url'])), - 'olinktitle' => sprintf( t('View %s\'s profile - %s'), $this->get_owner_name(), (($item['owner']['xchan_addr']) ? $item['owner']['xchan_addr'] : $item['owner']['xchan_url'])), + 'linktitle' => (($item['author']['xchan_addr']) ? $item['author']['xchan_addr'] : $item['author']['xchan_url']), + 'olinktitle' => (($item['owner']['xchan_addr']) ? $item['owner']['xchan_addr'] : $item['owner']['xchan_url']), 'llink' => $item['llink'], 'viewthread' => $viewthread, 'to' => t('to'), @@ -396,7 +424,7 @@ class ThreadItem { 'sparkle' => $sparkle, 'title' => $item['title'], 'title_tosource' => get_pconfig($conv->get_profile_owner(),'system','title_tosource'), - 'ago' => relative_date($item['created']), + //'ago' => relative_date($item['created']), 'app' => $item['app'], 'str_app' => sprintf( t('from %s'), $item['app']), 'isotime' => datetime_convert('UTC', date_default_timezone_get(), $item['created'], 'c'), @@ -404,6 +432,7 @@ class ThreadItem { 'editedtime' => (($item['edited'] != $item['created']) ? sprintf( t('last edited: %s'), datetime_convert('UTC', date_default_timezone_get(), $item['edited'], 'r')) : ''), 'expiretime' => (($item['expires'] > NULL_DATE) ? sprintf( t('Expires: %s'), datetime_convert('UTC', date_default_timezone_get(), $item['expires'], 'r')):''), 'lock' => $lock, + 'delayed' => $item['item_delayed'], 'privacy_warning' => $privacy_warning, 'verified' => $verified, 'unverified' => $unverified, @@ -437,6 +466,9 @@ class ThreadItem { 'star' => ((feature_enabled($conv->get_profile_owner(),'star_posts') && ($item['item_type'] == ITEM_TYPE_POST)) ? $star : ''), 'tagger' => ((feature_enabled($conv->get_profile_owner(),'commtag')) ? $tagger : ''), 'filer' => ((feature_enabled($conv->get_profile_owner(),'filing') && ($item['item_type'] == ITEM_TYPE_POST)) ? $filer : ''), + 'pinned' => ($pinned ? t('Pinned post') : ''), + 'pinnable' => (($this->is_toplevel() && local_channel() && $item['owner_xchan'] == $observer['xchan_hash'] && $allowed_type && $item['item_private'] == 0 && $item['item_delayed'] == 0) ? '1' : ''), + 'pinme' => ($pinned ? t('Unpin from the top') : t('Pin to the top')), 'bookmark' => (($conv->get_profile_owner() == local_channel() && local_channel() && $has_bookmarks) ? t('Save Bookmarks') : ''), 'addtocal' => (($has_event) ? t('Add to Calendar') : ''), 'drop' => $drop, @@ -463,14 +495,13 @@ class ThreadItem { 'modal_dismiss' => t('Close'), 'showlike' => $showlike, 'showdislike' => $showdislike, - 'comment' => $this->get_comment_box($indent), + 'comment' => ($item['item_delayed'] ? '' : $this->get_comment_box($indent)), 'previewing' => ($conv->is_preview() ? true : false ), 'preview_lbl' => t('This is an unsaved preview'), 'wait' => t('Please wait'), - 'submid' => str_replace(['+','='], ['',''], base64_encode($item['mid'])), 'thread_level' => $thread_level, 'settings' => $settings, - 'thr_parent' => (($item['parent_mid'] != $item['thr_parent']) ? $item['thr_parent'] : '') + 'thr_parent' => (($item['parent_mid'] != $item['thr_parent']) ? 'b64.' . base64url_encode($item['thr_parent']) : '') ); $arr = array('item' => $item, 'output' => $tmp_item); @@ -863,7 +894,4 @@ class ThreadItem { return $this->visiting; } - - - } diff --git a/Zotlabs/Lib/ThreadStream.php b/Zotlabs/Lib/ThreadStream.php index 020e8729b..68b2c70dd 100644 --- a/Zotlabs/Lib/ThreadStream.php +++ b/Zotlabs/Lib/ThreadStream.php @@ -23,7 +23,7 @@ class ThreadStream { private $preview = false; private $prepared_item = ''; public $reload = ''; - private $cipher = 'aes256'; + private $cipher = 'AES-128-CCM'; // $prepared_item is for use by alternate conversation structures such as photos // wherein we've already prepared a top level item which doesn't look anything like diff --git a/Zotlabs/Lib/Webfinger.php b/Zotlabs/Lib/Webfinger.php index c2364ac4d..611c36889 100644 --- a/Zotlabs/Lib/Webfinger.php +++ b/Zotlabs/Lib/Webfinger.php @@ -106,4 +106,4 @@ class Webfinger { -}
\ No newline at end of file +} diff --git a/Zotlabs/Lib/Zotfinger.php b/Zotlabs/Lib/Zotfinger.php index 2d2e6796b..722e34dfc 100644 --- a/Zotlabs/Lib/Zotfinger.php +++ b/Zotlabs/Lib/Zotfinger.php @@ -60,4 +60,4 @@ class Zotfinger { -}
\ No newline at end of file +} |