diff options
Diffstat (limited to 'Zotlabs/Lib')
-rw-r--r-- | Zotlabs/Lib/ASCollection.php | 150 | ||||
-rw-r--r-- | Zotlabs/Lib/Activity.php | 2723 | ||||
-rw-r--r-- | Zotlabs/Lib/ActivityStreams.php | 246 | ||||
-rw-r--r-- | Zotlabs/Lib/Apps.php | 208 | ||||
-rw-r--r-- | Zotlabs/Lib/Connect.php | 8 | ||||
-rw-r--r-- | Zotlabs/Lib/Crypto.php | 206 | ||||
-rw-r--r-- | Zotlabs/Lib/DReport.php | 3 | ||||
-rw-r--r-- | Zotlabs/Lib/Enotify.php | 201 | ||||
-rw-r--r-- | Zotlabs/Lib/Hashpath.php | 55 | ||||
-rw-r--r-- | Zotlabs/Lib/JSalmon.php | 14 | ||||
-rw-r--r-- | Zotlabs/Lib/Keyutils.php | 99 | ||||
-rw-r--r-- | Zotlabs/Lib/LDSignatures.php | 12 | ||||
-rw-r--r-- | Zotlabs/Lib/Libsync.php | 581 | ||||
-rw-r--r-- | Zotlabs/Lib/Libzot.php | 1548 | ||||
-rw-r--r-- | Zotlabs/Lib/Libzotdir.php | 90 | ||||
-rw-r--r-- | Zotlabs/Lib/MessageFilter.php | 2 | ||||
-rw-r--r-- | Zotlabs/Lib/NativeWiki.php | 49 | ||||
-rw-r--r-- | Zotlabs/Lib/NativeWikiPage.php | 40 | ||||
-rw-r--r-- | Zotlabs/Lib/PConfig.php | 1 | ||||
-rw-r--r-- | Zotlabs/Lib/Queue.php | 106 | ||||
-rw-r--r-- | Zotlabs/Lib/Share.php | 44 | ||||
-rw-r--r-- | Zotlabs/Lib/ThreadItem.php | 60 | ||||
-rw-r--r-- | Zotlabs/Lib/ThreadStream.php | 8 | ||||
-rw-r--r-- | Zotlabs/Lib/Verify.php | 8 | ||||
-rw-r--r-- | Zotlabs/Lib/ZotURL.php | 11 | ||||
-rw-r--r-- | Zotlabs/Lib/Zotfinger.php | 13 |
26 files changed, 3700 insertions, 2786 deletions
diff --git a/Zotlabs/Lib/ASCollection.php b/Zotlabs/Lib/ASCollection.php new file mode 100644 index 000000000..392dd5d4e --- /dev/null +++ b/Zotlabs/Lib/ASCollection.php @@ -0,0 +1,150 @@ +<?php + +namespace Zotlabs\Lib; + +/** + * Class for dealing with fetching ActivityStreams collections (ordered or unordered, normal or paged). + * Construct with either an existing object or url and an optional channel to sign requests. + * $direction is 0 (default) to fetch from the beginning, and 1 to fetch from the end and reverse order the resultant array. + * An optional limit to the number of records returned may also be specified. + * Use $class->get() to return an array of collection members. + */ +class ASCollection { + + private $channel = null; + private $nextpage = null; + private $limit = 0; + private $direction = 0; // 0 = forward, 1 = reverse + private $data = []; + private $history = []; + + function __construct($obj, $channel = null, $direction = 0, $limit = 0) { + + $this->channel = $channel; + $this->direction = $direction; + $this->limit = $limit; + + if (is_array($obj)) { + $data = $obj; + } + + if (is_string($obj)) { + $data = Activity::fetch($obj, $channel); + $this->history[] = $obj; + } + + if (!is_array($data)) { + return; + } + + if (!in_array($data['type'], ['Collection', 'OrderedCollection', 'OrderedCollectionPage'])) { + return false; + } + + if ($this->direction) { + if (array_key_exists('last', $data) && $data['last']) { + $this->nextpage = $data['last']; + } + } + else { + if (array_key_exists('first', $data) && $data['first']) { + $this->nextpage = $data['first']; + } + } + + if (isset($data['items']) && is_array($data['items'])) { + $this->data = (($this->direction) ? array_reverse($data['items']) : $data['items']); + } + elseif (isset($data['orderedItems']) && is_array($data['orderedItems'])) { + $this->data = (($this->direction) ? array_reverse($data['orderedItems']) : $data['orderedItems']); + } + + if ($this->limit) { + if (count($this->data) > $limit) { + $this->data = array_slice($this->data, 0, $limit); + return; + } + } + + do { + $x = $this->next(); + } while ($x); + } + + function get() { + return $this->data; + } + + function next() { + + if (!$this->nextpage) { + return false; + } + + if (is_array($this->nextpage)) { + $data = $this->nextpage; + } + + if (is_string($this->nextpage)) { + if (in_array($this->nextpage, $this->history)) { + // recursion detected + return false; + } + $data = Activity::fetch($this->nextpage, $this->channel); + $this->history[] = $this->nextpage; + } + + if (!is_array($data)) { + return false; + } + + if (!in_array($data['type'], ['CollectionPage', 'OrderedCollectionPage'])) { + return false; + } + + $this->setnext($data); + + if (isset($data['items']) && is_array($data['items'])) { + $this->data = array_merge($this->data, (($this->direction) ? array_reverse($data['items']) : $data['items'])); + } + elseif (isset($data['orderedItems']) && is_array($data['orderedItems'])) { + $this->data = array_merge($this->data, (($this->direction) ? array_reverse($data['orderedItems']) : $data['orderedItems'])); + } + + if ($this->limit) { + if (count($this->data) > $this->limit) { + $this->data = array_slice($this->data, 0, $this->limit); + $this->nextpage = false; + return true; + } + } + + return true; + } + + function setnext($data) { + if ($this->direction) { + if (array_key_exists('prev', $data) && $data['prev']) { + $this->nextpage = $data['prev']; + } + elseif (array_key_exists('first', $data) && $data['first']) { + $this->nextpage = $data['first']; + } + else { + $this->nextpage = false; + } + } + else { + if (array_key_exists('next', $data) && $data['next']) { + $this->nextpage = $data['next']; + } + elseif (array_key_exists('last', $data) && $data['last']) { + $this->nextpage = $data['last']; + } + else { + $this->nextpage = false; + } + } + logger('nextpage: ' . $this->nextpage, LOGGER_DEBUG); + } +}
\ No newline at end of file diff --git a/Zotlabs/Lib/Activity.php b/Zotlabs/Lib/Activity.php index 807216400..c355aa26e 100644 --- a/Zotlabs/Lib/Activity.php +++ b/Zotlabs/Lib/Activity.php @@ -2,82 +2,84 @@ namespace Zotlabs\Lib; +use App; use Zotlabs\Access\PermissionLimits; +use Zotlabs\Access\PermissionRoles; +use Zotlabs\Access\Permissions; use Zotlabs\Daemon\Master; use Zotlabs\Web\HTTPSig; +use Zotlabs\Lib\XConfig; +use Zotlabs\Lib\Libzot; require_once('include/event.php'); require_once('include/html2plain.php'); +require_once('include/items.php'); class Activity { static function encode_object($x) { - - if(($x) && (! is_array($x)) && (substr(trim($x),0,1)) === '{' ) { - $x = json_decode($x,true); + if (($x) && (!is_array($x)) && (substr(trim($x), 0, 1)) === '{') { + $x = json_decode($x, true); } - if(is_array($x)) { + if (is_array($x)) { - if(array_key_exists('asld',$x)) { + if (array_key_exists('asld', $x)) { return $x['asld']; } - if($x['type'] === ACTIVITY_OBJ_PERSON) { - return self::fetch_person($x); - } - if($x['type'] === ACTIVITY_OBJ_PROFILE) { - return self::fetch_profile($x); + if ($x['type'] === ACTIVITY_OBJ_PERSON) { + return self::fetch_person($x); } - if(in_array($x['type'], [ ACTIVITY_OBJ_NOTE, ACTIVITY_OBJ_ARTICLE ] )) { - return self::fetch_item($x); + if ($x['type'] === ACTIVITY_OBJ_PROFILE) { + return self::fetch_profile($x); } - if($x['type'] === ACTIVITY_OBJ_THING) { - return self::fetch_thing($x); + if (in_array($x['type'], [ACTIVITY_OBJ_NOTE, ACTIVITY_OBJ_ARTICLE])) { + return self::fetch_item($x); } - if($x['type'] === ACTIVITY_OBJ_EVENT) { - return self::fetch_event($x); + if ($x['type'] === ACTIVITY_OBJ_THING) { + return self::fetch_thing($x); } - if($x['type'] === ACTIVITY_OBJ_PHOTO) { - return self::fetch_image($x); + if ($x['type'] === ACTIVITY_OBJ_EVENT) { + return self::fetch_event($x); } - call_hooks('encode_object',$x); + call_hooks('encode_object', $x); } return $x; } - static function fetch($url,$channel = null) { + static function fetch($url, $channel = null) { $redirects = 0; - if(! check_siteallowed($url)) { + if (!check_siteallowed($url)) { logger('blacklisted: ' . $url); return null; } - if(! $channel) { + if (!$channel) { $channel = get_sys_channel(); } logger('fetch: ' . $url, LOGGER_DEBUG); - if(strpos($url,'x-zot:') === 0) { - $x = ZotURL::fetch($url,$channel); + if (strpos($url, 'x-zot:') === 0) { + $x = ZotURL::fetch($url, $channel); } else { $m = parse_url($url); // handle bearcaps if ($m['scheme'] === 'bear') { - $params = explode('&',$m['query']); + $params = explode('&', $m['query']); if ($params) { foreach ($params as $p) { - if (substr($p,0,2) === 'u=') { - $url = substr($p,2); + if (substr($p, 0, 2) === 'u=') { + $url = substr($p, 2); } - if (substr($p,0,2) === 't=') { - $token = substr($p,2); + if (substr($p, 0, 2) === 't=') { + $token = substr($p, 2); } } $m = parse_url($url); @@ -85,21 +87,37 @@ class Activity { } $headers = [ - 'Accept' => 'application/activity+json, application/ld+json; profile="https://www.w3.org/ns/activitystreams"', + 'Accept' => ActivityStreams::get_accept_header_string($channel), 'Host' => $m['host'], - 'Date' => datetime_convert('UTC','UTC', 'now', 'D, d M Y H:i:s \\G\\M\\T'), + 'Date' => datetime_convert('UTC', 'UTC', 'now', 'D, d M Y H:i:s \\G\\M\\T'), '(request-target)' => 'get ' . get_request_string($url) ]; + if (isset($token)) { $headers['Authorization'] = 'Bearer ' . $token; } - $h = HTTPSig::create_sig($headers,$channel['channel_prvkey'],channel_url($channel),false); - $x = z_fetch_url($url, true, $redirects, [ 'headers' => $h ] ); + + $h = HTTPSig::create_sig($headers, $channel['channel_prvkey'], channel_url($channel), false); + $x = z_fetch_url($url, true, $redirects, ['headers' => $h]); } - if($x['success']) { - $y = json_decode($x['body'],true); - logger('returned: ' . json_encode($y,JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES), LOGGER_DEBUG); + if ($x['success']) { + $m = parse_url($url); + if ($m) { + $y = [ 'scheme' => $m['scheme'], 'host' => $m['host'] ]; + if (array_key_exists('port', $m)) + $y['port'] = $m['port']; + $site_url = unparse_url($y); + q("UPDATE site SET site_update = '%s', site_dead = 0 WHERE site_url = '%s' AND site_update < %s - INTERVAL %s", + dbesc(datetime_convert()), + dbesc($site_url), + db_utcnow(), + db_quoteinterval('1 DAY') + ); + } + + $y = json_decode($x['body'], true); + logger('returned: ' . json_encode($y, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES), LOGGER_DEBUG); return json_decode($x['body'], true); } else { @@ -109,24 +127,21 @@ class Activity { return null; } - - - static function fetch_person($x) { return self::fetch_profile($x); } static function fetch_profile($x) { - $r = q("select * from xchan where xchan_url like '%s' limit 1", - dbesc($x['id'] . '/%') + $r = q("select * from xchan where xchan_url = '%s' limit 1", + dbesc($x['id']) ); - if(! $r) { + if (!$r) { $r = q("select * from xchan where xchan_hash = '%s' limit 1", dbesc($x['id']) ); - } - if(! $r) + } + if (!$r) return []; return self::encode_person($r[0]); @@ -140,7 +155,7 @@ class Activity { dbesc($x['id']) ); - if(! $r) + if (!$r) return []; $x = [ @@ -149,7 +164,7 @@ class Activity { 'name' => $r[0]['obj_term'] ]; - if($r[0]['obj_image']) + if ($r[0]['obj_image']) $x['image'] = $r[0]['obj_image']; return $x; @@ -158,7 +173,7 @@ class Activity { static function fetch_item($x) { - if (array_key_exists('source',$x)) { + if (array_key_exists('source', $x)) { // This item is already processed and encoded return $x; } @@ -166,8 +181,8 @@ class Activity { $r = q("select * from item where mid = '%s' limit 1", dbesc($x['id']) ); - if($r) { - xchan_query($r,true); + if ($r) { + xchan_query($r, true); $r = fetch_post_tags($r); if (in_array($r[0]['verb'], ['Create', 'Invite']) && $r[0]['obj_type'] === ACTIVITY_OBJ_EVENT) { $r[0]['verb'] = 'Invite'; @@ -177,22 +192,22 @@ class Activity { } } - static function fetch_image($x) { + $ret = [ - 'type' => 'Image', - 'id' => $x['id'], - 'name' => $x['title'], - 'content' => bbcode($x['body'], [ 'cache' => true ]), - 'source' => [ 'mediaType' => 'text/bbcode', 'content' => $x['body'] ], - 'published' => datetime_convert('UTC','UTC',$x['created'],ATOM_TIME), - 'updated' => datetime_convert('UTC','UTC', $x['edited'],ATOM_TIME), - 'url' => [ - 'type' => 'Link', - 'mediaType' => $x['link'][0]['type'], - 'href' => $x['link'][0]['href'], - 'width' => $x['link'][0]['width'], - 'height' => $x['link'][0]['height'] + 'type' => 'Image', + 'id' => $x['id'], + 'name' => $x['title'], + 'content' => bbcode($x['body'], ['cache' => true]), + 'source' => ['mediaType' => 'text/bbcode', 'content' => $x['body']], + 'published' => datetime_convert('UTC', 'UTC', $x['created'], ATOM_TIME), + 'updated' => datetime_convert('UTC', 'UTC', $x['edited'], ATOM_TIME), + 'url' => [ + 'type' => 'Link', + 'mediaType' => $x['link'][0]['type'], + 'href' => $x['link'][0]['href'], + 'width' => $x['link'][0]['width'], + 'height' => $x['link'][0]['height'] ] ]; return $ret; @@ -202,42 +217,42 @@ class Activity { // convert old Zot event objects to ActivityStreams Event objects - if (array_key_exists('content',$x) && array_key_exists('dtstart',$x)) { + if (array_key_exists('content', $x) && array_key_exists('dtstart', $x)) { $ev = bbtoevent($x['content']); - if($ev) { + if ($ev) { - if (! $ev['timezone']) { + if (!$ev['timezone']) { $ev['timezone'] = 'UTC'; } $actor = null; - if(array_key_exists('author',$x) && array_key_exists('link',$x['author'])) { + if (array_key_exists('author', $x) && array_key_exists('link', $x['author'])) { $actor = $x['author']['link'][0]['href']; } - $y = [ + $y = [ 'type' => 'Event', 'id' => z_root() . '/event/' . $ev['event_hash'], 'name' => $ev['summary'], -// 'summary' => bbcode($ev['summary'], [ 'cache' => true ]), + // 'summary' => bbcode($ev['summary'], [ 'cache' => true ]), // RFC3339 Section 4.3 - '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,true), 'mediaType' => 'text/bbcode' ], + '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, true), 'mediaType' => 'text/bbcode'], 'actor' => $actor, ]; - if(! $ev['nofinish']) { - $y['endTime'] = (($ev['adjust']) ? datetime_convert($ev['timezone'],'UTC',$ev['dtend'], ATOM_TIME) : datetime_convert('UTC','UTC',$ev['dtend'],'Y-m-d\\TH:i:s-00:00')); + if (!$ev['nofinish']) { + $y['endTime'] = (($ev['adjust']) ? datetime_convert($ev['timezone'], 'UTC', $ev['dtend'], ATOM_TIME) : datetime_convert('UTC', 'UTC', $ev['dtend'], 'Y-m-d\\TH:i:s-00:00')); } - + // copy attachments from the passed object - these are already formatted for ActivityStreams - if($x['attachment']) { + if ($x['attachment']) { $y['attachment'] = $x['attachment']; } - if($actor) { + if ($actor) { return $y; } } @@ -247,52 +262,112 @@ class Activity { } - - static function encode_item_collection($items,$id,$type,$extra = null) { + static function paged_collection_init($total, $id, $type = 'OrderedCollection') { $ret = [ - 'id' => z_root() . '/' . $id, - 'type' => $type, - 'totalItems' => count($items), + 'id' => z_root() . '/' . $id, + 'type' => $type, + 'totalItems' => $total, ]; - if($extra) - $ret = array_merge($ret,$extra); - if($items) { + $numpages = $total / App::$pager['itemspage']; + $lastpage = (($numpages > intval($numpages)) ? intval($numpages) + 1 : $numpages); + + $ret['first'] = z_root() . '/' . App::$query_string . '?page=1'; + $ret['last'] = z_root() . '/' . App::$query_string . '?page=' . $lastpage; + + return $ret; + + } + + static function encode_item_collection($items, $id, $type, $total = 0) { + + if ($total > 30) { + $ret = [ + 'id' => z_root() . '/' . $id, + 'type' => $type . 'Page', + ]; + + $numpages = $total / App::$pager['itemspage']; + $lastpage = (($numpages > intval($numpages)) ? intval($numpages) + 1 : $numpages); + $url_parts = parse_url($id); + + $ret['partOf'] = z_root() . '/' . $url_parts['path']; + + $extra_query_args = ''; + $query_args = null; + if(isset($url_parts['query'])) { + parse_str($url_parts['query'], $query_args); + } + + if(is_array($query_args)) { + unset($query_args['page']); + foreach($query_args as $k => $v) + $extra_query_args .= '&' . urlencode($k) . '=' . urlencode($v); + } + + if (App::$pager['page'] < $lastpage) { + $ret['next'] = z_root() . '/' . $url_parts['path'] . '?page=' . (intval(App::$pager['page']) + 1) . $extra_query_args; + } + if (App::$pager['page'] > 1) { + $ret['prev'] = z_root() . '/' . $url_parts['path'] . '?page=' . (intval(App::$pager['page']) - 1) . $extra_query_args; + } + } + else { + $ret = [ + 'id' => z_root() . '/' . $id, + 'type' => $type, + 'totalItems' => $total, + ]; + } + + if ($items) { $x = []; - foreach($items as $i) { - $t = self::encode_activity($i); - if($t) + foreach ($items as $i) { + $m = get_iconfig($i['id'], 'activitypub', 'rawmsg'); + if ($m) { + if (is_string($m)) + $t = json_decode($m, true); + else + $t = $m; + } + else { + $t = self::encode_activity($i); + } + if ($t) { $x[] = $t; + } } - if($type === 'OrderedCollection') + if ($type === 'OrderedCollection') { $ret['orderedItems'] = $x; - else + } + else { $ret['items'] = $x; + } } return $ret; } - static function encode_follow_collection($items,$id,$type,$extra = null) { + static function encode_follow_collection($items, $id, $type, $extra = null) { $ret = [ - 'id' => z_root() . '/' . $id, - 'type' => $type, + 'id' => z_root() . '/' . $id, + 'type' => $type, 'totalItems' => count($items), ]; - if($extra) - $ret = array_merge($ret,$extra); + if ($extra) + $ret = array_merge($ret, $extra); - if($items) { + if ($items) { $x = []; - foreach($items as $i) { - if($i['xchan_url']) { + foreach ($items as $i) { + if ($i['xchan_url']) { $x[] = $i['xchan_url']; } } - if($type === 'OrderedCollection') + if ($type === 'OrderedCollection') $ret['orderedItems'] = $x; else $ret['items'] = $x; @@ -301,18 +376,17 @@ class Activity { return $ret; } - - - static function encode_item($i) { $ret = []; - if($i['verb'] === ACTIVITY_FRIEND) { + + + if ($i['verb'] === ACTIVITY_FRIEND) { // Hubzilla 'make-friend' activity, no direct mapping from AS1 to AS2 - make it a note $objtype = 'Note'; } - else { + else { $objtype = self::activity_obj_mapper($i['obj_type']); } @@ -321,13 +395,13 @@ class Activity { } if (intval($i['item_deleted'])) { - $ret['type'] = 'Tombstone'; + $ret['type'] = 'Tombstone'; $ret['formerType'] = $objtype; - $ret['id'] = $i['mid']; - if($i['id'] != $i['parent']) + $ret['id'] = $i['mid']; + if ($i['id'] != $i['parent']) $ret['inReplyTo'] = $i['thr_parent']; - $ret['to'] = [ ACTIVITY_PUBLIC_INBOX ]; + $ret['to'] = [ACTIVITY_PUBLIC_INBOX]; return $ret; } @@ -336,7 +410,7 @@ class Activity { $ret = $i['obj']; } else { - $ret = json_decode($i['obj'],true); + $ret = json_decode($i['obj'], true); } } @@ -348,96 +422,95 @@ class Activity { $ret = $i['obj']; } else { - $ret = json_decode($i['obj'],true); + $ret = json_decode($i['obj'], true); } - - if(array_path_exists('actor/id',$ret)) { + + 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['id'] = ((strpos($i['mid'], 'http') === 0) ? $i['mid'] : z_root() . '/item/' . urlencode($i['mid'])); $ret['diaspora:guid'] = $i['uuid']; - if($i['title']) + if ($i['title']) $ret['name'] = $i['title']; - $ret['published'] = datetime_convert('UTC','UTC',$i['created'],ATOM_TIME); - if($i['created'] !== $i['edited']) - $ret['updated'] = datetime_convert('UTC','UTC',$i['edited'],ATOM_TIME); - if ($i['expires'] <= NULL_DATE) { - $ret['expires'] = datetime_convert('UTC','UTC',$i['expires'],ATOM_TIME); + $ret['published'] = datetime_convert('UTC', 'UTC', $i['created'], ATOM_TIME); + if ($i['created'] !== $i['edited']) + $ret['updated'] = datetime_convert('UTC', 'UTC', $i['edited'], ATOM_TIME); + if ($i['expires'] > NULL_DATE) { + $ret['expires'] = datetime_convert('UTC', 'UTC', $i['expires'], ATOM_TIME); } - if($i['app']) { - $ret['generator'] = [ 'type' => 'Application', 'name' => $i['app'] ]; + if ($i['app']) { + $ret['generator'] = ['type' => 'Application', 'name' => $i['app']]; } - if($i['location'] || $i['coord']) { - $ret['location'] = [ 'type' => 'Place' ]; - if($i['location']) { + if ($i['location'] || $i['coord']) { + $ret['location'] = ['type' => 'Place']; + if ($i['location']) { $ret['location']['name'] = $i['location']; } - if($i['coord']) { - $l = explode(' ',$i['coord']); - $ret['location']['latitude'] = $l[0]; + if ($i['coord']) { + $l = explode(' ', $i['coord']); + $ret['location']['latitude'] = $l[0]; $ret['location']['longitude'] = $l[1]; } } if (intval($i['item_wall']) && $i['mid'] === $i['parent_mid']) { - $ret['commentPolicy'] = map_scope(PermissionLimits::Get($i['uid'],'post_comments')); + $ret['commentPolicy'] = map_scope(PermissionLimits::Get($i['uid'], 'post_comments')); } if (intval($i['item_private']) === 2) { $ret['directMessage'] = true; } - if (array_key_exists('comments_closed',$i) && $i['comments_closed'] !== EMPTY_STR && $i['comments_closed'] !== NULL_DATE) { - if($ret['commentPolicy']) { + if (array_key_exists('comments_closed', $i) && $i['comments_closed'] !== EMPTY_STR && $i['comments_closed'] > NULL_DATE) { + if ($ret['commentPolicy']) { $ret['commentPolicy'] .= ' '; } - $ret['commentPolicy'] .= 'until=' . datetime_convert('UTC','UTC',$i['comments_closed'],ATOM_TIME); + $ret['commentPolicy'] .= 'until=' . datetime_convert('UTC', 'UTC', $i['comments_closed'], ATOM_TIME); } $ret['attributedTo'] = $i['author']['xchan_url']; - if($i['id'] != $i['parent']) { - $ret['inReplyTo'] = ((strpos($i['thr_parent'],'http') === 0) ? $i['thr_parent'] : z_root() . '/item/' . urlencode($i['thr_parent'])); + if ($i['id'] != $i['parent']) { + $ret['inReplyTo'] = ((strpos($i['thr_parent'], 'http') === 0) ? $i['thr_parent'] : z_root() . '/item/' . urlencode($i['thr_parent'])); } - if($i['mimetype'] === 'text/bbcode') { - if($i['title']) - $ret['name'] = bbcode($i['title'], [ 'cache' => true ]); - if($i['summary']) - $ret['summary'] = bbcode($i['summary'], [ 'cache' => true ]); - $ret['content'] = bbcode($i['body'], [ 'cache' => true ]); - $ret['source'] = [ 'content' => $i['body'], 'mediaType' => 'text/bbcode' ]; + if ($i['mimetype'] === 'text/bbcode') { + if ($i['title']) + $ret['name'] = bbcode($i['title'], ['cache' => true]); + if ($i['summary']) + $ret['summary'] = bbcode($i['summary'], ['cache' => true]); + $ret['content'] = bbcode($i['body'], ['cache' => true]); + $ret['source'] = ['content' => $i['body'], 'mediaType' => 'text/bbcode']; } - $actor = self::encode_person($i['author'],false); - if($actor) + $actor = self::encode_person($i['author'], false); + if ($actor) $ret['actor'] = $actor; else return []; $t = self::encode_taxonomy($i); - if($t) { - $ret['tag'] = $t; + if ($t) { + $ret['tag'] = $t; } $a = self::encode_attachment($i); - if($a) { + if ($a) { $ret['attachment'] = $a; } - $public = (($i['item_private']) ? false : true); + $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'],'@')) ]; + $ret['to'] = [ACTIVITY_PUBLIC_INBOX]; + $ret['cc'] = [z_root() . '/followers/' . substr($i['author']['xchan_addr'], 0, strpos($i['author']['xchan_addr'], '@'))]; } else { @@ -450,7 +523,7 @@ class Activity { $ret['to'] = []; if ($ret['tag']) { foreach ($ret['tag'] as $mention) { - if (is_array($mention) && array_key_exists('href',$mention) && $mention['href']) { + 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']) ); @@ -461,7 +534,7 @@ class Activity { else { $addr = $h[0]['hubloc_id_url']; } - if (! in_array($addr,$ret['to'])) { + if (!in_array($addr, $ret['to'])) { $ret['to'][] = $addr; } } @@ -478,7 +551,7 @@ class Activity { else { $addr = $d[0]['hubloc_id_url']; } - if (! in_array($addr,$ret['to'])) { + if (!in_array($addr, $ret['to'])) { $ret['cc'][] = $addr; } } @@ -487,7 +560,7 @@ class Activity { $mentions = self::map_mentions($i); if (count($mentions) > 0) { - if (! $ret['to']) { + if (!$ret['to']) { $ret['to'] = $mentions; } else { @@ -503,32 +576,32 @@ class Activity { $ret = []; - if ($item['tag'] && is_array($item['tag'])) { + if (array_key_exists('tag', $item) && is_array($item['tag'])) { $ptr = $item['tag']; - if (! array_key_exists(0,$ptr)) { - $ptr = [ $ptr ]; + if (!array_key_exists(0, $ptr)) { + $ptr = [$ptr]; } foreach ($ptr as $t) { - if (! array_key_exists('type',$t)) + if (!array_key_exists('type', $t)) $t['type'] = 'Hashtag'; - switch($t['type']) { - case 'Hashtag': - $ret[] = [ 'ttype' => TERM_HASHTAG, 'url' => $t['href'], 'term' => escape_tags((substr($t['name'],0,1) === '#') ? substr($t['name'],1) : $t['name']) ]; - break; + if (array_key_exists('href', $t) && array_key_exists('name', $t)) { + switch ($t['type']) { + case 'Hashtag': + $ret[] = ['ttype' => TERM_HASHTAG, 'url' => $t['href'], 'term' => escape_tags((substr($t['name'], 0, 1) === '#') ? substr($t['name'], 1) : $t['name'])]; + break; - case 'Mention': - $mention_type = substr($t['name'],0,1); - if ($mention_type === '!') { - $ret[] = [ 'ttype' => TERM_FORUM, 'url' => $t['href'], 'term' => escape_tags(substr($t['name'],1)) ]; - } - else { - $ret[] = [ 'ttype' => TERM_MENTION, 'url' => $t['href'], 'term' => escape_tags((substr($t['name'],0,1) === '@') ? substr($t['name'],1) : $t['name']) ]; - } - break; + case 'Mention': + $ret[] = ['ttype' => TERM_MENTION, 'url' => $t['href'], 'term' => escape_tags((substr($t['name'], 0, 1) === '@') ? substr($t['name'], 1) : $t['name'])]; + break; - default: - break; + case 'Bookmark': + $ret[] = ['ttype' => TERM_BOOKMARK, 'url' => $t['href'], 'term' => escape_tags($t['name'])]; + break; + + default: + break; + } } } } @@ -536,30 +609,28 @@ class Activity { return $ret; } - - static function encode_taxonomy($item) { $ret = []; - if($item['term']) { - foreach($item['term'] as $t) { - switch($t['ttype']) { + if (array_key_exists('term', $item) && is_array($item['term'])) { + foreach ($item['term'] as $t) { + switch ($t['ttype']) { case TERM_HASHTAG: // href is required so if we don't have a url in the taxonomy, ignore it and keep going. - if($t['url']) { - $ret[] = [ 'type' => 'Hashtag', 'href' => $t['url'], 'name' => '#' . $t['term'] ]; + if ($t['url']) { + $ret[] = ['type' => 'Hashtag', 'href' => $t['url'], 'name' => '#' . $t['term']]; } break; - case TERM_FORUM: - $ret[] = [ 'type' => 'Mention', 'href' => $t['url'], 'name' => '!' . $t['term'] ]; + case TERM_MENTION: + $ret[] = ['type' => 'Mention', 'href' => $t['url'], 'name' => '@' . $t['term']]; break; - case TERM_MENTION: - $ret[] = [ 'type' => 'Mention', 'href' => $t['url'], 'name' => '@' . $t['term'] ]; + case TERM_BOOKMARK: + $ret[] = ['type' => 'Bookmark', 'href' => $t['url'], 'name' => $t['term']]; break; - + default: break; } @@ -573,28 +644,28 @@ class Activity { $ret = []; - if($item['attach']) { - $atts = ((is_array($item['attach'])) ? $item['attach'] : json_decode($item['attach'],true)); - if($atts) { - foreach($atts as $att) { - if(strpos($att['type'],'image')) { - $ret[] = [ 'type' => 'Image', 'url' => $att['href'] ]; + if (array_key_exists('attach', $item)) { + $atts = ((is_array($item['attach'])) ? $item['attach'] : json_decode($item['attach'], true)); + if ($atts) { + foreach ($atts as $att) { + if (isset($att['type']) && strpos($att['type'], 'image')) { + $ret[] = ['type' => 'Image', 'url' => $att['href']]; } else { - $ret[] = [ 'type' => 'Link', 'mediaType' => $att['type'], 'href' => $att['href'] ]; + $ret[] = ['type' => 'Link', 'mediaType' => $att['type'], 'href' => $att['href']]; } } } } - if ($item['iconfig']) { + if (array_key_exists('iconfig', $item) && is_array($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 ]; + $ret[] = ['type' => 'PropertyValue', 'name' => 'zot.' . $att['cat'] . '.' . $att['k'], 'value' => $value]; } } } - + return $ret; } @@ -604,20 +675,20 @@ class Activity { if (is_array($item['attachment']) && $item['attachment']) { $ptr = $item['attachment']; - if (! array_key_exists(0,$ptr)) { - $ptr = [ $ptr ]; + 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 (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['cat'] = $key[1]; + $entry['k'] = $key[2]; + $entry['v'] = $att['value']; $entry['sharing'] = '1'; - $ret[] = $entry; + $ret[] = $entry; } } } @@ -626,24 +697,22 @@ class Activity { return $ret; } - - static function decode_attachment($item) { $ret = []; - if($item['attachment']) { - foreach($item['attachment'] as $att) { + if (array_key_exists('attachment', $item) && is_array($item['attachment'])) { + foreach ($item['attachment'] as $att) { $entry = []; - if($att['href']) + if (array_key_exists('href', $att)) $entry['href'] = $att['href']; - elseif($att['url']) + elseif (array_key_exists('url', $att)) $entry['href'] = $att['url']; - if($att['mediaType']) + if (array_key_exists('mediaType', $att)) $entry['type'] = $att['mediaType']; - elseif($att['type'] === 'Image') + elseif (array_key_exists('type', $att) && $att['type'] === 'Image') $entry['type'] = 'image/jpeg'; - if($entry) + if ($entry) $ret[] = $entry; } } @@ -651,211 +720,188 @@ class Activity { return $ret; } - - static function encode_activity($i, $recurse = false) { $ret = []; $reply = false; - - if($i['verb'] === ACTIVITY_FRIEND) { + if ($i['verb'] === ACTIVITY_FRIEND) { // Hubzilla 'make-friend' activity, no direct mapping from AS1 to AS2 - make it a note $ret['obj'] = []; } $ret['type'] = self::activity_mapper($i['verb']); - $fragment = ''; if (intval($i['item_deleted']) && !$recurse) { $is_response = false; if (ActivityStreams::is_response_activity($ret['type'])) { $ret['type'] = 'Undo'; - $fragment = 'undo'; + $fragment = 'undo'; $is_response = true; } else { $ret['type'] = 'Delete'; - $fragment = 'delete'; + $fragment = 'delete'; } - $ret['id'] = str_replace('/item/','/activity/',$i['mid']) . '#' . $fragment; - $actor = self::encode_person($i['author'],false); + $ret['id'] = str_replace('/item/', '/activity/', $i['mid']) . '#' . $fragment; + $actor = self::encode_person($i['author'], false); if ($actor) $ret['actor'] = $actor; else - return []; + return []; - $obj = (($is_response) ? self::encode_activity($i,true) : self::encode_item($i,true)); + $obj = (($is_response) ? self::encode_activity($i, true) : self::encode_item($i)); if ($obj) { - if (array_path_exists('object/id',$obj)) { + if (array_path_exists('object/id', $obj)) { $obj['object'] = $obj['object']['id']; } - unset($obj['cc']); - $obj['to'] = [ ACTIVITY_PUBLIC_INBOX ]; + if (isset($obj['cc'])) { + unset($obj['cc']); + } + $obj['to'] = [ACTIVITY_PUBLIC_INBOX]; $ret['object'] = $obj; } else return []; - $ret['to'] = [ ACTIVITY_PUBLIC_INBOX ]; + $ret['to'] = [ACTIVITY_PUBLIC_INBOX]; return $ret; } - if($ret['type'] === 'emojiReaction') { + if ($ret['type'] === 'emojiReaction') { // There may not be an object for these items for legacy reasons - it should be the conversation parent. $p = q("select * from item where mid = '%s' and uid = %d", dbesc($i['parent_mid']), intval($i['uid']) ); - if($p) { - xchan_query($p,true); - $p = fetch_post_tags($p); + if ($p) { + xchan_query($p, true); + $p = fetch_post_tags($p); $i['obj'] = self::encode_item($p[0]); // convert to zot6 emoji reaction encoding which uses the target object to indicate the // specific emoji instead of overloading the verb or type. - - $im = explode('#',$i['verb']); - if($im && count($im) > 1) + + $im = explode('#', $i['verb']); + if ($im && count($im) > 1) $emoji = $im[1]; - if(preg_match("/\[img(.*?)\](.*?)\[\/img\]/ism", $i['body'], $match)) { + if (preg_match("/\[img(.*?)\](.*?)\[\/img\]/ism", $i['body'], $match)) { $ln = $match[2]; } $i['tgt_type'] = 'Image'; - + $i['target'] = [ 'type' => 'Image', 'name' => $emoji, 'url' => (($ln) ? $ln : z_root() . '/images/emoji/' . $emoji . '.png') ]; - + } } - if (strpos($i['mid'],z_root() . '/item/') !== false) { - $ret['id'] = str_replace('/item/','/activity/',$i['mid']); + 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']); + 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 ])); + if ($i['title']) + $ret['name'] = html2plain(bbcode($i['title'], ['cache' => true])); - if($i['summary']) - $ret['summary'] = bbcode($i['summary'], [ 'cache' => true ]); + if ($i['summary']) + $ret['summary'] = bbcode($i['summary'], ['cache' => true]); - if($ret['type'] === 'Announce') { - $tmp = preg_replace('/\[share(.*?)\[\/share\]/ism',EMPTY_STR, $i['body']); - $ret['content'] = bbcode($tmp, [ 'cache' => true ]); - $ret['source'] = [ - 'content' => $i['body'], + if ($ret['type'] === 'Announce') { + $tmp = preg_replace('/\[share(.*?)\[\/share\]/ism', EMPTY_STR, $i['body']); + $ret['content'] = bbcode($tmp, ['cache' => true]); + $ret['source'] = [ + 'content' => $i['body'], 'mediaType' => 'text/bbcode' ]; } - $ret['published'] = datetime_convert('UTC','UTC',$i['created'],ATOM_TIME); - if($i['created'] !== $i['edited']) - $ret['updated'] = datetime_convert('UTC','UTC',$i['edited'],ATOM_TIME); - if($i['app']) { - $ret['generator'] = [ 'type' => 'Application', 'name' => $i['app'] ]; + $ret['published'] = datetime_convert('UTC', 'UTC', $i['created'], ATOM_TIME); + if ($i['created'] !== $i['edited']) + $ret['updated'] = datetime_convert('UTC', 'UTC', $i['edited'], ATOM_TIME); + if ($i['app']) { + $ret['generator'] = ['type' => 'Application', 'name' => $i['app']]; } - if($i['location'] || $i['coord']) { - $ret['location'] = [ 'type' => 'Place' ]; - if($i['location']) { + if ($i['location'] || $i['coord']) { + $ret['location'] = ['type' => 'Place']; + if ($i['location']) { $ret['location']['name'] = $i['location']; } - if($i['coord']) { - $l = explode(' ',$i['coord']); - $ret['location']['latitude'] = $l[0]; + if ($i['coord']) { + $l = explode(' ', $i['coord']); + $ret['location']['latitude'] = $l[0]; $ret['location']['longitude'] = $l[1]; } } - if($i['id'] != $i['parent']) { + if ($i['id'] != $i['parent']) { $reply = true; // 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'])); - } - - if($i['item_private']) { - $d = q("select xchan_url, xchan_addr, xchan_name from item left join xchan on xchan_hash = author_xchan where id = %d limit 1", - intval($i['parent']) - ); - if($d) { - $is_directmessage = false; - $recips = get_iconfig($i['parent'], 'activitypub', 'recips'); - if(array_path_exists('to', $recips) && in_array($i['author']['xchan_url'], $recips['to'])) { - $reply_url = $d[0]['xchan_url']; - $is_directmessage = true; - } - else { - $reply_url = z_root() . '/followers/' . substr($i['author']['xchan_addr'],0,strpos($i['author']['xchan_addr'],'@')); - } - - $reply_addr = (($d[0]['xchan_addr']) ? $d[0]['xchan_addr'] : $d[0]['xchan_name']); - } + 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'])); } - } - $actor = self::encode_person($i['author'],false); - if($actor) + $actor = self::encode_person($i['author'], false); + if ($actor) $ret['actor'] = $actor; else - return []; + return []; - if(strpos($i['body'],'[/share]') !== false) { + if (strpos($i['body'], '[/share]') !== false) { $i['obj'] = null; } - if($i['obj']) { - if(! is_array($i['obj'])) { - $i['obj'] = json_decode($i['obj'],true); + if ($i['obj']) { + if (!is_array($i['obj'])) { + $i['obj'] = json_decode($i['obj'], true); } - if($i['obj']['type'] === ACTIVITY_OBJ_PHOTO) { + if ($i['obj']['type'] === ACTIVITY_OBJ_PHOTO) { $i['obj']['id'] = $i['mid']; } $obj = self::encode_object($i['obj']); - if($obj) + if ($obj) $ret['object'] = $obj; else return []; } else { $obj = self::encode_item($i); - if($obj) + if ($obj) $ret['object'] = $obj; else return []; } - if(array_path_exists('object/type',$ret) && $ret['object']['type'] === 'Event' && $ret['type'] === 'Create') { + 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'])) { - $i['target'] = json_decode($i['target'],true); + if ($i['target']) { + if (!is_array($i['target'])) { + $i['target'] = json_decode($i['target'], true); } $tgt = self::encode_object($i['target']); - if($tgt) + if ($tgt) $ret['target'] = $tgt; else return []; @@ -868,12 +914,12 @@ class Activity { // addressing madness - $public = (($i['item_private']) ? false : true); + $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'],'@')) ]; + $ret['to'] = [ACTIVITY_PUBLIC_INBOX]; + $ret['cc'] = [z_root() . '/followers/' . substr($i['author']['xchan_addr'], 0, strpos($i['author']['xchan_addr'], '@'))]; } else { @@ -886,7 +932,7 @@ class Activity { $ret['to'] = []; if ($ret['tag']) { foreach ($ret['tag'] as $mention) { - if (is_array($mention) && array_key_exists('href',$mention) && $mention['href']) { + 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']) ); @@ -897,7 +943,7 @@ class Activity { else { $addr = $h[0]['hubloc_id_url']; } - if (! in_array($addr,$ret['to'])) { + if (!in_array($addr, $ret['to'])) { $ret['to'][] = $addr; } } @@ -915,7 +961,7 @@ class Activity { else { $addr = $d[0]['hubloc_id_url']; } - if (! in_array($addr,$ret['to'])) { + if (!in_array($addr, $ret['to'])) { $ret['cc'][] = $addr; } } @@ -924,7 +970,7 @@ class Activity { $mentions = self::map_mentions($i); if (count($mentions) > 0) { - if (! $ret['to']) { + if (!$ret['to']) { $ret['to'] = $mentions; } else { @@ -936,22 +982,19 @@ class Activity { } // Returns an array of URLS for any mention tags found in the item array $i. - static function map_mentions($i) { - if (! $i['term']) { - return []; - } - $list = []; - foreach ($i['term'] as $t) { - if (! $t['url']) { - continue; - } - if ($t['ttype'] == TERM_MENTION) { - $url = self::lookup_term_url($t['url']); - $list[] = (($url) ? $url : $t['url']); + if (array_key_exists('term', $i) && is_array($i['term'])) { + foreach ($i['term'] as $t) { + if (!$t['url']) { + continue; + } + if ($t['ttype'] == TERM_MENTION) { + $url = self::lookup_term_url($t['url']); + $list[] = (($url) ? $url : $t['url']); + } } } @@ -959,11 +1002,10 @@ class Activity { } // Returns an array of all recipients targeted by private item array $i. - static function map_acl($i) { $ret = []; - if (! $i['item_private']) { + if (!$i['item_private']) { return $ret; } @@ -977,8 +1019,8 @@ class Activity { } if ($i['allow_cid']) { - $tmp = expand_acl($i['allow_cid']); - $list = stringify_array($tmp,true); + $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) { @@ -1013,22 +1055,22 @@ class Activity { static function encode_person($p, $extended = true) { - if(! $p['xchan_url']) + if (!$p['xchan_url']) return []; - if(! $extended) { + if (!$extended) { return $p['xchan_url']; } $ret = []; - $c = ((array_key_exists('channel_id',$p)) ? $p : channelx_by_hash($p['xchan_hash'])); + $c = ((array_key_exists('channel_id', $p)) ? $p : channelx_by_hash($p['xchan_hash'])); - $ret['type'] = 'Person'; + $ret['type'] = 'Person'; if ($c) { - $role = get_pconfig($c['channel_id'],'system','permissions_role'); - if (strpos($role,'forum') !== false) { + $role = get_pconfig($c['channel_id'], 'system', 'permissions_role'); + if (strpos($role, 'forum') !== false) { $ret['type'] = 'Group'; } } @@ -1037,33 +1079,23 @@ class Activity { $ret['id'] = channel_url($c); } else { - $ret['id'] = ((strpos($p['xchan_hash'],'http') === 0) ? $p['xchan_hash'] : $p['xchan_url']); + $ret['id'] = ((strpos($p['xchan_hash'], 'http') === 0) ? $p['xchan_hash'] : $p['xchan_url']); } - if($p['xchan_addr'] && strpos($p['xchan_addr'],'@')) - $ret['preferredUsername'] = substr($p['xchan_addr'],0,strpos($p['xchan_addr'],'@')); - $ret['name'] = $p['xchan_name']; - $ret['updated'] = datetime_convert('UTC','UTC',$p['xchan_name_date'],ATOM_TIME); - $ret['icon'] = [ + if ($p['xchan_addr'] && strpos($p['xchan_addr'], '@')) + $ret['preferredUsername'] = substr($p['xchan_addr'], 0, strpos($p['xchan_addr'], '@')); + + $ret['name'] = $p['xchan_name']; + $ret['updated'] = datetime_convert('UTC', 'UTC', $p['xchan_name_date'], ATOM_TIME); + $ret['icon'] = [ 'type' => 'Image', - 'mediaType' => (($p['xchan_photo_mimetype']) ? $p['xchan_photo_mimetype'] : 'image/png' ), - 'updated' => datetime_convert('UTC','UTC',$p['xchan_photo_date'],ATOM_TIME), + 'mediaType' => (($p['xchan_photo_mimetype']) ? $p['xchan_photo_mimetype'] : 'image/png'), + 'updated' => datetime_convert('UTC', 'UTC', $p['xchan_photo_date'], ATOM_TIME), 'url' => $p['xchan_photo_l'], 'height' => 300, 'width' => 300, ]; - $ret['url'] = [ - [ - 'type' => 'Link', - 'mediaType' => 'text/html', - 'href' => $p['xchan_url'] - ], - [ - 'type' => 'Link', - 'mediaType' => 'text/x-zot+json', - 'href' => $p['xchan_url'] - ] - ]; + $ret['url'] = $p['xchan_url']; $ret['publicKey'] = [ 'id' => $p['xchan_url'], @@ -1071,98 +1103,126 @@ class Activity { 'publicKeyPem' => $p['xchan_pubkey'] ]; - $arr = [ 'xchan' => $p, 'encoded' => $ret ]; + if ($c) { + $ret['outbox'] = z_root() . '/outbox/' . $c['channel_address']; + } + + $arr = [ + 'xchan' => $p, + 'encoded' => $ret + ]; + call_hooks('encode_person', $arr); $ret = $arr['encoded']; - return $ret; } + static function encode_item_object($item, $elm = 'obj') { + $ret = []; + if ($item[$elm]) { + if (! is_array($item[$elm])) { + $item[$elm] = json_decode($item[$elm],true); + } + if ($item[$elm]['type'] === ACTIVITY_OBJ_PHOTO) { + $item[$elm]['id'] = $item['mid']; + } + $obj = self::encode_object($item[$elm]); + if ($obj) + return $obj; + else + return []; + } + else { + $obj = self::encode_item($item); + if ($obj) + return $obj; + else + return []; + } - - + } static function activity_mapper($verb) { - if(strpos($verb,'/') === false) { + if (strpos($verb, '/') === false) { return $verb; } $acts = [ - 'http://activitystrea.ms/schema/1.0/post' => 'Create', - 'http://activitystrea.ms/schema/1.0/share' => 'Announce', - 'http://activitystrea.ms/schema/1.0/update' => 'Update', - 'http://activitystrea.ms/schema/1.0/like' => 'Like', - 'http://activitystrea.ms/schema/1.0/favorite' => 'Like', - 'http://purl.org/zot/activity/dislike' => 'Dislike', - 'http://activitystrea.ms/schema/1.0/tag' => 'Add', - 'http://activitystrea.ms/schema/1.0/follow' => 'Follow', - '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', - 'Invite' => 'Invite', - 'Delete' => 'Delete', - 'Undo' => 'Undo' + 'http://activitystrea.ms/schema/1.0/post' => 'Create', + 'http://activitystrea.ms/schema/1.0/share' => 'Announce', + 'http://activitystrea.ms/schema/1.0/update' => 'Update', + 'http://activitystrea.ms/schema/1.0/like' => 'Like', + 'http://activitystrea.ms/schema/1.0/favorite' => 'Like', + 'http://purl.org/zot/activity/dislike' => 'Dislike', + 'http://activitystrea.ms/schema/1.0/tag' => 'Add', + 'http://activitystrea.ms/schema/1.0/follow' => 'Follow', + 'http://activitystrea.ms/schema/1.0/unfollow' => 'Unfollow', + 'http://activitystrea.ms/schema/1.0/stop-following' => 'Unfollow', + 'http://purl.org/zot/activity/attendyes' => 'Accept', + 'http://purl.org/zot/activity/attendno' => 'Reject', + 'http://purl.org/zot/activity/attendmaybe' => 'TentativeAccept', + 'Invite' => 'Invite', + 'Delete' => 'Delete', + 'Undo' => 'Undo' ]; - call_hooks('activity_mapper',$acts); + call_hooks('activity_mapper', $acts); - if(array_key_exists($verb,$acts) && $acts[$verb]) { + if (array_key_exists($verb, $acts) && $acts[$verb]) { return $acts[$verb]; } // Reactions will just map to normal activities - if(strpos($verb,ACTIVITY_REACT) !== false) + if (strpos($verb, ACTIVITY_REACT) !== false) return 'emojiReaction'; - if(strpos($verb,ACTIVITY_MOOD) !== false) + if (strpos($verb, ACTIVITY_MOOD) !== false) return 'Create'; - if(strpos($verb,ACTIVITY_FRIEND) !== false) + if (strpos($verb, ACTIVITY_FRIEND) !== false) return 'Create'; - if(strpos($verb,ACTIVITY_POKE) !== false) + if (strpos($verb, ACTIVITY_POKE) !== false) return 'Activity'; - // We should return false, however this will trigger an uncaught execption and crash + // We should return false, however this will trigger an uncaught execption and crash // the delivery system if encountered by the JSON-LDSignature library - + logger('Unmapped activity: ' . $verb); return 'Create'; - // return false; -} - - + // return false; + } static function activity_decode_mapper($verb) { $acts = [ - 'http://activitystrea.ms/schema/1.0/post' => 'Create', - 'http://activitystrea.ms/schema/1.0/share' => 'Announce', - 'http://activitystrea.ms/schema/1.0/update' => 'Update', - 'http://activitystrea.ms/schema/1.0/like' => 'Like', - 'http://activitystrea.ms/schema/1.0/favorite' => 'Like', - 'http://purl.org/zot/activity/dislike' => 'Dislike', - 'http://activitystrea.ms/schema/1.0/tag' => 'Add', - 'http://activitystrea.ms/schema/1.0/follow' => 'Follow', - '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', - 'Invite' => 'Invite', - 'Delete' => 'Delete', - 'Undo' => 'Undo' + 'http://activitystrea.ms/schema/1.0/post' => 'Create', + 'http://activitystrea.ms/schema/1.0/share' => 'Announce', + 'http://activitystrea.ms/schema/1.0/update' => 'Update', + 'http://activitystrea.ms/schema/1.0/like' => 'Like', + 'http://activitystrea.ms/schema/1.0/favorite' => 'Like', + 'http://purl.org/zot/activity/dislike' => 'Dislike', + 'http://activitystrea.ms/schema/1.0/tag' => 'Add', + 'http://activitystrea.ms/schema/1.0/follow' => 'Follow', + 'http://activitystrea.ms/schema/1.0/unfollow' => 'Unfollow', + 'http://activitystrea.ms/schema/1.0/stop-following' => 'Unfollow', + 'http://purl.org/zot/activity/attendyes' => 'Accept', + 'http://purl.org/zot/activity/attendno' => 'Reject', + 'http://purl.org/zot/activity/attendmaybe' => 'TentativeAccept', + 'Invite' => 'Invite', + 'Delete' => 'Delete', + 'Undo' => 'Undo' ]; - call_hooks('activity_decode_mapper',$acts); + call_hooks('activity_decode_mapper', $acts); - foreach($acts as $k => $v) { - if($verb === $v) { + foreach ($acts as $k => $v) { + if ($verb === $v) { return $k; } } @@ -1175,33 +1235,33 @@ class Activity { static function activity_obj_decode_mapper($obj) { $objs = [ - 'http://activitystrea.ms/schema/1.0/note' => 'Note', - 'http://activitystrea.ms/schema/1.0/note' => 'Article', - 'http://activitystrea.ms/schema/1.0/comment' => 'Note', - 'http://activitystrea.ms/schema/1.0/person' => 'Person', - 'http://purl.org/zot/activity/profile' => 'Profile', - '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://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' + 'http://activitystrea.ms/schema/1.0/note' => 'Note', + 'http://activitystrea.ms/schema/1.0/note' => 'Article', + 'http://activitystrea.ms/schema/1.0/comment' => 'Note', + 'http://activitystrea.ms/schema/1.0/person' => 'Person', + 'http://purl.org/zot/activity/profile' => 'Profile', + '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://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); + call_hooks('activity_obj_decode_mapper', $objs); - foreach($objs as $k => $v) { - if($obj === $v) { + foreach ($objs as $k => $v) { + if ($obj === $v) { return $k; } } @@ -1210,45 +1270,42 @@ class Activity { return 'Note'; } - - - static function activity_obj_mapper($obj) { $objs = [ - 'http://activitystrea.ms/schema/1.0/note' => 'Note', - 'http://activitystrea.ms/schema/1.0/comment' => 'Note', - 'http://activitystrea.ms/schema/1.0/person' => 'Person', - 'http://purl.org/zot/activity/profile' => 'Profile', - '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://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' + 'http://activitystrea.ms/schema/1.0/note' => 'Note', + 'http://activitystrea.ms/schema/1.0/comment' => 'Note', + 'http://activitystrea.ms/schema/1.0/person' => 'Person', + 'http://purl.org/zot/activity/profile' => 'Profile', + '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://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); + call_hooks('activity_obj_mapper', $objs); if ($obj === 'Answer') { return 'Note'; } - if (strpos($obj,'/') === false) { + if (strpos($obj, '/') === false) { return $obj; } - if(array_key_exists($obj,$objs)) { + if (array_key_exists($obj, $objs)) { return $objs[$obj]; } @@ -1259,108 +1316,103 @@ class Activity { } + static function follow($channel, $act) { - static function follow($channel,$act) { - - $contact = null; + $contact = null; $their_follow_id = null; /* - * - * if $act->type === 'Follow', actor is now following $channel - * if $act->type === 'Accept', actor has approved a follow request from $channel - * + * + * if $act->type === 'Follow', actor is now following $channel + * if $act->type === 'Accept', actor has approved a follow request from $channel + * */ - $person_obj = $act->actor; - - if($act->type === 'Follow') { - $their_follow_id = $act->id; - } - elseif($act->type === 'Accept') { - $my_follow_id = z_root() . '/follow/' . $contact['id']; + if (in_array($act->type, [ 'Follow', 'Invite', 'Join'])) { + $their_follow_id = $act->id; } - - if(is_array($person_obj)) { + + $person_obj = (($act->type == 'Invite') ? $act->obj : $act->actor); + + if (is_array($person_obj)) { // store their xchan and hubloc - self::actor_store($person_obj['id'],$person_obj); + self::actor_store($person_obj['id'], $person_obj); - // Find any existing abook record + // Find any existing abook record $r = q("select * from abook left join xchan on abook_xchan = xchan_hash where abook_xchan = '%s' and abook_channel = %d limit 1", dbesc($person_obj['id']), intval($channel['channel_id']) ); - if($r) { + if ($r) { $contact = $r[0]; } } $x = \Zotlabs\Access\PermissionRoles::role_perms('social'); - $p = \Zotlabs\Access\Permissions::FilledPerms($x['perms_connect']); - $their_perms = \Zotlabs\Access\Permissions::serialise($p); + $their_perms = \Zotlabs\Access\Permissions::FilledPerms($x['perms_connect']); - if($contact && $contact['abook_id']) { + if ($contact && $contact['abook_id']) { - // A relationship of some form already exists on this site. + // A relationship of some form already exists on this site. - switch($act->type) { + switch ($act->type) { case 'Follow': + case 'Invite': + case 'Join': // A second Follow request, but we haven't approved the first one - - if($contact['abook_pending']) { + if ($contact['abook_pending']) { return; } // We've already approved them or followed them first // Send an Accept back to them - - set_abconfig($channel['channel_id'],$person_obj['id'],'pubcrawl','their_follow_id', $their_follow_id); - Master::Summon([ 'Notifier', 'permissions_accept', $contact['abook_id'] ]); + set_abconfig($channel['channel_id'], $person_obj['id'], 'pubcrawl', 'their_follow_id', $their_follow_id); + Master::Summon(['Notifier', 'permission_accept', $contact['abook_id']]); return; case 'Accept': // They accepted our Follow request - set default permissions - - set_abconfig($channel['channel_id'],$contact['abook_xchan'],'system','their_perms',$their_perms); + + set_abconfig($channel['channel_id'], $contact['abook_xchan'], 'system', 'their_perms', $their_perms); $abook_instance = $contact['abook_instance']; - - if(strpos($abook_instance,z_root()) === false) { - if($abook_instance) + + if (strpos($abook_instance, z_root()) === false) { + if ($abook_instance) $abook_instance .= ','; $abook_instance .= z_root(); - $r = q("update abook set abook_instance = '%s', abook_not_here = 0 + q("update abook set abook_instance = '%s', abook_not_here = 0 where abook_id = %d and abook_channel = %d", dbesc($abook_instance), intval($contact['abook_id']), intval($channel['channel_id']) ); } - + return; default: return; - + } } // No previous relationship exists. - if($act->type === 'Accept') { + if ($act->type === 'Accept') { // This should not happen unless we deleted the connection before it was accepted. return; } // From here on out we assume a Follow activity to somebody we have no existing relationship with - set_abconfig($channel['channel_id'],$person_obj['id'],'pubcrawl','their_follow_id', $their_follow_id); + set_abconfig($channel['channel_id'], $person_obj['id'], 'pubcrawl', 'their_follow_id', $their_follow_id); // The xchan should have been created by actor_store() above @@ -1368,17 +1420,17 @@ class Activity { dbesc($person_obj['id']) ); - if(! $r) { + if (!$r) { logger('xchan not found for ' . $person_obj['id']); return; } $ret = $r[0]; $p = \Zotlabs\Access\Permissions::connect_perms($channel['channel_id']); - $my_perms = \Zotlabs\Access\Permissions::serialise($p['perms']); + $my_perms = $p['perms']; $automatic = $p['automatic']; - $closeness = get_pconfig($channel['channel_id'],'system','new_abook_closeness',80); + $closeness = get_pconfig($channel['channel_id'], 'system', 'new_abook_closeness', 80); $r = abook_store_lowlevel( [ @@ -1394,64 +1446,65 @@ class Activity { 'abook_instance' => z_root() ] ); - + if($my_perms) - set_abconfig($channel['channel_id'],$ret['xchan_hash'],'system','my_perms',$my_perms); + foreach($my_perms as $k => $v) + set_abconfig($channel['channel_id'],$ret['xchan_hash'],'my_perms',$k,$v); if($their_perms) - set_abconfig($channel['channel_id'],$ret['xchan_hash'],'system','their_perms',$their_perms); + foreach($their_perms as $k => $v) + set_abconfig($channel['channel_id'],$ret['xchan_hash'],'their_perms',$k,$v); - - if($r) { + if ($r) { logger("New ActivityPub follower for {$channel['channel_name']}"); $new_connection = q("select * from abook left join xchan on abook_xchan = xchan_hash left join hubloc on hubloc_hash = xchan_hash where abook_channel = %d and abook_xchan = '%s' order by abook_created desc limit 1", intval($channel['channel_id']), dbesc($ret['xchan_hash']) ); - if($new_connection) { - \Zotlabs\Lib\Enotify::submit( + if ($new_connection) { + Enotify::submit( [ - 'type' => NOTIFY_INTRO, - 'from_xchan' => $ret['xchan_hash'], - 'to_xchan' => $channel['channel_hash'], - 'link' => z_root() . '/connedit/' . $new_connection[0]['abook_id'], + 'type' => NOTIFY_INTRO, + 'from_xchan' => $ret['xchan_hash'], + 'to_xchan' => $channel['channel_hash'], + 'link' => z_root() . '/connedit/' . $new_connection[0]['abook_id'], ] ); - if($my_perms && $automatic) { + if ($my_perms && $automatic) { // send an Accept for this Follow activity - Master::Summon([ 'Notifier', 'permissions_accept', $new_connection[0]['abook_id'] ]); + Master::Summon(['Notifier', 'permission_accept', $new_connection[0]['abook_id']]); // Send back a Follow notification to them - Master::Summon([ 'Notifier', 'permissions_create', $new_connection[0]['abook_id'] ]); + Master::Summon(['Notifier', 'permission_create', $new_connection[0]['abook_id']]); } - $clone = array(); - foreach($new_connection[0] as $k => $v) { - if(strpos($k,'abook_') === 0) { + $clone = []; + foreach ($new_connection[0] as $k => $v) { + if (strpos($k, 'abook_') === 0) { $clone[$k] = $v; } } unset($clone['abook_id']); unset($clone['abook_account']); unset($clone['abook_channel']); - - $abconfig = load_abconfig($channel['channel_id'],$clone['abook_xchan']); - if($abconfig) + $abconfig = load_abconfig($channel['channel_id'], $clone['abook_xchan']); + + if ($abconfig) $clone['abconfig'] = $abconfig; - Libsync::build_sync_packet($channel['channel_id'], [ 'abook' => array($clone) ] ); + Libsync::build_sync_packet($channel['channel_id'], ['abook' => [$clone]]); } } /* If there is a default group for this channel and permissions are automatic, add this member to it */ - if($channel['channel_default_group'] && $automatic) { - $g = Group::rec_byhash($channel['channel_id'],$channel['channel_default_group']); - if($g) - Group::member_add($channel['channel_id'],'',$ret['xchan_hash'],$g['id']); + if ($channel['channel_default_group'] && $automatic) { + $g = Group::rec_byhash($channel['channel_id'], $channel['channel_default_group']); + if ($g) + Group::member_add($channel['channel_id'], '', $ret['xchan_hash'], $g['id']); } @@ -1459,8 +1512,7 @@ class Activity { } - - static function unfollow($channel,$act) { + static function unfollow($channel, $act) { $contact = null; @@ -1470,60 +1522,115 @@ class Activity { $person_obj = $act->actor; - if(is_array($person_obj)) { + if (is_array($person_obj)) { $r = q("select * from abook left join xchan on abook_xchan = xchan_hash where abook_xchan = '%s' and abook_channel = %d limit 1", dbesc($person_obj['id']), intval($channel['channel_id']) ); - if($r) { + if ($r) { // remove all permissions they provided - del_abconfig($channel['channel_id'],$r[0]['xchan_hash'],'system','their_perms',EMPTY_STR); + del_abconfig($channel['channel_id'], $r[0]['xchan_hash'], 'system', 'their_perms'); } } return; } + static function actor_store($url, $person_obj, $force = false) { + + if (!is_array($person_obj)) { + return; + } + +/* not implemented + if (array_key_exists('movedTo',$person_obj) && $person_obj['movedTo'] && ! is_array($person_obj['movedTo'])) { + $tgt = self::fetch($person_obj['movedTo']); + if (is_array($tgt)) { + self::actor_store($person_obj['movedTo'],$tgt); + ActivityPub::move($person_obj['id'],$tgt); + } + return; + } +*/ + $ap_hubloc = null; + + $hublocs = self::get_actor_hublocs($url); + if ($hublocs) { + foreach ($hublocs as $hub) { + if ($hub['hubloc_network'] === 'activitypub') { + $ap_hubloc = $hub; + } + if ($hub['hubloc_network'] === 'zot6') { + Libzot::update_cached_hubloc($hub); + } + } + } + if ($ap_hubloc) { + // we already have a stored record. Determine if it needs updating. + if ($ap_hubloc['hubloc_updated'] < datetime_convert('UTC','UTC',' now - 3 days') || $force) { + $person_obj = self::fetch($url); + } + else { + return; + } + } - static function actor_store($url,$person_obj) { + if (isset($person_obj['id'])) { + $url = $person_obj['id']; + } - if(! is_array($person_obj)) + if (! $url) { return; + } $inbox = $person_obj['inbox']; // invalid identity - if (! $inbox || strpos($inbox,z_root()) !== false) { + if (!$inbox || strpos($inbox, z_root()) !== false) { return; } + // store the actor record in XConfig + XConfig::Set($url, 'system', 'actor_record', $person_obj); + $name = $person_obj['name']; - if(! $name) + if (!$name) { $name = $person_obj['preferredUsername']; - if(! $name) + } + if (!$name) { $name = t('Unknown'); + } - if($person_obj['icon']) { - if(is_array($person_obj['icon'])) { - if(array_key_exists('url',$person_obj['icon'])) + $icon = z_root() . '/' . get_default_profile_photo(300); + if ($person_obj['icon']) { + if (is_array($person_obj['icon'])) { + if (array_key_exists('url', $person_obj['icon'])) { $icon = $person_obj['icon']['url']; - else - $icon = $person_obj['icon'][0]['url']; + } + else { + if (is_string($person_obj['icon'][0])) { + $icon = $person_obj['icon'][0]; + } + elseif (array_key_exists('url', $person_obj['icon'][0])) { + $icon = $person_obj['icon'][0]['url']; + } + } } - else + else { $icon = $person_obj['icon']; + } } - $links = false; + $links = false; $profile = false; if (is_array($person_obj['url'])) { - if (! array_key_exists(0,$person_obj['url'])) { - $links = [ $person_obj['url'] ]; + if (!array_key_exists(0, $person_obj['url'])) { + $links = [$person_obj['url']]; } else { $links = $person_obj['url']; @@ -1532,11 +1639,11 @@ class Activity { if ($links) { foreach ($links as $link) { - if (array_key_exists('mediaType',$link) && $link['mediaType'] === 'text/html') { + if (array_key_exists('mediaType', $link) && $link['mediaType'] === 'text/html') { $profile = $link['href']; } } - if (! $profile) { + if (!$profile) { $profile = $links[0]['href']; } } @@ -1544,109 +1651,98 @@ class Activity { $profile = $person_obj['url']; } - if (! $profile) { + if (!$profile) { $profile = $url; } - $collections = []; - - if($inbox) { - $collections['inbox'] = $inbox; - if($person_obj['outbox']) - $collections['outbox'] = $person_obj['outbox']; - if($person_obj['followers']) - $collections['followers'] = $person_obj['followers']; - if($person_obj['following']) - $collections['following'] = $person_obj['following']; - if($person_obj['endpoints'] && $person_obj['endpoints']['sharedInbox']) - $collections['sharedInbox'] = $person_obj['endpoints']['sharedInbox']; - } - - if(array_key_exists('publicKey',$person_obj) && array_key_exists('publicKeyPem',$person_obj['publicKey'])) { - if($person_obj['id'] === $person_obj['publicKey']['owner']) { + if (array_key_exists('publicKey', $person_obj) && array_key_exists('publicKeyPem', $person_obj['publicKey'])) { + if ($person_obj['id'] === $person_obj['publicKey']['owner']) { $pubkey = $person_obj['publicKey']['publicKeyPem']; - if(strstr($pubkey,'RSA ')) { - $pubkey = rsatopem($pubkey); + if (strstr($pubkey, 'RSA ')) { + $pubkey = Keyutils::rsaToPem($pubkey); } } } - $r = q("select * from xchan where xchan_hash = '%s' limit 1", + $m = parse_url($url); + if($m) { + $hostname = $m['host']; + $baseurl = $m['scheme'] . '://' . $m['host'] . (($m['port']) ? ':' . $m['port'] : ''); + $site_url = $m['scheme'] . '://' . $m['host']; + } + + $r = q("select * from xchan join hubloc on xchan_hash = hubloc_hash where xchan_hash = '%s'", dbesc($url) ); - if(! $r) { - // create a new record - - $r = xchan_store_lowlevel( - [ - 'xchan_hash' => $url, - 'xchan_guid' => $url, - 'xchan_pubkey' => $pubkey, - 'xchan_addr' => '', - 'xchan_url' => $profile, - 'xchan_name' => $name, - 'xchan_name_date' => datetime_convert(), - 'xchan_network' => 'activitypub' - ] - ); - } - else { + if($r) { // Record exists. Cache existing records for one week at most - // then refetch to catch updated profile photos, names, etc. - - $d = datetime_convert('UTC','UTC','now - 1 week'); - if($r[0]['xchan_name_date'] > $d) + // then refetch to catch updated profile photos, names, etc. + $d = datetime_convert('UTC', 'UTC', 'now - 3 days'); + if($r[0]['hubloc_updated'] > $d && !$force) { return; + } - // update existing record - $r = q("update xchan set xchan_name = '%s', xchan_pubkey = '%s', xchan_network = '%s', xchan_name_date = '%s' where xchan_hash = '%s'", - dbesc($name), - dbesc($pubkey), - dbesc('activitypub'), + q("UPDATE site SET site_update = '%s', site_dead = 0 WHERE site_url = '%s'", dbesc(datetime_convert()), + dbesc($site_url) + ); + + // update existing xchan record + q("update xchan set xchan_name = '%s', xchan_guid = '%s', xchan_pubkey = '%s', xchan_network = 'activitypub', xchan_name_date = '%s' where xchan_hash = '%s'", + dbesc(escape_tags($name)), + dbesc(escape_tags($url)), + dbesc(escape_tags($pubkey)), + dbescdate(datetime_convert()), dbesc($url) ); - } - if($collections) { - set_xconfig($url,'activitypub','collections',$collections); + // update existing hubloc record + q("update hubloc set hubloc_guid = '%s', hubloc_network = 'activitypub', hubloc_url = '%s', hubloc_host = '%s', hubloc_callback = '%s', hubloc_updated = '%s', hubloc_id_url = '%s' where hubloc_hash = '%s'", + dbesc(escape_tags($url)), + dbesc(escape_tags($baseurl)), + dbesc(escape_tags($hostname)), + dbesc(escape_tags($inbox)), + dbescdate(datetime_convert()), + dbesc(escape_tags($profile)), + dbesc($url) + ); } + else { + // create a new record - $r = q("select * from hubloc where hubloc_hash = '%s' limit 1", - dbesc($url) - ); - - - $m = parse_url($url); - if($m) { - $hostname = $m['host']; - $baseurl = $m['scheme'] . '://' . $m['host'] . (($m['port']) ? ':' . $m['port'] : ''); - } + xchan_store_lowlevel( + [ + 'xchan_hash' => escape_tags($url), + 'xchan_guid' => escape_tags($url), + 'xchan_pubkey' => escape_tags($pubkey), + 'xchan_addr' => '', + 'xchan_url' => escape_tags($profile), + 'xchan_name' => escape_tags($name), + 'xchan_name_date' => datetime_convert(), + 'xchan_network' => 'activitypub' + ] + ); - if(! $r) { - $r = hubloc_store_lowlevel( + hubloc_store_lowlevel( [ - 'hubloc_guid' => $url, - 'hubloc_hash' => $url, + 'hubloc_guid' => escape_tags($url), + 'hubloc_hash' => escape_tags($url), 'hubloc_addr' => '', 'hubloc_network' => 'activitypub', - 'hubloc_url' => $baseurl, - 'hubloc_host' => $hostname, - 'hubloc_callback' => $inbox, + 'hubloc_url' => escape_tags($baseurl), + 'hubloc_host' => escape_tags($hostname), + 'hubloc_callback' => escape_tags($inbox), 'hubloc_updated' => datetime_convert(), 'hubloc_primary' => 1, - 'hubloc_id_url' => $profile + 'hubloc_id_url' => escape_tags($profile) ] ); } - if(! $icon) - $icon = z_root() . '/' . get_default_profile_photo(300); - - $photos = import_xchan_photo($icon,$url); - $r = q("update xchan set xchan_photo_date = '%s', xchan_photo_l = '%s', xchan_photo_m = '%s', xchan_photo_s = '%s', xchan_photo_mimetype = '%s' where xchan_hash = '%s'", - dbescdate(datetime_convert('UTC','UTC',$photos[5])), + $photos = import_xchan_photo($icon, $url); + q("update xchan set xchan_photo_date = '%s', xchan_photo_l = '%s', xchan_photo_m = '%s', xchan_photo_s = '%s', xchan_photo_mimetype = '%s' where xchan_hash = '%s'", + dbescdate(datetime_convert('UTC', 'UTC', $photos[5])), dbesc($photos[0]), dbesc($photos[1]), dbesc($photos[2]), @@ -1656,54 +1752,46 @@ class Activity { } + static function create_action($channel, $observer_hash, $act) { - static function create_action($channel,$observer_hash,$act) { - - if(in_array($act->obj['type'], [ 'Note', 'Article', 'Video' ])) { - self::create_note($channel,$observer_hash,$act); + if (in_array($act->obj['type'], ['Note', 'Article', 'Video'])) { + self::create_note($channel, $observer_hash, $act); } } - static function announce_action($channel,$observer_hash,$act) { + static function announce_action($channel, $observer_hash, $act) { - if(in_array($act->type, [ 'Announce' ])) { - self::announce_note($channel,$observer_hash,$act); + if (in_array($act->type, ['Announce'])) { + self::announce_note($channel, $observer_hash, $act); } } + static function like_action($channel, $observer_hash, $act) { - static function like_action($channel,$observer_hash,$act) { - - if(in_array($act->obj['type'], [ 'Note', 'Article', 'Video' ])) { - self::like_note($channel,$observer_hash,$act); + if (in_array($act->obj['type'], ['Note', 'Article', 'Video'])) { + self::like_note($channel, $observer_hash, $act); } } // sort function width decreasing - - static function vid_sort($a,$b) { - if($a['width'] === $b['width']) + static function vid_sort($a, $b) { + if ($a['width'] === $b['width']) return 0; return (($a['width'] > $b['width']) ? -1 : 1); } - static function create_note($channel,$observer_hash,$act) { + static function create_note($channel, $observer_hash, $act) { $s = []; - - // Mastodon only allows visibility in public timelines if the public inbox is listed in the 'to' field. - // They are hidden in the public timeline if the public inbox is listed in the 'cc' field. - // This is not part of the activitypub protocol - we might change this to show all public posts in pubstream at some point. - $pubstream = ((is_array($act->obj) && array_key_exists('to', $act->obj) && in_array(ACTIVITY_PUBLIC_INBOX, $act->obj['to'])) ? true : false); $is_sys_channel = is_sys_channel($channel['channel_id']); + $parent = ((array_key_exists('inReplyTo', $act->obj)) ? urldecode($act->obj['inReplyTo']) : ''); - $parent = ((array_key_exists('inReplyTo',$act->obj)) ? urldecode($act->obj['inReplyTo']) : ''); - if($parent) { + if ($parent) { $r = q("select * from item where uid = %d and ( mid = '%s' or mid = '%s' ) limit 1", intval($channel['channel_id']), @@ -1711,39 +1799,54 @@ class Activity { dbesc(basename($parent)) ); - if(! $r) { + if (!$r) { logger('parent not found.'); return; } - if($r[0]['owner_xchan'] === $channel['channel_hash']) { - if(! perm_is_allowed($channel['channel_id'],$observer_hash,'send_stream') && ! ($is_sys_channel && $pubstream)) { + if ($r[0]['owner_xchan'] === $channel['channel_hash']) { + if (!perm_is_allowed($channel['channel_id'], $observer_hash, 'send_stream') && !$is_sys_channel) { logger('no comment permission.'); return; } } - $s['parent_mid'] = $r[0]['mid']; - $s['owner_xchan'] = $r[0]['owner_xchan']; + $s['parent_mid'] = $r[0]['mid']; + $s['owner_xchan'] = $r[0]['owner_xchan']; $s['author_xchan'] = $observer_hash; } else { - if(! perm_is_allowed($channel['channel_id'],$observer_hash,'send_stream') && ! ($is_sys_channel && $pubstream)) { - logger('no permission'); + if (!perm_is_allowed($channel['channel_id'], $observer_hash, 'send_stream') && !$is_sys_channel) { + logger('no send_stream permission'); return; } $s['owner_xchan'] = $s['author_xchan'] = $observer_hash; } - + + if ($act->recips && (!in_array(ACTIVITY_PUBLIC_INBOX, $act->recips))) + $s['item_private'] = 1; + + + if (array_key_exists('directMessage', $act->obj) && intval($act->obj['directMessage'])) { + $s['item_private'] = 2; + } + + if (intval($s['item_private']) === 2) { + if (!perm_is_allowed($channel['channel_id'], $observer_hash, 'post_mail')) { + logger('no post_mail permission'); + return; + } + } + $abook = q("select * from abook where abook_xchan = '%s' and abook_channel = %d limit 1", dbesc($observer_hash), intval($channel['channel_id']) ); - + $content = self::get_content($act->obj); - if(! $content) { + if (!$content) { logger('no content'); return; } @@ -1756,105 +1859,105 @@ class Activity { $s['author_xchan'] = self::find_best_identity($s['author_xchan']); $s['owner_xchan'] = self::find_best_identity($s['owner_xchan']); - if(!$s['author_xchan']) { + if (!$s['author_xchan']) { logger('No author: ' . print_r($act, true)); } - if(!$s['owner_xchan']) { + if (!$s['owner_xchan']) { logger('No owner: ' . print_r($act, true)); } - if(!$s['author_xchan'] || !$s['owner_xchan']) + if (!$s['author_xchan'] || !$s['owner_xchan']) return; - $s['mid'] = urldecode($act->obj['id']); - $s['uuid'] = $act->obj['diaspora:guid']; + $s['mid'] = urldecode($act->obj['id']); + $s['uuid'] = $act->obj['diaspora:guid']; $s['plink'] = urldecode($act->obj['id']); - if($act->data['published']) { - $s['created'] = datetime_convert('UTC','UTC',$act->data['published']); + if ($act->data['published']) { + $s['created'] = datetime_convert('UTC', 'UTC', $act->data['published']); } - elseif($act->obj['published']) { - $s['created'] = datetime_convert('UTC','UTC',$act->obj['published']); + elseif ($act->obj['published']) { + $s['created'] = datetime_convert('UTC', 'UTC', $act->obj['published']); } - if($act->data['updated']) { - $s['edited'] = datetime_convert('UTC','UTC',$act->data['updated']); + if ($act->data['updated']) { + $s['edited'] = datetime_convert('UTC', 'UTC', $act->data['updated']); } - elseif($act->obj['updated']) { - $s['edited'] = datetime_convert('UTC','UTC',$act->obj['updated']); + elseif ($act->obj['updated']) { + $s['edited'] = datetime_convert('UTC', 'UTC', $act->obj['updated']); } if ($act->data['expires']) { - $s['expires'] = datetime_convert('UTC','UTC',$act->data['expires']); + $s['expires'] = datetime_convert('UTC', 'UTC', $act->data['expires']); } elseif ($act->obj['expires']) { - $s['expires'] = datetime_convert('UTC','UTC',$act->obj['expires']); + $s['expires'] = datetime_convert('UTC', 'UTC', $act->obj['expires']); } - if(! $s['created']) + if (!$s['created']) $s['created'] = datetime_convert(); - if(! $s['edited']) + if (!$s['edited']) $s['edited'] = $s['created']; - if(! $s['parent_mid']) + if (!$s['parent_mid']) $s['parent_mid'] = $s['mid']; - - $s['title'] = self::bb_content($content,'name'); - $s['summary'] = self::bb_content($content,'summary'); - $s['body'] = self::bb_content($content,'content'); + + $s['title'] = self::bb_content($content, 'name'); + $s['summary'] = self::bb_content($content, 'summary'); + $s['body'] = self::bb_content($content, 'content'); $s['verb'] = ACTIVITY_POST; $s['obj_type'] = ACTIVITY_OBJ_NOTE; $generator = $act->get_property_obj('generator'); - if(! $generator) - $generator = $act->get_property_obj('generator',$act->obj); + if (!$generator) + $generator = $act->get_property_obj('generator', $act->obj); - if($generator && array_key_exists('type',$generator) - && in_array($generator['type'], [ 'Application','Service' ] ) && array_key_exists('name',$generator)) { + if ($generator && array_key_exists('type', $generator) + && in_array($generator['type'], ['Application', 'Service']) && array_key_exists('name', $generator)) { $s['app'] = escape_tags($generator['name']); } - if($channel['channel_system']) { - if(! \Zotlabs\Lib\MessageFilter::evaluate($s,get_config('system','pubstream_incl'),get_config('system','pubstream_excl'))) { + if ($channel['channel_system']) { + if (!MessageFilter::evaluate($s, get_config('system', 'pubstream_incl'), get_config('system', 'pubstream_excl'))) { logger('post is filtered'); return; } } - if($abook) { - if(! post_is_importable($s,$abook[0])) { + if ($abook) { + if (!post_is_importable($s, $abook[0])) { logger('post is filtered'); return; } } - if($act->obj['conversation']) { - set_iconfig($s,'ostatus','conversation',$act->obj['conversation'],1); + if ($act->obj['conversation']) { + set_iconfig($s, 'ostatus', 'conversation', $act->obj['conversation'], 1); } $a = self::decode_taxonomy($act->obj); - if($a) { + if ($a) { $s['term'] = $a; } $a = self::decode_attachment($act->obj); - if($a) { + if ($a) { $s['attach'] = $a; } - if($act->obj['type'] === 'Note' && $s['attach']) { - $s['body'] .= self::bb_attach($s['attach'],$s['body']); + if ($act->obj['type'] === 'Note' && $s['attach']) { + $s['body'] .= self::bb_attach($s['attach'], $s['body']); } // we will need a hook here to extract magnet links e.g. peertube // right now just link to the largest mp4 we find that will fit in our // standard content region - if($act->obj['type'] === 'Video') { + if ($act->obj['type'] === 'Video') { $vtypes = [ 'video/mp4', @@ -1863,20 +1966,20 @@ class Activity { ]; $mps = []; - if(array_key_exists('url',$act->obj) && is_array($act->obj['url'])) { - foreach($act->obj['url'] as $vurl) { - if(in_array($vurl['mimeType'], $vtypes)) { - if(! array_key_exists('width',$vurl)) { + if (array_key_exists('url', $act->obj) && is_array($act->obj['url'])) { + foreach ($act->obj['url'] as $vurl) { + if (in_array($vurl['mimeType'], $vtypes)) { + if (!array_key_exists('width', $vurl)) { $vurl['width'] = 0; } $mps[] = $vurl; } } } - if($mps) { - usort($mps,[ __CLASS__, 'vid_sort' ]); - foreach($mps as $m) { - if(intval($m['width']) < 500) { + if ($mps) { + usort($mps, [__CLASS__, 'vid_sort']); + foreach ($mps as $m) { + if (intval($m['width']) < 500) { $s['body'] .= "\n\n" . '[video]' . $m['href'] . '[/video]'; break; } @@ -1884,17 +1987,9 @@ class Activity { } } - if($act->recips && (! in_array(ACTIVITY_PUBLIC_INBOX,$act->recips))) - $s['item_private'] = 1; - - - if (array_key_exists('directMessage',$act->obj) && intval($act->obj['directMessage'])) { - $s['item_private'] = 2; - } - - set_iconfig($s,'activitypub','recips',$act->raw_recips); - if($parent) { - set_iconfig($s,'activitypub','rawmsg',$act->raw,1); + set_iconfig($s, 'activitypub', 'recips', $act->raw_recips); + if ($parent) { + set_iconfig($s, 'activitypub', 'rawmsg', $act->raw, 1); } $x = null; @@ -1903,8 +1998,8 @@ class Activity { dbesc($s['mid']), intval($s['uid']) ); - if($r) { - if($s['edited'] > $r[0]['edited']) { + if ($r) { + if ($s['edited'] > $r[0]['edited']) { $x = item_store_update($s); } else { @@ -1915,20 +2010,20 @@ class Activity { $x = item_store($s); } - if(is_array($x) && $x['item_id']) { - if($parent) { - if($s['owner_xchan'] === $channel['channel_hash']) { + if (is_array($x) && $x['item_id']) { + if ($parent) { + if ($s['owner_xchan'] === $channel['channel_hash']) { // We are the owner of this conversation, so send all received comments back downstream - Master::Summon(array('Notifier','comment-import',$x['item_id'])); + Master::Summon(['Notifier', 'comment-import', $x['item_id']]); } $r = q("select * from item where id = %d limit 1", intval($x['item_id']) ); - if($r) { - send_status_notifications($x['item_id'],$r[0]); + if ($r) { + send_status_notifications($x['item_id'], $r[0]); } } - sync_an_item($channel['channel_id'],$x['item_id']); + sync_an_item($channel['channel_id'], $x['item_id']); } } @@ -1940,26 +2035,24 @@ class Activity { dbesc($id) ); - if($x) { - return sprintf('@[zrl=%s]%s[/zrl]',$x[0]['xchan_url'],$x[0]['xchan_name']); + if ($x) { + return sprintf('@[zrl=%s]%s[/zrl]', $x[0]['xchan_url'], $x[0]['xchan_name']); } return '@{' . $id . '}'; } - - - static function update_poll($item,$post) { - $multi = false; - $mid = $post['mid']; + static function update_poll($item, $post) { + $multi = false; + $mid = $post['mid']; $content = $post['title']; - - if (! $item) { + + if (!$item) { return false; } - $o = json_decode($item['obj'],true); - if ($o && array_key_exists('anyOf',$o)) { + $o = json_decode($item['obj'], true); + if ($o && array_key_exists('anyOf', $o)) { $multi = true; } @@ -1969,7 +2062,7 @@ class Activity { ); // 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) { @@ -1986,103 +2079,112 @@ class Activity { } } } - + $answer_found = false; - $found = false; + $found = false; if ($multi) { - for ($c = 0; $c < count($o['anyOf']); $c ++) { + 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) { + 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' ]; + 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 ++) { + 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) { + 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' ]; + 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", + logger('updated_poll: ' . print_r($o, true), LOGGER_DATA); + if ($answer_found && !$found) { + 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'] ] ); + Master::Summon(['Notifier', 'wall-new', $item['id']]); return true; } return false; } + static function decode_note($act) { + // Within our family of projects, Follow/Unfollow of a thread is an internal activity which should not be transmitted, + // hence if we receive it - ignore or reject it. + // Unfollow is not defined by ActivityStreams, which prefers Undo->Follow. + // This may have to be revisited if AP projects start using Follow for objects other than actors. - static function decode_note($act) { + if (in_array($act->type, [ 'Follow', 'Unfollow' ])) { + return false; + } $response_activity = false; $s = []; - if(is_array($act->obj)) { + if (is_array($act->obj)) { $content = self::get_content($act->obj); } - + $s['owner_xchan'] = $act->actor['id']; $s['author_xchan'] = $act->actor['id']; // ensure we store the original actor - self::actor_store($act->actor['id'],$act->actor); + self::actor_store($act->actor['id'], $act->actor); $s['mid'] = $act->obj['id']; - $s['uuid'] = $act->obj['diaspora:guid']; + $s['uuid'] = $act->obj['diaspora:guid']; $s['parent_mid'] = $act->parent_id; - if($act->data['published']) { - $s['created'] = datetime_convert('UTC','UTC',$act->data['published']); + if (array_key_exists('published', $act->data)) { + $s['created'] = datetime_convert('UTC', 'UTC', $act->data['published']); + $s['commented'] = $s['created']; } - elseif($act->obj['published']) { - $s['created'] = datetime_convert('UTC','UTC',$act->obj['published']); + elseif (array_key_exists('published', $act->obj)) { + $s['created'] = datetime_convert('UTC', 'UTC', $act->obj['published']); + $s['commented'] = $s['created']; } - if($act->data['updated']) { - $s['edited'] = datetime_convert('UTC','UTC',$act->data['updated']); + if (array_key_exists('updated', $act->data)) { + $s['edited'] = datetime_convert('UTC', 'UTC', $act->data['updated']); } - elseif($act->obj['updated']) { - $s['edited'] = datetime_convert('UTC','UTC',$act->obj['updated']); + elseif (array_key_exists('updated', $act->obj)) { + $s['edited'] = datetime_convert('UTC', 'UTC', $act->obj['updated']); } - if ($act->data['expires']) { - $s['expires'] = datetime_convert('UTC','UTC',$act->data['expires']); + if (array_key_exists('expires', $act->data)) { + $s['expires'] = datetime_convert('UTC', 'UTC', $act->data['expires']); } - elseif ($act->obj['expires']) { - $s['expires'] = datetime_convert('UTC','UTC',$act->obj['expires']); + elseif (array_key_exists('expires', $act->obj)) { + $s['expires'] = datetime_convert('UTC', 'UTC', $act->obj['expires']); } - if(ActivityStreams::is_response_activity($act->type)) { + if (ActivityStreams::is_response_activity($act->type)) { $response_activity = true; @@ -2092,85 +2194,86 @@ class Activity { // over-ride the object timestamp with the activity - if($act->data['published']) { - $s['created'] = datetime_convert('UTC','UTC',$act->data['published']); + if ($act->data['published']) { + $s['created'] = datetime_convert('UTC', 'UTC', $act->data['published']); } - if($act->data['updated']) { - $s['edited'] = datetime_convert('UTC','UTC',$act->data['updated']); + if ($act->data['updated']) { + $s['edited'] = datetime_convert('UTC', 'UTC', $act->data['updated']); } $obj_actor = ((isset($act->obj['actor'])) ? $act->obj['actor'] : $act->get_actor('attributedTo', $act->obj)); // ensure we store the original actor - self::actor_store($obj_actor['id'],$obj_actor); + + self::actor_store($obj_actor['id'], $obj_actor); $mention = self::get_actor_bbmention($obj_actor['id']); - if($act->type === 'Like') { - $content['content'] = sprintf( t('Likes %1$s\'s %2$s'),$mention,$act->obj['type']) . "\n\n" . $content['content']; + if ($act->type === 'Like') { + $content['content'] = sprintf(t('Likes %1$s\'s %2$s'), $mention, $act->obj['type']) . "\n\n" . $content['content']; } - 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 === 'Dislike') { + $content['content'] = sprintf(t('Doesn\'t like %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->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']; + $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']; + $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']; + $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']; + $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']); + if ($act->type === 'Announce') { + $content['content'] = sprintf(t('🔁 Repeated %1$s\'s %2$s'), $mention, $act->obj['type']); } if ($act->type === 'emojiReaction') { $content['content'] = (($act->tgt && $act->tgt['type'] === 'Image') ? '[img=32x32]' . $act->tgt['url'] . '[/img]' : '&#x' . $act->tgt['name'] . ';'); - } + } } - if(! $s['created']) + if (! array_key_exists('created', $s)) $s['created'] = datetime_convert(); - if(! $s['edited']) + if (! array_key_exists('edited', $s)) $s['edited'] = $s['created']; - $s['title'] = (($response_activity) ? EMPTY_STR : self::bb_content($content,'name')); - $s['summary'] = self::bb_content($content,'summary'); - $s['body'] = ((self::bb_content($content,'bbcode') && (! $response_activity)) ? self::bb_content($content,'bbcode') : self::bb_content($content,'content')); + $s['title'] = (($response_activity) ? EMPTY_STR : self::bb_content($content, 'name')); + $s['summary'] = self::bb_content($content, 'summary'); + $s['body'] = ((self::bb_content($content, 'bbcode') && (!$response_activity)) ? self::bb_content($content, 'bbcode') : self::bb_content($content, 'content')); - $s['verb'] = self::activity_decode_mapper($act->type); + $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(in_array($act->type, [ 'Delete', 'Undo', 'Tombstone' ]) || ($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; } $s['obj_type'] = self::activity_obj_decode_mapper($act->obj['type']); - if($s['obj_type'] === ACTIVITY_OBJ_NOTE && $s['mid'] !== $s['parent_mid']) { + if ($s['obj_type'] === ACTIVITY_OBJ_NOTE && $s['mid'] !== $s['parent_mid']) { $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') { + 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->obj['type'] === 'Event') { if ($act->type === 'Invite') { $s['mid'] = $s['parent_mid'] = $act->id; } @@ -2179,52 +2282,51 @@ class Activity { if ($eventptr) { - $s['obj'] = []; - $s['obj']['asld'] = $eventptr; - $s['obj']['type'] = ACTIVITY_OBJ_EVENT; - $s['obj']['id'] = $eventptr['id']; + $s['obj'] = []; + $s['obj']['asld'] = $eventptr; + $s['obj']['type'] = ACTIVITY_OBJ_EVENT; + $s['obj']['id'] = $eventptr['id']; $s['obj']['title'] = $eventptr['name']; - if(strpos($act->obj['startTime'],'Z')) + if (strpos($act->obj['startTime'], 'Z')) $s['obj']['adjust'] = true; else $s['obj']['adjust'] = false; - $s['obj']['dtstart'] = datetime_convert('UTC','UTC',$eventptr['startTime']); - if($act->obj['endTime']) - $s['obj']['dtend'] = datetime_convert('UTC','UTC',$eventptr['endTime']); + $s['obj']['dtstart'] = datetime_convert('UTC', 'UTC', $eventptr['startTime']); + if ($act->obj['endTime']) + $s['obj']['dtend'] = datetime_convert('UTC', 'UTC', $eventptr['endTime']); else $s['obj']['nofinish'] = true; $s['obj']['description'] = $eventptr['content']; - if(array_path_exists('location/content',$eventptr)) + if (array_path_exists('location/content', $eventptr)) $s['obj']['location'] = $eventptr['location']['content']; } else { - $s['obj'] = $act->obj; + $s['obj'] = $act->obj; } $generator = $act->get_property_obj('generator'); - if((! $generator) && (! $response_activity)) { - $generator = $act->get_property_obj('generator',$act->obj); + if ((!$generator) && (!$response_activity)) { + $generator = $act->get_property_obj('generator', $act->obj); } - if($generator && array_key_exists('type',$generator) - && in_array($generator['type'], [ 'Application', 'Service' ] ) && array_key_exists('name',$generator)) { + if ($generator && array_key_exists('type', $generator) + && in_array($generator['type'], ['Application', 'Service']) && array_key_exists('name', $generator)) { $s['app'] = escape_tags($generator['name']); } - - if(! $response_activity) { + if (!$response_activity) { $a = self::decode_taxonomy($act->obj); - if($a) { + if ($a) { $s['term'] = $a; - foreach($a as $b) { - if($b['ttype'] === TERM_EMOJI) { - $s['title'] = str_replace($b['term'],'[img=16x16]' . $b['url'] . '[/img]',$s['title']); - $s['summary'] = str_replace($b['term'],'[img=16x16]' . $b['url'] . '[/img]',$s['summary']); - $s['body'] = str_replace($b['term'],'[img=16x16]' . $b['url'] . '[/img]',$s['body']); + foreach ($a as $b) { + if ($b['ttype'] === TERM_EMOJI) { + $s['title'] = str_replace($b['term'], '[img=16x16]' . $b['url'] . '[/img]', $s['title']); + $s['summary'] = str_replace($b['term'], '[img=16x16]' . $b['url'] . '[/img]', $s['summary']); + $s['body'] = str_replace($b['term'], '[img=16x16]' . $b['url'] . '[/img]', $s['body']); } } } @@ -2241,28 +2343,30 @@ class Activity { $s['iconfig'] = $a; } - if($act->obj['type'] === 'Note' && $s['attach']) { - $s['body'] .= self::bb_attach($s['attach'],$s['body']); - } + if (array_key_exists('type', $act->obj)) { - 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['type'] === 'Note' && $s['attach']) { + $s['body'] .= self::bb_attach($s['attach'], $s['body']); } - } - if ($act->obj['closed']) { - $s['comments_closed'] = datetime_convert('UTC','UTC', $act->obj['closed']); - } + if ($act->obj['type'] === 'Question' && in_array($act->type, ['Create', 'Update'])) { + if (array_key_exists('endTime', $act->obj)) { + $s['comments_closed'] = datetime_convert('UTC', 'UTC', $act->obj['endTime']); + } + } + } + if (array_key_exists('closed', $act->obj)) { + $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 // standard content region - if(! $response_activity) { - if($act->obj['type'] === 'Video') { + if (!$response_activity) { + if ($act->obj['type'] === 'Video') { $vtypes = [ 'video/mp4', @@ -2273,27 +2377,27 @@ class Activity { $mps = []; $ptr = null; - if(array_key_exists('url',$act->obj)) { - if(is_array($act->obj['url'])) { - if(array_key_exists(0,$act->obj['url'])) { + if (array_key_exists('url', $act->obj)) { + if (is_array($act->obj['url'])) { + if (array_key_exists(0, $act->obj['url'])) { $ptr = $act->obj['url']; } else { - $ptr = [ $act->obj['url'] ]; + $ptr = [$act->obj['url']]; } - foreach($ptr as $vurl) { + foreach ($ptr as $vurl) { // peertube uses the non-standard element name 'mimeType' here - if(array_key_exists('mimeType',$vurl)) { - if(in_array($vurl['mimeType'], $vtypes)) { - if(! array_key_exists('width',$vurl)) { + if (array_key_exists('mimeType', $vurl)) { + if (in_array($vurl['mimeType'], $vtypes)) { + if (!array_key_exists('width', $vurl)) { $vurl['width'] = 0; } $mps[] = $vurl; } } - elseif(array_key_exists('mediaType',$vurl)) { - if(in_array($vurl['mediaType'], $vtypes)) { - if(! array_key_exists('width',$vurl)) { + elseif (array_key_exists('mediaType', $vurl)) { + if (in_array($vurl['mediaType'], $vtypes)) { + if (!array_key_exists('width', $vurl)) { $vurl['width'] = 0; } $mps[] = $vurl; @@ -2301,22 +2405,22 @@ class Activity { } } } - if($mps) { - usort($mps,[ __CLASS__, 'vid_sort' ]); - foreach($mps as $m) { - if(intval($m['width']) < 500 && self::media_not_in_body($m['href'],$s['body'])) { + if ($mps) { + usort($mps, [__CLASS__, 'vid_sort']); + foreach ($mps as $m) { + if (intval($m['width']) < 500 && self::media_not_in_body($m['href'], $s['body'])) { $s['body'] .= "\n\n" . '[video]' . $m['href'] . '[/video]'; break; } } } - elseif(is_string($act->obj['url']) && self::media_not_in_body($act->obj['url'],$s['body'])) { + elseif (is_string($act->obj['url']) && self::media_not_in_body($act->obj['url'], $s['body'])) { $s['body'] .= "\n\n" . '[video]' . $act->obj['url'] . '[/video]'; } } } - if($act->obj['type'] === 'Audio') { + if ($act->obj['type'] === 'Audio') { $atypes = [ 'audio/mpeg', @@ -2326,50 +2430,50 @@ class Activity { $ptr = null; - if(array_key_exists('url',$act->obj)) { - if(is_array($act->obj['url'])) { - if(array_key_exists(0,$act->obj['url'])) { + if (array_key_exists('url', $act->obj)) { + if (is_array($act->obj['url'])) { + if (array_key_exists(0, $act->obj['url'])) { $ptr = $act->obj['url']; } else { - $ptr = [ $act->obj['url'] ]; + $ptr = [$act->obj['url']]; } - foreach($ptr as $vurl) { - if(in_array($vurl['mediaType'], $atypes) && self::media_not_in_body($vurl['href'],$s['body'])) { + foreach ($ptr as $vurl) { + if (in_array($vurl['mediaType'], $atypes) && self::media_not_in_body($vurl['href'], $s['body'])) { $s['body'] .= "\n\n" . '[audio]' . $vurl['href'] . '[/audio]'; break; } } } - elseif(is_string($act->obj['url']) && self::media_not_in_body($act->obj['url'],$s['body'])) { + elseif (is_string($act->obj['url']) && self::media_not_in_body($act->obj['url'], $s['body'])) { $s['body'] .= "\n\n" . '[audio]' . $act->obj['url'] . '[/audio]'; } } } - if($act->obj['type'] === 'Image') { + if ($act->obj['type'] === 'Image' && strpos($s['body'],'zrl=') === false) { $ptr = null; - if(array_key_exists('url',$act->obj)) { - if(is_array($act->obj['url'])) { - if(array_key_exists(0,$act->obj['url'])) { + if (array_key_exists('url', $act->obj)) { + if (is_array($act->obj['url'])) { + if (array_key_exists(0, $act->obj['url'])) { $ptr = $act->obj['url']; } else { - $ptr = [ $act->obj['url'] ]; + $ptr = [$act->obj['url']]; } - foreach($ptr as $vurl) { - if(strpos($s['body'],$vurl['href']) === false) { - $bb_imgs .= '[zmg]' . $vurl['href'] . '[/zmg]' . "\n\n"; + foreach ($ptr as $vurl) { + if (strpos($s['body'], $vurl['href']) === false) { + $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) { + elseif (is_string($act->obj['url'])) { + if (strpos($s['body'], $act->obj['url']) === false) { $s['body'] .= '[zmg]' . $act->obj['url'] . '[/zmg]' . "\n\n" . $s['body']; } } @@ -2377,36 +2481,36 @@ class Activity { } - if($act->obj['type'] === 'Page' && ! $s['body']) { + if ($act->obj['type'] === 'Page' && !$s['body']) { $ptr = null; $purl = EMPTY_STR; - if(array_key_exists('url',$act->obj)) { - if(is_array($act->obj['url'])) { - if(array_key_exists(0,$act->obj['url'])) { + if (array_key_exists('url', $act->obj)) { + if (is_array($act->obj['url'])) { + if (array_key_exists(0, $act->obj['url'])) { $ptr = $act->obj['url']; } else { - $ptr = [ $act->obj['url'] ]; + $ptr = [$act->obj['url']]; } - foreach($ptr as $vurl) { - if(array_key_exists('mediaType',$vurl) && $vurl['mediaType'] === 'text/html') { + foreach ($ptr as $vurl) { + if (array_key_exists('mediaType', $vurl) && $vurl['mediaType'] === 'text/html') { $purl = $vurl['href']; break; } - elseif(array_key_exists('mimeType',$vurl) && $vurl['mimeType'] === 'text/html') { + elseif (array_key_exists('mimeType', $vurl) && $vurl['mimeType'] === 'text/html') { $purl = $vurl['href']; break; } } } - elseif(is_string($act->obj['url'])) { + elseif (is_string($act->obj['url'])) { $purl = $act->obj['url']; } - if($purl) { + if ($purl) { $li = z_fetch_url(z_root() . '/linkinfo?binurl=' . bin2hex($purl)); - if($li['success'] && $li['body']) { + if ($li['success'] && $li['body']) { $s['body'] .= "\n" . $li['body']; } else { @@ -2418,32 +2522,31 @@ class Activity { } - - if(in_array($act->obj['type'],[ 'Note','Article','Page' ])) { + if (in_array($act->obj['type'], ['Note', 'Article', 'Page'])) { $ptr = null; - if(array_key_exists('url',$act->obj)) { - if(is_array($act->obj['url'])) { - if(array_key_exists(0,$act->obj['url'])) { + if (array_key_exists('url', $act->obj)) { + if (is_array($act->obj['url'])) { + if (array_key_exists(0, $act->obj['url'])) { $ptr = $act->obj['url']; } else { - $ptr = [ $act->obj['url'] ]; + $ptr = [$act->obj['url']]; } - foreach($ptr as $vurl) { - if(array_key_exists('mediaType',$vurl) && $vurl['mediaType'] === 'text/html') { + foreach ($ptr as $vurl) { + if (array_key_exists('mediaType', $vurl) && $vurl['mediaType'] === 'text/html') { $s['plink'] = $vurl['href']; break; } } } - elseif(is_string($act->obj['url'])) { + elseif (is_string($act->obj['url'])) { $s['plink'] = $act->obj['url']; } } } - if(! $s['plink']) { + if (!$s['plink']) { $s['plink'] = $s['mid']; } @@ -2456,24 +2559,24 @@ class Activity { } if (is_array($act->obj)) { - if (array_key_exists('directMessage',$act->obj) && intval($act->obj['directMessage'])) { + if (array_key_exists('directMessage', $act->obj) && intval($act->obj['directMessage'])) { $s['item_private'] = 2; } } - set_iconfig($s,'activitypub','recips',$act->raw_recips); + set_iconfig($s, 'activitypub', 'recips', $act->raw_recips); $parent = (($s['parent_mid'] && $s['parent_mid'] === $s['mid']) ? true : false); - if($parent) { - set_iconfig($s,'activitypub','rawmsg',$act->raw,1); + if ($parent) { + set_iconfig($s, 'activitypub', 'rawmsg', $act->raw, 1); } $hookinfo = [ 'act' => $act, - 's' => $s + 's' => $s ]; - call_hooks('decode_note',$hookinfo); + call_hooks('decode_note', $hookinfo); $s = $hookinfo['s']; @@ -2481,51 +2584,176 @@ class Activity { } - static function store($channel,$observer_hash,$act,$item,$fetch_parents = true) { - + static function store($channel, $observer_hash, $act, $item, $fetch_parents = true, $force = false) { $is_sys_channel = is_sys_channel($channel['channel_id']); + $is_child_node = false; - // Mastodon only allows visibility in public timelines if the public inbox is listed in the 'to' field. - // They are hidden in the public timeline if the public inbox is listed in the 'cc' field. - // This is not part of the activitypub protocol - we might change this to show all public posts in pubstream at some point. + // TODO: not implemented + // Pleroma scrobbles can be really noisy and contain lots of duplicate activities. Disable them by default. + /*if (($act->type === 'Listen') && ($is_sys_channel || get_pconfig($channel['channel_id'], 'system', 'allow_scrobbles', false))) { + return; + }*/ - $pubstream = ((is_array($act->obj) && array_key_exists('to', $act->obj) && in_array(ACTIVITY_PUBLIC_INBOX, $act->obj['to'])) ? true : false); - $is_parent = (($item['parent_mid'] && $item['parent_mid'] === $item['mid']) ? true : false); + // TODO: this his handled in pubcrawl atm. + // very unpleasant and imperfect way of determining a Mastodon DM + /*if ($act->raw_recips && array_key_exists('to',$act->raw_recips) && is_array($act->raw_recips['to']) && count($act->raw_recips['to']) === 1 && $act->raw_recips['to'][0] === channel_url($channel) && ! $act->raw_recips['cc']) { + $item['item_private'] = 2; + }*/ - if($is_parent && (! perm_is_allowed($channel['channel_id'],$observer_hash,'send_stream') && ! ($is_sys_channel && $pubstream))) { - logger('no permission'); - return; + if ($item['parent_mid'] && $item['parent_mid'] !== $item['mid']) { + $is_child_node = true; } - if(is_array($act->obj)) { - $content = self::get_content($act->obj); + $allowed = false; + + // TODO: not implemented + // $permit_mentions = intval(PConfig::Get($channel['channel_id'], 'system','permit_all_mentions') && i_am_mentioned($channel,$item)); + + if ($is_child_node) { + + $p = q("select * from item where mid = '%s' and uid = %d and item_wall = 1", + dbesc($item['parent_mid']), + intval($channel['channel_id']) + ); + if ($p) { + // set the owner to the owner of the parent + $item['owner_xchan'] = $p[0]['owner_xchan']; + + // check permissions against the author, not the sender + $allowed = perm_is_allowed($channel['channel_id'], $item['author_xchan'], 'post_comments'); + if ((!$allowed)/* && $permit_mentions*/) { + if ($p[0]['owner_xchan'] === $channel['channel_hash']) { + $allowed = false; + } + else { + $allowed = true; + } + } + + // TODO: not implemented + /*if (absolutely_no_comments($p[0])) { + $allowed = false; + }*/ + + if (!$allowed) { + logger('rejected comment from ' . $item['author_xchan'] . ' for ' . $channel['channel_address']); + logger('rejected: ' . print_r($item, true), LOGGER_DATA); + + // TODO: not implemented + // let the sender know we received their comment but we don't permit spam here. + // self::send_rejection_activity($channel,$item['author_xchan'],$item); + return; + } + + // TODO: not implemented + /*if (perm_is_allowed($channel['channel_id'],$item['author_xchan'],'moderated')) { + $item['item_blocked'] = ITEM_MODERATED; + }*/ + } + else { + + $allowed = true; + // reject public stream comments that weren't sent by the conversation owner + if ($is_sys_channel && $item['owner_xchan'] !== $observer_hash && !$fetch_parents) { + $allowed = false; + } + } + + if ($p && $p[0]['obj_type'] === 'Question') { + if ($item['obj_type'] === 'Note' && $item['title'] && (!$item['content'])) { + $item['obj_type'] = 'Answer'; + } + } } - if(! $content) { - logger('no content'); + else { + + // The $item['item_fetched'] flag is set in fetch_and_store_parents(). + // In this case we should check against author permissions because sender is not owner. + if (perm_is_allowed($channel['channel_id'], (($item['item_fetched']) ? $item['author_xchan'] : $observer_hash), 'send_stream') || $is_sys_channel) { + $allowed = true; + } + // TODO: not implemented + /*if ($permit_mentions) { + $allowed = true; + }*/ + } + + if (tgroup_check($channel['channel_id'], $item) && (!$is_child_node)) { + // for forum deliveries, make sure we keep a copy of the signed original + set_iconfig($item, 'activitypub', 'rawmsg', $act->raw, 1); + $allowed = true; + } + + if (intval($item['item_private']) === 2) { + if (!perm_is_allowed($channel['channel_id'], $observer_hash, 'post_mail')) { + $allowed = false; + } + } + + if ($is_sys_channel) { + + /* TODO: not implemented + if (! check_pubstream_channelallowed($observer_hash)) { + $allowed = false; + } + + // don't allow pubstream posts if the sender even has a clone on a pubstream denied site + + $h = q("select hubloc_url from hubloc where hubloc_hash = '%s'", + dbesc($observer_hash) + ); + if ($h) { + foreach ($h as $hub) { + if (! check_pubstream_siteallowed($hub['hubloc_url'])) { + $allowed = false; + break; + } + } + } + */ + + if (intval($item['item_private'])) { + $allowed = false; + } + } + + // TODO: not implemented + /*$blocked = LibBlock::fetch($channel['channel_id'],BLOCKTYPE_SERVER); + if ($blocked) { + foreach($blocked as $b) { + if (strpos($observer_hash,$b['block_entity']) !== false) { + $allowed = false; + } + } + }*/ + + if (!$allowed && !$force) { + logger('no permission'); return; } $item['aid'] = $channel['channel_account_id']; $item['uid'] = $channel['channel_id']; - // Make sure we use the zot6 identity where applicable + // Some authors may be zot6 authors in which case we want to store their nomadic identity + // instead of their ActivityPub identity $item['author_xchan'] = self::find_best_identity($item['author_xchan']); $item['owner_xchan'] = self::find_best_identity($item['owner_xchan']); - if(!$item['author_xchan']) { + if (!$item['author_xchan']) { logger('No author: ' . print_r($act, true)); } - if(!$item['owner_xchan']) { + if (!$item['owner_xchan']) { logger('No owner: ' . print_r($act, true)); } - if(!$item['author_xchan'] || !$item['owner_xchan']) + 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'))) { + if ($channel['channel_system']) { + if (!MessageFilter::evaluate($item, get_config('system', 'pubstream_incl'), get_config('system', 'pubstream_excl'))) { logger('post is filtered'); return; } @@ -2536,81 +2764,90 @@ class Activity { intval($channel['channel_id']) ); - if($abook) { - if(! post_is_importable($item,$abook[0])) { + if ($abook) { + if (!post_is_importable($item, $abook[0])) { logger('post is filtered'); return; } } - - if($act->obj['conversation']) { - set_iconfig($item,'ostatus','conversation',$act->obj['conversation'],1); + if (array_key_exists('conversation', $act->obj)) { + set_iconfig($item, 'ostatus', 'conversation', $act->obj['conversation'], 1); } // This isn't perfect but the best we can do for now. + $item['comment_policy'] = ((isset($act->data['commentPolicy'])) ? $act->data['commentPolicy'] : 'authenticated'); + + set_iconfig($item, 'activitypub', 'recips', $act->raw_recips); - $item['comment_policy'] = 'authenticated'; + // TODO: inheritPrivacy should probably be set in encode activity. Zap does not do so yet - check what this is about + if (!(isset($act->data['inheritPrivacy']) && $act->data['inheritPrivacy'])) { + if ($item['item_private']) { + $item['item_restrict'] = $item['item_restrict'] & 1; + if ($is_child_node) { + $item['allow_cid'] = '<' . $channel['channel_hash'] . '>'; + $item['allow_gid'] = $item['deny_cid'] = $item['deny_gid'] = ''; + } + logger('restricted'); + } + } - set_iconfig($item,'activitypub','recips',$act->raw_recips); + if (intval($act->sigok)) { + $item['item_verified'] = 1; + } - if(! $is_parent) { - $p = q("select parent_mid, id, obj_type from item where mid = '%s' and uid = %d limit 1", + $parent = null; + + if ($is_child_node) { + + $parent = q("select * from item where mid = '%s' and uid = %d limit 1", dbesc($item['parent_mid']), intval($item['uid']) ); - if(! $p) { - $a = (($fetch_parents) ? self::fetch_and_store_parents($channel,$act,$item) : false); - if($a) { - $p = q("select parent_mid from item where mid = '%s' and uid = %d limit 1", - dbesc($item['parent_mid']), - intval($item['uid']) - ); - } - else { - logger('could not fetch parents'); + if (!$parent) { + if (!plugin_is_installed('pubcrawl')) { return; - - // @TODO we maybe could accept these is we formatted the body correctly with share_bb() - // or at least provided a link to the object - // if(in_array($act->type,[ 'Like','Dislike' ])) { - // return; - // } - - // @TODO do we actually want that? - // if no parent was fetched, turn into a top-level post - - // turn into a top level post - // $s['parent_mid'] = $s['mid']; - // $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'; + else { + $fetch = false; + // TODO: debug + // if (perm_is_allowed($channel['channel_id'],$observer_hash,'send_stream') && (PConfig::Get($channel['channel_id'],'system','hyperdrive',true) || $act->type === 'Announce')) { + if (perm_is_allowed($channel['channel_id'], $observer_hash, 'send_stream') || $is_sys_channel) { + $fetch = (($fetch_parents) ? self::fetch_and_store_parents($channel, $observer_hash, $item, $force) : false); + } + if ($fetch) { + $parent = q("select * from item where mid = '%s' and uid = %d limit 1", + dbesc($item['parent_mid']), + intval($item['uid']) + ); + } + else { + logger('no parent'); + return; + } } } - - if($p[0]['parent_mid'] !== $item['parent_mid']) { + if ($parent[0]['parent_mid'] !== $item['parent_mid']) { $item['thr_parent'] = $item['parent_mid']; } else { - $item['thr_parent'] = $p[0]['parent_mid']; + $item['thr_parent'] = $parent[0]['parent_mid']; } - $item['parent_mid'] = $p[0]['parent_mid']; + $item['parent_mid'] = $parent[0]['parent_mid']; } + // TODO: not implemented + // self::rewrite_mentions($item); + $r = q("select id, created, edited from item where mid = '%s' and uid = %d limit 1", dbesc($item['mid']), intval($item['uid']) ); - if($r) { - if($item['edited'] > $r[0]['edited']) { + if ($r) { + if ($item['edited'] > $r[0]['edited']) { $item['id'] = $r[0]['id']; - $x = item_store_update($item); + $x = item_store_update($item); } else { return; @@ -2620,99 +2857,122 @@ class Activity { $x = item_store($item); } - if(is_array($x) && $x['item_id']) { - if($is_parent) { - if($item['owner_xchan'] === $channel['channel_hash']) { + if ($fetch_parents && $parent && !intval($parent[0]['item_private'])) { + logger('topfetch', LOGGER_DEBUG); + // if the thread owner is a connnection, we will already receive any additional comments to their posts + // but if they are not we can try to fetch others in the background + $x = q("SELECT abook.*, xchan.* FROM abook left join xchan on abook_xchan = xchan_hash + WHERE abook_channel = %d and abook_xchan = '%s' LIMIT 1", + intval($channel['channel_id']), + dbesc($parent[0]['owner_xchan']) + ); + if (!$x) { + // determine if the top-level post provides a replies collection + if ($parent[0]['obj']) { + $parent[0]['obj'] = json_decode($parent[0]['obj'], true); + } + logger('topfetch: ' . print_r($parent[0], true), LOGGER_ALL); + $id = ((array_path_exists('obj/replies/id', $parent[0])) ? $parent[0]['obj']['replies']['id'] : false); + if (!$id) { + $id = ((array_path_exists('obj/replies', $parent[0]) && is_string($parent[0]['obj']['replies'])) ? $parent[0]['obj']['replies'] : false); + } + if ($id) { + Master::Summon(['Convo', $id, $channel['channel_id'], $observer_hash]); + } + } + } + + if (is_array($x) && $x['item_id']) { + if ($is_child_node) { + if ($item['owner_xchan'] === $channel['channel_hash']) { // We are the owner of this conversation, so send all received comments back downstream - Master::Summon(array('Notifier','comment-import',$x['item_id'])); + Master::Summon(['Notifier', 'comment-import', $x['item_id']]); } $r = q("select * from item where id = %d limit 1", intval($x['item_id']) ); - if($r) { - send_status_notifications($x['item_id'],$r[0]); + if ($r) { + send_status_notifications($x['item_id'], $r[0]); } } - sync_an_item($channel['channel_id'],$x['item_id']); + sync_an_item($channel['channel_id'], $x['item_id']); } } - static public function fetch_and_store_parents($channel,$act,$item) { - + static public function fetch_and_store_parents($channel, $observer_hash, $item, $force = false) { logger('fetching parents'); $p = []; - $current_act = $act; $current_item = $item; - while($current_item['parent_mid'] !== $current_item['mid']) { + while ($current_item['parent_mid'] !== $current_item['mid']) { $n = self::fetch($current_item['parent_mid'], $channel); - if(! $n) { - break; - } - $a = new ActivityStreams($n); - - //logger($a->debug()); - if(! $a->is_valid()) { + if (!$n) { break; } - if (is_array($a->actor) && array_key_exists('id',$a->actor)) { - self::actor_store($a->actor['id'],$a->actor); + $a = new ActivityStreams($n); + if ($a->type === 'Announce' && is_array($a->obj) + && array_key_exists('object', $a->obj) && array_key_exists('actor', $a->obj)) { + // This is a relayed/forwarded Activity (as opposed to a shared/boosted object) + // Reparse the encapsulated Activity and use that instead + logger('relayed activity', LOGGER_DEBUG); + $a = new ActivityStreams($a->obj); } - $replies = null; - if(isset($a->obj['replies']['first']['items'])) { - $replies = $a->obj['replies']['first']['items']; - // we already have this one - array_diff($replies, [$current_item['mid']]); + logger($a->debug(), LOGGER_DATA); + + if (!$a->is_valid()) { + logger('not a valid activity'); + break; } - $item = null; - - switch($a->type) { - case 'Create': - case 'Update': - //case 'Like': - //case 'Dislike': - case 'Announce': - $item = self::decode_note($a); - break; - default: - break; + $item = Activity::decode_note($a); + if (!$item) { + break; } $hookinfo = [ - 'a' => $a, + 'a' => $a, 'item' => $item ]; - call_hooks('fetch_and_store',$hookinfo); + call_hooks('fetch_and_store', $hookinfo); $item = $hookinfo['item']; - if($item) { + if ($item) { + $item['item_fetched'] = 1; - array_unshift($p,[ $a, $item, $replies]); - - if($item['parent_mid'] === $item['mid'] || count($p) > 20) { + if (intval($channel['channel_system']) && intval($item['item_private'])) { + $p = []; break; } + if (count($p) > 100) { + $p = []; + break; + } + + array_unshift($p, [$a, $item]); + + if ($item['parent_mid'] === $item['mid']) { + break; + } } - $current_act = $a; + $current_item = $item; } - if($p) { - foreach($p as $pv) { - self::store($channel,$pv[0]->actor['id'],$pv[0],$pv[1],false); - if($pv[2]) - self::fetch_and_store_replies($channel, $pv[2]); + if ($p) { + foreach ($p as $pv) { + if ($pv[0]->is_valid()) { + Activity::store($channel, $observer_hash, $pv[0], $pv[1], false, $force); + } } return true; } @@ -2720,29 +2980,110 @@ class Activity { return false; } + /* + static public function fetch_and_store_parents($channel, $item) { + + logger('fetching parents'); + + $p = []; + + $current_item = $item; + + while ($current_item['parent_mid'] !== $current_item['mid']) { + $n = self::fetch($current_item['parent_mid'], $channel); + if (!$n) { + break; + } + $a = new ActivityStreams($n); + + //logger($a->debug()); + + if (!$a->is_valid()) { + break; + } + + if (is_array($a->actor) && array_key_exists('id', $a->actor)) { + self::actor_store($a->actor['id'], $a->actor); + } + + $replies = null; + if (isset($a->obj['replies']['first']['items'])) { + $replies = $a->obj['replies']['first']['items']; + // we already have this one + array_diff($replies, [$current_item['mid']]); + } + + $item = null; + + switch ($a->type) { + case 'Create': + case 'Update': + //case 'Like': + //case 'Dislike': + case 'Announce': + $item = self::decode_note($a); + break; + default: + break; + + } + + $hookinfo = [ + 'a' => $a, + 'item' => $item + ]; + + call_hooks('fetch_and_store', $hookinfo); + + $item = $hookinfo['item']; + + if ($item) { + + array_unshift($p, [$a, $item, $replies]); + + if ($item['parent_mid'] === $item['mid'] || count($p) > 20) { + break; + } + + } + $current_item = $item; + } + + if ($p) { + foreach ($p as $pv) { + self::store($channel, $pv[0]->actor['id'], $pv[0], $pv[1], false); + if ($pv[2]) + self::fetch_and_store_replies($channel, $pv[2]); + } + return true; + } + + return false; + } + */ static public function fetch_and_store_replies($channel, $arr) { logger('fetching replies'); - logger(print_r($arr,true)); + logger(print_r($arr, true)); $p = []; - foreach($arr as $url) { + foreach ($arr as $url) { $n = self::fetch($url, $channel); - if(! $n) { + if (!$n) { break; } $a = new ActivityStreams($n); - if(! $a->is_valid()) { + if (!$a->is_valid()) { break; } $item = null; - switch($a->type) { + switch ($a->type) { case 'Create': case 'Update': case 'Like': @@ -2755,62 +3096,56 @@ class Activity { } $hookinfo = [ - 'a' => $a, + 'a' => $a, 'item' => $item ]; - call_hooks('fetch_and_store',$hookinfo); + call_hooks('fetch_and_store', $hookinfo); $item = $hookinfo['item']; - if($item) { - array_unshift($p,[ $a, $item ]); + if ($item) { + array_unshift($p, [$a, $item]); } } - if($p) { - foreach($p as $pv) { - self::store($channel,$pv[0]->actor['id'],$pv[0],$pv[1],false); + if ($p) { + foreach ($p as $pv) { + self::store($channel, $pv[0]->actor['id'], $pv[0], $pv[1], false); } } } - static function announce_note($channel,$observer_hash,$act) { + static function announce_note($channel, $observer_hash, $act) { $s = []; - $is_sys_channel = is_sys_channel($channel['channel_id']); - // Mastodon only allows visibility in public timelines if the public inbox is listed in the 'to' field. - // They are hidden in the public timeline if the public inbox is listed in the 'cc' field. - // This is not part of the activitypub protocol - we might change this to show all public posts in pubstream at some point. - $pubstream = ((is_array($act->obj) && array_key_exists('to', $act->obj) && in_array(ACTIVITY_PUBLIC_INBOX, $act->obj['to'])) ? true : false); - - if(! perm_is_allowed($channel['channel_id'],$observer_hash,'send_stream') && ! ($is_sys_channel && $pubstream)) { + if (!perm_is_allowed($channel['channel_id'], $observer_hash, 'send_stream') && !$is_sys_channel) { logger('no permission'); return; } $content = self::get_content($act->obj); - if(! $content) { + if (!$content) { logger('no content'); return; } $s['owner_xchan'] = $s['author_xchan'] = $observer_hash; - $s['aid'] = $channel['channel_account_id']; - $s['uid'] = $channel['channel_id']; - $s['mid'] = urldecode($act->obj['id']); + $s['aid'] = $channel['channel_account_id']; + $s['uid'] = $channel['channel_id']; + $s['mid'] = urldecode($act->obj['id']); $s['plink'] = urldecode($act->obj['id']); - if(! $s['created']) + if (!$s['created']) $s['created'] = datetime_convert(); - if(! $s['edited']) + if (!$s['edited']) $s['edited'] = $s['created']; @@ -2820,8 +3155,8 @@ class Activity { $s['obj_type'] = ACTIVITY_OBJ_NOTE; $s['app'] = t('ActivityPub'); - if($channel['channel_system']) { - if(! \Zotlabs\Lib\MessageFilter::evaluate($s,get_config('system','pubstream_incl'),get_config('system','pubstream_excl'))) { + if ($channel['channel_system']) { + if (!MessageFilter::evaluate($s, get_config('system', 'pubstream_incl'), get_config('system', 'pubstream_excl'))) { logger('post is filtered'); return; } @@ -2832,61 +3167,61 @@ class Activity { intval($channel['channel_id']) ); - if($abook) { - if(! post_is_importable($s,$abook[0])) { + if ($abook) { + if (!post_is_importable($s, $abook[0])) { logger('post is filtered'); return; } } - if($act->obj['conversation']) { - set_iconfig($s,'ostatus','conversation',$act->obj['conversation'],1); + if ($act->obj['conversation']) { + set_iconfig($s, 'ostatus', 'conversation', $act->obj['conversation'], 1); } $a = self::decode_taxonomy($act->obj); - if($a) { + if ($a) { $s['term'] = $a; } $a = self::decode_attachment($act->obj); - if($a) { + if ($a) { $s['attach'] = $a; } - $body = "[share author='" . urlencode($act->sharee['name']) . - "' profile='" . $act->sharee['url'] . - "' avatar='" . $act->sharee['photo_s'] . - "' link='" . ((is_array($act->obj['url'])) ? $act->obj['url']['href'] : $act->obj['url']) . - "' auth='" . ((is_matrix_url($act->obj['url'])) ? 'true' : 'false' ) . - "' posted='" . $act->obj['published'] . - "' message_id='" . $act->obj['id'] . - "']"; + $body = "[share author='" . urlencode($act->sharee['name']) . + "' profile='" . $act->sharee['url'] . + "' avatar='" . $act->sharee['photo_s'] . + "' link='" . ((is_array($act->obj['url'])) ? $act->obj['url']['href'] : $act->obj['url']) . + "' auth='" . ((is_matrix_url($act->obj['url'])) ? 'true' : 'false') . + "' posted='" . $act->obj['published'] . + "' message_id='" . $act->obj['id'] . + "']"; - if($content['name']) - $body .= self::bb_content($content,'name') . "\r\n"; + if ($content['name']) + $body .= self::bb_content($content, 'name') . "\r\n"; - $body .= self::bb_content($content,'content'); + $body .= self::bb_content($content, 'content'); - if($act->obj['type'] === 'Note' && $s['attach']) { - $body .= self::bb_attach($s['attach'],$body); + if ($act->obj['type'] === 'Note' && $s['attach']) { + $body .= self::bb_attach($s['attach'], $body); } $body .= "[/share]"; - $s['title'] = self::bb_content($content,'name'); - $s['body'] = $body; + $s['title'] = self::bb_content($content, 'name'); + $s['body'] = $body; - if($act->recips && (! in_array(ACTIVITY_PUBLIC_INBOX,$act->recips))) + if ($act->recips && (!in_array(ACTIVITY_PUBLIC_INBOX, $act->recips))) $s['item_private'] = 1; - set_iconfig($s,'activitypub','recips',$act->raw_recips); + set_iconfig($s, 'activitypub', 'recips', $act->raw_recips); $r = q("select created, edited from item where mid = '%s' and uid = %d limit 1", dbesc($s['mid']), intval($s['uid']) ); - if($r) { - if($s['edited'] > $r[0]['edited']) { + if ($r) { + if ($s['edited'] > $r[0]['edited']) { $x = item_store_update($s); } else { @@ -2897,35 +3232,35 @@ class Activity { $x = item_store($s); } - if(is_array($x) && $x['item_id']) { - if($s['owner_xchan'] === $channel['channel_hash']) { + if (is_array($x) && $x['item_id']) { + if ($s['owner_xchan'] === $channel['channel_hash']) { // We are the owner of this conversation, so send all received comments back downstream - Master::Summon(array('Notifier','comment-import',$x['item_id'])); + Master::Summon(['Notifier', 'comment-import', $x['item_id']]); } $r = q("select * from item where id = %d limit 1", intval($x['item_id']) ); - if($r) { - send_status_notifications($x['item_id'],$r[0]); + if ($r) { + send_status_notifications($x['item_id'], $r[0]); } - sync_an_item($channel['channel_id'],$x['item_id']); + sync_an_item($channel['channel_id'], $x['item_id']); } } - static function like_note($channel,$observer_hash,$act) { + static function like_note($channel, $observer_hash, $act) { $s = []; $parent = $act->obj['id']; - - if($act->type === 'Like') + + if ($act->type === 'Like') $s['verb'] = ACTIVITY_LIKE; - if($act->type === 'Dislike') + if ($act->type === 'Dislike') $s['verb'] = ACTIVITY_DISLIKE; - if(! $parent) + if (!$parent) return; $r = q("select * from item where uid = %d and ( mid = '%s' or mid = '%s' ) limit 1", @@ -2934,7 +3269,7 @@ class Activity { dbesc(urldecode(basename($parent))) ); - if(! $r) { + if (!$r) { logger('parent not found.'); return; } @@ -2942,14 +3277,14 @@ class Activity { xchan_query($r); $parent_item = $r[0]; - if($parent_item['owner_xchan'] === $channel['channel_hash']) { - if(! perm_is_allowed($channel['channel_id'],$observer_hash,'post_comments')) { + if ($parent_item['owner_xchan'] === $channel['channel_hash']) { + if (!perm_is_allowed($channel['channel_id'], $observer_hash, 'post_comments')) { logger('no comment permission.'); return; } } - if($parent_item['mid'] === $parent_item['parent_mid']) { + if ($parent_item['mid'] === $parent_item['parent_mid']) { $s['parent_mid'] = $parent_item['mid']; } else { @@ -2957,31 +3292,29 @@ class Activity { $s['parent_mid'] = $parent_item['parent_mid']; } - $s['owner_xchan'] = $parent_item['owner_xchan']; + $s['owner_xchan'] = $parent_item['owner_xchan']; $s['author_xchan'] = $observer_hash; - + $s['aid'] = $channel['channel_account_id']; $s['uid'] = $channel['channel_id']; $s['mid'] = $act->id; - if(! $s['parent_mid']) + if (!$s['parent_mid']) $s['parent_mid'] = $s['mid']; - - $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 ); + $post_type = (($parent_item['resource_type'] === 'photo') ? t('photo') : t('post')); - $body = $parent_item['body']; + $links = [['rel' => 'alternate', 'type' => 'text/html', 'href' => $parent_item['plink']]]; + $objtype = (($parent_item['resource_type'] === 'photo') ? ACTIVITY_OBJ_PHOTO : ACTIVITY_OBJ_NOTE); $z = q("select * from xchan where xchan_hash = '%s' limit 1", dbesc($parent_item['author_xchan']) ); - if($z) - $item_author = $z[0]; + if ($z) + $item_author = $z[0]; - $object = json_encode(array( + $object = json_encode([ 'type' => $post_type, 'id' => $parent_item['mid'], 'parent' => (($parent_item['thr_parent']) ? $parent_item['thr_parent'] : $parent_item['parent_mid']), @@ -2990,77 +3323,75 @@ class Activity { 'content' => $parent_item['body'], 'created' => $parent_item['created'], 'edited' => $parent_item['edited'], - 'author' => array( + 'author' => [ 'name' => $item_author['xchan_name'], 'address' => $item_author['xchan_addr'], 'guid' => $item_author['xchan_guid'], 'guid_sig' => $item_author['xchan_guid_sig'], - 'link' => array( - array('rel' => 'alternate', 'type' => 'text/html', 'href' => $item_author['xchan_url']), - array('rel' => 'photo', 'type' => $item_author['xchan_photo_mimetype'], 'href' => $item_author['xchan_photo_m'])), - ), - ), JSON_UNESCAPED_SLASHES + 'link' => [ + ['rel' => 'alternate', 'type' => 'text/html', 'href' => $item_author['xchan_url']], + ['rel' => 'photo', 'type' => $item_author['xchan_photo_mimetype'], 'href' => $item_author['xchan_photo_m']]], + ], + ], JSON_UNESCAPED_SLASHES ); - if($act->type === 'Like') + if ($act->type === 'Like') $bodyverb = t('%1$s likes %2$s\'s %3$s'); - if($act->type === 'Dislike') + if ($act->type === 'Dislike') $bodyverb = t('%1$s doesn\'t like %2$s\'s %3$s'); - $ulink = '[url=' . $item_author['xchan_url'] . ']' . $item_author['xchan_name'] . '[/url]'; - $alink = '[url=' . $parent_item['author']['xchan_url'] . ']' . $parent_item['author']['xchan_name'] . '[/url]'; - $plink = '[url='. z_root() . '/display/' . urlencode($act->id) . ']' . $post_type . '[/url]'; - $s['body'] = sprintf( $bodyverb, $ulink, $alink, $plink ); + $ulink = '[url=' . $item_author['xchan_url'] . ']' . $item_author['xchan_name'] . '[/url]'; + $alink = '[url=' . $parent_item['author']['xchan_url'] . ']' . $parent_item['author']['xchan_name'] . '[/url]'; + $plink = '[url=' . z_root() . '/display/' . urlencode($act->id) . ']' . $post_type . '[/url]'; + $s['body'] = sprintf($bodyverb, $ulink, $alink, $plink); - $s['app'] = t('ActivityPub'); + $s['app'] = t('ActivityPub'); // set the route to that of the parent so downstream hubs won't reject it. - $s['route'] = $parent_item['route']; + $s['route'] = $parent_item['route']; $s['item_private'] = $parent_item['item_private']; - $s['obj_type'] = $objtype; - $s['obj'] = $object; + $s['obj_type'] = $objtype; + $s['obj'] = $object; - if($act->obj['conversation']) { - set_iconfig($s,'ostatus','conversation',$act->obj['conversation'],1); + if ($act->obj['conversation']) { + set_iconfig($s, 'ostatus', 'conversation', $act->obj['conversation'], 1); } - if($act->recips && (! in_array(ACTIVITY_PUBLIC_INBOX,$act->recips))) + if ($act->recips && (!in_array(ACTIVITY_PUBLIC_INBOX, $act->recips))) $s['item_private'] = 1; - set_iconfig($s,'activitypub','recips',$act->raw_recips); + set_iconfig($s, 'activitypub', 'recips', $act->raw_recips); $result = item_store($s); - if($result['success']) { + if ($result['success']) { // if the message isn't already being relayed, notify others - if(intval($parent_item['item_origin'])) - Master::Summon(array('Notifier','comment-import',$result['item_id'])); - sync_an_item($channel['channel_id'],$result['item_id']); + if (intval($parent_item['item_origin'])) + Master::Summon(['Notifier', 'comment-import', $result['item_id']]); + sync_an_item($channel['channel_id'], $result['item_id']); } return; } - - - static function bb_attach($attach,$body) { + static function bb_attach($attach, $body) { $ret = false; - foreach($attach as $a) { - if(strpos($a['type'],'image') !== false) { - if(self::media_not_in_body($a['href'],$body)) { + foreach ($attach as $a) { + if (array_key_exists('type',$a) && stripos($a['type'], 'image') !== false) { + if (self::media_not_in_body($a['href'], $body)) { $ret .= "\n\n" . '[img]' . $a['href'] . '[/img]'; } } - if(array_key_exists('type',$a) && strpos($a['type'], 'video') === 0) { - if(self::media_not_in_body($a['href'],$body)) { + if (array_key_exists('type', $a) && stripos($a['type'], 'video') !== false) { + if (self::media_not_in_body($a['href'], $body)) { $ret .= "\n\n" . '[video]' . $a['href'] . '[/video]'; } } - if(array_key_exists('type',$a) && strpos($a['type'], 'audio') === 0) { - if(self::media_not_in_body($a['href'],$body)) { + if (array_key_exists('type', $a) && stripos($a['type'], 'audio') !== false) { + if (self::media_not_in_body($a['href'], $body)) { $ret .= "\n\n" . '[audio]' . $a['href'] . '[/audio]'; } } @@ -3069,116 +3400,112 @@ class Activity { return $ret; } - // check for the existence of existing media link in body + static function media_not_in_body($s, $body) { - static function media_not_in_body($s,$body) { - - if((strpos($body,']' . $s . '[/img]') === false) && - (strpos($body,']' . $s . '[/zmg]') === false) && - (strpos($body,']' . $s . '[/video]') === false) && - (strpos($body,']' . $s . '[/audio]') === false)) { + if ((strpos($body, ']' . $s . '[/img]') === false) && + (strpos($body, ']' . $s . '[/zmg]') === false) && + (strpos($body, ']' . $s . '[/video]') === false) && + (strpos($body, ']' . $s . '[/audio]') === false)) { return true; } return false; } - - static function bb_content($content,$field) { + static function bb_content($content, $field) { require_once('include/html2bbcode.php'); require_once('include/event.php'); $ret = false; - if(is_array($content[$field])) { - foreach($content[$field] as $k => $v) { - $ret .= html2bbcode($v); - // save this for auto-translate or dynamic filtering - // $ret .= '[language=' . $k . ']' . html2bbcode($v) . '[/language]'; - } - } - else { - if($field === 'bbcode' && array_key_exists('bbcode',$content)) { - $ret = $content[$field]; + if (array_key_exists($field, $content)) { + if (is_array($content[$field])) { + foreach ($content[$field] as $k => $v) { + $ret .= html2bbcode($v); + // save this for auto-translate or dynamic filtering + // $ret .= '[language=' . $k . ']' . html2bbcode($v) . '[/language]'; + } } else { - $ret = html2bbcode($content[$field]); + if ($field === 'bbcode' && array_key_exists('bbcode', $content)) { + $ret = $content[$field]; + } + else { + $ret = html2bbcode($content[$field]); + } } } - if($field === 'content' && $content['event'] && (! strpos($ret,'[event'))) { + + if ($field === 'content' && array_key_exists('event', $content) && (!strpos($ret, '[event'))) { $ret .= format_event_bbcode($content['event']); } return $ret; } - static function get_content($act) { $content = []; - $event = null; + $event = null; - if ((! $act) || (! is_array($act))) { + if ((!$act) || (!is_array($act))) { return $content; } - if($act['type'] === 'Event') { - $adjust = false; - $event = []; - $event['event_hash'] = $act['id']; - if(array_key_exists('startTime',$act) && strpos($act['startTime'],-1,1) === 'Z') { - $adjust = true; - $event['adjust'] = 1; - $event['dtstart'] = datetime_convert('UTC','UTC',$event['startTime'] . (($adjust) ? '' : 'Z')); - } - if(array_key_exists('endTime',$act)) { - $event['dtend'] = datetime_convert('UTC','UTC',$event['endTime'] . (($adjust) ? '' : 'Z')); - } - else { - $event['nofinish'] = true; - } - } - - foreach ([ 'name', 'summary', 'content' ] as $a) { - if (($x = self::get_textfield($act,$a)) !== false) { + if ($act['type'] === 'Event') { + $adjust = false; + $event = []; + $event['event_hash'] = $act['id']; + if (array_key_exists('startTime', $act) && strpos($act['startTime'], -1, 1) === 'Z') { + $adjust = true; + $event['adjust'] = 1; + $event['dtstart'] = datetime_convert('UTC', 'UTC', $event['startTime'] . (($adjust) ? '' : 'Z')); + } + if (array_key_exists('endTime', $act)) { + $event['dtend'] = datetime_convert('UTC', 'UTC', $event['endTime'] . (($adjust) ? '' : 'Z')); + } + else { + $event['nofinish'] = true; + } + } + + foreach (['name', 'summary', 'content'] as $a) { + if (($x = self::get_textfield($act, $a)) !== false) { $content[$a] = $x; } } - if($event) { + if ($event) { $event['summary'] = $content['name']; - if(! $event['summary']) { - if($content['summary']) { + if (!$event['summary']) { + if ($content['summary']) { $event['summary'] = html2plain($content['summary']); } } $event['description'] = html2bbcode($content['content']); - if($event['summary'] && $event['dtstart']) { + if ($event['summary'] && $event['dtstart']) { $content['event'] = $event; } } - if (array_path_exists('source/mediaType',$act) && array_path_exists('source/content',$act)) { + 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']); } } - - return $content; } + static function get_textfield($act, $field) { - static function get_textfield($act,$field) { - $content = false; - if(array_key_exists($field,$act) && $act[$field]) + if (array_key_exists($field, $act) && $act[$field]) $content = purify_html($act[$field]); - elseif(array_key_exists($field . 'Map',$act) && $act[$field . 'Map']) { - foreach($act[$field . 'Map'] as $k => $v) { + elseif (array_key_exists($field . 'Map', $act) && $act[$field . 'Map']) { + foreach ($act[$field . 'Map'] as $k => $v) { $content[escape_tags($k)] = purify_html($v); } } @@ -3187,11 +3514,10 @@ class Activity { // 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) + 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 ); @@ -3200,8 +3526,8 @@ class Activity { } } - if (! $auth) { - if (array_key_exists('token',$_REQUEST) && $_REQUEST['token']) { + if (!$auth) { + if (array_key_exists('token', $_REQUEST) && $_REQUEST['token']) { $auth = $_REQUEST['token']; } } @@ -3211,8 +3537,8 @@ class Activity { 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", + 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', 'activitypub') AND hubloc_deleted = 0", dbesc($xchan) ); if ($r) { @@ -3226,4 +3552,67 @@ class Activity { } + static function get_cached_actor($id) { + $actor = XConfig::Get($id,'system', 'actor_record'); + + if ($actor) { + return $actor; + } + + // try other get_cached_actor providers (e.g. diaspora) + $hookdata = [ + 'id' => $id, + 'actor' => false + ]; + + call_hooks('get_cached_actor_provider', $hookdata); + + return $hookdata['actor']; + } + + static function get_actor_hublocs($url, $options = 'all') { + + $hublocs = false; + + switch ($options) { + case 'activitypub': + $hublocs = q("select * from hubloc left join xchan on hubloc_hash = xchan_hash where hubloc_hash = '%s' and hubloc_deleted = 0 ", + dbesc($url) + ); + break; + case 'zot6': + $hublocs = q("select * from hubloc left join xchan on hubloc_hash = xchan_hash where hubloc_id_url = '%s' and hubloc_deleted = 0 ", + dbesc($url) + ); + break; + case 'all': + default: + $hublocs = q("select * from hubloc left join xchan on hubloc_hash = xchan_hash where ( hubloc_id_url = '%s' OR hubloc_hash = '%s' ) and hubloc_deleted = 0 ", + dbesc($url), + dbesc($url) + ); + break; + } + + return $hublocs; + } + + static function get_actor_collections($url) { + $ret = []; + $actor_record = XConfig::Get($url,'system','actor_record'); + if (! $actor_record) { + return $ret; + } + + foreach ( [ 'inbox','outbox','followers','following' ] as $collection) { + if (isset($actor_record[$collection]) && $actor_record[$collection]) { + $ret[$collection] = $actor_record[$collection]; + } + } + if (array_path_exists('endpoints/sharedInbox',$actor_record) && $actor_record['endpoints']['sharedInbox']) { + $ret['sharedInbox'] = $actor_record['endpoints']['sharedInbox']; + } + + return $ret; + } } diff --git a/Zotlabs/Lib/ActivityStreams.php b/Zotlabs/Lib/ActivityStreams.php index a0ba52aa6..fa38c569e 100644 --- a/Zotlabs/Lib/ActivityStreams.php +++ b/Zotlabs/Lib/ActivityStreams.php @@ -7,25 +7,24 @@ namespace Zotlabs\Lib; * * Parses an ActivityStream JSON string. */ - class ActivityStreams { - public $raw = null; - public $data = null; - public $valid = false; - public $deleted = false; - public $id = ''; - public $parent_id = ''; - public $type = ''; - public $actor = null; - public $obj = null; - public $tgt = null; - public $origin = null; - public $owner = null; - public $signer = null; - public $ldsig = null; - public $sigok = false; - public $recips = null; + public $raw = null; + public $data = null; + public $valid = false; + public $deleted = false; + public $id = ''; + public $parent_id = ''; + public $type = ''; + public $actor = null; + public $obj = null; + public $tgt = null; + public $origin = null; + public $owner = null; + public $signer = null; + public $ldsig = null; + public $sigok = false; + public $recips = null; public $raw_recips = null; /** @@ -37,29 +36,29 @@ class ActivityStreams { */ function __construct($string) { - $this->raw = $string; + $this->raw = $string; - if(is_array($string)) { + if (is_array($string)) { $this->data = $string; } else { $this->data = json_decode($string, true); } - if($this->data) { + if ($this->data) { // verify and unpack JSalmon signature if present - - if(is_array($this->data) && array_key_exists('signed',$this->data)) { + + if (is_array($this->data) && array_key_exists('signed', $this->data)) { $ret = JSalmon::verify($this->data); $tmp = JSalmon::unpack($this->data['data']); - if($ret && $ret['success']) { - if($ret['signer']) { - $saved = json_encode($this->data,JSON_UNESCAPED_SLASHES); - $this->data = $tmp; - $this->data['signer'] = $ret['signer']; + if ($ret && $ret['success']) { + if ($ret['signer']) { + $saved = json_encode($this->data, JSON_UNESCAPED_SLASHES); + $this->data = $tmp; + $this->data['signer'] = $ret['signer']; $this->data['signed_data'] = $saved; - if($ret['hubloc']) { + if ($ret['hubloc']) { $this->data['hubloc'] = $ret['hubloc']; } } @@ -68,57 +67,57 @@ class ActivityStreams { $this->valid = true; - if(array_key_exists('type',$this->data) && array_key_exists('actor',$this->data) && array_key_exists('object',$this->data)) { - if($this->data['type'] === 'Delete' && $this->data['actor'] === $this->data['object']) { + if (array_key_exists('type', $this->data) && array_key_exists('actor', $this->data) && array_key_exists('object', $this->data)) { + if ($this->data['type'] === 'Delete' && $this->data['actor'] === $this->data['object']) { $this->deleted = $this->data['actor']; - $this->valid = false; + $this->valid = false; } } } - if($this->is_valid()) { + if ($this->is_valid()) { $this->id = $this->get_property_obj('id'); $this->type = $this->get_primary_type(); - $this->actor = $this->get_actor('actor','',''); + $this->actor = $this->get_actor('actor', '', ''); $this->obj = $this->get_compound_property('object'); $this->tgt = $this->get_compound_property('target'); $this->origin = $this->get_compound_property('origin'); $this->recips = $this->collect_recips(); $this->ldsig = $this->get_compound_property('signature'); - if($this->ldsig) { - $this->signer = $this->get_compound_property('creator',$this->ldsig); - if($this->signer && is_array($this->signer) && array_key_exists('publicKey',$this->signer) && is_array($this->signer['publicKey']) && $this->signer['publicKey']['publicKeyPem']) { - $this->sigok = LDSignatures::verify($this->data,$this->signer['publicKey']['publicKeyPem']); + if ($this->ldsig) { + $this->signer = $this->get_compound_property('creator', $this->ldsig); + if ($this->signer && is_array($this->signer) && array_key_exists('publicKey', $this->signer) && is_array($this->signer['publicKey']) && $this->signer['publicKey']['publicKeyPem']) { + $this->sigok = LDSignatures::verify($this->data, $this->signer['publicKey']['publicKeyPem']); } } - if(! $this->obj) { - $this->obj = $this->data; + if (!$this->obj) { + $this->obj = $this->data; $this->type = 'Create'; - if(! $this->actor) { - $this->actor = $this->get_actor('attributedTo',$this->obj); + if (!$this->actor) { + $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)) { + + 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']) - $this->tgt['actor'] = $this->get_actor('actor',$this->tgt); + 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']) + $this->tgt['actor'] = $this->get_actor('actor', $this->tgt); $this->parent_id = $this->get_property_obj('inReplyTo'); - if((! $this->parent_id) && is_array($this->obj)) { + if ((!$this->parent_id) && is_array($this->obj)) { $this->parent_id = $this->obj['inReplyTo']; } - if((! $this->parent_id) && is_array($this->obj)) { + if ((!$this->parent_id) && is_array($this->obj)) { $this->parent_id = $this->obj['id']; } } @@ -147,19 +146,19 @@ class ActivityStreams { function collect_recips($base = '', $namespace = '') { $x = []; - $fields = [ 'to', 'cc', 'bto', 'bcc', 'audience']; - foreach($fields as $f) { + $fields = ['to', 'cc', 'bto', 'bcc', 'audience']; + foreach ($fields as $f) { $y = $this->get_compound_property($f, $base, $namespace); - if($y) { - if (! is_array($this->raw_recips)) { + if ($y) { + if (!is_array($this->raw_recips)) { $this->raw_recips = []; } - if (! is_array($y)) { - $y = [ $y ]; + if (!is_array($y)) { + $y = [$y]; } $this->raw_recips[$f] = $y; - $x = array_merge($x, $y); + $x = array_merge($x, $y); } } // not yet ready for prime time @@ -167,21 +166,21 @@ class ActivityStreams { return $x; } - function expand($arr,$base = '',$namespace = '') { + function expand($arr, $base = '', $namespace = '') { $ret = []; // right now use a hardwired recursion depth of 5 - for($z = 0; $z < 5; $z ++) { - if(is_array($arr) && $arr) { - foreach($arr as $a) { - if(is_array($a)) { + for ($z = 0; $z < 5; $z++) { + if (is_array($arr) && $arr) { + foreach ($arr as $a) { + if (is_array($a)) { $ret[] = $a; } else { - $x = $this->get_compound_property($a,$base,$namespace); - if($x) { - $ret = array_merge($ret,$x); + $x = $this->get_compound_property($a, $base, $namespace); + if ($x) { + $ret = array_merge($ret, $x); } } } @@ -202,33 +201,33 @@ class ActivityStreams { */ function get_namespace($base, $namespace) { - if(! $namespace) + if (!$namespace) return ''; $key = null; - foreach( [ $this->data, $base ] as $b ) { - if(! $b) + foreach ([$this->data, $base] as $b) { + if (!$b) continue; - if(array_key_exists('@context', $b)) { - if(is_array($b['@context'])) { - foreach($b['@context'] as $ns) { - if(is_array($ns)) { - foreach($ns as $k => $v) { - if($namespace === $v) + if (array_key_exists('@context', $b)) { + if (is_array($b['@context'])) { + foreach ($b['@context'] as $ns) { + if (is_array($ns)) { + foreach ($ns as $k => $v) { + if ($namespace === $v) $key = $k; } } else { - if($namespace === $ns) { + if ($namespace === $ns) { $key = ''; } } } } else { - if($namespace === $b['@context']) { + if ($namespace === $b['@context']) { $key = ''; } } @@ -248,14 +247,14 @@ class ActivityStreams { */ function get_property_obj($property, $base = '', $namespace = '') { $prefix = $this->get_namespace($base, $namespace); - if($prefix === null) + if ($prefix === null) return null; - $base = (($base) ? $base : $this->data); + $base = (($base) ? $base : $this->data); $propname = (($prefix) ? $prefix . ':' : '') . $property; - if(! is_array($base)) { - btlogger('not an array: ' . print_r($base,true)); + if (!is_array($base)) { + btlogger('not an array: ' . print_r($base, true)); return null; } @@ -279,14 +278,14 @@ class ActivityStreams { } 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) { + if (!$s) { return false; } - return (in_array($s, [ 'Like', 'Dislike', 'Flag', 'Block', 'Announce', 'Accept', 'Reject', 'TentativeAccept', 'TentativeReject', 'emojiReaction', 'EmojiReaction', 'EmojiReact' ])); + return (in_array($s, ['Like', 'Dislike', 'Flag', 'Block', 'Accept', 'Reject', 'TentativeAccept', 'TentativeReject', 'emojiReaction', 'EmojiReaction', 'EmojiReact'])); } /** @@ -298,25 +297,17 @@ class ActivityStreams { * @return NULL|mixed */ - function get_actor($property,$base='',$namespace = '') { + function get_actor($property, $base = '', $namespace = '') { $x = $this->get_property_obj($property, $base, $namespace); - if($this->is_url($x)) { - - // SECURITY: If we have already stored the actor profile, re-generate it - // from cached data - don't refetch it from the network - - $r = q("select * from xchan left join hubloc on xchan_hash = hubloc_hash where hubloc_id_url = '%s' limit 1", - dbesc($x) - ); - if($r) { - $y = Activity::encode_person($r[0]); - $y['cached'] = true; + if ($this->is_url($x)) { + $y = Activity::get_cached_actor($x); + if ($y) { return $y; } } - $actor = $this->get_compound_property($property,$base,$namespace,true); - if(is_array($actor) && self::is_an_actor($actor['type'])) { - if(array_key_exists('id',$actor) && (! array_key_exists('inbox',$actor))) { + $actor = $this->get_compound_property($property, $base, $namespace, true); + if (is_array($actor) && self::is_an_actor($actor['type'])) { + if (array_key_exists('id', $actor) && (!array_key_exists('inbox', $actor))) { $actor = $this->fetch_property($actor['id']); } return $actor; @@ -336,7 +327,7 @@ class ActivityStreams { */ function get_compound_property($property, $base = '', $namespace = '', $first = false) { $x = $this->get_property_obj($property, $base, $namespace); - if($this->is_url($x)) { + if ($this->is_url($x)) { $y = $this->fetch_property($x); if (is_array($y)) { $x = $y; @@ -344,23 +335,23 @@ class ActivityStreams { } // verify and unpack JSalmon signature if present - - if(is_array($x) && array_key_exists('signed',$x)) { + + if (is_array($x) && array_key_exists('signed', $x)) { $ret = JSalmon::verify($x); $tmp = JSalmon::unpack($x['data']); - if($ret && $ret['success']) { - if($ret['signer']) { - $saved = json_encode($x,JSON_UNESCAPED_SLASHES); - $x = $tmp; - $x['signer'] = $ret['signer']; + if ($ret && $ret['success']) { + if ($ret['signer']) { + $saved = json_encode($x, JSON_UNESCAPED_SLASHES); + $x = $tmp; + $x['signer'] = $ret['signer']; $x['signed_data'] = $saved; - if($ret['hubloc']) { + if ($ret['hubloc']) { $x['hubloc'] = $ret['hubloc']; } } } } - if($first && is_array($x) && array_key_exists(0,$x)) { + if ($first && is_array($x) && array_key_exists(0, $x)) { return $x[0]; } @@ -374,7 +365,7 @@ class ActivityStreams { * @return boolean */ function is_url($url) { - if(($url) && (! is_array($url)) && (strpos($url, 'http') === 0)) { + if (($url) && (!is_array($url)) && (strpos($url, 'http') === 0)) { return true; } @@ -389,13 +380,13 @@ class ActivityStreams { * @return NULL|mixed */ function get_primary_type($base = '', $namespace = '') { - if(! $base) + if (!$base) $base = $this->data; $x = $this->get_property_obj('type', $base, $namespace); - if(is_array($x)) { - foreach($x as $y) { - if(strpos($y, ':') === false) { + if (is_array($x)) { + foreach ($x as $y) { + if (strpos($y, ':') === false) { return $y; } } @@ -409,15 +400,32 @@ class ActivityStreams { return $x; } - static function is_as_request() { + static function is_as_request($channel = null) { + + $hookdata = []; + if ($channel) + $hookdata['channel'] = $channel; + + $hookdata['data'] = ['application/x-zot-activity+json']; + + call_hooks('is_as_request', $hookdata); + + $x = getBestSupportedMimeType($hookdata['data']); + return (($x) ? true : false); + + } + + static function get_accept_header_string($channel = null) { + + $hookdata = []; + if ($channel) + $hookdata['channel'] = $channel; + + $hookdata['data'] = 'application/x-zot-activity+json'; - $x = getBestSupportedMimeType([ - 'application/ld+json;profile="https://www.w3.org/ns/activitystreams"', - 'application/activity+json', - 'application/ld+json;profile="http://www.w3.org/ns/activitystreams"' - ]); + call_hooks('get_accept_header_string', $hookdata); - return(($x) ? true : false); + return $hookdata['data']; } diff --git a/Zotlabs/Lib/Apps.php b/Zotlabs/Lib/Apps.php index 7b980b8d3..2c5b8a546 100644 --- a/Zotlabs/Lib/Apps.php +++ b/Zotlabs/Lib/Apps.php @@ -2,7 +2,7 @@ namespace Zotlabs\Lib; -use Zotlabs\Lib\Libsync; +use App; require_once('include/plugin.php'); require_once('include/channel.php'); @@ -21,9 +21,10 @@ class Apps { * @brief * * @param boolean $translate (optional) default true + * @param boolean $sync (optional) default false used if called from sync_sysapps() * @return array */ - static public function get_system_apps($translate = true) { + static public function get_system_apps($translate = true, $sync = false) { $ret = []; if(is_dir('apps')) @@ -33,7 +34,7 @@ class Apps { if($files) { foreach($files as $f) { - $x = self::parse_app_description($f,$translate); + $x = self::parse_app_description($f, $translate, $sync); if($x) { $ret[] = $x; } @@ -45,7 +46,7 @@ class Apps { $path = explode('/',$f); $plugin = trim($path[1]); if(plugin_is_installed($plugin)) { - $x = self::parse_app_description($f,$translate); + $x = self::parse_app_description($f, $translate, $sync); if($x) { $x['plugin'] = $plugin; $ret[] = $x; @@ -76,7 +77,9 @@ class Apps { 'Directory', 'Search', 'Help', - 'Profile Photo' + 'Profile Photo', + 'HQ', + 'Post' ]); /** @@ -209,7 +212,7 @@ class Apps { * @param boolean $translate (optional) default true * @return boolean|array */ - static public function parse_app_description($f, $translate = true) { + static public function parse_app_description($f, $translate = true, $sync = false) { $ret = []; $matches = []; @@ -255,7 +258,7 @@ class Apps { if(array_key_exists('categories',$ret)) $ret['categories'] = str_replace(array('\'','"'),array(''','&dquot;'),$ret['categories']); - if(array_key_exists('requires',$ret)) { + if(array_key_exists('requires',$ret) && !$sync) { $requires = explode(',',$ret['requires']); foreach($requires as $require) { $require = trim(strtolower($require)); @@ -307,14 +310,16 @@ class Apps { } } } - if($ret) { - if($translate) - self::translate_system_apps($ret); - return $ret; + if(empty($ret)) { + return false; } - return false; + if($translate) { + self::translate_system_apps($ret); + } + + return $ret; } @@ -374,7 +379,7 @@ class Apps { 'Permission Categories' => t('Permission Categories'), 'Public Stream' => t('Public Stream'), 'My Chatrooms' => t('My Chatrooms'), - 'Channel Export' => t('Channel Export') + 'Channel Export' => t('Channel Export'), ); if(array_key_exists('name',$arr)) { @@ -524,7 +529,7 @@ class Apps { } elseif(remote_channel()) { $observer = \App::get_observer(); - if($observer && $observer['xchan_network'] === 'zot') { + if($observer && $observer['xchan_network'] === 'zot6') { // some folks might have xchan_url redirected offsite, use the connurl $x = parse_url($observer['xchan_connurl']); if($x) { @@ -536,13 +541,47 @@ class Apps { $install_action = (($installed) ? t('Update') : t('Install')); $icon = ((strpos($papp['photo'],'icon:') === 0) ? substr($papp['photo'],5) : ''); + if (!$installed && $mode === 'module') { + $_SESSION['return_url'] = App::$query_string; + return replace_macros(get_markup_template('app_install.tpl'), [ + '$papp' => $papp, + '$install' => $install_action + ]); + } + if($mode === 'navbar') { + return replace_macros(get_markup_template('app_nav_pinned.tpl'),array( + '$app' => $papp, + '$icon' => $icon, + )); + } + + if($mode === 'nav') { return replace_macros(get_markup_template('app_nav.tpl'),array( '$app' => $papp, '$icon' => $icon, )); } + if($mode === 'inline') { + return replace_macros(get_markup_template('app_inline.tpl'),array( + '$app' => $papp, + '$icon' => $icon, + '$installed' => $installed, + '$purchase' => ((isset($papp['page']) && (! $installed)) ? t('Purchase') : ''), + '$action_label' => $install_action + )); + } + + if(in_array($mode, ['nav-order', 'nav-order-pinned'])) { + return replace_macros(get_markup_template('app_order.tpl'),array( + '$app' => $papp, + '$icon' => $icon, + '$hosturl' => $hosturl, + '$mode' => $mode + )); + } + if($mode === 'install') { $papp['embed'] = true; } @@ -551,7 +590,7 @@ class Apps { '$app' => $papp, '$icon' => $icon, '$hosturl' => $hosturl, - '$purchase' => (($papp['page'] && (! $installed)) ? t('Purchase') : ''), + '$purchase' => ((isset($papp['page']) && (! $installed)) ? t('Purchase') : ''), '$installed' => $installed, '$action_label' => (($hosturl && in_array($mode, ['view','install'])) ? $install_action : ''), '$edit' => ((local_channel() && $installed && $mode == 'edit') ? t('Edit') : ''), @@ -559,12 +598,10 @@ class Apps { '$undelete' => ((local_channel() && $mode == 'edit') ? t('Undelete') : ''), '$settings_url' => ((local_channel() && $installed && $mode == 'list') ? $papp['settings_url'] : ''), '$deleted' => $papp['deleted'], - '$feature' => (($papp['embed'] || $mode == 'edit') ? false : true), - '$pin' => (($papp['embed'] || $mode == 'edit') ? false : true), + '$feature' => ((isset($papp['embed']) || $mode == 'edit') ? false : true), + '$pin' => ((isset($papp['embed']) || $mode == 'edit') ? false : true), '$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' || $mode === 'nav-order-pinned') ? true : false), '$mode' => $mode, '$add' => t('Add to app-tray'), '$remove' => t('Remove from app-tray'), @@ -574,6 +611,7 @@ class Apps { )); } + static public function app_install($uid,$app) { if(! is_array($app)) { @@ -588,10 +626,12 @@ class Apps { $app['uid'] = $uid; - if(self::app_installed($uid,$app,true)) + if(self::app_installed($uid,$app,true)) { $x = self::app_update($app); - else + } + else { $x = self::app_store($app); + } if($x['success']) { $r = q("select * from app where app_id = '%s' and app_channel = %d limit 1", @@ -599,13 +639,12 @@ class Apps { intval($uid) ); if($r) { - if(($app['uid']) && (! $r[0]['app_system'])) { + if($app['uid']) { if($app['categories'] && (! $app['term'])) { $r[0]['term'] = q("select * from term where otype = %d and oid = %d", intval(TERM_OBJ_APP), intval($r[0]['id']) ); - Libsync::build_sync_packet($uid,array('app' => $r[0])); } } } @@ -634,6 +673,7 @@ class Apps { } } } + return true; } @@ -645,38 +685,35 @@ class Apps { dbesc($app['guid']), intval($uid) ); - if($x) { - if(! intval($x[0]['app_deleted'])) { - $x[0]['app_deleted'] = 1; - if(self::can_delete($uid,$app)) { - q("delete from app where app_id = '%s' and app_channel = %d", - dbesc($app['guid']), - intval($uid) - ); - q("delete from term where otype = %d and oid = %d", - intval(TERM_OBJ_APP), - intval($x[0]['id']) - ); - /** - * @hooks app_destroy - * Called after app entry got removed from database - * and provide app array from database. - */ - call_hooks('app_destroy', $x[0]); - } - else { - q("update app set app_deleted = 1 where app_id = '%s' and app_channel = %d", - dbesc($app['guid']), - intval($uid) - ); - } - if(! intval($x[0]['app_system'])) { - Libsync::build_sync_packet($uid,array('app' => $x)); - } - } - else { - self::app_undestroy($uid,$app); - } + + if($x && intval($x[0]['app_deleted'])) { + self::app_undestroy($uid, $app); + return; + } + + if(self::can_delete($uid,$app)) { + q("delete from app where app_id = '%s' and app_channel = %d", + dbesc($app['guid']), + intval($uid) + ); + + q("delete from term where otype = %d and oid = %d", + intval(TERM_OBJ_APP), + intval($x[0]['id']) + ); + + /** + * @hooks app_destroy + * Called after app entry got removed from database + * and provide app array from database. + */ + call_hooks('app_destroy', $x[0]); + } + else { + q("update app set app_deleted = 1 where app_id = '%s' and app_channel = %d", + dbesc($app['guid']), + intval($uid) + ); } } } @@ -693,13 +730,11 @@ class Apps { dbesc($app['guid']), intval($uid) ); - if($x) { - if($x[0]['app_system']) { - q("update app set app_deleted = 0 where app_id = '%s' and app_channel = %d", - dbesc($app['guid']), - intval($uid) - ); - } + if($x && intval($x[0]['app_deleted']) && $x[0]['app_system']) { + q("update app set app_deleted = 0 where app_id = '%s' and app_channel = %d", + dbesc($app['guid']), + intval($uid) + ); } } } @@ -1276,58 +1311,58 @@ class Apps { $ret['type'] = 'personal'; - if($app['app_id']) + if(!empty($app['app_id'])) $ret['guid'] = $app['app_id']; - if($app['app_sig']) + if(!empty($app['app_sig'])) $ret['sig'] = $app['app_sig']; - if($app['app_author']) + if(!empty($app['app_author'])) $ret['author'] = $app['app_author']; - if($app['app_name']) + if(!empty($app['app_name'])) $ret['name'] = $app['app_name']; - if($app['app_desc']) + if(!empty($app['app_desc'])) $ret['desc'] = $app['app_desc']; - if($app['app_url']) + if(!empty($app['app_url'])) $ret['url'] = $app['app_url']; - if($app['app_photo']) + if(!empty($app['app_photo'])) $ret['photo'] = $app['app_photo']; - if($app['app_icon']) + if(!empty($app['app_icon'])) $ret['icon'] = $app['app_icon']; - if($app['app_version']) + if(!empty($app['app_version'])) $ret['version'] = $app['app_version']; - if($app['app_addr']) + if(!empty($app['app_addr'])) $ret['addr'] = $app['app_addr']; - if($app['app_price']) + if(!empty($app['app_price'])) $ret['price'] = $app['app_price']; - if($app['app_page']) + if(!empty($app['app_page'])) $ret['page'] = $app['app_page']; - if($app['app_requires']) + if(!empty($app['app_requires'])) $ret['requires'] = $app['app_requires']; - if($app['app_system']) + if(!empty($app['app_system'])) $ret['system'] = $app['app_system']; - if($app['app_options']) + if(!empty($app['app_options'])) $ret['options'] = $app['app_options']; - if($app['app_plugin']) + if(!empty($app['app_plugin'])) $ret['plugin'] = trim($app['app_plugin']); - if($app['app_deleted']) + if(!empty($app['app_deleted'])) $ret['deleted'] = $app['app_deleted']; - if($app['term']) { + if(!empty($app['term']) && is_array($app['term'])) { $s = ''; foreach($app['term'] as $t) { if($s) @@ -1356,4 +1391,17 @@ class Apps { return chunk_split(base64_encode(json_encode($papp)),72,"\n"); } + static public function get_papp($app) { + + $r = q("select * from app where app_id = '%s' and app_channel = 0 limit 1", + dbesc(hash('whirlpool', $app)) + ); + + if ($r) { + $papp = self::app_encode($r[0]); + return $papp; + } + + return false; + } } diff --git a/Zotlabs/Lib/Connect.php b/Zotlabs/Lib/Connect.php index 481b02ce2..38fe69995 100644 --- a/Zotlabs/Lib/Connect.php +++ b/Zotlabs/Lib/Connect.php @@ -146,7 +146,7 @@ class Connect { } - $allowed = ((in_array($xchan['xchan_network'],['rss','zot','zot6'])) ? 1 : 0); + $allowed = ((in_array($xchan['xchan_network'],['rss', 'zot6'])) ? 1 : 0); $hookdata = ['channel_id' => $uid, 'follow_address' => $url, 'xchan' => $xchan, 'allowed' => $allowed, 'singleton' => 0]; call_hooks('follow_allow',$hookdata); @@ -207,13 +207,13 @@ class Connect { } $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 + $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) @@ -282,7 +282,7 @@ class Connect { // fetch the entire record - $r = q("select abook.*, xchan.* from abook left join xchan on abook_xchan = xchan_hash + $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) diff --git a/Zotlabs/Lib/Crypto.php b/Zotlabs/Lib/Crypto.php new file mode 100644 index 000000000..f1794ae64 --- /dev/null +++ b/Zotlabs/Lib/Crypto.php @@ -0,0 +1,206 @@ +<?php + +namespace Zotlabs\Lib; + +use Exception; + +class Crypto { + + public static $openssl_algorithms = [ + + // zot6 nickname, opensslname, keylength, ivlength + + ['aes256ctr', 'aes-256-ctr', 32, 16], + ['camellia256cfb', 'camellia-256-cfb', 32, 16], + ['cast5cfb', 'cast5-cfb', 16, 8], + ['aes256cbc', 'aes-256-cbc', 32, 16] // remove after legacy zot has been sunset + + ]; + + public static function methods() { + $ret = []; + + foreach (self::$openssl_algorithms as $ossl) { + $ret[] = $ossl[0] . '.oaep'; + } + + call_hooks('crypto_methods', $ret); + return $ret; + } + + public static function signing_methods() { + + $ret = ['sha256']; + call_hooks('signing_methods', $ret); + return $ret; + + } + + public static function new_keypair($bits) { + + $openssl_options = [ + 'digest_alg' => 'sha1', + 'private_key_bits' => $bits, + 'encrypt_key' => false + ]; + + $conf = get_config('system', 'openssl_conf_file'); + + if ($conf) { + $openssl_options['config'] = $conf; + } + + $result = openssl_pkey_new($openssl_options); + + if (empty($result)) { + return false; + } + + // Get private key + + $response = ['prvkey' => '', 'pubkey' => '']; + + openssl_pkey_export($result, $response['prvkey']); + + // Get public key + $pkey = openssl_pkey_get_details($result); + $response['pubkey'] = $pkey["key"]; + + return $response; + + } + + public static function sign($data, $key, $alg = 'sha256') { + + if (!$key) { + return false; + } + + $sig = ''; + openssl_sign($data, $sig, $key, $alg); + return $sig; + } + + public static function verify($data, $sig, $key, $alg = 'sha256') { + + if (!$key) { + return false; + } + + try { + $verify = openssl_verify($data, $sig, $key, $alg); + } catch (Exception $e) { + $verify = (-1); + } + + if ($verify === (-1)) { + while ($msg = openssl_error_string()) { + logger('openssl_verify: ' . $msg, LOGGER_NORMAL, LOG_ERR); + } + btlogger('openssl_verify: key: ' . $key, LOGGER_DEBUG, LOG_ERR); + } + + return (($verify > 0) ? true : false); + } + + public static function encapsulate($data, $pubkey, $alg) { + + if (!($alg && $pubkey)) { + return $data; + } + + $alg_base = $alg; + $padding = OPENSSL_PKCS1_PADDING; + + $exts = explode('.', $alg); + if (count($exts) > 1) { + switch ($exts[1]) { + case 'oaep': + $padding = OPENSSL_PKCS1_OAEP_PADDING; + break; + default: + break; + } + $alg_base = $exts[0]; + } + + $method = null; + + foreach (self::$openssl_algorithms as $ossl) { + if ($ossl[0] === $alg_base) { + $method = $ossl; + break; + } + } + + if ($method) { + $result = ['encrypted' => true]; + + $key = openssl_random_pseudo_bytes(256); + $iv = openssl_random_pseudo_bytes(256); + + $key1 = substr($key, 0, $method[2]); + $iv1 = substr($iv, 0, $method[3]); + + $result['data'] = base64url_encode(openssl_encrypt($data, $method[1], $key1, OPENSSL_RAW_DATA, $iv1), true); + + openssl_public_encrypt($key, $k, $pubkey, $padding); + openssl_public_encrypt($iv, $i, $pubkey, $padding); + + $result['alg'] = $alg; + $result['key'] = base64url_encode($k, true); + $result['iv'] = base64url_encode($i, true); + return $result; + + } + else { + $x = ['data' => $data, 'pubkey' => $pubkey, 'alg' => $alg, 'result' => $data]; + call_hooks('crypto_encapsulate', $x); + return $x['result']; + } + } + + public static function unencapsulate($data, $prvkey) { + + if (!(is_array($data) && array_key_exists('encrypted', $data) && array_key_exists('alg', $data) && $data['alg'])) { + logger('not encrypted'); + + return $data; + } + + $alg_base = $data['alg']; + $padding = OPENSSL_PKCS1_PADDING; + + $exts = explode('.', $data['alg']); + if (count($exts) > 1) { + switch ($exts[1]) { + case 'oaep': + $padding = OPENSSL_PKCS1_OAEP_PADDING; + break; + default: + break; + } + $alg_base = $exts[0]; + } + + $method = null; + + foreach (self::$openssl_algorithms as $ossl) { + if ($ossl[0] === $alg_base) { + $method = $ossl; + break; + } + } + + if ($method) { + openssl_private_decrypt(base64url_decode($data['key']), $k, $prvkey, $padding); + openssl_private_decrypt(base64url_decode($data['iv']), $i, $prvkey, $padding); + return openssl_decrypt(base64url_decode($data['data']), $method[1], substr($k, 0, $method[2]), OPENSSL_RAW_DATA, substr($i, 0, $method[3])); + } + else { + $x = ['data' => $data, 'prvkey' => $prvkey, 'alg' => $data['alg'], 'result' => $data]; + call_hooks('crypto_unencapsulate', $x); + return $x['result']; + } + } +} diff --git a/Zotlabs/Lib/DReport.php b/Zotlabs/Lib/DReport.php index 7515d3292..2263529b2 100644 --- a/Zotlabs/Lib/DReport.php +++ b/Zotlabs/Lib/DReport.php @@ -87,8 +87,7 @@ class DReport { // Is the sender one of our channels? - $c = q("select channel_id from channel where channel_hash = '%s' or channel_portable_id = '%s' limit 1", - dbesc($dr['sender']), + $c = q("select channel_id from channel where channel_hash = '%s' limit 1", dbesc($dr['sender']) ); diff --git a/Zotlabs/Lib/Enotify.php b/Zotlabs/Lib/Enotify.php index c78325ee3..d02dab739 100644 --- a/Zotlabs/Lib/Enotify.php +++ b/Zotlabs/Lib/Enotify.php @@ -43,7 +43,7 @@ class Enotify { dbesc($params['to_xchan']) ); } - if ($x & $y) { + if ($x && $y) { $sender = $x[0]; $recip = $y[0]; } else { @@ -64,29 +64,29 @@ class Enotify { $sitename = get_config('system','sitename'); $site_admin = sprintf( t('%s Administrator'), $sitename); $opt_out1 = sprintf( t('This email was sent by %1$s at %2$s.'), t('$Projectname'), \App::get_hostname()); - $opt_out2 = sprintf( t('To stop receiving these messages, please adjust your Notification Settings at %s'), z_root() . '/settings'); + $opt_out2 = sprintf( t('To stop receiving these messages, please adjust your Notification Settings at %s'), z_root() . '/settings'); $hopt_out2 = sprintf( t('To stop receiving these messages, please adjust your %s.'), '<a href="' . z_root() . '/settings' . '">' . t('Notification Settings') . '</a>'); $sender_name = $product; $hostname = \App::get_hostname(); if(strpos($hostname,':')) - $hostname = substr($hostname,0,strpos($hostname,':')); + $hostname = substr($hostname, 0, strpos($hostname,':')); // Do not translate 'noreply' as it must be a legal 7-bit email address - $reply_email = get_config('system','reply_address'); + $reply_email = get_config('system', 'reply_address'); if(! $reply_email) $reply_email = 'noreply' . '@' . $hostname; - $sender_email = get_config('system','from_email'); + $sender_email = get_config('system', 'from_email'); if(! $sender_email) $sender_email = 'Administrator' . '@' . $hostname; - - $sender_name = get_config('system','from_email_name'); + + $sender_name = get_config('system', 'from_email_name'); if(! $sender_name) $sender_name = \Zotlabs\Lib\System::get_site_name(); - $additional_mail_header = ""; + $additional_mail_header = ''; if(array_key_exists('item', $params)) { require_once('include/conversation.php'); @@ -108,33 +108,34 @@ class Enotify { logger('notification invoked for an old item which may have been refetched.',LOGGER_DEBUG,LOG_INFO); return; } - } + } else { $title = $body = ''; } - $always_show_in_notices = get_pconfig($recip['channel_id'],'system','always_show_in_notices'); - $vnotify = get_pconfig($recip['channel_id'],'system','vnotify'); + $always_show_in_notices = get_pconfig($recip['channel_id'], 'system', 'always_show_in_notices'); + $vnotify = get_pconfig($recip['channel_id'], 'system', 'vnotify'); $salutation = $recip['channel_name']; // e.g. "your post", "David's photo", etc. $possess_desc = t('%s <!item_type!>'); +// @@TODO: consider using switch instead of those elseif if ($params['type'] == NOTIFY_MAIL) { logger('notification: mail'); - $subject = sprintf( t('[$Projectname:Notify] New mail received at %s'),$sitename); - - $preamble = sprintf( t('%1$s sent you a new private message at %2$s.'), $sender['xchan_name'],$sitename); - $epreamble = sprintf( t('%1$s sent you %2$s.'),'[zrl=' . $sender['xchan_url'] . ']' . $sender['xchan_name'] . '[/zrl]', '[zrl=$itemlink]' . t('a private message') . '[/zrl]'); - $sitelink = t('Please visit %s to view and/or reply to your private messages.'); - $tsitelink = sprintf( $sitelink, $siteurl . '/mail/' . $params['item']['id'] ); - $hsitelink = sprintf( $sitelink, '<a href="' . $siteurl . '/mail/' . $params['item']['id'] . '">' . $sitename . '</a>'); - $itemlink = $siteurl . '/mail/' . $params['item']['id']; + $subject = sprintf( t('[$Projectname:Notify] New direct message received at %s'), $sitename); + + $preamble = sprintf( t('%1$s sent you a new direct message at %2$s'), $sender['xchan_name'], $sitename); + $epreamble = sprintf( t('%1$s sent you %2$s.'), '[zrl=' . $sender['xchan_url'] . ']' . $sender['xchan_name'] . '[/zrl]', '[zrl=$itemlink]' . t('a direct message') . '[/zrl]'); + $sitelink = t('Please visit %s to view and/or reply to your direct messages.'); + $tsitelink = sprintf( $sitelink, $siteurl . '/hq/' . gen_link_id($params['item']['mid'])); + $hsitelink = sprintf( $sitelink, '<a href="' . $siteurl . '/hq/' . gen_link_id($params['item']['mid']) . '">' . $sitename . '</a>'); + $itemlink = $siteurl . '/hq/' . gen_link_id($params['item']['mid']); } - if ($params['type'] == NOTIFY_COMMENT) { + elseif ($params['type'] === NOTIFY_COMMENT) { //logger("notification: params = " . print_r($params, true), LOGGER_DEBUG); $moderated = (($params['item']['item_blocked'] == ITEM_MODERATED) ? true : false); @@ -171,7 +172,6 @@ class Enotify { // Check to see if there was already a notify for this post. // If so don't create a second notification - $p = null; $p = q("select id from notify where link = '%s' and uid = %d limit 1", dbesc($params['link']), intval($recip['channel_id']) @@ -181,7 +181,7 @@ class Enotify { pop_lang(); return; } - + // if it's a post figure out who's post it is. @@ -196,6 +196,7 @@ class Enotify { xchan_query($p); +//@@FIXME $p can be null (line 188) $item_post_type = item_post_type($p[0]); // $private = $p[0]['item_private']; $parent_id = $p[0]['id']; @@ -219,7 +220,7 @@ class Enotify { $itemlink, $p[0]['author']['xchan_name'], $item_post_type); - + // "your post" if($p[0]['owner']['xchan_name'] == $p[0]['author']['xchan_name'] && intval($p[0]['item_wall'])) $dest_str = sprintf(t('%1$s %2$s [zrl=%3$s]your %4$s[/zrl]'), @@ -230,15 +231,15 @@ class Enotify { // Some mail softwares relies on subject field for threading. // So, we cannot have different subjects for notifications of the same thread. - // Before this we have the name of the replier on the subject rendering + // Before this we have the name of the replier on the subject rendering // differents subjects for messages on the same thread. if($moderated) $subject = sprintf( t('[$Projectname:Notify] Moderated Comment to conversation #%1$d by %2$s'), $parent_id, $sender['xchan_name']); else $subject = sprintf( t('[$Projectname:Notify] Comment to conversation #%1$d by %2$s'), $parent_id, $sender['xchan_name']); - $preamble = sprintf( t('%1$s commented on an item/conversation you have been following.'), $sender['xchan_name']); - $epreamble = $dest_str; + $preamble = sprintf( t('%1$s commented on an item/conversation you have been following'), $sender['xchan_name']); + $epreamble = $dest_str; $sitelink = t('Please visit %s to view and/or reply to the conversation.'); $tsitelink = sprintf( $sitelink, $siteurl ); @@ -247,10 +248,10 @@ class Enotify { $tsitelink .= "\n\n" . sprintf( t('Please visit %s to approve or reject this comment.'), z_root() . '/moderate' ); $hsitelink .= "<br><br>" . sprintf( t('Please visit %s to approve or reject this comment.'), '<a href="' . z_root() . '/moderate">' . z_root() . '/moderate</a>' ); } - + } - if ($params['type'] == NOTIFY_LIKE) { + elseif ($params['type'] === NOTIFY_LIKE) { // logger("notification: params = " . print_r($params, true), LOGGER_DEBUG); $itemlink = $params['link']; @@ -268,7 +269,6 @@ class Enotify { // Check to see if there was already a notify for this post. // If so don't create a second notification - $p = null; $p = q("select id from notify where link = '%s' and uid = %d limit 1", dbesc($params['link']), intval($recip['channel_id']) @@ -278,7 +278,7 @@ class Enotify { pop_lang(); return; } - + // if it's a post figure out who's post it is. @@ -293,7 +293,7 @@ class Enotify { xchan_query($p); - +//@@FIXME $p can be null (line 285) $item_post_type = item_post_type($p[0]); // $private = $p[0]['item_private']; $parent_id = $p[0]['id']; @@ -302,7 +302,7 @@ class Enotify { // "your post" - if($p[0]['owner']['xchan_name'] == $p[0]['author']['xchan_name'] && intval($p[0]['item_wall'])) + if($p[0]['owner']['xchan_name'] === $p[0]['author']['xchan_name'] && intval($p[0]['item_wall'])) $dest_str = sprintf(t('%1$s liked [zrl=%2$s]your %3$s[/zrl]'), '[zrl=' . $sender['xchan_url'] . ']' . $sender['xchan_name'] . '[/zrl]', $itemlink, @@ -314,12 +314,12 @@ class Enotify { // Some mail softwares relies on subject field for threading. // So, we cannot have different subjects for notifications of the same thread. - // Before this we have the name of the replier on the subject rendering + // Before this we have the name of the replier on the subject rendering // differents subjects for messages on the same thread. $subject = sprintf( t('[$Projectname:Notify] Like received to conversation #%1$d by %2$s'), $parent_id, $sender['xchan_name']); - $preamble = sprintf( t('%1$s liked an item/conversation you created.'), $sender['xchan_name']); - $epreamble = $dest_str; + $preamble = sprintf( t('%1$s liked an item/conversation you created'), $sender['xchan_name']); + $epreamble = $dest_str; $sitelink = t('Please visit %s to view and/or reply to the conversation.'); $tsitelink = sprintf( $sitelink, $siteurl ); @@ -328,14 +328,14 @@ class Enotify { - if($params['type'] == NOTIFY_WALL) { + elseif($params['type'] === NOTIFY_WALL) { $subject = sprintf( t('[$Projectname:Notify] %s posted to your profile wall') , $sender['xchan_name']); $preamble = sprintf( t('%1$s posted to your profile wall at %2$s') , $sender['xchan_name'], $sitename); $epreamble = sprintf( t('%1$s posted to [zrl=%2$s]your wall[/zrl]') , '[zrl=' . $sender['xchan_url'] . ']' . $sender['xchan_name'] . '[/zrl]', - $params['link']); + $params['link']); $sitelink = t('Please visit %s to view and/or reply to the conversation.'); $tsitelink = sprintf( $sitelink, $siteurl ); @@ -343,9 +343,8 @@ class Enotify { $itemlink = $params['link']; } - if ($params['type'] == NOTIFY_TAGSELF) { + elseif ($params['type'] === NOTIFY_TAGSELF) { - $p = null; $p = q("select id from notify where link = '%s' and uid = %d limit 1", dbesc($params['link']), intval($recip['channel_id']) @@ -355,12 +354,12 @@ class Enotify { pop_lang(); return; } - + $subject = sprintf( t('[$Projectname:Notify] %s tagged you') , $sender['xchan_name']); $preamble = sprintf( t('%1$s tagged you at %2$s') , $sender['xchan_name'], $sitename); $epreamble = sprintf( t('%1$s [zrl=%2$s]tagged you[/zrl].') , '[zrl=' . $sender['xchan_url'] . ']' . $sender['xchan_name'] . '[/zrl]', - $params['link']); + $params['link']); $sitelink = t('Please visit %s to view and/or reply to the conversation.'); $tsitelink = sprintf( $sitelink, $siteurl ); @@ -368,12 +367,12 @@ class Enotify { $itemlink = $params['link']; } - if ($params['type'] == NOTIFY_POKE) { + elseif ($params['type'] === NOTIFY_POKE) { $subject = sprintf( t('[$Projectname:Notify] %1$s poked you') , $sender['xchan_name']); $preamble = sprintf( t('%1$s poked you at %2$s') , $sender['xchan_name'], $sitename); $epreamble = sprintf( t('%1$s [zrl=%2$s]poked you[/zrl].') , '[zrl=' . $sender['xchan_url'] . ']' . $sender['xchan_name'] . '[/zrl]', - $params['link']); + $params['link']); $subject = str_replace('poked', t($params['activity']), $subject); $preamble = str_replace('poked', t($params['activity']), $preamble); @@ -385,12 +384,12 @@ class Enotify { $itemlink = $params['link']; } - if ($params['type'] == NOTIFY_TAGSHARE) { + elseif ($params['type'] === NOTIFY_TAGSHARE) { $subject = sprintf( t('[$Projectname:Notify] %s tagged your post') , $sender['xchan_name']); $preamble = sprintf( t('%1$s tagged your post at %2$s'),$sender['xchan_name'], $sitename); $epreamble = sprintf( t('%1$s tagged [zrl=%2$s]your post[/zrl]') , '[zrl=' . $sender['xchan_url'] . ']' . $sender['xchan_name'] . '[/zrl]', - $itemlink); + $itemlink); $sitelink = t('Please visit %s to view and/or reply to the conversation.'); $tsitelink = sprintf( $sitelink, $siteurl ); @@ -398,12 +397,12 @@ class Enotify { $itemlink = $params['link']; } - if ($params['type'] == NOTIFY_INTRO) { + elseif ($params['type'] === NOTIFY_INTRO) { $subject = sprintf( t('[$Projectname:Notify] Introduction received')); - $preamble = sprintf( t('You\'ve received an new connection request from \'%1$s\' at %2$s'), $sender['xchan_name'], $sitename); + $preamble = sprintf( t('You\'ve received an new connection request from \'%1$s\' at %2$s'), $sender['xchan_name'], $sitename); $epreamble = sprintf( t('You\'ve received [zrl=%1$s]a new connection request[/zrl] from %2$s.'), $siteurl . '/connections/ifpending', - '[zrl=' . $sender['xchan_url'] . ']' . $sender['xchan_name'] . '[/zrl]'); + '[zrl=' . $sender['xchan_url'] . ']' . $sender['xchan_name'] . '[/zrl]'); $body = sprintf( t('You may visit their profile at %s'),$sender['xchan_url']); $sitelink = t('Please visit %s to approve or reject the connection request.'); @@ -412,13 +411,13 @@ class Enotify { $itemlink = $params['link']; } - if ($params['type'] == NOTIFY_SUGGEST) { + elseif ($params['type'] === NOTIFY_SUGGEST) { $subject = sprintf( t('[$Projectname:Notify] Friend suggestion received')); - $preamble = sprintf( t('You\'ve received a friend suggestion from \'%1$s\' at %2$s'), $sender['xchan_name'], $sitename); + $preamble = sprintf( t('You\'ve received a friend suggestion from \'%1$s\' at %2$s'), $sender['xchan_name'], $sitename); $epreamble = sprintf( t('You\'ve received [zrl=%1$s]a friend suggestion[/zrl] for %2$s from %3$s.'), $itemlink, '[zrl=' . $params['item']['url'] . ']' . $params['item']['name'] . '[/zrl]', - '[zrl=' . $sender['xchan_url'] . ']' . $sender['xchan_name'] . '[/zrl]'); + '[zrl=' . $sender['xchan_url'] . ']' . $sender['xchan_name'] . '[/zrl]'); $body = t('Name:') . ' ' . $params['item']['name'] . "\n"; $body .= t('Photo:') . ' ' . $params['item']['photo'] . "\n"; @@ -430,11 +429,11 @@ class Enotify { $itemlink = $params['link']; } - if ($params['type'] == NOTIFY_CONFIRM) { + elseif ($params['type'] === NOTIFY_CONFIRM) { // ? } - if ($params['type'] == NOTIFY_SYSTEM) { + elseif ($params['type'] === NOTIFY_SYSTEM) { // ? } @@ -462,7 +461,7 @@ class Enotify { $sitelink = $h['sitelink']; $tsitelink = $h['tsitelink']; $hsitelink = $h['hsitelink']; - $itemlink = $h['itemlink']; + $itemlink = $h['itemlink']; require_once('include/html2bbcode.php'); @@ -477,7 +476,7 @@ class Enotify { } while ($dups === true); - $datarray = array(); + $datarray = []; $datarray['hash'] = $hash; $datarray['sender_hash'] = $sender['xchan_hash']; $datarray['xname'] = $sender['xchan_name']; @@ -510,11 +509,11 @@ class Enotify { // Mark some notifications as seen right away // Note! The notification have to be created, because they are used to send emails // So easiest solution to hide them from Notices is to mark them as seen right away. - // Another option would be to not add them to the DB, and change how emails are handled + // Another option would be to not add them to the DB, and change how emails are handled // (probably would be better that way) if (!$always_show_in_notices) { - if (($params['type'] == NOTIFY_WALL) || ($params['type'] == NOTIFY_MAIL) || ($params['type'] == NOTIFY_INTRO)) { + if (($params['type'] === NOTIFY_WALL) || ($params['type'] === NOTIFY_MAIL) || ($params['type'] === NOTIFY_INTRO)) { $seen = 1; } } @@ -550,12 +549,12 @@ class Enotify { } $itemlink = z_root() . '/notify/view/' . $notify_id; - $msg = str_replace('$itemlink',$itemlink,$epreamble); + $msg = str_replace('$itemlink', $itemlink, $epreamble); // wretched hack, but we don't want to duplicate all the preamble variations and we also don't want to screw up a translation if ((\App::$language === 'en' || (! \App::$language)) && strpos($msg,', ')) - $msg = substr($msg,strpos($msg,', ')+1); + $msg = substr($msg, strpos($msg,', ')+1); $datarray['id'] = $notify_id; $datarray['msg'] = $msg; @@ -575,7 +574,7 @@ class Enotify { logger('notification: sending notification email'); - $hn = get_pconfig($recip['channel_id'],'system','email_notify_host'); + $hn = get_pconfig($recip['channel_id'], 'system', 'email_notify_host'); if($hn && (! stristr(\App::get_hostname(),$hn))) { // this isn't the email notification host pop_lang(); @@ -584,15 +583,15 @@ class Enotify { $textversion = strip_tags(html_entity_decode(bbcode(stripslashes(str_replace(array("\\r", "\\n"), array( "", "\n"), $body))),ENT_QUOTES,'UTF-8')); - $htmlversion = bbcode(stripslashes(str_replace(array("\\r","\\n"), array("","<br />\n"),$body))); + $htmlversion = bbcode(stripslashes(str_replace(array("\\r","\\n"), array('',"<br />\n"),$body))); - // use $_SESSION['zid_override'] to force zid() to use + // use $_SESSION['zid_override'] to force zid() to use // the recipient address instead of the current observer $_SESSION['zid_override'] = channel_reddress($recip); $_SESSION['zrl_override'] = z_root() . '/channel/' . $recip['channel_address']; - + $textversion = zidify_links($textversion); $htmlversion = zidify_links($htmlversion); @@ -601,7 +600,7 @@ class Enotify { unset($_SESSION['zid_override']); unset($_SESSION['zrl_override']); - $datarray = array(); + $datarray = []; $datarray['banner'] = $banner; $datarray['product'] = $product; $datarray['preamble'] = $preamble; @@ -754,21 +753,21 @@ class Enotify { return $params['result']; } - $fromName = email_header_encode(html_entity_decode($params['fromName'],ENT_QUOTES,'UTF-8'),'UTF-8'); + $fromName = email_header_encode(html_entity_decode($params['fromName'],ENT_QUOTES,'UTF-8'),'UTF-8'); $messageSubject = email_header_encode(html_entity_decode($params['messageSubject'],ENT_QUOTES,'UTF-8'),'UTF-8'); // generate a mime boundary - $mimeBoundary = rand(0, 9) . "-" - .rand(100000000, 999999999) . "-" - .rand(100000000, 999999999) . "=:" + $mimeBoundary = rand(0, 9) . '-' + .rand(100000000, 999999999) . '-' + .rand(100000000, 999999999) . '=:' .rand(10000, 99999); // generate a multipart/alternative message header $messageHeader = $params['additionalMailHeader'] . "From: $fromName <{$params['fromEmail']}>" . PHP_EOL . - "Reply-To: $fromName <{$params['replyTo']}>" . PHP_EOL . - "MIME-Version: 1.0" . PHP_EOL . + "Reply-To: $fromName <{$params['replyTo']}>" . PHP_EOL . + 'MIME-Version: 1.0' . PHP_EOL . "Content-Type: multipart/alternative; boundary=\"{$mimeBoundary}\""; // assemble the final multipart message body with the text and html types included @@ -776,15 +775,15 @@ class Enotify { $htmlBody = chunk_split(base64_encode($params['htmlVersion'])); $multipartMessageBody = - "--" . $mimeBoundary . PHP_EOL . // plain text section - "Content-Type: text/plain; charset=UTF-8" . PHP_EOL . - "Content-Transfer-Encoding: base64" . PHP_EOL . PHP_EOL . + '--' . $mimeBoundary . PHP_EOL . // plain text section + 'Content-Type: text/plain; charset=UTF-8' . PHP_EOL . + 'Content-Transfer-Encoding: base64' . PHP_EOL . PHP_EOL . $textBody . PHP_EOL . - "--" . $mimeBoundary . PHP_EOL . // text/html section - "Content-Type: text/html; charset=UTF-8" . PHP_EOL . - "Content-Transfer-Encoding: base64" . PHP_EOL . PHP_EOL . + '--' . $mimeBoundary . PHP_EOL . // text/html section + 'Content-Type: text/html; charset=UTF-8' . PHP_EOL . + 'Content-Transfer-Encoding: base64' . PHP_EOL . PHP_EOL . $htmlBody . PHP_EOL . - "--" . $mimeBoundary . "--" . PHP_EOL; // message ending + '--' . $mimeBoundary . '--' . PHP_EOL; // message ending // send the message $res = mail( @@ -793,7 +792,7 @@ class Enotify { $multipartMessageBody, // message body $messageHeader // message headers ); - logger("notification: enotify::send returns " . (($res) ? 'success' : 'failure'), LOGGER_DEBUG); + logger('notification: enotify::send returns ' . (($res) ? 'success' : 'failure'), LOGGER_DEBUG); return $res; } @@ -803,7 +802,7 @@ class Enotify { require_once('include/conversation.php'); - // Call localize_item to get a one line status for activities. + // Call localize_item to get a one line status for activities. // This should set $item['localized'] to indicate we have a brief summary. // and perhaps $item['shortlocalized'] for an even briefer summary @@ -833,13 +832,12 @@ class Enotify { $edit = false; if($item['edited'] > $item['created']) { + $edit = true; if($item['item_thread_top']) { $itemem_text = sprintf( t('edited a post dated %s'), relative_date($item['created'])); - $edit = true; } else { $itemem_text = sprintf( t('edited a comment dated %s'), relative_date($item['created'])); - $edit = true; } } @@ -856,18 +854,18 @@ class Enotify { 'photo' => $item[$who]['xchan_photo_s'], 'when' => (($edit) ? datetime_convert('UTC', date_default_timezone_get(), $item['edited']) : datetime_convert('UTC', date_default_timezone_get(), $item['created'])), 'class' => (intval($item['item_unseen']) ? 'notify-unseen' : 'notify-seen'), - 'b64mid' => (($item['mid']) ? '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'])), + 'b64mid' => (($item['mid']) ? gen_link_id($item['mid']) : ''), + //'b64mid' => ((in_array($item['verb'], [ACTIVITY_LIKE, ACTIVITY_DISLIKE])) ? gen_link_id($item['thr_parent']) : gen_link_id($item['mid'])), 'thread_top' => (($item['item_thread_top']) ? true : false), 'message' => bbcode(escape_tags($itemem_text)), - 'body' => htmlentities(html2plain(bbcode($item['body']), 75, true), ENT_COMPAT, 'UTF-8', false), + 'body' => htmlentities(html2plain(bbcode($item['body'], ['drop_media', true]), 75, true), ENT_QUOTES, 'UTF-8', false), // these are for the superblock addon 'hash' => $item[$who]['xchan_hash'], 'uid' => $item['uid'], 'display' => true ); - call_hooks('enotify_format',$x); + call_hooks('enotify_format', $x); if(! $x['display']) { return []; } @@ -884,9 +882,9 @@ class Enotify { $mid = basename($tt['link']); - $b64mid = ((strpos($mid, 'b64.') === 0) ? $mid : 'b64.' . base64url_encode($mid)); + $b64mid = gen_link_id($mid); $x = [ - 'notify_link' => z_root() . '/notify/view/' . $tt['id'], + 'notify_link' => (($tt['ntype'] === NOTIFY_MAIL) ? $tt['link'] : z_root() . '/notify/view/' . $tt['id']), 'name' => $tt['xname'], 'url' => $tt['url'], 'photo' => $tt['photo'], @@ -903,7 +901,7 @@ class Enotify { static public function format_intros($rr) { - $x = [ + return [ 'notify_link' => z_root() . '/connections/ifpending', 'name' => $rr['xchan_name'], 'addr' => $rr['xchan_addr'], @@ -914,13 +912,11 @@ class Enotify { 'message' => t('added your channel') ]; - return $x; - } static public function format_files($rr) { - $x = [ + return [ 'notify_link' => z_root() . '/sharedwithme', 'name' => $rr['author']['xchan_name'], 'addr' => $rr['author']['xchan_addr'], @@ -931,13 +927,11 @@ class Enotify { 'message' => t('shared a file with you') ]; - return $x; - } static public function format_mail($rr) { - $x = [ + return [ 'notify_link' => z_root() . '/mail/' . $rr['id'], 'name' => $rr['xchan_name'], 'addr' => $rr['xchan_addr'], @@ -945,11 +939,9 @@ class Enotify { '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'), + 'message' => t('sent you a direct message'), ]; - return $x; - } static public function format_all_events($rr) { @@ -959,7 +951,7 @@ class Enotify { $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 = [ + return [ 'notify_link' => z_root() . '/cdav/calendar/' . $rr['event_hash'], 'name' => $rr['xchan_name'], 'addr' => $rr['xchan_addr'], @@ -970,23 +962,20 @@ class Enotify { 'message' => t('created an event') ]; - return $x; } static public function format_register($rr) { - $x = [ + return [ 'notify_link' => z_root() . '/admin/accounts', - 'name' => $rr['account_email'], - //'addr' => $rr['account_email'], + 'name' => $rr['reg_did2'], + //'addr' => '', 'photo' => z_root() . '/' . get_default_profile_photo(48), - 'when' => datetime_convert('UTC', date_default_timezone_get(),$rr['account_created']), + 'when' => datetime_convert('UTC', date_default_timezone_get(),$rr['reg_created']), 'hclass' => ('notify-unseen'), - 'message' => t('requires approval') + 'message' => t('status verified') ]; - return $x; - } } diff --git a/Zotlabs/Lib/Hashpath.php b/Zotlabs/Lib/Hashpath.php new file mode 100644 index 000000000..f3b25d2b6 --- /dev/null +++ b/Zotlabs/Lib/Hashpath.php @@ -0,0 +1,55 @@ +<?php +namespace Zotlabs\Lib; + +/* + * Zotlabs\Lib\Hashpath + * + * Creates hashed directory structures for fast access and resistance to overloading any single directory with files. + * + * Takes a $hash which could be any string + * a $prefix which is where to place the hash directory in the filesystem, default is current directory + * use an empty string for $prefix to place hash directories directly off the root directory + * an optional $depth and $slice (default is 2) to indicate the hash level + * $depth = 1, 256 directories, suitable for < 384K records/files + * $depth = 2, 65536 directories, suitable for < 98M records/files + * $depth = 3, 16777216 directories, suitable for < 2.5B records/files + * ... + * The total number of records anticipated divided by the number of hash directories should generally be kept to + * less than 1500 entries for optimum performance though this varies by operating system and filesystem type. + * ext4 uses 32 bit inode numbers (~4B record limit) so use caution or alternative filesystem types with $depth above 3. + * an optional $mkdir (boolean) to recursively create the directory (ignoring errors) before returning + * + * examples: for a $hash of 'abcdefg' and prefix of 'path' the following paths are returned for $depth = 1 and $depth = 3 + * path/7d/7d1a54127b222502f5b79b5fb0803061152a44f92b37e23c6527baf665d4da9a + * path/7d/1a/54/7d1a54127b222502f5b79b5fb0803061152a44f92b37e23c6527baf665d4da9a + * + * see also: boot.php:os_mkdir() - here we provide the equivalent of mkdir -p with permissions of 770. + * + */ + +class Hashpath { + + static function path($hash, $prefix = '.', $depth = 1, $slice = 2, $mkdir = true, $alg = false) { + + if ($alg) + $hash = hash($alg, $hash); + + $start = 0; + if ($depth < 1) + $depth = 1; + $sluglen = $depth * $slice; + + do { + $slug = substr($hash, $start, $slice); + $prefix .= '/' . $slug; + $start += $slice; + $sluglen -= $slice; + } + while ($sluglen); + + if ($mkdir) + os_mkdir($prefix, STORAGE_DEFAULT_PERMISSIONS, true); + + return $prefix . '/' . $hash; + } +} diff --git a/Zotlabs/Lib/JSalmon.php b/Zotlabs/Lib/JSalmon.php index 48a4e649b..f9fe99706 100644 --- a/Zotlabs/Lib/JSalmon.php +++ b/Zotlabs/Lib/JSalmon.php @@ -18,7 +18,7 @@ class JSalmon { $precomputed = '.' . base64url_encode($data_type,true) . '.YmFzZTY0dXJs.UlNBLVNIQTI1Ng'; - $signature = base64url_encode(rsa_sign($data . $precomputed, $key), true); + $signature = base64url_encode(Crypto::sign($data . $precomputed, $key), true); return ([ 'signed' => true, @@ -40,21 +40,21 @@ class JSalmon { $ret = [ 'results' => [] ]; if(! is_array($x)) { - return $false; + return false; } if(! ( array_key_exists('signed',$x) && $x['signed'])) { - return $false; + return false; } - $signed_data = preg_replace('/\s+/','',$x['data']) . '.' - . base64url_encode($x['data_type'],true) . '.' - . base64url_encode($x['encoding'],true) . '.' + $signed_data = preg_replace('/\s+/','',$x['data']) . '.' + . base64url_encode($x['data_type'],true) . '.' + . base64url_encode($x['encoding'],true) . '.' . base64url_encode($x['alg'],true); $key = HTTPSig::get_key(EMPTY_STR,'zot6',base64url_decode($x['sigs']['key_id'])); logger('key: ' . print_r($key,true)); if($key['portable_id'] && $key['public_key']) { - if(rsa_verify($signed_data,base64url_decode($x['sigs']['value']),$key['public_key'])) { + if(Crypto::verify($signed_data,base64url_decode($x['sigs']['value']),$key['public_key'])) { logger('verified'); $ret = [ 'success' => true, 'signer' => $key['portable_id'], 'hubloc' => $key['hubloc'] ]; } diff --git a/Zotlabs/Lib/Keyutils.php b/Zotlabs/Lib/Keyutils.php new file mode 100644 index 000000000..616ecfcf6 --- /dev/null +++ b/Zotlabs/Lib/Keyutils.php @@ -0,0 +1,99 @@ +<?php + +namespace Zotlabs\Lib; + +use phpseclib\Crypt\RSA; +use phpseclib\Math\BigInteger; + +/** + * Keyutils + * Convert RSA keys between various formats + */ +class Keyutils { + + /** + * @param string $m modulo + * @param string $e exponent + * @return string + */ + public static function meToPem($m, $e) { + + $rsa = new RSA(); + $rsa->loadKey([ + 'e' => new BigInteger($e, 256), + 'n' => new BigInteger($m, 256) + ]); + return $rsa->getPublicKey(); + + } + + /** + * @param string key + * @return string + */ + public static function rsaToPem($key) { + + $rsa = new RSA(); + $rsa->setPublicKey($key); + + return $rsa->getPublicKey(RSA::PUBLIC_FORMAT_PKCS8); + + } + + /** + * @param string key + * @return string + */ + public static function pemToRsa($key) { + + $rsa = new RSA(); + $rsa->setPublicKey($key); + + return $rsa->getPublicKey(RSA::PUBLIC_FORMAT_PKCS1); + + } + + /** + * @param string $key key + * @param string $m reference modulo + * @param string $e reference exponent + */ + public static function pemToMe($key, &$m, &$e) { + + $rsa = new RSA(); + $rsa->loadKey($key); + $rsa->setPublicKey(); + + $m = $rsa->modulus->toBytes(); + $e = $rsa->exponent->toBytes(); + + } + + /** + * @param string $pubkey + * @return string + */ + public static function salmonKey($pubkey) { + self::pemToMe($pubkey, $m, $e); + return 'RSA' . '.' . base64url_encode($m, true) . '.' . base64url_encode($e, true); + } + + /** + * @param string $key + * @return string + */ + public static function convertSalmonKey($key) { + if (strstr($key, ',')) + $rawkey = substr($key, strpos($key, ',') + 1); + else + $rawkey = substr($key, 5); + + $key_info = explode('.', $rawkey); + + $m = base64url_decode($key_info[1]); + $e = base64url_decode($key_info[2]); + + return self::meToPem($m, $e); + } + +}
\ No newline at end of file diff --git a/Zotlabs/Lib/LDSignatures.php b/Zotlabs/Lib/LDSignatures.php index 2eba66ccf..1c2095f10 100644 --- a/Zotlabs/Lib/LDSignatures.php +++ b/Zotlabs/Lib/LDSignatures.php @@ -12,7 +12,7 @@ class LDSignatures { $ohash = self::hash(self::signable_options($data['signature'])); $dhash = self::hash(self::signable_data($data)); - $x = rsa_verify($ohash . $dhash,base64_decode($data['signature']['signatureValue']), $pubkey); + $x = Crypto::verify($ohash . $dhash,base64_decode($data['signature']['signatureValue']), $pubkey); logger('LD-verify: ' . intval($x)); return $x; @@ -35,11 +35,11 @@ class LDSignatures { $ohash = self::hash(self::signable_options($options)); $dhash = self::hash(self::signable_data($data)); - $options['signatureValue'] = base64_encode(rsa_sign($ohash . $dhash,$channel['channel_prvkey'])); + $options['signatureValue'] = base64_encode(Crypto::sign($ohash . $dhash,$channel['channel_prvkey'])); $signed = array_merge([ - '@context' => [ - ACTIVITYSTREAMS_JSONLD_REV, + '@context' => [ + ACTIVITYSTREAMS_JSONLD_REV, 'https://w3id.org/security/v1' ], ],$options); @@ -88,7 +88,7 @@ class LDSignatures { return ''; jsonld_set_document_loader('jsonld_document_loader'); - + try { $d = jsonld_normalize($data,[ 'algorithm' => 'URDNA2015', 'format' => 'application/nquads' ]); } @@ -117,7 +117,7 @@ class LDSignatures { $precomputed = '.' . base64url_encode($data_type,false) . '.YmFzZTY0dXJs.UlNBLVNIQTI1Ng=='; - $signature = base64url_encode(rsa_sign($data . $precomputed,$channel['channel_prvkey'])); + $signature = base64url_encode(Crypto::sign($data . $precomputed,$channel['channel_prvkey'])); return ([ 'id' => $arr['id'], diff --git a/Zotlabs/Lib/Libsync.php b/Zotlabs/Lib/Libsync.php index cff320e11..c4f1b20ea 100644 --- a/Zotlabs/Lib/Libsync.php +++ b/Zotlabs/Lib/Libsync.php @@ -2,9 +2,9 @@ namespace Zotlabs\Lib; -use Zotlabs\Lib\Libzot; -use Zotlabs\Lib\Queue; +use App; +use Zotlabs\Daemon\Master; class Libsync { @@ -23,129 +23,128 @@ class Libsync { logger('build_sync_packet'); - $keychange = (($packet && array_key_exists('keychange',$packet)) ? true : false); - if($keychange) { + $keychange = (($packet && array_key_exists('keychange', $packet)) ? true : false); + if ($keychange) { logger('keychange sync'); } - if(! $uid) + if (!$uid) $uid = local_channel(); - if(! $uid) + if (!$uid) return; $r = q("select * from channel where channel_id = %d limit 1", intval($uid) ); - if(! $r) + if (!$r) return; $channel = $r[0]; - // don't provide these in the export + // don't provide these in the export unset($channel['channel_active']); unset($channel['channel_password']); unset($channel['channel_salt']); - if(intval($channel['channel_removed'])) + if (intval($channel['channel_removed'])) return; $h = q("select hubloc.*, site.site_crypto from hubloc left join site on site_url = hubloc_url where hubloc_hash = '%s' and hubloc_deleted = 0", dbesc(($keychange) ? $packet['keychange']['old_hash'] : $channel['channel_hash']) ); - if(! $h) + if (!$h) return; - $synchubs = array(); + $synchubs = []; - foreach($h as $x) { - if($x['hubloc_host'] == \App::get_hostname()) + foreach ($h as $x) { + if ($x['hubloc_host'] == App::get_hostname()) continue; $y = q("select site_dead from site where site_url = '%s' limit 1", dbesc($x['hubloc_url']) ); - if((! $y) || ($y[0]['site_dead'] == 0)) + if ((!$y) || ($y[0]['site_dead'] == 0)) $synchubs[] = $x; } - if(! $synchubs) + if (!$synchubs) return; - $env_recips = [ $channel['channel_hash'] ]; + $env_recips = [$channel['channel_hash']]; - if($packet) - logger('packet: ' . print_r($packet, true),LOGGER_DATA, LOG_DEBUG); + if ($packet) + logger('packet: ' . print_r($packet, true), LOGGER_DATA, LOG_DEBUG); - $info = (($packet) ? $packet : array()); - $info['type'] = 'sync'; + $info = (($packet) ? $packet : []); + $info['type'] = 'sync'; $info['encoding'] = 'hz'; // note: not zot, this packet is very platform specific - $info['relocate'] = ['channel_address' => $channel['channel_address'], 'url' => z_root() ]; + $info['relocate'] = ['channel_address' => $channel['channel_address'], 'url' => z_root()]; - if(array_key_exists($uid,\App::$config) && array_key_exists('transient',\App::$config[$uid])) { - $settings = \App::$config[$uid]['transient']; - if($settings) { + if (array_key_exists($uid, App::$config) && array_key_exists('transient', App::$config[$uid])) { + $settings = App::$config[$uid]['transient']; + if ($settings) { $info['config'] = $settings; } } - if($channel) { - $info['channel'] = array(); - foreach($channel as $k => $v) { + if ($channel) { + $info['channel'] = []; + foreach ($channel as $k => $v) { // filter out any joined tables like xchan - if(strpos($k,'channel_') !== 0) + if (strpos($k, 'channel_') !== 0) continue; // don't pass these elements, they should not be synchronised $disallowed = [ - 'channel_id','channel_account_id','channel_primary','channel_address', - 'channel_deleted','channel_removed','channel_system' + 'channel_id', 'channel_account_id', 'channel_primary', 'channel_address', + 'channel_deleted', 'channel_removed', 'channel_system' ]; - if(! $keychange) { + if (!$keychange) { $disallowed[] = 'channel_prvkey'; } - if(in_array($k,$disallowed)) + if (in_array($k, $disallowed)) continue; $info['channel'][$k] = $v; } } - if($groups_changed) { + if ($groups_changed) { $r = q("select hash as collection, visible, deleted, gname as name from pgrp where uid = %d", intval($uid) ); - if($r) + if ($r) $info['collections'] = $r; $r = q("select pgrp.hash as collection, pgrp_member.xchan as member from pgrp left join pgrp_member on pgrp.id = pgrp_member.gid where pgrp_member.uid = %d", intval($uid) ); - if($r) + if ($r) $info['collection_members'] = $r; } - $interval = ((get_config('system','delivery_interval') !== false) - ? intval(get_config('system','delivery_interval')) : 2 ); + $interval = ((get_config('system', 'delivery_interval') !== false) + ? intval(get_config('system', 'delivery_interval')) : 2); - logger('Packet: ' . print_r($info,true), LOGGER_DATA, LOG_DEBUG); + logger('Packet: ' . print_r($info, true), LOGGER_DATA, LOG_DEBUG); $total = count($synchubs); - - foreach($synchubs as $hub) { + foreach ($synchubs as $hub) { $hash = random_string(); - $n = Libzot::build_packet($channel,'sync',$env_recips,json_encode($info),'hz',$hub['hubloc_sitekey'],$hub['site_crypto']); - Queue::insert(array( + $n = Libzot::build_packet($channel, 'sync', $env_recips, json_encode($info), 'hz', $hub['hubloc_sitekey'], $hub['site_crypto']); + Queue::insert([ 'hash' => $hash, 'account_id' => $channel['channel_account_id'], 'channel_id' => $channel['channel_id'], @@ -153,29 +152,29 @@ class Libsync { 'driver' => $hub['hubloc_network'], 'notify' => $n, 'msg' => EMPTY_STR - )); + ]); $x = q("select count(outq_hash) as total from outq where outq_delivered = 0"); - if(intval($x[0]['total']) > intval(get_config('system','force_queue_threshold',3000))) { + if (intval($x[0]['total']) > intval(get_config('system', 'force_queue_threshold', 3000))) { logger('immediate delivery deferred.', LOGGER_DEBUG, LOG_INFO); Queue::update($hash); continue; } - \Zotlabs\Daemon\Master::Summon(array('Deliver', $hash)); + Master::Summon(['Deliver', $hash]); $total = $total - 1; - if($interval && $total) - @time_sleep_until(microtime(true) + (float) $interval); + if ($interval && $total) + @time_sleep_until(microtime(true) + (float)$interval); } } /** * @brief * - * @param array $sender + * @param string $sender * @param array $arr * @param array $deliveries * @return array @@ -186,17 +185,16 @@ class Libsync { require_once('include/import.php'); $result = []; - - $keychange = ((array_key_exists('keychange',$arr)) ? true : false); + $keychange = ((array_key_exists('keychange', $arr)) ? true : false); foreach ($deliveries as $d) { $r = q("select * from channel where channel_hash = '%s' limit 1", dbesc($sender) ); - $DR = new \Zotlabs\Lib\DReport(z_root(),$sender,$d,'sync'); + $DR = new DReport(z_root(), $sender, $d, 'sync'); - if (! $r) { + if (!$r) { $DR->update('recipient not found'); $result[] = $DR->get(); continue; @@ -206,153 +204,153 @@ class Libsync { $DR->set_name($channel['channel_name'] . ' <' . channel_reddress($channel) . '>'); - $max_friends = service_class_fetch($channel['channel_id'],'total_channels'); - $max_feeds = account_service_class_fetch($channel['channel_account_id'],'total_feeds'); + $max_friends = service_class_fetch($channel['channel_id'], 'total_channels'); + $max_feeds = account_service_class_fetch($channel['channel_account_id'], 'total_feeds'); - if($channel['channel_hash'] != $sender) { + if ($channel['channel_hash'] != $sender) { logger('Possible forgery. Sender ' . $sender . ' is not ' . $channel['channel_hash']); $DR->update('channel mismatch'); $result[] = $DR->get(); continue; } - if($keychange) { - self::keychange($channel,$arr); + if ($keychange) { + self::keychange($channel, $arr); continue; } // if the clone is active, so are we - if(substr($channel['channel_active'],0,10) !== substr(datetime_convert(),0,10)) { + if (substr($channel['channel_active'], 0, 10) !== substr(datetime_convert(), 0, 10)) { q("UPDATE channel set channel_active = '%s' where channel_id = %d", dbesc(datetime_convert()), intval($channel['channel_id']) ); } - if(array_key_exists('config',$arr) && is_array($arr['config']) && count($arr['config'])) { - foreach($arr['config'] as $cat => $k) { - foreach($arr['config'][$cat] as $k => $v) - set_pconfig($channel['channel_id'],$cat,$k,$v); + if (array_key_exists('config', $arr) && is_array($arr['config']) && count($arr['config'])) { + foreach ($arr['config'] as $cat => $k) { + foreach ($arr['config'][$cat] as $k => $v) + set_pconfig($channel['channel_id'], $cat, $k, $v); } } - if(array_key_exists('obj',$arr) && $arr['obj']) - sync_objs($channel,$arr['obj']); + if (array_key_exists('obj', $arr) && $arr['obj']) + sync_objs($channel, $arr['obj']); + + if (array_key_exists('likes', $arr) && $arr['likes']) + import_likes($channel, $arr['likes']); + + if (array_key_exists('app', $arr) && $arr['app']) + sync_apps($channel, $arr['app']); + + if (array_key_exists('sysapp',$arr) && $arr['sysapp']) { + sync_sysapps($channel, $arr['sysapp']); + } - if(array_key_exists('likes',$arr) && $arr['likes']) - import_likes($channel,$arr['likes']); + if (array_key_exists('addressbook', $arr) && $arr['addressbook']) + sync_addressbook($channel, $arr['addressbook']); - 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('calendar',$arr) && $arr['calendar']) - sync_calendar($channel,$arr['calendar']); + if (array_key_exists('chatroom', $arr) && $arr['chatroom']) + sync_chatrooms($channel, $arr['chatroom']); - if(array_key_exists('chatroom',$arr) && $arr['chatroom']) - sync_chatrooms($channel,$arr['chatroom']); + //if (array_key_exists('mail', $arr) && $arr['mail']) + // sync_mail($channel, $arr['mail']); - if(array_key_exists('conv',$arr) && $arr['conv']) - import_conv($channel,$arr['conv']); + if (array_key_exists('event', $arr) && $arr['event']) + sync_events($channel, $arr['event']); - if(array_key_exists('mail',$arr) && $arr['mail']) - sync_mail($channel,$arr['mail']); - - if(array_key_exists('event',$arr) && $arr['event']) - sync_events($channel,$arr['event']); + if (array_key_exists('event_item', $arr) && $arr['event_item']) + sync_items($channel, $arr['event_item'], ((array_key_exists('relocate', $arr)) ? $arr['relocate'] : null)); - if(array_key_exists('event_item',$arr) && $arr['event_item']) - sync_items($channel,$arr['event_item'],((array_key_exists('relocate',$arr)) ? $arr['relocate'] : null)); + if (array_key_exists('item', $arr) && $arr['item']) + sync_items($channel, $arr['item'], ((array_key_exists('relocate', $arr)) ? $arr['relocate'] : null)); - if(array_key_exists('item',$arr) && $arr['item']) - sync_items($channel,$arr['item'],((array_key_exists('relocate',$arr)) ? $arr['relocate'] : null)); - // deprecated, maintaining for a few months for upward compatibility // this should sync webpages, but the logic is a bit subtle - if(array_key_exists('item_id',$arr) && $arr['item_id']) - sync_items($channel,$arr['item_id']); + //if (array_key_exists('item_id', $arr) && $arr['item_id']) + // sync_items($channel, $arr['item_id']); - if(array_key_exists('menu',$arr) && $arr['menu']) - sync_menus($channel,$arr['menu']); - - if(array_key_exists('file',$arr) && $arr['file']) - sync_files($channel,$arr['file']); + if (array_key_exists('menu', $arr) && $arr['menu']) + sync_menus($channel, $arr['menu']); - if(array_key_exists('wiki',$arr) && $arr['wiki']) - sync_items($channel,$arr['wiki'],((array_key_exists('relocate',$arr)) ? $arr['relocate'] : null)); + if (array_key_exists('file', $arr) && $arr['file']) + sync_files($channel, $arr['file']); - if(array_key_exists('channel',$arr) && is_array($arr['channel']) && count($arr['channel'])) { + if (array_key_exists('wiki', $arr) && $arr['wiki']) + sync_items($channel, $arr['wiki'], ((array_key_exists('relocate', $arr)) ? $arr['relocate'] : null)); - $remote_channel = $arr['channel']; + if (array_key_exists('channel', $arr) && is_array($arr['channel']) && count($arr['channel'])) { + + $remote_channel = $arr['channel']; $remote_channel['channel_id'] = $channel['channel_id']; - if(array_key_exists('channel_pageflags',$arr['channel']) && intval($arr['channel']['channel_pageflags'])) { + if (array_key_exists('channel_pageflags', $arr['channel']) && intval($arr['channel']['channel_pageflags'])) { // Several pageflags are site-specific and cannot be sync'd. - // Only allow those bits which are shareable from the remote and then + // Only allow those bits which are shareable from the remote and then // logically OR with the local flags - $arr['channel']['channel_pageflags'] = $arr['channel']['channel_pageflags'] & (PAGE_HIDDEN|PAGE_AUTOCONNECT|PAGE_APPLICATION|PAGE_PREMIUM|PAGE_ADULT); + $arr['channel']['channel_pageflags'] = $arr['channel']['channel_pageflags'] & (PAGE_HIDDEN | PAGE_AUTOCONNECT | PAGE_APPLICATION | PAGE_PREMIUM | PAGE_ADULT); $arr['channel']['channel_pageflags'] = $arr['channel']['channel_pageflags'] | $channel['channel_pageflags']; } $disallowed = [ - 'channel_id', 'channel_account_id', 'channel_primary', 'channel_prvkey', - 'channel_address', 'channel_notifyflags', 'channel_removed', 'channel_deleted', - 'channel_system', 'channel_r_stream', 'channel_r_profile', 'channel_r_abook', - 'channel_r_storage', 'channel_r_pages', 'channel_w_stream', 'channel_w_wall', - 'channel_w_comment', 'channel_w_mail', 'channel_w_like', 'channel_w_tagwall', - 'channel_w_chat', 'channel_w_storage', 'channel_w_pages', 'channel_a_republish', + 'channel_id', 'channel_account_id', 'channel_primary', 'channel_prvkey', + 'channel_address', 'channel_notifyflags', 'channel_removed', 'channel_deleted', + 'channel_system', 'channel_r_stream', 'channel_r_profile', 'channel_r_abook', + 'channel_r_storage', 'channel_r_pages', 'channel_w_stream', 'channel_w_wall', + 'channel_w_comment', 'channel_w_mail', 'channel_w_like', 'channel_w_tagwall', + 'channel_w_chat', 'channel_w_storage', 'channel_w_pages', 'channel_a_republish', 'channel_a_delegate' ]; - $clean = array(); - foreach($arr['channel'] as $k => $v) { - if(in_array($k,$disallowed)) + $clean = []; + foreach ($arr['channel'] as $k => $v) { + if (in_array($k, $disallowed)) continue; $clean[$k] = $v; } - if(count($clean)) { - foreach($clean as $k => $v) { - $r = dbq("UPDATE channel set " . dbesc($k) . " = '" . dbesc($v) - . "' where channel_id = " . intval($channel['channel_id']) ); + if (count($clean)) { + foreach ($clean as $k => $v) { + dbq("UPDATE channel set " . dbesc($k) . " = '" . dbesc($v) . "' where channel_id = " . intval($channel['channel_id'])); } } } - if(array_key_exists('abook',$arr) && is_array($arr['abook']) && count($arr['abook'])) { + if (array_key_exists('abook', $arr) && is_array($arr['abook']) && count($arr['abook'])) { $total_friends = 0; - $total_feeds = 0; + $total_feeds = 0; $r = q("select abook_id, abook_feed from abook where abook_channel = %d", intval($channel['channel_id']) ); - if($r) { + if ($r) { // don't count yourself $total_friends = ((count($r) > 0) ? count($r) - 1 : 0); - foreach($r as $rr) - if(intval($rr['abook_feed'])) - $total_feeds ++; + foreach ($r as $rr) + if (intval($rr['abook_feed'])) + $total_feeds++; } - $disallowed = array('abook_id','abook_account','abook_channel','abook_rating','abook_rating_text','abook_not_here'); + $disallowed = ['abook_id', 'abook_account', 'abook_channel', 'abook_rating', 'abook_rating_text', 'abook_not_here']; $fields = db_columns('abook'); - foreach($arr['abook'] as $abook) { + foreach ($arr['abook'] as $abook) { $abconfig = null; - if(array_key_exists('abconfig',$abook) && is_array($abook['abconfig']) && count($abook['abconfig'])) + if (array_key_exists('abconfig', $abook) && is_array($abook['abconfig']) && count($abook['abconfig'])) $abconfig = $abook['abconfig']; - if(! array_key_exists('abook_blocked',$abook)) { + if (!array_key_exists('abook_blocked', $abook)) { // convert from redmatrix $abook['abook_blocked'] = (($abook['abook_flags'] & 0x0001) ? 1 : 0); $abook['abook_ignored'] = (($abook['abook_flags'] & 0x0002) ? 1 : 0); @@ -364,20 +362,20 @@ class Libsync { $abook['abook_feed'] = (($abook['abook_flags'] & 0x0100) ? 1 : 0); } - $clean = array(); - if($abook['abook_xchan'] && $abook['entry_deleted']) { + $clean = []; + if ($abook['abook_xchan'] && $abook['entry_deleted']) { logger('Removing abook entry for ' . $abook['abook_xchan']); $r = q("select abook_id, abook_feed from abook where abook_xchan = '%s' and abook_channel = %d and abook_self = 0 limit 1", dbesc($abook['abook_xchan']), intval($channel['channel_id']) ); - if($r) { - contact_remove($channel['channel_id'],$r[0]['abook_id']); - if($total_friends) - $total_friends --; - if(intval($r[0]['abook_feed'])) - $total_feeds --; + if ($r) { + contact_remove($channel['channel_id'], $r[0]['abook_id']); + if ($total_friends) + $total_friends--; + if (intval($r[0]['abook_feed'])) + $total_feeds--; } continue; } @@ -386,31 +384,31 @@ class Libsync { // This relies on the undocumented behaviour that red sites send xchan info with the abook // and import_author_xchan will look them up on all federated networks - if($abook['abook_xchan'] && $abook['xchan_addr']) { + if ($abook['abook_xchan'] && $abook['xchan_addr']) { $h = Libzot::get_hublocs($abook['abook_xchan']); - if(! $h) { + if (!$h) { $xhash = import_author_xchan(encode_item_xchan($abook)); - if(! $xhash) { + if (!$xhash) { logger('Import of ' . $abook['xchan_addr'] . ' failed.'); continue; } } } - foreach($abook as $k => $v) { - if(in_array($k,$disallowed) || (strpos($k,'abook') !== 0)) { + foreach ($abook as $k => $v) { + if (in_array($k, $disallowed) || (strpos($k, 'abook') !== 0)) { continue; } - if(! in_array($k,$fields)) { + if (!in_array($k, $fields)) { continue; } $clean[$k] = $v; } - if(! array_key_exists('abook_xchan',$clean)) + if (!array_key_exists('abook_xchan', $clean)) continue; - if(array_key_exists('abook_instance',$clean) && $clean['abook_instance'] && strpos($clean['abook_instance'],z_root()) === false) { + if (array_key_exists('abook_instance', $clean) && $clean['abook_instance'] && strpos($clean['abook_instance'], z_root()) === false) { $clean['abook_not_here'] = 1; } @@ -422,12 +420,12 @@ class Libsync { // make sure we have an abook entry for this xchan on this system - if(! $r) { - if($max_friends !== false && $total_friends > $max_friends) { + if (!$r) { + if ($max_friends !== false && $total_friends > $max_friends) { logger('total_channels service class limit exceeded'); continue; } - if($max_feeds !== false && intval($clean['abook_feed']) && $total_feeds > $max_feeds) { + if ($max_feeds !== false && intval($clean['abook_feed']) && $total_feeds > $max_feeds) { logger('total_feeds service class limit exceeded'); continue; } @@ -438,18 +436,16 @@ class Libsync { 'abook_channel' => $channel['channel_id'] ] ); - $total_friends ++; - if(intval($clean['abook_feed'])) - $total_feeds ++; + $total_friends++; + if (intval($clean['abook_feed'])) + $total_feeds++; } - if(count($clean)) { - foreach($clean as $k => $v) { - if($k == 'abook_dob') + if (count($clean)) { + foreach ($clean as $k => $v) { + if ($k == 'abook_dob') $v = dbescdate($v); - - $r = dbq("UPDATE abook set " . dbesc($k) . " = '" . dbesc($v) - . "' where abook_xchan = '" . dbesc($clean['abook_xchan']) . "' and abook_channel = " . intval($channel['channel_id'])); + dbq("UPDATE abook set " . dbesc($k) . " = '" . dbesc($v) . "' where abook_xchan = '" . dbesc($clean['abook_xchan']) . "' and abook_channel = " . intval($channel['channel_id'])); } } @@ -459,10 +455,10 @@ class Libsync { // translate_abook_perms_inbound($channel,$abook); - if($abconfig) { + if ($abconfig) { /// @fixme does not handle sync of del_abconfig - foreach($abconfig as $abc) { - set_abconfig($channel['channel_id'],$abc['xchan'],$abc['cat'],$abc['k'],$abc['v']); + foreach ($abconfig as $abc) { + set_abconfig($channel['channel_id'], $abc['xchan'], $abc['cat'], $abc['k'], $abc['v']); } } } @@ -470,21 +466,21 @@ class Libsync { // sync collections (privacy groups) oh joy... - if(array_key_exists('collections',$arr) && is_array($arr['collections']) && count($arr['collections'])) { + if (array_key_exists('collections', $arr) && is_array($arr['collections']) && count($arr['collections'])) { $x = q("select * from pgrp where uid = %d", intval($channel['channel_id']) ); - foreach($arr['collections'] as $cl) { + foreach ($arr['collections'] as $cl) { $found = false; - if($x) { - foreach($x as $y) { - if($cl['collection'] == $y['hash']) { + if ($x) { + foreach ($x as $y) { + if ($cl['collection'] == $y['hash']) { $found = true; break; } } - if($found) { - if(($y['gname'] != $cl['name']) + if ($found) { + if (($y['gname'] != $cl['name']) || ($y['visible'] != $cl['visible']) || ($y['deleted'] != $cl['deleted'])) { q("update pgrp set gname = '%s', visible = %d, deleted = %d where hash = '%s' and uid = %d", @@ -495,15 +491,15 @@ class Libsync { intval($channel['channel_id']) ); } - if(intval($cl['deleted']) && (! intval($y['deleted']))) { + if (intval($cl['deleted']) && (!intval($y['deleted']))) { q("delete from pgrp_member where gid = %d", intval($y['id']) ); } } } - if(! $found) { - $r = q("INSERT INTO pgrp ( hash, uid, visible, deleted, gname ) + if (!$found) { + q("INSERT INTO pgrp ( hash, uid, visible, deleted, gname ) VALUES( '%s', %d, %d, %d, '%s' ) ", dbesc($cl['collection']), intval($channel['channel_id']), @@ -517,16 +513,16 @@ class Libsync { // They need to be removed by marking deleted and removing the members. // This shouldn't happen except for clones created before this function was written. - if($x) { + if ($x) { $found_local = false; - foreach($x as $y) { - foreach($arr['collections'] as $cl) { - if($cl['collection'] == $y['hash']) { + foreach ($x as $y) { + foreach ($arr['collections'] as $cl) { + if ($cl['collection'] == $y['hash']) { $found_local = true; break; } } - if(! $found_local) { + if (!$found_local) { q("delete from pgrp_member where gid = %d", intval($y['id']) ); @@ -546,38 +542,38 @@ class Libsync { // now sync the members - if(array_key_exists('collection_members', $arr) + if (array_key_exists('collection_members', $arr) && is_array($arr['collection_members']) && count($arr['collection_members'])) { // first sort into groups keyed by the group hash - $members = array(); - foreach($arr['collection_members'] as $cm) { - if(! array_key_exists($cm['collection'],$members)) - $members[$cm['collection']] = array(); + $members = []; + foreach ($arr['collection_members'] as $cm) { + if (!array_key_exists($cm['collection'], $members)) + $members[$cm['collection']] = []; $members[$cm['collection']][] = $cm['member']; } // our group list is already synchronised - if($x) { - foreach($x as $y) { - + if ($x) { + foreach ($x as $y) { + // for each group, loop on members list we just received - if(isset($y['hash']) && isset($members[$y['hash']])) { - foreach($members[$y['hash']] as $member) { + if (isset($y['hash']) && isset($members[$y['hash']])) { + foreach ($members[$y['hash']] as $member) { $found = false; - $z = q("select xchan from pgrp_member where gid = %d and uid = %d and xchan = '%s' limit 1", + $z = q("select xchan from pgrp_member where gid = %d and uid = %d and xchan = '%s' limit 1", intval($y['id']), intval($channel['channel_id']), dbesc($member) ); - if($z) + if ($z) $found = true; - + // if somebody is in the group that wasn't before - add them - - if(! $found) { + + if (!$found) { q("INSERT INTO pgrp_member (uid, gid, xchan) VALUES( %d, %d, '%s' ) ", intval($channel['channel_id']), @@ -587,16 +583,16 @@ class Libsync { } } } - + // now retrieve a list of members we have on this site $m = q("select xchan from pgrp_member where gid = %d and uid = %d", intval($y['id']), intval($channel['channel_id']) ); - if($m) { - foreach($m as $mm) { + if ($m) { + foreach ($m as $mm) { // if the local existing member isn't in the list we just received - remove them - if(! in_array($mm['xchan'],$members[$y['hash']])) { + if (!in_array($mm['xchan'], $members[$y['hash']])) { q("delete from pgrp_member where xchan = '%s' and gid = %d and uid = %d", dbesc($mm['xchan']), intval($y['id']), @@ -610,17 +606,17 @@ class Libsync { } } - if(array_key_exists('profile',$arr) && is_array($arr['profile']) && count($arr['profile'])) { + if (array_key_exists('profile', $arr) && is_array($arr['profile']) && count($arr['profile'])) { - $disallowed = array('id','aid','uid','guid'); + $disallowed = ['id', 'aid', 'uid', 'guid']; + + foreach ($arr['profile'] as $profile) { - foreach($arr['profile'] as $profile) { - $x = q("select * from profile where profile_guid = '%s' and uid = %d limit 1", dbesc($profile['profile_guid']), intval($channel['channel_id']) ); - if(! $x) { + if (!$x) { profile_store_lowlevel( [ 'aid' => $channel['channel_account_id'], @@ -628,29 +624,29 @@ class Libsync { 'profile_guid' => $profile['profile_guid'], ] ); - + $x = q("select * from profile where profile_guid = '%s' and uid = %d limit 1", dbesc($profile['profile_guid']), intval($channel['channel_id']) ); - if(! $x) + if (!$x) continue; } - $clean = array(); - foreach($profile as $k => $v) { - if(in_array($k,$disallowed)) + $clean = []; + foreach ($profile as $k => $v) { + if (in_array($k, $disallowed)) continue; - if($profile['is_default'] && in_array($k,['photo','thumb'])) + if ($profile['is_default'] && in_array($k, ['photo', 'thumb'])) continue; - if($k === 'name') + if ($k === 'name') $clean['fullname'] = $v; - elseif($k === 'with') + elseif ($k === 'with') $clean['partner'] = $v; - elseif($k === 'work') + elseif ($k === 'work') $clean['employment'] = $v; - elseif(array_key_exists($k,$x[0])) + elseif (array_key_exists($k, $x[0])) $clean[$k] = $v; /** @@ -658,7 +654,7 @@ class Libsync { * We also need to import local photos if a custom photo is selected */ - if((strpos($profile['thumb'],'/photo/profile/l/') !== false) || intval($profile['is_default'])) { + if ((strpos($profile['thumb'], '/photo/profile/l/') !== false) || intval($profile['is_default'])) { $profile['photo'] = z_root() . '/photo/profile/l/' . $channel['channel_id']; $profile['thumb'] = z_root() . '/photo/profile/m/' . $channel['channel_id']; } @@ -668,11 +664,11 @@ class Libsync { } } - if(count($clean)) { - foreach($clean as $k => $v) { - $r = dbq("UPDATE profile set " . TQUOT . dbesc($k) . TQUOT . " = '" . dbesc($v) - . "' where profile_guid = '" . dbesc($profile['profile_guid']) - . "' and uid = " . intval($channel['channel_id'])); + if (count($clean)) { + foreach ($clean as $k => $v) { + dbq("UPDATE profile set " . TQUOT . dbesc($k) . TQUOT . " = '" . dbesc($v) + . "' where profile_guid = '" . dbesc($profile['profile_guid']) + . "' and uid = " . intval($channel['channel_id'])); } } } @@ -687,7 +683,7 @@ class Libsync { */ call_hooks('process_channel_sync_delivery', $addon); - $DR = new \Zotlabs\Lib\DReport(z_root(),$d,$d,'sync','channel sync delivered'); + $DR = new DReport(z_root(), $d, $d, 'sync', 'channel sync delivered'); $DR->set_name($channel['channel_name'] . ' <' . channel_reddress($channel) . '>'); @@ -708,22 +704,34 @@ class Libsync { static function sync_locations($sender, $arr, $absolute = false) { - $ret = array(); + $ret = []; + + // If a sender reports that the channel has been deleted, delete its hubloc + if (isset($arr['deleted_locally']) && intval($arr['deleted_locally'])) { + q("UPDATE hubloc SET hubloc_deleted = 1, hubloc_updated = '%s' WHERE hubloc_hash = '%s' AND hubloc_url = '%s'", + dbesc(datetime_convert()), + dbesc($sender['hash']), + dbesc($sender['site']['url']) + ); + } - if($arr['locations']) { + if ($arr['locations']) { - if($absolute) - self::check_location_move($sender['hash'],$arr['locations']); + if ($absolute) + Libzot::check_location_move($sender['hash'], $arr['locations']); $xisting = q("select * from hubloc where hubloc_hash = '%s'", dbesc($sender['hash']) ); + if(!$xisting) + $xisting = []; + // See if a primary is specified $has_primary = false; - foreach($arr['locations'] as $location) { - if($location['primary']) { + foreach ($arr['locations'] as $location) { + if ($location['primary']) { $has_primary = true; break; } @@ -731,32 +739,32 @@ class Libsync { // Ensure that they have one primary hub - if(! $has_primary) + if (!$has_primary) $arr['locations'][0]['primary'] = true; - foreach($arr['locations'] as $location) { - if(! Libzot::verify($location['url'],$location['url_sig'],$sender['public_key'])) { + foreach ($arr['locations'] as $location) { + if (!Libzot::verify($location['url'], $location['url_sig'], $sender['public_key'])) { logger('Unable to verify site signature for ' . $location['url']); - $ret['message'] .= sprintf( t('Unable to verify site signature for %s'), $location['url']) . EOL; + $ret['message'] .= sprintf(t('Unable to verify site signature for %s'), $location['url']) . EOL; continue; } - for($x = 0; $x < count($xisting); $x ++) { - if(($xisting[$x]['hubloc_url'] === $location['url']) + for ($x = 0; $x < count($xisting); $x++) { + if (($xisting[$x]['hubloc_url'] === $location['url']) && ($xisting[$x]['hubloc_sitekey'] === $location['sitekey'])) { $xisting[$x]['updated'] = true; } } - if(! $location['sitekey']) { - logger('Empty hubloc sitekey. ' . print_r($location,true)); + if (!$location['sitekey']) { + logger('Empty hubloc sitekey. ' . print_r($location, true)); continue; } // Catch some malformed entries from the past which still exist - if(strpos($location['address'],'/') !== false) - $location['address'] = substr($location['address'],0,strpos($location['address'],'/')); + if (strpos($location['address'], '/') !== false) + $location['address'] = substr($location['address'], 0, strpos($location['address'], '/')); // match as many fields as possible in case anything at all changed. @@ -773,18 +781,18 @@ class Libsync { dbesc($location['callback']), dbesc($location['sitekey']) ); - if($r) { + if ($r) { logger('Hub exists: ' . $location['url'], LOGGER_DEBUG); - + // update connection timestamp if this is the site we're talking to // This only happens when called from import_xchan $current_site = false; - $t = datetime_convert('UTC','UTC','now - 15 minutes'); - - if(array_key_exists('site',$arr) && $location['url'] == $arr['site']['url']) { - q("update hubloc set hubloc_connected = '%s', hubloc_updated = '%s' where hubloc_id = %d and hubloc_connected < '%s'", + $t = datetime_convert('UTC', 'UTC', 'now - 15 minutes'); + + if (array_key_exists('site', $arr) && $location['url'] == $arr['site']['url']) { + q("update hubloc set hubloc_connected = '%s', hubloc_updated = '%s' where hubloc_id = %d and hubloc_updated < '%s'", dbesc(datetime_convert()), dbesc(datetime_convert()), intval($r[0]['hubloc_id']), @@ -793,11 +801,11 @@ class Libsync { $current_site = true; } - if($current_site && intval($r[0]['hubloc_error'])) { + if ($current_site && intval($r[0]['hubloc_error'])) { q("update hubloc set hubloc_error = 0 where hubloc_id = %d", intval($r[0]['hubloc_id']) ); - if(intval($r[0]['hubloc_orphancheck'])) { + if (intval($r[0]['hubloc_orphancheck'])) { q("update hubloc set hubloc_orphancheck = 0 where hubloc_id = %d", intval($r[0]['hubloc_id']) ); @@ -808,70 +816,71 @@ class Libsync { } // Remove pure duplicates - if(count($r) > 1) { - for($h = 1; $h < count($r); $h ++) { + if (count($r) > 1) { + for ($h = 1; $h < count($r); $h++) { q("delete from hubloc where hubloc_id = %d", intval($r[$h]['hubloc_id']) ); - $what .= 'duplicate_hubloc_removed '; + $what .= 'duplicate_hubloc_removed '; $changed = true; } } - if(intval($r[0]['hubloc_primary']) && (! $location['primary'])) { - $m = q("update hubloc set hubloc_primary = 0, hubloc_updated = '%s' where hubloc_id_url = '%s'", + if (intval($r[0]['hubloc_primary']) && (!$location['primary'])) { + q("update hubloc set hubloc_primary = 0, hubloc_updated = '%s' where hubloc_id_url = '%s'", dbesc(datetime_convert()), dbesc($r[0]['hubloc_id_url']) ); $r[0]['hubloc_primary'] = intval($location['primary']); hubloc_change_primary($r[0]); - $what .= 'primary_hub '; + $what .= 'primary_hub '; $changed = true; } - elseif((! intval($r[0]['hubloc_primary'])) && ($location['primary'])) { - $m = q("update hubloc set hubloc_primary = 1, hubloc_updated = '%s' where hubloc_id = %d", + elseif ((!intval($r[0]['hubloc_primary'])) && ($location['primary'])) { + q("update hubloc set hubloc_primary = 1, hubloc_updated = '%s' where hubloc_id = %d", dbesc(datetime_convert()), intval($r[0]['hubloc_id']) ); // make sure hubloc_change_primary() has current data $r[0]['hubloc_primary'] = intval($location['primary']); hubloc_change_primary($r[0]); - $what .= 'primary_hub '; + $what .= 'primary_hub '; $changed = true; } - elseif($absolute) { + elseif ($absolute) { // Absolute sync - make sure the current primary is correctly reflected in the xchan $pr = hubloc_change_primary($r[0]); - if($pr) { - $what .= 'xchan_primary '; + if ($pr) { + $what .= 'xchan_primary '; $changed = true; } } - if(intval($r[0]['hubloc_deleted']) && (! intval($location['deleted']))) { - $n = q("update hubloc set hubloc_deleted = 0, hubloc_updated = '%s' where hubloc_id_url = '%s'", + if (intval($r[0]['hubloc_deleted']) && (!intval($location['deleted']))) { + q("update hubloc set hubloc_deleted = 0, hubloc_updated = '%s' where hubloc_id_url = '%s'", dbesc(datetime_convert()), dbesc($r[0]['hubloc_id_url']) ); - $what .= 'undelete_hub '; + $what .= 'undelete_hub '; $changed = true; } - elseif((! intval($r[0]['hubloc_deleted'])) && (intval($location['deleted']))) { + 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_url = '%s'", + q("update hubloc set hubloc_deleted = 1, hubloc_updated = '%s' where hubloc_id_url = '%s'", dbesc(datetime_convert()), dbesc($r[0]['hubloc_id_url']) ); - $what .= 'delete_hub '; + $what .= 'delete_hub '; $changed = true; } + continue; } // Existing hubs are dealt with. Now let's process any new ones. // New hub claiming to be primary. Make it so by removing any existing primaries. - if(intval($location['primary'])) { - $r = q("update hubloc set hubloc_primary = 0, hubloc_updated = '%s' where hubloc_hash = '%s' and hubloc_primary = 1", + if (intval($location['primary'])) { + q("update hubloc set hubloc_primary = 0, hubloc_updated = '%s' where hubloc_hash = '%s' and hubloc_primary = 1", dbesc(datetime_convert()), dbesc($sender['hash']) ); @@ -879,7 +888,7 @@ class Libsync { logger('New hub: ' . $location['url']); - $r = hubloc_store_lowlevel( + hubloc_store_lowlevel( [ 'hubloc_guid' => $sender['id'], 'hubloc_guid_sig' => $sender['id_sig'], @@ -890,7 +899,7 @@ class Libsync { 'hubloc_primary' => intval($location['primary']), 'hubloc_url' => $location['url'], 'hubloc_url_sig' => $location['url_sig'], - 'hubloc_site_id' => Libzot::make_xchan_hash($location['url'],$location['sitekey']), + 'hubloc_site_id' => Libzot::make_xchan_hash($location['url'], $location['sitekey']), 'hubloc_host' => $location['host'], 'hubloc_callback' => $location['callback'], 'hubloc_sitekey' => $location['sitekey'], @@ -899,30 +908,32 @@ class Libsync { ] ); - $what .= 'newhub '; + $what .= 'newhub '; $changed = true; - if($location['primary']) { - $r = q("select * from hubloc where hubloc_addr = '%s' and hubloc_sitekey = '%s' limit 1", + if ($location['primary']) { + $r = q("select * from hubloc where hubloc_addr = '%s' and hubloc_sitekey = '%s'", dbesc($location['address']), dbesc($location['sitekey']) ); - if($r) - hubloc_change_primary($r[0]); + if ($r) { + $r = Libzot::zot_record_preferred($r); + hubloc_change_primary($r); + } } } // get rid of any hubs we have for this channel which weren't reported. - if($absolute && $xisting) { - foreach($xisting as $x) { - if(! array_key_exists('updated',$x)) { + if ($absolute && $xisting) { + 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_url = '%s'", + q("update hubloc set hubloc_deleted = 1, hubloc_updated = '%s' where hubloc_id_url = '%s'", dbesc(datetime_convert()), dbesc($x['hubloc_id_url']) ); - $what .= 'removed_hub '; + $what .= 'removed_hub '; $changed = true; } } @@ -933,22 +944,22 @@ class Libsync { } $ret['change_message'] = $what; - $ret['changed'] = $changed; + $ret['changed'] = $changed; return $ret; } - static function keychange($channel,$arr) { + static function keychange($channel, $arr) { // verify the keychange operation - if(! Libzot::verify($arr['channel']['channel_pubkey'],$arr['keychange']['new_sig'],$channel['channel_prvkey'])) { + if (!Libzot::verify($arr['channel']['channel_pubkey'], $arr['keychange']['new_sig'], $channel['channel_prvkey'])) { logger('sync keychange: verification failed'); return; } - $sig = Libzot::sign($channel['channel_guid'],$arr['channel']['channel_prvkey']); - $hash = Libzot::make_xchan_hash($channel['channel_guid'],$arr['channel']['channel_pubkey']); + $sig = Libzot::sign($channel['channel_guid'], $arr['channel']['channel_prvkey']); + $hash = Libzot::make_xchan_hash($channel['channel_guid'], $arr['channel']['channel_pubkey']); $r = q("update channel set channel_prvkey = '%s', channel_pubkey = '%s', channel_guid_sig = '%s', @@ -959,16 +970,16 @@ class Libsync { dbesc($hash), intval($channel['channel_id']) ); - if(! $r) { + if (!$r) { logger('keychange sync: channel update failed'); return; - } + } $r = q("select * from channel where channel_id = %d", intval($channel['channel_id']) ); - if(! $r) { + if (!$r) { logger('keychange sync: channel retrieve failed'); return; } @@ -980,11 +991,11 @@ class Libsync { dbesc(z_root()) ); - if($h) { - foreach($h as $hv) { + if ($h) { + foreach ($h as $hv) { $hv['hubloc_guid_sig'] = $sig; $hv['hubloc_hash'] = $hash; - $hv['hubloc_url_sig'] = Libzot::sign(z_root(),$channel['channel_prvkey']); + $hv['hubloc_url_sig'] = Libzot::sign(z_root(), $channel['channel_prvkey']); hubloc_store_lowlevel($hv); } } @@ -997,12 +1008,12 @@ class Libsync { dbesc($hash) ); - if(($x) && (! $check)) { + if (($x) && (!$check)) { $oldxchan = $x[0]; - foreach($x as $xv) { - $xv['xchan_guid_sig'] = $sig; - $xv['xchan_hash'] = $hash; - $xv['xchan_pubkey'] = $channel['channel_pubkey']; + foreach ($x as $xv) { + $xv['xchan_guid_sig'] = $sig; + $xv['xchan_hash'] = $hash; + $xv['xchan_pubkey'] = $channel['channel_pubkey']; xchan_store_lowlevel($xv); $newxchan = $xv; } @@ -1012,14 +1023,14 @@ class Libsync { dbesc($arr['keychange']['old_hash']) ); - if($a) { + if ($a) { q("update abook set abook_xchan = '%s' where abook_id = %d", dbesc($hash), intval($a[0]['abook_id']) ); } - xchan_change_key($oldxchan,$newxchan,$arr['keychange']); + xchan_change_key($oldxchan, $newxchan, $arr['keychange']); } diff --git a/Zotlabs/Lib/Libzot.php b/Zotlabs/Lib/Libzot.php index f0fe3ab24..200a2c486 100644 --- a/Zotlabs/Lib/Libzot.php +++ b/Zotlabs/Lib/Libzot.php @@ -34,7 +34,7 @@ class Libzot { */ static function new_uid($channel_nick) { $rawstr = z_root() . '/' . $channel_nick . '.' . mt_rand(); - return(base64url_encode(hash('whirlpool', $rawstr, true), true)); + return (base64url_encode(hash('whirlpool', $rawstr, true), true)); } @@ -87,7 +87,7 @@ class Libzot { * packet type: one of 'ping', 'pickup', 'purge', 'refresh', 'keychange', 'force_refresh', 'notify', 'auth_check' * @param array $recipients * envelope recipients, array of portable_id's; empty for public posts - * @param string $msg + * @param array $msg * optional message * @param string $encoding * optional encoding, default 'activitystreams' @@ -98,15 +98,15 @@ class Libzot { * optional comma separated list of encryption methods @ref best_algorithm() * @returns string json encoded zot packet */ - static function build_packet($channel, $type = 'activity', $recipients = null, $msg = '', $encoding = 'activitystreams', $remote_key = null, $methods = '') { + static function build_packet($channel, $type = 'activity', $recipients = null, $msg = [], $encoding = 'activitystreams', $remote_key = null, $methods = '') { - $sig_method = get_config('system','signature_algorithm','sha256'); + $sig_method = get_config('system', 'signature_algorithm', 'sha256'); $data = [ 'type' => $type, 'encoding' => $encoding, 'sender' => $channel['channel_hash'], - 'site_id' => self::make_xchan_hash(z_root(), get_config('system','pubkey')), + 'site_id' => self::make_xchan_hash(z_root(), get_config('system', 'pubkey')), 'version' => System::get_zot_revision(), ]; @@ -116,8 +116,8 @@ class Libzot { if ($msg) { $actor = channel_url($channel); - if ($encoding === 'activitystreams' && array_key_exists('actor',$msg) && is_string($msg['actor']) && $actor === $msg['actor']) { - $msg = JSalmon::sign($msg,$actor,$channel['channel_prvkey']); + if ($encoding === 'activitystreams' && array_key_exists('actor', $msg) && is_string($msg['actor']) && $actor === $msg['actor']) { + $msg = JSalmon::sign($msg, $actor, $channel['channel_prvkey']); } $data['data'] = $msg; } @@ -125,12 +125,12 @@ class Libzot { unset($data['encoding']); } - logger('packet: ' . print_r($data,true), LOGGER_DATA, LOG_DEBUG); + logger('packet: ' . print_r($data, true), LOGGER_DATA, LOG_DEBUG); if ($remote_key) { $algorithm = self::best_algorithm($methods); if ($algorithm) { - $data = crypto_encapsulate(json_encode($data),$remote_key, $algorithm); + $data = Crypto::encapsulate(json_encode($data), $remote_key, $algorithm); } } @@ -143,14 +143,14 @@ class Libzot { * * @param string $methods * Comma separated list of encryption methods - * @return string first match from our site method preferences crypto_methods() array + * @return string first match from our site method preferences Crypto::methods() array * of a method which is common to both sites; or 'aes256cbc' if no matches are found. */ static function best_algorithm($methods) { $x = [ 'methods' => $methods, - 'result' => '' + 'result' => '' ]; /** @@ -161,18 +161,18 @@ class Libzot { */ call_hooks('zot_best_algorithm', $x); - if($x['result']) + if ($x['result']) return $x['result']; - if($methods) { + if ($methods) { $x = explode(',', $methods); - if($x) { - $y = crypto_methods(); - if($y) { - foreach($y as $yv) { + if ($x) { + $y = Crypto::methods(); + if ($y) { + foreach ($y as $yv) { $yv = trim($yv); - if(in_array($yv, $x)) { - return($yv); + if (in_array($yv, $x)) { + return ($yv); } } } @@ -186,17 +186,17 @@ class Libzot { /** * @brief Send a zot message. * - * @see z_post_url() - * * @param string $url - * @param array $data + * @param string $data * @param array $channel (required if using zot6 delivery) * @param array $crypto (required if encrypted httpsig, requires hubloc_sitekey and site_crypto elements) * @return array see z_post_url() for returned data format + * @see z_post_url() + * */ - static function zot($url, $data, $channel = null,$crypto = null) { + static function zot($url, $data, $channel = null, $crypto = null) { - if($channel) { + if ($channel) { $headers = [ 'X-Zot-Token' => random_string(), 'Digest' => HTTPSig::generate_digest_header($data), @@ -204,8 +204,8 @@ class Libzot { '(request-target)' => 'post ' . get_request_string($url) ]; - $h = HTTPSig::create_sig($headers,$channel['channel_prvkey'],channel_url($channel),false,'sha512', - (($crypto) ? [ 'key' => $crypto['hubloc_sitekey'], 'algorithm' => self::best_algorithm($crypto['site_crypto']) ] : false)); + $h = HTTPSig::create_sig($headers, $channel['channel_prvkey'], channel_url($channel), false, 'sha512', + (($crypto) ? ['key' => $crypto['hubloc_sitekey'], 'algorithm' => self::best_algorithm($crypto['site_crypto'])] : false)); } else { $h = []; @@ -213,7 +213,7 @@ class Libzot { $redirects = 0; - return z_post_url($url,$data,$redirects,((empty($h)) ? [] : [ 'headers' => $h ])); + return z_post_url($url, $data, $redirects, ((empty($h)) ? [] : ['headers' => $h])); } @@ -237,7 +237,7 @@ class Libzot { * * @param array $them => xchan structure of sender * @param array $channel => local channel structure of target recipient, required for "friending" operations - * @param array $force (optional) default false + * @param boolean $force (optional) default false * * @return boolean * * \b true if successful @@ -245,9 +245,9 @@ class Libzot { */ static function refresh($them, $channel = null, $force = false) { - logger('them: ' . print_r($them,true), LOGGER_DATA, LOG_DEBUG); + logger('them: ' . print_r($them, true), LOGGER_DATA, LOG_DEBUG); if ($channel) - logger('channel: ' . print_r($channel,true), LOGGER_DATA, LOG_DEBUG); + logger('channel: ' . print_r($channel, true), LOGGER_DATA, LOG_DEBUG); $url = null; @@ -261,12 +261,12 @@ class Libzot { // We'll order by reverse id to try and pick off the newest one first and hopefully end up with the // correct hubloc. If this doesn't work we may have to re-write this section to try them all. - if(array_key_exists('xchan_addr',$them) && $them['xchan_addr']) { + if (array_key_exists('xchan_addr', $them) && $them['xchan_addr']) { $r = q("select hubloc_id_url, hubloc_primary from hubloc where hubloc_addr = '%s' and hubloc_network = 'zot6' order by hubloc_id desc", dbesc($them['xchan_addr']) ); } - if(! $r) { + if (!$r && array_key_exists('xchan_hash', $them) && $them['xchan_hash']) { $r = q("select hubloc_id_url, hubloc_primary from hubloc where hubloc_hash = '%s' order by hubloc_id desc", dbesc($them['xchan_hash']) ); @@ -276,81 +276,84 @@ class Libzot { foreach ($r as $rr) { if (intval($rr['hubloc_primary'])) { $url = $rr['hubloc_id_url']; - $record = $rr; + break; } } - if (! $url) { + if (!$url) { $url = $r[0]['hubloc_id_url']; } } } - if (! $url) { + + if (!$url) { logger('zot_refresh: no url'); return false; } + $m = parse_url($url); + $site_url = unparse_url([ 'scheme' => $m['scheme'], 'host' => $m['host'] ]); + $s = q("select site_dead from site where site_url = '%s' limit 1", - dbesc($url) + dbesc($site_url) ); - if($s && intval($s[0]['site_dead']) && (! $force)) { + if ($s && intval($s[0]['site_dead']) && (!$force)) { logger('zot_refresh: site ' . $url . ' is marked dead and force flag is not set. Cancelling operation.'); return false; } - $record = Zotfinger::exec($url,$channel); + $record = Zotfinger::exec($url, $channel); // Check the HTTP signature - $hsig = $record['signature']; - if($hsig && $hsig['signer'] === $url && $hsig['header_valid'] === true && $hsig['content_valid'] === true) + if ($hsig && $hsig['signer'] === $url && $hsig['header_valid'] === true && $hsig['content_valid'] === true) { $hsig_valid = true; + } - if(! $hsig_valid) { - logger('http signature not valid: ' . print_r($hsig,true)); + if (!$hsig_valid) { + logger('http signature not valid: ' . print_r($hsig, true)); return false; } - - logger('zot-info: ' . print_r($record,true), LOGGER_DATA, LOG_DEBUG); + logger('zot-info: ' . print_r($record, true), LOGGER_DATA, LOG_DEBUG); $x = self::import_xchan($record['data'], (($force) ? UPDATE_FLAGS_FORCED : UPDATE_FLAGS_UPDATED)); - - if(! $x['success']) + if (!$x['success']) { return false; + } - if($channel && $record['data']['permissions']) { - $permissions = explode(',',$record['data']['permissions']); + 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'); + if ($permissions && is_array($permissions)) { + $old_read_stream_perm = get_abconfig($channel['channel_id'], $x['hash'], 'their_perms', 'view_stream'); $permissions = Permissions::FilledPerms($permissions); - foreach($permissions as $k => $v) { - set_abconfig($channel['channel_id'],$x['hash'],'their_perms',$k,$v); + foreach ($permissions as $k => $v) { + set_abconfig($channel['channel_id'], $x['hash'], 'their_perms', $k, $v); } } - if(array_key_exists('profile',$record['data']) && array_key_exists('next_birthday',$record['data']['profile'])) { - $next_birthday = datetime_convert('UTC','UTC',$record['data']['profile']['next_birthday']); + if (array_key_exists('profile', $record['data']) && array_key_exists('next_birthday', $record['data']['profile'])) { + $next_birthday = datetime_convert('UTC', 'UTC', $record['data']['profile']['next_birthday']); } else { $next_birthday = NULL_DATE; } - $profile_assign = get_pconfig($channel['channel_id'],'system','profile_assign',''); + $profile_assign = get_pconfig($channel['channel_id'], 'system', 'profile_assign', ''); // Keep original perms to check if we need to notify them - $previous_perms = get_all_perms($channel['channel_id'],$x['hash']); + $previous_perms = get_all_perms($channel['channel_id'], $x['hash']); $r = q("select * from abook where abook_xchan = '%s' and abook_channel = %d and abook_self = 0 limit 1", dbesc($x['hash']), intval($channel['channel_id']) ); - if($r) { + if ($r) { // connection exists @@ -358,8 +361,9 @@ class Libzot { // we have as we may have updated the year after sending a notification; and resetting // to the one we just received would cause us to create duplicated events. - if(substr($r[0]['abook_dob'],5) == substr($next_birthday,5)) + if (substr($r[0]['abook_dob'], 5) == substr($next_birthday, 5)) { $next_birthday = $r[0]['abook_dob']; + } $y = q("update abook set abook_dob = '%s' where abook_xchan = '%s' and abook_channel = %d @@ -369,12 +373,14 @@ class Libzot { intval($channel['channel_id']) ); - if(! $y) + if (!$y) { logger('abook update failed'); + } else { // if we were just granted read stream permission and didn't have it before, try to pull in some posts - if((! $old_read_stream_perm) && (intval($permissions['view_stream']))) - Master::Summon([ 'Onepoll', $r[0]['abook_id'] ]); + if (!$old_read_stream_perm && intval($permissions['view_stream'])) { + Master::Summon(['Onepoll', $r[0]['abook_id']]); + } } } else { @@ -386,13 +392,13 @@ class Libzot { // new connection - if($my_perms) { - foreach($my_perms as $k => $v) { - set_abconfig($channel['channel_id'],$x['hash'],'my_perms',$k,$v); + if ($my_perms) { + foreach ($my_perms as $k => $v) { + set_abconfig($channel['channel_id'], $x['hash'], 'my_perms', $k, $v); } } - $closeness = get_pconfig($channel['channel_id'],'system','new_abook_closeness',80); + $closeness = get_pconfig($channel['channel_id'], 'system', 'new_abook_closeness', 80); $y = abook_store_lowlevel( [ @@ -408,9 +414,9 @@ class Libzot { ] ); - if($y) { + if ($y) { logger("New introduction received for {$channel['channel_name']}"); - $new_perms = get_all_perms($channel['channel_id'],$x['hash'],false); + $new_perms = get_all_perms($channel['channel_id'], $x['hash'], false); // Send a clone sync packet and a permissions update if permissions have changed @@ -419,54 +425,63 @@ class Libzot { intval($channel['channel_id']) ); - if($new_connection) { - if(! Permissions::PermsCompare($new_perms,$previous_perms)) - Master::Summon([ 'Notifier', 'permissions_create', $new_connection[0]['abook_id'] ]); + if ($new_connection) { + if (!Permissions::PermsCompare($new_perms, $previous_perms)) { + Master::Summon(['Notifier', 'permission_create', $new_connection[0]['abook_id']]); + } + Enotify::submit( [ - 'type' => NOTIFY_INTRO, - 'from_xchan' => $x['hash'], - 'to_xchan' => $channel['channel_hash'], - 'link' => z_root() . '/connedit/' . $new_connection[0]['abook_id'] + 'type' => NOTIFY_INTRO, + 'from_xchan' => $x['hash'], + 'to_xchan' => $channel['channel_hash'], + 'link' => z_root() . '/connedit/' . $new_connection[0]['abook_id'] ] ); - if(intval($permissions['view_stream'])) { - if(intval(get_pconfig($channel['channel_id'],'perm_limits','send_stream') & PERMS_PENDING) - || (! intval($new_connection[0]['abook_pending']))) - Master::Summon([ 'Onepoll', $new_connection[0]['abook_id'] ]); + if (intval($permissions['view_stream'])) { + if (intval(get_pconfig($channel['channel_id'], 'perm_limits', 'send_stream') & PERMS_PENDING) + || (!intval($new_connection[0]['abook_pending']))) { + Master::Summon(['Onepoll', $new_connection[0]['abook_id']]); + } } - // If there is a default group for this channel, add this connection to it - // for pending connections this will happens at acceptance time. + // for pending connections this will happen at acceptance time. - if(! intval($new_connection[0]['abook_pending'])) { + if (!intval($new_connection[0]['abook_pending'])) { $default_group = $channel['channel_default_group']; - if($default_group) { - $g = Group::rec_byhash($channel['channel_id'],$default_group); - if($g) - Group::member_add($channel['channel_id'],'',$x['hash'],$g['id']); + + if ($default_group) { + $g = Group::rec_byhash($channel['channel_id'], $default_group); + + if ($g) { + Group::member_add($channel['channel_id'], '', $x['hash'], $g['id']); + } } } unset($new_connection[0]['abook_id']); unset($new_connection[0]['abook_account']); unset($new_connection[0]['abook_channel']); - $abconfig = load_abconfig($channel['channel_id'],$new_connection['abook_xchan']); - if($abconfig) + + $abconfig = load_abconfig($channel['channel_id'], $new_connection['abook_xchan']); + + if ($abconfig) { $new_connection['abconfig'] = $abconfig; + } + + Libsync::build_sync_packet($channel['channel_id'], ['abook' => $new_connection]); - Libsync::build_sync_packet($channel['channel_id'], array('abook' => $new_connection)); } } - } return true; } return false; } + /** * @brief Look up if channel is known and previously verified. * @@ -480,6 +495,7 @@ class Libzot { * * \e string \b id_sig => id signed with conversant's private key * * \e string \b location => URL of the origination hub of this communication * * \e string \b location_sig => URL signed with conversant's private key + * * \e string \b site_id => URL signed with conversant's private key * @param boolean $multiple (optional) default false * * @return array|null @@ -489,9 +505,9 @@ class Libzot { static function gethub($arr, $multiple = false) { - if($arr['id'] && $arr['id_sig'] && $arr['location'] && $arr['location_sig']) { + if ($arr['id'] && $arr['id_sig'] && $arr['location'] && $arr['location_sig'] && $arr['site_id']) { - if(! check_siteallowed($arr['location'])) { + if (!check_siteallowed($arr['location'])) { logger('blacklisted site: ' . $arr['location']); return null; } @@ -509,13 +525,13 @@ class Libzot { dbesc($arr['location_sig']), dbesc($arr['site_id']) ); - if($r) { + if ($r) { logger('Found', LOGGER_DEBUG); return (($multiple) ? $r : $r[0]); } + logger('Not found: ' . print_r($arr, true), LOGGER_DEBUG); } - logger('Not found: ' . print_r($arr,true), LOGGER_DEBUG); - + logger('Incomplete array: ' . print_r($arr, true), LOGGER_DEBUG); return false; } @@ -532,16 +548,16 @@ class Libzot { dbesc($sender), dbesc($site_id) ); - if(! $r) { + if (!$r) { return null; } - if(! check_siteallowed($r[0]['hubloc_url'])) { + if (!check_siteallowed($r[0]['hubloc_url'])) { logger('blacklisted site: ' . $r[0]['hubloc_url']); return null; } - if(! check_channelallowed($r[0]['hubloc_hash'])) { + if (!check_channelallowed($r[0]['hubloc_hash'])) { logger('blacklisted channel: ' . $r[0]['hubloc_hash']); return null; } @@ -567,9 +583,9 @@ class Libzot { $hsig_valid = false; - $result = [ 'success' => false ]; + $result = ['success' => false]; - if(! $id) { + if (!$id) { return $result; } @@ -578,16 +594,16 @@ class Libzot { // Check the HTTP signature $hsig = $record['signature']; - if($hsig['signer'] === $id && $hsig['header_valid'] === true && $hsig['content_valid'] === true) { + if ($hsig['signer'] === $id && $hsig['header_valid'] === true && $hsig['content_valid'] === true) { $hsig_valid = true; } - if(! $hsig_valid) { - logger('http signature not valid: ' . print_r($hsig,true)); + if (!$hsig_valid) { + logger('http signature not valid: ' . print_r($hsig, true)); return $result; } $c = self::import_xchan($record['data']); - if($c['success']) { + if ($c['success']) { $result['success'] = true; } else { @@ -617,7 +633,6 @@ class Libzot { */ static function import_xchan($arr, $ud_flags = UPDATE_FLAGS_UPDATED, $ud_arr = null) { - /** * @hooks import_xchan * Called when processing the result of zot_finger() to store the result @@ -625,26 +640,26 @@ class Libzot { */ call_hooks('import_xchan', $arr); - $ret = array('success' => false); - $dirmode = intval(get_config('system','directory_mode')); + $ret = ['success' => false]; + $dirmode = intval(get_config('system', 'directory_mode')); $changed = false; - $what = ''; + $what = ''; - if(! ($arr['id'] && $arr['id_sig'])) { - logger('No identity information provided. ' . print_r($arr,true)); + if (!($arr['id'] && $arr['id_sig'])) { + logger('No identity information provided. ' . print_r($arr, true)); return $ret; } - $xchan_hash = self::make_xchan_hash($arr['id'],$arr['public_key']); + $xchan_hash = self::make_xchan_hash($arr['id'], $arr['public_key']); $arr['hash'] = $xchan_hash; $import_photos = false; - $sig_methods = ((array_key_exists('signing',$arr) && is_array($arr['signing'])) ? $arr['signing'] : [ 'sha256' ]); - $verified = false; + $sig_methods = ((array_key_exists('signing', $arr) && is_array($arr['signing'])) ? $arr['signing'] : ['sha256']); + $verified = false; - if(! self::verify($arr['id'],$arr['id_sig'],$arr['public_key'])) { + if (!self::verify($arr['id'], $arr['id_sig'], $arr['public_key'])) { logger('Unable to verify channel signature for ' . $arr['address']); return $ret; } @@ -652,7 +667,7 @@ class Libzot { $verified = true; } - if(! $verified) { + if (!$verified) { $ret['message'] = t('Unable to verify channel signature'); return $ret; } @@ -663,40 +678,40 @@ class Libzot { dbesc($xchan_hash) ); - if(! array_key_exists('connect_url', $arr)) + if (!array_key_exists('connect_url', $arr)) $arr['connect_url'] = ''; - if($r) { - if($arr['photo'] && array_key_exists('updated',$arr['photo']) && $r[0]['xchan_photo_date'] != $arr['photo']['updated']) { + if ($r) { + + if ($arr['photo'] && array_key_exists('updated', $arr['photo']) && $arr['photo']['updated'] > $r[0]['xchan_photo_date']) $import_photos = true; - } // if we import an entry from a site that's not ours and either or both of us is off the grid - hide the entry. /** @TODO: check if we're the same directory realm, which would mean we are allowed to see it */ - $dirmode = get_config('system','directory_mode'); + $dirmode = get_config('system', 'directory_mode'); - if((($arr['site']['directory_mode'] === 'standalone') || ($dirmode & DIRECTORY_MODE_STANDALONE)) && ($arr['site']['url'] != z_root())) + if ((($arr['site']['directory_mode'] === 'standalone') || ($dirmode & DIRECTORY_MODE_STANDALONE)) && ($arr['site']['url'] != z_root())) $arr['searchable'] = false; $hidden = (1 - intval($arr['searchable'])); $hidden_changed = $adult_changed = $deleted_changed = $pubforum_changed = 0; - if(intval($r[0]['xchan_hidden']) != (1 - intval($arr['searchable']))) + if (intval($r[0]['xchan_hidden']) != (1 - intval($arr['searchable']))) $hidden_changed = 1; - if(intval($r[0]['xchan_selfcensored']) != intval($arr['adult_content'])) + if (intval($r[0]['xchan_selfcensored']) != intval($arr['adult_content'])) $adult_changed = 1; - if(intval($r[0]['xchan_deleted']) != intval($arr['deleted'])) + if (intval($r[0]['xchan_deleted']) != intval($arr['deleted'])) $deleted_changed = 1; // new style 6-MAR-2019 - if(array_key_exists('channel_type',$arr)) { - if($arr['channel_type'] === 'collection') { + if (array_key_exists('channel_type', $arr)) { + if ($arr['channel_type'] === 'collection') { // do nothing at this time. } - elseif($arr['channel_type'] === 'group') { + elseif ($arr['channel_type'] === 'group') { $arr['public_forum'] = 1; } else { @@ -706,27 +721,27 @@ class Libzot { // old style - if(intval($r[0]['xchan_pubforum']) != intval($arr['public_forum'])) + if (intval($r[0]['xchan_pubforum']) != intval($arr['public_forum'])) $pubforum_changed = 1; - if($arr['protocols']) { - $protocols = implode(',',$arr['protocols']); - if($protocols !== 'zot6') { - set_xconfig($xchan_hash,'system','protocols',$protocols); + if ($arr['protocols']) { + $protocols = implode(',', $arr['protocols']); + if ($protocols !== 'zot6') { + set_xconfig($xchan_hash, 'system', 'protocols', $protocols); } else { - del_xconfig($xchan_hash,'system','protocols'); + del_xconfig($xchan_hash, 'system', 'protocols'); } } - if(($r[0]['xchan_name_date'] != $arr['name_updated']) + if (($r[0]['xchan_name_date'] != $arr['name_updated']) || ($r[0]['xchan_connurl'] != $arr['primary_location']['connections_url']) || ($r[0]['xchan_addr'] != $arr['primary_location']['address']) || ($r[0]['xchan_follow'] != $arr['primary_location']['follow_url']) || ($r[0]['xchan_connpage'] != $arr['connect_url']) || ($r[0]['xchan_url'] != $arr['primary_location']['url']) - || $hidden_changed || $adult_changed || $deleted_changed || $pubforum_changed ) { + || $hidden_changed || $adult_changed || $deleted_changed || $pubforum_changed) { $rup = q("update xchan set xchan_name = '%s', xchan_name_date = '%s', xchan_connurl = '%s', xchan_follow = '%s', xchan_connpage = '%s', xchan_hidden = %d, xchan_selfcensored = %d, xchan_deleted = %d, xchan_pubforum = %d, xchan_addr = '%s', xchan_url = '%s' where xchan_hash = '%s'", @@ -744,18 +759,18 @@ class Libzot { dbesc($xchan_hash) ); - logger('Update: existing: ' . print_r($r[0],true), LOGGER_DATA, LOG_DEBUG); - logger('Update: new: ' . print_r($arr,true), LOGGER_DATA, LOG_DEBUG); - $what .= 'xchan '; + logger('Update: existing: ' . print_r($r[0], true), LOGGER_DATA, LOG_DEBUG); + logger('Update: new: ' . print_r($arr, true), LOGGER_DATA, LOG_DEBUG); + $what .= 'xchan '; $changed = true; } } else { $import_photos = true; - if((($arr['site']['directory_mode'] === 'standalone') + if ((($arr['site']['directory_mode'] === 'standalone') || ($dirmode & DIRECTORY_MODE_STANDALONE)) - && ($arr['site']['url'] != z_root())) + && ($arr['site']['url'] != z_root())) $arr['searchable'] = false; $x = xchan_store_lowlevel( @@ -764,8 +779,8 @@ class Libzot { 'xchan_guid' => $arr['id'], 'xchan_guid_sig' => $arr['id_sig'], 'xchan_pubkey' => $arr['public_key'], - 'xchan_photo_mimetype' => $arr['photo_mimetype'], - 'xchan_photo_l' => $arr['photo'], + 'xchan_photo_mimetype' => $arr['photo']['type'], + 'xchan_photo_l' => $arr['photo']['url'], 'xchan_addr' => escape_tags($arr['primary_location']['address']), 'xchan_url' => escape_tags($arr['primary_location']['url']), 'xchan_connurl' => $arr['primary_location']['connections_url'], @@ -773,7 +788,7 @@ class Libzot { 'xchan_connpage' => $arr['connect_url'], 'xchan_name' => (($arr['name']) ? escape_tags($arr['name']) : '-'), 'xchan_network' => 'zot6', - 'xchan_photo_date' => $arr['photo_updated'], + 'xchan_photo_date' => $arr['photo']['updated'], 'xchan_name_date' => $arr['name_updated'], 'xchan_hidden' => intval(1 - intval($arr['searchable'])), 'xchan_selfcensored' => $arr['adult_content'], @@ -782,11 +797,11 @@ class Libzot { ] ); - $what .= 'new_xchan'; + $what .= 'new_xchan'; $changed = true; } - if($import_photos) { + if ($import_photos) { require_once('include/photo/photo_driver.php'); @@ -795,13 +810,16 @@ class Libzot { $local = q("select channel_account_id, channel_id from channel where channel_hash = '%s' limit 1", dbesc($xchan_hash) ); - if($local) { - $ph = z_fetch_url($arr['photo']['url'], true); - if($ph['success']) { + if ($local) { + + $ph = false; + if (strpos($arr['photo']['url'], z_root()) === false) + $ph = z_fetch_url($arr['photo']['url'], true); + if ($ph['success']) { $hash = import_channel_photo($ph['body'], $arr['photo']['type'], $local[0]['channel_account_id'], $local[0]['channel_id']); - if($hash) { + if ($hash) { // unless proven otherwise $is_default_profile = 1; @@ -809,13 +827,13 @@ class Libzot { intval($local[0]['channel_account_id']), intval($local[0]['channel_id']) ); - if($profile) { - if(! intval($profile[0]['is_default'])) + if ($profile) { + if (!intval($profile[0]['is_default'])) $is_default_profile = 0; } // If setting for the default profile, unset the profile photo flag from any other photos I own - if($is_default_profile) { + if ($is_default_profile) { q("UPDATE photo SET photo_usage = %d WHERE photo_usage = %d AND resource_id != '%s' AND aid = %d AND uid = %d", intval(PHOTO_NORMAL), intval(PHOTO_PROFILE), @@ -827,20 +845,20 @@ class Libzot { } // reset the names in case they got messed up when we had a bug in this function - $photos = array( + $photos = [ z_root() . '/photo/profile/l/' . $local[0]['channel_id'], z_root() . '/photo/profile/m/' . $local[0]['channel_id'], z_root() . '/photo/profile/s/' . $local[0]['channel_id'], $arr['photo_mimetype'], false - ); + ]; } } else { $photos = import_xchan_photo($arr['photo']['url'], $xchan_hash); } - if($photos) { - if($photos[4]) { + if ($photos) { + if ($photos[4]) { // importing the photo failed somehow. Leave the photo_date alone so we can try again at a later date. // This often happens when somebody joins the matrix with a bad cert. $r = q("update xchan set xchan_photo_l = '%s', xchan_photo_m = '%s', xchan_photo_s = '%s', xchan_photo_mimetype = '%s' @@ -855,7 +873,7 @@ class Libzot { else { $r = q("update xchan set xchan_photo_date = '%s', xchan_photo_l = '%s', xchan_photo_m = '%s', xchan_photo_s = '%s', xchan_photo_mimetype = '%s' where xchan_hash = '%s'", - dbescdate(datetime_convert('UTC','UTC',$arr['photo_updated'])), + dbescdate(datetime_convert('UTC', 'UTC', $arr['photo_updated'])), dbesc($photos[0]), dbesc($photos[1]), dbesc($photos[2]), @@ -863,7 +881,7 @@ class Libzot { dbesc($xchan_hash) ); } - $what .= 'photo '; + $what .= 'photo '; $changed = true; } } @@ -873,12 +891,12 @@ class Libzot { $s = Libsync::sync_locations($arr, $arr); - if($s) { - if($s['change_message']) + if ($s) { + if ($s['change_message']) $what .= $s['change_message']; - if($s['changed']) + if ($s['changed']) $changed = $s['changed']; - if($s['message']) + if ($s['message']) $ret['message'] .= $s['message']; } @@ -890,24 +908,24 @@ class Libzot { // Are we a directory server of some kind? $other_realm = false; - $realm = get_directory_realm(); - if(array_key_exists('site',$arr) - && array_key_exists('realm',$arr['site']) - && (strpos($arr['site']['realm'],$realm) === false)) + $realm = get_directory_realm(); + if (array_key_exists('site', $arr) + && array_key_exists('realm', $arr['site']) + && (strpos($arr['site']['realm'], $realm) === false)) $other_realm = true; - if($dirmode != DIRECTORY_MODE_NORMAL) { + if ($dirmode != DIRECTORY_MODE_NORMAL) { // We're some kind of directory server. However we can only add directory information // if the entry is in the same realm (or is a sub-realm). Sub-realms are denoted by // including the parent realm in the name. e.g. 'RED_GLOBAL:foo' would allow an entry to // be in directories for the local realm (foo) and also the RED_GLOBAL realm. - if(array_key_exists('profile',$arr) && is_array($arr['profile']) && (! $other_realm)) { - $profile_changed = Libzotdir::import_directory_profile($xchan_hash,$arr['profile'],$address,$ud_flags, 1); - if($profile_changed) { - $what .= 'profile '; + if (array_key_exists('profile', $arr) && is_array($arr['profile']) && (!$other_realm)) { + $profile_changed = Libzotdir::import_directory_profile($xchan_hash, $arr['profile'], $address, $ud_flags, 1); + if ($profile_changed) { + $what .= 'profile '; $changed = true; } } @@ -923,20 +941,20 @@ class Libzot { } } - if(array_key_exists('site',$arr) && is_array($arr['site'])) { + if (array_key_exists('site', $arr) && is_array($arr['site'])) { $profile_changed = self::import_site($arr['site']); - if($profile_changed) { - $what .= 'site '; + if ($profile_changed) { + $what .= 'site '; $changed = true; } } - if(($changed) || ($ud_flags == UPDATE_FLAGS_FORCED)) { + if (($changed) || ($ud_flags == UPDATE_FLAGS_FORCED)) { $guid = random_string() . '@' . \App::get_hostname(); - Libzotdir::update_modtime($xchan_hash,$guid,$address,$ud_flags); - logger('Changed: ' . $what,LOGGER_DEBUG); + Libzotdir::update_modtime($xchan_hash, $guid, $address, $ud_flags); + logger('Changed: ' . $what, LOGGER_DEBUG); } - elseif(! $ud_flags) { + elseif (!$ud_flags) { // nothing changed but we still need to update the updates record q("update updates set ud_flags = ( ud_flags | %d ) where ud_addr = '%s' and not (ud_flags & %d) > 0 ", intval(UPDATE_FLAGS_UPDATED), @@ -945,12 +963,12 @@ class Libzot { ); } - if(! x($ret,'message')) { + if (!x($ret, 'message')) { $ret['success'] = true; - $ret['hash'] = $xchan_hash; + $ret['hash'] = $xchan_hash; } - logger('Result: ' . print_r($ret,true), LOGGER_DATA, LOG_DEBUG); + logger('Result: ' . print_r($ret, true), LOGGER_DATA, LOG_DEBUG); return $ret; } @@ -967,32 +985,32 @@ class Libzot { */ static function process_response($hub, $arr, $outq) { - logger('remote: ' . print_r($arr,true),LOGGER_DATA); + logger('remote: ' . print_r($arr, true), LOGGER_DATA); - if(! $arr['success']) { + if (!$arr['success']) { logger('Failed: ' . $hub); return; } $x = json_decode($arr['body'], true); - if(! $x) { + if (!$x) { logger('No json from ' . $hub); logger('Headers: ' . print_r($arr['header'], true), LOGGER_DATA, LOG_DEBUG); } - $x = crypto_unencapsulate($x, get_config('system','prvkey')); + $x = Crypto::unencapsulate($x, get_config('system', 'prvkey')); - if(! is_array($x)) { - $x = json_decode($x,true); + if (!is_array($x)) { + $x = json_decode($x, true); } - if(! is_array($x)) { + if (!is_array($x)) { btlogger('failed communication - no response'); } - if($x) { - if(! $x['success']) { + if ($x) { + if (!$x['success']) { // handle remote validation issues @@ -1003,18 +1021,18 @@ class Libzot { ); } - if(is_array($x) && array_key_exists('delivery_report',$x) && is_array($x['delivery_report'])) { + if (is_array($x) && array_key_exists('delivery_report', $x) && is_array($x['delivery_report'])) { - foreach($x['delivery_report'] as $xx) { - call_hooks('dreport_process',$xx); - if(is_array($xx) && array_key_exists('message_id',$xx) && DReport::is_storable($xx)) { + foreach ($x['delivery_report'] as $xx) { + call_hooks('dreport_process', $xx); + if (is_array($xx) && array_key_exists('message_id', $xx) && DReport::is_storable($xx)) { // legacy recipients add a space and their name to the xchan. split those if true. $legacy_recipient = strpos($xx['recipient'], ' '); - if($legacy_recipient !== false) { + if ($legacy_recipient !== false) { $legacy_recipient_parts = explode(' ', $xx['recipient'], 2); - $xx['recipient'] = $legacy_recipient_parts[0]; - $xx['name'] = $legacy_recipient_parts[1]; + $xx['recipient'] = $legacy_recipient_parts[0]; + $xx['name'] = $legacy_recipient_parts[1]; } q("insert into dreport ( dreport_mid, dreport_site, dreport_recip, dreport_name, dreport_result, dreport_time, dreport_xchan ) values ( '%s', '%s', '%s','%s','%s','%s','%s' ) ", @@ -1023,7 +1041,7 @@ class Libzot { dbesc($xx['recipient']), dbesc($xx['name']), dbesc($xx['status']), - dbesc(datetime_convert('UTC','UTC',$xx['date'])), + dbesc(datetime_convert('UTC', 'UTC', $xx['date'])), dbesc($xx['sender']) ); } @@ -1046,10 +1064,10 @@ class Libzot { // synchronous message types are handled immediately // async messages remain in the queue until processed. - if(intval($outq['outq_async'])) - Queue::remove($outq['outq_hash'],$outq['outq_channel']); + if (intval($outq['outq_async'])) + Queue::remove($outq['outq_hash'], $outq['outq_channel']); - logger('zot_process_response: ' . print_r($x,true), LOGGER_DEBUG); + logger('zot_process_response: ' . print_r($x, true), LOGGER_DEBUG); } /** @@ -1067,16 +1085,16 @@ class Libzot { * If everything checks out on the remote end, we will receive back a packet containing one or more messages, * which will be processed and delivered before this function ultimately returns. * - * @see zot_import() - * * @param array $arr * decrypted and json decoded notify packet from remote site * @return array from zot_import() + * @see zot_import() + * */ static function fetch($arr) { - logger('zot_fetch: ' . print_r($arr,true), LOGGER_DATA, LOG_DEBUG); + logger('zot_fetch: ' . print_r($arr, true), LOGGER_DATA, LOG_DEBUG); return self::import($arr); @@ -1101,15 +1119,15 @@ class Libzot { */ static function import($arr) { - $env = $arr; + $env = $arr; $private = false; - $return = []; + $return = []; $result = null; - logger('Notify: ' . print_r($env,true), LOGGER_DATA, LOG_DEBUG); + logger('Notify: ' . print_r($env, true), LOGGER_DATA, LOG_DEBUG); - if(! is_array($env)) { + if (!is_array($env)) { logger('decode error'); return; } @@ -1117,59 +1135,60 @@ class Libzot { $message_request = false; - $has_data = array_key_exists('data',$env) && $env['data']; - $data = (($has_data) ? $env['data'] : false); + $has_data = array_key_exists('data', $env) && $env['data']; + $data = (($has_data) ? $env['data'] : false); $AS = null; - if($env['encoding'] === 'activitystreams') { + if ($env['encoding'] === 'activitystreams') { - $AS = new ActivityStreams($data); - if(! $AS->is_valid()) { - logger('Activity rejected: ' . print_r($data,true)); - return; - } - if (is_array($AS->obj)) { - $arr = Activity::decode_note($AS); - } - else { - $arr = []; - } + $AS = new ActivityStreams($data); + if (!$AS->is_valid()) { + logger('Activity rejected: ' . print_r($data, true)); + return; + } + if (is_array($AS->obj)) { + $arr = Activity::decode_note($AS); + } + else { + $arr = []; + } - logger($AS->debug(),LOGGER_DATA); + logger($AS->debug(), LOGGER_DATA); } + $deliveries = null; - if(array_key_exists('recipients',$env) && count($env['recipients'])) { + if (array_key_exists('recipients', $env) && count($env['recipients'])) { logger('specific recipients'); - logger('recipients: ' . print_r($env['recipients'],true),LOGGER_DEBUG); + logger('recipients: ' . print_r($env['recipients'], true), LOGGER_DEBUG); $recip_arr = []; - foreach($env['recipients'] as $recip) { - $recip_arr[] = $recip; + foreach ($env['recipients'] as $recip) { + $recip_arr[] = $recip; } $r = false; - if($recip_arr) { - stringify_array_elms($recip_arr,true); - $recips = implode(',',$recip_arr); - $r = q("select channel_hash as hash from channel where channel_hash in ( " . $recips . " ) and channel_removed = 0 "); + if ($recip_arr) { + stringify_array_elms($recip_arr, true); + $recips = implode(',', $recip_arr); + $r = q("select channel_hash as hash from channel where channel_hash in ( " . $recips . " ) and channel_removed = 0 "); } - if(! $r) { + if (!$r) { logger('recips: no recipients on this site'); return; } // Response messages will inherit the privacy of the parent - if($env['type'] !== 'response') + if ($env['type'] !== 'response') $private = true; - $deliveries = ids_to_array($r,'hash'); + $deliveries = ids_to_array($r, 'hash'); // We found somebody on this site that's in the recipient list. } @@ -1182,28 +1201,32 @@ class Libzot { // and who are allowed to see them based on the sender's permissions // @fixme; - $deliveries = self::public_recips($env,$AS); + $deliveries = self::public_recips($env, $AS); } $deliveries = array_unique($deliveries); - if(! $deliveries) { + if (!$deliveries) { logger('No deliveries on this site'); return; } - if($has_data) { + if ($has_data) { - if(in_array($env['type'],['activity','response'])) { + if (in_array($env['type'], ['activity', 'response'])) { + + if(!isset($AS->actor['id'])) { + logger('No actor id!'); + return; + } - $r = q("select hubloc_hash, hubloc_network from hubloc where hubloc_id_url = '%s' ", + $r = q("select hubloc_hash, hubloc_network, hubloc_url from hubloc where hubloc_id_url = '%s'", dbesc($AS->actor['id']) ); - if($r) { - // selects a zot6 hash if available, otherwise use whatever we have + if ($r) { $r = self::zot_record_preferred($r); $arr['author_xchan'] = $r['hubloc_hash']; } @@ -1213,48 +1236,58 @@ class Libzot { return; } - $s = q("select hubloc_hash from hubloc where hubloc_id_url = '%s' and hubloc_network = 'zot6' limit 1", - dbesc($env['sender']) - ); + $arr['owner_xchan'] = $env['sender']; + + if(filter_var($env['sender'], FILTER_VALIDATE_URL)) { + // in individual delivery, change owner if needed + $s = q("select hubloc_hash, hubloc_url from hubloc where hubloc_id_url = '%s' and hubloc_network = 'zot6' limit 1", + dbesc($env['sender']) + ); - // in individual delivery, change owner if needed - if($s) { - $arr['owner_xchan'] = $s[0]['hubloc_hash']; + if ($s) { + $arr['owner_xchan'] = $s[0]['hubloc_hash']; + } } - else { - $arr['owner_xchan'] = $env['sender']; + + if (! $arr['owner_xchan']) { + logger('No owner!'); + return; } - if ($private && (! intval($arr['item_private']))) { + if ($private && (!intval($arr['item_private']))) { $arr['item_private'] = 1; } if ($arr['mid'] === $arr['parent_mid']) { - if (is_array($AS->obj) && array_key_exists('commentPolicy',$AS->obj)) { - $p = strstr($AS->obj['commentPolicy'],'until='); - if($p !== false) { - $arr['comments_closed'] = datetime_convert('UTC','UTC', substr($p,6)); - $arr['comment_policy'] = trim(str_replace($p,'',$AS->obj['commentPolicy'])); + if (is_array($AS->obj) && array_key_exists('commentPolicy', $AS->obj)) { + $p = strstr($AS->obj['commentPolicy'], 'until='); + if ($p !== false) { + $comments_closed_at = datetime_convert('UTC', 'UTC', substr($p, 6)); + if ($comments_closed_at === $arr['created']) { + $arr['item_nocomment'] = 1; + } + else { + $arr['comments_closed'] = $comments_closed_at; + $arr['comment_policy'] = trim(str_replace($p, '', $AS->obj['commentPolicy'])); + } } else { - $arr['comment_policy'] = $AS->obj['commentPolicy']; + $arr['comment_policy'] = $AS->obj['commentPolicy']; } } } - - /// @FIXME - spoofable - if($AS->data['hubloc']) { + if ($AS->data['hubloc']) { $arr['item_verified'] = true; - if (! array_key_exists('comment_policy',$arr)) { + if (!array_key_exists('comment_policy', $arr)) { // set comment policy depending on source hub. Unknown or osada is ActivityPub. // Anything else we'll say is zot - which could have a range of project names $s = q("select site_project from site where site_url = '%s' limit 1", dbesc($r[0]['hubloc_url']) ); - if ((! $s) || (in_array($s[0]['site_project'],[ '', 'osada' ]))) { + if ((!$s) || (in_array($s[0]['site_project'], ['', 'osada']))) { $arr['comment_policy'] = 'authenticated'; } else { @@ -1262,28 +1295,28 @@ class Libzot { } } } - if($AS->data['signed_data']) { - IConfig::Set($arr,'activitystreams','signed_data',$AS->data['signed_data'],false); - } + if ($AS->data['signed_data']) { + IConfig::Set($arr, 'activitypub', 'signed_data', $AS->data['signed_data'], false); + } - logger('Activity received: ' . print_r($arr,true), LOGGER_DATA, LOG_DEBUG); - logger('Activity recipients: ' . print_r($deliveries,true), LOGGER_DATA, LOG_DEBUG); + logger('Activity received: ' . print_r($arr, true), LOGGER_DATA, LOG_DEBUG); + logger('Activity recipients: ' . print_r($deliveries, true), LOGGER_DATA, LOG_DEBUG); - $relay = (($env['type'] === 'response') ? true : false ); + $relay = (($env['type'] === 'response') ? true : false); - $result = self::process_delivery($env['sender'],$AS,$arr,$deliveries,$relay,false,$message_request); + $result = self::process_delivery($env['sender'], $AS, $arr, $deliveries, $relay, false, $message_request); } - elseif($env['type'] === 'sync') { + elseif ($env['type'] === 'sync') { // $arr = get_channelsync_elements($data); - $arr = json_decode($data,true); + $arr = json_decode($data, true); - logger('Channel sync received: ' . print_r($arr,true), LOGGER_DATA, LOG_DEBUG); - logger('Channel sync recipients: ' . print_r($deliveries,true), LOGGER_DATA, LOG_DEBUG); + logger('Channel sync received: ' . print_r($arr, true), LOGGER_DATA, LOG_DEBUG); + logger('Channel sync recipients: ' . print_r($deliveries, true), LOGGER_DATA, LOG_DEBUG); if ($env['encoding'] === 'hz') { - $result = Libsync::process_channel_sync_delivery($env['sender'],$arr,$deliveries); + $result = Libsync::process_channel_sync_delivery($env['sender'], $arr, $deliveries); } else { logger('sync packet type not supported.'); @@ -1305,15 +1338,15 @@ class Libzot { * @return boolean */ static function is_top_level($env, $act) { - if($env['encoding'] === 'zot' && array_key_exists('flags',$env) && in_array('thread_parent', $env['flags'])) { + if ($env['encoding'] === 'zot' && array_key_exists('flags', $env) && in_array('thread_parent', $env['flags'])) { return true; } - if($act) { - if(in_array($act->type, ['Like','Dislike'])) { + if ($act) { + if (in_array($act->type, ['Like', 'Dislike'])) { return false; } - $x = self::find_parent($env,$act); - if($x === $act->id || $x === $act->obj['id']) { + $x = self::find_parent($env, $act); + if ($x === $act->id || $x === $act->obj['id']) { return true; } } @@ -1321,12 +1354,12 @@ class Libzot { } - static function find_parent($env,$act) { - if($act) { - if(in_array($act->type, ['Like','Dislike'])) { + static function find_parent($env, $act) { + if ($act) { + if (in_array($act->type, ['Like', 'Dislike']) && is_array($act->obj)) { return $act->obj['id']; } - if($act->parent_id) { + if ($act->parent_id) { return $act->parent_id; } } @@ -1355,58 +1388,55 @@ class Libzot { require_once('include/channel.php'); $check_mentions = false; - $include_sys = false; + $include_sys = false; - if($msg['type'] === 'activity') { - $disable_discover_tab = get_config('system','disable_discover_tab') || get_config('system','disable_discover_tab') === false; - if(! $disable_discover_tab) + if ($msg['type'] === 'activity') { + $disable_discover_tab = get_config('system', 'disable_discover_tab') || get_config('system', 'disable_discover_tab') === false; + if (!$disable_discover_tab) $include_sys = true; $perm = 'send_stream'; - if(self::is_top_level($msg,$act)) { + if (self::is_top_level($msg, $act)) { $check_mentions = true; } } - elseif($msg['type'] === 'mail') - $perm = 'post_mail'; $r = []; $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)) { + if ($c) { + foreach ($c as $cc) { + if (perm_is_allowed($cc['channel_id'], $msg['sender'], $perm)) { $r[] = $cc['channel_hash']; } } } - if($include_sys) { + if ($include_sys) { $sys = get_sys_channel(); - if($sys) + if ($sys) $r[] = $sys['channel_hash']; } - // look for any public mentions on this site // They will get filtered by tgroup_check() so we don't need to check permissions now - if($check_mentions) { + if ($check_mentions) { // It's a top level post. Look at the tags. See if any of them are mentions and are on this hub. - if($act && $act->obj) { - if(is_array($act->obj['tag']) && $act->obj['tag']) { - foreach($act->obj['tag'] as $tag) { - if($tag['type'] === 'Mention' && (strpos($tag['href'],z_root()) !== false)) { + if ($act && $act->obj) { + if (is_array($act->obj['tag']) && $act->obj['tag']) { + foreach ($act->obj['tag'] as $tag) { + if ($tag['type'] === 'Mention' && (strpos($tag['href'], z_root()) !== false)) { $address = basename($tag['href']); - if($address) { + if ($address) { $z = q("select channel_hash as hash from channel where channel_address = '%s' and channel_removed = 0 limit 1", dbesc($address) ); - if($z) { + if ($z) { $r[] = $z[0]['hash']; } } @@ -1420,15 +1450,15 @@ class Libzot { // everybody that stored a copy of the parent. This way we know we're covered. We'll check the // comment permissions when we deliver them. - $thread_parent = self::find_parent($msg,$act); + $thread_parent = self::find_parent($msg, $act); - if($thread_parent) { + if ($thread_parent) { $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) ); - if($z) { - foreach($z as $zv) { + if ($z) { + foreach ($z as $zv) { $r[] = $zv['hash']; } } @@ -1438,11 +1468,11 @@ class Libzot { // There are probably a lot of duplicates in $r at this point. We need to filter those out. // It's a bit of work since it's a multi-dimensional array - if($r) { + if ($r) { $r = array_values(array_unique($r)); } - logger('public_recips: ' . print_r($r,true), LOGGER_DATA, LOG_DEBUG); + logger('public_recips: ' . print_r($r, true), LOGGER_DATA, LOG_DEBUG); return $r; } @@ -1465,22 +1495,22 @@ class Libzot { // We've validated the sender. Now make sure that the sender is the owner or author - if(! $public) { - if($sender != $arr['owner_xchan'] && $sender != $arr['author_xchan']) { + if (!$public) { + if ($sender != $arr['owner_xchan'] && $sender != $arr['author_xchan']) { logger("Sender $sender is not owner {$arr['owner_xchan']} or author {$arr['author_xchan']} - mid {$arr['mid']}"); return; } } - foreach($deliveries as $d) { + foreach ($deliveries as $d) { $local_public = $public; - $DR = new DReport(z_root(),$sender,$d,$arr['mid']); + $DR = new DReport(z_root(), $sender, $d, $arr['mid']); $channel = channelx_by_hash($d); - if (! $channel) { + if (!$channel) { $DR->update('recipient not found'); $result[] = $DR->get(); continue; @@ -1488,16 +1518,16 @@ class Libzot { $DR->set_name($channel['channel_name'] . ' <' . channel_reddress($channel) . '>'); - if(($act) && ($act->obj) && (! is_array($act->obj))) { - // The initial object fetch failed using the sys channel credentials. + if (($act) && ($act->obj) && (!is_array($act->obj))) { + // The initial object fetch failed using the sys channel credentials. // Try again using the delivery channel credentials. - // We will also need to re-parse the $item array, + // 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) { + $o = Activity::fetch($act->obj, $channel); + if ($o) { $act->obj = $o; - $arr = array_merge(Activity::decode_note($act),$arr); + $arr = array_merge(Activity::decode_note($act), $arr); } else { @@ -1505,7 +1535,7 @@ class Libzot { $result[] = $DR->get(); continue; } - } + } /** * We need to block normal top-level message delivery from our clones, as the delivered @@ -1516,7 +1546,7 @@ class Libzot { * access checks. */ - if($sender === $channel['channel_hash'] && $arr['author_xchan'] === $channel['channel_hash'] && $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; @@ -1526,32 +1556,31 @@ class Libzot { // for comments travelling upstream. Wait and catch them on the way down. // They may have been blocked by the owner. - if(intval($channel['channel_system']) && (! $arr['item_private']) && (! $relay)) { + if (intval($channel['channel_system']) && (!$arr['item_private']) && (!$relay)) { $local_public = true; $r = q("select xchan_selfcensored from xchan where xchan_hash = '%s' limit 1", dbesc($sender) ); // don't import sys channel posts from selfcensored authors - if($r && (intval($r[0]['xchan_selfcensored']))) { + if ($r && (intval($r[0]['xchan_selfcensored']))) { $local_public = false; continue; } - if(! MessageFilter::evaluate($arr,get_config('system','pubstream_incl'),get_config('system','pubstream_excl'))) { + if (!MessageFilter::evaluate($arr, get_config('system', 'pubstream_incl'), get_config('system', 'pubstream_excl'))) { $local_public = false; continue; } } - $tag_delivery = tgroup_check($channel['channel_id'],$arr); - - $perm = 'send_stream'; - if(($arr['mid'] !== $arr['parent_mid']) && ($relay)) + $tag_delivery = tgroup_check($channel['channel_id'], $arr); + $perm = 'send_stream'; + if (($arr['mid'] !== $arr['parent_mid']) && ($relay)) $perm = 'post_comments'; // This is our own post, possibly coming from a channel clone - if($arr['owner_xchan'] == $d) { + if ($arr['owner_xchan'] == $d) { $arr['item_wall'] = 1; } else { @@ -1560,15 +1589,15 @@ class Libzot { $friendofriend = false; - if ((! $tag_delivery) && (! $local_public)) { - $allowed = (perm_is_allowed($channel['channel_id'],$sender,$perm)); - if((! $allowed) && $perm === 'post_comments') { + if ((!$tag_delivery) && (!$local_public)) { + $allowed = (perm_is_allowed($channel['channel_id'], $sender, $perm)); + if (!$allowed) { $parent = q("select * from item where mid = '%s' and uid = %d limit 1", dbesc($arr['parent_mid']), intval($channel['channel_id']) ); if ($parent) { - $allowed = can_comment_on_post($sender,$parent[0]); + $allowed = can_comment_on_post($sender, $parent[0]); } } @@ -1588,7 +1617,7 @@ class Libzot { // doesn't exist. if ($perm === 'send_stream') { - if (get_pconfig($channel['channel_id'],'system','hyperdrive',false) || $arr['verb'] === ACTIVITY_SHARE) { + if (get_pconfig($channel['channel_id'], 'system', 'hyperdrive', false) || $arr['verb'] === ACTIVITY_SHARE) { $allowed = true; } } @@ -1599,7 +1628,13 @@ class Libzot { $friendofriend = true; } - if (! $allowed) { + if (intval($arr['item_private']) === 2) { + if (!perm_is_allowed($channel['channel_id'], $sender, 'post_mail')) { + $allowed = false; + } + } + + if (!$allowed) { logger("permission denied for delivery to channel {$channel['channel_id']} {$channel['channel_address']}"); $DR->update('permission denied'); $result[] = $DR->get(); @@ -1609,7 +1644,7 @@ class Libzot { // logger('item: ' . print_r($arr,true), LOGGER_DATA); - if($arr['mid'] !== $arr['parent_mid']) { + if ($arr['mid'] !== $arr['parent_mid']) { logger('checking source: "' . $arr['mid'] . '" != "' . $arr['parent_mid'] . '"'); @@ -1624,7 +1659,7 @@ class Libzot { intval($channel['channel_id']) ); - if(! $r) { + if (!$r) { $DR->update('comment parent not found'); $result[] = $DR->get(); @@ -1634,14 +1669,14 @@ class Libzot { // have the copy and we don't want the request to loop. // Also don't do this if this comment came from a conversation request packet. // It's possible that comments are allowed but posting isn't and that could - // cause a conversation fetch loop. + // cause a conversation fetch loop. // We'll also check the send_stream permission - because if it isn't allowed, // the top level post is unlikely to be imported and // this is just an exercise in futility. - if((! $relay) && (! $request) && (! $local_public) - && perm_is_allowed($channel['channel_id'],$sender,'send_stream')) { - self::fetch_conversation($channel,$arr['parent_mid']); + if ((!$relay) && (!$request) && (!$local_public) + && perm_is_allowed($channel['channel_id'], $sender, 'send_stream')) { + self::fetch_conversation($channel, $arr['parent_mid']); } continue; } @@ -1650,13 +1685,13 @@ class Libzot { // 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'])) { + 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)) { + 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 // with whatever route our parent has. @@ -1664,7 +1699,7 @@ class Libzot { // but we are now getting comments via listener delivery // and if there is no privacy on this or the parent, we don't care about the route, // so just set the owner and route accordingly. - $arr['route'] = $r[0]['route']; + $arr['route'] = $r[0]['route']; $arr['owner_xchan'] = $r[0]['owner_xchan']; } else { @@ -1676,24 +1711,24 @@ class Libzot { // Always accept empty routes and firehose items (route contains 'undefined') . $existing_route = explode(',', $r[0]['route']); - $routes = count($existing_route); - if($routes) { - $last_hop = array_pop($existing_route); - $last_prior_route = implode(',',$existing_route); + $routes = count($existing_route); + if ($routes) { + $last_hop = array_pop($existing_route); + $last_prior_route = implode(',', $existing_route); } else { - $last_hop = ''; + $last_hop = ''; $last_prior_route = ''; } - if(in_array('undefined',$existing_route) || $last_hop == 'undefined' || $sender == 'undefined') + if (in_array('undefined', $existing_route) || $last_hop == 'undefined' || $sender == 'undefined') $last_hop = ''; $current_route = (($arr['route']) ? $arr['route'] . ',' : '') . $sender; - if($last_hop && $last_hop != $sender) { + if ($last_hop && $last_hop != $sender) { logger('comment route mismatch: parent route = ' . $r[0]['route'] . ' expected = ' . $current_route, LOGGER_DEBUG); - logger('comment route mismatch: parent msg = ' . $r[0]['id'],LOGGER_DEBUG); + logger('comment route mismatch: parent msg = ' . $r[0]['id'], LOGGER_DEBUG); $DR->update('comment route mismatch'); $result[] = $DR->get(); continue; @@ -1712,10 +1747,10 @@ class Libzot { ); $abook = (($ab) ? $ab[0] : null); - if(intval($arr['item_deleted'])) { + if (intval($arr['item_deleted'])) { // remove_community_tag is a no-op if this isn't a community tag activity - self::remove_community_tag($sender,$arr,$channel['channel_id']); + self::remove_community_tag($sender, $arr, $channel['channel_id']); // set these just in case we need to store a fresh copy of the deleted post. // This could happen if the delete got here before the original post did. @@ -1723,13 +1758,13 @@ class Libzot { $arr['aid'] = $channel['channel_account_id']; $arr['uid'] = $channel['channel_id']; - $item_id = self::delete_imported_item($sender,$act,$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(); - if($relay && $item_id) { + if ($relay && $item_id) { logger('process_delivery: invoking relay'); - Master::Summon([ 'Notifier', 'relay', intval($item_id) ]); + Master::Summon(['Notifier', 'relay', intval($item_id)]); $DR->update('relayed'); $result[] = $DR->get(); } @@ -1746,11 +1781,11 @@ class Libzot { intval($channel['channel_id']) ); - if($r) { + if ($r) { // We already have this post. $item_id = $r[0]['id']; - if(intval($r[0]['item_deleted'])) { + if (intval($r[0]['item_deleted'])) { // It was deleted locally. $DR->update('update ignored'); $result[] = $DR->get(); @@ -1758,19 +1793,19 @@ class Libzot { continue; } // Maybe it has been edited? - elseif($arr['edited'] > $r[0]['edited']) { - $arr['id'] = $r[0]['id']; + elseif ($arr['edited'] > $r[0]['edited']) { + $arr['id'] = $r[0]['id']; $arr['uid'] = $channel['channel_id']; - if(($arr['mid'] == $arr['parent_mid']) && (! post_is_importable($arr,$abook))) { + if (($arr['mid'] == $arr['parent_mid']) && (!post_is_importable($arr, $abook))) { $DR->update('update ignored'); $result[] = $DR->get(); } else { - $item_result = self::update_imported_item($sender,$arr,$r[0],$channel['channel_id'],$tag_delivery); + $item_result = self::update_imported_item($sender, $arr, $r[0], $channel['channel_id'], $tag_delivery); $DR->update('updated'); $result[] = $DR->get(); - if(! $relay) - add_source_route($item_id,$sender); + if (!$relay) + add_source_route($item_id, $sender); } } else { @@ -1779,7 +1814,7 @@ class Libzot { // We need this line to ensure wall-to-wall comments are relayed (by falling through to the relay bit), // and at the same time not relay any other relayable posts more than once, because to do so is very wasteful. - if(! intval($r[0]['item_origin'])) + if (!intval($r[0]['item_origin'])) continue; } } @@ -1790,7 +1825,7 @@ class Libzot { // if it's a sourced post, call the post_local hooks as if it were // posted locally so that crosspost connectors will be triggered. - if(check_item_source($arr['uid'], $arr) || ($channel['xchan_pubforum'] == 1)) { + if (check_item_source($arr['uid'], $arr) || ($channel['xchan_pubforum'] == 1)) { /** * @hooks post_local * Called when an item has been posted on this machine via mod/item.php (also via API). @@ -1801,19 +1836,19 @@ class Libzot { $item_id = 0; - if(($arr['mid'] == $arr['parent_mid']) && (! post_is_importable($arr,$abook))) { + if (($arr['mid'] == $arr['parent_mid']) && (!post_is_importable($arr, $abook))) { $DR->update('post ignored'); $result[] = $DR->get(); } else { $item_result = item_store($arr); - if($item_result['success']) { + if ($item_result['success']) { $item_id = $item_result['item_id']; - $parr = [ - 'item_id' => $item_id, - 'item' => $arr, - 'sender' => $sender, - 'channel' => $channel + $parr = [ + 'item_id' => $item_id, + 'item' => $arr, + 'sender' => $sender, + 'channel' => $channel ]; /** * @hooks activity_received @@ -1825,8 +1860,8 @@ class Libzot { */ call_hooks('activity_received', $parr); // don't add a source route if it's a relay or later recipients will get a route mismatch - if(! $relay) - add_source_route($item_id,$sender); + if (!$relay) + add_source_route($item_id, $sender); } $DR->update(($item_id) ? 'posted' : 'storage failed: ' . $item_result['message']); $result[] = $DR->get(); @@ -1836,132 +1871,132 @@ class Libzot { // preserve conversations with which you are involved from expiration $stored = (($item_result && $item_result['item']) ? $item_result['item'] : false); - if((is_array($stored)) && ($stored['id'] != $stored['parent']) + if ((is_array($stored)) && ($stored['id'] != $stored['parent']) && ($stored['author_xchan'] === $channel['channel_hash'] || $stored['author_xchan'] === $channel['channel_hash'])) { retain_item($stored['item']['parent']); } - if($relay && $item_id) { + if ($relay && $item_id) { logger('Invoking relay'); - Master::Summon([ 'Notifier', 'relay', intval($item_id) ]); + Master::Summon(['Notifier', 'relay', intval($item_id)]); $DR->addto_update('relayed'); $result[] = $DR->get(); } } - if(! $deliveries) - $result[] = array('', 'no recipients', '', $arr['mid']); + if (!$deliveries) + $result[] = ['', 'no recipients', '', $arr['mid']]; logger('Local results: ' . print_r($result, true), LOGGER_DEBUG); return $result; } - static public function fetch_conversation($channel,$mid) { + static public function fetch_conversation($channel, $mid) { // Use Zotfinger to create a signed request - $a = Zotfinger::exec($mid,$channel); + logger('fetching conversation: ' . $mid, LOGGER_DEBUG); - logger('received conversation: ' . print_r($a,true), LOGGER_DATA); + $a = Zotfinger::exec($mid, $channel); - if($a['data']['type'] !== 'OrderedCollection') { - return; + logger('received conversation: ' . print_r($a, true), LOGGER_DATA); + + if (!$a) { + return false; } - if(! intval($a['data']['totalItems'])) { - return; + if ($a['data']['type'] !== 'OrderedCollection') { + return false; + } + + $obj = new ASCollection($a['data'], $channel); + $items = $obj->get(); + + if (!$items) { + return false; } $ret = []; + $signer = q("select hubloc_hash, hubloc_url from hubloc where hubloc_id_url = '%s' and hubloc_network = 'zot6' limit 1", dbesc($a['signature']['signer']) ); - foreach($a['data']['orderedItems'] as $activity) { + foreach ($items as $activity) { $AS = new ActivityStreams($activity); - if(! $AS->is_valid()) { - logger('FOF Activity rejected: ' . print_r($activity,true)); + if ($AS->is_valid() && $AS->type === 'Announce' && is_array($AS->obj) + && array_key_exists('object', $AS->obj) && array_key_exists('actor', $AS->obj)) { + // This is a relayed/forwarded Activity (as opposed to a shared/boosted object) + // Reparse the encapsulated Activity and use that instead + logger('relayed activity', LOGGER_DEBUG); + $AS = new ActivityStreams($AS->obj); + } + + if (!$AS->is_valid()) { + logger('FOF Activity rejected: ' . print_r($activity, true)); continue; } $arr = Activity::decode_note($AS); - logger($AS->debug()); + // logger($AS->debug()); - - $r = q("select hubloc_hash from hubloc where hubloc_id_url = '%s' and hubloc_network = 'zot6' limit 1", + $r = q("select hubloc_hash, hubloc_network from hubloc where hubloc_id_url = '%s'", dbesc($AS->actor['id']) ); + $r = self::zot_record_preferred($r); - if(! $r) { - $y = import_author_xchan([ 'url' => $AS->actor['id'] ]); - if($y) { - $r = q("select hubloc_hash from hubloc where hubloc_id_url = '%s' and hubloc_network = 'zot6' limit 1", + if (!$r) { + $y = import_author_xchan(['url' => $AS->actor['id']]); + if ($y) { + $r = q("select hubloc_hash, hubloc_network from hubloc where hubloc_id_url = '%s'", dbesc($AS->actor['id']) ); + $r = self::zot_record_preferred($r); } - if(! $r) { + if (!$r) { logger('FOF Activity: no actor'); continue; } } - if($AS->obj['actor'] && $AS->obj['actor']['id'] && $AS->obj['actor']['id'] !== $AS->actor['id']) { - $y = import_author_xchan([ 'url' => $AS->obj['actor']['id'] ]); - if(! $y) { + if ($AS->obj['actor'] && $AS->obj['actor']['id'] && $AS->obj['actor']['id'] !== $AS->actor['id']) { + $y = import_author_xchan(['url' => $AS->obj['actor']['id']]); + if (!$y) { logger('FOF Activity: no object actor'); continue; } } - - if($r) { - $arr['author_xchan'] = $r[0]['hubloc_hash']; + if ($r) { + $arr['author_xchan'] = $r['hubloc_hash']; } - $s = q("select hubloc_hash from hubloc where hubloc_id_url = '%s' and hubloc_network = 'zot6' limit 1", - dbesc($a['signature']['signer']) - ); - - if($s) { - $arr['owner_xchan'] = $s[0]['hubloc_hash']; + if ($signer) { + $arr['owner_xchan'] = $signer[0]['hubloc_hash']; } else { $arr['owner_xchan'] = $a['signature']['signer']; } - - /// @FIXME - spoofable - if($AS->data['hubloc']) { + if ($AS->data['hubloc'] || $arr['author_xchan'] === $arr['owner_xchan']) { $arr['item_verified'] = true; } - // set comment policy depending on source hub. Unknown or osada is ActivityPub. - // Anything else we'll say is zot - which could have a range of project names - - if ($signer) { - $s = q("select site_project from site where site_url = '%s' limit 1", - dbesc($signer[0]['hubloc_url']) - ); - if ((! $s) || (in_array($s[0]['site_project'],[ '', 'osada' ]))) { - $arr['comment_policy'] = 'authenticated'; - } - else { - $arr['comment_policy'] = 'contacts'; + if ($AS->data['signed_data']) { + IConfig::Set($arr, 'activitypub', 'signed_data', $AS->data['signed_data'], false); + $j = json_decode($AS->data['signed_data'], true); + if ($j) { + IConfig::Set($arr, 'activitypub', 'rawmsg', json_encode(JSalmon::unpack($j['data'])), true); } } - - if($AS->data['signed_data']) { - IConfig::Set($arr,'activitystreams','signed_data',$AS->data['signed_data'],false); - } - - logger('FOF Activity received: ' . print_r($arr,true), LOGGER_DATA, LOG_DEBUG); + logger('FOF Activity received: ' . print_r($arr, true), 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_hash'] ],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); } @@ -1970,7 +2005,6 @@ class Libzot { return $ret; } - /** * @brief Remove community tag. * @@ -1984,12 +2018,12 @@ class Libzot { */ static function remove_community_tag($sender, $arr, $uid) { - if(! (activity_match($arr['verb'], ACTIVITY_TAG) && ($arr['obj_type'] == ACTIVITY_OBJ_TAGTERM))) + if (!(activity_match($arr['verb'], ACTIVITY_TAG) && ($arr['obj_type'] == ACTIVITY_OBJ_TAGTERM))) return; logger('remove_community_tag: invoked'); - if(! get_pconfig($uid,'system','blocktags')) { + if (!get_pconfig($uid, 'system', 'blocktags')) { logger('Permission denied.'); return; } @@ -1998,24 +2032,24 @@ class Libzot { dbesc($arr['mid']), intval($uid) ); - if(! $r) { + if (!$r) { logger('No item'); return; } - if(($sender != $r[0]['owner_xchan']) && ($sender != $r[0]['author_xchan'])) { + if (($sender != $r[0]['owner_xchan']) && ($sender != $r[0]['author_xchan'])) { logger('Sender not authorised.'); return; } $i = $r[0]; - if($i['target']) - $i['target'] = json_decode($i['target'],true); - if($i['object']) - $i['object'] = json_decode($i['object'],true); + if ($i['target']) + $i['target'] = json_decode($i['target'], true); + if ($i['object']) + $i['object'] = json_decode($i['object'], true); - if(! ($i['target'] && $i['object'])) { + if (!($i['target'] && $i['object'])) { logger('No target/object'); return; } @@ -2026,7 +2060,7 @@ class Libzot { dbesc($message_id), intval($uid) ); - if(! $r) { + if (!$r) { logger('No parent message'); return; } @@ -2038,28 +2072,28 @@ class Libzot { intval(TERM_HASHTAG), intval(TERM_COMMUNITYTAG), dbesc($i['object']['title']), - dbesc(get_rel_link($i['object']['link'],'alternate')) + dbesc(get_rel_link($i['object']['link'], 'alternate')) ); } /** * @brief Updates an imported item. * - * @see item_store_update() - * * @param string $sender * @param array $item * @param array $orig * @param int $uid * @param boolean $tag_delivery * @return void|array + * @see item_store_update() + * */ static function update_imported_item($sender, $item, $orig, $uid, $tag_delivery) { // If this is a comment being updated, remove any privacy information // so that item_store_update will set it from the original. - if($item['mid'] !== $item['parent_mid']) { + if ($item['mid'] !== $item['parent_mid']) { unset($item['allow_cid']); unset($item['allow_gid']); unset($item['deny_cid']); @@ -2070,7 +2104,7 @@ class Libzot { // we need the tag_delivery check for downstream flowing posts as the stored post // may have a different owner than the one being transmitted. - if(($sender != $orig['owner_xchan'] && $sender != $orig['author_xchan']) && (! $tag_delivery)) { + if (($sender != $orig['owner_xchan'] && $sender != $orig['author_xchan']) && (!$tag_delivery)) { logger('sender is not owner or author'); return; } @@ -2081,13 +2115,13 @@ class Libzot { // If we're updating an event that we've saved locally, we store the item info first // because event_addtocal will parse the body to get the 'new' event details - if($orig['resource_type'] === 'event') { + if ($orig['resource_type'] === 'event') { $res = event_addtocal($orig['id'], $uid); - if(! $res) + if (!$res) logger('update event: failed'); } - if(! $x['item_id']) + if (!$x['item_id']) logger('update_imported_item: failed: ' . $x['message']); else logger('update_imported_item'); @@ -2111,8 +2145,8 @@ class Libzot { logger('invoked', LOGGER_DEBUG); $ownership_valid = false; - $item_found = false; - $post_id = 0; + $item_found = false; + $post_id = 0; if ($item['verb'] === 'Tombstone') { // The id of the deleted thing is the item mid (activity id) @@ -2131,17 +2165,17 @@ class Libzot { dbesc($sender), dbesc($sender), dbesc($mid), - dbesc(str_replace('/activity/','/item/',$mid)), + dbesc(str_replace('/activity/', '/item/', $mid)), intval($uid) ); - if($r) { + if ($r) { $stored = $r[0]; // we proved ownership in the sql query $ownership_valid = true; - $post_id = $stored['id']; + $post_id = $stored['id']; $item_found = true; } else { @@ -2149,7 +2183,7 @@ class Libzot { logger('delete received for non-existent item or not owned by sender - ignoring.'); } - if($ownership_valid === false) { + if ($ownership_valid === false) { logger('delete_imported_item: failed: ownership issue'); return false; } @@ -2173,10 +2207,10 @@ class Libzot { } } - if($item_found) { - if(intval($stored['item_deleted'])) { + if ($item_found) { + if (intval($stored['item_deleted'])) { logger('delete_imported_item: item was already deleted'); - if(! $relay) + if (!$relay) return false; // This is a bit hackish, but may have to suffice until the notification/delivery loop is optimised @@ -2205,100 +2239,16 @@ class Libzot { return $post_id; } - static function process_mail_delivery($sender, $arr, $deliveries) { - - $result = array(); - - if($sender != $arr['from_xchan']) { - logger('process_mail_delivery: sender is not mail author'); - return; - } - - foreach($deliveries as $d) { - - $DR = new DReport(z_root(),$sender,$d,$arr['mid']); - - $r = q("select * from channel where channel_hash = '%s' limit 1", - dbesc($d['hash']) - ); - - if(! $r) { - $DR->update('recipient not found'); - $result[] = $DR->get(); - continue; - } - - $channel = $r[0]; - $DR->set_name($channel['channel_name'] . ' <' . channel_reddress($channel) . '>'); - - - if(! perm_is_allowed($channel['channel_id'],$sender,'post_mail')) { - - /* - * Always allow somebody to reply if you initiated the conversation. It's anti-social - * and a bit rude to send a private message to somebody and block their ability to respond. - * If you are being harrassed and want to put an end to it, delete the conversation. - */ - - $return = false; - if($arr['parent_mid']) { - $return = q("select * from mail where mid = '%s' and channel_id = %d limit 1", - dbesc($arr['parent_mid']), - intval($channel['channel_id']) - ); - } - if(! $return) { - logger("permission denied for mail delivery {$channel['channel_id']}"); - $DR->update('permission denied'); - $result[] = $DR->get(); - continue; - } - } - - - $r = q("select id from mail where mid = '%s' and channel_id = %d limit 1", - dbesc($arr['mid']), - intval($channel['channel_id']) - ); - if($r) { - if(intval($arr['mail_recalled'])) { - $x = q("delete from mail where id = %d and channel_id = %d", - intval($r[0]['id']), - intval($channel['channel_id']) - ); - $DR->update('mail recalled'); - $result[] = $DR->get(); - logger('mail_recalled'); - } - else { - $DR->update('duplicate mail received'); - $result[] = $DR->get(); - logger('duplicate mail received'); - } - continue; - } - else { - $arr['account_id'] = $channel['channel_account_id']; - $arr['channel_id'] = $channel['channel_id']; - $item_id = mail_store($arr); - $DR->update('mail delivered'); - $result[] = $DR->get(); - } - } - - return $result; - } - /** * @brief Processes delivery of profile. * - * @see import_directory_profile() - * - * @param string $sender + * @param string $sender * @param array $arr * @param array $deliveries (unused) * @return void + * @see import_directory_profile() + * */ static function process_profile_delivery($sender, $arr, $deliveries) { @@ -2307,7 +2257,7 @@ class Libzot { $r = q("select xchan_addr from xchan where xchan_hash = '%s' limit 1", dbesc($sender) ); - if($r) { + if ($r) { Libzotdir::import_directory_profile($sender, $arr, $r[0]['xchan_addr'], UPDATE_FLAGS_UPDATED, 0); } } @@ -2316,7 +2266,7 @@ class Libzot { /** * @brief * - * @param string $sender + * @param string $sender * @param array $arr * @param array $deliveries (unused) deliveries is irrelevant * @return void @@ -2329,16 +2279,16 @@ class Libzot { $r = q("select * from xchan where xchan_hash = '%s' limit 1", dbesc($sender) ); - if($r) { - $xchan = [ 'id' => $r[0]['xchan_guid'], 'id_sig' => $r[0]['xchan_guid_sig'], - 'hash' => $r[0]['xchan_hash'], 'public_key' => $r[0]['xchan_pubkey'] ]; - } - if(array_key_exists('locations',$arr) && $arr['locations']) { - $x = Libsync::sync_locations($xchan,$arr,true); - logger('results: ' . print_r($x,true), LOGGER_DEBUG); - if($x['changed']) { + if ($r) { + $xchan = ['id' => $r[0]['xchan_guid'], 'id_sig' => $r[0]['xchan_guid_sig'], + 'hash' => $r[0]['xchan_hash'], 'public_key' => $r[0]['xchan_pubkey']]; + } + if (array_key_exists('locations', $arr) && $arr['locations']) { + $x = Libsync::sync_locations($xchan, $arr, true); + logger('results: ' . print_r($x, true), LOGGER_DEBUG); + if ($x['changed']) { //$guid = random_string() . '@' . App::get_hostname(); - Libzotdir::update_modtime($sender,$r[0]['xchan_guid'],$arr['locations'][0]['address'],UPDATE_FLAGS_UPDATED); + Libzotdir::update_modtime($sender, $r[0]['xchan_guid'], $arr['locations'][0]['address'], UPDATE_FLAGS_UPDATED); } } } @@ -2365,10 +2315,10 @@ class Libzot { */ static function check_location_move($sender_hash, $locations) { - if(! $locations) + if (!$locations) return; - if(count($locations) != 1) + if (count($locations) != 1) return; $loc = $locations[0]; @@ -2377,10 +2327,10 @@ class Libzot { dbesc($sender_hash) ); - if(! $r) + if (!$r) return; - if($loc['url'] !== z_root()) { + if ($loc['url'] !== z_root()) { $x = q("update channel set channel_moved = '%s' where channel_hash = '%s' limit 1", dbesc($loc['url']), dbesc($sender_hash) @@ -2390,7 +2340,7 @@ class Libzot { // of the move on singleton networks $arr = [ - 'channel' => $r[0], + 'channel' => $r[0], 'locations' => $locations ]; /** @@ -2407,23 +2357,23 @@ class Libzot { /** * @brief Returns an array with all known distinct hubs for this channel. * - * @see self::get_hublocs() * @param array $channel an associative array which must contain * * \e string \b channel_hash the hash of the channel * @return array an array with associative arrays + * @see self::get_hublocs() */ static function encode_locations($channel) { $ret = []; $x = self::get_hublocs($channel['channel_hash']); - if($x && count($x)) { - foreach($x as $hub) { + if ($x && count($x)) { + foreach ($x as $hub) { // if this is a local channel that has been deleted, the hubloc is no good - make sure it is marked deleted // so that nobody tries to use it. - if(intval($channel['channel_removed']) && $hub['hubloc_url'] === z_root()) + if (intval($channel['channel_removed']) && $hub['hubloc_url'] === z_root()) $hub['hubloc_deleted'] = 1; @@ -2442,15 +2392,15 @@ class Libzot { // version compatibility tweaks - if(! strpos($z['url_sig'],'.')) { + if (!strpos($z['url_sig'], '.')) { $z['url_sig'] = 'sha256.' . $z['url_sig']; } - if(! $z['id_url']) { - $z['id_url'] = $z['url'] . '/channel/' . substr($z['address'],0,strpos($z['address'],'@')); + if (!$z['id_url']) { + $z['id_url'] = $z['url'] . '/channel/' . substr($z['address'], 0, strpos($z['address'], '@')); } - if(! $z['site_id']) { - $z['site_id'] = Libzot::make_xchan_hash($z['url'],$z['sitekey']); + if (!$z['site_id']) { + $z['site_id'] = Libzot::make_xchan_hash($z['url'], $z['sitekey']); } $ret[] = $z; @@ -2469,10 +2419,10 @@ class Libzot { */ static function import_site($arr) { - if( (! is_array($arr)) || (! $arr['url']) || (! $arr['site_sig'])) + if ((!is_array($arr)) || (!$arr['url']) || (!$arr['site_sig'])) return false; - if(! self::verify($arr['url'], $arr['site_sig'], $arr['sitekey'])) { + if (!self::verify($arr['url'], $arr['site_sig'], $arr['sitekey'])) { logger('Bad url_sig'); return false; } @@ -2483,66 +2433,66 @@ class Libzot { $r = q("select * from site where site_url = '%s' limit 1", dbesc($arr['url']) ); - if($r) { - $exists = true; + if ($r) { + $exists = true; $siterecord = $r[0]; } $site_directory = 0; - if($arr['directory_mode'] == 'normal') + if ($arr['directory_mode'] == 'normal') $site_directory = DIRECTORY_MODE_NORMAL; - if($arr['directory_mode'] == 'primary') + if ($arr['directory_mode'] == 'primary') $site_directory = DIRECTORY_MODE_PRIMARY; - if($arr['directory_mode'] == 'secondary') + if ($arr['directory_mode'] == 'secondary') $site_directory = DIRECTORY_MODE_SECONDARY; - if($arr['directory_mode'] == 'standalone') + if ($arr['directory_mode'] == 'standalone') $site_directory = DIRECTORY_MODE_STANDALONE; $register_policy = 0; - if($arr['register_policy'] == 'closed') + if ($arr['register_policy'] == 'closed') $register_policy = REGISTER_CLOSED; - if($arr['register_policy'] == 'open') + if ($arr['register_policy'] == 'open') $register_policy = REGISTER_OPEN; - if($arr['register_policy'] == 'approve') + if ($arr['register_policy'] == 'approve') $register_policy = REGISTER_APPROVE; $access_policy = 0; - if(array_key_exists('access_policy',$arr)) { - if($arr['access_policy'] === 'private') + if (array_key_exists('access_policy', $arr)) { + if ($arr['access_policy'] === 'private') $access_policy = ACCESS_PRIVATE; - if($arr['access_policy'] === 'paid') + if ($arr['access_policy'] === 'paid') $access_policy = ACCESS_PAID; - if($arr['access_policy'] === 'free') + if ($arr['access_policy'] === 'free') $access_policy = ACCESS_FREE; - if($arr['access_policy'] === 'tiered') + if ($arr['access_policy'] === 'tiered') $access_policy = ACCESS_TIERED; } // don't let insecure sites register as public hubs - if(strpos($arr['url'],'https://') === false) + if (strpos($arr['url'], 'https://') === false) $access_policy = ACCESS_PRIVATE; - if($access_policy != ACCESS_PRIVATE) { + if ($access_policy != ACCESS_PRIVATE) { $x = z_fetch_url($arr['url'] . '/siteinfo.json'); - if(! $x['success']) + if (!$x['success']) $access_policy = ACCESS_PRIVATE; } - $directory_url = htmlspecialchars($arr['directory_url'],ENT_COMPAT,'UTF-8',false); - $url = htmlspecialchars(strtolower($arr['url']),ENT_COMPAT,'UTF-8',false); - $sellpage = htmlspecialchars($arr['sellpage'],ENT_COMPAT,'UTF-8',false); - $site_location = htmlspecialchars($arr['location'],ENT_COMPAT,'UTF-8',false); - $site_realm = htmlspecialchars($arr['realm'],ENT_COMPAT,'UTF-8',false); - $site_project = htmlspecialchars($arr['project'],ENT_COMPAT,'UTF-8',false); - $site_crypto = ((array_key_exists('encryption',$arr) && is_array($arr['encryption'])) ? htmlspecialchars(implode(',',$arr['encryption']),ENT_COMPAT,'UTF-8',false) : ''); - $site_version = ((array_key_exists('version',$arr)) ? htmlspecialchars($arr['version'],ENT_COMPAT,'UTF-8',false) : ''); + $directory_url = htmlspecialchars($arr['directory_url'], ENT_COMPAT, 'UTF-8', false); + $url = htmlspecialchars(strtolower($arr['url']), ENT_COMPAT, 'UTF-8', false); + $sellpage = htmlspecialchars($arr['sellpage'], ENT_COMPAT, 'UTF-8', false); + $site_location = htmlspecialchars($arr['location'], ENT_COMPAT, 'UTF-8', false); + $site_realm = htmlspecialchars($arr['realm'], ENT_COMPAT, 'UTF-8', false); + $site_project = htmlspecialchars($arr['project'], ENT_COMPAT, 'UTF-8', false); + $site_crypto = ((array_key_exists('encryption', $arr) && is_array($arr['encryption'])) ? htmlspecialchars(implode(',', $arr['encryption']), ENT_COMPAT, 'UTF-8', false) : ''); + $site_version = ((array_key_exists('version', $arr)) ? htmlspecialchars($arr['version'], ENT_COMPAT, 'UTF-8', false) : ''); // You can have one and only one primary directory per realm. // Downgrade any others claiming to be primary. As they have // flubbed up this badly already, don't let them be directory servers at all. - if(($site_directory === DIRECTORY_MODE_PRIMARY) + if (($site_directory === DIRECTORY_MODE_PRIMARY) && ($site_realm === get_directory_realm()) && ($arr['url'] != get_directory_primary())) { $site_directory = DIRECTORY_MODE_NORMAL; @@ -2550,12 +2500,12 @@ class Libzot { $site_flags = $site_directory; - if(array_key_exists('zot',$arr)) { - set_sconfig($arr['url'],'system','zot_version',$arr['zot']); + if (array_key_exists('zot', $arr)) { + set_sconfig($arr['url'], 'system', 'zot_version', $arr['zot']); } - if($exists) { - if(($siterecord['site_flags'] != $site_flags) + if ($exists) { + if (($siterecord['site_flags'] != $site_flags) || ($siterecord['site_access'] != $access_policy) || ($siterecord['site_directory'] != $directory_url) || ($siterecord['site_sellpage'] != $sellpage) @@ -2564,12 +2514,12 @@ class Libzot { || ($siterecord['site_project'] != $site_project) || ($siterecord['site_realm'] != $site_realm) || ($siterecord['site_crypto'] != $site_crypto) - || ($siterecord['site_version'] != $site_version) ) { + || ($siterecord['site_version'] != $site_version)) { $update = true; - // logger('import_site: input: ' . print_r($arr,true)); - // logger('import_site: stored: ' . print_r($siterecord,true)); + // logger('import_site: input: ' . print_r($arr,true)); + // logger('import_site: stored: ' . print_r($siterecord,true)); $r = q("update site set site_dead = 0, site_location = '%s', site_flags = %d, site_access = %d, site_directory = '%s', site_register = %d, site_update = '%s', site_sellpage = '%s', site_realm = '%s', site_type = %d, site_project = '%s', site_version = '%s', site_crypto = '%s' where site_url = '%s'", @@ -2587,8 +2537,8 @@ class Libzot { dbesc($site_crypto), dbesc($url) ); - if(! $r) { - logger('Update failed. ' . print_r($arr,true)); + if (!$r) { + logger('Update failed. ' . print_r($arr, true)); } } else { @@ -2620,8 +2570,8 @@ class Libzot { ] ); - if(! $r) { - logger('Record create failed. ' . print_r($arr,true)); + if (!$r) { + logger('Record create failed. ' . print_r($arr, true)); } } @@ -2631,14 +2581,14 @@ class Libzot { /** * @brief Returns path to /rpost * - * @todo We probably should make rpost discoverable. - * * @param array $observer * * \e string \b xchan_url * @return string + * @todo We probably should make rpost discoverable. + * */ static function get_rpost_path($observer) { - if(! $observer) + if (!$observer) return ''; $parsed = parse_url($observer['xchan_url']); @@ -2659,39 +2609,44 @@ class Libzot { // we may only end up with one; which results in posts with no author name or photo and are a bit // of a hassle to repair. If either or both are missing, do a full discovery probe. - if(! array_key_exists('id',$x)) { - return import_author_activitypub($x); + if(!isset($x['id']) && !isset($x['key']) && !isset($x['id_sig'])) { + return false; } - $hash = self::make_xchan_hash($x['id'],$x['key']); + $hash = self::make_xchan_hash($x['id'], $x['key']); $desturl = $x['url']; + $found_primary = false; + $r1 = q("select hubloc_url, hubloc_updated, site_dead from hubloc left join site on hubloc_url = site_url where hubloc_guid = '%s' and hubloc_guid_sig = '%s' and hubloc_primary = 1 limit 1", dbesc($x['id']), dbesc($x['id_sig']) ); + if ($r1) { + $found_primary = true; + } $r2 = q("select xchan_hash from xchan where xchan_guid = '%s' and xchan_guid_sig = '%s' limit 1", dbesc($x['id']), dbesc($x['id_sig']) ); - $site_dead = false; + $primary_dead = false; - if($r1 && intval($r1[0]['site_dead'])) { - $site_dead = true; + if ($r1 && intval($r1[0]['site_dead'])) { + $primary_dead = true; } // We have valid and somewhat fresh information. Always true if it is our own site. - if($r1 && $r2 && ( $r1[0]['hubloc_updated'] > datetime_convert('UTC','UTC','now - 1 week') || $r1[0]['hubloc_url'] === z_root() ) ) { + if ($r1 && $r2 && ($r1[0]['hubloc_updated'] > datetime_convert('UTC', 'UTC', 'now - 1 week') || $r1[0]['hubloc_url'] === z_root())) { logger('in cache', LOGGER_DEBUG); return $hash; } - logger('not in cache or cache stale - probing: ' . print_r($x,true), LOGGER_DEBUG,LOG_INFO); + logger('not in cache or cache stale - probing: ' . print_r($x, true), LOGGER_DEBUG, LOG_INFO); // The primary hub may be dead. Try to find another one associated with this identity that is // still alive. If we find one, use that url for the discovery/refresh probe. Otherwise, the dead site @@ -2699,24 +2654,25 @@ class Libzot { // cached entry and the identity is valid. It's just unreachable until they bring back their // server from the grave or create another clone elsewhere. - if($site_dead) { - logger('dead site - ignoring', LOGGER_DEBUG,LOG_INFO); + if ($primary_dead || ! $found_primary) { + logger('dead or unknown primary site - ignoring', LOGGER_DEBUG, LOG_INFO); $r = q("select hubloc_id_url from hubloc left join site on hubloc_url = site_url where hubloc_hash = '%s' and site_dead = 0", dbesc($hash) ); - if($r) { - logger('found another site that is not dead: ' . $r[0]['hubloc_url'], LOGGER_DEBUG,LOG_INFO); - $desturl = $r[0]['hubloc_url']; + + if ($r) { + logger('found another site that is not dead: ' . $r[0]['hubloc_id_url'], LOGGER_DEBUG, LOG_INFO); + $desturl = $r[0]['hubloc_id_url']; } else { return $hash; } } - $them = [ 'hubloc_id_url' => $desturl ]; - if(self::refresh($them)) + $them = ['hubloc_id_url' => $desturl]; + if (self::refresh($them)) return $hash; return false; @@ -2724,27 +2680,27 @@ class Libzot { static function zotinfo($arr) { - logger('arr: ' . print_r($arr,true)); + logger('arr: ' . print_r($arr, true)); $ret = []; - $zhash = ((x($arr,'guid_hash')) ? $arr['guid_hash'] : ''); - $zguid = ((x($arr,'guid')) ? $arr['guid'] : ''); - $zguid_sig = ((x($arr,'guid_sig')) ? $arr['guid_sig'] : ''); - $zaddr = ((x($arr,'address')) ? $arr['address'] : ''); - $ztarget = ((x($arr,'target_url')) ? $arr['target_url'] : ''); - $zsig = ((x($arr,'target_sig')) ? $arr['target_sig'] : ''); - $zkey = ((x($arr,'key')) ? $arr['key'] : ''); - $mindate = ((x($arr,'mindate')) ? $arr['mindate'] : ''); - $token = ((x($arr,'token')) ? $arr['token'] : ''); - $feed = ((x($arr,'feed')) ? intval($arr['feed']) : 0); - - if($ztarget) { + $zhash = ((x($arr, 'guid_hash')) ? $arr['guid_hash'] : ''); + $zguid = ((x($arr, 'guid')) ? $arr['guid'] : ''); + $zguid_sig = ((x($arr, 'guid_sig')) ? $arr['guid_sig'] : ''); + $zaddr = ((x($arr, 'address')) ? $arr['address'] : ''); + $ztarget = ((x($arr, 'target_url')) ? $arr['target_url'] : ''); + $zsig = ((x($arr, 'target_sig')) ? $arr['target_sig'] : ''); + $zkey = ((x($arr, 'key')) ? $arr['key'] : ''); + $mindate = ((x($arr, 'mindate')) ? $arr['mindate'] : ''); + $token = ((x($arr, 'token')) ? $arr['token'] : ''); + $feed = ((x($arr, 'feed')) ? intval($arr['feed']) : 0); + + if ($ztarget) { $t = q("select * from hubloc where hubloc_id_url = '%s' and hubloc_network = 'zot6' limit 1", dbesc($ztarget) ); - if($t) { + if ($t) { $ztarget_hash = $t[0]['hubloc_hash']; @@ -2762,21 +2718,21 @@ class Libzot { $r = null; - if(strlen($zhash)) { + if (strlen($zhash)) { $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)) { + elseif (strlen($zguid) && strlen($zguid_sig)) { $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) ); } - elseif(strlen($zaddr)) { - if(strpos($zaddr,'[system]') === false) { /* normal address lookup */ + elseif (strlen($zaddr)) { + if (strpos($zaddr, '[system]') === false) { /* normal address lookup */ $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), @@ -2799,7 +2755,7 @@ class Libzot { $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) { + if (!$r) { $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"); } @@ -2807,45 +2763,44 @@ class Libzot { } else { $ret['message'] = 'Invalid request'; - return($ret); + return ($ret); } - if(! $r) { + if (!$r) { $ret['message'] = 'Item not found.'; - return($ret); + return ($ret); } $e = $r[0]; - $id = $e['channel_id']; - $sys_channel = (intval($e['channel_system']) ? true : false); - $special_channel = (($e['channel_pageflags'] & PAGE_PREMIUM) ? true : false); - $adult_channel = (($e['channel_pageflags'] & PAGE_ADULT) ? true : false); + $sys_channel = (intval($e['channel_system']) ? true : false); + $special_channel = (($e['channel_pageflags'] & PAGE_PREMIUM) ? true : false); + $adult_channel = (($e['channel_pageflags'] & PAGE_ADULT) ? true : false); $censored = (($e['channel_pageflags'] & PAGE_CENSORED) ? true : false); - $searchable = (($e['channel_pageflags'] & PAGE_HIDDEN) ? false : true); + $searchable = (($e['channel_pageflags'] & PAGE_HIDDEN) ? false : true); $deleted = (intval($e['xchan_deleted']) ? true : false); - if($deleted || $censored || $sys_channel) + if ($deleted || $censored || $sys_channel) $searchable = false; $public_forum = false; - $role = get_pconfig($e['channel_id'],'system','permissions_role'); - if($role === 'forum' || $role === 'repository') { + $role = get_pconfig($e['channel_id'], 'system', 'permissions_role'); + if ($role === 'forum' || $role === 'repository') { $public_forum = true; } else { // check if it has characteristics of a public forum based on custom permissions. $m = Permissions::FilledAutoperms($e['channel_id']); - if($m) { - foreach($m as $k => $v) { - if($k == 'tag_deliver' && intval($v) == 1) - $ch ++; - if($k == 'send_stream' && intval($v) == 0) - $ch ++; - } - if($ch == 2) + if ($m) { + foreach ($m as $k => $v) { + if ($k == 'tag_deliver' && intval($v) == 1) + $ch++; + if ($k == 'send_stream' && intval($v) == 0) + $ch++; + } + if ($ch == 2) $public_forum = true; } } @@ -2856,128 +2811,141 @@ class Libzot { intval($e['channel_id']) ); - $profile = array(); + $profile = []; - if($p) { + if ($p) { - if(! intval($p[0]['publish'])) + if (!intval($p[0]['publish'])) $searchable = false; - $profile['description'] = $p[0]['pdesc']; - $profile['birthday'] = $p[0]['dob']; - if(($profile['birthday'] != '0000-00-00') && (($bd = z_birthday($p[0]['dob'],'UTC')) !== '')) + $profile['description'] = $p[0]['pdesc']; + $profile['birthday'] = $p[0]['dob']; + if (($profile['birthday'] != '0000-00-00') && (($bd = z_birthday($p[0]['dob'], 'UTC')) !== '')) $profile['next_birthday'] = $bd; - if($age = age($p[0]['dob'],$e['channel_timezone'],'')) + if ($age = age($p[0]['dob'], $e['channel_timezone'], '')) $profile['age'] = $age; - $profile['gender'] = $p[0]['gender']; - $profile['marital'] = $p[0]['marital']; - $profile['sexual'] = $p[0]['sexual']; - $profile['locale'] = $p[0]['locality']; - $profile['region'] = $p[0]['region']; - $profile['postcode'] = $p[0]['postal_code']; - $profile['country'] = $p[0]['country_name']; - $profile['about'] = $p[0]['about']; - $profile['homepage'] = $p[0]['homepage']; - $profile['hometown'] = $p[0]['hometown']; - - if($p[0]['keywords']) { - $tags = array(); - $k = explode(' ',$p[0]['keywords']); - if($k) { - foreach($k as $kk) { - if(trim($kk," \t\n\r\0\x0B,")) { - $tags[] = trim($kk," \t\n\r\0\x0B,"); + $profile['gender'] = $p[0]['gender']; + $profile['marital'] = $p[0]['marital']; + $profile['sexual'] = $p[0]['sexual']; + $profile['locale'] = $p[0]['locality']; + $profile['region'] = $p[0]['region']; + $profile['postcode'] = $p[0]['postal_code']; + $profile['country'] = $p[0]['country_name']; + $profile['about'] = $p[0]['about']; + $profile['homepage'] = $p[0]['homepage']; + $profile['hometown'] = $p[0]['hometown']; + + if ($p[0]['keywords']) { + $tags = []; + $k = explode(' ', $p[0]['keywords']); + if ($k) { + foreach ($k as $kk) { + if (trim($kk, " \t\n\r\0\x0B,")) { + $tags[] = trim($kk, " \t\n\r\0\x0B,"); } } } - if($tags) + if ($tags) $profile['keywords'] = $tags; } } // Communication details - $ret['id'] = $e['xchan_guid']; - $ret['id_sig'] = self::sign($e['xchan_guid'], $e['channel_prvkey']); + $ret['id'] = $e['xchan_guid']; + $ret['id_sig'] = self::sign($e['xchan_guid'], $e['channel_prvkey']); $ret['primary_location'] = [ - 'address' => $e['xchan_addr'], - 'url' => $e['xchan_url'], - 'connections_url' => $e['xchan_connurl'], - 'follow_url' => $e['xchan_follow'], + 'address' => $e['xchan_addr'], + 'url' => $e['xchan_url'], + 'connections_url' => $e['xchan_connurl'], + 'follow_url' => $e['xchan_follow'], ]; - $ret['public_key'] = $e['xchan_pubkey']; - $ret['username'] = $e['channel_address']; - $ret['name'] = $e['xchan_name']; - $ret['name_updated'] = $e['xchan_name_date']; - $ret['photo'] = [ + $ret['public_key'] = $e['xchan_pubkey']; + $ret['username'] = $e['channel_address']; + $ret['name'] = $e['xchan_name']; + $ret['name_updated'] = $e['xchan_name_date']; + $ret['photo'] = [ 'url' => $e['xchan_photo_l'], 'type' => $e['xchan_photo_mimetype'], 'updated' => $e['xchan_photo_date'] ]; - $ret['channel_role'] = get_pconfig($e['channel_id'],'system','permissions_role','custom'); - $ret['protocols'] = [ 'zot6', 'zot' ]; - $ret['searchable'] = $searchable; - $ret['adult_content'] = $adult_channel; - $ret['public_forum'] = $public_forum; + $ret['channel_role'] = get_pconfig($e['channel_id'], 'system', 'permissions_role', 'custom'); + + $hookinfo = [ + 'channel_id' => $id, + 'protocols' => ['zot6'] + ]; + /** + * @hooks channel_protocols + * * \e int \b channel_id + * * \e array \b protocols + */ + call_hooks('channel_protocols', $hookinfo); + + $ret['protocols'] = $hookinfo['protocols']; + $ret['searchable'] = $searchable; + $ret['adult_content'] = $adult_channel; + $ret['public_forum'] = $public_forum; - $ret['comments'] = map_scope(PermissionLimits::Get($e['channel_id'],'post_comments')); - $ret['mail'] = map_scope(PermissionLimits::Get($e['channel_id'],'post_mail')); + $ret['comments'] = map_scope(PermissionLimits::Get($e['channel_id'], 'post_comments')); + $ret['mail'] = map_scope(PermissionLimits::Get($e['channel_id'], 'post_mail')); - if($deleted) - $ret['deleted'] = $deleted; + if ($deleted) + $ret['deleted'] = $deleted; - if(intval($e['channel_removed'])) + if (intval($e['channel_removed'])) { $ret['deleted_locally'] = true; + } // premium or other channel desiring some contact with potential followers before connecting. // This is a template - %s will be replaced with the follow_url we discover for the return channel. - if($special_channel) { + if ($special_channel) { $ret['connect_url'] = (($e['xchan_connpage']) ? $e['xchan_connpage'] : z_root() . '/connect/' . $e['channel_address']); } // This is a template for our follow url, %s will be replaced with a webbie - if(! $ret['follow_url']) + if (!$ret['follow_url']) $ret['follow_url'] = z_root() . '/follow?f=&url=%s'; - $permissions = get_all_perms($e['channel_id'],$ztarget_hash,false,false); + $permissions = get_all_perms($e['channel_id'], $ztarget_hash, false, false); - if($ztarget_hash) { + if ($ztarget_hash) { $permissions['connected'] = false; - $b = q("select * from abook where abook_xchan = '%s' and abook_channel = %d limit 1", + $b = q("select * from abook where abook_xchan = '%s' and abook_channel = %d limit 1", dbesc($ztarget_hash), intval($e['channel_id']) ); - if($b) + if ($b) $permissions['connected'] = true; } - if($permissions['view_profile']) - $ret['profile'] = $profile; + if ($permissions['view_profile']) + $ret['profile'] = $profile; $concise_perms = []; - if($permissions) { - foreach($permissions as $k => $v) { - if($v) { + if ($permissions) { + foreach ($permissions as $k => $v) { + if ($v) { $concise_perms[] = $k; } } - $permissions = implode(',',$concise_perms); + $permissions = implode(',', $concise_perms); } - $ret['permissions'] = $permissions; - $ret['permissions_for'] = $ztarget; + $ret['permissions'] = $permissions; + $ret['permissions_for'] = $ztarget; // array of (verified) hubs this channel uses $x = self::encode_locations($e); - if($x) + if ($x) $ret['locations'] = $x; $ret['site'] = self::site_info(); @@ -2997,58 +2965,58 @@ class Libzot { */ static function site_info() { - $signing_key = get_config('system','prvkey'); - $sig_method = get_config('system','signature_algorithm','sha256'); + $signing_key = get_config('system', 'prvkey'); + $sig_method = get_config('system', 'signature_algorithm', 'sha256'); - $ret = []; - $ret['site'] = []; - $ret['site']['url'] = z_root(); - $ret['site']['site_sig'] = self::sign(z_root(), $signing_key); - $ret['site']['post'] = z_root() . '/zot'; + $ret = []; + $ret['site'] = []; + $ret['site']['url'] = z_root(); + $ret['site']['site_sig'] = self::sign(z_root(), $signing_key); + $ret['site']['post'] = z_root() . '/zot'; $ret['site']['openWebAuth'] = z_root() . '/owa'; $ret['site']['authRedirect'] = z_root() . '/magic'; - $ret['site']['sitekey'] = get_config('system','pubkey'); + $ret['site']['sitekey'] = get_config('system', 'pubkey'); - $dirmode = get_config('system','directory_mode'); - if(($dirmode === false) || ($dirmode == DIRECTORY_MODE_NORMAL)) + $dirmode = get_config('system', 'directory_mode'); + if (($dirmode === false) || ($dirmode == DIRECTORY_MODE_NORMAL)) $ret['site']['directory_mode'] = 'normal'; - if($dirmode == DIRECTORY_MODE_PRIMARY) + if ($dirmode == DIRECTORY_MODE_PRIMARY) $ret['site']['directory_mode'] = 'primary'; - elseif($dirmode == DIRECTORY_MODE_SECONDARY) + elseif ($dirmode == DIRECTORY_MODE_SECONDARY) $ret['site']['directory_mode'] = 'secondary'; - elseif($dirmode == DIRECTORY_MODE_STANDALONE) + elseif ($dirmode == DIRECTORY_MODE_STANDALONE) $ret['site']['directory_mode'] = 'standalone'; - if($dirmode != DIRECTORY_MODE_NORMAL) + if ($dirmode != DIRECTORY_MODE_NORMAL) $ret['site']['directory_url'] = z_root() . '/dirsearch'; - $ret['site']['encryption'] = crypto_methods(); - $ret['site']['zot'] = System::get_zot_revision(); + $ret['site']['encryption'] = Crypto::methods(); + $ret['site']['zot'] = System::get_zot_revision(); // hide detailed site information if you're off the grid - if($dirmode != DIRECTORY_MODE_STANDALONE) { + if ($dirmode != DIRECTORY_MODE_STANDALONE) { - $register_policy = intval(get_config('system','register_policy')); + $register_policy = intval(get_config('system', 'register_policy')); - if($register_policy == REGISTER_CLOSED) + if ($register_policy == REGISTER_CLOSED) $ret['site']['register_policy'] = 'closed'; - if($register_policy == REGISTER_APPROVE) + if ($register_policy == REGISTER_APPROVE) $ret['site']['register_policy'] = 'approve'; - if($register_policy == REGISTER_OPEN) + if ($register_policy == REGISTER_OPEN) $ret['site']['register_policy'] = 'open'; - $access_policy = intval(get_config('system','access_policy')); + $access_policy = intval(get_config('system', 'access_policy')); - if($access_policy == ACCESS_PRIVATE) + if ($access_policy == ACCESS_PRIVATE) $ret['site']['access_policy'] = 'private'; - if($access_policy == ACCESS_PAID) + if ($access_policy == ACCESS_PAID) $ret['site']['access_policy'] = 'paid'; - if($access_policy == ACCESS_FREE) + if ($access_policy == ACCESS_FREE) $ret['site']['access_policy'] = 'free'; - if($access_policy == ACCESS_TIERED) + if ($access_policy == ACCESS_TIERED) $ret['site']['access_policy'] = 'tiered'; $ret['site']['accounts'] = account_total(); @@ -3056,24 +3024,24 @@ class Libzot { require_once('include/channel.php'); $ret['site']['channels'] = channel_total(); - $ret['site']['admin'] = get_config('system','admin_email'); + $ret['site']['admin'] = get_config('system', 'admin_email'); - $visible_plugins = array(); - if(is_array(\App::$plugins) && count(\App::$plugins)) { + $visible_plugins = []; + if (is_array(\App::$plugins) && count(\App::$plugins)) { $r = q("select * from addon where hidden = 0"); - if($r) - foreach($r as $rr) + if ($r) + foreach ($r as $rr) $visible_plugins[] = $rr['aname']; } - $ret['site']['plugins'] = $visible_plugins; - $ret['site']['sitehash'] = get_config('system','location_hash'); - $ret['site']['sitename'] = get_config('system','sitename'); - $ret['site']['sellpage'] = get_config('system','sellpage'); - $ret['site']['location'] = get_config('system','site_location'); - $ret['site']['realm'] = get_directory_realm(); - $ret['site']['project'] = System::get_platform_name(); - $ret['site']['version'] = System::get_project_version(); + $ret['site']['plugins'] = $visible_plugins; + $ret['site']['sitehash'] = get_config('system', 'location_hash'); + $ret['site']['sitename'] = get_config('system', 'sitename'); + $ret['site']['sellpage'] = get_config('system', 'sellpage'); + $ret['site']['location'] = get_config('system', 'site_location'); + $ret['site']['realm'] = get_directory_realm(); + $ret['site']['project'] = System::get_platform_name(); + $ret['site']['version'] = System::get_project_version(); } @@ -3143,6 +3111,11 @@ class Libzot { ); } + // this site obviously isn't dead because they are trying to communicate with us. + q("update site set site_dead = 0 where site_dead = 1 and site_url = '%s' ", + dbesc($hub['hubloc_url']) + ); + return $hub['hubloc_url']; } @@ -3154,36 +3127,36 @@ class Libzot { * @param string $alg (optional) default 'sha256' * @return string */ - static function sign($data,$key,$alg = 'sha256') { - if(! $key) + static function sign($data, $key, $alg = 'sha256') { + if (!$key) return 'no key'; $sig = ''; - openssl_sign($data,$sig,$key,$alg); + openssl_sign($data, $sig, $key, $alg); return $alg . '.' . base64url_encode($sig); } - static function verify($data,$sig,$key) { + static function verify($data, $sig, $key) { $verify = 0; - $x = explode('.',$sig,2); + $x = explode('.', $sig, 2); if ($key && count($x) === 2) { - $alg = $x[0]; + $alg = $x[0]; $signature = base64url_decode($x[1]); - $verify = @openssl_verify($data,$signature,$key,$alg); + $verify = @openssl_verify($data, $signature, $key, $alg); if ($verify === (-1)) { while ($msg = openssl_error_string()) { - logger('openssl_verify: ' . $msg,LOGGER_NORMAL,LOG_ERR); + logger('openssl_verify: ' . $msg, LOGGER_NORMAL, LOG_ERR); } btlogger('openssl_verify: key: ' . $key, LOGGER_DEBUG, LOG_ERR); } } - return(($verify > 0) ? true : false); + return (($verify > 0) ? true : false); } /** @@ -3192,25 +3165,20 @@ class Libzot { * @return boolean */ static function is_zot_request() { - $x = getBestSupportedMimeType([ 'application/x-zot+json' ]); + $x = getBestSupportedMimeType(['application/x-zot+json']); - return(($x) ? true : false); + return (($x) ? true : false); } static public function zot_record_preferred($arr, $check = 'hubloc_network') { - if(! $arr) { + if (!$arr) { return $arr; } - foreach($arr as $v) { - if($v[$check] === 'zot6') { - return $v; - } - } - foreach($arr as $v) { - if($v[$check] === 'zot') { + foreach ($arr as $v) { + if ($v[$check] === 'zot6') { return $v; } } @@ -3219,4 +3187,10 @@ class Libzot { } + static function update_cached_hubloc($hubloc) { + if ($hubloc['hubloc_updated'] > datetime_convert('UTC','UTC','now - 1 week') || $hubloc['hubloc_url'] === z_root()) { + return; + } + self::refresh( [ 'hubloc_id_url' => $hubloc['hubloc_id_url'] ] ); + } } diff --git a/Zotlabs/Lib/Libzotdir.php b/Zotlabs/Lib/Libzotdir.php index b02516a98..4f35a1b80 100644 --- a/Zotlabs/Lib/Libzotdir.php +++ b/Zotlabs/Lib/Libzotdir.php @@ -19,7 +19,6 @@ class Libzotdir { */ static function find_upstream_directory($dirmode) { - global $DIRECTORY_FALLBACK_SERVERS; $preferred = get_config('system','directory_server'); @@ -31,7 +30,7 @@ class Libzotdir { ); if(($r) && ($r[0]['site_flags'] & DIRECTORY_MODE_STANDALONE)) { $preferred = ''; - } + } } @@ -42,19 +41,21 @@ class Libzotdir { * from our list of directory servers. However, if we're a directory * server ourself, point at the local instance * We will then set this value so this should only ever happen once. - * Ideally there will be an admin setting to change to a different + * Ideally there will be an admin setting to change to a different * directory server if you don't like our choice or if circumstances change. */ + $directory_fallback_servers = get_directory_fallback_servers(); + $dirmode = intval(get_config('system','directory_mode')); if ($dirmode == DIRECTORY_MODE_NORMAL) { - $toss = mt_rand(0,count($DIRECTORY_FALLBACK_SERVERS)); - $preferred = $DIRECTORY_FALLBACK_SERVERS[$toss]; + $toss = mt_rand(0,count($directory_fallback_servers)); + $preferred = $directory_fallback_servers[$toss]; if(! $preferred) { $preferred = DIRECTORY_FALLBACK_MASTER; } set_config('system','directory_server',$preferred); - } + } else { set_config('system','directory_server',z_root()); } @@ -108,7 +109,7 @@ class Libzotdir { $ret = get_config('directory', $setting); - // 'safemode' is the default if there is no observer or no established preference. + // 'safemode' is the default if there is no observer or no established preference. if($setting === 'safemode' && $ret === false) $ret = 1; @@ -175,8 +176,8 @@ class Libzotdir { * * Checks the directory mode of this hub to see if it is some form of directory server. If it is, * get the directory realm of this hub. Fetch a list of all other directory servers in this realm and request - * a directory sync packet. This will contain both directory updates and new ratings. Store these all in the DB. - * In the case of updates, we will query each of them asynchronously from a poller task. Ratings are stored + * a directory sync packet. This will contain both directory updates and new ratings. Store these all in the DB. + * In the case of updates, we will query each of them asynchronously from a poller task. Ratings are stored * directly if the rater's signature matches. * * @param int $dirmode; @@ -188,16 +189,17 @@ class Libzotdir { return; $realm = get_directory_realm(); + if ($realm == DIRECTORY_REALM) { - $r = q("select * from site where (site_flags & %d) > 0 and site_url != '%s' and site_type = %d and ( site_realm = '%s' or site_realm = '') ", + $r = q("select * from site where (site_flags & %d) > 0 and site_url != '%s' and site_type = %d and ( site_realm = '%s' or site_realm = '') and site_dead = 0", intval(DIRECTORY_MODE_PRIMARY|DIRECTORY_MODE_SECONDARY), dbesc(z_root()), intval(SITE_TYPE_ZOT), dbesc($realm) ); - } + } else { - $r = q("select * from site where (site_flags & %d) > 0 and site_url != '%s' and site_realm like '%s' and site_type = %d ", + $r = q("select * from site where (site_flags & %d) > 0 and site_url != '%s' and site_realm like '%s' and site_type = %d and site_dead = 0", intval(DIRECTORY_MODE_PRIMARY|DIRECTORY_MODE_SECONDARY), dbesc(z_root()), dbesc(protect_sprintf('%' . $realm . '%')), @@ -214,14 +216,14 @@ class Libzotdir { [ 'site_url' => DIRECTORY_FALLBACK_MASTER, 'site_flags' => DIRECTORY_MODE_PRIMARY, - 'site_update' => NULL_DATE, + 'site_update' => NULL_DATE, 'site_directory' => DIRECTORY_FALLBACK_MASTER . '/dirsearch', 'site_realm' => DIRECTORY_REALM, 'site_valid' => 1, ] ); - $r = q("select * from site where site_flags in (%d, %d) and site_url != '%s' and site_type = %d ", + $r = q("select * from site where site_flags in (%d, %d) and site_url != '%s' and site_type = %d and site_dead = 0", intval(DIRECTORY_MODE_PRIMARY), intval(DIRECTORY_MODE_SECONDARY), dbesc(z_root()), @@ -250,7 +252,7 @@ class Libzotdir { continue; $j = json_decode($x['body'],true); - if (!($j['transactions']) || ($j['ratings'])) + if (!$j['transactions']) continue; q("update site set site_sync = '%s' where site_url = '%s'", @@ -262,6 +264,11 @@ class Libzotdir { if (is_array($j['transactions']) && count($j['transactions'])) { foreach ($j['transactions'] as $t) { + + if (empty($t['hash']) || empty($t['transaction_id']) || empty($t['address'])) { + continue; + } + $r = q("select * from updates where ud_guid = '%s' limit 1", dbesc($t['transaction_id']) ); @@ -273,7 +280,7 @@ class Libzotdir { $ud_flags |= UPDATE_FLAGS_DELETED; if (is_array($t['flags']) && in_array('forced',$t['flags'])) $ud_flags |= UPDATE_FLAGS_FORCED; - + $z = q("insert into updates ( ud_hash, ud_guid, ud_date, ud_flags, ud_addr ) values ( '%s', '%s', '%s', %d, '%s' ) ", dbesc($t['hash']), @@ -308,13 +315,22 @@ class Libzotdir { if ($ud['ud_addr'] && (! ($ud['ud_flags'] & UPDATE_FLAGS_DELETED))) { $success = false; + $zf = []; $href = Webfinger::zot_url(punify($ud['ud_addr'])); if($href) { $zf = Zotfinger::exec($href); } - if(is_array($zf) && array_path_exists('signature/signer',$zf) && $zf['signature']['signer'] === $href && intval($zf['signature']['header_valid'])) { + if(array_path_exists('signature/signer',$zf) && $zf['signature']['signer'] === $href && intval($zf['signature']['header_valid'])) { $xc = Libzot::import_xchan($zf['data'], 0, $ud); + // This is a workaround for a missing xchan_updated column + // TODO: implement xchan_updated in the xchan table and update this column instead + if($zf['data']['primary_location']['address'] && $zf['data']['primary_location']['url']) { + q("UPDATE hubloc SET hubloc_updated = '%s' WHERE hubloc_id_url = '%s' AND hubloc_primary = 1", + dbesc(datetime_convert()), + dbesc($zf['data']['primary_location']['url']) + ); + } } else { q("update updates set ud_last = '%s' where ud_addr = '%s'", @@ -338,10 +354,10 @@ class Libzotdir { static function local_dir_update($uid, $force) { - + logger('local_dir_update: uid: ' . $uid, LOGGER_DEBUG); - $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", + $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", intval($uid) ); @@ -350,11 +366,10 @@ 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']; - if ($age = age($p[0]['dob'],$p[0]['channel_timezone'],'')) + if ($age = age($p[0]['dob'],$p[0]['channel_timezone'],'')) $profile['age'] = $age; $profile['gender'] = $p[0]['gender']; @@ -389,10 +404,9 @@ class Libzotdir { ); if(intval($r[0]['xchan_hidden']) != $hidden) { - $r = q("update xchan set xchan_hidden = %d where xchan_hash in ('%s', '%s')", + $r = q("update xchan set xchan_hidden = %d where xchan_hash = '%s'", intval($hidden), - dbesc($hash), - dbesc($legacy_hash) + dbesc($hash) ); } @@ -406,16 +420,14 @@ class Libzotdir { } else { // they may have made it private - q("delete from xprof where xprof_hash in ('%s', '%s')", - dbesc($hash), - dbesc($legacy_hash) + q("delete from xprof where xprof_hash = '%s'", + dbesc($hash) ); - q("delete from xtag where xtag_hash in ('%s', '%s')", - dbesc($hash), - dbesc($legacy_hash) + q("delete from xtag where xtag_hash = '%s'", + dbesc($hash) ); } - + } $ud_hash = random_string() . '@' . \App::get_hostname(); @@ -446,7 +458,7 @@ class Libzotdir { $arr['xprof_hash'] = $hash; $arr['xprof_dob'] = (($profile['birthday'] === '0000-00-00') ? $profile['birthday'] : datetime_convert('','',$profile['birthday'],'Y-m-d')); // !!!! check this for 0000 year $arr['xprof_age'] = (($profile['age']) ? intval($profile['age']) : 0); - $arr['xprof_desc'] = (($profile['description']) ? htmlspecialchars($profile['description'], ENT_COMPAT,'UTF-8',false) : ''); + $arr['xprof_desc'] = (($profile['description']) ? htmlspecialchars($profile['description'], ENT_COMPAT,'UTF-8',false) : ''); $arr['xprof_gender'] = (($profile['gender']) ? htmlspecialchars($profile['gender'], ENT_COMPAT,'UTF-8',false) : ''); $arr['xprof_marital'] = (($profile['marital']) ? htmlspecialchars($profile['marital'], ENT_COMPAT,'UTF-8',false) : ''); $arr['xprof_sexual'] = (($profile['sexual']) ? htmlspecialchars($profile['sexual'], ENT_COMPAT,'UTF-8',false) : ''); @@ -631,8 +643,13 @@ class Libzotdir { $dirmode = intval(get_config('system', 'directory_mode')); - if($dirmode == DIRECTORY_MODE_NORMAL) + if($dirmode == DIRECTORY_MODE_NORMAL) { return; + } + + if (empty($hash) || empty($guid) || empty($addr)) { + return; + } if($flags) { q("insert into updates (ud_hash, ud_guid, ud_date, ud_flags, ud_addr ) values ( '%s', '%s', '%s', %d, '%s' )", @@ -641,7 +658,7 @@ class Libzotdir { dbesc(datetime_convert()), intval($flags), dbesc($addr) - ); + ); } else { q("update updates set ud_flags = ( ud_flags | %d ) where ud_addr = '%s' and not (ud_flags & %d)>0 ", @@ -652,9 +669,4 @@ class Libzotdir { } } - - - - - } diff --git a/Zotlabs/Lib/MessageFilter.php b/Zotlabs/Lib/MessageFilter.php index 750d6d424..21e6ca26a 100644 --- a/Zotlabs/Lib/MessageFilter.php +++ b/Zotlabs/Lib/MessageFilter.php @@ -11,8 +11,6 @@ class MessageFilter { require_once('include/html2plain.php'); - unobscure($item); - $text = prepare_text($item['body'],$item['mimetype']); $text = html2plain(($item['title']) ? $item['title'] . ' ' . $text : $text); diff --git a/Zotlabs/Lib/NativeWiki.php b/Zotlabs/Lib/NativeWiki.php index 3ec032075..9e6a3ac85 100644 --- a/Zotlabs/Lib/NativeWiki.php +++ b/Zotlabs/Lib/NativeWiki.php @@ -9,7 +9,7 @@ define ( 'NWIKI_ITEM_RESOURCE_TYPE', 'nwiki' ); class NativeWiki { - static public function listwikis($channel, $observer_hash) { + public static function listwikis($channel, $observer_hash) { $sql_extra = item_permissions_sql($channel['channel_id'], $observer_hash); $wikis = q("SELECT * FROM item @@ -40,7 +40,7 @@ class NativeWiki { } - function create_wiki($channel, $observer_hash, $wiki, $acl) { + public static function create_wiki($channel, $observer_hash, $wiki, $acl) { $resource_id = new_uuid(); $uuid = new_uuid(); @@ -101,7 +101,8 @@ class NativeWiki { } } - function update_wiki($channel_id, $observer_hash, $arr, $acl) { + + public static function update_wiki($channel_id, $observer_hash, $arr, $acl) { $w = self::get_wiki($channel_id, $observer_hash, $arr['resource_id']); $item = $w['wiki']; @@ -156,8 +157,8 @@ class NativeWiki { } } - static public function sync_a_wiki_item($uid,$id,$resource_id) { + public static function sync_a_wiki_item($uid,$id,$resource_id) { $r = q("SELECT * from item WHERE uid = %d AND ( id = %d OR ( resource_type = '%s' and resource_id = '%s' )) ", intval($uid), @@ -165,8 +166,8 @@ class NativeWiki { dbesc(NWIKI_ITEM_RESOURCE_TYPE), dbesc($resource_id) ); - if($r) { + $q = q("select * from item where resource_type = 'nwikipage' and resource_id = '%s'", dbesc($r[0]['resource_id']) ); @@ -185,35 +186,42 @@ class NativeWiki { } } - function delete_wiki($channel_id,$observer_hash,$resource_id) { + + public static function delete_wiki($channel_id,$observer_hash,$resource_id) { $w = self::get_wiki($channel_id,$observer_hash,$resource_id); - $item = $w['wiki']; - if(! $item) { - return array('item' => null, 'success' => false); - } - else { - $drop = drop_item($item['id'], false, DROPITEM_NORMAL); + if(! $w['wiki']) { + return [ 'success' => false ]; } + else { + + $r = q("SELECT id FROM item WHERE uid = %s AND resource_id = '%s'", + intval($channel_id), + dbesc($resource_id) + ); + + $ids = array_column($r, 'id'); + drop_items($ids, true, DROPITEM_PHASE1); - info( t('Wiki files deleted successfully')); + info(t('Wiki files deleted successfully')); - return array('item' => $item, 'item_id' => $item['id'], 'success' => (($drop === 1) ? true : false)); + return [ 'success' => true ]; + } } - static public function get_wiki($channel_id, $observer_hash, $resource_id) { + public static function get_wiki($channel_id, $observer_hash, $resource_id) { $sql_extra = item_permissions_sql($channel_id,$observer_hash); $item = q("SELECT * FROM item WHERE uid = %d AND resource_type = '%s' AND resource_id = '%s' AND item_deleted = 0 - $sql_extra limit 1", + $sql_extra ORDER BY id LIMIT 1", intval($channel_id), dbesc(NWIKI_ITEM_RESOURCE_TYPE), dbesc($resource_id) ); if(! $item) { - return array('wiki' => null); + return [ 'wiki' => null ]; } else { @@ -236,7 +244,7 @@ class NativeWiki { } - static public function exists_by_name($uid, $urlName) { + public static function exists_by_name($uid, $urlName) { $sql_extra = item_permissions_sql($uid); @@ -258,7 +266,8 @@ class NativeWiki { } - static public function get_permissions($resource_id, $owner_id, $observer_hash) { + public static function get_permissions($resource_id, $owner_id, $observer_hash) { + // TODO: For now, only the owner can edit $sql_extra = item_permissions_sql($owner_id, $observer_hash); @@ -283,6 +292,7 @@ class NativeWiki { } } + public static function name_encode ($string) { $string = html_entity_decode($string); @@ -298,6 +308,7 @@ class NativeWiki { return $ret; } + public static function name_decode ($string) { $encoding = mb_internal_encoding(); diff --git a/Zotlabs/Lib/NativeWikiPage.php b/Zotlabs/Lib/NativeWikiPage.php index d84cc50a8..3c61ea800 100644 --- a/Zotlabs/Lib/NativeWikiPage.php +++ b/Zotlabs/Lib/NativeWikiPage.php @@ -109,6 +109,7 @@ class NativeWikiPage { return [ 'success' => false, 'message' => t('Wiki page create failed.') ]; } + static public function rename_page($arr) { $pageUrlName = ((array_key_exists('pageUrlName',$arr)) ? $arr['pageUrlName'] : ''); @@ -163,11 +164,13 @@ class NativeWikiPage { return [ 'success' => true, 'page' => $page ]; } - return [ 'success' => false, 'item_id' => $c['item_id'], 'message' => t('Page not found') ]; + return [ 'success' => false, 'message' => t('Page not found') ]; } + static public function get_page_content($arr) { + $pageUrlName = ((array_key_exists('pageUrlName',$arr)) ? $arr['pageUrlName'] : ''); $resource_id = ((array_key_exists('resource_id',$arr)) ? $arr['resource_id'] : ''); $observer_hash = ((array_key_exists('observer_hash',$arr)) ? $arr['observer_hash'] : ''); @@ -198,7 +201,9 @@ class NativeWikiPage { } + static public function page_history($arr) { + $pageUrlName = ((array_key_exists('pageUrlName',$arr)) ? $arr['pageUrlName'] : ''); $resource_id = ((array_key_exists('resource_id',$arr)) ? $arr['resource_id'] : ''); $observer_hash = ((array_key_exists('observer_hash',$arr)) ? $arr['observer_hash'] : ''); @@ -290,6 +295,7 @@ class NativeWikiPage { return null; } + static public function load_page_history($arr) { $pageUrlName = ((array_key_exists('pageUrlName',$arr)) ? $arr['pageUrlName'] : ''); @@ -338,8 +344,8 @@ class NativeWikiPage { return null; } - static public function save_page($arr) { + static public function save_page($arr) { $pageUrlName = ((array_key_exists('pageUrlName',$arr)) ? $arr['pageUrlName'] : ''); $content = ((array_key_exists('content',$arr)) ? $arr['content'] : ''); $resource_id = ((array_key_exists('resource_id',$arr)) ? $arr['resource_id'] : ''); @@ -385,19 +391,20 @@ class NativeWikiPage { $ret = item_store($item, false, false); if($ret['item_id']) - return array('message' => '', 'item_id' => $ret['item_id'], 'filename' => $filename, 'success' => true); + return array('message' => '', 'item_id' => $ret['item_id'], 'filename' => $pageUrlName, 'success' => true); else return array('message' => t('Page update failed.'), 'success' => false); } + static public function delete_page($arr) { - $pageUrlName = ((array_key_exists('pageUrlName',$arr)) ? $arr['pageUrlName'] : ''); - $resource_id = ((array_key_exists('resource_id',$arr)) ? $arr['resource_id'] : ''); - $observer_hash = ((array_key_exists('observer_hash',$arr)) ? $arr['observer_hash'] : ''); - $channel_id = ((array_key_exists('channel_id',$arr)) ? $arr['channel_id'] : 0); - $w = Zlib\NativeWiki::get_wiki($channel_id, $observer_hash, $resource_id); + $pageUrlName = (array_key_exists('pageUrlName',$arr) ? $arr['pageUrlName'] : ''); + $resource_id = (array_key_exists('resource_id',$arr) ? $arr['resource_id'] : ''); + $observer_hash = (array_key_exists('observer_hash',$arr) ? $arr['observer_hash'] : ''); + $channel_id = (array_key_exists('channel_id',$arr) ? $arr['channel_id'] : 0); + $w = Zlib\NativeWiki::get_wiki($channel_id, $observer_hash, $resource_id); if(! $w['wiki']) { return [ 'success' => false, 'message' => t('Error reading wiki') ]; } @@ -417,14 +424,16 @@ class NativeWikiPage { } if($ids) { - drop_items($ids); + drop_items($ids, true, DROPITEM_PHASE1); return [ 'success' => true ]; } return [ 'success' => false, 'message' => t('Nothing deleted') ]; } + static public function revert_page($arr) { + $pageUrlName = ((array_key_exists('pageUrlName',$arr)) ? $arr['pageUrlName'] : ''); $resource_id = ((array_key_exists('resource_id',$arr)) ? $arr['resource_id'] : ''); $commitHash = ((array_key_exists('commitHash',$arr)) ? $arr['commitHash'] : null); @@ -432,12 +441,12 @@ class NativeWikiPage { $channel_id = ((array_key_exists('channel_id',$arr)) ? $arr['channel_id'] : 0); if (! $commitHash) { - return array('content' => $content, 'message' => 'No commit was provided', 'success' => false); + return array('message' => 'No commit was provided', 'success' => false); } $w = Zlib\NativeWiki::get_wiki($channel_id, $observer_hash, $resource_id); if (!$w['wiki']) { - return array('content' => $content, 'message' => 'Error reading wiki', 'success' => false); + return array('message' => 'Error reading wiki', 'success' => false); } $x = $arr; @@ -451,11 +460,13 @@ class NativeWikiPage { $content = $loaded['body']; return [ 'content' => $content, 'success' => true ]; } - return [ 'content' => $content, 'success' => false ]; + return [ 'success' => false ]; } } + static public function compare_page($arr) { + $pageUrlName = ((array_key_exists('pageUrlName',$arr)) ? $arr['pageUrlName'] : ''); $resource_id = ((array_key_exists('resource_id',$arr)) ? $arr['resource_id'] : ''); $currentCommit = ((array_key_exists('currentCommit',$arr)) ? $arr['currentCommit'] : (-1)); @@ -491,6 +502,7 @@ class NativeWikiPage { } + static public function commit($arr) { $commit_msg = ((array_key_exists('commit_msg', $arr)) ? $arr['commit_msg'] : t('Page updated')); @@ -571,7 +583,6 @@ class NativeWikiPage { } - /** * Replace the instances of the string [toc] with a list element that will be populated by * a table of contents by the JavaScript library @@ -587,6 +598,7 @@ class NativeWikiPage { return $s; } + /** * Converts a select set of bbcode tags. Much of the code is copied from include/bbcode.php * @param string $s @@ -626,7 +638,9 @@ class NativeWikiPage { return $s; } + static public function get_file_ext($arr) { + if($arr['mimetype'] === 'text/bbcode') return '.bb'; elseif($arr['mimetype'] === 'text/markdown') diff --git a/Zotlabs/Lib/PConfig.php b/Zotlabs/Lib/PConfig.php index c08c11e75..765131f0d 100644 --- a/Zotlabs/Lib/PConfig.php +++ b/Zotlabs/Lib/PConfig.php @@ -132,6 +132,7 @@ class PConfig { // manage array value $dbvalue = ((is_array($value)) ? serialize($value) : $value); $dbvalue = ((is_bool($dbvalue)) ? intval($dbvalue) : $dbvalue); + $new = false; $now = datetime_convert(); if (! $updated) { diff --git a/Zotlabs/Lib/Queue.php b/Zotlabs/Lib/Queue.php index 6acc58bc5..e03816f05 100644 --- a/Zotlabs/Lib/Queue.php +++ b/Zotlabs/Lib/Queue.php @@ -2,8 +2,8 @@ namespace Zotlabs\Lib; -use Zotlabs\Lib\Libzot; - +use Zotlabs\Zot6\Receiver; +use Zotlabs\Zot6\Zot6Handler; class Queue { @@ -31,19 +31,19 @@ class Queue { $might_be_down = ((datetime_convert('UTC','UTC',$y[0]['earliest']) < datetime_convert('UTC','UTC','now - 2 days')) ? true : false); - // Set all other records for this destination way into the future. + // Set all other records for this destination way into the future. // The queue delivers by destination. We'll keep one queue item for // this destination (this one) with a shorter delivery. If we succeed // once, we'll try to deliver everything for that destination. - // The delivery will be set to at most once per hour, and if the + // The delivery will be set to at most once per hour, and if the // queue item is less than 12 hours old, we'll schedule for fifteen - // minutes. + // minutes. - $r = q("UPDATE outq SET outq_scheduled = '%s' WHERE outq_posturl = '%s'", + q("UPDATE outq SET outq_scheduled = '%s' WHERE outq_posturl = '%s'", dbesc(datetime_convert('UTC','UTC','now + 5 days')), dbesc($x[0]['outq_posturl']) ); - + $since = datetime_convert('UTC','UTC',$x[0]['outq_created']); if(($might_be_down) || ($since < datetime_convert('UTC','UTC','now - 12 hour'))) { @@ -53,9 +53,9 @@ class Queue { $next = datetime_convert('UTC','UTC','now + ' . intval($add_priority) . ' minutes'); } - q("UPDATE outq SET outq_updated = '%s', - outq_priority = outq_priority + %d, - outq_scheduled = '%s' + q("UPDATE outq SET outq_updated = '%s', + outq_priority = outq_priority + %d, + outq_scheduled = '%s' WHERE outq_hash = '%s'", dbesc(datetime_convert()), @@ -69,7 +69,7 @@ class Queue { static function remove($id,$channel_id = 0) { logger('queue: remove queue item ' . $id,LOGGER_DEBUG); $sql_extra = (($channel_id) ? " and outq_channel = " . intval($channel_id) . " " : ''); - + q("DELETE FROM outq WHERE outq_hash = '%s' $sql_extra", dbesc($id) ); @@ -78,7 +78,7 @@ class Queue { static function remove_by_posturl($posturl) { logger('queue: remove queue posturl ' . $posturl,LOGGER_DEBUG); - + q("DELETE FROM outq WHERE outq_posturl = '%s' ", dbesc($posturl) ); @@ -88,10 +88,10 @@ class Queue { static function set_delivered($id,$channel = 0) { logger('queue: set delivered ' . $id,LOGGER_DEBUG); - $sql_extra = (($channel_id) ? " and outq_channel = " . intval($channel_id) . " " : ''); + $sql_extra = (($channel['channel_id']) ? " and outq_channel = " . intval($channel['channel_id']) . " " : ''); // Set the next scheduled run date so far in the future that it will be expired - // long before it ever makes it back into the delivery chain. + // long before it ever makes it back into the delivery chain. q("update outq set outq_delivered = 1, outq_updated = '%s', outq_scheduled = '%s' where outq_hash = '%s' $sql_extra ", dbesc(datetime_convert()), @@ -111,7 +111,7 @@ class Queue { } $x = q("insert into outq ( outq_hash, outq_account, outq_channel, outq_driver, outq_posturl, outq_async, outq_priority, - outq_created, outq_updated, outq_scheduled, outq_notify, outq_msg ) + outq_created, outq_updated, outq_scheduled, outq_notify, outq_msg ) values ( '%s', %d, %d, '%s', '%s', %d, %d, '%s', '%s', '%s', '%s', '%s' )", dbesc($arr['hash']), intval($arr['account_id']), @@ -119,7 +119,7 @@ class Queue { dbesc(($arr['driver']) ? $arr['driver'] : 'zot6'), dbesc($arr['posturl']), intval(1), - intval(($arr['priority']) ? $arr['priority'] : 0), + intval(isset($arr['priority']) ? $arr['priority'] : 0), dbesc(datetime_convert()), dbesc(datetime_convert()), dbesc(datetime_convert()), @@ -136,8 +136,8 @@ class Queue { $base = null; $h = parse_url($outq['outq_posturl']); - if($h !== false) - $base = $h['scheme'] . '://' . $h['host'] . (($h['port']) ? ':' . $h['port'] : ''); + if($h !== false) + $base = $h['scheme'] . '://' . $h['host'] . (isset($h['port']) ? ':' . $h['port'] : ''); if(($base) && ($base !== z_root()) && ($immediate)) { $y = q("select site_update, site_dead from site where site_url = '%s' ", @@ -150,7 +150,7 @@ class Queue { return; } if($y[0]['site_update'] < datetime_convert('UTC','UTC','now - 1 month')) { - self::update($outq['outq_hash'],10); + self::update($outq['outq_hash'], 10); logger('immediate delivery deferred for site ' . $base); return; } @@ -161,12 +161,12 @@ class Queue { // your site has existed. Since we don't know for sure what these sites are, // call them unknown - site_store_lowlevel( + site_store_lowlevel( [ 'site_url' => $base, 'site_update' => datetime_convert(), 'site_dead' => 0, - 'site_type' => intval(($outq['outq_driver'] === 'post') ? SITE_TYPE_NOTZOT : SITE_TYPE_UNKNOWN), + 'site_type' => SITE_TYPE_UNKNOWN, 'site_crypto' => '' ] ); @@ -174,67 +174,18 @@ class Queue { } $arr = array('outq' => $outq, 'base' => $base, 'handled' => false, 'immediate' => $immediate); - call_hooks('queue_deliver',$arr); + call_hooks('queue_deliver', $arr); if($arr['handled']) return; - // "post" queue driver - used for diaspora and friendica-over-diaspora communications. - - if($outq['outq_driver'] === 'post') { - $result = z_post_url($outq['outq_posturl'],$outq['outq_msg']); - if($result['success'] && $result['return_code'] < 300) { - logger('deliver: queue post success to ' . $outq['outq_posturl'], LOGGER_DEBUG); - if($base) { - q("update site set site_update = '%s', site_dead = 0 where site_url = '%s' ", - dbesc(datetime_convert()), - dbesc($base) - ); - } - q("update dreport set dreport_result = '%s', dreport_time = '%s' where dreport_queue = '%s'", - dbesc('accepted for delivery'), - dbesc(datetime_convert()), - dbesc($outq['outq_hash']) - ); - self::remove($outq['outq_hash']); - - // server is responding - see if anything else is going to this destination and is piled up - // and try to send some more. We're relying on the fact that do_delivery() results in an - // immediate delivery otherwise we could get into a queue loop. - - if(! $immediate) { - $x = q("select outq_hash from outq where outq_posturl = '%s' and outq_delivered = 0", - dbesc($outq['outq_posturl']) - ); - - $piled_up = array(); - if($x) { - foreach($x as $xx) { - $piled_up[] = $xx['outq_hash']; - } - } - if($piled_up) { - // call do_delivery() with the force flag - do_delivery($piled_up, true); - } - } - } - else { - logger('deliver: queue post returned ' . $result['return_code'] - . ' from ' . $outq['outq_posturl'],LOGGER_DEBUG); - self::update($outq['outq_hash'],10); - } - return; - } - // normal zot delivery logger('deliver: dest: ' . $outq['outq_posturl'], LOGGER_DEBUG); - if($outq['outq_posturl'] === z_root() . '/zot') { // local delivery - $zot = new \Zotlabs\Zot6\Receiver(new \Zotlabs\Zot6\Zot6Handler(),$outq['outq_notify']); - $result = $zot->run(true); + $zot = new Receiver(new Zot6Handler(), $outq['outq_notify']); + $result = $zot->run(); logger('returned_json: ' . json_encode($result,JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES), LOGGER_DATA); logger('deliver: local zot delivery succeeded to ' . $outq['outq_posturl']); Libzot::process_response($outq['outq_posturl'],[ 'success' => true, 'body' => json_encode($result) ], $outq); @@ -244,13 +195,14 @@ class Queue { $channel = null; if($outq['outq_channel']) { - $channel = channelx_by_n($outq['outq_channel']); + $channel = channelx_by_n($outq['outq_channel'], true); } $host_crypto = null; if($channel && $base) { - $h = q("select hubloc_sitekey, site_crypto from hubloc left join site on hubloc_url = site_url where site_url = '%s' and hubloc_sitekey != '' order by hubloc_id desc limit 1", + $h = q("SELECT hubloc_sitekey, site_crypto FROM hubloc LEFT JOIN site ON hubloc_url = site_url + WHERE site_url = '%s' AND hubloc_network = 'zot6' ORDER BY hubloc_id DESC LIMIT 1", dbesc($base) ); if($h) { @@ -260,7 +212,7 @@ class Queue { $msg = $outq['outq_notify']; - $result = Libzot::zot($outq['outq_posturl'],$msg,$channel,$host_crypto); + $result = Libzot::zot($outq['outq_posturl'], $msg, $channel, $host_crypto); if($result['success']) { logger('deliver: remote zot delivery succeeded to ' . $outq['outq_posturl']); @@ -269,7 +221,7 @@ class Queue { else { logger('deliver: remote zot delivery failed to ' . $outq['outq_posturl']); logger('deliver: remote zot delivery fail data: ' . print_r($result,true), LOGGER_DATA); - self::update($outq['outq_hash'],10); + self::update($outq['outq_hash'], 10); } } return; diff --git a/Zotlabs/Lib/Share.php b/Zotlabs/Lib/Share.php index d34c0eaba..81f378d0d 100644 --- a/Zotlabs/Lib/Share.php +++ b/Zotlabs/Lib/Share.php @@ -2,21 +2,19 @@ namespace Zotlabs\Lib; -use Zotlabs\Lib\Activity; - class Share { private $item = null; public function __construct($post_id) { - + if(! $post_id) return; - + if(! (local_channel() || remote_channel())) return; - + $r = q("SELECT * from item left join xchan on author_xchan = xchan_hash WHERE id = %d LIMIT 1", intval($post_id) ); @@ -25,26 +23,26 @@ class Share { if(($r[0]['item_private']) && ($r[0]['xchan_network'] !== 'rss')) return; - + $sql_extra = item_permissions_sql($r[0]['uid']); - + $r = q("select * from item where id = %d $sql_extra", intval($post_id) ); if(! $r) return; - + if($r[0]['mimetype'] !== 'text/bbcode') return; - + /** @FIXME eventually we want to post remotely via rpost on your home site */ // When that works remove this next bit: - + if(! local_channel()) return; xchan_query($r); - + $this->item = $r[0]; return; } @@ -68,14 +66,14 @@ class Share { 'address' => $this->item['author']['xchan_addr'], 'network' => $this->item['author']['xchan_network'], 'link' => [ - [ - 'rel' => 'alternate', - 'type' => 'text/html', + [ + 'rel' => 'alternate', + 'type' => 'text/html', 'href' => $this->item['author']['xchan_url'] ], [ - 'rel' => 'photo', - 'type' => $this->item['author']['xchan_photo_mimetype'], + 'rel' => 'photo', + 'type' => $this->item['author']['xchan_photo_mimetype'], 'href' => $this->item['author']['xchan_photo_m'] ] ] @@ -86,14 +84,14 @@ class Share { 'address' => $this->item['owner']['xchan_addr'], 'network' => $this->item['owner']['xchan_network'], 'link' => [ - [ - 'rel' => 'alternate', - 'type' => 'text/html', + [ + 'rel' => 'alternate', + 'type' => 'text/html', 'href' => $this->item['owner']['xchan_url'] ], [ - 'rel' => 'photo', - 'type' => $this->item['owner']['xchan_photo_mimetype'], + 'rel' => 'photo', + 'type' => $this->item['owner']['xchan_photo_mimetype'], 'href' => $this->item['owner']['xchan_photo_m'] ] ] @@ -119,7 +117,7 @@ class Share { $object = json_decode($this->item['obj'],true); $photo_bb = $object['body']; } - + if (strpos($this->item['body'], "[/share]") !== false) { $pos = strpos($this->item['body'], "[share"); $bb = substr($this->item['body'], $pos); @@ -128,7 +126,7 @@ class Share { "' profile='" . $this->item['author']['xchan_url'] . "' avatar='" . $this->item['author']['xchan_photo_s'] . "' link='" . $this->item['plink'] . - "' auth='" . ((in_array($this->item['author']['xchan_network'], ['zot6', 'zot'])) ? 'true' : 'false') . + "' auth='" . (($this->item['author']['xchan_network'] === 'zot6') ? 'true' : 'false') . "' posted='" . $this->item['created'] . "' message_id='" . $this->item['mid'] . "']"; diff --git a/Zotlabs/Lib/ThreadItem.php b/Zotlabs/Lib/ThreadItem.php index 024502d2a..cd54fea17 100644 --- a/Zotlabs/Lib/ThreadItem.php +++ b/Zotlabs/Lib/ThreadItem.php @@ -35,7 +35,7 @@ class ThreadItem { public function __construct($data) { - + $this->data = $data; $this->toplevel = ($this->get_id() == $this->get_data_value('parent')); $this->threaded = get_config('system','thread_allow'); @@ -43,7 +43,7 @@ class ThreadItem { $observer = \App::get_observer(); // Prepare the children - if($data['children']) { + if(isset($data['children'])) { foreach($data['children'] as $item) { /* @@ -98,10 +98,11 @@ class ThreadItem { $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']) + $lock = (((intval($item['item_private'])) || (($item['uid'] == local_channel()) && (strlen($item['allow_cid']) || strlen($item['allow_gid']) || strlen($item['deny_cid']) || strlen($item['deny_gid'])))) ? t('Private Message') : false); + $locktype = $item['item_private']; $shareable = ((($conv->get_profile_owner() == local_channel() && local_channel()) && ($item['item_private'] != 1)) ? true : false); @@ -151,9 +152,9 @@ class ThreadItem { if($observer && $observer['xchan_hash'] - && ($observer['xchan_hash'] == $this->get_data_value('author_xchan') - || $observer['xchan_hash'] == $this->get_data_value('owner_xchan') - || $observer['xchan_hash'] == $this->get_data_value('source_xchan') + && ($observer['xchan_hash'] == $this->get_data_value('author_xchan') + || $observer['xchan_hash'] == $this->get_data_value('owner_xchan') + || $observer['xchan_hash'] == $this->get_data_value('source_xchan') || $this->get_data_value('uid') == local_channel())) $dropping = true; @@ -169,15 +170,15 @@ class ThreadItem { 'dropping' => $dropping, 'delete' => t('Delete'), ); - } + } elseif(is_site_admin()) { $drop = [ 'dropping' => true, 'delete' => t('Admin Delete') ]; } // FIXME - if($observer_is_pageowner) { + if($observer_is_pageowner) { $multidrop = array( - 'select' => t('Select'), + 'select' => t('Select'), ); } @@ -223,7 +224,7 @@ class ThreadItem { if(! feature_enabled($conv->get_profile_owner(),'dislike')) unset($conv_responses['dislike']); - + $responses = get_responses($conv_responses,$response_verbs,$this,$item); $my_responses = []; @@ -254,7 +255,7 @@ class ThreadItem { } $showlike = ((x($conv_responses['like'],$item['mid'])) ? format_like($conv_responses['like'][$item['mid']],$conv_responses['like'][$item['mid'] . '-l'],'like',$item['mid']) : ''); - $showdislike = ((x($conv_responses['dislike'],$item['mid']) && feature_enabled($conv->get_profile_owner(),'dislike')) + $showdislike = ((x($conv_responses['dislike'],$item['mid']) && feature_enabled($conv->get_profile_owner(),'dislike')) ? format_like($conv_responses['dislike'][$item['mid']],$conv_responses['dislike'][$item['mid'] . '-l'],'dislike',$item['mid']) : ''); /* @@ -264,7 +265,7 @@ class ThreadItem { */ $this->check_wall_to_wall(); - + if($this->is_toplevel()) { // FIXME check this permission if(($conv->get_profile_owner() == local_channel()) && (! array_key_exists('real_uid',$item))) { @@ -275,7 +276,7 @@ class ThreadItem { ); } - } + } else { $is_comment = true; } @@ -298,7 +299,7 @@ class ThreadItem { ); */ - $settings = t('Conversation Tools'); + $settings = t('Conversation Features'); } $has_bookmarks = false; @@ -349,7 +350,7 @@ class ThreadItem { // $viewthread (below) is only valid in list mode. If this is a channel page, build the thread viewing link // since we can't depend on llink or plink pointing to the right local location. - + $owner_address = substr($item['owner']['xchan_addr'],0,strpos($item['owner']['xchan_addr'],'@')); $viewthread = $item['llink']; if($conv->get_mode() === 'channel') @@ -357,7 +358,7 @@ class ThreadItem { $comment_count_txt = sprintf( tt('%d comment','%d comments',$total_children),$total_children ); $list_unseen_txt = (($unseen_comments) ? sprintf( t('%d unseen'),$unseen_comments) : ''); - + $children = $this->get_children(); $has_tags = (($body['tags'] || $body['categories'] || $body['mentions'] || $body['attachments'] || $body['folders']) ? true : false); @@ -366,7 +367,7 @@ class ThreadItem { call_hooks('dropdown_extras',$dropdown_extras_arr); $dropdown_extras = $dropdown_extras_arr['dropdown_extras']; - $midb64 = 'b64.' . base64url_encode($item['mid']); + $midb64 = gen_link_id($item['mid']); $mids = [ $midb64 ]; $response_mids = []; foreach($response_verbs as $v) { @@ -386,7 +387,7 @@ class ThreadItem { $tmp_item = array( 'template' => $this->get_template(), 'mode' => $mode, - 'item_type' => intval($item['item_type']), + 'item_type' => intval($item['item_type']), //'type' => implode("",array_slice(explode("/",$item['verb']),-1)), 'body' => $body['html'], 'tags' => $body['tags'], @@ -432,6 +433,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, + 'locktype' => $locktype, 'delayed' => $item['item_delayed'], 'privacy_warning' => $privacy_warning, 'verified' => $verified, @@ -501,7 +503,7 @@ class ThreadItem { 'wait' => t('Please wait'), 'thread_level' => $thread_level, 'settings' => $settings, - 'thr_parent' => (($item['parent_mid'] != $item['thr_parent']) ? 'b64.' . base64url_encode($item['thr_parent']) : '') + 'thr_parent' => (($item['parent_mid'] != $item['thr_parent']) ? gen_link_id($item['thr_parent']) : '') ); $arr = array('item' => $item, 'output' => $tmp_item); @@ -518,8 +520,8 @@ class ThreadItem { // needed for scroll to comment from notification but needs more work // as we do not want to open all comments unless there is actually an #item_xx anchor -// and the url fragment is not sent to the server. -// if(in_array(\App::$module,['display','update_display'])) +// and the url fragment is not sent to the server. +// if(in_array(\App::$module,['display','update_display'])) // $visible_comments = 99999; if(($this->get_display_mode() === 'normal') && ($nb_children > 0)) { @@ -539,7 +541,7 @@ class ThreadItem { } } } - + $result['private'] = $item['item_private']; $result['toplevel'] = ($this->is_toplevel() ? 'toplevel_item' : ''); @@ -554,7 +556,7 @@ class ThreadItem { return $result; } - + public function get_id() { return $this->get_data_value('id'); } @@ -609,7 +611,7 @@ class ThreadItem { if(activity_match($item->get_data_value('verb'),ACTIVITY_LIKE) || activity_match($item->get_data_value('verb'),ACTIVITY_DISLIKE)) { return false; } - + $item->set_parent($this); $this->children[] = $item; return end($this->children); @@ -683,7 +685,7 @@ class ThreadItem { */ public function set_conversation($conv) { $previous_mode = ($this->conversation ? $this->conversation->get_mode() : ''); - + $this->conversation = $conv; // Set it on our children too @@ -792,7 +794,7 @@ class ThreadItem { if(!$this->is_toplevel() && !get_config('system','thread_allow')) { return ''; } - + $comment_box = ''; $conv = $this->get_conversation(); @@ -808,7 +810,7 @@ class ThreadItem { $arr = array('comment_buttons' => '','id' => $this->get_id()); call_hooks('comment_buttons',$arr); $comment_buttons = $arr['comment_buttons']; - + $comment_box = replace_macros($template,array( '$return_path' => '', '$threaded' => $this->is_threaded(), @@ -840,7 +842,7 @@ class ThreadItem { '$cipher' => $conv->get_cipher(), '$sourceapp' => \App::$sourcename, '$observer' => get_observer_hash(), - '$anoncomments' => ((($conv->get_mode() === 'channel' || $conv->get_mode() === 'display') && perm_is_allowed($conv->get_profile_owner(),'','post_comments')) ? true : false), + '$anoncomments' => ((in_array($conv->get_mode(), ['channel', 'display', 'cards', 'articles']) && perm_is_allowed($conv->get_profile_owner(),'','post_comments')) ? true : false), '$anonname' => [ 'anonname', t('Your full name (required)') ], '$anonmail' => [ 'anonmail', t('Your email address (required)') ], '$anonurl' => [ 'anonurl', t('Your website URL (optional)') ] @@ -865,7 +867,7 @@ class ThreadItem { if($conv->get_mode() === 'channel') return; - + if($this->is_toplevel() && ($this->get_data_value('author_xchan') != $this->get_data_value('owner_xchan'))) { $this->owner_url = chanlink_hash($this->data['owner']['xchan_hash']); $this->owner_photo = $this->data['owner']['xchan_photo_m']; diff --git a/Zotlabs/Lib/ThreadStream.php b/Zotlabs/Lib/ThreadStream.php index 68b2c70dd..7fe8fcc2e 100644 --- a/Zotlabs/Lib/ThreadStream.php +++ b/Zotlabs/Lib/ThreadStream.php @@ -77,7 +77,7 @@ class ThreadStream { $this->reload = $_SESSION['return_url']; break; case 'display': - // in this mode we set profile_owner after initialisation (from conversation()) and then + // in this mode we set profile_owner after initialisation (from conversation()) and then // pull some trickery which allows us to re-invoke this function afterward // it's an ugly hack so @FIXME $this->writable = perm_is_allowed($this->profile_owner,$ob_hash,'post_comments'); @@ -170,14 +170,14 @@ class ThreadStream { * Only add things that will be displayed */ - + if(($item->get_data_value('id') != $item->get_data_value('parent')) && (activity_match($item->get_data_value('verb'),ACTIVITY_LIKE) || activity_match($item->get_data_value('verb'),ACTIVITY_DISLIKE))) { return false; } $item->set_commentable(false); $ob_hash = (($this->observer) ? $this->observer['xchan_hash'] : ''); - + if(! comments_are_now_closed($item->get_data())) { if(($item->get_data_value('author_xchan') === $ob_hash) || ($item->get_data_value('owner_xchan') === $ob_hash)) $item->set_commentable(true); @@ -194,7 +194,7 @@ class ThreadStream { } if($this->mode === 'pubstream' && (! local_channel())) { $item->set_commentable(false); - } + } $item->set_conversation($this); diff --git a/Zotlabs/Lib/Verify.php b/Zotlabs/Lib/Verify.php index 8703e29e6..f8dc8f8d4 100644 --- a/Zotlabs/Lib/Verify.php +++ b/Zotlabs/Lib/Verify.php @@ -5,7 +5,7 @@ namespace Zotlabs\Lib; class Verify { - function create($type,$channel_id,$token,$meta) { + public static function create($type,$channel_id,$token,$meta) { return q("insert into verify ( vtype, channel, token, meta, created ) values ( '%s', %d, '%s', '%s', '%s' )", dbesc($type), intval($channel_id), @@ -15,7 +15,7 @@ class Verify { ); } - function match($type,$channel_id,$token,$meta) { + public static function match($type,$channel_id,$token,$meta) { $r = q("select id from verify where vtype = '%s' and channel = %d and token = '%s' and meta = '%s' limit 1", dbesc($type), intval($channel_id), @@ -31,7 +31,7 @@ class Verify { return false; } - function get_meta($type,$channel_id,$token) { + public static function get_meta($type,$channel_id,$token) { $r = q("select id, meta from verify where vtype = '%s' and channel = %d and token = '%s' limit 1", dbesc($type), intval($channel_id), @@ -52,7 +52,7 @@ class Verify { * @param string $type Verify type * @param string $interval SQL compatible time interval */ - function purge($type, $interval) { + public static function purge($type, $interval) { q("delete from verify where vtype = '%s' and created < %s - INTERVAL %s", dbesc($type), db_utcnow(), diff --git a/Zotlabs/Lib/ZotURL.php b/Zotlabs/Lib/ZotURL.php index 98d1febe5..6bb01fd7a 100644 --- a/Zotlabs/Lib/ZotURL.php +++ b/Zotlabs/Lib/ZotURL.php @@ -21,9 +21,8 @@ class ZotURL { } $portable_url = substr($url,6); - $u = explode('/',$portable_url); + $u = explode('/',$portable_url); $portable_id = $u[0]; - $hosts = self::lookup($portable_id); if(! $hosts) { @@ -39,8 +38,8 @@ class ZotURL { if($channel && $m) { - $headers = [ - 'Accept' => 'application/x-zot+json', + $headers = [ + 'Accept' => 'application/x-zot+json', 'Content-Type' => 'application/x-zot+json', 'X-Zot-Token' => random_string(), 'Digest' => HTTPSig::generate_digest_header($data), @@ -50,9 +49,9 @@ class ZotURL { $h = HTTPSig::create_sig($headers,$channel['channel_prvkey'],channel_url($channel),false); } else { - $h = [ 'Accept: application/x-zot+json' ]; + $h = [ 'Accept: application/x-zot+json' ]; } - + $result = []; $redirects = 0; diff --git a/Zotlabs/Lib/Zotfinger.php b/Zotlabs/Lib/Zotfinger.php index e853d6ebc..840d91403 100644 --- a/Zotlabs/Lib/Zotfinger.php +++ b/Zotlabs/Lib/Zotfinger.php @@ -18,8 +18,8 @@ class Zotfinger { if($channel && $m) { - $headers = [ - 'Accept' => 'application/x-zot+json', + $headers = [ + 'Accept' => 'application/x-zot+json', 'Content-Type' => 'application/x-zot+json', 'X-Zot-Token' => random_string(), 'Digest' => HTTPSig::generate_digest_header($data), @@ -29,11 +29,10 @@ class Zotfinger { $h = HTTPSig::create_sig($headers,$channel['channel_prvkey'],channel_url($channel),false); } else { - $h = [ 'Accept: application/x-zot+json' ]; + $h = [ 'Accept: application/x-zot+json' ]; } - - $result = []; + $result = []; $redirects = 0; $x = z_post_url($resource,$data,$redirects, [ 'headers' => $h ] ); @@ -44,11 +43,11 @@ class Zotfinger { if ($verify) { $result['signature'] = HTTPSig::verify($x, EMPTY_STR, 'zot6'); } - + $result['data'] = json_decode($x['body'],true); if($result['data'] && is_array($result['data']) && array_key_exists('encrypted',$result['data']) && $result['data']['encrypted']) { - $result['data'] = json_decode(crypto_unencapsulate($result['data'],get_config('system','prvkey')),true); + $result['data'] = json_decode(Crypto::unencapsulate($result['data'],get_config('system','prvkey')),true); } logger('decrypted: ' . print_r($result,true)); |