From 989443a5698adf5e7a93f874048699526aa103a7 Mon Sep 17 00:00:00 2001 From: zotlabs Date: Thu, 30 Jan 2020 15:56:33 -0800 Subject: basic poll support and patch to not call System::get_platform_name() within t() unless needed. Polls probably need refining and have not yet been fully tested after porting --- Zotlabs/Lib/Activity.php | 45 +++++++++++++++-- Zotlabs/Module/Item.php | 63 ++++++++++++++++++++++- Zotlabs/Module/Vote.php | 129 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 231 insertions(+), 6 deletions(-) create mode 100644 Zotlabs/Module/Vote.php (limited to 'Zotlabs') diff --git a/Zotlabs/Lib/Activity.php b/Zotlabs/Lib/Activity.php index 43315a87f..66b1ee4b8 100644 --- a/Zotlabs/Lib/Activity.php +++ b/Zotlabs/Lib/Activity.php @@ -813,6 +813,10 @@ class Activity { static function activity_mapper($verb) { + if ($verb === 'Answer') { + return 'Note'; + } + if(strpos($verb,'/') === false) { return $verb; } @@ -932,10 +936,6 @@ class Activity { static function activity_obj_mapper($obj) { - if(strpos($obj,'/') === false) { - return $obj; - } - $objs = [ 'http://activitystrea.ms/schema/1.0/note' => 'Note', 'http://activitystrea.ms/schema/1.0/comment' => 'Note', @@ -956,6 +956,15 @@ class Activity { call_hooks('activity_obj_mapper',$objs); + if ($obj === 'Answer') { + return 'Note'; + } + + if (strpos($obj,'/') === false) { + return $obj; + } + + if(array_key_exists($obj,$objs)) { return $objs[$obj]; } @@ -1644,6 +1653,13 @@ class Activity { $s['expires'] = datetime_convert('UTC','UTC',$act->obj['expires']); } + if ($act->type === 'Note' && $act->obj['type'] === 'Question' && $act->data['name']) { + $s['mid'] = $act->id; + $s['parent_mid'] = $act->obj['id']; + $s['replyto'] = $act->replyto; + $s['verb'] = 'Answer'; + $content['content'] = EMPTY_STR; + } if(in_array($act->type, [ 'Like', 'Dislike', 'Flag', 'Block', 'Announce', 'Accept', 'Reject', 'TentativeAccept', 'emojiReaction' ])) { @@ -1711,6 +1727,15 @@ class Activity { $s['verb'] = self::activity_decode_mapper($act->type); + if ($act->type === 'Note' && $act->obj['type'] === 'Question' && $act->data['name'] && ! $content['content']) { + $s['verb'] = 'Answer'; + $s['title'] = purify_html($act->data['name']); + } + + // Mastodon does not provide update timestamps when updating poll tallies which means race conditions may occur here. + if ($act->type === 'Update' && $act->obj['type'] === 'Question' && $s['edited'] === $s['created']) { + $s['edited'] = datetime_convert(); + } if($act->type === 'Tombstone' || $act->type === 'Delete' || ($act->type === 'Create' && $act->obj['type'] === 'Tombstone')) { $s['item_deleted'] = 1; @@ -1798,6 +1823,18 @@ class Activity { } + if ($act->obj['type'] === 'Question' && in_array($act->type,['Create','Update'])) { + if ($act->obj['endTime']) { + $s['comments_closed'] = datetime_convert('UTC','UTC', $act->obj['endTime']); + } + } + + if ($act->obj['closed']) { + $s['comments_closed'] = datetime_convert('UTC','UTC', $act->obj['closed']); + } + + + // 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 diff --git a/Zotlabs/Module/Item.php b/Zotlabs/Module/Item.php index 1a25e54df..8b4bbae91 100644 --- a/Zotlabs/Module/Item.php +++ b/Zotlabs/Module/Item.php @@ -718,7 +718,14 @@ class Item extends Controller { // BBCODE alert: the following functions assume bbcode input // and will require alternatives for alternative content-types (text/html, text/markdown, text/plain, etc.) // we may need virtual or template classes to implement the possible alternatives - + + $obj = $this->extract_poll_data($body); + if ($obj) { + $datarray['obj'] = $obj; + $obj_type = 'Question'; + } + + if(strpos($body,'[/summary]') !== false) { $match = ''; @@ -1387,5 +1394,57 @@ class Item extends Controller { return $ret; } - + function extract_poll_data(&$body) { + + $multiple = false; + + if (strpos($body,'[/question]') === false && strpos($body,'[/answer]') === false) { + return false; + } + if (strpos($body,'[nobb]') !== false) { + return false; + } + + + $obj = []; + $ptr = []; + $matches = null; + $obj['type'] = 'Question'; + + if (preg_match_all('/\[answer\](.*?)\[\/answer\]/',$body,$matches,PREG_SET_ORDER)) { + foreach ($matches as $match) { + $ptr[] = [ 'name' => $match[1], 'type' => 'Note', 'replies' => [ 'type' => 'Collection', 'totalItems' => 0 ]]; + $body = str_replace('[answer]' . $match[1] . '[/answer]', EMPTY_STR, $body); + } + } + + $matches = null; + + if (preg_match('/\[question\](.*?)\[\/question\]/',$body,$matches)) { + $obj['content'] = bbcode($matches[1]); + $body = str_replace('[question]' . $matches[1] . '[/question]', $matches[1], $body); + $obj['oneOf'] = $ptr; + } + + $matches = null; + + if (preg_match('/\[question=multiple\](.*?)\[\/question\]/',$body,$matches)) { + $obj['content'] = bbcode($matches[1]); + $body = str_replace('[question=multiple]' . $matches[1] . '[/question]', $matches[1], $body); + $obj['anyOf'] = $ptr; + } + + $matches = null; + + if (preg_match('/\[ends\](.*?)\[\/ends\]',$body,$matches)) { + $obj['endTime'] = datetime_convert(date_default_timezone_get(),'UTC', $matches[1],ATOM_TIME); + $body = str_replace('[ends]' . $match[1] . '[/ends]', EMPTY_STR, $body); + } + + return $obj; + + } + + + } diff --git a/Zotlabs/Module/Vote.php b/Zotlabs/Module/Vote.php new file mode 100644 index 000000000..52d6a4bea --- /dev/null +++ b/Zotlabs/Module/Vote.php @@ -0,0 +1,129 @@ + false, 'message' => EMPTY_STR ]; + + $channel = App::get_channel(); + + if (! $channel) { + $ret['message'] = t('Permission denied.'); + json_return_and_die($ret); + } + + + $fetch = null; + $id = argv(1); + $response = $_REQUEST['answer']; + + if ($id) { + $fetch = q("select * from item where id = %d limit 1", + intval($id) + ); + } + + + if ($fetch && $fetch[0]['obj_type'] === 'Question') { + $obj = json_decode($fetch[0]['obj'],true); + + } + else { + $ret['message'] = t('Poll not found.'); + json_return_and_die($ret); + } + + $valid = false; + + if ($obj['oneOf']) { + foreach($obj['oneOf'] as $selection) { + // logger('selection: ' . $selection); + // logger('response: ' . $response); + if($selection['name'] && $selection['name'] === $response) { + $valid = true; + } + } + } + + $choices = []; + if ($obj['anyOf']) { + foreach ($obj['anyOf'] as $selection) { + $choices[] = $selection['name']; + } + foreach ($response as $res) { + if (! in_array($res,$choices)) { + $valid = false; + break; + } + $valid = true; + } + } + + if (! $valid) { + $ret['message'] = t('Invalid response.'); + json_return_and_die($ret); + } + + if (! is_array($response)) { + $response = [ $response ]; + } + + foreach ($response as $res) { + + $item = []; + + + $item['aid'] = $channel['channel_account_id']; + $item['uid'] = $channel['channel_id']; + $item['item_origin'] = true; + $item['parent'] = $fetch[0]['id']; + $item['parent_mid'] = $fetch[0]['mid']; + $item['uuid'] = new_uuid(); + $item['mid'] = z_root() . '/item/' . $item['uuid']; + $item['verb'] = 'Answer'; + $item['title'] = $res; + $item['author_xchan'] = $channel['channel_hash']; + $item['owner_xchan'] = $fetch[0]['author_xchan']; + + $item['obj'] = $obj; + $item['obj_type'] = 'Question'; + + $x = item_store($item); + + retain_item($fetch[0]['id']); + + if($x['success']) { + $itemid = $x['item_id']; + Master::Summon( [ 'Notifier', 'like', $itemid ] ); + } + + $r = q("select * from item where id = %d", + intval($itemid) + ); + if ($r) { + xchan_query($r); + $sync_item = fetch_post_tags($r); + Libsync::build_sync_packet($channel['channel_id'], [ 'item' => [ encode_item($sync_item[0],true) ] ]); + } + } + $ret['success'] = true; + $ret['message'] = t('Response submitted. Updates may not appear instantly.'); + json_return_and_die($ret); + } +} + + + + + + + + -- cgit v1.2.3