aboutsummaryrefslogtreecommitdiffstats
path: root/include
diff options
context:
space:
mode:
Diffstat (limited to 'include')
-rw-r--r--include/api_auth.php79
-rw-r--r--include/attach.php31
-rw-r--r--include/auth.php7
-rw-r--r--include/bbcode.php10
-rw-r--r--include/channel.php223
-rw-r--r--include/config.php16
-rw-r--r--include/connections.php20
-rw-r--r--include/contact_widgets.php72
-rw-r--r--include/conversation.php100
-rw-r--r--include/crypto.php14
-rw-r--r--include/features.php9
-rw-r--r--include/feedutils.php136
-rw-r--r--include/follow.php28
-rw-r--r--include/help.php68
-rw-r--r--include/html2bbcode.php3
-rw-r--r--include/import.php14
-rwxr-xr-xinclude/items.php99
-rw-r--r--include/nav.php168
-rw-r--r--include/network.php78
-rwxr-xr-xinclude/oembed.php11
-rw-r--r--include/perm_upgrade.php3
-rw-r--r--include/photo/photo_driver.php56
-rw-r--r--include/photos.php81
-rw-r--r--include/security.php7
-rw-r--r--include/taxonomy.php78
-rw-r--r--include/text.php108
-rw-r--r--include/xchan.php80
-rw-r--r--include/zid.php77
-rw-r--r--include/zot.php335
29 files changed, 1658 insertions, 353 deletions
diff --git a/include/api_auth.php b/include/api_auth.php
index e5cd7cab3..0818fa54b 100644
--- a/include/api_auth.php
+++ b/include/api_auth.php
@@ -7,6 +7,8 @@
function api_login(&$a){
$record = null;
+ $remote_auth = false;
+ $sigblock = null;
require_once('include/oauth.php');
@@ -33,21 +35,66 @@ function api_login(&$a){
// workarounds for HTTP-auth in CGI mode
- if(x($_SERVER,'REDIRECT_REMOTE_USER')) {
- $userpass = base64_decode(substr($_SERVER["REDIRECT_REMOTE_USER"],6)) ;
- if(strlen($userpass)) {
- list($name, $password) = explode(':', $userpass);
- $_SERVER['PHP_AUTH_USER'] = $name;
- $_SERVER['PHP_AUTH_PW'] = $password;
+ foreach([ 'REDIRECT_REMOTE_USER', 'HTTP_AUTHORIZATION' ] as $head) {
+
+ /* Basic authentication */
+
+ if(array_key_exists($head,$_SERVER) && substr(trim($_SERVER[$head]),0,5) === 'Basic') {
+ $userpass = @base64_decode(substr(trim($_SERVER[$head]),6)) ;
+ if(strlen($userpass)) {
+ list($name, $password) = explode(':', $userpass);
+ $_SERVER['PHP_AUTH_USER'] = $name;
+ $_SERVER['PHP_AUTH_PW'] = $password;
+ }
+ break;
}
- }
- if(x($_SERVER,'HTTP_AUTHORIZATION')) {
- $userpass = base64_decode(substr($_SERVER["HTTP_AUTHORIZATION"],6)) ;
- if(strlen($userpass)) {
- list($name, $password) = explode(':', $userpass);
- $_SERVER['PHP_AUTH_USER'] = $name;
- $_SERVER['PHP_AUTH_PW'] = $password;
+ /* Signature authentication */
+
+ if(array_key_exists($head,$_SERVER) && substr(trim($_SERVER[$head]),0,9) === 'Signature') {
+ if($head !== 'HTTP_AUTHORIZATION') {
+ $_SERVER['HTTP_AUTHORIZATION'] = $_SERVER[$head];
+ continue;
+ }
+
+ $sigblock = \Zotlabs\Web\HTTPSig::parse_sigheader($_SERVER[$head]);
+ if($sigblock) {
+ $keyId = $sigblock['keyId'];
+ if($keyId) {
+ $r = q("select * from hubloc where hubloc_addr = '%s' limit 1",
+ dbesc($keyId)
+ );
+ if($r) {
+ $c = channelx_by_hash($r[0]['hubloc_hash']);
+ if($c) {
+ $a = q("select * from account where account_id = %d limit 1",
+ intval($c['channel_account_id'])
+ );
+ if($a) {
+ $record = [ 'channel' => $c, 'account' => $a[0] ];
+ $channel_login = $c['channel_id'];
+ }
+ else {
+ continue;
+ }
+ }
+ else {
+ continue;
+ }
+ }
+ else {
+ continue;
+ }
+
+ if($record) {
+ $verified = \Zotlabs\Web\HTTPSig::verify('',$record['channel']['channel_pubkey']);
+ if(! ($verified && $verified['header_signed'] && $verified['header_valid'])) {
+ $record = null;
+ }
+ break;
+ }
+ }
+ }
}
}
@@ -64,8 +111,6 @@ function api_login(&$a){
}
}
-
-
if($record['account']) {
authenticate_success($record['account']);
@@ -85,8 +130,8 @@ function api_login(&$a){
}
-function retry_basic_auth() {
- header('WWW-Authenticate: Basic realm="Hubzilla"');
+function retry_basic_auth($method = 'Basic') {
+ header('WWW-Authenticate: ' . $method . ' realm="Hubzilla"');
header('HTTP/1.0 401 Unauthorized');
echo('This api requires login');
killme();
diff --git a/include/attach.php b/include/attach.php
index 50de3ac53..78e133b03 100644
--- a/include/attach.php
+++ b/include/attach.php
@@ -423,6 +423,7 @@ function attach_store($channel, $observer_hash, $options = '', $arr = null) {
$hash = (($arr && $arr['hash']) ? $arr['hash'] : null);
$upload_path = (($arr && $arr['directory']) ? $arr['directory'] : '');
$visible = (($arr && $arr['visible']) ? $arr['visible'] : '');
+ $notify = (($arr && $arr['notify']) ? $arr['notify'] : '');
$observer = array();
@@ -459,6 +460,7 @@ function attach_store($channel, $observer_hash, $options = '', $arr = null) {
// By default remove $src when finished
$remove_when_processed = true;
+ $import_replace = false;
if($options === 'import') {
$src = $arr['src'];
@@ -475,6 +477,9 @@ function attach_store($channel, $observer_hash, $options = '', $arr = null) {
if($arr['preserve_original'])
$remove_when_processed = false;
+ if($arr['replace'])
+ $import_replace = true;
+
// if importing a directory, just do it now and go home - we're done.
if(array_key_exists('is_dir',$arr) && intval($arr['is_dir'])) {
@@ -616,8 +621,10 @@ function attach_store($channel, $observer_hash, $options = '', $arr = null) {
dbesc($folder_hash)
);
if($r) {
- $overwrite = get_pconfig($channel_id,'system','overwrite_dup_files');
+ $overwrite = (($import_replace || get_pconfig($channel_id,'system','overwrite_dup_files')) ? true : false);
if(($overwrite) || ($options === 'import')) {
+ if(! array_key_exists('edited',$arr))
+ $arr['edited'] = datetime_convert();
$options = 'replace';
$existing_id = $x[0]['id'];
$existing_size = intval($x[0]['filesize']);
@@ -709,7 +716,7 @@ function attach_store($channel, $observer_hash, $options = '', $arr = null) {
$os_relpath = ltrim($os_relpath,'/');
$os_path = $os_relpath;
- $display_path = $pathname . '/' . $filename;
+ $display_path = ltrim($pathname . '/' . $filename,'/');
if($src)
@file_put_contents($os_basepath . $os_relpath,@file_get_contents($src));
@@ -886,6 +893,12 @@ function attach_store($channel, $observer_hash, $options = '', $arr = null) {
build_sync_packet($channel['channel_id'],array('file' => array($sync)));
}
+ if($notify) {
+ //$cloudPath = z_root() . '/cloud/' . $channel['channel_address'] . '/' . $r['0']['display_path'];
+ //$object = get_file_activity_object($channel['channel_id'], $r['0']['hash'], $cloudPath);
+ //file_activity($channel['channel_id'], $object, $r['0']['allow_cid'], $r['0']['allow_gid'], $r['0']['deny_cid'], $r['0']['deny_gid'], 'post', $notify);
+ }
+
return $ret;
}
@@ -1299,8 +1312,8 @@ function attach_delete($channel_id, $resource, $is_photo = 0) {
return;
}
- $cloudpath = get_parent_cloudpath($channel_id, $channel_address, $resource);
- $object = get_file_activity_object($channel_id, $resource, $cloudpath);
+ $url = get_cloudpath($channel_id, $channel_address, $resource);
+ $object = get_file_activity_object($channel_id, $resource, $url);
// If resource is a directory delete everything in the directory recursive
if(intval($r[0]['is_dir'])) {
@@ -1441,7 +1454,7 @@ function get_cloudpath($arr) {
* @param string $attachHash
* @return string with the full folder path
*/
-function get_parent_cloudpath($channel_id, $channel_name, $attachHash) {
+function get_cloud_url($channel_id, $channel_name, $attachHash) {
$parentFullPath = '';
// build directory tree
$parentHash = $attachHash;
@@ -1453,9 +1466,9 @@ function get_parent_cloudpath($channel_id, $channel_name, $attachHash) {
}
} while ($parentHash);
- $parentFullPath = z_root() . '/cloud/' . $channel_name . '/' . $parentFullPath;
+ $url = z_root() . '/cloud/' . $channel_name . '/' . $parentFullPath . find_filename_by_hash($channel_id, $attachHash);
- return $parentFullPath;
+ return $url;
}
/**
@@ -1719,14 +1732,14 @@ function file_activity($channel_id, $object, $allow_cid, $allow_gid, $deny_cid,
* @param string $hash
* @param string $cloudpath
*/
-function get_file_activity_object($channel_id, $hash, $cloudpath) {
+function get_file_activity_object($channel_id, $hash, $url) {
$x = q("SELECT creator, filename, filetype, filesize, revision, folder, os_storage, is_photo, is_dir, flags, created, edited, allow_cid, allow_gid, deny_cid, deny_gid FROM attach WHERE uid = %d AND hash = '%s' LIMIT 1",
intval($channel_id),
dbesc($hash)
);
- $url = rawurlencode($cloudpath . $x[0]['filename']);
+ $url = rawurlencode($url);
$links = array();
$links[] = array(
diff --git a/include/auth.php b/include/auth.php
index c7be69583..78be32bf4 100644
--- a/include/auth.php
+++ b/include/auth.php
@@ -83,7 +83,7 @@ function account_verify_password($login, $pass) {
if(($email_verify) && ($register_policy == REGISTER_OPEN) && ($account['account_flags'] & ACCOUNT_UNVERIFIED)) {
logger('email verification required for ' . $login);
- return null;
+ return ( [ 'reason' => 'unvalidated' ] );
}
if(($account['account_flags'] == ACCOUNT_OK)
@@ -259,7 +259,10 @@ else {
}
else {
$verify = account_verify_password($_POST['username'], $_POST['password']);
- if($verify) {
+ if($verify && array_key_exists('reason',$verify) && $verify['reason'] === 'unvalidated') {
+ notice( t('Email validation is incomplete. Please check your email.'));
+ }
+ elseif($verify) {
$atoken = $verify['xchan'];
$channel = $verify['channel'];
$account = App::$account = $verify['account'];
diff --git a/include/bbcode.php b/include/bbcode.php
index 9f9b5c5e1..9a2a6eb9b 100644
--- a/include/bbcode.php
+++ b/include/bbcode.php
@@ -327,11 +327,16 @@ function bb_ShareAttributes($match) {
if ($avatar != "")
$headline .= '<a href="' . zid($profile) . '" ><img src="' . $avatar . '" alt="' . $author . '" height="32" width="32" /></a>';
+ if(strpos($link,'/cards/'))
+ $type = t('card');
+ else
+ $type = t('post');
+
// Bob Smith wrote the following post 2 hours ago
$fmt = sprintf( t('%1$s wrote the following %2$s %3$s'),
'<a href="' . zid($profile) . '" >' . $author . '</a>',
- '<a href="' . zid($link) . '" >' . t('post') . '</a>',
+ '<a href="' . zid($link) . '" >' . $type . '</a>',
$reldate
);
@@ -1255,6 +1260,9 @@ function bbcode($Text, $preserve_nl = false, $tryoembed = true, $cache = false)
$Text = preg_replace_callback("/\[pre\](.*?)\[\/pre\]/ism", 'bb_unspacefy_and_trim', $Text);
}
+ // replace escaped links in code= blocks
+ $Text = str_replace('%eY9-!','http', $Text);
+
$Text = preg_replace('/\[\&amp\;([#a-z0-9]+)\;\]/', '&$1;', $Text);
// fix any escaped ampersands that may have been converted into links
diff --git a/include/channel.php b/include/channel.php
index 49da57fd6..d7116ce28 100644
--- a/include/channel.php
+++ b/include/channel.php
@@ -7,6 +7,7 @@ require_once('include/zot.php');
require_once('include/crypto.php');
require_once('include/menu.php');
require_once('include/perm_upgrade.php');
+require_once('include/photo/photo_driver.php');
/**
* @brief Called when creating a new channel.
@@ -52,7 +53,7 @@ function identity_check_service_class($account_id) {
*
* This action is pluggable.
* We're currently only checking for an empty name or one that exceeds our
- * storage limit (255 chars). 255 chars is probably going to create a mess on
+ * storage limit (191 chars). 191 chars is probably going to create a mess on
* some pages.
* Plugins can set additional policies such as full name requirements, character
* sets, multi-byte length, etc.
@@ -67,7 +68,7 @@ function validate_channelname($name) {
if (! $name)
return t('Empty name');
- if (strlen($name) > 255)
+ if (mb_strlen($name) > 191)
return t('Name too long');
$arr = ['name' => $name];
@@ -273,6 +274,19 @@ function create_identity($arr) {
return $ret;
}
+ $a = q("select * from account where account_id = %d",
+ intval($arr['account_id'])
+ );
+
+ $photo_type = null;
+
+ $z = [ 'account' => $a[0], 'channel' => $r[0], 'photo_url' => '' ];
+ call_hooks('create_channel_photo',$z);
+
+ if($z['photo_url']) {
+ $photo_type = import_channel_photo_from_url($z['photo_url'],$arr['account_id'],$r[0]['channel_id']);
+ }
+
if($role_permissions && array_key_exists('limits',$role_permissions))
$perm_limits = $role_permissions['limits'];
else
@@ -318,6 +332,7 @@ function create_identity($arr) {
'xchan_guid' => $guid,
'xchan_guid_sig' => $sig,
'xchan_pubkey' => $key['pubkey'],
+ 'xchan_photo_mimetype' => (($photo_type) ? $photo_type : 'image/png'),
'xchan_photo_l' => z_root() . "/photo/profile/l/{$newuid}",
'xchan_photo_m' => z_root() . "/photo/profile/m/{$newuid}",
'xchan_photo_s' => z_root() . "/photo/profile/s/{$newuid}",
@@ -454,6 +469,194 @@ function create_identity($arr) {
return $ret;
}
+
+function change_channel_keys($channel) {
+
+ $ret = array('success' => false);
+
+ $stored = [];
+
+ $key = new_keypair(4096);
+
+ $sig = base64url_encode(rsa_sign($channel['channel_guid'],$key['prvkey']));
+ $hash = make_xchan_hash($channel['channel_guid'],$sig);
+
+ $stored['old_guid'] = $channel['channel_guid'];
+ $stored['old_guid_sig'] = $channel['channel_guid_sig'];
+ $stored['old_key'] = $channel['channel_pubkey'];
+ $stored['old_hash'] = $channel['channel_hash'];
+
+ $stored['new_key'] = $key['pubkey'];
+ $stored['new_sig'] = base64url_encode(rsa_sign($key['pubkey'],$channel['channel_prvkey']));
+
+ // Save this info for the notifier to collect
+
+ set_pconfig($channel['channel_id'],'system','keychange',$stored);
+
+ $r = q("update channel set channel_prvkey = '%s', channel_pubkey = '%s', channel_guid_sig = '%s', channel_hash = '%s' where channel_id = %d",
+ dbesc($key['prvkey']),
+ dbesc($key['pubkey']),
+ dbesc($sig),
+ dbesc($hash),
+ intval($channel['channel_id'])
+ );
+ if(! $r) {
+ return $ret;
+ }
+
+ $r = q("select * from channel where channel_id = %d",
+ intval($channel['channel_id'])
+ );
+
+ if(! $r) {
+ $ret['message'] = t('Unable to retrieve modified identity');
+ return $ret;
+ }
+
+ $modified = $r[0];
+
+ $h = q("select * from hubloc where hubloc_hash = '%s' and hubloc_url = '%s' ",
+ dbesc($stored['old_hash']),
+ dbesc(z_root())
+ );
+
+ if($h) {
+ foreach($h as $hv) {
+ $hv['hubloc_guid_sig'] = $sig;
+ $hv['hubloc_hash'] = $hash;
+ $hv['hubloc_url_sig'] = base64url_encode(rsa_sign(z_root(),$modifed['channel_prvkey']));
+ hubloc_store_lowlevel($hv);
+ }
+ }
+
+ $x = q("select * from xchan where xchan_hash = '%s' ",
+ dbesc($stored['old_hash'])
+ );
+
+ $check = q("select * from xchan where xchan_hash = '%s'",
+ dbesc($hash)
+ );
+
+ if(($x) && (! $check)) {
+ $oldxchan = $x[0];
+ foreach($x as $xv) {
+ $xv['xchan_guid_sig'] = $sig;
+ $xv['xchan_hash'] = $hash;
+ $xv['xchan_pubkey'] = $key['pubkey'];
+ xchan_store_lowlevel($xv);
+ $newxchan = $xv;
+ }
+ }
+
+ build_sync_packet($channel['channel_id'], [ 'keychange' => $stored ]);
+
+ $a = q("select * from abook where abook_xchan = '%s' and abook_self = 1",
+ dbesc($stored['old_hash'])
+ );
+
+ if($a) {
+ q("update abook set abook_xchan = '%s' where abook_id = %d",
+ dbesc($hash),
+ intval($a[0]['abook_id'])
+ );
+ }
+
+ xchan_change_key($oldxchan,$newxchan,$stored);
+
+ Zotlabs\Daemon\Master::Summon(array('Notifier', 'keychange', $channel['channel_id']));
+
+ $ret['success'] = true;
+ return $ret;
+}
+
+function channel_change_address($channel,$new_address) {
+
+ $ret = array('success' => false);
+
+ $old_address = $channel['channel_address'];
+
+ if($new_address === 'sys') {
+ $ret['message'] = t('Reserved nickname. Please choose another.');
+ return $ret;
+ }
+
+ if(check_webbie(array($new_address)) !== $new_address) {
+ $ret['message'] = t('Nickname has unsupported characters or is already being used on this site.');
+ return $ret;
+ }
+
+ $r = q("update channel set channel_address = '%s' where channel_id = %d",
+ dbesc($new_address),
+ intval($channel['channel_id'])
+ );
+ if(! $r) {
+ return $ret;
+ }
+
+ $r = q("select * from channel where channel_id = %d",
+ intval($channel['channel_id'])
+ );
+
+ if(! $r) {
+ $ret['message'] = t('Unable to retrieve modified identity');
+ return $ret;
+ }
+
+ $r = q("update xchan set xchan_addr = '%s' where xchan_hash = '%s'",
+ dbesc($new_address . '@' . App::get_hostname()),
+ dbesc($channel['channel_hash'])
+ );
+
+ $h = q("select * from hubloc where hubloc_hash = '%s' and hubloc_url = '%s' ",
+ dbesc($channel['channel_hash']),
+ dbesc(z_root())
+ );
+
+ if($h) {
+ foreach($h as $hv) {
+ if($hv['hubloc_primary']) {
+ q("update hubloc set hubloc_primary = 0 where hubloc_id = %d",
+ intval($hv['hubloc_id'])
+ );
+ }
+ q("update hubloc set hubloc_deleted = 1 where hubloc_id = %d",
+ intval($hv['hubloc_id'])
+ );
+
+ unset($hv['hubloc_id']);
+ $hv['hubloc_addr'] = $new_address . '@' . App::get_hostname();
+ hubloc_store_lowlevel($hv);
+ }
+ }
+
+ // fix apps which were stored with the actual name rather than a macro
+
+ $r = q("select * from app where app_channel = %d and app_system = 1",
+ intval($channel['channel_id'])
+ );
+ if($r) {
+ foreach($r as $rv) {
+ $replace = preg_replace('/([\=\/])(' . $old_address . ')($|[\%\/])/ism','$1' . $new_address . '$3',$rv['app_url']);
+ if($replace != $rv['app_url']) {
+ q("update app set app_url = '%s' where id = %d",
+ dbesc($replace),
+ intval($rv['id'])
+ );
+ }
+ }
+ }
+
+ Zotlabs\Daemon\Master::Summon(array('Notifier', 'refresh_all', $channel['channel_id']));
+
+ $ret['success'] = true;
+ return $ret;
+}
+
+
+
+
+
+
/**
* @brief Set default channel to be used on login.
*
@@ -1170,7 +1373,6 @@ function profile_sidebar($profile, $block = 0, $show_connect = true, $zcard = fa
? trim(substr($profile['channel_name'],0,strpos($profile['channel_name'],' '))) : $profile['channel_name']);
$lastname = (($firstname === $profile['channel_name']) ? '' : trim(substr($profile['channel_name'],strlen($firstname))));
- // @fixme move this to the diaspora plugin itself
$contact_block = contact_block();
@@ -1428,13 +1630,15 @@ function get_my_address() {
function zid_init() {
$tmp_str = get_my_address();
if(validate_email($tmp_str)) {
- Zotlabs\Daemon\Master::Summon(array('Gprobe',bin2hex($tmp_str)));
$arr = array('zid' => $tmp_str, 'url' => App::$cmd);
call_hooks('zid_init',$arr);
if(! local_channel()) {
$r = q("select * from hubloc where hubloc_addr = '%s' order by hubloc_connected desc limit 1",
dbesc($tmp_str)
);
+ if(! $r) {
+ Zotlabs\Daemon\Master::Summon(array('Gprobe',bin2hex($tmp_str)));
+ }
if($r && remote_channel() && remote_channel() === $r[0]['hubloc_hash'])
return;
logger('zid_init: not authenticated. Invoking reverse magic-auth for ' . $tmp_str);
@@ -1443,7 +1647,7 @@ function zid_init() {
$query = str_replace(array('?zid=','&zid='),array('?rzid=','&rzid='),$query);
$dest = '/' . urlencode($query);
if($r && ($r[0]['hubloc_url'] != z_root()) && (! strstr($dest,'/magic')) && (! strstr($dest,'/rmagic'))) {
- goaway($r[0]['hubloc_url'] . '/magic' . '?f=&rev=1&dest=' . z_root() . $dest);
+ goaway($r[0]['hubloc_url'] . '/magic' . '?f=&rev=1&owa=1&dest=' . z_root() . $dest);
}
else
logger('zid_init: no hubloc found.');
@@ -2104,7 +2308,7 @@ function profile_store_lowlevel($arr) {
// It is the caller's responsibility to confirm the requestor's intent and
// authorisation to do this.
-function account_remove($account_id,$local = true,$unset_session=true) {
+function account_remove($account_id,$local = true,$unset_session = true) {
logger('account_remove: ' . $account_id);
@@ -2149,13 +2353,12 @@ function account_remove($account_id,$local = true,$unset_session=true) {
if ($unset_session) {
- unset($_SESSION['authenticated']);
- unset($_SESSION['uid']);
- notice( sprintf(t("User '%s' deleted"),$account_email) . EOL);
+ App::$session->nuke();
+ notice( sprintf(t('Account \'%s\' deleted'),$account_email) . EOL);
goaway(z_root());
}
- return $r;
+ return $r;
}
/**
diff --git a/include/config.php b/include/config.php
index 0b0e639ab..0be791715 100644
--- a/include/config.php
+++ b/include/config.php
@@ -126,3 +126,19 @@ function set_iconfig(&$item, $family, $key, $value, $sharing = false) {
function del_iconfig(&$item, $family, $key) {
return Zlib\IConfig::Delete($item, $family, $key);
}
+
+function load_sconfig($server_id) {
+ Zlib\SConfig::Load($server_id);
+}
+
+function get_sconfig($server_id, $family, $key, $default = false) {
+ return Zlib\SConfig::Get($server_id, $family, $key, $default);
+}
+
+function set_sconfig($server_id, $family, $key, $value) {
+ return Zlib\SConfig::Set($server_id, $family, $key, $value);
+}
+
+function del_sconfig($server_id, $family, $key) {
+ return Zlib\SConfig::Delete($server_id, $family, $key);
+}
diff --git a/include/connections.php b/include/connections.php
index 8df795190..60bce018e 100644
--- a/include/connections.php
+++ b/include/connections.php
@@ -115,7 +115,7 @@ function vcard_from_xchan($xchan, $observer = null, $mode = '') {
App::$profile_uid = $xchan['channel_id'];
$url = (($observer)
- ? z_root() . '/magic?f=&dest=' . $xchan['xchan_url'] . '&addr=' . $xchan['xchan_addr']
+ ? z_root() . '/magic?f=&owa=1&dest=' . $xchan['xchan_url'] . '&addr=' . $xchan['xchan_addr']
: $xchan['xchan_url']
);
@@ -369,7 +369,8 @@ function contact_remove($channel_id, $abook_id) {
return false;
- $r = q("select * from item where author_xchan = '%s' and uid = %d",
+ $r = q("select * from item where (owner_xchan = '%s' or author_xchan = '%s') and uid = %d",
+ dbesc($abook['abook_xchan']),
dbesc($abook['abook_xchan']),
intval($channel_id)
);
@@ -629,13 +630,20 @@ function get_vcard_array($vc,$id) {
if($vc->ADR) {
foreach($vc->ADR as $adr) {
$type = (($adr['TYPE']) ? vcard_translate_type((string)$adr['TYPE']) : '');
- $adrs[] = [
+ $entry = [
'type' => $type,
'address' => $adr->getParts()
];
- $last_entry = end($adrs);
- if($last_entry && is_array($adrs[$last_entry]['address']))
- array_walk($adrs[$last_entry]['address'],'array_escape_tags');
+
+ if(is_array($entry['address'])) {
+ array_walk($entry['address'],'array_escape_tags');
+ }
+ else {
+ $entry['address'] = (string) escape_tags($entry['address']);
+ }
+
+ $adrs[] = $entry;
+
}
}
diff --git a/include/contact_widgets.php b/include/contact_widgets.php
index 8f76bb4bc..9cc9f0baf 100644
--- a/include/contact_widgets.php
+++ b/include/contact_widgets.php
@@ -65,6 +65,10 @@ function categories_widget($baseurl,$selected = '') {
if(! feature_enabled(App::$profile['profile_uid'],'categories'))
return '';
+ require_once('include/security.php');
+
+ $sql_extra = item_permissions_sql(App::$profile['profile_uid']);
+
$item_normal = item_normal();
$terms = array();
@@ -77,6 +81,7 @@ function categories_widget($baseurl,$selected = '') {
and item.owner_xchan = '%s'
and item.item_wall = 1
$item_normal
+ $sql_extra
order by term.term asc",
intval(App::$profile['profile_uid']),
intval(TERM_CATEGORY),
@@ -100,7 +105,53 @@ function categories_widget($baseurl,$selected = '') {
return '';
}
-function common_friends_visitor_widget($profile_uid) {
+function cardcategories_widget($baseurl,$selected = '') {
+
+ if(! feature_enabled(App::$profile['profile_uid'],'categories'))
+ return '';
+
+ $sql_extra = item_permissions_sql(App::$profile['profile_uid']);
+
+ $item_normal = "and item.item_hidden = 0 and item.item_type = 6 and item.item_deleted = 0
+ and item.item_unpublished = 0 and item.item_delayed = 0 and item.item_pending_remove = 0
+ and item.item_blocked = 0 ";
+
+ $terms = array();
+ $r = q("select distinct(term.term)
+ from term join item on term.oid = item.id
+ where item.uid = %d
+ and term.uid = item.uid
+ and term.ttype = %d
+ and term.otype = %d
+ and item.owner_xchan = '%s'
+ $item_normal
+ $sql_extra
+ order by term.term asc",
+ intval(App::$profile['profile_uid']),
+ intval(TERM_CATEGORY),
+ intval(TERM_OBJ_POST),
+ dbesc(App::$profile['channel_hash'])
+ );
+ if($r && count($r)) {
+ foreach($r as $rr)
+ $terms[] = array('name' => $rr['term'], 'selected' => (($selected == $rr['term']) ? 'selected' : ''));
+
+ return replace_macros(get_markup_template('categories_widget.tpl'),array(
+ '$title' => t('Categories'),
+ '$desc' => '',
+ '$sel_all' => (($selected == '') ? 'selected' : ''),
+ '$all' => t('Everything'),
+ '$terms' => $terms,
+ '$base' => $baseurl,
+
+ ));
+ }
+ return '';
+}
+
+
+
+function common_friends_visitor_widget($profile_uid,$cnt = 25) {
if(local_channel() == $profile_uid)
return;
@@ -113,19 +164,20 @@ function common_friends_visitor_widget($profile_uid) {
require_once('include/socgraph.php');
$t = count_common_friends($profile_uid,$observer_hash);
+
if(! $t)
return;
- $r = common_friends($profile_uid,$observer_hash,0,5,true);
-
+ $r = common_friends($profile_uid,$observer_hash,0,$cnt,true);
+
return replace_macros(get_markup_template('remote_friends_common.tpl'), array(
- '$desc' => sprintf( tt("%d connection in common", "%d connections in common", $t), $t),
- '$base' => z_root(),
- '$uid' => $profile_uid,
- '$cid' => $observer,
- '$linkmore' => (($t > 5) ? 'true' : ''),
- '$more' => t('show more'),
- '$items' => $r
+ '$desc' => t('Common Connections'),
+ '$base' => z_root(),
+ '$uid' => $profile_uid,
+ '$cid' => $observer,
+ '$linkmore' => (($t > $cnt) ? 'true' : ''),
+ '$more' => sprintf( t('View all %d common connections'), $t),
+ '$items' => $r
));
};
diff --git a/include/conversation.php b/include/conversation.php
index 23220b390..f395b2cbe 100644
--- a/include/conversation.php
+++ b/include/conversation.php
@@ -295,7 +295,7 @@ function localize_item(&$item){
}
$plink = '[zrl=' . $obj['plink'] . ']' . $post_type . '[/zrl]';
- $parsedobj = parse_xml_string($xmlhead.$item['obj']);
+// $parsedobj = parse_xml_string($xmlhead.$item['obj']);
$tag = sprintf('#[zrl=%s]%s[/zrl]', $parsedobj->id, $parsedobj->content);
$item['body'] = sprintf( t('%1$s tagged %2$s\'s %3$s with %4$s'), $author, $objauthor, $plink, $tag );
@@ -312,7 +312,7 @@ function localize_item(&$item){
$xmlhead="<"."?xml version='1.0' encoding='UTF-8' ?".">";
- $obj = parse_xml_string($xmlhead.$item['obj']);
+// $obj = parse_xml_string($xmlhead.$item['obj']);
if(strlen($obj->id)) {
$r = q("select * from item where mid = '%s' and uid = %d limit 1",
dbesc($obj->id),
@@ -464,9 +464,11 @@ function conversation($items, $mode, $update, $page_mode = 'traditional', $prepa
$profile_owner = 0;
$page_writeable = false;
$live_update_div = '';
+ $jsreload = '';
$preview = (($page_mode === 'preview') ? true : false);
$previewing = (($preview) ? ' preview ' : '');
+ $preview_lbl = t('This is an unsaved preview');
if ($mode === 'network') {
@@ -516,6 +518,16 @@ function conversation($items, $mode, $update, $page_mode = 'traditional', $prepa
}
}
+ elseif ($mode === 'cards') {
+ $profile_owner = App::$profile['profile_uid'];
+ $page_writeable = ($profile_owner == local_channel());
+ $live_update_div = '<div id="live-cards"></div>' . "\r\n"
+ . "<script> var profile_uid = " . App::$profile['profile_uid']
+ . "; var netargs = '?f='; var profile_page = " . App::$pager['page'] . "; </script>\r\n";
+ $jsreload = $_SESSION['return_url'];
+ }
+
+
elseif ($mode === 'display') {
$profile_owner = local_channel();
$page_writeable = false;
@@ -549,6 +561,19 @@ function conversation($items, $mode, $update, $page_mode = 'traditional', $prepa
if (! feature_enabled($profile_owner,'multi_delete'))
$page_dropping = false;
+ $uploading = true;
+
+ if($profile_owner > 0) {
+ $owner_channel = channelx_by_n($profile_owner);
+ if($owner_channel['channel_allow_cid'] || $owner_channel['channel_allow_gid']
+ || $owner_channel['channel_deny_cid'] || $owner_channel['channel_deny_gid']) {
+ $uploading = false;
+ }
+ }
+ else {
+ $uploading = false;
+ }
+
$channel = App::get_channel();
$observer = App::get_observer();
@@ -678,12 +703,19 @@ function conversation($items, $mode, $update, $page_mode = 'traditional', $prepa
if(strcmp(datetime_convert('UTC','UTC',$item['created']),datetime_convert('UTC','UTC','now - 12 hours')) > 0)
$is_new = true;
+ $conv_link_mid = (($mode == 'moderate') ? $item['parent_mid'] : $item['mid']);
+
+ $conv_link = (($item['item_type'] == ITEM_TYPE_CARD) ? $item['plink'] : z_root() . '/display/' . gen_link_id($conv_link_mid));
+
+
$tmp_item = array(
'template' => $tpl,
'toplevel' => 'toplevel_item',
+ 'item_type' => intval($item['item_type']),
'mode' => $mode,
'approve' => t('Approve'),
'delete' => t('Delete'),
+ 'preview_lbl' => $preview_lbl,
'id' => (($preview) ? 'P0' : $item['item_id']),
'linktitle' => sprintf( t('View %s\'s profile @ %s'), $profile_name, $profile_url),
'profile_url' => $profile_link,
@@ -731,7 +763,7 @@ function conversation($items, $mode, $update, $page_mode = 'traditional', $prepa
'like' => '',
'dislike' => '',
'comment' => '',
- 'conv' => (($preview) ? '' : array('href'=> z_root() . '/display/' . gen_link_id($item['mid']), 'title'=> t('View in context'))),
+ 'conv' => (($preview) ? '' : array('href'=> $conv_link, 'title'=> t('View in context'))),
'previewing' => $previewing,
'wait' => t('Please wait'),
'thread_level' => 1,
@@ -751,7 +783,7 @@ function conversation($items, $mode, $update, $page_mode = 'traditional', $prepa
// Normal View
// logger('conv: items: ' . print_r($items,true));
- $conv = new Zotlabs\Lib\ThreadStream($mode, $preview, $prepared_item);
+ $conv = new Zotlabs\Lib\ThreadStream($mode, $preview, $uploading, $prepared_item);
// In the display mode we don't have a profile owner.
@@ -793,6 +825,10 @@ function conversation($items, $mode, $update, $page_mode = 'traditional', $prepa
$item_object->set_template('conv_list.tpl');
$item_object->set_display_mode('list');
}
+ if($page_mode === 'cards') {
+ $item_object->set_reload($jsreload);
+ }
+
}
}
@@ -1290,6 +1326,11 @@ function status_editor($a, $x, $popup = false) {
if(! $cipher)
$cipher = 'aes256';
+ if(array_key_exists('catsenabled',$x))
+ $catsenabled = $x['catsenabled'];
+ else
+ $catsenabled = ((feature_enabled($x['profile_uid'], 'categories') && (! $webpage)) ? 'categories' : '');
+
// avoid illegal offset errors
if(! array_key_exists('permissions',$x))
$x['permissions'] = [ 'allow_cid' => '', 'allow_gid' => '', 'deny_cid' => '', 'deny_gid' => '' ];
@@ -1302,10 +1343,14 @@ function status_editor($a, $x, $popup = false) {
call_hooks('jot_networks', $jotnets);
}
+ $sharebutton = (x($x,'button') ? $x['button'] : t('Share'));
+ $placeholdtext = (x($x,'content_label') ? $x['content_label'] : $sharebutton);
+
$o .= replace_macros($tpl, array(
'$return_path' => ((x($x, 'return_path')) ? $x['return_path'] : App::$query_string),
'$action' => z_root() . '/item',
- '$share' => (x($x,'button') ? $x['button'] : t('Share')),
+ '$share' => $sharebutton,
+ '$placeholdtext' => $placeholdtext,
'$webpage' => $webpage,
'$placeholdpagetitle' => ((x($x,'ptlabel')) ? $x['ptlabel'] : t('Page link name')),
'$pagetitle' => (x($x,'pagetitle') ? $x['pagetitle'] : ''),
@@ -1334,7 +1379,7 @@ function status_editor($a, $x, $popup = false) {
'$clearloc' => $clearloc,
'$title' => ((x($x, 'title')) ? htmlspecialchars($x['title'], ENT_COMPAT,'UTF-8') : ''),
'$placeholdertitle' => ((x($x, 'placeholdertitle')) ? $x['placeholdertitle'] : t('Title (optional)')),
- '$catsenabled' => ((feature_enabled($x['profile_uid'], 'categories') && (! $webpage)) ? 'categories' : ''),
+ '$catsenabled' => $catsenabled,
'$category' => ((x($x, 'category')) ? $x['category'] : ''),
'$placeholdercategory' => t('Categories (optional, comma-separated list)'),
'$permset' => t('Permission settings'),
@@ -1441,6 +1486,8 @@ function conv_sort($arr, $order) {
usort($parents,'sort_thr_created');
elseif(stristr($order,'commented'))
usort($parents,'sort_thr_commented');
+ elseif(stristr($order,'updated'))
+ usort($parents,'sort_thr_updated');
elseif(stristr($order,'ascending'))
usort($parents,'sort_thr_created_rev');
@@ -1482,6 +1529,12 @@ function sort_thr_commented($a,$b) {
return strcmp($b['commented'],$a['commented']);
}
+function sort_thr_updated($a,$b) {
+ $indexa = (($a['changed'] > $a['edited']) ? $a['changed'] : $a['edited']);
+ $indexb = (($b['changed'] > $b['edited']) ? $b['changed'] : $b['edited']);
+ return strcmp($indexb,$indexa);
+}
+
function find_thread_parent_index($arr,$x) {
foreach($arr as $k => $v)
if($v['id'] == $x['parent'])
@@ -1573,7 +1626,6 @@ function network_tabs() {
$conv_active = '';
$spam_active = '';
$postord_active = '';
- $public_active = '';
if(x($_GET,'new')) {
$new_active = 'active';
@@ -1595,16 +1647,11 @@ function network_tabs() {
$spam_active = 'active';
}
- if(x($_GET,'fh')) {
- $public_active = 'active';
- }
-
if (($new_active == '')
&& ($starred_active == '')
&& ($conv_active == '')
&& ($search_active == '')
- && ($spam_active == '')
- && ($public_active == '')) {
+ && ($spam_active == '')) {
$no_active = 'active';
}
@@ -1622,17 +1669,6 @@ function network_tabs() {
// tabs
$tabs = array();
- $disable_discover_tab = get_config('system','disable_discover_tab') || get_config('system','disable_discover_tab') === false;
-
- if(! $disable_discover_tab) {
- $tabs[] = array(
- 'label' => t('Discover'),
- 'url' => z_root() . '/' . $cmd . '?f=&fh=1' ,
- 'sel' => $public_active,
- 'title' => t('Imported public streams'),
- );
- }
-
$tabs[] = array(
'label' => t('Commented Order'),
'url'=>z_root() . '/' . $cmd . '?f=&order=comment' . ((x($_GET,'cid')) ? '&cid=' . $_GET['cid'] : '') . ((x($_GET,'gid')) ? '&gid=' . $_GET['gid'] : ''),
@@ -1820,7 +1856,8 @@ function profile_tabs($a, $is_owner = false, $nickname = null){
require_once('include/menu.php');
$has_bookmarks = menu_list_count(local_channel(),'',MENU_BOOKMARK) + menu_list_count(local_channel(),'',MENU_SYSTEM|MENU_BOOKMARK);
- if ($is_owner && $has_bookmarks) {
+
+ if($is_owner && $has_bookmarks) {
$tabs[] = array(
'label' => t('Bookmarks'),
'url' => z_root() . '/bookmarks',
@@ -1831,6 +1868,17 @@ function profile_tabs($a, $is_owner = false, $nickname = null){
);
}
+ if(feature_enabled($uid,'cards')) {
+ $tabs[] = array(
+ 'label' => t('Cards'),
+ 'url' => z_root() . '/cards/' . $nickname,
+ 'sel' => ((argv(0) == 'cards') ? 'active' : ''),
+ 'title' => t('View Cards'),
+ 'id' => 'cards-tab',
+ 'icon' => 'list'
+ );
+ }
+
if($has_webpages && feature_enabled($uid,'webpages')) {
$tabs[] = array(
'label' => t('Webpages'),
@@ -1841,7 +1889,7 @@ function profile_tabs($a, $is_owner = false, $nickname = null){
'icon' => 'newspaper-o'
);
}
-
+
if ($p['view_wiki']) {
if(feature_enabled($uid,'wiki') && (get_account_techlevel($account_id) > 3)) {
diff --git a/include/crypto.php b/include/crypto.php
index 2c5545e9b..622add4dc 100644
--- a/include/crypto.php
+++ b/include/crypto.php
@@ -148,6 +148,7 @@ function other_encapsulate($data,$pubkey,$alg) {
// compromised by state actors and evidence is mounting that this has
// already happened.
+ $result = [ 'encrypted' => true ];
$key = openssl_random_pseudo_bytes(256);
$iv = openssl_random_pseudo_bytes(256);
$result['data'] = base64url_encode($fn($data,$key,$iv),true);
@@ -185,11 +186,24 @@ function crypto_methods() {
}
+function signing_methods() {
+
+
+ $r = [ 'sha256' ];
+ call_hooks('signing_methods',$r);
+ return $r;
+
+}
+
+
function aes_encapsulate($data,$pubkey) {
if(! $pubkey)
logger('aes_encapsulate: no key. data: ' . $data);
$key = openssl_random_pseudo_bytes(32);
$iv = openssl_random_pseudo_bytes(16);
+
+ $result = [ 'encrypted' => true ];
+
$result['data'] = base64url_encode(AES256CBC_encrypt($data,$key,$iv),true);
// log the offending call so we can track it down
if(! openssl_public_encrypt($key,$k,$pubkey)) {
diff --git a/include/features.php b/include/features.php
index f32dbe82e..f84c9cb05 100644
--- a/include/features.php
+++ b/include/features.php
@@ -118,6 +118,15 @@ function get_features($filtered = true) {
],
[
+ 'cards',
+ t('Cards'),
+ t('Create personal planning cards'),
+ false,
+ get_config('feature_lock','cards'),
+ feature_level('cards',1),
+ ],
+
+ [
'nav_channel_select',
t('Navigation Channel Select'),
t('Change channels directly from within the navigation dropdown menu'),
diff --git a/include/feedutils.php b/include/feedutils.php
index 07cb79340..217da8188 100644
--- a/include/feedutils.php
+++ b/include/feedutils.php
@@ -255,7 +255,7 @@ function get_atom_elements($feed, $item, &$author) {
$author['author_is_feed'] = false;
$rawauthor = $feed->get_feed_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'author');
- logger('rawauthor: ' . print_r($rawauthor, true));
+ //logger('rawauthor: ' . print_r($rawauthor, true));
}
else {
@@ -519,7 +519,7 @@ function get_atom_elements($feed, $item, &$author) {
// turn Mastodon content warning into a #nsfw hashtag
if($mastodon && $summary) {
- $res['body'] .= "\n\n#ContentWarning\n";
+ $res['body'] = $summary . "\n\n" . $res['body'] . "\n\n#ContentWarning\n";
}
@@ -811,6 +811,7 @@ function feed_get_reshare(&$res,$item) {
}
$attach = $share['links'];
+
if($attach) {
foreach($attach as $att) {
if($att['rel'] === 'alternate') {
@@ -845,6 +846,10 @@ function feed_get_reshare(&$res,$item) {
}
}
+ if((! $body) && ($share['alternate'])) {
+ $body = $share['alternate'];
+ }
+
$res['body'] = "[share author='" . urlencode($share['author']) .
"' profile='" . $share['profile'] .
"' avatar='" . $share['avatar'] .
@@ -895,6 +900,41 @@ function encode_rel_links($links) {
return $o;
}
+
+function process_feed_tombstones($feed,$importer,$contact,$pass) {
+
+ $arr_deleted = [];
+
+ $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
+ if(is_array($del_entries) && count($del_entries) && $pass != 2) {
+ foreach($del_entries as $dentry) {
+ if(isset($dentry['attribs']['']['ref'])) {
+ $arr_deleted[] = normalise_id($dentry['attribs']['']['ref']);
+ }
+ }
+ }
+
+ if($arr_deleted && is_array($contact)) {
+ foreach($arr_deleted as $mid) {
+ $r = q("SELECT * from item where mid = '%s' and author_xchan = '%s' and uid = %d limit 1",
+ dbesc($mid),
+ dbesc($contact['xchan_hash']),
+ intval($importer['channel_id'])
+ );
+
+ if($r) {
+ $item = $r[0];
+
+ if(! intval($item['item_deleted'])) {
+ logger('deleting item ' . $item['id'] . ' mid=' . $item['mid'], LOGGER_DEBUG);
+ drop_item($item['id'],false);
+ }
+ }
+ }
+ }
+}
+
+
/**
* @brief Process atom feed and update anything/everything we might need to update.
*
@@ -950,43 +990,11 @@ function consume_feed($xml, $importer, &$contact, $pass = 0) {
$permalink = $feed->get_permalink();
- // Check at the feed level for updated contact name and/or photo
- // process any deleted entries
+ // Check at the feed level for tombstones
- $del_entries = $feed->get_feed_tags(NAMESPACE_TOMB, 'deleted-entry');
- if(is_array($del_entries) && count($del_entries) && $pass != 2) {
- foreach($del_entries as $dentry) {
- $deleted = false;
- if(isset($dentry['attribs']['']['ref'])) {
- $mid = normalise_id($dentry['attribs']['']['ref']);
- $deleted = true;
- if(isset($dentry['attribs']['']['when'])) {
- $when = $dentry['attribs']['']['when'];
- $when = datetime_convert('UTC','UTC', $when, 'Y-m-d H:i:s');
- }
- else
- $when = datetime_convert('UTC','UTC','now','Y-m-d H:i:s');
- }
+ process_feed_tombstones($feed,$importer,$contact,$pass);
- if($deleted && is_array($contact)) {
- $r = q("SELECT * from item where mid = '%s' and author_xchan = '%s' and uid = %d limit 1",
- dbesc($mid),
- dbesc($contact['xchan_hash']),
- intval($importer['channel_id'])
- );
-
- if($r) {
- $item = $r[0];
-
- if(! intval($item['item_deleted'])) {
- logger('deleting item ' . $item['id'] . ' mid=' . $item['mid'], LOGGER_DEBUG);
- drop_item($item['id'],false);
- }
- }
- }
- }
- }
// Now process the feed
@@ -1028,6 +1036,13 @@ function consume_feed($xml, $importer, &$contact, $pass = 0) {
if(! $datarray['mid'])
continue;
+
+ $item_parent_mid = q("select parent_mid from item where mid = '%s' and uid = %d limit 1",
+ dbesc($parent_mid),
+ intval($importer['channel_id'])
+ );
+
+
// This probably isn't an appropriate default but we're about to change it
// if it's wrong.
@@ -1079,7 +1094,7 @@ function consume_feed($xml, $importer, &$contact, $pass = 0) {
$datarray['owner_xchan'] = $contact['xchan_hash'];
- $r = q("SELECT id, edited FROM item WHERE mid = '%s' AND uid = %d LIMIT 1",
+ $r = q("SELECT id, edited, author_xchan, item_deleted FROM item WHERE mid = '%s' AND uid = %d LIMIT 1",
dbesc($datarray['mid']),
intval($importer['channel_id'])
);
@@ -1088,6 +1103,15 @@ function consume_feed($xml, $importer, &$contact, $pass = 0) {
// Update content if 'updated' changes
if($r) {
+ if(activity_match($datarray['verb'],ACTIVITY_DELETE)
+ && $datarray['author_xchan'] === $r[0]['author_xchan']) {
+ if(! intval($r[0]['item_deleted'])) {
+ logger('deleting item ' . $r[0]['id'] . ' mid=' . $datarray['mid'], LOGGER_DEBUG);
+ drop_item($r[0]['id'],false);
+ }
+ continue;
+ }
+
if((x($datarray,'edited') !== false)
&& (datetime_convert('UTC','UTC',$datarray['edited']) !== $r[0]['edited'])) {
@@ -1123,18 +1147,12 @@ function consume_feed($xml, $importer, &$contact, $pass = 0) {
$datarray['parent_mid'] = $pmid;
}
}
- if(! $pmid) {
- $x = q("select parent_mid from item where mid = '%s' and uid = %d limit 1",
- dbesc($parent_mid),
- intval($importer['channel_id'])
- );
-
- if($x) {
- logger('find_parent: matched in-reply-to: ' . $parent_mid, LOGGER_DEBUG);
- $pmid = $x[0]['parent_mid'];
- $datarray['parent_mid'] = $pmid;
- }
+ if(($item_parent_mid) && (! $pmid)) {
+ logger('find_parent: matched in-reply-to: ' . $parent_mid, LOGGER_DEBUG);
+ $pmid = $item_parent_mid[0]['parent_mid'];
+ $datarray['parent_mid'] = $pmid;
}
+
if((! $pmid) && $parent_link !== '') {
$f = feed_conversation_fetch($importer,$contact,$parent_link);
if($f) {
@@ -1156,6 +1174,7 @@ function consume_feed($xml, $importer, &$contact, $pass = 0) {
);
if($x) {
+ $item_parent_mid = $x;
$pmid = $x[0]['parent_mid'];
$datarray['parent_mid'] = $pmid;
}
@@ -1237,6 +1256,13 @@ function consume_feed($xml, $importer, &$contact, $pass = 0) {
set_iconfig($datarray,'system','parent_mid',$parent_mid,true);
}
+
+ // allow likes of comments
+
+ if($item_parent_mid && activity_match($datarray['verb'],ACTVITY_LIKE)) {
+ $datarray['thr_parent'] = $item_parent_mid[0]['parent_mid'];
+ }
+
$datarray['aid'] = $importer['channel_account_id'];
$datarray['uid'] = $importer['channel_id'];
@@ -1330,8 +1356,7 @@ function consume_feed($xml, $importer, &$contact, $pass = 0) {
}
}
-
- $r = q("SELECT id, edited FROM item WHERE mid = '%s' AND uid = %d LIMIT 1",
+ $r = q("SELECT id, edited, author_xchan, item_deleted FROM item WHERE mid = '%s' AND uid = %d LIMIT 1",
dbesc($datarray['mid']),
intval($importer['channel_id'])
);
@@ -1339,6 +1364,15 @@ function consume_feed($xml, $importer, &$contact, $pass = 0) {
// Update content if 'updated' changes
if($r) {
+ if(activity_match($datarray['verb'],ACTIVITY_DELETE)
+ && $datarray['author_xchan'] === $r[0]['author_xchan']) {
+ if(! intval($r[0]['item_deleted'])) {
+ logger('deleting item ' . $r[0]['id'] . ' mid=' . $datarray['mid'], LOGGER_DEBUG);
+ drop_item($r[0]['id'],false);
+ }
+ continue;
+ }
+
if((x($datarray,'edited') !== false)
&& (datetime_convert('UTC','UTC',$datarray['edited']) !== $r[0]['edited'])) {
@@ -1706,7 +1740,7 @@ function compat_photos_list($s) {
$found = preg_match_all('/\[[zi]mg(.*?)\](.*?)\[/ism',$s,$matches,PREG_SET_ORDER);
if($found) {
- foreach($matches as $match) {
+ foreach($matches as $match) {
$ret[] = [
'href' => $match[2],
'length' => 0,
diff --git a/include/follow.php b/include/follow.php
index 9e2fd6a9c..56d8294c5 100644
--- a/include/follow.php
+++ b/include/follow.php
@@ -17,6 +17,17 @@ function new_contact($uid,$url,$channel,$interactive = false, $confirm = false)
$my_perms = false;
$is_zot = false;
+ $protocol = '';
+
+
+ if(substr($url,0,1) === '[') {
+ $x = strpos($url,']');
+ if($x) {
+ $protocol = substr($url,1,$x-1);
+ $url = substr($url,$x+1);
+ }
+ }
+
$is_http = ((strpos($url,'://') !== false) ? true : false);
if($is_http && substr($url,-1,1) === '/')
@@ -47,13 +58,13 @@ function new_contact($uid,$url,$channel,$interactive = false, $confirm = false)
}
- $arr = array('url' => $url, 'channel' => array());
+ $arr = array('url' => $url, 'protocol', 'channel' => array());
call_hooks('follow_init', $arr);
if($arr['channel']['success'])
$ret = $arr['channel'];
- elseif(! $is_http)
+ elseif((! $is_http) && ((! $protocol) || (strtolower($protocol) === 'zot')))
$ret = Zotlabs\Zot\Finger::run($url,$channel);
if($ret && is_array($ret) && $ret['success']) {
@@ -118,26 +129,31 @@ function new_contact($uid,$url,$channel,$interactive = false, $confirm = false)
else {
$xchan_hash = '';
+ $sql_options = (($protocol) ? " and xchan_network = '" . dbesc($protocol) . "' " : '');
+
- $r = q("select * from xchan where xchan_hash = '%s' or xchan_url = '%s' limit 1",
+ $r = q("select * from xchan where xchan_hash = '%s' or xchan_url = '%s' $sql_options limit 1",
dbesc($url),
dbesc($url)
);
if(! $r) {
+
// attempt network auto-discovery
- $d = discover_by_webbie($url);
+ $d = discover_by_webbie($url,$protocol);
if((! $d) && ($is_http)) {
// try RSS discovery
- if(get_config('system','feed_contacts')) {
+ $feeds = get_config('system','feed_contacts');
+
+ if(($feeds) && ($protocol === '' || $protocol === 'feed')) {
$d = discover_by_url($url);
}
else {
- $result['message'] = t('Protocol disabled.');
+ $result['message'] = t('Remote channel or protocol unavailable.');
return $result;
}
}
diff --git a/include/help.php b/include/help.php
index 4f9251b1b..02c3cb8e4 100644
--- a/include/help.php
+++ b/include/help.php
@@ -28,10 +28,23 @@ function get_help_content($tocpath = false) {
}
if($path) {
+
$title = basename($path);
if(! $tocpath)
\App::$page['title'] = t('Help:') . ' ' . ucwords(str_replace('-',' ',notags($title)));
+ // Check that there is a "toc" or "sitetoc" located at the specified path.
+ // If there is not, then there was not a translation of the table of contents
+ // available and so default back to the English TOC at /doc/toc.{html,bb,md}
+ // TODO: This is incompatible with the hierarchical TOC construction
+ // defined in /Zotlabs/Widget/Helpindex.php.
+ if($tocpath !== false &&
+ load_doc_file('doc/' . $path . '.md') === '' &&
+ load_doc_file('doc/' . $path . '.bb') === '' &&
+ load_doc_file('doc/' . $path . '.html') === ''
+ ) {
+ $path = $title;
+ }
$text = load_doc_file('doc/' . $path . '.md');
if(! $text) {
@@ -107,20 +120,55 @@ function preg_callback_help_include($matches) {
}
-
+function determine_help_language() {
+ require_once('Text/LanguageDetect.php');
+ $lang_detect = new Text_LanguageDetect();
+ // Set this mode to recognize language by the short code like "en", "ru", etc.
+ $lang_detect->setNameMode(2);
+ // If the language was specified in the URL, override the language preference
+ // of the browser. Default to English if both of these are absent.
+ if($lang_detect->languageExists(argv(1))) {
+ $lang = argv(1);
+ $from_url = true;
+ } else {
+ $lang = \App::$language;
+ if(! isset($lang))
+ $lang = 'en';
+ $from_url = false;
+ }
+ return array('language' => $lang, 'from_url' => $from_url);
+}
function load_doc_file($s) {
- $lang = \App::$language;
- if(! isset($lang))
- $lang = 'en';
- $b = basename($s);
- $d = dirname($s);
+ $path = 'doc';
+ // Determine the language and modify the path accordingly
+ $x = determine_help_language();
+ $lang = $x['language'];
+ $url_idx = ($x['from_url'] ? 1 : 0);
+ // The English translation is at the root of /doc/. Other languages are in
+ // subfolders named by the language code such as "de", "es", etc.
+ if($lang !== 'en') {
+ $path .= '/' . $lang;
+ }
- if($dirname !== '-') {
- $c = find_doc_file("$d/$lang/$b");
- if($c)
- return $c;
+ $b = basename($s);
+
+ for($i=1+$url_idx; $i<argc()-1; $i++) {
+ $path .= '/' . argv($i);
+ }
+ $c = find_doc_file($path . '/' . $b);
+ if($c)
+ return $c;
+ // Possibly a translation was requested that has not been translated, so fall
+ // back to the English version
+ $path = 'doc';
+ for($i=1+$url_idx; $i<argc()-1; $i++) {
+ $path .= '/' . argv($i);
}
+ $c = find_doc_file($path . '/' . $b);
+ if($c)
+ return $c;
+ // Try one last time to find the file at the explicit path input to the function
$c = find_doc_file($s);
if($c)
return $c;
diff --git a/include/html2bbcode.php b/include/html2bbcode.php
index 29880a627..d7fbd8660 100644
--- a/include/html2bbcode.php
+++ b/include/html2bbcode.php
@@ -87,9 +87,6 @@ function deletenode(&$doc, $node)
function html2bbcode($message)
{
- //$file = tempnam("/tmp/", "html");
- //file_put_contents($file, $message);
-
$message = str_replace("\r", "", $message);
$message = str_replace(array(
diff --git a/include/import.php b/include/import.php
index 5cb354cc3..f32f655da 100644
--- a/include/import.php
+++ b/include/import.php
@@ -603,6 +603,11 @@ function import_items($channel, $items, $sync = false, $relocate = null) {
if(! $item)
continue;
+ // deprecated
+
+ if(array_key_exists('diaspora_meta',$item))
+ unset($item['diaspora_meta']);
+
if($relocate && $item['mid'] === $item['parent_mid']) {
item_url_replace($channel,$item,$relocate['url'],z_root(),$relocate['channel_address']);
}
@@ -640,6 +645,12 @@ function import_items($channel, $items, $sync = false, $relocate = null) {
fix_attached_file_permissions($channel,$item['author_xchan'],$item['body'],$item['allow_cid'],$item['allow_gid'],$item['deny_cid'],$item['deny_gid']);
+ if($sync && $item['item_wall']) {
+ // deliver singletons if we have any
+ if($item_result && $item_result['success']) {
+ Zotlabs\Daemon\Master::Summon( [ 'Notifier','single_activity',$item_result['item_id'] ]);
+ }
+ }
}
}
}
@@ -1013,6 +1024,9 @@ function import_mail($channel, $mails, $sync = false) {
$m['aid'] = $channel['channel_account_id'];
$m['uid'] = $channel['channel_id'];
$mail_id = mail_store($m);
+ if($sync && $mail_id) {
+ Zotlabs\Daemon\Master::Summon(array('Notifier','single_mail',$mail_id));
+ }
}
}
}
diff --git a/include/items.php b/include/items.php
index 5a0ca01c6..dd8b394d3 100755
--- a/include/items.php
+++ b/include/items.php
@@ -177,7 +177,13 @@ function item_normal() {
}
function item_normal_search() {
- return " and item.item_hidden = 0 and item.item_type in (0,3) and item.item_deleted = 0
+ return " and item.item_hidden = 0 and item.item_type in (0,3,6) and item.item_deleted = 0
+ and item.item_unpublished = 0 and item.item_delayed = 0 and item.item_pending_remove = 0
+ and item.item_blocked = 0 ";
+}
+
+function item_normal_update() {
+ return " and item.item_hidden = 0 and item.item_type = 0
and item.item_unpublished = 0 and item.item_delayed = 0 and item.item_pending_remove = 0
and item.item_blocked = 0 ";
}
@@ -1124,7 +1130,7 @@ function encode_item_xchan($xchan) {
function encode_item_terms($terms,$mirror = false) {
$ret = array();
- $allowed_export_terms = array( TERM_UNKNOWN, TERM_HASHTAG, TERM_MENTION, TERM_CATEGORY, TERM_BOOKMARK, TERM_COMMUNITYTAG );
+ $allowed_export_terms = array( TERM_UNKNOWN, TERM_HASHTAG, TERM_MENTION, TERM_CATEGORY, TERM_BOOKMARK, TERM_COMMUNITYTAG, TERM_FORUM );
if($mirror) {
$allowed_export_terms[] = TERM_PCATEGORY;
@@ -1172,7 +1178,7 @@ function decode_item_meta($meta) {
* @return string
*/
function termtype($t) {
- $types = array('unknown','hashtag','mention','category','private_category','file','search','thing','bookmark', 'hierarchy', 'communitytag');
+ $types = array('unknown','hashtag','mention','category','private_category','file','search','thing','bookmark', 'hierarchy', 'communitytag', 'forum');
return(($types[$t]) ? $types[$t] : 'unknown');
}
@@ -1221,6 +1227,9 @@ function decode_tags($t) {
case 'communitytag':
$tag['ttype'] = TERM_COMMUNITYTAG;
break;
+ case 'forum':
+ $tag['ttype'] = TERM_FORUM;
+ break;
default:
case 'unknown':
$tag['ttype'] = TERM_UNKNOWN;
@@ -1600,7 +1609,6 @@ function item_store($arr, $allow_exec = false, $deliver = true) {
$arr['aid'] = ((x($arr,'aid')) ? intval($arr['aid']) : 0);
$arr['mid'] = ((x($arr,'mid')) ? notags(trim($arr['mid'])) : random_string());
$arr['revision'] = ((x($arr,'revision') && intval($arr['revision']) > 0) ? intval($arr['revision']) : 0);
-logger('revision: ' . $arr['revision']);
$arr['author_xchan'] = ((x($arr,'author_xchan')) ? notags(trim($arr['author_xchan'])) : '');
$arr['owner_xchan'] = ((x($arr,'owner_xchan')) ? notags(trim($arr['owner_xchan'])) : '');
@@ -1999,17 +2007,17 @@ function item_store_update($arr,$allow_exec = false, $deliver = true) {
$arr = $translate['item'];
}
- if((x($arr,'obj')) && is_array($arr['obj'])) {
+ if((array_key_exists('obj',$arr)) && is_array($arr['obj'])) {
activity_sanitise($arr['obj']);
$arr['obj'] = json_encode($arr['obj']);
}
- if((x($arr,'target')) && is_array($arr['target'])) {
+ if((array_key_exists('target',$arr)) && is_array($arr['target'])) {
activity_sanitise($arr['target']);
$arr['target'] = json_encode($arr['target']);
}
- if((x($arr,'attach')) && is_array($arr['attach'])) {
+ if((array_key_exists('attach',$arr)) && is_array($arr['attach'])) {
activity_sanitise($arr['attach']);
$arr['attach'] = json_encode($arr['attach']);
}
@@ -2452,7 +2460,7 @@ function tag_deliver($uid, $item_id) {
* Now we've got those out of the way. Let's see if this is a post that's tagged for re-delivery
*/
- $terms = get_terms_oftype($item['term'],TERM_MENTION);
+ $terms = array_merge(get_terms_oftype($item['term'],TERM_MENTION),get_terms_oftype($item['term'],TERM_FORUM));
if($terms)
logger('tag_deliver: post mentions: ' . print_r($terms,true), LOGGER_DATA);
@@ -2487,22 +2495,46 @@ function tag_deliver($uid, $item_id) {
$plustagged = false;
$matches = array();
- $pattern = '/@\!?\[zrl\=' . preg_quote($term['url'],'/') . '\]' . preg_quote($term['term'],'/') . '\[\/zrl\]/';
+ $pattern = '/[\!@]\!?\[zrl\=' . preg_quote($term['url'],'/') . '\]' . preg_quote($term['term'],'/') . '\[\/zrl\]/';
if(preg_match($pattern,$body,$matches))
$tagged = true;
- $pattern = '/@\!?\[zrl\=([^\]]*?)\]((?:.(?!\[zrl\=))*?)\+\[\/zrl\]/';
+ // original red forum tagging sequence @forumname+
+ // standard forum tagging sequence !forumname
+
+ $pluspattern = '/@\!?\[zrl\=([^\]]*?)\]((?:.(?!\[zrl\=))*?)\+\[\/zrl\]/';
+
+ $forumpattern = '/\!\!?\[zrl\=([^\]]*?)\]((?:.(?!\[zrl\=))*?)\[\/zrl\]/';
- if(preg_match_all($pattern,$body,$matches,PREG_SET_ORDER)) {
- $max_forums = get_config('system','max_tagged_forums');
- if(! $max_forums)
- $max_forums = 2;
- $matched_forums = 0;
+ $found = false;
+
+ $max_forums = get_config('system','max_tagged_forums');
+ if(! $max_forums)
+ $max_forums = 2;
+ $matched_forums = 0;
+ $matches = array();
+
+ if(preg_match_all($pluspattern,$body,$matches,PREG_SET_ORDER)) {
foreach($matches as $match) {
$matched_forums ++;
if($term['url'] === $match[1] && $term['term'] === $match[2]) {
if($matched_forums <= $max_forums) {
$plustagged = true;
+ $found = true;
+ break;
+ }
+ logger('forum ' . $term['term'] . ' exceeded max_tagged_forums - ignoring');
+ }
+ }
+ }
+
+ if(preg_match_all($forumpattern,$body,$matches,PREG_SET_ORDER)) {
+ foreach($matches as $match) {
+ $matched_forums ++;
+ if($term['url'] === $match[1] && $term['term'] === $match[2]) {
+ if($matched_forums <= $max_forums) {
+ $plustagged = true;
+ $found = true;
break;
}
logger('forum ' . $term['term'] . ' exceeded max_tagged_forums - ignoring');
@@ -2601,7 +2633,8 @@ function tgroup_check($uid,$item) {
if(! $u)
return false;
- $terms = get_terms_oftype($item['term'],TERM_MENTION);
+
+ $terms = array_merge(get_terms_oftype($item['term'],TERM_MENTION),get_terms_oftype($item['term'],TERM_FORUM));
if($terms)
logger('tgroup_check: post mentions: ' . print_r($terms,true), LOGGER_DATA);
@@ -2632,18 +2665,34 @@ function tgroup_check($uid,$item) {
$body = preg_replace('/\[share(.*?)\[\/share\]/','',$body);
-// $pattern = '/@\!?\[zrl\=' . preg_quote($term['url'],'/') . '\]' . preg_quote($term['term'] . '+','/') . '\[\/zrl\]/';
- $pattern = '/@\!?\[zrl\=([^\]]*?)\]((?:.(?!\[zrl\=))*?)\+\[\/zrl\]/';
+ $pluspattern = '/@\!?\[zrl\=([^\]]*?)\]((?:.(?!\[zrl\=))*?)\+\[\/zrl\]/';
+
+ $forumpattern = '/\!\!?\[zrl\=([^\]]*?)\]((?:.(?!\[zrl\=))*?)\[\/zrl\]/';
+
$found = false;
+
+ $max_forums = get_config('system','max_tagged_forums');
+ if(! $max_forums)
+ $max_forums = 2;
+ $matched_forums = 0;
$matches = array();
- if(preg_match_all($pattern,$body,$matches,PREG_SET_ORDER)) {
- $max_forums = get_config('system','max_tagged_forums');
- if(! $max_forums)
- $max_forums = 2;
- $matched_forums = 0;
+ if(preg_match_all($pluspattern,$body,$matches,PREG_SET_ORDER)) {
+ foreach($matches as $match) {
+ $matched_forums ++;
+ if($term['url'] === $match[1] && $term['term'] === $match[2]) {
+ if($matched_forums <= $max_forums) {
+ $found = true;
+ break;
+ }
+ logger('forum ' . $term['term'] . ' exceeded max_tagged_forums - ignoring');
+ }
+ }
+ }
+
+ if(preg_match_all($forumpattern,$body,$matches,PREG_SET_ORDER)) {
foreach($matches as $match) {
$matched_forums ++;
if($term['url'] === $match[1] && $term['term'] === $match[2]) {
@@ -4084,7 +4133,7 @@ function items_fetch($arr,$channel = null,$observer_hash = null,$client_mode = C
//$third = dba_timer();
- $items = fetch_post_tags($items,true);
+ $items = fetch_post_tags($items,false);
//$fourth = dba_timer();
@@ -4112,6 +4161,8 @@ function webpage_to_namespace($webpage) {
$page_type = 'BUILDBLOCK';
elseif($webpage == ITEM_TYPE_PDL)
$page_type = 'PDL';
+ elseif($webpage == ITEM_TYPE_CARD)
+ $page_type = 'CARD';
elseif($webpage == ITEM_TYPE_DOC)
$page_type = 'docfile';
else
diff --git a/include/nav.php b/include/nav.php
index 2004f6ae3..89947e270 100644
--- a/include/nav.php
+++ b/include/nav.php
@@ -6,7 +6,7 @@ require_once('include/security.php');
require_once('include/menu.php');
-function nav() {
+function nav($template = 'default') {
/**
*
@@ -62,7 +62,6 @@ EOT;
if($banner === false)
$banner = get_config('system','sitename');
- //the notifications template is in hdr.tpl
App::$page['header'] .= replace_macros(get_markup_template('hdr.tpl'), array(
//we could additionally use this to display important system notifications e.g. for updates
));
@@ -92,34 +91,77 @@ EOT;
$nav['loginmenu'][] = Array('rmagic',t('Remote authentication'),'',t('Click to authenticate to your home hub'),'rmagic_nav_btn');
}
+ if(local_channel()) {
- if(local_channel()) {
+
+ $nav['network'] = array('network', t('Activity'), "", t('Network Activity'),'network_nav_btn');
+ $nav['network']['all'] = [ 'network', t('View your network activity'), '','' ];
+ $nav['network']['mark'] = array('', t('Mark all activity notifications seen'), '','');
+
+ $nav['home'] = array('channel/' . $channel['channel_address'], t('Channel Home'), "", t('Channel home'),'home_nav_btn');
+ $nav['home']['all'] = [ 'channel/' . $channel['channel_address'], t('View your channel home'), '' , '' ];
+ $nav['home']['mark'] = array('', t('Mark all channel notifications seen'), '','');
+
+
+ $nav['intros'] = array('connections/ifpending', t('Connections'), "", t('Connections'),'connections_nav_btn');
+ if(is_site_admin())
+ $nav['registrations'] = array('admin/accounts', t('Registrations'), "", t('Registrations'),'registrations_nav_btn');
+
+
+ $nav['notifications'] = array('notifications/system', t('Notices'), "", t('Notifications'),'notifications_nav_btn');
+ $nav['notifications']['all']=array('notifications/system', t('View all notifications'), "", "");
+ $nav['notifications']['mark'] = array('', t('Mark all system notifications seen'), '','');
+
+ $nav['messages'] = array('mail/combined', t('Mail'), "", t('Private mail'),'mail_nav_btn');
+ $nav['messages']['all']=array('mail/combined', t('View your private messages'), "", "");
+ $nav['messages']['mark'] = array('', t('Mark all private messages seen'), '','');
+ $nav['messages']['inbox'] = array('mail/inbox', t('Inbox'), "", t('Inbox'));
+ $nav['messages']['outbox']= array('mail/outbox', t('Outbox'), "", t('Outbox'));
+ $nav['messages']['new'] = array('mail/new', t('New Message'), "", t('New Message'));
+
+
+ $nav['all_events'] = array('events', t('Events'), "", t('Event Calendar'),'events_nav_btn');
+ $nav['all_events']['all']=array('events', t('View events'), "", "");
+ $nav['all_events']['mark'] = array('', t('Mark all events seen'), '','');
+
+ if(! $_SESSION['delegate']) {
+ $nav['manage'] = array('manage', t('Channel Manager'), "", t('Manage Your Channels'),'manage_nav_btn');
+ }
+
+ $nav['settings'] = array('settings', t('Settings'),"", t('Account/Channel Settings'),'settings_nav_btn');
+
+
if($chans && count($chans) > 1 && feature_enabled(local_channel(),'nav_channel_select'))
$nav['channels'] = $chans;
$nav['logout'] = ['logout',t('Logout'), "", t('End this session'),'logout_nav_btn'];
// user menu
- $nav['usermenu'][] = ['profile/' . $channel['channel_address'], t('View Profile'), ((\App::$nav_sel['active'] == 'Profile') ? 'active' : ''), t('Your profile page'),'profile_nav_btn'];
+ $nav['usermenu'][] = ['profile/' . $channel['channel_address'], t('View Profile'), ((\App::$nav_sel['name'] == 'Profile') ? 'active' : ''), t('Your profile page'),'profile_nav_btn'];
if(feature_enabled(local_channel(),'multi_profiles'))
- $nav['usermenu'][] = ['profiles', t('Edit Profiles'), ((\App::$nav_sel['active'] == 'Profiles') ? 'active' : '') , t('Manage/Edit profiles'),'profiles_nav_btn'];
+ $nav['usermenu'][] = ['profiles', t('Edit Profiles'), ((\App::$nav_sel['name'] == 'Profiles') ? 'active' : '') , t('Manage/Edit profiles'),'profiles_nav_btn'];
else
- $nav['usermenu'][] = ['profiles/' . $prof[0]['id'], t('Edit Profile'), ((\App::$nav_sel['active'] == 'Profiles') ? 'active' : ''), t('Edit your profile'),'profiles_nav_btn'];
+ $nav['usermenu'][] = ['profiles/' . $prof[0]['id'], t('Edit Profile'), ((\App::$nav_sel['name'] == 'Profiles') ? 'active' : ''), t('Edit your profile'),'profiles_nav_btn'];
}
else {
if(! get_account_id()) {
- $nav['login'] = login(true,'main-login',false,false);
- $nav['loginmenu'][] = ['login',t('Login'),'',t('Sign in'),'login_nav_btn'];
- App::$page['content'] .= replace_macros(get_markup_template('nav_login.tpl'),
- [
- '$nav' => $nav,
- 'userinfo' => $userinfo
- ]
- );
-
+ if(App::$module === 'channel') {
+ $nav['login'] = login(true,'main-login',false,false);
+ $nav['loginmenu'][] = ['login',t('Login'),'',t('Sign in'),''];
+ }
+ else {
+ $nav['login'] = login(true,'main-login',false,false);
+ $nav['loginmenu'][] = ['login',t('Login'),'',t('Sign in'),'login_nav_btn'];
+ App::$page['content'] .= replace_macros(get_markup_template('nav_login.tpl'),
+ [
+ '$nav' => $nav,
+ 'userinfo' => $userinfo
+ ]
+ );
+ }
}
else
$nav['alogout'] = ['logout',t('Logout'), "", t('End this session'),'logout_nav_btn'];
@@ -169,47 +211,16 @@ EOT;
*/
if(local_channel()) {
-
- $nav['network'] = array('network', t('Grid'), "", t('Your grid'),'network_nav_btn');
- $nav['network']['all'] = [ 'network', t('View your network/grid'), '','' ];
- $nav['network']['mark'] = array('', t('Mark all grid notifications seen'), '','');
-
- $nav['home'] = array('channel/' . $channel['channel_address'], t('Channel Home'), "", t('Channel home'),'home_nav_btn');
- $nav['home']['all'] = [ 'channel/' . $channel['channel_address'], t('View your channel home'), '' , '' ];
- $nav['home']['mark'] = array('', t('Mark all channel notifications seen'), '','');
-
-
- $nav['intros'] = array('connections/ifpending', t('Connections'), "", t('Connections'),'connections_nav_btn');
-
-
- $nav['notifications'] = array('notifications/system', t('Notices'), "", t('Notifications'),'notifications_nav_btn');
- $nav['notifications']['all']=array('notifications/system', t('View all notifications'), "", "");
- $nav['notifications']['mark'] = array('', t('Mark all system notifications seen'), '','');
-
- $nav['messages'] = array('mail/combined', t('Mail'), "", t('Private mail'),'mail_nav_btn');
- $nav['messages']['all']=array('mail/combined', t('View your private messages'), "", "");
- $nav['messages']['mark'] = array('', t('Mark all private messages seen'), '','');
- $nav['messages']['inbox'] = array('mail/inbox', t('Inbox'), "", t('Inbox'));
- $nav['messages']['outbox']= array('mail/outbox', t('Outbox'), "", t('Outbox'));
- $nav['messages']['new'] = array('mail/new', t('New Message'), "", t('New Message'));
-
-
- $nav['all_events'] = array('events', t('Events'), "", t('Event Calendar'),'events_nav_btn');
- $nav['all_events']['all']=array('events', t('View events'), "", "");
- $nav['all_events']['mark'] = array('', t('Mark all events seen'), '','');
-
if(! $_SESSION['delegate']) {
$nav['manage'] = array('manage', t('Channel Manager'), "", t('Manage Your Channels'),'manage_nav_btn');
}
-
$nav['settings'] = array('settings', t('Settings'),"", t('Account/Channel Settings'),'settings_nav_btn');
-
}
/**
* Admin page
*/
- if (is_site_admin()){
+ if (is_site_admin()) {
$nav['admin'] = array('admin/', t('Admin'), "", t('Site Setup and Configuration'),'admin_nav_btn');
}
@@ -221,6 +232,17 @@ EOT;
// turned off until somebody discovers this and figures out a good location for it.
$powered_by = '';
+ if(App::$profile_uid && App::$nav_sel['raw_name']) {
+ $active_app = q("SELECT app_url FROM app WHERE app_channel = %d AND app_name = '%s' LIMIT 1",
+ intval(App::$profile_uid),
+ dbesc(App::$nav_sel['raw_name'])
+ );
+
+ if($active_app) {
+ $url = $active_app[0]['app_url'];
+ }
+ }
+
//app bin
if($is_owner) {
if(get_pconfig(local_channel(), 'system','initial_import_system_apps') === false) {
@@ -246,16 +268,33 @@ EOT;
$syslist = Zlib\Apps::app_order(local_channel(),$syslist);
foreach($syslist as $app) {
- if(\App::$nav_sel['active'] == $app['name'])
+ if(\App::$nav_sel['name'] == $app['name'])
$app['active'] = true;
- if($is_owner)
+ if($is_owner) {
$nav_apps[] = Zlib\Apps::app_render($app,'nav');
- elseif(! $is_owner && strpos($app['requires'], 'local_channel') === false)
+ if(strpos($app['categories'],'navbar_' . $template)) {
+ $navbar_apps[] = Zlib\Apps::app_render($app,'navbar');
+ }
+ }
+ elseif(! $is_owner && strpos($app['requires'], 'local_channel') === false) {
$nav_apps[] = Zlib\Apps::app_render($app,'nav');
+ if(strpos($app['categories'],'navbar_' . $template)) {
+ $navbar_apps[] = Zlib\Apps::app_render($app,'navbar');
+ }
+ }
}
- $tpl = get_markup_template('nav.tpl');
+ $c = theme_include('navbar_' . purify_filename($template) . '.css');
+ $tpl = get_markup_template('navbar_' . purify_filename($template) . '.tpl');
+
+ if($c && $tpl) {
+ head_add_css('navbar_' . $template . '.css');
+ }
+
+ if(! $tpl) {
+ $tpl = get_markup_template('navbar_default.tpl');
+ }
App::$page['nav'] .= replace_macros($tpl, array(
'$baseurl' => z_root(),
@@ -267,15 +306,19 @@ EOT;
'$userinfo' => $x['usermenu'],
'$localuser' => local_channel(),
'$is_owner' => $is_owner,
- '$sel' => App::$nav_sel,
+ '$sel' => App::$nav_sel,
'$powered_by' => $powered_by,
'$help' => t('@name, #tag, ?doc, content'),
'$pleasewait' => t('Please wait...'),
'$nav_apps' => $nav_apps,
+ '$navbar_apps' => $navbar_apps,
+ '$channel_menu' => get_config('system','channel_menu'),
+ '$channel_thumb' => ((App::$profile) ? App::$profile['thumb'] : ''),
'$channel_apps' => $channel_apps,
'$addapps' => t('Add Apps'),
'$orderapps' => t('Arrange Apps'),
- '$sysapps_toggle' => t('Toggle System Apps')
+ '$sysapps_toggle' => t('Toggle System Apps'),
+ '$url' => (($url) ? $url : App::$cmd)
));
if(x($_SESSION, 'reload_avatar') && $observer) {
@@ -298,7 +341,10 @@ EOT;
*
*/
function nav_set_selected($item){
- App::$nav_sel['active'] = $item;
+ App::$nav_sel['raw_name'] = $item;
+ $item = ['name' => $item];
+ Zlib\Apps::translate_system_apps($item);
+ App::$nav_sel['name'] = $item['name'];
}
@@ -428,6 +474,18 @@ function channel_apps($is_owner = false, $nickname = null) {
];
}
+ if($p['view_pages'] && feature_enabled($uid,'cards')) {
+ $tabs[] = [
+ 'label' => t('Cards'),
+ 'url' => z_root() . '/cards/' . $nickname ,
+ 'sel' => ((argv(0) == 'cards') ? 'active' : ''),
+ 'title' => t('View Cards'),
+ 'id' => 'cards-tab',
+ 'icon' => 'list'
+ ];
+ }
+
+
if($has_webpages && feature_enabled($uid,'webpages')) {
$tabs[] = [
'label' => t('Webpages'),
@@ -461,7 +519,7 @@ function channel_apps($is_owner = false, $nickname = null) {
[
'$tabs' => $arr['tabs'],
'$name' => App::$profile['channel_name'],
- '$thumb' => App::$profile['thumb']
+ '$thumb' => App::$profile['thumb'],
]
);
}
diff --git a/include/network.php b/include/network.php
index 5f814ef32..7e2dbf4cf 100644
--- a/include/network.php
+++ b/include/network.php
@@ -411,7 +411,7 @@ function http_status($val, $msg = '') {
if ($val >= 200 && $val < 300)
$msg = (($msg) ? $msg : 'OK');
- logger('' . $val . ' ' . $msg);
+ logger(\App::$query_string . ':' . $val . ' ' . $msg);
header($_SERVER['SERVER_PROTOCOL'] . ' ' . $val . ' ' . $msg);
}
@@ -1137,13 +1137,13 @@ function discover_by_url($url, $arr = null) {
return true;
}
-function discover_by_webbie($webbie) {
+function discover_by_webbie($webbie,$protocol = '') {
$result = [];
$network = null;
- $webbie = strtolower($webbie);
+// $webbie = strtolower($webbie);
$x = webfinger_rfc7033($webbie,true);
if($x && array_key_exists('links',$x) && $x['links']) {
@@ -1153,7 +1153,7 @@ function discover_by_webbie($webbie) {
// If we discover zot - don't search further; grab the info and get out of
// here.
- if($link['rel'] === PROTOCOL_ZOT) {
+ if($link['rel'] === PROTOCOL_ZOT && ((! $protocol) || (strtolower($protocol) === 'zot'))) {
logger('discover_by_webbie: zot found for ' . $webbie, LOGGER_DEBUG);
if(array_key_exists('zot',$x) && $x['zot']['success']) {
$i = import_xchan($x['zot']);
@@ -1174,7 +1174,7 @@ function discover_by_webbie($webbie) {
logger('webfinger: ' . print_r($x,true), LOGGER_DATA, LOG_INFO);
- $arr = array('address' => $webbie, 'success' => false, 'webfinger' => $x);
+ $arr = array('address' => $webbie, 'protocol' => $protocol, 'success' => false, 'webfinger' => $x);
call_hooks('discover_channel_webfinger', $arr);
if($arr['success'])
return true;
@@ -1291,7 +1291,7 @@ function fetch_xrd_links($url) {
return array();
$h = parse_xml_string($xml);
- if(! $h)
+ if($h === false)
return array();
$arr = convert_xml_element_to_array($h);
@@ -1652,9 +1652,17 @@ function check_channelallowed($hash) {
}
function deliverable_singleton($channel_id,$xchan) {
+
+ if(array_key_exists('xchan_hash',$xchan))
+ $xchan_hash = $xchan['xchan_hash'];
+ elseif(array_key_exists('hubloc_hash',$xchan))
+ $xchan_hash = $xchan['hubloc_hash'];
+ else
+ return true;
+
$r = q("select abook_instance from abook where abook_channel = %d and abook_xchan = '%s' limit 1",
intval($channel_id),
- dbesc($xchan['xchan_hash'])
+ dbesc($xchan_hash)
);
if($r) {
if(! $r[0]['abook_instance'])
@@ -1689,18 +1697,19 @@ function get_repository_version($branch = 'master') {
function network_to_name($s) {
$nets = array(
- NETWORK_DFRN => t('Friendica'),
- NETWORK_FRND => t('Friendica'),
- NETWORK_OSTATUS => t('OStatus'),
- NETWORK_GNUSOCIAL => t('GNU-Social'),
- NETWORK_FEED => t('RSS/Atom'),
- NETWORK_MAIL => t('Email'),
- NETWORK_DIASPORA => t('Diaspora'),
- NETWORK_FACEBOOK => t('Facebook'),
- NETWORK_ZOT => t('Zot'),
- NETWORK_LINKEDIN => t('LinkedIn'),
- NETWORK_XMPP => t('XMPP/IM'),
- NETWORK_MYSPACE => t('MySpace'),
+ NETWORK_DFRN => t('Friendica'),
+ NETWORK_FRND => t('Friendica'),
+ NETWORK_OSTATUS => t('OStatus'),
+ NETWORK_GNUSOCIAL => t('GNU-Social'),
+ NETWORK_FEED => t('RSS/Atom'),
+ NETWORK_ACTIVITYPUB => t('ActivityPub'),
+ NETWORK_MAIL => t('Email'),
+ NETWORK_DIASPORA => t('Diaspora'),
+ NETWORK_FACEBOOK => t('Facebook'),
+ NETWORK_ZOT => t('Zot'),
+ NETWORK_LINKEDIN => t('LinkedIn'),
+ NETWORK_XMPP => t('XMPP/IM'),
+ NETWORK_MYSPACE => t('MySpace'),
);
call_hooks('network_to_name', $nets);
@@ -1934,4 +1943,35 @@ function getBestSupportedMimeType($mimeTypes = null, $acceptedTypes = false) {
}
// no mime-type found
return null;
+}
+
+
+function jsonld_document_loader($url) {
+
+ // perform caching for jsonld normaliser
+
+ require_once('library/jsonld/jsonld.php');
+
+ $cachepath = 'store/[data]/ldcache';
+ if(! is_dir($cachepath))
+ os_mkdir($cachepath,STORAGE_DEFAULT_PERMISSIONS,true);
+
+ $filename = $cachepath . '/' . urlencode($url);
+ if(file_exists($filename) && filemtime($filename) > time() - (12 * 60 * 60)) {
+ return json_decode(file_get_contents($filename));
+ }
+
+ $r = jsonld_default_document_loader($url);
+ if($r) {
+ file_put_contents($filename,json_encode($r));
+ return $r;
+ }
+
+ logger('not found');
+ if(file_exists($filename)) {
+ return json_decode(file_get_contents($filename));
+ }
+
+ return [];
+
} \ No newline at end of file
diff --git a/include/oembed.php b/include/oembed.php
index 460e0244e..c2bf0a0ed 100755
--- a/include/oembed.php
+++ b/include/oembed.php
@@ -225,6 +225,17 @@ function oembed_fetch_url($embedurl){
if($j['html']) {
$orig = $j['html'];
$allow_position = (($is_matrix) ? true : false);
+
+ // some sites wrap their entire embed in an iframe
+ // which we will purify away and which we provide anyway.
+ // So if we see this, grab the frame src url and use that
+ // as the embed content - which will still need to be purified.
+
+ if(preg_match('#\<iframe(.*?)src\=[\'\"](.*?)[\'\"]#',$j['html'],$matches)) {
+ $x = z_fetch_url($matches[2]);
+ $j['html'] = $x['body'];
+ }
+
$j['html'] = purify_html($j['html'],$allow_position);
if($j['html'] != $orig) {
logger('oembed html was purified. original: ' . $orig . ' purified: ' . $j['html'], LOGGER_DEBUG, LOG_INFO);
diff --git a/include/perm_upgrade.php b/include/perm_upgrade.php
index 5be1ffbb2..9eb1efba2 100644
--- a/include/perm_upgrade.php
+++ b/include/perm_upgrade.php
@@ -135,6 +135,9 @@ function translate_abook_perms_outbound(&$abook) {
$my_perms = 0;
$their_perms = 0;
+ if(! $abook)
+ return;
+
if(array_key_exists('abconfig',$abook) && is_array($abook['abconfig']) && $abook['abconfig']) {
foreach($abook['abconfig'] as $p) {
if($p['cat'] === 'their_perms') {
diff --git a/include/photo/photo_driver.php b/include/photo/photo_driver.php
index f47a9c878..5eb1f9113 100644
--- a/include/photo/photo_driver.php
+++ b/include/photo/photo_driver.php
@@ -446,7 +446,7 @@ abstract class photo_driver {
*/
function guess_image_type($filename, $headers = '') {
- logger('Photo: guess_image_type: '.$filename . ($headers?' from curl headers':''), LOGGER_DEBUG);
+// logger('Photo: guess_image_type: '.$filename . ($headers?' from curl headers':''), LOGGER_DEBUG);
$type = null;
if ($headers) {
@@ -513,11 +513,32 @@ function guess_image_type($filename, $headers = '') {
}
}
- logger('Photo: guess_image_type: type = ' . $type, LOGGER_DEBUG);
+ logger('Photo: guess_image_type: filename = ' . $filename . ' type = ' . $type, LOGGER_DEBUG);
return $type;
}
+
+function delete_thing_photo($url,$ob_hash) {
+
+ $hash = basename($url);
+ $hash = substr($hash,0,strpos($hash,'-'));
+
+ // hashes should be 32 bytes.
+
+ if((! $ob_hash) || (strlen($hash) < 16))
+ return;
+
+ $r = q("delete from photo where xchan = '%s' and photo_usage = %d and resource_id = '%s'",
+ dbesc($ob_hash),
+ intval(PHOTO_THING),
+ dbesc($hash)
+ );
+
+}
+
+
+
function import_xchan_photo($photo,$xchan,$thing = false) {
$flags = (($thing) ? PHOTO_THING : PHOTO_XCHAN);
@@ -637,6 +658,37 @@ function import_xchan_photo($photo,$xchan,$thing = false) {
}
+function import_channel_photo_from_url($photo,$aid,$uid) {
+
+ if($photo) {
+ $filename = basename($photo);
+
+ $result = z_fetch_url($photo,true);
+
+ if($result['success']) {
+ $img_str = $result['body'];
+ $type = guess_image_type($photo, $result['header']);
+
+ $h = explode("\n",$result['header']);
+ if($h) {
+ foreach($h as $hl) {
+ if(stristr($hl,'content-type:')) {
+ if(! stristr($hl,'image/')) {
+ $photo_failure = true;
+ }
+ }
+ }
+ }
+ }
+ }
+ else {
+ $photo_failure = true;
+ }
+
+ import_channel_photo($img_str,$type,$aid,$uid);
+
+ return $type;
+}
function import_channel_photo($photo,$type,$aid,$uid) {
diff --git a/include/photos.php b/include/photos.php
index f5d5fdb48..5de68f162 100644
--- a/include/photos.php
+++ b/include/photos.php
@@ -66,7 +66,19 @@ function photo_upload($channel, $observer, $args) {
$os_storage = 0;
if($args['os_syspath'] && $args['getimagesize']) {
- $imagedata = @file_get_contents($args['os_syspath']);
+ if($args['getimagesize'][0] > 1600 || $args['getimagesize'][1] > 1600) {
+ $imagick_path = get_config('system','imagick_convert_path');
+ if($imagick_path && @file_exists($imagick_path)) {
+ $tmp_name = $args['os_syspath'] . '-001';
+ $newsize = photo_calculate_1600_scale($args['getimagesize']);
+ exec($imagick_path . ' ' . $args['os_syspath'] . ' -resize ' . $newsize . '^ ' . $tmp_name);
+ $imagedata = @file_get_contents($tmp_name);
+ @unlink($tmp_name);
+ }
+ }
+ else {
+ $imagedata = @file_get_contents($args['os_syspath']);
+ }
$filename = $args['filename'];
$filesize = strlen($imagedata);
// this is going to be deleted if it exists
@@ -122,7 +134,6 @@ function photo_upload($channel, $observer, $args) {
}
logger('photo_upload: loading the contents of ' . $src , LOGGER_DEBUG);
-
$imagedata = @file_get_contents($src);
}
@@ -428,6 +439,70 @@ function photo_upload($channel, $observer, $args) {
return $ret;
}
+
+function photo_calculate_1600_scale($arr) {
+
+ $max = 1600;
+ $width = $arr[0];
+ $height = $arr[1];
+
+ $dest_width = $dest_height = 0;
+
+ if((! $width)|| (! $height))
+ return FALSE;
+
+ if($width > $max && $height > $max) {
+
+ // very tall image (greater than 16:9)
+ // constrain the width - let the height float.
+
+ if((($height * 9) / 16) > $width) {
+ $dest_width = $max;
+ $dest_height = intval(( $height * $max ) / $width);
+ }
+
+ // else constrain both dimensions
+
+ elseif($width > $height) {
+ $dest_width = $max;
+ $dest_height = intval(( $height * $max ) / $width);
+ }
+ else {
+ $dest_width = intval(( $width * $max ) / $height);
+ $dest_height = $max;
+ }
+ }
+ else {
+ if( $width > $max ) {
+ $dest_width = $max;
+ $dest_height = intval(( $height * $max ) / $width);
+ }
+ else {
+ if( $height > $max ) {
+ // very tall image (greater than 16:9)
+ // but width is OK - don't do anything
+
+ if((($height * 9) / 16) > $width) {
+ $dest_width = $width;
+ $dest_height = $height;
+ }
+ else {
+ $dest_width = intval(( $width * $max ) / $height);
+ $dest_height = $max;
+ }
+ }
+ else {
+ $dest_width = $width;
+ $dest_height = $height;
+ }
+ }
+ }
+
+ return $dest_width . 'x' . $dest_height;
+
+}
+
+
/**
* @brief Returns a list with all photo albums observer is allowed to see.
*
@@ -595,7 +670,7 @@ function photos_album_exists($channel_id, $observer_hash, $album) {
// partial backward compatibility with Hubzilla < 2.4 when we used the filename only
// (ambiguous which would get chosen if you had two albums of the same name in different directories)
- if(!$r) {
+ if(!$r && ctype_xdigit($album)) {
$r = q("SELECT folder, hash, is_dir, filename, os_path, display_path FROM attach WHERE filename = '%s' AND is_dir = 1 AND uid = %d $sql_extra limit 1",
dbesc(hex2bin($album)),
intval($channel_id)
diff --git a/include/security.php b/include/security.php
index 6e7b3dbb2..450cc4f69 100644
--- a/include/security.php
+++ b/include/security.php
@@ -114,9 +114,9 @@ function atoken_xchan($atoken) {
'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(),
+ 'xchan_addr' => 'guest:' . $atoken['atoken_name'] . '@' . \App::get_hostname(),
'xchan_network' => 'unknown',
- 'xchan_url' => z_root(),
+ 'xchan_url' => z_root() . '/guest/' . substr($c['channel_hash'],0,16) . '.' . $atoken['atoken_name'],
'xchan_hidden' => 1,
'xchan_photo_mimetype' => 'image/jpeg',
'xchan_photo_l' => get_default_profile_photo(300),
@@ -640,7 +640,7 @@ function stream_perms_xchans($perms = NULL ) {
if(local_channel())
$ret[] = get_observer_hash();
- $x = q("select uid from pconfig where cat = 'perm_limits' and k = 'view_stream' ");
+ $x = q("select uid, v from pconfig where cat = 'perm_limits' and k = 'view_stream' ");
if($x) {
$y = [];
foreach($x as $xv) {
@@ -650,6 +650,7 @@ function stream_perms_xchans($perms = NULL ) {
}
if($y) {
$ids = ids_to_querystr($y,'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)
);
diff --git a/include/taxonomy.php b/include/taxonomy.php
index 46d661581..23acaa24d 100644
--- a/include/taxonomy.php
+++ b/include/taxonomy.php
@@ -134,6 +134,8 @@ function format_term_for_display($term) {
$s = '';
if(($term['ttype'] == TERM_HASHTAG) || ($term['ttype'] == TERM_COMMUNITYTAG))
$s .= '#';
+ elseif($term['ttype'] == TERM_FORUM)
+ $s .= '!';
elseif($term['ttype'] == TERM_MENTION)
$s .= '@';
else
@@ -199,6 +201,62 @@ function tagadelic($uid, $count = 0, $authors = '', $owner = '', $flags = 0, $re
}
+
+
+function card_tagadelic($uid, $count = 0, $authors = '', $owner = '', $flags = 0, $restrict = 0, $type = TERM_CATEGORY) {
+
+ require_once('include/security.php');
+
+ if(! perm_is_allowed($uid,get_observer_hash(),'view_pages'))
+ return array();
+
+
+ $item_normal = item_normal();
+ $sql_options = item_permissions_sql($uid);
+ $count = intval($count);
+
+ if($flags) {
+ if($flags === 'wall')
+ $sql_options .= " and item_wall = 1 ";
+ }
+
+ if($authors) {
+ if(! is_array($authors))
+ $authors = array($authors);
+
+ stringify_array_elms($authors,true);
+ $sql_options .= " and author_xchan in (" . implode(',',$authors) . ") ";
+ }
+
+ if($owner) {
+ $sql_options .= " and owner_xchan = '" . dbesc($owner) . "' ";
+ }
+
+
+ // Fetch tags
+ $r = q("select term, count(term) as total from term left join item on term.oid = item.id
+ where term.uid = %d and term.ttype = %d
+ and otype = %d and item_type = %d and item_private = 0
+ $sql_options $item_normal
+ group by term order by total desc %s",
+ intval($uid),
+ intval($type),
+ intval(TERM_OBJ_POST),
+ intval($restrict),
+ ((intval($count)) ? "limit $count" : '')
+ );
+
+ if(! $r)
+ return array();
+
+ return Zotlabs\Text\Tagadelic::calc($r);
+
+}
+
+
+
+
+
function dir_tagadelic($count = 0) {
$count = intval($count);
@@ -316,6 +374,26 @@ function catblock($uid,$count = 0,$authors = '',$owner = '', $flags = 0,$restric
return $o;
}
+function card_catblock($uid,$count = 0,$authors = '',$owner = '', $flags = 0,$restrict = 0,$type = TERM_CATEGORY) {
+ $o = '';
+
+ $r = card_tagadelic($uid,$count,$authors,$owner,$flags,$restrict,$type);
+
+ if($r) {
+ $c = q("select channel_address from channel where channel_id = %d limit 1",
+ intval($uid)
+ );
+
+ $o = '<div class="tagblock widget"><h3>' . t('Categories') . '</h3><div class="tags" align="center">';
+ foreach($r as $rr) {
+ $o .= '<a href="cards/' . $c[0]['channel_address']. '?f=&cat=' . urlencode($rr[0]).'" class="tag'.$rr[2].'">'.$rr[0].'</a> ' . "\r\n";
+ }
+ $o .= '</div></div>';
+ }
+
+ return $o;
+}
+
function dir_tagblock($link,$r) {
$o = '';
diff --git a/include/text.php b/include/text.php
index 5af4f1b67..d81b59d75 100644
--- a/include/text.php
+++ b/include/text.php
@@ -651,7 +651,7 @@ function logger($msg, $level = LOGGER_NORMAL, $priority = LOG_INFO) {
$stack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
$where = basename($stack[0]['file']) . ':' . $stack[0]['line'] . ':' . $stack[1]['function'] . ': ';
- $s = datetime_convert() . ':' . log_priority_str($priority) . ':' . session_id() . ':' . $where . $msg . PHP_EOL;
+ $s = datetime_convert('UTC','UTC', 'now', ATOM_TIME) . ':' . log_priority_str($priority) . ':' . session_id() . ':' . $where . $msg . PHP_EOL;
$pluginfo = array('filename' => $logfile, 'loglevel' => $level, 'message' => $s,'priority' => $priority, 'logged' => false);
if(! (App::$module == 'setup'))
@@ -679,7 +679,7 @@ function btlogger($msg, $level = LOGGER_NORMAL, $priority = LOG_INFO) {
if(file_exists(BTLOGGER_DEBUG_FILE) && is_writable(BTLOGGER_DEBUG_FILE)) {
$stack = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 2);
$where = basename($stack[0]['file']) . ':' . $stack[0]['line'] . ':' . $stack[1]['function'] . ': ';
- $s = datetime_convert() . ':' . log_priority_str($priority) . ':' . session_id() . ':' . $where . $msg . PHP_EOL;
+ $s = datetime_convert('UTC','UTC', 'now', ATOM_TIME) . ':' . log_priority_str($priority) . ':' . session_id() . ':' . $where . $msg . PHP_EOL;
@file_put_contents(BTLOGGER_DEBUG_FILE, $s, FILE_APPEND);
}
@@ -750,7 +750,7 @@ function dlogger($msg, $level = 0) {
$where = basename($stack[0]['file']) . ':' . $stack[0]['line'] . ':' . $stack[1]['function'] . ': ';
- @file_put_contents($logfile, datetime_convert() . ':' . session_id() . ' ' . $where . $msg . PHP_EOL, FILE_APPEND);
+ @file_put_contents($logfile, datetime_convert('UTC','UTC', 'now', ATOM_TIME) . ':' . session_id() . ' ' . $where . $msg . PHP_EOL, FILE_APPEND);
}
@@ -761,9 +761,17 @@ function profiler($t1,$t2,$label) {
function activity_match($haystack,$needle) {
- if(($haystack === $needle) || ((basename($needle) === $haystack) && strstr($needle,NAMESPACE_ACTIVITY_SCHEMA)))
- return true;
+ if(! is_array($needle))
+ $needle = [ $needle ];
+
+ if($needle) {
+ foreach($needle as $n) {
+ if(($haystack === $n) || (strtolower(basename($n)) === strtolower(basename($haystack)))) {
+ return true;
+ }
+ }
+ }
return false;
}
@@ -794,7 +802,7 @@ function get_tags($s) {
// match any double quoted tags
- if(preg_match_all('/([@#]\&quot\;.*?\&quot\;)/',$s,$match)) {
+ if(preg_match_all('/([@#!]\&quot\;.*?\&quot\;)/',$s,$match)) {
foreach($match[1] as $mtch) {
$ret[] = $mtch;
}
@@ -823,7 +831,7 @@ function get_tags($s) {
// Otherwise pull out single word tags. These can be @nickname, @first_last
// and #hash tags.
- if(preg_match_all('/(?<![a-zA-Z0-9=\/\?\;])([@#][^ \x0D\x0A,;:?\[]+)/',$s,$match)) {
+ if(preg_match_all('/(?<![a-zA-Z0-9=\/\?\;])([@#\!][^ \x0D\x0A,;:?\[]+)/',$s,$match)) {
foreach($match[1] as $mtch) {
if(substr($mtch,-1,1) === '.')
$mtch = substr($mtch,0,-1);
@@ -987,7 +995,7 @@ function chanlink_cid($d) {
function magiclink_url($observer,$myaddr,$url) {
return (($observer)
- ? z_root() . '/magic?f=&dest=' . $url . '&addr=' . $myaddr
+ ? z_root() . '/magic?f=&owa=1&dest=' . $url . '&addr=' . $myaddr
: $url
);
}
@@ -1389,7 +1397,7 @@ function theme_attachments(&$item) {
if(is_foreigner($item['author_xchan']))
$url = $r['href'];
else
- $url = z_root() . '/magic?f=&hash=' . $item['author_xchan'] . '&dest=' . $r['href'] . '/' . $r['revision'];
+ $url = z_root() . '/magic?f=&owa=1&hash=' . $item['author_xchan'] . '&dest=' . $r['href'] . '/' . $r['revision'];
//$s .= '<a href="' . $url . '" title="' . $title . '" class="attachlink" >' . $icon . '</a>';
$attaches[] = array('label' => $label, 'url' => $url, 'icon' => $icon, 'title' => $title);
@@ -1786,28 +1794,28 @@ function layout_select($channel_id, $current = '') {
}
-function mimetype_select($channel_id, $current = 'text/bbcode') {
+function mimetype_select($channel_id, $current = 'text/bbcode', $choices = null, $element = 'mimetype') {
- $x = array(
- 'text/bbcode',
- 'text/html',
- 'text/markdown',
- 'text/plain',
- 'application/x-pdl'
- );
+ $x = (($choices) ? $choices : [
+ 'text/bbcode' => t('BBcode'),
+ 'text/html' => t('HTML'),
+ 'text/markdown' => t('Markdown'),
+ 'text/plain' => t('Text'),
+ 'application/x-pdl' => t('Comanche Layout')
+ ]);
if((App::$is_sys) || (channel_codeallowed($channel_id) && $channel_id == local_channel())){
- $x[] = 'application/x-php';
+ $x['application/x-php'] = t('PHP');
}
- foreach($x as $y) {
+ foreach($x as $y => $z) {
$selected = (($y == $current) ? ' selected="selected" ' : '');
- $options .= '<option name="' . $y . '"' . $selected . '>' . $y . '</option>';
+ $options .= '<option value="' . $y . '"' . $selected . '>' . $z . '</option>';
}
$o = replace_macros(get_markup_template('field_select_raw.tpl'), array(
- '$field' => array('mimetype', t('Page content type'), $selected, '', $options)
+ '$field' => array( $element, t('Page content type'), $selected, '', $options)
));
return $o;
@@ -1984,14 +1992,14 @@ function is_a_date_arg($s) {
}
function legal_webbie($s) {
- if(! strlen($s))
+ if(! $s)
return '';
- // WARNING: This regex will not work in a federated environment.
+ // WARNING: This regex may not work in a federated environment.
// You will probably want something like
// preg_replace('/([^a-z0-9\_])/','',strtolower($s));
- $r = preg_replace('/([^a-z0-9\-\_\.])/','',strtolower($s));
+ $r = preg_replace('/([^a-z0-9\-\_])/','',strtolower($s));
$x = [ 'input' => $s, 'output' => $r ];
call_hooks('legal_webbie',$x);
@@ -2003,7 +2011,7 @@ function legal_webbie_text() {
// WARNING: This will not work in a federated environment.
- $s = t('a-z, 0-9, -, _, and . only');
+ $s = t('a-z, 0-9, -, and _ only');
$x = [ 'text' => $s ];
call_hooks('legal_webbie_text',$x);
@@ -2383,8 +2391,9 @@ function handle_tag($a, &$body, &$access_tag, &$str_tags, $profile_uid, $tag, $d
$r = null;
$match = array();
- $termtype = ((strpos($tag,'#') === 0) ? TERM_HASHTAG : TERM_UNKNOWN);
- $termtype = ((strpos($tag,'@') === 0) ? TERM_MENTION : $termtype);
+ $termtype = ((strpos($tag,'#') === 0) ? TERM_HASHTAG : TERM_UNKNOWN);
+ $termtype = ((strpos($tag,'@') === 0) ? TERM_MENTION : $termtype);
+ $termtype = ((strpos($tag,'!') === 0) ? TERM_FORUM : $termtype);
$termtype = ((strpos($tag,'#^[') === 0) ? TERM_BOOKMARK : $termtype);
//is it a hash tag?
@@ -2401,16 +2410,9 @@ function handle_tag($a, &$body, &$access_tag, &$str_tags, $profile_uid, $tag, $d
//...do nothing
return $replaced;
}
- if($tag == '#getzot') {
- $basetag = 'getzot';
- $url = 'http://hubzilla.org';
- $newtag = '#[zrl=' . $url . ']' . $basetag . '[/zrl]';
- $body = str_replace($tag,$newtag,$body);
- $replaced = true;
- }
if(! $replaced) {
- //base tag has the tags name only
+ // base tag has the tags name only
if((substr($tag,0,7) === '#&quot;') && (substr($tag,-6,6) === '&quot;')) {
$basetag = substr($tag,7);
@@ -2448,10 +2450,16 @@ function handle_tag($a, &$body, &$access_tag, &$str_tags, $profile_uid, $tag, $d
//is it a person tag?
- if(strpos($tag,'@') === 0) {
+ $grouptag = false;
+
+ if(strpos($tag,'!') === 0) {
+ $grouptag = true;
+ }
+
+ if(strpos($tag,'@') === 0 || $grouptag) {
// The @! tag will alter permissions
- $exclusive = ((strpos($tag,'!') === 1 && (! $diaspora)) ? true : false);
+ $exclusive = (((! $grouptag) && (strpos($tag,'!') === 1) && (! $diaspora)) ? true : false);
//is it already replaced?
if(strpos($tag,'[zrl='))
@@ -2492,7 +2500,7 @@ function handle_tag($a, &$body, &$access_tag, &$str_tags, $profile_uid, $tag, $d
if(($t2) && (! $diaspora)) {
//get the id
- $tagcid = substr($newname,$t2 + 1);
+ $tagcid = urldecode(substr($newname,$t2 + 1));
if(strrpos($tagcid,' '))
$tagcid = substr($tagcid,0,strrpos($tagcid,' '));
@@ -2623,8 +2631,15 @@ function handle_tag($a, &$body, &$access_tag, &$str_tags, $profile_uid, $tag, $d
//create profile link
$profile = str_replace(',','%2c',$profile);
$url = $profile;
- $newtag = '@' . (($exclusive) ? '!' : '') . '[zrl=' . $profile . ']' . $newname . (($forum && ! $trailing_plus_name) ? '+' : '') . '[/zrl]';
- $body = str_replace('@' . (($exclusive) ? '!' : '') . $name, $newtag, $body);
+ if($grouptag) {
+ $newtag = '!' . '[zrl=' . $profile . ']' . $newname . '[/zrl]';
+ $body = str_replace('!' . $name, $newtag, $body);
+ }
+ else {
+ $newtag = '@' . (($exclusive) ? '!' : '') . '[zrl=' . $profile . ']' . $newname . (($forum && ! $trailing_plus_name) ? '+' : '') . '[/zrl]';
+ $body = str_replace('@' . (($exclusive) ? '!' : '') . $name, $newtag, $body);
+ }
+
//append tag to str_tags
if(! stristr($str_tags,$newtag)) {
if(strlen($str_tags))
@@ -2831,7 +2846,7 @@ function item_url_replace($channel,&$item,$old,$new,$oldnick = '') {
*/
function sanitise_acl(&$item) {
if (strlen($item))
- $item = '<' . notags(trim($item)) . '>';
+ $item = '<' . notags(trim(urldecode($item))) . '>';
else
unset($item);
}
@@ -2973,6 +2988,9 @@ function flatten_array_recursive($arr) {
* @param string $s Text to highlight
* @param string $lang Which language should be highlighted
* @return string
+ * Important: The returned text has the text pattern 'http' translated to '%eY9-!' which should be converted back
+ * after further processing. This was done to prevent oembed links from occurring inside code blocks.
+ * See include/bbcode.php
*/
function text_highlight($s, $lang) {
@@ -2993,6 +3011,8 @@ function text_highlight($s, $lang) {
else
$o = $s;
+ $o = str_replace('http','%eY9-!',$o);
+
return('<code>' . $o . '</code>');
}
@@ -3136,3 +3156,9 @@ function ellipsify($s,$maxlen) {
return mb_substr($s,0,$maxlen / 2) . '...' . mb_substr($s,mb_strlen($s) - ($maxlen / 2));
}
+
+function purify_filename($s) {
+ if(($s[0] === '.') || strpos($s,'/') !== false)
+ return '';
+ return $s;
+}
diff --git a/include/xchan.php b/include/xchan.php
index 12eb674fa..8c9c09c72 100644
--- a/include/xchan.php
+++ b/include/xchan.php
@@ -137,3 +137,83 @@ function xchan_fetch($arr) {
}
+function xchan_keychange_table($table,$column,$oldxchan,$newxchan) {
+ $r = q("update $table set $column = '%s' where $column = '%s'",
+ dbesc($newxchan['xchan_hash']),
+ dbesc($oldxchan['xchan_hash'])
+ );
+ return $r;
+}
+
+function xchan_keychange_acl($table,$column,$oldxchan,$newxchan) {
+
+ $allow = (($table === 'channel') ? 'channel_allow_cid' : 'allow_cid');
+ $deny = (($table === 'channel') ? 'channel_deny_cid' : 'deny_cid');
+
+
+ $r = q("select $column, $allow, $deny from $table where ($allow like '%s' or $deny like '%s') ",
+ dbesc('<' . $oldxchan['xchan_hash'] . '>'),
+ dbesc('<' . $oldxchan['xchan_hash'] . '>')
+ );
+
+ if($r) {
+ foreach($r as $rv) {
+ $z = q("update $table set $allow = '%s', $deny = '%s' where $column = %d",
+ dbesc(str_replace('<' . $oldxchan['xchan_hash'] . '>', '<' . $newxchan['xchan_hash'] . '>',
+ $rv[$allow])),
+ dbesc(str_replace('<' . $oldxchan['xchan_hash'] . '>', '<' . $newxchan['xchan_hash'] . '>',
+ $rv[$deny])),
+ intval($rv[$column])
+ );
+ }
+ }
+ return $z;
+}
+
+
+function xchan_change_key($oldx,$newx,$data) {
+
+ $tables = [
+ 'abook' => 'abook_xchan',
+ 'abconfig' => 'xchan',
+ 'group_member' => 'xchan',
+ 'chat' => 'chat_xchan',
+ 'chatpresence' => 'cp_xchan',
+ 'event' => 'event_xchan',
+ 'item' => 'owner_xchan',
+ 'item' => 'author_xchan',
+ 'item' => 'source_xchan',
+ 'mail' => 'from_xchan',
+ 'mail' => 'to_xchan',
+ 'shares' => 'share_xchan',
+ 'source' => 'src_channel_xchan',
+ 'source' => 'src_xchan',
+ 'xchat' => 'xchat_xchan',
+ 'xconfig' => 'xchan',
+ 'xign' => 'xchan',
+ 'xlink' => 'xlink_xchan',
+ 'xprof' => 'xprof_hash',
+ 'xtag' => 'xtag_hash'
+ ];
+
+
+ $acls = [
+ 'channel' => 'channel_id',
+ 'attach' => 'id',
+ 'chatroom' => 'cr_id',
+ 'event' => 'id',
+ 'item' => 'id',
+ 'menu_item' => 'mitem_id',
+ 'obj' => 'obj_id',
+ 'photo' => 'id'
+ ];
+
+
+ foreach($tables as $k => $v) {
+ xchan_keychange_table($k,$v,$oldx,$newx);
+ }
+
+ foreach($acls as $k => $v) {
+ xchan_keychange_acl($k,$v,$oldx,$newx);
+ }
+} \ No newline at end of file
diff --git a/include/zid.php b/include/zid.php
index ee43fd7c8..ce9f70385 100644
--- a/include/zid.php
+++ b/include/zid.php
@@ -81,6 +81,10 @@ function zid($s,$address = '') {
}
+function strip_query_param($s,$param) {
+ return preg_replace('/[\?&]' . $param . '=(.*?)(&|$)/ism','$2',$s);
+}
+
function strip_zids($s) {
return preg_replace('/[\?&]zid=(.*?)(&|$)/ism','$2',$s);
}
@@ -230,3 +234,76 @@ function red_zrlify_img_callback($matches) {
return $matches[0];
}
+function owt_init($token) {
+
+ \Zotlabs\Zot\Verify::purge('owt','3 MINUTE');
+
+ $ob_hash = \Zotlabs\Zot\Verify::get_meta('owt',0,$token);
+
+ if($ob_hash === false) {
+ return;
+ }
+
+ $r = q("select * from hubloc left join xchan on xchan_hash = hubloc_hash
+ where hubloc_addr = '%s' order by hubloc_id desc",
+ dbesc($ob_hash)
+ );
+
+ if(! $r) {
+ // finger them if they can't be found.
+ $j = \Zotlabs\Zot\Finger::run($ob_hash, null);
+ if ($j['success']) {
+ import_xchan($j);
+ $r = q("select * from hubloc left join xchan on xchan_hash = hubloc_hash
+ where hubloc_addr = '%s' order by hubloc_id desc",
+ dbesc($ob_hash)
+ );
+ }
+ }
+ if(! $r) {
+ logger('owt: unable to finger ' . $ob_hash);
+ return;
+ }
+ $hubloc = $r[0];
+
+ $_SESSION['authenticated'] = 1;
+
+ $delegate_success = false;
+ if($_REQUEST['delegate']) {
+ $r = q("select * from channel left join xchan on channel_hash = xchan_hash where xchan_addr = '%s' limit 1",
+ dbesc($_REQUEST['delegate'])
+ );
+ if ($r && intval($r[0]['channel_id'])) {
+ $allowed = perm_is_allowed($r[0]['channel_id'],$hubloc['xchan_hash'],'delegate');
+ if($allowed) {
+ $_SESSION['delegate_channel'] = $r[0]['channel_id'];
+ $_SESSION['delegate'] = $hubloc['xchan_hash'];
+ $_SESSION['account_id'] = intval($r[0]['channel_account_id']);
+ require_once('include/security.php');
+ // this will set the local_channel authentication in the session
+ change_channel($r[0]['channel_id']);
+ $delegate_success = true;
+ }
+ }
+ }
+
+ if (! $delegate_success) {
+ // normal visitor (remote_channel) login session credentials
+ $_SESSION['visitor_id'] = $hubloc['xchan_hash'];
+ $_SESSION['my_url'] = $hubloc['xchan_url'];
+ $_SESSION['my_address'] = $hubloc['hubloc_addr'];
+ $_SESSION['remote_hub'] = $hubloc['hubloc_url'];
+ $_SESSION['DNT'] = 1;
+ }
+
+ $arr = array('xchan' => $hubloc, 'url' => \App::$query_string, 'session' => $_SESSION);
+ call_hooks('magic_auth_success',$arr);
+ \App::set_observer($hubloc);
+ require_once('include/security.php');
+ \App::set_groups(init_groups_visitor($_SESSION['visitor_id']));
+ if(! get_config('system','hide_owa_greeting'))
+ info(sprintf( t('OpenWebAuth: %1$s welcomes %2$s'),\App::get_hostname(), $hubloc['xchan_name']));
+ logger('OpenWebAuth: auth success from ' . $hubloc['xchan_addr']);
+
+
+} \ No newline at end of file
diff --git a/include/zot.php b/include/zot.php
index e120755b5..37c3c1444 100644
--- a/include/zot.php
+++ b/include/zot.php
@@ -31,9 +31,9 @@ require_once('include/perm_upgrade.php');
* @param string $channel_nick a unique nickname of controlling entity
* @returns string
*/
+
function zot_new_uid($channel_nick) {
$rawstr = z_root() . '/' . $channel_nick . '.' . mt_rand();
-
return(base64url_encode(hash('whirlpool', $rawstr, true), true));
}
@@ -49,6 +49,7 @@ function zot_new_uid($channel_nick) {
* @param string $guid
* @param string $guid_sig
*/
+
function make_xchan_hash($guid, $guid_sig) {
return base64url_encode(hash('whirlpool', $guid . $guid_sig, true));
}
@@ -62,17 +63,17 @@ function make_xchan_hash($guid, $guid_sig) {
* @param string $hash - xchan_hash
* @returns array of hubloc (hub location structures)
* * \b hubloc_id int
- * * \b hubloc_guid char(255)
+ * * \b hubloc_guid char(191)
* * \b hubloc_guid_sig text
- * * \b hubloc_hash char(255)
- * * \b hubloc_addr char(255)
+ * * \b hubloc_hash char(191)
+ * * \b hubloc_addr char(191)
* * \b hubloc_flags int
* * \b hubloc_status int
- * * \b hubloc_url char(255)
+ * * \b hubloc_url char(191)
* * \b hubloc_url_sig text
- * * \b hubloc_host char(255)
- * * \b hubloc_callback char(255)
- * * \b hubloc_connect char(255)
+ * * \b hubloc_host char(191)
+ * * \b hubloc_callback char(191)
+ * * \b hubloc_connect char(191)
* * \b hubloc_sitekey text
* * \b hubloc_updated datetime
* * \b hubloc_connected datetime
@@ -97,7 +98,7 @@ function zot_get_hublocs($hash) {
* @param array $channel
* sender channel structure
* @param string $type
- * packet type: one of 'ping', 'pickup', 'purge', 'refresh', 'force_refresh', 'notify', 'auth_check'
+ * packet type: one of 'ping', 'pickup', 'purge', 'refresh', 'keychange', 'force_refresh', 'notify', 'auth_check'
* @param array $recipients
* envelope information, array ( 'guid' => string, 'guid_sig' => string ); empty for public posts
* @param string $remote_key
@@ -111,18 +112,21 @@ function zot_get_hublocs($hash) {
*/
function zot_build_packet($channel, $type = 'notify', $recipients = null, $remote_key = null, $methods = '', $secret = null, $extra = null) {
+ $sig_method = get_config('system','signature_algorithm','sha256');
+
$data = [
'type' => $type,
'sender' => [
'guid' => $channel['channel_guid'],
- 'guid_sig' => base64url_encode(rsa_sign($channel['channel_guid'],$channel['channel_prvkey'])),
+ 'guid_sig' => base64url_encode(rsa_sign($channel['channel_guid'],$channel['channel_prvkey'],$sig_method)),
'url' => z_root(),
- 'url_sig' => base64url_encode(rsa_sign(z_root(),$channel['channel_prvkey'])),
+ 'url_sig' => base64url_encode(rsa_sign(z_root(),$channel['channel_prvkey'],$sig_method)),
'sitekey' => get_config('system','pubkey')
],
'callback' => '/post',
- 'version' => ZOT_REVISION,
- 'encryption' => crypto_methods()
+ 'version' => Zotlabs\Lib\System::get_zot_revision(),
+ 'encryption' => crypto_methods(),
+ 'signing' => signing_methods()
];
if ($recipients) {
@@ -134,7 +138,7 @@ function zot_build_packet($channel, $type = 'notify', $recipients = null, $remot
if ($secret) {
$data['secret'] = preg_replace('/[^0-9a-fA-F]/','',$secret);
- $data['secret_sig'] = base64url_encode(rsa_sign($secret,$channel['channel_prvkey']));
+ $data['secret_sig'] = base64url_encode(rsa_sign($secret,$channel['channel_prvkey'],$sig_method));
}
if ($extra) {
@@ -308,6 +312,7 @@ function zot_refresh($them, $channel = null, $force = false) {
logger('zot_refresh: ' . $url, LOGGER_DATA, LOG_INFO);
+
$result = z_post_url($url . $rhs,$postvars);
if ($result['success']) {
@@ -356,8 +361,6 @@ function zot_refresh($them, $channel = null, $force = false) {
else
$permissions = $j['permissions'];
- $connected_set = false;
-
if($permissions && is_array($permissions)) {
$old_read_stream_perm = get_abconfig($channel['channel_id'],$x['hash'],'their_perms','view_stream');
@@ -529,7 +532,7 @@ function zot_gethub($arr, $multiple = false) {
}
$limit = (($multiple) ? '' : ' limit 1 ');
- $sitekey = ((array_key_exists('sitekey',$arr) && $arr['sitekey']) ? " and hubloc_sitekey = '" . protect_sprintf($arr['sitekey']) . "' " : '');
+ $sitekey = ((array_key_exists('sitekey',$arr) && $arr['sitekey']) ? " and hubloc_sitekey = '" . dbesc(protect_sprintf($arr['sitekey'])) . "' " : '');
$r = q("select hubloc.*, site.site_crypto from hubloc left join site on hubloc_url = site_url
where hubloc_guid = '%s' and hubloc_guid_sig = '%s'
@@ -575,6 +578,8 @@ function zot_register_hub($arr) {
if($arr['url'] && $arr['url_sig'] && $arr['guid'] && $arr['guid_sig']) {
+ $sig_methods = ((array_key_exists('signing',$arr) && is_array($arr['signing'])) ? $arr['signing'] : [ 'sha256' ]);
+
$guid_hash = make_xchan_hash($arr['guid'],$arr['guid_sig']);
$url = $arr['url'] . '/.well-known/zot-info/?f=&guid_hash=' . $guid_hash;
@@ -594,17 +599,18 @@ function zot_register_hub($arr) {
* our current communication.
*/
- if((rsa_verify($arr['guid'],base64url_decode($arr['guid_sig']),$record['key']))
- && (rsa_verify($arr['url'],base64url_decode($arr['url_sig']),$record['key']))
+ foreach($sig_methods as $method) {
+ if((rsa_verify($arr['guid'],base64url_decode($arr['guid_sig']),$record['key'],$method))
+ && (rsa_verify($arr['url'],base64url_decode($arr['url_sig']),$record['key'],$method))
&& ($arr['guid'] === $record['guid'])
&& ($arr['guid_sig'] === $record['guid_sig'])) {
-
- $c = import_xchan($record);
- if($c['success'])
- $result['success'] = true;
- }
- else {
- logger('zot_register_hub: failure to verify returned packet.');
+ $c = import_xchan($record);
+ if($c['success'])
+ $result['success'] = true;
+ }
+ else {
+ logger('zot_register_hub: failure to verify returned packet using ' . $method);
+ }
}
}
}
@@ -657,8 +663,19 @@ function import_xchan($arr,$ud_flags = UPDATE_FLAGS_UPDATED, $ud_arr = null) {
$import_photos = false;
- if(! rsa_verify($arr['guid'],base64url_decode($arr['guid_sig']),$arr['key'])) {
- logger('import_xchan: Unable to verify channel signature for ' . $arr['address']);
+ $sig_methods = ((array_key_exists('signing',$arr) && is_array($arr['signing'])) ? $arr['signing'] : [ 'sha256' ]);
+ $verified = false;
+
+ foreach($sig_methods as $method) {
+ if(! rsa_verify($arr['guid'],base64url_decode($arr['guid_sig']),$arr['key'],$method)) {
+ logger('import_xchan: Unable to verify channel signature for ' . $arr['address'] . ' using ' . $method);
+ continue;
+ }
+ else {
+ $verified = true;
+ }
+ }
+ if(! $verified) {
$ret['message'] = t('Unable to verify channel signature');
return $ret;
}
@@ -700,6 +717,16 @@ function import_xchan($arr,$ud_flags = UPDATE_FLAGS_UPDATED, $ud_arr = null) {
if(intval($r[0]['xchan_pubforum']) != intval($arr['public_forum']))
$pubforum_changed = 1;
+ if($arr['protocols']) {
+ $protocols = implode(',',$arr['protocols']);
+ if($protocols !== 'zot') {
+ set_xconfig($xchan_hash,'system','protocols',$protocols);
+ }
+ else {
+ del_xconfig($xchan_hash,'system','protocols');
+ }
+ }
+
if(($r[0]['xchan_name_date'] != $arr['name_updated'])
|| ($r[0]['xchan_connurl'] != $arr['connections_url'])
|| ($r[0]['xchan_addr'] != $arr['address'])
@@ -917,7 +944,7 @@ function import_xchan($arr,$ud_flags = UPDATE_FLAGS_UPDATED, $ud_arr = null) {
}
elseif(! $ud_flags) {
// nothing changed but we still need to update the updates record
- q("update updates set ud_flags = ( ud_flags | %d ) where ud_addr = '%s' and not (ud_flags & %d)>0 ",
+ q("update updates set ud_flags = ( ud_flags | %d ) where ud_addr = '%s' and not (ud_flags & %d) > 0 ",
intval(UPDATE_FLAGS_UPDATED),
dbesc($address),
intval(UPDATE_FLAGS_UPDATED)
@@ -959,6 +986,18 @@ function zot_process_response($hub, $arr, $outq) {
}
if(is_array($x) && array_key_exists('delivery_report',$x) && is_array($x['delivery_report'])) {
+
+ if(array_key_exists('iv',$x['delivery_report'])) {
+ $j = crypto_unencapsulate($x['delivery_report'],get_config('system','prvkey'));
+ if($j) {
+ $x['delivery_report'] = json_decode($j,true);
+ }
+ if(! (is_array($x['delivery_report']) && count($x['delivery_report']))) {
+ logger('encrypted delivery report could not be decrypted');
+ return;
+ }
+ }
+
foreach($x['delivery_report'] as $xx) {
if(is_array($xx) && array_key_exists('message_id',$xx) && delivery_report_is_storable($xx)) {
q("insert into dreport ( dreport_mid, dreport_site, dreport_recip, dreport_result, dreport_time, dreport_xchan ) values ( '%s', '%s','%s','%s','%s','%s' ) ",
@@ -1030,13 +1069,15 @@ function zot_fetch($arr) {
foreach($ret_hubs as $ret_hub) {
+ $secret = substr(preg_replace('/[^0-9a-fA-F]/','',$arr['secret']),0,64);
+
$data = [
'type' => 'pickup',
'url' => z_root(),
'callback_sig' => base64url_encode(rsa_sign(z_root() . '/post', get_config('system','prvkey'))),
'callback' => z_root() . '/post',
- 'secret' => $arr['secret'],
- 'secret_sig' => base64url_encode(rsa_sign($arr['secret'], get_config('system','prvkey')))
+ 'secret' => $secret,
+ 'secret_sig' => base64url_encode(rsa_sign($secret, get_config('system','prvkey')))
];
$algorithm = zot_best_algorithm($ret_hub['site_crypto']);
@@ -1046,8 +1087,11 @@ function zot_fetch($arr) {
$result = zot_import($fetch, $arr['sender']['url']);
- if($result)
+ if($result) {
+ $result = crypto_encapsulate(json_encode($result),$ret_hub['hubloc_sitekey'], $algorithm);
return $result;
+ }
+
}
return;
@@ -1397,7 +1441,7 @@ function public_recips($msg) {
if($msg['message']['tags']) {
if(is_array($msg['message']['tags']) && $msg['message']['tags']) {
foreach($msg['message']['tags'] as $tag) {
- if(($tag['type'] === 'mention') && (strpos($tag['url'],z_root()) !== false)) {
+ if(($tag['type'] === 'mention' || $tag['type'] === 'forum') && (strpos($tag['url'],z_root()) !== false)) {
$address = basename($tag['url']);
if($address) {
$z = q("select channel_hash as hash from channel where channel_address = '%s'
@@ -2857,8 +2901,14 @@ function import_site($arr, $pubkey) {
$site_directory = DIRECTORY_MODE_NORMAL;
}
+ $site_flags = $site_directory;
+
+ if(array_key_exists('zot',$arr)) {
+ set_sconfig($arr['url'],'system','zot_version',$arr['zot']);
+ }
+
if($exists) {
- if(($siterecord['site_flags'] != $site_directory)
+ if(($siterecord['site_flags'] != $site_flags)
|| ($siterecord['site_access'] != $access_policy)
|| ($siterecord['site_directory'] != $directory_url)
|| ($siterecord['site_sellpage'] != $sellpage)
@@ -2878,7 +2928,7 @@ function import_site($arr, $pubkey) {
$r = q("update site set site_dead = 0, site_location = '%s', site_flags = %d, site_access = %d, site_directory = '%s', site_register = %d, site_update = '%s', site_sellpage = '%s', site_realm = '%s', site_type = %d, site_project = '%s', site_version = '%s', site_crypto = '%s'
where site_url = '%s'",
dbesc($site_location),
- intval($site_directory),
+ intval($site_flags),
intval($access_policy),
dbesc($directory_url),
intval($register_policy),
@@ -2911,7 +2961,7 @@ function import_site($arr, $pubkey) {
'site_location' => $site_location,
'site_url' => $url,
'site_access' => intval($access_policy),
- 'site_flags' => intval($site_directory),
+ 'site_flags' => intval($site_flags),
'site_update' => datetime_convert(),
'site_directory' => $directory_url,
'site_register' => intval($register_policy),
@@ -2947,8 +2997,11 @@ function build_sync_packet($uid = 0, $packet = null, $groups_changed = false) {
logger('build_sync_packet');
- if($packet)
- logger('packet: ' . print_r($packet, true),LOGGER_DATA, LOG_DEBUG);
+
+ $keychange = (($packet && array_key_exists('keychange',$packet)) ? true : false);
+ if($keychange) {
+ logger('keychange sync');
+ }
if(! $uid)
$uid = local_channel();
@@ -2963,6 +3016,7 @@ function build_sync_packet($uid = 0, $packet = null, $groups_changed = false) {
return;
$channel = $r[0];
+
unset($channel['channel_password']);
unset($channel['channel_salt']);
@@ -2973,12 +3027,11 @@ function build_sync_packet($uid = 0, $packet = null, $groups_changed = false) {
}
}
-
if(intval($channel['channel_removed']))
return;
$h = q("select hubloc.*, site.site_crypto from hubloc left join site on site_url = hubloc_url where hubloc_hash = '%s' and hubloc_deleted = 0",
- dbesc($channel['channel_hash'])
+ dbesc(($keychange) ? $packet['keychange']['old_hash'] : $channel['channel_hash'])
);
if(! $h)
@@ -3010,6 +3063,9 @@ function build_sync_packet($uid = 0, $packet = null, $groups_changed = false) {
$env_recips = array();
$env_recips[] = array('guid' => $r[0]['xchan_guid'],'guid_sig' => $r[0]['xchan_guid_sig']);
+ if($packet)
+ logger('packet: ' . print_r($packet, true),LOGGER_DATA, LOG_DEBUG);
+
$info = (($packet) ? $packet : array());
$info['type'] = 'channel_sync';
$info['encoding'] = 'red'; // note: not zot, this packet is very platform specific
@@ -3033,7 +3089,15 @@ function build_sync_packet($uid = 0, $packet = null, $groups_changed = false) {
// don't pass these elements, they should not be synchronised
- $disallowed = array('channel_id','channel_account_id','channel_primary','channel_prvkey','channel_address','channel_deleted','channel_removed','channel_system');
+
+ $disallowed = [
+ 'channel_id','channel_account_id','channel_primary','channel_address',
+ 'channel_deleted','channel_removed','channel_system'
+ ];
+
+ if(! $keychange) {
+ $disallowed[] = 'channel_prvkey';
+ }
if(in_array($k,$disallowed))
continue;
@@ -3093,17 +3157,18 @@ function build_sync_packet($uid = 0, $packet = null, $groups_changed = false) {
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. */
- $result = array();
+ $result = [];
+
+ $keychange = ((array_key_exists('keychange',$arr)) ? true : false);
foreach ($deliveries as $d) {
$r = q("select * from channel where channel_hash = '%s' limit 1",
- dbesc($d['hash'])
+ dbesc(($keychange) ? $arr['keychange']['old_hash'] : $d['hash'])
);
if (! $r) {
@@ -3122,6 +3187,94 @@ function process_channel_sync_delivery($sender, $arr, $deliveries) {
continue;
}
+ if($keychange) {
+ // verify the keychange operation
+ if(! rsa_verify($arr['channel']['channel_pubkey'],base64url_decode($arr['keychange']['new_sig']),$channel['channel_prvkey'])) {
+ logger('sync keychange: verification failed');
+ continue;
+ }
+
+ $sig = base64url_encode(rsa_sign($channel['channel_guid'],$arr['channel']['channel_prvkey']));
+ $hash = make_xchan_hash($channel['channel_guid'],$sig);
+
+
+ $r = q("update channel set channel_prvkey = '%s', channel_pubkey = '%s', channel_guid_sig = '%s',
+ channel_hash = '%s' where channel_id = %d",
+ dbesc($arr['channel']['channel_prvkey']),
+ dbesc($arr['channel']['channel_pubkey']),
+ dbesc($sig),
+ dbesc($hash),
+ intval($channel['channel_id'])
+ );
+ if(! $r) {
+ logger('keychange sync: channel update failed');
+ continue;
+ }
+
+ $r = q("select * from channel where channel_id = %d",
+ intval($channel['channel_id'])
+ );
+
+ if(! $r) {
+ logger('keychange sync: channel retrieve failed');
+ continue;
+ }
+
+ $channel = $r[0];
+
+ $h = q("select * from hubloc where hubloc_hash = '%s' and hubloc_url = '%s' ",
+ dbesc($arr['keychange']['old_hash']),
+ dbesc(z_root())
+ );
+
+ if($h) {
+ foreach($h as $hv) {
+ $hv['hubloc_guid_sig'] = $sig;
+ $hv['hubloc_hash'] = $hash;
+ $hv['hubloc_url_sig'] = base64url_encode(rsa_sign(z_root(),$channel['channel_prvkey']));
+ hubloc_store_lowlevel($hv);
+ }
+ }
+
+ $x = q("select * from xchan where xchan_hash = '%s' ",
+ dbesc($arr['keychange']['old_hash'])
+ );
+
+ $check = q("select * from xchan where xchan_hash = '%s'",
+ dbesc($hash)
+ );
+
+ if(($x) && (! $check)) {
+ $oldxchan = $x[0];
+ foreach($x as $xv) {
+ $xv['xchan_guid_sig'] = $sig;
+ $xv['xchan_hash'] = $hash;
+ $xv['xchan_pubkey'] = $channel['channel_pubkey'];
+ xchan_store_lowlevel($xv);
+ $newxchan = $xv;
+ }
+ }
+
+ $a = q("select * from abook where abook_xchan = '%s' and abook_self = 1",
+ dbesc($arr['keychange']['old_hash'])
+ );
+
+ if($a) {
+ q("update abook set abook_xchan = '%s' where abook_id = %d",
+ dbesc($hash),
+ intval($a[0]['abook_id'])
+ );
+ }
+
+ xchan_change_key($oldxchan,$newxchan,$arr['keychange']);
+
+ // keychange operations can end up in a confused state if you try and sync anything else
+ // besides the channel keys, so ignore any other packets.
+
+ continue;
+ }
+
+
if(array_key_exists('config',$arr) && is_array($arr['config']) && count($arr['config'])) {
foreach($arr['config'] as $cat => $k) {
foreach($arr['config'][$cat] as $k => $v)
@@ -3759,11 +3912,57 @@ function zot_reply_message_request($data) {
json_return_and_die($ret);
}
+function zot_rekey_request($sender,$data) {
+
+ $ret = array('success' => false);
+
+ // newsig is newkey signed with oldkey
+
+ // The original xchan will remain. In Zot/Receiver we will have imported the new xchan and hubloc to verify
+ // the packet authenticity. What we will do now is verify that the keychange operation was signed by the
+ // oldkey, and if so change all the abook, abconfig, group, and permission elements which reference the
+ // old xchan_hash.
+
+ if((! $data['old_key']) && (! $data['new_key']) && (! $data['new_sig']))
+ json_return_and_die($ret);
+
+ $oldhash = make_xchan_hash($data['old_guid'],$data['old_guid_sig']);
+
+ $r = q("select * from xchan where xchan_hash = '%s' limit 1",
+ dbesc($oldhash)
+ );
+
+ if(! $r) {
+ json_return_and_die($ret);
+ }
+
+ $xchan = $r[0];
+
+ if(! rsa_verify($data['new_key'],base64url_decode($data['new_sig']),$xchan['xchan_pubkey'])) {
+ json_return_and_die($ret);
+ }
+
+ $newhash = make_xchan_hash($sender['guid'],$sender['guid_sig']);
+
+ $r = q("select * from xchan where xchan_hash = '%s' limit 1",
+ dbesc($newhash)
+ );
+
+ $newxchan = $r[0];
+
+ xchan_change_key($xchan,$newxchan,$data);
+
+ $ret['success'] = true;
+ json_return_and_die($ret);
+}
+
function zotinfo($arr) {
$ret = array('success' => false);
+ $sig_method = get_config('system','signature_algorithm','sha256');
+
$zhash = ((x($arr,'guid_hash')) ? $arr['guid_hash'] : '');
$zguid = ((x($arr,'guid')) ? $arr['guid'] : '');
$zguid_sig = ((x($arr,'guid_sig')) ? $arr['guid_sig'] : '');
@@ -3845,6 +4044,11 @@ function zotinfo($arr) {
$id = $e['channel_id'];
+ $x = [ 'channel_id' => $id, 'protocols' => ['zot'] ];
+ call_hooks('channel_protocols',$x);
+ $protocols = $x['protocols'];
+
+
$sys_channel = (intval($e['channel_system']) ? true : false);
$special_channel = (($e['channel_pageflags'] & PAGE_PREMIUM) ? true : false);
$adult_channel = (($e['channel_pageflags'] & PAGE_ADULT) ? true : false);
@@ -3927,7 +4131,7 @@ function zotinfo($arr) {
// Communication details
if($token)
- $ret['signed_token'] = base64url_encode(rsa_sign('token.' . $token,$e['channel_prvkey']));
+ $ret['signed_token'] = base64url_encode(rsa_sign('token.' . $token,$e['channel_prvkey'],$sig_method));
$ret['guid'] = $e['xchan_guid'];
@@ -3945,6 +4149,7 @@ function zotinfo($arr) {
$ret['target'] = $ztarget;
$ret['target_sig'] = $zsig;
$ret['searchable'] = $searchable;
+ $ret['protocols'] = $protocols;
$ret['adult_content'] = $adult_channel;
$ret['public_forum'] = $public_forum;
if($deleted)
@@ -3970,7 +4175,7 @@ function zotinfo($arr) {
if($ztarget_hash) {
$permissions['connected'] = false;
- $b = q("select * from abook where abook_xchan = '%s' and abook_channel = %d limit 1",
+ $b = q("select * from abook where abook_xchan = '%s' and abook_channel = %d and abook_pending = 0 limit 1",
dbesc($ztarget_hash),
intval($e['channel_id'])
);
@@ -3994,10 +4199,33 @@ function zotinfo($arr) {
if($x)
$ret['locations'] = $x;
- $ret['site'] = array();
+ $ret['site'] = zot_site_info($e['channel_prvkey']);
+
+ check_zotinfo($e,$x,$ret);
+
+
+ call_hooks('zot_finger',$ret);
+ return($ret);
+
+}
+
+
+function zot_site_info($channel_key = '') {
+
+ $signing_key = get_config('system','prvkey');
+ $sig_method = get_config('system','signature_algorithm','sha256');
+
+ $ret = [];
+ $ret['site'] = [];
$ret['site']['url'] = z_root();
- $ret['site']['url_sig'] = base64url_encode(rsa_sign(z_root(),$e['channel_prvkey']));
- $ret['site']['zot_auth'] = z_root() . '/magic';
+ if($channel_key) {
+ $ret['site']['url_sig'] = base64url_encode(rsa_sign(z_root(),$channel_key,$sig_method));
+ }
+ $ret['site']['url_site_sig'] = base64url_encode(rsa_sign(z_root(),$signing_key,$sig_method));
+ $ret['site']['post'] = z_root() . '/post';
+ $ret['site']['openWebAuth'] = z_root() . '/owa';
+ $ret['site']['authRedirect'] = z_root() . '/magic';
+ $ret['site']['key'] = get_config('system','pubkey');
$dirmode = get_config('system','directory_mode');
if(($dirmode === false) || ($dirmode == DIRECTORY_MODE_NORMAL))
@@ -4014,6 +4242,8 @@ function zotinfo($arr) {
$ret['site']['encryption'] = crypto_methods();
+ $ret['site']['signing'] = signing_methods();
+ $ret['site']['zot'] = Zotlabs\Lib\System::get_zot_revision();
// hide detailed site information if you're off the grid
@@ -4066,15 +4296,10 @@ function zotinfo($arr) {
}
- check_zotinfo($e,$x,$ret);
-
-
- call_hooks('zot_finger',$ret);
- return($ret);
+ return $ret['site'];
}
-
function check_zotinfo($channel,$locations,&$ret) {