aboutsummaryrefslogtreecommitdiffstats
path: root/Zotlabs
diff options
context:
space:
mode:
Diffstat (limited to 'Zotlabs')
-rw-r--r--Zotlabs/Daemon/Cron.php2
-rw-r--r--Zotlabs/Daemon/Master.php127
-rw-r--r--Zotlabs/Daemon/Notifier.php2
-rw-r--r--Zotlabs/Extend/Route.php48
-rw-r--r--Zotlabs/Extend/Widget.php47
-rw-r--r--Zotlabs/Identity/OAuth2Server.php5
-rw-r--r--Zotlabs/Identity/OAuth2Storage.php72
-rw-r--r--Zotlabs/Lib/Activity.php1725
-rw-r--r--Zotlabs/Lib/Apps.php224
-rw-r--r--Zotlabs/Lib/Group.php405
-rw-r--r--Zotlabs/Lib/Libsync.php1019
-rw-r--r--Zotlabs/Lib/Libzot.php2849
-rw-r--r--Zotlabs/Lib/Libzotdir.php654
-rw-r--r--Zotlabs/Lib/NativeWiki.php37
-rw-r--r--Zotlabs/Lib/NativeWikiPage.php20
-rw-r--r--Zotlabs/Lib/Queue.php278
-rw-r--r--Zotlabs/Lib/ThreadItem.php18
-rw-r--r--Zotlabs/Lib/ThreadStream.php1
-rw-r--r--Zotlabs/Lib/Webfinger.php109
-rw-r--r--Zotlabs/Lib/Zotfinger.php50
-rw-r--r--Zotlabs/Module/Acl.php14
-rw-r--r--Zotlabs/Module/Admin.php2
-rw-r--r--Zotlabs/Module/Admin/Account_edit.php5
-rw-r--r--Zotlabs/Module/Admin/Site.php26
-rw-r--r--Zotlabs/Module/Appman.php6
-rw-r--r--Zotlabs/Module/Apps.php6
-rw-r--r--Zotlabs/Module/Article_edit.php2
-rw-r--r--Zotlabs/Module/Articles.php59
-rw-r--r--Zotlabs/Module/Authorize.php58
-rw-r--r--Zotlabs/Module/Blocks.php2
-rw-r--r--Zotlabs/Module/Bookmarks.php21
-rw-r--r--Zotlabs/Module/Cal.php10
-rw-r--r--Zotlabs/Module/Card_edit.php2
-rw-r--r--Zotlabs/Module/Cards.php45
-rw-r--r--Zotlabs/Module/Cdav.php59
-rw-r--r--Zotlabs/Module/Channel.php112
-rw-r--r--Zotlabs/Module/Chat.php70
-rw-r--r--Zotlabs/Module/Connect.php68
-rw-r--r--Zotlabs/Module/Connections.php23
-rw-r--r--Zotlabs/Module/Connedit.php5
-rw-r--r--Zotlabs/Module/Contactgroup.php2
-rw-r--r--Zotlabs/Module/Defperms.php50
-rw-r--r--Zotlabs/Module/Directory.php6
-rw-r--r--Zotlabs/Module/Display.php105
-rw-r--r--Zotlabs/Module/Editblock.php2
-rw-r--r--Zotlabs/Module/Editlayout.php2
-rw-r--r--Zotlabs/Module/Editpost.php2
-rw-r--r--Zotlabs/Module/Editwebpage.php2
-rw-r--r--Zotlabs/Module/Events.php11
-rw-r--r--Zotlabs/Module/Filestorage.php2
-rw-r--r--Zotlabs/Module/Group.php39
-rw-r--r--Zotlabs/Module/Home.php6
-rw-r--r--Zotlabs/Module/Hq.php2
-rw-r--r--Zotlabs/Module/Import.php6
-rw-r--r--Zotlabs/Module/Invite.php25
-rw-r--r--Zotlabs/Module/Item.php36
-rw-r--r--Zotlabs/Module/Lang.php19
-rw-r--r--Zotlabs/Module/Layouts.php2
-rw-r--r--Zotlabs/Module/Lockview.php4
-rw-r--r--Zotlabs/Module/Magic.php7
-rw-r--r--Zotlabs/Module/Mail.php2
-rw-r--r--Zotlabs/Module/Manage.php6
-rw-r--r--Zotlabs/Module/Mood.php23
-rw-r--r--Zotlabs/Module/Network.php66
-rw-r--r--Zotlabs/Module/New_channel.php10
-rw-r--r--Zotlabs/Module/Notes.php41
-rw-r--r--Zotlabs/Module/Oauth.php (renamed from Zotlabs/Module/Settings/Oauth.php)67
-rw-r--r--Zotlabs/Module/Oauth2.php (renamed from Zotlabs/Module/Settings/Oauth2.php)100
-rw-r--r--Zotlabs/Module/Oauthinfo.php6
-rw-r--r--Zotlabs/Module/Owa.php2
-rw-r--r--Zotlabs/Module/Pconfig.php14
-rw-r--r--Zotlabs/Module/Pdledit.php21
-rw-r--r--Zotlabs/Module/Permcats.php (renamed from Zotlabs/Module/Settings/Permcats.php)39
-rw-r--r--Zotlabs/Module/Photos.php3
-rw-r--r--Zotlabs/Module/Ping.php11
-rw-r--r--Zotlabs/Module/Poke.php21
-rw-r--r--Zotlabs/Module/Probe.php26
-rw-r--r--Zotlabs/Module/Profiles.php4
-rw-r--r--Zotlabs/Module/Pubstream.php16
-rw-r--r--Zotlabs/Module/Randprof.php22
-rw-r--r--Zotlabs/Module/Register.php5
-rw-r--r--Zotlabs/Module/Rpost.php2
-rw-r--r--Zotlabs/Module/Settings/Account.php15
-rw-r--r--Zotlabs/Module/Settings/Calendar.php47
-rw-r--r--Zotlabs/Module/Settings/Channel.php50
-rw-r--r--Zotlabs/Module/Settings/Channel_home.php95
-rw-r--r--Zotlabs/Module/Settings/Connections.php47
-rw-r--r--Zotlabs/Module/Settings/Conversation.php60
-rw-r--r--Zotlabs/Module/Settings/Directory.php47
-rw-r--r--Zotlabs/Module/Settings/Display.php32
-rw-r--r--Zotlabs/Module/Settings/Editor.php47
-rw-r--r--Zotlabs/Module/Settings/Events.php47
-rw-r--r--Zotlabs/Module/Settings/Features.php35
-rw-r--r--Zotlabs/Module/Settings/Manage.php47
-rw-r--r--Zotlabs/Module/Settings/Network.php67
-rw-r--r--Zotlabs/Module/Settings/Photos.php47
-rw-r--r--Zotlabs/Module/Settings/Profiles.php56
-rw-r--r--Zotlabs/Module/Setup.php13
-rw-r--r--Zotlabs/Module/Sharedwithme.php1
-rw-r--r--Zotlabs/Module/Siteinfo.php2
-rw-r--r--Zotlabs/Module/Sources.php24
-rw-r--r--Zotlabs/Module/Suggest.php21
-rw-r--r--Zotlabs/Module/Token.php8
-rw-r--r--Zotlabs/Module/Tokens.php (renamed from Zotlabs/Module/Settings/Tokens.php)44
-rw-r--r--Zotlabs/Module/Uexport.php21
-rw-r--r--Zotlabs/Module/Userinfo.php17
-rw-r--r--Zotlabs/Module/Viewsrc.php2
-rw-r--r--Zotlabs/Module/Webpages.php57
-rw-r--r--Zotlabs/Module/Well_known.php1
-rw-r--r--Zotlabs/Module/Wfinger.php5
-rw-r--r--Zotlabs/Module/Wiki.php192
-rw-r--r--Zotlabs/Module/Zfinger.php4
-rw-r--r--Zotlabs/Module/Zot.php25
-rw-r--r--Zotlabs/Module/Zot_probe.php47
-rw-r--r--Zotlabs/Render/Comanche.php38
-rwxr-xr-xZotlabs/Render/SmartyTemplate.php19
-rw-r--r--Zotlabs/Update/_1217.php22
-rw-r--r--Zotlabs/Update/_1218.php31
-rw-r--r--Zotlabs/Update/_1219.php26
-rw-r--r--Zotlabs/Update/_1220.php47
-rw-r--r--Zotlabs/Update/_1221.php25
-rw-r--r--Zotlabs/Update/_1222.php23
-rw-r--r--Zotlabs/Update/_1223.php23
-rw-r--r--Zotlabs/Update/_1224.php28
-rw-r--r--Zotlabs/Web/HTTPSig.php13
-rw-r--r--Zotlabs/Web/HttpMeta.php15
-rw-r--r--Zotlabs/Web/Router.php32
-rw-r--r--Zotlabs/Web/SubModule.php17
-rw-r--r--Zotlabs/Widget/Activity_filter.php10
-rw-r--r--Zotlabs/Widget/Activity_order.php7
-rw-r--r--Zotlabs/Widget/Appstore.php6
-rw-r--r--Zotlabs/Widget/Archive.php6
-rw-r--r--Zotlabs/Widget/Categories.php13
-rw-r--r--Zotlabs/Widget/Cover_photo.php12
-rw-r--r--Zotlabs/Widget/Newmember.php17
-rw-r--r--Zotlabs/Widget/Notes.php14
-rw-r--r--Zotlabs/Widget/Notifications.php2
-rw-r--r--Zotlabs/Widget/Settings_menu.php87
-rw-r--r--Zotlabs/Widget/Wiki_list.php6
-rw-r--r--Zotlabs/Widget/Wiki_page_history.php5
-rw-r--r--Zotlabs/Widget/Wiki_pages.php8
-rw-r--r--Zotlabs/Zot/Finger.php9
-rw-r--r--Zotlabs/Zot6/Finger.php146
-rw-r--r--Zotlabs/Zot6/HTTPSig.php507
-rw-r--r--Zotlabs/Zot6/IHandler.php18
-rw-r--r--Zotlabs/Zot6/Receiver.php220
-rw-r--r--Zotlabs/Zot6/Zot6Handler.php266
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 . '&nbsp;(' . $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,'&nbsp;','&nbsp;&nbsp;&nbsp;&nbsp;'],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,'&nbsp;','&nbsp;&nbsp;&nbsp;&nbsp;'],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;
+ }
+
+
+
+
+
+
+}