]> git.mxchange.org Git - quix0rs-gnu-social.git/blob - plugins/Event/EventPlugin.php
Removing unnecessary debug messages etc.
[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         $profile = $stored->getProfile();
293         $event   = Happening::fromStored($stored);
294
295         $out->elementStart('div', 'h-event');
296
297         $out->elementStart('h3', 'p-summary p-name');
298
299         try {
300             $out->element('a', array('href' => $event->getUrl()), $event->title);
301         } catch (InvalidUrlException $e) {
302             $out->text($event->title);
303         }
304
305         $out->elementEnd('h3');
306
307         $now       = new DateTime();
308         $startDate = new DateTime($event->start_time);
309         $endDate   = new DateTime($event->end_time);
310         $userTz    = new DateTimeZone(common_timezone());
311
312         // Localize the time for the observer
313         $now->setTimeZone($userTz);
314         $startDate->setTimezone($userTz);
315         $endDate->setTimezone($userTz);
316
317         $thisYear  = $now->format('Y');
318         $startYear = $startDate->format('Y');
319         $endYear   = $endDate->format('Y');
320
321         $dateFmt = 'D, F j, '; // e.g.: Mon, Aug 31
322
323         if ($startYear != $thisYear || $endYear != $thisYear) {
324             $dateFmt .= 'Y,'; // append year if we need to think about years
325         }
326
327         $startDateStr = $startDate->format($dateFmt);
328         $endDateStr = $endDate->format($dateFmt);
329
330         $timeFmt = 'g:ia';
331
332         $startTimeStr = $startDate->format($timeFmt);
333         $endTimeStr = $endDate->format("{$timeFmt} (T)");
334
335         $out->elementStart('div', 'event-times'); // VEVENT/EVENT-TIMES IN
336
337         // TRANS: Field label for event description.
338         $out->element('strong', null, _m('Time:'));
339
340         $out->element('time', array('class' => 'dt-start',
341                                     'datetime' => common_date_iso8601($event->start_time)),
342                       $startDateStr . ' ' . $startTimeStr);
343         $out->text(' – ');
344         $out->element('time', array('class' => 'dt-end',
345                                     'datetime' => common_date_iso8601($event->end_time)),
346                       $startDateStr != $endDateStr
347                                     ? "$endDateStr $endTimeStr"
348                                     :  $endTimeStr);
349
350         $out->elementEnd('div'); // VEVENT/EVENT-TIMES OUT
351
352         if (!empty($event->location)) {
353             $out->elementStart('div', 'event-location');
354             // TRANS: Field label for event description.
355             $out->element('strong', null, _m('Location:'));
356             $out->element('span', 'p-location', $event->location);
357             $out->elementEnd('div');
358         }
359
360         if (!empty($event->description)) {
361             $out->elementStart('div', 'event-description');
362             // TRANS: Field label for event description.
363             $out->element('strong', null, _m('Description:'));
364             $out->element('div', 'p-description', $event->description);
365             $out->elementEnd('div');
366         }
367
368         $rsvps = $event->getRSVPs();
369
370         $out->elementStart('div', 'event-rsvps');
371
372         // TRANS: Field label for event description.
373         $out->element('strong', null, _m('Attending:'));
374         $out->elementStart('ul', 'attending-list');
375
376         foreach ($rsvps as $verb => $responses) {
377             $out->elementStart('li', 'rsvp-list');
378             switch ($verb) {
379             case RSVP::POSITIVE:
380                 $out->text(_('Yes:'));
381                 break;
382             case RSVP::NEGATIVE:
383                 $out->text(_('No:'));
384                 break;
385             case RSVP::POSSIBLE:
386                 $out->text(_('Maybe:'));
387                 break;
388             }
389             $ids = array();
390             foreach ($responses as $response) {
391                 $ids[] = $response->profile_id;
392             }
393             $ids = array_slice($ids, 0, ProfileMiniList::MAX_PROFILES + 1);
394             $minilist = new ProfileMiniList(Profile::multiGet('id', $ids), $out);
395             $minilist->show();
396
397             $out->elementEnd('li');
398         }
399
400         $out->elementEnd('ul');
401         $out->elementEnd('div');
402
403         if ($scoped instanceof Profile) {
404             $form = new RSVPForm($out, array('event'=>$event, 'scoped'=>$scoped));
405             $form->show();
406         }
407         $out->elementEnd('div');
408     }
409
410     protected function showRSVP(Notice $stored, HTMLOutputter $out, Profile $scoped=null)
411     {
412         try {
413             $rsvp = RSVP::fromStored($stored);
414         } catch (NoResultException $e) {
415             // TRANS: Content for a deleted RSVP list item (RSVP stands for "please respond").
416             $out->element('p', null, _m('Deleted.'));
417             return;
418         }
419
420         $out->elementStart('div', 'rsvp');
421         $out->raw($rsvp->asHTML());
422         $out->elementEnd('div');
423     }
424
425     function onEndPersonalGroupNav(Menu $menu, Profile $target, Profile $scoped=null)
426     {
427         $menu->menuItem(common_local_url('events', array('nickname' => $target->getNickname())),
428                           // TRANS: Menu item in sample plugin.
429                           _m('Happenings'),
430                           // TRANS: Menu item title in sample plugin.
431                           _m('A list of your events'), false, 'nav_timeline_events');
432         return true;
433     }
434 }