diff options
Diffstat (limited to 'include')
34 files changed, 1208 insertions, 1315 deletions
diff --git a/include/Import/Importer.php b/include/Import/Importer.php deleted file mode 100644 index 1fa677db0..000000000 --- a/include/Import/Importer.php +++ /dev/null @@ -1,81 +0,0 @@ -<?php /** @file */ - -namespace Hubzilla\Import; - -/** - * @brief Class Import - * - */ -class Import { - - private $credentials = null; - - protected $itemlist = null; - protected $src_items = null; - protected $items = null; - - function get_credentials() { - return $this->credentials; - } - - function get_itemlist() { - return $this->itemlist; - } - - function get_item_ident($item) { - - } - - function get_item($item_ident) { - - } - - function get_taxonomy($item_ident) { - - } - - function get_children($item_ident) { - - } - - function convert_item($item_ident) { - - } - - function convert_taxonomy($item_ident) { - - } - - function convert_child($child) { - - } - - function store($item, $update = false) { - - } - - function run() { - $this->credentials = $this->get_credentials(); - $this->itemlist = $this->get_itemlist(); - if($this->itemlist) { - $this->src_items = array(); - $this->items = array(); - $cnt = 0; - foreach($this->itemlist as $item) { - $ident = $item->get_item_ident($item); - $this->src_items[$ident]['item'] = $this->get_item($ident); - $this->src_items[$ident]['taxonomy'] = $this->get_taxonomy($ident); - $this->src_items[$ident]['children'] = $this->get_children($ident); - $this->items[$cnt]['item'] = $this->convert_item($ident); - $this->items[$cnt]['item']['term'] = $this->convert_taxonomy($ident); - if($this->src_items[$ident]['children']) { - $this->items[$cnt]['children'] = array(); - foreach($this->src_items[$ident]['children'] as $child) { - $this[$cnt]['children'][] = $this->convert_child($child); - } - } - $cnt ++; - } - } - } -}
\ No newline at end of file diff --git a/include/Import/refimport.php b/include/Import/refimport.php deleted file mode 100644 index 04540a9bd..000000000 --- a/include/Import/refimport.php +++ /dev/null @@ -1,284 +0,0 @@ -<?php - -require_once('include/html2bbcode.php'); - -// Sample module for importing conversation data from Reflection CMS. Some preparation was used to -// dump relevant posts, categories and comments into individual JSON files, and also JSON dump of -// the user table to search for avatars. Importation was also batched in sets of 20 posts per page -// visit so as to survive shared hosting process limits. This provides some clues as how to handle -// WordPress imports, which use a somewhat similar DB structure. The batching and individual files -// might not be needed in VPS environments. As such this could be considered an extreme test case, but -// the importation was successful in all regards using this code. The module URL was visited repeatedly -// with a browser until all the posts had been imported. - - -define('REDMATRIX_IMPORTCHANNEL','mike'); -define('REFLECT_EXPORTUSERNAME','mike'); -define('REFLECT_BLOGNAME','Diary and Other Rantings'); -define('REFLECT_BASEURL','http://example.com/'); -define('REFLECT_USERFILE','user.json'); - -// set to true if you need to process everything again -define('REFLECT_OVERWRITE',false); - -// we'll only process a small number of posts at a time on a shared host. - -define('REFLECT_MAXPERRUN',30); - -function reflect_get_channel() { - - // this will be the channel_address or nickname of the red channel - - $c = q("select * from channel left join xchan on channel_hash = xchan_hash - where channel_address = '%s' limit 1", - dbesc(REDMATRIX_IMPORTCHANNEL) - ); - return $c[0]; -} - - -function refimport_content(&$a) { - - $channel = reflect_get_channel(); - - // load the user file. We need that to find the commenter's avatars - - $u = file_get_contents(REFLECT_USERFILE); - if($u) { - $users = json_decode($u,true); - } - - $ignored = 0; - $processed = 0; - - $files = glob('article/*'); - if(! $files) - return; - - foreach($files as $f) { - $s = file_get_contents($f); - $j = json_decode($s,true); - - if(! $j) - continue; - - $arr = array(); - - // see if this article was already processed - $r = q("select * from item where mid = '%s' and uid = %d limit 1", - dbesc($j['guid']), - intval($channel['channel_id']) - ); - if($r) { - if(REFLECT_OVERWRITE) - $arr['id'] = $r[0]['id']; - else { - $ignored ++; - rename($f,str_replace('article','done',$f)); - continue; - } - } - - $arr['uid'] = $channel['channel_account_id']; - $arr['aid'] = $channel['channel_id']; - $arr['mid'] = $arr['parent_mid'] = $j['guid']; - $arr['created'] = $j['created']; - $arr['edited'] = $j['edited']; - $arr['author_xchan'] = $channel['channel_hash']; - $arr['owner_xchan'] = $channel['channel_hash']; - $arr['app'] = REFLECT_BLOGNAME; - - $arr['item_origin'] = 1; - $arr['item_wall'] = 1; - $arr['item_thread_top'] = 1; - - $arr['verb'] = ACTIVITY_POST; - - // this is an assumption - $arr['comment_policy'] = 'contacts'; - - - // import content. In this case the content is XHTML. - - $arr['title'] = html2bbcode($j['title']); - $arr['title'] = htmlspecialchars($arr['title'],ENT_COMPAT,'UTF-8',false); - - - $arr['body'] = html2bbcode($j['body']); - $arr['body'] = htmlspecialchars($arr['body'],ENT_COMPAT,'UTF-8',false); - - - // convert relative urls to other posts on that service to absolute url on our service. - $arr['body'] = preg_replace_callback("/\[url\=\/+article\/(.*?)\](.*?)\[url\]/",'reflect_article_callback',$arr['body']); - - // also import any photos - $arr['body'] = preg_replace_callback("/\[img(.*?)\](.*?)\[\/img\]/",'reflect_photo_callback',$arr['body']); - - - // add categories - - if($j['taxonomy'] && is_array($j['taxonomy']) && count($j['taxonomy'])) { - $arr['term'] = array(); - foreach($j['taxonomy'] as $tax) { - $arr['term'][] = array( - 'uid' => $channel['channel_id'], - 'type' => TERM_CATEGORY, - 'otype' => TERM_OBJ_POST, - 'term' => trim($tax['name']), - 'url' => $channel['xchan_url'] . '?f=&cat=' . urlencode(trim($tax['name'])) - ); - } - } - - // store the item - - if($arr['id']) - item_store_update($arr); - else - item_store($arr); - - // if there are any comments, process them - // $comment['registered'] is somebody with an account on the system. Others are mostly anonymous - - if($j['comments']) { - foreach($j['comments'] as $comment) { - $user = (($comment['registered']) ? reflect_find_user($users,$comment['author']) : null); - reflect_comment_store($channel,$arr,$comment,$user); - } - } - $processed ++; - - if(REFLECT_MAXPERRUN && $processed > REFLECT_MAXPERRUN) - break; - } - return 'processed: ' . $processed . EOL . 'completed: ' . $ignored . EOL; - -} - -function reflect_article_callback($matches) { - return '[zrl=' . z_root() . '/display/'. $matches[1] . ']' . $matches[2] . '[/zrl]'; -} - -function reflect_photo_callback($matches) { - - if(strpos($matches[2],'http') !== false) - return $matches[0]; - - $prefix = REFLECT_BASEURL; - $x = z_fetch_url($prefix.$matches[2],true); - - $hash = basename($matches[2]); - - if($x['success']) { - $channel = reflect_get_channel(); - require_once('include/photos.php'); - $p = photo_upload($channel,$channel, - array('data' => $x['body'], - 'resource_id' => str_replace('-','',$hash), - 'filename' => $hash . '.jpg', - 'type' => 'image/jpeg', - 'visible' => false - ) - ); - - if($p['success']) - $newlink = $p['resource_id'] . '-0.jpg'; - - - // import photo and locate the link for it. - return '[zmg]' . z_root() . '/photo/' . $newlink . '[/zmg]'; - - } - // no replacement. Leave it alone. - return $matches[0]; -} - -function reflect_find_user($users,$name) { - if($users) { - foreach($users as $x) { - if($x['name'] === $name) { - return $x; - } - } - } - - return false; - -} - -function reflect_comment_store($channel,$post,$comment,$user) { - - // if the commenter was the channel owner, use their hubzilla xchan - - if($comment['author'] === REFLECT_EXPORTUSERNAME && $comment['registered']) - $hash = $channel['xchan_hash']; - else { - // we need a unique hash for the commenter. We don't know how many may have supplied - // http://yahoo.com as their URL, so we'll use their avatar guid if they have one. - // anonymous folks may get more than one xchan_hash if they commented more than once. - - $hash = (($comment['registered'] && $user) ? $user['avatar'] : ''); - if(! $hash) - $hash = random_string() . '.unknown'; - - // create an xchan for them which will also import their profile photo - // they will have a network type 'unknown'. - - $x = array( - 'hash' => $hash, - 'guid' => $hash, - 'url' => (($comment['url']) ? $comment['url'] : z_root()), - 'photo' => (($user) ? REFLECT_BASEURL . $user['avatar'] : z_root() . '/' . get_default_profile_photo()), - 'name' => $comment['author'] - ); - xchan_store($x); - - } - - $arr = array(); - - $r = q("select * from item where mid = '%s' and uid = %d limit 1", - dbesc($comment['guid']), - intval($channel['channel_id']) - ); - if($r) { - if(REFLECT_OVERWRITE) - $arr['id'] = $r[0]['id']; - else - return; - } - - // this is a lot like storing the post except for subtle differences, like parent_mid, flags, author_xchan, - // and we don't have a comment edited field so use creation date - - $arr['uid'] = $channel['channel_account_id']; - $arr['aid'] = $channel['channel_id']; - $arr['mid'] = $comment['guid']; - $arr['parent_mid'] = $post['mid']; - $arr['created'] = $comment['created']; - $arr['edited'] = $comment['created']; - $arr['author_xchan'] = $hash; - $arr['owner_xchan'] = $channel['channel_hash']; - $arr['item_origin'] = 1; - $arr['item_wall'] = 1; - $arr['verb'] = ACTIVITY_POST; - $arr['comment_policy'] = 'contacts'; - - - $arr['title'] = html2bbcode($comment['title']); - $arr['title'] = htmlspecialchars($arr['title'],ENT_COMPAT,'UTF-8',false); - - - $arr['body'] = html2bbcode($comment['body']); - $arr['body'] = htmlspecialchars($arr['body'],ENT_COMPAT,'UTF-8',false); - $arr['body'] = preg_replace_callback("/\[url\=\/+article\/(.*?)\](.*?)\[url\]/",'reflect_article_callback',$arr['body']); - $arr['body'] = preg_replace_callback("/\[img(.*?)\](.*?)\[\/img\]/",'reflect_photo_callback',$arr['body']); - - // logger('comment: ' . print_r($arr,true)); - - if($arr['id']) - item_store_update($arr); - else - item_store($arr); - -} diff --git a/include/account.php b/include/account.php index 6c6fdece4..40cf281c3 100644 --- a/include/account.php +++ b/include/account.php @@ -262,24 +262,46 @@ function create_account($arr) { function verify_email_address($arr) { - $hash = random_string(); - - $r = q("INSERT INTO register ( hash, created, uid, password, lang ) VALUES ( '%s', '%s', %d, '%s', '%s' ) ", - dbesc($hash), - dbesc(datetime_convert()), - intval($arr['account']['account_id']), - dbesc('verify'), - dbesc($arr['account']['account_language']) - ); + if(array_key_exists('resend',$arr)) { + $email = $arr['email']; + $a = q("select * from account where account_email = '%s' limit 1", + dbesc($arr['email']) + ); + if(! ($a && ($a[0]['account_flags'] & ACCOUNT_UNVERIFIED))) { + return false; + } + $account = $a[0]; + $v = q("select * from register where uid = %d and password = 'verify' limit 1", + intval($account['account_id']) + ); + if($v) { + $hash = $v[0]['hash']; + } + else { + return false; + } + } + else { + $hash = random_string(24); + + $r = q("INSERT INTO register ( hash, created, uid, password, lang ) VALUES ( '%s', '%s', %d, '%s', '%s' ) ", + dbesc($hash), + dbesc(datetime_convert()), + intval($arr['account']['account_id']), + dbesc('verify'), + dbesc($arr['account']['account_language']) + ); + $account = $arr['account']; + } - push_lang(($arr['account']['account_language']) ? $arr['account']['account_language'] : 'en'); + push_lang(($account['account_language']) ? $account['account_language'] : 'en'); $email_msg = replace_macros(get_intltext_template('register_verify_member.tpl'), [ '$sitename' => get_config('system','sitename'), '$siteurl' => z_root(), '$email' => $arr['email'], - '$uid' => $arr['account']['account_id'], + '$uid' => $account['account_id'], '$hash' => $hash, '$details' => $details ] @@ -508,7 +530,7 @@ function account_deny($hash) { function account_approve($hash) { - $ret = array('success' => false); + $ret = false; // Note: when the password in the register table is 'verify', the uid actually contains the account_id diff --git a/include/acl_selectors.php b/include/acl_selectors.php index 4e203074b..bada3e528 100644 --- a/include/acl_selectors.php +++ b/include/acl_selectors.php @@ -5,104 +5,9 @@ * @package acl_selectors */ -/** - * @brief - * - * @param string $selname - * @param string $selclass - * @param mixed $preselected - * @param number $size - * @return string - */ -function group_select($selname, $selclass, $preselected = false, $size = 4) { - - $o = ''; - - $o .= "<select name=\"{$selname}[]\" id=\"$selclass\" class=\"$selclass\" multiple=\"multiple\" size=\"$size\">\r\n"; - - $r = q("SELECT * FROM groups WHERE deleted = 0 AND uid = %d ORDER BY gname ASC", - intval(local_channel()) - ); - - - $arr = array('group' => $r, 'entry' => $o); - - // e.g. 'network_pre_group_deny', 'profile_pre_group_allow' - - call_hooks(App::$module . '_pre_' . $selname, $arr); - - if($r) { - foreach($r as $rr) { - if((is_array($preselected)) && in_array($rr['id'], $preselected)) - $selected = " selected=\"selected\" "; - else - $selected = ''; - $trimmed = mb_substr($rr['gname'],0,12); - - $o .= "<option value=\"{$rr['id']}\" $selected title=\"{$rr['name']}\" >$trimmed</option>\r\n"; - } - - } - $o .= "</select>\r\n"; - - call_hooks(App::$module . '_post_' . $selname, $o); - - return $o; -} - -function contact_select($selname, $selclass, $preselected = false, $size = 4, $privmail = false, $celeb = false, $privatenet = false, $tabindex = null) { - - $o = ''; - - // When used for private messages, we limit correspondence to mutual DFRN/Friendica friends and the selector - // to one recipient. By default our selector allows multiple selects amongst all contacts. - - $sql_extra = ''; - - $tabindex = ($tabindex > 0 ? 'tabindex="$tabindex"' : ''); - - if($privmail) - $o .= "<select name=\"$selname\" id=\"$selclass\" class=\"$selclass\" size=\"$size\" $tabindex >\r\n"; - else - $o .= "<select name=\"{$selname}[]\" id=\"$selclass\" class=\"$selclass\" multiple=\"multiple\" size=\"$size\" $tabindex>\r\n"; - - $r = q("SELECT abook_id, xchan_name, xchan_url, xchan_photo_s from abook left join xchan on abook_xchan = xchan_hash - where abook_self = 0 and abook_channel = %d - $sql_extra - ORDER BY xchan_name ASC", - intval(local_channel()) - ); - - - $arr = array('contact' => $r, 'entry' => $o); - - // e.g. 'network_pre_contact_deny', 'profile_pre_contact_allow' - - call_hooks(App::$module . '_pre_' . $selname, $arr); - - if($r) { - foreach($r as $rr) { - if((is_array($preselected)) && in_array($rr['id'], $preselected)) - $selected = ' selected="selected" '; - else - $selected = ''; - - $trimmed = mb_substr($rr['xchan_name'], 0, 20); - - $o .= "<option value=\"{$rr['abook_id']}\" $selected title=\"{$rr['xchan_name']}|{$rr['xchan_url']}\" >$trimmed</option>\r\n"; - } - } - - $o .= "</select>\r\n"; - - call_hooks(App::$module . '_post_' . $selname, $o); - - return $o; -} - function fixacl(&$item) { - $item = str_replace(array('<', '>'), array('', ''), $item); + $item = str_replace( [ '<', '>' ], [ '', '' ], $item); } /** diff --git a/include/api_zot.php b/include/api_zot.php index 54f905b4c..1d30a0845 100644 --- a/include/api_zot.php +++ b/include/api_zot.php @@ -6,6 +6,8 @@ api_register_func('api/export/basic','api_export_basic', true); api_register_func('api/red/channel/export/basic','api_export_basic', true); api_register_func('api/z/1.0/channel/export/basic','api_export_basic', true); + api_register_func('api/red/channel/list','api_channel_list', true); + api_register_func('api/z/1.0/channel/list','api_channel_list', true); api_register_func('api/red/channel/stream','api_channel_stream', true); api_register_func('api/z/1.0/channel/stream','api_channel_stream', true); api_register_func('api/red/files','api_attach_list', true); @@ -111,9 +113,31 @@ } } + function api_channel_list($type) { + if(api_user() === false) { + logger('api_channel_stream: no user'); + return false; + } + + $channel = channelx_by_n(api_user()); + if(! $channel) + return false; + $ret = []; + $r = q("select channel_address from channel where channel_account_id = %d", + intval($channel['channel_account_id']) + ); + + if($r) { + foreach($r as $rv) { + $ret[] = $rv['channel_address']; + } + } + + json_return_and_die($ret); + } function api_channel_stream($type) { diff --git a/include/auth.php b/include/auth.php index 78be32bf4..6f5e58361 100644 --- a/include/auth.php +++ b/include/auth.php @@ -261,6 +261,7 @@ else { $verify = account_verify_password($_POST['username'], $_POST['password']); if($verify && array_key_exists('reason',$verify) && $verify['reason'] === 'unvalidated') { notice( t('Email validation is incomplete. Please check your email.')); + goaway(z_root() . '/email_validation/' . bin2hex(trim(escape_tags($_POST['username'])))); } elseif($verify) { $atoken = $verify['xchan']; diff --git a/include/bbcode.php b/include/bbcode.php index 775a91f9a..03a46444b 100644 --- a/include/bbcode.php +++ b/include/bbcode.php @@ -108,7 +108,11 @@ function tryzrlvideo($match) { if($zrl) $link = zid($link); - return '<video controls="controls" preload="none" src="' . str_replace(' ','%20',$link) . '" style="width:100%; max-width:' . App::$videowidth . 'px"><a href="' . str_replace(' ','%20',$link) . '">' . $link . '</a></video>'; + $static_link = get_config('system','video_default_poster','images/video_poster.jpg'); + if($static_link) + $poster = 'poster="' . escape_tags($static_link) . '" ' ; + + return '<video ' . $poster . ' controls="controls" preload="none" src="' . str_replace(' ','%20',$link) . '" style="width:100%; max-width:' . App::$videowidth . 'px"><a href="' . str_replace(' ','%20',$link) . '">' . $link . '</a></video>'; } // [noparse][i]italic[/i][/noparse] turns into @@ -428,6 +432,16 @@ function bb_spoilertag($match) { return '<div onclick="openClose(\'opendiv-' . $rnd . '\'); return false;" class="fakelink">' . $openclose . '</div><blockquote id="opendiv-' . $rnd . '" style="display: none;">' . $text . '</blockquote>'; } +function bb_summary($match) { + $rnd1 = mt_rand(); + $rnd2 = mt_rand(); + $rnd3 = mt_rand(); + $rnd4 = mt_rand(); + + return $match[1] . '<div style="display: block;" id="opendiv-' . $rnd2 . '">' . $match[2] . '</div><div style="display: block;" id="opendiv-' . $rnd3 . '" onclick="openClose(\'opendiv-' . $rnd1 . '\'); openClose(\'opendiv-' . $rnd2 . '\'); openClose(\'opendiv-' . $rnd3 . '\'); openClose(\'opendiv-' . $rnd4 . '\'); return false;" class="fakelink view-article">' . t('View article') . '</div><div style="display: none;" id="opendiv-' . $rnd4 . '" onclick="openClose(\'opendiv-' . $rnd1 . '\'); openClose(\'opendiv-' . $rnd2 . '\'); openClose(\'opendiv-' . $rnd3 . '\'); openClose(\'opendiv-' . $rnd4 . '\'); return false;" class="fakelink view-summary">' . t('View summary') . '</div><div id="opendiv-' . $rnd1 . '" style="display: none;">' . $match[3] . '</div>'; +} + + function bb_definitionList($match) { // $match[1] is the markup styles for the "terms" in the definition list. // $match[2] is the content between the [dl]...[/dl] tags @@ -600,29 +614,47 @@ function bb_observer($Text) { return $Text; } +function bb_code_protect($s) { + return 'b64.^9e%.' . base64_encode($s) . '.b64.$9e%'; +} + +function bb_code_unprotect($s) { + return preg_replace_callback('|b64\.\^9e\%\.(.*?)\.b64\.\$9e\%|ism','bb_code_unprotect_sub',$s); +} + +function bb_code_unprotect_sub($match) { + return base64_decode($match[1]); +} + function bb_code($match) { if(strpos($match[0], "<br />")) - return '<code>' . trim($match[1]) . '</code>'; + return '<pre><code>' . bb_code_protect(trim($match[1])) . '</code></pre>'; else - return '<code class="inline-code">' . trim($match[1]) . '</code>'; + return '<code class="inline-code">' . bb_code_protect(trim($match[1])) . '</code>'; } function bb_code_options($match) { if(strpos($match[0], "<br />")) { $class = ""; + $pre = true; } else { $class = "inline-code"; + $pre = false; } if(strpos($match[1], 'nowrap')) { $style = "overflow-x: auto; white-space: pre;"; } else { $style = ""; } - return '<code class="'. $class .'" style="'. $style .'">' . trim($match[2]) . '</code>'; + if($pre) { + return '<pre><code class="'. $class .'" style="'. $style .'">' . bb_code_protect(trim($match[2])) . '</code></pre>'; + } else { + return '<code class="'. $class .'" style="'. $style .'">' . bb_code_protect(trim($match[2])) . '</code>'; + } } function bb_highlight($match) { - return text_highlight($match[2],strtolower($match[1])); + return bb_code_protect(text_highlight($match[2],strtolower($match[1]))); } function bb_fixtable_lf($match) { @@ -812,6 +844,17 @@ function bbcode($Text, $options = []) { $Text = str_replace(array("\t", " "), array(" ", " "), $Text); + + // Check for [code] text + if (strpos($Text,'[code]') !== false) { + $Text = preg_replace_callback("/\[code\](.*?)\[\/code\]/ism", 'bb_code', $Text); + } + + // Check for [code options] text + if (strpos($Text,'[code ') !== false) { + $Text = preg_replace_callback("/\[code(.*?)\](.*?)\[\/code\]/ism", 'bb_code_options', $Text); + } + // Set up the parameters for a URL search string $URLSearchString = "^\[\]"; // Set up the parameters for a MAIL search string @@ -1052,14 +1095,9 @@ function bbcode($Text, $options = []) { $Text = preg_replace("/\[font=(.*?)\](.*?)\[\/font\]/sm", "<span style=\"font-family: $1;\">$2</span>", $Text); } - // Check for [code] text - if (strpos($Text,'[code]') !== false) { - $Text = preg_replace_callback("/\[code\](.*?)\[\/code\]/ism", 'bb_code', $Text); - } - // Check for [code options] text - if (strpos($Text,'[code ') !== false) { - $Text = preg_replace_callback("/\[code(.*?)\](.*?)\[\/code\]/ism", 'bb_code_options', $Text); + if(strpos($Text,'[/summary]') !== false) { + $Text = preg_replace_callback("/^(.*?)\[summary\](.*?)\[\/summary\](.*?)$/ism", 'bb_summary', $Text); } // Check for [spoiler] text @@ -1273,6 +1311,7 @@ function bbcode($Text, $options = []) { // replace escaped links in code= blocks $Text = str_replace('%eY9-!','http', $Text); + $Text = bb_code_unprotect($Text); $Text = preg_replace('/\[\&\;([#a-z0-9]+)\;\]/', '&$1;', $Text); diff --git a/include/channel.php b/include/channel.php index b9adc588b..a754d3504 100644 --- a/include/channel.php +++ b/include/channel.php @@ -1126,8 +1126,7 @@ function channel_export_items($channel_id, $start, $finish) { /** * @brief Loads a profile into the App structure. * - * The function requires a writeable copy of the main App structure, and the - * nickname of a valid channel. + * The function requires the nickname of a valid channel. * * Permissions of the current observer are checked. If a restricted profile is available * to the current observer, that will be loaded instead of the channel default profile. @@ -1449,6 +1448,7 @@ function profile_sidebar($profile, $block = 0, $show_connect = true, $zcard = fa '$reddress' => $reddress, '$rating' => '', '$contact_block' => $contact_block, + '$change_photo' => t('Change your profile photo'), '$editmenu' => profile_edit_menu($profile['uid']) )); @@ -1896,8 +1896,9 @@ function is_public_profile() { function get_profile_fields_basic($filter = 0) { $profile_fields_basic = (($filter == 0) ? get_config('system','profile_fields_basic') : null); + if(! $profile_fields_basic) - $profile_fields_basic = array('fullname','pdesc','chandesc','comms','gender','dob','dob_tz','address','locality','region','postal_code','country_name','marital','sexual','homepage','hometown','keywords','about','contact'); + $profile_fields_basic = array('fullname','pdesc','chandesc','comms','gender','dob','dob_tz','region','country_name','marital','sexual','homepage','hometown','keywords','about','contact'); $x = array(); if($profile_fields_basic) @@ -1912,7 +1913,7 @@ function get_profile_fields_advanced($filter = 0) { $basic = get_profile_fields_basic($filter); $profile_fields_advanced = (($filter == 0) ? get_config('system','profile_fields_advanced') : null); if(! $profile_fields_advanced) - $profile_fields_advanced = array('partner','howlong','politic','religion','likes','dislikes','interest','channels','music','book','film','tv','romance','employment','education'); + $profile_fields_advanced = array('address','locality','postal_code','partner','howlong','politic','religion','likes','dislikes','interest','channels','music','book','film','tv','romance','employment','education'); $x = array(); if($basic) @@ -2553,10 +2554,10 @@ function channel_remove($channel_id, $local = true, $unset_session = false) { q("DELETE FROM profile WHERE uid = %d", intval($channel_id)); q("DELETE FROM src WHERE src_channel_id = %d", intval($channel_id)); - $r = q("select resource_id FROM attach WHERE uid = %d", intval($channel_id)); + $r = q("select hash FROM attach WHERE uid = %d", intval($channel_id)); if($r) { foreach($r as $rv) { - attach_delete($channel_id,$rv['resource_id']); + attach_delete($channel_id,$rv['hash']); } } diff --git a/include/conversation.php b/include/conversation.php index 77694deb3..6374267eb 100644 --- a/include/conversation.php +++ b/include/conversation.php @@ -974,7 +974,7 @@ function author_is_pmable($xchan, $abook) { if($x['result'] !== 'unset') return $x['result']; - if($xchan['xchan_network'] === 'zot') + if($xchan['xchan_network'] === 'zot' && get_observer_hash()) return true; return false; @@ -1005,7 +1005,7 @@ function thread_author_menu($item, $mode = '') { $contact = App::$contacts[$item['author_xchan']]; else if($local_channel && $item['author']['xchan_addr']) - $follow_url = z_root() . '/follow/?f=&url=' . urlencode($item['author']['xchan_addr']); + $follow_url = z_root() . '/follow/?f=&url=' . urlencode($item['author']['xchan_addr']) . '&interactive=0'; if($item['uid'] > 0 && author_is_pmable($item['author'],$contact)) { @@ -1051,8 +1051,8 @@ function thread_author_menu($item, $mode = '') { 'menu' => 'follow', 'title' => t('Connect'), 'icon' => 'fw', - 'action' => '', - 'href' => $follow_url + 'action' => 'doFollowAuthor(\'' . $follow_url . '\'); return false;', + 'href' => '#', ]; } diff --git a/include/crypto.php b/include/crypto.php index b990b18d9..1040ac29b 100644 --- a/include/crypto.php +++ b/include/crypto.php @@ -31,19 +31,6 @@ function rsa_verify($data,$sig,$key,$alg = 'sha256') { return (($verify > 0) ? true : false); } -function pkcs5_pad ($text, $blocksize) -{ - $pad = $blocksize - (strlen($text) % $blocksize); - return $text . str_repeat(chr($pad), $pad); -} - -function pkcs5_unpad($text) -{ - $pad = ord($text{strlen($text)-1}); - if ($pad > strlen($text)) return false; - if (strspn($text, chr($pad), strlen($text) - $pad) != $pad) return false; - return substr($text, 0, -1 * $pad); -} function AES256CBC_encrypt($data,$key,$iv) { @@ -132,10 +119,27 @@ function crypto_encapsulate($data,$pubkey,$alg='aes256cbc') { } function other_encapsulate($data,$pubkey,$alg) { + if(! $pubkey) logger('no key. data: ' . $data); - $fn = strtoupper($alg) . '_encrypt'; + // This default will change in the future. For now make it backward compatible. + + $padding = OPENSSL_PKCS1_PADDING; + $base = $alg; + + $exts = explode('.',$alg); + if(count($exts) > 1) { + switch($exts[1]) { + case 'oaep': + $padding = OPENSSL_PKCS1_OAEP_PADDING; + break; + } + $base = $exts[0]; + } + + + $fn = strtoupper($base) . '_encrypt'; if(function_exists($fn)) { // A bit hesitant to use openssl_random_pseudo_bytes() as we know @@ -153,14 +157,14 @@ function other_encapsulate($data,$pubkey,$alg) { $iv = openssl_random_pseudo_bytes(256); $result['data'] = base64url_encode($fn($data,$key,$iv),true); // log the offending call so we can track it down - if(! openssl_public_encrypt($key,$k,$pubkey)) { + if(! openssl_public_encrypt($key,$k,$pubkey,$padding)) { $x = debug_backtrace(); logger('RSA failed. ' . print_r($x[0],true)); } $result['alg'] = $alg; $result['key'] = base64url_encode($k,true); - openssl_public_encrypt($iv,$i,$pubkey); + openssl_public_encrypt($iv,$i,$pubkey,$padding); $result['iv'] = base64url_encode($i,true); return $result; } @@ -179,7 +183,7 @@ function crypto_methods() { // The actual methods are responsible for deriving the actual key/iv from the provided parameters; // possibly by truncation or segmentation - though many other methods could be used. - $r = [ 'aes256ctr', 'camellia256cfb', 'cast5cfb', 'aes256cbc', 'aes128cbc', 'cast5cbc' ]; + $r = [ 'aes256ctr.oaep', 'camellia256cfb.oaep', 'cast5cfb.oaep', 'aes256ctr', 'camellia256cfb', 'cast5cfb', 'aes256cbc', 'aes128cbc', 'cast5cbc' ]; call_hooks('crypto_methods',$r); return $r; @@ -220,6 +224,7 @@ function aes_encapsulate($data,$pubkey) { function crypto_unencapsulate($data,$prvkey) { if(! $data) return; + $alg = ((array_key_exists('alg',$data)) ? $data['alg'] : 'aes256cbc'); if($alg === 'aes256cbc') return aes_unencapsulate($data,$prvkey); @@ -229,10 +234,26 @@ function crypto_unencapsulate($data,$prvkey) { } function other_unencapsulate($data,$prvkey,$alg) { - $fn = strtoupper($alg) . '_decrypt'; + + // This default will change in the future. For now make it backward compatible. + + $padding = OPENSSL_PKCS1_PADDING; + $base = $alg; + + $exts = explode('.',$alg); + if(count($exts) > 1) { + switch($exts[1]) { + case 'oaep': + $padding = OPENSSL_PKCS1_OAEP_PADDING; + break; + } + $base = $exts[0]; + } + + $fn = strtoupper($base) . '_decrypt'; if(function_exists($fn)) { - openssl_private_decrypt(base64url_decode($data['key']),$k,$prvkey); - openssl_private_decrypt(base64url_decode($data['iv']),$i,$prvkey); + openssl_private_decrypt(base64url_decode($data['key']),$k,$prvkey,$padding); + openssl_private_decrypt(base64url_decode($data['iv']),$i,$prvkey,$padding); return $fn(base64url_decode($data['data']),$k,$i); } else { @@ -282,37 +303,6 @@ function new_keypair($bits) { } -function pkcs1to8($oldkey,$len) { - - if($len == 4096) - $c = 'g'; - if($len == 2048) - $c = 'Q'; - - if(strstr($oldkey,'BEGIN PUBLIC')) - return $oldkey; - - $oldkey = str_replace('-----BEGIN RSA PUBLIC KEY-----', '', $oldkey); - $oldkey = trim(str_replace('-----END RSA PUBLIC KEY-----', '', $oldkey)); - $key = 'MIICIjANBgkqhkiG9w0BAQEFAAOCA' . $c . '8A' . str_replace("\n", '', $oldkey); - $key = "-----BEGIN PUBLIC KEY-----\n" . wordwrap($key, 64, "\n", true) . "\n-----END PUBLIC KEY-----"; - return $key; -} - -function pkcs8to1($oldkey,$len) { - - if(strstr($oldkey,'BEGIN RSA')) - return $oldkey; - - $oldkey = str_replace('-----BEGIN PUBLIC KEY-----', '', $oldkey); - $oldkey = trim(str_replace('-----END PUBLIC KEY-----', '', $oldkey)); - $key = str_replace("\n",'',$oldkey); - $key = substr($key,32); - $key = "-----BEGIN RSA PUBLIC KEY-----\n" . wordwrap($key, 64, "\n", true) . "\n-----END RSA PUBLIC KEY-----"; - return $key; -} - - function DerToPem($Der, $Private=false) { //Encode: diff --git a/include/datetime.php b/include/datetime.php index 0fcd957be..766c90d16 100644 --- a/include/datetime.php +++ b/include/datetime.php @@ -93,16 +93,6 @@ function datetime_convert($from = 'UTC', $to = 'UTC', $s = 'now', $fmt = "Y-m-d return $d->format($fmt); } - // Slight hackish adjustment so that 'zero' datetime actually returns what is intended - // otherwise we end up with -0001-11-30 ... - // add 32 days so that we at least get year 00, and then hack around the fact that - // months and days always start with 1. - -// if(substr($s,0,10) == '0000-00-00') { -// $d = new DateTime($s . ' + 32 days', new DateTimeZone('UTC')); -// return str_replace('1', '0', $d->format($fmt)); -// } - try { $from_obj = new DateTimeZone($from); } catch(Exception $e) { @@ -135,70 +125,20 @@ function datetime_convert($from = 'UTC', $to = 'UTC', $s = 'now', $fmt = "Y-m-d */ function dob($dob) { - list($year, $month, $day) = sscanf($dob, '%4d-%2d-%2d'); - $f = get_config('system', 'birthday_input_format'); - if (! $f) - $f = 'ymd'; - - if ($dob === '0000-00-00') + if ($dob === '0000-00-00' || $dob === '') $value = ''; else $value = (($year) ? datetime_convert('UTC','UTC',$dob,'Y-m-d') : datetime_convert('UTC','UTC',$dob,'m-d')); - $o = replace_macros(get_markup_template("field_input.tpl"), array('$field' => array( - 'dob', - t('Birthday'), - $value, - ((intval($value)) ? t('Age: ') . age($value,App::$user['timezone'],App::$user['timezone']) : ''), - '', - 'placeholder="' . t('YYYY-MM-DD or MM-DD') .'"' - ))); - + $o = replace_macros(get_markup_template("field_input.tpl"), [ + '$field' => [ 'dob', t('Birthday'), $value, ((intval($value)) ? t('Age: ') . age($value,App::$user['timezone'],App::$user['timezone']) : ''), '', 'placeholder="' . t('YYYY-MM-DD or MM-DD') .'"' ] + ]); -// if ($dob && $dob != '0000-00-00') -// $o = datesel($f,mktime(0,0,0,0,0,1900),mktime(),mktime(0,0,0,$month,$day,$year),'dob'); -// else -// $o = datesel($f,mktime(0,0,0,0,0,1900),mktime(),false,'dob'); return $o; } /** - * @brief Returns a date selector. - * - * @see datetimesel() - * @param string $format - * format string, e.g. 'ymd' or 'mdy'. Not currently supported - * @param DateTime $min - * unix timestamp of minimum date - * @param DateTime $max - * unix timestap of maximum date - * @param DateTime $default - * unix timestamp of default date - * @param string $id - * id and name of datetimepicker (defaults to "datetimepicker") - */ -function datesel($format, $min, $max, $default, $id = 'datepicker') { - return datetimesel($format, $min, $max, $default, '', $id, true, false, '', ''); -} - -/** - * @brief Returns a time selector. - * - * @param string $format - * format string, e.g. 'ymd' or 'mdy'. Not currently supported - * @param string $h - * already selected hour - * @param string $m - * already selected minute - * @param string $id - * id and name of datetimepicker (defaults to "timepicker") - */ -function timesel($format, $h, $m, $id='timepicker') { - return datetimesel($format, new DateTime(), new DateTime(), new DateTime("$h:$m"), '', $id, false, true); -} - -/** * @brief Returns a datetime selector. * * @param string $format @@ -449,12 +389,7 @@ function cal($y = 0, $m = 0, $links = false, $class='') { // month table - start at 1 to match human usage. - $mtab = array(' ', - 'January','February','March', - 'April','May','June', - 'July','August','September', - 'October','November','December' - ); + $mtab = [ ' ', 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ]; $thisyear = datetime_convert('UTC',date_default_timezone_get(),'now','Y'); $thismonth = datetime_convert('UTC',date_default_timezone_get(),'now','m'); @@ -463,7 +398,7 @@ function cal($y = 0, $m = 0, $links = false, $class='') { if (! $m) $m = intval($thismonth); - $dn = array('Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'); + $dn = [ 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday' ]; $f = get_first_dim($y, $m); $l = get_dim($y, $m); $d = 1; @@ -569,17 +504,17 @@ function update_birthdays() { if (! perm_is_allowed($rr['abook_channel'], $rr['xchan_hash'], 'send_stream')) continue; - $ev = array(); - $ev['uid'] = $rr['abook_channel']; - $ev['account'] = $rr['abook_account']; - $ev['event_xchan'] = $rr['xchan_hash']; - $ev['dtstart'] = datetime_convert('UTC', 'UTC', $rr['abook_dob']); - $ev['dtend'] = datetime_convert('UTC', 'UTC', $rr['abook_dob'] . ' + 1 day '); - $ev['adjust'] = intval(feature_enabled($rr['abook_channel'],'smart_birthdays')); - $ev['summary'] = sprintf( t('%1$s\'s birthday'), $rr['xchan_name']); - $ev['description'] = sprintf( t('Happy Birthday %1$s'), - '[zrl=' . $rr['xchan_url'] . ']' . $rr['xchan_name'] . '[/zrl]') ; - $ev['etype'] = 'birthday'; + $ev = [ + 'uid' => $rr['abook_channel'], + 'account' => $rr['abook_account'], + 'event_xchan' => $rr['xchan_hash'], + 'dtstart' => datetime_convert('UTC', 'UTC', $rr['abook_dob']), + 'dtend' => datetime_convert('UTC', 'UTC', $rr['abook_dob'] . ' + 1 day '), + 'adjust' => intval(feature_enabled($rr['abook_channel'],'smart_birthdays')), + 'summary' => sprintf( t('%1$s\'s birthday'), $rr['xchan_name']), + 'description' => sprintf( t('Happy Birthday %1$s'), '[zrl=' . $rr['xchan_url'] . ']' . $rr['xchan_name'] . '[/zrl]'), + 'etype' => 'birthday', + ]; $z = event_store_event($ev); if ($z) { diff --git a/include/dba/dba_driver.php b/include/dba/dba_driver.php index 7e925a106..b3298b673 100755 --- a/include/dba/dba_driver.php +++ b/include/dba/dba_driver.php @@ -1,15 +1,20 @@ <?php +/** + * @file dba_driver.php + * @brief Some database related functions and database classes. + * + * This file contains the abstract database driver class dba_driver, the + * database class DBA and some functions for working with databases. + */ +/** + * @brief Database classs with database factory method. + * + * The factory will return a database driver which is an implementation of the + * abstract dba_driver class. + */ class DBA { - /** - * @file dba_driver.php - * @brief some database related functions and abstract driver class. - * - * This file contains the abstract database driver class dba_driver and some - * functions for working with databases. - */ - static public $dba = null; static public $dbtype = null; static public $scheme = 'mysql'; @@ -21,7 +26,6 @@ class DBA { static public $tquot = "`"; - /** * @brief Returns the database driver object. * @@ -34,14 +38,11 @@ class DBA { * @param bool $install Defaults to false * @return null|dba_driver A database driver object (dba_pdo) or null if no driver found. */ - static public function dba_factory($server,$port,$user,$pass,$db,$dbtype,$install = false) { self::$dba = null; - self::$dbtype = intval($dbtype); - if(self::$dbtype == DBTYPE_POSTGRES) { if(!($port)) $port = 5432; @@ -50,7 +51,6 @@ class DBA { self::$utc_now = "now() at time zone 'UTC'"; self::$tquot = '"'; self::$scheme = 'pgsql'; - } else { @@ -66,40 +66,27 @@ class DBA { require_once('include/dba/dba_pdo.php'); self::$dba = new dba_pdo($server,self::$scheme,$port,$user,$pass,$db,$install); - - if(is_object(self::$dba) && self::$dba->connected) { - - if(strpbrk($server,':;')) { - $dsn = $server; - } - else { - $dsn = self::$scheme . ':host=' . $server . (intval($port) ? '' : ';port=' . $port); - } - $dsn .= ';dbname=' . $db; - - - self::$dba->pdo_set(array($dsn,$user,$pass)); - } define('NULL_DATE', self::$null_date); define('ACTIVE_DBTYPE', self::$dbtype); define('TQUOT', self::$tquot); + return self::$dba; } } /** - * @brief abstract database driver class. + * @brief Abstract database driver class. * - * This class gets extended by the real database driver classes, e.g. dba_mysql, - * dba_mysqli. + * This class gets extended by the real database driver class. We used to have + * dba_mysql, dba_mysqli or dba_postgres, but we moved to PDO and the only + * implemented driver is dba_pdo. */ abstract class dba_driver { // legacy behavior public $db; - protected $pdo = array(); public $debug = 0; public $connected = false; @@ -111,6 +98,7 @@ abstract class dba_driver { * This abstract function needs to be implemented in the real driver. * * @param string $server DB server name + * @param string $scheme DB scheme * @param string $port DB port * @param string $user DB username * @param string $pass DB password @@ -166,6 +154,7 @@ abstract class dba_driver { $platform_name = \Zotlabs\Lib\System::get_platform_name(); if(file_exists('install/' . $platform_name . '/' . \DBA::$install_script)) return 'install/' . $platform_name . '/' . \DBA::$install_script; + return 'install/' . \DBA::$install_script; } @@ -173,7 +162,6 @@ abstract class dba_driver { return \DBA::$tquot; } - function utcnow() { return \DBA::$utc_now; } @@ -232,19 +220,12 @@ abstract class dba_driver { return $str; } - function pdo_set($x) { - $this->pdo = $x; - } - - function pdo_get() { - return $this->pdo; - } - } // end abstract dba_driver class - +// // Procedural functions +// function printable($s) { $s = preg_replace("~([\x01-\x08\x0E-\x0F\x10-\x1F\x7F-\xFF])~",".", $s); @@ -275,7 +256,7 @@ function dbg($state) { * wrapping with intval(). * * @param string $str A string to pass to a DB query - * @return Return an escaped string of the value to pass to a DB query. + * @return string Return an escaped string of the value to pass to a DB query. */ function dbesc($str) { @@ -298,6 +279,7 @@ function dbunescbin($str) { function dbescdate($date) { if(is_null_date($date)) return \DBA::$dba->escape(NULL_DATE); + return \DBA::$dba->escape($date); } @@ -321,22 +303,26 @@ function db_concat($fld, $sep) { return \DBA::$dba->concat($fld, $sep); } +function db_use_index($str) { + return \DBA::$dba->use_index($str); +} + /** * @brief Execute a SQL query with printf style args. * * printf style arguments %s and %d are replaced with variable arguments, which * should each be appropriately dbesc() or intval(). + * * SELECT queries return an array of results or false if SQL or DB error. Other * queries return true if the command was successful or false if it wasn't. * * Example: - * $r = q("SELECT * FROM %s WHERE `uid` = %d", - * 'user', 1); + * @code{.php}$r = q("SELECT * FROM %s WHERE `uid` = %d", + * 'user', 1);@endcode * * @param string $sql The SQL query to execute * @return bool|array */ - function q($sql) { $args = func_get_args(); @@ -355,8 +341,8 @@ function q($sql) { } /* - * This will happen occasionally trying to store the - * session data after abnormal program termination + * This will happen occasionally trying to store the + * session data after abnormal program termination */ db_logger('dba: no database: ' . print_r($args,true),LOGGER_NORMAL,LOG_CRIT); @@ -385,8 +371,8 @@ function dbq($sql) { // Caller is responsible for ensuring that any integer arguments to // dbesc_array are actually integers and not malformed strings containing -// SQL injection vectors. All integer array elements should be specifically -// cast to int to avoid trouble. +// SQL injection vectors. All integer array elements should be specifically +// cast to int to avoid trouble. function dbesc_array_cb(&$item, $key) { if(is_string($item)) { @@ -419,7 +405,7 @@ function dbesc_array(&$arr) { function db_getfunc($f) { $lookup = array( 'rand'=>array( - DBTYPE_MYSQL=>'RAND()', + DBTYPE_MYSQL=>'RAND()', DBTYPE_POSTGRES=>'RANDOM()' ), 'utc_timestamp'=>array( diff --git a/include/dba/dba_mysql.php b/include/dba/dba_mysql.php deleted file mode 100755 index 8b51cf578..000000000 --- a/include/dba/dba_mysql.php +++ /dev/null @@ -1,67 +0,0 @@ -<?php - -require_once('include/dba/dba_driver.php'); - - -class dba_mysql extends dba_driver { - - function connect($server, $scheme, $port, $user,$pass,$db) { - $this->db = mysql_connect($server.":".$port,$user,$pass); - if($this->db && mysql_select_db($db,$this->db)) { - $this->connected = true; - } - if($this->connected) { - return true; - } - return false; - } - - - function q($sql) { - if((! $this->db) || (! $this->connected)) - return false; - - $this->error = ''; - $result = @mysql_query($sql,$this->db); - - - if(mysql_errno($this->db)) - $this->error = mysql_error($this->db); - - if($result === false || $this->error) { - logger('dba_mysql: ' . printable($sql) . ' returned false.' . "\n" . $this->error); - if(file_exists('dbfail.out')) - file_put_contents('dbfail.out', datetime_convert() . "\n" . printable($sql) . ' returned false' . "\n" . $this->error . "\n", FILE_APPEND); - } - - if(($result === true) || ($result === false)) - return $result; - - $r = array(); - if(mysql_num_rows($result)) { - while($x = mysql_fetch_array($result,MYSQL_ASSOC)) - $r[] = $x; - mysql_free_result($result); - if($this->debug) - logger('dba_mysql: ' . printable(print_r($r,true))); - } - return $r; - } - - function escape($str) { - if($this->db && $this->connected) { - return @mysql_real_escape_string($str,$this->db); - } - } - - function close() { - if($this->db) - mysql_close($this->db); - $this->connected = false; - } - - function getdriver() { - return 'mysql'; - } - -} diff --git a/include/dba/dba_mysqli.php b/include/dba/dba_mysqli.php deleted file mode 100755 index 165c8e969..000000000 --- a/include/dba/dba_mysqli.php +++ /dev/null @@ -1,86 +0,0 @@ -<?php /** @file */ - -require_once('include/dba/dba_driver.php'); - -class dba_mysqli extends dba_driver { - - function connect($server,$scheme,$port,$user,$pass,$db) { - if($port) - $this->db = new mysqli($server,$user,$pass,$db, $port); - else - $this->db = new mysqli($server,$user,$pass,$db); - - if($this->db->connect_error) { - $this->connected = false; - $this->error = $this->db->connect_error; - - if(file_exists('dbfail.out')) { - file_put_contents('dbfail.out', datetime_convert() . "\nConnect: " . $this->error . "\n", FILE_APPEND); - } - - return false; - } - else { - $this->connected = true; - return true; - } - } - - function q($sql) { - if((! $this->db) || (! $this->connected)) - return false; - - $this->error = ''; - $result = $this->db->query($sql); - - if($this->db->errno) - $this->error = $this->db->error; - - - if($this->error) { - db_logger('dba_mysqli: ERROR: ' . printable($sql) . "\n" . $this->error, LOGGER_NORMAL, LOG_ERR); - if(file_exists('dbfail.out')) { - file_put_contents('dbfail.out', datetime_convert() . "\n" . printable($sql) . "\n" . $this->error . "\n", FILE_APPEND); - } - } - - if(($result === true) || ($result === false)) { - if($this->debug) { - db_logger('dba_mysqli: DEBUG: ' . printable($sql) . ' returns ' . (($result) ? 'true' : 'false'), LOGGER_NORMAL,(($result) ? LOG_INFO : LOG_ERR)); - } - return $result; - } - - if($this->debug) { - db_logger('dba_mysqli: DEBUG: ' . printable($sql) . ' returned ' . $result->num_rows . ' results.', LOGGER_NORMAL, LOG_INFO); - } - - $r = array(); - if($result->num_rows) { - while($x = $result->fetch_array(MYSQLI_ASSOC)) - $r[] = $x; - $result->free_result(); - if($this->debug) { - db_logger('dba_mysqli: ' . printable(print_r($r,true)), LOGGER_NORMAL, LOG_INFO); - } - } - return $r; - } - - function escape($str) { - if($this->db && $this->connected) { - return @$this->db->real_escape_string($str); - } - } - - function close() { - if($this->db) - $this->db->close(); - $this->connected = false; - } - - function getdriver() { - return 'mysqli'; - } - -}
\ No newline at end of file diff --git a/include/dba/dba_pdo.php b/include/dba/dba_pdo.php index f119d8926..f24c5381a 100755 --- a/include/dba/dba_pdo.php +++ b/include/dba/dba_pdo.php @@ -1,23 +1,30 @@ -<?php /** @file */ +<?php -require_once('include/dba/dba_driver.php'); +require_once 'include/dba/dba_driver.php'; +/** + * @brief PDO based database driver. + * + */ class dba_pdo extends dba_driver { - public $driver_dbtype = null; - function connect($server,$scheme,$port,$user,$pass,$db) { - + /** + * {@inheritDoc} + * @see dba_driver::connect() + */ + function connect($server, $scheme, $port, $user, $pass, $db) { + $this->driver_dbtype = $scheme; if(strpbrk($server,':;')) { $dsn = $server; } else { - $dsn = $this->driver_dbtype . ':host=' . $server . (intval($port) ? '' : ';port=' . $port); + $dsn = $this->driver_dbtype . ':host=' . $server . (intval($port) ? ';port=' . $port : ''); } - + $dsn .= ';dbname=' . $db; try { @@ -36,10 +43,19 @@ class dba_pdo extends dba_driver { $this->q("SET standard_conforming_strings = 'off'; SET backslash_quote = 'on';"); $this->connected = true; - return true; + return true; } + /** + * {@inheritDoc} + * @see dba_driver::q() + * + * @return bool|array|PDOStatement + * - \b false if not connected or PDOException occured on query + * - \b array with results on a SELECT query + * - \b PDOStatement on a non SELECT SQL query + */ function q($sql) { if((! $this->db) || (! $this->connected)) return false; @@ -50,14 +66,15 @@ class dba_pdo extends dba_driver { } } + $result = null; $this->error = ''; - $select = ((stripos($sql,'select') === 0) ? true : false); + $select = ((stripos($sql, 'select') === 0) ? true : false); try { $result = $this->db->query($sql, PDO::FETCH_ASSOC); } catch(PDOException $e) { - + $this->error = $e->getMessage(); if($this->error) { db_logger('dba_pdo: ERROR: ' . printable($sql) . "\n" . $this->error, LOGGER_NORMAL, LOG_ERR); @@ -82,11 +99,10 @@ class dba_pdo extends dba_driver { } if($this->debug) { - db_logger('dba_pdo: DEBUG: ' . printable($sql) . ' returned ' . count($r) . ' results.', LOGGER_NORMAL, LOG_INFO); + db_logger('dba_pdo: DEBUG: ' . printable($sql) . ' returned ' . count($r) . ' results.', LOGGER_NORMAL, LOG_INFO); db_logger('dba_pdo: ' . printable(print_r($r,true)), LOGGER_NORMAL, LOG_INFO); } - return (($this->error) ? false : $r); } @@ -99,9 +115,10 @@ class dba_pdo extends dba_driver { function close() { if($this->db) $this->db = null; + $this->connected = false; } - + function concat($fld,$sep) { if($this->driver_dbtype === 'pgsql') { return 'string_agg(' . $fld . ',\'' . $sep . '\')'; @@ -111,6 +128,15 @@ class dba_pdo extends dba_driver { } } + function use_index($str) { + if($this->driver_dbtype === 'pgsql') { + return ''; + } + else { + return 'USE INDEX( ' . $str . ')'; + } + } + function quote_interval($txt) { if($this->driver_dbtype === 'pgsql') { return "'$txt'"; @@ -131,7 +157,7 @@ class dba_pdo extends dba_driver { return $this->escape($str); } } - + function unescapebin($str) { if($this->driver_dbtype === 'pgsql' && (! is_null($str))) { $x = ''; @@ -154,4 +180,4 @@ class dba_pdo extends dba_driver { return 'pdo'; } -}
\ No newline at end of file +} diff --git a/include/dba/dba_postgres.php b/include/dba/dba_postgres.php deleted file mode 100644 index 560d8da60..000000000 --- a/include/dba/dba_postgres.php +++ /dev/null @@ -1,117 +0,0 @@ -<?php - -require_once('include/dba/dba_driver.php'); - - -class dba_postgres extends dba_driver { - const INSTALL_SCRIPT='install/schema_postgres.sql'; - const NULL_DATE = '0001-01-01 00:00:00'; - const UTC_NOW = "now() at time zone 'UTC'"; - const TQUOT = '"'; - - function connect($server,$scheme,$port,$user,$pass,$db) { - if(!$port) $port = 5432; - $connstr = 'host=' . $server . ' port='.$port . ' user=' . $user . ' password=' . $pass . ' dbname='. $db; - $this->db = pg_connect($connstr); - if($this->db !== false) { - $this->connected = true; - } else { - $this->connected = false; - } - $this->q("SET standard_conforming_strings = 'off'; SET backslash_quote = 'on';"); // emulate mysql string escaping to prevent massive code-clobber - return $this->connected; - } - - function q($sql) { - if((! $this->db) || (! $this->connected)) - return false; - - if(!strpos($sql, ';')) - $sql .= ';'; - - if(strpos($sql, '`')) // this is a hack. quoted identifiers should be replaced everywhere in the code with dbesc_identifier(), remove this once it is - $sql = str_replace('`', '"', $sql); - - $this->error = ''; - $result = @pg_query($this->db, $sql); - if(file_exists('db-allqueries.out')) { - $bt = debug_backtrace(); - $trace = array(); - foreach($bt as $frame) { - if(!empty($frame['file']) && @strstr($frame['file'], $_SERVER['DOCUMENT_ROOT'])) - $frame['file'] = substr($frame['file'], strlen($_SERVER['DOCUMENT_ROOT'])+1); - - $trace[] = $frame['file'] . ':' . $frame['function'] . '():' . $frame['line'] ; - } - $compact = join(', ', $trace); - file_put_contents('db-allqueries.out', datetime_convert() . ": " . $sql . ' is_resource: '.var_export(is_resource($result), true).', backtrace: '.$compact."\n\n", FILE_APPEND); - } - - if($result === false) - $this->error = pg_last_error($this->db); - - if($result === false || $this->error) { - //db_logger('dba_postgres: ' . printable($sql) . ' returned false.' . "\n" . $this->error); - if(file_exists('dbfail.out')) - file_put_contents('dbfail.out', datetime_convert() . "\n" . printable($sql) . ' returned false' . "\n" . $this->error . "\n", FILE_APPEND); - } - - if(($result === true) || ($result === false)) - return $result; - - if(pg_result_status($result) == PGSQL_COMMAND_OK) - return true; - - $r = array(); - if(pg_num_rows($result)) { - while($x = pg_fetch_array($result, null, PGSQL_ASSOC)) - $r[] = $x; - pg_free_result($result); - if($this->debug) - db_logger('dba_postgres: ' . printable(print_r($r,true))); - } - return $r; - } - - function escape($str) { - if($this->db && $this->connected) { - $x = @pg_escape_string($this->db, $str); - return $x; - } - } - - function escapebin($str) { - return pg_escape_bytea($str); - } - - function unescapebin($str) { - return pg_unescape_bytea($str); - } - - function close() { - if($this->db) - pg_close($this->db); - $this->connected = false; - } - - function quote_interval($txt) { - return "'$txt'"; - } - - function escape_identifier($str) { - return pg_escape_identifier($this->db, $str); - } - - function optimize_table($table) { - // perhaps do some equivalent thing here, vacuum, etc? I think this is the DBA's domain anyway. Applications should not need to muss with this. - // for now do nothing without a compelling reason. function overrides default legacy mysql. - } - - function concat($fld, $sep) { - return 'string_agg(' . $fld . ',\'' . $sep . '\')'; - } - - function getdriver() { - return 'pgsql'; - } -}
\ No newline at end of file diff --git a/include/event.php b/include/event.php index 282c1a9d7..1077a3c64 100644 --- a/include/event.php +++ b/include/event.php @@ -6,6 +6,8 @@ use Sabre\VObject; +require_once('include/bbcode.php'); + /** * @brief Returns an event as HTML. * @@ -14,7 +16,6 @@ use Sabre\VObject; */ function format_event_html($ev) { - require_once('include/bbcode.php'); if(! ((is_array($ev)) && count($ev))) return ''; @@ -192,7 +193,7 @@ function format_todo_ical($ev) { function format_ical_text($s) { - require_once('include/bbcode.php'); + require_once('include/html2plain.php'); $s = html2plain(bbcode($s)); @@ -983,7 +984,6 @@ function event_store_item($arr, $event) { require_once('include/datetime.php'); require_once('include/items.php'); - require_once('include/bbcode.php'); $item = null; @@ -1200,6 +1200,13 @@ function event_store_item($arr, $event) { )); } + // propagate the event resource_id so that posts containing it are easily searchable in downstream copies + // of the item which have not stored the actual event. Required for Diaspora event federation as Diaspora + // event_participation messages refer to the event resource_id as a parent, while out own event attendance + // activities refer to the item message_id as the parent. + + set_iconfig($item_arr, 'system','event_id',$event['event_hash'],true); + $res = item_store($item_arr); $item_id = $res['item_id']; diff --git a/include/features.php b/include/features.php index 8601ff79e..993266977 100644 --- a/include/features.php +++ b/include/features.php @@ -54,14 +54,6 @@ function get_features($filtered = true) { t('General Features'), - [ - 'multi_profiles', - t('Multiple Profiles'), - t('Ability to create multiple profiles'), - false, - get_config('feature_lock','multi_profiles'), - feature_level('multi_profiles',3), - ], [ 'advanced_profiles', @@ -126,7 +118,7 @@ function get_features($filtered = true) { feature_level('cards',1), ], -/* reserved, work in progress + [ 'articles', t('Articles'), @@ -135,7 +127,7 @@ function get_features($filtered = true) { get_config('feature_lock','articles'), feature_level('articles',1), ], -*/ + [ 'nav_channel_select', t('Navigation Channel Select'), @@ -163,14 +155,6 @@ function get_features($filtered = true) { feature_level('ajaxchat',1), ], - [ - 'permcats', - t('Permission Groups'), - t('Provide alternate connection permission roles.'), - false, - get_config('feature_lock','permcats'), - feature_level('permcats',2), - ], [ 'smart_birthdays', @@ -190,6 +174,16 @@ function get_features($filtered = true) { feature_level('event_tz_select',2), ], + + [ + 'premium_channel', + t('Premium Channel'), + t('Allows you to set restrictions and terms on those that connect with your channel'), + false, + get_config('feature_lock','premium_channel'), + feature_level('premium_channel',4), + ], + [ 'advanced_dirsearch', t('Advanced Directory Search'), @@ -209,6 +203,58 @@ function get_features($filtered = true) { ], ], + + 'access_control' => [ + t('Access Control and Permissions'), + + [ + 'groups', + t('Privacy Groups'), + t('Enable management and selection of privacy groups'), + true, + get_config('feature_lock','groups'), + feature_level('groups',0), + ], + + [ + 'multi_profiles', + t('Multiple Profiles'), + t('Ability to create multiple profiles'), + false, + get_config('feature_lock','multi_profiles'), + feature_level('multi_profiles',3), + ], + + + [ + 'permcats', + t('Permission Groups'), + t('Provide alternate connection permission roles.'), + false, + get_config('feature_lock','permcats'), + feature_level('permcats',2), + ], + + [ + 'oauth_clients', + t('OAuth Clients'), + t('Manage authenticatication tokens for mobile and remote apps.'), + false, + get_config('feature_lock','oauth_clients'), + feature_level('oauth_clients',1), + ], + + [ + 'access_tokens', + t('Access Tokens'), + t('Create access tokens so that non-members can access private content.'), + false, + get_config('feature_lock','access_tokens'), + feature_level('access_tokens',2), + ], + + ], + // Post composition 'composition' => [ @@ -302,14 +348,6 @@ function get_features($filtered = true) { feature_level('archives',1), ], - [ - 'groups', - t('Privacy Groups'), - t('Enable management and selection of privacy groups'), - true, - get_config('feature_lock','groups'), - feature_level('groups',0), - ], [ 'savedsearch', @@ -374,15 +412,6 @@ function get_features($filtered = true) { t('Post/Comment Tools'), [ - 'markdown', - t('Markdown'), - t('Use markdown for editing posts'), - false, - get_config('feature_lock','markdown'), - feature_level('markdown',2), - ], - - [ 'commtag', t('Community Tagging'), t('Ability to tag existing posts'), @@ -447,16 +476,10 @@ function get_features($filtered = true) { ], ]; + $x = [ 'features' => $arr, ]; + call_hooks('get_features',$x); - $arr['general'][] = [ - 'premium_channel', - t('Premium Channel'), - t('Allows you to set restrictions and terms on those that connect with your channel'), - false, - get_config('feature_lock','premium_channel'), - feature_level('premium_channel',4), - ]; - + $arr = $x['features']; $techlevel = get_account_techlevel(); @@ -490,6 +513,6 @@ function get_features($filtered = true) { else { $narr = $arr; } - call_hooks('get_features',$narr); + return $narr; } diff --git a/include/feedutils.php b/include/feedutils.php index 4638ef66a..369193fce 100644 --- a/include/feedutils.php +++ b/include/feedutils.php @@ -15,15 +15,6 @@ */ function get_public_feed($channel, $params) { -/* $type = 'xml'; - $begin = NULL_DATE; - $end = ''; - $start = 0; - $records = 40; - $direction = 'desc'; - $pages = 0; -*/ - if(! $params) $params = []; @@ -106,23 +97,15 @@ function get_feed_for($channel, $observer_hash, $params) { $owner = atom_render_author('zot:owner',$channel); $atom .= replace_macros($feed_template, array( - '$version' => xmlify(Zotlabs\Lib\System::get_project_version()), - '$red' => xmlify(Zotlabs\Lib\System::get_platform_name()), - '$feed_id' => xmlify($channel['xchan_url']), - '$feed_title' => xmlify($channel['channel_name']), - '$feed_updated' => xmlify(datetime_convert('UTC', 'UTC', 'now', ATOM_TIME)), - '$author' => $feed_author, - '$owner' => $owner, - '$name' => xmlify($channel['channel_name']), - '$profile_page' => xmlify($channel['xchan_url']), - '$mimephoto' => xmlify($channel['xchan_photo_mimetype']), - '$photo' => xmlify($channel['xchan_photo_l']), - '$thumb' => xmlify($channel['xchan_photo_m']), - '$picdate' => '', - '$uridate' => '', - '$namdate' => '', - '$birthday' => '', - '$community' => '', + '$version' => xmlify(Zotlabs\Lib\System::get_project_version()), + '$generator' => xmlify(Zotlabs\Lib\System::get_platform_name()), + '$generator_uri' => 'https://hubzilla.org', + '$feed_id' => xmlify($channel['xchan_url']), + '$feed_title' => xmlify($channel['channel_name']), + '$feed_updated' => xmlify(datetime_convert('UTC', 'UTC', 'now', ATOM_TIME)), + '$author' => $feed_author, + '$owner' => $owner, + '$profile_page' => xmlify($channel['xchan_url']), )); @@ -270,19 +253,18 @@ function construct_activity_target($item) { return ''; } + /** - * @brief Return an array with a parsed atom item. + * @brief Return an array with a parsed atom author. * * @param SimplePie $feed - * @param array $item - * @param[out] array $author - * @return array Associative array with the parsed item data + * @param SimplePie $item + * @return array $author */ -function get_atom_elements($feed, $item, &$author) { - require_once('include/html2bbcode.php'); +function get_atom_author($feed, $item) { - $res = array(); + $author = []; $found_author = $item->get_author(); if($found_author) { @@ -307,52 +289,6 @@ function get_atom_elements($feed, $item, &$author) { if(substr($author['author_link'],-1,1) == '/') $author['author_link'] = substr($author['author_link'],0,-1); - $res['mid'] = normalise_id(unxmlify($item->get_id())); - $res['title'] = unxmlify($item->get_title()); - $res['body'] = unxmlify($item->get_content()); - $res['plink'] = unxmlify($item->get_link(0)); - $res['item_rss'] = 1; - - - $summary = unxmlify($item->get_description(true)); - - // removing the content of the title if its identically to the body - // This helps with auto generated titles e.g. from tumblr - - if (title_is_body($res['title'], $res['body'])) - $res['title'] = ""; - - if($res['plink']) - $base_url = implode('/', array_slice(explode('/',$res['plink']),0,3)); - else - $base_url = ''; - - - $rawcreated = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'published'); - if($rawcreated) - $res['created'] = unxmlify($rawcreated[0]['data']); - - $rawedited = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'updated'); - if($rawedited) - $res['edited'] = unxmlify($rawedited[0]['data']); - - if((x($res,'edited')) && (! (x($res,'created')))) - $res['created'] = $res['edited']; - - if(! $res['created']) - $res['created'] = $item->get_date('c'); - - if(! $res['edited']) - $res['edited'] = $item->get_date('c'); - - $rawverb = $item->get_item_tags(NAMESPACE_ACTIVITY, 'verb'); - - // select between supported verbs - - if($rawverb) { - $res['verb'] = unxmlify($rawverb[0]['data']); - } - // look for a photo. We should check media size and find the best one, // but for now let's just find any author photo @@ -431,6 +367,122 @@ function get_atom_elements($feed, $item, &$author) { } } + $rawowner = $item->get_item_tags(NAMESPACE_DFRN, 'owner'); + if(! $rawowner) + $rawowner = $item->get_item_tags(NAMESPACE_ZOT, 'owner'); + + if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data']) + $author['owner_name'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data']); + elseif($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data']) + $author['owner_name'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data']); + if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data']) + $author['owner_link'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data']); + elseif($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data']) + $author['owner_link'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data']); + + if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) { + $base = $rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']; + + foreach($base as $link) { + if(!x($author, 'owner_photo') || ! $author['owner_photo']) { + if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar') + $author['owner_photo'] = unxmlify($link['attribs']['']['href']); + } + } + } + + // build array to pass to hook + $arr = [ + 'feed' => $feed, + 'item' => $item, + 'author' => $author + ]; + /** + * @hooks parse_atom + * * \e SimplePie \b feed - The original SimplePie feed + * * \e SimplePie \b item + * * \e array \b result - the result array that will also get returned + */ + call_hooks('parse_atom_author', $arr); + + logger('author: ' . print_r($arr['author'], true), LOGGER_DATA); + + return $arr['author']; +} + + +/** + * @brief Return an array with a parsed atom item. + * + * @param SimplePie $feed + * @param SimplePie $item + * @param[out] array $author + * @return array Associative array with the parsed item data + */ + +function get_atom_elements($feed, $item) { + + require_once('include/html2bbcode.php'); + + $res = array(); + + + $res['mid'] = normalise_id(unxmlify($item->get_id())); + $res['title'] = unxmlify($item->get_title()); + $res['body'] = unxmlify($item->get_content()); + $res['plink'] = unxmlify($item->get_link(0)); + $res['item_rss'] = 1; + + + $summary = unxmlify($item->get_description(true)); + + if($summary === $res['body']) + $summary = ''; + + if(($summary) && ((strpos($summary,'<') !== false) || (strpos($summary,'>') !== false))) { + $summary = purify_html($summary); + $summary = html2bbcode($summary); + } + + + + // removing the content of the title if its identically to the body + // This helps with auto generated titles e.g. from tumblr + + if (title_is_body($res['title'], $res['body'])) + $res['title'] = ""; + + if($res['plink']) + $base_url = implode('/', array_slice(explode('/',$res['plink']),0,3)); + else + $base_url = ''; + + + $rawcreated = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'published'); + if($rawcreated) + $res['created'] = unxmlify($rawcreated[0]['data']); + + $rawedited = $item->get_item_tags(SIMPLEPIE_NAMESPACE_ATOM_10, 'updated'); + if($rawedited) + $res['edited'] = unxmlify($rawedited[0]['data']); + + if((x($res,'edited')) && (! (x($res,'created')))) + $res['created'] = $res['edited']; + + if(! $res['created']) + $res['created'] = $item->get_date('c'); + + if(! $res['edited']) + $res['edited'] = $item->get_date('c'); + + $rawverb = $item->get_item_tags(NAMESPACE_ACTIVITY, 'verb'); + + // select between supported verbs + + if($rawverb) { + $res['verb'] = unxmlify($rawverb[0]['data']); + } + $rawcnv = $item->get_item_tags(NAMESPACE_OSTATUS, 'conversation'); if($rawcnv) { // new style @@ -445,7 +497,7 @@ function get_atom_elements($feed, $item, &$author) { } } - $ostatus_protocol = (($ostatus_conversation) ? true : false); + $ostatus_protocol = (($ostatus_conversation || $res['verb']) ? true : false); $mastodon = (($item->get_item_tags('http://mastodon.social/schema/1.0','scope')) ? true : false); if($mastodon) { @@ -454,6 +506,8 @@ function get_atom_elements($feed, $item, &$author) { $res['item_private'] = 1; } + logger('ostatus_protocol: ' . intval($ostatus_protocol), LOGGER_DEBUG); + $apps = $item->get_item_tags(NAMESPACE_STATUSNET, 'notice_info'); if($apps && $apps[0]['attribs']['']['source']) { $res['app'] = strip_tags(unxmlify($apps[0]['attribs']['']['source'])); @@ -557,9 +611,8 @@ function get_atom_elements($feed, $item, &$author) { ); } - // turn Mastodon content warning into a #nsfw hashtag - if($mastodon && $summary) { - $res['body'] = $summary . "\n\n" . $res['body'] . "\n\n#ContentWarning\n"; + if($summary && $res['body']) { + $res['body'] = '[summary]' . $summary . '[/summary]' . $res['body']; } @@ -588,29 +641,6 @@ function get_atom_elements($feed, $item, &$author) { $res['created'] = datetime_convert('UTC','UTC',$res['created']); $res['edited'] = datetime_convert('UTC','UTC',$res['edited']); - $rawowner = $item->get_item_tags(NAMESPACE_DFRN, 'owner'); - if(! $rawowner) - $rawowner = $item->get_item_tags(NAMESPACE_ZOT, 'owner'); - - if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data']) - $author['owner_name'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['name'][0]['data']); - elseif($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data']) - $author['owner_name'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['name'][0]['data']); - if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data']) - $author['owner_link'] = unxmlify($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['uri'][0]['data']); - elseif($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data']) - $author['owner_link'] = unxmlify($rawowner[0]['child'][NAMESPACE_DFRN]['uri'][0]['data']); - - if($rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']) { - $base = $rawowner[0]['child'][SIMPLEPIE_NAMESPACE_ATOM_10]['link']; - - foreach($base as $link) { - if(!x($author, 'owner_photo') || ! $author['owner_photo']) { - if($link['attribs']['']['rel'] === 'photo' || $link['attribs']['']['rel'] === 'avatar') - $author['owner_photo'] = unxmlify($link['attribs']['']['href']); - } - } - } $rawgeo = $item->get_item_tags(NAMESPACE_GEORSS, 'point'); if($rawgeo) @@ -773,20 +803,17 @@ function get_atom_elements($feed, $item, &$author) { $arr = [ 'feed' => $feed, 'item' => $item, - 'author' => $author, 'result' => $res ]; /** * @hooks parse_atom * * \e SimplePie \b feed - The original SimplePie feed - * * \e array \b item - * * \e array \b author + * * \e SimplePie \b item * * \e array \b result - the result array that will also get returned */ call_hooks('parse_atom', $arr); - logger('author: ' .print_r($arr['author'], true), LOGGER_DATA); - logger('result: ' .print_r($arr['result'], true), LOGGER_DATA); + logger('result: ' . print_r($arr['result'], true), LOGGER_DATA); return $arr['result']; } @@ -985,9 +1012,9 @@ function process_feed_tombstones($feed,$importer,$contact,$pass) { * @param string $xml * The (atom) feed to consume - RSS isn't as fully supported but may work for simple feeds. * @param array $importer - * The contact_record (joined to user_record) of the local user who owns this + * The channel record of the local user who owns this * relationship. It is this person's stuff that is going to be updated. - * @param[in,out] array $contact + * @param[in,out] array $contact (abook record joined to xchan record) * The person who is sending us stuff. If not set, we MAY be processing a "follow" activity * from an external network and MAY create an appropriate contact record. Otherwise, we MUST * have a contact record. @@ -1074,8 +1101,8 @@ function consume_feed($xml, $importer, &$contact, $pass = 0) { // Have we seen it? If not, import it. - $author = array(); - $datarray = get_atom_elements($feed,$item,$author); + $author = get_atom_author($feed,$item); + $datarray = get_atom_elements($feed,$item); if(! $datarray['mid']) continue; @@ -1327,8 +1354,8 @@ function consume_feed($xml, $importer, &$contact, $pass = 0) { // Head post of a conversation. Have we seen it? If not, import it. - $author = array(); - $datarray = get_atom_elements($feed,$item,$author); + $author = get_atom_author($feed,$item); + $datarray = get_atom_elements($feed,$item); if(! $datarray['mid']) continue; @@ -1530,11 +1557,11 @@ function normalise_id($id) { */ function process_salmon_feed($xml, $importer) { - $ret = array(); + $ret = []; if(! strlen($xml)) { logger('process_feed: empty input'); - return; + return $ret; } $feed = new SimplePie(); @@ -1548,8 +1575,10 @@ function process_salmon_feed($xml, $importer) { $feed->init(); - if($feed->error()) + if($feed->error()) { logger('Error parsing XML: ' . $feed->error()); + return $ret; + } $permalink = $feed->get_permalink(); @@ -1576,16 +1605,13 @@ function process_salmon_feed($xml, $importer) { if($is_reply) $ret['parent_mid'] = $parent_mid; - $ret['author'] = array(); - - $datarray = get_atom_elements($feed, $item, $ret['author']); + $ret['author'] = get_atom_author($feed,$item); + $ret['item'] = get_atom_elements($feed,$item); // reset policies which are restricted by default for RSS connections // This item is likely coming from GNU-social via salmon and allows public interaction - $datarray['public_policy'] = ''; - $datarray['comment_policy'] = 'authenticated'; - - $ret['item'] = $datarray; + $ret['item']['public_policy'] = ''; + $ret['item']['comment_policy'] = 'authenticated'; } } @@ -1801,12 +1827,17 @@ function compat_photos_list($s) { if($found) { foreach($matches as $match) { - $ret[] = [ + $entry = [ 'href' => $match[2], - 'length' => 0, 'type' => guess_image_type($match[2]) ]; + $sizer = new \Zotlabs\Lib\Img_filesize($match[2]); + $size = $sizer->getSize(); + if(intval($size)) { + $entry['length'] = intval($size); + } + $ret[] = $entry; } } @@ -1838,10 +1869,24 @@ function atom_entry($item, $type, $author, $owner, $comment = false, $cid = 0, $ create_export_photo_body($item); - if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid']) - $body = fix_private_photos($item['body'],$owner['uid'],$item,$cid); + // provide separate summary and content unless compat is true; as summary represents a content-warning on some networks + + $matches = false; + if(preg_match('|\[summary\](.*?)\[/summary\]|ism',$item['body'],$matches)) + $summary = $matches[1]; else - $body = $item['body']; + $summary = ''; + + $body = $item['body']; + + if($summary) + $body = preg_replace('|^(.*?)\[summary\](.*?)\[/summary\](.*?)$|ism','$1$3',$item['body']); + + if($compat) + $summary = ''; + + if($item['allow_cid'] || $item['allow_gid'] || $item['deny_cid'] || $item['deny_gid']) + $body = fix_private_photos($body,$owner['uid'],$item,$cid); if($compat) { $compat_photos = compat_photos_list($body); @@ -1882,6 +1927,8 @@ function atom_entry($item, $type, $author, $owner, $comment = false, $cid = 0, $ } else { $o .= '<title>' . xmlify($item['title']) . '</title>' . "\r\n"; + if($summary) + $o .= '<summary type="' . $type . '" >' . xmlify(prepare_text($summary,$item['mimetype'])) . '</summary>' . "\r\n"; $o .= '<content type="' . $type . '" >' . xmlify(prepare_text($body,$item['mimetype'])) . '</content>' . "\r\n"; } diff --git a/include/html2bbcode.php b/include/html2bbcode.php index f67231847..4166299db 100644 --- a/include/html2bbcode.php +++ b/include/html2bbcode.php @@ -188,13 +188,14 @@ function html2bbcode($message) node2bbcode($doc, 'hr', array(), "[hr]", ""); - node2bbcode($doc, 'table', array(), "", ""); - node2bbcode($doc, 'tr', array(), "\n", ""); - node2bbcode($doc, 'td', array(), "\t", ""); - //node2bbcode($doc, 'table', array(), "[table]", "[/table]"); - //node2bbcode($doc, 'th', array(), "[th]", "[/th]"); - //node2bbcode($doc, 'tr', array(), "[tr]", "[/tr]"); - //node2bbcode($doc, 'td', array(), "[td]", "[/td]"); +// node2bbcode($doc, 'table', array(), "", ""); +// node2bbcode($doc, 'tr', array(), "\n", ""); +// node2bbcode($doc, 'td', array(), "\t", ""); + + node2bbcode($doc, 'table', array(), "[table]", "[/table]"); + node2bbcode($doc, 'th', array(), "[th]", "[/th]"); + node2bbcode($doc, 'tr', array(), "[tr]", "[/tr]"); + node2bbcode($doc, 'td', array(), "[td]", "[/td]"); node2bbcode($doc, 'h1', array(), "\n\n[h1]", "[/h1]\n"); node2bbcode($doc, 'h2', array(), "\n\n[h2]", "[/h2]\n"); @@ -211,7 +212,7 @@ function html2bbcode($message) node2bbcode($doc, 'video', array('src'=>'/(.+)/'), '[video]$1', '[/video]'); node2bbcode($doc, 'audio', array('src'=>'/(.+)/'), '[audio]$1', '[/audio]'); - node2bbcode($doc, 'iframe', array('src'=>'/(.+)/'), '[iframe]$1', '[/iframe]'); +// node2bbcode($doc, 'iframe', array('src'=>'/(.+)/'), '[iframe]$1', '[/iframe]'); node2bbcode($doc, 'code', array(), '[code]', '[/code]'); diff --git a/include/hubloc.php b/include/hubloc.php index 0daa5908c..0d1a2e560 100644 --- a/include/hubloc.php +++ b/include/hubloc.php @@ -257,6 +257,24 @@ function hubloc_mark_as_down($posturl) { } +/** + * @brief return comma separated string of non-dead clone locations (net addresses) for a given netid + * + * @param string $netid network identity (typically xchan_hash or hubloc_hash) + * @return string + */ + +function locations_by_netid($netid) { + + $locs = q("select hubloc_addr as location from hubloc left join site on hubloc_url = site_url where hubloc_hash = '%s' and hubloc_deleted = 0 and site_dead = 0", + dbesc($netid) + ); + + return array_elm_to_str($locs,'location',', '); + +} + + function ping_site($url) { diff --git a/include/import.php b/include/import.php index a6d738e52..47808544b 100644 --- a/include/import.php +++ b/include/import.php @@ -82,7 +82,8 @@ function import_channel($channel, $account_id, $seize) { 'channel_r_storage', 'channel_r_pages', 'channel_w_stream', 'channel_w_wall', 'channel_w_comment', 'channel_w_mail', 'channel_w_like', 'channel_w_tagwall', 'channel_w_chat', 'channel_w_storage', 'channel_w_pages', 'channel_a_republish', - 'channel_a_delegate', 'perm_limits', 'channel_password', 'channel_salt' + 'channel_a_delegate', 'perm_limits', 'channel_password', 'channel_salt', + 'channel_moved' ]; $clean = array(); @@ -185,7 +186,7 @@ function import_profiles($channel, $profiles) { * * @param array $channel * @param array $hublocs - * @param unknown $seize + * @param boolean $seize * @param boolean $moving (optional) default false */ function import_hublocs($channel, $hublocs, $seize, $moving = false) { diff --git a/include/items.php b/include/items.php index d0b9cffc9..50f663836 100755 --- a/include/items.php +++ b/include/items.php @@ -390,7 +390,7 @@ function post_activity_item($arr, $allow_code = false, $deliver = true) { $arr['comment_policy'] = map_scope(\Zotlabs\Access\PermissionLimits::Get($channel['channel_id'],'post_comments')); if ((! $arr['plink']) && (intval($arr['item_thread_top']))) { - $arr['plink'] = z_root() . '/channel/' . $channel['channel_address'] . '/?f=&mid=' . urlencode($arr['mid']); + $arr['plink'] = substr(z_root() . '/channel/' . $channel['channel_address'] . '/?f=&mid=' . urlencode($arr['mid']),0,190); } @@ -1412,6 +1412,13 @@ function get_mail_elements($x) { } else { $arr['body'] = (($x['body']) ? htmlspecialchars($x['body'], ENT_COMPAT,'UTF-8',false) : ''); + + $maxlen = get_max_import_size(); + + if($maxlen && mb_strlen($arr['body']) > $maxlen) { + $arr['body'] = mb_substr($arr['body'],0,$maxlen,'UTF-8'); + logger('message length exceeds max_import_size: truncated'); + } } $arr['title'] = (($x['title'])? htmlspecialchars($x['title'],ENT_COMPAT,'UTF-8',false) : ''); @@ -2327,6 +2334,16 @@ function send_status_notifications($post_id,$item) { $parent = 0; + if(array_key_exists('verb',$item) && (activity_match($item['verb'], ACTIVITY_LIKE) || activity_match($item['verb'], ACTIVITY_DISLIKE))) { + + $r = q("select id from item where mid = '%s' and uid = %d limit 1", + dbesc($item['thr_parent']), + intval($item['uid']) + ); + + $thr_parent_id = $r[0]['id']; + } + $r = q("select channel_hash from channel where channel_id = %d limit 1", intval($item['uid']) ); @@ -2392,10 +2409,10 @@ function send_status_notifications($post_id,$item) { 'to_xchan' => $r[0]['channel_hash'], 'item' => $item, 'link' => $link, - 'verb' => ACTIVITY_POST, + 'verb' => $item['verb'], 'otype' => 'item', - 'parent' => $parent, - 'parent_mid' => $item['parent_mid'] + 'parent' => $thr_parent_id ? $thr_parent_id : $parent, + 'parent_mid' => $thr_parent_id ? $item['thr_parent'] : $item['parent_mid'] )); } @@ -2454,7 +2471,7 @@ function tag_deliver($uid, $item_id) { // this is an update (edit) to a post which was already processed by us and has a second delivery chain // Just start the second delivery chain to deliver the updated post // after resetting ownership and permission bits - + logger('updating edited tag_deliver post for ' . $u[0]['channel_address']); start_delivery_chain($u[0], $item, $item_id, 0); return; } @@ -2757,6 +2774,16 @@ function tgroup_check($uid, $item) { return false; } + + // see if we already have this item. Maybe it is being updated. + + $r = q("select id from item where mid = '%s' and uid = %d limit 1", + dbesc($item['mid']), + intval($uid) + ); + if($r) + return true; + if(! perm_is_allowed($uid,$item['author_xchan'],'tag_deliver')) return false; @@ -3009,14 +3036,17 @@ function check_item_source($uid, $item) { $words = explode("\n",$r[0]['src_patt']); if($words) { foreach($words as $word) { - if(substr($word,0,1) === '#' && $tags) { + $w = trim($word); + if(! $w) + continue; + if(substr($w,0,1) === '#' && $tags) { foreach($tags as $t) - if((($t['ttype'] == TERM_HASHTAG) || ($t['ttype'] == TERM_COMMUNITYTAG)) && (($t['term'] === substr($word,1)) || (substr($word,1) === '*'))) + if((($t['ttype'] == TERM_HASHTAG) || ($t['ttype'] == TERM_COMMUNITYTAG)) && (($t['term'] === substr($w,1)) || (substr($w,1) === '*'))) return true; } - elseif((strpos($word,'/') === 0) && preg_match($word,$text)) + elseif((strpos($w,'/') === 0) && preg_match($w,$text)) return true; - elseif(stristr($text,$word) !== false) + elseif(stristr($text,$w) !== false) return true; } } @@ -3650,7 +3680,7 @@ function delete_item_lowlevel($item, $stage = DROPITEM_NORMAL, $force = false) { $linked_item = (($item['resource_id']) ? true : false); - logger('item: ' . $item . ' stage: ' . $stage . ' force: ' . $force, LOGGER_DATA); + logger('item: ' . $item['id'] . ' stage: ' . $stage . ' force: ' . $force, LOGGER_DATA); switch($stage) { case DROPITEM_PHASE2: @@ -3990,18 +4020,24 @@ function zot_feed($uid, $observer_hash, $arr) { $item_normal = item_normal(); if(is_sys_channel($uid)) { - $r = q("SELECT parent, created, postopts from item - WHERE uid != %d - $item_normal + + $nonsys_uids = q("SELECT channel_id FROM channel WHERE channel_system = 0"); + $nonsys_uids_str = ids_to_querystr($nonsys_uids,'channel_id'); + + $r = q("SELECT parent, postopts FROM item + WHERE uid IN ( %s ) AND item_wall = 1 - and item_private = 0 $sql_extra ORDER BY created ASC $limit", - intval($uid) + AND item_private = 0 + $item_normal + $sql_extra ORDER BY created ASC $limit", + intval($nonsys_uids_str) ); } else { - $r = q("SELECT parent, created, postopts from item - WHERE uid = %d $item_normal + $r = q("SELECT parent, postopts FROM item + WHERE uid = %d AND item_wall = 1 + $item_normal $sql_extra ORDER BY created ASC $limit", intval($uid) ); diff --git a/include/js_strings.php b/include/js_strings.php index 1b4668061..936594291 100644 --- a/include/js_strings.php +++ b/include/js_strings.php @@ -24,10 +24,16 @@ function js_strings() { '$leavethispage' => t('Unsaved changes. Are you sure you wish to leave this page?'), '$location' => t('Location'), - '$t01' => ((t('timeago.prefixAgo') != 'timeago.prefixAgo') ? t('timeago.prefixAgo') : ''), - '$t02' => ((t('timeago.prefixFromNow') != 'timeago.prefixFromNow') ? t('timeago.prefixFromNow') : ''), - '$t03' => t('ago'), - '$t04' => t('from now'), + // translatable prefix and suffix strings for jquery.timeago - + // using the defaults set below if left untranslated, empty strings if + // translated to "NONE" and the corresponding language strings + // if translated to anything else + '$t01' => ((t('timeago.prefixAgo') == 'timeago.prefixAgo') ? '' : ((t('timeago.prefixAgo') == 'NONE') ? '' : t('timeago.prefixAgo'))), + '$t02' => ((t('timeago.prefixFromNow') == 'timeago.prefixFromNow') ? '' : ((t('timeago.prefixFromNow') == 'NONE') ? '' : t('timeago.prefixFromNow'))), + '$t03' => ((t('timeago.suffixAgo') == 'timeago.suffixAgo') ? 'ago' : ((t('timeago.suffixAgo') == 'NONE') ? '' : t('timeago.suffixAgo'))), + '$t04' => ((t('timeago.suffixFromNow') == 'timeago.suffixFromNow') ? 'from now' : ((t('timeago.suffixFromNow') == 'NONE') ? '' : t('timeago.suffixFromNow'))), + + // translatable main strings for jquery.timeago '$t05' => t('less than a minute'), '$t06' => t('about a minute'), '$t07' => t('%d minutes'), diff --git a/include/language.php b/include/language.php index f6f266685..d0ecd3a85 100644 --- a/include/language.php +++ b/include/language.php @@ -73,8 +73,35 @@ function get_best_language() { } } - if(! isset($preferred)) + + if(! isset($preferred)) { + + /* + * We could find no perfect match for any of the preferred languages. + * For cases where the preference is fr-fr and we have fr but *not* fr-fr + * run the test again and only look for the language base + * which should provide an interface they can sort of understand + */ + + if(isset($langs) && count($langs)) { + foreach ($langs as $lang => $v) { + if(strlen($lang) === 2) { + /* we have already checked this language */ + continue; + } + /* Check the base */ + $lang = strtolower(substr($lang,0,2)); + if(is_dir("view/$lang")) { + $preferred = $lang; + break; + } + } + } + } + + if(! isset($preferred)) { $preferred = 'unset'; + } $arr = array('langs' => $langs, 'preferred' => $preferred); @@ -86,6 +113,12 @@ function get_best_language() { return ((isset(App::$config['system']['language'])) ? App::$config['system']['language'] : 'en'); } +/* + * push_lang and pop_lang let you temporarily override the default language. + * Often used to email the administrator during a session created in another language. + * The stack is one level deep - so you must pop after every push. + */ + function push_lang($language) { diff --git a/include/nav.php b/include/nav.php index 8566cc58c..df58ee96f 100644 --- a/include/nav.php +++ b/include/nav.php @@ -73,9 +73,7 @@ EOT; // nav links: array of array('href', 'text', 'extra css classes', 'title') $nav = []; - $disable_discover_tab = get_config('system','disable_discover_tab') || get_config('system','disable_discover_tab') === false; - - if(! $disable_discover_tab) + if(can_view_public_stream()) $nav['pubs'] = true; /** @@ -100,38 +98,6 @@ EOT; 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'); } diff --git a/include/network.php b/include/network.php index 79a8c6578..f8cb68613 100644 --- a/include/network.php +++ b/include/network.php @@ -1579,6 +1579,7 @@ function get_site_info() { $channels_active_halfyear_stat = intval(get_config('system','channels_active_halfyear_stat')); $channels_active_monthly_stat = intval(get_config('system','channels_active_monthly_stat')); $local_posts_stat = intval(get_config('system','local_posts_stat')); + $local_comments_stat = intval(get_config('system','local_comments_stat')); $hide_in_statistics = intval(get_config('system','hide_in_statistics')); $site_expire = intval(get_config('system', 'default_expire_days')); @@ -1604,24 +1605,28 @@ function get_site_info() { 'commit' => $commit, 'plugins' => $visible_plugins, 'register_policy' => $register_policy[get_config('system','register_policy')], - 'invitation_only' => intval(get_config('system','invitation_only')), + 'invitation_only' => (bool) intval(get_config('system','invitation_only')), 'directory_mode' => $directory_mode[get_config('system','directory_mode')], 'language' => get_config('system','language'), - 'rss_connections' => intval(get_config('system','feed_contacts')), + 'rss_connections' => (bool) intval(get_config('system','feed_contacts')), 'expiration' => $site_expire, 'default_service_restrictions' => $service_class, 'locked_features' => $locked_features, 'admin' => $admin, - 'dbdriver' => DBA::$dba->getdriver(), + 'dbdriver' => DBA::$dba->getdriver() . ' ' . ((ACTIVE_DBTYPE == DBTYPE_POSTGRES) ? 'postgres' : 'mysql'), 'lastpoll' => get_config('system','lastpoll'), 'info' => (($site_info) ? $site_info : ''), 'channels_total' => $channels_total_stat, - 'channels_active_halfyear' => $channels_active_halfyear_stat, - 'channels_active_monthly' => $channels_active_monthly_stat, - 'local_posts' => $local_posts_stat, 'hide_in_statistics' => $hide_in_statistics ]; + if(! $hide_in_statistics) { + $data['channels_active_halfyear'] = $channels_active_halfyear_stat; + $data['channels_active_monthly'] = $channels_active_monthly_stat; + $data['local_posts'] = $local_posts_stat; + $data['local_comments'] = $local_comments_stat; + } + return $data; } diff --git a/include/plugin.php b/include/plugin.php index 379d8e799..62d443ab8 100755 --- a/include/plugin.php +++ b/include/plugin.php @@ -47,7 +47,10 @@ function uninstall_plugin($plugin) { } /** - * @brief installs an addon. + * @brief Installs an addon. + * + * This function is called once to install the addon (either from the cli or via + * the web admin). This will also call load_plugin() once. * * @param string $plugin name of the addon * @return bool @@ -176,19 +179,91 @@ function reload_plugins() { } +function plugins_installed_list() { + + $r = q("select * from addon where installed = 1 order by aname asc"); + return(($r) ? ids_to_array($r,'aname') : []); +} + + +function plugins_sync() { + + /** + * + * Synchronise plugins: + * + * App::$config['system']['addon'] contains a comma-separated list of names + * of plugins/addons which are used on this system. + * Go through the database list of already installed addons, and if we have + * an entry, but it isn't in the config list, call the unload procedure + * and mark it uninstalled in the database (for now we'll remove it). + * Then go through the config list and if we have a plugin that isn't installed, + * call the install procedure and add it to the database. + * + */ + + $installed = plugins_installed_list(); + + $plugins = get_config('system', 'addon', ''); + + $plugins_arr = explode(',', $plugins); + + // array_trim is in include/text.php + + if(! array_walk($plugins_arr,'array_trim')) + return; + + App::$plugins = $plugins_arr; + + $installed_arr = []; + + if(count($installed)) { + foreach($installed as $i) { + if(! in_array($i, $plugins_arr)) { + unload_plugin($i); + } + else { + $installed_arr[] = $i; + } + } + } + + if(count($plugins_arr)) { + foreach($plugins_arr as $p) { + if(! in_array($p, $installed_arr)) { + load_plugin($p); + } + } + } + +} + + /** * @brief Get a list of non hidden addons. * * @return array */ function visible_plugin_list() { + $r = q("select * from addon where hidden = 0 order by aname asc"); - return(($r) ? ids_to_array($r,'aname') : array()); + $x = (($r) ? ids_to_array($r,'aname') : array()); + $y = []; + if($x) { + foreach($x as $xv) { + if(is_dir('addon/' . $xv)) { + $y[] = $xv; + } + } + } + return $y; } /** - * @brief registers a hook. + * @brief Registers a hook. + * + * @see ::Zotlabs::Extend::Hook::register() * * @param string $hook the name of the hook * @param string $file the name of the file that hooks into @@ -219,6 +294,8 @@ function register_hook($hook, $file, $function, $priority = 0) { /** * @brief unregisters a hook. * + * @see ::Zotlabs::Extend::Hook::unregister + * * @param string $hook the name of the hook * @param string $file the name of the file that hooks into * @param string $function the name of the function that the hook called @@ -397,6 +474,83 @@ function get_plugin_info($plugin){ return $info; } +/** + * @brief Parse widget comment in search of widget info. + * + * like + * \code + * * Name: MyWidget + * * Description: A widget + * * Version: 1.2.3 + * * Author: John <profile url> + * * Author: Jane <email> + * * + *\endcode + * @param string $widget the name of the widget + * @return array with the information + */ +function get_widget_info($widget){ + $m = array(); + $info = array( + 'name' => $widget, + 'description' => '', + 'author' => array(), + 'maintainer' => array(), + 'version' => '', + 'requires' => '' + ); + + $ucwidget = ucfirst($widget); + + $checkpaths = [ + "Zotlabs/SiteWidget/$ucwidget.php", + "Zotlibs/Widget/$ucwidget.php", + "addon/$ucwidget/$ucwidget.php", + "addon/$widget.php" + ]; + + $widget_found = false; + + foreach ($checkpaths as $path) { + if (is_file($path)) { + $widget_found = true; + $f = file_get_contents($path); + break; + } + } + + if(! ($widget_found && $f)) + return $info; + + $f = escape_tags($f); + $r = preg_match("|/\*.*\*/|msU", $f, $m); + + if ($r) { + $ll = explode("\n", $m[0]); + foreach( $ll as $l ) { + $l = trim($l, "\t\n\r */"); + if ($l != ""){ + list($k, $v) = array_map("trim", explode(":", $l, 2)); + $k = strtolower($k); + if ($k == 'author' || $k == 'maintainer'){ + $r = preg_match("|([^<]+)<([^>]+)>|", $v, $m); + if ($r) { + $info[$k][] = array('name' => $m[1], 'link' => $m[2]); + } else { + $info[$k][] = array('name' => $v); + } + } + else { + $info[$k] = $v; + } + } + } + } + + return $info; +} + + function check_plugin_versions($info) { if(! is_array($info)) diff --git a/include/queue_fn.php b/include/queue_fn.php index 5fb0d5f1e..798ac36db 100644 --- a/include/queue_fn.php +++ b/include/queue_fn.php @@ -121,7 +121,7 @@ function queue_deliver($outq, $immediate = false) { $base = null; $h = parse_url($outq['outq_posturl']); - if($h) + if($h !== false) $base = $h['scheme'] . '://' . $h['host'] . (($h['port']) ? ':' . $h['port'] : ''); if(($base) && ($base !== z_root()) && ($immediate)) { @@ -160,6 +160,9 @@ function queue_deliver($outq, $immediate = false) { + + + $arr = array('outq' => $outq, 'base' => $base, 'handled' => false, 'immediate' => $immediate); call_hooks('queue_deliver',$arr); if($arr['handled']) @@ -216,7 +219,29 @@ function queue_deliver($outq, $immediate = false) { // normal zot delivery logger('deliver: dest: ' . $outq['outq_posturl'], LOGGER_DEBUG); - $result = zot_zot($outq['outq_posturl'],$outq['outq_notify']); + + $channel = null; + + if($outq['outq_msg'] && $outq['outq_channel']) { + $channel = channelx_by_n($outq['outq_channel']); + } + + $host_crypto = null; + + if($channel && $base) { + $h = q("select hubloc_sitekey, site_crypto from hubloc left join site on hubloc_url = site_url where site_url = '%s' order by hubloc_id desc limit 1", + dbesc($base) + ); + if($h) { + $host_crypto = $h[0]; + } + } + + $msg = $outq['outq_notify']; + + $result = zot_zot($outq['outq_posturl'],$msg,$channel,$host_crypto); + + if($result['success']) { logger('deliver: remote zot delivery succeeded to ' . $outq['outq_posturl']); zot_process_response($outq['outq_posturl'],$result, $outq); diff --git a/include/socgraph.php b/include/socgraph.php index 26446d9c7..6cddbbaac 100644 --- a/include/socgraph.php +++ b/include/socgraph.php @@ -52,7 +52,7 @@ function poco_load($xchan = '', $url = null) { elseif($s['return_code'] == 404) logger('poco_load: nothing found'); else - logger('poco_load: returns ' . print_r($s,true)); + logger('poco_load: returns ' . print_r($s,true), LOGGER_DATA); return; } @@ -178,11 +178,12 @@ function poco_load($xchan = '', $url = null) { ); if(! $r) { - q("insert into xlink ( xlink_xchan, xlink_link, xlink_rating, xlink_rating_text, xlink_updated, xlink_static ) values ( '%s', '%s', %d, '%s', '%s', 0 ) ", + q("insert into xlink ( xlink_xchan, xlink_link, xlink_rating, xlink_rating_text, xlink_sig, xlink_updated, xlink_static ) values ( '%s', '%s', %d, '%s', '%s', '%s', 0 ) ", dbesc($xchan), dbesc($hash), intval(0), dbesc(''), + dbesc(''), dbesc(datetime_convert()) ); } @@ -287,11 +288,14 @@ function suggestion_query($uid, $myxchan, $start = 0, $limit = 80) { function update_suggestions() { - $dirmode = get_config('system', 'directory_mode'); - if($dirmode === false) - $dirmode = DIRECTORY_MODE_NORMAL; + $dirmode = get_config('system', 'directory_mode', DIRECTORY_MODE_NORMAL); + + if($dirmode == DIRECTORY_MODE_STANDALONE) { + poco_load('', z_root() . '/poco'); + return; + } - if(($dirmode == DIRECTORY_MODE_PRIMARY) || ($dirmode == DIRECTORY_MODE_STANDALONE)) { + if($dirmode == DIRECTORY_MODE_PRIMARY) { $url = z_root() . '/sitelist'; } else { diff --git a/include/taxonomy.php b/include/taxonomy.php index a646df28c..393b8718e 100644 --- a/include/taxonomy.php +++ b/include/taxonomy.php @@ -309,35 +309,29 @@ function article_tagadelic($uid, $count = 0, $authors = '', $owner = '', $flags -function dir_tagadelic($count = 0) { +function dir_tagadelic($count = 0, $hub = '') { $count = intval($count); - $dirmode = get_config('system','directory_mode'); - - if($dirmode == DIRECTORY_MODE_STANDALONE) { - // Fetch tags + if($hub) { $r = q("select xtag_term as term, count(xtag_term) as total from xtag left join hubloc on xtag_hash = hubloc_hash - where xtag_flags = 0 and hubloc_url = '%s' + where xtag_flags = 0 and xtag_hash in (select hubloc_hash from hubloc where hubloc_host = '%s' ) group by xtag_term order by total desc %s", - dbesc(z_root()), + dbesc($hub), ((intval($count)) ? "limit $count" : '') ); } else { - // Fetch tags $r = q("select xtag_term as term, count(xtag_term) as total from xtag where xtag_flags = 0 group by xtag_term order by total desc %s", ((intval($count)) ? "limit $count" : '') ); } if(! $r) - return array(); - + return []; return Zotlabs\Text\Tagadelic::calc($r); - } @@ -485,9 +479,6 @@ function dir_tagblock($link,$r) { $o = ''; $observer = get_observer_hash(); - if(! get_directory_setting($observer, 'globaldir')) - return $o; - if(! $r) $r = App::$data['directory_keywords']; diff --git a/include/text.php b/include/text.php index 107efe0cb..c82fad517 100644 --- a/include/text.php +++ b/include/text.php @@ -24,12 +24,20 @@ define('RANDOM_STRING_TEXT', 0x01 ); * @return string substituted string */ function replace_macros($s, $r) { + $arr = [ + 'template' => $s, + 'params' => $r + ]; - $arr = array('template' => $s, 'params' => $r); + /** + * @hooks replace_macros + * * \e string \b template + * * \e array \b params + */ call_hooks('replace_macros', $arr); $t = App::template_engine(); - $output = $t->replace_macros($arr['template'],$arr['params']); + $output = $t->replace_macros($arr['template'], $arr['params']); return $output; } @@ -301,12 +309,16 @@ function purify_html($s, $allow_position = false) { /** - * @brief generate a string that's random, but usually pronounceable. + * @brief Generate a string that's random, but usually pronounceable. * * Used to generate initial passwords. * - * @param int $len - * @return string + * @note In order to create "pronounceable" strings some consonant pairs or + * letters that does not make a very good word ending are chopped off, so that + * the returned string length can be lower than $len. + * + * @param int $len max length of generated string + * @return string Genereated random, but usually pronounceable string */ function autoname($len) { @@ -343,6 +355,7 @@ function autoname($len) { $midcons = array('ck','ct','gn','ld','lf','lm','lt','mb','mm', 'mn','mp', 'nd','ng','nk','nt','rn','rp','rt'); + // avoid these consonant pairs at the end of the string $noend = array('bl', 'br', 'cl','cr','dr','fl','fr','gl','gr', 'kh', 'kl','kr','mn','pl','pr','rh','tr','qu','wh'); @@ -355,7 +368,7 @@ function autoname($len) { $word = ''; for ($x = 0; $x < $len; $x ++) { - $r = mt_rand(0,count($table) - 1); + $r = mt_rand(0, count($table) - 1); $word .= $table[$r]; if ($table == $vowels) @@ -364,14 +377,15 @@ function autoname($len) { $table = $vowels; } - $word = substr($word,0,$len); + $word = substr($word, 0, $len); foreach ($noend as $noe) { - if ((strlen($word) > 2) && (substr($word,-2) == $noe)) { - $word = substr($word,0,-1); + if ((strlen($word) > 2) && (substr($word, -2) == $noe)) { + $word = substr($word, 0, -1); break; } } + // avoid the letter 'q' as it does not make a very good word ending if (substr($word, -1) == 'q') $word = substr($word, 0, -1); @@ -959,7 +973,14 @@ function contact_block() { $contacts = t('Connections'); $micropro = Array(); foreach($r as $rr) { - $rr['archived'] = (intval($rr['abook_archived']) ? true : false); + + // There is no setting to discover if you are bi-directionally connected + // Use the ability to post comments as an indication that this relationship is more + // than wishful thinking; even though soapbox channels and feeds will disable it. + + if(! intval(get_abconfig(App::$profile['uid'],$rr['xchan_hash'],'their_perms','post_comments'))) { + $rr['oneway'] = true; + } $micropro[] = micropro($rr,true,'mpfriend'); } } @@ -1012,6 +1033,7 @@ function micropro($contact, $redirect = false, $class = '', $textmode = false) { return replace_macros(get_markup_template(($textmode)?'micropro_txt.tpl':'micropro_img.tpl'),array( '$click' => (($contact['click']) ? $contact['click'] : ''), '$class' => $class . (($contact['archived']) ? ' archived' : ''), + '$oneway' => (($contact['oneway']) ? true : false), '$url' => $url, '$photo' => $contact['xchan_photo_s'], '$name' => $contact['xchan_name'], @@ -1094,17 +1116,19 @@ function sslify($s) { return $s; } - +/** + * @brief Get an array of poke verbs. + * + * @return array + * * \e index is present tense verb + * * \e value is array containing past tense verb, translation of present, translation of past + */ function get_poke_verbs() { - // index is present tense verb - // value is array containing past tense verb, translation of present, translation of past - - if(get_config('system','poke_basic')) { + if (get_config('system', 'poke_basic')) { $arr = array( - 'poke' => array( 'poked', t('poke'), t('poked')), + 'poke' => array('poked', t('poke'), t('poked')), ); - } - else { + } else { $arr = array( 'poke' => array( 'poked', t('poke'), t('poked')), 'ping' => array( 'pinged', t('ping'), t('pinged')), @@ -1114,15 +1138,26 @@ function get_poke_verbs() { 'rebuff' => array( 'rebuffed', t('rebuff'), t('rebuffed')), ); + /** + * @hooks poke_verbs + * * \e array associative array with another array as value + */ call_hooks('poke_verbs', $arr); } return $arr; } +/** + * @brief Get an array of mood verbs. + * + * @return array + * * \e index is the verb + * * \e value is the translated verb + */ function get_mood_verbs() { - $arr = array( + $arr = [ 'happy' => t('happy'), 'sad' => t('sad'), 'mellow' => t('mellow'), @@ -1144,9 +1179,14 @@ function get_mood_verbs() { 'motivated' => t('motivated'), 'relaxed' => t('relaxed'), 'surprised' => t('surprised'), - ); + ]; + /** + * @hooks mood_verbs + * * \e array associative array with mood verbs + */ call_hooks('mood_verbs', $arr); + return $arr; } @@ -1513,14 +1553,37 @@ function format_filer(&$item) { function generate_map($coord) { $coord = trim($coord); $coord = str_replace(array(',','/',' '),array(' ',' ',' '),$coord); - $arr = array('lat' => trim(substr($coord,0,strpos($coord,' '))), 'lon' => trim(substr($coord,strpos($coord,' ')+1)), 'html' => ''); - call_hooks('generate_map',$arr); + + $arr = [ + 'lat' => trim(substr($coord, 0, strpos($coord, ' '))), + 'lon' => trim(substr($coord, strpos($coord, ' ')+1)), + 'html' => '' + ]; + + /** + * @hooks generate_map + * * \e string \b lat + * * \e string \b lon + * * \e string \b html the parsed HTML to return + */ + call_hooks('generate_map', $arr); + return (($arr['html']) ? $arr['html'] : $coord); } function generate_named_map($location) { - $arr = array('location' => $location, 'html' => ''); - call_hooks('generate_named_map',$arr); + $arr = [ + 'location' => $location, + 'html' => '' + ]; + + /** + * @hooks generate_named_map + * * \e string \b location + * * \e string \b html the parsed HTML to return + */ + call_hooks('generate_named_map', $arr); + return (($arr['html']) ? $arr['html'] : $location); } @@ -1626,13 +1689,11 @@ function prepare_binary($item) { } - - /** * @brief Given a text string, convert from bbcode to html and add smilie icons. * * @param string $text - * @param sting $content_type (optional) default text/bbcode + * @param string $content_type (optional) default text/bbcode * @param boolean $cache (optional) default false * * @return string @@ -1958,18 +2019,37 @@ function item_post_type($item) { return $post_type; } +// This needs to be fixed to use quoted tag strings function undo_post_tagging($s) { + $matches = null; + // undo tags and mentions $cnt = preg_match_all('/([@#])(\!*)\[zrl=(.*?)\](.*?)\[\/zrl\]/ism',$s,$matches,PREG_SET_ORDER); if($cnt) { foreach($matches as $mtch) { - $s = str_replace($mtch[0], $mtch[1] . $mtch[2] . str_replace(' ','_',$mtch[4]),$s); + $s = str_replace($mtch[0], $mtch[1] . $mtch[2] . quote_tag($mtch[4]),$s); + } + } + // undo forum tags + $cnt = preg_match_all('/\!\[zrl=(.*?)\](.*?)\[\/zrl\]/ism',$s,$matches,PREG_SET_ORDER); + if($cnt) { + foreach($matches as $mtch) { + $s = str_replace($mtch[0], '!' . quote_tag($mtch[2]),$s); } } + + return $s; } +function quote_tag($s) { + if(strpos($s,' ') !== false) + return '"' . $s . '"'; + return $s; +} + + function fix_mce_lf($s) { $s = str_replace("\r\n","\n",$s); // $s = str_replace("\n\n","\n",$s); @@ -2100,6 +2180,35 @@ function ids_to_querystr($arr,$idx = 'id',$quote = false) { } /** + * @brief array_elm_to_str($arr,$elm,$delim = ',') extract unique individual elements from an array of arrays and return them as a string separated by a delimiter + * similar to ids_to_querystr, but allows a different delimiter instead of a db-quote option + * empty elements (evaluated after trim()) are ignored. + * @param $arr array + * @param $elm array key to extract from sub-array + * @param $delim string default ',' + * @returns string + */ + +function array_elm_to_str($arr,$elm,$delim = ',') { + + $tmp = []; + if($arr && is_array($arr)) { + foreach($arr as $x) { + if(is_array($x) && array_key_exists($elm,$x)) { + $z = trim($x[$elm]); + if(($z) && (! in_array($z,$tmp))) { + $tmp[] = $z; + } + } + } + } + return implode($delim,$tmp); +} + + + + +/** * @brief Fetches xchan and hubloc data for an array of items with only an * author_xchan and owner_xchan. * @@ -3033,8 +3142,19 @@ function text_highlight($s, $lang) { $s = jindent($s); } - $arr = [ 'text' => $s, 'language' => $lang, 'success' => false ]; - call_hooks('text_highlight',$arr); + $arr = [ + 'text' => $s, + 'language' => $lang, + 'success' => false + ]; + + /** + * @hooks text_highlight + * * \e string \b text + * * \e string \b language + * * \e boolean \b success default false + */ + call_hooks('text_highlight', $arr); if($arr['success']) $o = $arr['text']; @@ -3117,7 +3237,6 @@ function share_unshield($m) { function cleanup_bbcode($body) { - /** * fix naked links by passing through a callback to see if this is a hubzilla site * (already known to us) which will get a zrl, otherwise link with url, add bookmark tag to both. @@ -3155,7 +3274,6 @@ function cleanup_bbcode($body) { return $body; - } function gen_link_id($mid) { @@ -3192,3 +3310,5 @@ function purify_filename($s) { return ''; return $s; } + + diff --git a/include/zid.php b/include/zid.php index 6ebc9a6ab..67c1d9f6c 100644 --- a/include/zid.php +++ b/include/zid.php @@ -127,8 +127,11 @@ function clean_query_string($s = '') { * @return string */ function zidify_callback($match) { - $is_zid = ((feature_enabled(local_channel(), 'sendzid')) || (strpos($match[1], 'zrl')) ? true : false); - $replace = '<a' . $match[1] . ' href="' . (($is_zid) ? zid($match[2]) : $match[2]) . '"'; + + $arr = [ 'zid' => ((strpos($match[1],'zrl')) ? true : false), 'url' => $match[2] ]; + call_hooks('zidify', $arr); + + $replace = '<a' . $match[1] . ' href="' . (intval($arr['zid']) ? zid($arr['url']) : $arr['url']) . '"'; $x = str_replace($match[0], $replace, $match[0]); @@ -136,8 +139,11 @@ function zidify_callback($match) { } function zidify_img_callback($match) { - $is_zid = ((feature_enabled(local_channel(), 'sendzid')) || (strpos($match[1], 'zrl')) ? true : false); - $replace = '<img' . $match[1] . ' src="' . (($is_zid) ? zid($match[2]) : $match[2]) . '"'; + + $arr = [ 'zid' => ((strpos($match[1],'zrl')) ? true : false), 'url' => $match[2] ]; + call_hooks('zidify', $arr); + + $replace = '<img' . $match[1] . ' src="' . (intval($arr['zid']) ? zid($arr['url']) : $arr['url']) . '"'; $x = str_replace($match[0], $replace, $match[0]); @@ -259,9 +265,9 @@ function red_zrlify_img_callback($matches) { */ function owt_init($token) { - \Zotlabs\Zot\Verify::purge('owt', '3 MINUTE'); + \Zotlabs\Lib\Verify::purge('owt', '3 MINUTE'); - $ob_hash = \Zotlabs\Zot\Verify::get_meta('owt', 0, $token); + $ob_hash = \Zotlabs\Lib\Verify::get_meta('owt', 0, $token); if($ob_hash === false) { return; diff --git a/include/zot.php b/include/zot.php index 18960db46..87f449fb1 100644 --- a/include/zot.php +++ b/include/zot.php @@ -158,6 +158,85 @@ function zot_build_packet($channel, $type = 'notify', $recipients = null, $remot return json_encode($data); } + +/** + * @brief Builds a zot6 notification packet. + * + * Builds a zot6 notification packet that you can either store in the queue with + * a message array or call zot_zot to immediately zot it to the other side. + * + * @param array $channel + * sender channel structure + * @param string $type + * packet type: one of 'ping', 'pickup', 'purge', 'refresh', 'keychange', 'force_refresh', 'notify', 'auth_check' + * @param array $recipients + * envelope information, array ( 'guid' => string, 'guid_sig' => string ); empty for public posts + * @param string $remote_key + * optional public site key of target hub used to encrypt entire packet + * NOTE: remote_key and encrypted packets are required for 'auth_check' packets, optional for all others + * @param string $methods + * optional comma separated list of encryption methods @ref zot_best_algorithm() + * @param string $secret + * random string, required for packets which require verification/callback + * e.g. 'pickup', 'purge', 'notify', 'auth_check'. Packet types 'ping', 'force_refresh', and 'refresh' do not require verification + * @param string $extra + * @returns string json encoded zot packet + */ +function zot6_build_packet($channel, $type = 'notify', $recipients = null, $msg = '', $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'],$sig_method)), + 'url' => z_root(), + 'url_sig' => base64url_encode(rsa_sign(z_root(),$channel['channel_prvkey'],$sig_method)), + 'sitekey' => get_config('system','pubkey') + ], + 'callback' => '/post', + 'version' => Zotlabs\Lib\System::get_zot_revision(), + 'encryption' => crypto_methods(), + 'signing' => signing_methods() + ]; + + if ($recipients) { + for ($x = 0; $x < count($recipients); $x ++) + unset($recipients[$x]['hash']); + + $data['recipients'] = $recipients; + } + + if($msg) { + $data['msg'] = $msg; + } + + if ($secret) { + $data['secret'] = preg_replace('/[^0-9a-fA-F]/','',$secret); + $data['secret_sig'] = base64url_encode(rsa_sign($secret,$channel['channel_prvkey'],$sig_method)); + } + + if ($extra) { + foreach ($extra as $k => $v) + $data[$k] = $v; + } + + logger('zot6_build_packet: ' . print_r($data,true), LOGGER_DATA, LOG_DEBUG); + + // Hush-hush ultra top-secret mode + + if($remote_key) { + $algorithm = zot_best_algorithm($methods); + $data = crypto_encapsulate(json_encode($data),$remote_key, $algorithm); + } + + return json_encode($data); +} + + + + /** * @brief Choose best encryption function from those available on both sites. * @@ -209,10 +288,23 @@ function zot_best_algorithm($methods) { * * @param string $url * @param array $data + * @param array $channel (optional if using zot6 delivery) + * @param array $crypto (optional if encrypted httpsig, requires hubloc_sitekey and site_crypto elements) * @return array see z_post_url() for returned data format */ -function zot_zot($url, $data) { - return z_post_url($url, array('data' => $data)); +function zot_zot($url, $data, $channel = null,$crypto = null) { + + $headers = []; + + if($channel) { + $headers['X-Zot-Token'] = random_string(); + $hash = \Zotlabs\Web\HTTPSig::generate_digest($data,false); + $headers['X-Zot-Digest'] = 'SHA-256=' . $hash; + $h = \Zotlabs\Web\HTTPSig::create_sig('',$headers,$channel['channel_prvkey'],'acct:' . $channel['channel_address'] . '@' . \App::get_hostname(),false,false,'sha512',(($crypto) ? $crypto['hubloc_sitekey'] : ''), (($crypto) ? zot_best_algorithm($crypto['site_crypto']) : '')); + } + + $redirects = 0; + return z_post_url($url, array('data' => $data),$redirects,((empty($h)) ? [] : [ 'headers' => $h ])); } /** @@ -495,13 +587,16 @@ function zot_refresh($them, $channel = null, $force = false) { // If there is a default group for this channel, add this connection to it - - $default_group = $channel['channel_default_group']; - if($default_group) { - require_once('include/group.php'); - $g = group_rec_byhash($channel['channel_id'],$default_group); - if($g) - group_add_member($channel['channel_id'],'',$x['hash'],$g['id']); + // for pending connections this will happens at acceptance time. + + if(! intval($new_connection[0]['abook_pending'])) { + $default_group = $channel['channel_default_group']; + if($default_group) { + require_once('include/group.php'); + $g = group_rec_byhash($channel['channel_id'],$default_group); + if($g) + group_add_member($channel['channel_id'],'',$x['hash'],$g['id']); + } } unset($new_connection[0]['abook_id']); @@ -1060,7 +1155,12 @@ function zot_process_response($hub, $arr, $outq) { * @brief * * We received a notification packet (in mod_post) that a message is waiting for us, and we've verified the sender. - * Now send back a pickup message, using our message tracking ID ($arr['secret']), which we will sign with our site + * Check if the site is using zot6 delivery and includes a verified HTTP Signature, signed content, and a 'msg' field, + * and also that the signer and the sender match. + * If that happens, we do not need to fetch/pickup the message - we have it already and it is verified. + * Translate it into the form we need for zot_import() and import it. + * + * Otherwise send back a pickup message, using our message tracking ID ($arr['secret']), which we will sign with our site * private key. * The entire pickup message is encrypted with the remote site's public key. * If everything checks out on the remote end, we will receive back a packet containing one or more messages, @@ -1078,38 +1178,61 @@ function zot_fetch($arr) { $url = $arr['sender']['url'] . $arr['callback']; - // set $multiple param on zot_gethub() to return all matching hubs - // This allows us to recover from re-installs when a redundant (but invalid) hubloc for - // this identity is widely dispersed throughout the network. + $import = null; + $hubs = null; + + $zret = zot6_check_sig(); - $ret_hubs = zot_gethub($arr['sender'],true); - if(! $ret_hubs) { + if($zret['success'] && $zret['hubloc'] && $zret['hubloc']['hubloc_guid'] === $data['sender']['guid'] && $data['msg']) { + logger('zot6_delivery',LOGGER_DEBUG); + logger('zot6_data: ' . print_r($data,true),LOGGER_DATA); + + $ret['collected'] = true; + + $import = [ 'success' => true, 'body' => json_encode( [ 'success' => true, 'pickup' => [ [ 'notify' => $data, 'message' => json_decode($data['msg'],true) ] ] ] ) ]; + $hubs = [ $zret['hubloc'] ] ; + } + + if(! $hubs) { + // set $multiple param on zot_gethub() to return all matching hubs + // This allows us to recover from re-installs when a redundant (but invalid) hubloc for + // this identity is widely dispersed throughout the network. + + $hubs = zot_gethub($arr['sender'],true); + } + + if(! $hubs) { logger('No hub: ' . print_r($arr['sender'],true)); return; } - foreach($ret_hubs as $ret_hub) { + foreach($hubs as $hub) { - $secret = substr(preg_replace('/[^0-9a-fA-F]/','',$arr['secret']),0,64); + if(! $import) { + $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' => $secret, - 'secret_sig' => base64url_encode(rsa_sign($secret, get_config('system','prvkey'))) - ]; + $data = [ + 'type' => 'pickup', + 'url' => z_root(), + 'callback_sig' => base64url_encode(rsa_sign(z_root() . '/post', get_config('system','prvkey'))), + 'callback' => z_root() . '/post', + 'secret' => $secret, + 'secret_sig' => base64url_encode(rsa_sign($secret, get_config('system','prvkey'))) + ]; - $algorithm = zot_best_algorithm($ret_hub['site_crypto']); - $datatosend = json_encode(crypto_encapsulate(json_encode($data),$ret_hub['hubloc_sitekey'], $algorithm)); + $algorithm = zot_best_algorithm($hub['site_crypto']); + $datatosend = json_encode(crypto_encapsulate(json_encode($data),$hub['hubloc_sitekey'], $algorithm)); - $fetch = zot_zot($url,$datatosend); + $import = zot_zot($url,$datatosend); + } + else { + $algorithm = zot_best_algorithm($hub['site_crypto']); + } - $result = zot_import($fetch, $arr['sender']['url']); + $result = zot_import($import, $arr['sender']['url']); if($result) { - $result = crypto_encapsulate(json_encode($result),$ret_hub['hubloc_sitekey'], $algorithm); + $result = crypto_encapsulate(json_encode($result),$hub['hubloc_sitekey'], $algorithm); return $result; } @@ -1608,7 +1731,7 @@ function process_delivery($sender, $arr, $deliveries, $relay, $public = false, $ foreach($deliveries as $d) { $local_public = $public; - $DR = new Zotlabs\Zot\DReport(z_root(),$sender['hash'],$d['hash'],$arr['mid']); + $DR = new Zotlabs\Lib\DReport(z_root(),$sender['hash'],$d['hash'],$arr['mid']); $r = q("select * from channel where channel_hash = '%s' limit 1", dbesc($d['hash']) @@ -2137,7 +2260,7 @@ function process_mail_delivery($sender, $arr, $deliveries) { foreach($deliveries as $d) { - $DR = new Zotlabs\Zot\DReport(z_root(),$sender['hash'],$d['hash'],$arr['mid']); + $DR = new Zotlabs\Lib\DReport(z_root(),$sender['hash'],$d['hash'],$arr['mid']); $r = q("select * from channel where channel_hash = '%s' limit 1", dbesc($d['hash']) @@ -3778,11 +3901,11 @@ function process_channel_sync_delivery($sender, $arr, $deliveries) { // we should probably do this for all items, but usually we only send one. if(array_key_exists('item',$arr) && is_array($arr['item'][0])) { - $DR = new Zotlabs\Zot\DReport(z_root(),$d['hash'],$d['hash'],$arr['item'][0]['message_id'],'channel sync processed'); + $DR = new Zotlabs\Lib\DReport(z_root(),$d['hash'],$d['hash'],$arr['item'][0]['message_id'],'channel sync processed'); $DR->addto_recipient($channel['channel_name'] . ' <' . channel_reddress($channel) . '>'); } else - $DR = new Zotlabs\Zot\DReport(z_root(),$d['hash'],$d['hash'],'sync packet','channel sync delivered'); + $DR = new Zotlabs\Lib\DReport(z_root(),$d['hash'],$d['hash'],'sync packet','channel sync delivered'); $result[] = $DR->get(); } @@ -4793,7 +4916,7 @@ function zot_reply_auth_check($data,$encrypted_packet) { * the web server. We should probably convert this to webserver time rather than DB time so * that the different clocks won't affect it and allow us to keep the time short. */ - Zotlabs\Zot\Verify::purge('auth', '30 MINUTE'); + Zotlabs\Lib\Verify::purge('auth', '30 MINUTE'); $y = q("select xchan_pubkey from xchan where xchan_hash = '%s' limit 1", dbesc($sender_hash) @@ -4834,7 +4957,7 @@ function zot_reply_auth_check($data,$encrypted_packet) { // This additionally checks for forged sites since we already stored the expected result in meta // and we've already verified that this is them via zot_gethub() and that their key signed our token - $z = Zotlabs\Zot\Verify::match('auth',$c[0]['channel_id'],$data['secret'],$data['sender']['url']); + $z = Zotlabs\Lib\Verify::match('auth',$c[0]['channel_id'],$data['secret'],$data['sender']['url']); if (! $z) { logger('mod_zot: auth_check: verification key not found.'); $ret['message'] .= 'verification key not found' . EOL; @@ -4967,6 +5090,39 @@ function zot_reply_refresh($sender, $recipients) { } +function zot6_check_sig() { + + $ret = [ 'success' => false ]; + + logger('server: ' . print_r($_SERVER,true), LOGGER_DATA); + + if(array_key_exists('HTTP_SIGNATURE',$_SERVER)) { + $sigblock = \Zotlabs\Web\HTTPSig::parse_sigheader($_SERVER['HTTP_SIGNATURE']); + if($sigblock) { + $keyId = $sigblock['keyId']; + + if($keyId) { + $r = q("select hubloc.*, site_crypto from hubloc left join site on hubloc_url = site_url + where hubloc_addr = '%s' ", + dbesc(str_replace('acct:','',$keyId)) + ); + if($r) { + foreach($r as $hubloc) { + $verified = \Zotlabs\Web\HTTPSig::verify('',$hubloc['xchan_pubkey']); + if($verified && $verified['header_signed'] && $verified['header_valid'] && $verified['content_signed'] && $verified['content_valid']) { + $ret['hubloc'] = $hubloc; + $ret['success'] = true; + return $ret; + } + } + } + } + } + } + + return $ret; +} + function zot_reply_notify($data) { $ret = array('success' => false); |