diff --git a/Zotlabs/Access/PermissionLimits.php b/Zotlabs/Access/PermissionLimits.php new file mode 100644 index 000000000..909b654d5 --- /dev/null +++ b/Zotlabs/Access/PermissionLimits.php @@ -0,0 +1,36 @@ + $v) { + if(strstr($k,'view')) + $limits[$k] = PERMS_PUBLIC; + else + $limits[$k] = PERMS_SPECIFIC; + } + return $limits; + } + + static public function Set($channel_id,$perm,$perm_limit) { + ZLib\PConfig::Set($channel_id,'perm_limits',$perm,$perm_limit); + } + + static public function Get($channel_id,$perm = '') { + if($perm) { + return Zlib\PConfig::Get($channel_id,'perm_limits',$perm); + } + else { + Zlib\PConfig::Load($channel_id); + if(array_key_exists($channel_id,\App::$config) && array_key_exists('perm_limits',\App::$config[$channel_id])) + return \App::$config[$channel_id]['perm_limits']; + return false; + } + } +} \ No newline at end of file diff --git a/Zotlabs/Access/PermissionRoles.php b/Zotlabs/Access/PermissionRoles.php new file mode 100644 index 000000000..8b116adc5 --- /dev/null +++ b/Zotlabs/Access/PermissionRoles.php @@ -0,0 +1,215 @@ + [ + 'social' => t('Social - Mostly Public'), + 'social_restricted' => t('Social - Restricted'), + 'social_private' => t('Social - Private') + ], + + t('Community Forum') => [ + 'forum' => t('Forum - Mostly Public'), + 'forum_restricted' => t('Forum - Restricted'), + 'forum_private' => t('Forum - Private') + ], + + t('Feed Republish') => [ + 'feed' => t('Feed - Mostly Public'), + 'feed_restricted' => t('Feed - Restricted') + ], + + t('Special Purpose') => [ + 'soapbox' => t('Special - Celebrity/Soapbox'), + 'repository' => t('Special - Group Repository') + ], + + t('Other') => [ + 'custom' => t('Custom/Expert Mode') + ] + + ]; + + return $roles; + } + + + +} \ No newline at end of file diff --git a/Zotlabs/Access/Permissions.php b/Zotlabs/Access/Permissions.php new file mode 100644 index 000000000..61ea51a48 --- /dev/null +++ b/Zotlabs/Access/Permissions.php @@ -0,0 +1,116 @@ + t('Can view my channel stream and posts'), + 'send_stream' => t('Can send me their channel stream and posts'), + 'view_profile' => t('Can view my default channel profile'), + 'view_contacts' => t('Can view my connections'), + 'view_storage' => t('Can view my file storage and photos'), + 'write_storage' => t('Can upload/modify my file storage and photos'), + 'view_pages' => t('Can view my channel webpages'), + 'write_pages' => t('Can create/edit my channel webpages'), + 'post_wall' => t('Can post on my channel (wall) page'), + 'post_comments' => t('Can comment on or like my posts'), + 'post_mail' => t('Can send me private mail messages'), + 'post_like' => t('Can like/dislike profiles and profile things'), + 'tag_deliver' => t('Can forward to all my channel connections via @+ mentions in posts'), + 'chat' => t('Can chat with me'), + 'republish' => t('Can source my public posts in derived channels'), + 'delegate' => t('Can administer my channel') + ]; + + $x = array('permissions' => $perms, 'filter' => $filter); + call_hooks('permissions_list',$x); + return($x['permissions']); + + } + + static public function BlockedAnonPerms() { + + // Perms from the above list that are blocked from anonymous observers. + // e.g. you must be authenticated. + + $res = array(); + $perms = PermissionLimits::Std_limits(); + foreach($perms as $perm => $limit) { + if($limit != PERMS_PUBLIC) { + $res[] = $perm; + } + } + + $x = array('permissions' => $res); + call_hooks('write_perms',$x); + return($x['permissions']); + + } + + // converts [ 0 => 'view_stream', ... ] + // to [ 'view_stream' => 1 ] + // for any permissions in $arr; + // Undeclared permissions are set to 0 + + static public function FilledPerms($arr) { + $everything = self::Perms(); + $ret = []; + foreach($everything as $k => $v) { + if(in_array($k,$arr)) + $ret[$k] = 1; + else + $ret[$k] = 0; + } + return $ret; + + } + + static public function FilledAutoperms($channel_id) { + if(! intval(get_pconfig($channel_id,'system','autoperms'))) + return false; + + $arr = []; + $r = q("select * from pconfig where uid = %d and cat = 'autoperms'", + intval($channel_id) + ); + if($r) { + foreach($r as $rr) { + $arr[$rr['k']] = $arr[$rr['v']]; + } + } + return $arr; + } + + static public function PermsCompare($p1,$p2) { + foreach($p1 as $k => $v) { + if(! array_key_exists($k,$p2)) + return false; + if($p1[$k] != $p2[$k]) + return false; + } + return true; + } +} \ No newline at end of file diff --git a/Zotlabs/Daemon/Cron.php b/Zotlabs/Daemon/Cron.php index 5af8174bf..c6e82b13a 100644 --- a/Zotlabs/Daemon/Cron.php +++ b/Zotlabs/Daemon/Cron.php @@ -66,7 +66,7 @@ class Cron { q("delete from atoken where atoken_expires != '%s' && atoken_expires < %s", dbesc(NULL_DATE), - dbutcnow() + db_utcnow() ); diff --git a/Zotlabs/Daemon/Onepoll.php b/Zotlabs/Daemon/Onepoll.php index 036a4991b..21c46cec5 100644 --- a/Zotlabs/Daemon/Onepoll.php +++ b/Zotlabs/Daemon/Onepoll.php @@ -102,7 +102,9 @@ class Onepoll { $fetch_feed = true; $x = null; - if(! ($contact['abook_their_perms'] & PERMS_R_STREAM )) + $can_view_stream = intval(get_abconfig($importer_uid,$contact['abook_xchan'],'their_perms','view_stream')); + + if(! $can_view_stream) $fetch_feed = false; if($fetch_feed) { diff --git a/Zotlabs/Lib/AbConfig.php b/Zotlabs/Lib/AbConfig.php index cab59abbd..cb5d96951 100644 --- a/Zotlabs/Lib/AbConfig.php +++ b/Zotlabs/Lib/AbConfig.php @@ -7,7 +7,7 @@ class AbConfig { static public function Load($chan,$xhash,$family = '') { if($family) - $where = sprintf(" and family = '%s' ",dbesc($family)); + $where = sprintf(" and cat = '%s' ",dbesc($family)); $r = q("select * from abconfig where chan = %d and xchan = '%s' $where", intval($chan), dbesc($xhash) diff --git a/Zotlabs/Lib/PConfig.php b/Zotlabs/Lib/PConfig.php index 195321375..319b8f203 100644 --- a/Zotlabs/Lib/PConfig.php +++ b/Zotlabs/Lib/PConfig.php @@ -17,7 +17,7 @@ class PConfig { */ static public function Load($uid) { - if($uid === false) + if(is_null($uid) || $uid === false) return false; if(! array_key_exists($uid, \App::$config)) @@ -61,7 +61,7 @@ class PConfig { static public function Get($uid,$family,$key,$instore = false) { - if($uid === false) + if(is_null($uid) || $uid === false) return false; if(! array_key_exists($uid, \App::$config)) @@ -102,7 +102,7 @@ class PConfig { // we provide a function backtrace in the logs so that we can find // and fix the calling function. - if($uid === false) { + if(is_null($uid) || $uid === false) { btlogger('UID is FALSE!', LOGGER_NORMAL, LOG_ERR); return; } @@ -172,6 +172,9 @@ class PConfig { static public function Delete($uid, $family, $key) { + if(is_null($uid) || $uid === false) + return false; + $ret = false; if(array_key_exists($key, \App::$config[$uid][$family])) diff --git a/Zotlabs/Lib/PermissionDescription.php b/Zotlabs/Lib/PermissionDescription.php index 55aac2dea..b6c6dd29d 100644 --- a/Zotlabs/Lib/PermissionDescription.php +++ b/Zotlabs/Lib/PermissionDescription.php @@ -78,22 +78,13 @@ class PermissionDescription { $result = null; - $global_perms = get_perms(); + $global_perms = \Zotlabs\Access\Permissions::Perms(); if (array_key_exists($permname, $global_perms)) { - $permDetails = $global_perms[$permname]; + $channelPerm = \Zotlabs\Access\PermissionLimits::Get(\App::$channel['channel_id'],$permname); - // It should be OK to always just read the permissions from App::$channel - // - // App::$profile is a union of channel and profile fields. - // The distinction is basically that App::$profile is pointing to the resource - // being observed. App::$channel is referring to the current logged-in channel - // member (if this is a local channel) e.g. the observer. We only show the ACL - // widget to the page owner (observer and observed are the same) so in that case - // I believe either may be safely used here. - $channelPerm = \App::$channel[$permDetails[0]]; - $result = new PermissionDescription($permDetails[1], $channelPerm); + $result = new PermissionDescription('', $channelPerm); } else { // The acl dialog can handle null arguments, but it shouldn't happen logger('null PermissionDescription from unknown global permission: ' . $permname ,LOGGER_DEBUG, LOG_ERROR); diff --git a/Zotlabs/Module/Acl.php b/Zotlabs/Module/Acl.php index 15609c3c8..03dc6c5d3 100644 --- a/Zotlabs/Module/Acl.php +++ b/Zotlabs/Module/Acl.php @@ -58,7 +58,23 @@ class Acl extends \Zotlabs\Web\Controller { if( (! local_channel()) && (! ($type == 'x' || $type == 'c'))) killme(); - + + $permitted = []; + + if(in_array($type, [ 'm', 'a', 'c' ])) { + + // These queries require permission checking. We'll create a simple array of xchan_hash for those with + // the requisite permissions which we can check against. + + $x = q("select xchan from abconfig where chan = %d and cat = 'their_perms' and k = '%s' and v = 1", + intval(local_channel()), + dbesc(($type === 'm') ? 'post_mail' : 'tag_deliver') + ); + + $permitted = ids_to_array($x,'xchan'); + } + + if($search) { $sql_extra = " AND `name` LIKE " . protect_sprintf( "'%" . dbesc($search) . "%'" ) . " "; $sql_extra2 = "AND ( xchan_name LIKE " . protect_sprintf( "'%" . dbesc($search) . "%'" ) . " OR xchan_addr LIKE " . protect_sprintf( "'%" . dbesc($search) . ((strpos($search,'@') === false) ? "%@%'" : "%'")) . ") "; @@ -87,13 +103,13 @@ class Acl extends \Zotlabs\Web\Controller { if($type == '' || $type == 'g') { - $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 groups.id, groups.hash, groups.gname + FROM groups,group_member + WHERE groups.deleted = 0 AND groups.uid = %d + AND group_member.gid=groups.id $sql_extra - GROUP BY `groups`.`id` - ORDER BY `groups`.`gname` + GROUP BY groups.id + ORDER BY groups.gname LIMIT %d OFFSET %d", intval(local_channel()), intval($count), @@ -156,7 +172,7 @@ class Acl extends \Zotlabs\Web\Controller { } - $r = q("SELECT abook_id as id, xchan_hash as hash, xchan_name as name, xchan_photo_s as micro, xchan_url as url, xchan_addr as nick, abook_their_perms, abook_flags, abook_self + $r = q("SELECT abook_id as id, xchan_hash as hash, xchan_name as name, xchan_photo_s as micro, xchan_url as url, xchan_addr as nick, abook_their_perms, xchan_pubforum, abook_flags, abook_self FROM abook left join xchan on abook_xchan = xchan_hash WHERE (abook_channel = %d $extra_channels_sql) AND abook_blocked = 0 and abook_pending = 0 and xchan_deleted = 0 $sql_extra2 order by $order_extra2 xchan_name asc" , intval(local_channel()) @@ -221,16 +237,24 @@ class Acl extends \Zotlabs\Web\Controller { } } elseif($type == 'm') { - - $r = q("SELECT xchan_hash as id, xchan_name as name, xchan_addr as nick, xchan_photo_s as micro, xchan_url as url + + $r = array(); + $z = q("SELECT xchan_hash as hash, xchan_name as name, xchan_addr as nick, xchan_photo_s as micro, xchan_url as url FROM abook left join xchan on abook_xchan = xchan_hash - WHERE abook_channel = %d and ( (abook_their_perms = null) or (abook_their_perms & %d )>0) + WHERE abook_channel = %d and xchan_deleted = 0 $sql_extra3 - ORDER BY `xchan_name` ASC ", - intval(local_channel()), - intval(PERMS_W_MAIL) + ORDER BY xchan_name ASC ", + intval(local_channel()) ); + if($z) { + foreach($z as $zz) { + if(in_array($zz['id'],$permitted)) { + $r[] = $zz; + } + } + } + } elseif($type == 'a') { @@ -274,7 +298,7 @@ class Acl extends \Zotlabs\Web\Controller { if(strpos($g['hash'],'/') && $type != 'a') continue; - if(($g['abook_their_perms'] & PERMS_W_TAGWALL) && $type == 'c' && (! $noforums)) { + if(in_array($g['hash'],$permitted) && $type == 'c' && (! $noforums)) { $contacts[] = array( "type" => "c", "photo" => "images/twopeople.png", diff --git a/Zotlabs/Module/Connedit.php b/Zotlabs/Module/Connedit.php index 7db4950b1..93ee30999 100644 --- a/Zotlabs/Module/Connedit.php +++ b/Zotlabs/Module/Connedit.php @@ -126,15 +126,30 @@ class Connedit extends \Zotlabs\Web\Controller { $rating = 10; $rating_text = trim(escape_tags($_REQUEST['rating_text'])); - - $abook_my_perms = 0; - - foreach($_POST as $k => $v) { - if(strpos($k,'perms_') === 0) { - $abook_my_perms += $v; + + $all_perms = \Zotlabs\Access\Permissions::Perms(); + + if($all_perms) { + foreach($all_perms as $perm => $desc) { + if(array_key_exists('perms_' . $perm, $_POST)) { + set_abconfig($channel['channel_id'],$orig_record[0]['abook_xchan'],'my_perms',$perm, + intval($_POST['perms_' . $perm])); + if($autoperms) { + set_pconfig($channel['channel_id'],'autoperms',$perm,intval($_POST['perms_' . $perm])); + } + } + else { + set_abconfig($channel['channel_id'],$orig_record[0]['abook_xchan'],'my_perms',$perm,0); + if($autoperms) { + set_pconfig($channel['channel_id'],'autoperms',$perm,0); + } + } } } - + + if(! is_null($autoperms)) + set_pconfig($channel['channel_id'],'system','autoperms',$autoperms); + $new_friend = false; if(! $is_self) { @@ -194,19 +209,25 @@ class Connedit extends \Zotlabs\Web\Controller { $role = get_pconfig(local_channel(),'system','permissions_role'); if($role) { - $x = get_role_perms($role); - if($x['perms_accept']) - $abook_my_perms = $x['perms_accept']; + $x = \Zotlabs\Access\PermissionRoles::role_perms($role); + if($x['perms_connect']) { + $abook_my_perms = $x['perms_connect']; + } } + + $filled_perms = \Zotlabs\Access\Permissions::FilledPerms($abook_my_perms); + foreach($filled_perms as $k => $v) { + set_abconfig($channel['channel_id'],$orig_record[0]['abook_xchan'],'my_perms',$k,$v); + } + } - + $abook_pending = (($new_friend) ? 0 : $orig_record[0]['abook_pending']); - $r = q("UPDATE abook SET abook_profile = '%s', abook_my_perms = %d , abook_closeness = %d, abook_pending = %d, + $r = q("UPDATE abook SET abook_profile = '%s', abook_closeness = %d, abook_pending = %d, abook_incl = '%s', abook_excl = '%s' where abook_id = %d AND abook_channel = %d", dbesc($profile_id), - intval($abook_my_perms), intval($closeness), intval($abook_pending), dbesc($abook_incl), @@ -227,10 +248,13 @@ class Connedit extends \Zotlabs\Web\Controller { info( t('Connection updated.') . EOL); else notice( t('Failed to update connection record.') . EOL); - - if(\App::$poi && \App::$poi['abook_my_perms'] != $abook_my_perms - && (! intval(\App::$poi['abook_self']))) { - \Zotlabs\Daemon\Master::Summon(array('Notifier', (($new_friend) ? 'permission_create' : 'permission_update'), $contact_id)); + + if(! intval(\App::$poi['abook_self'])) { + \Zotlabs\Daemon\Master::Summon( [ + 'Notifier', + (($new_friend) ? 'permission_create' : 'permission_update'), + $contact_id + ]); } if($new_friend) { @@ -371,9 +395,9 @@ class Connedit extends \Zotlabs\Web\Controller { $my_perms = get_channel_default_perms(local_channel()); $role = get_pconfig(local_channel(),'system','permissions_role'); if($role) { - $x = get_role_perms($role); - if($x['perms_accept']) - $my_perms = $x['perms_accept']; + $x = \Zotlabs\Access\PermissionRoles::role_perms($role); + if($x['perms_connect']) + $my_perms = $x['perms_connect']; } $yes_no = array(t('No'),t('Yes')); @@ -654,7 +678,8 @@ class Connedit extends \Zotlabs\Web\Controller { $perms = array(); $channel = \App::get_channel(); - $global_perms = get_perms(); + $global_perms = \Zotlabs\Access\Permissions::Perms(); + $existing = get_all_perms(local_channel(),$contact['abook_xchan']); $unapproved = array('pending', t('Approve this connection'), '', t('Accept connection to allow communication'), array(t('No'),('Yes'))); @@ -670,16 +695,32 @@ class Connedit extends \Zotlabs\Web\Controller { if($slide && $multiprofs) $affinity = t('Set Affinity & Profile'); + $theirs = q("select * from abconfig where chan = %d and xchan = '%s' and cat = 'their_perms'", + intval(local_channel()), + dbesc($contact['abook_xchan']) + ); + $their_perms = array(); + if($theirs) { + foreach($theirs as $t) { + $their_perms[$t['k']] = $t['v']; + } + } + foreach($global_perms as $k => $v) { - $thisperm = (($contact['abook_my_perms'] & $v[1]) ? "1" : ''); - $checkinherited = ((($channel[$v[0]]) && ($channel[$v[0]] != PERMS_SPECIFIC)) ? "1" : ''); + $thisperm = get_abconfig(local_channel(),$contact['abook_xchan'],'my_perms',$k); +//fixme + + $checkinherited = \Zotlabs\Access\PermissionLimits::Get(local_channel(),$k); // For auto permissions (when $self is true) we don't want to look at existing // permissions because they are enabled for the channel owner if((! $self) && ($existing[$k])) $thisperm = "1"; + + + - $perms[] = array('perms_' . $k, $v[3], (($contact['abook_their_perms'] & $v[1]) ? "1" : ""),$thisperm, $v[1], (($channel[$v[0]] == PERMS_SPECIFIC) ? '' : '1'), $v[4], $checkinherited); + $perms[] = array('perms_' . $k, $v, ((array_key_exists($k,$their_perms)) ? intval($their_perms[$k]) : ''),$thisperm, 1, (($checkinherited & PERMS_SPECIFIC) ? '' : '1'), '', $checkinherited); } $locstr = ''; diff --git a/Zotlabs/Module/Dav.php b/Zotlabs/Module/Dav.php index ba2394388..aaf69844c 100644 --- a/Zotlabs/Module/Dav.php +++ b/Zotlabs/Module/Dav.php @@ -48,55 +48,13 @@ class Dav extends \Zotlabs\Web\Controller { if (! is_dir('store')) os_mkdir('store', STORAGE_DEFAULT_PERMISSIONS, false); - $which = null; if (argc() > 1) - $which = argv(1); + profile_load(argv(1),0); - $profile = 0; - - \App::$page['htmlhead'] .= '' . "\r\n"; - - if ($which) - profile_load( $which, $profile); - - - $auth = new \Zotlabs\Storage\BasicAuth(); - $auth->setRealm(ucfirst(\Zotlabs\Lib\System::get_platform_name()) . 'WebDAV'); + $auth->setRealm(ucfirst(\Zotlabs\Lib\System::get_platform_name()) . ' ' . 'WebDAV'); -// $authBackend = new \Sabre\DAV\Auth\Backend\BasicCallBack(function($userName,$password) { -// if(account_verify_password($userName,$password)) -// return true; -// return false; -// }); - -// $ob_hash = get_observer_hash(); - -// if ($ob_hash) { -// if (local_channel()) { -// $channel = \App::get_channel(); -// $auth->setCurrentUser($channel['channel_address']); -// $auth->channel_id = $channel['channel_id']; -// $auth->channel_hash = $channel['channel_hash']; -// $auth->channel_account_id = $channel['channel_account_id']; -// if($channel['channel_timezone']) -// $auth->setTimezone($channel['channel_timezone']); -// } -// $auth->observer = $ob_hash; -// } - -// if ($_GET['davguest']) -// $_SESSION['davguest'] = true; - -// $_SERVER['QUERY_STRING'] = str_replace(array('?f=', '&f='), array('', ''), $_SERVER['QUERY_STRING']); -// $_SERVER['QUERY_STRING'] = strip_zids($_SERVER['QUERY_STRING']); -// $_SERVER['QUERY_STRING'] = preg_replace('/[\?&]davguest=(.*?)([\?&]|$)/ism', '', $_SERVER['QUERY_STRING']); -// -// $_SERVER['REQUEST_URI'] = str_replace(array('?f=', '&f='), array('', ''), $_SERVER['REQUEST_URI']); -// $_SERVER['REQUEST_URI'] = strip_zids($_SERVER['REQUEST_URI']); -// $_SERVER['REQUEST_URI'] = preg_replace('/[\?&]davguest=(.*?)([\?&]|$)/ism', '', $_SERVER['REQUEST_URI']); - $rootDirectory = new \Zotlabs\Storage\Directory('/', $auth); // A SabreDAV server-object @@ -113,48 +71,13 @@ class Dav extends \Zotlabs\Web\Controller { $server->addPlugin($lockPlugin); - // The next section of code allows us to bypass prompting for http-auth if a - // FILE is being accessed anonymously and permissions allow this. This way - // one can create hotlinks to public media files in their cloud and anonymous - // viewers won't get asked to login. - // If a DIRECTORY is accessed or there are permission issues accessing the - // file and we aren't previously authenticated via zot, prompt for HTTP-auth. - // This will be the default case for mounting a DAV directory. - // In order to avoid prompting for passwords for viewing a DIRECTORY, add - // the URL query parameter 'davguest=1'. - -// $isapublic_file = false; -// $davguest = ((x($_SESSION, 'davguest')) ? true : false); - -// if ((! $auth->observer) && ($_SERVER['REQUEST_METHOD'] === 'GET')) { -// try { -// $x = RedFileData('/' . \App::$cmd, $auth); -// if($x instanceof \Zotlabs\Storage\File) -// $isapublic_file = true; -// } -// catch (Exception $e) { -// $isapublic_file = false; -// } -// } - -// if ((! $auth->observer) && (! $isapublic_file) && (! $davguest)) { -// try { -// $auth->Authenticate($server, t('$Projectname channel')); -// } -// catch (Exception $e) { -// logger('mod_cloud: auth exception' . $e->getMessage()); -// http_status_exit($e->getHTTPCode(), $e->getMessage()); -// } -// } - - // require_once('Zotlabs/Storage/Browser.php'); // provide a directory view for the cloud in Hubzilla $browser = new \Zotlabs\Storage\Browser($auth); $auth->setBrowserPlugin($browser); // Experimental QuotaPlugin - // require_once('Zotlabs/Storage/QuotaPlugin.php'); - // $server->addPlugin(new \Zotlabs\Storage\QuotaPlugin($auth)); + // require_once('Zotlabs/Storage/QuotaPlugin.php'); + // $server->addPlugin(new \Zotlabs\Storage\QuotaPlugin($auth)); // All we need to do now, is to fire up the server $server->exec(); diff --git a/Zotlabs/Module/Editpost.php b/Zotlabs/Module/Editpost.php index da859de3e..838fe9e4f 100644 --- a/Zotlabs/Module/Editpost.php +++ b/Zotlabs/Module/Editpost.php @@ -47,9 +47,9 @@ class Editpost extends \Zotlabs\Web\Controller { if(intval($itm[0]['item_obscured'])) { $key = get_config('system','prvkey'); if($itm[0]['title']) - $itm[0]['title'] = crypto_unencapsulate(json_decode_plus($itm[0]['title']),$key); + $itm[0]['title'] = crypto_unencapsulate(json_decode($itm[0]['title'],true),$key); if($itm[0]['body']) - $itm[0]['body'] = crypto_unencapsulate(json_decode_plus($itm[0]['body']),$key); + $itm[0]['body'] = crypto_unencapsulate(json_decode($itm[0]['body'],true),$key); } $category = ''; diff --git a/Zotlabs/Module/Editwebpage.php b/Zotlabs/Module/Editwebpage.php index be4803a07..a55f81101 100644 --- a/Zotlabs/Module/Editwebpage.php +++ b/Zotlabs/Module/Editwebpage.php @@ -108,9 +108,9 @@ class Editwebpage extends \Zotlabs\Web\Controller { if(intval($itm[0]['item_obscured'])) { $key = get_config('system','prvkey'); if($itm[0]['title']) - $itm[0]['title'] = crypto_unencapsulate(json_decode_plus($itm[0]['title']),$key); + $itm[0]['title'] = crypto_unencapsulate(json_decode($itm[0]['title'],true),$key); if($itm[0]['body']) - $itm[0]['body'] = crypto_unencapsulate(json_decode_plus($itm[0]['body']),$key); + $itm[0]['body'] = crypto_unencapsulate(json_decode($itm[0]['body'],true),$key); } $item_id = q("select * from iconfig where cat = 'system' and k = 'WEBPAGE' and iid = %d limit 1", diff --git a/Zotlabs/Module/Follow.php b/Zotlabs/Module/Follow.php index 3641330c9..da9ab3670 100644 --- a/Zotlabs/Module/Follow.php +++ b/Zotlabs/Module/Follow.php @@ -47,12 +47,13 @@ class Follow extends \Zotlabs\Web\Controller { if($abconfig) $clone['abconfig'] = $abconfig; - build_sync_packet(0 /* use the current local_channel */, array('abook' => array($clone))); + build_sync_packet(0 /* use the current local_channel */, array('abook' => array($clone)), true); + $can_view_stream = intval(get_abconfig($channel['channel_id'],$clone['abook_xchan'],'their_perms','view_stream')); // If we can view their stream, pull in some posts - if(($result['abook']['abook_their_perms'] & PERMS_R_STREAM) || ($result['abook']['xchan_network'] === 'rss')) + if(($can_view_stream) || ($result['abook']['xchan_network'] === 'rss')) \Zotlabs\Daemon\Master::Summon(array('Onepoll',$result['abook']['abook_id'])); goaway(z_root() . '/connedit/' . $result['abook']['abook_id'] . '?f=&follow=1'); diff --git a/Zotlabs/Module/Import.php b/Zotlabs/Module/Import.php index e34f5e49e..d27f013b9 100644 --- a/Zotlabs/Module/Import.php +++ b/Zotlabs/Module/Import.php @@ -8,6 +8,7 @@ namespace Zotlabs\Module; require_once('include/zot.php'); require_once('include/channel.php'); require_once('include/import.php'); +require_once('include/perm_upgrade.php'); @@ -339,6 +340,8 @@ class Import extends \Zotlabs\Web\Controller { $abooks = $data['abook']; if($abooks) { foreach($abooks as $abook) { + + $abook_copy = $abook; $abconfig = null; if(array_key_exists('abconfig',$abook) && is_array($abook['abconfig']) && count($abook['abconfig'])) @@ -347,6 +350,10 @@ class Import extends \Zotlabs\Web\Controller { unset($abook['abook_id']); unset($abook['abook_rating']); unset($abook['abook_rating_text']); + unset($abook['abconfig']); + unset($abook['abook_their_perms']); + unset($abook['abook_my_perms']); + $abook['abook_account'] = $account_id; $abook['abook_channel'] = $channel['channel_id']; if(! array_key_exists('abook_blocked',$abook)) { @@ -385,6 +392,8 @@ class Import extends \Zotlabs\Web\Controller { $friends ++; if(intval($abook['abook_feed'])) $feeds ++; + + translate_abook_perms_inbound($channel,$abook_copy); if($abconfig) { // @fixme does not handle sync of del_abconfig diff --git a/Zotlabs/Module/Item.php b/Zotlabs/Module/Item.php index 235c5528e..2d0c1ba02 100644 --- a/Zotlabs/Module/Item.php +++ b/Zotlabs/Module/Item.php @@ -183,7 +183,9 @@ class Item extends \Zotlabs\Web\Controller { } // can_comment_on_post() needs info from the following xchan_query - xchan_query($r); + // This may be from the discover tab which means we need to correct the effective uid + + xchan_query($r,true,(($r[0]['uid'] == local_channel()) ? 0 : local_channel())); $parent_item = $r[0]; $parent = $r[0]['id']; @@ -316,9 +318,11 @@ class Item extends \Zotlabs\Web\Controller { } $acl = new \Zotlabs\Access\AccessList($channel); + + $view_policy = \Zotlabs\Access\PermissionLimits::Get($channel['channel_id'],'view_stream'); + $comment_policy = \Zotlabs\Access\PermissionLimits::Get($channel['channel_id'],'post_comments'); - - $public_policy = ((x($_REQUEST,'public_policy')) ? escape_tags($_REQUEST['public_policy']) : map_scope($channel['channel_r_stream'],true)); + $public_policy = ((x($_REQUEST,'public_policy')) ? escape_tags($_REQUEST['public_policy']) : map_scope($view_policy,true)); if($webpage) $public_policy = ''; if($public_policy) @@ -526,11 +530,11 @@ class Item extends \Zotlabs\Web\Controller { if((! $parent) && (get_pconfig($profile_uid,'system','tagifonlyrecip')) && (substr_count($str_contact_allow,'<') == 1) && ($str_group_allow == '') && ($str_contact_deny == '') && ($str_group_deny == '')) { - $x = q("select abook_id, abook_their_perms from abook where abook_xchan = '%s' and abook_channel = %d limit 1", + $x = q("select abook_id, abconfig.v from abook left join abconfig on abook_xchan = abconfig.xchan and abook_channel = abconfig.chan and cat= 'their_perms' and abconfig.k = 'tag_deliver' and abconfig.v = 1 and abook_xchan = '%s' and abook_channel = %d limit 1", dbesc(str_replace(array('<','>'),array('',''),$str_contact_allow)), intval($profile_uid) ); - if($x && ($x[0]['abook_their_perms'] & PERMS_W_TAGWALL)) + if($x) $body .= "\n\n@group+" . $x[0]['abook_id'] . "\n"; } @@ -810,7 +814,7 @@ class Item extends \Zotlabs\Web\Controller { $datarray['layout_mid'] = $layout_mid; $datarray['public_policy'] = $public_policy; - $datarray['comment_policy'] = map_scope($channel['channel_w_comment']); + $datarray['comment_policy'] = map_scope($comment_policy); $datarray['term'] = $post_tags; $datarray['plink'] = $plink; $datarray['route'] = $route; diff --git a/Zotlabs/Module/Like.php b/Zotlabs/Module/Like.php index 1ca37d646..170349509 100644 --- a/Zotlabs/Module/Like.php +++ b/Zotlabs/Module/Like.php @@ -264,23 +264,22 @@ class Like extends \Zotlabs\Web\Controller { logger('like: no item ' . $item_id); killme(); } - - + + + xchan_query($r,true,(($r[0]['uid'] == local_channel()) ? 0 : local_channel())); + $item = $r[0]; - $owner_uid = $item['uid']; - $owner_aid = $item['aid']; - - - $sys = get_sys_channel(); - - - // if this is a "discover" item, (item['uid'] is the sys channel), - // fallback to the item comment policy, which should've been - // respected when generating the conversation thread. - // Even if the activity is rejected by the item owner, it should still get attached - // to the local discover conversation on this site. - - if(($owner_uid != $sys['channel_id']) && (! perm_is_allowed($owner_uid,$observer['xchan_hash'],'post_comments'))) { + + $owner_uid = $r[0]['uid']; + $owner_aid = $r[0]['aid']; + + $can_comment = false; + if((array_key_exists('owner',$item)) && intval($item['owner']['abook_self'])) + $can_comment = perm_is_allowed($item['uid'],$observer['xchan_hash'],'post_comments'); + else + $can_comment = can_comment_on_post($observer['xchan_hash'],$item); + + if(! $can_comment) { notice( t('Permission denied') . EOL); killme(); } diff --git a/Zotlabs/Module/Login.php b/Zotlabs/Module/Login.php index ff75e5268..ae35b922f 100644 --- a/Zotlabs/Module/Login.php +++ b/Zotlabs/Module/Login.php @@ -7,6 +7,9 @@ class Login extends \Zotlabs\Web\Controller { function get() { if(local_channel()) goaway(z_root()); + if(remote_channel() && $_SESSION['atoken']) + goaway(z_root()); + return login((\App::$config['system']['register_policy'] == REGISTER_CLOSED) ? false : true); } diff --git a/Zotlabs/Module/Mail.php b/Zotlabs/Module/Mail.php index aae7585c4..043c28078 100644 --- a/Zotlabs/Module/Mail.php +++ b/Zotlabs/Module/Mail.php @@ -57,8 +57,6 @@ class Mail extends \Zotlabs\Web\Controller { $their_perms = 0; - $global_perms = get_perms(); - if($j['permissions']['data']) { $permissions = crypto_unencapsulate($j['permissions'],$channel['channel_prvkey']); if($permissions) @@ -68,13 +66,7 @@ class Mail extends \Zotlabs\Web\Controller { else $permissions = $j['permissions']; - foreach($permissions as $k => $v) { - if($v) { - $their_perms = $their_perms | intval($global_perms[$k][1]); - } - } - - if(! ($their_perms & PERMS_W_MAIL)) { + if(! ($permissions['post_mail'])) { notice( t('Selected channel has private message restrictions. Send failed.')); // reported issue: let's still save the message and continue. We'll just tell them // that nothing useful is likely to happen. They might have spent hours on it. @@ -120,7 +112,7 @@ class Mail extends \Zotlabs\Web\Controller { } - function get() { + function get() { $o = ''; nav_set_selected('messages'); diff --git a/Zotlabs/Module/Manage.php b/Zotlabs/Module/Manage.php index 4ca044c4a..8f815d6d4 100644 --- a/Zotlabs/Module/Manage.php +++ b/Zotlabs/Module/Manage.php @@ -143,9 +143,9 @@ class Manage extends \Zotlabs\Web\Controller { $create = array( 'new_channel', t('Create a new channel'), t('Create New')); $delegates = q("select * from abook left join xchan on abook_xchan = xchan_hash where - abook_channel = %d and (abook_their_perms & %d) > 0", + abook_channel = %d and abook_xchan in ( select xchan from abconfig where chan = %d and cat = 'their_perms' and k = 'delegate' and v = 1 )", intval(local_channel()), - intval(PERMS_A_DELEGATE) + intval(local_channel()) ); if($delegates) { diff --git a/Zotlabs/Module/Openid.php b/Zotlabs/Module/Openid.php index 7a6e4a81f..8cbc6d2fd 100644 --- a/Zotlabs/Module/Openid.php +++ b/Zotlabs/Module/Openid.php @@ -48,7 +48,7 @@ class Openid extends \Zotlabs\Web\Controller { $_SESSION['uid'] = $r[0]['channel_id']; $_SESSION['account_id'] = $r[0]['channel_account_id']; $_SESSION['authenticated'] = true; - authenticate_success($record,true,true,true,true); + authenticate_success($record,$r[0],true,true,true,true); goaway(z_root()); } } diff --git a/Zotlabs/Module/Probe.php b/Zotlabs/Module/Probe.php index dda792131..7fc0e8ff5 100644 --- a/Zotlabs/Module/Probe.php +++ b/Zotlabs/Module/Probe.php @@ -23,8 +23,6 @@ class Probe extends \Zotlabs\Web\Controller { $j = \Zotlabs\Zot\Finger::run($addr,$channel,false); - // $res = zot_finger($addr,$channel,false); - $o .= '
';
 			if(! $j['success']) {
 				$o .= sprintf( t('Fetching URL returns error: %1$s'),$res['error'] . "\r\n\r\n");
diff --git a/Zotlabs/Module/Profiles.php b/Zotlabs/Module/Profiles.php
index 899c79b15..4b05182c2 100644
--- a/Zotlabs/Module/Profiles.php
+++ b/Zotlabs/Module/Profiles.php
@@ -708,7 +708,7 @@ class Profiles extends \Zotlabs\Web\Controller {
 				'$profile_id'   => $r[0]['id'],
 				'$profile_name' => array('profile_name', t('Profile name'), $r[0]['profile_name'], t('Required'), '*'),
 				'$is_default'   => $is_default,
-				'$default'      => t('This is your default profile.') . EOL . translate_scope(map_scope($channel['channel_r_profile'])),
+				'$default'      => t('This is your default profile.') . EOL . translate_scope(map_scope(\Zotlabs\Access\PermissionLimits::Get($channel['channel_id'],'view_profile'))),
 				'$advanced'     => $advanced,
 				'$name'         => array('name', t('Your full name'), $r[0]['fullname'], t('Required'), '*'),
 				'$pdesc'        => array('pdesc', t('Title/Description'), $r[0]['pdesc']),
@@ -767,7 +767,7 @@ class Profiles extends \Zotlabs\Web\Controller {
 						'$alt' => t('Profile Image'),
 						'$profile_name' => $rr['profile_name'],
 						'$visible' => (($rr['is_default']) 
-							? '' . translate_scope(map_scope($channel['channel_r_profile'])) . '' 
+							? '' . translate_scope(map_scope(\Zotlabs\Access\PermissionLimits::Get($channel['channel_id'],'view_profile'))) . '' 
 							: '' . t('Edit visibility') . '')
 					));
 				}
diff --git a/Zotlabs/Module/Register.php b/Zotlabs/Module/Register.php
index 6afa4a94c..45123b88d 100644
--- a/Zotlabs/Module/Register.php
+++ b/Zotlabs/Module/Register.php
@@ -146,7 +146,7 @@ class Register extends \Zotlabs\Web\Controller {
 			goaway(z_root());
 		}
 	
-		authenticate_success($result['account'],true,false,true);
+		authenticate_success($result['account'],null,true,false,true);
 		
 		$new_channel = false;
 		$next_page = 'new_channel';
diff --git a/Zotlabs/Module/Removeaccount.php b/Zotlabs/Module/Removeaccount.php
index 39e06bb7f..9fac7838e 100644
--- a/Zotlabs/Module/Removeaccount.php
+++ b/Zotlabs/Module/Removeaccount.php
@@ -25,7 +25,8 @@ class Removeaccount extends \Zotlabs\Web\Controller {
 		$account = \App::get_account();
 		$account_id = get_account_id();
 	
-		if(! account_verify_password($account['account_email'],$_POST['qxz_password']))
+		$x = account_verify_password($account['account_email'],$_POST['qxz_password']);
+		if(! ($x && $x['account']))
 			return;
 	
 		if($account['account_password_changed'] != NULL_DATE) {
diff --git a/Zotlabs/Module/Removeme.php b/Zotlabs/Module/Removeme.php
index e611d8112..bc18fe0f8 100644
--- a/Zotlabs/Module/Removeme.php
+++ b/Zotlabs/Module/Removeme.php
@@ -24,7 +24,9 @@ class Removeme extends \Zotlabs\Web\Controller {
 	
 		$account = \App::get_account();
 	
-		if(! account_verify_password($account['account_email'],$_POST['qxz_password']))
+	
+		$x = account_verify_password($account['account_email'],$_POST['qxz_password']);
+		if(! ($x && $x['account']))
 			return;
 	
 		if($account['account_password_changed'] != NULL_DATE) {
diff --git a/Zotlabs/Module/Settings.php b/Zotlabs/Module/Settings.php
index b1258e049..af3a25c60 100644
--- a/Zotlabs/Module/Settings.php
+++ b/Zotlabs/Module/Settings.php
@@ -21,10 +21,7 @@ class Settings extends \Zotlabs\Web\Controller {
 			// We are setting these values - don't use the argc(), argv() functions here
 			\App::$argc = 2;
 			\App::$argv[] = 'channel';
-		}
-	
-	
-	
+		}	
 	}
 	
 	
@@ -38,7 +35,7 @@ class Settings extends \Zotlabs\Web\Controller {
 	
 		$channel = \App::get_channel();
 	
-		 logger('mod_settings: ' . print_r($_REQUEST,true));
+		// logger('mod_settings: ' . print_r($_REQUEST,true));
 	
 	
 		if((argc() > 1) && (argv(1) === 'oauth') && x($_POST,'remove')){
@@ -363,10 +360,10 @@ class Settings extends \Zotlabs\Web\Controller {
 					intval(local_channel())
 				);	
 	
-				$global_perms = get_perms();
+				$global_perms = \Zotlabs\Access\Permissions::Perms();
 	
 				foreach($global_perms as $k => $v) {
-					$set_perms .= ', ' . $v[0] . ' = ' . intval($_POST[$k]) . ' ';
+					\Zotlabs\Access\PermissionLimits::Set(local_channel(),$k,intval($_POST[$k]));
 				}
 				$acl = new \Zotlabs\Access\AccessList($channel);
 				$acl->set_from_array($_POST);
@@ -382,7 +379,7 @@ class Settings extends \Zotlabs\Web\Controller {
 				);
 			}
 		    else {
-			   	$role_permissions = get_role_perms($_POST['permissions_role']);
+			   	$role_permissions = \Zotlabs\Access\PermissionRoles::role_perms($_POST['permissions_role']);
 				if(! $role_permissions) {
 					notice('Permissions category could not be found.');
 					return;
@@ -422,19 +419,24 @@ class Settings extends \Zotlabs\Web\Controller {
 					);
 				}
 	
-				$r = q("update abook set abook_my_perms  = %d where abook_channel = %d and abook_self = 1",
-					intval((array_key_exists('perms_accept',$role_permissions)) ? $role_permissions['perms_accept'] : 0),
-					intval(local_channel())
-				);
-				set_pconfig(local_channel(),'system','autoperms',(($role_permissions['perms_auto']) ? intval($role_permissions['perms_accept']) : 0));
-	
-				foreach($role_permissions as $p => $v) {
-					if(strpos($p,'channel_') !== false) {
-						$set_perms .= ', ' . $p . ' = ' . intval($v) . ' ';
+				$x = \Zotlabs\Access\Permissions::FilledPerms($role_permissions['perms_connect']);
+				foreach($x as $k => $v) {
+					set_abconfig(local_channel(),$channel['channel_hash'],'my_perms',$k, $v);
+					if($role_permissions['perms_auto']) {
+						set_pconfig(local_channel(),'autoperms',$k,$v);
 					}
-					if($p === 'directory_publish') {
-						$publish = intval($v);
+					else {
+						del_pconfig(local_channel(),'autoperms',$k);
 					}
+				}	
+
+				if($role_permissions['limits']) {
+					foreach($role_permissions['limits'] as $k => $v) {
+						\Zotlabs\Access\PermissionLimits::Set(local_channel(),$k,$v);
+					}
+				}
+				if(array_key_exists('directory_publish',$role_permissions)) {
+					$publish = intval($role_permissions['directory_publish']);
 				}
 			}
 	
@@ -963,11 +965,7 @@ class Settings extends \Zotlabs\Web\Controller {
 			
 			return $o;
 		}
-		
-		
-	
-	
-	
+			
 		if(argv(1) === 'channel') {
 	
 			require_once('include/acl_selectors.php');
@@ -984,9 +982,8 @@ class Settings extends \Zotlabs\Web\Controller {
 	
 			$channel = \App::get_channel();
 	
-	
-			$global_perms = get_perms();
-	
+			$global_perms = \Zotlabs\Access\Permissions::Perms();
+
 			$permiss = array();
 	
 			$perm_opts = array(
@@ -1000,19 +997,18 @@ class Settings extends \Zotlabs\Web\Controller {
 				array( t('Anybody on the internet'), PERMS_PUBLIC)
 			);
 	
+			$limits = \Zotlabs\Access\PermissionLimits::Get(local_channel());
 	
 			foreach($global_perms as $k => $perm) {
 				$options = array();
 				foreach($perm_opts as $opt) {
-					if((! $perm[2]) && $opt[1] == PERMS_PUBLIC)
-						continue;
 					$options[$opt[1]] = $opt[0];
 				}
-				$permiss[] = array($k,$perm[3],$channel[$perm[0]],$perm[4],$options);			
+				$permiss[] = array($k,$perm,$limits[$k],'',$options);			
 			}
 	
 	
-	//		logger('permiss: ' . print_r($permiss,true));
+			//logger('permiss: ' . print_r($permiss,true));
 	
 	
 	
diff --git a/Zotlabs/Storage/BasicAuth.php b/Zotlabs/Storage/BasicAuth.php
index 9c73b47b9..995976dcd 100644
--- a/Zotlabs/Storage/BasicAuth.php
+++ b/Zotlabs/Storage/BasicAuth.php
@@ -91,33 +91,20 @@ class BasicAuth extends DAV\Auth\Backend\AbstractBasic {
 
 		require_once('include/auth.php');
 		$record = account_verify_password($username, $password);
-		if ($record && $record['account_default_channel']) {
-			$r = q("SELECT * FROM channel WHERE channel_account_id = %d AND channel_id = %d LIMIT 1",
-				intval($record['account_id']),
-				intval($record['account_default_channel'])
-			);
-			if($r && $this->check_module_access($r[0]['channel_id'])) {
-				return $this->setAuthenticated($r[0]);
+		if($record && $record['account']) {
+			if($record['channel'])
+				$channel = $record['channel'];
+			else {
+				$r = q("SELECT * FROM channel WHERE channel_account_id = %d AND channel_id = %d LIMIT 1",
+					intval($record['account']['account_id']),
+					intval($record['account']['account_default_channel'])
+				);
+				if($r)
+					$channel = $r[0];
 			}
 		}
-		$r = q("SELECT * FROM channel WHERE channel_address = '%s' LIMIT 1",
-			dbesc($username)
-		);
-		if ($r) {
-			$x = q("SELECT account_flags, account_salt, account_password FROM account WHERE account_id = %d LIMIT 1",
-				intval($r[0]['channel_account_id'])
-			);
-			if ($x) {
-				// @fixme this foreach should not be needed?
-				foreach ($x as $record) {
-					if ((($record['account_flags'] == ACCOUNT_OK) || ($record['account_flags'] == ACCOUNT_UNVERIFIED))
-					&& (hash('whirlpool', $record['account_salt'] . $password) === $record['account_password'])) {
-						logger('password verified for ' . $username);
-						if($this->check_module_access($r[0]['channel_id']))
-							return $this->setAuthenticated($r[0]);
-					}
-				}
-			}
+		if($channel && $this->check_module_access($channel['channel_id'])) {
+			return $this->setAuthenticated($channel);
 		}
 
 		if($this->module_disabled)
@@ -178,6 +165,7 @@ class BasicAuth extends DAV\Auth\Backend\AbstractBasic {
     function check(RequestInterface $request, ResponseInterface $response) {
 
 		if(local_channel()) {
+			$this->setAuthenticated(\App::get_channel());
 			return [ true, $this->principalPrefix . $this->channel_name ];
 		}
 
@@ -275,4 +263,4 @@ class BasicAuth extends DAV\Auth\Backend\AbstractBasic {
 		logger('owner_id ' . $this->owner_id, LOGGER_DATA);
 		logger('owner_nick ' . $this->owner_nick, LOGGER_DATA);
 	}
-}
\ No newline at end of file
+}
diff --git a/Zotlabs/Zot/Finger.php b/Zotlabs/Zot/Finger.php
index 229fda8bd..e7603442f 100644
--- a/Zotlabs/Zot/Finger.php
+++ b/Zotlabs/Zot/Finger.php
@@ -28,7 +28,7 @@ class Finger {
 
 		if (strpos($webbie,'@') === false) {
 			$address = $webbie;
-			$host = App::get_hostname();
+			$host = \App::get_hostname();
 		} else {
 			$address = substr($webbie,0,strpos($webbie,'@'));
 			$host = substr($webbie,strpos($webbie,'@')+1);
@@ -127,4 +127,4 @@ class Finger {
 		return $x;
 	}
 
-}
\ No newline at end of file
+}
diff --git a/boot.php b/boot.php
index 08da2a9d6..de483035e 100755
--- a/boot.php
+++ b/boot.php
@@ -47,7 +47,7 @@ define ( 'PLATFORM_NAME',           'hubzilla' );
 define ( 'STD_VERSION',             '1.11' );
 define ( 'ZOT_REVISION',            '1.1' );
 
-define ( 'DB_UPDATE_VERSION',       1180  );
+define ( 'DB_UPDATE_VERSION',       1181  );
 
 
 /**
@@ -2449,6 +2449,7 @@ function check_cron_broken() {
 	
 	if((! $d) || ($d < datetime_convert('UTC','UTC','now - 4 hours'))) {
 		Zotlabs\Daemon\Master::Summon(array('Cron'));
+		set_config('system','lastcron',datetime_convert());
 	}
 
 	$t = get_config('system','lastcroncheck');
diff --git a/include/account.php b/include/account.php
index c02a74928..142ad1bea 100644
--- a/include/account.php
+++ b/include/account.php
@@ -515,7 +515,7 @@ function account_approve($hash) {
 		auto_channel_create($register[0]['uid']);
 	else {
 		$_SESSION['login_return_url'] = 'new_channel';
-		authenticate_success($account[0],true,true,false,true);
+		authenticate_success($account[0],null,true,true,false,true);
 	}	
 
 
diff --git a/include/api.php b/include/api.php
index df6aba957..8d475c5fa 100644
--- a/include/api.php
+++ b/include/api.php
@@ -282,7 +282,8 @@ require_once('include/api_auth.php');
 					intval($uinfo[0]['xchan_hash'])
 			);
 			$countitms = $r[0]['count'];
-			$following = (($uinfo[0]['abook_myperms'] & PERMS_R_STREAM) ? true : false );
+			
+			$following = ((get_abconfig($uinfo[0]['abook_channel'],$uinfo[0]['abook_xchan'],'my_perms','view_stream')) ? true : false );
 		}
 
 
diff --git a/include/api_auth.php b/include/api_auth.php
index dc8492b20..7a71bad73 100644
--- a/include/api_auth.php
+++ b/include/api_auth.php
@@ -59,20 +59,12 @@ function api_login(&$a){
 	if(isset($_SERVER['PHP_AUTH_USER'])) {
 		$channel_login = 0;
 		$record = account_verify_password($_SERVER['PHP_AUTH_USER'],$_SERVER['PHP_AUTH_PW']);
-		if(! $record) {
-	        $r = q("select * from channel left join account on account.account_id = channel.channel_account_id 
-				where channel.channel_address = '%s' limit 1",
-		       dbesc($_SERVER['PHP_AUTH_USER'])
-			);
-        	if ($r) {
-				$record = account_verify_password($r[0]['account_email'],$_SERVER['PHP_AUTH_PW']);
-				if($record)
-					$channel_login = $r[0]['channel_id'];
-			}
+		if($record && $record['channel']) {
+			$channel_login = $record['channel']['channel_id'];
 		}
 	}
 
-	if($record) {
+	if($record['account']) {
 		authenticate_success($record);
 
 		if($channel_login)
diff --git a/include/auth.php b/include/auth.php
index 79d04c728..f3592cee3 100644
--- a/include/auth.php
+++ b/include/auth.php
@@ -16,66 +16,97 @@ require_once('include/security.php');
 /**
  * @brief Verify login credentials.
  *
- * If system authlog is set a log entry will be added for failed login
+ * If system.authlog is set a log entry will be added for failed login
  * attempts.
  *
- * @param string $email
- *  The email address to verify.
+ * @param string $login
+ *  The login to verify (channel address, account email or guest login token).
  * @param string $pass
  *  The provided password to verify.
  * @return array|null
  *  Returns account record on success, null on failure.
+ *  The return array is dependent on the login mechanism.
+ *    $ret['account'] will be set if either an email or channel address validation was successful (local login).
+ *    $ret['channel'] will be set if a channel address validation was successful.
+ *    $ret['xchan'] will be set if a guest access token validation was successful. 
+ *   Keys will exist for invalid return arrays but will be set to null. 
+ *   This function does not perform a login. It merely validates systems passwords and tokens.  
+ *
  */
-function account_verify_password($email, $pass) {
+
+function account_verify_password($login, $pass) {
+
+	$ret = [ 'account' => null, 'channel' => null, 'xchan' => null ];
 
 	$email_verify = get_config('system', 'verify_email');
 	$register_policy = get_config('system', 'register_policy');
 
+	if(! $login)
+		return null;
+
+	$account = null;
+	$channel = null;
+	$xchan   = null;
+
+	if(! strpos($login,'@')) {
+		$channel = channelx_by_nick($login);
+		if(! $channel) {
+			$x = q("select * from atoken where atoken_name = '%s' and atoken_token = '%s' limit 1",
+				dbesc($login),
+				dbesc($pass)
+			);
+			if($x) {
+				$ret['xchan'] = atoken_xchan($x[0]);
+				return $ret;
+			}
+		}
+	}
+	if($channel) {
+		$where = " where account_id = " . intval($channel['channel_account_id']) . " ";
+	}
+	else {
+		$where = " where account_email = '" . dbesc($login) . "' ";
+	}
+
+	$a = q("select * from account $where");
+	if(! $a) {
+		return null;
+	}
+
+	$account = $a[0];
+
 	// Currently we only verify email address if there is an open registration policy.
 	// This isn't because of any policy - it's because the workflow gets too complicated if 
 	// you have to verify the email and then go through the account approval workflow before
 	// letting them login.
 
-	// @bug there is no record here
-	//if(($email_verify) && ($register_policy == REGISTER_OPEN) && ($record['account_flags'] & ACCOUNT_UNVERIFIED))
-	//	return null;
-
-	$r = q("select * from account where account_email = '%s'",
-		dbesc($email)
-	);
-	if($r) {
-
-		foreach($r as $record) {
-			if(($record['account_flags'] == ACCOUNT_OK)
-				&& (hash('whirlpool', $record['account_salt'] . $pass) === $record['account_password'])) {
-				logger('password verified for ' . $email);
-				return $record;
-			}
-		}
+	if(($email_verify) && ($register_policy == REGISTER_OPEN) && ($account['account_flags'] & ACCOUNT_UNVERIFIED)) {
+		logger('email verification required for ' . $login);
+		return null;
 	}
 
-	$x = q("select * from atoken where atoken_name = '%s' and atoken_token = '%s' limit 1",
-		dbesc($email),
-		dbesc($pass)
-	);
-	if($x) {
-		atoken_login($x[0]);		
-		return $x[0];
+	if(($account['account_flags'] == ACCOUNT_OK) 
+		&& (hash('whirlpool',$account['account_salt'] . $pass) === $account['account_password'])) {
+		logger('password verified for ' . $login);
+		$ret['account'] = $account;
+		if($channel)
+			$ret['channel'] = $channel;
+		return $ret;
 	}
 
-	$error = 'password failed for ' . $email;
+	$error = 'password failed for ' . $login;
 	logger($error);
 
-	if($record['account_flags'] & ACCOUNT_UNVERIFIED)
-		logger('Account is unverified. account_flags = ' . $record['account_flags']);
-	if($record['account_flags'] & ACCOUNT_BLOCKED)
-		logger('Account is blocked. account_flags = ' . $record['account_flags']);
-	if($record['account_flags'] & ACCOUNT_EXPIRED)
-		logger('Account is expired. account_flags = ' . $record['account_flags']);
-	if($record['account_flags'] & ACCOUNT_REMOVED)
-		logger('Account is removed. account_flags = ' . $record['account_flags']);
-	if($record['account_flags'] & ACCOUNT_PENDING)
-		logger('Account is pending. account_flags = ' . $record['account_flags']);
+	if($account['account_flags'] & ACCOUNT_UNVERIFIED)
+		logger('Account is unverified. account_flags = ' . $account['account_flags']);
+	if($account['account_flags'] & ACCOUNT_BLOCKED)
+		logger('Account is blocked. account_flags = ' . $account['account_flags']);
+	if($account['account_flags'] & ACCOUNT_EXPIRED)
+		logger('Account is expired. account_flags = ' . $account['account_flags']);
+	if($account['account_flags'] & ACCOUNT_REMOVED)
+		logger('Account is removed. account_flags = ' . $account['account_flags']);
+	if($account['account_flags'] & ACCOUNT_PENDING)
+		logger('Account is pending. account_flags = ' . $account['account_flags']);
 
 	log_failed_login($error);
 
@@ -131,7 +162,7 @@ if((isset($_SESSION)) && (x($_SESSION, 'authenticated')) &&
 				App::$session->new_cookie(60 * 60 * 24); // one day
 				$_SESSION['last_login_date'] = datetime_convert();
 				unset($_SESSION['visitor_id']); // no longer a visitor
-				authenticate_success($x[0], true, true);
+				authenticate_success($x[0], null, true, true);
 			}
 		}
 		if(array_key_exists('atoken',$_SESSION)) {
@@ -177,7 +208,8 @@ if((isset($_SESSION)) && (x($_SESSION, 'authenticated')) &&
 				App::$session->extend_cookie();
 				$login_refresh = true;
 			}
-			authenticate_success($r[0], false, false, false, $login_refresh);
+			$ch = (($_SESSION['uid']) ? channelx_by_n($_SESSION['uid']) : null);
+			authenticate_success($r[0], null, $ch, false, false, $login_refresh);
 		}
 		else {
 			$_SESSION['account_id'] = 0;
@@ -218,37 +250,38 @@ else {
 
 		call_hooks('authenticate', $addon_auth);
 
-		$atoken = false;
+		$atoken  = null;
+		$account = null;
 
 		if(($addon_auth['authenticated']) && (count($addon_auth['user_record']))) {
-			$record = $addon_auth['user_record'];
+			$account = $addon_auth['user_record'];
 		}
 		else {
-			$x = account_verify_password($_POST['username'], $_POST['password']);
-			if(array_key_exists('atoken',$x))
-				$atoken = true;
-			if(! $atoken) {
-				$record = App::$account = $x;
+			$verify = account_verify_password($_POST['username'], $_POST['password']);
+			if($verify) {
+				$atoken  = $verify['xchan'];
+				$channel = $verify['channel'];
+				$account = App::$account = $verify['account'];
+			}
 
-				if(App::$account) {
-					$_SESSION['account_id'] = App::$account['account_id'];
-				}
-				else {
-					notice( t('Failed authentication') . EOL);
-				}
-
-				logger('authenticate: ' . print_r(App::$account, true), LOGGER_ALL);
+			if(App::$account) {
+				$_SESSION['account_id'] = App::$account['account_id'];
+			}
+			elseif($atoken) {
+				atoken_login($atoken);
+			}
+			else {
+				notice( t('Failed authentication') . EOL);
 			}
 		}
 
-		if((! $record) || (! count($record))) {
+		if(! ($account || $atoken)) {
 			$error = 'authenticate: failed login attempt: ' . notags(trim($_POST['username'])) . ' from IP ' . $_SERVER['REMOTE_ADDR'];
 			logger($error); 
 			// Also log failed logins to a separate auth log to reduce overhead for server side intrusion prevention
 			$authlog = get_config('system', 'authlog');
 			if ($authlog)
 				@file_put_contents($authlog, datetime_convert() . ':' . session_id() . ' ' . $error . "\n", FILE_APPEND);
-
 			notice( t('Login failed.') . EOL );
 			goaway(z_root() . '/login');
 		}
@@ -279,7 +312,7 @@ else {
 
 		$_SESSION['last_login_date'] = datetime_convert();
 		if(! $atoken)
-			authenticate_success($record, true, true);
+			authenticate_success($account,$channel,true, true);
 	}
 }
 
diff --git a/include/channel.php b/include/channel.php
index 1a6508803..a5233743d 100644
--- a/include/channel.php
+++ b/include/channel.php
@@ -6,6 +6,7 @@
 require_once('include/zot.php');
 require_once('include/crypto.php');
 require_once('include/menu.php');
+require_once('include/perm_upgrade.php');
 
 /**
  * @brief Called when creating a new channel.
@@ -225,42 +226,26 @@ function create_identity($arr) {
 	if(array_key_exists('publish', $arr))
 		$publish = intval($arr['publish']);
 
+	$role_permissions = null;
+
+	if(array_key_exists('permissions_role',$arr) && $arr['permissions_role']) {
+		$role_permissions = \Zotlabs\Access\PermissionRoles::role_perms($arr['permissions_role']);
+	}
+
+	if($role_permissions && array_key_exists('directory_publish',$role_permissions))
+		$publish = intval($role_permissions['directory_publish']);
+
 	$primary = true;
 		
 	if(array_key_exists('primary', $arr))
 		$primary = intval($arr['primary']);
 
-	$role_permissions = null;
-	$global_perms = get_perms();
-
-	if(array_key_exists('permissions_role',$arr) && $arr['permissions_role']) {
-		$role_permissions = get_role_perms($arr['permissions_role']);
-
-		if($role_permissions) {
-			foreach($role_permissions as $p => $v) {
-				if(strpos($p,'channel_') !== false) {
-					$perms_keys .= ', ' . $p;
-					$perms_vals .= ', ' . intval($v);
-				}
-				if($p === 'directory_publish')
-					$publish = intval($v);
-			}
-		}
-	}
-	else {
-		$defperms = site_default_perms();
-		foreach($defperms as $p => $v) {
-			$perms_keys .= ', ' . $global_perms[$p][0];
-			$perms_vals .= ', ' . intval($v);
-		}
-	}
-
 	$expire = 0;
 
 	$r = q("insert into channel ( channel_account_id, channel_primary, 
 		channel_name, channel_address, channel_guid, channel_guid_sig,
-		channel_hash, channel_prvkey, channel_pubkey, channel_pageflags, channel_system, channel_expire_days, channel_timezone $perms_keys )
-		values ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, %d, %d, '%s' $perms_vals ) ",
+		channel_hash, channel_prvkey, channel_pubkey, channel_pageflags, channel_system, channel_expire_days, channel_timezone )
+		values ( %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, %d, %d, '%s' ) ",
 
 		intval($arr['account_id']),
 		intval($primary),
@@ -288,6 +273,17 @@ function create_identity($arr) {
 		return $ret;
 	}
 
+	if($role_permissions && array_key_exists('limits',$role_permissions))
+		$perm_limits = $role_permissions['limits'];
+	else
+		$perm_limits = site_default_perms();
+
+	foreach($perm_limits as $p => $v)
+		\Zotlabs\Access\PermissionLimits::Set($r[0]['channel_id'],$p,$v);
+
+	if($role_permissions && array_key_exists('perms_auto',$role_permissions))
+		set_pconfig($r[0]['channel_id'],'system','autoperms',intval($role_permissions['perms_auto']));
+
 	$ret['channel'] = $r[0];
 
 	if(intval($arr['account_id']))
@@ -351,25 +347,29 @@ function create_identity($arr) {
 	);
 
 	if($role_permissions) {
-		$myperms = ((array_key_exists('perms_accept',$role_permissions)) ? intval($role_permissions['perms_accept']) : 0);
+		$myperms = ((array_key_exists('perms_connect',$role_permissions)) ? $role_permissions['perms_connect'] : array());
+	}
+	else {
+		$x = \Zotlabs\Access\PermissionRoles::role_perms('social');
+		$myperms = $x['perms_connect'];
 	}
-	else
-		$myperms = PERMS_R_STREAM|PERMS_R_PROFILE|PERMS_R_PHOTOS|PERMS_R_ABOOK
-			|PERMS_W_STREAM|PERMS_W_WALL|PERMS_W_COMMENT|PERMS_W_MAIL|PERMS_W_CHAT
-			|PERMS_R_STORAGE|PERMS_R_PAGES|PERMS_W_LIKE;
 
-	$r = q("insert into abook ( abook_account, abook_channel, abook_xchan, abook_closeness, abook_created, abook_updated, abook_self, abook_my_perms )
-		values ( %d, %d, '%s', %d, '%s', '%s', %d, %d ) ",
+	$r = q("insert into abook ( abook_account, abook_channel, abook_xchan, abook_closeness, abook_created, abook_updated, abook_self )
+		values ( %d, %d, '%s', %d, '%s', '%s', %d ) ",
 		intval($ret['channel']['channel_account_id']),
 		intval($newuid),
 		dbesc($hash),
 		intval(0),
 		dbesc(datetime_convert()),
 		dbesc(datetime_convert()),
-		intval(1),
-		intval($myperms)
+		intval(1)
 	);
 
+	$x = \Zotlabs\Access\Permissions::FilledPerms($myperms);
+	foreach($x as $k => $v) {
+		set_abconfig($newuid,$hash,'my_perms',$k,$v);
+	}
+
 	if(intval($ret['channel']['channel_account_id'])) {
 
 		// Save our permissions role so we can perhaps call it up and modify it later.
@@ -378,8 +378,21 @@ function create_identity($arr) {
 			set_pconfig($newuid,'system','permissions_role',$arr['permissions_role']);
 			if(array_key_exists('online',$role_permissions))
 				set_pconfig($newuid,'system','hide_presence',1-intval($role_permissions['online']));
-			if(array_key_exists('perms_auto',$role_permissions))
-				set_pconfig($newuid,'system','autoperms',(($role_permissions['perms_auto']) ? $role_permissions['perms_accept'] : 0));
+			if(array_key_exists('perms_auto',$role_permissions)) {
+				$autoperms = intval($role_permissions['perms_auto']);
+				set_pconfig($newuid,'system','autoperms',$autoperms);
+				if($autoperms) {
+					$x = \Zotlabs\Access\Permissions::FilledPerms($role_permissions['perms_connect']);
+					foreach($x as $k => $v) {
+						set_pconfig($newuid,'autoperms',$k,$v);
+					}
+				}
+				else {
+					$r = q("delete from pconfig where uid = %d and cat = 'autoperms'",
+						intval($newuid)
+					);
+				}
+			}						
 		}
 
 		// Create a group with yourself as a member. This allows somebody to use it 
@@ -497,7 +510,8 @@ function identity_basic_export($channel_id, $items = false) {
 		intval($channel_id)
 	);
 	if($r) {
-		$ret['channel'] = $r[0];
+		translate_channel_perms_outbound($r[0]);
+		$ret['channel'] = $r[0];		
 		$ret['relocate'] = [ 'channel_address' => $r[0]['channel_address'], 'url' => z_root()];
 	}
 
@@ -519,6 +533,7 @@ function identity_basic_export($channel_id, $items = false) {
 			$abconfig = load_abconfig($channel_id,$ret['abook'][$x]['abook_xchan']);
 			if($abconfig)
 				$ret['abook'][$x]['abconfig'] = $abconfig;
+			translate_abook_perms_outbound($ret['abook'][$x]);
 		}		 
 		stringify_array_elms($xchans);
 	}
@@ -1356,7 +1371,8 @@ function zat_init() {
 		dbesc($_REQUEST['zat'])
 	);
 	if($r) {
-		atoken_login($r[0]);
+		$xchan = atoken_xchan($r[0]);
+		atoken_login($xchan);
 	}
 
 }
@@ -1551,9 +1567,11 @@ function is_public_profile() {
 	if(intval(get_config('system','block_public')))
 		return false;
 	$channel = App::get_channel();
-	if($channel && $channel['channel_r_profile'] == PERMS_PUBLIC)
-		return true;
-
+	if($channel) {
+		$perm = \Zotlabs\Access\PermissionLimit::Get($channel['channel_id'],'view_profile');
+		if($perm == PERMS_PUBLIC)
+			return true;
+	}
 	return false;
 }
 
@@ -1625,13 +1643,13 @@ function notifications_on($channel_id,$value) {
 
 function get_channel_default_perms($uid) {
 
-	$r = q("select abook_my_perms from abook where abook_channel = %d and abook_self = 1 limit 1",
+	$r = q("select abook_xchan from abook where abook_channel = %d and abook_self = 1 limit 1",
 		intval($uid)
 	);
 	if($r)
-		return $r[0]['abook_my_perms'];
+		return load_abconfig($uid,$r[0]['abook_xchan'],'my_perms');
 
-	return 0;
+	return array();
 }
 
 
diff --git a/include/config.php b/include/config.php
index 08810e298..8c0469392 100644
--- a/include/config.php
+++ b/include/config.php
@@ -97,7 +97,6 @@ function del_aconfig($account_id, $family, $key) {
 	return Zlib\AConfig::Delete($account_id, $family, $key);
 }
 
-
 function load_abconfig($chan, $xhash, $family = '') {
 	return Zlib\AbConfig::Load($chan,$xhash,$family);
 }
diff --git a/include/connections.php b/include/connections.php
index ed4526a09..9f55820cc 100644
--- a/include/connections.php
+++ b/include/connections.php
@@ -260,15 +260,15 @@ function channel_remove($channel_id, $local = true, $unset_session=false) {
 	
 	if(! $local) {
 
-		$r = q("update channel set channel_deleted = '%s', channel_removed = 1, channel_r_stream = 0, channel_r_profile = 0,
-			channel_r_photos = 0, channel_r_abook = 0, channel_w_stream = 0, channel_w_wall = 0, channel_w_tagwall = 0,
-			channel_w_comment = 0, channel_w_mail = 0, channel_w_photos = 0, channel_w_chat = 0, channel_a_delegate = 0,
-			channel_r_storage = 0, channel_w_storage = 0, channel_r_pages = 0, channel_w_pages = 0, channel_a_republish = 0 
-			where channel_id = %d",
+		$r = q("update channel set channel_deleted = '%s', channel_removed = 1 where channel_id = %d",
 			dbesc(datetime_convert()),
 			intval($channel_id)
 		);
 
+		q("delete from pconfig where uid = %d",
+			intval($channel_id)
+		);
+
 		logger('deleting hublocs',LOGGER_DEBUG);
 	
 		$r = q("update hubloc set hubloc_deleted = 1 where hubloc_hash = '%s'",
diff --git a/include/conversation.php b/include/conversation.php
index 957dbf8e9..1efca37f3 100644
--- a/include/conversation.php
+++ b/include/conversation.php
@@ -99,7 +99,7 @@ function localize_item(&$item){
 		if(intval($item['item_thread_top']))
 			return;	
 
-		$obj = json_decode_plus($item['obj']);
+		$obj = json_decode($item['obj'],true);
 		if((! $obj) && ($item['obj'])) {
 			logger('localize_item: failed to decode object: ' . print_r($item['obj'],true));
 		}
@@ -186,7 +186,7 @@ function localize_item(&$item){
 		$Alink = $item['author']['xchan_url'];
 
 
-		$obj= json_decode_plus($item['obj']);
+		$obj= json_decode($item['obj'],true);
 		
 		$Blink = $Bphoto = '';
 
@@ -219,7 +219,7 @@ function localize_item(&$item){
 		$Aname = $item['author']['xchan_name'];
 		$Alink = $item['author']['xchan_url'];
 
-		$obj= json_decode_plus($item['obj']);
+		$obj= json_decode($item['obj'],true);
 
 		$Blink = $Bphoto = '';
 
diff --git a/include/datetime.php b/include/datetime.php
index 600ad6ec4..76bd6b8d6 100644
--- a/include/datetime.php
+++ b/include/datetime.php
@@ -556,13 +556,13 @@ function update_birthdays() {
 			$ev['uid'] = $rr['abook_channel'];
 			$ev['account'] = $rr['abook_account'];
 			$ev['event_xchan'] = $rr['xchan_hash'];
-			$ev['start'] = datetime_convert('UTC', 'UTC', $rr['abook_dob']);
-			$ev['finish'] = datetime_convert('UTC', 'UTC', $rr['abook_dob'] . ' + 1 day ');
+			$ev['dtstart'] = datetime_convert('UTC', 'UTC', $rr['abook_dob']);
+			$ev['dtend'] = datetime_convert('UTC', 'UTC', $rr['abook_dob'] . ' + 1 day ');
 			$ev['adjust'] = intval(feature_enabled($rr['abook_channel'],'smart_birthdays'));
 			$ev['summary'] = sprintf( t('%1$s\'s birthday'), $rr['xchan_name']);
 			$ev['description'] = sprintf( t('Happy Birthday %1$s'), 
 				'[zrl=' . $rr['xchan_url'] . ']' . $rr['xchan_name'] . '[/zrl]') ;
-			$ev['type'] = 'birthday';
+			$ev['etype'] = 'birthday';
 
 			$z = event_store_event($ev);
 			if ($z) {
diff --git a/include/event.php b/include/event.php
index a4118ec78..3d650cd14 100644
--- a/include/event.php
+++ b/include/event.php
@@ -183,7 +183,9 @@ function format_ical_text($s) {
 	require_once('include/bbcode.php');
 	require_once('include/html2plain.php');
 
-	return(wordwrap(str_replace(array(',',';','\\'),array('\\,','\\;','\\\\'),html2plain(bbcode($s))),72,"\r\n ",true));
+	$s = html2plain(bbcode($s));
+	$s = str_replace(["\r\n","\n"],["",""],$s);
+	return(wordwrap(str_replace(['\\',',',';'],['\\\\','\\,','\\;'],$s),72,"\r\n ",true));
 }
 
 
diff --git a/include/follow.php b/include/follow.php
index e5a74f85e..5f63687f8 100644
--- a/include/follow.php
+++ b/include/follow.php
@@ -66,12 +66,11 @@ function new_contact($uid,$url,$channel,$interactive = false, $confirm = false)
 
 	$role = get_pconfig($uid,'system','permissions_role');
 	if($role) {
-		$x = get_role_perms($role);
-		if($x['perms_follow'])
-			$my_perms = $x['perms_follow'];
+		$x = \Zotlabs\Access\PermissionRoles::role_perms($role);
+		if($x['perms_connect'])
+			$my_perms = $x['perms_connect'];
 	}
 
-
 	if($is_red && $j) {
 
 		logger('follow: ' . $url . ' ' . print_r($j,true), LOGGER_DEBUG);
@@ -104,10 +103,6 @@ function new_contact($uid,$url,$channel,$interactive = false, $confirm = false)
 
 		$xchan_hash = $x['hash'];
 
-		$their_perms = 0;
-
-		$global_perms = get_perms();
-
 		if( array_key_exists('permissions',$j) && array_key_exists('data',$j['permissions'])) {
 			$permissions = crypto_unencapsulate(array(
 				'data' => $j['permissions']['data'],
@@ -121,16 +116,14 @@ function new_contact($uid,$url,$channel,$interactive = false, $confirm = false)
 		else
 			$permissions = $j['permissions'];
 
-
-		foreach($permissions as $k => $v) {
-			if($v) {
-				$their_perms = $their_perms | intval($global_perms[$k][1]);
+		if(is_array($permissions) && $permissions) {
+			foreach($permissions as $k => $v) {
+				set_abconfig($channel['channel_uid'],$xchan_hash,'their_perms',$k,intval($v));
 			}
 		}
 	}
 	else {
 
-		$their_perms = 0;
 		$xchan_hash = '';
 
 		$r = q("select * from xchan where xchan_hash = '%s' or xchan_url = '%s' limit 1",
@@ -190,6 +183,7 @@ function new_contact($uid,$url,$channel,$interactive = false, $confirm = false)
 		$result['message'] = t('Protocol disabled.');
 		return $result;
 	}
+
 	$singleton = intval($x['singleton']);
 
 	$aid = $channel['channel_account_id'];
@@ -222,6 +216,15 @@ function new_contact($uid,$url,$channel,$interactive = false, $confirm = false)
 		intval($uid)
 	);
 
+	if($is_http) {
+
+		// Always set these "remote" permissions for feeds since we cannot interact with them
+		// to negotiate a suitable permission response
+
+		set_abconfig($uid,$xchan_hash,'their_perms','view_stream',1);
+		set_abconfig($uid,$xchan_hash,'their_perms','republish',1);
+	}
+
 	if($r) {
 		$abook_instance = $r[0]['abook_instance'];
 
@@ -231,8 +234,7 @@ function new_contact($uid,$url,$channel,$interactive = false, $confirm = false)
 			$abook_instance .= z_root();
 		}
 
-		$x = q("update abook set abook_their_perms = %d, abook_instance = '%s' where abook_id = %d",
-			intval($their_perms),
+		$x = q("update abook set abook_instance = '%s' where abook_id = %d",
 			dbesc($abook_instance),
 			intval($r[0]['abook_id'])
 		);		
@@ -242,15 +244,13 @@ function new_contact($uid,$url,$channel,$interactive = false, $confirm = false)
 		if($closeness === false)
 			$closeness = 80;
 
-		$r = q("insert into abook ( abook_account, abook_channel, abook_closeness, abook_xchan, abook_feed, abook_their_perms, abook_my_perms, abook_created, abook_updated, abook_instance )
-			values( %d, %d, %d, '%s', %d, %d, %d, '%s', '%s', '%s' ) ",
+		$r = q("insert into abook ( abook_account, abook_channel, abook_closeness, abook_xchan, abook_feed, abook_created, abook_updated, abook_instance )
+			values( %d, %d, %d, '%s', %d, '%s', '%s', '%s' ) ",
 			intval($aid),
 			intval($uid),
 			intval($closeness),
 			dbesc($xchan_hash),
 			intval(($is_http) ? 1 : 0),
-			intval(($is_http) ? $their_perms|PERMS_R_STREAM|PERMS_A_REPUBLISH : $their_perms),
-			intval($my_perms),
 			dbesc(datetime_convert()),
 			dbesc(datetime_convert()),
 			dbesc(($singleton) ? z_root() : '')
@@ -260,6 +260,16 @@ function new_contact($uid,$url,$channel,$interactive = false, $confirm = false)
 	if(! $r)
 		logger('mod_follow: abook creation failed');
 
+	$all_perms = \Zotlabs\Access\Permissions::Perms();
+	if($all_perms) {
+		foreach($all_perms as $k => $v) {
+			if(in_array($k,$my_perms))
+				set_abconfig($uid,$xchan_hash,'my_perms',$k,1);
+			else
+				set_abconfig($uid,$xchan_hash,'my_perms',$k,0);
+		}
+	}
+
 	$r = q("select abook.*, xchan.* from abook left join xchan on abook_xchan = xchan_hash 
 		where abook_xchan = '%s' and abook_channel = %d limit 1",
 		dbesc($xchan_hash),
diff --git a/include/import.php b/include/import.php
index 00ecef07d..42c902a0a 100644
--- a/include/import.php
+++ b/include/import.php
@@ -1,6 +1,7 @@
  $v) {
+		if(in_array($k,$disallowed))
+			continue;
+		$clean[$k] = $v;
+	}
+
+	if($clean) {
+		dbesc_array($clean);
+
+		$r = dbq("INSERT INTO channel (`" 
+			. implode("`, `", array_keys($clean)) 
+			. "`) VALUES ('" 
+			. implode("', '", array_values($clean)) 
+			. "')" 
+		);
+	}
 
 	if(! $r) {
 		logger('mod_import: channel clone failed. ', print_r($channel,true));
@@ -86,6 +107,14 @@ function import_channel($channel, $account_id, $seize) {
 		notice( t('Cloned channel not found. Import failed.') . EOL);
 		return false;
 	}
+
+	// extract the permissions from the original imported array and use our new channel_id to set them
+	// These could be in the old channel permission stule or the new pconfig. We have a function to
+	// translate and store them no matter which they throw at us.
+
+	$channel['channel_id'] = $r[0]['channel_id'];
+	translate_channel_perms_inbound($channel);
+
 	// reset
 	$channel = $r[0];
 
@@ -1004,7 +1033,7 @@ function sync_files($channel,$files) {
 						$attach_id = $x[0]['id'];
 					}
 
-					$newfname = 'store/' . $channel['channel_address'] . '/' . get_attach_binname($att['data']);
+					$newfname = 'store/' . $channel['channel_address'] . '/' . get_attach_binname($att['content']);
 
  					unset($att['id']);
 					$att['aid'] = $channel['channel_account_id'];
diff --git a/include/items.php b/include/items.php
index 373090d41..178fb30d6 100755
--- a/include/items.php
+++ b/include/items.php
@@ -183,7 +183,7 @@ function is_item_normal($item) {
  * This function examines the comment_policy attached to an item and decides if the current observer has
  * sufficient privileges to comment. This will normally be called on a remote site where perm_is_allowed()
  * will not be suitable because the post owner does not have a local channel_id.
- * Generally we should look at the item - in particular the author['book_flags'] and see if ABOOK_FLAG_SELF is set.
+ * Generally we should look at the item - in particular the author['abook_flags'] and see if ABOOK_FLAG_SELF is set.
  * If it is, you should be able to use perm_is_allowed( ... 'post_comments'), and if it isn't you need to call
  * can_comment_on_post()
  * We also check the comments_closed date/time on the item if this is set.
@@ -224,8 +224,7 @@ function can_comment_on_post($observer_xchan, $item) {
 		case 'contacts':
 		case 'authenticated':
 		case '':
-			if(array_key_exists('owner',$item)) {
-				if(($item['owner']['abook_xchan']) && ($item['owner']['abook_their_perms'] & PERMS_W_COMMENT))
+			if(array_key_exists('owner',$item) && get_abconfig($item['uid'],$item['owner']['abook_xchan'],'their_perms','post_comments')) {
 					return true;
 			}
 			break;
@@ -386,7 +385,7 @@ function post_activity_item($arr) {
 		return $ret;
 	}
 
-	$arr['public_policy'] = ((x($_REQUEST,'public_policy')) ? escape_tags($_REQUEST['public_policy']) : map_scope($channel['channel_r_stream'],true));
+	$arr['public_policy'] = ((x($_REQUEST,'public_policy')) ? escape_tags($_REQUEST['public_policy']) : map_scope(\Zotlabs\Access\PermissionLimits::Get($channel['channel_id'],'view_stream'),true));
 	if($arr['public_policy'])
 		$arr['item_private'] = 1;
 
@@ -422,7 +421,7 @@ function post_activity_item($arr) {
 	$arr['deny_cid']     = ((x($arr,'deny_cid')) ? $arr['deny_cid'] : $channel['channel_deny_cid']);
 	$arr['deny_gid']     = ((x($arr,'deny_gid')) ? $arr['deny_gid'] : $channel['channel_deny_gid']);
 
-	$arr['comment_policy'] = map_scope($channel['channel_w_comment']);
+	$arr['comment_policy'] = map_scope(\Zotlabs\Access/PermissionLimits::Get($channel['channel_id'],'post_comments'));
 
 	if ((! $arr['plink']) && (intval($arr['item_thread_top']))) {
 		$arr['plink'] = z_root() . '/channel/' . $channel['channel_address'] . '/?f=&mid=' . $arr['mid'];
@@ -971,12 +970,12 @@ function encode_item($item,$mirror = false) {
 
 //	logger('encode_item: ' . print_r($item,true));
 
-	$r = q("select channel_r_stream, channel_w_comment from channel where channel_id = %d limit 1",
+	$r = q("select channel_id from channel where channel_id = %d limit 1",
 		intval($item['uid'])
 	);
 
 	if($r)
-		$comment_scope = $r[0]['channel_w_comment'];
+		$comment_scope = \Zotlabs\Access\PermissionLimits::Get($item['uid'],'post_comments');
 	else
 		$comment_scope = 0;
 
@@ -990,9 +989,9 @@ function encode_item($item,$mirror = false) {
 
 	if(array_key_exists('item_obscured',$item) && intval($item['item_obscured'])) {
 		if($item['title'])
-			$item['title'] = crypto_unencapsulate(json_decode_plus($item['title']),$key);
+			$item['title'] = crypto_unencapsulate(json_decode($item['title'],true),$key);
 		if($item['body'])
-			$item['body'] = crypto_unencapsulate(json_decode_plus($item['body']),$key);
+			$item['body'] = crypto_unencapsulate(json_decode($item['body'],true),$key);
 	}
 
 	// If we're trying to backup an item so that it's recoverable or for export/imprt,
@@ -1062,11 +1061,11 @@ function encode_item($item,$mirror = false) {
 	$x['owner']           = encode_item_xchan($item['owner']);
 	$x['author']          = encode_item_xchan($item['author']);
 	if($item['obj'])
-		$x['object']      = json_decode_plus($item['obj']);
+		$x['object']      = json_decode($item['obj'],true);
 	if($item['target'])
-		$x['target']      = json_decode_plus($item['target']);
+		$x['target']      = json_decode($item['target'],true);
 	if($item['attach'])
-		$x['attach']      = json_decode_plus($item['attach']);
+		$x['attach']      = json_decode($item['attach'],true);
 	if($y = encode_item_flags($item))
 		$x['flags']       = $y;
 
@@ -1382,7 +1381,7 @@ function encode_mail($item,$extended = false) {
 	$x['to']             = encode_item_xchan($item['to']);
 
 	if($item['attach'])
-		$x['attach']     = json_decode_plus($item['attach']);
+		$x['attach']     = json_decode($item['attach'],true);
 
 	$x['flags'] = array();
 
@@ -2390,7 +2389,7 @@ function tag_deliver($uid, $item_id) {
 		if(($item['obj_type'] == "") || ($item['obj_type'] !== ACTIVITY_OBJ_PERSON) || (! $item['obj']))
 			$poke_notify = false;
 
-		$obj = json_decode_plus($item['obj']);
+		$obj = json_decode($item['obj'],true);
 		if($obj) {
 			if($obj['id'] !== $u[0]['channel_hash'])
 				$poke_notify = false;
@@ -2427,14 +2426,14 @@ function tag_deliver($uid, $item_id) {
 
 		if(($item['owner_xchan'] === $u[0]['channel_hash']) && (! get_pconfig($u[0]['channel_id'],'system','blocktags'))) {
 			logger('tag_deliver: community tag recipient: ' . $u[0]['channel_name']);
-			$j_tgt = json_decode_plus($item['target']);
+			$j_tgt = json_decode($item['target'],true);
 			if($j_tgt && $j_tgt['id']) {
 				$p = q("select * from item where mid = '%s' and uid = %d limit 1",
 					dbesc($j_tgt['id']),
 					intval($u[0]['channel_id'])
 				);
 				if($p) {
-					$j_obj = json_decode_plus($item['obj']);
+					$j_obj = json_decode($item['obj'],true);
 					logger('tag_deliver: tag object: ' . print_r($j_obj,true), LOGGER_DATA);
 					if($j_obj && $j_obj['id'] && $j_obj['title']) {
 						if(is_array($j_obj['link']))
@@ -2519,7 +2518,7 @@ function tag_deliver($uid, $item_id) {
 		if(intval($item['item_obscured'])) {
 			$key = get_config('system','prvkey');
 			if($item['body'])
-				$body = crypto_unencapsulate(json_decode_plus($item['body']),$key);
+				$body = crypto_unencapsulate(json_decode($item['body'],true),$key);
 		}
 		else
 			$body = $item['body'];
@@ -2762,7 +2761,7 @@ function start_delivery_chain($channel, $item, $item_id, $parent) {
 	$private = (($channel['channel_allow_cid'] || $channel['channel_allow_gid']
 		|| $channel['channel_deny_cid'] || $channel['channel_deny_gid']) ? 1 : 0);
 
-	$new_public_policy = map_scope($channel['channel_r_stream'],true);
+	$new_public_policy = map_scope(\Zotlabs\Access\PermissionLimits::Get($channel['channel_id'],'view_stream'),true);
 
 	if((! $private) && $new_public_policy)
 		$private = 1;
@@ -2807,7 +2806,7 @@ function start_delivery_chain($channel, $item, $item_id, $parent) {
 		dbesc($channel['channel_deny_gid']),
 		intval($private),
 		dbesc($new_public_policy),
-		dbesc(map_scope($channel['channel_w_comment'])),
+		dbesc(map_scope(\Zotlabs\Access\PermissionLimits::Get($channel['channel_id'],'post_comments'))),
 		dbesc($title),
 		dbesc($body),
 		intval($item_wall),
@@ -2856,7 +2855,7 @@ function check_item_source($uid, $item) {
 	if(! $x)
 		return false;
 
-	if(! ($x[0]['abook_their_perms'] & PERMS_A_REPUBLISH))
+	if(! get_abconfig($uid,$item['owner_xchan'],'their_perms','republish'))
 		return false;
 
 	if($item['item_private'] && (! intval($x[0]['abook_feed'])))
diff --git a/include/oauth.php b/include/oauth.php
index 984e0e6c6..a3c52bf27 100644
--- a/include/oauth.php
+++ b/include/oauth.php
@@ -170,7 +170,7 @@ class ZotOAuth1 extends OAuth1Server {
 		);
 		if($x) {
 			require_once('include/security.php');
-			authenticate_success($x[0],true,false,true,true);
+			authenticate_success($x[0],null,true,false,true,true);
 			$_SESSION['allow_api'] = true;
 		}
 	}
diff --git a/include/oembed.php b/include/oembed.php
index fe068278e..fe3a3c33c 100755
--- a/include/oembed.php
+++ b/include/oembed.php
@@ -215,6 +215,17 @@ function oembed_fetch_url($embedurl){
 			if($j->html != $orig) {
 				logger('oembed html was purified. original: ' . $orig . ' purified: ' . $j->html, LOGGER_DEBUG, LOG_INFO); 
 			}
+
+			$orig_len = trim(mb_strlen($orig));
+			$new_len = trim(mb_strlen($j->html));
+			if(! $new_len)
+				$j->type = 'error';
+			elseif($orig_len) {
+				$ratio = $new_len / $orig_len;
+				if($ratio < 0.8)
+					$j->type = 'error';
+			}
+
 		}
 	}
 
diff --git a/include/perm_upgrade.php b/include/perm_upgrade.php
new file mode 100644
index 000000000..5be1ffbb2
--- /dev/null
+++ b/include/perm_upgrade.php
@@ -0,0 +1,236 @@
+ $v) {
+				set_pconfig($channel['channel_id'],'autoperms',$k,$v);
+			}
+		}
+	}
+}
+
+
+function perm_abook_upgrade($abook) {
+
+	$x = perms_int_to_array($abook['abook_their_perms']);
+	if($x) {
+		foreach($x as $k => $v) {
+			set_abconfig($abook['abook_channel'],$abook['abook_xchan'],'their_perms',$k, $v);
+		}
+	}
+
+	$x = perms_int_to_array($abook['abook_my_perms']);
+	if($x) {
+		foreach($x as $k => $v) {
+			set_abconfig($abook['abook_channel'],$abook['abook_xchan'],'my_perms',$k, $v);
+		}
+	}
+}
+
+function translate_channel_perms_outbound(&$channel) {
+	$r = q("select * from pconfig where uid = %d and cat = 'perm_limits' ",
+		intval($channel['channel_id'])
+	);
+
+	if($r) {
+		foreach($r as $rr) {
+			if($rr['k'] === 'view_stream')
+				$channel['channel_r_stream'] = $rr['v'];
+			if($rr['k'] === 'view_profile')
+				$channel['channel_r_profile'] = $rr['v'];
+			if($rr['k'] === 'view_contacts')
+				$channel['channel_r_abook'] = $rr['v'];
+			if($rr['k'] === 'view_storage')
+				$channel['channel_r_storage'] = $rr['v'];
+			if($rr['k'] === 'view_pages')
+				$channel['channel_r_pages'] = $rr['v'];
+			if($rr['k'] === 'send_stream')
+				$channel['channel_w_stream'] = $rr['v'];
+			if($rr['k'] === 'post_wall')
+				$channel['channel_w_wall'] = $rr['v'];
+			if($rr['k'] === 'post_comments')
+				$channel['channel_w_comment'] = $rr['v'];
+			if($rr['k'] === 'post_mail')
+				$channel['channel_w_mail'] = $rr['v'];
+			if($rr['k'] === 'post_like')
+				$channel['channel_w_like'] = $rr['v'];
+			if($rr['k'] === 'tag_deliver')
+				$channel['channel_w_tagwall'] = $rr['v'];
+			if($rr['k'] === 'chat')
+				$channel['channel_w_chat'] = $rr['v'];
+			if($rr['k'] === 'write_storage')
+				$channel['channel_w_storage'] = $rr['v'];
+			if($rr['k'] === 'write_pages')
+				$channel['channel_w_pages'] = $rr['v'];
+			if($rr['k'] === 'republish')
+				$channel['channel_a_republish'] = $rr['v'];
+			if($rr['k'] === 'delegate')
+				$channel['channel_a_delegate'] = $rr['v'];
+
+		}
+		$channel['perm_limits'] = $r;
+	}
+}
+
+function translate_channel_perms_inbound($channel) {
+	
+	if($channel['perm_limits']) {
+		foreach($channel['perm_limits'] as $p) {
+			set_pconfig($channel['channel_id'],'perm_limits',$p['k'],$p['v']);
+		}
+	}
+	else {
+		perm_limits_upgrade($channel);
+	}
+
+}
+
+function translate_abook_perms_outbound(&$abook) {
+	$my_perms = 0;
+	$their_perms = 0;
+
+	if(array_key_exists('abconfig',$abook) && is_array($abook['abconfig']) && $abook['abconfig']) {
+		foreach($abook['abconfig'] as $p) {
+			if($p['cat'] === 'their_perms') {
+				if($p['k'] === 'view_stream' && intval($p['v']))
+					$their_perms += PERMS_R_STREAM; 
+				if($p['k'] === 'view_profile' && intval($p['v']))
+					$their_perms += PERMS_R_PROFILE;
+				if($p['k'] === 'view_contacts' && intval($p['v']))
+					$their_perms += PERMS_R_ABOOK; 
+				if($p['k'] === 'view_storage' && intval($p['v']))
+					$their_perms += PERMS_R_STORAGE; 
+				if($p['k'] === 'view_pages' && intval($p['v']))
+					$their_perms += PERMS_R_PAGES; 
+				if($p['k'] === 'send_stream' && intval($p['v']))
+					$their_perms += PERMS_W_STREAM; 
+				if($p['k'] === 'post_wall' && intval($p['v']))
+					$their_perms += PERMS_W_WALL; 
+				if($p['k'] === 'post_comments' && intval($p['v']))
+					$their_perms += PERMS_W_COMMENT; 
+				if($p['k'] === 'post_mail' && intval($p['v']))
+					$their_perms += PERMS_W_MAIL; 
+				if($p['k'] === 'post_like' && intval($p['v']))
+					$their_perms += PERMS_W_LIKE; 
+				if($p['k'] === 'tag_deliver' && intval($p['v']))
+					$their_perms += PERMS_W_TAGWALL; 
+				if($p['k'] === 'chat' && intval($p['v']))
+					$their_perms += PERMS_W_CHAT; 
+				if($p['k'] === 'write_storage' && intval($p['v']))
+					$their_perms += PERMS_W_STORAGE; 
+				if($p['k'] === 'write_pages' && intval($p['v']))
+					$their_perms += PERMS_W_PAGES; 
+				if($p['k'] === 'republish' && intval($p['v']))
+					$their_perms += PERMS_A_REPUBLISH; 
+				if($p['k'] === 'delegate' && intval($p['v']))
+					$their_perms += PERMS_A_DELEGATE; 
+			}
+			if($p['cat'] === 'my_perms') {
+				if($p['k'] === 'view_stream' && intval($p['v']))
+					$my_perms += PERMS_R_STREAM; 
+				if($p['k'] === 'view_profile' && intval($p['v']))
+					$my_perms += PERMS_R_PROFILE;
+				if($p['k'] === 'view_contacts' && intval($p['v']))
+					$my_perms += PERMS_R_ABOOK; 
+				if($p['k'] === 'view_storage' && intval($p['v']))
+					$my_perms += PERMS_R_STORAGE; 
+				if($p['k'] === 'view_pages' && intval($p['v']))
+					$my_perms += PERMS_R_PAGES; 
+				if($p['k'] === 'send_stream' && intval($p['v']))
+					$my_perms += PERMS_W_STREAM; 
+				if($p['k'] === 'post_wall' && intval($p['v']))
+					$my_perms += PERMS_W_WALL; 
+				if($p['k'] === 'post_comments' && intval($p['v']))
+					$my_perms += PERMS_W_COMMENT; 
+				if($p['k'] === 'post_mail' && intval($p['v']))
+					$my_perms += PERMS_W_MAIL; 
+				if($p['k'] === 'post_like' && intval($p['v']))
+					$my_perms += PERMS_W_LIKE; 
+				if($p['k'] === 'tag_deliver' && intval($p['v']))
+					$my_perms += PERMS_W_TAGWALL; 
+				if($p['k'] === 'chat' && intval($p['v']))
+					$my_perms += PERMS_W_CHAT; 
+				if($p['k'] === 'write_storage' && intval($p['v']))
+					$my_perms += PERMS_W_STORAGE; 
+				if($p['k'] === 'write_pages' && intval($p['v']))
+					$my_perms += PERMS_W_PAGES; 
+				if($p['k'] === 'republish' && intval($p['v']))
+					$my_perms += PERMS_A_REPUBLISH; 
+				if($p['k'] === 'delegate' && intval($p['v']))
+					$my_perms += PERMS_A_DELEGATE; 
+			}
+		}
+
+		$abook['abook_their_perms'] = $their_perms;
+		$abook['abook_my_perms'] = $my_perms;
+	}
+}
+
+function translate_abook_perms_inbound($channel,$abook) {
+
+	$new_perms = false;
+	$abook['abook_channel'] = $channel['channel_id'];
+
+	if(array_key_exists('abconfig',$abook) && is_array($abook['abconfig']) && $abook['abconfig']) {
+			foreach($abook['abconfig'] as $p) {
+			if($p['cat'] == 'their_perms' || $p['cat'] == 'my_perms') {
+				$new_perms = true; 
+				break;
+			}
+		}
+	}
+
+	if($new_perms == false) {
+		perm_abook_upgrade($abook);
+	}
+
+}
+
+
+
diff --git a/include/permissions.php b/include/permissions.php
index 19242d29f..638bedb24 100644
--- a/include/permissions.php
+++ b/include/permissions.php
@@ -67,7 +67,7 @@ function get_all_perms($uid, $observer_xchan, $internal_use = true) {
 	if($api)
 		return get_all_api_perms($uid,$api);	
 
-	$global_perms = get_perms();
+	$global_perms = \Zotlabs\Access\Permissions::Perms();
 
 	// Save lots of individual lookups
 
@@ -81,11 +81,13 @@ function get_all_perms($uid, $observer_xchan, $internal_use = true) {
 
 	$ret = array();
 
+	$abperms = (($uid && $observer_xchan) ? load_abconfig($uid,$observer_xchan,'my_perms') : array());
+
 	foreach($global_perms as $perm_name => $permission) {
 
 		// First find out what the channel owner declared permissions to be.
 
-		$channel_perm = $permission[0];
+		$channel_perm = \Zotlabs\Access\PermissionLimits::Get($uid,$perm_name);
 
 		if(! $channel_checked) {
 			$r = q("select * from channel where channel_id = %d limit 1",
@@ -105,7 +107,7 @@ function get_all_perms($uid, $observer_xchan, $internal_use = true) {
 		// These take priority over all other settings.
 
 		if($observer_xchan) {
-			if($r[0][$channel_perm] & PERMS_AUTHED) {
+			if($channel_perm & PERMS_AUTHED) {
 				$ret[$perm_name] = true;
 				continue;
 			}
@@ -136,7 +138,10 @@ function get_all_perms($uid, $observer_xchan, $internal_use = true) {
 			// Check if this is a write permission and they are being ignored
 			// This flag is only visible internally.
 
-			if(($x) && ($internal_use) && (! $global_perms[$perm_name][2]) && intval($x[0]['abook_ignored'])) {
+			$blocked_anon_perms = \Zotlabs\Access\Permissions::BlockedAnonPerms();
+
+
+			if(($x) && ($internal_use) && in_array($perm_name,$blocked_anon_perms) && intval($x[0]['abook_ignored'])) {
 				$ret[$perm_name] = false;
 				continue;
 			}
@@ -154,7 +159,7 @@ function get_all_perms($uid, $observer_xchan, $internal_use = true) {
 		// if you've moved elsewhere, you will only have read only access
 
 		if(($observer_xchan) && ($r[0]['channel_hash'] === $observer_xchan)) {
-			if($r[0]['channel_moved'] && (! $permission[2]))
+			if($r[0]['channel_moved'] && (in_array($perm_name,$blocked_anon_perms)))
 				$ret[$perm_name] = false;
 			else
 				$ret[$perm_name] = true;
@@ -163,7 +168,7 @@ function get_all_perms($uid, $observer_xchan, $internal_use = true) {
 
 		// Anybody at all (that wasn't blocked or ignored). They have permission.
 
-		if($r[0][$channel_perm] & PERMS_PUBLIC) {
+		if($channel_perm & PERMS_PUBLIC) {
 			$ret[$perm_name] = true;
 			continue;
 		}
@@ -178,7 +183,7 @@ function get_all_perms($uid, $observer_xchan, $internal_use = true) {
 
 		// If we're still here, we have an observer, check the network.
 
-		if($r[0][$channel_perm] & PERMS_NETWORK) {
+		if($channel_perm & PERMS_NETWORK) {
 			if(($x && $x[0]['xchan_network'] === 'zot') || ($y && $y[0]['xchan_network'] === 'zot')) {
 				$ret[$perm_name] = true;
 				continue;
@@ -187,7 +192,7 @@ function get_all_perms($uid, $observer_xchan, $internal_use = true) {
 
 		// If PERMS_SITE is specified, find out if they've got an account on this hub
 
-		if($r[0][$channel_perm] & PERMS_SITE) {
+		if($channel_perm & PERMS_SITE) {
 			if(! $onsite_checked) {
 				$c = q("select channel_hash from channel where channel_hash = '%s' limit 1",
 					dbesc($observer_xchan)
@@ -214,7 +219,7 @@ function get_all_perms($uid, $observer_xchan, $internal_use = true) {
 
 		// They are in your address book, but haven't been approved
 
-		if($r[0][$channel_perm] & PERMS_PENDING) {
+		if($channel_perm & PERMS_PENDING) {
 			$ret[$perm_name] = true;
 			continue;
 		}
@@ -226,16 +231,21 @@ function get_all_perms($uid, $observer_xchan, $internal_use = true) {
 
 		// They're a contact, so they have permission
 
-		if($r[0][$channel_perm] & PERMS_CONTACTS) {
+		if($channel_perm & PERMS_CONTACTS) {
 			$ret[$perm_name] = true;
 			continue;
 		}
 
 		// Permission granted to certain channels. Let's see if the observer is one of them
 
-		if($r[0][$channel_perm] & PERMS_SPECIFIC) {
-			if(($x[0]['abook_my_perms'] & $global_perms[$perm_name][1])) {
-				$ret[$perm_name] = true;
+		if($channel_perm & PERMS_SPECIFIC) {
+			if($abperms) {
+				foreach($abperms as $ab) {
+					if(($ab['cat'] == 'my_perms') && ($ab['k'] == $perm_name)) {
+						$ret[$perm_name] = (intval($ab['v']) ? true : false);
+						break;
+					}
+				}
 				continue;
 			}
 		}
@@ -284,21 +294,23 @@ function perm_is_allowed($uid, $observer_xchan, $permission) {
 	if($arr['result'])
 		return true;
 
-	$global_perms = get_perms();
+	$global_perms = \Zotlabs\Access\Permissions::Perms();
 
 	// First find out what the channel owner declared permissions to be.
 
-	$channel_perm = $global_perms[$permission][0];
+	$channel_perm = \Zotlabs\Access\PermissionLimits::Get($uid,$permission);
 
-	$r = q("select %s, channel_pageflags, channel_moved, channel_hash from channel where channel_id = %d limit 1",
-		dbesc($channel_perm),
+	$r = q("select channel_pageflags, channel_moved, channel_hash from channel where channel_id = %d limit 1",
 		intval($uid)
 	);
 	if(! $r)
 		return false;
 
+
+	$blocked_anon_perms = \Zotlabs\Access\Permissions::BlockedAnonPerms();
+
 	if($observer_xchan) {
-		if($r[0][$channel_perm] & PERMS_AUTHED)
+		if($channel_perm & PERMS_AUTHED)
 			return true;
 
 		$x = q("select abook_my_perms, abook_blocked, abook_ignored, abook_pending, xchan_network from abook left join xchan on abook_xchan = xchan_hash 
@@ -312,7 +324,7 @@ function perm_is_allowed($uid, $observer_xchan, $permission) {
 		if(($x) && intval($x[0]['abook_blocked']))
 			return false;
 
-		if(($x) && (! $global_perms[$permission][2]) && intval($x[0]['abook_ignored']))
+		if(($x) && in_array($permission,$blocked_anon_perms) && intval($x[0]['abook_ignored']))
 			return false;
 
 		if(! $x) {
@@ -321,7 +333,9 @@ function perm_is_allowed($uid, $observer_xchan, $permission) {
 				dbesc($observer_xchan)
 			);
 		}
+		$abperms = load_abconfig($uid,$observer_xchan,'my_perms');
 	}
+	
 
 	// system is blocked to anybody who is not authenticated
 
@@ -333,13 +347,13 @@ function perm_is_allowed($uid, $observer_xchan, $permission) {
 	// in which case you will have read_only access
 
 	if($r[0]['channel_hash'] === $observer_xchan) {
-		if($r[0]['channel_moved'] && (! $global_perms[$permission][2]))
+		if($r[0]['channel_moved'] && (in_array($permission,$blocked_anon_perms)))
 			return false;
 		else
 			return true;
 	}
 
-	if($r[0][$channel_perm] & PERMS_PUBLIC)
+	if($channel_perm & PERMS_PUBLIC)
 		return true;
 
 	// If it's an unauthenticated observer, we only need to see if PERMS_PUBLIC is set
@@ -350,14 +364,14 @@ function perm_is_allowed($uid, $observer_xchan, $permission) {
 
 	// If we're still here, we have an observer, check the network.
 
-	if($r[0][$channel_perm] & PERMS_NETWORK) {
+	if($channel_perm & PERMS_NETWORK) {
 		if (($x && $x[0]['xchan_network'] === 'zot') || ($y && $y[0]['xchan_network'] === 'zot'))
 			return true;
 	}
 
 	// If PERMS_SITE is specified, find out if they've got an account on this hub
 
-	if($r[0][$channel_perm] & PERMS_SITE) {
+	if($channel_perm & PERMS_SITE) {
 		$c = q("select channel_hash from channel where channel_hash = '%s' limit 1",
 			dbesc($observer_xchan)
 		);
@@ -376,7 +390,7 @@ function perm_is_allowed($uid, $observer_xchan, $permission) {
 
 	// They are in your address book, but haven't been approved
 
-	if($r[0][$channel_perm] & PERMS_PENDING) {
+	if($channel_perm & PERMS_PENDING) {
 		return true;
 	}
 
@@ -386,15 +400,20 @@ function perm_is_allowed($uid, $observer_xchan, $permission) {
 
 	// They're a contact, so they have permission
 
-	if($r[0][$channel_perm] & PERMS_CONTACTS) {
+	if($channel_perm & PERMS_CONTACTS) {
 		return true;
 	}
 
 	// Permission granted to certain channels. Let's see if the observer is one of them
 
-	if(($r) && $r[0][$channel_perm] & PERMS_SPECIFIC) {
-		if($x[0]['abook_my_perms'] & $global_perms[$permission][1])
-			return true;
+	if(($r) && ($channel_perm & PERMS_SPECIFIC)) {
+		if($abperms) {
+			foreach($abperms as $ab) {
+				if($ab['cat'] == 'my_perms' && $ab['k'] == $permission) {
+					return ((intval($ab['v'])) ? true : false);
+				}
+			}
+		}
 	}
 
 	// No permissions allowed.
@@ -560,28 +579,28 @@ function get_role_perms($role) {
 			$ret['default_collection'] = false;
 			$ret['directory_publish'] = true;
 			$ret['online'] = true;
-			$ret['perms_follow'] = PERMS_R_STREAM|PERMS_R_PROFILE|PERMS_R_ABOOK
-				|PERMS_W_STREAM|PERMS_W_WALL|PERMS_W_COMMENT|PERMS_W_MAIL|PERMS_W_CHAT
-				|PERMS_R_STORAGE|PERMS_R_PAGES|PERMS_A_REPUBLISH|PERMS_W_LIKE;
-			$ret['perms_accept'] = PERMS_R_STREAM|PERMS_R_PROFILE|PERMS_R_ABOOK
-				|PERMS_W_STREAM|PERMS_W_WALL|PERMS_W_COMMENT|PERMS_W_MAIL|PERMS_W_CHAT
-				|PERMS_R_STORAGE|PERMS_R_PAGES|PERMS_A_REPUBLISH|PERMS_W_LIKE;
-			$ret['channel_r_stream']    = PERMS_PUBLIC;
-			$ret['channel_r_profile']   = PERMS_PUBLIC;
-			$ret['channel_r_abook']     = PERMS_PUBLIC;
-			$ret['channel_w_stream']    = PERMS_SPECIFIC;
-			$ret['channel_w_wall']      = PERMS_SPECIFIC;
-			$ret['channel_w_tagwall']   = PERMS_SPECIFIC;
-			$ret['channel_w_comment']   = PERMS_SPECIFIC;
-			$ret['channel_w_mail']      = PERMS_SPECIFIC;
-			$ret['channel_w_chat']      = PERMS_SPECIFIC;
-			$ret['channel_a_delegate']  = PERMS_SPECIFIC;
-			$ret['channel_r_storage']   = PERMS_PUBLIC;
-			$ret['channel_w_storage']   = PERMS_SPECIFIC;
-			$ret['channel_r_pages']     = PERMS_PUBLIC;
-			$ret['channel_w_pages']     = PERMS_SPECIFIC;
-			$ret['channel_a_republish'] = PERMS_SPECIFIC;
-			$ret['channel_w_like']      = PERMS_NETWORK;
+			$ret['perms_connect'] = [ 
+				'view_stream', 'view_profile', 'view_contacts', 'view_storage',
+				'view_pages', 'send_stream', 'post_wall', 'post_comments', 
+				'post_mail', 'chat', 'post_like', 'republish' ];
+			$ret['limits'] = [
+				'view_stream'    => PERMS_PUBLIC,
+				'view_profile'   => PERMS_PUBLIC,
+				'view_contacts'  => PERMS_PUBLIC,
+				'view_storage'   => PERMS_PUBLIC,
+				'view_pages'     => PERMS_PUBLIC,
+				'send_stream'    => PERMS_SPECIFIC,
+				'post_wall'      => PERMS_SPECIFIC,
+				'post_comments'  => PERMS_SPECIFIC,
+				'post_mail'      => PERMS_SPECIFIC,
+				'post_like'      => PERMS_SPECIFIC,
+				'tag_deliver'    => PERMS_SPECIFIC,
+				'chat'           => PERMS_SPECIFIC,
+				'write_storage'  => PERMS_SPECIFIC,
+				'write_pages'    => PERMS_SPECIFIC,
+				'republish'      => PERMS_SPECIFIC,
+				'delegate'       => PERMS_SPECIFIC
+			];
 
 			break;
 
@@ -590,28 +609,29 @@ function get_role_perms($role) {
 			$ret['default_collection'] = true;
 			$ret['directory_publish'] = true;
 			$ret['online'] = true;
-			$ret['perms_follow'] = PERMS_R_STREAM|PERMS_R_PROFILE|PERMS_R_ABOOK
-				|PERMS_W_STREAM|PERMS_W_WALL|PERMS_W_COMMENT|PERMS_W_MAIL|PERMS_W_CHAT
-				|PERMS_R_STORAGE|PERMS_R_PAGES|PERMS_W_LIKE;
-			$ret['perms_accept'] = PERMS_R_STREAM|PERMS_R_PROFILE|PERMS_R_ABOOK
-				|PERMS_W_STREAM|PERMS_W_WALL|PERMS_W_COMMENT|PERMS_W_MAIL|PERMS_W_CHAT
-				|PERMS_R_STORAGE|PERMS_R_PAGES|PERMS_W_LIKE;
-			$ret['channel_r_stream']    = PERMS_PUBLIC;
-			$ret['channel_r_profile']   = PERMS_PUBLIC;
-			$ret['channel_r_abook']     = PERMS_PUBLIC;
-			$ret['channel_w_stream']    = PERMS_SPECIFIC;
-			$ret['channel_w_wall']      = PERMS_SPECIFIC;
-			$ret['channel_w_tagwall']   = PERMS_SPECIFIC;
-			$ret['channel_w_comment']   = PERMS_SPECIFIC;
-			$ret['channel_w_mail']      = PERMS_SPECIFIC;
-			$ret['channel_w_chat']      = PERMS_SPECIFIC;
-			$ret['channel_a_delegate']  = PERMS_SPECIFIC;
-			$ret['channel_r_storage']   = PERMS_PUBLIC;
-			$ret['channel_w_storage']   = PERMS_SPECIFIC;
-			$ret['channel_r_pages']     = PERMS_PUBLIC;
-			$ret['channel_w_pages']     = PERMS_SPECIFIC;
-			$ret['channel_a_republish'] = PERMS_SPECIFIC;
-			$ret['channel_w_like']      = PERMS_SPECIFIC;
+			$ret['perms_connect'] = [ 
+				'view_stream', 'view_profile', 'view_contacts', 'view_storage',
+				'view_pages', 'send_stream', 'post_wall', 'post_comments', 
+				'post_mail', 'chat', 'post_like' ];
+			$ret['limits'] = [
+				'view_stream'    => PERMS_PUBLIC,
+				'view_profile'   => PERMS_PUBLIC,
+				'view_contacts'  => PERMS_PUBLIC,
+				'view_storage'   => PERMS_PUBLIC,
+				'view_pages'     => PERMS_PUBLIC,
+				'send_stream'    => PERMS_SPECIFIC,
+				'post_wall'      => PERMS_SPECIFIC,
+				'post_comments'  => PERMS_SPECIFIC,
+				'post_mail'      => PERMS_SPECIFIC,
+				'post_like'      => PERMS_SPECIFIC,
+				'tag_deliver'    => PERMS_SPECIFIC,
+				'chat'           => PERMS_SPECIFIC,
+				'write_storage'  => PERMS_SPECIFIC,
+				'write_pages'    => PERMS_SPECIFIC,
+				'republish'      => PERMS_SPECIFIC,
+				'delegate'       => PERMS_SPECIFIC
+			];
+
 
 			break;
 
@@ -620,28 +640,28 @@ function get_role_perms($role) {
 			$ret['default_collection'] = true;
 			$ret['directory_publish'] = false;
 			$ret['online'] = false;
-			$ret['perms_follow'] = PERMS_R_STREAM|PERMS_R_PROFILE|PERMS_R_ABOOK
-				|PERMS_W_STREAM|PERMS_W_WALL|PERMS_W_COMMENT|PERMS_W_MAIL|PERMS_W_CHAT
-				|PERMS_R_STORAGE|PERMS_R_PAGES|PERMS_W_LIKE;
-			$ret['perms_accept'] = PERMS_R_STREAM|PERMS_R_PROFILE|PERMS_R_ABOOK
-				|PERMS_W_STREAM|PERMS_W_WALL|PERMS_W_COMMENT|PERMS_W_MAIL|PERMS_W_CHAT
-				|PERMS_R_STORAGE|PERMS_R_PAGES|PERMS_W_LIKE;
-			$ret['channel_r_stream']    = PERMS_PUBLIC;
-			$ret['channel_r_profile']   = PERMS_PUBLIC;
-			$ret['channel_r_abook']     = PERMS_SPECIFIC;
-			$ret['channel_w_stream']    = PERMS_SPECIFIC;
-			$ret['channel_w_wall']      = PERMS_SPECIFIC;
-			$ret['channel_w_tagwall']   = PERMS_SPECIFIC;
-			$ret['channel_w_comment']   = PERMS_SPECIFIC;
-			$ret['channel_w_mail']      = PERMS_SPECIFIC;
-			$ret['channel_w_chat']      = PERMS_SPECIFIC;
-			$ret['channel_a_delegate']  = PERMS_SPECIFIC;
-			$ret['channel_r_storage']   = PERMS_SPECIFIC;
-			$ret['channel_w_storage']   = PERMS_SPECIFIC;
-			$ret['channel_r_pages']     = PERMS_PUBLIC;
-			$ret['channel_w_pages']     = PERMS_SPECIFIC;
-			$ret['channel_a_republish'] = PERMS_SPECIFIC;
-			$ret['channel_w_like']      = PERMS_SPECIFIC;
+			$ret['perms_connect'] = [ 
+				'view_stream', 'view_profile', 'view_contacts', 'view_storage',
+				'view_pages', 'send_stream', 'post_wall', 'post_comments', 
+				'post_mail', 'post_like' ];
+			$ret['limits'] = [
+				'view_stream'    => PERMS_PUBLIC,
+				'view_profile'   => PERMS_PUBLIC,
+				'view_contacts'  => PERMS_SPECIFIC,
+				'view_storage'   => PERMS_SPECIFIC,
+				'view_pages'     => PERMS_PUBLIC,
+				'send_stream'    => PERMS_SPECIFIC,
+				'post_wall'      => PERMS_SPECIFIC,
+				'post_comments'  => PERMS_SPECIFIC,
+				'post_mail'      => PERMS_SPECIFIC,
+				'post_like'      => PERMS_SPECIFIC,
+				'tag_deliver'    => PERMS_SPECIFIC,
+				'chat'           => PERMS_SPECIFIC,
+				'write_storage'  => PERMS_SPECIFIC,
+				'write_pages'    => PERMS_SPECIFIC,
+				'republish'      => PERMS_SPECIFIC,
+				'delegate'       => PERMS_SPECIFIC
+			];
 
 			break;
 
@@ -650,28 +670,28 @@ function get_role_perms($role) {
 			$ret['default_collection'] = false;
 			$ret['directory_publish'] = true;
 			$ret['online'] = false;
-			$ret['perms_follow'] = PERMS_R_STREAM|PERMS_R_PROFILE|PERMS_R_ABOOK
-				|PERMS_W_WALL|PERMS_W_COMMENT|PERMS_W_MAIL|PERMS_W_CHAT
-				|PERMS_R_STORAGE|PERMS_R_PAGES|PERMS_A_REPUBLISH|PERMS_W_LIKE|PERMS_W_TAGWALL;
-			$ret['perms_accept'] = PERMS_R_STREAM|PERMS_R_PROFILE|PERMS_R_ABOOK
-				|PERMS_W_WALL|PERMS_W_COMMENT|PERMS_W_MAIL|PERMS_W_CHAT
-				|PERMS_R_STORAGE|PERMS_R_PAGES|PERMS_A_REPUBLISH|PERMS_W_LIKE|PERMS_W_TAGWALL;
-			$ret['channel_r_stream']    = PERMS_PUBLIC;
-			$ret['channel_r_profile']   = PERMS_PUBLIC;
-			$ret['channel_r_abook']     = PERMS_PUBLIC;
-			$ret['channel_w_stream']    = PERMS_SPECIFIC;
-			$ret['channel_w_wall']      = PERMS_SPECIFIC;
-			$ret['channel_w_tagwall']   = PERMS_SPECIFIC;
-			$ret['channel_w_comment']   = PERMS_SPECIFIC;
-			$ret['channel_w_mail']      = PERMS_SPECIFIC;
-			$ret['channel_w_chat']      = PERMS_SPECIFIC;
-			$ret['channel_a_delegate']  = PERMS_SPECIFIC;
-			$ret['channel_r_storage']   = PERMS_PUBLIC;
-			$ret['channel_w_storage']   = PERMS_SPECIFIC;
-			$ret['channel_r_pages']     = PERMS_PUBLIC;
-			$ret['channel_w_pages']     = PERMS_SPECIFIC;
-			$ret['channel_a_republish'] = PERMS_SPECIFIC;
-			$ret['channel_w_like']      = PERMS_NETWORK;
+			$ret['perms_connect'] = [ 
+				'view_stream', 'view_profile', 'view_contacts', 'view_storage',
+				'view_pages', 'post_wall', 'post_comments', 'tag_deliver',
+				'post_mail', 'post_like' , 'republish', 'chat' ];
+			$ret['limits'] = [
+				'view_stream'    => PERMS_PUBLIC,
+				'view_profile'   => PERMS_PUBLIC,
+				'view_contacts'  => PERMS_PUBLIC,
+				'view_storage'   => PERMS_PUBLIC,
+				'view_pages'     => PERMS_PUBLIC,
+				'send_stream'    => PERMS_SPECIFIC,
+				'post_wall'      => PERMS_SPECIFIC,
+				'post_comments'  => PERMS_SPECIFIC,
+				'post_mail'      => PERMS_SPECIFIC,
+				'post_like'      => PERMS_SPECIFIC,
+				'tag_deliver'    => PERMS_SPECIFIC,
+				'chat'           => PERMS_SPECIFIC,
+				'write_storage'  => PERMS_SPECIFIC,
+				'write_pages'    => PERMS_SPECIFIC,
+				'republish'      => PERMS_SPECIFIC,
+				'delegate'       => PERMS_SPECIFIC
+			];
 
 			break;
 
@@ -680,28 +700,28 @@ function get_role_perms($role) {
 			$ret['default_collection'] = true;
 			$ret['directory_publish'] = true;
 			$ret['online'] = false;
-			$ret['perms_follow'] = PERMS_R_STREAM|PERMS_R_PROFILE|PERMS_R_ABOOK
-				|PERMS_W_STREAM|PERMS_W_WALL|PERMS_W_COMMENT|PERMS_W_MAIL|PERMS_W_CHAT
-				|PERMS_R_STORAGE|PERMS_R_PAGES|PERMS_W_LIKE|PERMS_W_TAGWALL;
-			$ret['perms_accept'] = PERMS_R_STREAM|PERMS_R_PROFILE|PERMS_R_ABOOK
-				|PERMS_W_STREAM|PERMS_W_WALL|PERMS_W_COMMENT|PERMS_W_MAIL|PERMS_W_CHAT
-				|PERMS_R_STORAGE|PERMS_R_PAGES|PERMS_W_LIKE|PERMS_W_TAGWALL;
-			$ret['channel_r_stream']    = PERMS_PUBLIC;
-			$ret['channel_r_profile']   = PERMS_PUBLIC;
-			$ret['channel_r_abook']     = PERMS_PUBLIC;
-			$ret['channel_w_stream']    = PERMS_SPECIFIC;
-			$ret['channel_w_wall']      = PERMS_SPECIFIC;
-			$ret['channel_w_tagwall']   = PERMS_SPECIFIC;
-			$ret['channel_w_comment']   = PERMS_SPECIFIC;
-			$ret['channel_w_mail']      = PERMS_SPECIFIC;
-			$ret['channel_w_chat']      = PERMS_SPECIFIC;
-			$ret['channel_a_delegate']  = PERMS_SPECIFIC;
-			$ret['channel_r_storage']   = PERMS_PUBLIC;
-			$ret['channel_w_storage']   = PERMS_SPECIFIC;
-			$ret['channel_r_pages']     = PERMS_PUBLIC;
-			$ret['channel_w_pages']     = PERMS_SPECIFIC;
-			$ret['channel_a_republish'] = PERMS_SPECIFIC;
-			$ret['channel_w_like']      = PERMS_SPECIFIC;
+			$ret['perms_connect'] = [ 
+				'view_stream', 'view_profile', 'view_contacts', 'view_storage',
+				'view_pages', 'post_wall', 'post_comments', 'tag_deliver',
+				'post_mail', 'post_like' , 'chat' ];
+			$ret['limits'] = [
+				'view_stream'    => PERMS_PUBLIC,
+				'view_profile'   => PERMS_PUBLIC,
+				'view_contacts'  => PERMS_PUBLIC,
+				'view_storage'   => PERMS_PUBLIC,
+				'view_pages'     => PERMS_PUBLIC,
+				'send_stream'    => PERMS_SPECIFIC,
+				'post_wall'      => PERMS_SPECIFIC,
+				'post_comments'  => PERMS_SPECIFIC,
+				'post_mail'      => PERMS_SPECIFIC,
+				'post_like'      => PERMS_SPECIFIC,
+				'tag_deliver'    => PERMS_SPECIFIC,
+				'chat'           => PERMS_SPECIFIC,
+				'write_storage'  => PERMS_SPECIFIC,
+				'write_pages'    => PERMS_SPECIFIC,
+				'republish'      => PERMS_SPECIFIC,
+				'delegate'       => PERMS_SPECIFIC
+			];
 
 			break;
 
@@ -710,28 +730,29 @@ function get_role_perms($role) {
 			$ret['default_collection'] = true;
 			$ret['directory_publish'] = false;
 			$ret['online'] = false;
-			$ret['perms_follow'] = PERMS_R_STREAM|PERMS_R_PROFILE|PERMS_R_ABOOK
-				|PERMS_W_STREAM|PERMS_W_WALL|PERMS_W_COMMENT|PERMS_W_MAIL|PERMS_W_CHAT
-				|PERMS_R_STORAGE|PERMS_R_PAGES|PERMS_W_LIKE;
-			$ret['perms_accept'] = PERMS_R_STREAM|PERMS_R_PROFILEPERMS_R_ABOOK
-				|PERMS_W_STREAM|PERMS_W_WALL|PERMS_W_COMMENT|PERMS_W_MAIL|PERMS_W_CHAT
-				|PERMS_R_STORAGE|PERMS_R_PAGES|PERMS_W_LIKE;
-			$ret['channel_r_stream']    = PERMS_PUBLIC;
-			$ret['channel_r_profile']   = PERMS_SPECIFIC;
-			$ret['channel_r_abook']     = PERMS_SPECIFIC;
-			$ret['channel_w_stream']    = PERMS_SPECIFIC;
-			$ret['channel_w_wall']      = PERMS_SPECIFIC;
-			$ret['channel_w_tagwall']   = PERMS_SPECIFIC;
-			$ret['channel_w_comment']   = PERMS_SPECIFIC;
-			$ret['channel_w_mail']      = PERMS_SPECIFIC;
-			$ret['channel_w_chat']      = PERMS_SPECIFIC;
-			$ret['channel_a_delegate']  = PERMS_SPECIFIC;
-			$ret['channel_r_storage']   = PERMS_SPECIFIC;
-			$ret['channel_w_storage']   = PERMS_SPECIFIC;
-			$ret['channel_r_pages']     = PERMS_SPECIFIC;
-			$ret['channel_w_pages']     = PERMS_SPECIFIC;
-			$ret['channel_a_republish'] = PERMS_SPECIFIC;
-			$ret['channel_w_like']      = PERMS_SPECIFIC;
+
+			$ret['perms_connect'] = [ 
+				'view_stream', 'view_profile', 'view_contacts', 'view_storage',
+				'view_pages', 'post_wall', 'post_comments',
+				'post_mail', 'post_like' , 'chat' ];
+			$ret['limits'] = [
+				'view_stream'    => PERMS_PUBLIC,
+				'view_profile'   => PERMS_SPECIFIC,
+				'view_contacts'  => PERMS_SPECIFIC,
+				'view_storage'   => PERMS_SPECIFIC,
+				'view_pages'     => PERMS_SPECIFIC,
+				'send_stream'    => PERMS_SPECIFIC,
+				'post_wall'      => PERMS_SPECIFIC,
+				'post_comments'  => PERMS_SPECIFIC,
+				'post_mail'      => PERMS_SPECIFIC,
+				'post_like'      => PERMS_SPECIFIC,
+				'tag_deliver'    => PERMS_SPECIFIC,
+				'chat'           => PERMS_SPECIFIC,
+				'write_storage'  => PERMS_SPECIFIC,
+				'write_pages'    => PERMS_SPECIFIC,
+				'republish'      => PERMS_SPECIFIC,
+				'delegate'       => PERMS_SPECIFIC
+			];
 
 			break;
 
@@ -740,28 +761,29 @@ function get_role_perms($role) {
 			$ret['default_collection'] = false;
 			$ret['directory_publish'] = true;
 			$ret['online'] = false;
-			$ret['perms_follow'] = PERMS_R_STREAM|PERMS_R_PROFILE|PERMS_R_ABOOK
-				|PERMS_W_STREAM|PERMS_W_WALL|PERMS_W_COMMENT|PERMS_W_MAIL
-				|PERMS_R_STORAGE|PERMS_R_PAGES|PERMS_A_REPUBLISH|PERMS_W_LIKE;
-			$ret['perms_accept'] = PERMS_R_STREAM|PERMS_R_PROFILE|PERMS_R_ABOOK
-				|PERMS_W_STREAM|PERMS_W_WALL|PERMS_W_COMMENT|PERMS_W_MAIL
-				|PERMS_R_STORAGE|PERMS_R_PAGES|PERMS_A_REPUBLISH|PERMS_W_LIKE;
-			$ret['channel_r_stream']    = PERMS_PUBLIC;
-			$ret['channel_r_profile']   = PERMS_PUBLIC;
-			$ret['channel_r_abook']     = PERMS_PUBLIC;
-			$ret['channel_w_stream']    = PERMS_SPECIFIC;
-			$ret['channel_w_wall']      = PERMS_SPECIFIC;
-			$ret['channel_w_tagwall']   = PERMS_SPECIFIC;
-			$ret['channel_w_comment']   = PERMS_SPECIFIC;
-			$ret['channel_w_mail']      = PERMS_SPECIFIC;
-			$ret['channel_w_chat']      = PERMS_SPECIFIC;
-			$ret['channel_a_delegate']  = PERMS_SPECIFIC;
-			$ret['channel_r_storage']   = PERMS_PUBLIC;
-			$ret['channel_w_storage']   = PERMS_SPECIFIC;
-			$ret['channel_r_pages']     = PERMS_PUBLIC;
-			$ret['channel_w_pages']     = PERMS_SPECIFIC;
-			$ret['channel_a_republish'] = PERMS_NETWORK;
-			$ret['channel_w_like']      = PERMS_NETWORK;
+
+			$ret['perms_connect'] = [ 
+				'view_stream', 'view_profile', 'view_contacts', 'view_storage',
+				'view_pages', 'send_stream', 'post_wall', 'post_comments', 
+				'post_mail', 'post_like' , 'republish' ];
+			$ret['limits'] = [
+				'view_stream'    => PERMS_PUBLIC,
+				'view_profile'   => PERMS_PUBLIC,
+				'view_contacts'  => PERMS_PUBLIC,
+				'view_storage'   => PERMS_PUBLIC,
+				'view_pages'     => PERMS_PUBLIC,
+				'send_stream'    => PERMS_SPECIFIC,
+				'post_wall'      => PERMS_SPECIFIC,
+				'post_comments'  => PERMS_SPECIFIC,
+				'post_mail'      => PERMS_SPECIFIC,
+				'post_like'      => PERMS_SPECIFIC,
+				'tag_deliver'    => PERMS_SPECIFIC,
+				'chat'           => PERMS_SPECIFIC,
+				'write_storage'  => PERMS_SPECIFIC,
+				'write_pages'    => PERMS_SPECIFIC,
+				'republish'      => PERMS_SPECIFIC,
+				'delegate'       => PERMS_SPECIFIC
+			];
 
 			break;
 
@@ -770,28 +792,28 @@ function get_role_perms($role) {
 			$ret['default_collection'] = true;
 			$ret['directory_publish'] = false;
 			$ret['online'] = false;
-			$ret['perms_follow'] = PERMS_R_STREAM|PERMS_R_PROFILE|PERMS_R_ABOOK
-				|PERMS_W_STREAM|PERMS_W_WALL|PERMS_W_COMMENT|PERMS_W_MAIL
-				|PERMS_R_STORAGE|PERMS_R_PAGES|PERMS_W_LIKE;
-			$ret['perms_accept'] = PERMS_R_STREAM|PERMS_R_PROFILE|PERMS_R_ABOOK
-				|PERMS_W_STREAM|PERMS_W_WALL|PERMS_W_COMMENT|PERMS_W_MAIL
-				|PERMS_R_STORAGE|PERMS_R_PAGES|PERMS_W_LIKE;
-			$ret['channel_r_stream']    = PERMS_PUBLIC;
-			$ret['channel_r_profile']   = PERMS_PUBLIC;
-			$ret['channel_r_abook']     = PERMS_PUBLIC;
-			$ret['channel_w_stream']    = PERMS_SPECIFIC;
-			$ret['channel_w_wall']      = PERMS_SPECIFIC;
-			$ret['channel_w_tagwall']   = PERMS_SPECIFIC;
-			$ret['channel_w_comment']   = PERMS_SPECIFIC;
-			$ret['channel_w_mail']      = PERMS_SPECIFIC;
-			$ret['channel_w_chat']      = PERMS_SPECIFIC;
-			$ret['channel_a_delegate']  = PERMS_SPECIFIC;
-			$ret['channel_r_storage']   = PERMS_PUBLIC;
-			$ret['channel_w_storage']   = PERMS_SPECIFIC;
-			$ret['channel_r_pages']     = PERMS_PUBLIC;
-			$ret['channel_w_pages']     = PERMS_SPECIFIC;
-			$ret['channel_a_republish'] = PERMS_SPECIFIC;
-			$ret['channel_w_like']      = PERMS_NETWORK;
+			$ret['perms_connect'] = [ 
+				'view_stream', 'view_profile', 'view_contacts', 'view_storage',
+				'view_pages', 'send_stream', 'post_wall', 'post_comments', 
+				'post_mail', 'post_like' , 'republish' ];
+			$ret['limits'] = [
+				'view_stream'    => PERMS_PUBLIC,
+				'view_profile'   => PERMS_PUBLIC,
+				'view_contacts'  => PERMS_PUBLIC,
+				'view_storage'   => PERMS_PUBLIC,
+				'view_pages'     => PERMS_PUBLIC,
+				'send_stream'    => PERMS_SPECIFIC,
+				'post_wall'      => PERMS_SPECIFIC,
+				'post_comments'  => PERMS_SPECIFIC,
+				'post_mail'      => PERMS_SPECIFIC,
+				'post_like'      => PERMS_SPECIFIC,
+				'tag_deliver'    => PERMS_SPECIFIC,
+				'chat'           => PERMS_SPECIFIC,
+				'write_storage'  => PERMS_SPECIFIC,
+				'write_pages'    => PERMS_SPECIFIC,
+				'republish'      => PERMS_SPECIFIC,
+				'delegate'       => PERMS_SPECIFIC
+			];
 
 			break;
 
@@ -800,26 +822,29 @@ function get_role_perms($role) {
 			$ret['default_collection'] = false;
 			$ret['directory_publish'] = true;
 			$ret['online'] = false;
-			$ret['perms_follow'] = PERMS_R_STREAM|PERMS_R_PROFILE|PERMS_R_ABOOK
-				|PERMS_R_STORAGE|PERMS_R_PAGES|PERMS_A_REPUBLISH|PERMS_W_LIKE;
-			$ret['perms_accept'] = PERMS_R_STREAM|PERMS_R_PROFILE|PERMS_R_ABOOK
-				|PERMS_R_STORAGE|PERMS_R_PAGES|PERMS_A_REPUBLISH|PERMS_W_LIKE;
-			$ret['channel_r_stream']    = PERMS_PUBLIC;
-			$ret['channel_r_profile']   = PERMS_PUBLIC;
-			$ret['channel_r_abook']     = PERMS_PUBLIC;
-			$ret['channel_w_stream']    = PERMS_SPECIFIC;
-			$ret['channel_w_wall']      = PERMS_SPECIFIC;
-			$ret['channel_w_tagwall']   = PERMS_SPECIFIC;
-			$ret['channel_w_comment']   = PERMS_SPECIFIC;
-			$ret['channel_w_mail']      = PERMS_SPECIFIC;
-			$ret['channel_w_chat']      = PERMS_SPECIFIC;
-			$ret['channel_a_delegate']  = PERMS_SPECIFIC;
-			$ret['channel_r_storage']   = PERMS_PUBLIC;
-			$ret['channel_w_storage']   = PERMS_SPECIFIC;
-			$ret['channel_r_pages']     = PERMS_PUBLIC;
-			$ret['channel_w_pages']     = PERMS_SPECIFIC;
-			$ret['channel_a_republish'] = PERMS_SPECIFIC;
-			$ret['channel_w_like']      = PERMS_NETWORK;
+
+			$ret['perms_connect'] = [ 
+				'view_stream', 'view_profile', 'view_contacts', 'view_storage',
+				'view_pages', 'post_like' , 'republish' ];
+
+			$ret['limits'] = [
+				'view_stream'    => PERMS_PUBLIC,
+				'view_profile'   => PERMS_PUBLIC,
+				'view_contacts'  => PERMS_PUBLIC,
+				'view_storage'   => PERMS_PUBLIC,
+				'view_pages'     => PERMS_PUBLIC,
+				'send_stream'    => PERMS_SPECIFIC,
+				'post_wall'      => PERMS_SPECIFIC,
+				'post_comments'  => PERMS_SPECIFIC,
+				'post_mail'      => PERMS_SPECIFIC,
+				'post_like'      => PERMS_SPECIFIC,
+				'tag_deliver'    => PERMS_SPECIFIC,
+				'chat'           => PERMS_SPECIFIC,
+				'write_storage'  => PERMS_SPECIFIC,
+				'write_pages'    => PERMS_SPECIFIC,
+				'republish'      => PERMS_SPECIFIC,
+				'delegate'       => PERMS_SPECIFIC
+			];
 
 			break;
 
@@ -828,28 +853,30 @@ function get_role_perms($role) {
 			$ret['default_collection'] = false;
 			$ret['directory_publish'] = true;
 			$ret['online'] = false;
-			$ret['perms_follow'] = PERMS_R_STREAM|PERMS_R_PROFILE|PERMS_R_ABOOK
-				|PERMS_W_STREAM|PERMS_W_WALL|PERMS_W_COMMENT|PERMS_W_MAIL|PERMS_W_CHAT
-				|PERMS_R_STORAGE|PERMS_W_STORAGE|PERMS_R_PAGES|PERMS_A_REPUBLISH|PERMS_W_LIKE|PERMS_W_TAGWALL;
-			$ret['perms_accept'] = PERMS_R_STREAM|PERMS_R_PROFILE|PERMS_R_ABOOK
-				|PERMS_W_STREAM|PERMS_W_WALL|PERMS_W_COMMENT|PERMS_W_MAIL|PERMS_W_CHAT
-				|PERMS_R_STORAGE|PERMS_W_STORAGE|PERMS_R_PAGES|PERMS_A_REPUBLISH|PERMS_W_LIKE|PERMS_W_TAGWALL;
-			$ret['channel_r_stream']    = PERMS_PUBLIC;
-			$ret['channel_r_profile']   = PERMS_PUBLIC;
-			$ret['channel_r_abook']     = PERMS_PUBLIC;
-			$ret['channel_w_stream']    = PERMS_SPECIFIC;
-			$ret['channel_w_wall']      = PERMS_SPECIFIC;
-			$ret['channel_w_tagwall']   = PERMS_SPECIFIC;
-			$ret['channel_w_comment']   = PERMS_SPECIFIC;
-			$ret['channel_w_mail']      = PERMS_SPECIFIC;
-			$ret['channel_w_chat']      = PERMS_SPECIFIC;
-			$ret['channel_a_delegate']  = PERMS_SPECIFIC;
-			$ret['channel_r_storage']   = PERMS_PUBLIC;
-			$ret['channel_w_storage']   = PERMS_SPECIFIC;
-			$ret['channel_r_pages']     = PERMS_PUBLIC;
-			$ret['channel_w_pages']     = PERMS_SPECIFIC;
-			$ret['channel_a_republish'] = PERMS_SPECIFIC;
-			$ret['channel_w_like']      = PERMS_NETWORK;
+
+			$ret['perms_connect'] = [ 
+				'view_stream', 'view_profile', 'view_contacts', 'view_storage',
+				'view_pages', 'write_storage', 'write_pages', 'post_wall', 'post_comments', 'tag_deliver',
+				'post_mail', 'post_like' , 'republish', 'chat' ];
+			$ret['limits'] = [
+				'view_stream'    => PERMS_PUBLIC,
+				'view_profile'   => PERMS_PUBLIC,
+				'view_contacts'  => PERMS_PUBLIC,
+				'view_storage'   => PERMS_PUBLIC,
+				'view_pages'     => PERMS_PUBLIC,
+				'send_stream'    => PERMS_SPECIFIC,
+				'post_wall'      => PERMS_SPECIFIC,
+				'post_comments'  => PERMS_SPECIFIC,
+				'post_mail'      => PERMS_SPECIFIC,
+				'post_like'      => PERMS_SPECIFIC,
+				'tag_deliver'    => PERMS_SPECIFIC,
+				'chat'           => PERMS_SPECIFIC,
+				'write_storage'  => PERMS_SPECIFIC,
+				'write_pages'    => PERMS_SPECIFIC,
+				'republish'      => PERMS_SPECIFIC,
+				'delegate'       => PERMS_SPECIFIC
+			];
+
 
 			break;
 
diff --git a/include/photos.php b/include/photos.php
index c70478146..d14c12d84 100644
--- a/include/photos.php
+++ b/include/photos.php
@@ -412,7 +412,7 @@ function photo_upload($channel, $observer, $args) {
 		// in the photos pages - using the photos permissions instead. We need the public policy to keep the photo
 		// linked item from leaking into the feed when somebody has a channel with read_stream restrictions.  
 
-		$arr['public_policy']   = map_scope($channel['channel_r_stream'],true);
+		$arr['public_policy']   = map_scope(\Zotlabs\Access\PermissionLimits::Get($channel['channel_id'],'view_stream'),true);
 		if($arr['public_policy'])
 			$arr['item_private'] = 1;
 
diff --git a/include/security.php b/include/security.php
index e345636e7..c67a1b400 100644
--- a/include/security.php
+++ b/include/security.php
@@ -12,7 +12,7 @@
  * @param bool $return
  * @param bool $update_lastlog
  */
-function authenticate_success($user_record, $login_initial = false, $interactive = false, $return = false, $update_lastlog = false) {
+function authenticate_success($user_record, $channel = null, $login_initial = false, $interactive = false, $return = false, $update_lastlog = false) {
 
 	$_SESSION['addr'] = $_SERVER['REMOTE_ADDR'];
 
@@ -23,11 +23,15 @@ function authenticate_success($user_record, $login_initial = false, $interactive
 		$_SESSION['account_id'] = $user_record['account_id'];
 		$_SESSION['authenticated'] = 1;
 
+		if($channel)
+			$uid_to_load = $channel['channel_id'];
 
-		$uid_to_load = (((x($_SESSION,'uid')) && (intval($_SESSION['uid']))) 
-			? intval($_SESSION['uid']) 
-			: intval(App::$account['account_default_channel'])
-		);
+		if(! $uid_to_load) {
+			$uid_to_load = (((x($_SESSION,'uid')) && (intval($_SESSION['uid']))) 
+				? intval($_SESSION['uid']) 
+				: intval(App::$account['account_default_channel'])
+			);
+		}
 
 		if($uid_to_load) {
 			change_channel($uid_to_load);
@@ -85,16 +89,12 @@ function authenticate_success($user_record, $login_initial = false, $interactive
 function atoken_login($atoken) {
 	if(! $atoken)
 		return false;
-
-	$xchan = atoken_xchan($atoken);
-
 	$_SESSION['authenticated'] = 1;
-	$_SESSION['visitor_id'] = $xchan['xchan_hash'];
+	$_SESSION['visitor_id'] = $atoken['xchan_hash'];
 	$_SESSION['atoken'] = $atoken['atoken_id'];
 
-	\App::set_observer($xchan);
-
-	return [ 'atoken' => true ];
+	\App::set_observer($atoken);
+	return true;
 }
 
 
@@ -102,7 +102,8 @@ function atoken_xchan($atoken) {
 
 	$c = channelx_by_n($atoken['atoken_uid']);
 	if($c) {
-		return [ 
+		return [
+			'atoken_id' => $atoken['atoken_id'], 
 			'xchan_hash' =>  substr($c['channel_hash'],0,16) . '.' . $atoken['atoken_name'],
 			'xchan_name' => $atoken['atoken_name'],
 			'xchan_addr' => t('guest:') . $atoken['atoken_name'] . '@' . \App::get_hostname(),
@@ -115,7 +116,7 @@ function atoken_xchan($atoken) {
 	
 		];
 	}
-
+	return null;
 }
 
 
@@ -127,6 +128,7 @@ function atoken_xchan($atoken) {
  *
  * @return bool|array false or channel record of the new channel
  */
+
 function change_channel($change_channel) {
 
 	$ret = false;
@@ -476,14 +478,19 @@ function stream_perms_api_uids($perms = NULL, $limit = 0, $rand = 0 ) {
 	$random_sql = (($rand) ? " ORDER BY " . db_getfunc('RAND') . " " : '');
 	if(local_channel())
 		$ret[] = local_channel();
-	$r = q("select channel_id from channel where channel_r_stream > 0 and ( channel_r_stream & %d )>0 and ( channel_pageflags & %d ) = 0 and channel_system = 0 and channel_removed = 0 $random_sql $limit_sql ",
-		intval($perms),
-		intval(PAGE_ADULT|PAGE_CENSORED)
+	$x = q("select uid from pconfig where cat = 'perm_limits' and k = 'view_stream' and ( v & %d ) > 0 ",
+		intval($perms)
 	);
-	if($r) {
-		foreach($r as $rr)
-			if(! in_array($rr['channel_id'], $ret))
-				$ret[] = $rr['channel_id'];
+	if($x) {
+		$ids = ids_to_querystr($x,'uid');
+		$r = q("select channel_id from channel where channel_id in ( $ids ) and ( channel_pageflags & %d ) = 0 and channel_system = 0 and channel_removed = 0 $random_sql $limit_sql ",
+			intval(PAGE_ADULT|PAGE_CENSORED)
+		);
+		if($r) {
+			foreach($r as $rr)
+				if(! in_array($rr['channel_id'], $ret))
+					$ret[] = $rr['channel_id'];
+		}
 	}
 
 	$str = '';
@@ -509,16 +516,21 @@ function stream_perms_xchans($perms = NULL ) {
 	if(local_channel())
 		$ret[] = get_observer_hash();
 
-	$r = q("select channel_hash from channel where channel_r_stream > 0 and (channel_r_stream & %d)>0 and not (channel_pageflags & %d)>0 and channel_system = 0 and channel_removed = 0 ",
-		intval($perms),
-		intval(PAGE_ADULT|PAGE_CENSORED)
+	$x = q("select uid from pconfig where cat = 'perm_limits' and k = 'view_stream' and ( v & %d ) > 0 ",
+		intval($perms)
 	);
-	if($r) {
-		foreach($r as $rr)
-			if(! in_array($rr['channel_hash'], $ret))
-				$ret[] = $rr['channel_hash'];
-	}
+	if($x) {
+		$ids = ids_to_querystr($x,'uid');
+		$r = q("select channel_hash from channel where channel_id in ( $ids ) and ( channel_pageflags & %d ) = 0 and channel_system = 0 and channel_removed = 0 ",
+			intval(PAGE_ADULT|PAGE_CENSORED)
+		);
 
+		if($r) {
+			foreach($r as $rr)
+				if(! in_array($rr['channel_hash'], $ret))
+					$ret[] = $rr['channel_hash'];
+		}
+	}
 	$str = '';
 	if($ret) {
 		foreach($ret as $rr) {
diff --git a/include/text.php b/include/text.php
index d4d151f2e..dd7d9eaa8 100644
--- a/include/text.php
+++ b/include/text.php
@@ -1284,9 +1284,9 @@ function unobscure(&$item) {
 	if(array_key_exists('item_obscured',$item) && intval($item['item_obscured'])) {
 		$key = get_config('system','prvkey');
 		if($item['title'])
-			$item['title'] = crypto_unencapsulate(json_decode_plus($item['title']),$key);
+			$item['title'] = crypto_unencapsulate(json_decode($item['title'],true),$key);
 		if($item['body'])
-			$item['body'] = crypto_unencapsulate(json_decode_plus($item['body']),$key);
+			$item['body'] = crypto_unencapsulate(json_decode($item['body'],true),$key);
 		if(get_config('system','item_cache')) {
 			q("update item set title = '%s', body = '%s', item_obscured = 0 where id = %d",
 				dbesc($item['title']),
@@ -1309,7 +1309,7 @@ function unobscure_mail(&$item) {
 
 function theme_attachments(&$item) {
 
-	$arr = json_decode_plus($item['attach']);
+	$arr = json_decode($item['attach'],true);
 	if(is_array($arr) && count($arr)) {
 		$attaches = array();
 		foreach($arr as $r) {
@@ -2212,20 +2212,12 @@ function jindent($json) {
 	return $result;
 }
 
-
-function json_decode_plus($s) {
-	$x = json_decode($s,true);
-	if(! $x)
-		$x = json_decode(str_replace(array('\\"','\\\\'),array('"','\\'),$s),true);
-
-	return $x;
-}
-
 /**
  * @brief Creates navigation menu for webpage, layout, blocks, menu sites.
  *
  * @return string
  */
+
 function design_tools() {
 
 	$channel  = App::get_channel();
diff --git a/include/widgets.php b/include/widgets.php
index da73657f5..3516e82da 100644
--- a/include/widgets.php
+++ b/include/widgets.php
@@ -1356,9 +1356,14 @@ function widget_forums($arr) {
 
 	$perms_sql = item_permissions_sql(local_channel()) . item_normal();
 
-	$r1 = q("select abook_id, xchan_hash, xchan_name, xchan_url, xchan_photo_s from abook left join xchan on abook_xchan = xchan_hash where ( xchan_pubforum = 1 or ((abook_their_perms & %d ) != 0 and (abook_their_perms & %d ) = 0) ) and xchan_deleted = 0 and abook_channel = %d order by xchan_name $limit ",
-		intval(PERMS_W_TAGWALL),
-		intval(PERMS_W_STREAM),
+	/**
+	 * We used to try and find public forums with custom permissions by checking to see if
+	 * send_stream was false and tag_deliver was true. However with the newer extensible 
+	 * permissions infrastructure this makes for a very complicated query. Now we're only
+	 * checking channels that report themselves specifically as pubforums
+	 */
+
+	$r1 = q("select abook_id, xchan_hash, xchan_name, xchan_url, xchan_photo_s from abook left join xchan on abook_xchan = xchan_hash where xchan_pubforum = 1 and xchan_deleted = 0 and abook_channel = %d order by xchan_name $limit ",
 		intval(local_channel())
 	);
 	if(! $r1)
diff --git a/include/zot.php b/include/zot.php
index 45347ef22..73d9ef950 100644
--- a/include/zot.php
+++ b/include/zot.php
@@ -12,6 +12,7 @@ require_once('include/crypto.php');
 require_once('include/items.php');
 require_once('include/hubloc.php');
 require_once('include/queue_fn.php');
+require_once('include/perm_upgrade.php');
 
 
 /**
@@ -388,10 +389,7 @@ function zot_refresh($them, $channel = null, $force = false) {
 		if(! $x['success'])
 			return false;
 
-		$their_perms = 0;
-
 		if($channel) {
-			$global_perms = get_perms();
 			if($j['permissions']['data']) {
 				$permissions = crypto_unencapsulate(array(
 					'data' => $j['permissions']['data'],
@@ -408,15 +406,10 @@ function zot_refresh($them, $channel = null, $force = false) {
 			$connected_set = false;
 
 			if($permissions && is_array($permissions)) {
+				$old_read_stream_perm = get_abconfig($channel['channel_id'],$x['hash'],'their_perms','view_stream');
+
 				foreach($permissions as $k => $v) {
-					// The connected permission means you are in their address book
-					if($k === 'connected') {
-						$connected_set = intval($v);
-						continue;
-					}
-					if(($v) && (array_key_exists($k,$global_perms))) {
-						$their_perms = $their_perms | intval($global_perms[$k][1]);
-					}
+					set_abconfig($channel['channel_id'],$x['hash'],'their_perms',$k,$v);
 				}
 			}
 
@@ -443,36 +436,19 @@ function zot_refresh($them, $channel = null, $force = false) {
 				if(substr($r[0]['abook_dob'],5) == substr($next_birthday,5))
 					$next_birthday = $r[0]['abook_dob'];
 
-				$current_abook_connected = (intval($r[0]['abook_unconnected']) ? 0 : 1);
-
-				$y = q("update abook set abook_their_perms = %d, abook_dob = '%s'
+				$y = q("update abook set abook_dob = '%s'
 					where abook_xchan = '%s' and abook_channel = %d
 					and abook_self = 0 ",
-					intval($their_perms),
 					dbescdate($next_birthday),
 					dbesc($x['hash']),
 					intval($channel['channel_id'])
 				);
 
-//				if(($connected_set === 0 || $connected_set === 1) && ($connected_set !== $current_abook_unconnected)) {
-
-					// if they are in your address book but you aren't in theirs, and/or this does not
-					// match your current connected state setting, toggle it.
-					/** @FIXME uncoverted to postgres */
-					/** @FIXME when this was enabled, all contacts became unconnected. Currently disabled intentionally */
-//					$y1 = q("update abook set abook_unconnected = 1
-//						where abook_xchan = '%s' and abook_channel = %d
-//						and abook_self = 0 limit 1",
-//						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((! ($r[0]['abook_their_perms'] & PERMS_R_STREAM)) && ($their_perms & PERMS_R_STREAM))
+					if((! $old_read_stream_perm) && (intval($permissions['view_stream'])))
 						Zotlabs\Daemon\Master::Summon(array('Onepoll',$r[0]['abook_id']));
 				}
 			}
@@ -480,15 +456,29 @@ function zot_refresh($them, $channel = null, $force = false) {
 
 				// new connection
 
+				$my_perms = null;
+
 				$role = get_pconfig($channel['channel_id'],'system','permissions_role');
 				if($role) {
-					$xx = get_role_perms($role);
-					if($xx['perms_auto'])
-						$default_perms = $xx['perms_accept'];
+					$xx = \Zotlabs\Access\PermissionRoles::role_perms($role);
+					if($xx['perms_auto']) {
+						$default_perms = $xx['perms_connect'];
+						$my_perms = \Zotlabs\Access\Permissions::FilledPerms($default_perms);
+					}
 				}
-				if(! $default_perms)
-					$default_perms = intval(get_pconfig($channel['channel_id'],'system','autoperms'));
 
+				if(! $my_perms) {
+					$m = \Zotlabs\Access\Permissions::FilledAutoperms($channel['channel_id']);
+					if($m) {
+						$my_perms = $m;
+					}
+				}
+
+				if($my_perms) {
+					foreach($my_perms as $k => $v) {
+						set_abconfig($channel['channel_id'],$x['hash'],'my_perms',$k,$v);
+					}
+				}
 
 				// Keep original perms to check if we need to notify them
 				$previous_perms = get_all_perms($channel['channel_id'],$x['hash']);
@@ -498,13 +488,11 @@ function zot_refresh($them, $channel = null, $force = false) {
 				if($closeness === false)
 					$closeness = 80;
 
-				$y = q("insert into abook ( abook_account, abook_channel, abook_closeness, abook_xchan, abook_their_perms, abook_my_perms, abook_created, abook_updated, abook_dob, abook_pending ) values ( %d, %d, %d, '%s', %d, %d, '%s', '%s', '%s', %d )",
+				$y = q("insert into abook ( abook_account, abook_channel, abook_closeness, abook_xchan, abook_created, abook_updated, abook_dob, abook_pending ) values ( %d, %d, %d, '%s', '%s', '%s', '%s', %d )",
 					intval($channel['channel_account_id']),
 					intval($channel['channel_id']),
 					intval($closeness),
 					dbesc($x['hash']),
-					intval($their_perms),
-					intval($default_perms),
 					dbesc(datetime_convert()),
 					dbesc(datetime_convert()),
 					dbesc($next_birthday),
@@ -523,7 +511,7 @@ function zot_refresh($them, $channel = null, $force = false) {
 					);
 
 					if($new_connection) {
-						if($new_perms != $previous_perms)
+						if(! \Zotlabs\Access\Permissions::PermsCompare($new_perms,$previous_perms))
 							Zotlabs\Daemon\Master::Summon(array('Notifier','permission_create',$new_connection[0]['abook_id']));
 						Zotlabs\Lib\Enotify::submit(array(
 							'type'       => NOTIFY_INTRO,
@@ -532,9 +520,9 @@ function zot_refresh($them, $channel = null, $force = false) {
 							'link'       => z_root() . '/connedit/' . $new_connection[0]['abook_id'],
 						));
 					
-						if($their_perms & PERMS_R_STREAM) {
-							if(($channel['channel_w_stream'] & PERMS_PENDING)
-								|| (! intval($new_connection[0]['abook_pending'])) )
+						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']));
 						}
 
@@ -1371,8 +1359,8 @@ function public_recips($msg) {
 	if($msg['message']['type'] === 'activity') {
 		if(! get_config('system','disable_discover_tab'))
 			$include_sys = true;
-		$col = 'channel_w_stream';
-		$field = PERMS_W_STREAM;
+		$perm = 'send_stream';
+
 		if(array_key_exists('flags',$msg['message']) && in_array('thread_parent', $msg['message']['flags'])) {
 			// check mention recipient permissions on top level posts only
 			$check_mentions = true;
@@ -1404,65 +1392,30 @@ function public_recips($msg) {
 			// contains the tag. we'll solve that further below.
 
 			if($msg['notify']['sender']['guid_sig'] != $msg['message']['owner']['guid_sig']) {
-				$col = 'channel_w_comment';
-				$field = PERMS_W_COMMENT;
+				$perm = 'post_comments';
 			}
 		}
 	}
-	elseif($msg['message']['type'] === 'mail') {
-		$col = 'channel_w_mail';
-		$field = PERMS_W_MAIL;
+	elseif($msg['message']['type'] === 'mail')
+		$perm = 'post_mail';
+
+	$r = array();
+	
+	$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['notify']['sender']['hash'],$perm)) {
+				$r[] = [ 'hash' => $cc['channel_hash'] ];
+			}
+		}
 	}
 
-	if(! $col)
-		return NULL;
-
-	$col = dbesc($col);
-
-	// First find those channels who are accepting posts from anybody, or at least
-	// something greater than just their connections.
-
-	if($msg['notify']['sender']['url'] === z_root()) {
-		$sql = " where (( " . $col . " & " . intval(PERMS_NETWORK) . " ) > 0
-					or (  " . $col . " & " . intval(PERMS_SITE) . " ) > 0
-					or (  " . $col . " & " . intval(PERMS_PUBLIC) . ") > 0
-					or (  " . $col . " & " . intval(PERMS_AUTHED)  . ") > 0 ) ";
-	} else {
-		$sql = " where ( " . $col . " = " . intval(PERMS_NETWORK) . " 
-					or " . $col . " = " . intval(PERMS_PUBLIC) . "
-					or " . $col . " = " . intval(PERMS_AUTHED) . " ) ";
-	}
-
-	$r = q("select channel_hash as hash from channel $sql or channel_hash = '%s'
-		and channel_removed = 0 ",
-		dbesc($msg['notify']['sender']['hash'])
-	);
-
-	if(! $r)
-		$r = array();
-
-	// Now we have to get a bit dirty. Find every channel that has the sender in their connections (abook)
-	// and is allowing this sender at least at a high level.
-
-	$x = q("select channel_hash as hash from channel left join abook on abook_channel = channel_id
-		where abook_xchan = '%s' and channel_removed = 0
-		and (( " . $col . " = " . intval(PERMS_SPECIFIC) . " and ( abook_my_perms & " . intval($field) . " ) > 0 )
-		OR   " . $col . " = " . intval(PERMS_PENDING) . " 
-		OR  ( " . $col . " = " . intval(PERMS_CONTACTS) . " and abook_pending = 0 )) ",
-		dbesc($msg['notify']['sender']['hash'])
-	);
-
-	if(! $x)
-		$x = array();
-
-	$r = array_merge($r,$x);
-
-	//logger('message: ' . print_r($msg['message'],true));
+	// logger('message: ' . print_r($msg['message'],true));
 
 	if($include_sys && array_key_exists('public_scope',$msg['message']) && $msg['message']['public_scope'] === 'public') {
 		$sys = get_sys_channel();
 		if($sys)
-			$r[] = array('hash' => $sys['channel_hash']);
+			$r[] = [ 'hash' => $sys['channel_hash'] ];
 	}
 
 	// look for any public mentions on this site
@@ -1943,9 +1896,9 @@ function remove_community_tag($sender, $arr, $uid) {
 	$i = $r[0];
 
 	if($i['target'])
-		$i['target'] = json_decode_plus($i['target']);
+		$i['target'] = json_decode($i['target'],true);
 	if($i['object'])
-		$i['object'] = json_decode_plus($i['object']);
+		$i['object'] = json_decode($i['object'],true);
 
 	if(! ($i['target'] && $i['object'])) {
 		logger('remove_community_tag: no target/object');
@@ -2998,6 +2951,14 @@ function build_sync_packet($uid = 0, $packet = null, $groups_changed = false) {
 
 	$channel = $r[0];
 
+	translate_channel_perms_outbound($channel);
+	if($packet && array_key_exists('abook',$packet) && $packet['abook']) {
+		for($x = 0; $x < count($packet['abook']); $x ++) {
+			translate_abook_perms_outbound($packet['abook'][$x]);
+		}
+	}
+
+
 	if(intval($channel['channel_removed']))
 		return;
 
@@ -3121,7 +3082,8 @@ function process_channel_sync_delivery($sender, $arr, $deliveries) {
 
 	require_once('include/import.php');
 
-	/** @FIXME this will sync red structures (channel, pconfig and abook). Eventually we need to make this application agnostic. */
+	/** @FIXME this will sync red structures (channel, pconfig and abook). 
+		Eventually we need to make this application agnostic. */
 
 	$result = array();
 
@@ -3194,6 +3156,8 @@ function process_channel_sync_delivery($sender, $arr, $deliveries) {
 
 		if(array_key_exists('channel',$arr) && is_array($arr['channel']) && count($arr['channel'])) {
 
+			translate_channel_perms_inbound($arr['channel']);
+
 			if(array_key_exists('channel_pageflags',$arr['channel']) && intval($arr['channel']['channel_pageflags'])) {
 				// These flags cannot be sync'd.
 				// remove the bits from the incoming flags.
@@ -3207,7 +3171,15 @@ function process_channel_sync_delivery($sender, $arr, $deliveries) {
 
 			}
 			
-			$disallowed = array('channel_id','channel_account_id','channel_primary','channel_prvkey', 'channel_address', 'channel_notifyflags', 'channel_removed', 'channel_deleted', 'channel_system');
+			$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) {
@@ -3243,6 +3215,8 @@ function process_channel_sync_delivery($sender, $arr, $deliveries) {
 
 			foreach($arr['abook'] as $abook) {
 
+				
+
 				$abconfig = null;
 
 				if(array_key_exists('abconfig',$abook) && is_array($abook['abconfig']) && count($abook['abconfig']))
@@ -3337,6 +3311,12 @@ function process_channel_sync_delivery($sender, $arr, $deliveries) {
 					}
 				}
 
+				// 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) {
@@ -3802,11 +3782,21 @@ function zotinfo($arr) {
 	}
 	else {
 		// check if it has characteristics of a public forum based on custom permissions.
-		$t = q("select abook_my_perms from abook where abook_channel = %d and abook_self = 1 limit 1",
-			intval($e['channel_id'])
+		$t = q("select * from abconfig where abconfig.cat = 'my_perms' and abconfig.chan = %d and abconfig.xchan = '%s' and abconfig.k in ('tag_deliver', 'send_stream') ",
+			intval($e['channel_id']),
+			intval($e['channel_hash'])
 		);
-		if(($t) && (($t[0]['abook_my_perms'] & PERMS_W_TAGWALL) && (! ($t[0]['abook_my_perms'] & PERMS_W_STREAM))))
-			$public_forum = true;
+		$ch = 0;
+		if($t) {
+			foreach($t as $tt) {
+				if($tt['k'] == 'tag_deliver' && $tt['v'] == 1)
+					$ch ++;
+				if($tt['k'] == 'send_stream' && $tt['v'] == 0)
+					$ch ++;
+			}
+			if($ch == 2)
+				$public_forum = true;
+		}
 	}
 
 
diff --git a/install/schema_mysql.sql b/install/schema_mysql.sql
index 5335c231e..5e5b9d5be 100644
--- a/install/schema_mysql.sql
+++ b/install/schema_mysql.sql
@@ -920,6 +920,7 @@ CREATE TABLE IF NOT EXISTS `pconfig` (
   UNIQUE KEY `access` (`uid`,`cat`,`k`)
 ) ENGINE=MyISAM  DEFAULT CHARSET=utf8;
 
+
 CREATE TABLE IF NOT EXISTS `photo` (
   `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
   `aid` int(10) unsigned NOT NULL DEFAULT '0',
diff --git a/install/schema_postgres.sql b/install/schema_postgres.sql
index 1a770d4ff..44711c190 100644
--- a/install/schema_postgres.sql
+++ b/install/schema_postgres.sql
@@ -903,6 +903,7 @@ CREATE TABLE "pconfig" (
   PRIMARY KEY ("id"),
   UNIQUE ("uid","cat","k")
 );
+
 CREATE TABLE "photo" (
   "id" serial  NOT NULL,
   "aid" bigint  NOT NULL DEFAULT '0',
diff --git a/install/update.php b/install/update.php
index f2d97430b..3d0e5b00d 100644
--- a/install/update.php
+++ b/install/update.php
@@ -1,6 +1,6 @@
 
+
+```
+
+Or, using Webpack or Browserify:
+
+```javascript
+require('readmore-js');
 ```
 
 
@@ -49,17 +55,23 @@ $('article').readmore({
 * `startOpen: false` do not immediately truncate, start in the fully opened position
 * `beforeToggle: function() {}` called after a more or less link is clicked, but *before* the block is collapsed or expanded
 * `afterToggle: function() {}` called *after* the block is collapsed or expanded
+* `blockProcessed: function() {}` called once per block during initilization after Readmore.js has processed the block.
 
 If the element has a `max-height` CSS property, Readmore.js will use that value rather than the value of the `collapsedHeight` option.
 
 ### The callbacks:
 
-The callback functions, `beforeToggle` and `afterToggle`, both receive the same arguments: `trigger`, `element`, and `expanded`.
+The `beforeToggle` and `afterToggle` callbacks both receive the same arguments: `trigger`, `element`, and `expanded`.
 
 * `trigger`: the "Read more" or "Close" element that was clicked
 * `element`: the block that is being collapsed or expanded
 * `expanded`: Boolean; `true` means the block is expanded
 
+The `blockProcessed` callback receives `element` and `collapsable`.
+
+* `element`: the block that has just been processed
+* `collapsable`: Boolean; `false` means the block was shorter than the specified minimum `collapsedHeight`--the block will not have a "Read more" link
+
 #### Callback example:
 
 Here's an example of how you could use the `afterToggle` callback to scroll back to the top of a block when the "Close" link is clicked.
@@ -166,6 +178,6 @@ $ npm install
 Which will install the necessary development dependencies. Then, to build the minified script:
 
 ```
-$ gulp compress
+$ npm run build
 ```
 
diff --git a/library/readmore.js/hubzilla-custom-fix-webkit-browsers.patch b/library/readmore.js/hubzilla-custom-fix-webkit-browsers.patch
new file mode 100644
index 000000000..fd3146152
--- /dev/null
+++ b/library/readmore.js/hubzilla-custom-fix-webkit-browsers.patch
@@ -0,0 +1,13 @@
+diff --git a/library/readmore.js/readmore.js b/library/readmore.js/readmore.js
+index 34a624e..51222ce 100644
+--- a/library/readmore.js/readmore.js
++++ b/library/readmore.js/readmore.js
+@@ -246,7 +246,7 @@
+           collapsedHeight = $element.data('collapsedHeight');
+ 
+       if ($element.height() <= collapsedHeight) {
+-        newHeight = $element.data('expandedHeight') + 'px';
++        newHeight = 100 + '%';
+         newLink = 'lessLink';
+         expanded = true;
+       }
diff --git a/library/readmore.js/readmore.js b/library/readmore.js/readmore.js
index fb5a0e0f9..51222ced0 100644
--- a/library/readmore.js/readmore.js
+++ b/library/readmore.js/readmore.js
@@ -37,8 +37,9 @@
         startOpen: false,
 
         // callbacks
-        beforeToggle: function(){},
-        afterToggle: function(){}
+        blockProcessed: function() {},
+        beforeToggle: function() {},
+        afterToggle: function() {}
       },
       cssEmbedded = {},
       uniqueIdCounter = 0;
@@ -187,6 +188,9 @@
 
       if (current.outerHeight(true) <= collapsedHeight + heightMargin) {
         // The block is shorter than the limit, so there's no need to truncate it.
+        if (this.options.blockProcessed && typeof this.options.blockProcessed === 'function') {
+          this.options.blockProcessed(current, false);
+        }
         return true;
       }
       else {
@@ -206,7 +210,7 @@
             };
           })(this))
           .attr({
-            'data-readmore-toggle': '',
+            'data-readmore-toggle': id,
             'aria-controls': id
           }));
 
@@ -215,6 +219,10 @@
             height: collapsedHeight
           });
         }
+
+        if (this.options.blockProcessed && typeof this.options.blockProcessed === 'function') {
+          this.options.blockProcessed(current, true);
+        }
       }
     },
 
@@ -224,11 +232,11 @@
       }
 
       if (! trigger) {
-        trigger = $('[aria-controls="' + _this.element.id + '"]')[0];
+        trigger = $('[aria-controls="' + this.element.id + '"]')[0];
       }
 
       if (! element) {
-        element = _this.element;
+        element = this.element;
       }
 
       var $element = $(element),
@@ -250,14 +258,18 @@
       // Fire beforeToggle callback
       // Since we determined the new "expanded" state above we're now out of sync
       // with our true current state, so we need to flip the value of `expanded`
-      this.options.beforeToggle(trigger, $element, ! expanded);
+      if (this.options.beforeToggle && typeof this.options.beforeToggle === 'function') {
+        this.options.beforeToggle(trigger, $element, ! expanded);
+      }
 
       $element.css({'height': newHeight});
 
       // Fire afterToggle callback
       $element.on('transitionend', (function(_this) {
         return function() {
-          _this.options.afterToggle(trigger, $element, expanded);
+          if (_this.options.afterToggle && typeof _this.options.afterToggle === 'function') {
+            _this.options.afterToggle(trigger, $element, expanded);
+          }
 
           $(this).attr({
             'aria-expanded': expanded
@@ -272,7 +284,7 @@
             };
           })(this))
         .attr({
-          'data-readmore-toggle': '',
+          'data-readmore-toggle': $element.attr('id'),
           'aria-controls': $element.attr('id')
         }));
     },
diff --git a/util/fresh b/util/fresh
index 9d74ea584..7f57931aa 100755
--- a/util/fresh
+++ b/util/fresh
@@ -82,12 +82,15 @@ function process_command($line) {
 				exec('/bin/stty echo');
 				echo "\n";
 				require_once('include/auth.php');
-				$record = App::$account = account_verify_password(argv(1),trim($x,"\n"));
+				$record = null;
+				$x = account_verify_password(argv(1),trim($x,"\n"));
+				if($x['account'])
+					$record = App::$account = $x['account'];
 
 				if($record) {
 					$_SESSION['account_id'] = App::$account['account_id'];
 					$_SESSION['last_login_date'] = datetime_convert();
-					authenticate_success($record, true, true);
+					authenticate_success($record, $x['channel'], true, true);
 					echo 'logged in';
 					$channel = App::get_channel();
 					if($channel)
diff --git a/view/js/main.js b/view/js/main.js
index a288f98f5..a3fade0ea 100644
--- a/view/js/main.js
+++ b/view/js/main.js
@@ -659,7 +659,7 @@ function collapseHeight() {
 	var position = $(window).scrollTop();
 
 	$(".wall-item-content, .directory-collapse").each(function() {
-		var orgHeight = parseInt($(this).css('height'));
+		var orgHeight = $(this).outerHeight(true);
 		if(orgHeight > divmore_height) {
 			if(! $(this).hasClass('divmore')) {
 
@@ -679,7 +679,7 @@ function collapseHeight() {
 					beforeToggle: function(trigger, element, expanded) {
 						if(expanded) {
 							if((($(element).offset().top + divmore_height) - $(window).scrollTop()) < 65 ) {
-								$(window).scrollTop($(window).scrollTop() - (orgHeight - divmore_height));
+								$(window).scrollTop($(window).scrollTop() - ($(element).outerHeight(true) - divmore_height));
 							}
 						}
 					}
diff --git a/view/theme/redbasic/css/style.css b/view/theme/redbasic/css/style.css
index d61e60dc9..6cc3e2f10 100644
--- a/view/theme/redbasic/css/style.css
+++ b/view/theme/redbasic/css/style.css
@@ -2037,3 +2037,7 @@ dl.bb-dl > dd > li {
     border-style: solid;
     border-width: 5px;
 }
+
+#wiki-preview img {
+    max-width: 100%;
+}
\ No newline at end of file
diff --git a/view/tpl/wiki.tpl b/view/tpl/wiki.tpl
index 7617808f4..d9a4f8be3 100644
--- a/view/tpl/wiki.tpl
+++ b/view/tpl/wiki.tpl
@@ -311,6 +311,9 @@ function wiki_delete_wiki(wikiHtmlName, resource_id) {
       ev.preventDefault();
       return false;
     }
+    if(!confirm('Are you sure you want to delete the page: ' + window.wiki_page_name)) {
+      return;
+    }
     $.post("wiki/{{$channel}}/delete/page", {name: window.wiki_page_name, resource_id: window.wiki_resource_id}, 
       function (data) {
         if (data.success) {