]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - plugins/Poll/PollPlugin.php
Work in progress: fixing Poll posting URLs, working on AS input
[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     const POLL_OBJECT     = 'http://apinamespace.org/activitystreams/object/poll';
51
52     /**
53      * Database schema setup
54      *
55      * @see Schema
56      * @see ColumnDef
57      *
58      * @return boolean hook value; true means continue processing, false means stop.
59      */
60
61     function onCheckSchema()
62     {
63         $schema = Schema::get();
64         $schema->ensureTable('poll', Poll::schemaDef());
65         $schema->ensureTable('poll_response', Poll_response::schemaDef());
66         return true;
67     }
68
69     /**
70      * Show the CSS necessary for this plugin
71      *
72      * @param Action $action the action being run
73      *
74      * @return boolean hook value
75      */
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
91     function onAutoload($cls)
92     {
93         $dir = dirname(__FILE__);
94
95         switch ($cls)
96         {
97         case 'ShowpollAction':
98         case 'NewpollAction':
99         case 'RespondpollAction':
100             include_once $dir . '/' . strtolower(mb_substr($cls, 0, -6)) . '.php';
101             return false;
102         case 'Poll':
103         case 'Poll_response':
104             include_once $dir.'/'.$cls.'.php';
105             return false;
106         case 'NewPollForm':
107         case 'PollResponseForm':
108         case 'PollResultForm':
109             include_once $dir.'/'.strtolower($cls).'.php';
110             return false;
111         default:
112             return true;
113         }
114     }
115
116     /**
117      * Map URLs to actions
118      *
119      * @param Net_URL_Mapper $m path-to-action mapper
120      *
121      * @return boolean hook value; true means continue processing, false means stop.
122      */
123
124     function onRouterInitialized($m)
125     {
126         $m->connect('main/poll/new',
127                     array('action' => 'newpoll'));
128
129         $m->connect('main/poll/:id',
130                     array('action' => 'showpoll'),
131                     array('id' => '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'));
132
133         $m->connect('main/poll/:id/respond',
134                     array('action' => 'respondpoll'),
135                     array('id' => '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'));
136
137         return true;
138     }
139
140     /**
141      * Plugin version data
142      *
143      * @param array &$versions array of version data
144      *
145      * @return value
146      */
147
148     function onPluginVersion(&$versions)
149     {
150         $versions[] = array('name' => 'Poll',
151                             'version' => self::VERSION,
152                             'author' => 'Brion Vibber',
153                             'homepage' => 'http://status.net/wiki/Plugin:Poll',
154                             'rawdescription' =>
155                             _m('Simple extension for supporting basic polls.'));
156         return true;
157     }
158
159     function types()
160     {
161         return array(self::POLL_OBJECT);
162     }
163
164     /**
165      * When a notice is deleted, delete the related Poll
166      *
167      * @param Notice $notice Notice being deleted
168      *
169      * @return boolean hook value
170      */
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
193     function saveNoticeFromActivity($activity, $profile, $options=array())
194     {
195         // @fixme
196         common_log(LOG_DEBUG, "XXX activity: " . var_export($activity, 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             $elements = $activity->entry->getElementsByTagNameNS(self::POLL_OBJECT, 'data');
203             if ($elements->length) {
204                 $data = $elements->item(0);
205                 $question = $data->getAttribute('question');
206                 $opts = array();
207                 foreach ($data->attributes as $node) {
208                     $name = $node->nodeName;
209                     if (substr($name, 0, 6) == 'option') {
210                         $n = intval(substr($name, 6));
211                         if ($n > 0) {
212                             $opts[$n - 1] = $node->nodeValue;
213                         }
214                     }
215                 }
216                 common_log(LOG_DEBUG, "YYY question: $question");
217                 common_log(LOG_DEBUG, "YYY opts: " . var_export($opts, true));
218             } else {
219                 common_log(LOG_DEBUG, "YYY no poll data");
220             }
221         }
222     }
223
224     function activityObjectFromNotice($notice)
225     {
226         assert($this->isMyNotice($notice));
227
228         $object = new ActivityObject();
229         $object->id      = $notice->uri;
230         $object->type    = self::POLL_OBJECT;
231         $object->title   = 'Poll title';
232         $object->summary = 'Poll summary';
233         $object->link    = $notice->bestUrl();
234
235         $poll = Poll::getByNotice($notice);
236         /**
237          * Adding the poll-specific data. There's no standard in AS for polls,
238          * so we're making stuff up.
239          *
240          * For the moment, using a kind of icky-looking schema that happens to
241          * work with out code for generating both Atom and JSON forms, though
242          * I don't like it:
243          *
244          * <poll:data xmlns:poll="http://apinamespace.org/activitystreams/object/poll"
245          *            question="Who wants a poll question?"
246          *            option1="Option one"
247          *            option2="Option two"
248          *            option3="Option three"></poll:data>
249          *
250          * "poll:data": {
251          *     "xmlns:poll": http://apinamespace.org/activitystreams/object/poll
252          *     "question": "Who wants a poll question?"
253          *     "option1": "Option one"
254          *     "option2": "Option two"
255          *     "option3": "Option three"
256          * }
257          *
258          */
259         // @fixme there's no way to specify an XML node tree here, like <poll><option/><option/></poll>
260         // @fixme there's no way to specify a JSON array or multi-level tree unless you break the XML attribs
261         // @fixme XML node contents don't get shown in JSON
262         $data = array('xmlns:poll' => self::POLL_OBJECT,
263                       'question'   => $poll->question);
264         foreach ($poll->getOptions() as $i => $opt) {
265             $data['option' . ($i + 1)] = $opt;
266         }
267         $object->extra[] = array('poll:data', $data, '');
268         return $object;
269     }
270
271     /**
272      * @fixme WARNING WARNING WARNING parent class closes the final div that we
273      * open here, but we probably shouldn't open it here. Check parent class
274      * and Bookmark plugin for if that's right.
275      */
276     function showNotice($notice, $out)
277     {
278         $user = common_current_user();
279
280         // @hack we want regular rendering, then just add stuff after that
281         $nli = new NoticeListItem($notice, $out);
282         $nli->showNotice();
283
284         $out->elementStart('div', array('class' => 'entry-content poll-content'));
285         $poll = Poll::getByNotice($notice);
286         if ($poll) {
287             if ($user) {
288                 $profile = $user->getProfile();
289                 $response = $poll->getResponse($profile);
290                 if ($response) {
291                     // User has already responded; show the results.
292                     $form = new PollResultForm($poll, $out);
293                 } else {
294                     $form = new PollResponseForm($poll, $out);
295                 }
296                 $form->show();
297             }
298         } else {
299             $out->text('Poll data is missing');
300         }
301         $out->elementEnd('div');
302
303         // @fixme
304         $out->elementStart('div', array('class' => 'entry-content'));
305     }
306
307     function entryForm($out)
308     {
309         return new NewPollForm($out);
310     }
311
312     // @fixme is this from parent?
313     function tag()
314     {
315         return 'poll';
316     }
317
318     function appTitle()
319     {
320         return _m('Poll');
321     }
322 }