]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - plugins/Poll/PollPlugin.php
Work on turning poll responses into activities so we can send them over ostatus bridg...
[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
47 class PollPlugin extends MicroAppPlugin
48 {
49     const VERSION         = '0.1';
50
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';
54
55     /**
56      * Database schema setup
57      *
58      * @see Schema
59      * @see ColumnDef
60      *
61      * @return boolean hook value; true means continue processing, false means stop.
62      */
63
64     function onCheckSchema()
65     {
66         $schema = Schema::get();
67         $schema->ensureTable('poll', Poll::schemaDef());
68         $schema->ensureTable('poll_response', Poll_response::schemaDef());
69         return true;
70     }
71
72     /**
73      * Show the CSS necessary for this plugin
74      *
75      * @param Action $action the action being run
76      *
77      * @return boolean hook value
78      */
79
80     function onEndShowStyles($action)
81     {
82         $action->cssLink($this->path('poll.css'));
83         return true;
84     }
85
86     /**
87      * Load related modules when needed
88      *
89      * @param string $cls Name of the class to be loaded
90      *
91      * @return boolean hook value; true means continue processing, false means stop.
92      */
93
94     function onAutoload($cls)
95     {
96         $dir = dirname(__FILE__);
97
98         switch ($cls)
99         {
100         case 'ShowpollAction':
101         case 'NewpollAction':
102         case 'RespondpollAction':
103             include_once $dir . '/' . strtolower(mb_substr($cls, 0, -6)) . '.php';
104             return false;
105         case 'Poll':
106         case 'Poll_response':
107             include_once $dir.'/'.$cls.'.php';
108             return false;
109         case 'NewPollForm':
110         case 'PollResponseForm':
111         case 'PollResultForm':
112             include_once $dir.'/'.strtolower($cls).'.php';
113             return false;
114         default:
115             return true;
116         }
117     }
118
119     /**
120      * Map URLs to actions
121      *
122      * @param Net_URL_Mapper $m path-to-action mapper
123      *
124      * @return boolean hook value; true means continue processing, false means stop.
125      */
126
127     function onRouterInitialized($m)
128     {
129         $m->connect('main/poll/new',
130                     array('action' => 'newpoll'));
131
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}'));
135
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}'));
139
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}'));
143
144         return true;
145     }
146
147     /**
148      * Plugin version data
149      *
150      * @param array &$versions array of version data
151      *
152      * @return value
153      */
154
155     function onPluginVersion(&$versions)
156     {
157         $versions[] = array('name' => 'Poll',
158                             'version' => self::VERSION,
159                             'author' => 'Brion Vibber',
160                             'homepage' => 'http://status.net/wiki/Plugin:Poll',
161                             'rawdescription' =>
162                             _m('Simple extension for supporting basic polls.'));
163         return true;
164     }
165
166     function types()
167     {
168         return array(self::POLL_OBJECT, self::POLL_RESPONSE_OBJECT);
169     }
170
171     /**
172      * When a notice is deleted, delete the related Poll
173      *
174      * @param Notice $notice Notice being deleted
175      *
176      * @return boolean hook value
177      */
178
179     function deleteRelated($notice)
180     {
181         $p = Poll::getByNotice($notice);
182
183         if (!empty($p)) {
184             $p->delete();
185         }
186
187         return true;
188     }
189
190     /**
191      * Save a poll from an activity
192      *
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
196      *
197      * @return Notice resulting notice
198      */
199
200     function saveNoticeFromActivity($activity, $profile, $options=array())
201     {
202         // @fixme
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));
206
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             $elements = $activity->entry->getElementsByTagNameNS(self::POLL_OBJECT, 'data');
211             if ($elements->length) {
212                 $data = $elements->item(0);
213                 $question = $data->getAttribute('question');
214                 $opts = array();
215                 foreach ($data->attributes as $node) {
216                     $name = $node->nodeName;
217                     if (substr($name, 0, 6) == 'option') {
218                         $n = intval(substr($name, 6));
219                         if ($n > 0) {
220                             $opts[$n - 1] = $node->nodeValue;
221                         }
222                     }
223                 }
224                 common_log(LOG_DEBUG, "YYY question: $question");
225                 common_log(LOG_DEBUG, "YYY opts: " . var_export($opts, true));
226                 try {
227                     $notice = Poll::saveNew($profile, $question, $opts, $options);
228                     common_log(LOG_DEBUG, "YYY ok: " . $notice->id);
229                     return $notice;
230                 } catch (Exception $e) {
231                     common_log(LOG_DEBUG, "YYY fail: " . $e->getMessage());
232                 }
233             } else {
234                 common_log(LOG_DEBUG, "YYY no poll data");
235             }
236         }
237     }
238
239     function activityObjectFromNotice($notice)
240     {
241         assert($this->isMyNotice($notice));
242
243         $object = new ActivityObject();
244         $object->id      = $notice->uri;
245         $object->type    = self::POLL_OBJECT;
246         $object->title   = 'Poll title';
247         $object->summary = 'Poll summary';
248         $object->link    = $notice->bestUrl();
249
250         $poll = Poll::getByNotice($notice);
251         /**
252          * Adding the poll-specific data. There's no standard in AS for polls,
253          * so we're making stuff up.
254          *
255          * For the moment, using a kind of icky-looking schema that happens to
256          * work with out code for generating both Atom and JSON forms, though
257          * I don't like it:
258          *
259          * <poll:data xmlns:poll="http://apinamespace.org/activitystreams/object/poll"
260          *            question="Who wants a poll question?"
261          *            option1="Option one"
262          *            option2="Option two"
263          *            option3="Option three"></poll:data>
264          *
265          * "poll:data": {
266          *     "xmlns:poll": http://apinamespace.org/activitystreams/object/poll
267          *     "question": "Who wants a poll question?"
268          *     "option1": "Option one"
269          *     "option2": "Option two"
270          *     "option3": "Option three"
271          * }
272          *
273          */
274         // @fixme there's no way to specify an XML node tree here, like <poll><option/><option/></poll>
275         // @fixme there's no way to specify a JSON array or multi-level tree unless you break the XML attribs
276         // @fixme XML node contents don't get shown in JSON
277         $data = array('xmlns:poll' => self::POLL_OBJECT,
278                       'question'   => $poll->question);
279         foreach ($poll->getOptions() as $i => $opt) {
280             $data['option' . ($i + 1)] = $opt;
281         }
282         $object->extra[] = array('poll:data', $data, '');
283         return $object;
284     }
285
286     /**
287      * @fixme WARNING WARNING WARNING parent class closes the final div that we
288      * open here, but we probably shouldn't open it here. Check parent class
289      * and Bookmark plugin for if that's right.
290      */
291     function showNotice($notice, $out)
292     {
293         switch ($notice->object_type) {
294         case self::POLL_OBJECT:
295             return $this->showNoticePoll($notice, $out);
296         case self::POLL_RESPONSE_OBJECT:
297             return $this->showNoticePollResponse($notice, $out);
298         default:
299             throw new Exception('Unexpected type for poll plugin: ' . $notice->object_type);
300         }
301     }
302
303     function showNoticePoll($notice, $out)
304     {
305         $user = common_current_user();
306
307         // @hack we want regular rendering, then just add stuff after that
308         $nli = new NoticeListItem($notice, $out);
309         $nli->showNotice();
310
311         $out->elementStart('div', array('class' => 'entry-content poll-content'));
312         $poll = Poll::getByNotice($notice);
313         if ($poll) {
314             if ($user) {
315                 $profile = $user->getProfile();
316                 $response = $poll->getResponse($profile);
317                 if ($response) {
318                     // User has already responded; show the results.
319                     $form = new PollResultForm($poll, $out);
320                 } else {
321                     $form = new PollResponseForm($poll, $out);
322                 }
323                 $form->show();
324             }
325         } else {
326             $out->text('Poll data is missing');
327         }
328         $out->elementEnd('div');
329
330         // @fixme
331         $out->elementStart('div', array('class' => 'entry-content'));
332     }
333
334     function showNoticePollResponse($notice, $out)
335     {
336         $user = common_current_user();
337
338         // @hack we want regular rendering, then just add stuff after that
339         $nli = new NoticeListItem($notice, $out);
340         $nli->showNotice();
341
342         // @fixme
343         $out->elementStart('div', array('class' => 'entry-content'));
344     }
345
346     function entryForm($out)
347     {
348         return new NewPollForm($out);
349     }
350
351     // @fixme is this from parent?
352     function tag()
353     {
354         return 'poll';
355     }
356
357     function appTitle()
358     {
359         return _m('Poll');
360     }
361 }