aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Zotlabs/Daemon/Fetchparents.php42
-rw-r--r--Zotlabs/Daemon/Notifier.php3
-rw-r--r--Zotlabs/Lib/ASCache.php33
-rw-r--r--Zotlabs/Lib/Activity.php858
-rw-r--r--Zotlabs/Lib/ActivityStreams.php21
-rw-r--r--Zotlabs/Lib/Cache.php16
-rw-r--r--Zotlabs/Lib/Libzot.php303
-rw-r--r--Zotlabs/Lib/ThreadItem.php6
-rw-r--r--Zotlabs/Module/Share.php24
-rw-r--r--Zotlabs/Module/Sse_bs.php14
-rw-r--r--Zotlabs/Widget/Messages.php3
-rw-r--r--boot.php3
-rw-r--r--doc/admin/administrator_guide.md5
-rw-r--r--doc/hook/check_account_password.bb16
-rw-r--r--include/bbcode.php4
-rw-r--r--include/event.php5
-rw-r--r--include/items.php4
-rw-r--r--include/text.php5
18 files changed, 441 insertions, 924 deletions
diff --git a/Zotlabs/Daemon/Fetchparents.php b/Zotlabs/Daemon/Fetchparents.php
new file mode 100644
index 000000000..b00acdfbf
--- /dev/null
+++ b/Zotlabs/Daemon/Fetchparents.php
@@ -0,0 +1,42 @@
+<?php
+
+namespace Zotlabs\Daemon;
+
+use Zotlabs\Lib\Activity;
+
+class Fetchparents {
+
+ static public function run($argc, $argv) {
+
+ logger('Fetchparents invoked: ' . print_r($argv, true));
+
+ if ($argc < 4) {
+ return;
+ }
+
+ $channels = explode(',', $argv[1]);
+ if (!$channels) {
+ return;
+ }
+
+ $observer_hash = $argv[2];
+ if (!$observer_hash) {
+ return;
+ }
+
+ $mid = $argv[3];
+ if (!$mid) {
+ return;
+ }
+
+ $force = $argv[4] ?? false;
+
+ foreach ($channels as $channel_id) {
+ $channel = channelx_by_n($channel_id);
+ Activity::fetch_and_store_parents($channel, $observer_hash, $mid, null, $force);
+ }
+
+ return;
+
+ }
+}
diff --git a/Zotlabs/Daemon/Notifier.php b/Zotlabs/Daemon/Notifier.php
index d1c0e4ec8..0474a4a26 100644
--- a/Zotlabs/Daemon/Notifier.php
+++ b/Zotlabs/Daemon/Notifier.php
@@ -374,7 +374,8 @@ class Notifier {
if (($relay_to_owner || $uplink) && ($cmd !== 'relay')) {
logger('notifier: followup relay', LOGGER_DEBUG);
- $sendto = (($uplink) ? $parent_item['source_xchan'] : (($parent_item['verb'] === ACTIVITY_SHARE) ? $parent_item['author_xchan'] : $parent_item['owner_xchan']));
+ // If the Parent item is an Announce the real owner is the parent author
+ $sendto = (($uplink) ? $parent_item['source_xchan'] : $parent_item['owner_xchan']);
self::$recipients = [$sendto];
self::$private = true;
$upstream = true;
diff --git a/Zotlabs/Lib/ASCache.php b/Zotlabs/Lib/ASCache.php
new file mode 100644
index 000000000..63bd73ea7
--- /dev/null
+++ b/Zotlabs/Lib/ASCache.php
@@ -0,0 +1,33 @@
+<?php /** @file */
+
+namespace Zotlabs\Lib;
+
+ /**
+ * A wrapper for the cache api
+ */
+
+class ASCache {
+ public static function isEnabled() {
+ return Config::Get('system', 'as_object_cache_enabled', false);
+ }
+
+ public static function getAge() {
+ return Config::Get('system', 'as_object_cache_time', '10 MINUTE');
+ }
+
+ public static function Get($key) {
+ if (!self::isEnabled()) {
+ return;
+ }
+
+ return Cache::get($key, self::getAge());
+ }
+
+ public static function Set($key, $value) {
+ if (!self::isEnabled()) {
+ return;
+ }
+
+ Cache::set($key, $value);
+ }
+}
diff --git a/Zotlabs/Lib/Activity.php b/Zotlabs/Lib/Activity.php
index 2bf8543b2..a225e551b 100644
--- a/Zotlabs/Lib/Activity.php
+++ b/Zotlabs/Lib/Activity.php
@@ -153,6 +153,7 @@ class Activity {
}
else {
logger('logger_stats_data cmd:Activity_fetch' . ' start:' . $start_timestamp . ' ' . 'end:' . microtime(true) . ' meta:' . $url . '#' . random_string(16));
+ btlogger('activity fetch');
}
return json_decode($x['body'], true);
@@ -363,7 +364,7 @@ class Activity {
if ($items) {
$x = [];
foreach ($items as $i) {
- $m = get_iconfig($i['id'], 'activitypub', 'rawmsg');
+ $m = IConfig::Get($i['id'], 'activitypub', 'rawmsg');
if ($m) {
if (is_string($m))
$t = json_decode($m, true);
@@ -498,7 +499,7 @@ class Activity {
// set this for descendants even if the current item is not private
// because it may have been relayed from a private item.
- $token = get_iconfig($i, 'ocap', 'relay');
+ $token = IConfig::Get($i, 'ocap', 'relay');
if ($token && $has_images) {
$matches_processed = [];
for ($n = 0; $n < count($images); $n++) {
@@ -1083,11 +1084,11 @@ class Activity {
$ret['type'] = 'Person';
if ($c) {
- if (get_pconfig($c['channel_id'], 'system', 'group_actor')) {
+ if (PConfig::Get($c['channel_id'], 'system', 'group_actor')) {
$ret['type'] = 'Group';
}
- $ret['manuallyApprovesFollowers'] = ((get_pconfig($c['channel_id'], 'system', 'autoperms')) ? false : true);
+ $ret['manuallyApprovesFollowers'] = ((PConfig::Get($c['channel_id'], 'system', 'autoperms')) ? false : true);
}
$ret['id'] = $id;
@@ -1396,7 +1397,7 @@ class Activity {
}
}
- $role = get_pconfig($channel['channel_id'], 'system', 'permissions_role', 'personal');
+ $role = PConfig::Get($channel['channel_id'], 'system', 'permissions_role', 'personal');
$x = PermissionRoles::role_perms($role);
$their_perms = Permissions::FilledPerms($x['perms_connect']);
@@ -1417,7 +1418,7 @@ class Activity {
// We've already approved them or followed them first
// Send an Accept back to them
- set_abconfig($channel['channel_id'], $person_obj['id'], 'pubcrawl', 'their_follow_id', $their_follow_id);
+ AbConfig::Set($channel['channel_id'], $person_obj['id'], 'pubcrawl', 'their_follow_id', $their_follow_id);
Master::Summon(['Notifier', 'permission_accept', $contact['abook_id']]);
return;
@@ -1429,7 +1430,7 @@ class Activity {
if(in_array($k, ['send_stream', 'post_wall'])) {
continue; // Those will be set once we accept their follow request
}
- set_abconfig($channel['channel_id'], $contact['abook_xchan'], 'their_perms', $k, $v);
+ AbConfig::Set($channel['channel_id'], $contact['abook_xchan'], 'their_perms', $k, $v);
}
$abook_instance = $contact['abook_instance'];
@@ -1463,7 +1464,7 @@ class Activity {
// From here on out we assume a Follow activity to somebody we have no existing relationship with
- set_abconfig($channel['channel_id'], $person_obj['id'], 'pubcrawl', 'their_follow_id', $their_follow_id);
+ AbConfig::Set($channel['channel_id'], $person_obj['id'], 'pubcrawl', 'their_follow_id', $their_follow_id);
// The xchan should have been created by actor_store() above
@@ -1481,7 +1482,7 @@ class Activity {
$my_perms = $p['perms'];
$automatic = $p['automatic'];
- $closeness = get_pconfig($channel['channel_id'], 'system', 'new_abook_closeness', 80);
+ $closeness = PConfig::Get($channel['channel_id'], 'system', 'new_abook_closeness', 80);
$r = abook_store_lowlevel(
[
@@ -1500,11 +1501,11 @@ class Activity {
if ($my_perms)
foreach ($my_perms as $k => $v)
- set_abconfig($channel['channel_id'], $ret['xchan_hash'], 'my_perms', $k, $v);
+ AbConfig::Set($channel['channel_id'], $ret['xchan_hash'], 'my_perms', $k, $v);
if ($their_perms)
foreach ($their_perms as $k => $v)
- set_abconfig($channel['channel_id'], $ret['xchan_hash'], 'their_perms', $k, $v);
+ AbConfig::Set($channel['channel_id'], $ret['xchan_hash'], 'their_perms', $k, $v);
if ($r) {
logger("New ActivityPub follower for {$channel['channel_name']}");
@@ -1540,7 +1541,7 @@ class Activity {
unset($clone['abook_account']);
unset($clone['abook_channel']);
- $abconfig = load_abconfig($channel['channel_id'], $clone['abook_xchan']);
+ $abconfig = AbConfig::Load($channel['channel_id'], $clone['abook_xchan']);
if ($abconfig)
$clone['abconfig'] = $abconfig;
@@ -1581,7 +1582,7 @@ class Activity {
);
if ($r) {
// remove all permissions they provided
- del_abconfig($channel['channel_id'], $r[0]['xchan_hash'], 'system', 'their_perms');
+ AbConfig::Delete($channel['channel_id'], $r[0]['xchan_hash'], 'system', 'their_perms');
}
}
@@ -1869,31 +1870,6 @@ class Activity {
}
}
- static function create_action($channel, $observer_hash, $act) {
-
- if (in_array($act->obj['type'], ['Note', 'Article', 'Video'])) {
- self::create_note($channel, $observer_hash, $act);
- }
-
-
- }
-
- static function announce_action($channel, $observer_hash, $act) {
-
- if (in_array($act->type, ['Announce'])) {
- self::announce_note($channel, $observer_hash, $act);
- }
-
- }
-
- static function like_action($channel, $observer_hash, $act) {
-
- if (in_array($act->obj['type'], ['Note', 'Article', 'Video'])) {
- self::like_note($channel, $observer_hash, $act);
- }
-
-
- }
// sort function width decreasing
static function vid_sort($a, $b) {
@@ -1906,251 +1882,6 @@ class Activity {
return (($a_width > $b_width) ? -1 : 1);
}
- static function create_note($channel, $observer_hash, $act) {
-
- $s = [];
- $is_sys_channel = is_sys_channel($channel['channel_id']);
- $parent = ((array_key_exists('inReplyTo', $act->obj)) ? urldecode($act->obj['inReplyTo']) : '');
-
- if ($parent) {
-
- $r = q("select * from item where uid = %d and ( mid = '%s' or mid = '%s' ) limit 1",
- intval($channel['channel_id']),
- dbesc($parent),
- dbesc(basename($parent))
- );
-
- if (!$r) {
- logger('parent not found.');
- return;
- }
-
- if ($r[0]['owner_xchan'] === $channel['channel_hash']) {
- if (!perm_is_allowed($channel['channel_id'], $observer_hash, 'send_stream') && !$is_sys_channel) {
- logger('no comment permission.');
- return;
- }
- }
-
- $s['parent_mid'] = $r[0]['mid'];
- $s['owner_xchan'] = $r[0]['owner_xchan'];
- $s['author_xchan'] = $observer_hash;
-
- }
- else {
- if (!perm_is_allowed($channel['channel_id'], $observer_hash, 'send_stream') && !$is_sys_channel) {
- logger('no send_stream permission');
- return;
- }
- $s['owner_xchan'] = $s['author_xchan'] = $observer_hash;
- }
-
- if ($act->recips && (!in_array(ACTIVITY_PUBLIC_INBOX, $act->recips)))
- $s['item_private'] = 1;
-
-
- if (array_key_exists('directMessage', $act->obj) && intval($act->obj['directMessage'])) {
- $s['item_private'] = 2;
- }
-
- if (intval($s['item_private']) === 2) {
- if (!perm_is_allowed($channel['channel_id'], $observer_hash, 'post_mail')) {
- logger('no post_mail permission');
- return;
- }
- }
-
- $content = self::get_content($act->obj);
-
- if (!$content) {
- logger('no content');
- return;
- }
-
- $s['aid'] = $channel['channel_account_id'];
- $s['uid'] = $channel['channel_id'];
-
- // Make sure we use the zot6 identity where applicable
-
- $s['author_xchan'] = self::find_best_identity($s['author_xchan']);
- $s['owner_xchan'] = self::find_best_identity($s['owner_xchan']);
-
- if (!$s['author_xchan']) {
- logger('No author: ' . print_r($act, true));
- }
-
- if (!$s['owner_xchan']) {
- logger('No owner: ' . print_r($act, true));
- }
-
- if (!$s['author_xchan'] || !$s['owner_xchan'])
- return;
-
- $s['mid'] = urldecode($act->obj['id']);
- $s['uuid'] = $act->obj['diaspora:guid'];
- $s['plink'] = urldecode($act->obj['id']);
-
-
- if ($act->data['published']) {
- $s['created'] = datetime_convert('UTC', 'UTC', $act->data['published']);
- }
- elseif ($act->obj['published']) {
- $s['created'] = datetime_convert('UTC', 'UTC', $act->obj['published']);
- }
- if ($act->data['updated']) {
- $s['edited'] = datetime_convert('UTC', 'UTC', $act->data['updated']);
- }
- elseif ($act->obj['updated']) {
- $s['edited'] = datetime_convert('UTC', 'UTC', $act->obj['updated']);
- }
- if ($act->data['expires']) {
- $s['expires'] = datetime_convert('UTC', 'UTC', $act->data['expires']);
- }
- elseif ($act->obj['expires']) {
- $s['expires'] = datetime_convert('UTC', 'UTC', $act->obj['expires']);
- }
-
- if (!$s['created'])
- $s['created'] = datetime_convert();
-
- if (!$s['edited'])
- $s['edited'] = $s['created'];
-
-
- if (!$s['parent_mid'])
- $s['parent_mid'] = $s['mid'];
-
-
- $s['title'] = self::bb_content($content, 'name');
- $s['summary'] = self::bb_content($content, 'summary');
- $s['body'] = self::bb_content($content, 'content');
- $s['verb'] = ACTIVITY_POST;
- $s['obj_type'] = ACTIVITY_OBJ_NOTE;
-
- $generator = $act->get_property_obj('generator');
- if (!$generator)
- $generator = $act->get_property_obj('generator', $act->obj);
-
- if ($generator && array_key_exists('type', $generator)
- && in_array($generator['type'], ['Application', 'Service']) && array_key_exists('name', $generator)) {
- $s['app'] = escape_tags($generator['name']);
- }
-
- if ($channel['channel_system']) {
- $incl = get_config('system','pubstream_incl');
- $excl = get_config('system','pubstream_excl');
-
- if(($incl || $excl) && !MessageFilter::evaluate($s, $incl, $excl)) {
- logger('post is filtered');
- return;
- }
- }
-
- $abook = q("select * from abook where (abook_xchan = '%s' OR abook_xchan = '%s') and abook_channel = %d ",
- dbesc($s['author_xchan']),
- dbesc($s['owner_xchan']),
- intval($channel['channel_id'])
- );
-
- if ($abook) {
- if (!post_is_importable($channel['channel_id'], $s, $abook)) {
- logger('post is filtered');
- return;
- }
- }
-
- if ($act->obj['conversation']) {
- set_iconfig($s, 'ostatus', 'conversation', $act->obj['conversation'], 1);
- }
-
- $a = self::decode_taxonomy($act->obj);
- if ($a) {
- $s['term'] = $a;
- }
-
- $a = self::decode_attachment($act->obj);
- if ($a) {
- $s['attach'] = $a;
- }
-
- if ($act->obj['type'] === 'Note' && $s['attach']) {
- $s['body'] .= self::bb_attach($s['attach'], $s['body']);
- }
-
- // we will need a hook here to extract magnet links e.g. peertube
- // right now just link to the largest mp4 we find that will fit in our
- // standard content region
-
- if ($act->obj['type'] === 'Video') {
-
- $vtypes = [
- 'video/mp4',
- 'video/ogg',
- 'video/webm'
- ];
-
- $mps = [];
- if (array_key_exists('url', $act->obj) && is_array($act->obj['url'])) {
- foreach ($act->obj['url'] as $vurl) {
- if (in_array($vurl['mimeType'], $vtypes)) {
- if (!array_key_exists('width', $vurl)) {
- $vurl['width'] = 0;
- }
- $mps[] = $vurl;
- }
- }
- }
- if ($mps) {
- usort($mps, [__CLASS__, 'vid_sort']);
- foreach ($mps as $m) {
- if (intval($m['width']) < 500) {
- $s['body'] .= "\n\n" . '[video]' . $m['href'] . '[/video]';
- break;
- }
- }
- }
- }
-
- set_iconfig($s, 'activitypub', 'recips', $act->raw_recips);
- if ($parent) {
- set_iconfig($s, 'activitypub', 'rawmsg', $act->raw, 1);
- }
-
- $x = null;
-
- $r = q("select created, edited from item where mid = '%s' and uid = %d limit 1",
- dbesc($s['mid']),
- intval($s['uid'])
- );
- if ($r) {
- if ($s['edited'] > $r[0]['edited']) {
- $x = item_store_update($s);
- }
- else {
- return;
- }
- }
- else {
- $x = item_store($s);
- }
-
- if (is_array($x) && $x['item_id']) {
- if ($parent) {
- if ($s['owner_xchan'] === $channel['channel_hash']) {
- // We are the owner of this conversation, so send all received comments back downstream
- Master::Summon(['Notifier', 'comment-import', $x['item_id']]);
- }
- $r = q("select * from item where id = %d limit 1",
- intval($x['item_id'])
- );
- if ($r) {
- send_status_notifications($x['item_id'], $r[0]);
- }
- }
- sync_an_item($channel['channel_id'], $x['item_id']);
- }
-
- }
static function get_actor_bbmention($id) {
@@ -2330,7 +2061,16 @@ class Activity {
$content = self::get_content($act->obj);
}
- $s['mid'] = $act->objprop('id') ?: $act->obj;
+ $s['mid'] = $act->objprop('id');
+
+ if (!$s['mid'] && is_string($act->obj)) {
+ $s['mid'] = $act->obj;
+ }
+
+ // pleroma fetched activities
+ if (!$s['mid'] && isset($act->obj['data']['id'])) {
+ $s['mid'] = $act->obj['data']['id'];
+ }
if (!$s['mid']) {
return false;
@@ -2908,22 +2648,22 @@ class Activity {
}
if ($ap_rawmsg) {
- set_iconfig($s, 'activitypub', 'rawmsg', $ap_rawmsg, 1);
+ IConfig::Set($s, 'activitypub', 'rawmsg', $ap_rawmsg, 1);
}
elseif (!array_key_exists('signed', $raw_arr)) {
- set_iconfig($s, 'activitypub', 'rawmsg', $act->raw, 1);
+ IConfig::Set($s, 'activitypub', 'rawmsg', $act->raw, 1);
}
if ($diaspora_rawmsg) {
- set_iconfig($s, 'diaspora', 'fields', $diaspora_rawmsg, 1);
+ IConfig::Set($s, 'diaspora', 'fields', $diaspora_rawmsg, 1);
}
if ($act->raw_recips) {
- set_iconfig($s, 'activitypub', 'recips', $act->raw_recips);
+ IConfig::Set($s, 'activitypub', 'recips', $act->raw_recips);
}
if ($act->objprop('type') === 'Event' && $act->objprop('timezone')) {
- set_iconfig($s, 'event', 'timezone', $act->objprop('timezone'), true);
+ IConfig::Set($s, 'event', 'timezone', $act->objprop('timezone'), true);
}
$hookinfo = [
@@ -2965,6 +2705,28 @@ class Activity {
intval($channel['channel_id'])
);
+ if (!$parent) {
+ if (perm_is_allowed($channel['channel_id'], $observer_hash, 'send_stream') || $is_sys_channel) {
+ if ($item['verb'] === 'Announce') {
+ $force = true;
+ }
+
+ if ($fetch_parents) {
+ // Cache::set($item['mid'], 'json:' . $act->raw);
+ App::$cache['fetch_objects'][$item['mid']]['channels'][] = $channel['channel_id'];
+ App::$cache['fetch_objects'][$item['mid']]['force'] = intval($force);
+
+ //Master::Summon(['Fetchparents', $channel['channel_id'], $observer_hash, $item['mid'], $force]);
+ //self::fetch_and_store_parents($channel, $observer_hash, $item, $act, $force);
+ return;
+ }
+ }
+
+ logger('no parent');
+ return;
+ }
+
+
// TODO: if we do not have a parent stop here and move the fetch to background?
if ($parent && $parent[0]['obj_type'] === 'Question') {
@@ -3005,7 +2767,7 @@ class Activity {
}*/
if (!$allowed) {
- if (get_pconfig($channel['channel_id'], 'system', 'moderate_unsolicited_comments') && $item['obj_type'] !== 'Answer') {
+ if (PConfig::Get($channel['channel_id'], 'system', 'moderate_unsolicited_comments') && $item['obj_type'] !== 'Answer') {
$item['item_blocked'] = ITEM_MODERATED;
$allowed = true;
}
@@ -3050,7 +2812,7 @@ class Activity {
if (tgroup_check($channel['channel_id'], $item) && (!$is_child_node)) {
// for forum deliveries, make sure we keep a copy of the signed original
- set_iconfig($item, 'activitypub', 'rawmsg', $act->raw, 1);
+ IConfig::Set($item, 'activitypub', 'rawmsg', $act->raw, 1);
$allowed = true;
}
@@ -3110,6 +2872,7 @@ class Activity {
$item['author_xchan'] = self::find_best_identity($item['author_xchan']);
$item['owner_xchan'] = self::find_best_identity($item['owner_xchan']);
+ $item['source_xchan'] = ((!empty($item['source_xchan'])) ? self::find_best_identity($item['source_xchan']) : '');
if (!$item['author_xchan']) {
logger('No author: ' . print_r($act, true));
@@ -3123,8 +2886,8 @@ class Activity {
return;
if ($channel['channel_system']) {
- $incl = get_config('system','pubstream_incl');
- $excl = get_config('system','pubstream_excl');
+ $incl = Config::Get('system','pubstream_incl');
+ $excl = Config::Get('system','pubstream_excl');
if(($incl || $excl) && !MessageFilter::evaluate($item, $incl, $excl)) {
logger('post is filtered');
@@ -3132,9 +2895,10 @@ class Activity {
}
}
- $abook = q("select * from abook where ( abook_xchan = '%s' OR abook_xchan = '%s') and abook_channel = %d ",
+ $abook = q("select * from abook where ( abook_xchan = '%s' OR abook_xchan = '%s' OR abook_xchan = '%s') and abook_channel = %d ",
dbesc($item['author_xchan']),
dbesc($item['owner_xchan']),
+ dbesc($item['source_xchan']),
intval($channel['channel_id'])
);
@@ -3146,48 +2910,21 @@ class Activity {
}
if (array_key_exists('conversation', $act->obj)) {
- set_iconfig($item, 'ostatus', 'conversation', $act->obj['conversation'], 1);
+ IConfig::Set($item, 'ostatus', 'conversation', $act->obj['conversation'], 1);
}
// This isn't perfect but the best we can do for now.
$item['comment_policy'] = ((isset($act->data['commentPolicy'])) ? $act->data['commentPolicy'] : 'authenticated');
- set_iconfig($item, 'activitypub', 'recips', $act->raw_recips);
+ IConfig::Set($item, 'activitypub', 'recips', $act->raw_recips);
if (intval($act->sigok)) {
$item['item_verified'] = 1;
}
if ($is_child_node) {
- if (!$parent) {
- if (!plugin_is_installed('pubcrawl')) {
- return;
- }
-
- $fetch = false;
-
- if (perm_is_allowed($channel['channel_id'], $observer_hash, 'send_stream') || $is_sys_channel) {
- if ($item['verb'] === 'Announce') {
- $force = true;
- }
- $fetch = (($fetch_parents) ? self::fetch_and_store_parents($channel, $observer_hash, $item, $force) : false);
- }
-
- if ($fetch) {
- $parent = q("select * from item where mid = '%s' and uid = %d",
- dbesc($item['parent_mid']),
- intval($item['uid'])
- );
- }
- }
-
- if (!$parent) {
- logger('no parent');
- return;
- }
-
- $item['owner_xchan'] = (($item['verb'] === 'Announce') ? $parent[0]['author_xchan'] : $parent[0]['owner_xchan']);
+ $item['owner_xchan'] = $parent[0]['owner_xchan'];
if ($parent[0]['parent_mid'] !== $item['parent_mid']) {
$item['thr_parent'] = $item['parent_mid'];
@@ -3306,20 +3043,55 @@ class Activity {
}
- static public function fetch_and_store_parents($channel, $observer_hash, $item, $force = false) {
+ /**
+ * @brief fetch a thread upwards by either providing a message id or an item/activity pair
+ *
+ * @param array $channel
+ * @param array $observer_hash
+ * @param array $item string|array
+ * @param object $act activitystreams object (optional) default null
+ * @param bool $force disregard permissions and force storage (optional) default false
+ * @return bool
+ */
+
+ static public function fetch_and_store_parents($channel, $observer_hash, $item, $act = null, $force = false) {
logger('fetching parents');
+ if (!$item) {
+ return false;
+ }
+
$p = [];
+ $announce_init = false;
- $announce_init = $item['verb'] === 'Announce';
+ if (is_object($act) && is_array($item)) {
+ $p[] = [$act, $item];
+ $announce_init = ($item['verb'] === 'Announce');
+ }
+
+ if (is_string($item)) {
+ $mid = $item;
+ $item = [
+ 'parent_mid' => $mid,
+ 'mid' => ''
+ ];
+ }
$current_item = $item;
- while ($current_item['parent_mid'] !== $current_item['mid']) {
- $n = self::fetch($current_item['parent_mid'], $channel);
+ $i = 0;
- if (!$n) {
- break;
+ while ($current_item['parent_mid'] !== $current_item['mid']) {
+ $cached = ASCache::Get($current_item['parent_mid']);
+ if ($cached) {
+ $n = unserialise($cached);
+ }
+ else {
+ $n = self::fetch($current_item['parent_mid'], $channel);
+ if (!$n) {
+ break;
+ }
+ ASCache::Set($current_item['parent_mid'], serialise($n));
}
$a = new ActivityStreams($n);
@@ -3357,6 +3129,19 @@ class Activity {
$item['item_fetched'] = true;
+ if ($announce_init) {
+ // Store the sender of the initial announce
+ $item['source_xchan'] = $observer_hash;
+ // WARNING: the presence of both source_xchan and non-zero item_uplink here will cause a delivery loop
+ $item['item_uplink'] = 0;
+ $item['verb'] = 'Announce';
+ $item['parent_mid'] = $item['thr_parent'] = $item['mid'];
+ $item['item_thread_top'] = 1;
+ }
+ else {
+ $announce_init = ($i === 0 && $item['verb'] === 'Announce');
+ }
+
if (intval($channel['channel_system']) && intval($item['item_private'])) {
$p = [];
break;
@@ -3367,27 +3152,18 @@ class Activity {
break;
}
- if ($announce_init) {
- // If the fetch was initiated by an announce activity
- // do not set item fetched. This way the owner will be set to the
- // observer -> the announce actor
- unset($item['item_fetched']);
- $item['verb'] = 'Announce';
- $item['parent_mid'] = $item['mid'];
- $item['item_thread_top'] = 1;
- }
-
array_unshift($p, [$a, $item]);
- if ($announce_init || $item['parent_mid'] === $item['mid']) {
+ if ($item['parent_mid'] === $item['mid']) {
break;
}
+
}
$current_item = $item;
+ $i++;
}
-
if ($p) {
foreach ($p as $pv) {
if ($pv[0]->is_valid()) {
@@ -3396,407 +3172,9 @@ class Activity {
}
return true;
}
-
return false;
}
- /*
- static public function fetch_and_store_parents($channel, $item) {
-
- logger('fetching parents');
-
- $p = [];
-
- $current_item = $item;
-
- while ($current_item['parent_mid'] !== $current_item['mid']) {
- $n = self::fetch($current_item['parent_mid'], $channel);
- if (!$n) {
- break;
- }
- $a = new ActivityStreams($n);
-
- //logger($a->debug());
-
- if (!$a->is_valid()) {
- break;
- }
-
- if (is_array($a->actor) && array_key_exists('id', $a->actor)) {
- self::actor_store($a->actor);
- }
-
- $replies = null;
- if (isset($a->obj['replies']['first']['items'])) {
- $replies = $a->obj['replies']['first']['items'];
- // we already have this one
- array_diff($replies, [$current_item['mid']]);
- }
-
- $item = null;
-
- switch ($a->type) {
- case 'Create':
- case 'Update':
- //case 'Like':
- //case 'Dislike':
- case 'Announce':
- $item = self::decode_note($a);
- break;
- default:
- break;
-
- }
-
- $hookinfo = [
- 'a' => $a,
- 'item' => $item
- ];
-
- call_hooks('fetch_and_store', $hookinfo);
-
- $item = $hookinfo['item'];
-
- if ($item) {
-
- array_unshift($p, [$a, $item, $replies]);
-
- if ($item['parent_mid'] === $item['mid'] || count($p) > 20) {
- break;
- }
-
- }
- $current_item = $item;
- }
-
- if ($p) {
- foreach ($p as $pv) {
- self::store($channel, $pv[0]->actor['id'], $pv[0], $pv[1], false);
- if ($pv[2])
- self::fetch_and_store_replies($channel, $pv[2]);
- }
- return true;
- }
-
- return false;
- }
-
- static public function fetch_and_store_replies($channel, $arr) {
-
- logger('fetching replies');
- logger(print_r($arr, true));
-
- $p = [];
-
- foreach ($arr as $url) {
-
- $n = self::fetch($url, $channel);
- if (!$n) {
- break;
- }
-
- $a = new ActivityStreams($n);
-
- if (!$a->is_valid()) {
- break;
- }
-
- $item = null;
-
- switch ($a->type) {
- case 'Create':
- case 'Update':
- case 'Like':
- case 'Dislike':
- case 'Announce':
- $item = self::decode_note($a);
- break;
- default:
- break;
- }
-
- $hookinfo = [
- 'a' => $a,
- 'item' => $item
- ];
-
- call_hooks('fetch_and_store', $hookinfo);
-
- $item = $hookinfo['item'];
-
- if ($item) {
- array_unshift($p, [$a, $item]);
- }
-
- }
-
- if ($p) {
- foreach ($p as $pv) {
- self::store($channel, $pv[0]->actor['id'], $pv[0], $pv[1], false);
- }
- }
-
- }
-*/
-
-/* this is deprecated and not used anymore
- static function announce_note($channel, $observer_hash, $act) {
-
- $s = [];
- $is_sys_channel = is_sys_channel($channel['channel_id']);
-
- if (!perm_is_allowed($channel['channel_id'], $observer_hash, 'send_stream') && !$is_sys_channel) {
- logger('no permission');
- return;
- }
-
- $content = self::get_content($act->obj);
-
- if (!$content) {
- logger('no content');
- return;
- }
-
- $s['owner_xchan'] = $s['author_xchan'] = $observer_hash;
-
- $s['aid'] = $channel['channel_account_id'];
- $s['uid'] = $channel['channel_id'];
- $s['mid'] = urldecode($act->obj['id']);
- $s['plink'] = urldecode($act->obj['id']);
-
- if (!$s['created'])
- $s['created'] = datetime_convert();
-
- if (!$s['edited'])
- $s['edited'] = $s['created'];
-
-
- $s['parent_mid'] = $s['mid'];
-
- $s['verb'] = ACTIVITY_POST;
- $s['obj_type'] = ACTIVITY_OBJ_NOTE;
- $s['app'] = t('ActivityPub');
-
- if ($channel['channel_system']) {
- if (!MessageFilter::evaluate($s, get_config('system', 'pubstream_incl'), get_config('system', 'pubstream_excl'))) {
- logger('post is filtered');
- return;
- }
- }
-
- $abook = q("select * from abook where abook_xchan = '%s' and abook_channel = %d limit 1",
- dbesc($observer_hash),
- intval($channel['channel_id'])
- );
-
- if ($abook) {
- if (!post_is_importable($s, $abook[0])) {
- logger('post is filtered');
- return;
- }
- }
-
- if ($act->obj['conversation']) {
- set_iconfig($s, 'ostatus', 'conversation', $act->obj['conversation'], 1);
- }
-
- $a = self::decode_taxonomy($act->obj);
- if ($a) {
- $s['term'] = $a;
- }
-
- $a = self::decode_attachment($act->obj);
- if ($a) {
- $s['attach'] = $a;
- }
-
- $body = "[share author='" . urlencode($act->sharee['name']) .
- "' profile='" . $act->sharee['url'] .
- "' avatar='" . $act->sharee['photo_s'] .
- "' link='" . ((is_array($act->obj['url'])) ? $act->obj['url']['href'] : $act->obj['url']) .
- "' auth='" . ((is_matrix_url($act->obj['url'])) ? 'true' : 'false') .
- "' posted='" . $act->obj['published'] .
- "' message_id='" . $act->obj['id'] .
- "']";
-
- if ($content['name'])
- $body .= self::bb_content($content, 'name') . "\r\n";
-
- $body .= self::bb_content($content, 'content');
-
- if ($act->obj['type'] === 'Note' && $s['attach']) {
- $body .= self::bb_attach($s['attach'], $body);
- }
-
- $body .= "[/share]";
-
- $s['title'] = self::bb_content($content, 'name');
- $s['body'] = $body;
-
- if ($act->recips && (!in_array(ACTIVITY_PUBLIC_INBOX, $act->recips)))
- $s['item_private'] = 1;
-
- set_iconfig($s, 'activitypub', 'recips', $act->raw_recips);
-
- $r = q("select created, edited from item where mid = '%s' and uid = %d limit 1",
- dbesc($s['mid']),
- intval($s['uid'])
- );
- if ($r) {
- if ($s['edited'] > $r[0]['edited']) {
- $x = item_store_update($s);
- }
- else {
- return;
- }
- }
- else {
- $x = item_store($s);
- }
-
- if (is_array($x) && $x['item_id']) {
- if ($s['owner_xchan'] === $channel['channel_hash']) {
- // We are the owner of this conversation, so send all received comments back downstream
- Master::Summon(['Notifier', 'comment-import', $x['item_id']]);
- }
- $r = q("select * from item where id = %d limit 1",
- intval($x['item_id'])
- );
- if ($r) {
- send_status_notifications($x['item_id'], $r[0]);
- }
-
- sync_an_item($channel['channel_id'], $x['item_id']);
- }
-
- }
-*/
-
- static function like_note($channel, $observer_hash, $act) {
-
- $s = [];
-
- $parent = $act->obj['id'];
-
- if ($act->type === 'Like')
- $s['verb'] = ACTIVITY_LIKE;
- if ($act->type === 'Dislike')
- $s['verb'] = ACTIVITY_DISLIKE;
-
- if (!$parent)
- return;
-
- $r = q("select * from item where uid = %d and ( mid = '%s' or mid = '%s' ) limit 1",
- intval($channel['channel_id']),
- dbesc($parent),
- dbesc(urldecode(basename($parent)))
- );
-
- if (!$r) {
- logger('parent not found.');
- return;
- }
-
- xchan_query($r);
- $parent_item = $r[0];
-
- if ($parent_item['owner_xchan'] === $channel['channel_hash']) {
- if (!perm_is_allowed($channel['channel_id'], $observer_hash, 'post_comments')) {
- logger('no comment permission.');
- return;
- }
- }
-
- if ($parent_item['mid'] === $parent_item['parent_mid']) {
- $s['parent_mid'] = $parent_item['mid'];
- }
- else {
- $s['thr_parent'] = $parent_item['mid'];
- $s['parent_mid'] = $parent_item['parent_mid'];
- }
-
- $s['owner_xchan'] = $parent_item['owner_xchan'];
- $s['author_xchan'] = $observer_hash;
-
- $s['aid'] = $channel['channel_account_id'];
- $s['uid'] = $channel['channel_id'];
- $s['mid'] = $act->id;
-
- if (!$s['parent_mid'])
- $s['parent_mid'] = $s['mid'];
-
-
- $post_type = (($parent_item['resource_type'] === 'photo') ? t('photo') : t('post'));
-
- $links = [['rel' => 'alternate', 'type' => 'text/html', 'href' => $parent_item['plink']]];
- $objtype = (($parent_item['resource_type'] === 'photo') ? ACTIVITY_OBJ_PHOTO : ACTIVITY_OBJ_NOTE);
-
- $z = q("select * from xchan where xchan_hash = '%s' limit 1",
- dbesc($parent_item['author_xchan'])
- );
- if ($z)
- $item_author = $z[0];
-
- $object = json_encode([
- 'type' => $post_type,
- 'id' => $parent_item['mid'],
- 'parent' => (($parent_item['thr_parent']) ? $parent_item['thr_parent'] : $parent_item['parent_mid']),
- 'link' => $links,
- 'title' => $parent_item['title'],
- 'content' => $parent_item['body'],
- 'created' => $parent_item['created'],
- 'edited' => $parent_item['edited'],
- 'author' => [
- 'name' => $item_author['xchan_name'],
- 'address' => $item_author['xchan_addr'],
- 'guid' => $item_author['xchan_guid'],
- 'guid_sig' => $item_author['xchan_guid_sig'],
- 'link' => [
- ['rel' => 'alternate', 'type' => 'text/html', 'href' => $item_author['xchan_url']],
- ['rel' => 'photo', 'type' => $item_author['xchan_photo_mimetype'], 'href' => $item_author['xchan_photo_m']]],
- ],
- ], JSON_UNESCAPED_SLASHES
- );
-
- if ($act->type === 'Like')
- $bodyverb = t('%1$s likes %2$s\'s %3$s');
- if ($act->type === 'Dislike')
- $bodyverb = t('%1$s doesn\'t like %2$s\'s %3$s');
-
- $ulink = '[url=' . $item_author['xchan_url'] . ']' . $item_author['xchan_name'] . '[/url]';
- $alink = '[url=' . $parent_item['author']['xchan_url'] . ']' . $parent_item['author']['xchan_name'] . '[/url]';
- $plink = '[url=' . z_root() . '/display/' . urlencode($act->id) . ']' . $post_type . '[/url]';
- $s['body'] = sprintf($bodyverb, $ulink, $alink, $plink);
-
- $s['app'] = t('ActivityPub');
-
- // set the route to that of the parent so downstream hubs won't reject it.
-
- $s['route'] = $parent_item['route'];
- $s['item_private'] = $parent_item['item_private'];
- $s['obj_type'] = $objtype;
- $s['obj'] = $object;
-
- if ($act->obj['conversation']) {
- set_iconfig($s, 'ostatus', 'conversation', $act->obj['conversation'], 1);
- }
-
- if ($act->recips && (!in_array(ACTIVITY_PUBLIC_INBOX, $act->recips)))
- $s['item_private'] = 1;
-
- set_iconfig($s, 'activitypub', 'recips', $act->raw_recips);
-
- $result = item_store($s);
-
- if ($result['success']) {
- // if the message isn't already being relayed, notify others
- if (intval($parent_item['item_origin']))
- Master::Summon(['Notifier', 'comment-import', $result['item_id']]);
- sync_an_item($channel['channel_id'], $result['item_id']);
- }
-
- return;
- }
public static function bb_attach($item) {
diff --git a/Zotlabs/Lib/ActivityStreams.php b/Zotlabs/Lib/ActivityStreams.php
index 0770f2040..3749126d3 100644
--- a/Zotlabs/Lib/ActivityStreams.php
+++ b/Zotlabs/Lib/ActivityStreams.php
@@ -90,6 +90,15 @@ class ActivityStreams {
// Attempt to assemble an Activity from what we were given.
if ($this->is_valid()) {
$this->id = $this->get_property_obj('id');
+
+ if (!$this->id) {
+ logger('Data with mmissing id: ' . print_r($this->data, true));
+ return;
+ }
+
+ // cache for future use
+ ASCache::Set($this->id, 'json:' . $this->raw);
+
$this->type = $this->get_primary_type();
$this->actor = $this->get_actor('actor', '', '');
$this->obj = $this->get_compound_property('object');
@@ -394,12 +403,22 @@ class ActivityStreams {
$x = $this->get_property_obj($property, $base, $namespace);
if ($this->is_url($x)) {
- $y = $this->fetch_property($x);
+ $cached = ASCache::Get($x);
+ if ($cached) {
+ $y = unserialise($cached);
+ }
+ else {
+ $y = $this->fetch_property($x);
+ if ($y) {
+ ASCache::Set($x, serialise($y));
+ }
+ }
if (is_array($y)) {
$x = $y;
}
}
+
// verify and unpack JSalmon signature if present
if (is_array($x) && array_key_exists('signed', $x)) {
diff --git a/Zotlabs/Lib/Cache.php b/Zotlabs/Lib/Cache.php
index 60bf64611..f3f520496 100644
--- a/Zotlabs/Lib/Cache.php
+++ b/Zotlabs/Lib/Cache.php
@@ -17,8 +17,8 @@ class Cache {
*/
public static function get($key, $age = '') {
-
- $hash = hash('whirlpool',$key);
+// $hash = hash('whirlpool',$key);
+ $hash = uuid_from_url($key);
$r = q("SELECT v FROM cache WHERE k = '%s' AND updated > %s - INTERVAL %s LIMIT 1",
dbesc($hash),
@@ -32,23 +32,25 @@ class Cache {
}
public static function set($key,$value) {
+// $hash = hash('whirlpool',$key);
+ $hash = uuid_from_url($key);
- $hash = hash('whirlpool',$key);
-
- $r = q("SELECT * FROM cache WHERE k = '%s' limit 1",
+ $r = q("SELECT * FROM cache WHERE k = '%s' LIMIT 1",
dbesc($hash)
);
if($r) {
q("UPDATE cache SET v = '%s', updated = '%s' WHERE k = '%s'",
dbesc($value),
dbesc(datetime_convert()),
- dbesc($hash));
+ dbesc($hash)
+ );
}
else {
q("INSERT INTO cache (k, v, updated) VALUES ('%s', '%s', '%s')",
dbesc($hash),
dbesc($value),
- dbesc(datetime_convert()));
+ dbesc(datetime_convert())
+ );
}
}
}
diff --git a/Zotlabs/Lib/Libzot.php b/Zotlabs/Lib/Libzot.php
index be1ca15c0..4a327e24f 100644
--- a/Zotlabs/Lib/Libzot.php
+++ b/Zotlabs/Lib/Libzot.php
@@ -1157,7 +1157,6 @@ class Libzot {
else {
$item = [];
}
-
logger($AS->debug(), LOGGER_DATA);
}
@@ -1364,11 +1363,13 @@ class Libzot {
static function find_parent_owner_hashes($env, $act) {
$r = [];
- $thread_parent = self::find_parent($env, $act);
- if ($thread_parent) {
- $uids = q("SELECT uid FROM item WHERE thr_parent = '%s' OR parent_mid = '%s'",
- dbesc($thread_parent),
- dbesc($thread_parent)
+ $parent = self::find_parent($env, $act);
+
+ if ($parent) {
+ $uids = q("SELECT uid FROM item WHERE thr_parent = '%s' OR parent_mid = '%s' OR mid = '%s'",
+ dbesc($parent),
+ dbesc($parent),
+ dbesc($parent)
);
if ($uids) {
@@ -1579,6 +1580,39 @@ class Libzot {
continue;
}
+ $arr['item_wall'] = 0;
+
+ // This is our own post, possibly coming from a channel clone
+ if ($arr['owner_xchan'] === $d) {
+ $arr['item_wall'] = 1;
+ }
+
+ if (isset($arr['item_deleted']) && $arr['item_deleted']) {
+
+ // remove_community_tag is a no-op if this isn't a community tag activity
+ // self::remove_community_tag($sender, $arr, $channel['channel_id']);
+
+ // set these just in case we need to store a fresh copy of the deleted post.
+ // This could happen if the delete got here before the original post did.
+
+ $arr['aid'] = $channel['channel_account_id'];
+ $arr['uid'] = $channel['channel_id'];
+
+ $item_id = self::delete_imported_item($sender, $act, $arr, $channel['channel_id'], $relay);
+ $DR->update(($item_id) ? 'deleted' : 'delete_failed');
+ $result[] = $DR->get();
+
+ if ($relay && $item_id) {
+ logger('process_delivery: invoking relay');
+ Master::Summon(['Notifier', 'relay', intval($item_id), 'delete']);
+ $DR->update('relayed');
+ $result[] = $DR->get();
+ }
+
+ continue;
+ }
+
+
// allow public postings to the sys channel regardless of permissions, but not
// for comments travelling upstream. Wait and catch them on the way down.
// They may have been blocked by the owner.
@@ -1605,119 +1639,23 @@ class Libzot {
}
$tag_delivery = tgroup_check($channel['channel_id'], $arr);
- $perm = 'send_stream';
- if (($arr['mid'] !== $arr['parent_mid']) && ($relay))
- $perm = 'post_comments';
-
- // This is our own post, possibly coming from a channel clone
-
- if ($arr['owner_xchan'] == $d) {
- $arr['item_wall'] = 1;
- }
- else {
- $arr['item_wall'] = 0;
- }
-
- $friendofriend = false;
-
- if ((!$tag_delivery) && (!$local_public)) {
- $allowed = (perm_is_allowed($channel['channel_id'], $sender, $perm));
-
- $permit_mentions = intval(PConfig::Get($channel['channel_id'], 'system', 'permit_all_mentions') && i_am_mentioned($channel, $arr));
- if (!$allowed) {
- if ($perm === 'post_comments') {
- $parent = q("select * from item where mid = '%s' and uid = %d limit 1",
- dbesc($arr['parent_mid']),
- intval($channel['channel_id'])
- );
- if ($parent) {
- $allowed = can_comment_on_post($sender, $parent[0]);
- if (!$allowed && $permit_mentions) {
- $allowed = true;
- }
-
- if (!$allowed) {
- if (PConfig::Get($channel['channel_id'], 'system', 'moderate_unsolicited_comments') && $arr['obj_type'] !== 'Answer') {
- $arr['item_blocked'] = ITEM_MODERATED;
- $allowed = true;
- }
- }
- }
-
- }
- elseif ($permit_mentions) {
- $allowed = true;
- }
-
-
- }
-
- if ($request) {
- // Conversation fetches (e.g. $request == true) take place for
- // a) new comments on expired posts
- // b) hyperdrive (friend-of-friend) conversations
- // c) Repeats of posts by others
-
-
- // over-ride normal connection permissions for hyperdrive (friend-of-friend) conversations
- // (if hyperdrive is enabled) and repeated posts by a friend.
- // If $allowed is already true, this is probably the conversation of a direct friend or a
- // conversation fetch for a new comment on an expired post
- // Comments of all these activities are allowed and will only be rejected (later) if the parent
- // doesn't exist.
-
- if ($perm === 'send_stream') {
- if ($force || get_pconfig($channel['channel_id'], 'system', 'hyperdrive', false)) {
- $allowed = true;
- }
- }
- else {
- $allowed = true;
- }
-
- $friendofriend = true;
- }
-
- if (intval($arr['item_private']) === 2) {
- if (!perm_is_allowed($channel['channel_id'], $sender, 'post_mail')) {
- $allowed = false;
- }
- }
-
- if (!$allowed) {
- logger("permission denied for delivery to channel {$channel['channel_id']} {$channel['channel_address']}");
- $DR->update('permission denied');
- $result[] = $DR->get();
- continue;
- }
- }
-
- // logger('item: ' . print_r($arr,true), LOGGER_DATA);
+ $perm = 'send_stream';
if ($arr['mid'] !== $arr['parent_mid']) {
- logger('checking source: "' . $arr['mid'] . '" != "' . $arr['parent_mid'] . '"');
+ if ($relay)
+ $perm = 'post_comments';
- // check source route.
- // We are only going to accept comments from this sender if the comment has the same route as the top-level-post,
- // this is so that permissions mismatches between senders apply to the entire conversation
- // As a side effect we will also do a preliminary check that we have the top-level-post, otherwise
- // processing it is pointless.
-
- $r = q("select route, id, parent_mid, mid, owner_xchan, item_private, obj_type from item where mid = '%s' and uid = %d limit 1",
+ $parent = q("select * from item where mid = '%s' and uid = %d limit 1",
dbesc($arr['parent_mid']),
intval($channel['channel_id'])
);
- if (!$r) {
+ if (!$parent) {
$DR->update('comment parent not found');
$result[] = $DR->get();
- if ($relay || $request || $local_public) {
- continue;
- }
-
// We don't seem to have a copy of this conversation or at least the parent
// - so request a copy of the entire conversation to date.
// Don't do this if it's a relay post as we're the ones who are supposed to
@@ -1729,28 +1667,38 @@ class Libzot {
// the top level post is unlikely to be imported and
// this is just an exercise in futility.
+ if ($relay || $request || $local_public || !perm_is_allowed($channel['channel_id'], $sender, 'send_stream')) {
+ continue;
+ }
+
if ($arr['verb'] === 'Announce') {
- Activity::fetch_and_store_parents($channel, $sender, $arr, true);
+ Master::Summon(['Fetchparents', $channel['channel_id'], $sender, $arr['mid'], true]);
}
else {
- if (perm_is_allowed($channel['channel_id'], $sender, 'send_stream')) {
- Master::Summon(['Zotconvo', $channel['channel_id'], $arr['parent_mid']]);
- }
- continue;
+ Master::Summon(['Zotconvo', $channel['channel_id'], $arr['parent_mid']]);
}
+
+ continue;
}
- if ($r[0]['obj_type'] === 'Question') {
+ logger('checking source: "' . $arr['mid'] . '" != "' . $arr['parent_mid'] . '"');
+
+ // check source route.
+ // We are only going to accept comments from this sender if the comment has the same route as the top-level-post,
+ // this is so that permissions mismatches between senders apply to the entire conversation
+ // As a side effect we will also do a preliminary check that we have the top-level-post, otherwise
+ // processing it is pointless.
+
+ if ($parent[0]['obj_type'] === 'Question') {
// route checking doesn't work correctly here because we've changed the privacy
- $r[0]['route'] = EMPTY_STR;
+ $parent[0]['route'] = EMPTY_STR;
// If this is a poll response, convert the obj_type to our (internal-only) "Answer" type
if ($arr['obj_type'] === ACTIVITY_OBJ_COMMENT && $arr['title'] && (!$arr['body'])) {
$arr['obj_type'] = 'Answer';
}
}
-
- if ($relay || $friendofriend || (intval($r[0]['item_private']) === 0 && intval($arr['item_private']) === 0)) {
+ if ($relay || (intval($parent[0]['item_private']) === 0 && intval($arr['item_private']) === 0)) {
// reset the route in case it travelled a great distance upstream
// use our parent's route so when we go back downstream we'll match
// with whatever route our parent has.
@@ -1758,10 +1706,8 @@ class Libzot {
// but we are now getting comments via listener delivery
// and if there is no privacy on this or the parent, we don't care about the route,
// so just set the owner and route accordingly.
- $arr['route'] = $r[0]['route'];
- $arr['owner_xchan'] = $r[0]['owner_xchan'];
-
-
+ $arr['route'] = $parent[0]['route'];
+ $arr['owner_xchan'] = $parent[0]['owner_xchan'];
}
else {
@@ -1771,7 +1717,7 @@ class Libzot {
// only compare the last hop since it could have arrived at the last location any number of ways.
// Always accept empty routes and firehose items (route contains 'undefined') .
- $existing_route = explode(',', $r[0]['route']);
+ $existing_route = explode(',', $parent[0]['route']);
$routes = count($existing_route);
if ($routes) {
$last_hop = array_pop($existing_route);
@@ -1788,8 +1734,8 @@ class Libzot {
$current_route = ((isset($arr['route']) && $arr['route']) ? $arr['route'] . ',' : '') . $sender;
if ($last_hop && $last_hop != $sender) {
- logger('comment route mismatch: parent route = ' . $r[0]['route'] . ' expected = ' . $current_route, LOGGER_DEBUG);
- logger('comment route mismatch: parent msg = ' . $r[0]['id'], LOGGER_DEBUG);
+ logger('comment route mismatch: parent route = ' . $parent[0]['route'] . ' expected = ' . $current_route, LOGGER_DEBUG);
+ logger('comment route mismatch: parent msg = ' . $parent[0]['id'], LOGGER_DEBUG);
$DR->update('comment route mismatch');
$result[] = $DR->get();
continue;
@@ -1802,42 +1748,86 @@ class Libzot {
}
}
- // This is used to fetch allow/deny rules if either the sender
- // or owner is a connection. post_is_importable() evaluates all of them
- $abook = q("select * from abook where abook_channel = %d and ( abook_xchan = '%s' OR abook_xchan = '%s' )",
- intval($channel['channel_id']),
- dbesc($arr['owner_xchan']),
- dbesc($arr['author_xchan'])
- );
+ if (!$tag_delivery && !$local_public) {
+ $allowed = (perm_is_allowed($channel['channel_id'], $sender, $perm));
- if (isset($arr['item_deleted']) && $arr['item_deleted']) {
+ $permit_mentions = intval(PConfig::Get($channel['channel_id'], 'system', 'permit_all_mentions') && i_am_mentioned($channel, $arr));
- // remove_community_tag is a no-op if this isn't a community tag activity
- self::remove_community_tag($sender, $arr, $channel['channel_id']);
+ if (!$allowed) {
+ if ($parent && $perm === 'send_stream') {
+ // if we own the parent we will accept its comments
+ $allowed = true;
+ }
- // set these just in case we need to store a fresh copy of the deleted post.
- // This could happen if the delete got here before the original post did.
+ elseif ($parent && $perm === 'post_comments') {
+ $allowed = can_comment_on_post($sender, $parent[0]);
- $arr['aid'] = $channel['channel_account_id'];
- $arr['uid'] = $channel['channel_id'];
+ if (!$allowed && $permit_mentions) {
+ $allowed = true;
+ }
+
+ if (!$allowed) {
+ if (PConfig::Get($channel['channel_id'], 'system', 'moderate_unsolicited_comments') && $arr['obj_type'] !== 'Answer') {
+ $arr['item_blocked'] = ITEM_MODERATED;
+ $allowed = true;
+ }
+ }
+
+ }
+ elseif ($permit_mentions) {
+ $allowed = true;
+ }
- $item_id = self::delete_imported_item($sender, $act, $arr, $channel['channel_id'], $relay);
- $DR->update(($item_id) ? 'deleted' : 'delete_failed');
- $result[] = $DR->get();
- if ($relay && $item_id) {
- logger('process_delivery: invoking relay');
- Master::Summon(['Notifier', 'relay', intval($item_id)]);
- $DR->update('relayed');
- $result[] = $DR->get();
}
- continue;
+ if ($request) {
+ // Conversation fetches (e.g. $request == true) take place for
+ // a) new comments on expired posts
+ // b) if we received an announce
+
+
+ // over-ride normal connection permissions for hyperdrive (friend-of-friend) conversations
+ // (if hyperdrive is enabled) and repeated posts by a friend.
+ // If $allowed is already true, this is probably the conversation of a direct friend or a
+ // conversation fetch for a new comment on an expired post
+ // Comments of all these activities are allowed and will only be rejected (later) if the parent
+ // doesn't exist.
+
+ if ($perm === 'send_stream') {
+ if ($force) {
+ $allowed = true;
+ }
+ }
+ else {
+ $allowed = true;
+ }
+ }
+
+ if (intval($arr['item_private']) === 2) {
+ if (!perm_is_allowed($channel['channel_id'], $sender, 'post_mail')) {
+ $allowed = false;
+ }
+ }
+
+ if (!$allowed) {
+ logger("permission denied for delivery to channel {$channel['channel_id']} {$channel['channel_address']}");
+ $DR->update('permission denied');
+ $result[] = $DR->get();
+ continue;
+ }
}
+ // This is used to fetch allow/deny rules if either the sender
+ // or owner is a connection. post_is_importable() evaluates all of them
+ $abook = q("select * from abook where abook_channel = %d and ( abook_xchan = '%s' OR abook_xchan = '%s' )",
+ intval($channel['channel_id']),
+ dbesc($arr['owner_xchan']),
+ dbesc($arr['author_xchan'])
+ );
+
// reactions such as like and dislike could have an mid with /activity/ in it.
// Check for both forms in order to prevent duplicates.
-
$r = q("select * from item where mid in ('%s','%s') and uid = %d limit 1",
dbesc($arr['mid']),
dbesc(str_replace(z_root() . '/activity/', z_root() . '/item/', $arr['mid'])),
@@ -1845,7 +1835,6 @@ class Libzot {
);
if ($r) {
-
$item_id = $r[0]['id'];
if (intval($r[0]['item_deleted'])) {
@@ -1915,7 +1904,6 @@ class Libzot {
if (post_is_importable($arr['uid'], $arr, $abook)) {
$item_result = item_store($arr);
if ($item_result['success']) {
-
$item_id = $item_result['item_id'];
if ($item_source && in_array($item_result['item']['obj_type'], ['Event', ACTIVITY_OBJ_EVENT])) {
@@ -1957,6 +1945,7 @@ class Libzot {
// preserve conversations with which you are involved from expiration
$stored = ((isset($item_result['item'])) ? $item_result['item'] : false);
+
if ((is_array($stored)) && ($stored['id'] != $stored['parent'])
&& ($stored['author_xchan'] === $channel['channel_hash'])) {
retain_item($stored['item']['parent']);
@@ -2320,12 +2309,20 @@ class Libzot {
// this information from the metadata should have no other discernible impact.
if (($stored['id'] != $stored['parent']) && intval($stored['item_origin'])) {
- q("update item set item_origin = 0 where id = %d and uid = %d",
- intval($stored['id']),
- intval($stored['uid'])
+ q("update item set item_origin = 0 where id = %d",
+ intval($stored['id'])
);
}
- }
+ } else {
+ if ($stored['id'] !== $stored['parent']) {
+ q(
+ "update item set commented = '%s', changed = '%s' where id = %d",
+ dbesc(datetime_convert()),
+ dbesc(datetime_convert()),
+ intval($stored['parent'])
+ );
+ }
+ }
// Use phased deletion to set the deleted flag, call both tag_deliver and the notifier to notify downstream channels
diff --git a/Zotlabs/Lib/ThreadItem.php b/Zotlabs/Lib/ThreadItem.php
index e7d6e33f8..77f26c386 100644
--- a/Zotlabs/Lib/ThreadItem.php
+++ b/Zotlabs/Lib/ThreadItem.php
@@ -907,6 +907,12 @@ class ThreadItem {
$this->owner_name = $this->data['owner']['xchan_name'];
$this->wall_to_wall = true;
}
+ elseif($this->is_toplevel() && $this->get_data_value('verb') === 'Announce' && isset($this->data['source'])) {
+ $this->owner_url = chanlink_hash($this->data['source']['xchan_hash']);
+ $this->owner_photo = $this->data['source']['xchan_photo_s'];
+ $this->owner_name = $this->data['source']['xchan_name'];
+ $this->wall_to_wall = true;
+ }
}
private function is_wall_to_wall() {
diff --git a/Zotlabs/Module/Share.php b/Zotlabs/Module/Share.php
index 248126f7f..ea9313fa8 100644
--- a/Zotlabs/Module/Share.php
+++ b/Zotlabs/Module/Share.php
@@ -65,8 +65,6 @@ class Share extends \Zotlabs\Web\Controller {
$item = $r[0];
- $owner_uid = $r[0]['uid'];
- $owner_aid = $r[0]['aid'];
/*
$can_comment = false;
if((array_key_exists('owner',$item)) && intval($item['owner']['abook_self']))
@@ -96,25 +94,39 @@ class Share extends \Zotlabs\Web\Controller {
else
killme();
-
- $arr['aid'] = $owner_aid;
- $arr['uid'] = $owner_uid;
+ $arr['aid'] = $item['aid'];
+ $arr['uid'] = $item['uid'];
$arr['item_origin'] = 1;
$arr['item_wall'] = $item['item_wall'];
+ $arr['item_private'] = $item['item_private'];
$arr['uuid'] = item_message_id();
$arr['mid'] = z_root() . '/activity/' . $arr['uuid'];
- $arr['parent_mid'] = $item['mid'];
+ $arr['parent_mid'] = $item['parent_mid'];
+ $arr['thr_parent'] = $item['mid'];
+
+ $created = datetime_convert();
+
+ $arr['created'] = $created;
+ $arr['edited'] = $created;
+ $arr['commented'] = $created;
+ $arr['received'] = $created;
+ $arr['changed'] = $created;
+ $arr['item_type'] = ITEM_TYPE_POST;
$mention = '@[zrl=' . $item['author']['xchan_url'] . ']' . $item['author']['xchan_name'] . '[/zrl]';
$arr['body'] = sprintf( t('&#x1f501; Repeated %1$s\'s %2$s'), $mention, Activity::activity_obj_mapper($item['obj_type']));
$arr['author_xchan'] = $channel['channel_hash'];
$arr['owner_xchan'] = $item['author_xchan'];
+ $arr['source_xchan'] = '';
+
$arr['obj'] = $item['obj'];
$arr['obj_type'] = $item['obj_type'];
$arr['verb'] = ACTIVITY_SHARE;
+ call_hooks('post_local', $arr);
+
$post = item_store($arr);
$post_id = $post['item_id'];
diff --git a/Zotlabs/Module/Sse_bs.php b/Zotlabs/Module/Sse_bs.php
index 78adf1859..2e9904cd3 100644
--- a/Zotlabs/Module/Sse_bs.php
+++ b/Zotlabs/Module/Sse_bs.php
@@ -121,12 +121,7 @@ class Sse_bs extends Controller {
$mids = [];
$str = '';
- $mids_all_json = Cache::get('sse_mids_all_' . session_id());
-
- if (!$mids_all_json)
- $mids_all_json = '[]';
-
- $mids_all = json_decode($mids_all_json, true);
+ $mids_all = unserialise(Cache::get('sse_mids_all_' . session_id()));
foreach($arr as $a) {
$mid_str = '\'' . dbesc(unpack_link_id($a)) . '\'';
@@ -137,7 +132,7 @@ class Sse_bs extends Controller {
}
}
- Cache::set('sse_mids_all_' . session_id(), json_encode($mids_all));
+ Cache::set('sse_mids_all_' . session_id(), serialise($mids_all));
if(! self::$uid) {
return;
@@ -448,9 +443,8 @@ class Sse_bs extends Controller {
$sql_extra2 = " AND CASE WHEN verb = '" . ACTIVITY_SHARE . "' THEN owner_xchan ELSE author_xchan END IN (" . self::$xchans . ") ";
$sql_extra3 = '';
- $sse_mids_all_json = Cache::get('sse_mids_all_' . session_id());
- if ($sse_mids_all_json) {
- $sse_mids_all = json_decode($sse_mids_all_json, true);
+ $sse_mids_all = unserialise(Cache::get('sse_mids_all_' . session_id()));
+ if ($sse_mids_all) {
$sql_extra3 = " AND mid NOT IN (" . protect_sprintf(implode(',', $sse_mids_all)) . ") ";
}
diff --git a/Zotlabs/Widget/Messages.php b/Zotlabs/Widget/Messages.php
index cdd889121..b56fc86f9 100644
--- a/Zotlabs/Widget/Messages.php
+++ b/Zotlabs/Widget/Messages.php
@@ -147,6 +147,9 @@ class Messages {
if($item['owner_xchan'] !== $item['author_xchan']) {
$info .= t('via') . ' ' . $item['owner']['xchan_name'];
}
+ elseif($item['verb'] === 'Announce' && isset($item['source'])) {
+ $info .= t('via') . ' ' . $item['source']['xchan_name'];
+ }
$summary = $item['title'];
if (!$summary) {
diff --git a/boot.php b/boot.php
index 2ccdd12d0..7bd68be5f 100644
--- a/boot.php
+++ b/boot.php
@@ -62,7 +62,7 @@ require_once('include/conversation.php');
require_once('include/acl_selectors.php');
define('PLATFORM_NAME', 'hubzilla');
-define('STD_VERSION', '8.9.3');
+define('STD_VERSION', '8.9.4');
define('ZOT_REVISION', '6.0');
define('DB_UPDATE_VERSION', 1261);
@@ -804,6 +804,7 @@ class App {
public static $is_sys = false;
public static $nav_sel;
public static $comanche;
+ public static $cache = []; // general purpose cache
public static $channel_links;
diff --git a/doc/admin/administrator_guide.md b/doc/admin/administrator_guide.md
index 0cc6b4c0f..bf4dc7355 100644
--- a/doc/admin/administrator_guide.md
+++ b/doc/admin/administrator_guide.md
@@ -100,7 +100,10 @@ There are several ways to deploy a new hub.
* some form of email server or email gateway such that PHP mail() works.
-* Mysql 5.x or MariaDB or postgres database server.
+* A supported database server. The supported databases are:
+ - Mysql version 8.0.22 or later
+ - MariaDB version 10.4 or later
+ - PostgreSQL version 12 or later
* ability to schedule jobs with cron.
diff --git a/doc/hook/check_account_password.bb b/doc/hook/check_account_password.bb
index 53562ec6e..ce5202f48 100644
--- a/doc/hook/check_account_password.bb
+++ b/doc/hook/check_account_password.bb
@@ -1 +1,17 @@
[h2]check_account_password[/h2]
+Use this hook to provide additional checks or validations of the password given when
+registering and account.
+[h3]Arguments:[/h3]
+[code=php]array(
+ 'password' => $password, // The password to check
+ 'result' => array(
+ 'error' => false,
+ 'message' => ''
+ )
+)[/code]
+[h3]Results:[/h3]
+For a failed check set the [code]error[/code] member of the [code]result[/code]
+array to [code]true[/code] and the [code]message[/code] to a short message
+explaining why it failed.
+
+Otherwise, leave it alone.
diff --git a/include/bbcode.php b/include/bbcode.php
index e0a0fe9a1..fb10ceb4a 100644
--- a/include/bbcode.php
+++ b/include/bbcode.php
@@ -1097,6 +1097,10 @@ function parseIdentityAwareHTML($Text) {
function bbcode($Text, $options = []) {
+ if (!$Text) {
+ return EMPTY_STR;
+ }
+
if(! is_array($options)) {
$options = [];
}
diff --git a/include/event.php b/include/event.php
index 745469064..da504c2d7 100644
--- a/include/event.php
+++ b/include/event.php
@@ -58,7 +58,9 @@ function format_event_html($ev) {
$ev['dtend'] , $bd_format )))
. '</span></div>' . "\r\n";
- $o .= '<div class="event-description">' . zidify_links(smilies(bbcode($ev['description']))) . '</div>' . "\r\n";
+ if (!empty($ev['description'])) {
+ $o .= '<div class="event-description">' . zidify_links(smilies(bbcode($ev['description']))) . '</div>' . "\r\n";
+ }
if(isset($ev['location']) && $ev['location'])
$o .= '<div class="event-location"><span class="event-label"> ' . t('Location:') . '</span>&nbsp;<span class="location">'
@@ -117,6 +119,7 @@ function format_event_obj($jobject) {
$dtdiff = $dtstart->diff($dtend_obj);
+ $oneday = false;
if($allday && ($dtdiff->days < 2))
$oneday = true;
diff --git a/include/items.php b/include/items.php
index 6cef3d2cd..56534c4e4 100644
--- a/include/items.php
+++ b/include/items.php
@@ -353,7 +353,7 @@ function can_comment_on_post($observer_xchan, $item) {
case 'specific':
case 'contacts':
case '':
- if(local_channel() && get_abconfig(local_channel(), (($item['verb'] === ACTIVITY_SHARE) ? $item['author_xchan'] : $item['owner_xchan']), 'their_perms', 'post_comments')) {
+ if(local_channel() && get_abconfig(local_channel(), (($item['verb'] === ACTIVITY_SHARE) ? $item['source_xchan'] : $item['owner_xchan']), 'their_perms', 'post_comments')) {
return true;
}
if(intval($item['item_wall']) && perm_is_allowed($item['uid'],$observer_xchan,'post_comments')) {
@@ -1837,7 +1837,7 @@ function item_store($arr, $allow_exec = false, $deliver = true) {
dbesc($r[0]['parent_mid']),
intval($arr['uid'])
);
- if($z && count($z))
+ if($z)
$r = $z;
}
diff --git a/include/text.php b/include/text.php
index c6b2bed78..41fb11ba2 100644
--- a/include/text.php
+++ b/include/text.php
@@ -2616,7 +2616,7 @@ function trim_and_unpunify($s) {
* @param number $effective_uid
*/
function xchan_query(&$items, $abook = true, $effective_uid = 0) {
- $arr = array();
+ $arr = [];
if($items && count($items)) {
if($effective_uid) {
@@ -2631,6 +2631,8 @@ function xchan_query(&$items, $abook = true, $effective_uid = 0) {
$arr[] = "'" . dbesc($item['owner_xchan']) . "'";
if($item['author_xchan'] && (! in_array("'" . dbesc($item['author_xchan']) . "'",$arr)))
$arr[] = "'" . dbesc($item['author_xchan']) . "'";
+ if($item['source_xchan'] && (! in_array("'" . dbesc($item['source_xchan']) . "'",$arr)))
+ $arr[] = "'" . dbesc($item['source_xchan']) . "'";
}
}
if(count($arr)) {
@@ -2654,6 +2656,7 @@ function xchan_query(&$items, $abook = true, $effective_uid = 0) {
for($x = 0; $x < count($items); $x ++) {
$items[$x]['owner'] = find_xchan_in_array($items[$x]['owner_xchan'],$chans);
$items[$x]['author'] = find_xchan_in_array($items[$x]['author_xchan'],$chans);
+ $items[$x]['source'] = find_xchan_in_array($items[$x]['source_xchan'],$chans);
}
}
}