diff options
-rw-r--r-- | Zotlabs/Lib/Activity.php | 216 | ||||
-rw-r--r-- | Zotlabs/Lib/Libzot.php | 6 | ||||
-rw-r--r-- | Zotlabs/Web/HTTPSig.php | 54 | ||||
-rw-r--r-- | boot.php | 7 | ||||
-rw-r--r-- | include/event.php | 182 | ||||
-rw-r--r-- | include/html2bbcode.php | 2 |
6 files changed, 373 insertions, 94 deletions
diff --git a/Zotlabs/Lib/Activity.php b/Zotlabs/Lib/Activity.php index b83de6bb6..0c25605e7 100644 --- a/Zotlabs/Lib/Activity.php +++ b/Zotlabs/Lib/Activity.php @@ -501,7 +501,7 @@ class Activity { $ret['attributedTo'] = $i['author']['xchan_url']; - if ($i['id'] != $i['parent']) { + if ($i['mid'] !== $i['parent_mid']) { $ret['inReplyTo'] = ((strpos($i['thr_parent'], 'http') === 0) ? $i['thr_parent'] : z_root() . '/item/' . urlencode($i['thr_parent'])); } @@ -875,7 +875,7 @@ class Activity { } } - if ($i['id'] != $i['parent']) { + if ($i['mid'] !== $i['parent_mid']) { $reply = true; // inReplyTo needs to be set in the activity for followup actions (Like, Dislike, Announce, etc.), @@ -1154,9 +1154,10 @@ class Activity { $ret['url'] = $p['xchan_url']; $ret['publicKey'] = [ - 'id' => $p['xchan_url'], - 'owner' => $p['xchan_url'], - 'publicKeyPem' => $p['xchan_pubkey'] + 'id' => $p['xchan_url'], + 'owner' => $p['xchan_url'], + 'signatureAlgorithm' => 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256', + 'publicKeyPem' => $p['xchan_pubkey'] ]; if ($c) { @@ -1599,6 +1600,25 @@ class Activity { return; } + public static function drop($channel, $observer, $act) { + $r = q( + "select * from item where mid = '%s' and uid = %d limit 1", + dbesc((is_array($act->obj)) ? $act->obj['id'] : $act->obj), + intval($channel['channel_id']) + ); + + if (!$r) { + return; + } + + if (in_array($observer, [$r[0]['author_xchan'], $r[0]['owner_xchan']])) { + drop_item($r[0]['id'], false); + } elseif (in_array($act->actor['id'], [$r[0]['author_xchan'], $r[0]['owner_xchan']])) { + drop_item($r[0]['id'], false); + } + } + + static function actor_store($url, $person_obj, $force = false) { if (!is_array($person_obj)) { @@ -2003,7 +2023,10 @@ class Activity { } if ($channel['channel_system']) { - if (!MessageFilter::evaluate($s, get_config('system', 'pubstream_incl'), get_config('system', 'pubstream_excl'))) { + $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; } @@ -2225,6 +2248,21 @@ class Activity { static function decode_note($act) { + $response_activity = false; + + $s = []; + + // These activities should have been handled separately in the Inbox module and should not be turned into posts + + if ( + in_array($act->type, ['Follow', 'Accept', 'Reject', 'Create', 'Update']) && + is_array($act->obj) && + array_key_exists('type', $act->obj) && + ($act->obj['type'] === 'Follow' || ActivityStreams::is_an_actor($act->obj['type'])) + ) { + return false; + } + // Within our family of projects, Follow/Unfollow of a thread is an internal activity which should not be transmitted, // hence if we receive it - ignore or reject it. // Unfollow is not defined by ActivityStreams, which prefers Undo->Follow. @@ -2234,22 +2272,31 @@ class Activity { return false; } - $response_activity = false; + if (!isset($act->actor['id'])) { + logger('No actor!'); + return false; + } - $s = []; + // ensure we store the original actor + self::actor_store($act->actor['id'], $act->actor); + + $s['owner_xchan'] = $act->actor['id']; + $s['author_xchan'] = $act->actor['id']; if (is_array($act->obj)) { $content = self::get_content($act->obj); } - $s['owner_xchan'] = $act->actor['id']; - $s['author_xchan'] = $act->actor['id']; + $s['mid'] = ((is_array($act->obj) && isset($act->obj['id'])) ? $act->obj['id'] : $act->obj); - // ensure we store the original actor - self::actor_store($act->actor['id'], $act->actor); + if (!$s['mid']) { + return false; + } + + // Friendica sends the diaspora guid in a nonstandard field via AP + // If no uuid is provided we will create an uuid v5 from the mid + $s['uuid'] = ((is_array($act->obj) && isset($act->obj['diaspora:guid'])) ? $act->obj['diaspora:guid'] : uuid_from_url($s['mid'])); - $s['mid'] = $act->obj['id']; - $s['uuid'] = $act->obj['diaspora:guid']; $s['parent_mid'] = $act->parent_id; if (array_key_exists('published', $act->data)) { @@ -2273,13 +2320,18 @@ class Activity { $s['expires'] = datetime_convert('UTC', 'UTC', $act->obj['expires']); } + if ($act->type === 'Invite' && is_array($act->obj) && array_key_exists('type', $act->obj) && $act->obj['type'] === 'Event') { + $s['mid'] = $s['parent_mid'] = $act->id; + } + if (ActivityStreams::is_response_activity($act->type)) { $response_activity = true; $s['mid'] = $act->id; - // $s['parent_mid'] = $act->obj['id']; - $s['uuid'] = $act->data['diaspora:guid']; + $s['uuid'] = ((is_array($act->data) && isset($act->data['diaspora:guid'])) ? $act->data['diaspora:guid'] : uuid_from_url($s['mid'])); + + $s['parent_mid'] = ((is_array($act->obj) && isset($act->obj['id'])) ? $act->obj['id'] : $act->obj); // over-ride the object timestamp with the activity @@ -2292,8 +2344,8 @@ class Activity { } $obj_actor = ((isset($act->obj['actor'])) ? $act->obj['actor'] : $act->get_actor('attributedTo', $act->obj)); - // ensure we store the original actor + // ensure we store the original actor self::actor_store($obj_actor['id'], $obj_actor); $mention = self::get_actor_bbmention($obj_actor['id']); @@ -2322,13 +2374,41 @@ class Activity { } if ($act->type === 'Announce') { - $content['content'] = sprintf(t('🔁 Repeated %1$s\'s %2$s'), $mention, $act->obj['type']); + $s['author_xchan'] = $obj_actor['id']; + $s['mid'] = $act->obj['id']; + $s['parent_mid'] = $act->obj['id']; } if ($act->type === 'emojiReaction') { $content['content'] = (($act->tgt && $act->tgt['type'] === 'Image') ? '[img=32x32]' . $act->tgt['url'] . '[/img]' : '&#x' . $act->tgt['name'] . ';'); } } + $s['item_thread_top'] = 0; + $s['comment_policy'] = 'authenticated'; + + if ($s['mid'] === $s['parent_mid']) { + $s['item_thread_top'] = 1; + + // it is a parent node - decode the comment policy info if present + if (isset($act->obj['commentPolicy'])) { + $until = strpos($act->obj['commentPolicy'], 'until='); + if ($until !== false) { + $s['comments_closed'] = datetime_convert('UTC', 'UTC', substr($act->obj['commentPolicy'], $until + 6)); + if ($s['comments_closed'] < datetime_convert()) { + $s['nocomment'] = true; + } + } + + $remainder = substr($act->obj['commentPolicy'], 0, (($until) ? $until : strlen($act->obj['commentPolicy']))); + if ($remainder) { + $s['comment_policy'] = $remainder; + } + if (!(isset($item['comment_policy']) && strlen($item['comment_policy']))) { + $s['comment_policy'] = 'contacts'; + } + } + } + if (!array_key_exists('created', $s)) $s['created'] = datetime_convert(); @@ -2365,6 +2445,12 @@ class Activity { $s['obj_type'] = ACTIVITY_OBJ_COMMENT; } + $s['obj'] = $act->obj; + if (is_array($s['obj']) && array_path_exists('actor/id', $s['obj'])) { + $s['obj']['actor'] = $s['obj']['actor']['id']; + } + +/* $eventptr = null; if ($act->obj['type'] === 'Invite' && array_path_exists('object/type', $act->obj) && $act->obj['object']['type'] === 'Event') { @@ -2385,19 +2471,19 @@ class Activity { $s['obj']['asld'] = $eventptr; $s['obj']['type'] = ACTIVITY_OBJ_EVENT; $s['obj']['id'] = $eventptr['id']; - $s['obj']['title'] = $eventptr['name']; + $s['obj']['title'] = html2plain($eventptr['name']); if (strpos($act->obj['startTime'], 'Z')) $s['obj']['adjust'] = true; else - $s['obj']['adjust'] = false; + $s['obj']['adjust'] = true; $s['obj']['dtstart'] = datetime_convert('UTC', 'UTC', $eventptr['startTime']); if ($act->obj['endTime']) $s['obj']['dtend'] = datetime_convert('UTC', 'UTC', $eventptr['endTime']); else $s['obj']['nofinish'] = true; - $s['obj']['description'] = $eventptr['content']; + $s['obj']['description'] = html2bbcode($eventptr['content']); if (array_path_exists('location/content', $eventptr)) $s['obj']['location'] = $eventptr['location']['content']; @@ -2406,6 +2492,7 @@ class Activity { else { $s['obj'] = $act->obj; } +*/ $generator = $act->get_property_obj('generator'); if ((!$generator) && (!$response_activity)) { @@ -2445,7 +2532,7 @@ class Activity { if (array_key_exists('type', $act->obj)) { if ($act->obj['type'] === 'Note' && $s['attach']) { - $s['body'] .= self::bb_attach($s['attach'], $s['body']); + $s['body'] = self::bb_attach($s['attach'], $s['body']) . $s['body']; } if ($act->obj['type'] === 'Question' && in_array($act->type, ['Create', 'Update'])) { @@ -2534,13 +2621,13 @@ class Activity { usort($mps,[ '\Zotlabs\Lib\Activity', 'vid_sort' ]); foreach ($mps as $m) { if (intval($m['height']) < 500 && Activity::media_not_in_body($m['href'],$s['body'])) { - $s['body'] .= "\n\n" . $tag . $m['href'] . '[/video]'; + $s['body'] = $tag . $m['href'] . '[/video]' . "\n\n" . $s['body']; break; } } } elseif (is_string($act->obj['url']) && Activity::media_not_in_body($act->obj['url'],$s['body'])) { - $s['body'] .= "\n\n" . $tag . $act->obj['url'] . '[/video]'; + $s['body'] = $tag . $act->obj['url'] . '[/video]' . "\n\n" . $s['body']; } } @@ -2566,13 +2653,13 @@ class Activity { } foreach ($ptr as $vurl) { if (in_array($vurl['mediaType'], $atypes) && self::media_not_in_body($vurl['href'], $s['body'])) { - $s['body'] .= "\n\n" . '[audio]' . $vurl['href'] . '[/audio]'; + $s['body'] = '[audio]' . $vurl['href'] . '[/audio]' . "\n\n" . $s['body']; break; } } } elseif (is_string($act->obj['url']) && self::media_not_in_body($act->obj['url'], $s['body'])) { - $s['body'] .= "\n\n" . '[audio]' . $act->obj['url'] . '[/audio]'; + $s['body'] = '[audio]' . $act->obj['url'] . '[/audio]' . "\n\n" . $s['body']; } } @@ -2647,7 +2734,6 @@ class Activity { } } - if (in_array($act->obj['type'], ['Note', 'Article', 'Page'])) { $ptr = null; @@ -2785,12 +2871,6 @@ class Activity { return; }*/ - // TODO: this his handled in pubcrawl atm. - // very unpleasant and imperfect way of determining a Mastodon DM - /*if ($act->raw_recips && array_key_exists('to',$act->raw_recips) && is_array($act->raw_recips['to']) && count($act->raw_recips['to']) === 1 && $act->raw_recips['to'][0] === channel_url($channel) && ! $act->raw_recips['cc']) { - $item['item_private'] = 2; - }*/ - if ($item['parent_mid'] && $item['parent_mid'] !== $item['mid']) { $is_child_node = true; } @@ -2955,7 +3035,10 @@ class Activity { return; if ($channel['channel_system']) { - if (!MessageFilter::evaluate($item, get_config('system', 'pubstream_incl'), get_config('system', 'pubstream_excl'))) { + $incl = get_config('system','pubstream_incl'); + $excl = get_config('system','pubstream_excl'); + + if(($incl || $excl) && !MessageFilter::evaluate($item, $incl, $excl)) { logger('post is filtered'); return; } @@ -3038,6 +3121,19 @@ class Activity { $item['thr_parent'] = $parent[0]['parent_mid']; } $item['parent_mid'] = $parent[0]['parent_mid']; + //$item['item_private'] = $parent[0]['item_private']; + + } + + // An ugly and imperfect way to recognise a mastodon direct message + if ( + $item['item_private'] === 1 && + !isset($act->raw_recips['cc']) && + is_array($act->raw_recips['to']) && + in_array(channel_url($channel), $act->raw_recips['to']) && + !in_array($act->actor['followers'], $act->raw_recips['to']) + ) { + $item['item_private'] = 2; } // TODO: not implemented @@ -3048,6 +3144,12 @@ class Activity { intval($item['uid']) ); if ($r) { + + // If we already have the item, dismiss its announce + if ($act->type === 'Announce') { + return; + } + if ($item['edited'] > $r[0]['edited']) { $item['id'] = $r[0]['id']; $x = item_store_update($item); @@ -3064,12 +3166,12 @@ class Activity { logger('topfetch', LOGGER_DEBUG); // if the thread owner is a connnection, we will already receive any additional comments to their posts // but if they are not we can try to fetch others in the background - $x = q("SELECT abook.*, xchan.* FROM abook left join xchan on abook_xchan = xchan_hash + $connected = q("SELECT abook.*, xchan.* FROM abook left join xchan on abook_xchan = xchan_hash WHERE abook_channel = %d and abook_xchan = '%s' LIMIT 1", intval($channel['channel_id']), dbesc($parent[0]['owner_xchan']) ); - if (!$x) { + if (!$connected) { // determine if the top-level post provides a replies collection if ($parent[0]['obj']) { $parent[0]['obj'] = json_decode($parent[0]['obj'], true); @@ -3673,7 +3775,49 @@ class Activity { $event['nofinish'] = true; } } +/* + $eventptr = null; + + if ($act->obj['type'] === 'Invite' && array_path_exists('object/type', $act->obj) && $act->obj['object']['type'] === 'Event') { + $eventptr = $act->obj['object']; + $s['mid'] = $s['parent_mid'] = $act->obj['id']; + } + + if ($act->obj['type'] === 'Event') { + if ($act->type === 'Invite') { + $s['mid'] = $s['parent_mid'] = $act->id; + } + $eventptr = $act->obj; + } + + if ($eventptr) { + + $s['obj'] = []; + $s['obj']['asld'] = $eventptr; + $s['obj']['type'] = ACTIVITY_OBJ_EVENT; + $s['obj']['id'] = $eventptr['id']; + $s['obj']['title'] = html2plain($eventptr['name']); + if (strpos($act->obj['startTime'], 'Z')) + $s['obj']['adjust'] = true; + else + $s['obj']['adjust'] = true; + + $s['obj']['dtstart'] = datetime_convert('UTC', 'UTC', $eventptr['startTime']); + if ($act->obj['endTime']) + $s['obj']['dtend'] = datetime_convert('UTC', 'UTC', $eventptr['endTime']); + else + $s['obj']['nofinish'] = true; + $s['obj']['description'] = html2bbcode($eventptr['content']); + + if (array_path_exists('location/content', $eventptr)) + $s['obj']['location'] = $eventptr['location']['content']; + + } + else { + $s['obj'] = $act->obj; + } +*/ foreach (['name', 'summary', 'content'] as $a) { if (($x = self::get_textfield($act, $a)) !== false) { $content[$a] = $x; diff --git a/Zotlabs/Lib/Libzot.php b/Zotlabs/Lib/Libzot.php index fdeb7a3b0..40422a7d8 100644 --- a/Zotlabs/Lib/Libzot.php +++ b/Zotlabs/Lib/Libzot.php @@ -1578,7 +1578,11 @@ class Libzot { $local_public = false; continue; } - if (!MessageFilter::evaluate($arr, get_config('system', 'pubstream_incl'), get_config('system', 'pubstream_excl'))) { + + $incl = get_config('system','pubstream_incl'); + $excl = get_config('system','pubstream_excl'); + + if(($incl || $excl) && !MessageFilter::evaluate($arr, $incl, $excl)) { $local_public = false; continue; } diff --git a/Zotlabs/Web/HTTPSig.php b/Zotlabs/Web/HTTPSig.php index 660fdc4ce..4177477a1 100644 --- a/Zotlabs/Web/HTTPSig.php +++ b/Zotlabs/Web/HTTPSig.php @@ -127,6 +127,7 @@ class HTTPSig { if (array_key_exists($h, $headers)) { $signed_data .= $h . ': ' . $headers[$h] . "\n"; } + if ($h === 'date') { $d = new DateTime($headers[$h]); $d->setTimeZone(new DateTimeZone('UTC')); @@ -142,20 +143,34 @@ class HTTPSig { $signed_data = rtrim($signed_data, "\n"); $algorithm = null; + if ($sig_block['algorithm'] === 'rsa-sha256') { $algorithm = 'sha256'; } + if ($sig_block['algorithm'] === 'rsa-sha512') { $algorithm = 'sha512'; } - if (!array_key_exists('keyId', $sig_block)) + if (!array_key_exists('keyId', $sig_block)) { return $result; + } $result['signer'] = $sig_block['keyId']; $cached_key = self::get_key($key, $keytype, $result['signer']); + if ($sig_block['algorithm'] === 'hs2019') { + if (isset($cached_key['algorithm'])) { + if (strpos($cached_key['algorithm'], 'rsa-sha256') !== false) { + $algorithm = 'sha256'; + } + + if (strpos($cached_key['algorithm'], 'rsa-sha512') !== false) { + $algorithm = 'sha512'; + } + } + } if (!($cached_key && $cached_key['public_key'])) { return $result; @@ -296,7 +311,7 @@ class HTTPSig { $best = Libzot::zot_record_preferred($x); } if ($best && $best['xchan_pubkey']) { - return ['portable_id' => $best['xchan_hash'], 'public_key' => $best['xchan_pubkey'], 'hubloc' => $best]; + return ['portable_id' => $best['xchan_hash'], 'public_key' => $best['xchan_pubkey'], 'algorithm' => get_xconfig($best['xchan_hash'], 'system', 'signing_algorithm'), 'hubloc' => $best]; } } @@ -308,12 +323,38 @@ class HTTPSig { // The record wasn't in cache. Fetch it now. $r = ActivityStreams::fetch($id); + $signatureAlgorithm = EMPTY_STR; if ($r) { if (array_key_exists('publicKey', $r) && array_key_exists('publicKeyPem', $r['publicKey']) && array_key_exists('id', $r['publicKey'])) { if ($r['publicKey']['id'] === $id || $r['id'] === $id) { $portable_id = ((array_key_exists('owner', $r['publicKey'])) ? $r['publicKey']['owner'] : EMPTY_STR); - return ['public_key' => self::convertKey($r['publicKey']['publicKeyPem']), 'portable_id' => $portable_id, 'hubloc' => []]; + + // the w3c sec context has conflicting names and no defined values for this property except + // "http://www.w3.org/2000/09/xmldsig#rsa-sha1" + + // Since the names conflict, it could mess up LD-signatures but we will accept both, and at this + // time we will only look for the substrings 'rsa-sha256' and 'rsa-sha512' within those properties. + // We will also accept a toplevel 'sigAlgorithm' regardless of namespace with the same constraints. + // Default to rsa-sha256 if we can't figure out. If they're sending 'hs2019' we have to + // look for something. + + if (isset($r['publicKey']['signingAlgorithm'])) { + $signatureAlgorithm = $r['publicKey']['signingAlgorithm']; + set_xconfig($portable_id, 'system', 'signing_algorithm', $signatureAlgorithm); + } + + if (isset($r['publicKey']['signatureAlgorithm'])) { + $signatureAlgorithm = $r['publicKey']['signatureAlgorithm']; + set_xconfig($portable_id, 'system', 'signing_algorithm', $signatureAlgorithm); + } + + if (isset($r['sigAlgorithm'])) { + $signatureAlgorithm = $r['sigAlgorithm']; + set_xconfig($portable_id, 'system', 'signing_algorithm', $signatureAlgorithm); + } + + return ['public_key' => self::convertKey($r['publicKey']['publicKeyPem']), 'portable_id' => $portable_id, 'algorithm' => (($signatureAlgorithm) ? $signatureAlgorithm : 'rsa-sha256'), 'hubloc' => []]; } } } @@ -343,7 +384,7 @@ class HTTPSig { } if ($best && $best['xchan_pubkey']) { - return ['portable_id' => $best['xchan_hash'], 'public_key' => $best['xchan_pubkey'], 'hubloc' => $best]; + return ['portable_id' => $best['xchan_hash'], 'public_key' => $best['xchan_pubkey'], 'algorithm' => get_xconfig($best['xchan_hash'], 'system', 'signing_algorithm'), 'hubloc' => $best]; } } @@ -389,7 +430,7 @@ class HTTPSig { } if ($best && $best['xchan_pubkey']) { - return ['portable_id' => $best['xchan_hash'], 'public_key' => $best['xchan_pubkey'], 'hubloc' => $best]; + return ['portable_id' => $best['xchan_hash'], 'public_key' => $best['xchan_pubkey'], 'algorithm' => get_xconfig($best['xchan_hash'], 'system', 'signing_algorithm'), 'hubloc' => $best]; } } @@ -461,6 +502,9 @@ class HTTPSig { $x = self::sign($head, $prvkey, $alg); + // TODO: should we default to hs2019? + // $headerval = 'keyId="' . $keyid . '",algorithm="' . (($algorithm === 'rsa-sha256') ? 'hs2019' : $algorithm) . '",headers="' . $x['headers'] . '",signature="' . $x['signature'] . '"'; + $headerval = 'keyId="' . $keyid . '",algorithm="' . $algorithm . '",headers="' . $x['headers'] . '",signature="' . $x['signature'] . '"'; if ($encryption) { @@ -60,7 +60,7 @@ require_once('include/bbcode.php'); require_once('include/items.php'); define('PLATFORM_NAME', 'hubzilla'); -define('STD_VERSION', '7.1.4'); +define('STD_VERSION', '7.1.6'); define('ZOT_REVISION', '6.0'); define('DB_UPDATE_VERSION', 1252); @@ -915,9 +915,14 @@ class App { if ((x($_SERVER, 'QUERY_STRING')) && substr($_SERVER['QUERY_STRING'], 0, 2) === "q=") { self::$query_string = str_replace(['<', '>'], ['<', '>'], substr($_SERVER['QUERY_STRING'], 2)); + // removing trailing / - maybe a nginx problem if (substr(self::$query_string, 0, 1) == "/") self::$query_string = substr(self::$query_string, 1); + + // trim trailing '&' if no extra args are present + self::$query_string = rtrim(self::$query_string, '&'); + // change the first & to ? self::$query_string = preg_replace('/&/', '?', self::$query_string, 1); } diff --git a/include/event.php b/include/event.php index 440f559da..3d3dda035 100644 --- a/include/event.php +++ b/include/event.php @@ -70,8 +70,87 @@ function format_event_html($ev) { } function format_event_obj($jobject) { + $event = []; + $object = json_decode($jobject, true); + +/******* + This is our encoded format + + $x = [ + 'type' => 'Event', + 'id' => z_root() . '/event/' . $r[0]['resource_id'], + 'summary' => bbcode($arr['summary']), + // RFC3339 Section 4.3 + 'startTime' => (($arr['adjust']) ? datetime_convert('UTC','UTC',$arr['dtstart'], ATOM_TIME) : datetime_convert('UTC','UTC',$arr['dtstart'],'Y-m-d\\TH:i:s-00:00')), + 'content' => bbcode($arr['description']), + 'location' => [ 'type' => 'Place', 'content' => $arr['location'] ], + 'source' => [ 'content' => format_event_bbcode($arr), 'mediaType' => 'text/x-multicode' ], + 'url' => [ [ 'mediaType' => 'text/calendar', 'href' => z_root() . '/events/ical/' . $event['event_hash'] ] ], + 'actor' => Activity::encode_person($r[0],false), + ]; + if(! $arr['nofinish']) { + $x['endTime'] = (($arr['adjust']) ? datetime_convert('UTC','UTC',$arr['dtend'], ATOM_TIME) : datetime_convert('UTC','UTC',$arr['dtend'],'Y-m-d\\TH:i:s-00:00')); + } + +******/ + + if (is_array($object) && (array_key_exists('summary', $object) || array_key_exists('name', $object))) { + + $dtend = ((array_key_exists('endTime', $object)) ? $object['endTime'] : NULL_DATE); + $title = ((isset($object['summary']) && $object['summary']) ? zidify_links(smilies(bbcode($object['summary']))) : $object['name']); + + // mobilizon sets a timezone in the object + // we will assume that events with an timezone should be adjusted + $tz = $object['timezone'] ?? ''; + + // friendica has its own flag for adjust + $dfrn_adjust = $object['dfrn:adjust'] ?? ''; + + $adjust = ((strpos($object['startTime'], 'Z') !== false) || $tz || $dfrn_adjust); + + $allday = (($adjust) ? false : true); + + $dtstart = new DateTime($object['startTime']); + $dtend_obj = new DateTime($dtend); + + $dtdiff = $dtstart->diff($dtend_obj); + + if($allday && ($dtdiff->days < 2)) + $oneday = true; + + if($allday && !$oneday) { + // Subtract one day from the end date so we can use the "first day - last day" format for display. + $dtend_obj->modify('-1 day'); + $dtend = datetime_convert('UTC', 'UTC', $dtend_obj->format('Y-m-d H:i:s')); + } + + $bd_format = (($allday) ? t('l F d, Y') : t('l F d, Y \@ g:i A')); // Friday January 18, 2011 @ 8:01 AM or Friday January 18, 2011 for allday events + + $event['header'] = replace_macros(get_markup_template('event_item_header.tpl'), array( + '$title' => $title, + '$dtstart_label' => t('Start:'), + '$dtstart_title' => datetime_convert('UTC', 'UTC', $object['startTime'], ((strpos($object['startTime'], 'Z')) ? ATOM_TIME : 'Y-m-d\TH:i:s' )), + '$dtstart_dt' => (($adjust) ? day_translate(datetime_convert('UTC', date_default_timezone_get(), $object['startTime'], $bd_format)) : day_translate(datetime_convert('UTC', 'UTC', $object['startTime'], $bd_format))), + '$finish' => ((array_key_exists('endTime', $object)) ? true : false), + '$dtend_label' => t('End:'), + '$dtend_title' => datetime_convert('UTC', 'UTC', $dtend, ((strpos($object['startTime'], 'Z')) ? ATOM_TIME : 'Y-m-d\TH:i:s' )), + '$dtend_dt' => (($adjust) ? day_translate(datetime_convert('UTC', date_default_timezone_get(), $dtend, $bd_format)) : day_translate(datetime_convert('UTC', 'UTC', $dtend, $bd_format))), + '$allday' => $allday, + '$oneday' => $oneday, + '$event_tz' => ['label' => t('Timezone'), 'value' => (($tz === date_default_timezone_get()) ? '' : $tz)] + )); + $event['content'] = replace_macros(get_markup_template('event_item_content.tpl'), array( + '$description' => $object['content'], + '$location_label' => t('Location:'), + '$location' => ((array_path_exists('location/name', $object)) ? zidify_links(smilies(bbcode($object['location']['name']))) : EMPTY_STR) + )); + } + + return $event; +/* + $event = []; $object = json_decode($jobject,true); $event_tz = ''; @@ -136,6 +215,7 @@ function format_event_obj($jobject) { )); return $event; +*/ } function ical_wrapper($ev) { @@ -1122,34 +1202,35 @@ function event_store_item($arr, $event) { if($r) { - set_iconfig($r[0]['id'], 'event', 'timezone', $arr['timezone'], true); - xchan_query($r); - $r = fetch_post_tags($r,true); - - $object = json_encode(array( - 'type' => ACTIVITY_OBJ_EVENT, - 'id' => z_root() . '/event/' . $r[0]['resource_id'], - 'title' => $arr['summary'], - 'timezone' => $arr['timezone'], - 'dtstart' => $arr['dtstart'], - 'dtend' => $arr['dtend'], - 'nofinish' => $arr['nofinish'], - 'description' => $arr['description'], - 'location' => $arr['location'], - 'adjust' => $arr['adjust'], - 'content' => format_event_bbcode($arr), + //set_iconfig($r[0]['id'], 'event', 'timezone', $arr['timezone'], true); + //xchan_query($r); + //$r = fetch_post_tags($r,true); + + $x = [ + 'type' => 'Event', + 'id' => z_root() . '/event/' . $r[0]['resource_id'], + 'name' => $arr['summary'], +// 'summary' => bbcode($arr['summary']), + // RFC3339 Section 4.3 + 'startTime' => (($arr['adjust']) ? datetime_convert('UTC', 'UTC', $arr['dtstart'], ATOM_TIME) : datetime_convert('UTC', 'UTC', $arr['dtstart'], 'Y-m-d\\TH:i:s-00:00')), + 'content' => bbcode($arr['description']), + 'location' => [ 'type' => 'Place', 'name' => $arr['location'] ], + 'source' => [ 'content' => format_event_bbcode($arr), 'mediaType' => 'text/x-multicode' ], + 'url' => [ [ 'mediaType' => 'text/calendar', 'href' => z_root() . '/events/ical/' . $event['event_hash'] ] ], + 'actor' => Activity::encode_person($r[0], false), 'attachment' => Activity::encode_attachment($r[0]), - 'author' => array( - 'name' => $r[0]['author']['xchan_name'], - 'address' => $r[0]['author']['xchan_addr'], - 'guid' => $r[0]['author']['xchan_guid'], - 'guid_sig' => $r[0]['author']['xchan_guid_sig'], - 'link' => array( - array('rel' => 'alternate', 'type' => 'text/html', 'href' => $r[0]['author']['xchan_url']), - array('rel' => 'photo', 'type' => $r[0]['author']['xchan_photo_mimetype'], 'href' => $r[0]['author']['xchan_photo_m']) - ), - ), - )); + 'tag' => Activity::encode_taxonomy($r[0]) + ]; + + if (! $arr['nofinish']) { + $x['endTime'] = (($arr['adjust']) ? datetime_convert('UTC', 'UTC', $arr['dtend'], ATOM_TIME) : datetime_convert('UTC', 'UTC', $arr['dtend'], 'Y-m-d\\TH:i:s-00:00')); + } + + if ($event['event_repeat']) { + $x['eventRepeat'] = $event['event_repeat']; + } + + $object = json_encode($x); $private = (($arr['allow_cid'] || $arr['allow_gid'] || $arr['deny_cid'] || $arr['deny_gid']) ? 1 : 0); @@ -1285,29 +1366,30 @@ function event_store_item($arr, $event) { dbesc($arr['event_xchan']) ); if($x) { - $item_arr['obj'] = json_encode(array( - 'type' => ACTIVITY_OBJ_EVENT, - 'id' => z_root() . '/event/' . $event['event_hash'], - 'title' => $arr['summary'], - 'timezone' => $arr['timezone'], - 'dtstart' => $arr['dtstart'], - 'dtend' => $arr['dtend'], - 'nofinish' => $arr['nofinish'], - 'description' => $arr['description'], - 'location' => $arr['location'], - 'adjust' => $arr['adjust'], - 'content' => format_event_bbcode($arr), - 'attachment' => Activity::encode_attachment($item_arr), - 'author' => array( - 'name' => $x[0]['xchan_name'], - 'address' => $x[0]['xchan_addr'], - 'guid' => $x[0]['xchan_guid'], - 'guid_sig' => $x[0]['xchan_guid_sig'], - 'link' => array( - array('rel' => 'alternate', 'type' => 'text/html', 'href' => $x[0]['xchan_url']), - array('rel' => 'photo', 'type' => $x[0]['xchan_photo_mimetype'], 'href' => $x[0]['xchan_photo_m'])), - ), - )); + $y = [ + 'type' => 'Event', + 'id' => z_root() . '/event/' . $event['event_hash'], + 'name' => $arr['summary'], +// 'summary' => bbcode($arr['summary']), + // RFC3339 Section 4.3 + 'startTime' => (($arr['adjust']) ? datetime_convert('UTC', 'UTC', $arr['dtstart'], ATOM_TIME) : datetime_convert('UTC', 'UTC', $arr['dtstart'], 'Y-m-d\\TH:i:s-00:00')), + 'content' => bbcode($arr['description']), + 'location' => [ 'type' => 'Place', 'name' => bbcode($arr['location']) ], + 'source' => [ 'content' => format_event_bbcode($arr), 'mediaType' => 'text/x-multicode' ], + 'url' => [ [ 'mediaType' => 'text/calendar', 'href' => z_root() . '/events/ical/' . $event['event_hash'] ] ], + 'actor' => Activity::encode_person($z, false), + 'attachment' => Activity::encode_attachment($item_arr), + 'tag' => Activity::encode_taxonomy($item_arr) + ]; + + if (! $arr['nofinish']) { + $y['endTime'] = (($arr['adjust']) ? datetime_convert('UTC', 'UTC', $arr['dtend'], ATOM_TIME) : datetime_convert('UTC', 'UTC', $arr['dtend'], 'Y-m-d\\TH:i:s-00:00')); + } + if ($arr['event_repeat']) { + $y['eventRepeat'] = $arr['event_repeat']; + } + + $item_arr['obj'] = json_encode($y); } // propagate the event resource_id so that posts containing it are easily searchable in downstream copies diff --git a/include/html2bbcode.php b/include/html2bbcode.php index 173ea63bd..cc67a5666 100644 --- a/include/html2bbcode.php +++ b/include/html2bbcode.php @@ -87,7 +87,7 @@ function deletenode(&$doc, $node) function html2bbcode($message) { - if(!$message) + if(!is_string($message) && !$message) return; $message = str_replace("\r", "", $message); |