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/
47 class PollPlugin extends MicroAppPlugin
49 const VERSION = '0.1';
51 // @fixme which domain should we use for these namespaces?
52 const POLL_OBJECT = 'http://apinamespace.org/activitystreams/object/poll';
53 const POLL_RESPONSE_OBJECT = 'http://apinamespace.org/activitystreams/object/poll-response';
56 * Database schema setup
61 * @return boolean hook value; true means continue processing, false means stop.
64 function onCheckSchema()
66 $schema = Schema::get();
67 $schema->ensureTable('poll', Poll::schemaDef());
68 $schema->ensureTable('poll_response', Poll_response::schemaDef());
73 * Show the CSS necessary for this plugin
75 * @param Action $action the action being run
77 * @return boolean hook value
80 function onEndShowStyles($action)
82 $action->cssLink($this->path('poll.css'));
87 * Load related modules when needed
89 * @param string $cls Name of the class to be loaded
91 * @return boolean hook value; true means continue processing, false means stop.
94 function onAutoload($cls)
96 $dir = dirname(__FILE__);
100 case 'ShowpollAction':
101 case 'NewpollAction':
102 case 'RespondpollAction':
103 include_once $dir . '/' . strtolower(mb_substr($cls, 0, -6)) . '.php';
106 case 'Poll_response':
107 include_once $dir.'/'.$cls.'.php';
110 case 'PollResponseForm':
111 case 'PollResultForm':
112 include_once $dir.'/'.strtolower($cls).'.php';
120 * Map URLs to actions
122 * @param Net_URL_Mapper $m path-to-action mapper
124 * @return boolean hook value; true means continue processing, false means stop.
127 function onRouterInitialized($m)
129 $m->connect('main/poll/new',
130 array('action' => 'newpoll'));
132 $m->connect('main/poll/:id',
133 array('action' => 'showpoll'),
134 array('id' => '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'));
136 $m->connect('main/poll/response/:id',
137 array('action' => 'showpollresponse'),
138 array('id' => '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'));
140 $m->connect('main/poll/:id/respond',
141 array('action' => 'respondpoll'),
142 array('id' => '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'));
148 * Plugin version data
150 * @param array &$versions array of version data
155 function onPluginVersion(&$versions)
157 $versions[] = array('name' => 'Poll',
158 'version' => self::VERSION,
159 'author' => 'Brion Vibber',
160 'homepage' => 'http://status.net/wiki/Plugin:Poll',
162 _m('Simple extension for supporting basic polls.'));
168 return array(self::POLL_OBJECT, self::POLL_RESPONSE_OBJECT);
172 * When a notice is deleted, delete the related Poll
174 * @param Notice $notice Notice being deleted
176 * @return boolean hook value
179 function deleteRelated($notice)
181 $p = Poll::getByNotice($notice);
191 * Save a poll from an activity
193 * @param Profile $profile Profile to use as author
194 * @param Activity $activity Activity to save
195 * @param array $options Options to pass to bookmark-saving code
197 * @return Notice resulting notice
200 function saveNoticeFromActivity($activity, $profile, $options=array())
203 common_log(LOG_DEBUG, "XXX activity: " . var_export($activity, true));
204 common_log(LOG_DEBUG, "XXX profile: " . var_export($profile, true));
205 common_log(LOG_DEBUG, "XXX options: " . var_export($options, true));
207 // Ok for now, we can grab stuff from the XML entry directly.
208 // This won't work when reading from JSON source
209 if ($activity->entry) {
210 $pollElements = $activity->entry->getElementsByTagNameNS(self::POLL_OBJECT, 'poll');
211 $responseElements = $activity->entry->getElementsByTagNameNS(self::POLL_OBJECT, 'response');
212 if ($dataElements->length) {
213 $data = $dataElements->item(0);
214 $question = $data->getAttribute('question');
216 foreach ($data->attributes as $node) {
217 $name = $node->nodeName;
218 if (substr($name, 0, 6) == 'option') {
219 $n = intval(substr($name, 6));
221 $opts[$n - 1] = $node->nodeValue;
225 common_log(LOG_DEBUG, "YYY question: $question");
226 common_log(LOG_DEBUG, "YYY opts: " . var_export($opts, true));
228 $notice = Poll::saveNew($profile, $question, $opts, $options);
229 common_log(LOG_DEBUG, "YYY ok: " . $notice->id);
231 } catch (Exception $e) {
232 common_log(LOG_DEBUG, "YYY fail: " . $e->getMessage());
234 } else if ($responseElements->length) {
235 $data = $responseElements->item(0);
236 $pollUri = $data->getAttribute('poll');
237 $selection = intval($data->getAttribute('selection'));
240 throw new Exception('Invalid poll response: no poll reference.');
242 $poll = Poll::staticGet('uri', $pollUri);
244 throw new Exception('Invalid poll response: poll is unknown.');
247 $notice = Poll_response::saveNew($profile, $poll, $selection, $options);
248 common_log(LOG_DEBUG, "YYY response ok: " . $notice->id);
250 } catch (Exception $e) {
251 common_log(LOG_DEBUG, "YYY response fail: " . $e->getMessage());
254 common_log(LOG_DEBUG, "YYY no poll data");
259 function activityObjectFromNotice($notice)
261 assert($this->isMyNotice($notice));
263 switch ($notice->object_type) {
264 case self::POLL_OBJECT:
265 return $this->activityObjectFromNoticePoll($notice);
266 case self::POLL_RESPONSE_OBJECT:
267 return $this->activityObjectFromNoticePollResponse($notice);
269 throw new Exception('Unexpected type for poll plugin: ' . $notice->object_type);
273 function activityObjectFromNoticePollResponse($notice)
275 $object = new ActivityObject();
276 $object->id = $notice->uri;
277 $object->type = self::POLL_OBJECT;
278 $object->title = $notice->content;
279 $object->summary = $notice->content;
280 $object->link = $notice->bestUrl();
282 $response = Poll_response::getByNotice($notice);
283 $poll = $response->getPoll();
286 * For the moment, using a kind of icky-looking schema that happens to
287 * work with out code for generating both Atom and JSON forms, though
290 * <poll:response xmlns:poll="http://apinamespace.org/activitystreams/object/poll"
291 * poll="http://..../poll/...."
295 * "xmlns:poll": http://apinamespace.org/activitystreams/object/poll
296 * "uri": "http://..../poll/...."
301 // @fixme there's no way to specify an XML node tree here, like <poll><option/><option/></poll>
302 // @fixme there's no way to specify a JSON array or multi-level tree unless you break the XML attribs
303 // @fixme XML node contents don't get shown in JSON
304 $data = array('xmlns:poll' => self::POLL_OBJECT,
305 'poll' => $poll->uri,
306 'selection' => intval($response->selection));
307 $object->extra[] = array('poll:response', $data, '');
311 function activityObjectFromNoticePoll($notice)
313 $object = new ActivityObject();
314 $object->id = $notice->uri;
315 $object->type = self::POLL_RESPONSE_OBJECT;
316 $object->title = $notice->content;
317 $object->summary = $notice->content;
318 $object->link = $notice->bestUrl();
320 $poll = Poll::getByNotice($notice);
322 * Adding the poll-specific data. There's no standard in AS for polls,
323 * so we're making stuff up.
325 * For the moment, using a kind of icky-looking schema that happens to
326 * work with out code for generating both Atom and JSON forms, though
329 * <poll:data xmlns:poll="http://apinamespace.org/activitystreams/object/poll"
330 * question="Who wants a poll question?"
331 * option1="Option one"
332 * option2="Option two"
333 * option3="Option three"></poll:data>
336 * "xmlns:poll": http://apinamespace.org/activitystreams/object/poll
337 * "question": "Who wants a poll question?"
338 * "option1": "Option one"
339 * "option2": "Option two"
340 * "option3": "Option three"
344 // @fixme there's no way to specify an XML node tree here, like <poll><option/><option/></poll>
345 // @fixme there's no way to specify a JSON array or multi-level tree unless you break the XML attribs
346 // @fixme XML node contents don't get shown in JSON
347 $data = array('xmlns:poll' => self::POLL_OBJECT,
348 'question' => $poll->question);
349 foreach ($poll->getOptions() as $i => $opt) {
350 $data['option' . ($i + 1)] = $opt;
352 $object->extra[] = array('poll:poll', $data, '');
357 * @fixme WARNING WARNING WARNING parent class closes the final div that we
358 * open here, but we probably shouldn't open it here. Check parent class
359 * and Bookmark plugin for if that's right.
361 function showNotice($notice, $out)
363 switch ($notice->object_type) {
364 case self::POLL_OBJECT:
365 return $this->showNoticePoll($notice, $out);
366 case self::POLL_RESPONSE_OBJECT:
367 return $this->showNoticePollResponse($notice, $out);
369 throw new Exception('Unexpected type for poll plugin: ' . $notice->object_type);
373 function showNoticePoll($notice, $out)
375 $user = common_current_user();
377 // @hack we want regular rendering, then just add stuff after that
378 $nli = new NoticeListItem($notice, $out);
381 $out->elementStart('div', array('class' => 'entry-content poll-content'));
382 $poll = Poll::getByNotice($notice);
385 $profile = $user->getProfile();
386 $response = $poll->getResponse($profile);
388 // User has already responded; show the results.
389 $form = new PollResultForm($poll, $out);
391 $form = new PollResponseForm($poll, $out);
396 $out->text('Poll data is missing');
398 $out->elementEnd('div');
401 $out->elementStart('div', array('class' => 'entry-content'));
404 function showNoticePollResponse($notice, $out)
406 $user = common_current_user();
408 // @hack we want regular rendering, then just add stuff after that
409 $nli = new NoticeListItem($notice, $out);
413 $out->elementStart('div', array('class' => 'entry-content'));
416 function entryForm($out)
418 return new NewPollForm($out);
421 // @fixme is this from parent?