123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475 |
- <?php
- /**
- * StatusNet - the distributed open-source microblogging tool
- * Copyright (C) 2011, StatusNet, Inc.
- *
- * A plugin to enable social-bookmarking functionality
- *
- * PHP version 5
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU Affero General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU Affero General Public License for more details.
- *
- * You should have received a copy of the GNU Affero General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
- * @category PollPlugin
- * @package StatusNet
- * @author Brion Vibber <brion@status.net>
- * @copyright 2011 StatusNet, Inc.
- * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
- * @link http://status.net/
- */
- if (!defined('STATUSNET')) {
- exit(1);
- }
- /**
- * Poll plugin main class
- *
- * @category PollPlugin
- * @package StatusNet
- * @author Brion Vibber <brionv@status.net>
- * @author Evan Prodromou <evan@status.net>
- * @copyright 2011 StatusNet, Inc.
- * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
- * @link http://status.net/
- */
- class PollPlugin extends MicroAppPlugin
- {
- const PLUGIN_VERSION = '0.1.1';
- // @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';
- public $oldSaveNew = true;
- /**
- * Database schema setup
- *
- * @return boolean hook value; true means continue processing, false means stop.
- * @see ColumnDef
- *
- * @see Schema
- */
- public 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;
- }
- /**
- * Show the CSS necessary for this plugin
- *
- * @param Action $action the action being run
- *
- * @return boolean hook value
- */
- public function onEndShowStyles($action)
- {
- $action->cssLink($this->path('css/poll.css'));
- return true;
- }
- /**
- * Map URLs to actions
- *
- * @param URLMapper $m path-to-action mapper
- *
- * @return boolean hook value; true means continue processing, false means stop.
- */
- public function onRouterInitialized(URLMapper $m)
- {
- $m->connect('main/poll/new',
- ['action' => 'newpoll']);
- $m->connect('main/poll/:id',
- ['action' => 'showpoll'],
- ['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',
- ['action' => 'showpollresponse'],
- ['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',
- ['action' => 'respondpoll'],
- ['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',
- ['action' => 'pollsettings']);
- return true;
- }
- /**
- * Plugin version data
- *
- * @param array &$versions array of version data
- *
- * @return bool true hook value
- * @throws Exception
- */
- public function onPluginVersion(array &$versions): bool
- {
- $versions[] = array('name' => 'Poll',
- 'version' => self::PLUGIN_VERSION,
- 'author' => 'Brion Vibber',
- 'homepage' => 'https://git.gnu.io/gnu/gnu-social/tree/master/plugins/Poll',
- 'rawdescription' =>
- // TRANS: Plugin description.
- _m('Simple extension for supporting basic polls.'));
- return true;
- }
- public function types()
- {
- return array(self::POLL_OBJECT, self::POLL_RESPONSE_OBJECT);
- }
- /**
- * When a notice is deleted, delete the related Poll
- *
- * @param Notice $notice Notice being deleted
- *
- * @return boolean hook value
- */
- public function deleteRelated(Notice $notice)
- {
- $p = Poll::getByNotice($notice);
- if (!empty($p)) {
- $p->delete();
- }
- return true;
- }
- /**
- * Save a poll from an activity
- *
- * @param Activity $activity Activity to save
- * @param Profile $profile Profile to use as author
- * @param array $options Options to pass to bookmark-saving code
- *
- * @return Notice resulting notice
- * @throws Exception if it failed
- */
- public function saveNoticeFromActivity(Activity $activity, Profile $profile, array $options = array())
- {
- // @fixme
- common_log(LOG_DEBUG, "XXX activity: " . var_export($activity, true));
- common_log(LOG_DEBUG, "XXX profile: " . var_export($profile, true));
- common_log(LOG_DEBUG, "XXX options: " . var_export($options, true));
- // Ok for now, we can grab stuff from the XML entry directly.
- // This won't work when reading from JSON source
- if ($activity->entry) {
- $pollElements = $activity->entry->getElementsByTagNameNS(self::POLL_OBJECT, 'poll');
- $responseElements = $activity->entry->getElementsByTagNameNS(self::POLL_OBJECT, 'response');
- if ($pollElements->length) {
- $question = '';
- $opts = [];
- $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;
- }
- try {
- $notice = Poll::saveNew($profile, $question, $opts, $options);
- common_log(LOG_DEBUG, "Saved Poll from ActivityStream data ok: notice id " . $notice->id);
- return $notice;
- } catch (Exception $e) {
- common_log(LOG_DEBUG, "Poll save from ActivityStream data failed: " . $e->getMessage());
- }
- } elseif ($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());
- // TRANS: Exception thrown trying to respond to a non-existing poll.
- }
- } else {
- common_log(LOG_DEBUG, "YYY no poll data");
- }
- }
- // If it didn't return before
- throw new ServerException(_m('Failed to save Poll response.'));
- }
- public 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));
- }
- }
- public 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;
- }
- public function activityObjectFromNoticePoll(Notice $notice)
- {
- $object = new ActivityObject();
- $object->id = $notice->uri;
- $object->type = self::POLL_OBJECT;
- $object->title = $notice->content;
- $object->summary = $notice->content;
- $object->link = $notice->getUrl();
- $poll = Poll::getByNotice($notice);
- if ($poll) {
- // Stash data to be formatted later by
- // $this->activityObjectOutputAtom() or
- // $this->activityObjectOutputJson()...
- $object->pollQuestion = $poll->question;
- $object->pollOptions = $poll->getOptions();
- }
- return $object;
- }
- /**
- * 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
- */
- public function activityObjectOutputAtom(ActivityObject $obj, XMLOutputter $out)
- {
- if (isset($obj->pollQuestion)) {
- /**
- * <poll:poll xmlns:poll="http://apinamespace.org/activitystreams/object/poll">
- * <poll:question>Who wants a poll question?</poll:question>
- * <poll:option>Option one</poll:option>
- * <poll:option>Option two</poll:option>
- * <poll:option>Option three</poll:option>
- * </poll:poll>
- */
- $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);
- }
- $out->elementEnd('poll:poll');
- }
- if (isset($obj->pollSelection)) {
- /**
- * <poll:response xmlns:poll="http://apinamespace.org/activitystreams/object/poll">
- * poll="http://..../poll/...."
- * selection="3" />
- */
- $data = array('xmlns:poll' => self::POLL_OBJECT,
- 'poll' => $obj->pollUri,
- 'selection' => $obj->pollSelection);
- $out->element('poll:response', $data, '');
- }
- }
- /**
- * 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;
- }
- }
- public function entryForm($out)
- {
- return new NewPollForm($out);
- }
- // @fixme is this from parent?
- public function tag()
- {
- return 'poll';
- }
- public function appTitle()
- {
- // TRANS: Application title.
- return _m('APPTITLE', 'Poll');
- }
- public 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
- public 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
- */
- public 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'));
- }
- }
- }
|