X-Git-Url: https://git.mxchange.org/?a=blobdiff_plain;f=plugins%2FPoll%2FPollPlugin.php;h=53a491ef4776918d27ae6db14848bcc95d06c37e;hb=871912a00a3e627569ac65111d55b47f5d8ccedf;hp=8a69ccd225b4f37856f910f493cc164c4bee935b;hpb=051180035102f5ab02e0913801a716e1291b9469;p=quix0rs-gnu-social.git diff --git a/plugins/Poll/PollPlugin.php b/plugins/Poll/PollPlugin.php index 8a69ccd225..53a491ef47 100644 --- a/plugins/Poll/PollPlugin.php +++ b/plugins/Poll/PollPlugin.php @@ -43,11 +43,15 @@ if (!defined('STATUSNET')) { * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0 * @link http://status.net/ */ - class PollPlugin extends MicroAppPlugin { const VERSION = '0.1'; - const POLL_OBJECT = 'http://apinamespace.org/activitystreams/object/poll'; + + // @fixme which domain should we use for these namespaces? + const POLL_OBJECT = 'http://activityschema.org/object/poll'; + const POLL_RESPONSE_OBJECT = 'http://activityschema.org/object/poll-response'; + + var $oldSaveNew = true; /** * Database schema setup @@ -57,12 +61,12 @@ class PollPlugin extends MicroAppPlugin * * @return boolean hook value; true means continue processing, false means stop. */ - function onCheckSchema() { $schema = Schema::get(); $schema->ensureTable('poll', Poll::schemaDef()); $schema->ensureTable('poll_response', Poll_response::schemaDef()); + $schema->ensureTable('user_poll_prefs', User_poll_prefs::schemaDef()); return true; } @@ -73,55 +77,20 @@ class PollPlugin extends MicroAppPlugin * * @return boolean hook value */ - function onEndShowStyles($action) { - $action->cssLink($this->path('poll.css')); + $action->cssLink($this->path('css/poll.css')); return true; } - /** - * Load related modules when needed - * - * @param string $cls Name of the class to be loaded - * - * @return boolean hook value; true means continue processing, false means stop. - */ - - function onAutoload($cls) - { - $dir = dirname(__FILE__); - - switch ($cls) - { - case 'ShowpollAction': - case 'NewpollAction': - case 'RespondpollAction': - include_once $dir . '/' . strtolower(mb_substr($cls, 0, -6)) . '.php'; - return false; - case 'Poll': - case 'Poll_response': - include_once $dir.'/'.$cls.'.php'; - return false; - case 'NewPollForm': - case 'PollResponseForm': - case 'PollResultForm': - include_once $dir.'/'.strtolower($cls).'.php'; - return false; - default: - return true; - } - } - /** * Map URLs to actions * - * @param Net_URL_Mapper $m path-to-action mapper + * @param URLMapper $m path-to-action mapper * * @return boolean hook value; true means continue processing, false means stop. */ - - function onRouterInitialized($m) + public function onRouterInitialized(URLMapper $m) { $m->connect('main/poll/new', array('action' => 'newpoll')); @@ -130,10 +99,17 @@ class PollPlugin extends MicroAppPlugin array('action' => 'showpoll'), array('id' => '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}')); + $m->connect('main/poll/response/:id', + array('action' => 'showpollresponse'), + array('id' => '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}')); + $m->connect('main/poll/:id/respond', array('action' => 'respondpoll'), array('id' => '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}')); + $m->connect('settings/poll', + array('action' => 'pollsettings')); + return true; } @@ -144,21 +120,21 @@ class PollPlugin extends MicroAppPlugin * * @return value */ - - function onPluginVersion(&$versions) + function onPluginVersion(array &$versions) { $versions[] = array('name' => 'Poll', 'version' => self::VERSION, 'author' => 'Brion Vibber', 'homepage' => 'http://status.net/wiki/Plugin:Poll', 'rawdescription' => + // TRANS: Plugin description. _m('Simple extension for supporting basic polls.')); return true; } function types() { - return array(self::POLL_OBJECT); + return array(self::POLL_OBJECT, self::POLL_RESPONSE_OBJECT); } /** @@ -168,8 +144,7 @@ class PollPlugin extends MicroAppPlugin * * @return boolean hook value */ - - function deleteRelated($notice) + function deleteRelated(Notice $notice) { $p = Poll::getByNotice($notice); @@ -189,8 +164,7 @@ class PollPlugin extends MicroAppPlugin * * @return Notice resulting notice */ - - function saveNoticeFromActivity($activity, $profile, $options=array()) + function saveNoticeFromActivity(Activity $activity, Profile $profile, array $options=array()) { // @fixme common_log(LOG_DEBUG, "XXX activity: " . var_export($activity, true)); @@ -200,28 +174,46 @@ class PollPlugin extends MicroAppPlugin // Ok for now, we can grab stuff from the XML entry directly. // This won't work when reading from JSON source if ($activity->entry) { - $elements = $activity->entry->getElementsByTagNameNS(self::POLL_OBJECT, 'data'); - if ($elements->length) { - $data = $elements->item(0); - $question = $data->getAttribute('question'); + $pollElements = $activity->entry->getElementsByTagNameNS(self::POLL_OBJECT, 'poll'); + $responseElements = $activity->entry->getElementsByTagNameNS(self::POLL_OBJECT, 'response'); + if ($pollElements->length) { + $question = ''; $opts = array(); - foreach ($data->attributes as $node) { - $name = $node->nodeName; - if (substr($name, 0, 6) == 'option') { - $n = intval(substr($name, 6)); - if ($n > 0) { - $opts[$n - 1] = $node->nodeValue; - } - } + + $data = $pollElements->item(0); + foreach ($data->getElementsByTagNameNS(self::POLL_OBJECT, 'question') as $node) { + $question = $node->textContent; + } + foreach ($data->getElementsByTagNameNS(self::POLL_OBJECT, 'option') as $node) { + $opts[] = $node->textContent; } - common_log(LOG_DEBUG, "YYY question: $question"); - common_log(LOG_DEBUG, "YYY opts: " . var_export($opts, true)); try { $notice = Poll::saveNew($profile, $question, $opts, $options); - common_log(LOG_DEBUG, "YYY ok: " . $notice->id); + common_log(LOG_DEBUG, "Saved Poll from ActivityStream data ok: notice id " . $notice->id); return $notice; } catch (Exception $e) { - common_log(LOG_DEBUG, "YYY fail: " . $e->getMessage()); + common_log(LOG_DEBUG, "Poll save from ActivityStream data failed: " . $e->getMessage()); + } + } else if ($responseElements->length) { + $data = $responseElements->item(0); + $pollUri = $data->getAttribute('poll'); + $selection = intval($data->getAttribute('selection')); + + if (!$pollUri) { + // TRANS: Exception thrown trying to respond to a poll without a poll reference. + throw new Exception(_m('Invalid poll response: No poll reference.')); + } + $poll = Poll::getKV('uri', $pollUri); + if (!$poll) { + // TRANS: Exception thrown trying to respond to a non-existing poll. + throw new Exception(_m('Invalid poll response: Poll is unknown.')); + } + try { + $notice = Poll_response::saveNew($profile, $poll, $selection, $options); + common_log(LOG_DEBUG, "Saved Poll_response ok, notice id: " . $notice->id); + return $notice; + } catch (Exception $e) { + common_log(LOG_DEBUG, "Poll response save fail: " . $e->getMessage()); } } else { common_log(LOG_DEBUG, "YYY no poll data"); @@ -229,87 +221,158 @@ class PollPlugin extends MicroAppPlugin } } - function activityObjectFromNotice($notice) + function activityObjectFromNotice(Notice $notice) { assert($this->isMyNotice($notice)); + switch ($notice->object_type) { + case self::POLL_OBJECT: + return $this->activityObjectFromNoticePoll($notice); + case self::POLL_RESPONSE_OBJECT: + return $this->activityObjectFromNoticePollResponse($notice); + default: + // TRANS: Exception thrown when performing an unexpected action on a poll. + // TRANS: %s is the unexpected object type. + throw new Exception(sprintf(_m('Unexpected type for poll plugin: %s.'), $notice->object_type)); + } + } + + function activityObjectFromNoticePollResponse(Notice $notice) + { + $object = new ActivityObject(); + $object->id = $notice->uri; + $object->type = self::POLL_RESPONSE_OBJECT; + $object->title = $notice->content; + $object->summary = $notice->content; + $object->link = $notice->getUrl(); + + $response = Poll_response::getByNotice($notice); + if ($response) { + $poll = $response->getPoll(); + if ($poll) { + // Stash data to be formatted later by + // $this->activityObjectOutputAtom() or + // $this->activityObjectOutputJson()... + $object->pollSelection = intval($response->selection); + $object->pollUri = $poll->uri; + } + } + return $object; + } + + function activityObjectFromNoticePoll(Notice $notice) + { $object = new ActivityObject(); $object->id = $notice->uri; $object->type = self::POLL_OBJECT; - $object->title = 'Poll title'; - $object->summary = 'Poll summary'; - $object->link = $notice->bestUrl(); + $object->title = $notice->content; + $object->summary = $notice->content; + $object->link = $notice->getUrl(); $poll = Poll::getByNotice($notice); - /** - * Adding the poll-specific data. There's no standard in AS for polls, - * so we're making stuff up. - * - * For the moment, using a kind of icky-looking schema that happens to - * work with out code for generating both Atom and JSON forms, though - * I don't like it: - * - * - * - * "poll:data": { - * "xmlns:poll": http://apinamespace.org/activitystreams/object/poll - * "question": "Who wants a poll question?" - * "option1": "Option one" - * "option2": "Option two" - * "option3": "Option three" - * } - * - */ - // @fixme there's no way to specify an XML node tree here, like - // @fixme there's no way to specify a JSON array or multi-level tree unless you break the XML attribs - // @fixme XML node contents don't get shown in JSON - $data = array('xmlns:poll' => self::POLL_OBJECT, - 'question' => $poll->question); - foreach ($poll->getOptions() as $i => $opt) { - $data['option' . ($i + 1)] = $opt; + if ($poll) { + // Stash data to be formatted later by + // $this->activityObjectOutputAtom() or + // $this->activityObjectOutputJson()... + $object->pollQuestion = $poll->question; + $object->pollOptions = $poll->getOptions(); } - $object->extra[] = array('poll:data', $data, ''); + return $object; } /** - * @fixme WARNING WARNING WARNING parent class closes the final div that we - * open here, but we probably shouldn't open it here. Check parent class - * and Bookmark plugin for if that's right. + * Called when generating Atom XML ActivityStreams output from an + * ActivityObject belonging to this plugin. Gives the plugin + * a chance to add custom output. + * + * Note that you can only add output of additional XML elements, + * not change existing stuff here. + * + * If output is already handled by the base Activity classes, + * you can leave this base implementation as a no-op. + * + * @param ActivityObject $obj + * @param XMLOutputter $out to add elements at end of object */ - function showNotice($notice, $out) + function activityObjectOutputAtom(ActivityObject $obj, XMLOutputter $out) { - $user = common_current_user(); - - // @hack we want regular rendering, then just add stuff after that - $nli = new NoticeListItem($notice, $out); - $nli->showNotice(); - - $out->elementStart('div', array('class' => 'entry-content poll-content')); - $poll = Poll::getByNotice($notice); - if ($poll) { - if ($user) { - $profile = $user->getProfile(); - $response = $poll->getResponse($profile); - if ($response) { - // User has already responded; show the results. - $form = new PollResultForm($poll, $out); - } else { - $form = new PollResponseForm($poll, $out); - } - $form->show(); + if (isset($obj->pollQuestion)) { + /** + * + * Who wants a poll question? + * Option one + * Option two + * Option three + * + */ + $data = array('xmlns:poll' => self::POLL_OBJECT); + $out->elementStart('poll:poll', $data); + $out->element('poll:question', array(), $obj->pollQuestion); + foreach ($obj->pollOptions as $opt) { + $out->element('poll:option', array(), $opt); } - } else { - $out->text('Poll data is missing'); + $out->elementEnd('poll:poll'); + } + if (isset($obj->pollSelection)) { + /** + * + * poll="http://..../poll/...." + * selection="3" /> + */ + $data = array('xmlns:poll' => self::POLL_OBJECT, + 'poll' => $obj->pollUri, + 'selection' => $obj->pollSelection); + $out->element('poll:response', $data, ''); } - $out->elementEnd('div'); + } - // @fixme - $out->elementStart('div', array('class' => 'entry-content')); + /** + * Called when generating JSON ActivityStreams output from an + * ActivityObject belonging to this plugin. Gives the plugin + * a chance to add custom output. + * + * Modify the array contents to your heart's content, and it'll + * all get serialized out as JSON. + * + * If output is already handled by the base Activity classes, + * you can leave this base implementation as a no-op. + * + * @param ActivityObject $obj + * @param array &$out JSON-targeted array which can be modified + */ + public function activityObjectOutputJson(ActivityObject $obj, array &$out) + { + common_log(LOG_DEBUG, 'QQQ: ' . var_export($obj, true)); + if (isset($obj->pollQuestion)) { + /** + * "poll": { + * "question": "Who wants a poll question?", + * "options": [ + * "Option 1", + * "Option 2", + * "Option 3" + * ] + * } + */ + $data = array('question' => $obj->pollQuestion, + 'options' => array()); + foreach ($obj->pollOptions as $opt) { + $data['options'][] = $opt; + } + $out['poll'] = $data; + } + if (isset($obj->pollSelection)) { + /** + * "pollResponse": { + * "poll": "http://..../poll/....", + * "selection": 3 + * } + */ + $data = array('poll' => $obj->pollUri, + 'selection' => $obj->pollSelection); + $out['pollResponse'] = $data; + } } function entryForm($out) @@ -325,6 +388,79 @@ class PollPlugin extends MicroAppPlugin function appTitle() { - return _m('Poll'); + // TRANS: Application title. + return _m('APPTITLE','Poll'); + } + + function onStartAddNoticeReply($nli, $parent, $child) + { + // Filter out any poll responses + if ($parent->object_type == self::POLL_OBJECT && + $child->object_type == self::POLL_RESPONSE_OBJECT) { + return false; + } + return true; + } + + // Hide poll responses for @chuck + + function onEndNoticeWhoGets($notice, &$ni) { + if ($notice->object_type == self::POLL_RESPONSE_OBJECT) { + foreach ($ni as $id => $source) { + $user = User::getKV('id', $id); + if (!empty($user)) { + $pollPrefs = User_poll_prefs::getKV('user_id', $user->id); + if (!empty($pollPrefs) && ($pollPrefs->hide_responses)) { + unset($ni[$id]); + } + } + } + } + return true; + } + + /** + * Menu item for personal subscriptions/groups area + * + * @param Action $action action being executed + * + * @return boolean hook return + */ + + function onEndAccountSettingsNav($action) + { + $action_name = $action->trimmed('action'); + + $action->menuItem(common_local_url('pollsettings'), + // TRANS: Poll plugin menu item on user settings page. + _m('MENU', 'Polls'), + // TRANS: Poll plugin tooltip for user settings menu item. + _m('Configure poll behavior'), + $action_name === 'pollsettings'); + + return true; + } + + protected function showNoticeContent(Notice $stored, HTMLOutputter $out, Profile $scoped=null) + { + if ($stored->object_type == self::POLL_RESPONSE_OBJECT) { + parent::showNoticeContent($stored, $out, $scoped); + return; + } + + // If the stored notice is a POLL_OBJECT + $poll = Poll::getByNotice($stored); + if ($poll instanceof Poll) { + if (!$scoped instanceof Profile || $poll->getResponse($scoped) instanceof Poll_response) { + // Either the user is not logged in or it has already responded; show the results. + $form = new PollResultForm($poll, $out); + } else { + $form = new PollResponseForm($poll, $out); + } + $form->show(); + } else { + // TRANS: Error text displayed if no poll data could be found. + $out->text(_m('Poll data is missing')); + } } }