3 * StatusNet - the distributed open-source microblogging tool
4 * Copyright (C) 2011, StatusNet, Inc.
6 * A plugin to enable social-bookmarking functionality
10 * This program is free software: you can redistribute it and/or modify
11 * it under the terms of the GNU Affero General Public License as published by
12 * the Free Software Foundation, either version 3 of the License, or
13 * (at your option) any later version.
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU Affero General Public License for more details.
20 * You should have received a copy of the GNU Affero General Public License
21 * along with this program. If not, see <http://www.gnu.org/licenses/>.
23 * @category PollPlugin
25 * @author Brion Vibber <brion@status.net>
26 * @copyright 2011 StatusNet, Inc.
27 * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
28 * @link http://status.net/
31 if (!defined('STATUSNET')) {
36 * Poll plugin main class
38 * @category PollPlugin
40 * @author Brion Vibber <brionv@status.net>
41 * @author Evan Prodromou <evan@status.net>
42 * @copyright 2011 StatusNet, Inc.
43 * @license http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
44 * @link http://status.net/
46 class PollPlugin extends MicroAppPlugin
48 const VERSION = '0.1';
50 // @fixme which domain should we use for these namespaces?
51 const POLL_OBJECT = 'http://activityschema.org/object/poll';
52 const POLL_RESPONSE_OBJECT = 'http://activityschema.org/object/poll-response';
55 * Database schema setup
60 * @return boolean hook value; true means continue processing, false means stop.
62 function onCheckSchema()
64 $schema = Schema::get();
65 $schema->ensureTable('poll', Poll::schemaDef());
66 $schema->ensureTable('poll_response', Poll_response::schemaDef());
71 * Show the CSS necessary for this plugin
73 * @param Action $action the action being run
75 * @return boolean hook value
77 function onEndShowStyles($action)
79 $action->cssLink($this->path('poll.css'));
84 * Load related modules when needed
86 * @param string $cls Name of the class to be loaded
88 * @return boolean hook value; true means continue processing, false means stop.
90 function onAutoload($cls)
92 $dir = dirname(__FILE__);
96 case 'ShowpollAction':
98 case 'RespondpollAction':
99 include_once $dir . '/' . strtolower(mb_substr($cls, 0, -6)) . '.php';
102 case 'Poll_response':
103 include_once $dir.'/'.$cls.'.php';
106 case 'PollResponseForm':
107 case 'PollResultForm':
108 include_once $dir.'/'.strtolower($cls).'.php';
116 * Map URLs to actions
118 * @param Net_URL_Mapper $m path-to-action mapper
120 * @return boolean hook value; true means continue processing, false means stop.
122 function onRouterInitialized($m)
124 $m->connect('main/poll/new',
125 array('action' => 'newpoll'));
127 $m->connect('main/poll/:id',
128 array('action' => 'showpoll'),
129 array('id' => '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'));
131 $m->connect('main/poll/response/:id',
132 array('action' => 'showpollresponse'),
133 array('id' => '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'));
135 $m->connect('main/poll/:id/respond',
136 array('action' => 'respondpoll'),
137 array('id' => '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'));
143 * Plugin version data
145 * @param array &$versions array of version data
149 function onPluginVersion(&$versions)
151 $versions[] = array('name' => 'Poll',
152 'version' => self::VERSION,
153 'author' => 'Brion Vibber',
154 'homepage' => 'http://status.net/wiki/Plugin:Poll',
156 // TRANS: Plugin description.
157 _m('Simple extension for supporting basic polls.'));
163 return array(self::POLL_OBJECT, self::POLL_RESPONSE_OBJECT);
167 * When a notice is deleted, delete the related Poll
169 * @param Notice $notice Notice being deleted
171 * @return boolean hook value
173 function deleteRelated($notice)
175 $p = Poll::getByNotice($notice);
185 * Save a poll from an activity
187 * @param Profile $profile Profile to use as author
188 * @param Activity $activity Activity to save
189 * @param array $options Options to pass to bookmark-saving code
191 * @return Notice resulting notice
193 function saveNoticeFromActivity($activity, $profile, $options=array())
196 common_log(LOG_DEBUG, "XXX activity: " . var_export($activity, true));
197 common_log(LOG_DEBUG, "XXX profile: " . var_export($profile, true));
198 common_log(LOG_DEBUG, "XXX options: " . var_export($options, true));
200 // Ok for now, we can grab stuff from the XML entry directly.
201 // This won't work when reading from JSON source
202 if ($activity->entry) {
203 $pollElements = $activity->entry->getElementsByTagNameNS(self::POLL_OBJECT, 'poll');
204 $responseElements = $activity->entry->getElementsByTagNameNS(self::POLL_OBJECT, 'response');
205 if ($pollElements->length) {
206 $data = $pollElements->item(0);
207 $question = $data->getAttribute('question');
209 foreach ($data->attributes as $node) {
210 $name = $node->nodeName;
211 if (substr($name, 0, 6) == 'option') {
212 $n = intval(substr($name, 6));
214 $opts[$n - 1] = $node->nodeValue;
218 common_log(LOG_DEBUG, "YYY question: $question");
219 common_log(LOG_DEBUG, "YYY opts: " . var_export($opts, true));
221 $notice = Poll::saveNew($profile, $question, $opts, $options);
222 common_log(LOG_DEBUG, "YYY ok: " . $notice->id);
224 } catch (Exception $e) {
225 common_log(LOG_DEBUG, "YYY fail: " . $e->getMessage());
227 } else if ($responseElements->length) {
228 $data = $responseElements->item(0);
229 $pollUri = $data->getAttribute('poll');
230 $selection = intval($data->getAttribute('selection'));
233 // TRANS: Exception thrown trying to respond to a poll without a poll reference.
234 throw new Exception(_m('Invalid poll response: no poll reference.'));
236 $poll = Poll::staticGet('uri', $pollUri);
238 // TRANS: Exception thrown trying to respond to a non-existing poll.
239 throw new Exception(_m('Invalid poll response: poll is unknown.'));
242 $notice = Poll_response::saveNew($profile, $poll, $selection, $options);
243 common_log(LOG_DEBUG, "YYY response ok: " . $notice->id);
245 } catch (Exception $e) {
246 common_log(LOG_DEBUG, "YYY response fail: " . $e->getMessage());
249 common_log(LOG_DEBUG, "YYY no poll data");
254 function activityObjectFromNotice($notice)
256 assert($this->isMyNotice($notice));
258 switch ($notice->object_type) {
259 case self::POLL_OBJECT:
260 return $this->activityObjectFromNoticePoll($notice);
261 case self::POLL_RESPONSE_OBJECT:
262 return $this->activityObjectFromNoticePollResponse($notice);
264 // TRANS: Exception thrown when performing an unexpected action on a poll.
265 // TRANS: %s is the unpexpected object type.
266 throw new Exception(sprintf(_m('Unexpected type for poll plugin: %s.'), $notice->object_type));
270 function activityObjectFromNoticePollResponse($notice)
272 $object = new ActivityObject();
273 $object->id = $notice->uri;
274 $object->type = self::POLL_OBJECT;
275 $object->title = $notice->content;
276 $object->summary = $notice->content;
277 $object->link = $notice->bestUrl();
279 $response = Poll_response::getByNotice($notice);
281 $poll = $response->getPoll();
283 // Stash data to be formatted later by
284 // $this->activityObjectOutputAtom() or
285 // $this->activityObjectOutputJson()...
286 $object->pollSelection = intval($response->selection);
287 $object->pollUri = $poll->uri;
293 function activityObjectFromNoticePoll($notice)
295 $object = new ActivityObject();
296 $object->id = $notice->uri;
297 $object->type = self::POLL_RESPONSE_OBJECT;
298 $object->title = $notice->content;
299 $object->summary = $notice->content;
300 $object->link = $notice->bestUrl();
302 $poll = Poll::getByNotice($notice);
304 // Stash data to be formatted later by
305 // $this->activityObjectOutputAtom() or
306 // $this->activityObjectOutputJson()...
307 $object->pollQuestion = $poll->question;
308 $object->pollOptions = $poll->getOptions();
315 * Called when generating Atom XML ActivityStreams output from an
316 * ActivityObject belonging to this plugin. Gives the plugin
317 * a chance to add custom output.
319 * Note that you can only add output of additional XML elements,
320 * not change existing stuff here.
322 * If output is already handled by the base Activity classes,
323 * you can leave this base implementation as a no-op.
325 * @param ActivityObject $obj
326 * @param XMLOutputter $out to add elements at end of object
328 function activityObjectOutputAtom(ActivityObject $obj, XMLOutputter $out)
330 if (isset($obj->pollQuestion)) {
332 * <poll:poll xmlns:poll="http://apinamespace.org/activitystreams/object/poll">
333 * <poll:question>Who wants a poll question?</poll:question>
334 * <poll:option>Option one</poll:option>
335 * <poll:option>Option two</poll:option>
336 * <poll:option>Option three</poll:option>
339 $data = array('xmlns:poll' => self::POLL_OBJECT);
340 $out->elementStart('poll:poll', $data);
341 $out->element('poll:question', array(), $obj->pollQuestion);
342 foreach ($obj->pollOptions as $opt) {
343 $out->element('poll:option', array(), $opt);
345 $out->elementEnd('poll:poll');
347 if (isset($obj->pollSelection)) {
349 * <poll:response xmlns:poll="http://apinamespace.org/activitystreams/object/poll">
350 * poll="http://..../poll/...."
353 $data = array('xmlns:poll' => self::POLL_OBJECT,
354 'poll' => $obj->pollUri,
355 'selection' => $obj->pollSelection);
356 $out->element('poll:response', $data, '');
361 * Called when generating JSON ActivityStreams output from an
362 * ActivityObject belonging to this plugin. Gives the plugin
363 * a chance to add custom output.
365 * Modify the array contents to your heart's content, and it'll
366 * all get serialized out as JSON.
368 * If output is already handled by the base Activity classes,
369 * you can leave this base implementation as a no-op.
371 * @param ActivityObject $obj
372 * @param array &$out JSON-targeted array which can be modified
374 public function activityObjectOutputJson(ActivityObject $obj, array &$out)
376 common_log(LOG_DEBUG, 'QQQ: ' . var_export($obj, true));
377 if (isset($obj->pollQuestion)) {
380 * "question": "Who wants a poll question?",
388 $data = array('question' => $obj->pollQuestion,
389 'options' => array());
390 foreach ($obj->pollOptions as $opt) {
391 $data['options'][] = $opt;
393 $out['poll'] = $data;
395 if (isset($obj->pollSelection)) {
398 * "poll": "http://..../poll/....",
402 $data = array('poll' => $obj->pollUri,
403 'selection' => $obj->pollSelection);
404 $out['pollResponse'] = $data;
410 * @fixme WARNING WARNING WARNING parent class closes the final div that we
411 * open here, but we probably shouldn't open it here. Check parent class
412 * and Bookmark plugin for if that's right.
414 function showNotice($notice, $out)
416 switch ($notice->object_type) {
417 case self::POLL_OBJECT:
418 return $this->showNoticePoll($notice, $out);
419 case self::POLL_RESPONSE_OBJECT:
420 return $this->showNoticePollResponse($notice, $out);
422 // TRANS: Exception thrown when performing an unexpected action on a poll.
423 // TRANS: %s is the unpexpected object type.
424 throw new Exception(sprintf(_m('Unexpected type for poll plugin: %s.'), $notice->object_type));
428 function showNoticePoll($notice, $out)
430 $user = common_current_user();
432 // @hack we want regular rendering, then just add stuff after that
433 $nli = new NoticeListItem($notice, $out);
436 $out->elementStart('div', array('class' => 'entry-content poll-content'));
437 $poll = Poll::getByNotice($notice);
440 $profile = $user->getProfile();
441 $response = $poll->getResponse($profile);
443 // User has already responded; show the results.
444 $form = new PollResultForm($poll, $out);
446 $form = new PollResponseForm($poll, $out);
451 $out->text(_('Poll data is missing'));
453 $out->elementEnd('div');
456 $out->elementStart('div', array('class' => 'entry-content'));
459 function showNoticePollResponse($notice, $out)
461 $user = common_current_user();
463 // @hack we want regular rendering, then just add stuff after that
464 $nli = new NoticeListItem($notice, $out);
468 $out->elementStart('div', array('class' => 'entry-content'));
471 function entryForm($out)
473 return new NewPollForm($out);
476 // @fixme is this from parent?
484 // TRANS: Application title.
485 return _m('APPTITLE','Poll');