]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - plugins/Event/EventPlugin.php
add url to events
[quix0rs-gnu-social.git] / plugins / Event / EventPlugin.php
1 <?php
2 /**
3  * StatusNet - the distributed open-source microblogging tool
4  * Copyright (C) 2011, StatusNet, Inc.
5  *
6  * Microapp plugin for event invitations and RSVPs
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  Event
24  * @package   StatusNet
25  * @author    Evan Prodromou <evan@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     // This check helps protect against security problems;
33     // your code file can't be executed directly from the web.
34     exit(1);
35 }
36
37 /**
38  * Event plugin
39  *
40  * @category  Sample
41  * @package   StatusNet
42  * @author    Evan Prodromou <evan@status.net>
43  * @copyright 2011 StatusNet, Inc.
44  * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
45  * @link      http://status.net/
46  */
47 class EventPlugin extends MicroappPlugin
48 {
49     /**
50      * Set up our tables (event and rsvp)
51      *
52      * @see Schema
53      * @see ColumnDef
54      *
55      * @return boolean hook value; true means continue processing, false means stop.
56      */
57     function onCheckSchema()
58     {
59         $schema = Schema::get();
60
61         $schema->ensureTable('happening', Happening::schemaDef());
62         $schema->ensureTable('rsvp', RSVP::schemaDef());
63
64         return true;
65     }
66
67     /**
68      * Load related modules when needed
69      *
70      * @param string $cls Name of the class to be loaded
71      *
72      * @return boolean hook value; true means continue processing, false means stop.
73      */
74     function onAutoload($cls)
75     {
76         $dir = dirname(__FILE__);
77
78         switch ($cls)
79         {
80         case 'NeweventAction':
81         case 'NewrsvpAction':
82         case 'ShoweventAction':
83         case 'ShowrsvpAction':
84             include_once $dir . '/' . strtolower(mb_substr($cls, 0, -6)) . '.php';
85             return false;
86         case 'EventForm':
87             include_once $dir . '/'.strtolower($cls).'.php';
88             break;
89         case 'Happening':
90         case 'RSVP':
91             include_once $dir . '/'.$cls.'.php';
92             return false;
93         default:
94             return true;
95         }
96     }
97
98     /**
99      * Map URLs to actions
100      *
101      * @param Net_URL_Mapper $m path-to-action mapper
102      *
103      * @return boolean hook value; true means continue processing, false means stop.
104      */
105
106     function onRouterInitialized($m)
107     {
108         $m->connect('main/event/new',
109                     array('action' => 'newevent'));
110         $m->connect('main/event/rsvp',
111                     array('action' => 'newrsvp'));
112         $m->connect('event/:id',
113                     array('action' => 'showevent'),
114                     array('id' => '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'));
115         $m->connect('rsvp/:id',
116                     array('action' => 'showrsvp'),
117                     array('id' => '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'));
118         return true;
119     }
120
121     function onPluginVersion(&$versions)
122     {
123         $versions[] = array('name' => 'Event',
124                             'version' => STATUSNET_VERSION,
125                             'author' => 'Evan Prodromou',
126                             'homepage' => 'http://status.net/wiki/Plugin:Event',
127                             'description' =>
128                             _m('Event invitations and RSVPs.'));
129         return true;
130     }
131
132     function appTitle() {
133         return _m('Event');
134     }
135
136     function tag() {
137         return 'event';
138     }
139
140     function types() {
141         return array(Happening::OBJECT_TYPE,
142                      RSVP::POSITIVE,
143                      RSVP::NEGATIVE,
144                      RSVP::POSSIBLE);
145     }
146
147     /**
148      * Given a parsed ActivityStreams activity, save it into a notice
149      * and other data structures.
150      *
151      * @param Activity $activity
152      * @param Profile $actor
153      * @param array $options=array()
154      *
155      * @return Notice the resulting notice
156      */
157     function saveNoticeFromActivity($activity, $actor, $options=array())
158     {
159         if (count($activity->objects) != 1) {
160             throw new Exception('Too many activity objects.');
161         }
162
163         $happeningObj = $activity->objects[0];
164
165         if ($happeningObj->type != Happening::OBJECT_TYPE) {
166             throw new Exception('Wrong type for object.');
167         }
168
169         $notice = null;
170
171         switch ($activity->verb) {
172         case ActivityVerb::POST:
173             $notice = Happening::saveNew($actor, 
174                                      $start_time, 
175                                      $end_time,
176                                      $happeningObj->title,
177                                      null,
178                                      $happeningObj->summary,
179                                      $options);
180             break;
181         case RSVP::POSITIVE:
182         case RSVP::NEGATIVE:
183         case RSVP::POSSIBLE:
184             $happening = Happening::staticGet('uri', $happeningObj->id);
185             if (empty($happening)) {
186                 // FIXME: save the event
187                 throw new Exception("RSVP for unknown event.");
188             }
189             $notice = RSVP::saveNew($actor, $happening, $activity->verb, $options);
190             break;
191         default:
192             throw new Exception("Unknown verb for events");
193         }
194
195         return $notice;
196     }
197
198     /**
199      * Turn a Notice into an activity object
200      *
201      * @param Notice $notice
202      *
203      * @return ActivityObject
204      */
205
206     function activityObjectFromNotice($notice)
207     {
208         $happening = null;
209
210         switch ($notice->object_type) {
211         case Happening::OBJECT_TYPE:
212             $happening = Happening::fromNotice($notice);
213             break;
214         case RSVP::POSITIVE:
215         case RSVP::NEGATIVE:
216         case RSVP::POSSIBLE:
217             $rsvp  = RSVP::fromNotice($notice);
218             $happening = $rsvp->getEvent();
219             break;
220         }
221
222         if (empty($happening)) {
223             throw new Exception("Unknown object type.");
224         }
225
226         $notice = $happening->getNotice();
227
228         if (empty($notice)) {
229             throw new Exception("Unknown event notice.");
230         }
231
232         $obj = new ActivityObject();
233
234         $obj->id      = $happening->uri;
235         $obj->type    = Happening::OBJECT_TYPE;
236         $obj->title   = $happening->title;
237         $obj->summary = $happening->description;
238         $obj->link    = $notice->bestUrl();
239
240         // XXX: how to get this stuff into JSON?!
241
242         $obj->extra[] = array('dtstart',
243                               array('xmlns' => 'urn:ietf:params:xml:ns:xcal'),
244                               common_date_iso8601($happening->start_date));
245
246         $obj->extra[] = array('dtend',
247                               array('xmlns' => 'urn:ietf:params:xml:ns:xcal'),
248                               common_date_iso8601($happening->end_date));
249
250         // XXX: probably need other stuff here
251
252         return $obj;
253     }
254
255     /**
256      * Change the verb on RSVP notices
257      *
258      * @param Notice $notice
259      *
260      * @return ActivityObject
261      */
262
263     function onEndNoticeAsActivity($notice, &$act) {
264         switch ($notice->object_type) {
265         case RSVP::POSITIVE:
266         case RSVP::NEGATIVE:
267         case RSVP::POSSIBLE:
268             $act->verb = $notice->object_type;
269             break;
270         }
271         return true;
272     }
273
274     /**
275      * Custom HTML output for our notices
276      *
277      * @param Notice $notice
278      * @param HTMLOutputter $out
279      */
280
281     function showNotice($notice, $out)
282     {
283         switch ($notice->object_type) {
284         case Happening::OBJECT_TYPE:
285             $this->showEventNotice($notice, $out);
286             break;
287         case RSVP::POSITIVE:
288         case RSVP::NEGATIVE:
289         case RSVP::POSSIBLE:
290             $this->showRSVPNotice($notice, $out);
291             break;
292         }
293     }
294
295     function showRSVPNotice($notice, $out)
296     {
297         $out->element('span', null, 'RSVP');
298         return;
299     }
300
301     function showEventNotice($notice, $out)
302     {
303         $profile = $notice->getProfile();
304         $event   = Happening::fromNotice($notice);
305
306         assert(!empty($event));
307         assert(!empty($profile));
308
309         $out->elementStart('div', 'vevent');
310
311         $out->elementStart('h3');
312
313         if (!empty($event->url)) {
314             $out->element('a',
315                           array('href' => $att->url,
316                                 'class' => 'event-title entry-title summary'),
317                           $event->title);
318         } else {
319             $out->text($event->title);
320         }
321
322         $out->elementEnd('h3');
323
324         $out->elementStart('div', 'event-times');
325         $out->element('abbr', array('class' => 'dtstart',
326                                     'title' => common_date_iso8601($event->start_time)),
327                       common_exact_date($event->start_time));
328         $out->text(' - ');
329         $out->element('span', array('class' => 'dtend',
330                                     'title' => common_date_iso8601($event->end_time)),
331                       common_exact_date($event->end_time));
332         $out->elementEnd('div');
333
334         if (!empty($event->description)) {
335             $out->element('div', 'description', $event->description);
336         }
337
338         if (!empty($event->location)) {
339             $out->element('div', 'location', $event->location);
340         }
341
342         $out->elementStart('div', array('class' => 'event-info entry-content'));
343
344         $avatar = $profile->getAvatar(AVATAR_MINI_SIZE);
345
346         $out->element('img', 
347                       array('src' => ($avatar) ?
348                             $avatar->displayUrl() :
349                             Avatar::defaultImage(AVATAR_MINI_SIZE),
350                             'class' => 'avatar photo bookmark-avatar',
351                             'width' => AVATAR_MINI_SIZE,
352                             'height' => AVATAR_MINI_SIZE,
353                             'alt' => $profile->getBestName()));
354
355         $out->raw('&#160;'); // avoid &nbsp; for AJAX XML compatibility
356
357         $out->elementStart('span', 'vcard author'); // hack for belongsOnTimeline; JS needs to be able to find the author
358         $out->element('a', 
359                       array('class' => 'url',
360                             'href' => $profile->profileurl,
361                             'title' => $profile->getBestName()),
362                       $profile->nickname);
363         $out->elementEnd('span');
364
365         $out->elementEnd('div');
366     }
367
368     /**
369      * Form for our app
370      *
371      * @param HTMLOutputter $out
372      * @return Widget
373      */
374
375     function entryForm($out)
376     {
377         return new EventForm($out);
378     }
379
380     /**
381      * When a notice is deleted, clean up related tables.
382      *
383      * @param Notice $notice
384      */
385
386     function deleteRelated($notice)
387     {
388         switch ($notice->object_type) {
389         case Happening::OBJECT_TYPE:
390             $happening = Happening::fromNotice($notice);
391             $happening->delete();
392             break;
393         case RSVP::POSITIVE:
394         case RSVP::NEGATIVE:
395         case RSVP::POSSIBLE:
396             $rsvp = RSVP::fromNotice($notice);
397             $rsvp->delete();
398             break;
399         }
400     }
401 }