]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - plugins/Poll/PollPlugin.php
* Superfluous whitespace removed
[quix0rs-gnu-social.git] / plugins / Poll / PollPlugin.php
1 <?php
2 /**
3  * StatusNet - the distributed open-source microblogging tool
4  * Copyright (C) 2011, StatusNet, Inc.
5  *
6  * A plugin to enable social-bookmarking functionality
7  *
8  * PHP version 5
9  *
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.
14  *
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.
19  *
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/>.
22  *
23  * @category  PollPlugin
24  * @package   StatusNet
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/
29  */
30
31 if (!defined('STATUSNET')) {
32     exit(1);
33 }
34
35 /**
36  * Poll plugin main class
37  *
38  * @category  PollPlugin
39  * @package   StatusNet
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/
45  */
46 class PollPlugin extends MicroAppPlugin
47 {
48     const VERSION         = '0.1';
49
50     // @fixme which domain should we use for these namespaces?
51     const POLL_OBJECT          = 'http://apinamespace.org/activitystreams/object/poll';
52     const POLL_RESPONSE_OBJECT = 'http://apinamespace.org/activitystreams/object/poll-response';
53
54     /**
55      * Database schema setup
56      *
57      * @see Schema
58      * @see ColumnDef
59      *
60      * @return boolean hook value; true means continue processing, false means stop.
61      */
62     function onCheckSchema()
63     {
64         $schema = Schema::get();
65         $schema->ensureTable('poll', Poll::schemaDef());
66         $schema->ensureTable('poll_response', Poll_response::schemaDef());
67         return true;
68     }
69
70     /**
71      * Show the CSS necessary for this plugin
72      *
73      * @param Action $action the action being run
74      *
75      * @return boolean hook value
76      */
77     function onEndShowStyles($action)
78     {
79         $action->cssLink($this->path('poll.css'));
80         return true;
81     }
82
83     /**
84      * Load related modules when needed
85      *
86      * @param string $cls Name of the class to be loaded
87      *
88      * @return boolean hook value; true means continue processing, false means stop.
89      */
90     function onAutoload($cls)
91     {
92         $dir = dirname(__FILE__);
93
94         switch ($cls)
95         {
96         case 'ShowpollAction':
97         case 'NewpollAction':
98         case 'RespondpollAction':
99             include_once $dir . '/' . strtolower(mb_substr($cls, 0, -6)) . '.php';
100             return false;
101         case 'Poll':
102         case 'Poll_response':
103             include_once $dir.'/'.$cls.'.php';
104             return false;
105         case 'NewPollForm':
106         case 'PollResponseForm':
107         case 'PollResultForm':
108             include_once $dir.'/'.strtolower($cls).'.php';
109             return false;
110         default:
111             return true;
112         }
113     }
114
115     /**
116      * Map URLs to actions
117      *
118      * @param Net_URL_Mapper $m path-to-action mapper
119      *
120      * @return boolean hook value; true means continue processing, false means stop.
121      */
122     function onRouterInitialized($m)
123     {
124         $m->connect('main/poll/new',
125                     array('action' => 'newpoll'));
126
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}'));
130
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}'));
134
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}'));
138
139         return true;
140     }
141
142     /**
143      * Plugin version data
144      *
145      * @param array &$versions array of version data
146      *
147      * @return value
148      */
149     function onPluginVersion(&$versions)
150     {
151         $versions[] = array('name' => 'Poll',
152                             'version' => self::VERSION,
153                             'author' => 'Brion Vibber',
154                             'homepage' => 'http://status.net/wiki/Plugin:Poll',
155                             'rawdescription' =>
156                             _m('Simple extension for supporting basic polls.'));
157         return true;
158     }
159
160     function types()
161     {
162         return array(self::POLL_OBJECT, self::POLL_RESPONSE_OBJECT);
163     }
164
165     /**
166      * When a notice is deleted, delete the related Poll
167      *
168      * @param Notice $notice Notice being deleted
169      *
170      * @return boolean hook value
171      */
172     function deleteRelated($notice)
173     {
174         $p = Poll::getByNotice($notice);
175
176         if (!empty($p)) {
177             $p->delete();
178         }
179
180         return true;
181     }
182
183     /**
184      * Save a poll from an activity
185      *
186      * @param Profile  $profile  Profile to use as author
187      * @param Activity $activity Activity to save
188      * @param array    $options  Options to pass to bookmark-saving code
189      *
190      * @return Notice resulting notice
191      */
192     function saveNoticeFromActivity($activity, $profile, $options=array())
193     {
194         // @fixme
195         common_log(LOG_DEBUG, "XXX activity: " . var_export($activity, true));
196         common_log(LOG_DEBUG, "XXX profile: " . var_export($profile, true));
197         common_log(LOG_DEBUG, "XXX options: " . var_export($options, true));
198
199         // Ok for now, we can grab stuff from the XML entry directly.
200         // This won't work when reading from JSON source
201         if ($activity->entry) {
202             $pollElements = $activity->entry->getElementsByTagNameNS(self::POLL_OBJECT, 'poll');
203             $responseElements = $activity->entry->getElementsByTagNameNS(self::POLL_OBJECT, 'response');
204             if ($pollElements->length) {
205                 $data = $pollElements->item(0);
206                 $question = $data->getAttribute('question');
207                 $opts = array();
208                 foreach ($data->attributes as $node) {
209                     $name = $node->nodeName;
210                     if (substr($name, 0, 6) == 'option') {
211                         $n = intval(substr($name, 6));
212                         if ($n > 0) {
213                             $opts[$n - 1] = $node->nodeValue;
214                         }
215                     }
216                 }
217                 common_log(LOG_DEBUG, "YYY question: $question");
218                 common_log(LOG_DEBUG, "YYY opts: " . var_export($opts, true));
219                 try {
220                     $notice = Poll::saveNew($profile, $question, $opts, $options);
221                     common_log(LOG_DEBUG, "YYY ok: " . $notice->id);
222                     return $notice;
223                 } catch (Exception $e) {
224                     common_log(LOG_DEBUG, "YYY fail: " . $e->getMessage());
225                 }
226             } else if ($responseElements->length) {
227                 $data = $responseElements->item(0);
228                 $pollUri = $data->getAttribute('poll');
229                 $selection = intval($data->getAttribute('selection'));
230
231                 if (!$pollUri) {
232                     throw new Exception('Invalid poll response: no poll reference.');
233                 }
234                 $poll = Poll::staticGet('uri', $pollUri);
235                 if (!$poll) {
236                     throw new Exception('Invalid poll response: poll is unknown.');
237                 }
238                 try {
239                     $notice = Poll_response::saveNew($profile, $poll, $selection, $options);
240                     common_log(LOG_DEBUG, "YYY response ok: " . $notice->id);
241                     return $notice;
242                 } catch (Exception $e) {
243                     common_log(LOG_DEBUG, "YYY response fail: " . $e->getMessage());
244                 }
245             } else {
246                 common_log(LOG_DEBUG, "YYY no poll data");
247             }
248         }
249     }
250
251     function activityObjectFromNotice($notice)
252     {
253         assert($this->isMyNotice($notice));
254
255         switch ($notice->object_type) {
256         case self::POLL_OBJECT:
257             return $this->activityObjectFromNoticePoll($notice);
258         case self::POLL_RESPONSE_OBJECT:
259             return $this->activityObjectFromNoticePollResponse($notice);
260         default:
261             throw new Exception('Unexpected type for poll plugin: ' . $notice->object_type);
262         }
263     }
264
265     function activityObjectFromNoticePollResponse($notice)
266     {
267         $object = new ActivityObject();
268         $object->id      = $notice->uri;
269         $object->type    = self::POLL_OBJECT;
270         $object->title   = $notice->content;
271         $object->summary = $notice->content;
272         $object->link    = $notice->bestUrl();
273
274         $response = Poll_response::getByNotice($notice);
275         if (!$response) {
276             common_log(LOG_DEBUG, "QQQ notice uri: $notice->uri");
277         } else {
278             $poll = $response->getPoll();
279             /**
280              * For the moment, using a kind of icky-looking schema that happens to
281              * work with out code for generating both Atom and JSON forms, though
282              * I don't like it:
283              *
284              * <poll:response xmlns:poll="http://apinamespace.org/activitystreams/object/poll"
285              *                poll="http://..../poll/...."
286              *                selection="3" />
287              *
288              * "poll:response": {
289              *     "xmlns:poll": http://apinamespace.org/activitystreams/object/poll
290              *     "uri": "http://..../poll/...."
291              *     "selection": 3
292              * }
293              *
294              */
295             // @fixme there's no way to specify an XML node tree here, like <poll><option/><option/></poll>
296             // @fixme there's no way to specify a JSON array or multi-level tree unless you break the XML attribs
297             // @fixme XML node contents don't get shown in JSON
298             $data = array('xmlns:poll' => self::POLL_OBJECT,
299                           'poll'       => $poll->uri,
300                           'selection'  => intval($response->selection));
301             $object->extra[] = array('poll:response', $data, '');
302         }
303         return $object;
304     }
305
306     function activityObjectFromNoticePoll($notice)
307     {
308         $object = new ActivityObject();
309         $object->id      = $notice->uri;
310         $object->type    = self::POLL_RESPONSE_OBJECT;
311         $object->title   = $notice->content;
312         $object->summary = $notice->content;
313         $object->link    = $notice->bestUrl();
314
315         $poll = Poll::getByNotice($notice);
316         /**
317          * Adding the poll-specific data. There's no standard in AS for polls,
318          * so we're making stuff up.
319          *
320          * For the moment, using a kind of icky-looking schema that happens to
321          * work with out code for generating both Atom and JSON forms, though
322          * I don't like it:
323          *
324          * <poll:data xmlns:poll="http://apinamespace.org/activitystreams/object/poll"
325          *            question="Who wants a poll question?"
326          *            option1="Option one"
327          *            option2="Option two"
328          *            option3="Option three"></poll:data>
329          *
330          * "poll:response": {
331          *     "xmlns:poll": http://apinamespace.org/activitystreams/object/poll
332          *     "question": "Who wants a poll question?"
333          *     "option1": "Option one"
334          *     "option2": "Option two"
335          *     "option3": "Option three"
336          * }
337          *
338          */
339         // @fixme there's no way to specify an XML node tree here, like <poll><option/><option/></poll>
340         // @fixme there's no way to specify a JSON array or multi-level tree unless you break the XML attribs
341         // @fixme XML node contents don't get shown in JSON
342         $data = array('xmlns:poll' => self::POLL_OBJECT,
343                       'question'   => $poll->question);
344         foreach ($poll->getOptions() as $i => $opt) {
345             $data['option' . ($i + 1)] = $opt;
346         }
347         $object->extra[] = array('poll:poll', $data, '');
348         return $object;
349     }
350
351     /**
352      * @fixme WARNING WARNING WARNING parent class closes the final div that we
353      * open here, but we probably shouldn't open it here. Check parent class
354      * and Bookmark plugin for if that's right.
355      */
356     function showNotice($notice, $out)
357     {
358         switch ($notice->object_type) {
359         case self::POLL_OBJECT:
360             return $this->showNoticePoll($notice, $out);
361         case self::POLL_RESPONSE_OBJECT:
362             return $this->showNoticePollResponse($notice, $out);
363         default:
364             throw new Exception('Unexpected type for poll plugin: ' . $notice->object_type);
365         }
366     }
367
368     function showNoticePoll($notice, $out)
369     {
370         $user = common_current_user();
371
372         // @hack we want regular rendering, then just add stuff after that
373         $nli = new NoticeListItem($notice, $out);
374         $nli->showNotice();
375
376         $out->elementStart('div', array('class' => 'entry-content poll-content'));
377         $poll = Poll::getByNotice($notice);
378         if ($poll) {
379             if ($user) {
380                 $profile = $user->getProfile();
381                 $response = $poll->getResponse($profile);
382                 if ($response) {
383                     // User has already responded; show the results.
384                     $form = new PollResultForm($poll, $out);
385                 } else {
386                     $form = new PollResponseForm($poll, $out);
387                 }
388                 $form->show();
389             }
390         } else {
391             $out->text('Poll data is missing');
392         }
393         $out->elementEnd('div');
394
395         // @fixme
396         $out->elementStart('div', array('class' => 'entry-content'));
397     }
398
399     function showNoticePollResponse($notice, $out)
400     {
401         $user = common_current_user();
402
403         // @hack we want regular rendering, then just add stuff after that
404         $nli = new NoticeListItem($notice, $out);
405         $nli->showNotice();
406
407         // @fixme
408         $out->elementStart('div', array('class' => 'entry-content'));
409     }
410
411     function entryForm($out)
412     {
413         return new NewPollForm($out);
414     }
415
416     // @fixme is this from parent?
417     function tag()
418     {
419         return 'poll';
420     }
421
422     function appTitle()
423     {
424         return _m('Poll');
425     }
426 }