Merge branch 'dev' of /home/macgirvin/roadhouse into dev

This commit is contained in:
nobody 2021-04-18 13:19:55 -07:00
commit 12b943fb2d
12 changed files with 314 additions and 45 deletions

View file

@ -9,6 +9,7 @@
# - misty : https://zotlabs.com/misty/ # - misty : https://zotlabs.com/misty/
# - osada : https://codeberg.org/zot/osada # - osada : https://codeberg.org/zot/osada
# - redmatrix : https://codeberg.org/zot/redmatrix # - redmatrix : https://codeberg.org/zot/redmatrix
# - roadhouse : https://codeberg.org/zot/roadhouse
# under Debian Linux "Buster" # under Debian Linux "Buster"
# #
# 1) Copy the file "zotserver-config.txt.template" to "zotserver-config.txt" # 1) Copy the file "zotserver-config.txt.template" to "zotserver-config.txt"
@ -532,8 +533,11 @@ function zotserver_name {
elif git remote -v | grep -i "origin.*redmatrix.*" elif git remote -v | grep -i "origin.*redmatrix.*"
then then
zotserver=redmatrix zotserver=redmatrix
elif git remote -v | grep -i "origin.*roadhouse.*"
then
zotserver=roadhouse
else else
die "neither redmatrix, osada, misty, zap nor hubzilla repository > did not install redmatrix/osada/misty/zap/hubzilla" die "neither roadhouse, redmatrix, osada, misty, zap nor hubzilla repository > did not install roadhouse/redmatrix/osada/misty/zap/hubzilla"
fi fi
} }
@ -560,8 +564,12 @@ function install_zotserver {
then then
print_info "redmatrix" print_info "redmatrix"
util/add_addon_repo https://codeberg.org/zot/redmatrix-addons.git raddons util/add_addon_repo https://codeberg.org/zot/redmatrix-addons.git raddons
elif [ $zotserver = "roadhouse" ]
then
print_info "roadhouse"
util/add_addon_repo https://codeberg.org/zot/roadhouse-addons.git rhaddons
else else
die "neither redmatrix, osada, misty, zap nor hubzilla repository > did not install addons or redmatrix/osada/misty/zap/hubzilla" die "neither roadhouse, redmatrix, osada, misty, zap nor hubzilla repository > did not install addons or roadhouse/redmatrix/osada/misty/zap/hubzilla"
fi fi
mkdir -p "cache/smarty3" mkdir -p "cache/smarty3"
mkdir -p "store" mkdir -p "store"

View file

@ -61,11 +61,11 @@ The Zot permission system has years of historical use and is different than and
Delivery model Delivery model
This project uses the relay system pioneered by projects such as Friendica, Diaspora, and Hubzilla which attempts to keep entire conversations intact and keeps the conversation initiator in control of the privacy distribution. This is not guaranteed under ActivityPub where conversation members can cc: others who were not in the initial privacy group. We encourage projects to not allow additional recipients or not include their own followers in followups. Followups SHOULD have one recipient - the conversation owner or originator, and are relayed by the owner to the other conversation members. This normally requires the use of LD-Signatures but may also be accessible through authenticated fetch of the activity using HTTP signatures. This project uses the relay system pioneered by projects such as Friendica, Diaspora, and Hubzilla which attempts to keep entire conversations intact and keeps the conversation initiator in control of the privacy distribution. This is not guaranteed under ActivityPub where conversation members can cc: others who were not in the initial privacy group. We encourage projects to not allow additional recipients or not include their own followers in followups. Followups SHOULD have one recipient - the conversation owner or originator, and are relayed by the owner to the other conversation members. This normally requires the use of LD-Signatures but may also be accessible through authenticated fetch of the activity using HTTP signatures.
Content Content
Content may be rich multi-media and renders nicely as HTML. Bbcode is used internally due to its ease of purification while still providing rich multi-media support. Content is not obviously length-limited and authors MAY use up to the storage maximum of 24MB. In practice bbcode conversion limits the effective length to around 200KB and the default "maximum length of imported content" from other sites is 200KB. This can be changed on a per-site basis but this is rare. A Note may contain one or more images or links. The images are also added as an attachment for the benefit of Mastodon, but remain in the HTML source. When importing content from other sites, if the content contains an image attachment, the content is scanned to see if a link (a) or (img) tag containing that image is already present in the HTML. If not, an image tag is added inline to the end of the incoming content. Multiple images are supported using this mechanism. Content may be rich multi-media and renders nicely as HTML. Multicode is used and stored internally. Multicode is html, markdown, and bbcode. The multicode interpreter allows you to switch between these or combine them at will. Content is not obviously length-limited and authors MAY use up to the storage maximum of 24MB. In practice markup conversion limits the effective length to around 200KB and the default "maximum length of imported content" from other sites is 200KB. This can be changed on a per-site basis but this is rare. A Note may contain one or more images, other media, and links. HTML purification primarily removes javascript and iframes and allows most legitimate tags and CSS styles through. Inline images are also added as attachments for the benefit of Mastodon (which strips most HTML), but remain in the HTML source. When importing content from other sites, if the content contains an image attachment, the content is scanned to see if a link (a) or (img) tag containing that image is already present in the HTML. If not, an image tag is added inline to the end of the incoming content. Multiple images are supported using this mechanism.
Mastodon 'summary' does not invoke any special handling so 'summary' can be used for its intended purpose as a content summary. Mastodon 'sensitive' is honoured and results in invoking whatever mechanisms the user has selected to deal with this type of content. By default images are obscured and are 'click to view'. Sensitive text is not treated specially, but may be obscured using the NSFW plugin or filtered per connection based on string match, tags, patterns, languages, or other criteria. Mastodon 'summary' does not invoke any special handling so 'summary' can be used for its intended purpose as a content summary. Mastodon 'sensitive' is honoured and results in invoking whatever mechanisms the user has selected to deal with this type of content. By default images are obscured and are 'click to view'. Sensitive text is not treated specially, but may be obscured using the NSFW plugin or filtered per connection based on string match, tags, patterns, languages, or other criteria.
@ -75,7 +75,9 @@ Edited posts and comments are sent with Update/Note and an 'updated' timestamp a
Announce Announce
Announce and relay activities are supported on the inbound side but are not generated. Instead a new message is generated with an embedded rendering of the shared content as the message content. This message may (should) contain additional commentary in order to comply with the Fair Use provisions of copyright law. Announce and relay activities use two mechanisms. As wll as the Announce activity, a new message is generated with an embedded rendering of the shared content as the message content. This message may (should) contain additional commentary in order to comply with the Fair Use provisions of copyright law. The other reason is our use of comment permissions. Comments to Announce activities are sent to the author (who typically accepts comments only from connections). Comment to embedded forwards are sent to the sender. This difference in behaviour allows groups to work correctly in the presence of comment permissions.
Discussion (2021-04-17): In the email world this type of conflict is resolved by the use of the reply-to header (e.g. in this case reply to the group rather than to the author) as well as the concept of a 'sender' which is different than 'from' (the author). We will soon be modelling the first one in ActivityPub with the use of 'replyTo'. If you see 'replyTo' in an activity it indicates that replies SHOULD go to that address rather than the author's inbox. We will implement this first and come up with a proposal for 'sender' if this gets any traction. If enough projects support these constructs we can eliminate the multiple relay mechanisms and in the process make ActivityPub much more versatile when it comes to organisational and group communications. Our primary use case for 'sender' is to provide an ActivityPub origin to a message that was imported from another system entirely (such as Diaspora or from RSS source). In this case we would set 'attributedTo' to the remote identity that authored the content, and 'sender' to the person that posted it in ActivityPub.
Mastodon Custom Emojis Mastodon Custom Emojis
@ -84,5 +86,14 @@ Mastodon Custom Emojis are only supported for post content. Display names and me
Mentions and private mentions Mentions and private mentions
By default the mention format is '@Display Name', but other options are available, such as '@username' and both '@Display Name (username)'. Mentions may also contain an exclamation character, for example '@!Display Name'. This indicates a Direct or private message to 'Display Name' and post addressing/privacy are altered accordingly. By default the mention format is '@Display Name', but other options are available, such as '@username' and both '@Display Name (username)'. Mentions may also contain an exclamation character, for example '@!Display Name'. This indicates a Direct or private message to 'Display Name' and post addressing/privacy are altered accordingly. All incoming and outgoing content to your stream is re-written to display mentions in your chosen style. This mechanism may need to be adopted by other projects to provide consistency as there are strong feelings on both sides of the debate about which form should be prevalent in the fediverse.
(Mastodon) Comment Notifications
Our projects send comment notifications if somebody replies to a post you either created or have previously interacted with in some way. They also are able to send a "mention" notification if you were mentioned in the post. This differs from Mastodon which does not appear to support comment notifications at all and only provides mention notifications. For this reason, Mastodon users don't typically get notified unless the author they are replying to is mentioned in the post. We provide this mention in the 'tag' field of the generated Activity, but normally don't include it in the message body, as we don't actually require mentions that were created for the sole purpose of triggering a comment notification.
Conversation Completion
(2021-04-17) It's easy to fetch missing pieces of a conversation going "upstream", but there is no agreed-on method to fetch a complete conversation from the viewpoint of the origin actor and upstream fetching only provides a single conversation branch, rather than the entire tree. We provide 'context' as a URL to a collection containing the entire conversation (all known branches) as seen by its creator. This requires special treatment and is very similar to ostatus:conversation in that if context is present, it needs to be replicated in conversation descendants. We still support ostatus:conversation but usage is deprecated. We do not use 'replies' to achieve the same purposes because 'replies' only applies to direct descendants at any point in the conversation tree.

View file

@ -401,6 +401,35 @@ class Notifier {
logger('followup relay (upstream delivery)', LOGGER_DEBUG); logger('followup relay (upstream delivery)', LOGGER_DEBUG);
$sendto = ($uplink) ? $parent_item['source_xchan'] : $parent_item['owner_xchan']; $sendto = ($uplink) ? $parent_item['source_xchan'] : $parent_item['owner_xchan'];
self::$recipients = [ $sendto ]; self::$recipients = [ $sendto ];
if (defined('X-REPLY-TO')) {
// experimental until debugging is completed
if ($parent_item['replyto'] && (! $uplink)) {
$ptr = unserialise($parent_item['replyto']);
if (is_string($ptr)) {
if (ActivityStreams::is_url($sendto)) {
$sendto = $ptr;
self::$recipients = [ $sendto ];
}
}
elseif (is_array($ptr)) {
$sendto = [];
foreach ($ptr as $rto) {
if (is_string($rto)) {
$sendto[] = $rto;
}
elseif (is_array($rto) && isset($rto['id'])) {
$sendto[] = $rto['id'];
}
}
self::$recipients = $sendto;
}
}
}
// END defined('X-REPLY-TO')
self::$private = true; self::$private = true;
$upstream = true; $upstream = true;
self::$packet_type = 'response'; self::$packet_type = 'response';

View file

@ -745,7 +745,10 @@ class Activity {
if (! in_array($ret['type'],[ 'Create','Update','Accept','Reject','TentativeAccept','TentativeReject' ])) { if (! in_array($ret['type'],[ 'Create','Update','Accept','Reject','TentativeAccept','TentativeReject' ])) {
$ret['inReplyTo'] = $i['thr_parent']; $ret['inReplyTo'] = $i['thr_parent'];
$cnv = get_iconfig($i['parent'],'ostatus','conversation'); $cnv = get_iconfig($i['parent'],'activitypub','context');
if (! $cnv) {
$cnv = get_iconfig($i['parent'],'ostatus','conversation');
}
if (! $cnv) { if (! $cnv) {
$cnv = $ret['parent_mid']; $cnv = $ret['parent_mid'];
} }
@ -753,15 +756,19 @@ class Activity {
} }
if (! (isset($cnv) && $cnv)) { if (! (isset($cnv) && $cnv)) {
// This method may be called before the item is actually saved - in which case there is no id and IConfig cannot be used $cnv = get_iconfig($i,'activitypub','context');
if ($i['id']) { if (! $cnv) {
$cnv = get_iconfig($i,'ostatus','conversation'); $cnv = get_iconfig($i,'ostatus','conversation');
} }
else { if (! $cnv) {
$cnv = $i['parent_mid']; $cnv = $i['parent_mid'];
} }
} }
if (isset($cnv) && $cnv) { if (isset($cnv) && $cnv) {
if (strpos($cnv,z_root()) === 0) {
$cnv = str_replace(['/item/','/activity/'],[ '/conversation/', '/conversation/' ], $cnv);
}
$ret['context'] = $cnv;
$ret['conversation'] = $cnv; $ret['conversation'] = $cnv;
} }
@ -775,10 +782,11 @@ class Activity {
else else
return []; return [];
$replyto = self::encode_person($i['owner'],false);
// if ($replyto) { $replyto = unserialise($i['replyto']);
// $ret['replyTo'] = $replyto; if ($replyto) {
// } $ret['replyTo'] = $replyto;
}
if (! isset($ret['url'])) { if (! isset($ret['url'])) {
$urls = []; $urls = [];
@ -1065,7 +1073,10 @@ class Activity {
if ($i['mid'] !== $i['parent_mid']) { if ($i['mid'] !== $i['parent_mid']) {
$ret['inReplyTo'] = $i['thr_parent']; $ret['inReplyTo'] = $i['thr_parent'];
$cnv = get_iconfig($i['parent'],'ostatus','conversation'); $cnv = get_iconfig($i['parent'],'activitypub','context');
if (! $cnv) {
$cnv = get_iconfig($i['parent'],'ostatus','conversation');
}
if (! $cnv) { if (! $cnv) {
$cnv = $ret['parent_mid']; $cnv = $ret['parent_mid'];
} }
@ -1091,14 +1102,19 @@ class Activity {
} }
} }
if (! isset($cnv)) { if (! isset($cnv)) {
if ($i['id']) { $cnv = get_iconfig($i,'activitypub','context');
if (! $cnv) {
$cnv = get_iconfig($i,'ostatus','conversation'); $cnv = get_iconfig($i,'ostatus','conversation');
} }
else { if (! $cnv) {
$cnv = $i['parent_mid']; $cnv = $i['parent_mid'];
} }
} }
if ($cnv) { if (isset($cnv) && $cnv) {
if (strpos($cnv,z_root()) === 0) {
$cnv = str_replace(['/item/','/activity/'],[ '/conversation/', '/conversation/' ], $cnv);
}
$ret['context'] = $cnv;
$ret['conversation'] = $cnv; $ret['conversation'] = $cnv;
} }
@ -1151,10 +1167,10 @@ class Activity {
} }
} }
$replyto = self::encode_person($i['owner'],false); $replyto = unserialise($i['replyto']);
// if ($replyto) { if ($replyto) {
// $ret['replyTo'] = $replyto; $ret['replyTo'] = $replyto;
// } }
if (! isset($ret['url'])) { if (! isset($ret['url'])) {
$urls = []; $urls = [];
@ -1483,9 +1499,10 @@ class Activity {
$ret['following'] = z_root() . '/following/' . $c['channel_address']; $ret['following'] = z_root() . '/following/' . $c['channel_address'];
$ret['endpoints'] = [ $ret['endpoints'] = [
'sharedInbox' => z_root() . '/inbox', 'sharedInbox' => z_root() . '/inbox',
'oauthRegistrationEndpoint' => z_root() . '/api/client/register',
'oauthAuthorizationEndpoint' => z_root() . '/authorize', 'oauthAuthorizationEndpoint' => z_root() . '/authorize',
'oauthTokenEndpoint' => z_root() . '/token' 'oauthTokenEndpoint' => z_root() . '/token'
]; ];
$ret['discoverable'] = ((1 - intval($p['xchan_hidden'])) ? true : false); $ret['discoverable'] = ((1 - intval($p['xchan_hidden'])) ? true : false);
@ -2499,6 +2516,15 @@ class Activity {
$s['mid'] = $s['parent_mid'] = $act->id; $s['mid'] = $s['parent_mid'] = $act->id;
} }
if (isset($act->replyto) && ! empty($act->replyto)) {
if (is_array($act->replyto) && isset($act->replyto['id'])) {
$s['replyto'] = $act->replyto['id'];
}
else {
$s['replyto'] = $act->replyto;
}
}
if (ActivityStreams::is_response_activity($act->type)) { if (ActivityStreams::is_response_activity($act->type)) {
$response_activity = true; $response_activity = true;
@ -2506,9 +2532,6 @@ class Activity {
$s['mid'] = $act->id; $s['mid'] = $act->id;
$s['parent_mid'] = $act->obj['id']; $s['parent_mid'] = $act->obj['id'];
// if (isset($act->replyto) && ! empty($act->replyto)) {
// $s['replyto'] = $act->replyto;
// }
// over-ride the object timestamp with the activity // over-ride the object timestamp with the activity
@ -3317,11 +3340,14 @@ class Activity {
return; return;
} }
if ($act->obj['context']) {
set_iconfig($item,'activitypub','context',$act->obj['context'],1);
}
if ($act->obj['conversation']) { if ($act->obj['conversation']) {
set_iconfig($item,'ostatus','conversation',$act->obj['conversation'],1); set_iconfig($item,'ostatus','conversation',$act->obj['conversation'],1);
} }
set_iconfig($item,'activitypub','recips',$act->raw_recips); set_iconfig($item,'activitypub','recips',$act->raw_recips);
if (! (isset($act->data['inheritPrivacy']) && $act->data['inheritPrivacy'])) { if (! (isset($act->data['inheritPrivacy']) && $act->data['inheritPrivacy'])) {
@ -3790,8 +3816,10 @@ class Activity {
'toot' => 'http://joinmastodon.org/ns#', 'toot' => 'http://joinmastodon.org/ns#',
'ostatus' => 'http://ostatus.org#', 'ostatus' => 'http://ostatus.org#',
'schema' => 'http://schema.org#', 'schema' => 'http://schema.org#',
'litepub' => 'http://litepub.social/ns#',
'conversation' => 'ostatus:conversation', 'conversation' => 'ostatus:conversation',
'manuallyApprovesFollowers' => 'as:manuallyApprovesFollowers', 'manuallyApprovesFollowers' => 'as:manuallyApprovesFollowers',
'oauthRegistrationEndpoint' => 'litepub:oauthRegistrationEndpoint',
'sensitive' => 'as:sensitive', 'sensitive' => 'as:sensitive',
'movedTo' => 'as:movedTo', 'movedTo' => 'as:movedTo',
'copiedTo' => 'as:copiedTo', 'copiedTo' => 'as:copiedTo',

View file

@ -232,7 +232,7 @@ class Activity extends Controller {
if(! perm_is_allowed($chan['channel_id'],get_observer_hash(),'view_stream')) if(! perm_is_allowed($chan['channel_id'],get_observer_hash(),'view_stream'))
http_status_exit(403, 'Forbidden'); http_status_exit(403, 'Forbidden');
$i = ZlibActivity::encode_item_collection($nitems,'conversation/' . $item_id,'OrderedCollection',true); $i = ZlibActivity::encode_item_collection($nitems,'conversation/' . $item_id,'OrderedCollection',true, count($nitems));
if ($portable_id && (! intval($items[0]['item_private']))) { if ($portable_id && (! intval($items[0]['item_private']))) {
ThreadListener::store(z_root() . '/activity/' . $item_id,$portable_id); ThreadListener::store(z_root() . '/activity/' . $item_id,$portable_id);
} }

View file

@ -0,0 +1,162 @@
<?php
namespace Zotlabs\Module;
use Zotlabs\Web\Controller;
use Zotlabs\Lib\ActivityStreams;
use Zotlabs\Lib\Activity as ZlibActivity;
use Zotlabs\Lib\Libzot;
use Zotlabs\Web\HTTPSig;
use Zotlabs\Lib\LDSignatures;
use Zotlabs\Lib\ThreadListener;
use App;
class Conversation extends Controller {
function init() {
if (ActivityStreams::is_as_request()) {
$item_id = argv(1);
if (! $item_id)
http_status_exit(404, 'Not found');
$portable_id = EMPTY_STR;
$item_normal = " and item.item_hidden = 0 and item.item_type = 0 and item.item_unpublished = 0 and item.item_delayed = 0 and item.item_blocked = 0 and not verb in ( 'Follow', 'Unfollow' ) ";
$i = null;
// do we have the item (at all)?
$r = q("select * from item where mid = '%s' $item_normal limit 1",
dbesc(z_root() . '/activity/' . $item_id)
);
if (! $r) {
$r = q("select * from item where mid = '%s' $item_normal limit 1",
dbesc(z_root() . '/item/' . $item_id)
);
if (! $r) {
http_status_exit(404,'Not found');
}
}
// process an authenticated fetch
$sigdata = HTTPSig::verify(EMPTY_STR);
if($sigdata['portable_id'] && $sigdata['header_valid']) {
$portable_id = $sigdata['portable_id'];
observer_auth($portable_id);
// first see if we have a copy of this item's parent owned by the current signer
// include xchans for all zot-like networks - these will have the same guid and public key
$x = q("select * from xchan where xchan_hash = '%s'",
dbesc($sigdata['portable_id'])
);
if ($x) {
$xchans = q("select xchan_hash from xchan where xchan_hash = '%s' OR ( xchan_guid = '%s' AND xchan_pubkey = '%s' ) ",
dbesc($sigdata['portable_id']),
dbesc($x[0]['xchan_guid']),
dbesc($x[0]['xchan_pubkey'])
);
if ($xchans) {
$hashes = ids_to_querystr($xchans,'xchan_hash',true);
$i = q("select id as item_id from item where mid = '%s' $item_normal and owner_xchan in ( " . protect_sprintf($hashes) . " ) limit 1",
dbesc($r[0]['parent_mid'])
);
}
}
}
// if we don't have a parent id belonging to the signer see if we can obtain one as a visitor that we have permission to access
// with a bias towards those items owned by channels on this site (item_wall = 1)
$sql_extra = item_permissions_sql(0);
if (! $i) {
$i = q("select id as item_id from item where mid = '%s' $item_normal $sql_extra order by item_wall desc limit 1",
dbesc($r[0]['parent_mid'])
);
}
if(! $i) {
http_status_exit(403,'Forbidden');
}
$parents_str = ids_to_querystr($i,'item_id');
$items = q("SELECT item.*, item.id AS item_id FROM item WHERE item.parent IN ( %s ) $item_normal ",
dbesc($parents_str)
);
if(! $items) {
http_status_exit(404, 'Not found');
}
xchan_query($items,true);
$items = fetch_post_tags($items,true);
$observer = App::get_observer();
$parent = $items[0];
$recips = (($parent['owner']['xchan_network'] === 'activitypub') ? get_iconfig($parent['id'],'activitypub','recips', []) : []);
$to = (($recips && array_key_exists('to',$recips) && is_array($recips['to'])) ? $recips['to'] : null);
$nitems = [];
foreach($items as $i) {
$mids = [];
if(intval($i['item_private'])) {
if(! $observer) {
continue;
}
// ignore private reshare, possibly from hubzilla
if($i['verb'] === 'Announce') {
if(! in_array($i['thr_parent'],$mids)) {
$mids[] = $i['thr_parent'];
}
continue;
}
// also ignore any children of the private reshares
if(in_array($i['thr_parent'],$mids)) {
continue;
}
if((! $to) || (! in_array($observer['xchan_url'],$to))) {
continue;
}
}
$nitems[] = $i;
}
if(! $nitems)
http_status_exit(404, 'Not found');
$chan = channelx_by_n($nitems[0]['uid']);
if(! $chan)
http_status_exit(404, 'Not found');
if(! perm_is_allowed($chan['channel_id'],get_observer_hash(),'view_stream'))
http_status_exit(403, 'Forbidden');
$i = ZlibActivity::encode_item_collection($nitems,'conversation/' . $item_id,'OrderedCollection',true, count($nitems));
if ($portable_id && (! intval($items[0]['item_private']))) {
ThreadListener::store(z_root() . '/activity/' . $item_id,$portable_id);
}
if(! $i)
http_status_exit(404, 'Not found');
$channel = channelx_by_n($items[0]['uid']);
as_return_and_die($i,$channel);
}
goaway(z_root() . '/item/' . argv(1));
}
}

View file

@ -234,8 +234,7 @@ class Item extends Controller {
http_status_exit(403, 'Forbidden'); http_status_exit(403, 'Forbidden');
} }
$i = Activity::encode_item_collection($items,'conversation/' . $item_id,'OrderedCollection',true, count($nitems));
$i = Activity::encode_item_collection($items,'conversation/' . $item_id,'OrderedCollection',true);
if ($portable_id && (! intval($items[0]['item_private']))) { if ($portable_id && (! intval($items[0]['item_private']))) {
ThreadListener::store(z_root() . '/item/' . $item_id,$portable_id); ThreadListener::store(z_root() . '/item/' . $item_id,$portable_id);
} }
@ -534,6 +533,10 @@ class Item extends Controller {
} }
if ($parent_item && isset($parent_item['replyto']) && $parent_item['replyto']) {
$replyto = unserialise($parent_item['replyto']);
}
$moderated = false; $moderated = false;
if (! $observer) { if (! $observer) {
@ -697,7 +700,17 @@ class Item extends Controller {
} }
} }
} }
if (! isset($replyto)) {
if (strpos($owner_xchan['xchan_hash'],'http') === 0) {
$replyto = $owner_xchan['xchan_hash'];
}
else {
$replyto = $owner_xchan['xchan_url'];
}
}
$acl = new AccessControl($channel); $acl = new AccessControl($channel);
$view_policy = PermissionLimits::Get($channel['channel_id'],'view_stream'); $view_policy = PermissionLimits::Get($channel['channel_id'],'view_stream');
@ -1341,7 +1354,7 @@ class Item extends Controller {
$datarray['term'] = $post_tags; $datarray['term'] = $post_tags;
$datarray['plink'] = $plink; $datarray['plink'] = $plink;
$datarray['route'] = $route; $datarray['route'] = $route;
$datarray['replyto'] = $replyto;
// A specific ACL over-rides public_policy completely // A specific ACL over-rides public_policy completely

View file

@ -16,23 +16,25 @@ class Notifications extends \Zotlabs\Web\Controller {
$o = ''; $o = '';
$r = q("select count(*) as total from notify where uid = %d and seen = 0", $n = q("select count(*) as total from notify where uid = %d and seen = 0",
intval(local_channel()) intval(local_channel())
); );
if($r && intval($r[0]['total']) > 49) { if($n && intval($n[0]['total']) > 49) {
$r = q("select * from notify where uid = %d $r = q("select * from notify where uid = %d
and seen = 0 order by created desc limit 50", and seen = 0 order by created desc limit 50",
intval(local_channel()) intval(local_channel())
); );
} else { }
else {
$r1 = q("select * from notify where uid = %d $r1 = q("select * from notify where uid = %d
and seen = 0 order by created desc limit 50", and seen = 0 order by created desc limit 50",
intval(local_channel()) intval(local_channel())
); );
$r2 = q("select * from notify where uid = %d $r2 = q("select * from notify where uid = %d
and seen = 1 order by created desc limit %d", and seen = 1 order by created desc limit %d",
intval(local_channel()), intval(local_channel()),
intval(50 - intval($r[0]['total'])) intval(50 - intval($n[0]['total']))
); );
$r = array_merge($r1,$r2); $r = array_merge($r1,$r2);
} }
@ -41,7 +43,7 @@ class Notifications extends \Zotlabs\Web\Controller {
$notifications_available = 1; $notifications_available = 1;
foreach ($r as $rr) { foreach ($r as $rr) {
$x = strip_tags(bbcode($rr['msg'])); $x = strip_tags(bbcode($rr['msg']));
$notif_content = replace_macros(get_markup_template('notify.tpl'),array( $notif_content .= replace_macros(get_markup_template('notify.tpl'),array(
'$item_link' => z_root().'/notify/view/'. $rr['id'], '$item_link' => z_root().'/notify/view/'. $rr['id'],
'$item_image' => $rr['photo'], '$item_image' => $rr['photo'],
'$item_text' => $x, '$item_text' => $x,

View file

@ -2323,6 +2323,15 @@ function construct_page() {
header("X-Content-Type-Options: nosniff"); header("X-Content-Type-Options: nosniff");
} }
if (isset(App::$config['system']['perm_policy_header']) && App::$config['system']['perm_policy_header']) {
header("Permissions-Policy: " . App::$config['system']['perm_policy_header']);
}
else {
// opt-out this site from federated browser surveillance
header("Permissions-Policy: interest-cohort=()");
}
if (isset(App::$config['system']['public_key_pins']) && App::$config['system']['public_key_pins']) { if (isset(App::$config['system']['public_key_pins']) && App::$config['system']['public_key_pins']) {
header("Public-Key-Pins: " . App::$config['system']['public_key_pins']); header("Public-Key-Pins: " . App::$config['system']['public_key_pins']);
} }

View file

@ -196,8 +196,7 @@ require_once('include/api_zot.php');
$secret = random_string(16); $secret = random_string(16);
$name = trim(escape_tags($_REQUEST['client_name'])); $name = trim(escape_tags($_REQUEST['client_name']));
if (! $name) { if (! $name) {
// json_return_and_die($ret); json_return_and_die($ret);
$name = random_string(8);
} }
if (is_array($_REQUEST['redirect_uris'])) { if (is_array($_REQUEST['redirect_uris'])) {
$redirect = trim($_REQUEST['redirect_uris'][0]); $redirect = trim($_REQUEST['redirect_uris'][0]);

View file

@ -282,7 +282,7 @@ function bb_parse_crypt($match) {
$onclick = 'onclick="' . $f . '(\'' . $algorithm . '\',\'' . $hint . '\',\'' . $match[2] . '\',\'#' . $x . '\');"'; $onclick = 'onclick="' . $f . '(\'' . $algorithm . '\',\'' . $hint . '\',\'' . $match[2] . '\',\'#' . $x . '\');"';
$label = t('Encrypted content'); $label = t('Encrypted content');
$Text = '<br /><div id="' . $x . '"><img class="cursor-pointer" src="' . z_root() . '/images/lock_icon.svg" ' . $onclick . ' alt="' . $label . '" title="' . $label . '" /></div><br />'; $Text = '<br><div id="' . $x . '"><img class="cursor-pointer" src="' . z_root() . '/images/lock_icon.svg" ' . $onclick . ' alt="' . $label . '" title="' . $label . '" /></div><br><br>' . bb_parse_b64_crypt($match);
return $Text; return $Text;
} }
@ -298,9 +298,11 @@ function bb_parse_b64_crypt($match) {
if(empty($match[2])) if(empty($match[2]))
return; return;
$r .= '----- ENCRYPTED CONTENT -----' . PHP_EOL; $r .= '----- ENCRYPTED CONTENT -----' . "\n";
$r .= $match[2] . PHP_EOL; $r .= base64_encode($match[1]) . "." . $match[2] . "\n";
$r .= '----- END ENCRYPTED CONTENT -----'; $r .= '----- END ENCRYPTED CONTENT -----' . "\n";
$r = '<code>' . str_replace("\n",'<br>',wordwrap($r,75,"\n",true)) . '</code>';
return $r; return $r;

View file

@ -724,6 +724,7 @@ function get_item_elements($x,$allow_code = false) {
} }
$arr['attach'] = activity_sanitise($x['attach']); $arr['attach'] = activity_sanitise($x['attach']);
$arr['replyto'] = activity_sanitise($c['replyto']);
$arr['term'] = decode_tags($x['tags']); $arr['term'] = decode_tags($x['tags']);
$arr['iconfig'] = decode_item_meta($x['meta']); $arr['iconfig'] = decode_item_meta($x['meta']);
@ -1115,6 +1116,7 @@ function encode_item($item,$mirror = false) {
$x['longlat'] = $item['coord']; $x['longlat'] = $item['coord'];
$x['signature'] = $item['sig']; $x['signature'] = $item['sig'];
$x['route'] = $item['route']; $x['route'] = $item['route'];
$x['replyto'] = $item['replyto'];
$x['owner'] = encode_item_xchan($item['owner']); $x['owner'] = encode_item_xchan($item['owner']);
$x['author'] = encode_item_xchan($item['author']); $x['author'] = encode_item_xchan($item['author']);
@ -3058,6 +3060,9 @@ function start_delivery_chain($channel, $item, $item_id, $parent, $group = false
$arr['deny_gid'] = $channel['channel_deny_gid']; $arr['deny_gid'] = $channel['channel_deny_gid'];
$arr['comment_policy'] = map_scope(PermissionLimits::Get($channel['channel_id'],'post_comments')); $arr['comment_policy'] = map_scope(PermissionLimits::Get($channel['channel_id'],'post_comments'));
$arr['replyto'] = z_root() . '/channel/' . $channel['channel_address'];
if ($arr['id']) { if ($arr['id']) {
$post = item_store_update($arr); $post = item_store_update($arr);
} }
@ -3117,12 +3122,13 @@ function start_delivery_chain($channel, $item, $item_id, $parent, $group = false
$title = $item['title']; $title = $item['title'];
$body = $item['body']; $body = $item['body'];
$r = q("update item set item_uplink = %d, item_nocomment = %d, item_flags = %d, owner_xchan = '%s', allow_cid = '%s', allow_gid = '%s', $r = q("update item set item_uplink = %d, item_nocomment = %d, item_flags = %d, owner_xchan = '%s', replyto = '%s', allow_cid = '%s', allow_gid = '%s',
deny_cid = '%s', deny_gid = '%s', item_private = %d, comment_policy = '%s', title = '%s', body = '%s', item_wall = %d, item_origin = %d where id = %d", deny_cid = '%s', deny_gid = '%s', item_private = %d, comment_policy = '%s', title = '%s', body = '%s', item_wall = %d, item_origin = %d where id = %d",
intval($item_uplink), intval($item_uplink),
intval($item_nocomment), intval($item_nocomment),
intval($flag_bits), intval($flag_bits),
dbesc($channel['channel_hash']), dbesc($channel['channel_hash']),
dbesc(channel_url($channel)),
dbesc($channel['channel_allow_cid']), dbesc($channel['channel_allow_cid']),
dbesc($channel['channel_allow_gid']), dbesc($channel['channel_allow_gid']),
dbesc($channel['channel_deny_cid']), dbesc($channel['channel_deny_cid']),