diff options
Diffstat (limited to 'Zotlabs')
147 files changed, 11065 insertions, 1000 deletions
diff --git a/Zotlabs/Daemon/Cron.php b/Zotlabs/Daemon/Cron.php index d1c516f96..25e49b817 100644 --- a/Zotlabs/Daemon/Cron.php +++ b/Zotlabs/Daemon/Cron.php @@ -60,7 +60,7 @@ class Cron { drop_item($rr['id'],false,(($rr['item_wall']) ? DROPITEM_PHASE1 : DROPITEM_NORMAL)); if($rr['item_wall']) { // The notifier isn't normally invoked unless item_drop is interactive. - Zotlabs\Daemon\Master::Summon( [ 'Notifier', 'drop', $rr['id'] ] ); + Master::Summon( [ 'Notifier', 'drop', $rr['id'] ] ); } } } diff --git a/Zotlabs/Daemon/Master.php b/Zotlabs/Daemon/Master.php index 580df97db..3a71ee578 100644 --- a/Zotlabs/Daemon/Master.php +++ b/Zotlabs/Daemon/Master.php @@ -3,7 +3,6 @@ namespace Zotlabs\Daemon; if(array_search( __file__ , get_included_files()) === 0) { - require_once('include/cli_startup.php'); array_shift($argv); $argc = count($argv); @@ -17,14 +16,134 @@ if(array_search( __file__ , get_included_files()) === 0) { class Master { + static public $queueworker = null; + static public function Summon($arr) { proc_run('php','Zotlabs/Daemon/Master.php',$arr); } static public function Release($argc,$argv) { cli_startup(); - logger('Master: release: ' . print_r($argv,true), LOGGER_ALL,LOG_DEBUG); - $cls = '\\Zotlabs\\Daemon\\' . $argv[0]; - $cls::run($argc,$argv); + + $maxworkers = get_config('system','max_queue_workers'); + + if (!$maxworkers || $maxworkers == 0) { + logger('Master: release: ' . print_r($argv,true), LOGGER_ALL,LOG_DEBUG); + $cls = '\\Zotlabs\\Daemon\\' . $argv[0]; + $cls::run($argc,$argv); + self::ClearQueue(); + } else { + logger('Master: enqueue: ' . print_r($argv,true), LOGGER_ALL,LOG_DEBUG); + $workinfo = ['argc'=>$argc,'argv'=>$argv]; + q("insert into config (cat,k,v) values ('queuework','%s','%s')", + dbesc(uniqid('workitem:',true)), + dbesc(serialize($workinfo))); + self::Process(); + } + } + + static public function GetWorkerID() { + $maxworkers = get_config('system','max_queue_workers'); + $maxworkers = ($maxworkers) ? $maxworkers : 3; + + $workermaxage = get_config('system','max_queue_worker_age'); + $workermaxage = ($workermaxage) ? $workermaxage : 300; + + $workers = q("select * from config where cat='queueworkers' and k like '%s'", 'workerstarted_%'); + + if (count($workers) > $maxworkers) { + foreach ($workers as $idx => $worker) { + $curtime = time(); + $age = (intval($curtime) - intval($worker['v'])); + if ( $age > $workermaxage) { + logger("Prune worker: ".$worker['k'], LOGGER_ALL, LOGGER_DEBUG); + $k = explode('_',$worker['k']); + q("delete from config where cat='queueworkers' and k='%s'", + 'workerstarted_'.$k[1]); + q("update config set k='workitem' where cat='queuework' and k='%s'", + 'workitem_'.$k[1]); + unset($workers[$idx]); + } + } + if (count($workers) > $maxworkers) { + return false; + } + } + return uniqid(); + + } + + static public function Process() { + + self::$queueworker = self::GetWorkerID(); + + if (!self::$queueworker) { + logger('Master: unable to obtain worker ID.'); + killme(); + } + + set_config('queueworkers','workerstarted_'.self::$queueworker,time()); + + $workersleep = get_config('system','queue_worker_sleep'); + $workersleep = ($workersleep) ? $workersleep : 5; + cli_startup(); + + $work = q("update config set k='%s' where cat='queuework' and k like '%s' limit 1", + 'workitem_'.self::$queueworker, + dbesc('workitem:%')); + $jobs = 0; + while ($work) { + $workitem = q("select * from config where cat='queuework' and k='%s'", + 'workitem_'.self::$queueworker); + + if (isset($workitem[0])) { + $jobs++; + $workinfo = unserialize($workitem[0]['v']); + $argc = $workinfo['argc']; + $argv = $workinfo['argv']; + logger('Master: process: ' . print_r($argv,true), LOGGER_ALL,LOG_DEBUG); + + //Delete unclaimed duplicate workitems. + q("delete from config where cat='queuework' and k='workitem' and v='%s'", + serialize($argv)); + + $cls = '\\Zotlabs\\Daemon\\' . $argv[0]; + $cls::run($argc,$argv); + + //Right now we assume that if we get a return, everything is OK. + //At some point we may want to test whether the run returns true/false + // and requeue the work to be tried again. But we probably want + // to implement some sort of "retry interval" first. + + q("delete from config where cat='queuework' and k='%s'", + 'workitem_'.self::$queueworker); + } else { + break; + } + sleep ($workersleep); + $work = q("update config set k='%s' where cat='queuework' and k like '%s' limit 1", + 'workitem_'.self::$queueworker, + dbesc('workitem:%')); + + } + logger('Master: Worker Thread: queue items processed:' . $jobs); + q("delete from config where cat='queueworkers' and k='%s'", + 'workerstarted_'.self::$queueworker); } + + static public function ClearQueue() { + $work = q("select * from config where cat='queuework' and k like '%s'", + dbesc('workitem%')); + foreach ($work as $workitem) { + $workinfo = unserialize($workitem['v']); + $argc = $workinfo['argc']; + $argv = $workinfo['argv']; + logger('Master: process: ' . print_r($argv,true), LOGGER_ALL,LOG_DEBUG); + $cls = '\\Zotlabs\\Daemon\\' . $argv[0]; + $cls::run($argc,$argv); + } + $work = q("delete from config where cat='queuework' and k like '%s'", + dbesc('workitem%')); + } + } diff --git a/Zotlabs/Daemon/Notifier.php b/Zotlabs/Daemon/Notifier.php index fa2368a92..f74c8f11c 100644 --- a/Zotlabs/Daemon/Notifier.php +++ b/Zotlabs/Daemon/Notifier.php @@ -559,6 +559,8 @@ class Notifier { foreach($dhubs as $hub) { + logger('notifier_hub: ' . $hub['hubloc_url'],LOGGER_DEBUG); + if($hub['hubloc_network'] !== 'zot') { $narr = [ 'channel' => $channel, diff --git a/Zotlabs/Extend/Route.php b/Zotlabs/Extend/Route.php new file mode 100644 index 000000000..f7b90ec6e --- /dev/null +++ b/Zotlabs/Extend/Route.php @@ -0,0 +1,48 @@ +<?php + +namespace Zotlabs\Extend; + + +class Route { + + static function register($file,$modname) { + $rt = self::get(); + $rt[] = [ $file, $modname ]; + self::set($rt); + } + + static function unregister($file,$modname) { + $rt = self::get(); + if($rt) { + $n = []; + foreach($rt as $r) { + if($r[0] !== $file && $r[1] !== $modname) { + $n[] = $r; + } + } + self::set($n); + } + } + + static function unregister_by_file($file) { + $rt = self::get(); + if($rt) { + $n = []; + foreach($rt as $r) { + if($r[0] !== $file) { + $n[] = $r; + } + } + self::set($n); + } + } + + static function get() { + return get_config('system','routes',[]); + } + + static function set($r) { + return set_config('system','routes',$r); + } +} + diff --git a/Zotlabs/Extend/Widget.php b/Zotlabs/Extend/Widget.php new file mode 100644 index 000000000..dee64c61b --- /dev/null +++ b/Zotlabs/Extend/Widget.php @@ -0,0 +1,47 @@ +<?php + +namespace Zotlabs\Extend; + + +class Widget { + + static function register($file,$widget) { + $rt = self::get(); + $rt[] = [ $file, $widget ]; + self::set($rt); + } + + static function unregister($file,$widget) { + $rt = self::get(); + if($rt) { + $n = []; + foreach($rt as $r) { + if($r[0] !== $file && $r[1] !== $widget) { + $n[] = $r; + } + } + self::set($n); + } + } + + static function unregister_by_file($file) { + $rt = self::get(); + if($rt) { + $n = []; + foreach($rt as $r) { + if($r[0] !== $file) { + $n[] = $r; + } + } + self::set($n); + } + } + + static function get() { + return get_config('system','widgets',[]); + } + + static function set($r) { + return set_config('system','widgets',$r); + } +} diff --git a/Zotlabs/Identity/OAuth2Server.php b/Zotlabs/Identity/OAuth2Server.php index cbb4748fe..b747b95db 100644 --- a/Zotlabs/Identity/OAuth2Server.php +++ b/Zotlabs/Identity/OAuth2Server.php @@ -4,7 +4,7 @@ namespace Zotlabs\Identity; class OAuth2Server extends \OAuth2\Server { - public function __construct(OAuth2Storage $storage, $config = []) { + public function __construct(OAuth2Storage $storage, $config = null) { if(! is_array($config)) { $config = [ @@ -19,7 +19,8 @@ class OAuth2Server extends \OAuth2\Server { $this->addGrantType(new \OAuth2\GrantType\ClientCredentials($storage)); // Add the "Authorization Code" grant type (this is where the oauth magic happens) - $this->addGrantType(new \OAuth2\GrantType\AuthorizationCode($storage)); + // Need to use OpenID\GrantType to return id_token (see:https://github.com/bshaffer/oauth2-server-php/issues/443) + $this->addGrantType(new \OAuth2\OpenID\GrantType\AuthorizationCode($storage)); $keyStorage = new \OAuth2\Storage\Memory( [ 'keys' => [ diff --git a/Zotlabs/Identity/OAuth2Storage.php b/Zotlabs/Identity/OAuth2Storage.php index bc6db565c..bbf61cf2b 100644 --- a/Zotlabs/Identity/OAuth2Storage.php +++ b/Zotlabs/Identity/OAuth2Storage.php @@ -50,20 +50,78 @@ class OAuth2Storage extends \OAuth2\Storage\Pdo { public function getUser($username) { - $x = channelx_by_nick($username); + $x = channelx_by_n($username); if(! $x) { return false; } + $a = q("select * from account where account_id = %d", + intval($x['channel_account_id']) + ); + + $n = explode(' ', $x['channel_name']); + return( [ - 'username' => $x['channel_address'], - 'user_id' => $x['channel_id'], - 'firstName' => $x['channel_name'], - 'lastName' => '', - 'password' => 'NotARealPassword' + 'webfinger' => channel_reddress($x), + 'portable_id' => $x['channel_hash'], + 'email' => $a['account_email'], + 'username' => $x['channel_address'], + 'user_id' => $x['channel_id'], + 'name' => $x['channel_name'], + 'firstName' => ((count($n) > 1) ? $n[1] : $n[0]), + 'lastName' => ((count($n) > 2) ? $n[count($n) - 1] : ''), + 'picture' => $x['xchan_photo_l'] ] ); } + public function scopeExists($scope) { + // Report that the scope is valid even if it's not. + // We will only return a very small subset no matter what. + // @TODO: Truly validate the scope + // see vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/ScopeInterface.php and + // vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/Pdo.php + // for more info. + return true; + } + + public function getDefaultScope($client_id=null) { + // Do not REQUIRE a scope + // see vendor/bshaffer/oauth2-server-php/src/OAuth2/Storage/ScopeInterface.php and + // for more info. + return null; + } + + public function getUserClaims ($user_id, $claims) { + // Populate the CLAIMS requested (if any). + // @TODO: create a more reasonable/comprehensive list. + // @TODO: present claims on the AUTHORIZATION screen + + $userClaims = Array(); + $claims = explode (' ', trim($claims)); + $validclaims = Array ("name","preferred_username","webfinger","portable_id","email","picture","firstName","lastName"); + $claimsmap = Array ( + "webfinger" => 'webfinger', + "portable_id" => 'portable_id', + "name" => 'name', + "email" => 'email', + "preferred_username" => 'username', + "picture" => 'picture', + "given_name" => 'firstName', + "family_name" => 'lastName' + ); + $userinfo = $this->getUser($user_id); + foreach ($validclaims as $validclaim) { + if (in_array($validclaim,$claims)) { + $claimkey = $claimsmap[$validclaim]; + $userClaims[$validclaim] = $userinfo[$claimkey]; + } else { + $userClaims[$validclaim] = $validclaim; + } + } + $userClaims["sub"]=$user_id; + return $userClaims; + } + /** * plaintext passwords are bad! Override this for your application * @@ -78,4 +136,4 @@ class OAuth2Storage extends \OAuth2\Storage\Pdo { return true; } -}
\ No newline at end of file +} diff --git a/Zotlabs/Lib/Activity.php b/Zotlabs/Lib/Activity.php new file mode 100644 index 000000000..6ddbbb9db --- /dev/null +++ b/Zotlabs/Lib/Activity.php @@ -0,0 +1,1725 @@ +<?php + +namespace Zotlabs\Lib; + +use Zotlabs\Lib\Libzot; +use Zotlabs\Lib\Libsync; +use Zotlabs\Lib\ActivityStreams; +use Zotlabs\Lib\Group; + +class Activity { + + static function encode_object($x) { + + if(($x) && (! is_array($x)) && (substr(trim($x),0,1)) === '{' ) { + $x = json_decode($x,true); + } + if($x['type'] === ACTIVITY_OBJ_PERSON) { + return self::fetch_person($x); + } + if($x['type'] === ACTIVITY_OBJ_PROFILE) { + return self::fetch_profile($x); + } + if(in_array($x['type'], [ ACTIVITY_OBJ_NOTE, ACTIVITY_OBJ_ARTICLE ] )) { + return self::fetch_item($x); + } + if($x['type'] === ACTIVITY_OBJ_THING) { + return self::fetch_thing($x); + } + + return $x; + + } + + + static function fetch_person($x) { + return self::fetch_profile($x); + } + + static function fetch_profile($x) { + $r = q("select * from xchan where xchan_url like '%s' limit 1", + dbesc($x['id'] . '/%') + ); + if(! $r) { + $r = q("select * from xchan where xchan_hash = '%s' limit 1", + dbesc($x['id']) + ); + + } + if(! $r) + return []; + + return self::encode_person($r[0]); + + } + + static function fetch_thing($x) { + + $r = q("select * from obj where obj_type = %d and obj_obj = '%s' limit 1", + intval(TERM_OBJ_THING), + dbesc($x['id']) + ); + + if(! $r) + return []; + + $x = [ + 'type' => 'Object', + 'id' => z_root() . '/thing/' . $r[0]['obj_obj'], + 'name' => $r[0]['obj_term'] + ]; + + if($r[0]['obj_image']) + $x['image'] = $r[0]['obj_image']; + + return $x; + + } + + static function fetch_item($x) { + + if (array_key_exists('source',$x)) { + // This item is already processed and encoded + return $x; + } + + $r = q("select * from item where mid = '%s' limit 1", + dbesc($x['id']) + ); + if($r) { + xchan_query($r,true); + $r = fetch_post_tags($r,true); + return self::encode_item($r[0]); + } + } + + static function encode_item_collection($items,$id,$type,$extra = null) { + + $ret = [ + 'id' => z_root() . '/' . $id, + 'type' => $type, + 'totalItems' => count($items), + ]; + if($extra) + $ret = array_merge($ret,$extra); + + if($items) { + $x = []; + foreach($items as $i) { + $t = self::encode_activity($i); + if($t) + $x[] = $t; + } + if($type === 'OrderedCollection') + $ret['orderedItems'] = $x; + else + $ret['items'] = $x; + } + + return $ret; + } + + static function encode_follow_collection($items,$id,$type,$extra = null) { + + $ret = [ + 'id' => z_root() . '/' . $id, + 'type' => $type, + 'totalItems' => count($items), + ]; + if($extra) + $ret = array_merge($ret,$extra); + + if($items) { + $x = []; + foreach($items as $i) { + if($i['xchan_url']) { + $x[] = $i['xchan_url']; + } + } + + if($type === 'OrderedCollection') + $ret['orderedItems'] = $x; + else + $ret['items'] = $x; + } + + return $ret; + } + + + + + static function encode_item($i) { + + $ret = []; + + $objtype = self::activity_obj_mapper($i['obj_type']); + + if(intval($i['item_deleted'])) { + $ret['type'] = 'Tombstone'; + $ret['formerType'] = $objtype; + $ret['id'] = ((strpos($i['mid'],'http') === 0) ? $i['mid'] : z_root() . '/item/' . urlencode($i['mid'])); + return $ret; + } + + $ret['type'] = $objtype; + + $ret['id'] = ((strpos($i['mid'],'http') === 0) ? $i['mid'] : z_root() . '/item/' . urlencode($i['mid'])); + + if($i['title']) + $ret['title'] = bbcode($i['title']); + + $ret['published'] = datetime_convert('UTC','UTC',$i['created'],ATOM_TIME); + if($i['created'] !== $i['edited']) + $ret['updated'] = datetime_convert('UTC','UTC',$i['edited'],ATOM_TIME); + if($i['app']) { + $ret['instrument'] = [ 'type' => 'Service', 'name' => $i['app'] ]; + } + if($i['location'] || $i['coord']) { + $ret['location'] = [ 'type' => 'Place' ]; + if($i['location']) { + $ret['location']['name'] = $i['location']; + } + if($i['coord']) { + $l = explode(' ',$i['coord']); + $ret['location']['latitude'] = $l[0]; + $ret['location']['longitude'] = $l[1]; + } + } + + $ret['attributedTo'] = $i['author']['xchan_url']; + + if($i['id'] != $i['parent']) { + $ret['inReplyTo'] = ((strpos($i['parent_mid'],'http') === 0) ? $i['parent_mid'] : z_root() . '/item/' . urlencode($i['parent_mid'])); + } + + if($i['mimetype'] === 'text/bbcode') { + if($i['title']) + $ret['name'] = bbcode($i['title']); + if($i['summary']) + $ret['summary'] = bbcode($i['summary']); + $ret['content'] = bbcode($i['body']); + $ret['source'] = [ 'content' => $i['body'], 'mediaType' => 'text/bbcode' ]; + } + + $actor = self::encode_person($i['author'],false); + if($actor) + $ret['actor'] = $actor; + else + return []; + + $t = self::encode_taxonomy($i); + if($t) { + $ret['tag'] = $t; + } + + $a = self::encode_attachment($i); + if($a) { + $ret['attachment'] = $a; + } + + return $ret; + } + + static function decode_taxonomy($item) { + + $ret = []; + + if($item['tag']) { + foreach($item['tag'] as $t) { + if(! array_key_exists('type',$t)) + $t['type'] = 'Hashtag'; + + switch($t['type']) { + case 'Hashtag': + $ret[] = [ 'ttype' => TERM_HASHTAG, 'url' => $t['href'], 'term' => escape_tags((substr($t['name'],0,1) === '#') ? substr($t['name'],1) : $t['name']) ]; + break; + + case 'Mention': + $mention_type = substr($t['name'],0,1); + if($mention_type === '!') { + $ret[] = [ 'ttype' => TERM_FORUM, 'url' => $t['href'], 'term' => escape_tags(substr($t['name'],1)) ]; + } + else { + $ret[] = [ 'ttype' => TERM_MENTION, 'url' => $t['href'], 'term' => escape_tags((substr($t['name'],0,1) === '@') ? substr($t['name'],1) : $t['name']) ]; + } + break; + + default: + break; + } + } + } + + return $ret; + } + + + static function encode_taxonomy($item) { + + $ret = []; + + if($item['term']) { + foreach($item['term'] as $t) { + switch($t['ttype']) { + case TERM_HASHTAG: + // An id is required so if we don't have a url in the taxonomy, ignore it and keep going. + if($t['url']) { + $ret[] = [ 'id' => $t['url'], 'name' => '#' . $t['term'] ]; + } + break; + + case TERM_FORUM: + $ret[] = [ 'type' => 'Mention', 'href' => $t['url'], 'name' => '!' . $t['term'] ]; + break; + + case TERM_MENTION: + $ret[] = [ 'type' => 'Mention', 'href' => $t['url'], 'name' => '@' . $t['term'] ]; + break; + + default: + break; + } + } + } + + return $ret; + } + + static function encode_attachment($item) { + + $ret = []; + + if($item['attach']) { + $atts = json_decode($item['attach'],true); + if($atts) { + foreach($atts as $att) { + if(strpos($att['type'],'image')) { + $ret[] = [ 'type' => 'Image', 'url' => $att['href'] ]; + } + else { + $ret[] = [ 'type' => 'Link', 'mediaType' => $att['type'], 'href' => $att['href'] ]; + } + } + } + } + + return $ret; + } + + + static function decode_attachment($item) { + + $ret = []; + + if($item['attachment']) { + foreach($item['attachment'] as $att) { + $entry = []; + if($att['href']) + $entry['href'] = $att['href']; + elseif($att['url']) + $entry['href'] = $att['url']; + if($att['mediaType']) + $entry['type'] = $att['mediaType']; + elseif($att['type'] === 'Image') + $entry['type'] = 'image/jpeg'; + if($entry) + $ret[] = $entry; + } + } + + return $ret; + } + + + + static function encode_activity($i) { + + $ret = []; + $reply = false; + + if(intval($i['item_deleted'])) { + $ret['type'] = 'Tombstone'; + $ret['formerType'] = self::activity_obj_mapper($i['obj_type']); + $ret['id'] = ((strpos($i['mid'],'http') === 0) ? $i['mid'] : z_root() . '/item/' . urlencode($i['mid'])); + return $ret; + } + + $ret['type'] = self::activity_mapper($i['verb']); + $ret['id'] = ((strpos($i['mid'],'http') === 0) ? $i['mid'] : z_root() . '/activity/' . urlencode($i['mid'])); + + if($i['title']) + $ret['name'] = html2plain(bbcode($i['title'])); + + if($i['summary']) + $ret['summary'] = bbcode($i['summary']); + + if($ret['type'] === 'Announce') { + $tmp = preg_replace('/\[share(.*?)\[\/share\]/ism',EMPTY_STR, $i['body']); + $ret['content'] = bbcode($tmp); + $ret['source'] = [ + 'content' => $i['body'], + 'mediaType' => 'text/bbcode' + ]; + } + + $ret['published'] = datetime_convert('UTC','UTC',$i['created'],ATOM_TIME); + if($i['created'] !== $i['edited']) + $ret['updated'] = datetime_convert('UTC','UTC',$i['edited'],ATOM_TIME); + if($i['app']) { + $ret['instrument'] = [ 'type' => 'Service', 'name' => $i['app'] ]; + } + if($i['location'] || $i['coord']) { + $ret['location'] = [ 'type' => 'Place' ]; + if($i['location']) { + $ret['location']['name'] = $i['location']; + } + if($i['coord']) { + $l = explode(' ',$i['coord']); + $ret['location']['latitude'] = $l[0]; + $ret['location']['longitude'] = $l[1]; + } + } + + if($i['id'] != $i['parent']) { + $ret['inReplyTo'] = ((strpos($i['parent_mid'],'http') === 0) ? $i['parent_mid'] : z_root() . '/item/' . urlencode($i['parent_mid'])); + $reply = true; + + if($i['item_private']) { + $d = q("select xchan_url, xchan_addr, xchan_name from item left join xchan on xchan_hash = author_xchan where id = %d limit 1", + intval($i['parent']) + ); + if($d) { + $is_directmessage = false; + $recips = get_iconfig($i['parent'], 'activitypub', 'recips'); + + if(in_array($i['author']['xchan_url'], $recips['to'])) { + $reply_url = $d[0]['xchan_url']; + $is_directmessage = true; + } + else { + $reply_url = z_root() . '/followers/' . substr($i['author']['xchan_addr'],0,strpos($i['author']['xchan_addr'],'@')); + } + + $reply_addr = (($d[0]['xchan_addr']) ? $d[0]['xchan_addr'] : $d[0]['xchan_name']); + } + } + + } + + $actor = self::encode_person($i['author'],false); + if($actor) + $ret['actor'] = $actor; + else + return []; + + if($i['obj']) { + if(! is_array($i['obj'])) { + $i['obj'] = json_decode($i['obj'],true); + } + $obj = self::encode_object($i['obj']); + if($obj) + $ret['object'] = $obj; + else + return []; + } + else { + $obj = self::encode_item($i); + if($obj) + $ret['object'] = $obj; + else + return []; + } + + if($i['target']) { + if(! is_array($i['target'])) { + $i['target'] = json_decode($i['target'],true); + } + $tgt = self::encode_object($i['target']); + if($tgt) + $ret['target'] = $tgt; + else + return []; + } + + return $ret; + } + + static function map_mentions($i) { + if(! $i['term']) { + return []; + } + + $list = []; + + foreach ($i['term'] as $t) { + if($t['ttype'] == TERM_MENTION) { + $list[] = $t['url']; + } + } + + return $list; + } + + static function map_acl($i,$mentions = false) { + + $private = false; + $list = []; + $x = collect_recipients($i,$private); + if($x) { + stringify_array_elms($x); + if(! $x) + return; + + $strict = (($mentions) ? true : get_config('activitypub','compliance')); + + $sql_extra = (($strict) ? " and xchan_network = 'activitypub' " : ''); + + $details = q("select xchan_url, xchan_addr, xchan_name from xchan where xchan_hash in (" . implode(',',$x) . ") $sql_extra"); + + if($details) { + foreach($details as $d) { + if($mentions) { + $list[] = [ 'type' => 'Mention', 'href' => $d['xchan_url'], 'name' => '@' . (($d['xchan_addr']) ? $d['xchan_addr'] : $d['xchan_name']) ]; + } + else { + $list[] = $d['xchan_url']; + } + } + } + } + + return $list; + + } + + + static function encode_person($p, $extended = true) { + + if(! $p['xchan_url']) + return []; + + if(! $extended) { + return $p['xchan_url']; + } + $ret = []; + + $ret['type'] = 'Person'; + $ret['id'] = $p['xchan_url']; + if($p['xchan_addr'] && strpos($p['xchan_addr'],'@')) + $ret['preferredUsername'] = substr($p['xchan_addr'],0,strpos($p['xchan_addr'],'@')); + $ret['name'] = $p['xchan_name']; + $ret['updated'] = datetime_convert('UTC','UTC',$p['xchan_name_date'],ATOM_TIME); + $ret['icon'] = [ + 'type' => 'Image', + 'mediaType' => (($p['xchan_photo_mimetype']) ? $p['xchan_photo_mimetype'] : 'image/png' ), + 'updated' => datetime_convert('UTC','UTC',$p['xchan_photo_date'],ATOM_TIME), + 'url' => $p['xchan_photo_l'], + 'height' => 300, + 'width' => 300, + ]; + $ret['url'] = [ + [ + 'type' => 'Link', + 'mediaType' => 'text/html', + 'href' => $p['xchan_url'] + ], + [ + 'type' => 'Link', + 'mediaType' => 'text/x-zot+json', + 'href' => $p['xchan_url'] + ] + ]; + + $arr = [ 'xchan' => $p, 'encoded' => $ret ]; + call_hooks('encode_person', $arr); + $ret = $arr['encoded']; + + + return $ret; + } + + + static function activity_mapper($verb) { + + if(strpos($verb,'/') === false) { + return $verb; + } + + $acts = [ + 'http://activitystrea.ms/schema/1.0/post' => 'Create', + 'http://activitystrea.ms/schema/1.0/share' => 'Announce', + 'http://activitystrea.ms/schema/1.0/update' => 'Update', + 'http://activitystrea.ms/schema/1.0/like' => 'Like', + 'http://activitystrea.ms/schema/1.0/favorite' => 'Like', + 'http://purl.org/zot/activity/dislike' => 'Dislike', + 'http://activitystrea.ms/schema/1.0/tag' => 'Add', + 'http://activitystrea.ms/schema/1.0/follow' => 'Follow', + 'http://activitystrea.ms/schema/1.0/unfollow' => 'Unfollow', + ]; + + + if(array_key_exists($verb,$acts) && $acts[$verb]) { + return $acts[$verb]; + } + + // Reactions will just map to normal activities + + if(strpos($verb,ACTIVITY_REACT) !== false) + return 'Create'; + if(strpos($verb,ACTIVITY_MOOD) !== false) + return 'Create'; + + if(strpos($verb,ACTIVITY_POKE) !== false) + return 'Activity'; + + // We should return false, however this will trigger an uncaught execption and crash + // the delivery system if encountered by the JSON-LDSignature library + + logger('Unmapped activity: ' . $verb); + return 'Create'; + // return false; +} + + + static function activity_obj_mapper($obj) { + + if(strpos($obj,'/') === false) { + return $obj; + } + + $objs = [ + 'http://activitystrea.ms/schema/1.0/note' => 'Note', + 'http://activitystrea.ms/schema/1.0/comment' => 'Note', + 'http://activitystrea.ms/schema/1.0/person' => 'Person', + 'http://purl.org/zot/activity/profile' => 'Profile', + 'http://activitystrea.ms/schema/1.0/photo' => 'Image', + 'http://activitystrea.ms/schema/1.0/profile-photo' => 'Icon', + 'http://activitystrea.ms/schema/1.0/event' => 'Event', + 'http://activitystrea.ms/schema/1.0/wiki' => 'Document', + 'http://purl.org/zot/activity/location' => 'Place', + 'http://purl.org/zot/activity/chessgame' => 'Game', + 'http://purl.org/zot/activity/tagterm' => 'zot:Tag', + 'http://purl.org/zot/activity/thing' => 'Object', + 'http://purl.org/zot/activity/file' => 'zot:File', + 'http://purl.org/zot/activity/mood' => 'zot:Mood', + + ]; + + if(array_key_exists($obj,$objs)) { + return $objs[$obj]; + } + + logger('Unmapped activity object: ' . $obj); + return 'Note'; + + // return false; + + } + + + static function follow($channel,$act) { + + $contact = null; + $their_follow_id = null; + + /* + * + * if $act->type === 'Follow', actor is now following $channel + * if $act->type === 'Accept', actor has approved a follow request from $channel + * + */ + + $person_obj = $act->actor; + + if($act->type === 'Follow') { + $their_follow_id = $act->id; + } + elseif($act->type === 'Accept') { + $my_follow_id = z_root() . '/follow/' . $contact['id']; + } + + if(is_array($person_obj)) { + + // store their xchan and hubloc + + self::actor_store($person_obj['id'],$person_obj); + + // Find any existing abook record + + $r = q("select * from abook left join xchan on abook_xchan = xchan_hash where abook_xchan = '%s' and abook_channel = %d limit 1", + dbesc($person_obj['id']), + intval($channel['channel_id']) + ); + if($r) { + $contact = $r[0]; + } + } + + $x = \Zotlabs\Access\PermissionRoles::role_perms('social'); + $p = \Zotlabs\Access\Permissions::FilledPerms($x['perms_connect']); + $their_perms = \Zotlabs\Access\Permissions::serialise($p); + + if($contact && $contact['abook_id']) { + + // A relationship of some form already exists on this site. + + switch($act->type) { + + case 'Follow': + + // A second Follow request, but we haven't approved the first one + + if($contact['abook_pending']) { + return; + } + + // We've already approved them or followed them first + // Send an Accept back to them + + set_abconfig($channel['channel_id'],$person_obj['id'],'pubcrawl','their_follow_id', $their_follow_id); + \Zotlabs\Daemon\Master::Summon([ 'Notifier', 'permissions_accept', $contact['abook_id'] ]); + return; + + case 'Accept': + + // They accepted our Follow request - set default permissions + + set_abconfig($channel['channel_id'],$contact['abook_xchan'],'system','their_perms',$their_perms); + + $abook_instance = $contact['abook_instance']; + + if(strpos($abook_instance,z_root()) === false) { + if($abook_instance) + $abook_instance .= ','; + $abook_instance .= z_root(); + + $r = q("update abook set abook_instance = '%s', abook_not_here = 0 + where abook_id = %d and abook_channel = %d", + dbesc($abook_instance), + intval($contact['abook_id']), + intval($channel['channel_id']) + ); + } + + return; + default: + return; + + } + } + + // No previous relationship exists. + + if($act->type === 'Accept') { + // This should not happen unless we deleted the connection before it was accepted. + return; + } + + // From here on out we assume a Follow activity to somebody we have no existing relationship with + + set_abconfig($channel['channel_id'],$person_obj['id'],'pubcrawl','their_follow_id', $their_follow_id); + + // The xchan should have been created by actor_store() above + + $r = q("select * from xchan where xchan_hash = '%s' and xchan_network = 'activitypub' limit 1", + dbesc($person_obj['id']) + ); + + if(! $r) { + logger('xchan not found for ' . $person_obj['id']); + return; + } + $ret = $r[0]; + + $p = \Zotlabs\Access\Permissions::connect_perms($channel['channel_id']); + $my_perms = \Zotlabs\Access\Permissions::serialise($p['perms']); + $automatic = $p['automatic']; + + $closeness = get_pconfig($channel['channel_id'],'system','new_abook_closeness',80); + + $r = abook_store_lowlevel( + [ + 'abook_account' => intval($channel['channel_account_id']), + 'abook_channel' => intval($channel['channel_id']), + 'abook_xchan' => $ret['xchan_hash'], + 'abook_closeness' => intval($closeness), + 'abook_created' => datetime_convert(), + 'abook_updated' => datetime_convert(), + 'abook_connected' => datetime_convert(), + 'abook_dob' => NULL_DATE, + 'abook_pending' => intval(($automatic) ? 0 : 1), + 'abook_instance' => z_root() + ] + ); + + if($my_perms) + set_abconfig($channel['channel_id'],$ret['xchan_hash'],'system','my_perms',$my_perms); + + if($their_perms) + set_abconfig($channel['channel_id'],$ret['xchan_hash'],'system','their_perms',$their_perms); + + + if($r) { + logger("New ActivityPub follower for {$channel['channel_name']}"); + + $new_connection = q("select * from abook left join xchan on abook_xchan = xchan_hash left join hubloc on hubloc_hash = xchan_hash where abook_channel = %d and abook_xchan = '%s' order by abook_created desc limit 1", + intval($channel['channel_id']), + dbesc($ret['xchan_hash']) + ); + if($new_connection) { + \Zotlabs\Lib\Enotify::submit( + [ + 'type' => NOTIFY_INTRO, + 'from_xchan' => $ret['xchan_hash'], + 'to_xchan' => $channel['channel_hash'], + 'link' => z_root() . '/connedit/' . $new_connection[0]['abook_id'], + ] + ); + + if($my_perms && $automatic) { + // send an Accept for this Follow activity + \Zotlabs\Daemon\Master::Summon([ 'Notifier', 'permissions_accept', $new_connection[0]['abook_id'] ]); + // Send back a Follow notification to them + \Zotlabs\Daemon\Master::Summon([ 'Notifier', 'permissions_create', $new_connection[0]['abook_id'] ]); + } + + $clone = array(); + foreach($new_connection[0] as $k => $v) { + if(strpos($k,'abook_') === 0) { + $clone[$k] = $v; + } + } + unset($clone['abook_id']); + unset($clone['abook_account']); + unset($clone['abook_channel']); + + $abconfig = load_abconfig($channel['channel_id'],$clone['abook_xchan']); + + if($abconfig) + $clone['abconfig'] = $abconfig; + + Libsync::build_sync_packet($channel['channel_id'], [ 'abook' => array($clone) ] ); + } + } + + + /* If there is a default group for this channel and permissions are automatic, add this member to it */ + + if($channel['channel_default_group'] && $automatic) { + $g = Group::rec_byhash($channel['channel_id'],$channel['channel_default_group']); + if($g) + Group::member_add($channel['channel_id'],'',$ret['xchan_hash'],$g['id']); + } + + + return; + + } + + + static function unfollow($channel,$act) { + + $contact = null; + + /* @FIXME This really needs to be a signed request. */ + + /* actor is unfollowing $channel */ + + $person_obj = $act->actor; + + if(is_array($person_obj)) { + + $r = q("select * from abook left join xchan on abook_xchan = xchan_hash where abook_xchan = '%s' and abook_channel = %d limit 1", + dbesc($person_obj['id']), + intval($channel['channel_id']) + ); + if($r) { + // remove all permissions they provided + del_abconfig($channel['channel_id'],$r[0]['xchan_hash'],'system','their_perms',EMPTY_STR); + } + } + + return; + } + + + + + static function actor_store($url,$person_obj) { + + if(! is_array($person_obj)) + return; + + $name = $person_obj['name']; + if(! $name) + $name = $person_obj['preferredUsername']; + if(! $name) + $name = t('Unknown'); + + if($person_obj['icon']) { + if(is_array($person_obj['icon'])) { + if(array_key_exists('url',$person_obj['icon'])) + $icon = $person_obj['icon']['url']; + else + $icon = $person_obj['icon'][0]['url']; + } + else + $icon = $person_obj['icon']; + } + + if(is_array($person_obj['url']) && array_key_exists('href', $person_obj['url'])) + $profile = $person_obj['url']['href']; + else + $profile = $url; + + + $inbox = $person_obj['inbox']; + + $collections = []; + + if($inbox) { + $collections['inbox'] = $inbox; + if($person_obj['outbox']) + $collections['outbox'] = $person_obj['outbox']; + if($person_obj['followers']) + $collections['followers'] = $person_obj['followers']; + if($person_obj['following']) + $collections['following'] = $person_obj['following']; + if($person_obj['endpoints'] && $person_obj['endpoints']['sharedInbox']) + $collections['sharedInbox'] = $person_obj['endpoints']['sharedInbox']; + } + + if(array_key_exists('publicKey',$person_obj) && array_key_exists('publicKeyPem',$person_obj['publicKey'])) { + if($person_obj['id'] === $person_obj['publicKey']['owner']) { + $pubkey = $person_obj['publicKey']['publicKeyPem']; + if(strstr($pubkey,'RSA ')) { + $pubkey = rsatopem($pubkey); + } + } + } + + $r = q("select * from xchan where xchan_hash = '%s' limit 1", + dbesc($url) + ); + if(! $r) { + // create a new record + $r = xchan_store_lowlevel( + [ + 'xchan_hash' => $url, + 'xchan_guid' => $url, + 'xchan_pubkey' => $pubkey, + 'xchan_addr' => '', + 'xchan_url' => $profile, + 'xchan_name' => $name, + 'xchan_name_date' => datetime_convert(), + 'xchan_network' => 'activitypub' + ] + ); + } + else { + + // Record exists. Cache existing records for one week at most + // then refetch to catch updated profile photos, names, etc. + + $d = datetime_convert('UTC','UTC','now - 1 week'); + if($r[0]['xchan_name_date'] > $d) + return; + + // update existing record + $r = q("update xchan set xchan_name = '%s', xchan_pubkey = '%s', xchan_network = '%s', xchan_name_date = '%s' where xchan_hash = '%s'", + dbesc($name), + dbesc($pubkey), + dbesc('activitypub'), + dbesc(datetime_convert()), + dbesc($url) + ); + } + + if($collections) { + set_xconfig($url,'activitypub','collections',$collections); + } + + $r = q("select * from hubloc where hubloc_hash = '%s' limit 1", + dbesc($url) + ); + + + $m = parse_url($url); + if($m) { + $hostname = $m['host']; + $baseurl = $m['scheme'] . '://' . $m['host'] . (($m['port']) ? ':' . $m['port'] : ''); + } + + if(! $r) { + $r = hubloc_store_lowlevel( + [ + 'hubloc_guid' => $url, + 'hubloc_hash' => $url, + 'hubloc_addr' => '', + 'hubloc_network' => 'activitypub', + 'hubloc_url' => $baseurl, + 'hubloc_host' => $hostname, + 'hubloc_callback' => $inbox, + 'hubloc_updated' => datetime_convert(), + 'hubloc_primary' => 1 + ] + ); + } + + if(! $icon) + $icon = z_root() . '/' . get_default_profile_photo(300); + + $photos = import_xchan_photo($icon,$url); + $r = q("update xchan set xchan_photo_date = '%s', xchan_photo_l = '%s', xchan_photo_m = '%s', xchan_photo_s = '%s', xchan_photo_mimetype = '%s' where xchan_hash = '%s'", + dbescdate(datetime_convert('UTC','UTC',$arr['photo_updated'])), + dbesc($photos[0]), + dbesc($photos[1]), + dbesc($photos[2]), + dbesc($photos[3]), + dbesc($url) + ); + + } + + + static function create_action($channel,$observer_hash,$act) { + + if(in_array($act->obj['type'], [ 'Note', 'Article', 'Video' ])) { + self::create_note($channel,$observer_hash,$act); + } + + + } + + static function announce_action($channel,$observer_hash,$act) { + + if(in_array($act->type, [ 'Announce' ])) { + self::announce_note($channel,$observer_hash,$act); + } + + } + + + static function like_action($channel,$observer_hash,$act) { + + if(in_array($act->obj['type'], [ 'Note', 'Article', 'Video' ])) { + self::like_note($channel,$observer_hash,$act); + } + + + } + + // sort function width decreasing + + static function as_vid_sort($a,$b) { + if($a['width'] === $b['width']) + return 0; + return (($a['width'] > $b['width']) ? -1 : 1); + } + + static function create_note($channel,$observer_hash,$act) { + + $s = []; + + // Mastodon only allows visibility in public timelines if the public inbox is listed in the 'to' field. + // They are hidden in the public timeline if the public inbox is listed in the 'cc' field. + // This is not part of the activitypub protocol - we might change this to show all public posts in pubstream at some point. + $pubstream = ((is_array($act->obj) && array_key_exists('to', $act->obj) && in_array(ACTIVITY_PUBLIC_INBOX, $act->obj['to'])) ? true : false); + $is_sys_channel = is_sys_channel($channel['channel_id']); + + $parent = ((array_key_exists('inReplyTo',$act->obj)) ? urldecode($act->obj['inReplyTo']) : ''); + if($parent) { + + $r = q("select * from item where uid = %d and ( mid = '%s' or mid = '%s' ) limit 1", + intval($channel['channel_id']), + dbesc($parent), + dbesc(basename($parent)) + ); + + if(! $r) { + logger('parent not found.'); + return; + } + + if($r[0]['owner_xchan'] === $channel['channel_hash']) { + if(! perm_is_allowed($channel['channel_id'],$observer_hash,'send_stream') && ! ($is_sys_channel && $pubstream)) { + logger('no comment permission.'); + return; + } + } + + $s['parent_mid'] = $r[0]['mid']; + $s['owner_xchan'] = $r[0]['owner_xchan']; + $s['author_xchan'] = $observer_hash; + + } + else { + if(! perm_is_allowed($channel['channel_id'],$observer_hash,'send_stream') && ! ($is_sys_channel && $pubstream)) { + logger('no permission'); + return; + } + $s['owner_xchan'] = $s['author_xchan'] = $observer_hash; + } + + $abook = q("select * from abook where abook_xchan = '%s' and abook_channel = %d limit 1", + dbesc($observer_hash), + intval($channel['channel_id']) + ); + + $content = self::get_content($act->obj); + + if(! $content) { + logger('no content'); + return; + } + + $s['aid'] = $channel['channel_account_id']; + $s['uid'] = $channel['channel_id']; + $s['mid'] = urldecode($act->obj['id']); + $s['plink'] = urldecode($act->obj['id']); + + + if($act->data['published']) { + $s['created'] = datetime_convert('UTC','UTC',$act->data['published']); + } + elseif($act->obj['published']) { + $s['created'] = datetime_convert('UTC','UTC',$act->obj['published']); + } + if($act->data['updated']) { + $s['edited'] = datetime_convert('UTC','UTC',$act->data['updated']); + } + elseif($act->obj['updated']) { + $s['edited'] = datetime_convert('UTC','UTC',$act->obj['updated']); + } + + if(! $s['created']) + $s['created'] = datetime_convert(); + + if(! $s['edited']) + $s['edited'] = $s['created']; + + + if(! $s['parent_mid']) + $s['parent_mid'] = $s['mid']; + + + $s['title'] = self::bb_content($content,'name'); + $s['summary'] = self::bb_content($content,'summary'); + $s['body'] = self::bb_content($content,'content'); + $s['verb'] = ACTIVITY_POST; + $s['obj_type'] = ACTIVITY_OBJ_NOTE; + + $instrument = $act->get_property_obj('instrument'); + if(! $instrument) + $instrument = $act->get_property_obj('instrument',$act->obj); + + if($instrument && array_key_exists('type',$instrument) + && $instrument['type'] === 'Service' && array_key_exists('name',$instrument)) { + $s['app'] = escape_tags($instrument['name']); + } + + if($channel['channel_system']) { + if(! \Zotlabs\Lib\MessageFilter::evaluate($s,get_config('system','pubstream_incl'),get_config('system','pubstream_excl'))) { + logger('post is filtered'); + return; + } + } + + + if($abook) { + if(! post_is_importable($s,$abook[0])) { + logger('post is filtered'); + return; + } + } + + if($act->obj['conversation']) { + set_iconfig($s,'ostatus','conversation',$act->obj['conversation'],1); + } + + $a = self::decode_taxonomy($act->obj); + if($a) { + $s['term'] = $a; + } + + $a = self::decode_attachment($act->obj); + if($a) { + $s['attach'] = $a; + } + + if($act->obj['type'] === 'Note' && $s['attach']) { + $s['body'] .= self::bb_attach($s['attach']); + } + + // we will need a hook here to extract magnet links e.g. peertube + // right now just link to the largest mp4 we find that will fit in our + // standard content region + + if($act->obj['type'] === 'Video') { + + $vtypes = [ + 'video/mp4', + 'video/ogg', + 'video/webm' + ]; + + $mps = []; + if(array_key_exists('url',$act->obj) && is_array($act->obj['url'])) { + foreach($act->obj['url'] as $vurl) { + if(in_array($vurl['mimeType'], $vtypes)) { + if(! array_key_exists('width',$vurl)) { + $vurl['width'] = 0; + } + $mps[] = $vurl; + } + } + } + if($mps) { + usort($mps,'as_vid_sort'); + foreach($mps as $m) { + if(intval($m['width']) < 500) { + $s['body'] .= "\n\n" . '[video]' . $m['href'] . '[/video]'; + break; + } + } + } + } + + if($act->recips && (! in_array(ACTIVITY_PUBLIC_INBOX,$act->recips))) + $s['item_private'] = 1; + + set_iconfig($s,'activitypub','recips',$act->raw_recips); + if($parent) { + set_iconfig($s,'activitypub','rawmsg',$act->raw,1); + } + + $x = null; + + $r = q("select created, edited from item where mid = '%s' and uid = %d limit 1", + dbesc($s['mid']), + intval($s['uid']) + ); + if($r) { + if($s['edited'] > $r[0]['edited']) { + $x = item_store_update($s); + } + else { + return; + } + } + else { + $x = item_store($s); + } + + if(is_array($x) && $x['item_id']) { + if($parent) { + if($s['owner_xchan'] === $channel['channel_hash']) { + // We are the owner of this conversation, so send all received comments back downstream + Zotlabs\Daemon\Master::Summon(array('Notifier','comment-import',$x['item_id'])); + } + $r = q("select * from item where id = %d limit 1", + intval($x['item_id']) + ); + if($r) { + send_status_notifications($x['item_id'],$r[0]); + } + } + sync_an_item($channel['channel_id'],$x['item_id']); + } + + } + + + static function decode_note($act) { + + $s = []; + + + + $content = self::get_content($act->obj); + + $s['owner_xchan'] = $act->actor['id']; + $s['author_xchan'] = $act->actor['id']; + + $s['mid'] = $act->id; + $s['parent_mid'] = $act->parent_id; + + + if($act->data['published']) { + $s['created'] = datetime_convert('UTC','UTC',$act->data['published']); + } + elseif($act->obj['published']) { + $s['created'] = datetime_convert('UTC','UTC',$act->obj['published']); + } + if($act->data['updated']) { + $s['edited'] = datetime_convert('UTC','UTC',$act->data['updated']); + } + elseif($act->obj['updated']) { + $s['edited'] = datetime_convert('UTC','UTC',$act->obj['updated']); + } + + if(! $s['created']) + $s['created'] = datetime_convert(); + + if(! $s['edited']) + $s['edited'] = $s['created']; + + if(in_array($act->type,['Announce'])) { + $root_content = self::get_content($act->raw); + + $s['title'] = self::bb_content($root_content,'name'); + $s['summary'] = self::bb_content($root_content,'summary'); + $s['body'] = (self::bb_content($root_content,'bbcode') ? : self::bb_content($root_content,'content')); + + if(strpos($s['body'],'[share') === false) { + + // @fixme - error check and set defaults + + $name = urlencode($act->obj['actor']['name']); + $profile = $act->obj['actor']['id']; + $photo = $act->obj['icon']['url']; + + $s['body'] .= "\r\n[share author='" . $name . + "' profile='" . $profile . + "' avatar='" . $photo . + "' link='" . $act->obj['id'] . + "' auth='" . ((is_matrix_url($act->obj['id'])) ? 'true' : 'false' ) . + "' posted='" . $act->obj['published'] . + "' message_id='" . $act->obj['id'] . + "']"; + } + } + else { + $s['title'] = self::bb_content($content,'name'); + $s['summary'] = self::bb_content($content,'summary'); + $s['body'] = (self::bb_content($content,'bbcode') ? : self::bb_content($content,'content')); + } + + $s['verb'] = self::activity_mapper($act->type); + + if($act->type === 'Tombstone') { + $s['item_deleted'] = 1; + } + + $s['obj_type'] = self::activity_obj_mapper($act->obj['type']); + $s['obj'] = $act->obj; + + $instrument = $act->get_property_obj('instrument'); + if(! $instrument) + $instrument = $act->get_property_obj('instrument',$act->obj); + + if($instrument && array_key_exists('type',$instrument) + && $instrument['type'] === 'Service' && array_key_exists('name',$instrument)) { + $s['app'] = escape_tags($instrument['name']); + } + + $a = self::decode_taxonomy($act->obj); + if($a) { + $s['term'] = $a; + } + + $a = self::decode_attachment($act->obj); + if($a) { + $s['attach'] = $a; + } + + // we will need a hook here to extract magnet links e.g. peertube + // right now just link to the largest mp4 we find that will fit in our + // standard content region + + if($act->obj['type'] === 'Video') { + + $vtypes = [ + 'video/mp4', + 'video/ogg', + 'video/webm' + ]; + + $mps = []; + if(array_key_exists('url',$act->obj) && is_array($act->obj['url'])) { + foreach($act->obj['url'] as $vurl) { + if(in_array($vurl['mimeType'], $vtypes)) { + if(! array_key_exists('width',$vurl)) { + $vurl['width'] = 0; + } + $mps[] = $vurl; + } + } + } + if($mps) { + usort($mps,'as_vid_sort'); + foreach($mps as $m) { + if(intval($m['width']) < 500) { + $s['body'] .= "\n\n" . '[video]' . $m['href'] . '[/video]'; + break; + } + } + } + } + + if($act->recips && (! in_array(ACTIVITY_PUBLIC_INBOX,$act->recips))) + $s['item_private'] = 1; + + set_iconfig($s,'activitypub','recips',$act->raw_recips); + + if($parent) { + set_iconfig($s,'activitypub','rawmsg',$act->raw,1); + } + + return $s; + + } + + + + static function announce_note($channel,$observer_hash,$act) { + + $s = []; + + $is_sys_channel = is_sys_channel($channel['channel_id']); + + // Mastodon only allows visibility in public timelines if the public inbox is listed in the 'to' field. + // They are hidden in the public timeline if the public inbox is listed in the 'cc' field. + // This is not part of the activitypub protocol - we might change this to show all public posts in pubstream at some point. + $pubstream = ((is_array($act->obj) && array_key_exists('to', $act->obj) && in_array(ACTIVITY_PUBLIC_INBOX, $act->obj['to'])) ? true : false); + + if(! perm_is_allowed($channel['channel_id'],$observer_hash,'send_stream') && ! ($is_sys_channel && $pubstream)) { + logger('no permission'); + return; + } + + $content = self::get_content($act->obj); + + if(! $content) { + logger('no content'); + return; + } + + $s['owner_xchan'] = $s['author_xchan'] = $observer_hash; + + $s['aid'] = $channel['channel_account_id']; + $s['uid'] = $channel['channel_id']; + $s['mid'] = urldecode($act->obj['id']); + $s['plink'] = urldecode($act->obj['id']); + + if(! $s['created']) + $s['created'] = datetime_convert(); + + if(! $s['edited']) + $s['edited'] = $s['created']; + + + $s['parent_mid'] = $s['mid']; + + $s['verb'] = ACTIVITY_POST; + $s['obj_type'] = ACTIVITY_OBJ_NOTE; + $s['app'] = t('ActivityPub'); + + if($channel['channel_system']) { + if(! \Zotlabs\Lib\MessageFilter::evaluate($s,get_config('system','pubstream_incl'),get_config('system','pubstream_excl'))) { + logger('post is filtered'); + return; + } + } + + $abook = q("select * from abook where abook_xchan = '%s' and abook_channel = %d limit 1", + dbesc($observer_hash), + intval($channel['channel_id']) + ); + + if($abook) { + if(! post_is_importable($s,$abook[0])) { + logger('post is filtered'); + return; + } + } + + if($act->obj['conversation']) { + set_iconfig($s,'ostatus','conversation',$act->obj['conversation'],1); + } + + $a = self::decode_taxonomy($act->obj); + if($a) { + $s['term'] = $a; + } + + $a = self::decode_attachment($act->obj); + if($a) { + $s['attach'] = $a; + } + + $body = "[share author='" . urlencode($act->sharee['name']) . + "' profile='" . $act->sharee['url'] . + "' avatar='" . $act->sharee['photo_s'] . + "' link='" . ((is_array($act->obj['url'])) ? $act->obj['url']['href'] : $act->obj['url']) . + "' auth='" . ((is_matrix_url($act->obj['url'])) ? 'true' : 'false' ) . + "' posted='" . $act->obj['published'] . + "' message_id='" . $act->obj['id'] . + "']"; + + if($content['name']) + $body .= self::bb_content($content,'name') . "\r\n"; + + $body .= self::bb_content($content,'content'); + + if($act->obj['type'] === 'Note' && $s['attach']) { + $body .= self::bb_attach($s['attach']); + } + + $body .= "[/share]"; + + $s['title'] = self::bb_content($content,'name'); + $s['body'] = $body; + + if($act->recips && (! in_array(ACTIVITY_PUBLIC_INBOX,$act->recips))) + $s['item_private'] = 1; + + set_iconfig($s,'activitypub','recips',$act->raw_recips); + + $r = q("select created, edited from item where mid = '%s' and uid = %d limit 1", + dbesc($s['mid']), + intval($s['uid']) + ); + if($r) { + if($s['edited'] > $r[0]['edited']) { + $x = item_store_update($s); + } + else { + return; + } + } + else { + $x = item_store($s); + } + + + if(is_array($x) && $x['item_id']) { + if($parent) { + if($s['owner_xchan'] === $channel['channel_hash']) { + // We are the owner of this conversation, so send all received comments back downstream + Zotlabs\Daemon\Master::Summon(array('Notifier','comment-import',$x['item_id'])); + } + $r = q("select * from item where id = %d limit 1", + intval($x['item_id']) + ); + if($r) { + send_status_notifications($x['item_id'],$r[0]); + } + } + sync_an_item($channel['channel_id'],$x['item_id']); + } + + + } + + static function like_note($channel,$observer_hash,$act) { + + $s = []; + + $parent = $act->obj['id']; + + if($act->type === 'Like') + $s['verb'] = ACTIVITY_LIKE; + if($act->type === 'Dislike') + $s['verb'] = ACTIVITY_DISLIKE; + + if(! $parent) + return; + + $r = q("select * from item where uid = %d and ( mid = '%s' or mid = '%s' ) limit 1", + intval($channel['channel_id']), + dbesc($parent), + dbesc(urldecode(basename($parent))) + ); + + if(! $r) { + logger('parent not found.'); + return; + } + + xchan_query($r); + $parent_item = $r[0]; + + if($parent_item['owner_xchan'] === $channel['channel_hash']) { + if(! perm_is_allowed($channel['channel_id'],$observer_hash,'post_comments')) { + logger('no comment permission.'); + return; + } + } + + if($parent_item['mid'] === $parent_item['parent_mid']) { + $s['parent_mid'] = $parent_item['mid']; + } + else { + $s['thr_parent'] = $parent_item['mid']; + $s['parent_mid'] = $parent_item['parent_mid']; + } + + $s['owner_xchan'] = $parent_item['owner_xchan']; + $s['author_xchan'] = $observer_hash; + + $s['aid'] = $channel['channel_account_id']; + $s['uid'] = $channel['channel_id']; + $s['mid'] = $act->id; + + if(! $s['parent_mid']) + $s['parent_mid'] = $s['mid']; + + + $post_type = (($parent_item['resource_type'] === 'photo') ? t('photo') : t('status')); + + $links = array(array('rel' => 'alternate','type' => 'text/html', 'href' => $parent_item['plink'])); + $objtype = (($parent_item['resource_type'] === 'photo') ? ACTIVITY_OBJ_PHOTO : ACTIVITY_OBJ_NOTE ); + + $body = $parent_item['body']; + + $z = q("select * from xchan where xchan_hash = '%s' limit 1", + dbesc($parent_item['author_xchan']) + ); + if($z) + $item_author = $z[0]; + + $object = json_encode(array( + 'type' => $post_type, + 'id' => $parent_item['mid'], + 'parent' => (($parent_item['thr_parent']) ? $parent_item['thr_parent'] : $parent_item['parent_mid']), + 'link' => $links, + 'title' => $parent_item['title'], + 'content' => $parent_item['body'], + 'created' => $parent_item['created'], + 'edited' => $parent_item['edited'], + 'author' => array( + 'name' => $item_author['xchan_name'], + 'address' => $item_author['xchan_addr'], + 'guid' => $item_author['xchan_guid'], + 'guid_sig' => $item_author['xchan_guid_sig'], + 'link' => array( + array('rel' => 'alternate', 'type' => 'text/html', 'href' => $item_author['xchan_url']), + array('rel' => 'photo', 'type' => $item_author['xchan_photo_mimetype'], 'href' => $item_author['xchan_photo_m'])), + ), + ), JSON_UNESCAPED_SLASHES + ); + + if($act->type === 'Like') + $bodyverb = t('%1$s likes %2$s\'s %3$s'); + if($act->type === 'Dislike') + $bodyverb = t('%1$s doesn\'t like %2$s\'s %3$s'); + + $ulink = '[url=' . $item_author['xchan_url'] . ']' . $item_author['xchan_name'] . '[/url]'; + $alink = '[url=' . $parent_item['author']['xchan_url'] . ']' . $parent_item['author']['xchan_name'] . '[/url]'; + $plink = '[url='. z_root() . '/display/' . urlencode($act->id) . ']' . $post_type . '[/url]'; + $s['body'] = sprintf( $bodyverb, $ulink, $alink, $plink ); + + $s['app'] = t('ActivityPub'); + + // set the route to that of the parent so downstream hubs won't reject it. + + $s['route'] = $parent_item['route']; + $s['item_private'] = $parent_item['item_private']; + $s['obj_type'] = $objtype; + $s['obj'] = $object; + + if($act->obj['conversation']) { + set_iconfig($s,'ostatus','conversation',$act->obj['conversation'],1); + } + + if($act->recips && (! in_array(ACTIVITY_PUBLIC_INBOX,$act->recips))) + $s['item_private'] = 1; + + set_iconfig($s,'activitypub','recips',$act->raw_recips); + + $result = item_store($s); + + if($result['success']) { + // if the message isn't already being relayed, notify others + if(intval($parent_item['item_origin'])) + Zotlabs\Daemon\Master::Summon(array('Notifier','comment-import',$result['item_id'])); + sync_an_item($channel['channel_id'],$result['item_id']); + } + + return; + } + + + static function bb_attach($attach) { + + $ret = false; + + foreach($attach as $a) { + if(strpos($a['type'],'image') !== false) { + $ret .= "\n\n" . '[img]' . $a['href'] . '[/img]'; + } + if(array_key_exists('type',$a) && strpos($a['type'], 'video') === 0) { + $ret .= "\n\n" . '[video]' . $a['href'] . '[/video]'; + } + if(array_key_exists('type',$a) && strpos($a['type'], 'audio') === 0) { + $ret .= "\n\n" . '[audio]' . $a['href'] . '[/audio]'; + } + } + + return $ret; + } + + + + static function bb_content($content,$field) { + + require_once('include/html2bbcode.php'); + + $ret = false; + + if(is_array($content[$field])) { + foreach($content[$field] as $k => $v) { + $ret .= '[language=' . $k . ']' . html2bbcode($v) . '[/language]'; + } + } + else { + if($field === 'bbcode' && array_key_exists('bbcode',$content)) { + $ret = $content[$field]; + } + else { + $ret = html2bbcode($content[$field]); + } + } + + return $ret; + } + + + static function get_content($act) { + + $content = []; + if (! $act) { + return $content; + } + + foreach ([ 'name', 'summary', 'content' ] as $a) { + if (($x = self::get_textfield($act,$a)) !== false) { + $content[$a] = $x; + } + } + if (array_key_exists('source',$act) && array_key_exists('mediaType',$act['source'])) { + if ($act['source']['mediaType'] === 'text/bbcode') { + $content['bbcode'] = purify_html($act['source']['content']); + } + } + + return $content; + } + + + static function get_textfield($act,$field) { + + $content = false; + + if(array_key_exists($field,$act) && $act[$field]) + $content = purify_html($act[$field]); + elseif(array_key_exists($field . 'Map',$act) && $act[$field . 'Map']) { + foreach($act[$field . 'Map'] as $k => $v) { + $content[escape_tags($k)] = purify_html($v); + } + } + return $content; + } +}
\ No newline at end of file diff --git a/Zotlabs/Lib/Apps.php b/Zotlabs/Lib/Apps.php index a966842ae..b13658be2 100644 --- a/Zotlabs/Lib/Apps.php +++ b/Zotlabs/Lib/Apps.php @@ -56,15 +56,10 @@ class Apps { } - - static public function import_system_apps() { - if(! local_channel()) - return; - - self::$base_apps = get_config('system','base_apps',[ + static public function get_base_apps() { + return get_config('system','base_apps',[ 'Connections', - 'Suggest Channels', - 'Grid', + 'Network', 'Settings', 'Files', 'Channel Home', @@ -77,7 +72,14 @@ class Apps { 'Mail', 'Profile Photo' ]); + } + + static public function import_system_apps() { + if(! local_channel()) + return; + self::$base_apps = self::get_base_apps(); + $apps = self::get_system_apps(false); self::$available_apps = q("select * from app where app_channel = 0"); @@ -266,6 +268,10 @@ class Apps { if(! can_view_public_stream()) unset($ret); break; + case 'custom_role': + if(get_pconfig(local_channel(),'system','permissions_role') !== 'custom') + unset($ret); + break; case 'observer': if(! $observer) unset($ret); @@ -297,14 +303,14 @@ class Apps { 'Cards' => t('Cards'), 'Admin' => t('Site Admin'), 'Report Bug' => t('Report Bug'), - 'View Bookmarks' => t('View Bookmarks'), - 'My Chatrooms' => t('My Chatrooms'), + 'Bookmarks' => t('Bookmarks'), + 'Chatrooms' => t('Chatrooms'), 'Connections' => t('Connections'), 'Remote Diagnostics' => t('Remote Diagnostics'), 'Suggest Channels' => t('Suggest Channels'), 'Login' => t('Login'), 'Channel Manager' => t('Channel Manager'), - 'Grid' => t('Activity'), + 'Network' => t('Stream'), 'Settings' => t('Settings'), 'Files' => t('Files'), 'Webpages' => t('Webpages'), @@ -332,7 +338,20 @@ class Apps { 'Profiles' => t('Profiles'), 'Privacy Groups' => t('Privacy Groups'), 'Notifications' => t('Notifications'), - 'Order Apps' => t('Order Apps') + 'Order Apps' => t('Order Apps'), + 'CalDAV' => t('CalDAV'), + 'CardDAV' => t('CardDAV'), + 'Channel Sources' => t('Channel Sources'), + 'Guest Access' => t('Guest Access'), + 'Notes' => t('Notes'), + 'OAuth Apps Manager' => t('OAuth Apps Manager'), + 'OAuth2 Apps Manager' => t('OAuth2 Apps Manager'), + 'PDL Editor' => t('PDL Editor'), + 'Permission Categories' => t('Permission Categories'), + 'Premium Channel' => t('Premium Channel'), + 'Public Stream' => t('Public Stream'), + 'My Chatrooms' => t('My Chatrooms'), + 'Channel Export' => t('Channel Export') ); if(array_key_exists('name',$arr)) { @@ -344,6 +363,9 @@ class Apps { for($x = 0; $x < count($arr); $x++) { if(array_key_exists($arr[$x]['name'],$apps)) { $arr[$x]['name'] = $apps[$arr[$x]['name']]; + } else { + // Try to guess by app name if not in list + $arr[$x]['name'] = t(trim($arr[$x]['name'])); } } } @@ -383,18 +405,23 @@ class Apps { // This will catch somebody clicking on a system "available" app that hasn't had the path macros replaced // and they are allowed to see the app - - - if(strstr($papp['url'],'$baseurl') || strstr($papp['url'],'$nick') || strstr($papp['photo'],'$baseurl') || strstr($pap['photo'],'$nick')) { + if(strpos($papp['url'],'$baseurl') !== false || strpos($papp['url'],'$nick') !== false || strpos($papp['photo'],'$baseurl') !== false || strpos($papp['photo'],'$nick') !== false) { $view_channel = local_channel(); if(! $view_channel) { + $sys = get_sys_channel(); $view_channel = $sys['channel_id']; } self::app_macros($view_channel,$papp); } - if(! strstr($papp['url'],'://')) + if(strpos($papp['url'], ',')) { + $urls = explode(',', $papp['url']); + $papp['url'] = trim($urls[0]); + $papp['settings_url'] = trim($urls[1]); + } + + if(! strpos($papp['url'],'://')) $papp['url'] = z_root() . ((strpos($papp['url'],'/') === 0) ? '' : '/') . $papp['url']; @@ -442,6 +469,10 @@ class Apps { if(! can_view_public_stream()) return ''; break; + case 'custom_role': + if(get_pconfig(local_channel(),'system','permissions_role') != 'custom') + return ''; + break; case 'observer': $observer = \App::get_observer(); if(! $observer) @@ -463,7 +494,9 @@ class Apps { $hosturl = ''; if(local_channel()) { - $installed = self::app_installed(local_channel(),$papp); + if(self::app_installed(local_channel(),$papp) && !$papp['deleted']) + $installed = true; + $hosturl = z_root() . '/'; } elseif(remote_channel()) { @@ -490,18 +523,21 @@ class Apps { if($mode === 'install') { $papp['embed'] = true; } + return replace_macros(get_markup_template('app.tpl'),array( '$app' => $papp, '$icon' => $icon, '$hosturl' => $hosturl, '$purchase' => (($papp['page'] && (! $installed)) ? t('Purchase') : ''), - '$install' => (($hosturl && in_array($mode, ['view','install'])) ? $install_action : ''), + '$installed' => $installed, + '$action_label' => (($hosturl && in_array($mode, ['view','install'])) ? $install_action : ''), '$edit' => ((local_channel() && $installed && $mode == 'edit') ? t('Edit') : ''), - '$delete' => ((local_channel() && $installed && $mode == 'edit') ? t('Delete') : ''), - '$undelete' => ((local_channel() && $installed && $mode == 'edit') ? t('Undelete') : ''), + '$delete' => ((local_channel() && $mode == 'edit') ? t('Delete') : ''), + '$undelete' => ((local_channel() && $mode == 'edit') ? t('Undelete') : ''), + '$settings_url' => ((local_channel() && $installed && $mode == 'list') ? $papp['settings_url'] : ''), '$deleted' => $papp['deleted'], - '$feature' => (($papp['embed']) ? false : true), - '$pin' => (($papp['embed']) ? false : true), + '$feature' => (($papp['embed'] || $mode == 'edit') ? false : true), + '$pin' => (($papp['embed'] || $mode == 'edit') ? false : true), '$featured' => ((strpos($papp['categories'], 'nav_featured_app') === false) ? false : true), '$pinned' => ((strpos($papp['categories'], 'nav_pinned_app') === false) ? false : true), '$navapps' => (($mode == 'nav') ? true : false), @@ -509,14 +545,26 @@ class Apps { '$add' => t('Add to app-tray'), '$remove' => t('Remove from app-tray'), '$add_nav' => t('Pin to navbar'), - '$remove_nav' => t('Unpin from navbar') + '$remove_nav' => t('Unpin from navbar'), + '$rpath' => z_root() . '/apps' )); } static public function app_install($uid,$app) { + + if(! is_array($app)) { + $r = q("select * from app where app_name = '%s' and app_channel = 0", + dbesc($app) + ); + if(! $r) + return false; + + $app = self::app_encode($r[0]); + } + $app['uid'] = $uid; - if(self::app_installed($uid,$app)) + if(self::app_installed($uid,$app,true)) $x = self::app_update($app); else $x = self::app_store($app); @@ -527,7 +575,7 @@ class Apps { intval($uid) ); if($r) { - if(! $r[0]['app_system']) { + if(($app['uid']) && (! $r[0]['app_system'])) { if($app['categories'] && (! $app['term'])) { $r[0]['term'] = q("select * from term where otype = %d and oid = %d", intval(TERM_OBJ_APP), @@ -542,9 +590,26 @@ class Apps { return false; } - static public function app_destroy($uid,$app) { + + static public function can_delete($uid,$app) { + if(! $uid) { + return false; + } + + $base_apps = self::get_base_apps(); + if($base_apps) { + foreach($base_apps as $b) { + if($app['guid'] === hash('whirlpool',$b)) { + return false; + } + } + } + return true; + } + static public function app_destroy($uid,$app) { + if($uid && $app['guid']) { $x = q("select * from app where app_id = '%s' and app_channel = %d limit 1", @@ -554,23 +619,33 @@ class Apps { if($x) { if(! intval($x[0]['app_deleted'])) { $x[0]['app_deleted'] = 1; - q("delete from term where otype = %d and oid = %d", - intval(TERM_OBJ_APP), - intval($x[0]['id']) - ); - $r = q("delete from app where app_id = '%s' and app_channel = %d", - dbesc($app['guid']), - intval($uid) - ); - - // we don't sync system apps - they may be completely different on the other system - build_sync_packet($uid,array('app' => $x)); + if(self::can_delete($uid,$app)) { + $r = 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']) + ); + call_hooks('app_destroy', $x[0]); + } + else { + $r = 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'])) { + build_sync_packet($uid,array('app' => $x)); + } } else { self::app_undestroy($uid,$app); } } } + } static public function app_undestroy($uid,$app) { @@ -618,16 +693,65 @@ class Apps { } } - static public function app_installed($uid,$app) { + static public function app_installed($uid,$app,$bypass_filter=false) { $r = q("select id from app where app_id = '%s' and app_channel = %d limit 1", dbesc((array_key_exists('guid',$app)) ? $app['guid'] : ''), intval($uid) ); + if (!$bypass_filter) { + $filter_arr = [ + 'uid'=>$uid, + 'app'=>$app, + 'installed'=>$r + ]; + call_hooks('app_installed_filter',$filter_arr); + $r = $filter_arr['installed']; + } + return(($r) ? true : false); + + } + + + static public function addon_app_installed($uid,$app,$bypass_filter=false) { + + $r = q("select id from app where app_plugin = '%s' and app_channel = %d limit 1", + dbesc($app), + intval($uid) + ); + if (!$bypass_filter) { + $filter_arr = [ + 'uid'=>$uid, + 'app'=>$app, + 'installed'=>$r + ]; + call_hooks('addon_app_installed_filter',$filter_arr); + $r = $filter_arr['installed']; + } return(($r) ? true : false); } + static public function system_app_installed($uid,$app,$bypass_filter=false) { + + $r = q("select id from app where app_id = '%s' and app_channel = %d limit 1", + dbesc(hash('whirlpool',$app)), + intval($uid) + ); + if (!$bypass_filter) { + $filter_arr = [ + 'uid'=>$uid, + 'app'=>$app, + 'installed'=>$r + ]; + call_hooks('system_app_installed_filter',$filter_arr); + $r = $filter_arr['installed']; + } + return(($r) ? true : false); + + } + + static public function app_list($uid, $deleted = false, $cats = []) { if($deleted) @@ -668,6 +792,9 @@ class Apps { ); if($r) { + $hookinfo = Array('uid'=>$uid,'deleted'=>$deleted,'cats'=>$cats,'apps'=>$r); + call_hooks('app_list',$hookinfo); + $r = $hookinfo['apps']; for($x = 0; $x < count($r); $x ++) { if(! $r[$x]['app_system']) $r[$x]['type'] = 'personal'; @@ -855,8 +982,8 @@ class Apps { $arr['author'] = $sys['channel_hash']; } - if($arr['photo'] && (strpos($arr['photo'],'icon:') !== 0) && (! strstr($arr['photo'],z_root()))) { - $x = import_xchan_photo($arr['photo'],get_observer_hash(),true); + if($arr['photo'] && (strpos($arr['photo'],'icon:') === false) && (strpos($arr['photo'],z_root()) !== false)) { + $x = import_xchan_photo(str_replace('$baseurl',z_root(),$arr['photo']),get_observer_hash(),true); $arr['photo'] = $x[1]; } @@ -875,10 +1002,11 @@ class Apps { $darray['app_requires'] = ((x($arr,'requires')) ? escape_tags($arr['requires']) : ''); $darray['app_system'] = ((x($arr,'system')) ? intval($arr['system']) : 0); $darray['app_deleted'] = ((x($arr,'deleted')) ? intval($arr['deleted']) : 0); + $darray['app_options'] = ((x($arr,'options')) ? intval($arr['options']) : 0); $created = datetime_convert(); - $r = q("insert into app ( app_id, app_sig, app_author, app_name, app_desc, app_url, app_photo, app_version, app_channel, app_addr, app_price, app_page, app_requires, app_created, app_edited, app_system, app_plugin, app_deleted ) values ( '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, '%s', '%s', '%s', '%s', '%s', '%s', %d, '%s', %d )", + $r = q("insert into app ( app_id, app_sig, app_author, app_name, app_desc, app_url, app_photo, app_version, app_channel, app_addr, app_price, app_page, app_requires, app_created, app_edited, app_system, app_plugin, app_deleted, app_options ) values ( '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, '%s', '%s', '%s', '%s', '%s', '%s', %d, '%s', %d, %d )", dbesc($darray['app_id']), dbesc($darray['app_sig']), dbesc($darray['app_author']), @@ -896,7 +1024,8 @@ class Apps { dbesc($created), intval($darray['app_system']), dbesc($darray['app_plugin']), - intval($darray['app_deleted']) + intval($darray['app_deleted']), + intval($darray['app_options']) ); if($r) { @@ -939,8 +1068,8 @@ class Apps { if((! $darray['app_url']) || (! $darray['app_id'])) return $ret; - if($arr['photo'] && (strpos($arr['photo'],'icon:') !== 0) && (! strstr($arr['photo'],z_root()))) { - $x = import_xchan_photo($arr['photo'],get_observer_hash(),true); + if($arr['photo'] && (strpos($arr['photo'],'icon:') === false) && (strpos($arr['photo'],z_root()) !== false)) { + $x = import_xchan_photo(str_replace('$baseurl',z_root(),$arr['photo']),get_observer_hash(),true); $arr['photo'] = $x[1]; } @@ -957,10 +1086,11 @@ class Apps { $darray['app_requires'] = ((x($arr,'requires')) ? escape_tags($arr['requires']) : ''); $darray['app_system'] = ((x($arr,'system')) ? intval($arr['system']) : 0); $darray['app_deleted'] = ((x($arr,'deleted')) ? intval($arr['deleted']) : 0); + $darray['app_options'] = ((x($arr,'options')) ? intval($arr['options']) : 0); $edited = datetime_convert(); - $r = q("update app set app_sig = '%s', app_author = '%s', app_name = '%s', app_desc = '%s', app_url = '%s', app_photo = '%s', app_version = '%s', app_addr = '%s', app_price = '%s', app_page = '%s', app_requires = '%s', app_edited = '%s', app_system = %d, app_plugin = '%s', app_deleted = %d where app_id = '%s' and app_channel = %d", + $r = q("update app set app_sig = '%s', app_author = '%s', app_name = '%s', app_desc = '%s', app_url = '%s', app_photo = '%s', app_version = '%s', app_addr = '%s', app_price = '%s', app_page = '%s', app_requires = '%s', app_edited = '%s', app_system = %d, app_plugin = '%s', app_deleted = %d, app_options = %d where app_id = '%s' and app_channel = %d", dbesc($darray['app_sig']), dbesc($darray['app_author']), dbesc($darray['app_name']), @@ -976,6 +1106,7 @@ class Apps { intval($darray['app_system']), dbesc($darray['app_plugin']), intval($darray['app_deleted']), + intval($darray['app_options']), dbesc($darray['app_id']), intval($darray['app_channel']) ); @@ -1065,6 +1196,9 @@ class Apps { if($app['app_system']) $ret['system'] = $app['app_system']; + if($app['app_options']) + $ret['options'] = $app['app_options']; + if($app['app_plugin']) $ret['plugin'] = trim($app['app_plugin']); diff --git a/Zotlabs/Lib/Group.php b/Zotlabs/Lib/Group.php new file mode 100644 index 000000000..a4ff4fced --- /dev/null +++ b/Zotlabs/Lib/Group.php @@ -0,0 +1,405 @@ +<?php + +namespace Zotlabs\Lib; + +use Zotlabs\Lib\Libsync; + + +class Group { + + static function add($uid,$name,$public = 0) { + + $ret = false; + if(x($uid) && x($name)) { + $r = self::byname($uid,$name); // check for dups + if($r !== false) { + + // This could be a problem. + // Let's assume we've just created a group which we once deleted + // all the old members are gone, but the group remains so we don't break any security + // access lists. What we're doing here is reviving the dead group, but old content which + // was restricted to this group may now be seen by the new group members. + + $z = q("SELECT * FROM pgrp WHERE id = %d LIMIT 1", + intval($r) + ); + if(($z) && $z[0]['deleted']) { + q('UPDATE pgrp SET deleted = 0 WHERE id = %d', intval($z[0]['id'])); + notice( t('A deleted group with this name was revived. Existing item permissions <strong>may</strong> apply to this group and any future members. If this is not what you intended, please create another group with a different name.') . EOL); + } + return true; + } + + do { + $dups = false; + $hash = random_string(32) . str_replace(['<','>'],['.','.'], $name); + + $r = q("SELECT id FROM pgrp WHERE hash = '%s' LIMIT 1", dbesc($hash)); + if($r) + $dups = true; + } while($dups == true); + + + $r = q("INSERT INTO pgrp ( hash, uid, visible, gname ) + VALUES( '%s', %d, %d, '%s' ) ", + dbesc($hash), + intval($uid), + intval($public), + dbesc($name) + ); + $ret = $r; + } + + Libsync::build_sync_packet($uid,null,true); + return $ret; + } + + + static function remove($uid,$name) { + $ret = false; + if(x($uid) && x($name)) { + $r = q("SELECT id, hash FROM pgrp WHERE uid = %d AND gname = '%s' LIMIT 1", + intval($uid), + dbesc($name) + ); + if($r) { + $group_id = $r[0]['id']; + $group_hash = $r[0]['hash']; + } + + if(! $group_id) + return false; + + // remove group from default posting lists + $r = q("SELECT channel_default_group, channel_allow_gid, channel_deny_gid FROM channel WHERE channel_id = %d LIMIT 1", + intval($uid) + ); + if($r) { + $user_info = $r[0]; + $change = false; + + if($user_info['channel_default_group'] == $group_hash) { + $user_info['channel_default_group'] = ''; + $change = true; + } + if(strpos($user_info['channel_allow_gid'], '<' . $group_hash . '>') !== false) { + $user_info['channel_allow_gid'] = str_replace('<' . $group_hash . '>', '', $user_info['channel_allow_gid']); + $change = true; + } + if(strpos($user_info['channel_deny_gid'], '<' . $group_hash . '>') !== false) { + $user_info['channel_deny_gid'] = str_replace('<' . $group_hash . '>', '', $user_info['channel_deny_gid']); + $change = true; + } + + if($change) { + q("UPDATE channel SET channel_default_group = '%s', channel_allow_gid = '%s', channel_deny_gid = '%s' + WHERE channel_id = %d", + intval($user_info['channel_default_group']), + dbesc($user_info['channel_allow_gid']), + dbesc($user_info['channel_deny_gid']), + intval($uid) + ); + } + } + + // remove all members + $r = q("DELETE FROM pgrp_member WHERE uid = %d AND gid = %d ", + intval($uid), + intval($group_id) + ); + + // remove group + $r = q("UPDATE pgrp SET deleted = 1 WHERE uid = %d AND gname = '%s'", + intval($uid), + dbesc($name) + ); + + $ret = $r; + + } + + Libsync::build_sync_packet($uid,null,true); + + return $ret; + } + + + static function byname($uid,$name) { + if((! $uid) || (! strlen($name))) + return false; + $r = q("SELECT * FROM pgrp WHERE uid = %d AND gname = '%s' LIMIT 1", + intval($uid), + dbesc($name) + ); + if($r) + return $r[0]['id']; + return false; + } + + + static function rec_byhash($uid,$hash) { + if((! $uid) || (! strlen($hash))) + return false; + $r = q("SELECT * FROM pgrp WHERE uid = %d AND hash = '%s' LIMIT 1", + intval($uid), + dbesc($hash) + ); + if($r) + return $r[0]; + return false; + } + + + static function member_remove($uid,$name,$member) { + $gid = self::byname($uid,$name); + if(! $gid) + return false; + if(! ( $uid && $gid && $member)) + return false; + $r = q("DELETE FROM pgrp_member WHERE uid = %d AND gid = %d AND xchan = '%s' ", + intval($uid), + intval($gid), + dbesc($member) + ); + + Libsync::build_sync_packet($uid,null,true); + + return $r; + } + + + static function member_add($uid,$name,$member,$gid = 0) { + if(! $gid) + $gid = self::byname($uid,$name); + if((! $gid) || (! $uid) || (! $member)) + return false; + + $r = q("SELECT * FROM pgrp_member WHERE uid = %d AND gid = %d AND xchan = '%s' LIMIT 1", + intval($uid), + intval($gid), + dbesc($member) + ); + if($r) + return true; // You might question this, but + // we indicate success because the group member was in fact created + // -- It was just created at another time + if(! $r) + $r = q("INSERT INTO pgrp_member (uid, gid, xchan) + VALUES( %d, %d, '%s' ) ", + intval($uid), + intval($gid), + dbesc($member) + ); + + Libsync::build_sync_packet($uid,null,true); + + return $r; + } + + + static function members($gid) { + $ret = array(); + if(intval($gid)) { + $r = q("SELECT * FROM pgrp_member + LEFT JOIN abook ON abook_xchan = pgrp_member.xchan left join xchan on xchan_hash = abook_xchan + WHERE gid = %d AND abook_channel = %d and pgrp_member.uid = %d and xchan_deleted = 0 and abook_self = 0 and abook_blocked = 0 and abook_pending = 0 ORDER BY xchan_name ASC ", + intval($gid), + intval(local_channel()), + intval(local_channel()) + ); + if($r) + $ret = $r; + } + return $ret; + } + + static function members_xchan($gid) { + $ret = []; + if(intval($gid)) { + $r = q("SELECT xchan FROM pgrp_member WHERE gid = %d AND uid = %d", + intval($gid), + intval(local_channel()) + ); + if($r) { + foreach($r as $rr) { + $ret[] = $rr['xchan']; + } + } + } + return $ret; + } + + static function members_profile_xchan($uid,$gid) { + $ret = []; + + if(intval($gid)) { + $r = q("SELECT abook_xchan as xchan from abook left join profile on abook_profile = profile_guid where profile.id = %d and profile.uid = %d", + intval($gid), + intval($uid) + ); + if($r) { + foreach($r as $rr) { + $ret[] = $rr['xchan']; + } + } + } + return $ret; + } + + + + + static function select($uid,$group = '') { + + $grps = []; + $o = ''; + + $r = q("SELECT * FROM pgrp WHERE deleted = 0 AND uid = %d ORDER BY gname ASC", + intval($uid) + ); + $grps[] = array('name' => '', 'hash' => '0', 'selected' => ''); + if($r) { + foreach($r as $rr) { + $grps[] = array('name' => $rr['gname'], 'id' => $rr['hash'], 'selected' => (($group == $rr['hash']) ? 'true' : '')); + } + + } + logger('select: ' . print_r($grps,true), LOGGER_DATA); + + $o = replace_macros(get_markup_template('group_selection.tpl'), array( + '$label' => t('Add new connections to this privacy group'), + '$groups' => $grps + )); + return $o; + } + + + + + static function widget($every="connections",$each="group",$edit = false, $group_id = 0, $cid = '',$mode = 1) { + + $o = ''; + + if(! (local_channel() && feature_enabled(local_channel(),'groups'))) { + return ''; + } + + $groups = array(); + + $r = q("SELECT * FROM pgrp WHERE deleted = 0 AND uid = %d ORDER BY gname ASC", + intval($_SESSION['uid']) + ); + $member_of = array(); + if($cid) { + $member_of = self::containing(local_channel(),$cid); + } + + if($r) { + foreach($r as $rr) { + $selected = (($group_id == $rr['id']) ? ' group-selected' : ''); + + if ($edit) { + $groupedit = [ 'href' => "group/".$rr['id'], 'title' => t('edit') ]; + } + else { + $groupedit = null; + } + + $groups[] = [ + 'id' => $rr['id'], + 'enc_cid' => base64url_encode($cid), + 'cid' => $cid, + 'text' => $rr['gname'], + 'selected' => $selected, + 'href' => (($mode == 0) ? $each.'?f=&gid='.$rr['id'] : $each."/".$rr['id']) . ((x($_GET,'new')) ? '&new=' . $_GET['new'] : '') . ((x($_GET,'order')) ? '&order=' . $_GET['order'] : ''), + 'edit' => $groupedit, + 'ismember' => in_array($rr['id'],$member_of), + ]; + } + } + + + $tpl = get_markup_template("group_side.tpl"); + $o = replace_macros($tpl, array( + '$title' => t('Privacy Groups'), + '$edittext' => t('Edit group'), + '$createtext' => t('Add privacy group'), + '$ungrouped' => (($every === 'contacts') ? t('Channels not in any privacy group') : ''), + '$groups' => $groups, + '$add' => t('add'), + )); + + + return $o; + } + + + static function expand($g) { + if(! (is_array($g) && count($g))) + return array(); + + $ret = []; + $x = []; + + // private profile linked virtual groups + + foreach($g as $gv) { + if(substr($gv,0,3) === 'vp.') { + $profile_hash = substr($gv,3); + if($profile_hash) { + $r = q("select abook_xchan from abook where abook_profile = '%s'", + dbesc($profile_hash) + ); + if($r) { + foreach($r as $rv) { + $ret[] = $rv['abook_xchan']; + } + } + } + } + else { + $x[] = $gv; + } + } + + if($x) { + stringify_array_elms($x,true); + $groups = implode(',', $x); + if($groups) { + $r = q("SELECT xchan FROM pgrp_member WHERE gid IN ( select id from pgrp where hash in ( $groups ))"); + if($r) { + foreach($r as $rr) { + $ret[] = $rr['xchan']; + } + } + } + } + return $ret; + } + + + static function member_of($c) { + $r = q("SELECT pgrp.gname, pgrp.id FROM pgrp LEFT JOIN pgrp_member ON pgrp_member.gid = pgrp.id WHERE pgrp_member.xchan = '%s' AND pgrp.deleted = 0 ORDER BY pgrp.gname ASC ", + dbesc($c) + ); + + return $r; + + } + + static function containing($uid,$c) { + + $r = q("SELECT gid FROM pgrp_member WHERE uid = %d AND pgrp_member.xchan = '%s' ", + intval($uid), + dbesc($c) + ); + + $ret = array(); + if($r) { + foreach($r as $rr) + $ret[] = $rr['gid']; + } + + return $ret; + } +}
\ No newline at end of file diff --git a/Zotlabs/Lib/Libsync.php b/Zotlabs/Lib/Libsync.php new file mode 100644 index 000000000..d037a0058 --- /dev/null +++ b/Zotlabs/Lib/Libsync.php @@ -0,0 +1,1019 @@ +<?php + +namespace Zotlabs\Lib; + +use Zotlabs\Lib\Libzot; +use Zotlabs\Lib\Queue; + + +class Libsync { + + /** + * @brief Builds and sends a sync packet. + * + * Send a zot packet to all hubs where this channel is duplicated, refreshing + * such things as personal settings, channel permissions, address book updates, etc. + * + * @param int $uid (optional) default 0 + * @param array $packet (optional) default null + * @param boolean $groups_changed (optional) default false + */ + + static function build_sync_packet($uid = 0, $packet = null, $groups_changed = false) { + + logger('build_sync_packet'); + + $keychange = (($packet && array_key_exists('keychange',$packet)) ? true : false); + if($keychange) { + logger('keychange sync'); + } + + if(! $uid) + $uid = local_channel(); + + if(! $uid) + return; + + $r = q("select * from channel where channel_id = %d limit 1", + intval($uid) + ); + if(! $r) + return; + + $channel = $r[0]; + + // don't provide these in the export + + unset($channel['channel_active']); + unset($channel['channel_password']); + unset($channel['channel_salt']); + + + if(intval($channel['channel_removed'])) + return; + + $h = q("select hubloc.*, site.site_crypto from hubloc left join site on site_url = hubloc_url where hubloc_hash = '%s' and hubloc_deleted = 0", + dbesc(($keychange) ? $packet['keychange']['old_hash'] : $channel['channel_hash']) + ); + + if(! $h) + return; + + $synchubs = array(); + + foreach($h as $x) { + if($x['hubloc_host'] == \App::get_hostname()) + continue; + + $y = q("select site_dead from site where site_url = '%s' limit 1", + dbesc($x['hubloc_url']) + ); + + if((! $y) || ($y[0]['site_dead'] == 0)) + $synchubs[] = $x; + } + + if(! $synchubs) + return; + + $env_recips = [ $channel['channel_hash'] ]; + + if($packet) + logger('packet: ' . print_r($packet, true),LOGGER_DATA, LOG_DEBUG); + + $info = (($packet) ? $packet : array()); + $info['type'] = 'sync'; + $info['encoding'] = 'red'; // note: not zot, this packet is very platform specific + $info['relocate'] = ['channel_address' => $channel['channel_address'], 'url' => z_root() ]; + + if(array_key_exists($uid,\App::$config) && array_key_exists('transient',\App::$config[$uid])) { + $settings = \App::$config[$uid]['transient']; + if($settings) { + $info['config'] = $settings; + } + } + + if($channel) { + $info['channel'] = array(); + foreach($channel as $k => $v) { + + // filter out any joined tables like xchan + + if(strpos($k,'channel_') !== 0) + continue; + + // don't pass these elements, they should not be synchronised + + + $disallowed = [ + 'channel_id','channel_account_id','channel_primary','channel_address', + 'channel_deleted','channel_removed','channel_system' + ]; + + if(! $keychange) { + $disallowed[] = 'channel_prvkey'; + } + + if(in_array($k,$disallowed)) + continue; + + $info['channel'][$k] = $v; + } + } + + if($groups_changed) { + $r = q("select hash as collection, visible, deleted, gname as name from pgrp where uid = %d", + intval($uid) + ); + if($r) + $info['collections'] = $r; + + $r = q("select pgrp.hash as collection, pgrp_member.xchan as member from pgrp left join pgrp_member on pgrp.id = pgrp_member.gid where pgrp_member.uid = %d", + intval($uid) + ); + if($r) + $info['collection_members'] = $r; + } + + $interval = ((get_config('system','delivery_interval') !== false) + ? intval(get_config('system','delivery_interval')) : 2 ); + + 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),'red',$hub['hubloc_sitekey'],$hub['site_crypto']); + Queue::insert(array( + 'hash' => $hash, + 'account_id' => $channel['channel_account_id'], + 'channel_id' => $channel['channel_id'], + 'posturl' => $hub['hubloc_callback'], + 'notify' => $n, + 'msg' => EMPTY_STR + )); + + + $x = q("select count(outq_hash) as total from outq where outq_delivered = 0"); + if(intval($x[0]['total']) > intval(get_config('system','force_queue_threshold',3000))) { + logger('immediate delivery deferred.', LOGGER_DEBUG, LOG_INFO); + Queue::update($hash); + continue; + } + + + \Zotlabs\Daemon\Master::Summon(array('Deliver', $hash)); + $total = $total - 1; + + if($interval && $total) + @time_sleep_until(microtime(true) + (float) $interval); + } + } + + /** + * @brief + * + * @param array $sender + * @param array $arr + * @param array $deliveries + * @return array + */ + + static function process_channel_sync_delivery($sender, $arr, $deliveries) { + + require_once('include/import.php'); + + $result = []; + + $keychange = ((array_key_exists('keychange',$arr)) ? true : false); + + foreach ($deliveries as $d) { + $r = q("select * from channel where channel_hash = '%s' limit 1", + dbesc($sender) + ); + + $DR = new \Zotlabs\Lib\DReport(z_root(),$sender,$d,'sync'); + + if (! $r) { + $DR->update('recipient not found'); + $result[] = $DR->get(); + continue; + } + + $channel = $r[0]; + + $DR->set_name($channel['channel_name'] . ' <' . channel_reddress($channel) . '>'); + + $max_friends = service_class_fetch($channel['channel_id'],'total_channels'); + $max_feeds = account_service_class_fetch($channel['channel_account_id'],'total_feeds'); + + if($channel['channel_hash'] != $sender) { + logger('Possible forgery. Sender ' . $sender . ' is not ' . $channel['channel_hash']); + $DR->update('channel mismatch'); + $result[] = $DR->get(); + continue; + } + + if($keychange) { + self::keychange($channel,$arr); + continue; + } + + // if the clone is active, so are we + + if(substr($channel['channel_active'],0,10) !== substr(datetime_convert(),0,10)) { + q("UPDATE channel set channel_active = '%s' where channel_id = %d", + dbesc(datetime_convert()), + intval($channel['channel_id']) + ); + } + + if(array_key_exists('config',$arr) && is_array($arr['config']) && count($arr['config'])) { + foreach($arr['config'] as $cat => $k) { + foreach($arr['config'][$cat] as $k => $v) + set_pconfig($channel['channel_id'],$cat,$k,$v); + } + } + + if(array_key_exists('obj',$arr) && $arr['obj']) + sync_objs($channel,$arr['obj']); + + if(array_key_exists('likes',$arr) && $arr['likes']) + import_likes($channel,$arr['likes']); + + if(array_key_exists('app',$arr) && $arr['app']) + sync_apps($channel,$arr['app']); + + if(array_key_exists('chatroom',$arr) && $arr['chatroom']) + sync_chatrooms($channel,$arr['chatroom']); + + if(array_key_exists('conv',$arr) && $arr['conv']) + import_conv($channel,$arr['conv']); + + if(array_key_exists('mail',$arr) && $arr['mail']) + sync_mail($channel,$arr['mail']); + + if(array_key_exists('event',$arr) && $arr['event']) + sync_events($channel,$arr['event']); + + if(array_key_exists('event_item',$arr) && $arr['event_item']) + sync_items($channel,$arr['event_item'],((array_key_exists('relocate',$arr)) ? $arr['relocate'] : null)); + + if(array_key_exists('item',$arr) && $arr['item']) + sync_items($channel,$arr['item'],((array_key_exists('relocate',$arr)) ? $arr['relocate'] : null)); + + // deprecated, maintaining for a few months for upward compatibility + // this should sync webpages, but the logic is a bit subtle + + if(array_key_exists('item_id',$arr) && $arr['item_id']) + sync_items($channel,$arr['item_id']); + + if(array_key_exists('menu',$arr) && $arr['menu']) + sync_menus($channel,$arr['menu']); + + if(array_key_exists('file',$arr) && $arr['file']) + sync_files($channel,$arr['file']); + + if(array_key_exists('wiki',$arr) && $arr['wiki']) + sync_items($channel,$arr['wiki'],((array_key_exists('relocate',$arr)) ? $arr['relocate'] : null)); + + if(array_key_exists('channel',$arr) && is_array($arr['channel']) && count($arr['channel'])) { + + $remote_channel = $arr['channel']; + $remote_channel['channel_id'] = $channel['channel_id']; + + if(array_key_exists('channel_pageflags',$arr['channel']) && intval($arr['channel']['channel_pageflags'])) { + + // Several pageflags are site-specific and cannot be sync'd. + // Only allow those bits which are shareable from the remote and then + // logically OR with the local flags + + $arr['channel']['channel_pageflags'] = $arr['channel']['channel_pageflags'] & (PAGE_HIDDEN|PAGE_AUTOCONNECT|PAGE_APPLICATION|PAGE_PREMIUM|PAGE_ADULT); + $arr['channel']['channel_pageflags'] = $arr['channel']['channel_pageflags'] | $channel['channel_pageflags']; + + } + + $disallowed = [ + 'channel_id', 'channel_account_id', 'channel_primary', 'channel_prvkey', + 'channel_address', 'channel_notifyflags', 'channel_removed', 'channel_deleted', + 'channel_system', 'channel_r_stream', 'channel_r_profile', 'channel_r_abook', + 'channel_r_storage', 'channel_r_pages', 'channel_w_stream', 'channel_w_wall', + 'channel_w_comment', 'channel_w_mail', 'channel_w_like', 'channel_w_tagwall', + 'channel_w_chat', 'channel_w_storage', 'channel_w_pages', 'channel_a_republish', + 'channel_a_delegate' + ]; + + $clean = array(); + foreach($arr['channel'] as $k => $v) { + if(in_array($k,$disallowed)) + continue; + $clean[$k] = $v; + } + if(count($clean)) { + foreach($clean as $k => $v) { + $r = dbq("UPDATE channel set " . dbesc($k) . " = '" . dbesc($v) + . "' where channel_id = " . intval($channel['channel_id']) ); + } + } + } + + if(array_key_exists('abook',$arr) && is_array($arr['abook']) && count($arr['abook'])) { + $total_friends = 0; + $total_feeds = 0; + + $r = q("select abook_id, abook_feed from abook where abook_channel = %d", + intval($channel['channel_id']) + ); + if($r) { + // don't count yourself + $total_friends = ((count($r) > 0) ? count($r) - 1 : 0); + foreach($r as $rr) + if(intval($rr['abook_feed'])) + $total_feeds ++; + } + + + $disallowed = array('abook_id','abook_account','abook_channel','abook_rating','abook_rating_text','abook_not_here'); + + $fields = db_columns($abook); + + foreach($arr['abook'] as $abook) { + + $abconfig = null; + + if(array_key_exists('abconfig',$abook) && is_array($abook['abconfig']) && count($abook['abconfig'])) + $abconfig = $abook['abconfig']; + + if(! array_key_exists('abook_blocked',$abook)) { + // convert from redmatrix + $abook['abook_blocked'] = (($abook['abook_flags'] & 0x0001) ? 1 : 0); + $abook['abook_ignored'] = (($abook['abook_flags'] & 0x0002) ? 1 : 0); + $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); + } + + $clean = array(); + if($abook['abook_xchan'] && $abook['entry_deleted']) { + logger('Removing abook entry for ' . $abook['abook_xchan']); + + $r = q("select abook_id, abook_feed from abook where abook_xchan = '%s' and abook_channel = %d and abook_self = 0 limit 1", + dbesc($abook['abook_xchan']), + intval($channel['channel_id']) + ); + if($r) { + contact_remove($channel['channel_id'],$r[0]['abook_id']); + if($total_friends) + $total_friends --; + if(intval($r[0]['abook_feed'])) + $total_feeds --; + } + continue; + } + + // Perform discovery if the referenced xchan hasn't ever been seen on this hub. + // This relies on the undocumented behaviour that red sites send xchan info with the abook + // and import_author_xchan will look them up on all federated networks + + if($abook['abook_xchan'] && $abook['xchan_addr']) { + $h = Libzot::get_hublocs($abook['abook_xchan']); + if(! $h) { + $xhash = import_author_xchan(encode_item_xchan($abook)); + if(! $xhash) { + logger('Import of ' . $abook['xchan_addr'] . ' failed.'); + continue; + } + } + } + + foreach($abook as $k => $v) { + if(in_array($k,$disallowed) || (strpos($k,'abook') !== 0)) { + continue; + } + if(! in_array($k,$fields)) { + continue; + } + $clean[$k] = $v; + } + + if(! array_key_exists('abook_xchan',$clean)) + continue; + + if(array_key_exists('abook_instance',$clean) && $clean['abook_instance'] && strpos($clean['abook_instance'],z_root()) === false) { + $clean['abook_not_here'] = 1; + } + + + $r = q("select * from abook where abook_xchan = '%s' and abook_channel = %d limit 1", + dbesc($clean['abook_xchan']), + intval($channel['channel_id']) + ); + + // make sure we have an abook entry for this xchan on this system + + if(! $r) { + if($max_friends !== false && $total_friends > $max_friends) { + logger('total_channels service class limit exceeded'); + continue; + } + if($max_feeds !== false && intval($clean['abook_feed']) && $total_feeds > $max_feeds) { + logger('total_feeds service class limit exceeded'); + continue; + } + abook_store_lowlevel( + [ + 'abook_xchan' => $clean['abook_xchan'], + 'abook_account' => $channel['channel_account_id'], + 'abook_channel' => $channel['channel_id'] + ] + ); + $total_friends ++; + if(intval($clean['abook_feed'])) + $total_feeds ++; + } + + if(count($clean)) { + foreach($clean as $k => $v) { + if($k == 'abook_dob') + $v = dbescdate($v); + + $r = dbq("UPDATE abook set " . dbesc($k) . " = '" . dbesc($v) + . "' where abook_xchan = '" . dbesc($clean['abook_xchan']) . "' and abook_channel = " . intval($channel['channel_id'])); + } + } + + // This will set abconfig vars if the sender is using old-style fixed permissions + // using the raw abook record as passed to us. New-style permissions will fall through + // and be set using abconfig + + // translate_abook_perms_inbound($channel,$abook); + + 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']); + } + } + } + } + + // sync collections (privacy groups) oh joy... + + if(array_key_exists('collections',$arr) && is_array($arr['collections']) && count($arr['collections'])) { + $x = q("select * from pgrp where uid = %d", + intval($channel['channel_id']) + ); + foreach($arr['collections'] as $cl) { + $found = false; + if($x) { + foreach($x as $y) { + if($cl['collection'] == $y['hash']) { + $found = true; + break; + } + } + if($found) { + if(($y['gname'] != $cl['name']) + || ($y['visible'] != $cl['visible']) + || ($y['deleted'] != $cl['deleted'])) { + q("update pgrp set gname = '%s', visible = %d, deleted = %d where hash = '%s' and uid = %d", + dbesc($cl['name']), + intval($cl['visible']), + intval($cl['deleted']), + dbesc($cl['collection']), + intval($channel['channel_id']) + ); + } + if(intval($cl['deleted']) && (! intval($y['deleted']))) { + q("delete from pgrp_member where gid = %d", + intval($y['id']) + ); + } + } + } + if(! $found) { + $r = q("INSERT INTO pgrp ( hash, uid, visible, deleted, gname ) + VALUES( '%s', %d, %d, %d, '%s' ) ", + dbesc($cl['collection']), + intval($channel['channel_id']), + intval($cl['visible']), + intval($cl['deleted']), + dbesc($cl['name']) + ); + } + + // now look for any collections locally which weren't in the list we just received. + // They need to be removed by marking deleted and removing the members. + // This shouldn't happen except for clones created before this function was written. + + if($x) { + $found_local = false; + foreach($x as $y) { + foreach($arr['collections'] as $cl) { + if($cl['collection'] == $y['hash']) { + $found_local = true; + break; + } + } + if(! $found_local) { + q("delete from pgrp_member where gid = %d", + intval($y['id']) + ); + q("update pgrp set deleted = 1 where id = %d and uid = %d", + intval($y['id']), + intval($channel['channel_id']) + ); + } + } + } + } + + // reload the group list with any updates + $x = q("select * from pgrp where uid = %d", + intval($channel['channel_id']) + ); + + // now sync the members + + if(array_key_exists('collection_members', $arr) + && is_array($arr['collection_members']) + && count($arr['collection_members'])) { + + // first sort into groups keyed by the group hash + $members = array(); + foreach($arr['collection_members'] as $cm) { + if(! array_key_exists($cm['collection'],$members)) + $members[$cm['collection']] = array(); + + $members[$cm['collection']][] = $cm['member']; + } + + // our group list is already synchronised + if($x) { + foreach($x as $y) { + + // for each group, loop on members list we just received + if(isset($y['hash']) && isset($members[$y['hash']])) { + foreach($members[$y['hash']] as $member) { + $found = false; + $z = q("select xchan from pgrp_member where gid = %d and uid = %d and xchan = '%s' limit 1", + intval($y['id']), + intval($channel['channel_id']), + dbesc($member) + ); + if($z) + $found = true; + + // if somebody is in the group that wasn't before - add them + + if(! $found) { + q("INSERT INTO pgrp_member (uid, gid, xchan) + VALUES( %d, %d, '%s' ) ", + intval($channel['channel_id']), + intval($y['id']), + dbesc($member) + ); + } + } + } + + // now retrieve a list of members we have on this site + $m = q("select xchan from pgrp_member where gid = %d and uid = %d", + intval($y['id']), + intval($channel['channel_id']) + ); + if($m) { + foreach($m as $mm) { + // if the local existing member isn't in the list we just received - remove them + if(! in_array($mm['xchan'],$members[$y['hash']])) { + q("delete from pgrp_member where xchan = '%s' and gid = %d and uid = %d", + dbesc($mm['xchan']), + intval($y['id']), + intval($channel['channel_id']) + ); + } + } + } + } + } + } + } + + if(array_key_exists('profile',$arr) && is_array($arr['profile']) && count($arr['profile'])) { + + $disallowed = array('id','aid','uid','guid'); + + foreach($arr['profile'] as $profile) { + + $x = q("select * from profile where profile_guid = '%s' and uid = %d limit 1", + dbesc($profile['profile_guid']), + intval($channel['channel_id']) + ); + if(! $x) { + profile_store_lowlevel( + [ + 'aid' => $channel['channel_account_id'], + 'uid' => $channel['channel_id'], + 'profile_guid' => $profile['profile_guid'], + ] + ); + + $x = q("select * from profile where profile_guid = '%s' and uid = %d limit 1", + dbesc($profile['profile_guid']), + intval($channel['channel_id']) + ); + if(! $x) + continue; + } + $clean = array(); + foreach($profile as $k => $v) { + if(in_array($k,$disallowed)) + continue; + + if($profile['is_default'] && in_array($k,['photo','thumb'])) + continue; + + if($k === 'name') + $clean['fullname'] = $v; + elseif($k === 'with') + $clean['partner'] = $v; + elseif($k === 'work') + $clean['employment'] = $v; + elseif(array_key_exists($k,$x[0])) + $clean[$k] = $v; + + /** + * @TODO + * We also need to import local photos if a custom photo is selected + */ + + if((strpos($profile['thumb'],'/photo/profile/l/') !== false) || intval($profile['is_default'])) { + $profile['photo'] = z_root() . '/photo/profile/l/' . $channel['channel_id']; + $profile['thumb'] = z_root() . '/photo/profile/m/' . $channel['channel_id']; + } + else { + $profile['photo'] = z_root() . '/photo/' . basename($profile['photo']); + $profile['thumb'] = z_root() . '/photo/' . basename($profile['thumb']); + } + } + + if(count($clean)) { + foreach($clean as $k => $v) { + $r = dbq("UPDATE profile set " . TQUOT . dbesc($k) . TQUOT . " = '" . dbesc($v) + . "' where profile_guid = '" . dbesc($profile['profile_guid']) + . "' and uid = " . intval($channel['channel_id'])); + } + } + } + } + + $addon = ['channel' => $channel, 'data' => $arr]; + /** + * @hooks process_channel_sync_delivery + * Called when accepting delivery of a 'sync packet' containing structure and table updates from a channel clone. + * * \e array \b channel + * * \e array \b data + */ + call_hooks('process_channel_sync_delivery', $addon); + + $DR = new \Zotlabs\Lib\DReport(z_root(),$d,$d,'sync','channel sync delivered'); + + $DR->set_name($channel['channel_name'] . ' <' . channel_reddress($channel) . '>'); + + $result[] = $DR->get(); + } + + return $result; + } + + /** + * @brief Synchronises locations. + * + * @param array $sender + * @param array $arr + * @param boolean $absolute (optional) default false + * @return array + */ + + static function sync_locations($sender, $arr, $absolute = false) { + + $ret = array(); + + if($arr['locations']) { + + if($absolute) + self::check_location_move($sender['hash'],$arr['locations']); + + $xisting = q("select * from hubloc where hubloc_hash = '%s'", + dbesc($sender['hash']) + ); + + // See if a primary is specified + + $has_primary = false; + foreach($arr['locations'] as $location) { + if($location['primary']) { + $has_primary = true; + break; + } + } + + // Ensure that they have one primary hub + + if(! $has_primary) + $arr['locations'][0]['primary'] = true; + + foreach($arr['locations'] as $location) { + if(! Libzot::verify($location['url'],$location['url_sig'],$sender['public_key'])) { + logger('Unable to verify site signature for ' . $location['url']); + $ret['message'] .= sprintf( t('Unable to verify site signature for %s'), $location['url']) . EOL; + continue; + } + + for($x = 0; $x < count($xisting); $x ++) { + if(($xisting[$x]['hubloc_url'] === $location['url']) + && ($xisting[$x]['hubloc_sitekey'] === $location['sitekey'])) { + $xisting[$x]['updated'] = true; + } + } + + if(! $location['sitekey']) { + logger('Empty hubloc sitekey. ' . print_r($location,true)); + continue; + } + + // Catch some malformed entries from the past which still exist + + if(strpos($location['address'],'/') !== false) + $location['address'] = substr($location['address'],0,strpos($location['address'],'/')); + + // match as many fields as possible in case anything at all changed. + + $r = q("select * from hubloc where hubloc_hash = '%s' and hubloc_guid = '%s' and hubloc_guid_sig = '%s' and hubloc_id_url = '%s' and hubloc_url = '%s' and hubloc_url_sig = '%s' and hubloc_site_id = '%s' and hubloc_host = '%s' and hubloc_addr = '%s' and hubloc_callback = '%s' and hubloc_sitekey = '%s' ", + dbesc($sender['hash']), + dbesc($sender['id']), + dbesc($sender['id_sig']), + dbesc($location['id_url']), + dbesc($location['url']), + dbesc($location['url_sig']), + dbesc($location['site_id']), + dbesc($location['host']), + dbesc($location['address']), + dbesc($location['callback']), + dbesc($location['sitekey']) + ); + if($r) { + logger('Hub exists: ' . $location['url'], LOGGER_DEBUG); + + // update connection timestamp if this is the site we're talking to + // This only happens when called from import_xchan + + $current_site = false; + + $t = datetime_convert('UTC','UTC','now - 15 minutes'); + + if(array_key_exists('site',$arr) && $location['url'] == $arr['site']['url']) { + q("update hubloc set hubloc_connected = '%s', hubloc_updated = '%s' where hubloc_id = %d and hubloc_connected < '%s'", + dbesc(datetime_convert()), + dbesc(datetime_convert()), + intval($r[0]['hubloc_id']), + dbesc($t) + ); + $current_site = true; + } + + if($current_site && intval($r[0]['hubloc_error'])) { + q("update hubloc set hubloc_error = 0 where hubloc_id = %d", + intval($r[0]['hubloc_id']) + ); + if(intval($r[0]['hubloc_orphancheck'])) { + q("update hubloc set hubloc_orphancheck = 0 where hubloc_id = %d", + intval($r[0]['hubloc_id']) + ); + } + q("update xchan set xchan_orphan = 0 where xchan_orphan = 1 and xchan_hash = '%s'", + dbesc($sender['hash']) + ); + } + + // Remove pure duplicates + if(count($r) > 1) { + for($h = 1; $h < count($r); $h ++) { + q("delete from hubloc where hubloc_id = %d", + intval($r[$h]['hubloc_id']) + ); + $what .= 'duplicate_hubloc_removed '; + $changed = true; + } + } + + if(intval($r[0]['hubloc_primary']) && (! $location['primary'])) { + $m = q("update hubloc set hubloc_primary = 0, hubloc_updated = '%s' where hubloc_id = %d", + dbesc(datetime_convert()), + intval($r[0]['hubloc_id']) + ); + $r[0]['hubloc_primary'] = intval($location['primary']); + hubloc_change_primary($r[0]); + $what .= 'primary_hub '; + $changed = true; + } + elseif((! intval($r[0]['hubloc_primary'])) && ($location['primary'])) { + $m = q("update hubloc set hubloc_primary = 1, hubloc_updated = '%s' where hubloc_id = %d", + dbesc(datetime_convert()), + intval($r[0]['hubloc_id']) + ); + // make sure hubloc_change_primary() has current data + $r[0]['hubloc_primary'] = intval($location['primary']); + hubloc_change_primary($r[0]); + $what .= 'primary_hub '; + $changed = true; + } + elseif($absolute) { + // Absolute sync - make sure the current primary is correctly reflected in the xchan + $pr = hubloc_change_primary($r[0]); + if($pr) { + $what .= 'xchan_primary '; + $changed = true; + } + } + if(intval($r[0]['hubloc_deleted']) && (! intval($location['deleted']))) { + $n = q("update hubloc set hubloc_deleted = 0, hubloc_updated = '%s' where hubloc_id = %d", + dbesc(datetime_convert()), + intval($r[0]['hubloc_id']) + ); + $what .= 'undelete_hub '; + $changed = true; + } + elseif((! intval($r[0]['hubloc_deleted'])) && (intval($location['deleted']))) { + logger('deleting hubloc: ' . $r[0]['hubloc_addr']); + $n = q("update hubloc set hubloc_deleted = 1, hubloc_updated = '%s' where hubloc_id = %d", + dbesc(datetime_convert()), + intval($r[0]['hubloc_id']) + ); + $what .= 'delete_hub '; + $changed = true; + } + continue; + } + + // Existing hubs are dealt with. Now let's process any new ones. + // New hub claiming to be primary. Make it so by removing any existing primaries. + + if(intval($location['primary'])) { + $r = q("update hubloc set hubloc_primary = 0, hubloc_updated = '%s' where hubloc_hash = '%s' and hubloc_primary = 1", + dbesc(datetime_convert()), + dbesc($sender['hash']) + ); + } + + logger('New hub: ' . $location['url']); + + $r = hubloc_store_lowlevel( + [ + 'hubloc_guid' => $sender['id'], + 'hubloc_guid_sig' => $sender['id_sig'], + 'hubloc_id_url' => $location['id_url'], + 'hubloc_hash' => $sender['hash'], + 'hubloc_addr' => $location['address'], + 'hubloc_network' => 'zot6', + 'hubloc_primary' => intval($location['primary']), + 'hubloc_url' => $location['url'], + 'hubloc_url_sig' => $location['url_sig'], + 'hubloc_site_id' => Libzot::make_xchan_hash($location['url'],$location['sitekey']), + 'hubloc_host' => $location['host'], + 'hubloc_callback' => $location['callback'], + 'hubloc_sitekey' => $location['sitekey'], + 'hubloc_updated' => datetime_convert(), + 'hubloc_connected' => datetime_convert() + ] + ); + + $what .= 'newhub '; + $changed = true; + + if($location['primary']) { + $r = q("select * from hubloc where hubloc_addr = '%s' and hubloc_sitekey = '%s' limit 1", + dbesc($location['address']), + dbesc($location['sitekey']) + ); + if($r) + hubloc_change_primary($r[0]); + } + } + + // get rid of any hubs we have for this channel which weren't reported. + + if($absolute && $xisting) { + foreach($xisting as $x) { + if(! array_key_exists('updated',$x)) { + logger('Deleting unreferenced hub location ' . $x['hubloc_addr']); + $r = q("update hubloc set hubloc_deleted = 1, hubloc_updated = '%s' where hubloc_id = %d", + dbesc(datetime_convert()), + intval($x['hubloc_id']) + ); + $what .= 'removed_hub '; + $changed = true; + } + } + } + } + else { + logger('No locations to sync!'); + } + + $ret['change_message'] = $what; + $ret['changed'] = $changed; + + return $ret; + } + + + static function keychange($channel,$arr) { + + // verify the keychange operation + if(! Libzot::verify($arr['channel']['channel_pubkey'],$arr['keychange']['new_sig'],$channel['channel_prvkey'])) { + logger('sync keychange: verification failed'); + return; + } + + $sig = Libzot::sign($channel['channel_guid'],$arr['channel']['channel_prvkey']); + $hash = Libzot::make_xchan_hash($channel['channel_guid'],$arr['channel']['channel_pubkey']); + + + $r = q("update channel set channel_prvkey = '%s', channel_pubkey = '%s', channel_guid_sig = '%s', + channel_hash = '%s' where channel_id = %d", + dbesc($arr['channel']['channel_prvkey']), + dbesc($arr['channel']['channel_pubkey']), + dbesc($sig), + dbesc($hash), + intval($channel['channel_id']) + ); + if(! $r) { + logger('keychange sync: channel update failed'); + return; + } + + $r = q("select * from channel where channel_id = %d", + intval($channel['channel_id']) + ); + + if(! $r) { + logger('keychange sync: channel retrieve failed'); + return; + } + + $channel = $r[0]; + + $h = q("select * from hubloc where hubloc_hash = '%s' and hubloc_url = '%s' ", + dbesc($arr['keychange']['old_hash']), + dbesc(z_root()) + ); + + if($h) { + foreach($h as $hv) { + $hv['hubloc_guid_sig'] = $sig; + $hv['hubloc_hash'] = $hash; + $hv['hubloc_url_sig'] = Libzot::sign(z_root(),$channel['channel_prvkey']); + hubloc_store_lowlevel($hv); + } + } + + $x = q("select * from xchan where xchan_hash = '%s' ", + dbesc($arr['keychange']['old_hash']) + ); + + $check = q("select * from xchan where xchan_hash = '%s'", + dbesc($hash) + ); + + if(($x) && (! $check)) { + $oldxchan = $x[0]; + foreach($x as $xv) { + $xv['xchan_guid_sig'] = $sig; + $xv['xchan_hash'] = $hash; + $xv['xchan_pubkey'] = $channel['channel_pubkey']; + xchan_store_lowlevel($xv); + $newxchan = $xv; + } + } + + $a = q("select * from abook where abook_xchan = '%s' and abook_self = 1", + dbesc($arr['keychange']['old_hash']) + ); + + if($a) { + q("update abook set abook_xchan = '%s' where abook_id = %d", + dbesc($hash), + intval($a[0]['abook_id']) + ); + } + + xchan_change_key($oldxchan,$newxchan,$arr['keychange']); + + } + +}
\ No newline at end of file diff --git a/Zotlabs/Lib/Libzot.php b/Zotlabs/Lib/Libzot.php new file mode 100644 index 000000000..ec9db4ce1 --- /dev/null +++ b/Zotlabs/Lib/Libzot.php @@ -0,0 +1,2849 @@ +<?php + +namespace Zotlabs\Lib; + +/** + * @brief lowlevel implementation of Zot6 protocol. + * + */ + +use Zotlabs\Lib\DReport; +use Zotlabs\Lib\Enotify; +use Zotlabs\Lib\Group; +use Zotlabs\Lib\Libsync; +use Zotlabs\Lib\Libzotdir; +use Zotlabs\Lib\System; +use Zotlabs\Lib\MessageFilter; +use Zotlabs\Lib\Queue; +use Zotlabs\Lib\Zotfinger; +use Zotlabs\Web\HTTPSig; + +require_once('include/crypto.php'); + + +class Libzot { + + /** + * @brief Generates a unique string for use as a zot guid. + * + * Generates a unique string for use as a zot guid using our DNS-based url, the + * channel nickname and some entropy. + * The entropy ensures uniqueness against re-installs where the same URL and + * nickname are chosen. + * + * @note zot doesn't require this to be unique. Internally we use a whirlpool + * hash of this guid and the signature of this guid signed with the channel + * private key. This can be verified and should make the probability of + * collision of the verified result negligible within the constraints of our + * immediate universe. + * + * @param string $channel_nick a unique nickname of controlling entity + * @returns string + */ + + static function new_uid($channel_nick) { + $rawstr = z_root() . '/' . $channel_nick . '.' . mt_rand(); + return(base64url_encode(hash('whirlpool', $rawstr, true), true)); + } + + + /** + * @brief Generates a portable hash identifier for a channel. + * + * Generates a portable hash identifier for the channel identified by $guid and + * $pubkey. + * + * @note This ID is portable across the network but MUST be calculated locally + * by verifying the signature and can not be trusted as an identity. + * + * @param string $guid + * @param string $pubkey + */ + + static function make_xchan_hash($guid, $pubkey) { + return base64url_encode(hash('whirlpool', $guid . $pubkey, true)); + } + + /** + * @brief Given a zot hash, return all distinct hubs. + * + * This function is used in building the zot discovery packet and therefore + * should only be used by channels which are defined on this hub. + * + * @param string $hash - xchan_hash + * @returns array of hubloc (hub location structures) + * + */ + + static function get_hublocs($hash) { + + /* Only search for active hublocs - e.g. those that haven't been marked deleted */ + + $ret = q("select * from hubloc where hubloc_hash = '%s' and hubloc_deleted = 0 order by hubloc_url ", + dbesc($hash) + ); + + return $ret; + } + + /** + * @brief Builds a zot6 notification packet. + * + * Builds a zot6 notification packet that you can either store in the queue with + * a message array or call zot_zot to immediately zot it to the other side. + * + * @param array $channel + * sender channel structure + * @param string $type + * packet type: one of 'ping', 'pickup', 'purge', 'refresh', 'keychange', 'force_refresh', 'notify', 'auth_check' + * @param array $recipients + * envelope recipients, array of portable_id's; empty for public posts + * @param string msg + * optional message + * @param string $remote_key + * optional public site key of target hub used to encrypt entire packet + * NOTE: remote_key and encrypted packets are required for 'auth_check' packets, optional for all others + * @param string $methods + * optional comma separated list of encryption methods @ref self::best_algorithm() + * @returns string json encoded zot packet + */ + + static function build_packet($channel, $type = 'activity', $recipients = null, $msg = '', $encoding = 'activitystreams', $remote_key = null, $methods = '') { + + $sig_method = get_config('system','signature_algorithm','sha256'); + + $data = [ + 'type' => $type, + 'encoding' => $encoding, + 'sender' => $channel['channel_hash'], + 'site_id' => self::make_xchan_hash(z_root(), get_config('system','pubkey')), + 'version' => System::get_zot_revision(), + ]; + + if ($recipients) { + $data['recipients'] = $recipients; + } + + if ($msg) { + $actor = channel_url($channel); + if ($encoding === 'activitystreams' && array_key_exists('actor',$msg) && is_string($msg['actor']) && $actor === $msg['actor']) { + $msg = JSalmon::sign($msg,$actor,$channel['channel_prvkey']); + } + $data['data'] = $msg; + } + else { + unset($data['encoding']); + } + + logger('packet: ' . print_r($data,true), LOGGER_DATA, LOG_DEBUG); + + if ($remote_key) { + $algorithm = self::best_algorithm($methods); + if ($algorithm) { + $data = crypto_encapsulate(json_encode($data),$remote_key, $algorithm); + } + } + + return json_encode($data); + } + + + /** + * @brief Choose best encryption function from those available on both sites. + * + * @param string $methods + * comma separated list of encryption methods + * @return string first match from our site method preferences crypto_methods() array + * of a method which is common to both sites; or 'aes256cbc' if no matches are found. + */ + + static function best_algorithm($methods) { + + $x = [ + 'methods' => $methods, + 'result' => '' + ]; + + /** + * @hooks zot_best_algorithm + * Called when negotiating crypto algorithms with remote sites. + * * \e string \b methods - comma separated list of encryption methods + * * \e string \b result - the algorithm to return + */ + + call_hooks('zot_best_algorithm', $x); + + if($x['result']) + return $x['result']; + + if($methods) { + $x = explode(',', $methods); + if($x) { + $y = crypto_methods(); + if($y) { + foreach($y as $yv) { + $yv = trim($yv); + if(in_array($yv, $x)) { + return($yv); + } + } + } + } + } + + return ''; + } + + + /** + * @brief send a zot message + * + * @see z_post_url() + * + * @param string $url + * @param array $data + * @param array $channel (required if using zot6 delivery) + * @param array $crypto (required if encrypted httpsig, requires hubloc_sitekey and site_crypto elements) + * @return array see z_post_url() for returned data format + */ + + static function zot($url, $data, $channel = null,$crypto = null) { + + if($channel) { + $headers = [ + 'X-Zot-Token' => random_string(), + 'Digest' => HTTPSig::generate_digest_header($data), + 'Content-type' => 'application/x-zot+json' + ]; + + $h = HTTPSig::create_sig($headers,$channel['channel_prvkey'],channel_url($channel),false,'sha512', + (($crypto) ? [ 'key' => $crypto['hubloc_sitekey'], 'algorithm' => self::best_algorithm($crypto['site_crypto']) ] : false)); + } + else { + $h = []; + } + + $redirects = 0; + + return z_post_url($url,$data,$redirects,((empty($h)) ? [] : [ 'headers' => $h ])); + } + + + /** + * @brief Refreshes after permission changed or friending, etc. + * + * + * refresh is typically invoked when somebody has changed permissions of a channel and they are notified + * to fetch new permissions via a finger/discovery operation. This may result in a new connection + * (abook entry) being added to a local channel and it may result in auto-permissions being granted. + * + * Friending in zot is accomplished by sending a refresh packet to a specific channel which indicates a + * permission change has been made by the sender which affects the target channel. The hub controlling + * the target channel does targetted discovery (a zot-finger request requesting permissions for the local + * channel). These are decoded here, and if necessary and abook structure (addressbook) is created to store + * the permissions assigned to this channel. + * + * Initially these abook structures are created with a 'pending' flag, so that no reverse permissions are + * implied until this is approved by the owner channel. A channel can also auto-populate permissions in + * return and send back a refresh packet of its own. This is used by forum and group communication channels + * so that friending and membership in the channel's "club" is automatic. + * + * @param array $them => xchan structure of sender + * @param array $channel => local channel structure of target recipient, required for "friending" operations + * @param array $force (optional) default false + * + * @return boolean + * * \b true if successful + * * otherwise \b false + */ + + static function refresh($them, $channel = null, $force = false) { + + logger('them: ' . print_r($them,true), LOGGER_DATA, LOG_DEBUG); + if ($channel) + logger('channel: ' . print_r($channel,true), LOGGER_DATA, LOG_DEBUG); + + $url = null; + + if ($them['hubloc_id_url']) { + $url = $them['hubloc_id_url']; + } + else { + $r = null; + + // if they re-installed the server we could end up with the wrong record - pointing to the old install. + // We'll order by reverse id to try and pick off the newest one first and hopefully end up with the + // correct hubloc. If this doesn't work we may have to re-write this section to try them all. + + if(array_key_exists('xchan_addr',$them) && $them['xchan_addr']) { + $r = q("select hubloc_id_url, hubloc_primary from hubloc where hubloc_addr = '%s' order by hubloc_id desc", + dbesc($them['xchan_addr']) + ); + } + if(! $r) { + $r = q("select hubloc_id_url, hubloc_primary from hubloc where hubloc_hash = '%s' order by hubloc_id desc", + dbesc($them['xchan_hash']) + ); + } + + if ($r) { + foreach ($r as $rr) { + if (intval($rr['hubloc_primary'])) { + $url = $rr['hubloc_id_url']; + $record = $rr; + } + } + if (! $url) { + $url = $r[0]['hubloc_id_url']; + } + } + } + if (! $url) { + logger('zot_refresh: no url'); + return false; + } + + $s = q("select site_dead from site where site_url = '%s' limit 1", + dbesc($url) + ); + + if($s && intval($s[0]['site_dead']) && (! $force)) { + logger('zot_refresh: site ' . $url . ' is marked dead and force flag is not set. Cancelling operation.'); + return false; + } + + $record = Zotfinger::exec($url,$channel); + + // Check the HTTP signature + + $hsig = $record['signature']; + 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 $result; + } + + + 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']) + return false; + + if($channel && $record['data']['permissions']) { + $old_read_stream_perm = their_perms_contains($channel['channel_id'],$x['hash'],'view_stream'); + set_abconfig($channel['channel_id'],$x['hash'],'system','their_perms',$record['data']['permissions']); + + if(array_key_exists('profile',$record['data']) && array_key_exists('next_birthday',$record['data']['profile'])) { + $next_birthday = datetime_convert('UTC','UTC',$record['data']['profile']['next_birthday']); + } + else { + $next_birthday = NULL_DATE; + } + + $profile_assign = get_pconfig($channel['channel_id'],'system','profile_assign',''); + + // Keep original perms to check if we need to notify them + $previous_perms = get_all_perms($channel['channel_id'],$x['hash']); + + $r = q("select * from abook where abook_xchan = '%s' and abook_channel = %d and abook_self = 0 limit 1", + dbesc($x['hash']), + intval($channel['channel_id']) + ); + + if($r) { + + // connection exists + + // if the dob is the same as what we have stored (disregarding the year), keep the one + // 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)) + $next_birthday = $r[0]['abook_dob']; + + $y = q("update abook set abook_dob = '%s' + where abook_xchan = '%s' and abook_channel = %d + and abook_self = 0 ", + dbescdate($next_birthday), + dbesc($x['hash']), + intval($channel['channel_id']) + ); + + 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']))) + \Zotlabs\Daemon\Master::Summon(array('Onepoll',$r[0]['abook_id'])); + } + } + else { + + $p = \Zotlabs\Access\Permissions::connect_perms($channel['channel_id']); + $my_perms = \Zotlabs\Access\Permissions::serialise($p['perms']); + + $automatic = $p['automatic']; + + // new connection + + if($my_perms) { + set_abconfig($channel['channel_id'],$x['hash'],'system','my_perms',$my_perms); + } + + $closeness = get_pconfig($channel['channel_id'],'system','new_abook_closeness'); + if($closeness === false) + $closeness = 80; + + $y = abook_store_lowlevel( + [ + 'abook_account' => intval($channel['channel_account_id']), + 'abook_channel' => intval($channel['channel_id']), + 'abook_closeness' => intval($closeness), + 'abook_xchan' => $x['hash'], + 'abook_profile' => $profile_assign, + 'abook_created' => datetime_convert(), + 'abook_updated' => datetime_convert(), + 'abook_dob' => $next_birthday, + 'abook_pending' => intval(($automatic) ? 0 : 1) + ] + ); + + if($y) { + logger("New introduction received for {$channel['channel_name']}"); + $new_perms = get_all_perms($channel['channel_id'],$x['hash']); + + // Send a clone sync packet and a permissions update if permissions have changed + + $new_connection = q("select * from abook left join xchan on abook_xchan = xchan_hash where abook_xchan = '%s' and abook_channel = %d and abook_self = 0 order by abook_created desc limit 1", + dbesc($x['hash']), + intval($channel['channel_id']) + ); + + if($new_connection) { + if(! \Zotlabs\Access\Permissions::PermsCompare($new_perms,$previous_perms)) + \Zotlabs\Daemon\Master::Summon(array('Notifier','permissions_create',$new_connection[0]['abook_id'])); + Enotify::submit( + [ + 'type' => NOTIFY_INTRO, + 'from_xchan' => $x['hash'], + 'to_xchan' => $channel['channel_hash'], + 'link' => z_root() . '/connedit/' . $new_connection[0]['abook_id'] + ] + ); + + if(intval($permissions['view_stream'])) { + if(intval(get_pconfig($channel['channel_id'],'perm_limits','send_stream') & PERMS_PENDING) + || (! intval($new_connection[0]['abook_pending']))) + \Zotlabs\Daemon\Master::Summon(array('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. + + if(! intval($new_connection[0]['abook_pending'])) { + $default_group = $channel['channel_default_group']; + if($default_group) { + $g = Group::rec_byhash($channel['channel_id'],$default_group); + if($g) + Group::member_add($channel['channel_id'],'',$x['hash'],$g['id']); + } + } + + 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) + $new_connection['abconfig'] = $abconfig; + + Libsync::build_sync_packet($channel['channel_id'], array('abook' => $new_connection)); + } + } + + } + return true; + } + return false; + } + + /** + * @brief Look up if channel is known and previously verified. + * + * A guid and a url, both signed by the sender, distinguish a known sender at a + * known location. + * This function looks these up to see if the channel is known and therefore + * previously verified. If not, we will need to verify it. + * + * @param array $arr an associative array which must contain: + * * \e string \b id => id of conversant + * * \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 + * @param boolean $multiple (optional) default false + * + * @return array|null + * * null if site is blacklisted or not found + * * otherwise an array with an hubloc record + */ + + static function gethub($arr, $multiple = false) { + + if($arr['id'] && $arr['id_sig'] && $arr['location'] && $arr['location_sig']) { + + if(! check_siteallowed($arr['location'])) { + logger('blacklisted site: ' . $arr['location']); + return null; + } + + $limit = (($multiple) ? '' : ' limit 1 '); + + $r = q("select hubloc.*, site.site_crypto from hubloc left join site on hubloc_url = site_url + where hubloc_guid = '%s' and hubloc_guid_sig = '%s' + and hubloc_url = '%s' and hubloc_url_sig = '%s' + and hubloc_site_id = '%s' $limit", + dbesc($arr['id']), + dbesc($arr['id_sig']), + dbesc($arr['location']), + dbesc($arr['location_sig']), + dbesc($arr['site_id']) + ); + if($r) { + logger('Found', LOGGER_DEBUG); + return (($multiple) ? $r : $r[0]); + } + } + logger('Not found: ' . print_r($arr,true), LOGGER_DEBUG); + + return false; + } + + + + + static function valid_hub($sender,$site_id) { + + $r = q("select hubloc.*, site.site_crypto from hubloc left join site on hubloc_url = site_url where hubloc_hash = '%s' and hubloc_site_id = '%s' limit 1", + dbesc($sender), + dbesc($site_id) + ); + if(! $r) { + return null; + } + + if(! check_siteallowed($r[0]['hubloc_url'])) { + logger('blacklisted site: ' . $r[0]['hubloc_url']); + return null; + } + + if(! check_channelallowed($r[0]['hubloc_hash'])) { + logger('blacklisted channel: ' . $r[0]['hubloc_hash']); + return null; + } + + return $r[0]; + + } + + /** + * @brief Registers an unknown hub. + * + * A communication has been received which has an unknown (to us) sender. + * Perform discovery based on our calculated hash of the sender at the + * origination address. This will fetch the discovery packet of the sender, + * which contains the public key we need to verify our guid and url signatures. + * + * @param array $arr an associative array which must contain: + * * \e string \b guid => guid of conversant + * * \e string \b guid_sig => guid signed with conversant's private key + * * \e string \b url => URL of the origination hub of this communication + * * \e string \b url_sig => URL signed with conversant's private key + * + * @return array An associative array with + * * \b success boolean true or false + * * \b message (optional) error string only if success is false + */ + + static function register_hub($id) { + + $id_hash = false; + $valid = false; + $hsig_valid = false; + + $result = [ 'success' => false ]; + + if(! $id) { + return $result; + } + + $record = Zotfinger::exec($id); + + // Check the HTTP signature + + $hsig = $record['signature']; + if($hsig['signer'] === $id && $hsig['header_valid'] === true && $hsig['content_valid'] === true) { + $hsig_valid = true; + } + if(! $hsig_valid) { + logger('http signature not valid: ' . print_r($hsig,true)); + return $result; + } + + $c = self::import_xchan($record['data']); + if($c['success']) { + $result['success'] = true; + } + else { + logger('Failure to verify zot packet'); + } + + return $result; + } + + /** + * @brief Takes an associative array of a fetch discovery packet and updates + * all internal data structures which need to be updated as a result. + * + * @param array $arr => json_decoded discovery packet + * @param int $ud_flags + * Determines whether to create a directory update record if any changes occur, default is UPDATE_FLAGS_UPDATED + * $ud_flags = UPDATE_FLAGS_FORCED indicates a forced refresh where we unconditionally create a directory update record + * this typically occurs once a month for each channel as part of a scheduled ping to notify the directory + * that the channel still exists + * @param array $ud_arr + * If set [typically by update_directory_entry()] indicates a specific update table row and more particularly + * contains a particular address (ud_addr) which needs to be updated in that table. + * + * @return array An associative array with: + * * \e boolean \b success boolean true or false + * * \e string \b message (optional) error string only if success is false + */ + + 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 + * * \e array + */ + call_hooks('import_xchan', $arr); + + $ret = array('success' => false); + $dirmode = intval(get_config('system','directory_mode')); + + $changed = false; + $what = ''; + + if(! ($arr['id'] && $arr['id_sig'])) { + logger('No identity information provided. ' . print_r($arr,true)); + return $ret; + } + + $xchan_hash = self::make_xchan_hash($arr['id'],$arr['public_key']); + $arr['hash'] = $xchan_hash; + + $import_photos = false; + + $sig_methods = ((array_key_exists('signing',$arr) && is_array($arr['signing'])) ? $arr['signing'] : [ 'sha256' ]); + $verified = false; + + if(! self::verify($arr['id'],$arr['id_sig'],$arr['public_key'])) { + logger('Unable to verify channel signature for ' . $arr['address']); + return $ret; + } + else { + $verified = true; + } + + if(! $verified) { + $ret['message'] = t('Unable to verify channel signature'); + return $ret; + } + + logger('import_xchan: ' . $xchan_hash, LOGGER_DEBUG); + + $r = q("select * from xchan where xchan_hash = '%s' limit 1", + dbesc($xchan_hash) + ); + + if(! array_key_exists('connect_url', $arr)) + $arr['connect_url'] = ''; + + if($r) { + if($arr['photo'] && array_key_exists('updated',$arr['photo']) && $r[0]['xchan_photo_date'] != $arr['photo']['updated']) { + $import_photos = true; + } + + // if we import an entry from a site that's not ours and either or both of us is off the grid - hide the entry. + /** @TODO: check if we're the same directory realm, which would mean we are allowed to see it */ + + $dirmode = get_config('system','directory_mode'); + + if((($arr['site']['directory_mode'] === 'standalone') || ($dirmode & DIRECTORY_MODE_STANDALONE)) && ($arr['site']['url'] != z_root())) + $arr['searchable'] = false; + + $hidden = (1 - intval($arr['searchable'])); + + $hidden_changed = $adult_changed = $deleted_changed = $pubforum_changed = 0; + + if(intval($r[0]['xchan_hidden']) != (1 - intval($arr['searchable']))) + $hidden_changed = 1; + if(intval($r[0]['xchan_selfcensored']) != intval($arr['adult_content'])) + $adult_changed = 1; + if(intval($r[0]['xchan_deleted']) != intval($arr['deleted'])) + $deleted_changed = 1; + if(intval($r[0]['xchan_pubforum']) != intval($arr['public_forum'])) + $pubforum_changed = 1; + + if($arr['protocols']) { + $protocols = implode(',',$arr['protocols']); + if($protocols !== 'zot6') { + set_xconfig($xchan_hash,'system','protocols',$protocols); + } + else { + del_xconfig($xchan_hash,'system','protocols'); + } + } + + if(($r[0]['xchan_name_date'] != $arr['name_updated']) + || ($r[0]['xchan_connurl'] != $arr['primary_location']['connections_url']) + || ($r[0]['xchan_addr'] != $arr['primary_location']['address']) + || ($r[0]['xchan_follow'] != $arr['primary_location']['follow_url']) + || ($r[0]['xchan_connpage'] != $arr['connect_url']) + || ($r[0]['xchan_url'] != $arr['primary_location']['url']) + || $hidden_changed || $adult_changed || $deleted_changed || $pubforum_changed ) { + $rup = q("update xchan set xchan_name = '%s', xchan_name_date = '%s', xchan_connurl = '%s', xchan_follow = '%s', + xchan_connpage = '%s', xchan_hidden = %d, xchan_selfcensored = %d, xchan_deleted = %d, xchan_pubforum = %d, + xchan_addr = '%s', xchan_url = '%s' where xchan_hash = '%s'", + dbesc(($arr['name']) ? escape_tags($arr['name']) : '-'), + dbesc($arr['name_updated']), + dbesc($arr['primary_location']['connections_url']), + dbesc($arr['primary_location']['follow_url']), + dbesc($arr['primary_location']['connect_url']), + intval(1 - intval($arr['searchable'])), + intval($arr['adult_content']), + intval($arr['deleted']), + intval($arr['public_forum']), + dbesc(escape_tags($arr['primary_location']['address'])), + dbesc(escape_tags($arr['primary_location']['url'])), + dbesc($xchan_hash) + ); + + logger('Update: existing: ' . print_r($r[0],true), LOGGER_DATA, LOG_DEBUG); + logger('Update: new: ' . print_r($arr,true), LOGGER_DATA, LOG_DEBUG); + $what .= 'xchan '; + $changed = true; + } + } + else { + $import_photos = true; + + if((($arr['site']['directory_mode'] === 'standalone') + || ($dirmode & DIRECTORY_MODE_STANDALONE)) + && ($arr['site']['url'] != z_root())) + $arr['searchable'] = false; + + $x = xchan_store_lowlevel( + [ + 'xchan_hash' => $xchan_hash, + 'xchan_guid' => $arr['id'], + 'xchan_guid_sig' => $arr['id_sig'], + 'xchan_pubkey' => $arr['public_key'], + 'xchan_photo_mimetype' => $arr['photo_mimetype'], + 'xchan_photo_l' => $arr['photo'], + 'xchan_addr' => escape_tags($arr['primary_location']['address']), + 'xchan_url' => escape_tags($arr['primary_location']['url']), + 'xchan_connurl' => $arr['primary_location']['connections_url'], + 'xchan_follow' => $arr['primary_location']['follow_url'], + 'xchan_connpage' => $arr['connect_url'], + 'xchan_name' => (($arr['name']) ? escape_tags($arr['name']) : '-'), + 'xchan_network' => 'zot6', + 'xchan_photo_date' => $arr['photo_updated'], + 'xchan_name_date' => $arr['name_updated'], + 'xchan_hidden' => intval(1 - intval($arr['searchable'])), + 'xchan_selfcensored' => $arr['adult_content'], + 'xchan_deleted' => $arr['deleted'], + 'xchan_pubforum' => $arr['public_forum'] + ] + ); + + $what .= 'new_xchan'; + $changed = true; + } + + if($import_photos) { + + require_once('include/photo/photo_driver.php'); + + // see if this is a channel clone that's hosted locally - which we treat different from other xchans/connections + + $local = q("select channel_account_id, channel_id from channel where channel_hash = '%s' limit 1", + dbesc($xchan_hash) + ); + if($local) { + $ph = z_fetch_url($arr['photo']['url'], true); + if($ph['success']) { + + $hash = import_channel_photo($ph['body'], $arr['photo']['type'], $local[0]['channel_account_id'], $local[0]['channel_id']); + + if($hash) { + // unless proven otherwise + $is_default_profile = 1; + + $profile = q("select is_default from profile where aid = %d and uid = %d limit 1", + intval($local[0]['channel_account_id']), + intval($local[0]['channel_id']) + ); + if($profile) { + if(! intval($profile[0]['is_default'])) + $is_default_profile = 0; + } + + // If setting for the default profile, unset the profile photo flag from any other photos I own + if($is_default_profile) { + q("UPDATE photo SET photo_usage = %d WHERE photo_usage = %d AND resource_id != '%s' AND aid = %d AND uid = %d", + intval(PHOTO_NORMAL), + intval(PHOTO_PROFILE), + dbesc($hash), + intval($local[0]['channel_account_id']), + intval($local[0]['channel_id']) + ); + } + } + + // reset the names in case they got messed up when we had a bug in this function + $photos = array( + z_root() . '/photo/profile/l/' . $local[0]['channel_id'], + z_root() . '/photo/profile/m/' . $local[0]['channel_id'], + z_root() . '/photo/profile/s/' . $local[0]['channel_id'], + $arr['photo_mimetype'], + false + ); + } + } + else { + $photos = import_xchan_photo($arr['photo']['url'], $xchan_hash); + } + if($photos) { + if($photos[4]) { + // importing the photo failed somehow. Leave the photo_date alone so we can try again at a later date. + // This often happens when somebody joins the matrix with a bad cert. + $r = q("update xchan set xchan_photo_l = '%s', xchan_photo_m = '%s', xchan_photo_s = '%s', xchan_photo_mimetype = '%s' + where xchan_hash = '%s'", + dbesc($photos[0]), + dbesc($photos[1]), + dbesc($photos[2]), + dbesc($photos[3]), + dbesc($xchan_hash) + ); + } + else { + $r = q("update xchan set xchan_photo_date = '%s', xchan_photo_l = '%s', xchan_photo_m = '%s', xchan_photo_s = '%s', xchan_photo_mimetype = '%s' + where xchan_hash = '%s'", + dbescdate(datetime_convert('UTC','UTC',$arr['photo_updated'])), + dbesc($photos[0]), + dbesc($photos[1]), + dbesc($photos[2]), + dbesc($photos[3]), + dbesc($xchan_hash) + ); + } + $what .= 'photo '; + $changed = true; + } + } + + // what we are missing for true hub independence is for any changes in the primary hub to + // get reflected not only in the hublocs, but also to update the URLs and addr in the appropriate xchan + + $s = Libsync::sync_locations($arr, $arr); + + if($s) { + if($s['change_message']) + $what .= $s['change_message']; + if($s['changed']) + $changed = $s['changed']; + if($s['message']) + $ret['message'] .= $s['message']; + } + + // Which entries in the update table are we interested in updating? + + $address = (($ud_arr && $ud_arr['ud_addr']) ? $ud_arr['ud_addr'] : $arr['address']); + + + // Are we a directory server of some kind? + + $other_realm = false; + $realm = get_directory_realm(); + if(array_key_exists('site',$arr) + && array_key_exists('realm',$arr['site']) + && (strpos($arr['site']['realm'],$realm) === false)) + $other_realm = true; + + + if($dirmode != DIRECTORY_MODE_NORMAL) { + + // We're some kind of directory server. However we can only add directory information + // if the entry is in the same realm (or is a sub-realm). Sub-realms are denoted by + // including the parent realm in the name. e.g. 'RED_GLOBAL:foo' would allow an entry to + // be in directories for the local realm (foo) and also the RED_GLOBAL realm. + + if(array_key_exists('profile',$arr) && is_array($arr['profile']) && (! $other_realm)) { + $profile_changed = Libzotdir::import_directory_profile($xchan_hash,$arr['profile'],$address,$ud_flags, 1); + if($profile_changed) { + $what .= 'profile '; + $changed = true; + } + } + else { + logger('Profile not available - hiding'); + // they may have made it private + $r = q("delete from xprof where xprof_hash = '%s'", + dbesc($xchan_hash) + ); + $r = q("delete from xtag where xtag_hash = '%s' and xtag_flags = 0", + dbesc($xchan_hash) + ); + } + } + + if(array_key_exists('site',$arr) && is_array($arr['site'])) { + $profile_changed = self::import_site($arr['site']); + if($profile_changed) { + $what .= 'site '; + $changed = true; + } + } + + if(($changed) || ($ud_flags == UPDATE_FLAGS_FORCED)) { + $guid = random_string() . '@' . \App::get_hostname(); + Libzotdir::update_modtime($xchan_hash,$guid,$address,$ud_flags); + logger('Changed: ' . $what,LOGGER_DEBUG); + } + elseif(! $ud_flags) { + // nothing changed but we still need to update the updates record + q("update updates set ud_flags = ( ud_flags | %d ) where ud_addr = '%s' and not (ud_flags & %d) > 0 ", + intval(UPDATE_FLAGS_UPDATED), + dbesc($address), + intval(UPDATE_FLAGS_UPDATED) + ); + } + + if(! x($ret,'message')) { + $ret['success'] = true; + $ret['hash'] = $xchan_hash; + } + + logger('Result: ' . print_r($ret,true), LOGGER_DATA, LOG_DEBUG); + return $ret; + } + + /** + * @brief Called immediately after sending a zot message which is using queue processing. + * + * Updates the queue item according to the response result and logs any information + * returned to aid communications troubleshooting. + * + * @param string $hub - url of site we just contacted + * @param array $arr - output of z_post_url() + * @param array $outq - The queue structure attached to this request + */ + + static function process_response($hub, $arr, $outq) { + + logger('remote: ' . print_r($arr,true),LOGGER_DATA); + + if(! $arr['success']) { + logger('Failed: ' . $hub); + return; + } + + $x = json_decode($arr['body'], true); + + if(! $x) { + logger('No json from ' . $hub); + logger('Headers: ' . print_r($arr['header'], true), LOGGER_DATA, LOG_DEBUG); + } + + $x = crypto_unencapsulate($x, get_config('system','prvkey')); + if(! is_array($x)) { + $x = json_decode($x,true); + } + + if(! $x['success']) { + + // handle remote validation issues + + $b = q("update dreport set dreport_result = '%s', dreport_time = '%s' where dreport_queue = '%s'", + dbesc(($x['message']) ? $x['message'] : 'unknown delivery error'), + dbesc(datetime_convert()), + dbesc($outq['outq_hash']) + ); + } + + if(array_key_exists('delivery_report',$x) && is_array($x['delivery_report'])) { + foreach($x['delivery_report'] as $xx) { + if(is_array($xx) && array_key_exists('message_id',$xx) && DReport::is_storable($xx)) { + q("insert into dreport ( dreport_mid, dreport_site, dreport_recip, dreport_name, dreport_result, dreport_time, dreport_xchan ) values ( '%s', '%s', '%s','%s','%s','%s','%s' ) ", + dbesc($xx['message_id']), + dbesc($xx['location']), + dbesc($xx['recipient']), + dbesc($xx['name']), + dbesc($xx['status']), + dbesc(datetime_convert($xx['date'])), + dbesc($xx['sender']) + ); + } + } + + // we have a more descriptive delivery report, so discard the per hub 'queue' report. + + q("delete from dreport where dreport_queue = '%s' ", + dbesc($outq['outq_hash']) + ); + } + + // update the timestamp for this site + + q("update site set site_dead = 0, site_update = '%s' where site_url = '%s'", + dbesc(datetime_convert()), + dbesc(dirname($hub)) + ); + + // synchronous message types are handled immediately + // async messages remain in the queue until processed. + + if(intval($outq['outq_async'])) + Queue::remove($outq['outq_hash'],$outq['outq_channel']); + + logger('zot_process_response: ' . print_r($x,true), LOGGER_DEBUG); + } + + /** + * @brief + * + * We received a notification packet (in mod_post) that a message is waiting for us, and we've verified the sender. + * Check if the site is using zot6 delivery and includes a verified HTTP Signature, signed content, and a 'msg' field, + * and also that the signer and the sender match. + * If that happens, we do not need to fetch/pickup the message - we have it already and it is verified. + * Translate it into the form we need for zot_import() and import it. + * + * Otherwise send back a pickup message, using our message tracking ID ($arr['secret']), which we will sign with our site + * private key. + * The entire pickup message is encrypted with the remote site's public key. + * If everything checks out on the remote end, we will receive back a packet containing one or more messages, + * which will be processed and delivered before this function ultimately returns. + * + * @see zot_import() + * + * @param array $arr + * decrypted and json decoded notify packet from remote site + * @return array from zot_import() + */ + + static function fetch($arr) { + + logger('zot_fetch: ' . print_r($arr,true), LOGGER_DATA, LOG_DEBUG); + + return self::import($arr); + + } + + /** + * @brief Process incoming array of messages. + * + * Process an incoming array of messages which were obtained via pickup, and + * import, update, delete as directed. + * + * The message types handled here are 'activity' (e.g. posts), and 'sync'. + * + * @param array $arr + * 'pickup' structure returned from remote site + * @param string $sender_url + * the url specified by the sender in the initial communication. + * We will verify the sender and url in each returned message structure and + * also verify that all the messages returned match the site url that we are + * currently processing. + * + * @returns array + * Suitable for logging remotely, enumerating the processing results of each message/recipient combination + * * [0] => \e string $channel_hash + * * [1] => \e string $delivery_status + * * [2] => \e string $address + */ + + static function import($arr) { + + $env = $arr; + $private = false; + $return = []; + + $result = null; + + logger('Notify: ' . print_r($env,true), LOGGER_DATA, LOG_DEBUG); + + if(! is_array($env)) { + logger('decode error'); + return; + } + + $message_request = ((array_key_exists('message_id',$env)) ? true : false); + if($message_request) + logger('processing message request'); + + $has_data = array_key_exists('data',$env) && $env['data']; + $data = (($has_data) ? $env['data'] : false); + + $deliveries = null; + + if(array_key_exists('recipients',$env) && count($env['recipients'])) { + logger('specific recipients'); + logger('recipients: ' . print_r($env['recipients'],true),LOGGER_DEBUG); + + $recip_arr = []; + foreach($env['recipients'] as $recip) { + $recip_arr[] = $recip; + } + + $r = false; + if($recip_arr) { + stringify_array_elms($recip_arr,true); + $recips = implode(',',$recip_arr); + $r = q("select channel_hash as hash from channel where channel_hash in ( " . $recips . " ) and channel_removed = 0 "); + } + + if(! $r) { + logger('recips: no recipients on this site'); + return; + } + + // Response messages will inherit the privacy of the parent + + if($env['type'] !== 'response') + $private = true; + + $deliveries = ids_to_array($r,'hash'); + + // We found somebody on this site that's in the recipient list. + } + else { + + logger('public post'); + + + // Public post. look for any site members who are or may be accepting posts from this sender + // and who are allowed to see them based on the sender's permissions + // @fixme; + + $deliveries = self::public_recips($env); + + + } + + $deliveries = array_unique($deliveries); + + if(! $deliveries) { + logger('No deliveries on this site'); + return; + } + + + if($has_data) { + + if(in_array($env['type'],['activity','response'])) { + + if($env['encoding'] === 'zot') { + $arr = get_item_elements($data); + + $v = validate_item_elements($data,$arr); + + if(! $v['success']) { + logger('Activity rejected: ' . $v['message'] . ' ' . print_r($data,true)); + return; + } + } + elseif($env['encoding'] === 'activitystreams') { + + $AS = new \Zotlabs\Lib\ActivityStreams($data); + if(! $AS->is_valid()) { + logger('Activity rejected: ' . print_r($data,true)); + return; + } + $arr = \Zotlabs\Lib\Activity::decode_note($AS); + + logger($AS->debug()); + + $r = q("select hubloc_hash from hubloc where hubloc_id_url = '%s' limit 1", + dbesc($AS->actor['id']) + ); + + if($r) { + $arr['author_xchan'] = $r[0]['hubloc_hash']; + } + // @fixme (in individual delivery, change owner if needed) + $arr['owner_xchan'] = $env['sender']; + if($private) { + $arr['item_private'] = true; + } + // @fixme - spoofable + if($AS->data['hubloc']) { + $arr['item_verified'] = true; + } + if($AS->data['signed_data']) { + IConfig::Set($arr,'activitystreams','signed_data',$AS->data['signed_data'],false); + } + + } + + logger('Activity received: ' . print_r($arr,true), LOGGER_DATA, LOG_DEBUG); + logger('Activity recipients: ' . print_r($deliveries,true), LOGGER_DATA, LOG_DEBUG); + + $relay = (($env['type'] === 'response') ? true : false ); + + $result = self::process_delivery($env['sender'],$arr,$deliveries,$relay,false,$message_request); + } + elseif($env['type'] === 'sync') { + // $arr = get_channelsync_elements($data); + + $arr = json_decode($data,true); + + logger('Channel sync received: ' . print_r($arr,true), LOGGER_DATA, LOG_DEBUG); + logger('Channel sync recipients: ' . print_r($deliveries,true), LOGGER_DATA, LOG_DEBUG); + + $result = Libsync::process_channel_sync_delivery($env['sender'],$arr,$deliveries); + } + } + if ($result) { + $return = array_merge($return, $result); + } + return $return; + } + + + static function is_top_level($env) { + if($env['encoding'] === 'zot' && array_key_exists('flags',$env) && in_array('thread_parent', $env['flags'])) { + return true; + } + if($env['encoding'] === 'activitystreams') { + if(array_key_exists('inReplyTo',$env['data']) && $env['data']['inReplyTo']) { + return false; + } + return true; + } + return false; + } + + + /** + * @brief + * + * A public message with no listed recipients can be delivered to anybody who + * has PERMS_NETWORK for that type of post, PERMS_AUTHED (in-network senders are + * by definition authenticated) or PERMS_SITE and is one the same site, + * or PERMS_SPECIFIC and the sender is a contact who is granted permissions via + * their connection permissions in the address book. + * Here we take a given message and construct a list of hashes of everybody + * on the site that we should try and deliver to. + * Some of these will be rejected, but this gives us a place to start. + * + * @param array $msg + * @return NULL|array + */ + + static function public_recips($msg) { + + require_once('include/channel.php'); + + $check_mentions = false; + $include_sys = false; + + if($msg['type'] === 'activity') { + $disable_discover_tab = get_config('system','disable_discover_tab') || get_config('system','disable_discover_tab') === false; + if(! $disable_discover_tab) + $include_sys = true; + + $perm = 'send_stream'; + + if(self::is_top_level($msg)) { + $check_mentions = true; + } + } + elseif($msg['type'] === 'mail') + $perm = 'post_mail'; + + $r = []; + + $c = q("select channel_id, channel_hash from channel where channel_removed = 0"); + + if($c) { + foreach($c as $cc) { + if(perm_is_allowed($cc['channel_id'],$msg['sender'],$perm)) { + $r[] = $cc['channel_hash']; + } + } + } + + if($include_sys) { + $sys = get_sys_channel(); + if($sys) + $r[] = $sys['channel_hash']; + } + + + + // look for any public mentions on this site + // They will get filtered by tgroup_check() so we don't need to check permissions now + + if($check_mentions) { + // It's a top level post. Look at the tags. See if any of them are mentions and are on this hub. + if(array_path_exists('data/object/tag',$msg)) { + if(is_array($msg['data']['object']['tag']) && $msg['data']['object']['tag']) { + foreach($msg['data']['object']['tag'] as $tag) { + if($tag['type'] === 'Mention' && (strpos($tag['href'],z_root()) !== false)) { + $address = basename($tag['href']); + if($address) { + $z = q("select channel_hash as hash from channel where channel_address = '%s' + and channel_removed = 0 limit 1", + dbesc($address) + ); + if($z) { + $r[] = $z[0]['hash']; + } + } + } + } + } + } + } + else { + // This is a comment. We need to find any parent with ITEM_UPLINK set. But in fact, let's just return + // everybody that stored a copy of the parent. This way we know we're covered. We'll check the + // comment permissions when we deliver them. + + if(array_path_exists('data/inReplyTo',$msg)) { + $z = q("select owner_xchan as hash from item where parent_mid = '%s' ", + dbesc($msg['data']['inReplyTo']) + ); + if($z) { + foreach($z as $zv) { + $r[] = $zv['hash']; + } + } + } + } + + // There are probably a lot of duplicates in $r at this point. We need to filter those out. + // It's a bit of work since it's a multi-dimensional array + + if($r) { + $r = array_unique($r); + } + + logger('public_recips: ' . print_r($r,true), LOGGER_DATA, LOG_DEBUG); + return $r; + } + + + /** + * @brief + * + * @param array $sender + * @param array $arr + * @param array $deliveries + * @param boolean $relay + * @param boolean $public (optional) default false + * @param boolean $request (optional) default false + * @return array + */ + + static function process_delivery($sender, $arr, $deliveries, $relay, $public = false, $request = false) { + + $result = []; + + // We've validated the sender. Now make sure that the sender is the owner or author + + if(! $public) { + if($sender != $arr['owner_xchan'] && $sender != $arr['author_xchan']) { + logger("Sender $sender is not owner {$arr['owner_xchan']} or author {$arr['author_xchan']} - mid {$arr['mid']}"); + return; + } + } + + foreach($deliveries as $d) { + + $local_public = $public; + + $DR = new \Zotlabs\Lib\DReport(z_root(),$sender,$d,$arr['mid']); + + $channel = channelx_by_hash($d); + + if (! $channel) { + $DR->update('recipient not found'); + $result[] = $DR->get(); + continue; + } + + $DR->set_name($channel['channel_name'] . ' <' . channel_reddress($channel) . '>'); + + /** + * We need to block normal top-level message delivery from our clones, as the delivered + * message doesn't have ACL information in it as the cloned copy does. That copy + * will normally arrive first via sync delivery, but this isn't guaranteed. + * There's a chance the current delivery could take place before the cloned copy arrives + * hence the item could have the wrong ACL and *could* be used in subsequent deliveries or + * access checks. + */ + + if($sender === $channel['channel_hash'] && $arr['author_xchan'] === $channel['channel_hash'] && $arr['mid'] === $arr['parent_mid']) { + $DR->update('self delivery ignored'); + $result[] = $DR->get(); + continue; + } + + // allow public postings to the sys channel regardless of permissions, but not + // for comments travelling upstream. Wait and catch them on the way down. + // They may have been blocked by the owner. + + if(intval($channel['channel_system']) && (! $arr['item_private']) && (! $relay)) { + $local_public = true; + + $r = q("select xchan_selfcensored from xchan where xchan_hash = '%s' limit 1", + dbesc($sender['hash']) + ); + // don't import sys channel posts from selfcensored authors + if($r && (intval($r[0]['xchan_selfcensored']))) { + $local_public = false; + continue; + } + if(! MessageFilter::evaluate($arr,get_config('system','pubstream_incl'),get_config('system','pubstream_excl'))) { + $local_public = false; + continue; + } + } + + $tag_delivery = tgroup_check($channel['channel_id'],$arr); + + $perm = 'send_stream'; + if(($arr['mid'] !== $arr['parent_mid']) && ($relay)) + $perm = 'post_comments'; + + // This is our own post, possibly coming from a channel clone + + if($arr['owner_xchan'] == $d) { + $arr['item_wall'] = 1; + } + else { + $arr['item_wall'] = 0; + } + + if((! perm_is_allowed($channel['channel_id'],$sender,$perm)) && (! $tag_delivery) && (! $local_public)) { + logger("permission denied for delivery to channel {$channel['channel_id']} {$channel['channel_address']}"); + $DR->update('permission denied'); + $result[] = $DR->get(); + continue; + } + + if($arr['mid'] != $arr['parent_mid']) { + + // check source route. + // We are only going to accept comments from this sender if the comment has the same route as the top-level-post, + // this is so that permissions mismatches between senders apply to the entire conversation + // As a side effect we will also do a preliminary check that we have the top-level-post, otherwise + // processing it is pointless. + + $r = q("select route, id from item where mid = '%s' and uid = %d limit 1", + dbesc($arr['parent_mid']), + intval($channel['channel_id']) + ); + if(! $r) { + $DR->update('comment parent not found'); + $result[] = $DR->get(); + + // We don't seem to have a copy of this conversation or at least the parent + // - so request a copy of the entire conversation to date. + // Don't do this if it's a relay post as we're the ones who are supposed to + // have the copy and we don't want the request to loop. + // Also don't do this if this comment came from a conversation request packet. + // It's possible that comments are allowed but posting isn't and that could + // cause a conversation fetch loop. We can detect these packets since they are + // delivered via a 'notify' packet type that has a message_id element in the + // initial zot packet (just like the corresponding 'request' packet type which + // makes the request). + // We'll also check the send_stream permission - because if it isn't allowed, + // the top level post is unlikely to be imported and + // this is just an exercise in futility. + + if((! $relay) && (! $request) && (! $local_public) + && perm_is_allowed($channel['channel_id'],$sender,'send_stream')) { + \Zotlabs\Daemon\Master::Summon(array('Notifier', 'request', $channel['channel_id'], $sender, $arr['parent_mid'])); + } + continue; + } + if($relay) { + // reset the route in case it travelled a great distance upstream + // use our parent's route so when we go back downstream we'll match + // with whatever route our parent has. + $arr['route'] = $r[0]['route']; + } + else { + + // going downstream check that we have the same upstream provider that + // sent it to us originally. Ignore it if it came from another source + // (with potentially different permissions). + // only compare the last hop since it could have arrived at the last location any number of ways. + // Always accept empty routes and firehose items (route contains 'undefined') . + + $existing_route = explode(',', $r[0]['route']); + $routes = count($existing_route); + if($routes) { + $last_hop = array_pop($existing_route); + $last_prior_route = implode(',',$existing_route); + } + else { + $last_hop = ''; + $last_prior_route = ''; + } + + if(in_array('undefined',$existing_route) || $last_hop == 'undefined' || $sender == 'undefined') + $last_hop = ''; + + $current_route = (($arr['route']) ? $arr['route'] . ',' : '') . $sender; + + if($last_hop && $last_hop != $sender) { + logger('comment route mismatch: parent route = ' . $r[0]['route'] . ' expected = ' . $current_route, LOGGER_DEBUG); + logger('comment route mismatch: parent msg = ' . $r[0]['id'],LOGGER_DEBUG); + $DR->update('comment route mismatch'); + $result[] = $DR->get(); + continue; + } + + // we'll add sender onto this when we deliver it. $last_prior_route now has the previously stored route + // *except* for the sender which would've been the last hop before it got to us. + + $arr['route'] = $last_prior_route; + } + } + + $ab = q("select * from abook where abook_channel = %d and abook_xchan = '%s'", + intval($channel['channel_id']), + dbesc($arr['owner_xchan']) + ); + $abook = (($ab) ? $ab[0] : null); + + if(intval($arr['item_deleted'])) { + + // remove_community_tag is a no-op if this isn't a community tag activity + self::remove_community_tag($sender,$arr,$channel['channel_id']); + + // set these just in case we need to store a fresh copy of the deleted post. + // This could happen if the delete got here before the original post did. + + $arr['aid'] = $channel['channel_account_id']; + $arr['uid'] = $channel['channel_id']; + + $item_id = delete_imported_item($sender,$arr,$channel['channel_id'],$relay); + $DR->update(($item_id) ? 'deleted' : 'delete_failed'); + $result[] = $DR->get(); + + if($relay && $item_id) { + logger('process_delivery: invoking relay'); + \Zotlabs\Daemon\Master::Summon(array('Notifier','relay',intval($item_id))); + $DR->update('relayed'); + $result[] = $DR->get(); + } + + continue; + } + + + $r = q("select * from item where mid = '%s' and uid = %d limit 1", + dbesc($arr['mid']), + intval($channel['channel_id']) + ); + if($r) { + // We already have this post. + $item_id = $r[0]['id']; + + if(intval($r[0]['item_deleted'])) { + // It was deleted locally. + $DR->update('update ignored'); + $result[] = $DR->get(); + + continue; + } + // Maybe it has been edited? + elseif($arr['edited'] > $r[0]['edited']) { + $arr['id'] = $r[0]['id']; + $arr['uid'] = $channel['channel_id']; + if(($arr['mid'] == $arr['parent_mid']) && (! post_is_importable($arr,$abook))) { + $DR->update('update ignored'); + $result[] = $DR->get(); + } + else { + $item_result = self::update_imported_item($sender,$arr,$r[0],$channel['channel_id'],$tag_delivery); + $DR->update('updated'); + $result[] = $DR->get(); + if(! $relay) + add_source_route($item_id,$sender); + } + } + else { + $DR->update('update ignored'); + $result[] = $DR->get(); + + // We need this line to ensure wall-to-wall comments are relayed (by falling through to the relay bit), + // and at the same time not relay any other relayable posts more than once, because to do so is very wasteful. + if(! intval($r[0]['item_origin'])) + continue; + } + } + else { + $arr['aid'] = $channel['channel_account_id']; + $arr['uid'] = $channel['channel_id']; + + // if it's a sourced post, call the post_local hooks as if it were + // posted locally so that crosspost connectors will be triggered. + + if(check_item_source($arr['uid'], $arr)) { + /** + * @hooks post_local + * Called when an item has been posted on this machine via mod/item.php (also via API). + * * \e array with an item + */ + call_hooks('post_local', $arr); + } + + $item_id = 0; + + if(($arr['mid'] == $arr['parent_mid']) && (! post_is_importable($arr,$abook))) { + $DR->update('post ignored'); + $result[] = $DR->get(); + } + else { + $item_result = item_store($arr); + if($item_result['success']) { + $item_id = $item_result['item_id']; + $parr = [ + 'item_id' => $item_id, + 'item' => $arr, + 'sender' => $sender, + 'channel' => $channel + ]; + /** + * @hooks activity_received + * Called when an activity (post, comment, like, etc.) has been received from a zot source. + * * \e int \b item_id + * * \e array \b item + * * \e array \b sender + * * \e array \b channel + */ + call_hooks('activity_received', $parr); + // don't add a source route if it's a relay or later recipients will get a route mismatch + if(! $relay) + add_source_route($item_id,$sender); + } + $DR->update(($item_id) ? 'posted' : 'storage failed: ' . $item_result['message']); + $result[] = $DR->get(); + } + } + + // preserve conversations with which you are involved from expiration + + $stored = (($item_result && $item_result['item']) ? $item_result['item'] : false); + if((is_array($stored)) && ($stored['id'] != $stored['parent']) + && ($stored['author_xchan'] === $channel['channel_hash'])) { + retain_item($stored['item']['parent']); + } + + if($relay && $item_id) { + logger('Invoking relay'); + \Zotlabs\Daemon\Master::Summon(array('Notifier','relay',intval($item_id))); + $DR->addto_update('relayed'); + $result[] = $DR->get(); + } + } + + if(! $deliveries) + $result[] = array('', 'no recipients', '', $arr['mid']); + + logger('Local results: ' . print_r($result, true), LOGGER_DEBUG); + + return $result; + } + + /** + * @brief Remove community tag. + * + * @param array $sender an associative array with + * * \e string \b hash a xchan_hash + * @param array $arr an associative array + * * \e int \b verb + * * \e int \b obj_type + * * \e int \b mid + * @param int $uid + */ + + static function remove_community_tag($sender, $arr, $uid) { + + if(! (activity_match($arr['verb'], ACTIVITY_TAG) && ($arr['obj_type'] == ACTIVITY_OBJ_TAGTERM))) + return; + + logger('remove_community_tag: invoked'); + + if(! get_pconfig($uid,'system','blocktags')) { + logger('Permission denied.'); + return; + } + + $r = q("select * from item where mid = '%s' and uid = %d limit 1", + dbesc($arr['mid']), + intval($uid) + ); + if(! $r) { + logger('No item'); + return; + } + + if(($sender != $r[0]['owner_xchan']) && ($sender != $r[0]['author_xchan'])) { + logger('Sender not authorised.'); + return; + } + + $i = $r[0]; + + if($i['target']) + $i['target'] = json_decode($i['target'],true); + if($i['object']) + $i['object'] = json_decode($i['object'],true); + + if(! ($i['target'] && $i['object'])) { + logger('No target/object'); + return; + } + + $message_id = $i['target']['id']; + + $r = q("select id from item where mid = '%s' and uid = %d limit 1", + dbesc($message_id), + intval($uid) + ); + if(! $r) { + logger('No parent message'); + return; + } + + q("delete from term where uid = %d and oid = %d and otype = %d and ttype in ( %d, %d ) and term = '%s' and url = '%s'", + intval($uid), + intval($r[0]['id']), + intval(TERM_OBJ_POST), + intval(TERM_HASHTAG), + intval(TERM_COMMUNITYTAG), + dbesc($i['object']['title']), + dbesc(get_rel_link($i['object']['link'],'alternate')) + ); + } + + /** + * @brief Updates an imported item. + * + * @see item_store_update() + * + * @param array $sender + * @param array $item + * @param array $orig + * @param int $uid + * @param boolean $tag_delivery + */ + + static function update_imported_item($sender, $item, $orig, $uid, $tag_delivery) { + + // If this is a comment being updated, remove any privacy information + // so that item_store_update will set it from the original. + + if($item['mid'] !== $item['parent_mid']) { + unset($item['allow_cid']); + unset($item['allow_gid']); + unset($item['deny_cid']); + unset($item['deny_gid']); + unset($item['item_private']); + } + + // we need the tag_delivery check for downstream flowing posts as the stored post + // may have a different owner than the one being transmitted. + + if(($sender != $orig['owner_xchan'] && $sender != $orig['author_xchan']) && (! $tag_delivery)) { + logger('sender is not owner or author'); + return; + } + + + $x = item_store_update($item); + + // If we're updating an event that we've saved locally, we store the item info first + // because event_addtocal will parse the body to get the 'new' event details + + if($orig['resource_type'] === 'event') { + $res = event_addtocal($orig['id'], $uid); + if(! $res) + logger('update event: failed'); + } + + if(! $x['item_id']) + logger('update_imported_item: failed: ' . $x['message']); + else + logger('update_imported_item'); + + return $x; + } + + /** + * @brief Deletes an imported item. + * + * @param array $sender + * * \e string \b hash a xchan_hash + * @param array $item + * @param int $uid + * @param boolean $relay + * @return boolean|int post_id + */ + + static function delete_imported_item($sender, $item, $uid, $relay) { + + logger('invoked', LOGGER_DEBUG); + + $ownership_valid = false; + $item_found = false; + $post_id = 0; + + $r = q("select id, author_xchan, owner_xchan, source_xchan, item_deleted from item where ( author_xchan = '%s' or owner_xchan = '%s' or source_xchan = '%s' ) + and mid = '%s' and uid = %d limit 1", + dbesc($sender['hash']), + dbesc($sender['hash']), + dbesc($sender['hash']), + dbesc($item['mid']), + intval($uid) + ); + + if($r) { + if($r[0]['author_xchan'] === $sender || $r[0]['owner_xchan'] === $sender || $r[0]['source_xchan'] === $sender) + $ownership_valid = true; + + $post_id = $r[0]['id']; + $item_found = true; + } + else { + + // perhaps the item is still in transit and the delete notification got here before the actual item did. Store it with the deleted flag set. + // item_store() won't try to deliver any notifications or start delivery chains if this flag is set. + // This means we won't end up with potentially even more delivery threads trying to push this delete notification. + // But this will ensure that if the (undeleted) original post comes in at a later date, we'll reject it because it will have an older timestamp. + + logger('delete received for non-existent item - storing item data.'); + + if($item['author_xchan'] === $sender || $item['owner_xchan'] === $sender || $item['source_xchan'] === $sender) { + $ownership_valid = true; + $item_result = item_store($item); + $post_id = $item_result['item_id']; + } + } + + if($ownership_valid === false) { + logger('delete_imported_item: failed: ownership issue'); + return false; + } + + if($item_found) { + if(intval($r[0]['item_deleted'])) { + logger('delete_imported_item: item was already deleted'); + if(! $relay) + return false; + + // This is a bit hackish, but may have to suffice until the notification/delivery loop is optimised + // a bit further. We're going to strip the ITEM_ORIGIN on this item if it's a comment, because + // it was already deleted, and we're already relaying, and this ensures that no other process or + // code path downstream can relay it again (causing a loop). Since it's already gone it's not coming + // back, and we aren't going to (or shouldn't at any rate) delete it again in the future - so losing + // this information from the metadata should have no other discernible impact. + + if (($r[0]['id'] != $r[0]['parent']) && intval($r[0]['item_origin'])) { + q("update item set item_origin = 0 where id = %d and uid = %d", + intval($r[0]['id']), + intval($r[0]['uid']) + ); + } + } + + + // Use phased deletion to set the deleted flag, call both tag_deliver and the notifier to notify downstream channels + // and then clean up after ourselves with a cron job after several days to do the delete_item_lowlevel() (DROPITEM_PHASE2). + + drop_item($post_id, false, DROPITEM_PHASE1); + tag_deliver($uid, $post_id); + } + + return $post_id; + } + + static function process_mail_delivery($sender, $arr, $deliveries) { + + $result = array(); + + if($sender != $arr['from_xchan']) { + logger('process_mail_delivery: sender is not mail author'); + return; + } + + foreach($deliveries as $d) { + + $DR = new \Zotlabs\Lib\DReport(z_root(),$sender,$d,$arr['mid']); + + $r = q("select * from channel where channel_hash = '%s' limit 1", + dbesc($d['hash']) + ); + + if(! $r) { + $DR->update('recipient not found'); + $result[] = $DR->get(); + continue; + } + + $channel = $r[0]; + $DR->set_name($channel['channel_name'] . ' <' . channel_reddress($channel) . '>'); + + + if(! perm_is_allowed($channel['channel_id'],$sender,'post_mail')) { + + /* + * Always allow somebody to reply if you initiated the conversation. It's anti-social + * and a bit rude to send a private message to somebody and block their ability to respond. + * If you are being harrassed and want to put an end to it, delete the conversation. + */ + + $return = false; + if($arr['parent_mid']) { + $return = q("select * from mail where mid = '%s' and channel_id = %d limit 1", + dbesc($arr['parent_mid']), + intval($channel['channel_id']) + ); + } + if(! $return) { + logger("permission denied for mail delivery {$channel['channel_id']}"); + $DR->update('permission denied'); + $result[] = $DR->get(); + continue; + } + } + + + $r = q("select id from mail where mid = '%s' and channel_id = %d limit 1", + dbesc($arr['mid']), + intval($channel['channel_id']) + ); + if($r) { + if(intval($arr['mail_recalled'])) { + $x = q("delete from mail where id = %d and channel_id = %d", + intval($r[0]['id']), + intval($channel['channel_id']) + ); + $DR->update('mail recalled'); + $result[] = $DR->get(); + logger('mail_recalled'); + } + else { + $DR->update('duplicate mail received'); + $result[] = $DR->get(); + logger('duplicate mail received'); + } + continue; + } + else { + $arr['account_id'] = $channel['channel_account_id']; + $arr['channel_id'] = $channel['channel_id']; + $item_id = mail_store($arr); + $DR->update('mail delivered'); + $result[] = $DR->get(); + } + } + + return $result; + } + + + /** + * @brief Processes delivery of profile. + * + * @see import_directory_profile() + * @param array $sender an associative array + * * \e string \b hash a xchan_hash + * @param array $arr + * @param array $deliveries (unused) + */ + + static function process_profile_delivery($sender, $arr, $deliveries) { + + logger('process_profile_delivery', LOGGER_DEBUG); + + $r = q("select xchan_addr from xchan where xchan_hash = '%s' limit 1", + dbesc($sender['hash']) + ); + if($r) { + Libzotdir::import_directory_profile($sender, $arr, $r[0]['xchan_addr'], UPDATE_FLAGS_UPDATED, 0); + } + } + + + /** + * @brief + * + * @param array $sender an associative array + * * \e string \b hash a xchan_hash + * @param array $arr + * @param array $deliveries (unused) deliveries is irrelevant + */ + static function process_location_delivery($sender, $arr, $deliveries) { + + // deliveries is irrelevant + logger('process_location_delivery', LOGGER_DEBUG); + + $r = q("select * from xchan where xchan_hash = '%s' limit 1", + dbesc($sender) + ); + if($r) { + $xchan = [ 'id' => $r[0]['xchan_guid'], 'id_sig' => $r[0]['xchan_guid_sig'], + 'hash' => $r[0]['xchan_hash'], 'public_key' => $r[0]['xchan_pubkey'] ]; + } + if(array_key_exists('locations',$arr) && $arr['locations']) { + $x = Libsync::sync_locations($xchan,$arr,true); + logger('results: ' . print_r($x,true), LOGGER_DEBUG); + if($x['changed']) { + $guid = random_string() . '@' . App::get_hostname(); + Libzotdir::update_modtime($sender,$r[0]['xchan_guid'],$arr['locations'][0]['address'],UPDATE_FLAGS_UPDATED); + } + } + } + + /** + * @brief Checks for a moved channel and sets the channel_moved flag. + * + * Currently the effect of this flag is to turn the channel into 'read-only' mode. + * New content will not be processed (there was still an issue with blocking the + * ability to post comments as of 10-Mar-2016). + * We do not physically remove the channel at this time. The hub admin may choose + * to do so, but is encouraged to allow a grace period of several days in case there + * are any issues migrating content. This packet will generally be received by the + * original site when the basic channel import has been processed. + * + * This will only be executed on the old location + * if a new location is reported and there is only one location record. + * The rest of the hubloc syncronisation will be handled within + * sync_locations + * + * @param string $sender_hash A channel hash + * @param array $locations + */ + + static function check_location_move($sender_hash, $locations) { + + if(! $locations) + return; + + if(count($locations) != 1) + return; + + $loc = $locations[0]; + + $r = q("select * from channel where channel_hash = '%s' limit 1", + dbesc($sender_hash) + ); + + if(! $r) + return; + + if($loc['url'] !== z_root()) { + $x = q("update channel set channel_moved = '%s' where channel_hash = '%s' limit 1", + dbesc($loc['url']), + dbesc($sender_hash) + ); + + // federation plugins may wish to notify connections + // of the move on singleton networks + + $arr = [ + 'channel' => $r[0], + 'locations' => $locations + ]; + /** + * @hooks location_move + * Called when a new location has been provided to a UNO channel (indicating a move rather than a clone). + * * \e array \b channel + * * \e array \b locations + */ + call_hooks('location_move', $arr); + } + } + + + + /** + * @brief Returns an array with all known distinct hubs for this channel. + * + * @see self::get_hublocs() + * @param array $channel an associative array which must contain + * * \e string \b channel_hash the hash of the channel + * @return array an array with associative arrays + */ + + static function encode_locations($channel) { + $ret = []; + + $x = self::get_hublocs($channel['channel_hash']); + + if($x && count($x)) { + foreach($x as $hub) { + + // if this is a local channel that has been deleted, the hubloc is no good - make sure it is marked deleted + // so that nobody tries to use it. + + if(intval($channel['channel_removed']) && $hub['hubloc_url'] === z_root()) + $hub['hubloc_deleted'] = 1; + + $ret[] = [ + 'host' => $hub['hubloc_host'], + 'address' => $hub['hubloc_addr'], + 'id_url' => $hub['hubloc_id_url'], + 'primary' => (intval($hub['hubloc_primary']) ? true : false), + 'url' => $hub['hubloc_url'], + 'url_sig' => $hub['hubloc_url_sig'], + 'site_id' => $hub['hubloc_site_id'], + 'callback' => $hub['hubloc_callback'], + 'sitekey' => $hub['hubloc_sitekey'], + 'deleted' => (intval($hub['hubloc_deleted']) ? true : false) + ]; + } + } + + return $ret; + } + + + /** + * @brief + * + * @param array $arr + * @param string $pubkey + * @return boolean true if updated or inserted + */ + + static function import_site($arr) { + + if( (! is_array($arr)) || (! $arr['url']) || (! $arr['site_sig'])) + return false; + + if(! self::verify($arr['url'], $arr['site_sig'], $arr['sitekey'])) { + logger('Bad url_sig'); + return false; + } + + $update = false; + $exists = false; + + $r = q("select * from site where site_url = '%s' limit 1", + dbesc($arr['url']) + ); + if($r) { + $exists = true; + $siterecord = $r[0]; + } + + $site_directory = 0; + if($arr['directory_mode'] == 'normal') + $site_directory = DIRECTORY_MODE_NORMAL; + if($arr['directory_mode'] == 'primary') + $site_directory = DIRECTORY_MODE_PRIMARY; + if($arr['directory_mode'] == 'secondary') + $site_directory = DIRECTORY_MODE_SECONDARY; + if($arr['directory_mode'] == 'standalone') + $site_directory = DIRECTORY_MODE_STANDALONE; + + $register_policy = 0; + if($arr['register_policy'] == 'closed') + $register_policy = REGISTER_CLOSED; + if($arr['register_policy'] == 'open') + $register_policy = REGISTER_OPEN; + if($arr['register_policy'] == 'approve') + $register_policy = REGISTER_APPROVE; + + $access_policy = 0; + if(array_key_exists('access_policy',$arr)) { + if($arr['access_policy'] === 'private') + $access_policy = ACCESS_PRIVATE; + if($arr['access_policy'] === 'paid') + $access_policy = ACCESS_PAID; + if($arr['access_policy'] === 'free') + $access_policy = ACCESS_FREE; + if($arr['access_policy'] === 'tiered') + $access_policy = ACCESS_TIERED; + } + + // don't let insecure sites register as public hubs + + if(strpos($arr['url'],'https://') === false) + $access_policy = ACCESS_PRIVATE; + + if($access_policy != ACCESS_PRIVATE) { + $x = z_fetch_url($arr['url'] . '/siteinfo.json'); + if(! $x['success']) + $access_policy = ACCESS_PRIVATE; + } + + $directory_url = htmlspecialchars($arr['directory_url'],ENT_COMPAT,'UTF-8',false); + $url = htmlspecialchars(strtolower($arr['url']),ENT_COMPAT,'UTF-8',false); + $sellpage = htmlspecialchars($arr['sellpage'],ENT_COMPAT,'UTF-8',false); + $site_location = htmlspecialchars($arr['location'],ENT_COMPAT,'UTF-8',false); + $site_realm = htmlspecialchars($arr['realm'],ENT_COMPAT,'UTF-8',false); + $site_project = htmlspecialchars($arr['project'],ENT_COMPAT,'UTF-8',false); + $site_crypto = ((array_key_exists('encryption',$arr) && is_array($arr['encryption'])) ? htmlspecialchars(implode(',',$arr['encryption']),ENT_COMPAT,'UTF-8',false) : ''); + $site_version = ((array_key_exists('version',$arr)) ? htmlspecialchars($arr['version'],ENT_COMPAT,'UTF-8',false) : ''); + + // You can have one and only one primary directory per realm. + // Downgrade any others claiming to be primary. As they have + // flubbed up this badly already, don't let them be directory servers at all. + + if(($site_directory === DIRECTORY_MODE_PRIMARY) + && ($site_realm === get_directory_realm()) + && ($arr['url'] != get_directory_primary())) { + $site_directory = DIRECTORY_MODE_NORMAL; + } + + $site_flags = $site_directory; + + if(array_key_exists('zot',$arr)) { + set_sconfig($arr['url'],'system','zot_version',$arr['zot']); + } + + if($exists) { + if(($siterecord['site_flags'] != $site_flags) + || ($siterecord['site_access'] != $access_policy) + || ($siterecord['site_directory'] != $directory_url) + || ($siterecord['site_sellpage'] != $sellpage) + || ($siterecord['site_location'] != $site_location) + || ($siterecord['site_register'] != $register_policy) + || ($siterecord['site_project'] != $site_project) + || ($siterecord['site_realm'] != $site_realm) + || ($siterecord['site_crypto'] != $site_crypto) + || ($siterecord['site_version'] != $site_version) ) { + + $update = true; + + // logger('import_site: input: ' . print_r($arr,true)); + // logger('import_site: stored: ' . print_r($siterecord,true)); + + $r = q("update site set site_dead = 0, site_location = '%s', site_flags = %d, site_access = %d, site_directory = '%s', site_register = %d, site_update = '%s', site_sellpage = '%s', site_realm = '%s', site_type = %d, site_project = '%s', site_version = '%s', site_crypto = '%s' + where site_url = '%s'", + dbesc($site_location), + intval($site_flags), + intval($access_policy), + dbesc($directory_url), + intval($register_policy), + dbesc(datetime_convert()), + dbesc($sellpage), + dbesc($site_realm), + intval(SITE_TYPE_ZOT), + dbesc($site_project), + dbesc($site_version), + dbesc($site_crypto), + dbesc($url) + ); + if(! $r) { + logger('Update failed. ' . print_r($arr,true)); + } + } + else { + // update the timestamp to indicate we communicated with this site + q("update site set site_dead = 0, site_update = '%s' where site_url = '%s'", + dbesc(datetime_convert()), + dbesc($url) + ); + } + } + else { + $update = true; + + $r = site_store_lowlevel( + [ + 'site_location' => $site_location, + 'site_url' => $url, + 'site_access' => intval($access_policy), + 'site_flags' => intval($site_flags), + 'site_update' => datetime_convert(), + 'site_directory' => $directory_url, + 'site_register' => intval($register_policy), + 'site_sellpage' => $sellpage, + 'site_realm' => $site_realm, + 'site_type' => intval(SITE_TYPE_ZOT), + 'site_project' => $site_project, + 'site_version' => $site_version, + 'site_crypto' => $site_crypto + ] + ); + + if(! $r) { + logger('Record create failed. ' . print_r($arr,true)); + } + } + + return $update; + } + + /** + * @brief Returns path to /rpost + * + * @todo We probably should make rpost discoverable. + * + * @param array $observer + * * \e string \b xchan_url + * @return string + */ + static function get_rpost_path($observer) { + if(! $observer) + return ''; + + $parsed = parse_url($observer['xchan_url']); + + return $parsed['scheme'] . '://' . $parsed['host'] . (($parsed['port']) ? ':' . $parsed['port'] : '') . '/rpost?f='; + } + + /** + * @brief + * + * @param array $x + * @return boolean|string return false or a hash + */ + + static function import_author_zot($x) { + + // Check that we have both a hubloc and xchan record - as occasionally storage calls will fail and + // we may only end up with one; which results in posts with no author name or photo and are a bit + // of a hassle to repair. If either or both are missing, do a full discovery probe. + + $hash = self::make_xchan_hash($x['id'],$x['key']); + + $desturl = $x['url']; + + $r1 = q("select hubloc_url, hubloc_updated, site_dead from hubloc left join site on + hubloc_url = site_url where hubloc_guid = '%s' and hubloc_guid_sig = '%s' and hubloc_primary = 1 limit 1", + dbesc($x['id']), + dbesc($x['id_sig']) + ); + + $r2 = q("select xchan_hash from xchan where xchan_guid = '%s' and xchan_guid_sig = '%s' limit 1", + dbesc($x['id']), + dbesc($x['id_sig']) + ); + + $site_dead = false; + + if($r1 && intval($r1[0]['site_dead'])) { + $site_dead = true; + } + + // We have valid and somewhat fresh information. Always true if it is our own site. + + if($r1 && $r2 && ( $r1[0]['hubloc_updated'] > datetime_convert('UTC','UTC','now - 1 week') || $r1[0]['hubloc_url'] === z_root() ) ) { + logger('in cache', LOGGER_DEBUG); + return $hash; + } + + logger('not in cache or cache stale - probing: ' . print_r($x,true), LOGGER_DEBUG,LOG_INFO); + + // The primary hub may be dead. Try to find another one associated with this identity that is + // still alive. If we find one, use that url for the discovery/refresh probe. Otherwise, the dead site + // is all we have and there is no point probing it. Just return the hash indicating we have a + // cached entry and the identity is valid. It's just unreachable until they bring back their + // server from the grave or create another clone elsewhere. + + if($site_dead) { + logger('dead site - ignoring', LOGGER_DEBUG,LOG_INFO); + + $r = q("select hubloc_id_url from hubloc left join site on hubloc_url = site_url + where hubloc_hash = '%s' and site_dead = 0", + dbesc($hash) + ); + if($r) { + logger('found another site that is not dead: ' . $r[0]['hubloc_url'], LOGGER_DEBUG,LOG_INFO); + $desturl = $r[0]['hubloc_url']; + } + else { + return $hash; + } + } + + $them = [ 'hubloc_id_url' => $desturl ]; + if(self::refresh($them)) + return $hash; + + return false; + } + + static function zotinfo($arr) { + + $ret = []; + + $zhash = ((x($arr,'guid_hash')) ? $arr['guid_hash'] : ''); + $zguid = ((x($arr,'guid')) ? $arr['guid'] : ''); + $zguid_sig = ((x($arr,'guid_sig')) ? $arr['guid_sig'] : ''); + $zaddr = ((x($arr,'address')) ? $arr['address'] : ''); + $ztarget = ((x($arr,'target_url')) ? $arr['target_url'] : ''); + $zsig = ((x($arr,'target_sig')) ? $arr['target_sig'] : ''); + $zkey = ((x($arr,'key')) ? $arr['key'] : ''); + $mindate = ((x($arr,'mindate')) ? $arr['mindate'] : ''); + $token = ((x($arr,'token')) ? $arr['token'] : ''); + $feed = ((x($arr,'feed')) ? intval($arr['feed']) : 0); + + if($ztarget) { + $t = q("select * from hubloc where hubloc_id_url = '%s' limit 1", + dbesc($ztarget) + ); + if($t) { + + $ztarget_hash = $t[0]['hubloc_hash']; + + } + else { + + // should probably perform discovery of the requestor (target) but if they actually had + // permissions we would know about them and we only want to know who they are to + // enumerate their specific permissions + + $ztarget_hash = EMPTY_STR; + } + } + + + $r = null; + + if(strlen($zhash)) { + $r = q("select channel.*, xchan.* from channel left join xchan on channel_hash = xchan_hash + where channel_hash = '%s' limit 1", + dbesc($zhash) + ); + } + elseif(strlen($zguid) && strlen($zguid_sig)) { + $r = q("select channel.*, xchan.* from channel left join xchan on channel_hash = xchan_hash + where channel_guid = '%s' and channel_guid_sig = '%s' limit 1", + dbesc($zguid), + dbesc($zguid_sig) + ); + } + elseif(strlen($zaddr)) { + if(strpos($zaddr,'[system]') === false) { /* normal address lookup */ + $r = q("select channel.*, xchan.* from channel left join xchan on channel_hash = xchan_hash + where ( channel_address = '%s' or xchan_addr = '%s' ) limit 1", + dbesc($zaddr), + dbesc($zaddr) + ); + } + + else { + + /** + * The special address '[system]' will return a system channel if one has been defined, + * Or the first valid channel we find if there are no system channels. + * + * This is used by magic-auth if we have no prior communications with this site - and + * returns an identity on this site which we can use to create a valid hub record so that + * we can exchange signed messages. The precise identity is irrelevant. It's the hub + * information that we really need at the other end - and this will return it. + * + */ + + $r = q("select channel.*, xchan.* from channel left join xchan on channel_hash = xchan_hash + where channel_system = 1 order by channel_id limit 1"); + if(! $r) { + $r = q("select channel.*, xchan.* from channel left join xchan on channel_hash = xchan_hash + where channel_removed = 0 order by channel_id limit 1"); + } + } + } + else { + $ret['message'] = 'Invalid request'; + return($ret); + } + + if(! $r) { + $ret['message'] = 'Item not found.'; + return($ret); + } + + $e = $r[0]; + + $id = $e['channel_id']; + + $sys_channel = (intval($e['channel_system']) ? true : false); + $special_channel = (($e['channel_pageflags'] & PAGE_PREMIUM) ? true : false); + $adult_channel = (($e['channel_pageflags'] & PAGE_ADULT) ? true : false); + $censored = (($e['channel_pageflags'] & PAGE_CENSORED) ? true : false); + $searchable = (($e['channel_pageflags'] & PAGE_HIDDEN) ? false : true); + $deleted = (intval($e['xchan_deleted']) ? true : false); + + if($deleted || $censored || $sys_channel) + $searchable = false; + + $public_forum = false; + + $role = get_pconfig($e['channel_id'],'system','permissions_role'); + if($role === 'forum' || $role === 'repository') { + $public_forum = true; + } + else { + // check if it has characteristics of a public forum based on custom permissions. + $m = \Zotlabs\Access\Permissions::FilledAutoperms($e['channel_id']); + if($m) { + foreach($m as $k => $v) { + if($k == 'tag_deliver' && intval($v) == 1) + $ch ++; + if($k == 'send_stream' && intval($v) == 0) + $ch ++; + } + if($ch == 2) + $public_forum = true; + } + } + + + // This is for birthdays and keywords, but must check access permissions + $p = q("select * from profile where uid = %d and is_default = 1", + intval($e['channel_id']) + ); + + $profile = array(); + + if($p) { + + if(! intval($p[0]['publish'])) + $searchable = false; + + $profile['description'] = $p[0]['pdesc']; + $profile['birthday'] = $p[0]['dob']; + if(($profile['birthday'] != '0000-00-00') && (($bd = z_birthday($p[0]['dob'],$e['channel_timezone'])) !== '')) + $profile['next_birthday'] = $bd; + + if($age = age($p[0]['dob'],$e['channel_timezone'],'')) + $profile['age'] = $age; + $profile['gender'] = $p[0]['gender']; + $profile['marital'] = $p[0]['marital']; + $profile['sexual'] = $p[0]['sexual']; + $profile['locale'] = $p[0]['locality']; + $profile['region'] = $p[0]['region']; + $profile['postcode'] = $p[0]['postal_code']; + $profile['country'] = $p[0]['country_name']; + $profile['about'] = $p[0]['about']; + $profile['homepage'] = $p[0]['homepage']; + $profile['hometown'] = $p[0]['hometown']; + + if($p[0]['keywords']) { + $tags = array(); + $k = explode(' ',$p[0]['keywords']); + if($k) { + foreach($k as $kk) { + if(trim($kk," \t\n\r\0\x0B,")) { + $tags[] = trim($kk," \t\n\r\0\x0B,"); + } + } + } + if($tags) + $profile['keywords'] = $tags; + } + } + + // Communication details + + $ret['id'] = $e['xchan_guid']; + $ret['id_sig'] = self::sign($e['xchan_guid'], $e['channel_prvkey']); + + $ret['primary_location'] = [ + 'address' => $e['xchan_addr'], + 'url' => $e['xchan_url'], + 'connections_url' => $e['xchan_connurl'], + 'follow_url' => $e['xchan_follow'], + ]; + + $ret['public_key'] = $e['xchan_pubkey']; + $ret['username'] = $e['channel_address']; + $ret['name'] = $e['xchan_name']; + $ret['name_updated'] = $e['xchan_name_date']; + $ret['photo'] = [ + 'url' => $e['xchan_photo_l'], + 'type' => $e['xchan_photo_mimetype'], + 'updated' => $e['xchan_photo_date'] + ]; + + $ret['channel_role'] = get_pconfig($e['channel_id'],'system','permissions_role','custom'); + + $ret['searchable'] = $searchable; + $ret['adult_content'] = $adult_channel; + $ret['public_forum'] = $public_forum; + + $ret['comments'] = map_scope(\Zotlabs\Access\PermissionLimits::Get($e['channel_id'],'post_comments')); + $ret['mail'] = map_scope(\Zotlabs\Access\PermissionLimits::Get($e['channel_id'],'post_mail')); + + if($deleted) + $ret['deleted'] = $deleted; + + if(intval($e['channel_removed'])) + $ret['deleted_locally'] = true; + + // premium or other channel desiring some contact with potential followers before connecting. + // This is a template - %s will be replaced with the follow_url we discover for the return channel. + + if($special_channel) { + $ret['connect_url'] = (($e['xchan_connpage']) ? $e['xchan_connpage'] : z_root() . '/connect/' . $e['channel_address']); + } + + // This is a template for our follow url, %s will be replaced with a webbie + if(! $ret['follow_url']) + $ret['follow_url'] = z_root() . '/follow?f=&url=%s'; + + $permissions = get_all_perms($e['channel_id'],$ztarget_hash,false); + + if($ztarget_hash) { + $permissions['connected'] = false; + $b = q("select * from abook where abook_xchan = '%s' and abook_channel = %d limit 1", + dbesc($ztarget_hash), + intval($e['channel_id']) + ); + if($b) + $permissions['connected'] = true; + } + + if($permissions['view_profile']) + $ret['profile'] = $profile; + + + $concise_perms = []; + if($permissions) { + foreach($permissions as $k => $v) { + if($v) { + $concise_perms[] = $k; + } + } + $permissions = implode(',',$concise_perms); + } + + $ret['permissions'] = $permissions; + $ret['permissions_for'] = $ztarget; + + + // array of (verified) hubs this channel uses + + $x = self::encode_locations($e); + if($x) + $ret['locations'] = $x; + + $ret['site'] = self::site_info(); + + call_hooks('zotinfo',$ret); + + return($ret); + + } + + + static function site_info() { + + $signing_key = get_config('system','prvkey'); + $sig_method = get_config('system','signature_algorithm','sha256'); + + $ret = []; + $ret['site'] = []; + $ret['site']['url'] = z_root(); + $ret['site']['site_sig'] = self::sign(z_root(), $signing_key); + $ret['site']['post'] = z_root() . '/zot'; + $ret['site']['openWebAuth'] = z_root() . '/owa'; + $ret['site']['authRedirect'] = z_root() . '/magic'; + $ret['site']['sitekey'] = get_config('system','pubkey'); + + $dirmode = get_config('system','directory_mode'); + if(($dirmode === false) || ($dirmode == DIRECTORY_MODE_NORMAL)) + $ret['site']['directory_mode'] = 'normal'; + + if($dirmode == DIRECTORY_MODE_PRIMARY) + $ret['site']['directory_mode'] = 'primary'; + elseif($dirmode == DIRECTORY_MODE_SECONDARY) + $ret['site']['directory_mode'] = 'secondary'; + elseif($dirmode == DIRECTORY_MODE_STANDALONE) + $ret['site']['directory_mode'] = 'standalone'; + if($dirmode != DIRECTORY_MODE_NORMAL) + $ret['site']['directory_url'] = z_root() . '/dirsearch'; + + + $ret['site']['encryption'] = crypto_methods(); + $ret['site']['zot'] = System::get_zot_revision(); + + // hide detailed site information if you're off the grid + + if($dirmode != DIRECTORY_MODE_STANDALONE) { + + $register_policy = intval(get_config('system','register_policy')); + + if($register_policy == REGISTER_CLOSED) + $ret['site']['register_policy'] = 'closed'; + if($register_policy == REGISTER_APPROVE) + $ret['site']['register_policy'] = 'approve'; + if($register_policy == REGISTER_OPEN) + $ret['site']['register_policy'] = 'open'; + + + $access_policy = intval(get_config('system','access_policy')); + + if($access_policy == ACCESS_PRIVATE) + $ret['site']['access_policy'] = 'private'; + if($access_policy == ACCESS_PAID) + $ret['site']['access_policy'] = 'paid'; + if($access_policy == ACCESS_FREE) + $ret['site']['access_policy'] = 'free'; + if($access_policy == ACCESS_TIERED) + $ret['site']['access_policy'] = 'tiered'; + + $ret['site']['accounts'] = account_total(); + + require_once('include/channel.php'); + $ret['site']['channels'] = channel_total(); + + $ret['site']['admin'] = get_config('system','admin_email'); + + $visible_plugins = array(); + if(is_array(\App::$plugins) && count(\App::$plugins)) { + $r = q("select * from addon where hidden = 0"); + if($r) + foreach($r as $rr) + $visible_plugins[] = $rr['aname']; + } + + $ret['site']['plugins'] = $visible_plugins; + $ret['site']['sitehash'] = get_config('system','location_hash'); + $ret['site']['sitename'] = get_config('system','sitename'); + $ret['site']['sellpage'] = get_config('system','sellpage'); + $ret['site']['location'] = get_config('system','site_location'); + $ret['site']['realm'] = get_directory_realm(); + $ret['site']['project'] = System::get_platform_name(); + $ret['site']['version'] = System::get_project_version(); + + } + + return $ret['site']; + + } + + /** + * @brief + * + * @param array $hub + * @param string $sitekey (optional, default empty) + * + * @return string hubloc_url + */ + + static function update_hub_connected($hub, $site_id = '') { + + if ($site_id) { + + /* + * This hub has now been proven to be valid. + * Any hub with the same URL and a different sitekey cannot be valid. + * Get rid of them (mark them deleted). There's a good chance they were re-installs. + */ + + q("update hubloc set hubloc_deleted = 1, hubloc_error = 1 where hubloc_hash = '%s' and hubloc_url = '%s' and hubloc_site_id != '%s' ", + dbesc($hub['hubloc_hash']), + dbesc($hub['hubloc_url']), + dbesc($site_id) + ); + + } + else { + $site_id = $hub['hubloc_site_id']; + } + + // $sender['sitekey'] is a new addition to the protocol to distinguish + // hublocs coming from re-installed sites. Older sites will not provide + // this field and we have to still mark them valid, since we can't tell + // if this hubloc has the same sitekey as the packet we received. + // Update our DB to show when we last communicated successfully with this hub + // This will allow us to prune dead hubs from using up resources + + $t = datetime_convert('UTC', 'UTC', 'now - 15 minutes'); + + $r = q("update hubloc set hubloc_connected = '%s' where hubloc_id = %d and hubloc_site_id = '%s' and hubloc_connected < '%s' ", + dbesc(datetime_convert()), + intval($hub['hubloc_id']), + dbesc($site_id), + dbesc($t) + ); + + // a dead hub came back to life - reset any tombstones we might have + + if (intval($hub['hubloc_error'])) { + q("update hubloc set hubloc_error = 0 where hubloc_id = %d and hubloc_site_id = '%s' ", + intval($hub['hubloc_id']), + dbesc($site_id) + ); + if (intval($hub['hubloc_orphancheck'])) { + q("update hubloc set hubloc_orphancheck = 0 where hubloc_id = %d and hubloc_site_id = '%s' ", + intval($hub['hubloc_id']), + dbesc($site_id) + ); + } + q("update xchan set xchan_orphan = 0 where xchan_orphan = 1 and xchan_hash = '%s'", + dbesc($hub['hubloc_hash']) + ); + } + + return $hub['hubloc_url']; + } + + + static function sign($data,$key,$alg = 'sha256') { + if(! $key) + return 'no key'; + $sig = ''; + openssl_sign($data,$sig,$key,$alg); + return $alg . '.' . base64url_encode($sig); + } + + static function verify($data,$sig,$key) { + + $verify = 0; + + $x = explode('.',$sig,2); + + if ($key && count($x) === 2) { + $alg = $x[0]; + $signature = base64url_decode($x[1]); + + $verify = @openssl_verify($data,$signature,$key,$alg); + + if ($verify === (-1)) { + while ($msg = openssl_error_string()) { + logger('openssl_verify: ' . $msg,LOGGER_NORMAL,LOG_ERR); + } + btlogger('openssl_verify: key: ' . $key, LOGGER_DEBUG, LOG_ERR); + } + } + return(($verify > 0) ? true : false); + } + + + + static function is_zot_request() { + + $x = getBestSupportedMimeType([ 'application/x-zot+json' ]); + return(($x) ? true : false); + } + +} diff --git a/Zotlabs/Lib/Libzotdir.php b/Zotlabs/Lib/Libzotdir.php new file mode 100644 index 000000000..91d089c86 --- /dev/null +++ b/Zotlabs/Lib/Libzotdir.php @@ -0,0 +1,654 @@ +<?php + +namespace Zotlabs\Lib; + +use Zotlabs\Lib\Libzot; + +require_once('include/permissions.php'); + + +class Libzotdir { + + /** + * @brief + * + * @param int $dirmode + * @return array + */ + + static function find_upstream_directory($dirmode) { + global $DIRECTORY_FALLBACK_SERVERS; + + $preferred = get_config('system','directory_server'); + + // Thwart attempts to use a private directory + + if(($preferred) && ($preferred != z_root())) { + $r = q("select * from site where site_url = '%s' limit 1", + dbesc($preferred) + ); + if(($r) && ($r[0]['site_flags'] & DIRECTORY_MODE_STANDALONE)) { + $preferred = ''; + } + } + + + if (! $preferred) { + + /* + * No directory has yet been set. For most sites, pick one at random + * from our list of directory servers. However, if we're a directory + * server ourself, point at the local instance + * We will then set this value so this should only ever happen once. + * Ideally there will be an admin setting to change to a different + * directory server if you don't like our choice or if circumstances change. + */ + + $dirmode = intval(get_config('system','directory_mode')); + if ($dirmode == DIRECTORY_MODE_NORMAL) { + $toss = mt_rand(0,count($DIRECTORY_FALLBACK_SERVERS)); + $preferred = $DIRECTORY_FALLBACK_SERVERS[$toss]; + if(! $preferred) { + $preferred = DIRECTORY_FALLBACK_MASTER; + } + set_config('system','directory_server',$preferred); + } + else { + set_config('system','directory_server',z_root()); + } + } + if($preferred) { + return [ 'url' => $preferred ]; + } + else { + return []; + } + } + + + /** + * Directories may come and go over time. We will need to check that our + * directory server is still valid occasionally, and reset to something that + * is if our directory has gone offline for any reason + */ + + static function check_upstream_directory() { + + $directory = get_config('system', 'directory_server'); + + // it's possible there is no directory server configured and the local hub is being used. + // If so, default to preserving the absence of a specific server setting. + + $isadir = true; + + if ($directory) { + $j = Zotfinger::exec($directory); + if(array_path_exists('data/directory_mode',$j)) { + if ($j['data']['directory_mode'] === 'normal') { + $isadir = false; + } + } + } + + if (! $isadir) + set_config('system', 'directory_server', ''); + } + + + static function get_directory_setting($observer, $setting) { + + if ($observer) + $ret = get_xconfig($observer, 'directory', $setting); + else + $ret = ((array_key_exists($setting,$_SESSION)) ? intval($_SESSION[$setting]) : false); + + if($ret === false) + $ret = get_config('directory', $setting); + + + // 'safemode' is the default if there is no observer or no established preference. + + if($setting === 'safemode' && $ret === false) + $ret = 1; + + if($setting === 'globaldir' && intval(get_config('system','localdir_hide'))) + $ret = 1; + + return $ret; + } + + /** + * @brief Called by the directory_sort widget. + */ + static function dir_sort_links() { + + $safe_mode = 1; + + $observer = get_observer_hash(); + + $safe_mode = self::get_directory_setting($observer, 'safemode'); + $globaldir = self::get_directory_setting($observer, 'globaldir'); + $pubforums = self::get_directory_setting($observer, 'pubforums'); + + $hide_local = intval(get_config('system','localdir_hide')); + if($hide_local) + $globaldir = 1; + + + // Build urls without order and pubforums so it's easy to tack on the changed value + // Probably there's an easier way to do this + + $directory_sort_order = get_config('system','directory_sort_order'); + if(! $directory_sort_order) + $directory_sort_order = 'date'; + + $current_order = (($_REQUEST['order']) ? $_REQUEST['order'] : $directory_sort_order); + $suggest = (($_REQUEST['suggest']) ? '&suggest=' . $_REQUEST['suggest'] : ''); + + $url = 'directory?f='; + + $tmp = array_merge($_GET,$_POST); + unset($tmp['suggest']); + unset($tmp['pubforums']); + unset($tmp['global']); + unset($tmp['safe']); + unset($tmp['q']); + unset($tmp['f']); + $forumsurl = $url . http_build_query($tmp) . $suggest; + + $o = replace_macros(get_markup_template('dir_sort_links.tpl'), [ + '$header' => t('Directory Options'), + '$forumsurl' => $forumsurl, + '$safemode' => array('safemode', t('Safe Mode'),$safe_mode,'',array(t('No'), t('Yes')),' onchange=\'window.location.href="' . $forumsurl . '&safe="+(this.checked ? 1 : 0)\''), + '$pubforums' => array('pubforums', t('Public Forums Only'),$pubforums,'',array(t('No'), t('Yes')),' onchange=\'window.location.href="' . $forumsurl . '&pubforums="+(this.checked ? 1 : 0)\''), + '$hide_local' => $hide_local, + '$globaldir' => array('globaldir', t('This Website Only'), 1-intval($globaldir),'',array(t('No'), t('Yes')),' onchange=\'window.location.href="' . $forumsurl . '&global="+(this.checked ? 0 : 1)\''), + ]); + + return $o; + } + + /** + * @brief Checks the directory mode of this hub. + * + * Checks the directory mode of this hub to see if it is some form of directory server. If it is, + * get the directory realm of this hub. Fetch a list of all other directory servers in this realm and request + * a directory sync packet. This will contain both directory updates and new ratings. Store these all in the DB. + * In the case of updates, we will query each of them asynchronously from a poller task. Ratings are stored + * directly if the rater's signature matches. + * + * @param int $dirmode; + */ + + static function sync_directories($dirmode) { + + if ($dirmode == DIRECTORY_MODE_STANDALONE || $dirmode == DIRECTORY_MODE_NORMAL) + return; + + $realm = get_directory_realm(); + if ($realm == DIRECTORY_REALM) { + $r = q("select * from site where (site_flags & %d) > 0 and site_url != '%s' and site_type = %d and ( site_realm = '%s' or site_realm = '') ", + intval(DIRECTORY_MODE_PRIMARY|DIRECTORY_MODE_SECONDARY), + dbesc(z_root()), + intval(SITE_TYPE_ZOT), + dbesc($realm) + ); + } + else { + $r = q("select * from site where (site_flags & %d) > 0 and site_url != '%s' and site_realm like '%s' and site_type = %d ", + intval(DIRECTORY_MODE_PRIMARY|DIRECTORY_MODE_SECONDARY), + dbesc(z_root()), + dbesc(protect_sprintf('%' . $realm . '%')), + intval(SITE_TYPE_ZOT) + ); + } + + // If there are no directory servers, setup the fallback master + /** @FIXME What to do if we're in a different realm? */ + + if ((! $r) && (z_root() != DIRECTORY_FALLBACK_MASTER)) { + + $x = site_store_lowlevel( + [ + 'site_url' => DIRECTORY_FALLBACK_MASTER, + 'site_flags' => DIRECTORY_MODE_PRIMARY, + 'site_update' => NULL_DATE, + 'site_directory' => DIRECTORY_FALLBACK_MASTER . '/dirsearch', + 'site_realm' => DIRECTORY_REALM, + 'site_valid' => 1, + ] + ); + + $r = q("select * from site where site_flags in (%d, %d) and site_url != '%s' and site_type = %d ", + intval(DIRECTORY_MODE_PRIMARY), + intval(DIRECTORY_MODE_SECONDARY), + dbesc(z_root()), + intval(SITE_TYPE_ZOT) + ); + } + if (! $r) + return; + + foreach ($r as $rr) { + if (! $rr['site_directory']) + continue; + + logger('sync directories: ' . $rr['site_directory']); + + // for brand new directory servers, only load the last couple of days. + // It will take about a month for a new directory to obtain the full current repertoire of channels. + /** @FIXME Go back and pick up earlier ratings if this is a new directory server. These do not get refreshed. */ + + $token = get_config('system','realm_token'); + + $syncdate = (($rr['site_sync'] <= NULL_DATE) ? datetime_convert('UTC','UTC','now - 2 days') : $rr['site_sync']); + $x = z_fetch_url($rr['site_directory'] . '?f=&sync=' . urlencode($syncdate) . (($token) ? '&t=' . $token : '')); + + if (! $x['success']) + continue; + + $j = json_decode($x['body'],true); + if (!($j['transactions']) || ($j['ratings'])) + continue; + + q("update site set site_sync = '%s' where site_url = '%s'", + dbesc(datetime_convert()), + dbesc($rr['site_url']) + ); + + logger('sync_directories: ' . $rr['site_url'] . ': ' . print_r($j,true), LOGGER_DATA); + + if (is_array($j['transactions']) && count($j['transactions'])) { + foreach ($j['transactions'] as $t) { + $r = q("select * from updates where ud_guid = '%s' limit 1", + dbesc($t['transaction_id']) + ); + if($r) + continue; + + $ud_flags = 0; + if (is_array($t['flags']) && in_array('deleted',$t['flags'])) + $ud_flags |= UPDATE_FLAGS_DELETED; + if (is_array($t['flags']) && in_array('forced',$t['flags'])) + $ud_flags |= UPDATE_FLAGS_FORCED; + + $z = q("insert into updates ( ud_hash, ud_guid, ud_date, ud_flags, ud_addr ) + values ( '%s', '%s', '%s', %d, '%s' ) ", + dbesc($t['hash']), + dbesc($t['transaction_id']), + dbesc($t['timestamp']), + intval($ud_flags), + dbesc($t['address']) + ); + } + } + } + } + + + + /** + * @brief + * + * Given an update record, probe the channel, grab a zot-info packet and refresh/sync the data. + * + * Ignore updating records marked as deleted. + * + * If successful, sets ud_last in the DB to the current datetime for this + * reddress/webbie. + * + * @param array $ud Entry from update table + */ + + static function update_directory_entry($ud) { + + logger('update_directory_entry: ' . print_r($ud,true), LOGGER_DATA); + + if ($ud['ud_addr'] && (! ($ud['ud_flags'] & UPDATE_FLAGS_DELETED))) { + $success = false; + + $href = \Zotlabs\Lib\Webfinger::zot_url(punify($url)); + if($href) { + $zf = \Zotlabs\Lib\Zotfinger::exec($href); + } + if(is_array($zf) && array_path_exists('signature/signer',$zf) && $zf['signature']['signer'] === $href && intval($zf['signature']['header_valid'])) { + $xc = Libzot::import_xchan($zf['data'], 0, $ud); + } + else { + q("update updates set ud_last = '%s' where ud_addr = '%s'", + dbesc(datetime_convert()), + dbesc($ud['ud_addr']) + ); + } + } + } + + + /** + * @brief Push local channel updates to a local directory server. + * + * This is called from include/directory.php if a profile is to be pushed to the + * directory and the local hub in this case is any kind of directory server. + * + * @param int $uid + * @param boolean $force + */ + + static function local_dir_update($uid, $force) { + + + logger('local_dir_update: uid: ' . $uid, LOGGER_DEBUG); + + $p = q("select channel.channel_hash, channel_address, channel_timezone, profile.* from profile left join channel on channel_id = uid where uid = %d and is_default = 1", + intval($uid) + ); + + $profile = array(); + $profile['encoding'] = 'zot'; + + if ($p) { + $hash = $p[0]['channel_hash']; + + $profile['description'] = $p[0]['pdesc']; + $profile['birthday'] = $p[0]['dob']; + if ($age = age($p[0]['dob'],$p[0]['channel_timezone'],'')) + $profile['age'] = $age; + + $profile['gender'] = $p[0]['gender']; + $profile['marital'] = $p[0]['marital']; + $profile['sexual'] = $p[0]['sexual']; + $profile['locale'] = $p[0]['locality']; + $profile['region'] = $p[0]['region']; + $profile['postcode'] = $p[0]['postal_code']; + $profile['country'] = $p[0]['country_name']; + $profile['about'] = $p[0]['about']; + $profile['homepage'] = $p[0]['homepage']; + $profile['hometown'] = $p[0]['hometown']; + + if ($p[0]['keywords']) { + $tags = array(); + $k = explode(' ', $p[0]['keywords']); + if ($k) + foreach ($k as $kk) + if (trim($kk)) + $tags[] = trim($kk); + + if ($tags) + $profile['keywords'] = $tags; + } + + $hidden = (1 - intval($p[0]['publish'])); + + logger('hidden: ' . $hidden); + + $r = q("select xchan_hidden from xchan where xchan_hash = '%s' limit 1", + dbesc($p[0]['channel_hash']) + ); + + if(intval($r[0]['xchan_hidden']) != $hidden) { + $r = q("update xchan set xchan_hidden = %d where xchan_hash = '%s'", + intval($hidden), + dbesc($p[0]['channel_hash']) + ); + } + + $arr = [ 'channel_id' => $uid, 'hash' => $hash, 'profile' => $profile ]; + call_hooks('local_dir_update', $arr); + + $address = channel_reddress($p[0]); + + if (perm_is_allowed($uid, '', 'view_profile')) { + self::import_directory_profile($hash, $arr['profile'], $address, 0); + } + else { + // they may have made it private + $r = q("delete from xprof where xprof_hash = '%s'", + dbesc($hash) + ); + $r = q("delete from xtag where xtag_hash = '%s'", + dbesc($hash) + ); + } + + } + + $ud_hash = random_string() . '@' . \App::get_hostname(); + self::update_modtime($hash, $ud_hash, channel_reddress($p[0]),(($force) ? UPDATE_FLAGS_FORCED : UPDATE_FLAGS_UPDATED)); + } + + + + /** + * @brief Imports a directory profile. + * + * @param string $hash + * @param array $profile + * @param string $addr + * @param number $ud_flags (optional) UPDATE_FLAGS_UPDATED + * @param number $suppress_update (optional) default 0 + * @return boolean $updated if something changed + */ + + static function import_directory_profile($hash, $profile, $addr, $ud_flags = UPDATE_FLAGS_UPDATED, $suppress_update = 0) { + + logger('import_directory_profile', LOGGER_DEBUG); + if (! $hash) + return false; + + $arr = array(); + + $arr['xprof_hash'] = $hash; + $arr['xprof_dob'] = (($profile['birthday'] === '0000-00-00') ? $profile['birthday'] : datetime_convert('','',$profile['birthday'],'Y-m-d')); // !!!! check this for 0000 year + $arr['xprof_age'] = (($profile['age']) ? intval($profile['age']) : 0); + $arr['xprof_desc'] = (($profile['description']) ? htmlspecialchars($profile['description'], ENT_COMPAT,'UTF-8',false) : ''); + $arr['xprof_gender'] = (($profile['gender']) ? htmlspecialchars($profile['gender'], ENT_COMPAT,'UTF-8',false) : ''); + $arr['xprof_marital'] = (($profile['marital']) ? htmlspecialchars($profile['marital'], ENT_COMPAT,'UTF-8',false) : ''); + $arr['xprof_sexual'] = (($profile['sexual']) ? htmlspecialchars($profile['sexual'], ENT_COMPAT,'UTF-8',false) : ''); + $arr['xprof_locale'] = (($profile['locale']) ? htmlspecialchars($profile['locale'], ENT_COMPAT,'UTF-8',false) : ''); + $arr['xprof_region'] = (($profile['region']) ? htmlspecialchars($profile['region'], ENT_COMPAT,'UTF-8',false) : ''); + $arr['xprof_postcode'] = (($profile['postcode']) ? htmlspecialchars($profile['postcode'], ENT_COMPAT,'UTF-8',false) : ''); + $arr['xprof_country'] = (($profile['country']) ? htmlspecialchars($profile['country'], ENT_COMPAT,'UTF-8',false) : ''); + $arr['xprof_about'] = (($profile['about']) ? htmlspecialchars($profile['about'], ENT_COMPAT,'UTF-8',false) : ''); + $arr['xprof_homepage'] = (($profile['homepage']) ? htmlspecialchars($profile['homepage'], ENT_COMPAT,'UTF-8',false) : ''); + $arr['xprof_hometown'] = (($profile['hometown']) ? htmlspecialchars($profile['hometown'], ENT_COMPAT,'UTF-8',false) : ''); + + $clean = array(); + if (array_key_exists('keywords', $profile) and is_array($profile['keywords'])) { + self::import_directory_keywords($hash,$profile['keywords']); + foreach ($profile['keywords'] as $kw) { + $kw = trim(htmlspecialchars($kw,ENT_COMPAT, 'UTF-8', false)); + $kw = trim($kw, ','); + $clean[] = $kw; + } + } + + $arr['xprof_keywords'] = implode(' ',$clean); + + // Self censored, make it so + // These are not translated, so the German "erwachsenen" keyword will not censor the directory profile. Only the English form - "adult". + + + if(in_arrayi('nsfw',$clean) || in_arrayi('adult',$clean)) { + q("update xchan set xchan_selfcensored = 1 where xchan_hash = '%s'", + dbesc($hash) + ); + } + + $r = q("select * from xprof where xprof_hash = '%s' limit 1", + dbesc($hash) + ); + + if ($arr['xprof_age'] > 150) + $arr['xprof_age'] = 150; + if ($arr['xprof_age'] < 0) + $arr['xprof_age'] = 0; + + if ($r) { + $update = false; + foreach ($r[0] as $k => $v) { + if ((array_key_exists($k,$arr)) && ($arr[$k] != $v)) { + logger('import_directory_profile: update ' . $k . ' => ' . $arr[$k]); + $update = true; + break; + } + } + if ($update) { + q("update xprof set + xprof_desc = '%s', + xprof_dob = '%s', + xprof_age = %d, + xprof_gender = '%s', + xprof_marital = '%s', + xprof_sexual = '%s', + xprof_locale = '%s', + xprof_region = '%s', + xprof_postcode = '%s', + xprof_country = '%s', + xprof_about = '%s', + xprof_homepage = '%s', + xprof_hometown = '%s', + xprof_keywords = '%s' + where xprof_hash = '%s'", + dbesc($arr['xprof_desc']), + dbesc($arr['xprof_dob']), + intval($arr['xprof_age']), + dbesc($arr['xprof_gender']), + dbesc($arr['xprof_marital']), + dbesc($arr['xprof_sexual']), + dbesc($arr['xprof_locale']), + dbesc($arr['xprof_region']), + dbesc($arr['xprof_postcode']), + dbesc($arr['xprof_country']), + dbesc($arr['xprof_about']), + dbesc($arr['xprof_homepage']), + dbesc($arr['xprof_hometown']), + dbesc($arr['xprof_keywords']), + dbesc($arr['xprof_hash']) + ); + } + } else { + $update = true; + logger('New profile'); + q("insert into xprof (xprof_hash, xprof_desc, xprof_dob, xprof_age, xprof_gender, xprof_marital, xprof_sexual, xprof_locale, xprof_region, xprof_postcode, xprof_country, xprof_about, xprof_homepage, xprof_hometown, xprof_keywords) values ('%s', '%s', '%s', %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s') ", + dbesc($arr['xprof_hash']), + dbesc($arr['xprof_desc']), + dbesc($arr['xprof_dob']), + intval($arr['xprof_age']), + dbesc($arr['xprof_gender']), + dbesc($arr['xprof_marital']), + dbesc($arr['xprof_sexual']), + dbesc($arr['xprof_locale']), + dbesc($arr['xprof_region']), + dbesc($arr['xprof_postcode']), + dbesc($arr['xprof_country']), + dbesc($arr['xprof_about']), + dbesc($arr['xprof_homepage']), + dbesc($arr['xprof_hometown']), + dbesc($arr['xprof_keywords']) + ); + } + + $d = [ + 'xprof' => $arr, + 'profile' => $profile, + 'update' => $update + ]; + /** + * @hooks import_directory_profile + * Called when processing delivery of a profile structure from an external source (usually for directory storage). + * * \e array \b xprof + * * \e array \b profile + * * \e boolean \b update + */ + call_hooks('import_directory_profile', $d); + + if (($d['update']) && (! $suppress_update)) + self::update_modtime($arr['xprof_hash'],random_string() . '@' . \App::get_hostname(), $addr, $ud_flags); + + return $d['update']; + } + + /** + * @brief + * + * @param string $hash An xtag_hash + * @param array $keywords + */ + + static function import_directory_keywords($hash, $keywords) { + + $existing = array(); + $r = q("select * from xtag where xtag_hash = '%s' and xtag_flags = 0", + dbesc($hash) + ); + + if($r) { + foreach($r as $rr) + $existing[] = $rr['xtag_term']; + } + + $clean = array(); + foreach($keywords as $kw) { + $kw = trim(htmlspecialchars($kw,ENT_COMPAT, 'UTF-8', false)); + $kw = trim($kw, ','); + $clean[] = $kw; + } + + foreach($existing as $x) { + if(! in_array($x, $clean)) + $r = q("delete from xtag where xtag_hash = '%s' and xtag_term = '%s' and xtag_flags = 0", + dbesc($hash), + dbesc($x) + ); + } + foreach($clean as $x) { + if(! in_array($x, $existing)) { + $r = q("insert into xtag ( xtag_hash, xtag_term, xtag_flags) values ( '%s' ,'%s', 0 )", + dbesc($hash), + dbesc($x) + ); + } + } + } + + + /** + * @brief + * + * @param string $hash + * @param string $guid + * @param string $addr + * @param int $flags (optional) default 0 + */ + + static function update_modtime($hash, $guid, $addr, $flags = 0) { + + $dirmode = intval(get_config('system', 'directory_mode')); + + if($dirmode == DIRECTORY_MODE_NORMAL) + return; + + if($flags) { + q("insert into updates (ud_hash, ud_guid, ud_date, ud_flags, ud_addr ) values ( '%s', '%s', '%s', %d, '%s' )", + dbesc($hash), + dbesc($guid), + dbesc(datetime_convert()), + intval($flags), + dbesc($addr) + ); + } + else { + q("update updates set ud_flags = ( ud_flags | %d ) where ud_addr = '%s' and not (ud_flags & %d)>0 ", + intval(UPDATE_FLAGS_UPDATED), + dbesc($addr), + intval(UPDATE_FLAGS_UPDATED) + ); + } + } + + + + + + +}
\ No newline at end of file diff --git a/Zotlabs/Lib/NativeWiki.php b/Zotlabs/Lib/NativeWiki.php index 6f916216e..cdabbc3e9 100644 --- a/Zotlabs/Lib/NativeWiki.php +++ b/Zotlabs/Lib/NativeWiki.php @@ -26,7 +26,8 @@ class NativeWiki { $w['rawName'] = get_iconfig($w, 'wiki', 'rawName'); $w['htmlName'] = escape_tags($w['rawName']); - $w['urlName'] = urlencode(urlencode($w['rawName'])); + //$w['urlName'] = urlencode(urlencode($w['rawName'])); + $w['urlName'] = self::name_encode($w['rawName']); $w['mimeType'] = get_iconfig($w, 'wiki', 'mimeType'); $w['typelock'] = get_iconfig($w, 'wiki', 'typelock'); $w['lockstate'] = (($w['allow_cid'] || $w['allow_gid'] || $w['deny_cid'] || $w['deny_gid']) ? 'lock' : 'unlock'); @@ -233,7 +234,8 @@ class NativeWiki { 'wiki' => $w, 'rawName' => $rawName, 'htmlName' => escape_tags($rawName), - 'urlName' => urlencode(urlencode($rawName)), + //'urlName' => urlencode(urlencode($rawName)), + 'urlName' => self::name_encode($rawName), 'mimeType' => $mimeType, 'typelock' => $typelock ); @@ -249,7 +251,8 @@ class NativeWiki { 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(urldecode($urlName)), + dbesc(self::name_decode($urlName)), intval($uid) ); @@ -286,4 +289,32 @@ class NativeWiki { return array('read' => true, 'write' => $write, 'success' => true); } } + + public static function name_encode ($string) { + + $string = html_entity_decode($string); + $encoding = mb_internal_encoding(); + mb_internal_encoding("UTF-8"); + $ret = mb_ereg_replace_callback ('[^A-Za-z0-9\-\_\.\~]',function ($char) { + $charhex = unpack('H*',$char[0]); + $ret = '('.$charhex[1].')'; + return $ret; + } + ,$string); + mb_internal_encoding($encoding); + return $ret; + } + + public static function name_decode ($string) { + + $encoding = mb_internal_encoding(); + mb_internal_encoding("UTF-8"); + $ret = mb_ereg_replace_callback ('(\(([0-9a-f]+)\))',function ($chars) { + return pack('H*',$chars[2]); + } + ,$string); + mb_internal_encoding($encoding); + return $ret; + } + } diff --git a/Zotlabs/Lib/NativeWikiPage.php b/Zotlabs/Lib/NativeWikiPage.php index 919c51276..dddd26af3 100644 --- a/Zotlabs/Lib/NativeWikiPage.php +++ b/Zotlabs/Lib/NativeWikiPage.php @@ -44,7 +44,8 @@ class NativeWikiPage { $pages[] = [ 'resource_id' => $resource_id, 'title' => escape_tags($title), - 'url' => str_replace('%2F','/',urlencode(str_replace('%2F','/',urlencode($title)))), + //'url' => str_replace('%2F','/',urlencode(str_replace('%2F','/',urlencode($title)))), + 'url' => Zlib\NativeWiki::name_encode($title), 'link_id' => 'id_' . substr($resource_id, 0, 10) . '_' . $page_item['id'] ]; } @@ -98,7 +99,8 @@ class NativeWikiPage { $page = [ 'rawName' => $name, 'htmlName' => escape_tags($name), - 'urlName' => urlencode($name), + //'urlName' => urlencode($name), + 'urlName' => Zlib\NativeWiki::name_encode($name) ]; @@ -154,7 +156,8 @@ class NativeWikiPage { $page = [ 'rawName' => $pageNewName, 'htmlName' => escape_tags($pageNewName), - 'urlName' => urlencode(escape_tags($pageNewName)) + //'urlName' => urlencode(escape_tags($pageNewName)) + 'urlName' => Zlib\NativeWiki::name_encode($pageNewName) ]; return [ 'success' => true, 'page' => $page ]; @@ -365,7 +368,6 @@ class NativeWikiPage { unset($item['id']); unset($item['author']); - $item['parent'] = 0; $item['body'] = $content; $item['author_xchan'] = $observer_hash; @@ -527,7 +529,8 @@ class NativeWikiPage { $pages = $pageURLs = array(); foreach ($match[1] as $m) { // TODO: Why do we need to double urlencode for this to work? - $pageURLs[] = urlencode(urlencode(escape_tags($m))); + //$pageURLs[] = urlencode(urlencode(escape_tags($m))); + $pageURLs[] = Zlib\NativeWiki::name_encode(escape_tags($m)); $pages[] = $m; } $idx = 0; @@ -556,7 +559,10 @@ class NativeWikiPage { '$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') )); } @@ -613,7 +619,7 @@ class NativeWikiPage { $s = str_replace('[observer.webname]', '', $s); $s = str_replace('[observer.photo]', '', $s); } - + return $s; } diff --git a/Zotlabs/Lib/Queue.php b/Zotlabs/Lib/Queue.php new file mode 100644 index 000000000..baa1da70d --- /dev/null +++ b/Zotlabs/Lib/Queue.php @@ -0,0 +1,278 @@ +<?php /** @file */ + +namespace Zotlabs\Lib; + +use Zotlabs\Lib\Libzot; + + +class Queue { + + static function update($id, $add_priority = 0) { + + logger('queue: requeue item ' . $id,LOGGER_DEBUG); + $x = q("select outq_created, outq_posturl from outq where outq_hash = '%s' limit 1", + dbesc($id) + ); + if(! $x) + return; + + + $y = q("select min(outq_created) as earliest from outq where outq_posturl = '%s'", + dbesc($x[0]['outq_posturl']) + ); + + // look for the oldest queue entry with this destination URL. If it's older than a couple of days, + // the destination is considered to be down and only scheduled once an hour, regardless of the + // age of the current queue item. + + $might_be_down = false; + + if($y) + $might_be_down = ((datetime_convert('UTC','UTC',$y[0]['earliest']) < datetime_convert('UTC','UTC','now - 2 days')) ? true : false); + + + // Set all other records for this destination way into the future. + // The queue delivers by destination. We'll keep one queue item for + // this destination (this one) with a shorter delivery. If we succeed + // once, we'll try to deliver everything for that destination. + // The delivery will be set to at most once per hour, and if the + // queue item is less than 12 hours old, we'll schedule for fifteen + // minutes. + + $r = q("UPDATE outq SET outq_scheduled = '%s' WHERE outq_posturl = '%s'", + dbesc(datetime_convert('UTC','UTC','now + 5 days')), + dbesc($x[0]['outq_posturl']) + ); + + $since = datetime_convert('UTC','UTC',$x[0]['outq_created']); + + if(($might_be_down) || ($since < datetime_convert('UTC','UTC','now - 12 hour'))) { + $next = datetime_convert('UTC','UTC','now + 1 hour'); + } + else { + $next = datetime_convert('UTC','UTC','now + ' . intval($add_priority) . ' minutes'); + } + + q("UPDATE outq SET outq_updated = '%s', + outq_priority = outq_priority + %d, + outq_scheduled = '%s' + WHERE outq_hash = '%s'", + + dbesc(datetime_convert()), + intval($add_priority), + dbesc($next), + dbesc($id) + ); + } + + + static function remove($id,$channel_id = 0) { + logger('queue: remove queue item ' . $id,LOGGER_DEBUG); + $sql_extra = (($channel_id) ? " and outq_channel = " . intval($channel_id) . " " : ''); + + q("DELETE FROM outq WHERE outq_hash = '%s' $sql_extra", + dbesc($id) + ); + } + + + static function remove_by_posturl($posturl) { + logger('queue: remove queue posturl ' . $posturl,LOGGER_DEBUG); + + q("DELETE FROM outq WHERE outq_posturl = '%s' ", + dbesc($posturl) + ); + } + + + + static function set_delivered($id,$channel = 0) { + logger('queue: set delivered ' . $id,LOGGER_DEBUG); + $sql_extra = (($channel_id) ? " and outq_channel = " . intval($channel_id) . " " : ''); + + // Set the next scheduled run date so far in the future that it will be expired + // long before it ever makes it back into the delivery chain. + + q("update outq set outq_delivered = 1, outq_updated = '%s', outq_scheduled = '%s' where outq_hash = '%s' $sql_extra ", + dbesc(datetime_convert()), + dbesc(datetime_convert('UTC','UTC','now + 5 days')), + dbesc($id) + ); + } + + + + static function insert($arr) { + + // do not queue anything with no destination + + if(! (array_key_exists('posturl',$arr) && trim($arr['posturl']))) { + return false; + } + + $x = q("insert into outq ( outq_hash, outq_account, outq_channel, outq_driver, outq_posturl, outq_async, outq_priority, + outq_created, outq_updated, outq_scheduled, outq_notify, outq_msg ) + values ( '%s', %d, %d, '%s', '%s', %d, %d, '%s', '%s', '%s', '%s', '%s' )", + dbesc($arr['hash']), + intval($arr['account_id']), + intval($arr['channel_id']), + dbesc(($arr['driver']) ? $arr['driver'] : 'zot'), + dbesc($arr['posturl']), + intval(1), + intval(($arr['priority']) ? $arr['priority'] : 0), + dbesc(datetime_convert()), + dbesc(datetime_convert()), + dbesc(datetime_convert()), + dbesc($arr['notify']), + dbesc(($arr['msg']) ? $arr['msg'] : '') + ); + return $x; + + } + + + + static function deliver($outq, $immediate = false) { + + $base = null; + $h = parse_url($outq['outq_posturl']); + if($h !== false) + $base = $h['scheme'] . '://' . $h['host'] . (($h['port']) ? ':' . $h['port'] : ''); + + if(($base) && ($base !== z_root()) && ($immediate)) { + $y = q("select site_update, site_dead from site where site_url = '%s' ", + dbesc($base) + ); + if($y) { + if(intval($y[0]['site_dead'])) { + self::remove_by_posturl($outq['outq_posturl']); + logger('dead site ignored ' . $base); + return; + } + if($y[0]['site_update'] < datetime_convert('UTC','UTC','now - 1 month')) { + self::update($outq['outq_hash'],10); + logger('immediate delivery deferred for site ' . $base); + return; + } + } + else { + + // zot sites should all have a site record, unless they've been dead for as long as + // your site has existed. Since we don't know for sure what these sites are, + // call them unknown + + site_store_lowlevel( + [ + 'site_url' => $base, + 'site_update' => datetime_convert(), + 'site_dead' => 0, + 'site_type' => intval(($outq['outq_driver'] === 'post') ? SITE_TYPE_NOTZOT : SITE_TYPE_UNKNOWN), + 'site_crypto' => '' + ] + ); + } + } + + $arr = array('outq' => $outq, 'base' => $base, 'handled' => false, 'immediate' => $immediate); + call_hooks('queue_deliver',$arr); + if($arr['handled']) + return; + + // "post" queue driver - used for diaspora and friendica-over-diaspora communications. + + if($outq['outq_driver'] === 'post') { + $result = z_post_url($outq['outq_posturl'],$outq['outq_msg']); + if($result['success'] && $result['return_code'] < 300) { + logger('deliver: queue post success to ' . $outq['outq_posturl'], LOGGER_DEBUG); + if($base) { + q("update site set site_update = '%s', site_dead = 0 where site_url = '%s' ", + dbesc(datetime_convert()), + dbesc($base) + ); + } + q("update dreport set dreport_result = '%s', dreport_time = '%s' where dreport_queue = '%s'", + dbesc('accepted for delivery'), + dbesc(datetime_convert()), + dbesc($outq['outq_hash']) + ); + self::remove($outq['outq_hash']); + + // server is responding - see if anything else is going to this destination and is piled up + // and try to send some more. We're relying on the fact that do_delivery() results in an + // immediate delivery otherwise we could get into a queue loop. + + if(! $immediate) { + $x = q("select outq_hash from outq where outq_posturl = '%s' and outq_delivered = 0", + dbesc($outq['outq_posturl']) + ); + + $piled_up = array(); + if($x) { + foreach($x as $xx) { + $piled_up[] = $xx['outq_hash']; + } + } + if($piled_up) { + // call do_delivery() with the force flag + do_delivery($piled_up, true); + } + } + } + else { + logger('deliver: queue post returned ' . $result['return_code'] + . ' from ' . $outq['outq_posturl'],LOGGER_DEBUG); + self::update($outq['outq_hash'],10); + } + return; + } + + // normal zot delivery + + logger('deliver: dest: ' . $outq['outq_posturl'], LOGGER_DEBUG); + + + if($outq['outq_posturl'] === z_root() . '/zot') { + // local delivery + $zot = new \Zotlabs\Zot6\Receiver(new \Zotlabs\Zot6\Zot6Handler(),$outq['outq_notify']); + $result = $zot->run(true); + logger('returned_json: ' . json_encode($result,JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES), LOGGER_DATA); + logger('deliver: local zot delivery succeeded to ' . $outq['outq_posturl']); + Libzot::process_response($outq['outq_posturl'],[ 'success' => true, 'body' => json_encode($result) ], $outq); + } + else { + logger('remote'); + $channel = null; + + if($outq['outq_channel']) { + $channel = channelx_by_n($outq['outq_channel']); + } + + $host_crypto = null; + + if($channel && $base) { + $h = q("select hubloc_sitekey, site_crypto from hubloc left join site on hubloc_url = site_url where site_url = '%s' order by hubloc_id desc limit 1", + dbesc($base) + ); + if($h) { + $host_crypto = $h[0]; + } + } + + $msg = $outq['outq_notify']; + + $result = Libzot::zot($outq['outq_posturl'],$msg,$channel,$host_crypto); + + if($result['success']) { + logger('deliver: remote zot delivery succeeded to ' . $outq['outq_posturl']); + Libzot::process_response($outq['outq_posturl'],$result, $outq); + } + else { + logger('deliver: remote zot delivery failed to ' . $outq['outq_posturl']); + logger('deliver: remote zot delivery fail data: ' . print_r($result,true), LOGGER_DATA); + self::update($outq['outq_hash'],10); + } + } + return; + } +} + diff --git a/Zotlabs/Lib/ThreadItem.php b/Zotlabs/Lib/ThreadItem.php index ed78ae00b..78714c2c4 100644 --- a/Zotlabs/Lib/ThreadItem.php +++ b/Zotlabs/Lib/ThreadItem.php @@ -2,6 +2,8 @@ namespace Zotlabs\Lib; +use Zotlabs\Lib\Apps; + require_once('include/text.php'); /** @@ -259,7 +261,7 @@ class ThreadItem { $forged = ((($item['sig']) && (! intval($item['item_verified']))) ? t('Message signature incorrect') : ''); $unverified = '' ; // (($this->is_wall_to_wall() && (! intval($item['item_verified']))) ? t('Message cannot be verified') : ''); - + $settings = ''; // FIXME - check this permission if($conv->get_profile_owner() == local_channel()) { @@ -267,12 +269,14 @@ class ThreadItem { 'tagit' => t("Add Tag"), 'classtagger' => "", ); + + $settings = t('Conversation Tools'); } $has_bookmarks = false; - if(is_array($item['term'])) { + if(Apps::system_app_installed(local_channel(), 'Bookmarks') && is_array($item['term'])) { foreach($item['term'] as $t) { - if((get_account_techlevel() > 0) && ($t['ttype'] == TERM_BOOKMARK)) + if(($t['ttype'] == TERM_BOOKMARK)) $has_bookmarks = true; } } @@ -325,6 +329,10 @@ class ThreadItem { $has_tags = (($body['tags'] || $body['categories'] || $body['mentions'] || $body['attachments'] || $body['folders']) ? true : false); + $dropdown_extras_arr = [ 'item' => $item , 'dropdown_extras' => '' ]; + call_hooks('dropdown_extras',$dropdown_extras_arr); + $dropdown_extras = $dropdown_extras_arr['dropdown_extras']; + $tmp_item = array( 'template' => $this->get_template(), 'mode' => $mode, @@ -404,6 +412,7 @@ class ThreadItem { 'addtocal' => (($has_event) ? t('Add to Calendar') : ''), 'drop' => $drop, 'multidrop' => ((feature_enabled($conv->get_profile_owner(),'multi_delete')) ? $multidrop : ''), + 'dropdown_extras' => $dropdown_extras, // end toolbar buttons 'unseen_comments' => $unseen_comments, @@ -431,7 +440,8 @@ class ThreadItem { 'preview_lbl' => t('This is an unsaved preview'), 'wait' => t('Please wait'), 'submid' => str_replace(['+','='], ['',''], base64_encode($item['mid'])), - 'thread_level' => $thread_level + 'thread_level' => $thread_level, + 'settings' => $settings ); $arr = array('item' => $item, 'output' => $tmp_item); diff --git a/Zotlabs/Lib/ThreadStream.php b/Zotlabs/Lib/ThreadStream.php index d0c964149..020e8729b 100644 --- a/Zotlabs/Lib/ThreadStream.php +++ b/Zotlabs/Lib/ThreadStream.php @@ -196,7 +196,6 @@ class ThreadStream { $item->set_commentable(false); } - require_once('include/channel.php'); $item->set_conversation($this); $this->threads[] = $item; diff --git a/Zotlabs/Lib/Webfinger.php b/Zotlabs/Lib/Webfinger.php new file mode 100644 index 000000000..c2364ac4d --- /dev/null +++ b/Zotlabs/Lib/Webfinger.php @@ -0,0 +1,109 @@ +<?php + +namespace Zotlabs\Lib; + +/** + * @brief Fetch and return a webfinger for a resource + * + * @param string $resource - The resource + * @return boolean|string false or associative array from result JSON + */ + +class Webfinger { + + static private $server = EMPTY_STR; + static private $resource = EMPTY_STR; + + static function exec($resource) { + + if(! $resource) { + return false; + } + + self::parse_resource($resource); + + if(! ( self::$server && self::$resource)) { + return false; + } + + if(! check_siteallowed(self::$server)) { + logger('blacklisted: ' . self::$server); + return false; + } + + btlogger('fetching resource: ' . self::$resource . ' from ' . self::$server, LOGGER_DEBUG, LOG_INFO); + + $url = 'https://' . self::$server . '/.well-known/webfinger?f=&resource=' . self::$resource ; + + $counter = 0; + $s = z_fetch_url($url, false, $counter, [ 'headers' => [ 'Accept: application/jrd+json, */*' ] ]); + + if($s['success']) { + $j = json_decode($s['body'], true); + return($j); + } + + return false; + } + + static function parse_resource($resource) { + + self::$resource = urlencode($resource); + + if(strpos($resource,'http') === 0) { + $m = parse_url($resource); + if($m) { + if($m['scheme'] !== 'https') { + return false; + } + self::$server = $m['host'] . (($m['port']) ? ':' . $m['port'] : ''); + } + else { + return false; + } + } + elseif(strpos($resource,'tag:') === 0) { + $arr = explode(':',$resource); // split the tag + $h = explode(',',$arr[1]); // split the host,date + self::$server = $h[0]; + } + else { + $x = explode('@',$resource); + $username = $x[0]; + if(count($x) > 1) { + self::$server = $x[1]; + } + else { + return false; + } + if(strpos($resource,'acct:') !== 0) { + self::$resource = urlencode('acct:' . $resource); + } + } + + } + + /** + * @brief fetch a webfinger resource and return a zot6 discovery url if present + * + */ + + static function zot_url($resource) { + + $arr = self::exec($resource); + + if(is_array($arr) && array_key_exists('links',$arr)) { + foreach($arr['links'] as $link) { + if(array_key_exists('rel',$link) && $link['rel'] === PROTOCOL_ZOT6) { + if(array_key_exists('href',$link) && $link['href'] !== EMPTY_STR) { + return $link['href']; + } + } + } + } + return false; + } + + + +}
\ No newline at end of file diff --git a/Zotlabs/Lib/Zotfinger.php b/Zotlabs/Lib/Zotfinger.php new file mode 100644 index 000000000..537e440d4 --- /dev/null +++ b/Zotlabs/Lib/Zotfinger.php @@ -0,0 +1,50 @@ +<?php + +namespace Zotlabs\Lib; + +use Zotlabs\Web\HTTPSig; + +class Zotfinger { + + static function exec($resource,$channel = null) { + + if(! $resource) { + return false; + } + + if($channel) { + $headers = [ + 'Accept' => 'application/x-zot+json', + 'X-Zot-Token' => random_string(), + ]; + $h = HTTPSig::create_sig($headers,$channel['channel_prvkey'],channel_url($channel),false); + } + else { + $h = [ 'Accept: application/x-zot+json' ]; + } + + $result = []; + + + $redirects = 0; + $x = z_fetch_url($resource,false,$redirects, [ 'headers' => $h ] ); + + if($x['success']) { + + $result['signature'] = HTTPSig::verify($x); + + $result['data'] = json_decode($x['body'],true); + + if($result['data'] && is_array($result['data']) && array_key_exists('encrypted',$result['data']) && $result['data']['encrypted']) { + $result['data'] = json_decode(crypto_unencapsulate($result['data'],get_config('system','prvkey')),true); + } + + return $result; + } + + return false; + } + + + +}
\ No newline at end of file diff --git a/Zotlabs/Module/Acl.php b/Zotlabs/Module/Acl.php index 0c2ad7522..ea131e08c 100644 --- a/Zotlabs/Module/Acl.php +++ b/Zotlabs/Module/Acl.php @@ -81,7 +81,7 @@ class Acl extends \Zotlabs\Web\Controller { if($search) { - $sql_extra = " AND groups.gname LIKE " . protect_sprintf( "'%" . dbesc($search) . "%'" ) . " "; + $sql_extra = " AND pgrp.gname LIKE " . protect_sprintf( "'%" . dbesc($search) . "%'" ) . " "; $sql_extra2 = "AND ( xchan_name LIKE " . protect_sprintf( "'%" . dbesc($search) . "%'" ) . " OR xchan_addr LIKE " . protect_sprintf( "'%" . dbesc(punify($search)) . ((strpos($search,'@') === false) ? "%@%'" : "%'")) . ") "; // This horrible mess is needed because position also returns 0 if nothing is found. @@ -128,13 +128,13 @@ class Acl extends \Zotlabs\Web\Controller { // Normal privacy groups - $r = q("SELECT groups.id, groups.hash, groups.gname - FROM groups, group_member - WHERE groups.deleted = 0 AND groups.uid = %d - AND group_member.gid = groups.id + $r = q("SELECT pgrp.id, pgrp.hash, pgrp.gname + FROM pgrp, pgrp_member + WHERE pgrp.deleted = 0 AND pgrp.uid = %d + AND pgrp_member.gid = pgrp.id $sql_extra - GROUP BY groups.id - ORDER BY groups.gname + GROUP BY pgrp.id + ORDER BY pgrp.gname LIMIT %d OFFSET %d", intval(local_channel()), intval($count), diff --git a/Zotlabs/Module/Admin.php b/Zotlabs/Module/Admin.php index 2df8dc25d..6edced9b5 100644 --- a/Zotlabs/Module/Admin.php +++ b/Zotlabs/Module/Admin.php @@ -109,7 +109,7 @@ class Admin extends \Zotlabs\Web\Controller { // available channels, primary and clones $channels = array(); - $r = q("SELECT COUNT(*) AS total, COUNT(CASE WHEN channel_primary = 1 THEN 1 ELSE NULL END) AS main, COUNT(CASE WHEN channel_primary = 0 THEN 1 ELSE NULL END) AS clones FROM channel WHERE channel_removed = 0"); + $r = q("SELECT COUNT(*) AS total, COUNT(CASE WHEN channel_primary = 1 THEN 1 ELSE NULL END) AS main, COUNT(CASE WHEN channel_primary = 0 THEN 1 ELSE NULL END) AS clones FROM channel WHERE channel_removed = 0 and channel_system = 0"); if ($r) { $channels['total'] = array('label' => t('Channels'), 'val' => $r[0]['total']); $channels['main'] = array('label' => t('Primary'), 'val' => $r[0]['main']); diff --git a/Zotlabs/Module/Admin/Account_edit.php b/Zotlabs/Module/Admin/Account_edit.php index 6dfadf183..0300fb10c 100644 --- a/Zotlabs/Module/Admin/Account_edit.php +++ b/Zotlabs/Module/Admin/Account_edit.php @@ -31,7 +31,7 @@ class Account_edit { } $service_class = trim($_REQUEST['service_class']); - $account_level = intval(trim($_REQUEST['account_level'])); + $account_level = 5; $account_language = trim($_REQUEST['account_language']); $r = q("update account set account_service_class = '%s', account_level = %d, account_language = '%s' @@ -68,7 +68,6 @@ class Account_edit { '$title' => t('Account Edit'), '$pass1' => [ 'pass1', t('New Password'), ' ','' ], '$pass2' => [ 'pass2', t('New Password again'), ' ','' ], - '$account_level' => [ 'account_level', t('Technical skill level'), $x[0]['account_level'], '', \Zotlabs\Lib\Techlevels::levels() ], '$account_language' => [ 'account_language' , t('Account language (for emails)'), $x[0]['account_language'], '', language_list() ], '$service_class' => [ 'service_class', t('Service class'), $x[0]['account_service_class'], '' ], '$submit' => t('Submit'), @@ -81,4 +80,4 @@ class Account_edit { } -}
\ No newline at end of file +} diff --git a/Zotlabs/Module/Admin/Site.php b/Zotlabs/Module/Admin/Site.php index 5912a7c97..09b038729 100644 --- a/Zotlabs/Module/Admin/Site.php +++ b/Zotlabs/Module/Admin/Site.php @@ -72,7 +72,6 @@ class Site { $maxloadavg = ((x($_POST,'maxloadavg')) ? intval(trim($_POST['maxloadavg'])) : 50); $feed_contacts = ((x($_POST,'feed_contacts')) ? intval($_POST['feed_contacts']) : 0); $verify_email = ((x($_POST,'verify_email')) ? 1 : 0); - $techlevel_lock = ((x($_POST,'techlock')) ? intval($_POST['techlock']) : 0); $imagick_path = ((x($_POST,'imagick_path')) ? trim($_POST['imagick_path']) : ''); $thumbnail_security = ((x($_POST,'thumbnail_security')) ? intval($_POST['thumbnail_security']) : 0); $force_queue = ((intval($_POST['force_queue']) > 0) ? intval($_POST['force_queue']) : 3000); @@ -81,10 +80,6 @@ class Site { $permissions_role = escape_tags(trim($_POST['permissions_role'])); - $techlevel = null; - if(array_key_exists('techlevel', $_POST)) - $techlevel = intval($_POST['techlevel']); - set_config('system', 'feed_contacts', $feed_contacts); set_config('system', 'delivery_interval', $delivery_interval); set_config('system', 'delivery_batch_count', $delivery_batch_count); @@ -110,12 +105,6 @@ class Site { set_config('system', 'pubstream_incl',$pub_incl); set_config('system', 'pubstream_excl',$pub_excl); - set_config('system', 'techlevel_lock', $techlevel_lock); - - - - if(! is_null($techlevel)) - set_config('system', 'techlevel', $techlevel); if($directory_server) set_config('system','directory_server',$directory_server); @@ -284,15 +273,6 @@ class Site { // now invert the logic for the setting. $discover_tab = (1 - $discover_tab); - $techlevels = [ - '0' => t('Beginner/Basic'), - '1' => t('Novice - not skilled but willing to learn'), - '2' => t('Intermediate - somewhat comfortable'), - '3' => t('Advanced - very comfortable'), - '4' => t('Expert - I can write computer code'), - '5' => t('Wizard - I probably know more than you do') - ]; - $perm_roles = \Zotlabs\Access\PermissionRoles::roles(); $default_role = get_config('system','default_permissions_role','social'); @@ -316,10 +296,6 @@ class Site { // name, label, value, help string, extra data... '$sitename' => array('sitename', t("Site name"), htmlspecialchars(get_config('system','sitename'), ENT_QUOTES, 'UTF-8'),''), - '$techlevel' => [ 'techlevel', t('Site default technical skill level'), get_config('system','techlevel'), t('Used to provide a member experience matched to technical comfort level'), $techlevels ], - - '$techlock' => [ 'techlock', t('Lock the technical skill level setting'), get_config('system','techlevel_lock'), t('Members can set their own technical comfort level by default') ], - '$banner' => array('banner', t("Banner/Logo"), $banner, t('Unfiltered HTML/CSS/JS is allowed')), '$admininfo' => array('admininfo', t("Administrator Information"), $admininfo, t("Contact information for site administrators. Displayed on siteinfo page. BBCode can be used here")), '$siteinfo' => array('siteinfo', t('Site Information'), get_config('system','siteinfo'), t("Publicly visible description of this site. Displayed on siteinfo page. BBCode can be used here")), @@ -335,7 +311,7 @@ class Site { '$access_policy' => array('access_policy', t("Which best describes the types of account offered by this hub?"), get_config('system','access_policy'), t("This is displayed on the public server site list."), $access_choices), '$register_text' => array('register_text', t("Register text"), htmlspecialchars(get_config('system','register_text'), ENT_QUOTES, 'UTF-8'), t("Will be displayed prominently on the registration page.")), '$role' => $role, - '$frontpage' => array('frontpage', t("Site homepage to show visitors (default: login box)"), get_config('system','frontpage'), t("example: 'public' to show public stream, 'page/sys/home' to show a system webpage called 'home' or 'include:home.html' to include a file.")), + '$frontpage' => array('frontpage', t("Site homepage to show visitors (default: login box)"), get_config('system','frontpage'), t("example: 'pubstream' to show public stream, 'page/sys/home' to show a system webpage called 'home' or 'include:home.html' to include a file.")), '$mirror_frontpage' => array('mirror_frontpage', t("Preserve site homepage URL"), get_config('system','mirror_frontpage'), t('Present the site homepage in a frame at the original location instead of redirecting')), '$abandon_days' => array('abandon_days', t('Accounts abandoned after x days'), get_config('system','account_abandon_days'), t('Will not waste system resources polling external sites for abandonded accounts. Enter 0 for no time limit.')), '$allowed_sites' => array('allowed_sites', t("Allowed friend domains"), get_config('system','allowed_sites'), t("Comma separated list of domains which are allowed to establish friendships with this site. Wildcards are accepted. Empty to allow any domains")), diff --git a/Zotlabs/Module/Appman.php b/Zotlabs/Module/Appman.php index 3ebafafa4..f50dcc2ab 100644 --- a/Zotlabs/Module/Appman.php +++ b/Zotlabs/Module/Appman.php @@ -113,10 +113,12 @@ class Appman extends \Zotlabs\Web\Controller { if($r) { $app = $r[0]; - $term = q("select * from term where otype = %d and oid = %d", + $term = q("select * from term where otype = %d and oid = %d and uid = %d", intval(TERM_OBJ_APP), - intval($r[0]['id']) + intval($r[0]['id']), + intval(local_channel()) ); + if($term) { $app['categories'] = ''; foreach($term as $t) { diff --git a/Zotlabs/Module/Apps.php b/Zotlabs/Module/Apps.php index 78c8d99ae..05b4495fc 100644 --- a/Zotlabs/Module/Apps.php +++ b/Zotlabs/Module/Apps.php @@ -47,11 +47,11 @@ class Apps extends \Zotlabs\Web\Controller { return replace_macros(get_markup_template('myapps.tpl'), array( '$sitename' => get_config('system','sitename'), '$cat' => $cat, - '$title' => t('Apps'), + '$title' => (($available) ? t('Available Apps') : t('Installed Apps')), '$apps' => $apps, '$authed' => ((local_channel()) ? true : false), - '$manage' => (($available) ? '' : t('Manage apps')), - '$create' => (($mode == 'edit') ? t('Create new app') : '') + '$manage' => (($available) ? '' : t('Manage Apps')), + '$create' => (($mode == 'edit') ? t('Create Custom App') : '') )); } diff --git a/Zotlabs/Module/Article_edit.php b/Zotlabs/Module/Article_edit.php index 89abccc40..d3cce343f 100644 --- a/Zotlabs/Module/Article_edit.php +++ b/Zotlabs/Module/Article_edit.php @@ -122,7 +122,7 @@ class Article_edit extends \Zotlabs\Web\Controller { 'bbcode' => (($mimetype == 'text/bbcode') ? true : false) ); - $editor = status_editor($a, $x); + $editor = status_editor($a, $x, false, 'Article_edit'); $o .= replace_macros(get_markup_template('edpost_head.tpl'), array( '$title' => t('Edit Article'), diff --git a/Zotlabs/Module/Articles.php b/Zotlabs/Module/Articles.php index 284868241..58c16be45 100644 --- a/Zotlabs/Module/Articles.php +++ b/Zotlabs/Module/Articles.php @@ -1,12 +1,17 @@ <?php namespace Zotlabs\Module; +use App; +use Zotlabs\Lib\Apps; +use Zotlabs\Web\Controller; +use Zotlabs\Lib\PermissionDescription; + require_once('include/channel.php'); require_once('include/conversation.php'); require_once('include/acl_selectors.php'); -class Articles extends \Zotlabs\Web\Controller { +class Articles extends Controller { function init() { @@ -25,22 +30,27 @@ class Articles extends \Zotlabs\Web\Controller { return login(); } - if(! \App::$profile) { + if(! App::$profile) { notice( t('Requested profile is not available.') . EOL ); - \App::$error = 404; + App::$error = 404; return; } - if(! feature_enabled(\App::$profile_uid,'articles')) { - return; + if(! Apps::system_app_installed(App::$profile_uid, 'Articles')) { + //Do not display any associated widgets at this point + App::$pdl = ''; + + $o = '<b>' . t('Articles App') . ' (' . t('Not Installed') . '):</b><br>'; + $o .= t('Create interactive articles'); + return $o; } - nav_set_selected(t('Articles')); + nav_set_selected('Articles'); head_add_link([ 'rel' => 'alternate', 'type' => 'application/json+oembed', - 'href' => z_root() . '/oep?f=&url=' . urlencode(z_root() . '/' . \App::$query_string), + 'href' => z_root() . '/oep?f=&url=' . urlencode(z_root() . '/' . App::$query_string), 'title' => 'oembed' ]); @@ -48,19 +58,21 @@ class Articles extends \Zotlabs\Web\Controller { $category = (($_REQUEST['cat']) ? escape_tags(trim($_REQUEST['cat'])) : ''); if($category) { - $sql_extra2 .= protect_sprintf(term_item_parent_query(\App::$profile['profile_uid'],'item', $category, TERM_CATEGORY)); + $sql_extra2 .= protect_sprintf(term_item_parent_query(App::$profile['profile_uid'],'item', $category, TERM_CATEGORY)); } + $datequery = ((x($_GET,'dend') && is_a_date_arg($_GET['dend'])) ? notags($_GET['dend']) : ''); + $datequery2 = ((x($_GET,'dbegin') && is_a_date_arg($_GET['dbegin'])) ? notags($_GET['dbegin']) : ''); $which = argv(1); $selected_card = ((argc() > 2) ? argv(2) : ''); - $_SESSION['return_url'] = \App::$query_string; + $_SESSION['return_url'] = App::$query_string; $uid = local_channel(); - $owner = \App::$profile_uid; - $observer = \App::get_observer(); + $owner = App::$profile_uid; + $observer = App::get_observer(); $ob_hash = (($observer) ? $observer['xchan_hash'] : ''); @@ -98,7 +110,7 @@ class Articles extends \Zotlabs\Web\Controller { 'lockstate' => (($channel['channel_allow_cid'] || $channel['channel_allow_gid'] || $channel['channel_deny_cid'] || $channel['channel_deny_gid']) ? 'lock' : 'unlock'), 'acl' => (($is_owner) ? populate_acl($channel_acl, false, - \Zotlabs\Lib\PermissionDescription::fromGlobalPermission('view_pages')) : ''), + PermissionDescription::fromGlobalPermission('view_pages')) : ''), 'permissions' => $channel_acl, 'showacl' => (($is_owner) ? true : false), 'visitor' => true, @@ -120,7 +132,7 @@ class Articles extends \Zotlabs\Web\Controller { $x['title'] = $_REQUEST['title']; if($_REQUEST['body']) $x['body'] = $_REQUEST['body']; - $editor = status_editor($a,$x); + $editor = status_editor($a,$x,false,'Articles'); } else { @@ -128,8 +140,8 @@ class Articles extends \Zotlabs\Web\Controller { } $itemspage = get_pconfig(local_channel(),'system','itemspage'); - \App::set_pager_itemspage(((intval($itemspage)) ? $itemspage : 20)); - $pager_sql = sprintf(" LIMIT %d OFFSET %d ", intval(\App::$pager['itemspage']), intval(\App::$pager['start'])); + App::set_pager_itemspage(((intval($itemspage)) ? $itemspage : 20)); + $pager_sql = sprintf(" LIMIT %d OFFSET %d ", intval(App::$pager['itemspage']), intval(App::$pager['start'])); $sql_extra = item_permissions_sql($owner); @@ -143,10 +155,21 @@ class Articles extends \Zotlabs\Web\Controller { $sql_item = "and item.id = " . intval($r[0]['iid']) . " "; } } - + if($datequery) { + $sql_extra2 .= protect_sprintf(sprintf(" AND item.created <= '%s' ", dbesc(datetime_convert(date_default_timezone_get(),'',$datequery)))); + $order = 'post'; + } + if($datequery2) { + $sql_extra2 .= protect_sprintf(sprintf(" AND item.created >= '%s' ", dbesc(datetime_convert(date_default_timezone_get(),'',$datequery2)))); + } + + if($datequery || $datequery2) { + $sql_extra2 .= " and item.item_thread_top != 0 "; + } + $r = q("select * from item where item.uid = %d and item_type = %d - $sql_extra $sql_item order by item.created desc $pager_sql", + $sql_extra $sql_extra2 $sql_item order by item.created desc $pager_sql", intval($owner), intval(ITEM_TYPE_ARTICLE) ); @@ -166,7 +189,7 @@ class Articles extends \Zotlabs\Web\Controller { WHERE item.uid = %d $item_normal AND item.parent IN ( %s ) $sql_extra $sql_extra2 ", - intval(\App::$profile['profile_uid']), + intval(App::$profile['profile_uid']), dbesc($parents_str) ); if($items) { diff --git a/Zotlabs/Module/Authorize.php b/Zotlabs/Module/Authorize.php index bfb76150f..c6709f602 100644 --- a/Zotlabs/Module/Authorize.php +++ b/Zotlabs/Module/Authorize.php @@ -7,27 +7,34 @@ use Zotlabs\Identity\OAuth2Storage; class Authorize extends \Zotlabs\Web\Controller { function get() { - if (!local_channel()) { + if (! local_channel()) { return login(); - } else { - // TODO: Fully implement the dynamic client registration protocol: - // OpenID Connect Dynamic Client Registration 1.0 Client Metadata - // http://openid.net/specs/openid-connect-registration-1_0.html - $app = array( - 'name' => (x($_REQUEST, 'client_name') ? urldecode($_REQUEST['client_name']) : t('Unknown App')), - 'icon' => (x($_REQUEST, 'logo_uri') ? urldecode($_REQUEST['logo_uri']) : z_root() . '/images/icons/plugin.png'), - 'url' => (x($_REQUEST, 'client_uri') ? urldecode($_REQUEST['client_uri']) : ''), - ); - $o .= replace_macros(get_markup_template('oauth_authorize.tpl'), array( - '$title' => t('Authorize'), - '$authorize' => sprintf( t('Do you authorize the app %s to access your channel data?'), '<a style="float: none;" href="' . $app['url'] . '">' . $app['name'] . '</a> '), - '$app' => $app, - '$yes' => t('Allow'), - '$no' => t('Deny'), - '$client_id' => (x($_REQUEST, 'client_id') ? $_REQUEST['client_id'] : ''), + } + else { + + $name = $_REQUEST['client_name']; + if(! $name) { + $name = (($_REQUEST['client_id']) ?: t('Unknown App')); + } + + $app = [ + 'name' => $name, + 'icon' => (x($_REQUEST, 'logo_uri') ? $_REQUEST['logo_uri'] : z_root() . '/images/icons/plugin.png'), + 'url' => (x($_REQUEST, 'client_uri') ? $_REQUEST['client_uri'] : ''), + ]; + + $link = (($app['url']) ? '<a style="float: none;" href="' . $app['url'] . '">' . $app['name'] . '</a> ' : $app['name']); + + $o .= replace_macros(get_markup_template('oauth_authorize.tpl'), [ + '$title' => t('Authorize'), + '$authorize' => sprintf( t('Do you authorize the app %s to access your channel data?'), $link ), + '$app' => $app, + '$yes' => t('Allow'), + '$no' => t('Deny'), + '$client_id' => (x($_REQUEST, 'client_id') ? $_REQUEST['client_id'] : ''), '$redirect_uri' => (x($_REQUEST, 'redirect_uri') ? $_REQUEST['redirect_uri'] : ''), - '$state' => (x($_REQUEST, 'state') ? $_REQUEST['state'] : ''), - )); + '$state' => (x($_REQUEST, 'state') ? $_REQUEST['state'] : ''), + ]); return $o; } } @@ -60,13 +67,16 @@ class Authorize extends \Zotlabs\Web\Controller { $request = \OAuth2\Request::createFromGlobals(); $response = new \OAuth2\Response(); + // Note, "sub" field must match type and content. $user_id is used to populate - make sure it's a string. + $channel = channelx_by_n(local_channel()); + $user_id = $channel['channel_id']; + // If the client is not registered, add to the database if (!$client = $storage->getClientDetails($client_id)) { - $client_secret = random_string(16); + // Until "Dynamic Client Registration" is pursued - allow new clients to assign their own secret in the REQUEST + $client_secret = (isset($_REQUEST['client_secret'])) ? $_REQUEST['client_secret'] : random_string(16); // Client apps are registered per channel - $user_id = local_channel(); - $storage->setClientDetails($client_id, $client_secret, $redirect_uri, 'authorization_code', null, $user_id); - + $storage->setClientDetails($client_id, $client_secret, $redirect_uri, 'authorization_code', $_REQUEST['scope'], $user_id); } if (!$client = $storage->getClientDetails($client_id)) { // There was an error registering the client. @@ -83,7 +93,7 @@ class Authorize extends \Zotlabs\Web\Controller { // print the authorization code if the user has authorized your client $is_authorized = ($_POST['authorize'] === 'allow'); - $s->handleAuthorizeRequest($request, $response, $is_authorized, local_channel()); + $s->handleAuthorizeRequest($request, $response, $is_authorized, $user_id); if ($is_authorized) { $code = substr($response->getHttpHeader('Location'), strpos($response->getHttpHeader('Location'), 'code=') + 5, 40); logger('Authorization Code: ' . $code); diff --git a/Zotlabs/Module/Blocks.php b/Zotlabs/Module/Blocks.php index e6a97794d..fde30a6dd 100644 --- a/Zotlabs/Module/Blocks.php +++ b/Zotlabs/Module/Blocks.php @@ -109,7 +109,7 @@ class Blocks extends \Zotlabs\Web\Controller { if($_REQUEST['pagetitle']) $x['pagetitle'] = $_REQUEST['pagetitle']; - $editor = status_editor($a,$x); + $editor = status_editor($a,$x,false,'Blocks'); $r = q("select iconfig.iid, iconfig.k, iconfig.v, mid, title, body, mimetype, created, edited from iconfig diff --git a/Zotlabs/Module/Bookmarks.php b/Zotlabs/Module/Bookmarks.php index e147ffe6c..4b4929c65 100644 --- a/Zotlabs/Module/Bookmarks.php +++ b/Zotlabs/Module/Bookmarks.php @@ -1,6 +1,9 @@ <?php namespace Zotlabs\Module; +use App; +use Zotlabs\Lib\Apps; + class Bookmarks extends \Zotlabs\Web\Controller { @@ -8,7 +11,10 @@ class Bookmarks extends \Zotlabs\Web\Controller { if(! local_channel()) return; - nav_set_selected('View Bookmarks'); + if(! Apps::system_app_installed(local_channel(), 'Bookmarks')) + return; + + nav_set_selected('Bookmarks'); $item_id = intval($_REQUEST['item']); $burl = trim($_REQUEST['burl']); @@ -59,19 +65,26 @@ class Bookmarks extends \Zotlabs\Web\Controller { killme(); } - function get() { + function get() { if(! local_channel()) { notice( t('Permission denied.') . EOL); return; } - + + if(! Apps::system_app_installed(local_channel(), 'Bookmarks')) { + //Do not display any associated widgets at this point + App::$pdl = ''; + + $o = '<b>' . t('Bookmarks App') . ' (' . t('Not Installed') . '):</b><br>'; + $o .= t('Bookmark links from posts and manage them'); + return $o; + } require_once('include/menu.php'); require_once('include/conversation.php'); $channel = \App::get_channel(); - //$o = profile_tabs($a,true,$channel['channel_address']); $o = ''; $o .= '<div class="generic-content-wrapper-styled">'; diff --git a/Zotlabs/Module/Cal.php b/Zotlabs/Module/Cal.php index c8403e979..70098a2a1 100644 --- a/Zotlabs/Module/Cal.php +++ b/Zotlabs/Module/Cal.php @@ -6,6 +6,7 @@ require_once('include/bbcode.php'); require_once('include/datetime.php'); require_once('include/event.php'); require_once('include/items.php'); +require_once('include/html2plain.php'); class Cal extends \Zotlabs\Web\Controller { @@ -74,7 +75,7 @@ class Cal extends \Zotlabs\Web\Controller { $sql_extra = permissions_sql($channel['channel_id'],get_observer_hash(),'event'); - $first_day = get_pconfig(local_channel(),'system','cal_first_day'); + $first_day = feature_enabled($channel['channel_id'], 'events_cal_first_day'); $first_day = (($first_day) ? $first_day : 0); $htpl = get_markup_template('event_head.tpl'); @@ -88,9 +89,6 @@ class Cal extends \Zotlabs\Web\Controller { $o = ''; - //$tabs = profile_tabs($a, True, $channel['channel_address']); - $tabs = ''; - $mode = 'view'; $y = 0; $m = 0; @@ -296,6 +294,7 @@ class Cal extends \Zotlabs\Web\Controller { } $html = format_event_html($rr); $rr['desc'] = zidify_links(smilies(bbcode($rr['desc']))); + $rr['description'] = htmlentities(html2plain(bbcode($rr['description'])),ENT_COMPAT,'UTF-8',false); $rr['location'] = zidify_links(smilies(bbcode($rr['location']))); $events[] = array( 'id'=>$rr['id'], @@ -347,8 +346,7 @@ class Cal extends \Zotlabs\Web\Controller { '$next' => t('Next'), '$today' => t('Today'), '$form' => $form, - '$expandform' => ((x($_GET,'expandform')) ? true : false), - '$tabs' => $tabs + '$expandform' => ((x($_GET,'expandform')) ? true : false) )); if (x($_GET,'id')){ echo $o; killme(); } diff --git a/Zotlabs/Module/Card_edit.php b/Zotlabs/Module/Card_edit.php index 694bdc4ea..e01e70fdb 100644 --- a/Zotlabs/Module/Card_edit.php +++ b/Zotlabs/Module/Card_edit.php @@ -122,7 +122,7 @@ class Card_edit extends \Zotlabs\Web\Controller { 'bbcode' => (($mimetype == 'text/bbcode') ? true : false) ); - $editor = status_editor($a, $x); + $editor = status_editor($a, $x, false, 'Card_edit'); $o .= replace_macros(get_markup_template('edpost_head.tpl'), array( '$title' => t('Edit Card'), diff --git a/Zotlabs/Module/Cards.php b/Zotlabs/Module/Cards.php index f196988a2..b66de158b 100644 --- a/Zotlabs/Module/Cards.php +++ b/Zotlabs/Module/Cards.php @@ -1,12 +1,16 @@ <?php namespace Zotlabs\Module; +use App; +use Zotlabs\Lib\Apps; +use Zotlabs\Web\Controller; +use Zotlabs\Lib\PermissionDescription; + require_once('include/channel.php'); require_once('include/conversation.php'); require_once('include/acl_selectors.php'); - -class Cards extends \Zotlabs\Web\Controller { +class Cards extends Controller { function init() { @@ -29,22 +33,27 @@ class Cards extends \Zotlabs\Web\Controller { return login(); } - if(! \App::$profile) { + if(! App::$profile) { notice( t('Requested profile is not available.') . EOL ); - \App::$error = 404; + App::$error = 404; return; } - if(! feature_enabled(\App::$profile_uid, 'cards')) { - return; + if(! Apps::system_app_installed(App::$profile_uid, 'Cards')) { + //Do not display any associated widgets at this point + App::$pdl = ''; + + $o = '<b>' . t('Cards App') . ' (' . t('Not Installed') . '):</b><br>'; + $o .= t('Create personal planning cards'); + return $o; } - nav_set_selected(t('Cards')); + nav_set_selected('Cards'); head_add_link([ 'rel' => 'alternate', 'type' => 'application/json+oembed', - 'href' => z_root() . '/oep?f=&url=' . urlencode(z_root() . '/' . \App::$query_string), + 'href' => z_root() . '/oep?f=&url=' . urlencode(z_root() . '/' . App::$query_string), 'title' => 'oembed' ]); @@ -52,7 +61,7 @@ class Cards extends \Zotlabs\Web\Controller { $category = (($_REQUEST['cat']) ? escape_tags(trim($_REQUEST['cat'])) : ''); if($category) { - $sql_extra2 .= protect_sprintf(term_item_parent_query(\App::$profile['profile_uid'], 'item', $category, TERM_CATEGORY)); + $sql_extra2 .= protect_sprintf(term_item_parent_query(App::$profile['profile_uid'], 'item', $category, TERM_CATEGORY)); } @@ -60,11 +69,11 @@ class Cards extends \Zotlabs\Web\Controller { $selected_card = ((argc() > 2) ? argv(2) : ''); - $_SESSION['return_url'] = \App::$query_string; + $_SESSION['return_url'] = App::$query_string; $uid = local_channel(); - $owner = \App::$profile_uid; - $observer = \App::get_observer(); + $owner = App::$profile_uid; + $observer = App::get_observer(); $ob_hash = (($observer) ? $observer['xchan_hash'] : ''); @@ -101,8 +110,8 @@ class Cards extends \Zotlabs\Web\Controller { 'nickname' => $channel['channel_address'], 'lockstate' => (($channel['channel_allow_cid'] || $channel['channel_allow_gid'] || $channel['channel_deny_cid'] || $channel['channel_deny_gid']) ? 'lock' : 'unlock'), - 'acl' => (($is_owner) ? populate_acl($channel_acl, false, - \Zotlabs\Lib\PermissionDescription::fromGlobalPermission('view_pages')) : ''), + 'acl' => (($is_owner) ? populate_acl($channel_acl, false, + PermissionDescription::fromGlobalPermission('view_pages')) : ''), 'permissions' => $channel_acl, 'showacl' => (($is_owner) ? true : false), 'visitor' => true, @@ -124,7 +133,7 @@ class Cards extends \Zotlabs\Web\Controller { if($_REQUEST['body']) $x['body'] = $_REQUEST['body']; - $editor = status_editor($a, $x); + $editor = status_editor($a, $x, false, 'Cards'); } else { $editor = ''; @@ -132,8 +141,8 @@ class Cards extends \Zotlabs\Web\Controller { $itemspage = get_pconfig(local_channel(),'system','itemspage'); - \App::set_pager_itemspage(((intval($itemspage)) ? $itemspage : 20)); - $pager_sql = sprintf(" LIMIT %d OFFSET %d ", intval(\App::$pager['itemspage']), intval(\App::$pager['start'])); + App::set_pager_itemspage(((intval($itemspage)) ? $itemspage : 20)); + $pager_sql = sprintf(" LIMIT %d OFFSET %d ", intval(App::$pager['itemspage']), intval(App::$pager['start'])); $sql_extra = item_permissions_sql($owner); @@ -171,7 +180,7 @@ class Cards extends \Zotlabs\Web\Controller { WHERE item.uid = %d $item_normal AND item.parent IN ( %s ) $sql_extra $sql_extra2 ", - intval(\App::$profile['profile_uid']), + intval(App::$profile['profile_uid']), dbesc($parents_str) ); if($items) { diff --git a/Zotlabs/Module/Cdav.php b/Zotlabs/Module/Cdav.php index 6737ac4ee..d644e48b1 100644 --- a/Zotlabs/Module/Cdav.php +++ b/Zotlabs/Module/Cdav.php @@ -1,12 +1,16 @@ <?php namespace Zotlabs\Module; +use App; +use Zotlabs\Lib\Apps; +use Zotlabs\Web\Controller; + require_once('include/event.php'); require_once('include/auth.php'); require_once('include/security.php'); -class Cdav extends \Zotlabs\Web\Controller { +class Cdav extends Controller { function init() { @@ -126,8 +130,18 @@ class Cdav extends \Zotlabs\Web\Controller { $auth->setRealm(ucfirst(\Zotlabs\Lib\System::get_platform_name()) . 'CalDAV/CardDAV'); if (local_channel()) { + logger('loggedin'); - $channel = \App::get_channel(); + + if((argv(1) == 'calendars') && (!Apps::system_app_installed(local_channel(), 'CalDAV'))) { + killme(); + } + + if((argv(1) == 'addressbooks') && (!Apps::system_app_installed(local_channel(), 'CardDAV'))) { + killme(); + } + + $channel = App::get_channel(); $auth->setCurrentUser($channel['channel_address']); $auth->channel_id = $channel['channel_id']; $auth->channel_hash = $channel['channel_hash']; @@ -161,12 +175,15 @@ class Cdav extends \Zotlabs\Web\Controller { $nodes = [ // /principals new \Sabre\CalDAV\Principal\Collection($principalBackend), + // /calendars new \Sabre\CalDAV\CalendarRoot($principalBackend, $caldavBackend), + // /addressbook - new \Sabre\CardDAV\AddressBookRoot($principalBackend, $carddavBackend), + new \Sabre\CardDAV\AddressBookRoot($principalBackend, $carddavBackend) ]; + // The object tree needs in turn to be passed to the server class $server = new \Sabre\DAV\Server($nodes); @@ -204,7 +221,15 @@ class Cdav extends \Zotlabs\Web\Controller { if(! local_channel()) return; - $channel = \App::get_channel(); + if((argv(1) === 'calendar') && (! Apps::system_app_installed(local_channel(), 'CalDAV'))) { + return; + } + + if((argv(1) === 'addressbook') && (! Apps::system_app_installed(local_channel(), 'CardDAV'))) { + return; + } + + $channel = App::get_channel(); $principalUri = 'principals/' . $channel['channel_address']; if(!cdav_principal($principalUri)) @@ -807,7 +832,27 @@ class Cdav extends \Zotlabs\Web\Controller { if(!local_channel()) return; - $channel = \App::get_channel(); + if((argv(1) === 'calendar') && (! Apps::system_app_installed(local_channel(), 'CalDAV'))) { + //Do not display any associated widgets at this point + App::$pdl = ''; + + $o = '<b>' . t('CalDAV App') . ' (' . t('Not Installed') . '):</b><br>'; + $o .= t('CalDAV capable calendar'); + return $o; + } + + if((argv(1) === 'addressbook') && (! Apps::system_app_installed(local_channel(), 'CardDAV'))) { + //Do not display any associated widgets at this point + App::$pdl = ''; + + $o = '<b>' . t('CardDAV App') . ' (' . t('Not Installed') . '):</b><br>'; + $o .= t('CalDAV capable addressbook'); + return $o; + } + + App::$profile_uid = local_channel(); + + $channel = App::get_channel(); $principalUri = 'principals/' . $channel['channel_address']; $pdo = \DBA::$dba->db; @@ -862,7 +907,7 @@ class Cdav extends \Zotlabs\Web\Controller { $sources = rtrim($sources, ', '); - $first_day = get_pconfig(local_channel(),'system','cal_first_day'); + $first_day = feature_enabled(local_channel(), 'cal_first_day'); $first_day = (($first_day) ? $first_day : 0); $title = ['title', t('Event title')]; @@ -874,7 +919,7 @@ class Cdav extends \Zotlabs\Web\Controller { $o .= replace_macros(get_markup_template('cdav_calendar.tpl'), [ '$sources' => $sources, '$color' => $color, - '$lang' => \App::$language, + '$lang' => App::$language, '$first_day' => $first_day, '$prev' => t('Previous'), '$next' => t('Next'), diff --git a/Zotlabs/Module/Channel.php b/Zotlabs/Module/Channel.php index b5e6b3aee..f1537ed15 100644 --- a/Zotlabs/Module/Channel.php +++ b/Zotlabs/Module/Channel.php @@ -2,19 +2,23 @@ namespace Zotlabs\Module; -require_once('include/contact_widgets.php'); + +use App; +use Zotlabs\Web\Controller; +use Zotlabs\Lib\PermissionDescription; + require_once('include/items.php'); -require_once("include/bbcode.php"); require_once('include/security.php'); require_once('include/conversation.php'); require_once('include/acl_selectors.php'); -require_once('include/permissions.php'); + /** * @brief Channel Controller * */ -class Channel extends \Zotlabs\Web\Controller { + +class Channel extends Controller { function init() { @@ -26,7 +30,7 @@ class Channel extends \Zotlabs\Web\Controller { $which = argv(1); if(! $which) { if(local_channel()) { - $channel = \App::get_channel(); + $channel = App::get_channel(); if($channel && $channel['channel_address']) $which = $channel['channel_address']; } @@ -37,7 +41,7 @@ class Channel extends \Zotlabs\Web\Controller { } $profile = 0; - $channel = \App::get_channel(); + $channel = App::get_channel(); if((local_channel()) && (argc() > 2) && (argv(2) === 'view')) { $which = $channel['channel_address']; @@ -67,11 +71,11 @@ class Channel extends \Zotlabs\Web\Controller { function get($update = 0, $load = false) { + $noscript_content = get_config('system', 'noscript_content', '1'); + if($load) $_SESSION['loadtime'] = datetime_convert(); - $checkjs = new \Zotlabs\Web\CheckJS(1); - $category = $datequery = $datequery2 = ''; $mid = ((x($_REQUEST,'mid')) ? $_REQUEST['mid'] : ''); @@ -95,22 +99,22 @@ class Channel extends \Zotlabs\Web\Controller { if($update) { // Ensure we've got a profile owner if updating. - \App::$profile['profile_uid'] = \App::$profile_uid = $update; + App::$profile['profile_uid'] = App::$profile_uid = $update; } - $is_owner = (((local_channel()) && (\App::$profile['profile_uid'] == local_channel())) ? true : false); + $is_owner = (((local_channel()) && (App::$profile['profile_uid'] == local_channel())) ? true : false); - $channel = \App::get_channel(); - $observer = \App::get_observer(); + $channel = App::get_channel(); + $observer = App::get_observer(); $ob_hash = (($observer) ? $observer['xchan_hash'] : ''); - $perms = get_all_perms(\App::$profile['profile_uid'],$ob_hash); + $perms = get_all_perms(App::$profile['profile_uid'],$ob_hash); if(! $perms['view_stream']) { // We may want to make the target of this redirect configurable if($perms['view_profile']) { notice( t('Insufficient permissions. Request redirected to profile page.') . EOL); - goaway (z_root() . "/profile/" . \App::$profile['channel_address']); + goaway (z_root() . "/profile/" . App::$profile['channel_address']); } notice( t('Permission denied.') . EOL); return; @@ -121,7 +125,7 @@ class Channel extends \Zotlabs\Web\Controller { nav_set_selected('Channel Home'); - $static = channel_manual_conv_update(\App::$profile['profile_uid']); + $static = channel_manual_conv_update(App::$profile['profile_uid']); // search terms header if($search) { @@ -147,16 +151,16 @@ class Channel extends \Zotlabs\Web\Controller { $x = array( 'is_owner' => $is_owner, - 'allow_location' => ((($is_owner || $observer) && (intval(get_pconfig(\App::$profile['profile_uid'],'system','use_browser_location')))) ? true : false), - 'default_location' => (($is_owner) ? \App::$profile['channel_location'] : ''), - 'nickname' => \App::$profile['channel_address'], - 'lockstate' => (((strlen(\App::$profile['channel_allow_cid'])) || (strlen(\App::$profile['channel_allow_gid'])) || (strlen(\App::$profile['channel_deny_cid'])) || (strlen(\App::$profile['channel_deny_gid']))) ? 'lock' : 'unlock'), - 'acl' => (($is_owner) ? populate_acl($channel_acl,true, \Zotlabs\Lib\PermissionDescription::fromGlobalPermission('view_stream'), get_post_aclDialogDescription(), 'acl_dialog_post') : ''), + 'allow_location' => ((($is_owner || $observer) && (intval(get_pconfig(App::$profile['profile_uid'],'system','use_browser_location')))) ? true : false), + 'default_location' => (($is_owner) ? App::$profile['channel_location'] : ''), + 'nickname' => App::$profile['channel_address'], + 'lockstate' => (((strlen(App::$profile['channel_allow_cid'])) || (strlen(App::$profile['channel_allow_gid'])) || (strlen(App::$profile['channel_deny_cid'])) || (strlen(App::$profile['channel_deny_gid']))) ? 'lock' : 'unlock'), + 'acl' => (($is_owner) ? populate_acl($channel_acl,true, PermissionDescription::fromGlobalPermission('view_stream'), get_post_aclDialogDescription(), 'acl_dialog_post') : ''), 'permissions' => $channel_acl, 'showacl' => (($is_owner) ? 'yes' : ''), 'bang' => '', 'visitor' => (($is_owner || $observer) ? true : false), - 'profile_uid' => \App::$profile['profile_uid'], + 'profile_uid' => App::$profile['profile_uid'], 'editor_autocomplete' => true, 'bbco_autocomplete' => 'bbcode', 'bbcode' => true, @@ -164,7 +168,7 @@ class Channel extends \Zotlabs\Web\Controller { 'reset' => t('Reset form') ); - $o .= status_editor($a,$x); + $o .= status_editor($a,$x,false,'Channel'); } } @@ -176,14 +180,14 @@ class Channel extends \Zotlabs\Web\Controller { $item_normal = item_normal(); $item_normal_update = item_normal_update(); - $sql_extra = item_permissions_sql(\App::$profile['profile_uid']); + $sql_extra = item_permissions_sql(App::$profile['profile_uid']); - if(get_pconfig(\App::$profile['profile_uid'],'system','channel_list_mode') && (! $mid)) + if(feature_enabled(App::$profile['profile_uid'], 'channel_list_mode') && (! $mid)) $page_mode = 'list'; else $page_mode = 'client'; - $abook_uids = " and abook.abook_channel = " . intval(\App::$profile['profile_uid']) . " "; + $abook_uids = " and abook.abook_channel = " . intval(App::$profile['profile_uid']) . " "; $simple_update = (($update) ? " AND item_unseen = 1 " : ''); @@ -193,7 +197,8 @@ class Channel extends \Zotlabs\Web\Controller { $sql_extra .= term_query('item',substr($search,1),TERM_HASHTAG,TERM_COMMUNITYTAG); } else { - $sql_extra .= sprintf(" AND item.body like '%s' ", + $sql_extra .= sprintf(" AND (item.body like '%s' OR item.title like '%s') ", + dbesc(protect_sprintf('%' . $search . '%')), dbesc(protect_sprintf('%' . $search . '%')) ); } @@ -203,7 +208,7 @@ class Channel extends \Zotlabs\Web\Controller { head_add_link([ 'rel' => 'alternate', 'type' => 'application/json+oembed', - 'href' => z_root() . '/oep?f=&url=' . urlencode(z_root() . '/' . \App::$query_string), + 'href' => z_root() . '/oep?f=&url=' . urlencode(z_root() . '/' . App::$query_string), 'title' => 'oembed' ]); @@ -221,7 +226,7 @@ class Channel extends \Zotlabs\Web\Controller { $r = q("SELECT parent AS item_id from item where mid like '%s' and uid = %d $item_normal_update AND item_wall = 1 $simple_update $sql_extra limit 1", dbesc($mid . '%'), - intval(\App::$profile['profile_uid']) + intval(App::$profile['profile_uid']) ); $_SESSION['loadtime'] = datetime_convert(); } @@ -233,7 +238,7 @@ class Channel extends \Zotlabs\Web\Controller { AND (abook.abook_blocked = 0 or abook.abook_flags is null) $sql_extra ORDER BY created DESC", - intval(\App::$profile['profile_uid']) + intval(App::$profile['profile_uid']) ); $_SESSION['loadtime'] = datetime_convert(); } @@ -242,10 +247,10 @@ class Channel extends \Zotlabs\Web\Controller { else { if(x($category)) { - $sql_extra2 .= protect_sprintf(term_item_parent_query(\App::$profile['profile_uid'],'item', $category, TERM_CATEGORY)); + $sql_extra2 .= protect_sprintf(term_item_parent_query(App::$profile['profile_uid'],'item', $category, TERM_CATEGORY)); } if(x($hashtags)) { - $sql_extra2 .= protect_sprintf(term_item_parent_query(\App::$profile['profile_uid'],'item', $hashtags, TERM_HASHTAG, TERM_COMMUNITYTAG)); + $sql_extra2 .= protect_sprintf(term_item_parent_query(App::$profile['profile_uid'],'item', $hashtags, TERM_HASHTAG, TERM_COMMUNITYTAG)); } if($datequery) { @@ -267,15 +272,15 @@ class Channel extends \Zotlabs\Web\Controller { $itemspage = get_pconfig(local_channel(),'system','itemspage'); - \App::set_pager_itemspage(((intval($itemspage)) ? $itemspage : 20)); - $pager_sql = sprintf(" LIMIT %d OFFSET %d ", intval(\App::$pager['itemspage']), intval(\App::$pager['start'])); + App::set_pager_itemspage(((intval($itemspage)) ? $itemspage : 20)); + $pager_sql = sprintf(" LIMIT %d OFFSET %d ", intval(App::$pager['itemspage']), intval(App::$pager['start'])); - if($load || ($checkjs->disabled())) { + if($noscript_content || $load) { if($mid) { $r = q("SELECT parent AS item_id from item where mid like '%s' and uid = %d $item_normal AND item_wall = 1 $sql_extra limit 1", dbesc($mid . '%'), - intval(\App::$profile['profile_uid']) + intval(App::$profile['profile_uid']) ); if (! $r) { notice( t('Permission denied.') . EOL); @@ -289,7 +294,7 @@ class Channel extends \Zotlabs\Web\Controller { AND item.item_wall = 1 AND item.item_thread_top = 1 $sql_extra $sql_extra2 ORDER BY $ordering DESC $pager_sql ", - intval(\App::$profile['profile_uid']) + intval(App::$profile['profile_uid']) ); } } @@ -306,7 +311,7 @@ class Channel extends \Zotlabs\Web\Controller { WHERE item.uid = %d $item_normal AND item.parent IN ( %s ) $sql_extra ", - intval(\App::$profile['profile_uid']), + intval(App::$profile['profile_uid']), dbesc($parents_str) ); @@ -329,19 +334,19 @@ class Channel extends \Zotlabs\Web\Controller { // This is ugly, but we can't pass the profile_uid through the session to the ajax updater, // because browser prefetching might change it on us. We have to deliver it with the page. - $maxheight = get_pconfig(\App::$profile['profile_uid'],'system','channel_divmore_height'); + $maxheight = get_pconfig(App::$profile['profile_uid'],'system','channel_divmore_height'); if(! $maxheight) $maxheight = 400; $o .= '<div id="live-channel"></div>' . "\r\n"; - $o .= "<script> var profile_uid = " . \App::$profile['profile_uid'] - . "; var netargs = '?f='; var profile_page = " . \App::$pager['page'] + $o .= "<script> var profile_uid = " . App::$profile['profile_uid'] + . "; var netargs = '?f='; var profile_page = " . App::$pager['page'] . "; divmore_height = " . intval($maxheight) . "; </script>\r\n"; - \App::$page['htmlhead'] .= replace_macros(get_markup_template("build_query.tpl"),array( + App::$page['htmlhead'] .= replace_macros(get_markup_template("build_query.tpl"),array( '$baseurl' => z_root(), '$pgtype' => 'channel', - '$uid' => ((\App::$profile['profile_uid']) ? \App::$profile['profile_uid'] : '0'), + '$uid' => ((App::$profile['profile_uid']) ? App::$profile['profile_uid'] : '0'), '$gid' => '0', '$cid' => '0', '$cmin' => '(-1)', @@ -354,7 +359,7 @@ class Channel extends \Zotlabs\Web\Controller { '$wall' => '1', '$fh' => '0', '$static' => $static, - '$page' => ((\App::$pager['page'] != 1) ? \App::$pager['page'] : 1), + '$page' => ((App::$pager['page'] != 1) ? App::$pager['page'] : 1), '$search' => $search, '$xchan' => '', '$order' => $order, @@ -405,17 +410,26 @@ class Channel extends \Zotlabs\Web\Controller { $mode = (($search) ? 'search' : 'channel'); - if($checkjs->disabled()) { - $o .= conversation($items,$mode,$update,'traditional'); + if($update) { + $o .= conversation($items,$mode,$update,$page_mode); } else { + + $o .= '<noscript>'; + if($noscript_content) { + $o .= conversation($items,$mode,$update,'traditional'); + $o .= alt_pager(count($items)); + } + else { + $o .= '<div class="section-content-warning-wrapper">' . t('You must enable javascript for your browser to be able to view this content.') . '</div>'; + } + $o .= '</noscript>'; + $o .= conversation($items,$mode,$update,$page_mode); - } - if((! $update) || ($checkjs->disabled())) { - $o .= alt_pager(count($items)); if ($mid && $items[0]['title']) - \App::$page['title'] = $items[0]['title'] . " - " . \App::$page['title']; + App::$page['title'] = $items[0]['title'] . " - " . App::$page['title']; + } if($mid) diff --git a/Zotlabs/Module/Chat.php b/Zotlabs/Module/Chat.php index 378c9f4dd..db77e2612 100644 --- a/Zotlabs/Module/Chat.php +++ b/Zotlabs/Module/Chat.php @@ -1,13 +1,19 @@ <?php /** @file */ -namespace Zotlabs\Module; +namespace Zotlabs\Module; + +use App; +use Zotlabs\Lib\Apps; +use Zotlabs\Web\Controller; +use Zotlabs\Lib\Chatroom; +use Zotlabs\Access\AccessList; -require_once('include/bookmarks.php'); -use \Zotlabs\Lib as Zlib; -class Chat extends \Zotlabs\Web\Controller { +require_once('include/bookmarks.php'); + +class Chat extends Controller { function init() { @@ -16,7 +22,7 @@ class Chat extends \Zotlabs\Web\Controller { $which = argv(1); if(! $which) { if(local_channel()) { - $channel = \App::get_channel(); + $channel = App::get_channel(); if($channel && $channel['channel_address']) $which = $channel['channel_address']; } @@ -27,7 +33,7 @@ class Chat extends \Zotlabs\Web\Controller { } $profile = 0; - $channel = \App::get_channel(); + $channel = App::get_channel(); if((local_channel()) && (argc() > 2) && (argv(2) === 'view')) { $which = $channel['channel_address']; @@ -49,16 +55,16 @@ class Chat extends \Zotlabs\Web\Controller { if((! $room) || (! local_channel())) return; - $channel = \App::get_channel(); + $channel = App::get_channel(); if($_POST['action'] === 'drop') { logger('delete chatroom'); - Zlib\Chatroom::destroy($channel,array('cr_name' => $room)); + Chatroom::destroy($channel,array('cr_name' => $room)); goaway(z_root() . '/chat/' . $channel['channel_address']); } - $acl = new \Zotlabs\Access\AccessList($channel); + $acl = new AccessList($channel); $acl->set_from_array($_REQUEST); $arr = $acl->get(); @@ -67,7 +73,7 @@ class Chat extends \Zotlabs\Web\Controller { if(intval($arr['expire']) < 0) $arr['expire'] = 0; - Zlib\Chatroom::create($channel,$arr); + Chatroom::create($channel,$arr); $x = q("select * from chatroom where cr_name = '%s' and cr_uid = %d limit 1", dbesc($room), @@ -88,26 +94,35 @@ class Chat extends \Zotlabs\Web\Controller { function get() { + + if(! Apps::system_app_installed(App::$profile_uid, 'Chatrooms')) { + //Do not display any associated widgets at this point + App::$pdl = ''; + + $o = '<b>' . t('Chatrooms App') . ' (' . t('Not Installed') . '):</b><br>'; + $o .= t('Access Controlled Chatrooms'); + return $o; + } if(local_channel()) { - $channel = \App::get_channel(); - nav_set_selected('My Chatrooms'); + $channel = App::get_channel(); + nav_set_selected('Chatrooms'); } - $ob = \App::get_observer(); + $ob = App::get_observer(); $observer = get_observer_hash(); if(! $observer) { notice( t('Permission denied.') . EOL); return; } - if(! perm_is_allowed(\App::$profile['profile_uid'],$observer,'chat')) { + if(! perm_is_allowed(App::$profile['profile_uid'],$observer,'chat')) { notice( t('Permission denied.') . EOL); return; } if((argc() > 3) && intval(argv(2)) && (argv(3) === 'leave')) { - Zlib\Chatroom::leave($observer,argv(2),$_SERVER['REMOTE_ADDR']); + Chatroom::leave($observer,argv(2),$_SERVER['REMOTE_ADDR']); goaway(z_root() . '/channel/' . argv(1)); } @@ -160,16 +175,16 @@ class Chat extends \Zotlabs\Web\Controller { $room_id = intval(argv(2)); $bookmark_link = get_bookmark_link($ob); - $x = Zlib\Chatroom::enter($observer,$room_id,'online',$_SERVER['REMOTE_ADDR']); + $x = Chatroom::enter($observer,$room_id,'online',$_SERVER['REMOTE_ADDR']); if(! $x) return; $x = q("select * from chatroom where cr_id = %d and cr_uid = %d $sql_extra limit 1", intval($room_id), - intval(\App::$profile['profile_uid']) + intval(App::$profile['profile_uid']) ); if($x) { - $acl = new \Zotlabs\Access\AccessList(false); + $acl = new AccessList(false); $acl->set($x[0]); $private = $acl->is_private(); @@ -208,19 +223,12 @@ class Chat extends \Zotlabs\Web\Controller { )); return $o; } - - + require_once('include/conversation.php'); - //$o = profile_tabs($a,((local_channel() && local_channel() == \App::$profile['profile_uid']) ? true : false),\App::$profile['channel_address']); $o = ''; - - if(! feature_enabled(\App::$profile['profile_uid'],'ajaxchat')) { - notice( t('Feature disabled.') . EOL); - return $o; - } - $acl = new \Zotlabs\Access\AccessList($channel); + $acl = new AccessList($channel); $channel_acl = $acl->get(); $lockstate = (($channel_acl['allow_cid'] || $channel_acl['allow_gid'] || $channel_acl['deny_cid'] || $channel_acl['deny_gid']) ? 'lock' : 'unlock'); @@ -244,17 +252,17 @@ class Chat extends \Zotlabs\Web\Controller { )); } - $rooms = Zlib\Chatroom::roomlist(\App::$profile['profile_uid']); + $rooms = Chatroom::roomlist(App::$profile['profile_uid']); $o .= replace_macros(get_markup_template('chatrooms.tpl'), array( - '$header' => sprintf( t('%1$s\'s Chatrooms'), \App::$profile['fullname']), + '$header' => sprintf( t('%1$s\'s Chatrooms'), App::$profile['fullname']), '$name' => t('Name'), '$baseurl' => z_root(), - '$nickname' => \App::$profile['channel_address'], + '$nickname' => App::$profile['channel_address'], '$rooms' => $rooms, '$norooms' => t('No chatrooms available'), '$newroom' => t('Create New'), - '$is_owner' => ((local_channel() && local_channel() == \App::$profile['profile_uid']) ? 1 : 0), + '$is_owner' => ((local_channel() && local_channel() == App::$profile['profile_uid']) ? 1 : 0), '$chatroom_new' => $chatroom_new, '$expire' => t('Expiration'), '$expire_unit' => t('min') //minutes diff --git a/Zotlabs/Module/Connect.php b/Zotlabs/Module/Connect.php index cd43ea290..62d3af840 100644 --- a/Zotlabs/Module/Connect.php +++ b/Zotlabs/Module/Connect.php @@ -1,21 +1,21 @@ <?php namespace Zotlabs\Module; /** @file */ - +use App; +use Zotlabs\Web\Controller; +use Zotlabs\Lib\Apps; require_once('include/contact_widgets.php'); require_once('include/items.php'); - - -class Connect extends \Zotlabs\Web\Controller { +class Connect extends Controller { function init() { if(argc() > 1) $which = argv(1); else { notice( t('Requested profile is not available.') . EOL ); - \App::$error = 404; + App::$error = 404; return; } @@ -24,20 +24,32 @@ class Connect extends \Zotlabs\Web\Controller { ); if($r) - \App::$data['channel'] = $r[0]; + App::$data['channel'] = $r[0]; + + $channel_id = App::$data['channel']['channel_id']; + + if(! Apps::system_app_installed($channel_id, 'Premium Channel')) { + return; + } profile_load($which,''); } function post() { - if(! array_key_exists('channel', \App::$data)) + if(! array_key_exists('channel', App::$data)) + return; + + $channel_id = App::$data['channel']['channel_id']; + + if(! Apps::system_app_installed($channel_id, 'Premium Channel')) { return; + } - $edit = ((local_channel() && (local_channel() == \App::$data['channel']['channel_id'])) ? true : false); + $edit = ((local_channel() && (local_channel() == $channel_id)) ? true : false); if($edit) { - $has_premium = ((\App::$data['channel']['channel_pageflags'] & PAGE_PREMIUM) ? 1 : 0); + $has_premium = ((App::$data['channel']['channel_pageflags'] & PAGE_PREMIUM) ? 1 : 0); $premium = (($_POST['premium']) ? intval($_POST['premium']) : 0); $text = escape_tags($_POST['text']); @@ -48,25 +60,25 @@ class Connect extends \Zotlabs\Web\Controller { intval(local_channel()) ); - \Zotlabs\Daemon\Master::Summon(array('Notifier','refresh_all',\App::$data['channel']['channel_id'])); + \Zotlabs\Daemon\Master::Summon(array('Notifier','refresh_all',$channel_id)); } - set_pconfig(\App::$data['channel']['channel_id'],'system','selltext',$text); + set_pconfig($channel_id,'system','selltext',$text); // reload the page completely to get fresh data - goaway(z_root() . '/' . \App::$query_string); + goaway(z_root() . '/' . App::$query_string); } $url = ''; - $observer = \App::get_observer(); + $observer = App::get_observer(); if(($observer) && ($_POST['submit'] === t('Continue'))) { if($observer['xchan_follow']) - $url = sprintf($observer['xchan_follow'],urlencode(channel_reddress(\App::$data['channel']))); + $url = sprintf($observer['xchan_follow'],urlencode(channel_reddress(App::$data['channel']))); if(! $url) { $r = q("select * from hubloc where hubloc_hash = '%s' order by hubloc_id desc limit 1", dbesc($observer['xchan_hash']) ); if($r) - $url = $r[0]['hubloc_url'] . '/follow?f=&url=' . urlencode(channel_reddress(\App::$data['channel'])); + $url = $r[0]['hubloc_url'] . '/follow?f=&url=' . urlencode(channel_reddress(App::$data['channel'])); } } if($url) @@ -79,17 +91,31 @@ class Connect extends \Zotlabs\Web\Controller { function get() { + + if(! array_key_exists('channel', App::$data)) + return; + + $channel_id = App::$data['channel']['channel_id']; + + if(! Apps::system_app_installed($channel_id, 'Premium Channel')) { + //Do not display any associated widgets at this point + App::$pdl = ''; + + $o = '<b>' . t('Premium Channel App') . ' (' . t('Not Installed') . '):</b><br>'; + $o .= t('Allows you to set restrictions and terms on those that connect with your channel'); + return $o; + } - $edit = ((local_channel() && (local_channel() == \App::$data['channel']['channel_id'])) ? true : false); + $edit = ((local_channel() && (local_channel() == $channel_id)) ? true : false); - $text = get_pconfig(\App::$data['channel']['channel_id'],'system','selltext'); + $text = get_pconfig($channel_id,'system','selltext'); if($edit) { $o = replace_macros(get_markup_template('sellpage_edit.tpl'),array( '$header' => t('Premium Channel Setup'), - '$address' => \App::$data['channel']['channel_address'], - '$premium' => array('premium', t('Enable premium channel connection restrictions'),((\App::$data['channel']['channel_pageflags'] & PAGE_PREMIUM) ? '1' : ''),''), + '$address' => App::$data['channel']['channel_address'], + '$premium' => array('premium', t('Enable premium channel connection restrictions'),((App::$data['channel']['channel_pageflags'] & PAGE_PREMIUM) ? '1' : ''),''), '$lbl_about' => t('Please enter your restrictions or conditions, such as paypal receipt, usage guidelines, etc.'), '$text' => $text, '$desc' => t('This channel may require additional steps or acknowledgement of the following conditions prior to connecting:'), @@ -107,7 +133,7 @@ class Connect extends \Zotlabs\Web\Controller { $submit = replace_macros(get_markup_template('sellpage_submit.tpl'), array( '$continue' => t('Continue'), - '$address' => \App::$data['channel']['channel_address'] + '$address' => App::$data['channel']['channel_address'] )); $o = replace_macros(get_markup_template('sellpage_view.tpl'),array( @@ -120,7 +146,7 @@ class Connect extends \Zotlabs\Web\Controller { )); - $arr = array('channel' => \App::$data['channel'],'observer' => \App::get_observer(), 'sellpage' => $o, 'submit' => $submit); + $arr = array('channel' => App::$data['channel'],'observer' => App::get_observer(), 'sellpage' => $o, 'submit' => $submit); call_hooks('connect_premium', $arr); $o = $arr['sellpage']; diff --git a/Zotlabs/Module/Connections.php b/Zotlabs/Module/Connections.php index cecada769..967e9521d 100644 --- a/Zotlabs/Module/Connections.php +++ b/Zotlabs/Module/Connections.php @@ -1,6 +1,7 @@ <?php namespace Zotlabs\Module; +use App; require_once('include/socgraph.php'); require_once('include/selectors.php'); @@ -12,8 +13,10 @@ class Connections extends \Zotlabs\Web\Controller { if(! local_channel()) return; + + App::$profile_uid = local_channel(); - $channel = \App::get_channel(); + $channel = App::get_channel(); if($channel) head_set_icon($channel['xchan_photo_s']); @@ -43,7 +46,7 @@ class Connections extends \Zotlabs\Web\Controller { $all = false; if(! $_REQUEST['aj']) - $_SESSION['return_url'] = \App::$query_string; + $_SESSION['return_url'] = App::$query_string; $search_flags = ""; $head = ''; @@ -88,14 +91,14 @@ class Connections extends \Zotlabs\Web\Controller { $search_flags = " and abook_pending = 1 "; $head = t('New'); $pending = true; - \App::$argv[1] = 'pending'; + App::$argv[1] = 'pending'; } else { $head = t('All'); $search_flags = ''; $all = true; - \App::$argc = 1; - unset(\App::$argv[1]); + App::$argc = 1; + unset(App::$argv[1]); } break; // case 'unconnected': @@ -217,7 +220,7 @@ class Connections extends \Zotlabs\Web\Controller { $sql_extra .= (($searching) ? protect_sprintf(" AND xchan_name like '%$search_txt%' ") : ""); if($_REQUEST['gid']) { - $sql_extra .= " and xchan_hash in ( select xchan from group_member where gid = " . intval($_REQUEST['gid']) . " and uid = " . intval(local_channel()) . " ) "; + $sql_extra .= " and xchan_hash in ( select xchan from pgrp_member where gid = " . intval($_REQUEST['gid']) . " and uid = " . intval(local_channel()) . " ) "; } $r = q("SELECT COUNT(abook.abook_id) AS total FROM abook left join xchan on abook.abook_xchan = xchan.xchan_hash @@ -225,15 +228,15 @@ class Connections extends \Zotlabs\Web\Controller { intval(local_channel()) ); if($r) { - \App::set_pager_total($r[0]['total']); + App::set_pager_total($r[0]['total']); $total = $r[0]['total']; } $r = q("SELECT abook.*, xchan.* FROM abook left join xchan on abook.abook_xchan = xchan.xchan_hash WHERE abook_channel = %d and abook_self = 0 and xchan_deleted = 0 and xchan_orphan = 0 $sql_extra $sql_extra2 ORDER BY xchan_name LIMIT %d OFFSET %d ", intval(local_channel()), - intval(\App::$pager['itemspage']), - intval(\App::$pager['start']) + intval(App::$pager['itemspage']), + intval(App::$pager['start']) ); $contacts = array(); @@ -337,7 +340,7 @@ class Connections extends \Zotlabs\Web\Controller { '$finding' => (($searching) ? t('Connections search') . ": '" . $search . "'" : ""), '$submit' => t('Find'), '$edit' => t('Edit'), - '$cmd' => \App::$cmd, + '$cmd' => App::$cmd, '$contacts' => $contacts, '$paginate' => paginate($a), diff --git a/Zotlabs/Module/Connedit.php b/Zotlabs/Module/Connedit.php index cb9c19cf0..3d7ee449a 100644 --- a/Zotlabs/Module/Connedit.php +++ b/Zotlabs/Module/Connedit.php @@ -7,6 +7,7 @@ namespace Zotlabs\Module; * */ +use Zotlabs\Lib\Apps; require_once('include/socgraph.php'); require_once('include/selectors.php'); @@ -774,7 +775,7 @@ class Connedit extends \Zotlabs\Web\Controller { $global_perms = \Zotlabs\Access\Permissions::Perms(); - $existing = get_all_perms(local_channel(),$contact['abook_xchan']); + $existing = get_all_perms(local_channel(),$contact['abook_xchan'],false); $unapproved = array('pending', t('Approve this connection'), '', t('Accept connection to allow communication'), array(t('No'),('Yes'))); @@ -851,7 +852,7 @@ class Connedit extends \Zotlabs\Web\Controller { '$autoperms' => array('autoperms',t('Apply these permissions automatically'), ((get_pconfig(local_channel(),'system','autoperms')) ? 1 : 0), t('Connection requests will be approved without your interaction'), $yes_no), '$permcat' => [ 'permcat', t('Permission role'), '', '<span class="loading invisible">' . t('Loading') . '<span class="jumping-dots"><span class="dot-1">.</span><span class="dot-2">.</span><span class="dot-3">.</span></span></span>',$permcats ], '$permcat_new' => t('Add permission role'), - '$permcat_enable' => feature_enabled(local_channel(),'permcats'), + '$permcat_enable' => Apps::system_app_installed(local_channel(), 'Permission Categories'), '$addr' => unpunify($contact['xchan_addr']), '$primeurl' => unpunify($contact['xchan_url']), '$section' => $section, diff --git a/Zotlabs/Module/Contactgroup.php b/Zotlabs/Module/Contactgroup.php index 2ba53517f..36aaf7da0 100644 --- a/Zotlabs/Module/Contactgroup.php +++ b/Zotlabs/Module/Contactgroup.php @@ -23,7 +23,7 @@ class Contactgroup extends \Zotlabs\Web\Controller { if((argc() > 1) && (intval(argv(1)))) { - $r = q("SELECT * FROM groups WHERE id = %d AND uid = %d AND deleted = 0 LIMIT 1", + $r = q("SELECT * FROM pgrp WHERE id = %d AND uid = %d AND deleted = 0 LIMIT 1", intval(argv(1)), intval(local_channel()) ); diff --git a/Zotlabs/Module/Defperms.php b/Zotlabs/Module/Defperms.php index 97d9cfd1d..463ecb57a 100644 --- a/Zotlabs/Module/Defperms.php +++ b/Zotlabs/Module/Defperms.php @@ -1,14 +1,16 @@ <?php namespace Zotlabs\Module; +use App; +use Zotlabs\Lib\Apps; +use Zotlabs\Web\Controller; require_once('include/socgraph.php'); require_once('include/selectors.php'); require_once('include/group.php'); require_once('include/photos.php'); - -class Defperms extends \Zotlabs\Web\Controller { +class Defperms extends Controller { /* @brief Initialize the connection-editor * @@ -19,6 +21,9 @@ class Defperms extends \Zotlabs\Web\Controller { if(! local_channel()) return; + + if(! Apps::system_app_installed(local_channel(), 'Default Permissions')) + return; $r = q("SELECT abook.*, xchan.* FROM abook left join xchan on abook_xchan = xchan_hash @@ -26,10 +31,10 @@ class Defperms extends \Zotlabs\Web\Controller { intval(local_channel()) ); if($r) { - \App::$poi = $r[0]; + App::$poi = $r[0]; } - $channel = \App::get_channel(); + $channel = App::get_channel(); if($channel) head_set_icon($channel['xchan_photo_s']); } @@ -43,12 +48,15 @@ class Defperms extends \Zotlabs\Web\Controller { if(! local_channel()) return; + + if(! Apps::system_app_installed(local_channel(), 'Default Permissions')) + return; $contact_id = intval(argv(1)); if(! $contact_id) return; - $channel = \App::get_channel(); + $channel = App::get_channel(); $orig_record = q("SELECT * FROM abook WHERE abook_id = %d AND abook_channel = %d LIMIT 1", intval($contact_id), @@ -112,7 +120,7 @@ class Defperms extends \Zotlabs\Web\Controller { intval($contact_id) ); if($r) { - \App::$poi = $r[0]; + App::$poi = $r[0]; } @@ -131,22 +139,22 @@ class Defperms extends \Zotlabs\Web\Controller { function defperms_clone(&$a) { - if(! \App::$poi) + if(! App::$poi) return; - $channel = \App::get_channel(); + $channel = App::get_channel(); $r = q("SELECT abook.*, xchan.* FROM abook left join xchan on abook_xchan = xchan_hash WHERE abook_channel = %d and abook_id = %d LIMIT 1", intval(local_channel()), - intval(\App::$poi['abook_id']) + intval(App::$poi['abook_id']) ); if($r) { - \App::$poi = array_shift($r); + App::$poi = array_shift($r); } - $clone = \App::$poi; + $clone = App::$poi; unset($clone['abook_id']); unset($clone['abook_account']); @@ -173,9 +181,18 @@ class Defperms extends \Zotlabs\Web\Controller { notice( t('Permission denied.') . EOL); return login(); } + + if(! Apps::system_app_installed(local_channel(), 'Default Permissions')) { + //Do not display any associated widgets at this point + App::$pdl = ''; + + $o = '<b>' . t('Default Permissions App') . ' (' . t('Not Installed') . '):</b><br>'; + $o .= t('Set custom default permissions for new connections'); + return $o; + } $section = ((array_key_exists('section',$_REQUEST)) ? $_REQUEST['section'] : ''); - $channel = \App::get_channel(); + $channel = App::get_channel(); $yes_no = array(t('No'),t('Yes')); @@ -193,7 +210,7 @@ class Defperms extends \Zotlabs\Web\Controller { } $o .= " }\n</script>\n"; - if(\App::$poi) { + if(App::$poi) { $sections = []; @@ -203,13 +220,12 @@ class Defperms extends \Zotlabs\Web\Controller { $perms = array(); - $channel = \App::get_channel(); + $channel = App::get_channel(); - $contact = \App::$poi; + $contact = App::$poi; $global_perms = \Zotlabs\Access\Permissions::Perms(); - $existing = get_all_perms(local_channel(),$contact['abook_xchan']); $hidden_perms = []; foreach($global_perms as $k => $v) { @@ -239,7 +255,7 @@ class Defperms extends \Zotlabs\Web\Controller { '$autoperms' => array('autoperms',t('Apply these permissions automatically'), ((get_pconfig(local_channel(),'system','autoperms')) ? 1 : 0), t('If enabled, connection requests will be approved without your interaction'), $yes_no), '$permcat' => [ 'permcat', t('Permission role'), '', '<span class="loading invisible">' . t('Loading') . '<span class="jumping-dots"><span class="dot-1">.</span><span class="dot-2">.</span><span class="dot-3">.</span></span></span>',$permcats ], '$permcat_new' => t('Add permission role'), - '$permcat_enable' => feature_enabled(local_channel(),'permcats'), + '$permcat_enable' => Apps::system_app_installed(local_channel(), 'Permission Categories'), '$section' => $section, '$sections' => $sections, '$autolbl' => t('The permissions indicated on this page will be applied to all new connections.'), diff --git a/Zotlabs/Module/Directory.php b/Zotlabs/Module/Directory.php index 8a7c6baf6..c29fa8326 100644 --- a/Zotlabs/Module/Directory.php +++ b/Zotlabs/Module/Directory.php @@ -12,13 +12,16 @@ class Directory extends \Zotlabs\Web\Controller { function init() { \App::set_pager_itemspage(60); - if(x($_GET,'ignore')) { + if(local_channel() && x($_GET,'ignore')) { q("insert into xign ( uid, xchan ) values ( %d, '%s' ) ", intval(local_channel()), dbesc($_GET['ignore']) ); goaway(z_root() . '/directory?f=&suggest=1'); } + + if(local_channel()) + \App::$profile_uid = local_channel(); $observer = get_observer_hash(); $global_changed = false; @@ -55,6 +58,7 @@ class Directory extends \Zotlabs\Web\Controller { if($observer) set_xconfig($observer,'directory','pubforums',$pubforums); } + } function get() { diff --git a/Zotlabs/Module/Display.php b/Zotlabs/Module/Display.php index fe0408c6f..d1755c183 100644 --- a/Zotlabs/Module/Display.php +++ b/Zotlabs/Module/Display.php @@ -12,8 +12,9 @@ class Display extends \Zotlabs\Web\Controller { function get($update = 0, $load = false) { - $module_format = 'html'; + $noscript_content = (get_config('system', 'noscript_content', '1') && (! $update)); + $module_format = 'html'; if(argc() > 1) { $module_format = substr(argv(1),strrpos(argv(1),'.') + 1); @@ -21,8 +22,6 @@ class Display extends \Zotlabs\Web\Controller { $module_format = 'html'; } - $checkjs = new \Zotlabs\Web\CheckJS(1); - if($load) $_SESSION['loadtime'] = datetime_convert(); @@ -82,7 +81,7 @@ class Display extends \Zotlabs\Web\Controller { ); $o = '<div id="jot-popup">'; - $o .= status_editor($a,$x); + $o .= status_editor($a,$x,false,'Display'); $o .= '</div>'; } @@ -253,53 +252,44 @@ class Display extends \Zotlabs\Web\Controller { $sql_extra = public_permissions_sql($observer_hash); - if(($update && $load) || ($checkjs->disabled()) || ($module_format !== 'html')) { + if($noscript_content || $load) { - $pager_sql = sprintf(" LIMIT %d OFFSET %d ", intval(\App::$pager['itemspage']),intval(\App::$pager['start'])); + $r = null; - if($load || ($checkjs->disabled()) || ($module_format !== 'html')) { + require_once('include/channel.php'); + $sys = get_sys_channel(); + $sysid = $sys['channel_id']; - $r = null; + if(local_channel()) { + $r = q("SELECT item.id as item_id from item WHERE uid = %d and mid = '%s' $item_normal limit 1", + intval(local_channel()), + dbesc($target_item['parent_mid']) + ); + if($r) { + $updateable = true; + } + } - require_once('include/channel.php'); - $sys = get_sys_channel(); - $sysid = $sys['channel_id']; + if(! $r) { - if(local_channel()) { - $r = q("SELECT item.id as item_id from item - WHERE uid = %d - and mid = '%s' - $item_normal - limit 1", - intval(local_channel()), - dbesc($target_item['parent_mid']) - ); - if($r) { - $updateable = true; - } - } + // in case somebody turned off public access to sys channel content using permissions + // make that content unsearchable by ensuring the owner uid can't match - if(! $r) { - - // in case somebody turned off public access to sys channel content using permissions - // make that content unsearchable by ensuring the owner uid can't match - - if(! perm_is_allowed($sysid,$observer_hash,'view_stream')) - $sysid = 0; - - $r = q("SELECT item.id as item_id from item - WHERE mid = '%s' - AND (((( item.allow_cid = '' AND item.allow_gid = '' AND item.deny_cid = '' - AND item.deny_gid = '' AND item_private = 0 ) - and uid in ( " . stream_perms_api_uids(($observer_hash) ? (PERMS_NETWORK|PERMS_PUBLIC) : PERMS_PUBLIC) . " )) - OR uid = %d ) - $sql_extra ) - $item_normal - limit 1", - dbesc($target_item['parent_mid']), - intval($sysid) - ); - } + if(! perm_is_allowed($sysid,$observer_hash,'view_stream')) + $sysid = 0; + + $r = q("SELECT item.id as item_id from item + WHERE mid = '%s' + AND (((( item.allow_cid = '' AND item.allow_gid = '' AND item.deny_cid = '' + AND item.deny_gid = '' AND item_private = 0 ) + and uid in ( " . stream_perms_api_uids(($observer_hash) ? (PERMS_NETWORK|PERMS_PUBLIC) : PERMS_PUBLIC) . " )) + OR uid = %d ) + $sql_extra ) + $item_normal + limit 1", + dbesc($target_item['parent_mid']), + intval($sysid) + ); } } @@ -309,7 +299,6 @@ class Display extends \Zotlabs\Web\Controller { require_once('include/channel.php'); $sys = get_sys_channel(); $sysid = $sys['channel_id']; - if(local_channel()) { $r = q("SELECT item.parent AS item_id from item WHERE uid = %d @@ -350,7 +339,7 @@ class Display extends \Zotlabs\Web\Controller { else { $r = array(); } - + if($r) { $parents_str = ids_to_querystr($r,'item_id'); if($parents_str) { @@ -373,14 +362,24 @@ class Display extends \Zotlabs\Web\Controller { case 'html': - if ($checkjs->disabled()) { - $o .= conversation($items, 'display', $update, 'traditional'); + if ($update) { + $o .= conversation($items, 'display', $update, 'client'); + } + else { + $o .= '<noscript>'; + if($noscript_content) { + $o .= conversation($items, 'display', $update, 'traditional'); + } + else { + $o .= '<div class="section-content-warning-wrapper">' . t('You must enable javascript for your browser to be able to view this content.') . '</div>'; + } + $o .= '</noscript>'; + if ($items[0]['title']) \App::$page['title'] = $items[0]['title'] . " - " . \App::$page['title']; - } - else { + $o .= conversation($items, 'display', $update, 'client'); - } + } break; @@ -435,7 +434,7 @@ class Display extends \Zotlabs\Web\Controller { $o .= '<div id="content-complete"></div>'; - if((($update && $load) || $checkjs->disabled()) && (! $items)) { + if((($update && $load) || $noscript_content) && (! $items)) { $r = q("SELECT id, item_deleted FROM item WHERE mid = '%s' LIMIT 1", dbesc($item_hash) diff --git a/Zotlabs/Module/Editblock.php b/Zotlabs/Module/Editblock.php index 563ad9ca2..c031f32a1 100644 --- a/Zotlabs/Module/Editblock.php +++ b/Zotlabs/Module/Editblock.php @@ -132,7 +132,7 @@ class Editblock extends \Zotlabs\Web\Controller { 'bbcode' => (($mimetype == 'text/bbcode') ? true : false) ); - $editor = status_editor($a, $x); + $editor = status_editor($a, $x, false, 'Editblock'); $o .= replace_macros(get_markup_template('edpost_head.tpl'), array( '$title' => t('Edit Block'), diff --git a/Zotlabs/Module/Editlayout.php b/Zotlabs/Module/Editlayout.php index 67e0bcd32..50096f1a1 100644 --- a/Zotlabs/Module/Editlayout.php +++ b/Zotlabs/Module/Editlayout.php @@ -131,7 +131,7 @@ class Editlayout extends \Zotlabs\Web\Controller { 'profile_uid' => intval($owner), ); - $editor = status_editor($a, $x); + $editor = status_editor($a, $x, false, 'Editlayout'); $o .= replace_macros(get_markup_template('edpost_head.tpl'), array( '$title' => t('Edit Layout'), diff --git a/Zotlabs/Module/Editpost.php b/Zotlabs/Module/Editpost.php index 45d8e7644..1c9068e07 100644 --- a/Zotlabs/Module/Editpost.php +++ b/Zotlabs/Module/Editpost.php @@ -102,7 +102,7 @@ class Editpost extends \Zotlabs\Web\Controller { 'bbcode' => true ); - $editor = status_editor($a, $x); + $editor = status_editor($a, $x, false, 'Editpost'); $o .= replace_macros(get_markup_template('edpost_head.tpl'), array( '$title' => t('Edit post'), diff --git a/Zotlabs/Module/Editwebpage.php b/Zotlabs/Module/Editwebpage.php index b67421cd5..785eeb4ec 100644 --- a/Zotlabs/Module/Editwebpage.php +++ b/Zotlabs/Module/Editwebpage.php @@ -160,7 +160,7 @@ class Editwebpage extends \Zotlabs\Web\Controller { 'bbcode' => (($mimetype == 'text/bbcode') ? true : false) ); - $editor = status_editor($a, $x); + $editor = status_editor($a, $x, false, 'Editwebpage'); $o .= replace_macros(get_markup_template('edpost_head.tpl'), array( '$title' => t('Edit Webpage'), diff --git a/Zotlabs/Module/Events.php b/Zotlabs/Module/Events.php index 33c8b8249..7e5204e62 100644 --- a/Zotlabs/Module/Events.php +++ b/Zotlabs/Module/Events.php @@ -6,7 +6,7 @@ require_once('include/bbcode.php'); require_once('include/datetime.php'); require_once('include/event.php'); require_once('include/items.php'); - +require_once('include/html2plain.php'); class Events extends \Zotlabs\Web\Controller { @@ -271,8 +271,10 @@ class Events extends \Zotlabs\Web\Controller { notice( t('Permission denied.') . EOL); return; } - + + \App::$profile_uid = local_channel(); nav_set_selected('Events'); + if((argc() > 2) && (argv(1) === 'ignore') && intval(argv(2))) { $r = q("update event set dismissed = 1 where id = %d and uid = %d", @@ -288,7 +290,7 @@ class Events extends \Zotlabs\Web\Controller { ); } - $first_day = get_pconfig(local_channel(),'system','cal_first_day'); + $first_day = feature_enabled(local_channel(), 'events_cal_first_day'); $first_day = (($first_day) ? $first_day : 0); $htpl = get_markup_template('event_head.tpl'); @@ -641,6 +643,7 @@ class Events extends \Zotlabs\Web\Controller { } $html = format_event_html($rr); $rr['desc'] = zidify_links(smilies(bbcode($rr['desc']))); + $rr['description'] = htmlentities(html2plain(bbcode($rr['description'])),ENT_COMPAT,'UTF-8',false); $rr['location'] = zidify_links(smilies(bbcode($rr['location']))); $events[] = array( 'id'=>$rr['id'], @@ -659,8 +662,6 @@ class Events extends \Zotlabs\Web\Controller { 'html'=>$html, 'plink' => array($rr['plink'],t('Link to Source'),'',''), ); - - } } diff --git a/Zotlabs/Module/Filestorage.php b/Zotlabs/Module/Filestorage.php index cd9ab601d..23bd63f95 100644 --- a/Zotlabs/Module/Filestorage.php +++ b/Zotlabs/Module/Filestorage.php @@ -128,7 +128,7 @@ class Filestorage extends \Zotlabs\Web\Controller { } } - if(json_return) + if($json_return) json_return_and_die([ 'success' => true ]); goaway(dirname($url)); diff --git a/Zotlabs/Module/Group.php b/Zotlabs/Module/Group.php index acebe995d..c8ccaa2cb 100644 --- a/Zotlabs/Module/Group.php +++ b/Zotlabs/Module/Group.php @@ -1,11 +1,13 @@ <?php namespace Zotlabs\Module; -require_once('include/group.php'); - +use App; +use Zotlabs\Web\Controller; +use Zotlabs\Lib\Apps; +require_once('include/group.php'); -class Group extends \Zotlabs\Web\Controller { +class Group extends Controller { function init() { if(! local_channel()) { @@ -13,7 +15,11 @@ class Group extends \Zotlabs\Web\Controller { return; } - \App::$profile_uid = local_channel(); + if(! Apps::system_app_installed(local_channel(), 'Privacy Groups')) { + return; + } + + App::$profile_uid = local_channel(); nav_set_selected('Privacy Groups'); } @@ -24,6 +30,10 @@ class Group extends \Zotlabs\Web\Controller { notice( t('Permission denied.') . EOL); return; } + + if(! Apps::system_app_installed(local_channel(), 'Privacy Groups')) { + return; + } if((argc() == 2) && (argv(1) === 'new')) { check_form_security_token_redirectOnErr('/group/new', 'group_edit'); @@ -43,7 +53,7 @@ class Group extends \Zotlabs\Web\Controller { if((argc() == 2) && (intval(argv(1)))) { check_form_security_token_redirectOnErr('/group', 'group_edit'); - $r = q("SELECT * FROM groups WHERE id = %d AND uid = %d LIMIT 1", + $r = q("SELECT * FROM pgrp WHERE id = %d AND uid = %d LIMIT 1", intval(argv(1)), intval(local_channel()) ); @@ -57,7 +67,7 @@ class Group extends \Zotlabs\Web\Controller { $public = intval($_POST['public']); if((strlen($groupname)) && (($groupname != $group['gname']) || ($public != $group['visible']))) { - $r = q("UPDATE groups SET gname = '%s', visible = %d WHERE uid = %d AND id = %d", + $r = q("UPDATE pgrp SET gname = '%s', visible = %d WHERE uid = %d AND id = %d", dbesc($groupname), intval($public), intval(local_channel()), @@ -77,13 +87,22 @@ class Group extends \Zotlabs\Web\Controller { $change = false; - logger('mod_group: ' . \App::$cmd,LOGGER_DEBUG); + logger('mod_group: ' . App::$cmd,LOGGER_DEBUG); if(! local_channel()) { notice( t('Permission denied') . EOL); return; } + if(! Apps::system_app_installed(local_channel(), 'Privacy Groups')) { + //Do not display any associated widgets at this point + App::$pdl = ''; + + $o = '<b>' . t('Privacy Groups App') . ' (' . t('Not Installed') . '):</b><br>'; + $o .= t('Management of privacy groups'); + return $o; + } + // Switch to text mode interface if we have more than 'n' contacts or group members $switchtotext = get_pconfig(local_channel(),'system','groupedit_image_limit'); if($switchtotext === false) @@ -96,7 +115,7 @@ class Group extends \Zotlabs\Web\Controller { $new = (((argc() == 2) && (argv(1) === 'new')) ? true : false); - $groups = q("SELECT id, gname FROM groups WHERE deleted = 0 AND uid = %d ORDER BY gname ASC", + $groups = q("SELECT id, gname FROM pgrp WHERE deleted = 0 AND uid = %d ORDER BY gname ASC", intval(local_channel()) ); @@ -141,7 +160,7 @@ class Group extends \Zotlabs\Web\Controller { check_form_security_token_redirectOnErr('/group', 'group_drop', 't'); if(intval(argv(2))) { - $r = q("SELECT gname FROM groups WHERE id = %d AND uid = %d LIMIT 1", + $r = q("SELECT gname FROM pgrp WHERE id = %d AND uid = %d LIMIT 1", intval(argv(2)), intval(local_channel()) ); @@ -173,7 +192,7 @@ class Group extends \Zotlabs\Web\Controller { if((argc() > 1) && (intval(argv(1)))) { require_once('include/acl_selectors.php'); - $r = q("SELECT * FROM groups WHERE id = %d AND uid = %d AND deleted = 0 LIMIT 1", + $r = q("SELECT * FROM pgrp WHERE id = %d AND uid = %d AND deleted = 0 LIMIT 1", intval(argv(1)), intval(local_channel()) ); diff --git a/Zotlabs/Module/Home.php b/Zotlabs/Module/Home.php index 647a6412a..7f2d6424d 100644 --- a/Zotlabs/Module/Home.php +++ b/Zotlabs/Module/Home.php @@ -13,14 +13,12 @@ class Home extends \Zotlabs\Web\Controller { $ret = array(); call_hooks('home_init',$ret); - + $splash = ((argc() > 1 && argv(1) === 'splash') ? true : false); $channel = \App::get_channel(); if(local_channel() && $channel && $channel['xchan_url'] && ! $splash) { - $dest = $channel['channel_startpage']; - if(! $dest) - $dest = get_pconfig(local_channel(),'system','startpage'); + $dest = (($ret['startpage']) ? $ret['startpage'] : ''); if(! $dest) $dest = get_config('system','startpage'); if(! $dest) diff --git a/Zotlabs/Module/Hq.php b/Zotlabs/Module/Hq.php index fee4246c0..3535ac71a 100644 --- a/Zotlabs/Module/Hq.php +++ b/Zotlabs/Module/Hq.php @@ -138,7 +138,7 @@ class Hq extends \Zotlabs\Web\Controller { [ '$no_messages' => (($target_item) ? false : true), '$no_messages_label' => [ t('Welcome to Hubzilla!'), t('You have got no unseen posts...') ], - '$editor' => status_editor($a,$x) + '$editor' => status_editor($a,$x,false,'Hq') ] ); diff --git a/Zotlabs/Module/Import.php b/Zotlabs/Module/Import.php index d031bf16b..6016328a5 100644 --- a/Zotlabs/Module/Import.php +++ b/Zotlabs/Module/Import.php @@ -426,9 +426,9 @@ class Import extends \Zotlabs\Web\Controller { unset($group['id']); $group['uid'] = $channel['channel_id']; - create_table_from_array('groups', $group); + create_table_from_array('pgrp', $group); } - $r = q("select * from groups where uid = %d", + $r = q("select * from pgrp where uid = %d", intval($channel['channel_id']) ); if($r) { @@ -448,7 +448,7 @@ class Import extends \Zotlabs\Web\Controller { if($x['old'] == $group_member['gid']) $group_member['gid'] = $x['new']; } - create_table_from_array('group_member', $group_member); + create_table_from_array('pgrp_member', $group_member); } } diff --git a/Zotlabs/Module/Invite.php b/Zotlabs/Module/Invite.php index 359f99b3e..6359da54c 100644 --- a/Zotlabs/Module/Invite.php +++ b/Zotlabs/Module/Invite.php @@ -1,6 +1,10 @@ <?php namespace Zotlabs\Module; +use App; +use Zotlabs\Lib\Apps; +use Zotlabs\Web\Controller; + /** * module: invite.php * @@ -9,7 +13,7 @@ namespace Zotlabs\Module; */ -class Invite extends \Zotlabs\Web\Controller { +class Invite extends Controller { function post() { @@ -17,6 +21,10 @@ class Invite extends \Zotlabs\Web\Controller { notice( t('Permission denied.') . EOL); return; } + + if(! Apps::system_app_installed(local_channel(), 'Invite')) { + return; + } check_form_security_token_redirectOnErr('/', 'send_invite'); @@ -57,7 +65,7 @@ class Invite extends \Zotlabs\Web\Controller { else $nmessage = $message; - $account = \App::get_account(); + $account = App::get_account(); $res = z_mail( [ @@ -95,6 +103,15 @@ class Invite extends \Zotlabs\Web\Controller { return; } + if(! Apps::system_app_installed(local_channel(), 'Invite')) { + //Do not display any associated widgets at this point + App::$pdl = ''; + + $o = '<b>' . t('Invite App') . ' (' . t('Not Installed') . '):</b><br>'; + $o .= t('Send email invitations to join this network'); + return $o; + } + nav_set_selected('Invite'); $tpl = get_markup_template('invite.tpl'); @@ -127,11 +144,11 @@ class Invite extends \Zotlabs\Web\Controller { } } - $ob = \App::get_observer(); + $ob = App::get_observer(); if(! $ob) return $o; - $channel = \App::get_channel(); + $channel = App::get_channel(); $o = replace_macros($tpl, array( '$form_security_token' => get_form_security_token("send_invite"), diff --git a/Zotlabs/Module/Item.php b/Zotlabs/Module/Item.php index 9419dea0f..2ee639874 100644 --- a/Zotlabs/Module/Item.php +++ b/Zotlabs/Module/Item.php @@ -29,7 +29,7 @@ use \Zotlabs\Lib as Zlib; class Item extends \Zotlabs\Web\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. @@ -237,10 +237,12 @@ class Item extends \Zotlabs\Web\Controller { if($parent) { logger('mod_item: item_post parent=' . $parent); $can_comment = false; - if((array_key_exists('owner',$parent_item)) && intval($parent_item['owner']['abook_self'])) - $can_comment = perm_is_allowed($profile_uid,$observer['xchan_hash'],'post_comments'); - else - $can_comment = can_comment_on_post($observer['xchan_hash'],$parent_item); + + $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) ; @@ -533,7 +535,7 @@ class Item extends \Zotlabs\Web\Controller { // Look for tags and linkify them $results = linkify_tags($a, $body, ($uid) ? $uid : $profile_uid); -logger('linkify: ' . print_r($results,true)); + if($results) { // Set permissions based on tag replacements @@ -1163,28 +1165,6 @@ logger('linkify: ' . print_r($results,true)); return $ret; } - // auto-upgrade beginner (techlevel 0) accounts - if they have at least two friends and ten posts - // and have uploaded something (like a profile photo), promote them to level 1. - - $a = q("select account_id, account_level from account where account_id = (select channel_account_id from channel where channel_id = %d limit 1)", - intval($channel_id) - ); - if((! intval($a[0]['account_level'])) && intval($r[0]['total']) > 10) { - $x = q("select count(abook_id) as total from abook where abook_channel = %d", - intval($channel_id) - ); - if($x && intval($x[0]['total']) > 2) { - $y = q("select count(id) as total from attach where uid = %d", - intval($channel_id) - ); - if($y && intval($y[0]['total']) > 1) { - q("update account set account_level = 1 where account_id = %d", - intval($a[0]['account_id']) - ); - } - } - } - 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'])) { diff --git a/Zotlabs/Module/Lang.php b/Zotlabs/Module/Lang.php index 0e5d85d05..a32f933a6 100644 --- a/Zotlabs/Module/Lang.php +++ b/Zotlabs/Module/Lang.php @@ -1,13 +1,28 @@ <?php namespace Zotlabs\Module; +use App; +use Zotlabs\Lib\Apps; +use Zotlabs\Web\Controller; -class Lang extends \Zotlabs\Web\Controller { +class Lang extends Controller { function get() { + + if(local_channel()) { + if(! Apps::system_app_installed(local_channel(), 'Language')) { + //Do not display any associated widgets at this point + App::$pdl = ''; + + $o = '<b>' . t('Language App') . ' (' . t('Not Installed') . '):</b><br>'; + $o .= t('Change UI language'); + return $o; + } + } + nav_set_selected('Language'); return lang_selector(); + } - } diff --git a/Zotlabs/Module/Layouts.php b/Zotlabs/Module/Layouts.php index 19efb37fd..25e27d226 100644 --- a/Zotlabs/Module/Layouts.php +++ b/Zotlabs/Module/Layouts.php @@ -141,7 +141,7 @@ class Layouts extends \Zotlabs\Web\Controller { if($_REQUEST['pagetitle']) $x['pagetitle'] = $_REQUEST['pagetitle']; - $editor = status_editor($a,$x); + $editor = status_editor($a,$x,false,'Layouts'); $r = q("select iconfig.iid, iconfig.v, mid, title, body, mimetype, created, edited, item_type from iconfig left join item on iconfig.iid = item.id diff --git a/Zotlabs/Module/Lockview.php b/Zotlabs/Module/Lockview.php index 466d16997..d7ed07a53 100644 --- a/Zotlabs/Module/Lockview.php +++ b/Zotlabs/Module/Lockview.php @@ -118,7 +118,7 @@ class Lockview extends \Zotlabs\Web\Controller { } if(count($allowed_groups)) { - $r = q("SELECT gname FROM groups WHERE hash IN ( " . implode(', ', $allowed_groups) . " )"); + $r = q("SELECT gname FROM pgrp WHERE hash IN ( " . implode(', ', $allowed_groups) . " )"); if($r) foreach($r as $rr) $l[] = '<div class="dropdown-item"><b>' . $rr['gname'] . '</b></div>'; @@ -156,7 +156,7 @@ class Lockview extends \Zotlabs\Web\Controller { if(count($deny_groups)) { - $r = q("SELECT gname FROM groups WHERE hash IN ( " . implode(', ', $deny_groups) . " )"); + $r = q("SELECT gname FROM pgrp WHERE hash IN ( " . implode(', ', $deny_groups) . " )"); if($r) foreach($r as $rr) $l[] = '<div class="dropdown-item"><b><strike>' . $rr['gname'] . '</strike></b></div>'; diff --git a/Zotlabs/Module/Magic.php b/Zotlabs/Module/Magic.php index be6866592..71737eef8 100644 --- a/Zotlabs/Module/Magic.php +++ b/Zotlabs/Module/Magic.php @@ -146,12 +146,17 @@ class Magic extends \Zotlabs\Web\Controller { $dest = strip_zids($dest); $dest = strip_query_param($dest,'f'); + $data = json_encode([ 'OpenWebAuth' => random_string() ]); + $headers = []; $headers['Accept'] = 'application/x-zot+json' ; $headers['X-Open-Web-Auth'] = random_string(); + $headers['Host'] = $parsed['host']; + $headers['Digest'] = 'SHA-256=' . \Zotlabs\Web\HTTPSig::generate_digest($data,false); + $headers = \Zotlabs\Web\HTTPSig::create_sig('',$headers,$channel['channel_prvkey'], 'acct:' . $channel['channel_address'] . '@' . \App::get_hostname(),false,true,'sha512'); - $x = z_fetch_url($basepath . '/owa',false,$redirects,[ 'headers' => $headers ]); + $x = z_post_url($basepath . '/owa',$data,$redirects,[ 'headers' => $headers ]); if($x['success']) { $j = json_decode($x['body'],true); diff --git a/Zotlabs/Module/Mail.php b/Zotlabs/Module/Mail.php index ca183f644..d38c1d88c 100644 --- a/Zotlabs/Module/Mail.php +++ b/Zotlabs/Module/Mail.php @@ -393,7 +393,7 @@ class Mail extends \Zotlabs\Web\Controller { 'delete' => t('Delete message'), 'dreport' => t('Delivery report'), 'recall' => t('Recall message'), - 'can_recall' => (($channel['channel_hash'] == $message['from_xchan'] && get_account_techlevel() > 0) ? true : false), + 'can_recall' => ($channel['channel_hash'] == $message['from_xchan']), 'is_recalled' => (intval($message['mail_recalled']) ? t('Message has been recalled.') : ''), 'date' => datetime_convert('UTC',date_default_timezone_get(),$message['created'], 'c'), ); diff --git a/Zotlabs/Module/Manage.php b/Zotlabs/Module/Manage.php index 2c88a4df0..20d5b0449 100644 --- a/Zotlabs/Module/Manage.php +++ b/Zotlabs/Module/Manage.php @@ -11,7 +11,7 @@ class Manage extends \Zotlabs\Web\Controller { return; } - nav_set_selected('Channel Manager'); + nav_set_selected('Channel Manager', 'settings/manage'); require_once('include/security.php'); @@ -129,7 +129,7 @@ class Manage extends \Zotlabs\Web\Controller { } } - + $r = q("select count(channel_id) as total from channel where channel_account_id = %d and channel_removed = 0", intval(get_account_id()) ); @@ -170,7 +170,7 @@ class Manage extends \Zotlabs\Web\Controller { '$header' => t('Channel Manager'), '$msg_selected' => t('Current Channel'), '$selected' => local_channel(), - '$desc' => t('Switch to one of your channels by selecting it.'), + '$desc' => ((count($channels) > 1 || $delegates) ? t('Switch to one of your channels by selecting it.') : ''), '$msg_default' => t('Default Channel'), '$msg_make_default' => t('Make Default'), '$create' => $create, diff --git a/Zotlabs/Module/Mood.php b/Zotlabs/Module/Mood.php index ad29ec7e8..16ef0b171 100644 --- a/Zotlabs/Module/Mood.php +++ b/Zotlabs/Module/Mood.php @@ -1,21 +1,29 @@ <?php namespace Zotlabs\Module; +use App; +use Zotlabs\Lib\Apps; +use Zotlabs\Web\Controller; + require_once('include/security.php'); require_once('include/bbcode.php'); require_once('include/items.php'); -class Mood extends \Zotlabs\Web\Controller { +class Mood extends Controller { function init() { if(! local_channel()) return; + + if(! Apps::system_app_installed(local_channel(), 'Mood')) { + return; + } $uid = local_channel(); - $channel = \App::get_channel(); + $channel = App::get_channel(); $verb = notags(trim($_GET['verb'])); if(! $verb) @@ -60,7 +68,7 @@ class Mood extends \Zotlabs\Web\Controller { $deny_gid = $channel['channel_deny_gid']; } - $poster = \App::get_observer(); + $poster = App::get_observer(); $mid = item_message_id(); @@ -117,6 +125,15 @@ class Mood extends \Zotlabs\Web\Controller { return; } + if(! Apps::system_app_installed(local_channel(), 'Mood')) { + //Do not display any associated widgets at this point + App::$pdl = ''; + + $o = '<b>' . t('Mood App') . ' (' . t('Not Installed') . '):</b><br>'; + $o .= t('Set your current mood and tell your friends'); + return $o; + } + nav_set_selected('Mood'); $parent = ((x($_GET,'parent')) ? intval($_GET['parent']) : '0'); diff --git a/Zotlabs/Module/Network.php b/Zotlabs/Module/Network.php index 77a08585b..792f92418 100644 --- a/Zotlabs/Module/Network.php +++ b/Zotlabs/Module/Network.php @@ -1,6 +1,8 @@ <?php namespace Zotlabs\Module; +use App; + require_once('include/items.php'); require_once('include/group.php'); require_once('include/contact_widgets.php'); @@ -25,8 +27,8 @@ class Network extends \Zotlabs\Web\Controller { goaway('network' . '?f=&' . $network_options); } - $channel = \App::get_channel(); - \App::$profile_uid = local_channel(); + $channel = App::get_channel(); + App::$profile_uid = local_channel(); head_set_icon($channel['xchan_photo_s']); } @@ -34,7 +36,7 @@ class Network extends \Zotlabs\Web\Controller { function get($update = 0, $load = false) { if(! local_channel()) { - $_SESSION['return_url'] = \App::$query_string; + $_SESSION['return_url'] = App::$query_string; return login(false); } @@ -44,11 +46,11 @@ class Network extends \Zotlabs\Web\Controller { $_SESSION['loadtime'] = datetime_convert(); } - $arr = array('query' => \App::$query_string); + $arr = array('query' => App::$query_string); call_hooks('network_content_init', $arr); - $channel = \App::get_channel(); + $channel = App::get_channel(); $item_normal = item_normal(); $item_normal_update = item_normal_update(); @@ -82,20 +84,9 @@ class Network extends \Zotlabs\Web\Controller { $search = (($_GET['search']) ? $_GET['search'] : ''); if($search) { - $_GET['netsearch'] = escape_tags($search); - if(strpos($search,'@') === 0) { - $r = q("select abook_id from abook left join xchan on abook_xchan = xchan_hash where xchan_name = '%s' and abook_channel = %d limit 1", - dbesc(substr($search,1)), - intval(local_channel()) - ); - if($r) { - $_GET['cid'] = $r[0]['abook_id']; - $search = $_GET['search'] = ''; - } - } - elseif(strpos($search,'#') === 0) { + if(strpos($search,'#') === 0) { $hashtags = substr($search,1); - $search = $_GET['search'] = ''; + $search = ''; } } @@ -106,7 +97,7 @@ class Network extends \Zotlabs\Web\Controller { // filter by collection (e.g. group) if($gid) { - $r = q("SELECT * FROM groups WHERE id = %d AND uid = %d LIMIT 1", + $r = q("SELECT * FROM pgrp WHERE id = %d AND uid = %d LIMIT 1", intval($gid), intval(local_channel()) ); @@ -141,7 +132,7 @@ class Network extends \Zotlabs\Web\Controller { $deftag = ''; - if(x($_GET,'search') || $file || (!$pf && $cid)) + if(x($_GET,'search') || $file || (!$pf && $cid) || $hashtags || $verb || $category) $nouveau = true; if($cid) { @@ -165,17 +156,15 @@ class Network extends \Zotlabs\Web\Controller { } if(! $update) { - $tabs = ''; //network_tabs(); - $o .= $tabs; // search terms header - if($search) { + if($search || $hashtags) { $o .= replace_macros(get_markup_template("section_title.tpl"),array( - '$title' => t('Search Results For:') . ' ' . htmlspecialchars($search, ENT_COMPAT,'UTF-8') + '$title' => t('Search Results For:') . ' ' . (($search) ? htmlspecialchars($search, ENT_COMPAT,'UTF-8') : '#' . htmlspecialchars($hashtags, ENT_COMPAT,'UTF-8')) )); } - nav_set_selected('Grid'); + nav_set_selected('Network'); $channel_acl = array( 'allow_cid' => $channel['channel_allow_cid'], @@ -207,7 +196,7 @@ class Network extends \Zotlabs\Web\Controller { $x['pretext'] = $deftag; - $status_editor = status_editor($a,$x); + $status_editor = status_editor($a,$x,false,'Network'); $o .= $status_editor; $static = channel_manual_conv_update(local_channel()); @@ -254,8 +243,7 @@ class Network extends \Zotlabs\Web\Controller { )); } - $o = $tabs; - $o .= $title; + $o = $title; $o .= $status_editor; } @@ -281,8 +269,7 @@ class Network extends \Zotlabs\Web\Controller { '$title' => '<a href="' . zid($cid_r[0]['xchan_url']) . '" ><img src="' . zid($cid_r[0]['xchan_photo_s']) . '" alt="' . urlencode($cid_r[0]['xchan_name']) . '" /></a> <a href="' . zid($cid_r[0]['xchan_url']) . '" >' . $cid_r[0]['xchan_name'] . '</a>' )); - $o = $tabs; - $o .= $title; + $o = $title; $o .= $status_editor; } elseif($xchan) { @@ -295,8 +282,8 @@ class Network extends \Zotlabs\Web\Controller { $title = replace_macros(get_markup_template("section_title.tpl"),array( '$title' => '<a href="' . zid($r[0]['xchan_url']) . '" ><img src="' . zid($r[0]['xchan_photo_s']) . '" alt="' . urlencode($r[0]['xchan_name']) . '" /></a> <a href="' . zid($r[0]['xchan_url']) . '" >' . $r[0]['xchan_name'] . '</a>' )); - $o = $tabs; - $o .= $title; + + $o = $title; $o .= $status_editor; } @@ -326,10 +313,10 @@ class Network extends \Zotlabs\Web\Controller { $o .= '<div id="live-network"></div>' . "\r\n"; $o .= "<script> var profile_uid = " . local_channel() - . "; var profile_page = " . \App::$pager['page'] + . "; var profile_page = " . App::$pager['page'] . "; divmore_height = " . intval($maxheight) . "; </script>\r\n"; - \App::$page['htmlhead'] .= replace_macros(get_markup_template("build_query.tpl"),array( + App::$page['htmlhead'] .= replace_macros(get_markup_template("build_query.tpl"),array( '$baseurl' => z_root(), '$pgtype' => 'network', '$uid' => ((local_channel()) ? local_channel() : '0'), @@ -346,7 +333,7 @@ class Network extends \Zotlabs\Web\Controller { '$wall' => '0', '$static' => $static, '$list' => ((x($_REQUEST,'list')) ? intval($_REQUEST['list']) : 0), - '$page' => ((\App::$pager['page'] != 1) ? \App::$pager['page'] : 1), + '$page' => ((App::$pager['page'] != 1) ? App::$pager['page'] : 1), '$search' => (($search) ? $search : ''), '$xchan' => $xchan, '$order' => $order, @@ -380,7 +367,8 @@ class Network extends \Zotlabs\Web\Controller { $sql_extra .= term_query('item',substr($search,1),TERM_HASHTAG,TERM_COMMUNITYTAG); } else { - $sql_extra .= sprintf(" AND item.body like '%s' ", + $sql_extra .= sprintf(" AND (item.body like '%s' OR item.title like '%s') ", + dbesc(protect_sprintf('%' . $search . '%')), dbesc(protect_sprintf('%' . $search . '%')) ); } @@ -417,8 +405,8 @@ class Network extends \Zotlabs\Web\Controller { } else { $itemspage = get_pconfig(local_channel(),'system','itemspage'); - \App::set_pager_itemspage(((intval($itemspage)) ? $itemspage : 20)); - $pager_sql = sprintf(" LIMIT %d OFFSET %d ", intval(\App::$pager['itemspage']), intval(\App::$pager['start'])); + App::set_pager_itemspage(((intval($itemspage)) ? $itemspage : 20)); + $pager_sql = sprintf(" LIMIT %d OFFSET %d ", intval(App::$pager['itemspage']), intval(App::$pager['start'])); } // cmin and cmax are both -1 when the affinity tool is disabled @@ -449,7 +437,7 @@ class Network extends \Zotlabs\Web\Controller { $abook_uids = " and abook.abook_channel = " . local_channel() . " "; $uids = " and item.uid = " . local_channel() . " "; - if(get_pconfig(local_channel(),'system','network_list_mode')) + if(feature_enabled(local_channel(), 'network_list_mode')) $page_mode = 'list'; else $page_mode = 'client'; diff --git a/Zotlabs/Module/New_channel.php b/Zotlabs/Module/New_channel.php index 97a46a43e..a9022a03a 100644 --- a/Zotlabs/Module/New_channel.php +++ b/Zotlabs/Module/New_channel.php @@ -142,9 +142,12 @@ class New_channel extends \Zotlabs\Web\Controller { } $limit = account_service_class_fetch(get_account_id(),'total_identities'); - + $canadd = true; if($r && ($limit !== false)) { $channel_usage_message = sprintf( t("You have created %1$.0f of %2$.0f allowed channels."), $r[0]['total'], $limit); + if ($r[0]['total'] >= $limit) { + $canadd = false; + } } else { $channel_usage_message = ''; @@ -168,8 +171,6 @@ class New_channel extends \Zotlabs\Web\Controller { $privacy_role = ((x($_REQUEST,'permissions_role')) ? $_REQUEST['permissions_role'] : "" ); $perm_roles = \Zotlabs\Access\PermissionRoles::roles(); - if((get_account_techlevel() < 4) && $privacy_role !== 'custom') - unset($perm_roles[t('Other')]); $name = array('name', t('Channel name'), ((x($_REQUEST,'name')) ? $_REQUEST['name'] : ''), $name_help, "*"); $nickhub = '@' . \App::get_hostname(); @@ -186,7 +187,8 @@ class New_channel extends \Zotlabs\Web\Controller { '$nickname' => $nickname, '$validate' => t('Validate'), '$submit' => t('Create'), - '$channel_usage_message' => $channel_usage_message + '$channel_usage_message' => $channel_usage_message, + '$canadd' => $canadd )); return $o; diff --git a/Zotlabs/Module/Notes.php b/Zotlabs/Module/Notes.php index e530e6ff4..178a6bce0 100644 --- a/Zotlabs/Module/Notes.php +++ b/Zotlabs/Module/Notes.php @@ -1,13 +1,19 @@ <?php namespace Zotlabs\Module; /** @file */ +use App; +use Zotlabs\Web\Controller; +use Zotlabs\Lib\Apps; -class Notes extends \Zotlabs\Web\Controller { +class Notes extends Controller { - function init() { + function post() { if(! local_channel()) - return; + return EMPTY_STR; + + if(! Apps::system_app_installed(local_channel(), 'Notes')) + return EMPTY_STR; $ret = array('success' => true); if(array_key_exists('note_text',$_REQUEST)) { @@ -24,17 +30,38 @@ class Notes extends \Zotlabs\Web\Controller { } set_pconfig(local_channel(),'notes','text',$body); } - + // push updates to channel clones - + if((argc() > 1) && (argv(1) === 'sync')) { require_once('include/zot.php'); build_sync_packet(); } - + logger('notes saved.', LOGGER_DEBUG); json_return_and_die($ret); - + + } + + function get() { + + if(! local_channel()) + return EMPTY_STR; + + if(! Apps::system_app_installed(local_channel(), 'Notes')) { + //Do not display any associated widgets at this point + App::$pdl = EMPTY_STR; + + $o = '<b>' . t('Notes App') . ' (' . t('Not Installed') . '):</b><br>'; + $o .= t('A simple notes app with a widget (note: notes are not encrypted)'); + return $o; + } + + $w = new \Zotlabs\Widget\Notes; + $arr = ['app' => true]; + + return $w->widget($arr); + } } diff --git a/Zotlabs/Module/Settings/Oauth.php b/Zotlabs/Module/Oauth.php index d6576c6de..27c062df2 100644 --- a/Zotlabs/Module/Settings/Oauth.php +++ b/Zotlabs/Module/Oauth.php @@ -1,27 +1,37 @@ <?php -namespace Zotlabs\Module\Settings; +namespace Zotlabs\Module; +use App; +use Zotlabs\Lib\Apps; +use Zotlabs\Web\Controller; -class Oauth { +class Oauth extends Controller { function post() { + + if(! local_channel()) + return; + + + if(! Apps::system_app_installed(local_channel(), 'OAuth Apps Manager')) + return; if(x($_POST,'remove')){ - check_form_security_token_redirectOnErr('/settings/oauth', 'settings_oauth'); + check_form_security_token_redirectOnErr('/oauth', 'oauth'); $key = $_POST['remove']; q("DELETE FROM tokens WHERE id='%s' AND uid=%d", dbesc($key), local_channel()); - goaway(z_root()."/settings/oauth/"); + goaway(z_root()."/oauth"); return; } - if((argc() > 2) && (argv(2) === 'edit' || argv(2) === 'add') && x($_POST,'submit')) { + if((argc() > 1) && (argv(1) === 'edit' || argv(1) === 'add') && x($_POST,'submit')) { - check_form_security_token_redirectOnErr('/settings/oauth', 'settings_oauth'); + check_form_security_token_redirectOnErr('oauth', 'oauth'); $name = ((x($_POST,'name')) ? escape_tags($_POST['name']) : ''); $key = ((x($_POST,'key')) ? escape_tags($_POST['key']) : ''); @@ -73,17 +83,30 @@ class Oauth { ); } } - goaway(z_root()."/settings/oauth/"); + goaway(z_root()."/oauth"); return; } } function get() { + + if(! local_channel()) + return; + + if(! Apps::system_app_installed(local_channel(), 'OAuth Apps Manager')) { + //Do not display any associated widgets at this point + App::$pdl = ''; + + $o = '<b>' . t('OAuth Apps Manager App') . ' (' . t('Not Installed') . '):</b><br>'; + $o .= t('OAuth authentication tokens for mobile and remote apps'); + return $o; + } + - if((argc() > 2) && (argv(2) === 'add')) { - $tpl = get_markup_template("settings_oauth_edit.tpl"); + if((argc() > 1) && (argv(1) === 'add')) { + $tpl = get_markup_template("oauth_edit.tpl"); $o .= replace_macros($tpl, array( - '$form_security_token' => get_form_security_token("settings_oauth"), + '$form_security_token' => get_form_security_token("oauth"), '$title' => t('Add application'), '$submit' => t('Submit'), '$cancel' => t('Cancel'), @@ -96,9 +119,9 @@ class Oauth { return $o; } - if((argc() > 3) && (argv(2) === 'edit')) { + if((argc() > 2) && (argv(1) === 'edit')) { $r = q("SELECT * FROM clients WHERE client_id='%s' AND uid=%d", - dbesc(argv(3)), + dbesc(argv(2)), local_channel()); if (!count($r)){ @@ -107,9 +130,9 @@ class Oauth { } $app = $r[0]; - $tpl = get_markup_template("settings_oauth_edit.tpl"); + $tpl = get_markup_template("oauth_edit.tpl"); $o .= replace_macros($tpl, array( - '$form_security_token' => get_form_security_token("settings_oauth"), + '$form_security_token' => get_form_security_token("oauth"), '$title' => t('Add application'), '$submit' => t('Update'), '$cancel' => t('Cancel'), @@ -122,13 +145,13 @@ class Oauth { return $o; } - if((argc() > 3) && (argv(2) === 'delete')) { - check_form_security_token_redirectOnErr('/settings/oauth', 'settings_oauth', 't'); + if((argc() > 2) && (argv(1) === 'delete')) { + check_form_security_token_redirectOnErr('/oauth', 'oauth', 't'); $r = q("DELETE FROM clients WHERE client_id='%s' AND uid=%d", - dbesc(argv(3)), + dbesc(argv(2)), local_channel()); - goaway(z_root()."/settings/oauth/"); + goaway(z_root()."/oauth"); return; } @@ -141,11 +164,11 @@ class Oauth { local_channel()); - $tpl = get_markup_template("settings_oauth.tpl"); + $tpl = get_markup_template("oauth.tpl"); $o .= replace_macros($tpl, array( - '$form_security_token' => get_form_security_token("settings_oauth"), + '$form_security_token' => get_form_security_token("oauth"), '$baseurl' => z_root(), - '$title' => t('Connected Apps'), + '$title' => t('Connected OAuth Apps'), '$add' => t('Add application'), '$edit' => t('Edit'), '$delete' => t('Delete'), @@ -158,4 +181,4 @@ class Oauth { } -}
\ No newline at end of file +} diff --git a/Zotlabs/Module/Settings/Oauth2.php b/Zotlabs/Module/Oauth2.php index 985095115..db2687b4c 100644 --- a/Zotlabs/Module/Settings/Oauth2.php +++ b/Zotlabs/Module/Oauth2.php @@ -1,28 +1,46 @@ <?php -namespace Zotlabs\Module\Settings; +namespace Zotlabs\Module; +use App; +use Zotlabs\Lib\Apps; +use Zotlabs\Web\Controller; -class Oauth2 { +class Oauth2 extends Controller { function post() { + + if(! local_channel()) + return; + + if(! Apps::system_app_installed(local_channel(), 'OAuth2 Apps Manager')) + return; if(x($_POST,'remove')){ - check_form_security_token_redirectOnErr('/settings/oauth2', 'settings_oauth2'); - + check_form_security_token_redirectOnErr('oauth2', 'oauth2'); + $name = ((x($_POST,'name')) ? escape_tags(trim($_POST['name'])) : ''); + logger("REMOVE! ".$name." uid: ".local_channel()); $key = $_POST['remove']; - q("DELETE FROM tokens WHERE id='%s' AND uid=%d", - dbesc($key), + q("DELETE FROM oauth_authorization_codes WHERE client_id='%s' AND user_id=%d", + dbesc($name), intval(local_channel()) ); - goaway(z_root()."/settings/oauth2/"); + q("DELETE FROM oauth_access_tokens WHERE client_id='%s' AND user_id=%d", + dbesc($name), + intval(local_channel()) + ); + q("DELETE FROM oauth_refresh_tokens WHERE client_id='%s' AND user_id=%d", + dbesc($name), + intval(local_channel()) + ); + goaway(z_root()."/oauth2"); return; } - if((argc() > 2) && (argv(2) === 'edit' || argv(2) === 'add') && x($_POST,'submit')) { + if((argc() > 1) && (argv(1) === 'edit' || argv(1) === 'add') && x($_POST,'submit')) { - check_form_security_token_redirectOnErr('/settings/oauth2', 'settings_oauth2'); + check_form_security_token_redirectOnErr('oauth2', 'oauth2'); $name = ((x($_POST,'name')) ? escape_tags(trim($_POST['name'])) : ''); $secret = ((x($_POST,'secret')) ? escape_tags(trim($_POST['secret'])) : ''); @@ -45,14 +63,15 @@ class Oauth2 { grant_types = '%s', scope = '%s', user_id = %d - WHERE client_id='%s'", + WHERE client_id='%s' and user_id = %s", dbesc($name), dbesc($secret), dbesc($redirect), dbesc($grant), dbesc($scope), intval(local_channel()), - dbesc($name)); + dbesc($name), + intval(local_channel())); } else { $r = q("INSERT INTO oauth_clients (client_id, client_secret, redirect_uri, grant_types, scope, user_id) VALUES ('%s','%s','%s','%s','%s',%d)", @@ -70,17 +89,29 @@ class Oauth2 { ); } } - goaway(z_root()."/settings/oauth2/"); + goaway(z_root()."/oauth2"); return; } } function get() { + + if(! local_channel()) + return; + + if(! Apps::system_app_installed(local_channel(), 'OAuth2 Apps Manager')) { + //Do not display any associated widgets at this point + App::$pdl = ''; + + $o = '<b>' . t('OAuth2 Apps Manager App') . ' (' . t('Not Installed') . '):</b><br>'; + $o .= t('OAuth2 authenticatication tokens for mobile and remote apps'); + return $o; + } - if((argc() > 2) && (argv(2) === 'add')) { - $tpl = get_markup_template("settings_oauth2_edit.tpl"); + if((argc() > 1) && (argv(1) === 'add')) { + $tpl = get_markup_template("oauth2_edit.tpl"); $o .= replace_macros($tpl, array( - '$form_security_token' => get_form_security_token("settings_oauth2"), + '$form_security_token' => get_form_security_token("oauth2"), '$title' => t('Add OAuth2 application'), '$submit' => t('Submit'), '$cancel' => t('Cancel'), @@ -93,9 +124,9 @@ class Oauth2 { return $o; } - if((argc() > 3) && (argv(2) === 'edit')) { + if((argc() > 2) && (argv(1) === 'edit')) { $r = q("SELECT * FROM oauth_clients WHERE client_id='%s' AND user_id= %d", - dbesc(argv(3)), + dbesc(argv(2)), intval(local_channel()) ); @@ -106,44 +137,57 @@ class Oauth2 { $app = $r[0]; - $tpl = get_markup_template("settings_oauth2_edit.tpl"); + $tpl = get_markup_template("oauth2_edit.tpl"); $o .= replace_macros($tpl, array( - '$form_security_token' => get_form_security_token("settings_oauth2"), + '$form_security_token' => get_form_security_token("oauth2"), '$title' => t('Add application'), '$submit' => t('Update'), '$cancel' => t('Cancel'), '$name' => array('name', t('Name'), $app['client_id'], t('Name of application')), '$secret' => array('secret', t('Consumer Secret'), $app['client_secret'], t('Automatically generated - change if desired. Max length 20')), '$redirect' => array('redirect', t('Redirect'), $app['redirect_uri'], t('Redirect URI - leave blank unless your application specifically requires this')), - '$grant' => array('grant', t('Grant Types'), $app['grant_types'], t('leave blank unless your application sepcifically requires this')), - '$scope' => array('scope', t('Authorization scope'), $app['scope'], t('leave blank unless your application sepcifically requires this')), + '$grant' => array('grant', t('Grant Types'), $app['grant_types'], t('leave blank unless your application specifically requires this')), + '$scope' => array('scope', t('Authorization scope'), $app['scope'], t('leave blank unless your application specifically requires this')), )); return $o; } - if((argc() > 3) && (argv(2) === 'delete')) { - check_form_security_token_redirectOnErr('/settings/oauth2', 'settings_oauth2', 't'); + if((argc() > 2) && (argv(1) === 'delete')) { + check_form_security_token_redirectOnErr('oauth2', 'oauth2', 't'); $r = q("DELETE FROM oauth_clients WHERE client_id = '%s' AND user_id = %d", - dbesc(argv(3)), + dbesc(argv(2)), + intval(local_channel()) + ); + $r = q("DELETE FROM oauth_access_tokens WHERE client_id = '%s' AND user_id = %d", + dbesc(argv(2)), + intval(local_channel()) + ); + $r = q("DELETE FROM oauth_authorization_codes WHERE client_id = '%s' AND user_id = %d", + dbesc(argv(2)), + intval(local_channel()) + ); + $r = q("DELETE FROM oauth_refresh_tokens WHERE client_id = '%s' AND user_id = %d", + dbesc(argv(2)), intval(local_channel()) ); - goaway(z_root()."/settings/oauth2/"); + goaway(z_root()."/oauth2"); return; } $r = q("SELECT oauth_clients.*, oauth_access_tokens.access_token as oauth_token, (oauth_clients.user_id = %d) AS my FROM oauth_clients - LEFT JOIN oauth_access_tokens ON oauth_clients.client_id=oauth_access_tokens.client_id + LEFT JOIN oauth_access_tokens ON oauth_clients.client_id=oauth_access_tokens.client_id AND + oauth_clients.user_id=oauth_access_tokens.user_id WHERE oauth_clients.user_id IN (%d,0)", intval(local_channel()), intval(local_channel()) ); - $tpl = get_markup_template("settings_oauth2.tpl"); + $tpl = get_markup_template("oauth2.tpl"); $o .= replace_macros($tpl, array( - '$form_security_token' => get_form_security_token("settings_oauth2"), + '$form_security_token' => get_form_security_token("oauth2"), '$baseurl' => z_root(), '$title' => t('Connected OAuth2 Apps'), '$add' => t('Add application'), diff --git a/Zotlabs/Module/Oauthinfo.php b/Zotlabs/Module/Oauthinfo.php index 2d10913c4..f380cec97 100644 --- a/Zotlabs/Module/Oauthinfo.php +++ b/Zotlabs/Module/Oauthinfo.php @@ -5,19 +5,17 @@ namespace Zotlabs\Module; class Oauthinfo extends \Zotlabs\Web\Controller { - function init() { $ret = [ 'issuer' => z_root(), 'authorization_endpoint' => z_root() . '/authorize', 'token_endpoint' => z_root() . '/token', + 'userinfo_endpoint' => z_root() . '/userinfo', + 'scopes_supported' => [ 'openid', 'profile', 'email' ], 'response_types_supported' => [ 'code', 'token', 'id_token', 'code id_token', 'token id_token' ] ]; - json_return_and_die($ret); } - - }
\ No newline at end of file diff --git a/Zotlabs/Module/Owa.php b/Zotlabs/Module/Owa.php index da26748b3..4a488086f 100644 --- a/Zotlabs/Module/Owa.php +++ b/Zotlabs/Module/Owa.php @@ -45,7 +45,7 @@ class Owa extends \Zotlabs\Web\Controller { } if($r) { foreach($r as $hubloc) { - $verified = \Zotlabs\Web\HTTPSig::verify('',$hubloc['xchan_pubkey']); + $verified = \Zotlabs\Web\HTTPSig::verify(file_get_contents('php://input'),$hubloc['xchan_pubkey']); if($verified && $verified['header_signed'] && $verified['header_valid']) { logger('OWA header: ' . print_r($verified,true),LOGGER_DATA); logger('OWA success: ' . $hubloc['hubloc_addr'],LOGGER_DATA); diff --git a/Zotlabs/Module/Pconfig.php b/Zotlabs/Module/Pconfig.php index b6264bddc..7c82bac7d 100644 --- a/Zotlabs/Module/Pconfig.php +++ b/Zotlabs/Module/Pconfig.php @@ -13,14 +13,15 @@ class Pconfig extends \Zotlabs\Web\Controller { return; - if($_SESSION['delegate']) - return; + if($_SESSION['delegate']) + return; check_form_security_token_redirectOnErr('/pconfig', 'pconfig'); $cat = trim(escape_tags($_POST['cat'])); $k = trim(escape_tags($_POST['k'])); $v = trim($_POST['v']); + $aj = intval($_POST['aj']); if(in_array(argv(2),$this->disallowed_pconfig())) { notice( t('This setting requires special processing and editing has been blocked.') . EOL); @@ -33,9 +34,12 @@ class Pconfig extends \Zotlabs\Web\Controller { set_pconfig(local_channel(),$cat,$k,$v); build_sync_packet(); - - goaway(z_root() . '/pconfig/' . $cat . '/' . $k); - + + if($aj) + killme(); + else + goaway(z_root() . '/pconfig/' . $cat . '/' . $k); + } diff --git a/Zotlabs/Module/Pdledit.php b/Zotlabs/Module/Pdledit.php index 9b86b599b..5cedb29a8 100644 --- a/Zotlabs/Module/Pdledit.php +++ b/Zotlabs/Module/Pdledit.php @@ -1,15 +1,20 @@ <?php namespace Zotlabs\Module; +use App; +use Zotlabs\Web\Controller; +use Zotlabs\Lib\Apps; -class Pdledit extends \Zotlabs\Web\Controller { +class Pdledit extends Controller { function post() { if(! local_channel()) return; - if(! $_REQUEST['module']) + + if(! Apps::system_app_installed(local_channel(), 'PDL Editor')) return; - if(! feature_enabled(local_channel(),'advanced_theming')) + + if(! $_REQUEST['module']) return; if(! trim($_REQUEST['content'])) { @@ -30,9 +35,13 @@ class Pdledit extends \Zotlabs\Web\Controller { return; } - if(! feature_enabled(local_channel(),'advanced_theming')) { - notice( t('Feature disabled.') . EOL); - return; + if(! Apps::system_app_installed(local_channel(), 'PDL Editor')) { + //Do not display any associated widgets at this point + App::$pdl = ''; + + $o = '<b>' . t('PDL Editor App') . ' (' . t('Not Installed') . '):</b><br>'; + $o .= t('Provides the ability to edit system page layouts'); + return $o; } if(argc() > 2 && argv(2) === 'reset') { diff --git a/Zotlabs/Module/Settings/Permcats.php b/Zotlabs/Module/Permcats.php index 40641c3f2..75ac2ac87 100644 --- a/Zotlabs/Module/Settings/Permcats.php +++ b/Zotlabs/Module/Permcats.php @@ -1,26 +1,31 @@ <?php -namespace Zotlabs\Module\Settings; +namespace Zotlabs\Module; +use App; +use Zotlabs\Web\Controller; +use Zotlabs\Lib\Apps; - -class Permcats { +class Permcats extends Controller { function post() { if(! local_channel()) return; - $channel = \App::get_channel(); + if(! Apps::system_app_installed(local_channel(), 'Permission Categories')) + return; + + $channel = App::get_channel(); - check_form_security_token_redirectOnErr('/settings/permcats', 'settings_permcats'); + check_form_security_token_redirectOnErr('/permcats', 'permcats'); $all_perms = \Zotlabs\Access\Permissions::Perms(); $name = escape_tags(trim($_POST['name'])); if(! $name) { - notice( t('Permission Name is required.') . EOL); + notice( t('Permission category name is required.') . EOL); return; } @@ -50,13 +55,21 @@ class Permcats { if(! local_channel()) return; - $channel = \App::get_channel(); + if(! Apps::system_app_installed(local_channel(), 'Permission Categories')) { + //Do not display any associated widgets at this point + App::$pdl = ''; + + $o = '<b>' . t('Permission Categories App') . ' (' . t('Not Installed') . '):</b><br>'; + $o .= t('Create custom connection permission limits'); + return $o; + } + $channel = App::get_channel(); - if(argc() > 2) - $name = hex2bin(argv(2)); + if(argc() > 1) + $name = hex2bin(argv(1)); - if(argc() > 3 && argv(3) === 'drop') { + if(argc() > 2 && argv(2) === 'drop') { \Zotlabs\Lib\Permcat::delete(local_channel(),$name); build_sync_packet(); json_return_and_die([ 'success' => true ]); @@ -93,9 +106,9 @@ class Permcats { - $tpl = get_markup_template("settings_permcats.tpl"); + $tpl = get_markup_template("permcats.tpl"); $o .= replace_macros($tpl, array( - '$form_security_token' => get_form_security_token("settings_permcats"), + '$form_security_token' => get_form_security_token("permcats"), '$title' => t('Permission Categories'), '$desc' => $desc, '$desc2' => $desc2, @@ -104,7 +117,7 @@ class Permcats { '$atoken' => $atoken, '$url1' => z_root() . '/channel/' . $channel['channel_address'], '$url2' => z_root() . '/photos/' . $channel['channel_address'], - '$name' => array('name', t('Permission Name') . ' <span class="required">*</span>', (($name) ? $name : ''), ''), + '$name' => array('name', t('Permission category name') . ' <span class="required">*</span>', (($name) ? $name : ''), ''), '$me' => t('My Settings'), '$perms' => $perms, '$inherited' => t('inherited'), diff --git a/Zotlabs/Module/Photos.php b/Zotlabs/Module/Photos.php index 489bffc4a..f97f31ff7 100644 --- a/Zotlabs/Module/Photos.php +++ b/Zotlabs/Module/Photos.php @@ -620,10 +620,7 @@ class Photos extends \Zotlabs\Web\Controller { $o .= "<script> var profile_uid = " . \App::$profile['profile_uid'] . "; var netargs = '?f='; var profile_page = " . \App::$pager['page'] . "; </script>\r\n"; - // tabs - $_is_owner = (local_channel() && (local_channel() == $owner_uid)); - //$o .= profile_tabs($a,$_is_owner, \App::$data['channel']['channel_address']); /** * Display upload form diff --git a/Zotlabs/Module/Ping.php b/Zotlabs/Module/Ping.php index baefe62ec..14627f56e 100644 --- a/Zotlabs/Module/Ping.php +++ b/Zotlabs/Module/Ping.php @@ -2,6 +2,8 @@ namespace Zotlabs\Module; +use Zotlabs\Lib\Apps; + require_once('include/bbcode.php'); /** @@ -147,9 +149,12 @@ class Ping extends \Zotlabs\Web\Controller { if(! ($vnotify & VNOTIFY_LIKE)) $sql_extra = " AND verb NOT IN ('" . dbesc(ACTIVITY_LIKE) . "', '" . dbesc(ACTIVITY_DISLIKE) . "') "; - $discover_tab_on = can_view_public_stream(); - - $notify_pubs = ((local_channel()) ? ($vnotify & VNOTIFY_PUBS) && $discover_tab_on : $discover_tab_on); + if(local_channel()) { + $notify_pubs = ($vnotify & VNOTIFY_PUBS) && can_view_public_stream() && Apps::system_app_installed(local_channel(), 'Public Stream'); + } + else { + $notify_pubs = can_view_public_stream(); + } if($notify_pubs) { $sys = get_sys_channel(); diff --git a/Zotlabs/Module/Poke.php b/Zotlabs/Module/Poke.php index d13ec5ced..1f1edfa18 100644 --- a/Zotlabs/Module/Poke.php +++ b/Zotlabs/Module/Poke.php @@ -1,6 +1,10 @@ <?php namespace Zotlabs\Module; /** @file */ +use App; +use Zotlabs\Lib\Apps; +use Zotlabs\Web\Controller; + /** * * Poke, prod, finger, or otherwise do unspeakable things to somebody - who must be a connection in your address book @@ -18,15 +22,19 @@ namespace Zotlabs\Module; /** @file */ require_once('include/items.php'); -class Poke extends \Zotlabs\Web\Controller { +class Poke extends Controller { function init() { if(! local_channel()) return; + + if(! Apps::system_app_installed(local_channel(), 'Poke')) { + return; + } $uid = local_channel(); - $channel = \App::get_channel(); + $channel = App::get_channel(); $verb = notags(trim($_REQUEST['verb'])); @@ -150,6 +158,15 @@ class Poke extends \Zotlabs\Web\Controller { return; } + if(! Apps::system_app_installed(local_channel(), 'Poke')) { + //Do not display any associated widgets at this point + App::$pdl = ''; + + $o = '<b>' . t('Poke App') . ' (' . t('Not Installed') . '):</b><br>'; + $o .= t('Poke somebody in your addressbook'); + return $o; + } + nav_set_selected('Poke'); $name = ''; diff --git a/Zotlabs/Module/Probe.php b/Zotlabs/Module/Probe.php index 2e65f107c..d338b08ea 100644 --- a/Zotlabs/Module/Probe.php +++ b/Zotlabs/Module/Probe.php @@ -1,16 +1,29 @@ <?php namespace Zotlabs\Module; -require_once('include/zot.php'); +use App; +use Zotlabs\Lib\Apps; +require_once('include/zot.php'); class Probe extends \Zotlabs\Web\Controller { function get() { + if(local_channel()) { + if(! Apps::system_app_installed(local_channel(), 'Remote Diagnostics')) { + //Do not display any associated widgets at this point + App::$pdl = ''; + + $o = '<b>' . t('Remote Diagnostics App') . ' (' . t('Not Installed') . '):</b><br>'; + $o .= t('Perform diagnostics on remote channels'); + return $o; + } + } + nav_set_selected('Remote Diagnostics'); - $o .= '<h3>Probe Diagnostic</h3>'; + $o .= '<h3>Remote Diagnostics</h3>'; $o .= '<form action="probe" method="get">'; $o .= 'Lookup address: <input type="text" style="width: 250px;" name="addr" value="' . $_GET['addr'] .'" />'; @@ -19,7 +32,7 @@ class Probe extends \Zotlabs\Web\Controller { $o .= '<br /><br />'; if(x($_GET,'addr')) { - $channel = \App::get_channel(); + $channel = App::get_channel(); $addr = trim($_GET['addr']); $do_import = ((intval($_GET['import']) && is_site_admin()) ? true : false); @@ -27,12 +40,11 @@ class Probe extends \Zotlabs\Web\Controller { $o .= '<pre>'; if(! $j['success']) { - $o .= sprintf( t('Fetching URL returns error: %1$s'),$res['error'] . "\r\n\r\n"); $o .= "<strong>https connection failed. Trying again with auto failover to http.</strong>\r\n\r\n"; $j = \Zotlabs\Zot\Finger::run($addr,$channel,true); - if(! $j['success']) - $o .= sprintf( t('Fetching URL returns error: %1$s'),$res['error'] . "\r\n\r\n"); - + if(! $j['success']) { + return $o; + } } if($do_import && $j) $x = import_xchan($j); diff --git a/Zotlabs/Module/Profiles.php b/Zotlabs/Module/Profiles.php index 202ee462a..de4075ba9 100644 --- a/Zotlabs/Module/Profiles.php +++ b/Zotlabs/Module/Profiles.php @@ -8,8 +8,8 @@ require_once('include/selectors.php'); class Profiles extends \Zotlabs\Web\Controller { function init() { - - nav_set_selected('Profiles'); + + nav_set_selected('Profiles', 'settings/profiles'); if(! local_channel()) { return; diff --git a/Zotlabs/Module/Pubstream.php b/Zotlabs/Module/Pubstream.php index 7b80a3978..94df29984 100644 --- a/Zotlabs/Module/Pubstream.php +++ b/Zotlabs/Module/Pubstream.php @@ -1,6 +1,9 @@ <?php namespace Zotlabs\Module; +use App; +use Zotlabs\Lib\Apps; + require_once('include/conversation.php'); require_once('include/acl_selectors.php'); @@ -9,6 +12,17 @@ class Pubstream extends \Zotlabs\Web\Controller { function get($update = 0, $load = false) { + if(local_channel()) { + if(! Apps::system_app_installed(local_channel(), 'Public Stream')) { + //Do not display any associated widgets at this point + App::$pdl = ''; + + $o = '<b>' . t('Public Stream App') . ' (' . t('Not Installed') . '):</b><br>'; + $o .= t('The unmoderated public stream of this hub'); + return $o; + } + } + if($load) $_SESSION['loadtime'] = datetime_convert(); @@ -81,7 +95,7 @@ class Pubstream extends \Zotlabs\Web\Controller { ); $o = '<div id="jot-popup">'; - $o .= status_editor($a,$x); + $o .= status_editor($a,$x,false,'Pubstream'); $o .= '</div>'; } diff --git a/Zotlabs/Module/Randprof.php b/Zotlabs/Module/Randprof.php index 94ec095cb..c38b07ead 100644 --- a/Zotlabs/Module/Randprof.php +++ b/Zotlabs/Module/Randprof.php @@ -1,11 +1,17 @@ <?php namespace Zotlabs\Module; - +use App; +use Zotlabs\Lib\Apps; class Randprof extends \Zotlabs\Web\Controller { function init() { + if(local_channel()) { + if(! Apps::system_app_installed(local_channel(), 'Random Channel')) + return; + } + $x = random_profile(); if($x) goaway(chanlink_hash($x)); @@ -13,5 +19,19 @@ class Randprof extends \Zotlabs\Web\Controller { /** FIXME this doesn't work at the moment as a fallback */ goaway(z_root() . '/profile'); } + + function get() { + if(local_channel()) { + if(! Apps::system_app_installed(local_channel(), 'Random Channel')) { + //Do not display any associated widgets at this point + App::$pdl = ''; + + $o = '<b>' . t('Random Channel App') . ' (' . t('Not Installed') . '):</b><br>'; + $o .= t('Visit a random channel in the $Projectname network'); + return $o; + } + } + + } } diff --git a/Zotlabs/Module/Register.php b/Zotlabs/Module/Register.php index 3dded19c7..f9d81be0c 100644 --- a/Zotlabs/Module/Register.php +++ b/Zotlabs/Module/Register.php @@ -227,11 +227,6 @@ class Register extends \Zotlabs\Web\Controller { $perm_roles = \Zotlabs\Access\PermissionRoles::roles(); - // A new account will not have a techlevel, but accounts can also be created by the administrator. - - if((get_account_techlevel() < 4) && $privacy_role !== 'custom') - unset($perm_roles[t('Other')]); - // Configurable terms of service link $tosurl = get_config('system','tos_url'); diff --git a/Zotlabs/Module/Rpost.php b/Zotlabs/Module/Rpost.php index 86ee296ec..f03dae2bf 100644 --- a/Zotlabs/Module/Rpost.php +++ b/Zotlabs/Module/Rpost.php @@ -138,7 +138,7 @@ class Rpost extends \Zotlabs\Web\Controller { 'jotnets' => true ); - $editor = status_editor($a,$x); + $editor = status_editor($a,$x,false,'Rpost'); $o .= replace_macros(get_markup_template('edpost_head.tpl'), array( '$title' => t('Edit post'), diff --git a/Zotlabs/Module/Settings/Account.php b/Zotlabs/Module/Settings/Account.php index 9643c5958..b40f516ca 100644 --- a/Zotlabs/Module/Settings/Account.php +++ b/Zotlabs/Module/Settings/Account.php @@ -12,7 +12,6 @@ class Account { $errs = array(); $email = ((x($_POST,'email')) ? trim(notags($_POST['email'])) : ''); - $techlevel = ((array_key_exists('techlevel',$_POST)) ? intval($_POST['techlevel']) : 0); $account = \App::get_account(); if($email != $account['account_email']) { @@ -32,13 +31,6 @@ class Account { $errs[] = t('System failure storing new email. Please try again.'); } } - if($techlevel != $account['account_level']) { - $r = q("update account set account_level = %d where account_id = %d", - intval($techlevel), - intval($account['account_id']) - ); - info( t('Technical skill level updated') . EOL); - } if($errs) { foreach($errs as $err) @@ -101,11 +93,6 @@ class Account { $email = \App::$account['account_email']; - $techlevels = \Zotlabs\Lib\Techlevels::levels(); - - $def_techlevel = \App::$account['account_level']; - $techlock = get_config('system','techlevel_lock'); - $tpl = get_markup_template("settings_account.tpl"); $o .= replace_macros($tpl, array( '$form_security_token' => get_form_security_token("settings_account"), @@ -113,8 +100,6 @@ class Account { '$origpass' => array('origpass', t('Current Password'), ' ',''), '$password1'=> array('npassword', t('Enter New Password'), '', ''), '$password2'=> array('confirm', t('Confirm New Password'), '', t('Leave password fields blank unless changing')), - '$techlevel' => [ 'techlevel', t('Your technical skill level'), $def_techlevel, t('Used to provide a member experience and additional features consistent with your comfort level'), $techlevels ], - '$techlock' => $techlock, '$submit' => t('Submit'), '$email' => array('email', t('Email Address:'), $email, ''), '$removeme' => t('Remove Account'), diff --git a/Zotlabs/Module/Settings/Calendar.php b/Zotlabs/Module/Settings/Calendar.php new file mode 100644 index 000000000..a27bf0fa5 --- /dev/null +++ b/Zotlabs/Module/Settings/Calendar.php @@ -0,0 +1,47 @@ +<?php + +namespace Zotlabs\Module\Settings; + + +class Calendar { + + function post() { + + $module = substr(strrchr(strtolower(static::class), '\\'), 1); + + check_form_security_token_redirectOnErr('/settings/' . $module, 'settings_' . $module); + + $features = get_module_features($module); + + process_module_features_post(local_channel(), $features, $_POST); + + build_sync_packet(); + + if($_POST['rpath']) + goaway($_POST['rpath']); + + return; + } + + function get() { + + $module = substr(strrchr(strtolower(static::class), '\\'), 1); + + $features = get_module_features($module); + $rpath = (($_GET['rpath']) ? $_GET['rpath'] : ''); + + $tpl = get_markup_template("settings_module.tpl"); + + $o .= replace_macros($tpl, array( + '$rpath' => $rpath, + '$action_url' => 'settings/' . $module, + '$form_security_token' => get_form_security_token('settings_' . $module), + '$title' => t('CalDAV Settings'), + '$features' => process_module_features_get(local_channel(), $features), + '$submit' => t('Submit') + )); + + return $o; + } + +} diff --git a/Zotlabs/Module/Settings/Channel.php b/Zotlabs/Module/Settings/Channel.php index 3a6e03588..b0115d352 100644 --- a/Zotlabs/Module/Settings/Channel.php +++ b/Zotlabs/Module/Settings/Channel.php @@ -2,6 +2,8 @@ namespace Zotlabs\Module\Settings; +use Zotlabs\Lib\Apps; + require_once('include/selectors.php'); @@ -63,7 +65,7 @@ class Channel { } $hide_presence = 1 - (intval($role_permissions['online'])); if($role_permissions['default_collection']) { - $r = q("select hash from groups where uid = %d and gname = '%s' limit 1", + $r = q("select hash from pgrp where uid = %d and gname = '%s' limit 1", intval(local_channel()), dbesc( t('Friends') ) ); @@ -71,7 +73,7 @@ class Channel { require_once('include/group.php'); group_add(local_channel(), t('Friends')); group_add_member(local_channel(),t('Friends'),$channel['channel_hash']); - $r = q("select hash from groups where uid = %d and gname = '%s' limit 1", + $r = q("select hash from pgrp where uid = %d and gname = '%s' limit 1", intval(local_channel()), dbesc( t('Friends') ) ); @@ -133,8 +135,6 @@ class Channel { $photo_path = ((x($_POST,'photo_path')) ? escape_tags(trim($_POST['photo_path'])) : ''); $attach_path = ((x($_POST,'attach_path')) ? escape_tags(trim($_POST['attach_path'])) : ''); - $channel_menu = ((x($_POST['channel_menu'])) ? htmlspecialchars_decode(trim($_POST['channel_menu']),ENT_QUOTES) : ''); - $expire_items = ((x($_POST,'expire_items')) ? intval($_POST['expire_items']) : 0); $expire_starred = ((x($_POST,'expire_starred')) ? intval($_POST['expire_starred']) : 0); $expire_photos = ((x($_POST,'expire_photos'))? intval($_POST['expire_photos']) : 0); @@ -154,10 +154,7 @@ class Channel { $adult = (($_POST['adult'] == 1) ? 1 : 0); $defpermcat = ((x($_POST,'defpermcat')) ? notags(trim($_POST['defpermcat'])) : 'default'); - $cal_first_day = (((x($_POST,'first_day')) && (intval($_POST['first_day']) == 1)) ? 1: 0); $mailhost = ((array_key_exists('mailhost',$_POST)) ? notags(trim($_POST['mailhost'])) : ''); - $profile_assign = ((x($_POST,'profile_assign')) ? notags(trim($_POST['profile_assign'])) : ''); - $pageflags = $channel['channel_pageflags']; $existing_adult = (($pageflags & PAGE_ADULT) ? 1 : 0); @@ -245,16 +242,13 @@ class Channel { set_pconfig(local_channel(),'system','post_joingroup', $post_joingroup); set_pconfig(local_channel(),'system','post_profilechange', $post_profilechange); set_pconfig(local_channel(),'system','blocktags',$blocktags); - set_pconfig(local_channel(),'system','channel_menu',$channel_menu); 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','evdays',$evdays); set_pconfig(local_channel(),'system','photo_path',$photo_path); set_pconfig(local_channel(),'system','attach_path',$attach_path); - set_pconfig(local_channel(),'system','cal_first_day',$cal_first_day); set_pconfig(local_channel(),'system','default_permcat',$defpermcat); set_pconfig(local_channel(),'system','email_notify_host',$mailhost); - set_pconfig(local_channel(),'system','profile_assign',$profile_assign); set_pconfig(local_channel(),'system','autoperms',$autoperms); $r = q("update channel set channel_name = '%s', channel_pageflags = %d, channel_timezone = '%s', channel_location = '%s', channel_notifyflags = %d, channel_max_anon_mail = %d, channel_max_friend_req = %d, channel_expire_days = %d $set_perms where channel_id = %d", @@ -434,7 +428,7 @@ class Channel { '$nickname' => (($intl_nickname === $webbie) ? $webbie : $intl_nickname . ' (' . $webbie . ')'), '$subdir' => $subdir, '$davdesc' => t('Your files/photos are accessible via WebDAV at'), - '$davpath' => ((get_account_techlevel() > 3) ? z_root() . '/dav/' . $nickname : ''), + '$davpath' => z_root() . '/dav/' . $nickname, '$basepath' => \App::get_hostname() )); @@ -460,18 +454,6 @@ class Channel { require_once('include/group.php'); $group_select = mini_group_select(local_channel(),$channel['channel_default_group']); - require_once('include/menu.php'); - $m1 = menu_list(local_channel()); - $menu = false; - if($m1) { - $menu = array(); - $current = get_pconfig(local_channel(),'system','channel_menu'); - $menu[] = array('name' => '', 'selected' => ((! $current) ? true : false)); - foreach($m1 as $m) { - $menu[] = array('name' => htmlspecialchars($m['menu_name'],ENT_COMPAT,'UTF-8'), 'selected' => (($m['menu_name'] === $current) ? ' selected="selected" ' : false)); - } - } - $evdays = get_pconfig(local_channel(),'system','evdays'); if(! $evdays) $evdays = 3; @@ -492,18 +474,13 @@ class Channel { $permissions_set = (($permissions_role != 'custom') ? true : false); $perm_roles = \Zotlabs\Access\PermissionRoles::roles(); - if((get_account_techlevel() < 4) && $permissions_role !== 'custom') - unset($perm_roles[t('Other')]); - - - $vnotify = get_pconfig(local_channel(),'system','vnotify'); $always_show_in_notices = get_pconfig(local_channel(),'system','always_show_in_notices'); if($vnotify === false) $vnotify = (-1); - $plugin = [ 'basic' => '', 'security' => '', 'notify' => '', 'misc' => '' ]; + $plugin = [ 'basic' => '', 'security' => '', 'notify' => '' ]; call_hooks('channel_settings',$plugin); $disable_discover_tab = intval(get_config('system','disable_discover_tab',1)) == 1; @@ -548,8 +525,6 @@ class Channel { '$permissions' => t('Default Privacy Group'), '$permdesc' => t("\x28click to open/close\x29"), '$aclselect' => populate_acl($perm_defaults, false, \Zotlabs\Lib\PermissionDescription::fromDescription(t('Use my default audience setting for the type of object published'))), - '$profseltxt' => t('Profile to assign new connections'), - '$profselect' => ((feature_enabled(local_channel(),'multi_profiles')) ? contact_profile_assign(get_pconfig(local_channel(),'system','profile_assign','')) : ''), '$allow_cid' => acl2json($perm_defaults['allow_cid']), '$allow_gid' => acl2json($perm_defaults['allow_gid']), @@ -558,8 +533,8 @@ class Channel { '$suggestme' => $suggestme, '$group_select' => $group_select, '$role' => array('permissions_role' , t('Channel role and privacy'), $permissions_role, '', $perm_roles), - '$defpermcat' => [ 'defpermcat', t('Default Permissions Group'), $default_permcat, '', $permcats ], - '$permcat_enable' => feature_enabled(local_channel(),'permcats'), + '$defpermcat' => [ 'defpermcat', t('Default permissions category'), $default_permcat, '', $permcats ], + '$permcat_enable' => Apps::system_app_installed(local_channel(), 'Permission Categories'), '$profile_in_dir' => $profile_in_dir, '$hide_friends' => $hide_friends, '$hide_wall' => $hide_wall, @@ -587,7 +562,7 @@ class Channel { '$lbl_vnot' => t('Show visual notifications including:'), - '$vnotify1' => array('vnotify1', t('Unseen grid activity'), ($vnotify & VNOTIFY_NETWORK), VNOTIFY_NETWORK, '', $yes_no), + '$vnotify1' => array('vnotify1', t('Unseen stream activity'), ($vnotify & VNOTIFY_NETWORK), VNOTIFY_NETWORK, '', $yes_no), '$vnotify2' => array('vnotify2', t('Unseen channel activity'), ($vnotify & VNOTIFY_CHANNEL), VNOTIFY_CHANNEL, '', $yes_no), '$vnotify3' => array('vnotify3', t('Unseen private messages'), ($vnotify & VNOTIFY_MAIL), VNOTIFY_MAIL, t('Recommended'), $yes_no), '$vnotify4' => array('vnotify4', t('Upcoming events'), ($vnotify & VNOTIFY_EVENT), VNOTIFY_EVENT, '', $yes_no), @@ -599,7 +574,7 @@ class Channel { '$vnotify10' => array('vnotify10', t('New connections'), ($vnotify & VNOTIFY_INTRO), VNOTIFY_INTRO, t('Recommended'), $yes_no), '$vnotify11' => ((is_site_admin()) ? array('vnotify11', t('System Registrations'), ($vnotify & VNOTIFY_REGISTER), VNOTIFY_REGISTER, '', $yes_no) : array()), '$vnotify12' => array('vnotify12', t('Unseen shared files'), ($vnotify & VNOTIFY_FILES), VNOTIFY_FILES, '', $yes_no), - '$vnotify13' => (($disable_discover_tab && !$site_firehose) ? array() : array('vnotify13', t('Unseen public activity'), ($vnotify & VNOTIFY_PUBS), VNOTIFY_PUBS, '', $yes_no)), + '$vnotify13' => ((($disable_discover_tab && !$site_firehose) || !Apps::system_app_installed(local_channel(), 'Public Stream')) ? array() : array('vnotify13', t('Unseen public stream activity'), ($vnotify & VNOTIFY_PUBS), VNOTIFY_PUBS, '', $yes_no)), '$vnotify14' => array('vnotify14', t('Unseen likes and dislikes'), ($vnotify & VNOTIFY_LIKE), VNOTIFY_LIKE, '', $yes_no), '$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()) ], @@ -609,7 +584,6 @@ class Channel { '$basic_addon' => $plugin['basic'], '$sec_addon' => $plugin['security'], '$notify_addon' => $plugin['notify'], - '$misc_addon' => $plugin['misc'], '$h_advn' => t('Advanced Account/Page Type Settings'), '$h_descadvn' => t('Change the behaviour of this account for special situations'), @@ -617,12 +591,8 @@ class Channel { '$lbl_misc' => t('Miscellaneous Settings'), '$photo_path' => array('photo_path', t('Default photo upload folder'), get_pconfig(local_channel(),'system','photo_path'), t('%Y - current year, %m - current month')), '$attach_path' => array('attach_path', t('Default file upload folder'), get_pconfig(local_channel(),'system','attach_path'), t('%Y - current year, %m - current month')), - '$menus' => $menu, - '$menu_desc' => t('Personal menu to display in your channel pages'), '$removeme' => t('Remove Channel'), '$removechannel' => t('Remove this channel.'), - '$firefoxshare' => t('Firefox Share $Projectname provider'), - '$cal_first_day' => array('first_day', t('Start calendar week on Monday'), ((get_pconfig(local_channel(),'system','cal_first_day')) ? 1 : ''), '', $yes_no), )); call_hooks('settings_form',$o); diff --git a/Zotlabs/Module/Settings/Channel_home.php b/Zotlabs/Module/Settings/Channel_home.php new file mode 100644 index 000000000..b6ecf4ff1 --- /dev/null +++ b/Zotlabs/Module/Settings/Channel_home.php @@ -0,0 +1,95 @@ +<?php + +namespace Zotlabs\Module\Settings; + +require_once('include/menu.php'); + +class Channel_home { + + function post() { + + $module = substr(strrchr(strtolower(static::class), '\\'), 1); + + check_form_security_token_redirectOnErr('/settings/' . $module, 'settings_' . $module); + + $features = get_module_features($module); + + process_module_features_post(local_channel(), $features, $_POST); + + $channel_divmore_height = ((x($_POST,'channel_divmore_height')) ? intval($_POST['channel_divmore_height']) : 400); + if($channel_divmore_height < 50) + $channel_divmore_height = 50; + set_pconfig(local_channel(),'system','channel_divmore_height', $channel_divmore_height); + + $channel_menu = ((x($_POST['channel_menu'])) ? htmlspecialchars_decode(trim($_POST['channel_menu']),ENT_QUOTES) : ''); + set_pconfig(local_channel(),'system','channel_menu',$channel_menu); + + build_sync_packet(); + + if($_POST['rpath']) + goaway($_POST['rpath']); + + return; + } + + function get() { + + $module = substr(strrchr(strtolower(static::class), '\\'), 1); + + $features = get_module_features($module); + $rpath = (($_GET['rpath']) ? $_GET['rpath'] : ''); + + $channel_divmore_height = [ + 'channel_divmore_height', + t('Max height of content (in pixels)'), + ((get_pconfig(local_channel(),'system','channel_divmore_height')) ? get_pconfig(local_channel(),'system','channel_divmore_height') : 400), + t('Click to expand content exceeding this height') + ]; + + $menus = menu_list(local_channel()); + if($menus) { + $current = get_pconfig(local_channel(),'system','channel_menu'); + $menu[] = ''; + foreach($menus as $m) { + $menu[$m['menu_name']] = htmlspecialchars($m['menu_name'],ENT_COMPAT,'UTF-8'); + } + + $menu_select = [ + 'channel_menu', + t('Personal menu to display in your channel pages'), + $current, + '', + $menu + ]; + } + + $extra_settings_html = replace_macros(get_markup_template('field_input.tpl'), + [ + '$field' => $channel_divmore_height + ] + ); + + if($menu) { + $extra_settings_html .= replace_macros(get_markup_template('field_select.tpl'), + [ + '$field' => $menu_select + ] + ); + } + + $tpl = get_markup_template("settings_module.tpl"); + + $o .= replace_macros($tpl, array( + '$rpath' => $rpath, + '$action_url' => 'settings/' . $module, + '$form_security_token' => get_form_security_token('settings_' . $module), + '$title' => t('Channel Home Settings'), + '$features' => process_module_features_get(local_channel(), $features), + '$extra_settings_html' => $extra_settings_html, + '$submit' => t('Submit') + )); + + return $o; + } + +} diff --git a/Zotlabs/Module/Settings/Connections.php b/Zotlabs/Module/Settings/Connections.php new file mode 100644 index 000000000..cac357791 --- /dev/null +++ b/Zotlabs/Module/Settings/Connections.php @@ -0,0 +1,47 @@ +<?php + +namespace Zotlabs\Module\Settings; + + +class Connections { + + function post() { + + $module = substr(strrchr(strtolower(static::class), '\\'), 1); + + check_form_security_token_redirectOnErr('/settings/' . $module, 'settings_' . $module); + + $features = get_module_features($module); + + process_module_features_post(local_channel(), $features, $_POST); + + build_sync_packet(); + + if($_POST['rpath']) + goaway($_POST['rpath']); + + return; + } + + function get() { + + $module = substr(strrchr(strtolower(static::class), '\\'), 1); + + $features = get_module_features($module); + $rpath = (($_GET['rpath']) ? $_GET['rpath'] : ''); + + $tpl = get_markup_template("settings_module.tpl"); + + $o .= replace_macros($tpl, array( + '$rpath' => $rpath, + '$action_url' => 'settings/' . $module, + '$form_security_token' => get_form_security_token('settings_' . $module), + '$title' => t('Connections Settings'), + '$features' => process_module_features_get(local_channel(), $features), + '$submit' => t('Submit') + )); + + return $o; + } + +} diff --git a/Zotlabs/Module/Settings/Conversation.php b/Zotlabs/Module/Settings/Conversation.php new file mode 100644 index 000000000..43e59a3c2 --- /dev/null +++ b/Zotlabs/Module/Settings/Conversation.php @@ -0,0 +1,60 @@ +<?php + +namespace Zotlabs\Module\Settings; + + +class Conversation { + + function post() { + + $module = substr(strrchr(strtolower(static::class), '\\'), 1); + + check_form_security_token_redirectOnErr('/settings/' . $module, 'settings_' . $module); + + $features = get_module_features($module); + + process_module_features_post(local_channel(), $features, $_POST); + + build_sync_packet(); + + if($_POST['aj']) { + if($_POST['auto_update'] == 1) + info(t('Settings saved.') . EOL); + else + info(t('Settings saved. Reload page please.') . EOL); + + killme(); + } + else { + return; + } + } + + function get() { + + $aj = ((isset($_GET['aj'])) ? true : false); + + $module = substr(strrchr(strtolower(static::class), '\\'), 1); + + $features = get_module_features($module); + + $tpl = (($aj) ? get_markup_template("settings_module_ajax.tpl") : get_markup_template("settings_module.tpl")); + + $o .= replace_macros($tpl, array( + '$action_url' => 'settings/' . $module, + '$form_security_token' => get_form_security_token('settings_' . $module), + '$title' => t('Conversation Settings'), + '$features' => process_module_features_get(local_channel(), $features), + '$submit' => t('Submit') + )); + + if($aj) { + echo $o; + killme(); + } + else { + return $o; + } + } + +} diff --git a/Zotlabs/Module/Settings/Directory.php b/Zotlabs/Module/Settings/Directory.php new file mode 100644 index 000000000..13fe6eb79 --- /dev/null +++ b/Zotlabs/Module/Settings/Directory.php @@ -0,0 +1,47 @@ +<?php + +namespace Zotlabs\Module\Settings; + + +class Directory { + + function post() { + + $module = substr(strrchr(strtolower(static::class), '\\'), 1); + + check_form_security_token_redirectOnErr('/settings/' . $module, 'settings_' . $module); + + $features = get_module_features($module); + + process_module_features_post(local_channel(), $features, $_POST); + + build_sync_packet(); + + if($_POST['rpath']) + goaway($_POST['rpath']); + + return; + } + + function get() { + + $module = substr(strrchr(strtolower(static::class), '\\'), 1); + + $features = get_module_features($module); + $rpath = (($_GET['rpath']) ? $_GET['rpath'] : ''); + + $tpl = get_markup_template("settings_module.tpl"); + + $o .= replace_macros($tpl, array( + '$rpath' => $rpath, + '$action_url' => 'settings/' . $module, + '$form_security_token' => get_form_security_token('settings_' . $module), + '$title' => t('Directory Settings'), + '$features' => process_module_features_get(local_channel(), $features), + '$submit' => t('Submit') + )); + + return $o; + } + +} diff --git a/Zotlabs/Module/Settings/Display.php b/Zotlabs/Module/Settings/Display.php index 340b3c0bb..45d80e011 100644 --- a/Zotlabs/Module/Settings/Display.php +++ b/Zotlabs/Module/Settings/Display.php @@ -27,16 +27,8 @@ class Display { $user_scalable = ((x($_POST,'user_scalable')) ? intval($_POST['user_scalable']) : 0); $nosmile = ((x($_POST,'nosmile')) ? intval($_POST['nosmile']) : 0); $title_tosource = ((x($_POST,'title_tosource')) ? intval($_POST['title_tosource']) : 0); - $channel_list_mode = ((x($_POST,'channel_list_mode')) ? intval($_POST['channel_list_mode']) : 0); - $network_list_mode = ((x($_POST,'network_list_mode')) ? intval($_POST['network_list_mode']) : 0); $manual_update = ((array_key_exists('manual_update',$_POST)) ? intval($_POST['manual_update']) : 0); - - $channel_divmore_height = ((x($_POST,'channel_divmore_height')) ? intval($_POST['channel_divmore_height']) : 400); - if($channel_divmore_height < 50) - $channel_divmore_height = 50; - $network_divmore_height = ((x($_POST,'network_divmore_height')) ? intval($_POST['network_divmore_height']) : 400); - if($network_divmore_height < 50) - $network_divmore_height = 50; + $start_menu = ((x($_POST,'start_menu')) ? intval($_POST['start_menu']) : 0); $browser_update = ((x($_POST,'browser_update')) ? intval($_POST['browser_update']) : 0); $browser_update = $browser_update * 1000; @@ -54,12 +46,9 @@ class Display { set_pconfig(local_channel(),'system','itemspage', $itemspage); set_pconfig(local_channel(),'system','no_smilies',1-intval($nosmile)); set_pconfig(local_channel(),'system','title_tosource',$title_tosource); - set_pconfig(local_channel(),'system','channel_list_mode', $channel_list_mode); - set_pconfig(local_channel(),'system','network_list_mode', $network_list_mode); - set_pconfig(local_channel(),'system','channel_divmore_height', $channel_divmore_height); - set_pconfig(local_channel(),'system','network_divmore_height', $network_divmore_height); set_pconfig(local_channel(),'system','manual_conversation_update', $manual_update); set_pconfig(local_channel(),'system','channel_menu', $channel_menu); + set_pconfig(local_channel(),'system','start_menu', $start_menu); $newschema = ''; if($theme){ @@ -150,6 +139,14 @@ class Display { $theme_selected = explode(':', $theme_selected)[0]; } + $account = \App::get_account(); + + if($account['account_created'] > datetime_convert('','','now - 60 days')) { + $start_menu = get_pconfig(local_channel(), 'system', 'start_menu', 1); + } + else { + $start_menu = get_pconfig(local_channel(), 'system', 'start_menu', 0); + } $preload_images = get_pconfig(local_channel(),'system','preload_images'); $preload_images = (($preload_images===false)? '0': $preload_images); // default if not set: 0 @@ -204,15 +201,8 @@ class Display { '$channel_menu' => [ 'channel_menu', t('Provide channel menu in navigation bar'), get_pconfig(local_channel(),'system','channel_menu',get_config('system','channel_menu',0)), t('Default: channel menu located in app menu'),$yes_no ], '$manual_update' => array('manual_update', t('Manual conversation updates'), channel_manual_conv_update(local_channel()), t('Default is on, turning this off may increase screen jumping'), $yes_no), '$title_tosource' => array('title_tosource', t("Link post titles to source"), $title_tosource, '', $yes_no), - '$layout_editor' => t('System Page Layout Editor - (advanced)'), '$theme_config' => $theme_config, - '$expert' => feature_enabled(local_channel(),'advanced_theming'), - '$channel_list_mode' => array('channel_list_mode', t('Use blog/list mode on channel page'), get_pconfig(local_channel(),'system','channel_list_mode'), t('(comments displayed separately)'), $yes_no), - '$network_list_mode' => array('network_list_mode', t('Use blog/list mode on grid page'), get_pconfig(local_channel(),'system','network_list_mode'), t('(comments displayed separately)'), $yes_no), - '$channel_divmore_height' => array('channel_divmore_height', t('Channel page max height of content (in pixels)'), ((get_pconfig(local_channel(),'system','channel_divmore_height')) ? get_pconfig(local_channel(),'system','channel_divmore_height') : 400), t('click to expand content exceeding this height')), - '$network_divmore_height' => array('network_divmore_height', t('Grid page max height of content (in pixels)'), ((get_pconfig(local_channel(),'system','network_divmore_height')) ? get_pconfig(local_channel(),'system','network_divmore_height') : 400) , t('click to expand content exceeding this height')), - - + '$start_menu' => ['start_menu', t('New Member Links'), $start_menu, t('Display new member quick links menu'), $yes_no] )); call_hooks('display_settings',$o); diff --git a/Zotlabs/Module/Settings/Editor.php b/Zotlabs/Module/Settings/Editor.php new file mode 100644 index 000000000..5e7a9473a --- /dev/null +++ b/Zotlabs/Module/Settings/Editor.php @@ -0,0 +1,47 @@ +<?php + +namespace Zotlabs\Module\Settings; + + +class Editor { + + function post() { + + $module = substr(strrchr(strtolower(static::class), '\\'), 1); + + check_form_security_token_redirectOnErr('/settings/' . $module, 'settings_' . $module); + + $features = get_module_features($module); + + process_module_features_post(local_channel(), $features, $_POST); + + build_sync_packet(); + + if($_POST['rpath']) + goaway($_POST['rpath']); + + return; + } + + function get() { + + $module = substr(strrchr(strtolower(static::class), '\\'), 1); + + $features = get_module_features($module); + $rpath = (($_GET['rpath']) ? $_GET['rpath'] : ''); + + $tpl = get_markup_template("settings_module.tpl"); + + $o .= replace_macros($tpl, array( + '$rpath' => $rpath, + '$action_url' => 'settings/' . $module, + '$form_security_token' => get_form_security_token('settings_' . $module), + '$title' => t('Editor Settings'), + '$features' => process_module_features_get(local_channel(), $features), + '$submit' => t('Submit') + )); + + return $o; + } + +} diff --git a/Zotlabs/Module/Settings/Events.php b/Zotlabs/Module/Settings/Events.php new file mode 100644 index 000000000..eb6dda99b --- /dev/null +++ b/Zotlabs/Module/Settings/Events.php @@ -0,0 +1,47 @@ +<?php + +namespace Zotlabs\Module\Settings; + + +class Events { + + function post() { + + $module = substr(strrchr(strtolower(static::class), '\\'), 1); + + check_form_security_token_redirectOnErr('/settings/' . $module, 'settings_' . $module); + + $features = get_module_features($module); + + process_module_features_post(local_channel(), $features, $_POST); + + build_sync_packet(); + + if($_POST['rpath']) + goaway($_POST['rpath']); + + return; + } + + function get() { + + $module = substr(strrchr(strtolower(static::class), '\\'), 1); + + $features = get_module_features($module); + $rpath = (($_GET['rpath']) ? $_GET['rpath'] : ''); + + $tpl = get_markup_template("settings_module.tpl"); + + $o .= replace_macros($tpl, array( + '$rpath' => $rpath, + '$action_url' => 'settings/' . $module, + '$form_security_token' => get_form_security_token('settings_' . $module), + '$title' => t('Events Settings'), + '$features' => process_module_features_get(local_channel(), $features), + '$submit' => t('Submit') + )); + + return $o; + } + +} diff --git a/Zotlabs/Module/Settings/Features.php b/Zotlabs/Module/Settings/Features.php index 888032c28..6a3ab104b 100644 --- a/Zotlabs/Module/Settings/Features.php +++ b/Zotlabs/Module/Settings/Features.php @@ -26,44 +26,14 @@ class Features { function get() { $arr = []; - $harr = []; - if(intval($_REQUEST['techlevel'])) - $level = intval($_REQUEST['techlevel']); - else { - $level = get_account_techlevel(); - } - - if(! intval($level)) { - notice( t('Permission denied.') . EOL); - return; - } - - $techlevels = \Zotlabs\Lib\Techlevels::levels(); - - // This page isn't accessible at techlevel 0 - - unset($techlevels[0]); - - $def_techlevel = (($level > 0) ? $level : 1); - $techlock = get_config('system','techlevel_lock'); - - $all_features_raw = get_features(false); - - foreach($all_features_raw as $fname => $fdata) { - foreach(array_slice($fdata,1) as $f) { - $harr[$f[0]] = ((intval(feature_enabled(local_channel(),$f[0]))) ? "1" : ''); - } - } - - $features = get_features(true,$level); + $features = get_features(false); foreach($features as $fname => $fdata) { $arr[$fname] = array(); $arr[$fname][0] = $fdata[0]; foreach(array_slice($fdata,1) as $f) { $arr[$fname][1][] = array('feature_' . $f[0],$f[1],((intval(feature_enabled(local_channel(),$f[0]))) ? "1" : ''),$f[2],array(t('Off'),t('On'))); - unset($harr[$f[0]]); } } @@ -71,10 +41,7 @@ class Features { $o .= replace_macros($tpl, array( '$form_security_token' => get_form_security_token("settings_features"), '$title' => t('Additional Features'), - '$techlevel' => [ 'techlevel', t('Your technical skill level'), $def_techlevel, t('Used to provide a member experience and additional features consistent with your comfort level'), $techlevels ], - '$techlock' => $techlock, '$features' => $arr, - '$hiddens' => $harr, '$baseurl' => z_root(), '$submit' => t('Submit'), )); diff --git a/Zotlabs/Module/Settings/Manage.php b/Zotlabs/Module/Settings/Manage.php new file mode 100644 index 000000000..9bae12022 --- /dev/null +++ b/Zotlabs/Module/Settings/Manage.php @@ -0,0 +1,47 @@ +<?php + +namespace Zotlabs\Module\Settings; + + +class Manage { + + function post() { + + $module = substr(strrchr(strtolower(static::class), '\\'), 1); + + check_form_security_token_redirectOnErr('/settings/' . $module, 'settings_' . $module); + + $features = get_module_features($module); + + process_module_features_post(local_channel(), $features, $_POST); + + build_sync_packet(); + + if($_POST['rpath']) + goaway($_POST['rpath']); + + return; + } + + function get() { + + $module = substr(strrchr(strtolower(static::class), '\\'), 1); + + $features = get_module_features($module); + $rpath = (($_GET['rpath']) ? $_GET['rpath'] : ''); + + $tpl = get_markup_template("settings_module.tpl"); + + $o .= replace_macros($tpl, array( + '$rpath' => $rpath, + '$action_url' => 'settings/' . $module, + '$form_security_token' => get_form_security_token('settings_' . $module), + '$title' => t('Channel Manager Settings'), + '$features' => process_module_features_get(local_channel(), $features), + '$submit' => t('Submit') + )); + + return $o; + } + +} diff --git a/Zotlabs/Module/Settings/Network.php b/Zotlabs/Module/Settings/Network.php new file mode 100644 index 000000000..ae02b06e9 --- /dev/null +++ b/Zotlabs/Module/Settings/Network.php @@ -0,0 +1,67 @@ +<?php + +namespace Zotlabs\Module\Settings; + + +class Network { + + function post() { + + $module = substr(strrchr(strtolower(static::class), '\\'), 1); + + check_form_security_token_redirectOnErr('/settings/' . $module, 'settings_' . $module); + + $features = get_module_features($module); + + process_module_features_post(local_channel(), $features, $_POST); + + $network_divmore_height = ((x($_POST,'network_divmore_height')) ? intval($_POST['network_divmore_height']) : 400); + if($network_divmore_height < 50) + $network_divmore_height = 50; + + set_pconfig(local_channel(),'system','network_divmore_height', $network_divmore_height); + + build_sync_packet(); + + if($_POST['rpath']) + goaway($_POST['rpath']); + + return; + } + + function get() { + + $module = substr(strrchr(strtolower(static::class), '\\'), 1); + + $features = get_module_features($module); + $rpath = (($_GET['rpath']) ? $_GET['rpath'] : ''); + + $network_divmore_height = [ + 'network_divmore_height', + t('Max height of content (in pixels)'), + ((get_pconfig(local_channel(),'system','network_divmore_height')) ? get_pconfig(local_channel(),'system','network_divmore_height') : 400), + t('Click to expand content exceeding this height') + ]; + + $extra_settings_html = replace_macros(get_markup_template('field_input.tpl'), + [ + '$field' => $network_divmore_height + ] + ); + + $tpl = get_markup_template("settings_module.tpl"); + + $o .= replace_macros($tpl, array( + '$rpath' => $rpath, + '$action_url' => 'settings/' . $module, + '$form_security_token' => get_form_security_token('settings_' . $module), + '$title' => t('Stream Settings'), + '$features' => process_module_features_get(local_channel(), $features), + '$extra_settings_html' => $extra_settings_html, + '$submit' => t('Submit') + )); + + return $o; + } + +} diff --git a/Zotlabs/Module/Settings/Photos.php b/Zotlabs/Module/Settings/Photos.php new file mode 100644 index 000000000..9edbaa929 --- /dev/null +++ b/Zotlabs/Module/Settings/Photos.php @@ -0,0 +1,47 @@ +<?php + +namespace Zotlabs\Module\Settings; + + +class Photos { + + function post() { + + $module = substr(strrchr(strtolower(static::class), '\\'), 1); + + check_form_security_token_redirectOnErr('/settings/' . $module, 'settings_' . $module); + + $features = get_module_features($module); + + process_module_features_post(local_channel(), $features, $_POST); + + build_sync_packet(); + + if($_POST['rpath']) + goaway($_POST['rpath']); + + return; + } + + function get() { + + $module = substr(strrchr(strtolower(static::class), '\\'), 1); + + $features = get_module_features($module); + $rpath = (($_GET['rpath']) ? $_GET['rpath'] : ''); + + $tpl = get_markup_template("settings_module.tpl"); + + $o .= replace_macros($tpl, array( + '$rpath' => $rpath, + '$action_url' => 'settings/' . $module, + '$form_security_token' => get_form_security_token('settings_' . $module), + '$title' => t('Photos Settings'), + '$features' => process_module_features_get(local_channel(), $features), + '$submit' => t('Submit') + )); + + return $o; + } + +} diff --git a/Zotlabs/Module/Settings/Profiles.php b/Zotlabs/Module/Settings/Profiles.php new file mode 100644 index 000000000..fb6abf664 --- /dev/null +++ b/Zotlabs/Module/Settings/Profiles.php @@ -0,0 +1,56 @@ +<?php + +namespace Zotlabs\Module\Settings; + +require_once('include/selectors.php'); + +class Profiles { + + function post() { + + $module = substr(strrchr(strtolower(static::class), '\\'), 1); + + check_form_security_token_redirectOnErr('/settings/' . $module, 'settings_' . $module); + + $features = get_module_features($module); + + process_module_features_post(local_channel(), $features, $_POST); + + $profile_assign = ((x($_POST,'profile_assign')) ? notags(trim($_POST['profile_assign'])) : ''); + set_pconfig(local_channel(),'system','profile_assign',$profile_assign); + + build_sync_packet(); + + if($_POST['rpath']) + goaway($_POST['rpath']); + + return; + } + + function get() { + + $module = substr(strrchr(strtolower(static::class), '\\'), 1); + + $features = get_module_features($module); + $rpath = (($_GET['rpath']) ? $_GET['rpath'] : ''); + + $extra_settings_html = ''; + if(feature_enabled(local_channel(),'multi_profiles')) + $extra_settings_html = contact_profile_assign(get_pconfig(local_channel(),'system','profile_assign','')); + + $tpl = get_markup_template("settings_module.tpl"); + + $o .= replace_macros($tpl, array( + '$rpath' => $rpath, + '$action_url' => 'settings/' . $module, + '$form_security_token' => get_form_security_token('settings_' . $module), + '$title' => t('Profiles Settings'), + '$features' => process_module_features_get(local_channel(), $features), + '$extra_settings_html' => $extra_settings_html, + '$submit' => t('Submit') + )); + + return $o; + } + +} diff --git a/Zotlabs/Module/Setup.php b/Zotlabs/Module/Setup.php index a3832d156..c0716ca7c 100644 --- a/Zotlabs/Module/Setup.php +++ b/Zotlabs/Module/Setup.php @@ -526,15 +526,22 @@ class Setup extends \Zotlabs\Web\Controller { $ck_funcs[0]['status'] = false; $ck_funcs[0]['help'] = t('Error: libCURL PHP module required but not installed.'); } - if(! function_exists('imagecreatefromjpeg')) { + if((! function_exists('imagecreatefromjpeg')) && (! class_exists('\\Imagick'))) { $ck_funcs[1]['status'] = false; - $ck_funcs[1]['help'] = t('Error: GD graphics PHP module with JPEG support required but not installed.'); + $ck_funcs[1]['help'] = t('Error: GD PHP module with JPEG support or ImageMagick graphics library required but not installed.'); } if(! function_exists('openssl_public_encrypt')) { $ck_funcs[2]['status'] = false; $ck_funcs[2]['help'] = t('Error: openssl PHP module required but not installed.'); } - if(! class_exists('PDO')) { + if(class_exists('\\PDO')) { + $x = \PDO::getAvailableDrivers(); + if((! in_array('mysql',$x)) && (! in_array('pgsql',$x))) { + $ck_funcs[3]['status'] = false; + $ck_funcs[3]['help'] = t('Error: PDO database PHP module missing a driver for either mysql or pgsql.'); + } + } + if(! class_exists('\\PDO')) { $ck_funcs[3]['status'] = false; $ck_funcs[3]['help'] = t('Error: PDO database PHP module required but not installed.'); } diff --git a/Zotlabs/Module/Sharedwithme.php b/Zotlabs/Module/Sharedwithme.php index 2c97e9726..c986f6695 100644 --- a/Zotlabs/Module/Sharedwithme.php +++ b/Zotlabs/Module/Sharedwithme.php @@ -97,7 +97,6 @@ class Sharedwithme extends \Zotlabs\Web\Controller { } - //$o = profile_tabs($a, $is_owner, $channel['channel_address']); $o = ''; $o .= replace_macros(get_markup_template('sharedwithme.tpl'), array( diff --git a/Zotlabs/Module/Siteinfo.php b/Zotlabs/Module/Siteinfo.php index 25276815d..a8c5bda91 100644 --- a/Zotlabs/Module/Siteinfo.php +++ b/Zotlabs/Module/Siteinfo.php @@ -32,7 +32,7 @@ class Siteinfo extends \Zotlabs\Web\Controller { '$transport_link' => '<a href="https://zotlabs.com">https://zotlabs.com</a>', '$additional_text' => t('Additional federated transport protocols:'), - '$additional_fed' => implode(',',$federated), + '$additional_fed' => implode(', ', $federated), '$prj_version' => ((get_config('system','hidden_version_siteinfo')) ? '' : sprintf( t('Version %s'), \Zotlabs\Lib\System::get_project_version())), '$prj_linktxt' => t('Project homepage'), '$prj_srctxt' => t('Developer homepage'), diff --git a/Zotlabs/Module/Sources.php b/Zotlabs/Module/Sources.php index 91e2efa60..e535f6ebf 100644 --- a/Zotlabs/Module/Sources.php +++ b/Zotlabs/Module/Sources.php @@ -1,15 +1,18 @@ <?php namespace Zotlabs\Module; /** @file */ +use App; +use Zotlabs\Lib\Apps; +use Zotlabs\Web\Controller; -class Sources extends \Zotlabs\Web\Controller { +class Sources extends Controller { function post() { if(! local_channel()) return; - if(! feature_enabled(local_channel(),'channel_sources')) - return ''; + if(! Apps::system_app_installed(local_channel(), 'Channel Sources')) + return; $source = intval($_REQUEST['source']); $xchan = escape_tags($_REQUEST['xchan']); @@ -75,12 +78,17 @@ class Sources extends \Zotlabs\Web\Controller { function get() { if(! local_channel()) { notice( t('Permission denied.') . EOL); - return ''; + return; } - if(! feature_enabled(local_channel(),'channel_sources')) { - return ''; - } + if(! Apps::system_app_installed(local_channel(), 'Channel Sources')) { + //Do not display any associated widgets at this point + App::$pdl = ''; + + $o = '<b>' . t('Sources App') . ' (' . t('Not Installed') . '):</b><br>'; + $o .= t('Automatically import channel content from other channels or feeds'); + return $o; + } // list sources if(argc() == 1) { @@ -111,7 +119,7 @@ class Sources extends \Zotlabs\Web\Controller { '$title' => t('New Source'), '$desc' => t('Import all or selected content from the following channel into this channel and distribute it according to your channel settings.'), '$words' => array( 'words', t('Only import content with these words (one per line)'),'',t('Leave blank to import all public content')), - '$name' => array( 'name', t('Channel Name'), '', ''), + '$name' => array( 'name', t('Channel Name'), '', '', '', 'autocomplete="off"'), '$tags' => array('tags', t('Add the following categories to posts imported from this source (comma separated)'),'',t('Optional')), '$resend' => [ 'resend', t('Resend posts with this channel as author'), 0, t('Copyrights may apply'), [ t('No'), t('Yes') ]], '$submit' => t('Submit') diff --git a/Zotlabs/Module/Suggest.php b/Zotlabs/Module/Suggest.php index f79e4e245..18961214e 100644 --- a/Zotlabs/Module/Suggest.php +++ b/Zotlabs/Module/Suggest.php @@ -1,15 +1,20 @@ <?php namespace Zotlabs\Module; +use App; +use Zotlabs\Lib\Apps; + require_once('include/socgraph.php'); require_once('include/contact_widgets.php'); - class Suggest extends \Zotlabs\Web\Controller { function init() { if(! local_channel()) return; + + if(! Apps::system_app_installed(local_channel(), 'Suggest Channels')) + return; if(x($_GET,'ignore')) { q("insert into xign ( uid, xchan ) values ( %d, '%s' ) ", @@ -22,13 +27,23 @@ class Suggest extends \Zotlabs\Web\Controller { function get() { - - $o = ''; + if(! local_channel()) { notice( t('Permission denied.') . EOL); return; } + if(! Apps::system_app_installed(local_channel(), 'Suggest Channels')) { + //Do not display any associated widgets at this point + App::$pdl = ''; + + $o = '<b>' . t('Suggest Channels App') . ' (' . t('Not Installed') . '):</b><br>'; + $o .= t('Suggestions for channels in the $Projectname network you might be interested in'); + return $o; + } + + $o = ''; + nav_set_selected('Suggest Channels'); $_SESSION['return_url'] = z_root() . '/' . \App::$cmd; diff --git a/Zotlabs/Module/Token.php b/Zotlabs/Module/Token.php index 32cf95c61..2bd33c761 100644 --- a/Zotlabs/Module/Token.php +++ b/Zotlabs/Module/Token.php @@ -27,11 +27,11 @@ class Token extends \Zotlabs\Web\Controller { $_SERVER['PHP_AUTH_PW'] = $password; } } - - $s = new \Zotlabs\Identity\OAuth2Server(new OAuth2Storage(\DBA::$dba->db)); + $storage = new OAuth2Storage(\DBA::$dba->db); + $s = new \Zotlabs\Identity\OAuth2Server($storage); $request = \OAuth2\Request::createFromGlobals(); - $s->handleTokenRequest($request)->send(); - + $response = $s->handleTokenRequest($request); + $response->send(); killme(); } diff --git a/Zotlabs/Module/Settings/Tokens.php b/Zotlabs/Module/Tokens.php index 619c8b5ba..1ba41dcc5 100644 --- a/Zotlabs/Module/Settings/Tokens.php +++ b/Zotlabs/Module/Tokens.php @@ -1,16 +1,24 @@ <?php -namespace Zotlabs\Module\Settings; +namespace Zotlabs\Module; +use App; +use Zotlabs\Web\Controller; +use Zotlabs\Lib\Apps; - -class Tokens { +class Tokens extends Controller { function post() { - $channel = \App::get_channel(); + if(! local_channel()) + return; + + if(! Apps::system_app_installed(local_channel(), 'Guest Access')) + return; + + $channel = App::get_channel(); - check_form_security_token_redirectOnErr('/settings/tokens', 'settings_tokens'); + check_form_security_token_redirectOnErr('tokens', 'tokens'); $token_errs = 0; if(array_key_exists('token',$_POST)) { $atoken_id = (($_POST['atoken_id']) ? intval($_POST['atoken_id']) : 0); @@ -81,13 +89,25 @@ class Tokens { function get() { - $channel = \App::get_channel(); + if(! local_channel()) + return; + + if(! Apps::system_app_installed(local_channel(), 'Guest Access')) { + //Do not display any associated widgets at this point + App::$pdl = ''; + + $o = '<b>' . t('Guest Access App') . ' (' . t('Not Installed') . '):</b><br>'; + $o .= t('Create access tokens so that non-members can access private content'); + return $o; + } + + $channel = App::get_channel(); $atoken = null; $atoken_xchan = ''; - if(argc() > 2) { - $id = argv(2); + if(argc() > 1) { + $id = argv(1); $atoken = q("select * from atoken where atoken_id = %d and atoken_uid = %d", intval($id), @@ -99,7 +119,7 @@ class Tokens { $atoken_xchan = substr($channel['channel_hash'],0,16) . '.' . $atoken['atoken_name']; } - if($atoken && argc() > 3 && argv(3) === 'drop') { + if($atoken && argc() > 2 && argv(2) === 'drop') { atoken_delete($id); $atoken = null; $atoken_xchan = ''; @@ -117,7 +137,7 @@ class Tokens { $global_perms = \Zotlabs\Access\Permissions::Perms(); $their_perms = []; - $existing = get_all_perms(local_channel(),(($atoken_xchan) ? $atoken_xchan : '')); + $existing = get_all_perms(local_channel(),(($atoken_xchan) ? $atoken_xchan : ''),false); if($atoken_xchan) { $theirs = q("select * from abconfig where chan = %d and xchan = '%s' and cat = 'their_perms'", @@ -144,9 +164,9 @@ class Tokens { - $tpl = get_markup_template("settings_tokens.tpl"); + $tpl = get_markup_template("tokens.tpl"); $o .= replace_macros($tpl, array( - '$form_security_token' => get_form_security_token("settings_tokens"), + '$form_security_token' => get_form_security_token("tokens"), '$title' => t('Guest Access Tokens'), '$desc' => $desc, '$desc2' => $desc2, diff --git a/Zotlabs/Module/Uexport.php b/Zotlabs/Module/Uexport.php index 9af1887dc..3d1587b87 100644 --- a/Zotlabs/Module/Uexport.php +++ b/Zotlabs/Module/Uexport.php @@ -1,18 +1,24 @@ <?php namespace Zotlabs\Module; +use App; +use Zotlabs\Lib\Apps; +use Zotlabs\Web\Controller; -class Uexport extends \Zotlabs\Web\Controller { +class Uexport extends Controller { function init() { if(! local_channel()) killme(); - + + if(! Apps::system_app_installed(local_channel(), 'Channel Export')) + return; + if(argc() > 1) { $sections = (($_REQUEST['sections']) ? explode(',',$_REQUEST['sections']) : ''); - $channel = \App::get_channel(); + $channel = App::get_channel(); if(argc() > 1 && intval(argv(1)) > 1900) { $year = intval(argv(1)); @@ -47,6 +53,15 @@ class Uexport extends \Zotlabs\Web\Controller { } function get() { + + if(! Apps::system_app_installed(local_channel(), 'Channel Export')) { + //Do not display any associated widgets at this point + App::$pdl = ''; + + $o = '<b>' . t('Channel Export App') . ' (' . t('Not Installed') . '):</b><br>'; + $o .= t('Export your channel'); + return $o; + } $y = datetime_convert('UTC',date_default_timezone_get(),'now','Y'); diff --git a/Zotlabs/Module/Userinfo.php b/Zotlabs/Module/Userinfo.php new file mode 100644 index 000000000..6c881f078 --- /dev/null +++ b/Zotlabs/Module/Userinfo.php @@ -0,0 +1,17 @@ +<?php + +namespace Zotlabs\Module; + +use Zotlabs\Identity\OAuth2Storage; + + +class Userinfo extends \Zotlabs\Web\Controller { + + function init() { + $s = new \Zotlabs\Identity\OAuth2Server(new OAuth2Storage(\DBA::$dba->db)); + $request = \OAuth2\Request::createFromGlobals(); + $s->handleUserInfoRequest($request)->send(); + killme(); + } + +} diff --git a/Zotlabs/Module/Viewsrc.php b/Zotlabs/Module/Viewsrc.php index 5900e385a..119990b57 100644 --- a/Zotlabs/Module/Viewsrc.php +++ b/Zotlabs/Module/Viewsrc.php @@ -47,7 +47,7 @@ class Viewsrc extends \Zotlabs\Web\Controller { $content = escape_tags($r[0]['body']); - $o = (($json) ? json_encode($content) : str_replace("\n",'<br />',$content)); + $o = (($json) ? json_encode($content) : $content); } } diff --git a/Zotlabs/Module/Webpages.php b/Zotlabs/Module/Webpages.php index 97ec55ba3..787ed5850 100644 --- a/Zotlabs/Module/Webpages.php +++ b/Zotlabs/Module/Webpages.php @@ -1,19 +1,25 @@ <?php namespace Zotlabs\Module; +use App; +use Zotlabs\Web\Controller; +use Zotlabs\Lib\Apps; +use Zotlabs\Lib\PermissionDescription; +use Zotlabs\Lib\ExtendedZip; +use ZipArchive; + require_once('include/channel.php'); require_once('include/conversation.php'); require_once('include/acl_selectors.php'); - -class Webpages extends \Zotlabs\Web\Controller { +class Webpages extends Controller { function init() { if(argc() > 1 && argv(1) === 'sys' && is_site_admin()) { $sys = get_sys_channel(); if($sys && intval($sys['channel_id'])) { - \App::$is_sys = true; + App::$is_sys = true; } } @@ -29,23 +35,32 @@ class Webpages extends \Zotlabs\Web\Controller { function get() { - if(! \App::$profile) { + if(! App::$profile) { notice( t('Requested profile is not available.') . EOL ); - \App::$error = 404; + App::$error = 404; return; } + if(! Apps::system_app_installed(App::$profile_uid, 'Webpages')) { + //Do not display any associated widgets at this point + App::$pdl = ''; + + $o = '<b>' . t('Webpages App') . ' (' . t('Not Installed') . '):</b><br>'; + $o .= t('Provide managed web pages on your channel'); + return $o; + } + nav_set_selected('Webpages'); $which = argv(1); - $_SESSION['return_url'] = \App::$query_string; + $_SESSION['return_url'] = App::$query_string; $uid = local_channel(); $owner = 0; - $observer = \App::get_observer(); + $observer = App::get_observer(); - $channel = \App::get_channel(); + $channel = App::get_channel(); switch ($_SESSION['action']) { case 'import': @@ -91,7 +106,7 @@ class Webpages extends \Zotlabs\Web\Controller { } - if(\App::$is_sys && is_site_admin()) { + if(App::$is_sys && is_site_admin()) { $sys = get_sys_channel(); if($sys && intval($sys['channel_id'])) { $uid = $owner = intval($sys['channel_id']); @@ -127,8 +142,8 @@ class Webpages extends \Zotlabs\Web\Controller { // Nickname is set to the observers xchan, and profile_uid to the owner's. // This lets you post pages at other people's channels. - if((! $channel) && ($uid) && ($uid == \App::$profile_uid)) { - $channel = \App::get_channel(); + if((! $channel) && ($uid) && ($uid == App::$profile_uid)) { + $channel = App::get_channel(); } if($channel) { $channel_acl = array( @@ -144,15 +159,15 @@ class Webpages extends \Zotlabs\Web\Controller { $is_owner = ($uid && $uid == $owner); - //$o = profile_tabs($a, $is_owner, \App::$profile['channel_address']); + $o = ''; $x = array( 'webpage' => ITEM_TYPE_WEBPAGE, 'is_owner' => true, - 'nickname' => \App::$profile['channel_address'], + 'nickname' => App::$profile['channel_address'], 'lockstate' => (($channel['channel_allow_cid'] || $channel['channel_allow_gid'] || $channel['channel_deny_cid'] || $channel['channel_deny_gid']) ? 'lock' : 'unlock'), - 'acl' => (($is_owner) ? populate_acl($channel_acl,false, \Zotlabs\Lib\PermissionDescription::fromGlobalPermission('view_pages')) : ''), + 'acl' => (($is_owner) ? populate_acl($channel_acl,false, PermissionDescription::fromGlobalPermission('view_pages')) : ''), 'permissions' => $channel_acl, 'showacl' => (($is_owner) ? true : false), 'visitor' => true, @@ -193,7 +208,7 @@ class Webpages extends \Zotlabs\Web\Controller { if(! $r) $x['pagetitle'] = 'home'; - $editor = status_editor($a,$x); + $editor = status_editor($a,$x,false,'Webpages'); $pages = null; @@ -280,7 +295,7 @@ class Webpages extends \Zotlabs\Web\Controller { notice( t('Invalid file type.') . EOL); return; } - $zip = new \ZipArchive(); + $zip = new ZipArchive(); if ($zip->open($source) === true) { $tmp_folder_name = random_string(5); $website = dirname($source) . '/' . $tmp_folder_name; @@ -297,7 +312,7 @@ class Webpages extends \Zotlabs\Web\Controller { // Website files are to be imported from the channel cloud files if (($_POST) && array_key_exists('path',$_POST) && isset($_POST['cloudsubmit'])) { - $channel = \App::get_channel(); + $channel = App::get_channel(); $dirpath = get_dirpath_by_cloudpath($channel, $_POST['path']); if(!$dirpath) { notice( t('Invalid folder path.') . EOL); @@ -343,7 +358,7 @@ class Webpages extends \Zotlabs\Web\Controller { case 'importselected': require_once('include/import.php'); - $channel = \App::get_channel(); + $channel = App::get_channel(); // Import layout first so that pages that reference new layouts will find // the mid of layout items in the database @@ -438,7 +453,7 @@ class Webpages extends \Zotlabs\Web\Controller { case 'cloud': case 'zipfile': - $channel = \App::get_channel(); + $channel = App::get_channel(); $tmp_folder_name = random_string(10); $zip_folder_name = random_string(10); @@ -657,7 +672,7 @@ class Webpages extends \Zotlabs\Web\Controller { } if($action === 'zipfile') { // Generate the zip file - \Zotlabs\Lib\ExtendedZip::zipTree($tmp_folderpath, $zip_filepath, \ZipArchive::CREATE); + ExtendedZip::zipTree($tmp_folderpath, $zip_filepath, ZipArchive::CREATE); // Output the file for download header('Content-disposition: attachment; filename="' . $zip_filename . '"'); header("Content-Type: application/zip"); @@ -666,7 +681,7 @@ class Webpages extends \Zotlabs\Web\Controller { if(isset($_SESSION['exportcloudpath'])) { require_once('include/attach.php'); $cloudpath = urldecode($_SESSION['exportcloudpath']); - $channel = \App::get_channel(); + $channel = App::get_channel(); $dirpath = get_dirpath_by_cloudpath($channel, $cloudpath); if(!$dirpath) { $x = attach_mkdirp($channel, $channel['channel_hash'], array('pathname' => $cloudpath)); diff --git a/Zotlabs/Module/Well_known.php b/Zotlabs/Module/Well_known.php index 442994b54..09e743788 100644 --- a/Zotlabs/Module/Well_known.php +++ b/Zotlabs/Module/Well_known.php @@ -52,6 +52,7 @@ class Well_known extends \Zotlabs\Web\Controller { break; case 'oauth-authorization-server': + case 'openid-configuration': \App::$argc -= 1; array_shift(\App::$argv); \App::$argv[0] = 'oauthinfo'; diff --git a/Zotlabs/Module/Wfinger.php b/Zotlabs/Module/Wfinger.php index 88cb3e879..1866bce40 100644 --- a/Zotlabs/Module/Wfinger.php +++ b/Zotlabs/Module/Wfinger.php @@ -172,6 +172,11 @@ class Wfinger extends \Zotlabs\Web\Controller { 'href' => z_root() . '/hcard/' . $r[0]['channel_address'] ], + [ + 'rel' => 'http://openid.net/specs/connect/1.0/issuer', + 'href' => z_root() + ], + [ 'rel' => 'http://webfinger.net/rel/profile-page', diff --git a/Zotlabs/Module/Wiki.php b/Zotlabs/Module/Wiki.php index 322a3933c..892810241 100644 --- a/Zotlabs/Module/Wiki.php +++ b/Zotlabs/Module/Wiki.php @@ -2,15 +2,20 @@ namespace Zotlabs\Module; -use \Zotlabs\Lib as Zlib; -use \Michelf\MarkdownExtra; +use App; +use Zotlabs\Web\Controller; +use Zotlabs\Lib\Apps; +use Zotlabs\Lib\PermissionDescription; +use Zotlabs\Lib\NativeWiki; +use Zotlabs\Lib\NativeWikiPage; +use Zotlabs\Lib\MarkdownSoap; +use Michelf\MarkdownExtra; require_once('include/acl_selectors.php'); require_once('include/conversation.php'); require_once('include/bbcode.php'); - -class Wiki extends \Zotlabs\Web\Controller { +class Wiki extends Controller { private $wiki = null; @@ -40,10 +45,14 @@ class Wiki extends \Zotlabs\Web\Controller { return login(); } - if(! feature_enabled(\App::$profile_uid,'wiki')) { - notice( t('Not found') . EOL); - return; - } + if(! Apps::system_app_installed(App::$profile_uid, 'Wiki')) { + //Do not display any associated widgets at this point + App::$pdl = ''; + + $o = '<b>' . t('Wiki App') . ' (' . t('Not Installed') . '):</b><br>'; + $o .= t('Provide a wiki for your channel'); + return $o; + } if(! perm_is_allowed(\App::$profile_uid,get_observer_hash(),'view_wiki')) { @@ -95,7 +104,7 @@ class Wiki extends \Zotlabs\Web\Controller { $owner['channel_deny_gid']) ? 'lock' : 'unlock' ), - 'acl' => populate_acl($owner_acl, false, \Zotlabs\Lib\PermissionDescription::fromGlobalPermission('view_wiki')), + 'acl' => populate_acl($owner_acl, false, PermissionDescription::fromGlobalPermission('view_wiki')), 'allow_cid' => acl2json($owner_acl['allow_cid']), 'allow_gid' => acl2json($owner_acl['allow_gid']), 'deny_cid' => acl2json($owner_acl['deny_cid']), @@ -109,7 +118,7 @@ class Wiki extends \Zotlabs\Web\Controller { } $is_owner = ((local_channel()) && (local_channel() == \App::$profile['profile_uid']) ? true : false); - //$o = profile_tabs($a, $is_owner, \App::$profile['channel_address']); + $o = ''; // Download a wiki @@ -117,9 +126,9 @@ class Wiki extends \Zotlabs\Web\Controller { if((argc() > 3) && (argv(2) === 'download') && (argv(3) === 'wiki')) { $resource_id = argv(4); - $w = Zlib\NativeWiki::get_wiki($owner['channel_id'],$observer_hash,$resource_id); + $w = NativeWiki::get_wiki($owner['channel_id'],$observer_hash,$resource_id); -// $w = Zlib\NativeWiki::get_wiki($owner,$observer_hash,$resource_id); +// $w = NativeWiki::get_wiki($owner,$observer_hash,$resource_id); if(! $w['htmlName']) { notice(t('Error retrieving wiki') . EOL); } @@ -157,9 +166,9 @@ class Wiki extends \Zotlabs\Web\Controller { $content = html_entity_decode($iv['body'],ENT_COMPAT,'UTF-8'); } elseif($iv['mimetype'] === 'text/markdown') { - $content = html_entity_decode(Zlib\MarkdownSoap::unescape($iv['body']),ENT_COMPAT,'UTF-8'); + $content = html_entity_decode(MarkdownSoap::unescape($iv['body']),ENT_COMPAT,'UTF-8'); } - $fname = get_iconfig($iv['id'],'nwikipage','pagetitle') . Zlib\NativeWikiPage::get_file_ext($iv); + $fname = get_iconfig($iv['id'],'nwikipage','pagetitle') . NativeWikiPage::get_file_ext($iv); $zip->addFromString($fname,$content); $pages[] = $iv['mid']; } @@ -190,7 +199,7 @@ class Wiki extends \Zotlabs\Web\Controller { switch(argc()) { case 2: - $wikis = Zlib\NativeWiki::listwikis($owner, get_observer_hash()); + $wikis = NativeWiki::listwikis($owner, get_observer_hash()); if($wikis) { $o .= replace_macros(get_markup_template('wikilist.tpl'), array( @@ -228,7 +237,8 @@ class Wiki extends \Zotlabs\Web\Controller { // /wiki/channel/wiki -> No page was specified, so redirect to Home.md - $wikiUrlName = urlencode(argv(2)); + //$wikiUrlName = urlencode(argv(2)); + $wikiUrlName = NativeWiki::name_encode(argv(2)); goaway(z_root() . '/' . argv(0) . '/' . argv(1) . '/' . $wikiUrlName . '/Home'); case 4: @@ -237,7 +247,8 @@ class Wiki extends \Zotlabs\Web\Controller { // GET /wiki/channel/wiki/page // Fetch the wiki info and determine observer permissions - $wikiUrlName = urldecode(argv(2)); + //$wikiUrlName = urldecode(argv(2)); + $wikiUrlName = NativeWiki::name_decode(argv(2)); $page_name = ''; $ignore_language = false; @@ -253,10 +264,11 @@ class Wiki extends \Zotlabs\Web\Controller { $page_name .= argv($x); } - $pageUrlName = urldecode($page_name); - $langPageUrlName = urldecode(\App::$language . '/' . $page_name); + //$pageUrlName = urldecode($page_name); + $pageUrlName = NativeWiki::name_decode($page_name); + $langPageUrlName = \App::$language . '/' . $pageUrlName; - $w = Zlib\NativeWiki::exists_by_name($owner['channel_id'], $wikiUrlName); + $w = NativeWiki::exists_by_name($owner['channel_id'], $wikiUrlName); if(! $w['resource_id']) { notice(t('Wiki not found') . EOL); @@ -268,7 +280,7 @@ class Wiki extends \Zotlabs\Web\Controller { if(! $wiki_owner) { // Check for observer permissions $observer_hash = get_observer_hash(); - $perms = Zlib\NativeWiki::get_permissions($resource_id, intval($owner['channel_id']), $observer_hash); + $perms = NativeWiki::get_permissions($resource_id, intval($owner['channel_id']), $observer_hash); if(! $perms['read']) { notice(t('Permission denied.') . EOL); goaway(z_root() . '/' . argv(0) . '/' . argv(1)); @@ -280,8 +292,10 @@ class Wiki extends \Zotlabs\Web\Controller { $wiki_editor = true; } - $wikiheaderName = urldecode($wikiUrlName); - $wikiheaderPage = urldecode($pageUrlName); + //$wikiheaderName = urldecode($wikiUrlName); + $wikiheaderName = NativeWiki::name_decode($wikiUrlName); + //$wikiheaderPage = urldecode($pageUrlName); + $wikiheaderPage = NativeWiki::name_decode($pageUrlName); $renamePage = (($wikiheaderPage === 'Home') ? '' : t('Rename page')); $sharePage = t('Share'); @@ -289,10 +303,10 @@ class Wiki extends \Zotlabs\Web\Controller { $p = []; if(! $ignore_language) { - $p = Zlib\NativeWikiPage::get_page_content(array('channel_id' => $owner['channel_id'], 'observer_hash' => $observer_hash, 'resource_id' => $resource_id, 'pageUrlName' => $langPageUrlName)); + $p = NativeWikiPage::get_page_content(array('channel_id' => $owner['channel_id'], 'observer_hash' => $observer_hash, 'resource_id' => $resource_id, 'pageUrlName' => $langPageUrlName)); } if(! ($p && $p['success'])) { - $p = Zlib\NativeWikiPage::get_page_content(array('channel_id' => $owner['channel_id'], 'observer_hash' => $observer_hash, 'resource_id' => $resource_id, 'pageUrlName' => $pageUrlName)); + $p = NativeWikiPage::get_page_content(array('channel_id' => $owner['channel_id'], 'observer_hash' => $observer_hash, 'resource_id' => $resource_id, 'pageUrlName' => $pageUrlName)); } if(! ($p && $p['success'])) { $x = new \Zotlabs\Widget\Wiki_pages(); @@ -306,7 +320,7 @@ class Wiki extends \Zotlabs\Web\Controller { //json_return_and_die(array('pages' => $page_list_html, 'message' => '', 'success' => true)); notice( t('Error retrieving page content') . EOL); //goaway(z_root() . '/' . argv(0) . '/' . argv(1) ); - $renderedContent = Zlib\NativeWikiPage::convert_links($html, argv(0) . '/' . argv(1) . '/' . $wikiUrlName); + $renderedContent = NativeWikiPage::convert_links($html, argv(0) . '/' . argv(1) . '/' . NativeWiki::name_encode($wikiUrlName)); $showPageControls = $wiki_editor; } else { @@ -317,24 +331,31 @@ class Wiki extends \Zotlabs\Web\Controller { $sampleContent = t('New page'); $content = (($p['content'] == '') ? $sampleContent : $p['content']); + + $hookinfo = ['content' => $content, 'mimetype' => $mimeType]; + call_hooks('wiki_preprocess',$hookinfo); + $content = $hookinfo['content']; // Render the Markdown-formatted page content in HTML if($mimeType == 'text/bbcode') { - $renderedContent = Zlib\NativeWikiPage::convert_links(zidify_links(smilies(bbcode($content))), argv(0) . '/' . argv(1) . '/' . $wikiUrlName); + $renderedContent = zidify_links(smilies(bbcode($content))); + $renderedContent = NativeWikiPage::convert_links($renderedContent,argv(0) . '/' . argv(1) . '/' . NativeWiki::name_encode($wikiUrlName)); } elseif($mimeType === 'text/plain') { $renderedContent = str_replace(["\n",' ',"\t"],[EOL,' ',' '],htmlentities($content,ENT_COMPAT,'UTF-8',false)); } elseif($mimeType === 'text/markdown') { - $content = Zlib\MarkdownSoap::unescape($content); - $html = Zlib\NativeWikiPage::generate_toc(zidify_text(MarkdownExtra::defaultTransform(Zlib\NativeWikiPage::bbcode($content)))); - $renderedContent = Zlib\NativeWikiPage::convert_links($html, argv(0) . '/' . argv(1) . '/' . $wikiUrlName); + $content = MarkdownSoap::unescape($content); + //$html = NativeWikiPage::generate_toc(zidify_text(MarkdownExtra::defaultTransform(NativeWikiPage::bbcode($content)))); + //$renderedContent = NativeWikiPage::convert_links($html, argv(0) . '/' . argv(1) . '/' . $wikiUrlName); + $html = NativeWikiPage::convert_links($content, argv(0) . '/' . argv(1) . '/' . NativeWiki::name_encode($wikiUrlName)); + $renderedContent = NativeWikiPage::generate_toc(zidify_text(MarkdownExtra::defaultTransform(NativeWikiPage::bbcode($html)))); } $showPageControls = $wiki_editor; } break; // default: // Strip the extraneous URL components -// goaway('/' . argv(0) . '/' . argv(1) . '/' . $wikiUrlName . '/' . $pageUrlName); +// goaway('/' . argv(0) . '/' . argv(1) . '/' . NativeWiki::name_encode($wikiUrlName) . '/' . $pageUrlName); } @@ -351,13 +372,14 @@ class Wiki extends \Zotlabs\Web\Controller { $currenttype = $types[$mimeType]; $placeholder = t('Short description of your changes (optional)'); - + + $zrl = urlencode( z_root() . '/wiki/' . argv(1) . '/' . NativeWiki::name_encode($wikiUrlName) . '/' . NativeWiki::name_encode($pageUrlName) ); $o .= replace_macros(get_markup_template('wiki.tpl'),array( '$wikiheaderName' => $wikiheaderName, '$wikiheaderPage' => $wikiheaderPage, '$renamePage' => $renamePage, '$sharePage' => $sharePage, - '$shareLink' => urlencode('#^[zrl=' . z_root() . '/wiki/' . argv(1) . '/' . $wikiUrlName . '/' . $pageUrlName . ']' . '[ ' . $owner['channel_name'] . ' ] ' . $wikiheaderName . ' - ' . $wikiheaderPage . '[/zrl]'), + '$shareLink' => '#^[zrl=' . $zrl . ']' . '[ ' . $owner['channel_name'] . ' ] ' . $wikiheaderName . ' - ' . $wikiheaderPage . '[/zrl]', '$showPageControls' => $showPageControls, '$editOrSourceLabel' => (($showPageControls) ? t('Edit') : t('Source')), '$tools_label' => 'Page Tools', @@ -384,6 +406,8 @@ class Wiki extends \Zotlabs\Web\Controller { '$modalerrorlist' => t('Error getting album list'), '$modalerrorlink' => t('Error getting photo link'), '$modalerroralbum' => t('Error getting album'), + '$view_lbl' => t('View'), + '$history_lbl' => t('History') )); if($p['pageMimeType'] === 'text/markdown') @@ -411,23 +435,24 @@ class Wiki extends \Zotlabs\Web\Controller { $content = $_POST['content']; $resource_id = $_POST['resource_id']; - $w = Zlib\NativeWiki::get_wiki($owner['channel_id'],$observer_hash,$resource_id); + $w = NativeWiki::get_wiki($owner['channel_id'],$observer_hash,$resource_id); $wikiURL = argv(0) . '/' . argv(1) . '/' . $w['urlName']; $mimeType = $_POST['mimetype']; if($mimeType === 'text/bbcode') { - $html = Zlib\NativeWikiPage::convert_links(zidify_links(smilies(bbcode($content))),$wikiURL); + $linkconverted = NativeWikiPage::convert_links($content,$wikiURL); + $html = zidify_links(smilies(bbcode($linkconverted))); } elseif($mimeType === 'text/markdown') { - $bb = Zlib\NativeWikiPage::bbcode($content); - $x = new ZLib\MarkdownSoap($bb); + $linkconverted = NativeWikiPage::convert_links($content,$wikiURL); + $bb = NativeWikiPage::bbcode($linkconverted); + $x = new MarkdownSoap($bb); $md = $x->clean(); - $md = ZLib\MarkdownSoap::unescape($md); + $md = MarkdownSoap::unescape($md); $html = MarkdownExtra::defaultTransform($md); - $html = Zlib\NativeWikiPage::generate_toc(zidify_text($html)); - $html = Zlib\NativeWikiPage::convert_links($html,$wikiURL); + $html = NativeWikiPage::generate_toc(zidify_text($html)); } elseif($mimeType === 'text/plain') { $html = str_replace(["\n",' ',"\t"],[EOL,' ',' '],htmlentities($content,ENT_COMPAT,'UTF-8',false)); @@ -454,7 +479,8 @@ class Wiki extends \Zotlabs\Web\Controller { $wiki['postVisible'] = ((intval($_POST['postVisible'])) ? 1 : 0); $wiki['rawName'] = $name; $wiki['htmlName'] = escape_tags($name); - $wiki['urlName'] = urlencode(urlencode($name)); + //$wiki['urlName'] = urlencode(urlencode($name)); + $wiki['urlName'] = NativeWiki::name_encode($name); $wiki['mimeType'] = $_POST['mimeType']; $wiki['typelock'] = $_POST['typelock']; @@ -464,7 +490,7 @@ class Wiki extends \Zotlabs\Web\Controller { return; //not reached } - $exists = Zlib\NativeWiki::exists_by_name($owner['channel_id'], $wiki['urlName']); + $exists = NativeWiki::exists_by_name($owner['channel_id'], $wiki['urlName']); if($exists['id']) { notice( t('A wiki with this name already exists.') . EOL); goaway('/wiki'); @@ -474,16 +500,16 @@ class Wiki extends \Zotlabs\Web\Controller { // Get ACL for permissions $acl = new \Zotlabs\Access\AccessList($owner); $acl->set_from_array($_POST); - $r = Zlib\NativeWiki::create_wiki($owner, $observer_hash, $wiki, $acl); + $r = NativeWiki::create_wiki($owner, $observer_hash, $wiki, $acl); if($r['success']) { - Zlib\NativeWiki::sync_a_wiki_item($owner['channel_id'],$r['item_id'],$r['item']['resource_id']); - $homePage = Zlib\NativeWikiPage::create_page($owner['channel_id'],$observer_hash,'Home', $r['item']['resource_id'], $wiki['mimeType']); + 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']); if(! $homePage['success']) { notice( t('Wiki created, but error creating Home page.')); - goaway(z_root() . '/wiki/' . $nick . '/' . $wiki['urlName']); + goaway(z_root() . '/wiki/' . $nick . '/' . NativeWiki::name_encode($wiki['urlName'])); } - Zlib\NativeWiki::sync_a_wiki_item($owner['channel_id'],$homePage['item_id'],$r['item']['resource_id']); - goaway(z_root() . '/wiki/' . $nick . '/' . $wiki['urlName'] . '/' . $homePage['page']['urlName']); + NativeWiki::sync_a_wiki_item($owner['channel_id'],$homePage['item_id'],$r['item']['resource_id']); + goaway(z_root() . '/wiki/' . $nick . '/' . NativeWiki::name_encode($wiki['urlName']) . '/' . NativeWiki::name_encode($homePage['page']['urlName'])); } else { notice( t('Error creating wiki')); @@ -503,7 +529,8 @@ class Wiki extends \Zotlabs\Web\Controller { $arr = []; - $arr['urlName'] = urlencode(urlencode($_POST['origRawName'])); + //$arr['urlName'] = urlencode(urlencode($_POST['origRawName'])); + $arr['urlName'] = NativeWiki::name_encode($_POST['origRawName']); if($_POST['updateRawName']) $arr['updateRawName'] = $_POST['updateRawName']; @@ -514,7 +541,7 @@ class Wiki extends \Zotlabs\Web\Controller { return; //not reached } - $wiki = Zlib\NativeWiki::exists_by_name($owner['channel_id'], urldecode($arr['urlName'])); + $wiki = NativeWiki::exists_by_name($owner['channel_id'], $arr['urlName']); if($wiki['resource_id']) { @@ -523,9 +550,9 @@ class Wiki extends \Zotlabs\Web\Controller { $acl = new \Zotlabs\Access\AccessList($owner); $acl->set_from_array($_POST); - $r = Zlib\NativeWiki::update_wiki($owner['channel_id'], $observer_hash, $arr, $acl); + $r = NativeWiki::update_wiki($owner['channel_id'], $observer_hash, $arr, $acl); if($r['success']) { - Zlib\NativeWiki::sync_a_wiki_item($owner['channel_id'],$r['item_id'],$r['item']['resource_id']); + NativeWiki::sync_a_wiki_item($owner['channel_id'],$r['item_id'],$r['item']['resource_id']); goaway(z_root() . '/wiki/' . $nick); } else { @@ -547,9 +574,9 @@ class Wiki extends \Zotlabs\Web\Controller { json_return_and_die(array('message' => t('Wiki delete permission denied.'), 'success' => false)); } $resource_id = $_POST['resource_id']; - $deleted = Zlib\NativeWiki::delete_wiki($owner['channel_id'],$observer_hash,$resource_id); + $deleted = NativeWiki::delete_wiki($owner['channel_id'],$observer_hash,$resource_id); if ($deleted['success']) { - Zlib\NativeWiki::sync_a_wiki_item($owner['channel_id'],$deleted['item_id'],$resource_id); + NativeWiki::sync_a_wiki_item($owner['channel_id'],$deleted['item_id'],$resource_id); json_return_and_die(array('message' => '', 'success' => true)); } else { @@ -568,25 +595,25 @@ class Wiki extends \Zotlabs\Web\Controller { // Determine if observer has permission to create a page - $perms = Zlib\NativeWiki::get_permissions($resource_id, intval($owner['channel_id']), $observer_hash, $mimetype); + $perms = NativeWiki::get_permissions($resource_id, intval($owner['channel_id']), $observer_hash, $mimetype); if(! $perms['write']) { logger('Wiki write permission denied. ' . EOL); json_return_and_die(array('success' => false)); } - $name = $_POST['pageName']; //Get new page name + $name = isset($_POST['pageName']) ? $_POST['pageName'] : $_POST['missingPageName']; //Get new page name // backslashes won't work well in the javascript functions $name = str_replace('\\','',$name); - if(urlencode(escape_tags($name)) === '') { + if(NativeWiki::name_encode(escape_tags($name)) === '') { json_return_and_die(array('message' => 'Error creating page. Invalid name (' . print_r($_POST,true) . ').', 'success' => false)); } - $page = Zlib\NativeWikiPage::create_page($owner['channel_id'],$observer_hash, $name, $resource_id, $mimetype); + $page = NativeWikiPage::create_page($owner['channel_id'],$observer_hash, $name, $resource_id, $mimetype); if($page['item_id']) { - $commit = Zlib\NativeWikiPage::commit(array( + $commit = NativeWikiPage::commit(array( 'commit_msg' => t('New page created'), 'resource_id' => $resource_id, 'channel_id' => $owner['channel_id'], @@ -595,11 +622,12 @@ class Wiki extends \Zotlabs\Web\Controller { )); if($commit['success']) { - Zlib\NativeWiki::sync_a_wiki_item($owner['channel_id'],$commit['item_id'],$resource_id); - json_return_and_die(array('url' => '/' . argv(0) . '/' . argv(1) . '/' . urlencode($page['wiki']['urlName']) . '/' . urlencode($page['page']['urlName']), 'success' => true)); + NativeWiki::sync_a_wiki_item($owner['channel_id'],$commit['item_id'],$resource_id); + //json_return_and_die(array('url' => '/' . argv(0) . '/' . argv(1) . '/' . urlencode($page['wiki']['urlName']) . '/' . urlencode($page['page']['urlName']), 'success' => true)); + json_return_and_die(array('url' => '/' . argv(0) . '/' . argv(1) . '/' . $page['wiki']['urlName'] . '/' . $page['page']['urlName'], 'success' => true)); } else { - json_return_and_die(array('message' => 'Error making git commit','url' => '/' . argv(0) . '/' . argv(1) . '/' . urlencode($page['wiki']['urlName']) . '/' . urlencode($page['page']['urlName']),'success' => false)); + json_return_and_die(array('message' => 'Error making git commit','url' => '/' . argv(0) . '/' . argv(1) . '/' . NativeWiki::name_encode($page['wiki']['urlName']) . '/' . NativeWiki::name_encode($page['page']['urlName']),'success' => false)); } @@ -614,7 +642,7 @@ class Wiki extends \Zotlabs\Web\Controller { if((argc() === 5) && (argv(2) === 'get') && (argv(3) === 'page') && (argv(4) === 'list')) { $resource_id = $_POST['resource_id']; // resource_id for wiki in db - $perms = Zlib\NativeWiki::get_permissions($resource_id, intval($owner['channel_id']), $observer_hash); + $perms = NativeWiki::get_permissions($resource_id, intval($owner['channel_id']), $observer_hash); if(!$perms['read']) { logger('Wiki read permission denied.' . EOL); json_return_and_die(array('pages' => null, 'message' => 'Permission denied.', 'success' => false)); @@ -646,16 +674,16 @@ class Wiki extends \Zotlabs\Web\Controller { } // Determine if observer has permission to save content - $perms = Zlib\NativeWiki::get_permissions($resource_id, intval($owner['channel_id']), $observer_hash); + $perms = NativeWiki::get_permissions($resource_id, intval($owner['channel_id']), $observer_hash); if(! $perms['write']) { logger('Wiki write permission denied. ' . EOL); json_return_and_die(array('success' => false)); } - $saved = Zlib\NativeWikiPage::save_page(array('channel_id' => $owner['channel_id'], 'observer_hash' => $observer_hash, 'resource_id' => $resource_id, 'pageUrlName' => $pageUrlName, 'content' => $content)); + $saved = NativeWikiPage::save_page(array('channel_id' => $owner['channel_id'], 'observer_hash' => $observer_hash, 'resource_id' => $resource_id, 'pageUrlName' => $pageUrlName, 'content' => $content)); if($saved['success']) { - $commit = Zlib\NativeWikiPage::commit(array( + $commit = NativeWikiPage::commit(array( 'commit_msg' => $commitMsg, 'pageUrlName' => $pageUrlName, 'resource_id' => $resource_id, @@ -665,8 +693,8 @@ class Wiki extends \Zotlabs\Web\Controller { )); if($commit['success']) { - Zlib\NativeWiki::sync_a_wiki_item($owner['channel_id'],$commit['item_id'],$resource_id); - json_return_and_die(array('message' => 'Wiki git repo commit made', 'success' => true)); + NativeWiki::sync_a_wiki_item($owner['channel_id'],$commit['item_id'],$resource_id); + json_return_and_die(array('message' => 'Wiki git repo commit made', 'success' => true , 'content' => $content)); } else { json_return_and_die(array('message' => 'Error making git commit','success' => false)); @@ -686,7 +714,7 @@ class Wiki extends \Zotlabs\Web\Controller { // Determine if observer has permission to read content - $perms = Zlib\NativeWiki::get_permissions($resource_id, intval($owner['channel_id']), $observer_hash); + $perms = NativeWiki::get_permissions($resource_id, intval($owner['channel_id']), $observer_hash); if(! $perms['read']) { logger('Wiki read permission denied.' . EOL); json_return_and_die(array('historyHTML' => '', 'message' => 'Permission denied.', 'success' => false)); @@ -718,15 +746,15 @@ class Wiki extends \Zotlabs\Web\Controller { json_return_and_die(array('success' => false)); } - $perms = Zlib\NativeWiki::get_permissions($resource_id, intval($owner['channel_id']), $observer_hash); + $perms = NativeWiki::get_permissions($resource_id, intval($owner['channel_id']), $observer_hash); if(! $perms['write']) { logger('Wiki write permission denied. ' . EOL); json_return_and_die(array('success' => false)); } - $deleted = Zlib\NativeWikiPage::delete_page(array('channel_id' => $owner['channel_id'], 'observer_hash' => $observer_hash, 'resource_id' => $resource_id, 'pageUrlName' => $pageUrlName)); + $deleted = NativeWikiPage::delete_page(array('channel_id' => $owner['channel_id'], 'observer_hash' => $observer_hash, 'resource_id' => $resource_id, 'pageUrlName' => $pageUrlName)); if($deleted['success']) { - Zlib\NativeWiki::sync_a_wiki_item($owner['channel_id'],$commit['item_id'],$resource_id); + NativeWiki::sync_a_wiki_item($owner['channel_id'],$commit['item_id'],$resource_id); json_return_and_die(array('message' => 'Wiki git repo commit made', 'success' => true)); } else { @@ -742,13 +770,13 @@ class Wiki extends \Zotlabs\Web\Controller { $commitHash = $_POST['commitHash']; // Determine if observer has permission to revert pages - $perms = Zlib\NativeWiki::get_permissions($resource_id, intval($owner['channel_id']), $observer_hash); + $perms = NativeWiki::get_permissions($resource_id, intval($owner['channel_id']), $observer_hash); if(! $perms['write']) { logger('Wiki write permission denied.' . EOL); json_return_and_die(array('success' => false)); } - $reverted = Zlib\NativeWikiPage::revert_page(array('channel_id' => $owner['channel_id'], 'observer_hash' => $observer_hash, 'commitHash' => $commitHash, 'resource_id' => $resource_id, 'pageUrlName' => $pageUrlName)); + $reverted = NativeWikiPage::revert_page(array('channel_id' => $owner['channel_id'], 'observer_hash' => $observer_hash, 'commitHash' => $commitHash, 'resource_id' => $resource_id, 'pageUrlName' => $pageUrlName)); if($reverted['success']) { json_return_and_die(array('content' => $reverted['content'], 'message' => '', 'success' => true)); } else { @@ -764,13 +792,13 @@ class Wiki extends \Zotlabs\Web\Controller { $currentCommit = $_POST['currentCommit']; // Determine if observer has permission to revert pages - $perms = Zlib\NativeWiki::get_permissions($resource_id, intval($owner['channel_id']), $observer_hash); + $perms = NativeWiki::get_permissions($resource_id, intval($owner['channel_id']), $observer_hash); if(!$perms['read']) { logger('Wiki read permission denied.' . EOL); json_return_and_die(array('success' => false)); } - $compare = Zlib\NativeWikiPage::compare_page(array('channel_id' => $owner['channel_id'], 'observer_hash' => $observer_hash, 'currentCommit' => $currentCommit, 'compareCommit' => $compareCommit, 'resource_id' => $resource_id, 'pageUrlName' => $pageUrlName)); + $compare = NativeWikiPage::compare_page(array('channel_id' => $owner['channel_id'], 'observer_hash' => $observer_hash, 'currentCommit' => $currentCommit, 'compareCommit' => $compareCommit, 'resource_id' => $resource_id, 'pageUrlName' => $pageUrlName)); if($compare['success']) { $diffHTML = '<table class="text-center" width="100%"><tr><td class="lead" width="50%">' . t('Current Revision') . '</td><td class="lead" width="50%">' . t('Selected Revision') . '</td></tr></table>' . $compare['diff']; json_return_and_die(array('diff' => $diffHTML, 'message' => '', 'success' => true)); @@ -787,29 +815,29 @@ class Wiki extends \Zotlabs\Web\Controller { if ($pageUrlName === 'Home') { json_return_and_die(array('message' => 'Cannot rename Home','success' => false)); } - if(urlencode(escape_tags($pageNewName)) === '') { + if(NativeWiki::name_encode(escape_tags($pageNewName)) === '') { json_return_and_die(array('message' => 'Error renaming page. Invalid name.', 'success' => false)); } // Determine if observer has permission to rename pages - $perms = Zlib\NativeWiki::get_permissions($resource_id, intval($owner['channel_id']), $observer_hash); + $perms = NativeWiki::get_permissions($resource_id, intval($owner['channel_id']), $observer_hash); if(! $perms['write']) { logger('Wiki write permission denied. ' . EOL); json_return_and_die(array('success' => false)); } - $renamed = Zlib\NativeWikiPage::rename_page(array('channel_id' => $owner['channel_id'], 'observer_hash' => $observer_hash, 'resource_id' => $resource_id, 'pageUrlName' => $pageUrlName, 'pageNewName' => $pageNewName)); + $renamed = NativeWikiPage::rename_page(array('channel_id' => $owner['channel_id'], 'observer_hash' => $observer_hash, 'resource_id' => $resource_id, 'pageUrlName' => $pageUrlName, 'pageNewName' => $pageNewName)); if($renamed['success']) { - $commit = Zlib\NativeWikiPage::commit(array( + $commit = NativeWikiPage::commit(array( 'channel_id' => $owner['channel_id'], - 'commit_msg' => 'Renamed ' . urldecode($pageUrlName) . ' to ' . $renamed['page']['htmlName'], + 'commit_msg' => 'Renamed ' . NativeWiki::name_decode($pageUrlName) . ' to ' . $renamed['page']['htmlName'], 'resource_id' => $resource_id, 'observer_hash' => $observer_hash, 'pageUrlName' => $pageNewName )); if($commit['success']) { - Zlib\NativeWiki::sync_a_wiki_item($owner['channel_id'],$commit['item_id'],$resource_id); + NativeWiki::sync_a_wiki_item($owner['channel_id'],$commit['item_id'],$resource_id); json_return_and_die(array('name' => $renamed['page'], 'message' => 'Wiki git repo commit made', 'success' => true)); } else { diff --git a/Zotlabs/Module/Zfinger.php b/Zotlabs/Module/Zfinger.php index 0f7f6a64b..6ed001df5 100644 --- a/Zotlabs/Module/Zfinger.php +++ b/Zotlabs/Module/Zfinger.php @@ -36,10 +36,6 @@ class Zfinger extends \Zotlabs\Web\Controller { echo $ret; killme(); - - - - json_return_and_die($x); } diff --git a/Zotlabs/Module/Zot.php b/Zotlabs/Module/Zot.php new file mode 100644 index 000000000..8c34dced1 --- /dev/null +++ b/Zotlabs/Module/Zot.php @@ -0,0 +1,25 @@ +<?php +/** + * @file Zotlabs/Module/Zot.php + * + * @brief Zot endpoint. + * + */ + +namespace Zotlabs\Module; + +use Zotlabs\Zot6 as ZotProtocol; + +/** + * @brief Zot module. + * + */ + +class Zot extends \Zotlabs\Web\Controller { + + function init() { + $zot = new ZotProtocol\Receiver(new ZotProtocol\Zot6Handler()); + json_return_and_die($zot->run(),'application/x-zot+jzon'); + } + +} diff --git a/Zotlabs/Module/Zot_probe.php b/Zotlabs/Module/Zot_probe.php new file mode 100644 index 000000000..d0c7e688f --- /dev/null +++ b/Zotlabs/Module/Zot_probe.php @@ -0,0 +1,47 @@ +<?php + +namespace Zotlabs\Module; + +use Zotlabs\Lib\Zotfinger; +use Zotlabs\Zot6\HTTPSig; + +class Zot_probe extends \Zotlabs\Web\Controller { + + function get() { + + $o .= '<h3>Zot6 Probe Diagnostic</h3>'; + + $o .= '<form action="zot_probe" method="get">'; + $o .= 'Lookup URI: <input type="text" style="width: 250px;" name="addr" value="' . $_GET['addr'] .'" /><br>'; + $o .= '<input type="submit" name="submit" value="Submit" /></form>'; + + $o .= '<br /><br />'; + + if(x($_GET,'addr')) { + $addr = $_GET['addr']; + + + $x = Zotfinger::exec($addr); + + $o .= '<pre>' . htmlspecialchars(print_array($x)) . '</pre>'; + + $headers = 'Accept: application/x-zot+json, application/jrd+json, application/json'; + + $redirects = 0; + $x = z_fetch_url($addr,true,$redirects, [ 'headers' => [ $headers ]]); + + if($x['success']) { + + $o .= '<pre>' . htmlspecialchars($x['header']) . '</pre>' . EOL; + + $o .= 'verify returns: ' . str_replace("\n",EOL,print_r(HTTPSig::verify($x),true)) . EOL; + + $o .= '<pre>' . htmlspecialchars(json_encode(json_decode($x['body']),JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES)) . '</pre>' . EOL; + + } + + } + return $o; + } + +} diff --git a/Zotlabs/Render/Comanche.php b/Zotlabs/Render/Comanche.php index fb400b6fe..cf87cc7d7 100644 --- a/Zotlabs/Render/Comanche.php +++ b/Zotlabs/Render/Comanche.php @@ -441,7 +441,7 @@ class Comanche { $path = 'view/js/jquery.js'; break; case 'bootstrap': - $path = 'library/bootstrap/js/bootstrap.min.js'; + $path = 'vendor/twbs/bootstrap/dist/js/bootstrap.bundle.min.js'; break; case 'foundation': $path = 'library/foundation/js/foundation.js'; @@ -466,7 +466,7 @@ class Comanche { switch($s) { case 'bootstrap': - $path = 'library/bootstrap/css/bootstrap.min.css'; + $path = 'vendor/twbs/bootstrap/dist/css/bootstrap.min.css'; break; case 'foundation': $path = 'library/foundation/css/foundation.min.css'; @@ -528,18 +528,32 @@ class Comanche { $clsname = ucfirst($name); $nsname = "\\Zotlabs\\Widget\\" . $clsname; - if(file_exists('Zotlabs/SiteWidget/' . $clsname . '.php')) - require_once('Zotlabs/SiteWidget/' . $clsname . '.php'); - elseif(file_exists('widget/' . $clsname . '/' . $clsname . '.php')) - require_once('widget/' . $clsname . '/' . $clsname . '.php'); - elseif(file_exists('Zotlabs/Widget/' . $clsname . '.php')) - require_once('Zotlabs/Widget/' . $clsname . '.php'); - else { - $pth = theme_include($clsname . '.php'); - if($pth) { - require_once($pth); + $found = false; + $widgets = \Zotlabs\Extend\Widget::get(); + if($widgets) { + foreach($widgets as $widget) { + if(is_array($widget) && strtolower($widget[1]) === strtolower($name) && file_exists($widget[0])) { + require_once($widget[0]); + $found = true; + } } } + + if(! $found) { + if(file_exists('Zotlabs/SiteWidget/' . $clsname . '.php')) + require_once('Zotlabs/SiteWidget/' . $clsname . '.php'); + elseif(file_exists('widget/' . $clsname . '/' . $clsname . '.php')) + require_once('widget/' . $clsname . '/' . $clsname . '.php'); + elseif(file_exists('Zotlabs/Widget/' . $clsname . '.php')) + require_once('Zotlabs/Widget/' . $clsname . '.php'); + else { + $pth = theme_include($clsname . '.php'); + if($pth) { + require_once($pth); + } + } + } + if(class_exists($nsname)) { $x = new $nsname; $f = 'widget'; diff --git a/Zotlabs/Render/SmartyTemplate.php b/Zotlabs/Render/SmartyTemplate.php index ffe58e286..f14d63064 100755 --- a/Zotlabs/Render/SmartyTemplate.php +++ b/Zotlabs/Render/SmartyTemplate.php @@ -64,17 +64,20 @@ class SmartyTemplate implements TemplateEngine { public function get_intltext_template($file, $root='') { $lang = \App::$language; - - if(file_exists("view/$lang/$file")) - $template_file = "view/$lang/$file"; - elseif(file_exists("view/en/$file")) - $template_file = "view/en/$file"; - else - $template_file = theme_include($file,$root); + if ($root != '' && substr($root,-1) != '/' ) { + $root .= '/'; + } + foreach (Array( + $root."view/$lang/$file", + $root."view/en/$file", + '' + ) as $template_file) { + if (is_file($template_file)) { break; } + } + if ($template_file=='') {$template_file = theme_include($file,$root);} if($template_file) { $template = new SmartyInterface(); $template->filename = $template_file; - return $template; } return ""; diff --git a/Zotlabs/Update/_1217.php b/Zotlabs/Update/_1217.php new file mode 100644 index 000000000..15d2d06b3 --- /dev/null +++ b/Zotlabs/Update/_1217.php @@ -0,0 +1,22 @@ +<?php + +namespace Zotlabs\Update; + +class _1217 { + + function run() { + if(ACTIVE_DBTYPE == DBTYPE_POSTGRES) { + $r = q("ALTER TABLE app ADD app_options smallint NOT NULL DEFAULT '0' "); + } + else { + $r = q("ALTER TABLE app ADD app_options int(11) NOT NULL DEFAULT 0 "); + + } + + if($r) { + return UPDATE_SUCCESS; + } + return UPDATE_FAILED; + } +} + diff --git a/Zotlabs/Update/_1218.php b/Zotlabs/Update/_1218.php new file mode 100644 index 000000000..07c7dba20 --- /dev/null +++ b/Zotlabs/Update/_1218.php @@ -0,0 +1,31 @@ +<?php + +namespace Zotlabs\Update; + +class _1218 { + + function run() { + + if(ACTIVE_DBTYPE == DBTYPE_POSTGRES) { + $r1 = q("ALTER TABLE hubloc add hubloc_id_url text NOT NULL DEFAULT ''"); + $r2 = q("create index \"hubloc_id_url\" on hubloc (\"hubloc_id_url\")"); + $r3 = q("ALTER TABLE hubloc add hubloc_site_id text NOT NULL DEFAULT ''"); + $r4 = q("create index \"hubloc_site_id\" on hubloc (\"hubloc_site_id\")"); + + $r = $r1 && $r2 && $r3 && $r4; + } + + if(ACTIVE_DBTYPE == DBTYPE_MYSQL) { + $r1 = q("ALTER TABLE hubloc add hubloc_id_url varchar(191) NOT NULL, ADD INDEX hubloc_id_url (hubloc_id_url)"); + $r2 = q("ALTER TABLE hubloc add hubloc_site_id varchar(191) NOT NULL, ADD INDEX hubloc_site_id (hubloc_site_id)"); + + $r = $r1 && $r2; + } + + if($r) + return UPDATE_SUCCESS; + return UPDATE_FAILED; + + } + +} diff --git a/Zotlabs/Update/_1219.php b/Zotlabs/Update/_1219.php new file mode 100644 index 000000000..be2534001 --- /dev/null +++ b/Zotlabs/Update/_1219.php @@ -0,0 +1,26 @@ +<?php + +namespace Zotlabs\Update; + +class _1219 { + + function run() { + q("START TRANSACTION"); + + $r = q("DELETE FROM xchan WHERE + xchan_hash like '%s' AND + xchan_network = 'activitypub'", + dbesc(z_root()) . '%' + ); + + if($r) { + q("COMMIT"); + return UPDATE_SUCCESS; + } + else { + q("ROLLBACK"); + return UPDATE_FAILED; + } + } + +} diff --git a/Zotlabs/Update/_1220.php b/Zotlabs/Update/_1220.php new file mode 100644 index 000000000..6ce09c16b --- /dev/null +++ b/Zotlabs/Update/_1220.php @@ -0,0 +1,47 @@ +<?php + +namespace Zotlabs\Update; + +class _1220 { + + function run() { + + if(ACTIVE_DBTYPE == DBTYPE_POSTGRES) { + $r1 = q("CREATE TABLE listeners ( + id serial NOT NULL, + target_id text NOT NULL, + portable_id text NOT NULL, + ltype smallint NOT NULL DEFAULT '0', + PRIMARY KEY (id) +)"); + + $r2 = q("create index \"target_id_idx\" on listeners (\"target_id\")"); + $r3 = q("create index \"portable_id_idx\" on listeners (\"portable_id\")"); + $r4 = q("create index \"ltype_idx\" on listeners (\"ltype\")"); + + $r = $r1 && $r2 && $r3 && $r4; + + } + + if(ACTIVE_DBTYPE == DBTYPE_MYSQL) { + $r = q("CREATE TABLE IF NOT EXISTS listeners ( + id int(11) NOT NULL AUTO_INCREMENT, + target_id varchar(191) NOT NULL DEFAULT '', + portable_id varchar(191) NOT NULL DEFAULT '', + ltype int(11) NOT NULL DEFAULT 0, + PRIMARY KEY (id), + KEY target_id (target_id), + KEY portable_id (portable_id), + KEY ltype (ltype) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4"); + + } + + if($r) { + return UPDATE_SUCCESS; + } + return UPDATE_FAILED; + + } + +} diff --git a/Zotlabs/Update/_1221.php b/Zotlabs/Update/_1221.php new file mode 100644 index 000000000..75b400adc --- /dev/null +++ b/Zotlabs/Update/_1221.php @@ -0,0 +1,25 @@ +<?php + +namespace Zotlabs\Update; + +class _1221 { + + function run() { + + q("START TRANSACTION"); + + $r1 = q("ALTER table " . TQUOT . 'groups' . TQUOT . " rename to pgrp "); + $r2 = q("ALTER table " . TQUOT . 'group_member' . TQUOT . " rename to pgrp_member "); + + + if($r1 && $r2) { + q("COMMIT"); + return UPDATE_SUCCESS; + } + + q("ROLLBACK"); + return UPDATE_FAILED; + + } + +} diff --git a/Zotlabs/Update/_1222.php b/Zotlabs/Update/_1222.php new file mode 100644 index 000000000..ad8d03197 --- /dev/null +++ b/Zotlabs/Update/_1222.php @@ -0,0 +1,23 @@ +<?php + +namespace Zotlabs\Update; + +class _1222 { + + function run() { + + q("START TRANSACTION"); + + $r1 = q("DELETE FROM app WHERE app_name = 'Grid' and app_system = 1"); + + if($r1) { + q("COMMIT"); + return UPDATE_SUCCESS; + } + + q("ROLLBACK"); + return UPDATE_FAILED; + + } + +} diff --git a/Zotlabs/Update/_1223.php b/Zotlabs/Update/_1223.php new file mode 100644 index 000000000..c6f05c806 --- /dev/null +++ b/Zotlabs/Update/_1223.php @@ -0,0 +1,23 @@ +<?php + +namespace Zotlabs\Update; + +class _1223 { + + function run() { + + q("START TRANSACTION"); + + $r1 = q("DELETE FROM app WHERE app_name = 'View Bookmarks' and app_system = 1"); + + if($r1) { + q("COMMIT"); + return UPDATE_SUCCESS; + } + + q("ROLLBACK"); + return UPDATE_FAILED; + + } + +} diff --git a/Zotlabs/Update/_1224.php b/Zotlabs/Update/_1224.php new file mode 100644 index 000000000..d160cea5d --- /dev/null +++ b/Zotlabs/Update/_1224.php @@ -0,0 +1,28 @@ +<?php + +namespace Zotlabs\Update; + +class _1224 { + + function run() { + if(ACTIVE_DBTYPE == DBTYPE_MYSQL) { + q("START TRANSACTION"); + + $r1 = q("ALTER TABLE hubloc ALTER hubloc_id_url SET DEFAULT ''"); + $r2 = q("ALTER TABLE hubloc ALTER hubloc_site_id SET DEFAULT ''"); + + if($r1 && $r2) { + q("COMMIT"); + return UPDATE_SUCCESS; + } + + q("ROLLBACK"); + return UPDATE_FAILED; + } + if(ACTIVE_DBTYPE == DBTYPE_POSTGRES) { + return UPDATE_SUCCESS; + } + + } + +} diff --git a/Zotlabs/Web/HTTPSig.php b/Zotlabs/Web/HTTPSig.php index df66ecf5c..f27aa0556 100644 --- a/Zotlabs/Web/HTTPSig.php +++ b/Zotlabs/Web/HTTPSig.php @@ -52,6 +52,7 @@ class HTTPSig { $h = new \Zotlabs\Web\HTTPHeaders($data['header']); $headers = $h->fetcharr(); $body = $data['body']; + $headers['(request-target)'] = $data['request_target']; } else { @@ -60,6 +61,7 @@ class HTTPSig { strtolower($_SERVER['REQUEST_METHOD']) . ' ' . $_SERVER['REQUEST_URI']; $headers['content-type'] = $_SERVER['CONTENT_TYPE']; + $headers['content-length'] = $_SERVER['CONTENT_LENGTH']; foreach($_SERVER as $k => $v) { if(strpos($k,'HTTP_') === 0) { @@ -104,6 +106,17 @@ class HTTPSig { if(strpos($h,'.')) { $spoofable = true; } + 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"); diff --git a/Zotlabs/Web/HttpMeta.php b/Zotlabs/Web/HttpMeta.php index 469a9ed8b..ceaa82162 100644 --- a/Zotlabs/Web/HttpMeta.php +++ b/Zotlabs/Web/HttpMeta.php @@ -54,8 +54,19 @@ class HttpMeta { } } if($this->check_required()) { + $arrayproperties = [ 'og:image' ]; foreach($this->og as $k => $v) { - $o .= '<meta property="' . $k . '" content="' . urlencode($v) . '" />' . "\r\n" ; + if (in_array($k,$arrayproperties)) { + if (is_array($v)) { + foreach ($v as $v2) { + $o .= '<meta property="' . $k . '" content="' . $v2 . '" />' . "\r\n" ; + } + } else { + $o .= '<meta property="' . $k . '" content="' . $v . '" />' . "\r\n" ; + } + } else { + $o .= '<meta property="' . $k . '" content="' . $v . '" />' . "\r\n" ; + } } } if($o) @@ -63,4 +74,4 @@ class HttpMeta { return $o; } -}
\ No newline at end of file +} diff --git a/Zotlabs/Web/Router.php b/Zotlabs/Web/Router.php index fb551e36f..c4db0ef3e 100644 --- a/Zotlabs/Web/Router.php +++ b/Zotlabs/Web/Router.php @@ -2,6 +2,7 @@ namespace Zotlabs\Web; +use Zotlabs\Extend\Route; use Exception; /** @@ -52,14 +53,31 @@ class Router { * First see if we have a plugin which is masquerading as a module. */ - if(is_array(\App::$plugins) && in_array($module,\App::$plugins) && file_exists("addon/{$module}/{$module}.php")) { - include_once("addon/{$module}/{$module}.php"); - if(class_exists($modname)) { - $this->controller = new $modname; - \App::$module_loaded = true; + $routes = Route::get(); + if($routes) { + foreach($routes as $route) { + if(is_array($route) && strtolower($route[1]) === $module) { + include_once($route[0]); + if(class_exists($modname)) { + $this->controller = new $modname; + \App::$module_loaded = true; + } + } } - elseif(function_exists($module . '_module')) { - \App::$module_loaded = true; + } + + // legacy plugins - this can be removed when they have all been converted + + if(! (\App::$module_loaded)) { + if(is_array(\App::$plugins) && in_array($module,\App::$plugins) && file_exists("addon/{$module}/{$module}.php")) { + include_once("addon/{$module}/{$module}.php"); + if(class_exists($modname)) { + $this->controller = new $modname; + \App::$module_loaded = true; + } + elseif(function_exists($module . '_module')) { + \App::$module_loaded = true; + } } } diff --git a/Zotlabs/Web/SubModule.php b/Zotlabs/Web/SubModule.php index 7c8404201..763a55d86 100644 --- a/Zotlabs/Web/SubModule.php +++ b/Zotlabs/Web/SubModule.php @@ -2,6 +2,8 @@ namespace Zotlabs\Web; +use Zotlabs\Extend\Route; + /* * @brief * @@ -31,9 +33,23 @@ class SubModule { $filename = 'Zotlabs/Module/' . ucfirst(argv(0)) . '/'. ucfirst(argv($whicharg)) . '.php'; $modname = '\\Zotlabs\\Module\\' . ucfirst(argv(0)) . '\\' . ucfirst(argv($whicharg)); + if(file_exists($filename)) { $this->controller = new $modname(); } + + $routes = Route::get(); + + if($routes) { + foreach($routes as $route) { + if(is_array($route) && strtolower($route[1]) === strtolower(argv(0)) . '/' . strtolower(argv($whicharg))) { + include_once($route[0]); + if(class_exists($modname)) { + $this->controller = new $modname; + } + } + } + } } /** @@ -43,6 +59,7 @@ class SubModule { * @return boolean|mixed */ function call($method) { + if(! $this->controller) return false; diff --git a/Zotlabs/Widget/Activity_filter.php b/Zotlabs/Widget/Activity_filter.php index fadf39144..4ea0086dd 100644 --- a/Zotlabs/Widget/Activity_filter.php +++ b/Zotlabs/Widget/Activity_filter.php @@ -2,6 +2,8 @@ namespace Zotlabs\Widget; +use Zotlabs\Lib\Apps; + class Activity_filter { function widget($arr) { @@ -44,8 +46,8 @@ class Activity_filter { ]; } - if(feature_enabled(local_channel(),'groups')) { - $groups = q("SELECT * FROM groups WHERE deleted = 0 AND uid = %d ORDER BY gname ASC", + if(Apps::system_app_installed(local_channel(), 'Privacy Groups')) { + $groups = q("SELECT * FROM pgrp WHERE deleted = 0 AND uid = %d ORDER BY gname ASC", intval(local_channel()) ); @@ -180,7 +182,7 @@ class Activity_filter { $arr = ['tabs' => $tabs]; - call_hooks('network_tabs', $arr); + call_hooks('activity_filter', $arr); $o = ''; @@ -190,7 +192,7 @@ class Activity_filter { ]); $o .= replace_macros(get_markup_template('activity_filter_widget.tpl'), [ - '$title' => t('Activity Filters'), + '$title' => t('Stream Filters'), '$reset' => $reset, '$content' => $content, '$name' => $name diff --git a/Zotlabs/Widget/Activity_order.php b/Zotlabs/Widget/Activity_order.php index 7cb08b9e4..1cba1ce8c 100644 --- a/Zotlabs/Widget/Activity_order.php +++ b/Zotlabs/Widget/Activity_order.php @@ -34,6 +34,7 @@ class Activity_order { break; default: $commentord_active = 'active'; + break; } } else { @@ -54,7 +55,7 @@ class Activity_order { } // override order for search, filer and cid results - if(x($_GET,'search') || x($_GET,'file') || (! x($_GET,'pf') && x($_GET,'cid'))) { + if(x($_GET,'search') || x($_GET,'file') || (! x($_GET,'pf') && x($_GET,'cid')) || x($_GET,'verb') || x($_GET,'tag') || x($_GET,'cat')) { $unthreaded_active = 'active'; $commentord_active = $postord_active = 'disabled'; } @@ -109,7 +110,7 @@ class Activity_order { $arr = ['tabs' => $tabs]; - call_hooks('network_tabs', $arr); + call_hooks('activity_order', $arr); $o = ''; @@ -119,7 +120,7 @@ class Activity_order { ]); $o = replace_macros(get_markup_template('common_widget.tpl'), [ - '$title' => t('Activity Order'), + '$title' => t('Stream Order'), '$content' => $content, ]); } diff --git a/Zotlabs/Widget/Appstore.php b/Zotlabs/Widget/Appstore.php index 237707733..6a00ac06a 100644 --- a/Zotlabs/Widget/Appstore.php +++ b/Zotlabs/Widget/Appstore.php @@ -10,9 +10,9 @@ class Appstore { return replace_macros(get_markup_template('appstore.tpl'), [ '$title' => t('App Collections'), '$options' => [ - [ z_root() . '/apps/available', t('Available Apps'), $store ], - [ z_root() . '/apps', t('Installed apps'), 1 - $store ] + [ z_root() . '/apps', t('Installed apps'), 1 - $store ], + [ z_root() . '/apps/available', t('Available Apps'), $store ] ] ]); } -}
\ No newline at end of file +} diff --git a/Zotlabs/Widget/Archive.php b/Zotlabs/Widget/Archive.php index c151ca563..9adaac38f 100644 --- a/Zotlabs/Widget/Archive.php +++ b/Zotlabs/Widget/Archive.php @@ -22,12 +22,12 @@ class Archive { return ''; $wall = ((array_key_exists('wall', $arr)) ? intval($arr['wall']) : 0); + $wall = ((array_key_exists('articles', $arr)) ? 2 : $wall); + $style = ((array_key_exists('style', $arr)) ? $arr['style'] : 'select'); $showend = ((get_pconfig($uid,'system','archive_show_end_date')) ? true : false); $mindate = get_pconfig($uid,'system','archive_mindate'); - $visible_years = get_pconfig($uid,'system','archive_visible_years'); - if(! $visible_years) - $visible_years = 5; + $visible_years = get_pconfig($uid,'system','archive_visible_years',5); $url = z_root() . '/' . \App::$cmd; diff --git a/Zotlabs/Widget/Categories.php b/Zotlabs/Widget/Categories.php index 9bfa9742a..27d4b5980 100644 --- a/Zotlabs/Widget/Categories.php +++ b/Zotlabs/Widget/Categories.php @@ -2,6 +2,9 @@ namespace Zotlabs\Widget; +use App; +use Zotlabs\Lib\Apps; + require_once('include/contact_widgets.php'); class Categories { @@ -10,22 +13,22 @@ class Categories { $cards = ((array_key_exists('cards',$arr) && $arr['cards']) ? true : false); - if(($cards) && (! feature_enabled(\App::$profile['profile_uid'],'cards'))) + if(($cards) && (! Apps::system_app_installed(App::$profile['profile_uid'], 'Cards'))) return ''; $articles = ((array_key_exists('articles',$arr) && $arr['articles']) ? true : false); - if(($articles) && (! feature_enabled(\App::$profile['profile_uid'],'articles'))) + if(($articles) && (! feature_enabled(App::$profile['profile_uid'],'articles'))) return ''; - if((! \App::$profile['profile_uid']) - || (! perm_is_allowed(\App::$profile['profile_uid'],get_observer_hash(),(($cards || $articles) ? 'view_pages' : 'view_stream')))) { + if((! App::$profile['profile_uid']) + || (! perm_is_allowed(App::$profile['profile_uid'],get_observer_hash(),(($cards || $articles) ? 'view_pages' : 'view_stream')))) { return ''; } $cat = ((x($_REQUEST,'cat')) ? htmlspecialchars($_REQUEST['cat'],ENT_COMPAT,'UTF-8') : ''); - $srchurl = (($cards) ? \App::$argv[0] . '/' . \App::$argv[1] : \App::$query_string); + $srchurl = (($cards) ? App::$argv[0] . '/' . App::$argv[1] : App::$query_string); $srchurl = rtrim(preg_replace('/cat\=[^\&].*?(\&|$)/is','',$srchurl),'&'); $srchurl = str_replace(array('?f=','&f='),array('',''),$srchurl); diff --git a/Zotlabs/Widget/Cover_photo.php b/Zotlabs/Widget/Cover_photo.php index d2eb1be92..955048992 100644 --- a/Zotlabs/Widget/Cover_photo.php +++ b/Zotlabs/Widget/Cover_photo.php @@ -20,6 +20,16 @@ class Cover_photo { if(! $channel_id) return ''; + // only show cover photos once per login session + $hide_cover = false; + if(array_key_exists('channels_visited',$_SESSION) && is_array($_SESSION['channels_visited']) && in_array($channel_id,$_SESSION['channels_visited'])) { + $hide_cover = true; + } + if(! array_key_exists('channels_visited',$_SESSION)) { + $_SESSION['channels_visited'] = []; + } + $_SESSION['channels_visited'][] = $channel_id; + $channel = channelx_by_n($channel_id); if(array_key_exists('style', $arr) && isset($arr['style'])) @@ -45,6 +55,7 @@ class Cover_photo { $c = get_cover_photo($channel_id,'html'); if($c) { + $c = str_replace('src=', 'data-src=', $c); $photo_html = (($style) ? str_replace('alt=',' style="' . $style . '" alt=',$c) : $c); $o = replace_macros(get_markup_template('cover_photo_widget.tpl'),array( @@ -52,6 +63,7 @@ class Cover_photo { '$title' => $title, '$subtitle' => $subtitle, '$hovertitle' => t('Click to show more'), + '$hide_cover' => $hide_cover )); } return $o; diff --git a/Zotlabs/Widget/Newmember.php b/Zotlabs/Widget/Newmember.php index 1a4b575b9..224f7a8a2 100644 --- a/Zotlabs/Widget/Newmember.php +++ b/Zotlabs/Widget/Newmember.php @@ -17,7 +17,14 @@ class Newmember { if(! $a) return EMPTY_STR; - if(! feature_enabled(local_channel(),'start_menu')) + if($a['account_created'] > datetime_convert('','','now - 60 days')) { + $enabled = get_pconfig(local_channel(), 'system', 'start_menu', 1); + } + else { + $enabled = get_pconfig(local_channel(), 'system', 'start_menu', 0); + } + + if(! $enabled) return EMPTY_STR; $options = [ @@ -44,7 +51,13 @@ class Newmember { t('Miscellaneous'), [ 'settings' => t('Settings'), - 'help' => t('Documentation'), + 'help' => t('Documentation'), + ], + + t('Missing Features?'), + [ + 'apps' => t('Pin apps to navigation bar'), + 'apps/available' => t('Install more apps') ] ]; diff --git a/Zotlabs/Widget/Notes.php b/Zotlabs/Widget/Notes.php index 5c83a550f..238008d81 100644 --- a/Zotlabs/Widget/Notes.php +++ b/Zotlabs/Widget/Notes.php @@ -2,20 +2,26 @@ namespace Zotlabs\Widget; +use Zotlabs\Lib\Apps; + class Notes { function widget($arr) { if(! local_channel()) - return ''; - if(! feature_enabled(local_channel(),'private_notes')) - return ''; + return EMPTY_STR; + + if(! Apps::system_app_installed(local_channel(), 'Notes')) + return EMPTY_STR; $text = get_pconfig(local_channel(),'notes','text'); - $o = replace_macros(get_markup_template('notes.tpl'), array( + $tpl = get_markup_template('notes.tpl'); + + $o = replace_macros($tpl, array( '$banner' => t('Notes'), '$text' => $text, '$save' => t('Save'), + '$app' => ((isset($arr['app'])) ? true : false) )); return $o; diff --git a/Zotlabs/Widget/Notifications.php b/Zotlabs/Widget/Notifications.php index a4cf4e706..0f9f609e4 100644 --- a/Zotlabs/Widget/Notifications.php +++ b/Zotlabs/Widget/Notifications.php @@ -160,7 +160,7 @@ class Notifications { '$notifications' => $notifications, '$no_notifications' => t('Sorry, you have got no notifications at the moment'), '$loading' => t('Loading'), - '$startpage' => get_pconfig(local_channel(), 'system', 'startpage') + '$startpage' => $channel['channel_startpage'] )); return $o; diff --git a/Zotlabs/Widget/Settings_menu.php b/Zotlabs/Widget/Settings_menu.php index f35d6f147..c537c3835 100644 --- a/Zotlabs/Widget/Settings_menu.php +++ b/Zotlabs/Widget/Settings_menu.php @@ -9,15 +9,12 @@ class Settings_menu { if(! local_channel()) return; - $channel = \App::get_channel(); $abook_self_id = 0; // Retrieve the 'self' address book entry for use in the auto-permissions link - $role = get_pconfig(local_channel(),'system','permissions_role'); - $abk = q("select abook_id from abook where abook_channel = %d and abook_self = 1 limit 1", intval(local_channel()) ); @@ -45,19 +42,6 @@ class Settings_menu { ); - if(get_account_techlevel() > 0 && get_features()) { - $tabs[] = array( - 'label' => t('Additional features'), - 'url' => z_root().'/settings/features', - 'selected' => ((argv(1) === 'features') ? 'active' : ''), - ); - } - - $tabs[] = array( - 'label' => t('Addon settings'), - 'url' => z_root().'/settings/featured', - 'selected' => ((argv(1) === 'featured') ? 'active' : ''), - ); $tabs[] = array( 'label' => t('Display settings'), @@ -65,6 +49,12 @@ class Settings_menu { 'selected' => ((argv(1) === 'display') ? 'active' : ''), ); + $tabs[] = array( + 'label' => t('Addon settings'), + 'url' => z_root().'/settings/featured', + 'selected' => ((argv(1) === 'featured') ? 'active' : ''), + ); + if($hublocs) { $tabs[] = array( 'label' => t('Manage locations'), @@ -73,69 +63,6 @@ class Settings_menu { ); } - $tabs[] = array( - 'label' => t('Export channel'), - 'url' => z_root() . '/uexport', - 'selected' => '' - ); - - if(feature_enabled(local_channel(),'oauth_clients')) { - $tabs[] = array( - 'label' => t('OAuth1 apps'), - 'url' => z_root() . '/settings/oauth', - 'selected' => ((argv(1) === 'oauth') ? 'active' : ''), - ); - } - - if(feature_enabled(local_channel(),'oauth2_clients')) { - $tabs[] = array( - 'label' => t('OAuth2 apps'), - 'url' => z_root() . '/settings/oauth2', - 'selected' => ((argv(1) === 'oauth2') ? 'active' : ''), - ); - } - - if(feature_enabled(local_channel(),'access_tokens')) { - $tabs[] = array( - 'label' => t('Guest Access Tokens'), - 'url' => z_root() . '/settings/tokens', - 'selected' => ((argv(1) === 'tokens') ? 'active' : ''), - ); - } - - if(feature_enabled(local_channel(),'permcats')) { - $tabs[] = array( - 'label' => t('Permission Categories'), - 'url' => z_root() . '/settings/permcats', - 'selected' => ((argv(1) === 'permcats') ? 'active' : ''), - ); - } - - - if($role === false || $role === 'custom') { - $tabs[] = array( - 'label' => t('Connection Default Permissions'), - 'url' => z_root() . '/defperms', - 'selected' => '' - ); - } - - if(feature_enabled(local_channel(),'premium_channel')) { - $tabs[] = array( - 'label' => t('Premium Channel Settings'), - 'url' => z_root() . '/connect/' . $channel['channel_address'], - 'selected' => '' - ); - } - - if(feature_enabled(local_channel(),'channel_sources')) { - $tabs[] = array( - 'label' => t('Channel Sources'), - 'url' => z_root() . '/sources', - 'selected' => '' - ); - } - $tabtpl = get_markup_template("generic_links_widget.tpl"); return replace_macros($tabtpl, array( '$title' => t('Settings'), @@ -144,4 +71,4 @@ class Settings_menu { )); } -}
\ No newline at end of file +} diff --git a/Zotlabs/Widget/Wiki_list.php b/Zotlabs/Widget/Wiki_list.php index 62f32dbf0..c8d83cbe8 100644 --- a/Zotlabs/Widget/Wiki_list.php +++ b/Zotlabs/Widget/Wiki_list.php @@ -6,13 +6,17 @@ class Wiki_list { function widget($arr) { + if(argc() < 3) { + return; + } + $channel = channelx_by_n(\App::$profile_uid); $wikis = \Zotlabs\Lib\NativeWiki::listwikis($channel,get_observer_hash()); if($wikis) { return replace_macros(get_markup_template('wikilist_widget.tpl'), array( - '$header' => t('Wiki List'), + '$header' => t('Wikis'), '$channel' => $channel['channel_address'], '$wikis' => $wikis['wikis'] )); diff --git a/Zotlabs/Widget/Wiki_page_history.php b/Zotlabs/Widget/Wiki_page_history.php index dcec9a037..dbb322dc3 100644 --- a/Zotlabs/Widget/Wiki_page_history.php +++ b/Zotlabs/Widget/Wiki_page_history.php @@ -20,7 +20,10 @@ class Wiki_page_history { '$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') )); } diff --git a/Zotlabs/Widget/Wiki_pages.php b/Zotlabs/Widget/Wiki_pages.php index 06b32b5f5..f178c940d 100644 --- a/Zotlabs/Widget/Wiki_pages.php +++ b/Zotlabs/Widget/Wiki_pages.php @@ -2,6 +2,7 @@ namespace Zotlabs\Widget; +use Zotlabs\Lib\NativeWiki; class Wiki_pages { @@ -10,7 +11,7 @@ class Wiki_pages { return; $c = channelx_by_nick(argv(1)); - $w = \Zotlabs\Lib\NativeWiki::exists_by_name($c['channel_id'],urldecode(argv(2))); + $w = \Zotlabs\Lib\NativeWiki::exists_by_name($c['channel_id'],NativeWiki::name_decode(argv(2))); $arr = array( 'resource_id' => $w['resource_id'], 'channel_id' => $c['channel_id'], @@ -21,8 +22,9 @@ class Wiki_pages { $can_create = perm_is_allowed(\App::$profile['uid'],get_observer_hash(),'write_wiki'); $can_delete = ((local_channel() && (local_channel() == \App::$profile['uid'])) ? true : false); - $pageName = addslashes(escape_tags(urldecode(argv(3)))); + $pageName = NativeWiki::name_decode(escape_tags(argv(3))); + $wikiname = $w['urlName']; return replace_macros(get_markup_template('wiki_page_not_found.tpl'), array( '$resource_id' => $arr['resource_id'], '$channel_address' => $arr['channel_address'], @@ -48,7 +50,7 @@ class Wiki_pages { if(! $arr['resource_id']) { $c = channelx_by_nick(argv(1)); - $w = \Zotlabs\Lib\NativeWiki::exists_by_name($c['channel_id'],urldecode(argv(2))); + $w = \Zotlabs\Lib\NativeWiki::exists_by_name($c['channel_id'],NativeWiki::name_decode(argv(2))); $arr = array( 'resource_id' => $w['resource_id'], 'channel_id' => $c['channel_id'], diff --git a/Zotlabs/Zot/Finger.php b/Zotlabs/Zot/Finger.php index 348171bdc..77634777a 100644 --- a/Zotlabs/Zot/Finger.php +++ b/Zotlabs/Zot/Finger.php @@ -55,7 +55,7 @@ class Finger { $r = q("select xchan.*, hubloc.* from xchan left join hubloc on xchan_hash = hubloc_hash - where xchan_addr = '%s' and hubloc_primary = 1 limit 1", + where xchan_addr = '%s' and hubloc_primary = 1 and hubloc_deleted = 0 limit 1", dbesc($xchan_addr) ); @@ -71,6 +71,11 @@ class Finger { $url = 'https://' . $host; } + $m = parse_url($url); + if($m) { + $parsed_host = strtolower($m['host']); + } + $rhs = '/.well-known/zot-info'; $https = ((strpos($url,'https://') === 0) ? true : false); @@ -88,6 +93,8 @@ class Finger { $headers = []; $headers['X-Zot-Channel'] = $channel['channel_address'] . '@' . \App::get_hostname(); $headers['X-Zot-Nonce'] = random_string(); + $headers['Host'] = $parsed_host; + $xhead = \Zotlabs\Web\HTTPSig::create_sig('',$headers,$channel['channel_prvkey'], 'acct:' . $channel['channel_address'] . '@' . \App::get_hostname(),false); diff --git a/Zotlabs/Zot6/Finger.php b/Zotlabs/Zot6/Finger.php new file mode 100644 index 000000000..f1fe41352 --- /dev/null +++ b/Zotlabs/Zot6/Finger.php @@ -0,0 +1,146 @@ +<?php + +namespace Zotlabs\Zot6; + +/** + * @brief Finger + * + */ +class Finger { + + static private $token; + + /** + * @brief Look up information about channel. + * + * @param string $webbie + * does not have to be host qualified e.g. 'foo' is treated as 'foo\@thishub' + * @param array $channel + * (optional), if supplied permissions will be enumerated specifically for $channel + * @param boolean $autofallback + * fallback/failover to http if https connection cannot be established. Default is true. + * + * @return zotinfo array (with 'success' => true) or array('success' => false); + */ + + static public function run($webbie, $channel = null, $autofallback = true) { + + $ret = array('success' => false); + + self::$token = random_string(); + + if (strpos($webbie, '@') === false) { + $address = $webbie; + $host = \App::get_hostname(); + } else { + $address = substr($webbie,0,strpos($webbie,'@')); + $host = substr($webbie,strpos($webbie,'@')+1); + if(strpos($host,'/')) + $host = substr($host,0,strpos($host,'/')); + } + + $xchan_addr = $address . '@' . $host; + + if ((! $address) || (! $xchan_addr)) { + logger('zot_finger: no address :' . $webbie); + + return $ret; + } + + logger('using xchan_addr: ' . $xchan_addr, LOGGER_DATA, LOG_DEBUG); + + // potential issue here; the xchan_addr points to the primary hub. + // The webbie we were called with may not, so it might not be found + // unless we query for hubloc_addr instead of xchan_addr + + $r = q("select xchan.*, hubloc.* from xchan + left join hubloc on xchan_hash = hubloc_hash + where xchan_addr = '%s' and hubloc_primary = 1 limit 1", + dbesc($xchan_addr) + ); + + if($r) { + $url = $r[0]['hubloc_url']; + + if($r[0]['hubloc_network'] && $r[0]['hubloc_network'] !== 'zot') { + logger('zot_finger: alternate network: ' . $webbie); + logger('url: ' . $url . ', net: ' . var_export($r[0]['hubloc_network'],true), LOGGER_DATA, LOG_DEBUG); + return $ret; + } + } else { + $url = 'https://' . $host; + } + + $rhs = '/.well-known/zot-info'; + $https = ((strpos($url,'https://') === 0) ? true : false); + + logger('zot_finger: ' . $address . ' at ' . $url, LOGGER_DEBUG); + + if ($channel) { + $postvars = array( + 'address' => $address, + 'target' => $channel['channel_guid'], + 'target_sig' => $channel['channel_guid_sig'], + 'key' => $channel['channel_pubkey'], + 'token' => self::$token + ); + + $headers = []; + $headers['X-Zot-Channel'] = $channel['channel_address'] . '@' . \App::get_hostname(); + $headers['X-Zot-Nonce'] = random_string(); + $xhead = \Zotlabs\Web\HTTPSig::create_sig('',$headers,$channel['channel_prvkey'], + 'acct:' . $channel['channel_address'] . '@' . \App::get_hostname(),false); + + $retries = 0; + + $result = z_post_url($url . $rhs,$postvars,$retries, [ 'headers' => $xhead ]); + + if ((! $result['success']) && ($autofallback)) { + if ($https) { + logger('zot_finger: https failed. falling back to http'); + $result = z_post_url('http://' . $host . $rhs,$postvars, $retries, [ 'headers' => $xhead ]); + } + } + } + else { + $rhs .= '?f=&address=' . urlencode($address) . '&token=' . self::$token; + + $result = z_fetch_url($url . $rhs); + if((! $result['success']) && ($autofallback)) { + if($https) { + logger('zot_finger: https failed. falling back to http'); + $result = z_fetch_url('http://' . $host . $rhs); + } + } + } + + if(! $result['success']) { + logger('zot_finger: no results'); + + return $ret; + } + + $x = json_decode($result['body'], true); + + $verify = \Zotlabs\Web\HTTPSig::verify($result,(($x) ? $x['key'] : '')); + + if($x && (! $verify['header_valid'])) { + $signed_token = ((is_array($x) && array_key_exists('signed_token', $x)) ? $x['signed_token'] : null); + if($signed_token) { + $valid = zot_verify('token.' . self::$token, base64url_decode($signed_token), $x['key']); + if(! $valid) { + logger('invalid signed token: ' . $url . $rhs, LOGGER_NORMAL, LOG_ERR); + + return $ret; + } + } + else { + logger('No signed token from ' . $url . $rhs, LOGGER_NORMAL, LOG_WARNING); + return $ret; + } + } + + return $x; + } + +} diff --git a/Zotlabs/Zot6/HTTPSig.php b/Zotlabs/Zot6/HTTPSig.php new file mode 100644 index 000000000..a0f0d3500 --- /dev/null +++ b/Zotlabs/Zot6/HTTPSig.php @@ -0,0 +1,507 @@ +<?php + +namespace Zotlabs\Zot6; + +use Zotlabs\Lib\ActivityStreams; +use Zotlabs\Lib\Webfinger; +use Zotlabs\Web\HTTPHeaders; + +/** + * @brief Implements HTTP Signatures per draft-cavage-http-signatures-10. + * + * @see https://tools.ietf.org/html/draft-cavage-http-signatures-10 + */ + +class HTTPSig { + + /** + * @brief RFC5843 + * + * @see https://tools.ietf.org/html/rfc5843 + * + * @param string $body The value to create the digest for + * @param string $alg hash algorithm (one of 'sha256','sha512') + * @return string The generated digest header string for $body + */ + + static function generate_digest_header($body,$alg = 'sha256') { + + $digest = base64_encode(hash($alg, $body, true)); + switch($alg) { + case 'sha512': + return 'SHA-512=' . $digest; + case 'sha256': + default: + return 'SHA-256=' . $digest; + break; + } + } + + static function find_headers($data,&$body) { + + // decide if $data arrived via controller submission or curl + + if(is_array($data) && $data['header']) { + if(! $data['success']) + return []; + + $h = new HTTPHeaders($data['header']); + $headers = $h->fetcharr(); + $body = $data['body']; + } + + else { + $headers = []; + $headers['(request-target)'] = strtolower($_SERVER['REQUEST_METHOD']) . ' ' . $_SERVER['REQUEST_URI']; + $headers['content-type'] = $_SERVER['CONTENT_TYPE']; + + foreach($_SERVER as $k => $v) { + if(strpos($k,'HTTP_') === 0) { + $field = str_replace('_','-',strtolower(substr($k,5))); + $headers[$field] = $v; + } + } + } + + //logger('SERVER: ' . print_r($_SERVER,true), LOGGER_ALL); + + //logger('headers: ' . print_r($headers,true), LOGGER_ALL); + + return $headers; + } + + + // See draft-cavage-http-signatures-10 + + static function verify($data,$key = '') { + + $body = $data; + $headers = null; + + $result = [ + 'signer' => '', + 'portable_id' => '', + 'header_signed' => false, + 'header_valid' => false, + 'content_signed' => false, + 'content_valid' => false + ]; + + + $headers = self::find_headers($data,$body); + + if(! $headers) + return $result; + + $sig_block = null; + + if(array_key_exists('signature',$headers)) { + $sig_block = self::parse_sigheader($headers['signature']); + } + elseif(array_key_exists('authorization',$headers)) { + $sig_block = self::parse_sigheader($headers['authorization']); + } + + if(! $sig_block) { + logger('no signature provided.', LOGGER_DEBUG); + return $result; + } + + // Warning: This log statement includes binary data + // logger('sig_block: ' . print_r($sig_block,true), LOGGER_DATA); + + $result['header_signed'] = true; + + $signed_headers = $sig_block['headers']; + if(! $signed_headers) + $signed_headers = [ 'date' ]; + + $signed_data = ''; + foreach($signed_headers as $h) { + if(array_key_exists($h,$headers)) { + $signed_data .= $h . ': ' . $headers[$h] . "\n"; + } + } + $signed_data = rtrim($signed_data,"\n"); + + $algorithm = null; + if($sig_block['algorithm'] === 'rsa-sha256') { + $algorithm = 'sha256'; + } + if($sig_block['algorithm'] === 'rsa-sha512') { + $algorithm = 'sha512'; + } + + if(! array_key_exists('keyId',$sig_block)) + return $result; + + $result['signer'] = $sig_block['keyId']; + + $key = self::get_key($key,$result['signer']); + + if(! ($key && $key['public_key'])) { + return $result; + } + + $x = rsa_verify($signed_data,$sig_block['signature'],$key['public_key'],$algorithm); + + logger('verified: ' . $x, LOGGER_DEBUG); + + if(! $x) + return $result; + + $result['portable_id'] = $key['portable_id']; + $result['header_valid'] = true; + + if(in_array('digest',$signed_headers)) { + $result['content_signed'] = true; + $digest = explode('=', $headers['digest'], 2); + if($digest[0] === 'SHA-256') + $hashalg = 'sha256'; + if($digest[0] === 'SHA-512') + $hashalg = 'sha512'; + + if(base64_encode(hash($hashalg,$body,true)) === $digest[1]) { + $result['content_valid'] = true; + } + + logger('Content_Valid: ' . (($result['content_valid']) ? 'true' : 'false')); + } + + return $result; + } + + static function get_key($key,$id) { + + if($key) { + if(function_exists($key)) { + return $key($id); + } + return [ 'public_key' => $key ]; + } + + $key = self::get_webfinger_key($id); + + if(! $key) { + $key = self::get_activitystreams_key($id); + } + + return $key; + + } + + + function convertKey($key) { + + if(strstr($key,'RSA ')) { + return rsatopem($key); + } + elseif(substr($key,0,5) === 'data:') { + return convert_salmon_key($key); + } + else { + return $key; + } + + } + + + /** + * @brief + * + * @param string $id + * @return boolean|string + * false if no pub key found, otherwise return the pub key + */ + + function get_activitystreams_key($id) { + + $x = q("select * from xchan left join hubloc on xchan_hash = hubloc_hash where hubloc_addr = '%s' or hubloc_id_url = '%s' limit 1", + dbesc(str_replace('acct:','',$id)), + dbesc($id) + ); + + if($x && $x[0]['xchan_pubkey']) { + return [ 'portable_id' => $x[0]['xchan_hash'], 'public_key' => $x[0]['xchan_pubkey'] , 'hubloc' => $x[0] ]; + } + + $r = ActivityStreams::fetch_property($id); + + if($r) { + if(array_key_exists('publicKey',$j) && array_key_exists('publicKeyPem',$j['publicKey']) && array_key_exists('id',$j['publicKey'])) { + if($j['publicKey']['id'] === $id || $j['id'] === $id) { + return [ 'public_key' => self::convertKey($j['publicKey']['publicKeyPem']), 'portable_id' => '', 'hubloc' => [] ]; + } + } + } + + return false; + } + + + function get_webfinger_key($id) { + + $x = q("select * from xchan left join hubloc on xchan_hash = hubloc_hash where hubloc_addr = '%s' or hubloc_id_url = '%s' limit 1", + dbesc(str_replace('acct:','',$id)), + dbesc($id) + ); + + if($x && $x[0]['xchan_pubkey']) { + return [ 'portable_id' => $x[0]['xchan_hash'], 'public_key' => $x[0]['xchan_pubkey'] , 'hubloc' => $x[0] ]; + } + + $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'])) { + $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))) { + continue; + } + if($l['rel'] === 'magic-public-key' && array_key_exists('href',$l) && $key['public_key'] === EMPTY_STR) { + $key['public_key'] = self::convertKey($l['href']); + } + } + } + } + + return (($key['public_key']) ? $key : false); + } + + + function get_zotfinger_key($id) { + + $x = q("select * from xchan left join hubloc on xchan_hash = hubloc_hash where hubloc_addr = '%s' or hubloc_id_url = '%s' limit 1", + dbesc(str_replace('acct:','',$id)), + dbesc($id) + ); + if($x && $x[0]['xchan_pubkey']) { + return [ 'portable_id' => $x[0]['xchan_hash'], 'public_key' => $x[0]['xchan_pubkey'] , 'hubloc' => $x[0] ]; + } + + $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'])) { + $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))) { + continue; + } + if($l['rel'] === 'http://purl.org/zot/protocol/6.0' && array_key_exists('href',$l) && $l['href'] !== EMPTY_STR) { + $z = \Zotlabs\Lib\Zotfinger::exec($l['href']); + if($z) { + $i = Zotlabs\Lib\Libzot::import_xchan($z['data']); + 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' limit 1", + dbesc($l['href']) + ); + if($x) { + $key['hubloc'] = $x[0]; + } + } + } + } + if($l['rel'] === 'magic-public-key' && array_key_exists('href',$l) && $key['public_key'] === EMPTY_STR) { + $key['public_key'] = self::convertKey($l['href']); + } + } + } + } + + return (($key['public_key']) ? $key : false); + } + + + /** + * @brief + * + * @param array $head + * @param string $prvkey + * @param string $keyid (optional, default '') + * @param boolean $auth (optional, default false) + * @param string $alg (optional, default 'sha256') + * @param array $encryption [ 'key', 'algorithm' ] or false + * @return array + */ + static function create_sig($head, $prvkey, $keyid = EMPTY_STR, $auth = false, $alg = 'sha256', $encryption = false ) { + + $return_headers = []; + + if($alg === 'sha256') { + $algorithm = 'rsa-sha256'; + } + if($alg === 'sha512') { + $algorithm = 'rsa-sha512'; + } + + $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)) { + $headerval = 'iv="' . $x['iv'] . '",key="' . $x['key'] . '",alg="' . $x['alg'] . '",data="' . $x['data'] . '"'; + } + } + + if($auth) { + $sighead = 'Authorization: Signature ' . $headerval; + } + else { + $sighead = 'Signature: ' . $headerval; + } + + if($head) { + foreach($head as $k => $v) { + // strip the request-target virtual header from the output headers + if($k === '(request-target)') { + continue; + } + $return_headers[] = $k . ': ' . $v; + } + } + $return_headers[] = $sighead; + + return $return_headers; + } + + /** + * @brief set headers + * + * @param array $headers + * @return void + */ + + + static function set_headers($headers) { + if($headers && is_array($headers)) { + foreach($headers as $h) { + header($h); + } + } + } + + + /** + * @brief + * + * @param array $head + * @param string $prvkey + * @param string $alg (optional) default 'sha256' + * @return array + */ + + static function sign($head, $prvkey, $alg = 'sha256') { + + $ret = []; + + $headers = ''; + $fields = ''; + + if($head) { + foreach($head as $k => $v) { + $headers .= strtolower($k) . ': ' . trim($v) . "\n"; + if($fields) + $fields .= ' '; + + $fields .= strtolower($k); + } + // strip the trailing linefeed + $headers = rtrim($headers,"\n"); + } + + $sig = base64_encode(rsa_sign($headers,$prvkey,$alg)); + + $ret['headers'] = $fields; + $ret['signature'] = $sig; + + return $ret; + } + + /** + * @brief + * + * @param string $header + * @return array associate array with + * - \e string \b keyID + * - \e string \b algorithm + * - \e array \b headers + * - \e string \b signature + */ + + static function parse_sigheader($header) { + + $ret = []; + $matches = []; + + // if the header is encrypted, decrypt with (default) site private key and continue + + if(preg_match('/iv="(.*?)"/ism',$header,$matches)) + $header = self::decrypt_sigheader($header); + + if(preg_match('/keyId="(.*?)"/ism',$header,$matches)) + $ret['keyId'] = $matches[1]; + if(preg_match('/algorithm="(.*?)"/ism',$header,$matches)) + $ret['algorithm'] = $matches[1]; + 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(($ret['signature']) && ($ret['algorithm']) && (! $ret['headers'])) + $ret['headers'] = [ 'date' ]; + + return $ret; + } + + + /** + * @brief + * + * @param string $header + * @param string $prvkey (optional), if not set use site private key + * @return array|string associative array, empty string if failue + * - \e string \b iv + * - \e string \b key + * - \e string \b alg + * - \e string \b data + */ + + static function decrypt_sigheader($header, $prvkey = null) { + + $iv = $key = $alg = $data = null; + + if(! $prvkey) { + $prvkey = get_config('system', 'prvkey'); + } + + $matches = []; + + if(preg_match('/iv="(.*?)"/ism',$header,$matches)) + $iv = $matches[1]; + if(preg_match('/key="(.*?)"/ism',$header,$matches)) + $key = $matches[1]; + if(preg_match('/alg="(.*?)"/ism',$header,$matches)) + $alg = $matches[1]; + 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); + } + + return ''; + } + +} diff --git a/Zotlabs/Zot6/IHandler.php b/Zotlabs/Zot6/IHandler.php new file mode 100644 index 000000000..53b6caa89 --- /dev/null +++ b/Zotlabs/Zot6/IHandler.php @@ -0,0 +1,18 @@ +<?php + +namespace Zotlabs\Zot6; + +interface IHandler { + + function Notify($data,$hub); + + function Request($data,$hub); + + function Rekey($sender,$data,$hub); + + function Refresh($sender,$recipients,$hub); + + function Purge($sender,$recipients,$hub); + +} + diff --git a/Zotlabs/Zot6/Receiver.php b/Zotlabs/Zot6/Receiver.php new file mode 100644 index 000000000..4f26e2b0c --- /dev/null +++ b/Zotlabs/Zot6/Receiver.php @@ -0,0 +1,220 @@ +<?php + +namespace Zotlabs\Zot6; + +use Zotlabs\Lib\Config; +use Zotlabs\Lib\Libzot; +use Zotlabs\Web\HTTPSig; + +class Receiver { + + protected $data; + protected $encrypted; + protected $error; + protected $messagetype; + protected $sender; + protected $site_id; + protected $validated; + protected $recipients; + protected $response; + protected $handler; + protected $prvkey; + protected $rawdata; + protected $sigdata; + + function __construct($handler, $localdata = null) { + + $this->error = false; + $this->validated = false; + $this->messagetype = ''; + $this->response = [ 'success' => false ]; + $this->handler = $handler; + $this->data = null; + $this->rawdata = null; + $this->site_id = null; + $this->prvkey = Config::get('system','prvkey'); + + if($localdata) { + $this->rawdata = $localdata; + } + else { + $this->rawdata = file_get_contents('php://input'); + + // All access to the zot endpoint must use http signatures + + if (! $this->Valid_Httpsig()) { + logger('signature failed'); + $this->error = true; + $this->response['message'] = 'signature invalid'; + return; + } + } + + logger('received raw: ' . print_r($this->rawdata,true), LOGGER_DATA); + + + if ($this->rawdata) { + $this->data = json_decode($this->rawdata,true); + } + else { + $this->error = true; + $this->response['message'] = 'no data'; + } + + logger('received_json: ' . json_encode($this->data,JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES), LOGGER_DATA); + + logger('received: ' . print_r($this->data,true), LOGGER_DATA); + + if ($this->data && is_array($this->data)) { + $this->encrypted = ((array_key_exists('encrypted',$this->data) && intval($this->data['encrypted'])) ? true : false); + + if ($this->encrypted && $this->prvkey) { + $uncrypted = crypto_unencapsulate($this->data,$this->prvkey); + if ($uncrypted) { + $this->data = json_decode($uncrypted,true); + } + else { + $this->error = true; + $this->response['message'] = 'no data'; + } + } + } + } + + + function run() { + + if ($this->error) { + // make timing attacks on the decryption engine a bit more difficult + usleep(mt_rand(10000,100000)); + return($this->response); + } + + if ($this->data) { + if (array_key_exists('type',$this->data)) { + $this->messagetype = $this->data['type']; + } + + if (! $this->messagetype) { + $this->error = true; + $this->response['message'] = 'no datatype'; + return $this->response; + } + + $this->sender = ((array_key_exists('sender',$this->data)) ? $this->data['sender'] : null); + $this->recipients = ((array_key_exists('recipients',$this->data)) ? $this->data['recipients'] : null); + $this->site_id = ((array_key_exists('site_id',$this->data)) ? $this->data['site_id'] : null); + } + + if ($this->sender) { + $result = $this->ValidateSender(); + if (! $result) { + $this->error = true; + return $this->response; + } + } + + return $this->Dispatch(); + } + + function ValidateSender() { + + $hub = Libzot::valid_hub($this->sender,$this->site_id); + + if (! $hub) { + $x = Libzot::register_hub($this->sigdata['signer']); + if($x['success']) { + $hub = Libzot::valid_hub($this->sender,$this->site_id); + } + if(! $hub) { + $this->response['message'] = 'sender unknown'; + return false; + } + } + + if (! check_siteallowed($hub['hubloc_url'])) { + $this->response['message'] = 'forbidden'; + return false; + } + + if (! check_channelallowed($this->sender)) { + $this->response['message'] = 'forbidden'; + return false; + } + + Libzot::update_hub_connected($hub,$this->site_id); + + $this->validated = true; + $this->hub = $hub; + return true; + } + + + function Valid_Httpsig() { + + $result = false; + + $this->sigdata = HTTPSig::verify($this->rawdata); + + if ($this->sigdata && $this->sigdata['header_signed'] && $this->sigdata['header_valid']) { + $result = true; + + // It is OK to not have signed content - not all messages provide content. + // But if it is signed, it has to be valid + + if (($this->sigdata['content_signed']) && (! $this->sigdata['content_valid'])) { + $result = false; + } + } + return $result; + } + + function Dispatch() { + + switch ($this->messagetype) { + + case 'request': + $this->response = $this->handler->Request($this->data,$this->hub); + break; + + case 'purge': + $this->response = $this->handler->Purge($this->sender,$this->recipients,$this->hub); + break; + + case 'refresh': + $this->response = $this->handler->Refresh($this->sender,$this->recipients,$this->hub); + break; + + case 'rekey': + $this->response = $this->handler->Rekey($this->sender, $this->data,$this->hub); + break; + + case 'activity': + case 'response': // upstream message + case 'sync': + default: + $this->response = $this->handler->Notify($this->data,$this->hub); + break; + + } + + logger('response_to_return: ' . print_r($this->response,true),LOGGER_DATA); + + if ($this->encrypted) { + $this->EncryptResponse(); + } + + return($this->response); + } + + function EncryptResponse() { + $algorithm = Libzot::best_algorithm($this->hub['site_crypto']); + if ($algorithm) { + $this->response = crypto_encapsulate(json_encode($this->response),$this->hub['hubloc_sitekey'], $algorithm); + } + } + +} + + + diff --git a/Zotlabs/Zot6/Zot6Handler.php b/Zotlabs/Zot6/Zot6Handler.php new file mode 100644 index 000000000..5597921cc --- /dev/null +++ b/Zotlabs/Zot6/Zot6Handler.php @@ -0,0 +1,266 @@ +<?php + +namespace Zotlabs\Zot6; + +use Zotlabs\Lib\Libzot; +use Zotlabs\Lib\Queue; + +class Zot6Handler implements IHandler { + + function Notify($data,$hub) { + return self::reply_notify($data,$hub); + } + + function Request($data,$hub) { + return self::reply_message_request($data,$hub); + } + + function Rekey($sender,$data,$hub) { + return self::reply_rekey_request($sender,$data,$hub); + } + + function Refresh($sender,$recipients,$hub) { + return self::reply_refresh($sender,$recipients,$hub); + } + + function Purge($sender,$recipients,$hub) { + return self::reply_purge($sender,$recipients,$hub); + } + + + // Implementation of specific methods follows; + // These generally do a small amout of validation and call Libzot + // to do any heavy lifting + + static function reply_notify($data,$hub) { + + $ret = [ 'success' => false ]; + + logger('notify received from ' . $hub['hubloc_url']); + + $x = Libzot::fetch($data); + $ret['delivery_report'] = $x; + + + $ret['success'] = true; + return $ret; + } + + + + /** + * @brief Remote channel info (such as permissions or photo or something) + * has been updated. Grab a fresh copy and sync it. + * + * The difference between refresh and force_refresh is that force_refresh + * unconditionally creates a directory update record, even if no changes were + * detected upon processing. + * + * @param array $sender + * @param array $recipients + * + * @return json_return_and_die() + */ + + static function reply_refresh($sender, $recipients,$hub) { + $ret = array('success' => false); + + if($recipients) { + + // This would be a permissions update, typically for one connection + + foreach ($recipients as $recip) { + $r = q("select channel.*,xchan.* from channel + left join xchan on channel_hash = xchan_hash + where channel_hash ='%s' limit 1", + dbesc($recip) + ); + + $x = Libzot::refresh( [ 'hubloc_id_url' => $hub['hubloc_id_url'] ], $r[0], (($msgtype === 'force_refresh') ? true : false)); + } + } + else { + // system wide refresh + + $x = Libzot::refresh( [ 'hubloc_id_url' => $hub['hubloc_id_url'] ], null, (($msgtype === 'force_refresh') ? true : false)); + } + + $ret['success'] = true; + return $ret; + } + + + + /** + * @brief Process a message request. + * + * If a site receives a comment to a post but finds they have no parent to attach it with, they + * may send a 'request' packet containing the message_id of the missing parent. This is the handler + * for that packet. We will create a message_list array of the entire conversation starting with + * the missing parent and invoke delivery to the sender of the packet. + * + * Zotlabs/Daemon/Deliver.php (for local delivery) and + * mod/post.php???? @fixme (for web delivery) detect the existence of + * this 'message_list' at the destination and split it into individual messages which are + * processed/delivered in order. + * + * + * @param array $data + * @return array + */ + + static function reply_message_request($data,$hub) { + $ret = [ 'success' => false ]; + + $message_id = EMPTY_STR; + + if(array_key_exists('data',$data)) + $ptr = $data['data']; + if(is_array($ptr) && array_key_exists(0,$ptr)) { + $ptr = $ptr[0]; + } + if(is_string($ptr)) { + $message_id = $ptr; + } + if(is_array($ptr) && array_key_exists('id',$ptr)) { + $message_id = $ptr['id']; + } + + if (! $message_id) { + $ret['message'] = 'no message_id'; + logger('no message_id'); + return $ret; + } + + $sender = $hub['hubloc_hash']; + + /* + * Find the local channel in charge of this post (the first and only recipient of the request packet) + */ + + $arr = $data['recipients'][0]; + + $c = q("select * from channel left join xchan on channel_hash = xchan_hash where channel_hash = '%s' limit 1", + dbesc($arr['portable_id']) + ); + if (! $c) { + logger('recipient channel not found.'); + $ret['message'] .= 'recipient not found.' . EOL; + return $ret; + } + + /* + * fetch the requested conversation + */ + + $messages = zot_feed($c[0]['channel_id'],$sender_hash, [ 'message_id' => $data['message_id'], 'encoding' => 'activitystreams' ]); + + return (($messages) ? : [] ); + + } + + static function rekey_request($sender,$data,$hub) { + + $ret = array('success' => false); + + // newsig is newkey signed with oldkey + + // The original xchan will remain. In Zot/Receiver we will have imported the new xchan and hubloc to verify + // the packet authenticity. What we will do now is verify that the keychange operation was signed by the + // oldkey, and if so change all the abook, abconfig, group, and permission elements which reference the + // old xchan_hash. + + if((! $data['old_key']) && (! $data['new_key']) && (! $data['new_sig'])) + return $ret; + + + $old = null; + + if(Libzot::verify($data['old_guid'],$data['old_guid_sig'],$data['old_key'])) { + $oldhash = make_xchan_hash($data['old_guid'],$data['old_key']); + $old = q("select * from xchan where xchan_hash = '%s' limit 1", + dbesc($oldhash) + ); + } + else + return $ret; + + + if(! $old) { + return $ret; + } + + $xchan = $old[0]; + + if(! Libzot::verify($data['new_key'],$data['new_sig'],$xchan['xchan_pubkey'])) { + return $ret; + } + + $r = q("select * from xchan where xchan_hash = '%s' limit 1", + dbesc($sender) + ); + + $newxchan = $r[0]; + + // @todo + // if ! $update create a linked identity + + + xchan_change_key($xchan,$newxchan,$data); + + $ret['success'] = true; + return $ret; + } + + + /** + * @brief + * + * @param array $sender + * @param array $recipients + * + * return json_return_and_die() + */ + + static function reply_purge($sender, $recipients, $hub) { + + $ret = array('success' => false); + + if ($recipients) { + // basically this means "unfriend" + foreach ($recipients as $recip) { + $r = q("select channel.*,xchan.* from channel + left join xchan on channel_hash = xchan_hash + where channel_hash = '%s' and channel_guid_sig = '%s' limit 1", + dbesc($recip) + ); + if ($r) { + $r = q("select abook_id from abook where uid = %d and abook_xchan = '%s' limit 1", + intval($r[0]['channel_id']), + dbesc($sender) + ); + if ($r) { + contact_remove($r[0]['channel_id'],$r[0]['abook_id']); + } + } + } + $ret['success'] = true; + } + else { + + // Unfriend everybody - basically this means the channel has committed suicide + + remove_all_xchan_resources($sender); + + $ret['success'] = true; + } + + return $ret; + } + + + + + + +} |