diff options
Diffstat (limited to 'include')
-rw-r--r-- | include/api_auth.php | 79 | ||||
-rw-r--r-- | include/attach.php | 31 | ||||
-rw-r--r-- | include/auth.php | 7 | ||||
-rw-r--r-- | include/bbcode.php | 10 | ||||
-rw-r--r-- | include/channel.php | 223 | ||||
-rw-r--r-- | include/config.php | 16 | ||||
-rw-r--r-- | include/connections.php | 20 | ||||
-rw-r--r-- | include/contact_widgets.php | 72 | ||||
-rw-r--r-- | include/conversation.php | 100 | ||||
-rw-r--r-- | include/crypto.php | 14 | ||||
-rw-r--r-- | include/features.php | 9 | ||||
-rw-r--r-- | include/feedutils.php | 136 | ||||
-rw-r--r-- | include/follow.php | 28 | ||||
-rw-r--r-- | include/help.php | 68 | ||||
-rw-r--r-- | include/html2bbcode.php | 3 | ||||
-rw-r--r-- | include/import.php | 14 | ||||
-rwxr-xr-x | include/items.php | 99 | ||||
-rw-r--r-- | include/nav.php | 168 | ||||
-rw-r--r-- | include/network.php | 78 | ||||
-rwxr-xr-x | include/oembed.php | 11 | ||||
-rw-r--r-- | include/perm_upgrade.php | 3 | ||||
-rw-r--r-- | include/photo/photo_driver.php | 56 | ||||
-rw-r--r-- | include/photos.php | 81 | ||||
-rw-r--r-- | include/security.php | 7 | ||||
-rw-r--r-- | include/taxonomy.php | 78 | ||||
-rw-r--r-- | include/text.php | 108 | ||||
-rw-r--r-- | include/xchan.php | 80 | ||||
-rw-r--r-- | include/zid.php | 77 | ||||
-rw-r--r-- | include/zot.php | 335 |
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('/\[\&\;([#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('/([@#]\"\;.*?\"\;)/',$s,$match)) { + if(preg_match_all('/([@#!]\"\;.*?\"\;)/',$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) === '#"') && (substr($tag,-6,6) === '"')) { $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) { |