diff options
author | Mario <mario@mariovavti.com> | 2021-11-09 09:10:19 +0000 |
---|---|---|
committer | Mario <mario@mariovavti.com> | 2021-11-09 09:10:19 +0000 |
commit | fe7ecede700fe04631d23f36473e697ce2b364dc (patch) | |
tree | e713fc39dba500a25cb2acf8561e286fb8b41ff0 /Zotlabs | |
parent | 42de18d96d201d74e5df3ed1b8f6132cb00357b6 (diff) | |
parent | 089708ab9f90309a0c27ae633cf8f2604fce1170 (diff) | |
download | volse-hubzilla-6.4.tar.gz volse-hubzilla-6.4.tar.bz2 volse-hubzilla-6.4.zip |
Merge branch '6.4RC'6.4
Diffstat (limited to 'Zotlabs')
53 files changed, 2934 insertions, 1951 deletions
diff --git a/Zotlabs/Daemon/Channel_purge.php b/Zotlabs/Daemon/Channel_purge.php new file mode 100644 index 000000000..9fceb0fb9 --- /dev/null +++ b/Zotlabs/Daemon/Channel_purge.php @@ -0,0 +1,34 @@ +<?php + +namespace Zotlabs\Daemon; + +class Channel_purge { + + static public function run($argc,$argv) { + + cli_startup(); + + $channel_id = intval($argv[1]); + + $channel = q("select * from channel where channel_id = %d and channel_removed = 1", + intval($channel_id) + ); + + if (! $channel) { + return; + } + + do { + $r = q("select id from item where uid = %d and item_deleted = 0 limit 1000", + intval($channel_id) + ); + if ($r) { + foreach ($r as $rv) { + drop_item($rv['id'], false); + } + } + } while ($r); + + return; + } +} diff --git a/Zotlabs/Daemon/Content_importer.php b/Zotlabs/Daemon/Content_importer.php new file mode 100644 index 000000000..67f1c8e80 --- /dev/null +++ b/Zotlabs/Daemon/Content_importer.php @@ -0,0 +1,77 @@ +<?php + +namespace Zotlabs\Daemon; + +use Zotlabs\Web\HTTPSig; +use Zotlabs\Lib\PConfig; + + +require_once('include/cli_startup.php'); +require_once('include/attach.php'); +require_once('include/import.php'); + +class Content_importer { + + static public function run($argc,$argv) { + cli_startup(); + + $page = $argv[1]; + $since = $argv[2]; + $until = $argv[3]; + $channel_address = $argv[4]; + $hz_server = urldecode($argv[5]); + + $m = parse_url($hz_server); + + $channel = channelx_by_nick($channel_address); + if(! $channel) { + logger('channel not found'); + return; + } + + $headers = [ + 'X-API-Token' => random_string(), + 'X-API-Request' => $hz_server . '/api/z/1.0/item/export_page?f=&since=' . urlencode($since) . '&until=' . urlencode($until) . '&page=' . $page , + 'Host' => $m['host'], + '(request-target)' => 'get /api/z/1.0/item/export_page?f=&since=' . urlencode($since) . '&until=' . urlencode($until) . '&page=' . $page , + ]; + + $headers = HTTPSig::create_sig($headers,$channel['channel_prvkey'], channel_url($channel),true,'sha512'); + + $x = z_fetch_url($hz_server . '/api/z/1.0/item/export_page?f=&since=' . urlencode($since) . '&until=' . urlencode($until) . '&page=' . $page,false,$redirects,[ 'headers' => $headers ]); + + // logger('item fetch: ' . print_r($x,true)); + + if(! $x['success']) { + logger('no API response',LOGGER_DEBUG); + killme(); + } + + $j = json_decode($x['body'],true); + + if(! is_array($j['item']) || ! count($j['item'])) { + PConfig::Set($channel['channel_id'], 'import', 'content_completed', 1); + return; + } + + $saved_notification_flags = notifications_off($channel['channel_id']); + + import_items($channel,$j['item'],false,((array_key_exists('relocate',$j)) ? $j['relocate'] : null)); + + notifications_on($channel['channel_id'], $saved_notification_flags); + + PConfig::Set($channel['channel_id'], 'import', 'content_progress', [ + 'items_total' => $j['items_total'], + 'items_page' => $j['items_page'], + 'items_current_page' => count($j['item']), + 'last_page' => $page, + 'next_cmd' => ['Content_importer', sprintf('%d',$page + 1), $since, $until, $channel['channel_address'], urlencode($hz_server)] + ]); + + $page++; + + Master::Summon([ 'Content_importer', sprintf('%d',$page), $since, $until, $channel['channel_address'], urlencode($hz_server) ]); + + return; + } +} diff --git a/Zotlabs/Daemon/Cron.php b/Zotlabs/Daemon/Cron.php index c0a190c8e..6629491de 100644 --- a/Zotlabs/Daemon/Cron.php +++ b/Zotlabs/Daemon/Cron.php @@ -36,7 +36,6 @@ class Cron { // run queue delivery process in the background Master::Summon(array('Queue')); - Master::Summon(array('Poller')); /** @@ -206,10 +205,9 @@ class Cron { // pull in some public posts -/* $disable_discover_tab = get_config('system', 'disable_discover_tab') || get_config('system', 'disable_discover_tab') === false; + $disable_discover_tab = get_config('system', 'disable_discover_tab') || get_config('system', 'disable_discover_tab') === false; if (!$disable_discover_tab) - Master::Summon(array('Externals')); -*/ + Master::Summon(['Externals']); $restart = false; diff --git a/Zotlabs/Daemon/Directory.php b/Zotlabs/Daemon/Directory.php index 35d184206..3996b8079 100644 --- a/Zotlabs/Daemon/Directory.php +++ b/Zotlabs/Daemon/Directory.php @@ -49,8 +49,9 @@ class Directory { ); // Now update all the connections - if ($pushall) + if ($pushall) { Master::Summon(array('Notifier', 'refresh_all', $channel['channel_id'])); + } return; } @@ -93,8 +94,8 @@ class Directory { } // Now update all the connections - if ($pushall) + if ($pushall) { Master::Summon(array('Notifier', 'refresh_all', $channel['channel_id'])); - + } } } diff --git a/Zotlabs/Daemon/Externals.php b/Zotlabs/Daemon/Externals.php index 064b3f71d..81414d02d 100644 --- a/Zotlabs/Daemon/Externals.php +++ b/Zotlabs/Daemon/Externals.php @@ -3,6 +3,7 @@ namespace Zotlabs\Daemon; use Zotlabs\Lib\Activity; +use Zotlabs\Lib\Libzot; use Zotlabs\Lib\ActivityStreams; use Zotlabs\Lib\ASCollection; @@ -31,25 +32,55 @@ class Externals { $url = $arr['url']; } else { + $networks = ['zot6']; + + if (plugin_is_installed('pubcrawl')) { + $networks[] = 'activitypub'; + } + + stringify_array_elms($networks); + $networks_str = implode(',', $networks); + $randfunc = db_getfunc('RAND'); // fixme this query does not deal with directory realms. - - $r = q("select site_url, site_pull from site where site_url != '%s' - and site_flags != %d and site_type = %d - and site_dead = 0 and site_project like '%s' and site_version > '5.3.1' order by $randfunc limit 1", + //$r = q("select site_url, site_pull from site where site_url != '%s' + //and site_flags != %d and site_type = %d + //and site_dead = 0 and site_project like '%s' and site_version > '5.3.1' order by $randfunc limit 1", + //dbesc(z_root()), + //intval(DIRECTORY_MODE_STANDALONE), + //intval(SITE_TYPE_ZOT), + //dbesc('hubzilla%') + //); + + $r = q("SELECT * FROM hubloc + LEFT JOIN abook ON abook_xchan = hubloc_hash + LEFT JOIN site ON site_url = hubloc_url WHERE + hubloc_network IN ( $networks_str ) AND + abook_xchan IS NULL AND + hubloc_url != '%s' AND + hubloc_updated > '%s' AND + hubloc_primary = 1 AND hubloc_deleted = 0 AND + site_dead = 0 + ORDER BY $randfunc LIMIT 1", dbesc(z_root()), - intval(DIRECTORY_MODE_STANDALONE), - intval(SITE_TYPE_ZOT), - dbesc('hubzilla%') + datetime_convert('UTC', 'UTC', 'now - 30 days') ); - if ($r) - $url = $r[0]['site_url']; + + $contact = $r[0]; + + if ($contact) { + $url = $contact['hubloc_id_url']; + } + } + + if (!$url) { + continue; } $blacklisted = false; - if (!check_siteallowed($url)) { + if (!check_siteallowed($contact['hubloc_url'])) { logger('blacklisted site: ' . $url); $blacklisted = true; } @@ -59,123 +90,65 @@ class Externals { // make sure we can eventually break out if somebody blacklists all known sites if ($blacklisted) { - if ($attempts > 20) + if ($attempts > 5) break; $attempts--; continue; } - if ($url) { - - $max = intval(get_config('system', 'max_imported_posts', 30)); - if (intval($max)) { - logger('externals: fetching outbox'); - - $feed_url = $url . '/zotfeed'; - $obj = new ASCollection($feed_url, $importer, 0, $max); - $messages = $obj->get(); + $cl = Activity::get_actor_collections($contact['hubloc_hash']); + if(empty($cl)) { + $cl = get_xconfig($contact['hubloc_hash'], 'activitypub', 'collections'); + } - if ($messages) { - foreach ($messages as $message) { - if (is_string($message)) { - $message = Activity::fetch($message, $importer); - } - $AS = new ActivityStreams($message); - if ($AS->is_valid() && is_array($AS->obj)) { - $item = Activity::decode_note($AS); - Activity::store($importer, $importer['xchan_hash'], $AS, $item, true); - $total++; - } - } - } - logger('externals: import_public_posts: ' . $total . ' messages imported', LOGGER_DEBUG); + if (is_array($cl) && array_key_exists('outbox', $cl)) { + $url = $cl['outbox']; + } + else { + $url = str_replace('/channel/', '/outbox/', $contact['hubloc_id_url']); + if ($url) { + $url .= '?top=1'; } } - } - return; - - /* $total = 0; - $attempts = 0; - - logger('externals: startup', LOGGER_DEBUG); - - // pull in some public posts - while ($total == 0 && $attempts < 3) { - $arr = ['url' => '']; - call_hooks('externals_url_select', $arr); - - if ($arr['url']) { - $url = $arr['url']; - } - else { - $randfunc = db_getfunc('RAND'); - - // fixme this query does not deal with directory realms. - - $r = q("select site_url, site_pull from site where site_url != '%s' and site_flags != %d and site_type = %d and site_dead = 0 order by $randfunc limit 1", - dbesc(z_root()), - intval(DIRECTORY_MODE_STANDALONE), - intval(SITE_TYPE_ZOT) - ); - if ($r) - $url = $r[0]['site_url']; - } - - $blacklisted = false; + if ($url) { + logger('fetching outbox: ' . $url); - if (!check_siteallowed($url)) { - logger('blacklisted site: ' . $url); - $blacklisted = true; - } + $obj = new ASCollection($url, $importer, 0, 10); + $messages = $obj->get(); - $attempts++; + if ($messages) { + foreach ($messages as $message) { + if (is_string($message)) { + $message = Activity::fetch($message, $importer); + } - // make sure we can eventually break out if somebody blacklists all known sites + if ($message['type'] !== 'Create') { + continue; + } - if ($blacklisted) { - if ($attempts > 20) - break; - $attempts--; - continue; - } + if ($contact['hubloc_network'] === 'zot6') { + // make sure we only fetch top level items + if (isset($message['object']['inReplyTo'])) { + continue; + } - if ($url) { - if ($r[0]['site_pull'] > NULL_DATE) - $mindate = urlencode(datetime_convert('', '', $r[0]['site_pull'] . ' - 1 day')); - else { - $days = get_config('externals', 'since_days'); - if ($days === false) - $days = 15; - $mindate = urlencode(datetime_convert('', '', 'now - ' . intval($days) . ' days')); + Libzot::fetch_conversation($importer, $message['object']['id']); + $total++; + continue; } - $feedurl = $url . '/zotfeed?f=&mindate=' . $mindate; - - logger('externals: pulling public content from ' . $feedurl, LOGGER_DEBUG); - - $x = z_fetch_url($feedurl); - if (($x) && ($x['success'])) { - - q("update site set site_pull = '%s' where site_url = '%s'", - dbesc(datetime_convert()), - dbesc($url) - ); - - $j = json_decode($x['body'], true); - if ($j['success'] && $j['messages']) { - $sys = get_sys_channel(); - foreach ($j['messages'] as $message) { - // on these posts, clear any route info. - $message['route'] = ''; - process_delivery(['hash' => 'undefined'], get_item_elements($message), - [['hash' => $sys['xchan_hash']]], false, true); - $total++; - } - logger('externals: import_public_posts: ' . $total . ' messages imported', LOGGER_DEBUG); - } + $AS = new ActivityStreams($message); + if ($AS->is_valid() && is_array($AS->obj)) { + $item = Activity::decode_note($AS); + Activity::store($importer, $contact['abook_xchan'], $AS, $item); + $total++; } } - }*/ + } + logger('fetched messages count: ' . $total); + } + } + return; } } diff --git a/Zotlabs/Daemon/File_importer.php b/Zotlabs/Daemon/File_importer.php new file mode 100644 index 000000000..7067e152d --- /dev/null +++ b/Zotlabs/Daemon/File_importer.php @@ -0,0 +1,71 @@ +<?php + +namespace Zotlabs\Daemon; + +use Zotlabs\Web\HTTPSig; +use Zotlabs\Lib\PConfig; + + +require_once('include/cli_startup.php'); +require_once('include/attach.php'); +require_once('include/import.php'); + +class File_importer { + + static public function run($argc,$argv) { + + cli_startup(); + + $page = $argv[1]; + $channel_address = $argv[2]; + $hz_server = urldecode($argv[3]); + + $m = parse_url($hz_server); + + $channel = channelx_by_nick($channel_address); + if(! $channel) { + logger('channel not found'); + return; + } + + $headers = [ + 'X-API-Token' => random_string(), + 'X-API-Request' => $hz_server . '/api/z/1.0/file/export_page?f=records=1&page=' . $page, + 'Host' => $m['host'], + '(request-target)' => 'get /api/z/1.0/file/export_page?f=records=1&page=' . $page, + ]; + + $headers = HTTPSig::create_sig($headers,$channel['channel_prvkey'],channel_url($channel),true,'sha512'); + + // TODO: implement total count + $x = z_fetch_url($hz_server . '/api/z/1.0/file/export_page?f=records=1&page=' . $page, false, $redirects, [ 'headers' => $headers ]); + // logger('file fetch: ' . print_r($x,true)); + + if(! $x['success']) { + logger('no API response',LOGGER_DEBUG); + killme(); + } + + $j = json_decode($x['body'],true); + + if(! is_array($j['results'][0]['attach']) || ! count($j['results'][0]['attach'])) { + PConfig::Set($channel['channel_id'], 'import', 'files_completed', 1); + return; + } + + $r = sync_files($channel, $j['results']); + + PConfig::Set($channel['channel_id'], 'import', 'files_progress', [ + 'files_total' => $j['total'], + 'files_page' => 1, // export page atm returns just one file + 'last_page' => $page, + 'next_cmd' => ['File_importer',sprintf('%d',$page + 1), $channel['channel_address'], urlencode($hz_server)] + ]); + + $page++; + + Master::Summon([ 'File_importer',sprintf('%d',$page), $channel['channel_address'], urlencode($hz_server) ]); + + return; + } +} diff --git a/Zotlabs/Daemon/Notifier.php b/Zotlabs/Daemon/Notifier.php index 0ae887932..368a9229d 100644 --- a/Zotlabs/Daemon/Notifier.php +++ b/Zotlabs/Daemon/Notifier.php @@ -95,7 +95,6 @@ class Notifier { return; } - self::$deliveries = []; self::$recipients = []; self::$env_recips = []; @@ -170,7 +169,7 @@ class Notifier { elseif ($cmd === 'refresh_all') { logger('notifier: refresh_all: ' . $item_id); - self::$channel = channelx_by_n($item_id); + self::$channel = channelx_by_n($item_id, true); $r = q("select abook_xchan from abook where abook_channel = %d", intval($item_id) @@ -180,6 +179,11 @@ class Notifier { self::$recipients[] = $rr['abook_xchan']; } } + + // In case we deleted the channel, our abook entry has already vanished. + // In order to be able to update our clones we need to add ourself here. + self::$recipients[] = self::$channel['channel_hash']; + self::$private = false; self::$packet_type = 'refresh'; } @@ -190,14 +194,14 @@ class Notifier { return; } - self::$channel = channelx_by_n($item_id); + self::$channel = channelx_by_n($item_id, true); self::$recipients = [$xchan]; self::$private = true; self::$packet_type = 'purge'; } elseif ($cmd === 'purge_all') { logger('notifier: purge_all: ' . $item_id); - self::$channel = channelx_by_n($item_id); + self::$channel = channelx_by_n($item_id, true); self::$recipients = []; self::$private = false; self::$packet_type = 'purge'; @@ -443,7 +447,6 @@ class Notifier { } } - $narr = [ 'channel' => self::$channel, 'upstream' => $upstream, @@ -526,16 +529,18 @@ class Notifier { */ - $hublist = []; // this provides an easily printable list for the logs - $dhubs = []; // delivery hubs where we store our resulting unique array - $keys = []; // array of keys to check uniquness for zot hubs - $urls = []; // array of urls to check uniqueness of hubs from other networks - $hub_env = []; // per-hub envelope so we don't broadcast the entire envelope to all - $dead = []; // known dead hubs - report them as undeliverable + $hublist = []; // this provides an easily printable list for the logs + $dhubs = []; // delivery hubs where we store our resulting unique array + $keys = []; // array of keys to check uniquness for zot hubs + $urls = []; // array of urls to check uniqueness of hubs from other networks + $hub_env = []; // per-hub envelope so we don't broadcast the entire envelope to all + $dead_hosts = []; // known dead hubs - report them as undeliverable foreach ($hubs as $hub) { if (isset($hub['site_dead']) && intval($hub['site_dead'])) { - $dead[] = $hub; + if(!in_array($hub['hubloc_host'], $dead_hosts)) { + $dead_hosts[] = $hub['hubloc_host']; + } continue; } @@ -674,21 +679,19 @@ class Notifier { do_delivery(self::$deliveries); } - if ($dead) { - foreach ($dead as $deceased) { - if (is_array($target_item) && (!$target_item['item_deleted']) && (!get_config('system', 'disable_dreport'))) { - q("insert into dreport ( dreport_mid, dreport_site, dreport_recip, dreport_name, dreport_result, dreport_time, dreport_xchan, dreport_queue ) - values ( '%s', '%s','%s','%s','%s','%s','%s','%s' ) ", - dbesc($target_item['mid']), - dbesc($deceased['hubloc_host']), - dbesc($deceased['hubloc_host']), - dbesc($deceased['hubloc_host']), - dbesc('undeliverable/unresponsive site'), - dbesc(datetime_convert()), - dbesc(self::$channel['channel_hash']), - dbesc(new_uuid()) - ); - } + if ($dead_hosts && is_array($target_item) && (!$target_item['item_deleted']) && (!get_config('system', 'disable_dreport'))) { + foreach ($dead_hosts as $deceased_host) { + $r = q("insert into dreport ( dreport_mid, dreport_site, dreport_recip, dreport_name, dreport_result, dreport_time, dreport_xchan, dreport_queue ) + values ( '%s', '%s','%s','%s','%s','%s','%s','%s' ) ", + dbesc($target_item['mid']), + dbesc($deceased_host), + dbesc($deceased_host), + dbesc($deceased_host), + dbesc('undeliverable/unresponsive site'), + dbesc(datetime_convert()), + dbesc(self::$channel['channel_hash']), + dbesc(new_uuid()) + ); } } diff --git a/Zotlabs/Daemon/Onepoll.php b/Zotlabs/Daemon/Onepoll.php index 5374f49d5..79fd06df9 100644 --- a/Zotlabs/Daemon/Onepoll.php +++ b/Zotlabs/Daemon/Onepoll.php @@ -48,15 +48,11 @@ class Onepoll { $contact = $contacts[0]; $importer_uid = $contact['abook_channel']; - $r = q("SELECT * from channel left join xchan on channel_hash = xchan_hash where channel_id = %d limit 1", - intval($importer_uid) - ); + $importer = channelx_by_n($importer_uid); - if (!$r) + if (!$importer) return; - $importer = $r[0]; - logger("onepoll: poll: ({$contact['id']}) IMPORTER: {$importer['xchan_name']}, CONTACT: {$contact['xchan_name']}"); $last_update = ((($contact['abook_updated'] === $contact['abook_created']) || ($contact['abook_updated'] <= NULL_DATE)) @@ -135,19 +131,34 @@ class Onepoll { $url = $cl['outbox']; } else { - $url = str_replace('/poco/', '/zotfeed/', $contact['xchan_connurl']); + $url = str_replace('/poco/', '/outbox/', $contact['xchan_connurl']); } if ($url) { logger('fetching outbox'); - $url = $url . '?date_begin=' . urlencode($last_update); + $url = $url . '?date_begin=' . urlencode($last_update); + + if($contact['xchan_network'] === 'zot6') { + $url = $url . '&top=1'; + } + $obj = new ASCollection($url, $importer, 0, $max); $messages = $obj->get(); + if ($messages) { foreach ($messages as $message) { if (is_string($message)) { $message = Activity::fetch($message, $importer); } + + if ($contact['xchan_network'] === 'zot6') { + // make sure we only fetch top level items + if ($message['type'] === 'Create' && !isset($message['object']['inReplyTo'])) { + Libzot::fetch_conversation($importer, $message['object']['id']); + } + continue; + } + $AS = new ActivityStreams($message); if ($AS->is_valid() && is_array($AS->obj)) { $item = Activity::decode_note($AS); diff --git a/Zotlabs/Lib/Activity.php b/Zotlabs/Lib/Activity.php index a0a9a7c7f..664886fc2 100644 --- a/Zotlabs/Lib/Activity.php +++ b/Zotlabs/Lib/Activity.php @@ -8,8 +8,6 @@ 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'); @@ -104,7 +102,7 @@ class Activity { if ($x['success']) { $m = parse_url($url); if ($m) { - $y = [ 'scheme' => $m['scheme'], 'host' => $m['host'] ]; + $y = ['scheme' => $m['scheme'], 'host' => $m['host']]; if (array_key_exists('port', $m)) $y['port'] = $m['port']; $site_url = unparse_url($y); @@ -288,21 +286,21 @@ class Activity { 'type' => $type . 'Page', ]; - $numpages = $total / App::$pager['itemspage']; - $lastpage = (($numpages > intval($numpages)) ? intval($numpages) + 1 : $numpages); + $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'])) { + $query_args = null; + if (isset($url_parts['query'])) { parse_str($url_parts['query'], $query_args); } - if(is_array($query_args)) { + if (is_array($query_args)) { unset($query_args['page']); - foreach($query_args as $k => $v) + foreach ($query_args as $k => $v) $extra_query_args .= '&' . urlencode($k) . '=' . urlencode($v); } @@ -376,11 +374,33 @@ class Activity { return $ret; } - static function encode_item($i) { + static function encode_simple_collection($items, $id, $type, $total = 0, $extra = null) { - $ret = []; + $ret = [ + 'id' => z_root() . '/' . $id, + 'type' => $type, + 'totalItems' => $total, + ]; + if ($extra) { + $ret = array_merge($ret, $extra); + } + + if ($items) { + if ($type === 'OrderedCollection') { + $ret['orderedItems'] = $items; + } + else { + $ret['items'] = $items; + } + } + + return $ret; + } + + static function encode_item($i) { + $ret = []; if ($i['verb'] === ACTIVITY_FRIEND) { // Hubzilla 'make-friend' activity, no direct mapping from AS1 to AS2 - make it a note @@ -1095,7 +1115,33 @@ class Activity { 'height' => 300, 'width' => 300, ]; - $ret['url'] = $p['xchan_url']; + +/* This could be used to distinguish actors by protocol instead of tags, + * array urls are not supported by some AP projects (pixelfed) though. + * + $ret['url'] = [ + [ + 'type' => 'Link', + 'rel' => 'alternate', + 'mediaType' => 'application/x-zot+json', + 'href' => $p['xchan_url'] + ], + [ + 'type' => 'Link', + 'rel' => 'alternate', + 'mediaType' => 'application/activity+json', + 'href' => $p['xchan_url'] + ], + [ + 'type' => 'Link', + 'rel' => 'alternate', // 'me'? + 'mediaType' => 'text/html', + 'href' => $p['xchan_url'] + ] + ]; +*/ + + $ret['url'] = $p['xchan_url']; $ret['publicKey'] = [ 'id' => $p['xchan_url'], @@ -1103,15 +1149,24 @@ class Activity { 'publicKeyPem' => $p['xchan_pubkey'] ]; + if ($c) { + $ret['tag'][] = [ + 'type' => 'PropertyValue', + 'name' => 'Protocol', + 'value' => 'zot6' + ]; + + $ret['outbox'] = z_root() . '/outbox/' . $c['channel_address']; + } + $arr = [ - 'xchan' => $p, + 'xchan' => $p, 'encoded' => $ret ]; call_hooks('encode_person', $arr); $ret = $arr['encoded']; - return $ret; } @@ -1119,8 +1174,8 @@ class Activity { $ret = []; if ($item[$elm]) { - if (! is_array($item[$elm])) { - $item[$elm] = json_decode($item[$elm],true); + if (!is_array($item[$elm])) { + $item[$elm] = json_decode($item[$elm], true); } if ($item[$elm]['type'] === ACTIVITY_OBJ_PHOTO) { $item[$elm]['id'] = $item['mid']; @@ -1150,22 +1205,22 @@ class Activity { } $acts = [ - 'http://activitystrea.ms/schema/1.0/post' => 'Create', - 'http://activitystrea.ms/schema/1.0/share' => 'Announce', - 'http://activitystrea.ms/schema/1.0/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/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' + '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); @@ -1198,22 +1253,22 @@ class Activity { 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://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' + '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); @@ -1325,7 +1380,7 @@ class Activity { * */ - if (in_array($act->type, [ 'Follow', 'Invite', 'Join'])) { + if (in_array($act->type, ['Follow', 'Invite', 'Join'])) { $their_follow_id = $act->id; } @@ -1348,8 +1403,8 @@ class Activity { } } - $x = \Zotlabs\Access\PermissionRoles::role_perms('social'); - $their_perms = \Zotlabs\Access\Permissions::FilledPerms($x['perms_connect']); + $x = PermissionRoles::role_perms('social'); + $their_perms = Permissions::FilledPerms($x['perms_connect']); if ($contact && $contact['abook_id']) { @@ -1423,7 +1478,7 @@ class Activity { } $ret = $r[0]; - $p = \Zotlabs\Access\Permissions::connect_perms($channel['channel_id']); + $p = Permissions::connect_perms($channel['channel_id']); $my_perms = $p['perms']; $automatic = $p['automatic']; @@ -1444,13 +1499,13 @@ class Activity { ] ); - if($my_perms) - foreach($my_perms as $k => $v) - set_abconfig($channel['channel_id'],$ret['xchan_hash'],'my_perms',$k,$v); + if ($my_perms) + foreach ($my_perms as $k => $v) + set_abconfig($channel['channel_id'], $ret['xchan_hash'], 'my_perms', $k, $v); - if($their_perms) - foreach($their_perms as $k => $v) - set_abconfig($channel['channel_id'],$ret['xchan_hash'],'their_perms',$k,$v); + if ($their_perms) + foreach ($their_perms as $k => $v) + set_abconfig($channel['channel_id'], $ret['xchan_hash'], 'their_perms', $k, $v); if ($r) { logger("New ActivityPub follower for {$channel['channel_name']}"); @@ -1540,16 +1595,16 @@ class Activity { 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; - } -*/ + /* 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); @@ -1567,7 +1622,7 @@ class Activity { if ($ap_hubloc) { // we already have a stored record. Determine if it needs updating. - if ($ap_hubloc['hubloc_updated'] < datetime_convert('UTC','UTC',' now - 3 days') || $force) { + if ($ap_hubloc['hubloc_updated'] < datetime_convert('UTC', 'UTC', ' now - 3 days') || $force) { $person_obj = self::fetch($url); } else { @@ -1579,7 +1634,7 @@ class Activity { $url = $person_obj['id']; } - if (! $url) { + if (!$url) { return; } @@ -1602,6 +1657,19 @@ class Activity { $name = t('Unknown'); } + $webfinger_addr = ''; + + $m = parse_url($url); + if ($m) { + $hostname = $m['host']; + $baseurl = $m['scheme'] . '://' . $m['host'] . (($m['port']) ? ':' . $m['port'] : ''); + $site_url = $m['scheme'] . '://' . $m['host']; + } + + if (!empty($person_obj['preferredUsername']) && isset($parsed_url['host'])) { + $webfinger_addr = escape_tags($person_obj['preferredUsername']) . '@' . $hostname; + } + $icon = z_root() . '/' . get_default_profile_photo(300); if ($person_obj['icon']) { if (is_array($person_obj['icon'])) { @@ -1661,22 +1729,15 @@ class Activity { } } - $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) { + 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 - 3 days'); - if($r[0]['hubloc_updated'] > $d && !$force) { + if ($r[0]['hubloc_updated'] > $d && !$force) { return; } @@ -1686,17 +1747,19 @@ class Activity { ); // 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'", + q("update xchan set xchan_name = '%s', xchan_guid = '%s', xchan_pubkey = '%s', xchan_addr = '%s', xchan_network = 'activitypub', xchan_name_date = '%s' where xchan_hash = '%s'", dbesc(escape_tags($name)), dbesc(escape_tags($url)), dbesc(escape_tags($pubkey)), + dbesc(escape_tags($webfinger_addr)), dbescdate(datetime_convert()), dbesc($url) ); // 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'", + q("update hubloc set hubloc_guid = '%s', hubloc_addr = '%s', hubloc_network = 'activitypub', hubloc_url = '%s', hubloc_host = '%s', hubloc_callback = '%s', hubloc_updated = '%s', hubloc_id_url = '%s' where hubloc_hash = '%s'", dbesc(escape_tags($url)), + dbesc(escape_tags($webfinger_addr)), dbesc(escape_tags($baseurl)), dbesc(escape_tags($hostname)), dbesc(escape_tags($inbox)), @@ -1710,14 +1773,14 @@ class Activity { 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' + 'xchan_hash' => escape_tags($url), + 'xchan_guid' => escape_tags($url), + 'xchan_pubkey' => escape_tags($pubkey), + 'xchan_addr' => $webfinger_addr, + 'xchan_url' => escape_tags($profile), + 'xchan_name' => escape_tags($name), + 'xchan_name_date' => datetime_convert(), + 'xchan_network' => 'activitypub' ] ); @@ -1725,7 +1788,7 @@ class Activity { [ 'hubloc_guid' => escape_tags($url), 'hubloc_hash' => escape_tags($url), - 'hubloc_addr' => '', + 'hubloc_addr' => $webfinger_addr, 'hubloc_network' => 'activitypub', 'hubloc_url' => escape_tags($baseurl), 'hubloc_host' => escape_tags($hostname), @@ -1737,6 +1800,19 @@ class Activity { ); } + // We store all ActivityPub actors we can resolve. Some of them may be able to communicate over Zot6. Find them. + // Adding zot discovery urls to the actor record will cause federation to fail with the 20-30 projects which don't accept arrays in the url field. + + $actor_protocols = self::get_actor_protocols($person_obj); + if (in_array('zot6', $actor_protocols)) { + $zx = q("select * from hubloc where hubloc_id_url = '%s' and hubloc_network = 'zot6'", + dbesc($url) + ); + if (!$zx && $webfinger_addr) { + Master::Summon(['Gprobe', bin2hex($webfinger_addr)]); + } + } + $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])), @@ -1784,9 +1860,9 @@ class Activity { static function create_note($channel, $observer_hash, $act) { - $s = []; + $s = []; $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) { @@ -2138,7 +2214,7 @@ class Activity { // 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. - if (in_array($act->type, [ 'Follow', 'Unfollow' ])) { + if (in_array($act->type, ['Follow', 'Unfollow'])) { return false; } @@ -2161,10 +2237,12 @@ class Activity { $s['parent_mid'] = $act->parent_id; if (array_key_exists('published', $act->data)) { - $s['created'] = datetime_convert('UTC', 'UTC', $act->data['published']); + $s['created'] = datetime_convert('UTC', 'UTC', $act->data['published']); + $s['commented'] = $s['created']; } elseif (array_key_exists('published', $act->obj)) { - $s['created'] = datetime_convert('UTC', 'UTC', $act->obj['published']); + $s['created'] = datetime_convert('UTC', 'UTC', $act->obj['published']); + $s['commented'] = $s['created']; } if (array_key_exists('updated', $act->data)) { $s['edited'] = datetime_convert('UTC', 'UTC', $act->data['updated']); @@ -2235,10 +2313,10 @@ class Activity { } } - if (! array_key_exists('created', $s)) + if (!array_key_exists('created', $s)) $s['created'] = datetime_convert(); - if (! array_key_exists('edited', $s)) + if (!array_key_exists('edited', $s)) $s['edited'] = $s['created']; $s['title'] = (($response_activity) ? EMPTY_STR : self::bb_content($content, 'name')); @@ -2447,7 +2525,7 @@ class Activity { } - if ($act->obj['type'] === 'Image' && strpos($s['body'],'zrl=') === false) { + if ($act->obj['type'] === 'Image' && strpos($s['body'], 'zrl=') === false) { $ptr = null; @@ -3115,7 +3193,7 @@ class Activity { static function announce_note($channel, $observer_hash, $act) { - $s = []; + $s = []; $is_sys_channel = is_sys_channel($channel['channel_id']); if (!perm_is_allowed($channel['channel_id'], $observer_hash, 'send_stream') && !$is_sys_channel) { @@ -3375,7 +3453,7 @@ class Activity { $ret = false; foreach ($attach as $a) { - if (array_key_exists('type',$a) && stripos($a['type'], 'image') !== false) { + 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]'; } @@ -3533,7 +3611,7 @@ 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 = 'zot6' and hubloc_deleted = 0", + $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) { @@ -3548,7 +3626,7 @@ class Activity { } static function get_cached_actor($id) { - $actor = XConfig::Get($id,'system', 'actor_record'); + $actor = XConfig::Get($id, 'system', 'actor_record'); if ($actor) { return $actor; @@ -3556,7 +3634,7 @@ class Activity { // try other get_cached_actor providers (e.g. diaspora) $hookdata = [ - 'id' => $id, + 'id' => $id, 'actor' => false ]; @@ -3567,8 +3645,6 @@ class Activity { 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 ", @@ -3593,21 +3669,41 @@ class Activity { } static function get_actor_collections($url) { - $ret = []; - $actor_record = XConfig::Get($url,'system','actor_record'); - if (! $actor_record) { + $ret = []; + $actor_record = XConfig::Get($url, 'system', 'actor_record'); + if (!$actor_record) { return $ret; } - foreach ( [ 'inbox','outbox','followers','following' ] as $collection) { + 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']) { + if (array_path_exists('endpoints/sharedInbox', $actor_record) && $actor_record['endpoints']['sharedInbox']) { $ret['sharedInbox'] = $actor_record['endpoints']['sharedInbox']; } return $ret; } + + + static function get_actor_protocols($actor) { + $ret = []; + + if (!array_key_exists('tag', $actor) || empty($actor['tag']) || !is_array($actor['tag'])) { + return $ret; + } + + foreach ($tag as $t) { + if ((isset($t['type']) && $t['type'] === 'PropertyValue') && + (isset($t['name']) && $t['name'] === 'Protocol') && + (isset($t['value']) && in_array($t['value'], ['zot6', 'activitypub', 'diaspora'])) + ) { + $ret[] = $t['value']; + } + } + + return $ret; + } } diff --git a/Zotlabs/Lib/Apps.php b/Zotlabs/Lib/Apps.php index 3c09adaa5..c4ddcff1b 100644 --- a/Zotlabs/Lib/Apps.php +++ b/Zotlabs/Lib/Apps.php @@ -3,7 +3,6 @@ namespace Zotlabs\Lib; use App; -use Zotlabs\Lib\Libsync; require_once('include/plugin.php'); require_once('include/channel.php'); @@ -22,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')) @@ -34,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; } @@ -46,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; @@ -210,9 +210,10 @@ class Apps { * * @param string $f filename * @param boolean $translate (optional) default true + * @param boolean $sync (optional) default false * @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 = []; @@ -258,7 +259,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)); @@ -310,14 +311,16 @@ class Apps { } } } - if(isset($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; } @@ -624,10 +627,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", @@ -635,13 +640,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])); } } } @@ -670,6 +674,7 @@ class Apps { } } } + return true; } @@ -681,38 +686,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) + ); } } } @@ -729,13 +731,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) + ); } } } @@ -1194,9 +1194,9 @@ class Apps { $y = explode(',',$arr['categories']); if($y) { foreach($y as $t) { - $t = trim($t); + $t = escape_tags(trim($t)); if($t) { - store_item_tag($darray['app_channel'],$x[0]['id'],TERM_OBJ_APP,TERM_CATEGORY,escape_tags($t),escape_tags(z_root() . '/apps/?f=&cat=' . escape_tags($t))); + store_item_tag($darray['app_channel'], $x[0]['id'], TERM_OBJ_APP, TERM_CATEGORY, $t, z_root() . '/apps/?f=&cat=' . $t); } } } diff --git a/Zotlabs/Lib/Enotify.php b/Zotlabs/Lib/Enotify.php index 632848290..d02dab739 100644 --- a/Zotlabs/Lib/Enotify.php +++ b/Zotlabs/Lib/Enotify.php @@ -127,7 +127,7 @@ class Enotify { logger('notification: mail'); $subject = sprintf( t('[$Projectname:Notify] New direct message received at %s'), $sitename); - $preamble = sprintf( t('%1$s sent you a new direct message at %2$s.'), $sender['xchan_name'], $sitename); + $preamble = sprintf( t('%1$s sent you a new 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'])); @@ -238,7 +238,7 @@ class Enotify { $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']); + $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.'); @@ -318,7 +318,7 @@ class Enotify { // 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']); + $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.'); diff --git a/Zotlabs/Lib/Libsync.php b/Zotlabs/Lib/Libsync.php index 67603a0e3..c4f1b20ea 100644 --- a/Zotlabs/Lib/Libsync.php +++ b/Zotlabs/Lib/Libsync.php @@ -141,7 +141,6 @@ class Libsync { logger('Packet: ' . print_r($info, true), LOGGER_DATA, LOG_DEBUG); $total = count($synchubs); - 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']); @@ -186,7 +185,6 @@ class Libsync { require_once('include/import.php'); $result = []; - $keychange = ((array_key_exists('keychange', $arr)) ? true : false); foreach ($deliveries as $d) { @@ -246,6 +244,10 @@ class Libsync { 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('addressbook', $arr) && $arr['addressbook']) sync_addressbook($channel, $arr['addressbook']); @@ -255,8 +257,8 @@ class Libsync { 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('mail', $arr) && $arr['mail']) + // sync_mail($channel, $arr['mail']); if (array_key_exists('event', $arr) && $arr['event']) sync_events($channel, $arr['event']); @@ -270,8 +272,8 @@ class Libsync { // 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']); @@ -704,6 +706,15 @@ class Libsync { $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 ($absolute) @@ -861,6 +872,7 @@ class Libsync { $what .= 'delete_hub '; $changed = true; } + continue; } diff --git a/Zotlabs/Lib/Libzot.php b/Zotlabs/Lib/Libzot.php index e03f0173d..31b8f04de 100644 --- a/Zotlabs/Lib/Libzot.php +++ b/Zotlabs/Lib/Libzot.php @@ -266,7 +266,7 @@ class Libzot { 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']) ); @@ -275,8 +275,8 @@ class Libzot { if ($r) { foreach ($r as $rr) { if (intval($rr['hubloc_primary'])) { - $url = $rr['hubloc_id_url']; - $record = $rr; + $url = $rr['hubloc_id_url']; + break; } } if (!$url) { @@ -284,13 +284,17 @@ class Libzot { } } } + 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)) { @@ -299,25 +303,25 @@ class Libzot { } $record = Zotfinger::exec($url, $channel); - // Check the HTTP signature + // 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)); return false; } - 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']); @@ -357,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 @@ -368,17 +373,19 @@ 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']))) + if (!$old_read_stream_perm && intval($permissions['view_stream'])) { Master::Summon(['Onepoll', $r[0]['abook_id']]); + } } } else { - $p = Permissions::connect_perms($channel['channel_id']); + $p = Permissions::connect_perms($channel['channel_id']); $my_perms = $p['perms']; $automatic = $p['automatic']; @@ -419,8 +426,10 @@ class Libzot { ); if ($new_connection) { - if (!Permissions::PermsCompare($new_perms, $previous_perms)) + if (!Permissions::PermsCompare($new_perms, $previous_perms)) { Master::Summon(['Notifier', 'permission_create', $new_connection[0]['abook_id']]); + } + Enotify::submit( [ 'type' => NOTIFY_INTRO, @@ -432,40 +441,47 @@ class Libzot { if (intval($permissions['view_stream'])) { if (intval(get_pconfig($channel['channel_id'], 'perm_limits', 'send_stream') & PERMS_PENDING) - || (!intval($new_connection[0]['abook_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'])) { $default_group = $channel['channel_default_group']; + if ($default_group) { $g = Group::rec_byhash($channel['channel_id'], $default_group); - if ($g) + + 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) + + if ($abconfig) { $new_connection['abconfig'] = $abconfig; + } Libsync::build_sync_packet($channel['channel_id'], ['abook' => $new_connection]); + } } - } return true; } return false; } + /** * @brief Look up if channel is known and previously verified. * @@ -479,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 @@ -488,7 +505,7 @@ 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'])) { logger('blacklisted site: ' . $arr['location']); @@ -512,9 +529,9 @@ class Libzot { 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; } @@ -616,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 @@ -666,6 +682,7 @@ class Libzot { $arr['connect_url'] = ''; if ($r) { + if ($arr['photo'] && array_key_exists('updated', $arr['photo']) && $arr['photo']['updated'] > $r[0]['xchan_photo_date']) $import_photos = true; @@ -1339,7 +1356,7 @@ class Libzot { static function find_parent($env, $act) { if ($act) { - if (in_array($act->type, ['Like', 'Dislike'])) { + if (in_array($act->type, ['Like', 'Dislike']) && is_array($act->obj)) { return $act->obj['id']; } if ($act->parent_id) { @@ -1469,10 +1486,11 @@ class Libzot { * @param boolean $relay * @param boolean $public (optional) default false * @param boolean $request (optional) default false + * @param boolean $force (optional) default false - should only be set for manual fetch * @return array */ - static function process_delivery($sender, $act, $arr, $deliveries, $relay, $public = false, $request = false) { + static function process_delivery($sender, $act, $arr, $deliveries, $relay, $public = false, $request = false, $force = false) { $result = []; @@ -1574,7 +1592,7 @@ class Libzot { if ((!$tag_delivery) && (!$local_public)) { $allowed = (perm_is_allowed($channel['channel_id'], $sender, $perm)); - if (!$allowed) { + if ((!$allowed) && $perm === 'post_comments') { $parent = q("select * from item where mid = '%s' and uid = %d limit 1", dbesc($arr['parent_mid']), intval($channel['channel_id']) @@ -1600,7 +1618,7 @@ class Libzot { // doesn't exist. if ($perm === 'send_stream') { - if (get_pconfig($channel['channel_id'], 'system', 'hyperdrive', false) || $arr['verb'] === ACTIVITY_SHARE) { + if ($force || get_pconfig($channel['channel_id'], 'system', 'hyperdrive', false) || $arr['verb'] === ACTIVITY_SHARE) { $allowed = true; } } @@ -1875,7 +1893,7 @@ class Libzot { return $result; } - static public function fetch_conversation($channel, $mid) { + static public function fetch_conversation($channel, $mid, $force = false) { // Use Zotfinger to create a signed request @@ -1979,7 +1997,7 @@ class Libzot { logger('FOF Activity received: ' . print_r($arr, true), LOGGER_DATA, LOG_DEBUG); logger('FOF Activity recipient: ' . $channel['channel_hash'], LOGGER_DATA, LOG_DEBUG); - $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, $force); if ($result) { $ret = array_merge($ret, $result); } @@ -2880,8 +2898,9 @@ class Libzot { 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. diff --git a/Zotlabs/Lib/NativeWiki.php b/Zotlabs/Lib/NativeWiki.php index 9e6a3ac85..bf4ac8e87 100644 --- a/Zotlabs/Lib/NativeWiki.php +++ b/Zotlabs/Lib/NativeWiki.php @@ -12,8 +12,8 @@ class NativeWiki { public static function listwikis($channel, $observer_hash) { $sql_extra = item_permissions_sql($channel['channel_id'], $observer_hash); - $wikis = q("SELECT * FROM item - WHERE resource_type = '%s' AND mid = parent_mid AND uid = %d AND item_deleted = 0 $sql_extra", + $wikis = q("SELECT * FROM item + WHERE resource_type = '%s' AND mid = parent_mid AND uid = %d AND item_deleted = 0 $sql_extra", dbesc(NWIKI_ITEM_RESOURCE_TYPE), intval($channel['channel_id']) ); @@ -49,7 +49,7 @@ class NativeWiki { $mid = z_root() . '/item/' . $uuid; $arr = array(); // Initialize the array of parameters for the post - $item_hidden = ((intval($wiki['postVisible']) === 0) ? 1 : 0); + $item_hidden = ((intval($wiki['postVisible']) === 0) ? 1 : 0); $wiki_url = z_root() . '/wiki/' . $channel['channel_address'] . '/' . $wiki['urlName']; $arr['aid'] = $channel['channel_account_id']; $arr['uuid'] = $uuid; @@ -61,8 +61,8 @@ class NativeWiki { $arr['resource_id'] = $resource_id; $arr['owner_xchan'] = $channel['channel_hash']; $arr['author_xchan'] = $observer_hash; - $arr['plink'] = z_root() . '/channel/' . $channel['channel_address'] . '/?f=&mid=' . urlencode($arr['mid']); - $arr['llink'] = $arr['plink']; + $arr['plink'] = $mid; + $arr['llink'] = z_root() . '/display/' . gen_link_id($mid); $arr['title'] = $wiki['htmlName']; // name of new wiki; $arr['allow_cid'] = $ac['allow_cid']; $arr['allow_gid'] = $ac['allow_gid']; @@ -133,13 +133,13 @@ class NativeWiki { // update acl for any existing wiki pages q("update item set allow_cid = '%s', allow_gid = '%s', deny_cid = '%s', deny_gid = '%s', item_private = %d where resource_type = 'nwikipage' and resource_id = '%s'", - dbesc($item['allow_cid']), - dbesc($item['allow_gid']), - dbesc($item['deny_cid']), - dbesc($item['deny_gid']), - dbesc($item['item_private']), + dbesc($item['allow_cid']), + dbesc($item['allow_gid']), + dbesc($item['deny_cid']), + dbesc($item['deny_gid']), + dbesc($item['item_private']), dbesc($arr['resource_id']) - ); + ); if($update['item_id']) { @@ -211,12 +211,12 @@ class NativeWiki { 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 + $item = q("SELECT * FROM item WHERE uid = %d AND resource_type = '%s' AND resource_id = '%s' AND item_deleted = 0 $sql_extra ORDER BY id LIMIT 1", - intval($channel_id), + intval($channel_id), dbesc(NWIKI_ITEM_RESOURCE_TYPE), dbesc($resource_id) ); @@ -224,7 +224,7 @@ class NativeWiki { return [ 'wiki' => null ]; } else { - + $w = $item[0]; // wiki item table record // Get wiki metadata $rawName = get_iconfig($w, 'wiki', 'rawName'); @@ -246,20 +246,20 @@ class NativeWiki { public static function exists_by_name($uid, $urlName) { - $sql_extra = item_permissions_sql($uid); + $sql_extra = item_permissions_sql($uid); - $item = q("SELECT item.id, resource_id FROM item left join iconfig on iconfig.iid = item.id - WHERE resource_type = '%s' AND iconfig.v = '%s' AND uid = %d - AND item_deleted = 0 $sql_extra limit 1", - dbesc(NWIKI_ITEM_RESOURCE_TYPE), - //dbesc(urldecode($urlName)), + $item = q("SELECT item.id, resource_id FROM item left join iconfig on iconfig.iid = item.id + WHERE resource_type = '%s' AND iconfig.v = '%s' AND uid = %d + AND item_deleted = 0 $sql_extra limit 1", + dbesc(NWIKI_ITEM_RESOURCE_TYPE), + //dbesc(urldecode($urlName)), dbesc(self::name_decode($urlName)), intval($uid) ); if($item) { return array('id' => $item[0]['id'], 'resource_id' => $item[0]['resource_id']); - } + } else { return array('id' => null, 'resource_id' => null); } @@ -277,7 +277,7 @@ class NativeWiki { $r = q("SELECT * FROM item WHERE uid = %d and resource_type = '%s' AND resource_id = '%s' $sql_extra LIMIT 1", intval($owner_id), - dbesc(NWIKI_ITEM_RESOURCE_TYPE), + dbesc(NWIKI_ITEM_RESOURCE_TYPE), dbesc($resource_id) ); @@ -285,8 +285,6 @@ class NativeWiki { return array('read' => false, 'write' => false, 'success' => true); } else { - // TODO: Create a new permission setting for wiki analogous to webpages. Until - // then, use webpage permissions $write = perm_is_allowed($owner_id, $observer_hash,'write_wiki'); return array('read' => true, 'write' => $write, 'success' => true); } diff --git a/Zotlabs/Lib/NativeWikiPage.php b/Zotlabs/Lib/NativeWikiPage.php index 3c61ea800..1e944f7ac 100644 --- a/Zotlabs/Lib/NativeWikiPage.php +++ b/Zotlabs/Lib/NativeWikiPage.php @@ -2,14 +2,15 @@ namespace Zotlabs\Lib; -use \Zotlabs\Lib as Zlib; +use App; +use Zotlabs\Access\PermissionLimits; class NativeWikiPage { - static public function page_list($channel_id,$observer_hash, $resource_id) { + static public function page_list($channel_id, $observer_hash, $resource_id) { // TODO: Create item table records for pages so that metadata like title can be applied - $w = Zlib\NativeWiki::get_wiki($channel_id,$observer_hash,$resource_id); + $w = NativeWiki::get_wiki($channel_id, $observer_hash, $resource_id); $pages[] = [ 'resource_id' => '', @@ -18,134 +19,149 @@ class NativeWikiPage { 'link_id' => 'id_wiki_home_0' ]; - $sql_extra = item_permissions_sql($channel_id,$observer_hash); + $sql_extra = item_permissions_sql($channel_id, $observer_hash); - $r = q("select * from item where resource_type = 'nwikipage' and resource_id = '%s' and uid = %d and item_deleted = 0 + $r = q("select * from item where resource_type = 'nwikipage' and resource_id = '%s' and uid = %d and item_deleted = 0 $sql_extra order by title asc", dbesc($resource_id), intval($channel_id) ); - if($r) { + if ($r) { $x = []; $y = []; - foreach($r as $rv) { - if(! in_array($rv['mid'],$x)) { + foreach ($r as $rv) { + if (!in_array($rv['mid'], $x)) { $y[] = $rv; $x[] = $rv['mid']; } } - $items = fetch_post_tags($y,true); + $items = fetch_post_tags($y, true); - foreach($items as $page_item) { - $title = get_iconfig($page_item['id'],'nwikipage','pagetitle',t('(No Title)')); - if(urldecode($title) !== 'Home') { + foreach ($items as $page_item) { + $title = get_iconfig($page_item['id'], 'nwikipage', 'pagetitle', t('(No Title)')); + if (urldecode($title) !== 'Home') { $pages[] = [ 'resource_id' => $resource_id, 'title' => escape_tags($title), //'url' => str_replace('%2F','/',urlencode(str_replace('%2F','/',urlencode($title)))), - 'url' => Zlib\NativeWiki::name_encode($title), + 'url' => NativeWiki::name_encode($title), 'link_id' => 'id_' . substr($resource_id, 0, 10) . '_' . $page_item['id'] ]; } } } - return array('pages' => $pages, 'wiki' => $w); + return ['pages' => $pages, 'wiki' => $w]; } - static public function create_page($channel_id, $observer_hash, $name, $resource_id, $mimetype = 'text/bbcode') { + static public function create_page($channel, $observer_hash, $name, $resource_id, $mimetype = 'text/bbcode') { logger('mimetype: ' . $mimetype); - if(! in_array($mimetype,[ 'text/markdown','text/bbcode','text/plain','text/html' ])) + if (!in_array($mimetype, ['text/markdown', 'text/bbcode', 'text/plain', 'text/html'])) $mimetype = 'text/markdown'; - $w = Zlib\NativeWiki::get_wiki($channel_id, $observer_hash, $resource_id); + $w = NativeWiki::get_wiki($channel['channel_id'], $observer_hash, $resource_id); - if (! $w['wiki']) { - return array('content' => null, 'message' => 'Error reading wiki', 'success' => false); + if (!$w['wiki']) { + return ['content' => null, 'message' => 'Error reading wiki', 'success' => false]; } // backslashes won't work well in the javascript functions - $name = str_replace('\\','',$name); - - // create an empty activity + $name = str_replace('\\', '', $name); - $arr = []; - $arr['uid'] = $channel_id; - $arr['author_xchan'] = $observer_hash; - $arr['mimetype'] = $mimetype; - $arr['title'] = $name; - $arr['resource_type'] = 'nwikipage'; - $arr['resource_id'] = $resource_id; - $arr['allow_cid'] = $w['wiki']['allow_cid']; - $arr['allow_gid'] = $w['wiki']['allow_gid']; - $arr['deny_cid'] = $w['wiki']['deny_cid']; - $arr['deny_gid'] = $w['wiki']['deny_gid']; + $uuid = new_uuid(); + $mid = z_root() . '/item/' . $uuid; - $arr['public_policy'] = map_scope(\Zotlabs\Access\PermissionLimits::Get($channel_id,'view_wiki'),true); + // create an empty activity + $arr = []; + $arr['aid'] = $channel['channel_account_id']; + $arr['uid'] = $channel['channel_id']; + $arr['mid'] = $mid; + $arr['parent_mid'] = $w['wiki']['mid']; + $arr['parent'] = $w['wiki']['parent']; + $arr['uuid'] = $uuid; + $arr['item_hidden'] = $w['wiki']['item_hidden']; + $arr['plink'] = $mid; + $arr['llink'] = z_root() . '/display/' . gen_link_id($mid); + $arr['author_xchan'] = $observer_hash; + $arr['mimetype'] = $mimetype; + $arr['title'] = $name; + $arr['resource_type'] = 'nwikipage'; + $arr['resource_id'] = $resource_id; + $arr['allow_cid'] = $w['wiki']['allow_cid']; + $arr['allow_gid'] = $w['wiki']['allow_gid']; + $arr['deny_cid'] = $w['wiki']['deny_cid']; + $arr['deny_gid'] = $w['wiki']['deny_gid']; + $arr['item_private'] = $w['wiki']['item_private']; + $arr['item_wall'] = 1; + $arr['item_origin'] = 1; + $arr['item_thread_top'] = 1; + $arr['verb'] = ACTIVITY_CREATE; + $arr['obj_type'] = 'Document'; + // TODO: add an object? + $arr['public_policy'] = map_scope(PermissionLimits::Get($channel['channel_id'], 'view_wiki'), true); // We may wish to change this some day. $arr['item_unpublished'] = 1; - set_iconfig($arr,'nwikipage','pagetitle',(($name) ? $name : t('(No Title)')),true); - - $p = post_activity_item($arr, false, false); + set_iconfig($arr, 'nwikipage', 'pagetitle', (($name) ? $name : t('(No Title)')), true); + $p = item_store($arr, false, false); - if($p['item_id']) { - $page = [ + if ($p['item_id']) { + $page = [ 'rawName' => $name, 'htmlName' => escape_tags($name), - //'urlName' => urlencode($name), - 'urlName' => Zlib\NativeWiki::name_encode($name) + //'urlName' => urlencode($name), + 'urlName' => NativeWiki::name_encode($name) ]; - return array('page' => $page, 'item_id' => $p['item_id'], 'item' => $p['activity'], 'wiki' => $w, 'message' => '', 'success' => true); + return ['page' => $page, 'item_id' => $p['item_id'], 'item' => $p['activity'], 'wiki' => $w, 'message' => '', 'success' => true]; } - return [ 'success' => false, 'message' => t('Wiki page create failed.') ]; + return ['success' => false, 'message' => t('Wiki page create failed.')]; } static public function rename_page($arr) { - $pageUrlName = ((array_key_exists('pageUrlName',$arr)) ? $arr['pageUrlName'] : ''); - $pageNewName = ((array_key_exists('pageNewName',$arr)) ? $arr['pageNewName'] : ''); - $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); + $pageUrlName = ((array_key_exists('pageUrlName', $arr)) ? $arr['pageUrlName'] : ''); + $pageNewName = ((array_key_exists('pageNewName', $arr)) ? $arr['pageNewName'] : ''); + $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 array('message' => t('Wiki not found.'), 'success' => false); + $w = NativeWiki::get_wiki($channel_id, $observer_hash, $resource_id); + if (!$w['wiki']) { + return ['message' => t('Wiki not found.'), 'success' => false]; } - $ic = q("select * from iconfig left join item on iconfig.iid = item.id + $ic = q("select * from iconfig left join item on iconfig.iid = item.id where uid = %d and cat = 'nwikipage' and k = 'pagetitle' and v = '%s'", intval($channel_id), dbesc($pageNewName) ); - if($ic) { - return [ 'success' => false, 'message' => t('Destination name already exists') ]; + if ($ic) { + return ['success' => false, 'message' => t('Destination name already exists')]; } $ids = []; - $ic = q("select *, item.id as item_id from iconfig left join item on iconfig.iid = item.id + $ic = q("select *, item.id as item_id from iconfig left join item on iconfig.iid = item.id where uid = %d and cat = 'nwikipage' and k = 'pagetitle' and v = '%s'", intval($channel_id), dbesc($pageUrlName) ); - if($ic) { - foreach($ic as $c) { - set_iconfig($c['item_id'],'nwikipage','pagetitle',$pageNewName); + if ($ic) { + foreach ($ic as $c) { + set_iconfig($c['item_id'], 'nwikipage', 'pagetitle', $pageNewName); $ids[] = $c['item_id']; } @@ -154,105 +170,101 @@ class NativeWikiPage { dbesc($pageNewName) ); - $page = [ - 'rawName' => $pageNewName, - 'htmlName' => escape_tags($pageNewName), + $page = [ + 'rawName' => $pageNewName, + 'htmlName' => escape_tags($pageNewName), //'urlName' => urlencode(escape_tags($pageNewName)) - 'urlName' => Zlib\NativeWiki::name_encode($pageNewName) + 'urlName' => NativeWiki::name_encode($pageNewName) ]; - return [ 'success' => true, 'page' => $page ]; + return ['success' => true, 'page' => $page]; } - return [ 'success' => false, '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'] : ''); - $channel_id = ((array_key_exists('channel_id',$arr)) ? intval($arr['channel_id']) : 0); - $revision = ((array_key_exists('revision',$arr)) ? intval($arr['revision']) : (-1)); - + $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)) ? intval($arr['channel_id']) : 0); - $w = Zlib\NativeWiki::get_wiki($channel_id, $observer_hash, $resource_id); - if (! $w['wiki']) { - return array('content' => null, 'message' => 'Error reading wiki', 'success' => false); + $w = NativeWiki::get_wiki($channel_id, $observer_hash, $resource_id); + if (!$w['wiki']) { + return ['content' => null, 'message' => 'Error reading wiki', 'success' => false]; } $item = self::load_page($arr); - if($item) { + if ($item) { $content = $item['body']; - return [ + return [ 'content' => $content, 'mimeType' => $w['mimeType'], - 'pageMimeType' => $item['mimetype'], - 'message' => '', + 'pageMimeType' => $item['mimetype'], + 'message' => '', 'success' => true ]; } - - return array('content' => null, 'message' => t('Error reading page content'), 'success' => false); + + return ['content' => null, 'message' => t('Error reading page content'), 'success' => false]; } 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'] : ''); - $channel_id = ((array_key_exists('channel_id',$arr)) ? $arr['channel_id'] : 0); + $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); + $w = NativeWiki::get_wiki($channel_id, $observer_hash, $resource_id); if (!$w['wiki']) { - return array('history' => null, 'message' => 'Error reading wiki', 'success' => false); + return ['history' => null, 'message' => 'Error reading wiki', 'success' => false]; } $items = self::load_page_history($arr); $history = []; - if($items) { + if ($items) { $processed = 0; - foreach($items as $item) { - if($processed > 1000) + foreach ($items as $item) { + if ($processed > 1000) break; - $processed ++; - $history[] = [ + $processed++; + $history[] = [ 'revision' => $item['revision'], - 'date' => datetime_convert('UTC',date_default_timezone_get(),$item['edited']), - 'name' => $item['author']['xchan_name'], - 'title' => get_iconfig($item,'nwikipage','commit_msg') + 'date' => datetime_convert('UTC', date_default_timezone_get(), $item['edited']), + 'name' => $item['author']['xchan_name'], + 'title' => get_iconfig($item, 'nwikipage', 'commit_msg') ]; } - return [ 'success' => true, 'history' => $history ]; + return ['success' => true, 'history' => $history]; } - return [ 'success' => false ]; + return ['success' => false]; } - + static public function load_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); - $revision = ((array_key_exists('revision',$arr)) ? $arr['revision'] : (-1)); + $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); + $revision = ((array_key_exists('revision', $arr)) ? $arr['revision'] : (-1)); - $w = Zlib\NativeWiki::get_wiki($channel_id, $observer_hash, $resource_id); + $w = NativeWiki::get_wiki($channel_id, $observer_hash, $resource_id); - if (! $w['wiki']) { - return array('content' => null, 'message' => 'Error reading wiki', 'success' => false); + if (!$w['wiki']) { + return ['content' => null, 'message' => 'Error reading wiki', 'success' => false]; } $ids = ''; @@ -262,32 +274,32 @@ class NativeWikiPage { dbesc($pageUrlName) ); - if($ic) { - foreach($ic as $c) { - if($ids) + if ($ic) { + foreach ($ic as $c) { + if ($ids) $ids .= ','; $ids .= intval($c['iid']); } } - $sql_extra = item_permissions_sql($channel_id,$observer_hash); + $sql_extra = item_permissions_sql($channel_id, $observer_hash); - if($revision == (-1)) + if ($revision == (-1)) $sql_extra .= " order by revision desc "; - elseif($revision) + elseif ($revision) $sql_extra .= " and revision = " . intval($revision) . " "; $r = null; - if($ids) { + if ($ids) { $r = q("select * from item where resource_type = 'nwikipage' and resource_id = '%s' and uid = %d and id in ( $ids ) $sql_extra limit 1", dbesc($resource_id), intval($channel_id) ); - if($r) { - $items = fetch_post_tags($r,true); + if ($r) { + $items = fetch_post_tags($r, true); return $items[0]; } } @@ -298,15 +310,14 @@ class NativeWikiPage { static public function load_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'] : ''); - $channel_id = ((array_key_exists('channel_id',$arr)) ? $arr['channel_id'] : 0); - $revision = ((array_key_exists('revision',$arr)) ? $arr['revision'] : (-1)); + $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 array('content' => null, 'message' => 'Error reading wiki', 'success' => false); + $w = NativeWiki::get_wiki($channel_id, $observer_hash, $resource_id); + if (!$w['wiki']) { + return ['content' => null, 'message' => 'Error reading wiki', 'success' => false]; } $ids = ''; @@ -315,28 +326,28 @@ class NativeWikiPage { intval($channel_id), dbesc($pageUrlName) ); - - if($ic) { - foreach($ic as $c) { - if($ids) + + if ($ic) { + foreach ($ic as $c) { + if ($ids) $ids .= ','; $ids .= intval($c['iid']); } } - $sql_extra = item_permissions_sql($channel_id,$observer_hash); + $sql_extra = item_permissions_sql($channel_id, $observer_hash); $sql_extra .= " order by revision desc "; $r = null; - if($ids) { + if ($ids) { $r = q("select * from item where resource_type = 'nwikipage' and resource_id = '%s' and uid = %d and id in ( $ids ) and item_deleted = 0 $sql_extra", dbesc($resource_id), intval($channel_id) ); - if($r) { + if ($r) { xchan_query($r); - $items = fetch_post_tags($r,true); + $items = fetch_post_tags($r, true); return $items; } } @@ -346,31 +357,30 @@ class NativeWikiPage { 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'] : ''); - $observer_hash = ((array_key_exists('observer_hash',$arr)) ? $arr['observer_hash'] : ''); - $channel_id = ((array_key_exists('channel_id',$arr)) ? $arr['channel_id'] : 0); - $revision = ((array_key_exists('revision',$arr)) ? $arr['revision'] : 0); + $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'] : ''); + $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); + $w = NativeWiki::get_wiki($channel_id, $observer_hash, $resource_id); if (!$w['wiki']) { - return array('message' => t('Error reading wiki'), 'success' => false); + return ['message' => t('Error reading wiki'), 'success' => false]; } - - // fetch the most recently saved revision. + + // fetch the most recently saved revision. $item = self::load_page($arr); - if(! $item) { - return array('message' => t('Page not found'), 'success' => false); + if (!$item) { + return ['message' => t('Page not found'), 'success' => false]; } $mimetype = $item['mimetype']; - // change just the fields we need to change to create a revision; + // change just the fields we need to change to create a revision; unset($item['id']); unset($item['author']); @@ -381,8 +391,8 @@ class NativeWikiPage { $item['edited'] = datetime_convert(); $item['mimetype'] = $mimetype; - if($item['iconfig'] && is_array($item['iconfig']) && count($item['iconfig'])) { - for($x = 0; $x < count($item['iconfig']); $x ++) { + if ($item['iconfig'] && is_array($item['iconfig']) && count($item['iconfig'])) { + for ($x = 0; $x < count($item['iconfig']); $x++) { unset($item['iconfig'][$x]['id']); unset($item['iconfig'][$x]['iid']); } @@ -390,168 +400,164 @@ class NativeWikiPage { $ret = item_store($item, false, false); - if($ret['item_id']) - return array('message' => '', 'item_id' => $ret['item_id'], 'filename' => $pageUrlName, 'success' => true); + if ($ret['item_id']) + return ['message' => '', 'item_id' => $ret['item_id'], 'filename' => $pageUrlName, 'success' => true]; else - return array('message' => t('Page update failed.'), 'success' => false); - } + return ['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); + $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') ]; + $w = NativeWiki::get_wiki($channel_id, $observer_hash, $resource_id); + if (!$w['wiki']) { + return ['success' => false, 'message' => t('Error reading wiki')]; } $ids = []; - $ic = q("select * from iconfig left join item on iconfig.iid = item.id + $ic = q("select * from iconfig left join item on iconfig.iid = item.id where uid = %d and cat = 'nwikipage' and k = 'pagetitle' and v = '%s'", intval($channel_id), dbesc($pageUrlName) ); - if($ic) { - foreach($ic as $c) { + if ($ic) { + foreach ($ic as $c) { $ids[] = intval($c['iid']); } } - if($ids) { + if ($ids) { drop_items($ids, true, DROPITEM_PHASE1); - return [ 'success' => true ]; + return ['success' => true]; } - return [ 'success' => false, 'message' => t('Nothing deleted') ]; + 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); - $observer_hash = ((array_key_exists('observer_hash',$arr)) ? $arr['observer_hash'] : ''); - $channel_id = ((array_key_exists('channel_id',$arr)) ? $arr['channel_id'] : 0); + $resource_id = ((array_key_exists('resource_id', $arr)) ? $arr['resource_id'] : ''); + $commitHash = ((array_key_exists('commitHash', $arr)) ? $arr['commitHash'] : null); + $observer_hash = ((array_key_exists('observer_hash', $arr)) ? $arr['observer_hash'] : ''); + $channel_id = ((array_key_exists('channel_id', $arr)) ? $arr['channel_id'] : 0); - if (! $commitHash) { - return array('message' => 'No commit was provided', 'success' => false); + if (!$commitHash) { + return ['message' => 'No commit was provided', 'success' => false]; } - $w = Zlib\NativeWiki::get_wiki($channel_id, $observer_hash, $resource_id); + $w = NativeWiki::get_wiki($channel_id, $observer_hash, $resource_id); if (!$w['wiki']) { - return array('message' => 'Error reading wiki', 'success' => false); + return ['message' => 'Error reading wiki', 'success' => false]; } $x = $arr; - if(intval($commitHash) > 0) { + if (intval($commitHash) > 0) { unset($x['commitHash']); $x['revision'] = intval($commitHash) - 1; - $loaded = self::load_page($x); + $loaded = self::load_page($x); - if($loaded) { + if ($loaded) { $content = $loaded['body']; - return [ 'content' => $content, 'success' => true ]; + return ['content' => $content, 'success' => true]; } - return [ '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)); - $compareCommit = ((array_key_exists('compareCommit',$arr)) ? $arr['compareCommit'] : 0); - $observer_hash = ((array_key_exists('observer_hash',$arr)) ? $arr['observer_hash'] : ''); - $channel_id = ((array_key_exists('channel_id',$arr)) ? $arr['channel_id'] : 0); + $resource_id = ((array_key_exists('resource_id', $arr)) ? $arr['resource_id'] : ''); + $compareCommit = ((array_key_exists('compareCommit', $arr)) ? $arr['compareCommit'] : 0); + $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); + $w = NativeWiki::get_wiki($channel_id, $observer_hash, $resource_id); if (!$w['wiki']) { - return array('message' => t('Error reading wiki'), 'success' => false); + return ['message' => t('Error reading wiki'), 'success' => false]; } - $x = $arr; + $x = $arr; $x['revision'] = (-1); $currpage = self::load_page($x); - if($currpage) + if ($currpage) $currentContent = $currpage['body']; $x['revision'] = $compareCommit; - $comppage = self::load_page($x); - if($comppage) + $comppage = self::load_page($x); + if ($comppage) $compareContent = $comppage['body']; - if($currpage && $comppage) { + if ($currpage && $comppage) { require_once('library/class.Diff.php'); $diff = \Diff::toTable(\Diff::compare($currentContent, $compareContent)); - return [ 'success' => true, 'diff' => $diff ]; + return ['success' => true, 'diff' => $diff]; } - return [ 'success' => false, 'message' => t('Compare: object not found.') ]; + return ['success' => false, 'message' => t('Compare: object not found.')]; } - + static public function commit($arr) { - $commit_msg = ((array_key_exists('commit_msg', $arr)) ? $arr['commit_msg'] : t('Page updated')); - $observer_hash = ((array_key_exists('observer_hash',$arr)) ? $arr['observer_hash'] : ''); - $channel_id = ((array_key_exists('channel_id',$arr)) ? $arr['channel_id'] : 0); - $pageUrlName = ((array_key_exists('pageUrlName',$arr)) ? $arr['pageUrlName'] : t('Untitled')); + $commit_msg = ((array_key_exists('commit_msg', $arr)) ? $arr['commit_msg'] : t('Page updated')); + $observer_hash = ((array_key_exists('observer_hash', $arr)) ? $arr['observer_hash'] : ''); + $channel_id = ((array_key_exists('channel_id', $arr)) ? $arr['channel_id'] : 0); - if(array_key_exists('resource_id', $arr)) { + if (array_key_exists('resource_id', $arr)) { $resource_id = $arr['resource_id']; } else { - return array('message' => t('Wiki resource_id required for git commit'), 'success' => false); + return ['message' => t('Wiki resource_id required for git commit'), 'success' => false]; } - $w = Zlib\NativeWiki::get_wiki($channel_id, $observer_hash, $resource_id); - if (! $w['wiki']) { - return array('message' => t('Error reading wiki'), 'success' => false); + $w = NativeWiki::get_wiki($channel_id, $observer_hash, $resource_id); + if (!$w['wiki']) { + return ['message' => t('Error reading wiki'), 'success' => false]; } $page = self::load_page($arr); - if($page) { - set_iconfig($page['id'],'nwikipage','commit_msg',escape_tags($commit_msg),true); - return [ 'success' => true, 'item_id' => $page['id'], 'page' => $page ]; + if ($page) { + set_iconfig($page['id'], 'nwikipage', 'commit_msg', escape_tags($commit_msg), true); + return ['success' => true, 'item_id' => $page['id'], 'page' => $page]; } - return [ 'success' => false, 'message' => t('Page not found.') ]; + return ['success' => false, 'message' => t('Page not found.')]; } - + static public function convert_links($s, $wikiURL) { - - if (strpos($s,'[[') !== false) { + + if (strpos($s, '[[') !== false) { preg_match_all("/\[\[(.*?)\]\]/", $s, $match); - $pages = $pageURLs = array(); + $pages = $pageURLs = []; foreach ($match[1] as $m) { // TODO: Why do we need to double urlencode for this to work? //$pageURLs[] = urlencode(urlencode(escape_tags($m))); - $titleUri = explode('|',$m); - $page = $titleUri[0] ?? ''; - $title = $titleUri[1] ?? $page; - $pageURLs[] = Zlib\NativeWiki::name_encode(escape_tags($page)); - $pages[] = $title; + $titleUri = explode('|', $m); + $page = $titleUri[0] ?? ''; + $title = $titleUri[1] ?? $page; + $pageURLs[] = NativeWiki::name_encode(escape_tags($page)); + $pages[] = $title; } $idx = 0; - while(strpos($s,'[[') !== false) { - $replace = '<a href="'.$wikiURL.'/'.$pageURLs[$idx].'">'.$pages[$idx].'</a>'; - $s = preg_replace("/\[\[(.*?)\]\]/", $replace, $s, 1); + while (strpos($s, '[[') !== false) { + $replace = '<a href="' . $wikiURL . '/' . $pageURLs[$idx] . '">' . $pages[$idx] . '</a>'; + $s = preg_replace("/\[\[(.*?)\]\]/", $replace, $s, 1); $idx++; } } @@ -564,21 +570,21 @@ class NativeWikiPage { $resource_id = ((array_key_exists('resource_id', $arr)) ? $arr['resource_id'] : ''); $pageHistory = self::page_history([ - 'channel_id' => \App::$profile_uid, + 'channel_id' => App::$profile_uid, 'observer_hash' => get_observer_hash(), 'resource_id' => $resource_id, 'pageUrlName' => $pageUrlName ]); - return replace_macros(get_markup_template('nwiki_page_history.tpl'), array( + return replace_macros(get_markup_template('nwiki_page_history.tpl'), [ '$pageHistory' => $pageHistory['history'], '$permsWrite' => $arr['permsWrite'], '$name_lbl' => t('Name'), - '$msg_label' => t('Message','wiki_history'), + '$msg_label' => t('Message', 'wiki_history'), '$date_lbl' => t('Date'), '$revert_btn' => t('Revert'), '$compare_btn' => t('Compare') - )); + ]); } @@ -590,14 +596,14 @@ class NativeWikiPage { * @return string */ static public function generate_toc($s) { - if (strpos($s,'[toc]') !== false) { + if (strpos($s, '[toc]') !== false) { //$toc_md = wiki_toc($s); // Generate Markdown-formatted list prior to HTML render $toc_md = '<ul id="wiki-toc"></ul>'; // use the available jQuery plugin http://ndabas.github.io/toc/ - $s = preg_replace("/\[toc\]/", $toc_md, $s, -1); + $s = preg_replace("/\[toc\]/", $toc_md, $s, -1); } return $s; } - + /** * Converts a select set of bbcode tags. Much of the code is copied from include/bbcode.php @@ -605,27 +611,27 @@ class NativeWikiPage { * @return string */ static public function bbcode($s) { - - $s = str_replace(array('[baseurl]', '[sitename]'), array(z_root(), get_config('system', 'sitename')), $s); - - $s = preg_replace_callback("/\[observer\.language\=(.*?)\](.*?)\[\/observer\]/ism",'oblanguage_callback', $s); - $s = preg_replace_callback("/\[observer\.language\!\=(.*?)\](.*?)\[\/observer\]/ism",'oblanguage_necallback', $s); + $s = str_replace(['[baseurl]', '[sitename]'], [z_root(), get_config('system', 'sitename')], $s); + + $s = preg_replace_callback("/\[observer\.language\=(.*?)\](.*?)\[\/observer\]/ism", 'oblanguage_callback', $s); + $s = preg_replace_callback("/\[observer\.language\!\=(.*?)\](.*?)\[\/observer\]/ism", 'oblanguage_necallback', $s); - $observer = \App::get_observer(); + + $observer = App::get_observer(); if ($observer) { - $s1 = '<span class="bb_observer" title="' . t('Different viewers will see this text differently') . '">'; - $s2 = '</span>'; + $s1 = '<span class="bb_observer" title="' . t('Different viewers will see this text differently') . '">'; + $s2 = '</span>'; $obsBaseURL = $observer['xchan_connurl']; $obsBaseURL = preg_replace("/\/poco\/.*$/", '', $obsBaseURL); - $s = str_replace('[observer.baseurl]', $obsBaseURL, $s); - $s = str_replace('[observer.url]', $observer['xchan_url'], $s); - $s = str_replace('[observer.name]', $s1 . $observer['xchan_name'] . $s2, $s); - $s = str_replace('[observer.address]', $s1 . $observer['xchan_addr'] . $s2, $s); - $s = str_replace('[observer.webname]', substr($observer['xchan_addr'], 0, strpos($observer['xchan_addr'], '@')), $s); - $s = str_replace('[observer.photo]', '', $s); - } + $s = str_replace('[observer.baseurl]', $obsBaseURL, $s); + $s = str_replace('[observer.url]', $observer['xchan_url'], $s); + $s = str_replace('[observer.name]', $s1 . $observer['xchan_name'] . $s2, $s); + $s = str_replace('[observer.address]', $s1 . $observer['xchan_addr'] . $s2, $s); + $s = str_replace('[observer.webname]', substr($observer['xchan_addr'], 0, strpos($observer['xchan_addr'], '@')), $s); + $s = str_replace('[observer.photo]', '', $s); + } else { $s = str_replace('[observer.baseurl]', '', $s); $s = str_replace('[observer.url]', '', $s); @@ -637,62 +643,63 @@ class NativeWikiPage { return $s; } - + static public function get_file_ext($arr) { - if($arr['mimetype'] === 'text/bbcode') + if ($arr['mimetype'] === 'text/bbcode') return '.bb'; - elseif($arr['mimetype'] === 'text/markdown') + elseif ($arr['mimetype'] === 'text/markdown') return '.md'; - elseif($arr['mimetype'] === 'text/plain') + elseif ($arr['mimetype'] === 'text/plain') return '.txt'; } - - // This function is derived from + + // This function is derived from // http://stackoverflow.com/questions/32068537/generate-table-of-contents-from-markdown-in-php static public function toc($content) { - // ensure using only "\n" as line-break - $source = str_replace(["\r\n", "\r"], "\n", $content); - - // look for markdown TOC items - preg_match_all( - '/^(?:=|-|#).*$/m', - $source, - $matches, - PREG_PATTERN_ORDER | PREG_OFFSET_CAPTURE - ); - - // preprocess: iterate matched lines to create an array of items - // where each item is an array(level, text) - $file_size = strlen($source); - foreach ($matches[0] as $item) { - $found_mark = substr($item[0], 0, 1); - if ($found_mark == '#') { - // text is the found item - $item_text = $item[0]; - $item_level = strrpos($item_text, '#') + 1; - $item_text = substr($item_text, $item_level); - } else { - // text is the previous line (empty if <hr>) - $item_offset = $item[1]; - $prev_line_offset = strrpos($source, "\n", -($file_size - $item_offset + 2)); - $item_text = - substr($source, $prev_line_offset, $item_offset - $prev_line_offset - 1); - $item_text = trim($item_text); - $item_level = $found_mark == '=' ? 1 : 2; - } - if (!trim($item_text) OR strpos($item_text, '|') !== FALSE) { - // item is an horizontal separator or a table header, don't mind - continue; - } - $raw_toc[] = ['level' => $item_level, 'text' => trim($item_text)]; - } + // ensure using only "\n" as line-break + $source = str_replace(["\r\n", "\r"], "\n", $content); + + // look for markdown TOC items + preg_match_all( + '/^(?:=|-|#).*$/m', + $source, + $matches, + PREG_PATTERN_ORDER | PREG_OFFSET_CAPTURE + ); + + // preprocess: iterate matched lines to create an array of items + // where each item is an array(level, text) + $file_size = strlen($source); + foreach ($matches[0] as $item) { + $found_mark = substr($item[0], 0, 1); + if ($found_mark == '#') { + // text is the found item + $item_text = $item[0]; + $item_level = strrpos($item_text, '#') + 1; + $item_text = substr($item_text, $item_level); + } + else { + // text is the previous line (empty if <hr>) + $item_offset = $item[1]; + $prev_line_offset = strrpos($source, "\n", -($file_size - $item_offset + 2)); + $item_text = + substr($source, $prev_line_offset, $item_offset - $prev_line_offset - 1); + $item_text = trim($item_text); + $item_level = $found_mark == '=' ? 1 : 2; + } + if (!trim($item_text) or strpos($item_text, '|') !== FALSE) { + // item is an horizontal separator or a table header, don't mind + continue; + } + $raw_toc[] = ['level' => $item_level, 'text' => trim($item_text)]; + } $o = ''; - foreach($raw_toc as $t) { + foreach ($raw_toc as $t) { $level = intval($t['level']); - $text = $t['text']; + $text = $t['text']; switch ($level) { case 1: $li = '* '; @@ -712,7 +719,7 @@ class NativeWikiPage { } $o .= $li . $text . "\n"; } - return $o; + return $o; } } diff --git a/Zotlabs/Lib/Queue.php b/Zotlabs/Lib/Queue.php index 35eb1e264..e03816f05 100644 --- a/Zotlabs/Lib/Queue.php +++ b/Zotlabs/Lib/Queue.php @@ -195,7 +195,7 @@ 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; diff --git a/Zotlabs/Module/Album.php b/Zotlabs/Module/Album.php new file mode 100644 index 000000000..f80880184 --- /dev/null +++ b/Zotlabs/Module/Album.php @@ -0,0 +1,103 @@ +<?php + +namespace Zotlabs\Module; + +use App; +use Zotlabs\Web\Controller; +use Zotlabs\Lib\Activity; +use Zotlabs\Lib\ActivityStreams; +use Zotlabs\Lib\Config; +use Zotlabs\Web\HTTPSig; + +require_once('include/security.php'); +require_once('include/attach.php'); +require_once('include/photo/photo_driver.php'); +require_once('include/photos.php'); + + +class Album extends Controller { + + function init() { + + if (ActivityStreams::is_as_request()) { + $sigdata = HTTPSig::verify(EMPTY_STR); + if ($sigdata['portable_id'] && $sigdata['header_valid']) { + $portable_id = $sigdata['portable_id']; + if (!check_channelallowed($portable_id)) { + http_status_exit(403, 'Permission denied'); + } + if (!check_siteallowed($sigdata['signer'])) { + http_status_exit(403, 'Permission denied'); + } + observer_auth($portable_id); + } + elseif (Config::get('system', 'require_authenticated_fetch', false)) { + http_status_exit(403, 'Permission denied'); + } + + $observer_xchan = get_observer_hash(); + $allowed = false; + + $bear = Activity::token_from_request(); + if ($bear) { + logger('bear: ' . $bear, LOGGER_DEBUG); + } + + $channel = null; + + if (argc() > 1) { + $channel = channelx_by_nick(argv(1)); + } + if (!$channel) { + http_status_exit(404, 'Not found.'); + } + + $sql_extra = permissions_sql($channel['channel_id'], $observer_xchan); + + if (argc() > 2) { + $folder = argv(2); + $r = q("select * from attach where is_dir = 1 and hash = '%s' and uid = %d $sql_extra limit 1", + dbesc($folder), + intval($channel['channel_id']) + ); + $allowed = (($r) ? attach_can_view($channel['channel_id'], $observer_xchan, $r[0]['hash'] /*,$bear */) : false); + } + else { + $folder = EMPTY_STR; + $allowed = perm_is_allowed($channel['channel_id'], $observer_xchan, 'view_storage'); + } + + if (!$allowed) { + http_status_exit(403, 'Permission denied.'); + } + + $x = q("select * from attach where folder = '%s' and uid = %d $sql_extra", + dbesc($folder), + intval($channel['channel_id']) + ); + + $contents = []; + + if ($x) { + foreach ($x as $xv) { + if (intval($xv['is_dir'])) { + continue; + } + if (!attach_can_view($channel['channel_id'], $observer_xchan, $xv['hash'] /*,$bear*/)) { + continue; + } + if (intval($xv['is_photo'])) { + $contents[] = z_root() . '/photo/' . $xv['hash']; + } + } + } + + $obj = Activity::encode_simple_collection($contents, App::$query_string, 'OrderedCollection', count($contents)); + as_return_and_die($obj, $channel); + + } + + } + +} + diff --git a/Zotlabs/Module/Appman.php b/Zotlabs/Module/Appman.php index 9d065768e..d287115d4 100644 --- a/Zotlabs/Module/Appman.php +++ b/Zotlabs/Module/Appman.php @@ -2,9 +2,9 @@ namespace Zotlabs\Module; -//require_once('include/apps.php'); - -use \Zotlabs\Lib as Zlib; +use App; +use Zotlabs\Lib\Apps; +use Zotlabs\Lib\Libsync; class Appman extends \Zotlabs\Web\Controller { @@ -33,9 +33,9 @@ class Appman extends \Zotlabs\Web\Controller { 'categories' => escape_tags($_REQUEST['categories']) ); - $_REQUEST['appid'] = Zlib\Apps::app_install(local_channel(),$arr); + $_REQUEST['appid'] = Apps::app_install(local_channel(),$arr); - if(Zlib\Apps::app_installed(local_channel(),$arr)) + if(Apps::app_installed(local_channel(),$arr)) info( t('App installed.') . EOL); goaway(z_root() . '/apps'); @@ -43,7 +43,7 @@ class Appman extends \Zotlabs\Web\Controller { } - $papp = Zlib\Apps::app_decode($_POST['papp']); + $papp = Apps::app_decode($_POST['papp']); if(! is_array($papp)) { notice( t('Malformed app.') . EOL); @@ -51,13 +51,51 @@ class Appman extends \Zotlabs\Web\Controller { } if($_POST['install']) { - Zlib\Apps::app_install(local_channel(),$papp); - if(Zlib\Apps::app_installed(local_channel(),$papp)) + Apps::app_install(local_channel(),$papp); + if(Apps::app_installed(local_channel(),$papp)) info( t('App installed.') . EOL); + + $sync = q("SELECT * FROM app WHERE app_channel = %d AND app_id = '%s' LIMIT 1", + intval(local_channel()), + dbesc($papp['guid']) + ); + + if (!$sync) { + return; + } + + if (intval($sync[0]['app_system'])) { + Libsync::build_sync_packet($uid, ['sysapp' => $sync]); + } + else { + Libsync::build_sync_packet($uid, ['app' => $sync]); + } + } if($_POST['delete']) { - Zlib\Apps::app_destroy(local_channel(),$papp); + + // Fetch the app for sync before it is deleted (if it is deletable)) + $sync = q("SELECT * FROM app WHERE app_channel = %d AND app_id = '%s' LIMIT 1", + intval(local_channel()), + dbesc($papp['guid']) + ); + + if (!$sync) { + return; + } + + Apps::app_destroy(local_channel(), $papp); + + // Now flag it deleted + $sync[0]['app_deleted'] = 1; + + if (intval($sync[0]['app_system'])) { + Libsync::build_sync_packet($uid, ['sysapp' => $sync]); + } + else { + Libsync::build_sync_packet($uid, ['app' => $sync]); + } } if($_POST['edit']) { @@ -65,11 +103,35 @@ class Appman extends \Zotlabs\Web\Controller { } if($_POST['feature']) { - Zlib\Apps::app_feature(local_channel(), $papp, $_POST['feature']); + Apps::app_feature(local_channel(), $papp, $_POST['feature']); + + $sync = q("SELECT * FROM app WHERE app_channel = %d AND app_id = '%s' LIMIT 1", + intval(local_channel()), + dbesc($papp['guid']) + ); + + if (intval($sync[0]['app_system'])) { + Libsync::build_sync_packet($uid, ['sysapp' => $sync]); + } + else { + Libsync::build_sync_packet($uid, ['app' => $sync]); + } } if($_POST['pin']) { - Zlib\Apps::app_feature(local_channel(), $papp, $_POST['pin']); + Apps::app_feature(local_channel(), $papp, $_POST['pin']); + + $sync = q("SELECT * FROM app WHERE app_channel = %d AND app_id = '%s' LIMIT 1", + intval(local_channel()), + dbesc($papp['guid']) + ); + + if (intval($sync[0]['app_system'])) { + Libsync::build_sync_packet($uid, ['sysapp' => $sync]); + } + else { + Libsync::build_sync_packet($uid, ['app' => $sync]); + } } if($_POST['aj']) { @@ -92,14 +154,14 @@ class Appman extends \Zotlabs\Web\Controller { return; } - $channel = \App::get_channel(); + $channel = App::get_channel(); if(argc() > 3) { if(argv(2) === 'moveup') { - Zlib\Apps::moveup(local_channel(),argv(1),argv(3)); + Apps::moveup(local_channel(),argv(1),argv(3)); } if(argv(2) === 'movedown') { - Zlib\Apps::movedown(local_channel(),argv(1),argv(3)); + Apps::movedown(local_channel(),argv(1),argv(3)); } goaway(z_root() . '/apporder'); } @@ -133,7 +195,7 @@ class Appman extends \Zotlabs\Web\Controller { } } - $embed = array('embed', t('Embed code'), Zlib\Apps::app_encode($app,true),'', 'onclick="this.select();"'); + $embed = array('embed', t('Embed code'), Apps::app_encode($app,true),'', 'onclick="this.select();"'); } diff --git a/Zotlabs/Module/Apschema.php b/Zotlabs/Module/Apschema.php index 6b0325d44..eab82eb29 100644 --- a/Zotlabs/Module/Apschema.php +++ b/Zotlabs/Module/Apschema.php @@ -50,7 +50,7 @@ class Apschema extends \Zotlabs\Web\Controller { 'guid' => 'diaspora:guid', 'Hashtag' => 'as:Hashtag' - + ] ]; diff --git a/Zotlabs/Module/Channel.php b/Zotlabs/Module/Channel.php index f726426ad..6261a2f06 100644 --- a/Zotlabs/Module/Channel.php +++ b/Zotlabs/Module/Channel.php @@ -53,14 +53,7 @@ class Channel extends Controller { $profile = argv(1); } - - // Do not use channelx_by_nick() here since it will dismiss deleted channels. - // We need to provide zotinfo for deleted channels so that directories can pick up the info. - $r = q("SELECT * FROM channel left join xchan on channel_hash = xchan_hash WHERE channel_address = '%s' LIMIT 1", - dbesc($which) - ); - - $channel = $r[0]; + $channel = channelx_by_nick($which, true); if (!$channel) { http_status_exit(404, 'Not found'); @@ -100,7 +93,7 @@ class Channel extends Controller { } if ($channel['channel_removed']) { - http_status_exit(404, 'Not found'); + http_status_exit(410, 'Gone'); } if (ActivityStreams::is_as_request($channel)) { diff --git a/Zotlabs/Module/File_upload.php b/Zotlabs/Module/File_upload.php index e18067e20..d4c9ad59a 100644 --- a/Zotlabs/Module/File_upload.php +++ b/Zotlabs/Module/File_upload.php @@ -99,6 +99,9 @@ class File_upload extends \Zotlabs\Web\Controller { } } + if(is_ajax()) + killme(); + goaway(z_root() . '/' . $_REQUEST['return_url']); } diff --git a/Zotlabs/Module/Hq.php b/Zotlabs/Module/Hq.php index 929f2b758..3b8e88488 100644 --- a/Zotlabs/Module/Hq.php +++ b/Zotlabs/Module/Hq.php @@ -254,7 +254,6 @@ class Hq extends \Zotlabs\Web\Controller { return; $options['offset'] = $_REQUEST['offset']; - $options['dm'] = $_REQUEST['dm']; $options['type'] = $_REQUEST['type']; $ret = Messages::get_messages_page($options); diff --git a/Zotlabs/Module/Impel.php b/Zotlabs/Module/Impel.php index e05027d9f..869de2669 100644 --- a/Zotlabs/Module/Impel.php +++ b/Zotlabs/Module/Impel.php @@ -1,5 +1,9 @@ <?php -namespace Zotlabs\Module; /** @file */ +namespace Zotlabs\Module; + +use URLify; + +/** @file */ // import page design element @@ -9,33 +13,33 @@ require_once('include/menu.php'); class Impel extends \Zotlabs\Web\Controller { function init() { - + $ret = array('success' => false); - + if(! local_channel()) json_return_and_die($ret); - + logger('impel: ' . print_r($_REQUEST,true), LOGGER_DATA); - + $elm = $_REQUEST['element']; $x = base64url_decode($elm); if(! $x) json_return_and_die($ret); - + $j = json_decode($x,true); if(! $j) json_return_and_die($ret); - + // logger('element: ' . print_r($j,true)); $channel = \App::get_channel(); - + $arr = array(); $is_menu = false; - + // a portable menu has its links rewritten with the local baseurl $portable_menu = false; - + switch($j['type']) { case 'webpage': $arr['item_type'] = ITEM_TYPE_WEBPAGE; @@ -58,12 +62,12 @@ class Impel extends \Zotlabs\Web\Controller { case 'menu': $is_menu = true; $installed_type = t('menu'); - break; + break; default: logger('mod_impel: unrecognised element type' . print_r($j,true)); break; } - + if($is_menu) { $m = array(); $m['menu_channel_id'] = local_channel(); @@ -73,23 +77,23 @@ class Impel extends \Zotlabs\Web\Controller { $m['menu_created'] = datetime_convert($j['created']); if($j['edited']) $m['menu_edited'] = datetime_convert($j['edited']); - + $m['menu_flags'] = 0; if($j['flags']) { if(in_array('bookmark',$j['flags'])) $m['menu_flags'] |= MENU_BOOKMARK; if(in_array('system',$j['flags'])) $m['menu_flags'] |= MENU_SYSTEM; - + } - + $menu_id = menu_create($m); - + if($menu_id) { if(is_array($j['items'])) { foreach($j['items'] as $it) { $mitem = array(); - + $mitem['mitem_link'] = str_replace('[channelurl]',z_root() . '/channel/' . $channel['channel_address'],$it['link']); $mitem['mitem_link'] = str_replace('[pageurl]',z_root() . '/page/' . $channel['channel_address'],$it['link']); $mitem['mitem_link'] = str_replace('[cloudurl]',z_root() . '/cloud/' . $channel['channel_address'],$it['link']); @@ -115,7 +119,7 @@ class Impel extends \Zotlabs\Web\Controller { intval(local_channel()) ); } - } + } $ret['success'] = true; } $x = $ret; @@ -132,22 +136,21 @@ class Impel extends \Zotlabs\Web\Controller { $arr['owner_xchan'] = get_observer_hash(); $arr['author_xchan'] = (($j['author_xchan']) ? $j['author_xchan'] : get_observer_hash()); $arr['mimetype'] = (($j['mimetype']) ? $j['mimetype'] : 'text/bbcode'); - + if(! $j['mid']) { $j['uuid'] = item_message_id(); $j['mid'] = z_root() . '/item/' . $j['uuid']; } $arr['uuid'] = $j['uuid']; $arr['mid'] = $arr['parent_mid'] = $j['mid']; - - + + if($j['pagetitle']) { - require_once('library/urlify/URLify.php'); - $pagetitle = strtolower(\URLify::transliterate($j['pagetitle'])); + $pagetitle = strtolower(URLify::transliterate($j['pagetitle'])); } - + // Verify ability to use html or php!!! - + $execflag = ((intval($channel['channel_id']) == intval(local_channel()) && ($channel['channel_pageflags'] & PAGE_ALLOWCODE)) ? true : false); $i = q("select id, edited, item_deleted from item where mid = '%s' and uid = %d limit 1", @@ -156,7 +159,7 @@ class Impel extends \Zotlabs\Web\Controller { ); \Zotlabs\Lib\IConfig::Set($arr,'system',$namespace,(($pagetitle) ? $pagetitle : substr($arr['mid'],0,16)),true); - + if($i) { $arr['id'] = $i[0]['id']; // don't update if it has the same timestamp as the original @@ -174,24 +177,24 @@ class Impel extends \Zotlabs\Web\Controller { else $x = item_store($arr,$execflag); } - + if($x && $x['success']) { $item_id = $x['item_id']; } } - + if($x['success']) { $ret['success'] = true; - info( sprintf( t('%s element installed'), $installed_type)); + info( sprintf( t('%s element installed'), $installed_type)); } else { - notice( sprintf( t('%s element installation failed'), $installed_type)); + notice( sprintf( t('%s element installation failed'), $installed_type)); } - - //??? should perhaps return ret? + + //??? should perhaps return ret? json_return_and_die(true); - + } - + } diff --git a/Zotlabs/Module/Import.php b/Zotlabs/Module/Import.php index 77a9ec844..ec47e370b 100644 --- a/Zotlabs/Module/Import.php +++ b/Zotlabs/Module/Import.php @@ -5,10 +5,12 @@ namespace Zotlabs\Module; require_once('include/channel.php'); require_once('include/import.php'); require_once('include/perm_upgrade.php'); -require_once('library/urlify/URLify.php'); -use Zotlabs\Lib\Crypto; +use App; +use URLify; +use Zotlabs\Daemon\Master; use Zotlabs\Lib\Libzot; +use Zotlabs\Web\Controller; /** @@ -17,7 +19,7 @@ use Zotlabs\Lib\Libzot; * Import a channel, either by direct file upload or via * connection to another server. */ -class Import extends \Zotlabs\Web\Controller { +class Import extends Controller { /** * @brief Import channel into account. @@ -26,95 +28,94 @@ class Import extends \Zotlabs\Web\Controller { */ function import_account($account_id) { - if(! $account_id){ + if (!$account_id) { logger('No account ID supplied'); return; } - $max_friends = account_service_class_fetch($account_id,'total_channels'); - $max_feeds = account_service_class_fetch($account_id,'total_feeds'); - $data = null; - $seize = ((x($_REQUEST,'make_primary')) ? intval($_REQUEST['make_primary']) : 0); - $import_posts = ((x($_REQUEST,'import_posts')) ? intval($_REQUEST['import_posts']) : 0); - $moving = intval($_REQUEST['moving']); - $src = $_FILES['filename']['tmp_name']; - $filename = basename($_FILES['filename']['name']); - $filesize = intval($_FILES['filename']['size']); - $filetype = $_FILES['filename']['type']; - $newname = trim(strtolower($_REQUEST['newname'])); + $max_friends = account_service_class_fetch($account_id, 'total_channels'); + $max_feeds = account_service_class_fetch($account_id, 'total_feeds'); + $data = null; + $seize = ((x($_REQUEST, 'make_primary')) ? intval($_REQUEST['make_primary']) : 0); + $import_posts = ((x($_REQUEST, 'import_posts')) ? intval($_REQUEST['import_posts']) : 0); + $moving = false; //intval($_REQUEST['moving']); + $src = $_FILES['filename']['tmp_name']; + $filename = basename($_FILES['filename']['name']); + $filesize = intval($_FILES['filename']['size']); + $filetype = $_FILES['filename']['type']; + $newname = trim(strtolower($_REQUEST['newname'])); // import channel from file - if($src) { + if ($src) { // This is OS specific and could also fail if your tmpdir isn't very // large mostly used for Diaspora which exports gzipped files. - if(strpos($filename,'.gz')){ - @rename($src,$src . '.gz'); + if (strpos($filename, '.gz')) { + @rename($src, $src . '.gz'); @system('gunzip ' . escapeshellarg($src . '.gz')); } - if($filesize) { + if ($filesize) { $data = @file_get_contents($src); } unlink($src); } // import channel from another server - if(! $src) { - $old_address = ((x($_REQUEST,'old_address')) ? $_REQUEST['old_address'] : ''); - if(! $old_address) { + if (!$src) { + $old_address = ((x($_REQUEST, 'old_address')) ? $_REQUEST['old_address'] : ''); + if (!$old_address) { logger('Nothing to import.'); - notice( t('Nothing to import.') . EOL); + notice(t('Nothing to import.') . EOL); return; - } else if(strpos($old_address, 'ï¼ ')) { + } + else if (strpos($old_address, 'ï¼ ')) { // if you copy the identity address from your profile page, make it work for convenience - WARNING: this is a utf-8 variant and NOT an ASCII ampersand. Please do not edit. $old_address = str_replace('ï¼ ', '@', $old_address); } - $email = ((x($_REQUEST,'email')) ? $_REQUEST['email'] : ''); - $password = ((x($_REQUEST,'password')) ? $_REQUEST['password'] : ''); + $email = ((x($_REQUEST, 'email')) ? $_REQUEST['email'] : ''); + $password = ((x($_REQUEST, 'password')) ? $_REQUEST['password'] : ''); - $channelname = substr($old_address,0,strpos($old_address,'@')); - $servername = substr($old_address,strpos($old_address,'@')+1); + $channelname = substr($old_address, 0, strpos($old_address, '@')); + $servername = substr($old_address, strpos($old_address, '@') + 1); $api_path = probe_api_path($servername); - if(! $api_path) { - notice( t('Unable to download data from old server') . EOL); + if (!$api_path) { + notice(t('Unable to download data from old server') . EOL); return; } $api_path .= 'channel/export/basic?f=&channel=' . $channelname; - if($import_posts) - $api_path .= '&posts=1'; - $binary = false; + $binary = false; $redirects = 0; - $opts = array('http_auth' => $email . ':' . $password); - $ret = z_fetch_url($api_path, $binary, $redirects, $opts); - if($ret['success']) { + $opts = ['http_auth' => $email . ':' . $password]; + $ret = z_fetch_url($api_path, $binary, $redirects, $opts); + if ($ret['success']) { $data = $ret['body']; } else { - notice( t('Unable to download data from old server') . EOL); + notice(t('Unable to download data from old server') . EOL); return; } } - if(! $data) { + if (!$data) { logger('Empty import file.'); - notice( t('Imported file is empty.') . EOL); + notice(t('Imported file is empty.') . EOL); return; } - $data = json_decode($data,true); + $data = json_decode($data, true); //logger('import: data: ' . print_r($data,true)); //print_r($data); - if(! array_key_exists('compatibility',$data)) { - call_hooks('import_foreign_channel_data',$data); - if($data['handled']) + if (!array_key_exists('compatibility', $data)) { + call_hooks('import_foreign_channel_data', $data); + if ($data['handled']) return; } @@ -132,47 +133,47 @@ class Import extends \Zotlabs\Web\Controller { // prevent incompatible osada or zap data from horking your database - if(array_path_exists('compatibility/codebase',$data)) { + if (array_path_exists('compatibility/codebase', $data)) { notice('Data export format is not compatible with this software'); return; } - if(version_compare($data['compatibility']['version'], '4.7.3', '<=')) { + if (version_compare($data['compatibility']['version'], '4.7.3', '<=')) { // zot6 transition: cloning is not compatible with older versions notice('Data export format is not compatible with this software (not a zot6 channel)'); return; } - if($moving) + if ($moving) $seize = 1; // import channel - $relocate = ((array_key_exists('relocate',$data)) ? $data['relocate'] : null); + $relocate = ((array_key_exists('relocate', $data)) ? $data['relocate'] : null); - if(array_key_exists('channel',$data)) { + if (array_key_exists('channel', $data)) { - $max_identities = account_service_class_fetch($account_id,'total_identities'); + $max_identities = account_service_class_fetch($account_id, 'total_identities'); - if($max_identities !== false) { - $r = q("select channel_id from channel where channel_account_id = %d", + if ($max_identities !== false) { + $r = q("select channel_id from channel where channel_account_id = %d and channel_removed = 0", intval($account_id) ); - if($r && count($r) > $max_identities) { - notice( sprintf( t('Your service plan only allows %d channels.'), $max_identities) . EOL); + if ($r && count($r) > $max_identities) { + notice(sprintf(t('Your service plan only allows %d channels.'), $max_identities) . EOL); return; } } - if($newname) { - $x = false; + if ($newname) { + $x = false; - if(get_config('system','unicode_usernames')) { - $x = punify(mb_strtolower($newname)); - } + if (get_config('system', 'unicode_usernames')) { + $x = punify(mb_strtolower($newname)); + } - if((! $x) || strlen($x) > 64) { - $x = strtolower(\URLify::transliterate($newname)); + if ((!$x) || strlen($x) > 64) { + $x = strtolower(URLify::transliterate($newname)); } $newname = $x; } @@ -181,36 +182,36 @@ class Import extends \Zotlabs\Web\Controller { } else { $moving = false; - $channel = \App::get_channel(); + $channel = App::get_channel(); } - if(! $channel) { - logger('Channel not found. ', print_r($channel,true)); - notice( t('No channel. Import failed.') . EOL); + if (!$channel) { + logger('Channel not found. ', print_r($channel, true)); + notice(t('No channel. Import failed.') . EOL); return; } - if(is_array($data['config'])) { - import_config($channel,$data['config']); + if (is_array($data['config'])) { + import_config($channel, $data['config']); } logger('import step 2'); - if(array_key_exists('channel',$data)) { - if($data['photo']) { + if (array_key_exists('channel', $data)) { + if ($data['photo']) { require_once('include/photo/photo_driver.php'); - import_channel_photo(base64url_decode($data['photo']['data']),$data['photo']['type'],$account_id,$channel['channel_id']); + import_channel_photo(base64url_decode($data['photo']['data']), $data['photo']['type'], $account_id, $channel['channel_id']); } - if(is_array($data['profile'])) - import_profiles($channel,$data['profile']); + if (is_array($data['profile'])) + import_profiles($channel, $data['profile']); } logger('import step 3'); // create new hubloc for the new channel at this site - if(array_key_exists('channel',$data)) { + if (array_key_exists('channel', $data)) { // create a new zot6 hubloc @@ -223,18 +224,18 @@ class Import extends \Zotlabs\Web\Controller { 'hubloc_network' => 'zot6', 'hubloc_primary' => (($seize) ? 1 : 0), 'hubloc_url' => z_root(), - 'hubloc_url_sig' => 'sha256.' . base64url_encode(Crypto::sign(z_root(),$channel['channel_prvkey'])), - 'hubloc_host' => \App::get_hostname(), + 'hubloc_url_sig' => Libzot::sign(z_root(), $channel['channel_prvkey']), + 'hubloc_host' => App::get_hostname(), 'hubloc_callback' => z_root() . '/zot', - 'hubloc_sitekey' => get_config('system','pubkey'), + 'hubloc_sitekey' => get_config('system', 'pubkey'), 'hubloc_updated' => datetime_convert(), 'hubloc_id_url' => channel_url($channel), - 'hubloc_site_id' => Libzot::make_xchan_hash(z_root(),get_config('system','pubkey')) + 'hubloc_site_id' => Libzot::make_xchan_hash(z_root(), get_config('system', 'pubkey')) ] ); // reset the original primary hubloc if it is being seized - if($seize) { + if ($seize) { $r = q("update hubloc set hubloc_primary = 0 where hubloc_primary = 1 and hubloc_hash = '%s' and hubloc_url != '%s' ", dbesc($channel['channel_hash']), dbesc(z_root()) @@ -245,10 +246,9 @@ class Import extends \Zotlabs\Web\Controller { logger('import step 4'); - // import xchans and contact photos - if(array_key_exists('channel',$data) && $seize) { + if (array_key_exists('channel', $data) && $seize) { // replace any existing xchan we may have on this site if we're seizing control @@ -258,21 +258,21 @@ class Import extends \Zotlabs\Web\Controller { $r = xchan_store_lowlevel( [ - 'xchan_hash' => $channel['channel_hash'], - 'xchan_guid' => $channel['channel_guid'], - 'xchan_guid_sig' => $channel['channel_guid_sig'], - 'xchan_pubkey' => $channel['channel_pubkey'], - 'xchan_photo_l' => z_root() . "/photo/profile/l/" . $channel['channel_id'], - 'xchan_photo_m' => z_root() . "/photo/profile/m/" . $channel['channel_id'], - 'xchan_photo_s' => z_root() . "/photo/profile/s/" . $channel['channel_id'], - 'xchan_addr' => channel_reddress($channel), - 'xchan_url' => z_root() . '/channel/' . $channel['channel_address'], - 'xchan_connurl' => z_root() . '/poco/' . $channel['channel_address'], - 'xchan_follow' => z_root() . '/follow?f=&url=%s', - 'xchan_name' => $channel['channel_name'], - 'xchan_network' => 'zot6', - 'xchan_photo_date' => datetime_convert(), - 'xchan_name_date' => datetime_convert() + 'xchan_hash' => $channel['channel_hash'], + 'xchan_guid' => $channel['channel_guid'], + 'xchan_guid_sig' => $channel['channel_guid_sig'], + 'xchan_pubkey' => $channel['channel_pubkey'], + 'xchan_photo_l' => z_root() . "/photo/profile/l/" . $channel['channel_id'], + 'xchan_photo_m' => z_root() . "/photo/profile/m/" . $channel['channel_id'], + 'xchan_photo_s' => z_root() . "/photo/profile/s/" . $channel['channel_id'], + 'xchan_addr' => channel_reddress($channel), + 'xchan_url' => z_root() . '/channel/' . $channel['channel_address'], + 'xchan_connurl' => z_root() . '/poco/' . $channel['channel_address'], + 'xchan_follow' => z_root() . '/follow?f=&url=%s', + 'xchan_name' => $channel['channel_name'], + 'xchan_network' => 'zot6', + 'xchan_photo_date' => datetime_convert(), + 'xchan_name_date' => datetime_convert() ] ); @@ -282,18 +282,18 @@ class Import extends \Zotlabs\Web\Controller { // import xchans $xchans = $data['xchan']; - if($xchans) { - foreach($xchans as $xchan) { + if ($xchans) { + foreach ($xchans as $xchan) { - if($xchan['xchan_network'] === 'zot6') { - $zhash = Libzot::make_xchan_hash($xchan['xchan_guid'],$xchan['xchan_pubkey']); - if($zhash !== $xchan['xchan_hash']) { - logger('forged xchan: ' . print_r($xchan,true)); + if ($xchan['xchan_network'] === 'zot6') { + $zhash = Libzot::make_xchan_hash($xchan['xchan_guid'], $xchan['xchan_pubkey']); + if ($zhash !== $xchan['xchan_hash']) { + logger('forged xchan: ' . print_r($xchan, true)); continue; } } - if(! array_key_exists('xchan_hidden',$xchan)) { + if (!array_key_exists('xchan_hidden', $xchan)) { $xchan['xchan_hidden'] = (($xchan['xchan_flags'] & 0x0001) ? 1 : 0); $xchan['xchan_orphan'] = (($xchan['xchan_flags'] & 0x0002) ? 1 : 0); $xchan['xchan_censored'] = (($xchan['xchan_flags'] & 0x0004) ? 1 : 0); @@ -306,14 +306,14 @@ class Import extends \Zotlabs\Web\Controller { $r = q("select xchan_hash from xchan where xchan_hash = '%s' limit 1", dbesc($xchan['xchan_hash']) ); - if($r) + if ($r) continue; - create_table_from_array('xchan',$xchan); + create_table_from_array('xchan', $xchan); require_once('include/photo/photo_driver.php'); - if($xchan['xchan_hash'] === $channel['channel_hash']) { + if ($xchan['xchan_hash'] === $channel['channel_hash']) { $r = q("update xchan set xchan_photo_l = '%s', xchan_photo_m = '%s', xchan_photo_s = '%s' where xchan_hash = '%s'", dbesc(z_root() . '/photo/profile/l/' . $channel['channel_id']), dbesc(z_root() . '/photo/profile/m/' . $channel['channel_id']), @@ -322,13 +322,13 @@ class Import extends \Zotlabs\Web\Controller { ); } else { - $photos = import_xchan_photo($xchan['xchan_photo_l'],$xchan['xchan_hash']); - if($photos[4]) + $photos = import_xchan_photo($xchan['xchan_photo_l'], $xchan['xchan_hash']); + if ($photos[4]) $photodate = NULL_DATE; else $photodate = $xchan['xchan_photo_date']; - $r = q("update xchan set xchan_photo_l = '%s', xchan_photo_m = '%s', xchan_photo_s = '%s', xchan_photo_mimetype = '%s', xchan_photo_date = '%s' where xchan_hash = '%s'", + q("update xchan set xchan_photo_l = '%s', xchan_photo_m = '%s', xchan_photo_s = '%s', xchan_photo_mimetype = '%s', xchan_photo_date = '%s' where xchan_hash = '%s'", dbesc($photos[0]), dbesc($photos[1]), dbesc($photos[2]), @@ -345,22 +345,22 @@ class Import extends \Zotlabs\Web\Controller { logger('import step 7'); // this must happen after xchans got imported! - if(is_array($data['hubloc'])) { - import_hublocs($channel,$data['hubloc'],$seize,$moving); + if (is_array($data['hubloc'])) { + import_hublocs($channel, $data['hubloc'], $seize, $moving); } $friends = 0; - $feeds = 0; + $feeds = 0; // import contacts $abooks = $data['abook']; - if($abooks) { - foreach($abooks as $abook) { + if ($abooks) { + foreach ($abooks as $abook) { $abook_copy = $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']; unset($abook['abook_id']); @@ -373,33 +373,33 @@ class Import extends \Zotlabs\Web\Controller { $abook['abook_account'] = $account_id; $abook['abook_channel'] = $channel['channel_id']; - if(! array_key_exists('abook_blocked',$abook)) { - $abook['abook_blocked'] = (($abook['abook_flags'] & 0x0001 ) ? 1 : 0); - $abook['abook_ignored'] = (($abook['abook_flags'] & 0x0002 ) ? 1 : 0); - $abook['abook_hidden'] = (($abook['abook_flags'] & 0x0004 ) ? 1 : 0); - $abook['abook_archived'] = (($abook['abook_flags'] & 0x0008 ) ? 1 : 0); - $abook['abook_pending'] = (($abook['abook_flags'] & 0x0010 ) ? 1 : 0); - $abook['abook_unconnected'] = (($abook['abook_flags'] & 0x0020 ) ? 1 : 0); - $abook['abook_self'] = (($abook['abook_flags'] & 0x0080 ) ? 1 : 0); - $abook['abook_feed'] = (($abook['abook_flags'] & 0x0100 ) ? 1 : 0); + if (!array_key_exists('abook_blocked', $abook)) { + $abook['abook_blocked'] = (($abook['abook_flags'] & 0x0001) ? 1 : 0); + $abook['abook_ignored'] = (($abook['abook_flags'] & 0x0002) ? 1 : 0); + $abook['abook_hidden'] = (($abook['abook_flags'] & 0x0004) ? 1 : 0); + $abook['abook_archived'] = (($abook['abook_flags'] & 0x0008) ? 1 : 0); + $abook['abook_pending'] = (($abook['abook_flags'] & 0x0010) ? 1 : 0); + $abook['abook_unconnected'] = (($abook['abook_flags'] & 0x0020) ? 1 : 0); + $abook['abook_self'] = (($abook['abook_flags'] & 0x0080) ? 1 : 0); + $abook['abook_feed'] = (($abook['abook_flags'] & 0x0100) ? 1 : 0); } - if(array_key_exists('abook_instance',$abook) && $abook['abook_instance'] && strpos($abook['abook_instance'],z_root()) === false) { + if (array_key_exists('abook_instance', $abook) && $abook['abook_instance'] && strpos($abook['abook_instance'], z_root()) === false) { $abook['abook_not_here'] = 1; } - if($abook['abook_self']) { - $role = get_pconfig($channel['channel_id'],'system','permissions_role'); - if(($role === 'forum') || ($abook['abook_my_perms'] & PERMS_W_TAGWALL)) { + if ($abook['abook_self']) { + $role = get_pconfig($channel['channel_id'], 'system', 'permissions_role'); + if (($role === 'forum') || ($abook['abook_my_perms'] & PERMS_W_TAGWALL)) { q("update xchan set xchan_pubforum = 1 where xchan_hash = '%s' ", dbesc($abook['abook_xchan']) ); } } else { - if($max_friends !== false && $friends > $max_friends) + if ($max_friends !== false && $friends > $max_friends) continue; - if($max_feeds !== false && intval($abook['abook_feed']) && ($feeds > $max_feeds)) + if ($max_feeds !== false && intval($abook['abook_feed']) && ($feeds > $max_feeds)) continue; } @@ -407,9 +407,9 @@ class Import extends \Zotlabs\Web\Controller { dbesc($abook['abook_xchan']), intval($channel['channel_id']) ); - if($r) { - foreach($abook as $k => $v) { - $r = q("UPDATE abook SET " . TQUOT . "%s" . TQUOT . " = '%s' WHERE abook_xchan = '%s' AND abook_channel = %d", + if ($r) { + foreach ($abook as $k => $v) { + q("UPDATE abook SET " . TQUOT . "%s" . TQUOT . " = '%s' WHERE abook_xchan = '%s' AND abook_channel = %d", dbesc($k), dbesc($v), dbesc($abook['abook_xchan']), @@ -420,17 +420,17 @@ class Import extends \Zotlabs\Web\Controller { else { abook_store_lowlevel($abook); - $friends ++; - if(intval($abook['abook_feed'])) - $feeds ++; + $friends++; + if (intval($abook['abook_feed'])) + $feeds++; } - translate_abook_perms_inbound($channel,$abook_copy); + translate_abook_perms_inbound($channel, $abook_copy); - 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']); } } } @@ -438,13 +438,14 @@ class Import extends \Zotlabs\Web\Controller { logger('import step 8'); } + // import groups $groups = $data['group']; - if($groups) { - $saved = array(); - foreach($groups as $group) { - $saved[$group['hash']] = array('old' => $group['id']); - if(array_key_exists('name', $group)) { + if ($groups) { + $saved = []; + foreach ($groups as $group) { + $saved[$group['hash']] = ['old' => $group['id']]; + if (array_key_exists('name', $group)) { $group['gname'] = $group['name']; unset($group['name']); } @@ -456,8 +457,8 @@ class Import extends \Zotlabs\Web\Controller { $r = q("select * from pgrp where uid = %d", intval($channel['channel_id']) ); - if($r) { - foreach($r as $rr) { + if ($r) { + foreach ($r as $rr) { $saved[$rr['hash']]['new'] = $rr['id']; } } @@ -465,12 +466,12 @@ class Import extends \Zotlabs\Web\Controller { // import group members $group_members = $data['group_member']; - if($group_members) { - foreach($group_members as $group_member) { + if ($group_members) { + foreach ($group_members as $group_member) { unset($group_member['id']); $group_member['uid'] = $channel['channel_id']; - foreach($saved as $x) { - if($x['old'] == $group_member['gid']) + foreach ($saved as $x) { + if ($x['old'] == $group_member['gid']) $group_member['gid'] = $x['new']; } create_table_from_array('pgrp_member', $group_member); @@ -479,59 +480,85 @@ class Import extends \Zotlabs\Web\Controller { logger('import step 9'); - if(is_array($data['obj'])) - import_objs($channel,$data['obj']); - - if(is_array($data['likes'])) - import_likes($channel,$data['likes']); - if(is_array($data['app'])) - import_apps($channel,$data['app']); + if (is_array($data['obj'])) + import_objs($channel, $data['obj']); - if(is_array($data['sysapp'])) - import_sysapps($channel,$data['sysapp']); + if (is_array($data['likes'])) + import_likes($channel, $data['likes']); - if(is_array($data['chatroom'])) - import_chatrooms($channel,$data['chatroom']); + if (is_array($data['app'])) + import_apps($channel, $data['app']); - if(is_array($data['event'])) - import_events($channel,$data['event']); + if (is_array($data['sysapp'])) + import_sysapps($channel, $data['sysapp']); - if(is_array($data['event_item'])) - import_items($channel,$data['event_item'],false,$relocate); + if (is_array($data['chatroom'])) + import_chatrooms($channel, $data['chatroom']); - if(is_array($data['menu'])) - import_menus($channel,$data['menu']); + if (is_array($data['event'])) + import_events($channel, $data['event']); - if(is_array($data['wiki'])) - import_items($channel,$data['wiki'],false,$relocate); + if (is_array($data['event_item'])) + import_items($channel, $data['event_item'], false, $relocate); - if(is_array($data['webpages'])) - import_items($channel,$data['webpages'],false,$relocate); + if (is_array($data['menu'])) + import_menus($channel, $data['menu']); - $addon = array('channel' => $channel,'data' => $data); - call_hooks('import_channel',$addon); + if (is_array($data['wiki'])) + import_items($channel, $data['wiki'], false, $relocate); - $saved_notification_flags = notifications_off($channel['channel_id']); + if (is_array($data['webpages'])) + import_items($channel, $data['webpages'], false, $relocate); - if($import_posts && array_key_exists('item',$data) && $data['item']) - import_items($channel,$data['item'],false,$relocate); + $addon = ['channel' => $channel, 'data' => $data]; + call_hooks('import_channel', $addon); - notifications_on($channel['channel_id'],$saved_notification_flags); + if ($import_posts && array_key_exists('item', $data) && $data['item']) { + import_items($channel, $data['item'], false, $relocate); + } - if(array_key_exists('item_id',$data) && $data['item_id']) - import_item_ids($channel,$data['item_id']); + // Immediately notify old server about the new clone + Master::Summon(['Notifier', 'refresh_all', $channel['channel_id']]); // This will indirectly perform a refresh_all *and* update the directory + Master::Summon(['Directory', $channel['channel_id']]); + + $cf_api_compat = true; + + if ($api_path && $import_posts) { // we are importing from a server and not a file + if (version_compare($data['compatibility']['version'], '6.3.4', '>=')) { + + $m = parse_url($api_path); + + $hz_server = $m['scheme'] . '://' . $m['host']; - \Zotlabs\Daemon\Master::Summon(array('Directory', $channel['channel_id'])); + $since = datetime_convert(date_default_timezone_get(), date_default_timezone_get(), '0001-01-01 00:00'); + $until = datetime_convert(date_default_timezone_get(), date_default_timezone_get(), 'now + 1 day'); + $poll_interval = get_config('system', 'poll_interval', 3); + $page = 0; - notice( t('Import completed.') . EOL); + Master::Summon(['Content_importer', sprintf('%d', $page), $since, $until, $channel['channel_address'], urlencode($hz_server)]); + Master::Summon(['File_importer', sprintf('%d', $page), $channel['channel_address'], urlencode($hz_server)]); + } + else { + $cf_api_compat = false; + } + } change_channel($channel['channel_id']); - goaway(z_root() . '/network' ); + if ($api_path && $import_posts && $cf_api_compat) { + goaway(z_root() . '/import_progress'); + } + + if (!$cf_api_compat) { + notice(t('Automatic content and files import was not possible due to API version incompatiblity. Please import content and files manually!') . EOL); + } + + goaway(z_root()); + } /** @@ -539,7 +566,7 @@ class Import extends \Zotlabs\Web\Controller { */ function post() { $account_id = get_account_id(); - if(! $account_id) + if (!$account_id) return; check_form_security_token_redirectOnErr('/import', 'channel_import'); @@ -554,33 +581,35 @@ class Import extends \Zotlabs\Web\Controller { */ function get() { - if(! get_account_id()) { - notice( t('You must be logged in to use this feature.') . EOL); + if (!get_account_id()) { + notice(t('You must be logged in to use this feature.') . EOL); return ''; } - $o = replace_macros(get_markup_template('channel_import.tpl'),array( - '$title' => t('Import Channel'), - '$desc' => t('Use this form to import an existing channel from a different server/hub. You may retrieve the channel identity from the old server/hub via the network or provide an export file.'), + nav_set_selected('Channel Import'); + + $o = replace_macros(get_markup_template('channel_import.tpl'), [ + '$title' => t('Channel Import'), + '$desc' => t('Use this form to import an existing channel from a different server/hub. You may retrieve the channel identity from the old server/hub via the network or provide an export file.'), '$label_filename' => t('File to Upload'), - '$choice' => t('Or provide the old server/hub details'), + '$choice' => t('Or provide the old server/hub details'), - '$old_address' => [ 'old_address', t('Your old identity address (xyz@example.com)'), '', ''], - '$email' => [ 'email', t('Your old login email address'), '', '' ], - '$password' => [ 'password', t('Your old login password'), '', '' ], - '$import_posts' => [ 'import_posts', t('Import a few months of posts if possible (limited by available memory'), false, '', [ t('No'), t('Yes') ]], + '$old_address' => ['old_address', t('Your old identity address (xyz@example.com)'), '', ''], + '$email' => ['email', t('Your old login email address'), '', ''], + '$password' => ['password', t('Your old login password'), '', ''], + '$import_posts' => ['import_posts', t('Import your items and files (limited by available memory)'), false, '', [t('No'), t('Yes')]], '$common' => t('For either option, please choose whether to make this hub your new primary address, or whether your old location should continue this role. You will be able to post from either location, but only one can be marked as the primary location for files, photos, and media.'), - '$make_primary' => [ 'make_primary', t('Make this hub my primary location'), false, '', [ t('No'), t('Yes') ] ], - '$moving' => [ 'moving', t('Move this channel (disable all previous locations)'), false, '', [ t('No'), t('Yes') ] ], - '$newname' => [ 'newname', t('Use this channel nickname instead of the one provided'), '', t('Leave blank to keep your existing channel nickname. You will be randomly assigned a similar nickname if either name is already allocated on this site.')], + '$make_primary' => ['make_primary', t('Make this hub my primary location'), false, '', [t('No'), t('Yes')]], + '$moving' => ['moving', t('Move this channel (disable all previous locations)'), false, '', [t('No'), t('Yes')]], + '$newname' => ['newname', t('Use this channel nickname instead of the one provided'), '', t('Leave blank to keep your existing channel nickname. You will be randomly assigned a similar nickname if either name is already allocated on this site.')], '$pleasewait' => t('This process may take several minutes to complete. Please submit the form only once and leave this page open until finished.'), '$form_security_token' => get_form_security_token('channel_import'), - '$submit' => t('Submit') - )); + '$submit' => t('Submit') + ]); return $o; } diff --git a/Zotlabs/Module/Import_items.php b/Zotlabs/Module/Import_items.php index c2b2506fe..1a1e8d061 100644 --- a/Zotlabs/Module/Import_items.php +++ b/Zotlabs/Module/Import_items.php @@ -1,6 +1,11 @@ <?php + namespace Zotlabs\Module; +use App; +use ZipArchive; +use Zotlabs\Web\Controller; + require_once('include/import.php'); /** @@ -8,128 +13,184 @@ require_once('include/import.php'); * * Import existing posts and content from an export file. */ -class Import_items extends \Zotlabs\Web\Controller { +class Import_items extends Controller { function post() { - if(! local_channel()) + if (!local_channel()) return; check_form_security_token_redirectOnErr('/import_items', 'import_items'); - $data = null; + $data = null; $src = $_FILES['filename']['tmp_name']; $filename = basename($_FILES['filename']['name']); $filesize = intval($_FILES['filename']['size']); $filetype = $_FILES['filename']['type']; - if($src) { + $channel = App::get_channel(); + + if ($src) { + + if ($filetype === 'application/zip') { + $zip = new ZipArchive; + + $r = $zip->open($src); + if ($r === true) { + for ($i = 0; $i < $zip->count(); $i++) { + $data = $zip->getFromIndex($i); + self::import($channel, $data); + } + $zip->close(); + unlink($src); + return; + } + + notice(t('Not a zip file or zip file corrupted.') . EOL); + unlink($src); + return; + } + // This is OS specific and could also fail if your tmpdir isn't very large // mostly used for Diaspora which exports gzipped files. - if(strpos($filename,'.gz')){ - @rename($src,$src . '.gz'); - @system('gunzip ' . escapeshellarg($src . '.gz')); - } + //if(strpos($filename,'.gz')){ + //@rename($src,$src . '.gz'); + //@system('gunzip ' . escapeshellarg($src . '.gz')); + //} - if($filesize) { + if ($filesize) { $data = @file_get_contents($src); + self::import($channel, $data); } unlink($src); + return; } + /* + if(! $src) { + + $old_address = ((x($_REQUEST,'old_address')) ? $_REQUEST['old_address'] : ''); + + if(! $old_address) { + logger('Nothing to import.'); + notice( t('Nothing to import.') . EOL); + return; + } + + $email = ((x($_REQUEST,'email')) ? $_REQUEST['email'] : ''); + $password = ((x($_REQUEST,'password')) ? $_REQUEST['password'] : ''); + + $year = ((x($_REQUEST,'year')) ? $_REQUEST['year'] : ''); + + $channelname = substr($old_address,0,strpos($old_address,'@')); + $servername = substr($old_address,strpos($old_address,'@')+1); + + $scheme = 'https://'; + $api_path = '/api/red/channel/export/items?f=&channel=' . $channelname . '&year=' . intval($year); + $binary = false; + $redirects = 0; + $opts = array('http_auth' => $email . ':' . $password); + $url = $scheme . $servername . $api_path; + $ret = z_fetch_url($url, $binary, $redirects, $opts); + if(! $ret['success']) + $ret = z_fetch_url('http://' . $servername . $api_path, $binary, $redirects, $opts); + if($ret['success']) + $data = $ret['body']; + else + notice( t('Unable to download data from old server') . EOL); + } + */ - if(! $src) { + } - $old_address = ((x($_REQUEST,'old_address')) ? $_REQUEST['old_address'] : ''); - if(! $old_address) { - logger('Nothing to import.'); - notice( t('Nothing to import.') . EOL); - return; - } + /** + * @brief Generate item import page. + * + * @return string with parsed HTML. + */ + function get() { - $email = ((x($_REQUEST,'email')) ? $_REQUEST['email'] : ''); - $password = ((x($_REQUEST,'password')) ? $_REQUEST['password'] : ''); - - $year = ((x($_REQUEST,'year')) ? $_REQUEST['year'] : ''); - - $channelname = substr($old_address,0,strpos($old_address,'@')); - $servername = substr($old_address,strpos($old_address,'@')+1); - - $scheme = 'https://'; - $api_path = '/api/red/channel/export/items?f=&channel=' . $channelname . '&year=' . intval($year); - $binary = false; - $redirects = 0; - $opts = array('http_auth' => $email . ':' . $password); - $url = $scheme . $servername . $api_path; - $ret = z_fetch_url($url, $binary, $redirects, $opts); - if(! $ret['success']) - $ret = z_fetch_url('http://' . $servername . $api_path, $binary, $redirects, $opts); - if($ret['success']) - $data = $ret['body']; - else - notice( t('Unable to download data from old server') . EOL); + if (!local_channel()) { + notice(t('Permission denied') . EOL); + return login(); } - if(! $data) { + $o = replace_macros(get_markup_template('item_import.tpl'), [ + '$title' => t('Import Items'), + '$desc' => t('Use this form to import existing posts and content from an export file.'), + '$label_filename' => t('File to Upload'), + '$form_security_token' => get_form_security_token('import_items'), + '$submit' => t('Submit') + ]); + + return $o; + } + + + public static function import($channel, $data) { + + if (!$data) { logger('Empty file.'); - notice( t('Imported file is empty.') . EOL); + notice(t('Imported file is empty.') . EOL); return; } $data = json_decode($data, true); - //logger('import: data: ' . print_r($data,true)); //print_r($data); - if(! is_array($data)) + if (!is_array($data)) { return; + } - if(array_key_exists('compatibility',$data) && array_key_exists('database',$data['compatibility'])) { - $v1 = substr($data['compatibility']['database'],-4); - $v2 = substr(DB_UPDATE_VERSION,-4); - if($v2 > $v1) { - $t = sprintf( t('Warning: Database versions differ by %1$d updates.'), $v2 - $v1 ); - notice($t . EOL); - } + //if (array_key_exists('compatibility', $data) && array_key_exists('database', $data['compatibility'])) { + //$v1 = substr($data['compatibility']['database'], -4); + //$v2 = substr(DB_UPDATE_VERSION, -4); + //if ($v2 > $v1) { + //$t = sprintf(t('Warning: Database versions differ by %1$d updates.'), $v2 - $v1); + //notice($t . EOL); + //} + //} + + if (array_key_exists('item', $data) && is_array($data['item'])) { + import_items($channel, $data['item'], false, ((array_key_exists('relocate', $data)) ? $data['relocate'] : null)); + info(t('Content import completed') . EOL); } - $channel = \App::get_channel(); + if (array_key_exists('chatroom', $data) && is_array($data['chatroom'])) { + import_chatrooms($channel, $data['chatroom']); + info(t('Chatroom import completed') . EOL); - if(array_key_exists('item',$data) && $data['item']) { - import_items($channel,$data['item'],false,((array_key_exists('relocate',$data)) ? $data['relocate'] : null)); } - if(array_key_exists('item_id',$data) && $data['item_id']) { - import_item_ids($channel,$data['item_id']); - } + if (array_key_exists('event', $data) && is_array($data['event'])) { + import_events($channel, $data['event']); + info(t('Channel calendar import 1/2 completed') . EOL); - info( t('Import completed') . EOL); - } + } + if (array_key_exists('event_item', $data) && is_array($data['event_item'])) { + import_items($channel, $data['event_item'], false, ((array_key_exists('relocate', $data)) ? $data['relocate'] : null)); + info(t('Channel calendar import 2/2 completed') . EOL); + } - /** - * @brief Generate item import page. - * - * @return string with parsed HTML. - */ - function get() { + if (array_key_exists('menu', $data) && is_array($data['menu'])) { + import_menus($channel, $data['menu']); + info(t('Menu import completed') . EOL); + } - if(! local_channel()) { - notice( t('Permission denied') . EOL); - return login(); + if (array_key_exists('wiki', $data) && is_array($data['wiki'])) { + import_items($channel, $data['wiki'], false, ((array_key_exists('relocate', $data)) ? $data['relocate'] : null)); + info(t('Wiki import completed') . EOL); } - $o = replace_macros(get_markup_template('item_import.tpl'), array( - '$title' => t('Import Items'), - '$desc' => t('Use this form to import existing posts and content from an export file.'), - '$label_filename' => t('File to Upload'), - '$form_security_token' => get_form_security_token('import_items'), - '$submit' => t('Submit') - )); + if (array_key_exists('webpages', $data) && is_array($data['webpages'])) { + import_items($channel, $data['webpages'], false, ((array_key_exists('relocate', $data)) ? $data['relocate'] : null)); + info(t('Webpages import completed') . EOL); + } - return $o; } } diff --git a/Zotlabs/Module/Import_progress.php b/Zotlabs/Module/Import_progress.php new file mode 100644 index 000000000..761d2f215 --- /dev/null +++ b/Zotlabs/Module/Import_progress.php @@ -0,0 +1,122 @@ +<?php +namespace Zotlabs\Module; + +use Zotlabs\Lib\PConfig; +use Zotlabs\Daemon\Master; + +class Import_progress extends \Zotlabs\Web\Controller { + + function post() { + + if(! local_channel()) + return; + + } + + function get() { + + if(! local_channel()) { + return; + } + + nav_set_selected('Channel Import'); + + // items + $c = PConfig::Get(local_channel(), 'import', 'content_progress'); + + if ($c) { + $total_cpages = floor(intval($c['items_total']) / intval($c['items_page'])); + if(!$total_cpages) { + $total_cpages = 1; // because of floor + } + + $cpage = $c['last_page'] + 1; // because page count start at 0 + + $cprogress = intval(floor((intval($cpage) * 100) / $total_cpages)); + $ccompleted_str = t('Item sync completed!'); + + if(argv(1) === 'resume_itemsync' && $cprogress < 100) { + Master::Summon($c['next_cmd']); + goaway('/import_progress'); + } + } + else { + $cprogress = 'waiting to start...'; + + if (PConfig::Get(local_channel(), 'import', 'content_completed')) { + // There was nothing todo. Fake 100% and mention that there were no files found + $cprogress = 100; + } + + $ccompleted_str = t('Item sync completed but no items were found!'); + + if(argv(1) === 'resume_itemsync') { + Master::Summon(["Content_importer","0","0001-01-01 00:00:00","2021-10-02 19:49:14","ct5","https%3A%2F%2Fhub.somaton.com"]); + goaway('/import_progress'); + } + } + + $cprogress_str = ((intval($cprogress)) ? $cprogress . '%' : $cprogress); + + // files + $f = PConfig::Get(local_channel(), 'import', 'files_progress'); + + if ($f) { + $total_fpages = floor(intval($f['files_total']) / intval($f['files_page'])); + if(!$total_fpages) { + $total_fpages = 1; + } + + $fpage = $f['last_page'] + 1; + + $fprogress = intval(floor((intval($fpage) * 100) / $total_fpages)); + $fcompleted_str = t('File sync completed!'); + + if(argv(1) === 'resume_filesync' && $fprogress < 100) { + Master::Summon($f['next_cmd']); + goaway('/import_progress'); + } + + + } + else { + $fprogress = 'waiting to start...'; + + if (PConfig::Get(local_channel(), 'import', 'files_completed')) { + // There was nothing todo. Fake 100% and mention that there were no files found + $fprogress = 100; + } + + $fcompleted_str = t('File sync completed but no files were found!'); + } + + $fprogress_str = ((intval($fprogress)) ? $fprogress . '%' : $fprogress); + + if(is_ajax()) { + $ret = [ + 'cprogress' => $cprogress, + 'fprogress' => $fprogress + ]; + + json_return_and_die($ret); + } + + $o = replace_macros(get_markup_template("import_progress.tpl"), [ + '$chtitle_str' => t('Channel clone status'), + '$ctitle_str' => t('Item sync status'), + '$ftitle_str' => t('File sync status'), + '$cprogress_str' => $cprogress_str, + '$cprogress' => intval($cprogress), + '$fprogress_str' => $fprogress_str, + '$fprogress' => intval($fprogress), + '$fcompleted_str' => $fcompleted_str, + '$ccompleted_str' => $ccompleted_str, + '$chcompleted_str' => t('Channel cloning completed!'), + '$resume_str' => t('Resume'), + '$resume_helper_str' => t('Only resume if sync stalled!') + ]); + + return $o; + } + +} diff --git a/Zotlabs/Module/Invite.php b/Zotlabs/Module/Invite.php index a0bd10ff1..2a126ac27 100644 --- a/Zotlabs/Module/Invite.php +++ b/Zotlabs/Module/Invite.php @@ -129,11 +129,11 @@ class Invite extends Controller { if(! $recip) continue; // see if we have an email address who@domain.tld - if (!preg_match('/^.{2,64}\@[a-z0-9.-]{4,32}\.[a-z]{2,12}$/', $recip)) { - $feedbk .= 'ZAI0203E ' . ($n+1) . ': ' . sprintf( t('(%s) : Not a valid email address'), $recip) . $eol; - $ko++; - continue; - } + //if (!preg_match('/^.{2,64}\@[a-z0-9.-]{2,32}\.[a-z]{2,12}$/', $recip)) { + //$feedbk .= 'ZAI0203E ' . ($n+1) . ': ' . sprintf( t('(%s) : Not a valid email address'), $recip) . $eol; + //$ko++; + //continue; + //} if(! validate_email($recip)) { $feedbk .= 'ZAI0204E ' . ($n+1) . ': ' . sprintf( t('(%s) : Not a real email address'), $recip) . $eol; $ko++; @@ -225,7 +225,7 @@ class Invite extends Controller { '$projectname' => t('$Projectname'), '$invite_code' => $invite_code, '$invite_where' => z_root() . '/register', - '$invite_whereami' => str_replace('@', '@+', $reonar['whereami']), + '$invite_whereami' => $reonar['whereami'], '$invite_whoami' => z_root() . '/channel/' . $reonar['whoami'], '$invite_anywhere' => z_root() . '/pubsites' ) diff --git a/Zotlabs/Module/Item.php b/Zotlabs/Module/Item.php index 7099a54e5..9b76c7569 100644 --- a/Zotlabs/Module/Item.php +++ b/Zotlabs/Module/Item.php @@ -2,6 +2,8 @@ namespace Zotlabs\Module; +use App; +use URLify; use Zotlabs\Lib\Config; use Zotlabs\Lib\IConfig; use Zotlabs\Lib\Enotify; @@ -15,7 +17,6 @@ use Zotlabs\Lib\Libzot; use Zotlabs\Lib\Libsync; use Zotlabs\Lib\ThreadListener; use Zotlabs\Access\PermissionRoles; -use App; require_once('include/crypto.php'); require_once('include/items.php'); @@ -37,8 +38,6 @@ require_once('include/conversation.php'); * posting categories go through item_store() instead of this function. * */ - - class Item extends Controller { @@ -46,11 +45,9 @@ class Item extends Controller { if (Libzot::is_zot_request()) { - $conversation = false; - $item_id = argv(1); - if(! $item_id) + if (!$item_id) http_status_exit(404, 'Not found'); $portable_id = EMPTY_STR; @@ -70,8 +67,8 @@ class Item extends Controller { dbesc(z_root() . '/item/' . $item_id) ); - if (! $r) { - http_status_exit(404,'Not found'); + if (!$r) { + http_status_exit(404, 'Not found'); } // process an authenticated fetch @@ -79,10 +76,10 @@ class Item extends Controller { $sigdata = HTTPSig::verify(($_SERVER['REQUEST_METHOD'] === 'POST') ? file_get_contents('php://input') : EMPTY_STR); if ($sigdata['portable_id'] && $sigdata['header_valid']) { $portable_id = $sigdata['portable_id']; - if (! check_channelallowed($portable_id)) { + if (!check_channelallowed($portable_id)) { http_status_exit(403, 'Permission denied'); } - if (! check_siteallowed($sigdata['signer'])) { + if (!check_siteallowed($sigdata['signer'])) { http_status_exit(403, 'Permission denied'); } observer_auth($portable_id); @@ -92,8 +89,8 @@ class Item extends Controller { dbesc($portable_id) ); } - elseif (Config::get('system','require_authenticated_fetch',false)) { - http_status_exit(403,'Permission denied'); + elseif (Config::get('system', 'require_authenticated_fetch', false)) { + http_status_exit(403, 'Permission denied'); } // if we don't have a parent id belonging to the signer see if we can obtain one as a visitor that we have permission to access @@ -101,47 +98,47 @@ class Item extends Controller { $sql_extra = item_permissions_sql(0); - if (! $i) { + if (!$i) { $i = q("select id as item_id from item where mid = '%s' $item_normal $sql_extra order by item_wall desc limit 1", dbesc($r[0]['parent_mid']) ); } - if(! $i) { - http_status_exit(403,'Forbidden'); + if (!$i) { + http_status_exit(403, 'Forbidden'); } - $parents_str = ids_to_querystr($i,'item_id'); + $parents_str = ids_to_querystr($i, 'item_id'); $items = q("SELECT item.*, item.id AS item_id FROM item WHERE item.parent IN ( %s ) $item_normal order by item.id asc", dbesc($parents_str) ); - if(! $items) { + if (!$items) { http_status_exit(404, 'Not found'); } - xchan_query($items,true); - $items = fetch_post_tags($items,true); + xchan_query($items, true); + $items = fetch_post_tags($items, true); - if(! $items) + if (!$items) http_status_exit(404, 'Not found'); $chan = channelx_by_n($items[0]['uid']); - if(! $chan) + if (!$chan) http_status_exit(404, 'Not found'); - if(! perm_is_allowed($chan['channel_id'],get_observer_hash(),'view_stream')) + if (!perm_is_allowed($chan['channel_id'], get_observer_hash(), 'view_stream')) http_status_exit(403, 'Forbidden'); $i = Activity::encode_item_collection($items, 'conversation/' . $item_id, 'OrderedCollection'); - if(! $i) + if (!$i) http_status_exit(404, 'Not found'); - if($portable_id && (! intval($items[0]['item_private']))) { + if ($portable_id && (!intval($items[0]['item_private']))) { ThreadListener::store(z_root() . '/item/' . $item_id, $portable_id); } @@ -149,25 +146,25 @@ class Item extends Controller { ACTIVITYSTREAMS_JSONLD_REV, 'https://w3id.org/security/v1', z_root() . ZOT_APSCHEMA_REV - ]], $i); + ]], $i); - $headers = []; - $headers['Content-Type'] = 'application/x-zot+json' ; - $x['signature'] = LDSignatures::sign($x,$chan); - $ret = json_encode($x, JSON_UNESCAPED_SLASHES); - $headers['Digest'] = HTTPSig::generate_digest_header($ret); + $headers = []; + $headers['Content-Type'] = 'application/x-zot+json'; + $x['signature'] = LDSignatures::sign($x, $chan); + $ret = json_encode($x, JSON_UNESCAPED_SLASHES); + $headers['Digest'] = HTTPSig::generate_digest_header($ret); $headers['(request-target)'] = strtolower($_SERVER['REQUEST_METHOD']) . ' ' . $_SERVER['REQUEST_URI']; - $h = HTTPSig::create_sig($headers,$chan['channel_prvkey'],channel_url($chan)); + $h = HTTPSig::create_sig($headers, $chan['channel_prvkey'], channel_url($chan)); HTTPSig::set_headers($h); echo $ret; killme(); } - if(ActivityStreams::is_as_request()) { + if (ActivityStreams::is_as_request()) { $item_id = argv(1); - if(! $item_id) + if (!$item_id) http_status_exit(404, 'Not found'); $portable_id = EMPTY_STR; @@ -189,8 +186,8 @@ class Item extends Controller { dbesc($item_id) ); - if (! $r) { - http_status_exit(404,'Not found'); + if (!$r) { + http_status_exit(404, 'Not found'); } // process an authenticated fetch @@ -198,10 +195,10 @@ class Item extends Controller { $sigdata = HTTPSig::verify(EMPTY_STR); if ($sigdata['portable_id'] && $sigdata['header_valid']) { $portable_id = $sigdata['portable_id']; - if (! check_channelallowed($portable_id)) { + if (!check_channelallowed($portable_id)) { http_status_exit(403, 'Permission denied'); } - if (! check_siteallowed($sigdata['signer'])) { + if (!check_siteallowed($sigdata['signer'])) { http_status_exit(403, 'Permission denied'); } observer_auth($portable_id); @@ -211,8 +208,8 @@ class Item extends Controller { dbesc($portable_id) ); } - elseif (Config::get('system','require_authenticated_fetch',false)) { - http_status_exit(403,'Permission denied'); + elseif (Config::get('system', 'require_authenticated_fetch', false)) { + http_status_exit(403, 'Permission denied'); } // if we don't have a parent id belonging to the signer see if we can obtain one as a visitor that we have permission to access @@ -220,40 +217,40 @@ class Item extends Controller { $sql_extra = item_permissions_sql(0); - if (! $i) { + if (!$i) { $i = q("select id as item_id from item where mid = '%s' $item_normal $sql_extra order by item_wall desc limit 1", dbesc($r[0]['parent_mid']) ); } - if(! $i) { - http_status_exit(403,'Forbidden'); + if (!$i) { + http_status_exit(403, 'Forbidden'); } // If we get to this point we have determined we can access the original in $r (fetched much further above), so use it. - xchan_query($r,true); - $items = fetch_post_tags($r,false); + xchan_query($r, true); + $items = fetch_post_tags($r, false); $chan = channelx_by_n($items[0]['uid']); - if(! $chan) + if (!$chan) http_status_exit(404, 'Not found'); - if(! perm_is_allowed($chan['channel_id'],get_observer_hash(),'view_stream')) + if (!perm_is_allowed($chan['channel_id'], get_observer_hash(), 'view_stream')) http_status_exit(403, 'Forbidden'); - $i = Activity::encode_item($items[0],true); + $i = Activity::encode_item($items[0]); - if(! $i) + if (!$i) http_status_exit(404, 'Not found'); - if ($portable_id && (! intval($items[0]['item_private']))) { + if ($portable_id && (!intval($items[0]['item_private']))) { $c = q("select abook_id from abook where abook_channel = %d and abook_xchan = '%s'", intval($items[0]['uid']), dbesc($portable_id) ); - if (! $c) { + if (!$c) { ThreadListener::store(z_root() . '/item/' . $item_id, $portable_id); } } @@ -262,16 +259,16 @@ class Item extends Controller { ACTIVITYSTREAMS_JSONLD_REV, 'https://w3id.org/security/v1', z_root() . ZOT_APSCHEMA_REV - ]], $i); - - $headers = []; - $headers['Content-Type'] = 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"' ; - $x['signature'] = LDSignatures::sign($x,$chan); - $ret = json_encode($x, JSON_UNESCAPED_SLASHES); - $headers['Date'] = datetime_convert('UTC','UTC', 'now', 'D, d M Y H:i:s \\G\\M\\T'); - $headers['Digest'] = HTTPSig::generate_digest_header($ret); + ]], $i); + + $headers = []; + $headers['Content-Type'] = 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'; + $x['signature'] = LDSignatures::sign($x, $chan); + $ret = json_encode($x, JSON_UNESCAPED_SLASHES); + $headers['Date'] = datetime_convert('UTC', 'UTC', 'now', 'D, d M Y H:i:s \\G\\M\\T'); + $headers['Digest'] = HTTPSig::generate_digest_header($ret); $headers['(request-target)'] = strtolower($_SERVER['REQUEST_METHOD']) . ' ' . $_SERVER['REQUEST_URI']; - $h = HTTPSig::create_sig($headers,$chan['channel_prvkey'],channel_url($chan)); + $h = HTTPSig::create_sig($headers, $chan['channel_prvkey'], channel_url($chan)); HTTPSig::set_headers($h); echo $ret; killme(); @@ -279,14 +276,14 @@ class Item extends Controller { } - if(argc() > 1 && argv(1) !== 'drop') { + if (argc() > 1 && argv(1) !== 'drop') { $x = q("select uid, item_wall, llink, mid from item where mid = '%s' or mid = '%s' or uuid = '%s'", dbesc(z_root() . '/item/' . argv(1)), dbesc(z_root() . '/activity/' . argv(1)), dbesc(argv(1)) ); - if($x) { - foreach($x as $xv) { + if ($x) { + foreach ($x as $xv) { if (intval($xv['item_wall'])) { $c = channelx_by_n($xv['uid']); if ($c) { @@ -302,17 +299,16 @@ class Item extends Controller { } - function post() { // This will change. Figure out who the observer is and whether or not // they have permission to post here. Else ignore the post. - if((! local_channel()) && (! remote_channel()) && (! x($_REQUEST,'anonname'))) + if ((!local_channel()) && (!remote_channel()) && (!x($_REQUEST, 'anonname'))) return; - $uid = local_channel(); - $channel = null; + $uid = local_channel(); + $channel = null; $observer = null; $datarray = []; @@ -321,34 +317,34 @@ class Item extends Controller { * Is this a reply to something? */ - $parent = ((x($_REQUEST,'parent')) ? intval($_REQUEST['parent']) : 0); - $parent_mid = ((x($_REQUEST,'parent_mid')) ? trim($_REQUEST['parent_mid']) : ''); - $mode = (($_REQUEST['conv_mode'] === 'channel') ? 'channel' : 'network'); + $parent = ((x($_REQUEST, 'parent')) ? intval($_REQUEST['parent']) : 0); + $parent_mid = ((x($_REQUEST, 'parent_mid')) ? trim($_REQUEST['parent_mid']) : ''); + $mode = (($_REQUEST['conv_mode'] === 'channel') ? 'channel' : 'network'); - $remote_xchan = ((x($_REQUEST,'remote_xchan')) ? trim($_REQUEST['remote_xchan']) : false); - $r = q("select * from xchan where xchan_hash = '%s' limit 1", + $remote_xchan = ((x($_REQUEST, 'remote_xchan')) ? trim($_REQUEST['remote_xchan']) : false); + $r = q("select * from xchan where xchan_hash = '%s' limit 1", dbesc($remote_xchan) ); - if($r) + if ($r) $remote_observer = $r[0]; else $remote_xchan = $remote_observer = false; - $profile_uid = ((x($_REQUEST,'profile_uid')) ? intval($_REQUEST['profile_uid']) : 0); + $profile_uid = ((x($_REQUEST, 'profile_uid')) ? intval($_REQUEST['profile_uid']) : 0); require_once('include/channel.php'); $sys = get_sys_channel(); - if($sys && $profile_uid && ($sys['channel_id'] == $profile_uid) && is_site_admin()) { - $uid = intval($sys['channel_id']); - $channel = $sys; + if ($sys && $profile_uid && ($sys['channel_id'] == $profile_uid) && is_site_admin()) { + $uid = intval($sys['channel_id']); + $channel = $sys; $observer = $sys; } - if(x($_REQUEST,'dropitems')) { + if (x($_REQUEST, 'dropitems')) { require_once('include/items.php'); - $arr_drop = explode(',',$_REQUEST['dropitems']); + $arr_drop = explode(',', $_REQUEST['dropitems']); drop_items($arr_drop); - $json = array('success' => 1); + $json = ['success' => 1]; echo json_encode($json); killme(); } @@ -357,7 +353,7 @@ class Item extends Controller { // logger('postvars ' . print_r($_REQUEST,true), LOGGER_DATA); - $api_source = ((x($_REQUEST,'api_source') && $_REQUEST['api_source']) ? true : false); + $api_source = ((x($_REQUEST, 'api_source') && $_REQUEST['api_source']) ? true : false); $consensus = intval($_REQUEST['consensus']); $nocomment = intval($_REQUEST['nocomment']); @@ -373,77 +369,74 @@ class Item extends Controller { // If you are unsure, it is prudent (and important) to leave it unset. - $origin = (($api_source && array_key_exists('origin',$_REQUEST)) ? intval($_REQUEST['origin']) : 1); + $origin = (($api_source && array_key_exists('origin', $_REQUEST)) ? intval($_REQUEST['origin']) : 1); // To represent message-ids on other networks - this will create an iconfig record - $namespace = (($api_source && array_key_exists('namespace',$_REQUEST)) ? strip_tags($_REQUEST['namespace']) : ''); - $remote_id = (($api_source && array_key_exists('remote_id',$_REQUEST)) ? strip_tags($_REQUEST['remote_id']) : ''); + $namespace = (($api_source && array_key_exists('namespace', $_REQUEST)) ? strip_tags($_REQUEST['namespace']) : ''); + $remote_id = (($api_source && array_key_exists('remote_id', $_REQUEST)) ? strip_tags($_REQUEST['remote_id']) : ''); $owner_hash = null; - $message_id = ((x($_REQUEST,'message_id') && $api_source) ? strip_tags($_REQUEST['message_id']) : ''); - $created = ((x($_REQUEST,'created')) ? datetime_convert(date_default_timezone_get(),'UTC',$_REQUEST['created']) : datetime_convert()); - $post_id = ((x($_REQUEST,'post_id')) ? intval($_REQUEST['post_id']) : 0); - $app = ((x($_REQUEST,'source')) ? strip_tags($_REQUEST['source']) : ''); - $return_path = ((x($_REQUEST,'return')) ? $_REQUEST['return'] : ''); - $preview = ((x($_REQUEST,'preview')) ? intval($_REQUEST['preview']) : 0); - $categories = ((x($_REQUEST,'category')) ? escape_tags($_REQUEST['category']) : ''); - $webpage = ((x($_REQUEST,'webpage')) ? intval($_REQUEST['webpage']) : 0); - $item_obscured = ((x($_REQUEST,'obscured')) ? intval($_REQUEST['obscured']) : 0); - $pagetitle = ((x($_REQUEST,'pagetitle')) ? escape_tags(urlencode($_REQUEST['pagetitle'])) : ''); - $layout_mid = ((x($_REQUEST,'layout_mid')) ? escape_tags($_REQUEST['layout_mid']): ''); - $plink = ((x($_REQUEST,'permalink')) ? escape_tags($_REQUEST['permalink']) : ''); - $obj_type = ((x($_REQUEST,'obj_type')) ? escape_tags($_REQUEST['obj_type']) : ACTIVITY_OBJ_NOTE); + $message_id = ((x($_REQUEST, 'message_id') && $api_source) ? strip_tags($_REQUEST['message_id']) : ''); + $created = ((x($_REQUEST, 'created')) ? datetime_convert(date_default_timezone_get(), 'UTC', $_REQUEST['created']) : datetime_convert()); + $post_id = ((x($_REQUEST, 'post_id')) ? intval($_REQUEST['post_id']) : 0); + $app = ((x($_REQUEST, 'source')) ? strip_tags($_REQUEST['source']) : ''); + $return_path = ((x($_REQUEST, 'return')) ? $_REQUEST['return'] : ''); + $preview = ((x($_REQUEST, 'preview')) ? intval($_REQUEST['preview']) : 0); + $categories = ((x($_REQUEST, 'category')) ? escape_tags($_REQUEST['category']) : ''); + $webpage = ((x($_REQUEST, 'webpage')) ? intval($_REQUEST['webpage']) : 0); + $item_obscured = ((x($_REQUEST, 'obscured')) ? intval($_REQUEST['obscured']) : 0); + $pagetitle = ((x($_REQUEST, 'pagetitle')) ? escape_tags(urlencode($_REQUEST['pagetitle'])) : ''); + $layout_mid = ((x($_REQUEST, 'layout_mid')) ? escape_tags($_REQUEST['layout_mid']) : ''); + $plink = ((x($_REQUEST, 'permalink')) ? escape_tags($_REQUEST['permalink']) : ''); + $obj_type = ((x($_REQUEST, 'obj_type')) ? escape_tags($_REQUEST['obj_type']) : ACTIVITY_OBJ_NOTE); // allow API to bulk load a bunch of imported items with sending out a bunch of posts. - $nopush = ((x($_REQUEST,'nopush')) ? intval($_REQUEST['nopush']) : 0); + $nopush = ((x($_REQUEST, 'nopush')) ? intval($_REQUEST['nopush']) : 0); /* * Check service class limits */ - if ($uid && !(x($_REQUEST,'parent')) && !(x($_REQUEST,'post_id'))) { - $ret = $this->item_check_service_class($uid,(($_REQUEST['webpage'] == ITEM_TYPE_WEBPAGE) ? true : false)); + if ($uid && !(x($_REQUEST, 'parent')) && !(x($_REQUEST, 'post_id'))) { + $ret = $this->item_check_service_class($uid, (($_REQUEST['webpage'] == ITEM_TYPE_WEBPAGE) ? true : false)); if (!$ret['success']) { - notice( t($ret['message']) . EOL) ; - if($api_source) - return ( [ 'success' => false, 'message' => 'service class exception' ] ); - if(x($_REQUEST,'return')) - goaway(z_root() . "/" . $return_path ); + notice(t($ret['message']) . EOL); + if ($api_source) + return (['success' => false, 'message' => 'service class exception']); + if (x($_REQUEST, 'return')) + goaway(z_root() . "/" . $return_path); killme(); } } - if($pagetitle) { - require_once('library/urlify/URLify.php'); - $pagetitle = strtolower(\URLify::transliterate($pagetitle)); + if ($pagetitle) { + $pagetitle = strtolower(URLify::transliterate($pagetitle)); } - $item_flags = $item_restrict = 0; $expires = NULL_DATE; - $route = ''; - $parent_item = null; + $route = ''; + $parent_item = null; $parent_contact = null; - $thr_parent = ''; - $parid = 0; - $r = false; + $thr_parent = ''; + $r = false; - if($parent || $parent_mid) { + if ($parent || $parent_mid) { - if(! x($_REQUEST,'type')) + if (!x($_REQUEST, 'type')) $_REQUEST['type'] = 'net-comment'; - if($obj_type == ACTIVITY_OBJ_NOTE) + if ($obj_type == ACTIVITY_OBJ_NOTE) $obj_type = ACTIVITY_OBJ_COMMENT; - if($parent) { + if ($parent) { $r = q("SELECT * FROM item WHERE id = %d LIMIT 1", intval($parent) ); } - elseif($parent_mid && $uid) { + elseif ($parent_mid && $uid) { // This is coming from an API source, and we are logged in $r = q("SELECT * FROM item WHERE mid = '%s' AND uid = %d LIMIT 1", dbesc($parent_mid), @@ -451,10 +444,10 @@ class Item extends Controller { ); } // if this isn't the real parent of the conversation, find it - if($r) { - $parid = $r[0]['parent']; + if ($r) { + $parid = $r[0]['parent']; $parent_mid = $r[0]['mid']; - if($r[0]['id'] != $r[0]['parent']) { + if ($r[0]['id'] != $r[0]['parent']) { $r = q("SELECT * FROM item WHERE id = parent AND parent = %d LIMIT 1", intval($parid) ); @@ -463,24 +456,24 @@ class Item extends Controller { // if interacting with a pubstream item, // create a copy of the parent in your stream - if($r[0]['uid'] === $sys['channel_id'] && local_channel()) { - $r = [ copy_of_pubitem(\App::get_channel(), $r[0]['mid']) ]; + if ($r[0]['uid'] === $sys['channel_id'] && local_channel()) { + $r = [copy_of_pubitem(App::get_channel(), $r[0]['mid'])]; } } - if(! $r) { - notice( t('Unable to locate original post.') . EOL); - if($api_source) - return ( [ 'success' => false, 'message' => 'invalid post id' ] ); - if(x($_REQUEST,'return')) - goaway(z_root() . "/" . $return_path ); + if (!$r) { + notice(t('Unable to locate original post.') . EOL); + if ($api_source) + return (['success' => false, 'message' => 'invalid post id']); + if (x($_REQUEST, 'return')) + goaway(z_root() . "/" . $return_path); killme(); } - xchan_query($r,true); + xchan_query($r, true); $parent_item = $r[0]; - $parent = $r[0]['id']; + $parent = $r[0]['id']; // multi-level threading - preserve the info but re-parent to our single level threading @@ -492,52 +485,52 @@ class Item extends Controller { $moderated = false; - if(! $observer) { - $observer = \App::get_observer(); - if(! $observer) { + if (!$observer) { + $observer = App::get_observer(); + if (!$observer) { $observer = anon_identity_init($_REQUEST); - if($observer) { - $moderated = true; + if ($observer) { + $moderated = true; $remote_xchan = $remote_observer = $observer; } } } - if(! $observer) { - notice( t('Permission denied.') . EOL) ; - if($api_source) - return ( [ 'success' => false, 'message' => 'permission denied' ] ); - if(x($_REQUEST,'return')) - goaway(z_root() . "/" . $return_path ); + if (!$observer) { + notice(t('Permission denied.') . EOL); + if ($api_source) + return (['success' => false, 'message' => 'permission denied']); + if (x($_REQUEST, 'return')) + goaway(z_root() . "/" . $return_path); killme(); } - if($parent) { + if ($parent) { logger('mod_item: item_post parent=' . $parent); $can_comment = false; - $can_comment = can_comment_on_post($observer['xchan_hash'],$parent_item); - if (!$can_comment) { - if((array_key_exists('owner',$parent_item)) && intval($parent_item['owner']['abook_self'])==1 ) - $can_comment = perm_is_allowed($profile_uid,$observer['xchan_hash'],'post_comments'); - } - - if(! $can_comment) { - notice( t('Permission denied.') . EOL) ; - if($api_source) - return ( [ 'success' => false, 'message' => 'permission denied' ] ); - if(x($_REQUEST,'return')) - goaway(z_root() . "/" . $return_path ); + $can_comment = can_comment_on_post($observer['xchan_hash'], $parent_item); + if (!$can_comment) { + if ((array_key_exists('owner', $parent_item)) && intval($parent_item['owner']['abook_self']) == 1) + $can_comment = perm_is_allowed($profile_uid, $observer['xchan_hash'], 'post_comments'); + } + + if (!$can_comment) { + notice(t('Permission denied.') . EOL); + if ($api_source) + return (['success' => false, 'message' => 'permission denied']); + if (x($_REQUEST, 'return')) + goaway(z_root() . "/" . $return_path); killme(); } } else { - if(! perm_is_allowed($profile_uid,$observer['xchan_hash'],($webpage) ? 'write_pages' : 'post_wall')) { - notice( t('Permission denied.') . EOL) ; - if($api_source) - return ( [ 'success' => false, 'message' => 'permission denied' ] ); - if(x($_REQUEST,'return')) - goaway(z_root() . "/" . $return_path ); + if (!perm_is_allowed($profile_uid, $observer['xchan_hash'], ($webpage) ? 'write_pages' : 'post_wall')) { + notice(t('Permission denied.') . EOL); + if ($api_source) + return (['success' => false, 'message' => 'permission denied']); + if (x($_REQUEST, 'return')) + goaway(z_root() . "/" . $return_path); killme(); } } @@ -547,53 +540,53 @@ class Item extends Controller { $orig_post = null; - if($namespace && $remote_id) { + if ($namespace && $remote_id) { // It wasn't an internally generated post - see if we've got an item matching this remote service id $i = q("select iid from iconfig where cat = 'system' and k = '%s' and v = '%s' limit 1", dbesc($namespace), dbesc($remote_id) ); - if($i) + if ($i) $post_id = $i[0]['iid']; } $iconfig = null; - if($post_id) { + if ($post_id) { $i = q("SELECT * FROM item WHERE uid = %d AND id = %d LIMIT 1", intval($profile_uid), intval($post_id) ); - if(! count($i)) + if (!count($i)) killme(); $orig_post = $i[0]; - $iconfig = q("select * from iconfig where iid = %d", + $iconfig = q("select * from iconfig where iid = %d", intval($post_id) ); } - if(! $channel) { - if($uid && $uid == $profile_uid) { - $channel = \App::get_channel(); + if (!$channel) { + if ($uid && $uid == $profile_uid) { + $channel = App::get_channel(); } else { // posting as yourself but not necessarily to a channel you control $r = q("select * from channel left join account on channel_account_id = account_id where channel_id = %d LIMIT 1", intval($profile_uid) ); - if($r) + if ($r) $channel = $r[0]; } } - if(! $channel) { + if (!$channel) { logger("mod_item: no channel."); - if($api_source) - return ( [ 'success' => false, 'message' => 'no channel' ] ); - if(x($_REQUEST,'return')) - goaway(z_root() . "/" . $return_path ); + if ($api_source) + return (['success' => false, 'message' => 'no channel']); + if (x($_REQUEST, 'return')) + goaway(z_root() . "/" . $return_path); killme(); } @@ -602,37 +595,37 @@ class Item extends Controller { $r = q("select * from xchan where xchan_hash = '%s' limit 1", dbesc($channel['channel_hash']) ); - if($r && count($r)) { + if ($r && count($r)) { $owner_xchan = $r[0]; } else { logger("mod_item: no owner."); - if($api_source) - return ( [ 'success' => false, 'message' => 'no owner' ] ); - if(x($_REQUEST,'return')) - goaway(z_root() . "/" . $return_path ); + if ($api_source) + return (['success' => false, 'message' => 'no owner']); + if (x($_REQUEST, 'return')) + goaway(z_root() . "/" . $return_path); killme(); } - $walltowall = false; + $walltowall = false; $walltowall_comment = false; - if($remote_xchan && ! $moderated) + if ($remote_xchan && !$moderated) $observer = $remote_observer; - if($observer) { + if ($observer) { logger('mod_item: post accepted from ' . $observer['xchan_name'] . ' for ' . $owner_xchan['xchan_name'], LOGGER_DEBUG); // wall-to-wall detection. // For top-level posts, if the author and owner are different it's a wall-to-wall // For comments, We need to additionally look at the parent and see if it's a wall post that originated locally. - if($observer['xchan_name'] != $owner_xchan['xchan_name']) { - if(($parent_item) && ($parent_item['item_wall'] && $parent_item['item_origin'])) { + if ($observer['xchan_name'] != $owner_xchan['xchan_name']) { + if (($parent_item) && ($parent_item['item_wall'] && $parent_item['item_origin'])) { $walltowall_comment = true; - $walltowall = true; + $walltowall = true; } - if(! $parent) { + if (!$parent) { $walltowall = true; } } @@ -640,83 +633,78 @@ class Item extends Controller { $acl = new \Zotlabs\Access\AccessList($channel); - $view_policy = \Zotlabs\Access\PermissionLimits::Get($channel['channel_id'],'view_stream'); - $comment_policy = \Zotlabs\Access\PermissionLimits::Get($channel['channel_id'],'post_comments'); + $view_policy = \Zotlabs\Access\PermissionLimits::Get($channel['channel_id'], 'view_stream'); + $comment_policy = \Zotlabs\Access\PermissionLimits::Get($channel['channel_id'], 'post_comments'); - $public_policy = ((x($_REQUEST,'public_policy')) ? escape_tags($_REQUEST['public_policy']) : map_scope($view_policy,true)); - if($webpage) + $public_policy = ((x($_REQUEST, 'public_policy')) ? escape_tags($_REQUEST['public_policy']) : map_scope($view_policy, true)); + if ($webpage) $public_policy = ''; - if($public_policy) + if ($public_policy) $private = 1; - if($orig_post) { + if ($orig_post) { $private = 0; // webpages are allowed to change ACLs after the fact. Normal conversation items aren't. - if($webpage) { + if ($webpage) { $acl->set_from_array($_REQUEST); } else { $acl->set($orig_post); - $public_policy = $orig_post['public_policy']; - $private = $orig_post['item_private']; + $public_policy = $orig_post['public_policy']; + $private = $orig_post['item_private']; } - if($public_policy || $acl->is_private()) { + if ($public_policy || $acl->is_private()) { $private = (($private) ? $private : 1); } - $location = $orig_post['location']; - $coord = $orig_post['coord']; - $verb = $orig_post['verb']; - $app = $orig_post['app']; - $title = escape_tags(trim($_REQUEST['title'])); - $summary = trim($_REQUEST['summary']); - $body = trim($_REQUEST['body']); - $item_flags = $orig_post['item_flags']; - - $item_origin = $orig_post['item_origin']; - $item_unseen = $orig_post['item_unseen']; - $item_starred = $orig_post['item_starred']; - $item_uplink = $orig_post['item_uplink']; - $item_consensus = $orig_post['item_consensus']; - $item_wall = $orig_post['item_wall']; - $item_thread_top = $orig_post['item_thread_top']; - $item_notshown = $orig_post['item_notshown']; - $item_nsfw = $orig_post['item_nsfw']; - $item_relay = $orig_post['item_relay']; - $item_mentionsme = $orig_post['item_mentionsme']; - $item_nocomment = $orig_post['item_nocomment']; - $item_obscured = $orig_post['item_obscured']; - $item_verified = $orig_post['item_verified']; - $item_retained = $orig_post['item_retained']; - $item_rss = $orig_post['item_rss']; - $item_deleted = $orig_post['item_deleted']; - $item_type = $orig_post['item_type']; - $item_hidden = $orig_post['item_hidden']; - $item_unpublished = $orig_post['item_unpublished']; - $item_delayed = $orig_post['item_delayed']; - $item_pending_remove = $orig_post['item_pending_remove']; - $item_blocked = $orig_post['item_blocked']; - - - - $postopts = $orig_post['postopts']; - $created = $orig_post['created']; - $expires = $orig_post['expires']; - $mid = $orig_post['mid']; - $parent_mid = $orig_post['parent_mid']; - $plink = $orig_post['plink']; - + $location = $orig_post['location']; + $coord = $orig_post['coord']; + $verb = $orig_post['verb']; + $app = $orig_post['app']; + $title = escape_tags(trim($_REQUEST['title'])); + $summary = trim($_REQUEST['summary']); + $body = trim($_REQUEST['body']); + $item_flags = $orig_post['item_flags']; + $item_origin = $orig_post['item_origin']; + $item_unseen = $orig_post['item_unseen']; + $item_starred = $orig_post['item_starred']; + $item_uplink = $orig_post['item_uplink']; + $item_consensus = $orig_post['item_consensus']; + $item_wall = $orig_post['item_wall']; + $item_thread_top = $orig_post['item_thread_top']; + $item_notshown = $orig_post['item_notshown']; + $item_nsfw = $orig_post['item_nsfw']; + $item_relay = $orig_post['item_relay']; + $item_mentionsme = $orig_post['item_mentionsme']; + $item_nocomment = $orig_post['item_nocomment']; + $item_obscured = $orig_post['item_obscured']; + $item_verified = $orig_post['item_verified']; + $item_retained = $orig_post['item_retained']; + $item_rss = $orig_post['item_rss']; + $item_deleted = $orig_post['item_deleted']; + $item_type = $orig_post['item_type']; + $item_hidden = $orig_post['item_hidden']; + $item_unpublished = $orig_post['item_unpublished']; + $item_delayed = $orig_post['item_delayed']; + $item_pending_remove = $orig_post['item_pending_remove']; + $item_blocked = $orig_post['item_blocked']; + $postopts = $orig_post['postopts']; + $created = $orig_post['created']; + $expires = $orig_post['expires']; + $mid = $orig_post['mid']; + $parent_mid = $orig_post['parent_mid']; + $plink = $orig_post['plink']; } else { - if(! $walltowall) { - if((array_key_exists('contact_allow',$_REQUEST)) - || (array_key_exists('group_allow',$_REQUEST)) - || (array_key_exists('contact_deny',$_REQUEST)) - || (array_key_exists('group_deny',$_REQUEST))) { + if (!$walltowall) { + if ((array_key_exists('contact_allow', $_REQUEST)) + || (array_key_exists('group_allow', $_REQUEST)) + || (array_key_exists('contact_deny', $_REQUEST)) + || (array_key_exists('group_deny', $_REQUEST))) { $acl->set_from_array($_REQUEST); } - elseif(! $api_source) { + elseif (!$api_source) { // if no ACL has been defined and we aren't using the API, the form // didn't send us any parameters. This means there's no ACL or it has @@ -724,27 +712,27 @@ class Item extends Controller { // If $api_source is set and there are no ACL parameters, we default // to the channel permissions which were set in the ACL contructor. - $acl->set(array('allow_cid' => '', 'allow_gid' => '', 'deny_cid' => '', 'deny_gid' => '')); + $acl->set(['allow_cid' => '', 'allow_gid' => '', 'deny_cid' => '', 'deny_gid' => '']); } } - $location = notags(trim($_REQUEST['location'])); - $coord = notags(trim($_REQUEST['coord'])); - $verb = notags(trim($_REQUEST['verb'])); - $title = escape_tags(trim($_REQUEST['title'])); - $summary = trim($_REQUEST['summary']); - $body = trim($_REQUEST['body']); - $body .= trim($_REQUEST['attachment']); - $postopts = ''; + $location = notags(trim($_REQUEST['location'])); + $coord = notags(trim($_REQUEST['coord'])); + $verb = notags(trim($_REQUEST['verb'])); + $title = escape_tags(trim($_REQUEST['title'])); + $summary = trim($_REQUEST['summary']); + $body = trim($_REQUEST['body']); + $body .= trim($_REQUEST['attachment']); + $postopts = ''; - $allow_empty = ((array_key_exists('allow_empty',$_REQUEST)) ? intval($_REQUEST['allow_empty']) : 0); + $allow_empty = ((array_key_exists('allow_empty', $_REQUEST)) ? intval($_REQUEST['allow_empty']) : 0); $private = (($private) ? $private : intval($acl->is_private() || ($public_policy))); // If this is a comment, set the permissions from the parent. - if($parent_item) { + if ($parent_item) { $acl->set($parent_item); $private = intval($parent_item['item_private']); $public_policy = $parent_item['public_policy']; @@ -752,51 +740,50 @@ class Item extends Controller { $webpage = $parent_item['item_type']; } - if((! $allow_empty) && (! strlen($body))) { - if($preview) + if ((!$allow_empty) && (!strlen($body))) { + if ($preview) killme(); - info( t('Empty post discarded.') . EOL ); - if($api_source) - return ( [ 'success' => false, 'message' => 'no content' ] ); - if(x($_REQUEST,'return')) - goaway(z_root() . "/" . $return_path ); + info(t('Empty post discarded.') . EOL); + if ($api_source) + return (['success' => false, 'message' => 'no content']); + if (x($_REQUEST, 'return')) + goaway(z_root() . "/" . $return_path); killme(); } } - - if(feature_enabled($profile_uid,'content_expire')) { - if(x($_REQUEST,'expire')) { - $expires = datetime_convert(date_default_timezone_get(),'UTC', $_REQUEST['expire']); - if($expires <= datetime_convert()) + if (feature_enabled($profile_uid, 'content_expire')) { + if (x($_REQUEST, 'expire')) { + $expires = datetime_convert(date_default_timezone_get(), 'UTC', $_REQUEST['expire']); + if ($expires <= datetime_convert()) $expires = NULL_DATE; } } $mimetype = notags(trim($_REQUEST['mimetype'])); - if(! $mimetype) + if (!$mimetype) $mimetype = 'text/bbcode'; $execflag = ((intval($uid) == intval($profile_uid) && ($channel['channel_pageflags'] & PAGE_ALLOWCODE)) ? true : false); - if($preview) { - $summary = z_input_filter($summary,$mimetype,$execflag); - $body = z_input_filter($body,$mimetype,$execflag); + if ($preview) { + $summary = z_input_filter($summary, $mimetype, $execflag); + $body = z_input_filter($body, $mimetype, $execflag); } - $arr = [ 'profile_uid' => $profile_uid, 'summary' => $summary, 'content' => $body, 'mimetype' => $mimetype ]; - call_hooks('post_content',$arr); - $summary = $arr['summary']; - $body = $arr['content']; + $arr = ['profile_uid' => $profile_uid, 'summary' => $summary, 'content' => $body, 'mimetype' => $mimetype]; + call_hooks('post_content', $arr); + $summary = $arr['summary']; + $body = $arr['content']; $mimetype = $arr['mimetype']; - $gacl = $acl->get(); + $gacl = $acl->get(); $str_contact_allow = $gacl['allow_cid']; $str_group_allow = $gacl['allow_gid']; $str_contact_deny = $gacl['deny_cid']; @@ -807,7 +794,7 @@ class Item extends Controller { // if this is a wall-to-wall post to a group, turn it into a direct message - $role = get_pconfig($profile_uid,'system','permissions_role'); + $role = get_pconfig($profile_uid, 'system', 'permissions_role'); $rolesettings = PermissionRoles::role_perms($role); @@ -815,17 +802,16 @@ class Item extends Controller { $is_group = (($channel_type === 'group') ? true : false); - if (($is_group) && ($walltowall) && (! $walltowall_comment)) { - $groupww = true; + if (($is_group) && ($walltowall) && (!$walltowall_comment)) { + $groupww = true; $str_contact_allow = $owner_xchan['xchan_hash']; - $str_group_allow = ''; + $str_group_allow = ''; } $post_tags = []; - - if($mimetype === 'text/bbcode') { + if ($mimetype === 'text/bbcode') { require_once('include/text.php'); @@ -840,27 +826,27 @@ class Item extends Controller { $results = linkify_tags($body, ($uid) ? $uid : $profile_uid); - if($results) { + if ($results) { // Set permissions based on tag replacements set_linkified_perms($results, $str_contact_allow, $str_group_allow, $profile_uid, $private, $parent_item); - foreach($results as $result) { + foreach ($results as $result) { $success = $result['success']; - if($success['replaced']) { - $post_tags[] = array( + if ($success['replaced']) { + $post_tags[] = [ 'uid' => $profile_uid, 'ttype' => $success['termtype'], 'otype' => TERM_OBJ_POST, 'term' => $success['term'], 'url' => $success['url'] - ); + ]; } } } - if(($str_contact_allow) && (! $str_group_allow)) { + if (($str_contact_allow) && (!$str_group_allow)) { // direct message - private between individual channels but not groups $private = 2; } @@ -885,45 +871,45 @@ class Item extends Controller { * */ - if(! $preview) { - fix_attached_photo_permissions($profile_uid,$owner_xchan['xchan_hash'],((strpos($body,'[/crypt]')) ? $_POST['media_str'] : $body),$str_contact_allow,$str_group_allow,$str_contact_deny,$str_group_deny); - fix_attached_photo_permissions($profile_uid,$owner_xchan['xchan_hash'],((strpos($summary,'[/crypt]')) ? $_POST['media_str'] : $summary),$str_contact_allow,$str_group_allow,$str_contact_deny,$str_group_deny); - fix_attached_file_permissions($channel,$observer['xchan_hash'],((strpos($body,'[/crypt]')) ? $_POST['media_str'] : $body),$str_contact_allow,$str_group_allow,$str_contact_deny,$str_group_deny); + if (!$preview) { + fix_attached_photo_permissions($profile_uid, $owner_xchan['xchan_hash'], ((strpos($body, '[/crypt]')) ? $_POST['media_str'] : $body), $str_contact_allow, $str_group_allow, $str_contact_deny, $str_group_deny); + fix_attached_photo_permissions($profile_uid, $owner_xchan['xchan_hash'], ((strpos($summary, '[/crypt]')) ? $_POST['media_str'] : $summary), $str_contact_allow, $str_group_allow, $str_contact_deny, $str_group_deny); + fix_attached_file_permissions($channel, $observer['xchan_hash'], ((strpos($body, '[/crypt]')) ? $_POST['media_str'] : $body), $str_contact_allow, $str_group_allow, $str_contact_deny, $str_group_deny); } $attachments = ''; - $match = false; + $match = false; - if(preg_match_all('/(\[attachment\](.*?)\[\/attachment\])/',$body,$match)) { - $attachments = array(); - $i = 0; - foreach($match[2] as $mtch) { + if (preg_match_all('/(\[attachment\](.*?)\[\/attachment\])/', $body, $match)) { + $attachments = []; + $i = 0; + foreach ($match[2] as $mtch) { $attach_link = ''; - $hash = substr($mtch,0,strpos($mtch,',')); - $rev = intval(substr($mtch,strpos($mtch,','))); - $r = attach_by_hash_nodata($hash, $observer['xchan_hash'], $rev); - if($r['success']) { - $attachments[] = array( + $hash = substr($mtch, 0, strpos($mtch, ',')); + $rev = intval(substr($mtch, strpos($mtch, ','))); + $r = attach_by_hash_nodata($hash, $observer['xchan_hash'], $rev); + if ($r['success']) { + $attachments[] = [ 'href' => z_root() . '/attach/' . $r['data']['hash'], 'length' => $r['data']['filesize'], 'type' => $r['data']['filetype'], 'title' => urlencode($r['data']['filename']), 'revision' => $r['data']['revision'] - ); + ]; } - $body = str_replace($match[1][$i],$attach_link,$body); + $body = str_replace($match[1][$i], $attach_link, $body); $i++; } } - if(preg_match_all('/(\[share=(.*?)\](.*?)\[\/share\])/',$body,$match)) { + if (preg_match_all('/(\[share=(.*?)\](.*?)\[\/share\])/', $body, $match)) { // process share by id $i = 0; - foreach($match[2] as $mtch) { + foreach ($match[2] as $mtch) { $reshare = new \Zotlabs\Lib\Share($mtch); - $body = str_replace($match[1][$i],$reshare->bbcode(),$body); + $body = str_replace($match[1][$i], $reshare->bbcode(), $body); $i++; } } @@ -931,32 +917,32 @@ class Item extends Controller { // BBCODE end alert } - if(strlen($categories)) { + if (strlen($categories)) { - $cats = explode(',',$categories); - foreach($cats as $cat) { + $cats = explode(',', $categories); + foreach ($cats as $cat) { - if($webpage == ITEM_TYPE_CARD) { + if ($webpage == ITEM_TYPE_CARD) { $catlink = z_root() . '/cards/' . $channel['channel_address'] . '?f=&cat=' . urlencode(trim($cat)); } - elseif($webpage == ITEM_TYPE_ARTICLE) { + elseif ($webpage == ITEM_TYPE_ARTICLE) { $catlink = z_root() . '/articles/' . $channel['channel_address'] . '?f=&cat=' . urlencode(trim($cat)); } else { $catlink = $owner_xchan['xchan_url'] . '?f=&cat=' . urlencode(trim($cat)); } - $post_tags[] = array( + $post_tags[] = [ 'uid' => $profile_uid, 'ttype' => TERM_CATEGORY, 'otype' => TERM_OBJ_POST, 'term' => trim($cat), 'url' => $catlink - ); + ]; } } - if($orig_post) { + if ($orig_post) { // preserve original tags $t = q("select * from term where oid = %d and otype = %d and uid = %d and ttype in ( %d, %d, %d )", intval($orig_post['id']), @@ -966,120 +952,119 @@ class Item extends Controller { intval(TERM_FILE), intval(TERM_COMMUNITYTAG) ); - if($t) { - foreach($t as $t1) { - $post_tags[] = array( + if ($t) { + foreach ($t as $t1) { + $post_tags[] = [ 'uid' => $profile_uid, 'ttype' => $t1['ttype'], 'otype' => TERM_OBJ_POST, 'term' => $t1['term'], 'url' => $t1['url'], - ); + ]; } } } - $item_unseen = ((local_channel() != $profile_uid) ? 1 : 0); - $item_wall = (($_REQUEST['type'] === 'wall' || $_REQUEST['type'] === 'wall-comment') ? 1 : 0); - $item_origin = (($origin) ? 1 : 0); + $item_unseen = ((local_channel() != $profile_uid) ? 1 : 0); + $item_wall = (($_REQUEST['type'] === 'wall' || $_REQUEST['type'] === 'wall-comment') ? 1 : 0); + $item_origin = (($origin) ? 1 : 0); $item_consensus = (($consensus) ? 1 : 0); $item_nocomment = (($nocomment) ? 1 : 0); // determine if this is a wall post - if($parent) { + if ($parent) { $item_wall = $parent_item['item_wall']; } else { - if(! $webpage) { + if (!$webpage) { $item_wall = 1; } } - if($moderated) + if ($moderated) $item_blocked = ITEM_MODERATED; - if(! strlen($verb)) - $verb = ACTIVITY_POST ; + if (!strlen($verb)) + $verb = ACTIVITY_POST; - $notify_type = (($parent) ? 'comment-new' : 'wall-new' ); + $notify_type = (($parent) ? 'comment-new' : 'wall-new'); - if(! $mid) { + if (!$mid) { $uuid = (($message_id) ? $message_id : item_message_id()); - $mid = z_root() . '/item/' . $uuid; + $mid = z_root() . '/item/' . $uuid; } - if($is_poll) { + if ($is_poll) { $poll = [ - 'question' => $body, - 'answers' => $_REQUEST['poll_answers'], + 'question' => $body, + 'answers' => $_REQUEST['poll_answers'], 'multiple_answers' => $_REQUEST['poll_multiple_answers'], - 'expire_value' => $_REQUEST['poll_expire_value'], - 'expire_unit' => $_REQUEST['poll_expire_unit'] + 'expire_value' => $_REQUEST['poll_expire_value'], + 'expire_unit' => $_REQUEST['poll_expire_unit'] ]; - $obj = $this->extract_poll_data($poll, [ 'item_private' => $private, 'allow_cid' => $str_contact_allow, 'allow_gid' => $str_contact_deny ]); + $obj = $this->extract_poll_data($poll, ['item_private' => $private, 'allow_cid' => $str_contact_allow, 'allow_gid' => $str_contact_deny]); } else { - $obj = $this->extract_bb_poll_data($body,[ 'item_private' => $private, 'allow_cid' => $str_contact_allow, 'allow_gid' => $str_contact_deny ]); + $obj = $this->extract_bb_poll_data($body, ['item_private' => $private, 'allow_cid' => $str_contact_allow, 'allow_gid' => $str_contact_deny]); } if ($obj) { - $obj['url'] = $mid; + $obj['url'] = $mid; $obj['attributedTo'] = channel_url($channel); - $datarray['obj'] = $obj; - $obj_type = 'Question'; + $datarray['obj'] = $obj; + $obj_type = 'Question'; } - if(! $parent_mid) { + if (!$parent_mid) { $parent_mid = $mid; } - if($parent_item) + if ($parent_item) $parent_mid = $parent_item['mid']; - // Fallback so that we alway have a thr_parent - if(!$thr_parent) + if (!$thr_parent) $thr_parent = $mid; - $item_thread_top = ((! $parent) ? 1 : 0); + $item_thread_top = ((!$parent) ? 1 : 0); // fix permalinks for cards - if($webpage == ITEM_TYPE_CARD) { + if ($webpage == ITEM_TYPE_CARD) { $plink = z_root() . '/cards/' . $channel['channel_address'] . '/' . (($pagetitle) ? $pagetitle : $uuid); } - if(($parent_item) && ($parent_item['item_type'] == ITEM_TYPE_CARD)) { + if (($parent_item) && ($parent_item['item_type'] == ITEM_TYPE_CARD)) { $r = q("select v from iconfig where iconfig.cat = 'system' and iconfig.k = 'CARD' and iconfig.iid = %d limit 1", intval($parent_item['id']) ); - if($r) { + if ($r) { $plink = z_root() . '/cards/' . $channel['channel_address'] . '/' . $r[0]['v']; } } - if($webpage == ITEM_TYPE_ARTICLE) { + if ($webpage == ITEM_TYPE_ARTICLE) { $plink = z_root() . '/articles/' . $channel['channel_address'] . '/' . (($pagetitle) ? $pagetitle : $uuid); } - if(($parent_item) && ($parent_item['item_type'] == ITEM_TYPE_ARTICLE)) { + if (($parent_item) && ($parent_item['item_type'] == ITEM_TYPE_ARTICLE)) { $r = q("select v from iconfig where iconfig.cat = 'system' and iconfig.k = 'ARTICLE' and iconfig.iid = %d limit 1", intval($parent_item['id']) ); - if($r) { + if ($r) { $plink = z_root() . '/articles/' . $channel['channel_address'] . '/' . $r[0]['v']; } } - if ((! $plink) && ($item_thread_top)) { + if ((!$plink) && ($item_thread_top)) { // $plink = z_root() . '/channel/' . $channel['channel_address'] . '/?f=&mid=' . gen_link_id($mid); // $plink = substr($plink,0,190); $plink = $mid; @@ -1152,33 +1137,33 @@ class Item extends Controller { // A specific ACL over-rides public_policy completely - if(! empty_acl($datarray)) + if (!empty_acl($datarray)) $datarray['public_policy'] = ''; - if($iconfig) + if ($iconfig) $datarray['iconfig'] = $iconfig; // preview mode - prepare the body for display and send it via json - if($preview) { + if ($preview) { require_once('include/conversation.php'); - $datarray['owner'] = $owner_xchan; + $datarray['owner'] = $owner_xchan; $datarray['author'] = $observer; $datarray['attach'] = json_encode($datarray['attach']); - $o = conversation(array($datarray),'search',false,'preview'); - // logger('preview: ' . $o, LOGGER_DEBUG); - echo json_encode(array('preview' => $o)); + $o = conversation([$datarray], 'search', false, 'preview'); + // logger('preview: ' . $o, LOGGER_DEBUG); + echo json_encode(['preview' => $o]); killme(); } - if($orig_post) + if ($orig_post) $datarray['edit'] = true; // suppress duplicates, *unless* you're editing an existing post. This could get picked up // as a duplicate if you're editing it very soon after posting it initially and you edited // some attribute besides the content, such as title or categories. - if(feature_enabled($profile_uid,'suppress_duplicates') && (! $orig_post)) { + if (feature_enabled($profile_uid, 'suppress_duplicates') && (!$orig_post)) { $z = q("select created from item where uid = %d and created > %s - INTERVAL %s and body = '%s' limit 1", intval($profile_uid), @@ -1187,45 +1172,45 @@ class Item extends Controller { dbesc($body) ); - if($z) { + if ($z) { $datarray['cancel'] = 1; - notice( t('Duplicate post suppressed.') . EOL); + notice(t('Duplicate post suppressed.') . EOL); logger('Duplicate post. Faking plugin cancel.'); } } - call_hooks('post_local',$datarray); + call_hooks('post_local', $datarray); - if(x($datarray,'cancel')) { + if (x($datarray, 'cancel')) { logger('mod_item: post cancelled by plugin or duplicate suppressed.'); - if($return_path) + if ($return_path) goaway(z_root() . "/" . $return_path); - if($api_source) - return ( [ 'success' => false, 'message' => 'operation cancelled' ] ); - $json = array('cancel' => 1); + if ($api_source) + return (['success' => false, 'message' => 'operation cancelled']); + $json = ['cancel' => 1]; $json['reload'] = z_root() . '/' . $_REQUEST['jsreload']; echo json_encode($json); killme(); } - if(mb_strlen($datarray['title']) > 191) - $datarray['title'] = mb_substr($datarray['title'],0,191); + if (mb_strlen($datarray['title']) > 191) + $datarray['title'] = mb_substr($datarray['title'], 0, 191); - if($webpage) { - IConfig::Set($datarray,'system', webpage_to_namespace($webpage), + if ($webpage) { + IConfig::Set($datarray, 'system', webpage_to_namespace($webpage), (($pagetitle) ? $pagetitle : basename($datarray['mid'])), true); } - elseif($namespace) { - IConfig::Set($datarray,'system', $namespace, + elseif ($namespace) { + IConfig::Set($datarray, 'system', $namespace, (($remote_id) ? $remote_id : basename($datarray['mid'])), true); } - if($orig_post) { + if ($orig_post) { $datarray['id'] = $post_id; - $x = item_store_update($datarray,$execflag); + $x = item_store_update($datarray, $execflag); // We only need edit activities for other federated protocols // which do not support edits natively. While this does federate @@ -1239,82 +1224,80 @@ class Item extends Controller { // item_create_edit_activity($x); - if(! $parent) { + if (!$parent) { $r = q("select * from item where id = %d", intval($post_id) ); - if($r) { + if ($r) { xchan_query($r); $sync_item = fetch_post_tags($r); - Libsync::build_sync_packet($profile_uid,array('item' => array(encode_item($sync_item[0],true)))); + Libsync::build_sync_packet($profile_uid, ['item' => [encode_item($sync_item[0], true)]]); } } - if(! $nopush) - Master::Summon([ 'Notifier', 'edit_post', $post_id ]); + if (!$nopush) + Master::Summon(['Notifier', 'edit_post', $post_id]); - if($api_source) - return($x); + if ($api_source) + return ($x); - if((x($_REQUEST,'return')) && strlen($return_path)) { + if ((x($_REQUEST, 'return')) && strlen($return_path)) { logger('return: ' . $return_path); - goaway(z_root() . "/" . $return_path ); + goaway(z_root() . "/" . $return_path); } killme(); } - else - $post_id = 0; - $post = item_store($datarray,$execflag); + $post = item_store($datarray, $execflag); $post_id = $post['item_id']; $datarray = $post['item']; - if($post_id) { + if ($post_id) { logger('mod_item: saved item ' . $post_id); - if($parent) { + if ($parent) { // prevent conversations which you are involved from being expired - if(local_channel()) + if (local_channel()) retain_item($parent); // only send comment notification if this is a wall-to-wall comment, // otherwise it will happen during delivery - if(($datarray['owner_xchan'] != $datarray['author_xchan']) && (intval($parent_item['item_wall']))) { - Enotify::submit(array( - 'type' => NOTIFY_COMMENT, - 'from_xchan' => $datarray['author_xchan'], - 'to_xchan' => $datarray['owner_xchan'], - 'item' => $datarray, - 'link' => z_root() . '/display/' . gen_link_id($datarray['mid']), - 'verb' => ACTIVITY_POST, - 'otype' => 'item', - 'parent' => $parent, - 'parent_mid' => $parent_item['mid'] - )); + if (($datarray['owner_xchan'] != $datarray['author_xchan']) && (intval($parent_item['item_wall']))) { + Enotify::submit([ + 'type' => NOTIFY_COMMENT, + 'from_xchan' => $datarray['author_xchan'], + 'to_xchan' => $datarray['owner_xchan'], + 'item' => $datarray, + 'link' => z_root() . '/display/' . gen_link_id($datarray['mid']), + 'verb' => ACTIVITY_POST, + 'otype' => 'item', + 'parent' => $parent, + 'parent_mid' => $parent_item['mid'] + ]); } } else { $parent = $post_id; - if(($datarray['owner_xchan'] != $datarray['author_xchan']) && ($datarray['item_type'] == ITEM_TYPE_POST)) { - Enotify::submit(array( - 'type' => NOTIFY_WALL, - 'from_xchan' => $datarray['author_xchan'], - 'to_xchan' => $datarray['owner_xchan'], - 'item' => $datarray, - 'link' => z_root() . '/display/' . gen_link_id($datarray['mid']), - 'verb' => ACTIVITY_POST, - 'otype' => 'item' - )); + if (($datarray['owner_xchan'] != $datarray['author_xchan']) && ($datarray['item_type'] == ITEM_TYPE_POST)) { + Enotify::submit([ + 'type' => NOTIFY_WALL, + 'from_xchan' => $datarray['author_xchan'], + 'to_xchan' => $datarray['owner_xchan'], + 'item' => $datarray, + 'link' => z_root() . '/display/' . gen_link_id($datarray['mid']), + 'verb' => ACTIVITY_POST, + 'otype' => 'item' + ]); } - if($uid && $uid == $profile_uid && (is_item_normal($datarray))) { + if ($uid && $uid == $profile_uid && (is_item_normal($datarray))) { q("update channel set channel_lastpost = '%s' where channel_id = %d", dbesc(datetime_convert()), intval($uid) @@ -1326,7 +1309,7 @@ class Item extends Controller { // This way we don't see every picture in your new photo album posted to your wall at once. // They will show up as people comment on them. - if(intval($parent_item['item_hidden'])) { + if (intval($parent_item['item_hidden'])) { $r = q("UPDATE item SET item_hidden = 0 WHERE id = %d", intval($parent_item['id']) ); @@ -1334,22 +1317,22 @@ class Item extends Controller { } else { logger('mod_item: unable to retrieve post that was just stored.'); - notice( t('System error. Post not saved.') . EOL); - if($return_path) - goaway(z_root() . "/" . $return_path ); - if($api_source) - return ( [ 'success' => false, 'message' => 'system error' ] ); + notice(t('System error. Post not saved.') . EOL); + if ($return_path) + goaway(z_root() . "/" . $return_path); + if ($api_source) + return (['success' => false, 'message' => 'system error']); killme(); } - if($parent || $datarray['item_private'] == 1) { + if ($parent || $datarray['item_private'] == 1) { $r = q("select * from item where id = %d", intval($post_id) ); - if($r) { + if ($r) { xchan_query($r); $sync_item = fetch_post_tags($r); - Libsync::build_sync_packet($profile_uid,array('item' => array(encode_item($sync_item[0],true)))); + Libsync::build_sync_packet($profile_uid, ['item' => [encode_item($sync_item[0], true)]]); } } @@ -1362,46 +1345,46 @@ class Item extends Controller { $nopush = false; } - if(! $nopush) - Master::Summon([ 'Notifier', $notify_type, $post_id ]); + if (!$nopush) + Master::Summon(['Notifier', $notify_type, $post_id]); logger('post_complete'); - if($moderated) { + if ($moderated) { info(t('Your comment is awaiting approval.') . EOL); } // figure out how to return, depending on from whence we came - if($api_source) + if ($api_source) return $post; - if($return_path) { - if($return_path === 'hq') { + if ($return_path) { + if ($return_path === 'hq') { goaway(z_root() . '/hq/' . gen_link_id($datarray['mid'])); } goaway(z_root() . "/" . $return_path); } - if($mode === 'channel') + if ($mode === 'channel') profile_load($channel['channel_address']); - $item[] = $datarray; - $item[0]['owner'] = $owner_xchan; + $item[] = $datarray; + $item[0]['owner'] = $owner_xchan; $item[0]['author'] = $observer; $item[0]['attach'] = $datarray['attach']; $json = [ 'success' => 1, - 'id' => $post_id, - 'html' => conversation($item,$mode,true,'r_preview'), + 'id' => $post_id, + 'html' => conversation($item, $mode, true, 'r_preview'), ]; - if(x($_REQUEST,'jsreload') && strlen($_REQUEST['jsreload'])) + if (x($_REQUEST, 'jsreload') && strlen($_REQUEST['jsreload'])) $json['reload'] = z_root() . '/' . $_REQUEST['jsreload']; - logger('post_json: ' . print_r($json,true), LOGGER_DEBUG); + logger('post_json: ' . print_r($json, true), LOGGER_DEBUG); echo json_encode($json); killme(); @@ -1411,10 +1394,10 @@ class Item extends Controller { function get() { - if((! local_channel()) && (! remote_channel())) + if ((!local_channel()) && (!remote_channel())) return; - if((argc() == 3) && (argv(1) === 'drop') && intval(argv(2))) { + if ((argc() == 3) && (argv(1) === 'drop') && intval(argv(2))) { require_once('include/items.php'); @@ -1423,16 +1406,16 @@ class Item extends Controller { intval(argv(2)) ); - if($i) { - $can_delete = false; + if ($i) { + $can_delete = false; $local_delete = false; - if(local_channel() && local_channel() == $i[0]['uid']) { + if (local_channel() && local_channel() == $i[0]['uid']) { $local_delete = true; } $ob_hash = get_observer_hash(); - if($ob_hash && ($ob_hash === $i[0]['author_xchan'] || $ob_hash === $i[0]['owner_xchan'] || $ob_hash === $i[0]['source_xchan'])) { + if ($ob_hash && ($ob_hash === $i[0]['author_xchan'] || $ob_hash === $i[0]['owner_xchan'] || $ob_hash === $i[0]['source_xchan'])) { $can_delete = true; } @@ -1440,15 +1423,15 @@ class Item extends Controller { // If the item originated on this site+channel the deletion will propagate downstream. // Otherwise just the local copy is removed. - if(is_site_admin()) { + if (is_site_admin()) { $local_delete = true; - if(intval($i[0]['item_origin'])) + if (intval($i[0]['item_origin'])) $can_delete = true; } - if(! ($can_delete || $local_delete)) { - notice( t('Permission denied.') . EOL); + if (!($can_delete || $local_delete)) { + notice(t('Permission denied.') . EOL); return; } @@ -1457,35 +1440,34 @@ class Item extends Controller { $complex = false; - if(intval($i[0]['item_type']) || ($local_delete && (! $can_delete))) { + if (intval($i[0]['item_type']) || ($local_delete && (!$can_delete))) { drop_item($i[0]['id']); } else { // complex deletion that needs to propagate and be performed in phases - drop_item($i[0]['id'],true,DROPITEM_PHASE1); + drop_item($i[0]['id'], true, DROPITEM_PHASE1); $complex = true; } $r = q("select * from item where id = %d", intval($i[0]['id']) ); - if($r) { + if ($r) { xchan_query($r); $sync_item = fetch_post_tags($r); - Libsync::build_sync_packet($i[0]['uid'],array('item' => array(encode_item($sync_item[0],true)))); + Libsync::build_sync_packet($i[0]['uid'], ['item' => [encode_item($sync_item[0], true)]]); } - if($complex) { - tag_deliver($i[0]['uid'],$i[0]['id']); + if ($complex) { + tag_deliver($i[0]['uid'], $i[0]['id']); } } } } - - function item_check_service_class($channel_id,$iswebpage) { - $ret = array('success' => false, 'message' => ''); + function item_check_service_class($channel_id, $iswebpage) { + $ret = ['success' => false, 'message' => '']; if ($iswebpage) { $r = q("select count(i.id) as total from item i @@ -1501,23 +1483,23 @@ class Item extends Controller { ); } - if(! $r) { + if (!$r) { $ret['message'] = t('Unable to obtain post information from database.'); return $ret; } if (!$iswebpage) { - $max = engr_units_to_bytes(service_class_fetch($channel_id,'total_items')); - if(! service_class_allows($channel_id,'total_items',$r[0]['total'])) { - $result['message'] .= upgrade_message() . sprintf( t('You have reached your limit of %1$.0f top level posts.'),$max); - return $result; + $max = engr_units_to_bytes(service_class_fetch($channel_id, 'total_items')); + if (!service_class_allows($channel_id, 'total_items', $r[0]['total'])) { + $ret['message'] .= upgrade_message() . sprintf(t('You have reached your limit of %1$.0f top level posts.'), $max); + return $ret; } } else { - $max = engr_units_to_bytes(service_class_fetch($channel_id,'total_pages')); - if(! service_class_allows($channel_id,'total_pages',$r[0]['total'])) { - $result['message'] .= upgrade_message() . sprintf( t('You have reached your limit of %1$.0f webpages.'),$max); - return $result; + $max = engr_units_to_bytes(service_class_fetch($channel_id, 'total_pages')); + if (!service_class_allows($channel_id, 'total_pages', $r[0]['total'])) { + $ret['message'] .= upgrade_message() . sprintf(t('You have reached your limit of %1$.0f webpages.'), $max); + return $ret; } } @@ -1525,51 +1507,51 @@ class Item extends Controller { return $ret; } - function extract_bb_poll_data(&$body,$item) { + function extract_bb_poll_data(&$body, $item) { $multiple = false; - if (strpos($body,'[/question]') === false && strpos($body,'[/answer]') === false) { + if (strpos($body, '[/question]') === false && strpos($body, '[/answer]') === false) { return false; } - if (strpos($body,'[nobb]') !== false) { + if (strpos($body, '[nobb]') !== false) { return false; } - $obj = []; - $ptr = []; - $matches = null; + $obj = []; + $ptr = []; + $matches = null; $obj['type'] = 'Question'; - if (preg_match_all('/\[answer\](.*?)\[\/answer\]/ism',$body,$matches,PREG_SET_ORDER)) { + if (preg_match_all('/\[answer\](.*?)\[\/answer\]/ism', $body, $matches, PREG_SET_ORDER)) { foreach ($matches as $match) { - $ptr[] = [ 'name' => $match[1], 'type' => 'Note', 'replies' => [ 'type' => 'Collection', 'totalItems' => 0 ]]; - $body = str_replace('[answer]' . $match[1] . '[/answer]', EMPTY_STR, $body); + $ptr[] = ['name' => $match[1], 'type' => 'Note', 'replies' => ['type' => 'Collection', 'totalItems' => 0]]; + $body = str_replace('[answer]' . $match[1] . '[/answer]', EMPTY_STR, $body); } } $matches = null; - if (preg_match('/\[question\](.*?)\[\/question\]/ism',$body,$matches)) { + if (preg_match('/\[question\](.*?)\[\/question\]/ism', $body, $matches)) { $obj['content'] = bbcode($matches[1]); - $body = str_replace('[question]' . $matches[1] . '[/question]', $matches[1], $body); - $obj['oneOf'] = $ptr; + $body = str_replace('[question]' . $matches[1] . '[/question]', $matches[1], $body); + $obj['oneOf'] = $ptr; } $matches = null; - if (preg_match('/\[question=multiple\](.*?)\[\/question\]/ism',$body,$matches)) { + if (preg_match('/\[question=multiple\](.*?)\[\/question\]/ism', $body, $matches)) { $obj['content'] = bbcode($matches[1]); - $body = str_replace('[question=multiple]' . $matches[1] . '[/question]', $matches[1], $body); - $obj['anyOf'] = $ptr; + $body = str_replace('[question=multiple]' . $matches[1] . '[/question]', $matches[1], $body); + $obj['anyOf'] = $ptr; } $matches = null; - if (preg_match('/\[ends\](.*?)\[\/ends\]/ism',$body,$matches)) { - $obj['endTime'] = datetime_convert(date_default_timezone_get(),'UTC', $matches[1],ATOM_TIME); - $body = str_replace('[ends]' . $matches[1] . '[/ends]', EMPTY_STR, $body); + if (preg_match('/\[ends\](.*?)\[\/ends\]/ism', $body, $matches)) { + $obj['endTime'] = datetime_convert(date_default_timezone_get(), 'UTC', $matches[1], ATOM_TIME); + $body = str_replace('[ends]' . $matches[1] . '[/ends]', EMPTY_STR, $body); } @@ -1577,7 +1559,7 @@ class Item extends Controller { $obj['to'] = Activity::map_acl($item); } else { - $obj['to'] = [ ACTIVITY_PUBLIC_INBOX ]; + $obj['to'] = [ACTIVITY_PUBLIC_INBOX]; } return $obj; @@ -1587,23 +1569,23 @@ class Item extends Controller { function extract_poll_data($poll, $item) { - $multiple = intval($poll['multiple_answers']); + $multiple = intval($poll['multiple_answers']); $expire_value = intval($poll['expire_value']); - $expire_unit = $poll['expire_unit']; - $question = $poll['question']; - $answers = $poll['answers']; + $expire_unit = $poll['expire_unit']; + $question = $poll['question']; + $answers = $poll['answers']; - $obj = []; - $ptr = []; - $obj['type'] = 'Question'; + $obj = []; + $ptr = []; + $obj['type'] = 'Question'; $obj['content'] = bbcode($question); - foreach($answers as $answer) { - if(trim($answer)) - $ptr[] = [ 'name' => escape_tags($answer), 'type' => 'Note', 'replies' => [ 'type' => 'Collection', 'totalItems' => 0 ]]; + foreach ($answers as $answer) { + if (trim($answer)) + $ptr[] = ['name' => escape_tags($answer), 'type' => 'Note', 'replies' => ['type' => 'Collection', 'totalItems' => 0]]; } - if($multiple) { + if ($multiple) { $obj['anyOf'] = $ptr; } else { @@ -1616,7 +1598,7 @@ class Item extends Controller { $obj['to'] = Activity::map_acl($item); } else { - $obj['to'] = [ ACTIVITY_PUBLIC_INBOX ]; + $obj['to'] = [ACTIVITY_PUBLIC_INBOX]; } return $obj; @@ -1624,5 +1606,4 @@ class Item extends Controller { } - } diff --git a/Zotlabs/Module/Linkinfo.php b/Zotlabs/Module/Linkinfo.php index a05575cb6..038c739d5 100644 --- a/Zotlabs/Module/Linkinfo.php +++ b/Zotlabs/Module/Linkinfo.php @@ -29,6 +29,9 @@ class Linkinfo extends \Zotlabs\Web\Controller { if((substr($url,0,1) != '/') && (substr($url,0,4) != 'http')) $url = 'http://' . $url; + $x = parse_url($url); + if ($x) + $url = str_replace($x['host'], punify($x['host']), $url); if($_GET['title']) $title = strip_tags(trim($_GET['title'])); diff --git a/Zotlabs/Module/Locs.php b/Zotlabs/Module/Locs.php index 59b872982..1ece47231 100644 --- a/Zotlabs/Module/Locs.php +++ b/Zotlabs/Module/Locs.php @@ -116,11 +116,6 @@ class Locs extends Controller { return; } - for($x = 0; $x < count($r); $x ++) { - $r[$x]['primary'] = (intval($r[$x]['hubloc_primary']) ? true : false); - $r[$x]['deleted'] = (intval($r[$x]['hubloc_deleted']) ? true : false); - } - $o = replace_macros(get_markup_template('locmanage.tpl'), array( '$header' => t('Manage Channel Locations'), '$loc' => t('Location'), @@ -132,7 +127,8 @@ class Locs extends Controller { '$sync_text' => t('Please wait several minutes between consecutive operations.'), '$drop_text' => t('When possible, drop a location by logging into that website/hub and removing your channel.'), '$last_resort' => t('Use this form to drop the location if the hub is no longer operating.'), - '$hubs' => $r + '$hubs' => $r, + '$base_url' => z_root() )); return $o; diff --git a/Zotlabs/Module/Manage.php b/Zotlabs/Module/Manage.php index e7d9d5cba..3f168c15d 100644 --- a/Zotlabs/Module/Manage.php +++ b/Zotlabs/Module/Manage.php @@ -61,7 +61,7 @@ class Manage extends \Zotlabs\Web\Controller { $channels[$x]['default'] = (($channels[$x]['channel_id'] == $account['account_default_channel']) ? "1" : ''); $channels[$x]['default_links'] = '1'; - + /* this is not currently implemented in the UI and probably should not (performance) $c = q("SELECT id, item_wall FROM item WHERE item_unseen = 1 and uid = %d " . item_normal(), intval($channels[$x]['channel_id']) @@ -75,7 +75,7 @@ class Manage extends \Zotlabs\Web\Controller { $channels[$x]['network'] ++; } } - + */ $intr = q("SELECT COUNT(abook.abook_id) AS total FROM abook left join xchan on abook.abook_xchan = xchan.xchan_hash where abook_channel = %d and abook_pending = 1 and abook_self = 0 and abook_ignored = 0 and xchan_deleted = 0 and xchan_orphan = 0 ", intval($channels[$x]['channel_id']) @@ -84,6 +84,7 @@ class Manage extends \Zotlabs\Web\Controller { if($intr) $channels[$x]['intros'] = intval($intr[0]['total']); + /* this is not currently implemented in the UI and probably should not (performance) $events = q("SELECT etype, dtstart, adjust FROM event WHERE event.uid = %d AND dtstart < '%s' AND dtstart > '%s' and dismissed = 0 ORDER BY dtstart ASC ", @@ -116,6 +117,7 @@ class Manage extends \Zotlabs\Web\Controller { } } } + */ } } diff --git a/Zotlabs/Module/Manifest.php b/Zotlabs/Module/Manifest.php index 6fe468a14..859efe737 100644 --- a/Zotlabs/Module/Manifest.php +++ b/Zotlabs/Module/Manifest.php @@ -23,10 +23,10 @@ class Manifest extends Controller { [ 'src' => '/images/app/hz-512.png', 'sizes' => '512x512', 'type' => 'image/png' ], [ 'src' => '/images/app/hz.svg', 'sizes' => '64x64', 'type' => 'image/xml+svg' ] ], + 'theme_color' => '#343a40', 'scope' => '/', 'start_url' => z_root(), 'display' => 'standalone', - 'orientation' => 'any', 'share_target' => [ 'action' => '/rpost', 'method' => 'POST', diff --git a/Zotlabs/Module/New_channel.php b/Zotlabs/Module/New_channel.php index 84d492f8f..3b0b35258 100644 --- a/Zotlabs/Module/New_channel.php +++ b/Zotlabs/Module/New_channel.php @@ -1,6 +1,8 @@ <?php namespace Zotlabs\Module; +use URLify; + require_once('include/channel.php'); require_once('include/permissions.php'); @@ -13,7 +15,6 @@ class New_channel extends \Zotlabs\Web\Controller { $cmd = ((argc() > 1) ? argv(1) : ''); if($cmd === 'autofill.json') { - require_once('library/urlify/URLify.php'); $result = array('error' => false, 'message' => ''); $n = trim($_REQUEST['name']); @@ -24,7 +25,7 @@ class New_channel extends \Zotlabs\Web\Controller { } if((! $x) || strlen($x) > 64) - $x = strtolower(\URLify::transliterate($n)); + $x = strtolower(URLify::transliterate($n)); $test = array(); @@ -46,7 +47,6 @@ class New_channel extends \Zotlabs\Web\Controller { } if($cmd === 'checkaddr.json') { - require_once('library/urlify/URLify.php'); $result = array('error' => false, 'message' => ''); $n = trim($_REQUEST['nick']); if(! $n) { @@ -60,7 +60,7 @@ class New_channel extends \Zotlabs\Web\Controller { } if((! $x) || strlen($x) > 64) - $x = strtolower(\URLify::transliterate($n)); + $x = strtolower(URLify::transliterate($n)); $test = array(); diff --git a/Zotlabs/Module/Notes.php b/Zotlabs/Module/Notes.php index 6e8e03f20..57b8f30db 100644 --- a/Zotlabs/Module/Notes.php +++ b/Zotlabs/Module/Notes.php @@ -19,7 +19,12 @@ class Notes extends Controller { if(! Apps::system_app_installed(local_channel(), 'Notes')) return EMPTY_STR; - $ret = array('success' => true); + $ret = [ + 'success' => false, + 'html' => '' + ]; + + if(array_key_exists('note_text',$_REQUEST)) { $body = escape_tags($_REQUEST['note_text']); @@ -33,6 +38,10 @@ class Notes extends Controller { set_pconfig(local_channel(),'notes','text.bak',$old_text); } set_pconfig(local_channel(),'notes','text',$body); + + $ret['html'] = bbcode($body); + $ret['success'] = true; + } // push updates to channel clones diff --git a/Zotlabs/Module/Notify.php b/Zotlabs/Module/Notify.php index 5bfcec4f7..4cbcfee05 100644 --- a/Zotlabs/Module/Notify.php +++ b/Zotlabs/Module/Notify.php @@ -1,19 +1,35 @@ <?php namespace Zotlabs\Module; +use \Zotlabs\Lib\PConfig; +use \Zotlabs\Web\Controller; - -class Notify extends \Zotlabs\Web\Controller { +class Notify extends Controller { function init() { if(! local_channel()) return; if($_REQUEST['notify_id']) { - q("update notify set seen = 1 where id = %d and uid = %d", - intval($_REQUEST['notify_id']), - intval(local_channel()) - ); + $update_notices_per_parent = PConfig::Get(local_channel(), 'system', 'update_notices_per_parent', 1); + + if($update_notices_per_parent) { + $r = q("SELECT parent FROM notify WHERE id = %d AND uid = %d", + intval($_REQUEST['notify_id']), + intval(local_channel()) + ); + q("update notify set seen = 1 where parent = '%s' and uid = %d", + dbesc($r[0]['parent']), + intval(local_channel()) + ); + } + else { + q("update notify set seen = 1 where id = %d and uid = %d", + intval($_REQUEST['notify_id']), + intval(local_channel()) + ); + } + killme(); } diff --git a/Zotlabs/Module/Outbox.php b/Zotlabs/Module/Outbox.php new file mode 100644 index 000000000..503b464d1 --- /dev/null +++ b/Zotlabs/Module/Outbox.php @@ -0,0 +1,124 @@ +<?php + +namespace Zotlabs\Module; + +use App; +use Zotlabs\Lib\Activity; +use Zotlabs\Lib\ActivityStreams; +use Zotlabs\Lib\Config; +use Zotlabs\Lib\ThreadListener; +use Zotlabs\Web\Controller; +use Zotlabs\Web\HTTPSig; + +class Outbox extends Controller { + + function init() { + if (ActivityStreams::is_as_request()) { + + if (observer_prohibited(true)) { + killme(); + } + + $channel = channelx_by_nick(argv(1)); + if (!$channel) { + killme(); + } + + if (intval($channel['channel_system'])) { + killme(); + } + + $sigdata = HTTPSig::verify(($_SERVER['REQUEST_METHOD'] === 'POST') ? file_get_contents('php://input') : EMPTY_STR); + if ($sigdata['portable_id'] && $sigdata['header_valid']) { + $portable_id = $sigdata['portable_id']; + if (!check_channelallowed($portable_id)) { + http_status_exit(403, 'Permission denied'); + } + if (!check_siteallowed($sigdata['signer'])) { + http_status_exit(403, 'Permission denied'); + } + observer_auth($portable_id); + } + elseif (Config::get('system', 'require_authenticated_fetch', false)) { + http_status_exit(403, 'Permission denied'); + } + + $observer_hash = get_observer_hash(); + + $params = []; + + $params['begin'] = ((x($_REQUEST, 'date_begin')) ? $_REQUEST['date_begin'] : NULL_DATE); + $params['end'] = ((x($_REQUEST, 'date_end')) ? $_REQUEST['date_end'] : ''); + $params['type'] = 'json'; + $params['pages'] = ((x($_REQUEST, 'pages')) ? intval($_REQUEST['pages']) : 0); + $params['top'] = ((x($_REQUEST, 'top')) ? intval($_REQUEST['top']) : 0); + $params['direction'] = ((x($_REQUEST, 'direction')) ? dbesc($_REQUEST['direction']) : 'desc'); // unimplemented + $params['cat'] = ((x($_REQUEST, 'cat')) ? escape_tags($_REQUEST['cat']) : ''); + $params['compat'] = 1; + + $total = items_fetch( + [ + 'total' => true, + 'wall' => 1, + 'datequery' => $params['end'], + 'datequery2' => $params['begin'], + 'direction' => dbesc($params['direction']), + 'pages' => $params['pages'], + 'order' => dbesc('post'), + 'top' => $params['top'], + 'cat' => $params['cat'], + 'compat' => $params['compat'] + ], $channel, $observer_hash, CLIENT_MODE_NORMAL, App::$module + ); + + if ($total) { + App::set_pager_total($total); + App::set_pager_itemspage(30); + } + + if (App::$pager['unset'] && $total > 30) { + $ret = Activity::paged_collection_init($total, App::$query_string); + } + else { + + $items = items_fetch( + [ + 'wall' => 1, + 'datequery' => $params['end'], + 'datequery2' => $params['begin'], + 'records' => intval(App::$pager['itemspage']), + 'start' => intval(App::$pager['start']), + 'direction' => dbesc($params['direction']), + 'pages' => $params['pages'], + 'order' => dbesc('post'), + 'top' => $params['top'], + 'cat' => $params['cat'], + 'compat' => $params['compat'] + ], $channel, $observer_hash, CLIENT_MODE_NORMAL, App::$module + ); + + if ($items && $observer_hash) { + + // check to see if this observer is a connection. If not, register any items + // belonging to this channel for notification of deletion/expiration + + $x = q("select abook_id from abook where abook_channel = %d and abook_xchan = '%s'", + intval($channel['channel_id']), + dbesc($observer_hash) + ); + if (!$x) { + foreach ($items as $item) { + if (strpos($item['mid'], z_root()) === 0) { + ThreadListener::store($item['mid'], $observer_hash); + } + } + } + } + + $ret = Activity::encode_item_collection($items, App::$query_string, 'OrderedCollection', $total); + } + + as_return_and_die($ret, $channel); + } + } +} diff --git a/Zotlabs/Module/Photo.php b/Zotlabs/Module/Photo.php index 87697f5a7..10d2e8f47 100644 --- a/Zotlabs/Module/Photo.php +++ b/Zotlabs/Module/Photo.php @@ -3,6 +3,12 @@ namespace Zotlabs\Module; +use Zotlabs\Lib\Activity; +use Zotlabs\Lib\ActivityStreams; +use Zotlabs\Web\HTTPSig; +use Zotlabs\Lib\Config; + + require_once('include/security.php'); require_once('include/attach.php'); require_once('include/photo/photo_driver.php'); @@ -11,6 +17,48 @@ class Photo extends \Zotlabs\Web\Controller { function init() { + if (ActivityStreams::is_as_request()) { + + $sigdata = HTTPSig::verify(EMPTY_STR); + if ($sigdata['portable_id'] && $sigdata['header_valid']) { + $portable_id = $sigdata['portable_id']; + if (! check_channelallowed($portable_id)) { + http_status_exit(403, 'Permission denied'); + } + if (! check_siteallowed($sigdata['signer'])) { + http_status_exit(403, 'Permission denied'); + } + observer_auth($portable_id); + } + elseif (Config::get('system','require_authenticated_fetch',false)) { + http_status_exit(403,'Permission denied'); + } + + $observer_xchan = get_observer_hash(); + $allowed = false; + + $bear = Activity::token_from_request(); + if ($bear) { + logger('bear: ' . $bear, LOGGER_DEBUG); + } + + $r = q("select * from item where resource_type = 'photo' and resource_id = '%s' limit 1", + dbesc(argv(1)) + ); + if ($r) { + $allowed = attach_can_view($r[0]['uid'],$observer_xchan,argv(1)/*,$bear*/); + } + if (! $allowed) { + http_status_exit(404,'Permission denied.'); + } + $channel = channelx_by_n($r[0]['uid']); + + $obj = json_decode($r[0]['obj'],true); + + as_return_and_die($obj,$channel); + + } + $streaming = null; $channel = null; $person = 0; @@ -33,19 +81,19 @@ class Photo extends \Zotlabs\Web\Controller { $cache_mode = [ 'on' => false, 'age' => 86400, 'exp' => true, 'leak' => false ]; call_hooks('cache_mode_hook', $cache_mode); - + $observer_xchan = get_observer_hash(); $cachecontrol = ', no-cache'; if(isset($type)) { - + /** * Profile photos - Access controls on default profile photos are not honoured since they need to be exchanged with remote sites. - * + * */ - + $default = get_default_profile_photo(); - + if($type === 'profile') { switch($res) { case 'm': @@ -62,9 +110,9 @@ class Photo extends \Zotlabs\Web\Controller { break; } } - + $uid = $person; - + $data = ''; if ($uid > 0) { @@ -81,13 +129,13 @@ class Photo extends \Zotlabs\Web\Controller { else $data = dbunescbin($r[0]['content']); } - + if(! $data) { $d = [ 'imgscale' => $resolution, 'channel_id' => $uid, 'default' => $default, 'data' => '', 'mimetype' => '' ]; call_hooks('get_profile_photo',$d); - + $resolution = $d['imgscale']; - $uid = $d['channel_id']; + $uid = $d['channel_id']; $default = $d['default']; $data = $d['data']; $mimetype = $d['mimetype']; @@ -105,11 +153,11 @@ class Photo extends \Zotlabs\Web\Controller { $cachecontrol .= ', must-revalidate'; } else { - + /** * Other photos */ - + /* Check for a cookie to indicate display pixel density, in order to detect high-resolution displays. This procedure was derived from the "Retina Images" by Jeremey Worboys, used in accordance with the Creative Commons Attribution 3.0 Unported License. @@ -127,12 +175,12 @@ class Photo extends \Zotlabs\Web\Controller { // $prvcachecontrol = 'no-cache'; $status = 'no cookie'; } - + $resolution = 0; - + if(strpos($photo,'.') !== false) $photo = substr($photo,0,strpos($photo,'.')); - + if(substr($photo,-2,1) == '-') { $resolution = intval(substr($photo,-1,1)); $photo = substr($photo,0,-2); @@ -140,7 +188,7 @@ class Photo extends \Zotlabs\Web\Controller { if ($resolution == 2 && ($cookie_value > 1)) $resolution = 1; } - + $r = q("SELECT * FROM photo WHERE resource_id = '%s' AND imgscale = %d LIMIT 1", dbesc($photo), intval($resolution) @@ -151,7 +199,7 @@ class Photo extends \Zotlabs\Web\Controller { $u = intval($r[0]['photo_usage']); if($u) { $allowed = 1; - if($u === PHOTO_COVER) + if($u === PHOTO_COVER) if($resolution < PHOTO_RES_COVER_1200) $allowed = (-1); if($u === PHOTO_PROFILE) @@ -184,9 +232,9 @@ class Photo extends \Zotlabs\Web\Controller { dbesc($photo), intval($resolution) ); - + $exists = (($e) ? true : false); - + if($exists && $allowed) { $expires = strtotime($e[0]['expires'] . 'Z'); $data = dbunescbin($e[0]['content']); @@ -209,16 +257,16 @@ class Photo extends \Zotlabs\Web\Controller { } } - } + } else http_status_exit(404,'not found'); } if(! $data) killme(); - + $etag = '"' . md5($data . $modified) . '"'; - + if($modified == 0) $modified = time(); @@ -241,39 +289,39 @@ class Photo extends \Zotlabs\Web\Controller { } if(isset($prvcachecontrol)) { - + // it is a private photo that they have no permission to view. // tell the browser not to cache it, in case they authenticate // and subsequently have permission to see it - + header("Cache-Control: " . $prvcachecontrol); - + } else { // The photo cache default is 1 day to provide a privacy trade-off, - // as somebody reducing photo permissions on a photo that is already + // as somebody reducing photo permissions on a photo that is already // "in the wild" won't be able to stop the photo from being viewed // for this amount amount of time once it is in the browser cache. - // The privacy expectations of your site members and their perception + // The privacy expectations of your site members and their perception // of privacy where it affects the entire project may be affected. - // This has performance considerations but we highly recommend you - // leave it alone. - + // This has performance considerations but we highly recommend you + // leave it alone. + $maxage = $cache_mode['age']; if($cache_mode['exp'] || (! isset($expires)) || (isset($expires) && $expires - 60 < time())) $expires = time() + $maxage; else $maxage = $expires - time(); - + header("Expires: " . gmdate("D, d M Y H:i:s", $expires) . " GMT"); - // set CDN/Infrastructure caching much lower than maxage + // set CDN/Infrastructure caching much lower than maxage // in the event that infrastructure caching is present. $smaxage = intval($maxage/12); header("Cache-Control: s-maxage=" . $smaxage . ", max-age=" . $maxage . $cachecontrol); - + } header("Content-type: " . $mimetype); @@ -281,7 +329,7 @@ class Photo extends \Zotlabs\Web\Controller { header("ETag: " . $etag); header("Content-Length: " . (isset($filesize) ? $filesize : strlen($data))); - // If it's a file resource, stream it. + // If it's a file resource, stream it. if($streaming) { if(strpos($streaming,'store') !== false) $istream = fopen($streaming,'rb'); @@ -300,5 +348,5 @@ class Photo extends \Zotlabs\Web\Controller { killme(); } - + } diff --git a/Zotlabs/Module/Photos.php b/Zotlabs/Module/Photos.php index 3aad70d18..45fe3d9e0 100644 --- a/Zotlabs/Module/Photos.php +++ b/Zotlabs/Module/Photos.php @@ -171,6 +171,7 @@ class Photos extends \Zotlabs\Web\Controller { } goaway(z_root() . '/photos/' . \App::$data['channel']['channel_address']); + } if((argc() > 2) && (x($_REQUEST,'delete')) && ($_REQUEST['delete'] === t('Delete Photo'))) { @@ -501,6 +502,9 @@ class Photos extends \Zotlabs\Web\Controller { goaway(z_root() . '/photos/' . \App::$data['channel']['channel_address']); } + if(is_ajax()) + killme(); + goaway(z_root() . '/photos/' . \App::$data['channel']['channel_address'] . '/album/' . $r['data']['folder']); } @@ -709,13 +713,15 @@ class Photos extends \Zotlabs\Web\Controller { ]); if($x = photos_album_exists($owner_uid, get_observer_hash(), $datum)) { - \App::set_pager_itemspage(30); $album = $x['display_path']; } else { - goaway(z_root() . '/photos/' . \App::$data['channel']['channel_address']); + $album = '/'; + //goaway(z_root() . '/photos/' . \App::$data['channel']['channel_address']); } + \App::set_pager_itemspage(30); + if($_GET['order'] === 'posted') $order = 'ASC'; else diff --git a/Zotlabs/Module/Profiles.php b/Zotlabs/Module/Profiles.php index 631a41ddc..73bae45e8 100644 --- a/Zotlabs/Module/Profiles.php +++ b/Zotlabs/Module/Profiles.php @@ -595,30 +595,31 @@ class Profiles extends \Zotlabs\Web\Controller { if($r) info( t('Profile updated.') . EOL); - $r = q("select * from profile where id = %d and uid = %d limit 1", - intval(argv(1)), - intval(local_channel()) - ); - if($r) { - Libsync::build_sync_packet(local_channel(),array('profile' => $r)); - } - $channel = \App::get_channel(); if($namechanged && $is_default) { - $r = q("UPDATE xchan SET xchan_name = '%s', xchan_name_date = '%s' WHERE xchan_hash = '%s'", + q("UPDATE xchan SET xchan_name = '%s', xchan_name_date = '%s' WHERE xchan_hash = '%s'", dbesc($name), dbesc(datetime_convert()), dbesc($channel['xchan_hash']) ); - $r = q("UPDATE channel SET channel_name = '%s' WHERE channel_hash = '%s'", + q("UPDATE channel SET channel_name = '%s' WHERE channel_hash = '%s'", dbesc($name), dbesc($channel['xchan_hash']) ); } + $r = q("select * from profile where id = %d and uid = %d limit 1", + intval(argv(1)), + intval(local_channel()) + ); + + if($r) { + Libsync::build_sync_packet(local_channel(), ['profile' => $r]); + } + if($is_default) { - // reload the info for the sidebar widget - why does this not work? + // reload the info for the sidebar widget profile_load($channel['channel_address']); \Zotlabs\Daemon\Master::Summon(array('Directory',local_channel())); } diff --git a/Zotlabs/Module/Regate.php b/Zotlabs/Module/Regate.php index 379195461..462c997ff 100644 --- a/Zotlabs/Module/Regate.php +++ b/Zotlabs/Module/Regate.php @@ -2,6 +2,9 @@ namespace Zotlabs\Module; +use Zotlabs\Lib\Connect; +use Zotlabs\Daemon\Master; + require_once('include/security.php'); /** @@ -184,7 +187,24 @@ class Regate extends \Zotlabs\Web\Controller { $new_channel = auto_channel_create($cra['account']['account_id']); if($new_channel['success']) { + $channel_id = $new_channel['channel']['channel_id']; + + // If we have an inviter, connect. + if ($didx === 'i' && intval($r['reg_byc'])) { + $invite_channel = channelx_by_n($r['reg_byc']); + if ($invite_channel) { + $f = Connect::connect($new_channel['channel'], $invite_channel['xchan_addr']); + if ($f['success']) { + $can_view_stream = their_perms_contains($channel_id, $f['abook']['abook_xchan'], 'view_stream'); + // If we can view their stream, pull in some posts + if ($can_view_stream) { + Master::Summon(['Onepoll', $f['abook']['abook_id']]); + } + } + } + } + change_channel($channel_id); $nextpage = 'profiles/' . $channel_id; $msg_code = 'ZAR1239I'; diff --git a/Zotlabs/Module/Search.php b/Zotlabs/Module/Search.php index 06a761998..5db0ce423 100644 --- a/Zotlabs/Module/Search.php +++ b/Zotlabs/Module/Search.php @@ -3,6 +3,7 @@ namespace Zotlabs\Module; use App; +use Zotlabs\Lib\Libzot; use Zotlabs\Lib\Activity; use Zotlabs\Lib\ActivityStreams; use Zotlabs\Web\Controller; @@ -57,26 +58,15 @@ class Search extends Controller { $o .= search($search, 'search-box', '/search', ((local_channel()) ? true : false)); if (local_channel() && strpos($search, 'https://') === 0 && !$update && !$load) { - $j = Activity::fetch(punify($search), App::get_channel()); - if ($j) { - $AS = new ActivityStreams($j); - if ($AS->is_valid()) { - // check if is_an_actor, otherwise import activity - if (is_array($AS->obj) && !ActivityStreams::is_an_actor($AS->obj)) { - $item = Activity::decode_note($AS); - if ($item) { - logger('parsed_item: ' . print_r($item, true), LOGGER_DATA); - Activity::store(App::get_channel(), $observer_hash, $AS, $item, true, true); - goaway(z_root() . '/display/' . gen_link_id($item['mid'])); - } - } - } + $f = Libzot::fetch_conversation(App::get_channel(), punify($search), true); + + if ($f) { + goaway(z_root() . '/hq/' . gen_link_id($f['message_id'])); } else { - // try other fetch providers (e.g. diaspora) + // try other fetch providers (e.g. diaspora, pubcrawl) $hookdata = [ - 'channel' => App::get_channel(), - 'data' => $search + 'url' => punify($search) ]; call_hooks('fetch_provider', $hookdata); } diff --git a/Zotlabs/Module/Settings/Channel.php b/Zotlabs/Module/Settings/Channel.php index 5732d2628..e95752338 100644 --- a/Zotlabs/Module/Settings/Channel.php +++ b/Zotlabs/Module/Settings/Channel.php @@ -216,7 +216,8 @@ class Channel { if(x($_POST,'vnotify15')) $vnotify += intval($_POST['vnotify15']); - $always_show_in_notices = x($_POST,'always_show_in_notices') ? 1 : 0; + $always_show_in_notices = x($_POST, 'always_show_in_notices') ? 1 : 0; + $update_notices_per_parent = x($_POST, 'update_notices_per_parent') ? 1 : 0; $err = ''; @@ -245,6 +246,7 @@ class Channel { set_pconfig(local_channel(),'system','blocktags',$blocktags); set_pconfig(local_channel(),'system','vnotify',$vnotify); set_pconfig(local_channel(),'system','always_show_in_notices',$always_show_in_notices); + set_pconfig(local_channel(),'system','update_notices_per_parent',$update_notices_per_parent); set_pconfig(local_channel(),'system','evdays',$evdays); set_pconfig(local_channel(),'system','photo_path',$photo_path); set_pconfig(local_channel(),'system','attach_path',$attach_path); @@ -477,8 +479,10 @@ class Channel { $perm_roles = \Zotlabs\Access\PermissionRoles::roles(); - $vnotify = get_pconfig(local_channel(),'system','vnotify'); $always_show_in_notices = get_pconfig(local_channel(),'system','always_show_in_notices'); + $update_notices_per_parent = get_pconfig(local_channel(), 'system', 'update_notices_per_parent', 1); + $vnotify = get_pconfig(local_channel(),'system','vnotify'); + if($vnotify === false) $vnotify = (-1); @@ -581,6 +585,7 @@ class Channel { '$vnotify15' => array('vnotify15', t('Unseen forum posts'), ($vnotify & VNOTIFY_FORUMS), VNOTIFY_FORUMS, '', $yes_no), '$mailhost' => [ 'mailhost', t('Email notification hub (hostname)'), get_pconfig(local_channel(),'system','email_notify_host',\App::get_hostname()), sprintf( t('If your channel is mirrored to multiple hubs, set this to your preferred location. This will prevent duplicate email notifications. Example: %s'),\App::get_hostname()) ], '$always_show_in_notices' => array('always_show_in_notices', t('Show new wall posts, private messages and connections under Notices'), $always_show_in_notices, 1, '', $yes_no), + '$update_notices_per_parent' => array('update_notices_per_parent', t('Mark all notices of the thread read if a notice is clicked'), $update_notices_per_parent, 1, t('If no, only the clicked notice will be marked read'), $yes_no), '$desktop_notifications_info' => t('Desktop notifications are unavailable because the required browser permission has not been granted'), '$desktop_notifications_request' => t('Grant permission'), '$evdays' => array('evdays', t('Notify me of events this many days in advance'), $evdays, t('Must be greater than 0')), diff --git a/Zotlabs/Module/Sse.php b/Zotlabs/Module/Sse.php index 6f3df299f..3dab3d465 100644 --- a/Zotlabs/Module/Sse.php +++ b/Zotlabs/Module/Sse.php @@ -34,6 +34,7 @@ class Sse extends Controller { self::$uid = local_channel(); self::$ob_hash = get_observer_hash(); self::$sse_id = false; + self::$vnotify = -1; if(! self::$ob_hash) { if(session_id()) { @@ -45,7 +46,9 @@ class Sse extends Controller { } } - self::$vnotify = get_pconfig(self::$uid, 'system', 'vnotify'); + if (self::$uid) { + self::$vnotify = get_pconfig(self::$uid, 'system', 'vnotify'); + } $sleep_seconds = 3; @@ -94,6 +97,14 @@ class Sse extends Controller { $result = XConfig::Get(self::$ob_hash, 'sse', 'notifications', []); $lock = XConfig::Get(self::$ob_hash, 'sse', 'lock'); + // We do not have the local_channel in the addon. + // Reset pubs here if the app is not installed. + if (self::$uid && (!(self::$vnotify & VNOTIFY_PUBS) || !Apps::system_app_installed(self::$uid, 'Public Stream'))) { + $result['pubs']['count'] = 0; + $result['pubs']['notifications'] = []; + $result['pubs']['offset'] = -1; + } + if($result && !$lock) { echo "event: notifications\n"; echo 'data: ' . json_encode($result); diff --git a/Zotlabs/Module/Sse_bs.php b/Zotlabs/Module/Sse_bs.php index b445b235d..eaaeae7b7 100644 --- a/Zotlabs/Module/Sse_bs.php +++ b/Zotlabs/Module/Sse_bs.php @@ -373,7 +373,7 @@ class Sse_bs extends Controller { $result['pubs']['notifications'] = []; $result['pubs']['count'] = 0; - if(! (self::$vnotify & VNOTIFY_PUBS)) { + if(! (self::$vnotify & VNOTIFY_PUBS) || !Apps::system_app_installed(self::$uid, 'Public Stream')) { $result['pubs']['offset'] = -1; return $result; } diff --git a/Zotlabs/Module/Uexport.php b/Zotlabs/Module/Uexport.php index d73bc40d4..8116f616b 100644 --- a/Zotlabs/Module/Uexport.php +++ b/Zotlabs/Module/Uexport.php @@ -2,24 +2,27 @@ namespace Zotlabs\Module; use App; +use ZipArchive; use Zotlabs\Lib\Apps; use Zotlabs\Web\Controller; class Uexport extends Controller { function init() { - if(! local_channel()) - killme(); + if(! local_channel()) { + return; + } - if(! Apps::system_app_installed(local_channel(), 'Channel Export')) + if(! Apps::system_app_installed(local_channel(), 'Channel Export')) { return; + } if(argc() > 1) { - $sections = (($_REQUEST['sections']) ? explode(',',$_REQUEST['sections']) : ''); $zap_compat = (($_REQUEST['zap_compat']) ? intval($_REQUEST['zap_compat']) : false); - $channel = App::get_channel(); + $year = null; + $month = null; if(argc() > 1 && intval(argv(1)) > 1900) { $year = intval(argv(1)); @@ -29,25 +32,110 @@ class Uexport extends Controller { $month = intval(argv(2)); } - header('content-type: application/json'); - header('content-disposition: attachment; filename="' . $channel['channel_address'] . (($year) ? '-' . $year : '') . (($month) ? '-' . $month : '') . (($_REQUEST['sections']) ? '-' . $_REQUEST['sections'] : '') . '.json"' ); + $sections = []; + $section = ''; + if(argc() > 1 && ctype_lower(argv(1))) { + $section = argv(1); + } - if($year) { - echo json_encode(identity_export_year(local_channel(),$year,$month, $zap_compat)); - killme(); + switch ($section) { + case 'channel': + $sections = get_default_export_sections(); + break; + case 'chatrooms': + $sections = ['chatrooms']; + break; + case 'events': + $sections = ['events']; + break; + case 'webpages': + $sections = ['webpages']; + break; + case 'wikis': + $sections = ['wikis']; + break; + case 'custom': + default: + $custom_sections = ['channel', 'connections', 'config', 'apps', 'chatrooms', 'events', 'webpages', 'wikis']; + $raw_sections = (($_REQUEST['sections']) ? explode(',', $_REQUEST['sections']) : ''); + if ($raw_sections) { + foreach ($raw_sections as $raw_section) { + if(in_array($raw_section, $custom_sections)) { + $sections[] = $raw_section; + } + } + } } - if(argc() > 1 && argv(1) === 'basic') { - echo json_encode(identity_basic_export(local_channel(),$sections, $zap_compat)); + if ($sections) { + + $export = json_encode(identity_basic_export(local_channel(), $sections, $zap_compat)); + + header('Content-Type: application/json'); + header('Content-Disposition: attachment; filename="' . $channel['channel_address'] . '-' . implode('-', $sections) . '.json"'); + header('Content-Length: ' . strlen($export)); + + echo $export; + + killme(); + } + elseif ($year && !$month) { + $zip_dir = 'store/[data]/' . $channel['channel_address'] . '/tmp'; + if (!is_dir($zip_dir)) + mkdir($zip_dir, STORAGE_DEFAULT_PERMISSIONS, true); + + $zip_file = $channel['channel_address'] . '-' . $year . '.zip'; + $zip_path = $zip_dir . '/' . $zip_file; + $zip_content_available = false; + $zip = new ZipArchive(); + + if ($zip->open($zip_path, ZipArchive::CREATE) === true) { + $month = 1; + while ($month <= 12) { + $name = $channel['channel_address'] . '-' . $year . '-' . $month . '.json'; + $content = conv_item_export_year(local_channel(), $year, $month, $zap_compat); + if(isset($content['item'])) { + $zip_content_available = true; + $zip->addFromString($name, json_encode($content)); + } + $month++; + } + $zip->setCompressionName($zip_path, ZipArchive::CM_STORE); + $zip->close(); + } + if (!$zip_content_available) { + unlink($zip_path); + notice(t('No content available for year') . ' ' . $year . EOL); + goaway('/uexport'); + } + + header('Content-Type: application/zip'); + header('Content-Disposition: attachment; filename="' . $zip_file . '"'); + header('Content-Length: ' . filesize($zip_path)); + + $istream = fopen($zip_path, 'rb'); + $ostream = fopen('php://output', 'wb'); + if ($istream && $ostream) { + pipe_streams($istream, $ostream); + fclose($istream); + fclose($ostream); + } + + unlink($zip_path); killme(); } + elseif ($year && $month) { + $export = json_encode(conv_item_export_year(local_channel(), $year, $month, $zap_compat)); - // Warning: this option may consume a lot of memory + header('Content-Type: application/json'); + header('Content-Disposition: attachment; filename="' . $channel['channel_address'] . '-' . $year . '-' . $month . '.json"'); + header('Content-Length: ' . strlen($export)); - if(argc() > 1 && argv(1) === 'complete') { - $sections = get_default_export_sections(); - $sections[] = 'items'; - echo json_encode(identity_basic_export(local_channel(),$sections, $zap_compat)); + echo $export; + + killme(); + } + else { killme(); } } @@ -62,27 +150,47 @@ class Uexport extends Controller { return Apps::app_render($papp, 'module'); } - $y = datetime_convert('UTC',date_default_timezone_get(),'now','Y'); + $account = App::get_account(); + $year_start = datetime_convert('UTC', date_default_timezone_get(), $account['account_created'], 'Y'); + $year_end = datetime_convert('UTC', date_default_timezone_get(), 'now', 'Y'); + $years = []; + + while ($year_start <= $year_end) { + $years[] = $year_start; + $year_start++; + } + + $item_import_url = '/import_items'; + $channel_import_url = '/import'; - $yearurl = z_root() . '/uexport/' . $y; - $janurl = z_root() . '/uexport/' . $y . '/1'; - $impurl = '/import_items'; $o = replace_macros(get_markup_template('uexport.tpl'), array( '$title' => t('Export Channel'), - '$basictitle' => t('Export Channel'), - '$basic' => t('Export your basic channel information to a file. This acts as a backup of your connections, permissions, profile and basic data, which can be used to import your data to a new server hub, but does not contain your content.'), - '$fulltitle' => t('Export Content'), - '$full' => t('Export your channel information and recent content to a JSON backup that can be restored or imported to another server hub. This backs up all of your connections, permissions, profile data and several months of posts. This file may be VERY large. Please be patient - it may take several minutes for this download to begin.'), - '$by_year' => t('Export your posts from a given year.'), + '$channel_title' => t('Export channel'), + '$channel_info' => t('This will export your identity and social graph into a file which can be used to import your channel to a new hub.'), + + '$years' => $years, + '$content_title' => t('Export content'), + '$content_info' => t('This will export your posts, direct messages, articles and cards per month stored into a zip file per year. Months with no posts will be dismissed.'), - '$extra' => t('You may also export your posts and conversations for a particular year or month. Adjust the date in your browser location bar to select other dates. If the export fails (possibly due to memory exhaustion on your server hub), please try again selecting a more limited date range.'), - '$extra2' => sprintf( t('To select all posts for a given year, such as this year, visit <a href="%1$s">%2$s</a>'),$yearurl,$yearurl), - '$extra3' => sprintf( t('To select all posts for a given month, such as January of this year, visit <a href="%1$s">%2$s</a>'),$janurl,$janurl), - '$extra4' => sprintf( t('These content files may be imported or restored by visiting <a href="%1$s">%2$s</a> on any site containing your channel. For best results please import or restore these in date order (oldest first).'),$impurl,$impurl) + '$wikis_title' => t('Export wikis'), + '$wikis_info' => t('This will export your wikis and wiki pages.'), + '$webpages_title' => t('Export webpages'), + '$webpages_info' => t('This will export your webpages and menus.'), + + '$events_title' => t('Export channel calendar'), + '$events_info' => t('This will export your channel calendar events and associated items. CalDAV calendars are not included.'), + + '$chatrooms_title' => t('Export chatrooms'), + '$chatrooms_info' => t('This will export your chatrooms. Chat history is dismissed.'), + + '$items_extra_info' => sprintf( t('This export can be imported or restored by visiting <a href="%1$s">%2$s</a> on any site containing your channel.'), $item_import_url, $item_import_url), )); - return $o; + return $o; } + + + } diff --git a/Zotlabs/Module/Wfinger.php b/Zotlabs/Module/Wfinger.php index 6dedc1ef1..43102f006 100644 --- a/Zotlabs/Module/Wfinger.php +++ b/Zotlabs/Module/Wfinger.php @@ -72,20 +72,16 @@ class Wfinger extends \Zotlabs\Web\Controller { dbesc($channel) ); if($r) { - $r[0] = pchan_to_chan($r[0]); + $r = pchan_to_chan($r[0]); } } else { - $r = q("select * from channel left join xchan on channel_hash = xchan_hash - where channel_address = '%s' limit 1", - dbesc($channel) - ); + $r = channelx_by_nick($channel); } } header('Access-Control-Allow-Origin: *'); - if($root_resource) { $result['subject'] = $resource; $result['properties'] = [ @@ -107,15 +103,15 @@ class Wfinger extends \Zotlabs\Web\Controller { if($resource && $r) { $h = q("select hubloc_addr from hubloc where hubloc_hash = '%s' and hubloc_deleted = 0", - dbesc($r[0]['channel_hash']) + dbesc($r['channel_hash']) ); $result['subject'] = $resource; $aliases = array( - z_root() . (($pchan) ? '/pchan/' : '/channel/') . $r[0]['channel_address'], - z_root() . '/~' . $r[0]['channel_address'], - z_root() . '/@' . $r[0]['channel_address'] + z_root() . (($pchan) ? '/pchan/' : '/channel/') . $r['channel_address'], + z_root() . '/~' . $r['channel_address'], + z_root() . '/@' . $r['channel_address'] ); if($h) { @@ -127,9 +123,9 @@ class Wfinger extends \Zotlabs\Web\Controller { $result['aliases'] = []; $result['properties'] = [ - 'http://webfinger.net/ns/name' => $r[0]['channel_name'], - 'http://xmlns.com/foaf/0.1/name' => $r[0]['channel_name'], - 'https://w3id.org/security/v1#publicKeyPem' => $r[0]['xchan_pubkey'], + 'http://webfinger.net/ns/name' => $r['channel_name'], + 'http://xmlns.com/foaf/0.1/name' => $r['channel_name'], + 'https://w3id.org/security/v1#publicKeyPem' => $r['xchan_pubkey'], 'http://purl.org/zot/federation' => 'zot6,zot' ]; @@ -143,18 +139,18 @@ class Wfinger extends \Zotlabs\Web\Controller { [ 'rel' => 'http://webfinger.net/rel/avatar', - 'type' => $r[0]['xchan_photo_mimetype'], - 'href' => $r[0]['xchan_photo_l'] + 'type' => $r['xchan_photo_mimetype'], + 'href' => $r['xchan_photo_l'] ], [ 'rel' => 'http://webfinger.net/rel/profile-page', - 'href' => $r[0]['xchan_url'], + 'href' => $r['xchan_url'], ], [ 'rel' => 'magic-public-key', - 'href' => 'data:application/magic-public-key,' . Keyutils::salmonKey($r[0]['channel_pubkey']), + 'href' => 'data:application/magic-public-key,' . Keyutils::salmonKey($r['channel_pubkey']), ] ]; @@ -167,14 +163,14 @@ class Wfinger extends \Zotlabs\Web\Controller { [ 'rel' => 'http://webfinger.net/rel/avatar', - 'type' => $r[0]['xchan_photo_mimetype'], - 'href' => $r[0]['xchan_photo_l'] + 'type' => $r['xchan_photo_mimetype'], + 'href' => $r['xchan_photo_l'] ], [ 'rel' => 'http://microformats.org/profile/hcard', 'type' => 'text/html', - 'href' => z_root() . '/hcard/' . $r[0]['channel_address'] + 'href' => z_root() . '/hcard/' . $r['channel_address'] ], [ @@ -184,18 +180,18 @@ class Wfinger extends \Zotlabs\Web\Controller { [ 'rel' => 'http://webfinger.net/rel/profile-page', - 'href' => z_root() . '/profile/' . $r[0]['channel_address'], + 'href' => z_root() . '/profile/' . $r['channel_address'], ], [ 'rel' => 'http://schemas.google.com/g/2010#updates-from', 'type' => 'application/atom+xml', - 'href' => z_root() . '/ofeed/' . $r[0]['channel_address'] + 'href' => z_root() . '/ofeed/' . $r['channel_address'] ], [ 'rel' => 'http://webfinger.net/rel/blog', - 'href' => z_root() . '/channel/' . $r[0]['channel_address'], + 'href' => z_root() . '/channel/' . $r['channel_address'], ], [ @@ -206,12 +202,12 @@ class Wfinger extends \Zotlabs\Web\Controller { [ 'rel' => 'http://purl.org/zot/protocol/6.0', 'type' => 'application/x-zot+json', - 'href' => channel_url($r[0]) + 'href' => channel_url($r) ], [ 'rel' => 'http://purl.org/zot/protocol', - 'href' => z_root() . '/.well-known/zot-info' . '?address=' . $r[0]['xchan_addr'], + 'href' => z_root() . '/.well-known/zot-info' . '?address=' . $r['xchan_addr'], ], [ @@ -222,14 +218,14 @@ class Wfinger extends \Zotlabs\Web\Controller { [ 'rel' => 'magic-public-key', - 'href' => 'data:application/magic-public-key,' . Keyutils::salmonKey($r[0]['channel_pubkey']), + 'href' => 'data:application/magic-public-key,' . Keyutils::salmonKey($r['channel_pubkey']), ] ]; } if($zot) { // get a zotinfo packet and return it with webfinger - $result['zot'] = Libzot::zotinfo( [ 'address' => $r[0]['xchan_addr'] ]); + $result['zot'] = Libzot::zotinfo( [ 'address' => $r['xchan_addr'] ]); } } @@ -238,7 +234,7 @@ class Wfinger extends \Zotlabs\Web\Controller { killme(); } - $arr = [ 'channel' => $r[0], 'pchan' => $pchan, 'request' => $_REQUEST, 'result' => $result ]; + $arr = [ 'channel' => $r, 'pchan' => $pchan, 'request' => $_REQUEST, 'result' => $result ]; call_hooks('webfinger',$arr); json_return_and_die($arr['result'],'application/jrd+json'); diff --git a/Zotlabs/Module/Wiki.php b/Zotlabs/Module/Wiki.php index a06119506..3d0c07492 100644 --- a/Zotlabs/Module/Wiki.php +++ b/Zotlabs/Module/Wiki.php @@ -500,7 +500,7 @@ class Wiki extends Controller { $r = NativeWiki::create_wiki($owner, $observer_hash, $wiki, $acl); if($r['success']) { NativeWiki::sync_a_wiki_item($owner['channel_id'],$r['item_id'],$r['item']['resource_id']); - $homePage = NativeWikiPage::create_page($owner['channel_id'],$observer_hash,'Home', $r['item']['resource_id'], $wiki['mimeType']); + $homePage = NativeWikiPage::create_page($owner, $observer_hash, 'Home', $r['item']['resource_id'], $wiki['mimeType']); if(! $homePage['success']) { notice( t('Wiki created, but error creating Home page.')); goaway(z_root() . '/wiki/' . $nick . '/' . NativeWiki::name_encode($wiki['urlName'])); @@ -606,7 +606,7 @@ class Wiki extends Controller { json_return_and_die(array('message' => 'Error creating page. Invalid name (' . print_r($_POST,true) . ').', 'success' => false)); } - $page = NativeWikiPage::create_page($owner['channel_id'],$observer_hash, $name, $resource_id, $mimetype); + $page = NativeWikiPage::create_page($owner, $observer_hash, $name, $resource_id, $mimetype); if($page['item_id']) { $commit = NativeWikiPage::commit([ diff --git a/Zotlabs/Module/Xrd.php b/Zotlabs/Module/Xrd.php index 21574eb8d..b7868c2cc 100644 --- a/Zotlabs/Module/Xrd.php +++ b/Zotlabs/Module/Xrd.php @@ -28,19 +28,18 @@ class Xrd extends \Zotlabs\Web\Controller { $name = substr($local,0,strpos($local,'@')); } - $r = q("SELECT * FROM channel WHERE channel_address = '%s' LIMIT 1", - dbesc($name) - ); + $r = channelx_by_nick($name); + if(! $r) killme(); - $salmon_key = Keyutils::salmonKey($r[0]['channel_pubkey']); + $salmon_key = Keyutils::salmonKey($r['channel_pubkey']); header('Access-Control-Allow-Origin: *'); header("Content-type: application/xrd+xml"); - $aliases = array('acct:' . channel_reddress($r[0]), z_root() . '/channel/' . $r[0]['channel_address'], z_root() . '/~' . $r[0]['channel_address']); + $aliases = array('acct:' . channel_reddress($r), z_root() . '/channel/' . $r['channel_address'], z_root() . '/~' . $r['channel_address']); for($x = 0; $x < count($aliases); $x ++) { if($aliases[$x] === $resource) @@ -48,23 +47,23 @@ class Xrd extends \Zotlabs\Web\Controller { } $o = replace_macros(get_markup_template('xrd_person.tpl'), array( - '$nick' => $r[0]['channel_address'], + '$nick' => $r['channel_address'], '$accturi' => $resource, '$subject' => $subject, '$aliases' => $aliases, - '$channel_url' => z_root() . '/channel/' . $r[0]['channel_address'], - '$profile_url' => z_root() . '/channel/' . $r[0]['channel_address'], - '$hcard_url' => z_root() . '/hcard/' . $r[0]['channel_address'], - '$atom' => z_root() . '/ofeed/' . $r[0]['channel_address'], - '$zot_post' => z_root() . '/post/' . $r[0]['channel_address'], - '$poco_url' => z_root() . '/poco/' . $r[0]['channel_address'], - '$photo' => z_root() . '/photo/profile/l/' . $r[0]['channel_id'], + '$channel_url' => z_root() . '/channel/' . $r['channel_address'], + '$profile_url' => z_root() . '/channel/' . $r['channel_address'], + '$hcard_url' => z_root() . '/hcard/' . $r['channel_address'], + '$atom' => z_root() . '/ofeed/' . $r['channel_address'], + '$zot_post' => z_root() . '/post/' . $r['channel_address'], + '$poco_url' => z_root() . '/poco/' . $r['channel_address'], + '$photo' => z_root() . '/photo/profile/l/' . $r['channel_id'], '$modexp' => 'data:application/magic-public-key,' . $salmon_key, '$subscribe' => z_root() . '/follow?f=&url={uri}', )); - $arr = array('user' => $r[0], 'xml' => $o); + $arr = array('user' => $r, 'xml' => $o); call_hooks('personal_xrd', $arr); echo $arr['xml']; diff --git a/Zotlabs/Module/Zotfeed.php b/Zotlabs/Module/Zotfeed.php index e47367036..0b4c3c007 100644 --- a/Zotlabs/Module/Zotfeed.php +++ b/Zotlabs/Module/Zotfeed.php @@ -1,124 +1,22 @@ <?php - namespace Zotlabs\Module; -use App; -use Zotlabs\Lib\Activity; -use Zotlabs\Lib\ActivityStreams; -use Zotlabs\Lib\Config; -use Zotlabs\Lib\ThreadListener; use Zotlabs\Web\Controller; -use Zotlabs\Web\HTTPSig; class Zotfeed extends Controller { - function init() { - if (ActivityStreams::is_as_request()) { - - if (observer_prohibited(true)) { - killme(); - } - - $channel = channelx_by_nick(argv(1)); - if (!$channel) { - killme(); - } - - if (intval($channel['channel_system'])) { - killme(); - } - - $sigdata = HTTPSig::verify(($_SERVER['REQUEST_METHOD'] === 'POST') ? file_get_contents('php://input') : EMPTY_STR); - if ($sigdata['portable_id'] && $sigdata['header_valid']) { - $portable_id = $sigdata['portable_id']; - if (!check_channelallowed($portable_id)) { - http_status_exit(403, 'Permission denied'); - } - if (!check_siteallowed($sigdata['signer'])) { - http_status_exit(403, 'Permission denied'); - } - observer_auth($portable_id); - } - elseif (Config::get('system', 'require_authenticated_fetch', false)) { - http_status_exit(403, 'Permission denied'); - } - - $observer_hash = get_observer_hash(); - - $params = []; + function post() { - $params['begin'] = ((x($_REQUEST, 'date_begin')) ? $_REQUEST['date_begin'] : NULL_DATE); - $params['end'] = ((x($_REQUEST, 'date_end')) ? $_REQUEST['date_end'] : ''); - $params['type'] = 'json'; - $params['pages'] = ((x($_REQUEST, 'pages')) ? intval($_REQUEST['pages']) : 0); - $params['top'] = ((x($_REQUEST, 'top')) ? intval($_REQUEST['top']) : 0); - $params['direction'] = ((x($_REQUEST, 'direction')) ? dbesc($_REQUEST['direction']) : 'desc'); // unimplemented - $params['cat'] = ((x($_REQUEST, 'cat')) ? escape_tags($_REQUEST['cat']) : ''); - $params['compat'] = 1; - - $total = items_fetch( - [ - 'total' => true, - 'wall' => 1, - 'datequery' => $params['end'], - 'datequery2' => $params['begin'], - 'direction' => dbesc($params['direction']), - 'pages' => $params['pages'], - 'order' => dbesc('post'), - 'top' => $params['top'], - 'cat' => $params['cat'], - 'compat' => $params['compat'] - ], $channel, $observer_hash, CLIENT_MODE_NORMAL, App::$module - ); - - if ($total) { - App::set_pager_total($total); - App::set_pager_itemspage(30); - } + } - if (App::$pager['unset'] && $total > 30) { - $ret = Activity::paged_collection_init($total, App::$query_string); - } - else { + function get() { - $items = items_fetch( - [ - 'wall' => 1, - 'datequery' => $params['end'], - 'datequery2' => $params['begin'], - 'records' => intval(App::$pager['itemspage']), - 'start' => intval(App::$pager['start']), - 'direction' => dbesc($params['direction']), - 'pages' => $params['pages'], - 'order' => dbesc('post'), - 'top' => $params['top'], - 'cat' => $params['cat'], - 'compat' => $params['compat'] - ], $channel, $observer_hash, CLIENT_MODE_NORMAL, App::$module - ); + $outbox = new Outbox(); + return $outbox->init(); - if ($items && $observer_hash) { + } - // check to see if this observer is a connection. If not, register any items - // belonging to this channel for notification of deletion/expiration +} - $x = q("select abook_id from abook where abook_channel = %d and abook_xchan = '%s'", - intval($channel['channel_id']), - dbesc($observer_hash) - ); - if (!$x) { - foreach ($items as $item) { - if (strpos($item['mid'], z_root()) === 0) { - ThreadListener::store($item['mid'], $observer_hash); - } - } - } - } - $ret = Activity::encode_item_collection($items, App::$query_string, 'OrderedCollection', $total); - } - as_return_and_die($ret, $channel); - } - } -} diff --git a/Zotlabs/Web/HTTPSig.php b/Zotlabs/Web/HTTPSig.php index 35b18c763..7da9acabf 100644 --- a/Zotlabs/Web/HTTPSig.php +++ b/Zotlabs/Web/HTTPSig.php @@ -2,10 +2,13 @@ namespace Zotlabs\Web; +use DateTime; +use DateTimeZone; use Zotlabs\Lib\ActivityStreams; use Zotlabs\Lib\Crypto; use Zotlabs\Lib\Keyutils; use Zotlabs\Lib\Webfinger; +use Zotlabs\Lib\Zotfinger; use Zotlabs\Lib\Libzot; /** @@ -13,7 +16,6 @@ use Zotlabs\Lib\Libzot; * * @see https://tools.ietf.org/html/draft-cavage-http-signatures-10 */ - class HTTPSig { /** @@ -26,10 +28,10 @@ class HTTPSig { * @return string The generated digest header string for $body */ - static function generate_digest_header($body,$alg = 'sha256') { + static function generate_digest_header($body, $alg = 'sha256') { $digest = base64_encode(hash($alg, $body, true)); - switch($alg) { + switch ($alg) { case 'sha512': return 'SHA-512=' . $digest; case 'sha256': @@ -39,29 +41,29 @@ class HTTPSig { } } - static function find_headers($data,&$body) { + static function find_headers($data, &$body) { // decide if $data arrived via controller submission or curl - if(is_array($data) && $data['header']) { - if(! $data['success']) + if (is_array($data) && $data['header']) { + if (!$data['success']) return []; - $h = new HTTPHeaders($data['header']); - $headers = $h->fetcharr(); - $body = $data['body']; + $h = new HTTPHeaders($data['header']); + $headers = $h->fetcharr(); + $body = $data['body']; $headers['(request-target)'] = $data['request_target']; } else { - $headers = []; + $headers = []; $headers['(request-target)'] = strtolower($_SERVER['REQUEST_METHOD']) . ' ' . $_SERVER['REQUEST_URI']; - $headers['content-type'] = $_SERVER['CONTENT_TYPE']; - $headers['content-length'] = $_SERVER['CONTENT_LENGTH']; + $headers['content-type'] = $_SERVER['CONTENT_TYPE']; + $headers['content-length'] = $_SERVER['CONTENT_LENGTH']; - foreach($_SERVER as $k => $v) { - if(strpos($k,'HTTP_') === 0) { - $field = str_replace('_','-',strtolower(substr($k,5))); + foreach ($_SERVER as $k => $v) { + if (strpos($k, 'HTTP_') === 0) { + $field = str_replace('_', '-', strtolower(substr($k, 5))); $headers[$field] = $v; } } @@ -77,10 +79,10 @@ class HTTPSig { // See draft-cavage-http-signatures-10 - static function verify($data,$key = '', $keytype = '') { + static function verify($data, $key = '', $keytype = '') { - $body = $data; - $headers = null; + $body = $data; + $headers = null; $result = [ 'signer' => '', @@ -92,21 +94,21 @@ class HTTPSig { ]; - $headers = self::find_headers($data,$body); + $headers = self::find_headers($data, $body); - if(! $headers) + if (!$headers) return $result; $sig_block = null; - if(array_key_exists('signature',$headers)) { + if (array_key_exists('signature', $headers)) { $sig_block = self::parse_sigheader($headers['signature']); } - elseif(array_key_exists('authorization',$headers)) { + elseif (array_key_exists('authorization', $headers)) { $sig_block = self::parse_sigheader($headers['authorization']); } - if(! $sig_block) { + if (!$sig_block) { logger('no signature provided.', LOGGER_DEBUG); return $result; } @@ -117,71 +119,71 @@ class HTTPSig { $result['header_signed'] = true; $signed_headers = $sig_block['headers']; - if(! $signed_headers) - $signed_headers = [ 'date' ]; + if (!$signed_headers) + $signed_headers = ['date']; $signed_data = ''; - foreach($signed_headers as $h) { - if(array_key_exists($h,$headers)) { + foreach ($signed_headers as $h) { + if (array_key_exists($h, $headers)) { $signed_data .= $h . ': ' . $headers[$h] . "\n"; } - if($h === 'date') { - $d = new \DateTime($headers[$h]); - $d->setTimeZone(new \DateTimeZone('UTC')); - $dplus = datetime_convert('UTC','UTC','now + 1 day'); - $dminus = datetime_convert('UTC','UTC','now - 1 day'); - $c = $d->format('Y-m-d H:i:s'); - if($c > $dplus || $c < $dminus) { + if ($h === 'date') { + $d = new DateTime($headers[$h]); + $d->setTimeZone(new DateTimeZone('UTC')); + $dplus = datetime_convert('UTC', 'UTC', 'now + 1 day'); + $dminus = datetime_convert('UTC', 'UTC', 'now - 1 day'); + $c = $d->format('Y-m-d H:i:s'); + if ($c > $dplus || $c < $dminus) { logger('bad time: ' . $c); return $result; } } } - $signed_data = rtrim($signed_data,"\n"); + $signed_data = rtrim($signed_data, "\n"); $algorithm = null; - if($sig_block['algorithm'] === 'rsa-sha256') { + if ($sig_block['algorithm'] === 'rsa-sha256') { $algorithm = 'sha256'; } - if($sig_block['algorithm'] === 'rsa-sha512') { + if ($sig_block['algorithm'] === 'rsa-sha512') { $algorithm = 'sha512'; } - if(! array_key_exists('keyId',$sig_block)) + if (!array_key_exists('keyId', $sig_block)) return $result; $result['signer'] = $sig_block['keyId']; - $cached_key = self::get_key($key,$keytype,$result['signer']); + $cached_key = self::get_key($key, $keytype, $result['signer']); - if(! ($cached_key && $cached_key['public_key'])) { + if (!($cached_key && $cached_key['public_key'])) { return $result; } - $x = Crypto::verify($signed_data,$sig_block['signature'],$cached_key['public_key'],$algorithm); + $x = Crypto::verify($signed_data, $sig_block['signature'], $cached_key['public_key'], $algorithm); logger('verified: ' . $x, LOGGER_DEBUG); $fetched_key = ''; - if(! $x) { + if (!$x) { // try again, ignoring the local actor (xchan) cache and refetching the key // from its source - $fetched_key = self::get_key($key,$keytype,$result['signer'],true); + $fetched_key = self::get_key($key, $keytype, $result['signer'], true); if ($fetched_key && $fetched_key['public_key']) { - $y = Crypto::verify($signed_data,$sig_block['signature'],$fetched_key['public_key'],$algorithm); + $y = Crypto::verify($signed_data, $sig_block['signature'], $fetched_key['public_key'], $algorithm); logger('verified: (cache reload) ' . $x, LOGGER_DEBUG); } - if (! $y) { + if (!$y) { logger('verify failed for ' . $result['signer'] . ' alg=' . $algorithm . (($fetched_key['public_key']) ? '' : ' no key')); $sig_block['signature'] = base64_encode($sig_block['signature']); - logger('affected sigblock: ' . print_r($sig_block,true)); - logger('headers: ' . print_r($headers,true)); - logger('server: ' . print_r($_SERVER,true)); + logger('affected sigblock: ' . print_r($sig_block, true)); + logger('headers: ' . print_r($headers, true)); + logger('server: ' . print_r($_SERVER, true)); return $result; } @@ -189,58 +191,59 @@ class HTTPSig { $key = (($fetched_key) ? $fetched_key : $cached_key); - $result['portable_id'] = $key['portable_id']; + $result['portable_id'] = $key['portable_id']; $result['header_valid'] = true; - if(in_array('digest',$signed_headers)) { + if (in_array('digest', $signed_headers)) { $result['content_signed'] = true; - $digest = explode('=', $headers['digest'], 2); - if($digest[0] === 'SHA-256') + $digest = explode('=', $headers['digest'], 2); + if ($digest[0] === 'SHA-256') $hashalg = 'sha256'; - if($digest[0] === 'SHA-512') + if ($digest[0] === 'SHA-512') $hashalg = 'sha512'; - if(base64_encode(hash($hashalg,$body,true)) === $digest[1]) { + if (base64_encode(hash($hashalg, $body, true)) === $digest[1]) { $result['content_valid'] = true; } logger('Content_Valid: ' . (($result['content_valid']) ? 'true' : 'false')); - if (! $result['content_valid']) { - logger('invalid content signature: data ' . print_r($data,true)); - logger('invalid content signature: headers ' . print_r($headers,true)); - logger('invalid content signature: body ' . print_r($body,true)); + if (!$result['content_valid']) { + logger('invalid content signature: data ' . print_r($data, true)); + logger('invalid content signature: headers ' . print_r($headers, true)); + logger('invalid content signature: body ' . print_r($body, true)); } } return $result; } - static function get_key($key,$keytype,$id) { + static function get_key($key, $keytype, $id, $force = false) { - if(is_array($key)) - btlogger('key is array: ' . print_r($key,true)); + if (is_array($key)) + btlogger('key is array: ' . print_r($key, true)); - if($key) { - if(function_exists($key)) { + if ($key) { + if (function_exists($key)) { return $key($id); } - return [ 'public_key' => $key ]; + return ['public_key' => $key]; } - if($keytype === 'zot6') { - $key = self::get_zotfinger_key($id); - if($key) { + if ($keytype === 'zot6') { + $key = self::get_zotfinger_key($id, $force); + if ($key) { return $key; } } - if(strpos($id,'#') === false) { - $key = self::get_webfinger_key($id); + if (strpos($id, '#') === false) { + $key = self::get_webfinger_key($id, $force); + if ($key) { + return $key; + } } - if(! $key) { - $key = self::get_activitystreams_key($id); - } + $key = self::get_activitystreams_key($id, $force); return $key; @@ -249,10 +252,10 @@ class HTTPSig { static function convertKey($key) { - if(strstr($key,'RSA ')) { + if (strstr($key, 'RSA ')) { return Keyutils::rsaToPem($key); } - elseif(substr($key,0,5) === 'data:') { + elseif (substr($key, 0, 5) === 'data:') { return Keyutils::convertSalmonKey($key); } else { @@ -263,70 +266,88 @@ class HTTPSig { /** - * @brief + * @brief get a cached key or fetch it with ActivityStreams * * @param string $id - * @return boolean|string - * false if no pub key found, otherwise return the pub key + * @param boolean $force (optional, default false) + * @return boolean|array + * false if no pub key found, otherwise return an array with the pub key */ - static function get_activitystreams_key($id) { - - // remove fragment - - $url = ((strpos($id,'#')) ? substr($id,0,strpos($id,'#')) : $id); + static function get_activitystreams_key($id, $force = false) { - $x = q("select * from xchan left join hubloc on xchan_hash = hubloc_hash where hubloc_addr = '%s' or hubloc_id_url = '%s' and hubloc_network in ('zot6', 'activitypub')", - dbesc(str_replace('acct:','',$url)), - dbesc($url) - ); + // Check the local cache first, but remove any fragments like #main-key since these won't be present in our cached data + $url = ((strpos($id, '#')) ? substr($id, 0, strpos($id, '#')) : $id); - $x = Libzot::zot_record_preferred($x); + // $force is used to ignore the local cache and only use the remote data; for instance the cached key might be stale + if (!$force) { + $x = q("select * from xchan left join hubloc on xchan_hash = hubloc_hash where (hubloc_id_url = '%s' or hubloc_hash = '%s') and hubloc_network in ('zot6', 'activitypub') order by hubloc_id desc", + dbesc($url), + dbesc($url) + ); - if($x && $x['xchan_pubkey']) { - return [ 'portable_id' => $x['xchan_hash'], 'public_key' => $x['xchan_pubkey'] , 'hubloc' => $x ]; + if ($x) { + $best = Libzot::zot_record_preferred($x); + } + if ($best && $best['xchan_pubkey']) { + return ['portable_id' => $best['xchan_hash'], 'public_key' => $best['xchan_pubkey'], 'hubloc' => $best]; + } } + // The record wasn't in cache. Fetch it now. $r = ActivityStreams::fetch($id); - if($r) { - if(array_key_exists('publicKey',$r) && array_key_exists('publicKeyPem',$r['publicKey']) && array_key_exists('id',$r['publicKey'])) { - if($r['publicKey']['id'] === $id || $r['id'] === $id) { - $portable_id = ((array_key_exists('owner',$r['publicKey'])) ? $r['publicKey']['owner'] : EMPTY_STR); - return [ 'public_key' => self::convertKey($r['publicKey']['publicKeyPem']), 'portable_id' => $portable_id, 'hubloc' => [] ]; + if ($r) { + if (array_key_exists('publicKey', $r) && array_key_exists('publicKeyPem', $r['publicKey']) && array_key_exists('id', $r['publicKey'])) { + if ($r['publicKey']['id'] === $id || $r['id'] === $id) { + $portable_id = ((array_key_exists('owner', $r['publicKey'])) ? $r['publicKey']['owner'] : EMPTY_STR); + return ['public_key' => self::convertKey($r['publicKey']['publicKeyPem']), 'portable_id' => $portable_id, 'hubloc' => []]; } } } + + // No key was found return false; } + /** + * @brief get a cached key or fetch it with Webfinger + * + * @param string $id + * @param boolean $force (optional, default false) + * @return boolean|array + * false if no pub key found, otherwise return an array with the pub key + */ - static function get_webfinger_key($id) { + static function get_webfinger_key($id, $force = false) { - $x = q("select * from xchan left join hubloc on xchan_hash = hubloc_hash where hubloc_addr = '%s' or hubloc_id_url = '%s'", - dbesc(str_replace('acct:','',$id)), - dbesc($id) - ); + if (!$force) { + $x = q("select * from xchan left join hubloc on xchan_hash = hubloc_hash where hubloc_id_url = '%s' and hubloc_network in ('zot6', 'activitypub') order by hubloc_id desc", + dbesc($id) + ); - $x = Libzot::zot_record_preferred($x); + if ($x) { + $best = Libzot::zot_record_preferred($x); + } - if($x && $x['xchan_pubkey']) { - return [ 'portable_id' => $x['xchan_hash'], 'public_key' => $x['xchan_pubkey'] , 'hubloc' => $x ]; + if ($best && $best['xchan_pubkey']) { + return ['portable_id' => $best['xchan_hash'], 'public_key' => $best['xchan_pubkey'], 'hubloc' => $best]; + } } - $wf = Webfinger::exec($id); - $key = [ 'portable_id' => '', 'public_key' => '', 'hubloc' => [] ]; + $wf = Webfinger::exec($id); + $key = ['portable_id' => '', 'public_key' => '', 'hubloc' => []]; - if($wf) { - if(array_key_exists('properties',$wf) && array_key_exists('https://w3id.org/security/v1#publicKeyPem',$wf['properties'])) { + if ($wf) { + if (array_key_exists('properties', $wf) && array_key_exists('https://w3id.org/security/v1#publicKeyPem', $wf['properties'])) { $key['public_key'] = self::convertKey($wf['properties']['https://w3id.org/security/v1#publicKeyPem']); } - if(array_key_exists('links', $wf) && is_array($wf['links'])) { - foreach($wf['links'] as $l) { - if(! (is_array($l) && array_key_exists('rel',$l))) { + if (array_key_exists('links', $wf) && is_array($wf['links'])) { + foreach ($wf['links'] as $l) { + if (!(is_array($l) && array_key_exists('rel', $l))) { continue; } - if($l['rel'] === 'magic-public-key' && array_key_exists('href',$l) && $key['public_key'] === EMPTY_STR) { + if ($l['rel'] === 'magic-public-key' && array_key_exists('href', $l) && $key['public_key'] === EMPTY_STR) { $key['public_key'] = self::convertKey($l['href']); } } @@ -336,51 +357,64 @@ class HTTPSig { return (($key['public_key']) ? $key : false); } - static function get_zotfinger_key($id) { + /** + * @brief get a cached key or fetch it with Zotfinger + * + * @param string $id + * @param boolean $force (optional, default false) + * @return boolean|array + * false if no pub key found, otherwise return an array with the public key + */ - $x = q("select * from xchan left join hubloc on xchan_hash = hubloc_hash where hubloc_addr = '%s' or hubloc_id_url = '%s' and hubloc_network = 'zot6'", - dbesc(str_replace('acct:','',$id)), - dbesc($id) - ); + static function get_zotfinger_key($id, $force = false) { + if (!$force) { + $x = q("select * from xchan left join hubloc on xchan_hash = hubloc_hash where hubloc_id_url = '%s' and hubloc_network = 'zot6' order by hubloc_id desc", + dbesc($id) + ); - if($x && $x[0]['xchan_pubkey']) { - return [ 'portable_id' => $x[0]['xchan_hash'], 'public_key' => $x[0]['xchan_pubkey'] , 'hubloc' => $x[0] ]; + if ($x) { + $best = Libzot::zot_record_preferred($x); + } + + if ($best && $best['xchan_pubkey']) { + return ['portable_id' => $best['xchan_hash'], 'public_key' => $best['xchan_pubkey'], 'hubloc' => $best]; + } } - $wf = Webfinger::exec($id); - $key = [ 'portable_id' => '', 'public_key' => '', 'hubloc' => [] ]; + $wf = Webfinger::exec($id); + $key = ['portable_id' => '', 'public_key' => '', 'hubloc' => []]; - if($wf) { - if(array_key_exists('properties',$wf) && array_key_exists('https://w3id.org/security/v1#publicKeyPem',$wf['properties'])) { + if ($wf) { + if (array_key_exists('properties', $wf) && array_key_exists('https://w3id.org/security/v1#publicKeyPem', $wf['properties'])) { $key['public_key'] = self::convertKey($wf['properties']['https://w3id.org/security/v1#publicKeyPem']); } - if(array_key_exists('links', $wf) && is_array($wf['links'])) { - foreach($wf['links'] as $l) { - if(! (is_array($l) && array_key_exists('rel',$l))) { + if (array_key_exists('links', $wf) && is_array($wf['links'])) { + foreach ($wf['links'] as $l) { + if (!(is_array($l) && array_key_exists('rel', $l))) { continue; } - if($l['rel'] === 'http://purl.org/zot/protocol/6.0' && array_key_exists('href',$l) && $l['href'] !== EMPTY_STR) { + if ($l['rel'] === 'http://purl.org/zot/protocol/6.0' && array_key_exists('href', $l) && $l['href'] !== EMPTY_STR) { // The third argument to Zotfinger::exec() tells it not to verify signatures // Since we're inside a function that is fetching keys with which to verify signatures, // this is necessary to prevent infinite loops. - $z = \Zotlabs\Lib\Zotfinger::exec($l['href'],null,false); - if($z) { + $z = Zotfinger::exec($l['href'], null, false); + if ($z) { $i = Libzot::import_xchan($z['data']); - if($i['success']) { + if ($i['success']) { $key['portable_id'] = $i['hash']; - $x = q("select * from xchan left join hubloc on xchan_hash = hubloc_hash where hubloc_id_url = '%s' and hubloc_network = 'zot6'", + $x = q("select * from xchan left join hubloc on xchan_hash = hubloc_hash where hubloc_id_url = '%s' and hubloc_network = 'zot6' order by hubloc_id desc", dbesc($l['href']) ); - if($x) { + if ($x) { $key['hubloc'] = $x[0]; } } } } - if($l['rel'] === 'magic-public-key' && array_key_exists('href',$l) && $key['public_key'] === EMPTY_STR) { + if ($l['rel'] === 'magic-public-key' && array_key_exists('href', $l) && $key['public_key'] === EMPTY_STR) { $key['public_key'] = self::convertKey($l['href']); } } @@ -402,39 +436,39 @@ class HTTPSig { * @param array $encryption [ 'key', 'algorithm' ] or false * @return array */ - static function create_sig($head, $prvkey, $keyid = EMPTY_STR, $auth = false, $alg = 'sha256', $encryption = false ) { + static function create_sig($head, $prvkey, $keyid = EMPTY_STR, $auth = false, $alg = 'sha256', $encryption = false) { $return_headers = []; - if($alg === 'sha256') { + if ($alg === 'sha256') { $algorithm = 'rsa-sha256'; } - if($alg === 'sha512') { + if ($alg === 'sha512') { $algorithm = 'rsa-sha512'; } - $x = self::sign($head,$prvkey,$alg); + $x = self::sign($head, $prvkey, $alg); $headerval = 'keyId="' . $keyid . '",algorithm="' . $algorithm . '",headers="' . $x['headers'] . '",signature="' . $x['signature'] . '"'; - if($encryption) { - $x = Crypto::encapsulate($headerval,$encryption['key'],$encryption['algorithm']); - if(is_array($x)) { + if ($encryption) { + $x = Crypto::encapsulate($headerval, $encryption['key'], $encryption['algorithm']); + if (is_array($x)) { $headerval = 'iv="' . $x['iv'] . '",key="' . $x['key'] . '",alg="' . $x['alg'] . '",data="' . $x['data'] . '"'; } } - if($auth) { + if ($auth) { $sighead = 'Authorization: Signature ' . $headerval; } else { $sighead = 'Signature: ' . $headerval; } - if($head) { - foreach($head as $k => $v) { + if ($head) { + foreach ($head as $k => $v) { // strip the request-target virtual header from the output headers - if($k === '(request-target)') { + if ($k === '(request-target)') { continue; } $return_headers[] = $k . ': ' . $v; @@ -454,8 +488,8 @@ class HTTPSig { static function set_headers($headers) { - if($headers && is_array($headers)) { - foreach($headers as $h) { + if ($headers && is_array($headers)) { + foreach ($headers as $h) { header($h); } } @@ -465,7 +499,7 @@ class HTTPSig { /** * @brief * - * @param array $head + * @param array $head * @param string $prvkey * @param string $alg (optional) default 'sha256' * @return array @@ -478,21 +512,21 @@ class HTTPSig { $headers = ''; $fields = ''; - logger('signing: ' . print_r($head,true), LOGGER_DATA); + logger('signing: ' . print_r($head, true), LOGGER_DATA); - if($head) { - foreach($head as $k => $v) { + if ($head) { + foreach ($head as $k => $v) { $headers .= strtolower($k) . ': ' . trim($v) . "\n"; - if($fields) + if ($fields) $fields .= ' '; $fields .= strtolower($k); } // strip the trailing linefeed - $headers = rtrim($headers,"\n"); + $headers = rtrim($headers, "\n"); } - $sig = base64_encode(Crypto::sign($headers,$prvkey,$alg)); + $sig = base64_encode(Crypto::sign($headers, $prvkey, $alg)); $ret['headers'] = $fields; $ret['signature'] = $sig; @@ -513,26 +547,26 @@ class HTTPSig { static function parse_sigheader($header) { - $ret = []; + $ret = []; $matches = []; // if the header is encrypted, decrypt with (default) site private key and continue - if(preg_match('/iv="(.*?)"/ism',$header,$matches)) + if (preg_match('/iv="(.*?)"/ism', $header, $matches)) $header = self::decrypt_sigheader($header); - if(preg_match('/keyId="(.*?)"/ism',$header,$matches)) + if (preg_match('/keyId="(.*?)"/ism', $header, $matches)) $ret['keyId'] = $matches[1]; - if(preg_match('/algorithm="(.*?)"/ism',$header,$matches)) + if (preg_match('/algorithm="(.*?)"/ism', $header, $matches)) $ret['algorithm'] = $matches[1]; - if(preg_match('/headers="(.*?)"/ism',$header,$matches)) + if (preg_match('/headers="(.*?)"/ism', $header, $matches)) $ret['headers'] = explode(' ', $matches[1]); - if(preg_match('/signature="(.*?)"/ism',$header,$matches)) - $ret['signature'] = base64_decode(preg_replace('/\s+/','',$matches[1])); + if (preg_match('/signature="(.*?)"/ism', $header, $matches)) + $ret['signature'] = base64_decode(preg_replace('/\s+/', '', $matches[1])); - if(($ret['signature']) && ($ret['algorithm']) && (! $ret['headers'])) - $ret['headers'] = [ 'date' ]; + if (($ret['signature']) && ($ret['algorithm']) && (!$ret['headers'])) + $ret['headers'] = ['date']; - return $ret; + return $ret; } @@ -552,23 +586,23 @@ class HTTPSig { $iv = $key = $alg = $data = null; - if(! $prvkey) { + if (!$prvkey) { $prvkey = get_config('system', 'prvkey'); } $matches = []; - if(preg_match('/iv="(.*?)"/ism',$header,$matches)) + if (preg_match('/iv="(.*?)"/ism', $header, $matches)) $iv = $matches[1]; - if(preg_match('/key="(.*?)"/ism',$header,$matches)) + if (preg_match('/key="(.*?)"/ism', $header, $matches)) $key = $matches[1]; - if(preg_match('/alg="(.*?)"/ism',$header,$matches)) + if (preg_match('/alg="(.*?)"/ism', $header, $matches)) $alg = $matches[1]; - if(preg_match('/data="(.*?)"/ism',$header,$matches)) + if (preg_match('/data="(.*?)"/ism', $header, $matches)) $data = $matches[1]; - if($iv && $key && $alg && $data) { - return Crypto::unencapsulate([ 'encrypted' => true, 'iv' => $iv, 'key' => $key, 'alg' => $alg, 'data' => $data ] , $prvkey); + if ($iv && $key && $alg && $data) { + return Crypto::unencapsulate(['encrypted' => true, 'iv' => $iv, 'key' => $key, 'alg' => $alg, 'data' => $data], $prvkey); } return ''; diff --git a/Zotlabs/Widget/Messages.php b/Zotlabs/Widget/Messages.php index 21375b08f..c0fef9f75 100644 --- a/Zotlabs/Widget/Messages.php +++ b/Zotlabs/Widget/Messages.php @@ -11,7 +11,7 @@ class Messages { if (!local_channel()) return EMPTY_STR; - $page = self::get_messages_page($options); + $page = self::get_messages_page([]); $_SESSION['messages_loadtime'] = datetime_convert(); @@ -24,6 +24,7 @@ class Messages { 'messages_title' => t('Public and restricted messages'), 'direct_messages_title' => t('Direct messages'), 'starred_messages_title' => t('Starred messages'), + 'notice_messages_title' => t('Notices'), 'loading' => t('Loading'), 'empty' => t('No messages') ] @@ -40,6 +41,10 @@ class Messages { return; } + if ($options['type'] == 'notification') { + return self::get_notices_page($options); + } + $channel = App::get_channel(); $item_normal = item_normal(); $entries = []; @@ -81,6 +86,7 @@ class Messages { xchan_query($items, false); $i = 0; + $entries = []; foreach($items as $item) { @@ -171,8 +177,7 @@ class Messages { stringify_array_elms($recips, true); $query_str = implode(',', $recips); - $xchans = dbq("SELECT DISTINCT xchan_name FROM xchan WHERE $column IN ($query_str)"); - + $xchans = dbq("SELECT DISTINCT xchan_name FROM xchan WHERE $column IN ($query_str) AND xchan_deleted = 0"); foreach($xchans as $xchan) { $recipients .= $xchan['xchan_name'] . ', '; } @@ -181,4 +186,51 @@ class Messages { return trim($recipients, ', '); } + public static function get_notices_page($options) { + + if (!local_channel()) + return; + + $limit = 30; + + $offset = 0; + if ($options['offset']) { + $offset = intval($options['offset']); + } + + $notices = q("SELECT * FROM notify WHERE uid = %d + ORDER BY created DESC LIMIT $limit OFFSET $offset", + intval(local_channel()) + ); + + $i = 0; + $entries = []; + + foreach($notices as $notice) { + + $summary = trim(strip_tags(bbcode($notice['msg']))); + + if(strpos($summary, $notice['xname']) === 0) { + $summary = substr($summary, strlen($notice['xname']) + 1); + } + + $entries[$i]['author_name'] = $notice['xname']; + $entries[$i]['author_addr'] = $notice['url']; + $entries[$i]['info'] = ''; + $entries[$i]['created'] = datetime_convert('UTC', date_default_timezone_get(), $notice['created']); + $entries[$i]['summary'] = $summary; + $entries[$i]['b64mid'] = basename($notice['link']); + $entries[$i]['href'] = (($notice['ntype'] & NOTIFY_INTRO) ? $notice['link'] : z_root() . '/hq/' . basename($notice['link'])); + $entries[$i]['icon'] = (($notice['ntype'] & NOTIFY_INTRO) ? '<i class="fa fa-user-plus"></i>' : ''); + + $i++; + } + + $result = [ + 'offset' => ((count($entries) < $limit) ? -1 : intval($offset + $limit)), + 'entries' => $entries + ]; + + return $result; + } } diff --git a/Zotlabs/Widget/Notes.php b/Zotlabs/Widget/Notes.php index 05c1a0292..659b62390 100644 --- a/Zotlabs/Widget/Notes.php +++ b/Zotlabs/Widget/Notes.php @@ -18,13 +18,24 @@ class Notes { $tpl = get_markup_template('notes.tpl'); $o = replace_macros($tpl, array( - '$banner' => t('Notes'), '$text' => $text, - '$save' => t('Save'), + '$html' => bbcode($text), '$app' => ((isset($arr['app'])) ? true : false), - '$hidden' => ((isset($arr['hidden'])) ? true : false) + '$hidden' => ((isset($arr['hidden'])) ? true : false), + '$strings' => [ + 'title' => t('Notes'), + 'read' => t('Read mode'), + 'edit' => t('Edit mode'), + 'editing' => t('Editing'), + 'saving' => t('Saving'), + 'saved' => t('Saved'), + 'dots' => '<span class="jumping-dots"><span class="dot-1">.</span><span class="dot-2">.</span><span class="dot-3">.</span></span>' + ] )); + + + return $o; } } diff --git a/Zotlabs/Zot6/Zot6Handler.php b/Zotlabs/Zot6/Zot6Handler.php index dd241f2bf..4dc410f52 100644 --- a/Zotlabs/Zot6/Zot6Handler.php +++ b/Zotlabs/Zot6/Zot6Handler.php @@ -71,13 +71,11 @@ class Zot6Handler implements IHandler { where xchan_hash ='%s' limit 1", dbesc($recip) ); - /// @FIXME $msgtype is undefined $x = Libzot::refresh([ 'hubloc_id_url' => $hub['hubloc_id_url']], $r[0], $force); } } else { // system wide refresh - /// @FIXME $msgtype is undefined $x = Libzot::refresh(['hubloc_id_url' => $hub['hubloc_id_url']], null, $force); } |