]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - plugins/Poll/PollPlugin.php
Fixed type-hints.
[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://activityschema.org/object/poll';
52     const POLL_RESPONSE_OBJECT = 'http://activityschema.org/object/poll-response';
53
54     var $oldSaveNew = true;
55
56     /**
57      * Database schema setup
58      *
59      * @see Schema
60      * @see ColumnDef
61      *
62      * @return boolean hook value; true means continue processing, false means stop.
63      */
64     function onCheckSchema()
65     {
66         $schema = Schema::get();
67         $schema->ensureTable('poll', Poll::schemaDef());
68         $schema->ensureTable('poll_response', Poll_response::schemaDef());
69         $schema->ensureTable('user_poll_prefs', User_poll_prefs::schemaDef());
70         return true;
71     }
72
73     /**
74      * Show the CSS necessary for this plugin
75      *
76      * @param Action $action the action being run
77      *
78      * @return boolean hook value
79      */
80     function onEndShowStyles($action)
81     {
82         $action->cssLink($this->path('css/poll.css'));
83         return true;
84     }
85
86     /**
87      * Map URLs to actions
88      *
89      * @param URLMapper $m path-to-action mapper
90      *
91      * @return boolean hook value; true means continue processing, false means stop.
92      */
93     function onRouterInitialized(URLMapper $m)
94     {
95         $m->connect('main/poll/new',
96                     array('action' => 'newpoll'));
97
98         $m->connect('main/poll/:id',
99                     array('action' => 'showpoll'),
100                     array('id' => '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'));
101
102         $m->connect('main/poll/response/:id',
103                     array('action' => 'showpollresponse'),
104                     array('id' => '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'));
105
106         $m->connect('main/poll/:id/respond',
107                     array('action' => 'respondpoll'),
108                     array('id' => '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'));
109
110         $m->connect('settings/poll',
111                     array('action' => 'pollsettings'));
112
113         return true;
114     }
115
116     /**
117      * Plugin version data
118      *
119      * @param array &$versions array of version data
120      *
121      * @return value
122      */
123     function onPluginVersion(array &$versions)
124     {
125         $versions[] = array('name' => 'Poll',
126                             'version' => self::VERSION,
127                             'author' => 'Brion Vibber',
128                             'homepage' => 'http://status.net/wiki/Plugin:Poll',
129                             'rawdescription' =>
130                             // TRANS: Plugin description.
131                             _m('Simple extension for supporting basic polls.'));
132         return true;
133     }
134
135     function types()
136     {
137         return array(self::POLL_OBJECT, self::POLL_RESPONSE_OBJECT);
138     }
139
140
141     function adaptNoticeListItem($nli) {
142         return new PollListItem($nli);
143     }
144
145     /**
146      * When a notice is deleted, delete the related Poll
147      *
148      * @param Notice $notice Notice being deleted
149      *
150      * @return boolean hook value
151      */
152     function deleteRelated(Notice $notice)
153     {
154         $p = Poll::getByNotice($notice);
155
156         if (!empty($p)) {
157             $p->delete();
158         }
159
160         return true;
161     }
162
163     /**
164      * Save a poll from an activity
165      *
166      * @param Profile  $profile  Profile to use as author
167      * @param Activity $activity Activity to save
168      * @param array    $options  Options to pass to bookmark-saving code
169      *
170      * @return Notice resulting notice
171      */
172     function saveNoticeFromActivity(Activity $activity, Profile $profile, array $options=array())
173     {
174         // @fixme
175         common_log(LOG_DEBUG, "XXX activity: " . var_export($activity, true));
176         common_log(LOG_DEBUG, "XXX profile: " . var_export($profile, true));
177         common_log(LOG_DEBUG, "XXX options: " . var_export($options, true));
178
179         // Ok for now, we can grab stuff from the XML entry directly.
180         // This won't work when reading from JSON source
181         if ($activity->entry) {
182             $pollElements = $activity->entry->getElementsByTagNameNS(self::POLL_OBJECT, 'poll');
183             $responseElements = $activity->entry->getElementsByTagNameNS(self::POLL_OBJECT, 'response');
184             if ($pollElements->length) {
185                 $question = '';
186                 $opts = array();
187
188                 $data = $pollElements->item(0);
189                 foreach ($data->getElementsByTagNameNS(self::POLL_OBJECT, 'question') as $node) {
190                     $question = $node->textContent;
191                 }
192                 foreach ($data->getElementsByTagNameNS(self::POLL_OBJECT, 'option') as $node) {
193                     $opts[] = $node->textContent;
194                 }
195                 try {
196                     $notice = Poll::saveNew($profile, $question, $opts, $options);
197                     common_log(LOG_DEBUG, "Saved Poll from ActivityStream data ok: notice id " . $notice->id);
198                     return $notice;
199                 } catch (Exception $e) {
200                     common_log(LOG_DEBUG, "Poll save from ActivityStream data failed: " . $e->getMessage());
201                 }
202             } else if ($responseElements->length) {
203                 $data = $responseElements->item(0);
204                 $pollUri = $data->getAttribute('poll');
205                 $selection = intval($data->getAttribute('selection'));
206
207                 if (!$pollUri) {
208                     // TRANS: Exception thrown trying to respond to a poll without a poll reference.
209                     throw new Exception(_m('Invalid poll response: No poll reference.'));
210                 }
211                 $poll = Poll::getKV('uri', $pollUri);
212                 if (!$poll) {
213                     // TRANS: Exception thrown trying to respond to a non-existing poll.
214                     throw new Exception(_m('Invalid poll response: Poll is unknown.'));
215                 }
216                 try {
217                     $notice = Poll_response::saveNew($profile, $poll, $selection, $options);
218                     common_log(LOG_DEBUG, "Saved Poll_response ok, notice id: " . $notice->id);
219                     return $notice;
220                 } catch (Exception $e) {
221                     common_log(LOG_DEBUG, "Poll response  save fail: " . $e->getMessage());
222                 }
223             } else {
224                 common_log(LOG_DEBUG, "YYY no poll data");
225             }
226         }
227     }
228
229     function activityObjectFromNotice(Notice $notice)
230     {
231         assert($this->isMyNotice($notice));
232
233         switch ($notice->object_type) {
234         case self::POLL_OBJECT:
235             return $this->activityObjectFromNoticePoll($notice);
236         case self::POLL_RESPONSE_OBJECT:
237             return $this->activityObjectFromNoticePollResponse($notice);
238         default:
239             // TRANS: Exception thrown when performing an unexpected action on a poll.
240             // TRANS: %s is the unexpected object type.
241             throw new Exception(sprintf(_m('Unexpected type for poll plugin: %s.'), $notice->object_type));
242         }
243     }
244
245     function activityObjectFromNoticePollResponse(Notice $notice)
246     {
247         $object = new ActivityObject();
248         $object->id      = $notice->uri;
249         $object->type    = self::POLL_RESPONSE_OBJECT;
250         $object->title   = $notice->content;
251         $object->summary = $notice->content;
252         $object->link    = $notice->getUrl();
253
254         $response = Poll_response::getByNotice($notice);
255         if ($response) {
256             $poll = $response->getPoll();
257             if ($poll) {
258                 // Stash data to be formatted later by
259                 // $this->activityObjectOutputAtom() or
260                 // $this->activityObjectOutputJson()...
261                 $object->pollSelection = intval($response->selection);
262                 $object->pollUri = $poll->uri;
263             }
264         }
265         return $object;
266     }
267
268     function activityObjectFromNoticePoll(Notice $notice)
269     {
270         $object = new ActivityObject();
271         $object->id      = $notice->uri;
272         $object->type    = self::POLL_OBJECT;
273         $object->title   = $notice->content;
274         $object->summary = $notice->content;
275         $object->link    = $notice->getUrl();
276
277         $poll = Poll::getByNotice($notice);
278         if ($poll) {
279             // Stash data to be formatted later by
280             // $this->activityObjectOutputAtom() or
281             // $this->activityObjectOutputJson()...
282             $object->pollQuestion = $poll->question;
283             $object->pollOptions = $poll->getOptions();
284         }
285
286         return $object;
287     }
288
289     /**
290      * Called when generating Atom XML ActivityStreams output from an
291      * ActivityObject belonging to this plugin. Gives the plugin
292      * a chance to add custom output.
293      *
294      * Note that you can only add output of additional XML elements,
295      * not change existing stuff here.
296      *
297      * If output is already handled by the base Activity classes,
298      * you can leave this base implementation as a no-op.
299      *
300      * @param ActivityObject $obj
301      * @param XMLOutputter $out to add elements at end of object
302      */
303     function activityObjectOutputAtom(ActivityObject $obj, XMLOutputter $out)
304     {
305         if (isset($obj->pollQuestion)) {
306             /**
307              * <poll:poll xmlns:poll="http://apinamespace.org/activitystreams/object/poll">
308              *   <poll:question>Who wants a poll question?</poll:question>
309              *   <poll:option>Option one</poll:option>
310              *   <poll:option>Option two</poll:option>
311              *   <poll:option>Option three</poll:option>
312              * </poll:poll>
313              */
314             $data = array('xmlns:poll' => self::POLL_OBJECT);
315             $out->elementStart('poll:poll', $data);
316             $out->element('poll:question', array(), $obj->pollQuestion);
317             foreach ($obj->pollOptions as $opt) {
318                 $out->element('poll:option', array(), $opt);
319             }
320             $out->elementEnd('poll:poll');
321         }
322         if (isset($obj->pollSelection)) {
323             /**
324              * <poll:response xmlns:poll="http://apinamespace.org/activitystreams/object/poll">
325              *                poll="http://..../poll/...."
326              *                selection="3" />
327              */
328             $data = array('xmlns:poll' => self::POLL_OBJECT,
329                           'poll'       => $obj->pollUri,
330                           'selection'  => $obj->pollSelection);
331             $out->element('poll:response', $data, '');
332         }
333     }
334
335     /**
336      * Called when generating JSON ActivityStreams output from an
337      * ActivityObject belonging to this plugin. Gives the plugin
338      * a chance to add custom output.
339      *
340      * Modify the array contents to your heart's content, and it'll
341      * all get serialized out as JSON.
342      *
343      * If output is already handled by the base Activity classes,
344      * you can leave this base implementation as a no-op.
345      *
346      * @param ActivityObject $obj
347      * @param array &$out JSON-targeted array which can be modified
348      */
349     public function activityObjectOutputJson(ActivityObject $obj, array &$out)
350     {
351         common_log(LOG_DEBUG, 'QQQ: ' . var_export($obj, true));
352         if (isset($obj->pollQuestion)) {
353             /**
354              * "poll": {
355              *   "question": "Who wants a poll question?",
356              *   "options": [
357              *     "Option 1",
358              *     "Option 2",
359              *     "Option 3"
360              *   ]
361              * }
362              */
363             $data = array('question' => $obj->pollQuestion,
364                           'options' => array());
365             foreach ($obj->pollOptions as $opt) {
366                 $data['options'][] = $opt;
367             }
368             $out['poll'] = $data;
369         }
370         if (isset($obj->pollSelection)) {
371             /**
372              * "pollResponse": {
373              *   "poll": "http://..../poll/....",
374              *   "selection": 3
375              * }
376              */
377             $data = array('poll'       => $obj->pollUri,
378                           'selection'  => $obj->pollSelection);
379             $out['pollResponse'] = $data;
380         }
381     }
382
383     function entryForm($out)
384     {
385         return new NewPollForm($out);
386     }
387
388     // @fixme is this from parent?
389     function tag()
390     {
391         return 'poll';
392     }
393
394     function appTitle()
395     {
396         // TRANS: Application title.
397         return _m('APPTITLE','Poll');
398     }
399
400     function onStartAddNoticeReply($nli, $parent, $child)
401     {
402         // Filter out any poll responses
403         if ($parent->object_type == self::POLL_OBJECT &&
404             $child->object_type == self::POLL_RESPONSE_OBJECT) {
405             return false;
406         }
407         return true;
408     }
409
410     // Hide poll responses for @chuck
411
412     function onEndNoticeWhoGets($notice, &$ni) {
413         if ($notice->object_type == self::POLL_RESPONSE_OBJECT) {
414             foreach ($ni as $id => $source) {
415                 $user = User::getKV('id', $id);
416                 if (!empty($user)) {
417                     $pollPrefs = User_poll_prefs::getKV('user_id', $user->id);
418                     if (!empty($pollPrefs) && ($pollPrefs->hide_responses)) {
419                         unset($ni[$id]);
420                     }
421                 }
422             }
423         }
424         return true;
425     }
426
427     /**
428      * Menu item for personal subscriptions/groups area
429      *
430      * @param Action $action action being executed
431      *
432      * @return boolean hook return
433      */
434
435     function onEndAccountSettingsNav($action)
436     {
437         $action_name = $action->trimmed('action');
438
439         $action->menuItem(common_local_url('pollsettings'),
440                           // TRANS: Poll plugin menu item on user settings page.
441                           _m('MENU', 'Polls'),
442                           // TRANS: Poll plugin tooltip for user settings menu item.
443                           _m('Configure poll behavior'),
444                           $action_name === 'pollsettings');
445
446         return true;
447     }
448 }