aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Zotlabs/Lib/Activity.php45
-rw-r--r--Zotlabs/Module/Item.php63
-rw-r--r--Zotlabs/Module/Vote.php129
-rw-r--r--include/conversation.php4
-rw-r--r--include/language.php7
-rw-r--r--include/text.php77
-rw-r--r--view/js/autocomplete.js2
-rw-r--r--view/js/main.js14
8 files changed, 330 insertions, 11 deletions
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 @@
+<?php
+namespace Zotlabs\Module;
+
+use App;
+use Zotlabs\Web\Controller;
+use Zotlabs\Lib\Activity;
+use Zotlabs\Daemon\Master;
+use Zotlabs\Lib\Libsync;
+
+class Vote extends Controller {
+
+ function init() {
+
+ $ret = [ 'success' => 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);
+ }
+}
+
+
+
+
+
+
+
+
diff --git a/include/conversation.php b/include/conversation.php
index 07d43e660..45b2b4d80 100644
--- a/include/conversation.php
+++ b/include/conversation.php
@@ -408,7 +408,7 @@ function count_descendants($item) {
* @return boolean
*/
function visible_activity($item) {
- $hidden_activities = [ ACTIVITY_LIKE, ACTIVITY_DISLIKE, ACTIVITY_AGREE, ACTIVITY_DISAGREE, ACTIVITY_ABSTAIN, ACTIVITY_ATTEND, ACTIVITY_ATTENDNO, ACTIVITY_ATTENDMAYBE, ACTIVITY_POLLRESPONSE ];
+ $hidden_activities = [ ACTIVITY_LIKE, ACTIVITY_DISLIKE, ACTIVITY_AGREE, ACTIVITY_DISAGREE, ACTIVITY_ABSTAIN, ACTIVITY_ATTEND, ACTIVITY_ATTENDNO, ACTIVITY_ATTENDMAYBE, ACTIVITY_POLLRESPONSE, 'Answer' ];
if(intval($item['item_notshown']))
return false;
@@ -419,6 +419,8 @@ function visible_activity($item) {
}
}
+
+
if(is_edit_activity($item))
return false;
diff --git a/include/language.php b/include/language.php
index e9d62e434..622b9614d 100644
--- a/include/language.php
+++ b/include/language.php
@@ -217,9 +217,10 @@ function t($s, $ctx = '') {
*/
function translate_projectname($s) {
-
- return str_replace(array('$projectname','$Projectname'),array(Zotlabs\Lib\System::get_platform_name(),ucfirst(Zotlabs\Lib\System::get_platform_name())),$s);
-
+ if(strpos($s,'rojectname') !== false) {
+ return str_replace(array('$projectname','$Projectname'),array(Zotlabs\Lib\System::get_platform_name(),ucfirst(Zotlabs\Lib\System::get_platform_name())),$s);
+ }
+ return $s;
}
diff --git a/include/text.php b/include/text.php
index 1f0af08e3..caecae33e 100644
--- a/include/text.php
+++ b/include/text.php
@@ -1731,6 +1731,11 @@ function prepare_body(&$item,$attach = false,$opts = false) {
}
}
+ $poll = (($item['obj_type'] === 'Question' && in_array($item['verb'],[ 'Create','Update' ])) ? format_poll($item, $s, $opts) : false);
+ if ($poll) {
+ $s = $poll;
+ }
+
$event = (($item['obj_type'] === ACTIVITY_OBJ_EVENT) ? format_event_obj($item['obj']) : false);
$prep_arr = [
@@ -1814,6 +1819,78 @@ function prepare_binary($item) {
}
+function format_poll($item,$s,$opts) {
+
+ if (! is_array($item['obj'])) {
+ $act = json_decode($item['obj'],true);
+ }
+ else {
+ $act = $item['obj'];
+ }
+
+ if (! is_array($act)) {
+ return EMPTY_STR;
+ }
+
+ $commentable = can_comment_on_post(((local_channel()) ? get_observer_hash() : EMPTY_STR),$item);
+
+ //logger('format_poll: ' . print_r($item,true));
+ $activated = ((local_channel() && local_channel() == $item['uid']) ? true : false);
+ $output = $s . EOL. EOL;
+
+ if ($act['type'] === 'Question') {
+ if ($activated and $commentable) {
+ $output .= '<form id="question-form-' . $item['id'] . '" >';
+ }
+ if (array_key_exists('anyOf',$act) && is_array($act['anyOf'])) {
+ foreach ($act['anyOf'] as $poll) {
+ if (array_key_exists('name',$poll) && $poll['name']) {
+ $text = html2plain(purify_html($poll['name']),256);
+ if (array_path_exists('replies/totalItems',$poll)) {
+ $total = $poll['replies']['totalItems'];
+ }
+ else {
+ $total = 0;
+ }
+ if ($activated && $commentable) {
+ $output .= '<input type="checkbox" name="answer[]" value="' . htmlspecialchars($text) . '"> ' . $text . '</input>' . ' (' . $total . ')' . EOL;
+ }
+ else {
+ $output .= '[ ] ' . $text . ' (' . $total . ')' . EOL;
+ }
+ }
+ }
+ }
+ if (array_key_exists('oneOf',$act) && is_array($act['oneOf'])) {
+ foreach ($act['oneOf'] as $poll) {
+ if (array_key_exists('name',$poll) && $poll['name']) {
+ $text = html2plain(purify_html($poll['name']),256);
+ if (array_path_exists('replies/totalItems',$poll)) {
+ $total = $poll['replies']['totalItems'];
+ }
+ else {
+ $total = 0;
+ }
+ if ($activated && $commentable) {
+ $output .= '<input type="radio" name="answer" value="' . htmlspecialchars($text) . '"> ' . $text . '</input>' . ' (' . $total . ')' . EOL;
+ }
+ else {
+ $output .= '( ) ' . $text . ' (' . $total . ')' . EOL;
+ }
+ }
+ }
+ }
+ if ($activated and $commentable) {
+ $output .= EOL . '<input type="button" class="btn btn-std btn-success" name="vote" value="vote" onclick="submitPoll(' . $item['id'] . '); return false;">'. '</form>';
+ }
+
+ }
+ return $output;
+}
+
+
+
+
/**
* @brief Given a text string, convert from content_type to HTML.
*
diff --git a/view/js/autocomplete.js b/view/js/autocomplete.js
index 278a0a176..c194338d6 100644
--- a/view/js/autocomplete.js
+++ b/view/js/autocomplete.js
@@ -372,7 +372,7 @@ function string2bb(element) {
return;
if(type=='bbcode') {
- var open_close_elements = ['bold', 'italic', 'underline', 'overline', 'strike', 'superscript', 'subscript', 'quote', 'code', 'open', 'spoiler', 'summary', 'map', 'nobb', 'list', 'checklist', 'ul', 'ol', 'dl', 'li', 'table', 'tr', 'th', 'td', 'center', 'color', 'font', 'size', 'zrl', 'zmg', 'rpost', 'qr', 'observer', 'observer.language','embed', 'highlight', 'url', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
+ var open_close_elements = ['bold', 'italic', 'underline', 'overline', 'strike', 'superscript', 'subscript', 'quote', 'code', 'open', 'spoiler', 'summary', 'map', 'nobb', 'list', 'checklist', 'question', 'answer', 'ul', 'ol', 'dl', 'li', 'table', 'tr', 'th', 'td', 'center', 'color', 'font', 'size', 'zrl', 'zmg', 'rpost', 'qr', 'observer', 'observer.language','embed', 'highlight', 'url', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
var open_elements = ['observer.baseurl', 'observer.address', 'observer.photo', 'observer.name', 'observer.webname', 'observer.url', '*', 'hr' ];
var elements = open_close_elements.concat(open_elements);
diff --git a/view/js/main.js b/view/js/main.js
index 4ec7a71aa..e735e9d6b 100644
--- a/view/js/main.js
+++ b/view/js/main.js
@@ -1267,6 +1267,20 @@ function filestorage(event, nick, id) {
});
}
+function submitPoll(id) {
+
+ $.post('vote/' + id,
+ $('#question-form-' + id).serialize(),
+ function(data) {
+ $.jGrowl(data.message, { sticky: false, theme: ((data.success) ? 'info' : 'notice'), life: 10000 });
+ if(timer) clearTimeout(timer);
+ timer = setTimeout(updateInit,1500);
+ }
+ );
+
+}
+
+
function post_comment(id) {
unpause();
commentBusy = true;