aboutsummaryrefslogtreecommitdiffstats
path: root/Zotlabs/Lib
diff options
context:
space:
mode:
Diffstat (limited to 'Zotlabs/Lib')
-rw-r--r--Zotlabs/Lib/ActivityStreams.php199
-rw-r--r--Zotlabs/Lib/Apps.php187
-rw-r--r--Zotlabs/Lib/Cache.php12
-rw-r--r--Zotlabs/Lib/Config.php2
-rw-r--r--Zotlabs/Lib/DB_Upgrade.php121
-rw-r--r--Zotlabs/Lib/Enotify.php20
-rw-r--r--Zotlabs/Lib/LDSignatures.php135
-rw-r--r--Zotlabs/Lib/MarkdownSoap.php103
-rw-r--r--Zotlabs/Lib/NativeWiki.php90
-rw-r--r--Zotlabs/Lib/NativeWikiPage.php131
-rw-r--r--Zotlabs/Lib/PConfig.php11
-rw-r--r--Zotlabs/Lib/SConfig.php25
-rw-r--r--Zotlabs/Lib/System.php21
-rw-r--r--Zotlabs/Lib/Techlevels.php12
-rw-r--r--Zotlabs/Lib/ThreadItem.php66
-rw-r--r--Zotlabs/Lib/ThreadStream.php18
16 files changed, 1028 insertions, 125 deletions
diff --git a/Zotlabs/Lib/ActivityStreams.php b/Zotlabs/Lib/ActivityStreams.php
new file mode 100644
index 000000000..379e78a59
--- /dev/null
+++ b/Zotlabs/Lib/ActivityStreams.php
@@ -0,0 +1,199 @@
+<?php
+
+namespace Zotlabs\Lib;
+
+class ActivityStreams {
+
+ public $data;
+ public $valid = false;
+ public $id = '';
+ public $type = '';
+ public $actor = null;
+ public $obj = null;
+ public $tgt = null;
+ public $origin = null;
+ public $owner = null;
+ public $signer = null;
+ public $ldsig = null;
+ public $sigok = false;
+ public $recips = null;
+ public $raw_recips = null;
+
+ function __construct($string) {
+
+ $this->data = json_decode($string,true);
+ if($this->data) {
+ $this->valid = true;
+ }
+
+ if($this->is_valid()) {
+ $this->id = $this->get_property_obj('id');
+ $this->type = $this->get_primary_type();
+ $this->actor = $this->get_compound_property('actor');
+ $this->obj = $this->get_compound_property('object');
+ $this->tgt = $this->get_compound_property('target');
+ $this->origin = $this->get_compound_property('origin');
+ $this->recips = $this->collect_recips();
+
+ $this->ldsig = $this->get_compound_property('signature');
+ if($this->ldsig) {
+ $this->signer = $this->get_compound_property('creator',$this->ldsig);
+ if($this->signer && $this->signer['publicKey'] && $this->signer['publicKey']['publicKeyPem']) {
+ $this->sigok = \Zotlabs\Lib\LDSignatures::verify($this->data,$this->signer['publicKey']['publicKeyPem']);
+ }
+ }
+
+ if(($this->type === 'Note') && (! $this->obj)) {
+ $this->obj = $this->data;
+ $this->type = 'Create';
+ }
+ }
+ }
+
+ function is_valid() {
+ return $this->valid;
+ }
+
+ function set_recips($arr) {
+ $this->saved_recips = $arr;
+ }
+
+ function collect_recips($base = '',$namespace = '') {
+ $x = [];
+ $fields = [ 'to','cc','bto','bcc','audience'];
+ foreach($fields as $f) {
+ $y = $this->get_compound_property($f,$base,$namespace);
+ if($y) {
+ $x = array_merge($x,$y);
+ if(! is_array($this->raw_recips))
+ $this->raw_recips = [];
+ $this->raw_recips[$f] = $x;
+ }
+ }
+// not yet ready for prime time
+// $x = $this->expand($x,$base,$namespace);
+ return $x;
+ }
+
+ function expand($arr,$base = '',$namespace = '') {
+ $ret = [];
+
+ // right now use a hardwired recursion depth of 5
+
+ for($z = 0; $z < 5; $z ++) {
+ if(is_array($arr) && $arr) {
+ foreach($arr as $a) {
+ if(is_array($a)) {
+ $ret[] = $a;
+ }
+ else {
+ $x = $this->get_compound_property($a,$base,$namespace);
+ if($x) {
+ $ret = array_merge($ret,$x);
+ }
+ }
+ }
+ }
+ }
+
+ // @fixme de-duplicate
+
+ return $ret;
+ }
+
+ function get_namespace($base,$namespace) {
+
+ if(! $namespace)
+ return '';
+
+ $key = null;
+
+
+ foreach( [ $this->data, $base ] as $b ) {
+ if(! $b)
+ continue;
+ if(array_key_exists('@context',$b)) {
+ if(is_array($b['@context'])) {
+ foreach($b['@context'] as $ns) {
+ if(is_array($ns)) {
+ foreach($ns as $k => $v) {
+ if($namespace === $v)
+ $key = $k;
+ }
+ }
+ else {
+ if($namespace === $ns) {
+ $key = '';
+ }
+ }
+ }
+ }
+ else {
+ if($namespace === $b['@context']) {
+ $key = '';
+ }
+ }
+ }
+ }
+ return $key;
+ }
+
+
+ function get_property_obj($property,$base = '',$namespace = '' ) {
+ $prefix = $this->get_namespace($base,$namespace);
+ if($prefix === null)
+ return null;
+ $base = (($base) ? $base : $this->data);
+ $propname = (($prefix) ? $prefix . ':' : '') . $property;
+ return ((array_key_exists($propname,$base)) ? $base[$propname] : null);
+ }
+
+ function fetch_property($url) {
+ $redirects = 0;
+ if(! check_siteallowed($url)) {
+ logger('blacklisted: ' . $url);
+ return null;
+ }
+
+ $x = z_fetch_url($url,true,$redirects,
+ ['headers' => [ 'Accept: application/ld+json; profile="https://www.w3.org/ns/activitystreams", application/activity+json' ]]);
+ if($x['success'])
+ return json_decode($x['body'],true);
+ return null;
+ }
+
+ function get_compound_property($property,$base = '',$namespace = '') {
+ $x = $this->get_property_obj($property,$base,$namespace);
+ if($this->is_url($x)) {
+ $x = $this->fetch_property($x);
+ }
+ return $x;
+ }
+
+ function is_url($url) {
+ if(($url) && (! is_array($url)) && (strpos($url,'http') === 0)) {
+ return true;
+ }
+ return false;
+ }
+
+ function get_primary_type($base = '',$namespace = '') {
+ if(! $base)
+ $base = $this->data;
+ $x = $this->get_property_obj('type',$base,$namespace);
+ if(is_array($x)) {
+ foreach($x as $y) {
+ if(strpos($y,':') === false) {
+ return $y;
+ }
+ }
+ }
+ return $x;
+ }
+
+ function debug() {
+ $x = var_export($this,true);
+ return $x;
+ }
+
+} \ No newline at end of file
diff --git a/Zotlabs/Lib/Apps.php b/Zotlabs/Lib/Apps.php
index 1432cbdcf..37cbf9497 100644
--- a/Zotlabs/Lib/Apps.php
+++ b/Zotlabs/Lib/Apps.php
@@ -34,7 +34,7 @@ class Apps {
if($files) {
foreach($files as $f) {
$path = explode('/',$f);
- $plugin = $path[1];
+ $plugin = trim($path[1]);
if(plugin_is_installed($plugin)) {
$x = self::parse_app_description($f,$translate);
if($x) {
@@ -169,6 +169,14 @@ class Apps {
$requires = explode(',',$ret['requires']);
foreach($requires as $require) {
$require = trim(strtolower($require));
+ $config = false;
+
+ if(substr($require, 0, 7) == 'config:') {
+ $config = true;
+ $require = ltrim($require, 'config:');
+ $require = explode('=', $require);
+ }
+
switch($require) {
case 'nologin':
if(local_channel())
@@ -191,10 +199,13 @@ class Apps {
unset($ret);
break;
default:
- if(! (local_channel() && feature_enabled(local_channel(),$require)))
+ if($config)
+ $unset = ((get_config('system', $require[0]) == $require[1]) ? false : true);
+ else
+ $unset = ((local_channel() && feature_enabled(local_channel(),$require)) ? false : true);
+ if($unset)
unset($ret);
break;
-
}
}
}
@@ -209,6 +220,8 @@ class Apps {
static public function translate_system_apps(&$arr) {
$apps = array(
+ 'Apps' => t('Apps'),
+ 'Cards' => t('Cards'),
'Site Admin' => t('Site Admin'),
'Report Bug' => t('Report Bug'),
'View Bookmarks' => t('View Bookmarks'),
@@ -219,7 +232,7 @@ class Apps {
'Suggest Channels' => t('Suggest Channels'),
'Login' => t('Login'),
'Channel Manager' => t('Channel Manager'),
- 'Grid' => t('Grid'),
+ 'Grid' => t('Activity'),
'Settings' => t('Settings'),
'Files' => t('Files'),
'Webpages' => t('Webpages'),
@@ -245,9 +258,19 @@ class Apps {
'Profile Photo' => t('Profile Photo')
);
- if(array_key_exists($arr['name'],$apps)) {
- $arr['name'] = $apps[$arr['name']];
+ if(array_key_exists('name',$arr)) {
+ if(array_key_exists($arr['name'],$apps)) {
+ $arr['name'] = $apps[$arr['name']];
+ }
+ }
+ else {
+ for($x = 0; $x < count($arr); $x++) {
+ if(array_key_exists($arr[$x]['name'],$apps)) {
+ $arr[$x]['name'] = $apps[$arr[$x]['name']];
+ }
+ }
}
+
}
@@ -275,7 +298,7 @@ class Apps {
self::translate_system_apps($papp);
- if(($papp['plugin']) && (! plugin_is_installed($papp['plugin'])))
+ if(trim($papp['plugin']) && (! plugin_is_installed(trim($papp['plugin']))))
return '';
$papp['papp'] = self::papp_encode($papp);
@@ -294,8 +317,17 @@ class Apps {
if($k === 'requires') {
$requires = explode(',',$v);
+
foreach($requires as $require) {
$require = trim(strtolower($require));
+ $config = false;
+
+ if(substr($require, 0, 7) == 'config:') {
+ $config = true;
+ $require = ltrim($require, 'config:');
+ $require = explode('=', $require);
+ }
+
switch($require) {
case 'nologin':
if(local_channel())
@@ -319,10 +351,13 @@ class Apps {
return '';
break;
default:
- if(! (local_channel() && feature_enabled(local_channel(),$require)))
+ if($config)
+ $unset = ((get_config('system', $require[0]) == $require[1]) ? false : true);
+ else
+ $unset = ((local_channel() && feature_enabled(local_channel(),$require)) ? false : true);
+ if($unset)
return '';
break;
-
}
}
}
@@ -360,7 +395,10 @@ class Apps {
'$deleted' => $papp['deleted'],
'$feature' => (($papp['embed']) ? false : true),
'$featured' => ((strpos($papp['categories'], 'nav_featured_app') === false) ? false : true),
- '$navapps' => (($mode == 'nav') ? true : false)
+ '$navapps' => (($mode == 'nav') ? true : false),
+ '$order' => (($mode == 'nav-order') ? true : false),
+ '$add' => t('Add to app-tray'),
+ '$remove' => t('Remove from app-tray')
));
}
@@ -527,6 +565,129 @@ class Apps {
return($r);
}
+ static public function app_order($uid,$apps) {
+
+ if(! $apps)
+ return $apps;
+
+ $x = (($uid) ? get_pconfig($uid,'system','app_order') : get_config('system','app_order'));
+ if(($x) && (! is_array($x))) {
+ $y = explode(',',$x);
+ $y = array_map('trim',$y);
+ $x = $y;
+ }
+
+ if(! (is_array($x) && ($x)))
+ return $apps;
+
+ $ret = [];
+ foreach($x as $xx) {
+ $y = self::find_app_in_array($xx,$apps);
+ if($y) {
+ $ret[] = $y;
+ }
+ }
+ foreach($apps as $ap) {
+ if(! self::find_app_in_array($ap['name'],$ret)) {
+ $ret[] = $ap;
+ }
+ }
+ return $ret;
+
+ }
+
+ static function find_app_in_array($name,$arr) {
+ if(! $arr)
+ return false;
+ foreach($arr as $x) {
+ if($x['name'] === $name) {
+ return $x;
+ }
+ }
+ return false;
+ }
+
+ static function moveup($uid,$guid) {
+ $syslist = array();
+ $list = self::app_list($uid, false, 'nav_featured_app');
+ if($list) {
+ foreach($list as $li) {
+ $syslist[] = self::app_encode($li);
+ }
+ }
+ self::translate_system_apps($syslist);
+
+ usort($syslist,'self::app_name_compare');
+
+ $syslist = self::app_order($uid,$syslist);
+
+ if(! $syslist)
+ return;
+
+ $newlist = [];
+
+ foreach($syslist as $k => $li) {
+ if($li['guid'] === $guid) {
+ $position = $k;
+ break;
+ }
+ }
+ if(! $position)
+ return;
+ $dest_position = $position - 1;
+ $saved = $syslist[$dest_position];
+ $syslist[$dest_position] = $syslist[$position];
+ $syslist[$position] = $saved;
+
+ $narr = [];
+ foreach($syslist as $x) {
+ $narr[] = $x['name'];
+ }
+
+ set_pconfig($uid,'system','app_order',implode(',',$narr));
+
+ }
+
+ static function movedown($uid,$guid) {
+ $syslist = array();
+ $list = self::app_list($uid, false, 'nav_featured_app');
+ if($list) {
+ foreach($list as $li) {
+ $syslist[] = self::app_encode($li);
+ }
+ }
+ self::translate_system_apps($syslist);
+
+ usort($syslist,'self::app_name_compare');
+
+ $syslist = self::app_order($uid,$syslist);
+
+ if(! $syslist)
+ return;
+
+ $newlist = [];
+
+ foreach($syslist as $k => $li) {
+ if($li['guid'] === $guid) {
+ $position = $k;
+ break;
+ }
+ }
+ if($position >= count($syslist) - 1)
+ return;
+ $dest_position = $position + 1;
+ $saved = $syslist[$dest_position];
+ $syslist[$dest_position] = $syslist[$position];
+ $syslist[$position] = $saved;
+
+ $narr = [];
+ foreach($syslist as $x) {
+ $narr[] = $x['name'];
+ }
+
+ set_pconfig($uid,'system','app_order',implode(',',$narr));
+
+ }
static public function app_decode($s) {
$x = base64_decode(str_replace(array('<br />',"\r","\n",' '),array('','','',''),$s));
@@ -563,7 +724,7 @@ class Apps {
$darray['app_addr'] = ((x($arr,'addr')) ? escape_tags($arr['addr']) : '');
$darray['app_price'] = ((x($arr,'price')) ? escape_tags($arr['price']) : '');
$darray['app_page'] = ((x($arr,'page')) ? escape_tags($arr['page']) : '');
- $darray['app_plugin'] = ((x($arr,'plugin')) ? escape_tags($arr['plugin']) : '');
+ $darray['app_plugin'] = ((x($arr,'plugin')) ? escape_tags(trim($arr['plugin'])) : '');
$darray['app_requires'] = ((x($arr,'requires')) ? escape_tags($arr['requires']) : '');
$darray['app_system'] = ((x($arr,'system')) ? intval($arr['system']) : 0);
$darray['app_deleted'] = ((x($arr,'deleted')) ? intval($arr['deleted']) : 0);
@@ -641,7 +802,7 @@ class Apps {
$darray['app_addr'] = ((x($arr,'addr')) ? escape_tags($arr['addr']) : '');
$darray['app_price'] = ((x($arr,'price')) ? escape_tags($arr['price']) : '');
$darray['app_page'] = ((x($arr,'page')) ? escape_tags($arr['page']) : '');
- $darray['app_plugin'] = ((x($arr,'plugin')) ? escape_tags($arr['plugin']) : '');
+ $darray['app_plugin'] = ((x($arr,'plugin')) ? escape_tags(trim($arr['plugin'])) : '');
$darray['app_requires'] = ((x($arr,'requires')) ? escape_tags($arr['requires']) : '');
$darray['app_system'] = ((x($arr,'system')) ? intval($arr['system']) : 0);
$darray['app_deleted'] = ((x($arr,'deleted')) ? intval($arr['deleted']) : 0);
@@ -751,7 +912,7 @@ class Apps {
$ret['system'] = $app['app_system'];
if($app['app_plugin'])
- $ret['plugin'] = $app['app_plugin'];
+ $ret['plugin'] = trim($app['app_plugin']);
if($app['app_deleted'])
$ret['deleted'] = $app['app_deleted'];
diff --git a/Zotlabs/Lib/Cache.php b/Zotlabs/Lib/Cache.php
index f211269be..cea075659 100644
--- a/Zotlabs/Lib/Cache.php
+++ b/Zotlabs/Lib/Cache.php
@@ -9,10 +9,10 @@ namespace Zotlabs\Lib;
class Cache {
public static function get($key) {
- $key = substr($key,0,254);
+ $hash = hash('whirlpool',$key);
$r = q("SELECT v FROM cache WHERE k = '%s' limit 1",
- dbesc($key)
+ dbesc($hash)
);
if ($r)
@@ -22,20 +22,20 @@ class Cache {
public static function set($key,$value) {
- $key = substr($key,0,254);
+ $hash = hash('whirlpool',$key);
$r = q("SELECT * FROM cache WHERE k = '%s' limit 1",
- dbesc($key)
+ dbesc($hash)
);
if($r) {
q("UPDATE cache SET v = '%s', updated = '%s' WHERE k = '%s'",
dbesc($value),
dbesc(datetime_convert()),
- dbesc($key));
+ dbesc($hash));
}
else {
q("INSERT INTO cache ( k, v, updated) VALUES ('%s','%s','%s')",
- dbesc($key),
+ dbesc($hash),
dbesc($value),
dbesc(datetime_convert()));
}
diff --git a/Zotlabs/Lib/Config.php b/Zotlabs/Lib/Config.php
index 5625a3f79..6e042feba 100644
--- a/Zotlabs/Lib/Config.php
+++ b/Zotlabs/Lib/Config.php
@@ -53,7 +53,7 @@ class Config {
$dbvalue = ((is_array($value)) ? serialize($value) : $value);
$dbvalue = ((is_bool($dbvalue)) ? intval($dbvalue) : $dbvalue);
- if(get_config($family, $key) === false || (! self::get_from_storage($family, $key))) {
+ if(self::Get($family, $key) === false || (! self::get_from_storage($family, $key))) {
$ret = q("INSERT INTO config ( cat, k, v ) VALUES ( '%s', '%s', '%s' ) ",
dbesc($family),
dbesc($key),
diff --git a/Zotlabs/Lib/DB_Upgrade.php b/Zotlabs/Lib/DB_Upgrade.php
new file mode 100644
index 000000000..8f0488f6f
--- /dev/null
+++ b/Zotlabs/Lib/DB_Upgrade.php
@@ -0,0 +1,121 @@
+<?php
+
+namespace Zotlabs\Lib;
+
+
+class DB_Upgrade {
+
+ public $config_name = '';
+ public $func_prefix = '';
+
+ function __construct($db_revision) {
+
+ $platform_name = System::get_platform_name();
+
+ $update_file = 'install/' . $platform_name . '/update.php';
+ if(! file_exists($update_file)) {
+ $update_file = 'install/update.php';
+ $this->config_name = 'db_version';
+ $this->func_prefix = 'update_r';
+ }
+ else {
+ $this->config_name = $platform_name . '_db_version';
+ $this->func_prefix = $platform_name . '_update_';
+ }
+
+ $build = get_config('system', $this->config_name, 0);
+ if(! intval($build))
+ $build = set_config('system', $this->config_name, $db_revision);
+
+ if($build == $db_revision) {
+ // Nothing to be done.
+ return;
+ }
+ else {
+ $stored = intval($build);
+ if(! $stored) {
+ logger('Critical: check_config unable to determine database schema version');
+ return;
+ }
+
+ $current = intval($db_revision);
+
+ if(($stored < $current) && file_exists($update_file)) {
+
+ Config::Load('database');
+
+ // We're reporting a different version than what is currently installed.
+ // Run any existing update scripts to bring the database up to current.
+
+ require_once($update_file);
+
+ // make sure that boot.php and update.php are the same release, we might be
+ // updating from git right this very second and the correct version of the update.php
+ // file may not be here yet. This can happen on a very busy site.
+
+ if($db_revision == UPDATE_VERSION) {
+ for($x = $stored; $x < $current; $x ++) {
+ $func = $this->func_prefix . $x;
+ if(function_exists($func)) {
+ // There could be a lot of processes running or about to run.
+ // We want exactly one process to run the update command.
+ // So store the fact that we're taking responsibility
+ // after first checking to see if somebody else already has.
+
+ // If the update fails or times-out completely you may need to
+ // delete the config entry to try again.
+
+ if(get_config('database', $func))
+ break;
+ set_config('database',$func, '1');
+ // call the specific update
+
+ $retval = $func();
+ if($retval) {
+
+ // Prevent sending hundreds of thousands of emails by creating
+ // a lockfile.
+
+ $lockfile = 'store/[data]/mailsent';
+
+ if ((file_exists($lockfile)) && (filemtime($lockfile) > (time() - 86400)))
+ return;
+ @unlink($lockfile);
+ //send the administrator an e-mail
+ file_put_contents($lockfile, $x);
+
+ $r = q("select account_language from account where account_email = '%s' limit 1",
+ dbesc(\App::$config['system']['admin_email'])
+ );
+ push_lang(($r) ? $r[0]['account_language'] : 'en');
+
+ z_mail(
+ [
+ 'toEmail' => \App::$config['system']['admin_email'],
+ 'messageSubject' => sprintf( t('Update Error at %s'), z_root()),
+ 'textVersion' => replace_macros(get_intltext_template('update_fail_eml.tpl'),
+ [
+ '$sitename' => \App::$config['system']['sitename'],
+ '$siteurl' => z_root(),
+ '$update' => $x,
+ '$error' => sprintf( t('Update %s failed. See error logs.'), $x)
+ ]
+ )
+ ]
+ );
+
+ //try the logger
+ logger('CRITICAL: Update Failed: ' . $x);
+ pop_lang();
+ }
+ else {
+ set_config('database',$func, 'success');
+ }
+ }
+ }
+ set_config('system', $this->config_name, $db_revision);
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Zotlabs/Lib/Enotify.php b/Zotlabs/Lib/Enotify.php
index 257687567..9f3347d19 100644
--- a/Zotlabs/Lib/Enotify.php
+++ b/Zotlabs/Lib/Enotify.php
@@ -67,7 +67,7 @@ class Enotify {
$sender_name = $product;
$hostname = \App::get_hostname();
if(strpos($hostname,':'))
- $hostname = substr($hostname,0,strpos($hostname,':'));
+ $hostname = substr($hostname,0,strpos($hostname,':'));
// Do not translate 'noreply' as it must be a legal 7-bit email address
@@ -77,7 +77,7 @@ class Enotify {
$sender_email = get_config('system','from_email');
if(! $sender_email)
- $sender_email = 'Administrator' . '@' . \App::get_hostname();
+ $sender_email = 'Administrator' . '@' . $hostname;
$sender_name = get_config('system','from_email_name');
if(! $sender_name)
@@ -170,6 +170,7 @@ class Enotify {
xchan_query($p);
+ $moderated = (($p[0]['item_blocked'] == ITEM_MODERATED) ? true : false);
$item_post_type = item_post_type($p[0]);
// $private = $p[0]['item_private'];
@@ -208,13 +209,21 @@ class Enotify {
// Before this we have the name of the replier on the subject rendering
// differents subjects for messages on the same thread.
- $subject = sprintf( t('[$Projectname:Notify] Comment to conversation #%1$d by %2$s'), $parent_id, $sender['xchan_name']);
+ if($moderated)
+ $subject = sprintf( t('[$Projectname:Notify] Moderated Comment to conversation #%1$d by %2$s'), $parent_id, $sender['xchan_name']);
+ else
+ $subject = sprintf( t('[$Projectname:Notify] Comment to conversation #%1$d by %2$s'), $parent_id, $sender['xchan_name']);
$preamble = sprintf( t('%1$s, %2$s commented on an item/conversation you have been following.'), $recip['channel_name'], $sender['xchan_name']);
$epreamble = $dest_str;
$sitelink = t('Please visit %s to view and/or reply to the conversation.');
$tsitelink = sprintf( $sitelink, $siteurl );
$hsitelink = sprintf( $sitelink, '<a href="' . $siteurl . '">' . $sitename . '</a>');
+ if($moderated) {
+ $tsitelink .= "\n\n" . sprintf( t('Please visit %s to approve or reject this comment.'), z_root() . '/moderate' );
+ $hsitelink .= "<br><br>" . sprintf( t('Please visit %s to approve or reject this comment.'), '<a href="' . z_root() . '/moderate">' . z_root() . '/moderate</a>' );
+ }
+
}
if ($params['type'] == NOTIFY_LIKE) {
@@ -495,13 +504,14 @@ class Enotify {
}
}
- $r = q("insert into notify (hash,xname,url,photo,created,aid,uid,link,parent,seen,ntype,verb,otype)
- values('%s','%s','%s','%s','%s',%d,%d,'%s','%s',%d,%d,'%s','%s')",
+ $r = q("insert into notify (hash,xname,url,photo,created,msg,aid,uid,link,parent,seen,ntype,verb,otype)
+ values('%s','%s','%s','%s','%s','%s',%d,%d,'%s','%s',%d,%d,'%s','%s')",
dbesc($datarray['hash']),
dbesc($datarray['xname']),
dbesc($datarray['url']),
dbesc($datarray['photo']),
dbesc($datarray['created']),
+ dbesc(''), // will fill this in below after the record is created
intval($datarray['aid']),
intval($datarray['uid']),
dbesc($datarray['link']),
diff --git a/Zotlabs/Lib/LDSignatures.php b/Zotlabs/Lib/LDSignatures.php
new file mode 100644
index 000000000..6d7127cde
--- /dev/null
+++ b/Zotlabs/Lib/LDSignatures.php
@@ -0,0 +1,135 @@
+<?php
+
+namespace Zotlabs\Lib;
+
+require_once('library/jsonld/jsonld.php');
+
+class LDSignatures {
+
+
+ static function verify($data,$pubkey) {
+
+ $ohash = self::hash(self::signable_options($data['signature']));
+ $dhash = self::hash(self::signable_data($data));
+
+ $x = rsa_verify($ohash . $dhash,base64_decode($data['signature']['signatureValue']), $pubkey);
+ logger('LD-verify: ' . intval($x));
+
+ return $x;
+ }
+
+ static function dopplesign(&$data,$channel) {
+ // remove for the time being - performance issues
+ // $data['magicEnv'] = self::salmon_sign($data,$channel);
+ return self::sign($data,$channel);
+ }
+
+ static function sign($data,$channel) {
+
+ $options = [
+ 'type' => 'RsaSignature2017',
+ 'nonce' => random_string(64),
+ 'creator' => z_root() . '/channel/' . $channel['channel_address'] . '/public_key_pem',
+ 'created' => datetime_convert('UTC','UTC', 'now', 'Y-m-d\Th:i:s\Z')
+ ];
+
+ $ohash = self::hash(self::signable_options($options));
+ $dhash = self::hash(self::signable_data($data));
+ $options['signatureValue'] = base64_encode(rsa_sign($ohash . $dhash,$channel['channel_prvkey']));
+
+ $signed = array_merge([
+ '@context' => [
+ ACTIVITYSTREAMS_JSONLD_REV,
+ 'https://w3id.org/security/v1' ],
+ ],$options);
+
+ return $signed;
+ }
+
+
+ static function signable_data($data) {
+
+ $newdata = [];
+ if($data) {
+ foreach($data as $k => $v) {
+ if(! in_array($k,[ 'signature' ])) {
+ $newdata[$k] = $v;
+ }
+ }
+ }
+ return json_encode($newdata,JSON_UNESCAPED_SLASHES);
+ }
+
+
+ static function signable_options($options) {
+
+ $newopts = [ '@context' => 'https://w3id.org/identity/v1' ];
+ if($options) {
+ foreach($options as $k => $v) {
+ if(! in_array($k,[ 'type','id','signatureValue' ])) {
+ $newopts[$k] = $v;
+ }
+ }
+ }
+ return json_encode($newopts,JSON_UNESCAPED_SLASHES);
+ }
+
+ static function hash($obj) {
+
+ return hash('sha256',self::normalise($obj));
+ }
+
+ static function normalise($data) {
+ if(is_string($data)) {
+ $data = json_decode($data);
+ }
+
+ if(! is_object($data))
+ return '';
+
+ jsonld_set_document_loader('jsonld_document_loader');
+
+ try {
+ $d = jsonld_normalize($data,[ 'algorithm' => 'URDNA2015', 'format' => 'application/nquads' ]);
+ }
+ catch (\Exception $e) {
+ logger('normalise error:' . print_r($e,true));
+ logger('normalise error: ' . print_r($data,true));
+ }
+
+ return $d;
+ }
+
+ static function salmon_sign($data,$channel) {
+
+ $arr = $data;
+ $data = json_encode($data,JSON_UNESCAPED_SLASHES);
+ $data = base64url_encode($data, false); // do not strip padding
+ $data_type = 'application/activity+json';
+ $encoding = 'base64url';
+ $algorithm = 'RSA-SHA256';
+ $keyhash = base64url_encode(z_root() . '/channel/' . $channel['channel_address']);
+
+ $data = str_replace(array(" ","\t","\r","\n"),array("","","",""),$data);
+
+ // precomputed base64url encoding of data_type, encoding, algorithm concatenated with periods
+
+ $precomputed = '.' . base64url_encode($data_type,false) . '.YmFzZTY0dXJs.UlNBLVNIQTI1Ng==';
+
+ $signature = base64url_encode(rsa_sign($data . $precomputed,$channel['channel_prvkey']));
+
+ return ([
+ 'id' => $arr['id'],
+ 'meData' => $data,
+ 'meDataType' => $data_type,
+ 'meEncoding' => $encoding,
+ 'meAlgorithm' => $algorithm,
+ 'meCreator' => z_root() . '/channel/' . $channel['channel_address'] . '/public_key_pem',
+ 'meSignatureValue' => $signature
+ ]);
+
+ }
+
+
+
+} \ No newline at end of file
diff --git a/Zotlabs/Lib/MarkdownSoap.php b/Zotlabs/Lib/MarkdownSoap.php
new file mode 100644
index 000000000..534ad819f
--- /dev/null
+++ b/Zotlabs/Lib/MarkdownSoap.php
@@ -0,0 +1,103 @@
+<?php
+
+namespace Zotlabs\Lib;
+
+/**
+ * MarkdownSoap
+ * Purify Markdown for storage
+ * $x = new MarkdownSoap($string_to_be_cleansed);
+ * $text = $x->clean();
+ *
+ * What this does:
+ * 1. extracts code blocks and privately escapes them from processing
+ * 2. Run html purifier on the content
+ * 3. put back the code blocks
+ * 4. run htmlspecialchars on the entire content for safe storage
+ *
+ * At render time:
+ * $markdown = \Zotlabs\Lib\MarkdownSoap::unescape($text);
+ * $html = \Michelf\MarkdownExtra::DefaultTransform($markdown);
+ */
+
+
+
+class MarkdownSoap {
+
+ private $token;
+
+ private $str;
+
+ function __construct($s) {
+ $this->str = $s;
+ $this->token = random_string(20);
+ }
+
+
+ function clean() {
+
+ $x = $this->extract_code($this->str);
+
+ $x = $this->purify($x);
+
+ $x = $this->putback_code($x);
+
+ $x = $this->escape($x);
+
+ return $x;
+ }
+
+ function extract_code($s) {
+
+ $text = preg_replace_callback('{
+ (?:\n\n|\A\n?)
+ ( # $1 = the code block -- one or more lines, starting with a space/tab
+ (?>
+ [ ]{'.'4'.'} # Lines must start with a tab or a tab-width of spaces
+ .*\n+
+ )+
+ )
+ ((?=^[ ]{0,'.'4'.'}\S)|\Z) # Lookahead for non-space at line-start, or end of doc
+ }xm',
+ [ $this , 'encode_code' ], $s);
+
+ return $text;
+ }
+
+ function encode_code($matches) {
+ return $this->token . ';' . base64_encode($matches[0]) . ';' ;
+ }
+
+ function decode_code($matches) {
+ return base64_decode($matches[1]);
+ }
+
+ function putback_code($s) {
+ $text = preg_replace_callback('{' . $this->token . '\;(.*?)\;}xm',[ $this, 'decode_code' ], $s);
+ return $text;
+ }
+
+ function purify($s) {
+ $s = $this->protect_autolinks($s);
+ $s = purify_html($s);
+ $s = $this->unprotect_autolinks($s);
+ return $s;
+ }
+
+ function protect_autolinks($s) {
+ $s = preg_replace('/\<(https?\:\/\/)(.*?)\>/','[$1$2]($1$2)',$s);
+ return $s;
+ }
+
+ function unprotect_autolinks($s) {
+ return $s;
+
+ }
+
+ function escape($s) {
+ return htmlspecialchars($s,ENT_QUOTES);
+ }
+
+ static public function unescape($s) {
+ return htmlspecialchars_decode($s,ENT_QUOTES);
+ }
+}
diff --git a/Zotlabs/Lib/NativeWiki.php b/Zotlabs/Lib/NativeWiki.php
index 519102d24..7642dbb3e 100644
--- a/Zotlabs/Lib/NativeWiki.php
+++ b/Zotlabs/Lib/NativeWiki.php
@@ -18,11 +18,18 @@ class NativeWiki {
if($wikis) {
foreach($wikis as &$w) {
+
+ $w['json_allow_cid'] = acl2json($w['allow_cid']);
+ $w['json_allow_gid'] = acl2json($w['allow_gid']);
+ $w['json_deny_cid'] = acl2json($w['deny_cid']);
+ $w['json_deny_gid'] = acl2json($w['deny_gid']);
+
$w['rawName'] = get_iconfig($w, 'wiki', 'rawName');
$w['htmlName'] = escape_tags($w['rawName']);
$w['urlName'] = urlencode(urlencode($w['rawName']));
$w['mimeType'] = get_iconfig($w, 'wiki', 'mimeType');
- $w['lock'] = (($w['item_private'] || $w['allow_cid'] || $w['allow_gid'] || $w['deny_cid'] || $w['deny_gid']) ? true : false);
+ $w['typelock'] = get_iconfig($w, 'wiki', 'typelock');
+ $w['lockstate'] = (($w['allow_cid'] || $w['allow_gid'] || $w['deny_cid'] || $w['deny_gid']) ? 'lock' : 'unlock');
}
}
// TODO: query db for wikis the observer can access. Return with two lists, for read and write access
@@ -75,6 +82,8 @@ class NativeWiki {
$arr['obj_type'] = ACTIVITY_OBJ_WIKI;
$arr['body'] = '[table][tr][td][h1]New Wiki[/h1][/td][/tr][tr][td][zrl=' . $wiki_url . ']' . $wiki['htmlName'] . '[/zrl][/td][/tr][/table]';
+ $arr['public_policy'] = map_scope(\Zotlabs\Access\PermissionLimits::Get($channel['channel_id'],'view_wiki'),true);
+
// Save the wiki name information using iconfig. This is shareable.
if(! set_iconfig($arr, 'wiki', 'rawName', $wiki['rawName'], true)) {
return array('item' => null, 'success' => false);
@@ -82,7 +91,9 @@ class NativeWiki {
if(! set_iconfig($arr, 'wiki', 'mimeType', $wiki['mimeType'], true)) {
return array('item' => null, 'success' => false);
}
-
+
+ set_iconfig($arr,'wiki','typelock',$wiki['typelock'],true);
+
$post = item_store($arr);
$item_id = $post['item_id'];
@@ -96,16 +107,77 @@ class NativeWiki {
}
}
+ function update_wiki($channel_id, $observer_hash, $arr, $acl) {
+
+ $w = self::get_wiki($channel_id, $observer_hash, $arr['resource_id']);
+ $item = $w['wiki'];
+
+ if(! $item) {
+ return array('item' => null, 'success' => false);
+ }
+
+ $x = $acl->get();
+
+ $item['allow_cid'] = $x['allow_cid'];
+ $item['allow_gid'] = $x['allow_gid'];
+ $item['deny_cid'] = $x['deny_cid'];
+ $item['deny_gid'] = $x['deny_gid'];
+ $item['item_private'] = intval($acl->is_private());
+
+ $update_title = false;
+
+ if($item['title'] !== $arr['updateRawName']) {
+ $update_title = true;
+ $item['title'] = $arr['updateRawName'];
+ }
+
+ $update = item_store_update($item);
+
+ $item_id = $update['item_id'];
+
+ // update acl for any existing wiki pages
+
+ q("update item set allow_cid = '%s', allow_gid = '%s', deny_cid = '%s', deny_gid = '%s', item_private = %d where resource_type = 'nwikipage' and resource_id = '%s'",
+ dbesc($item['allow_cid']),
+ dbesc($item['allow_gid']),
+ dbesc($item['deny_cid']),
+ dbesc($item['deny_gid']),
+ dbesc($item['item_private']),
+ dbesc($arr['resource_id'])
+ );
+
+
+ if($update['item_id']) {
+ info( t('Wiki updated successfully'));
+ if($update_title) {
+ // Update the wiki name information using iconfig.
+ if(! set_iconfig($update['item_id'], 'wiki', 'rawName', $arr['updateRawName'], true)) {
+ return array('item' => null, 'success' => false);
+ }
+ }
+ return array('item' => $update['item'], 'item_id' => $update['item_id'], 'success' => $update['success']);
+ }
+ else {
+ return array('item' => null, 'success' => false);
+ }
+ }
+
static public function sync_a_wiki_item($uid,$id,$resource_id) {
- $r = q("SELECT * from item WHERE uid = %d AND ( id = %d OR ( resource_type = '%s' and resource_id = %d )) ",
+ $r = q("SELECT * from item WHERE uid = %d AND ( id = %d OR ( resource_type = '%s' and resource_id = '%s' )) ",
intval($uid),
intval($id),
dbesc(NWIKI_ITEM_RESOURCE_TYPE),
- intval($resource_id)
+ dbesc($resource_id)
);
if($r) {
+ $q = q("select * from item where resource_type = 'nwikipage' and resource_id = '%s'",
+ dbesc($r[0]['resource_type'])
+ );
+ if($q) {
+ $r = array_merge($r,$q);
+ }
xchan_query($r);
$sync_item = fetch_post_tags($r);
build_sync_packet($uid,array('wiki' => array(encode_item($sync_item[0],true))));
@@ -148,13 +220,15 @@ class NativeWiki {
// Get wiki metadata
$rawName = get_iconfig($w, 'wiki', 'rawName');
$mimeType = get_iconfig($w, 'wiki', 'mimeType');
+ $typelock = get_iconfig($w, 'wiki', 'typelock');
return array(
- 'wiki' => $w,
- 'rawName' => $rawName,
+ 'wiki' => $w,
+ 'rawName' => $rawName,
'htmlName' => escape_tags($rawName),
- 'urlName' => urlencode(urlencode($rawName)),
- 'mimeType' => $mimeType
+ 'urlName' => urlencode(urlencode($rawName)),
+ 'mimeType' => $mimeType,
+ 'typelock' => $typelock
);
}
}
diff --git a/Zotlabs/Lib/NativeWikiPage.php b/Zotlabs/Lib/NativeWikiPage.php
index 1467a1cfb..209a5ef3c 100644
--- a/Zotlabs/Lib/NativeWikiPage.php
+++ b/Zotlabs/Lib/NativeWikiPage.php
@@ -21,19 +21,30 @@ class NativeWikiPage {
$sql_extra = item_permissions_sql($channel_id,$observer_hash);
$r = q("select * from item where resource_type = 'nwikipage' and resource_id = '%s' and uid = %d and item_deleted = 0
- $sql_extra group by mid",
+ $sql_extra order by title asc",
dbesc($resource_id),
intval($channel_id)
);
if($r) {
- $items = fetch_post_tags($r,true);
+ $x = [];
+ $y = [];
+
+ foreach($r as $rv) {
+ if(! in_array($rv['mid'],$x)) {
+ $y[] = $rv;
+ $x[] = $rv['mid'];
+ }
+ }
+
+ $items = fetch_post_tags($y,true);
+
foreach($items as $page_item) {
$title = get_iconfig($page_item['id'],'nwikipage','pagetitle',t('(No Title)'));
if(urldecode($title) !== 'Home') {
$pages[] = [
'resource_id' => $resource_id,
'title' => escape_tags($title),
- 'url' => urlencode(urlencode($title)),
+ 'url' => str_replace('%2F','/',urlencode(str_replace('%2F','/',urlencode($title)))),
'link_id' => 'id_' . substr($resource_id, 0, 10) . '_' . $page_item['id']
];
}
@@ -44,17 +55,34 @@ class NativeWikiPage {
}
- static public function create_page($channel_id, $observer_hash, $name, $resource_id) {
+ static public function create_page($channel_id, $observer_hash, $name, $resource_id, $mimetype = 'text/bbcode') {
+
+ logger('mimetype: ' . $mimetype);
+
+ if(! in_array($mimetype,[ 'text/markdown','text/bbcode','text/plain','text/html' ]))
+ $mimetype = 'text/markdown';
$w = Zlib\NativeWiki::get_wiki($channel_id, $observer_hash, $resource_id);
+ if (! $w['wiki']) {
+ return array('content' => null, 'message' => 'Error reading wiki', 'success' => false);
+ }
+
// create an empty activity
$arr = [];
- $arr['uid'] = $channel_id;
- $arr['author_xchan'] = $observer_hash;
+ $arr['uid'] = $channel_id;
+ $arr['author_xchan'] = $observer_hash;
+ $arr['mimetype'] = $mimetype;
+ $arr['title'] = $name;
$arr['resource_type'] = 'nwikipage';
- $arr['resource_id'] = $resource_id;
+ $arr['resource_id'] = $resource_id;
+ $arr['allow_cid'] = $w['wiki']['allow_cid'];
+ $arr['allow_gid'] = $w['wiki']['allow_gid'];
+ $arr['deny_cid'] = $w['wiki']['deny_cid'];
+ $arr['deny_gid'] = $w['wiki']['deny_gid'];
+
+ $arr['public_policy'] = map_scope(\Zotlabs\Access\PermissionLimits::Get($channel_id,'view_wiki'),true);
// We may wish to change this some day.
$arr['item_unpublished'] = 1;
@@ -112,8 +140,14 @@ class NativeWikiPage {
if($ic) {
foreach($ic as $c) {
set_iconfig($c['item_id'],'nwikipage','pagetitle',$pageNewName);
+ $ids[] = $c['item_id'];
}
+ $str_ids = implode(',', $ids);
+ q("update item set title = '%s' where id in ($str_ids)",
+ dbesc($pageNewName)
+ );
+
$page = [
'rawName' => $pageNewName,
'htmlName' => escape_tags($pageNewName),
@@ -146,10 +180,11 @@ class NativeWikiPage {
$content = $item['body'];
return [
- 'content' => json_encode($content),
- 'mimeType' => $w['mimeType'],
- 'message' => '',
- 'success' => true
+ 'content' => $content,
+ 'mimeType' => $w['mimeType'],
+ 'pageMimeType' => $item['mimetype'],
+ 'message' => '',
+ 'success' => true
];
}
@@ -180,7 +215,7 @@ class NativeWikiPage {
$processed ++;
$history[] = [
'revision' => $item['revision'],
- 'date' => datetime_convert('UTC',date_default_timezone_get(),$item['created']),
+ 'date' => datetime_convert('UTC',date_default_timezone_get(),$item['edited']),
'name' => $item['author']['xchan_name'],
'title' => get_iconfig($item,'nwikipage','commit_msg')
];
@@ -225,6 +260,7 @@ class NativeWikiPage {
}
$sql_extra = item_permissions_sql($channel_id,$observer_hash);
+
if($revision == (-1))
$sql_extra .= " order by revision desc ";
elseif($revision)
@@ -277,6 +313,7 @@ class NativeWikiPage {
}
$sql_extra = item_permissions_sql($channel_id,$observer_hash);
+
$sql_extra .= " order by revision desc ";
$r = null;
@@ -295,48 +332,21 @@ class NativeWikiPage {
return null;
}
-
-
- static public function prepare_content($s) {
-
- $text = preg_replace_callback('{
- (?:\n\n|\A\n?)
- ( # $1 = the code block -- one or more lines, starting with a space/tab
- (?>
- [ ]{'.'4'.'} # Lines must start with a tab or a tab-width of spaces
- .*\n+
- )+
- )
- ((?=^[ ]{0,'.'4'.'}\S)|\Z) # Lookahead for non-space at line-start, or end of doc
- }xm',
- 'self::nwiki_prepare_content_callback', $s);
-
- return $text;
- }
-
- static public function nwiki_prepare_content_callback($matches) {
- $codeblock = $matches[1];
-
- $codeblock = htmlspecialchars($codeblock, ENT_NOQUOTES, UTF8, false);
- return "\n\n" . $codeblock ;
- }
-
-
-
static public function save_page($arr) {
- $pageUrlName = ((array_key_exists('pageUrlName',$arr)) ? $arr['pageUrlName'] : '');
- $content = ((array_key_exists('content',$arr)) ? purify_html(Zlib\NativeWikiPage::prepare_content($arr['content'])) : '');
- $resource_id = ((array_key_exists('resource_id',$arr)) ? $arr['resource_id'] : '');
+ $pageUrlName = ((array_key_exists('pageUrlName',$arr)) ? $arr['pageUrlName'] : '');
+ $content = ((array_key_exists('content',$arr)) ? $arr['content'] : '');
+ $resource_id = ((array_key_exists('resource_id',$arr)) ? $arr['resource_id'] : '');
$observer_hash = ((array_key_exists('observer_hash',$arr)) ? $arr['observer_hash'] : '');
$channel_id = ((array_key_exists('channel_id',$arr)) ? $arr['channel_id'] : 0);
- $revision = ((array_key_exists('revision',$arr)) ? $arr['revision'] : 0);
+ $revision = ((array_key_exists('revision',$arr)) ? $arr['revision'] : 0);
$w = Zlib\NativeWiki::get_wiki($channel_id, $observer_hash, $resource_id);
if (!$w['wiki']) {
return array('message' => t('Error reading wiki'), 'success' => false);
}
+
// fetch the most recently saved revision.
@@ -345,6 +355,8 @@ class NativeWikiPage {
return array('message' => t('Page not found'), 'success' => false);
}
+ $mimetype = $item['mimetype'];
+
// change just the fields we need to change to create a revision;
unset($item['id']);
@@ -355,6 +367,7 @@ class NativeWikiPage {
$item['author_xchan'] = $observer_hash;
$item['revision'] = (($arr['revision']) ? intval($arr['revision']) + 1 : intval($item['revision']) + 1);
$item['edited'] = datetime_convert();
+ $item['mimetype'] = $mimetype;
if($item['iconfig'] && is_array($item['iconfig']) && count($item['iconfig'])) {
for($x = 0; $x < count($item['iconfig']); $x ++) {
@@ -522,6 +535,29 @@ class NativeWikiPage {
}
return $s;
}
+
+ static public function render_page_history($arr) {
+
+ $pageUrlName = ((array_key_exists('pageUrlName', $arr)) ? $arr['pageUrlName'] : '');
+ $resource_id = ((array_key_exists('resource_id', $arr)) ? $arr['resource_id'] : '');
+
+ $pageHistory = self::page_history([
+ 'channel_id' => \App::$profile_uid,
+ 'observer_hash' => get_observer_hash(),
+ 'resource_id' => $resource_id,
+ 'pageUrlName' => $pageUrlName
+ ]);
+
+ return replace_macros(get_markup_template('nwiki_page_history.tpl'), array(
+ '$pageHistory' => $pageHistory['history'],
+ '$permsWrite' => $arr['permsWrite'],
+ '$name_lbl' => t('Name'),
+ '$msg_label' => t('Message','wiki_history')
+ ));
+
+ }
+
+
/**
* Replace the instances of the string [toc] with a list element that will be populated by
@@ -578,10 +614,13 @@ class NativeWikiPage {
}
static public function get_file_ext($arr) {
- if($arr['mimeType'] == 'text/bbcode')
+ if($arr['mimetype'] === 'text/bbcode')
return '.bb';
- else
+ elseif($arr['mimetype'] === 'text/markdown')
return '.md';
+ elseif($arr['mimetype'] === 'text/plain')
+ return '.txt';
+
}
// This function is derived from
diff --git a/Zotlabs/Lib/PConfig.php b/Zotlabs/Lib/PConfig.php
index d70697fbc..2a0b18aac 100644
--- a/Zotlabs/Lib/PConfig.php
+++ b/Zotlabs/Lib/PConfig.php
@@ -20,11 +20,12 @@ class PConfig {
if(is_null($uid) || $uid === false)
return false;
- if(! array_key_exists($uid, \App::$config))
- \App::$config[$uid] = array();
-
if(! is_array(\App::$config)) {
- btlogger('App::$config not an array: ' . $uid);
+ btlogger('App::$config not an array');
+ }
+
+ if(! array_key_exists($uid, \App::$config)) {
+ \App::$config[$uid] = array();
}
if(! is_array(\App::$config[$uid])) {
@@ -119,7 +120,7 @@ class PConfig {
$dbvalue = ((is_array($value)) ? serialize($value) : $value);
$dbvalue = ((is_bool($dbvalue)) ? intval($dbvalue) : $dbvalue);
- if(get_pconfig($uid, $family, $key) === false) {
+ if(self::Get($uid, $family, $key) === false) {
if(! array_key_exists($uid, \App::$config))
\App::$config[$uid] = array();
if(! array_key_exists($family, \App::$config[$uid]))
diff --git a/Zotlabs/Lib/SConfig.php b/Zotlabs/Lib/SConfig.php
new file mode 100644
index 000000000..ca0d133b2
--- /dev/null
+++ b/Zotlabs/Lib/SConfig.php
@@ -0,0 +1,25 @@
+<?php
+
+namespace Zotlabs\Lib;
+
+// account configuration storage is built on top of the under-utilised xconfig
+
+class SConfig {
+
+ static public function Load($server_id) {
+ return XConfig::Load('s_' . $server_id);
+ }
+
+ static public function Get($server_id,$family,$key,$default = false) {
+ return XConfig::Get('s_' . $server_id,$family,$key, $default);
+ }
+
+ static public function Set($server_id,$family,$key,$value) {
+ return XConfig::Set('s_' . $server_id,$family,$key,$value);
+ }
+
+ static public function Delete($server_id,$family,$key) {
+ return XConfig::Delete('s_' . $server_id,$family,$key);
+ }
+
+}
diff --git a/Zotlabs/Lib/System.php b/Zotlabs/Lib/System.php
index 306c90f4a..c3e11eb6a 100644
--- a/Zotlabs/Lib/System.php
+++ b/Zotlabs/Lib/System.php
@@ -19,6 +19,9 @@ class System {
static public function get_project_version() {
if(is_array(\App::$config) && is_array(\App::$config['system']) && \App::$config['system']['hide_version'])
return '';
+ if(is_array(\App::$config) && is_array(\App::$config['system']) && array_key_exists('std_version',\App::$config['system']))
+ return \App::$config['system']['std_version'];
+
return self::get_std_version();
}
@@ -54,12 +57,15 @@ class System {
return 'https://github.com/redmatrix/hubzilla';
}
+ static public function get_server_role() {
+ return 'pro';
+ }
- static public function get_server_role() {
- if(is_array(\App::$config) && is_array(\App::$config['system']) && \App::$config['system']['server_role'])
- return \App::$config['system']['server_role'];
- return 'standard';
+ static public function get_zot_revision() {
+ $x = [ 'revision' => ZOT_REVISION ];
+ call_hooks('zot_revision',$x);
+ return $x['revision'];
}
static public function get_std_version() {
@@ -72,11 +78,8 @@ class System {
if(get_directory_realm() != DIRECTORY_REALM)
return true;
-
- foreach(['hubzilla','zap'] as $t) {
- if(stristr($p,$t))
- return true;
- }
+ if(in_array(strtolower($p),['hubzilla','zap','red']))
+ return true;
return false;
}
}
diff --git a/Zotlabs/Lib/Techlevels.php b/Zotlabs/Lib/Techlevels.php
index 6a8c36fb3..380901678 100644
--- a/Zotlabs/Lib/Techlevels.php
+++ b/Zotlabs/Lib/Techlevels.php
@@ -7,12 +7,12 @@ class Techlevels {
static public function levels() {
$techlevels = [
- '0' => t('Beginner/Basic'),
- '1' => t('Novice - not skilled but willing to learn'),
- '2' => t('Intermediate - somewhat comfortable'),
- '3' => t('Advanced - very comfortable'),
- '4' => t('Expert - I can write computer code'),
- '5' => t('Wizard - I probably know more than you do')
+ '0' => t('0. Beginner/Basic'),
+ '1' => t('1. Novice - not skilled but willing to learn'),
+ '2' => t('2. Intermediate - somewhat comfortable'),
+ '3' => t('3. Advanced - very comfortable'),
+ '4' => t('4. Expert - I can write computer code'),
+ '5' => t('5. Wizard - I probably know more than you do')
];
return $techlevels;
}
diff --git a/Zotlabs/Lib/ThreadItem.php b/Zotlabs/Lib/ThreadItem.php
index 0ee8e6680..d916ce2c1 100644
--- a/Zotlabs/Lib/ThreadItem.php
+++ b/Zotlabs/Lib/ThreadItem.php
@@ -29,6 +29,7 @@ class ThreadItem {
private $visiting = false;
private $channel = null;
private $display_mode = 'normal';
+ private $reload = '';
public function __construct($data) {
@@ -82,7 +83,8 @@ class ThreadItem {
$dropping = false;
$star = false;
$isstarred = "unstarred fa-star-o";
- $indent = '';
+ $is_comment = false;
+ $is_item = false;
$osparkle = '';
$total_children = $this->count_descendants();
$unseen_comments = (($item['real_uid']) ? 0 : $this->count_unseen_descendants());
@@ -100,10 +102,13 @@ class ThreadItem {
if($item['author']['xchan_network'] === 'rss')
$shareable = true;
+
$mode = $conv->get_mode();
+ $edlink = (($item['item_type'] == ITEM_TYPE_CARD) ? 'card_edit' : 'editpost');
+
if(local_channel() && $observer['xchan_hash'] === $item['author_xchan'])
- $edpost = array(z_root()."/editpost/".$item['id'], t("Edit"));
+ $edpost = array(z_root() . '/' . $edlink . '/' . $item['id'], t('Edit'));
else
$edpost = false;
@@ -136,7 +141,7 @@ class ThreadItem {
$filer = ((($conv->get_profile_owner() == local_channel()) && (! array_key_exists('real_uid',$item))) ? t("Save to Folder") : false);
$profile_avatar = $item['author']['xchan_photo_m'];
- $profile_link = chanlink_url($item['author']['xchan_url']);
+ $profile_link = chanlink_hash($item['author_xchan']);
$profile_name = $item['author']['xchan_name'];
$location = format_location($item);
@@ -152,7 +157,7 @@ class ThreadItem {
$response_verbs[] = 'attendyes';
$response_verbs[] = 'attendno';
$response_verbs[] = 'attendmaybe';
- if($this->is_commentable()) {
+ if($this->is_commentable() && $observer) {
$isevent = true;
$attend = array( t('I will attend'), t('I will not attend'), t('I might attend'));
}
@@ -163,7 +168,7 @@ class ThreadItem {
$response_verbs[] = 'agree';
$response_verbs[] = 'disagree';
$response_verbs[] = 'abstain';
- if($this->is_commentable()) {
+ if($this->is_commentable() && $observer) {
$conlabels = array( t('I agree'), t('I disagree'), t('I abstain'));
$canvote = true;
}
@@ -183,7 +188,7 @@ class ThreadItem {
$like_list = ((x($conv_responses['like'],$item['mid'])) ? $conv_responses['like'][$item['mid'] . '-l'] : '');
if (count($like_list) > MAX_LIKERS) {
$like_list_part = array_slice($like_list, 0, MAX_LIKERS);
- array_push($like_list_part, '<a href="#" data-toggle="modal" data-target="#likeModal-' . $this->get_id() . '"><b>' . t('View all') . '</b></a>');
+ array_push($like_list_part, '<a class="dropdown-item" href="#" data-toggle="modal" data-target="#likeModal-' . $this->get_id() . '"><b>' . t('View all') . '</b></a>');
} else {
$like_list_part = '';
}
@@ -195,7 +200,7 @@ class ThreadItem {
$dislike_button_label = tt('Dislike','Dislikes',$dislike_count,'noun');
if (count($dislike_list) > MAX_LIKERS) {
$dislike_list_part = array_slice($dislike_list, 0, MAX_LIKERS);
- array_push($dislike_list_part, '<a href="#" data-toggle="modal" data-target="#dislikeModal-' . $this->get_id() . '"><b>' . t('View all') . '</b></a>');
+ array_push($dislike_list_part, '<a class="dropdown-item" href="#" data-toggle="modal" data-target="#dislikeModal-' . $this->get_id() . '"><b>' . t('View all') . '</b></a>');
} else {
$dislike_list_part = '';
}
@@ -232,7 +237,7 @@ class ThreadItem {
}
}
else {
- $indent = 'comment';
+ $is_comment = true;
}
@@ -250,8 +255,6 @@ class ThreadItem {
);
}
- $server_role = get_config('system','server_role');
-
$has_bookmarks = false;
if(is_array($item['term'])) {
foreach($item['term'] as $t) {
@@ -264,7 +267,7 @@ class ThreadItem {
if(($item['obj_type'] === ACTIVITY_OBJ_EVENT) && $conv->get_profile_owner() == local_channel())
$has_event = true;
- if($this->is_commentable()) {
+ if($this->is_commentable() && $observer) {
$like = array( t("I like this \x28toggle\x29"), t("like"));
$dislike = array( t("I don't like this \x28toggle\x29"), t("dislike"));
}
@@ -276,13 +279,13 @@ class ThreadItem {
$keep_reports = intval(get_config('system','expire_delivery_reports'));
if($keep_reports === 0)
- $keep_reports = 30;
+ $keep_reports = 10;
if((! get_config('system','disable_dreport')) && strcmp(datetime_convert('UTC','UTC',$item['created']),datetime_convert('UTC','UTC',"now - $keep_reports days")) > 0)
$dreport = t('Delivery Report');
if(strcmp(datetime_convert('UTC','UTC',$item['created']),datetime_convert('UTC','UTC','now - 12 hours')) > 0)
- $indent .= ' shiny';
+ $is_new = true;
localize_item($item);
@@ -310,7 +313,8 @@ class ThreadItem {
$tmp_item = array(
'template' => $this->get_template(),
- 'mode' => $mode,
+ 'mode' => $mode,
+ 'item_type' => intval($item['item_type']),
'type' => implode("",array_slice(explode("/",$item['verb']),-1)),
'body' => $body['html'],
'tags' => $body['tags'],
@@ -337,7 +341,6 @@ class ThreadItem {
'profile_url' => $profile_link,
'thread_action_menu' => thread_action_menu($item,$conv->get_mode()),
'thread_author_menu' => thread_author_menu($item,$conv->get_mode()),
- 'item_photo_menu' => item_photo_menu($item),
'dreport' => $dreport,
'name' => $profile_name,
'thumb' => $profile_avatar,
@@ -361,7 +364,8 @@ class ThreadItem {
'attend_title' => t('Attendance Options'),
'vote_label' => t('Vote'),
'vote_title' => t('Voting Options'),
- 'indent' => $indent,
+ 'is_comment' => $is_comment,
+ 'is_new' => $is_new,
'owner_url' => $this->get_owner_url(),
'owner_photo' => $this->get_owner_photo(),
'owner_name' => $this->get_owner_name(),
@@ -370,7 +374,7 @@ class ThreadItem {
'has_tags' => $has_tags,
'reactions' => $this->reactions,
// Item toolbar buttons
- 'emojis' => (($this->is_toplevel() && $this->is_commentable() && feature_enabled($conv->get_profile_owner(),'emojis')) ? '1' : ''),
+ 'emojis' => (($this->is_toplevel() && $this->is_commentable() && $observer && feature_enabled($conv->get_profile_owner(),'emojis')) ? '1' : ''),
'like' => $like,
'dislike' => ((feature_enabled($conv->get_profile_owner(),'dislike')) ? $dislike : ''),
'share' => $share,
@@ -407,9 +411,10 @@ class ThreadItem {
'showlike' => $showlike,
'showdislike' => $showdislike,
'comment' => $this->get_comment_box($indent),
- 'previewing' => ($conv->is_preview() ? ' preview ' : ''),
+ 'previewing' => ($conv->is_preview() ? true : false ),
+ 'preview_lbl' => t('This is an unsaved preview'),
'wait' => t('Please wait'),
- 'submid' => str_replace(['+','='], ['',''], base64_encode(substr($item['mid'],0,32))),
+ 'submid' => str_replace(['+','='], ['',''], base64_encode($item['mid'])),
'thread_level' => $thread_level
);
@@ -480,6 +485,14 @@ class ThreadItem {
return $this->threaded;
}
+ public function set_reload($val) {
+ $this->reload = $val;
+ }
+
+ public function get_reload() {
+ return $this->reload;
+ }
+
public function set_commentable($val) {
$this->commentable = $val;
foreach($this->get_children() as $child)
@@ -713,11 +726,10 @@ class ThreadItem {
call_hooks('comment_buttons',$arr);
$comment_buttons = $arr['comment_buttons'];
-
$comment_box = replace_macros($template,array(
'$return_path' => '',
'$threaded' => $this->is_threaded(),
- '$jsreload' => '', //(($conv->get_mode() === 'display') ? $_SESSION['return_url'] : ''),
+ '$jsreload' => $conv->reload,
'$type' => (($conv->get_mode() === 'channel') ? 'wall-comment' : 'net-comment'),
'$id' => $this->get_id(),
'$parent' => $this->get_id(),
@@ -735,15 +747,21 @@ class ThreadItem {
'$edquote' => t('Quote'),
'$edcode' => t('Code'),
'$edimg' => t('Image'),
+ '$edatt' => t('Attach File'),
'$edurl' => t('Insert Link'),
'$edvideo' => t('Video'),
'$preview' => t('Preview'), // ((feature_enabled($conv->get_profile_owner(),'preview')) ? t('Preview') : ''),
'$indent' => $indent,
+ '$can_upload' => (perm_is_allowed($conv->get_profile_owner(),get_observer_hash(),'write_storage') && $conv->is_uploadable()),
'$feature_encrypt' => ((feature_enabled($conv->get_profile_owner(),'content_encrypt')) ? true : false),
'$encrypt' => t('Encrypt text'),
'$cipher' => $conv->get_cipher(),
- '$sourceapp' => \App::$sourcename
-
+ '$sourceapp' => \App::$sourcename,
+ '$observer' => get_observer_hash(),
+ '$anoncomments' => (($conv->get_mode() === 'channel' && perm_is_allowed($conv->get_profile_owner(),'','post_comments')) ? true : false),
+ '$anonname' => [ 'anonname', t('Your full name (required)') ],
+ '$anonmail' => [ 'anonmail', t('Your email address (required)') ],
+ '$anonurl' => [ 'anonurl', t('Your website URL (optional)') ]
));
return $comment_box;
@@ -767,7 +785,7 @@ class ThreadItem {
return;
if($this->is_toplevel() && ($this->get_data_value('author_xchan') != $this->get_data_value('owner_xchan'))) {
- $this->owner_url = chanlink_url($this->data['owner']['xchan_url']);
+ $this->owner_url = chanlink_hash($this->data['owner']['xchan_hash']);
$this->owner_photo = $this->data['owner']['xchan_photo_m'];
$this->owner_name = $this->data['owner']['xchan_name'];
$this->wall_to_wall = true;
diff --git a/Zotlabs/Lib/ThreadStream.php b/Zotlabs/Lib/ThreadStream.php
index beb626f31..d7a898704 100644
--- a/Zotlabs/Lib/ThreadStream.php
+++ b/Zotlabs/Lib/ThreadStream.php
@@ -18,18 +18,21 @@ class ThreadStream {
private $observer = null;
private $writable = false;
private $commentable = false;
+ private $uploadable = false;
private $profile_owner = 0;
private $preview = false;
private $prepared_item = '';
+ public $reload = '';
private $cipher = 'aes256';
// $prepared_item is for use by alternate conversation structures such as photos
// wherein we've already prepared a top level item which doesn't look anything like
// a normal "post" item
- public function __construct($mode, $preview, $prepared_item = '') {
+ public function __construct($mode, $preview, $uploadable, $prepared_item = '') {
$this->set_mode($mode);
$this->preview = $preview;
+ $this->uploadable = $uploadable;
$this->prepared_item = $prepared_item;
$c = ((local_channel()) ? get_pconfig(local_channel(),'system','default_cipher') : '');
if($c)
@@ -55,11 +58,17 @@ class ThreadStream {
$this->profile_owner = \App::$profile['profile_uid'];
$this->writable = perm_is_allowed($this->profile_owner,$ob_hash,'post_comments');
break;
+ case 'cards':
+ $this->profile_owner = \App::$profile['profile_uid'];
+ $this->writable = perm_is_allowed($this->profile_owner,$ob_hash,'post_comments');
+ $this->reload = $_SESSION['return_url'];
+ break;
case 'display':
// in this mode we set profile_owner after initialisation (from conversation()) and then
// pull some trickery which allows us to re-invoke this function afterward
// it's an ugly hack so @FIXME
$this->writable = perm_is_allowed($this->profile_owner,$ob_hash,'post_comments');
+ $this->uploadable = false;
break;
case 'page':
$this->profile_owner = \App::$profile['uid'];
@@ -91,6 +100,11 @@ class ThreadStream {
return $this->commentable;
}
+ public function is_uploadable() {
+ return $this->uploadable;
+ }
+
+
/**
* Check if page is a preview
*/
@@ -158,7 +172,7 @@ class ThreadStream {
if(intval($item->get_data_value('item_nocomment'))) {
$item->set_commentable(false);
}
- elseif(($this->observer) && (! $item->is_commentable())) {
+ elseif(! $item->is_commentable()) {
if((array_key_exists('owner',$item->data)) && intval($item->data['owner']['abook_self']))
$item->set_commentable(perm_is_allowed($this->profile_owner,$ob_hash,'post_comments'));
else