diff --git a/Zotlabs/Lib/LDSignatures.php b/Zotlabs/Lib/LDSignatures.php index d500799c0..6d7127cde 100644 --- a/Zotlabs/Lib/LDSignatures.php +++ b/Zotlabs/Lib/LDSignatures.php @@ -88,8 +88,16 @@ class LDSignatures { return ''; jsonld_set_document_loader('jsonld_document_loader'); + + try { + $d = jsonld_normalize($data,[ 'algorithm' => 'URDNA2015', 'format' => 'application/nquads' ]); + } + catch (\Exception $e) { + logger('normalise error:' . print_r($e,true)); + logger('normalise error: ' . print_r($data,true)); + } - return jsonld_normalize($data,[ 'algorithm' => 'URDNA2015', 'format' => 'application/nquads' ]); + return $d; } static function salmon_sign($data,$channel) { diff --git a/Zotlabs/Lib/SConfig.php b/Zotlabs/Lib/SConfig.php new file mode 100644 index 000000000..ca0d133b2 --- /dev/null +++ b/Zotlabs/Lib/SConfig.php @@ -0,0 +1,25 @@ + standard ACL request // 'g' => Groups only ACL request + // 'f' => forums only ACL request // 'c' => Connections only ACL request or editor (textarea) mention request // $_REQUEST['search'] contains ACL search text. @@ -56,12 +57,12 @@ class Acl extends \Zotlabs\Web\Controller { $search = $_REQUEST['query']; } - if( (! local_channel()) && (! ($type == 'x' || $type == 'c'))) + if( (! local_channel()) && (! in_array($type, [ 'x', 'c', 'f' ]))) killme(); $permitted = []; - if(in_array($type, [ 'm', 'a', 'c' ])) { + if(in_array($type, [ 'm', 'a', 'c', 'f' ])) { // These queries require permission checking. We'll create a simple array of xchan_hash for those with // the requisite permissions which we can check against. @@ -154,7 +155,7 @@ class Acl extends \Zotlabs\Web\Controller { } } - if($type == '' || $type == 'c') { + if($type == '' || $type == 'c' || $type === 'f') { $extra_channels_sql = ''; @@ -336,12 +337,12 @@ class Acl extends \Zotlabs\Web\Controller { $g['nick'] = $t[0] . '@'; } - if(in_array($g['hash'],$permitted) && $type == 'c' && (! $noforums)) { + if(in_array($g['hash'],$permitted) && in_array($type, [ 'c', 'f' ]) && (! $noforums)) { $contacts[] = array( "type" => "c", "photo" => "images/twopeople.png", - "name" => $g['name'] . '+', - "id" => $g['id'] . '+', + "name" => $g['name'] . (($type === 'f') ? '' : '+'), + "id" => $g['id'] . (($type === 'f') ? '' : '+'), "xid" => $g['hash'], "link" => $g['nick'], "nick" => substr($g['nick'],0,strpos($g['nick'],'@')), @@ -350,18 +351,20 @@ class Acl extends \Zotlabs\Web\Controller { "label" => t('network') ); } - $contacts[] = array( - "type" => "c", - "photo" => $g['micro'], - "name" => $g['name'], - "id" => $g['id'], - "xid" => $g['hash'], - "link" => $g['nick'], - "nick" => (($g['nick']) ? substr($g['nick'],0,strpos($g['nick'],'@')) : $g['nick']), - "self" => (intval($g['abook_self']) ? 'abook-self' : ''), - "taggable" => '', - "label" => '', - ); + if($type !== 'f') { + $contacts[] = array( + "type" => "c", + "photo" => $g['micro'], + "name" => $g['name'], + "id" => $g['id'], + "xid" => $g['hash'], + "link" => $g['nick'], + "nick" => (($g['nick']) ? substr($g['nick'],0,strpos($g['nick'],'@')) : $g['nick']), + "self" => (intval($g['abook_self']) ? 'abook-self' : ''), + "taggable" => '', + "label" => '', + ); + } } } diff --git a/Zotlabs/Web/HTTPSig.php b/Zotlabs/Web/HTTPSig.php index 8062764fb..445ba200b 100644 --- a/Zotlabs/Web/HTTPSig.php +++ b/Zotlabs/Web/HTTPSig.php @@ -175,7 +175,8 @@ class HTTPSig { - static function create_sig($request,$head,$prvkey,$keyid = 'Key',$send_headers = false,$auth = false,$alg = 'sha256') { + static function create_sig($request,$head,$prvkey,$keyid = 'Key',$send_headers = false,$auth = false,$alg = 'sha256', + $crypt_key = null, $crypt_algo = 'aes256ctr') { $return_headers = []; @@ -186,15 +187,21 @@ class HTTPSig { $algorithm = 'rsa-sha512'; } - $x = self::sign($request,$head,$prvkey,$alg); + $x = self::sign($request,$head,$prvkey,$alg); - if($auth) { - $sighead = 'Authorization: Signature keyId="' . $keyid . '",algorithm="' . $algorithm + $headerval = 'keyId="' . $keyid . '",algorithm="' . $algorithm . '",headers="' . $x['headers'] . '",signature="' . $x['signature'] . '"'; + + if($crypt_key) { + $x = crypto_encapsulate($headerval,$crypt_key,$crypt_alg); + $headerval = 'iv="' . $x['iv'] . '",key="' . $x['key'] . '",alg="' . $x['alg'] . '",data="' . $x['data']; + } + + if($auth) { + $sighead = 'Authorization: Signature ' . $headerval; } else { - $sighead = 'Signature: keyId="' . $keyid . '",algorithm="' . $algorithm - . '",headers="' . $x['headers'] . '",signature="' . $x['signature'] . '"'; + $sighead = 'Signature: ' . $headerval; } if($head) { @@ -249,8 +256,15 @@ class HTTPSig { } static function parse_sigheader($header) { + $ret = []; $matches = []; + + // if the header is encrypted, decrypt with (default) site private key and continue + + if(preg_match('/iv="(.*?)"/ism',$header,$matches)) + $header = self::decrypt_sigheader($header); + if(preg_match('/keyId="(.*?)"/ism',$header,$matches)) $ret['keyId'] = $matches[1]; if(preg_match('/algorithm="(.*?)"/ism',$header,$matches)) @@ -267,6 +281,32 @@ class HTTPSig { } + static function decrypt_sigheader($header,$prvkey = null) { + + $iv = $key = $alg = $data = null; + + if(! $prvkey) { + $prvkey = get_config('system','prvkey'); + } + + $matches = []; + + if(preg_match('/iv="(.*?)"/ism',$header,$matches)) + $iv = $matches[1]; + if(preg_match('/key="(.*?)"/ism',$header,$matches)) + $key = $matches[1]; + if(preg_match('/alg="(.*?)"/ism',$header,$matches)) + $alg = $matches[1]; + if(preg_match('/data="(.*?)"/ism',$header,$matches)) + $data = $matches[1]; + + if($iv && $key && $alg && $data) { + return crypto_unencapsulate([ 'iv' => $iv, 'key' => $key, 'alg' => $alg, 'data' => $data ] , $prvkey); + } + return ''; + + } + } diff --git a/boot.php b/boot.php index 2940aba54..df5c77e43 100755 --- a/boot.php +++ b/boot.php @@ -425,6 +425,7 @@ define ( 'TERM_THING', 7 ); define ( 'TERM_BOOKMARK', 8 ); define ( 'TERM_HIERARCHY', 9 ); define ( 'TERM_COMMUNITYTAG', 10 ); +define ( 'TERM_FORUM', 11 ); define ( 'TERM_OBJ_POST', 1 ); define ( 'TERM_OBJ_PHOTO', 2 ); diff --git a/doc/hook/channel_links.bb b/doc/hook/channel_links.bb new file mode 100644 index 000000000..c0243dac6 --- /dev/null +++ b/doc/hook/channel_links.bb @@ -0,0 +1,12 @@ +[h2]channel_links[/h2] + +Called when generating the Link HTTP header for the channel page. Different protocol stacks can add links to this header. + +Hook data = array + 'channel_address' => channel nickname, no checking is done to see if it is valid + 'channel_links' => array of channel links in the format + 'url' => url of resource + 'rel' => link relation + 'type' => MIME type + +All fields are required \ No newline at end of file diff --git a/include/config.php b/include/config.php index 0b0e639ab..0be791715 100644 --- a/include/config.php +++ b/include/config.php @@ -126,3 +126,19 @@ function set_iconfig(&$item, $family, $key, $value, $sharing = false) { function del_iconfig(&$item, $family, $key) { return Zlib\IConfig::Delete($item, $family, $key); } + +function load_sconfig($server_id) { + Zlib\SConfig::Load($server_id); +} + +function get_sconfig($server_id, $family, $key, $default = false) { + return Zlib\SConfig::Get($server_id, $family, $key, $default); +} + +function set_sconfig($server_id, $family, $key, $value) { + return Zlib\SConfig::Set($server_id, $family, $key, $value); +} + +function del_sconfig($server_id, $family, $key) { + return Zlib\SConfig::Delete($server_id, $family, $key); +} diff --git a/include/feedutils.php b/include/feedutils.php index 112e10155..451946114 100644 --- a/include/feedutils.php +++ b/include/feedutils.php @@ -1033,6 +1033,12 @@ function consume_feed($xml, $importer, &$contact, $pass = 0) { if(! $datarray['mid']) continue; + $item_parent_mid = q("select parent_mid from item where mid = '%s' and uid = %d limit 1", + dbesc($parent_mid), + intval($importer['channel_id']) + ); + + // This probably isn't an appropriate default but we're about to change it // if it's wrong. @@ -1128,18 +1134,12 @@ function consume_feed($xml, $importer, &$contact, $pass = 0) { $datarray['parent_mid'] = $pmid; } } - if(! $pmid) { - $x = q("select parent_mid from item where mid = '%s' and uid = %d limit 1", - dbesc($parent_mid), - intval($importer['channel_id']) - ); - - if($x) { - logger('find_parent: matched in-reply-to: ' . $parent_mid, LOGGER_DEBUG); - $pmid = $x[0]['parent_mid']; - $datarray['parent_mid'] = $pmid; - } + if(($item_parent_mid) && (! $pmid)) { + logger('find_parent: matched in-reply-to: ' . $parent_mid, LOGGER_DEBUG); + $pmid = $item_parent_mid[0]['parent_mid']; + $datarray['parent_mid'] = $pmid; } + if((! $pmid) && $parent_link !== '') { $f = feed_conversation_fetch($importer,$contact,$parent_link); if($f) { @@ -1161,6 +1161,7 @@ function consume_feed($xml, $importer, &$contact, $pass = 0) { ); if($x) { + $item_parent_mid = $x; $pmid = $x[0]['parent_mid']; $datarray['parent_mid'] = $pmid; } @@ -1242,6 +1243,13 @@ function consume_feed($xml, $importer, &$contact, $pass = 0) { set_iconfig($datarray,'system','parent_mid',$parent_mid,true); } + + // allow likes of comments + + if($item_parent_mid && activity_match($datarray['verb'],ACTVITY_LIKE)) { + $datarray['thr_parent'] = $item_parent_mid[0]['parent_mid']; + } + $datarray['aid'] = $importer['channel_account_id']; $datarray['uid'] = $importer['channel_id']; diff --git a/include/items.php b/include/items.php index daf41aa4b..cc209aacf 100755 --- a/include/items.php +++ b/include/items.php @@ -1130,7 +1130,7 @@ function encode_item_xchan($xchan) { function encode_item_terms($terms,$mirror = false) { $ret = array(); - $allowed_export_terms = array( TERM_UNKNOWN, TERM_HASHTAG, TERM_MENTION, TERM_CATEGORY, TERM_BOOKMARK, TERM_COMMUNITYTAG ); + $allowed_export_terms = array( TERM_UNKNOWN, TERM_HASHTAG, TERM_MENTION, TERM_CATEGORY, TERM_BOOKMARK, TERM_COMMUNITYTAG, TERM_FORUM ); if($mirror) { $allowed_export_terms[] = TERM_PCATEGORY; @@ -1178,7 +1178,7 @@ function decode_item_meta($meta) { * @return string */ function termtype($t) { - $types = array('unknown','hashtag','mention','category','private_category','file','search','thing','bookmark', 'hierarchy', 'communitytag'); + $types = array('unknown','hashtag','mention','category','private_category','file','search','thing','bookmark', 'hierarchy', 'communitytag', 'forum'); return(($types[$t]) ? $types[$t] : 'unknown'); } @@ -1227,6 +1227,9 @@ function decode_tags($t) { case 'communitytag': $tag['ttype'] = TERM_COMMUNITYTAG; break; + case 'forum': + $tag['ttype'] = TERM_FORUM; + break; default: case 'unknown': $tag['ttype'] = TERM_UNKNOWN; @@ -2457,7 +2460,7 @@ function tag_deliver($uid, $item_id) { * Now we've got those out of the way. Let's see if this is a post that's tagged for re-delivery */ - $terms = get_terms_oftype($item['term'],TERM_MENTION); + $terms = array_merge(get_terms_oftype($item['term'],TERM_MENTION),get_terms_oftype($item['term'],TERM_FORUM)); if($terms) logger('tag_deliver: post mentions: ' . print_r($terms,true), LOGGER_DATA); @@ -2492,25 +2495,46 @@ function tag_deliver($uid, $item_id) { $plustagged = false; $matches = array(); - $pattern = '/@\!?\[zrl\=' . preg_quote($term['url'],'/') . '\]' . preg_quote($term['term'],'/') . '\[\/zrl\]/'; + $pattern = '/[\!@]\!?\[zrl\=' . preg_quote($term['url'],'/') . '\]' . preg_quote($term['term'],'/') . '\[\/zrl\]/'; if(preg_match($pattern,$body,$matches)) $tagged = true; - $pattern = '/@\!?\[zrl\=([^\]]*?)\]((?:.(?!\[zrl\=))*?)\+\[\/zrl\]/'; + // original red forum tagging sequence @forumname+ + // standard forum tagging sequence !forumname - // statusnet style group tags - $pattern2 = '/^|[^@]\!\[zrl\=([^\]]*?)\]((?:.(?!\[zrl\=))*?)\+\[\/zrl\]/'; + $pluspattern = '/@\!?\[zrl\=([^\]]*?)\]((?:.(?!\[zrl\=))*?)\+\[\/zrl\]/'; - if(preg_match_all($pattern,$body,$matches,PREG_SET_ORDER)) { - $max_forums = get_config('system','max_tagged_forums'); - if(! $max_forums) - $max_forums = 2; - $matched_forums = 0; + $forumpattern = '/\!\!?\[zrl\=([^\]]*?)\]((?:.(?!\[zrl\=))*?)\[\/zrl\]/'; + + $found = false; + + $max_forums = get_config('system','max_tagged_forums'); + if(! $max_forums) + $max_forums = 2; + $matched_forums = 0; + $matches = array(); + + if(preg_match_all($pluspattern,$body,$matches,PREG_SET_ORDER)) { foreach($matches as $match) { $matched_forums ++; if($term['url'] === $match[1] && $term['term'] === $match[2]) { if($matched_forums <= $max_forums) { $plustagged = true; + $found = true; + break; + } + logger('forum ' . $term['term'] . ' exceeded max_tagged_forums - ignoring'); + } + } + } + + if(preg_match_all($forumpattern,$body,$matches,PREG_SET_ORDER)) { + foreach($matches as $match) { + $matched_forums ++; + if($term['url'] === $match[1] && $term['term'] === $match[2]) { + if($matched_forums <= $max_forums) { + $plustagged = true; + $found = true; break; } logger('forum ' . $term['term'] . ' exceeded max_tagged_forums - ignoring'); @@ -2609,7 +2633,8 @@ function tgroup_check($uid,$item) { if(! $u) return false; - $terms = get_terms_oftype($item['term'],TERM_MENTION); + + $terms = array_merge(get_terms_oftype($item['term'],TERM_MENTION),get_terms_oftype($item['term'],TERM_FORUM)); if($terms) logger('tgroup_check: post mentions: ' . print_r($terms,true), LOGGER_DATA); @@ -2640,18 +2665,34 @@ function tgroup_check($uid,$item) { $body = preg_replace('/\[share(.*?)\[\/share\]/','',$body); -// $pattern = '/@\!?\[zrl\=' . preg_quote($term['url'],'/') . '\]' . preg_quote($term['term'] . '+','/') . '\[\/zrl\]/'; - $pattern = '/@\!?\[zrl\=([^\]]*?)\]((?:.(?!\[zrl\=))*?)\+\[\/zrl\]/'; + $pluspattern = '/@\!?\[zrl\=([^\]]*?)\]((?:.(?!\[zrl\=))*?)\+\[\/zrl\]/'; + + $forumpattern = '/\!\!?\[zrl\=([^\]]*?)\]((?:.(?!\[zrl\=))*?)\[\/zrl\]/'; + $found = false; + + $max_forums = get_config('system','max_tagged_forums'); + if(! $max_forums) + $max_forums = 2; + $matched_forums = 0; $matches = array(); - if(preg_match_all($pattern,$body,$matches,PREG_SET_ORDER)) { - $max_forums = get_config('system','max_tagged_forums'); - if(! $max_forums) - $max_forums = 2; - $matched_forums = 0; + if(preg_match_all($pluspattern,$body,$matches,PREG_SET_ORDER)) { + foreach($matches as $match) { + $matched_forums ++; + if($term['url'] === $match[1] && $term['term'] === $match[2]) { + if($matched_forums <= $max_forums) { + $found = true; + break; + } + logger('forum ' . $term['term'] . ' exceeded max_tagged_forums - ignoring'); + } + } + } + + if(preg_match_all($forumpattern,$body,$matches,PREG_SET_ORDER)) { foreach($matches as $match) { $matched_forums ++; if($term['url'] === $match[1] && $term['term'] === $match[2]) { diff --git a/include/taxonomy.php b/include/taxonomy.php index 36e05b008..23acaa24d 100644 --- a/include/taxonomy.php +++ b/include/taxonomy.php @@ -134,6 +134,8 @@ function format_term_for_display($term) { $s = ''; if(($term['ttype'] == TERM_HASHTAG) || ($term['ttype'] == TERM_COMMUNITYTAG)) $s .= '#'; + elseif($term['ttype'] == TERM_FORUM) + $s .= '!'; elseif($term['ttype'] == TERM_MENTION) $s .= '@'; else diff --git a/include/text.php b/include/text.php index 49cc2d6c5..bd802b2c1 100644 --- a/include/text.php +++ b/include/text.php @@ -761,9 +761,17 @@ function profiler($t1,$t2,$label) { function activity_match($haystack,$needle) { - if(($haystack === $needle) || ((basename($needle) === $haystack) && strstr($needle,NAMESPACE_ACTIVITY_SCHEMA))) - return true; + if(! is_array($needle)) + $needle = [ $needle ]; + + if($needle) { + foreach($needle as $n) { + if(($haystack === $n) || (strtolower(basename($n)) === strtolower(basename($haystack)))) { + return true; + } + } + } return false; } @@ -794,7 +802,7 @@ function get_tags($s) { // match any double quoted tags - if(preg_match_all('/([@#]\"\;.*?\"\;)/',$s,$match)) { + if(preg_match_all('/([@#!]\"\;.*?\"\;)/',$s,$match)) { foreach($match[1] as $mtch) { $ret[] = $mtch; } @@ -823,7 +831,7 @@ function get_tags($s) { // Otherwise pull out single word tags. These can be @nickname, @first_last // and #hash tags. - if(preg_match_all('/(?