]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - plugins/Event/EventPlugin.php
ed78b156e1f7d20d4427407068bed4d35730dd9c
[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('GNUSOCIAL')) { exit(1); }
32
33 /**
34  * Event plugin
35  *
36  * @category  Event
37  * @package   StatusNet
38  * @author    Evan Prodromou <evan@status.net>
39  * @copyright 2011 StatusNet, Inc.
40  * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
41  * @link      http://status.net/
42  */
43 class EventPlugin extends ActivityVerbHandlerPlugin
44 {
45     /**
46      * Set up our tables (event and rsvp)
47      *
48      * @see Schema
49      * @see ColumnDef
50      *
51      * @return boolean hook value; true means continue processing, false means stop.
52      */
53     function onCheckSchema()
54     {
55         $schema = Schema::get();
56
57         $schema->ensureTable('happening', Happening::schemaDef());
58         $schema->ensureTable('rsvp', RSVP::schemaDef());
59
60         return true;
61     }
62
63     public function onBeforePluginCheckSchema()
64     {
65         RSVP::beforeSchemaUpdate();
66         return true;
67     }
68
69     /**
70      * Map URLs to actions
71      *
72      * @param URLMapper $m path-to-action mapper
73      *
74      * @return boolean hook value; true means continue processing, false means stop.
75      */
76     public function onRouterInitialized(URLMapper $m)
77     {
78         $m->connect('main/event/new',
79                     array('action' => 'newevent'));
80         $m->connect('main/event/rsvp',
81                     array('action' => 'rsvp'));
82         $m->connect('main/event/rsvp/:rsvp',    // this will probably change to include event notice id
83                     array('action' => 'rsvp'),
84                     array('rsvp'   => '[a-z]+'));
85         $m->connect('event/:id',
86                     array('action' => 'showevent'),
87                     array('id' => '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'));
88         $m->connect('rsvp/:id',
89                     array('action' => 'showrsvp'),
90                     array('id' => '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'));
91         $m->connect('main/event/updatetimes',
92                     array('action' => 'timelist'));
93
94         $m->connect(':nickname/events',
95                     array('action' => 'events'),
96                     array('nickname' => Nickname::DISPLAY_FMT));
97         return true;
98     }
99
100     function onPluginVersion(array &$versions)
101     {
102         $versions[] = array('name' => 'Event',
103                             'version' => GNUSOCIAL_VERSION,
104                             'author' => 'Evan Prodromou',
105                             'homepage' => 'http://status.net/wiki/Plugin:Event',
106                             'description' =>
107                             // TRANS: Plugin description.
108                             _m('Event invitations and RSVPs.'));
109         return true;
110     }
111
112     function appTitle() {
113         // TRANS: Title for event application.
114         return _m('TITLE','Event');
115     }
116
117     function tag() {
118         return 'event';
119     }
120
121     function types() {
122         return array(Happening::OBJECT_TYPE);
123     }
124
125     function verbs() {
126         return array(ActivityVerb::POST,
127                      RSVP::POSITIVE,
128                      RSVP::NEGATIVE,
129                      RSVP::POSSIBLE);
130     }
131
132     function isMyNotice(Notice $notice) {
133         if (!empty($notice->object_type)) {
134             return parent::isMyNotice($notice);
135         }
136         return $this->isMyVerb($notice->verb);
137     }
138
139     public function newFormAction() {
140         // such as 'newbookmark' or 'newevent' route
141         return 'new'.$this->tag();
142     }
143
144     function onStartShowEntryForms(&$tabs)
145     {
146         $tabs[$this->tag()] = array('title' => $this->appTitle(),
147                                     'href'  => common_local_url($this->newFormAction()),
148                                    );
149         return true;
150     }
151
152     function onStartMakeEntryForm($tag, $out, &$form)
153     {
154         if ($tag == $this->tag()) {
155             $form = $this->entryForm($out);
156             return false;
157         }
158
159         return true;
160     }
161
162     protected function getActionTitle(ManagedAction $action, $verb, Notice $target, Profile $scoped)
163     {
164         return $verb;
165     }
166
167     protected function doActionPreparation(ManagedAction $action, $verb, Notice $target, Profile $scoped)
168     {
169         return true;
170     }
171
172     protected function doActionPost(ManagedAction $action, $verb, Notice $target, Profile $scoped)
173     {
174         throw new ServerException('Event does not handle doActionPost yet', 501);
175     }
176
177     protected function getActivityForm(ManagedAction $action, $verb, Notice $target, Profile $scoped)
178     {
179         return new RSVPForm(Happening::fromStored($target), $action);
180     }
181
182     protected function saveObjectFromActivity(Activity $act, Notice $stored, array $options=array())
183     {
184         switch (true) {
185         case ActivityUtils::compareVerbs($stored->getVerb(), [ActivityVerb::POST]):
186             return Happening::saveActivityObject($act, $stored);
187             break;
188
189         case ActivityUtils::compareVerbs($stored->getVerb(), [RSVP::POSITIVE, RSVP::NEGATIVE, RSVP::POSSIBLE]):
190             return RSVP::saveActivityObject($act, $stored);
191             break;
192         }
193         return null;
194     }
195
196     function activityObjectFromNotice(Notice $stored)
197     {
198         $happening = null;
199
200         switch (true) {
201         case $stored->isVerb([ActivityVerb::POST]) && $stored->isObjectType([Happening::OBJECT_TYPE]):
202             $obj = Happening::fromStored($stored)->asActivityObject();
203             break;
204         // isObjectType here is because we had the verb stored in object_type previously for unknown reasons
205         case $stored->isObjectType([RSVP::POSITIVE, RSVP::NEGATIVE, RSVP::POSSIBLE]):
206         case $stored->isVerb([RSVP::POSITIVE, RSVP::NEGATIVE, RSVP::POSSIBLE]):
207             $obj = RSVP::fromStored($stored)->asActivityObject();
208             break;
209         default:
210             // TRANS: Exception thrown when event plugin comes across a unknown object type.
211             throw new Exception(_m('Unknown object type.'));
212         }
213
214         return $obj;
215     }
216
217     /**
218      * Form for our app
219      *
220      * @param HTMLOutputter $out
221      * @return Widget
222      */
223     function entryForm($out)
224     {
225         return new EventForm($out);
226     }
227
228     function deleteRelated(Notice $stored)
229     {
230         switch (true) {
231         case $stored->isObjectType([Happening::OBJECT_TYPE]):
232             common_log(LOG_DEBUG, "Deleting event from notice...");
233             try {
234                 $happening = Happening::fromStored($stored);
235                 $happening->delete();
236             } catch (NoResultException $e) {
237                 // already gone
238             }
239             break;
240         // isObjectType here is because we had the verb stored in object_type previously for unknown reasons
241         case $stored->isObjectType([RSVP::POSITIVE, RSVP::NEGATIVE, RSVP::POSSIBLE]):
242         case $stored->isVerb([RSVP::POSITIVE, RSVP::NEGATIVE, RSVP::POSSIBLE]):
243             common_log(LOG_DEBUG, "Deleting rsvp from notice...");
244             try {
245                 $rsvp = RSVP::fromStored($stored);
246                 $rsvp->delete();
247             } catch (NoResultException $e) {
248                 // already gone
249             }
250             break;
251         }
252     }
253
254     function onEndShowScripts($action)
255     {
256         $action->script($this->path('js/event.js'));
257     }
258
259     function onEndShowStyles($action)
260     {
261         $action->cssLink($this->path('css/event.css'));
262         return true;
263     }
264
265     function onStartAddNoticeReply($nli, $parent, $child)
266     {
267         if (($parent->object_type == Happening::OBJECT_TYPE) &&
268             in_array($child->object_type, array(RSVP::POSITIVE, RSVP::NEGATIVE, RSVP::POSSIBLE))) {
269             return false;
270         }
271         return true;
272     }
273
274     protected function showNoticeContent(Notice $stored, HTMLOutputter $out, Profile $scoped=null)
275     {
276         switch (true) {
277         case ActivityUtils::compareTypes($stored->verb, array(ActivityVerb::POST)) &&
278                 ActivityUtils::compareTypes($stored->object_type, array(Happening::OBJECT_TYPE)):
279             $this->showEvent($stored, $out, $scoped);
280             break;
281         case ActivityUtils::compareVerbs($stored->verb, array(RSVP::POSITIVE, RSVP::NEGATIVE, RSVP::POSSIBLE)):
282             $this->showRSVP($stored, $out, $scoped);
283             break;
284         default:
285             throw new ServerException('This is not an Event notice');
286         }
287         return true;
288     }
289
290     protected function showEvent(Notice $stored, HTMLOutputter $out, Profile $scoped=null)
291     {
292         common_debug('shownotice'.$stored->getID());
293         $profile = $stored->getProfile();
294         $event   = Happening::fromStored($stored);
295
296         $out->elementStart('div', 'h-event');
297
298         $out->elementStart('h3', 'p-summary p-name');
299
300         try {
301             $out->element('a', array('href' => $event->getUrl()), $event->title);
302         } catch (InvalidUrlException $e) {
303             $out->text($event->title);
304         }
305
306         $out->elementEnd('h3');
307
308         $now       = new DateTime();
309         $startDate = new DateTime($event->start_time);
310         $endDate   = new DateTime($event->end_time);
311         $userTz    = new DateTimeZone(common_timezone());
312
313         // Localize the time for the observer
314         $now->setTimeZone($userTz);
315         $startDate->setTimezone($userTz);
316         $endDate->setTimezone($userTz);
317
318         $thisYear  = $now->format('Y');
319         $startYear = $startDate->format('Y');
320         $endYear   = $endDate->format('Y');
321
322         $dateFmt = 'D, F j, '; // e.g.: Mon, Aug 31
323
324         if ($startYear != $thisYear || $endYear != $thisYear) {
325             $dateFmt .= 'Y,'; // append year if we need to think about years
326         }
327
328         $startDateStr = $startDate->format($dateFmt);
329         $endDateStr = $endDate->format($dateFmt);
330
331         $timeFmt = 'g:ia';
332
333         $startTimeStr = $startDate->format($timeFmt);
334         $endTimeStr = $endDate->format("{$timeFmt} (T)");
335
336         $out->elementStart('div', 'event-times'); // VEVENT/EVENT-TIMES IN
337
338         // TRANS: Field label for event description.
339         $out->element('strong', null, _m('Time:'));
340
341         $out->element('time', array('class' => 'dt-start',
342                                     'datetime' => common_date_iso8601($event->start_time)),
343                       $startDateStr . ' ' . $startTimeStr);
344         $out->text(' – ');
345         $out->element('time', array('class' => 'dt-end',
346                                     'datetime' => common_date_iso8601($event->end_time)),
347                       $startDateStr != $endDateStr
348                                     ? "$endDateStr $endTimeStr"
349                                     :  $endTimeStr);
350
351         $out->elementEnd('div'); // VEVENT/EVENT-TIMES OUT
352
353         if (!empty($event->location)) {
354             $out->elementStart('div', 'event-location');
355             // TRANS: Field label for event description.
356             $out->element('strong', null, _m('Location:'));
357             $out->element('span', 'p-location', $event->location);
358             $out->elementEnd('div');
359         }
360
361         if (!empty($event->description)) {
362             $out->elementStart('div', 'event-description');
363             // TRANS: Field label for event description.
364             $out->element('strong', null, _m('Description:'));
365             $out->element('div', 'p-description', $event->description);
366             $out->elementEnd('div');
367         }
368
369         $rsvps = $event->getRSVPs();
370
371         $out->elementStart('div', 'event-rsvps');
372
373         // TRANS: Field label for event description.
374         $out->element('strong', null, _m('Attending:'));
375         $out->elementStart('ul', 'attending-list');
376
377         foreach ($rsvps as $verb => $responses) {
378             $out->elementStart('li', 'rsvp-list');
379             switch ($verb) {
380             case RSVP::POSITIVE:
381                 $out->text(_('Yes:'));
382                 break;
383             case RSVP::NEGATIVE:
384                 $out->text(_('No:'));
385                 break;
386             case RSVP::POSSIBLE:
387                 $out->text(_('Maybe:'));
388                 break;
389             }
390             $ids = array();
391             foreach ($responses as $response) {
392                 $ids[] = $response->profile_id;
393             }
394             $ids = array_slice($ids, 0, ProfileMiniList::MAX_PROFILES + 1);
395             $minilist = new ProfileMiniList(Profile::multiGet('id', $ids), $out);
396             $minilist->show();
397
398             $out->elementEnd('li');
399         }
400
401         $out->elementEnd('ul');
402         $out->elementEnd('div');
403
404         if ($scoped instanceof Profile) {
405             $form = new RSVPForm($out, array('event'=>$event, 'scoped'=>$scoped));
406             $form->show();
407         }
408         $out->elementEnd('div');
409     }
410
411     protected function showRSVP(Notice $stored, HTMLOutputter $out, Profile $scoped=null)
412     {
413         try {
414             $rsvp = RSVP::fromStored($stored);
415         } catch (NoResultException $e) {
416             // TRANS: Content for a deleted RSVP list item (RSVP stands for "please respond").
417             $out->element('p', null, _m('Deleted.'));
418             return;
419         }
420
421         $out->elementStart('div', 'rsvp');
422         $out->raw($rsvp->asHTML());
423         $out->elementEnd('div');
424     }
425
426     function onEndPersonalGroupNav(Menu $menu, Profile $target, Profile $scoped=null)
427     {
428         $menu->menuItem(common_local_url('events', array('nickname' => $target->getNickname())),
429                           // TRANS: Menu item in sample plugin.
430                           _m('Happenings'),
431                           // TRANS: Menu item title in sample plugin.
432                           _m('A list of your events'), false, 'nav_timeline_events');
433         return true;
434     }
435 }